From 0fb49c7656dfd6ad28a7b3138a6d56a04c23b6fb Mon Sep 17 00:00:00 2001 From: Julien Francioli Date: Tue, 19 Sep 2017 15:44:24 +0200 Subject: [PATCH 001/871] Initial empty repository NMODL Repo SHA: BlueBrain/nmodl@4d2f8456ae052beb6f93396c7a0d9c67c9c183f0 From d2a566d71a55d6d811addbb8fb9937d2b826cff6 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 6 Oct 2017 10:42:14 +0200 Subject: [PATCH 002/871] Bootstrapping project structure for NOCMODL Change-Id: I6403d39bb3d5e9aff627099887895fce7d3f30c7 NMODL Repo SHA: BlueBrain/nmodl@15a732e56f5528235adb60139c751d27be53421b --- .clang-format | 93 ++++++++++++++++++++++++++++++++ .gitreview | 6 +++ README.md | 12 +++++ cmake/nmodl/CMakeLists.txt | 36 +++++++++++++ cmake/nmodl/GitRevision.cmake | 30 +++++++++++ src/nmodl/main.cpp | 10 ++++ src/nmodl/nmodl/LICENSE | 16 ++++++ src/nmodl/version/version.cpp.in | 6 +++ src/nmodl/version/version.h | 10 ++++ 9 files changed, 219 insertions(+) create mode 100644 .clang-format create mode 100644 .gitreview create mode 100644 README.md create mode 100644 cmake/nmodl/CMakeLists.txt create mode 100644 cmake/nmodl/GitRevision.cmake create mode 100644 src/nmodl/main.cpp create mode 100644 src/nmodl/nmodl/LICENSE create mode 100644 src/nmodl/version/version.cpp.in create mode 100644 src/nmodl/version/version.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000000..d3599f260b --- /dev/null +++ b/.clang-format @@ -0,0 +1,93 @@ +--- +Language: Cpp +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: false +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 8 +UseTab: Never +JavaScriptQuotes: Leave +... + diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000000..054b6aa692 --- /dev/null +++ b/.gitreview @@ -0,0 +1,6 @@ +[gerrit] +host=bbpcode.epfl.ch +port=22 +project=incubator/nocmodl +defaultbranch=master +defaultremote=origin diff --git a/README.md b/README.md new file mode 100644 index 0000000000..da9df8e802 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +PROJECT_SOURCE_DIR## NOCMODL : Code Generation Framework + +This is prototype implementation of code generation framework for NMODL. + +### How To Build? + +``` +mkdir build +cd build +cmake .. +make +``` diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt new file mode 100644 index 0000000000..59771877f5 --- /dev/null +++ b/cmake/nmodl/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) + +project(nocmodl CXX) +set(PROJECT_VERSION_MAJOR 0) +set(PROJECT_VERSION_MINOR 1) + +set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +message(STATUS "CHECKING FOR FLEX/BISON/PYTHON") + +find_package(PythonInterp REQUIRED) +find_package(BISON REQUIRED) +find_package(FLEX REQUIRED) + +list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +include(GitRevision) + +include_directories(${PROJECT_SOURCE_DIR}/src) + +set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) + +# generate source file with version number +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in + ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) + +ADD_EXECUTABLE(${PROJECT_NAME} + src/main.cpp + ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) + +add_custom_target(clangformat + COMMAND + clang-format -i + --style=file ${FILES_FOR_CLANG_FORMAT}) diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake new file mode 100644 index 0000000000..b70ad2aa87 --- /dev/null +++ b/cmake/nmodl/GitRevision.cmake @@ -0,0 +1,30 @@ +# For now use simple approach to get version information as +# git is always avaialble on the machine where we are building + +find_package(Git) + +if(GIT_FOUND) + + # get last commit sha1 + execute_process(COMMAND + ${GIT_EXECUTABLE} log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + + # get last commit date + execute_process(COMMAND + ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_DATE + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + + # remove extra double quotes + string(REGEX REPLACE "\"" "" GIT_REVISION_DATE ${GIT_REVISION_DATE}) + set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") + +else() + + set(GIT_REVISION "unknown") + +endif() diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp new file mode 100644 index 0000000000..0ec75a8722 --- /dev/null +++ b/src/nmodl/main.cpp @@ -0,0 +1,10 @@ +#include +#include "version/version.h" + +using namespace nocmodl; + +int main(int argc, char* argv[]) { + + std::cout << " NOCMODL " << version::NOCMODL_VERSION << " [" << version::GIT_REVISION << "]" << std::endl; + +} diff --git a/src/nmodl/nmodl/LICENSE b/src/nmodl/nmodl/LICENSE new file mode 100644 index 0000000000..fb7f8d875c --- /dev/null +++ b/src/nmodl/nmodl/LICENSE @@ -0,0 +1,16 @@ +/********************************************************************************* + * + * Copyright (c) 2017, Blue Brain Project, EPFL. + * All rights reverved. + * + * Do not redistribute, copy or create a derivative work without prior permission. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *********************************************************************************/ diff --git a/src/nmodl/version/version.cpp.in b/src/nmodl/version/version.cpp.in new file mode 100644 index 0000000000..244db73770 --- /dev/null +++ b/src/nmodl/version/version.cpp.in @@ -0,0 +1,6 @@ +#include "version/version.h" + +using namespace nocmodl; + +const std::string version::GIT_REVISION = "@GIT_REVISION@"; +const std::string version::NOCMODL_VERSION = "@PROJECT_VERSION@"; diff --git a/src/nmodl/version/version.h b/src/nmodl/version/version.h new file mode 100644 index 0000000000..d429ec1a5f --- /dev/null +++ b/src/nmodl/version/version.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace nocmodl { + struct version { + static const std::string GIT_REVISION; + static const std::string NOCMODL_VERSION; + }; +} // namespace nocmodl From 52f9232c01f72665e4021048cda781312c4b3760 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <“pramod.kumbhar@epfl.ch”> Date: Mon, 9 Oct 2017 15:46:52 +0200 Subject: [PATCH 003/871] Backporting minimum files requires for lexer and parser: - note that these are files from original master branch but the pycleanup branch has divereged. So first need get that merged into master Change-Id: I96447e3f263e383206ad335479ba0a55c221d1be NMODL Repo SHA: BlueBrain/nmodl@0210f6f660ab2fb2d433371467db5633f74aa19b --- cmake/nmodl/CMakeLists.txt | 47 +- src/nmodl/ast/ast.cpp | 4948 ++++++++++++++++++++++++++++ src/nmodl/ast/ast.hpp | 3292 ++++++++++++++++++ src/nmodl/ast/astutils.hpp | 80 + src/nmodl/lexer/init.cpp | 436 +++ src/nmodl/lexer/init.hpp | 45 + src/nmodl/lexer/lexer_utils.hpp | 14 + src/nmodl/lexer/list.cpp | 49 + src/nmodl/lexer/list.hpp | 10 + src/nmodl/lexer/modl.h | 37 + src/nmodl/lexer/modtoken.hpp | 145 + src/nmodl/lexer/nmodl.l | 560 ++++ src/nmodl/parser/nmodl.y | 1133 +++++++ src/nmodl/parser/nmodl_context.hpp | 66 + src/nmodl/utils/commonutils.hpp | 9 + src/nmodl/utils/stringutils.hpp | 78 + src/nmodl/visitors/visitor.hpp | 138 + 17 files changed, 11084 insertions(+), 3 deletions(-) create mode 100644 src/nmodl/ast/ast.cpp create mode 100644 src/nmodl/ast/ast.hpp create mode 100644 src/nmodl/ast/astutils.hpp create mode 100644 src/nmodl/lexer/init.cpp create mode 100644 src/nmodl/lexer/init.hpp create mode 100644 src/nmodl/lexer/lexer_utils.hpp create mode 100644 src/nmodl/lexer/list.cpp create mode 100755 src/nmodl/lexer/list.hpp create mode 100644 src/nmodl/lexer/modl.h create mode 100644 src/nmodl/lexer/modtoken.hpp create mode 100755 src/nmodl/lexer/nmodl.l create mode 100644 src/nmodl/parser/nmodl.y create mode 100644 src/nmodl/parser/nmodl_context.hpp create mode 100755 src/nmodl/utils/commonutils.hpp create mode 100644 src/nmodl/utils/stringutils.hpp create mode 100644 src/nmodl/visitors/visitor.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 59771877f5..b5410bbd49 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -12,8 +12,8 @@ set(CMAKE_CXX_EXTENSIONS OFF) message(STATUS "CHECKING FOR FLEX/BISON/PYTHON") find_package(PythonInterp REQUIRED) -find_package(BISON REQUIRED) -find_package(FLEX REQUIRED) +find_package(BISON 3.0 REQUIRED) +find_package(FLEX 2.6 REQUIRED) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(GitRevision) @@ -26,7 +26,48 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) -ADD_EXECUTABLE(${PROJECT_NAME} +# command to generate lexer +add_custom_command( + COMMAND ${FLEX_EXECUTABLE} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl.l + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl_lexer.cpp + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl_lexer.hpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/lexer_utils.hpp + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/ + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl.l + COMMENT "-- NOCMODL : GENERATING NMODL LEXER WITH FLEX! --" +) + +# command to generate parser +add_custom_command( + COMMAND ${BISON_EXECUTABLE} + ARGS -d -o ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl.y + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl_parser.hpp + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl.y + COMMENT "-- NOCMODL : GENERATING NMODL PARSER WITH BISON! --" +) + +set(LEXER_SOURCE_FILES + src/lexer/init.cpp + src/lexer/list.cpp + src/lexer/nmodl_lexer.cpp + src/lexer/nmodl.l +) + +set(PARSER_SOURCE_FILES + src/parser/nmodl_parser.cpp + src/parser/nmodl.y + src/ast/astutils.hpp + src/ast/ast.hpp + src/ast/ast.cpp +) + + +add_executable(${PROJECT_NAME} + ${LEXER_SOURCE_FILES} + ${PARSER_SOURCE_FILES} src/main.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) diff --git a/src/nmodl/ast/ast.cpp b/src/nmodl/ast/ast.cpp new file mode 100644 index 0000000000..2cb8c3bade --- /dev/null +++ b/src/nmodl/ast/ast.cpp @@ -0,0 +1,4948 @@ +#include "ast/ast.hpp" +/* for node constructors, all children are taken as + * parameters, and must be passed in. Optional children + * may be NULL pointers. List children are pointers to + * std::vectors of the appropriate type (pointer to some + * node type) + */ + +namespace ast { + + /* visit Children method for Expression ast node */ + void ExpressionNode::visitChildren(Visitor* v) { + } + + /* destructor for Expression ast node */ + ExpressionNode::~ExpressionNode() { + } + + /* visit Children method for Statement ast node */ + void StatementNode::visitChildren(Visitor* v) { + } + + /* destructor for Statement ast node */ + StatementNode::~StatementNode() { + } + + /* visit Children method for Identifier ast node */ + void IdentifierNode::visitChildren(Visitor* v) { + } + + /* destructor for Identifier ast node */ + IdentifierNode::~IdentifierNode() { + } + + /* visit Children method for Block ast node */ + void BlockNode::visitChildren(Visitor* v) { + } + + /* destructor for Block ast node */ + BlockNode::~BlockNode() { + } + + /* visit Children method for Number ast node */ + void NumberNode::visitChildren(Visitor* v) { + } + + /* destructor for Number ast node */ + NumberNode::~NumberNode() { + } + + /* visit Children method for String ast node */ + void StringNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for String ast node */ + StringNode::StringNode(std::string value) { + this->value = value; + this->token = NULL; + } + + /* copy constructor for String ast node */ + StringNode::StringNode(const StringNode& obj) { + this->value = obj.value; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + } + + /* destructor for String ast node */ + StringNode::~StringNode() { + if(token) + delete token; + } + + /* visit Children method for Integer ast node */ + void IntegerNode::visitChildren(Visitor* v) { + /* no children */ + if (this->macroname) { + this->macroname->accept(v); + } + + } + + /* constructor for Integer ast node */ + IntegerNode::IntegerNode(int value, NameNode * macroname) { + this->value = value; + this->macroname = macroname; + this->token = NULL; + } + + /* copy constructor for Integer ast node */ + IntegerNode::IntegerNode(const IntegerNode& obj) { + this->value = obj.value; + if(obj.macroname) + this->macroname = obj.macroname->clone(); + else + this->macroname = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + } + + /* destructor for Integer ast node */ + IntegerNode::~IntegerNode() { + if(token) + delete token; + } + + /* visit Children method for Float ast node */ + void FloatNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for Float ast node */ + FloatNode::FloatNode(float value) { + this->value = value; + } + + /* copy constructor for Float ast node */ + FloatNode::FloatNode(const FloatNode& obj) { + this->value = obj.value; + } + + /* destructor for Float ast node */ + FloatNode::~FloatNode() { + } + + /* visit Children method for Double ast node */ + void DoubleNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for Double ast node */ + DoubleNode::DoubleNode(double value) { + this->value = value; + this->token = NULL; + } + + /* copy constructor for Double ast node */ + DoubleNode::DoubleNode(const DoubleNode& obj) { + this->value = obj.value; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + } + + /* destructor for Double ast node */ + DoubleNode::~DoubleNode() { + if(token) + delete token; + } + + /* visit Children method for Boolean ast node */ + void BooleanNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for Boolean ast node */ + BooleanNode::BooleanNode(int value) { + this->value = value; + } + + /* copy constructor for Boolean ast node */ + BooleanNode::BooleanNode(const BooleanNode& obj) { + this->value = obj.value; + } + + /* destructor for Boolean ast node */ + BooleanNode::~BooleanNode() { + } + + /* visit Children method for Name ast node */ + void NameNode::visitChildren(Visitor* v) { + value->accept(v); + } + + /* constructor for Name ast node */ + NameNode::NameNode(StringNode * value) { + this->value = value; + this->token = NULL; + } + + /* copy constructor for Name ast node */ + NameNode::NameNode(const NameNode& obj) { + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + } + + /* destructor for Name ast node */ + NameNode::~NameNode() { + if(value) + delete value; + if(token) + delete token; + } + + /* visit Children method for PrimeName ast node */ + void PrimeNameNode::visitChildren(Visitor* v) { + value->accept(v); + order->accept(v); + } + + /* constructor for PrimeName ast node */ + PrimeNameNode::PrimeNameNode(StringNode * value, IntegerNode * order) { + this->value = value; + this->order = order; + this->token = NULL; + } + + /* copy constructor for PrimeName ast node */ + PrimeNameNode::PrimeNameNode(const PrimeNameNode& obj) { + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + if(obj.order) + this->order = obj.order->clone(); + else + this->order = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + } + + /* destructor for PrimeName ast node */ + PrimeNameNode::~PrimeNameNode() { + if(value) + delete value; + if(order) + delete order; + if(token) + delete token; + } + + /* visit Children method for VarName ast node */ + void VarNameNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->at_index) { + this->at_index->accept(v); + } + + } + + /* constructor for VarName ast node */ + VarNameNode::VarNameNode(IdentifierNode * name, IntegerNode * at_index) { + this->name = name; + this->at_index = at_index; + } + + /* copy constructor for VarName ast node */ + VarNameNode::VarNameNode(const VarNameNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.at_index) + this->at_index = obj.at_index->clone(); + else + this->at_index = NULL; + } + + /* destructor for VarName ast node */ + VarNameNode::~VarNameNode() { + if(name) + delete name; + if(at_index) + delete at_index; + } + + /* visit Children method for IndexedName ast node */ + void IndexedNameNode::visitChildren(Visitor* v) { + name->accept(v); + index->accept(v); + } + + /* constructor for IndexedName ast node */ + IndexedNameNode::IndexedNameNode(IdentifierNode * name, ExpressionNode * index) { + this->name = name; + this->index = index; + } + + /* copy constructor for IndexedName ast node */ + IndexedNameNode::IndexedNameNode(const IndexedNameNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.index) + this->index = obj.index->clone(); + else + this->index = NULL; + } + + /* destructor for IndexedName ast node */ + IndexedNameNode::~IndexedNameNode() { + if(name) + delete name; + if(index) + delete index; + } + + /* visit Children method for Unit ast node */ + void UnitNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for Unit ast node */ + UnitNode::UnitNode(StringNode * name) { + this->name = name; + } + + /* copy constructor for Unit ast node */ + UnitNode::UnitNode(const UnitNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for Unit ast node */ + UnitNode::~UnitNode() { + if(name) + delete name; + } + + /* visit Children method for UnitState ast node */ + void UnitStateNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for UnitState ast node */ + UnitStateNode::UnitStateNode(UnitStateType value) { + this->value = value; + } + + /* copy constructor for UnitState ast node */ + UnitStateNode::UnitStateNode(const UnitStateNode& obj) { + this->value = obj.value; + } + + /* destructor for UnitState ast node */ + UnitStateNode::~UnitStateNode() { + } + + /* visit Children method for DoubleUnit ast node */ + void DoubleUnitNode::visitChildren(Visitor* v) { + values->accept(v); + if (this->unit) { + this->unit->accept(v); + } + + } + + /* constructor for DoubleUnit ast node */ + DoubleUnitNode::DoubleUnitNode(DoubleNode * values, UnitNode * unit) { + this->values = values; + this->unit = unit; + } + + /* copy constructor for DoubleUnit ast node */ + DoubleUnitNode::DoubleUnitNode(const DoubleUnitNode& obj) { + if(obj.values) + this->values = obj.values->clone(); + else + this->values = NULL; + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + } + + /* destructor for DoubleUnit ast node */ + DoubleUnitNode::~DoubleUnitNode() { + if(values) + delete values; + if(unit) + delete unit; + } + + /* visit Children method for Argument ast node */ + void ArgumentNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->unit) { + this->unit->accept(v); + } + + } + + /* constructor for Argument ast node */ + ArgumentNode::ArgumentNode(IdentifierNode * name, UnitNode * unit) { + this->name = name; + this->unit = unit; + } + + /* copy constructor for Argument ast node */ + ArgumentNode::ArgumentNode(const ArgumentNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + } + + /* destructor for Argument ast node */ + ArgumentNode::~ArgumentNode() { + if(name) + delete name; + if(unit) + delete unit; + } + + /* visit Children method for LocalVariable ast node */ + void LocalVariableNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for LocalVariable ast node */ + LocalVariableNode::LocalVariableNode(IdentifierNode * name) { + this->name = name; + } + + /* copy constructor for LocalVariable ast node */ + LocalVariableNode::LocalVariableNode(const LocalVariableNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for LocalVariable ast node */ + LocalVariableNode::~LocalVariableNode() { + if(name) + delete name; + } + + /* visit Children method for LocalListStatement ast node */ + void LocalListStatementNode::visitChildren(Visitor* v) { + if (this->variables) { + for(LocalVariableNodeList::iterator iter = this->variables->begin(); + iter != this->variables->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for LocalListStatement ast node */ + LocalListStatementNode::LocalListStatementNode(LocalVariableNodeList * variables) { + this->variables = variables; + } + + /* copy constructor for LocalListStatement ast node */ + LocalListStatementNode::LocalListStatementNode(const LocalListStatementNode& obj) { + // cloning list + if (obj.variables) { + this->variables = new LocalVariableNodeList(); + for(LocalVariableNodeList::iterator iter = obj.variables->begin(); + iter != obj.variables->end(); iter++) { + this->variables->push_back((*iter)->clone()); + } + } + else + this->variables = NULL; + + } + + /* destructor for LocalListStatement ast node */ + LocalListStatementNode::~LocalListStatementNode() { + if (variables) { + for(LocalVariableNodeList::iterator iter = this->variables->begin(); + iter != this->variables->end(); iter++) { + delete (*iter); + } + } + + if(variables) + delete variables; + } + + /* visit Children method for Limits ast node */ + void LimitsNode::visitChildren(Visitor* v) { + min->accept(v); + max->accept(v); + } + + /* constructor for Limits ast node */ + LimitsNode::LimitsNode(DoubleNode * min, DoubleNode * max) { + this->min = min; + this->max = max; + } + + /* copy constructor for Limits ast node */ + LimitsNode::LimitsNode(const LimitsNode& obj) { + if(obj.min) + this->min = obj.min->clone(); + else + this->min = NULL; + if(obj.max) + this->max = obj.max->clone(); + else + this->max = NULL; + } + + /* destructor for Limits ast node */ + LimitsNode::~LimitsNode() { + if(min) + delete min; + if(max) + delete max; + } + + /* visit Children method for NumberRange ast node */ + void NumberRangeNode::visitChildren(Visitor* v) { + min->accept(v); + max->accept(v); + } + + /* constructor for NumberRange ast node */ + NumberRangeNode::NumberRangeNode(NumberNode * min, NumberNode * max) { + this->min = min; + this->max = max; + } + + /* copy constructor for NumberRange ast node */ + NumberRangeNode::NumberRangeNode(const NumberRangeNode& obj) { + if(obj.min) + this->min = obj.min->clone(); + else + this->min = NULL; + if(obj.max) + this->max = obj.max->clone(); + else + this->max = NULL; + } + + /* destructor for NumberRange ast node */ + NumberRangeNode::~NumberRangeNode() { + if(min) + delete min; + if(max) + delete max; + } + + /* visit Children method for Program ast node */ + void ProgramNode::visitChildren(Visitor* v) { + if (this->statements) { + for(StatementNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->blocks) { + for(BlockNodeList::iterator iter = this->blocks->begin(); + iter != this->blocks->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for Program ast node */ + ProgramNode::ProgramNode(StatementNodeList * statements, BlockNodeList * blocks) { + this->statements = statements; + this->blocks = blocks; + this->symtab = NULL; + } + + /* copy constructor for Program ast node */ + ProgramNode::ProgramNode(const ProgramNode& obj) { + // cloning list + if (obj.statements) { + this->statements = new StatementNodeList(); + for(StatementNodeList::iterator iter = obj.statements->begin(); + iter != obj.statements->end(); iter++) { + this->statements->push_back((*iter)->clone()); + } + } + else + this->statements = NULL; + + // cloning list + if (obj.blocks) { + this->blocks = new BlockNodeList(); + for(BlockNodeList::iterator iter = obj.blocks->begin(); + iter != obj.blocks->end(); iter++) { + this->blocks->push_back((*iter)->clone()); + } + } + else + this->blocks = NULL; + + this->symtab = NULL; + } + + /* destructor for Program ast node */ + ProgramNode::~ProgramNode() { + if (statements) { + for(StatementNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + delete (*iter); + } + } + + if(statements) + delete statements; + if (blocks) { + for(BlockNodeList::iterator iter = this->blocks->begin(); + iter != this->blocks->end(); iter++) { + delete (*iter); + } + } + + if(blocks) + delete blocks; + } + + /* visit Children method for Model ast node */ + void ModelNode::visitChildren(Visitor* v) { + title->accept(v); + } + + /* constructor for Model ast node */ + ModelNode::ModelNode(StringNode * title) { + this->title = title; + } + + /* copy constructor for Model ast node */ + ModelNode::ModelNode(const ModelNode& obj) { + if(obj.title) + this->title = obj.title->clone(); + else + this->title = NULL; + } + + /* destructor for Model ast node */ + ModelNode::~ModelNode() { + if(title) + delete title; + } + + /* visit Children method for Define ast node */ + void DefineNode::visitChildren(Visitor* v) { + name->accept(v); + value->accept(v); + } + + /* constructor for Define ast node */ + DefineNode::DefineNode(NameNode * name, IntegerNode * value) { + this->name = name; + this->value = value; + } + + /* copy constructor for Define ast node */ + DefineNode::DefineNode(const DefineNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + } + + /* destructor for Define ast node */ + DefineNode::~DefineNode() { + if(name) + delete name; + if(value) + delete value; + } + + /* visit Children method for Include ast node */ + void IncludeNode::visitChildren(Visitor* v) { + filename->accept(v); + } + + /* constructor for Include ast node */ + IncludeNode::IncludeNode(StringNode * filename) { + this->filename = filename; + } + + /* copy constructor for Include ast node */ + IncludeNode::IncludeNode(const IncludeNode& obj) { + if(obj.filename) + this->filename = obj.filename->clone(); + else + this->filename = NULL; + } + + /* destructor for Include ast node */ + IncludeNode::~IncludeNode() { + if(filename) + delete filename; + } + + /* visit Children method for ParamBlock ast node */ + void ParamBlockNode::visitChildren(Visitor* v) { + if (this->statements) { + for(ParamAssignNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for ParamBlock ast node */ + ParamBlockNode::ParamBlockNode(ParamAssignNodeList * statements) { + this->statements = statements; + this->symtab = NULL; + } + + /* copy constructor for ParamBlock ast node */ + ParamBlockNode::ParamBlockNode(const ParamBlockNode& obj) { + // cloning list + if (obj.statements) { + this->statements = new ParamAssignNodeList(); + for(ParamAssignNodeList::iterator iter = obj.statements->begin(); + iter != obj.statements->end(); iter++) { + this->statements->push_back((*iter)->clone()); + } + } + else + this->statements = NULL; + + this->symtab = NULL; + } + + /* destructor for ParamBlock ast node */ + ParamBlockNode::~ParamBlockNode() { + if (statements) { + for(ParamAssignNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + delete (*iter); + } + } + + if(statements) + delete statements; + } + + /* visit Children method for ParamAssign ast node */ + void ParamAssignNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->value) { + this->value->accept(v); + } + + if (this->unit) { + this->unit->accept(v); + } + + if (this->limit) { + this->limit->accept(v); + } + + } + + /* constructor for ParamAssign ast node */ + ParamAssignNode::ParamAssignNode(IdentifierNode * name, NumberNode * value, UnitNode * unit, LimitsNode * limit) { + this->name = name; + this->value = value; + this->unit = unit; + this->limit = limit; + } + + /* copy constructor for ParamAssign ast node */ + ParamAssignNode::ParamAssignNode(const ParamAssignNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + if(obj.limit) + this->limit = obj.limit->clone(); + else + this->limit = NULL; + } + + /* destructor for ParamAssign ast node */ + ParamAssignNode::~ParamAssignNode() { + if(name) + delete name; + if(value) + delete value; + if(unit) + delete unit; + if(limit) + delete limit; + } + + /* visit Children method for StepBlock ast node */ + void StepBlockNode::visitChildren(Visitor* v) { + if (this->statements) { + for(SteppedNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for StepBlock ast node */ + StepBlockNode::StepBlockNode(SteppedNodeList * statements) { + this->statements = statements; + this->symtab = NULL; + } + + /* copy constructor for StepBlock ast node */ + StepBlockNode::StepBlockNode(const StepBlockNode& obj) { + // cloning list + if (obj.statements) { + this->statements = new SteppedNodeList(); + for(SteppedNodeList::iterator iter = obj.statements->begin(); + iter != obj.statements->end(); iter++) { + this->statements->push_back((*iter)->clone()); + } + } + else + this->statements = NULL; + + this->symtab = NULL; + } + + /* destructor for StepBlock ast node */ + StepBlockNode::~StepBlockNode() { + if (statements) { + for(SteppedNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + delete (*iter); + } + } + + if(statements) + delete statements; + } + + /* visit Children method for Stepped ast node */ + void SteppedNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->values) { + for(NumberNodeList::iterator iter = this->values->begin(); + iter != this->values->end(); iter++) { + (*iter)->accept(v); + } + } + + unit->accept(v); + } + + /* constructor for Stepped ast node */ + SteppedNode::SteppedNode(NameNode * name, NumberNodeList * values, UnitNode * unit) { + this->name = name; + this->values = values; + this->unit = unit; + } + + /* copy constructor for Stepped ast node */ + SteppedNode::SteppedNode(const SteppedNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.values) { + this->values = new NumberNodeList(); + for(NumberNodeList::iterator iter = obj.values->begin(); + iter != obj.values->end(); iter++) { + this->values->push_back((*iter)->clone()); + } + } + else + this->values = NULL; + + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + } + + /* destructor for Stepped ast node */ + SteppedNode::~SteppedNode() { + if(name) + delete name; + if (values) { + for(NumberNodeList::iterator iter = this->values->begin(); + iter != this->values->end(); iter++) { + delete (*iter); + } + } + + if(values) + delete values; + if(unit) + delete unit; + } + + /* visit Children method for IndependentBlock ast node */ + void IndependentBlockNode::visitChildren(Visitor* v) { + if (this->definitions) { + for(IndependentDefNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for IndependentBlock ast node */ + IndependentBlockNode::IndependentBlockNode(IndependentDefNodeList * definitions) { + this->definitions = definitions; + this->symtab = NULL; + } + + /* copy constructor for IndependentBlock ast node */ + IndependentBlockNode::IndependentBlockNode(const IndependentBlockNode& obj) { + // cloning list + if (obj.definitions) { + this->definitions = new IndependentDefNodeList(); + for(IndependentDefNodeList::iterator iter = obj.definitions->begin(); + iter != obj.definitions->end(); iter++) { + this->definitions->push_back((*iter)->clone()); + } + } + else + this->definitions = NULL; + + this->symtab = NULL; + } + + /* destructor for IndependentBlock ast node */ + IndependentBlockNode::~IndependentBlockNode() { + if (definitions) { + for(IndependentDefNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + delete (*iter); + } + } + + if(definitions) + delete definitions; + } + + /* visit Children method for IndependentDef ast node */ + void IndependentDefNode::visitChildren(Visitor* v) { + if (this->sweep) { + this->sweep->accept(v); + } + + name->accept(v); + from->accept(v); + to->accept(v); + with->accept(v); + if (this->opstart) { + this->opstart->accept(v); + } + + unit->accept(v); + } + + /* constructor for IndependentDef ast node */ + IndependentDefNode::IndependentDefNode(BooleanNode * sweep, NameNode * name, NumberNode * from, NumberNode * to, IntegerNode * with, NumberNode * opstart, UnitNode * unit) { + this->sweep = sweep; + this->name = name; + this->from = from; + this->to = to; + this->with = with; + this->opstart = opstart; + this->unit = unit; + } + + /* copy constructor for IndependentDef ast node */ + IndependentDefNode::IndependentDefNode(const IndependentDefNode& obj) { + if(obj.sweep) + this->sweep = obj.sweep->clone(); + else + this->sweep = NULL; + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.from) + this->from = obj.from->clone(); + else + this->from = NULL; + if(obj.to) + this->to = obj.to->clone(); + else + this->to = NULL; + if(obj.with) + this->with = obj.with->clone(); + else + this->with = NULL; + if(obj.opstart) + this->opstart = obj.opstart->clone(); + else + this->opstart = NULL; + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + } + + /* destructor for IndependentDef ast node */ + IndependentDefNode::~IndependentDefNode() { + if(sweep) + delete sweep; + if(name) + delete name; + if(from) + delete from; + if(to) + delete to; + if(with) + delete with; + if(opstart) + delete opstart; + if(unit) + delete unit; + } + + /* visit Children method for DependentDef ast node */ + void DependentDefNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->index) { + this->index->accept(v); + } + + if (this->from) { + this->from->accept(v); + } + + if (this->to) { + this->to->accept(v); + } + + if (this->opstart) { + this->opstart->accept(v); + } + + if (this->unit) { + this->unit->accept(v); + } + + if (this->abstol) { + this->abstol->accept(v); + } + + } + + /* constructor for DependentDef ast node */ + DependentDefNode::DependentDefNode(IdentifierNode * name, IntegerNode * index, NumberNode * from, NumberNode * to, NumberNode * opstart, UnitNode * unit, DoubleNode * abstol) { + this->name = name; + this->index = index; + this->from = from; + this->to = to; + this->opstart = opstart; + this->unit = unit; + this->abstol = abstol; + } + + /* copy constructor for DependentDef ast node */ + DependentDefNode::DependentDefNode(const DependentDefNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.index) + this->index = obj.index->clone(); + else + this->index = NULL; + if(obj.from) + this->from = obj.from->clone(); + else + this->from = NULL; + if(obj.to) + this->to = obj.to->clone(); + else + this->to = NULL; + if(obj.opstart) + this->opstart = obj.opstart->clone(); + else + this->opstart = NULL; + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + if(obj.abstol) + this->abstol = obj.abstol->clone(); + else + this->abstol = NULL; + } + + /* destructor for DependentDef ast node */ + DependentDefNode::~DependentDefNode() { + if(name) + delete name; + if(index) + delete index; + if(from) + delete from; + if(to) + delete to; + if(opstart) + delete opstart; + if(unit) + delete unit; + if(abstol) + delete abstol; + } + + /* visit Children method for DependentBlock ast node */ + void DependentBlockNode::visitChildren(Visitor* v) { + if (this->definitions) { + for(DependentDefNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for DependentBlock ast node */ + DependentBlockNode::DependentBlockNode(DependentDefNodeList * definitions) { + this->definitions = definitions; + this->symtab = NULL; + } + + /* copy constructor for DependentBlock ast node */ + DependentBlockNode::DependentBlockNode(const DependentBlockNode& obj) { + // cloning list + if (obj.definitions) { + this->definitions = new DependentDefNodeList(); + for(DependentDefNodeList::iterator iter = obj.definitions->begin(); + iter != obj.definitions->end(); iter++) { + this->definitions->push_back((*iter)->clone()); + } + } + else + this->definitions = NULL; + + this->symtab = NULL; + } + + /* destructor for DependentBlock ast node */ + DependentBlockNode::~DependentBlockNode() { + if (definitions) { + for(DependentDefNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + delete (*iter); + } + } + + if(definitions) + delete definitions; + } + + /* visit Children method for StateBlock ast node */ + void StateBlockNode::visitChildren(Visitor* v) { + if (this->definitions) { + for(DependentDefNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for StateBlock ast node */ + StateBlockNode::StateBlockNode(DependentDefNodeList * definitions) { + this->definitions = definitions; + this->symtab = NULL; + } + + /* copy constructor for StateBlock ast node */ + StateBlockNode::StateBlockNode(const StateBlockNode& obj) { + // cloning list + if (obj.definitions) { + this->definitions = new DependentDefNodeList(); + for(DependentDefNodeList::iterator iter = obj.definitions->begin(); + iter != obj.definitions->end(); iter++) { + this->definitions->push_back((*iter)->clone()); + } + } + else + this->definitions = NULL; + + this->symtab = NULL; + } + + /* destructor for StateBlock ast node */ + StateBlockNode::~StateBlockNode() { + if (definitions) { + for(DependentDefNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + delete (*iter); + } + } + + if(definitions) + delete definitions; + } + + /* visit Children method for PlotBlock ast node */ + void PlotBlockNode::visitChildren(Visitor* v) { + plot->accept(v); + } + + /* constructor for PlotBlock ast node */ + PlotBlockNode::PlotBlockNode(PlotDeclarationNode * plot) { + this->plot = plot; + this->symtab = NULL; + } + + /* copy constructor for PlotBlock ast node */ + PlotBlockNode::PlotBlockNode(const PlotBlockNode& obj) { + if(obj.plot) + this->plot = obj.plot->clone(); + else + this->plot = NULL; + this->symtab = NULL; + } + + /* destructor for PlotBlock ast node */ + PlotBlockNode::~PlotBlockNode() { + if(plot) + delete plot; + } + + /* visit Children method for PlotDeclaration ast node */ + void PlotDeclarationNode::visitChildren(Visitor* v) { + if (this->pvlist) { + for(PlotVariableNodeList::iterator iter = this->pvlist->begin(); + iter != this->pvlist->end(); iter++) { + (*iter)->accept(v); + } + } + + name->accept(v); + } + + /* constructor for PlotDeclaration ast node */ + PlotDeclarationNode::PlotDeclarationNode(PlotVariableNodeList * pvlist, PlotVariableNode * name) { + this->pvlist = pvlist; + this->name = name; + } + + /* copy constructor for PlotDeclaration ast node */ + PlotDeclarationNode::PlotDeclarationNode(const PlotDeclarationNode& obj) { + // cloning list + if (obj.pvlist) { + this->pvlist = new PlotVariableNodeList(); + for(PlotVariableNodeList::iterator iter = obj.pvlist->begin(); + iter != obj.pvlist->end(); iter++) { + this->pvlist->push_back((*iter)->clone()); + } + } + else + this->pvlist = NULL; + + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for PlotDeclaration ast node */ + PlotDeclarationNode::~PlotDeclarationNode() { + if (pvlist) { + for(PlotVariableNodeList::iterator iter = this->pvlist->begin(); + iter != this->pvlist->end(); iter++) { + delete (*iter); + } + } + + if(pvlist) + delete pvlist; + if(name) + delete name; + } + + /* visit Children method for PlotVariable ast node */ + void PlotVariableNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->index) { + this->index->accept(v); + } + + } + + /* constructor for PlotVariable ast node */ + PlotVariableNode::PlotVariableNode(IdentifierNode * name, IntegerNode * index) { + this->name = name; + this->index = index; + } + + /* copy constructor for PlotVariable ast node */ + PlotVariableNode::PlotVariableNode(const PlotVariableNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.index) + this->index = obj.index->clone(); + else + this->index = NULL; + } + + /* destructor for PlotVariable ast node */ + PlotVariableNode::~PlotVariableNode() { + if(name) + delete name; + if(index) + delete index; + } + + /* visit Children method for InitialBlock ast node */ + void InitialBlockNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for InitialBlock ast node */ + InitialBlockNode::InitialBlockNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for InitialBlock ast node */ + InitialBlockNode::InitialBlockNode(const InitialBlockNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for InitialBlock ast node */ + InitialBlockNode::~InitialBlockNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for ConstructorBlock ast node */ + void ConstructorBlockNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for ConstructorBlock ast node */ + ConstructorBlockNode::ConstructorBlockNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for ConstructorBlock ast node */ + ConstructorBlockNode::ConstructorBlockNode(const ConstructorBlockNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for ConstructorBlock ast node */ + ConstructorBlockNode::~ConstructorBlockNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for DestructorBlock ast node */ + void DestructorBlockNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for DestructorBlock ast node */ + DestructorBlockNode::DestructorBlockNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for DestructorBlock ast node */ + DestructorBlockNode::DestructorBlockNode(const DestructorBlockNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for DestructorBlock ast node */ + DestructorBlockNode::~DestructorBlockNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for ConductanceHint ast node */ + void ConductanceHintNode::visitChildren(Visitor* v) { + conductance->accept(v); + if (this->ion) { + this->ion->accept(v); + } + + } + + /* constructor for ConductanceHint ast node */ + ConductanceHintNode::ConductanceHintNode(NameNode * conductance, NameNode * ion) { + this->conductance = conductance; + this->ion = ion; + } + + /* copy constructor for ConductanceHint ast node */ + ConductanceHintNode::ConductanceHintNode(const ConductanceHintNode& obj) { + if(obj.conductance) + this->conductance = obj.conductance->clone(); + else + this->conductance = NULL; + if(obj.ion) + this->ion = obj.ion->clone(); + else + this->ion = NULL; + } + + /* destructor for ConductanceHint ast node */ + ConductanceHintNode::~ConductanceHintNode() { + if(conductance) + delete conductance; + if(ion) + delete ion; + } + + /* visit Children method for ExpressionStatement ast node */ + void ExpressionStatementNode::visitChildren(Visitor* v) { + expression->accept(v); + } + + /* constructor for ExpressionStatement ast node */ + ExpressionStatementNode::ExpressionStatementNode(ExpressionNode * expression) { + this->expression = expression; + } + + /* copy constructor for ExpressionStatement ast node */ + ExpressionStatementNode::ExpressionStatementNode(const ExpressionStatementNode& obj) { + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + } + + /* destructor for ExpressionStatement ast node */ + ExpressionStatementNode::~ExpressionStatementNode() { + if(expression) + delete expression; + } + + /* visit Children method for ProtectStatement ast node */ + void ProtectStatementNode::visitChildren(Visitor* v) { + expression->accept(v); + } + + /* constructor for ProtectStatement ast node */ + ProtectStatementNode::ProtectStatementNode(ExpressionNode * expression) { + this->expression = expression; + } + + /* copy constructor for ProtectStatement ast node */ + ProtectStatementNode::ProtectStatementNode(const ProtectStatementNode& obj) { + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + } + + /* destructor for ProtectStatement ast node */ + ProtectStatementNode::~ProtectStatementNode() { + if(expression) + delete expression; + } + + /* visit Children method for StatementBlock ast node */ + void StatementBlockNode::visitChildren(Visitor* v) { + if (this->statements) { + for(StatementNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for StatementBlock ast node */ + StatementBlockNode::StatementBlockNode(StatementNodeList * statements) { + this->statements = statements; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for StatementBlock ast node */ + StatementBlockNode::StatementBlockNode(const StatementBlockNode& obj) { + // cloning list + if (obj.statements) { + this->statements = new StatementNodeList(); + for(StatementNodeList::iterator iter = obj.statements->begin(); + iter != obj.statements->end(); iter++) { + this->statements->push_back((*iter)->clone()); + } + } + else + this->statements = NULL; + + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for StatementBlock ast node */ + StatementBlockNode::~StatementBlockNode() { + if (statements) { + for(StatementNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + delete (*iter); + } + } + + if(statements) + delete statements; + if(token) + delete token; + } + + /* visit Children method for BinaryOperator ast node */ + void BinaryOperatorNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for BinaryOperator ast node */ + BinaryOperatorNode::BinaryOperatorNode(BinaryOp value) { + this->value = value; + } + + /* copy constructor for BinaryOperator ast node */ + BinaryOperatorNode::BinaryOperatorNode(const BinaryOperatorNode& obj) { + this->value = obj.value; + } + + /* destructor for BinaryOperator ast node */ + BinaryOperatorNode::~BinaryOperatorNode() { + } + + /* visit Children method for UnaryOperator ast node */ + void UnaryOperatorNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for UnaryOperator ast node */ + UnaryOperatorNode::UnaryOperatorNode(UnaryOp value) { + this->value = value; + } + + /* copy constructor for UnaryOperator ast node */ + UnaryOperatorNode::UnaryOperatorNode(const UnaryOperatorNode& obj) { + this->value = obj.value; + } + + /* destructor for UnaryOperator ast node */ + UnaryOperatorNode::~UnaryOperatorNode() { + } + + /* visit Children method for ReactionOperator ast node */ + void ReactionOperatorNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for ReactionOperator ast node */ + ReactionOperatorNode::ReactionOperatorNode(ReactionOp value) { + this->value = value; + } + + /* copy constructor for ReactionOperator ast node */ + ReactionOperatorNode::ReactionOperatorNode(const ReactionOperatorNode& obj) { + this->value = obj.value; + } + + /* destructor for ReactionOperator ast node */ + ReactionOperatorNode::~ReactionOperatorNode() { + } + + /* visit Children method for BinaryExpression ast node */ + void BinaryExpressionNode::visitChildren(Visitor* v) { + lhs->accept(v); + op->accept(v); + rhs->accept(v); + } + + /* constructor for BinaryExpression ast node */ + BinaryExpressionNode::BinaryExpressionNode(ExpressionNode * lhs, BinaryOperatorNode * op, ExpressionNode * rhs) { + this->lhs = lhs; + this->op = op; + this->rhs = rhs; + } + + /* copy constructor for BinaryExpression ast node */ + BinaryExpressionNode::BinaryExpressionNode(const BinaryExpressionNode& obj) { + if(obj.lhs) + this->lhs = obj.lhs->clone(); + else + this->lhs = NULL; + if(obj.op) + this->op = obj.op->clone(); + else + this->op = NULL; + if(obj.rhs) + this->rhs = obj.rhs->clone(); + else + this->rhs = NULL; + } + + /* destructor for BinaryExpression ast node */ + BinaryExpressionNode::~BinaryExpressionNode() { + if(lhs) + delete lhs; + if(op) + delete op; + if(rhs) + delete rhs; + } + + /* visit Children method for UnaryExpression ast node */ + void UnaryExpressionNode::visitChildren(Visitor* v) { + op->accept(v); + expression->accept(v); + } + + /* constructor for UnaryExpression ast node */ + UnaryExpressionNode::UnaryExpressionNode(UnaryOperatorNode * op, ExpressionNode * expression) { + this->op = op; + this->expression = expression; + } + + /* copy constructor for UnaryExpression ast node */ + UnaryExpressionNode::UnaryExpressionNode(const UnaryExpressionNode& obj) { + if(obj.op) + this->op = obj.op->clone(); + else + this->op = NULL; + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + } + + /* destructor for UnaryExpression ast node */ + UnaryExpressionNode::~UnaryExpressionNode() { + if(op) + delete op; + if(expression) + delete expression; + } + + /* visit Children method for NonLinEuation ast node */ + void NonLinEuationNode::visitChildren(Visitor* v) { + lhs->accept(v); + rhs->accept(v); + } + + /* constructor for NonLinEuation ast node */ + NonLinEuationNode::NonLinEuationNode(ExpressionNode * lhs, ExpressionNode * rhs) { + this->lhs = lhs; + this->rhs = rhs; + } + + /* copy constructor for NonLinEuation ast node */ + NonLinEuationNode::NonLinEuationNode(const NonLinEuationNode& obj) { + if(obj.lhs) + this->lhs = obj.lhs->clone(); + else + this->lhs = NULL; + if(obj.rhs) + this->rhs = obj.rhs->clone(); + else + this->rhs = NULL; + } + + /* destructor for NonLinEuation ast node */ + NonLinEuationNode::~NonLinEuationNode() { + if(lhs) + delete lhs; + if(rhs) + delete rhs; + } + + /* visit Children method for LinEquation ast node */ + void LinEquationNode::visitChildren(Visitor* v) { + leftlinexpr->accept(v); + linexpr->accept(v); + } + + /* constructor for LinEquation ast node */ + LinEquationNode::LinEquationNode(ExpressionNode * leftlinexpr, ExpressionNode * linexpr) { + this->leftlinexpr = leftlinexpr; + this->linexpr = linexpr; + } + + /* copy constructor for LinEquation ast node */ + LinEquationNode::LinEquationNode(const LinEquationNode& obj) { + if(obj.leftlinexpr) + this->leftlinexpr = obj.leftlinexpr->clone(); + else + this->leftlinexpr = NULL; + if(obj.linexpr) + this->linexpr = obj.linexpr->clone(); + else + this->linexpr = NULL; + } + + /* destructor for LinEquation ast node */ + LinEquationNode::~LinEquationNode() { + if(leftlinexpr) + delete leftlinexpr; + if(linexpr) + delete linexpr; + } + + /* visit Children method for FunctionCall ast node */ + void FunctionCallNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->arguments) { + for(ExpressionNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for FunctionCall ast node */ + FunctionCallNode::FunctionCallNode(NameNode * name, ExpressionNodeList * arguments) { + this->name = name; + this->arguments = arguments; + } + + /* copy constructor for FunctionCall ast node */ + FunctionCallNode::FunctionCallNode(const FunctionCallNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.arguments) { + this->arguments = new ExpressionNodeList(); + for(ExpressionNodeList::iterator iter = obj.arguments->begin(); + iter != obj.arguments->end(); iter++) { + this->arguments->push_back((*iter)->clone()); + } + } + else + this->arguments = NULL; + + } + + /* destructor for FunctionCall ast node */ + FunctionCallNode::~FunctionCallNode() { + if(name) + delete name; + if (arguments) { + for(ExpressionNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + delete (*iter); + } + } + + if(arguments) + delete arguments; + } + + /* visit Children method for FromStatement ast node */ + void FromStatementNode::visitChildren(Visitor* v) { + name->accept(v); + from->accept(v); + to->accept(v); + if (this->opinc) { + this->opinc->accept(v); + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for FromStatement ast node */ + FromStatementNode::FromStatementNode(NameNode * name, ExpressionNode * from, ExpressionNode * to, ExpressionNode * opinc, StatementBlockNode * statementblock) { + this->name = name; + this->from = from; + this->to = to; + this->opinc = opinc; + this->statementblock = statementblock; + } + + /* copy constructor for FromStatement ast node */ + FromStatementNode::FromStatementNode(const FromStatementNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.from) + this->from = obj.from->clone(); + else + this->from = NULL; + if(obj.to) + this->to = obj.to->clone(); + else + this->to = NULL; + if(obj.opinc) + this->opinc = obj.opinc->clone(); + else + this->opinc = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + } + + /* destructor for FromStatement ast node */ + FromStatementNode::~FromStatementNode() { + if(name) + delete name; + if(from) + delete from; + if(to) + delete to; + if(opinc) + delete opinc; + if(statementblock) + delete statementblock; + } + + /* visit Children method for ForAllStatement ast node */ + void ForAllStatementNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for ForAllStatement ast node */ + ForAllStatementNode::ForAllStatementNode(NameNode * name, StatementBlockNode * statementblock) { + this->name = name; + this->statementblock = statementblock; + } + + /* copy constructor for ForAllStatement ast node */ + ForAllStatementNode::ForAllStatementNode(const ForAllStatementNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + } + + /* destructor for ForAllStatement ast node */ + ForAllStatementNode::~ForAllStatementNode() { + if(name) + delete name; + if(statementblock) + delete statementblock; + } + + /* visit Children method for WhileStatement ast node */ + void WhileStatementNode::visitChildren(Visitor* v) { + condition->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for WhileStatement ast node */ + WhileStatementNode::WhileStatementNode(ExpressionNode * condition, StatementBlockNode * statementblock) { + this->condition = condition; + this->statementblock = statementblock; + } + + /* copy constructor for WhileStatement ast node */ + WhileStatementNode::WhileStatementNode(const WhileStatementNode& obj) { + if(obj.condition) + this->condition = obj.condition->clone(); + else + this->condition = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + } + + /* destructor for WhileStatement ast node */ + WhileStatementNode::~WhileStatementNode() { + if(condition) + delete condition; + if(statementblock) + delete statementblock; + } + + /* visit Children method for IfStatement ast node */ + void IfStatementNode::visitChildren(Visitor* v) { + condition->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + if (this->elseifs) { + for(ElseIfStatementNodeList::iterator iter = this->elseifs->begin(); + iter != this->elseifs->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->elses) { + this->elses->accept(v); + } + + } + + /* constructor for IfStatement ast node */ + IfStatementNode::IfStatementNode(ExpressionNode * condition, StatementBlockNode * statementblock, ElseIfStatementNodeList * elseifs, ElseStatementNode * elses) { + this->condition = condition; + this->statementblock = statementblock; + this->elseifs = elseifs; + this->elses = elses; + } + + /* copy constructor for IfStatement ast node */ + IfStatementNode::IfStatementNode(const IfStatementNode& obj) { + if(obj.condition) + this->condition = obj.condition->clone(); + else + this->condition = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + // cloning list + if (obj.elseifs) { + this->elseifs = new ElseIfStatementNodeList(); + for(ElseIfStatementNodeList::iterator iter = obj.elseifs->begin(); + iter != obj.elseifs->end(); iter++) { + this->elseifs->push_back((*iter)->clone()); + } + } + else + this->elseifs = NULL; + + if(obj.elses) + this->elses = obj.elses->clone(); + else + this->elses = NULL; + } + + /* destructor for IfStatement ast node */ + IfStatementNode::~IfStatementNode() { + if(condition) + delete condition; + if(statementblock) + delete statementblock; + if (elseifs) { + for(ElseIfStatementNodeList::iterator iter = this->elseifs->begin(); + iter != this->elseifs->end(); iter++) { + delete (*iter); + } + } + + if(elseifs) + delete elseifs; + if(elses) + delete elses; + } + + /* visit Children method for ElseIfStatement ast node */ + void ElseIfStatementNode::visitChildren(Visitor* v) { + condition->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for ElseIfStatement ast node */ + ElseIfStatementNode::ElseIfStatementNode(ExpressionNode * condition, StatementBlockNode * statementblock) { + this->condition = condition; + this->statementblock = statementblock; + } + + /* copy constructor for ElseIfStatement ast node */ + ElseIfStatementNode::ElseIfStatementNode(const ElseIfStatementNode& obj) { + if(obj.condition) + this->condition = obj.condition->clone(); + else + this->condition = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + } + + /* destructor for ElseIfStatement ast node */ + ElseIfStatementNode::~ElseIfStatementNode() { + if(condition) + delete condition; + if(statementblock) + delete statementblock; + } + + /* visit Children method for ElseStatement ast node */ + void ElseStatementNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for ElseStatement ast node */ + ElseStatementNode::ElseStatementNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + } + + /* copy constructor for ElseStatement ast node */ + ElseStatementNode::ElseStatementNode(const ElseStatementNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + } + + /* destructor for ElseStatement ast node */ + ElseStatementNode::~ElseStatementNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for DerivativeBlock ast node */ + void DerivativeBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for DerivativeBlock ast node */ + DerivativeBlockNode::DerivativeBlockNode(NameNode * name, StatementBlockNode * statementblock) { + this->name = name; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for DerivativeBlock ast node */ + DerivativeBlockNode::DerivativeBlockNode(const DerivativeBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for DerivativeBlock ast node */ + DerivativeBlockNode::~DerivativeBlockNode() { + if(name) + delete name; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for LinearBlock ast node */ + void LinearBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->solvefor) { + for(NameNodeList::iterator iter = this->solvefor->begin(); + iter != this->solvefor->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for LinearBlock ast node */ + LinearBlockNode::LinearBlockNode(NameNode * name, NameNodeList * solvefor, StatementBlockNode * statementblock) { + this->name = name; + this->solvefor = solvefor; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for LinearBlock ast node */ + LinearBlockNode::LinearBlockNode(const LinearBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.solvefor) { + this->solvefor = new NameNodeList(); + for(NameNodeList::iterator iter = obj.solvefor->begin(); + iter != obj.solvefor->end(); iter++) { + this->solvefor->push_back((*iter)->clone()); + } + } + else + this->solvefor = NULL; + + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for LinearBlock ast node */ + LinearBlockNode::~LinearBlockNode() { + if(name) + delete name; + if (solvefor) { + for(NameNodeList::iterator iter = this->solvefor->begin(); + iter != this->solvefor->end(); iter++) { + delete (*iter); + } + } + + if(solvefor) + delete solvefor; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for NonLinearBlock ast node */ + void NonLinearBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->solvefor) { + for(NameNodeList::iterator iter = this->solvefor->begin(); + iter != this->solvefor->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for NonLinearBlock ast node */ + NonLinearBlockNode::NonLinearBlockNode(NameNode * name, NameNodeList * solvefor, StatementBlockNode * statementblock) { + this->name = name; + this->solvefor = solvefor; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for NonLinearBlock ast node */ + NonLinearBlockNode::NonLinearBlockNode(const NonLinearBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.solvefor) { + this->solvefor = new NameNodeList(); + for(NameNodeList::iterator iter = obj.solvefor->begin(); + iter != obj.solvefor->end(); iter++) { + this->solvefor->push_back((*iter)->clone()); + } + } + else + this->solvefor = NULL; + + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for NonLinearBlock ast node */ + NonLinearBlockNode::~NonLinearBlockNode() { + if(name) + delete name; + if (solvefor) { + for(NameNodeList::iterator iter = this->solvefor->begin(); + iter != this->solvefor->end(); iter++) { + delete (*iter); + } + } + + if(solvefor) + delete solvefor; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for DiscreteBlock ast node */ + void DiscreteBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for DiscreteBlock ast node */ + DiscreteBlockNode::DiscreteBlockNode(NameNode * name, StatementBlockNode * statementblock) { + this->name = name; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for DiscreteBlock ast node */ + DiscreteBlockNode::DiscreteBlockNode(const DiscreteBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for DiscreteBlock ast node */ + DiscreteBlockNode::~DiscreteBlockNode() { + if(name) + delete name; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for PartialBlock ast node */ + void PartialBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for PartialBlock ast node */ + PartialBlockNode::PartialBlockNode(NameNode * name, StatementBlockNode * statementblock) { + this->name = name; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for PartialBlock ast node */ + PartialBlockNode::PartialBlockNode(const PartialBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for PartialBlock ast node */ + PartialBlockNode::~PartialBlockNode() { + if(name) + delete name; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for PartialEquation ast node */ + void PartialEquationNode::visitChildren(Visitor* v) { + prime->accept(v); + name1->accept(v); + name2->accept(v); + name3->accept(v); + } + + /* constructor for PartialEquation ast node */ + PartialEquationNode::PartialEquationNode(PrimeNameNode * prime, NameNode * name1, NameNode * name2, NameNode * name3) { + this->prime = prime; + this->name1 = name1; + this->name2 = name2; + this->name3 = name3; + } + + /* copy constructor for PartialEquation ast node */ + PartialEquationNode::PartialEquationNode(const PartialEquationNode& obj) { + if(obj.prime) + this->prime = obj.prime->clone(); + else + this->prime = NULL; + if(obj.name1) + this->name1 = obj.name1->clone(); + else + this->name1 = NULL; + if(obj.name2) + this->name2 = obj.name2->clone(); + else + this->name2 = NULL; + if(obj.name3) + this->name3 = obj.name3->clone(); + else + this->name3 = NULL; + } + + /* destructor for PartialEquation ast node */ + PartialEquationNode::~PartialEquationNode() { + if(prime) + delete prime; + if(name1) + delete name1; + if(name2) + delete name2; + if(name3) + delete name3; + } + + /* visit Children method for FirstLastTypeIndex ast node */ + void FirstLastTypeIndexNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for FirstLastTypeIndex ast node */ + FirstLastTypeIndexNode::FirstLastTypeIndexNode(FirstLastType value) { + this->value = value; + } + + /* copy constructor for FirstLastTypeIndex ast node */ + FirstLastTypeIndexNode::FirstLastTypeIndexNode(const FirstLastTypeIndexNode& obj) { + this->value = obj.value; + } + + /* destructor for FirstLastTypeIndex ast node */ + FirstLastTypeIndexNode::~FirstLastTypeIndexNode() { + } + + /* visit Children method for PartialBoundary ast node */ + void PartialBoundaryNode::visitChildren(Visitor* v) { + if (this->del) { + this->del->accept(v); + } + + name->accept(v); + if (this->index) { + this->index->accept(v); + } + + if (this->expression) { + this->expression->accept(v); + } + + if (this->name1) { + this->name1->accept(v); + } + + if (this->del2) { + this->del2->accept(v); + } + + if (this->name2) { + this->name2->accept(v); + } + + if (this->name3) { + this->name3->accept(v); + } + + } + + /* constructor for PartialBoundary ast node */ + PartialBoundaryNode::PartialBoundaryNode(NameNode * del, IdentifierNode * name, FirstLastTypeIndexNode * index, ExpressionNode * expression, NameNode * name1, NameNode * del2, NameNode * name2, NameNode * name3) { + this->del = del; + this->name = name; + this->index = index; + this->expression = expression; + this->name1 = name1; + this->del2 = del2; + this->name2 = name2; + this->name3 = name3; + } + + /* copy constructor for PartialBoundary ast node */ + PartialBoundaryNode::PartialBoundaryNode(const PartialBoundaryNode& obj) { + if(obj.del) + this->del = obj.del->clone(); + else + this->del = NULL; + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.index) + this->index = obj.index->clone(); + else + this->index = NULL; + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + if(obj.name1) + this->name1 = obj.name1->clone(); + else + this->name1 = NULL; + if(obj.del2) + this->del2 = obj.del2->clone(); + else + this->del2 = NULL; + if(obj.name2) + this->name2 = obj.name2->clone(); + else + this->name2 = NULL; + if(obj.name3) + this->name3 = obj.name3->clone(); + else + this->name3 = NULL; + } + + /* destructor for PartialBoundary ast node */ + PartialBoundaryNode::~PartialBoundaryNode() { + if(del) + delete del; + if(name) + delete name; + if(index) + delete index; + if(expression) + delete expression; + if(name1) + delete name1; + if(del2) + delete del2; + if(name2) + delete name2; + if(name3) + delete name3; + } + + /* visit Children method for FunctionTableBlock ast node */ + void FunctionTableBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->unit) { + this->unit->accept(v); + } + + } + + /* constructor for FunctionTableBlock ast node */ + FunctionTableBlockNode::FunctionTableBlockNode(NameNode * name, ArgumentNodeList * arguments, UnitNode * unit) { + this->name = name; + this->arguments = arguments; + this->unit = unit; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for FunctionTableBlock ast node */ + FunctionTableBlockNode::FunctionTableBlockNode(const FunctionTableBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.arguments) { + this->arguments = new ArgumentNodeList(); + for(ArgumentNodeList::iterator iter = obj.arguments->begin(); + iter != obj.arguments->end(); iter++) { + this->arguments->push_back((*iter)->clone()); + } + } + else + this->arguments = NULL; + + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for FunctionTableBlock ast node */ + FunctionTableBlockNode::~FunctionTableBlockNode() { + if(name) + delete name; + if (arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + delete (*iter); + } + } + + if(arguments) + delete arguments; + if(unit) + delete unit; + if(token) + delete token; + } + + /* visit Children method for FunctionBlock ast node */ + void FunctionBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->unit) { + this->unit->accept(v); + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for FunctionBlock ast node */ + FunctionBlockNode::FunctionBlockNode(NameNode * name, ArgumentNodeList * arguments, UnitNode * unit, StatementBlockNode * statementblock) { + this->name = name; + this->arguments = arguments; + this->unit = unit; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for FunctionBlock ast node */ + FunctionBlockNode::FunctionBlockNode(const FunctionBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.arguments) { + this->arguments = new ArgumentNodeList(); + for(ArgumentNodeList::iterator iter = obj.arguments->begin(); + iter != obj.arguments->end(); iter++) { + this->arguments->push_back((*iter)->clone()); + } + } + else + this->arguments = NULL; + + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for FunctionBlock ast node */ + FunctionBlockNode::~FunctionBlockNode() { + if(name) + delete name; + if (arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + delete (*iter); + } + } + + if(arguments) + delete arguments; + if(unit) + delete unit; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for ProcedureBlock ast node */ + void ProcedureBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->unit) { + this->unit->accept(v); + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for ProcedureBlock ast node */ + ProcedureBlockNode::ProcedureBlockNode(NameNode * name, ArgumentNodeList * arguments, UnitNode * unit, StatementBlockNode * statementblock) { + this->name = name; + this->arguments = arguments; + this->unit = unit; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for ProcedureBlock ast node */ + ProcedureBlockNode::ProcedureBlockNode(const ProcedureBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.arguments) { + this->arguments = new ArgumentNodeList(); + for(ArgumentNodeList::iterator iter = obj.arguments->begin(); + iter != obj.arguments->end(); iter++) { + this->arguments->push_back((*iter)->clone()); + } + } + else + this->arguments = NULL; + + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for ProcedureBlock ast node */ + ProcedureBlockNode::~ProcedureBlockNode() { + if(name) + delete name; + if (arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + delete (*iter); + } + } + + if(arguments) + delete arguments; + if(unit) + delete unit; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for NetReceiveBlock ast node */ + void NetReceiveBlockNode::visitChildren(Visitor* v) { + if (this->arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for NetReceiveBlock ast node */ + NetReceiveBlockNode::NetReceiveBlockNode(ArgumentNodeList * arguments, StatementBlockNode * statementblock) { + this->arguments = arguments; + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for NetReceiveBlock ast node */ + NetReceiveBlockNode::NetReceiveBlockNode(const NetReceiveBlockNode& obj) { + // cloning list + if (obj.arguments) { + this->arguments = new ArgumentNodeList(); + for(ArgumentNodeList::iterator iter = obj.arguments->begin(); + iter != obj.arguments->end(); iter++) { + this->arguments->push_back((*iter)->clone()); + } + } + else + this->arguments = NULL; + + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for NetReceiveBlock ast node */ + NetReceiveBlockNode::~NetReceiveBlockNode() { + if (arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + delete (*iter); + } + } + + if(arguments) + delete arguments; + if(statementblock) + delete statementblock; + } + + /* visit Children method for SolveBlock ast node */ + void SolveBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->method) { + this->method->accept(v); + } + + if (this->ifsolerr) { + this->ifsolerr->accept(v); + } + + } + + /* constructor for SolveBlock ast node */ + SolveBlockNode::SolveBlockNode(NameNode * name, NameNode * method, StatementBlockNode * ifsolerr) { + this->name = name; + this->method = method; + this->ifsolerr = ifsolerr; + this->symtab = NULL; + } + + /* copy constructor for SolveBlock ast node */ + SolveBlockNode::SolveBlockNode(const SolveBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.method) + this->method = obj.method->clone(); + else + this->method = NULL; + if(obj.ifsolerr) + this->ifsolerr = obj.ifsolerr->clone(); + else + this->ifsolerr = NULL; + this->symtab = NULL; + } + + /* destructor for SolveBlock ast node */ + SolveBlockNode::~SolveBlockNode() { + if(name) + delete name; + if(method) + delete method; + if(ifsolerr) + delete ifsolerr; + } + + /* visit Children method for BreakpointBlock ast node */ + void BreakpointBlockNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for BreakpointBlock ast node */ + BreakpointBlockNode::BreakpointBlockNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for BreakpointBlock ast node */ + BreakpointBlockNode::BreakpointBlockNode(const BreakpointBlockNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for BreakpointBlock ast node */ + BreakpointBlockNode::~BreakpointBlockNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for TerminalBlock ast node */ + void TerminalBlockNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for TerminalBlock ast node */ + TerminalBlockNode::TerminalBlockNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for TerminalBlock ast node */ + TerminalBlockNode::TerminalBlockNode(const TerminalBlockNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for TerminalBlock ast node */ + TerminalBlockNode::~TerminalBlockNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for BeforeBlock ast node */ + void BeforeBlockNode::visitChildren(Visitor* v) { + block->accept(v); + } + + /* constructor for BeforeBlock ast node */ + BeforeBlockNode::BeforeBlockNode(BABlockNode * block) { + this->block = block; + this->symtab = NULL; + } + + /* copy constructor for BeforeBlock ast node */ + BeforeBlockNode::BeforeBlockNode(const BeforeBlockNode& obj) { + if(obj.block) + this->block = obj.block->clone(); + else + this->block = NULL; + this->symtab = NULL; + } + + /* destructor for BeforeBlock ast node */ + BeforeBlockNode::~BeforeBlockNode() { + if(block) + delete block; + } + + /* visit Children method for AfterBlock ast node */ + void AfterBlockNode::visitChildren(Visitor* v) { + block->accept(v); + } + + /* constructor for AfterBlock ast node */ + AfterBlockNode::AfterBlockNode(BABlockNode * block) { + this->block = block; + this->symtab = NULL; + } + + /* copy constructor for AfterBlock ast node */ + AfterBlockNode::AfterBlockNode(const AfterBlockNode& obj) { + if(obj.block) + this->block = obj.block->clone(); + else + this->block = NULL; + this->symtab = NULL; + } + + /* destructor for AfterBlock ast node */ + AfterBlockNode::~AfterBlockNode() { + if(block) + delete block; + } + + /* visit Children method for BABlockType ast node */ + void BABlockTypeNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for BABlockType ast node */ + BABlockTypeNode::BABlockTypeNode(BAType value) { + this->value = value; + } + + /* copy constructor for BABlockType ast node */ + BABlockTypeNode::BABlockTypeNode(const BABlockTypeNode& obj) { + this->value = obj.value; + } + + /* destructor for BABlockType ast node */ + BABlockTypeNode::~BABlockTypeNode() { + } + + /* visit Children method for BABlock ast node */ + void BABlockNode::visitChildren(Visitor* v) { + type->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for BABlock ast node */ + BABlockNode::BABlockNode(BABlockTypeNode * type, StatementBlockNode * statementblock) { + this->type = type; + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for BABlock ast node */ + BABlockNode::BABlockNode(const BABlockNode& obj) { + if(obj.type) + this->type = obj.type->clone(); + else + this->type = NULL; + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for BABlock ast node */ + BABlockNode::~BABlockNode() { + if(type) + delete type; + if(statementblock) + delete statementblock; + } + + /* visit Children method for WatchStatement ast node */ + void WatchStatementNode::visitChildren(Visitor* v) { + if (this->statements) { + for(WatchNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for WatchStatement ast node */ + WatchStatementNode::WatchStatementNode(WatchNodeList * statements) { + this->statements = statements; + } + + /* copy constructor for WatchStatement ast node */ + WatchStatementNode::WatchStatementNode(const WatchStatementNode& obj) { + // cloning list + if (obj.statements) { + this->statements = new WatchNodeList(); + for(WatchNodeList::iterator iter = obj.statements->begin(); + iter != obj.statements->end(); iter++) { + this->statements->push_back((*iter)->clone()); + } + } + else + this->statements = NULL; + + } + + /* destructor for WatchStatement ast node */ + WatchStatementNode::~WatchStatementNode() { + if (statements) { + for(WatchNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + delete (*iter); + } + } + + if(statements) + delete statements; + } + + /* visit Children method for Watch ast node */ + void WatchNode::visitChildren(Visitor* v) { + expression->accept(v); + value->accept(v); + } + + /* constructor for Watch ast node */ + WatchNode::WatchNode(ExpressionNode * expression, ExpressionNode * value) { + this->expression = expression; + this->value = value; + } + + /* copy constructor for Watch ast node */ + WatchNode::WatchNode(const WatchNode& obj) { + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + } + + /* destructor for Watch ast node */ + WatchNode::~WatchNode() { + if(expression) + delete expression; + if(value) + delete value; + } + + /* visit Children method for ForNetcon ast node */ + void ForNetconNode::visitChildren(Visitor* v) { + if (this->arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for ForNetcon ast node */ + ForNetconNode::ForNetconNode(ArgumentNodeList * arguments, StatementBlockNode * statementblock) { + this->arguments = arguments; + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for ForNetcon ast node */ + ForNetconNode::ForNetconNode(const ForNetconNode& obj) { + // cloning list + if (obj.arguments) { + this->arguments = new ArgumentNodeList(); + for(ArgumentNodeList::iterator iter = obj.arguments->begin(); + iter != obj.arguments->end(); iter++) { + this->arguments->push_back((*iter)->clone()); + } + } + else + this->arguments = NULL; + + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for ForNetcon ast node */ + ForNetconNode::~ForNetconNode() { + if (arguments) { + for(ArgumentNodeList::iterator iter = this->arguments->begin(); + iter != this->arguments->end(); iter++) { + delete (*iter); + } + } + + if(arguments) + delete arguments; + if(statementblock) + delete statementblock; + } + + /* visit Children method for MutexLock ast node */ + void MutexLockNode::visitChildren(Visitor* v) { + } + + /* destructor for MutexLock ast node */ + MutexLockNode::~MutexLockNode() { + } + + /* visit Children method for MutexUnlock ast node */ + void MutexUnlockNode::visitChildren(Visitor* v) { + } + + /* destructor for MutexUnlock ast node */ + MutexUnlockNode::~MutexUnlockNode() { + } + + /* visit Children method for Reset ast node */ + void ResetNode::visitChildren(Visitor* v) { + } + + /* destructor for Reset ast node */ + ResetNode::~ResetNode() { + } + + /* visit Children method for Sens ast node */ + void SensNode::visitChildren(Visitor* v) { + if (this->senslist) { + for(VarNameNodeList::iterator iter = this->senslist->begin(); + iter != this->senslist->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for Sens ast node */ + SensNode::SensNode(VarNameNodeList * senslist) { + this->senslist = senslist; + } + + /* copy constructor for Sens ast node */ + SensNode::SensNode(const SensNode& obj) { + // cloning list + if (obj.senslist) { + this->senslist = new VarNameNodeList(); + for(VarNameNodeList::iterator iter = obj.senslist->begin(); + iter != obj.senslist->end(); iter++) { + this->senslist->push_back((*iter)->clone()); + } + } + else + this->senslist = NULL; + + } + + /* destructor for Sens ast node */ + SensNode::~SensNode() { + if (senslist) { + for(VarNameNodeList::iterator iter = this->senslist->begin(); + iter != this->senslist->end(); iter++) { + delete (*iter); + } + } + + if(senslist) + delete senslist; + } + + /* visit Children method for Conserve ast node */ + void ConserveNode::visitChildren(Visitor* v) { + react->accept(v); + expr->accept(v); + } + + /* constructor for Conserve ast node */ + ConserveNode::ConserveNode(ExpressionNode * react, ExpressionNode * expr) { + this->react = react; + this->expr = expr; + } + + /* copy constructor for Conserve ast node */ + ConserveNode::ConserveNode(const ConserveNode& obj) { + if(obj.react) + this->react = obj.react->clone(); + else + this->react = NULL; + if(obj.expr) + this->expr = obj.expr->clone(); + else + this->expr = NULL; + } + + /* destructor for Conserve ast node */ + ConserveNode::~ConserveNode() { + if(react) + delete react; + if(expr) + delete expr; + } + + /* visit Children method for Compartment ast node */ + void CompartmentNode::visitChildren(Visitor* v) { + if (this->name) { + this->name->accept(v); + } + + expression->accept(v); + if (this->names) { + for(NameNodeList::iterator iter = this->names->begin(); + iter != this->names->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for Compartment ast node */ + CompartmentNode::CompartmentNode(NameNode * name, ExpressionNode * expression, NameNodeList * names) { + this->name = name; + this->expression = expression; + this->names = names; + } + + /* copy constructor for Compartment ast node */ + CompartmentNode::CompartmentNode(const CompartmentNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + // cloning list + if (obj.names) { + this->names = new NameNodeList(); + for(NameNodeList::iterator iter = obj.names->begin(); + iter != obj.names->end(); iter++) { + this->names->push_back((*iter)->clone()); + } + } + else + this->names = NULL; + + } + + /* destructor for Compartment ast node */ + CompartmentNode::~CompartmentNode() { + if(name) + delete name; + if(expression) + delete expression; + if (names) { + for(NameNodeList::iterator iter = this->names->begin(); + iter != this->names->end(); iter++) { + delete (*iter); + } + } + + if(names) + delete names; + } + + /* visit Children method for LDifuse ast node */ + void LDifuseNode::visitChildren(Visitor* v) { + if (this->name) { + this->name->accept(v); + } + + expression->accept(v); + if (this->names) { + for(NameNodeList::iterator iter = this->names->begin(); + iter != this->names->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for LDifuse ast node */ + LDifuseNode::LDifuseNode(NameNode * name, ExpressionNode * expression, NameNodeList * names) { + this->name = name; + this->expression = expression; + this->names = names; + } + + /* copy constructor for LDifuse ast node */ + LDifuseNode::LDifuseNode(const LDifuseNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + // cloning list + if (obj.names) { + this->names = new NameNodeList(); + for(NameNodeList::iterator iter = obj.names->begin(); + iter != obj.names->end(); iter++) { + this->names->push_back((*iter)->clone()); + } + } + else + this->names = NULL; + + } + + /* destructor for LDifuse ast node */ + LDifuseNode::~LDifuseNode() { + if(name) + delete name; + if(expression) + delete expression; + if (names) { + for(NameNodeList::iterator iter = this->names->begin(); + iter != this->names->end(); iter++) { + delete (*iter); + } + } + + if(names) + delete names; + } + + /* visit Children method for KineticBlock ast node */ + void KineticBlockNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->solvefor) { + for(NameNodeList::iterator iter = this->solvefor->begin(); + iter != this->solvefor->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for KineticBlock ast node */ + KineticBlockNode::KineticBlockNode(NameNode * name, NameNodeList * solvefor, StatementBlockNode * statementblock) { + this->name = name; + this->solvefor = solvefor; + this->statementblock = statementblock; + this->token = NULL; + this->symtab = NULL; + } + + /* copy constructor for KineticBlock ast node */ + KineticBlockNode::KineticBlockNode(const KineticBlockNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.solvefor) { + this->solvefor = new NameNodeList(); + for(NameNodeList::iterator iter = obj.solvefor->begin(); + iter != obj.solvefor->end(); iter++) { + this->solvefor->push_back((*iter)->clone()); + } + } + else + this->solvefor = NULL; + + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + this->symtab = NULL; + } + + /* destructor for KineticBlock ast node */ + KineticBlockNode::~KineticBlockNode() { + if(name) + delete name; + if (solvefor) { + for(NameNodeList::iterator iter = this->solvefor->begin(); + iter != this->solvefor->end(); iter++) { + delete (*iter); + } + } + + if(solvefor) + delete solvefor; + if(statementblock) + delete statementblock; + if(token) + delete token; + } + + /* visit Children method for ReactionStatement ast node */ + void ReactionStatementNode::visitChildren(Visitor* v) { + react1->accept(v); + op->accept(v); + if (this->react2) { + this->react2->accept(v); + } + + expr1->accept(v); + if (this->expr2) { + this->expr2->accept(v); + } + + } + + /* constructor for ReactionStatement ast node */ + ReactionStatementNode::ReactionStatementNode(ExpressionNode * react1, ReactionOperatorNode * op, ExpressionNode * react2, ExpressionNode * expr1, ExpressionNode * expr2) { + this->react1 = react1; + this->op = op; + this->react2 = react2; + this->expr1 = expr1; + this->expr2 = expr2; + } + + /* copy constructor for ReactionStatement ast node */ + ReactionStatementNode::ReactionStatementNode(const ReactionStatementNode& obj) { + if(obj.react1) + this->react1 = obj.react1->clone(); + else + this->react1 = NULL; + if(obj.op) + this->op = obj.op->clone(); + else + this->op = NULL; + if(obj.react2) + this->react2 = obj.react2->clone(); + else + this->react2 = NULL; + if(obj.expr1) + this->expr1 = obj.expr1->clone(); + else + this->expr1 = NULL; + if(obj.expr2) + this->expr2 = obj.expr2->clone(); + else + this->expr2 = NULL; + } + + /* destructor for ReactionStatement ast node */ + ReactionStatementNode::~ReactionStatementNode() { + if(react1) + delete react1; + if(op) + delete op; + if(react2) + delete react2; + if(expr1) + delete expr1; + if(expr2) + delete expr2; + } + + /* visit Children method for ReactVarName ast node */ + void ReactVarNameNode::visitChildren(Visitor* v) { + if (this->value) { + this->value->accept(v); + } + + name->accept(v); + } + + /* constructor for ReactVarName ast node */ + ReactVarNameNode::ReactVarNameNode(IntegerNode * value, VarNameNode * name) { + this->value = value; + this->name = name; + } + + /* copy constructor for ReactVarName ast node */ + ReactVarNameNode::ReactVarNameNode(const ReactVarNameNode& obj) { + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for ReactVarName ast node */ + ReactVarNameNode::~ReactVarNameNode() { + if(value) + delete value; + if(name) + delete name; + } + + /* visit Children method for LagStatement ast node */ + void LagStatementNode::visitChildren(Visitor* v) { + name->accept(v); + byname->accept(v); + } + + /* constructor for LagStatement ast node */ + LagStatementNode::LagStatementNode(IdentifierNode * name, NameNode * byname) { + this->name = name; + this->byname = byname; + } + + /* copy constructor for LagStatement ast node */ + LagStatementNode::LagStatementNode(const LagStatementNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.byname) + this->byname = obj.byname->clone(); + else + this->byname = NULL; + } + + /* destructor for LagStatement ast node */ + LagStatementNode::~LagStatementNode() { + if(name) + delete name; + if(byname) + delete byname; + } + + /* visit Children method for QueueStatement ast node */ + void QueueStatementNode::visitChildren(Visitor* v) { + qype->accept(v); + name->accept(v); + } + + /* constructor for QueueStatement ast node */ + QueueStatementNode::QueueStatementNode(QueueExpressionTypeNode * qype, IdentifierNode * name) { + this->qype = qype; + this->name = name; + } + + /* copy constructor for QueueStatement ast node */ + QueueStatementNode::QueueStatementNode(const QueueStatementNode& obj) { + if(obj.qype) + this->qype = obj.qype->clone(); + else + this->qype = NULL; + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for QueueStatement ast node */ + QueueStatementNode::~QueueStatementNode() { + if(qype) + delete qype; + if(name) + delete name; + } + + /* visit Children method for QueueExpressionType ast node */ + void QueueExpressionTypeNode::visitChildren(Visitor* v) { + /* no children */ + } + + /* constructor for QueueExpressionType ast node */ + QueueExpressionTypeNode::QueueExpressionTypeNode(QueueType value) { + this->value = value; + } + + /* copy constructor for QueueExpressionType ast node */ + QueueExpressionTypeNode::QueueExpressionTypeNode(const QueueExpressionTypeNode& obj) { + this->value = obj.value; + } + + /* destructor for QueueExpressionType ast node */ + QueueExpressionTypeNode::~QueueExpressionTypeNode() { + } + + /* visit Children method for MatchBlock ast node */ + void MatchBlockNode::visitChildren(Visitor* v) { + if (this->matchs) { + for(MatchNodeList::iterator iter = this->matchs->begin(); + iter != this->matchs->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for MatchBlock ast node */ + MatchBlockNode::MatchBlockNode(MatchNodeList * matchs) { + this->matchs = matchs; + this->symtab = NULL; + } + + /* copy constructor for MatchBlock ast node */ + MatchBlockNode::MatchBlockNode(const MatchBlockNode& obj) { + // cloning list + if (obj.matchs) { + this->matchs = new MatchNodeList(); + for(MatchNodeList::iterator iter = obj.matchs->begin(); + iter != obj.matchs->end(); iter++) { + this->matchs->push_back((*iter)->clone()); + } + } + else + this->matchs = NULL; + + this->symtab = NULL; + } + + /* destructor for MatchBlock ast node */ + MatchBlockNode::~MatchBlockNode() { + if (matchs) { + for(MatchNodeList::iterator iter = this->matchs->begin(); + iter != this->matchs->end(); iter++) { + delete (*iter); + } + } + + if(matchs) + delete matchs; + } + + /* visit Children method for Match ast node */ + void MatchNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->expression) { + this->expression->accept(v); + } + + } + + /* constructor for Match ast node */ + MatchNode::MatchNode(IdentifierNode * name, ExpressionNode * expression) { + this->name = name; + this->expression = expression; + } + + /* copy constructor for Match ast node */ + MatchNode::MatchNode(const MatchNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.expression) + this->expression = obj.expression->clone(); + else + this->expression = NULL; + } + + /* destructor for Match ast node */ + MatchNode::~MatchNode() { + if(name) + delete name; + if(expression) + delete expression; + } + + /* visit Children method for UnitBlock ast node */ + void UnitBlockNode::visitChildren(Visitor* v) { + if (this->definitions) { + for(ExpressionNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for UnitBlock ast node */ + UnitBlockNode::UnitBlockNode(ExpressionNodeList * definitions) { + this->definitions = definitions; + this->symtab = NULL; + } + + /* copy constructor for UnitBlock ast node */ + UnitBlockNode::UnitBlockNode(const UnitBlockNode& obj) { + // cloning list + if (obj.definitions) { + this->definitions = new ExpressionNodeList(); + for(ExpressionNodeList::iterator iter = obj.definitions->begin(); + iter != obj.definitions->end(); iter++) { + this->definitions->push_back((*iter)->clone()); + } + } + else + this->definitions = NULL; + + this->symtab = NULL; + } + + /* destructor for UnitBlock ast node */ + UnitBlockNode::~UnitBlockNode() { + if (definitions) { + for(ExpressionNodeList::iterator iter = this->definitions->begin(); + iter != this->definitions->end(); iter++) { + delete (*iter); + } + } + + if(definitions) + delete definitions; + } + + /* visit Children method for UnitDef ast node */ + void UnitDefNode::visitChildren(Visitor* v) { + unit1->accept(v); + unit2->accept(v); + } + + /* constructor for UnitDef ast node */ + UnitDefNode::UnitDefNode(UnitNode * unit1, UnitNode * unit2) { + this->unit1 = unit1; + this->unit2 = unit2; + } + + /* copy constructor for UnitDef ast node */ + UnitDefNode::UnitDefNode(const UnitDefNode& obj) { + if(obj.unit1) + this->unit1 = obj.unit1->clone(); + else + this->unit1 = NULL; + if(obj.unit2) + this->unit2 = obj.unit2->clone(); + else + this->unit2 = NULL; + } + + /* destructor for UnitDef ast node */ + UnitDefNode::~UnitDefNode() { + if(unit1) + delete unit1; + if(unit2) + delete unit2; + } + + /* visit Children method for Factordef ast node */ + void FactordefNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->value) { + this->value->accept(v); + } + + unit1->accept(v); + if (this->gt) { + this->gt->accept(v); + } + + if (this->unit2) { + this->unit2->accept(v); + } + + } + + /* constructor for Factordef ast node */ + FactordefNode::FactordefNode(NameNode * name, DoubleNode * value, UnitNode * unit1, BooleanNode * gt, UnitNode * unit2) { + this->name = name; + this->value = value; + this->unit1 = unit1; + this->gt = gt; + this->unit2 = unit2; + this->token = NULL; + } + + /* copy constructor for Factordef ast node */ + FactordefNode::FactordefNode(const FactordefNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + if(obj.unit1) + this->unit1 = obj.unit1->clone(); + else + this->unit1 = NULL; + if(obj.gt) + this->gt = obj.gt->clone(); + else + this->gt = NULL; + if(obj.unit2) + this->unit2 = obj.unit2->clone(); + else + this->unit2 = NULL; + if(obj.token) + this->token = obj.token->clone(); + else + this->token = NULL; + } + + /* destructor for Factordef ast node */ + FactordefNode::~FactordefNode() { + if(name) + delete name; + if(value) + delete value; + if(unit1) + delete unit1; + if(gt) + delete gt; + if(unit2) + delete unit2; + if(token) + delete token; + } + + /* visit Children method for ConstantStatement ast node */ + void ConstantStatementNode::visitChildren(Visitor* v) { + name->accept(v); + value->accept(v); + if (this->unit) { + this->unit->accept(v); + } + + } + + /* constructor for ConstantStatement ast node */ + ConstantStatementNode::ConstantStatementNode(NameNode * name, NumberNode * value, UnitNode * unit) { + this->name = name; + this->value = value; + this->unit = unit; + } + + /* copy constructor for ConstantStatement ast node */ + ConstantStatementNode::ConstantStatementNode(const ConstantStatementNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + if(obj.unit) + this->unit = obj.unit->clone(); + else + this->unit = NULL; + } + + /* destructor for ConstantStatement ast node */ + ConstantStatementNode::~ConstantStatementNode() { + if(name) + delete name; + if(value) + delete value; + if(unit) + delete unit; + } + + /* visit Children method for ConstantBlock ast node */ + void ConstantBlockNode::visitChildren(Visitor* v) { + if (this->statements) { + for(ConstantStatementNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for ConstantBlock ast node */ + ConstantBlockNode::ConstantBlockNode(ConstantStatementNodeList * statements) { + this->statements = statements; + this->symtab = NULL; + } + + /* copy constructor for ConstantBlock ast node */ + ConstantBlockNode::ConstantBlockNode(const ConstantBlockNode& obj) { + // cloning list + if (obj.statements) { + this->statements = new ConstantStatementNodeList(); + for(ConstantStatementNodeList::iterator iter = obj.statements->begin(); + iter != obj.statements->end(); iter++) { + this->statements->push_back((*iter)->clone()); + } + } + else + this->statements = NULL; + + this->symtab = NULL; + } + + /* destructor for ConstantBlock ast node */ + ConstantBlockNode::~ConstantBlockNode() { + if (statements) { + for(ConstantStatementNodeList::iterator iter = this->statements->begin(); + iter != this->statements->end(); iter++) { + delete (*iter); + } + } + + if(statements) + delete statements; + } + + /* visit Children method for TableStatement ast node */ + void TableStatementNode::visitChildren(Visitor* v) { + if (this->tablst) { + for(NameNodeList::iterator iter = this->tablst->begin(); + iter != this->tablst->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->dependlst) { + for(NameNodeList::iterator iter = this->dependlst->begin(); + iter != this->dependlst->end(); iter++) { + (*iter)->accept(v); + } + } + + from->accept(v); + to->accept(v); + with->accept(v); + } + + /* constructor for TableStatement ast node */ + TableStatementNode::TableStatementNode(NameNodeList * tablst, NameNodeList * dependlst, ExpressionNode * from, ExpressionNode * to, IntegerNode * with) { + this->tablst = tablst; + this->dependlst = dependlst; + this->from = from; + this->to = to; + this->with = with; + } + + /* copy constructor for TableStatement ast node */ + TableStatementNode::TableStatementNode(const TableStatementNode& obj) { + // cloning list + if (obj.tablst) { + this->tablst = new NameNodeList(); + for(NameNodeList::iterator iter = obj.tablst->begin(); + iter != obj.tablst->end(); iter++) { + this->tablst->push_back((*iter)->clone()); + } + } + else + this->tablst = NULL; + + // cloning list + if (obj.dependlst) { + this->dependlst = new NameNodeList(); + for(NameNodeList::iterator iter = obj.dependlst->begin(); + iter != obj.dependlst->end(); iter++) { + this->dependlst->push_back((*iter)->clone()); + } + } + else + this->dependlst = NULL; + + if(obj.from) + this->from = obj.from->clone(); + else + this->from = NULL; + if(obj.to) + this->to = obj.to->clone(); + else + this->to = NULL; + if(obj.with) + this->with = obj.with->clone(); + else + this->with = NULL; + } + + /* destructor for TableStatement ast node */ + TableStatementNode::~TableStatementNode() { + if (tablst) { + for(NameNodeList::iterator iter = this->tablst->begin(); + iter != this->tablst->end(); iter++) { + delete (*iter); + } + } + + if(tablst) + delete tablst; + if (dependlst) { + for(NameNodeList::iterator iter = this->dependlst->begin(); + iter != this->dependlst->end(); iter++) { + delete (*iter); + } + } + + if(dependlst) + delete dependlst; + if(from) + delete from; + if(to) + delete to; + if(with) + delete with; + } + + /* visit Children method for NeuronBlock ast node */ + void NeuronBlockNode::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + + } + + /* constructor for NeuronBlock ast node */ + NeuronBlockNode::NeuronBlockNode(StatementBlockNode * statementblock) { + this->statementblock = statementblock; + this->symtab = NULL; + } + + /* copy constructor for NeuronBlock ast node */ + NeuronBlockNode::NeuronBlockNode(const NeuronBlockNode& obj) { + if(obj.statementblock) + this->statementblock = obj.statementblock->clone(); + else + this->statementblock = NULL; + this->symtab = NULL; + } + + /* destructor for NeuronBlock ast node */ + NeuronBlockNode::~NeuronBlockNode() { + if(statementblock) + delete statementblock; + } + + /* visit Children method for ReadIonVar ast node */ + void ReadIonVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ReadIonVar ast node */ + ReadIonVarNode::ReadIonVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for ReadIonVar ast node */ + ReadIonVarNode::ReadIonVarNode(const ReadIonVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for ReadIonVar ast node */ + ReadIonVarNode::~ReadIonVarNode() { + if(name) + delete name; + } + + /* visit Children method for WriteIonVar ast node */ + void WriteIonVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for WriteIonVar ast node */ + WriteIonVarNode::WriteIonVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for WriteIonVar ast node */ + WriteIonVarNode::WriteIonVarNode(const WriteIonVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for WriteIonVar ast node */ + WriteIonVarNode::~WriteIonVarNode() { + if(name) + delete name; + } + + /* visit Children method for NonspeCurVar ast node */ + void NonspeCurVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for NonspeCurVar ast node */ + NonspeCurVarNode::NonspeCurVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for NonspeCurVar ast node */ + NonspeCurVarNode::NonspeCurVarNode(const NonspeCurVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for NonspeCurVar ast node */ + NonspeCurVarNode::~NonspeCurVarNode() { + if(name) + delete name; + } + + /* visit Children method for ElectrodeCurVar ast node */ + void ElectrodeCurVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ElectrodeCurVar ast node */ + ElectrodeCurVarNode::ElectrodeCurVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for ElectrodeCurVar ast node */ + ElectrodeCurVarNode::ElectrodeCurVarNode(const ElectrodeCurVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for ElectrodeCurVar ast node */ + ElectrodeCurVarNode::~ElectrodeCurVarNode() { + if(name) + delete name; + } + + /* visit Children method for SectionVar ast node */ + void SectionVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for SectionVar ast node */ + SectionVarNode::SectionVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for SectionVar ast node */ + SectionVarNode::SectionVarNode(const SectionVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for SectionVar ast node */ + SectionVarNode::~SectionVarNode() { + if(name) + delete name; + } + + /* visit Children method for RangeVar ast node */ + void RangeVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for RangeVar ast node */ + RangeVarNode::RangeVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for RangeVar ast node */ + RangeVarNode::RangeVarNode(const RangeVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for RangeVar ast node */ + RangeVarNode::~RangeVarNode() { + if(name) + delete name; + } + + /* visit Children method for GlobalVar ast node */ + void GlobalVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for GlobalVar ast node */ + GlobalVarNode::GlobalVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for GlobalVar ast node */ + GlobalVarNode::GlobalVarNode(const GlobalVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for GlobalVar ast node */ + GlobalVarNode::~GlobalVarNode() { + if(name) + delete name; + } + + /* visit Children method for PointerVar ast node */ + void PointerVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for PointerVar ast node */ + PointerVarNode::PointerVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for PointerVar ast node */ + PointerVarNode::PointerVarNode(const PointerVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for PointerVar ast node */ + PointerVarNode::~PointerVarNode() { + if(name) + delete name; + } + + /* visit Children method for BbcorePointerVar ast node */ + void BbcorePointerVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for BbcorePointerVar ast node */ + BbcorePointerVarNode::BbcorePointerVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for BbcorePointerVar ast node */ + BbcorePointerVarNode::BbcorePointerVarNode(const BbcorePointerVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for BbcorePointerVar ast node */ + BbcorePointerVarNode::~BbcorePointerVarNode() { + if(name) + delete name; + } + + /* visit Children method for ExternVar ast node */ + void ExternVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ExternVar ast node */ + ExternVarNode::ExternVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for ExternVar ast node */ + ExternVarNode::ExternVarNode(const ExternVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for ExternVar ast node */ + ExternVarNode::~ExternVarNode() { + if(name) + delete name; + } + + /* visit Children method for ThreadsafeVar ast node */ + void ThreadsafeVarNode::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ThreadsafeVar ast node */ + ThreadsafeVarNode::ThreadsafeVarNode(NameNode * name) { + this->name = name; + } + + /* copy constructor for ThreadsafeVar ast node */ + ThreadsafeVarNode::ThreadsafeVarNode(const ThreadsafeVarNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for ThreadsafeVar ast node */ + ThreadsafeVarNode::~ThreadsafeVarNode() { + if(name) + delete name; + } + + /* visit Children method for NrnSuffix ast node */ + void NrnSuffixNode::visitChildren(Visitor* v) { + type->accept(v); + name->accept(v); + } + + /* constructor for NrnSuffix ast node */ + NrnSuffixNode::NrnSuffixNode(NameNode * type, NameNode * name) { + this->type = type; + this->name = name; + } + + /* copy constructor for NrnSuffix ast node */ + NrnSuffixNode::NrnSuffixNode(const NrnSuffixNode& obj) { + if(obj.type) + this->type = obj.type->clone(); + else + this->type = NULL; + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + } + + /* destructor for NrnSuffix ast node */ + NrnSuffixNode::~NrnSuffixNode() { + if(type) + delete type; + if(name) + delete name; + } + + /* visit Children method for NrnUseion ast node */ + void NrnUseionNode::visitChildren(Visitor* v) { + name->accept(v); + if (this->readlist) { + for(ReadIonVarNodeList::iterator iter = this->readlist->begin(); + iter != this->readlist->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->writelist) { + for(WriteIonVarNodeList::iterator iter = this->writelist->begin(); + iter != this->writelist->end(); iter++) { + (*iter)->accept(v); + } + } + + if (this->valence) { + this->valence->accept(v); + } + + } + + /* constructor for NrnUseion ast node */ + NrnUseionNode::NrnUseionNode(NameNode * name, ReadIonVarNodeList * readlist, WriteIonVarNodeList * writelist, ValenceNode * valence) { + this->name = name; + this->readlist = readlist; + this->writelist = writelist; + this->valence = valence; + } + + /* copy constructor for NrnUseion ast node */ + NrnUseionNode::NrnUseionNode(const NrnUseionNode& obj) { + if(obj.name) + this->name = obj.name->clone(); + else + this->name = NULL; + // cloning list + if (obj.readlist) { + this->readlist = new ReadIonVarNodeList(); + for(ReadIonVarNodeList::iterator iter = obj.readlist->begin(); + iter != obj.readlist->end(); iter++) { + this->readlist->push_back((*iter)->clone()); + } + } + else + this->readlist = NULL; + + // cloning list + if (obj.writelist) { + this->writelist = new WriteIonVarNodeList(); + for(WriteIonVarNodeList::iterator iter = obj.writelist->begin(); + iter != obj.writelist->end(); iter++) { + this->writelist->push_back((*iter)->clone()); + } + } + else + this->writelist = NULL; + + if(obj.valence) + this->valence = obj.valence->clone(); + else + this->valence = NULL; + } + + /* destructor for NrnUseion ast node */ + NrnUseionNode::~NrnUseionNode() { + if(name) + delete name; + if (readlist) { + for(ReadIonVarNodeList::iterator iter = this->readlist->begin(); + iter != this->readlist->end(); iter++) { + delete (*iter); + } + } + + if(readlist) + delete readlist; + if (writelist) { + for(WriteIonVarNodeList::iterator iter = this->writelist->begin(); + iter != this->writelist->end(); iter++) { + delete (*iter); + } + } + + if(writelist) + delete writelist; + if(valence) + delete valence; + } + + /* visit Children method for NrnNonspecific ast node */ + void NrnNonspecificNode::visitChildren(Visitor* v) { + if (this->currents) { + for(NonspeCurVarNodeList::iterator iter = this->currents->begin(); + iter != this->currents->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnNonspecific ast node */ + NrnNonspecificNode::NrnNonspecificNode(NonspeCurVarNodeList * currents) { + this->currents = currents; + } + + /* copy constructor for NrnNonspecific ast node */ + NrnNonspecificNode::NrnNonspecificNode(const NrnNonspecificNode& obj) { + // cloning list + if (obj.currents) { + this->currents = new NonspeCurVarNodeList(); + for(NonspeCurVarNodeList::iterator iter = obj.currents->begin(); + iter != obj.currents->end(); iter++) { + this->currents->push_back((*iter)->clone()); + } + } + else + this->currents = NULL; + + } + + /* destructor for NrnNonspecific ast node */ + NrnNonspecificNode::~NrnNonspecificNode() { + if (currents) { + for(NonspeCurVarNodeList::iterator iter = this->currents->begin(); + iter != this->currents->end(); iter++) { + delete (*iter); + } + } + + if(currents) + delete currents; + } + + /* visit Children method for NrnElctrodeCurrent ast node */ + void NrnElctrodeCurrentNode::visitChildren(Visitor* v) { + if (this->ecurrents) { + for(ElectrodeCurVarNodeList::iterator iter = this->ecurrents->begin(); + iter != this->ecurrents->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnElctrodeCurrent ast node */ + NrnElctrodeCurrentNode::NrnElctrodeCurrentNode(ElectrodeCurVarNodeList * ecurrents) { + this->ecurrents = ecurrents; + } + + /* copy constructor for NrnElctrodeCurrent ast node */ + NrnElctrodeCurrentNode::NrnElctrodeCurrentNode(const NrnElctrodeCurrentNode& obj) { + // cloning list + if (obj.ecurrents) { + this->ecurrents = new ElectrodeCurVarNodeList(); + for(ElectrodeCurVarNodeList::iterator iter = obj.ecurrents->begin(); + iter != obj.ecurrents->end(); iter++) { + this->ecurrents->push_back((*iter)->clone()); + } + } + else + this->ecurrents = NULL; + + } + + /* destructor for NrnElctrodeCurrent ast node */ + NrnElctrodeCurrentNode::~NrnElctrodeCurrentNode() { + if (ecurrents) { + for(ElectrodeCurVarNodeList::iterator iter = this->ecurrents->begin(); + iter != this->ecurrents->end(); iter++) { + delete (*iter); + } + } + + if(ecurrents) + delete ecurrents; + } + + /* visit Children method for NrnSection ast node */ + void NrnSectionNode::visitChildren(Visitor* v) { + if (this->sections) { + for(SectionVarNodeList::iterator iter = this->sections->begin(); + iter != this->sections->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnSection ast node */ + NrnSectionNode::NrnSectionNode(SectionVarNodeList * sections) { + this->sections = sections; + } + + /* copy constructor for NrnSection ast node */ + NrnSectionNode::NrnSectionNode(const NrnSectionNode& obj) { + // cloning list + if (obj.sections) { + this->sections = new SectionVarNodeList(); + for(SectionVarNodeList::iterator iter = obj.sections->begin(); + iter != obj.sections->end(); iter++) { + this->sections->push_back((*iter)->clone()); + } + } + else + this->sections = NULL; + + } + + /* destructor for NrnSection ast node */ + NrnSectionNode::~NrnSectionNode() { + if (sections) { + for(SectionVarNodeList::iterator iter = this->sections->begin(); + iter != this->sections->end(); iter++) { + delete (*iter); + } + } + + if(sections) + delete sections; + } + + /* visit Children method for NrnRange ast node */ + void NrnRangeNode::visitChildren(Visitor* v) { + if (this->range_vars) { + for(RangeVarNodeList::iterator iter = this->range_vars->begin(); + iter != this->range_vars->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnRange ast node */ + NrnRangeNode::NrnRangeNode(RangeVarNodeList * range_vars) { + this->range_vars = range_vars; + } + + /* copy constructor for NrnRange ast node */ + NrnRangeNode::NrnRangeNode(const NrnRangeNode& obj) { + // cloning list + if (obj.range_vars) { + this->range_vars = new RangeVarNodeList(); + for(RangeVarNodeList::iterator iter = obj.range_vars->begin(); + iter != obj.range_vars->end(); iter++) { + this->range_vars->push_back((*iter)->clone()); + } + } + else + this->range_vars = NULL; + + } + + /* destructor for NrnRange ast node */ + NrnRangeNode::~NrnRangeNode() { + if (range_vars) { + for(RangeVarNodeList::iterator iter = this->range_vars->begin(); + iter != this->range_vars->end(); iter++) { + delete (*iter); + } + } + + if(range_vars) + delete range_vars; + } + + /* visit Children method for NrnGlobal ast node */ + void NrnGlobalNode::visitChildren(Visitor* v) { + if (this->global_vars) { + for(GlobalVarNodeList::iterator iter = this->global_vars->begin(); + iter != this->global_vars->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnGlobal ast node */ + NrnGlobalNode::NrnGlobalNode(GlobalVarNodeList * global_vars) { + this->global_vars = global_vars; + } + + /* copy constructor for NrnGlobal ast node */ + NrnGlobalNode::NrnGlobalNode(const NrnGlobalNode& obj) { + // cloning list + if (obj.global_vars) { + this->global_vars = new GlobalVarNodeList(); + for(GlobalVarNodeList::iterator iter = obj.global_vars->begin(); + iter != obj.global_vars->end(); iter++) { + this->global_vars->push_back((*iter)->clone()); + } + } + else + this->global_vars = NULL; + + } + + /* destructor for NrnGlobal ast node */ + NrnGlobalNode::~NrnGlobalNode() { + if (global_vars) { + for(GlobalVarNodeList::iterator iter = this->global_vars->begin(); + iter != this->global_vars->end(); iter++) { + delete (*iter); + } + } + + if(global_vars) + delete global_vars; + } + + /* visit Children method for NrnPointer ast node */ + void NrnPointerNode::visitChildren(Visitor* v) { + if (this->pointers) { + for(PointerVarNodeList::iterator iter = this->pointers->begin(); + iter != this->pointers->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnPointer ast node */ + NrnPointerNode::NrnPointerNode(PointerVarNodeList * pointers) { + this->pointers = pointers; + } + + /* copy constructor for NrnPointer ast node */ + NrnPointerNode::NrnPointerNode(const NrnPointerNode& obj) { + // cloning list + if (obj.pointers) { + this->pointers = new PointerVarNodeList(); + for(PointerVarNodeList::iterator iter = obj.pointers->begin(); + iter != obj.pointers->end(); iter++) { + this->pointers->push_back((*iter)->clone()); + } + } + else + this->pointers = NULL; + + } + + /* destructor for NrnPointer ast node */ + NrnPointerNode::~NrnPointerNode() { + if (pointers) { + for(PointerVarNodeList::iterator iter = this->pointers->begin(); + iter != this->pointers->end(); iter++) { + delete (*iter); + } + } + + if(pointers) + delete pointers; + } + + /* visit Children method for NrnBbcorePtr ast node */ + void NrnBbcorePtrNode::visitChildren(Visitor* v) { + if (this->bbcore_pointers) { + for(BbcorePointerVarNodeList::iterator iter = this->bbcore_pointers->begin(); + iter != this->bbcore_pointers->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnBbcorePtr ast node */ + NrnBbcorePtrNode::NrnBbcorePtrNode(BbcorePointerVarNodeList * bbcore_pointers) { + this->bbcore_pointers = bbcore_pointers; + } + + /* copy constructor for NrnBbcorePtr ast node */ + NrnBbcorePtrNode::NrnBbcorePtrNode(const NrnBbcorePtrNode& obj) { + // cloning list + if (obj.bbcore_pointers) { + this->bbcore_pointers = new BbcorePointerVarNodeList(); + for(BbcorePointerVarNodeList::iterator iter = obj.bbcore_pointers->begin(); + iter != obj.bbcore_pointers->end(); iter++) { + this->bbcore_pointers->push_back((*iter)->clone()); + } + } + else + this->bbcore_pointers = NULL; + + } + + /* destructor for NrnBbcorePtr ast node */ + NrnBbcorePtrNode::~NrnBbcorePtrNode() { + if (bbcore_pointers) { + for(BbcorePointerVarNodeList::iterator iter = this->bbcore_pointers->begin(); + iter != this->bbcore_pointers->end(); iter++) { + delete (*iter); + } + } + + if(bbcore_pointers) + delete bbcore_pointers; + } + + /* visit Children method for NrnExternal ast node */ + void NrnExternalNode::visitChildren(Visitor* v) { + if (this->externals) { + for(ExternVarNodeList::iterator iter = this->externals->begin(); + iter != this->externals->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnExternal ast node */ + NrnExternalNode::NrnExternalNode(ExternVarNodeList * externals) { + this->externals = externals; + } + + /* copy constructor for NrnExternal ast node */ + NrnExternalNode::NrnExternalNode(const NrnExternalNode& obj) { + // cloning list + if (obj.externals) { + this->externals = new ExternVarNodeList(); + for(ExternVarNodeList::iterator iter = obj.externals->begin(); + iter != obj.externals->end(); iter++) { + this->externals->push_back((*iter)->clone()); + } + } + else + this->externals = NULL; + + } + + /* destructor for NrnExternal ast node */ + NrnExternalNode::~NrnExternalNode() { + if (externals) { + for(ExternVarNodeList::iterator iter = this->externals->begin(); + iter != this->externals->end(); iter++) { + delete (*iter); + } + } + + if(externals) + delete externals; + } + + /* visit Children method for NrnThreadSafe ast node */ + void NrnThreadSafeNode::visitChildren(Visitor* v) { + if (this->threadsafe) { + for(ThreadsafeVarNodeList::iterator iter = this->threadsafe->begin(); + iter != this->threadsafe->end(); iter++) { + (*iter)->accept(v); + } + } + + } + + /* constructor for NrnThreadSafe ast node */ + NrnThreadSafeNode::NrnThreadSafeNode(ThreadsafeVarNodeList * threadsafe) { + this->threadsafe = threadsafe; + } + + /* copy constructor for NrnThreadSafe ast node */ + NrnThreadSafeNode::NrnThreadSafeNode(const NrnThreadSafeNode& obj) { + // cloning list + if (obj.threadsafe) { + this->threadsafe = new ThreadsafeVarNodeList(); + for(ThreadsafeVarNodeList::iterator iter = obj.threadsafe->begin(); + iter != obj.threadsafe->end(); iter++) { + this->threadsafe->push_back((*iter)->clone()); + } + } + else + this->threadsafe = NULL; + + } + + /* destructor for NrnThreadSafe ast node */ + NrnThreadSafeNode::~NrnThreadSafeNode() { + if (threadsafe) { + for(ThreadsafeVarNodeList::iterator iter = this->threadsafe->begin(); + iter != this->threadsafe->end(); iter++) { + delete (*iter); + } + } + + if(threadsafe) + delete threadsafe; + } + + /* visit Children method for Verbatim ast node */ + void VerbatimNode::visitChildren(Visitor* v) { + statement->accept(v); + } + + /* constructor for Verbatim ast node */ + VerbatimNode::VerbatimNode(StringNode * statement) { + this->statement = statement; + } + + /* copy constructor for Verbatim ast node */ + VerbatimNode::VerbatimNode(const VerbatimNode& obj) { + if(obj.statement) + this->statement = obj.statement->clone(); + else + this->statement = NULL; + } + + /* destructor for Verbatim ast node */ + VerbatimNode::~VerbatimNode() { + if(statement) + delete statement; + } + + /* visit Children method for Comment ast node */ + void CommentNode::visitChildren(Visitor* v) { + comment->accept(v); + } + + /* constructor for Comment ast node */ + CommentNode::CommentNode(StringNode * comment) { + this->comment = comment; + } + + /* copy constructor for Comment ast node */ + CommentNode::CommentNode(const CommentNode& obj) { + if(obj.comment) + this->comment = obj.comment->clone(); + else + this->comment = NULL; + } + + /* destructor for Comment ast node */ + CommentNode::~CommentNode() { + if(comment) + delete comment; + } + + /* visit Children method for Valence ast node */ + void ValenceNode::visitChildren(Visitor* v) { + type->accept(v); + value->accept(v); + } + + /* constructor for Valence ast node */ + ValenceNode::ValenceNode(NameNode * type, DoubleNode * value) { + this->type = type; + this->value = value; + } + + /* copy constructor for Valence ast node */ + ValenceNode::ValenceNode(const ValenceNode& obj) { + if(obj.type) + this->type = obj.type->clone(); + else + this->type = NULL; + if(obj.value) + this->value = obj.value->clone(); + else + this->value = NULL; + } + + /* destructor for Valence ast node */ + ValenceNode::~ValenceNode() { + if(type) + delete type; + if(value) + delete value; + } + +} //namespace ast diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp new file mode 100644 index 0000000000..b4bc67eff4 --- /dev/null +++ b/src/nmodl/ast/ast.hpp @@ -0,0 +1,3292 @@ +#ifndef __AST_HPP +#define __AST_HPP + +/* headers */ +#include +#include +#include +#include +#include +#include + +#include "utils/commonutils.hpp" +#include "lexer/modtoken.hpp" + +namespace ast { + + /* enumaration of all binary operators in the language */ + typedef enum {BOP_ADDITION, BOP_SUBTRACTION, BOP_MULTIPLICATION, BOP_DIVISION, BOP_POWER, BOP_AND, BOP_OR, BOP_GREATER, BOP_LESS, BOP_GREATER_EQUAL, BOP_LESS_EQUAL, BOP_ASSIGN, BOP_NOT_EQUAL, BOP_EXACT_EQUAL} BinaryOp; + static const std::string BinaryOpNames[] = {"+", "-", "*", "/", "^", "&&", "||", ">", "<", ">=", "<=", "=", "!=", "=="}; + + /* enumaration of all unary operators in the language */ + typedef enum {UOP_NOT, UOP_NEGATION} UnaryOp; + static const std::string UnaryOpNames[] = {"!", "-"}; + + /* enumaration of types used in partial equation */ + typedef enum {PEQ_FIRST, PEQ_LAST} FirstLastType; + static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; + + /* enumaration of queue types */ + typedef enum {PUT_QUEUE, GET_QUEUE} QueueType; + static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; + + /* enumaration of type used for BEFORE or AFTER block */ + typedef enum {BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP} BAType; + static const std::string BATypeNames[] = {"BREAKPOINT", "SOLVE", "INITIAL", "STEP"}; + + /* enumaration of type used for UNIT_ON or UNIT_OFF state*/ + typedef enum {UNIT_ON, UNIT_OFF} UnitStateType; + static const std::string UnitStateTypeNames[] = {"UNITSON", "UNITSOFF"}; + + /* enumaration of type used for Reaction statement */ + typedef enum {LTMINUSGT, LTLT, MINUSGT} ReactionOp; + static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; + + /* forward declarations of AST Node classes */ + class ExpressionNode; + class StatementNode; + class IdentifierNode; + class BlockNode; + class NumberNode; + class StringNode; + class IntegerNode; + class FloatNode; + class DoubleNode; + class BooleanNode; + class NameNode; + class PrimeNameNode; + class VarNameNode; + class IndexedNameNode; + class UnitNode; + class UnitStateNode; + class DoubleUnitNode; + class ArgumentNode; + class LocalVariableNode; + class LocalListStatementNode; + class LimitsNode; + class NumberRangeNode; + class ProgramNode; + class ModelNode; + class DefineNode; + class IncludeNode; + class ParamBlockNode; + class ParamAssignNode; + class StepBlockNode; + class SteppedNode; + class IndependentBlockNode; + class IndependentDefNode; + class DependentDefNode; + class DependentBlockNode; + class StateBlockNode; + class PlotBlockNode; + class PlotDeclarationNode; + class PlotVariableNode; + class InitialBlockNode; + class ConstructorBlockNode; + class DestructorBlockNode; + class ConductanceHintNode; + class ExpressionStatementNode; + class ProtectStatementNode; + class StatementBlockNode; + class BinaryOperatorNode; + class UnaryOperatorNode; + class ReactionOperatorNode; + class BinaryExpressionNode; + class UnaryExpressionNode; + class NonLinEuationNode; + class LinEquationNode; + class FunctionCallNode; + class FromStatementNode; + class ForAllStatementNode; + class WhileStatementNode; + class IfStatementNode; + class ElseIfStatementNode; + class ElseStatementNode; + class DerivativeBlockNode; + class LinearBlockNode; + class NonLinearBlockNode; + class DiscreteBlockNode; + class PartialBlockNode; + class PartialEquationNode; + class FirstLastTypeIndexNode; + class PartialBoundaryNode; + class FunctionTableBlockNode; + class FunctionBlockNode; + class ProcedureBlockNode; + class NetReceiveBlockNode; + class SolveBlockNode; + class BreakpointBlockNode; + class TerminalBlockNode; + class BeforeBlockNode; + class AfterBlockNode; + class BABlockTypeNode; + class BABlockNode; + class WatchStatementNode; + class WatchNode; + class ForNetconNode; + class MutexLockNode; + class MutexUnlockNode; + class ResetNode; + class SensNode; + class ConserveNode; + class CompartmentNode; + class LDifuseNode; + class KineticBlockNode; + class ReactionStatementNode; + class ReactVarNameNode; + class LagStatementNode; + class QueueStatementNode; + class QueueExpressionTypeNode; + class MatchBlockNode; + class MatchNode; + class UnitBlockNode; + class UnitDefNode; + class FactordefNode; + class ConstantStatementNode; + class ConstantBlockNode; + class TableStatementNode; + class NeuronBlockNode; + class ReadIonVarNode; + class WriteIonVarNode; + class NonspeCurVarNode; + class ElectrodeCurVarNode; + class SectionVarNode; + class RangeVarNode; + class GlobalVarNode; + class PointerVarNode; + class BbcorePointerVarNode; + class ExternVarNode; + class ThreadsafeVarNode; + class NrnSuffixNode; + class NrnUseionNode; + class NrnNonspecificNode; + class NrnElctrodeCurrentNode; + class NrnSectionNode; + class NrnRangeNode; + class NrnGlobalNode; + class NrnPointerNode; + class NrnBbcorePtrNode; + class NrnExternalNode; + class NrnThreadSafeNode; + class VerbatimNode; + class CommentNode; + class ValenceNode; + + /* abstract node classes */ + class ASTNode; + class ExpressionNode; + class NumberNode; + class IdentifierNode; + class StatementNode; + class BlockNode; + + /* std::vector for convenience */ + typedef std::vector ExpressionNodeList; + typedef std::vector StatementNodeList; + typedef std::vector IdentifierNodeList; + typedef std::vector BlockNodeList; + typedef std::vector NumberNodeList; + typedef std::vector StringNodeList; + typedef std::vector IntegerNodeList; + typedef std::vector FloatNodeList; + typedef std::vector DoubleNodeList; + typedef std::vector BooleanNodeList; + typedef std::vector NameNodeList; + typedef std::vector PrimeNameNodeList; + typedef std::vector VarNameNodeList; + typedef std::vector IndexedNameNodeList; + typedef std::vector UnitNodeList; + typedef std::vector UnitStateNodeList; + typedef std::vector DoubleUnitNodeList; + typedef std::vector ArgumentNodeList; + typedef std::vector LocalVariableNodeList; + typedef std::vector LocalListStatementNodeList; + typedef std::vector LimitsNodeList; + typedef std::vector NumberRangeNodeList; + typedef std::vector ProgramNodeList; + typedef std::vector ModelNodeList; + typedef std::vector DefineNodeList; + typedef std::vector IncludeNodeList; + typedef std::vector ParamBlockNodeList; + typedef std::vector ParamAssignNodeList; + typedef std::vector StepBlockNodeList; + typedef std::vector SteppedNodeList; + typedef std::vector IndependentBlockNodeList; + typedef std::vector IndependentDefNodeList; + typedef std::vector DependentDefNodeList; + typedef std::vector DependentBlockNodeList; + typedef std::vector StateBlockNodeList; + typedef std::vector PlotBlockNodeList; + typedef std::vector PlotDeclarationNodeList; + typedef std::vector PlotVariableNodeList; + typedef std::vector InitialBlockNodeList; + typedef std::vector ConstructorBlockNodeList; + typedef std::vector DestructorBlockNodeList; + typedef std::vector ConductanceHintNodeList; + typedef std::vector ExpressionStatementNodeList; + typedef std::vector ProtectStatementNodeList; + typedef std::vector StatementBlockNodeList; + typedef std::vector BinaryOperatorNodeList; + typedef std::vector UnaryOperatorNodeList; + typedef std::vector ReactionOperatorNodeList; + typedef std::vector BinaryExpressionNodeList; + typedef std::vector UnaryExpressionNodeList; + typedef std::vector NonLinEuationNodeList; + typedef std::vector LinEquationNodeList; + typedef std::vector FunctionCallNodeList; + typedef std::vector FromStatementNodeList; + typedef std::vector ForAllStatementNodeList; + typedef std::vector WhileStatementNodeList; + typedef std::vector IfStatementNodeList; + typedef std::vector ElseIfStatementNodeList; + typedef std::vector ElseStatementNodeList; + typedef std::vector DerivativeBlockNodeList; + typedef std::vector LinearBlockNodeList; + typedef std::vector NonLinearBlockNodeList; + typedef std::vector DiscreteBlockNodeList; + typedef std::vector PartialBlockNodeList; + typedef std::vector PartialEquationNodeList; + typedef std::vector FirstLastTypeIndexNodeList; + typedef std::vector PartialBoundaryNodeList; + typedef std::vector FunctionTableBlockNodeList; + typedef std::vector FunctionBlockNodeList; + typedef std::vector ProcedureBlockNodeList; + typedef std::vector NetReceiveBlockNodeList; + typedef std::vector SolveBlockNodeList; + typedef std::vector BreakpointBlockNodeList; + typedef std::vector TerminalBlockNodeList; + typedef std::vector BeforeBlockNodeList; + typedef std::vector AfterBlockNodeList; + typedef std::vector BABlockTypeNodeList; + typedef std::vector BABlockNodeList; + typedef std::vector WatchStatementNodeList; + typedef std::vector WatchNodeList; + typedef std::vector ForNetconNodeList; + typedef std::vector MutexLockNodeList; + typedef std::vector MutexUnlockNodeList; + typedef std::vector ResetNodeList; + typedef std::vector SensNodeList; + typedef std::vector ConserveNodeList; + typedef std::vector CompartmentNodeList; + typedef std::vector LDifuseNodeList; + typedef std::vector KineticBlockNodeList; + typedef std::vector ReactionStatementNodeList; + typedef std::vector ReactVarNameNodeList; + typedef std::vector LagStatementNodeList; + typedef std::vector QueueStatementNodeList; + typedef std::vector QueueExpressionTypeNodeList; + typedef std::vector MatchBlockNodeList; + typedef std::vector MatchNodeList; + typedef std::vector UnitBlockNodeList; + typedef std::vector UnitDefNodeList; + typedef std::vector FactordefNodeList; + typedef std::vector ConstantStatementNodeList; + typedef std::vector ConstantBlockNodeList; + typedef std::vector TableStatementNodeList; + typedef std::vector NeuronBlockNodeList; + typedef std::vector ReadIonVarNodeList; + typedef std::vector WriteIonVarNodeList; + typedef std::vector NonspeCurVarNodeList; + typedef std::vector ElectrodeCurVarNodeList; + typedef std::vector SectionVarNodeList; + typedef std::vector RangeVarNodeList; + typedef std::vector GlobalVarNodeList; + typedef std::vector PointerVarNodeList; + typedef std::vector BbcorePointerVarNodeList; + typedef std::vector ExternVarNodeList; + typedef std::vector ThreadsafeVarNodeList; + typedef std::vector NrnSuffixNodeList; + typedef std::vector NrnUseionNodeList; + typedef std::vector NrnNonspecificNodeList; + typedef std::vector NrnElctrodeCurrentNodeList; + typedef std::vector NrnSectionNodeList; + typedef std::vector NrnRangeNodeList; + typedef std::vector NrnGlobalNodeList; + typedef std::vector NrnPointerNodeList; + typedef std::vector NrnBbcorePtrNodeList; + typedef std::vector NrnExternalNodeList; + typedef std::vector NrnThreadSafeNodeList; + typedef std::vector VerbatimNodeList; + typedef std::vector CommentNodeList; + typedef std::vector ValenceNodeList; + typedef std::vector ASTNodeList; + typedef std::vector ExpressionNodeList; + typedef std::vector NumberNodeList; + typedef std::vector IdentifierNodeList; + typedef std::vector StatementNodeList; + typedef std::vector BlockNodeList; + /* basic types */ + + /* @todo: how to do this */ + #include "visitors/visitor.hpp" + /* check order of inclusion */ + #ifdef YYSTYPE_IS_TRIVIAL + #error Make sure to include this file BEFORE parser.hpp + #endif + + /* define YYSTYPE (type of all $$, $N variables) as a union + * of all necessary pointers (including lists). This will + * be used in %type specifiers and in lexer. + */ + typedef union { + ExpressionNode *expression_ptr; + StatementNode *statement_ptr; + IdentifierNode *identifier_ptr; + BlockNode *block_ptr; + NumberNode *number_ptr; + StringNode *string_ptr; + IntegerNode *integer_ptr; + NameNode *name_ptr; + FloatNode *float_ptr; + DoubleNode *double_ptr; + BooleanNode *boolean_ptr; + PrimeNameNode *primename_ptr; + VarNameNode *varname_ptr; + IndexedNameNode *indexedname_ptr; + UnitNode *unit_ptr; + UnitStateNode *unitstate_ptr; + DoubleUnitNode *doubleunit_ptr; + ArgumentNode *argument_ptr; + LocalVariableNode *localvariable_ptr; + LocalListStatementNode *localliststatement_ptr; + LocalVariableNodeList *localvariable_list_ptr; + LimitsNode *limits_ptr; + NumberRangeNode *numberrange_ptr; + ProgramNode *program_ptr; + StatementNodeList *statement_list_ptr; + BlockNodeList *block_list_ptr; + ModelNode *model_ptr; + DefineNode *define_ptr; + IncludeNode *include_ptr; + ParamBlockNode *paramblock_ptr; + ParamAssignNodeList *paramassign_list_ptr; + ParamAssignNode *paramassign_ptr; + StepBlockNode *stepblock_ptr; + SteppedNodeList *stepped_list_ptr; + SteppedNode *stepped_ptr; + NumberNodeList *number_list_ptr; + IndependentBlockNode *independentblock_ptr; + IndependentDefNodeList *independentdef_list_ptr; + IndependentDefNode *independentdef_ptr; + DependentDefNode *dependentdef_ptr; + DependentBlockNode *dependentblock_ptr; + DependentDefNodeList *dependentdef_list_ptr; + StateBlockNode *stateblock_ptr; + PlotBlockNode *plotblock_ptr; + PlotDeclarationNode *plotdeclaration_ptr; + PlotVariableNodeList *plotvariable_list_ptr; + PlotVariableNode *plotvariable_ptr; + InitialBlockNode *initialblock_ptr; + StatementBlockNode *statementblock_ptr; + ConstructorBlockNode *constructorblock_ptr; + DestructorBlockNode *destructorblock_ptr; + ConductanceHintNode *conductancehint_ptr; + ExpressionStatementNode *expressionstatement_ptr; + ProtectStatementNode *protectstatement_ptr; + BinaryOperatorNode *binaryoperator_ptr; + UnaryOperatorNode *unaryoperator_ptr; + ReactionOperatorNode *reactionoperator_ptr; + BinaryExpressionNode *binaryexpression_ptr; + UnaryExpressionNode *unaryexpression_ptr; + NonLinEuationNode *nonlineuation_ptr; + LinEquationNode *linequation_ptr; + FunctionCallNode *functioncall_ptr; + ExpressionNodeList *expression_list_ptr; + FromStatementNode *fromstatement_ptr; + ForAllStatementNode *forallstatement_ptr; + WhileStatementNode *whilestatement_ptr; + IfStatementNode *ifstatement_ptr; + ElseIfStatementNodeList *elseifstatement_list_ptr; + ElseStatementNode *elsestatement_ptr; + ElseIfStatementNode *elseifstatement_ptr; + DerivativeBlockNode *derivativeblock_ptr; + LinearBlockNode *linearblock_ptr; + NameNodeList *name_list_ptr; + NonLinearBlockNode *nonlinearblock_ptr; + DiscreteBlockNode *discreteblock_ptr; + PartialBlockNode *partialblock_ptr; + PartialEquationNode *partialequation_ptr; + FirstLastTypeIndexNode *firstlasttypeindex_ptr; + PartialBoundaryNode *partialboundary_ptr; + FunctionTableBlockNode *functiontableblock_ptr; + ArgumentNodeList *argument_list_ptr; + FunctionBlockNode *functionblock_ptr; + ProcedureBlockNode *procedureblock_ptr; + NetReceiveBlockNode *netreceiveblock_ptr; + SolveBlockNode *solveblock_ptr; + BreakpointBlockNode *breakpointblock_ptr; + TerminalBlockNode *terminalblock_ptr; + BeforeBlockNode *beforeblock_ptr; + BABlockNode *bablock_ptr; + AfterBlockNode *afterblock_ptr; + BABlockTypeNode *bablocktype_ptr; + WatchStatementNode *watchstatement_ptr; + WatchNodeList *watch_list_ptr; + WatchNode *watch_ptr; + ForNetconNode *fornetcon_ptr; + MutexLockNode *mutexlock_ptr; + MutexUnlockNode *mutexunlock_ptr; + ResetNode *reset_ptr; + SensNode *sens_ptr; + VarNameNodeList *varname_list_ptr; + ConserveNode *conserve_ptr; + CompartmentNode *compartment_ptr; + LDifuseNode *ldifuse_ptr; + KineticBlockNode *kineticblock_ptr; + ReactionStatementNode *reactionstatement_ptr; + ReactVarNameNode *reactvarname_ptr; + LagStatementNode *lagstatement_ptr; + QueueStatementNode *queuestatement_ptr; + QueueExpressionTypeNode *queueexpressiontype_ptr; + MatchBlockNode *matchblock_ptr; + MatchNodeList *match_list_ptr; + MatchNode *match_ptr; + UnitBlockNode *unitblock_ptr; + UnitDefNode *unitdef_ptr; + FactordefNode *factordef_ptr; + ConstantStatementNode *constantstatement_ptr; + ConstantBlockNode *constantblock_ptr; + ConstantStatementNodeList *constantstatement_list_ptr; + TableStatementNode *tablestatement_ptr; + NeuronBlockNode *neuronblock_ptr; + ReadIonVarNode *readionvar_ptr; + WriteIonVarNode *writeionvar_ptr; + NonspeCurVarNode *nonspecurvar_ptr; + ElectrodeCurVarNode *electrodecurvar_ptr; + SectionVarNode *sectionvar_ptr; + RangeVarNode *rangevar_ptr; + GlobalVarNode *globalvar_ptr; + PointerVarNode *pointervar_ptr; + BbcorePointerVarNode *bbcorepointervar_ptr; + ExternVarNode *externvar_ptr; + ThreadsafeVarNode *threadsafevar_ptr; + NrnSuffixNode *nrnsuffix_ptr; + NrnUseionNode *nrnuseion_ptr; + ReadIonVarNodeList *readionvar_list_ptr; + WriteIonVarNodeList *writeionvar_list_ptr; + ValenceNode *valence_ptr; + NrnNonspecificNode *nrnnonspecific_ptr; + NonspeCurVarNodeList *nonspecurvar_list_ptr; + NrnElctrodeCurrentNode *nrnelctrodecurrent_ptr; + ElectrodeCurVarNodeList *electrodecurvar_list_ptr; + NrnSectionNode *nrnsection_ptr; + SectionVarNodeList *sectionvar_list_ptr; + NrnRangeNode *nrnrange_ptr; + RangeVarNodeList *rangevar_list_ptr; + NrnGlobalNode *nrnglobal_ptr; + GlobalVarNodeList *globalvar_list_ptr; + NrnPointerNode *nrnpointer_ptr; + PointerVarNodeList *pointervar_list_ptr; + NrnBbcorePtrNode *nrnbbcoreptr_ptr; + BbcorePointerVarNodeList *bbcorepointervar_list_ptr; + NrnExternalNode *nrnexternal_ptr; + ExternVarNodeList *externvar_list_ptr; + NrnThreadSafeNode *nrnthreadsafe_ptr; + ThreadsafeVarNodeList *threadsafevar_list_ptr; + VerbatimNode *verbatim_ptr; + CommentNode *comment_ptr; + ASTNode *ast_ptr; + char* base_char_ptr; + int base_int; + } astnode_union; + + //#define YYSTYPE astnode_union + + +/* define abstract base class for all AST Nodes + * this also serves to define the visitable objects. + */ +class ASTNode { + + public: + /* all AST nodes have a member which stores their + * basetype (int, bool, none, object). Further type + * information will come from the symbol table. + * BaseType basetype; + */ + + /* all AST nodes provide visit children and accept methods */ + virtual void visitChildren(Visitor* v) = 0; + virtual void accept(Visitor* v) = 0; + virtual std::string getNodeType() = 0; + /* @todo: please revisit this. adding quickly for symtab */ + virtual std::string getName() { return "";} + virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ return NULL; } + //virtual ASTNode* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } +}; + + + + /* Define all other AST nodes */ + + /* ast Node for Expression */ + class ExpressionNode : public ASTNode { + public: + + virtual ~ExpressionNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitExpressionNode(this); } + virtual std::string getNodeType() { return "Expression"; } + virtual ExpressionNode* clone() { return new ExpressionNode(*this); } + }; + + /* ast Node for Statement */ + class StatementNode : public ASTNode { + public: + + virtual ~StatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStatementNode(this); } + virtual std::string getNodeType() { return "Statement"; } + virtual StatementNode* clone() { return new StatementNode(*this); } + }; + + /* ast Node for Identifier */ + class IdentifierNode : public ExpressionNode { + public: + + virtual ~IdentifierNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIdentifierNode(this); } + virtual std::string getNodeType() { return "Identifier"; } + virtual IdentifierNode* clone() { return new IdentifierNode(*this); } + virtual void setName(std::string name) { std::cout << "ERROR : setName() not implemented! "; abort(); } + }; + + /* ast Node for Block */ + class BlockNode : public ExpressionNode { + public: + + virtual ~BlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBlockNode(this); } + virtual std::string getNodeType() { return "Block"; } + virtual BlockNode* clone() { return new BlockNode(*this); } + }; + + /* ast Node for Number */ + class NumberNode : public ExpressionNode { + public: + + virtual ~NumberNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNumberNode(this); } + virtual std::string getNodeType() { return "Number"; } + virtual NumberNode* clone() { return new NumberNode(*this); } + virtual void negate() { std::cout << "ERROR : negate() not implemented! "; abort(); } + }; + + /* ast Node for String */ + class StringNode : public ExpressionNode { + public: + /* member variables */ + std::string value; + ModToken *token; + + /* constructor */ + StringNode(std::string value); + + /* copy constructor */ + StringNode(const StringNode& obj); + + virtual ~StringNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStringNode(this); } + virtual std::string getNodeType() { return "String"; } + virtual StringNode* clone() { return new StringNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + std::string eval() { return value; } + virtual void set(std::string _value) { value = _value; } + }; + + /* ast Node for Integer */ + class IntegerNode : public NumberNode { + public: + /* member variables */ + int value; + NameNode *macroname; + ModToken *token; + + /* constructor */ + IntegerNode(int value, NameNode *macroname); + + /* copy constructor */ + IntegerNode(const IntegerNode& obj); + + virtual ~IntegerNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIntegerNode(this); } + virtual std::string getNodeType() { return "Integer"; } + virtual IntegerNode* clone() { return new IntegerNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + void negate() { value = -value; } + int eval() { return value; } + virtual void set(int _value) { value = _value; } + }; + + /* ast Node for Float */ + class FloatNode : public NumberNode { + public: + /* member variables */ + float value; + + /* constructor */ + FloatNode(float value); + + /* copy constructor */ + FloatNode(const FloatNode& obj); + + virtual ~FloatNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFloatNode(this); } + virtual std::string getNodeType() { return "Float"; } + virtual FloatNode* clone() { return new FloatNode(*this); } + void negate() { value = -value; } + float eval() { return value; } + virtual void set(float _value) { value = _value; } + }; + + /* ast Node for Double */ + class DoubleNode : public NumberNode { + public: + /* member variables */ + double value; + ModToken *token; + + /* constructor */ + DoubleNode(double value); + + /* copy constructor */ + DoubleNode(const DoubleNode& obj); + + virtual ~DoubleNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDoubleNode(this); } + virtual std::string getNodeType() { return "Double"; } + virtual DoubleNode* clone() { return new DoubleNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + void negate() { value = -value; } + double eval() { return value; } + virtual void set(double _value) { value = _value; } + }; + + /* ast Node for Boolean */ + class BooleanNode : public NumberNode { + public: + /* member variables */ + int value; + + /* constructor */ + BooleanNode(int value); + + /* copy constructor */ + BooleanNode(const BooleanNode& obj); + + virtual ~BooleanNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBooleanNode(this); } + virtual std::string getNodeType() { return "Boolean"; } + virtual BooleanNode* clone() { return new BooleanNode(*this); } + void negate() { value = !value; } + bool eval() { return value; } + virtual void set(bool _value) { value = _value; } + }; + + /* ast Node for Name */ + class NameNode : public IdentifierNode { + public: + /* member variables */ + StringNode *value; + ModToken *token; + + /* constructor */ + NameNode(StringNode *value); + + /* copy constructor */ + NameNode(const NameNode& obj); + + virtual std::string getName() { return value->eval(); } + + virtual ~NameNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNameNode(this); } + virtual std::string getNodeType() { return "Name"; } + virtual NameNode* clone() { return new NameNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setName(std::string name) { value->set(name); } + }; + + /* ast Node for PrimeName */ + class PrimeNameNode : public IdentifierNode { + public: + /* member variables */ + StringNode *value; + IntegerNode *order; + ModToken *token; + + /* constructor */ + PrimeNameNode(StringNode *value, IntegerNode *order); + + /* copy constructor */ + PrimeNameNode(const PrimeNameNode& obj); + + virtual std::string getName() { return value->eval(); } + virtual int getOrder() { return order->eval(); } + + virtual ~PrimeNameNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPrimeNameNode(this); } + virtual std::string getNodeType() { return "PrimeName"; } + virtual PrimeNameNode* clone() { return new PrimeNameNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + }; + + /* ast Node for VarName */ + class VarNameNode : public IdentifierNode { + public: + /* member variables */ + IdentifierNode *name; + IntegerNode *at_index; + + /* constructor */ + VarNameNode(IdentifierNode *name, IntegerNode *at_index); + + /* copy constructor */ + VarNameNode(const VarNameNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~VarNameNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitVarNameNode(this); } + virtual std::string getNodeType() { return "VarName"; } + virtual VarNameNode* clone() { return new VarNameNode(*this); } + }; + + /* ast Node for IndexedName */ + class IndexedNameNode : public IdentifierNode { + public: + /* member variables */ + IdentifierNode *name; + ExpressionNode *index; + + /* constructor */ + IndexedNameNode(IdentifierNode *name, ExpressionNode *index); + + /* copy constructor */ + IndexedNameNode(const IndexedNameNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~IndexedNameNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIndexedNameNode(this); } + virtual std::string getNodeType() { return "IndexedName"; } + virtual IndexedNameNode* clone() { return new IndexedNameNode(*this); } + }; + + /* ast Node for Unit */ + class UnitNode : public ExpressionNode { + public: + /* member variables */ + StringNode *name; + + /* constructor */ + UnitNode(StringNode *name); + + /* copy constructor */ + UnitNode(const UnitNode& obj); + + virtual std::string getName() { return name->eval(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~UnitNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitNode(this); } + virtual std::string getNodeType() { return "Unit"; } + virtual UnitNode* clone() { return new UnitNode(*this); } + }; + + /* ast Node for UnitState */ + class UnitStateNode : public StatementNode { + public: + /* member variables */ + UnitStateType value; + + /* constructor */ + UnitStateNode(UnitStateType value); + + /* copy constructor */ + UnitStateNode(const UnitStateNode& obj); + + virtual ~UnitStateNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitStateNode(this); } + virtual std::string getNodeType() { return "UnitState"; } + virtual UnitStateNode* clone() { return new UnitStateNode(*this); } + std::string eval() { return UnitStateTypeNames[value]; } + }; + + /* ast Node for DoubleUnit */ + class DoubleUnitNode : public ExpressionNode { + public: + /* member variables */ + DoubleNode *values; + UnitNode *unit; + + /* constructor */ + DoubleUnitNode(DoubleNode *values, UnitNode *unit); + + /* copy constructor */ + DoubleUnitNode(const DoubleUnitNode& obj); + + virtual ~DoubleUnitNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDoubleUnitNode(this); } + virtual std::string getNodeType() { return "DoubleUnit"; } + virtual DoubleUnitNode* clone() { return new DoubleUnitNode(*this); } + }; + + /* ast Node for Argument */ + class ArgumentNode : public IdentifierNode { + public: + /* member variables */ + IdentifierNode *name; + UnitNode *unit; + + /* constructor */ + ArgumentNode(IdentifierNode *name, UnitNode *unit); + + /* copy constructor */ + ArgumentNode(const ArgumentNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ArgumentNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitArgumentNode(this); } + virtual std::string getNodeType() { return "Argument"; } + virtual ArgumentNode* clone() { return new ArgumentNode(*this); } + }; + + /* ast Node for LocalVariable */ + class LocalVariableNode : public ExpressionNode { + public: + /* member variables */ + IdentifierNode *name; + + /* constructor */ + LocalVariableNode(IdentifierNode *name); + + /* copy constructor */ + LocalVariableNode(const LocalVariableNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~LocalVariableNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLocalVariableNode(this); } + virtual std::string getNodeType() { return "LocalVariable"; } + virtual LocalVariableNode* clone() { return new LocalVariableNode(*this); } + }; + + /* ast Node for LocalListStatement */ + class LocalListStatementNode : public StatementNode { + public: + /* member variables */ + LocalVariableNodeList *variables; + + /* constructor */ + LocalListStatementNode(LocalVariableNodeList *variables); + + /* copy constructor */ + LocalListStatementNode(const LocalListStatementNode& obj); + + virtual ~LocalListStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLocalListStatementNode(this); } + virtual std::string getNodeType() { return "LocalListStatement"; } + virtual LocalListStatementNode* clone() { return new LocalListStatementNode(*this); } + }; + + /* ast Node for Limits */ + class LimitsNode : public ExpressionNode { + public: + /* member variables */ + DoubleNode *min; + DoubleNode *max; + + /* constructor */ + LimitsNode(DoubleNode *min, DoubleNode *max); + + /* copy constructor */ + LimitsNode(const LimitsNode& obj); + + virtual ~LimitsNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLimitsNode(this); } + virtual std::string getNodeType() { return "Limits"; } + virtual LimitsNode* clone() { return new LimitsNode(*this); } + }; + + /* ast Node for NumberRange */ + class NumberRangeNode : public ExpressionNode { + public: + /* member variables */ + NumberNode *min; + NumberNode *max; + + /* constructor */ + NumberRangeNode(NumberNode *min, NumberNode *max); + + /* copy constructor */ + NumberRangeNode(const NumberRangeNode& obj); + + virtual ~NumberRangeNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNumberRangeNode(this); } + virtual std::string getNodeType() { return "NumberRange"; } + virtual NumberRangeNode* clone() { return new NumberRangeNode(*this); } + }; + + /* ast Node for Program */ + class ProgramNode : public ASTNode { + public: + /* member variables */ + StatementNodeList *statements; + BlockNodeList *blocks; + void* symtab; + + /* constructor */ + ProgramNode(StatementNodeList *statements, BlockNodeList *blocks); + + /* copy constructor */ + ProgramNode(const ProgramNode& obj); + + void addStatement(StatementNode *s) { + statements->push_back(s); + } + + void addBlock(BlockNode *s) { + blocks->push_back(s); + } + ProgramNode() { + blocks = new BlockNodeList(); + statements = new StatementNodeList(); + } + + + virtual ~ProgramNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitProgramNode(this); } + virtual std::string getNodeType() { return "Program"; } + virtual ProgramNode* clone() { return new ProgramNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for Model */ + class ModelNode : public StatementNode { + public: + /* member variables */ + StringNode *title; + + /* constructor */ + ModelNode(StringNode *title); + + /* copy constructor */ + ModelNode(const ModelNode& obj); + + virtual ~ModelNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitModelNode(this); } + virtual std::string getNodeType() { return "Model"; } + virtual ModelNode* clone() { return new ModelNode(*this); } + }; + + /* ast Node for Define */ + class DefineNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + IntegerNode *value; + + /* constructor */ + DefineNode(NameNode *name, IntegerNode *value); + + /* copy constructor */ + DefineNode(const DefineNode& obj); + + virtual ~DefineNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDefineNode(this); } + virtual std::string getNodeType() { return "Define"; } + virtual DefineNode* clone() { return new DefineNode(*this); } + }; + + /* ast Node for Include */ + class IncludeNode : public StatementNode { + public: + /* member variables */ + StringNode *filename; + + /* constructor */ + IncludeNode(StringNode *filename); + + /* copy constructor */ + IncludeNode(const IncludeNode& obj); + + virtual ~IncludeNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIncludeNode(this); } + virtual std::string getNodeType() { return "Include"; } + virtual IncludeNode* clone() { return new IncludeNode(*this); } + }; + + /* ast Node for ParamBlock */ + class ParamBlockNode : public BlockNode { + public: + /* member variables */ + ParamAssignNodeList *statements; + void* symtab; + + /* constructor */ + ParamBlockNode(ParamAssignNodeList *statements); + + /* copy constructor */ + ParamBlockNode(const ParamBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~ParamBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitParamBlockNode(this); } + virtual std::string getNodeType() { return "ParamBlock"; } + virtual ParamBlockNode* clone() { return new ParamBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for ParamAssign */ + class ParamAssignNode : public StatementNode { + public: + /* member variables */ + IdentifierNode *name; + NumberNode *value; + UnitNode *unit; + LimitsNode *limit; + + /* constructor */ + ParamAssignNode(IdentifierNode *name, NumberNode *value, UnitNode *unit, LimitsNode *limit); + + /* copy constructor */ + ParamAssignNode(const ParamAssignNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ParamAssignNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitParamAssignNode(this); } + virtual std::string getNodeType() { return "ParamAssign"; } + virtual ParamAssignNode* clone() { return new ParamAssignNode(*this); } + }; + + /* ast Node for StepBlock */ + class StepBlockNode : public BlockNode { + public: + /* member variables */ + SteppedNodeList *statements; + void* symtab; + + /* constructor */ + StepBlockNode(SteppedNodeList *statements); + + /* copy constructor */ + StepBlockNode(const StepBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~StepBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStepBlockNode(this); } + virtual std::string getNodeType() { return "StepBlock"; } + virtual StepBlockNode* clone() { return new StepBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for Stepped */ + class SteppedNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + NumberNodeList *values; + UnitNode *unit; + + /* constructor */ + SteppedNode(NameNode *name, NumberNodeList *values, UnitNode *unit); + + /* copy constructor */ + SteppedNode(const SteppedNode& obj); + + virtual ~SteppedNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSteppedNode(this); } + virtual std::string getNodeType() { return "Stepped"; } + virtual SteppedNode* clone() { return new SteppedNode(*this); } + }; + + /* ast Node for IndependentBlock */ + class IndependentBlockNode : public BlockNode { + public: + /* member variables */ + IndependentDefNodeList *definitions; + void* symtab; + + /* constructor */ + IndependentBlockNode(IndependentDefNodeList *definitions); + + /* copy constructor */ + IndependentBlockNode(const IndependentBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~IndependentBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIndependentBlockNode(this); } + virtual std::string getNodeType() { return "IndependentBlock"; } + virtual IndependentBlockNode* clone() { return new IndependentBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for IndependentDef */ + class IndependentDefNode : public StatementNode { + public: + /* member variables */ + BooleanNode *sweep; + NameNode *name; + NumberNode *from; + NumberNode *to; + IntegerNode *with; + NumberNode *opstart; + UnitNode *unit; + + /* constructor */ + IndependentDefNode(BooleanNode *sweep, NameNode *name, NumberNode *from, NumberNode *to, IntegerNode *with, NumberNode *opstart, UnitNode *unit); + + /* copy constructor */ + IndependentDefNode(const IndependentDefNode& obj); + + virtual ~IndependentDefNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIndependentDefNode(this); } + virtual std::string getNodeType() { return "IndependentDef"; } + virtual IndependentDefNode* clone() { return new IndependentDefNode(*this); } + }; + + /* ast Node for DependentDef */ + class DependentDefNode : public StatementNode { + public: + /* member variables */ + IdentifierNode *name; + IntegerNode *index; + NumberNode *from; + NumberNode *to; + NumberNode *opstart; + UnitNode *unit; + DoubleNode *abstol; + + /* constructor */ + DependentDefNode(IdentifierNode *name, IntegerNode *index, NumberNode *from, NumberNode *to, NumberNode *opstart, UnitNode *unit, DoubleNode *abstol); + + /* copy constructor */ + DependentDefNode(const DependentDefNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~DependentDefNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDependentDefNode(this); } + virtual std::string getNodeType() { return "DependentDef"; } + virtual DependentDefNode* clone() { return new DependentDefNode(*this); } + }; + + /* ast Node for DependentBlock */ + class DependentBlockNode : public BlockNode { + public: + /* member variables */ + DependentDefNodeList *definitions; + void* symtab; + + /* constructor */ + DependentBlockNode(DependentDefNodeList *definitions); + + /* copy constructor */ + DependentBlockNode(const DependentBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~DependentBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDependentBlockNode(this); } + virtual std::string getNodeType() { return "DependentBlock"; } + virtual DependentBlockNode* clone() { return new DependentBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for StateBlock */ + class StateBlockNode : public BlockNode { + public: + /* member variables */ + DependentDefNodeList *definitions; + void* symtab; + + /* constructor */ + StateBlockNode(DependentDefNodeList *definitions); + + /* copy constructor */ + StateBlockNode(const StateBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~StateBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStateBlockNode(this); } + virtual std::string getNodeType() { return "StateBlock"; } + virtual StateBlockNode* clone() { return new StateBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for PlotBlock */ + class PlotBlockNode : public BlockNode { + public: + /* member variables */ + PlotDeclarationNode *plot; + void* symtab; + + /* constructor */ + PlotBlockNode(PlotDeclarationNode *plot); + + /* copy constructor */ + PlotBlockNode(const PlotBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~PlotBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPlotBlockNode(this); } + virtual std::string getNodeType() { return "PlotBlock"; } + virtual PlotBlockNode* clone() { return new PlotBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for PlotDeclaration */ + class PlotDeclarationNode : public StatementNode { + public: + /* member variables */ + PlotVariableNodeList *pvlist; + PlotVariableNode *name; + + /* constructor */ + PlotDeclarationNode(PlotVariableNodeList *pvlist, PlotVariableNode *name); + + /* copy constructor */ + PlotDeclarationNode(const PlotDeclarationNode& obj); + + virtual ~PlotDeclarationNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPlotDeclarationNode(this); } + virtual std::string getNodeType() { return "PlotDeclaration"; } + virtual PlotDeclarationNode* clone() { return new PlotDeclarationNode(*this); } + }; + + /* ast Node for PlotVariable */ + class PlotVariableNode : public ExpressionNode { + public: + /* member variables */ + IdentifierNode *name; + IntegerNode *index; + + /* constructor */ + PlotVariableNode(IdentifierNode *name, IntegerNode *index); + + /* copy constructor */ + PlotVariableNode(const PlotVariableNode& obj); + + virtual ~PlotVariableNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPlotVariableNode(this); } + virtual std::string getNodeType() { return "PlotVariable"; } + virtual PlotVariableNode* clone() { return new PlotVariableNode(*this); } + }; + + /* ast Node for InitialBlock */ + class InitialBlockNode : public BlockNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + InitialBlockNode(StatementBlockNode *statementblock); + + /* copy constructor */ + InitialBlockNode(const InitialBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~InitialBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitInitialBlockNode(this); } + virtual std::string getNodeType() { return "InitialBlock"; } + virtual InitialBlockNode* clone() { return new InitialBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for ConstructorBlock */ + class ConstructorBlockNode : public BlockNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + ConstructorBlockNode(StatementBlockNode *statementblock); + + /* copy constructor */ + ConstructorBlockNode(const ConstructorBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~ConstructorBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConstructorBlockNode(this); } + virtual std::string getNodeType() { return "ConstructorBlock"; } + virtual ConstructorBlockNode* clone() { return new ConstructorBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for DestructorBlock */ + class DestructorBlockNode : public BlockNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + DestructorBlockNode(StatementBlockNode *statementblock); + + /* copy constructor */ + DestructorBlockNode(const DestructorBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~DestructorBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDestructorBlockNode(this); } + virtual std::string getNodeType() { return "DestructorBlock"; } + virtual DestructorBlockNode* clone() { return new DestructorBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for ConductanceHint */ + class ConductanceHintNode : public StatementNode { + public: + /* member variables */ + NameNode *conductance; + NameNode *ion; + + /* constructor */ + ConductanceHintNode(NameNode *conductance, NameNode *ion); + + /* copy constructor */ + ConductanceHintNode(const ConductanceHintNode& obj); + + virtual ~ConductanceHintNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConductanceHintNode(this); } + virtual std::string getNodeType() { return "ConductanceHint"; } + virtual ConductanceHintNode* clone() { return new ConductanceHintNode(*this); } + }; + + /* ast Node for ExpressionStatement */ + class ExpressionStatementNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *expression; + + /* constructor */ + ExpressionStatementNode(ExpressionNode *expression); + + /* copy constructor */ + ExpressionStatementNode(const ExpressionStatementNode& obj); + + virtual ~ExpressionStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitExpressionStatementNode(this); } + virtual std::string getNodeType() { return "ExpressionStatement"; } + virtual ExpressionStatementNode* clone() { return new ExpressionStatementNode(*this); } + }; + + /* ast Node for ProtectStatement */ + class ProtectStatementNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *expression; + + /* constructor */ + ProtectStatementNode(ExpressionNode *expression); + + /* copy constructor */ + ProtectStatementNode(const ProtectStatementNode& obj); + + virtual ~ProtectStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitProtectStatementNode(this); } + virtual std::string getNodeType() { return "ProtectStatement"; } + virtual ProtectStatementNode* clone() { return new ProtectStatementNode(*this); } + }; + + /* ast Node for StatementBlock */ + class StatementBlockNode : public BlockNode { + public: + /* member variables */ + StatementNodeList *statements; + ModToken *token; + void* symtab; + + /* constructor */ + StatementBlockNode(StatementNodeList *statements); + + /* copy constructor */ + StatementBlockNode(const StatementBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~StatementBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStatementBlockNode(this); } + virtual std::string getNodeType() { return "StatementBlock"; } + virtual StatementBlockNode* clone() { return new StatementBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for BinaryOperator */ + class BinaryOperatorNode : public ExpressionNode { + public: + /* member variables */ + BinaryOp value; + + /* constructor */ + BinaryOperatorNode(BinaryOp value); + + /* copy constructor */ + BinaryOperatorNode(const BinaryOperatorNode& obj); + + virtual ~BinaryOperatorNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBinaryOperatorNode(this); } + virtual std::string getNodeType() { return "BinaryOperator"; } + virtual BinaryOperatorNode* clone() { return new BinaryOperatorNode(*this); } + std::string eval() { return BinaryOpNames[value]; } + }; + + /* ast Node for UnaryOperator */ + class UnaryOperatorNode : public ExpressionNode { + public: + /* member variables */ + UnaryOp value; + + /* constructor */ + UnaryOperatorNode(UnaryOp value); + + /* copy constructor */ + UnaryOperatorNode(const UnaryOperatorNode& obj); + + virtual ~UnaryOperatorNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnaryOperatorNode(this); } + virtual std::string getNodeType() { return "UnaryOperator"; } + virtual UnaryOperatorNode* clone() { return new UnaryOperatorNode(*this); } + std::string eval() { return UnaryOpNames[value]; } + }; + + /* ast Node for ReactionOperator */ + class ReactionOperatorNode : public ExpressionNode { + public: + /* member variables */ + ReactionOp value; + + /* constructor */ + ReactionOperatorNode(ReactionOp value); + + /* copy constructor */ + ReactionOperatorNode(const ReactionOperatorNode& obj); + + virtual ~ReactionOperatorNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReactionOperatorNode(this); } + virtual std::string getNodeType() { return "ReactionOperator"; } + virtual ReactionOperatorNode* clone() { return new ReactionOperatorNode(*this); } + std::string eval() { return ReactionOpNames[value]; } + }; + + /* ast Node for BinaryExpression */ + class BinaryExpressionNode : public ExpressionNode { + public: + /* member variables */ + ExpressionNode *lhs; + BinaryOperatorNode *op; + ExpressionNode *rhs; + + /* constructor */ + BinaryExpressionNode(ExpressionNode *lhs, BinaryOperatorNode *op, ExpressionNode *rhs); + + /* copy constructor */ + BinaryExpressionNode(const BinaryExpressionNode& obj); + + virtual ~BinaryExpressionNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBinaryExpressionNode(this); } + virtual std::string getNodeType() { return "BinaryExpression"; } + virtual BinaryExpressionNode* clone() { return new BinaryExpressionNode(*this); } + }; + + /* ast Node for UnaryExpression */ + class UnaryExpressionNode : public ExpressionNode { + public: + /* member variables */ + UnaryOperatorNode *op; + ExpressionNode *expression; + + /* constructor */ + UnaryExpressionNode(UnaryOperatorNode *op, ExpressionNode *expression); + + /* copy constructor */ + UnaryExpressionNode(const UnaryExpressionNode& obj); + + virtual ~UnaryExpressionNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnaryExpressionNode(this); } + virtual std::string getNodeType() { return "UnaryExpression"; } + virtual UnaryExpressionNode* clone() { return new UnaryExpressionNode(*this); } + }; + + /* ast Node for NonLinEuation */ + class NonLinEuationNode : public ExpressionNode { + public: + /* member variables */ + ExpressionNode *lhs; + ExpressionNode *rhs; + + /* constructor */ + NonLinEuationNode(ExpressionNode *lhs, ExpressionNode *rhs); + + /* copy constructor */ + NonLinEuationNode(const NonLinEuationNode& obj); + + virtual ~NonLinEuationNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNonLinEuationNode(this); } + virtual std::string getNodeType() { return "NonLinEuation"; } + virtual NonLinEuationNode* clone() { return new NonLinEuationNode(*this); } + }; + + /* ast Node for LinEquation */ + class LinEquationNode : public ExpressionNode { + public: + /* member variables */ + ExpressionNode *leftlinexpr; + ExpressionNode *linexpr; + + /* constructor */ + LinEquationNode(ExpressionNode *leftlinexpr, ExpressionNode *linexpr); + + /* copy constructor */ + LinEquationNode(const LinEquationNode& obj); + + virtual ~LinEquationNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLinEquationNode(this); } + virtual std::string getNodeType() { return "LinEquation"; } + virtual LinEquationNode* clone() { return new LinEquationNode(*this); } + }; + + /* ast Node for FunctionCall */ + class FunctionCallNode : public ExpressionNode { + public: + /* member variables */ + NameNode *name; + ExpressionNodeList *arguments; + + /* constructor */ + FunctionCallNode(NameNode *name, ExpressionNodeList *arguments); + + /* copy constructor */ + FunctionCallNode(const FunctionCallNode& obj); + + virtual ~FunctionCallNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFunctionCallNode(this); } + virtual std::string getNodeType() { return "FunctionCall"; } + virtual FunctionCallNode* clone() { return new FunctionCallNode(*this); } + }; + + /* ast Node for FromStatement */ + class FromStatementNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + ExpressionNode *from; + ExpressionNode *to; + ExpressionNode *opinc; + StatementBlockNode *statementblock; + + /* constructor */ + FromStatementNode(NameNode *name, ExpressionNode *from, ExpressionNode *to, ExpressionNode *opinc, StatementBlockNode *statementblock); + + /* copy constructor */ + FromStatementNode(const FromStatementNode& obj); + + virtual ~FromStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFromStatementNode(this); } + virtual std::string getNodeType() { return "FromStatement"; } + virtual FromStatementNode* clone() { return new FromStatementNode(*this); } + }; + + /* ast Node for ForAllStatement */ + class ForAllStatementNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + StatementBlockNode *statementblock; + + /* constructor */ + ForAllStatementNode(NameNode *name, StatementBlockNode *statementblock); + + /* copy constructor */ + ForAllStatementNode(const ForAllStatementNode& obj); + + virtual ~ForAllStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitForAllStatementNode(this); } + virtual std::string getNodeType() { return "ForAllStatement"; } + virtual ForAllStatementNode* clone() { return new ForAllStatementNode(*this); } + }; + + /* ast Node for WhileStatement */ + class WhileStatementNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *condition; + StatementBlockNode *statementblock; + + /* constructor */ + WhileStatementNode(ExpressionNode *condition, StatementBlockNode *statementblock); + + /* copy constructor */ + WhileStatementNode(const WhileStatementNode& obj); + + virtual ~WhileStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWhileStatementNode(this); } + virtual std::string getNodeType() { return "WhileStatement"; } + virtual WhileStatementNode* clone() { return new WhileStatementNode(*this); } + }; + + /* ast Node for IfStatement */ + class IfStatementNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *condition; + StatementBlockNode *statementblock; + ElseIfStatementNodeList *elseifs; + ElseStatementNode *elses; + + /* constructor */ + IfStatementNode(ExpressionNode *condition, StatementBlockNode *statementblock, ElseIfStatementNodeList *elseifs, ElseStatementNode *elses); + + /* copy constructor */ + IfStatementNode(const IfStatementNode& obj); + + virtual ~IfStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIfStatementNode(this); } + virtual std::string getNodeType() { return "IfStatement"; } + virtual IfStatementNode* clone() { return new IfStatementNode(*this); } + }; + + /* ast Node for ElseIfStatement */ + class ElseIfStatementNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *condition; + StatementBlockNode *statementblock; + + /* constructor */ + ElseIfStatementNode(ExpressionNode *condition, StatementBlockNode *statementblock); + + /* copy constructor */ + ElseIfStatementNode(const ElseIfStatementNode& obj); + + virtual ~ElseIfStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitElseIfStatementNode(this); } + virtual std::string getNodeType() { return "ElseIfStatement"; } + virtual ElseIfStatementNode* clone() { return new ElseIfStatementNode(*this); } + }; + + /* ast Node for ElseStatement */ + class ElseStatementNode : public StatementNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + + /* constructor */ + ElseStatementNode(StatementBlockNode *statementblock); + + /* copy constructor */ + ElseStatementNode(const ElseStatementNode& obj); + + virtual ~ElseStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitElseStatementNode(this); } + virtual std::string getNodeType() { return "ElseStatement"; } + virtual ElseStatementNode* clone() { return new ElseStatementNode(*this); } + }; + + /* ast Node for DerivativeBlock */ + class DerivativeBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + DerivativeBlockNode(NameNode *name, StatementBlockNode *statementblock); + + /* copy constructor */ + DerivativeBlockNode(const DerivativeBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~DerivativeBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDerivativeBlockNode(this); } + virtual std::string getNodeType() { return "DerivativeBlock"; } + virtual DerivativeBlockNode* clone() { return new DerivativeBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for LinearBlock */ + class LinearBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + NameNodeList *solvefor; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + LinearBlockNode(NameNode *name, NameNodeList *solvefor, StatementBlockNode *statementblock); + + /* copy constructor */ + LinearBlockNode(const LinearBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~LinearBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLinearBlockNode(this); } + virtual std::string getNodeType() { return "LinearBlock"; } + virtual LinearBlockNode* clone() { return new LinearBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for NonLinearBlock */ + class NonLinearBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + NameNodeList *solvefor; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + NonLinearBlockNode(NameNode *name, NameNodeList *solvefor, StatementBlockNode *statementblock); + + /* copy constructor */ + NonLinearBlockNode(const NonLinearBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~NonLinearBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNonLinearBlockNode(this); } + virtual std::string getNodeType() { return "NonLinearBlock"; } + virtual NonLinearBlockNode* clone() { return new NonLinearBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for DiscreteBlock */ + class DiscreteBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + DiscreteBlockNode(NameNode *name, StatementBlockNode *statementblock); + + /* copy constructor */ + DiscreteBlockNode(const DiscreteBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~DiscreteBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDiscreteBlockNode(this); } + virtual std::string getNodeType() { return "DiscreteBlock"; } + virtual DiscreteBlockNode* clone() { return new DiscreteBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for PartialBlock */ + class PartialBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + PartialBlockNode(NameNode *name, StatementBlockNode *statementblock); + + /* copy constructor */ + PartialBlockNode(const PartialBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~PartialBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPartialBlockNode(this); } + virtual std::string getNodeType() { return "PartialBlock"; } + virtual PartialBlockNode* clone() { return new PartialBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for PartialEquation */ + class PartialEquationNode : public StatementNode { + public: + /* member variables */ + PrimeNameNode *prime; + NameNode *name1; + NameNode *name2; + NameNode *name3; + + /* constructor */ + PartialEquationNode(PrimeNameNode *prime, NameNode *name1, NameNode *name2, NameNode *name3); + + /* copy constructor */ + PartialEquationNode(const PartialEquationNode& obj); + + virtual ~PartialEquationNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPartialEquationNode(this); } + virtual std::string getNodeType() { return "PartialEquation"; } + virtual PartialEquationNode* clone() { return new PartialEquationNode(*this); } + }; + + /* ast Node for FirstLastTypeIndex */ + class FirstLastTypeIndexNode : public ExpressionNode { + public: + /* member variables */ + FirstLastType value; + + /* constructor */ + FirstLastTypeIndexNode(FirstLastType value); + + /* copy constructor */ + FirstLastTypeIndexNode(const FirstLastTypeIndexNode& obj); + + virtual ~FirstLastTypeIndexNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFirstLastTypeIndexNode(this); } + virtual std::string getNodeType() { return "FirstLastTypeIndex"; } + virtual FirstLastTypeIndexNode* clone() { return new FirstLastTypeIndexNode(*this); } + std::string eval() { return FirstLastTypeNames[value]; } + }; + + /* ast Node for PartialBoundary */ + class PartialBoundaryNode : public StatementNode { + public: + /* member variables */ + NameNode *del; + IdentifierNode *name; + FirstLastTypeIndexNode *index; + ExpressionNode *expression; + NameNode *name1; + NameNode *del2; + NameNode *name2; + NameNode *name3; + + /* constructor */ + PartialBoundaryNode(NameNode *del, IdentifierNode *name, FirstLastTypeIndexNode *index, ExpressionNode *expression, NameNode *name1, NameNode *del2, NameNode *name2, NameNode *name3); + + /* copy constructor */ + PartialBoundaryNode(const PartialBoundaryNode& obj); + + virtual ~PartialBoundaryNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPartialBoundaryNode(this); } + virtual std::string getNodeType() { return "PartialBoundary"; } + virtual PartialBoundaryNode* clone() { return new PartialBoundaryNode(*this); } + }; + + /* ast Node for FunctionTableBlock */ + class FunctionTableBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + ArgumentNodeList *arguments; + UnitNode *unit; + ModToken *token; + void* symtab; + + /* constructor */ + FunctionTableBlockNode(NameNode *name, ArgumentNodeList *arguments, UnitNode *unit); + + /* copy constructor */ + FunctionTableBlockNode(const FunctionTableBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~FunctionTableBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFunctionTableBlockNode(this); } + virtual std::string getNodeType() { return "FunctionTableBlock"; } + virtual FunctionTableBlockNode* clone() { return new FunctionTableBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for FunctionBlock */ + class FunctionBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + ArgumentNodeList *arguments; + UnitNode *unit; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + FunctionBlockNode(NameNode *name, ArgumentNodeList *arguments, UnitNode *unit, StatementBlockNode *statementblock); + + /* copy constructor */ + FunctionBlockNode(const FunctionBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~FunctionBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFunctionBlockNode(this); } + virtual std::string getNodeType() { return "FunctionBlock"; } + virtual FunctionBlockNode* clone() { return new FunctionBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for ProcedureBlock */ + class ProcedureBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + ArgumentNodeList *arguments; + UnitNode *unit; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + ProcedureBlockNode(NameNode *name, ArgumentNodeList *arguments, UnitNode *unit, StatementBlockNode *statementblock); + + /* copy constructor */ + ProcedureBlockNode(const ProcedureBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~ProcedureBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitProcedureBlockNode(this); } + virtual std::string getNodeType() { return "ProcedureBlock"; } + virtual ProcedureBlockNode* clone() { return new ProcedureBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for NetReceiveBlock */ + class NetReceiveBlockNode : public BlockNode { + public: + /* member variables */ + ArgumentNodeList *arguments; + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + NetReceiveBlockNode(ArgumentNodeList *arguments, StatementBlockNode *statementblock); + + /* copy constructor */ + NetReceiveBlockNode(const NetReceiveBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~NetReceiveBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNetReceiveBlockNode(this); } + virtual std::string getNodeType() { return "NetReceiveBlock"; } + virtual NetReceiveBlockNode* clone() { return new NetReceiveBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for SolveBlock */ + class SolveBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + NameNode *method; + StatementBlockNode *ifsolerr; + void* symtab; + + /* constructor */ + SolveBlockNode(NameNode *name, NameNode *method, StatementBlockNode *ifsolerr); + + /* copy constructor */ + SolveBlockNode(const SolveBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~SolveBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSolveBlockNode(this); } + virtual std::string getNodeType() { return "SolveBlock"; } + virtual SolveBlockNode* clone() { return new SolveBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for BreakpointBlock */ + class BreakpointBlockNode : public BlockNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + BreakpointBlockNode(StatementBlockNode *statementblock); + + /* copy constructor */ + BreakpointBlockNode(const BreakpointBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~BreakpointBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBreakpointBlockNode(this); } + virtual std::string getNodeType() { return "BreakpointBlock"; } + virtual BreakpointBlockNode* clone() { return new BreakpointBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for TerminalBlock */ + class TerminalBlockNode : public BlockNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + TerminalBlockNode(StatementBlockNode *statementblock); + + /* copy constructor */ + TerminalBlockNode(const TerminalBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~TerminalBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitTerminalBlockNode(this); } + virtual std::string getNodeType() { return "TerminalBlock"; } + virtual TerminalBlockNode* clone() { return new TerminalBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for BeforeBlock */ + class BeforeBlockNode : public BlockNode { + public: + /* member variables */ + BABlockNode *block; + void* symtab; + + /* constructor */ + BeforeBlockNode(BABlockNode *block); + + /* copy constructor */ + BeforeBlockNode(const BeforeBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~BeforeBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBeforeBlockNode(this); } + virtual std::string getNodeType() { return "BeforeBlock"; } + virtual BeforeBlockNode* clone() { return new BeforeBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for AfterBlock */ + class AfterBlockNode : public BlockNode { + public: + /* member variables */ + BABlockNode *block; + void* symtab; + + /* constructor */ + AfterBlockNode(BABlockNode *block); + + /* copy constructor */ + AfterBlockNode(const AfterBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~AfterBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitAfterBlockNode(this); } + virtual std::string getNodeType() { return "AfterBlock"; } + virtual AfterBlockNode* clone() { return new AfterBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for BABlockType */ + class BABlockTypeNode : public ExpressionNode { + public: + /* member variables */ + BAType value; + + /* constructor */ + BABlockTypeNode(BAType value); + + /* copy constructor */ + BABlockTypeNode(const BABlockTypeNode& obj); + + virtual ~BABlockTypeNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBABlockTypeNode(this); } + virtual std::string getNodeType() { return "BABlockType"; } + virtual BABlockTypeNode* clone() { return new BABlockTypeNode(*this); } + std::string eval() { return BATypeNames[value]; } + }; + + /* ast Node for BABlock */ + class BABlockNode : public BlockNode { + public: + /* member variables */ + BABlockTypeNode *type; + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + BABlockNode(BABlockTypeNode *type, StatementBlockNode *statementblock); + + /* copy constructor */ + BABlockNode(const BABlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~BABlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBABlockNode(this); } + virtual std::string getNodeType() { return "BABlock"; } + virtual BABlockNode* clone() { return new BABlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for WatchStatement */ + class WatchStatementNode : public StatementNode { + public: + /* member variables */ + WatchNodeList *statements; + + /* constructor */ + WatchStatementNode(WatchNodeList *statements); + + /* copy constructor */ + WatchStatementNode(const WatchStatementNode& obj); + + void addWatch(WatchNode *s) { + statements->push_back(s); + } + + virtual ~WatchStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWatchStatementNode(this); } + virtual std::string getNodeType() { return "WatchStatement"; } + virtual WatchStatementNode* clone() { return new WatchStatementNode(*this); } + }; + + /* ast Node for Watch */ + class WatchNode : public ExpressionNode { + public: + /* member variables */ + ExpressionNode *expression; + ExpressionNode *value; + + /* constructor */ + WatchNode(ExpressionNode *expression, ExpressionNode *value); + + /* copy constructor */ + WatchNode(const WatchNode& obj); + + virtual ~WatchNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWatchNode(this); } + virtual std::string getNodeType() { return "Watch"; } + virtual WatchNode* clone() { return new WatchNode(*this); } + }; + + /* ast Node for ForNetcon */ + class ForNetconNode : public BlockNode { + public: + /* member variables */ + ArgumentNodeList *arguments; + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + ForNetconNode(ArgumentNodeList *arguments, StatementBlockNode *statementblock); + + /* copy constructor */ + ForNetconNode(const ForNetconNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~ForNetconNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitForNetconNode(this); } + virtual std::string getNodeType() { return "ForNetcon"; } + virtual ForNetconNode* clone() { return new ForNetconNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for MutexLock */ + class MutexLockNode : public StatementNode { + public: + + virtual ~MutexLockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMutexLockNode(this); } + virtual std::string getNodeType() { return "MutexLock"; } + virtual MutexLockNode* clone() { return new MutexLockNode(*this); } + }; + + /* ast Node for MutexUnlock */ + class MutexUnlockNode : public StatementNode { + public: + + virtual ~MutexUnlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMutexUnlockNode(this); } + virtual std::string getNodeType() { return "MutexUnlock"; } + virtual MutexUnlockNode* clone() { return new MutexUnlockNode(*this); } + }; + + /* ast Node for Reset */ + class ResetNode : public StatementNode { + public: + + virtual ~ResetNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitResetNode(this); } + virtual std::string getNodeType() { return "Reset"; } + virtual ResetNode* clone() { return new ResetNode(*this); } + }; + + /* ast Node for Sens */ + class SensNode : public StatementNode { + public: + /* member variables */ + VarNameNodeList *senslist; + + /* constructor */ + SensNode(VarNameNodeList *senslist); + + /* copy constructor */ + SensNode(const SensNode& obj); + + virtual ~SensNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSensNode(this); } + virtual std::string getNodeType() { return "Sens"; } + virtual SensNode* clone() { return new SensNode(*this); } + }; + + /* ast Node for Conserve */ + class ConserveNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *react; + ExpressionNode *expr; + + /* constructor */ + ConserveNode(ExpressionNode *react, ExpressionNode *expr); + + /* copy constructor */ + ConserveNode(const ConserveNode& obj); + + virtual ~ConserveNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConserveNode(this); } + virtual std::string getNodeType() { return "Conserve"; } + virtual ConserveNode* clone() { return new ConserveNode(*this); } + }; + + /* ast Node for Compartment */ + class CompartmentNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + ExpressionNode *expression; + NameNodeList *names; + + /* constructor */ + CompartmentNode(NameNode *name, ExpressionNode *expression, NameNodeList *names); + + /* copy constructor */ + CompartmentNode(const CompartmentNode& obj); + + virtual ~CompartmentNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitCompartmentNode(this); } + virtual std::string getNodeType() { return "Compartment"; } + virtual CompartmentNode* clone() { return new CompartmentNode(*this); } + }; + + /* ast Node for LDifuse */ + class LDifuseNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + ExpressionNode *expression; + NameNodeList *names; + + /* constructor */ + LDifuseNode(NameNode *name, ExpressionNode *expression, NameNodeList *names); + + /* copy constructor */ + LDifuseNode(const LDifuseNode& obj); + + virtual ~LDifuseNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLDifuseNode(this); } + virtual std::string getNodeType() { return "LDifuse"; } + virtual LDifuseNode* clone() { return new LDifuseNode(*this); } + }; + + /* ast Node for KineticBlock */ + class KineticBlockNode : public BlockNode { + public: + /* member variables */ + NameNode *name; + NameNodeList *solvefor; + StatementBlockNode *statementblock; + ModToken *token; + void* symtab; + + /* constructor */ + KineticBlockNode(NameNode *name, NameNodeList *solvefor, StatementBlockNode *statementblock); + + /* copy constructor */ + KineticBlockNode(const KineticBlockNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~KineticBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitKineticBlockNode(this); } + virtual std::string getNodeType() { return "KineticBlock"; } + virtual KineticBlockNode* clone() { return new KineticBlockNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for ReactionStatement */ + class ReactionStatementNode : public StatementNode { + public: + /* member variables */ + ExpressionNode *react1; + ReactionOperatorNode *op; + ExpressionNode *react2; + ExpressionNode *expr1; + ExpressionNode *expr2; + + /* constructor */ + ReactionStatementNode(ExpressionNode *react1, ReactionOperatorNode *op, ExpressionNode *react2, ExpressionNode *expr1, ExpressionNode *expr2); + + /* copy constructor */ + ReactionStatementNode(const ReactionStatementNode& obj); + + virtual ~ReactionStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReactionStatementNode(this); } + virtual std::string getNodeType() { return "ReactionStatement"; } + virtual ReactionStatementNode* clone() { return new ReactionStatementNode(*this); } + }; + + /* ast Node for ReactVarName */ + class ReactVarNameNode : public IdentifierNode { + public: + /* member variables */ + IntegerNode *value; + VarNameNode *name; + + /* constructor */ + ReactVarNameNode(IntegerNode *value, VarNameNode *name); + + /* copy constructor */ + ReactVarNameNode(const ReactVarNameNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ReactVarNameNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReactVarNameNode(this); } + virtual std::string getNodeType() { return "ReactVarName"; } + virtual ReactVarNameNode* clone() { return new ReactVarNameNode(*this); } + }; + + /* ast Node for LagStatement */ + class LagStatementNode : public StatementNode { + public: + /* member variables */ + IdentifierNode *name; + NameNode *byname; + + /* constructor */ + LagStatementNode(IdentifierNode *name, NameNode *byname); + + /* copy constructor */ + LagStatementNode(const LagStatementNode& obj); + + virtual ~LagStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLagStatementNode(this); } + virtual std::string getNodeType() { return "LagStatement"; } + virtual LagStatementNode* clone() { return new LagStatementNode(*this); } + }; + + /* ast Node for QueueStatement */ + class QueueStatementNode : public StatementNode { + public: + /* member variables */ + QueueExpressionTypeNode *qype; + IdentifierNode *name; + + /* constructor */ + QueueStatementNode(QueueExpressionTypeNode *qype, IdentifierNode *name); + + /* copy constructor */ + QueueStatementNode(const QueueStatementNode& obj); + + virtual ~QueueStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitQueueStatementNode(this); } + virtual std::string getNodeType() { return "QueueStatement"; } + virtual QueueStatementNode* clone() { return new QueueStatementNode(*this); } + }; + + /* ast Node for QueueExpressionType */ + class QueueExpressionTypeNode : public ExpressionNode { + public: + /* member variables */ + QueueType value; + + /* constructor */ + QueueExpressionTypeNode(QueueType value); + + /* copy constructor */ + QueueExpressionTypeNode(const QueueExpressionTypeNode& obj); + + virtual ~QueueExpressionTypeNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitQueueExpressionTypeNode(this); } + virtual std::string getNodeType() { return "QueueExpressionType"; } + virtual QueueExpressionTypeNode* clone() { return new QueueExpressionTypeNode(*this); } + std::string eval() { return QueueTypeNames[value]; } + }; + + /* ast Node for MatchBlock */ + class MatchBlockNode : public BlockNode { + public: + /* member variables */ + MatchNodeList *matchs; + void* symtab; + + /* constructor */ + MatchBlockNode(MatchNodeList *matchs); + + /* copy constructor */ + MatchBlockNode(const MatchBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~MatchBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMatchBlockNode(this); } + virtual std::string getNodeType() { return "MatchBlock"; } + virtual MatchBlockNode* clone() { return new MatchBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for Match */ + class MatchNode : public ExpressionNode { + public: + /* member variables */ + IdentifierNode *name; + ExpressionNode *expression; + + /* constructor */ + MatchNode(IdentifierNode *name, ExpressionNode *expression); + + /* copy constructor */ + MatchNode(const MatchNode& obj); + + virtual ~MatchNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMatchNode(this); } + virtual std::string getNodeType() { return "Match"; } + virtual MatchNode* clone() { return new MatchNode(*this); } + }; + + /* ast Node for UnitBlock */ + class UnitBlockNode : public BlockNode { + public: + /* member variables */ + ExpressionNodeList *definitions; + void* symtab; + + /* constructor */ + UnitBlockNode(ExpressionNodeList *definitions); + + /* copy constructor */ + UnitBlockNode(const UnitBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~UnitBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitBlockNode(this); } + virtual std::string getNodeType() { return "UnitBlock"; } + virtual UnitBlockNode* clone() { return new UnitBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for UnitDef */ + class UnitDefNode : public ExpressionNode { + public: + /* member variables */ + UnitNode *unit1; + UnitNode *unit2; + + /* constructor */ + UnitDefNode(UnitNode *unit1, UnitNode *unit2); + + /* copy constructor */ + UnitDefNode(const UnitDefNode& obj); + + virtual std::string getName() { return unit1->getName(); } + virtual ModToken* getToken() { return unit1->getToken(); } + + virtual ~UnitDefNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitDefNode(this); } + virtual std::string getNodeType() { return "UnitDef"; } + virtual UnitDefNode* clone() { return new UnitDefNode(*this); } + }; + + /* ast Node for Factordef */ + class FactordefNode : public ExpressionNode { + public: + /* member variables */ + NameNode *name; + DoubleNode *value; + UnitNode *unit1; + BooleanNode *gt; + UnitNode *unit2; + ModToken *token; + + /* constructor */ + FactordefNode(NameNode *name, DoubleNode *value, UnitNode *unit1, BooleanNode *gt, UnitNode *unit2); + + /* copy constructor */ + FactordefNode(const FactordefNode& obj); + + virtual std::string getName() { return name->getName(); } + + virtual ~FactordefNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFactordefNode(this); } + virtual std::string getNodeType() { return "Factordef"; } + virtual FactordefNode* clone() { return new FactordefNode(*this); } + virtual ModToken *getToken() { return token; } + virtual void setToken(ModToken *tok) { token = tok; } + }; + + /* ast Node for ConstantStatement */ + class ConstantStatementNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + NumberNode *value; + UnitNode *unit; + + /* constructor */ + ConstantStatementNode(NameNode *name, NumberNode *value, UnitNode *unit); + + /* copy constructor */ + ConstantStatementNode(const ConstantStatementNode& obj); + + virtual ~ConstantStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConstantStatementNode(this); } + virtual std::string getNodeType() { return "ConstantStatement"; } + virtual ConstantStatementNode* clone() { return new ConstantStatementNode(*this); } + }; + + /* ast Node for ConstantBlock */ + class ConstantBlockNode : public BlockNode { + public: + /* member variables */ + ConstantStatementNodeList *statements; + void* symtab; + + /* constructor */ + ConstantBlockNode(ConstantStatementNodeList *statements); + + /* copy constructor */ + ConstantBlockNode(const ConstantBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~ConstantBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConstantBlockNode(this); } + virtual std::string getNodeType() { return "ConstantBlock"; } + virtual ConstantBlockNode* clone() { return new ConstantBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for TableStatement */ + class TableStatementNode : public StatementNode { + public: + /* member variables */ + NameNodeList *tablst; + NameNodeList *dependlst; + ExpressionNode *from; + ExpressionNode *to; + IntegerNode *with; + + /* constructor */ + TableStatementNode(NameNodeList *tablst, NameNodeList *dependlst, ExpressionNode *from, ExpressionNode *to, IntegerNode *with); + + /* copy constructor */ + TableStatementNode(const TableStatementNode& obj); + + virtual ~TableStatementNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitTableStatementNode(this); } + virtual std::string getNodeType() { return "TableStatement"; } + virtual TableStatementNode* clone() { return new TableStatementNode(*this); } + }; + + /* ast Node for NeuronBlock */ + class NeuronBlockNode : public BlockNode { + public: + /* member variables */ + StatementBlockNode *statementblock; + void* symtab; + + /* constructor */ + NeuronBlockNode(StatementBlockNode *statementblock); + + /* copy constructor */ + NeuronBlockNode(const NeuronBlockNode& obj); + std::string getName() { return getNodeType(); } + + virtual ~NeuronBlockNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNeuronBlockNode(this); } + virtual std::string getNodeType() { return "NeuronBlock"; } + virtual NeuronBlockNode* clone() { return new NeuronBlockNode(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + /* ast Node for ReadIonVar */ + class ReadIonVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + ReadIonVarNode(NameNode *name); + + /* copy constructor */ + ReadIonVarNode(const ReadIonVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ReadIonVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReadIonVarNode(this); } + virtual std::string getNodeType() { return "ReadIonVar"; } + virtual ReadIonVarNode* clone() { return new ReadIonVarNode(*this); } + }; + + /* ast Node for WriteIonVar */ + class WriteIonVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + WriteIonVarNode(NameNode *name); + + /* copy constructor */ + WriteIonVarNode(const WriteIonVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~WriteIonVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWriteIonVarNode(this); } + virtual std::string getNodeType() { return "WriteIonVar"; } + virtual WriteIonVarNode* clone() { return new WriteIonVarNode(*this); } + }; + + /* ast Node for NonspeCurVar */ + class NonspeCurVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + NonspeCurVarNode(NameNode *name); + + /* copy constructor */ + NonspeCurVarNode(const NonspeCurVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~NonspeCurVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNonspeCurVarNode(this); } + virtual std::string getNodeType() { return "NonspeCurVar"; } + virtual NonspeCurVarNode* clone() { return new NonspeCurVarNode(*this); } + }; + + /* ast Node for ElectrodeCurVar */ + class ElectrodeCurVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + ElectrodeCurVarNode(NameNode *name); + + /* copy constructor */ + ElectrodeCurVarNode(const ElectrodeCurVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ElectrodeCurVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitElectrodeCurVarNode(this); } + virtual std::string getNodeType() { return "ElectrodeCurVar"; } + virtual ElectrodeCurVarNode* clone() { return new ElectrodeCurVarNode(*this); } + }; + + /* ast Node for SectionVar */ + class SectionVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + SectionVarNode(NameNode *name); + + /* copy constructor */ + SectionVarNode(const SectionVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~SectionVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSectionVarNode(this); } + virtual std::string getNodeType() { return "SectionVar"; } + virtual SectionVarNode* clone() { return new SectionVarNode(*this); } + }; + + /* ast Node for RangeVar */ + class RangeVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + RangeVarNode(NameNode *name); + + /* copy constructor */ + RangeVarNode(const RangeVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~RangeVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitRangeVarNode(this); } + virtual std::string getNodeType() { return "RangeVar"; } + virtual RangeVarNode* clone() { return new RangeVarNode(*this); } + }; + + /* ast Node for GlobalVar */ + class GlobalVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + GlobalVarNode(NameNode *name); + + /* copy constructor */ + GlobalVarNode(const GlobalVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~GlobalVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitGlobalVarNode(this); } + virtual std::string getNodeType() { return "GlobalVar"; } + virtual GlobalVarNode* clone() { return new GlobalVarNode(*this); } + }; + + /* ast Node for PointerVar */ + class PointerVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + PointerVarNode(NameNode *name); + + /* copy constructor */ + PointerVarNode(const PointerVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~PointerVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPointerVarNode(this); } + virtual std::string getNodeType() { return "PointerVar"; } + virtual PointerVarNode* clone() { return new PointerVarNode(*this); } + }; + + /* ast Node for BbcorePointerVar */ + class BbcorePointerVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + BbcorePointerVarNode(NameNode *name); + + /* copy constructor */ + BbcorePointerVarNode(const BbcorePointerVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~BbcorePointerVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBbcorePointerVarNode(this); } + virtual std::string getNodeType() { return "BbcorePointerVar"; } + virtual BbcorePointerVarNode* clone() { return new BbcorePointerVarNode(*this); } + }; + + /* ast Node for ExternVar */ + class ExternVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + ExternVarNode(NameNode *name); + + /* copy constructor */ + ExternVarNode(const ExternVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ExternVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitExternVarNode(this); } + virtual std::string getNodeType() { return "ExternVar"; } + virtual ExternVarNode* clone() { return new ExternVarNode(*this); } + }; + + /* ast Node for ThreadsafeVar */ + class ThreadsafeVarNode : public IdentifierNode { + public: + /* member variables */ + NameNode *name; + + /* constructor */ + ThreadsafeVarNode(NameNode *name); + + /* copy constructor */ + ThreadsafeVarNode(const ThreadsafeVarNode& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + + virtual ~ThreadsafeVarNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitThreadsafeVarNode(this); } + virtual std::string getNodeType() { return "ThreadsafeVar"; } + virtual ThreadsafeVarNode* clone() { return new ThreadsafeVarNode(*this); } + }; + + /* ast Node for NrnSuffix */ + class NrnSuffixNode : public StatementNode { + public: + /* member variables */ + NameNode *type; + NameNode *name; + + /* constructor */ + NrnSuffixNode(NameNode *type, NameNode *name); + + /* copy constructor */ + NrnSuffixNode(const NrnSuffixNode& obj); + + virtual ~NrnSuffixNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnSuffixNode(this); } + virtual std::string getNodeType() { return "NrnSuffix"; } + virtual NrnSuffixNode* clone() { return new NrnSuffixNode(*this); } + }; + + /* ast Node for NrnUseion */ + class NrnUseionNode : public StatementNode { + public: + /* member variables */ + NameNode *name; + ReadIonVarNodeList *readlist; + WriteIonVarNodeList *writelist; + ValenceNode *valence; + + /* constructor */ + NrnUseionNode(NameNode *name, ReadIonVarNodeList *readlist, WriteIonVarNodeList *writelist, ValenceNode *valence); + + /* copy constructor */ + NrnUseionNode(const NrnUseionNode& obj); + + virtual ~NrnUseionNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnUseionNode(this); } + virtual std::string getNodeType() { return "NrnUseion"; } + virtual NrnUseionNode* clone() { return new NrnUseionNode(*this); } + }; + + /* ast Node for NrnNonspecific */ + class NrnNonspecificNode : public StatementNode { + public: + /* member variables */ + NonspeCurVarNodeList *currents; + + /* constructor */ + NrnNonspecificNode(NonspeCurVarNodeList *currents); + + /* copy constructor */ + NrnNonspecificNode(const NrnNonspecificNode& obj); + + virtual ~NrnNonspecificNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnNonspecificNode(this); } + virtual std::string getNodeType() { return "NrnNonspecific"; } + virtual NrnNonspecificNode* clone() { return new NrnNonspecificNode(*this); } + }; + + /* ast Node for NrnElctrodeCurrent */ + class NrnElctrodeCurrentNode : public StatementNode { + public: + /* member variables */ + ElectrodeCurVarNodeList *ecurrents; + + /* constructor */ + NrnElctrodeCurrentNode(ElectrodeCurVarNodeList *ecurrents); + + /* copy constructor */ + NrnElctrodeCurrentNode(const NrnElctrodeCurrentNode& obj); + + virtual ~NrnElctrodeCurrentNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnElctrodeCurrentNode(this); } + virtual std::string getNodeType() { return "NrnElctrodeCurrent"; } + virtual NrnElctrodeCurrentNode* clone() { return new NrnElctrodeCurrentNode(*this); } + }; + + /* ast Node for NrnSection */ + class NrnSectionNode : public StatementNode { + public: + /* member variables */ + SectionVarNodeList *sections; + + /* constructor */ + NrnSectionNode(SectionVarNodeList *sections); + + /* copy constructor */ + NrnSectionNode(const NrnSectionNode& obj); + + virtual ~NrnSectionNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnSectionNode(this); } + virtual std::string getNodeType() { return "NrnSection"; } + virtual NrnSectionNode* clone() { return new NrnSectionNode(*this); } + }; + + /* ast Node for NrnRange */ + class NrnRangeNode : public StatementNode { + public: + /* member variables */ + RangeVarNodeList *range_vars; + + /* constructor */ + NrnRangeNode(RangeVarNodeList *range_vars); + + /* copy constructor */ + NrnRangeNode(const NrnRangeNode& obj); + + virtual ~NrnRangeNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnRangeNode(this); } + virtual std::string getNodeType() { return "NrnRange"; } + virtual NrnRangeNode* clone() { return new NrnRangeNode(*this); } + }; + + /* ast Node for NrnGlobal */ + class NrnGlobalNode : public StatementNode { + public: + /* member variables */ + GlobalVarNodeList *global_vars; + + /* constructor */ + NrnGlobalNode(GlobalVarNodeList *global_vars); + + /* copy constructor */ + NrnGlobalNode(const NrnGlobalNode& obj); + + virtual ~NrnGlobalNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnGlobalNode(this); } + virtual std::string getNodeType() { return "NrnGlobal"; } + virtual NrnGlobalNode* clone() { return new NrnGlobalNode(*this); } + }; + + /* ast Node for NrnPointer */ + class NrnPointerNode : public StatementNode { + public: + /* member variables */ + PointerVarNodeList *pointers; + + /* constructor */ + NrnPointerNode(PointerVarNodeList *pointers); + + /* copy constructor */ + NrnPointerNode(const NrnPointerNode& obj); + + virtual ~NrnPointerNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnPointerNode(this); } + virtual std::string getNodeType() { return "NrnPointer"; } + virtual NrnPointerNode* clone() { return new NrnPointerNode(*this); } + }; + + /* ast Node for NrnBbcorePtr */ + class NrnBbcorePtrNode : public StatementNode { + public: + /* member variables */ + BbcorePointerVarNodeList *bbcore_pointers; + + /* constructor */ + NrnBbcorePtrNode(BbcorePointerVarNodeList *bbcore_pointers); + + /* copy constructor */ + NrnBbcorePtrNode(const NrnBbcorePtrNode& obj); + + virtual ~NrnBbcorePtrNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnBbcorePtrNode(this); } + virtual std::string getNodeType() { return "NrnBbcorePtr"; } + virtual NrnBbcorePtrNode* clone() { return new NrnBbcorePtrNode(*this); } + }; + + /* ast Node for NrnExternal */ + class NrnExternalNode : public StatementNode { + public: + /* member variables */ + ExternVarNodeList *externals; + + /* constructor */ + NrnExternalNode(ExternVarNodeList *externals); + + /* copy constructor */ + NrnExternalNode(const NrnExternalNode& obj); + + virtual ~NrnExternalNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnExternalNode(this); } + virtual std::string getNodeType() { return "NrnExternal"; } + virtual NrnExternalNode* clone() { return new NrnExternalNode(*this); } + }; + + /* ast Node for NrnThreadSafe */ + class NrnThreadSafeNode : public StatementNode { + public: + /* member variables */ + ThreadsafeVarNodeList *threadsafe; + + /* constructor */ + NrnThreadSafeNode(ThreadsafeVarNodeList *threadsafe); + + /* copy constructor */ + NrnThreadSafeNode(const NrnThreadSafeNode& obj); + + virtual ~NrnThreadSafeNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnThreadSafeNode(this); } + virtual std::string getNodeType() { return "NrnThreadSafe"; } + virtual NrnThreadSafeNode* clone() { return new NrnThreadSafeNode(*this); } + }; + + /* ast Node for Verbatim */ + class VerbatimNode : public StatementNode { + public: + /* member variables */ + StringNode *statement; + + /* constructor */ + VerbatimNode(StringNode *statement); + + /* copy constructor */ + VerbatimNode(const VerbatimNode& obj); + + virtual ~VerbatimNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitVerbatimNode(this); } + virtual std::string getNodeType() { return "Verbatim"; } + virtual VerbatimNode* clone() { return new VerbatimNode(*this); } + }; + + /* ast Node for Comment */ + class CommentNode : public StatementNode { + public: + /* member variables */ + StringNode *comment; + + /* constructor */ + CommentNode(StringNode *comment); + + /* copy constructor */ + CommentNode(const CommentNode& obj); + + virtual ~CommentNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitCommentNode(this); } + virtual std::string getNodeType() { return "Comment"; } + virtual CommentNode* clone() { return new CommentNode(*this); } + }; + + /* ast Node for Valence */ + class ValenceNode : public ExpressionNode { + public: + /* member variables */ + NameNode *type; + DoubleNode *value; + + /* constructor */ + ValenceNode(NameNode *type, DoubleNode *value); + + /* copy constructor */ + ValenceNode(const ValenceNode& obj); + + virtual ~ValenceNode(); + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitValenceNode(this); } + virtual std::string getNodeType() { return "Valence"; } + virtual ValenceNode* clone() { return new ValenceNode(*this); } + }; + +} //namespace ast + +#endif diff --git a/src/nmodl/ast/astutils.hpp b/src/nmodl/ast/astutils.hpp new file mode 100644 index 0000000000..76c504eb2b --- /dev/null +++ b/src/nmodl/ast/astutils.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include + +namespace ast { + + /* enumaration of all binary operators in the language */ + typedef enum { + BOP_ADDITION, + BOP_SUBTRACTION, + BOP_MULTIPLICATION, + BOP_DIVISION, + BOP_POWER, + BOP_AND, + BOP_OR, + BOP_GREATER, + BOP_LESS, + BOP_GREATER_EQUAL, + BOP_LESS_EQUAL, + BOP_ASSIGN, + BOP_NOT_EQUAL, + BOP_EXACT_EQUAL + } BinaryOp; + + static const std::string BinaryOpNames[] = {"+", "-", "*", "/", "^", "&&", "||", + ">", "<", ">=", "<=", "=", "!=", "=="}; + + /* enumaration of all unary operators in the language */ + typedef enum { UOP_NOT, UOP_NEGATION } UnaryOp; + static const std::string UnaryOpNames[] = {"!", "-"}; + + /* enumaration of types used in partial equation */ + typedef enum { PEQ_FIRST, PEQ_LAST } FirstLastType; + static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; + + /* enumaration of queue types */ + typedef enum { PUT_QUEUE, GET_QUEUE } QueueType; + static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; + + /* enumaration of type used for BEFORE or AFTER block */ + typedef enum { BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP } BAType; + static const std::string BATypeNames[] = {"BREAKPOINT", "SOLVE", "INITIAL", "STEP"}; + + /* enumaration of type used for UNIT_ON or UNIT_OFF state*/ + typedef enum { UNIT_ON, UNIT_OFF } UnitStateType; + static const std::string UnitStateTypeNames[] = {"UNITSON", "UNITSOFF"}; + + /* enumaration of type used for Reaction statement */ + typedef enum { LTMINUSGT, LTLT, MINUSGT } ReactionOp; + static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; + + /* base class for all visitors implementation */ + class Visitor; + + /* define abstract base class for all AST nodes + * this also serves to define the visitable objects. + */ + class AST { + public: + /* all AST nodes have a member which stores their + * basetype (int, bool, none, object). Further type + * information will come from the symbol table. + * BaseType basetype; + */ + + /* all AST nodes provide visit children and accept methods */ + virtual void visitChildren(Visitor* v) = 0; + virtual void accept(Visitor* v) = 0; + virtual std::string getType() = 0; + /* @todo: please revisit this. adding quickly for symtab */ + virtual std::string getName() { + return ""; + } + virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ + return NULL; + } + // virtual AST* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } + }; + +} // namespace ast diff --git a/src/nmodl/lexer/init.cpp b/src/nmodl/lexer/init.cpp new file mode 100644 index 0000000000..26f536c6e4 --- /dev/null +++ b/src/nmodl/lexer/init.cpp @@ -0,0 +1,436 @@ +#include +#include +#include +#include +#include +#include +#include "lexer/modtoken.hpp" +#include "ast/ast.hpp" +#include "parser/nmodl_context.hpp" +#include "parser/nmodl_parser.hpp" +#include "lexer/modl.h" +#include "lexer/init.hpp" + +/* Keywords from NMODL reprsent name and kval pair + * in the original struct [source init.c] + */ +namespace Keywords { + + static std::map create_keywords_map() { + /* @todo: map for pretty-printing keyrowrds in sorted order? */ + std::map km; + + km["VERBATIM"] = VERBATIM; + km["COMMENT"] = COMMENT; + km["TITLE"] = MODEL; + km["CONSTANT"] = CONSTANT; + km["PARAMETER"] = PARAMETER; + km["INDEPENDENT"] = INDEPENDENT; + km["ASSIGNED"] = DEPENDENT; + km["INITIAL"] = INITIAL1; + km["TERMINAL"] = TERMINAL; + km["DERIVATIVE"] = DERIVATIVE; + km["EQUATION"] = EQUATION; + km["BREAKPOINT"] = BREAKPOINT; + km["CONDUCTANCE"] = CONDUCTANCE; + km["SOLVE"] = SOLVE; + km["STATE"] = STATE; + km["STEPPED"] = STEPPED; + km["LINEAR"] = LINEAR; + km["NONLINEAR"] = NONLINEAR; + km["DISCRETE"] = DISCRETE; + km["FUNCTION"] = FUNCTION1; + km["FUNCTION_TABLE"] = FUNCTION_TABLE; + km["PROCEDURE"] = PROCEDURE; + km["PARTIAL"] = PARTIAL; + km["DEL2"] = DEL2; + km["DEL"] = DEL; + km["LOCAL"] = LOCAL; + km["METHOD"] = USING; + km["STEADYSTATE"] = USING; + km["SENS"] = SENS; + km["STEP"] = STEP; + km["WITH"] = WITH; + km["FROM"] = FROM; + km["FORALL"] = FORALL1; + km["TO"] = TO; + km["BY"] = BY; + km["if"] = IF; + km["else"] = ELSE; + km["while"] = WHILE; + km["START"] = START1; + km["DEFINE"] = DEFINE1; + km["KINETIC"] = KINETIC; + km["CONSERVE"] = CONSERVE; + km["PLOT"] = PLOT; + km["VS"] = VS; + km["LAG"] = LAG; + km["RESET"] = RESET; + km["MATCH"] = MATCH; + km["MODEL_LEVEL"] = MODEL_LEVEL; /* inserted by merge */ + km["SWEEP"] = SWEEP; + km["FIRST"] = FIRST; + km["LAST"] = LAST; + km["COMPARTMENT"] = COMPARTMENT; + km["LONGITUDINAL_DIFFUSION"] = LONGDIFUS; + km["PUTQ"] = PUTQ; + km["GETQ"] = GETQ; + km["IFERROR"] = IFERROR; + km["SOLVEFOR"] = SOLVEFOR; + km["UNITS"] = UNITS; + km["UNITSON"] = UNITSON; + km["UNITSOFF"] = UNITSOFF; + km["TABLE"] = TABLE; + km["DEPEND"] = DEPEND; + km["NEURON"] = NEURON; + km["SUFFIX"] = SUFFIX; + km["POINT_PROCESS"] = SUFFIX; + km["ARTIFICIAL_CELL"] = SUFFIX; + km["NONSPECIFIC_CURRENT"] = NONSPECIFIC; + km["ELECTRODE_CURRENT"] = ELECTRODE_CURRENT; + km["SECTION"] = SECTION; + km["RANGE"] = RANGE; + km["USEION"] = USEION; + km["READ"] = READ; + km["WRITE"] = WRITE; + km["VALENCE"] = VALENCE; + km["CHARGE"] = VALENCE; + km["GLOBAL"] = GLOBAL; + km["POINTER"] = POINTER; + km["BBCOREPOINTER"] = BBCOREPOINTER; + km["EXTERNAL"] = EXTERNAL; + km["INCLUDE"] = INCLUDE1; + km["CONSTRUCTOR"] = CONSTRUCTOR; + km["DESTRUCTOR"] = DESTRUCTOR; + km["NET_RECEIVE"] = NETRECEIVE; + km["BEFORE"] = BEFORE; /* before NEURON sets up cy' = f(y;t) */ + km["AFTER"] = AFTER; /* after NEURON solves cy' = f(y; t) */ + km["WATCH"] = WATCH; + km["FOR_NETCONS"] = FOR_NETCONS; + km["THREADSAFE"] = THREADSAFE; + km["PROTECT"] = PROTECT; + km["MUTEXLOCK"] = NRNMUTEXLOCK; + km["MUTEXUNLOCK"] = NRNMUTEXUNLOCK; + + return km; + } + + /* Keywords: name and kval pair */ + static std::map keywords = create_keywords_map(); + + /* keyword exists ? */ + bool exists(std::string key) { + return (keywords.find(key) != keywords.end()); + } + + /* return type of the keyword */ + int type(std::string key) { + if (!exists(key)) { + /* throw exception or abort here */ + std::cout << "Error : " << key << " doesn't exist! " << std::endl; + return -1; + } + + return keywords[key]; + } +} // namespace Keywords + +namespace Methods { + + /* Numerical methods from NMODL reprsent name, all the types + * that will work with this pair and whether it's a variable + * time step method. [source init.c] + */ + + static std::map create_methods_map() { + std::map mm; + + mm["adams"] = MethodInfo(DERF | KINF, 0); + mm["runge"] = MethodInfo(DERF | KINF, 0); + mm["euler"] = MethodInfo(DERF | KINF, 0); + mm["adeuler"] = MethodInfo(DERF | KINF, 1); + mm["heun"] = MethodInfo(DERF | KINF, 0); + mm["adrunge"] = MethodInfo(DERF | KINF, 1); + mm["gear"] = MethodInfo(DERF | KINF, 1); + mm["newton"] = MethodInfo(NLINF, 0); + mm["simplex"] = MethodInfo(NLINF, 0); + mm["simeq"] = MethodInfo(LINF, 0); + mm["seidel"] = MethodInfo(LINF, 0); + mm["_advance"] = MethodInfo(KINF, 0); + mm["sparse"] = MethodInfo(KINF, 0); + mm["derivimplicit"] = MethodInfo(DERF, 0); /* name hard wired in deriv.c */ + mm["cnexp"] = MethodInfo(DERF, 0); /* see solve.c */ + mm["clsoda"] = MethodInfo(DERF | KINF, 1); /* Tolerance built in to scopgear.c */ + mm["after_cvode"] = MethodInfo(0, 0); + mm["cvode_t"] = MethodInfo(0, 0); + mm["cvode_t_v"] = MethodInfo(0, 0); + + return mm; + } + + /* Methods: name, subtype and varstep touple */ + static std::map methods = create_methods_map(); + + /* Method exists ? */ + bool exists(std::string key) { + return (methods.find(key) != methods.end()); + } + + /* return type of the method */ + int type(std::string key) { + if (!exists(key)) { + /* throw exception or abort here */ + std::cout << "Error : " << key << " doesn't exist! " << std::endl; + return -1; + } + + return METHOD; + } + + std::vector get_methods() { + std::vector v; + + for (std::map::iterator it = methods.begin(); it != methods.end(); ++it) { + v.push_back(it->first); + } + + return v; + } +} // namespace Methods + +namespace ExternalDefinitions { + + /* In the original implementation, different vectors were created + * for extdef, extdef2, extdef3, extdef4 etc. Instead of that we + * are changing those vectors with map. This will + * help us to search in single map and find it's type. The types + * are defined as follows: + * + * TYPE_EXTDEF_DOUBLE: external names that can be used as doubles + * without giving an error message + * + * TYPE_EXTDEF_2 : external function names that can be used with + * array and function name arguments + * + * TYPE_EXTDEF_3 : function names that get two reset arguments added + * + * TYPE_EXTDEF_4 : functions that need a first arg of NrnThread* + * + * TYPE_EXTDEF_5 : the extdef names that are not threadsafe + * + * These name types were used so that it's easy to it to old implementation. + * + */ + + /* external names that can be used as doubles without giving an error message */ + static std::map create_extdef_map() { + std::map extdef; + + extdef["first_time"] = TYPE_EXTDEF_DOUBLE; + extdef["error"] = TYPE_EXTDEF_DOUBLE; + extdef["f_flux"] = TYPE_EXTDEF_DOUBLE; + extdef["b_flux"] = TYPE_EXTDEF_DOUBLE; + extdef["fabs"] = TYPE_EXTDEF_DOUBLE; + extdef["sqrt"] = TYPE_EXTDEF_DOUBLE; + extdef["sin"] = TYPE_EXTDEF_DOUBLE; + extdef["cos"] = TYPE_EXTDEF_DOUBLE; + extdef["tan"] = TYPE_EXTDEF_DOUBLE; + extdef["acos"] = TYPE_EXTDEF_DOUBLE; + extdef["asin"] = TYPE_EXTDEF_DOUBLE; + extdef["atan"] = TYPE_EXTDEF_DOUBLE; + extdef["atan2"] = TYPE_EXTDEF_DOUBLE; + extdef["sinh"] = TYPE_EXTDEF_DOUBLE; + extdef["cosh"] = TYPE_EXTDEF_DOUBLE; + extdef["tanh"] = TYPE_EXTDEF_DOUBLE; + extdef["floor"] = TYPE_EXTDEF_DOUBLE; + extdef["ceil"] = TYPE_EXTDEF_DOUBLE; + extdef["fmod"] = TYPE_EXTDEF_DOUBLE; + extdef["log10"] = TYPE_EXTDEF_DOUBLE; + extdef["log"] = TYPE_EXTDEF_DOUBLE; + extdef["pow"] = TYPE_EXTDEF_DOUBLE; + extdef["printf"] = TYPE_EXTDEF_DOUBLE; + extdef["prterr"] = TYPE_EXTDEF_DOUBLE; + extdef["exp"] = TYPE_EXTDEF_DOUBLE; + extdef["threshold"] = TYPE_EXTDEF_DOUBLE; + extdef["force"] = TYPE_EXTDEF_DOUBLE; + extdef["deflate"] = TYPE_EXTDEF_DOUBLE; + extdef["expfit"] = TYPE_EXTDEF_DOUBLE; + extdef["derivs"] = TYPE_EXTDEF_DOUBLE; + extdef["spline"] = TYPE_EXTDEF_DOUBLE; + extdef["hyperbol"] = TYPE_EXTDEF_DOUBLE; + extdef["revhyperbol"] = TYPE_EXTDEF_DOUBLE; + extdef["sigmoid"] = TYPE_EXTDEF_DOUBLE; + extdef["revsigmoid"] = TYPE_EXTDEF_DOUBLE; + extdef["harmonic"] = TYPE_EXTDEF_DOUBLE; + extdef["squarewave"] = TYPE_EXTDEF_DOUBLE; + extdef["sawtooth"] = TYPE_EXTDEF_DOUBLE; + extdef["revsawtooth"] = TYPE_EXTDEF_DOUBLE; + extdef["ramp"] = TYPE_EXTDEF_DOUBLE; + extdef["pulse"] = TYPE_EXTDEF_DOUBLE; + extdef["perpulse"] = TYPE_EXTDEF_DOUBLE; + extdef["step"] = TYPE_EXTDEF_DOUBLE; + extdef["perstep"] = TYPE_EXTDEF_DOUBLE; + extdef["erf"] = TYPE_EXTDEF_DOUBLE; + extdef["exprand"] = TYPE_EXTDEF_DOUBLE; + extdef["factorial"] = TYPE_EXTDEF_DOUBLE; + extdef["gauss"] = TYPE_EXTDEF_DOUBLE; + extdef["normrand"] = TYPE_EXTDEF_DOUBLE; + extdef["poisrand"] = TYPE_EXTDEF_DOUBLE; + extdef["poisson"] = TYPE_EXTDEF_DOUBLE; + extdef["setseed"] = TYPE_EXTDEF_DOUBLE; + extdef["scop_random"] = TYPE_EXTDEF_DOUBLE; + extdef["boundary"] = TYPE_EXTDEF_DOUBLE; + extdef["romberg"] = TYPE_EXTDEF_DOUBLE; + extdef["legendre"] = TYPE_EXTDEF_DOUBLE; + extdef["invert"] = TYPE_EXTDEF_DOUBLE; + extdef["stepforce"] = TYPE_EXTDEF_DOUBLE; + extdef["schedule"] = TYPE_EXTDEF_DOUBLE; + extdef["set_seed"] = TYPE_EXTDEF_DOUBLE; + extdef["nrn_pointing"] = TYPE_EXTDEF_DOUBLE; + extdef["state_discontinuity"] = TYPE_EXTDEF_DOUBLE; + extdef["net_send"] = TYPE_EXTDEF_DOUBLE; + extdef["net_move"] = TYPE_EXTDEF_DOUBLE; + extdef["net_event"] = TYPE_EXTDEF_DOUBLE; + extdef["nrn_random_play"] = TYPE_EXTDEF_DOUBLE; + extdef["at_time"] = TYPE_EXTDEF_DOUBLE; + extdef["nrn_ghk"] = TYPE_EXTDEF_DOUBLE; + + /* external function names that can be used with array and + * function name arguments + */ + extdef["romberg"] = TYPE_EXTDEF_2; + extdef["legendre"] = TYPE_EXTDEF_2; + extdef["deflate"] = TYPE_EXTDEF_2; + + /* function names that get two reset arguments added */ + extdef["threshold"] = TYPE_EXTDEF_3; + extdef["squarewave"] = TYPE_EXTDEF_3; + extdef["sawtooth"] = TYPE_EXTDEF_3; + extdef["revsawtooth"] = TYPE_EXTDEF_3; + extdef["ramp"] = TYPE_EXTDEF_3; + extdef["pulse"] = TYPE_EXTDEF_3; + extdef["perpulse"] = TYPE_EXTDEF_3; + extdef["step"] = TYPE_EXTDEF_3; + extdef["perstep"] = TYPE_EXTDEF_3; + extdef["stepforce"] = TYPE_EXTDEF_3; + extdef["schedule"] = TYPE_EXTDEF_3; + + /* functions that need a first arg of NrnThread* */ + extdef["at_time"] = TYPE_EXTDEF_4; + + /* the extdef names that are not threadsafe */ + extdef["force"] = TYPE_EXTDEF_5; + extdef["deflate"] = TYPE_EXTDEF_5; + extdef["expfit"] = TYPE_EXTDEF_5; + extdef["derivs"] = TYPE_EXTDEF_5; + extdef["spline"] = TYPE_EXTDEF_5; + extdef["exprand"] = TYPE_EXTDEF_5; + extdef["gauss"] = TYPE_EXTDEF_5; + extdef["normrand"] = TYPE_EXTDEF_5; + extdef["poisrand"] = TYPE_EXTDEF_5; + extdef["poisson"] = TYPE_EXTDEF_5; + extdef["setseed"] = TYPE_EXTDEF_5; + extdef["scop_random"] = TYPE_EXTDEF_5; + extdef["boundary"] = TYPE_EXTDEF_5; + extdef["romberg"] = TYPE_EXTDEF_5; + extdef["invert"] = TYPE_EXTDEF_5; + extdef["stepforce"] = TYPE_EXTDEF_5; + extdef["schedule"] = TYPE_EXTDEF_5; + extdef["set_seed"] = TYPE_EXTDEF_5; + extdef["nrn_random_play"] = TYPE_EXTDEF_5; + + return extdef; + } + + /* external names, function names with various attributes */ + static std::map extdef = create_extdef_map(); + + /* External definition exists ? */ + bool exists(std::string key) { + return (extdef.find(key) != extdef.end()); + } + + /* return type of the external definition */ + int type(std::string key) { + if (!exists(key)) { + /* throw exception or abort here */ + std::cout << "Error : " << key << " doesn't exist! " << std::endl; + return -1; + } + + return extdef[key]; + } + + std::vector get_extern_definitions() { + std::vector v; + + for (std::map::iterator it = extdef.begin(); it != extdef.end(); ++it) { + v.push_back(it->first); + } + + return v; + } +} // namespace ExternalDefinitions + +/* after constructing symbol table noticed that the variables + * from neuron are not yet listed so that error checker passes + * could use those. Note that these are not used by lexer at the + * moment and hence adding as vector of strings. + */ +namespace NeuronVariables { + + /* neuron variables used in neuron */ + static std::set create_neuron_var_vec() { + std::set var; + var.insert("t"); + var.insert("dt"); + var.insert("celsius"); + var.insert("v"); + var.insert("diam"); + var.insert("area"); + return var; + } + + /* neuron variables */ + static std::set variables = create_neuron_var_vec(); + + /* check if neuron variable */ + bool exists(std::string name) { + return (variables.find(name) != variables.end()); + } + + std::vector get_neuron_vars() { + std::vector v(variables.begin(), variables.end()); + return v; + } +} // namespace NeuronVariables + +namespace Lexer { + std::vector get_external_symbols() { + std::vector v, tmp; + + tmp = NeuronVariables::get_neuron_vars(); + v.insert(v.end(), tmp.begin(), tmp.end()); + + tmp = Methods::get_methods(); + v.insert(v.end(), tmp.begin(), tmp.end()); + + tmp = ExternalDefinitions::get_extern_definitions(); + v.insert(v.end(), tmp.begin(), tmp.end()); + + return v; + } + + static std::vector extern_symbols = get_external_symbols(); + + /* check if external symbol */ + bool is_extern_variable(std::string name) { + bool exist = false; + + if (std::find(extern_symbols.begin(), extern_symbols.end(), name) != extern_symbols.end()) { + exist = true; + } + + return exist; + } +} // namespace Lexer diff --git a/src/nmodl/lexer/init.hpp b/src/nmodl/lexer/init.hpp new file mode 100644 index 0000000000..158ceb1d4b --- /dev/null +++ b/src/nmodl/lexer/init.hpp @@ -0,0 +1,45 @@ +#ifndef _INIT_H_ +#define _INIT_H_ + +/* Numerical methods from NMODL reprsent name, all the types + * that will work with this pair and whether it's a variable + * time step method. [source init.c] + */ +struct MethodInfo { + long subtype; /* all the types that will work with this */ + short varstep; /* whether it's a variable step method */ + + MethodInfo() : subtype(0), varstep(0) { + } + MethodInfo(long s, short v) : subtype(s), varstep(v) { + } +}; + +namespace Keywords { + bool exists(std::string key); + int type(std::string key); +} // namespace Keywords + +namespace Methods { + bool exists(std::string key); + int type(std::string key); +} // namespace Methods + +namespace ExternalDefinitions { + bool exists(std::string key); + int type(std::string key); +} // namespace ExternalDefinitions + +namespace NeuronVariables { + bool exists(std::string key); +} + +/* naming convention is based on original nocmodl implementation */ +enum { TYPE_EXTDEF_DOUBLE, TYPE_EXTDEF_2, TYPE_EXTDEF_3, TYPE_EXTDEF_4, TYPE_EXTDEF_5 }; + +namespace Lexer { + bool is_extern_variable(std::string name); + std::vector get_external_symbols(); +} // namespace Lexer + +#endif diff --git a/src/nmodl/lexer/lexer_utils.hpp b/src/nmodl/lexer/lexer_utils.hpp new file mode 100644 index 0000000000..17dce19ab0 --- /dev/null +++ b/src/nmodl/lexer/lexer_utils.hpp @@ -0,0 +1,14 @@ +#ifndef _LEXER_UTILS_HPP_ +#define _LEXER_UTILS_HPP_ + +#include +#include "ast/ast.hpp" + +/* lexer provide some utility functions to process input */ +namespace LexerUtils { + ast::StringNode* inputtoparpar(void* scanner); + std::string inputline(void* scanner); + std::string input_until_token(std::string, void* scanner); +} + +#endif \ No newline at end of file diff --git a/src/nmodl/lexer/list.cpp b/src/nmodl/lexer/list.cpp new file mode 100644 index 0000000000..794f9354c7 --- /dev/null +++ b/src/nmodl/lexer/list.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include "lexer/modtoken.hpp" +#include "ast/ast.hpp" +#include "parser/nmodl_context.hpp" +#include "parser/nmodl_parser.hpp" +#include "lexer/modl.h" +#include "lexer/init.hpp" + +/* Lookup the token from lexer and check if it is keyword, method or external + * function definition. Currently just return type, no symbol creation + */ +ModToken* putintoken(std::string key, int type, Position pos) { + int toktype; + + switch (type) { + case STRING: + case REAL: + case INTEGER: + toktype = type; + break; + + default: + + /* check if name is KEYWORD in NMODL */ + if (Keywords::exists(key)) { + toktype = Keywords::type(key); + break; + } + + /* check if name is METHOD name in NMODL */ + if (Methods::exists(key)) { + toktype = Methods::type(key); + break; + } + + /* current token is neither keyword nor method name, treating as NAME */ + toktype = NAME; + } + + ModToken* tok = new ModToken(key, toktype, pos); + + // std::cout << std::endl << "\t" << tok; + + return tok; +} diff --git a/src/nmodl/lexer/list.hpp b/src/nmodl/lexer/list.hpp new file mode 100755 index 0000000000..a6273b0f79 --- /dev/null +++ b/src/nmodl/lexer/list.hpp @@ -0,0 +1,10 @@ +#ifndef _LIST_H_ +#define _LIST_H_ + +#include "modtoken.hpp" + +/* Methods to process tokens [source list.c] + */ +ModToken* putintoken(std::string, int, Position); + +#endif diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h new file mode 100644 index 0000000000..6c6c44554a --- /dev/null +++ b/src/nmodl/lexer/modl.h @@ -0,0 +1,37 @@ +#ifndef _MODL_H_ +#define _MODL_H_ + +/* subtypes */ +#define KEYWORD 01 +#define PARM 02 +#define INDEP 04 +#define DEP 010 /* also in usage field */ +#define STAT 020 +#define ARRAY 040 +#define FUNCT 0100 /* also in usage field */ +#define PROCED 0200 +#define NEGATIVE 0400 +#define SEMI 01 /* ";" */ + +/* @todo: we define these are now as tokens, probably we dont need it (?) */ +//#define BEGINBLK 02 /* "{" */ +//#define ENDBLK 04 /* "}" */ + +#define DERF 01000 +#define KINF 02000 +#define NLINF 04000 +#define DISCF 010000 +#define STEP1 020000 +#define PARF 040000 +#define EXTDEF 0100000 +#define LINF 0200000 +#define UNITDEF 0400000L +#define EXTDEF2 01000000L /* functions that can take array or function name arguments */ +#define nmodlCONST 02000000L /* constants that do not appear in .var file */ +#define EXTDEF3 04000000L /* get two extra reset arguments at beginning */ +#define INTGER 010000000L /* must be cast to double in expr */ +#define EXTDEF4 020000000L /* get extra NrnThread* arg at beginning */ +#define EXTDEF5 040000000L /* not threadsafe from the extdef list */ +#define EXPLICIT_DECL 01 /* usage field, variable occurs in input file */ + +#endif diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp new file mode 100644 index 0000000000..4aa30a85d4 --- /dev/null +++ b/src/nmodl/lexer/modtoken.hpp @@ -0,0 +1,145 @@ +#ifndef _MODL_TOKEN_H_ +#define _MODL_TOKEN_H_ + +#include +#include +#include +#include + +/* store the location of every token in the file */ +class Position { + public: + /* this is corresponding to the YYLTYPE structure + * of the Bison. It stores the location info of + * every token. This information is filled by Lex. + */ + int fline; // first_line + int lline; // last_line + int fcol; // first_column + int lcol; // last_column + + /* some symbols are externals. while showing them + * in symbol table, instead of showing unknown + * locations, we can show as an external + */ + + bool external; + + /* @todo: not sure how/when lcol != fcol for lexer (make sense for parser */ + + Position() : fline(-1), lline(-1), fcol(-1), lcol(-1), external(false) { + } + + Position(int fl, int ll, int fc, int lc) : fline(fl), lline(ll), fcol(fc), lcol(lc), external(false) { + } + + Position(bool ext) : external(ext) { + } + + int get_first_line() const { + return fline; + } + + int get_first_column() const { + return fcol; + } + + /* for token on same line, print it as line.firstcol.lastcol => [12.3-5] + * for token spanning different line, print as first_line-fitst_col.last_line-first_col => + * [12-3.13.5] + */ + friend std::ostream& operator<<(std::ostream& stream, const Position& p) { + if (p.external) { + return stream << "[EXTERNAL]"; + } else if (p.fline < 0 || p.lline < 0) { + return stream << "[UNKNOWN]"; + } else if (p.fline == p.lline) { + return stream << "[" << p.fline << "." << p.fcol << "-" << p.lcol << "]"; + } else { + return stream << "[" << p.fline << "-" << p.fcol << "." << p.lline << "-" << p.lcol << "]"; + } + } + + /** todo : duplicate logic for ostream overload as well */ + std::string string() { + std::stringstream ss; + + if (external) { + ss << "[EXTERNAL]"; + } else if (fline < 0 || lline < 0) { + ss << "[UNKNOWN]"; + } else if (fline == lline) { + ss << "[" << fline << "." << fcol << "-" << lcol << "]"; + } else { + ss << "[" << fline << "-" << fcol << "." << lline << "-" << lcol << "]"; + } + + return ss.str(); + } +}; + +/* every token returned by lexer, mainly value yytext, + * position information and type of the token. + * + * Do we need Position in token? Lexer can be built as a + * standalone component and can be run independently with + * it's test. + */ +class ModToken { + private: + std::string value; + int ktype; + Position _position; + + public: + ModToken() : ktype(-1) { + } + + /** todo: change this */ + ModToken(bool ext) : _position(ext) { + } + + ModToken(std::string v, int t, Position p) : value(v), ktype(t), _position(p) { + } + + ModToken(const ModToken& m) { + value = m.text(); + ktype = m.type(); + _position = m.position(); + } + + /* may not be useful */ + ModToken(std::string v, int t, int fline, int lline, int fcol, int lcol) + : value(v), ktype(t), _position(fline, lline, fcol, lcol) { + } + + ModToken* clone() const { + return new ModToken(*this); + } + + /* print token as "Token Name at Position with type Type" + * e.g. Token FUNCTION at [12.3-5] with type 258 + */ + friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { + return stream << std::setw(15) << mt.value << " at " << mt._position << " type " << mt.ktype; + } + + std::string text() const { + return value; + } + + int itext() const { + // atoi(value.str()); + return 1; + } + + int type() const { + return ktype; + } + + Position position() const { + return _position; + } +}; + +#endif diff --git a/src/nmodl/lexer/nmodl.l b/src/nmodl/lexer/nmodl.l new file mode 100755 index 0000000000..9b58005f70 --- /dev/null +++ b/src/nmodl/lexer/nmodl.l @@ -0,0 +1,560 @@ +%{ + #include + #include + #include + #include + #include + #include "ast/ast.hpp" + #include "lexer/list.hpp" + #include "lexer/modtoken.hpp" + #include "parser/nmodl_context.hpp" + #include "parser/nmodl_parser.hpp" + #include "utils/stringutils.hpp" + + /* we create position in YY_USER_ACTION */ + Position pos; + + /* for '~', we return different tokens based on the lexical + * context, store the current context. Note that this is + * returned from parser in original implementation, revisit + * this and check if this is sufficient __todo__ */ + static int lexcontext; + + /* where we update the location */ + void update_location(yyscan_t scanner); + + /* YY_USER_ACTION is "called" before each of token actions */ + #define YY_USER_ACTION update_location(yyscanner); + + /* the scanner state include a field called yyextra that can + * be used for user-defined state. The type of this field is + * specified by YY_EXTRA_TYPE + */ + #define YY_EXTRA_TYPE NmodlContext* + + /* we will parse strings as well as files and hence need to + * redefine YY_INPUT. This will use the istream from the parser + * context to read the next character. Note that the parser uses + * uses std::cin by default. We will set to istringstream as + * when constructing NmodlContext object. + */ + #define YY_INPUT(buf,result,max_size) \ + { \ + char c; \ + (*yyextra->is) >> std::noskipws >> c; \ + if(yyextra->is->eof()) \ + result = YY_NULL; \ + else { \ + buf[0] = c; \ + result = 1; \ + } \ + } + +%} + +D [0-9] +E [Ee][-+]?{D}+ + +/* need to provide this option to flex otherwise complain: + * "error: 'yymore_used_but_not_detected' was not declared in + * this scope + */ +%option yymore + +/* lexer header file */ +%option header-file="nmodl_lexer.hpp" + +/* lexer implementation file */ +%option outfile="nmodl_lexer.cpp" + +/* lexer prefix */ +%option prefix="Nmodl_" + +/* need to be reentrant */ +%option reentrant + +/* no need for includes */ +%option noyywrap + +/* need to unput in buffer for custom routines */ +%option unput + +/* need to input in buffer for custom routines */ +%option input + +/* not an interactive lexer, takes a file */ +%option batch + +/* debug, disable for production */ +%option debug + +/* bison compatible lexer */ +%option bison-bridge + +/* bison location information */ +%option bison-locations + +/* keep line information */ +%option yylineno + +/* mode for verbatim or comment */ +%x COPY_MODE + + +/* mode for DEFINE variable and it's value (i.e. macro definition) */ +%x DEFINE_MODE +%x DEFINE_INT_MODE + +%% + + +[a-zA-Z][a-zA-Z0-9_]*'+ { + + /*PRIME possibly high order*/ + int yleng = yyget_leng(yyscanner); + char nextch = yyinput(yyscan_t(yyscanner)); + std::string name(yytext); + + if(nextch == '0') { + name += "0"; + ModToken *tok = putintoken(yytext, NAME, pos); + yylval->name_ptr = new ast::NameNode( new ast::StringNode(name) ); + yylval->name_ptr->setToken(tok); + return NAME; + } else { + unput(nextch); + + ModToken *tok = putintoken(name, PRIME, pos); + size_t order = std::count(name.begin(), name.end(), '\''); + name.erase(std::remove(name.begin(), name.end(), '\''), name.end()); + yylval->primename_ptr = new ast::PrimeNameNode(new ast::StringNode(name.c_str()), new ast::IntegerNode(order, NULL)); + yylval->primename_ptr->setToken(tok); + return PRIME; + } + + } + +WHILE | +IF | +ELSE { + for(char *cp = yytext; *cp; cp++) + *cp = tolower(*cp); + ModToken *tok = putintoken(yytext, NAME, pos); + yylval->qp = tok; + return tok->type(); + } + +"VERBATIM" { BEGIN(COPY_MODE); } + +"COMMENT" { BEGIN(COPY_MODE); } + +"DEFINE" { + ModToken *tok = putintoken(yytext, NAME, pos); + yylval->qp = tok; + BEGIN(DEFINE_MODE); + return tok->type(); + } + +[a-zA-Z][a-zA-Z0-9_]* { + ModToken *tok = putintoken(yytext, INTEGER, pos); + yylval->name_ptr = new ast::NameNode( new ast::StringNode(yytext) ); + yylval->name_ptr->setToken(tok); + BEGIN(DEFINE_INT_MODE); + return NAME; + } + +{D}+ { + ModToken *tok = putintoken(yytext, INTEGER, pos); + yylval->integer_ptr = new ast::IntegerNode(atoi(yytext), NULL); + yylval->integer_ptr->setToken(tok); + BEGIN(INITIAL); + return INTEGER; + } + +[a-zA-Z][a-zA-Z0-9_]* { + ModToken *tok = putintoken(yytext, NAME, pos); + yylval->qp = tok; + int type = tok->type(); + + /* if the NAME is variable from macro definition then return INTEGER + * Make sure yyextra is not NULL because we also use lexer-only for + * testing purpose + */ + if(type == NAME && yyextra->is_defined_var(yytext)) { + int value = yyextra->get_defined_var_value(yytext); + ast::NameNode* macro_name = new ast::NameNode(new ast::StringNode(yytext)); + macro_name->setToken(tok->clone()); + yylval->integer_ptr = new ast::IntegerNode(value, macro_name); + yylval->integer_ptr->setToken(tok); + return INTEGER; + } + + if(type == NAME || type == METHOD || type == SUFFIX || type == VALENCE || type == DEL || type == DEL2) { + yylval->name_ptr = new ast::NameNode( new ast::StringNode(yytext) ); + yylval->name_ptr->setToken(tok); + } + + if( type == NONLINEAR || type == LINEAR || type == PARTIAL || type == KINETIC) { + lexcontext = type; + } + + return type; + } + + +{D}+ { + ModToken *tok = putintoken(yytext, INTEGER, pos); + yylval->integer_ptr = new ast::IntegerNode(atoi(yytext), NULL); + yylval->integer_ptr->setToken(tok); + return INTEGER; + } + +{D}+"."{D}*({E})? | +{D}*"."{D}+({E})? | +{D}+{E} { + ModToken *tok = putintoken(yytext, REAL, pos); + yylval->double_ptr = new ast::DoubleNode(atof(yytext)); + yylval->double_ptr->setToken(tok); + return REAL; + } + + +\"[^\"]*\" { + /* can't quote \" */ + ModToken *tok = putintoken(yytext, STRING, pos); + yylval->string_ptr = new ast::StringNode(yytext); + yylval->string_ptr->setToken(tok); + return STRING; + } + +">" { + ModToken *tok = putintoken(yytext, GT, pos); + yylval->qp = tok; + return GT; + } + +">=" { + ModToken *tok = putintoken(yytext, GE, pos); + yylval->qp = tok; + return GE; + } + +"<" { + ModToken *tok = putintoken(yytext, LT, pos); + yylval->qp = tok; + return LT; + } + +"<=" { + ModToken *tok = putintoken(yytext, LE, pos); + yylval->qp = tok; + return LE; + } + +"==" { + ModToken *tok = putintoken(yytext, EQ, pos); + yylval->qp = tok; + return EQ; + } + +"!=" { + ModToken *tok = putintoken(yytext, NE, pos); + yylval->qp = tok; + return NE; + } + +"!" { + ModToken *tok = putintoken(yytext, NOT, pos); + yylval->qp = tok; + return NOT; + } + +"&&" { + ModToken *tok = putintoken(yytext, AND, pos); + yylval->qp = tok; + return AND; + } + +"||" { + ModToken *tok = putintoken(yytext, OR, pos); + yylval->qp = tok; + return OR; + } + +"<->" { + ModToken *tok = putintoken(yytext, REACT1, pos); + yylval->qp = tok; + return REACT1; + } + +"~+" { + /* syntactic sugar for equation addition */ + ModToken *tok = putintoken(yytext, NONLIN1, pos); + yylval->qp = tok; + return NONLIN1; + } + +"~" { + /* syntactic sugar for equations */ + if (lexcontext == NONLINEAR) return NONLIN1; + if (lexcontext == LINEAR) return LIN1; + if (lexcontext == PARTIAL) return yytext[0]; + if (lexcontext == KINETIC) return REACTION; + std::cout << "\n ERROR: check lexer for ~ \n"; + abort(); + } + +"{" { + ModToken *tok = putintoken(yytext, BEGINBLK, pos); + yylval->qp = tok; + return yytext[0]; + } + +"}" { + ModToken *tok = putintoken(yytext, ENDBLK, pos); + yylval->qp = tok; + return yytext[0]; + } + +"(" { + ModToken *tok = putintoken(yytext, LEFT_PAREN, pos); + yylval->qp = tok; + return yytext[0]; + } + +")" { + ModToken *tok = putintoken(yytext, RIGHT_PAREN, pos); + yylval->qp = tok; + return yytext[0]; + } + +[ \t] { /*ignore spacing characters*/ } +\r\n { yyset_column(1, yyscanner); } +\r { yyset_column(1, yyscanner); } +\n.* { + yyset_column(1, yyscanner); + std::string str = std::string(yytext); + + StringUtils::trim(str); + + /* if non-empty string, then store it for error reporting */ + if(str.length()) { + StringUtils::trimnewline(str); + std::cout << std::endl << "LINE "<< yylineno << ": " << str; + } else + std::cout << std::endl << "LINE " << yylineno << ": "; + + /* pass back the entire string except newline character */ + yyless(1); + yyset_column(1, yyscanner); + } + +:.* | +\?.* { + /* Need to change parser grammar to accept one line comment + std::string str = "COMMENT " + std::string(yytext) + " ENDCOMMENT"; + yylval->str_ptr = new std::string(str); + return COMMENT; + */ + } + +. { + /* @todo: need to put this token */ + ModToken *tok = putintoken(yytext, 0, pos); + yylval->qp = tok; + return yytext[0]; + } + + /* for varbatim mode i.e. comment and verbatim */ + +[ \t] { yymore(); } +\n { yymore(); yyset_column(1, yyscanner); } +\r\n { yymore(); yyset_column(1, yyscanner); } +\r { yymore(); yyset_column(1, yyscanner); } + +"ENDVERBATIM" { + std::string str = "VERBATIM " + std::string(yytext); + yylval->str_ptr = new std::string(str); + BEGIN(INITIAL); + return VERBATIM; + } + +"ENDCOMMENT" { + std::string str = "COMMENT " + std::string(yytext); + yylval->str_ptr = new std::string(str); + BEGIN(INITIAL); + return COMMENT; + } + + +<> { + std::cout << "\n ERROR: Unexpected end of file in COPY_MODE! \n"; + return 0; + } + +. { + yymore(); + } + + /* @todo: adding new rules here, needed? */ + +\"[^\"\n]*$ { + printf("\n Unterminated String, e.g. for printf "); + } + +%% + +/* some of the utility functions can't be defined + * outside lexer file due to internal macros. These + * are utility functions ported from original nocmodl + * implementation. + */ +namespace LexerUtils { + + /* this routine is needed to input the unit + * from the UNIT block. yylex cant be used + * directly. Also, defining this routine outise + * lexer file gives compilation error as yyunput + * macros are defined in the lexer generated file + */ + ast::StringNode * inputtoparpar(void *yyscanner) { + char lastch; + std::string str; + + struct yyguts_t * yyg; + yyg = (struct yyguts_t*)yyscanner; + + int colno = yyget_column(yyscan_t(yyscanner)); + int lineno = yyget_lineno(yyscan_t(yyscanner)); + + while (1) { + lastch = yyinput(yyscan_t(yyscanner)); + + if(lastch == ')') { + unput(')'); + break; + } + else if ( lastch == '\n' || lastch == 0) { + std::cout << " Error: While parsing unit, closing parenthis not found! "; + break; + } + str += lastch; + } + + /* create new position for token */ + pos = Position(lineno, lineno, colno, colno + str.size()); + + ModToken *tok = putintoken(str, UNITS, pos); + ast::StringNode * node = new ast::StringNode(str); + node->setToken(tok); + return node; + } + + /* this routine is needed to input TITLE line. As + * we are ignoring new line in the lexer, we need + * custom routine to input a line. (@todo) + */ + std::string inputline(void *yyscanner) { + char lastch; + std::string str; + + /* note yyinput requires yyg and also yyscanner variable */ + struct yyguts_t * yyg; + yyg = (struct yyguts_t*)yyscanner; + + while (1) { + lastch = yyinput(yyscan_t(yyscanner)); + if ( lastch == '\n' || lastch == 0 || lastch == EOF) { + unput('\n'); + break; + } + str += lastch; + } + return str; + } + + /* this routine is needed to input COMMENT and VERBATIM + * blocks until ENDCOMMENT and ENDVERBATIM respectively + * @todo: this looses \n and \t which is bad for verbatim + * blocks. Need to fix this. + * @TODO: REMOVE THIS, LEFT FOR REFERENCE!! + */ + std::string input_until_token(std::string w, void *yyscanner) { + + std::string str; + char *text; + + /* required for yylex */ + YYSTYPE *yytype = yyget_lval(yyscanner); + YYLTYPE *yltype = yyget_lloc(yyscanner); + + StringUtils::trim(w); + + while(1) { + + /* get a token */ + yylex(yytype, yltype, yyscanner); + text = yyget_text(yyscanner); + + std::string s(text); + StringUtils::trim(s); + + /* break if we got expected token */ + if(strcmp(s.c_str(), w.c_str())==0) + break; + + /* otherwise just append */ + str += text; + } + + struct yyguts_t * yyg; + yyg = (struct yyguts_t*)yyscanner; + BEGIN(INITIAL); + return str; + } + + /* used for unit testing where stream is set to string buffer */ + int get_next_token(std::string name, YYSTYPE *stype, YYLTYPE *ltype) { + + int tok; + yyscan_t scanner; + YY_BUFFER_STATE buf; + NmodlContext context; + + yylex_init_extra(&context, &scanner); + + buf = yy_scan_string(name.c_str(), scanner); + tok = yylex(stype, ltype, scanner); + + yy_delete_buffer(buf, scanner); + yylex_destroy(scanner); + + return tok; + } +} + +/* initialize nmodl lexer context */ +void NmodlContext::init_scanner() { + yylex_init(&scanner); + yyset_extra(this, scanner); +} + +/* delete nmodl lexer context */ +void NmodlContext::destroy_scanner() { + yylex_destroy(scanner); +} + +/* update location information when current token being parsed */ +void update_location(yyscan_t scanner) { + + /* get column, line and length og token */ + int colno = yyget_column(scanner); + int lineno = yyget_lineno(scanner); + int yleng = yyget_leng(scanner); + + /* create new position for token */ + pos = Position(lineno, lineno, colno, colno+yleng-1); + + /* update scanner state */ + yyset_column(colno+yleng, scanner); +} diff --git a/src/nmodl/parser/nmodl.y b/src/nmodl/parser/nmodl.y new file mode 100644 index 0000000000..b282caeef1 --- /dev/null +++ b/src/nmodl/parser/nmodl.y @@ -0,0 +1,1133 @@ +/* Bison specification for NMODL Parser + * + * This is based on original NOCMODL implementation and similar + * to most of the open source tools implementation. There are C++ + * based parser examples (e.g. + * http://panthema.net/2007/flex-bison-cpp-example/) but we are + * avoiding it because they are somewhat difficult to understand + * and we have seen very few projects using flex/bison with C++. + */ + +%{ + + #include + #include + #include + #include + #include "lexer/modtoken.hpp" + #include "lexer/lexer_utils.hpp" + #include "parser/nmodl_context.hpp" + #include "utils/stringutils.hpp" + #include "ast/ast.hpp" + + /* root of the ast */ + //ast::ProgramNode *astRoot; + + /* a macro that extracts the scanner state from the parser state for yylex */ + #define scanner context->scanner + +%} + +/* print out verbose error instead of just message 'syntax error' */ +%error-verbose + +/* make a reentrant parser */ +%pure-parser + +/* parser prefix */ +%name-prefix "Nmodl_" + +/* enable location tracking */ +%locations + +/* generate header file */ +%defines + +/* yyparse() takes an extra argument context */ +%parse-param {NmodlContext* context} + +/* reentrant lexer needs an extra argument for yylex() */ +%lex-param {void * scanner} + +/* token types */ +%union { + ModToken *qp; + std::string *str_ptr; + + ast::ProgramNode *program_ptr; + ast::StatementNodeList *statement_list_ptr; + ast::ModelNode *model_ptr; + ast::StringNode *string_ptr; + ast::DefineNode *define_ptr; + ast::NameNode *name_ptr; + ast::IntegerNode *integer_ptr; + ast::ParamBlockNode *paramblock_ptr; + ast::ParamAssignNodeList *paramassign_list_ptr; + ast::ParamAssignNode *paramassign_ptr; + ast::IdentifierNode *identifier_ptr; + ast::NumberNode *number_ptr; + ast::UnitNode *units_ptr; + ast::LimitsNode *limits_ptr; + ast::UnitStateNode *unitstate_ptr; + ast::StepBlockNode *stepblock_ptr; + ast::SteppedNodeList *stepped_list_ptr; + ast::SteppedNode *stepped_ptr; + ast::NumberNodeList *number_list_ptr; + ast::IndependentBlockNode *independentblock_ptr; + ast::IndependentDefNodeList *independentdef_list_ptr; + ast::IndependentDefNode *independentdef_ptr; + ast::BooleanNode *boolean_ptr; + ast::DependentDefNode *dependentdef_ptr; + ast::DependentBlockNode *dependentblock_ptr; + ast::DependentDefNodeList *dependentdef_list_ptr; + ast::StateBlockNode *stateblock_ptr; + ast::PlotDeclarationNode *plotdeclaration_ptr; + ast::PlotVariableNodeList *plotvariable_list_ptr; + ast::InitialBlockNode *initialblock_ptr; + ast::LocalVariableNodeList *localvariable_list_ptr; + ast::ConstructorBlockNode *constructorblock_ptr; + ast::DestructorBlockNode *destructorblock_ptr; + ast::ConductanceHintNode *conductancehint_ptr; + ast::BinaryExpressionNode *binaryexpression_ptr; + ast::ExpressionNode *expression_ptr; + ast::UnaryExpressionNode *unaryexpression_ptr; + ast::NonLinEuationNode *nonlineuation_ptr; + ast::LinEquationNode *linequation_ptr; + ast::FunctionCallNode *functioncall_ptr; + ast::ExpressionNodeList *expression_list_ptr; + ast::ExpressionStatementNode *expressionstatement_ptr; + ast::FromStatementNode *fromstatement_ptr; + ast::ForAllStatementNode *forallstatement_ptr; + ast::WhileStatementNode *whilestatement_ptr; + ast::IfStatementNode *ifstatement_ptr; + ast::ElseIfStatementNodeList *elseifstatement_list_ptr; + ast::ElseStatementNode *elsestatement_ptr; + ast::DerivativeBlockNode *derivativeblock_ptr; + ast::LinearBlockNode *linearblock_ptr; + ast::NameNodeList *name_list_ptr; + ast::NonLinearBlockNode *nonlinearblock_ptr; + ast::DiscreteBlockNode *discreteblock_ptr; + ast::PartialBlockNode *partialblock_ptr; + ast::PartialEquationNode *partialequation_ptr; /*@TODO: check why this is not used */ + ast::PrimeNameNode *primename_ptr; + ast::PartialBoundaryNode *partialboundary_ptr; + ast::FunctionTableBlockNode *functiontableblock_ptr; + ast::ArgumentNodeList *argument_list_ptr; + ast::FunctionBlockNode *functionblock_ptr; + ast::ProcedureBlockNode *procedureblock_ptr; + ast::NetReceiveBlockNode *netreceiveblock_ptr; + ast::SolveBlockNode *solveblock_ptr; + ast::BreakpointBlockNode *breakpointblock_ptr; + ast::TerminalBlockNode *terminalblock_ptr; + ast::BABlockNode *bablock_ptr; + ast::BABlockTypeNode *bablocktype_ptr; + ast::WatchStatementNode *watchstatement_ptr; + ast::WatchNode *watch_ptr; + ast::DoubleNode *double_ptr; + ast::ForNetconNode *fornetcon_ptr; + ast::SensNode *sens_ptr; + ast::VarNameNodeList *varname_list_ptr; + ast::ConserveNode *conserve_ptr; + ast::CompartmentNode *compartment_ptr; + ast::LDifuseNode *ldifuse_ptr; + ast::KineticBlockNode *kineticblock_ptr; + ast::VarNameNode *varname_ptr; + ast::LagStatementNode *lagstatement_ptr; + ast::QueueStatementNode *queuestatement_ptr; + ast::QueueExpressionTypeNode *queueexpressiontype_ptr; + ast::MatchBlockNode *matchblock_ptr; + ast::MatchNodeList *match_list_ptr; + ast::MatchNode *match_ptr; + ast::UnitBlockNode *unitsblock_ptr; + ast::UnitDefNodeList *unitdef_list_ptr; + ast::FactordefNodeList *factordef_list_ptr; + ast::FactordefNode *factordef_ptr; + ast::UnitDefNode *unitdef_ptr; + ast::ConstantStatementNode *constatntstatement_ptr; + ast::ConstantBlockNode *constatntblock_ptr; + ast::ConstantStatementNodeList *constantstatement_list_ptr; + ast::TableStatementNode *tablestatement_ptr; + ast::NeuronBlockNode *neuronblock_ptr; + ast::NrnSuffixNode *nrnsuffix_ptr; + ast::NrnUseionNode *nrnuseion_ptr; + ast::NrnElctrodeCurrentNode *nrnelctrodecurrent_ptr; + ast::NrnSectionNode *nrnsection_ptr; + ast::NrnRangeNode *nrnrange_ptr; + ast::NrnGlobalNode *nrnglobal_ptr; + ast::NrnPointerNode *nrnpointer_ptr; + ast::NrnBbcorePtrNode *nrnbbcoreptr_ptr; + ast::NrnExternalNode *nrnexternal_ptr; + ast::NrnThreadSafeNode *nrnthreadsafe_ptr; + ast::ValenceNode *valence_ptr; + + ast::ReactionStatementNode *reactionstatement_ptr; + ast::ReactionOperatorNode *reactionoperator_ptr; + ast::ReadIonVarNodeList *readionvar_list_ptr; + ast::WriteIonVarNodeList *writeionvar_list_ptr; + ast::NonspeCurVarNodeList *nonspecurvar_list_ptr; + ast::ElectrodeCurVarNodeList *electrodecurvar_list_ptr; + ast::SectionVarNodeList *sectionvar_list_ptr; + ast::RangeVarNodeList *rangevar_list_ptr; + ast::GlobalVarNodeList *globalvar_list_ptr; + ast::PointerVarNodeList *pointervar_list_ptr; + ast::BbcorePointerVarNodeList *bbcorepointervar_list_ptr; + ast::ExternVarNodeList *externvar_list_ptr; + ast::ThreadsafeVarNodeList *threadsafevar_list_ptr; + + ast::VerbatimNode *verbatim_ptr; + ast::CommentNode *comment_ptr; + ast::FloatNode *float_ptr; + ast::IndexedNameNode *indexedname_ptr; + ast::ArgumentNode *argument_ptr; + ast::LocalVariableNode *localvariable_ptr; + ast::LocalListStatementNode *localliststatement_ptr; + ast::NumberRangeNode *numberrange_ptr; + ast::StatementNode *statement_ptr; + ast::StatementBlockNode *statementblock_ptr; + ast::BlockNode *block_ptr; + ast::FirstLastTypeIndexNode *firstlasttypeindex_ptr; + ast::BinaryOp binary_op_val; + ast::BinaryOperatorNode *binaryoperator_ptr; + ast::DoubleUnitNode *doubleunits_ptr; +} + + +/* Define our terminal symbols (tokens). This should + * match our tokens.l lex file. + */ + +%token MODEL CONSTANT INDEPENDENT DEPENDENT STATE +%token INITIAL1 DERIVATIVE SOLVE USING WITH STEPPED DISCRETE +%token FROM FORALL1 TO BY WHILE IF ELSE START1 STEP SENS SOLVEFOR +%token PROCEDURE PARTIAL DEFINE1 IFERROR PARAMETER +%token DERFUNC EQUATION TERMINAL LINEAR NONLINEAR FUNCTION1 LOCAL +%token LIN1 NONLIN1 PUTQ GETQ TABLE DEPEND BREAKPOINT +%token INCLUDE1 FUNCTION_TABLE PROTECT NRNMUTEXLOCK NRNMUTEXUNLOCK +%token '{' '}' '(' ')' '[' ']' '@' '+' '*' '-' '/' '=' '^' ':' ',' +%token '~' +%token OR AND GT LT LE EQ NE NOT + +%token PLOT VS LAG RESET MATCH MODEL_LEVEL SWEEP FIRST LAST +%token KINETIC CONSERVE REACTION REACT1 COMPARTMENT UNITS +%token UNITSON UNITSOFF LONGDIFUS + +%token NEURON NONSPECIFIC READ WRITE USEION THREADSAFE +%token GLOBAL SECTION RANGE POINTER BBCOREPOINTER EXTERNAL BEFORE AFTER WATCH +%token ELECTRODE_CURRENT CONSTRUCTOR DESTRUCTOR NETRECEIVE FOR_NETCONS +%token CONDUCTANCE + + + +/* Extra token that we are adding to print more useful errors. These + * are currently not being used. see wiki page section "Extra Tokens" + */ +%token BEGINBLK ENDBLK /* this is left and right curly braces */ +%token LEFT_PAREN RIGHT_PAREN /* this is left and right parenthesis */ + +/*building ast : probably dont do this, just return value!*/ +%token PRIME +%token INTEGER +%token REAL +%token STRING +%token NAME +%token METHOD +%token SUFFIX +%token VALENCE +%token DEL +%token DEL2 +%token DEFINEDVAR + +/*special token returning string */ +%token VERBATIM +%token COMMENT + +/* Define the type of node our nonterminal symbols represent. + The types refer to the %union declaration above. +*/ + +/* building AST */ +%type all + +%type Name +%type NUMBER +%type real +%type intexpr +%type integer + +%type line +%type model +%type units +%type optindex +%type unit + +%type proc + +%type limits +%type abstol +%type name +%type number + +%type primary +%type term +%type leftlinexpr +%type linexpr +%type numlist +%type expr +%type aexpr + +%type ostmt +%type astmt +%type stmtlist +%type locallist +%type locallist1 + +%type varname +%type exprlist +%type define1 +%type queuestmt +%type asgn + +%type fromstmt +%type whilestmt +%type ifstmt +%type optelseif +%type optelse +%type solveblk +%type funccall + +%type ifsolerr +%type opinc +%type opstart +/* @todo: though name is sens_pt it has senslist production*/ +%type senslist +%type sens + +%type lagstmt +%type forallstmt +%type parmasgn +%type stepped +%type indepdef + +%type depdef +/* @todo: productions for which we dont have %type + * withby : dont need, imo + */ +%type declare +%type parmblk +%type parmbody +%type indepblk +%type indepbody +%type depblk +%type depbody + +%type stateblk +%type stepblk + +%type stepbdy + +%type watchstmt +%type watchdir +%type watch1 +%type fornetcon +%type plotdecl + +%type pvlist + +%type constblk +%type conststmt +%type matchblk +%type matchlist +%type match +%type matchname + +%type pareqn +/* values from ast enum types */ +%type firstlast +%type reaction +%type conserve +%type react + +%type compart +%type ldifus +%type namelist +%type unitblk +%type unitbody +%type unitdef +%type factordef + +%type solvefor + +%type solvefor1 +%type uniton +%type tablst +%type tablst1 +%type tablestmt +%type dependlst + +%type arglist +%type arglist1 +%type locoptarray +%type neuronblk +%type nrnuse +%type nrnstmt + +%type nrnionrlist +%type nrnionwlist +%type nrnonspeclist +%type nrneclist +%type nrnseclist +%type nrnrangelist +%type nrnglobalist +%type nrnptrlist +%type nrnbbptrlist +%type nrnextlist +%type opthsafelist +%type threadsafelist +%type valence +%type initstmt +%type bablk + +%type conducthint + +/*missing types found while constructing ast */ +%type stmtlist1 +%type initblk +%type constructblk +%type destructblk +%type funcblk +%type kineticblk +%type brkptblk +%type derivblk +%type linblk +%type nonlinblk +%type procedblk +%type netrecblk +%type terminalblk +%type discretblk +%type partialblk +%type functableblk + +/* precedence in expressions--- low to high */ +%left OR +%left AND +%left GT GE LT LE EQ NE +%left '+' '-' /* left associative, same precedence */ +%left '*' '/' '%' /* left assoc., higher precedence */ +%left UNARYMINUS NOT +%right '^' /* exponentiation */ + +%{ + extern int yylex(YYSTYPE*, YYLTYPE*, void*); + extern int yyparse(NmodlContext*); + extern void yyerror(YYLTYPE*, NmodlContext*, const char *); + std::string parse_with_verbatim_parser(std::string *); +%} + + +/* start symbol is named "top" */ +%start top + +%% + + +top : all { context->astRoot = $1; } + | error { /*_ER_*/ } + ; + +all : { $$ = new ast::ProgramNode(); } + | all astmt { $1->addStatement($2); } /* __todo__ : for diffeq */ + | all model { $1->addStatement($2); } + | all locallist { $1->addStatement($2); } + | all define1 { $1->addStatement($2); } + | all declare { $1->addBlock($2); } + | all MODEL_LEVEL INTEGER declare { /*__todo__ @Michael : discussed with Michael, was inserted + by merge program (which no longer exist). This was to + avoid the name collision in case of include. Idea was + to have some kind of namespace! + */ + } + | all proc { $1->addBlock($2); } + | all VERBATIM { $1->addStatement(new ast::VerbatimNode(new ast::StringNode(parse_with_verbatim_parser($2)))); } + | all COMMENT { $1->addStatement(new ast::CommentNode(new ast::StringNode(parse_with_verbatim_parser($2)))); } + | all uniton { $1->addStatement($2); } + | all INCLUDE1 STRING { $1->addStatement(new ast::IncludeNode($3)); } + ; + +model : MODEL line { $$ = new ast::ModelNode($2); } + ; + +line : { $$ = new ast::StringNode(LexerUtils::inputline(scanner)); } + ; + +define1 : DEFINE1 NAME INTEGER { + $$ = new ast::DefineNode($2, $3); + context->add_defined_var($2->getName(), $3->eval()); + } + | DEFINE1 error { /*_ER_*/ } + ; + +Name : NAME { $$ = $1; } + ; + +declare : parmblk { $$ = $1; } + | indepblk { $$ = $1; } + | depblk { $$ = $1; } + | stateblk { $$ = $1; } + | stepblk { $$ = $1; } + | plotdecl { $$ = new ast::PlotBlockNode($1); } + | neuronblk { $$ = $1; } + | unitblk { $$ = $1; } + | constblk { $$ = $1; } + ; + +parmblk : PARAMETER '{' parmbody '}' { $$ = new ast::ParamBlockNode($3); } + ; + +parmbody : { $$ = new ast::ParamAssignNodeList(); } + | parmbody parmasgn { $1->push_back($2); } + ; + +parmasgn : NAME '=' number units limits { $$ = new ast::ParamAssignNode($1, $3, $4, $5); } + | NAME units limits { $$ = new ast::ParamAssignNode($1, NULL, $2, $3); } + | NAME '[' integer ']' units limits { $$ = new ast::ParamAssignNode( new ast::IndexedNameNode($1, $3), NULL, $5, $6); } + | error { /*_ER_*/} + ; + +units : { $$ = NULL; } + | unit { $$ = $1; } + ; + +unit : '(' { $$ = LexerUtils::inputtoparpar(scanner); } ')' { $$ = new ast::UnitNode($2); } + ; + +uniton : UNITSON { $$ = new ast::UnitStateNode(ast::UNIT_ON); } + | UNITSOFF { $$ = new ast::UnitStateNode(ast::UNIT_OFF); } + ; + +limits : { $$ = NULL; } + | LT real ',' real GT { $$ = new ast::LimitsNode($2, $4); } + ; + +stepblk : STEPPED '{' stepbdy '}' { $$ = new ast::StepBlockNode($3); } + ; + +stepbdy : { $$ = new ast::SteppedNodeList(); } + | stepbdy stepped { $1->push_back($2); } + ; + +stepped : NAME '=' numlist units { $$ = new ast::SteppedNode($1, $3, $4); } + ; + +numlist : number ',' number { $$ = new ast::NumberNodeList(); $$->push_back($1); $$->push_back($3); } + | numlist ',' number { $1->push_back($3); } + ; + +name : Name { $$ = $1; } + | PRIME { $$ = $1; } + ; + +number : NUMBER { $$ = $1; } + | '-' NUMBER { $2->negate(); $$ = $2; } + ; + +NUMBER : integer { $$ = $1; } + | REAL { $$ = $1; } + ; + +integer : INTEGER { $$ = $1; } + | DEFINEDVAR { $$ = $1; } + ; + +real : REAL { $$ = $1; } + | integer { $$ = new ast::DoubleNode(double($1->eval())); } + ; + +indepblk : INDEPENDENT '{' indepbody '}' { $$ = new ast::IndependentBlockNode($3); } + ; + +indepbody : { $$ = new ast::IndependentDefNodeList(); } + | indepbody indepdef { $1->push_back($2); } + | indepbody SWEEP indepdef { $1->push_back($3); $3->sweep = new ast::BooleanNode(1); } + ; + +indepdef : NAME FROM number TO number withby integer opstart units { $$ = new ast::IndependentDefNode(NULL, $1, $3, $5, $7, $8, $9); } + | error { /*_ER_*/ } + ; + +withby : WITH + ; + +depblk : DEPENDENT '{' depbody '}' { $$ = new ast::DependentBlockNode($3); } + ; + +depbody : { $$ = new ast::DependentDefNodeList(); } + | depbody depdef { $1->push_back($2); } + ; + +depdef : name opstart units abstol { $$ = new ast::DependentDefNode($1, NULL, NULL, NULL, $2, $3, $4); } + | name '[' integer ']' opstart units abstol { $$ = new ast::DependentDefNode($1, $3, NULL, NULL, $5, $6, $7);} + | name FROM number TO number opstart units abstol { $$ = new ast::DependentDefNode($1, NULL, $3, $5, $6, $7, $8); } + | name '[' integer ']' FROM number TO number opstart units abstol { $$ = new ast::DependentDefNode($1, $3, $6, $8, $9, $10, $11);} + | error { /*_ER_*/ } + ; + +opstart : { $$ = NULL; } + | START1 number { $$ = $2; } + ; + +abstol : { $$ = NULL; } + | LT real GT { $$ = $2; } + ; + +stateblk : STATE '{' depbody '}' { $$ = new ast::StateBlockNode($3); } + ; + +plotdecl : PLOT pvlist VS name optindex { $$ = new ast::PlotDeclarationNode($2, new ast::PlotVariableNode($4,$5)); } + | PLOT error { /*_ER_*/ } + ; + +pvlist : name optindex { $$ = new ast::PlotVariableNodeList(); $$->push_back(new ast::PlotVariableNode($1, $2)); } + | pvlist ',' name optindex { $$ = $1; $$->push_back(new ast::PlotVariableNode($3, $4));} + ; + +optindex : { $$ = NULL; } + | '[' INTEGER ']' { $$ = $2; } + ; + +proc : initblk { $$ = $1; } + | derivblk { $$ = $1; } + | brkptblk { $$ = $1; } + | linblk { $$ = $1; } + | nonlinblk { $$ = $1; } + | funcblk { $$ = $1; } + | procedblk { $$ = $1; } + | netrecblk { $$ = $1; } + | terminalblk { $$ = $1; } + | discretblk { $$ = $1; } + | partialblk { $$ = $1; } + | kineticblk { $$ = $1; } + | constructblk { $$ = $1; } + | destructblk { $$ = $1; } + | functableblk { $$ = $1; } + | BEFORE bablk { $$ = new ast::BeforeBlockNode($2); } + | AFTER bablk { $$ = new ast::AfterBlockNode($2); } + ; + +initblk : INITIAL1 stmtlist '}' { $$ = new ast::InitialBlockNode($2); } + ; + +constructblk : CONSTRUCTOR stmtlist '}' { $$ = new ast::ConstructorBlockNode($2); } + ; + +destructblk : DESTRUCTOR stmtlist '}' { $$ = new ast::DestructorBlockNode($2); } + ; + +stmtlist : '{' stmtlist1 { $$ = new ast::StatementBlockNode($2); $$->setToken($1->clone()); } + | '{' locallist stmtlist1 { $3->insert($3->begin(), $2); $$ = new ast::StatementBlockNode($3); $$->setToken($1->clone()); } + ; + +conducthint : CONDUCTANCE Name { $$ = new ast::ConductanceHintNode($2, NULL); } + | CONDUCTANCE Name USEION NAME { $$ = new ast::ConductanceHintNode($2, $4); } + ; + +locallist : LOCAL locallist1 { $$ = new ast::LocalListStatementNode($2); } + | LOCAL error { /*_ER_*/ } + ; + +locallist1 : NAME locoptarray + { + $$ = new ast::LocalVariableNodeList(); + if($2) { + $$->push_back( new ast::LocalVariableNode(new ast::IndexedNameNode($1, $2))); + } else { + $$->push_back( new ast::LocalVariableNode($1) ); + } + } + | locallist1 ',' NAME locoptarray + { + if($4) { + $1->push_back( new ast::LocalVariableNode(new ast::IndexedNameNode($3, $4))); + } else { + $1->push_back( new ast::LocalVariableNode($3) ); + } + } + ; + +locoptarray : { $$ = NULL; } + | '[' integer ']' { $$ = $2; } + ; + +stmtlist1 : { $$ = new ast::StatementNodeList(); } + | stmtlist1 ostmt { $1->push_back($2); } + | stmtlist1 astmt { $1->push_back($2); } + ; + +ostmt : fromstmt { $$ = $1; } + | forallstmt { $$ = $1; } + | whilestmt { $$ = $1; } + | ifstmt { $$ = $1; } + | stmtlist '}' { $$ = new ast::ExpressionStatementNode($1); } + | solveblk { $$ = new ast::ExpressionStatementNode($1); } + | conducthint { $$ = $1; } + | VERBATIM { $$ = new ast::VerbatimNode(new ast::StringNode(parse_with_verbatim_parser($1))); } + | COMMENT { $$ = new ast::CommentNode(new ast::StringNode(parse_with_verbatim_parser($1))); } + | sens { $$ = $1; } + | compart { $$ = $1; } + | ldifus { $$ = $1; } + | conserve { $$ = $1; } + | lagstmt { $$ = $1; } + | queuestmt { $$ = $1; } + | RESET { $$ = new ast::ResetNode(); } + | matchblk { $$ = new ast::ExpressionStatementNode($1); } + | pareqn { $$ = $1; } + | tablestmt { $$ = $1; } + | uniton { $$ = $1; } + | initstmt { $$ = $1; } + | watchstmt { $$ = $1; } + | fornetcon { $$ = new ast::ExpressionStatementNode($1); } + | NRNMUTEXLOCK { $$ = new ast::MutexLockNode(); } + | NRNMUTEXUNLOCK{ $$ = new ast::MutexUnlockNode(); } + | error { /*_ER_*/ } + ; + +astmt : asgn { $$ = new ast::ExpressionStatementNode($1); } + | PROTECT asgn { $$ = new ast::ProtectStatementNode($2); } + | reaction { $$ = $1; } + | funccall { $$ = new ast::ExpressionStatementNode($1); } + ; + +asgn : varname '=' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ASSIGN), $3); } + | nonlineqn expr '=' expr { $$ = new ast::NonLinEuationNode($2, $4); } + | lineqn leftlinexpr '=' linexpr { $$ = new ast::LinEquationNode($2, $4); } + ; + +varname : name { $$ = new ast::VarNameNode($1, NULL); } + | name '[' intexpr ']' { $$ = new ast::VarNameNode(new ast::IndexedNameNode($1, $3), NULL); } + | NAME '@' integer { $$ = new ast::VarNameNode($1, $3); } + | NAME '@' integer '[' intexpr ']' { $$ = new ast::VarNameNode(new ast::IndexedNameNode($1, $5), $3); } + ; + +intexpr : Name { $$ = $1; } + | integer { $$ = $1; } + | '(' intexpr ')' { $$ = $2; } + | intexpr '+' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } + | intexpr '-' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } + | intexpr '*' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } + | intexpr '/' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } + | error {} + ; + +expr : varname { $$ = $1; } + | real units { + if($2) + $$ = new ast::DoubleUnitNode($1, $2); + else + $$ = $1; + } + | funccall { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | expr '+' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } + | expr '-' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } + | expr '*' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } + | expr '/' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } + | expr '^' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_POWER), $3); } + | expr OR expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_OR), $3); } + | expr AND expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_AND), $3); } + | expr GT expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_GREATER), $3); } + | expr LT expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_LESS), $3); } + | expr GE expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_GREATER_EQUAL), $3); } + | expr LE expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_LESS_EQUAL), $3); } + | expr EQ expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_EXACT_EQUAL), $3); } + | expr NE expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_NOT_EQUAL), $3); } + | NOT expr { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NOT), $2); } + | '-' expr %prec UNARYMINUS { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NEGATION), $2); } + | error { /*_ER_*/ } + ; + + /* + | '(' expr { yyerror("Unbalanced left parenthesis followed by valid expressions"); } + | '(' error { yyerror("Unbalanced left parenthesis followed by non parseable"); } + | expr ')' { yyerror("Unbalanced right parenthesis"); } + */ + +nonlineqn : NONLIN1 + ; + +lineqn : LIN1 + ; + +leftlinexpr : linexpr { $$ = $1; } + ; + +linexpr : primary { $$ = $1; } + | '-' primary { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NEGATION), $2); } + | linexpr '+' primary { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } + | linexpr '-' primary { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } + ; + +primary : term { $$ = $1; } + | primary '*' term { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } + | primary '/' term { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } + ; + +term : varname { $$ = $1; } + | real { $$ = $1; } + | funccall { $$ = $1; } + | '(' expr ')' { $$ = $2; } + | error { /*_ER_*/ } + ; + +funccall : NAME '(' exprlist ')' { $$ = new ast::FunctionCallNode($1, $3); } + ; + +exprlist : { $$ = NULL; } + | expr { $$ = new ast::ExpressionNodeList(); $$->push_back($1); } + | STRING { $$ = new ast::ExpressionNodeList(); $$->push_back($1); } + | exprlist ',' expr { $1->push_back($3); } + | exprlist ',' STRING { $1->push_back($3); } + ; + +fromstmt : FROM NAME '=' intexpr TO intexpr opinc stmtlist '}' { $$ = new ast::FromStatementNode($2, $4, $6, $7, $8); } + | FROM error { /*_ER_*/ } + ; + +opinc : { $$ = NULL; } + | BY intexpr { $$ = $2; } + ; + +forallstmt : FORALL1 NAME stmtlist '}' { $$ = new ast::ForAllStatementNode($2, $3); } + | FORALL1 error { /*_ER_*/ } + ; + +whilestmt : WHILE '(' expr ')' stmtlist '}' { $$ = new ast::WhileStatementNode($3, $5); } + ; + +ifstmt : IF '(' expr ')' stmtlist '}' optelseif optelse { $$ = new ast::IfStatementNode($3, $5, $7, $8); } + ; + +optelseif : { $$ = new ast::ElseIfStatementNodeList(); } + | optelseif ELSE IF '(' expr ')' stmtlist '}' { $1->push_back(new ast::ElseIfStatementNode($5, $7)); } + ; + +optelse : { $$ = NULL; } + | ELSE stmtlist '}' { $$ = new ast::ElseStatementNode($2); } + ; + +derivblk : DERIVATIVE NAME stmtlist '}' { $$ = new ast::DerivativeBlockNode($2, $3); $$->setToken($1->clone()); } + ; + +linblk : LINEAR NAME solvefor stmtlist '}' { $$ = new ast::LinearBlockNode($2, $3, $4); $$->setToken($1->clone()); } + ; + +nonlinblk : NONLINEAR NAME solvefor stmtlist '}' { $$ = new ast::NonLinearBlockNode($2, $3, $4); $$->setToken($1->clone()); } + ; + +discretblk : DISCRETE NAME stmtlist '}' { $$ = new ast::DiscreteBlockNode($2, $3); $$->setToken($1->clone()); } + ; + +partialblk : PARTIAL NAME stmtlist '}' { $$ = new ast::PartialBlockNode($2, $3); $$->setToken($1->clone()); } + | PARTIAL error { /*_ER_*/ } + ; + +pareqn : '~' PRIME '=' NAME '*' DEL2 '(' NAME ')' '+' NAME { $$ = new ast::PartialBoundaryNode(NULL, $2, NULL, NULL, $4, $6, $8, $11); } + | '~' DEL NAME '[' firstlast ']' '=' expr { $$ = new ast::PartialBoundaryNode($2, $3, $5, $8, NULL, NULL, NULL, NULL); } + | '~' NAME '[' firstlast ']' '=' expr { $$ = new ast::PartialBoundaryNode(NULL, $2, $4, $7, NULL, NULL, NULL, NULL); } + ; + +firstlast : FIRST { $$ = new ast::FirstLastTypeIndexNode(ast::PEQ_FIRST); } + | LAST { $$ = new ast::FirstLastTypeIndexNode(ast::PEQ_LAST); } + ; + +functableblk : FUNCTION_TABLE NAME '(' arglist ')' units { $$ = new ast::FunctionTableBlockNode($2, $4, $6); $$->setToken($1->clone()); } + ; + +funcblk : FUNCTION1 NAME '(' arglist ')' units stmtlist '}' { $$ = new ast::FunctionBlockNode($2, $4, $6, $7); $$->setToken($1->clone()); } + ; + +arglist : { $$ = NULL; } + | arglist1 { $$ = $1; } + ; + +arglist1 : name units { $$ = new ast::ArgumentNodeList(); $$->push_back(new ast::ArgumentNode($1, $2)); } + | arglist1 ',' name units { $1->push_back(new ast::ArgumentNode($3, $4)); } + ; + +procedblk : PROCEDURE NAME '(' arglist ')' units stmtlist '}' { $$ = new ast::ProcedureBlockNode($2, $4, $6, $7); $$->setToken($1->clone()); } + ; + +netrecblk : NETRECEIVE '(' arglist ')' stmtlist '}' { $$ = new ast::NetReceiveBlockNode($3, $5); } + | NETRECEIVE error { /*_ER_*/ } + ; + +initstmt : INITIAL1 stmtlist '}' { $$ = new ast::ExpressionStatementNode(new ast::InitialBlockNode($2)); } + ; + +solveblk : SOLVE NAME ifsolerr { $$ = new ast::SolveBlockNode($2, NULL, $3); } + | SOLVE NAME USING METHOD ifsolerr { $$ = new ast::SolveBlockNode($2, $4, $5); } + | SOLVE error { /*_ER_*/ } + ; + +ifsolerr : { $$ = NULL; } + | IFERROR stmtlist '}' { $$ = $2; } + ; + +solvefor : { $$ = NULL; } + | solvefor1 { $$ = $1; } + ; + +solvefor1 : SOLVEFOR NAME { $$ = new ast::NameNodeList(); $$->push_back($2); } + | solvefor1 ',' NAME { $1->push_back($3); } + | SOLVEFOR error { /*_ER_*/ } + ; + +brkptblk : BREAKPOINT stmtlist '}' { $$ = new ast::BreakpointBlockNode($2); } + ; + +terminalblk : TERMINAL stmtlist '}' { $$ = new ast::TerminalBlockNode($2); } + ; + +bablk : BREAKPOINT stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_BREAKPOINT), $2); } + | SOLVE stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_SOLVE), $2); } + | INITIAL1 stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_INITIAL), $2); } + | STEP stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_STEP), $2); } + | error { /*_ER_*/ } + ; + +watchstmt : WATCH watch1 { $$ = new ast::WatchStatementNode(new ast::WatchNodeList()); $$->addWatch($2); } + | watchstmt ',' watch1 { $1->addWatch($3); } + | WATCH error { /*_ER_*/ } + ; + +watch1 : '(' aexpr watchdir aexpr ')' real { $$ = new ast::WatchNode( new ast::BinaryExpressionNode($2, $3, $4), $6); } + ; + +watchdir : GT { $$ = new ast::BinaryOperatorNode(ast::BOP_GREATER); } + | LT { $$ = new ast::BinaryOperatorNode(ast::BOP_LESS); } + ; + +fornetcon : FOR_NETCONS '(' arglist ')' stmtlist '}' { $$ = new ast::ForNetconNode($3, $5); } + | FOR_NETCONS error { /*_ER_*/ } + ; + +aexpr : varname { $$ = $1; } + | real units { $$ = new ast::DoubleUnitNode($1, $2); } + | funccall { $$ = $1; } + | '(' aexpr ')' { $$ = $2; } + | aexpr '+' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } + | aexpr '-' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } + | aexpr '*' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } + | aexpr '/' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } + | aexpr '^' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_POWER), $3); } + | '-' aexpr %prec UNARYMINUS { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NEGATION), $2); } + | error { /*_ER_*/ } + ; + +sens : SENS senslist { $$ = new ast::SensNode($2); } + | SENS error { /*_ER_*/ } + ; + +senslist : varname { $$ = new ast::VarNameNodeList(); $$->push_back($1); } + | senslist ',' varname { $1->push_back($3); } + ; + +conserve : CONSERVE react '=' expr { $$ = new ast::ConserveNode($2, $4); } + | CONSERVE error {} + ; + +compart : COMPARTMENT NAME ',' expr '{' namelist '}' { $$ = new ast::CompartmentNode($2, $4, $6); } + | COMPARTMENT expr '{' namelist '}' { $$ = new ast::CompartmentNode(NULL, $2, $4); } + ; + +ldifus : LONGDIFUS NAME ',' expr '{' namelist '}' { $$ = new ast::LDifuseNode($2, $4, $6); } + | LONGDIFUS expr '{' namelist '}' { $$ = new ast::LDifuseNode(NULL, $2, $4); } + ; + +namelist : NAME { $$ = new ast::NameNodeList(); $$->push_back($1); } + | namelist NAME { $1->push_back($2); } + ; + +kineticblk : KINETIC NAME solvefor stmtlist '}' { $$ = new ast::KineticBlockNode($2, $3, $4); $$->setToken($1->clone()); } + ; + +reaction : REACTION react REACT1 react '(' expr ',' expr ')' { $$ = new ast::ReactionStatementNode($2, new ast::ReactionOperatorNode(ast::LTMINUSGT), $4, $6, $8); } + | REACTION react LT LT '(' expr ')' { $$ = new ast::ReactionStatementNode($2, new ast::ReactionOperatorNode(ast::LTLT), NULL, $6, NULL); } + | REACTION react '-' GT '(' expr ')' { $$ = new ast::ReactionStatementNode($2, new ast::ReactionOperatorNode(ast::MINUSGT), NULL, $6, NULL); } + | REACTION error { /* todo : __REACTION_DISCUSS with Michael__ */ } + ; + +react : varname { $$ = $1; } + |integer varname { $$ = new ast::ReactVarNameNode($1, $2); } + |react '+' varname { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } + |react '+' integer varname { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), new ast::ReactVarNameNode($3, $4)); } + ; + +lagstmt : LAG name BY NAME { $$ = new ast::LagStatementNode($2, $4); } + | LAG error { /*_ER_*/ } + ; + +queuestmt : PUTQ name { $$ = new ast::QueueStatementNode(new ast::QueueExpressionTypeNode(ast::PUT_QUEUE), $2); } + | GETQ name { $$ = new ast::QueueStatementNode(new ast::QueueExpressionTypeNode(ast::GET_QUEUE), $2); } + ; + +matchblk : MATCH '{' matchlist '}' { $$ = new ast::MatchBlockNode($3); } + ; + +matchlist : match { $$ = new ast::MatchNodeList(); $$->push_back($1); } + | matchlist match { $1->push_back($2); } + ; + +match : name { $$ = new ast::MatchNode($1, NULL); } + | matchname '(' expr ')' '=' expr { $$ = new ast::MatchNode($1, new ast::BinaryExpressionNode($3, new ast::BinaryOperatorNode(ast::BOP_ASSIGN), $6)); } + | error { /*_ER_*/ } + ; + +matchname : name { $$ = $1; } + | name '[' NAME ']' { $$ = new ast::IndexedNameNode($1, $3); } + ; + +unitblk : UNITS '{' unitbody '}' { $$ = new ast::UnitBlockNode($3); } + ; + +unitbody : { $$ = new ast::ExpressionNodeList(); } + | unitbody unitdef { $1->push_back($2); } + | unitbody factordef { $1->push_back($2); } + ; + +unitdef : unit '=' unit { $$ = new ast::UnitDefNode($1, $3); } + | unit error { /*_ER_*/ } + ; + +factordef : NAME '=' real unit { $$ = new ast::FactordefNode($1, $3, $4, NULL, NULL); $$->setToken($1->getToken()->clone()); } + | NAME '=' unit unit { $$ = new ast::FactordefNode($1, NULL, $3, NULL, $4); $$->setToken($1->getToken()->clone()); } + | NAME '=' unit '-' GT unit { $$ = new ast::FactordefNode($1, NULL, $3, new ast::BooleanNode(1), $6); $$->setToken($1->getToken()->clone()); } + | error {} + ; + +constblk : CONSTANT '{' conststmt '}' { $$ = new ast::ConstantBlockNode($3); } + ; + +conststmt : { $$ = new ast::ConstantStatementNodeList(); } + | conststmt NAME '=' number units { $1->push_back( new ast::ConstantStatementNode($2, $4, $5) ); } + ; + +tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER { $$ = new ast::TableStatementNode($2, $3, $5, $7, $9); } + | TABLE error { /*_ER_*/ } + ; + +tablst : { $$ = NULL; } + | tablst1 { $$ = $1; } + ; + +tablst1 : Name { $$ = new ast::NameNodeList(); $$->push_back($1); } + | tablst1 ',' Name { $1->push_back($3); } + ; + +dependlst : { $$ = NULL; } + | DEPEND tablst1 { $$ = $2; } + ; + +neuronblk : NEURON '{' nrnstmt '}' { ast::StatementBlockNode *p = new ast::StatementBlockNode($3); p->setToken($2->clone()); $$ = new ast::NeuronBlockNode(p); } + ; + +nrnstmt : { $$ = new ast::StatementNodeList(); } + | nrnstmt SUFFIX NAME { $1->push_back( new ast::NrnSuffixNode($2, $3) ); } + | nrnstmt nrnuse { $1->push_back($2); } + | nrnstmt NONSPECIFIC nrnonspeclist { $1->push_back(new ast::NrnNonspecificNode($3)); } + | nrnstmt ELECTRODE_CURRENT nrneclist { $1->push_back(new ast::NrnElctrodeCurrentNode($3)); } + | nrnstmt SECTION nrnseclist { $1->push_back(new ast::NrnSectionNode($3)); } + | nrnstmt RANGE nrnrangelist { $1->push_back(new ast::NrnRangeNode($3)); } + | nrnstmt GLOBAL nrnglobalist { $1->push_back(new ast::NrnGlobalNode($3)); } + | nrnstmt POINTER nrnptrlist { $1->push_back(new ast::NrnPointerNode($3)); } + | nrnstmt BBCOREPOINTER nrnbbptrlist{ $1->push_back(new ast::NrnBbcorePtrNode($3)); } + | nrnstmt EXTERNAL nrnextlist { $1->push_back(new ast::NrnExternalNode($3)); } + | nrnstmt THREADSAFE opthsafelist { $1->push_back(new ast::NrnThreadSafeNode($3)); } + ; + +nrnuse : USEION NAME READ nrnionrlist valence { $$ = new ast::NrnUseionNode($2, $4, NULL, $5); } + | USEION NAME WRITE nrnionwlist valence { $$ = new ast::NrnUseionNode($2, NULL, $4, $5); } + | USEION NAME READ nrnionrlist WRITE nrnionwlist valence { $$ = new ast::NrnUseionNode($2, $4, $6, $7); } + | USEION error { /*_ER_*/ } + ; + +nrnionrlist : NAME { $$ = new ast::ReadIonVarNodeList(); $$->push_back(new ast::ReadIonVarNode($1)); } + | nrnionrlist ',' NAME { $1->push_back(new ast::ReadIonVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnionwlist : NAME { $$ = new ast::WriteIonVarNodeList(); $$->push_back(new ast::WriteIonVarNode($1)); } + | nrnionwlist ',' NAME { $1->push_back(new ast::WriteIonVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnonspeclist : NAME { $$ = new ast::NonspeCurVarNodeList(); $$->push_back(new ast::NonspeCurVarNode($1)); } + | nrnonspeclist ',' NAME { $1->push_back(new ast::NonspeCurVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrneclist : NAME { $$ = new ast::ElectrodeCurVarNodeList(); $$->push_back(new ast::ElectrodeCurVarNode($1)); } + | nrneclist ',' NAME { $1->push_back(new ast::ElectrodeCurVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnseclist : NAME { $$ = new ast::SectionVarNodeList(); $$->push_back(new ast::SectionVarNode($1)); } + | nrnseclist ',' NAME { $1->push_back(new ast::SectionVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnrangelist : NAME { $$ = new ast::RangeVarNodeList(); $$->push_back(new ast::RangeVarNode($1)); } + | nrnrangelist ',' NAME { $1->push_back(new ast::RangeVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnglobalist : NAME { $$ = new ast::GlobalVarNodeList(); $$->push_back(new ast::GlobalVarNode($1)); } + | nrnglobalist ',' NAME { $1->push_back(new ast::GlobalVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnptrlist : NAME { $$ = new ast::PointerVarNodeList(); $$->push_back(new ast::PointerVarNode($1)); } + | nrnptrlist ',' NAME { $1->push_back(new ast::PointerVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnbbptrlist : NAME { $$ = new ast::BbcorePointerVarNodeList(); $$->push_back(new ast::BbcorePointerVarNode($1)); } + | nrnbbptrlist ',' NAME { $1->push_back(new ast::BbcorePointerVarNode($3)); } + | error { /*_ER_*/ } + ; + +nrnextlist : NAME { $$ = new ast::ExternVarNodeList(); $$->push_back(new ast::ExternVarNode($1)); } + | nrnextlist ',' NAME { $1->push_back(new ast::ExternVarNode($3)); } + | error { /*_ER_*/ } + ; + +opthsafelist : { $$ = NULL; } + | threadsafelist { $$ = $1; } + ; + +threadsafelist : NAME { $$ = new ast::ThreadsafeVarNodeList(); $$->push_back(new ast::ThreadsafeVarNode($1)); } + | threadsafelist ',' NAME { $1->push_back(new ast::ThreadsafeVarNode($3)); } + ; + +valence : { $$ = NULL; } + | VALENCE real { $$ = new ast::ValenceNode($1, $2); } + | VALENCE '-' real { $3->negate(); $$ = new ast::ValenceNode($1, $3); } + ; + +%% + +std::string parse_with_verbatim_parser(std::string* str) { + /* + std::istringstream* is = new std::istringstream(str->c_str()); + VerbatimContext extcontext(is); + Verbatim_parse(&extcontext); + + std::string ss(*(extcontext.result)); + */ + std::string ss(*str); + return ss; +} + +void yyerror(YYLTYPE* locp, NmodlContext* context, const char *s) { + std::printf("\n Error: %s for token %s \n", s, "_for_token_"); + std::exit(1); +} + diff --git a/src/nmodl/parser/nmodl_context.hpp b/src/nmodl/parser/nmodl_context.hpp new file mode 100644 index 0000000000..4fc76a98ef --- /dev/null +++ b/src/nmodl/parser/nmodl_context.hpp @@ -0,0 +1,66 @@ +#ifndef _NMODL_CONTEXT_ +#define _NMODL_CONTEXT_ + +#include +#include +#include "ast/ast.hpp" + +class NmodlContext { + public: + /* root of the ast */ + ast::ProgramNode* astRoot; + + /* main scanner for NMODL */ + void* scanner; + + /* input stream for parsing */ + std::istream* is; + + /* list of all macro defined variables */ + std::map defined_var; + + /* constructor */ + NmodlContext(std::istream* is = &std::cin) { + init_scanner(); + this->is = is; + this->astRoot = NULL; + } + + void add_defined_var(std::string var, int value) { + defined_var[var] = value; + } + + bool is_defined_var(std::string name) { + if (defined_var.find(name) == defined_var.end()) { + return false; + } + + return true; + } + + int get_defined_var_value(std::string name) { + if (is_defined_var(name)) { + return defined_var[name]; + } + + std::cout << "\n ERROR: Trying to get value of undefined value!"; + abort(); + } + + virtual ~NmodlContext() { + destroy_scanner(); + + if (astRoot) { + delete astRoot; + } + } + + protected: + /* defined in lexer.l */ + void init_scanner(); + void destroy_scanner(); +}; + +int Nmodl_parse(NmodlContext*); + +#endif // _NMODL_CONTEXT_ diff --git a/src/nmodl/utils/commonutils.hpp b/src/nmodl/utils/commonutils.hpp new file mode 100755 index 0000000000..b1f1bc39a7 --- /dev/null +++ b/src/nmodl/utils/commonutils.hpp @@ -0,0 +1,9 @@ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +template +bool is_last(Iter iter, const Cont* cont) { + return ((iter != cont->end()) && (next(iter) == cont->end())); +} + +#endif diff --git a/src/nmodl/utils/stringutils.hpp b/src/nmodl/utils/stringutils.hpp new file mode 100644 index 0000000000..477d50a738 --- /dev/null +++ b/src/nmodl/utils/stringutils.hpp @@ -0,0 +1,78 @@ +#ifndef _UTILS_H_ +#define _UTILS_H_ + +#include +#include +#include +#include +#include +#include + +namespace StringUtils { + + /*trim implementations from + * http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring */ + + /* trim from start */ + static inline std::string& ltrim(std::string& s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + return s; + } + + /* trim from end */ + static inline std::string& rtrim(std::string& s) { + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + return s; + } + + /* trim from both ends */ + static inline std::string& trim(std::string& s) { + return ltrim(rtrim(s)); + } + + /* remove leading newline for the string read by grammar */ + static inline std::string& trimnewline(std::string& s) { + s.erase(std::remove(s.begin(), s.end(), '\n'), s.end()); + return s; + } + + /* for printing json, we have to escape double quotes */ + static inline std::string escape_quotes(const std::string& before) { + std::string after; + + for (std::string::size_type i = 0; i < before.length(); ++i) { + switch (before[i]) { + case '"': + case '\\': + after += '\\'; + + default: + after += before[i]; + } + } + + return after; + } + + inline void split(const std::string& s, char delim, std::vector& elems) { + std::stringstream ss(s); + std::string item; + + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } + } + + /* spilt string with given delimiter and returns vector */ + inline std::vector split_string(const std::string& s, char delim) { + std::vector elems; + split(s, delim, elems); + return elems; + } + + inline void remove_character(std::string& str, const char c) { + str.erase(std::remove(str.begin(), str.end(), c), str.end()); + } +} // namespace StringUtils + +#endif diff --git a/src/nmodl/visitors/visitor.hpp b/src/nmodl/visitors/visitor.hpp new file mode 100644 index 0000000000..e46cffe7d0 --- /dev/null +++ b/src/nmodl/visitors/visitor.hpp @@ -0,0 +1,138 @@ +#ifndef __VISITOR_HPP +#define __VISITOR_HPP + +/* define abstract base class for all Visitors */ +class Visitor { + public: + /* declare all virtual visitor functions (must be implemented in visitors) */ + virtual void visitExpressionNode(ExpressionNode* node) = 0; + virtual void visitStatementNode(StatementNode* node) = 0; + virtual void visitIdentifierNode(IdentifierNode* node) = 0; + virtual void visitBlockNode(BlockNode* node) = 0; + virtual void visitNumberNode(NumberNode* node) = 0; + virtual void visitStringNode(StringNode* node) = 0; + virtual void visitIntegerNode(IntegerNode* node) = 0; + virtual void visitFloatNode(FloatNode* node) = 0; + virtual void visitDoubleNode(DoubleNode* node) = 0; + virtual void visitBooleanNode(BooleanNode* node) = 0; + virtual void visitNameNode(NameNode* node) = 0; + virtual void visitPrimeNameNode(PrimeNameNode* node) = 0; + virtual void visitVarNameNode(VarNameNode* node) = 0; + virtual void visitIndexedNameNode(IndexedNameNode* node) = 0; + virtual void visitUnitNode(UnitNode* node) = 0; + virtual void visitUnitStateNode(UnitStateNode* node) = 0; + virtual void visitDoubleUnitNode(DoubleUnitNode* node) = 0; + virtual void visitArgumentNode(ArgumentNode* node) = 0; + virtual void visitLocalVariableNode(LocalVariableNode* node) = 0; + virtual void visitLocalListStatementNode(LocalListStatementNode* node) = 0; + virtual void visitLimitsNode(LimitsNode* node) = 0; + virtual void visitNumberRangeNode(NumberRangeNode* node) = 0; + virtual void visitProgramNode(ProgramNode* node) = 0; + virtual void visitModelNode(ModelNode* node) = 0; + virtual void visitDefineNode(DefineNode* node) = 0; + virtual void visitIncludeNode(IncludeNode* node) = 0; + virtual void visitParamBlockNode(ParamBlockNode* node) = 0; + virtual void visitParamAssignNode(ParamAssignNode* node) = 0; + virtual void visitStepBlockNode(StepBlockNode* node) = 0; + virtual void visitSteppedNode(SteppedNode* node) = 0; + virtual void visitIndependentBlockNode(IndependentBlockNode* node) = 0; + virtual void visitIndependentDefNode(IndependentDefNode* node) = 0; + virtual void visitDependentDefNode(DependentDefNode* node) = 0; + virtual void visitDependentBlockNode(DependentBlockNode* node) = 0; + virtual void visitStateBlockNode(StateBlockNode* node) = 0; + virtual void visitPlotBlockNode(PlotBlockNode* node) = 0; + virtual void visitPlotDeclarationNode(PlotDeclarationNode* node) = 0; + virtual void visitPlotVariableNode(PlotVariableNode* node) = 0; + virtual void visitInitialBlockNode(InitialBlockNode* node) = 0; + virtual void visitConstructorBlockNode(ConstructorBlockNode* node) = 0; + virtual void visitDestructorBlockNode(DestructorBlockNode* node) = 0; + virtual void visitConductanceHintNode(ConductanceHintNode* node) = 0; + virtual void visitExpressionStatementNode(ExpressionStatementNode* node) = 0; + virtual void visitProtectStatementNode(ProtectStatementNode* node) = 0; + virtual void visitStatementBlockNode(StatementBlockNode* node) = 0; + virtual void visitBinaryOperatorNode(BinaryOperatorNode* node) = 0; + virtual void visitUnaryOperatorNode(UnaryOperatorNode* node) = 0; + virtual void visitReactionOperatorNode(ReactionOperatorNode* node) = 0; + virtual void visitBinaryExpressionNode(BinaryExpressionNode* node) = 0; + virtual void visitUnaryExpressionNode(UnaryExpressionNode* node) = 0; + virtual void visitNonLinEuationNode(NonLinEuationNode* node) = 0; + virtual void visitLinEquationNode(LinEquationNode* node) = 0; + virtual void visitFunctionCallNode(FunctionCallNode* node) = 0; + virtual void visitFromStatementNode(FromStatementNode* node) = 0; + virtual void visitForAllStatementNode(ForAllStatementNode* node) = 0; + virtual void visitWhileStatementNode(WhileStatementNode* node) = 0; + virtual void visitIfStatementNode(IfStatementNode* node) = 0; + virtual void visitElseIfStatementNode(ElseIfStatementNode* node) = 0; + virtual void visitElseStatementNode(ElseStatementNode* node) = 0; + virtual void visitDerivativeBlockNode(DerivativeBlockNode* node) = 0; + virtual void visitLinearBlockNode(LinearBlockNode* node) = 0; + virtual void visitNonLinearBlockNode(NonLinearBlockNode* node) = 0; + virtual void visitDiscreteBlockNode(DiscreteBlockNode* node) = 0; + virtual void visitPartialBlockNode(PartialBlockNode* node) = 0; + virtual void visitPartialEquationNode(PartialEquationNode* node) = 0; + virtual void visitFirstLastTypeIndexNode(FirstLastTypeIndexNode* node) = 0; + virtual void visitPartialBoundaryNode(PartialBoundaryNode* node) = 0; + virtual void visitFunctionTableBlockNode(FunctionTableBlockNode* node) = 0; + virtual void visitFunctionBlockNode(FunctionBlockNode* node) = 0; + virtual void visitProcedureBlockNode(ProcedureBlockNode* node) = 0; + virtual void visitNetReceiveBlockNode(NetReceiveBlockNode* node) = 0; + virtual void visitSolveBlockNode(SolveBlockNode* node) = 0; + virtual void visitBreakpointBlockNode(BreakpointBlockNode* node) = 0; + virtual void visitTerminalBlockNode(TerminalBlockNode* node) = 0; + virtual void visitBeforeBlockNode(BeforeBlockNode* node) = 0; + virtual void visitAfterBlockNode(AfterBlockNode* node) = 0; + virtual void visitBABlockTypeNode(BABlockTypeNode* node) = 0; + virtual void visitBABlockNode(BABlockNode* node) = 0; + virtual void visitWatchStatementNode(WatchStatementNode* node) = 0; + virtual void visitWatchNode(WatchNode* node) = 0; + virtual void visitForNetconNode(ForNetconNode* node) = 0; + virtual void visitMutexLockNode(MutexLockNode* node) = 0; + virtual void visitMutexUnlockNode(MutexUnlockNode* node) = 0; + virtual void visitResetNode(ResetNode* node) = 0; + virtual void visitSensNode(SensNode* node) = 0; + virtual void visitConserveNode(ConserveNode* node) = 0; + virtual void visitCompartmentNode(CompartmentNode* node) = 0; + virtual void visitLDifuseNode(LDifuseNode* node) = 0; + virtual void visitKineticBlockNode(KineticBlockNode* node) = 0; + virtual void visitReactionStatementNode(ReactionStatementNode* node) = 0; + virtual void visitReactVarNameNode(ReactVarNameNode* node) = 0; + virtual void visitLagStatementNode(LagStatementNode* node) = 0; + virtual void visitQueueStatementNode(QueueStatementNode* node) = 0; + virtual void visitQueueExpressionTypeNode(QueueExpressionTypeNode* node) = 0; + virtual void visitMatchBlockNode(MatchBlockNode* node) = 0; + virtual void visitMatchNode(MatchNode* node) = 0; + virtual void visitUnitBlockNode(UnitBlockNode* node) = 0; + virtual void visitUnitDefNode(UnitDefNode* node) = 0; + virtual void visitFactordefNode(FactordefNode* node) = 0; + virtual void visitConstantStatementNode(ConstantStatementNode* node) = 0; + virtual void visitConstantBlockNode(ConstantBlockNode* node) = 0; + virtual void visitTableStatementNode(TableStatementNode* node) = 0; + virtual void visitNeuronBlockNode(NeuronBlockNode* node) = 0; + virtual void visitReadIonVarNode(ReadIonVarNode* node) = 0; + virtual void visitWriteIonVarNode(WriteIonVarNode* node) = 0; + virtual void visitNonspeCurVarNode(NonspeCurVarNode* node) = 0; + virtual void visitElectrodeCurVarNode(ElectrodeCurVarNode* node) = 0; + virtual void visitSectionVarNode(SectionVarNode* node) = 0; + virtual void visitRangeVarNode(RangeVarNode* node) = 0; + virtual void visitGlobalVarNode(GlobalVarNode* node) = 0; + virtual void visitPointerVarNode(PointerVarNode* node) = 0; + virtual void visitBbcorePointerVarNode(BbcorePointerVarNode* node) = 0; + virtual void visitExternVarNode(ExternVarNode* node) = 0; + virtual void visitThreadsafeVarNode(ThreadsafeVarNode* node) = 0; + virtual void visitNrnSuffixNode(NrnSuffixNode* node) = 0; + virtual void visitNrnUseionNode(NrnUseionNode* node) = 0; + virtual void visitNrnNonspecificNode(NrnNonspecificNode* node) = 0; + virtual void visitNrnElctrodeCurrentNode(NrnElctrodeCurrentNode* node) = 0; + virtual void visitNrnSectionNode(NrnSectionNode* node) = 0; + virtual void visitNrnRangeNode(NrnRangeNode* node) = 0; + virtual void visitNrnGlobalNode(NrnGlobalNode* node) = 0; + virtual void visitNrnPointerNode(NrnPointerNode* node) = 0; + virtual void visitNrnBbcorePtrNode(NrnBbcorePtrNode* node) = 0; + virtual void visitNrnExternalNode(NrnExternalNode* node) = 0; + virtual void visitNrnThreadSafeNode(NrnThreadSafeNode* node) = 0; + virtual void visitVerbatimNode(VerbatimNode* node) = 0; + virtual void visitCommentNode(CommentNode* node) = 0; + virtual void visitValenceNode(ValenceNode* node) = 0; +}; + +#endif From 997e8ddf690e32e75018c47e89bafeb7a48e4aa5 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sun, 15 Oct 2017 16:57:47 +0200 Subject: [PATCH 004/871] Refactor lexer implementation with C++ interface of the Flex. Move lexer code base gradually to C++11. Note that this changeset is incomplete (from feature branch) and need subsequent parser+ast chanegeset. Change-Id: I7e5f69b645a47e6da32d8efb84cc939fedb052b7 NMODL Repo SHA: BlueBrain/nmodl@a60b90ea6d6eb03800e48831181e5ab98b3f97d7 --- src/nmodl/lexer/init.cpp | 436 ----------------------- src/nmodl/lexer/init.hpp | 45 --- src/nmodl/lexer/lexer_utils.hpp | 14 - src/nmodl/lexer/list.cpp | 49 --- src/nmodl/lexer/list.hpp | 10 - src/nmodl/lexer/main.cpp | 128 +++++++ src/nmodl/lexer/modl.h | 74 ++-- src/nmodl/lexer/modtoken.cpp | 25 ++ src/nmodl/lexer/modtoken.hpp | 160 +++------ src/nmodl/lexer/nmodl.l | 560 ------------------------------ src/nmodl/lexer/nmodl.ll | 511 +++++++++++++++++++++++++++ src/nmodl/lexer/nmodl_lexer.hpp | 87 +++++ src/nmodl/lexer/nmodl_utils.cpp | 322 +++++++++++++++++ src/nmodl/lexer/nmodl_utils.hpp | 30 ++ src/nmodl/lexer/token_mapping.cpp | 338 ++++++++++++++++++ src/nmodl/lexer/token_mapping.hpp | 12 + src/nmodl/lexer/verbatim.l | 135 +++++++ 17 files changed, 1680 insertions(+), 1256 deletions(-) delete mode 100644 src/nmodl/lexer/init.cpp delete mode 100644 src/nmodl/lexer/init.hpp delete mode 100644 src/nmodl/lexer/lexer_utils.hpp delete mode 100644 src/nmodl/lexer/list.cpp delete mode 100755 src/nmodl/lexer/list.hpp create mode 100644 src/nmodl/lexer/main.cpp create mode 100644 src/nmodl/lexer/modtoken.cpp delete mode 100755 src/nmodl/lexer/nmodl.l create mode 100755 src/nmodl/lexer/nmodl.ll create mode 100644 src/nmodl/lexer/nmodl_lexer.hpp create mode 100644 src/nmodl/lexer/nmodl_utils.cpp create mode 100644 src/nmodl/lexer/nmodl_utils.hpp create mode 100644 src/nmodl/lexer/token_mapping.cpp create mode 100644 src/nmodl/lexer/token_mapping.hpp create mode 100755 src/nmodl/lexer/verbatim.l diff --git a/src/nmodl/lexer/init.cpp b/src/nmodl/lexer/init.cpp deleted file mode 100644 index 26f536c6e4..0000000000 --- a/src/nmodl/lexer/init.cpp +++ /dev/null @@ -1,436 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "lexer/modtoken.hpp" -#include "ast/ast.hpp" -#include "parser/nmodl_context.hpp" -#include "parser/nmodl_parser.hpp" -#include "lexer/modl.h" -#include "lexer/init.hpp" - -/* Keywords from NMODL reprsent name and kval pair - * in the original struct [source init.c] - */ -namespace Keywords { - - static std::map create_keywords_map() { - /* @todo: map for pretty-printing keyrowrds in sorted order? */ - std::map km; - - km["VERBATIM"] = VERBATIM; - km["COMMENT"] = COMMENT; - km["TITLE"] = MODEL; - km["CONSTANT"] = CONSTANT; - km["PARAMETER"] = PARAMETER; - km["INDEPENDENT"] = INDEPENDENT; - km["ASSIGNED"] = DEPENDENT; - km["INITIAL"] = INITIAL1; - km["TERMINAL"] = TERMINAL; - km["DERIVATIVE"] = DERIVATIVE; - km["EQUATION"] = EQUATION; - km["BREAKPOINT"] = BREAKPOINT; - km["CONDUCTANCE"] = CONDUCTANCE; - km["SOLVE"] = SOLVE; - km["STATE"] = STATE; - km["STEPPED"] = STEPPED; - km["LINEAR"] = LINEAR; - km["NONLINEAR"] = NONLINEAR; - km["DISCRETE"] = DISCRETE; - km["FUNCTION"] = FUNCTION1; - km["FUNCTION_TABLE"] = FUNCTION_TABLE; - km["PROCEDURE"] = PROCEDURE; - km["PARTIAL"] = PARTIAL; - km["DEL2"] = DEL2; - km["DEL"] = DEL; - km["LOCAL"] = LOCAL; - km["METHOD"] = USING; - km["STEADYSTATE"] = USING; - km["SENS"] = SENS; - km["STEP"] = STEP; - km["WITH"] = WITH; - km["FROM"] = FROM; - km["FORALL"] = FORALL1; - km["TO"] = TO; - km["BY"] = BY; - km["if"] = IF; - km["else"] = ELSE; - km["while"] = WHILE; - km["START"] = START1; - km["DEFINE"] = DEFINE1; - km["KINETIC"] = KINETIC; - km["CONSERVE"] = CONSERVE; - km["PLOT"] = PLOT; - km["VS"] = VS; - km["LAG"] = LAG; - km["RESET"] = RESET; - km["MATCH"] = MATCH; - km["MODEL_LEVEL"] = MODEL_LEVEL; /* inserted by merge */ - km["SWEEP"] = SWEEP; - km["FIRST"] = FIRST; - km["LAST"] = LAST; - km["COMPARTMENT"] = COMPARTMENT; - km["LONGITUDINAL_DIFFUSION"] = LONGDIFUS; - km["PUTQ"] = PUTQ; - km["GETQ"] = GETQ; - km["IFERROR"] = IFERROR; - km["SOLVEFOR"] = SOLVEFOR; - km["UNITS"] = UNITS; - km["UNITSON"] = UNITSON; - km["UNITSOFF"] = UNITSOFF; - km["TABLE"] = TABLE; - km["DEPEND"] = DEPEND; - km["NEURON"] = NEURON; - km["SUFFIX"] = SUFFIX; - km["POINT_PROCESS"] = SUFFIX; - km["ARTIFICIAL_CELL"] = SUFFIX; - km["NONSPECIFIC_CURRENT"] = NONSPECIFIC; - km["ELECTRODE_CURRENT"] = ELECTRODE_CURRENT; - km["SECTION"] = SECTION; - km["RANGE"] = RANGE; - km["USEION"] = USEION; - km["READ"] = READ; - km["WRITE"] = WRITE; - km["VALENCE"] = VALENCE; - km["CHARGE"] = VALENCE; - km["GLOBAL"] = GLOBAL; - km["POINTER"] = POINTER; - km["BBCOREPOINTER"] = BBCOREPOINTER; - km["EXTERNAL"] = EXTERNAL; - km["INCLUDE"] = INCLUDE1; - km["CONSTRUCTOR"] = CONSTRUCTOR; - km["DESTRUCTOR"] = DESTRUCTOR; - km["NET_RECEIVE"] = NETRECEIVE; - km["BEFORE"] = BEFORE; /* before NEURON sets up cy' = f(y;t) */ - km["AFTER"] = AFTER; /* after NEURON solves cy' = f(y; t) */ - km["WATCH"] = WATCH; - km["FOR_NETCONS"] = FOR_NETCONS; - km["THREADSAFE"] = THREADSAFE; - km["PROTECT"] = PROTECT; - km["MUTEXLOCK"] = NRNMUTEXLOCK; - km["MUTEXUNLOCK"] = NRNMUTEXUNLOCK; - - return km; - } - - /* Keywords: name and kval pair */ - static std::map keywords = create_keywords_map(); - - /* keyword exists ? */ - bool exists(std::string key) { - return (keywords.find(key) != keywords.end()); - } - - /* return type of the keyword */ - int type(std::string key) { - if (!exists(key)) { - /* throw exception or abort here */ - std::cout << "Error : " << key << " doesn't exist! " << std::endl; - return -1; - } - - return keywords[key]; - } -} // namespace Keywords - -namespace Methods { - - /* Numerical methods from NMODL reprsent name, all the types - * that will work with this pair and whether it's a variable - * time step method. [source init.c] - */ - - static std::map create_methods_map() { - std::map mm; - - mm["adams"] = MethodInfo(DERF | KINF, 0); - mm["runge"] = MethodInfo(DERF | KINF, 0); - mm["euler"] = MethodInfo(DERF | KINF, 0); - mm["adeuler"] = MethodInfo(DERF | KINF, 1); - mm["heun"] = MethodInfo(DERF | KINF, 0); - mm["adrunge"] = MethodInfo(DERF | KINF, 1); - mm["gear"] = MethodInfo(DERF | KINF, 1); - mm["newton"] = MethodInfo(NLINF, 0); - mm["simplex"] = MethodInfo(NLINF, 0); - mm["simeq"] = MethodInfo(LINF, 0); - mm["seidel"] = MethodInfo(LINF, 0); - mm["_advance"] = MethodInfo(KINF, 0); - mm["sparse"] = MethodInfo(KINF, 0); - mm["derivimplicit"] = MethodInfo(DERF, 0); /* name hard wired in deriv.c */ - mm["cnexp"] = MethodInfo(DERF, 0); /* see solve.c */ - mm["clsoda"] = MethodInfo(DERF | KINF, 1); /* Tolerance built in to scopgear.c */ - mm["after_cvode"] = MethodInfo(0, 0); - mm["cvode_t"] = MethodInfo(0, 0); - mm["cvode_t_v"] = MethodInfo(0, 0); - - return mm; - } - - /* Methods: name, subtype and varstep touple */ - static std::map methods = create_methods_map(); - - /* Method exists ? */ - bool exists(std::string key) { - return (methods.find(key) != methods.end()); - } - - /* return type of the method */ - int type(std::string key) { - if (!exists(key)) { - /* throw exception or abort here */ - std::cout << "Error : " << key << " doesn't exist! " << std::endl; - return -1; - } - - return METHOD; - } - - std::vector get_methods() { - std::vector v; - - for (std::map::iterator it = methods.begin(); it != methods.end(); ++it) { - v.push_back(it->first); - } - - return v; - } -} // namespace Methods - -namespace ExternalDefinitions { - - /* In the original implementation, different vectors were created - * for extdef, extdef2, extdef3, extdef4 etc. Instead of that we - * are changing those vectors with map. This will - * help us to search in single map and find it's type. The types - * are defined as follows: - * - * TYPE_EXTDEF_DOUBLE: external names that can be used as doubles - * without giving an error message - * - * TYPE_EXTDEF_2 : external function names that can be used with - * array and function name arguments - * - * TYPE_EXTDEF_3 : function names that get two reset arguments added - * - * TYPE_EXTDEF_4 : functions that need a first arg of NrnThread* - * - * TYPE_EXTDEF_5 : the extdef names that are not threadsafe - * - * These name types were used so that it's easy to it to old implementation. - * - */ - - /* external names that can be used as doubles without giving an error message */ - static std::map create_extdef_map() { - std::map extdef; - - extdef["first_time"] = TYPE_EXTDEF_DOUBLE; - extdef["error"] = TYPE_EXTDEF_DOUBLE; - extdef["f_flux"] = TYPE_EXTDEF_DOUBLE; - extdef["b_flux"] = TYPE_EXTDEF_DOUBLE; - extdef["fabs"] = TYPE_EXTDEF_DOUBLE; - extdef["sqrt"] = TYPE_EXTDEF_DOUBLE; - extdef["sin"] = TYPE_EXTDEF_DOUBLE; - extdef["cos"] = TYPE_EXTDEF_DOUBLE; - extdef["tan"] = TYPE_EXTDEF_DOUBLE; - extdef["acos"] = TYPE_EXTDEF_DOUBLE; - extdef["asin"] = TYPE_EXTDEF_DOUBLE; - extdef["atan"] = TYPE_EXTDEF_DOUBLE; - extdef["atan2"] = TYPE_EXTDEF_DOUBLE; - extdef["sinh"] = TYPE_EXTDEF_DOUBLE; - extdef["cosh"] = TYPE_EXTDEF_DOUBLE; - extdef["tanh"] = TYPE_EXTDEF_DOUBLE; - extdef["floor"] = TYPE_EXTDEF_DOUBLE; - extdef["ceil"] = TYPE_EXTDEF_DOUBLE; - extdef["fmod"] = TYPE_EXTDEF_DOUBLE; - extdef["log10"] = TYPE_EXTDEF_DOUBLE; - extdef["log"] = TYPE_EXTDEF_DOUBLE; - extdef["pow"] = TYPE_EXTDEF_DOUBLE; - extdef["printf"] = TYPE_EXTDEF_DOUBLE; - extdef["prterr"] = TYPE_EXTDEF_DOUBLE; - extdef["exp"] = TYPE_EXTDEF_DOUBLE; - extdef["threshold"] = TYPE_EXTDEF_DOUBLE; - extdef["force"] = TYPE_EXTDEF_DOUBLE; - extdef["deflate"] = TYPE_EXTDEF_DOUBLE; - extdef["expfit"] = TYPE_EXTDEF_DOUBLE; - extdef["derivs"] = TYPE_EXTDEF_DOUBLE; - extdef["spline"] = TYPE_EXTDEF_DOUBLE; - extdef["hyperbol"] = TYPE_EXTDEF_DOUBLE; - extdef["revhyperbol"] = TYPE_EXTDEF_DOUBLE; - extdef["sigmoid"] = TYPE_EXTDEF_DOUBLE; - extdef["revsigmoid"] = TYPE_EXTDEF_DOUBLE; - extdef["harmonic"] = TYPE_EXTDEF_DOUBLE; - extdef["squarewave"] = TYPE_EXTDEF_DOUBLE; - extdef["sawtooth"] = TYPE_EXTDEF_DOUBLE; - extdef["revsawtooth"] = TYPE_EXTDEF_DOUBLE; - extdef["ramp"] = TYPE_EXTDEF_DOUBLE; - extdef["pulse"] = TYPE_EXTDEF_DOUBLE; - extdef["perpulse"] = TYPE_EXTDEF_DOUBLE; - extdef["step"] = TYPE_EXTDEF_DOUBLE; - extdef["perstep"] = TYPE_EXTDEF_DOUBLE; - extdef["erf"] = TYPE_EXTDEF_DOUBLE; - extdef["exprand"] = TYPE_EXTDEF_DOUBLE; - extdef["factorial"] = TYPE_EXTDEF_DOUBLE; - extdef["gauss"] = TYPE_EXTDEF_DOUBLE; - extdef["normrand"] = TYPE_EXTDEF_DOUBLE; - extdef["poisrand"] = TYPE_EXTDEF_DOUBLE; - extdef["poisson"] = TYPE_EXTDEF_DOUBLE; - extdef["setseed"] = TYPE_EXTDEF_DOUBLE; - extdef["scop_random"] = TYPE_EXTDEF_DOUBLE; - extdef["boundary"] = TYPE_EXTDEF_DOUBLE; - extdef["romberg"] = TYPE_EXTDEF_DOUBLE; - extdef["legendre"] = TYPE_EXTDEF_DOUBLE; - extdef["invert"] = TYPE_EXTDEF_DOUBLE; - extdef["stepforce"] = TYPE_EXTDEF_DOUBLE; - extdef["schedule"] = TYPE_EXTDEF_DOUBLE; - extdef["set_seed"] = TYPE_EXTDEF_DOUBLE; - extdef["nrn_pointing"] = TYPE_EXTDEF_DOUBLE; - extdef["state_discontinuity"] = TYPE_EXTDEF_DOUBLE; - extdef["net_send"] = TYPE_EXTDEF_DOUBLE; - extdef["net_move"] = TYPE_EXTDEF_DOUBLE; - extdef["net_event"] = TYPE_EXTDEF_DOUBLE; - extdef["nrn_random_play"] = TYPE_EXTDEF_DOUBLE; - extdef["at_time"] = TYPE_EXTDEF_DOUBLE; - extdef["nrn_ghk"] = TYPE_EXTDEF_DOUBLE; - - /* external function names that can be used with array and - * function name arguments - */ - extdef["romberg"] = TYPE_EXTDEF_2; - extdef["legendre"] = TYPE_EXTDEF_2; - extdef["deflate"] = TYPE_EXTDEF_2; - - /* function names that get two reset arguments added */ - extdef["threshold"] = TYPE_EXTDEF_3; - extdef["squarewave"] = TYPE_EXTDEF_3; - extdef["sawtooth"] = TYPE_EXTDEF_3; - extdef["revsawtooth"] = TYPE_EXTDEF_3; - extdef["ramp"] = TYPE_EXTDEF_3; - extdef["pulse"] = TYPE_EXTDEF_3; - extdef["perpulse"] = TYPE_EXTDEF_3; - extdef["step"] = TYPE_EXTDEF_3; - extdef["perstep"] = TYPE_EXTDEF_3; - extdef["stepforce"] = TYPE_EXTDEF_3; - extdef["schedule"] = TYPE_EXTDEF_3; - - /* functions that need a first arg of NrnThread* */ - extdef["at_time"] = TYPE_EXTDEF_4; - - /* the extdef names that are not threadsafe */ - extdef["force"] = TYPE_EXTDEF_5; - extdef["deflate"] = TYPE_EXTDEF_5; - extdef["expfit"] = TYPE_EXTDEF_5; - extdef["derivs"] = TYPE_EXTDEF_5; - extdef["spline"] = TYPE_EXTDEF_5; - extdef["exprand"] = TYPE_EXTDEF_5; - extdef["gauss"] = TYPE_EXTDEF_5; - extdef["normrand"] = TYPE_EXTDEF_5; - extdef["poisrand"] = TYPE_EXTDEF_5; - extdef["poisson"] = TYPE_EXTDEF_5; - extdef["setseed"] = TYPE_EXTDEF_5; - extdef["scop_random"] = TYPE_EXTDEF_5; - extdef["boundary"] = TYPE_EXTDEF_5; - extdef["romberg"] = TYPE_EXTDEF_5; - extdef["invert"] = TYPE_EXTDEF_5; - extdef["stepforce"] = TYPE_EXTDEF_5; - extdef["schedule"] = TYPE_EXTDEF_5; - extdef["set_seed"] = TYPE_EXTDEF_5; - extdef["nrn_random_play"] = TYPE_EXTDEF_5; - - return extdef; - } - - /* external names, function names with various attributes */ - static std::map extdef = create_extdef_map(); - - /* External definition exists ? */ - bool exists(std::string key) { - return (extdef.find(key) != extdef.end()); - } - - /* return type of the external definition */ - int type(std::string key) { - if (!exists(key)) { - /* throw exception or abort here */ - std::cout << "Error : " << key << " doesn't exist! " << std::endl; - return -1; - } - - return extdef[key]; - } - - std::vector get_extern_definitions() { - std::vector v; - - for (std::map::iterator it = extdef.begin(); it != extdef.end(); ++it) { - v.push_back(it->first); - } - - return v; - } -} // namespace ExternalDefinitions - -/* after constructing symbol table noticed that the variables - * from neuron are not yet listed so that error checker passes - * could use those. Note that these are not used by lexer at the - * moment and hence adding as vector of strings. - */ -namespace NeuronVariables { - - /* neuron variables used in neuron */ - static std::set create_neuron_var_vec() { - std::set var; - var.insert("t"); - var.insert("dt"); - var.insert("celsius"); - var.insert("v"); - var.insert("diam"); - var.insert("area"); - return var; - } - - /* neuron variables */ - static std::set variables = create_neuron_var_vec(); - - /* check if neuron variable */ - bool exists(std::string name) { - return (variables.find(name) != variables.end()); - } - - std::vector get_neuron_vars() { - std::vector v(variables.begin(), variables.end()); - return v; - } -} // namespace NeuronVariables - -namespace Lexer { - std::vector get_external_symbols() { - std::vector v, tmp; - - tmp = NeuronVariables::get_neuron_vars(); - v.insert(v.end(), tmp.begin(), tmp.end()); - - tmp = Methods::get_methods(); - v.insert(v.end(), tmp.begin(), tmp.end()); - - tmp = ExternalDefinitions::get_extern_definitions(); - v.insert(v.end(), tmp.begin(), tmp.end()); - - return v; - } - - static std::vector extern_symbols = get_external_symbols(); - - /* check if external symbol */ - bool is_extern_variable(std::string name) { - bool exist = false; - - if (std::find(extern_symbols.begin(), extern_symbols.end(), name) != extern_symbols.end()) { - exist = true; - } - - return exist; - } -} // namespace Lexer diff --git a/src/nmodl/lexer/init.hpp b/src/nmodl/lexer/init.hpp deleted file mode 100644 index 158ceb1d4b..0000000000 --- a/src/nmodl/lexer/init.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _INIT_H_ -#define _INIT_H_ - -/* Numerical methods from NMODL reprsent name, all the types - * that will work with this pair and whether it's a variable - * time step method. [source init.c] - */ -struct MethodInfo { - long subtype; /* all the types that will work with this */ - short varstep; /* whether it's a variable step method */ - - MethodInfo() : subtype(0), varstep(0) { - } - MethodInfo(long s, short v) : subtype(s), varstep(v) { - } -}; - -namespace Keywords { - bool exists(std::string key); - int type(std::string key); -} // namespace Keywords - -namespace Methods { - bool exists(std::string key); - int type(std::string key); -} // namespace Methods - -namespace ExternalDefinitions { - bool exists(std::string key); - int type(std::string key); -} // namespace ExternalDefinitions - -namespace NeuronVariables { - bool exists(std::string key); -} - -/* naming convention is based on original nocmodl implementation */ -enum { TYPE_EXTDEF_DOUBLE, TYPE_EXTDEF_2, TYPE_EXTDEF_3, TYPE_EXTDEF_4, TYPE_EXTDEF_5 }; - -namespace Lexer { - bool is_extern_variable(std::string name); - std::vector get_external_symbols(); -} // namespace Lexer - -#endif diff --git a/src/nmodl/lexer/lexer_utils.hpp b/src/nmodl/lexer/lexer_utils.hpp deleted file mode 100644 index 17dce19ab0..0000000000 --- a/src/nmodl/lexer/lexer_utils.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef _LEXER_UTILS_HPP_ -#define _LEXER_UTILS_HPP_ - -#include -#include "ast/ast.hpp" - -/* lexer provide some utility functions to process input */ -namespace LexerUtils { - ast::StringNode* inputtoparpar(void* scanner); - std::string inputline(void* scanner); - std::string input_until_token(std::string, void* scanner); -} - -#endif \ No newline at end of file diff --git a/src/nmodl/lexer/list.cpp b/src/nmodl/lexer/list.cpp deleted file mode 100644 index 794f9354c7..0000000000 --- a/src/nmodl/lexer/list.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include -#include -#include -#include -#include -#include "lexer/modtoken.hpp" -#include "ast/ast.hpp" -#include "parser/nmodl_context.hpp" -#include "parser/nmodl_parser.hpp" -#include "lexer/modl.h" -#include "lexer/init.hpp" - -/* Lookup the token from lexer and check if it is keyword, method or external - * function definition. Currently just return type, no symbol creation - */ -ModToken* putintoken(std::string key, int type, Position pos) { - int toktype; - - switch (type) { - case STRING: - case REAL: - case INTEGER: - toktype = type; - break; - - default: - - /* check if name is KEYWORD in NMODL */ - if (Keywords::exists(key)) { - toktype = Keywords::type(key); - break; - } - - /* check if name is METHOD name in NMODL */ - if (Methods::exists(key)) { - toktype = Methods::type(key); - break; - } - - /* current token is neither keyword nor method name, treating as NAME */ - toktype = NAME; - } - - ModToken* tok = new ModToken(key, toktype, pos); - - // std::cout << std::endl << "\t" << tok; - - return tok; -} diff --git a/src/nmodl/lexer/list.hpp b/src/nmodl/lexer/list.hpp deleted file mode 100755 index a6273b0f79..0000000000 --- a/src/nmodl/lexer/list.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef _LIST_H_ -#define _LIST_H_ - -#include "modtoken.hpp" - -/* Methods to process tokens [source list.c] - */ -ModToken* putintoken(std::string, int, Position); - -#endif diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main.cpp new file mode 100644 index 0000000000..d70109ebbe --- /dev/null +++ b/src/nmodl/lexer/main.cpp @@ -0,0 +1,128 @@ +#include +#include + +#include "ast/ast.hpp" +#include "lexer/nmodl_lexer.hpp" +#include "parser/nmodl_driver.hpp" +#include "tclap/CmdLine.h" + +/** + * Standlone lexer program for NMODL. This demonstrate basic + * usage of scanner and driver class. We parse user provided + * nmodl file and print individual token with it's value and + * location. + */ + +int main(int argc, char* argv[]) { + try { + TCLAP::CmdLine cmd("NMODL Lexer: Standalone lexer program for NMODL"); + TCLAP::ValueArg filearg( + "", "file", "NMODL input file path", false, "../test/input/example1.mod", "string"); + + cmd.add(filearg); + cmd.parse(argc, argv); + + std::string filename = filearg.getValue(); + std::ifstream file(filename); + + if (!file) { + throw std::runtime_error("Could not open file " + filename); + } + + std::cout << "\n NMODL Lexer : Processing file : " << filename << std::endl; + + std::istream& in(file); + + /// lexer instace use driver object for error reporting + nmodl::Driver driver; + + /// lexer instace with stream to read-in tokens + nmodl::Lexer scanner(driver, &in); + + using Token = nmodl::Parser::token; + using TokenType = nmodl::Parser::token_type; + using SymbolType = nmodl::Parser::symbol_type; + + /// parse nmodl file untile EOF, print each token + while (1) { + SymbolType sym = scanner.next_token(); + TokenType token = sym.token(); + + /// end of file + if (token == Token::END) { + break; + } + + /** Lexer returns different ast types base on token type. We + * retrieve token object from each instance and print it. + * Note that value is of ast type i.e. ast::name_ptr etc. */ + switch (token) { + /// token with name ast class + case Token::NAME: + case Token::METHOD: + case Token::SUFFIX: + case Token::VALENCE: + case Token::DEL: + case Token::DEL2: { + auto value = sym.value.as(); + std::cout << *(value->getToken()) << std::endl; + delete value; + break; + } + + /// token with prime ast class + case Token::PRIME: { + auto value = sym.value.as(); + std::cout << *(value->getToken()) << std::endl; + delete value; + break; + } + + /// token with integer ast class + case Token::INTEGER: { + auto value = sym.value.as(); + std::cout << *(value->getToken()) << std::endl; + delete value; + break; + } + + /// token with double/float ast class + case Token::REAL: { + auto value = sym.value.as(); + std::cout << *(value->getToken()) << std::endl; + delete value; + break; + } + + /// token with string ast class + case Token::STRING: { + auto value = sym.value.as(); + std::cout << *(value->getToken()) << std::endl; + delete value; + break; + } + + /// token with string data type + case Token::VERBATIM: + case Token::COMMENT: + case Token::LINE_PART: { + auto str = sym.value.as(); + std::cout << str << std::endl; + break; + } + + /// all remaining tokens has ModToken* as a vaue + default: { + auto token = sym.value.as(); + std::cout << token << std::endl; + } + } + } + + } catch (TCLAP::ArgException& e) { + std::cout << std::endl << "Argument Error: " << e.error() << " for arg " << e.argId(); + return 1; + } + + return 0; +} diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index 6c6c44554a..be38137db5 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -1,37 +1,59 @@ -#ifndef _MODL_H_ -#define _MODL_H_ +#pragma once -/* subtypes */ +/** + * Original implementation of nocmodl use various flags to help + * code generation. These flags are implemented as bit masks in + * the token whic are later checked during code printing. We are + * using ast and hence don't need all bit masks. These are defined + * in modl.h file of original nocmodl implementation. + * + * \todo Remove these bit masks as we incorporate type information + * into corresponding ast types. */ + +/// subtypes of the token +#define ARRAY 040 +#define DEP 010 +#define FUNCT 0100 +#define INDEP 04 #define KEYWORD 01 +#define NEGATIVE 0400 #define PARM 02 -#define INDEP 04 -#define DEP 010 /* also in usage field */ -#define STAT 020 -#define ARRAY 040 -#define FUNCT 0100 /* also in usage field */ #define PROCED 0200 -#define NEGATIVE 0400 -#define SEMI 01 /* ";" */ +#define SEMI 01 +#define STAT 020 + +#define DISCF 010000 + +/// usage field (variable occurs in input file) +#define EXPLICIT_DECL 01 + +/// external definition +#define EXTDEF 0100000 + +//// functions that can take array or function name as an arguments +#define EXTDEF2 01000000L + +/// function that takes two extra reset arguments at beginning +#define EXTDEF3 04000000L + +/// function that takes an extra NrnThread* arg at beginning +#define EXTDEF4 020000000L -/* @todo: we define these are now as tokens, probably we dont need it (?) */ -//#define BEGINBLK 02 /* "{" */ -//#define ENDBLK 04 /* "}" */ +//// not threadsafe +#define EXTDEF5 040000000L +/// must be cast to double in expression +#define INTGER 010000000L + +/// method subtypes #define DERF 01000 #define KINF 02000 +#define LINF 0200000 #define NLINF 04000 -#define DISCF 010000 -#define STEP1 020000 + +//// constants that do not appear in .var file +#define nmodlCONST 02000000L + #define PARF 040000 -#define EXTDEF 0100000 -#define LINF 0200000 +#define STEP1 020000 #define UNITDEF 0400000L -#define EXTDEF2 01000000L /* functions that can take array or function name arguments */ -#define nmodlCONST 02000000L /* constants that do not appear in .var file */ -#define EXTDEF3 04000000L /* get two extra reset arguments at beginning */ -#define INTGER 010000000L /* must be cast to double in expr */ -#define EXTDEF4 020000000L /* get extra NrnThread* arg at beginning */ -#define EXTDEF5 040000000L /* not threadsafe from the extdef list */ -#define EXPLICIT_DECL 01 /* usage field, variable occurs in input file */ - -#endif diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp new file mode 100644 index 0000000000..4cf56ab382 --- /dev/null +++ b/src/nmodl/lexer/modtoken.cpp @@ -0,0 +1,25 @@ +#include "lexer/modtoken.hpp" + +/** Return position of the token as string. This is used used by other + * passes like scope checker to print the location in mod files. + * External token's position : [EXTERNAL] + * Token with unknown position : [UNKNOWN] + * Token from lexer with known position : [line.start_column-end_column] */ +std::string ModToken::position() const { + std::stringstream ss; + if (external) { + ss << "[EXTERNAL]"; + } else if (start_line() == 0) { + ss << "[UNKNOWN]"; + } else { + ss << "[" << pos << "]"; + } + return ss.str(); +} + +/** Print token as : token at [line.start_column-end_column] type token + * Example: v at [118.9-14] type 376 */ +std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { + stream << std::setw(15) << mt.name << " at " << mt.position(); + return stream << " type " << mt.token; +} \ No newline at end of file diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 4aa30a85d4..0bc6a2c34c 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -1,145 +1,73 @@ -#ifndef _MODL_TOKEN_H_ -#define _MODL_TOKEN_H_ +#ifndef _LEXER_MODTOKEN_HPP_ +#define _LEXER_MODTOKEN_HPP_ -#include +#include #include #include -#include - -/* store the location of every token in the file */ -class Position { - public: - /* this is corresponding to the YYLTYPE structure - * of the Bison. It stores the location info of - * every token. This information is filled by Lex. - */ - int fline; // first_line - int lline; // last_line - int fcol; // first_column - int lcol; // last_column - - /* some symbols are externals. while showing them - * in symbol table, instead of showing unknown - * locations, we can show as an external - */ - - bool external; - - /* @todo: not sure how/when lcol != fcol for lexer (make sense for parser */ - - Position() : fline(-1), lline(-1), fcol(-1), lcol(-1), external(false) { - } - - Position(int fl, int ll, int fc, int lc) : fline(fl), lline(ll), fcol(fc), lcol(lc), external(false) { - } - - Position(bool ext) : external(ext) { - } - - int get_first_line() const { - return fline; - } - - int get_first_column() const { - return fcol; - } - - /* for token on same line, print it as line.firstcol.lastcol => [12.3-5] - * for token spanning different line, print as first_line-fitst_col.last_line-first_col => - * [12-3.13.5] - */ - friend std::ostream& operator<<(std::ostream& stream, const Position& p) { - if (p.external) { - return stream << "[EXTERNAL]"; - } else if (p.fline < 0 || p.lline < 0) { - return stream << "[UNKNOWN]"; - } else if (p.fline == p.lline) { - return stream << "[" << p.fline << "." << p.fcol << "-" << p.lcol << "]"; - } else { - return stream << "[" << p.fline << "-" << p.fcol << "." << p.lline << "-" << p.lcol << "]"; - } - } +#include - /** todo : duplicate logic for ostream overload as well */ - std::string string() { - std::stringstream ss; - - if (external) { - ss << "[EXTERNAL]"; - } else if (fline < 0 || lline < 0) { - ss << "[UNKNOWN]"; - } else if (fline == lline) { - ss << "[" << fline << "." << fcol << "-" << lcol << "]"; - } else { - ss << "[" << fline << "-" << fcol << "." << lline << "-" << lcol << "]"; - } - - return ss.str(); - } -}; +#include "parser/location.hh" -/* every token returned by lexer, mainly value yytext, - * position information and type of the token. +/** + * \class ModToken + * \brief Represent token return by scanner * - * Do we need Position in token? Lexer can be built as a - * standalone component and can be run independently with - * it's test. + * Every token returned by lexer is represented by ModToken. + * Some tokens are also externally defined names like dt, t. + * These names are defined in NEURON and hence we set external + * property to true. Also, location class represent the position + * of the token in nmodl file. By default location is initialized + * to line,column as 1,1. Some tokens are explicitly added during + * compiler passes. Hence we set the position to 0,0 so that we + * can distinguish them from other tokens produced by lexer. + * + * \todo location object is copyable except if we specify the + * stream name. It would be good to trackfilenames when we go + * for multi-channel optimization and code generation. */ + class ModToken { private: - std::string value; - int ktype; - Position _position; + /// name of the token + std::string name; - public: - ModToken() : ktype(-1) { - } + /// token value returned by lexer + int token = -1; - /** todo: change this */ - ModToken(bool ext) : _position(ext) { - } + /// if token is externally defined symbol + bool external = false; - ModToken(std::string v, int t, Position p) : value(v), ktype(t), _position(p) { - } + /// position of the token in mod file + nmodl::location pos; - ModToken(const ModToken& m) { - value = m.text(); - ktype = m.type(); - _position = m.position(); - } - - /* may not be useful */ - ModToken(std::string v, int t, int fline, int lline, int fcol, int lcol) - : value(v), ktype(t), _position(fline, lline, fcol, lcol) { - } + public: + ModToken() : pos(nullptr, 0){}; + explicit ModToken(bool ext) : pos(nullptr, 0), external(ext) {} + ModToken(std::string str, int tok, nmodl::location& loc) : name(str), token(tok), pos(loc) {} ModToken* clone() const { return new ModToken(*this); } - /* print token as "Token Name at Position with type Type" - * e.g. Token FUNCTION at [12.3-5] with type 258 - */ - friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { - return stream << std::setw(15) << mt.value << " at " << mt._position << " type " << mt.ktype; - } - std::string text() const { - return value; + return name; } - int itext() const { - // atoi(value.str()); - return 1; + int type() const { + return token; } - int type() const { - return ktype; + int start_line() const { + return pos.begin.line; } - Position position() const { - return _position; + int start_column() const { + return pos.begin.column; } + + std::string position() const; + + friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt); }; #endif diff --git a/src/nmodl/lexer/nmodl.l b/src/nmodl/lexer/nmodl.l deleted file mode 100755 index 9b58005f70..0000000000 --- a/src/nmodl/lexer/nmodl.l +++ /dev/null @@ -1,560 +0,0 @@ -%{ - #include - #include - #include - #include - #include - #include "ast/ast.hpp" - #include "lexer/list.hpp" - #include "lexer/modtoken.hpp" - #include "parser/nmodl_context.hpp" - #include "parser/nmodl_parser.hpp" - #include "utils/stringutils.hpp" - - /* we create position in YY_USER_ACTION */ - Position pos; - - /* for '~', we return different tokens based on the lexical - * context, store the current context. Note that this is - * returned from parser in original implementation, revisit - * this and check if this is sufficient __todo__ */ - static int lexcontext; - - /* where we update the location */ - void update_location(yyscan_t scanner); - - /* YY_USER_ACTION is "called" before each of token actions */ - #define YY_USER_ACTION update_location(yyscanner); - - /* the scanner state include a field called yyextra that can - * be used for user-defined state. The type of this field is - * specified by YY_EXTRA_TYPE - */ - #define YY_EXTRA_TYPE NmodlContext* - - /* we will parse strings as well as files and hence need to - * redefine YY_INPUT. This will use the istream from the parser - * context to read the next character. Note that the parser uses - * uses std::cin by default. We will set to istringstream as - * when constructing NmodlContext object. - */ - #define YY_INPUT(buf,result,max_size) \ - { \ - char c; \ - (*yyextra->is) >> std::noskipws >> c; \ - if(yyextra->is->eof()) \ - result = YY_NULL; \ - else { \ - buf[0] = c; \ - result = 1; \ - } \ - } - -%} - -D [0-9] -E [Ee][-+]?{D}+ - -/* need to provide this option to flex otherwise complain: - * "error: 'yymore_used_but_not_detected' was not declared in - * this scope - */ -%option yymore - -/* lexer header file */ -%option header-file="nmodl_lexer.hpp" - -/* lexer implementation file */ -%option outfile="nmodl_lexer.cpp" - -/* lexer prefix */ -%option prefix="Nmodl_" - -/* need to be reentrant */ -%option reentrant - -/* no need for includes */ -%option noyywrap - -/* need to unput in buffer for custom routines */ -%option unput - -/* need to input in buffer for custom routines */ -%option input - -/* not an interactive lexer, takes a file */ -%option batch - -/* debug, disable for production */ -%option debug - -/* bison compatible lexer */ -%option bison-bridge - -/* bison location information */ -%option bison-locations - -/* keep line information */ -%option yylineno - -/* mode for verbatim or comment */ -%x COPY_MODE - - -/* mode for DEFINE variable and it's value (i.e. macro definition) */ -%x DEFINE_MODE -%x DEFINE_INT_MODE - -%% - - -[a-zA-Z][a-zA-Z0-9_]*'+ { - - /*PRIME possibly high order*/ - int yleng = yyget_leng(yyscanner); - char nextch = yyinput(yyscan_t(yyscanner)); - std::string name(yytext); - - if(nextch == '0') { - name += "0"; - ModToken *tok = putintoken(yytext, NAME, pos); - yylval->name_ptr = new ast::NameNode( new ast::StringNode(name) ); - yylval->name_ptr->setToken(tok); - return NAME; - } else { - unput(nextch); - - ModToken *tok = putintoken(name, PRIME, pos); - size_t order = std::count(name.begin(), name.end(), '\''); - name.erase(std::remove(name.begin(), name.end(), '\''), name.end()); - yylval->primename_ptr = new ast::PrimeNameNode(new ast::StringNode(name.c_str()), new ast::IntegerNode(order, NULL)); - yylval->primename_ptr->setToken(tok); - return PRIME; - } - - } - -WHILE | -IF | -ELSE { - for(char *cp = yytext; *cp; cp++) - *cp = tolower(*cp); - ModToken *tok = putintoken(yytext, NAME, pos); - yylval->qp = tok; - return tok->type(); - } - -"VERBATIM" { BEGIN(COPY_MODE); } - -"COMMENT" { BEGIN(COPY_MODE); } - -"DEFINE" { - ModToken *tok = putintoken(yytext, NAME, pos); - yylval->qp = tok; - BEGIN(DEFINE_MODE); - return tok->type(); - } - -[a-zA-Z][a-zA-Z0-9_]* { - ModToken *tok = putintoken(yytext, INTEGER, pos); - yylval->name_ptr = new ast::NameNode( new ast::StringNode(yytext) ); - yylval->name_ptr->setToken(tok); - BEGIN(DEFINE_INT_MODE); - return NAME; - } - -{D}+ { - ModToken *tok = putintoken(yytext, INTEGER, pos); - yylval->integer_ptr = new ast::IntegerNode(atoi(yytext), NULL); - yylval->integer_ptr->setToken(tok); - BEGIN(INITIAL); - return INTEGER; - } - -[a-zA-Z][a-zA-Z0-9_]* { - ModToken *tok = putintoken(yytext, NAME, pos); - yylval->qp = tok; - int type = tok->type(); - - /* if the NAME is variable from macro definition then return INTEGER - * Make sure yyextra is not NULL because we also use lexer-only for - * testing purpose - */ - if(type == NAME && yyextra->is_defined_var(yytext)) { - int value = yyextra->get_defined_var_value(yytext); - ast::NameNode* macro_name = new ast::NameNode(new ast::StringNode(yytext)); - macro_name->setToken(tok->clone()); - yylval->integer_ptr = new ast::IntegerNode(value, macro_name); - yylval->integer_ptr->setToken(tok); - return INTEGER; - } - - if(type == NAME || type == METHOD || type == SUFFIX || type == VALENCE || type == DEL || type == DEL2) { - yylval->name_ptr = new ast::NameNode( new ast::StringNode(yytext) ); - yylval->name_ptr->setToken(tok); - } - - if( type == NONLINEAR || type == LINEAR || type == PARTIAL || type == KINETIC) { - lexcontext = type; - } - - return type; - } - - -{D}+ { - ModToken *tok = putintoken(yytext, INTEGER, pos); - yylval->integer_ptr = new ast::IntegerNode(atoi(yytext), NULL); - yylval->integer_ptr->setToken(tok); - return INTEGER; - } - -{D}+"."{D}*({E})? | -{D}*"."{D}+({E})? | -{D}+{E} { - ModToken *tok = putintoken(yytext, REAL, pos); - yylval->double_ptr = new ast::DoubleNode(atof(yytext)); - yylval->double_ptr->setToken(tok); - return REAL; - } - - -\"[^\"]*\" { - /* can't quote \" */ - ModToken *tok = putintoken(yytext, STRING, pos); - yylval->string_ptr = new ast::StringNode(yytext); - yylval->string_ptr->setToken(tok); - return STRING; - } - -">" { - ModToken *tok = putintoken(yytext, GT, pos); - yylval->qp = tok; - return GT; - } - -">=" { - ModToken *tok = putintoken(yytext, GE, pos); - yylval->qp = tok; - return GE; - } - -"<" { - ModToken *tok = putintoken(yytext, LT, pos); - yylval->qp = tok; - return LT; - } - -"<=" { - ModToken *tok = putintoken(yytext, LE, pos); - yylval->qp = tok; - return LE; - } - -"==" { - ModToken *tok = putintoken(yytext, EQ, pos); - yylval->qp = tok; - return EQ; - } - -"!=" { - ModToken *tok = putintoken(yytext, NE, pos); - yylval->qp = tok; - return NE; - } - -"!" { - ModToken *tok = putintoken(yytext, NOT, pos); - yylval->qp = tok; - return NOT; - } - -"&&" { - ModToken *tok = putintoken(yytext, AND, pos); - yylval->qp = tok; - return AND; - } - -"||" { - ModToken *tok = putintoken(yytext, OR, pos); - yylval->qp = tok; - return OR; - } - -"<->" { - ModToken *tok = putintoken(yytext, REACT1, pos); - yylval->qp = tok; - return REACT1; - } - -"~+" { - /* syntactic sugar for equation addition */ - ModToken *tok = putintoken(yytext, NONLIN1, pos); - yylval->qp = tok; - return NONLIN1; - } - -"~" { - /* syntactic sugar for equations */ - if (lexcontext == NONLINEAR) return NONLIN1; - if (lexcontext == LINEAR) return LIN1; - if (lexcontext == PARTIAL) return yytext[0]; - if (lexcontext == KINETIC) return REACTION; - std::cout << "\n ERROR: check lexer for ~ \n"; - abort(); - } - -"{" { - ModToken *tok = putintoken(yytext, BEGINBLK, pos); - yylval->qp = tok; - return yytext[0]; - } - -"}" { - ModToken *tok = putintoken(yytext, ENDBLK, pos); - yylval->qp = tok; - return yytext[0]; - } - -"(" { - ModToken *tok = putintoken(yytext, LEFT_PAREN, pos); - yylval->qp = tok; - return yytext[0]; - } - -")" { - ModToken *tok = putintoken(yytext, RIGHT_PAREN, pos); - yylval->qp = tok; - return yytext[0]; - } - -[ \t] { /*ignore spacing characters*/ } -\r\n { yyset_column(1, yyscanner); } -\r { yyset_column(1, yyscanner); } -\n.* { - yyset_column(1, yyscanner); - std::string str = std::string(yytext); - - StringUtils::trim(str); - - /* if non-empty string, then store it for error reporting */ - if(str.length()) { - StringUtils::trimnewline(str); - std::cout << std::endl << "LINE "<< yylineno << ": " << str; - } else - std::cout << std::endl << "LINE " << yylineno << ": "; - - /* pass back the entire string except newline character */ - yyless(1); - yyset_column(1, yyscanner); - } - -:.* | -\?.* { - /* Need to change parser grammar to accept one line comment - std::string str = "COMMENT " + std::string(yytext) + " ENDCOMMENT"; - yylval->str_ptr = new std::string(str); - return COMMENT; - */ - } - -. { - /* @todo: need to put this token */ - ModToken *tok = putintoken(yytext, 0, pos); - yylval->qp = tok; - return yytext[0]; - } - - /* for varbatim mode i.e. comment and verbatim */ - -[ \t] { yymore(); } -\n { yymore(); yyset_column(1, yyscanner); } -\r\n { yymore(); yyset_column(1, yyscanner); } -\r { yymore(); yyset_column(1, yyscanner); } - -"ENDVERBATIM" { - std::string str = "VERBATIM " + std::string(yytext); - yylval->str_ptr = new std::string(str); - BEGIN(INITIAL); - return VERBATIM; - } - -"ENDCOMMENT" { - std::string str = "COMMENT " + std::string(yytext); - yylval->str_ptr = new std::string(str); - BEGIN(INITIAL); - return COMMENT; - } - - -<> { - std::cout << "\n ERROR: Unexpected end of file in COPY_MODE! \n"; - return 0; - } - -. { - yymore(); - } - - /* @todo: adding new rules here, needed? */ - -\"[^\"\n]*$ { - printf("\n Unterminated String, e.g. for printf "); - } - -%% - -/* some of the utility functions can't be defined - * outside lexer file due to internal macros. These - * are utility functions ported from original nocmodl - * implementation. - */ -namespace LexerUtils { - - /* this routine is needed to input the unit - * from the UNIT block. yylex cant be used - * directly. Also, defining this routine outise - * lexer file gives compilation error as yyunput - * macros are defined in the lexer generated file - */ - ast::StringNode * inputtoparpar(void *yyscanner) { - char lastch; - std::string str; - - struct yyguts_t * yyg; - yyg = (struct yyguts_t*)yyscanner; - - int colno = yyget_column(yyscan_t(yyscanner)); - int lineno = yyget_lineno(yyscan_t(yyscanner)); - - while (1) { - lastch = yyinput(yyscan_t(yyscanner)); - - if(lastch == ')') { - unput(')'); - break; - } - else if ( lastch == '\n' || lastch == 0) { - std::cout << " Error: While parsing unit, closing parenthis not found! "; - break; - } - str += lastch; - } - - /* create new position for token */ - pos = Position(lineno, lineno, colno, colno + str.size()); - - ModToken *tok = putintoken(str, UNITS, pos); - ast::StringNode * node = new ast::StringNode(str); - node->setToken(tok); - return node; - } - - /* this routine is needed to input TITLE line. As - * we are ignoring new line in the lexer, we need - * custom routine to input a line. (@todo) - */ - std::string inputline(void *yyscanner) { - char lastch; - std::string str; - - /* note yyinput requires yyg and also yyscanner variable */ - struct yyguts_t * yyg; - yyg = (struct yyguts_t*)yyscanner; - - while (1) { - lastch = yyinput(yyscan_t(yyscanner)); - if ( lastch == '\n' || lastch == 0 || lastch == EOF) { - unput('\n'); - break; - } - str += lastch; - } - return str; - } - - /* this routine is needed to input COMMENT and VERBATIM - * blocks until ENDCOMMENT and ENDVERBATIM respectively - * @todo: this looses \n and \t which is bad for verbatim - * blocks. Need to fix this. - * @TODO: REMOVE THIS, LEFT FOR REFERENCE!! - */ - std::string input_until_token(std::string w, void *yyscanner) { - - std::string str; - char *text; - - /* required for yylex */ - YYSTYPE *yytype = yyget_lval(yyscanner); - YYLTYPE *yltype = yyget_lloc(yyscanner); - - StringUtils::trim(w); - - while(1) { - - /* get a token */ - yylex(yytype, yltype, yyscanner); - text = yyget_text(yyscanner); - - std::string s(text); - StringUtils::trim(s); - - /* break if we got expected token */ - if(strcmp(s.c_str(), w.c_str())==0) - break; - - /* otherwise just append */ - str += text; - } - - struct yyguts_t * yyg; - yyg = (struct yyguts_t*)yyscanner; - BEGIN(INITIAL); - return str; - } - - /* used for unit testing where stream is set to string buffer */ - int get_next_token(std::string name, YYSTYPE *stype, YYLTYPE *ltype) { - - int tok; - yyscan_t scanner; - YY_BUFFER_STATE buf; - NmodlContext context; - - yylex_init_extra(&context, &scanner); - - buf = yy_scan_string(name.c_str(), scanner); - tok = yylex(stype, ltype, scanner); - - yy_delete_buffer(buf, scanner); - yylex_destroy(scanner); - - return tok; - } -} - -/* initialize nmodl lexer context */ -void NmodlContext::init_scanner() { - yylex_init(&scanner); - yyset_extra(this, scanner); -} - -/* delete nmodl lexer context */ -void NmodlContext::destroy_scanner() { - yylex_destroy(scanner); -} - -/* update location information when current token being parsed */ -void update_location(yyscan_t scanner) { - - /* get column, line and length og token */ - int colno = yyget_column(scanner); - int lineno = yyget_lineno(scanner); - int yleng = yyget_leng(scanner); - - /* create new position for token */ - pos = Position(lineno, lineno, colno, colno+yleng-1); - - /* update scanner state */ - yyset_column(colno+yleng, scanner); -} diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll new file mode 100755 index 0000000000..48d37eb3e0 --- /dev/null +++ b/src/nmodl/lexer/nmodl.ll @@ -0,0 +1,511 @@ +%{ + #include + #include "ast/ast.hpp" + #include "lexer/nmodl_lexer.hpp" + #include "lexer/nmodl_utils.hpp" + #include "lexer/token_mapping.hpp" + #include "parser/nmodl_driver.hpp" + #include "utils/stringutils.hpp" + + /** YY_USER_ACTION is called before each of token actions and + * we update columns by length of the token. Node that position + * starts at column 1 and increasing by yyleng gives extra position + * larger than exact columns span of the token. Hence in ModToken + * class we reduce the length by one column. */ + #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } + + /** By default yylex returns int, we use token_type. Unfortunately + * yyterminate by default returns 0, which is not of token_type. */ + #define yyterminate() return nmodl::Parser::make_END(loc); + + /** Disables inclusion of unistd.h, which is not available under Visual + * C++ on Win32. The C++ scanner uses STL streams instead. */ + #define YY_NO_UNISTD_H + +%} + + +/** regex for digits and exponent for float */ +D [0-9] +E [Ee][-+]?{D}+ + +/** we do use yymore feature in copy modes */ +%option yymore + +/** name of the lexer header file */ +%option header-file="nmodl_base_lexer.hpp" + +/** name of the lexer implementation file */ +%option outfile="nmodl_base_lexer.cpp" + +/** change the name of the scanner class (to "NmodlFlexLexer") */ +%option prefix="Nmodl" + +/** enable C++ scanner which is also reentrant */ +%option c++ + +/** no plan for include files for now */ +%option noyywrap + +/** need to unput characters back to buffer for custom routines */ +%option unput + +/** need to put in buffer for custom routines */ +%option input + +/** not an interactive lexer, takes a file instead */ +%option batch + +/** enable debug mode (disable for production) */ +%option debug + +/** instructs flex to generate an 8-bit scanner, i.e., + * one which can recognize 8-bit characters. */ +%option 8bit + +/** show warning messages */ +%option warn + +/* to insure there are no holes in scanner rules */ +%option nodefault + +/* keep line information */ +%option yylineno + +/* mode for verbatim or comment */ +%x COPY_MODE + +/* mode for DEFINE variable name (i.e. macro definition) */ +%x MACRO_NAME_MODE + +/* mode for DEFINE variable value (i.e. macro definition) */ +%x MACRO_VALUE_MODE + +/* mode for TITLE and single line comment */ +%x LINE_MODE + +/* enable use of start condition stacks */ +%option stack + +%% + + +[a-zA-Z][a-zA-Z0-9_]*'+ { + /** Prime could high order and if it is followed by 0 then + * it's name. For this we have to consume extra character + * and then have to decide the token type. If it's actually + * prime then we have to un-put consumed character. */ + auto nextch = yyinput(); + std::string name(yytext); + + if(nextch == '0') { + name += "0"; + return name_symbol(name, loc); + + } else { + unput(nextch); + return prime_symbol(name, loc); + } + } + +WHILE | +IF | +ELSE { + /** Lower or upper case if,else,while keywords are allowded. + * To avoid extra keywords, make token lower case */ + for (char *ch = yytext; *ch; ++ch) + *ch = tolower(*ch); + + return token_symbol(yytext, loc); + } + +"VERBATIM" { + /** start of verbatim block */ + BEGIN(COPY_MODE); + } + +"COMMENT" { + /** start of comment block */ + BEGIN(COPY_MODE); + } + +"DEFINE" { + /** start of macro definition */ + BEGIN(MACRO_NAME_MODE); + return token_symbol(yytext, loc); + } + +"TITLE" { + /** start of nmodl title. We return rest of line as LINE_PART. */ + BEGIN(LINE_MODE); + return token_symbol(yytext, loc); + } + +[a-zA-Z][a-zA-Z0-9_]* { + + /** macro name (typically string) */ + BEGIN(MACRO_VALUE_MODE); + return name_symbol(yytext, loc, Token::INTEGER); + } + +{D}+ { + /** macro value (typically integer) */ + BEGIN(INITIAL); + return integer_symbol(atoi(yytext), loc); + } + +[a-zA-Z][a-zA-Z0-9_]* { + /** First check if token is nmodl keyword or method name used in + * solve statements. */ + if (is_keyword(yytext) || is_method(yytext)) { + + /** Token for certain keywords need name_ptr value. */ + auto type = token_type(yytext); + ModToken tok(yytext, type, loc); + auto value = new ast::Name( new ast::String(yytext) ); + value->setToken(tok); + + switch (static_cast(type)) { + /** Tokens requiring name_ptr as value */ + case Token::METHOD: + return nmodl::Parser::make_METHOD(value, loc); + case Token::SUFFIX: + return nmodl::Parser::make_SUFFIX(value, loc); + case Token::VALENCE: + return nmodl::Parser::make_VALENCE(value, loc); + case Token::DEL: + return nmodl::Parser::make_DEL(value, loc); + case Token::DEL2: + return nmodl::Parser::make_DEL2(value, loc); + + /** We have to store context for the reaction type */ + case Token::NONLINEAR: + case Token::LINEAR: + case Token::PARTIAL: + case Token::KINETIC: + lexcontext = type; + break; + } + + /** value is not used */ + delete value; + return token_symbol(yytext, loc, type); + } else { + + /** Check if name is already defined as macro. If so, return token + * as integer with token as it's name. Otherwise return it as + * regular name token. */ + if (driver.is_defined_var(yytext)) { + auto value = driver.get_defined_var_value(yytext); + return integer_symbol(value, loc, yytext); + } else { + return name_symbol(yytext, loc); + } + } + } + +{D}+ { + return integer_symbol(atoi(yytext), loc); + } + +{D}+"."{D}*({E})? | +{D}*"."{D}+({E})? | +{D}+{E} { + return double_symbol(atof(yytext), loc); + } + +\"[^\"]*\" { + /* check comment about can't quote \" */ + return string_symbol(yytext, loc); + } + +">" { + return token_symbol(yytext, loc, Token::GT); + } + +">=" { + return token_symbol(yytext, loc, Token::GE); + } + +"<" { + return token_symbol(yytext, loc, Token::LT); + } + +"<=" { + return token_symbol(yytext, loc, Token::LE); + } + +"==" { + return token_symbol(yytext, loc, Token::EQ); + } + +"!=" { + return token_symbol(yytext, loc, Token::NE); + } + +"!" { + return token_symbol(yytext, loc, Token::NOT); + } + +"&&" { + return token_symbol(yytext, loc, Token::AND); + } + +"||" { + return token_symbol(yytext, loc, Token::OR); + } + +"<->" { + return token_symbol(yytext, loc, Token::REACT1); + } + +"~+" { + return token_symbol(yytext, loc, Token::NONLIN1); + } + +"~" { + /** return token depending on the reaction context */ + if (lexcontext == Token::NONLINEAR) { + return token_symbol(yytext, loc, Token::NONLIN1); + } + + if (lexcontext == Token::LINEAR) { + return token_symbol(yytext, loc, Token::LIN1); + } + + if (lexcontext == Token::PARTIAL) { + return token_symbol(yytext, loc, Token::TILDE); + } + + if (lexcontext == Token::KINETIC) { + return token_symbol(yytext, loc, Token::REACTION); + } + + auto msg = "Lexer Error : Invalid context, no token matched for ~"; + throw std::runtime_error(msg); + } + +"{" { + return token_symbol(yytext, loc, Token::OPEN_BRACE); + } + +"}" { + return token_symbol(yytext, loc, Token::CLOSE_BRACE); + } + +"(" { + return token_symbol(yytext, loc, Token::OPEN_PARENTHESIS); + } + +")" { + return token_symbol(yytext, loc, Token::CLOSE_PARENTHESIS); + } + +"[" { + return token_symbol(yytext, loc, Token::OPEN_BRACKET); + } + +"]" { + return token_symbol(yytext, loc, Token::CLOSE_BRACKET); + } + +"@" { + return token_symbol(yytext, loc, Token::AT); + } + +"+" { + return token_symbol(yytext, loc, Token::ADD); + } + +"-" { + return token_symbol(yytext, loc, Token::MINUS); + } + +"*" { + return token_symbol(yytext, loc, Token::MULTIPLY); + } + +"/" { + return token_symbol(yytext, loc, Token::DIVIDE); + } + +"=" { + return token_symbol(yytext, loc, Token::EQUAL); + } + +"^" { + return token_symbol(yytext, loc, Token::CARET); + } + +"," { + return token_symbol(yytext, loc, Token::COMMA); + } + +[ \t] | +[ \t] | +[ \t] { + loc.step(); + } + +\r\n { + loc.end.column = 1; + loc.step(); + loc.lines(1); + } + +\r { + loc.end.column = 1; + loc.step(); + } + +\n.* { + /** First we read entire line and print to stdout. This is useful + * for using lexer program. */ + std::string str(yytext); + stringutils::trim(str); + + if(str.length()) { + stringutils::trimnewline(str); + std::cout << "LINE "<< yylineno << ": " << str << std::endl; + } else { + std::cout << "LINE " << yylineno << ": " << std::endl; + } + + /** Pass back entire string except newline charactear */ + yyless(1); + + loc.lines(1); + loc.step(); + } + +:.* | +\?.* { + /** Todo : add grammar support for single line comment. + * Here yytext already has entire part of string */ + } + +. { + return token_symbol(yytext, loc, Token::PERIOD); + } + +[ \t] { + yymore(); + } + +\n { + yymore(); + loc.lines (1); + } + +\r\n { + yymore(); + loc.lines (1); + } + +\r { + yymore(); + loc.lines (1); + } + +"ENDVERBATIM" { + /** For verbatim block we construct entire block again, resent end + * column position to 0 and return token. We do same for comment. */ + auto str = "VERBATIM " + std::string(yytext); + BEGIN(INITIAL); + reset_end_position(); + return nmodl::Parser::make_VERBATIM(str, loc); + } + +"ENDCOMMENT" { + auto str = "COMMENT " + std::string(yytext); + BEGIN(INITIAL); + reset_end_position(); + return nmodl::Parser::make_COMMENT(str, loc); + } + +<> { + std::cout << "\n ERROR: Unexpected end of file in COPY_MODE! \n"; + return nmodl::Parser::make_END(loc); + } + +. { + yymore(); + } + +\n | +\r\n { + /** For title return string without new line character */ + loc.lines(1); + std::string str(yytext); + stringutils::trimnewline(str); + BEGIN(INITIAL); + return nmodl::Parser::make_LINE_PART(str, loc); + } + +. { + yymore(); + } + +\"[^\"\n]*$ { + std::cout << "\n ERROR: Unterminated string (e.g. for printf) \n"; + } + +%% + + +/** Some of the utility functions can't be defined outside due to macros. + * These are utility functions ported from original nocmodl implementation. */ + + +/** This implementation of NmodlFlexLexer::yylex() is required to fill the + * vtable of the class NmodlFlexLexer. We define the scanner's main yylex + * function via YY_DECL to reside in the Lexer class instead. */ +int NmodlFlexLexer::yylex() { + throw std::runtime_error("next_token() should be used instead of yylex()"); +} + +/** yy_flex_debug is member of parent scanner class */ +void nmodl::Lexer::set_debug(bool b) { + yy_flex_debug = b; +} + +/** Scan unit which is a string between opening and closing parenthesis. + * We store this in lexer itself and then consumed later from the parser. */ +void nmodl::Lexer::scan_unit() { + /** We are interested in unit after first parenthesis. + * So to get correct position update the location. */ + loc.step(); + std::string str; + + /** Unit is a string until close parenthis */ + while (1) { + auto lastch = yyinput(); + if(lastch == ')') { + unput(')'); + break; + } + else if ( lastch == '\n' || lastch == 0) { + std::cout << "ERROR: While parsing unit, closing parenthis not found"; + break; + } + str += lastch; + } + + /** YY_USER_ACTION is not executed if are consuming input + * using yyinput and hence increase location */ + loc.columns(str.size()); + + ModToken tok(str, Token::UNITS, loc); + last_unit = new ast::String(str); + last_unit->setToken(tok); +} + +/** return last scanned unit, it shouln't be null pointer */ +ast::string_ptr nmodl::Lexer::get_unit() { + if (last_unit == nullptr) { + throw std::runtime_error("Trying to get unscanned empty unit"); + } + auto result = last_unit; + last_unit = nullptr; + return result; +} diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp new file mode 100644 index 0000000000..3a928ab147 --- /dev/null +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -0,0 +1,87 @@ +#ifndef NMODL_LEXER_HPP +#define NMODL_LEXER_HPP + +#include "ast/ast.hpp" +#include "parser/nmodl_parser.hpp" + +/** Flex expects the declaration of yylex to be defined in the macro YY_DECL + * and C++ parser class expects it to be declared. */ +#ifndef YY_DECL +#define YY_DECL nmodl::Parser::symbol_type nmodl::Lexer::next_token() +#endif + +/** For creating multiple (different) lexer classes, we can use '-P' flag + * (or prefix option) to rename each yyFlexLexer to some other name like + * ‘xxFlexLexer’. And then include in other sources once per + * lexer class, first renaming yyFlexLexer as shown below. */ +#ifndef __FLEX_LEXER_H +#define yyFlexLexer NmodlFlexLexer +#include "FlexLexer.h" +#endif + +namespace nmodl { + + /** + * \class Lexer + * \brief Represent Lexer/Scanner class for NMODL language parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * Flex itself creates yyFlexLexer class, which we renamed using macros to + * NmodlFlexLexer. But we change the context of the generated yylex() function + * because the yylex() defined in NmodlFlexLexer has no parameters. Also, note + * that implementation of the member functions are in nmodl.l file due to use + * of macros. */ + class Lexer : public NmodlFlexLexer { + public: + /** Reference to driver object which contains this lexer instance. This is + * used for error reporting and checking macro definitions. */ + Driver& driver; + + /// For tracking location of the tokens + location loc; + + /// Units are stored in the scanner (could be stored in driver) + ast::String* last_unit = nullptr; + + /** For reaction ('~') we return different token based on the lexical + * context, store the current context. Note that this was returned from + * parser in original implementation. */ + int lexcontext = 0; + + /** The streams in and out default to cin and cout, but that assignment + * is only made when initializing in yylex(). */ + explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + : driver(drv), NmodlFlexLexer(in, out) {} + + ~Lexer() override= default;; + + /** Main lexing function generated by flex according to the macro declaration + * YY_DECL above. The generated bison parser then calls this virtual function + * to fetch new tokens. Note that yylex() has different declaration and hence + * it can't be used for new lexer. */ + virtual Parser::symbol_type next_token(); + + /// Enable debug output (via yyout) if compiled into the scanner. + void set_debug(bool b); + + /** For units we have to consume string until end of closing parenthesis + * and store it in the scanner object. */ + void scan_unit(); + + /** Return unit object created by scan_unit(). Initialize to nullptr to avoid + * using empty units or units from last parsing. */ + ast::String* get_unit(); + + /// For TITLE we have to input string until end of line. + std::string inputline(); + + /** Due to copy more the end position is not accurate. Set column 0 to avoid + * confusion (see NOCMODL-25). */ + void reset_end_position() { + loc.end.column = 0; + } + }; + +} // namespace nmodl + +#endif diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp new file mode 100644 index 0000000000..8d242c0149 --- /dev/null +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -0,0 +1,322 @@ +#include +#include + +#include "ast/ast.hpp" +#include "lexer/modtoken.hpp" +#include "lexer/nmodl_utils.hpp" +#include "lexer/token_mapping.hpp" +#include "utils/stringutils.hpp" + +namespace nmodl { + + /// create symbol for double/real ast class + SymbolType double_symbol(double value, PositionType& pos) { + ModToken token(std::to_string(value), Token::REAL, pos); + auto floatvalue = new ast::Double(value); + floatvalue->setToken(token); + return Parser::make_REAL(floatvalue, pos); + } + + /** Create symbol for integer ast class. Integer class also represent + * macro definition and hence could have associated text. */ + SymbolType integer_symbol(int value, PositionType& pos, const char* text) { + ast::name_ptr macro = nullptr; + ModToken token(std::to_string(value), Token::INTEGER, pos); + + if (text != nullptr) { + macro = new ast::Name(new ast::String(text)); + macro->setToken(token); + } + + auto intvalue = new ast::Integer(value, macro); + intvalue->setToken(token); + return Parser::make_INTEGER(intvalue, pos); + } + + /** Create symbol for name ast class. + * + * \todo In addition to keywords and methods, there are also external + * definitions for math and neuron specific functions/variables. In the + * token we should mark those as external. */ + SymbolType name_symbol(std::string text, PositionType& pos, TokenType type) { + ModToken token(text, type, pos); + auto value = new ast::Name(new ast::String(text)); + value->setToken(token); + return Parser::make_NAME(value, pos); + } + + /** Create symbol for prime ast class. Prime has name as well as + * order. Text returned by lexer has single quote (') as an order. + * We count order and remove quote from text */ + SymbolType prime_symbol(std::string text, PositionType& pos) { + ModToken token(text, Token::PRIME, pos); + auto order = std::count(text.begin(), text.end(), '\''); + stringutils::remove_character(text, '\''); + + auto prime_name = new ast::String(text); + auto prime_order = new ast::Integer(order, NULL); + auto value = new ast::PrimeName(prime_name, prime_order); + value->setToken(token); + return Parser::make_PRIME(value, pos); + } + + /// create symbol for string ast class + SymbolType string_symbol(std::string text, PositionType& pos) { + ModToken token(text, Token::STRING, pos); + auto value = new ast::String(text); + value->setToken(token); + return Parser::make_STRING(value, pos); + } + + /** Create symbol for generic / non-ast token. These tokens doesn't have + * specific value to pass to the parser. They are more part of grammar. + * Depending on the type of toke, we create appropriate symbol. From + * lexer we pass token type (which is optional). This is required for + * reaction parsing where we have "lexical context". Hence, if token + * type is passed then we don't check/search for the token type. */ + + SymbolType token_symbol(std::string key, PositionType& pos, TokenType type) { + /// if token type is not passed, check if it is keyword or method + if (type == Token::UNKNOWN) { + if (is_keyword(key) || is_method(key)) { + type = token_type(key); + } + } + + ModToken token(key, type, pos); + + /// lookup for token type and create approrpiate symbol type + switch (static_cast(type)) { + /// Most of the nmodl keywords + case Token::AFTER: + return Parser::make_AFTER(token, pos); + case Token::BBCOREPOINTER: + return Parser::make_BBCOREPOINTER(token, pos); + case Token::BEFORE: + return Parser::make_BEFORE(token, pos); + case Token::BREAKPOINT: + return Parser::make_BREAKPOINT(token, pos); + case Token::BY: + return Parser::make_BY(token, pos); + case Token::COMPARTMENT: + return Parser::make_COMPARTMENT(token, pos); + case Token::CONDUCTANCE: + return Parser::make_CONDUCTANCE(token, pos); + case Token::CONSERVE: + return Parser::make_CONSERVE(token, pos); + case Token::CONSTANT: + return Parser::make_CONSTANT(token, pos); + case Token::CONSTRUCTOR: + return Parser::make_CONSTRUCTOR(token, pos); + case Token::DEFINE1: + return Parser::make_DEFINE1(token, pos); + case Token::DEPEND: + return Parser::make_DEPEND(token, pos); + case Token::DEPENDENT: + return Parser::make_DEPENDENT(token, pos); + case Token::DERIVATIVE: + return Parser::make_DERIVATIVE(token, pos); + case Token::DESTRUCTOR: + return Parser::make_DESTRUCTOR(token, pos); + case Token::DISCRETE: + return Parser::make_DISCRETE(token, pos); + case Token::ELECTRODE_CURRENT: + return Parser::make_ELECTRODE_CURRENT(token, pos); + case Token::ELSE: + return Parser::make_ELSE(token, pos); + case Token::EQUATION: + return Parser::make_EQUATION(token, pos); + case Token::EXTERNAL: + return Parser::make_EXTERNAL(token, pos); + case Token::FIRST: + return Parser::make_FIRST(token, pos); + case Token::FOR_NETCONS: + return Parser::make_FOR_NETCONS(token, pos); + case Token::FORALL1: + return Parser::make_FORALL1(token, pos); + case Token::FROM: + return Parser::make_FROM(token, pos); + case Token::FUNCTION1: + return Parser::make_FUNCTION1(token, pos); + case Token::FUNCTION_TABLE: + return Parser::make_FUNCTION_TABLE(token, pos); + case Token::GETQ: + return Parser::make_GETQ(token, pos); + case Token::GLOBAL: + return Parser::make_GLOBAL(token, pos); + case Token::IF: + return Parser::make_IF(token, pos); + case Token::IFERROR: + return Parser::make_IFERROR(token, pos); + case Token::INCLUDE1: + return Parser::make_INCLUDE1(token, pos); + case Token::INDEPENDENT: + return Parser::make_INDEPENDENT(token, pos); + case Token::INITIAL1: + return Parser::make_INITIAL1(token, pos); + case Token::KINETIC: + return Parser::make_KINETIC(token, pos); + case Token::LAG: + return Parser::make_LAG(token, pos); + case Token::LAST: + return Parser::make_LAST(token, pos); + case Token::LINEAR: + return Parser::make_LINEAR(token, pos); + case Token::LOCAL: + return Parser::make_LOCAL(token, pos); + case Token::LONGDIFUS: + return Parser::make_LONGDIFUS(token, pos); + case Token::MATCH: + return Parser::make_MATCH(token, pos); + case Token::MODEL: + return Parser::make_MODEL(token, pos); + case Token::MODEL_LEVEL: + return Parser::make_MODEL_LEVEL(token, pos); + case Token::NETRECEIVE: + return Parser::make_NETRECEIVE(token, pos); + case Token::NEURON: + return Parser::make_NEURON(token, pos); + case Token::NONLINEAR: + return Parser::make_NONLINEAR(token, pos); + case Token::NONSPECIFIC: + return Parser::make_NONSPECIFIC(token, pos); + case Token::NRNMUTEXLOCK: + return Parser::make_NRNMUTEXLOCK(token, pos); + case Token::NRNMUTEXUNLOCK: + return Parser::make_NRNMUTEXUNLOCK(token, pos); + case Token::PARAMETER: + return Parser::make_PARAMETER(token, pos); + case Token::PARTIAL: + return Parser::make_PARTIAL(token, pos); + case Token::PLOT: + return Parser::make_PLOT(token, pos); + case Token::POINTER: + return Parser::make_POINTER(token, pos); + case Token::PROCEDURE: + return Parser::make_PROCEDURE(token, pos); + case Token::PROTECT: + return Parser::make_PROTECT(token, pos); + case Token::PUTQ: + return Parser::make_PUTQ(token, pos); + case Token::RANGE: + return Parser::make_RANGE(token, pos); + case Token::READ: + return Parser::make_READ(token, pos); + case Token::RESET: + return Parser::make_RESET(token, pos); + case Token::SECTION: + return Parser::make_SECTION(token, pos); + case Token::SENS: + return Parser::make_SENS(token, pos); + case Token::SOLVE: + return Parser::make_SOLVE(token, pos); + case Token::SOLVEFOR: + return Parser::make_SOLVEFOR(token, pos); + case Token::START1: + return Parser::make_START1(token, pos); + case Token::STATE: + return Parser::make_STATE(token, pos); + case Token::STEP: + return Parser::make_STEP(token, pos); + case Token::STEPPED: + return Parser::make_STEPPED(token, pos); + case Token::SWEEP: + return Parser::make_SWEEP(token, pos); + case Token::TABLE: + return Parser::make_TABLE(token, pos); + case Token::TERMINAL: + return Parser::make_TERMINAL(token, pos); + case Token::THREADSAFE: + return Parser::make_THREADSAFE(token, pos); + case Token::TO: + return Parser::make_TO(token, pos); + case Token::UNITS: + return Parser::make_UNITS(token, pos); + case Token::UNITSOFF: + return Parser::make_UNITSOFF(token, pos); + case Token::UNITSON: + return Parser::make_UNITSON(token, pos); + case Token::USEION: + return Parser::make_USEION(token, pos); + case Token::USING: + return Parser::make_USING(token, pos); + case Token::VS: + return Parser::make_VS(token, pos); + case Token::WATCH: + return Parser::make_WATCH(token, pos); + case Token::WHILE: + return Parser::make_WHILE(token, pos); + case Token::WITH: + return Parser::make_WITH(token, pos); + case Token::WRITE: + return Parser::make_WRITE(token, pos); + case Token::REACT1: + return Parser::make_REACT1(token, pos); + case Token::NONLIN1: + return Parser::make_NONLIN1(token, pos); + case Token::LIN1: + return Parser::make_LIN1(token, pos); + case Token::TILDE: + return Parser::make_TILDE(token, pos); + case Token::REACTION: + return Parser::make_REACTION(token, pos); + case Token::GT: + return Parser::make_GT(token, pos); + case Token::GE: + return Parser::make_GE(token, pos); + case Token::LT: + return Parser::make_LT(token, pos); + case Token::LE: + return Parser::make_LE(token, pos); + case Token::EQ: + return Parser::make_EQ(token, pos); + case Token::NE: + return Parser::make_NE(token, pos); + case Token::NOT: + return Parser::make_NOT(token, pos); + case Token::AND: + return Parser::make_AND(token, pos); + case Token::OR: + return Parser::make_OR(token, pos); + case Token::OPEN_BRACE: + return Parser::make_OPEN_BRACE(token, pos); + case Token::CLOSE_BRACE: + return Parser::make_CLOSE_BRACE(token, pos); + case Token::OPEN_PARENTHESIS: + return Parser::make_OPEN_PARENTHESIS(token, pos); + case Token::CLOSE_PARENTHESIS: + return Parser::make_CLOSE_PARENTHESIS(token, pos); + case Token::OPEN_BRACKET: + return Parser::make_OPEN_BRACKET(token, pos); + case Token::CLOSE_BRACKET: + return Parser::make_CLOSE_BRACKET(token, pos); + case Token::AT: + return Parser::make_AT(token, pos); + case Token::ADD: + return Parser::make_ADD(token, pos); + case Token::MINUS: + return Parser::make_MINUS(token, pos); + case Token::MULTIPLY: + return Parser::make_MULTIPLY(token, pos); + case Token::DIVIDE: + return Parser::make_DIVIDE(token, pos); + case Token::EQUAL: + return Parser::make_EQUAL(token, pos); + case Token::CARET: + return Parser::make_CARET(token, pos); + case Token::COLON: + return Parser::make_COLON(token, pos); + case Token::COMMA: + return Parser::make_COMMA(token, pos); + case Token::PERIOD: + return Parser::make_PERIOD(token, pos); + + /** We hit default case for missing token type. This is more likely + * a bug in the implementation where we forgot to handle token type. */ + default: + auto msg = "Token type not found while creating a symbol!"; + throw std::runtime_error(msg); + } + } + +} // namespace nmodl diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp new file mode 100644 index 0000000000..8f66005175 --- /dev/null +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "parser/location.hh" +#include "parser/nmodl_parser.hpp" + +/** + * \brief Utility functions for nmodl lexer + * + * From nmodl lexer we return different symbols to parser. + * Instead of writing those functions in the flex implementation + * file, those commonly used routines are defined here. Some of + * these tasks were implemented in list.c file in the oiginal mod2c + * implementation. + */ + +namespace nmodl { + + using PositionType = nmodl::location; + using SymbolType = nmodl::Parser::symbol_type; + using Token = nmodl::Parser::token; + using TokenType = nmodl::Parser::token_type; + + SymbolType double_symbol(double value, PositionType& pos); + SymbolType integer_symbol(int value, PositionType& pos, const char* name = nullptr); + SymbolType name_symbol(std::string text, PositionType& pos, TokenType token = Token::NAME); + SymbolType prime_symbol(std::string text, PositionType& pos); + SymbolType string_symbol(std::string text, PositionType& pos); + SymbolType token_symbol(std::string text, PositionType& pos, TokenType token = Token::UNKNOWN); + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp new file mode 100644 index 0000000000..a8decda830 --- /dev/null +++ b/src/nmodl/lexer/token_mapping.cpp @@ -0,0 +1,338 @@ +#include +#include +#include + +#include "ast/ast.hpp" +#include "lexer/modl.h" +#include "parser/nmodl_parser.hpp" + +namespace nmodl { + + using Token = nmodl::Parser::token; + using TokenType = nmodl::Parser::token_type; + + namespace internal { + + /** Keywords from NMODL language : name and token pair + * + * \todo Some keywords have different token names, e.g. TITLE + * keyword has MODEL as a keyword. These token names are used + * in multiple context and hence we are keeping original names. + * Once we finish code generation part then we change this. */ + static std::map keywords = { + {"VERBATIM", Token::VERBATIM}, + {"COMMENT", Token::COMMENT}, + {"TITLE", Token::MODEL}, + {"CONSTANT", Token::CONSTANT}, + {"PARAMETER", Token::PARAMETER}, + {"INDEPENDENT", Token::INDEPENDENT}, + {"ASSIGNED", Token::DEPENDENT}, + {"INITIAL", Token::INITIAL1}, + {"TERMINAL", Token::TERMINAL}, + {"DERIVATIVE", Token::DERIVATIVE}, + {"EQUATION", Token::EQUATION}, + {"BREAKPOINT", Token::BREAKPOINT}, + {"CONDUCTANCE", Token::CONDUCTANCE}, + {"SOLVE", Token::SOLVE}, + {"STATE", Token::STATE}, + {"STEPPED", Token::STEPPED}, + {"LINEAR", Token::LINEAR}, + {"NONLINEAR", Token::NONLINEAR}, + {"DISCRETE", Token::DISCRETE}, + {"FUNCTION", Token::FUNCTION1}, + {"FUNCTION_TABLE", Token::FUNCTION_TABLE}, + {"PROCEDURE", Token::PROCEDURE}, + {"PARTIAL", Token::PARTIAL}, + {"DEL2", Token::DEL2}, + {"DEL", Token::DEL}, + {"LOCAL", Token::LOCAL}, + {"METHOD", Token::USING}, + {"STEADYSTATE", Token::USING}, + {"SENS", Token::SENS}, + {"STEP", Token::STEP}, + {"WITH", Token::WITH}, + {"FROM", Token::FROM}, + {"FORALL", Token::FORALL1}, + {"TO", Token::TO}, + {"BY", Token::BY}, + {"if", Token::IF}, + {"else", Token::ELSE}, + {"while", Token::WHILE}, + {"START", Token::START1}, + {"DEFINE", Token::DEFINE1}, + {"KINETIC", Token::KINETIC}, + {"CONSERVE", Token::CONSERVE}, + {"PLOT", Token::PLOT}, + {"VS", Token::VS}, + {"LAG", Token::LAG}, + {"RESET", Token::RESET}, + {"MATCH", Token::MATCH}, + {"MODEL_LEVEL", Token::MODEL_LEVEL}, + {"SWEEP", Token::SWEEP}, + {"FIRST", Token::FIRST}, + {"LAST", Token::LAST}, + {"COMPARTMENT", Token::COMPARTMENT}, + {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, + {"PUTQ", Token::PUTQ}, + {"GETQ", Token::GETQ}, + {"IFERROR", Token::IFERROR}, + {"SOLVEFOR", Token::SOLVEFOR}, + {"UNITS", Token::UNITS}, + {"UNITSON", Token::UNITSON}, + {"UNITSOFF", Token::UNITSOFF}, + {"TABLE", Token::TABLE}, + {"DEPEND", Token::DEPEND}, + {"NEURON", Token::NEURON}, + {"SUFFIX", Token::SUFFIX}, + {"POINT_PROCESS", Token::SUFFIX}, + {"ARTIFICIAL_CELL", Token::SUFFIX}, + {"NONSPECIFIC_CURRENT", Token::NONSPECIFIC}, + {"ELECTRODE_CURRENT", Token::ELECTRODE_CURRENT}, + {"SECTION", Token::SECTION}, + {"RANGE", Token::RANGE}, + {"USEION", Token::USEION}, + {"READ", Token::READ}, + {"WRITE", Token::WRITE}, + {"VALENCE", Token::VALENCE}, + {"CHARGE", Token::VALENCE}, + {"GLOBAL", Token::GLOBAL}, + {"POINTER", Token::POINTER}, + {"BBCOREPOINTER", Token::BBCOREPOINTER}, + {"EXTERNAL", Token::EXTERNAL}, + {"INCLUDE", Token::INCLUDE1}, + {"CONSTRUCTOR", Token::CONSTRUCTOR}, + {"DESTRUCTOR", Token::DESTRUCTOR}, + {"NET_RECEIVE", Token::NETRECEIVE}, + {"BEFORE", Token::BEFORE}, + {"AFTER", Token::AFTER}, + {"WATCH", Token::WATCH}, + {"FOR_NETCONS", Token::FOR_NETCONS}, + {"THREADSAFE", Token::THREADSAFE}, + {"PROTECT", Token::PROTECT}, + {"MUTEXLOCK", Token::NRNMUTEXLOCK}, + {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; + + + /// numerical methods supported in nmodl + struct MethodInfo { + /// method types that will work with this method + long subtype = 0; + + /// if it's a variable timestep method + int varstep = 0; + + MethodInfo() {} + MethodInfo(long s, int v) : subtype(s), varstep(v) {} + }; + + static std::map methods = {{"adams", MethodInfo(DERF | KINF, 0)}, + {"runge", MethodInfo(DERF | KINF, 0)}, + {"euler", MethodInfo(DERF | KINF, 0)}, + {"adeuler", MethodInfo(DERF | KINF, 1)}, + {"heun", MethodInfo(DERF | KINF, 0)}, + {"adrunge", MethodInfo(DERF | KINF, 1)}, + {"gear", MethodInfo(DERF | KINF, 1)}, + {"newton", MethodInfo(NLINF, 0)}, + {"simplex", MethodInfo(NLINF, 0)}, + {"simeq", MethodInfo(LINF, 0)}, + {"seidel", MethodInfo(LINF, 0)}, + {"_advance", MethodInfo(KINF, 0)}, + {"sparse", MethodInfo(KINF, 0)}, + {"derivimplicit", MethodInfo(DERF, 0)}, + {"cnexp", MethodInfo(DERF, 0)}, + {"clsoda", MethodInfo(DERF | KINF, 1)}, + {"after_cvode", MethodInfo(0, 0)}, + {"cvode_t", MethodInfo(0, 0)}, + {"cvode_t_v", MethodInfo(0, 0)}}; + + + /** In the original implementation different vectors were created for + * extdef, extdef2, extdef3, extdef4 etc. Instead of that we are changing + * those vectors with map. This will help us to search + * in single map and find it's type. The types are defined as follows: + * + * DefinitionType::EXT_DOUBLE : external names that can be used as doubles + * without giving an error message + * + * DefinitionType::EXT_2 : external function names that can be used with + * array and function name arguments + * + * DefinitionType::EXT_3 : function names that get two reset arguments + * + * DefinitionType::EXT_4 : functions that need a first arg of NrnThread* + * + * DefinitionType::EXT_5 : the extdef names that are not threadsafe + * + * These types were used so that it's easy to it to old implementation. */ + + enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; + + static std::map extern_definitions = { + {"first_time", DefinitionType::EXT_DOUBLE}, + {"error", DefinitionType::EXT_DOUBLE}, + {"f_flux", DefinitionType::EXT_DOUBLE}, + {"b_flux", DefinitionType::EXT_DOUBLE}, + {"fabs", DefinitionType::EXT_DOUBLE}, + {"sqrt", DefinitionType::EXT_DOUBLE}, + {"sin", DefinitionType::EXT_DOUBLE}, + {"cos", DefinitionType::EXT_DOUBLE}, + {"tan", DefinitionType::EXT_DOUBLE}, + {"acos", DefinitionType::EXT_DOUBLE}, + {"asin", DefinitionType::EXT_DOUBLE}, + {"atan", DefinitionType::EXT_DOUBLE}, + {"atan2", DefinitionType::EXT_DOUBLE}, + {"sinh", DefinitionType::EXT_DOUBLE}, + {"cosh", DefinitionType::EXT_DOUBLE}, + {"tanh", DefinitionType::EXT_DOUBLE}, + {"floor", DefinitionType::EXT_DOUBLE}, + {"ceil", DefinitionType::EXT_DOUBLE}, + {"fmod", DefinitionType::EXT_DOUBLE}, + {"log10", DefinitionType::EXT_DOUBLE}, + {"log", DefinitionType::EXT_DOUBLE}, + {"pow", DefinitionType::EXT_DOUBLE}, + {"printf", DefinitionType::EXT_DOUBLE}, + {"prterr", DefinitionType::EXT_DOUBLE}, + {"exp", DefinitionType::EXT_DOUBLE}, + {"threshold", DefinitionType::EXT_DOUBLE}, + {"force", DefinitionType::EXT_DOUBLE}, + {"deflate", DefinitionType::EXT_DOUBLE}, + {"expfit", DefinitionType::EXT_DOUBLE}, + {"derivs", DefinitionType::EXT_DOUBLE}, + {"spline", DefinitionType::EXT_DOUBLE}, + {"hyperbol", DefinitionType::EXT_DOUBLE}, + {"revhyperbol", DefinitionType::EXT_DOUBLE}, + {"sigmoid", DefinitionType::EXT_DOUBLE}, + {"revsigmoid", DefinitionType::EXT_DOUBLE}, + {"harmonic", DefinitionType::EXT_DOUBLE}, + {"squarewave", DefinitionType::EXT_DOUBLE}, + {"sawtooth", DefinitionType::EXT_DOUBLE}, + {"revsawtooth", DefinitionType::EXT_DOUBLE}, + {"ramp", DefinitionType::EXT_DOUBLE}, + {"pulse", DefinitionType::EXT_DOUBLE}, + {"perpulse", DefinitionType::EXT_DOUBLE}, + {"step", DefinitionType::EXT_DOUBLE}, + {"perstep", DefinitionType::EXT_DOUBLE}, + {"erf", DefinitionType::EXT_DOUBLE}, + {"exprand", DefinitionType::EXT_DOUBLE}, + {"factorial", DefinitionType::EXT_DOUBLE}, + {"gauss", DefinitionType::EXT_DOUBLE}, + {"normrand", DefinitionType::EXT_DOUBLE}, + {"poisrand", DefinitionType::EXT_DOUBLE}, + {"poisson", DefinitionType::EXT_DOUBLE}, + {"setseed", DefinitionType::EXT_DOUBLE}, + {"scop_random", DefinitionType::EXT_DOUBLE}, + {"boundary", DefinitionType::EXT_DOUBLE}, + {"romberg", DefinitionType::EXT_DOUBLE}, + {"legendre", DefinitionType::EXT_DOUBLE}, + {"invert", DefinitionType::EXT_DOUBLE}, + {"stepforce", DefinitionType::EXT_DOUBLE}, + {"schedule", DefinitionType::EXT_DOUBLE}, + {"set_seed", DefinitionType::EXT_DOUBLE}, + {"nrn_pointing", DefinitionType::EXT_DOUBLE}, + {"state_discontinuity", DefinitionType::EXT_DOUBLE}, + {"net_send", DefinitionType::EXT_DOUBLE}, + {"net_move", DefinitionType::EXT_DOUBLE}, + {"net_event", DefinitionType::EXT_DOUBLE}, + {"nrn_random_play", DefinitionType::EXT_DOUBLE}, + {"at_time", DefinitionType::EXT_DOUBLE}, + {"nrn_ghk", DefinitionType::EXT_DOUBLE}, + {"romberg", DefinitionType::EXT_2}, + {"legendre", DefinitionType::EXT_2}, + {"deflate", DefinitionType::EXT_2}, + {"threshold", DefinitionType::EXT_3}, + {"squarewave", DefinitionType::EXT_3}, + {"sawtooth", DefinitionType::EXT_3}, + {"revsawtooth", DefinitionType::EXT_3}, + {"ramp", DefinitionType::EXT_3}, + {"pulse", DefinitionType::EXT_3}, + {"perpulse", DefinitionType::EXT_3}, + {"step", DefinitionType::EXT_3}, + {"perstep", DefinitionType::EXT_3}, + {"stepforce", DefinitionType::EXT_3}, + {"schedule", DefinitionType::EXT_3}, + {"at_time", DefinitionType::EXT_4}, + {"force", DefinitionType::EXT_5}, + {"deflate", DefinitionType::EXT_5}, + {"expfit", DefinitionType::EXT_5}, + {"derivs", DefinitionType::EXT_5}, + {"spline", DefinitionType::EXT_5}, + {"exprand", DefinitionType::EXT_5}, + {"gauss", DefinitionType::EXT_5}, + {"normrand", DefinitionType::EXT_5}, + {"poisrand", DefinitionType::EXT_5}, + {"poisson", DefinitionType::EXT_5}, + {"setseed", DefinitionType::EXT_5}, + {"scop_random", DefinitionType::EXT_5}, + {"boundary", DefinitionType::EXT_5}, + {"romberg", DefinitionType::EXT_5}, + {"invert", DefinitionType::EXT_5}, + {"stepforce", DefinitionType::EXT_5}, + {"schedule", DefinitionType::EXT_5}, + {"set_seed", DefinitionType::EXT_5}, + {"nrn_random_play", DefinitionType::EXT_5}}; + + + /** Internal NEURON variables that can be used in nmod files. The compiler + * passes like scope checker need to know if certain variable is undefined. + * Note that these are not used by lexer/parser. */ + + static std::vector neuron_vars = {"t", "dt", "celsius", "v", "diam", "area"}; + + TokenType keyword_type(std::string name) { + return keywords[name]; + } + + TokenType method_type(std::string name) { + return Token::METHOD; + } + + bool is_externdef(std::string name) { + return (extern_definitions.find(name) != extern_definitions.end()); + } + + DefinitionType extdef_type(std::string name) { + if (!is_externdef(name)) { + throw std::runtime_error("Can't find " + name + " in external definitions!"); + } + return extern_definitions[name]; + } + + } // namespace internal + + + /// methods exposed to lexer, parser and compilers passes + + + bool is_keyword(std::string name) { + return (internal::keywords.find(name) != internal::keywords.end()); + } + + bool is_method(std::string name) { + return (internal::methods.find(name) != internal::methods.end()); + } + + /// return token type for associated name (used by nmodl scanner) + TokenType token_type(std::string name) { + if (is_keyword(name)) { + return internal::keyword_type(name); + } + if (is_method(name)) { + return internal::method_type(name); + } + throw std::runtime_error("get_token_type called for non-existent token " + name); + } + + /// return all external variables (required for symbol table registration) + std::vector all_external_variables() { + std::vector result; + result.insert(result.end(), internal::neuron_vars.begin(), internal::neuron_vars.end()); + for (auto& method : internal::methods) { + result.push_back(method.first); + } + for (auto& extdef : internal::extern_definitions) { + result.push_back(extdef.first); + } + return result; + } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp new file mode 100644 index 0000000000..8a69c01173 --- /dev/null +++ b/src/nmodl/lexer/token_mapping.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include +#include "parser/nmodl_parser.hpp" + +namespace nmodl { + bool is_keyword(std::string name); + bool is_method(std::string name); + nmodl::Parser::token_type token_type(std::string name); + std::vector all_external_variables(); + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l new file mode 100755 index 0000000000..9474e13ab4 --- /dev/null +++ b/src/nmodl/lexer/verbatim.l @@ -0,0 +1,135 @@ +%{ + #include + #include + #include + #include + #include "parser/verbatim_context.hpp" + #include "parser/verbatim_parser.hpp" + + /* the scanner state will include a field called yyextra + * that can be used for user-defined state + */ + #define YY_EXTRA_TYPE VerbatimContext* + + + /* we need to parse strings as well as files, we redefine YY_INPUT + * and give it a C++ flavour. It will use the istream from the parser + * context to read the next character. The parser context defaults is + * to cin, but we will set it to an istringstream later + * @TODO: with this I am not able recognize NEWLINE character! + */ + #define YY_INPUT(buf,result,max_size) \ + { \ + char c; \ + (*yyextra->is) >> std::noskipws >> c; \ + if(yyextra->is->eof()) \ + result = YY_NULL; \ + else { \ + buf[0] = c; \ + result = 1; \ + } \ + } + +%} + +/* need to provide this option to flex otherwise complain: + * "error: 'yymore_used_but_not_detected' was not declared in + * this scope + */ +%option yymore + +/* lexer header file */ +%option header-file="verbatim_lexer.hpp" + +/* lexer implementation file */ +%option outfile="verbatim_lexer.cpp" + +/* lexer prefix */ +%option prefix="Verbatim_" + +/* need to be reentrant */ +%option reentrant + +/* no need for includes */ +%option noyywrap + +/* need to unput in buffer for custom routines */ +%option unput + +/* need to input in buffer for custom routines */ +%option input + +/* not an interactive lexer, takes a file */ +%option batch + +/* debug, disable for production */ +%option debug + +/* bison compatible lexer */ +%option bison-bridge + +/* bison location information */ +%option bison-locations + +/* keep line information */ +%option yylineno + +/* mode for verbatim or comment */ +%x COPY_MODE + +%% + + /* Currently we do simple thing for VERBATIM and COMMENT + * just pass them as it is! + * This will be revisited in the future once we have + * everything in place working with clang! + */ + +"VERBATIM" { + BEGIN(COPY_MODE); + return VERBATIM; + } + +"COMMENT" { + BEGIN(COPY_MODE); + return COMMENT; + } + +"ENDVERBATIM" { + BEGIN(INITIAL); + return ENDVERBATIM; + } + + +"ENDCOMMENT" { + BEGIN(INITIAL); + return ENDCOMMENT; + } + + +"\n" { + yylval->str = yytext[0]; + return NEWLINE; + } + +. { + yylval->str = yytext[0]; + return CHAR; + } + +%% + + +/* initialize nmodlext lexer context */ + +void VerbatimContext::init_scanner() { + yylex_init(&scanner); + yyset_extra(this, scanner); +} + +/* delete nmodlext lexer context */ + +void VerbatimContext::destroy_scanner() { + yylex_destroy(scanner); +} + From db816e3b457290a106afd2fb6ae12aa0bd1e71bb Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 20 Oct 2017 17:07:38 +0200 Subject: [PATCH 005/871] Refactor parser implementation with C++ interface of the Bison. Note that verbatim lexer/parser is still based on C interface but with reentrant as well. Also moving parser code gradually to C++11. Parser still construct raw pointers but AST classes take care of owning the pointers and creating smart pointers. So parser is not responsible for data ownership. This is done specifically to avoid changes in lot of bison's semantic actions. Similar to lexer, this changeset is incomplete (from feature branch) and need subsequent ast chanegeset. Change-Id: I948b0f26dd0677aaaca45298b2e9dcb497bf6c14 NMODL Repo SHA: BlueBrain/nmodl@60859d16650cf3a9b225d25eedeb40e58bc9f60e --- src/nmodl/parser/main.cpp | 49 + src/nmodl/parser/nmodl.y | 1133 ------------- src/nmodl/parser/nmodl.yy | 2195 +++++++++++++++++++++++++ src/nmodl/parser/nmodl_driver.cpp | 67 + src/nmodl/parser/nmodl_driver.hpp | 83 + src/nmodl/parser/verbatim.y | 91 + src/nmodl/parser/verbatim_context.hpp | 36 + 7 files changed, 2521 insertions(+), 1133 deletions(-) create mode 100644 src/nmodl/parser/main.cpp delete mode 100644 src/nmodl/parser/nmodl.y create mode 100644 src/nmodl/parser/nmodl.yy create mode 100644 src/nmodl/parser/nmodl_driver.cpp create mode 100644 src/nmodl/parser/nmodl_driver.hpp create mode 100644 src/nmodl/parser/verbatim.y create mode 100644 src/nmodl/parser/verbatim_context.hpp diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp new file mode 100644 index 0000000000..8717810856 --- /dev/null +++ b/src/nmodl/parser/main.cpp @@ -0,0 +1,49 @@ +#include +#include + +#include "parser/nmodl_driver.hpp" +#include "tclap/CmdLine.h" + +/** + * Standlone parser program for NMODL. This demonstrate basic + * usage of psrser and driver class. + * + * \todo Parser create ast during parse actions. We need to + * hook up json/yaml writer in this example to dump ast. + */ + +int main(int argc, char* argv[]) { + try { + TCLAP::CmdLine cmd("NMODL Parser: Standalone parser program for NMODL"); + TCLAP::ValueArg filearg( + "", "file", "NMODL input file path", false, "../test/input/example1.mod", "string"); + + cmd.add(filearg); + cmd.parse(argc, argv); + + std::string filename = filearg.getValue(); + std::ifstream file(filename); + + if (!file) { + throw std::runtime_error("Could not open file " + filename); + } + + std::cout << "\n NMODL Parser : Processing file : " << filename << std::endl; + + std::istream& in(file); + + /// driver object creates lexer and parser, just call parser method + nmodl::Driver driver; + driver.parse_stream(in); + + // driver.parse_file(filename); + + std::cout << "----PARSING FINISHED----" << std::endl; + + } catch (TCLAP::ArgException& e) { + std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; + return 1; + } + + return 0; +} diff --git a/src/nmodl/parser/nmodl.y b/src/nmodl/parser/nmodl.y deleted file mode 100644 index b282caeef1..0000000000 --- a/src/nmodl/parser/nmodl.y +++ /dev/null @@ -1,1133 +0,0 @@ -/* Bison specification for NMODL Parser - * - * This is based on original NOCMODL implementation and similar - * to most of the open source tools implementation. There are C++ - * based parser examples (e.g. - * http://panthema.net/2007/flex-bison-cpp-example/) but we are - * avoiding it because they are somewhat difficult to understand - * and we have seen very few projects using flex/bison with C++. - */ - -%{ - - #include - #include - #include - #include - #include "lexer/modtoken.hpp" - #include "lexer/lexer_utils.hpp" - #include "parser/nmodl_context.hpp" - #include "utils/stringutils.hpp" - #include "ast/ast.hpp" - - /* root of the ast */ - //ast::ProgramNode *astRoot; - - /* a macro that extracts the scanner state from the parser state for yylex */ - #define scanner context->scanner - -%} - -/* print out verbose error instead of just message 'syntax error' */ -%error-verbose - -/* make a reentrant parser */ -%pure-parser - -/* parser prefix */ -%name-prefix "Nmodl_" - -/* enable location tracking */ -%locations - -/* generate header file */ -%defines - -/* yyparse() takes an extra argument context */ -%parse-param {NmodlContext* context} - -/* reentrant lexer needs an extra argument for yylex() */ -%lex-param {void * scanner} - -/* token types */ -%union { - ModToken *qp; - std::string *str_ptr; - - ast::ProgramNode *program_ptr; - ast::StatementNodeList *statement_list_ptr; - ast::ModelNode *model_ptr; - ast::StringNode *string_ptr; - ast::DefineNode *define_ptr; - ast::NameNode *name_ptr; - ast::IntegerNode *integer_ptr; - ast::ParamBlockNode *paramblock_ptr; - ast::ParamAssignNodeList *paramassign_list_ptr; - ast::ParamAssignNode *paramassign_ptr; - ast::IdentifierNode *identifier_ptr; - ast::NumberNode *number_ptr; - ast::UnitNode *units_ptr; - ast::LimitsNode *limits_ptr; - ast::UnitStateNode *unitstate_ptr; - ast::StepBlockNode *stepblock_ptr; - ast::SteppedNodeList *stepped_list_ptr; - ast::SteppedNode *stepped_ptr; - ast::NumberNodeList *number_list_ptr; - ast::IndependentBlockNode *independentblock_ptr; - ast::IndependentDefNodeList *independentdef_list_ptr; - ast::IndependentDefNode *independentdef_ptr; - ast::BooleanNode *boolean_ptr; - ast::DependentDefNode *dependentdef_ptr; - ast::DependentBlockNode *dependentblock_ptr; - ast::DependentDefNodeList *dependentdef_list_ptr; - ast::StateBlockNode *stateblock_ptr; - ast::PlotDeclarationNode *plotdeclaration_ptr; - ast::PlotVariableNodeList *plotvariable_list_ptr; - ast::InitialBlockNode *initialblock_ptr; - ast::LocalVariableNodeList *localvariable_list_ptr; - ast::ConstructorBlockNode *constructorblock_ptr; - ast::DestructorBlockNode *destructorblock_ptr; - ast::ConductanceHintNode *conductancehint_ptr; - ast::BinaryExpressionNode *binaryexpression_ptr; - ast::ExpressionNode *expression_ptr; - ast::UnaryExpressionNode *unaryexpression_ptr; - ast::NonLinEuationNode *nonlineuation_ptr; - ast::LinEquationNode *linequation_ptr; - ast::FunctionCallNode *functioncall_ptr; - ast::ExpressionNodeList *expression_list_ptr; - ast::ExpressionStatementNode *expressionstatement_ptr; - ast::FromStatementNode *fromstatement_ptr; - ast::ForAllStatementNode *forallstatement_ptr; - ast::WhileStatementNode *whilestatement_ptr; - ast::IfStatementNode *ifstatement_ptr; - ast::ElseIfStatementNodeList *elseifstatement_list_ptr; - ast::ElseStatementNode *elsestatement_ptr; - ast::DerivativeBlockNode *derivativeblock_ptr; - ast::LinearBlockNode *linearblock_ptr; - ast::NameNodeList *name_list_ptr; - ast::NonLinearBlockNode *nonlinearblock_ptr; - ast::DiscreteBlockNode *discreteblock_ptr; - ast::PartialBlockNode *partialblock_ptr; - ast::PartialEquationNode *partialequation_ptr; /*@TODO: check why this is not used */ - ast::PrimeNameNode *primename_ptr; - ast::PartialBoundaryNode *partialboundary_ptr; - ast::FunctionTableBlockNode *functiontableblock_ptr; - ast::ArgumentNodeList *argument_list_ptr; - ast::FunctionBlockNode *functionblock_ptr; - ast::ProcedureBlockNode *procedureblock_ptr; - ast::NetReceiveBlockNode *netreceiveblock_ptr; - ast::SolveBlockNode *solveblock_ptr; - ast::BreakpointBlockNode *breakpointblock_ptr; - ast::TerminalBlockNode *terminalblock_ptr; - ast::BABlockNode *bablock_ptr; - ast::BABlockTypeNode *bablocktype_ptr; - ast::WatchStatementNode *watchstatement_ptr; - ast::WatchNode *watch_ptr; - ast::DoubleNode *double_ptr; - ast::ForNetconNode *fornetcon_ptr; - ast::SensNode *sens_ptr; - ast::VarNameNodeList *varname_list_ptr; - ast::ConserveNode *conserve_ptr; - ast::CompartmentNode *compartment_ptr; - ast::LDifuseNode *ldifuse_ptr; - ast::KineticBlockNode *kineticblock_ptr; - ast::VarNameNode *varname_ptr; - ast::LagStatementNode *lagstatement_ptr; - ast::QueueStatementNode *queuestatement_ptr; - ast::QueueExpressionTypeNode *queueexpressiontype_ptr; - ast::MatchBlockNode *matchblock_ptr; - ast::MatchNodeList *match_list_ptr; - ast::MatchNode *match_ptr; - ast::UnitBlockNode *unitsblock_ptr; - ast::UnitDefNodeList *unitdef_list_ptr; - ast::FactordefNodeList *factordef_list_ptr; - ast::FactordefNode *factordef_ptr; - ast::UnitDefNode *unitdef_ptr; - ast::ConstantStatementNode *constatntstatement_ptr; - ast::ConstantBlockNode *constatntblock_ptr; - ast::ConstantStatementNodeList *constantstatement_list_ptr; - ast::TableStatementNode *tablestatement_ptr; - ast::NeuronBlockNode *neuronblock_ptr; - ast::NrnSuffixNode *nrnsuffix_ptr; - ast::NrnUseionNode *nrnuseion_ptr; - ast::NrnElctrodeCurrentNode *nrnelctrodecurrent_ptr; - ast::NrnSectionNode *nrnsection_ptr; - ast::NrnRangeNode *nrnrange_ptr; - ast::NrnGlobalNode *nrnglobal_ptr; - ast::NrnPointerNode *nrnpointer_ptr; - ast::NrnBbcorePtrNode *nrnbbcoreptr_ptr; - ast::NrnExternalNode *nrnexternal_ptr; - ast::NrnThreadSafeNode *nrnthreadsafe_ptr; - ast::ValenceNode *valence_ptr; - - ast::ReactionStatementNode *reactionstatement_ptr; - ast::ReactionOperatorNode *reactionoperator_ptr; - ast::ReadIonVarNodeList *readionvar_list_ptr; - ast::WriteIonVarNodeList *writeionvar_list_ptr; - ast::NonspeCurVarNodeList *nonspecurvar_list_ptr; - ast::ElectrodeCurVarNodeList *electrodecurvar_list_ptr; - ast::SectionVarNodeList *sectionvar_list_ptr; - ast::RangeVarNodeList *rangevar_list_ptr; - ast::GlobalVarNodeList *globalvar_list_ptr; - ast::PointerVarNodeList *pointervar_list_ptr; - ast::BbcorePointerVarNodeList *bbcorepointervar_list_ptr; - ast::ExternVarNodeList *externvar_list_ptr; - ast::ThreadsafeVarNodeList *threadsafevar_list_ptr; - - ast::VerbatimNode *verbatim_ptr; - ast::CommentNode *comment_ptr; - ast::FloatNode *float_ptr; - ast::IndexedNameNode *indexedname_ptr; - ast::ArgumentNode *argument_ptr; - ast::LocalVariableNode *localvariable_ptr; - ast::LocalListStatementNode *localliststatement_ptr; - ast::NumberRangeNode *numberrange_ptr; - ast::StatementNode *statement_ptr; - ast::StatementBlockNode *statementblock_ptr; - ast::BlockNode *block_ptr; - ast::FirstLastTypeIndexNode *firstlasttypeindex_ptr; - ast::BinaryOp binary_op_val; - ast::BinaryOperatorNode *binaryoperator_ptr; - ast::DoubleUnitNode *doubleunits_ptr; -} - - -/* Define our terminal symbols (tokens). This should - * match our tokens.l lex file. - */ - -%token MODEL CONSTANT INDEPENDENT DEPENDENT STATE -%token INITIAL1 DERIVATIVE SOLVE USING WITH STEPPED DISCRETE -%token FROM FORALL1 TO BY WHILE IF ELSE START1 STEP SENS SOLVEFOR -%token PROCEDURE PARTIAL DEFINE1 IFERROR PARAMETER -%token DERFUNC EQUATION TERMINAL LINEAR NONLINEAR FUNCTION1 LOCAL -%token LIN1 NONLIN1 PUTQ GETQ TABLE DEPEND BREAKPOINT -%token INCLUDE1 FUNCTION_TABLE PROTECT NRNMUTEXLOCK NRNMUTEXUNLOCK -%token '{' '}' '(' ')' '[' ']' '@' '+' '*' '-' '/' '=' '^' ':' ',' -%token '~' -%token OR AND GT LT LE EQ NE NOT - -%token PLOT VS LAG RESET MATCH MODEL_LEVEL SWEEP FIRST LAST -%token KINETIC CONSERVE REACTION REACT1 COMPARTMENT UNITS -%token UNITSON UNITSOFF LONGDIFUS - -%token NEURON NONSPECIFIC READ WRITE USEION THREADSAFE -%token GLOBAL SECTION RANGE POINTER BBCOREPOINTER EXTERNAL BEFORE AFTER WATCH -%token ELECTRODE_CURRENT CONSTRUCTOR DESTRUCTOR NETRECEIVE FOR_NETCONS -%token CONDUCTANCE - - - -/* Extra token that we are adding to print more useful errors. These - * are currently not being used. see wiki page section "Extra Tokens" - */ -%token BEGINBLK ENDBLK /* this is left and right curly braces */ -%token LEFT_PAREN RIGHT_PAREN /* this is left and right parenthesis */ - -/*building ast : probably dont do this, just return value!*/ -%token PRIME -%token INTEGER -%token REAL -%token STRING -%token NAME -%token METHOD -%token SUFFIX -%token VALENCE -%token DEL -%token DEL2 -%token DEFINEDVAR - -/*special token returning string */ -%token VERBATIM -%token COMMENT - -/* Define the type of node our nonterminal symbols represent. - The types refer to the %union declaration above. -*/ - -/* building AST */ -%type all - -%type Name -%type NUMBER -%type real -%type intexpr -%type integer - -%type line -%type model -%type units -%type optindex -%type unit - -%type proc - -%type limits -%type abstol -%type name -%type number - -%type primary -%type term -%type leftlinexpr -%type linexpr -%type numlist -%type expr -%type aexpr - -%type ostmt -%type astmt -%type stmtlist -%type locallist -%type locallist1 - -%type varname -%type exprlist -%type define1 -%type queuestmt -%type asgn - -%type fromstmt -%type whilestmt -%type ifstmt -%type optelseif -%type optelse -%type solveblk -%type funccall - -%type ifsolerr -%type opinc -%type opstart -/* @todo: though name is sens_pt it has senslist production*/ -%type senslist -%type sens - -%type lagstmt -%type forallstmt -%type parmasgn -%type stepped -%type indepdef - -%type depdef -/* @todo: productions for which we dont have %type - * withby : dont need, imo - */ -%type declare -%type parmblk -%type parmbody -%type indepblk -%type indepbody -%type depblk -%type depbody - -%type stateblk -%type stepblk - -%type stepbdy - -%type watchstmt -%type watchdir -%type watch1 -%type fornetcon -%type plotdecl - -%type pvlist - -%type constblk -%type conststmt -%type matchblk -%type matchlist -%type match -%type matchname - -%type pareqn -/* values from ast enum types */ -%type firstlast -%type reaction -%type conserve -%type react - -%type compart -%type ldifus -%type namelist -%type unitblk -%type unitbody -%type unitdef -%type factordef - -%type solvefor - -%type solvefor1 -%type uniton -%type tablst -%type tablst1 -%type tablestmt -%type dependlst - -%type arglist -%type arglist1 -%type locoptarray -%type neuronblk -%type nrnuse -%type nrnstmt - -%type nrnionrlist -%type nrnionwlist -%type nrnonspeclist -%type nrneclist -%type nrnseclist -%type nrnrangelist -%type nrnglobalist -%type nrnptrlist -%type nrnbbptrlist -%type nrnextlist -%type opthsafelist -%type threadsafelist -%type valence -%type initstmt -%type bablk - -%type conducthint - -/*missing types found while constructing ast */ -%type stmtlist1 -%type initblk -%type constructblk -%type destructblk -%type funcblk -%type kineticblk -%type brkptblk -%type derivblk -%type linblk -%type nonlinblk -%type procedblk -%type netrecblk -%type terminalblk -%type discretblk -%type partialblk -%type functableblk - -/* precedence in expressions--- low to high */ -%left OR -%left AND -%left GT GE LT LE EQ NE -%left '+' '-' /* left associative, same precedence */ -%left '*' '/' '%' /* left assoc., higher precedence */ -%left UNARYMINUS NOT -%right '^' /* exponentiation */ - -%{ - extern int yylex(YYSTYPE*, YYLTYPE*, void*); - extern int yyparse(NmodlContext*); - extern void yyerror(YYLTYPE*, NmodlContext*, const char *); - std::string parse_with_verbatim_parser(std::string *); -%} - - -/* start symbol is named "top" */ -%start top - -%% - - -top : all { context->astRoot = $1; } - | error { /*_ER_*/ } - ; - -all : { $$ = new ast::ProgramNode(); } - | all astmt { $1->addStatement($2); } /* __todo__ : for diffeq */ - | all model { $1->addStatement($2); } - | all locallist { $1->addStatement($2); } - | all define1 { $1->addStatement($2); } - | all declare { $1->addBlock($2); } - | all MODEL_LEVEL INTEGER declare { /*__todo__ @Michael : discussed with Michael, was inserted - by merge program (which no longer exist). This was to - avoid the name collision in case of include. Idea was - to have some kind of namespace! - */ - } - | all proc { $1->addBlock($2); } - | all VERBATIM { $1->addStatement(new ast::VerbatimNode(new ast::StringNode(parse_with_verbatim_parser($2)))); } - | all COMMENT { $1->addStatement(new ast::CommentNode(new ast::StringNode(parse_with_verbatim_parser($2)))); } - | all uniton { $1->addStatement($2); } - | all INCLUDE1 STRING { $1->addStatement(new ast::IncludeNode($3)); } - ; - -model : MODEL line { $$ = new ast::ModelNode($2); } - ; - -line : { $$ = new ast::StringNode(LexerUtils::inputline(scanner)); } - ; - -define1 : DEFINE1 NAME INTEGER { - $$ = new ast::DefineNode($2, $3); - context->add_defined_var($2->getName(), $3->eval()); - } - | DEFINE1 error { /*_ER_*/ } - ; - -Name : NAME { $$ = $1; } - ; - -declare : parmblk { $$ = $1; } - | indepblk { $$ = $1; } - | depblk { $$ = $1; } - | stateblk { $$ = $1; } - | stepblk { $$ = $1; } - | plotdecl { $$ = new ast::PlotBlockNode($1); } - | neuronblk { $$ = $1; } - | unitblk { $$ = $1; } - | constblk { $$ = $1; } - ; - -parmblk : PARAMETER '{' parmbody '}' { $$ = new ast::ParamBlockNode($3); } - ; - -parmbody : { $$ = new ast::ParamAssignNodeList(); } - | parmbody parmasgn { $1->push_back($2); } - ; - -parmasgn : NAME '=' number units limits { $$ = new ast::ParamAssignNode($1, $3, $4, $5); } - | NAME units limits { $$ = new ast::ParamAssignNode($1, NULL, $2, $3); } - | NAME '[' integer ']' units limits { $$ = new ast::ParamAssignNode( new ast::IndexedNameNode($1, $3), NULL, $5, $6); } - | error { /*_ER_*/} - ; - -units : { $$ = NULL; } - | unit { $$ = $1; } - ; - -unit : '(' { $$ = LexerUtils::inputtoparpar(scanner); } ')' { $$ = new ast::UnitNode($2); } - ; - -uniton : UNITSON { $$ = new ast::UnitStateNode(ast::UNIT_ON); } - | UNITSOFF { $$ = new ast::UnitStateNode(ast::UNIT_OFF); } - ; - -limits : { $$ = NULL; } - | LT real ',' real GT { $$ = new ast::LimitsNode($2, $4); } - ; - -stepblk : STEPPED '{' stepbdy '}' { $$ = new ast::StepBlockNode($3); } - ; - -stepbdy : { $$ = new ast::SteppedNodeList(); } - | stepbdy stepped { $1->push_back($2); } - ; - -stepped : NAME '=' numlist units { $$ = new ast::SteppedNode($1, $3, $4); } - ; - -numlist : number ',' number { $$ = new ast::NumberNodeList(); $$->push_back($1); $$->push_back($3); } - | numlist ',' number { $1->push_back($3); } - ; - -name : Name { $$ = $1; } - | PRIME { $$ = $1; } - ; - -number : NUMBER { $$ = $1; } - | '-' NUMBER { $2->negate(); $$ = $2; } - ; - -NUMBER : integer { $$ = $1; } - | REAL { $$ = $1; } - ; - -integer : INTEGER { $$ = $1; } - | DEFINEDVAR { $$ = $1; } - ; - -real : REAL { $$ = $1; } - | integer { $$ = new ast::DoubleNode(double($1->eval())); } - ; - -indepblk : INDEPENDENT '{' indepbody '}' { $$ = new ast::IndependentBlockNode($3); } - ; - -indepbody : { $$ = new ast::IndependentDefNodeList(); } - | indepbody indepdef { $1->push_back($2); } - | indepbody SWEEP indepdef { $1->push_back($3); $3->sweep = new ast::BooleanNode(1); } - ; - -indepdef : NAME FROM number TO number withby integer opstart units { $$ = new ast::IndependentDefNode(NULL, $1, $3, $5, $7, $8, $9); } - | error { /*_ER_*/ } - ; - -withby : WITH - ; - -depblk : DEPENDENT '{' depbody '}' { $$ = new ast::DependentBlockNode($3); } - ; - -depbody : { $$ = new ast::DependentDefNodeList(); } - | depbody depdef { $1->push_back($2); } - ; - -depdef : name opstart units abstol { $$ = new ast::DependentDefNode($1, NULL, NULL, NULL, $2, $3, $4); } - | name '[' integer ']' opstart units abstol { $$ = new ast::DependentDefNode($1, $3, NULL, NULL, $5, $6, $7);} - | name FROM number TO number opstart units abstol { $$ = new ast::DependentDefNode($1, NULL, $3, $5, $6, $7, $8); } - | name '[' integer ']' FROM number TO number opstart units abstol { $$ = new ast::DependentDefNode($1, $3, $6, $8, $9, $10, $11);} - | error { /*_ER_*/ } - ; - -opstart : { $$ = NULL; } - | START1 number { $$ = $2; } - ; - -abstol : { $$ = NULL; } - | LT real GT { $$ = $2; } - ; - -stateblk : STATE '{' depbody '}' { $$ = new ast::StateBlockNode($3); } - ; - -plotdecl : PLOT pvlist VS name optindex { $$ = new ast::PlotDeclarationNode($2, new ast::PlotVariableNode($4,$5)); } - | PLOT error { /*_ER_*/ } - ; - -pvlist : name optindex { $$ = new ast::PlotVariableNodeList(); $$->push_back(new ast::PlotVariableNode($1, $2)); } - | pvlist ',' name optindex { $$ = $1; $$->push_back(new ast::PlotVariableNode($3, $4));} - ; - -optindex : { $$ = NULL; } - | '[' INTEGER ']' { $$ = $2; } - ; - -proc : initblk { $$ = $1; } - | derivblk { $$ = $1; } - | brkptblk { $$ = $1; } - | linblk { $$ = $1; } - | nonlinblk { $$ = $1; } - | funcblk { $$ = $1; } - | procedblk { $$ = $1; } - | netrecblk { $$ = $1; } - | terminalblk { $$ = $1; } - | discretblk { $$ = $1; } - | partialblk { $$ = $1; } - | kineticblk { $$ = $1; } - | constructblk { $$ = $1; } - | destructblk { $$ = $1; } - | functableblk { $$ = $1; } - | BEFORE bablk { $$ = new ast::BeforeBlockNode($2); } - | AFTER bablk { $$ = new ast::AfterBlockNode($2); } - ; - -initblk : INITIAL1 stmtlist '}' { $$ = new ast::InitialBlockNode($2); } - ; - -constructblk : CONSTRUCTOR stmtlist '}' { $$ = new ast::ConstructorBlockNode($2); } - ; - -destructblk : DESTRUCTOR stmtlist '}' { $$ = new ast::DestructorBlockNode($2); } - ; - -stmtlist : '{' stmtlist1 { $$ = new ast::StatementBlockNode($2); $$->setToken($1->clone()); } - | '{' locallist stmtlist1 { $3->insert($3->begin(), $2); $$ = new ast::StatementBlockNode($3); $$->setToken($1->clone()); } - ; - -conducthint : CONDUCTANCE Name { $$ = new ast::ConductanceHintNode($2, NULL); } - | CONDUCTANCE Name USEION NAME { $$ = new ast::ConductanceHintNode($2, $4); } - ; - -locallist : LOCAL locallist1 { $$ = new ast::LocalListStatementNode($2); } - | LOCAL error { /*_ER_*/ } - ; - -locallist1 : NAME locoptarray - { - $$ = new ast::LocalVariableNodeList(); - if($2) { - $$->push_back( new ast::LocalVariableNode(new ast::IndexedNameNode($1, $2))); - } else { - $$->push_back( new ast::LocalVariableNode($1) ); - } - } - | locallist1 ',' NAME locoptarray - { - if($4) { - $1->push_back( new ast::LocalVariableNode(new ast::IndexedNameNode($3, $4))); - } else { - $1->push_back( new ast::LocalVariableNode($3) ); - } - } - ; - -locoptarray : { $$ = NULL; } - | '[' integer ']' { $$ = $2; } - ; - -stmtlist1 : { $$ = new ast::StatementNodeList(); } - | stmtlist1 ostmt { $1->push_back($2); } - | stmtlist1 astmt { $1->push_back($2); } - ; - -ostmt : fromstmt { $$ = $1; } - | forallstmt { $$ = $1; } - | whilestmt { $$ = $1; } - | ifstmt { $$ = $1; } - | stmtlist '}' { $$ = new ast::ExpressionStatementNode($1); } - | solveblk { $$ = new ast::ExpressionStatementNode($1); } - | conducthint { $$ = $1; } - | VERBATIM { $$ = new ast::VerbatimNode(new ast::StringNode(parse_with_verbatim_parser($1))); } - | COMMENT { $$ = new ast::CommentNode(new ast::StringNode(parse_with_verbatim_parser($1))); } - | sens { $$ = $1; } - | compart { $$ = $1; } - | ldifus { $$ = $1; } - | conserve { $$ = $1; } - | lagstmt { $$ = $1; } - | queuestmt { $$ = $1; } - | RESET { $$ = new ast::ResetNode(); } - | matchblk { $$ = new ast::ExpressionStatementNode($1); } - | pareqn { $$ = $1; } - | tablestmt { $$ = $1; } - | uniton { $$ = $1; } - | initstmt { $$ = $1; } - | watchstmt { $$ = $1; } - | fornetcon { $$ = new ast::ExpressionStatementNode($1); } - | NRNMUTEXLOCK { $$ = new ast::MutexLockNode(); } - | NRNMUTEXUNLOCK{ $$ = new ast::MutexUnlockNode(); } - | error { /*_ER_*/ } - ; - -astmt : asgn { $$ = new ast::ExpressionStatementNode($1); } - | PROTECT asgn { $$ = new ast::ProtectStatementNode($2); } - | reaction { $$ = $1; } - | funccall { $$ = new ast::ExpressionStatementNode($1); } - ; - -asgn : varname '=' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ASSIGN), $3); } - | nonlineqn expr '=' expr { $$ = new ast::NonLinEuationNode($2, $4); } - | lineqn leftlinexpr '=' linexpr { $$ = new ast::LinEquationNode($2, $4); } - ; - -varname : name { $$ = new ast::VarNameNode($1, NULL); } - | name '[' intexpr ']' { $$ = new ast::VarNameNode(new ast::IndexedNameNode($1, $3), NULL); } - | NAME '@' integer { $$ = new ast::VarNameNode($1, $3); } - | NAME '@' integer '[' intexpr ']' { $$ = new ast::VarNameNode(new ast::IndexedNameNode($1, $5), $3); } - ; - -intexpr : Name { $$ = $1; } - | integer { $$ = $1; } - | '(' intexpr ')' { $$ = $2; } - | intexpr '+' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } - | intexpr '-' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } - | intexpr '*' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } - | intexpr '/' intexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } - | error {} - ; - -expr : varname { $$ = $1; } - | real units { - if($2) - $$ = new ast::DoubleUnitNode($1, $2); - else - $$ = $1; - } - | funccall { $$ = $1; } - | '(' expr ')' { $$ = $2; } - | expr '+' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } - | expr '-' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } - | expr '*' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } - | expr '/' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } - | expr '^' expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_POWER), $3); } - | expr OR expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_OR), $3); } - | expr AND expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_AND), $3); } - | expr GT expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_GREATER), $3); } - | expr LT expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_LESS), $3); } - | expr GE expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_GREATER_EQUAL), $3); } - | expr LE expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_LESS_EQUAL), $3); } - | expr EQ expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_EXACT_EQUAL), $3); } - | expr NE expr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_NOT_EQUAL), $3); } - | NOT expr { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NOT), $2); } - | '-' expr %prec UNARYMINUS { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NEGATION), $2); } - | error { /*_ER_*/ } - ; - - /* - | '(' expr { yyerror("Unbalanced left parenthesis followed by valid expressions"); } - | '(' error { yyerror("Unbalanced left parenthesis followed by non parseable"); } - | expr ')' { yyerror("Unbalanced right parenthesis"); } - */ - -nonlineqn : NONLIN1 - ; - -lineqn : LIN1 - ; - -leftlinexpr : linexpr { $$ = $1; } - ; - -linexpr : primary { $$ = $1; } - | '-' primary { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NEGATION), $2); } - | linexpr '+' primary { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } - | linexpr '-' primary { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } - ; - -primary : term { $$ = $1; } - | primary '*' term { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } - | primary '/' term { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } - ; - -term : varname { $$ = $1; } - | real { $$ = $1; } - | funccall { $$ = $1; } - | '(' expr ')' { $$ = $2; } - | error { /*_ER_*/ } - ; - -funccall : NAME '(' exprlist ')' { $$ = new ast::FunctionCallNode($1, $3); } - ; - -exprlist : { $$ = NULL; } - | expr { $$ = new ast::ExpressionNodeList(); $$->push_back($1); } - | STRING { $$ = new ast::ExpressionNodeList(); $$->push_back($1); } - | exprlist ',' expr { $1->push_back($3); } - | exprlist ',' STRING { $1->push_back($3); } - ; - -fromstmt : FROM NAME '=' intexpr TO intexpr opinc stmtlist '}' { $$ = new ast::FromStatementNode($2, $4, $6, $7, $8); } - | FROM error { /*_ER_*/ } - ; - -opinc : { $$ = NULL; } - | BY intexpr { $$ = $2; } - ; - -forallstmt : FORALL1 NAME stmtlist '}' { $$ = new ast::ForAllStatementNode($2, $3); } - | FORALL1 error { /*_ER_*/ } - ; - -whilestmt : WHILE '(' expr ')' stmtlist '}' { $$ = new ast::WhileStatementNode($3, $5); } - ; - -ifstmt : IF '(' expr ')' stmtlist '}' optelseif optelse { $$ = new ast::IfStatementNode($3, $5, $7, $8); } - ; - -optelseif : { $$ = new ast::ElseIfStatementNodeList(); } - | optelseif ELSE IF '(' expr ')' stmtlist '}' { $1->push_back(new ast::ElseIfStatementNode($5, $7)); } - ; - -optelse : { $$ = NULL; } - | ELSE stmtlist '}' { $$ = new ast::ElseStatementNode($2); } - ; - -derivblk : DERIVATIVE NAME stmtlist '}' { $$ = new ast::DerivativeBlockNode($2, $3); $$->setToken($1->clone()); } - ; - -linblk : LINEAR NAME solvefor stmtlist '}' { $$ = new ast::LinearBlockNode($2, $3, $4); $$->setToken($1->clone()); } - ; - -nonlinblk : NONLINEAR NAME solvefor stmtlist '}' { $$ = new ast::NonLinearBlockNode($2, $3, $4); $$->setToken($1->clone()); } - ; - -discretblk : DISCRETE NAME stmtlist '}' { $$ = new ast::DiscreteBlockNode($2, $3); $$->setToken($1->clone()); } - ; - -partialblk : PARTIAL NAME stmtlist '}' { $$ = new ast::PartialBlockNode($2, $3); $$->setToken($1->clone()); } - | PARTIAL error { /*_ER_*/ } - ; - -pareqn : '~' PRIME '=' NAME '*' DEL2 '(' NAME ')' '+' NAME { $$ = new ast::PartialBoundaryNode(NULL, $2, NULL, NULL, $4, $6, $8, $11); } - | '~' DEL NAME '[' firstlast ']' '=' expr { $$ = new ast::PartialBoundaryNode($2, $3, $5, $8, NULL, NULL, NULL, NULL); } - | '~' NAME '[' firstlast ']' '=' expr { $$ = new ast::PartialBoundaryNode(NULL, $2, $4, $7, NULL, NULL, NULL, NULL); } - ; - -firstlast : FIRST { $$ = new ast::FirstLastTypeIndexNode(ast::PEQ_FIRST); } - | LAST { $$ = new ast::FirstLastTypeIndexNode(ast::PEQ_LAST); } - ; - -functableblk : FUNCTION_TABLE NAME '(' arglist ')' units { $$ = new ast::FunctionTableBlockNode($2, $4, $6); $$->setToken($1->clone()); } - ; - -funcblk : FUNCTION1 NAME '(' arglist ')' units stmtlist '}' { $$ = new ast::FunctionBlockNode($2, $4, $6, $7); $$->setToken($1->clone()); } - ; - -arglist : { $$ = NULL; } - | arglist1 { $$ = $1; } - ; - -arglist1 : name units { $$ = new ast::ArgumentNodeList(); $$->push_back(new ast::ArgumentNode($1, $2)); } - | arglist1 ',' name units { $1->push_back(new ast::ArgumentNode($3, $4)); } - ; - -procedblk : PROCEDURE NAME '(' arglist ')' units stmtlist '}' { $$ = new ast::ProcedureBlockNode($2, $4, $6, $7); $$->setToken($1->clone()); } - ; - -netrecblk : NETRECEIVE '(' arglist ')' stmtlist '}' { $$ = new ast::NetReceiveBlockNode($3, $5); } - | NETRECEIVE error { /*_ER_*/ } - ; - -initstmt : INITIAL1 stmtlist '}' { $$ = new ast::ExpressionStatementNode(new ast::InitialBlockNode($2)); } - ; - -solveblk : SOLVE NAME ifsolerr { $$ = new ast::SolveBlockNode($2, NULL, $3); } - | SOLVE NAME USING METHOD ifsolerr { $$ = new ast::SolveBlockNode($2, $4, $5); } - | SOLVE error { /*_ER_*/ } - ; - -ifsolerr : { $$ = NULL; } - | IFERROR stmtlist '}' { $$ = $2; } - ; - -solvefor : { $$ = NULL; } - | solvefor1 { $$ = $1; } - ; - -solvefor1 : SOLVEFOR NAME { $$ = new ast::NameNodeList(); $$->push_back($2); } - | solvefor1 ',' NAME { $1->push_back($3); } - | SOLVEFOR error { /*_ER_*/ } - ; - -brkptblk : BREAKPOINT stmtlist '}' { $$ = new ast::BreakpointBlockNode($2); } - ; - -terminalblk : TERMINAL stmtlist '}' { $$ = new ast::TerminalBlockNode($2); } - ; - -bablk : BREAKPOINT stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_BREAKPOINT), $2); } - | SOLVE stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_SOLVE), $2); } - | INITIAL1 stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_INITIAL), $2); } - | STEP stmtlist '}' { $$ = new ast::BABlockNode(new ast::BABlockTypeNode(ast::BATYPE_STEP), $2); } - | error { /*_ER_*/ } - ; - -watchstmt : WATCH watch1 { $$ = new ast::WatchStatementNode(new ast::WatchNodeList()); $$->addWatch($2); } - | watchstmt ',' watch1 { $1->addWatch($3); } - | WATCH error { /*_ER_*/ } - ; - -watch1 : '(' aexpr watchdir aexpr ')' real { $$ = new ast::WatchNode( new ast::BinaryExpressionNode($2, $3, $4), $6); } - ; - -watchdir : GT { $$ = new ast::BinaryOperatorNode(ast::BOP_GREATER); } - | LT { $$ = new ast::BinaryOperatorNode(ast::BOP_LESS); } - ; - -fornetcon : FOR_NETCONS '(' arglist ')' stmtlist '}' { $$ = new ast::ForNetconNode($3, $5); } - | FOR_NETCONS error { /*_ER_*/ } - ; - -aexpr : varname { $$ = $1; } - | real units { $$ = new ast::DoubleUnitNode($1, $2); } - | funccall { $$ = $1; } - | '(' aexpr ')' { $$ = $2; } - | aexpr '+' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } - | aexpr '-' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_SUBTRACTION), $3); } - | aexpr '*' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_MULTIPLICATION), $3); } - | aexpr '/' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_DIVISION), $3); } - | aexpr '^' aexpr { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_POWER), $3); } - | '-' aexpr %prec UNARYMINUS { $$ = new ast::UnaryExpressionNode(new ast::UnaryOperatorNode(ast::UOP_NEGATION), $2); } - | error { /*_ER_*/ } - ; - -sens : SENS senslist { $$ = new ast::SensNode($2); } - | SENS error { /*_ER_*/ } - ; - -senslist : varname { $$ = new ast::VarNameNodeList(); $$->push_back($1); } - | senslist ',' varname { $1->push_back($3); } - ; - -conserve : CONSERVE react '=' expr { $$ = new ast::ConserveNode($2, $4); } - | CONSERVE error {} - ; - -compart : COMPARTMENT NAME ',' expr '{' namelist '}' { $$ = new ast::CompartmentNode($2, $4, $6); } - | COMPARTMENT expr '{' namelist '}' { $$ = new ast::CompartmentNode(NULL, $2, $4); } - ; - -ldifus : LONGDIFUS NAME ',' expr '{' namelist '}' { $$ = new ast::LDifuseNode($2, $4, $6); } - | LONGDIFUS expr '{' namelist '}' { $$ = new ast::LDifuseNode(NULL, $2, $4); } - ; - -namelist : NAME { $$ = new ast::NameNodeList(); $$->push_back($1); } - | namelist NAME { $1->push_back($2); } - ; - -kineticblk : KINETIC NAME solvefor stmtlist '}' { $$ = new ast::KineticBlockNode($2, $3, $4); $$->setToken($1->clone()); } - ; - -reaction : REACTION react REACT1 react '(' expr ',' expr ')' { $$ = new ast::ReactionStatementNode($2, new ast::ReactionOperatorNode(ast::LTMINUSGT), $4, $6, $8); } - | REACTION react LT LT '(' expr ')' { $$ = new ast::ReactionStatementNode($2, new ast::ReactionOperatorNode(ast::LTLT), NULL, $6, NULL); } - | REACTION react '-' GT '(' expr ')' { $$ = new ast::ReactionStatementNode($2, new ast::ReactionOperatorNode(ast::MINUSGT), NULL, $6, NULL); } - | REACTION error { /* todo : __REACTION_DISCUSS with Michael__ */ } - ; - -react : varname { $$ = $1; } - |integer varname { $$ = new ast::ReactVarNameNode($1, $2); } - |react '+' varname { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), $3); } - |react '+' integer varname { $$ = new ast::BinaryExpressionNode($1, new ast::BinaryOperatorNode(ast::BOP_ADDITION), new ast::ReactVarNameNode($3, $4)); } - ; - -lagstmt : LAG name BY NAME { $$ = new ast::LagStatementNode($2, $4); } - | LAG error { /*_ER_*/ } - ; - -queuestmt : PUTQ name { $$ = new ast::QueueStatementNode(new ast::QueueExpressionTypeNode(ast::PUT_QUEUE), $2); } - | GETQ name { $$ = new ast::QueueStatementNode(new ast::QueueExpressionTypeNode(ast::GET_QUEUE), $2); } - ; - -matchblk : MATCH '{' matchlist '}' { $$ = new ast::MatchBlockNode($3); } - ; - -matchlist : match { $$ = new ast::MatchNodeList(); $$->push_back($1); } - | matchlist match { $1->push_back($2); } - ; - -match : name { $$ = new ast::MatchNode($1, NULL); } - | matchname '(' expr ')' '=' expr { $$ = new ast::MatchNode($1, new ast::BinaryExpressionNode($3, new ast::BinaryOperatorNode(ast::BOP_ASSIGN), $6)); } - | error { /*_ER_*/ } - ; - -matchname : name { $$ = $1; } - | name '[' NAME ']' { $$ = new ast::IndexedNameNode($1, $3); } - ; - -unitblk : UNITS '{' unitbody '}' { $$ = new ast::UnitBlockNode($3); } - ; - -unitbody : { $$ = new ast::ExpressionNodeList(); } - | unitbody unitdef { $1->push_back($2); } - | unitbody factordef { $1->push_back($2); } - ; - -unitdef : unit '=' unit { $$ = new ast::UnitDefNode($1, $3); } - | unit error { /*_ER_*/ } - ; - -factordef : NAME '=' real unit { $$ = new ast::FactordefNode($1, $3, $4, NULL, NULL); $$->setToken($1->getToken()->clone()); } - | NAME '=' unit unit { $$ = new ast::FactordefNode($1, NULL, $3, NULL, $4); $$->setToken($1->getToken()->clone()); } - | NAME '=' unit '-' GT unit { $$ = new ast::FactordefNode($1, NULL, $3, new ast::BooleanNode(1), $6); $$->setToken($1->getToken()->clone()); } - | error {} - ; - -constblk : CONSTANT '{' conststmt '}' { $$ = new ast::ConstantBlockNode($3); } - ; - -conststmt : { $$ = new ast::ConstantStatementNodeList(); } - | conststmt NAME '=' number units { $1->push_back( new ast::ConstantStatementNode($2, $4, $5) ); } - ; - -tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER { $$ = new ast::TableStatementNode($2, $3, $5, $7, $9); } - | TABLE error { /*_ER_*/ } - ; - -tablst : { $$ = NULL; } - | tablst1 { $$ = $1; } - ; - -tablst1 : Name { $$ = new ast::NameNodeList(); $$->push_back($1); } - | tablst1 ',' Name { $1->push_back($3); } - ; - -dependlst : { $$ = NULL; } - | DEPEND tablst1 { $$ = $2; } - ; - -neuronblk : NEURON '{' nrnstmt '}' { ast::StatementBlockNode *p = new ast::StatementBlockNode($3); p->setToken($2->clone()); $$ = new ast::NeuronBlockNode(p); } - ; - -nrnstmt : { $$ = new ast::StatementNodeList(); } - | nrnstmt SUFFIX NAME { $1->push_back( new ast::NrnSuffixNode($2, $3) ); } - | nrnstmt nrnuse { $1->push_back($2); } - | nrnstmt NONSPECIFIC nrnonspeclist { $1->push_back(new ast::NrnNonspecificNode($3)); } - | nrnstmt ELECTRODE_CURRENT nrneclist { $1->push_back(new ast::NrnElctrodeCurrentNode($3)); } - | nrnstmt SECTION nrnseclist { $1->push_back(new ast::NrnSectionNode($3)); } - | nrnstmt RANGE nrnrangelist { $1->push_back(new ast::NrnRangeNode($3)); } - | nrnstmt GLOBAL nrnglobalist { $1->push_back(new ast::NrnGlobalNode($3)); } - | nrnstmt POINTER nrnptrlist { $1->push_back(new ast::NrnPointerNode($3)); } - | nrnstmt BBCOREPOINTER nrnbbptrlist{ $1->push_back(new ast::NrnBbcorePtrNode($3)); } - | nrnstmt EXTERNAL nrnextlist { $1->push_back(new ast::NrnExternalNode($3)); } - | nrnstmt THREADSAFE opthsafelist { $1->push_back(new ast::NrnThreadSafeNode($3)); } - ; - -nrnuse : USEION NAME READ nrnionrlist valence { $$ = new ast::NrnUseionNode($2, $4, NULL, $5); } - | USEION NAME WRITE nrnionwlist valence { $$ = new ast::NrnUseionNode($2, NULL, $4, $5); } - | USEION NAME READ nrnionrlist WRITE nrnionwlist valence { $$ = new ast::NrnUseionNode($2, $4, $6, $7); } - | USEION error { /*_ER_*/ } - ; - -nrnionrlist : NAME { $$ = new ast::ReadIonVarNodeList(); $$->push_back(new ast::ReadIonVarNode($1)); } - | nrnionrlist ',' NAME { $1->push_back(new ast::ReadIonVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnionwlist : NAME { $$ = new ast::WriteIonVarNodeList(); $$->push_back(new ast::WriteIonVarNode($1)); } - | nrnionwlist ',' NAME { $1->push_back(new ast::WriteIonVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnonspeclist : NAME { $$ = new ast::NonspeCurVarNodeList(); $$->push_back(new ast::NonspeCurVarNode($1)); } - | nrnonspeclist ',' NAME { $1->push_back(new ast::NonspeCurVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrneclist : NAME { $$ = new ast::ElectrodeCurVarNodeList(); $$->push_back(new ast::ElectrodeCurVarNode($1)); } - | nrneclist ',' NAME { $1->push_back(new ast::ElectrodeCurVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnseclist : NAME { $$ = new ast::SectionVarNodeList(); $$->push_back(new ast::SectionVarNode($1)); } - | nrnseclist ',' NAME { $1->push_back(new ast::SectionVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnrangelist : NAME { $$ = new ast::RangeVarNodeList(); $$->push_back(new ast::RangeVarNode($1)); } - | nrnrangelist ',' NAME { $1->push_back(new ast::RangeVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnglobalist : NAME { $$ = new ast::GlobalVarNodeList(); $$->push_back(new ast::GlobalVarNode($1)); } - | nrnglobalist ',' NAME { $1->push_back(new ast::GlobalVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnptrlist : NAME { $$ = new ast::PointerVarNodeList(); $$->push_back(new ast::PointerVarNode($1)); } - | nrnptrlist ',' NAME { $1->push_back(new ast::PointerVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnbbptrlist : NAME { $$ = new ast::BbcorePointerVarNodeList(); $$->push_back(new ast::BbcorePointerVarNode($1)); } - | nrnbbptrlist ',' NAME { $1->push_back(new ast::BbcorePointerVarNode($3)); } - | error { /*_ER_*/ } - ; - -nrnextlist : NAME { $$ = new ast::ExternVarNodeList(); $$->push_back(new ast::ExternVarNode($1)); } - | nrnextlist ',' NAME { $1->push_back(new ast::ExternVarNode($3)); } - | error { /*_ER_*/ } - ; - -opthsafelist : { $$ = NULL; } - | threadsafelist { $$ = $1; } - ; - -threadsafelist : NAME { $$ = new ast::ThreadsafeVarNodeList(); $$->push_back(new ast::ThreadsafeVarNode($1)); } - | threadsafelist ',' NAME { $1->push_back(new ast::ThreadsafeVarNode($3)); } - ; - -valence : { $$ = NULL; } - | VALENCE real { $$ = new ast::ValenceNode($1, $2); } - | VALENCE '-' real { $3->negate(); $$ = new ast::ValenceNode($1, $3); } - ; - -%% - -std::string parse_with_verbatim_parser(std::string* str) { - /* - std::istringstream* is = new std::istringstream(str->c_str()); - VerbatimContext extcontext(is); - Verbatim_parse(&extcontext); - - std::string ss(*(extcontext.result)); - */ - std::string ss(*str); - return ss; -} - -void yyerror(YYLTYPE* locp, NmodlContext* context, const char *s) { - std::printf("\n Error: %s for token %s \n", s, "_for_token_"); - std::exit(1); -} - diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy new file mode 100644 index 0000000000..3c3b50020b --- /dev/null +++ b/src/nmodl/parser/nmodl.yy @@ -0,0 +1,2195 @@ +/****************************************************************************** + * + * @brief Bison grammar and parser implementation for NMODL + * + * This implementation is based NEURON's nocmodl program. The grammar rules, + * symbols, terminals and non-terminals closely resember to NEURON + * implementation. This is to support entire NMODL language for NEURON as well + * as CoreNEURON. As opposed to non-reentrant C parser, this implementation + * uses C++ interface of flex and bison. This makes lexer/parser fully + * reentrant. + * One of the implementation decision was whether to construct/return smart + * pointers from parser or let ast class constructor to handle creation of + * smart pointers from raw one. After trying shared_ptr and unique_ptr + * implementations within bison parset, the later approach seems more + * convenient and flexible while working with large number of grammar rules. + * But this could be change once ast specification and compiler passes will + * be implemented. + *****************************************************************************/ + +/** to include parser's header file, one has to include ast definitions */ + %code requires + { + #include "ast/ast.hpp" + } + +/** use C++ parser interface of bison */ +%skeleton "lalr1.cc" + +/** require modern bison version */ +%require "3.0.2" + +/** print verbose error messages instead of just message 'syntax error' */ +%define parse.error verbose + +/** enable tracing parser for debugging */ +%define parse.trace + +/** enable location tracking */ +%locations + +/** add extra arguments to yyparse() and yylexe() methods */ +%parse-param {class Lexer& scanner} +%parse-param {class Driver& driver} +%lex-param { nmodl::Scanner &scanner } +%lex-param { nmodl::Driver &driver } + +/** use variant based implementation of semantic values */ +%define api.value.type variant + +/** assert correct cleanup of semantic value objects */ +%define parse.assert + +/** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ +%define api.token.constructor + +/** namespace to enclose parser */ +%name-prefix "nmodl" + +/** set the parser's class identifier */ +%define parser_class_name {Parser} + +/** keep track of the current position within the input */ +%locations + +/** Initializations before parsing : Use filename from driver to initialize location object */ +%initial-action +{ + @$.begin.filename = @$.end.filename = &driver.streamname; +}; + +/** Tokens for lexer : we return ModToken object from lexer. This is useful when + * we want to access original string and location. With C++ interface and other + * location related information, this is now less useful in parser. But when we + * lexer executable or tests, it's useful to return ModToken. Note that UNKNOWN + * token is added for convenience (with default argumebts). */ + +%token MODEL +%token CONSTANT +%token INDEPENDENT +%token DEPENDENT +%token STATE +%token INITIAL1 +%token DERIVATIVE +%token SOLVE +%token USING +%token WITH +%token STEPPED +%token DISCRETE +%token FROM +%token FORALL1 +%token TO +%token BY +%token WHILE +%token IF +%token ELSE +%token START1 +%token STEP +%token SENS +%token SOLVEFOR +%token PROCEDURE +%token PARTIAL +%token DEFINE1 +%token IFERROR +%token PARAMETER +%token DERFUNC +%token EQUATION +%token TERMINAL +%token LINEAR +%token NONLINEAR +%token FUNCTION1 +%token LOCAL +%token LIN1 +%token NONLIN1 +%token PUTQ +%token GETQ +%token TABLE +%token DEPEND +%token BREAKPOINT +%token INCLUDE1 +%token FUNCTION_TABLE +%token PROTECT +%token NRNMUTEXLOCK +%token NRNMUTEXUNLOCK +%token OR +%token AND +%token GT +%token LT +%token LE +%token EQ +%token NE +%token NOT +%token GE +%token PLOT +%token VS +%token LAG RESET +%token MATCH +%token MODEL_LEVEL +%token SWEEP +%token FIRST +%token LAST +%token KINETIC +%token CONSERVE +%token REACTION +%token REACT1 +%token COMPARTMENT +%token UNITS +%token UNITSON +%token UNITSOFF +%token LONGDIFUS +%token NEURON +%token NONSPECIFIC +%token READ +%token WRITE +%token USEION +%token THREADSAFE +%token GLOBAL +%token SECTION +%token RANGE POINTER +%token BBCOREPOINTER +%token EXTERNAL +%token BEFORE +%token AFTER +%token WATCH +%token ELECTRODE_CURRENT +%token CONSTRUCTOR +%token DESTRUCTOR +%token NETRECEIVE +%token FOR_NETCONS +%token CONDUCTANCE +%token REAL +%token INTEGER +%token DEFINEDVAR +%token NAME +%token METHOD +%token SUFFIX +%token VALENCE +%token DEL +%token DEL2 +%token PRIME +%token VERBATIM +%token COMMENT +%token LINE_PART +%token STRING +%token UNIT_STR +%token OPEN_BRACE "{" +%token CLOSE_BRACE "}" +%token OPEN_PARENTHESIS "(" +%token CLOSE_PARENTHESIS ")" +%token OPEN_BRACKET "[" +%token CLOSE_BRACKET "]" +%token AT "@" +%token ADD "+" +%token MULTIPLY "*" +%token MINUS "-" +%token DIVIDE "/" +%token EQUAL "=" +%token CARET "^" +%token COLON ":" +%token COMMA "," +%token TILDE "~" +%token PERIOD "." +%token END 0 "End of file" +%token UNKNOWN + +/** Define terminal and nonterminal symbols : Instead of using AST classes + * directly, we are using typedefs like program_ptr. This is useful when we + * want to transparently change naming/type scheme. For example, raw pointer + * to smakrt pointers. Manually changing all types in bison specification is + * time consuming. Also, naming of termina/non-terminal symbols is kept same + * as NEURON. This helps during development and debugging. This could be + * once all implementation details get ported. */ + +%type all +%type Name +%type NUMBER +%type real +%type intexpr +%type integer +%type model +%type units +%type optindex +%type unit +%type proc +%type limits +%type abstol +%type name +%type number +%type primary +%type term +%type leftlinexpr +%type linexpr +%type numlist +%type expr +%type aexpr +%type ostmt +%type astmt +%type stmtlist +%type locallist +%type locallist1 +%type varname +%type exprlist +%type define1 +%type queuestmt +%type asgn +%type fromstmt +%type whilestmt +%type ifstmt +%type optelseif +%type optelse +%type solveblk +%type funccall +%type ifsolerr +%type opinc +%type opstart +%type senslist +%type sens +%type lagstmt +%type forallstmt +%type parmasgn +%type stepped +%type indepdef +%type depdef +%type declare +%type parmblk +%type parmbody +%type indepblk +%type indepbody +%type depblk +%type depbody +%type stateblk +%type stepblk +%type stepbdy +%type watchstmt +%type watchdir +%type watch1 +%type fornetcon +%type plotdecl +%type pvlist +%type constblk +%type conststmt +%type matchblk +%type matchlist +%type match +%type matchname +%type pareqn +%type firstlast +%type reaction +%type conserve +%type react +%type compart +%type ldifus +%type namelist +%type unitblk +%type unitbody +%type unitdef +%type factordef +%type solvefor +%type solvefor1 +%type uniton +%type tablst +%type tablst1 +%type tablestmt +%type dependlst +%type arglist +%type arglist1 +%type locoptarray +%type neuronblk +%type nrnuse +%type nrnstmt +%type nrnionrlist +%type nrnionwlist +%type nrnonspeclist +%type nrneclist +%type nrnseclist +%type nrnrangelist +%type nrnglobalist +%type nrnptrlist +%type nrnbbptrlist +%type nrnextlist +%type opthsafelist +%type threadsafelist +%type valence +%type initstmt +%type bablk +%type conducthint +%type stmtlist1 +%type initblk +%type constructblk +%type destructblk +%type funcblk +%type kineticblk +%type brkptblk +%type derivblk +%type linblk +%type nonlinblk +%type procedblk +%type netrecblk +%type terminalblk +%type discretblk +%type partialblk +%type functableblk + +/** Precedence and Associativity : specify operator precedency and + * associativity (from lower to higher. Note that '^' represent + * exponentiation. */ + +%left OR +%left AND +%left GT GE LT LE EQ NE +%left "+" "-" +%left "*" "/" "%" +%left UNARYMINUS NOT +%right "^" + +%{ + #include "lexer/nmodl_lexer.hpp" + #include "parser/nmodl_driver.hpp" + #include "parser/verbatim_context.hpp" + + /// yylex takes scanner as well as driver reference + static nmodl::Parser::symbol_type yylex(nmodl::Lexer &scanner, nmodl::Driver &driver) { + return scanner.next_token(); + } + + /// forward declaration for the function which handles interface with other parsers + std::string parse_with_verbatim_parser(std::string); +%} + +/** start symbol */ +%start top + + +%% + +/** Grammar rules : specification of all grammar rules for NMODL. The core + * grammar rules are based on NEURON implementation but all other + * implementation details are changed to support generate AST. Some rules + * are adapted to support better error handling and location tracking. + * Note that YYLLOC_DEFAULT is not sufficient for all rules and hence + * we need to update @$ (especially @$.begin). Consider below example: + * + * nrnstmt RANGE nrnrangelist + * + * In this case we have to do : @$.begin = $1.begin (@$.end is already + * set to $3.end and hence don't need to update). + * + * \todo ModToken is set for the symbols returned by Lexer. But we need to + * set accurate location for each production. We need to add method in AST + * classes to handle this. */ + +top : all + { + driver.astRoot = std::shared_ptr($1); + } + | error + { + error(scanner.loc, "top"); + } + ; + + +all : { + $$ = new ast::Program(); + } + | all astmt + { + $1->addStatement($2); + $$ = $1; + } + | all model + { + $1->addStatement($2); + $$ = $1; + } + | all locallist + { + $1->addStatement($2); + $$ = $1; + } + | all define1 + { + $1->addStatement($2); + $$ = $1; + } + | all declare + { + $1->addBlock($2); + $$ = $1; + } + | all MODEL_LEVEL INTEGER declare + { + /** todo : This is discussed with Michael Hines. Model level was inserted + * by merge program which is no longer exist. This was to avoid the name + * collision in case of include. Idea was to have some kind of namespace! + */ + } + | all proc + { + $1->addBlock($2); + $$ = $1; + } + | all VERBATIM + { + auto text = parse_with_verbatim_parser($2); + auto statement = new ast::Verbatim(new ast::String(text)); + $1->addStatement(statement); + $$ = $1; + } + | all COMMENT + { + auto text = parse_with_verbatim_parser($2); + auto statement = new ast::Comment(new ast::String(text)); + $1->addStatement(statement); + $$ = $1; + } + | all uniton + { + $1->addStatement($2); + $$ = $1; + } + | all INCLUDE1 STRING + { + $1->addStatement(new ast::Include($3)); + $$ = $1; + } + ; + + +model : MODEL LINE_PART + { + $$ = new ast::Model(new ast::String($2)); + } + ; + + +define1 : DEFINE1 NAME INTEGER + { + $$ = new ast::Define($2, $3); + driver.add_defined_var($2->getName(), $3->eval()); + } + | DEFINE1 error + { + error(scanner.loc, "define1"); + } + ; + + +Name : NAME { $$ = $1; } + ; + + +declare : parmblk { $$ = $1; } + | indepblk { $$ = $1; } + | depblk { $$ = $1; } + | stateblk { $$ = $1; } + | stepblk { $$ = $1; } + | plotdecl + { + $$ = new ast::PlotBlock($1); + } + | neuronblk { $$ = $1; } + | unitblk { $$ = $1; } + | constblk { $$ = $1; } + ; + + +parmblk : PARAMETER "{" parmbody"}" + { + $$ = new ast::ParamBlock($3); + } + ; + + +parmbody : { + $$ = ast::ParamAssignList(); + } + | parmbody parmasgn + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +parmasgn : NAME "=" number units limits + { + $$ = new ast::ParamAssign($1, $3, $4, $5); + } + | NAME units limits + { + $$ = new ast::ParamAssign($1, NULL, $2, $3); + } + | NAME "[" integer "]" units limits + { + $$ = new ast::ParamAssign(new ast::IndexedName($1, $3), NULL, $5, $6); + } + | error + { + error(scanner.loc, "parmasgn"); + } + ; + + +units : { $$ = nullptr; } + | unit { $$ = $1; } + ; + + +unit : "(" { scanner.scan_unit(); } ")" + { + $$ = new ast::Unit(scanner.get_unit()); + } + ; + + +uniton : UNITSON + { + $$ = new ast::UnitState(ast::UNIT_ON); + } + | UNITSOFF + { + $$ = new ast::UnitState(ast::UNIT_OFF); + } + ; + + +limits : { $$ = nullptr; } + | LT real "," real GT + { + $$ = new ast::Limits($2, $4); + } + ; + + +stepblk : STEPPED "{" stepbdy "}" + { + $$ = new ast::StepBlock($3); + } + ; + + +stepbdy : { $$ = ast::SteppedList(); } + | stepbdy stepped + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +stepped : NAME "=" numlist units + { + $$ = new ast::Stepped($1, $3, $4); + } + ; + + +numlist : number "," number + { + $$ = ast::NumberList(); + $$.push_back(std::shared_ptr($1)); + $$.push_back(std::shared_ptr($3)); + } + | numlist "," number + { + $1.push_back(std::shared_ptr($3)); + $$ = $1; + } + ; + + +name : Name { $$ = $1; } + | PRIME { $$ = $1; } + ; + + +number : NUMBER { $$ = $1; } + | "-" NUMBER + { + $2->negate(); + $$ = $2; + } + ; + + +NUMBER : integer { $$ = $1; } + | REAL { $$ = $1; } + ; + + +integer : INTEGER { $$ = $1; } + | DEFINEDVAR { $$ = $1; } + ; + + +real : REAL { $$ = $1; } + | integer + { + $$ = new ast::Double(double($1->eval())); + delete($1); + } + ; + + +indepblk : INDEPENDENT "{" indepbody "}" + { + $$ = new ast::IndependentBlock($3); + } + ; + + +indepbody : { + $$ = ast::IndependentDefList(); + } + | indepbody indepdef + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + | indepbody SWEEP indepdef + { + $1.push_back(std::shared_ptr($3)); + $3->sweep = std::shared_ptr(new ast::Boolean(1)); + $$ = $1; + } + ; + + +indepdef : NAME FROM number TO number withby integer opstart units + { + $$ = new ast::IndependentDef(NULL, $1, $3, $5, $7, $8, $9); + } + | error + { + error(scanner.loc, "indepdef"); + } + ; + + +withby : WITH + ; + + +depblk : DEPENDENT "{" depbody"}" + { + $$ = new ast::DependentBlock($3); + } + ; + + +depbody : { + $$ = ast::DependentDefList(); + } + | depbody depdef + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +depdef : name opstart units abstol + { + $$ = new ast::DependentDef($1, NULL, NULL, NULL, $2, $3, $4); + } + | name "[" integer "]" opstart units abstol + { + $$ = new ast::DependentDef($1, $3, NULL, NULL, $5, $6, $7); + } + | name FROM number TO number opstart units abstol + { + $$ = new ast::DependentDef($1, NULL, $3, $5, $6, $7, $8); + } + | name "[" integer "]" FROM number TO number opstart units abstol + { + $$ = new ast::DependentDef($1, $3, $6, $8, $9, $10, $11); + } + | error + { + error(scanner.loc, "depdef"); + } + ; + + +opstart : { $$ = nullptr; } + | START1 number { $$ = $2; } + ; + + +abstol : { $$ = nullptr; } + | LT real GT { $$ = $2; } + ; + + +stateblk : STATE "{" depbody "}" + { + $$ = new ast::StateBlock($3); + } + ; + + +plotdecl : PLOT pvlist VS name optindex + { + $$ = new ast::PlotDeclaration($2, new ast::PlotVariable($4,$5)); + } + | PLOT error + { + error(scanner.loc, "plotdecl"); + } + ; + + +pvlist : name optindex + { + $$ = ast::PlotVariableList(); + auto variable = new ast::PlotVariable($1, $2); + $$.push_back(std::shared_ptr(variable)); + } + | pvlist "," name optindex + { + $$ = $1; + auto variable = new ast::PlotVariable($3, $4); + $$.push_back(std::shared_ptr(variable)); + } + ; + + +optindex : { $$ = nullptr; } + | "[" INTEGER "]" { $$ = $2; } + ; + + +proc : initblk { $$ = $1; } + | derivblk { $$ = $1; } + | brkptblk { $$ = $1; } + | linblk { $$ = $1; } + | nonlinblk { $$ = $1; } + | funcblk { $$ = $1; } + | procedblk { $$ = $1; } + | netrecblk { $$ = $1; } + | terminalblk { $$ = $1; } + | discretblk { $$ = $1; } + | partialblk { $$ = $1; } + | kineticblk { $$ = $1; } + | constructblk { $$ = $1; } + | destructblk { $$ = $1; } + | functableblk { $$ = $1; } + | BEFORE bablk + { + $$ = new ast::BeforeBlock($2); + } + | AFTER bablk + { + $$ = new ast::AfterBlock($2); + } + ; + + +initblk : INITIAL1 stmtlist "}" + { + $$ = new ast::InitialBlock($2); + } + ; + + +constructblk : CONSTRUCTOR stmtlist "}" + { + $$ = new ast::ConstructorBlock($2); + } + ; + + +destructblk : DESTRUCTOR stmtlist "}" + { + $$ = new ast::DestructorBlock($2); + } + ; + + +stmtlist : "{" stmtlist1 + { + $$ = new ast::StatementBlock($2); + $$->setToken($1); + } + | "{" locallist stmtlist1 + { + $3.insert($3.begin(), std::shared_ptr($2)); + $$ = new ast::StatementBlock($3); + $$->setToken($1); + } + ; + + +conducthint : CONDUCTANCE Name + { + $$ = new ast::ConductanceHint($2, NULL); + } + | CONDUCTANCE Name USEION NAME + { + $$ = new ast::ConductanceHint($2, $4); + } + ; + + +locallist : LOCAL locallist1 + { + $$ = new ast::LocalListStatement($2); + } + | LOCAL error + { + error(scanner.loc, "locallist"); + } + ; + + +locallist1 : NAME locoptarray + { + $$ = ast::LocalVariableList(); + if($2) { + auto variable = new ast::LocalVariable(new ast::IndexedName($1, $2)); + $$.push_back(std::shared_ptr(variable)); + } else { + auto variable = new ast::LocalVariable($1); + $$.push_back(std::shared_ptr(variable)); + } + } + | locallist1 "," NAME locoptarray + { + if($4) { + auto variable = new ast::LocalVariable(new ast::IndexedName($3, $4)); + $1.push_back(std::shared_ptr(variable)); + } else { + auto variable = new ast::LocalVariable($3); + $1.push_back(std::shared_ptr(variable)); + } + $$ = $1; + } + ; + + +locoptarray : { $$ = nullptr; } + | "[" integer "]" { $$ = $2; } + ; + + +stmtlist1 : { + $$ = ast::StatementList(); + } + | stmtlist1 ostmt + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + | stmtlist1 astmt + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +ostmt : fromstmt { $$ = $1; } + | forallstmt { $$ = $1; } + | whilestmt { $$ = $1; } + | ifstmt { $$ = $1; } + | stmtlist "}" + { + $$ = new ast::ExpressionStatement($1); + } + | solveblk + { + $$ = new ast::ExpressionStatement($1); + } + | conducthint { $$ = $1; } + | VERBATIM + { auto text = parse_with_verbatim_parser($1); + $$ = new ast::Verbatim(new ast::String(text)); + } + | COMMENT + { auto text = parse_with_verbatim_parser($1); + $$ = new ast::Comment(new ast::String(text)); + } + | sens { $$ = $1; } + | compart { $$ = $1; } + | ldifus { $$ = $1; } + | conserve { $$ = $1; } + | lagstmt { $$ = $1; } + | queuestmt { $$ = $1; } + | RESET + { + $$ = new ast::Reset(); + } + | matchblk + { + $$ = new ast::ExpressionStatement($1); + } + | pareqn { $$ = $1; } + | tablestmt { $$ = $1; } + | uniton { $$ = $1; } + | initstmt { $$ = $1; } + | watchstmt { $$ = $1; } + | fornetcon + { + $$ = new ast::ExpressionStatement($1); + } + | NRNMUTEXLOCK + { + $$ = new ast::MutexLock(); + } + | NRNMUTEXUNLOCK + { + $$ = new ast::MutexUnlock(); + } + | error + { + error(scanner.loc, "ostmt"); + } + ; + + +astmt : asgn + { + $$ = new ast::ExpressionStatement($1); + } + | PROTECT asgn + { + $$ = new ast::ProtectStatement($2); + } + | reaction { $$ = $1; } + | funccall + { + $$ = new ast::ExpressionStatement($1); + } + ; + + +asgn : varname "=" expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ASSIGN), $3); + } + | nonlineqn expr "=" expr + { + $$ = new ast::NonLinEuation($2, $4); + } + | lineqn leftlinexpr "=" linexpr + { + $$ = new ast::LinEquation($2, $4); + } + ; + + +varname : name + { + $$ = new ast::VarName($1, NULL); + } + | name "[" intexpr "]" + { + $$ = new ast::VarName(new ast::IndexedName($1, $3), NULL); + } + | NAME "@" integer + { + $$ = new ast::VarName($1, $3); + } + | NAME "@" integer "[" intexpr "]" + { + $$ = new ast::VarName(new ast::IndexedName($1, $5), $3); + } + ; + + +intexpr : Name { $$ = $1; } + | integer { $$ = $1; } + | "(" intexpr ")" { $$ = $2; } + | intexpr "+" intexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + } + | intexpr "-" intexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + } + | intexpr "*" intexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + } + | intexpr "/" intexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + } + | error + { + + } + ; + + +expr : varname { $$ = $1; } + | real units + { + if($2) + $$ = new ast::DoubleUnit($1, $2); + else + $$ = $1; + } + | funccall { $$ = $1; } + | "(" expr ")" { $$ = $2; } + | expr "+" expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + } + | expr "-" expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + } + | expr "*" expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + } + | expr "/" expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + } + | expr "^" expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); + } + | expr OR expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_OR), $3); + } + | expr AND expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_AND), $3); + } + | expr GT expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_GREATER), $3); + } + | expr LT expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_LESS), $3); + } + | expr GE expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_GREATER_EQUAL), $3); + } + | expr LE expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_LESS_EQUAL), $3); + } + | expr EQ expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_EXACT_EQUAL), $3); + } + | expr NE expr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_NOT_EQUAL), $3); + } + | NOT expr + { + $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NOT), $2); + } + | "-" expr %prec UNARYMINUS + { + $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NEGATION), $2); + } + | error + { + error(scanner.loc, "expr"); + } + ; + + /** \todo Add extra rules for better error reporting : + | "(" expr { yyerror("Unbalanced left parenthesis followed by valid expressions"); } + | "(" error { yyerror("Unbalanced left parenthesis followed by non parseable"); } + | expr ")" { yyerror("Unbalanced right parenthesis"); } + */ + + +nonlineqn : NONLIN1 + ; + + +lineqn : LIN1 + ; + + +leftlinexpr : linexpr { $$ = $1; } + ; + + +linexpr : primary { $$ = $1; } + | "-" primary + { + $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NEGATION), $2); + } + | linexpr "+" primary + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + } + | linexpr "-" primary + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + } + ; + + +primary : term { $$ = $1; } + | primary "*" term + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + } + | primary "/" term + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + } + ; + + +term : varname { $$ = $1; } + | real { $$ = $1; } + | funccall { $$ = $1; } + | "(" expr ")" { $$ = $2; } + | error + { + error(scanner.loc, "term"); + } + ; + + +funccall : NAME "(" exprlist ")" + { + $$ = new ast::FunctionCall($1, $3); + } + ; + + +exprlist : { + $$ = ast::ExpressionList(); + } + | expr + { + $$ = ast::ExpressionList(); + $$.push_back(std::shared_ptr($1)); + } + | STRING + { + $$ = ast::ExpressionList(); + $$.push_back(std::shared_ptr($1)); + } + | exprlist "," expr + { + $1.push_back(std::shared_ptr($3)); + $$ = $1; + } + | exprlist "," STRING + { + $1.push_back(std::shared_ptr($3)); + $$ = $1; + } + ; + + +fromstmt : FROM NAME "=" intexpr TO intexpr opinc stmtlist "}" + { + $$ = new ast::FromStatement($2, $4, $6, $7, $8); + } + | FROM error + { + error(scanner.loc, "fromstmt"); + } + ; + + +opinc : { $$ = nullptr; } + | BY intexpr { $$ = $2; } + ; + + +forallstmt : FORALL1 NAME stmtlist "}" + { + $$ = new ast::ForAllStatement($2, $3); + } + | FORALL1 error + { + error(scanner.loc, "forallstmt"); + } + ; + + +whilestmt : WHILE "(" expr ")" stmtlist "}" + { + $$ = new ast::WhileStatement($3, $5); + } + ; + + +ifstmt : IF "(" expr ")" stmtlist "}" optelseif optelse + { + $$ = new ast::IfStatement($3, $5, $7, $8); + } + ; + + +optelseif : { + $$ = ast::ElseIfStatementList(); + } + | optelseif ELSE IF "(" expr ")" stmtlist "}" + { + auto statement = new ast::ElseIfStatement($5, $7); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + ; + + +optelse : { $$ = nullptr; } + | ELSE stmtlist "}" + { + $$ = new ast::ElseStatement($2); + } + ; + + +derivblk : DERIVATIVE NAME stmtlist "}" + { + $$ = new ast::DerivativeBlock($2, $3); + $$->setToken($1); + } + ; + + +linblk : LINEAR NAME solvefor stmtlist "}" + { + $$ = new ast::LinearBlock($2, $3, $4); + $$->setToken($1); + } + ; + + +nonlinblk : NONLINEAR NAME solvefor stmtlist "}" + { + $$ = new ast::NonLinearBlock($2, $3, $4); + $$->setToken($1); + } + ; + + +discretblk : DISCRETE NAME stmtlist "}" + { + $$ = new ast::DiscreteBlock($2, $3); + $$->setToken($1); + } + ; + + +partialblk : PARTIAL NAME stmtlist "}" + { + $$ = new ast::PartialBlock($2, $3); + $$->setToken($1); + } + | PARTIAL error + { + error(scanner.loc, "partialblk"); + } + ; + + +pareqn : "~" PRIME "=" NAME "*" DEL2 "(" NAME ")" "+" NAME + { + $$ = new ast::PartialBoundary(NULL, $2, NULL, NULL, $4, $6, $8, $11); + } + | "~" DEL NAME "[" firstlast "]" "=" expr + { + $$ = new ast::PartialBoundary($2, $3, $5, $8, NULL, NULL, NULL, NULL); + } + | "~" NAME "[" firstlast "]" "=" expr + { + $$ = new ast::PartialBoundary(NULL, $2, $4, $7, NULL, NULL, NULL, NULL); + } + ; + + +firstlast : FIRST + { + $$ = new ast::FirstLastTypeIndex(ast::PEQ_FIRST); + } + | LAST + { + $$ = new ast::FirstLastTypeIndex(ast::PEQ_LAST); + } + ; + + +functableblk : FUNCTION_TABLE NAME "(" arglist ")" units + { + $$ = new ast::FunctionTableBlock($2, $4, $6); + $$->setToken($1); + } + ; + + +funcblk : FUNCTION1 NAME "(" arglist ")" units stmtlist "}" + { + $$ = new ast::FunctionBlock($2, $4, $6, $7); + $$->setToken($1); + } + ; + + +arglist : { + $$ = ast::ArgumentList(); + } + | arglist1 { $$ = $1; } + ; + + +arglist1 : name units + { + $$ = ast::ArgumentList(); + $$.push_back(std::shared_ptr(new ast::Argument($1, $2))); + } + | arglist1 "," name units + { + $1.push_back(std::shared_ptr(new ast::Argument($3, $4))); + $$ = $1; + } + ; + + +procedblk : PROCEDURE NAME "(" arglist ")" units stmtlist "}" + { + $$ = new ast::ProcedureBlock($2, $4, $6, $7); $$->setToken($1); + } + ; + + +netrecblk : NETRECEIVE "(" arglist ")" stmtlist "}" + { + $$ = new ast::NetReceiveBlock($3, $5); + } + | NETRECEIVE error + { + error(scanner.loc, "netrecblk"); + } + ; + + +initstmt : INITIAL1 stmtlist "}" + { + $$ = new ast::ExpressionStatement(new ast::InitialBlock($2)); + } + ; + + +solveblk : SOLVE NAME ifsolerr + { + $$ = new ast::SolveBlock($2, NULL, $3); + } + | SOLVE NAME USING METHOD ifsolerr + { + $$ = new ast::SolveBlock($2, $4, $5); + } + | SOLVE error + { + error(scanner.loc, "solveblk"); + } + ; + + +ifsolerr : { $$ = nullptr; } + | IFERROR stmtlist "}" { $$ = $2; } + ; + + +solvefor : { $$ = ast::NameList(); } + | solvefor1 { $$ = $1; } + ; + + +solvefor1 : SOLVEFOR NAME + { + $$ = ast::NameList(); + $$.push_back(std::shared_ptr($2)); + } + | solvefor1 "," NAME + { + $1.push_back(std::shared_ptr($3)); + $$ = $1; + } + | SOLVEFOR error + { + error(scanner.loc, "solvefor1"); + } + ; + + +brkptblk : BREAKPOINT stmtlist "}" + { + $$ = new ast::BreakpointBlock($2); + } + ; + + +terminalblk : TERMINAL stmtlist "}" + { + $$ = new ast::TerminalBlock($2); + } + ; + + +bablk : BREAKPOINT stmtlist "}" + { + $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_BREAKPOINT), $2); + } + | SOLVE stmtlist "}" + { + $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_SOLVE), $2); + } + | INITIAL1 stmtlist "}" + { + $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_INITIAL), $2); + } + | STEP stmtlist "}" + { + $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_STEP), $2); + } + | error + { + error(scanner.loc, "bablk"); + } + ; + + +watchstmt : WATCH watch1 + { + $$ = new ast::WatchStatement(ast::WatchList()); + $$->addWatch($2); + } + | watchstmt "," watch1 + { + $1->addWatch($3); $$ = $1; + } + | WATCH error + { + error(scanner.loc, "watchstmt"); + } + ; + + +watch1 : "(" aexpr watchdir aexpr ")" real + { + $$ = new ast::Watch( new ast::BinaryExpression($2, $3, $4), $6); + } + ; + + +watchdir : GT + { + $$ = ast::BinaryOperator(ast::BOP_GREATER); + } + | LT + { + $$ = ast::BinaryOperator(ast::BOP_LESS); + } + ; + + +fornetcon : FOR_NETCONS "(" arglist ")" stmtlist "}" + { + $$ = new ast::ForNetcon($3, $5); + } + | FOR_NETCONS error + { + error(scanner.loc, "fornetcon"); + } + ; + + +aexpr : varname { $$ = $1; } + | real units + { + $$ = new ast::DoubleUnit($1, $2); + } + | funccall { $$ = $1; } + | "(" aexpr ")" { $$ = $2; } + | aexpr "+" aexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + } + | aexpr "-" aexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + } + | aexpr "*" aexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + } + | aexpr "/" aexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + } + | aexpr "^" aexpr + { + $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); + } + | "-" aexpr %prec UNARYMINUS + { + $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NEGATION), $2); + } + | error + { + error(scanner.loc, "aexpr"); + } + ; + + +sens : SENS senslist + { + $$ = new ast::Sens($2); + } + | SENS error + { + error(scanner.loc, "sens"); + } + ; + + +senslist : varname + { + $$ = ast::VarNameList(); + $$.push_back(std::shared_ptr($1)); + } + | senslist "," varname + { + $1.push_back(std::shared_ptr($3)); + $$ = $1; + } + ; + + +conserve : CONSERVE react "=" expr + { + $$ = new ast::Conserve($2, $4); + } + | CONSERVE error + { + + } + ; + + +compart : COMPARTMENT NAME "," expr "{" namelist "}" + { + $$ = new ast::Compartment($2, $4, $6); + } + | COMPARTMENT expr "{" namelist "}" + { + $$ = new ast::Compartment(NULL, $2, $4); + } + ; + + +ldifus : LONGDIFUS NAME "," expr "{" namelist "}" + { + $$ = new ast::LDifuse($2, $4, $6); + } + | LONGDIFUS expr "{" namelist "}" + { + $$ = new ast::LDifuse(NULL, $2, $4); + } + ; + + +namelist : NAME + { + $$ = ast::NameList(); + $$.push_back(std::shared_ptr($1)); + } + | namelist NAME + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +kineticblk : KINETIC NAME solvefor stmtlist "}" + { + $$ = new ast::KineticBlock($2, $3, $4); + $$->setToken($1); + } + ; + + +reaction : REACTION react REACT1 react "(" expr "," expr ")" + { + auto op = ast::ReactionOperator(ast::LTMINUSGT); + $$ = new ast::ReactionStatement($2, op, $4, $6, $8); + } + | REACTION react LT LT "(" expr ")" + { + auto op = ast::ReactionOperator(ast::LTLT); + $$ = new ast::ReactionStatement($2, op, NULL, $6, NULL); + } + | REACTION react "-" GT "(" expr ")" + { + auto op = ast::ReactionOperator(ast::MINUSGT); + $$ = new ast::ReactionStatement($2, op, NULL, $6, NULL); + } + | REACTION error + { + /** \todo Need to revisit reaction implementation */ + } + ; + + +react : varname { $$ = $1; } + | integer varname + { + $$ = new ast::ReactVarName($1, $2); + } + | react "+" varname + { + auto op = ast::BinaryOperator(ast::BOP_ADDITION); + $$ = new ast::BinaryExpression($1, op, $3); + } + | react "+" integer varname + { + auto op = ast::BinaryOperator(ast::BOP_ADDITION); + auto variable = new ast::ReactVarName($3, $4); + $$ = new ast::BinaryExpression($1, op, variable); + } + ; + + +lagstmt : LAG name BY NAME + { + $$ = new ast::LagStatement($2, $4); + } + | LAG error + { + error(scanner.loc, "lagstmt"); + } + ; + + +queuestmt : PUTQ name + { + $$ = new ast::QueueStatement(new ast::QueueExpressionType(ast::PUT_QUEUE), $2); + } + | GETQ name + { + $$ = new ast::QueueStatement(new ast::QueueExpressionType(ast::GET_QUEUE), $2); + } + ; + + +matchblk : MATCH "{" matchlist "}" + { + $$ = new ast::MatchBlock($3); + } + ; + + +matchlist : match + { + $$ = ast::MatchList(); + $$.push_back(std::shared_ptr($1)); + } + | matchlist match + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +match : name + { + $$ = new ast::Match($1, NULL); + } + | matchname "(" expr ")" "=" expr + { + auto op = ast::BinaryOperator(ast::BOP_ASSIGN); + auto expr = new ast::BinaryExpression($3, op, $6); + $$ = new ast::Match($1, expr); + } + | error + { + error(scanner.loc, "match "); + } + ; + + +matchname : name { $$ = $1; } + | name "[" NAME "]" + { + $$ = new ast::IndexedName($1, $3); + } + ; + + +unitblk : UNITS "{" unitbody "}" + { + $$ = new ast::UnitBlock($3); + } + ; + + +unitbody : { + $$ = ast::ExpressionList(); + } + | unitbody unitdef + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + | unitbody factordef + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + ; + + +unitdef : unit "=" unit + { + $$ = new ast::UnitDef($1, $3); + } + | unit error + { + error(scanner.loc, "unitdef "); + } + ; + + +factordef : NAME "=" real unit + { + $$ = new ast::FactorDef($1, $3, $4, NULL, NULL); + $$->setToken(*($1->getToken())); + } + | NAME "=" unit unit + { + $$ = new ast::FactorDef($1, NULL, $3, NULL, $4); + $$->setToken(*($1->getToken())); + } + | NAME "=" unit "-" GT unit + { + $$ = new ast::FactorDef($1, NULL, $3, new ast::Boolean(1), $6); + $$->setToken(*($1->getToken())); + } + | error + { + + } + ; + + +constblk : CONSTANT "{" conststmt "}" + { + $$ = new ast::ConstantBlock($3); + } + ; + + +conststmt : { + $$ = ast::ConstantStatementList(); + } + | conststmt NAME "=" number units + { + auto statement = new ast::ConstantStatement($2, $4, $5); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + ; + + +tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER + { + $$ = new ast::TableStatement($2, $3, $5, $7, $9); + } + | TABLE error + { + error(scanner.loc, "tablestmt"); + } + ; + + +tablst : { $$ = ast::NameList(); } + | tablst1 { $$ = $1; } + ; + + +tablst1 : Name + { + $$ = ast::NameList(); + $$.push_back(std::shared_ptr($1)); + } + | tablst1 "," Name + { + $1.push_back(std::shared_ptr($3)); + $$ = $1; + } + ; + + +dependlst : { $$ = ast::NameList(); } + | DEPEND tablst1 { $$ = $2; } + ; + + +neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE + { + auto block = new ast::StatementBlock($3); + block->setToken($2); + $$ = new ast::NeuronBlock(block); + } + ; + + +nrnstmt : { $$ = ast::StatementList(); } + | nrnstmt SUFFIX NAME + { + auto statement = new ast::NrnSuffix($2, $3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt nrnuse + { + $1.push_back(std::shared_ptr($2)); + $$ = $1; + } + | nrnstmt NONSPECIFIC nrnonspeclist + { + auto statement = new ast::NrnNonspecific($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt ELECTRODE_CURRENT nrneclist + { + auto statement = new ast::NrnElctrodeCurrent($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt SECTION nrnseclist + { + auto statement = new ast::NrnSection($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt RANGE nrnrangelist + { + auto statement = new ast::NrnRange($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt GLOBAL nrnglobalist + { + auto statement = new ast::NrnGlobal($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt POINTER nrnptrlist + { + auto statement = new ast::NrnPointer($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt BBCOREPOINTER nrnbbptrlist + { + auto statement = new ast::NrnBbcorePtr($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt EXTERNAL nrnextlist + { + auto statement = new ast::NrnExternal($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + | nrnstmt THREADSAFE opthsafelist + { + auto statement = new ast::NrnThreadSafe($3); + $1.push_back(std::shared_ptr(statement)); + $$ = $1; + } + ; + + +nrnuse : USEION NAME READ nrnionrlist valence + { + $$ = new ast::NrnUseion($2, $4, ast::WriteIonVarList(), $5); + } + | USEION NAME WRITE nrnionwlist valence + { + $$ = new ast::NrnUseion($2, ast::ReadIonVarList(), $4, $5); + } + | USEION NAME READ nrnionrlist WRITE nrnionwlist valence + { + $$ = new ast::NrnUseion($2, $4, $6, $7); + } + | USEION error + { + error(scanner.loc, "nrnuse"); + } + ; + + +nrnionrlist : NAME + { + $$ = ast::ReadIonVarList(); + $$.push_back(std::shared_ptr(new ast::ReadIonVar($1))); + } + | nrnionrlist "," NAME + { + $1.push_back(std::shared_ptr(new ast::ReadIonVar($3))); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnionrlist"); + } + ; + + +nrnionwlist : NAME + { + $$ = ast::WriteIonVarList(); + $$.push_back(std::shared_ptr(new ast::WriteIonVar($1))); + } + | nrnionwlist "," NAME + { + $1.push_back(std::shared_ptr(new ast::WriteIonVar($3))); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnionwlist"); + } + ; + + +nrnonspeclist : NAME + { + $$ = ast::NonspeCurVarList(); + auto var = new ast::NonspeCurVar($1); + $$.push_back(std::shared_ptr(var)); + } + | nrnonspeclist "," NAME + { + auto var = new ast::NonspeCurVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnonspeclist"); + } + ; + + +nrneclist : NAME + { + $$ = ast::ElectrodeCurVarList(); + auto var = new ast::ElectrodeCurVar($1); + $$.push_back(std::shared_ptr(var)); + } + | nrneclist "," NAME + { + auto var = new ast::ElectrodeCurVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + | error + { + error(scanner.loc, "nrneclist"); + } + ; + + +nrnseclist : NAME + { + $$ = ast::SectionVarList(); + auto var = new ast::SectionVar($1); + $$.push_back(std::shared_ptr(var)); + } + | nrnseclist "," NAME + { + auto var = new ast::SectionVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnseclist"); + } + ; + + +nrnrangelist : NAME + { + $$ = ast::RangeVarList(); + $$.push_back(std::shared_ptr(new ast::RangeVar($1))); + } + | nrnrangelist "," NAME + { + $1.push_back(std::shared_ptr(new ast::RangeVar($3))); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnrangelist"); + } + ; + + +nrnglobalist : NAME + { + $$ = ast::GlobalVarList(); + $$.push_back(std::shared_ptr(new ast::GlobalVar($1))); + } + | nrnglobalist "," NAME + { + $1.push_back(std::shared_ptr(new ast::GlobalVar($3))); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnglobalist"); + } + ; + + +nrnptrlist : NAME + { + $$ = ast::PointerVarList(); + auto var = new ast::PointerVar($1); + $$.push_back(std::shared_ptr(var)); + } + | nrnptrlist "," NAME + { + auto var = new ast::PointerVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnptrlist"); + } + ; + + +nrnbbptrlist : NAME + { + $$ = ast::BbcorePointerVarList(); + auto var = new ast::BbcorePointerVar($1); + $$.push_back(std::shared_ptr(var)); + } + | nrnbbptrlist "," NAME + { + auto var = new ast::BbcorePointerVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnbbptrlist"); + } + ; + + +nrnextlist : NAME + { + $$ = ast::ExternVarList(); + auto var = new ast::ExternVar($1); + $$.push_back(std::shared_ptr(var)); + } + | nrnextlist "," NAME + { + auto var = new ast::ExternVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + | error + { + error(scanner.loc, "nrnextlist"); + } + ; + + +opthsafelist : { $$ = ast::ThreadsafeVarList(); } + | threadsafelist { $$ = $1; } + ; + + +threadsafelist : NAME + { + $$ = ast::ThreadsafeVarList(); + auto var = new ast::ThreadsafeVar($1); + $$.push_back(std::shared_ptr(var)); + } + | threadsafelist "," NAME + { + auto var = new ast::ThreadsafeVar($3); + $1.push_back(std::shared_ptr(var)); + $$ = $1; + } + ; + + +valence : { $$ = nullptr; } + | VALENCE real + { + $$ = new ast::Valence($1, $2); + } + | VALENCE "-" real + { + $3->negate(); + $$ = new ast::Valence($1, $3); + } + ; + +%% + + +/** Parse verbatim and commnet blocks : In the future we will use C parser to + * analyze verbatim blocks for better code generation. For GPU code generation + * we need to disable printf like statements. We could add new pragma + * annotations to enable certain optimizations. Idea of having separate parser + * is to support this type of analysis in separate parser. Currently we have + * "empty" Verbatim parser which scan and return same string. */ + +std::string parse_with_verbatim_parser(std::string str) { + auto is = new std::istringstream(str.c_str()); + + VerbatimContext extcontext(is); + Verbatim_parse(&extcontext); + + std::string ss(*(extcontext.result)); + + delete is; + return ss; +} + +/** Bison expects error handler for parser. + * \todo Need to implement error codes and driver should accumulate + * and report all errors. For now simply abort. + */ + +void nmodl::Parser::error(const location &loc , const std::string &message) { + std::stringstream ss; + ss << "NMODL Parser Error : " << message << " [Location : " << loc << "]"; + throw std::runtime_error(ss.str()); +} diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp new file mode 100644 index 0000000000..2e8c22d3cf --- /dev/null +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -0,0 +1,67 @@ +#include +#include + +#include "lexer/nmodl_lexer.hpp" +#include "parser/nmodl_driver.hpp" + +namespace nmodl { + + Driver::Driver(bool strace, bool ptrace) : trace_scanner(strace), trace_parser(ptrace) {} + + /// parse nmodl file provided as istream + bool Driver::parse_stream(std::istream& in) { + Lexer scanner(*this, &in); + Parser parser(scanner, *this); + + this->lexer = &scanner; + this->parser = &parser; + + scanner.set_debug(trace_scanner); + parser.set_debug_level(trace_parser); + return (parser.parse() == 0); + } + + //// parse nmodl file + bool Driver::parse_file(const std::string& filename) { + std::ifstream in(filename.c_str()); + streamname = filename; + + if (!in.good()) { + return false; + } + return parse_stream(in); + } + + /// parser nmodl provided as string (used for testing) + bool Driver::parse_string(const std::string& input) { + std::istringstream iss(input); + return parse_stream(iss); + } + + void Driver::error(const std::string& m, const class location& l) { + std::cerr << l << " : " << m << std::endl; + } + + void Driver::error(const std::string& m) { + std::cerr << m << std::endl; + } + + /// add macro definition and it's value (DEFINE keyword of nmodl) + void Driver::add_defined_var(const std::string& name, int value) { + defined_var[name] = value; + } + + /// check if particular text is defined as macro + bool Driver::is_defined_var(const std::string& name) { + return !(defined_var.find(name) == defined_var.end()); + } + + /// return variable's value defined as macro (always an integer) + int Driver::get_defined_var_value(const std::string& name) { + if (is_defined_var(name)) { + return defined_var[name]; + } + throw std::runtime_error("Trying to get undefined macro / define :" + name); + } + +} // namespace nmodl diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp new file mode 100644 index 0000000000..5199308bd1 --- /dev/null +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include +#include + +#include "ast/ast.hpp" + +/** The nmodl namespace encapsulates everything related to nmodl parsing + * which includes lexer, parser, driver, keywords, token mapping etc. */ +namespace nmodl { + + /** + * \class Driver + * \brief Class that binds all pieces together for parsing nmodl file + * + * Driver class bind components required for lexing, parsing and ast + * generation from nmodl file. We create an instance of lexer, parser + * and provides different methods to parse from file, stream or string. + * The scanner also gets reference to driver object for two purposes : + * scanner store/query the macro definitions into/from driver class + * and erros can be propogated back to driver (not implemented yet). + * Parser class also gets a reference to driver class as a parameter. + * Parsing actions generate ast and it's pointer is stored in driver + * class. + * + * \todo lexer, parser and ast member variables are used inside lexer/ + * parser instaces. The local instaces are created inside parse_stream + * and hence the pointers are no longer valid except ast. Need better + * way to handle this. + * + * \todo stream name is not used as it will need better support as + * location object used in scanner takes string pointer which could + * be invalid when we copy location object. + */ + + /// flex generated scanner class (extends base lexer class of flex) + class Lexer; + + /// parser class generated by bison + class Parser; + + class Driver { + private: + /// all macro defined in the mod file + std::map defined_var; + + /// enable debug output in the flex scanner + bool trace_scanner = false; + + /// enable debug output in the bison parser + bool trace_parser = false; + + public: + /// pointer to the lexer instance being used + Lexer* lexer = nullptr; + + /// pointer to the parser instance being used + Parser* parser = nullptr; + + /// root of the ast + std::shared_ptr astRoot = nullptr; + + /// file or input stream name (used by scanner for position), see todo + std::string streamname; + + Driver(){}; + Driver(bool strace, bool ptrace); + + void error(const std::string& m, const class location& l); + void error(const std::string& m); + + void add_defined_var(const std::string& name, int value); + bool is_defined_var(const std::string& name); + int get_defined_var_value(const std::string& name); + + bool parse_stream(std::istream& in); + + bool parse_string(const std::string& input); + bool parse_file(const std::string& filename); + + }; + +} // namespace nmodl diff --git a/src/nmodl/parser/verbatim.y b/src/nmodl/parser/verbatim.y new file mode 100644 index 0000000000..b1eb1d1b13 --- /dev/null +++ b/src/nmodl/parser/verbatim.y @@ -0,0 +1,91 @@ +/* Bison specification for NMODL Extensions which includes + * VERBATIM and COMMENT blocks + */ + +%{ + #include + #include + #include + #include + #include "parser/verbatim_context.hpp" +%} + +/* print out verbose error instead of just message 'syntax error' */ +%error-verbose + +/* make a reentrant parser */ +%pure-parser + +/* parser prefix */ +%name-prefix "Verbatim_" + +/* enable location tracking */ +%locations + +/* generate header file */ +%defines + +/* yyparse() takes an extra argument context */ +%parse-param {VerbatimContext* context} + +/* reentrant lexer needs an extra argument for yylex() */ +%lex-param {void * scanner} + +/* token types */ +%union +{ + char str; + std::string *string_ptr; +} + +/* define our terminal symbols (tokens) + */ +%token CHAR +%token NEWLINE +%token VERBATIM +%token COMMENT +%token ENDVERBATIM +%token ENDCOMMENT + +%type top +%type charlist +%type verbatimblock +%type commentblock + +%{ + /* a macro that extracts the scanner state from the parser state for yylex */ + #define scanner context->scanner + + extern int yylex(YYSTYPE*, YYLTYPE*, void*); + extern int yyparse(VerbatimContext*); + extern void yyerror(YYLTYPE*, VerbatimContext*, const char *); +%} + + +/* start symbol is named "top" */ +%start top + +%% + + +top : verbatimblock { $$ = $1; context->result = $1; } + | commentblock { $$ = $1; context->result = $1; } + | error { printf("\n _ERROR_"); } + ; + +verbatimblock : VERBATIM charlist ENDVERBATIM { $$ = $2; } + +commentblock : COMMENT charlist ENDCOMMENT { $$ = $2; } + + +charlist : { $$ = new std::string(""); } + | charlist CHAR { *($1) += $2; $$ = $1; } + | charlist NEWLINE { *($1) += $2; $$ = $1; } + ; + +%% + +void yyerror(YYLTYPE* locp, VerbatimContext* context, const char *s) { + std::printf("\n Error in verbatim parser : %s \n", s); + std::exit(1); +} diff --git a/src/nmodl/parser/verbatim_context.hpp b/src/nmodl/parser/verbatim_context.hpp new file mode 100644 index 0000000000..800a16a4ba --- /dev/null +++ b/src/nmodl/parser/verbatim_context.hpp @@ -0,0 +1,36 @@ +#ifndef _NMODL_VERBATIM_CONTEXT_ +#define _NMODL_VERBATIM_CONTEXT_ + +#include + +class VerbatimContext { + public: + void* scanner; + std::istream* is; + + std::string* result; + + VerbatimContext(std::istream* is = &std::cin) { + scanner = NULL; + result = NULL; + init_scanner(); + this->is = is; + } + + virtual ~VerbatimContext() { + destroy_scanner(); + + if (result) { + delete result; + } + } + + protected: + /* defined in nmodlext.l */ + void init_scanner(); + void destroy_scanner(); +}; + +int Verbatim_parse(VerbatimContext*); + +#endif // _NMODL_VERBATIM_CONTEXT_ From c33d8399c9c51f674b338724fa8d2db3032efa1c Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Mon, 30 Oct 2017 08:14:21 +0100 Subject: [PATCH 006/871] Using refactored implementation of AST classes. All AST classes now use raw pointers from parser but then construct shared pointers in constructor. We will integrate language definition based on YAML and associated python scripts for the same. Change-Id: I337f9ea977a0933ea891009c08d4903ab5eb0f77 NMODL Repo SHA: BlueBrain/nmodl@01664d8d359cc952023721c02ef04fbd56f23123 --- src/nmodl/ast/ast.cpp | 7771 ++++++----------- src/nmodl/ast/ast.hpp | 5861 ++++++------- src/nmodl/ast/{astutils.hpp => ast_utils.hpp} | 9 +- 3 files changed, 5410 insertions(+), 8231 deletions(-) rename src/nmodl/ast/{astutils.hpp => ast_utils.hpp} (96%) diff --git a/src/nmodl/ast/ast.cpp b/src/nmodl/ast/ast.cpp index 2cb8c3bade..c88a4f7cb7 100644 --- a/src/nmodl/ast/ast.cpp +++ b/src/nmodl/ast/ast.cpp @@ -1,4948 +1,2829 @@ #include "ast/ast.hpp" -/* for node constructors, all children are taken as - * parameters, and must be passed in. Optional children - * may be NULL pointers. List children are pointers to - * std::vectors of the appropriate type (pointer to some - * node type) - */ namespace ast { - - /* visit Children method for Expression ast node */ - void ExpressionNode::visitChildren(Visitor* v) { - } - - /* destructor for Expression ast node */ - ExpressionNode::~ExpressionNode() { - } - - /* visit Children method for Statement ast node */ - void StatementNode::visitChildren(Visitor* v) { - } - - /* destructor for Statement ast node */ - StatementNode::~StatementNode() { - } - - /* visit Children method for Identifier ast node */ - void IdentifierNode::visitChildren(Visitor* v) { - } - - /* destructor for Identifier ast node */ - IdentifierNode::~IdentifierNode() { - } - - /* visit Children method for Block ast node */ - void BlockNode::visitChildren(Visitor* v) { - } - - /* destructor for Block ast node */ - BlockNode::~BlockNode() { - } - - /* visit Children method for Number ast node */ - void NumberNode::visitChildren(Visitor* v) { - } - - /* destructor for Number ast node */ - NumberNode::~NumberNode() { - } - - /* visit Children method for String ast node */ - void StringNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for String ast node */ - StringNode::StringNode(std::string value) { - this->value = value; - this->token = NULL; - } - - /* copy constructor for String ast node */ - StringNode::StringNode(const StringNode& obj) { - this->value = obj.value; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - } - - /* destructor for String ast node */ - StringNode::~StringNode() { - if(token) - delete token; - } - - /* visit Children method for Integer ast node */ - void IntegerNode::visitChildren(Visitor* v) { - /* no children */ - if (this->macroname) { - this->macroname->accept(v); - } - - } - - /* constructor for Integer ast node */ - IntegerNode::IntegerNode(int value, NameNode * macroname) { - this->value = value; - this->macroname = macroname; - this->token = NULL; - } - - /* copy constructor for Integer ast node */ - IntegerNode::IntegerNode(const IntegerNode& obj) { - this->value = obj.value; - if(obj.macroname) - this->macroname = obj.macroname->clone(); - else - this->macroname = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - } - - /* destructor for Integer ast node */ - IntegerNode::~IntegerNode() { - if(token) - delete token; - } - - /* visit Children method for Float ast node */ - void FloatNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for Float ast node */ - FloatNode::FloatNode(float value) { - this->value = value; - } - - /* copy constructor for Float ast node */ - FloatNode::FloatNode(const FloatNode& obj) { - this->value = obj.value; - } - - /* destructor for Float ast node */ - FloatNode::~FloatNode() { - } - - /* visit Children method for Double ast node */ - void DoubleNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for Double ast node */ - DoubleNode::DoubleNode(double value) { - this->value = value; - this->token = NULL; - } - - /* copy constructor for Double ast node */ - DoubleNode::DoubleNode(const DoubleNode& obj) { - this->value = obj.value; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - } - - /* destructor for Double ast node */ - DoubleNode::~DoubleNode() { - if(token) - delete token; - } - - /* visit Children method for Boolean ast node */ - void BooleanNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for Boolean ast node */ - BooleanNode::BooleanNode(int value) { - this->value = value; - } - - /* copy constructor for Boolean ast node */ - BooleanNode::BooleanNode(const BooleanNode& obj) { - this->value = obj.value; - } - - /* destructor for Boolean ast node */ - BooleanNode::~BooleanNode() { - } - - /* visit Children method for Name ast node */ - void NameNode::visitChildren(Visitor* v) { - value->accept(v); - } - - /* constructor for Name ast node */ - NameNode::NameNode(StringNode * value) { - this->value = value; - this->token = NULL; - } - - /* copy constructor for Name ast node */ - NameNode::NameNode(const NameNode& obj) { - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - } - - /* destructor for Name ast node */ - NameNode::~NameNode() { - if(value) - delete value; - if(token) - delete token; - } - - /* visit Children method for PrimeName ast node */ - void PrimeNameNode::visitChildren(Visitor* v) { - value->accept(v); - order->accept(v); - } - - /* constructor for PrimeName ast node */ - PrimeNameNode::PrimeNameNode(StringNode * value, IntegerNode * order) { - this->value = value; - this->order = order; - this->token = NULL; - } - - /* copy constructor for PrimeName ast node */ - PrimeNameNode::PrimeNameNode(const PrimeNameNode& obj) { - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - if(obj.order) - this->order = obj.order->clone(); - else - this->order = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - } - - /* destructor for PrimeName ast node */ - PrimeNameNode::~PrimeNameNode() { - if(value) - delete value; - if(order) - delete order; - if(token) - delete token; - } - - /* visit Children method for VarName ast node */ - void VarNameNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->at_index) { - this->at_index->accept(v); - } - - } - - /* constructor for VarName ast node */ - VarNameNode::VarNameNode(IdentifierNode * name, IntegerNode * at_index) { - this->name = name; - this->at_index = at_index; - } - - /* copy constructor for VarName ast node */ - VarNameNode::VarNameNode(const VarNameNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.at_index) - this->at_index = obj.at_index->clone(); - else - this->at_index = NULL; - } - - /* destructor for VarName ast node */ - VarNameNode::~VarNameNode() { - if(name) - delete name; - if(at_index) - delete at_index; - } - - /* visit Children method for IndexedName ast node */ - void IndexedNameNode::visitChildren(Visitor* v) { - name->accept(v); - index->accept(v); - } - - /* constructor for IndexedName ast node */ - IndexedNameNode::IndexedNameNode(IdentifierNode * name, ExpressionNode * index) { - this->name = name; - this->index = index; - } - - /* copy constructor for IndexedName ast node */ - IndexedNameNode::IndexedNameNode(const IndexedNameNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.index) - this->index = obj.index->clone(); - else - this->index = NULL; - } - - /* destructor for IndexedName ast node */ - IndexedNameNode::~IndexedNameNode() { - if(name) - delete name; - if(index) - delete index; - } - - /* visit Children method for Unit ast node */ - void UnitNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for Unit ast node */ - UnitNode::UnitNode(StringNode * name) { - this->name = name; - } - - /* copy constructor for Unit ast node */ - UnitNode::UnitNode(const UnitNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for Unit ast node */ - UnitNode::~UnitNode() { - if(name) - delete name; - } - - /* visit Children method for UnitState ast node */ - void UnitStateNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for UnitState ast node */ - UnitStateNode::UnitStateNode(UnitStateType value) { - this->value = value; - } - - /* copy constructor for UnitState ast node */ - UnitStateNode::UnitStateNode(const UnitStateNode& obj) { - this->value = obj.value; - } - - /* destructor for UnitState ast node */ - UnitStateNode::~UnitStateNode() { - } - - /* visit Children method for DoubleUnit ast node */ - void DoubleUnitNode::visitChildren(Visitor* v) { - values->accept(v); - if (this->unit) { - this->unit->accept(v); - } - - } - - /* constructor for DoubleUnit ast node */ - DoubleUnitNode::DoubleUnitNode(DoubleNode * values, UnitNode * unit) { - this->values = values; - this->unit = unit; - } - - /* copy constructor for DoubleUnit ast node */ - DoubleUnitNode::DoubleUnitNode(const DoubleUnitNode& obj) { - if(obj.values) - this->values = obj.values->clone(); - else - this->values = NULL; - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - } - - /* destructor for DoubleUnit ast node */ - DoubleUnitNode::~DoubleUnitNode() { - if(values) - delete values; - if(unit) - delete unit; - } - - /* visit Children method for Argument ast node */ - void ArgumentNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->unit) { - this->unit->accept(v); - } - - } - - /* constructor for Argument ast node */ - ArgumentNode::ArgumentNode(IdentifierNode * name, UnitNode * unit) { - this->name = name; - this->unit = unit; - } - - /* copy constructor for Argument ast node */ - ArgumentNode::ArgumentNode(const ArgumentNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - } - - /* destructor for Argument ast node */ - ArgumentNode::~ArgumentNode() { - if(name) - delete name; - if(unit) - delete unit; - } - - /* visit Children method for LocalVariable ast node */ - void LocalVariableNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for LocalVariable ast node */ - LocalVariableNode::LocalVariableNode(IdentifierNode * name) { - this->name = name; - } - - /* copy constructor for LocalVariable ast node */ - LocalVariableNode::LocalVariableNode(const LocalVariableNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for LocalVariable ast node */ - LocalVariableNode::~LocalVariableNode() { - if(name) - delete name; - } - - /* visit Children method for LocalListStatement ast node */ - void LocalListStatementNode::visitChildren(Visitor* v) { - if (this->variables) { - for(LocalVariableNodeList::iterator iter = this->variables->begin(); - iter != this->variables->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for LocalListStatement ast node */ - LocalListStatementNode::LocalListStatementNode(LocalVariableNodeList * variables) { - this->variables = variables; - } - - /* copy constructor for LocalListStatement ast node */ - LocalListStatementNode::LocalListStatementNode(const LocalListStatementNode& obj) { - // cloning list - if (obj.variables) { - this->variables = new LocalVariableNodeList(); - for(LocalVariableNodeList::iterator iter = obj.variables->begin(); - iter != obj.variables->end(); iter++) { - this->variables->push_back((*iter)->clone()); - } - } - else - this->variables = NULL; - - } - - /* destructor for LocalListStatement ast node */ - LocalListStatementNode::~LocalListStatementNode() { - if (variables) { - for(LocalVariableNodeList::iterator iter = this->variables->begin(); - iter != this->variables->end(); iter++) { - delete (*iter); - } - } - - if(variables) - delete variables; - } - - /* visit Children method for Limits ast node */ - void LimitsNode::visitChildren(Visitor* v) { - min->accept(v); - max->accept(v); - } - - /* constructor for Limits ast node */ - LimitsNode::LimitsNode(DoubleNode * min, DoubleNode * max) { - this->min = min; - this->max = max; - } - - /* copy constructor for Limits ast node */ - LimitsNode::LimitsNode(const LimitsNode& obj) { - if(obj.min) - this->min = obj.min->clone(); - else - this->min = NULL; - if(obj.max) - this->max = obj.max->clone(); - else - this->max = NULL; - } - - /* destructor for Limits ast node */ - LimitsNode::~LimitsNode() { - if(min) - delete min; - if(max) - delete max; - } - - /* visit Children method for NumberRange ast node */ - void NumberRangeNode::visitChildren(Visitor* v) { - min->accept(v); - max->accept(v); - } - - /* constructor for NumberRange ast node */ - NumberRangeNode::NumberRangeNode(NumberNode * min, NumberNode * max) { - this->min = min; - this->max = max; - } - - /* copy constructor for NumberRange ast node */ - NumberRangeNode::NumberRangeNode(const NumberRangeNode& obj) { - if(obj.min) - this->min = obj.min->clone(); - else - this->min = NULL; - if(obj.max) - this->max = obj.max->clone(); - else - this->max = NULL; - } - - /* destructor for NumberRange ast node */ - NumberRangeNode::~NumberRangeNode() { - if(min) - delete min; - if(max) - delete max; - } - - /* visit Children method for Program ast node */ - void ProgramNode::visitChildren(Visitor* v) { - if (this->statements) { - for(StatementNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->blocks) { - for(BlockNodeList::iterator iter = this->blocks->begin(); - iter != this->blocks->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for Program ast node */ - ProgramNode::ProgramNode(StatementNodeList * statements, BlockNodeList * blocks) { - this->statements = statements; - this->blocks = blocks; - this->symtab = NULL; - } - - /* copy constructor for Program ast node */ - ProgramNode::ProgramNode(const ProgramNode& obj) { - // cloning list - if (obj.statements) { - this->statements = new StatementNodeList(); - for(StatementNodeList::iterator iter = obj.statements->begin(); - iter != obj.statements->end(); iter++) { - this->statements->push_back((*iter)->clone()); - } - } - else - this->statements = NULL; - - // cloning list - if (obj.blocks) { - this->blocks = new BlockNodeList(); - for(BlockNodeList::iterator iter = obj.blocks->begin(); - iter != obj.blocks->end(); iter++) { - this->blocks->push_back((*iter)->clone()); - } - } - else - this->blocks = NULL; - - this->symtab = NULL; - } - - /* destructor for Program ast node */ - ProgramNode::~ProgramNode() { - if (statements) { - for(StatementNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - delete (*iter); - } - } - - if(statements) - delete statements; - if (blocks) { - for(BlockNodeList::iterator iter = this->blocks->begin(); - iter != this->blocks->end(); iter++) { - delete (*iter); - } - } - - if(blocks) - delete blocks; - } - - /* visit Children method for Model ast node */ - void ModelNode::visitChildren(Visitor* v) { - title->accept(v); - } - - /* constructor for Model ast node */ - ModelNode::ModelNode(StringNode * title) { - this->title = title; - } - - /* copy constructor for Model ast node */ - ModelNode::ModelNode(const ModelNode& obj) { - if(obj.title) - this->title = obj.title->clone(); - else - this->title = NULL; - } - - /* destructor for Model ast node */ - ModelNode::~ModelNode() { - if(title) - delete title; - } - - /* visit Children method for Define ast node */ - void DefineNode::visitChildren(Visitor* v) { - name->accept(v); - value->accept(v); - } - - /* constructor for Define ast node */ - DefineNode::DefineNode(NameNode * name, IntegerNode * value) { - this->name = name; - this->value = value; - } - - /* copy constructor for Define ast node */ - DefineNode::DefineNode(const DefineNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - } - - /* destructor for Define ast node */ - DefineNode::~DefineNode() { - if(name) - delete name; - if(value) - delete value; - } - - /* visit Children method for Include ast node */ - void IncludeNode::visitChildren(Visitor* v) { - filename->accept(v); - } - - /* constructor for Include ast node */ - IncludeNode::IncludeNode(StringNode * filename) { - this->filename = filename; - } - - /* copy constructor for Include ast node */ - IncludeNode::IncludeNode(const IncludeNode& obj) { - if(obj.filename) - this->filename = obj.filename->clone(); - else - this->filename = NULL; - } - - /* destructor for Include ast node */ - IncludeNode::~IncludeNode() { - if(filename) - delete filename; - } - - /* visit Children method for ParamBlock ast node */ - void ParamBlockNode::visitChildren(Visitor* v) { - if (this->statements) { - for(ParamAssignNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for ParamBlock ast node */ - ParamBlockNode::ParamBlockNode(ParamAssignNodeList * statements) { - this->statements = statements; - this->symtab = NULL; - } - - /* copy constructor for ParamBlock ast node */ - ParamBlockNode::ParamBlockNode(const ParamBlockNode& obj) { - // cloning list - if (obj.statements) { - this->statements = new ParamAssignNodeList(); - for(ParamAssignNodeList::iterator iter = obj.statements->begin(); - iter != obj.statements->end(); iter++) { - this->statements->push_back((*iter)->clone()); - } - } - else - this->statements = NULL; - - this->symtab = NULL; - } - - /* destructor for ParamBlock ast node */ - ParamBlockNode::~ParamBlockNode() { - if (statements) { - for(ParamAssignNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - delete (*iter); - } - } - - if(statements) - delete statements; - } - - /* visit Children method for ParamAssign ast node */ - void ParamAssignNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->value) { - this->value->accept(v); - } - - if (this->unit) { - this->unit->accept(v); - } - - if (this->limit) { - this->limit->accept(v); - } - - } - - /* constructor for ParamAssign ast node */ - ParamAssignNode::ParamAssignNode(IdentifierNode * name, NumberNode * value, UnitNode * unit, LimitsNode * limit) { - this->name = name; - this->value = value; - this->unit = unit; - this->limit = limit; - } - - /* copy constructor for ParamAssign ast node */ - ParamAssignNode::ParamAssignNode(const ParamAssignNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - if(obj.limit) - this->limit = obj.limit->clone(); - else - this->limit = NULL; - } - - /* destructor for ParamAssign ast node */ - ParamAssignNode::~ParamAssignNode() { - if(name) - delete name; - if(value) - delete value; - if(unit) - delete unit; - if(limit) - delete limit; - } - - /* visit Children method for StepBlock ast node */ - void StepBlockNode::visitChildren(Visitor* v) { - if (this->statements) { - for(SteppedNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for StepBlock ast node */ - StepBlockNode::StepBlockNode(SteppedNodeList * statements) { - this->statements = statements; - this->symtab = NULL; - } - - /* copy constructor for StepBlock ast node */ - StepBlockNode::StepBlockNode(const StepBlockNode& obj) { - // cloning list - if (obj.statements) { - this->statements = new SteppedNodeList(); - for(SteppedNodeList::iterator iter = obj.statements->begin(); - iter != obj.statements->end(); iter++) { - this->statements->push_back((*iter)->clone()); - } - } - else - this->statements = NULL; - - this->symtab = NULL; - } - - /* destructor for StepBlock ast node */ - StepBlockNode::~StepBlockNode() { - if (statements) { - for(SteppedNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - delete (*iter); - } - } - - if(statements) - delete statements; - } - - /* visit Children method for Stepped ast node */ - void SteppedNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->values) { - for(NumberNodeList::iterator iter = this->values->begin(); - iter != this->values->end(); iter++) { - (*iter)->accept(v); - } - } - - unit->accept(v); - } - - /* constructor for Stepped ast node */ - SteppedNode::SteppedNode(NameNode * name, NumberNodeList * values, UnitNode * unit) { - this->name = name; - this->values = values; - this->unit = unit; - } - - /* copy constructor for Stepped ast node */ - SteppedNode::SteppedNode(const SteppedNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.values) { - this->values = new NumberNodeList(); - for(NumberNodeList::iterator iter = obj.values->begin(); - iter != obj.values->end(); iter++) { - this->values->push_back((*iter)->clone()); - } - } - else - this->values = NULL; - - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - } - - /* destructor for Stepped ast node */ - SteppedNode::~SteppedNode() { - if(name) - delete name; - if (values) { - for(NumberNodeList::iterator iter = this->values->begin(); - iter != this->values->end(); iter++) { - delete (*iter); - } - } - - if(values) - delete values; - if(unit) - delete unit; - } - - /* visit Children method for IndependentBlock ast node */ - void IndependentBlockNode::visitChildren(Visitor* v) { - if (this->definitions) { - for(IndependentDefNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for IndependentBlock ast node */ - IndependentBlockNode::IndependentBlockNode(IndependentDefNodeList * definitions) { - this->definitions = definitions; - this->symtab = NULL; - } - - /* copy constructor for IndependentBlock ast node */ - IndependentBlockNode::IndependentBlockNode(const IndependentBlockNode& obj) { - // cloning list - if (obj.definitions) { - this->definitions = new IndependentDefNodeList(); - for(IndependentDefNodeList::iterator iter = obj.definitions->begin(); - iter != obj.definitions->end(); iter++) { - this->definitions->push_back((*iter)->clone()); - } - } - else - this->definitions = NULL; - - this->symtab = NULL; - } - - /* destructor for IndependentBlock ast node */ - IndependentBlockNode::~IndependentBlockNode() { - if (definitions) { - for(IndependentDefNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - delete (*iter); - } - } - - if(definitions) - delete definitions; - } - - /* visit Children method for IndependentDef ast node */ - void IndependentDefNode::visitChildren(Visitor* v) { - if (this->sweep) { - this->sweep->accept(v); - } - - name->accept(v); - from->accept(v); - to->accept(v); - with->accept(v); - if (this->opstart) { - this->opstart->accept(v); - } - - unit->accept(v); - } - - /* constructor for IndependentDef ast node */ - IndependentDefNode::IndependentDefNode(BooleanNode * sweep, NameNode * name, NumberNode * from, NumberNode * to, IntegerNode * with, NumberNode * opstart, UnitNode * unit) { - this->sweep = sweep; - this->name = name; - this->from = from; - this->to = to; - this->with = with; - this->opstart = opstart; - this->unit = unit; - } - - /* copy constructor for IndependentDef ast node */ - IndependentDefNode::IndependentDefNode(const IndependentDefNode& obj) { - if(obj.sweep) - this->sweep = obj.sweep->clone(); - else - this->sweep = NULL; - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.from) - this->from = obj.from->clone(); - else - this->from = NULL; - if(obj.to) - this->to = obj.to->clone(); - else - this->to = NULL; - if(obj.with) - this->with = obj.with->clone(); - else - this->with = NULL; - if(obj.opstart) - this->opstart = obj.opstart->clone(); - else - this->opstart = NULL; - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - } - - /* destructor for IndependentDef ast node */ - IndependentDefNode::~IndependentDefNode() { - if(sweep) - delete sweep; - if(name) - delete name; - if(from) - delete from; - if(to) - delete to; - if(with) - delete with; - if(opstart) - delete opstart; - if(unit) - delete unit; - } - - /* visit Children method for DependentDef ast node */ - void DependentDefNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->index) { - this->index->accept(v); - } - - if (this->from) { - this->from->accept(v); - } - - if (this->to) { - this->to->accept(v); - } - - if (this->opstart) { - this->opstart->accept(v); - } - - if (this->unit) { - this->unit->accept(v); - } - - if (this->abstol) { - this->abstol->accept(v); - } - - } - - /* constructor for DependentDef ast node */ - DependentDefNode::DependentDefNode(IdentifierNode * name, IntegerNode * index, NumberNode * from, NumberNode * to, NumberNode * opstart, UnitNode * unit, DoubleNode * abstol) { - this->name = name; - this->index = index; - this->from = from; - this->to = to; - this->opstart = opstart; - this->unit = unit; - this->abstol = abstol; - } - - /* copy constructor for DependentDef ast node */ - DependentDefNode::DependentDefNode(const DependentDefNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.index) - this->index = obj.index->clone(); - else - this->index = NULL; - if(obj.from) - this->from = obj.from->clone(); - else - this->from = NULL; - if(obj.to) - this->to = obj.to->clone(); - else - this->to = NULL; - if(obj.opstart) - this->opstart = obj.opstart->clone(); - else - this->opstart = NULL; - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - if(obj.abstol) - this->abstol = obj.abstol->clone(); - else - this->abstol = NULL; - } - - /* destructor for DependentDef ast node */ - DependentDefNode::~DependentDefNode() { - if(name) - delete name; - if(index) - delete index; - if(from) - delete from; - if(to) - delete to; - if(opstart) - delete opstart; - if(unit) - delete unit; - if(abstol) - delete abstol; - } - - /* visit Children method for DependentBlock ast node */ - void DependentBlockNode::visitChildren(Visitor* v) { - if (this->definitions) { - for(DependentDefNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for DependentBlock ast node */ - DependentBlockNode::DependentBlockNode(DependentDefNodeList * definitions) { - this->definitions = definitions; - this->symtab = NULL; - } - - /* copy constructor for DependentBlock ast node */ - DependentBlockNode::DependentBlockNode(const DependentBlockNode& obj) { - // cloning list - if (obj.definitions) { - this->definitions = new DependentDefNodeList(); - for(DependentDefNodeList::iterator iter = obj.definitions->begin(); - iter != obj.definitions->end(); iter++) { - this->definitions->push_back((*iter)->clone()); - } - } - else - this->definitions = NULL; - - this->symtab = NULL; - } - - /* destructor for DependentBlock ast node */ - DependentBlockNode::~DependentBlockNode() { - if (definitions) { - for(DependentDefNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - delete (*iter); - } - } - - if(definitions) - delete definitions; - } - - /* visit Children method for StateBlock ast node */ - void StateBlockNode::visitChildren(Visitor* v) { - if (this->definitions) { - for(DependentDefNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for StateBlock ast node */ - StateBlockNode::StateBlockNode(DependentDefNodeList * definitions) { - this->definitions = definitions; - this->symtab = NULL; - } - - /* copy constructor for StateBlock ast node */ - StateBlockNode::StateBlockNode(const StateBlockNode& obj) { - // cloning list - if (obj.definitions) { - this->definitions = new DependentDefNodeList(); - for(DependentDefNodeList::iterator iter = obj.definitions->begin(); - iter != obj.definitions->end(); iter++) { - this->definitions->push_back((*iter)->clone()); - } - } - else - this->definitions = NULL; - - this->symtab = NULL; - } - - /* destructor for StateBlock ast node */ - StateBlockNode::~StateBlockNode() { - if (definitions) { - for(DependentDefNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - delete (*iter); - } - } - - if(definitions) - delete definitions; - } - - /* visit Children method for PlotBlock ast node */ - void PlotBlockNode::visitChildren(Visitor* v) { - plot->accept(v); - } - - /* constructor for PlotBlock ast node */ - PlotBlockNode::PlotBlockNode(PlotDeclarationNode * plot) { - this->plot = plot; - this->symtab = NULL; - } - - /* copy constructor for PlotBlock ast node */ - PlotBlockNode::PlotBlockNode(const PlotBlockNode& obj) { - if(obj.plot) - this->plot = obj.plot->clone(); - else - this->plot = NULL; - this->symtab = NULL; - } - - /* destructor for PlotBlock ast node */ - PlotBlockNode::~PlotBlockNode() { - if(plot) - delete plot; - } - - /* visit Children method for PlotDeclaration ast node */ - void PlotDeclarationNode::visitChildren(Visitor* v) { - if (this->pvlist) { - for(PlotVariableNodeList::iterator iter = this->pvlist->begin(); - iter != this->pvlist->end(); iter++) { - (*iter)->accept(v); - } - } - - name->accept(v); - } - - /* constructor for PlotDeclaration ast node */ - PlotDeclarationNode::PlotDeclarationNode(PlotVariableNodeList * pvlist, PlotVariableNode * name) { - this->pvlist = pvlist; - this->name = name; - } - - /* copy constructor for PlotDeclaration ast node */ - PlotDeclarationNode::PlotDeclarationNode(const PlotDeclarationNode& obj) { - // cloning list - if (obj.pvlist) { - this->pvlist = new PlotVariableNodeList(); - for(PlotVariableNodeList::iterator iter = obj.pvlist->begin(); - iter != obj.pvlist->end(); iter++) { - this->pvlist->push_back((*iter)->clone()); - } - } - else - this->pvlist = NULL; - - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for PlotDeclaration ast node */ - PlotDeclarationNode::~PlotDeclarationNode() { - if (pvlist) { - for(PlotVariableNodeList::iterator iter = this->pvlist->begin(); - iter != this->pvlist->end(); iter++) { - delete (*iter); - } - } - - if(pvlist) - delete pvlist; - if(name) - delete name; - } - - /* visit Children method for PlotVariable ast node */ - void PlotVariableNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->index) { - this->index->accept(v); - } - - } - - /* constructor for PlotVariable ast node */ - PlotVariableNode::PlotVariableNode(IdentifierNode * name, IntegerNode * index) { - this->name = name; - this->index = index; - } - - /* copy constructor for PlotVariable ast node */ - PlotVariableNode::PlotVariableNode(const PlotVariableNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.index) - this->index = obj.index->clone(); - else - this->index = NULL; - } - - /* destructor for PlotVariable ast node */ - PlotVariableNode::~PlotVariableNode() { - if(name) - delete name; - if(index) - delete index; - } - - /* visit Children method for InitialBlock ast node */ - void InitialBlockNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for InitialBlock ast node */ - InitialBlockNode::InitialBlockNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for InitialBlock ast node */ - InitialBlockNode::InitialBlockNode(const InitialBlockNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for InitialBlock ast node */ - InitialBlockNode::~InitialBlockNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for ConstructorBlock ast node */ - void ConstructorBlockNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for ConstructorBlock ast node */ - ConstructorBlockNode::ConstructorBlockNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for ConstructorBlock ast node */ - ConstructorBlockNode::ConstructorBlockNode(const ConstructorBlockNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for ConstructorBlock ast node */ - ConstructorBlockNode::~ConstructorBlockNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for DestructorBlock ast node */ - void DestructorBlockNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for DestructorBlock ast node */ - DestructorBlockNode::DestructorBlockNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for DestructorBlock ast node */ - DestructorBlockNode::DestructorBlockNode(const DestructorBlockNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for DestructorBlock ast node */ - DestructorBlockNode::~DestructorBlockNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for ConductanceHint ast node */ - void ConductanceHintNode::visitChildren(Visitor* v) { - conductance->accept(v); - if (this->ion) { - this->ion->accept(v); - } - - } - - /* constructor for ConductanceHint ast node */ - ConductanceHintNode::ConductanceHintNode(NameNode * conductance, NameNode * ion) { - this->conductance = conductance; - this->ion = ion; - } - - /* copy constructor for ConductanceHint ast node */ - ConductanceHintNode::ConductanceHintNode(const ConductanceHintNode& obj) { - if(obj.conductance) - this->conductance = obj.conductance->clone(); - else - this->conductance = NULL; - if(obj.ion) - this->ion = obj.ion->clone(); - else - this->ion = NULL; - } - - /* destructor for ConductanceHint ast node */ - ConductanceHintNode::~ConductanceHintNode() { - if(conductance) - delete conductance; - if(ion) - delete ion; - } - - /* visit Children method for ExpressionStatement ast node */ - void ExpressionStatementNode::visitChildren(Visitor* v) { - expression->accept(v); - } - - /* constructor for ExpressionStatement ast node */ - ExpressionStatementNode::ExpressionStatementNode(ExpressionNode * expression) { - this->expression = expression; - } - - /* copy constructor for ExpressionStatement ast node */ - ExpressionStatementNode::ExpressionStatementNode(const ExpressionStatementNode& obj) { - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - } - - /* destructor for ExpressionStatement ast node */ - ExpressionStatementNode::~ExpressionStatementNode() { - if(expression) - delete expression; - } - - /* visit Children method for ProtectStatement ast node */ - void ProtectStatementNode::visitChildren(Visitor* v) { - expression->accept(v); - } - - /* constructor for ProtectStatement ast node */ - ProtectStatementNode::ProtectStatementNode(ExpressionNode * expression) { - this->expression = expression; - } - - /* copy constructor for ProtectStatement ast node */ - ProtectStatementNode::ProtectStatementNode(const ProtectStatementNode& obj) { - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - } - - /* destructor for ProtectStatement ast node */ - ProtectStatementNode::~ProtectStatementNode() { - if(expression) - delete expression; - } - - /* visit Children method for StatementBlock ast node */ - void StatementBlockNode::visitChildren(Visitor* v) { - if (this->statements) { - for(StatementNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for StatementBlock ast node */ - StatementBlockNode::StatementBlockNode(StatementNodeList * statements) { - this->statements = statements; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for StatementBlock ast node */ - StatementBlockNode::StatementBlockNode(const StatementBlockNode& obj) { - // cloning list - if (obj.statements) { - this->statements = new StatementNodeList(); - for(StatementNodeList::iterator iter = obj.statements->begin(); - iter != obj.statements->end(); iter++) { - this->statements->push_back((*iter)->clone()); - } - } - else - this->statements = NULL; - - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for StatementBlock ast node */ - StatementBlockNode::~StatementBlockNode() { - if (statements) { - for(StatementNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - delete (*iter); - } - } - - if(statements) - delete statements; - if(token) - delete token; - } - - /* visit Children method for BinaryOperator ast node */ - void BinaryOperatorNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for BinaryOperator ast node */ - BinaryOperatorNode::BinaryOperatorNode(BinaryOp value) { - this->value = value; - } - - /* copy constructor for BinaryOperator ast node */ - BinaryOperatorNode::BinaryOperatorNode(const BinaryOperatorNode& obj) { - this->value = obj.value; - } - - /* destructor for BinaryOperator ast node */ - BinaryOperatorNode::~BinaryOperatorNode() { - } - - /* visit Children method for UnaryOperator ast node */ - void UnaryOperatorNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for UnaryOperator ast node */ - UnaryOperatorNode::UnaryOperatorNode(UnaryOp value) { - this->value = value; - } - - /* copy constructor for UnaryOperator ast node */ - UnaryOperatorNode::UnaryOperatorNode(const UnaryOperatorNode& obj) { - this->value = obj.value; - } - - /* destructor for UnaryOperator ast node */ - UnaryOperatorNode::~UnaryOperatorNode() { - } - - /* visit Children method for ReactionOperator ast node */ - void ReactionOperatorNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for ReactionOperator ast node */ - ReactionOperatorNode::ReactionOperatorNode(ReactionOp value) { - this->value = value; - } - - /* copy constructor for ReactionOperator ast node */ - ReactionOperatorNode::ReactionOperatorNode(const ReactionOperatorNode& obj) { - this->value = obj.value; - } - - /* destructor for ReactionOperator ast node */ - ReactionOperatorNode::~ReactionOperatorNode() { - } - - /* visit Children method for BinaryExpression ast node */ - void BinaryExpressionNode::visitChildren(Visitor* v) { - lhs->accept(v); - op->accept(v); - rhs->accept(v); - } - - /* constructor for BinaryExpression ast node */ - BinaryExpressionNode::BinaryExpressionNode(ExpressionNode * lhs, BinaryOperatorNode * op, ExpressionNode * rhs) { - this->lhs = lhs; - this->op = op; - this->rhs = rhs; - } - - /* copy constructor for BinaryExpression ast node */ - BinaryExpressionNode::BinaryExpressionNode(const BinaryExpressionNode& obj) { - if(obj.lhs) - this->lhs = obj.lhs->clone(); - else - this->lhs = NULL; - if(obj.op) - this->op = obj.op->clone(); - else - this->op = NULL; - if(obj.rhs) - this->rhs = obj.rhs->clone(); - else - this->rhs = NULL; - } - - /* destructor for BinaryExpression ast node */ - BinaryExpressionNode::~BinaryExpressionNode() { - if(lhs) - delete lhs; - if(op) - delete op; - if(rhs) - delete rhs; - } - - /* visit Children method for UnaryExpression ast node */ - void UnaryExpressionNode::visitChildren(Visitor* v) { - op->accept(v); - expression->accept(v); - } - - /* constructor for UnaryExpression ast node */ - UnaryExpressionNode::UnaryExpressionNode(UnaryOperatorNode * op, ExpressionNode * expression) { - this->op = op; - this->expression = expression; - } - - /* copy constructor for UnaryExpression ast node */ - UnaryExpressionNode::UnaryExpressionNode(const UnaryExpressionNode& obj) { - if(obj.op) - this->op = obj.op->clone(); - else - this->op = NULL; - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - } - - /* destructor for UnaryExpression ast node */ - UnaryExpressionNode::~UnaryExpressionNode() { - if(op) - delete op; - if(expression) - delete expression; - } - - /* visit Children method for NonLinEuation ast node */ - void NonLinEuationNode::visitChildren(Visitor* v) { - lhs->accept(v); - rhs->accept(v); - } - - /* constructor for NonLinEuation ast node */ - NonLinEuationNode::NonLinEuationNode(ExpressionNode * lhs, ExpressionNode * rhs) { - this->lhs = lhs; - this->rhs = rhs; - } - - /* copy constructor for NonLinEuation ast node */ - NonLinEuationNode::NonLinEuationNode(const NonLinEuationNode& obj) { - if(obj.lhs) - this->lhs = obj.lhs->clone(); - else - this->lhs = NULL; - if(obj.rhs) - this->rhs = obj.rhs->clone(); - else - this->rhs = NULL; - } - - /* destructor for NonLinEuation ast node */ - NonLinEuationNode::~NonLinEuationNode() { - if(lhs) - delete lhs; - if(rhs) - delete rhs; - } - - /* visit Children method for LinEquation ast node */ - void LinEquationNode::visitChildren(Visitor* v) { - leftlinexpr->accept(v); - linexpr->accept(v); - } - - /* constructor for LinEquation ast node */ - LinEquationNode::LinEquationNode(ExpressionNode * leftlinexpr, ExpressionNode * linexpr) { - this->leftlinexpr = leftlinexpr; - this->linexpr = linexpr; - } - - /* copy constructor for LinEquation ast node */ - LinEquationNode::LinEquationNode(const LinEquationNode& obj) { - if(obj.leftlinexpr) - this->leftlinexpr = obj.leftlinexpr->clone(); - else - this->leftlinexpr = NULL; - if(obj.linexpr) - this->linexpr = obj.linexpr->clone(); - else - this->linexpr = NULL; - } - - /* destructor for LinEquation ast node */ - LinEquationNode::~LinEquationNode() { - if(leftlinexpr) - delete leftlinexpr; - if(linexpr) - delete linexpr; - } - - /* visit Children method for FunctionCall ast node */ - void FunctionCallNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->arguments) { - for(ExpressionNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for FunctionCall ast node */ - FunctionCallNode::FunctionCallNode(NameNode * name, ExpressionNodeList * arguments) { - this->name = name; - this->arguments = arguments; - } - - /* copy constructor for FunctionCall ast node */ - FunctionCallNode::FunctionCallNode(const FunctionCallNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.arguments) { - this->arguments = new ExpressionNodeList(); - for(ExpressionNodeList::iterator iter = obj.arguments->begin(); - iter != obj.arguments->end(); iter++) { - this->arguments->push_back((*iter)->clone()); - } - } - else - this->arguments = NULL; - - } - - /* destructor for FunctionCall ast node */ - FunctionCallNode::~FunctionCallNode() { - if(name) - delete name; - if (arguments) { - for(ExpressionNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - delete (*iter); - } - } - - if(arguments) - delete arguments; - } - - /* visit Children method for FromStatement ast node */ - void FromStatementNode::visitChildren(Visitor* v) { - name->accept(v); - from->accept(v); - to->accept(v); - if (this->opinc) { - this->opinc->accept(v); - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for FromStatement ast node */ - FromStatementNode::FromStatementNode(NameNode * name, ExpressionNode * from, ExpressionNode * to, ExpressionNode * opinc, StatementBlockNode * statementblock) { - this->name = name; - this->from = from; - this->to = to; - this->opinc = opinc; - this->statementblock = statementblock; - } - - /* copy constructor for FromStatement ast node */ - FromStatementNode::FromStatementNode(const FromStatementNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.from) - this->from = obj.from->clone(); - else - this->from = NULL; - if(obj.to) - this->to = obj.to->clone(); - else - this->to = NULL; - if(obj.opinc) - this->opinc = obj.opinc->clone(); - else - this->opinc = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - } - - /* destructor for FromStatement ast node */ - FromStatementNode::~FromStatementNode() { - if(name) - delete name; - if(from) - delete from; - if(to) - delete to; - if(opinc) - delete opinc; - if(statementblock) - delete statementblock; - } - - /* visit Children method for ForAllStatement ast node */ - void ForAllStatementNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for ForAllStatement ast node */ - ForAllStatementNode::ForAllStatementNode(NameNode * name, StatementBlockNode * statementblock) { - this->name = name; - this->statementblock = statementblock; - } - - /* copy constructor for ForAllStatement ast node */ - ForAllStatementNode::ForAllStatementNode(const ForAllStatementNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - } - - /* destructor for ForAllStatement ast node */ - ForAllStatementNode::~ForAllStatementNode() { - if(name) - delete name; - if(statementblock) - delete statementblock; - } - - /* visit Children method for WhileStatement ast node */ - void WhileStatementNode::visitChildren(Visitor* v) { - condition->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for WhileStatement ast node */ - WhileStatementNode::WhileStatementNode(ExpressionNode * condition, StatementBlockNode * statementblock) { - this->condition = condition; - this->statementblock = statementblock; - } - - /* copy constructor for WhileStatement ast node */ - WhileStatementNode::WhileStatementNode(const WhileStatementNode& obj) { - if(obj.condition) - this->condition = obj.condition->clone(); - else - this->condition = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - } - - /* destructor for WhileStatement ast node */ - WhileStatementNode::~WhileStatementNode() { - if(condition) - delete condition; - if(statementblock) - delete statementblock; - } - - /* visit Children method for IfStatement ast node */ - void IfStatementNode::visitChildren(Visitor* v) { - condition->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - if (this->elseifs) { - for(ElseIfStatementNodeList::iterator iter = this->elseifs->begin(); - iter != this->elseifs->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->elses) { - this->elses->accept(v); - } - - } - - /* constructor for IfStatement ast node */ - IfStatementNode::IfStatementNode(ExpressionNode * condition, StatementBlockNode * statementblock, ElseIfStatementNodeList * elseifs, ElseStatementNode * elses) { - this->condition = condition; - this->statementblock = statementblock; - this->elseifs = elseifs; - this->elses = elses; - } - - /* copy constructor for IfStatement ast node */ - IfStatementNode::IfStatementNode(const IfStatementNode& obj) { - if(obj.condition) - this->condition = obj.condition->clone(); - else - this->condition = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - // cloning list - if (obj.elseifs) { - this->elseifs = new ElseIfStatementNodeList(); - for(ElseIfStatementNodeList::iterator iter = obj.elseifs->begin(); - iter != obj.elseifs->end(); iter++) { - this->elseifs->push_back((*iter)->clone()); - } - } - else - this->elseifs = NULL; - - if(obj.elses) - this->elses = obj.elses->clone(); - else - this->elses = NULL; - } - - /* destructor for IfStatement ast node */ - IfStatementNode::~IfStatementNode() { - if(condition) - delete condition; - if(statementblock) - delete statementblock; - if (elseifs) { - for(ElseIfStatementNodeList::iterator iter = this->elseifs->begin(); - iter != this->elseifs->end(); iter++) { - delete (*iter); - } - } - - if(elseifs) - delete elseifs; - if(elses) - delete elses; - } - - /* visit Children method for ElseIfStatement ast node */ - void ElseIfStatementNode::visitChildren(Visitor* v) { - condition->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for ElseIfStatement ast node */ - ElseIfStatementNode::ElseIfStatementNode(ExpressionNode * condition, StatementBlockNode * statementblock) { - this->condition = condition; - this->statementblock = statementblock; - } - - /* copy constructor for ElseIfStatement ast node */ - ElseIfStatementNode::ElseIfStatementNode(const ElseIfStatementNode& obj) { - if(obj.condition) - this->condition = obj.condition->clone(); - else - this->condition = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - } - - /* destructor for ElseIfStatement ast node */ - ElseIfStatementNode::~ElseIfStatementNode() { - if(condition) - delete condition; - if(statementblock) - delete statementblock; - } - - /* visit Children method for ElseStatement ast node */ - void ElseStatementNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for ElseStatement ast node */ - ElseStatementNode::ElseStatementNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - } - - /* copy constructor for ElseStatement ast node */ - ElseStatementNode::ElseStatementNode(const ElseStatementNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - } - - /* destructor for ElseStatement ast node */ - ElseStatementNode::~ElseStatementNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for DerivativeBlock ast node */ - void DerivativeBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for DerivativeBlock ast node */ - DerivativeBlockNode::DerivativeBlockNode(NameNode * name, StatementBlockNode * statementblock) { - this->name = name; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for DerivativeBlock ast node */ - DerivativeBlockNode::DerivativeBlockNode(const DerivativeBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for DerivativeBlock ast node */ - DerivativeBlockNode::~DerivativeBlockNode() { - if(name) - delete name; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for LinearBlock ast node */ - void LinearBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->solvefor) { - for(NameNodeList::iterator iter = this->solvefor->begin(); - iter != this->solvefor->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for LinearBlock ast node */ - LinearBlockNode::LinearBlockNode(NameNode * name, NameNodeList * solvefor, StatementBlockNode * statementblock) { - this->name = name; - this->solvefor = solvefor; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for LinearBlock ast node */ - LinearBlockNode::LinearBlockNode(const LinearBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.solvefor) { - this->solvefor = new NameNodeList(); - for(NameNodeList::iterator iter = obj.solvefor->begin(); - iter != obj.solvefor->end(); iter++) { - this->solvefor->push_back((*iter)->clone()); - } - } - else - this->solvefor = NULL; - - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for LinearBlock ast node */ - LinearBlockNode::~LinearBlockNode() { - if(name) - delete name; - if (solvefor) { - for(NameNodeList::iterator iter = this->solvefor->begin(); - iter != this->solvefor->end(); iter++) { - delete (*iter); - } - } - - if(solvefor) - delete solvefor; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for NonLinearBlock ast node */ - void NonLinearBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->solvefor) { - for(NameNodeList::iterator iter = this->solvefor->begin(); - iter != this->solvefor->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for NonLinearBlock ast node */ - NonLinearBlockNode::NonLinearBlockNode(NameNode * name, NameNodeList * solvefor, StatementBlockNode * statementblock) { - this->name = name; - this->solvefor = solvefor; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for NonLinearBlock ast node */ - NonLinearBlockNode::NonLinearBlockNode(const NonLinearBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.solvefor) { - this->solvefor = new NameNodeList(); - for(NameNodeList::iterator iter = obj.solvefor->begin(); - iter != obj.solvefor->end(); iter++) { - this->solvefor->push_back((*iter)->clone()); - } - } - else - this->solvefor = NULL; - - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for NonLinearBlock ast node */ - NonLinearBlockNode::~NonLinearBlockNode() { - if(name) - delete name; - if (solvefor) { - for(NameNodeList::iterator iter = this->solvefor->begin(); - iter != this->solvefor->end(); iter++) { - delete (*iter); - } - } - - if(solvefor) - delete solvefor; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for DiscreteBlock ast node */ - void DiscreteBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for DiscreteBlock ast node */ - DiscreteBlockNode::DiscreteBlockNode(NameNode * name, StatementBlockNode * statementblock) { - this->name = name; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for DiscreteBlock ast node */ - DiscreteBlockNode::DiscreteBlockNode(const DiscreteBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for DiscreteBlock ast node */ - DiscreteBlockNode::~DiscreteBlockNode() { - if(name) - delete name; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for PartialBlock ast node */ - void PartialBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for PartialBlock ast node */ - PartialBlockNode::PartialBlockNode(NameNode * name, StatementBlockNode * statementblock) { - this->name = name; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for PartialBlock ast node */ - PartialBlockNode::PartialBlockNode(const PartialBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for PartialBlock ast node */ - PartialBlockNode::~PartialBlockNode() { - if(name) - delete name; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for PartialEquation ast node */ - void PartialEquationNode::visitChildren(Visitor* v) { - prime->accept(v); - name1->accept(v); - name2->accept(v); - name3->accept(v); - } - - /* constructor for PartialEquation ast node */ - PartialEquationNode::PartialEquationNode(PrimeNameNode * prime, NameNode * name1, NameNode * name2, NameNode * name3) { - this->prime = prime; - this->name1 = name1; - this->name2 = name2; - this->name3 = name3; - } - - /* copy constructor for PartialEquation ast node */ - PartialEquationNode::PartialEquationNode(const PartialEquationNode& obj) { - if(obj.prime) - this->prime = obj.prime->clone(); - else - this->prime = NULL; - if(obj.name1) - this->name1 = obj.name1->clone(); - else - this->name1 = NULL; - if(obj.name2) - this->name2 = obj.name2->clone(); - else - this->name2 = NULL; - if(obj.name3) - this->name3 = obj.name3->clone(); - else - this->name3 = NULL; - } - - /* destructor for PartialEquation ast node */ - PartialEquationNode::~PartialEquationNode() { - if(prime) - delete prime; - if(name1) - delete name1; - if(name2) - delete name2; - if(name3) - delete name3; - } - - /* visit Children method for FirstLastTypeIndex ast node */ - void FirstLastTypeIndexNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for FirstLastTypeIndex ast node */ - FirstLastTypeIndexNode::FirstLastTypeIndexNode(FirstLastType value) { - this->value = value; - } - - /* copy constructor for FirstLastTypeIndex ast node */ - FirstLastTypeIndexNode::FirstLastTypeIndexNode(const FirstLastTypeIndexNode& obj) { - this->value = obj.value; - } - - /* destructor for FirstLastTypeIndex ast node */ - FirstLastTypeIndexNode::~FirstLastTypeIndexNode() { - } - - /* visit Children method for PartialBoundary ast node */ - void PartialBoundaryNode::visitChildren(Visitor* v) { - if (this->del) { - this->del->accept(v); - } - - name->accept(v); - if (this->index) { - this->index->accept(v); - } - - if (this->expression) { - this->expression->accept(v); - } - - if (this->name1) { - this->name1->accept(v); - } - - if (this->del2) { - this->del2->accept(v); - } - - if (this->name2) { - this->name2->accept(v); - } - - if (this->name3) { - this->name3->accept(v); - } - - } - - /* constructor for PartialBoundary ast node */ - PartialBoundaryNode::PartialBoundaryNode(NameNode * del, IdentifierNode * name, FirstLastTypeIndexNode * index, ExpressionNode * expression, NameNode * name1, NameNode * del2, NameNode * name2, NameNode * name3) { - this->del = del; - this->name = name; - this->index = index; - this->expression = expression; - this->name1 = name1; - this->del2 = del2; - this->name2 = name2; - this->name3 = name3; - } - - /* copy constructor for PartialBoundary ast node */ - PartialBoundaryNode::PartialBoundaryNode(const PartialBoundaryNode& obj) { - if(obj.del) - this->del = obj.del->clone(); - else - this->del = NULL; - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.index) - this->index = obj.index->clone(); - else - this->index = NULL; - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - if(obj.name1) - this->name1 = obj.name1->clone(); - else - this->name1 = NULL; - if(obj.del2) - this->del2 = obj.del2->clone(); - else - this->del2 = NULL; - if(obj.name2) - this->name2 = obj.name2->clone(); - else - this->name2 = NULL; - if(obj.name3) - this->name3 = obj.name3->clone(); - else - this->name3 = NULL; - } - - /* destructor for PartialBoundary ast node */ - PartialBoundaryNode::~PartialBoundaryNode() { - if(del) - delete del; - if(name) - delete name; - if(index) - delete index; - if(expression) - delete expression; - if(name1) - delete name1; - if(del2) - delete del2; - if(name2) - delete name2; - if(name3) - delete name3; - } - - /* visit Children method for FunctionTableBlock ast node */ - void FunctionTableBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->unit) { - this->unit->accept(v); - } - - } - - /* constructor for FunctionTableBlock ast node */ - FunctionTableBlockNode::FunctionTableBlockNode(NameNode * name, ArgumentNodeList * arguments, UnitNode * unit) { - this->name = name; - this->arguments = arguments; - this->unit = unit; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for FunctionTableBlock ast node */ - FunctionTableBlockNode::FunctionTableBlockNode(const FunctionTableBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.arguments) { - this->arguments = new ArgumentNodeList(); - for(ArgumentNodeList::iterator iter = obj.arguments->begin(); - iter != obj.arguments->end(); iter++) { - this->arguments->push_back((*iter)->clone()); - } - } - else - this->arguments = NULL; - - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for FunctionTableBlock ast node */ - FunctionTableBlockNode::~FunctionTableBlockNode() { - if(name) - delete name; - if (arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - delete (*iter); - } - } - - if(arguments) - delete arguments; - if(unit) - delete unit; - if(token) - delete token; - } - - /* visit Children method for FunctionBlock ast node */ - void FunctionBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->unit) { - this->unit->accept(v); - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for FunctionBlock ast node */ - FunctionBlockNode::FunctionBlockNode(NameNode * name, ArgumentNodeList * arguments, UnitNode * unit, StatementBlockNode * statementblock) { - this->name = name; - this->arguments = arguments; - this->unit = unit; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for FunctionBlock ast node */ - FunctionBlockNode::FunctionBlockNode(const FunctionBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.arguments) { - this->arguments = new ArgumentNodeList(); - for(ArgumentNodeList::iterator iter = obj.arguments->begin(); - iter != obj.arguments->end(); iter++) { - this->arguments->push_back((*iter)->clone()); - } - } - else - this->arguments = NULL; - - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for FunctionBlock ast node */ - FunctionBlockNode::~FunctionBlockNode() { - if(name) - delete name; - if (arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - delete (*iter); - } - } - - if(arguments) - delete arguments; - if(unit) - delete unit; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for ProcedureBlock ast node */ - void ProcedureBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->unit) { - this->unit->accept(v); - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for ProcedureBlock ast node */ - ProcedureBlockNode::ProcedureBlockNode(NameNode * name, ArgumentNodeList * arguments, UnitNode * unit, StatementBlockNode * statementblock) { - this->name = name; - this->arguments = arguments; - this->unit = unit; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for ProcedureBlock ast node */ - ProcedureBlockNode::ProcedureBlockNode(const ProcedureBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.arguments) { - this->arguments = new ArgumentNodeList(); - for(ArgumentNodeList::iterator iter = obj.arguments->begin(); - iter != obj.arguments->end(); iter++) { - this->arguments->push_back((*iter)->clone()); - } - } - else - this->arguments = NULL; - - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for ProcedureBlock ast node */ - ProcedureBlockNode::~ProcedureBlockNode() { - if(name) - delete name; - if (arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - delete (*iter); - } - } - - if(arguments) - delete arguments; - if(unit) - delete unit; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for NetReceiveBlock ast node */ - void NetReceiveBlockNode::visitChildren(Visitor* v) { - if (this->arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for NetReceiveBlock ast node */ - NetReceiveBlockNode::NetReceiveBlockNode(ArgumentNodeList * arguments, StatementBlockNode * statementblock) { - this->arguments = arguments; - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for NetReceiveBlock ast node */ - NetReceiveBlockNode::NetReceiveBlockNode(const NetReceiveBlockNode& obj) { - // cloning list - if (obj.arguments) { - this->arguments = new ArgumentNodeList(); - for(ArgumentNodeList::iterator iter = obj.arguments->begin(); - iter != obj.arguments->end(); iter++) { - this->arguments->push_back((*iter)->clone()); - } - } - else - this->arguments = NULL; - - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for NetReceiveBlock ast node */ - NetReceiveBlockNode::~NetReceiveBlockNode() { - if (arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - delete (*iter); - } - } - - if(arguments) - delete arguments; - if(statementblock) - delete statementblock; - } - - /* visit Children method for SolveBlock ast node */ - void SolveBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->method) { - this->method->accept(v); - } - - if (this->ifsolerr) { - this->ifsolerr->accept(v); - } - - } - - /* constructor for SolveBlock ast node */ - SolveBlockNode::SolveBlockNode(NameNode * name, NameNode * method, StatementBlockNode * ifsolerr) { - this->name = name; - this->method = method; - this->ifsolerr = ifsolerr; - this->symtab = NULL; - } - - /* copy constructor for SolveBlock ast node */ - SolveBlockNode::SolveBlockNode(const SolveBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.method) - this->method = obj.method->clone(); - else - this->method = NULL; - if(obj.ifsolerr) - this->ifsolerr = obj.ifsolerr->clone(); - else - this->ifsolerr = NULL; - this->symtab = NULL; - } - - /* destructor for SolveBlock ast node */ - SolveBlockNode::~SolveBlockNode() { - if(name) - delete name; - if(method) - delete method; - if(ifsolerr) - delete ifsolerr; - } - - /* visit Children method for BreakpointBlock ast node */ - void BreakpointBlockNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for BreakpointBlock ast node */ - BreakpointBlockNode::BreakpointBlockNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for BreakpointBlock ast node */ - BreakpointBlockNode::BreakpointBlockNode(const BreakpointBlockNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for BreakpointBlock ast node */ - BreakpointBlockNode::~BreakpointBlockNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for TerminalBlock ast node */ - void TerminalBlockNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for TerminalBlock ast node */ - TerminalBlockNode::TerminalBlockNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for TerminalBlock ast node */ - TerminalBlockNode::TerminalBlockNode(const TerminalBlockNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for TerminalBlock ast node */ - TerminalBlockNode::~TerminalBlockNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for BeforeBlock ast node */ - void BeforeBlockNode::visitChildren(Visitor* v) { - block->accept(v); - } - - /* constructor for BeforeBlock ast node */ - BeforeBlockNode::BeforeBlockNode(BABlockNode * block) { - this->block = block; - this->symtab = NULL; - } - - /* copy constructor for BeforeBlock ast node */ - BeforeBlockNode::BeforeBlockNode(const BeforeBlockNode& obj) { - if(obj.block) - this->block = obj.block->clone(); - else - this->block = NULL; - this->symtab = NULL; - } - - /* destructor for BeforeBlock ast node */ - BeforeBlockNode::~BeforeBlockNode() { - if(block) - delete block; - } - - /* visit Children method for AfterBlock ast node */ - void AfterBlockNode::visitChildren(Visitor* v) { - block->accept(v); - } - - /* constructor for AfterBlock ast node */ - AfterBlockNode::AfterBlockNode(BABlockNode * block) { - this->block = block; - this->symtab = NULL; - } - - /* copy constructor for AfterBlock ast node */ - AfterBlockNode::AfterBlockNode(const AfterBlockNode& obj) { - if(obj.block) - this->block = obj.block->clone(); - else - this->block = NULL; - this->symtab = NULL; - } - - /* destructor for AfterBlock ast node */ - AfterBlockNode::~AfterBlockNode() { - if(block) - delete block; - } - - /* visit Children method for BABlockType ast node */ - void BABlockTypeNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for BABlockType ast node */ - BABlockTypeNode::BABlockTypeNode(BAType value) { - this->value = value; - } - - /* copy constructor for BABlockType ast node */ - BABlockTypeNode::BABlockTypeNode(const BABlockTypeNode& obj) { - this->value = obj.value; - } - - /* destructor for BABlockType ast node */ - BABlockTypeNode::~BABlockTypeNode() { - } - - /* visit Children method for BABlock ast node */ - void BABlockNode::visitChildren(Visitor* v) { - type->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for BABlock ast node */ - BABlockNode::BABlockNode(BABlockTypeNode * type, StatementBlockNode * statementblock) { - this->type = type; - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for BABlock ast node */ - BABlockNode::BABlockNode(const BABlockNode& obj) { - if(obj.type) - this->type = obj.type->clone(); - else - this->type = NULL; - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for BABlock ast node */ - BABlockNode::~BABlockNode() { - if(type) - delete type; - if(statementblock) - delete statementblock; - } - - /* visit Children method for WatchStatement ast node */ - void WatchStatementNode::visitChildren(Visitor* v) { - if (this->statements) { - for(WatchNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for WatchStatement ast node */ - WatchStatementNode::WatchStatementNode(WatchNodeList * statements) { - this->statements = statements; - } - - /* copy constructor for WatchStatement ast node */ - WatchStatementNode::WatchStatementNode(const WatchStatementNode& obj) { - // cloning list - if (obj.statements) { - this->statements = new WatchNodeList(); - for(WatchNodeList::iterator iter = obj.statements->begin(); - iter != obj.statements->end(); iter++) { - this->statements->push_back((*iter)->clone()); - } - } - else - this->statements = NULL; - - } - - /* destructor for WatchStatement ast node */ - WatchStatementNode::~WatchStatementNode() { - if (statements) { - for(WatchNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - delete (*iter); - } - } - - if(statements) - delete statements; - } - - /* visit Children method for Watch ast node */ - void WatchNode::visitChildren(Visitor* v) { - expression->accept(v); - value->accept(v); - } - - /* constructor for Watch ast node */ - WatchNode::WatchNode(ExpressionNode * expression, ExpressionNode * value) { - this->expression = expression; - this->value = value; - } - - /* copy constructor for Watch ast node */ - WatchNode::WatchNode(const WatchNode& obj) { - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - } - - /* destructor for Watch ast node */ - WatchNode::~WatchNode() { - if(expression) - delete expression; - if(value) - delete value; - } - - /* visit Children method for ForNetcon ast node */ - void ForNetconNode::visitChildren(Visitor* v) { - if (this->arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for ForNetcon ast node */ - ForNetconNode::ForNetconNode(ArgumentNodeList * arguments, StatementBlockNode * statementblock) { - this->arguments = arguments; - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for ForNetcon ast node */ - ForNetconNode::ForNetconNode(const ForNetconNode& obj) { - // cloning list - if (obj.arguments) { - this->arguments = new ArgumentNodeList(); - for(ArgumentNodeList::iterator iter = obj.arguments->begin(); - iter != obj.arguments->end(); iter++) { - this->arguments->push_back((*iter)->clone()); - } - } - else - this->arguments = NULL; - - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for ForNetcon ast node */ - ForNetconNode::~ForNetconNode() { - if (arguments) { - for(ArgumentNodeList::iterator iter = this->arguments->begin(); - iter != this->arguments->end(); iter++) { - delete (*iter); - } - } - - if(arguments) - delete arguments; - if(statementblock) - delete statementblock; - } - - /* visit Children method for MutexLock ast node */ - void MutexLockNode::visitChildren(Visitor* v) { - } - - /* destructor for MutexLock ast node */ - MutexLockNode::~MutexLockNode() { - } - - /* visit Children method for MutexUnlock ast node */ - void MutexUnlockNode::visitChildren(Visitor* v) { - } - - /* destructor for MutexUnlock ast node */ - MutexUnlockNode::~MutexUnlockNode() { - } - - /* visit Children method for Reset ast node */ - void ResetNode::visitChildren(Visitor* v) { - } - - /* destructor for Reset ast node */ - ResetNode::~ResetNode() { - } - - /* visit Children method for Sens ast node */ - void SensNode::visitChildren(Visitor* v) { - if (this->senslist) { - for(VarNameNodeList::iterator iter = this->senslist->begin(); - iter != this->senslist->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for Sens ast node */ - SensNode::SensNode(VarNameNodeList * senslist) { - this->senslist = senslist; - } - - /* copy constructor for Sens ast node */ - SensNode::SensNode(const SensNode& obj) { - // cloning list - if (obj.senslist) { - this->senslist = new VarNameNodeList(); - for(VarNameNodeList::iterator iter = obj.senslist->begin(); - iter != obj.senslist->end(); iter++) { - this->senslist->push_back((*iter)->clone()); - } - } - else - this->senslist = NULL; - - } - - /* destructor for Sens ast node */ - SensNode::~SensNode() { - if (senslist) { - for(VarNameNodeList::iterator iter = this->senslist->begin(); - iter != this->senslist->end(); iter++) { - delete (*iter); - } - } - - if(senslist) - delete senslist; - } - - /* visit Children method for Conserve ast node */ - void ConserveNode::visitChildren(Visitor* v) { - react->accept(v); - expr->accept(v); - } - - /* constructor for Conserve ast node */ - ConserveNode::ConserveNode(ExpressionNode * react, ExpressionNode * expr) { - this->react = react; - this->expr = expr; - } - - /* copy constructor for Conserve ast node */ - ConserveNode::ConserveNode(const ConserveNode& obj) { - if(obj.react) - this->react = obj.react->clone(); - else - this->react = NULL; - if(obj.expr) - this->expr = obj.expr->clone(); - else - this->expr = NULL; - } - - /* destructor for Conserve ast node */ - ConserveNode::~ConserveNode() { - if(react) - delete react; - if(expr) - delete expr; - } - - /* visit Children method for Compartment ast node */ - void CompartmentNode::visitChildren(Visitor* v) { - if (this->name) { - this->name->accept(v); - } - - expression->accept(v); - if (this->names) { - for(NameNodeList::iterator iter = this->names->begin(); - iter != this->names->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for Compartment ast node */ - CompartmentNode::CompartmentNode(NameNode * name, ExpressionNode * expression, NameNodeList * names) { - this->name = name; - this->expression = expression; - this->names = names; - } - - /* copy constructor for Compartment ast node */ - CompartmentNode::CompartmentNode(const CompartmentNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - // cloning list - if (obj.names) { - this->names = new NameNodeList(); - for(NameNodeList::iterator iter = obj.names->begin(); - iter != obj.names->end(); iter++) { - this->names->push_back((*iter)->clone()); - } - } - else - this->names = NULL; - - } - - /* destructor for Compartment ast node */ - CompartmentNode::~CompartmentNode() { - if(name) - delete name; - if(expression) - delete expression; - if (names) { - for(NameNodeList::iterator iter = this->names->begin(); - iter != this->names->end(); iter++) { - delete (*iter); - } - } - - if(names) - delete names; - } - - /* visit Children method for LDifuse ast node */ - void LDifuseNode::visitChildren(Visitor* v) { - if (this->name) { - this->name->accept(v); - } - - expression->accept(v); - if (this->names) { - for(NameNodeList::iterator iter = this->names->begin(); - iter != this->names->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for LDifuse ast node */ - LDifuseNode::LDifuseNode(NameNode * name, ExpressionNode * expression, NameNodeList * names) { - this->name = name; - this->expression = expression; - this->names = names; - } - - /* copy constructor for LDifuse ast node */ - LDifuseNode::LDifuseNode(const LDifuseNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - // cloning list - if (obj.names) { - this->names = new NameNodeList(); - for(NameNodeList::iterator iter = obj.names->begin(); - iter != obj.names->end(); iter++) { - this->names->push_back((*iter)->clone()); - } - } - else - this->names = NULL; - - } - - /* destructor for LDifuse ast node */ - LDifuseNode::~LDifuseNode() { - if(name) - delete name; - if(expression) - delete expression; - if (names) { - for(NameNodeList::iterator iter = this->names->begin(); - iter != this->names->end(); iter++) { - delete (*iter); - } - } - - if(names) - delete names; - } - - /* visit Children method for KineticBlock ast node */ - void KineticBlockNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->solvefor) { - for(NameNodeList::iterator iter = this->solvefor->begin(); - iter != this->solvefor->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for KineticBlock ast node */ - KineticBlockNode::KineticBlockNode(NameNode * name, NameNodeList * solvefor, StatementBlockNode * statementblock) { - this->name = name; - this->solvefor = solvefor; - this->statementblock = statementblock; - this->token = NULL; - this->symtab = NULL; - } - - /* copy constructor for KineticBlock ast node */ - KineticBlockNode::KineticBlockNode(const KineticBlockNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.solvefor) { - this->solvefor = new NameNodeList(); - for(NameNodeList::iterator iter = obj.solvefor->begin(); - iter != obj.solvefor->end(); iter++) { - this->solvefor->push_back((*iter)->clone()); - } - } - else - this->solvefor = NULL; - - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - this->symtab = NULL; - } - - /* destructor for KineticBlock ast node */ - KineticBlockNode::~KineticBlockNode() { - if(name) - delete name; - if (solvefor) { - for(NameNodeList::iterator iter = this->solvefor->begin(); - iter != this->solvefor->end(); iter++) { - delete (*iter); - } - } - - if(solvefor) - delete solvefor; - if(statementblock) - delete statementblock; - if(token) - delete token; - } - - /* visit Children method for ReactionStatement ast node */ - void ReactionStatementNode::visitChildren(Visitor* v) { - react1->accept(v); - op->accept(v); - if (this->react2) { - this->react2->accept(v); - } - - expr1->accept(v); - if (this->expr2) { - this->expr2->accept(v); - } - - } - - /* constructor for ReactionStatement ast node */ - ReactionStatementNode::ReactionStatementNode(ExpressionNode * react1, ReactionOperatorNode * op, ExpressionNode * react2, ExpressionNode * expr1, ExpressionNode * expr2) { - this->react1 = react1; - this->op = op; - this->react2 = react2; - this->expr1 = expr1; - this->expr2 = expr2; - } - - /* copy constructor for ReactionStatement ast node */ - ReactionStatementNode::ReactionStatementNode(const ReactionStatementNode& obj) { - if(obj.react1) - this->react1 = obj.react1->clone(); - else - this->react1 = NULL; - if(obj.op) - this->op = obj.op->clone(); - else - this->op = NULL; - if(obj.react2) - this->react2 = obj.react2->clone(); - else - this->react2 = NULL; - if(obj.expr1) - this->expr1 = obj.expr1->clone(); - else - this->expr1 = NULL; - if(obj.expr2) - this->expr2 = obj.expr2->clone(); - else - this->expr2 = NULL; - } - - /* destructor for ReactionStatement ast node */ - ReactionStatementNode::~ReactionStatementNode() { - if(react1) - delete react1; - if(op) - delete op; - if(react2) - delete react2; - if(expr1) - delete expr1; - if(expr2) - delete expr2; - } - - /* visit Children method for ReactVarName ast node */ - void ReactVarNameNode::visitChildren(Visitor* v) { - if (this->value) { - this->value->accept(v); - } - - name->accept(v); - } - - /* constructor for ReactVarName ast node */ - ReactVarNameNode::ReactVarNameNode(IntegerNode * value, VarNameNode * name) { - this->value = value; - this->name = name; - } - - /* copy constructor for ReactVarName ast node */ - ReactVarNameNode::ReactVarNameNode(const ReactVarNameNode& obj) { - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for ReactVarName ast node */ - ReactVarNameNode::~ReactVarNameNode() { - if(value) - delete value; - if(name) - delete name; - } - - /* visit Children method for LagStatement ast node */ - void LagStatementNode::visitChildren(Visitor* v) { - name->accept(v); - byname->accept(v); - } - - /* constructor for LagStatement ast node */ - LagStatementNode::LagStatementNode(IdentifierNode * name, NameNode * byname) { - this->name = name; - this->byname = byname; - } - - /* copy constructor for LagStatement ast node */ - LagStatementNode::LagStatementNode(const LagStatementNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.byname) - this->byname = obj.byname->clone(); - else - this->byname = NULL; - } - - /* destructor for LagStatement ast node */ - LagStatementNode::~LagStatementNode() { - if(name) - delete name; - if(byname) - delete byname; - } - - /* visit Children method for QueueStatement ast node */ - void QueueStatementNode::visitChildren(Visitor* v) { - qype->accept(v); - name->accept(v); - } - - /* constructor for QueueStatement ast node */ - QueueStatementNode::QueueStatementNode(QueueExpressionTypeNode * qype, IdentifierNode * name) { - this->qype = qype; - this->name = name; - } - - /* copy constructor for QueueStatement ast node */ - QueueStatementNode::QueueStatementNode(const QueueStatementNode& obj) { - if(obj.qype) - this->qype = obj.qype->clone(); - else - this->qype = NULL; - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for QueueStatement ast node */ - QueueStatementNode::~QueueStatementNode() { - if(qype) - delete qype; - if(name) - delete name; - } - - /* visit Children method for QueueExpressionType ast node */ - void QueueExpressionTypeNode::visitChildren(Visitor* v) { - /* no children */ - } - - /* constructor for QueueExpressionType ast node */ - QueueExpressionTypeNode::QueueExpressionTypeNode(QueueType value) { - this->value = value; - } - - /* copy constructor for QueueExpressionType ast node */ - QueueExpressionTypeNode::QueueExpressionTypeNode(const QueueExpressionTypeNode& obj) { - this->value = obj.value; - } - - /* destructor for QueueExpressionType ast node */ - QueueExpressionTypeNode::~QueueExpressionTypeNode() { - } - - /* visit Children method for MatchBlock ast node */ - void MatchBlockNode::visitChildren(Visitor* v) { - if (this->matchs) { - for(MatchNodeList::iterator iter = this->matchs->begin(); - iter != this->matchs->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for MatchBlock ast node */ - MatchBlockNode::MatchBlockNode(MatchNodeList * matchs) { - this->matchs = matchs; - this->symtab = NULL; - } - - /* copy constructor for MatchBlock ast node */ - MatchBlockNode::MatchBlockNode(const MatchBlockNode& obj) { - // cloning list - if (obj.matchs) { - this->matchs = new MatchNodeList(); - for(MatchNodeList::iterator iter = obj.matchs->begin(); - iter != obj.matchs->end(); iter++) { - this->matchs->push_back((*iter)->clone()); - } - } - else - this->matchs = NULL; - - this->symtab = NULL; - } - - /* destructor for MatchBlock ast node */ - MatchBlockNode::~MatchBlockNode() { - if (matchs) { - for(MatchNodeList::iterator iter = this->matchs->begin(); - iter != this->matchs->end(); iter++) { - delete (*iter); - } - } - - if(matchs) - delete matchs; - } - - /* visit Children method for Match ast node */ - void MatchNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->expression) { - this->expression->accept(v); - } - - } - - /* constructor for Match ast node */ - MatchNode::MatchNode(IdentifierNode * name, ExpressionNode * expression) { - this->name = name; - this->expression = expression; - } - - /* copy constructor for Match ast node */ - MatchNode::MatchNode(const MatchNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.expression) - this->expression = obj.expression->clone(); - else - this->expression = NULL; - } - - /* destructor for Match ast node */ - MatchNode::~MatchNode() { - if(name) - delete name; - if(expression) - delete expression; - } - - /* visit Children method for UnitBlock ast node */ - void UnitBlockNode::visitChildren(Visitor* v) { - if (this->definitions) { - for(ExpressionNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for UnitBlock ast node */ - UnitBlockNode::UnitBlockNode(ExpressionNodeList * definitions) { - this->definitions = definitions; - this->symtab = NULL; - } - - /* copy constructor for UnitBlock ast node */ - UnitBlockNode::UnitBlockNode(const UnitBlockNode& obj) { - // cloning list - if (obj.definitions) { - this->definitions = new ExpressionNodeList(); - for(ExpressionNodeList::iterator iter = obj.definitions->begin(); - iter != obj.definitions->end(); iter++) { - this->definitions->push_back((*iter)->clone()); - } - } - else - this->definitions = NULL; - - this->symtab = NULL; - } - - /* destructor for UnitBlock ast node */ - UnitBlockNode::~UnitBlockNode() { - if (definitions) { - for(ExpressionNodeList::iterator iter = this->definitions->begin(); - iter != this->definitions->end(); iter++) { - delete (*iter); - } - } - - if(definitions) - delete definitions; - } - - /* visit Children method for UnitDef ast node */ - void UnitDefNode::visitChildren(Visitor* v) { - unit1->accept(v); - unit2->accept(v); - } - - /* constructor for UnitDef ast node */ - UnitDefNode::UnitDefNode(UnitNode * unit1, UnitNode * unit2) { - this->unit1 = unit1; - this->unit2 = unit2; - } - - /* copy constructor for UnitDef ast node */ - UnitDefNode::UnitDefNode(const UnitDefNode& obj) { - if(obj.unit1) - this->unit1 = obj.unit1->clone(); - else - this->unit1 = NULL; - if(obj.unit2) - this->unit2 = obj.unit2->clone(); - else - this->unit2 = NULL; - } - - /* destructor for UnitDef ast node */ - UnitDefNode::~UnitDefNode() { - if(unit1) - delete unit1; - if(unit2) - delete unit2; - } - - /* visit Children method for Factordef ast node */ - void FactordefNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->value) { - this->value->accept(v); - } - - unit1->accept(v); - if (this->gt) { - this->gt->accept(v); - } - - if (this->unit2) { - this->unit2->accept(v); - } - - } - - /* constructor for Factordef ast node */ - FactordefNode::FactordefNode(NameNode * name, DoubleNode * value, UnitNode * unit1, BooleanNode * gt, UnitNode * unit2) { - this->name = name; - this->value = value; - this->unit1 = unit1; - this->gt = gt; - this->unit2 = unit2; - this->token = NULL; - } - - /* copy constructor for Factordef ast node */ - FactordefNode::FactordefNode(const FactordefNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - if(obj.unit1) - this->unit1 = obj.unit1->clone(); - else - this->unit1 = NULL; - if(obj.gt) - this->gt = obj.gt->clone(); - else - this->gt = NULL; - if(obj.unit2) - this->unit2 = obj.unit2->clone(); - else - this->unit2 = NULL; - if(obj.token) - this->token = obj.token->clone(); - else - this->token = NULL; - } - - /* destructor for Factordef ast node */ - FactordefNode::~FactordefNode() { - if(name) - delete name; - if(value) - delete value; - if(unit1) - delete unit1; - if(gt) - delete gt; - if(unit2) - delete unit2; - if(token) - delete token; - } - - /* visit Children method for ConstantStatement ast node */ - void ConstantStatementNode::visitChildren(Visitor* v) { - name->accept(v); - value->accept(v); - if (this->unit) { - this->unit->accept(v); - } - - } - - /* constructor for ConstantStatement ast node */ - ConstantStatementNode::ConstantStatementNode(NameNode * name, NumberNode * value, UnitNode * unit) { - this->name = name; - this->value = value; - this->unit = unit; - } - - /* copy constructor for ConstantStatement ast node */ - ConstantStatementNode::ConstantStatementNode(const ConstantStatementNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - if(obj.unit) - this->unit = obj.unit->clone(); - else - this->unit = NULL; - } - - /* destructor for ConstantStatement ast node */ - ConstantStatementNode::~ConstantStatementNode() { - if(name) - delete name; - if(value) - delete value; - if(unit) - delete unit; - } - - /* visit Children method for ConstantBlock ast node */ - void ConstantBlockNode::visitChildren(Visitor* v) { - if (this->statements) { - for(ConstantStatementNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for ConstantBlock ast node */ - ConstantBlockNode::ConstantBlockNode(ConstantStatementNodeList * statements) { - this->statements = statements; - this->symtab = NULL; - } - - /* copy constructor for ConstantBlock ast node */ - ConstantBlockNode::ConstantBlockNode(const ConstantBlockNode& obj) { - // cloning list - if (obj.statements) { - this->statements = new ConstantStatementNodeList(); - for(ConstantStatementNodeList::iterator iter = obj.statements->begin(); - iter != obj.statements->end(); iter++) { - this->statements->push_back((*iter)->clone()); - } - } - else - this->statements = NULL; - - this->symtab = NULL; - } - - /* destructor for ConstantBlock ast node */ - ConstantBlockNode::~ConstantBlockNode() { - if (statements) { - for(ConstantStatementNodeList::iterator iter = this->statements->begin(); - iter != this->statements->end(); iter++) { - delete (*iter); - } - } - - if(statements) - delete statements; - } - - /* visit Children method for TableStatement ast node */ - void TableStatementNode::visitChildren(Visitor* v) { - if (this->tablst) { - for(NameNodeList::iterator iter = this->tablst->begin(); - iter != this->tablst->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->dependlst) { - for(NameNodeList::iterator iter = this->dependlst->begin(); - iter != this->dependlst->end(); iter++) { - (*iter)->accept(v); - } - } - - from->accept(v); - to->accept(v); - with->accept(v); - } - - /* constructor for TableStatement ast node */ - TableStatementNode::TableStatementNode(NameNodeList * tablst, NameNodeList * dependlst, ExpressionNode * from, ExpressionNode * to, IntegerNode * with) { - this->tablst = tablst; - this->dependlst = dependlst; - this->from = from; - this->to = to; - this->with = with; - } - - /* copy constructor for TableStatement ast node */ - TableStatementNode::TableStatementNode(const TableStatementNode& obj) { - // cloning list - if (obj.tablst) { - this->tablst = new NameNodeList(); - for(NameNodeList::iterator iter = obj.tablst->begin(); - iter != obj.tablst->end(); iter++) { - this->tablst->push_back((*iter)->clone()); - } - } - else - this->tablst = NULL; - - // cloning list - if (obj.dependlst) { - this->dependlst = new NameNodeList(); - for(NameNodeList::iterator iter = obj.dependlst->begin(); - iter != obj.dependlst->end(); iter++) { - this->dependlst->push_back((*iter)->clone()); - } - } - else - this->dependlst = NULL; - - if(obj.from) - this->from = obj.from->clone(); - else - this->from = NULL; - if(obj.to) - this->to = obj.to->clone(); - else - this->to = NULL; - if(obj.with) - this->with = obj.with->clone(); - else - this->with = NULL; - } - - /* destructor for TableStatement ast node */ - TableStatementNode::~TableStatementNode() { - if (tablst) { - for(NameNodeList::iterator iter = this->tablst->begin(); - iter != this->tablst->end(); iter++) { - delete (*iter); - } - } - - if(tablst) - delete tablst; - if (dependlst) { - for(NameNodeList::iterator iter = this->dependlst->begin(); - iter != this->dependlst->end(); iter++) { - delete (*iter); - } - } - - if(dependlst) - delete dependlst; - if(from) - delete from; - if(to) - delete to; - if(with) - delete with; - } - - /* visit Children method for NeuronBlock ast node */ - void NeuronBlockNode::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - - } - - /* constructor for NeuronBlock ast node */ - NeuronBlockNode::NeuronBlockNode(StatementBlockNode * statementblock) { - this->statementblock = statementblock; - this->symtab = NULL; - } - - /* copy constructor for NeuronBlock ast node */ - NeuronBlockNode::NeuronBlockNode(const NeuronBlockNode& obj) { - if(obj.statementblock) - this->statementblock = obj.statementblock->clone(); - else - this->statementblock = NULL; - this->symtab = NULL; - } - - /* destructor for NeuronBlock ast node */ - NeuronBlockNode::~NeuronBlockNode() { - if(statementblock) - delete statementblock; - } - - /* visit Children method for ReadIonVar ast node */ - void ReadIonVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ReadIonVar ast node */ - ReadIonVarNode::ReadIonVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for ReadIonVar ast node */ - ReadIonVarNode::ReadIonVarNode(const ReadIonVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for ReadIonVar ast node */ - ReadIonVarNode::~ReadIonVarNode() { - if(name) - delete name; - } - - /* visit Children method for WriteIonVar ast node */ - void WriteIonVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for WriteIonVar ast node */ - WriteIonVarNode::WriteIonVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for WriteIonVar ast node */ - WriteIonVarNode::WriteIonVarNode(const WriteIonVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for WriteIonVar ast node */ - WriteIonVarNode::~WriteIonVarNode() { - if(name) - delete name; - } - - /* visit Children method for NonspeCurVar ast node */ - void NonspeCurVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for NonspeCurVar ast node */ - NonspeCurVarNode::NonspeCurVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for NonspeCurVar ast node */ - NonspeCurVarNode::NonspeCurVarNode(const NonspeCurVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for NonspeCurVar ast node */ - NonspeCurVarNode::~NonspeCurVarNode() { - if(name) - delete name; - } - - /* visit Children method for ElectrodeCurVar ast node */ - void ElectrodeCurVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ElectrodeCurVar ast node */ - ElectrodeCurVarNode::ElectrodeCurVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for ElectrodeCurVar ast node */ - ElectrodeCurVarNode::ElectrodeCurVarNode(const ElectrodeCurVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for ElectrodeCurVar ast node */ - ElectrodeCurVarNode::~ElectrodeCurVarNode() { - if(name) - delete name; - } - - /* visit Children method for SectionVar ast node */ - void SectionVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for SectionVar ast node */ - SectionVarNode::SectionVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for SectionVar ast node */ - SectionVarNode::SectionVarNode(const SectionVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for SectionVar ast node */ - SectionVarNode::~SectionVarNode() { - if(name) - delete name; - } - - /* visit Children method for RangeVar ast node */ - void RangeVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for RangeVar ast node */ - RangeVarNode::RangeVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for RangeVar ast node */ - RangeVarNode::RangeVarNode(const RangeVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for RangeVar ast node */ - RangeVarNode::~RangeVarNode() { - if(name) - delete name; - } - - /* visit Children method for GlobalVar ast node */ - void GlobalVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for GlobalVar ast node */ - GlobalVarNode::GlobalVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for GlobalVar ast node */ - GlobalVarNode::GlobalVarNode(const GlobalVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for GlobalVar ast node */ - GlobalVarNode::~GlobalVarNode() { - if(name) - delete name; - } - - /* visit Children method for PointerVar ast node */ - void PointerVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for PointerVar ast node */ - PointerVarNode::PointerVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for PointerVar ast node */ - PointerVarNode::PointerVarNode(const PointerVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for PointerVar ast node */ - PointerVarNode::~PointerVarNode() { - if(name) - delete name; - } - - /* visit Children method for BbcorePointerVar ast node */ - void BbcorePointerVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for BbcorePointerVar ast node */ - BbcorePointerVarNode::BbcorePointerVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for BbcorePointerVar ast node */ - BbcorePointerVarNode::BbcorePointerVarNode(const BbcorePointerVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for BbcorePointerVar ast node */ - BbcorePointerVarNode::~BbcorePointerVarNode() { - if(name) - delete name; - } - - /* visit Children method for ExternVar ast node */ - void ExternVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ExternVar ast node */ - ExternVarNode::ExternVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for ExternVar ast node */ - ExternVarNode::ExternVarNode(const ExternVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for ExternVar ast node */ - ExternVarNode::~ExternVarNode() { - if(name) - delete name; - } - - /* visit Children method for ThreadsafeVar ast node */ - void ThreadsafeVarNode::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ThreadsafeVar ast node */ - ThreadsafeVarNode::ThreadsafeVarNode(NameNode * name) { - this->name = name; - } - - /* copy constructor for ThreadsafeVar ast node */ - ThreadsafeVarNode::ThreadsafeVarNode(const ThreadsafeVarNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for ThreadsafeVar ast node */ - ThreadsafeVarNode::~ThreadsafeVarNode() { - if(name) - delete name; - } - - /* visit Children method for NrnSuffix ast node */ - void NrnSuffixNode::visitChildren(Visitor* v) { - type->accept(v); - name->accept(v); - } - - /* constructor for NrnSuffix ast node */ - NrnSuffixNode::NrnSuffixNode(NameNode * type, NameNode * name) { - this->type = type; - this->name = name; - } - - /* copy constructor for NrnSuffix ast node */ - NrnSuffixNode::NrnSuffixNode(const NrnSuffixNode& obj) { - if(obj.type) - this->type = obj.type->clone(); - else - this->type = NULL; - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - } - - /* destructor for NrnSuffix ast node */ - NrnSuffixNode::~NrnSuffixNode() { - if(type) - delete type; - if(name) - delete name; - } - - /* visit Children method for NrnUseion ast node */ - void NrnUseionNode::visitChildren(Visitor* v) { - name->accept(v); - if (this->readlist) { - for(ReadIonVarNodeList::iterator iter = this->readlist->begin(); - iter != this->readlist->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->writelist) { - for(WriteIonVarNodeList::iterator iter = this->writelist->begin(); - iter != this->writelist->end(); iter++) { - (*iter)->accept(v); - } - } - - if (this->valence) { - this->valence->accept(v); - } - - } - - /* constructor for NrnUseion ast node */ - NrnUseionNode::NrnUseionNode(NameNode * name, ReadIonVarNodeList * readlist, WriteIonVarNodeList * writelist, ValenceNode * valence) { - this->name = name; - this->readlist = readlist; - this->writelist = writelist; - this->valence = valence; - } - - /* copy constructor for NrnUseion ast node */ - NrnUseionNode::NrnUseionNode(const NrnUseionNode& obj) { - if(obj.name) - this->name = obj.name->clone(); - else - this->name = NULL; - // cloning list - if (obj.readlist) { - this->readlist = new ReadIonVarNodeList(); - for(ReadIonVarNodeList::iterator iter = obj.readlist->begin(); - iter != obj.readlist->end(); iter++) { - this->readlist->push_back((*iter)->clone()); - } - } - else - this->readlist = NULL; - - // cloning list - if (obj.writelist) { - this->writelist = new WriteIonVarNodeList(); - for(WriteIonVarNodeList::iterator iter = obj.writelist->begin(); - iter != obj.writelist->end(); iter++) { - this->writelist->push_back((*iter)->clone()); - } - } - else - this->writelist = NULL; - - if(obj.valence) - this->valence = obj.valence->clone(); - else - this->valence = NULL; - } - - /* destructor for NrnUseion ast node */ - NrnUseionNode::~NrnUseionNode() { - if(name) - delete name; - if (readlist) { - for(ReadIonVarNodeList::iterator iter = this->readlist->begin(); - iter != this->readlist->end(); iter++) { - delete (*iter); - } - } - - if(readlist) - delete readlist; - if (writelist) { - for(WriteIonVarNodeList::iterator iter = this->writelist->begin(); - iter != this->writelist->end(); iter++) { - delete (*iter); - } - } - - if(writelist) - delete writelist; - if(valence) - delete valence; - } - - /* visit Children method for NrnNonspecific ast node */ - void NrnNonspecificNode::visitChildren(Visitor* v) { - if (this->currents) { - for(NonspeCurVarNodeList::iterator iter = this->currents->begin(); - iter != this->currents->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnNonspecific ast node */ - NrnNonspecificNode::NrnNonspecificNode(NonspeCurVarNodeList * currents) { - this->currents = currents; - } - - /* copy constructor for NrnNonspecific ast node */ - NrnNonspecificNode::NrnNonspecificNode(const NrnNonspecificNode& obj) { - // cloning list - if (obj.currents) { - this->currents = new NonspeCurVarNodeList(); - for(NonspeCurVarNodeList::iterator iter = obj.currents->begin(); - iter != obj.currents->end(); iter++) { - this->currents->push_back((*iter)->clone()); - } - } - else - this->currents = NULL; - - } - - /* destructor for NrnNonspecific ast node */ - NrnNonspecificNode::~NrnNonspecificNode() { - if (currents) { - for(NonspeCurVarNodeList::iterator iter = this->currents->begin(); - iter != this->currents->end(); iter++) { - delete (*iter); - } - } - - if(currents) - delete currents; - } - - /* visit Children method for NrnElctrodeCurrent ast node */ - void NrnElctrodeCurrentNode::visitChildren(Visitor* v) { - if (this->ecurrents) { - for(ElectrodeCurVarNodeList::iterator iter = this->ecurrents->begin(); - iter != this->ecurrents->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnElctrodeCurrent ast node */ - NrnElctrodeCurrentNode::NrnElctrodeCurrentNode(ElectrodeCurVarNodeList * ecurrents) { - this->ecurrents = ecurrents; - } - - /* copy constructor for NrnElctrodeCurrent ast node */ - NrnElctrodeCurrentNode::NrnElctrodeCurrentNode(const NrnElctrodeCurrentNode& obj) { - // cloning list - if (obj.ecurrents) { - this->ecurrents = new ElectrodeCurVarNodeList(); - for(ElectrodeCurVarNodeList::iterator iter = obj.ecurrents->begin(); - iter != obj.ecurrents->end(); iter++) { - this->ecurrents->push_back((*iter)->clone()); - } - } - else - this->ecurrents = NULL; - - } - - /* destructor for NrnElctrodeCurrent ast node */ - NrnElctrodeCurrentNode::~NrnElctrodeCurrentNode() { - if (ecurrents) { - for(ElectrodeCurVarNodeList::iterator iter = this->ecurrents->begin(); - iter != this->ecurrents->end(); iter++) { - delete (*iter); - } - } - - if(ecurrents) - delete ecurrents; - } - - /* visit Children method for NrnSection ast node */ - void NrnSectionNode::visitChildren(Visitor* v) { - if (this->sections) { - for(SectionVarNodeList::iterator iter = this->sections->begin(); - iter != this->sections->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnSection ast node */ - NrnSectionNode::NrnSectionNode(SectionVarNodeList * sections) { - this->sections = sections; - } - - /* copy constructor for NrnSection ast node */ - NrnSectionNode::NrnSectionNode(const NrnSectionNode& obj) { - // cloning list - if (obj.sections) { - this->sections = new SectionVarNodeList(); - for(SectionVarNodeList::iterator iter = obj.sections->begin(); - iter != obj.sections->end(); iter++) { - this->sections->push_back((*iter)->clone()); - } - } - else - this->sections = NULL; - - } - - /* destructor for NrnSection ast node */ - NrnSectionNode::~NrnSectionNode() { - if (sections) { - for(SectionVarNodeList::iterator iter = this->sections->begin(); - iter != this->sections->end(); iter++) { - delete (*iter); - } - } - - if(sections) - delete sections; - } - - /* visit Children method for NrnRange ast node */ - void NrnRangeNode::visitChildren(Visitor* v) { - if (this->range_vars) { - for(RangeVarNodeList::iterator iter = this->range_vars->begin(); - iter != this->range_vars->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnRange ast node */ - NrnRangeNode::NrnRangeNode(RangeVarNodeList * range_vars) { - this->range_vars = range_vars; - } - - /* copy constructor for NrnRange ast node */ - NrnRangeNode::NrnRangeNode(const NrnRangeNode& obj) { - // cloning list - if (obj.range_vars) { - this->range_vars = new RangeVarNodeList(); - for(RangeVarNodeList::iterator iter = obj.range_vars->begin(); - iter != obj.range_vars->end(); iter++) { - this->range_vars->push_back((*iter)->clone()); - } - } - else - this->range_vars = NULL; - - } - - /* destructor for NrnRange ast node */ - NrnRangeNode::~NrnRangeNode() { - if (range_vars) { - for(RangeVarNodeList::iterator iter = this->range_vars->begin(); - iter != this->range_vars->end(); iter++) { - delete (*iter); - } - } - - if(range_vars) - delete range_vars; - } - - /* visit Children method for NrnGlobal ast node */ - void NrnGlobalNode::visitChildren(Visitor* v) { - if (this->global_vars) { - for(GlobalVarNodeList::iterator iter = this->global_vars->begin(); - iter != this->global_vars->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnGlobal ast node */ - NrnGlobalNode::NrnGlobalNode(GlobalVarNodeList * global_vars) { - this->global_vars = global_vars; - } - - /* copy constructor for NrnGlobal ast node */ - NrnGlobalNode::NrnGlobalNode(const NrnGlobalNode& obj) { - // cloning list - if (obj.global_vars) { - this->global_vars = new GlobalVarNodeList(); - for(GlobalVarNodeList::iterator iter = obj.global_vars->begin(); - iter != obj.global_vars->end(); iter++) { - this->global_vars->push_back((*iter)->clone()); - } - } - else - this->global_vars = NULL; - - } - - /* destructor for NrnGlobal ast node */ - NrnGlobalNode::~NrnGlobalNode() { - if (global_vars) { - for(GlobalVarNodeList::iterator iter = this->global_vars->begin(); - iter != this->global_vars->end(); iter++) { - delete (*iter); - } - } - - if(global_vars) - delete global_vars; - } - - /* visit Children method for NrnPointer ast node */ - void NrnPointerNode::visitChildren(Visitor* v) { - if (this->pointers) { - for(PointerVarNodeList::iterator iter = this->pointers->begin(); - iter != this->pointers->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnPointer ast node */ - NrnPointerNode::NrnPointerNode(PointerVarNodeList * pointers) { - this->pointers = pointers; - } - - /* copy constructor for NrnPointer ast node */ - NrnPointerNode::NrnPointerNode(const NrnPointerNode& obj) { - // cloning list - if (obj.pointers) { - this->pointers = new PointerVarNodeList(); - for(PointerVarNodeList::iterator iter = obj.pointers->begin(); - iter != obj.pointers->end(); iter++) { - this->pointers->push_back((*iter)->clone()); - } - } - else - this->pointers = NULL; - - } - - /* destructor for NrnPointer ast node */ - NrnPointerNode::~NrnPointerNode() { - if (pointers) { - for(PointerVarNodeList::iterator iter = this->pointers->begin(); - iter != this->pointers->end(); iter++) { - delete (*iter); - } - } - - if(pointers) - delete pointers; - } - - /* visit Children method for NrnBbcorePtr ast node */ - void NrnBbcorePtrNode::visitChildren(Visitor* v) { - if (this->bbcore_pointers) { - for(BbcorePointerVarNodeList::iterator iter = this->bbcore_pointers->begin(); - iter != this->bbcore_pointers->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnBbcorePtr ast node */ - NrnBbcorePtrNode::NrnBbcorePtrNode(BbcorePointerVarNodeList * bbcore_pointers) { - this->bbcore_pointers = bbcore_pointers; - } - - /* copy constructor for NrnBbcorePtr ast node */ - NrnBbcorePtrNode::NrnBbcorePtrNode(const NrnBbcorePtrNode& obj) { - // cloning list - if (obj.bbcore_pointers) { - this->bbcore_pointers = new BbcorePointerVarNodeList(); - for(BbcorePointerVarNodeList::iterator iter = obj.bbcore_pointers->begin(); - iter != obj.bbcore_pointers->end(); iter++) { - this->bbcore_pointers->push_back((*iter)->clone()); - } - } - else - this->bbcore_pointers = NULL; - - } - - /* destructor for NrnBbcorePtr ast node */ - NrnBbcorePtrNode::~NrnBbcorePtrNode() { - if (bbcore_pointers) { - for(BbcorePointerVarNodeList::iterator iter = this->bbcore_pointers->begin(); - iter != this->bbcore_pointers->end(); iter++) { - delete (*iter); - } - } - - if(bbcore_pointers) - delete bbcore_pointers; - } - - /* visit Children method for NrnExternal ast node */ - void NrnExternalNode::visitChildren(Visitor* v) { - if (this->externals) { - for(ExternVarNodeList::iterator iter = this->externals->begin(); - iter != this->externals->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnExternal ast node */ - NrnExternalNode::NrnExternalNode(ExternVarNodeList * externals) { - this->externals = externals; - } - - /* copy constructor for NrnExternal ast node */ - NrnExternalNode::NrnExternalNode(const NrnExternalNode& obj) { - // cloning list - if (obj.externals) { - this->externals = new ExternVarNodeList(); - for(ExternVarNodeList::iterator iter = obj.externals->begin(); - iter != obj.externals->end(); iter++) { - this->externals->push_back((*iter)->clone()); - } - } - else - this->externals = NULL; - - } - - /* destructor for NrnExternal ast node */ - NrnExternalNode::~NrnExternalNode() { - if (externals) { - for(ExternVarNodeList::iterator iter = this->externals->begin(); - iter != this->externals->end(); iter++) { - delete (*iter); - } - } - - if(externals) - delete externals; - } - - /* visit Children method for NrnThreadSafe ast node */ - void NrnThreadSafeNode::visitChildren(Visitor* v) { - if (this->threadsafe) { - for(ThreadsafeVarNodeList::iterator iter = this->threadsafe->begin(); - iter != this->threadsafe->end(); iter++) { - (*iter)->accept(v); - } - } - - } - - /* constructor for NrnThreadSafe ast node */ - NrnThreadSafeNode::NrnThreadSafeNode(ThreadsafeVarNodeList * threadsafe) { - this->threadsafe = threadsafe; - } - - /* copy constructor for NrnThreadSafe ast node */ - NrnThreadSafeNode::NrnThreadSafeNode(const NrnThreadSafeNode& obj) { - // cloning list - if (obj.threadsafe) { - this->threadsafe = new ThreadsafeVarNodeList(); - for(ThreadsafeVarNodeList::iterator iter = obj.threadsafe->begin(); - iter != obj.threadsafe->end(); iter++) { - this->threadsafe->push_back((*iter)->clone()); - } - } - else - this->threadsafe = NULL; - - } - - /* destructor for NrnThreadSafe ast node */ - NrnThreadSafeNode::~NrnThreadSafeNode() { - if (threadsafe) { - for(ThreadsafeVarNodeList::iterator iter = this->threadsafe->begin(); - iter != this->threadsafe->end(); iter++) { - delete (*iter); - } - } - - if(threadsafe) - delete threadsafe; - } - - /* visit Children method for Verbatim ast node */ - void VerbatimNode::visitChildren(Visitor* v) { - statement->accept(v); - } - - /* constructor for Verbatim ast node */ - VerbatimNode::VerbatimNode(StringNode * statement) { - this->statement = statement; - } - - /* copy constructor for Verbatim ast node */ - VerbatimNode::VerbatimNode(const VerbatimNode& obj) { - if(obj.statement) - this->statement = obj.statement->clone(); - else - this->statement = NULL; - } - - /* destructor for Verbatim ast node */ - VerbatimNode::~VerbatimNode() { - if(statement) - delete statement; - } - - /* visit Children method for Comment ast node */ - void CommentNode::visitChildren(Visitor* v) { - comment->accept(v); - } - - /* constructor for Comment ast node */ - CommentNode::CommentNode(StringNode * comment) { - this->comment = comment; - } - - /* copy constructor for Comment ast node */ - CommentNode::CommentNode(const CommentNode& obj) { - if(obj.comment) - this->comment = obj.comment->clone(); - else - this->comment = NULL; - } - - /* destructor for Comment ast node */ - CommentNode::~CommentNode() { - if(comment) - delete comment; - } - - /* visit Children method for Valence ast node */ - void ValenceNode::visitChildren(Visitor* v) { - type->accept(v); - value->accept(v); - } - - /* constructor for Valence ast node */ - ValenceNode::ValenceNode(NameNode * type, DoubleNode * value) { - this->type = type; - this->value = value; - } - - /* copy constructor for Valence ast node */ - ValenceNode::ValenceNode(const ValenceNode& obj) { - if(obj.type) - this->type = obj.type->clone(); - else - this->type = NULL; - if(obj.value) - this->value = obj.value->clone(); - else - this->value = NULL; - } - - /* destructor for Valence ast node */ - ValenceNode::~ValenceNode() { - if(type) - delete type; - if(value) - delete value; - } - -} //namespace ast + void Statement::visitChildren(Visitor* v) {} + void Expression::visitChildren(Visitor* v) {} + void Block::visitChildren(Visitor* v) {} + void Identifier::visitChildren(Visitor* v) {} + void Number::visitChildren(Visitor* v) {} + void String::visitChildren(Visitor* v) {} + /* constructor for String ast node */ + String::String(std::string value) + : value(value) + { + } + + /* copy constructor for String ast node */ + String::String(const String& obj) + { + this->value = obj.value; + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for Integer ast node */ + void Integer::visitChildren(Visitor* v) { + if (this->macroname) { + this->macroname->accept(v); + } + } + + /* constructor for Integer ast node */ + Integer::Integer(int value, Name* macroname) + : value(value) + { + this->macroname = std::shared_ptr(macroname); + } + + /* copy constructor for Integer ast node */ + Integer::Integer(const Integer& obj) + { + this->value = obj.value; + if(obj.macroname) + this->macroname = std::shared_ptr(obj.macroname->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + void Float::visitChildren(Visitor* v) {} + /* constructor for Float ast node */ + Float::Float(float value) + : value(value) + { + } + + /* copy constructor for Float ast node */ + Float::Float(const Float& obj) + { + this->value = obj.value; + } + + void Double::visitChildren(Visitor* v) {} + /* constructor for Double ast node */ + Double::Double(double value) + : value(value) + { + } + + /* copy constructor for Double ast node */ + Double::Double(const Double& obj) + { + this->value = obj.value; + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + void Boolean::visitChildren(Visitor* v) {} + /* constructor for Boolean ast node */ + Boolean::Boolean(int value) + : value(value) + { + } + + /* copy constructor for Boolean ast node */ + Boolean::Boolean(const Boolean& obj) + { + this->value = obj.value; + } + + /* visit method for Name ast node */ + void Name::visitChildren(Visitor* v) { + value->accept(v); + } + + /* constructor for Name ast node */ + Name::Name(String* value) + { + this->value = std::shared_ptr(value); + } + + /* copy constructor for Name ast node */ + Name::Name(const Name& obj) + { + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for PrimeName ast node */ + void PrimeName::visitChildren(Visitor* v) { + value->accept(v); + order->accept(v); + } + + /* constructor for PrimeName ast node */ + PrimeName::PrimeName(String* value, Integer* order) + { + this->value = std::shared_ptr(value); + this->order = std::shared_ptr(order); + } + + /* copy constructor for PrimeName ast node */ + PrimeName::PrimeName(const PrimeName& obj) + { + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + if(obj.order) + this->order = std::shared_ptr(obj.order->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for VarName ast node */ + void VarName::visitChildren(Visitor* v) { + name->accept(v); + if (this->at_index) { + this->at_index->accept(v); + } + } + + /* constructor for VarName ast node */ + VarName::VarName(Identifier* name, Integer* at_index) + { + this->name = std::shared_ptr(name); + this->at_index = std::shared_ptr(at_index); + } + + /* copy constructor for VarName ast node */ + VarName::VarName(const VarName& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.at_index) + this->at_index = std::shared_ptr(obj.at_index->clone()); + } + + /* visit method for IndexedName ast node */ + void IndexedName::visitChildren(Visitor* v) { + name->accept(v); + index->accept(v); + } + + /* constructor for IndexedName ast node */ + IndexedName::IndexedName(Identifier* name, Expression* index) + { + this->name = std::shared_ptr(name); + this->index = std::shared_ptr(index); + } + + /* copy constructor for IndexedName ast node */ + IndexedName::IndexedName(const IndexedName& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.index) + this->index = std::shared_ptr(obj.index->clone()); + } + + /* visit method for Argument ast node */ + void Argument::visitChildren(Visitor* v) { + name->accept(v); + if (this->unit) { + this->unit->accept(v); + } + } + + /* constructor for Argument ast node */ + Argument::Argument(Identifier* name, Unit* unit) + { + this->name = std::shared_ptr(name); + this->unit = std::shared_ptr(unit); + } + + /* copy constructor for Argument ast node */ + Argument::Argument(const Argument& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + } + + /* visit method for ReactVarName ast node */ + void ReactVarName::visitChildren(Visitor* v) { + if (this->value) { + this->value->accept(v); + } + name->accept(v); + } + + /* constructor for ReactVarName ast node */ + ReactVarName::ReactVarName(Integer* value, VarName* name) + { + this->value = std::shared_ptr(value); + this->name = std::shared_ptr(name); + } + + /* copy constructor for ReactVarName ast node */ + ReactVarName::ReactVarName(const ReactVarName& obj) + { + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ReadIonVar ast node */ + void ReadIonVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ReadIonVar ast node */ + ReadIonVar::ReadIonVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for ReadIonVar ast node */ + ReadIonVar::ReadIonVar(const ReadIonVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for WriteIonVar ast node */ + void WriteIonVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for WriteIonVar ast node */ + WriteIonVar::WriteIonVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for WriteIonVar ast node */ + WriteIonVar::WriteIonVar(const WriteIonVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for NonspeCurVar ast node */ + void NonspeCurVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for NonspeCurVar ast node */ + NonspeCurVar::NonspeCurVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for NonspeCurVar ast node */ + NonspeCurVar::NonspeCurVar(const NonspeCurVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ElectrodeCurVar ast node */ + void ElectrodeCurVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ElectrodeCurVar ast node */ + ElectrodeCurVar::ElectrodeCurVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for ElectrodeCurVar ast node */ + ElectrodeCurVar::ElectrodeCurVar(const ElectrodeCurVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for SectionVar ast node */ + void SectionVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for SectionVar ast node */ + SectionVar::SectionVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for SectionVar ast node */ + SectionVar::SectionVar(const SectionVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for RangeVar ast node */ + void RangeVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for RangeVar ast node */ + RangeVar::RangeVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for RangeVar ast node */ + RangeVar::RangeVar(const RangeVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for GlobalVar ast node */ + void GlobalVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for GlobalVar ast node */ + GlobalVar::GlobalVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for GlobalVar ast node */ + GlobalVar::GlobalVar(const GlobalVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for PointerVar ast node */ + void PointerVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for PointerVar ast node */ + PointerVar::PointerVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for PointerVar ast node */ + PointerVar::PointerVar(const PointerVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for BbcorePointerVar ast node */ + void BbcorePointerVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for BbcorePointerVar ast node */ + BbcorePointerVar::BbcorePointerVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for BbcorePointerVar ast node */ + BbcorePointerVar::BbcorePointerVar(const BbcorePointerVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ExternVar ast node */ + void ExternVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ExternVar ast node */ + ExternVar::ExternVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for ExternVar ast node */ + ExternVar::ExternVar(const ExternVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ThreadsafeVar ast node */ + void ThreadsafeVar::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for ThreadsafeVar ast node */ + ThreadsafeVar::ThreadsafeVar(Name* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for ThreadsafeVar ast node */ + ThreadsafeVar::ThreadsafeVar(const ThreadsafeVar& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ParamBlock ast node */ + void ParamBlock::visitChildren(Visitor* v) { + for(auto& item : this->statements) { + item->accept(v); + } + } + + /* constructor for ParamBlock ast node */ + ParamBlock::ParamBlock(ParamAssignList statements) + : statements(statements) + { + } + + /* copy constructor for ParamBlock ast node */ + ParamBlock::ParamBlock(const ParamBlock& obj) + { + for(auto& item : obj.statements) { + this->statements.push_back(std::shared_ptr< ParamAssign>(item->clone())); + } + } + + /* visit method for StepBlock ast node */ + void StepBlock::visitChildren(Visitor* v) { + for(auto& item : this->statements) { + item->accept(v); + } + } + + /* constructor for StepBlock ast node */ + StepBlock::StepBlock(SteppedList statements) + : statements(statements) + { + } + + /* copy constructor for StepBlock ast node */ + StepBlock::StepBlock(const StepBlock& obj) + { + for(auto& item : obj.statements) { + this->statements.push_back(std::shared_ptr< Stepped>(item->clone())); + } + } + + /* visit method for IndependentBlock ast node */ + void IndependentBlock::visitChildren(Visitor* v) { + for(auto& item : this->definitions) { + item->accept(v); + } + } + + /* constructor for IndependentBlock ast node */ + IndependentBlock::IndependentBlock(IndependentDefList definitions) + : definitions(definitions) + { + } + + /* copy constructor for IndependentBlock ast node */ + IndependentBlock::IndependentBlock(const IndependentBlock& obj) + { + for(auto& item : obj.definitions) { + this->definitions.push_back(std::shared_ptr< IndependentDef>(item->clone())); + } + } + + /* visit method for DependentBlock ast node */ + void DependentBlock::visitChildren(Visitor* v) { + for(auto& item : this->definitions) { + item->accept(v); + } + } + + /* constructor for DependentBlock ast node */ + DependentBlock::DependentBlock(DependentDefList definitions) + : definitions(definitions) + { + } + + /* copy constructor for DependentBlock ast node */ + DependentBlock::DependentBlock(const DependentBlock& obj) + { + for(auto& item : obj.definitions) { + this->definitions.push_back(std::shared_ptr< DependentDef>(item->clone())); + } + } + + /* visit method for StateBlock ast node */ + void StateBlock::visitChildren(Visitor* v) { + for(auto& item : this->definitions) { + item->accept(v); + } + } + + /* constructor for StateBlock ast node */ + StateBlock::StateBlock(DependentDefList definitions) + : definitions(definitions) + { + } + + /* copy constructor for StateBlock ast node */ + StateBlock::StateBlock(const StateBlock& obj) + { + for(auto& item : obj.definitions) { + this->definitions.push_back(std::shared_ptr< DependentDef>(item->clone())); + } + } + + /* visit method for PlotBlock ast node */ + void PlotBlock::visitChildren(Visitor* v) { + plot->accept(v); + } + + /* constructor for PlotBlock ast node */ + PlotBlock::PlotBlock(PlotDeclaration* plot) + { + this->plot = std::shared_ptr(plot); + } + + /* copy constructor for PlotBlock ast node */ + PlotBlock::PlotBlock(const PlotBlock& obj) + { + if(obj.plot) + this->plot = std::shared_ptr(obj.plot->clone()); + } + + /* visit method for InitialBlock ast node */ + void InitialBlock::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for InitialBlock ast node */ + InitialBlock::InitialBlock(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for InitialBlock ast node */ + InitialBlock::InitialBlock(const InitialBlock& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for ConstructorBlock ast node */ + void ConstructorBlock::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for ConstructorBlock ast node */ + ConstructorBlock::ConstructorBlock(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for ConstructorBlock ast node */ + ConstructorBlock::ConstructorBlock(const ConstructorBlock& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for DestructorBlock ast node */ + void DestructorBlock::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for DestructorBlock ast node */ + DestructorBlock::DestructorBlock(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for DestructorBlock ast node */ + DestructorBlock::DestructorBlock(const DestructorBlock& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for StatementBlock ast node */ + void StatementBlock::visitChildren(Visitor* v) { + for(auto& item : this->statements) { + item->accept(v); + } + } + + /* constructor for StatementBlock ast node */ + StatementBlock::StatementBlock(StatementList statements) + : statements(statements) + { + } + + /* copy constructor for StatementBlock ast node */ + StatementBlock::StatementBlock(const StatementBlock& obj) + { + for(auto& item : obj.statements) { + this->statements.push_back(std::shared_ptr< Statement>(item->clone())); + } + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for DerivativeBlock ast node */ + void DerivativeBlock::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for DerivativeBlock ast node */ + DerivativeBlock::DerivativeBlock(Name* name, StatementBlock* statementblock) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for DerivativeBlock ast node */ + DerivativeBlock::DerivativeBlock(const DerivativeBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for LinearBlock ast node */ + void LinearBlock::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->solvefor) { + item->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for LinearBlock ast node */ + LinearBlock::LinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock) + : solvefor(solvefor) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for LinearBlock ast node */ + LinearBlock::LinearBlock(const LinearBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.solvefor) { + this->solvefor.push_back(std::shared_ptr< Name>(item->clone())); + } + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for NonLinearBlock ast node */ + void NonLinearBlock::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->solvefor) { + item->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for NonLinearBlock ast node */ + NonLinearBlock::NonLinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock) + : solvefor(solvefor) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for NonLinearBlock ast node */ + NonLinearBlock::NonLinearBlock(const NonLinearBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.solvefor) { + this->solvefor.push_back(std::shared_ptr< Name>(item->clone())); + } + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for DiscreteBlock ast node */ + void DiscreteBlock::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for DiscreteBlock ast node */ + DiscreteBlock::DiscreteBlock(Name* name, StatementBlock* statementblock) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for DiscreteBlock ast node */ + DiscreteBlock::DiscreteBlock(const DiscreteBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for PartialBlock ast node */ + void PartialBlock::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for PartialBlock ast node */ + PartialBlock::PartialBlock(Name* name, StatementBlock* statementblock) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for PartialBlock ast node */ + PartialBlock::PartialBlock(const PartialBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for FunctionTableBlock ast node */ + void FunctionTableBlock::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->arguments) { + item->accept(v); + } + if (this->unit) { + this->unit->accept(v); + } + } + + /* constructor for FunctionTableBlock ast node */ + FunctionTableBlock::FunctionTableBlock(Name* name, ArgumentList arguments, Unit* unit) + : arguments(arguments) + { + this->name = std::shared_ptr(name); + this->unit = std::shared_ptr(unit); + } + + /* copy constructor for FunctionTableBlock ast node */ + FunctionTableBlock::FunctionTableBlock(const FunctionTableBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.arguments) { + this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); + } + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for FunctionBlock ast node */ + void FunctionBlock::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->arguments) { + item->accept(v); + } + if (this->unit) { + this->unit->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for FunctionBlock ast node */ + FunctionBlock::FunctionBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock) + : arguments(arguments) + { + this->name = std::shared_ptr(name); + this->unit = std::shared_ptr(unit); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for FunctionBlock ast node */ + FunctionBlock::FunctionBlock(const FunctionBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.arguments) { + this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); + } + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for ProcedureBlock ast node */ + void ProcedureBlock::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->arguments) { + item->accept(v); + } + if (this->unit) { + this->unit->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for ProcedureBlock ast node */ + ProcedureBlock::ProcedureBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock) + : arguments(arguments) + { + this->name = std::shared_ptr(name); + this->unit = std::shared_ptr(unit); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for ProcedureBlock ast node */ + ProcedureBlock::ProcedureBlock(const ProcedureBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.arguments) { + this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); + } + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for NetReceiveBlock ast node */ + void NetReceiveBlock::visitChildren(Visitor* v) { + for(auto& item : this->arguments) { + item->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for NetReceiveBlock ast node */ + NetReceiveBlock::NetReceiveBlock(ArgumentList arguments, StatementBlock* statementblock) + : arguments(arguments) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for NetReceiveBlock ast node */ + NetReceiveBlock::NetReceiveBlock(const NetReceiveBlock& obj) + { + for(auto& item : obj.arguments) { + this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); + } + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for SolveBlock ast node */ + void SolveBlock::visitChildren(Visitor* v) { + name->accept(v); + if (this->method) { + this->method->accept(v); + } + if (this->ifsolerr) { + this->ifsolerr->accept(v); + } + } + + /* constructor for SolveBlock ast node */ + SolveBlock::SolveBlock(Name* name, Name* method, StatementBlock* ifsolerr) + { + this->name = std::shared_ptr(name); + this->method = std::shared_ptr(method); + this->ifsolerr = std::shared_ptr(ifsolerr); + } + + /* copy constructor for SolveBlock ast node */ + SolveBlock::SolveBlock(const SolveBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.method) + this->method = std::shared_ptr(obj.method->clone()); + if(obj.ifsolerr) + this->ifsolerr = std::shared_ptr(obj.ifsolerr->clone()); + } + + /* visit method for BreakpointBlock ast node */ + void BreakpointBlock::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for BreakpointBlock ast node */ + BreakpointBlock::BreakpointBlock(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for BreakpointBlock ast node */ + BreakpointBlock::BreakpointBlock(const BreakpointBlock& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for TerminalBlock ast node */ + void TerminalBlock::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for TerminalBlock ast node */ + TerminalBlock::TerminalBlock(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for TerminalBlock ast node */ + TerminalBlock::TerminalBlock(const TerminalBlock& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for BeforeBlock ast node */ + void BeforeBlock::visitChildren(Visitor* v) { + block->accept(v); + } + + /* constructor for BeforeBlock ast node */ + BeforeBlock::BeforeBlock(BABlock* block) + { + this->block = std::shared_ptr(block); + } + + /* copy constructor for BeforeBlock ast node */ + BeforeBlock::BeforeBlock(const BeforeBlock& obj) + { + if(obj.block) + this->block = std::shared_ptr(obj.block->clone()); + } + + /* visit method for AfterBlock ast node */ + void AfterBlock::visitChildren(Visitor* v) { + block->accept(v); + } + + /* constructor for AfterBlock ast node */ + AfterBlock::AfterBlock(BABlock* block) + { + this->block = std::shared_ptr(block); + } + + /* copy constructor for AfterBlock ast node */ + AfterBlock::AfterBlock(const AfterBlock& obj) + { + if(obj.block) + this->block = std::shared_ptr(obj.block->clone()); + } + + /* visit method for BABlock ast node */ + void BABlock::visitChildren(Visitor* v) { + type->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for BABlock ast node */ + BABlock::BABlock(BABlockType* type, StatementBlock* statementblock) + { + this->type = std::shared_ptr(type); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for BABlock ast node */ + BABlock::BABlock(const BABlock& obj) + { + if(obj.type) + this->type = std::shared_ptr(obj.type->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for ForNetcon ast node */ + void ForNetcon::visitChildren(Visitor* v) { + for(auto& item : this->arguments) { + item->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for ForNetcon ast node */ + ForNetcon::ForNetcon(ArgumentList arguments, StatementBlock* statementblock) + : arguments(arguments) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for ForNetcon ast node */ + ForNetcon::ForNetcon(const ForNetcon& obj) + { + for(auto& item : obj.arguments) { + this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); + } + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for KineticBlock ast node */ + void KineticBlock::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->solvefor) { + item->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for KineticBlock ast node */ + KineticBlock::KineticBlock(Name* name, NameList solvefor, StatementBlock* statementblock) + : solvefor(solvefor) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for KineticBlock ast node */ + KineticBlock::KineticBlock(const KineticBlock& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.solvefor) { + this->solvefor.push_back(std::shared_ptr< Name>(item->clone())); + } + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for MatchBlock ast node */ + void MatchBlock::visitChildren(Visitor* v) { + for(auto& item : this->matchs) { + item->accept(v); + } + } + + /* constructor for MatchBlock ast node */ + MatchBlock::MatchBlock(MatchList matchs) + : matchs(matchs) + { + } + + /* copy constructor for MatchBlock ast node */ + MatchBlock::MatchBlock(const MatchBlock& obj) + { + for(auto& item : obj.matchs) { + this->matchs.push_back(std::shared_ptr< Match>(item->clone())); + } + } + + /* visit method for UnitBlock ast node */ + void UnitBlock::visitChildren(Visitor* v) { + for(auto& item : this->definitions) { + item->accept(v); + } + } + + /* constructor for UnitBlock ast node */ + UnitBlock::UnitBlock(ExpressionList definitions) + : definitions(definitions) + { + } + + /* copy constructor for UnitBlock ast node */ + UnitBlock::UnitBlock(const UnitBlock& obj) + { + for(auto& item : obj.definitions) { + this->definitions.push_back(std::shared_ptr< Expression>(item->clone())); + } + } + + /* visit method for ConstantBlock ast node */ + void ConstantBlock::visitChildren(Visitor* v) { + for(auto& item : this->statements) { + item->accept(v); + } + } + + /* constructor for ConstantBlock ast node */ + ConstantBlock::ConstantBlock(ConstantStatementList statements) + : statements(statements) + { + } + + /* copy constructor for ConstantBlock ast node */ + ConstantBlock::ConstantBlock(const ConstantBlock& obj) + { + for(auto& item : obj.statements) { + this->statements.push_back(std::shared_ptr< ConstantStatement>(item->clone())); + } + } + + /* visit method for NeuronBlock ast node */ + void NeuronBlock::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for NeuronBlock ast node */ + NeuronBlock::NeuronBlock(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for NeuronBlock ast node */ + NeuronBlock::NeuronBlock(const NeuronBlock& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for Unit ast node */ + void Unit::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for Unit ast node */ + Unit::Unit(String* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for Unit ast node */ + Unit::Unit(const Unit& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for DoubleUnit ast node */ + void DoubleUnit::visitChildren(Visitor* v) { + values->accept(v); + if (this->unit) { + this->unit->accept(v); + } + } + + /* constructor for DoubleUnit ast node */ + DoubleUnit::DoubleUnit(Double* values, Unit* unit) + { + this->values = std::shared_ptr(values); + this->unit = std::shared_ptr(unit); + } + + /* copy constructor for DoubleUnit ast node */ + DoubleUnit::DoubleUnit(const DoubleUnit& obj) + { + if(obj.values) + this->values = std::shared_ptr(obj.values->clone()); + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + } + + /* visit method for LocalVariable ast node */ + void LocalVariable::visitChildren(Visitor* v) { + name->accept(v); + } + + /* constructor for LocalVariable ast node */ + LocalVariable::LocalVariable(Identifier* name) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for LocalVariable ast node */ + LocalVariable::LocalVariable(const LocalVariable& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for Limits ast node */ + void Limits::visitChildren(Visitor* v) { + min->accept(v); + max->accept(v); + } + + /* constructor for Limits ast node */ + Limits::Limits(Double* min, Double* max) + { + this->min = std::shared_ptr(min); + this->max = std::shared_ptr(max); + } + + /* copy constructor for Limits ast node */ + Limits::Limits(const Limits& obj) + { + if(obj.min) + this->min = std::shared_ptr(obj.min->clone()); + if(obj.max) + this->max = std::shared_ptr(obj.max->clone()); + } + + /* visit method for NumberRange ast node */ + void NumberRange::visitChildren(Visitor* v) { + min->accept(v); + max->accept(v); + } + + /* constructor for NumberRange ast node */ + NumberRange::NumberRange(Number* min, Number* max) + { + this->min = std::shared_ptr(min); + this->max = std::shared_ptr(max); + } + + /* copy constructor for NumberRange ast node */ + NumberRange::NumberRange(const NumberRange& obj) + { + if(obj.min) + this->min = std::shared_ptr(obj.min->clone()); + if(obj.max) + this->max = std::shared_ptr(obj.max->clone()); + } + + /* visit method for PlotVariable ast node */ + void PlotVariable::visitChildren(Visitor* v) { + name->accept(v); + if (this->index) { + this->index->accept(v); + } + } + + /* constructor for PlotVariable ast node */ + PlotVariable::PlotVariable(Identifier* name, Integer* index) + { + this->name = std::shared_ptr(name); + this->index = std::shared_ptr(index); + } + + /* copy constructor for PlotVariable ast node */ + PlotVariable::PlotVariable(const PlotVariable& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.index) + this->index = std::shared_ptr(obj.index->clone()); + } + + void BinaryOperator::visitChildren(Visitor* v) {} + /* constructor for BinaryOperator ast node */ + BinaryOperator::BinaryOperator(BinaryOp value) + : value(value) + { + } + + /* copy constructor for BinaryOperator ast node */ + BinaryOperator::BinaryOperator(const BinaryOperator& obj) + { + this->value = obj.value; + } + + void UnaryOperator::visitChildren(Visitor* v) {} + /* constructor for UnaryOperator ast node */ + UnaryOperator::UnaryOperator(UnaryOp value) + : value(value) + { + } + + /* copy constructor for UnaryOperator ast node */ + UnaryOperator::UnaryOperator(const UnaryOperator& obj) + { + this->value = obj.value; + } + + void ReactionOperator::visitChildren(Visitor* v) {} + /* constructor for ReactionOperator ast node */ + ReactionOperator::ReactionOperator(ReactionOp value) + : value(value) + { + } + + /* copy constructor for ReactionOperator ast node */ + ReactionOperator::ReactionOperator(const ReactionOperator& obj) + { + this->value = obj.value; + } + + /* visit method for BinaryExpression ast node */ + void BinaryExpression::visitChildren(Visitor* v) { + lhs->accept(v); + op.accept(v); + rhs->accept(v); + } + + /* constructor for BinaryExpression ast node */ + BinaryExpression::BinaryExpression(Expression* lhs, BinaryOperator op, Expression* rhs) + : op(op) + { + this->lhs = std::shared_ptr(lhs); + this->rhs = std::shared_ptr(rhs); + } + + /* copy constructor for BinaryExpression ast node */ + BinaryExpression::BinaryExpression(const BinaryExpression& obj) + { + if(obj.lhs) + this->lhs = std::shared_ptr(obj.lhs->clone()); + this->op = obj.op; + if(obj.rhs) + this->rhs = std::shared_ptr(obj.rhs->clone()); + } + + /* visit method for UnaryExpression ast node */ + void UnaryExpression::visitChildren(Visitor* v) { + op.accept(v); + expression->accept(v); + } + + /* constructor for UnaryExpression ast node */ + UnaryExpression::UnaryExpression(UnaryOperator op, Expression* expression) + : op(op) + { + this->expression = std::shared_ptr(expression); + } + + /* copy constructor for UnaryExpression ast node */ + UnaryExpression::UnaryExpression(const UnaryExpression& obj) + { + this->op = obj.op; + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + } + + /* visit method for NonLinEuation ast node */ + void NonLinEuation::visitChildren(Visitor* v) { + lhs->accept(v); + rhs->accept(v); + } + + /* constructor for NonLinEuation ast node */ + NonLinEuation::NonLinEuation(Expression* lhs, Expression* rhs) + { + this->lhs = std::shared_ptr(lhs); + this->rhs = std::shared_ptr(rhs); + } + + /* copy constructor for NonLinEuation ast node */ + NonLinEuation::NonLinEuation(const NonLinEuation& obj) + { + if(obj.lhs) + this->lhs = std::shared_ptr(obj.lhs->clone()); + if(obj.rhs) + this->rhs = std::shared_ptr(obj.rhs->clone()); + } + + /* visit method for LinEquation ast node */ + void LinEquation::visitChildren(Visitor* v) { + leftlinexpr->accept(v); + linexpr->accept(v); + } + + /* constructor for LinEquation ast node */ + LinEquation::LinEquation(Expression* leftlinexpr, Expression* linexpr) + { + this->leftlinexpr = std::shared_ptr(leftlinexpr); + this->linexpr = std::shared_ptr(linexpr); + } + + /* copy constructor for LinEquation ast node */ + LinEquation::LinEquation(const LinEquation& obj) + { + if(obj.leftlinexpr) + this->leftlinexpr = std::shared_ptr(obj.leftlinexpr->clone()); + if(obj.linexpr) + this->linexpr = std::shared_ptr(obj.linexpr->clone()); + } + + /* visit method for FunctionCall ast node */ + void FunctionCall::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->arguments) { + item->accept(v); + } + } + + /* constructor for FunctionCall ast node */ + FunctionCall::FunctionCall(Name* name, ExpressionList arguments) + : arguments(arguments) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for FunctionCall ast node */ + FunctionCall::FunctionCall(const FunctionCall& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.arguments) { + this->arguments.push_back(std::shared_ptr< Expression>(item->clone())); + } + } + + void FirstLastTypeIndex::visitChildren(Visitor* v) {} + /* constructor for FirstLastTypeIndex ast node */ + FirstLastTypeIndex::FirstLastTypeIndex(FirstLastType value) + : value(value) + { + } + + /* copy constructor for FirstLastTypeIndex ast node */ + FirstLastTypeIndex::FirstLastTypeIndex(const FirstLastTypeIndex& obj) + { + this->value = obj.value; + } + + /* visit method for Watch ast node */ + void Watch::visitChildren(Visitor* v) { + expression->accept(v); + value->accept(v); + } + + /* constructor for Watch ast node */ + Watch::Watch(Expression* expression, Expression* value) + { + this->expression = std::shared_ptr(expression); + this->value = std::shared_ptr(value); + } + + /* copy constructor for Watch ast node */ + Watch::Watch(const Watch& obj) + { + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + } + + void QueueExpressionType::visitChildren(Visitor* v) {} + /* constructor for QueueExpressionType ast node */ + QueueExpressionType::QueueExpressionType(QueueType value) + : value(value) + { + } + + /* copy constructor for QueueExpressionType ast node */ + QueueExpressionType::QueueExpressionType(const QueueExpressionType& obj) + { + this->value = obj.value; + } + + /* visit method for Match ast node */ + void Match::visitChildren(Visitor* v) { + name->accept(v); + if (this->expression) { + this->expression->accept(v); + } + } + + /* constructor for Match ast node */ + Match::Match(Identifier* name, Expression* expression) + { + this->name = std::shared_ptr(name); + this->expression = std::shared_ptr(expression); + } + + /* copy constructor for Match ast node */ + Match::Match(const Match& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + } + + void BABlockType::visitChildren(Visitor* v) {} + /* constructor for BABlockType ast node */ + BABlockType::BABlockType(BAType value) + : value(value) + { + } + + /* copy constructor for BABlockType ast node */ + BABlockType::BABlockType(const BABlockType& obj) + { + this->value = obj.value; + } + + /* visit method for UnitDef ast node */ + void UnitDef::visitChildren(Visitor* v) { + unit1->accept(v); + unit2->accept(v); + } + + /* constructor for UnitDef ast node */ + UnitDef::UnitDef(Unit* unit1, Unit* unit2) + { + this->unit1 = std::shared_ptr(unit1); + this->unit2 = std::shared_ptr(unit2); + } + + /* copy constructor for UnitDef ast node */ + UnitDef::UnitDef(const UnitDef& obj) + { + if(obj.unit1) + this->unit1 = std::shared_ptr(obj.unit1->clone()); + if(obj.unit2) + this->unit2 = std::shared_ptr(obj.unit2->clone()); + } + + /* visit method for FactorDef ast node */ + void FactorDef::visitChildren(Visitor* v) { + name->accept(v); + if (this->value) { + this->value->accept(v); + } + unit1->accept(v); + if (this->gt) { + this->gt->accept(v); + } + if (this->unit2) { + this->unit2->accept(v); + } + } + + /* constructor for FactorDef ast node */ + FactorDef::FactorDef(Name* name, Double* value, Unit* unit1, Boolean* gt, Unit* unit2) + { + this->name = std::shared_ptr(name); + this->value = std::shared_ptr(value); + this->unit1 = std::shared_ptr(unit1); + this->gt = std::shared_ptr(gt); + this->unit2 = std::shared_ptr(unit2); + } + + /* copy constructor for FactorDef ast node */ + FactorDef::FactorDef(const FactorDef& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + if(obj.unit1) + this->unit1 = std::shared_ptr(obj.unit1->clone()); + if(obj.gt) + this->gt = std::shared_ptr(obj.gt->clone()); + if(obj.unit2) + this->unit2 = std::shared_ptr(obj.unit2->clone()); + if(obj.token) + this->token = std::shared_ptr(obj.token->clone()); + } + + /* visit method for Valence ast node */ + void Valence::visitChildren(Visitor* v) { + type->accept(v); + value->accept(v); + } + + /* constructor for Valence ast node */ + Valence::Valence(Name* type, Double* value) + { + this->type = std::shared_ptr(type); + this->value = std::shared_ptr(value); + } + + /* copy constructor for Valence ast node */ + Valence::Valence(const Valence& obj) + { + if(obj.type) + this->type = std::shared_ptr(obj.type->clone()); + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + } + + void UnitState::visitChildren(Visitor* v) {} + /* constructor for UnitState ast node */ + UnitState::UnitState(UnitStateType value) + : value(value) + { + } + + /* copy constructor for UnitState ast node */ + UnitState::UnitState(const UnitState& obj) + { + this->value = obj.value; + } + + /* visit method for LocalListStatement ast node */ + void LocalListStatement::visitChildren(Visitor* v) { + for(auto& item : this->variables) { + item->accept(v); + } + } + + /* constructor for LocalListStatement ast node */ + LocalListStatement::LocalListStatement(LocalVariableList variables) + : variables(variables) + { + } + + /* copy constructor for LocalListStatement ast node */ + LocalListStatement::LocalListStatement(const LocalListStatement& obj) + { + for(auto& item : obj.variables) { + this->variables.push_back(std::shared_ptr< LocalVariable>(item->clone())); + } + } + + /* visit method for Model ast node */ + void Model::visitChildren(Visitor* v) { + title->accept(v); + } + + /* constructor for Model ast node */ + Model::Model(String* title) + { + this->title = std::shared_ptr(title); + } + + /* copy constructor for Model ast node */ + Model::Model(const Model& obj) + { + if(obj.title) + this->title = std::shared_ptr(obj.title->clone()); + } + + /* visit method for Define ast node */ + void Define::visitChildren(Visitor* v) { + name->accept(v); + value->accept(v); + } + + /* constructor for Define ast node */ + Define::Define(Name* name, Integer* value) + { + this->name = std::shared_ptr(name); + this->value = std::shared_ptr(value); + } + + /* copy constructor for Define ast node */ + Define::Define(const Define& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + } + + /* visit method for Include ast node */ + void Include::visitChildren(Visitor* v) { + filename->accept(v); + } + + /* constructor for Include ast node */ + Include::Include(String* filename) + { + this->filename = std::shared_ptr(filename); + } + + /* copy constructor for Include ast node */ + Include::Include(const Include& obj) + { + if(obj.filename) + this->filename = std::shared_ptr(obj.filename->clone()); + } + + /* visit method for ParamAssign ast node */ + void ParamAssign::visitChildren(Visitor* v) { + name->accept(v); + if (this->value) { + this->value->accept(v); + } + if (this->unit) { + this->unit->accept(v); + } + if (this->limit) { + this->limit->accept(v); + } + } + + /* constructor for ParamAssign ast node */ + ParamAssign::ParamAssign(Identifier* name, Number* value, Unit* unit, Limits* limit) + { + this->name = std::shared_ptr(name); + this->value = std::shared_ptr(value); + this->unit = std::shared_ptr(unit); + this->limit = std::shared_ptr(limit); + } + + /* copy constructor for ParamAssign ast node */ + ParamAssign::ParamAssign(const ParamAssign& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + if(obj.limit) + this->limit = std::shared_ptr(obj.limit->clone()); + } + + /* visit method for Stepped ast node */ + void Stepped::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->values) { + item->accept(v); + } + unit->accept(v); + } + + /* constructor for Stepped ast node */ + Stepped::Stepped(Name* name, NumberList values, Unit* unit) + : values(values) + { + this->name = std::shared_ptr(name); + this->unit = std::shared_ptr(unit); + } + + /* copy constructor for Stepped ast node */ + Stepped::Stepped(const Stepped& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.values) { + this->values.push_back(std::shared_ptr< Number>(item->clone())); + } + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + } + + /* visit method for IndependentDef ast node */ + void IndependentDef::visitChildren(Visitor* v) { + if (this->sweep) { + this->sweep->accept(v); + } + name->accept(v); + from->accept(v); + to->accept(v); + with->accept(v); + if (this->opstart) { + this->opstart->accept(v); + } + unit->accept(v); + } + + /* constructor for IndependentDef ast node */ + IndependentDef::IndependentDef(Boolean* sweep, Name* name, Number* from, Number* to, Integer* with, Number* opstart, Unit* unit) + { + this->sweep = std::shared_ptr(sweep); + this->name = std::shared_ptr(name); + this->from = std::shared_ptr(from); + this->to = std::shared_ptr(to); + this->with = std::shared_ptr(with); + this->opstart = std::shared_ptr(opstart); + this->unit = std::shared_ptr(unit); + } + + /* copy constructor for IndependentDef ast node */ + IndependentDef::IndependentDef(const IndependentDef& obj) + { + if(obj.sweep) + this->sweep = std::shared_ptr(obj.sweep->clone()); + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.from) + this->from = std::shared_ptr(obj.from->clone()); + if(obj.to) + this->to = std::shared_ptr(obj.to->clone()); + if(obj.with) + this->with = std::shared_ptr(obj.with->clone()); + if(obj.opstart) + this->opstart = std::shared_ptr(obj.opstart->clone()); + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + } + + /* visit method for DependentDef ast node */ + void DependentDef::visitChildren(Visitor* v) { + name->accept(v); + if (this->index) { + this->index->accept(v); + } + if (this->from) { + this->from->accept(v); + } + if (this->to) { + this->to->accept(v); + } + if (this->opstart) { + this->opstart->accept(v); + } + if (this->unit) { + this->unit->accept(v); + } + if (this->abstol) { + this->abstol->accept(v); + } + } + + /* constructor for DependentDef ast node */ + DependentDef::DependentDef(Identifier* name, Integer* index, Number* from, Number* to, Number* opstart, Unit* unit, Double* abstol) + { + this->name = std::shared_ptr(name); + this->index = std::shared_ptr(index); + this->from = std::shared_ptr(from); + this->to = std::shared_ptr(to); + this->opstart = std::shared_ptr(opstart); + this->unit = std::shared_ptr(unit); + this->abstol = std::shared_ptr(abstol); + } + + /* copy constructor for DependentDef ast node */ + DependentDef::DependentDef(const DependentDef& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.index) + this->index = std::shared_ptr(obj.index->clone()); + if(obj.from) + this->from = std::shared_ptr(obj.from->clone()); + if(obj.to) + this->to = std::shared_ptr(obj.to->clone()); + if(obj.opstart) + this->opstart = std::shared_ptr(obj.opstart->clone()); + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + if(obj.abstol) + this->abstol = std::shared_ptr(obj.abstol->clone()); + } + + /* visit method for PlotDeclaration ast node */ + void PlotDeclaration::visitChildren(Visitor* v) { + for(auto& item : this->pvlist) { + item->accept(v); + } + name->accept(v); + } + + /* constructor for PlotDeclaration ast node */ + PlotDeclaration::PlotDeclaration(PlotVariableList pvlist, PlotVariable* name) + : pvlist(pvlist) + { + this->name = std::shared_ptr(name); + } + + /* copy constructor for PlotDeclaration ast node */ + PlotDeclaration::PlotDeclaration(const PlotDeclaration& obj) + { + for(auto& item : obj.pvlist) { + this->pvlist.push_back(std::shared_ptr< PlotVariable>(item->clone())); + } + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ConductanceHint ast node */ + void ConductanceHint::visitChildren(Visitor* v) { + conductance->accept(v); + if (this->ion) { + this->ion->accept(v); + } + } + + /* constructor for ConductanceHint ast node */ + ConductanceHint::ConductanceHint(Name* conductance, Name* ion) + { + this->conductance = std::shared_ptr(conductance); + this->ion = std::shared_ptr(ion); + } + + /* copy constructor for ConductanceHint ast node */ + ConductanceHint::ConductanceHint(const ConductanceHint& obj) + { + if(obj.conductance) + this->conductance = std::shared_ptr(obj.conductance->clone()); + if(obj.ion) + this->ion = std::shared_ptr(obj.ion->clone()); + } + + /* visit method for ExpressionStatement ast node */ + void ExpressionStatement::visitChildren(Visitor* v) { + expression->accept(v); + } + + /* constructor for ExpressionStatement ast node */ + ExpressionStatement::ExpressionStatement(Expression* expression) + { + this->expression = std::shared_ptr(expression); + } + + /* copy constructor for ExpressionStatement ast node */ + ExpressionStatement::ExpressionStatement(const ExpressionStatement& obj) + { + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + } + + /* visit method for ProtectStatement ast node */ + void ProtectStatement::visitChildren(Visitor* v) { + expression->accept(v); + } + + /* constructor for ProtectStatement ast node */ + ProtectStatement::ProtectStatement(Expression* expression) + { + this->expression = std::shared_ptr(expression); + } + + /* copy constructor for ProtectStatement ast node */ + ProtectStatement::ProtectStatement(const ProtectStatement& obj) + { + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + } + + /* visit method for FromStatement ast node */ + void FromStatement::visitChildren(Visitor* v) { + name->accept(v); + from->accept(v); + to->accept(v); + if (this->opinc) { + this->opinc->accept(v); + } + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for FromStatement ast node */ + FromStatement::FromStatement(Name* name, Expression* from, Expression* to, Expression* opinc, StatementBlock* statementblock) + { + this->name = std::shared_ptr(name); + this->from = std::shared_ptr(from); + this->to = std::shared_ptr(to); + this->opinc = std::shared_ptr(opinc); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for FromStatement ast node */ + FromStatement::FromStatement(const FromStatement& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.from) + this->from = std::shared_ptr(obj.from->clone()); + if(obj.to) + this->to = std::shared_ptr(obj.to->clone()); + if(obj.opinc) + this->opinc = std::shared_ptr(obj.opinc->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for ForAllStatement ast node */ + void ForAllStatement::visitChildren(Visitor* v) { + name->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for ForAllStatement ast node */ + ForAllStatement::ForAllStatement(Name* name, StatementBlock* statementblock) + { + this->name = std::shared_ptr(name); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for ForAllStatement ast node */ + ForAllStatement::ForAllStatement(const ForAllStatement& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for WhileStatement ast node */ + void WhileStatement::visitChildren(Visitor* v) { + condition->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for WhileStatement ast node */ + WhileStatement::WhileStatement(Expression* condition, StatementBlock* statementblock) + { + this->condition = std::shared_ptr(condition); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for WhileStatement ast node */ + WhileStatement::WhileStatement(const WhileStatement& obj) + { + if(obj.condition) + this->condition = std::shared_ptr(obj.condition->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for IfStatement ast node */ + void IfStatement::visitChildren(Visitor* v) { + condition->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + for(auto& item : this->elseifs) { + item->accept(v); + } + if (this->elses) { + this->elses->accept(v); + } + } + + /* constructor for IfStatement ast node */ + IfStatement::IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementList elseifs, ElseStatement* elses) + : elseifs(elseifs) + { + this->condition = std::shared_ptr(condition); + this->statementblock = std::shared_ptr(statementblock); + this->elses = std::shared_ptr(elses); + } + + /* copy constructor for IfStatement ast node */ + IfStatement::IfStatement(const IfStatement& obj) + { + if(obj.condition) + this->condition = std::shared_ptr(obj.condition->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + for(auto& item : obj.elseifs) { + this->elseifs.push_back(std::shared_ptr< ElseIfStatement>(item->clone())); + } + if(obj.elses) + this->elses = std::shared_ptr(obj.elses->clone()); + } + + /* visit method for ElseIfStatement ast node */ + void ElseIfStatement::visitChildren(Visitor* v) { + condition->accept(v); + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for ElseIfStatement ast node */ + ElseIfStatement::ElseIfStatement(Expression* condition, StatementBlock* statementblock) + { + this->condition = std::shared_ptr(condition); + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for ElseIfStatement ast node */ + ElseIfStatement::ElseIfStatement(const ElseIfStatement& obj) + { + if(obj.condition) + this->condition = std::shared_ptr(obj.condition->clone()); + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for ElseStatement ast node */ + void ElseStatement::visitChildren(Visitor* v) { + if (this->statementblock) { + this->statementblock->accept(v); + } + } + + /* constructor for ElseStatement ast node */ + ElseStatement::ElseStatement(StatementBlock* statementblock) + { + this->statementblock = std::shared_ptr(statementblock); + } + + /* copy constructor for ElseStatement ast node */ + ElseStatement::ElseStatement(const ElseStatement& obj) + { + if(obj.statementblock) + this->statementblock = std::shared_ptr(obj.statementblock->clone()); + } + + /* visit method for PartialEquation ast node */ + void PartialEquation::visitChildren(Visitor* v) { + prime->accept(v); + name1->accept(v); + name2->accept(v); + name3->accept(v); + } + + /* constructor for PartialEquation ast node */ + PartialEquation::PartialEquation(PrimeName* prime, Name* name1, Name* name2, Name* name3) + { + this->prime = std::shared_ptr(prime); + this->name1 = std::shared_ptr(name1); + this->name2 = std::shared_ptr(name2); + this->name3 = std::shared_ptr(name3); + } + + /* copy constructor for PartialEquation ast node */ + PartialEquation::PartialEquation(const PartialEquation& obj) + { + if(obj.prime) + this->prime = std::shared_ptr(obj.prime->clone()); + if(obj.name1) + this->name1 = std::shared_ptr(obj.name1->clone()); + if(obj.name2) + this->name2 = std::shared_ptr(obj.name2->clone()); + if(obj.name3) + this->name3 = std::shared_ptr(obj.name3->clone()); + } + + /* visit method for PartialBoundary ast node */ + void PartialBoundary::visitChildren(Visitor* v) { + if (this->del) { + this->del->accept(v); + } + name->accept(v); + if (this->index) { + this->index->accept(v); + } + if (this->expression) { + this->expression->accept(v); + } + if (this->name1) { + this->name1->accept(v); + } + if (this->del2) { + this->del2->accept(v); + } + if (this->name2) { + this->name2->accept(v); + } + if (this->name3) { + this->name3->accept(v); + } + } + + /* constructor for PartialBoundary ast node */ + PartialBoundary::PartialBoundary(Name* del, Identifier* name, FirstLastTypeIndex* index, Expression* expression, Name* name1, Name* del2, Name* name2, Name* name3) + { + this->del = std::shared_ptr(del); + this->name = std::shared_ptr(name); + this->index = std::shared_ptr(index); + this->expression = std::shared_ptr(expression); + this->name1 = std::shared_ptr(name1); + this->del2 = std::shared_ptr(del2); + this->name2 = std::shared_ptr(name2); + this->name3 = std::shared_ptr(name3); + } + + /* copy constructor for PartialBoundary ast node */ + PartialBoundary::PartialBoundary(const PartialBoundary& obj) + { + if(obj.del) + this->del = std::shared_ptr(obj.del->clone()); + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.index) + this->index = std::shared_ptr(obj.index->clone()); + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + if(obj.name1) + this->name1 = std::shared_ptr(obj.name1->clone()); + if(obj.del2) + this->del2 = std::shared_ptr(obj.del2->clone()); + if(obj.name2) + this->name2 = std::shared_ptr(obj.name2->clone()); + if(obj.name3) + this->name3 = std::shared_ptr(obj.name3->clone()); + } + + /* visit method for WatchStatement ast node */ + void WatchStatement::visitChildren(Visitor* v) { + for(auto& item : this->statements) { + item->accept(v); + } + } + + /* constructor for WatchStatement ast node */ + WatchStatement::WatchStatement(WatchList statements) + : statements(statements) + { + } + + /* copy constructor for WatchStatement ast node */ + WatchStatement::WatchStatement(const WatchStatement& obj) + { + for(auto& item : obj.statements) { + this->statements.push_back(std::shared_ptr< Watch>(item->clone())); + } + } + + void MutexLock::visitChildren(Visitor* v) {} + void MutexUnlock::visitChildren(Visitor* v) {} + void Reset::visitChildren(Visitor* v) {} + /* visit method for Sens ast node */ + void Sens::visitChildren(Visitor* v) { + for(auto& item : this->senslist) { + item->accept(v); + } + } + + /* constructor for Sens ast node */ + Sens::Sens(VarNameList senslist) + : senslist(senslist) + { + } + + /* copy constructor for Sens ast node */ + Sens::Sens(const Sens& obj) + { + for(auto& item : obj.senslist) { + this->senslist.push_back(std::shared_ptr< VarName>(item->clone())); + } + } + + /* visit method for Conserve ast node */ + void Conserve::visitChildren(Visitor* v) { + react->accept(v); + expr->accept(v); + } + + /* constructor for Conserve ast node */ + Conserve::Conserve(Expression* react, Expression* expr) + { + this->react = std::shared_ptr(react); + this->expr = std::shared_ptr(expr); + } + + /* copy constructor for Conserve ast node */ + Conserve::Conserve(const Conserve& obj) + { + if(obj.react) + this->react = std::shared_ptr(obj.react->clone()); + if(obj.expr) + this->expr = std::shared_ptr(obj.expr->clone()); + } + + /* visit method for Compartment ast node */ + void Compartment::visitChildren(Visitor* v) { + if (this->name) { + this->name->accept(v); + } + expression->accept(v); + for(auto& item : this->names) { + item->accept(v); + } + } + + /* constructor for Compartment ast node */ + Compartment::Compartment(Name* name, Expression* expression, NameList names) + : names(names) + { + this->name = std::shared_ptr(name); + this->expression = std::shared_ptr(expression); + } + + /* copy constructor for Compartment ast node */ + Compartment::Compartment(const Compartment& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + for(auto& item : obj.names) { + this->names.push_back(std::shared_ptr< Name>(item->clone())); + } + } + + /* visit method for LDifuse ast node */ + void LDifuse::visitChildren(Visitor* v) { + if (this->name) { + this->name->accept(v); + } + expression->accept(v); + for(auto& item : this->names) { + item->accept(v); + } + } + + /* constructor for LDifuse ast node */ + LDifuse::LDifuse(Name* name, Expression* expression, NameList names) + : names(names) + { + this->name = std::shared_ptr(name); + this->expression = std::shared_ptr(expression); + } + + /* copy constructor for LDifuse ast node */ + LDifuse::LDifuse(const LDifuse& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.expression) + this->expression = std::shared_ptr(obj.expression->clone()); + for(auto& item : obj.names) { + this->names.push_back(std::shared_ptr< Name>(item->clone())); + } + } + + /* visit method for ReactionStatement ast node */ + void ReactionStatement::visitChildren(Visitor* v) { + react1->accept(v); + op.accept(v); + if (this->react2) { + this->react2->accept(v); + } + expr1->accept(v); + if (this->expr2) { + this->expr2->accept(v); + } + } + + /* constructor for ReactionStatement ast node */ + ReactionStatement::ReactionStatement(Expression* react1, ReactionOperator op, Expression* react2, Expression* expr1, Expression* expr2) + : op(op) + { + this->react1 = std::shared_ptr(react1); + this->react2 = std::shared_ptr(react2); + this->expr1 = std::shared_ptr(expr1); + this->expr2 = std::shared_ptr(expr2); + } + + /* copy constructor for ReactionStatement ast node */ + ReactionStatement::ReactionStatement(const ReactionStatement& obj) + { + if(obj.react1) + this->react1 = std::shared_ptr(obj.react1->clone()); + this->op = obj.op; + if(obj.react2) + this->react2 = std::shared_ptr(obj.react2->clone()); + if(obj.expr1) + this->expr1 = std::shared_ptr(obj.expr1->clone()); + if(obj.expr2) + this->expr2 = std::shared_ptr(obj.expr2->clone()); + } + + /* visit method for LagStatement ast node */ + void LagStatement::visitChildren(Visitor* v) { + name->accept(v); + byname->accept(v); + } + + /* constructor for LagStatement ast node */ + LagStatement::LagStatement(Identifier* name, Name* byname) + { + this->name = std::shared_ptr(name); + this->byname = std::shared_ptr(byname); + } + + /* copy constructor for LagStatement ast node */ + LagStatement::LagStatement(const LagStatement& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.byname) + this->byname = std::shared_ptr(obj.byname->clone()); + } + + /* visit method for QueueStatement ast node */ + void QueueStatement::visitChildren(Visitor* v) { + qype->accept(v); + name->accept(v); + } + + /* constructor for QueueStatement ast node */ + QueueStatement::QueueStatement(QueueExpressionType* qype, Identifier* name) + { + this->qype = std::shared_ptr(qype); + this->name = std::shared_ptr(name); + } + + /* copy constructor for QueueStatement ast node */ + QueueStatement::QueueStatement(const QueueStatement& obj) + { + if(obj.qype) + this->qype = std::shared_ptr(obj.qype->clone()); + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for ConstantStatement ast node */ + void ConstantStatement::visitChildren(Visitor* v) { + name->accept(v); + value->accept(v); + if (this->unit) { + this->unit->accept(v); + } + } + + /* constructor for ConstantStatement ast node */ + ConstantStatement::ConstantStatement(Name* name, Number* value, Unit* unit) + { + this->name = std::shared_ptr(name); + this->value = std::shared_ptr(value); + this->unit = std::shared_ptr(unit); + } + + /* copy constructor for ConstantStatement ast node */ + ConstantStatement::ConstantStatement(const ConstantStatement& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + if(obj.value) + this->value = std::shared_ptr(obj.value->clone()); + if(obj.unit) + this->unit = std::shared_ptr(obj.unit->clone()); + } + + /* visit method for TableStatement ast node */ + void TableStatement::visitChildren(Visitor* v) { + for(auto& item : this->tablst) { + item->accept(v); + } + for(auto& item : this->dependlst) { + item->accept(v); + } + from->accept(v); + to->accept(v); + with->accept(v); + } + + /* constructor for TableStatement ast node */ + TableStatement::TableStatement(NameList tablst, NameList dependlst, Expression* from, Expression* to, Integer* with) + : tablst(tablst), +dependlst(dependlst) + { + this->from = std::shared_ptr(from); + this->to = std::shared_ptr(to); + this->with = std::shared_ptr(with); + } + + /* copy constructor for TableStatement ast node */ + TableStatement::TableStatement(const TableStatement& obj) + { + for(auto& item : obj.tablst) { + this->tablst.push_back(std::shared_ptr< Name>(item->clone())); + } + for(auto& item : obj.dependlst) { + this->dependlst.push_back(std::shared_ptr< Name>(item->clone())); + } + if(obj.from) + this->from = std::shared_ptr(obj.from->clone()); + if(obj.to) + this->to = std::shared_ptr(obj.to->clone()); + if(obj.with) + this->with = std::shared_ptr(obj.with->clone()); + } + + /* visit method for NrnSuffix ast node */ + void NrnSuffix::visitChildren(Visitor* v) { + type->accept(v); + name->accept(v); + } + + /* constructor for NrnSuffix ast node */ + NrnSuffix::NrnSuffix(Name* type, Name* name) + { + this->type = std::shared_ptr(type); + this->name = std::shared_ptr(name); + } + + /* copy constructor for NrnSuffix ast node */ + NrnSuffix::NrnSuffix(const NrnSuffix& obj) + { + if(obj.type) + this->type = std::shared_ptr(obj.type->clone()); + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + } + + /* visit method for NrnUseion ast node */ + void NrnUseion::visitChildren(Visitor* v) { + name->accept(v); + for(auto& item : this->readlist) { + item->accept(v); + } + for(auto& item : this->writelist) { + item->accept(v); + } + if (this->valence) { + this->valence->accept(v); + } + } + + /* constructor for NrnUseion ast node */ + NrnUseion::NrnUseion(Name* name, ReadIonVarList readlist, WriteIonVarList writelist, Valence* valence) + : readlist(readlist), +writelist(writelist) + { + this->name = std::shared_ptr(name); + this->valence = std::shared_ptr(valence); + } + + /* copy constructor for NrnUseion ast node */ + NrnUseion::NrnUseion(const NrnUseion& obj) + { + if(obj.name) + this->name = std::shared_ptr(obj.name->clone()); + for(auto& item : obj.readlist) { + this->readlist.push_back(std::shared_ptr< ReadIonVar>(item->clone())); + } + for(auto& item : obj.writelist) { + this->writelist.push_back(std::shared_ptr< WriteIonVar>(item->clone())); + } + if(obj.valence) + this->valence = std::shared_ptr(obj.valence->clone()); + } + + /* visit method for NrnNonspecific ast node */ + void NrnNonspecific::visitChildren(Visitor* v) { + for(auto& item : this->currents) { + item->accept(v); + } + } + + /* constructor for NrnNonspecific ast node */ + NrnNonspecific::NrnNonspecific(NonspeCurVarList currents) + : currents(currents) + { + } + + /* copy constructor for NrnNonspecific ast node */ + NrnNonspecific::NrnNonspecific(const NrnNonspecific& obj) + { + for(auto& item : obj.currents) { + this->currents.push_back(std::shared_ptr< NonspeCurVar>(item->clone())); + } + } + + /* visit method for NrnElctrodeCurrent ast node */ + void NrnElctrodeCurrent::visitChildren(Visitor* v) { + for(auto& item : this->ecurrents) { + item->accept(v); + } + } + + /* constructor for NrnElctrodeCurrent ast node */ + NrnElctrodeCurrent::NrnElctrodeCurrent(ElectrodeCurVarList ecurrents) + : ecurrents(ecurrents) + { + } + + /* copy constructor for NrnElctrodeCurrent ast node */ + NrnElctrodeCurrent::NrnElctrodeCurrent(const NrnElctrodeCurrent& obj) + { + for(auto& item : obj.ecurrents) { + this->ecurrents.push_back(std::shared_ptr< ElectrodeCurVar>(item->clone())); + } + } + + /* visit method for NrnSection ast node */ + void NrnSection::visitChildren(Visitor* v) { + for(auto& item : this->sections) { + item->accept(v); + } + } + + /* constructor for NrnSection ast node */ + NrnSection::NrnSection(SectionVarList sections) + : sections(sections) + { + } + + /* copy constructor for NrnSection ast node */ + NrnSection::NrnSection(const NrnSection& obj) + { + for(auto& item : obj.sections) { + this->sections.push_back(std::shared_ptr< SectionVar>(item->clone())); + } + } + + /* visit method for NrnRange ast node */ + void NrnRange::visitChildren(Visitor* v) { + for(auto& item : this->range_vars) { + item->accept(v); + } + } + + /* constructor for NrnRange ast node */ + NrnRange::NrnRange(RangeVarList range_vars) + : range_vars(range_vars) + { + } + + /* copy constructor for NrnRange ast node */ + NrnRange::NrnRange(const NrnRange& obj) + { + for(auto& item : obj.range_vars) { + this->range_vars.push_back(std::shared_ptr< RangeVar>(item->clone())); + } + } + + /* visit method for NrnGlobal ast node */ + void NrnGlobal::visitChildren(Visitor* v) { + for(auto& item : this->global_vars) { + item->accept(v); + } + } + + /* constructor for NrnGlobal ast node */ + NrnGlobal::NrnGlobal(GlobalVarList global_vars) + : global_vars(global_vars) + { + } + + /* copy constructor for NrnGlobal ast node */ + NrnGlobal::NrnGlobal(const NrnGlobal& obj) + { + for(auto& item : obj.global_vars) { + this->global_vars.push_back(std::shared_ptr< GlobalVar>(item->clone())); + } + } + + /* visit method for NrnPointer ast node */ + void NrnPointer::visitChildren(Visitor* v) { + for(auto& item : this->pointers) { + item->accept(v); + } + } + + /* constructor for NrnPointer ast node */ + NrnPointer::NrnPointer(PointerVarList pointers) + : pointers(pointers) + { + } + + /* copy constructor for NrnPointer ast node */ + NrnPointer::NrnPointer(const NrnPointer& obj) + { + for(auto& item : obj.pointers) { + this->pointers.push_back(std::shared_ptr< PointerVar>(item->clone())); + } + } + + /* visit method for NrnBbcorePtr ast node */ + void NrnBbcorePtr::visitChildren(Visitor* v) { + for(auto& item : this->bbcore_pointers) { + item->accept(v); + } + } + + /* constructor for NrnBbcorePtr ast node */ + NrnBbcorePtr::NrnBbcorePtr(BbcorePointerVarList bbcore_pointers) + : bbcore_pointers(bbcore_pointers) + { + } + + /* copy constructor for NrnBbcorePtr ast node */ + NrnBbcorePtr::NrnBbcorePtr(const NrnBbcorePtr& obj) + { + for(auto& item : obj.bbcore_pointers) { + this->bbcore_pointers.push_back(std::shared_ptr< BbcorePointerVar>(item->clone())); + } + } + + /* visit method for NrnExternal ast node */ + void NrnExternal::visitChildren(Visitor* v) { + for(auto& item : this->externals) { + item->accept(v); + } + } + + /* constructor for NrnExternal ast node */ + NrnExternal::NrnExternal(ExternVarList externals) + : externals(externals) + { + } + + /* copy constructor for NrnExternal ast node */ + NrnExternal::NrnExternal(const NrnExternal& obj) + { + for(auto& item : obj.externals) { + this->externals.push_back(std::shared_ptr< ExternVar>(item->clone())); + } + } + + /* visit method for NrnThreadSafe ast node */ + void NrnThreadSafe::visitChildren(Visitor* v) { + for(auto& item : this->threadsafe) { + item->accept(v); + } + } + + /* constructor for NrnThreadSafe ast node */ + NrnThreadSafe::NrnThreadSafe(ThreadsafeVarList threadsafe) + : threadsafe(threadsafe) + { + } + + /* copy constructor for NrnThreadSafe ast node */ + NrnThreadSafe::NrnThreadSafe(const NrnThreadSafe& obj) + { + for(auto& item : obj.threadsafe) { + this->threadsafe.push_back(std::shared_ptr< ThreadsafeVar>(item->clone())); + } + } + + /* visit method for Verbatim ast node */ + void Verbatim::visitChildren(Visitor* v) { + statement->accept(v); + } + + /* constructor for Verbatim ast node */ + Verbatim::Verbatim(String* statement) + { + this->statement = std::shared_ptr(statement); + } + + /* copy constructor for Verbatim ast node */ + Verbatim::Verbatim(const Verbatim& obj) + { + if(obj.statement) + this->statement = std::shared_ptr(obj.statement->clone()); + } + + /* visit method for Comment ast node */ + void Comment::visitChildren(Visitor* v) { + comment->accept(v); + } + + /* constructor for Comment ast node */ + Comment::Comment(String* comment) + { + this->comment = std::shared_ptr(comment); + } + + /* copy constructor for Comment ast node */ + Comment::Comment(const Comment& obj) + { + if(obj.comment) + this->comment = std::shared_ptr(obj.comment->clone()); + } + + /* visit method for Program ast node */ + void Program::visitChildren(Visitor* v) { + for(auto& item : this->statements) { + item->accept(v); + } + for(auto& item : this->blocks) { + item->accept(v); + } + } + + /* constructor for Program ast node */ + Program::Program(StatementList statements, BlockList blocks) + : statements(statements), +blocks(blocks) + { + } + + /* copy constructor for Program ast node */ + Program::Program(const Program& obj) + { + for(auto& item : obj.statements) { + this->statements.push_back(std::shared_ptr< Statement>(item->clone())); + } + for(auto& item : obj.blocks) { + this->blocks.push_back(std::shared_ptr< Block>(item->clone())); + } + } + +} // namespace ast diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp index b4bc67eff4..a96b4ecc75 100644 --- a/src/nmodl/ast/ast.hpp +++ b/src/nmodl/ast/ast.hpp @@ -1,3292 +1,2585 @@ -#ifndef __AST_HPP -#define __AST_HPP +#pragma once -/* headers */ #include -#include -#include -#include #include -#include +#include -#include "utils/commonutils.hpp" +#include "ast/ast_utils.hpp" #include "lexer/modtoken.hpp" +#include "utils/commonutils.hpp" +/* all classes representing Abstract Syntax Tree (AST) nodes */ namespace ast { - /* enumaration of all binary operators in the language */ - typedef enum {BOP_ADDITION, BOP_SUBTRACTION, BOP_MULTIPLICATION, BOP_DIVISION, BOP_POWER, BOP_AND, BOP_OR, BOP_GREATER, BOP_LESS, BOP_GREATER_EQUAL, BOP_LESS_EQUAL, BOP_ASSIGN, BOP_NOT_EQUAL, BOP_EXACT_EQUAL} BinaryOp; - static const std::string BinaryOpNames[] = {"+", "-", "*", "/", "^", "&&", "||", ">", "<", ">=", "<=", "=", "!=", "=="}; - - /* enumaration of all unary operators in the language */ - typedef enum {UOP_NOT, UOP_NEGATION} UnaryOp; - static const std::string UnaryOpNames[] = {"!", "-"}; - - /* enumaration of types used in partial equation */ - typedef enum {PEQ_FIRST, PEQ_LAST} FirstLastType; - static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; - - /* enumaration of queue types */ - typedef enum {PUT_QUEUE, GET_QUEUE} QueueType; - static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; - - /* enumaration of type used for BEFORE or AFTER block */ - typedef enum {BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP} BAType; - static const std::string BATypeNames[] = {"BREAKPOINT", "SOLVE", "INITIAL", "STEP"}; - - /* enumaration of type used for UNIT_ON or UNIT_OFF state*/ - typedef enum {UNIT_ON, UNIT_OFF} UnitStateType; - static const std::string UnitStateTypeNames[] = {"UNITSON", "UNITSOFF"}; - - /* enumaration of type used for Reaction statement */ - typedef enum {LTMINUSGT, LTLT, MINUSGT} ReactionOp; - static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; - - /* forward declarations of AST Node classes */ - class ExpressionNode; - class StatementNode; - class IdentifierNode; - class BlockNode; - class NumberNode; - class StringNode; - class IntegerNode; - class FloatNode; - class DoubleNode; - class BooleanNode; - class NameNode; - class PrimeNameNode; - class VarNameNode; - class IndexedNameNode; - class UnitNode; - class UnitStateNode; - class DoubleUnitNode; - class ArgumentNode; - class LocalVariableNode; - class LocalListStatementNode; - class LimitsNode; - class NumberRangeNode; - class ProgramNode; - class ModelNode; - class DefineNode; - class IncludeNode; - class ParamBlockNode; - class ParamAssignNode; - class StepBlockNode; - class SteppedNode; - class IndependentBlockNode; - class IndependentDefNode; - class DependentDefNode; - class DependentBlockNode; - class StateBlockNode; - class PlotBlockNode; - class PlotDeclarationNode; - class PlotVariableNode; - class InitialBlockNode; - class ConstructorBlockNode; - class DestructorBlockNode; - class ConductanceHintNode; - class ExpressionStatementNode; - class ProtectStatementNode; - class StatementBlockNode; - class BinaryOperatorNode; - class UnaryOperatorNode; - class ReactionOperatorNode; - class BinaryExpressionNode; - class UnaryExpressionNode; - class NonLinEuationNode; - class LinEquationNode; - class FunctionCallNode; - class FromStatementNode; - class ForAllStatementNode; - class WhileStatementNode; - class IfStatementNode; - class ElseIfStatementNode; - class ElseStatementNode; - class DerivativeBlockNode; - class LinearBlockNode; - class NonLinearBlockNode; - class DiscreteBlockNode; - class PartialBlockNode; - class PartialEquationNode; - class FirstLastTypeIndexNode; - class PartialBoundaryNode; - class FunctionTableBlockNode; - class FunctionBlockNode; - class ProcedureBlockNode; - class NetReceiveBlockNode; - class SolveBlockNode; - class BreakpointBlockNode; - class TerminalBlockNode; - class BeforeBlockNode; - class AfterBlockNode; - class BABlockTypeNode; - class BABlockNode; - class WatchStatementNode; - class WatchNode; - class ForNetconNode; - class MutexLockNode; - class MutexUnlockNode; - class ResetNode; - class SensNode; - class ConserveNode; - class CompartmentNode; - class LDifuseNode; - class KineticBlockNode; - class ReactionStatementNode; - class ReactVarNameNode; - class LagStatementNode; - class QueueStatementNode; - class QueueExpressionTypeNode; - class MatchBlockNode; - class MatchNode; - class UnitBlockNode; - class UnitDefNode; - class FactordefNode; - class ConstantStatementNode; - class ConstantBlockNode; - class TableStatementNode; - class NeuronBlockNode; - class ReadIonVarNode; - class WriteIonVarNode; - class NonspeCurVarNode; - class ElectrodeCurVarNode; - class SectionVarNode; - class RangeVarNode; - class GlobalVarNode; - class PointerVarNode; - class BbcorePointerVarNode; - class ExternVarNode; - class ThreadsafeVarNode; - class NrnSuffixNode; - class NrnUseionNode; - class NrnNonspecificNode; - class NrnElctrodeCurrentNode; - class NrnSectionNode; - class NrnRangeNode; - class NrnGlobalNode; - class NrnPointerNode; - class NrnBbcorePtrNode; - class NrnExternalNode; - class NrnThreadSafeNode; - class VerbatimNode; - class CommentNode; - class ValenceNode; - - /* abstract node classes */ - class ASTNode; - class ExpressionNode; - class NumberNode; - class IdentifierNode; - class StatementNode; - class BlockNode; - - /* std::vector for convenience */ - typedef std::vector ExpressionNodeList; - typedef std::vector StatementNodeList; - typedef std::vector IdentifierNodeList; - typedef std::vector BlockNodeList; - typedef std::vector NumberNodeList; - typedef std::vector StringNodeList; - typedef std::vector IntegerNodeList; - typedef std::vector FloatNodeList; - typedef std::vector DoubleNodeList; - typedef std::vector BooleanNodeList; - typedef std::vector NameNodeList; - typedef std::vector PrimeNameNodeList; - typedef std::vector VarNameNodeList; - typedef std::vector IndexedNameNodeList; - typedef std::vector UnitNodeList; - typedef std::vector UnitStateNodeList; - typedef std::vector DoubleUnitNodeList; - typedef std::vector ArgumentNodeList; - typedef std::vector LocalVariableNodeList; - typedef std::vector LocalListStatementNodeList; - typedef std::vector LimitsNodeList; - typedef std::vector NumberRangeNodeList; - typedef std::vector ProgramNodeList; - typedef std::vector ModelNodeList; - typedef std::vector DefineNodeList; - typedef std::vector IncludeNodeList; - typedef std::vector ParamBlockNodeList; - typedef std::vector ParamAssignNodeList; - typedef std::vector StepBlockNodeList; - typedef std::vector SteppedNodeList; - typedef std::vector IndependentBlockNodeList; - typedef std::vector IndependentDefNodeList; - typedef std::vector DependentDefNodeList; - typedef std::vector DependentBlockNodeList; - typedef std::vector StateBlockNodeList; - typedef std::vector PlotBlockNodeList; - typedef std::vector PlotDeclarationNodeList; - typedef std::vector PlotVariableNodeList; - typedef std::vector InitialBlockNodeList; - typedef std::vector ConstructorBlockNodeList; - typedef std::vector DestructorBlockNodeList; - typedef std::vector ConductanceHintNodeList; - typedef std::vector ExpressionStatementNodeList; - typedef std::vector ProtectStatementNodeList; - typedef std::vector StatementBlockNodeList; - typedef std::vector BinaryOperatorNodeList; - typedef std::vector UnaryOperatorNodeList; - typedef std::vector ReactionOperatorNodeList; - typedef std::vector BinaryExpressionNodeList; - typedef std::vector UnaryExpressionNodeList; - typedef std::vector NonLinEuationNodeList; - typedef std::vector LinEquationNodeList; - typedef std::vector FunctionCallNodeList; - typedef std::vector FromStatementNodeList; - typedef std::vector ForAllStatementNodeList; - typedef std::vector WhileStatementNodeList; - typedef std::vector IfStatementNodeList; - typedef std::vector ElseIfStatementNodeList; - typedef std::vector ElseStatementNodeList; - typedef std::vector DerivativeBlockNodeList; - typedef std::vector LinearBlockNodeList; - typedef std::vector NonLinearBlockNodeList; - typedef std::vector DiscreteBlockNodeList; - typedef std::vector PartialBlockNodeList; - typedef std::vector PartialEquationNodeList; - typedef std::vector FirstLastTypeIndexNodeList; - typedef std::vector PartialBoundaryNodeList; - typedef std::vector FunctionTableBlockNodeList; - typedef std::vector FunctionBlockNodeList; - typedef std::vector ProcedureBlockNodeList; - typedef std::vector NetReceiveBlockNodeList; - typedef std::vector SolveBlockNodeList; - typedef std::vector BreakpointBlockNodeList; - typedef std::vector TerminalBlockNodeList; - typedef std::vector BeforeBlockNodeList; - typedef std::vector AfterBlockNodeList; - typedef std::vector BABlockTypeNodeList; - typedef std::vector BABlockNodeList; - typedef std::vector WatchStatementNodeList; - typedef std::vector WatchNodeList; - typedef std::vector ForNetconNodeList; - typedef std::vector MutexLockNodeList; - typedef std::vector MutexUnlockNodeList; - typedef std::vector ResetNodeList; - typedef std::vector SensNodeList; - typedef std::vector ConserveNodeList; - typedef std::vector CompartmentNodeList; - typedef std::vector LDifuseNodeList; - typedef std::vector KineticBlockNodeList; - typedef std::vector ReactionStatementNodeList; - typedef std::vector ReactVarNameNodeList; - typedef std::vector LagStatementNodeList; - typedef std::vector QueueStatementNodeList; - typedef std::vector QueueExpressionTypeNodeList; - typedef std::vector MatchBlockNodeList; - typedef std::vector MatchNodeList; - typedef std::vector UnitBlockNodeList; - typedef std::vector UnitDefNodeList; - typedef std::vector FactordefNodeList; - typedef std::vector ConstantStatementNodeList; - typedef std::vector ConstantBlockNodeList; - typedef std::vector TableStatementNodeList; - typedef std::vector NeuronBlockNodeList; - typedef std::vector ReadIonVarNodeList; - typedef std::vector WriteIonVarNodeList; - typedef std::vector NonspeCurVarNodeList; - typedef std::vector ElectrodeCurVarNodeList; - typedef std::vector SectionVarNodeList; - typedef std::vector RangeVarNodeList; - typedef std::vector GlobalVarNodeList; - typedef std::vector PointerVarNodeList; - typedef std::vector BbcorePointerVarNodeList; - typedef std::vector ExternVarNodeList; - typedef std::vector ThreadsafeVarNodeList; - typedef std::vector NrnSuffixNodeList; - typedef std::vector NrnUseionNodeList; - typedef std::vector NrnNonspecificNodeList; - typedef std::vector NrnElctrodeCurrentNodeList; - typedef std::vector NrnSectionNodeList; - typedef std::vector NrnRangeNodeList; - typedef std::vector NrnGlobalNodeList; - typedef std::vector NrnPointerNodeList; - typedef std::vector NrnBbcorePtrNodeList; - typedef std::vector NrnExternalNodeList; - typedef std::vector NrnThreadSafeNodeList; - typedef std::vector VerbatimNodeList; - typedef std::vector CommentNodeList; - typedef std::vector ValenceNodeList; - typedef std::vector ASTNodeList; - typedef std::vector ExpressionNodeList; - typedef std::vector NumberNodeList; - typedef std::vector IdentifierNodeList; - typedef std::vector StatementNodeList; - typedef std::vector BlockNodeList; - /* basic types */ - - /* @todo: how to do this */ - #include "visitors/visitor.hpp" - /* check order of inclusion */ - #ifdef YYSTYPE_IS_TRIVIAL - #error Make sure to include this file BEFORE parser.hpp - #endif - - /* define YYSTYPE (type of all $$, $N variables) as a union - * of all necessary pointers (including lists). This will - * be used in %type specifiers and in lexer. - */ - typedef union { - ExpressionNode *expression_ptr; - StatementNode *statement_ptr; - IdentifierNode *identifier_ptr; - BlockNode *block_ptr; - NumberNode *number_ptr; - StringNode *string_ptr; - IntegerNode *integer_ptr; - NameNode *name_ptr; - FloatNode *float_ptr; - DoubleNode *double_ptr; - BooleanNode *boolean_ptr; - PrimeNameNode *primename_ptr; - VarNameNode *varname_ptr; - IndexedNameNode *indexedname_ptr; - UnitNode *unit_ptr; - UnitStateNode *unitstate_ptr; - DoubleUnitNode *doubleunit_ptr; - ArgumentNode *argument_ptr; - LocalVariableNode *localvariable_ptr; - LocalListStatementNode *localliststatement_ptr; - LocalVariableNodeList *localvariable_list_ptr; - LimitsNode *limits_ptr; - NumberRangeNode *numberrange_ptr; - ProgramNode *program_ptr; - StatementNodeList *statement_list_ptr; - BlockNodeList *block_list_ptr; - ModelNode *model_ptr; - DefineNode *define_ptr; - IncludeNode *include_ptr; - ParamBlockNode *paramblock_ptr; - ParamAssignNodeList *paramassign_list_ptr; - ParamAssignNode *paramassign_ptr; - StepBlockNode *stepblock_ptr; - SteppedNodeList *stepped_list_ptr; - SteppedNode *stepped_ptr; - NumberNodeList *number_list_ptr; - IndependentBlockNode *independentblock_ptr; - IndependentDefNodeList *independentdef_list_ptr; - IndependentDefNode *independentdef_ptr; - DependentDefNode *dependentdef_ptr; - DependentBlockNode *dependentblock_ptr; - DependentDefNodeList *dependentdef_list_ptr; - StateBlockNode *stateblock_ptr; - PlotBlockNode *plotblock_ptr; - PlotDeclarationNode *plotdeclaration_ptr; - PlotVariableNodeList *plotvariable_list_ptr; - PlotVariableNode *plotvariable_ptr; - InitialBlockNode *initialblock_ptr; - StatementBlockNode *statementblock_ptr; - ConstructorBlockNode *constructorblock_ptr; - DestructorBlockNode *destructorblock_ptr; - ConductanceHintNode *conductancehint_ptr; - ExpressionStatementNode *expressionstatement_ptr; - ProtectStatementNode *protectstatement_ptr; - BinaryOperatorNode *binaryoperator_ptr; - UnaryOperatorNode *unaryoperator_ptr; - ReactionOperatorNode *reactionoperator_ptr; - BinaryExpressionNode *binaryexpression_ptr; - UnaryExpressionNode *unaryexpression_ptr; - NonLinEuationNode *nonlineuation_ptr; - LinEquationNode *linequation_ptr; - FunctionCallNode *functioncall_ptr; - ExpressionNodeList *expression_list_ptr; - FromStatementNode *fromstatement_ptr; - ForAllStatementNode *forallstatement_ptr; - WhileStatementNode *whilestatement_ptr; - IfStatementNode *ifstatement_ptr; - ElseIfStatementNodeList *elseifstatement_list_ptr; - ElseStatementNode *elsestatement_ptr; - ElseIfStatementNode *elseifstatement_ptr; - DerivativeBlockNode *derivativeblock_ptr; - LinearBlockNode *linearblock_ptr; - NameNodeList *name_list_ptr; - NonLinearBlockNode *nonlinearblock_ptr; - DiscreteBlockNode *discreteblock_ptr; - PartialBlockNode *partialblock_ptr; - PartialEquationNode *partialequation_ptr; - FirstLastTypeIndexNode *firstlasttypeindex_ptr; - PartialBoundaryNode *partialboundary_ptr; - FunctionTableBlockNode *functiontableblock_ptr; - ArgumentNodeList *argument_list_ptr; - FunctionBlockNode *functionblock_ptr; - ProcedureBlockNode *procedureblock_ptr; - NetReceiveBlockNode *netreceiveblock_ptr; - SolveBlockNode *solveblock_ptr; - BreakpointBlockNode *breakpointblock_ptr; - TerminalBlockNode *terminalblock_ptr; - BeforeBlockNode *beforeblock_ptr; - BABlockNode *bablock_ptr; - AfterBlockNode *afterblock_ptr; - BABlockTypeNode *bablocktype_ptr; - WatchStatementNode *watchstatement_ptr; - WatchNodeList *watch_list_ptr; - WatchNode *watch_ptr; - ForNetconNode *fornetcon_ptr; - MutexLockNode *mutexlock_ptr; - MutexUnlockNode *mutexunlock_ptr; - ResetNode *reset_ptr; - SensNode *sens_ptr; - VarNameNodeList *varname_list_ptr; - ConserveNode *conserve_ptr; - CompartmentNode *compartment_ptr; - LDifuseNode *ldifuse_ptr; - KineticBlockNode *kineticblock_ptr; - ReactionStatementNode *reactionstatement_ptr; - ReactVarNameNode *reactvarname_ptr; - LagStatementNode *lagstatement_ptr; - QueueStatementNode *queuestatement_ptr; - QueueExpressionTypeNode *queueexpressiontype_ptr; - MatchBlockNode *matchblock_ptr; - MatchNodeList *match_list_ptr; - MatchNode *match_ptr; - UnitBlockNode *unitblock_ptr; - UnitDefNode *unitdef_ptr; - FactordefNode *factordef_ptr; - ConstantStatementNode *constantstatement_ptr; - ConstantBlockNode *constantblock_ptr; - ConstantStatementNodeList *constantstatement_list_ptr; - TableStatementNode *tablestatement_ptr; - NeuronBlockNode *neuronblock_ptr; - ReadIonVarNode *readionvar_ptr; - WriteIonVarNode *writeionvar_ptr; - NonspeCurVarNode *nonspecurvar_ptr; - ElectrodeCurVarNode *electrodecurvar_ptr; - SectionVarNode *sectionvar_ptr; - RangeVarNode *rangevar_ptr; - GlobalVarNode *globalvar_ptr; - PointerVarNode *pointervar_ptr; - BbcorePointerVarNode *bbcorepointervar_ptr; - ExternVarNode *externvar_ptr; - ThreadsafeVarNode *threadsafevar_ptr; - NrnSuffixNode *nrnsuffix_ptr; - NrnUseionNode *nrnuseion_ptr; - ReadIonVarNodeList *readionvar_list_ptr; - WriteIonVarNodeList *writeionvar_list_ptr; - ValenceNode *valence_ptr; - NrnNonspecificNode *nrnnonspecific_ptr; - NonspeCurVarNodeList *nonspecurvar_list_ptr; - NrnElctrodeCurrentNode *nrnelctrodecurrent_ptr; - ElectrodeCurVarNodeList *electrodecurvar_list_ptr; - NrnSectionNode *nrnsection_ptr; - SectionVarNodeList *sectionvar_list_ptr; - NrnRangeNode *nrnrange_ptr; - RangeVarNodeList *rangevar_list_ptr; - NrnGlobalNode *nrnglobal_ptr; - GlobalVarNodeList *globalvar_list_ptr; - NrnPointerNode *nrnpointer_ptr; - PointerVarNodeList *pointervar_list_ptr; - NrnBbcorePtrNode *nrnbbcoreptr_ptr; - BbcorePointerVarNodeList *bbcorepointervar_list_ptr; - NrnExternalNode *nrnexternal_ptr; - ExternVarNodeList *externvar_list_ptr; - NrnThreadSafeNode *nrnthreadsafe_ptr; - ThreadsafeVarNodeList *threadsafevar_list_ptr; - VerbatimNode *verbatim_ptr; - CommentNode *comment_ptr; - ASTNode *ast_ptr; - char* base_char_ptr; - int base_int; - } astnode_union; - - //#define YYSTYPE astnode_union - - -/* define abstract base class for all AST Nodes - * this also serves to define the visitable objects. - */ -class ASTNode { - - public: - /* all AST nodes have a member which stores their - * basetype (int, bool, none, object). Further type - * information will come from the symbol table. - * BaseType basetype; - */ - - /* all AST nodes provide visit children and accept methods */ - virtual void visitChildren(Visitor* v) = 0; - virtual void accept(Visitor* v) = 0; - virtual std::string getNodeType() = 0; - /* @todo: please revisit this. adding quickly for symtab */ - virtual std::string getName() { return "";} - virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ return NULL; } - //virtual ASTNode* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } -}; - - - - /* Define all other AST nodes */ - - /* ast Node for Expression */ - class ExpressionNode : public ASTNode { - public: - - virtual ~ExpressionNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitExpressionNode(this); } - virtual std::string getNodeType() { return "Expression"; } - virtual ExpressionNode* clone() { return new ExpressionNode(*this); } - }; - - /* ast Node for Statement */ - class StatementNode : public ASTNode { - public: - - virtual ~StatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStatementNode(this); } - virtual std::string getNodeType() { return "Statement"; } - virtual StatementNode* clone() { return new StatementNode(*this); } - }; - - /* ast Node for Identifier */ - class IdentifierNode : public ExpressionNode { - public: - - virtual ~IdentifierNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIdentifierNode(this); } - virtual std::string getNodeType() { return "Identifier"; } - virtual IdentifierNode* clone() { return new IdentifierNode(*this); } - virtual void setName(std::string name) { std::cout << "ERROR : setName() not implemented! "; abort(); } - }; - - /* ast Node for Block */ - class BlockNode : public ExpressionNode { - public: - - virtual ~BlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBlockNode(this); } - virtual std::string getNodeType() { return "Block"; } - virtual BlockNode* clone() { return new BlockNode(*this); } - }; - - /* ast Node for Number */ - class NumberNode : public ExpressionNode { - public: - - virtual ~NumberNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNumberNode(this); } - virtual std::string getNodeType() { return "Number"; } - virtual NumberNode* clone() { return new NumberNode(*this); } - virtual void negate() { std::cout << "ERROR : negate() not implemented! "; abort(); } - }; - - /* ast Node for String */ - class StringNode : public ExpressionNode { - public: - /* member variables */ - std::string value; - ModToken *token; - - /* constructor */ - StringNode(std::string value); - - /* copy constructor */ - StringNode(const StringNode& obj); - - virtual ~StringNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStringNode(this); } - virtual std::string getNodeType() { return "String"; } - virtual StringNode* clone() { return new StringNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - std::string eval() { return value; } - virtual void set(std::string _value) { value = _value; } - }; - - /* ast Node for Integer */ - class IntegerNode : public NumberNode { - public: - /* member variables */ - int value; - NameNode *macroname; - ModToken *token; - - /* constructor */ - IntegerNode(int value, NameNode *macroname); - - /* copy constructor */ - IntegerNode(const IntegerNode& obj); - - virtual ~IntegerNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIntegerNode(this); } - virtual std::string getNodeType() { return "Integer"; } - virtual IntegerNode* clone() { return new IntegerNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - void negate() { value = -value; } - int eval() { return value; } - virtual void set(int _value) { value = _value; } - }; - - /* ast Node for Float */ - class FloatNode : public NumberNode { - public: - /* member variables */ - float value; - - /* constructor */ - FloatNode(float value); - - /* copy constructor */ - FloatNode(const FloatNode& obj); - - virtual ~FloatNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFloatNode(this); } - virtual std::string getNodeType() { return "Float"; } - virtual FloatNode* clone() { return new FloatNode(*this); } - void negate() { value = -value; } - float eval() { return value; } - virtual void set(float _value) { value = _value; } - }; - - /* ast Node for Double */ - class DoubleNode : public NumberNode { - public: - /* member variables */ - double value; - ModToken *token; - - /* constructor */ - DoubleNode(double value); - - /* copy constructor */ - DoubleNode(const DoubleNode& obj); - - virtual ~DoubleNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDoubleNode(this); } - virtual std::string getNodeType() { return "Double"; } - virtual DoubleNode* clone() { return new DoubleNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - void negate() { value = -value; } - double eval() { return value; } - virtual void set(double _value) { value = _value; } - }; - - /* ast Node for Boolean */ - class BooleanNode : public NumberNode { - public: - /* member variables */ - int value; - - /* constructor */ - BooleanNode(int value); - - /* copy constructor */ - BooleanNode(const BooleanNode& obj); - - virtual ~BooleanNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBooleanNode(this); } - virtual std::string getNodeType() { return "Boolean"; } - virtual BooleanNode* clone() { return new BooleanNode(*this); } - void negate() { value = !value; } - bool eval() { return value; } - virtual void set(bool _value) { value = _value; } - }; - - /* ast Node for Name */ - class NameNode : public IdentifierNode { - public: - /* member variables */ - StringNode *value; - ModToken *token; - - /* constructor */ - NameNode(StringNode *value); - - /* copy constructor */ - NameNode(const NameNode& obj); - - virtual std::string getName() { return value->eval(); } - - virtual ~NameNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNameNode(this); } - virtual std::string getNodeType() { return "Name"; } - virtual NameNode* clone() { return new NameNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setName(std::string name) { value->set(name); } - }; - - /* ast Node for PrimeName */ - class PrimeNameNode : public IdentifierNode { - public: - /* member variables */ - StringNode *value; - IntegerNode *order; - ModToken *token; - - /* constructor */ - PrimeNameNode(StringNode *value, IntegerNode *order); - - /* copy constructor */ - PrimeNameNode(const PrimeNameNode& obj); - - virtual std::string getName() { return value->eval(); } - virtual int getOrder() { return order->eval(); } - - virtual ~PrimeNameNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPrimeNameNode(this); } - virtual std::string getNodeType() { return "PrimeName"; } - virtual PrimeNameNode* clone() { return new PrimeNameNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - }; - - /* ast Node for VarName */ - class VarNameNode : public IdentifierNode { - public: - /* member variables */ - IdentifierNode *name; - IntegerNode *at_index; - - /* constructor */ - VarNameNode(IdentifierNode *name, IntegerNode *at_index); - - /* copy constructor */ - VarNameNode(const VarNameNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~VarNameNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitVarNameNode(this); } - virtual std::string getNodeType() { return "VarName"; } - virtual VarNameNode* clone() { return new VarNameNode(*this); } - }; - - /* ast Node for IndexedName */ - class IndexedNameNode : public IdentifierNode { - public: - /* member variables */ - IdentifierNode *name; - ExpressionNode *index; - - /* constructor */ - IndexedNameNode(IdentifierNode *name, ExpressionNode *index); - - /* copy constructor */ - IndexedNameNode(const IndexedNameNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~IndexedNameNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIndexedNameNode(this); } - virtual std::string getNodeType() { return "IndexedName"; } - virtual IndexedNameNode* clone() { return new IndexedNameNode(*this); } - }; - - /* ast Node for Unit */ - class UnitNode : public ExpressionNode { - public: - /* member variables */ - StringNode *name; - - /* constructor */ - UnitNode(StringNode *name); - - /* copy constructor */ - UnitNode(const UnitNode& obj); - - virtual std::string getName() { return name->eval(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~UnitNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitNode(this); } - virtual std::string getNodeType() { return "Unit"; } - virtual UnitNode* clone() { return new UnitNode(*this); } - }; - - /* ast Node for UnitState */ - class UnitStateNode : public StatementNode { - public: - /* member variables */ - UnitStateType value; - - /* constructor */ - UnitStateNode(UnitStateType value); - - /* copy constructor */ - UnitStateNode(const UnitStateNode& obj); - - virtual ~UnitStateNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitStateNode(this); } - virtual std::string getNodeType() { return "UnitState"; } - virtual UnitStateNode* clone() { return new UnitStateNode(*this); } - std::string eval() { return UnitStateTypeNames[value]; } - }; - - /* ast Node for DoubleUnit */ - class DoubleUnitNode : public ExpressionNode { - public: - /* member variables */ - DoubleNode *values; - UnitNode *unit; - - /* constructor */ - DoubleUnitNode(DoubleNode *values, UnitNode *unit); - - /* copy constructor */ - DoubleUnitNode(const DoubleUnitNode& obj); - - virtual ~DoubleUnitNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDoubleUnitNode(this); } - virtual std::string getNodeType() { return "DoubleUnit"; } - virtual DoubleUnitNode* clone() { return new DoubleUnitNode(*this); } - }; - - /* ast Node for Argument */ - class ArgumentNode : public IdentifierNode { - public: - /* member variables */ - IdentifierNode *name; - UnitNode *unit; - - /* constructor */ - ArgumentNode(IdentifierNode *name, UnitNode *unit); - - /* copy constructor */ - ArgumentNode(const ArgumentNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ArgumentNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitArgumentNode(this); } - virtual std::string getNodeType() { return "Argument"; } - virtual ArgumentNode* clone() { return new ArgumentNode(*this); } - }; - - /* ast Node for LocalVariable */ - class LocalVariableNode : public ExpressionNode { - public: - /* member variables */ - IdentifierNode *name; - - /* constructor */ - LocalVariableNode(IdentifierNode *name); - - /* copy constructor */ - LocalVariableNode(const LocalVariableNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~LocalVariableNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLocalVariableNode(this); } - virtual std::string getNodeType() { return "LocalVariable"; } - virtual LocalVariableNode* clone() { return new LocalVariableNode(*this); } - }; - - /* ast Node for LocalListStatement */ - class LocalListStatementNode : public StatementNode { - public: - /* member variables */ - LocalVariableNodeList *variables; - - /* constructor */ - LocalListStatementNode(LocalVariableNodeList *variables); - - /* copy constructor */ - LocalListStatementNode(const LocalListStatementNode& obj); - - virtual ~LocalListStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLocalListStatementNode(this); } - virtual std::string getNodeType() { return "LocalListStatement"; } - virtual LocalListStatementNode* clone() { return new LocalListStatementNode(*this); } - }; - - /* ast Node for Limits */ - class LimitsNode : public ExpressionNode { - public: - /* member variables */ - DoubleNode *min; - DoubleNode *max; - - /* constructor */ - LimitsNode(DoubleNode *min, DoubleNode *max); - - /* copy constructor */ - LimitsNode(const LimitsNode& obj); - - virtual ~LimitsNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLimitsNode(this); } - virtual std::string getNodeType() { return "Limits"; } - virtual LimitsNode* clone() { return new LimitsNode(*this); } - }; - - /* ast Node for NumberRange */ - class NumberRangeNode : public ExpressionNode { - public: - /* member variables */ - NumberNode *min; - NumberNode *max; - - /* constructor */ - NumberRangeNode(NumberNode *min, NumberNode *max); - - /* copy constructor */ - NumberRangeNode(const NumberRangeNode& obj); - - virtual ~NumberRangeNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNumberRangeNode(this); } - virtual std::string getNodeType() { return "NumberRange"; } - virtual NumberRangeNode* clone() { return new NumberRangeNode(*this); } - }; - - /* ast Node for Program */ - class ProgramNode : public ASTNode { - public: - /* member variables */ - StatementNodeList *statements; - BlockNodeList *blocks; - void* symtab; - - /* constructor */ - ProgramNode(StatementNodeList *statements, BlockNodeList *blocks); - - /* copy constructor */ - ProgramNode(const ProgramNode& obj); - - void addStatement(StatementNode *s) { - statements->push_back(s); - } - - void addBlock(BlockNode *s) { - blocks->push_back(s); - } - ProgramNode() { - blocks = new BlockNodeList(); - statements = new StatementNodeList(); - } - - - virtual ~ProgramNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitProgramNode(this); } - virtual std::string getNodeType() { return "Program"; } - virtual ProgramNode* clone() { return new ProgramNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for Model */ - class ModelNode : public StatementNode { - public: - /* member variables */ - StringNode *title; - - /* constructor */ - ModelNode(StringNode *title); - - /* copy constructor */ - ModelNode(const ModelNode& obj); - - virtual ~ModelNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitModelNode(this); } - virtual std::string getNodeType() { return "Model"; } - virtual ModelNode* clone() { return new ModelNode(*this); } - }; - - /* ast Node for Define */ - class DefineNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - IntegerNode *value; - - /* constructor */ - DefineNode(NameNode *name, IntegerNode *value); - - /* copy constructor */ - DefineNode(const DefineNode& obj); - - virtual ~DefineNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDefineNode(this); } - virtual std::string getNodeType() { return "Define"; } - virtual DefineNode* clone() { return new DefineNode(*this); } - }; - - /* ast Node for Include */ - class IncludeNode : public StatementNode { - public: - /* member variables */ - StringNode *filename; - - /* constructor */ - IncludeNode(StringNode *filename); - - /* copy constructor */ - IncludeNode(const IncludeNode& obj); - - virtual ~IncludeNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIncludeNode(this); } - virtual std::string getNodeType() { return "Include"; } - virtual IncludeNode* clone() { return new IncludeNode(*this); } - }; - - /* ast Node for ParamBlock */ - class ParamBlockNode : public BlockNode { - public: - /* member variables */ - ParamAssignNodeList *statements; - void* symtab; - - /* constructor */ - ParamBlockNode(ParamAssignNodeList *statements); - - /* copy constructor */ - ParamBlockNode(const ParamBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~ParamBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitParamBlockNode(this); } - virtual std::string getNodeType() { return "ParamBlock"; } - virtual ParamBlockNode* clone() { return new ParamBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for ParamAssign */ - class ParamAssignNode : public StatementNode { - public: - /* member variables */ - IdentifierNode *name; - NumberNode *value; - UnitNode *unit; - LimitsNode *limit; - - /* constructor */ - ParamAssignNode(IdentifierNode *name, NumberNode *value, UnitNode *unit, LimitsNode *limit); - - /* copy constructor */ - ParamAssignNode(const ParamAssignNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ParamAssignNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitParamAssignNode(this); } - virtual std::string getNodeType() { return "ParamAssign"; } - virtual ParamAssignNode* clone() { return new ParamAssignNode(*this); } - }; - - /* ast Node for StepBlock */ - class StepBlockNode : public BlockNode { - public: - /* member variables */ - SteppedNodeList *statements; - void* symtab; - - /* constructor */ - StepBlockNode(SteppedNodeList *statements); - - /* copy constructor */ - StepBlockNode(const StepBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~StepBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStepBlockNode(this); } - virtual std::string getNodeType() { return "StepBlock"; } - virtual StepBlockNode* clone() { return new StepBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for Stepped */ - class SteppedNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - NumberNodeList *values; - UnitNode *unit; - - /* constructor */ - SteppedNode(NameNode *name, NumberNodeList *values, UnitNode *unit); - - /* copy constructor */ - SteppedNode(const SteppedNode& obj); - - virtual ~SteppedNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSteppedNode(this); } - virtual std::string getNodeType() { return "Stepped"; } - virtual SteppedNode* clone() { return new SteppedNode(*this); } - }; - - /* ast Node for IndependentBlock */ - class IndependentBlockNode : public BlockNode { - public: - /* member variables */ - IndependentDefNodeList *definitions; - void* symtab; - - /* constructor */ - IndependentBlockNode(IndependentDefNodeList *definitions); - - /* copy constructor */ - IndependentBlockNode(const IndependentBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~IndependentBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIndependentBlockNode(this); } - virtual std::string getNodeType() { return "IndependentBlock"; } - virtual IndependentBlockNode* clone() { return new IndependentBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for IndependentDef */ - class IndependentDefNode : public StatementNode { - public: - /* member variables */ - BooleanNode *sweep; - NameNode *name; - NumberNode *from; - NumberNode *to; - IntegerNode *with; - NumberNode *opstart; - UnitNode *unit; - - /* constructor */ - IndependentDefNode(BooleanNode *sweep, NameNode *name, NumberNode *from, NumberNode *to, IntegerNode *with, NumberNode *opstart, UnitNode *unit); - - /* copy constructor */ - IndependentDefNode(const IndependentDefNode& obj); - - virtual ~IndependentDefNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIndependentDefNode(this); } - virtual std::string getNodeType() { return "IndependentDef"; } - virtual IndependentDefNode* clone() { return new IndependentDefNode(*this); } - }; - - /* ast Node for DependentDef */ - class DependentDefNode : public StatementNode { - public: - /* member variables */ - IdentifierNode *name; - IntegerNode *index; - NumberNode *from; - NumberNode *to; - NumberNode *opstart; - UnitNode *unit; - DoubleNode *abstol; - - /* constructor */ - DependentDefNode(IdentifierNode *name, IntegerNode *index, NumberNode *from, NumberNode *to, NumberNode *opstart, UnitNode *unit, DoubleNode *abstol); - - /* copy constructor */ - DependentDefNode(const DependentDefNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~DependentDefNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDependentDefNode(this); } - virtual std::string getNodeType() { return "DependentDef"; } - virtual DependentDefNode* clone() { return new DependentDefNode(*this); } - }; - - /* ast Node for DependentBlock */ - class DependentBlockNode : public BlockNode { - public: - /* member variables */ - DependentDefNodeList *definitions; - void* symtab; - - /* constructor */ - DependentBlockNode(DependentDefNodeList *definitions); - - /* copy constructor */ - DependentBlockNode(const DependentBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~DependentBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDependentBlockNode(this); } - virtual std::string getNodeType() { return "DependentBlock"; } - virtual DependentBlockNode* clone() { return new DependentBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for StateBlock */ - class StateBlockNode : public BlockNode { - public: - /* member variables */ - DependentDefNodeList *definitions; - void* symtab; - - /* constructor */ - StateBlockNode(DependentDefNodeList *definitions); - - /* copy constructor */ - StateBlockNode(const StateBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~StateBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStateBlockNode(this); } - virtual std::string getNodeType() { return "StateBlock"; } - virtual StateBlockNode* clone() { return new StateBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for PlotBlock */ - class PlotBlockNode : public BlockNode { - public: - /* member variables */ - PlotDeclarationNode *plot; - void* symtab; - - /* constructor */ - PlotBlockNode(PlotDeclarationNode *plot); - - /* copy constructor */ - PlotBlockNode(const PlotBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~PlotBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPlotBlockNode(this); } - virtual std::string getNodeType() { return "PlotBlock"; } - virtual PlotBlockNode* clone() { return new PlotBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for PlotDeclaration */ - class PlotDeclarationNode : public StatementNode { - public: - /* member variables */ - PlotVariableNodeList *pvlist; - PlotVariableNode *name; - - /* constructor */ - PlotDeclarationNode(PlotVariableNodeList *pvlist, PlotVariableNode *name); - - /* copy constructor */ - PlotDeclarationNode(const PlotDeclarationNode& obj); - - virtual ~PlotDeclarationNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPlotDeclarationNode(this); } - virtual std::string getNodeType() { return "PlotDeclaration"; } - virtual PlotDeclarationNode* clone() { return new PlotDeclarationNode(*this); } - }; - - /* ast Node for PlotVariable */ - class PlotVariableNode : public ExpressionNode { - public: - /* member variables */ - IdentifierNode *name; - IntegerNode *index; - - /* constructor */ - PlotVariableNode(IdentifierNode *name, IntegerNode *index); - - /* copy constructor */ - PlotVariableNode(const PlotVariableNode& obj); - - virtual ~PlotVariableNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPlotVariableNode(this); } - virtual std::string getNodeType() { return "PlotVariable"; } - virtual PlotVariableNode* clone() { return new PlotVariableNode(*this); } - }; - - /* ast Node for InitialBlock */ - class InitialBlockNode : public BlockNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - InitialBlockNode(StatementBlockNode *statementblock); - - /* copy constructor */ - InitialBlockNode(const InitialBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~InitialBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitInitialBlockNode(this); } - virtual std::string getNodeType() { return "InitialBlock"; } - virtual InitialBlockNode* clone() { return new InitialBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for ConstructorBlock */ - class ConstructorBlockNode : public BlockNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - ConstructorBlockNode(StatementBlockNode *statementblock); - - /* copy constructor */ - ConstructorBlockNode(const ConstructorBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~ConstructorBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConstructorBlockNode(this); } - virtual std::string getNodeType() { return "ConstructorBlock"; } - virtual ConstructorBlockNode* clone() { return new ConstructorBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for DestructorBlock */ - class DestructorBlockNode : public BlockNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - DestructorBlockNode(StatementBlockNode *statementblock); - - /* copy constructor */ - DestructorBlockNode(const DestructorBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~DestructorBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDestructorBlockNode(this); } - virtual std::string getNodeType() { return "DestructorBlock"; } - virtual DestructorBlockNode* clone() { return new DestructorBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for ConductanceHint */ - class ConductanceHintNode : public StatementNode { - public: - /* member variables */ - NameNode *conductance; - NameNode *ion; - - /* constructor */ - ConductanceHintNode(NameNode *conductance, NameNode *ion); - - /* copy constructor */ - ConductanceHintNode(const ConductanceHintNode& obj); - - virtual ~ConductanceHintNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConductanceHintNode(this); } - virtual std::string getNodeType() { return "ConductanceHint"; } - virtual ConductanceHintNode* clone() { return new ConductanceHintNode(*this); } - }; - - /* ast Node for ExpressionStatement */ - class ExpressionStatementNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *expression; - - /* constructor */ - ExpressionStatementNode(ExpressionNode *expression); - - /* copy constructor */ - ExpressionStatementNode(const ExpressionStatementNode& obj); - - virtual ~ExpressionStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitExpressionStatementNode(this); } - virtual std::string getNodeType() { return "ExpressionStatement"; } - virtual ExpressionStatementNode* clone() { return new ExpressionStatementNode(*this); } - }; - - /* ast Node for ProtectStatement */ - class ProtectStatementNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *expression; - - /* constructor */ - ProtectStatementNode(ExpressionNode *expression); - - /* copy constructor */ - ProtectStatementNode(const ProtectStatementNode& obj); - - virtual ~ProtectStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitProtectStatementNode(this); } - virtual std::string getNodeType() { return "ProtectStatement"; } - virtual ProtectStatementNode* clone() { return new ProtectStatementNode(*this); } - }; - - /* ast Node for StatementBlock */ - class StatementBlockNode : public BlockNode { - public: - /* member variables */ - StatementNodeList *statements; - ModToken *token; - void* symtab; - - /* constructor */ - StatementBlockNode(StatementNodeList *statements); - - /* copy constructor */ - StatementBlockNode(const StatementBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~StatementBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStatementBlockNode(this); } - virtual std::string getNodeType() { return "StatementBlock"; } - virtual StatementBlockNode* clone() { return new StatementBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for BinaryOperator */ - class BinaryOperatorNode : public ExpressionNode { - public: - /* member variables */ - BinaryOp value; - - /* constructor */ - BinaryOperatorNode(BinaryOp value); - - /* copy constructor */ - BinaryOperatorNode(const BinaryOperatorNode& obj); - - virtual ~BinaryOperatorNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBinaryOperatorNode(this); } - virtual std::string getNodeType() { return "BinaryOperator"; } - virtual BinaryOperatorNode* clone() { return new BinaryOperatorNode(*this); } - std::string eval() { return BinaryOpNames[value]; } - }; - - /* ast Node for UnaryOperator */ - class UnaryOperatorNode : public ExpressionNode { - public: - /* member variables */ - UnaryOp value; - - /* constructor */ - UnaryOperatorNode(UnaryOp value); - - /* copy constructor */ - UnaryOperatorNode(const UnaryOperatorNode& obj); - - virtual ~UnaryOperatorNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnaryOperatorNode(this); } - virtual std::string getNodeType() { return "UnaryOperator"; } - virtual UnaryOperatorNode* clone() { return new UnaryOperatorNode(*this); } - std::string eval() { return UnaryOpNames[value]; } - }; - - /* ast Node for ReactionOperator */ - class ReactionOperatorNode : public ExpressionNode { - public: - /* member variables */ - ReactionOp value; - - /* constructor */ - ReactionOperatorNode(ReactionOp value); - - /* copy constructor */ - ReactionOperatorNode(const ReactionOperatorNode& obj); - - virtual ~ReactionOperatorNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReactionOperatorNode(this); } - virtual std::string getNodeType() { return "ReactionOperator"; } - virtual ReactionOperatorNode* clone() { return new ReactionOperatorNode(*this); } - std::string eval() { return ReactionOpNames[value]; } - }; - - /* ast Node for BinaryExpression */ - class BinaryExpressionNode : public ExpressionNode { - public: - /* member variables */ - ExpressionNode *lhs; - BinaryOperatorNode *op; - ExpressionNode *rhs; - - /* constructor */ - BinaryExpressionNode(ExpressionNode *lhs, BinaryOperatorNode *op, ExpressionNode *rhs); - - /* copy constructor */ - BinaryExpressionNode(const BinaryExpressionNode& obj); - - virtual ~BinaryExpressionNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBinaryExpressionNode(this); } - virtual std::string getNodeType() { return "BinaryExpression"; } - virtual BinaryExpressionNode* clone() { return new BinaryExpressionNode(*this); } - }; - - /* ast Node for UnaryExpression */ - class UnaryExpressionNode : public ExpressionNode { - public: - /* member variables */ - UnaryOperatorNode *op; - ExpressionNode *expression; - - /* constructor */ - UnaryExpressionNode(UnaryOperatorNode *op, ExpressionNode *expression); - - /* copy constructor */ - UnaryExpressionNode(const UnaryExpressionNode& obj); - - virtual ~UnaryExpressionNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnaryExpressionNode(this); } - virtual std::string getNodeType() { return "UnaryExpression"; } - virtual UnaryExpressionNode* clone() { return new UnaryExpressionNode(*this); } - }; - - /* ast Node for NonLinEuation */ - class NonLinEuationNode : public ExpressionNode { - public: - /* member variables */ - ExpressionNode *lhs; - ExpressionNode *rhs; - - /* constructor */ - NonLinEuationNode(ExpressionNode *lhs, ExpressionNode *rhs); - - /* copy constructor */ - NonLinEuationNode(const NonLinEuationNode& obj); - - virtual ~NonLinEuationNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNonLinEuationNode(this); } - virtual std::string getNodeType() { return "NonLinEuation"; } - virtual NonLinEuationNode* clone() { return new NonLinEuationNode(*this); } - }; - - /* ast Node for LinEquation */ - class LinEquationNode : public ExpressionNode { - public: - /* member variables */ - ExpressionNode *leftlinexpr; - ExpressionNode *linexpr; - - /* constructor */ - LinEquationNode(ExpressionNode *leftlinexpr, ExpressionNode *linexpr); - - /* copy constructor */ - LinEquationNode(const LinEquationNode& obj); - - virtual ~LinEquationNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLinEquationNode(this); } - virtual std::string getNodeType() { return "LinEquation"; } - virtual LinEquationNode* clone() { return new LinEquationNode(*this); } - }; - - /* ast Node for FunctionCall */ - class FunctionCallNode : public ExpressionNode { - public: - /* member variables */ - NameNode *name; - ExpressionNodeList *arguments; - - /* constructor */ - FunctionCallNode(NameNode *name, ExpressionNodeList *arguments); - - /* copy constructor */ - FunctionCallNode(const FunctionCallNode& obj); - - virtual ~FunctionCallNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFunctionCallNode(this); } - virtual std::string getNodeType() { return "FunctionCall"; } - virtual FunctionCallNode* clone() { return new FunctionCallNode(*this); } - }; - - /* ast Node for FromStatement */ - class FromStatementNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - ExpressionNode *from; - ExpressionNode *to; - ExpressionNode *opinc; - StatementBlockNode *statementblock; - - /* constructor */ - FromStatementNode(NameNode *name, ExpressionNode *from, ExpressionNode *to, ExpressionNode *opinc, StatementBlockNode *statementblock); - - /* copy constructor */ - FromStatementNode(const FromStatementNode& obj); - - virtual ~FromStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFromStatementNode(this); } - virtual std::string getNodeType() { return "FromStatement"; } - virtual FromStatementNode* clone() { return new FromStatementNode(*this); } - }; - - /* ast Node for ForAllStatement */ - class ForAllStatementNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - StatementBlockNode *statementblock; - - /* constructor */ - ForAllStatementNode(NameNode *name, StatementBlockNode *statementblock); - - /* copy constructor */ - ForAllStatementNode(const ForAllStatementNode& obj); - - virtual ~ForAllStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitForAllStatementNode(this); } - virtual std::string getNodeType() { return "ForAllStatement"; } - virtual ForAllStatementNode* clone() { return new ForAllStatementNode(*this); } - }; - - /* ast Node for WhileStatement */ - class WhileStatementNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *condition; - StatementBlockNode *statementblock; - - /* constructor */ - WhileStatementNode(ExpressionNode *condition, StatementBlockNode *statementblock); - - /* copy constructor */ - WhileStatementNode(const WhileStatementNode& obj); - - virtual ~WhileStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWhileStatementNode(this); } - virtual std::string getNodeType() { return "WhileStatement"; } - virtual WhileStatementNode* clone() { return new WhileStatementNode(*this); } - }; - - /* ast Node for IfStatement */ - class IfStatementNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *condition; - StatementBlockNode *statementblock; - ElseIfStatementNodeList *elseifs; - ElseStatementNode *elses; - - /* constructor */ - IfStatementNode(ExpressionNode *condition, StatementBlockNode *statementblock, ElseIfStatementNodeList *elseifs, ElseStatementNode *elses); - - /* copy constructor */ - IfStatementNode(const IfStatementNode& obj); - - virtual ~IfStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIfStatementNode(this); } - virtual std::string getNodeType() { return "IfStatement"; } - virtual IfStatementNode* clone() { return new IfStatementNode(*this); } - }; - - /* ast Node for ElseIfStatement */ - class ElseIfStatementNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *condition; - StatementBlockNode *statementblock; - - /* constructor */ - ElseIfStatementNode(ExpressionNode *condition, StatementBlockNode *statementblock); - - /* copy constructor */ - ElseIfStatementNode(const ElseIfStatementNode& obj); - - virtual ~ElseIfStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitElseIfStatementNode(this); } - virtual std::string getNodeType() { return "ElseIfStatement"; } - virtual ElseIfStatementNode* clone() { return new ElseIfStatementNode(*this); } - }; - - /* ast Node for ElseStatement */ - class ElseStatementNode : public StatementNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - - /* constructor */ - ElseStatementNode(StatementBlockNode *statementblock); - - /* copy constructor */ - ElseStatementNode(const ElseStatementNode& obj); - - virtual ~ElseStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitElseStatementNode(this); } - virtual std::string getNodeType() { return "ElseStatement"; } - virtual ElseStatementNode* clone() { return new ElseStatementNode(*this); } - }; - - /* ast Node for DerivativeBlock */ - class DerivativeBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - DerivativeBlockNode(NameNode *name, StatementBlockNode *statementblock); - - /* copy constructor */ - DerivativeBlockNode(const DerivativeBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~DerivativeBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDerivativeBlockNode(this); } - virtual std::string getNodeType() { return "DerivativeBlock"; } - virtual DerivativeBlockNode* clone() { return new DerivativeBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for LinearBlock */ - class LinearBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - NameNodeList *solvefor; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - LinearBlockNode(NameNode *name, NameNodeList *solvefor, StatementBlockNode *statementblock); - - /* copy constructor */ - LinearBlockNode(const LinearBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~LinearBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLinearBlockNode(this); } - virtual std::string getNodeType() { return "LinearBlock"; } - virtual LinearBlockNode* clone() { return new LinearBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for NonLinearBlock */ - class NonLinearBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - NameNodeList *solvefor; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - NonLinearBlockNode(NameNode *name, NameNodeList *solvefor, StatementBlockNode *statementblock); - - /* copy constructor */ - NonLinearBlockNode(const NonLinearBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~NonLinearBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNonLinearBlockNode(this); } - virtual std::string getNodeType() { return "NonLinearBlock"; } - virtual NonLinearBlockNode* clone() { return new NonLinearBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for DiscreteBlock */ - class DiscreteBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - DiscreteBlockNode(NameNode *name, StatementBlockNode *statementblock); - - /* copy constructor */ - DiscreteBlockNode(const DiscreteBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~DiscreteBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDiscreteBlockNode(this); } - virtual std::string getNodeType() { return "DiscreteBlock"; } - virtual DiscreteBlockNode* clone() { return new DiscreteBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for PartialBlock */ - class PartialBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - PartialBlockNode(NameNode *name, StatementBlockNode *statementblock); - - /* copy constructor */ - PartialBlockNode(const PartialBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~PartialBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPartialBlockNode(this); } - virtual std::string getNodeType() { return "PartialBlock"; } - virtual PartialBlockNode* clone() { return new PartialBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for PartialEquation */ - class PartialEquationNode : public StatementNode { - public: - /* member variables */ - PrimeNameNode *prime; - NameNode *name1; - NameNode *name2; - NameNode *name3; - - /* constructor */ - PartialEquationNode(PrimeNameNode *prime, NameNode *name1, NameNode *name2, NameNode *name3); - - /* copy constructor */ - PartialEquationNode(const PartialEquationNode& obj); - - virtual ~PartialEquationNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPartialEquationNode(this); } - virtual std::string getNodeType() { return "PartialEquation"; } - virtual PartialEquationNode* clone() { return new PartialEquationNode(*this); } - }; - - /* ast Node for FirstLastTypeIndex */ - class FirstLastTypeIndexNode : public ExpressionNode { - public: - /* member variables */ - FirstLastType value; - - /* constructor */ - FirstLastTypeIndexNode(FirstLastType value); - - /* copy constructor */ - FirstLastTypeIndexNode(const FirstLastTypeIndexNode& obj); - - virtual ~FirstLastTypeIndexNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFirstLastTypeIndexNode(this); } - virtual std::string getNodeType() { return "FirstLastTypeIndex"; } - virtual FirstLastTypeIndexNode* clone() { return new FirstLastTypeIndexNode(*this); } - std::string eval() { return FirstLastTypeNames[value]; } - }; - - /* ast Node for PartialBoundary */ - class PartialBoundaryNode : public StatementNode { - public: - /* member variables */ - NameNode *del; - IdentifierNode *name; - FirstLastTypeIndexNode *index; - ExpressionNode *expression; - NameNode *name1; - NameNode *del2; - NameNode *name2; - NameNode *name3; - - /* constructor */ - PartialBoundaryNode(NameNode *del, IdentifierNode *name, FirstLastTypeIndexNode *index, ExpressionNode *expression, NameNode *name1, NameNode *del2, NameNode *name2, NameNode *name3); - - /* copy constructor */ - PartialBoundaryNode(const PartialBoundaryNode& obj); - - virtual ~PartialBoundaryNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPartialBoundaryNode(this); } - virtual std::string getNodeType() { return "PartialBoundary"; } - virtual PartialBoundaryNode* clone() { return new PartialBoundaryNode(*this); } - }; - - /* ast Node for FunctionTableBlock */ - class FunctionTableBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - ArgumentNodeList *arguments; - UnitNode *unit; - ModToken *token; - void* symtab; - - /* constructor */ - FunctionTableBlockNode(NameNode *name, ArgumentNodeList *arguments, UnitNode *unit); - - /* copy constructor */ - FunctionTableBlockNode(const FunctionTableBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~FunctionTableBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFunctionTableBlockNode(this); } - virtual std::string getNodeType() { return "FunctionTableBlock"; } - virtual FunctionTableBlockNode* clone() { return new FunctionTableBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for FunctionBlock */ - class FunctionBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - ArgumentNodeList *arguments; - UnitNode *unit; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - FunctionBlockNode(NameNode *name, ArgumentNodeList *arguments, UnitNode *unit, StatementBlockNode *statementblock); - - /* copy constructor */ - FunctionBlockNode(const FunctionBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~FunctionBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFunctionBlockNode(this); } - virtual std::string getNodeType() { return "FunctionBlock"; } - virtual FunctionBlockNode* clone() { return new FunctionBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for ProcedureBlock */ - class ProcedureBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - ArgumentNodeList *arguments; - UnitNode *unit; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - ProcedureBlockNode(NameNode *name, ArgumentNodeList *arguments, UnitNode *unit, StatementBlockNode *statementblock); - - /* copy constructor */ - ProcedureBlockNode(const ProcedureBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~ProcedureBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitProcedureBlockNode(this); } - virtual std::string getNodeType() { return "ProcedureBlock"; } - virtual ProcedureBlockNode* clone() { return new ProcedureBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for NetReceiveBlock */ - class NetReceiveBlockNode : public BlockNode { - public: - /* member variables */ - ArgumentNodeList *arguments; - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - NetReceiveBlockNode(ArgumentNodeList *arguments, StatementBlockNode *statementblock); - - /* copy constructor */ - NetReceiveBlockNode(const NetReceiveBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~NetReceiveBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNetReceiveBlockNode(this); } - virtual std::string getNodeType() { return "NetReceiveBlock"; } - virtual NetReceiveBlockNode* clone() { return new NetReceiveBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for SolveBlock */ - class SolveBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - NameNode *method; - StatementBlockNode *ifsolerr; - void* symtab; - - /* constructor */ - SolveBlockNode(NameNode *name, NameNode *method, StatementBlockNode *ifsolerr); - - /* copy constructor */ - SolveBlockNode(const SolveBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~SolveBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSolveBlockNode(this); } - virtual std::string getNodeType() { return "SolveBlock"; } - virtual SolveBlockNode* clone() { return new SolveBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for BreakpointBlock */ - class BreakpointBlockNode : public BlockNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - BreakpointBlockNode(StatementBlockNode *statementblock); - - /* copy constructor */ - BreakpointBlockNode(const BreakpointBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~BreakpointBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBreakpointBlockNode(this); } - virtual std::string getNodeType() { return "BreakpointBlock"; } - virtual BreakpointBlockNode* clone() { return new BreakpointBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for TerminalBlock */ - class TerminalBlockNode : public BlockNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - TerminalBlockNode(StatementBlockNode *statementblock); - - /* copy constructor */ - TerminalBlockNode(const TerminalBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~TerminalBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitTerminalBlockNode(this); } - virtual std::string getNodeType() { return "TerminalBlock"; } - virtual TerminalBlockNode* clone() { return new TerminalBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for BeforeBlock */ - class BeforeBlockNode : public BlockNode { - public: - /* member variables */ - BABlockNode *block; - void* symtab; - - /* constructor */ - BeforeBlockNode(BABlockNode *block); - - /* copy constructor */ - BeforeBlockNode(const BeforeBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~BeforeBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBeforeBlockNode(this); } - virtual std::string getNodeType() { return "BeforeBlock"; } - virtual BeforeBlockNode* clone() { return new BeforeBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for AfterBlock */ - class AfterBlockNode : public BlockNode { - public: - /* member variables */ - BABlockNode *block; - void* symtab; - - /* constructor */ - AfterBlockNode(BABlockNode *block); - - /* copy constructor */ - AfterBlockNode(const AfterBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~AfterBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitAfterBlockNode(this); } - virtual std::string getNodeType() { return "AfterBlock"; } - virtual AfterBlockNode* clone() { return new AfterBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for BABlockType */ - class BABlockTypeNode : public ExpressionNode { - public: - /* member variables */ - BAType value; - - /* constructor */ - BABlockTypeNode(BAType value); - - /* copy constructor */ - BABlockTypeNode(const BABlockTypeNode& obj); - - virtual ~BABlockTypeNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBABlockTypeNode(this); } - virtual std::string getNodeType() { return "BABlockType"; } - virtual BABlockTypeNode* clone() { return new BABlockTypeNode(*this); } - std::string eval() { return BATypeNames[value]; } - }; - - /* ast Node for BABlock */ - class BABlockNode : public BlockNode { - public: - /* member variables */ - BABlockTypeNode *type; - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - BABlockNode(BABlockTypeNode *type, StatementBlockNode *statementblock); - - /* copy constructor */ - BABlockNode(const BABlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~BABlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBABlockNode(this); } - virtual std::string getNodeType() { return "BABlock"; } - virtual BABlockNode* clone() { return new BABlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for WatchStatement */ - class WatchStatementNode : public StatementNode { - public: - /* member variables */ - WatchNodeList *statements; - - /* constructor */ - WatchStatementNode(WatchNodeList *statements); - - /* copy constructor */ - WatchStatementNode(const WatchStatementNode& obj); - - void addWatch(WatchNode *s) { - statements->push_back(s); - } - - virtual ~WatchStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWatchStatementNode(this); } - virtual std::string getNodeType() { return "WatchStatement"; } - virtual WatchStatementNode* clone() { return new WatchStatementNode(*this); } - }; - - /* ast Node for Watch */ - class WatchNode : public ExpressionNode { - public: - /* member variables */ - ExpressionNode *expression; - ExpressionNode *value; - - /* constructor */ - WatchNode(ExpressionNode *expression, ExpressionNode *value); - - /* copy constructor */ - WatchNode(const WatchNode& obj); - - virtual ~WatchNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWatchNode(this); } - virtual std::string getNodeType() { return "Watch"; } - virtual WatchNode* clone() { return new WatchNode(*this); } - }; - - /* ast Node for ForNetcon */ - class ForNetconNode : public BlockNode { - public: - /* member variables */ - ArgumentNodeList *arguments; - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - ForNetconNode(ArgumentNodeList *arguments, StatementBlockNode *statementblock); - - /* copy constructor */ - ForNetconNode(const ForNetconNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~ForNetconNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitForNetconNode(this); } - virtual std::string getNodeType() { return "ForNetcon"; } - virtual ForNetconNode* clone() { return new ForNetconNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for MutexLock */ - class MutexLockNode : public StatementNode { - public: - - virtual ~MutexLockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMutexLockNode(this); } - virtual std::string getNodeType() { return "MutexLock"; } - virtual MutexLockNode* clone() { return new MutexLockNode(*this); } - }; - - /* ast Node for MutexUnlock */ - class MutexUnlockNode : public StatementNode { - public: - - virtual ~MutexUnlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMutexUnlockNode(this); } - virtual std::string getNodeType() { return "MutexUnlock"; } - virtual MutexUnlockNode* clone() { return new MutexUnlockNode(*this); } - }; - - /* ast Node for Reset */ - class ResetNode : public StatementNode { - public: - - virtual ~ResetNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitResetNode(this); } - virtual std::string getNodeType() { return "Reset"; } - virtual ResetNode* clone() { return new ResetNode(*this); } - }; - - /* ast Node for Sens */ - class SensNode : public StatementNode { - public: - /* member variables */ - VarNameNodeList *senslist; - - /* constructor */ - SensNode(VarNameNodeList *senslist); - - /* copy constructor */ - SensNode(const SensNode& obj); - - virtual ~SensNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSensNode(this); } - virtual std::string getNodeType() { return "Sens"; } - virtual SensNode* clone() { return new SensNode(*this); } - }; - - /* ast Node for Conserve */ - class ConserveNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *react; - ExpressionNode *expr; - - /* constructor */ - ConserveNode(ExpressionNode *react, ExpressionNode *expr); - - /* copy constructor */ - ConserveNode(const ConserveNode& obj); - - virtual ~ConserveNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConserveNode(this); } - virtual std::string getNodeType() { return "Conserve"; } - virtual ConserveNode* clone() { return new ConserveNode(*this); } - }; - - /* ast Node for Compartment */ - class CompartmentNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - ExpressionNode *expression; - NameNodeList *names; - - /* constructor */ - CompartmentNode(NameNode *name, ExpressionNode *expression, NameNodeList *names); - - /* copy constructor */ - CompartmentNode(const CompartmentNode& obj); - - virtual ~CompartmentNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitCompartmentNode(this); } - virtual std::string getNodeType() { return "Compartment"; } - virtual CompartmentNode* clone() { return new CompartmentNode(*this); } - }; - - /* ast Node for LDifuse */ - class LDifuseNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - ExpressionNode *expression; - NameNodeList *names; - - /* constructor */ - LDifuseNode(NameNode *name, ExpressionNode *expression, NameNodeList *names); - - /* copy constructor */ - LDifuseNode(const LDifuseNode& obj); - - virtual ~LDifuseNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLDifuseNode(this); } - virtual std::string getNodeType() { return "LDifuse"; } - virtual LDifuseNode* clone() { return new LDifuseNode(*this); } - }; - - /* ast Node for KineticBlock */ - class KineticBlockNode : public BlockNode { - public: - /* member variables */ - NameNode *name; - NameNodeList *solvefor; - StatementBlockNode *statementblock; - ModToken *token; - void* symtab; - - /* constructor */ - KineticBlockNode(NameNode *name, NameNodeList *solvefor, StatementBlockNode *statementblock); - - /* copy constructor */ - KineticBlockNode(const KineticBlockNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~KineticBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitKineticBlockNode(this); } - virtual std::string getNodeType() { return "KineticBlock"; } - virtual KineticBlockNode* clone() { return new KineticBlockNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for ReactionStatement */ - class ReactionStatementNode : public StatementNode { - public: - /* member variables */ - ExpressionNode *react1; - ReactionOperatorNode *op; - ExpressionNode *react2; - ExpressionNode *expr1; - ExpressionNode *expr2; - - /* constructor */ - ReactionStatementNode(ExpressionNode *react1, ReactionOperatorNode *op, ExpressionNode *react2, ExpressionNode *expr1, ExpressionNode *expr2); - - /* copy constructor */ - ReactionStatementNode(const ReactionStatementNode& obj); - - virtual ~ReactionStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReactionStatementNode(this); } - virtual std::string getNodeType() { return "ReactionStatement"; } - virtual ReactionStatementNode* clone() { return new ReactionStatementNode(*this); } - }; - - /* ast Node for ReactVarName */ - class ReactVarNameNode : public IdentifierNode { - public: - /* member variables */ - IntegerNode *value; - VarNameNode *name; - - /* constructor */ - ReactVarNameNode(IntegerNode *value, VarNameNode *name); - - /* copy constructor */ - ReactVarNameNode(const ReactVarNameNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ReactVarNameNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReactVarNameNode(this); } - virtual std::string getNodeType() { return "ReactVarName"; } - virtual ReactVarNameNode* clone() { return new ReactVarNameNode(*this); } - }; - - /* ast Node for LagStatement */ - class LagStatementNode : public StatementNode { - public: - /* member variables */ - IdentifierNode *name; - NameNode *byname; - - /* constructor */ - LagStatementNode(IdentifierNode *name, NameNode *byname); - - /* copy constructor */ - LagStatementNode(const LagStatementNode& obj); - - virtual ~LagStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLagStatementNode(this); } - virtual std::string getNodeType() { return "LagStatement"; } - virtual LagStatementNode* clone() { return new LagStatementNode(*this); } - }; - - /* ast Node for QueueStatement */ - class QueueStatementNode : public StatementNode { - public: - /* member variables */ - QueueExpressionTypeNode *qype; - IdentifierNode *name; - - /* constructor */ - QueueStatementNode(QueueExpressionTypeNode *qype, IdentifierNode *name); - - /* copy constructor */ - QueueStatementNode(const QueueStatementNode& obj); - - virtual ~QueueStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitQueueStatementNode(this); } - virtual std::string getNodeType() { return "QueueStatement"; } - virtual QueueStatementNode* clone() { return new QueueStatementNode(*this); } - }; - - /* ast Node for QueueExpressionType */ - class QueueExpressionTypeNode : public ExpressionNode { - public: - /* member variables */ - QueueType value; - - /* constructor */ - QueueExpressionTypeNode(QueueType value); - - /* copy constructor */ - QueueExpressionTypeNode(const QueueExpressionTypeNode& obj); - - virtual ~QueueExpressionTypeNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitQueueExpressionTypeNode(this); } - virtual std::string getNodeType() { return "QueueExpressionType"; } - virtual QueueExpressionTypeNode* clone() { return new QueueExpressionTypeNode(*this); } - std::string eval() { return QueueTypeNames[value]; } - }; - - /* ast Node for MatchBlock */ - class MatchBlockNode : public BlockNode { - public: - /* member variables */ - MatchNodeList *matchs; - void* symtab; - - /* constructor */ - MatchBlockNode(MatchNodeList *matchs); - - /* copy constructor */ - MatchBlockNode(const MatchBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~MatchBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMatchBlockNode(this); } - virtual std::string getNodeType() { return "MatchBlock"; } - virtual MatchBlockNode* clone() { return new MatchBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for Match */ - class MatchNode : public ExpressionNode { - public: - /* member variables */ - IdentifierNode *name; - ExpressionNode *expression; - - /* constructor */ - MatchNode(IdentifierNode *name, ExpressionNode *expression); - - /* copy constructor */ - MatchNode(const MatchNode& obj); - - virtual ~MatchNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMatchNode(this); } - virtual std::string getNodeType() { return "Match"; } - virtual MatchNode* clone() { return new MatchNode(*this); } - }; - - /* ast Node for UnitBlock */ - class UnitBlockNode : public BlockNode { - public: - /* member variables */ - ExpressionNodeList *definitions; - void* symtab; - - /* constructor */ - UnitBlockNode(ExpressionNodeList *definitions); - - /* copy constructor */ - UnitBlockNode(const UnitBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~UnitBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitBlockNode(this); } - virtual std::string getNodeType() { return "UnitBlock"; } - virtual UnitBlockNode* clone() { return new UnitBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for UnitDef */ - class UnitDefNode : public ExpressionNode { - public: - /* member variables */ - UnitNode *unit1; - UnitNode *unit2; - - /* constructor */ - UnitDefNode(UnitNode *unit1, UnitNode *unit2); - - /* copy constructor */ - UnitDefNode(const UnitDefNode& obj); - - virtual std::string getName() { return unit1->getName(); } - virtual ModToken* getToken() { return unit1->getToken(); } - - virtual ~UnitDefNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitDefNode(this); } - virtual std::string getNodeType() { return "UnitDef"; } - virtual UnitDefNode* clone() { return new UnitDefNode(*this); } - }; - - /* ast Node for Factordef */ - class FactordefNode : public ExpressionNode { - public: - /* member variables */ - NameNode *name; - DoubleNode *value; - UnitNode *unit1; - BooleanNode *gt; - UnitNode *unit2; - ModToken *token; - - /* constructor */ - FactordefNode(NameNode *name, DoubleNode *value, UnitNode *unit1, BooleanNode *gt, UnitNode *unit2); - - /* copy constructor */ - FactordefNode(const FactordefNode& obj); - - virtual std::string getName() { return name->getName(); } - - virtual ~FactordefNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFactordefNode(this); } - virtual std::string getNodeType() { return "Factordef"; } - virtual FactordefNode* clone() { return new FactordefNode(*this); } - virtual ModToken *getToken() { return token; } - virtual void setToken(ModToken *tok) { token = tok; } - }; - - /* ast Node for ConstantStatement */ - class ConstantStatementNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - NumberNode *value; - UnitNode *unit; - - /* constructor */ - ConstantStatementNode(NameNode *name, NumberNode *value, UnitNode *unit); - - /* copy constructor */ - ConstantStatementNode(const ConstantStatementNode& obj); - - virtual ~ConstantStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConstantStatementNode(this); } - virtual std::string getNodeType() { return "ConstantStatement"; } - virtual ConstantStatementNode* clone() { return new ConstantStatementNode(*this); } - }; - - /* ast Node for ConstantBlock */ - class ConstantBlockNode : public BlockNode { - public: - /* member variables */ - ConstantStatementNodeList *statements; - void* symtab; - - /* constructor */ - ConstantBlockNode(ConstantStatementNodeList *statements); - - /* copy constructor */ - ConstantBlockNode(const ConstantBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~ConstantBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConstantBlockNode(this); } - virtual std::string getNodeType() { return "ConstantBlock"; } - virtual ConstantBlockNode* clone() { return new ConstantBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for TableStatement */ - class TableStatementNode : public StatementNode { - public: - /* member variables */ - NameNodeList *tablst; - NameNodeList *dependlst; - ExpressionNode *from; - ExpressionNode *to; - IntegerNode *with; - - /* constructor */ - TableStatementNode(NameNodeList *tablst, NameNodeList *dependlst, ExpressionNode *from, ExpressionNode *to, IntegerNode *with); - - /* copy constructor */ - TableStatementNode(const TableStatementNode& obj); - - virtual ~TableStatementNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitTableStatementNode(this); } - virtual std::string getNodeType() { return "TableStatement"; } - virtual TableStatementNode* clone() { return new TableStatementNode(*this); } - }; - - /* ast Node for NeuronBlock */ - class NeuronBlockNode : public BlockNode { - public: - /* member variables */ - StatementBlockNode *statementblock; - void* symtab; - - /* constructor */ - NeuronBlockNode(StatementBlockNode *statementblock); - - /* copy constructor */ - NeuronBlockNode(const NeuronBlockNode& obj); - std::string getName() { return getNodeType(); } - - virtual ~NeuronBlockNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNeuronBlockNode(this); } - virtual std::string getNodeType() { return "NeuronBlock"; } - virtual NeuronBlockNode* clone() { return new NeuronBlockNode(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } - }; - - /* ast Node for ReadIonVar */ - class ReadIonVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - ReadIonVarNode(NameNode *name); - - /* copy constructor */ - ReadIonVarNode(const ReadIonVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ReadIonVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReadIonVarNode(this); } - virtual std::string getNodeType() { return "ReadIonVar"; } - virtual ReadIonVarNode* clone() { return new ReadIonVarNode(*this); } - }; - - /* ast Node for WriteIonVar */ - class WriteIonVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - WriteIonVarNode(NameNode *name); - - /* copy constructor */ - WriteIonVarNode(const WriteIonVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~WriteIonVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWriteIonVarNode(this); } - virtual std::string getNodeType() { return "WriteIonVar"; } - virtual WriteIonVarNode* clone() { return new WriteIonVarNode(*this); } - }; - - /* ast Node for NonspeCurVar */ - class NonspeCurVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - NonspeCurVarNode(NameNode *name); - - /* copy constructor */ - NonspeCurVarNode(const NonspeCurVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~NonspeCurVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNonspeCurVarNode(this); } - virtual std::string getNodeType() { return "NonspeCurVar"; } - virtual NonspeCurVarNode* clone() { return new NonspeCurVarNode(*this); } - }; - - /* ast Node for ElectrodeCurVar */ - class ElectrodeCurVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - ElectrodeCurVarNode(NameNode *name); - - /* copy constructor */ - ElectrodeCurVarNode(const ElectrodeCurVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ElectrodeCurVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitElectrodeCurVarNode(this); } - virtual std::string getNodeType() { return "ElectrodeCurVar"; } - virtual ElectrodeCurVarNode* clone() { return new ElectrodeCurVarNode(*this); } - }; - - /* ast Node for SectionVar */ - class SectionVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - SectionVarNode(NameNode *name); - - /* copy constructor */ - SectionVarNode(const SectionVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~SectionVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSectionVarNode(this); } - virtual std::string getNodeType() { return "SectionVar"; } - virtual SectionVarNode* clone() { return new SectionVarNode(*this); } - }; - - /* ast Node for RangeVar */ - class RangeVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - RangeVarNode(NameNode *name); - - /* copy constructor */ - RangeVarNode(const RangeVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~RangeVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitRangeVarNode(this); } - virtual std::string getNodeType() { return "RangeVar"; } - virtual RangeVarNode* clone() { return new RangeVarNode(*this); } - }; - - /* ast Node for GlobalVar */ - class GlobalVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - GlobalVarNode(NameNode *name); - - /* copy constructor */ - GlobalVarNode(const GlobalVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~GlobalVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitGlobalVarNode(this); } - virtual std::string getNodeType() { return "GlobalVar"; } - virtual GlobalVarNode* clone() { return new GlobalVarNode(*this); } - }; - - /* ast Node for PointerVar */ - class PointerVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - PointerVarNode(NameNode *name); - - /* copy constructor */ - PointerVarNode(const PointerVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~PointerVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPointerVarNode(this); } - virtual std::string getNodeType() { return "PointerVar"; } - virtual PointerVarNode* clone() { return new PointerVarNode(*this); } - }; - - /* ast Node for BbcorePointerVar */ - class BbcorePointerVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - BbcorePointerVarNode(NameNode *name); - - /* copy constructor */ - BbcorePointerVarNode(const BbcorePointerVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~BbcorePointerVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBbcorePointerVarNode(this); } - virtual std::string getNodeType() { return "BbcorePointerVar"; } - virtual BbcorePointerVarNode* clone() { return new BbcorePointerVarNode(*this); } - }; - - /* ast Node for ExternVar */ - class ExternVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - ExternVarNode(NameNode *name); - - /* copy constructor */ - ExternVarNode(const ExternVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ExternVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitExternVarNode(this); } - virtual std::string getNodeType() { return "ExternVar"; } - virtual ExternVarNode* clone() { return new ExternVarNode(*this); } - }; - - /* ast Node for ThreadsafeVar */ - class ThreadsafeVarNode : public IdentifierNode { - public: - /* member variables */ - NameNode *name; - - /* constructor */ - ThreadsafeVarNode(NameNode *name); - - /* copy constructor */ - ThreadsafeVarNode(const ThreadsafeVarNode& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - - virtual ~ThreadsafeVarNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitThreadsafeVarNode(this); } - virtual std::string getNodeType() { return "ThreadsafeVar"; } - virtual ThreadsafeVarNode* clone() { return new ThreadsafeVarNode(*this); } - }; - - /* ast Node for NrnSuffix */ - class NrnSuffixNode : public StatementNode { - public: - /* member variables */ - NameNode *type; - NameNode *name; - - /* constructor */ - NrnSuffixNode(NameNode *type, NameNode *name); - - /* copy constructor */ - NrnSuffixNode(const NrnSuffixNode& obj); - - virtual ~NrnSuffixNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnSuffixNode(this); } - virtual std::string getNodeType() { return "NrnSuffix"; } - virtual NrnSuffixNode* clone() { return new NrnSuffixNode(*this); } - }; - - /* ast Node for NrnUseion */ - class NrnUseionNode : public StatementNode { - public: - /* member variables */ - NameNode *name; - ReadIonVarNodeList *readlist; - WriteIonVarNodeList *writelist; - ValenceNode *valence; - - /* constructor */ - NrnUseionNode(NameNode *name, ReadIonVarNodeList *readlist, WriteIonVarNodeList *writelist, ValenceNode *valence); - - /* copy constructor */ - NrnUseionNode(const NrnUseionNode& obj); - - virtual ~NrnUseionNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnUseionNode(this); } - virtual std::string getNodeType() { return "NrnUseion"; } - virtual NrnUseionNode* clone() { return new NrnUseionNode(*this); } - }; - - /* ast Node for NrnNonspecific */ - class NrnNonspecificNode : public StatementNode { - public: - /* member variables */ - NonspeCurVarNodeList *currents; - - /* constructor */ - NrnNonspecificNode(NonspeCurVarNodeList *currents); - - /* copy constructor */ - NrnNonspecificNode(const NrnNonspecificNode& obj); - - virtual ~NrnNonspecificNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnNonspecificNode(this); } - virtual std::string getNodeType() { return "NrnNonspecific"; } - virtual NrnNonspecificNode* clone() { return new NrnNonspecificNode(*this); } - }; - - /* ast Node for NrnElctrodeCurrent */ - class NrnElctrodeCurrentNode : public StatementNode { - public: - /* member variables */ - ElectrodeCurVarNodeList *ecurrents; - - /* constructor */ - NrnElctrodeCurrentNode(ElectrodeCurVarNodeList *ecurrents); - - /* copy constructor */ - NrnElctrodeCurrentNode(const NrnElctrodeCurrentNode& obj); - - virtual ~NrnElctrodeCurrentNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnElctrodeCurrentNode(this); } - virtual std::string getNodeType() { return "NrnElctrodeCurrent"; } - virtual NrnElctrodeCurrentNode* clone() { return new NrnElctrodeCurrentNode(*this); } - }; - - /* ast Node for NrnSection */ - class NrnSectionNode : public StatementNode { - public: - /* member variables */ - SectionVarNodeList *sections; - - /* constructor */ - NrnSectionNode(SectionVarNodeList *sections); - - /* copy constructor */ - NrnSectionNode(const NrnSectionNode& obj); - - virtual ~NrnSectionNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnSectionNode(this); } - virtual std::string getNodeType() { return "NrnSection"; } - virtual NrnSectionNode* clone() { return new NrnSectionNode(*this); } - }; - - /* ast Node for NrnRange */ - class NrnRangeNode : public StatementNode { - public: - /* member variables */ - RangeVarNodeList *range_vars; - - /* constructor */ - NrnRangeNode(RangeVarNodeList *range_vars); - - /* copy constructor */ - NrnRangeNode(const NrnRangeNode& obj); - - virtual ~NrnRangeNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnRangeNode(this); } - virtual std::string getNodeType() { return "NrnRange"; } - virtual NrnRangeNode* clone() { return new NrnRangeNode(*this); } - }; - - /* ast Node for NrnGlobal */ - class NrnGlobalNode : public StatementNode { - public: - /* member variables */ - GlobalVarNodeList *global_vars; - - /* constructor */ - NrnGlobalNode(GlobalVarNodeList *global_vars); - - /* copy constructor */ - NrnGlobalNode(const NrnGlobalNode& obj); - - virtual ~NrnGlobalNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnGlobalNode(this); } - virtual std::string getNodeType() { return "NrnGlobal"; } - virtual NrnGlobalNode* clone() { return new NrnGlobalNode(*this); } - }; - - /* ast Node for NrnPointer */ - class NrnPointerNode : public StatementNode { - public: - /* member variables */ - PointerVarNodeList *pointers; - - /* constructor */ - NrnPointerNode(PointerVarNodeList *pointers); - - /* copy constructor */ - NrnPointerNode(const NrnPointerNode& obj); - - virtual ~NrnPointerNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnPointerNode(this); } - virtual std::string getNodeType() { return "NrnPointer"; } - virtual NrnPointerNode* clone() { return new NrnPointerNode(*this); } - }; - - /* ast Node for NrnBbcorePtr */ - class NrnBbcorePtrNode : public StatementNode { - public: - /* member variables */ - BbcorePointerVarNodeList *bbcore_pointers; - - /* constructor */ - NrnBbcorePtrNode(BbcorePointerVarNodeList *bbcore_pointers); - - /* copy constructor */ - NrnBbcorePtrNode(const NrnBbcorePtrNode& obj); - - virtual ~NrnBbcorePtrNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnBbcorePtrNode(this); } - virtual std::string getNodeType() { return "NrnBbcorePtr"; } - virtual NrnBbcorePtrNode* clone() { return new NrnBbcorePtrNode(*this); } - }; - - /* ast Node for NrnExternal */ - class NrnExternalNode : public StatementNode { - public: - /* member variables */ - ExternVarNodeList *externals; - - /* constructor */ - NrnExternalNode(ExternVarNodeList *externals); - - /* copy constructor */ - NrnExternalNode(const NrnExternalNode& obj); - - virtual ~NrnExternalNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnExternalNode(this); } - virtual std::string getNodeType() { return "NrnExternal"; } - virtual NrnExternalNode* clone() { return new NrnExternalNode(*this); } - }; - - /* ast Node for NrnThreadSafe */ - class NrnThreadSafeNode : public StatementNode { - public: - /* member variables */ - ThreadsafeVarNodeList *threadsafe; - - /* constructor */ - NrnThreadSafeNode(ThreadsafeVarNodeList *threadsafe); - - /* copy constructor */ - NrnThreadSafeNode(const NrnThreadSafeNode& obj); - - virtual ~NrnThreadSafeNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnThreadSafeNode(this); } - virtual std::string getNodeType() { return "NrnThreadSafe"; } - virtual NrnThreadSafeNode* clone() { return new NrnThreadSafeNode(*this); } - }; - - /* ast Node for Verbatim */ - class VerbatimNode : public StatementNode { - public: - /* member variables */ - StringNode *statement; - - /* constructor */ - VerbatimNode(StringNode *statement); - - /* copy constructor */ - VerbatimNode(const VerbatimNode& obj); - - virtual ~VerbatimNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitVerbatimNode(this); } - virtual std::string getNodeType() { return "Verbatim"; } - virtual VerbatimNode* clone() { return new VerbatimNode(*this); } - }; - - /* ast Node for Comment */ - class CommentNode : public StatementNode { - public: - /* member variables */ - StringNode *comment; - - /* constructor */ - CommentNode(StringNode *comment); - - /* copy constructor */ - CommentNode(const CommentNode& obj); - - virtual ~CommentNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitCommentNode(this); } - virtual std::string getNodeType() { return "Comment"; } - virtual CommentNode* clone() { return new CommentNode(*this); } - }; - - /* ast Node for Valence */ - class ValenceNode : public ExpressionNode { - public: - /* member variables */ - NameNode *type; - DoubleNode *value; - - /* constructor */ - ValenceNode(NameNode *type, DoubleNode *value); - - /* copy constructor */ - ValenceNode(const ValenceNode& obj); - - virtual ~ValenceNode(); - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitValenceNode(this); } - virtual std::string getNodeType() { return "Valence"; } - virtual ValenceNode* clone() { return new ValenceNode(*this); } - }; - -} //namespace ast - -#endif + /* forward declarations of AST classes */ + class Statement; + class Expression; + class Block; + class Identifier; + class Number; + class String; + class Integer; + class Float; + class Double; + class Boolean; + class Name; + class PrimeName; + class VarName; + class IndexedName; + class Argument; + class ReactVarName; + class ReadIonVar; + class WriteIonVar; + class NonspeCurVar; + class ElectrodeCurVar; + class SectionVar; + class RangeVar; + class GlobalVar; + class PointerVar; + class BbcorePointerVar; + class ExternVar; + class ThreadsafeVar; + class ParamBlock; + class StepBlock; + class IndependentBlock; + class DependentBlock; + class StateBlock; + class PlotBlock; + class InitialBlock; + class ConstructorBlock; + class DestructorBlock; + class StatementBlock; + class DerivativeBlock; + class LinearBlock; + class NonLinearBlock; + class DiscreteBlock; + class PartialBlock; + class FunctionTableBlock; + class FunctionBlock; + class ProcedureBlock; + class NetReceiveBlock; + class SolveBlock; + class BreakpointBlock; + class TerminalBlock; + class BeforeBlock; + class AfterBlock; + class BABlock; + class ForNetcon; + class KineticBlock; + class MatchBlock; + class UnitBlock; + class ConstantBlock; + class NeuronBlock; + class Unit; + class DoubleUnit; + class LocalVariable; + class Limits; + class NumberRange; + class PlotVariable; + class BinaryOperator; + class UnaryOperator; + class ReactionOperator; + class BinaryExpression; + class UnaryExpression; + class NonLinEuation; + class LinEquation; + class FunctionCall; + class FirstLastTypeIndex; + class Watch; + class QueueExpressionType; + class Match; + class BABlockType; + class UnitDef; + class FactorDef; + class Valence; + class UnitState; + class LocalListStatement; + class Model; + class Define; + class Include; + class ParamAssign; + class Stepped; + class IndependentDef; + class DependentDef; + class PlotDeclaration; + class ConductanceHint; + class ExpressionStatement; + class ProtectStatement; + class FromStatement; + class ForAllStatement; + class WhileStatement; + class IfStatement; + class ElseIfStatement; + class ElseStatement; + class PartialEquation; + class PartialBoundary; + class WatchStatement; + class MutexLock; + class MutexUnlock; + class Reset; + class Sens; + class Conserve; + class Compartment; + class LDifuse; + class ReactionStatement; + class LagStatement; + class QueueStatement; + class ConstantStatement; + class TableStatement; + class NrnSuffix; + class NrnUseion; + class NrnNonspecific; + class NrnElctrodeCurrent; + class NrnSection; + class NrnRange; + class NrnGlobal; + class NrnPointer; + class NrnBbcorePtr; + class NrnExternal; + class NrnThreadSafe; + class Verbatim; + class Comment; + class Program; + + /* std::vector for convenience */ + using StatementList = std::vector>; + using ExpressionList = std::vector>; + using BlockList = std::vector>; + using IdentifierList = std::vector>; + using NumberList = std::vector>; + using StringList = std::vector>; + using IntegerList = std::vector>; + using FloatList = std::vector>; + using DoubleList = std::vector>; + using BooleanList = std::vector>; + using NameList = std::vector>; + using PrimeNameList = std::vector>; + using VarNameList = std::vector>; + using IndexedNameList = std::vector>; + using ArgumentList = std::vector>; + using ReactVarNameList = std::vector>; + using ReadIonVarList = std::vector>; + using WriteIonVarList = std::vector>; + using NonspeCurVarList = std::vector>; + using ElectrodeCurVarList = std::vector>; + using SectionVarList = std::vector>; + using RangeVarList = std::vector>; + using GlobalVarList = std::vector>; + using PointerVarList = std::vector>; + using BbcorePointerVarList = std::vector>; + using ExternVarList = std::vector>; + using ThreadsafeVarList = std::vector>; + using ParamBlockList = std::vector>; + using StepBlockList = std::vector>; + using IndependentBlockList = std::vector>; + using DependentBlockList = std::vector>; + using StateBlockList = std::vector>; + using PlotBlockList = std::vector>; + using InitialBlockList = std::vector>; + using ConstructorBlockList = std::vector>; + using DestructorBlockList = std::vector>; + using StatementBlockList = std::vector>; + using DerivativeBlockList = std::vector>; + using LinearBlockList = std::vector>; + using NonLinearBlockList = std::vector>; + using DiscreteBlockList = std::vector>; + using PartialBlockList = std::vector>; + using FunctionTableBlockList = std::vector>; + using FunctionBlockList = std::vector>; + using ProcedureBlockList = std::vector>; + using NetReceiveBlockList = std::vector>; + using SolveBlockList = std::vector>; + using BreakpointBlockList = std::vector>; + using TerminalBlockList = std::vector>; + using BeforeBlockList = std::vector>; + using AfterBlockList = std::vector>; + using BABlockList = std::vector>; + using ForNetconList = std::vector>; + using KineticBlockList = std::vector>; + using MatchBlockList = std::vector>; + using UnitBlockList = std::vector>; + using ConstantBlockList = std::vector>; + using NeuronBlockList = std::vector>; + using UnitList = std::vector>; + using DoubleUnitList = std::vector>; + using LocalVariableList = std::vector>; + using LimitsList = std::vector>; + using NumberRangeList = std::vector>; + using PlotVariableList = std::vector>; + using BinaryOperatorList = std::vector>; + using UnaryOperatorList = std::vector>; + using ReactionOperatorList = std::vector>; + using BinaryExpressionList = std::vector>; + using UnaryExpressionList = std::vector>; + using NonLinEuationList = std::vector>; + using LinEquationList = std::vector>; + using FunctionCallList = std::vector>; + using FirstLastTypeIndexList = std::vector>; + using WatchList = std::vector>; + using QueueExpressionTypeList = std::vector>; + using MatchList = std::vector>; + using BABlockTypeList = std::vector>; + using UnitDefList = std::vector>; + using FactorDefList = std::vector>; + using ValenceList = std::vector>; + using UnitStateList = std::vector>; + using LocalListStatementList = std::vector>; + using ModelList = std::vector>; + using DefineList = std::vector>; + using IncludeList = std::vector>; + using ParamAssignList = std::vector>; + using SteppedList = std::vector>; + using IndependentDefList = std::vector>; + using DependentDefList = std::vector>; + using PlotDeclarationList = std::vector>; + using ConductanceHintList = std::vector>; + using ExpressionStatementList = std::vector>; + using ProtectStatementList = std::vector>; + using FromStatementList = std::vector>; + using ForAllStatementList = std::vector>; + using WhileStatementList = std::vector>; + using IfStatementList = std::vector>; + using ElseIfStatementList = std::vector>; + using ElseStatementList = std::vector>; + using PartialEquationList = std::vector>; + using PartialBoundaryList = std::vector>; + using WatchStatementList = std::vector>; + using MutexLockList = std::vector>; + using MutexUnlockList = std::vector>; + using ResetList = std::vector>; + using SensList = std::vector>; + using ConserveList = std::vector>; + using CompartmentList = std::vector>; + using LDifuseList = std::vector>; + using ReactionStatementList = std::vector>; + using LagStatementList = std::vector>; + using QueueStatementList = std::vector>; + using ConstantStatementList = std::vector>; + using TableStatementList = std::vector>; + using NrnSuffixList = std::vector>; + using NrnUseionList = std::vector>; + using NrnNonspecificList = std::vector>; + using NrnElctrodeCurrentList = std::vector>; + using NrnSectionList = std::vector>; + using NrnRangeList = std::vector>; + using NrnGlobalList = std::vector>; + using NrnPointerList = std::vector>; + using NrnBbcorePtrList = std::vector>; + using NrnExternalList = std::vector>; + using NrnThreadSafeList = std::vector>; + using VerbatimList = std::vector>; + using CommentList = std::vector>; + using ProgramList = std::vector>; + using NumberList = std::vector>; + using IdentifierList = std::vector>; + using BlockList = std::vector>; + using ExpressionList = std::vector>; + using StatementList = std::vector>; + using statement_ptr = Statement*; + using expression_ptr = Expression*; + using block_ptr = Block*; + using identifier_ptr = Identifier*; + using number_ptr = Number*; + using string_ptr = String*; + using integer_ptr = Integer*; + using name_ptr = Name*; + using float_ptr = Float*; + using double_ptr = Double*; + using boolean_ptr = Boolean*; + using primename_ptr = PrimeName*; + using varname_ptr = VarName*; + using indexedname_ptr = IndexedName*; + using argument_ptr = Argument*; + using unit_ptr = Unit*; + using reactvarname_ptr = ReactVarName*; + using readionvar_ptr = ReadIonVar*; + using writeionvar_ptr = WriteIonVar*; + using nonspecurvar_ptr = NonspeCurVar*; + using electrodecurvar_ptr = ElectrodeCurVar*; + using sectionvar_ptr = SectionVar*; + using rangevar_ptr = RangeVar*; + using globalvar_ptr = GlobalVar*; + using pointervar_ptr = PointerVar*; + using bbcorepointervar_ptr = BbcorePointerVar*; + using externvar_ptr = ExternVar*; + using threadsafevar_ptr = ThreadsafeVar*; + using paramblock_ptr = ParamBlock*; + using paramassign_list = ParamAssignList; + using stepblock_ptr = StepBlock*; + using stepped_list = SteppedList; + using independentblock_ptr = IndependentBlock*; + using independentdef_list = IndependentDefList; + using dependentblock_ptr = DependentBlock*; + using dependentdef_list = DependentDefList; + using stateblock_ptr = StateBlock*; + using plotblock_ptr = PlotBlock*; + using plotdeclaration_ptr = PlotDeclaration*; + using initialblock_ptr = InitialBlock*; + using statementblock_ptr = StatementBlock*; + using constructorblock_ptr = ConstructorBlock*; + using destructorblock_ptr = DestructorBlock*; + using statement_list = StatementList; + using derivativeblock_ptr = DerivativeBlock*; + using linearblock_ptr = LinearBlock*; + using name_list = NameList; + using nonlinearblock_ptr = NonLinearBlock*; + using discreteblock_ptr = DiscreteBlock*; + using partialblock_ptr = PartialBlock*; + using functiontableblock_ptr = FunctionTableBlock*; + using argument_list = ArgumentList; + using functionblock_ptr = FunctionBlock*; + using procedureblock_ptr = ProcedureBlock*; + using netreceiveblock_ptr = NetReceiveBlock*; + using solveblock_ptr = SolveBlock*; + using breakpointblock_ptr = BreakpointBlock*; + using terminalblock_ptr = TerminalBlock*; + using beforeblock_ptr = BeforeBlock*; + using bablock_ptr = BABlock*; + using afterblock_ptr = AfterBlock*; + using bablocktype_ptr = BABlockType*; + using fornetcon_ptr = ForNetcon*; + using kineticblock_ptr = KineticBlock*; + using matchblock_ptr = MatchBlock*; + using match_list = MatchList; + using unitblock_ptr = UnitBlock*; + using expression_list = ExpressionList; + using constantblock_ptr = ConstantBlock*; + using constantstatement_list = ConstantStatementList; + using neuronblock_ptr = NeuronBlock*; + using doubleunit_ptr = DoubleUnit*; + using localvariable_ptr = LocalVariable*; + using limits_ptr = Limits*; + using numberrange_ptr = NumberRange*; + using plotvariable_ptr = PlotVariable*; + using binaryoperator_ptr = BinaryOperator; + using unaryoperator_ptr = UnaryOperator; + using reactionoperator_ptr = ReactionOperator; + using binaryexpression_ptr = BinaryExpression*; + using unaryexpression_ptr = UnaryExpression*; + using nonlineuation_ptr = NonLinEuation*; + using linequation_ptr = LinEquation*; + using functioncall_ptr = FunctionCall*; + using firstlasttypeindex_ptr = FirstLastTypeIndex*; + using watch_ptr = Watch*; + using queueexpressiontype_ptr = QueueExpressionType*; + using match_ptr = Match*; + using unitdef_ptr = UnitDef*; + using factordef_ptr = FactorDef*; + using valence_ptr = Valence*; + using unitstate_ptr = UnitState*; + using localliststatement_ptr = LocalListStatement*; + using localvariable_list = LocalVariableList; + using model_ptr = Model*; + using define_ptr = Define*; + using include_ptr = Include*; + using paramassign_ptr = ParamAssign*; + using stepped_ptr = Stepped*; + using number_list = NumberList; + using independentdef_ptr = IndependentDef*; + using dependentdef_ptr = DependentDef*; + using plotvariable_list = PlotVariableList; + using conductancehint_ptr = ConductanceHint*; + using expressionstatement_ptr = ExpressionStatement*; + using protectstatement_ptr = ProtectStatement*; + using fromstatement_ptr = FromStatement*; + using forallstatement_ptr = ForAllStatement*; + using whilestatement_ptr = WhileStatement*; + using ifstatement_ptr = IfStatement*; + using elseifstatement_list = ElseIfStatementList; + using elsestatement_ptr = ElseStatement*; + using elseifstatement_ptr = ElseIfStatement*; + using partialequation_ptr = PartialEquation*; + using partialboundary_ptr = PartialBoundary*; + using watchstatement_ptr = WatchStatement*; + using watch_list = WatchList; + using mutexlock_ptr = MutexLock*; + using mutexunlock_ptr = MutexUnlock*; + using reset_ptr = Reset*; + using sens_ptr = Sens*; + using varname_list = VarNameList; + using conserve_ptr = Conserve*; + using compartment_ptr = Compartment*; + using ldifuse_ptr = LDifuse*; + using reactionstatement_ptr = ReactionStatement*; + using lagstatement_ptr = LagStatement*; + using queuestatement_ptr = QueueStatement*; + using constantstatement_ptr = ConstantStatement*; + using tablestatement_ptr = TableStatement*; + using nrnsuffix_ptr = NrnSuffix*; + using nrnuseion_ptr = NrnUseion*; + using readionvar_list = ReadIonVarList; + using writeionvar_list = WriteIonVarList; + using nrnnonspecific_ptr = NrnNonspecific*; + using nonspecurvar_list = NonspeCurVarList; + using nrnelctrodecurrent_ptr = NrnElctrodeCurrent*; + using electrodecurvar_list = ElectrodeCurVarList; + using nrnsection_ptr = NrnSection*; + using sectionvar_list = SectionVarList; + using nrnrange_ptr = NrnRange*; + using rangevar_list = RangeVarList; + using nrnglobal_ptr = NrnGlobal*; + using globalvar_list = GlobalVarList; + using nrnpointer_ptr = NrnPointer*; + using pointervar_list = PointerVarList; + using nrnbbcoreptr_ptr = NrnBbcorePtr*; + using bbcorepointervar_list = BbcorePointerVarList; + using nrnexternal_ptr = NrnExternal*; + using externvar_list = ExternVarList; + using nrnthreadsafe_ptr = NrnThreadSafe*; + using threadsafevar_list = ThreadsafeVarList; + using verbatim_ptr = Verbatim*; + using comment_ptr = Comment*; + using program_ptr = Program*; + using block_list = BlockList; + + #include + + /* Define all AST nodes */ + + class Statement : public AST { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStatement(this); } + virtual std::string getType() { return "Statement"; } + virtual Statement* clone() { return new Statement(*this); } + }; + + class Expression : public AST { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitExpression(this); } + virtual std::string getType() { return "Expression"; } + virtual Expression* clone() { return new Expression(*this); } + }; + + class Block : public Expression { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBlock(this); } + virtual std::string getType() { return "Block"; } + virtual Block* clone() { return new Block(*this); } + }; + + class Identifier : public Expression { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIdentifier(this); } + virtual std::string getType() { return "Identifier"; } + virtual Identifier* clone() { return new Identifier(*this); } + virtual void setName(std::string name) { std::cout << "ERROR : setName() not implemented! "; abort(); } + }; + + class Number : public Expression { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNumber(this); } + virtual std::string getType() { return "Number"; } + virtual Number* clone() { return new Number(*this); } + virtual void negate() { std::cout << "ERROR : negate() not implemented! "; abort(); } + }; + + class String : public Expression { + public: + /* member variables */ + std::string value; + std::shared_ptr token; + /* constructors */ + String(std::string value); + String(const String& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitString(this); } + virtual std::string getType() { return "String"; } + virtual String* clone() { return new String(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + std::string eval() { return value; } + virtual void set(std::string _value) { value = _value; } + }; + + class Integer : public Number { + public: + /* member variables */ + int value; + std::shared_ptr macroname; + std::shared_ptr token; + /* constructors */ + Integer(int value, Name* macroname); + Integer(const Integer& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitInteger(this); } + virtual std::string getType() { return "Integer"; } + virtual Integer* clone() { return new Integer(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void negate() { value = -value; } + int eval() { return value; } + virtual void set(int _value) { value = _value; } + }; + + class Float : public Number { + public: + /* member variables */ + float value; + /* constructors */ + Float(float value); + Float(const Float& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFloat(this); } + virtual std::string getType() { return "Float"; } + virtual Float* clone() { return new Float(*this); } + void negate() { value = -value; } + float eval() { return value; } + virtual void set(float _value) { value = _value; } + }; + + class Double : public Number { + public: + /* member variables */ + double value; + std::shared_ptr token; + /* constructors */ + Double(double value); + Double(const Double& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDouble(this); } + virtual std::string getType() { return "Double"; } + virtual Double* clone() { return new Double(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void negate() { value = -value; } + double eval() { return value; } + virtual void set(double _value) { value = _value; } + }; + + class Boolean : public Number { + public: + /* member variables */ + int value; + /* constructors */ + Boolean(int value); + Boolean(const Boolean& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBoolean(this); } + virtual std::string getType() { return "Boolean"; } + virtual Boolean* clone() { return new Boolean(*this); } + void negate() { value = !value; } + bool eval() { return value; } + virtual void set(bool _value) { value = _value; } + }; + + class Name : public Identifier { + public: + /* member variables */ + std::shared_ptr value; + std::shared_ptr token; + /* constructors */ + Name(String* value); + Name(const Name& obj); + + virtual std::string getName() { return value->eval(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitName(this); } + virtual std::string getType() { return "Name"; } + virtual Name* clone() { return new Name(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setName(std::string name) { value->set(name); } + }; + + class PrimeName : public Identifier { + public: + /* member variables */ + std::shared_ptr value; + std::shared_ptr order; + std::shared_ptr token; + /* constructors */ + PrimeName(String* value, Integer* order); + PrimeName(const PrimeName& obj); + + virtual std::string getName() { return value->eval(); } + virtual int getOrder() { return order->eval(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPrimeName(this); } + virtual std::string getType() { return "PrimeName"; } + virtual PrimeName* clone() { return new PrimeName(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + }; + + class VarName : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr at_index; + /* constructors */ + VarName(Identifier* name, Integer* at_index); + VarName(const VarName& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitVarName(this); } + virtual std::string getType() { return "VarName"; } + virtual VarName* clone() { return new VarName(*this); } + }; + + class IndexedName : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr index; + /* constructors */ + IndexedName(Identifier* name, Expression* index); + IndexedName(const IndexedName& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIndexedName(this); } + virtual std::string getType() { return "IndexedName"; } + virtual IndexedName* clone() { return new IndexedName(*this); } + }; + + class Argument : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr unit; + /* constructors */ + Argument(Identifier* name, Unit* unit); + Argument(const Argument& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitArgument(this); } + virtual std::string getType() { return "Argument"; } + virtual Argument* clone() { return new Argument(*this); } + }; + + class ReactVarName : public Identifier { + public: + /* member variables */ + std::shared_ptr value; + std::shared_ptr name; + /* constructors */ + ReactVarName(Integer* value, VarName* name); + ReactVarName(const ReactVarName& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReactVarName(this); } + virtual std::string getType() { return "ReactVarName"; } + virtual ReactVarName* clone() { return new ReactVarName(*this); } + }; + + class ReadIonVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + ReadIonVar(Name* name); + ReadIonVar(const ReadIonVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReadIonVar(this); } + virtual std::string getType() { return "ReadIonVar"; } + virtual ReadIonVar* clone() { return new ReadIonVar(*this); } + }; + + class WriteIonVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + WriteIonVar(Name* name); + WriteIonVar(const WriteIonVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWriteIonVar(this); } + virtual std::string getType() { return "WriteIonVar"; } + virtual WriteIonVar* clone() { return new WriteIonVar(*this); } + }; + + class NonspeCurVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + NonspeCurVar(Name* name); + NonspeCurVar(const NonspeCurVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNonspeCurVar(this); } + virtual std::string getType() { return "NonspeCurVar"; } + virtual NonspeCurVar* clone() { return new NonspeCurVar(*this); } + }; + + class ElectrodeCurVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + ElectrodeCurVar(Name* name); + ElectrodeCurVar(const ElectrodeCurVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitElectrodeCurVar(this); } + virtual std::string getType() { return "ElectrodeCurVar"; } + virtual ElectrodeCurVar* clone() { return new ElectrodeCurVar(*this); } + }; + + class SectionVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + SectionVar(Name* name); + SectionVar(const SectionVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSectionVar(this); } + virtual std::string getType() { return "SectionVar"; } + virtual SectionVar* clone() { return new SectionVar(*this); } + }; + + class RangeVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + RangeVar(Name* name); + RangeVar(const RangeVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitRangeVar(this); } + virtual std::string getType() { return "RangeVar"; } + virtual RangeVar* clone() { return new RangeVar(*this); } + }; + + class GlobalVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + GlobalVar(Name* name); + GlobalVar(const GlobalVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitGlobalVar(this); } + virtual std::string getType() { return "GlobalVar"; } + virtual GlobalVar* clone() { return new GlobalVar(*this); } + }; + + class PointerVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + PointerVar(Name* name); + PointerVar(const PointerVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPointerVar(this); } + virtual std::string getType() { return "PointerVar"; } + virtual PointerVar* clone() { return new PointerVar(*this); } + }; + + class BbcorePointerVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + BbcorePointerVar(Name* name); + BbcorePointerVar(const BbcorePointerVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBbcorePointerVar(this); } + virtual std::string getType() { return "BbcorePointerVar"; } + virtual BbcorePointerVar* clone() { return new BbcorePointerVar(*this); } + }; + + class ExternVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + ExternVar(Name* name); + ExternVar(const ExternVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitExternVar(this); } + virtual std::string getType() { return "ExternVar"; } + virtual ExternVar* clone() { return new ExternVar(*this); } + }; + + class ThreadsafeVar : public Identifier { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + ThreadsafeVar(Name* name); + ThreadsafeVar(const ThreadsafeVar& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitThreadsafeVar(this); } + virtual std::string getType() { return "ThreadsafeVar"; } + virtual ThreadsafeVar* clone() { return new ThreadsafeVar(*this); } + }; + + class ParamBlock : public Block { + public: + /* member variables */ + ParamAssignList statements; + void* symtab = nullptr; + + /* constructors */ + ParamBlock(ParamAssignList statements); + ParamBlock(const ParamBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitParamBlock(this); } + virtual std::string getType() { return "ParamBlock"; } + virtual ParamBlock* clone() { return new ParamBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class StepBlock : public Block { + public: + /* member variables */ + SteppedList statements; + void* symtab = nullptr; + + /* constructors */ + StepBlock(SteppedList statements); + StepBlock(const StepBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStepBlock(this); } + virtual std::string getType() { return "StepBlock"; } + virtual StepBlock* clone() { return new StepBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class IndependentBlock : public Block { + public: + /* member variables */ + IndependentDefList definitions; + void* symtab = nullptr; + + /* constructors */ + IndependentBlock(IndependentDefList definitions); + IndependentBlock(const IndependentBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIndependentBlock(this); } + virtual std::string getType() { return "IndependentBlock"; } + virtual IndependentBlock* clone() { return new IndependentBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class DependentBlock : public Block { + public: + /* member variables */ + DependentDefList definitions; + void* symtab = nullptr; + + /* constructors */ + DependentBlock(DependentDefList definitions); + DependentBlock(const DependentBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDependentBlock(this); } + virtual std::string getType() { return "DependentBlock"; } + virtual DependentBlock* clone() { return new DependentBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class StateBlock : public Block { + public: + /* member variables */ + DependentDefList definitions; + void* symtab = nullptr; + + /* constructors */ + StateBlock(DependentDefList definitions); + StateBlock(const StateBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStateBlock(this); } + virtual std::string getType() { return "StateBlock"; } + virtual StateBlock* clone() { return new StateBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class PlotBlock : public Block { + public: + /* member variables */ + std::shared_ptr plot; + void* symtab = nullptr; + + /* constructors */ + PlotBlock(PlotDeclaration* plot); + PlotBlock(const PlotBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPlotBlock(this); } + virtual std::string getType() { return "PlotBlock"; } + virtual PlotBlock* clone() { return new PlotBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class InitialBlock : public Block { + public: + /* member variables */ + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + InitialBlock(StatementBlock* statementblock); + InitialBlock(const InitialBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitInitialBlock(this); } + virtual std::string getType() { return "InitialBlock"; } + virtual InitialBlock* clone() { return new InitialBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class ConstructorBlock : public Block { + public: + /* member variables */ + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + ConstructorBlock(StatementBlock* statementblock); + ConstructorBlock(const ConstructorBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConstructorBlock(this); } + virtual std::string getType() { return "ConstructorBlock"; } + virtual ConstructorBlock* clone() { return new ConstructorBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class DestructorBlock : public Block { + public: + /* member variables */ + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + DestructorBlock(StatementBlock* statementblock); + DestructorBlock(const DestructorBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDestructorBlock(this); } + virtual std::string getType() { return "DestructorBlock"; } + virtual DestructorBlock* clone() { return new DestructorBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class StatementBlock : public Block { + public: + /* member variables */ + StatementList statements; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + StatementBlock(StatementList statements); + StatementBlock(const StatementBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStatementBlock(this); } + virtual std::string getType() { return "StatementBlock"; } + virtual StatementBlock* clone() { return new StatementBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class DerivativeBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + DerivativeBlock(Name* name, StatementBlock* statementblock); + DerivativeBlock(const DerivativeBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDerivativeBlock(this); } + virtual std::string getType() { return "DerivativeBlock"; } + virtual DerivativeBlock* clone() { return new DerivativeBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class LinearBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + NameList solvefor; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + LinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock); + LinearBlock(const LinearBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLinearBlock(this); } + virtual std::string getType() { return "LinearBlock"; } + virtual LinearBlock* clone() { return new LinearBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class NonLinearBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + NameList solvefor; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + NonLinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock); + NonLinearBlock(const NonLinearBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNonLinearBlock(this); } + virtual std::string getType() { return "NonLinearBlock"; } + virtual NonLinearBlock* clone() { return new NonLinearBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class DiscreteBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + DiscreteBlock(Name* name, StatementBlock* statementblock); + DiscreteBlock(const DiscreteBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDiscreteBlock(this); } + virtual std::string getType() { return "DiscreteBlock"; } + virtual DiscreteBlock* clone() { return new DiscreteBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class PartialBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + PartialBlock(Name* name, StatementBlock* statementblock); + PartialBlock(const PartialBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPartialBlock(this); } + virtual std::string getType() { return "PartialBlock"; } + virtual PartialBlock* clone() { return new PartialBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class FunctionTableBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + ArgumentList arguments; + std::shared_ptr unit; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + FunctionTableBlock(Name* name, ArgumentList arguments, Unit* unit); + FunctionTableBlock(const FunctionTableBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFunctionTableBlock(this); } + virtual std::string getType() { return "FunctionTableBlock"; } + virtual FunctionTableBlock* clone() { return new FunctionTableBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class FunctionBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + ArgumentList arguments; + std::shared_ptr unit; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + FunctionBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock); + FunctionBlock(const FunctionBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFunctionBlock(this); } + virtual std::string getType() { return "FunctionBlock"; } + virtual FunctionBlock* clone() { return new FunctionBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class ProcedureBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + ArgumentList arguments; + std::shared_ptr unit; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + ProcedureBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock); + ProcedureBlock(const ProcedureBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitProcedureBlock(this); } + virtual std::string getType() { return "ProcedureBlock"; } + virtual ProcedureBlock* clone() { return new ProcedureBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class NetReceiveBlock : public Block { + public: + /* member variables */ + ArgumentList arguments; + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + NetReceiveBlock(ArgumentList arguments, StatementBlock* statementblock); + NetReceiveBlock(const NetReceiveBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNetReceiveBlock(this); } + virtual std::string getType() { return "NetReceiveBlock"; } + virtual NetReceiveBlock* clone() { return new NetReceiveBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class SolveBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr method; + std::shared_ptr ifsolerr; + void* symtab = nullptr; + + /* constructors */ + SolveBlock(Name* name, Name* method, StatementBlock* ifsolerr); + SolveBlock(const SolveBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSolveBlock(this); } + virtual std::string getType() { return "SolveBlock"; } + virtual SolveBlock* clone() { return new SolveBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class BreakpointBlock : public Block { + public: + /* member variables */ + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + BreakpointBlock(StatementBlock* statementblock); + BreakpointBlock(const BreakpointBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBreakpointBlock(this); } + virtual std::string getType() { return "BreakpointBlock"; } + virtual BreakpointBlock* clone() { return new BreakpointBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class TerminalBlock : public Block { + public: + /* member variables */ + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + TerminalBlock(StatementBlock* statementblock); + TerminalBlock(const TerminalBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitTerminalBlock(this); } + virtual std::string getType() { return "TerminalBlock"; } + virtual TerminalBlock* clone() { return new TerminalBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class BeforeBlock : public Block { + public: + /* member variables */ + std::shared_ptr block; + void* symtab = nullptr; + + /* constructors */ + BeforeBlock(BABlock* block); + BeforeBlock(const BeforeBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBeforeBlock(this); } + virtual std::string getType() { return "BeforeBlock"; } + virtual BeforeBlock* clone() { return new BeforeBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class AfterBlock : public Block { + public: + /* member variables */ + std::shared_ptr block; + void* symtab = nullptr; + + /* constructors */ + AfterBlock(BABlock* block); + AfterBlock(const AfterBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitAfterBlock(this); } + virtual std::string getType() { return "AfterBlock"; } + virtual AfterBlock* clone() { return new AfterBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class BABlock : public Block { + public: + /* member variables */ + std::shared_ptr type; + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + BABlock(BABlockType* type, StatementBlock* statementblock); + BABlock(const BABlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBABlock(this); } + virtual std::string getType() { return "BABlock"; } + virtual BABlock* clone() { return new BABlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class ForNetcon : public Block { + public: + /* member variables */ + ArgumentList arguments; + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + ForNetcon(ArgumentList arguments, StatementBlock* statementblock); + ForNetcon(const ForNetcon& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitForNetcon(this); } + virtual std::string getType() { return "ForNetcon"; } + virtual ForNetcon* clone() { return new ForNetcon(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class KineticBlock : public Block { + public: + /* member variables */ + std::shared_ptr name; + NameList solvefor; + std::shared_ptr statementblock; + std::shared_ptr token; + void* symtab = nullptr; + + /* constructors */ + KineticBlock(Name* name, NameList solvefor, StatementBlock* statementblock); + KineticBlock(const KineticBlock& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitKineticBlock(this); } + virtual std::string getType() { return "KineticBlock"; } + virtual KineticBlock* clone() { return new KineticBlock(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class MatchBlock : public Block { + public: + /* member variables */ + MatchList matchs; + void* symtab = nullptr; + + /* constructors */ + MatchBlock(MatchList matchs); + MatchBlock(const MatchBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMatchBlock(this); } + virtual std::string getType() { return "MatchBlock"; } + virtual MatchBlock* clone() { return new MatchBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class UnitBlock : public Block { + public: + /* member variables */ + ExpressionList definitions; + void* symtab = nullptr; + + /* constructors */ + UnitBlock(ExpressionList definitions); + UnitBlock(const UnitBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitBlock(this); } + virtual std::string getType() { return "UnitBlock"; } + virtual UnitBlock* clone() { return new UnitBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class ConstantBlock : public Block { + public: + /* member variables */ + ConstantStatementList statements; + void* symtab = nullptr; + + /* constructors */ + ConstantBlock(ConstantStatementList statements); + ConstantBlock(const ConstantBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConstantBlock(this); } + virtual std::string getType() { return "ConstantBlock"; } + virtual ConstantBlock* clone() { return new ConstantBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class NeuronBlock : public Block { + public: + /* member variables */ + std::shared_ptr statementblock; + void* symtab = nullptr; + + /* constructors */ + NeuronBlock(StatementBlock* statementblock); + NeuronBlock(const NeuronBlock& obj); + + std::string getName() { return getType(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNeuronBlock(this); } + virtual std::string getType() { return "NeuronBlock"; } + virtual NeuronBlock* clone() { return new NeuronBlock(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + class Unit : public Expression { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + Unit(String* name); + Unit(const Unit& obj); + + virtual std::string getName() { return name->eval(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnit(this); } + virtual std::string getType() { return "Unit"; } + virtual Unit* clone() { return new Unit(*this); } + }; + + class DoubleUnit : public Expression { + public: + /* member variables */ + std::shared_ptr values; + std::shared_ptr unit; + /* constructors */ + DoubleUnit(Double* values, Unit* unit); + DoubleUnit(const DoubleUnit& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDoubleUnit(this); } + virtual std::string getType() { return "DoubleUnit"; } + virtual DoubleUnit* clone() { return new DoubleUnit(*this); } + }; + + class LocalVariable : public Expression { + public: + /* member variables */ + std::shared_ptr name; + /* constructors */ + LocalVariable(Identifier* name); + LocalVariable(const LocalVariable& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLocalVariable(this); } + virtual std::string getType() { return "LocalVariable"; } + virtual LocalVariable* clone() { return new LocalVariable(*this); } + }; + + class Limits : public Expression { + public: + /* member variables */ + std::shared_ptr min; + std::shared_ptr max; + /* constructors */ + Limits(Double* min, Double* max); + Limits(const Limits& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLimits(this); } + virtual std::string getType() { return "Limits"; } + virtual Limits* clone() { return new Limits(*this); } + }; + + class NumberRange : public Expression { + public: + /* member variables */ + std::shared_ptr min; + std::shared_ptr max; + /* constructors */ + NumberRange(Number* min, Number* max); + NumberRange(const NumberRange& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNumberRange(this); } + virtual std::string getType() { return "NumberRange"; } + virtual NumberRange* clone() { return new NumberRange(*this); } + }; + + class PlotVariable : public Expression { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr index; + /* constructors */ + PlotVariable(Identifier* name, Integer* index); + PlotVariable(const PlotVariable& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPlotVariable(this); } + virtual std::string getType() { return "PlotVariable"; } + virtual PlotVariable* clone() { return new PlotVariable(*this); } + }; + + class BinaryOperator : public Expression { + public: + /* member variables */ + BinaryOp value; + /* constructors */ + BinaryOperator(BinaryOp value); + BinaryOperator(const BinaryOperator& obj); + + BinaryOperator() {} + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBinaryOperator(this); } + virtual std::string getType() { return "BinaryOperator"; } + virtual BinaryOperator* clone() { return new BinaryOperator(*this); } + std::string eval() { return BinaryOpNames[value]; } + }; + + class UnaryOperator : public Expression { + public: + /* member variables */ + UnaryOp value; + /* constructors */ + UnaryOperator(UnaryOp value); + UnaryOperator(const UnaryOperator& obj); + + UnaryOperator() {} + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnaryOperator(this); } + virtual std::string getType() { return "UnaryOperator"; } + virtual UnaryOperator* clone() { return new UnaryOperator(*this); } + std::string eval() { return UnaryOpNames[value]; } + }; + + class ReactionOperator : public Expression { + public: + /* member variables */ + ReactionOp value; + /* constructors */ + ReactionOperator(ReactionOp value); + ReactionOperator(const ReactionOperator& obj); + + ReactionOperator() {} + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReactionOperator(this); } + virtual std::string getType() { return "ReactionOperator"; } + virtual ReactionOperator* clone() { return new ReactionOperator(*this); } + std::string eval() { return ReactionOpNames[value]; } + }; + + class BinaryExpression : public Expression { + public: + /* member variables */ + std::shared_ptr lhs; + BinaryOperator op; + std::shared_ptr rhs; + /* constructors */ + BinaryExpression(Expression* lhs, BinaryOperator op, Expression* rhs); + BinaryExpression(const BinaryExpression& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBinaryExpression(this); } + virtual std::string getType() { return "BinaryExpression"; } + virtual BinaryExpression* clone() { return new BinaryExpression(*this); } + }; + + class UnaryExpression : public Expression { + public: + /* member variables */ + UnaryOperator op; + std::shared_ptr expression; + /* constructors */ + UnaryExpression(UnaryOperator op, Expression* expression); + UnaryExpression(const UnaryExpression& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnaryExpression(this); } + virtual std::string getType() { return "UnaryExpression"; } + virtual UnaryExpression* clone() { return new UnaryExpression(*this); } + }; + + class NonLinEuation : public Expression { + public: + /* member variables */ + std::shared_ptr lhs; + std::shared_ptr rhs; + /* constructors */ + NonLinEuation(Expression* lhs, Expression* rhs); + NonLinEuation(const NonLinEuation& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNonLinEuation(this); } + virtual std::string getType() { return "NonLinEuation"; } + virtual NonLinEuation* clone() { return new NonLinEuation(*this); } + }; + + class LinEquation : public Expression { + public: + /* member variables */ + std::shared_ptr leftlinexpr; + std::shared_ptr linexpr; + /* constructors */ + LinEquation(Expression* leftlinexpr, Expression* linexpr); + LinEquation(const LinEquation& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLinEquation(this); } + virtual std::string getType() { return "LinEquation"; } + virtual LinEquation* clone() { return new LinEquation(*this); } + }; + + class FunctionCall : public Expression { + public: + /* member variables */ + std::shared_ptr name; + ExpressionList arguments; + /* constructors */ + FunctionCall(Name* name, ExpressionList arguments); + FunctionCall(const FunctionCall& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFunctionCall(this); } + virtual std::string getType() { return "FunctionCall"; } + virtual FunctionCall* clone() { return new FunctionCall(*this); } + }; + + class FirstLastTypeIndex : public Expression { + public: + /* member variables */ + FirstLastType value; + /* constructors */ + FirstLastTypeIndex(FirstLastType value); + FirstLastTypeIndex(const FirstLastTypeIndex& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFirstLastTypeIndex(this); } + virtual std::string getType() { return "FirstLastTypeIndex"; } + virtual FirstLastTypeIndex* clone() { return new FirstLastTypeIndex(*this); } + std::string eval() { return FirstLastTypeNames[value]; } + }; + + class Watch : public Expression { + public: + /* member variables */ + std::shared_ptr expression; + std::shared_ptr value; + /* constructors */ + Watch(Expression* expression, Expression* value); + Watch(const Watch& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWatch(this); } + virtual std::string getType() { return "Watch"; } + virtual Watch* clone() { return new Watch(*this); } + }; + + class QueueExpressionType : public Expression { + public: + /* member variables */ + QueueType value; + /* constructors */ + QueueExpressionType(QueueType value); + QueueExpressionType(const QueueExpressionType& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitQueueExpressionType(this); } + virtual std::string getType() { return "QueueExpressionType"; } + virtual QueueExpressionType* clone() { return new QueueExpressionType(*this); } + std::string eval() { return QueueTypeNames[value]; } + }; + + class Match : public Expression { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr expression; + /* constructors */ + Match(Identifier* name, Expression* expression); + Match(const Match& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMatch(this); } + virtual std::string getType() { return "Match"; } + virtual Match* clone() { return new Match(*this); } + }; + + class BABlockType : public Expression { + public: + /* member variables */ + BAType value; + /* constructors */ + BABlockType(BAType value); + BABlockType(const BABlockType& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitBABlockType(this); } + virtual std::string getType() { return "BABlockType"; } + virtual BABlockType* clone() { return new BABlockType(*this); } + std::string eval() { return BATypeNames[value]; } + }; + + class UnitDef : public Expression { + public: + /* member variables */ + std::shared_ptr unit1; + std::shared_ptr unit2; + /* constructors */ + UnitDef(Unit* unit1, Unit* unit2); + UnitDef(const UnitDef& obj); + + virtual std::string getName() { return unit1->getName(); } + virtual ModToken* getToken() { return unit1->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitDef(this); } + virtual std::string getType() { return "UnitDef"; } + virtual UnitDef* clone() { return new UnitDef(*this); } + }; + + class FactorDef : public Expression { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr value; + std::shared_ptr unit1; + std::shared_ptr gt; + std::shared_ptr unit2; + std::shared_ptr token; + /* constructors */ + FactorDef(Name* name, Double* value, Unit* unit1, Boolean* gt, Unit* unit2); + FactorDef(const FactorDef& obj); + + virtual std::string getName() { return name->getName(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFactorDef(this); } + virtual std::string getType() { return "FactorDef"; } + virtual FactorDef* clone() { return new FactorDef(*this); } + virtual ModToken* getToken() { return token.get(); } + virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + }; + + class Valence : public Expression { + public: + /* member variables */ + std::shared_ptr type; + std::shared_ptr value; + /* constructors */ + Valence(Name* type, Double* value); + Valence(const Valence& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitValence(this); } + virtual std::string getType() { return "Valence"; } + virtual Valence* clone() { return new Valence(*this); } + }; + + class UnitState : public Statement { + public: + /* member variables */ + UnitStateType value; + /* constructors */ + UnitState(UnitStateType value); + UnitState(const UnitState& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitUnitState(this); } + virtual std::string getType() { return "UnitState"; } + virtual UnitState* clone() { return new UnitState(*this); } + std::string eval() { return UnitStateTypeNames[value]; } + }; + + class LocalListStatement : public Statement { + public: + /* member variables */ + LocalVariableList variables; + /* constructors */ + LocalListStatement(LocalVariableList variables); + LocalListStatement(const LocalListStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLocalListStatement(this); } + virtual std::string getType() { return "LocalListStatement"; } + virtual LocalListStatement* clone() { return new LocalListStatement(*this); } + }; + + class Model : public Statement { + public: + /* member variables */ + std::shared_ptr title; + /* constructors */ + Model(String* title); + Model(const Model& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitModel(this); } + virtual std::string getType() { return "Model"; } + virtual Model* clone() { return new Model(*this); } + }; + + class Define : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr value; + /* constructors */ + Define(Name* name, Integer* value); + Define(const Define& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDefine(this); } + virtual std::string getType() { return "Define"; } + virtual Define* clone() { return new Define(*this); } + }; + + class Include : public Statement { + public: + /* member variables */ + std::shared_ptr filename; + /* constructors */ + Include(String* filename); + Include(const Include& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitInclude(this); } + virtual std::string getType() { return "Include"; } + virtual Include* clone() { return new Include(*this); } + }; + + class ParamAssign : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr value; + std::shared_ptr unit; + std::shared_ptr limit; + /* constructors */ + ParamAssign(Identifier* name, Number* value, Unit* unit, Limits* limit); + ParamAssign(const ParamAssign& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitParamAssign(this); } + virtual std::string getType() { return "ParamAssign"; } + virtual ParamAssign* clone() { return new ParamAssign(*this); } + }; + + class Stepped : public Statement { + public: + /* member variables */ + std::shared_ptr name; + NumberList values; + std::shared_ptr unit; + /* constructors */ + Stepped(Name* name, NumberList values, Unit* unit); + Stepped(const Stepped& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitStepped(this); } + virtual std::string getType() { return "Stepped"; } + virtual Stepped* clone() { return new Stepped(*this); } + }; + + class IndependentDef : public Statement { + public: + /* member variables */ + std::shared_ptr sweep; + std::shared_ptr name; + std::shared_ptr from; + std::shared_ptr to; + std::shared_ptr with; + std::shared_ptr opstart; + std::shared_ptr unit; + /* constructors */ + IndependentDef(Boolean* sweep, Name* name, Number* from, Number* to, Integer* with, Number* opstart, Unit* unit); + IndependentDef(const IndependentDef& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIndependentDef(this); } + virtual std::string getType() { return "IndependentDef"; } + virtual IndependentDef* clone() { return new IndependentDef(*this); } + }; + + class DependentDef : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr index; + std::shared_ptr from; + std::shared_ptr to; + std::shared_ptr opstart; + std::shared_ptr unit; + std::shared_ptr abstol; + /* constructors */ + DependentDef(Identifier* name, Integer* index, Number* from, Number* to, Number* opstart, Unit* unit, Double* abstol); + DependentDef(const DependentDef& obj); + + virtual std::string getName() { return name->getName(); } + virtual ModToken* getToken() { return name->getToken(); } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitDependentDef(this); } + virtual std::string getType() { return "DependentDef"; } + virtual DependentDef* clone() { return new DependentDef(*this); } + }; + + class PlotDeclaration : public Statement { + public: + /* member variables */ + PlotVariableList pvlist; + std::shared_ptr name; + /* constructors */ + PlotDeclaration(PlotVariableList pvlist, PlotVariable* name); + PlotDeclaration(const PlotDeclaration& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPlotDeclaration(this); } + virtual std::string getType() { return "PlotDeclaration"; } + virtual PlotDeclaration* clone() { return new PlotDeclaration(*this); } + }; + + class ConductanceHint : public Statement { + public: + /* member variables */ + std::shared_ptr conductance; + std::shared_ptr ion; + /* constructors */ + ConductanceHint(Name* conductance, Name* ion); + ConductanceHint(const ConductanceHint& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConductanceHint(this); } + virtual std::string getType() { return "ConductanceHint"; } + virtual ConductanceHint* clone() { return new ConductanceHint(*this); } + }; + + class ExpressionStatement : public Statement { + public: + /* member variables */ + std::shared_ptr expression; + /* constructors */ + ExpressionStatement(Expression* expression); + ExpressionStatement(const ExpressionStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitExpressionStatement(this); } + virtual std::string getType() { return "ExpressionStatement"; } + virtual ExpressionStatement* clone() { return new ExpressionStatement(*this); } + }; + + class ProtectStatement : public Statement { + public: + /* member variables */ + std::shared_ptr expression; + /* constructors */ + ProtectStatement(Expression* expression); + ProtectStatement(const ProtectStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitProtectStatement(this); } + virtual std::string getType() { return "ProtectStatement"; } + virtual ProtectStatement* clone() { return new ProtectStatement(*this); } + }; + + class FromStatement : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr from; + std::shared_ptr to; + std::shared_ptr opinc; + std::shared_ptr statementblock; + /* constructors */ + FromStatement(Name* name, Expression* from, Expression* to, Expression* opinc, StatementBlock* statementblock); + FromStatement(const FromStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitFromStatement(this); } + virtual std::string getType() { return "FromStatement"; } + virtual FromStatement* clone() { return new FromStatement(*this); } + }; + + class ForAllStatement : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr statementblock; + /* constructors */ + ForAllStatement(Name* name, StatementBlock* statementblock); + ForAllStatement(const ForAllStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitForAllStatement(this); } + virtual std::string getType() { return "ForAllStatement"; } + virtual ForAllStatement* clone() { return new ForAllStatement(*this); } + }; + + class WhileStatement : public Statement { + public: + /* member variables */ + std::shared_ptr condition; + std::shared_ptr statementblock; + /* constructors */ + WhileStatement(Expression* condition, StatementBlock* statementblock); + WhileStatement(const WhileStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWhileStatement(this); } + virtual std::string getType() { return "WhileStatement"; } + virtual WhileStatement* clone() { return new WhileStatement(*this); } + }; + + class IfStatement : public Statement { + public: + /* member variables */ + std::shared_ptr condition; + std::shared_ptr statementblock; + ElseIfStatementList elseifs; + std::shared_ptr elses; + /* constructors */ + IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementList elseifs, ElseStatement* elses); + IfStatement(const IfStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitIfStatement(this); } + virtual std::string getType() { return "IfStatement"; } + virtual IfStatement* clone() { return new IfStatement(*this); } + }; + + class ElseIfStatement : public Statement { + public: + /* member variables */ + std::shared_ptr condition; + std::shared_ptr statementblock; + /* constructors */ + ElseIfStatement(Expression* condition, StatementBlock* statementblock); + ElseIfStatement(const ElseIfStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitElseIfStatement(this); } + virtual std::string getType() { return "ElseIfStatement"; } + virtual ElseIfStatement* clone() { return new ElseIfStatement(*this); } + }; + + class ElseStatement : public Statement { + public: + /* member variables */ + std::shared_ptr statementblock; + /* constructors */ + ElseStatement(StatementBlock* statementblock); + ElseStatement(const ElseStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitElseStatement(this); } + virtual std::string getType() { return "ElseStatement"; } + virtual ElseStatement* clone() { return new ElseStatement(*this); } + }; + + class PartialEquation : public Statement { + public: + /* member variables */ + std::shared_ptr prime; + std::shared_ptr name1; + std::shared_ptr name2; + std::shared_ptr name3; + /* constructors */ + PartialEquation(PrimeName* prime, Name* name1, Name* name2, Name* name3); + PartialEquation(const PartialEquation& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPartialEquation(this); } + virtual std::string getType() { return "PartialEquation"; } + virtual PartialEquation* clone() { return new PartialEquation(*this); } + }; + + class PartialBoundary : public Statement { + public: + /* member variables */ + std::shared_ptr del; + std::shared_ptr name; + std::shared_ptr index; + std::shared_ptr expression; + std::shared_ptr name1; + std::shared_ptr del2; + std::shared_ptr name2; + std::shared_ptr name3; + /* constructors */ + PartialBoundary(Name* del, Identifier* name, FirstLastTypeIndex* index, Expression* expression, Name* name1, Name* del2, Name* name2, Name* name3); + PartialBoundary(const PartialBoundary& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitPartialBoundary(this); } + virtual std::string getType() { return "PartialBoundary"; } + virtual PartialBoundary* clone() { return new PartialBoundary(*this); } + }; + + class WatchStatement : public Statement { + public: + /* member variables */ + WatchList statements; + /* constructors */ + WatchStatement(WatchList statements); + WatchStatement(const WatchStatement& obj); + + void addWatch(Watch *s) { + statements.push_back(std::shared_ptr(s)); + } + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitWatchStatement(this); } + virtual std::string getType() { return "WatchStatement"; } + virtual WatchStatement* clone() { return new WatchStatement(*this); } + }; + + class MutexLock : public Statement { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMutexLock(this); } + virtual std::string getType() { return "MutexLock"; } + virtual MutexLock* clone() { return new MutexLock(*this); } + }; + + class MutexUnlock : public Statement { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitMutexUnlock(this); } + virtual std::string getType() { return "MutexUnlock"; } + virtual MutexUnlock* clone() { return new MutexUnlock(*this); } + }; + + class Reset : public Statement { + public: + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReset(this); } + virtual std::string getType() { return "Reset"; } + virtual Reset* clone() { return new Reset(*this); } + }; + + class Sens : public Statement { + public: + /* member variables */ + VarNameList senslist; + /* constructors */ + Sens(VarNameList senslist); + Sens(const Sens& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitSens(this); } + virtual std::string getType() { return "Sens"; } + virtual Sens* clone() { return new Sens(*this); } + }; + + class Conserve : public Statement { + public: + /* member variables */ + std::shared_ptr react; + std::shared_ptr expr; + /* constructors */ + Conserve(Expression* react, Expression* expr); + Conserve(const Conserve& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConserve(this); } + virtual std::string getType() { return "Conserve"; } + virtual Conserve* clone() { return new Conserve(*this); } + }; + + class Compartment : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr expression; + NameList names; + /* constructors */ + Compartment(Name* name, Expression* expression, NameList names); + Compartment(const Compartment& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitCompartment(this); } + virtual std::string getType() { return "Compartment"; } + virtual Compartment* clone() { return new Compartment(*this); } + }; + + class LDifuse : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr expression; + NameList names; + /* constructors */ + LDifuse(Name* name, Expression* expression, NameList names); + LDifuse(const LDifuse& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLDifuse(this); } + virtual std::string getType() { return "LDifuse"; } + virtual LDifuse* clone() { return new LDifuse(*this); } + }; + + class ReactionStatement : public Statement { + public: + /* member variables */ + std::shared_ptr react1; + ReactionOperator op; + std::shared_ptr react2; + std::shared_ptr expr1; + std::shared_ptr expr2; + /* constructors */ + ReactionStatement(Expression* react1, ReactionOperator op, Expression* react2, Expression* expr1, Expression* expr2); + ReactionStatement(const ReactionStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitReactionStatement(this); } + virtual std::string getType() { return "ReactionStatement"; } + virtual ReactionStatement* clone() { return new ReactionStatement(*this); } + }; + + class LagStatement : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr byname; + /* constructors */ + LagStatement(Identifier* name, Name* byname); + LagStatement(const LagStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitLagStatement(this); } + virtual std::string getType() { return "LagStatement"; } + virtual LagStatement* clone() { return new LagStatement(*this); } + }; + + class QueueStatement : public Statement { + public: + /* member variables */ + std::shared_ptr qype; + std::shared_ptr name; + /* constructors */ + QueueStatement(QueueExpressionType* qype, Identifier* name); + QueueStatement(const QueueStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitQueueStatement(this); } + virtual std::string getType() { return "QueueStatement"; } + virtual QueueStatement* clone() { return new QueueStatement(*this); } + }; + + class ConstantStatement : public Statement { + public: + /* member variables */ + std::shared_ptr name; + std::shared_ptr value; + std::shared_ptr unit; + /* constructors */ + ConstantStatement(Name* name, Number* value, Unit* unit); + ConstantStatement(const ConstantStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitConstantStatement(this); } + virtual std::string getType() { return "ConstantStatement"; } + virtual ConstantStatement* clone() { return new ConstantStatement(*this); } + }; + + class TableStatement : public Statement { + public: + /* member variables */ + NameList tablst; + NameList dependlst; + std::shared_ptr from; + std::shared_ptr to; + std::shared_ptr with; + /* constructors */ + TableStatement(NameList tablst, NameList dependlst, Expression* from, Expression* to, Integer* with); + TableStatement(const TableStatement& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitTableStatement(this); } + virtual std::string getType() { return "TableStatement"; } + virtual TableStatement* clone() { return new TableStatement(*this); } + }; + + class NrnSuffix : public Statement { + public: + /* member variables */ + std::shared_ptr type; + std::shared_ptr name; + /* constructors */ + NrnSuffix(Name* type, Name* name); + NrnSuffix(const NrnSuffix& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnSuffix(this); } + virtual std::string getType() { return "NrnSuffix"; } + virtual NrnSuffix* clone() { return new NrnSuffix(*this); } + }; + + class NrnUseion : public Statement { + public: + /* member variables */ + std::shared_ptr name; + ReadIonVarList readlist; + WriteIonVarList writelist; + std::shared_ptr valence; + /* constructors */ + NrnUseion(Name* name, ReadIonVarList readlist, WriteIonVarList writelist, Valence* valence); + NrnUseion(const NrnUseion& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnUseion(this); } + virtual std::string getType() { return "NrnUseion"; } + virtual NrnUseion* clone() { return new NrnUseion(*this); } + }; + + class NrnNonspecific : public Statement { + public: + /* member variables */ + NonspeCurVarList currents; + /* constructors */ + NrnNonspecific(NonspeCurVarList currents); + NrnNonspecific(const NrnNonspecific& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnNonspecific(this); } + virtual std::string getType() { return "NrnNonspecific"; } + virtual NrnNonspecific* clone() { return new NrnNonspecific(*this); } + }; + + class NrnElctrodeCurrent : public Statement { + public: + /* member variables */ + ElectrodeCurVarList ecurrents; + /* constructors */ + NrnElctrodeCurrent(ElectrodeCurVarList ecurrents); + NrnElctrodeCurrent(const NrnElctrodeCurrent& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnElctrodeCurrent(this); } + virtual std::string getType() { return "NrnElctrodeCurrent"; } + virtual NrnElctrodeCurrent* clone() { return new NrnElctrodeCurrent(*this); } + }; + + class NrnSection : public Statement { + public: + /* member variables */ + SectionVarList sections; + /* constructors */ + NrnSection(SectionVarList sections); + NrnSection(const NrnSection& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnSection(this); } + virtual std::string getType() { return "NrnSection"; } + virtual NrnSection* clone() { return new NrnSection(*this); } + }; + + class NrnRange : public Statement { + public: + /* member variables */ + RangeVarList range_vars; + /* constructors */ + NrnRange(RangeVarList range_vars); + NrnRange(const NrnRange& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnRange(this); } + virtual std::string getType() { return "NrnRange"; } + virtual NrnRange* clone() { return new NrnRange(*this); } + }; + + class NrnGlobal : public Statement { + public: + /* member variables */ + GlobalVarList global_vars; + /* constructors */ + NrnGlobal(GlobalVarList global_vars); + NrnGlobal(const NrnGlobal& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnGlobal(this); } + virtual std::string getType() { return "NrnGlobal"; } + virtual NrnGlobal* clone() { return new NrnGlobal(*this); } + }; + + class NrnPointer : public Statement { + public: + /* member variables */ + PointerVarList pointers; + /* constructors */ + NrnPointer(PointerVarList pointers); + NrnPointer(const NrnPointer& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnPointer(this); } + virtual std::string getType() { return "NrnPointer"; } + virtual NrnPointer* clone() { return new NrnPointer(*this); } + }; + + class NrnBbcorePtr : public Statement { + public: + /* member variables */ + BbcorePointerVarList bbcore_pointers; + /* constructors */ + NrnBbcorePtr(BbcorePointerVarList bbcore_pointers); + NrnBbcorePtr(const NrnBbcorePtr& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnBbcorePtr(this); } + virtual std::string getType() { return "NrnBbcorePtr"; } + virtual NrnBbcorePtr* clone() { return new NrnBbcorePtr(*this); } + }; + + class NrnExternal : public Statement { + public: + /* member variables */ + ExternVarList externals; + /* constructors */ + NrnExternal(ExternVarList externals); + NrnExternal(const NrnExternal& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnExternal(this); } + virtual std::string getType() { return "NrnExternal"; } + virtual NrnExternal* clone() { return new NrnExternal(*this); } + }; + + class NrnThreadSafe : public Statement { + public: + /* member variables */ + ThreadsafeVarList threadsafe; + /* constructors */ + NrnThreadSafe(ThreadsafeVarList threadsafe); + NrnThreadSafe(const NrnThreadSafe& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitNrnThreadSafe(this); } + virtual std::string getType() { return "NrnThreadSafe"; } + virtual NrnThreadSafe* clone() { return new NrnThreadSafe(*this); } + }; + + class Verbatim : public Statement { + public: + /* member variables */ + std::shared_ptr statement; + /* constructors */ + Verbatim(String* statement); + Verbatim(const Verbatim& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitVerbatim(this); } + virtual std::string getType() { return "Verbatim"; } + virtual Verbatim* clone() { return new Verbatim(*this); } + }; + + class Comment : public Statement { + public: + /* member variables */ + std::shared_ptr comment; + /* constructors */ + Comment(String* comment); + Comment(const Comment& obj); + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitComment(this); } + virtual std::string getType() { return "Comment"; } + virtual Comment* clone() { return new Comment(*this); } + }; + + class Program : public AST { + public: + /* member variables */ + StatementList statements; + BlockList blocks; + void* symtab = nullptr; + + /* constructors */ + Program(StatementList statements, BlockList blocks); + Program(const Program& obj); + + void addStatement(Statement *s) { + statements.push_back(std::shared_ptr(s)); + } + void addBlock(Block *s) { + blocks.push_back(std::shared_ptr(s)); + } + Program() {} + + virtual void visitChildren(Visitor* v); + virtual void accept(Visitor* v) { v->visitProgram(this); } + virtual std::string getType() { return "Program"; } + virtual Program* clone() { return new Program(*this); } + virtual void setBlockSymbolTable(void *s) { symtab = s; } + virtual void* getBlockSymbolTable() { return symtab; } + }; + + + +} // namespace ast diff --git a/src/nmodl/ast/astutils.hpp b/src/nmodl/ast/ast_utils.hpp similarity index 96% rename from src/nmodl/ast/astutils.hpp rename to src/nmodl/ast/ast_utils.hpp index 76c504eb2b..f5d7921c49 100644 --- a/src/nmodl/ast/astutils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -1,7 +1,10 @@ -#pragma once +#ifndef ASTUTILS_HPP +#define ASTUTILS_HPP #include +#include "lexer/modtoken.hpp" + namespace ast { /* enumaration of all binary operators in the language */ @@ -72,9 +75,11 @@ namespace ast { return ""; } virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ - return NULL; + return nullptr; } // virtual AST* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } }; } // namespace ast + +#endif From 83140ad82ad402ea31b0cd874dd72cbd93f9eb00 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Tue, 31 Oct 2017 08:31:16 +0100 Subject: [PATCH 007/871] Renaming utils file and cleaning up visitor header Change-Id: I068551458afd4ad20553dd5ee93273dc7b7808a1 NMODL Repo SHA: BlueBrain/nmodl@7639432a264d6ec725b225b173c49b6c2704eb44 --- .../{commonutils.hpp => common_utils.hpp} | 0 .../{stringutils.hpp => string_utils.hpp} | 0 src/nmodl/visitors/visitor.hpp | 268 +++++++++--------- 3 files changed, 133 insertions(+), 135 deletions(-) rename src/nmodl/utils/{commonutils.hpp => common_utils.hpp} (100%) rename src/nmodl/utils/{stringutils.hpp => string_utils.hpp} (100%) diff --git a/src/nmodl/utils/commonutils.hpp b/src/nmodl/utils/common_utils.hpp similarity index 100% rename from src/nmodl/utils/commonutils.hpp rename to src/nmodl/utils/common_utils.hpp diff --git a/src/nmodl/utils/stringutils.hpp b/src/nmodl/utils/string_utils.hpp similarity index 100% rename from src/nmodl/utils/stringutils.hpp rename to src/nmodl/utils/string_utils.hpp diff --git a/src/nmodl/visitors/visitor.hpp b/src/nmodl/visitors/visitor.hpp index e46cffe7d0..9b1e63601e 100644 --- a/src/nmodl/visitors/visitor.hpp +++ b/src/nmodl/visitors/visitor.hpp @@ -1,138 +1,136 @@ -#ifndef __VISITOR_HPP -#define __VISITOR_HPP +#pragma once -/* define abstract base class for all Visitors */ + +/* Abstract base class for all visitor implementations */ class Visitor { - public: - /* declare all virtual visitor functions (must be implemented in visitors) */ - virtual void visitExpressionNode(ExpressionNode* node) = 0; - virtual void visitStatementNode(StatementNode* node) = 0; - virtual void visitIdentifierNode(IdentifierNode* node) = 0; - virtual void visitBlockNode(BlockNode* node) = 0; - virtual void visitNumberNode(NumberNode* node) = 0; - virtual void visitStringNode(StringNode* node) = 0; - virtual void visitIntegerNode(IntegerNode* node) = 0; - virtual void visitFloatNode(FloatNode* node) = 0; - virtual void visitDoubleNode(DoubleNode* node) = 0; - virtual void visitBooleanNode(BooleanNode* node) = 0; - virtual void visitNameNode(NameNode* node) = 0; - virtual void visitPrimeNameNode(PrimeNameNode* node) = 0; - virtual void visitVarNameNode(VarNameNode* node) = 0; - virtual void visitIndexedNameNode(IndexedNameNode* node) = 0; - virtual void visitUnitNode(UnitNode* node) = 0; - virtual void visitUnitStateNode(UnitStateNode* node) = 0; - virtual void visitDoubleUnitNode(DoubleUnitNode* node) = 0; - virtual void visitArgumentNode(ArgumentNode* node) = 0; - virtual void visitLocalVariableNode(LocalVariableNode* node) = 0; - virtual void visitLocalListStatementNode(LocalListStatementNode* node) = 0; - virtual void visitLimitsNode(LimitsNode* node) = 0; - virtual void visitNumberRangeNode(NumberRangeNode* node) = 0; - virtual void visitProgramNode(ProgramNode* node) = 0; - virtual void visitModelNode(ModelNode* node) = 0; - virtual void visitDefineNode(DefineNode* node) = 0; - virtual void visitIncludeNode(IncludeNode* node) = 0; - virtual void visitParamBlockNode(ParamBlockNode* node) = 0; - virtual void visitParamAssignNode(ParamAssignNode* node) = 0; - virtual void visitStepBlockNode(StepBlockNode* node) = 0; - virtual void visitSteppedNode(SteppedNode* node) = 0; - virtual void visitIndependentBlockNode(IndependentBlockNode* node) = 0; - virtual void visitIndependentDefNode(IndependentDefNode* node) = 0; - virtual void visitDependentDefNode(DependentDefNode* node) = 0; - virtual void visitDependentBlockNode(DependentBlockNode* node) = 0; - virtual void visitStateBlockNode(StateBlockNode* node) = 0; - virtual void visitPlotBlockNode(PlotBlockNode* node) = 0; - virtual void visitPlotDeclarationNode(PlotDeclarationNode* node) = 0; - virtual void visitPlotVariableNode(PlotVariableNode* node) = 0; - virtual void visitInitialBlockNode(InitialBlockNode* node) = 0; - virtual void visitConstructorBlockNode(ConstructorBlockNode* node) = 0; - virtual void visitDestructorBlockNode(DestructorBlockNode* node) = 0; - virtual void visitConductanceHintNode(ConductanceHintNode* node) = 0; - virtual void visitExpressionStatementNode(ExpressionStatementNode* node) = 0; - virtual void visitProtectStatementNode(ProtectStatementNode* node) = 0; - virtual void visitStatementBlockNode(StatementBlockNode* node) = 0; - virtual void visitBinaryOperatorNode(BinaryOperatorNode* node) = 0; - virtual void visitUnaryOperatorNode(UnaryOperatorNode* node) = 0; - virtual void visitReactionOperatorNode(ReactionOperatorNode* node) = 0; - virtual void visitBinaryExpressionNode(BinaryExpressionNode* node) = 0; - virtual void visitUnaryExpressionNode(UnaryExpressionNode* node) = 0; - virtual void visitNonLinEuationNode(NonLinEuationNode* node) = 0; - virtual void visitLinEquationNode(LinEquationNode* node) = 0; - virtual void visitFunctionCallNode(FunctionCallNode* node) = 0; - virtual void visitFromStatementNode(FromStatementNode* node) = 0; - virtual void visitForAllStatementNode(ForAllStatementNode* node) = 0; - virtual void visitWhileStatementNode(WhileStatementNode* node) = 0; - virtual void visitIfStatementNode(IfStatementNode* node) = 0; - virtual void visitElseIfStatementNode(ElseIfStatementNode* node) = 0; - virtual void visitElseStatementNode(ElseStatementNode* node) = 0; - virtual void visitDerivativeBlockNode(DerivativeBlockNode* node) = 0; - virtual void visitLinearBlockNode(LinearBlockNode* node) = 0; - virtual void visitNonLinearBlockNode(NonLinearBlockNode* node) = 0; - virtual void visitDiscreteBlockNode(DiscreteBlockNode* node) = 0; - virtual void visitPartialBlockNode(PartialBlockNode* node) = 0; - virtual void visitPartialEquationNode(PartialEquationNode* node) = 0; - virtual void visitFirstLastTypeIndexNode(FirstLastTypeIndexNode* node) = 0; - virtual void visitPartialBoundaryNode(PartialBoundaryNode* node) = 0; - virtual void visitFunctionTableBlockNode(FunctionTableBlockNode* node) = 0; - virtual void visitFunctionBlockNode(FunctionBlockNode* node) = 0; - virtual void visitProcedureBlockNode(ProcedureBlockNode* node) = 0; - virtual void visitNetReceiveBlockNode(NetReceiveBlockNode* node) = 0; - virtual void visitSolveBlockNode(SolveBlockNode* node) = 0; - virtual void visitBreakpointBlockNode(BreakpointBlockNode* node) = 0; - virtual void visitTerminalBlockNode(TerminalBlockNode* node) = 0; - virtual void visitBeforeBlockNode(BeforeBlockNode* node) = 0; - virtual void visitAfterBlockNode(AfterBlockNode* node) = 0; - virtual void visitBABlockTypeNode(BABlockTypeNode* node) = 0; - virtual void visitBABlockNode(BABlockNode* node) = 0; - virtual void visitWatchStatementNode(WatchStatementNode* node) = 0; - virtual void visitWatchNode(WatchNode* node) = 0; - virtual void visitForNetconNode(ForNetconNode* node) = 0; - virtual void visitMutexLockNode(MutexLockNode* node) = 0; - virtual void visitMutexUnlockNode(MutexUnlockNode* node) = 0; - virtual void visitResetNode(ResetNode* node) = 0; - virtual void visitSensNode(SensNode* node) = 0; - virtual void visitConserveNode(ConserveNode* node) = 0; - virtual void visitCompartmentNode(CompartmentNode* node) = 0; - virtual void visitLDifuseNode(LDifuseNode* node) = 0; - virtual void visitKineticBlockNode(KineticBlockNode* node) = 0; - virtual void visitReactionStatementNode(ReactionStatementNode* node) = 0; - virtual void visitReactVarNameNode(ReactVarNameNode* node) = 0; - virtual void visitLagStatementNode(LagStatementNode* node) = 0; - virtual void visitQueueStatementNode(QueueStatementNode* node) = 0; - virtual void visitQueueExpressionTypeNode(QueueExpressionTypeNode* node) = 0; - virtual void visitMatchBlockNode(MatchBlockNode* node) = 0; - virtual void visitMatchNode(MatchNode* node) = 0; - virtual void visitUnitBlockNode(UnitBlockNode* node) = 0; - virtual void visitUnitDefNode(UnitDefNode* node) = 0; - virtual void visitFactordefNode(FactordefNode* node) = 0; - virtual void visitConstantStatementNode(ConstantStatementNode* node) = 0; - virtual void visitConstantBlockNode(ConstantBlockNode* node) = 0; - virtual void visitTableStatementNode(TableStatementNode* node) = 0; - virtual void visitNeuronBlockNode(NeuronBlockNode* node) = 0; - virtual void visitReadIonVarNode(ReadIonVarNode* node) = 0; - virtual void visitWriteIonVarNode(WriteIonVarNode* node) = 0; - virtual void visitNonspeCurVarNode(NonspeCurVarNode* node) = 0; - virtual void visitElectrodeCurVarNode(ElectrodeCurVarNode* node) = 0; - virtual void visitSectionVarNode(SectionVarNode* node) = 0; - virtual void visitRangeVarNode(RangeVarNode* node) = 0; - virtual void visitGlobalVarNode(GlobalVarNode* node) = 0; - virtual void visitPointerVarNode(PointerVarNode* node) = 0; - virtual void visitBbcorePointerVarNode(BbcorePointerVarNode* node) = 0; - virtual void visitExternVarNode(ExternVarNode* node) = 0; - virtual void visitThreadsafeVarNode(ThreadsafeVarNode* node) = 0; - virtual void visitNrnSuffixNode(NrnSuffixNode* node) = 0; - virtual void visitNrnUseionNode(NrnUseionNode* node) = 0; - virtual void visitNrnNonspecificNode(NrnNonspecificNode* node) = 0; - virtual void visitNrnElctrodeCurrentNode(NrnElctrodeCurrentNode* node) = 0; - virtual void visitNrnSectionNode(NrnSectionNode* node) = 0; - virtual void visitNrnRangeNode(NrnRangeNode* node) = 0; - virtual void visitNrnGlobalNode(NrnGlobalNode* node) = 0; - virtual void visitNrnPointerNode(NrnPointerNode* node) = 0; - virtual void visitNrnBbcorePtrNode(NrnBbcorePtrNode* node) = 0; - virtual void visitNrnExternalNode(NrnExternalNode* node) = 0; - virtual void visitNrnThreadSafeNode(NrnThreadSafeNode* node) = 0; - virtual void visitVerbatimNode(VerbatimNode* node) = 0; - virtual void visitCommentNode(CommentNode* node) = 0; - virtual void visitValenceNode(ValenceNode* node) = 0; -}; -#endif + public: + virtual void visitStatement(Statement* node) = 0; + virtual void visitExpression(Expression* node) = 0; + virtual void visitBlock(Block* node) = 0; + virtual void visitIdentifier(Identifier* node) = 0; + virtual void visitNumber(Number* node) = 0; + virtual void visitString(String* node) = 0; + virtual void visitInteger(Integer* node) = 0; + virtual void visitFloat(Float* node) = 0; + virtual void visitDouble(Double* node) = 0; + virtual void visitBoolean(Boolean* node) = 0; + virtual void visitName(Name* node) = 0; + virtual void visitPrimeName(PrimeName* node) = 0; + virtual void visitVarName(VarName* node) = 0; + virtual void visitIndexedName(IndexedName* node) = 0; + virtual void visitArgument(Argument* node) = 0; + virtual void visitReactVarName(ReactVarName* node) = 0; + virtual void visitReadIonVar(ReadIonVar* node) = 0; + virtual void visitWriteIonVar(WriteIonVar* node) = 0; + virtual void visitNonspeCurVar(NonspeCurVar* node) = 0; + virtual void visitElectrodeCurVar(ElectrodeCurVar* node) = 0; + virtual void visitSectionVar(SectionVar* node) = 0; + virtual void visitRangeVar(RangeVar* node) = 0; + virtual void visitGlobalVar(GlobalVar* node) = 0; + virtual void visitPointerVar(PointerVar* node) = 0; + virtual void visitBbcorePointerVar(BbcorePointerVar* node) = 0; + virtual void visitExternVar(ExternVar* node) = 0; + virtual void visitThreadsafeVar(ThreadsafeVar* node) = 0; + virtual void visitParamBlock(ParamBlock* node) = 0; + virtual void visitStepBlock(StepBlock* node) = 0; + virtual void visitIndependentBlock(IndependentBlock* node) = 0; + virtual void visitDependentBlock(DependentBlock* node) = 0; + virtual void visitStateBlock(StateBlock* node) = 0; + virtual void visitPlotBlock(PlotBlock* node) = 0; + virtual void visitInitialBlock(InitialBlock* node) = 0; + virtual void visitConstructorBlock(ConstructorBlock* node) = 0; + virtual void visitDestructorBlock(DestructorBlock* node) = 0; + virtual void visitStatementBlock(StatementBlock* node) = 0; + virtual void visitDerivativeBlock(DerivativeBlock* node) = 0; + virtual void visitLinearBlock(LinearBlock* node) = 0; + virtual void visitNonLinearBlock(NonLinearBlock* node) = 0; + virtual void visitDiscreteBlock(DiscreteBlock* node) = 0; + virtual void visitPartialBlock(PartialBlock* node) = 0; + virtual void visitFunctionTableBlock(FunctionTableBlock* node) = 0; + virtual void visitFunctionBlock(FunctionBlock* node) = 0; + virtual void visitProcedureBlock(ProcedureBlock* node) = 0; + virtual void visitNetReceiveBlock(NetReceiveBlock* node) = 0; + virtual void visitSolveBlock(SolveBlock* node) = 0; + virtual void visitBreakpointBlock(BreakpointBlock* node) = 0; + virtual void visitTerminalBlock(TerminalBlock* node) = 0; + virtual void visitBeforeBlock(BeforeBlock* node) = 0; + virtual void visitAfterBlock(AfterBlock* node) = 0; + virtual void visitBABlock(BABlock* node) = 0; + virtual void visitForNetcon(ForNetcon* node) = 0; + virtual void visitKineticBlock(KineticBlock* node) = 0; + virtual void visitMatchBlock(MatchBlock* node) = 0; + virtual void visitUnitBlock(UnitBlock* node) = 0; + virtual void visitConstantBlock(ConstantBlock* node) = 0; + virtual void visitNeuronBlock(NeuronBlock* node) = 0; + virtual void visitUnit(Unit* node) = 0; + virtual void visitDoubleUnit(DoubleUnit* node) = 0; + virtual void visitLocalVariable(LocalVariable* node) = 0; + virtual void visitLimits(Limits* node) = 0; + virtual void visitNumberRange(NumberRange* node) = 0; + virtual void visitPlotVariable(PlotVariable* node) = 0; + virtual void visitBinaryOperator(BinaryOperator* node) = 0; + virtual void visitUnaryOperator(UnaryOperator* node) = 0; + virtual void visitReactionOperator(ReactionOperator* node) = 0; + virtual void visitBinaryExpression(BinaryExpression* node) = 0; + virtual void visitUnaryExpression(UnaryExpression* node) = 0; + virtual void visitNonLinEuation(NonLinEuation* node) = 0; + virtual void visitLinEquation(LinEquation* node) = 0; + virtual void visitFunctionCall(FunctionCall* node) = 0; + virtual void visitFirstLastTypeIndex(FirstLastTypeIndex* node) = 0; + virtual void visitWatch(Watch* node) = 0; + virtual void visitQueueExpressionType(QueueExpressionType* node) = 0; + virtual void visitMatch(Match* node) = 0; + virtual void visitBABlockType(BABlockType* node) = 0; + virtual void visitUnitDef(UnitDef* node) = 0; + virtual void visitFactorDef(FactorDef* node) = 0; + virtual void visitValence(Valence* node) = 0; + virtual void visitUnitState(UnitState* node) = 0; + virtual void visitLocalListStatement(LocalListStatement* node) = 0; + virtual void visitModel(Model* node) = 0; + virtual void visitDefine(Define* node) = 0; + virtual void visitInclude(Include* node) = 0; + virtual void visitParamAssign(ParamAssign* node) = 0; + virtual void visitStepped(Stepped* node) = 0; + virtual void visitIndependentDef(IndependentDef* node) = 0; + virtual void visitDependentDef(DependentDef* node) = 0; + virtual void visitPlotDeclaration(PlotDeclaration* node) = 0; + virtual void visitConductanceHint(ConductanceHint* node) = 0; + virtual void visitExpressionStatement(ExpressionStatement* node) = 0; + virtual void visitProtectStatement(ProtectStatement* node) = 0; + virtual void visitFromStatement(FromStatement* node) = 0; + virtual void visitForAllStatement(ForAllStatement* node) = 0; + virtual void visitWhileStatement(WhileStatement* node) = 0; + virtual void visitIfStatement(IfStatement* node) = 0; + virtual void visitElseIfStatement(ElseIfStatement* node) = 0; + virtual void visitElseStatement(ElseStatement* node) = 0; + virtual void visitPartialEquation(PartialEquation* node) = 0; + virtual void visitPartialBoundary(PartialBoundary* node) = 0; + virtual void visitWatchStatement(WatchStatement* node) = 0; + virtual void visitMutexLock(MutexLock* node) = 0; + virtual void visitMutexUnlock(MutexUnlock* node) = 0; + virtual void visitReset(Reset* node) = 0; + virtual void visitSens(Sens* node) = 0; + virtual void visitConserve(Conserve* node) = 0; + virtual void visitCompartment(Compartment* node) = 0; + virtual void visitLDifuse(LDifuse* node) = 0; + virtual void visitReactionStatement(ReactionStatement* node) = 0; + virtual void visitLagStatement(LagStatement* node) = 0; + virtual void visitQueueStatement(QueueStatement* node) = 0; + virtual void visitConstantStatement(ConstantStatement* node) = 0; + virtual void visitTableStatement(TableStatement* node) = 0; + virtual void visitNrnSuffix(NrnSuffix* node) = 0; + virtual void visitNrnUseion(NrnUseion* node) = 0; + virtual void visitNrnNonspecific(NrnNonspecific* node) = 0; + virtual void visitNrnElctrodeCurrent(NrnElctrodeCurrent* node) = 0; + virtual void visitNrnSection(NrnSection* node) = 0; + virtual void visitNrnRange(NrnRange* node) = 0; + virtual void visitNrnGlobal(NrnGlobal* node) = 0; + virtual void visitNrnPointer(NrnPointer* node) = 0; + virtual void visitNrnBbcorePtr(NrnBbcorePtr* node) = 0; + virtual void visitNrnExternal(NrnExternal* node) = 0; + virtual void visitNrnThreadSafe(NrnThreadSafe* node) = 0; + virtual void visitVerbatim(Verbatim* node) = 0; + virtual void visitComment(Comment* node) = 0; + virtual void visitProgram(Program* node) = 0; +}; From e0b6c107c3428caa6b3ff6e062ed50477795b875 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Wed, 1 Nov 2017 15:41:47 +0100 Subject: [PATCH 008/871] Adding external libraries : - tclap command line arguments parser - catch testing framework Change-Id: I88865ac0a2e7de5a4be5339747d1c66417b66bc4 NMODL Repo SHA: BlueBrain/nmodl@fa6bfa42369bbee86640ffe01607bffc491f1754 --- src/nmodl/ext/catch/catch.hpp | 9416 +++++++++++++++++ src/nmodl/ext/tclap/Arg.h | 692 ++ src/nmodl/ext/tclap/ArgException.h | 200 + src/nmodl/ext/tclap/ArgTraits.h | 87 + src/nmodl/ext/tclap/CmdLine.h | 651 ++ src/nmodl/ext/tclap/CmdLineInterface.h | 150 + src/nmodl/ext/tclap/CmdLineOutput.h | 74 + src/nmodl/ext/tclap/Constraint.h | 68 + src/nmodl/ext/tclap/DocBookOutput.h | 299 + src/nmodl/ext/tclap/HelpVisitor.h | 76 + src/nmodl/ext/tclap/IgnoreRestVisitor.h | 52 + src/nmodl/ext/tclap/MultiArg.h | 433 + src/nmodl/ext/tclap/MultiSwitchArg.h | 216 + .../ext/tclap/OptionalUnlabeledTracker.h | 62 + src/nmodl/ext/tclap/StandardTraits.h | 208 + src/nmodl/ext/tclap/StdOutput.h | 299 + src/nmodl/ext/tclap/SwitchArg.h | 266 + src/nmodl/ext/tclap/UnlabeledMultiArg.h | 301 + src/nmodl/ext/tclap/UnlabeledValueArg.h | 340 + src/nmodl/ext/tclap/ValueArg.h | 425 + src/nmodl/ext/tclap/ValuesConstraint.h | 148 + src/nmodl/ext/tclap/VersionVisitor.h | 81 + src/nmodl/ext/tclap/Visitor.h | 53 + src/nmodl/ext/tclap/XorHandler.h | 166 + src/nmodl/ext/tclap/ZshCompletionOutput.h | 323 + 25 files changed, 15086 insertions(+) create mode 100644 src/nmodl/ext/catch/catch.hpp create mode 100755 src/nmodl/ext/tclap/Arg.h create mode 100755 src/nmodl/ext/tclap/ArgException.h create mode 100755 src/nmodl/ext/tclap/ArgTraits.h create mode 100755 src/nmodl/ext/tclap/CmdLine.h create mode 100755 src/nmodl/ext/tclap/CmdLineInterface.h create mode 100755 src/nmodl/ext/tclap/CmdLineOutput.h create mode 100755 src/nmodl/ext/tclap/Constraint.h create mode 100755 src/nmodl/ext/tclap/DocBookOutput.h create mode 100755 src/nmodl/ext/tclap/HelpVisitor.h create mode 100755 src/nmodl/ext/tclap/IgnoreRestVisitor.h create mode 100755 src/nmodl/ext/tclap/MultiArg.h create mode 100755 src/nmodl/ext/tclap/MultiSwitchArg.h create mode 100755 src/nmodl/ext/tclap/OptionalUnlabeledTracker.h create mode 100755 src/nmodl/ext/tclap/StandardTraits.h create mode 100755 src/nmodl/ext/tclap/StdOutput.h create mode 100755 src/nmodl/ext/tclap/SwitchArg.h create mode 100755 src/nmodl/ext/tclap/UnlabeledMultiArg.h create mode 100755 src/nmodl/ext/tclap/UnlabeledValueArg.h create mode 100755 src/nmodl/ext/tclap/ValueArg.h create mode 100755 src/nmodl/ext/tclap/ValuesConstraint.h create mode 100755 src/nmodl/ext/tclap/VersionVisitor.h create mode 100755 src/nmodl/ext/tclap/Visitor.h create mode 100755 src/nmodl/ext/tclap/XorHandler.h create mode 100755 src/nmodl/ext/tclap/ZshCompletionOutput.h diff --git a/src/nmodl/ext/catch/catch.hpp b/src/nmodl/ext/catch/catch.hpp new file mode 100644 index 0000000000..de61226cf6 --- /dev/null +++ b/src/nmodl/ext/catch/catch.hpp @@ -0,0 +1,9416 @@ +/* + * Catch v1.2.1 + * Generated: 2015-06-30 18:23:27.961086 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + +#define TWOBLUECUBES_CATCH_HPP_INCLUDED + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// #included from: internal/catch_suppress_warnings.h + +#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic ignored "-Wglobal-constructors" +# pragma clang diagnostic ignored "-Wvariadic-macros" +# pragma clang diagnostic ignored "-Wc99-extensions" +# pragma clang diagnostic ignored "-Wunused-variable" +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wc++98-compat" +# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +# pragma clang diagnostic ignored "-Wswitch-enum" +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic ignored "-Wvariadic-macros" +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpadded" +#endif + +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +#endif + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// #included from: internal/catch_notimplemented_exception.h +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED + +// #included from: catch_common.h +#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED + +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) + +#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr +#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) + +#include +#include +#include + +// #included from: catch_compiler_capabilities.h +#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED + +// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// The following features are defined: +// +// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? +// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? +// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods +// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? +// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported + +// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? + +// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 + +#ifdef __clang__ + +# if __has_feature(cxx_nullptr) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# if __has_feature(cxx_noexcept) +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Borland +#ifdef __BORLANDC__ + +#endif // __BORLANDC__ + +//////////////////////////////////////////////////////////////////////////////// +// EDG +#ifdef __EDG_VERSION__ + +#endif // __EDG_VERSION__ + +//////////////////////////////////////////////////////////////////////////////// +// Digital Mars +#ifdef __DMC__ + +#endif // __DMC__ + +//////////////////////////////////////////////////////////////////////////////// +// GCC +#ifdef __GNUC__ + +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#endif // __GNUC__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#ifdef _MSC_VER + +#if (_MSC_VER >= 1600) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +#endif + +#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) +#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +#endif + +#endif // _MSC_VER + +// Use variadic macros if the compiler supports them +#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ + ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ + ( defined __GNUC__ && __GNUC__ >= 3 ) || \ + ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) + +#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS + +#endif + +//////////////////////////////////////////////////////////////////////////////// +// C++ language feature support + +// catch all support for C++11 +#if (__cplusplus >= 201103L) + +# define CATCH_CPP11_OR_GREATER + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) +# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM +# endif + +# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE +# endif + +# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS +# endif + +#endif // __cplusplus >= 201103L + +// Now set the actual defines based on the above + anything the user has configured +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NULLPTR +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_NOEXCEPT +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_IS_ENUM +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_TUPLE +#endif +#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) +#define CATCH_CONFIG_VARIADIC_MACROS +#endif + +// noexcept support: +#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) +# define CATCH_NOEXCEPT noexcept +# define CATCH_NOEXCEPT_IS(x) noexcept(x) +#else +# define CATCH_NOEXCEPT throw() +# define CATCH_NOEXCEPT_IS(x) +#endif + +namespace Catch { + + class NonCopyable { +#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; +#else + NonCopyable( NonCopyable const& info ); + NonCopyable& operator = ( NonCopyable const& ); +#endif + + protected: + NonCopyable() {} + virtual ~NonCopyable(); + }; + + class SafeBool { + public: + typedef void (SafeBool::*type)() const; + + static type makeSafe( bool value ) { + return value ? &SafeBool::trueValue : 0; + } + private: + void trueValue() const {} + }; + + template + inline void deleteAll( ContainerT& container ) { + typename ContainerT::const_iterator it = container.begin(); + typename ContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete *it; + } + template + inline void deleteAllValues( AssociativeContainerT& container ) { + typename AssociativeContainerT::const_iterator it = container.begin(); + typename AssociativeContainerT::const_iterator itEnd = container.end(); + for(; it != itEnd; ++it ) + delete it->second; + } + + bool startsWith( std::string const& s, std::string const& prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); + + struct pluralise { + pluralise( std::size_t count, std::string const& label ); + + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); + + std::size_t m_count; + std::string m_label; + }; + + struct SourceLineInfo { + + SourceLineInfo(); + SourceLineInfo( char const* _file, std::size_t _line ); + SourceLineInfo( SourceLineInfo const& other ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo( SourceLineInfo && ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo& operator = ( SourceLineInfo && ) = default; +# endif + bool empty() const; + bool operator == ( SourceLineInfo const& other ) const; + bool operator < ( SourceLineInfo const& other ) const; + + std::string file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // This is just here to avoid compiler warnings with macro constants and boolean literals + inline bool isTrue( bool value ){ return value; } + inline bool alwaysTrue() { return true; } + inline bool alwaysFalse() { return false; } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() { + return std::string(); + } + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) +#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); + +#include + +namespace Catch { + + class NotImplementedException : public std::exception + { + public: + NotImplementedException( SourceLineInfo const& lineInfo ); + NotImplementedException( NotImplementedException const& ) {} + + virtual ~NotImplementedException() CATCH_NOEXCEPT {} + + virtual const char* what() const CATCH_NOEXCEPT; + + private: + std::string m_what; + SourceLineInfo m_lineInfo; + }; + +} // end namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) + +// #included from: internal/catch_context.h +#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED + +// #included from: catch_interfaces_generators.h +#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED + +#include + +namespace Catch { + + struct IGeneratorInfo { + virtual ~IGeneratorInfo(); + virtual bool moveNext() = 0; + virtual std::size_t getCurrentIndex() const = 0; + }; + + struct IGeneratorsForTest { + virtual ~IGeneratorsForTest(); + + virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; + virtual bool moveNext() = 0; + }; + + IGeneratorsForTest* createGeneratorsForTest(); + +} // end namespace Catch + +// #included from: catch_ptr.hpp +#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + // An intrusive reference counting smart pointer. + // T must implement addRef() and release() methods + // typically implementing the IShared interface + template + class Ptr { + public: + Ptr() : m_p( NULL ){} + Ptr( T* p ) : m_p( p ){ + if( m_p ) + m_p->addRef(); + } + Ptr( Ptr const& other ) : m_p( other.m_p ){ + if( m_p ) + m_p->addRef(); + } + ~Ptr(){ + if( m_p ) + m_p->release(); + } + void reset() { + if( m_p ) + m_p->release(); + m_p = NULL; + } + Ptr& operator = ( T* p ){ + Ptr temp( p ); + swap( temp ); + return *this; + } + Ptr& operator = ( Ptr const& other ){ + Ptr temp( other ); + swap( temp ); + return *this; + } + void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } + T* get() { return m_p; } + const T* get() const{ return m_p; } + T& operator*() const { return *m_p; } + T* operator->() const { return m_p; } + bool operator !() const { return m_p == NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + + private: + T* m_p; + }; + + struct IShared : NonCopyable { + virtual ~IShared(); + virtual void addRef() const = 0; + virtual void release() const = 0; + }; + + template + struct SharedImpl : T { + + SharedImpl() : m_rc( 0 ){} + + virtual void addRef() const { + ++m_rc; + } + virtual void release() const { + if( --m_rc == 0 ) + delete this; + } + + mutable unsigned int m_rc; + }; + +} // end namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +namespace Catch { + + class TestCase; + class Stream; + struct IResultCapture; + struct IRunner; + struct IGeneratorsForTest; + struct IConfig; + + struct IContext + { + virtual ~IContext(); + + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; + virtual bool advanceGeneratorsForCurrentTest() = 0; + virtual Ptr getConfig() const = 0; + }; + + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( Ptr const& config ) = 0; + }; + + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); + Stream createStream( std::string const& streamName ); + +} + +// #included from: internal/catch_test_registry.hpp +#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + +// #included from: catch_interfaces_testcase.h +#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + +#include + +namespace Catch { + + class TestSpec; + + struct ITestCase : IShared { + virtual void invoke () const = 0; + protected: + virtual ~ITestCase(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; + + }; +} + +namespace Catch { + +template +class MethodTestCase : public SharedImpl { + +public: + MethodTestCase( void (C::*method)() ) : m_method( method ) {} + + virtual void invoke() const { + C obj; + (obj.*m_method)(); + } + +private: + virtual ~MethodTestCase() {} + + void (C::*m_method)(); +}; + +typedef void(*TestFunction)(); + +struct NameAndDesc { + NameAndDesc( const char* _name = "", const char* _description= "" ) + : name( _name ), description( _description ) + {} + + const char* name; + const char* description; +}; + +struct AutoReg { + + AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + + template + AutoReg( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + registerTestCase( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } + + void registerTestCase( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + + ~AutoReg(); + +private: + AutoReg( AutoReg const& ); + void operator= ( AutoReg const& ); +}; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( ... ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#else + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ + static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ + namespace{ \ + struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + void test(); \ + }; \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ + } \ + void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + +#endif + +// #included from: internal/catch_capture.hpp +#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED + +// #included from: catch_result_builder.h +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + +// #included from: catch_result_type.h +#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED + +namespace Catch { + + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, + + FailureBit = 0x10, + + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, + + Exception = 0x100 | FailureBit, + + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, + + FatalErrorCondition = 0x200 | FailureBit + + }; }; + + inline bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + inline bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, + + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; + + inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast( static_cast( lhs ) | static_cast( rhs ) ); + } + + inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch + +// #included from: catch_assertionresult.h +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + +#include + +namespace Catch { + + struct AssertionInfo + { + AssertionInfo() {} + AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ); + + std::string macroName; + SourceLineInfo lineInfo; + std::string capturedExpression; + ResultDisposition::Flags resultDisposition; + }; + + struct AssertionResultData + { + AssertionResultData() : resultType( ResultWas::Unknown ) {} + + std::string reconstructedExpression; + std::string message; + ResultWas::OfType resultType; + }; + + class AssertionResult { + public: + AssertionResult(); + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); + ~AssertionResult(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionResult( AssertionResult const& ) = default; + AssertionResult( AssertionResult && ) = default; + AssertionResult& operator = ( AssertionResult const& ) = default; + AssertionResult& operator = ( AssertionResult && ) = default; +# endif + + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; + + protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; + +} // end namespace Catch + +namespace Catch { + + struct TestFailureException{}; + + template class ExpressionLhs; + + struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + + struct CopyableStream { + CopyableStream() {} + CopyableStream( CopyableStream const& other ) { + oss << other.oss.str(); + } + CopyableStream& operator=( CopyableStream const& other ) { + oss.str(""); + oss << other.oss.str(); + return *this; + } + std::ostringstream oss; + }; + + class ResultBuilder { + public: + ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ); + + template + ExpressionLhs operator <= ( T const& operand ); + ExpressionLhs operator <= ( bool value ); + + template + ResultBuilder& operator << ( T const& value ) { + m_stream.oss << value; + return *this; + } + + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + + ResultBuilder& setResultType( ResultWas::OfType result ); + ResultBuilder& setResultType( bool result ); + ResultBuilder& setLhs( std::string const& lhs ); + ResultBuilder& setRhs( std::string const& rhs ); + ResultBuilder& setOp( std::string const& op ); + + void endExpression(); + + std::string reconstructExpression() const; + AssertionResult build() const; + + void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); + void captureResult( ResultWas::OfType resultType ); + void captureExpression(); + void react(); + bool shouldDebugBreak() const; + bool allowThrows() const; + + private: + AssertionInfo m_assertionInfo; + AssertionResultData m_data; + struct ExprComponents { + ExprComponents() : testFalse( false ) {} + bool testFalse; + std::string lhs, rhs, op; + } m_exprComponents; + CopyableStream m_stream; + + bool m_shouldDebugBreak; + bool m_shouldThrow; + }; + +} // namespace Catch + +// Include after due to circular dependency: +// #included from: catch_expression_lhs.hpp +#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + +// #included from: catch_evaluate.hpp +#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#endif + +#include + +namespace Catch { +namespace Internal { + + enum Operator { + IsEqualTo, + IsNotEqualTo, + IsLessThan, + IsGreaterThan, + IsLessThanOrEqualTo, + IsGreaterThanOrEqualTo + }; + + template struct OperatorTraits { static const char* getName(){ return "*error*"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "=="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "!="; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<"; } }; + template<> struct OperatorTraits { static const char* getName(){ return ">"; } }; + template<> struct OperatorTraits { static const char* getName(){ return "<="; } }; + template<> struct OperatorTraits{ static const char* getName(){ return ">="; } }; + + template + inline T& opCast(T const& t) { return const_cast(t); } + +// nullptr_t support based on pull request #154 from Konstantin Baumann +#ifdef CATCH_CONFIG_CPP11_NULLPTR + inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } +#endif // CATCH_CONFIG_CPP11_NULLPTR + + // So the compare overloads can be operator agnostic we convey the operator as a template + // enum, which is used to specialise an Evaluator for doing the comparison. + template + class Evaluator{}; + + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs) { + return opCast( lhs ) == opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) != opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) < opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) > opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) >= opCast( rhs ); + } + }; + template + struct Evaluator { + static bool evaluate( T1 const& lhs, T2 const& rhs ) { + return opCast( lhs ) <= opCast( rhs ); + } + }; + + template + bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // This level of indirection allows us to specialise for integer types + // to avoid signed/ unsigned warnings + + // "base" overload + template + bool compare( T1 const& lhs, T2 const& rhs ) { + return Evaluator::evaluate( lhs, rhs ); + } + + // unsigned X to int + template bool compare( unsigned int lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, int rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // unsigned X to long + template bool compare( unsigned int lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned long lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + template bool compare( unsigned char lhs, long rhs ) { + return applyEvaluator( lhs, static_cast( rhs ) ); + } + + // int to unsigned X + template bool compare( int lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( int lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // long to unsigned X + template bool compare( long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long (when comparing against NULL) + template bool compare( long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + + // pointer to int (when comparing against NULL) + template bool compare( int lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, int rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } + +#ifdef CATCH_CONFIG_CPP11_NULLPTR + // pointer to nullptr_t (when comparing against nullptr) + template bool compare( std::nullptr_t, T* rhs ) { + return Evaluator::evaluate( NULL, rhs ); + } + template bool compare( T* lhs, std::nullptr_t ) { + return Evaluator::evaluate( lhs, NULL ); + } +#endif // CATCH_CONFIG_CPP11_NULLPTR + +} // end of namespace Internal +} // end of namespace Catch + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +// #included from: catch_tostring.h +#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED + +#include +#include +#include +#include +#include + +#ifdef __OBJC__ +// #included from: catch_objc_arc.hpp +#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED + +#import + +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif + +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); + +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif + +#endif + +#ifdef CATCH_CONFIG_CPP11_TUPLE +#include +#endif + +#ifdef CATCH_CONFIG_CPP11_IS_ENUM +#include +#endif + +namespace Catch { + +// Why we're here. +template +std::string toString( T const& value ); + +// Built in overloads + +std::string toString( std::string const& value ); +std::string toString( std::wstring const& value ); +std::string toString( const char* const value ); +std::string toString( char* const value ); +std::string toString( const wchar_t* const value ); +std::string toString( wchar_t* const value ); +std::string toString( int value ); +std::string toString( unsigned long value ); +std::string toString( unsigned int value ); +std::string toString( const double value ); +std::string toString( const float value ); +std::string toString( bool value ); +std::string toString( char value ); +std::string toString( signed char value ); +std::string toString( unsigned char value ); + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ); +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ); + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); + std::string toString( NSObject* const& nsObject ); +#endif + +namespace Detail { + + extern std::string unprintableString; + + struct BorgType { + template BorgType( T const& ); + }; + + struct TrueType { char sizer[1]; }; + struct FalseType { char sizer[2]; }; + + TrueType& testStreamable( std::ostream& ); + FalseType testStreamable( FalseType ); + + FalseType operator<<( std::ostream const&, BorgType const& ); + + template + struct IsStreamInsertable { + static std::ostream &s; + static T const&t; + enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; + }; + +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template::value + > + struct EnumStringMaker + { + static std::string convert( T const& ) { return unprintableString; } + }; + + template + struct EnumStringMaker + { + static std::string convert( T const& v ) + { + return ::Catch::toString( + static_cast::type>(v) + ); + } + }; +#endif + template + struct StringMakerBase { +#if defined(CATCH_CONFIG_CPP11_IS_ENUM) + template + static std::string convert( T const& v ) + { + return EnumStringMaker::convert( v ); + } +#else + template + static std::string convert( T const& ) { return unprintableString; } +#endif + }; + + template<> + struct StringMakerBase { + template + static std::string convert( T const& _value ) { + std::ostringstream oss; + oss << _value; + return oss.str(); + } + }; + + std::string rawMemoryToString( const void *object, std::size_t size ); + + template + inline std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } + +} // end namespace Detail + +template +struct StringMaker : + Detail::StringMakerBase::value> {}; + +template +struct StringMaker { + template + static std::string convert( U* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +template +struct StringMaker { + static std::string convert( R C::* p ) { + if( !p ) + return INTERNAL_CATCH_STRINGIFY( NULL ); + else + return Detail::rawMemoryToString( p ); + } +}; + +namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ); +} + +//template +//struct StringMaker > { +// static std::string convert( std::vector const& v ) { +// return Detail::rangeToString( v.begin(), v.end() ); +// } +//}; + +template +std::string toString( std::vector const& v ) { + return Detail::rangeToString( v.begin(), v.end() ); +} + +#ifdef CATCH_CONFIG_CPP11_TUPLE + +// toString for tuples +namespace TupleDetail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size::value) + > + struct ElementPrinter { + static void print( const Tuple& tuple, std::ostream& os ) + { + os << ( N ? ", " : " " ) + << Catch::toString(std::get(tuple)); + ElementPrinter::print(tuple,os); + } + }; + + template< + typename Tuple, + std::size_t N + > + struct ElementPrinter { + static void print( const Tuple&, std::ostream& ) {} + }; + +} + +template +struct StringMaker> { + + static std::string convert( const std::tuple& tuple ) + { + std::ostringstream os; + os << '{'; + TupleDetail::ElementPrinter>::print( tuple, os ); + os << " }"; + return os.str(); + } +}; +#endif // CATCH_CONFIG_CPP11_TUPLE + +namespace Detail { + template + std::string makeString( T const& value ) { + return StringMaker::convert( value ); + } +} // end namespace Detail + +/// \brief converts any type to a string +/// +/// The default template forwards on to ostringstream - except when an +/// ostringstream overload does not exist - in which case it attempts to detect +/// that and writes {?}. +/// Overload (not specialise) this template for custom typs that you don't want +/// to provide an ostream overload for. +template +std::string toString( T const& value ) { + return StringMaker::convert( value ); +} + + namespace Detail { + template + std::string rangeToString( InputIterator first, InputIterator last ) { + std::ostringstream oss; + oss << "{ "; + if( first != last ) { + oss << Catch::toString( *first ); + for( ++first ; first != last ; ++first ) + oss << ", " << Catch::toString( *first ); + } + oss << " }"; + return oss.str(); + } +} + +} // end namespace Catch + +namespace Catch { + +// Wraps the LHS of an expression and captures the operator and RHS (if any) - +// wrapping them all in a ResultBuilder object +template +class ExpressionLhs { + ExpressionLhs& operator = ( ExpressionLhs const& ); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs& operator = ( ExpressionLhs && ) = delete; +# endif + +public: + ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + ExpressionLhs( ExpressionLhs const& ) = default; + ExpressionLhs( ExpressionLhs && ) = default; +# endif + + template + ResultBuilder& operator == ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator != ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator < ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator > ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator <= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + template + ResultBuilder& operator >= ( RhsT const& rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator == ( bool rhs ) { + return captureExpression( rhs ); + } + + ResultBuilder& operator != ( bool rhs ) { + return captureExpression( rhs ); + } + + void endExpression() { + bool value = m_lhs ? true : false; + m_rb + .setLhs( Catch::toString( value ) ) + .setResultType( value ) + .endExpression(); + } + + // Only simple binary expressions are allowed on the LHS. + // If more complex compositions are required then place the sub expression in parentheses + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); + template STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + +private: + template + ResultBuilder& captureExpression( RhsT const& rhs ) { + return m_rb + .setResultType( Internal::compare( m_lhs, rhs ) ) + .setLhs( Catch::toString( m_lhs ) ) + .setRhs( Catch::toString( rhs ) ) + .setOp( Internal::OperatorTraits::getName() ); + } + +private: + ResultBuilder& m_rb; + T m_lhs; +}; + +} // end namespace Catch + + +namespace Catch { + + template + inline ExpressionLhs ResultBuilder::operator <= ( T const& operand ) { + return ExpressionLhs( *this, operand ); + } + + inline ExpressionLhs ResultBuilder::operator <= ( bool value ) { + return ExpressionLhs( *this, value ); + } + +} // namespace Catch + +// #included from: catch_message.h +#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED + +#include + +namespace Catch { + + struct MessageInfo { + MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ); + + std::string macroName; + SourceLineInfo lineInfo; + ResultWas::OfType type; + std::string message; + unsigned int sequence; + + bool operator == ( MessageInfo const& other ) const { + return sequence == other.sequence; + } + bool operator < ( MessageInfo const& other ) const { + return sequence < other.sequence; + } + private: + static unsigned int globalCount; + }; + + struct MessageBuilder { + MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + : m_info( macroName, lineInfo, type ) + {} + + template + MessageBuilder& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + MessageInfo m_info; + std::ostringstream m_stream; + }; + + class ScopedMessage { + public: + ScopedMessage( MessageBuilder const& builder ); + ScopedMessage( ScopedMessage const& other ); + ~ScopedMessage(); + + MessageInfo m_info; + }; + +} // end namespace Catch + +// #included from: catch_interfaces_capture.h +#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + class AssertionResult; + struct AssertionInfo; + struct SectionInfo; + struct MessageInfo; + class ScopedMessageBuilder; + struct Counts; + + struct IResultCapture { + + virtual ~IResultCapture(); + + virtual void assertionEnded( AssertionResult const& result ) = 0; + virtual bool sectionStarted( SectionInfo const& sectionInfo, + Counts& assertions ) = 0; + virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; + virtual void popScopedMessage( MessageInfo const& message ) = 0; + + virtual std::string getCurrentTestName() const = 0; + virtual const AssertionResult* getLastResult() const = 0; + + virtual void handleFatalErrorCondition( std::string const& message ) = 0; + }; + + IResultCapture& getResultCapture(); +} + +// #included from: catch_debugger.h +#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED + +// #included from: catch_platform.h +#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED + +#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_MAC +#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) +#define CATCH_PLATFORM_IPHONE +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +#define CATCH_PLATFORM_WINDOWS +#endif + +#include + +namespace Catch{ + + bool isDebuggerActive(); + void writeToDebugConsole( std::string const& text ); +} + +#ifdef CATCH_PLATFORM_MAC + + // The following code snippet based on: + // http://cocoawithlove.com/2008/03/break-into-debugger.html + #ifdef DEBUG + #if defined(__ppc64__) || defined(__ppc__) + #define CATCH_BREAK_INTO_DEBUGGER() \ + if( Catch::isDebuggerActive() ) { \ + __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ + : : : "memory","r0","r3","r4" ); \ + } + #else + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} + #endif + #endif + +#elif defined(_MSC_VER) + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) void __stdcall DebugBreak(); + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } +#endif + +#ifndef CATCH_BREAK_INTO_DEBUGGER +#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#endif + +// #included from: catch_interfaces_runner.h +#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED + +namespace Catch { + class TestCase; + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// In the event of a failure works out if the debugger needs to be invoked +// and/or an exception thrown and takes appropriate action. +// This needs to be done as a macro so the debugger will stop in the user +// source code rather than in Catch library code +#define INTERNAL_CATCH_REACT( resultBuilder ) \ + if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + resultBuilder.react(); + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + ( __catchResult <= expr ).endExpression(); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ + INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ + if( !Catch::getResultCapture().getLastResult()->succeeded() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + if( __catchResult.allowThrows() ) \ + try { \ + expr; \ + __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType ) { \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + } \ + catch( ... ) { \ + __catchResult.useActiveException( resultDisposition ); \ + } \ + else \ + __catchResult.captureResult( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#else + #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + __catchResult << log + ::Catch::StreamEndStop(); \ + __catchResult.captureResult( messageType ); \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) +#endif + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_INFO( log, macroName ) \ + Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ + do { \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ + try { \ + std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + __catchResult \ + .setLhs( Catch::toString( arg ) ) \ + .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ + .setOp( "matches" ) \ + .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + __catchResult.captureExpression(); \ + } catch( ... ) { \ + __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ + } \ + INTERNAL_CATCH_REACT( __catchResult ) \ + } while( Catch::alwaysFalse() ) + +// #included from: internal/catch_section.h +#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED + +// #included from: catch_section_info.h +#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED + +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// #included from: catch_totals.hpp +#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED + +#include + +namespace Catch { + + struct Counts { + Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} + + Counts operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + Counts& operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t total() const { + return passed + failed + failedButOk; + } + bool allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool allOk() const { + return failed == 0; + } + + std::size_t passed; + std::size_t failed; + std::size_t failedButOk; + }; + + struct Totals { + + Totals operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + + Totals& operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Counts assertions; + Counts testCases; + }; +} + +// #included from: catch_timer.h +#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED + +#ifdef CATCH_PLATFORM_WINDOWS +typedef unsigned long long uint64_t; +#else +#include +#endif + +namespace Catch { + + class Timer { + public: + Timer() : m_ticks( 0 ) {} + void start(); + unsigned int getElapsedMicroseconds() const; + unsigned int getElapsedMilliseconds() const; + double getElapsedSeconds() const; + + private: + uint64_t m_ticks; + }; + +} // namespace Catch + +#include + +namespace Catch { + + class Section : NonCopyable { + public: + Section( SectionInfo const& info ); + ~Section(); + + // This indicates whether the section should be executed or not + operator bool() const; + + private: + SectionInfo m_info; + + std::string m_name; + Counts m_assertions; + bool m_sectionIncluded; + Timer m_timer; + }; + +} // end namespace Catch + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define INTERNAL_CATCH_SECTION( ... ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) +#else + #define INTERNAL_CATCH_SECTION( name, desc ) \ + if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) +#endif + +// #included from: internal/catch_generators.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED + +#include +#include +#include +#include + +namespace Catch { + +template +struct IGenerator { + virtual ~IGenerator() {} + virtual T getValue( std::size_t index ) const = 0; + virtual std::size_t size () const = 0; +}; + +template +class BetweenGenerator : public IGenerator { +public: + BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} + + virtual T getValue( std::size_t index ) const { + return m_from+static_cast( index ); + } + + virtual std::size_t size() const { + return static_cast( 1+m_to-m_from ); + } + +private: + + T m_from; + T m_to; +}; + +template +class ValuesGenerator : public IGenerator { +public: + ValuesGenerator(){} + + void add( T value ) { + m_values.push_back( value ); + } + + virtual T getValue( std::size_t index ) const { + return m_values[index]; + } + + virtual std::size_t size() const { + return m_values.size(); + } + +private: + std::vector m_values; +}; + +template +class CompositeGenerator { +public: + CompositeGenerator() : m_totalSize( 0 ) {} + + // *** Move semantics, similar to auto_ptr *** + CompositeGenerator( CompositeGenerator& other ) + : m_fileInfo( other.m_fileInfo ), + m_totalSize( 0 ) + { + move( other ); + } + + CompositeGenerator& setFileInfo( const char* fileInfo ) { + m_fileInfo = fileInfo; + return *this; + } + + ~CompositeGenerator() { + deleteAll( m_composed ); + } + + operator T () const { + size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + + typename std::vector*>::const_iterator it = m_composed.begin(); + typename std::vector*>::const_iterator itEnd = m_composed.end(); + for( size_t index = 0; it != itEnd; ++it ) + { + const IGenerator* generator = *it; + if( overallIndex >= index && overallIndex < index + generator->size() ) + { + return generator->getValue( overallIndex-index ); + } + index += generator->size(); + } + CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); + return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so + } + + void add( const IGenerator* generator ) { + m_totalSize += generator->size(); + m_composed.push_back( generator ); + } + + CompositeGenerator& then( CompositeGenerator& other ) { + move( other ); + return *this; + } + + CompositeGenerator& then( T value ) { + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( value ); + add( valuesGen ); + return *this; + } + +private: + + void move( CompositeGenerator& other ) { + std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); + m_totalSize += other.m_totalSize; + other.m_composed.clear(); + } + + std::vector*> m_composed; + std::string m_fileInfo; + size_t m_totalSize; +}; + +namespace Generators +{ + template + CompositeGenerator between( T from, T to ) { + CompositeGenerator generators; + generators.add( new BetweenGenerator( from, to ) ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3 ){ + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + generators.add( valuesGen ); + return generators; + } + + template + CompositeGenerator values( T val1, T val2, T val3, T val4 ) { + CompositeGenerator generators; + ValuesGenerator* valuesGen = new ValuesGenerator(); + valuesGen->add( val1 ); + valuesGen->add( val2 ); + valuesGen->add( val3 ); + valuesGen->add( val4 ); + generators.add( valuesGen ); + return generators; + } + +} // end namespace Generators + +using namespace Generators; + +} // end namespace Catch + +#define INTERNAL_CATCH_LINESTR2( line ) #line +#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) + +#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) + +// #included from: internal/catch_interfaces_exception.h +#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED + +#include +// #included from: catch_interfaces_registry_hub.h +#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED + +#include + +namespace Catch { + + class TestCase; + struct ITestCaseRegistry; + struct IExceptionTranslatorRegistry; + struct IExceptionTranslator; + struct IReporterRegistry; + struct IReporterFactory; + + struct IRegistryHub { + virtual ~IRegistryHub(); + + virtual IReporterRegistry const& getReporterRegistry() const = 0; + virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + }; + + struct IMutableRegistryHub { + virtual ~IMutableRegistryHub(); + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerTest( TestCase const& testInfo ) = 0; + virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + }; + + IRegistryHub& getRegistryHub(); + IMutableRegistryHub& getMutableRegistryHub(); + void cleanUp(); + std::string translateActiveException(); + +} + + +namespace Catch { + + typedef std::string(*exceptionTranslateFunction)(); + + struct IExceptionTranslator { + virtual ~IExceptionTranslator(); + virtual std::string translate() const = 0; + }; + + struct IExceptionTranslatorRegistry { + virtual ~IExceptionTranslatorRegistry(); + + virtual std::string translateActiveException() const = 0; + }; + + class ExceptionTranslatorRegistrar { + template + class ExceptionTranslator : public IExceptionTranslator { + public: + + ExceptionTranslator( std::string(*translateFunction)( T& ) ) + : m_translateFunction( translateFunction ) + {} + + virtual std::string translate() const { + try { + throw; + } + catch( T& ex ) { + return m_translateFunction( ex ); + } + } + + protected: + std::string(*m_translateFunction)( T& ); + }; + + public: + template + ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { + getMutableRegistryHub().registerTranslator + ( new ExceptionTranslator( translateFunction ) ); + } + }; +} + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ + static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) + +// #included from: internal/catch_approx.hpp +#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED + +#include +#include + +namespace Catch { +namespace Detail { + + class Approx { + public: + explicit Approx ( double value ) + : m_epsilon( std::numeric_limits::epsilon()*100 ), + m_scale( 1.0 ), + m_value( value ) + {} + + Approx( Approx const& other ) + : m_epsilon( other.m_epsilon ), + m_scale( other.m_scale ), + m_value( other.m_value ) + {} + + static Approx custom() { + return Approx( 0 ); + } + + Approx operator()( double value ) { + Approx approx( value ); + approx.epsilon( m_epsilon ); + approx.scale( m_scale ); + return approx; + } + + friend bool operator == ( double lhs, Approx const& rhs ) { + // Thanks to Richard Harris for his help refining this formula + return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + } + + friend bool operator == ( Approx const& lhs, double rhs ) { + return operator==( rhs, lhs ); + } + + friend bool operator != ( double lhs, Approx const& rhs ) { + return !operator==( lhs, rhs ); + } + + friend bool operator != ( Approx const& lhs, double rhs ) { + return !operator==( rhs, lhs ); + } + + Approx& epsilon( double newEpsilon ) { + m_epsilon = newEpsilon; + return *this; + } + + Approx& scale( double newScale ) { + m_scale = newScale; + return *this; + } + + std::string toString() const { + std::ostringstream oss; + oss << "Approx( " << Catch::toString( m_value ) << " )"; + return oss.str(); + } + + private: + double m_epsilon; + double m_scale; + double m_value; + }; +} + +template<> +inline std::string toString( Detail::Approx const& value ) { + return value.toString(); +} + +} // end namespace Catch + +// #included from: internal/catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + private: + std::vector > > m_matchers; + }; + + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct Equals : MatcherImpl { + Equals( std::string const& str ) : m_str( str ){} + Equals( Equals const& other ) : m_str( other.m_str ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_str == expr; + } + virtual std::string toString() const { + return "equals: \"" + m_str + "\""; + } + + std::string m_str; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr ) : m_substr( substr ){} + Contains( Contains const& other ) : m_substr( other.m_substr ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr ) : m_substr( substr ){} + StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr ) : m_substr( substr ){} + EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return expr.find( m_substr ) == expr.size() - m_substr.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_substr + "\""; + } + + std::string m_substr; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str ) { + return Impl::StdString::Equals( str ); + } + inline Impl::StdString::Equals Equals( const char* str ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); + } + inline Impl::StdString::Contains Contains( std::string const& substr ) { + return Impl::StdString::Contains( substr ); + } + inline Impl::StdString::Contains Contains( const char* substr ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + +// #included from: internal/catch_interfaces_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + +// #included from: catch_tag_alias.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + +#include + +namespace Catch { + + struct TagAlias { + TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + + std::string tag; + SourceLineInfo lineInfo; + }; + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } +// #included from: catch_option.hpp +#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + +namespace Catch { + + // An optional type + template + class Option { + public: + Option() : nullableValue( NULL ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + {} + + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); + } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; + } + + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = NULL; + } + + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } + + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; + } + + bool some() const { return nullableValue != NULL; } + bool none() const { return nullableValue == NULL; } + + bool operator !() const { return nullableValue == NULL; } + operator SafeBool::type() const { + return SafeBool::makeSafe( some() ); + } + + private: + T* nullableValue; + char storage[sizeof(T)]; + }; + +} // end namespace Catch + +namespace Catch { + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + virtual Option find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// These files are included here so the single_include script doesn't put them +// in the conditionally compiled sections +// #included from: internal/catch_test_case_info.h +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED + +#include +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + struct ITestCase; + + struct TestCaseInfo { + enum SpecialProperties{ + None = 0, + IsHidden = 1 << 1, + ShouldFail = 1 << 2, + MayFail = 1 << 3, + Throws = 1 << 4 + }; + + TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ); + + TestCaseInfo( TestCaseInfo const& other ); + + bool isHidden() const; + bool throws() const; + bool okToFail() const; + bool expectedToFail() const; + + std::string name; + std::string className; + std::string description; + std::set tags; + std::set lcaseTags; + std::string tagsAsString; + SourceLineInfo lineInfo; + SpecialProperties properties; + }; + + class TestCase : public TestCaseInfo { + public: + + TestCase( ITestCase* testCase, TestCaseInfo const& info ); + TestCase( TestCase const& other ); + + TestCase withName( std::string const& _newName ) const; + + void invoke() const; + + TestCaseInfo const& getTestCaseInfo() const; + + void swap( TestCase& other ); + bool operator == ( TestCase const& other ) const; + bool operator < ( TestCase const& other ) const; + TestCase& operator = ( TestCase const& other ); + + private: + Ptr test; + }; + + TestCase makeTestCase( ITestCase* testCase, + std::string const& className, + std::string const& name, + std::string const& description, + SourceLineInfo const& lineInfo ); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + + +#ifdef __OBJC__ +// #included from: internal/catch_objc.hpp +#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED + +#import + +#include + +// NB. Any general catch headers included here must be included +// in catch.hpp first to make sure they are included by the single +// header for non obj-usage + +/////////////////////////////////////////////////////////////////////////////// +// This protocol is really only here for (self) documenting purposes, since +// all its methods are optional. +@protocol OcFixture + +@optional + +-(void) setUp; +-(void) tearDown; + +@end + +namespace Catch { + + class OcMethod : public SharedImpl { + + public: + OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} + + virtual void invoke() const { + id obj = [[m_cls alloc] init]; + + performOptionalSelector( obj, @selector(setUp) ); + performOptionalSelector( obj, m_sel ); + performOptionalSelector( obj, @selector(tearDown) ); + + arcSafeRelease( obj ); + } + private: + virtual ~OcMethod() {} + + Class m_cls; + SEL m_sel; + }; + + namespace Detail{ + + inline std::string getAnnotation( Class cls, + std::string const& annotationName, + std::string const& testCaseName ) { + NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; + SEL sel = NSSelectorFromString( selStr ); + arcSafeRelease( selStr ); + id value = performOptionalSelector( cls, sel ); + if( value ) + return [(NSString*)value UTF8String]; + return ""; + } + } + + inline size_t registerTestMethods() { + size_t noTestMethods = 0; + int noClasses = objc_getClassList( NULL, 0 ); + + Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); + objc_getClassList( classes, noClasses ); + + for( int c = 0; c < noClasses; c++ ) { + Class cls = classes[c]; + { + u_int count; + Method* methods = class_copyMethodList( cls, &count ); + for( u_int m = 0; m < count ; m++ ) { + SEL selector = method_getName(methods[m]); + std::string methodName = sel_getName(selector); + if( startsWith( methodName, "Catch_TestCase_" ) ) { + std::string testCaseName = methodName.substr( 15 ); + std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); + std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); + const char* className = class_getName( cls ); + + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + noTestMethods++; + } + } + free(methods); + } + } + return noTestMethods; + } + + namespace Matchers { + namespace Impl { + namespace NSStringMatchers { + + template + struct StringHolder : MatcherImpl{ + StringHolder( NSString* substr ) : m_substr( [substr copy] ){} + StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} + StringHolder() { + arcSafeRelease( m_substr ); + } + + NSString* m_substr; + }; + + struct Equals : StringHolder { + Equals( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str isEqualToString:m_substr]; + } + + virtual std::string toString() const { + return "equals string: " + Catch::toString( m_substr ); + } + }; + + struct Contains : StringHolder { + Contains( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location != NSNotFound; + } + + virtual std::string toString() const { + return "contains string: " + Catch::toString( m_substr ); + } + }; + + struct StartsWith : StringHolder { + StartsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == 0; + } + + virtual std::string toString() const { + return "starts with: " + Catch::toString( m_substr ); + } + }; + struct EndsWith : StringHolder { + EndsWith( NSString* substr ) : StringHolder( substr ){} + + virtual bool match( ExpressionType const& str ) const { + return (str != nil || m_substr == nil ) && + [str rangeOfString:m_substr].location == [str length] - [m_substr length]; + } + + virtual std::string toString() const { + return "ends with: " + Catch::toString( m_substr ); + } + }; + + } // namespace NSStringMatchers + } // namespace Impl + + inline Impl::NSStringMatchers::Equals + Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } + + inline Impl::NSStringMatchers::Contains + Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } + + inline Impl::NSStringMatchers::StartsWith + StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } + + inline Impl::NSStringMatchers::EndsWith + EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } + + } // namespace Matchers + + using namespace Matchers; + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define OC_TEST_CASE( name, desc )\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ +{\ +return @ name; \ +}\ ++(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +{ \ +return @ desc; \ +} \ +-(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) + +#endif + +#ifdef CATCH_IMPL +// #included from: internal/catch_impl.hpp +#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED + +// Collect all the implementation files together here +// These are the equivalent of what would usually be cpp files + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif + +// #included from: ../catch_runner.hpp +#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED + +// #included from: internal/catch_commandline.hpp +#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED + +// #included from: catch_config.hpp +#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED + +// #included from: catch_test_spec_parser.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +// #included from: catch_test_spec.hpp +#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +#endif + +#include +#include + +namespace Catch { + + class TestSpec { + struct Pattern : SharedImpl<> { + virtual ~Pattern(); + virtual bool matches( TestCaseInfo const& testCase ) const = 0; + }; + class NamePattern : public Pattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { + if( startsWith( m_name, "*" ) ) { + m_name = m_name.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_name, "*" ) ) { + m_name = m_name.substr( 0, m_name.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~NamePattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_name == toLower( testCase.name ); + case WildcardAtStart: + return endsWith( toLower( testCase.name ), m_name ); + case WildcardAtEnd: + return startsWith( toLower( testCase.name ), m_name ); + case WildcardAtBothEnds: + return contains( toLower( testCase.name ), m_name ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string m_name; + WildcardPosition m_wildcard; + }; + class TagPattern : public Pattern { + public: + TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + virtual ~TagPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { + return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); + } + private: + std::string m_tag; + }; + class ExcludedPattern : public Pattern { + public: + ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + virtual ~ExcludedPattern(); + virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + private: + Ptr m_underlyingPattern; + }; + + struct Filter { + std::vector > m_patterns; + + bool matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( std::vector >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) + if( !(*it)->matches( testCase ) ) + return false; + return true; + } + }; + + public: + bool hasFilters() const { + return !m_filters.empty(); + } + bool matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( std::vector::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) + if( it->matches( testCase ) ) + return true; + return false; + } + + private: + std::vector m_filters; + + friend class TestSpecParser; + }; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag }; + Mode m_mode; + bool m_exclusion; + std::size_t m_start, m_pos; + std::string m_arg; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern(); + return *this; + } + TestSpec testSpec() { + addFilter(); + return m_testSpec; + } + private: + void visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern(); + startNewMode( Tag, ++m_pos ); + } + } + else if( m_mode == QuotedName && c == '"' ) + addPattern(); + else if( m_mode == Tag && c == ']' ) + addPattern(); + } + void startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + template + void addPattern() { + std::string token = subString(); + if( startsWith( token, "exclude:" ) ) { + m_exclusion = true; + token = token.substr( 8 ); + } + if( !token.empty() ) { + Ptr pattern = new T( token ); + if( m_exclusion ) + pattern = new TestSpec::ExcludedPattern( pattern ); + m_currentFilter.m_patterns.push_back( pattern ); + } + m_exclusion = false; + m_mode = None; + } + void addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + }; + inline TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +// #included from: catch_interfaces_config.h +#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct Verbosity { enum Level { + NoOutput = 0, + Quiet, + Normal + }; }; + + struct WarnAbout { enum What { + Nothing = 0x00, + NoAssertions = 0x01 + }; }; + + struct ShowDurations { enum OrNot { + DefaultForReporter, + Always, + Never + }; }; + struct RunTests { enum InWhatOrder { + InDeclarationOrder, + InLexicographicalOrder, + InRandomOrder + }; }; + + class TestSpec; + + struct IConfig : IShared { + + virtual ~IConfig(); + + virtual bool allowThrows() const = 0; + virtual std::ostream& stream() const = 0; + virtual std::string name() const = 0; + virtual bool includeSuccessfulResults() const = 0; + virtual bool shouldDebugBreak() const = 0; + virtual bool warnAboutMissingAssertions() const = 0; + virtual int abortAfter() const = 0; + virtual bool showInvisibles() const = 0; + virtual ShowDurations::OrNot showDurations() const = 0; + virtual TestSpec const& testSpec() const = 0; + virtual RunTests::InWhatOrder runOrder() const = 0; + virtual unsigned int rngSeed() const = 0; + virtual bool forceColour() const = 0; + }; +} + +// #included from: catch_stream.h +#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED + +#include + +#ifdef __clang__ +#pragma clang diagnostic ignored "-Wpadded" +#endif + +namespace Catch { + + class Stream { + public: + Stream(); + Stream( std::streambuf* _streamBuf, bool _isOwned ); + void release(); + + std::streambuf* streamBuf; + + private: + bool isOwned; + }; + + std::ostream& cout(); + std::ostream& cerr(); +} + +#include +#include +#include +#include +#include + +#ifndef CATCH_CONFIG_CONSOLE_WIDTH +#define CATCH_CONFIG_CONSOLE_WIDTH 80 +#endif + +namespace Catch { + + struct ConfigData { + + ConfigData() + : listTests( false ), + listTags( false ), + listReporters( false ), + listTestNamesOnly( false ), + showSuccessfulTests( false ), + shouldDebugBreak( false ), + noThrow( false ), + showHelp( false ), + showInvisibles( false ), + forceColour( false ), + abortAfter( -1 ), + rngSeed( 0 ), + verbosity( Verbosity::Normal ), + warnings( WarnAbout::Nothing ), + showDurations( ShowDurations::DefaultForReporter ), + runOrder( RunTests::InDeclarationOrder ) + {} + + bool listTests; + bool listTags; + bool listReporters; + bool listTestNamesOnly; + + bool showSuccessfulTests; + bool shouldDebugBreak; + bool noThrow; + bool showHelp; + bool showInvisibles; + bool forceColour; + + int abortAfter; + unsigned int rngSeed; + + Verbosity::Level verbosity; + WarnAbout::What warnings; + ShowDurations::OrNot showDurations; + RunTests::InWhatOrder runOrder; + + std::string reporterName; + std::string outputFilename; + std::string name; + std::string processName; + + std::vector testsOrTags; + }; + + class Config : public SharedImpl { + private: + Config( Config const& other ); + Config& operator = ( Config const& other ); + virtual void dummy(); + public: + + Config() + : m_os( Catch::cout().rdbuf() ) + {} + + Config( ConfigData const& data ) + : m_data( data ), + m_os( Catch::cout().rdbuf() ) + { + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) + parser.parse( data.testsOrTags[i] ); + m_testSpec = parser.testSpec(); + } + } + + virtual ~Config() { + m_os.rdbuf( Catch::cout().rdbuf() ); + m_stream.release(); + } + + void setFilename( std::string const& filename ) { + m_data.outputFilename = filename; + } + + std::string const& getFilename() const { + return m_data.outputFilename ; + } + + bool listTests() const { return m_data.listTests; } + bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool listTags() const { return m_data.listTags; } + bool listReporters() const { return m_data.listReporters; } + + std::string getProcessName() const { return m_data.processName; } + + bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + + void setStreamBuf( std::streambuf* buf ) { + m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); + } + + void useStream( std::string const& streamName ) { + Stream stream = createStream( streamName ); + setStreamBuf( stream.streamBuf ); + m_stream.release(); + m_stream = stream; + } + + std::string getReporterName() const { return m_data.reporterName; } + + int abortAfter() const { return m_data.abortAfter; } + + TestSpec const& testSpec() const { return m_testSpec; } + + bool showHelp() const { return m_data.showHelp; } + bool showInvisibles() const { return m_data.showInvisibles; } + + // IConfig interface + virtual bool allowThrows() const { return !m_data.noThrow; } + virtual std::ostream& stream() const { return m_os; } + virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } + virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } + virtual unsigned int rngSeed() const { return m_data.rngSeed; } + virtual bool forceColour() const { return m_data.forceColour; } + + private: + ConfigData m_data; + + Stream m_stream; + mutable std::ostream m_os; + TestSpec m_testSpec; + }; + +} // end namespace Catch + +// #included from: catch_clara.h +#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED + +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH +#undef CLARA_CONFIG_CONSOLE_WIDTH +#endif +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +// Declare Clara inside the Catch namespace +#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { +// #included from: ../external/clara.h + +// Only use header guard if we are not using an outer namespace +#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) + +#ifndef STITCH_CLARA_OPEN_NAMESPACE +#define TWOBLUECUBES_CLARA_H_INCLUDED +#define STITCH_CLARA_OPEN_NAMESPACE +#define STITCH_CLARA_CLOSE_NAMESPACE +#else +#define STITCH_CLARA_CLOSE_NAMESPACE } +#endif + +#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + +// ----------- #included from tbc_text_format.h ----------- + +// Only use header guard if we are not using an outer namespace +#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) +#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +#define TBC_TEXT_FORMAT_H_INCLUDED +#endif + +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TBC_TEXT_FORMAT_H_INCLUDED + +// ----------- end of #include from tbc_text_format.h ----------- +// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h + +#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE + +#include +#include +#include +#include + +// Use optional outer namespace +#ifdef STITCH_CLARA_OPEN_NAMESPACE +STITCH_CLARA_OPEN_NAMESPACE +#endif + +namespace Clara { + + struct UnpositionalTag {}; + + extern UnpositionalTag _; + +#ifdef CLARA_CONFIG_MAIN + UnpositionalTag _; +#endif + + namespace Detail { + +#ifdef CLARA_CONSOLE_WIDTH + const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + using namespace Tbc; + + inline bool startsWith( std::string const& str, std::string const& prefix ) { + return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + } + + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + template struct RemoveConstRef{ typedef T type; }; + + template struct IsBool { static const bool value = false; }; + template<> struct IsBool { static const bool value = true; }; + + template + void convertInto( std::string const& _source, T& _dest ) { + std::stringstream ss; + ss << _source; + ss >> _dest; + if( ss.fail() ) + throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); + } + inline void convertInto( std::string const& _source, std::string& _dest ) { + _dest = _source; + } + inline void convertInto( std::string const& _source, bool& _dest ) { + std::string sourceLC = _source; + std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); + if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) + _dest = true; + else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) + _dest = false; + else + throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); + } + inline void convertInto( bool _source, bool& _dest ) { + _dest = _source; + } + template + inline void convertInto( bool, T& ) { + throw std::runtime_error( "Invalid conversion" ); + } + + template + struct IArgFunction { + virtual ~IArgFunction() {} +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + IArgFunction() = default; + IArgFunction( IArgFunction const& ) = default; +# endif + virtual void set( ConfigT& config, std::string const& value ) const = 0; + virtual void setFlag( ConfigT& config ) const = 0; + virtual bool takesArg() const = 0; + virtual IArgFunction* clone() const = 0; + }; + + template + class BoundArgFunction { + public: + BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction& operator = ( BoundArgFunction const& other ) { + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + delete functionObj; + functionObj = newFunctionObj; + return *this; + } + ~BoundArgFunction() { delete functionObj; } + + void set( ConfigT& config, std::string const& value ) const { + functionObj->set( config, value ); + } + void setFlag( ConfigT& config ) const { + functionObj->setFlag( config ); + } + bool takesArg() const { return functionObj->takesArg(); } + + bool isSet() const { + return functionObj != NULL; + } + private: + IArgFunction* functionObj; + }; + + template + struct NullBinder : IArgFunction{ + virtual void set( C&, std::string const& ) const {} + virtual void setFlag( C& ) const {} + virtual bool takesArg() const { return true; } + virtual IArgFunction* clone() const { return new NullBinder( *this ); } + }; + + template + struct BoundDataMember : IArgFunction{ + BoundDataMember( M C::* _member ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + convertInto( stringValue, p.*member ); + } + virtual void setFlag( C& p ) const { + convertInto( true, p.*member ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundDataMember( *this ); } + M C::* member; + }; + template + struct BoundUnaryMethod : IArgFunction{ + BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + (p.*member)( value ); + } + virtual void setFlag( C& p ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + (p.*member)( value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundUnaryMethod( *this ); } + void (C::*member)( M ); + }; + template + struct BoundNullaryMethod : IArgFunction{ + BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} + virtual void set( C& p, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + (p.*member)(); + } + virtual void setFlag( C& p ) const { + (p.*member)(); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundNullaryMethod( *this ); } + void (C::*member)(); + }; + + template + struct BoundUnaryFunction : IArgFunction{ + BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + bool value; + convertInto( stringValue, value ); + if( value ) + function( obj ); + } + virtual void setFlag( C& p ) const { + function( p ); + } + virtual bool takesArg() const { return false; } + virtual IArgFunction* clone() const { return new BoundUnaryFunction( *this ); } + void (*function)( C& ); + }; + + template + struct BoundBinaryFunction : IArgFunction{ + BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} + virtual void set( C& obj, std::string const& stringValue ) const { + typename RemoveConstRef::type value; + convertInto( stringValue, value ); + function( obj, value ); + } + virtual void setFlag( C& obj ) const { + typename RemoveConstRef::type value; + convertInto( true, value ); + function( obj, value ); + } + virtual bool takesArg() const { return !IsBool::value; } + virtual IArgFunction* clone() const { return new BoundBinaryFunction( *this ); } + void (*function)( C&, T ); + }; + + } // namespace Detail + + struct Parser { + Parser() : separators( " \t=:" ) {} + + struct Token { + enum Type { Positional, ShortOpt, LongOpt }; + Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} + Type type; + std::string data; + }; + + void parseIntoTokens( int argc, char const * const * argv, std::vector& tokens ) const { + const std::string doubleDash = "--"; + for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) + parseIntoTokens( argv[i] , tokens); + } + void parseIntoTokens( std::string arg, std::vector& tokens ) const { + while( !arg.empty() ) { + Parser::Token token( Parser::Token::Positional, arg ); + arg = ""; + if( token.data[0] == '-' ) { + if( token.data.size() > 1 && token.data[1] == '-' ) { + token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); + } + else { + token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); + if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { + arg = "-" + token.data.substr( 1 ); + token.data = token.data.substr( 0, 1 ); + } + } + } + if( token.type != Parser::Token::Positional ) { + std::size_t pos = token.data.find_first_of( separators ); + if( pos != std::string::npos ) { + arg = token.data.substr( pos+1 ); + token.data = token.data.substr( 0, pos ); + } + } + tokens.push_back( token ); + } + } + std::string separators; + }; + + template + struct CommonArgProperties { + CommonArgProperties() {} + CommonArgProperties( Detail::BoundArgFunction const& _boundField ) : boundField( _boundField ) {} + + Detail::BoundArgFunction boundField; + std::string description; + std::string detail; + std::string placeholder; // Only value if boundField takes an arg + + bool takesArg() const { + return !placeholder.empty(); + } + void validate() const { + if( !boundField.isSet() ) + throw std::logic_error( "option not bound" ); + } + }; + struct OptionArgProperties { + std::vector shortNames; + std::string longName; + + bool hasShortName( std::string const& shortName ) const { + return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); + } + bool hasLongName( std::string const& _longName ) const { + return _longName == longName; + } + }; + struct PositionalArgProperties { + PositionalArgProperties() : position( -1 ) {} + int position; // -1 means non-positional (floating) + + bool isFixedPositional() const { + return position != -1; + } + }; + + template + class CommandLine { + + struct Arg : CommonArgProperties, OptionArgProperties, PositionalArgProperties { + Arg() {} + Arg( Detail::BoundArgFunction const& _boundField ) : CommonArgProperties( _boundField ) {} + + using CommonArgProperties::placeholder; // !TBD + + std::string dbgName() const { + if( !longName.empty() ) + return "--" + longName; + if( !shortNames.empty() ) + return "-" + shortNames[0]; + return "positional args"; + } + std::string commands() const { + std::ostringstream oss; + bool first = true; + std::vector::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); + for(; it != itEnd; ++it ) { + if( first ) + first = false; + else + oss << ", "; + oss << "-" << *it; + } + if( !longName.empty() ) { + if( !first ) + oss << ", "; + oss << "--" << longName; + } + if( !placeholder.empty() ) + oss << " <" << placeholder << ">"; + return oss.str(); + } + }; + + // NOTE: std::auto_ptr is deprecated in c++11/c++0x +#if defined(__cplusplus) && __cplusplus > 199711L + typedef std::unique_ptr ArgAutoPtr; +#else + typedef std::auto_ptr ArgAutoPtr; +#endif + + friend void addOptName( Arg& arg, std::string const& optName ) + { + if( optName.empty() ) + return; + if( Detail::startsWith( optName, "--" ) ) { + if( !arg.longName.empty() ) + throw std::logic_error( "Only one long opt may be specified. '" + + arg.longName + + "' already specified, now attempting to add '" + + optName + "'" ); + arg.longName = optName.substr( 2 ); + } + else if( Detail::startsWith( optName, "-" ) ) + arg.shortNames.push_back( optName.substr( 1 ) ); + else + throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); + } + friend void setPositionalArg( Arg& arg, int position ) + { + arg.position = position; + } + + class ArgBuilder { + public: + ArgBuilder( Arg* arg ) : m_arg( arg ) {} + + // Bind a non-boolean data member (requires placeholder string) + template + void bind( M C::* field, std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + m_arg->placeholder = placeholder; + } + // Bind a boolean data member (no placeholder required) + template + void bind( bool C::* field ) { + m_arg->boundField = new Detail::BoundDataMember( field ); + } + + // Bind a method taking a single, non-boolean argument (requires a placeholder string) + template + void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + m_arg->placeholder = placeholder; + } + + // Bind a method taking a single, boolean argument (no placeholder string required) + template + void bind( void (C::* unaryMethod)( bool ) ) { + m_arg->boundField = new Detail::BoundUnaryMethod( unaryMethod ); + } + + // Bind a method that takes no arguments (will be called if opt is present) + template + void bind( void (C::* nullaryMethod)() ) { + m_arg->boundField = new Detail::BoundNullaryMethod( nullaryMethod ); + } + + // Bind a free function taking a single argument - the object to operate on (no placeholder string required) + template + void bind( void (* unaryFunction)( C& ) ) { + m_arg->boundField = new Detail::BoundUnaryFunction( unaryFunction ); + } + + // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) + template + void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { + m_arg->boundField = new Detail::BoundBinaryFunction( binaryFunction ); + m_arg->placeholder = placeholder; + } + + ArgBuilder& describe( std::string const& description ) { + m_arg->description = description; + return *this; + } + ArgBuilder& detail( std::string const& detail ) { + m_arg->detail = detail; + return *this; + } + + protected: + Arg* m_arg; + }; + + class OptBuilder : public ArgBuilder { + public: + OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} + OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + + OptBuilder& operator[]( std::string const& optName ) { + addOptName( *ArgBuilder::m_arg, optName ); + return *this; + } + }; + + public: + + CommandLine() + : m_boundProcessName( new Detail::NullBinder() ), + m_highestSpecifiedArgPosition( 0 ), + m_throwOnUnrecognisedTokens( false ) + {} + CommandLine( CommandLine const& other ) + : m_boundProcessName( other.m_boundProcessName ), + m_options ( other.m_options ), + m_positionalArgs( other.m_positionalArgs ), + m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), + m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) + { + if( other.m_floatingArg.get() ) + m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); + } + + CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { + m_throwOnUnrecognisedTokens = shouldThrow; + return *this; + } + + OptBuilder operator[]( std::string const& optName ) { + m_options.push_back( Arg() ); + addOptName( m_options.back(), optName ); + OptBuilder builder( &m_options.back() ); + return builder; + } + + ArgBuilder operator[]( int position ) { + m_positionalArgs.insert( std::make_pair( position, Arg() ) ); + if( position > m_highestSpecifiedArgPosition ) + m_highestSpecifiedArgPosition = position; + setPositionalArg( m_positionalArgs[position], position ); + ArgBuilder builder( &m_positionalArgs[position] ); + return builder; + } + + // Invoke this with the _ instance + ArgBuilder operator[]( UnpositionalTag ) { + if( m_floatingArg.get() ) + throw std::logic_error( "Only one unpositional argument can be added" ); + m_floatingArg.reset( new Arg() ); + ArgBuilder builder( m_floatingArg.get() ); + return builder; + } + + template + void bindProcessName( M C::* field ) { + m_boundProcessName = new Detail::BoundDataMember( field ); + } + template + void bindProcessName( void (C::*_unaryMethod)( M ) ) { + m_boundProcessName = new Detail::BoundUnaryMethod( _unaryMethod ); + } + + void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { + typename std::vector::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; + std::size_t maxWidth = 0; + for( it = itBegin; it != itEnd; ++it ) + maxWidth = (std::max)( maxWidth, it->commands().size() ); + + for( it = itBegin; it != itEnd; ++it ) { + Detail::Text usage( it->commands(), Detail::TextAttributes() + .setWidth( maxWidth+indent ) + .setIndent( indent ) ); + Detail::Text desc( it->description, Detail::TextAttributes() + .setWidth( width - maxWidth - 3 ) ); + + for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { + std::string usageCol = i < usage.size() ? usage[i] : ""; + os << usageCol; + + if( i < desc.size() && !desc[i].empty() ) + os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) + << desc[i]; + os << "\n"; + } + } + } + std::string optUsage() const { + std::ostringstream oss; + optUsage( oss ); + return oss.str(); + } + + void argSynopsis( std::ostream& os ) const { + for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { + if( i > 1 ) + os << " "; + typename std::map::const_iterator it = m_positionalArgs.find( i ); + if( it != m_positionalArgs.end() ) + os << "<" << it->second.placeholder << ">"; + else if( m_floatingArg.get() ) + os << "<" << m_floatingArg->placeholder << ">"; + else + throw std::logic_error( "non consecutive positional arguments with no floating args" ); + } + // !TBD No indication of mandatory args + if( m_floatingArg.get() ) { + if( m_highestSpecifiedArgPosition > 1 ) + os << " "; + os << "[<" << m_floatingArg->placeholder << "> ...]"; + } + } + std::string argSynopsis() const { + std::ostringstream oss; + argSynopsis( oss ); + return oss.str(); + } + + void usage( std::ostream& os, std::string const& procName ) const { + validate(); + os << "usage:\n " << procName << " "; + argSynopsis( os ); + if( !m_options.empty() ) { + os << " [options]\n\nwhere options are: \n"; + optUsage( os, 2 ); + } + os << "\n"; + } + std::string usage( std::string const& procName ) const { + std::ostringstream oss; + usage( oss, procName ); + return oss.str(); + } + + ConfigT parse( int argc, char const * const * argv ) const { + ConfigT config; + parseInto( argc, argv, config ); + return config; + } + + std::vector parseInto( int argc, char const * const * argv, ConfigT& config ) const { + std::string processName = argv[0]; + std::size_t lastSlash = processName.find_last_of( "/\\" ); + if( lastSlash != std::string::npos ) + processName = processName.substr( lastSlash+1 ); + m_boundProcessName.set( config, processName ); + std::vector tokens; + Parser parser; + parser.parseIntoTokens( argc, argv, tokens ); + return populate( tokens, config ); + } + + std::vector populate( std::vector const& tokens, ConfigT& config ) const { + validate(); + std::vector unusedTokens = populateOptions( tokens, config ); + unusedTokens = populateFixedArgs( unusedTokens, config ); + unusedTokens = populateFloatingArgs( unusedTokens, config ); + return unusedTokens; + } + + std::vector populateOptions( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + std::vector errors; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::vector::const_iterator it = m_options.begin(), itEnd = m_options.end(); + for(; it != itEnd; ++it ) { + Arg const& arg = *it; + + try { + if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || + ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { + if( arg.takesArg() ) { + if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) + errors.push_back( "Expected argument to option: " + token.data ); + else + arg.boundField.set( config, tokens[++i].data ); + } + else { + arg.boundField.setFlag( config ); + } + break; + } + } + catch( std::exception& ex ) { + errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); + } + } + if( it == itEnd ) { + if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) + unusedTokens.push_back( token ); + else if( errors.empty() && m_throwOnUnrecognisedTokens ) + errors.push_back( "unrecognised option: " + token.data ); + } + } + if( !errors.empty() ) { + std::ostringstream oss; + for( std::vector::const_iterator it = errors.begin(), itEnd = errors.end(); + it != itEnd; + ++it ) { + if( it != errors.begin() ) + oss << "\n"; + oss << *it; + } + throw std::runtime_error( oss.str() ); + } + return unusedTokens; + } + std::vector populateFixedArgs( std::vector const& tokens, ConfigT& config ) const { + std::vector unusedTokens; + int position = 1; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + typename std::map::const_iterator it = m_positionalArgs.find( position ); + if( it != m_positionalArgs.end() ) + it->second.boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + if( token.type == Parser::Token::Positional ) + position++; + } + return unusedTokens; + } + std::vector populateFloatingArgs( std::vector const& tokens, ConfigT& config ) const { + if( !m_floatingArg.get() ) + return tokens; + std::vector unusedTokens; + for( std::size_t i = 0; i < tokens.size(); ++i ) { + Parser::Token const& token = tokens[i]; + if( token.type == Parser::Token::Positional ) + m_floatingArg->boundField.set( config, token.data ); + else + unusedTokens.push_back( token ); + } + return unusedTokens; + } + + void validate() const + { + if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) + throw std::logic_error( "No options or arguments specified" ); + + for( typename std::vector::const_iterator it = m_options.begin(), + itEnd = m_options.end(); + it != itEnd; ++it ) + it->validate(); + } + + private: + Detail::BoundArgFunction m_boundProcessName; + std::vector m_options; + std::map m_positionalArgs; + ArgAutoPtr m_floatingArg; + int m_highestSpecifiedArgPosition; + bool m_throwOnUnrecognisedTokens; + }; + +} // end namespace Clara + +STITCH_CLARA_CLOSE_NAMESPACE +#undef STITCH_CLARA_OPEN_NAMESPACE +#undef STITCH_CLARA_CLOSE_NAMESPACE + +#endif // TWOBLUECUBES_CLARA_H_INCLUDED +#undef STITCH_CLARA_OPEN_NAMESPACE + +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif + +#include + +namespace Catch { + + inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } + inline void abortAfterX( ConfigData& config, int x ) { + if( x < 1 ) + throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); + config.abortAfter = x; + } + inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + + inline void addWarning( ConfigData& config, std::string const& _warning ) { + if( _warning == "NoAssertions" ) + config.warnings = static_cast( config.warnings | WarnAbout::NoAssertions ); + else + throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); + } + inline void setOrder( ConfigData& config, std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; + else + throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); + } + inline void setRngSeed( ConfigData& config, std::string const& seed ) { + if( seed == "time" ) { + config.rngSeed = static_cast( std::time(0) ); + } + else { + std::stringstream ss; + ss << seed; + ss >> config.rngSeed; + if( ss.fail() ) + throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + } + } + inline void setVerbosity( ConfigData& config, int level ) { + // !TBD: accept strings? + config.verbosity = static_cast( level ); + } + inline void setShowDurations( ConfigData& config, bool _showDurations ) { + config.showDurations = _showDurations + ? ShowDurations::Always + : ShowDurations::Never; + } + inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { + std::ifstream f( _filename.c_str() ); + if( !f.is_open() ) + throw std::domain_error( "Unable to load input file: " + _filename ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, "#" ) ) + addTestOrTags( config, "\"" + line + "\"," ); + } + } + + inline Clara::CommandLine makeCommandLineParser() { + + using namespace Clara; + CommandLine cli; + + cli.bindProcessName( &ConfigData::processName ); + + cli["-?"]["-h"]["--help"] + .describe( "display usage information" ) + .bind( &ConfigData::showHelp ); + + cli["-l"]["--list-tests"] + .describe( "list all/matching test cases" ) + .bind( &ConfigData::listTests ); + + cli["-t"]["--list-tags"] + .describe( "list all/matching tags" ) + .bind( &ConfigData::listTags ); + + cli["-s"]["--success"] + .describe( "include successful tests in output" ) + .bind( &ConfigData::showSuccessfulTests ); + + cli["-b"]["--break"] + .describe( "break into debugger on failure" ) + .bind( &ConfigData::shouldDebugBreak ); + + cli["-e"]["--nothrow"] + .describe( "skip exception tests" ) + .bind( &ConfigData::noThrow ); + + cli["-i"]["--invisibles"] + .describe( "show invisibles (tabs, newlines)" ) + .bind( &ConfigData::showInvisibles ); + + cli["-o"]["--out"] + .describe( "output filename" ) + .bind( &ConfigData::outputFilename, "filename" ); + + cli["-r"]["--reporter"] +// .placeholder( "name[:filename]" ) + .describe( "reporter to use (defaults to console)" ) + .bind( &ConfigData::reporterName, "name" ); + + cli["-n"]["--name"] + .describe( "suite name" ) + .bind( &ConfigData::name, "name" ); + + cli["-a"]["--abort"] + .describe( "abort at first failure" ) + .bind( &abortAfterFirst ); + + cli["-x"]["--abortx"] + .describe( "abort after x failures" ) + .bind( &abortAfterX, "no. failures" ); + + cli["-w"]["--warn"] + .describe( "enable warnings" ) + .bind( &addWarning, "warning name" ); + +// - needs updating if reinstated +// cli.into( &setVerbosity ) +// .describe( "level of verbosity (0=no output)" ) +// .shortOpt( "v") +// .longOpt( "verbosity" ) +// .placeholder( "level" ); + + cli[_] + .describe( "which test or tests to use" ) + .bind( &addTestOrTags, "test name, pattern or tags" ); + + cli["-d"]["--durations"] + .describe( "show test durations" ) + .bind( &setShowDurations, "yes/no" ); + + cli["-f"]["--input-file"] + .describe( "load test names to run from a file" ) + .bind( &loadTestNamesFromFile, "filename" ); + + // Less common commands which don't have a short form + cli["--list-test-names-only"] + .describe( "list all/matching test cases names only" ) + .bind( &ConfigData::listTestNamesOnly ); + + cli["--list-reporters"] + .describe( "list all reporters" ) + .bind( &ConfigData::listReporters ); + + cli["--order"] + .describe( "test case order (defaults to decl)" ) + .bind( &setOrder, "decl|lex|rand" ); + + cli["--rng-seed"] + .describe( "set a specific seed for random numbers" ) + .bind( &setRngSeed, "'time'|number" ); + + cli["--force-colour"] + .describe( "force colourised output" ) + .bind( &ConfigData::forceColour ); + + return cli; + } + +} // end namespace Catch + +// #included from: internal/catch_list.hpp +#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED + +// #included from: catch_text.h +#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED + +#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH + +#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch +// #included from: ../external/tbc_text_format.h +// Only use header guard if we are not using an outer namespace +#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +# endif +# else +# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED +# endif +#endif +#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#include +#include +#include + +// Use optional outer namespace +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { +#endif + +namespace Tbc { + +#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH + const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; +#else + const unsigned int consoleWidth = 80; +#endif + + struct TextAttributes { + TextAttributes() + : initialIndent( std::string::npos ), + indent( 0 ), + width( consoleWidth-1 ), + tabChar( '\t' ) + {} + + TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } + TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } + TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } + TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + + std::size_t initialIndent; // indent of first line, or npos + std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos + std::size_t width; // maximum width of text, including indent. Longer text will wrap + char tabChar; // If this char is seen the indent is changed to current pos + }; + + class Text { + public: + Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) + : attr( _attr ) + { + std::string wrappableChars = " [({.,/|\\-"; + std::size_t indent = _attr.initialIndent != std::string::npos + ? _attr.initialIndent + : _attr.indent; + std::string remainder = _str; + + while( !remainder.empty() ) { + if( lines.size() >= 1000 ) { + lines.push_back( "... message truncated due to excessive size" ); + return; + } + std::size_t tabPos = std::string::npos; + std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); + std::size_t pos = remainder.find_first_of( '\n' ); + if( pos <= width ) { + width = pos; + } + pos = remainder.find_last_of( _attr.tabChar, width ); + if( pos != std::string::npos ) { + tabPos = pos; + if( remainder[width] == '\n' ) + width--; + remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); + } + + if( width == remainder.size() ) { + spliceLine( indent, remainder, width ); + } + else if( remainder[width] == '\n' ) { + spliceLine( indent, remainder, width ); + if( width <= 1 || remainder.size() != 1 ) + remainder = remainder.substr( 1 ); + indent = _attr.indent; + } + else { + pos = remainder.find_last_of( wrappableChars, width ); + if( pos != std::string::npos && pos > 0 ) { + spliceLine( indent, remainder, pos ); + if( remainder[0] == ' ' ) + remainder = remainder.substr( 1 ); + } + else { + spliceLine( indent, remainder, width-1 ); + lines.back() += "-"; + } + if( lines.size() == 1 ) + indent = _attr.indent; + if( tabPos != std::string::npos ) + indent += tabPos; + } + } + } + + void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { + lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); + _remainder = _remainder.substr( _pos ); + } + + typedef std::vector::const_iterator const_iterator; + + const_iterator begin() const { return lines.begin(); } + const_iterator end() const { return lines.end(); } + std::string const& last() const { return lines.back(); } + std::size_t size() const { return lines.size(); } + std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } + std::string toString() const { + std::ostringstream oss; + oss << *this; + return oss.str(); + } + + inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { + for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); + it != itEnd; ++it ) { + if( it != _text.begin() ) + _stream << "\n"; + _stream << *it; + } + return _stream; + } + + private: + std::string str; + TextAttributes attr; + std::vector lines; + }; + +} // end namespace Tbc + +#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE +} // end outer namespace +#endif + +#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED +#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + +namespace Catch { + using Tbc::Text; + using Tbc::TextAttributes; +} + +// #included from: catch_console_colour.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED + +namespace Catch { + + struct Colour { + enum Code { + None = 0, + + White, + Red, + Green, + Blue, + Cyan, + Yellow, + Grey, + + Bright = 0x10, + + BrightRed = Bright | Red, + BrightGreen = Bright | Green, + LightGrey = Bright | Grey, + BrightWhite = Bright | White, + + // By intention + FileName = LightGrey, + Warning = Yellow, + ResultError = BrightRed, + ResultSuccess = BrightGreen, + ResultExpectedFailure = Warning, + + Error = BrightRed, + Success = Green, + + OriginalExpression = Cyan, + ReconstructedExpression = Yellow, + + SecondaryText = LightGrey, + Headers = White + }; + + // Use constructed object for RAII guard + Colour( Code _colourCode ); + Colour( Colour const& other ); + ~Colour(); + + // Use static method for one-shot changes + static void use( Code _colourCode ); + + private: + bool m_moved; + }; + + inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + +} // end namespace Catch + +// #included from: catch_interfaces_reporter.h +#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED + +#include +#include +#include +#include + +namespace Catch +{ + struct ReporterConfig { + explicit ReporterConfig( Ptr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} + + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + + std::ostream& stream() const { return *m_stream; } + Ptr fullConfig() const { return m_fullConfig; } + + private: + std::ostream* m_stream; + Ptr m_fullConfig; + }; + + struct ReporterPreferences { + ReporterPreferences() + : shouldRedirectStdOut( false ) + {} + + bool shouldRedirectStdOut; + }; + + template + struct LazyStat : Option { + LazyStat() : used( false ) {} + LazyStat& operator=( T const& _value ) { + Option::operator=( _value ); + used = false; + return *this; + } + void reset() { + Option::reset(); + used = false; + } + bool used; + }; + + struct TestRunInfo { + TestRunInfo( std::string const& _name ) : name( _name ) {} + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} + + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; + + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); + + infoMessages.push_back( builder.m_info ); + } + } + virtual ~AssertionStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; +# endif + + AssertionResult assertionResult; + std::vector infoMessages; + Totals totals; + }; + + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} + virtual ~SectionStats(); +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; +# endif + + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; + + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} + virtual ~TestCaseStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; +# endif + + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; + + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} + TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} + virtual ~TestGroupStats(); + +# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; +# endif + + GroupInfo groupInfo; + Totals totals; + bool aborting; + }; + + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} + virtual ~TestRunStats(); + +# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS + TestRunStats( TestRunStats const& _other ) + : runInfo( _other.runInfo ), + totals( _other.totals ), + aborting( _other.aborting ) + {} +# else + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; +# endif + + TestRunInfo runInfo; + Totals totals; + bool aborting; + }; + + struct IStreamingReporter : IShared { + virtual ~IStreamingReporter(); + + // Implementing class must also provide the following static method: + // static std::string getDescription(); + + virtual ReporterPreferences getPreferences() const = 0; + + virtual void noMatchingTestCases( std::string const& spec ) = 0; + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; + }; + + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + + struct IReporterRegistry { + typedef std::map FactoryMap; + + virtual ~IReporterRegistry(); + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + }; + +} + +#include +#include + +namespace Catch { + + inline std::size_t listTests( Config const& config ) { + + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::size_t matchedTests = 0; + TextAttributes nameAttr, tagsAttr; + nameAttr.setInitialIndent( 2 ).setIndent( 4 ); + tagsAttr.setIndent( 6 ); + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); + + Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; + } + + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; + else + Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; + return matchedTests; + } + + inline std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + matchedTests++; + TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); + Catch::cout() << testCaseInfo.name << std::endl; + } + return matchedTests; + } + + struct TagInfo { + TagInfo() : count ( 0 ) {} + void add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + std::string all() const { + std::string out; + for( std::set::const_iterator it = spellings.begin(), itEnd = spellings.end(); + it != itEnd; + ++it ) + out += "[" + *it + "]"; + return out; + } + std::set spellings; + std::size_t count; + }; + + inline std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + } + + std::map tagCounts; + + std::vector matchedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); + it != itEnd; + ++it ) { + for( std::set::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), + tagItEnd = it->getTestCaseInfo().tags.end(); + tagIt != tagItEnd; + ++tagIt ) { + std::string tagName = *tagIt; + std::string lcaseTagName = toLower( tagName ); + std::map::iterator countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } + } + + for( std::map::const_iterator countIt = tagCounts.begin(), + countItEnd = tagCounts.end(); + countIt != countItEnd; + ++countIt ) { + std::ostringstream oss; + oss << " " << std::setw(2) << countIt->second.count << " "; + Text wrapper( countIt->second.all(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( oss.str().size() ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); + Catch::cout() << oss.str() << wrapper << "\n"; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; + return tagCounts.size(); + } + + inline std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; + std::size_t maxNameLen = 0; + for(it = itBegin; it != itEnd; ++it ) + maxNameLen = (std::max)( maxNameLen, it->first.size() ); + + for(it = itBegin; it != itEnd; ++it ) { + Text wrapper( it->second->getDescription(), TextAttributes() + .setInitialIndent( 0 ) + .setIndent( 7+maxNameLen ) + .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); + Catch::cout() << " " + << it->first + << ":" + << std::string( maxNameLen - it->first.size() + 2, ' ' ) + << wrapper << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } + + inline Option list( Config const& config ) { + Option listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } + +} // end namespace Catch + +// #included from: internal/catch_runner_impl.hpp +#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + +// #included from: catch_test_case_tracker.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { +namespace SectionTracking { + + class TrackedSection { + + typedef std::map TrackedSections; + + public: + enum RunState { + NotStarted, + Executing, + ExecutingChildren, + Completed + }; + + TrackedSection( std::string const& name, TrackedSection* parent ) + : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + {} + + RunState runState() const { return m_runState; } + + TrackedSection* findChild( std::string const& childName ); + TrackedSection* acquireChild( std::string const& childName ); + + void enter() { + if( m_runState == NotStarted ) + m_runState = Executing; + } + void leave(); + + TrackedSection* getParent() { + return m_parent; + } + bool hasChildren() const { + return !m_children.empty(); + } + + private: + std::string m_name; + RunState m_runState; + TrackedSections m_children; + TrackedSection* m_parent; + }; + + inline TrackedSection* TrackedSection::findChild( std::string const& childName ) { + TrackedSections::iterator it = m_children.find( childName ); + return it != m_children.end() + ? &it->second + : NULL; + } + inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { + if( TrackedSection* child = findChild( childName ) ) + return child; + m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); + return findChild( childName ); + } + inline void TrackedSection::leave() { + for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); + it != itEnd; + ++it ) + if( it->second.runState() != Completed ) { + m_runState = ExecutingChildren; + return; + } + m_runState = Completed; + } + + class TestCaseTracker { + public: + TestCaseTracker( std::string const& testCaseName ) + : m_testCase( testCaseName, NULL ), + m_currentSection( &m_testCase ), + m_completedASectionThisRun( false ) + {} + + bool enterSection( std::string const& name ) { + TrackedSection* child = m_currentSection->acquireChild( name ); + if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) + return false; + + m_currentSection = child; + m_currentSection->enter(); + return true; + } + void leaveSection() { + m_currentSection->leave(); + m_currentSection = m_currentSection->getParent(); + assert( m_currentSection != NULL ); + m_completedASectionThisRun = true; + } + + bool currentSectionHasChildren() const { + return m_currentSection->hasChildren(); + } + bool isCompleted() const { + return m_testCase.runState() == TrackedSection::Completed; + } + + class Guard { + public: + Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { + m_tracker.enterTestCase(); + } + ~Guard() { + m_tracker.leaveTestCase(); + } + private: + Guard( Guard const& ); + void operator = ( Guard const& ); + TestCaseTracker& m_tracker; + }; + + private: + void enterTestCase() { + m_currentSection = &m_testCase; + m_completedASectionThisRun = false; + m_testCase.enter(); + } + void leaveTestCase() { + m_testCase.leave(); + } + + TrackedSection m_testCase; + TrackedSection* m_currentSection; + bool m_completedASectionThisRun; + }; + +} // namespace SectionTracking + +using SectionTracking::TestCaseTracker; + +} // namespace Catch + +// #included from: catch_fatal_condition.hpp +#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED + +namespace Catch { + + // Report the error condition then exit the process + inline void fatal( std::string const& message, int exitCode ) { + IContext& context = Catch::getCurrentContext(); + IResultCapture* resultCapture = context.getResultCapture(); + resultCapture->handleFatalErrorCondition( message ); + + if( Catch::alwaysTrue() ) // avoids "no return" warnings + exit( exitCode ); + } + +} // namespace Catch + +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + +namespace Catch { + + struct FatalConditionHandler { + void reset() {} + }; + +} // namespace Catch + +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// + +#include + +namespace Catch { + + struct SignalDefs { int id; const char* name; }; + extern SignalDefs signalDefs[]; + SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; + + struct FatalConditionHandler { + + static void handleSignal( int sig ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + if( sig == signalDefs[i].id ) + fatal( signalDefs[i].name, -sig ); + fatal( "", -sig ); + } + + FatalConditionHandler() : m_isSet( true ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, handleSignal ); + } + ~FatalConditionHandler() { + reset(); + } + void reset() { + if( m_isSet ) { + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) + signal( signalDefs[i].id, SIG_DFL ); + m_isSet = false; + } + } + + bool m_isSet; + }; + +} // namespace Catch + +#endif // not Windows + +#include +#include + +namespace Catch { + + class StreamRedirect { + + public: + StreamRedirect( std::ostream& stream, std::string& targetString ) + : m_stream( stream ), + m_prevBuf( stream.rdbuf() ), + m_targetString( targetString ) + { + stream.rdbuf( m_oss.rdbuf() ); + } + + ~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf( m_prevBuf ); + } + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + RunContext( RunContext const& ); + void operator =( RunContext const& ); + + public: + + explicit RunContext( Ptr const& config, Ptr const& reporter ) + : m_runInfo( config->name() ), + m_context( getCurrentMutableContext() ), + m_activeTestCase( NULL ), + m_config( config ), + m_reporter( reporter ), + m_prevRunner( m_context.getRunner() ), + m_prevResultCapture( m_context.getResultCapture() ), + m_prevConfig( m_context.getConfig() ) + { + m_context.setRunner( this ); + m_context.setConfig( m_config ); + m_context.setResultCapture( this ); + m_reporter->testRunStarting( m_runInfo ); + } + + virtual ~RunContext() { + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); + m_context.setRunner( m_prevRunner ); + m_context.setConfig( NULL ); + m_context.setResultCapture( m_prevResultCapture ); + m_context.setConfig( m_prevConfig ); + } + + void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + } + void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { + m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); + } + + Totals runTest( TestCase const& testCase ) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting( testInfo ); + + m_activeTestCase = &testCase; + m_testCaseTracker = TestCaseTracker( testInfo.name ); + + do { + do { + runCurrentTest( redirectedCout, redirectedCerr ); + } + while( !m_testCaseTracker->isCompleted() && !aborting() ); + } + while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + + Totals deltaTotals = m_totals.delta( prevTotals ); + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting() ) ); + + m_activeTestCase = NULL; + m_testCaseTracker.reset(); + + return deltaTotals; + } + + Ptr config() const { + return m_config; + } + + private: // IResultCapture + + virtual void assertionEnded( AssertionResult const& result ) { + if( result.getResultType() == ResultWas::Ok ) { + m_totals.assertions.passed++; + } + else if( !result.isOk() ) { + m_totals.assertions.failed++; + } + + if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) + m_messages.clear(); + + // Reset working state + m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); + m_lastResult = result; + } + + virtual bool sectionStarted ( + SectionInfo const& sectionInfo, + Counts& assertions + ) + { + std::ostringstream oss; + oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + + if( !m_testCaseTracker->enterSection( oss.str() ) ) + return false; + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting( sectionInfo ); + + assertions = m_totals.assertions; + + return true; + } + bool testForMissingAssertions( Counts& assertions ) { + if( assertions.total() != 0 || + !m_config->warnAboutMissingAssertions() || + m_testCaseTracker->currentSectionHasChildren() ) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { + if( std::uncaught_exception() ) { + m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); + return; + } + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + m_testCaseTracker->leaveSection(); + + m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_messages.clear(); + } + + virtual void pushScopedMessage( MessageInfo const& message ) { + m_messages.push_back( message ); + } + + virtual void popScopedMessage( MessageInfo const& message ) { + m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); + } + + virtual std::string getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : ""; + } + + virtual const AssertionResult* getLastResult() const { + return &m_lastResult; + } + + virtual void handleFatalErrorCondition( std::string const& message ) { + ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); + resultBuilder.setResultType( ResultWas::FatalErrorCondition ); + resultBuilder << message; + resultBuilder.captureExpression(); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); + m_reporter->sectionEnded( testCaseSectionStats ); + + TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + m_reporter->testCaseEnded( TestCaseStats( testInfo, + deltaTotals, + "", + "", + false ) ); + m_totals.testCases.failed++; + testGroupEnded( "", m_totals, 1, 1 ); + m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); + } + + public: + // !TBD We need to do this another way! + bool aborting() const { + return m_totals.assertions.failed == static_cast( m_config->abortAfter() ); + } + + private: + + void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { + TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + m_reporter->sectionStarting( testCaseSection ); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + try { + m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); + TestCaseTracker::Guard guard( *m_testCaseTracker ); + + Timer timer; + timer.start(); + if( m_reporter->getPreferences().shouldRedirectStdOut ) { + StreamRedirect coutRedir( Catch::cout(), redirectedCout ); + StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); + invokeActiveTestCase(); + } + else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } + catch( TestFailureException& ) { + // This just means the test was aborted due to failure + } + catch(...) { + makeUnexpectedResultBuilder().useActiveException(); + } + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions( assertions ); + + if( testCaseInfo.okToFail() ) { + std::swap( assertions.failedButOk, assertions.failed ); + m_totals.assertions.failed -= assertions.failedButOk; + m_totals.assertions.failedButOk += assertions.failedButOk; + } + + SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); + m_reporter->sectionEnded( testCaseSectionStats ); + } + + void invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + private: + + ResultBuilder makeUnexpectedResultBuilder() const { + return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression.c_str(), + m_lastAssertionInfo.resultDisposition ); + } + + void handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it ) + sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + m_unfinishedSections.clear(); + } + + struct UnfinishedSections { + UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) + : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo info; + Counts prevAssertions; + double durationInSeconds; + }; + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase; + Option m_testCaseTracker; + AssertionResult m_lastResult; + + Ptr m_config; + Totals m_totals; + Ptr m_reporter; + std::vector m_messages; + IRunner* m_prevRunner; + IResultCapture* m_prevResultCapture; + Ptr m_prevConfig; + AssertionInfo m_lastAssertionInfo; + std::vector m_unfinishedSections; + }; + + IResultCapture& getResultCapture() { + if( IResultCapture* capture = getCurrentContext().getResultCapture() ) + return *capture; + else + throw std::logic_error( "No result capture instance" ); + } + +} // end namespace Catch + +// #included from: internal/catch_version.h +#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED + +namespace Catch { + + // Versioning information + struct Version { + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + std::string const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + + private: + void operator=( Version const& ); + }; + + extern Version libraryVersion; +} + +#include +#include +#include + +namespace Catch { + + class Runner { + + public: + Runner( Ptr const& config ) + : m_config( config ) + { + openStream(); + makeReporter(); + } + + Totals runTests() { + + RunContext context( m_config.get(), m_reporter ); + + Totals totals; + + context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + + TestSpec testSpec = m_config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector testCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + + int testsRunForGroup = 0; + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) { + testsRunForGroup++; + if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + + if( context.aborting() ) + break; + + totals += context.runTest( *it ); + m_testsAlreadyRun.insert( *it ); + } + } + std::vector skippedTestCases; + getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); + + for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); + it != itEnd; + ++it ) + m_reporter->skipTest( *it ); + + context.testGroupEnded( "all tests", totals, 1, 1 ); + return totals; + } + + private: + void openStream() { + // Open output file, if specified + if( !m_config->getFilename().empty() ) { + m_ofs.open( m_config->getFilename().c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << m_config->getFilename() << "'"; + throw std::domain_error( oss.str() ); + } + m_config->setStreamBuf( m_ofs.rdbuf() ); + } + } + void makeReporter() { + std::string reporterName = m_config->getReporterName().empty() + ? "console" + : m_config->getReporterName(); + + m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); + if( !m_reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + } + + private: + Ptr m_config; + std::ofstream m_ofs; + Ptr m_reporter; + std::set m_testsAlreadyRun; + }; + + class Session : NonCopyable { + static bool alreadyInstantiated; + + public: + + struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + + Session() + : m_cli( makeCommandLineParser() ) { + if( alreadyInstantiated ) { + std::string msg = "Only one instance of Catch::Session can ever be used"; + Catch::cerr() << msg << std::endl; + throw std::logic_error( msg ); + } + alreadyInstantiated = true; + } + ~Session() { + Catch::cleanUp(); + } + + void showHelp( std::string const& processName ) { + Catch::cout() << "\nCatch v" << libraryVersion << "\n"; + + m_cli.usage( Catch::cout(), processName ); + Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + } + + int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + try { + m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); + m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); + if( m_configData.showHelp ) + showHelp( m_configData.processName ); + m_config.reset(); + } + catch( std::exception& ex ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "\nError(s) in input:\n" + << Text( ex.what(), TextAttributes().setIndent(2) ) + << "\n\n"; + } + m_cli.usage( Catch::cout(), m_configData.processName ); + return (std::numeric_limits::max)(); + } + return 0; + } + + void useConfigData( ConfigData const& _configData ) { + m_configData = _configData; + m_config.reset(); + } + + int run( int argc, char* const argv[] ) { + + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + + int run() { + if( m_configData.showHelp ) + return 0; + + try + { + config(); // Force config to be constructed + + std::srand( m_configData.rngSeed ); + + Runner runner( m_config ); + + // Handle list request + if( Option listed = list( config() ) ) + return static_cast( *listed ); + + return static_cast( runner.runTests().assertions.failed ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return (std::numeric_limits::max)(); + } + } + + Clara::CommandLine const& cli() const { + return m_cli; + } + std::vector const& unusedTokens() const { + return m_unusedTokens; + } + ConfigData& configData() { + return m_configData; + } + Config& config() { + if( !m_config ) + m_config = new Config( m_configData ); + return *m_config; + } + + private: + Clara::CommandLine m_cli; + std::vector m_unusedTokens; + ConfigData m_configData; + Ptr m_config; + }; + + bool Session::alreadyInstantiated = false; + +} // end namespace Catch + +// #included from: catch_registry_hub.hpp +#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + +// #included from: catch_test_case_registry_impl.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace Catch { + + class TestRegistry : public ITestCaseRegistry { + struct LexSort { + bool operator() (TestCase i,TestCase j) const { return (i const& getAllTests() const { + return m_functionsInOrder; + } + + virtual std::vector const& getAllNonHiddenTests() const { + return m_nonHiddenFunctions; + } + + virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { + + for( std::vector::const_iterator it = m_functionsInOrder.begin(), + itEnd = m_functionsInOrder.end(); + it != itEnd; + ++it ) { + bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); + if( includeTest != negated ) + matchingTestCases.push_back( *it ); + } + sortTests( config, matchingTestCases ); + } + + private: + + static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + RandomNumberGenerator rng; + std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + } + std::set m_functions; + std::vector m_functionsInOrder; + std::vector m_nonHiddenFunctions; + size_t m_unnamedCount; + }; + + /////////////////////////////////////////////////////////////////////////// + + class FreeFunctionTestCase : public SharedImpl { + public: + + FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + + virtual void invoke() const { + m_fun(); + } + + private: + virtual ~FreeFunctionTestCase(); + + TestFunction m_fun; + }; + + inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, "&" ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + + /////////////////////////////////////////////////////////////////////////// + + AutoReg::AutoReg( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); + } + + AutoReg::~AutoReg() {} + + void AutoReg::registerTestCase( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { + + getMutableRegistryHub().registerTest + ( makeTestCase( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + +} // end namespace Catch + +// #included from: catch_reporter_registry.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + +#include + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + virtual ~ReporterRegistry() { + deleteAllValues( m_factories ); + } + + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + FactoryMap::const_iterator it = m_factories.find( name ); + if( it == m_factories.end() ) + return NULL; + return it->second->create( ReporterConfig( config ) ); + } + + void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_factories.insert( std::make_pair( name, factory ) ); + } + + FactoryMap const& getFactories() const { + return m_factories; + } + + private: + FactoryMap m_factories; + }; +} + +// #included from: catch_exception_translator_registry.hpp +#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + +#ifdef __OBJC__ +#import "Foundation/Foundation.h" +#endif + +namespace Catch { + + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry() { + deleteAll( m_translators ); + } + + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( translator ); + } + + virtual std::string translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + throw; + } + @catch (NSException *exception) { + return Catch::toString( [exception description] ); + } +#else + throw; +#endif + } + catch( TestFailureException& ) { + throw; + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return tryTranslators( m_translators.begin() ); + } + } + + std::string tryTranslators( std::vector::const_iterator it ) const { + if( it == m_translators.end() ) + return "Unknown exception"; + + try { + return (*it)->translate(); + } + catch(...) { + return tryTranslators( it+1 ); + } + } + + private: + std::vector m_translators; + }; +} + +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + + RegistryHub( RegistryHub const& ); + void operator=( RegistryHub const& ); + + public: // IRegistryHub + RegistryHub() { + } + virtual IReporterRegistry const& getReporterRegistry() const { + return m_reporterRegistry; + } + virtual ITestCaseRegistry const& getTestCaseRegistry() const { + return m_testCaseRegistry; + } + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + return m_exceptionTranslatorRegistry; + } + + public: // IMutableRegistryHub + virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + m_reporterRegistry.registerReporter( name, factory ); + } + virtual void registerTest( TestCase const& testInfo ) { + m_testCaseRegistry.registerTest( testInfo ); + } + virtual void registerTranslator( const IExceptionTranslator* translator ) { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + }; + + // Single, global, instance + inline RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = NULL; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = NULL; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch + +// #included from: catch_notimplemented_exception.hpp +#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + +#include + +namespace Catch { + + NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) + : m_lineInfo( lineInfo ) { + std::ostringstream oss; + oss << lineInfo << ": function "; + oss << "not implemented"; + m_what = oss.str(); + } + + const char* NotImplementedException::what() const CATCH_NOEXCEPT { + return m_what.c_str(); + } + +} // end namespace Catch + +// #included from: catch_context_impl.hpp +#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + +// #included from: catch_stream.hpp +#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + +#include + +namespace Catch { + + class StreamBufBase : public std::streambuf { + public: + virtual ~StreamBufBase() CATCH_NOEXCEPT; + }; +} + +#include +#include +#include + +namespace Catch { + + template + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() CATCH_NOEXCEPT { + sync(); + } + + private: + int overflow( int c ) { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast( c ) ) ); + else + sputc( static_cast( c ) ); + } + return 0; + } + + int sync() { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + Stream::Stream() + : streamBuf( NULL ), isOwned( false ) + {} + + Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) + : streamBuf( _streamBuf ), isOwned( _isOwned ) + {} + + void Stream::release() { + if( isOwned ) { + delete streamBuf; + streamBuf = NULL; + isOwned = false; + } + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } +#endif +} + +namespace Catch { + + class Context : public IMutableContext { + + Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context( Context const& ); + void operator=( Context const& ); + + public: // IContext + virtual IResultCapture* getResultCapture() { + return m_resultCapture; + } + virtual IRunner* getRunner() { + return m_runner; + } + virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { + return getGeneratorsForCurrentTest() + .getGeneratorInfo( fileInfo, totalSize ) + .getCurrentIndex(); + } + virtual bool advanceGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + return generators && generators->moveNext(); + } + + virtual Ptr getConfig() const { + return m_config; + } + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) { + m_resultCapture = resultCapture; + } + virtual void setRunner( IRunner* runner ) { + m_runner = runner; + } + virtual void setConfig( Ptr const& config ) { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IGeneratorsForTest* findGeneratorsForCurrentTest() { + std::string testName = getResultCapture()->getCurrentTestName(); + + std::map::const_iterator it = + m_generatorsByTestName.find( testName ); + return it != m_generatorsByTestName.end() + ? it->second + : NULL; + } + + IGeneratorsForTest& getGeneratorsForCurrentTest() { + IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); + if( !generators ) { + std::string testName = getResultCapture()->getCurrentTestName(); + generators = createGeneratorsForTest(); + m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); + } + return *generators; + } + + private: + Ptr m_config; + IRunner* m_runner; + IResultCapture* m_resultCapture; + std::map m_generatorsByTestName; + }; + + namespace { + Context* currentContext = NULL; + } + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + Stream createStream( std::string const& streamName ) { + if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); + if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); + if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); + + throw std::domain_error( "Unknown stream: " + streamName ); + } + + void cleanUpContext() { + delete currentContext; + currentContext = NULL; + } +} + +// #included from: catch_console_colour_impl.hpp +#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + +namespace Catch { + namespace { + + struct IColourImpl { + virtual ~IColourImpl() {} + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace +} // namespace Catch + +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif +#endif + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifdef __AFXDLL +#include +#else +#include +#endif + +namespace Catch { +namespace { + + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalAttributes = csbiInfo.wAttributes; + } + + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); + + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + } + HANDLE stdoutHandle; + WORD originalAttributes; + }; + + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; + return &s_instance; + } + +} // end anon namespace +} // end namespace Catch + +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + +#include + +namespace Catch { +namespace { + + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0:34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); + + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); + + case Colour::Bright: throw std::logic_error( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } + + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + IColourImpl* platformColourInstance() { + Ptr config = getCurrentContext().getConfig(); + return (config && config->forceColour()) || isatty(STDOUT_FILENO) + ? PosixColourImpl::instance() + : NoColourImpl::instance(); + } + +} // end anon namespace +} // end namespace Catch + +#else // not Windows or ANSI /////////////////////////////////////////////// + +namespace Catch { + + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + +} // end namespace Catch + +#endif // Windows/ ANSI/ None + +namespace Catch { + + Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } + Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast( _other ).m_moved = true; } + Colour::~Colour(){ if( !m_moved ) use( None ); } + + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = isDebuggerActive() + ? NoColourImpl::instance() + : platformColourInstance(); + impl->use( _colourCode ); + } + +} // end namespace Catch + +// #included from: catch_generators_impl.hpp +#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + struct GeneratorInfo : IGeneratorInfo { + + GeneratorInfo( std::size_t size ) + : m_size( size ), + m_currentIndex( 0 ) + {} + + bool moveNext() { + if( ++m_currentIndex == m_size ) { + m_currentIndex = 0; + return false; + } + return true; + } + + std::size_t getCurrentIndex() const { + return m_currentIndex; + } + + std::size_t m_size; + std::size_t m_currentIndex; + }; + + /////////////////////////////////////////////////////////////////////////// + + class GeneratorsForTest : public IGeneratorsForTest { + + public: + ~GeneratorsForTest() { + deleteAll( m_generatorsInOrder ); + } + + IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { + std::map::const_iterator it = m_generatorsByName.find( fileInfo ); + if( it == m_generatorsByName.end() ) { + IGeneratorInfo* info = new GeneratorInfo( size ); + m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); + m_generatorsInOrder.push_back( info ); + return *info; + } + return *it->second; + } + + bool moveNext() { + std::vector::const_iterator it = m_generatorsInOrder.begin(); + std::vector::const_iterator itEnd = m_generatorsInOrder.end(); + for(; it != itEnd; ++it ) { + if( (*it)->moveNext() ) + return true; + } + return false; + } + + private: + std::map m_generatorsByName; + std::vector m_generatorsInOrder; + }; + + IGeneratorsForTest* createGeneratorsForTest() + { + return new GeneratorsForTest(); + } + +} // end namespace Catch + +// #included from: catch_assertionresult.hpp +#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED + +namespace Catch { + + AssertionInfo::AssertionInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + std::string const& _capturedExpression, + ResultDisposition::Flags _resultDisposition ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + capturedExpression( _capturedExpression ), + resultDisposition( _resultDisposition ) + {} + + AssertionResult::AssertionResult() {} + + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} + + AssertionResult::~AssertionResult() {} + + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } + + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } + + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } + + bool AssertionResult::hasExpression() const { + return !m_info.capturedExpression.empty(); + } + + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } + + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!" + m_info.capturedExpression; + else + return m_info.capturedExpression; + } + std::string AssertionResult::getExpressionInMacro() const { + if( m_info.macroName.empty() ) + return m_info.capturedExpression; + else + return m_info.macroName + "( " + m_info.capturedExpression + " )"; + } + + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } + + std::string AssertionResult::getExpandedExpression() const { + return m_resultData.reconstructedExpression; + } + + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } + + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } + +} // end namespace Catch + +// #included from: catch_test_case_info.hpp +#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED + +namespace Catch { + + inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, "." ) || + tag == "hide" || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else + return TestCaseInfo::None; + } + inline bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); + } + inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + if( isReservedTag( tag ) ) { + { + Colour colourGuard( Colour::Red ); + Catch::cerr() + << "Tag name [" << tag << "] not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n"; + } + { + Colour colourGuard( Colour::FileName ); + Catch::cerr() << _lineInfo << std::endl; + } + exit(1); + } + } + + TestCase makeTestCase( ITestCase* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden( startsWith( _name, "./" ) ); // Legacy support + + // Parse out tags + std::set tags; + std::string desc, tag; + bool inTag = false; + for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { + char c = _descOrTags[i]; + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( prop == TestCaseInfo::IsHidden ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.insert( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.insert( "hide" ); + tags.insert( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::set const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + tags( _tags ), + lineInfo( _lineInfo ), + properties( None ) + { + std::ostringstream oss; + for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); + lcaseTags.insert( lcaseTag ); + } + tagsAsString = oss.str(); + } + + TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) + : name( other.name ), + className( other.className ), + description( other.description ), + tags( other.tags ), + lcaseTags( other.lcaseTags ), + tagsAsString( other.tagsAsString ), + lineInfo( other.lineInfo ), + properties( other.properties ) + {} + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase::TestCase( TestCase const& other ) + : TestCaseInfo( other ), + test( other.test ) + {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::swap( TestCase& other ) { + test.swap( other.test ); + name.swap( other.name ); + className.swap( other.className ); + description.swap( other.description ); + tags.swap( other.tags ); + lcaseTags.swap( other.lcaseTags ); + tagsAsString.swap( other.tagsAsString ); + std::swap( TestCaseInfo::properties, static_cast( other ).properties ); + std::swap( lineInfo, other.lineInfo ); + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + TestCase& TestCase::operator = ( TestCase const& other ) { + TestCase temp( other ); + swap( temp ); + return *this; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch + +// #included from: catch_version.hpp +#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + std::string const& _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << "." + << version.minorVersion << "." + << version.patchNumber; + + if( !version.branchName.empty() ) { + os << "-" << version.branchName + << "." << version.buildNumber; + } + return os; + } + + Version libraryVersion( 1, 2, 1, "", 0 ); + +} + +// #included from: catch_message.hpp +#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + ScopedMessage::ScopedMessage( ScopedMessage const& other ) + : m_info( other.m_info ) + {} + + ScopedMessage::~ScopedMessage() { + getResultCapture().popScopedMessage( m_info ); + } + +} // end namespace Catch + +// #included from: catch_legacy_reporter_adapter.hpp +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED + +// #included from: catch_legacy_reporter_adapter.h +#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED + +namespace Catch +{ + // Deprecated + struct IReporter : IShared { + virtual ~IReporter(); + + virtual bool shouldRedirectStdout() const = 0; + + virtual void StartTesting() = 0; + virtual void EndTesting( Totals const& totals ) = 0; + virtual void StartGroup( std::string const& groupName ) = 0; + virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; + virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; + virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; + virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; + virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; + virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; + virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; + virtual void Aborted() = 0; + virtual void Result( AssertionResult const& result ) = 0; + }; + + class LegacyReporterAdapter : public SharedImpl + { + public: + LegacyReporterAdapter( Ptr const& legacyReporter ); + virtual ~LegacyReporterAdapter(); + + virtual ReporterPreferences getPreferences() const; + virtual void noMatchingTestCases( std::string const& ); + virtual void testRunStarting( TestRunInfo const& ); + virtual void testGroupStarting( GroupInfo const& groupInfo ); + virtual void testCaseStarting( TestCaseInfo const& testInfo ); + virtual void sectionStarting( SectionInfo const& sectionInfo ); + virtual void assertionStarting( AssertionInfo const& ); + virtual bool assertionEnded( AssertionStats const& assertionStats ); + virtual void sectionEnded( SectionStats const& sectionStats ); + virtual void testCaseEnded( TestCaseStats const& testCaseStats ); + virtual void testGroupEnded( TestGroupStats const& testGroupStats ); + virtual void testRunEnded( TestRunStats const& testRunStats ); + virtual void skipTest( TestCaseInfo const& ); + + private: + Ptr m_legacyReporter; + }; +} + +namespace Catch +{ + LegacyReporterAdapter::LegacyReporterAdapter( Ptr const& legacyReporter ) + : m_legacyReporter( legacyReporter ) + {} + LegacyReporterAdapter::~LegacyReporterAdapter() {} + + ReporterPreferences LegacyReporterAdapter::getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); + return prefs; + } + + void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} + void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { + m_legacyReporter->StartTesting(); + } + void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { + m_legacyReporter->StartGroup( groupInfo.name ); + } + void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { + m_legacyReporter->StartTestCase( testInfo ); + } + void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { + m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); + } + void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { + // Not on legacy interface + } + + bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); + rb << it->message; + rb.setResultType( ResultWas::Info ); + AssertionResult result = rb.build(); + m_legacyReporter->Result( result ); + } + } + } + m_legacyReporter->Result( assertionStats.assertionResult ); + return true; + } + void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { + if( sectionStats.missingAssertions ) + m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); + m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); + } + void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { + m_legacyReporter->EndTestCase + ( testCaseStats.testInfo, + testCaseStats.totals, + testCaseStats.stdOut, + testCaseStats.stdErr ); + } + void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { + if( testGroupStats.aborting ) + m_legacyReporter->Aborted(); + m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); + } + void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { + m_legacyReporter->EndTesting( testRunStats.totals ); + } + void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { + } +} + +// #included from: catch_timer.hpp + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wc++11-long-long" +#endif + +#ifdef CATCH_PLATFORM_WINDOWS +#include +#else +#include +#endif + +namespace Catch { + + namespace { +#ifdef CATCH_PLATFORM_WINDOWS + uint64_t getCurrentTicks() { + static uint64_t hz=0, hzo=0; + if (!hz) { + QueryPerformanceFrequency( reinterpret_cast( &hz ) ); + QueryPerformanceCounter( reinterpret_cast( &hzo ) ); + } + uint64_t t; + QueryPerformanceCounter( reinterpret_cast( &t ) ); + return ((t-hzo)*1000000)/hz; + } +#else + uint64_t getCurrentTicks() { + timeval t; + gettimeofday(&t,NULL); + return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); + } +#endif + } + + void Timer::start() { + m_ticks = getCurrentTicks(); + } + unsigned int Timer::getElapsedMicroseconds() const { + return static_cast(getCurrentTicks() - m_ticks); + } + unsigned int Timer::getElapsedMilliseconds() const { + return static_cast(getElapsedMicroseconds()/1000); + } + double Timer::getElapsedSeconds() const { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +// #included from: catch_common.hpp +#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), ::tolower ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << " " << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << "s"; + return os; + } + + SourceLineInfo::SourceLineInfo() : line( 0 ){} + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) + : file( _file ), + line( _line ) + {} + SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) + : file( other.file ), + line( other.line ) + {} + bool SourceLineInfo::empty() const { + return file.empty(); + } + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { + return line == other.line && file == other.file; + } + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { + return line < other.line || ( line == other.line && file < other.file ); + } + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << "(" << info.line << ")"; +#else + os << info.file << ":" << info.line; +#endif + return os; + } + + void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { + std::ostringstream oss; + oss << locationInfo << ": Internal Catch error: '" << message << "'"; + if( alwaysTrue() ) + throw std::logic_error( oss.str() ); + } +} + +// #included from: catch_section.hpp +#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + + Section::~Section() { + if( m_sectionIncluded ) + getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + } + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch + +// #included from: catch_debugger.hpp +#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED + +#include + +#ifdef CATCH_PLATFORM_MAC + + #include + #include + #include + #include + #include + + namespace Catch{ + + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html + + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ + + int mib[4]; + struct kinfo_proc info; + size_t size; + + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. + + info.kp_proc.p_flag = 0; + + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. + + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); + + // Call sysctl. + + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } + + // We're being debugged if the P_TRACED flag is set. + + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch + +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + inline bool isDebuggerActive() { return false; } + } +#endif // Platform + +#ifdef CATCH_PLATFORM_WINDOWS + extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + ::OutputDebugStringA( text.c_str() ); + } + } +#else + namespace Catch { + void writeToDebugConsole( std::string const& text ) { + // !TBD: Need a version for Mac/ XCode and other IDEs + Catch::cout() << text; + } + } +#endif // Platform + +// #included from: catch_tostring.hpp +#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED + +namespace Catch { + +namespace Detail { + + std::string unprintableString = "{?}"; + + namespace { + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) + { + // Reverse order for little endian architectures + int i = 0, end = static_cast( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast(bytes[i]); + return os.str(); + } +} + +std::string toString( std::string const& value ) { + std::string s = value; + if( getCurrentContext().getConfig()->showInvisibles() ) { + for(size_t i = 0; i < s.size(); ++i ) { + std::string subs; + switch( s[i] ) { + case '\n': subs = "\\n"; break; + case '\t': subs = "\\t"; break; + default: break; + } + if( !subs.empty() ) { + s = s.substr( 0, i ) + subs + s.substr( i+1 ); + ++i; + } + } + } + return "\"" + s + "\""; +} +std::string toString( std::wstring const& value ) { + + std::string s; + s.reserve( value.size() ); + for(size_t i = 0; i < value.size(); ++i ) + s += value[i] <= 0xff ? static_cast( value[i] ) : '?'; + return Catch::toString( s ); +} + +std::string toString( const char* const value ) { + return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); +} + +std::string toString( char* const value ) { + return Catch::toString( static_cast( value ) ); +} + +std::string toString( const wchar_t* const value ) +{ + return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); +} + +std::string toString( wchar_t* const value ) +{ + return Catch::toString( static_cast( value ) ); +} + +std::string toString( int value ) { + std::ostringstream oss; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned long value ) { + std::ostringstream oss; + oss << value; + if( value >= 255 ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} + +std::string toString( unsigned int value ) { + return Catch::toString( static_cast( value ) ); +} + +template +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +std::string toString( const double value ) { + return fpToString( value, 10 ); +} +std::string toString( const float value ) { + return fpToString( value, 5 ) + "f"; +} + +std::string toString( bool value ) { + return value ? "true" : "false"; +} + +std::string toString( char value ) { + return value < ' ' + ? toString( static_cast( value ) ) + : Detail::makeString( value ); +} + +std::string toString( signed char value ) { + return toString( static_cast( value ) ); +} + +std::string toString( unsigned char value ) { + return toString( static_cast( value ) ); +} + +#ifdef CATCH_CONFIG_CPP11_NULLPTR +std::string toString( std::nullptr_t ) { + return "nullptr"; +} +#endif + +#ifdef __OBJC__ + std::string toString( NSString const * const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { + if( !nsstring ) + return "nil"; + return "@" + toString([nsstring UTF8String]); + } + std::string toString( NSObject* const& nsObject ) { + return toString( [nsObject description] ); + } +#endif + +} // end namespace Catch + +// #included from: catch_result_builder.hpp +#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED + +namespace Catch { + + ResultBuilder::ResultBuilder( char const* macroName, + SourceLineInfo const& lineInfo, + char const* capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + m_shouldDebugBreak( false ), + m_shouldThrow( false ) + {} + + ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { + m_data.resultType = result; + return *this; + } + ResultBuilder& ResultBuilder::setResultType( bool result ) { + m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; + return *this; + } + ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { + m_exprComponents.lhs = lhs; + return *this; + } + ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { + m_exprComponents.rhs = rhs; + return *this; + } + ResultBuilder& ResultBuilder::setOp( std::string const& op ) { + m_exprComponents.op = op; + return *this; + } + + void ResultBuilder::endExpression() { + m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); + captureExpression(); + } + + void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { + m_assertionInfo.resultDisposition = resultDisposition; + m_stream.oss << Catch::translateActiveException(); + captureResult( ResultWas::ThrewException ); + } + + void ResultBuilder::captureResult( ResultWas::OfType resultType ) { + setResultType( resultType ); + captureExpression(); + } + + void ResultBuilder::captureExpression() { + AssertionResult result = build(); + getResultCapture().assertionEnded( result ); + + if( !result.isOk() ) { + if( getCurrentContext().getConfig()->shouldDebugBreak() ) + m_shouldDebugBreak = true; + if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) + m_shouldThrow = true; + } + } + void ResultBuilder::react() { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } + + bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } + bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } + + AssertionResult ResultBuilder::build() const + { + assert( m_data.resultType != ResultWas::Unknown ); + + AssertionResultData data = m_data; + + // Flip bool results if testFalse is set + if( m_exprComponents.testFalse ) { + if( data.resultType == ResultWas::Ok ) + data.resultType = ResultWas::ExpressionFailed; + else if( data.resultType == ResultWas::ExpressionFailed ) + data.resultType = ResultWas::Ok; + } + + data.message = m_stream.oss.str(); + data.reconstructedExpression = reconstructExpression(); + if( m_exprComponents.testFalse ) { + if( m_exprComponents.op == "" ) + data.reconstructedExpression = "!" + data.reconstructedExpression; + else + data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; + } + return AssertionResult( m_assertionInfo, data ); + } + std::string ResultBuilder::reconstructExpression() const { + if( m_exprComponents.op == "" ) + return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; + else if( m_exprComponents.op == "matches" ) + return m_exprComponents.lhs + " " + m_exprComponents.rhs; + else if( m_exprComponents.op != "!" ) { + if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && + m_exprComponents.lhs.find("\n") == std::string::npos && + m_exprComponents.rhs.find("\n") == std::string::npos ) + return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; + else + return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; + } + else + return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; + } + +} // end namespace Catch + +// #included from: catch_tag_alias_registry.hpp +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + +// #included from: catch_tag_alias_registry.h +#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + +#include + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + virtual ~TagAliasRegistry(); + virtual Option find( std::string const& alias ) const; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; + void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + static TagAliasRegistry& get(); + + private: + std::map m_registry; + }; + +} // end namespace Catch + +#include +#include + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + Option TagAliasRegistry::find( std::string const& alias ) const { + std::map::const_iterator it = m_registry.find( alias ); + if( it != m_registry.end() ) + return it->second; + else + return Option(); + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( std::map::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); + it != itEnd; + ++it ) { + std::size_t pos = expandedTestSpec.find( it->first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + it->second.tag + + expandedTestSpec.substr( pos + it->first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + + if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { + std::ostringstream oss; + oss << "error: tag alias, \"" << alias << "\" already registered.\n" + << "\tFirst seen at " << find(alias)->lineInfo << "\n" + << "\tRedefined at " << lineInfo; + throw std::domain_error( oss.str().c_str() ); + } + } + + TagAliasRegistry& TagAliasRegistry::get() { + static TagAliasRegistry instance; + return instance; + + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } + + RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { + try { + TagAliasRegistry::get().add( alias, tag, lineInfo ); + } + catch( std::exception& ex ) { + Colour colourGuard( Colour::Red ); + Catch::cerr() << ex.what() << std::endl; + exit(1); + } + } + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_xml.hpp +#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED + +// #included from: catch_reporter_bases.hpp +#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED + +#include + +namespace Catch { + + struct StreamingReporterBase : SharedImpl { + + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + + virtual ~StreamingReporterBase(); + + virtual void noMatchingTestCases( std::string const& ) {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + currentTestRunInfo = _testRunInfo; + } + virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + currentGroupInfo = _groupInfo; + } + + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + currentTestCaseInfo = _testInfo; + } + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_sectionStack.push_back( _sectionInfo ); + } + + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + currentTestCaseInfo.reset(); + } + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + currentGroupInfo.reset(); + } + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); + } + + virtual void skipTest( TestCaseInfo const& ) { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. + } + + Ptr m_config; + std::ostream& stream; + + LazyStat currentTestRunInfo; + LazyStat currentGroupInfo; + LazyStat currentTestCaseInfo; + + std::vector m_sectionStack; + }; + + struct CumulativeReporterBase : SharedImpl { + template + struct Node : SharedImpl<> { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} + + typedef std::vector > ChildNodes; + T value; + ChildNodes children; + }; + struct SectionNode : SharedImpl<> { + explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} + virtual ~SectionNode(); + + bool operator == ( SectionNode const& other ) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == ( Ptr const& other ) const { + return operator==( *other ); + } + + SectionStats stats; + typedef std::vector > ChildSections; + typedef std::vector Assertions; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; + + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() ( Ptr const& node ) const { + return node->stats.sectionInfo.lineInfo == m_other.lineInfo; + } + private: + void operator=( BySectionInfo const& ); + SectionInfo const& m_other; + }; + + typedef Node TestCaseNode; + typedef Node TestGroupNode; + typedef Node TestRunNode; + + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + {} + ~CumulativeReporterBase(); + + virtual void testRunStarting( TestRunInfo const& ) {} + virtual void testGroupStarting( GroupInfo const& ) {} + + virtual void testCaseStarting( TestCaseInfo const& ) {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + Ptr node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = new SectionNode( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + SectionNode::ChildSections::const_iterator it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = new SectionNode( incompleteStats ); + parentNode.childSections.push_back( node ); + } + else + node = *it; + } + m_sectionStack.push_back( node ); + m_deepestSection = node; + } + + virtual void assertionStarting( AssertionInfo const& ) {} + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back( assertionStats ); + return true; + } + virtual void sectionEnded( SectionStats const& sectionStats ) { + assert( !m_sectionStack.empty() ); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); + } + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + Ptr node = new TestCaseNode( testCaseStats ); + assert( m_sectionStack.size() == 0 ); + node->children.push_back( m_rootSection ); + m_testCases.push_back( node ); + m_rootSection.reset(); + + assert( m_deepestSection ); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + Ptr node = new TestGroupNode( testGroupStats ); + node->children.swap( m_testCases ); + m_testGroups.push_back( node ); + } + virtual void testRunEnded( TestRunStats const& testRunStats ) { + Ptr node = new TestRunNode( testRunStats ); + node->children.swap( m_testGroups ); + m_testRuns.push_back( node ); + testRunEndedCumulative(); + } + virtual void testRunEndedCumulative() = 0; + + virtual void skipTest( TestCaseInfo const& ) {} + + Ptr m_config; + std::ostream& stream; + std::vector m_assertions; + std::vector > > m_sections; + std::vector > m_testCases; + std::vector > m_testGroups; + + std::vector > m_testRuns; + + Ptr m_rootSection; + Ptr m_deepestSection; + std::vector > m_sectionStack; + + }; + + template + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } + +} // end namespace Catch + +// #included from: ../internal/catch_reporter_registrars.hpp +#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED + +namespace Catch { + + template + class LegacyReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new LegacyReporterAdapter( new T( config ) ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + LegacyReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; + + template + class ReporterRegistrar { + + class ReporterFactory : public IReporterFactory { + + // *** Please Note ***: + // - If you end up here looking at a compiler error because it's trying to register + // your custom reporter class be aware that the native reporter interface has changed + // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via + // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. + // However please consider updating to the new interface as the old one is now + // deprecated and will probably be removed quite soon! + // Please contact me via github if you have any questions at all about this. + // In fact, ideally, please contact me anyway to let me know you've hit this - as I have + // no idea who is actually using custom reporters at all (possibly no-one!). + // The new interface is designed to minimise exposure to interface changes in the future. + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + + virtual std::string getDescription() const { + return T::getDescription(); + } + }; + + public: + + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); + } + }; +} + +#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ + namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ + namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + +// #included from: ../internal/catch_xmlwriter.hpp +#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + +#include +#include +#include + +namespace Catch { + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + ScopedElement( ScopedElement const& other ) + : m_writer( other.m_writer ){ + other.m_writer = NULL; + } + + ~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + ScopedElement& writeText( std::string const& text, bool indent = true ) { + m_writer->writeText( text, indent ); + return *this; + } + + template + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer; + }; + + XmlWriter() + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &Catch::cout() ) + {} + + XmlWriter( std::ostream& os ) + : m_tagIsOpen( false ), + m_needsNewline( false ), + m_os( &os ) + {} + + ~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + stream() << m_indent << "<" << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + ScopedElement scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + stream() << "/>\n"; + m_tagIsOpen = false; + } + else { + stream() << m_indent << "\n"; + } + m_tags.pop_back(); + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) { + stream() << " " << name << "=\""; + writeEncodedText( attribute ); + stream() << "\""; + } + return *this; + } + + XmlWriter& writeAttribute( std::string const& name, bool attribute ) { + stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; + return *this; + } + + template + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + if( !name.empty() ) + stream() << " " << name << "=\"" << attribute << "\""; + return *this; + } + + XmlWriter& writeText( std::string const& text, bool indent = true ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + stream() << m_indent; + writeEncodedText( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& writeComment( std::string const& text ) { + ensureTagClosed(); + stream() << m_indent << ""; + m_needsNewline = true; + return *this; + } + + XmlWriter& writeBlankLine() { + ensureTagClosed(); + stream() << "\n"; + return *this; + } + + void setStream( std::ostream& os ) { + m_os = &os; + } + + private: + XmlWriter( XmlWriter const& ); + void operator=( XmlWriter const& ); + + std::ostream& stream() { + return *m_os; + } + + void ensureTagClosed() { + if( m_tagIsOpen ) { + stream() << ">\n"; + m_tagIsOpen = false; + } + } + + void newlineIfNecessary() { + if( m_needsNewline ) { + stream() << "\n"; + m_needsNewline = false; + } + } + + void writeEncodedText( std::string const& text ) { + static const char* charsToEncode = "<&\""; + std::string mtext = text; + std::string::size_type pos = mtext.find_first_of( charsToEncode ); + while( pos != std::string::npos ) { + stream() << mtext.substr( 0, pos ); + + switch( mtext[pos] ) { + case '<': + stream() << "<"; + break; + case '&': + stream() << "&"; + break; + case '\"': + stream() << """; + break; + } + mtext = mtext.substr( pos+1 ); + pos = mtext.find_first_of( charsToEncode ); + } + stream() << mtext; + } + + bool m_tagIsOpen; + bool m_needsNewline; + std::vector m_tags; + std::string m_indent; + std::ostream* m_os; + }; + +} +namespace Catch { + class XmlReporter : public StreamingReporterBase { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_sectionDepth( 0 ) + {} + + virtual ~XmlReporter(); + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + public: // StreamingReporterBase + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& s ) { + StreamingReporterBase::noMatchingTestCases( s ); + } + + virtual void testRunStarting( TestRunInfo const& testInfo ) { + StreamingReporterBase::testRunStarting( testInfo ); + m_xml.setStream( stream ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + } + } + + virtual void assertionStarting( AssertionInfo const& ) { } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + const AssertionResult& assertionResult = assertionStats.assertionResult; + + // Print any info messages in tags. + if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { + for( std::vector::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); + it != itEnd; + ++it ) { + if( it->type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( it->message ); + } else if ( it->type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( it->message ); + } + } + } + + // Drop out if result was successful but we're not printing them. + if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) + return true; + + // Print the expression if there is one. + if( assertionResult.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", assertionResult.succeeded() ) + .writeAttribute( "type", assertionResult.getTestMacroName() ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ); + + m_xml.scopedElement( "Original" ) + .writeText( assertionResult.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( assertionResult.getExpandedExpression() ); + } + + // And... Print a result applicable to each result type. + switch( assertionResult.getResultType() ) { + case ResultWas::ThrewException: + m_xml.scopedElement( "Exception" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::FatalErrorCondition: + m_xml.scopedElement( "Fatal Error Condition" ) + .writeAttribute( "filename", assertionResult.getSourceInfo().file ) + .writeAttribute( "line", assertionResult.getSourceInfo().line ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( assertionResult.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.scopedElement( "Failure" ) + .writeText( assertionResult.getMessage() ); + break; + default: + break; + } + + if( assertionResult.hasExpression() ) + m_xml.endElement(); + + return true; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); + } + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + m_xml.endElement(); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_junit.hpp +#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + +#include + +namespace Catch { + + class JunitReporter : public CumulativeReporterBase { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + {} + + ~JunitReporter(); + + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } + + virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = true; + return prefs; + } + + virtual void testRunStarting( TestRunInfo const& runInfo ) { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } + + virtual bool assertionEnded( AssertionStats const& assertionStats ) { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); + } + + virtual void testRunEndedCumulative() { + xml.endElement(); + } + + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", "tbd" ); // !TBD + + // Write test cases + for( TestGroupNode::ChildNodes::const_iterator + it = groupNode.children.begin(), itEnd = groupNode.children.end(); + it != itEnd; + ++it ) + writeTestCase( **it ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + } + + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; + + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); + + std::string className = stats.testInfo.className; + + if( className.empty() ) { + if( rootSection.childSections.empty() ) + className = "global"; + } + writeSection( className, "", rootSection ); + } + + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + "/" + name; + + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + + writeAssertions( sectionNode ); + + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( SectionNode::ChildSections::const_iterator + it = sectionNode.childSections.begin(), + itEnd = sectionNode.childSections.end(); + it != itEnd; + ++it ) + if( className.empty() ) + writeSection( name, "", **it ); + else + writeSection( className, name, **it ); + } + + void writeAssertions( SectionNode const& sectionNode ) { + for( SectionNode::Assertions::const_iterator + it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); + it != itEnd; + ++it ) + writeAssertion( *it ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; + switch( result.getResultType() ) { + case ResultWas::ThrewException: + case ResultWas::FatalErrorCondition: + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; + break; + case ResultWas::DidntThrowException: + elementName = "failure"; + break; + + // We should never see these here: + case ResultWas::Info: + case ResultWas::Warning: + case ResultWas::Ok: + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + elementName = "internalError"; + break; + } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << "\n"; + for( std::vector::const_iterator + it = stats.infoMessages.begin(), + itEnd = stats.infoMessages.end(); + it != itEnd; + ++it ) + if( it->type == ResultWas::Info ) + oss << it->message << "\n"; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); + } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_console.hpp +#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + +namespace Catch { + + struct ConsoleReporter : StreamingReporterBase { + ConsoleReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_headerPrinted( false ) + {} + + virtual ~ConsoleReporter(); + static std::string getDescription() { + return "Reports test results as plain lines of text"; + } + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + lazyPrint(); + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + stream << std::endl; + return true; + } + + virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + m_headerPrinted = false; + StreamingReporterBase::sectionStarting( _sectionInfo ); + } + virtual void sectionEnded( SectionStats const& _sectionStats ) { + if( _sectionStats.missingAssertions ) { + lazyPrint(); + Colour colour( Colour::ResultError ); + if( m_sectionStack.size() > 1 ) + stream << "\nNo assertions in section"; + else + stream << "\nNo assertions in test case"; + stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; + } + if( m_headerPrinted ) { + if( m_config->showDurations() == ShowDurations::Always ) + stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + m_headerPrinted = false; + } + else { + if( m_config->showDurations() == ShowDurations::Always ) + stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; + } + StreamingReporterBase::sectionEnded( _sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + StreamingReporterBase::testCaseEnded( _testCaseStats ); + m_headerPrinted = false; + } + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + if( currentGroupInfo.used ) { + printSummaryDivider(); + stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; + printTotals( _testGroupStats.totals ); + stream << "\n" << std::endl; + } + StreamingReporterBase::testGroupEnded( _testGroupStats ); + } + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotalsDivider( _testRunStats.totals ); + printTotals( _testRunStats.totals ); + stream << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ), + stats( _stats ), + result( _stats.assertionResult ), + colour( Colour::None ), + message( result.getMessage() ), + messages( _stats.infoMessages ), + printInfoMessages( _printInfoMessages ) + { + switch( result.getResultType() ) { + case ResultWas::Ok: + colour = Colour::Success; + passOrFail = "PASSED"; + //if( result.hasMessage() ) + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) { + colour = Colour::Success; + passOrFail = "FAILED - but was ok"; + } + else { + colour = Colour::Error; + passOrFail = "FAILED"; + } + if( _stats.infoMessages.size() == 1 ) + messageLabel = "with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "with messages"; + break; + case ResultWas::ThrewException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to unexpected exception with message"; + break; + case ResultWas::FatalErrorCondition: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "due to a fatal error condition"; + break; + case ResultWas::DidntThrowException: + colour = Colour::Error; + passOrFail = "FAILED"; + messageLabel = "because no exception was thrown where one was expected"; + break; + case ResultWas::Info: + messageLabel = "info"; + break; + case ResultWas::Warning: + messageLabel = "warning"; + break; + case ResultWas::ExplicitFailure: + passOrFail = "FAILED"; + colour = Colour::Error; + if( _stats.infoMessages.size() == 1 ) + messageLabel = "explicitly with message"; + if( _stats.infoMessages.size() > 1 ) + messageLabel = "explicitly with messages"; + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + passOrFail = "** internal error **"; + colour = Colour::Error; + break; + } + } + + void print() const { + printSourceInfo(); + if( stats.totals.assertions.total() > 0 ) { + if( result.isOk() ) + stream << "\n"; + printResultType(); + printOriginalExpression(); + printReconstructedExpression(); + } + else { + stream << "\n"; + } + printMessage(); + } + + private: + void printResultType() const { + if( !passOrFail.empty() ) { + Colour colourGuard( colour ); + stream << passOrFail << ":\n"; + } + } + void printOriginalExpression() const { + if( result.hasExpression() ) { + Colour colourGuard( Colour::OriginalExpression ); + stream << " "; + stream << result.getExpressionInMacro(); + stream << "\n"; + } + } + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + stream << "with expansion:\n"; + Colour colourGuard( Colour::ReconstructedExpression ); + stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + } + } + void printMessage() const { + if( !messageLabel.empty() ) + stream << messageLabel << ":" << "\n"; + for( std::vector::const_iterator it = messages.begin(), itEnd = messages.end(); + it != itEnd; + ++it ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || it->type != ResultWas::Info ) + stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + } + } + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ": "; + } + + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + Colour::Code colour; + std::string passOrFail; + std::string messageLabel; + std::string message; + std::vector messages; + bool printInfoMessages; + }; + + void lazyPrint() { + + if( !currentTestRunInfo.used ) + lazyPrintRunInfo(); + if( !currentGroupInfo.used ) + lazyPrintGroupInfo(); + + if( !m_headerPrinted ) { + printTestCaseAndSectionHeader(); + m_headerPrinted = true; + } + } + void lazyPrintRunInfo() { + stream << "\n" << getLineOfChars<'~'>() << "\n"; + Colour colour( Colour::SecondaryText ); + stream << currentTestRunInfo->name + << " is a Catch v" << libraryVersion << " host application.\n" + << "Run with -? for options\n\n"; + + if( m_config->rngSeed() != 0 ) + stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; + + currentTestRunInfo.used = true; + } + void lazyPrintGroupInfo() { + if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { + printClosedHeader( "Group: " + currentGroupInfo->name ); + currentGroupInfo.used = true; + } + } + void printTestCaseAndSectionHeader() { + assert( !m_sectionStack.empty() ); + printOpenHeader( currentTestCaseInfo->name ); + + if( m_sectionStack.size() > 1 ) { + Colour colourGuard( Colour::Headers ); + + std::vector::const_iterator + it = m_sectionStack.begin()+1, // Skip first section (test case) + itEnd = m_sectionStack.end(); + for( ; it != itEnd; ++it ) + printHeaderString( it->name, 2 ); + } + + SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + + if( !lineInfo.empty() ){ + stream << getLineOfChars<'-'>() << "\n"; + Colour colourGuard( Colour::FileName ); + stream << lineInfo << "\n"; + } + stream << getLineOfChars<'.'>() << "\n" << std::endl; + } + + void printClosedHeader( std::string const& _name ) { + printOpenHeader( _name ); + stream << getLineOfChars<'.'>() << "\n"; + } + void printOpenHeader( std::string const& _name ) { + stream << getLineOfChars<'-'>() << "\n"; + { + Colour colourGuard( Colour::Headers ); + printHeaderString( _name ); + } + } + + // if string has a : in first line will set indent to follow it on + // subsequent lines + void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { + std::size_t i = _string.find( ": " ); + if( i != std::string::npos ) + i+=2; + else + i = 0; + stream << Text( _string, TextAttributes() + .setIndent( indent+i) + .setInitialIndent( indent ) ) << "\n"; + } + + struct SummaryColumn { + + SummaryColumn( std::string const& _label, Colour::Code _colour ) + : label( _label ), + colour( _colour ) + {} + SummaryColumn addRow( std::size_t count ) { + std::ostringstream oss; + oss << count; + std::string row = oss.str(); + for( std::vector::iterator it = rows.begin(); it != rows.end(); ++it ) { + while( it->size() < row.size() ) + *it = " " + *it; + while( it->size() > row.size() ) + row = " " + row; + } + rows.push_back( row ); + return *this; + } + + std::string label; + Colour::Code colour; + std::vector rows; + + }; + + void printTotals( Totals const& totals ) { + if( totals.testCases.total() == 0 ) { + stream << Colour( Colour::Warning ) << "No tests ran\n"; + } + else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + stream << Colour( Colour::ResultSuccess ) << "All tests passed"; + stream << " (" + << pluralise( totals.assertions.passed, "assertion" ) << " in " + << pluralise( totals.testCases.passed, "test case" ) << ")" + << "\n"; + } + else { + + std::vector columns; + columns.push_back( SummaryColumn( "", Colour::None ) + .addRow( totals.testCases.total() ) + .addRow( totals.assertions.total() ) ); + columns.push_back( SummaryColumn( "passed", Colour::Success ) + .addRow( totals.testCases.passed ) + .addRow( totals.assertions.passed ) ); + columns.push_back( SummaryColumn( "failed", Colour::ResultError ) + .addRow( totals.testCases.failed ) + .addRow( totals.assertions.failed ) ); + columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) + .addRow( totals.testCases.failedButOk ) + .addRow( totals.assertions.failedButOk ) ); + + printSummaryRow( "test cases", columns, 0 ); + printSummaryRow( "assertions", columns, 1 ); + } + } + void printSummaryRow( std::string const& label, std::vector const& cols, std::size_t row ) { + for( std::vector::const_iterator it = cols.begin(); it != cols.end(); ++it ) { + std::string value = it->rows[row]; + if( it->label.empty() ) { + stream << label << ": "; + if( value != "0" ) + stream << value; + else + stream << Colour( Colour::Warning ) << "- none -"; + } + else if( value != "0" ) { + stream << Colour( Colour::LightGrey ) << " | "; + stream << Colour( it->colour ) + << value << " " << it->label; + } + } + stream << "\n"; + } + + static std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; + } + static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } + + void printTotalsDivider( Totals const& totals ) { + if( totals.testCases.total() > 0 ) { + std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); + std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); + std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); + while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )++; + while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) + findMax( failedRatio, failedButOkRatio, passedRatio )--; + + stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); + stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); + if( totals.testCases.allPassed() ) + stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); + else + stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); + } + else { + stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); + } + stream << "\n"; + } + void printSummaryDivider() { + stream << getLineOfChars<'-'>() << "\n"; + } + + private: + bool m_headerPrinted; + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + +} // end namespace Catch + +// #included from: ../reporters/catch_reporter_compact.hpp +#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + +namespace Catch { + + struct CompactReporter : StreamingReporterBase { + + CompactReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual ~CompactReporter(); + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + virtual ReporterPreferences getPreferences() const { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + virtual void noMatchingTestCases( std::string const& spec ) { + stream << "No test cases matched '" << spec << "'" << std::endl; + } + + virtual void assertionStarting( AssertionInfo const& ) { + } + + virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + virtual void testRunEnded( TestRunStats const& _testRunStats ) { + printTotals( _testRunStats.totals ); + stream << "\n" << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + void operator= ( AssertionPrinter const& ); + public: + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , stats( _stats ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + // Colour::LightGrey + + static Colour::Code dimColour() { return Colour::FileName; } + +#ifdef CATCH_PLATFORM_MAC + static const char* failedString() { return "FAILED"; } + static const char* passedString() { return "PASSED"; } +#else + static const char* failedString() { return "failed"; } + static const char* passedString() { return "passed"; } +#endif + + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ":"; + } + + void printResultType( Colour::Code colour, std::string passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << " " << passOrFail; + } + stream << ":"; + } + } + + void printIssue( std::string issue ) const { + stream << " " << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ";"; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << " " << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } + + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << "'"; + ++itMessage; + } + } + + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; + + // using messages.end() directly yields compilation error: + std::vector::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ":"; + } + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << "'"; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } + } + } + + private: + std::ostream& stream; + AssertionStats const& stats; + AssertionResult const& result; + std::vector messages; + std::vector::const_iterator itMessage; + bool printInfoMessages; + }; + + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + std::string bothOrAll( std::size_t count ) const { + return count == 1 ? "" : count == 2 ? "both " : "all " ; + } + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : ""; + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; + } + } + }; + + INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch + +namespace Catch { + NonCopyable::~NonCopyable() {} + IShared::~IShared() {} + StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} + IContext::~IContext() {} + IResultCapture::~IResultCapture() {} + ITestCase::~ITestCase() {} + ITestCaseRegistry::~ITestCaseRegistry() {} + IRegistryHub::~IRegistryHub() {} + IMutableRegistryHub::~IMutableRegistryHub() {} + IExceptionTranslator::~IExceptionTranslator() {} + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} + IReporter::~IReporter() {} + IReporterFactory::~IReporterFactory() {} + IReporterRegistry::~IReporterRegistry() {} + IStreamingReporter::~IStreamingReporter() {} + AssertionStats::~AssertionStats() {} + SectionStats::~SectionStats() {} + TestCaseStats::~TestCaseStats() {} + TestGroupStats::~TestGroupStats() {} + TestRunStats::~TestRunStats() {} + CumulativeReporterBase::SectionNode::~SectionNode() {} + CumulativeReporterBase::~CumulativeReporterBase() {} + + StreamingReporterBase::~StreamingReporterBase() {} + ConsoleReporter::~ConsoleReporter() {} + CompactReporter::~CompactReporter() {} + IRunner::~IRunner() {} + IMutableContext::~IMutableContext() {} + IConfig::~IConfig() {} + XmlReporter::~XmlReporter() {} + JunitReporter::~JunitReporter() {} + TestRegistry::~TestRegistry() {} + FreeFunctionTestCase::~FreeFunctionTestCase() {} + IGeneratorInfo::~IGeneratorInfo() {} + IGeneratorsForTest::~IGeneratorsForTest() {} + TestSpec::Pattern::~Pattern() {} + TestSpec::NamePattern::~NamePattern() {} + TestSpec::TagPattern::~TagPattern() {} + TestSpec::ExcludedPattern::~ExcludedPattern() {} + + Matchers::Impl::StdString::Equals::~Equals() {} + Matchers::Impl::StdString::Contains::~Contains() {} + Matchers::Impl::StdString::StartsWith::~StartsWith() {} + Matchers::Impl::StdString::EndsWith::~EndsWith() {} + + void Config::dummy() {} +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#ifdef CATCH_CONFIG_MAIN +// #included from: internal/catch_default_main.hpp +#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED + +#ifndef __OBJC__ + +// Standard C/C++ main entry point +int main (int argc, char * const argv[]) { + return Catch::Session().run( argc, argv ); +} + +#else // __OBJC__ + +// Objective-C entry point +int main (int argc, char * const argv[]) { +#if !CATCH_ARC_ENABLED + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; +#endif + + Catch::registerTestMethods(); + int result = Catch::Session().run( argc, (char* const*)argv ); + +#if !CATCH_ARC_ENABLED + [pool drain]; +#endif + + return result; +} + +#endif // __OBJC__ + +#endif + +#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED +# undef CLARA_CONFIG_MAIN +#endif + +////// + +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) +#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) + +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) + +#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) +#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) +#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) +#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) +#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) + +#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) +#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) +#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) + #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) +#else + #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) + #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) +#endif +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) +#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required +#else + +#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) +#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) + +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) + +#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) +#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) +#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) +#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) +#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) + +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) + +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) + +#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) +#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) +#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) + +#ifdef CATCH_CONFIG_VARIADIC_MACROS + #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) + #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) + #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) + #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) + #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) +#else + #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) + #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) + #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) + #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) + #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) +#endif +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) + +#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) +#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) + +#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) + +#endif + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) + +// "BDD-style" convenience wrappers +#ifdef CATCH_CONFIG_VARIADIC_MACROS +#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) +#else +#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) +#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) +#endif +#define GIVEN( desc ) SECTION( " Given: " desc, "" ) +#define WHEN( desc ) SECTION( " When: " desc, "" ) +#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) +#define THEN( desc ) SECTION( " Then: " desc, "" ) +#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +using Catch::Detail::Approx; + +// #included from: internal/catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + +#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED + diff --git a/src/nmodl/ext/tclap/Arg.h b/src/nmodl/ext/tclap/Arg.h new file mode 100755 index 0000000000..b28eef117c --- /dev/null +++ b/src/nmodl/ext/tclap/Arg.h @@ -0,0 +1,692 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: Arg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_ARGUMENT_H +#define TCLAP_ARGUMENT_H + +#ifdef HAVE_CONFIG_H +#include +#else +#define HAVE_SSTREAM +#endif + +#include +#include +#include +#include +#include +#include + +#if defined(HAVE_SSTREAM) +#include +typedef std::istringstream istringstream; +#elif defined(HAVE_STRSTREAM) +#include +typedef std::istrstream istringstream; +#else +#error "Need a stringstream (sstream or strstream) to compile!" +#endif + +#include +#include +#include +#include +#include + +namespace TCLAP { + +/** + * A virtual base class that defines the essential data for all arguments. + * This class, or one of its existing children, must be subclassed to do + * anything. + */ +class Arg +{ + private: + /** + * Prevent accidental copying. + */ + Arg(const Arg& rhs); + + /** + * Prevent accidental copying. + */ + Arg& operator=(const Arg& rhs); + + /** + * Indicates whether the rest of the arguments should be ignored. + */ + static bool& ignoreRestRef() { static bool ign = false; return ign; } + + /** + * The delimiter that separates an argument flag/name from the + * value. + */ + static char& delimiterRef() { static char delim = ' '; return delim; } + + protected: + + /** + * The single char flag used to identify the argument. + * This value (preceded by a dash {-}), can be used to identify + * an argument on the command line. The _flag can be blank, + * in fact this is how unlabeled args work. Unlabeled args must + * override appropriate functions to get correct handling. Note + * that the _flag does NOT include the dash as part of the flag. + */ + std::string _flag; + + /** + * A single work namd indentifying the argument. + * This value (preceded by two dashed {--}) can also be used + * to identify an argument on the command line. Note that the + * _name does NOT include the two dashes as part of the _name. The + * _name cannot be blank. + */ + std::string _name; + + /** + * Description of the argument. + */ + std::string _description; + + /** + * Indicating whether the argument is required. + */ + bool _required; + + /** + * Label to be used in usage description. Normally set to + * "required", but can be changed when necessary. + */ + std::string _requireLabel; + + /** + * Indicates whether a value is required for the argument. + * Note that the value may be required but the argument/value + * combination may not be, as specified by _required. + */ + bool _valueRequired; + + /** + * Indicates whether the argument has been set. + * Indicates that a value on the command line has matched the + * name/flag of this argument and the values have been set accordingly. + */ + bool _alreadySet; + + /** + * A pointer to a vistitor object. + * The visitor allows special handling to occur as soon as the + * argument is matched. This defaults to NULL and should not + * be used unless absolutely necessary. + */ + Visitor* _visitor; + + /** + * Whether this argument can be ignored, if desired. + */ + bool _ignoreable; + + /** + * Indicates that the arg was set as part of an XOR and not on the + * command line. + */ + bool _xorSet; + + bool _acceptsMultipleValues; + + /** + * Performs the special handling described by the Vistitor. + */ + void _checkWithVisitor() const; + + /** + * Primary constructor. YOU (yes you) should NEVER construct an Arg + * directly, this is a base class that is extended by various children + * that are meant to be used. Use SwitchArg, ValueArg, MultiArg, + * UnlabeledValueArg, or UnlabeledMultiArg instead. + * + * \param flag - The flag identifying the argument. + * \param name - The name identifying the argument. + * \param desc - The description of the argument, used in the usage. + * \param req - Whether the argument is required. + * \param valreq - Whether the a value is required for the argument. + * \param v - The visitor checked by the argument. Defaults to NULL. + */ + Arg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + bool valreq, + Visitor* v = NULL ); + + public: + /** + * Destructor. + */ + virtual ~Arg(); + + /** + * Adds this to the specified list of Args. + * \param argList - The list to add this to. + */ + virtual void addToList( std::list& argList ) const; + + /** + * Begin ignoring arguments since the "--" argument was specified. + */ + static void beginIgnoring() { ignoreRestRef() = true; } + + /** + * Whether to ignore the rest. + */ + static bool ignoreRest() { return ignoreRestRef(); } + + /** + * The delimiter that separates an argument flag/name from the + * value. + */ + static char delimiter() { return delimiterRef(); } + + /** + * The char used as a place holder when SwitchArgs are combined. + * Currently set to the bell char (ASCII 7). + */ + static char blankChar() { return (char)7; } + + /** + * The char that indicates the beginning of a flag. Defaults to '-', but + * clients can define TCLAP_FLAGSTARTCHAR to override. + */ +#ifndef TCLAP_FLAGSTARTCHAR +#define TCLAP_FLAGSTARTCHAR '-' +#endif + static char flagStartChar() { return TCLAP_FLAGSTARTCHAR; } + + /** + * The sting that indicates the beginning of a flag. Defaults to "-", but + * clients can define TCLAP_FLAGSTARTSTRING to override. Should be the same + * as TCLAP_FLAGSTARTCHAR. + */ +#ifndef TCLAP_FLAGSTARTSTRING +#define TCLAP_FLAGSTARTSTRING "-" +#endif + static const std::string flagStartString() { return TCLAP_FLAGSTARTSTRING; } + + /** + * The sting that indicates the beginning of a name. Defaults to "--", but + * clients can define TCLAP_NAMESTARTSTRING to override. + */ +#ifndef TCLAP_NAMESTARTSTRING +#define TCLAP_NAMESTARTSTRING "--" +#endif + static const std::string nameStartString() { return TCLAP_NAMESTARTSTRING; } + + /** + * The name used to identify the ignore rest argument. + */ + static const std::string ignoreNameString() { return "ignore_rest"; } + + /** + * Sets the delimiter for all arguments. + * \param c - The character that delimits flags/names from values. + */ + static void setDelimiter( char c ) { delimiterRef() = c; } + + /** + * Pure virtual method meant to handle the parsing and value assignment + * of the string on the command line. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. What is + * passed in from main. + */ + virtual bool processArg(int *i, std::vector& args) = 0; + + /** + * Operator ==. + * Equality operator. Must be virtual to handle unlabeled args. + * \param a - The Arg to be compared to this. + */ + virtual bool operator==(const Arg& a) const; + + /** + * Returns the argument flag. + */ + const std::string& getFlag() const; + + /** + * Returns the argument name. + */ + const std::string& getName() const; + + /** + * Returns the argument description. + */ + std::string getDescription() const; + + /** + * Indicates whether the argument is required. + */ + virtual bool isRequired() const; + + /** + * Sets _required to true. This is used by the XorHandler. + * You really have no reason to ever use it. + */ + void forceRequired(); + + /** + * Sets the _alreadySet value to true. This is used by the XorHandler. + * You really have no reason to ever use it. + */ + void xorSet(); + + /** + * Indicates whether a value must be specified for argument. + */ + bool isValueRequired() const; + + /** + * Indicates whether the argument has already been set. Only true + * if the arg has been matched on the command line. + */ + bool isSet() const; + + /** + * Indicates whether the argument can be ignored, if desired. + */ + bool isIgnoreable() const; + + /** + * A method that tests whether a string matches this argument. + * This is generally called by the processArg() method. This + * method could be re-implemented by a child to change how + * arguments are specified on the command line. + * \param s - The string to be compared to the flag/name to determine + * whether the arg matches. + */ + virtual bool argMatches( const std::string& s ) const; + + /** + * Returns a simple string representation of the argument. + * Primarily for debugging. + */ + virtual std::string toString() const; + + /** + * Returns a short ID for the usage. + * \param valueId - The value used in the id. + */ + virtual std::string shortID( const std::string& valueId = "val" ) const; + + /** + * Returns a long ID for the usage. + * \param valueId - The value used in the id. + */ + virtual std::string longID( const std::string& valueId = "val" ) const; + + /** + * Trims a value off of the flag. + * \param flag - The string from which the flag and value will be + * trimmed. Contains the flag once the value has been trimmed. + * \param value - Where the value trimmed from the string will + * be stored. + */ + virtual void trimFlag( std::string& flag, std::string& value ) const; + + /** + * Checks whether a given string has blank chars, indicating that + * it is a combined SwitchArg. If so, return true, otherwise return + * false. + * \param s - string to be checked. + */ + bool _hasBlanks( const std::string& s ) const; + + /** + * Sets the requireLabel. Used by XorHandler. You shouldn't ever + * use this. + * \param s - Set the requireLabel to this value. + */ + void setRequireLabel( const std::string& s ); + + /** + * Used for MultiArgs and XorHandler to determine whether args + * can still be set. + */ + virtual bool allowMore(); + + /** + * Use by output classes to determine whether an Arg accepts + * multiple values. + */ + virtual bool acceptsMultipleValues(); + + /** + * Clears the Arg object and allows it to be reused by new + * command lines. + */ + virtual void reset(); +}; + +/** + * Typedef of an Arg list iterator. + */ +typedef std::list::iterator ArgListIterator; + +/** + * Typedef of an Arg vector iterator. + */ +typedef std::vector::iterator ArgVectorIterator; + +/** + * Typedef of a Visitor list iterator. + */ +typedef std::list::iterator VisitorListIterator; + +/* + * Extract a value of type T from it's string representation contained + * in strVal. The ValueLike parameter used to select the correct + * specialization of ExtractValue depending on the value traits of T. + * ValueLike traits use operator>> to assign the value from strVal. + */ +template void +ExtractValue(T &destVal, const std::string& strVal, ValueLike vl) +{ + static_cast(vl); // Avoid warning about unused vl + std::istringstream is(strVal); + + int valuesRead = 0; + while ( is.good() ) { + if ( is.peek() != EOF ) +#ifdef TCLAP_SETBASE_ZERO + is >> std::setbase(0) >> destVal; +#else + is >> destVal; +#endif + else + break; + + valuesRead++; + } + + if ( is.fail() ) + throw( ArgParseException("Couldn't read argument value " + "from string '" + strVal + "'")); + + + if ( valuesRead > 1 ) + throw( ArgParseException("More than one valid value parsed from " + "string '" + strVal + "'")); + +} + +/* + * Extract a value of type T from it's string representation contained + * in strVal. The ValueLike parameter used to select the correct + * specialization of ExtractValue depending on the value traits of T. + * StringLike uses assignment (operator=) to assign from strVal. + */ +template void +ExtractValue(T &destVal, const std::string& strVal, StringLike sl) +{ + static_cast(sl); // Avoid warning about unused sl + SetString(destVal, strVal); +} + +////////////////////////////////////////////////////////////////////// +//BEGIN Arg.cpp +////////////////////////////////////////////////////////////////////// + +inline Arg::Arg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + bool valreq, + Visitor* v) : + _flag(flag), + _name(name), + _description(desc), + _required(req), + _requireLabel("required"), + _valueRequired(valreq), + _alreadySet(false), + _visitor( v ), + _ignoreable(true), + _xorSet(false), + _acceptsMultipleValues(false) +{ + if ( _flag.length() > 1 ) + throw(SpecificationException( + "Argument flag can only be one character long", toString() ) ); + + if ( _name != ignoreNameString() && + ( _flag == Arg::flagStartString() || + _flag == Arg::nameStartString() || + _flag == " " ) ) + throw(SpecificationException("Argument flag cannot be either '" + + Arg::flagStartString() + "' or '" + + Arg::nameStartString() + "' or a space.", + toString() ) ); + + if ( ( _name.substr( 0, Arg::flagStartString().length() ) == Arg::flagStartString() ) || + ( _name.substr( 0, Arg::nameStartString().length() ) == Arg::nameStartString() ) || + ( _name.find( " ", 0 ) != std::string::npos ) ) + throw(SpecificationException("Argument name begin with either '" + + Arg::flagStartString() + "' or '" + + Arg::nameStartString() + "' or space.", + toString() ) ); + +} + +inline Arg::~Arg() { } + +inline std::string Arg::shortID( const std::string& valueId ) const +{ + std::string id = ""; + + if ( _flag != "" ) + id = Arg::flagStartString() + _flag; + else + id = Arg::nameStartString() + _name; + + if ( _valueRequired ) + id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; + + if ( !_required ) + id = "[" + id + "]"; + + return id; +} + +inline std::string Arg::longID( const std::string& valueId ) const +{ + std::string id = ""; + + if ( _flag != "" ) + { + id += Arg::flagStartString() + _flag; + + if ( _valueRequired ) + id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; + + id += ", "; + } + + id += Arg::nameStartString() + _name; + + if ( _valueRequired ) + id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; + + return id; + +} + +inline bool Arg::operator==(const Arg& a) const +{ + if ( ( _flag != "" && _flag == a._flag ) || _name == a._name) + return true; + else + return false; +} + +inline std::string Arg::getDescription() const +{ + std::string desc = ""; + if ( _required ) + desc = "(" + _requireLabel + ") "; + +// if ( _valueRequired ) +// desc += "(value required) "; + + desc += _description; + return desc; +} + +inline const std::string& Arg::getFlag() const { return _flag; } + +inline const std::string& Arg::getName() const { return _name; } + +inline bool Arg::isRequired() const { return _required; } + +inline bool Arg::isValueRequired() const { return _valueRequired; } + +inline bool Arg::isSet() const +{ + if ( _alreadySet && !_xorSet ) + return true; + else + return false; +} + +inline bool Arg::isIgnoreable() const { return _ignoreable; } + +inline void Arg::setRequireLabel( const std::string& s) +{ + _requireLabel = s; +} + +inline bool Arg::argMatches( const std::string& argFlag ) const +{ + if ( ( argFlag == Arg::flagStartString() + _flag && _flag != "" ) || + argFlag == Arg::nameStartString() + _name ) + return true; + else + return false; +} + +inline std::string Arg::toString() const +{ + std::string s = ""; + + if ( _flag != "" ) + s += Arg::flagStartString() + _flag + " "; + + s += "(" + Arg::nameStartString() + _name + ")"; + + return s; +} + +inline void Arg::_checkWithVisitor() const +{ + if ( _visitor != NULL ) + _visitor->visit(); +} + +/** + * Implementation of trimFlag. + */ +inline void Arg::trimFlag(std::string& flag, std::string& value) const +{ + int stop = 0; + for ( int i = 0; static_cast(i) < flag.length(); i++ ) + if ( flag[i] == Arg::delimiter() ) + { + stop = i; + break; + } + + if ( stop > 1 ) + { + value = flag.substr(stop+1); + flag = flag.substr(0,stop); + } + +} + +/** + * Implementation of _hasBlanks. + */ +inline bool Arg::_hasBlanks( const std::string& s ) const +{ + for ( int i = 1; static_cast(i) < s.length(); i++ ) + if ( s[i] == Arg::blankChar() ) + return true; + + return false; +} + +inline void Arg::forceRequired() +{ + _required = true; +} + +inline void Arg::xorSet() +{ + _alreadySet = true; + _xorSet = true; +} + +/** + * Overridden by Args that need to added to the end of the list. + */ +inline void Arg::addToList( std::list& argList ) const +{ + argList.push_front( const_cast(this) ); +} + +inline bool Arg::allowMore() +{ + return false; +} + +inline bool Arg::acceptsMultipleValues() +{ + return _acceptsMultipleValues; +} + +inline void Arg::reset() +{ + _xorSet = false; + _alreadySet = false; +} + +////////////////////////////////////////////////////////////////////// +//END Arg.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif + diff --git a/src/nmodl/ext/tclap/ArgException.h b/src/nmodl/ext/tclap/ArgException.h new file mode 100755 index 0000000000..3411aa9543 --- /dev/null +++ b/src/nmodl/ext/tclap/ArgException.h @@ -0,0 +1,200 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: ArgException.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_ARG_EXCEPTION_H +#define TCLAP_ARG_EXCEPTION_H + +#include +#include + +namespace TCLAP { + +/** + * A simple class that defines and argument exception. Should be caught + * whenever a CmdLine is created and parsed. + */ +class ArgException : public std::exception +{ + public: + + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source. + * \param td - Text describing the type of ArgException it is. + * of the exception. + */ + ArgException( const std::string& text = "undefined exception", + const std::string& id = "undefined", + const std::string& td = "Generic ArgException") + : std::exception(), + _errorText(text), + _argId( id ), + _typeDescription(td) + { } + + /** + * Destructor. + */ + virtual ~ArgException() throw() { } + + /** + * Returns the error text. + */ + std::string error() const { return ( _errorText ); } + + /** + * Returns the argument id. + */ + std::string argId() const + { + if ( _argId == "undefined" ) + return " "; + else + return ( "Argument: " + _argId ); + } + + /** + * Returns the arg id and error text. + */ + const char* what() const throw() + { + static std::string ex; + ex = _argId + " -- " + _errorText; + return ex.c_str(); + } + + /** + * Returns the type of the exception. Used to explain and distinguish + * between different child exceptions. + */ + std::string typeDescription() const + { + return _typeDescription; + } + + + private: + + /** + * The text of the exception message. + */ + std::string _errorText; + + /** + * The argument related to this exception. + */ + std::string _argId; + + /** + * Describes the type of the exception. Used to distinguish + * between different child exceptions. + */ + std::string _typeDescription; + +}; + +/** + * Thrown from within the child Arg classes when it fails to properly + * parse the argument it has been passed. + */ +class ArgParseException : public ArgException +{ + public: + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source + * of the exception. + */ + ArgParseException( const std::string& text = "undefined exception", + const std::string& id = "undefined" ) + : ArgException( text, + id, + std::string( "Exception found while parsing " ) + + std::string( "the value the Arg has been passed." )) + { } +}; + +/** + * Thrown from CmdLine when the arguments on the command line are not + * properly specified, e.g. too many arguments, required argument missing, etc. + */ +class CmdLineParseException : public ArgException +{ + public: + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source + * of the exception. + */ + CmdLineParseException( const std::string& text = "undefined exception", + const std::string& id = "undefined" ) + : ArgException( text, + id, + std::string( "Exception found when the values ") + + std::string( "on the command line do not meet ") + + std::string( "the requirements of the defined ") + + std::string( "Args." )) + { } +}; + +/** + * Thrown from Arg and CmdLine when an Arg is improperly specified, e.g. + * same flag as another Arg, same name, etc. + */ +class SpecificationException : public ArgException +{ + public: + /** + * Constructor. + * \param text - The text of the exception. + * \param id - The text identifying the argument source + * of the exception. + */ + SpecificationException( const std::string& text = "undefined exception", + const std::string& id = "undefined" ) + : ArgException( text, + id, + std::string("Exception found when an Arg object ")+ + std::string("is improperly defined by the ") + + std::string("developer." )) + { } + +}; + +class ExitException { +public: + ExitException(int estat) : _estat(estat) {} + + int getExitStatus() const { return _estat; } + +private: + int _estat; +}; + +} // namespace TCLAP + +#endif + diff --git a/src/nmodl/ext/tclap/ArgTraits.h b/src/nmodl/ext/tclap/ArgTraits.h new file mode 100755 index 0000000000..0b2c18f70c --- /dev/null +++ b/src/nmodl/ext/tclap/ArgTraits.h @@ -0,0 +1,87 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: ArgTraits.h + * + * Copyright (c) 2007, Daniel Aarno, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +// This is an internal tclap file, you should probably not have to +// include this directly + +#ifndef TCLAP_ARGTRAITS_H +#define TCLAP_ARGTRAITS_H + +namespace TCLAP { + +// We use two empty structs to get compile type specialization +// function to work + +/** + * A value like argument value type is a value that can be set using + * operator>>. This is the default value type. + */ +struct ValueLike { + typedef ValueLike ValueCategory; + virtual ~ValueLike() {} +}; + +/** + * A string like argument value type is a value that can be set using + * operator=(string). Usefull if the value type contains spaces which + * will be broken up into individual tokens by operator>>. + */ +struct StringLike { + virtual ~StringLike() {} +}; + +/** + * A class can inherit from this object to make it have string like + * traits. This is a compile time thing and does not add any overhead + * to the inherenting class. + */ +struct StringLikeTrait { + typedef StringLike ValueCategory; + virtual ~StringLikeTrait() {} +}; + +/** + * A class can inherit from this object to make it have value like + * traits. This is a compile time thing and does not add any overhead + * to the inherenting class. + */ +struct ValueLikeTrait { + typedef ValueLike ValueCategory; + virtual ~ValueLikeTrait() {} +}; + +/** + * Arg traits are used to get compile type specialization when parsing + * argument values. Using an ArgTraits you can specify the way that + * values gets assigned to any particular type during parsing. The two + * supported types are StringLike and ValueLike. + */ +template +struct ArgTraits { + typedef typename T::ValueCategory ValueCategory; + virtual ~ArgTraits() {} + //typedef ValueLike ValueCategory; +}; + +#endif + +} // namespace diff --git a/src/nmodl/ext/tclap/CmdLine.h b/src/nmodl/ext/tclap/CmdLine.h new file mode 100755 index 0000000000..aabe3a28e4 --- /dev/null +++ b/src/nmodl/ext/tclap/CmdLine.h @@ -0,0 +1,651 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: CmdLine.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_CMDLINE_H +#define TCLAP_CMDLINE_H + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include // Needed for exit(), which isn't defined in some envs. + +namespace TCLAP { + +template void DelPtr(T ptr) +{ + delete ptr; +} + +template void ClearContainer(C &c) +{ + typedef typename C::value_type value_type; + std::for_each(c.begin(), c.end(), DelPtr); + c.clear(); +} + + +/** + * The base class that manages the command line definition and passes + * along the parsing to the appropriate Arg classes. + */ +class CmdLine : public CmdLineInterface +{ + protected: + + /** + * The list of arguments that will be tested against the + * command line. + */ + std::list _argList; + + /** + * The name of the program. Set to argv[0]. + */ + std::string _progName; + + /** + * A message used to describe the program. Used in the usage output. + */ + std::string _message; + + /** + * The version to be displayed with the --version switch. + */ + std::string _version; + + /** + * The number of arguments that are required to be present on + * the command line. This is set dynamically, based on the + * Args added to the CmdLine object. + */ + int _numRequired; + + /** + * The character that is used to separate the argument flag/name + * from the value. Defaults to ' ' (space). + */ + char _delimiter; + + /** + * The handler that manages xoring lists of args. + */ + XorHandler _xorHandler; + + /** + * A list of Args to be explicitly deleted when the destructor + * is called. At the moment, this only includes the three default + * Args. + */ + std::list _argDeleteOnExitList; + + /** + * A list of Visitors to be explicitly deleted when the destructor + * is called. At the moment, these are the Vistors created for the + * default Args. + */ + std::list _visitorDeleteOnExitList; + + /** + * Object that handles all output for the CmdLine. + */ + CmdLineOutput* _output; + + /** + * Should CmdLine handle parsing exceptions internally? + */ + bool _handleExceptions; + + /** + * Throws an exception listing the missing args. + */ + void missingArgsException(); + + /** + * Checks whether a name/flag string matches entirely matches + * the Arg::blankChar. Used when multiple switches are combined + * into a single argument. + * \param s - The message to be used in the usage. + */ + bool _emptyCombined(const std::string& s); + + /** + * Perform a delete ptr; operation on ptr when this object is deleted. + */ + void deleteOnExit(Arg* ptr); + + /** + * Perform a delete ptr; operation on ptr when this object is deleted. + */ + void deleteOnExit(Visitor* ptr); + +private: + + /** + * Prevent accidental copying. + */ + CmdLine(const CmdLine& rhs); + CmdLine& operator=(const CmdLine& rhs); + + /** + * Encapsulates the code common to the constructors + * (which is all of it). + */ + void _constructor(); + + + /** + * Is set to true when a user sets the output object. We use this so + * that we don't delete objects that are created outside of this lib. + */ + bool _userSetOutput; + + /** + * Whether or not to automatically create help and version switches. + */ + bool _helpAndVersion; + + /** + * Whether or not to ignore unmatched args. + */ + bool _ignoreUnmatched; + + public: + + /** + * Command line constructor. Defines how the arguments will be + * parsed. + * \param message - The message to be used in the usage + * output. + * \param delimiter - The character that is used to separate + * the argument flag/name from the value. Defaults to ' ' (space). + * \param version - The version number to be used in the + * --version switch. + * \param helpAndVersion - Whether or not to create the Help and + * Version switches. Defaults to true. + */ + CmdLine(const std::string& message, + const char delimiter = ' ', + const std::string& version = "none", + bool helpAndVersion = true); + + /** + * Deletes any resources allocated by a CmdLine object. + */ + virtual ~CmdLine(); + + /** + * Adds an argument to the list of arguments to be parsed. + * \param a - Argument to be added. + */ + void add( Arg& a ); + + /** + * An alternative add. Functionally identical. + * \param a - Argument to be added. + */ + void add( Arg* a ); + + /** + * Add two Args that will be xor'd. If this method is used, add does + * not need to be called. + * \param a - Argument to be added and xor'd. + * \param b - Argument to be added and xor'd. + */ + void xorAdd( Arg& a, Arg& b ); + + /** + * Add a list of Args that will be xor'd. If this method is used, + * add does not need to be called. + * \param xors - List of Args to be added and xor'd. + */ + void xorAdd( std::vector& xors ); + + /** + * Parses the command line. + * \param argc - Number of arguments. + * \param argv - Array of arguments. + */ + void parse(int argc, const char * const * argv); + + /** + * Parses the command line. + * \param args - A vector of strings representing the args. + * args[0] is still the program name. + */ + void parse(std::vector& args); + + /** + * + */ + CmdLineOutput* getOutput(); + + /** + * + */ + void setOutput(CmdLineOutput* co); + + /** + * + */ + std::string& getVersion(); + + /** + * + */ + std::string& getProgramName(); + + /** + * + */ + std::list& getArgList(); + + /** + * + */ + XorHandler& getXorHandler(); + + /** + * + */ + char getDelimiter(); + + /** + * + */ + std::string& getMessage(); + + /** + * + */ + bool hasHelpAndVersion(); + + /** + * Disables or enables CmdLine's internal parsing exception handling. + * + * @param state Should CmdLine handle parsing exceptions internally? + */ + void setExceptionHandling(const bool state); + + /** + * Returns the current state of the internal exception handling. + * + * @retval true Parsing exceptions are handled internally. + * @retval false Parsing exceptions are propagated to the caller. + */ + bool getExceptionHandling() const; + + /** + * Allows the CmdLine object to be reused. + */ + void reset(); + + /** + * Allows unmatched args to be ignored. By default false. + * + * @param ignore If true the cmdline will ignore any unmatched args + * and if false it will behave as normal. + */ + void ignoreUnmatched(const bool ignore); +}; + + +/////////////////////////////////////////////////////////////////////////////// +//Begin CmdLine.cpp +/////////////////////////////////////////////////////////////////////////////// + +inline CmdLine::CmdLine(const std::string& m, + char delim, + const std::string& v, + bool help ) + : + _argList(std::list()), + _progName("not_set_yet"), + _message(m), + _version(v), + _numRequired(0), + _delimiter(delim), + _xorHandler(XorHandler()), + _argDeleteOnExitList(std::list()), + _visitorDeleteOnExitList(std::list()), + _output(0), + _handleExceptions(true), + _userSetOutput(false), + _helpAndVersion(help), + _ignoreUnmatched(false) +{ + _constructor(); +} + +inline CmdLine::~CmdLine() +{ + ClearContainer(_argDeleteOnExitList); + ClearContainer(_visitorDeleteOnExitList); + + if ( !_userSetOutput ) { + delete _output; + _output = 0; + } +} + +inline void CmdLine::_constructor() +{ + _output = new StdOutput; + + Arg::setDelimiter( _delimiter ); + + Visitor* v; + + if ( _helpAndVersion ) + { + v = new HelpVisitor( this, &_output ); + SwitchArg* help = new SwitchArg("h","help", + "Displays usage information and exits.", + false, v); + add( help ); + deleteOnExit(help); + deleteOnExit(v); + + v = new VersionVisitor( this, &_output ); + SwitchArg* vers = new SwitchArg("","version", + "Displays version information and exits.", + false, v); + add( vers ); + deleteOnExit(vers); + deleteOnExit(v); + } + + v = new IgnoreRestVisitor(); + SwitchArg* ignore = new SwitchArg(Arg::flagStartString(), + Arg::ignoreNameString(), + "Ignores the rest of the labeled arguments following this flag.", + false, v); + add( ignore ); + deleteOnExit(ignore); + deleteOnExit(v); +} + +inline void CmdLine::xorAdd( std::vector& ors ) +{ + _xorHandler.add( ors ); + + for (ArgVectorIterator it = ors.begin(); it != ors.end(); it++) + { + (*it)->forceRequired(); + (*it)->setRequireLabel( "OR required" ); + add( *it ); + } +} + +inline void CmdLine::xorAdd( Arg& a, Arg& b ) +{ + std::vector ors; + ors.push_back( &a ); + ors.push_back( &b ); + xorAdd( ors ); +} + +inline void CmdLine::add( Arg& a ) +{ + add( &a ); +} + +inline void CmdLine::add( Arg* a ) +{ + for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ ) + if ( *a == *(*it) ) + throw( SpecificationException( + "Argument with same flag/name already exists!", + a->longID() ) ); + + a->addToList( _argList ); + + if ( a->isRequired() ) + _numRequired++; +} + + +inline void CmdLine::parse(int argc, const char * const * argv) +{ + // this step is necessary so that we have easy access to + // mutable strings. + std::vector args; + for (int i = 0; i < argc; i++) + args.push_back(argv[i]); + + parse(args); +} + +inline void CmdLine::parse(std::vector& args) +{ + bool shouldExit = false; + int estat = 0; + + try { + _progName = args.front(); + args.erase(args.begin()); + + int requiredCount = 0; + + for (int i = 0; static_cast(i) < args.size(); i++) + { + bool matched = false; + for (ArgListIterator it = _argList.begin(); + it != _argList.end(); it++) { + if ( (*it)->processArg( &i, args ) ) + { + requiredCount += _xorHandler.check( *it ); + matched = true; + break; + } + } + + // checks to see if the argument is an empty combined + // switch and if so, then we've actually matched it + if ( !matched && _emptyCombined( args[i] ) ) + matched = true; + + if ( !matched && !Arg::ignoreRest() && !_ignoreUnmatched) + throw(CmdLineParseException("Couldn't find match " + "for argument", + args[i])); + } + + if ( requiredCount < _numRequired ) + missingArgsException(); + + if ( requiredCount > _numRequired ) + throw(CmdLineParseException("Too many arguments!")); + + } catch ( ArgException& e ) { + // If we're not handling the exceptions, rethrow. + if ( !_handleExceptions) { + throw; + } + + try { + _output->failure(*this,e); + } catch ( ExitException &ee ) { + estat = ee.getExitStatus(); + shouldExit = true; + } + } catch (ExitException &ee) { + // If we're not handling the exceptions, rethrow. + if ( !_handleExceptions) { + throw; + } + + estat = ee.getExitStatus(); + shouldExit = true; + } + + if (shouldExit) + exit(estat); +} + +inline bool CmdLine::_emptyCombined(const std::string& s) +{ + if ( s.length() > 0 && s[0] != Arg::flagStartChar() ) + return false; + + for ( int i = 1; static_cast(i) < s.length(); i++ ) + if ( s[i] != Arg::blankChar() ) + return false; + + return true; +} + +inline void CmdLine::missingArgsException() +{ + int count = 0; + + std::string missingArgList; + for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++) + { + if ( (*it)->isRequired() && !(*it)->isSet() ) + { + missingArgList += (*it)->getName(); + missingArgList += ", "; + count++; + } + } + missingArgList = missingArgList.substr(0,missingArgList.length()-2); + + std::string msg; + if ( count > 1 ) + msg = "Required arguments missing: "; + else + msg = "Required argument missing: "; + + msg += missingArgList; + + throw(CmdLineParseException(msg)); +} + +inline void CmdLine::deleteOnExit(Arg* ptr) +{ + _argDeleteOnExitList.push_back(ptr); +} + +inline void CmdLine::deleteOnExit(Visitor* ptr) +{ + _visitorDeleteOnExitList.push_back(ptr); +} + +inline CmdLineOutput* CmdLine::getOutput() +{ + return _output; +} + +inline void CmdLine::setOutput(CmdLineOutput* co) +{ + if ( !_userSetOutput ) + delete _output; + _userSetOutput = true; + _output = co; +} + +inline std::string& CmdLine::getVersion() +{ + return _version; +} + +inline std::string& CmdLine::getProgramName() +{ + return _progName; +} + +inline std::list& CmdLine::getArgList() +{ + return _argList; +} + +inline XorHandler& CmdLine::getXorHandler() +{ + return _xorHandler; +} + +inline char CmdLine::getDelimiter() +{ + return _delimiter; +} + +inline std::string& CmdLine::getMessage() +{ + return _message; +} + +inline bool CmdLine::hasHelpAndVersion() +{ + return _helpAndVersion; +} + +inline void CmdLine::setExceptionHandling(const bool state) +{ + _handleExceptions = state; +} + +inline bool CmdLine::getExceptionHandling() const +{ + return _handleExceptions; +} + +inline void CmdLine::reset() +{ + for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ ) + (*it)->reset(); + + _progName.clear(); +} + +inline void CmdLine::ignoreUnmatched(const bool ignore) +{ + _ignoreUnmatched = ignore; +} + +/////////////////////////////////////////////////////////////////////////////// +//End CmdLine.cpp +/////////////////////////////////////////////////////////////////////////////// + + + +} //namespace TCLAP +#endif diff --git a/src/nmodl/ext/tclap/CmdLineInterface.h b/src/nmodl/ext/tclap/CmdLineInterface.h new file mode 100755 index 0000000000..1b25e9b8c9 --- /dev/null +++ b/src/nmodl/ext/tclap/CmdLineInterface.h @@ -0,0 +1,150 @@ + +/****************************************************************************** + * + * file: CmdLineInterface.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_COMMANDLINE_INTERFACE_H +#define TCLAP_COMMANDLINE_INTERFACE_H + +#include +#include +#include +#include +#include + + +namespace TCLAP { + +class Arg; +class CmdLineOutput; +class XorHandler; + +/** + * The base class that manages the command line definition and passes + * along the parsing to the appropriate Arg classes. + */ +class CmdLineInterface +{ + public: + + /** + * Destructor + */ + virtual ~CmdLineInterface() {} + + /** + * Adds an argument to the list of arguments to be parsed. + * \param a - Argument to be added. + */ + virtual void add( Arg& a )=0; + + /** + * An alternative add. Functionally identical. + * \param a - Argument to be added. + */ + virtual void add( Arg* a )=0; + + /** + * Add two Args that will be xor'd. + * If this method is used, add does + * not need to be called. + * \param a - Argument to be added and xor'd. + * \param b - Argument to be added and xor'd. + */ + virtual void xorAdd( Arg& a, Arg& b )=0; + + /** + * Add a list of Args that will be xor'd. If this method is used, + * add does not need to be called. + * \param xors - List of Args to be added and xor'd. + */ + virtual void xorAdd( std::vector& xors )=0; + + /** + * Parses the command line. + * \param argc - Number of arguments. + * \param argv - Array of arguments. + */ + virtual void parse(int argc, const char * const * argv)=0; + + /** + * Parses the command line. + * \param args - A vector of strings representing the args. + * args[0] is still the program name. + */ + void parse(std::vector& args); + + /** + * Returns the CmdLineOutput object. + */ + virtual CmdLineOutput* getOutput()=0; + + /** + * \param co - CmdLineOutput object that we want to use instead. + */ + virtual void setOutput(CmdLineOutput* co)=0; + + /** + * Returns the version string. + */ + virtual std::string& getVersion()=0; + + /** + * Returns the program name string. + */ + virtual std::string& getProgramName()=0; + + /** + * Returns the argList. + */ + virtual std::list& getArgList()=0; + + /** + * Returns the XorHandler. + */ + virtual XorHandler& getXorHandler()=0; + + /** + * Returns the delimiter string. + */ + virtual char getDelimiter()=0; + + /** + * Returns the message string. + */ + virtual std::string& getMessage()=0; + + /** + * Indicates whether or not the help and version switches were created + * automatically. + */ + virtual bool hasHelpAndVersion()=0; + + /** + * Resets the instance as if it had just been constructed so that the + * instance can be reused. + */ + virtual void reset()=0; +}; + +} //namespace + + +#endif diff --git a/src/nmodl/ext/tclap/CmdLineOutput.h b/src/nmodl/ext/tclap/CmdLineOutput.h new file mode 100755 index 0000000000..71ee5a3b41 --- /dev/null +++ b/src/nmodl/ext/tclap/CmdLineOutput.h @@ -0,0 +1,74 @@ + + +/****************************************************************************** + * + * file: CmdLineOutput.h + * + * Copyright (c) 2004, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_CMDLINEOUTPUT_H +#define TCLAP_CMDLINEOUTPUT_H + +#include +#include +#include +#include +#include +#include + +namespace TCLAP { + +class CmdLineInterface; +class ArgException; + +/** + * The interface that any output object must implement. + */ +class CmdLineOutput +{ + + public: + + /** + * Virtual destructor. + */ + virtual ~CmdLineOutput() {} + + /** + * Generates some sort of output for the USAGE. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c)=0; + + /** + * Generates some sort of output for the version. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c)=0; + + /** + * Generates some sort of output for a failure. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure( CmdLineInterface& c, + ArgException& e )=0; + +}; + +} //namespace TCLAP +#endif diff --git a/src/nmodl/ext/tclap/Constraint.h b/src/nmodl/ext/tclap/Constraint.h new file mode 100755 index 0000000000..a92acf9a9a --- /dev/null +++ b/src/nmodl/ext/tclap/Constraint.h @@ -0,0 +1,68 @@ + +/****************************************************************************** + * + * file: Constraint.h + * + * Copyright (c) 2005, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_CONSTRAINT_H +#define TCLAP_CONSTRAINT_H + +#include +#include +#include +#include +#include +#include + +namespace TCLAP { + +/** + * The interface that defines the interaction between the Arg and Constraint. + */ +template +class Constraint +{ + + public: + /** + * Returns a description of the Constraint. + */ + virtual std::string description() const =0; + + /** + * Returns the short ID for the Constraint. + */ + virtual std::string shortID() const =0; + + /** + * The method used to verify that the value parsed from the command + * line meets the constraint. + * \param value - The value that will be checked. + */ + virtual bool check(const T& value) const =0; + + /** + * Destructor. + * Silences warnings about Constraint being a base class with virtual + * functions but without a virtual destructor. + */ + virtual ~Constraint() { ; } +}; + +} //namespace TCLAP +#endif diff --git a/src/nmodl/ext/tclap/DocBookOutput.h b/src/nmodl/ext/tclap/DocBookOutput.h new file mode 100755 index 0000000000..a42ca274df --- /dev/null +++ b/src/nmodl/ext/tclap/DocBookOutput.h @@ -0,0 +1,299 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: DocBookOutput.h + * + * Copyright (c) 2004, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_DOCBOOKOUTPUT_H +#define TCLAP_DOCBOOKOUTPUT_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace TCLAP { + +/** + * A class that generates DocBook output for usage() method for the + * given CmdLine and its Args. + */ +class DocBookOutput : public CmdLineOutput +{ + + public: + + /** + * Prints the usage to stdout. Can be overridden to + * produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c); + + /** + * Prints the version to stdout. Can be overridden + * to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c); + + /** + * Prints (to stderr) an error message, short usage + * Can be overridden to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure(CmdLineInterface& c, + ArgException& e ); + + protected: + + /** + * Substitutes the char r for string x in string s. + * \param s - The string to operate on. + * \param r - The char to replace. + * \param x - What to replace r with. + */ + void substituteSpecialChars( std::string& s, char r, std::string& x ); + void removeChar( std::string& s, char r); + void basename( std::string& s ); + + void printShortArg(Arg* it); + void printLongArg(Arg* it); + + char theDelimiter; +}; + + +inline void DocBookOutput::version(CmdLineInterface& _cmd) +{ + std::cout << _cmd.getVersion() << std::endl; +} + +inline void DocBookOutput::usage(CmdLineInterface& _cmd ) +{ + std::list argList = _cmd.getArgList(); + std::string progName = _cmd.getProgramName(); + std::string xversion = _cmd.getVersion(); + theDelimiter = _cmd.getDelimiter(); + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + basename(progName); + + std::cout << "" << std::endl; + std::cout << "" << std::endl << std::endl; + + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << progName << "" << std::endl; + std::cout << "1" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << progName << "" << std::endl; + std::cout << "" << _cmd.getMessage() << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << progName << "" << std::endl; + + // xor + for ( int i = 0; (unsigned int)i < xorList.size(); i++ ) + { + std::cout << "" << std::endl; + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); it++ ) + printShortArg((*it)); + + std::cout << "" << std::endl; + } + + // rest of args + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + if ( !xorHandler.contains( (*it) ) ) + printShortArg((*it)); + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "Description" << std::endl; + std::cout << "" << std::endl; + std::cout << _cmd.getMessage() << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "Options" << std::endl; + + std::cout << "" << std::endl; + + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + printLongArg((*it)); + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "Version" << std::endl; + std::cout << "" << std::endl; + std::cout << xversion << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + +} + +inline void DocBookOutput::failure( CmdLineInterface& _cmd, + ArgException& e ) +{ + static_cast(_cmd); // unused + std::cout << e.what() << std::endl; + throw ExitException(1); +} + +inline void DocBookOutput::substituteSpecialChars( std::string& s, + char r, + std::string& x ) +{ + size_t p; + while ( (p = s.find_first_of(r)) != std::string::npos ) + { + s.erase(p,1); + s.insert(p,x); + } +} + +inline void DocBookOutput::removeChar( std::string& s, char r) +{ + size_t p; + while ( (p = s.find_first_of(r)) != std::string::npos ) + { + s.erase(p,1); + } +} + +inline void DocBookOutput::basename( std::string& s ) +{ + size_t p = s.find_last_of('/'); + if ( p != std::string::npos ) + { + s.erase(0, p + 1); + } +} + +inline void DocBookOutput::printShortArg(Arg* a) +{ + std::string lt = "<"; + std::string gt = ">"; + + std::string id = a->shortID(); + substituteSpecialChars(id,'<',lt); + substituteSpecialChars(id,'>',gt); + removeChar(id,'['); + removeChar(id,']'); + + std::string choice = "opt"; + if ( a->isRequired() ) + choice = "plain"; + + std::cout << "acceptsMultipleValues() ) + std::cout << " rep='repeat'"; + + + std::cout << '>'; + if ( !a->getFlag().empty() ) + std::cout << a->flagStartChar() << a->getFlag(); + else + std::cout << a->nameStartString() << a->getName(); + if ( a->isValueRequired() ) + { + std::string arg = a->shortID(); + removeChar(arg,'['); + removeChar(arg,']'); + removeChar(arg,'<'); + removeChar(arg,'>'); + arg.erase(0, arg.find_last_of(theDelimiter) + 1); + std::cout << theDelimiter; + std::cout << "" << arg << ""; + } + std::cout << "" << std::endl; + +} + +inline void DocBookOutput::printLongArg(Arg* a) +{ + std::string lt = "<"; + std::string gt = ">"; + + std::string desc = a->getDescription(); + substituteSpecialChars(desc,'<',lt); + substituteSpecialChars(desc,'>',gt); + + std::cout << "" << std::endl; + + if ( !a->getFlag().empty() ) + { + std::cout << "" << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + } + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; + std::cout << "" << std::endl; + std::cout << desc << std::endl; + std::cout << "" << std::endl; + std::cout << "" << std::endl; + + std::cout << "" << std::endl; +} + +} //namespace TCLAP +#endif diff --git a/src/nmodl/ext/tclap/HelpVisitor.h b/src/nmodl/ext/tclap/HelpVisitor.h new file mode 100755 index 0000000000..cc3bd070ca --- /dev/null +++ b/src/nmodl/ext/tclap/HelpVisitor.h @@ -0,0 +1,76 @@ + +/****************************************************************************** + * + * file: HelpVisitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_HELP_VISITOR_H +#define TCLAP_HELP_VISITOR_H + +#include +#include +#include + +namespace TCLAP { + +/** + * A Visitor object that calls the usage method of the given CmdLineOutput + * object for the specified CmdLine object. + */ +class HelpVisitor: public Visitor +{ + private: + /** + * Prevent accidental copying. + */ + HelpVisitor(const HelpVisitor& rhs); + HelpVisitor& operator=(const HelpVisitor& rhs); + + protected: + + /** + * The CmdLine the output will be generated for. + */ + CmdLineInterface* _cmd; + + /** + * The output object. + */ + CmdLineOutput** _out; + + public: + + /** + * Constructor. + * \param cmd - The CmdLine the output will be generated for. + * \param out - The type of output. + */ + HelpVisitor(CmdLineInterface* cmd, CmdLineOutput** out) + : Visitor(), _cmd( cmd ), _out( out ) { } + + /** + * Calls the usage method of the CmdLineOutput for the + * specified CmdLine. + */ + void visit() { (*_out)->usage(*_cmd); throw ExitException(0); } + +}; + +} + +#endif diff --git a/src/nmodl/ext/tclap/IgnoreRestVisitor.h b/src/nmodl/ext/tclap/IgnoreRestVisitor.h new file mode 100755 index 0000000000..e328649e51 --- /dev/null +++ b/src/nmodl/ext/tclap/IgnoreRestVisitor.h @@ -0,0 +1,52 @@ + +/****************************************************************************** + * + * file: IgnoreRestVisitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_IGNORE_REST_VISITOR_H +#define TCLAP_IGNORE_REST_VISITOR_H + +#include +#include + +namespace TCLAP { + +/** + * A Vistor that tells the CmdLine to begin ignoring arguments after + * this one is parsed. + */ +class IgnoreRestVisitor: public Visitor +{ + public: + + /** + * Constructor. + */ + IgnoreRestVisitor() : Visitor() {} + + /** + * Sets Arg::_ignoreRest. + */ + void visit() { Arg::beginIgnoring(); } +}; + +} + +#endif diff --git a/src/nmodl/ext/tclap/MultiArg.h b/src/nmodl/ext/tclap/MultiArg.h new file mode 100755 index 0000000000..34bb2d7895 --- /dev/null +++ b/src/nmodl/ext/tclap/MultiArg.h @@ -0,0 +1,433 @@ +/****************************************************************************** + * + * file: MultiArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_MULTIPLE_ARGUMENT_H +#define TCLAP_MULTIPLE_ARGUMENT_H + +#include +#include + +#include +#include + +namespace TCLAP { +/** + * An argument that allows multiple values of type T to be specified. Very + * similar to a ValueArg, except a vector of values will be returned + * instead of just one. + */ +template +class MultiArg : public Arg +{ +public: + typedef std::vector container_type; + typedef typename container_type::iterator iterator; + typedef typename container_type::const_iterator const_iterator; + +protected: + + /** + * The list of values parsed from the CmdLine. + */ + std::vector _values; + + /** + * The description of type T to be used in the usage. + */ + std::string _typeDesc; + + /** + * A list of constraint on this Arg. + */ + Constraint* _constraint; + + /** + * Extracts the value from the string. + * Attempts to parse string as type T, if this fails an exception + * is thrown. + * \param val - The string to be read. + */ + void _extractValue( const std::string& val ); + + /** + * Used by XorHandler to decide whether to keep parsing for this arg. + */ + bool _allowMore; + +public: + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + Visitor* v = NULL); + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + Visitor* v = NULL ); + + /** + * Constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. It knows the difference + * between labeled and unlabeled. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns a vector of type T containing the values parsed from + * the command line. + */ + const std::vector& getValue(); + + /** + * Returns an iterator over the values parsed from the command + * line. + */ + const_iterator begin() const { return _values.begin(); } + + /** + * Returns the end of the values parsed from the command + * line. + */ + const_iterator end() const { return _values.end(); } + + /** + * Returns the a short id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string shortID(const std::string& val="val") const; + + /** + * Returns the a long id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string longID(const std::string& val="val") const; + + /** + * Once we've matched the first value, then the arg is no longer + * required. + */ + virtual bool isRequired() const; + + virtual bool allowMore(); + + virtual void reset(); + +private: + /** + * Prevent accidental copying + */ + MultiArg(const MultiArg& rhs); + MultiArg& operator=(const MultiArg& rhs); + +}; + +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + Visitor* v) : + Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( typeDesc ), + _constraint( NULL ), + _allowMore(false) +{ + _acceptsMultipleValues = true; +} + +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v) +: Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( typeDesc ), + _constraint( NULL ), + _allowMore(false) +{ + parser.add( this ); + _acceptsMultipleValues = true; +} + +/** + * + */ +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + Visitor* v) +: Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( constraint->shortID() ), + _constraint( constraint ), + _allowMore(false) +{ + _acceptsMultipleValues = true; +} + +template +MultiArg::MultiArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v) +: Arg( flag, name, desc, req, true, v ), + _values(std::vector()), + _typeDesc( constraint->shortID() ), + _constraint( constraint ), + _allowMore(false) +{ + parser.add( this ); + _acceptsMultipleValues = true; +} + +template +const std::vector& MultiArg::getValue() { return _values; } + +template +bool MultiArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + if ( _hasBlanks( args[*i] ) ) + return false; + + std::string flag = args[*i]; + std::string value = ""; + + trimFlag( flag, value ); + + if ( argMatches( flag ) ) + { + if ( Arg::delimiter() != ' ' && value == "" ) + throw( ArgParseException( + "Couldn't find delimiter for this argument!", + toString() ) ); + + // always take the first one, regardless of start string + if ( value == "" ) + { + (*i)++; + if ( static_cast(*i) < args.size() ) + _extractValue( args[*i] ); + else + throw( ArgParseException("Missing a value for this argument!", + toString() ) ); + } + else + _extractValue( value ); + + /* + // continuing taking the args until we hit one with a start string + while ( (unsigned int)(*i)+1 < args.size() && + args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 && + args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 ) + _extractValue( args[++(*i)] ); + */ + + _alreadySet = true; + _checkWithVisitor(); + + return true; + } + else + return false; +} + +/** + * + */ +template +std::string MultiArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::shortID(_typeDesc) + " ... "; +} + +/** + * + */ +template +std::string MultiArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::longID(_typeDesc) + " (accepted multiple times)"; +} + +/** + * Once we've matched the first value, then the arg is no longer + * required. + */ +template +bool MultiArg::isRequired() const +{ + if ( _required ) + { + if ( _values.size() > 1 ) + return false; + else + return true; + } + else + return false; + +} + +template +void MultiArg::_extractValue( const std::string& val ) +{ + try { + T tmp; + ExtractValue(tmp, val, typename ArgTraits::ValueCategory()); + _values.push_back(tmp); + } catch( ArgParseException &e) { + throw ArgParseException(e.error(), toString()); + } + + if ( _constraint != NULL ) + if ( ! _constraint->check( _values.back() ) ) + throw( CmdLineParseException( "Value '" + val + + "' does not meet constraint: " + + _constraint->description(), + toString() ) ); +} + +template +bool MultiArg::allowMore() +{ + bool am = _allowMore; + _allowMore = true; + return am; +} + +template +void MultiArg::reset() +{ + Arg::reset(); + _values.clear(); +} + +} // namespace TCLAP + +#endif diff --git a/src/nmodl/ext/tclap/MultiSwitchArg.h b/src/nmodl/ext/tclap/MultiSwitchArg.h new file mode 100755 index 0000000000..8820b64162 --- /dev/null +++ b/src/nmodl/ext/tclap/MultiSwitchArg.h @@ -0,0 +1,216 @@ + +/****************************************************************************** +* +* file: MultiSwitchArg.h +* +* Copyright (c) 2003, Michael E. Smoot . +* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. +* Copyright (c) 2005, Michael E. Smoot, Daniel Aarno, Erik Zeek. +* All rights reverved. +* +* See the file COPYING in the top directory of this distribution for +* more information. +* +* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +* DEALINGS IN THE SOFTWARE. +* +*****************************************************************************/ + + +#ifndef TCLAP_MULTI_SWITCH_ARG_H +#define TCLAP_MULTI_SWITCH_ARG_H + +#include +#include + +#include + +namespace TCLAP { + +/** +* A multiple switch argument. If the switch is set on the command line, then +* the getValue method will return the number of times the switch appears. +*/ +class MultiSwitchArg : public SwitchArg +{ + protected: + + /** + * The value of the switch. + */ + int _value; + + /** + * Used to support the reset() method so that ValueArg can be + * reset to their constructed value. + */ + int _default; + + public: + + /** + * MultiSwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param init - Optional. The initial/default value of this Arg. + * Defaults to 0. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + int init = 0, + Visitor* v = NULL); + + + /** + * MultiSwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param parser - A CmdLine parser object to add this Arg to + * \param init - Optional. The initial/default value of this Arg. + * Defaults to 0. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + int init = 0, + Visitor* v = NULL); + + + /** + * Handles the processing of the argument. + * This re-implements the SwitchArg version of this method to set the + * _value of the argument appropriately. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed + * in from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns int, the number of times the switch has been set. + */ + int getValue(); + + /** + * Returns the shortID for this Arg. + */ + std::string shortID(const std::string& val) const; + + /** + * Returns the longID for this Arg. + */ + std::string longID(const std::string& val) const; + + void reset(); + +}; + +////////////////////////////////////////////////////////////////////// +//BEGIN MultiSwitchArg.cpp +////////////////////////////////////////////////////////////////////// +inline MultiSwitchArg::MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + int init, + Visitor* v ) +: SwitchArg(flag, name, desc, false, v), +_value( init ), +_default( init ) +{ } + +inline MultiSwitchArg::MultiSwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + int init, + Visitor* v ) +: SwitchArg(flag, name, desc, false, v), +_value( init ), +_default( init ) +{ + parser.add( this ); +} + +inline int MultiSwitchArg::getValue() { return _value; } + +inline bool MultiSwitchArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + if ( argMatches( args[*i] )) + { + // so the isSet() method will work + _alreadySet = true; + + // Matched argument: increment value. + ++_value; + + _checkWithVisitor(); + + return true; + } + else if ( combinedSwitchesMatch( args[*i] ) ) + { + // so the isSet() method will work + _alreadySet = true; + + // Matched argument: increment value. + ++_value; + + // Check for more in argument and increment value. + while ( combinedSwitchesMatch( args[*i] ) ) + ++_value; + + _checkWithVisitor(); + + return false; + } + else + return false; +} + +inline std::string +MultiSwitchArg::shortID(const std::string& val) const +{ + return Arg::shortID(val) + " ... "; +} + +inline std::string +MultiSwitchArg::longID(const std::string& val) const +{ + return Arg::longID(val) + " (accepted multiple times)"; +} + +inline void +MultiSwitchArg::reset() +{ + MultiSwitchArg::_value = MultiSwitchArg::_default; +} + +////////////////////////////////////////////////////////////////////// +//END MultiSwitchArg.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif diff --git a/src/nmodl/ext/tclap/OptionalUnlabeledTracker.h b/src/nmodl/ext/tclap/OptionalUnlabeledTracker.h new file mode 100755 index 0000000000..8174c5f624 --- /dev/null +++ b/src/nmodl/ext/tclap/OptionalUnlabeledTracker.h @@ -0,0 +1,62 @@ + + +/****************************************************************************** + * + * file: OptionalUnlabeledTracker.h + * + * Copyright (c) 2005, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_OPTIONAL_UNLABELED_TRACKER_H +#define TCLAP_OPTIONAL_UNLABELED_TRACKER_H + +#include + +namespace TCLAP { + +class OptionalUnlabeledTracker +{ + + public: + + static void check( bool req, const std::string& argName ); + + static void gotOptional() { alreadyOptionalRef() = true; } + + static bool& alreadyOptional() { return alreadyOptionalRef(); } + + private: + + static bool& alreadyOptionalRef() { static bool ct = false; return ct; } +}; + + +inline void OptionalUnlabeledTracker::check( bool req, const std::string& argName ) +{ + if ( OptionalUnlabeledTracker::alreadyOptional() ) + throw( SpecificationException( + "You can't specify ANY Unlabeled Arg following an optional Unlabeled Arg", + argName ) ); + + if ( !req ) + OptionalUnlabeledTracker::gotOptional(); +} + + +} // namespace TCLAP + +#endif diff --git a/src/nmodl/ext/tclap/StandardTraits.h b/src/nmodl/ext/tclap/StandardTraits.h new file mode 100755 index 0000000000..46d7f6fafd --- /dev/null +++ b/src/nmodl/ext/tclap/StandardTraits.h @@ -0,0 +1,208 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: StandardTraits.h + * + * Copyright (c) 2007, Daniel Aarno, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +// This is an internal tclap file, you should probably not have to +// include this directly + +#ifndef TCLAP_STANDARD_TRAITS_H +#define TCLAP_STANDARD_TRAITS_H + +#ifdef HAVE_CONFIG_H +#include // To check for long long +#endif + +// If Microsoft has already typedef'd wchar_t as an unsigned +// short, then compiles will break because it's as if we're +// creating ArgTraits twice for unsigned short. Thus... +#ifdef _MSC_VER +#ifndef _NATIVE_WCHAR_T_DEFINED +#define TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS +#endif +#endif + +namespace TCLAP { + +// ====================================================================== +// Integer types +// ====================================================================== + +/** + * longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * ints have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * shorts have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * chars have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +#ifdef HAVE_LONG_LONG +/** + * long longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + +// ====================================================================== +// Unsigned integer types +// ====================================================================== + +/** + * unsigned longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * unsigned ints have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * unsigned shorts have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * unsigned chars have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +// Microsoft implements size_t awkwardly. +#if defined(_MSC_VER) && defined(_M_X64) +/** + * size_ts have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + + +#ifdef HAVE_LONG_LONG +/** + * unsigned long longs have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + +// ====================================================================== +// Float types +// ====================================================================== + +/** + * floats have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +/** + * doubles have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + +// ====================================================================== +// Other types +// ====================================================================== + +/** + * bools have value-like semantics. + */ +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; + + +/** + * wchar_ts have value-like semantics. + */ +#ifndef TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS +template<> +struct ArgTraits { + typedef ValueLike ValueCategory; +}; +#endif + +/** + * Strings have string like argument traits. + */ +template<> +struct ArgTraits { + typedef StringLike ValueCategory; +}; + +template +void SetString(T &dst, const std::string &src) +{ + dst = src; +} + +} // namespace + +#endif + diff --git a/src/nmodl/ext/tclap/StdOutput.h b/src/nmodl/ext/tclap/StdOutput.h new file mode 100755 index 0000000000..944cff4dd1 --- /dev/null +++ b/src/nmodl/ext/tclap/StdOutput.h @@ -0,0 +1,299 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: StdOutput.h + * + * Copyright (c) 2004, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_STDCMDLINEOUTPUT_H +#define TCLAP_STDCMDLINEOUTPUT_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace TCLAP { + +/** + * A class that isolates any output from the CmdLine object so that it + * may be easily modified. + */ +class StdOutput : public CmdLineOutput +{ + + public: + + /** + * Prints the usage to stdout. Can be overridden to + * produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c); + + /** + * Prints the version to stdout. Can be overridden + * to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c); + + /** + * Prints (to stderr) an error message, short usage + * Can be overridden to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure(CmdLineInterface& c, + ArgException& e ); + + protected: + + /** + * Writes a brief usage message with short args. + * \param c - The CmdLine object the output is generated for. + * \param os - The stream to write the message to. + */ + void _shortUsage( CmdLineInterface& c, std::ostream& os ) const; + + /** + * Writes a longer usage message with long and short args, + * provides descriptions and prints message. + * \param c - The CmdLine object the output is generated for. + * \param os - The stream to write the message to. + */ + void _longUsage( CmdLineInterface& c, std::ostream& os ) const; + + /** + * This function inserts line breaks and indents long strings + * according the params input. It will only break lines at spaces, + * commas and pipes. + * \param os - The stream to be printed to. + * \param s - The string to be printed. + * \param maxWidth - The maxWidth allowed for the output line. + * \param indentSpaces - The number of spaces to indent the first line. + * \param secondLineOffset - The number of spaces to indent the second + * and all subsequent lines in addition to indentSpaces. + */ + void spacePrint( std::ostream& os, + const std::string& s, + int maxWidth, + int indentSpaces, + int secondLineOffset ) const; + +}; + + +inline void StdOutput::version(CmdLineInterface& _cmd) +{ + std::string progName = _cmd.getProgramName(); + std::string xversion = _cmd.getVersion(); + + std::cout << std::endl << progName << " version: " + << xversion << std::endl << std::endl; +} + +inline void StdOutput::usage(CmdLineInterface& _cmd ) +{ + std::cout << std::endl << "USAGE: " << std::endl << std::endl; + + _shortUsage( _cmd, std::cout ); + + std::cout << std::endl << std::endl << "Where: " << std::endl << std::endl; + + _longUsage( _cmd, std::cout ); + + std::cout << std::endl; + +} + +inline void StdOutput::failure( CmdLineInterface& _cmd, + ArgException& e ) +{ + std::string progName = _cmd.getProgramName(); + + std::cerr << "PARSE ERROR: " << e.argId() << std::endl + << " " << e.error() << std::endl << std::endl; + + if ( _cmd.hasHelpAndVersion() ) + { + std::cerr << "Brief USAGE: " << std::endl; + + _shortUsage( _cmd, std::cerr ); + + std::cerr << std::endl << "For complete USAGE and HELP type: " + << std::endl << " " << progName << " " + << Arg::nameStartString() << "help" + << std::endl << std::endl; + } + else + usage(_cmd); + + throw ExitException(1); +} + +inline void +StdOutput::_shortUsage( CmdLineInterface& _cmd, + std::ostream& os ) const +{ + std::list argList = _cmd.getArgList(); + std::string progName = _cmd.getProgramName(); + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + + std::string s = progName + " "; + + // first the xor + for ( int i = 0; static_cast(i) < xorList.size(); i++ ) + { + s += " {"; + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); it++ ) + s += (*it)->shortID() + "|"; + + s[s.length()-1] = '}'; + } + + // then the rest + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + if ( !xorHandler.contains( (*it) ) ) + s += " " + (*it)->shortID(); + + // if the program name is too long, then adjust the second line offset + int secondLineOffset = static_cast(progName.length()) + 2; + if ( secondLineOffset > 75/2 ) + secondLineOffset = static_cast(75/2); + + spacePrint( os, s, 75, 3, secondLineOffset ); +} + +inline void +StdOutput::_longUsage( CmdLineInterface& _cmd, + std::ostream& os ) const +{ + std::list argList = _cmd.getArgList(); + std::string message = _cmd.getMessage(); + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + + // first the xor + for ( int i = 0; static_cast(i) < xorList.size(); i++ ) + { + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); + it++ ) + { + spacePrint( os, (*it)->longID(), 75, 3, 3 ); + spacePrint( os, (*it)->getDescription(), 75, 5, 0 ); + + if ( it+1 != xorList[i].end() ) + spacePrint(os, "-- OR --", 75, 9, 0); + } + os << std::endl << std::endl; + } + + // then the rest + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + if ( !xorHandler.contains( (*it) ) ) + { + spacePrint( os, (*it)->longID(), 75, 3, 3 ); + spacePrint( os, (*it)->getDescription(), 75, 5, 0 ); + os << std::endl; + } + + os << std::endl; + + spacePrint( os, message, 75, 3, 0 ); +} + +inline void StdOutput::spacePrint( std::ostream& os, + const std::string& s, + int maxWidth, + int indentSpaces, + int secondLineOffset ) const +{ + int len = static_cast(s.length()); + + if ( (len + indentSpaces > maxWidth) && maxWidth > 0 ) + { + int allowedLen = maxWidth - indentSpaces; + int start = 0; + while ( start < len ) + { + // find the substring length + // int stringLen = std::min( len - start, allowedLen ); + // doing it this way to support a VisualC++ 2005 bug + using namespace std; + int stringLen = min( len - start, allowedLen ); + + // trim the length so it doesn't end in middle of a word + if ( stringLen == allowedLen ) + while ( stringLen >= 0 && + s[stringLen+start] != ' ' && + s[stringLen+start] != ',' && + s[stringLen+start] != '|' ) + stringLen--; + + // ok, the word is longer than the line, so just split + // wherever the line ends + if ( stringLen <= 0 ) + stringLen = allowedLen; + + // check for newlines + for ( int i = 0; i < stringLen; i++ ) + if ( s[start+i] == '\n' ) + stringLen = i+1; + + // print the indent + for ( int i = 0; i < indentSpaces; i++ ) + os << " "; + + if ( start == 0 ) + { + // handle second line offsets + indentSpaces += secondLineOffset; + + // adjust allowed len + allowedLen -= secondLineOffset; + } + + os << s.substr(start,stringLen) << std::endl; + + // so we don't start a line with a space + while ( s[stringLen+start] == ' ' && start < len ) + start++; + + start += stringLen; + } + } + else + { + for ( int i = 0; i < indentSpaces; i++ ) + os << " "; + os << s << std::endl; + } +} + +} //namespace TCLAP +#endif diff --git a/src/nmodl/ext/tclap/SwitchArg.h b/src/nmodl/ext/tclap/SwitchArg.h new file mode 100755 index 0000000000..3916109069 --- /dev/null +++ b/src/nmodl/ext/tclap/SwitchArg.h @@ -0,0 +1,266 @@ + +/****************************************************************************** + * + * file: SwitchArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_SWITCH_ARG_H +#define TCLAP_SWITCH_ARG_H + +#include +#include + +#include + +namespace TCLAP { + +/** + * A simple switch argument. If the switch is set on the command line, then + * the getValue method will return the opposite of the default value for the + * switch. + */ +class SwitchArg : public Arg +{ + protected: + + /** + * The value of the switch. + */ + bool _value; + + /** + * Used to support the reset() method so that ValueArg can be + * reset to their constructed value. + */ + bool _default; + + public: + + /** + * SwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param def - The default value for this Switch. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool def = false, + Visitor* v = NULL); + + + /** + * SwitchArg constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param parser - A CmdLine parser object to add this Arg to + * \param def - The default value for this Switch. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + bool def = false, + Visitor* v = NULL); + + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed + * in from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Checks a string to see if any of the chars in the string + * match the flag for this Switch. + */ + bool combinedSwitchesMatch(std::string& combined); + + /** + * Returns bool, whether or not the switch has been set. + */ + bool getValue(); + + virtual void reset(); + + private: + /** + * Checks to see if we've found the last match in + * a combined string. + */ + bool lastCombined(std::string& combined); + + /** + * Does the common processing of processArg. + */ + void commonProcessing(); +}; + +////////////////////////////////////////////////////////////////////// +//BEGIN SwitchArg.cpp +////////////////////////////////////////////////////////////////////// +inline SwitchArg::SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool default_val, + Visitor* v ) +: Arg(flag, name, desc, false, false, v), + _value( default_val ), + _default( default_val ) +{ } + +inline SwitchArg::SwitchArg(const std::string& flag, + const std::string& name, + const std::string& desc, + CmdLineInterface& parser, + bool default_val, + Visitor* v ) +: Arg(flag, name, desc, false, false, v), + _value( default_val ), + _default(default_val) +{ + parser.add( this ); +} + +inline bool SwitchArg::getValue() { return _value; } + +inline bool SwitchArg::lastCombined(std::string& combinedSwitches ) +{ + for ( unsigned int i = 1; i < combinedSwitches.length(); i++ ) + if ( combinedSwitches[i] != Arg::blankChar() ) + return false; + + return true; +} + +inline bool SwitchArg::combinedSwitchesMatch(std::string& combinedSwitches ) +{ + // make sure this is actually a combined switch + if ( combinedSwitches.length() > 0 && + combinedSwitches[0] != Arg::flagStartString()[0] ) + return false; + + // make sure it isn't a long name + if ( combinedSwitches.substr( 0, Arg::nameStartString().length() ) == + Arg::nameStartString() ) + return false; + + // make sure the delimiter isn't in the string + if ( combinedSwitches.find_first_of( Arg::delimiter() ) != std::string::npos ) + return false; + + // ok, we're not specifying a ValueArg, so we know that we have + // a combined switch list. + for ( unsigned int i = 1; i < combinedSwitches.length(); i++ ) + if ( _flag.length() > 0 && + combinedSwitches[i] == _flag[0] && + _flag[0] != Arg::flagStartString()[0] ) + { + // update the combined switches so this one is no longer present + // this is necessary so that no unlabeled args are matched + // later in the processing. + //combinedSwitches.erase(i,1); + combinedSwitches[i] = Arg::blankChar(); + return true; + } + + // none of the switches passed in the list match. + return false; +} + +inline void SwitchArg::commonProcessing() +{ + if ( _xorSet ) + throw(CmdLineParseException( + "Mutually exclusive argument already set!", toString())); + + if ( _alreadySet ) + throw(CmdLineParseException("Argument already set!", toString())); + + _alreadySet = true; + + if ( _value == true ) + _value = false; + else + _value = true; + + _checkWithVisitor(); +} + +inline bool SwitchArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + // if the whole string matches the flag or name string + if ( argMatches( args[*i] ) ) + { + commonProcessing(); + + return true; + } + // if a substring matches the flag as part of a combination + else if ( combinedSwitchesMatch( args[*i] ) ) + { + // check again to ensure we don't misinterpret + // this as a MultiSwitchArg + if ( combinedSwitchesMatch( args[*i] ) ) + throw(CmdLineParseException("Argument already set!", + toString())); + + commonProcessing(); + + // We only want to return true if we've found the last combined + // match in the string, otherwise we return true so that other + // switches in the combination will have a chance to match. + return lastCombined( args[*i] ); + } + else + return false; +} + +inline void SwitchArg::reset() +{ + Arg::reset(); + _value = _default; +} +////////////////////////////////////////////////////////////////////// +//End SwitchArg.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif diff --git a/src/nmodl/ext/tclap/UnlabeledMultiArg.h b/src/nmodl/ext/tclap/UnlabeledMultiArg.h new file mode 100755 index 0000000000..d5e1781060 --- /dev/null +++ b/src/nmodl/ext/tclap/UnlabeledMultiArg.h @@ -0,0 +1,301 @@ + +/****************************************************************************** + * + * file: UnlabeledMultiArg.h + * + * Copyright (c) 2003, Michael E. Smoot. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_MULTIPLE_UNLABELED_ARGUMENT_H +#define TCLAP_MULTIPLE_UNLABELED_ARGUMENT_H + +#include +#include + +#include +#include + +namespace TCLAP { + +/** + * Just like a MultiArg, except that the arguments are unlabeled. Basically, + * this Arg will slurp up everything that hasn't been matched to another + * Arg. + */ +template +class UnlabeledMultiArg : public MultiArg +{ + + // If compiler has two stage name lookup (as gcc >= 3.4 does) + // this is requried to prevent undef. symbols + using MultiArg::_ignoreable; + using MultiArg::_hasBlanks; + using MultiArg::_extractValue; + using MultiArg::_typeDesc; + using MultiArg::_name; + using MultiArg::_description; + using MultiArg::_alreadySet; + using MultiArg::toString; + + public: + + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + bool ignoreable = false, + Visitor* v = NULL ); + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * Constructor. + * \param name - The name of the Arg. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Whether or not this argument can be ignored + * using the "--" flag. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + UnlabeledMultiArg( const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. It knows the difference + * between labeled and unlabeled. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns the a short id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string shortID(const std::string& val="val") const; + + /** + * Returns the a long id string. Used in the usage. + * \param val - value to be used. + */ + virtual std::string longID(const std::string& val="val") const; + + /** + * Opertor ==. + * \param a - The Arg to be compared to this. + */ + virtual bool operator==(const Arg& a) const; + + /** + * Pushes this to back of list rather than front. + * \param argList - The list this should be added to. + */ + virtual void addToList( std::list& argList ) const; +}; + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, typeDesc, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); +} + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, typeDesc, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); + parser.add( this ); +} + + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); +} + +template +UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, + const std::string& desc, + bool req, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: MultiArg("", name, desc, req, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(true, toString()); + parser.add( this ); +} + + +template +bool UnlabeledMultiArg::processArg(int *i, std::vector& args) +{ + + if ( _hasBlanks( args[*i] ) ) + return false; + + // never ignore an unlabeled multi arg + + + // always take the first value, regardless of the start string + _extractValue( args[(*i)] ); + + /* + // continue taking args until we hit the end or a start string + while ( (unsigned int)(*i)+1 < args.size() && + args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 && + args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 ) + _extractValue( args[++(*i)] ); + */ + + _alreadySet = true; + + return true; +} + +template +std::string UnlabeledMultiArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return std::string("<") + _typeDesc + "> ..."; +} + +template +std::string UnlabeledMultiArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return std::string("<") + _typeDesc + "> (accepted multiple times)"; +} + +template +bool UnlabeledMultiArg::operator==(const Arg& a) const +{ + if ( _name == a.getName() || _description == a.getDescription() ) + return true; + else + return false; +} + +template +void UnlabeledMultiArg::addToList( std::list& argList ) const +{ + argList.push_back( const_cast(static_cast(this)) ); +} + +} + +#endif diff --git a/src/nmodl/ext/tclap/UnlabeledValueArg.h b/src/nmodl/ext/tclap/UnlabeledValueArg.h new file mode 100755 index 0000000000..5721d61252 --- /dev/null +++ b/src/nmodl/ext/tclap/UnlabeledValueArg.h @@ -0,0 +1,340 @@ + +/****************************************************************************** + * + * file: UnlabeledValueArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_UNLABELED_VALUE_ARGUMENT_H +#define TCLAP_UNLABELED_VALUE_ARGUMENT_H + +#include +#include + +#include +#include + + +namespace TCLAP { + +/** + * The basic unlabeled argument that parses a value. + * This is a template class, which means the type T defines the type + * that a given object will attempt to parse when an UnlabeledValueArg + * is reached in the list of args that the CmdLine iterates over. + */ +template +class UnlabeledValueArg : public ValueArg +{ + + // If compiler has two stage name lookup (as gcc >= 3.4 does) + // this is requried to prevent undef. symbols + using ValueArg::_ignoreable; + using ValueArg::_hasBlanks; + using ValueArg::_extractValue; + using ValueArg::_typeDesc; + using ValueArg::_name; + using ValueArg::_description; + using ValueArg::_alreadySet; + using ValueArg::toString; + + public: + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + bool ignoreable = false, + Visitor* v = NULL); + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL ); + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + bool ignoreable = false, + Visitor* v = NULL ); + + + /** + * UnlabeledValueArg constructor. + * \param name - A one word name for the argument. Note that this is used for + * identification, not as a long flag. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to + * \param ignoreable - Allows you to specify that this argument can be + * ignored if the '--' flag is set. This defaults to false (cannot + * be ignored) and should generally stay that way unless you have + * some special need for certain arguments to be ignored. + * \param v - Optional Vistor. You should leave this blank unless + * you have a very good reason. + */ + UnlabeledValueArg( const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable = false, + Visitor* v = NULL); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. Handling specific to + * unlabled arguments. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Overrides shortID for specific behavior. + */ + virtual std::string shortID(const std::string& val="val") const; + + /** + * Overrides longID for specific behavior. + */ + virtual std::string longID(const std::string& val="val") const; + + /** + * Overrides operator== for specific behavior. + */ + virtual bool operator==(const Arg& a ) const; + + /** + * Instead of pushing to the front of list, push to the back. + * \param argList - The list to add this to. + */ + virtual void addToList( std::list& argList ) const; + +}; + +/** + * Constructor implemenation. + */ +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, typeDesc, v) +{ + _ignoreable = ignoreable; + + OptionalUnlabeledTracker::check(req, toString()); + +} + +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, typeDesc, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(req, toString()); + parser.add( this ); +} + +/** + * Constructor implemenation. + */ +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(req, toString()); +} + +template +UnlabeledValueArg::UnlabeledValueArg(const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + CmdLineInterface& parser, + bool ignoreable, + Visitor* v) +: ValueArg("", name, desc, req, val, constraint, v) +{ + _ignoreable = ignoreable; + OptionalUnlabeledTracker::check(req, toString()); + parser.add( this ); +} + +/** + * Implementation of processArg(). + */ +template +bool UnlabeledValueArg::processArg(int *i, std::vector& args) +{ + + if ( _alreadySet ) + return false; + + if ( _hasBlanks( args[*i] ) ) + return false; + + // never ignore an unlabeled arg + + _extractValue( args[*i] ); + _alreadySet = true; + return true; +} + +/** + * Overriding shortID for specific output. + */ +template +std::string UnlabeledValueArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return std::string("<") + _typeDesc + ">"; +} + +/** + * Overriding longID for specific output. + */ +template +std::string UnlabeledValueArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + + // Ideally we would like to be able to use RTTI to return the name + // of the type required for this argument. However, g++ at least, + // doesn't appear to return terribly useful "names" of the types. + return std::string("<") + _typeDesc + ">"; +} + +/** + * Overriding operator== for specific behavior. + */ +template +bool UnlabeledValueArg::operator==(const Arg& a ) const +{ + if ( _name == a.getName() || _description == a.getDescription() ) + return true; + else + return false; +} + +template +void UnlabeledValueArg::addToList( std::list& argList ) const +{ + argList.push_back( const_cast(static_cast(this)) ); +} + +} +#endif diff --git a/src/nmodl/ext/tclap/ValueArg.h b/src/nmodl/ext/tclap/ValueArg.h new file mode 100755 index 0000000000..7ac29526b9 --- /dev/null +++ b/src/nmodl/ext/tclap/ValueArg.h @@ -0,0 +1,425 @@ +/****************************************************************************** + * + * file: ValueArg.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_VALUE_ARGUMENT_H +#define TCLAP_VALUE_ARGUMENT_H + +#include +#include + +#include +#include + +namespace TCLAP { + +/** + * The basic labeled argument that parses a value. + * This is a template class, which means the type T defines the type + * that a given object will attempt to parse when the flag/name is matched + * on the command line. While there is nothing stopping you from creating + * an unflagged ValueArg, it is unwise and would cause significant problems. + * Instead use an UnlabeledValueArg. + */ +template +class ValueArg : public Arg +{ + protected: + + /** + * The value parsed from the command line. + * Can be of any type, as long as the >> operator for the type + * is defined. + */ + T _value; + + /** + * Used to support the reset() method so that ValueArg can be + * reset to their constructed value. + */ + T _default; + + /** + * A human readable description of the type to be parsed. + * This is a hack, plain and simple. Ideally we would use RTTI to + * return the name of type T, but until there is some sort of + * consistent support for human readable names, we are left to our + * own devices. + */ + std::string _typeDesc; + + /** + * A Constraint this Arg must conform to. + */ + Constraint* _constraint; + + /** + * Extracts the value from the string. + * Attempts to parse string as type T, if this fails an exception + * is thrown. + * \param val - value to be parsed. + */ + void _extractValue( const std::string& val ); + + public: + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + Visitor* v = NULL); + + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param typeDesc - A short, human readable description of the + * type that this object expects. This is used in the generation + * of the USAGE statement. The goal is to be helpful to the end user + * of the program. + * \param parser - A CmdLine parser object to add this Arg to + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param parser - A CmdLine parser object to add this Arg to. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v = NULL ); + + /** + * Labeled ValueArg constructor. + * You could conceivably call this constructor with a blank flag, + * but that would make you a bad person. It would also cause + * an exception to be thrown. If you want an unlabeled argument, + * use the other constructor. + * \param flag - The one character flag that identifies this + * argument on the command line. + * \param name - A one word name for the argument. Can be + * used as a long flag on the command line. + * \param desc - A description of what the argument is for or + * does. + * \param req - Whether the argument is required on the command + * line. + * \param value - The default value assigned to this argument if it + * is not present on the command line. + * \param constraint - A pointer to a Constraint object used + * to constrain this Arg. + * \param v - An optional visitor. You probably should not + * use this unless you have a very good reason. + */ + ValueArg( const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T value, + Constraint* constraint, + Visitor* v = NULL ); + + /** + * Handles the processing of the argument. + * This re-implements the Arg version of this method to set the + * _value of the argument appropriately. It knows the difference + * between labeled and unlabeled. + * \param i - Pointer the the current argument in the list. + * \param args - Mutable list of strings. Passed + * in from main(). + */ + virtual bool processArg(int* i, std::vector& args); + + /** + * Returns the value of the argument. + */ + T& getValue() ; + + /** + * Specialization of shortID. + * \param val - value to be used. + */ + virtual std::string shortID(const std::string& val = "val") const; + + /** + * Specialization of longID. + * \param val - value to be used. + */ + virtual std::string longID(const std::string& val = "val") const; + + virtual void reset() ; + +private: + /** + * Prevent accidental copying + */ + ValueArg(const ValueArg& rhs); + ValueArg& operator=(const ValueArg& rhs); +}; + + +/** + * Constructor implementation. + */ +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( typeDesc ), + _constraint( NULL ) +{ } + +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + const std::string& typeDesc, + CmdLineInterface& parser, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( typeDesc ), + _constraint( NULL ) +{ + parser.add( this ); +} + +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( constraint->shortID() ), + _constraint( constraint ) +{ } + +template +ValueArg::ValueArg(const std::string& flag, + const std::string& name, + const std::string& desc, + bool req, + T val, + Constraint* constraint, + CmdLineInterface& parser, + Visitor* v) +: Arg(flag, name, desc, req, true, v), + _value( val ), + _default( val ), + _typeDesc( constraint->shortID() ), + _constraint( constraint ) +{ + parser.add( this ); +} + + +/** + * Implementation of getValue(). + */ +template +T& ValueArg::getValue() { return _value; } + +/** + * Implementation of processArg(). + */ +template +bool ValueArg::processArg(int *i, std::vector& args) +{ + if ( _ignoreable && Arg::ignoreRest() ) + return false; + + if ( _hasBlanks( args[*i] ) ) + return false; + + std::string flag = args[*i]; + + std::string value = ""; + trimFlag( flag, value ); + + if ( argMatches( flag ) ) + { + if ( _alreadySet ) + { + if ( _xorSet ) + throw( CmdLineParseException( + "Mutually exclusive argument already set!", + toString()) ); + else + throw( CmdLineParseException("Argument already set!", + toString()) ); + } + + if ( Arg::delimiter() != ' ' && value == "" ) + throw( ArgParseException( + "Couldn't find delimiter for this argument!", + toString() ) ); + + if ( value == "" ) + { + (*i)++; + if ( static_cast(*i) < args.size() ) + _extractValue( args[*i] ); + else + throw( ArgParseException("Missing a value for this argument!", + toString() ) ); + } + else + _extractValue( value ); + + _alreadySet = true; + _checkWithVisitor(); + return true; + } + else + return false; +} + +/** + * Implementation of shortID. + */ +template +std::string ValueArg::shortID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::shortID( _typeDesc ); +} + +/** + * Implementation of longID. + */ +template +std::string ValueArg::longID(const std::string& val) const +{ + static_cast(val); // Ignore input, don't warn + return Arg::longID( _typeDesc ); +} + +template +void ValueArg::_extractValue( const std::string& val ) +{ + try { + ExtractValue(_value, val, typename ArgTraits::ValueCategory()); + } catch( ArgParseException &e) { + throw ArgParseException(e.error(), toString()); + } + + if ( _constraint != NULL ) + if ( ! _constraint->check( _value ) ) + throw( CmdLineParseException( "Value '" + val + + + "' does not meet constraint: " + + _constraint->description(), + toString() ) ); +} + +template +void ValueArg::reset() +{ + Arg::reset(); + _value = _default; +} + +} // namespace TCLAP + +#endif diff --git a/src/nmodl/ext/tclap/ValuesConstraint.h b/src/nmodl/ext/tclap/ValuesConstraint.h new file mode 100755 index 0000000000..cb41f645e5 --- /dev/null +++ b/src/nmodl/ext/tclap/ValuesConstraint.h @@ -0,0 +1,148 @@ + + +/****************************************************************************** + * + * file: ValuesConstraint.h + * + * Copyright (c) 2005, Michael E. Smoot + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_VALUESCONSTRAINT_H +#define TCLAP_VALUESCONSTRAINT_H + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include +#else +#define HAVE_SSTREAM +#endif + +#if defined(HAVE_SSTREAM) +#include +#elif defined(HAVE_STRSTREAM) +#include +#else +#error "Need a stringstream (sstream or strstream) to compile!" +#endif + +namespace TCLAP { + +/** + * A Constraint that constrains the Arg to only those values specified + * in the constraint. + */ +template +class ValuesConstraint : public Constraint +{ + + public: + + /** + * Constructor. + * \param allowed - vector of allowed values. + */ + ValuesConstraint(std::vector& allowed); + + /** + * Virtual destructor. + */ + virtual ~ValuesConstraint() {} + + /** + * Returns a description of the Constraint. + */ + virtual std::string description() const; + + /** + * Returns the short ID for the Constraint. + */ + virtual std::string shortID() const; + + /** + * The method used to verify that the value parsed from the command + * line meets the constraint. + * \param value - The value that will be checked. + */ + virtual bool check(const T& value) const; + + protected: + + /** + * The list of valid values. + */ + std::vector _allowed; + + /** + * The string used to describe the allowed values of this constraint. + */ + std::string _typeDesc; + +}; + +template +ValuesConstraint::ValuesConstraint(std::vector& allowed) +: _allowed(allowed), + _typeDesc("") +{ + for ( unsigned int i = 0; i < _allowed.size(); i++ ) + { + +#if defined(HAVE_SSTREAM) + std::ostringstream os; +#elif defined(HAVE_STRSTREAM) + std::ostrstream os; +#else +#error "Need a stringstream (sstream or strstream) to compile!" +#endif + + os << _allowed[i]; + + std::string temp( os.str() ); + + if ( i > 0 ) + _typeDesc += "|"; + _typeDesc += temp; + } +} + +template +bool ValuesConstraint::check( const T& val ) const +{ + if ( std::find(_allowed.begin(),_allowed.end(),val) == _allowed.end() ) + return false; + else + return true; +} + +template +std::string ValuesConstraint::shortID() const +{ + return _typeDesc; +} + +template +std::string ValuesConstraint::description() const +{ + return _typeDesc; +} + + +} //namespace TCLAP +#endif + diff --git a/src/nmodl/ext/tclap/VersionVisitor.h b/src/nmodl/ext/tclap/VersionVisitor.h new file mode 100755 index 0000000000..c110d4fa00 --- /dev/null +++ b/src/nmodl/ext/tclap/VersionVisitor.h @@ -0,0 +1,81 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: VersionVisitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_VERSION_VISITOR_H +#define TCLAP_VERSION_VISITOR_H + +#include +#include +#include + +namespace TCLAP { + +/** + * A Vistor that will call the version method of the given CmdLineOutput + * for the specified CmdLine object and then exit. + */ +class VersionVisitor: public Visitor +{ + private: + /** + * Prevent accidental copying + */ + VersionVisitor(const VersionVisitor& rhs); + VersionVisitor& operator=(const VersionVisitor& rhs); + + protected: + + /** + * The CmdLine of interest. + */ + CmdLineInterface* _cmd; + + /** + * The output object. + */ + CmdLineOutput** _out; + + public: + + /** + * Constructor. + * \param cmd - The CmdLine the output is generated for. + * \param out - The type of output. + */ + VersionVisitor( CmdLineInterface* cmd, CmdLineOutput** out ) + : Visitor(), _cmd( cmd ), _out( out ) { } + + /** + * Calls the version method of the output object using the + * specified CmdLine. + */ + void visit() { + (*_out)->version(*_cmd); + throw ExitException(0); + } + +}; + +} + +#endif diff --git a/src/nmodl/ext/tclap/Visitor.h b/src/nmodl/ext/tclap/Visitor.h new file mode 100755 index 0000000000..38ddcbdb86 --- /dev/null +++ b/src/nmodl/ext/tclap/Visitor.h @@ -0,0 +1,53 @@ + +/****************************************************************************** + * + * file: Visitor.h + * + * Copyright (c) 2003, Michael E. Smoot . + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + + +#ifndef TCLAP_VISITOR_H +#define TCLAP_VISITOR_H + +namespace TCLAP { + +/** + * A base class that defines the interface for visitors. + */ +class Visitor +{ + public: + + /** + * Constructor. Does nothing. + */ + Visitor() { } + + /** + * Destructor. Does nothing. + */ + virtual ~Visitor() { } + + /** + * Does nothing. Should be overridden by child. + */ + virtual void visit() { } +}; + +} + +#endif diff --git a/src/nmodl/ext/tclap/XorHandler.h b/src/nmodl/ext/tclap/XorHandler.h new file mode 100755 index 0000000000..d9dfad31f6 --- /dev/null +++ b/src/nmodl/ext/tclap/XorHandler.h @@ -0,0 +1,166 @@ + +/****************************************************************************** + * + * file: XorHandler.h + * + * Copyright (c) 2003, Michael E. Smoot . + * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_XORHANDLER_H +#define TCLAP_XORHANDLER_H + +#include +#include +#include +#include +#include + +namespace TCLAP { + +/** + * This class handles lists of Arg's that are to be XOR'd on the command + * line. This is used by CmdLine and you shouldn't ever use it. + */ +class XorHandler +{ + protected: + + /** + * The list of of lists of Arg's to be or'd together. + */ + std::vector< std::vector > _orList; + + public: + + /** + * Constructor. Does nothing. + */ + XorHandler( ) : _orList(std::vector< std::vector >()) {} + + /** + * Add a list of Arg*'s that will be orred together. + * \param ors - list of Arg* that will be xor'd. + */ + void add( std::vector& ors ); + + /** + * Checks whether the specified Arg is in one of the xor lists and + * if it does match one, returns the size of the xor list that the + * Arg matched. If the Arg matches, then it also sets the rest of + * the Arg's in the list. You shouldn't use this. + * \param a - The Arg to be checked. + */ + int check( const Arg* a ); + + /** + * Returns the XOR specific short usage. + */ + std::string shortUsage(); + + /** + * Prints the XOR specific long usage. + * \param os - Stream to print to. + */ + void printLongUsage(std::ostream& os); + + /** + * Simply checks whether the Arg is contained in one of the arg + * lists. + * \param a - The Arg to be checked. + */ + bool contains( const Arg* a ); + + std::vector< std::vector >& getXorList(); + +}; + + +////////////////////////////////////////////////////////////////////// +//BEGIN XOR.cpp +////////////////////////////////////////////////////////////////////// +inline void XorHandler::add( std::vector& ors ) +{ + _orList.push_back( ors ); +} + +inline int XorHandler::check( const Arg* a ) +{ + // iterate over each XOR list + for ( int i = 0; static_cast(i) < _orList.size(); i++ ) + { + // if the XOR list contains the arg.. + ArgVectorIterator ait = std::find( _orList[i].begin(), + _orList[i].end(), a ); + if ( ait != _orList[i].end() ) + { + // first check to see if a mutually exclusive switch + // has not already been set + for ( ArgVectorIterator it = _orList[i].begin(); + it != _orList[i].end(); + it++ ) + if ( a != (*it) && (*it)->isSet() ) + throw(CmdLineParseException( + "Mutually exclusive argument already set!", + (*it)->toString())); + + // go through and set each arg that is not a + for ( ArgVectorIterator it = _orList[i].begin(); + it != _orList[i].end(); + it++ ) + if ( a != (*it) ) + (*it)->xorSet(); + + // return the number of required args that have now been set + if ( (*ait)->allowMore() ) + return 0; + else + return static_cast(_orList[i].size()); + } + } + + if ( a->isRequired() ) + return 1; + else + return 0; +} + +inline bool XorHandler::contains( const Arg* a ) +{ + for ( int i = 0; static_cast(i) < _orList.size(); i++ ) + for ( ArgVectorIterator it = _orList[i].begin(); + it != _orList[i].end(); + it++ ) + if ( a == (*it) ) + return true; + + return false; +} + +inline std::vector< std::vector >& XorHandler::getXorList() +{ + return _orList; +} + + + +////////////////////////////////////////////////////////////////////// +//END XOR.cpp +////////////////////////////////////////////////////////////////////// + +} //namespace TCLAP + +#endif diff --git a/src/nmodl/ext/tclap/ZshCompletionOutput.h b/src/nmodl/ext/tclap/ZshCompletionOutput.h new file mode 100755 index 0000000000..0b37fc7296 --- /dev/null +++ b/src/nmodl/ext/tclap/ZshCompletionOutput.h @@ -0,0 +1,323 @@ +// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- + +/****************************************************************************** + * + * file: ZshCompletionOutput.h + * + * Copyright (c) 2006, Oliver Kiddle + * All rights reverved. + * + * See the file COPYING in the top directory of this distribution for + * more information. + * + * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + *****************************************************************************/ + +#ifndef TCLAP_ZSHCOMPLETIONOUTPUT_H +#define TCLAP_ZSHCOMPLETIONOUTPUT_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace TCLAP { + +/** + * A class that generates a Zsh completion function as output from the usage() + * method for the given CmdLine and its Args. + */ +class ZshCompletionOutput : public CmdLineOutput +{ + + public: + + ZshCompletionOutput(); + + /** + * Prints the usage to stdout. Can be overridden to + * produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void usage(CmdLineInterface& c); + + /** + * Prints the version to stdout. Can be overridden + * to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + */ + virtual void version(CmdLineInterface& c); + + /** + * Prints (to stderr) an error message, short usage + * Can be overridden to produce alternative behavior. + * \param c - The CmdLine object the output is generated for. + * \param e - The ArgException that caused the failure. + */ + virtual void failure(CmdLineInterface& c, + ArgException& e ); + + protected: + + void basename( std::string& s ); + void quoteSpecialChars( std::string& s ); + + std::string getMutexList( CmdLineInterface& _cmd, Arg* a ); + void printOption( Arg* it, std::string mutex ); + void printArg( Arg* it ); + + std::map common; + char theDelimiter; +}; + +ZshCompletionOutput::ZshCompletionOutput() +: common(std::map()), + theDelimiter('=') +{ + common["host"] = "_hosts"; + common["hostname"] = "_hosts"; + common["file"] = "_files"; + common["filename"] = "_files"; + common["user"] = "_users"; + common["username"] = "_users"; + common["directory"] = "_directories"; + common["path"] = "_directories"; + common["url"] = "_urls"; +} + +inline void ZshCompletionOutput::version(CmdLineInterface& _cmd) +{ + std::cout << _cmd.getVersion() << std::endl; +} + +inline void ZshCompletionOutput::usage(CmdLineInterface& _cmd ) +{ + std::list argList = _cmd.getArgList(); + std::string progName = _cmd.getProgramName(); + std::string xversion = _cmd.getVersion(); + theDelimiter = _cmd.getDelimiter(); + basename(progName); + + std::cout << "#compdef " << progName << std::endl << std::endl << + "# " << progName << " version " << _cmd.getVersion() << std::endl << std::endl << + "_arguments -s -S"; + + for (ArgListIterator it = argList.begin(); it != argList.end(); it++) + { + if ( (*it)->shortID().at(0) == '<' ) + printArg((*it)); + else if ( (*it)->getFlag() != "-" ) + printOption((*it), getMutexList(_cmd, *it)); + } + + std::cout << std::endl; +} + +inline void ZshCompletionOutput::failure( CmdLineInterface& _cmd, + ArgException& e ) +{ + static_cast(_cmd); // unused + std::cout << e.what() << std::endl; +} + +inline void ZshCompletionOutput::quoteSpecialChars( std::string& s ) +{ + size_t idx = s.find_last_of(':'); + while ( idx != std::string::npos ) + { + s.insert(idx, 1, '\\'); + idx = s.find_last_of(':', idx); + } + idx = s.find_last_of('\''); + while ( idx != std::string::npos ) + { + s.insert(idx, "'\\'"); + if (idx == 0) + idx = std::string::npos; + else + idx = s.find_last_of('\'', --idx); + } +} + +inline void ZshCompletionOutput::basename( std::string& s ) +{ + size_t p = s.find_last_of('/'); + if ( p != std::string::npos ) + { + s.erase(0, p + 1); + } +} + +inline void ZshCompletionOutput::printArg(Arg* a) +{ + static int count = 1; + + std::cout << " \\" << std::endl << " '"; + if ( a->acceptsMultipleValues() ) + std::cout << '*'; + else + std::cout << count++; + std::cout << ':'; + if ( !a->isRequired() ) + std::cout << ':'; + + std::cout << a->getName() << ':'; + std::map::iterator compArg = common.find(a->getName()); + if ( compArg != common.end() ) + { + std::cout << compArg->second; + } + else + { + std::cout << "_guard \"^-*\" " << a->getName(); + } + std::cout << '\''; +} + +inline void ZshCompletionOutput::printOption(Arg* a, std::string mutex) +{ + std::string flag = a->flagStartChar() + a->getFlag(); + std::string name = a->nameStartString() + a->getName(); + std::string desc = a->getDescription(); + + // remove full stop and capitalisation from description as + // this is the convention for zsh function + if (!desc.compare(0, 12, "(required) ")) + { + desc.erase(0, 12); + } + if (!desc.compare(0, 15, "(OR required) ")) + { + desc.erase(0, 15); + } + size_t len = desc.length(); + if (len && desc.at(--len) == '.') + { + desc.erase(len); + } + if (len) + { + desc.replace(0, 1, 1, tolower(desc.at(0))); + } + + std::cout << " \\" << std::endl << " '" << mutex; + + if ( a->getFlag().empty() ) + { + std::cout << name; + } + else + { + std::cout << "'{" << flag << ',' << name << "}'"; + } + if ( theDelimiter == '=' && a->isValueRequired() ) + std::cout << "=-"; + quoteSpecialChars(desc); + std::cout << '[' << desc << ']'; + + if ( a->isValueRequired() ) + { + std::string arg = a->shortID(); + arg.erase(0, arg.find_last_of(theDelimiter) + 1); + if ( arg.at(arg.length()-1) == ']' ) + arg.erase(arg.length()-1); + if ( arg.at(arg.length()-1) == ']' ) + { + arg.erase(arg.length()-1); + } + if ( arg.at(0) == '<' ) + { + arg.erase(arg.length()-1); + arg.erase(0, 1); + } + size_t p = arg.find('|'); + if ( p != std::string::npos ) + { + do + { + arg.replace(p, 1, 1, ' '); + } + while ( (p = arg.find_first_of('|', p)) != std::string::npos ); + quoteSpecialChars(arg); + std::cout << ": :(" << arg << ')'; + } + else + { + std::cout << ':' << arg; + std::map::iterator compArg = common.find(arg); + if ( compArg != common.end() ) + { + std::cout << ':' << compArg->second; + } + } + } + + std::cout << '\''; +} + +inline std::string ZshCompletionOutput::getMutexList( CmdLineInterface& _cmd, Arg* a) +{ + XorHandler xorHandler = _cmd.getXorHandler(); + std::vector< std::vector > xorList = xorHandler.getXorList(); + + if (a->getName() == "help" || a->getName() == "version") + { + return "(-)"; + } + + std::ostringstream list; + if ( a->acceptsMultipleValues() ) + { + list << '*'; + } + + for ( int i = 0; static_cast(i) < xorList.size(); i++ ) + { + for ( ArgVectorIterator it = xorList[i].begin(); + it != xorList[i].end(); + it++) + if ( a == (*it) ) + { + list << '('; + for ( ArgVectorIterator iu = xorList[i].begin(); + iu != xorList[i].end(); + iu++ ) + { + bool notCur = (*iu) != a; + bool hasFlag = !(*iu)->getFlag().empty(); + if ( iu != xorList[i].begin() && (notCur || hasFlag) ) + list << ' '; + if (hasFlag) + list << (*iu)->flagStartChar() << (*iu)->getFlag() << ' '; + if ( notCur || hasFlag ) + list << (*iu)->nameStartString() << (*iu)->getName(); + } + list << ')'; + return list.str(); + } + } + + // wasn't found in xor list + if (!a->getFlag().empty()) { + list << "(" << a->flagStartChar() << a->getFlag() << ' ' << + a->nameStartString() << a->getName() << ')'; + } + + return list.str(); +} + +} //namespace TCLAP +#endif From 9eff853faaebe160a5fb4965271d2c995cfbde87 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Thu, 2 Nov 2017 18:24:30 +0100 Subject: [PATCH 009/871] Integrate CMake changes and minor stringutils changes CMake refactored to account for separate lexer, parser and nocmodl binaries. Note that lexer needs parser and ast because we pass NMODL driver object as a parameter to lexer. This is required when we have decide about the macros. Change-Id: I30db1b7183ad5be82f10075ce6d8678de44c2e92 NMODL Repo SHA: BlueBrain/nmodl@5977d137fa0d179485c157082bed9f1f20310bbc --- cmake/nmodl/CMakeLists.txt | 61 ++++++------------------ src/nmodl/ast/ast.hpp | 2 +- src/nmodl/lexer/CMakeLists.txt | 82 ++++++++++++++++++++++++++++++++ src/nmodl/lexer/main.cpp | 2 +- src/nmodl/lexer/nmodl.ll | 2 +- src/nmodl/lexer/nmodl_utils.cpp | 2 +- src/nmodl/parser/CMakeLists.txt | 4 ++ src/nmodl/parser/main.cpp | 2 +- src/nmodl/utils/string_utils.hpp | 64 ++++++++++++------------- 9 files changed, 137 insertions(+), 84 deletions(-) create mode 100644 src/nmodl/lexer/CMakeLists.txt create mode 100644 src/nmodl/parser/CMakeLists.txt diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index b5410bbd49..b23911db60 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,13 +1,13 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) - project(nocmodl CXX) + set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 1) - set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) message(STATUS "CHECKING FOR FLEX/BISON/PYTHON") @@ -16,62 +16,29 @@ find_package(BISON 3.0 REQUIRED) find_package(FLEX 2.6 REQUIRED) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -include(GitRevision) -include_directories(${PROJECT_SOURCE_DIR}/src) +include(GitRevision) +include_directories( + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_SOURCE_DIR}/src/ext/ +) set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) -# generate source file with version number +# generate file with version number from git configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) -# command to generate lexer -add_custom_command( - COMMAND ${FLEX_EXECUTABLE} - ARGS ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl.l - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl_lexer.cpp - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl_lexer.hpp - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/lexer_utils.hpp - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/ - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/lexer/nmodl.l - COMMENT "-- NOCMODL : GENERATING NMODL LEXER WITH FLEX! --" -) +add_subdirectory(src/lexer) +add_subdirectory(src/parser) -# command to generate parser -add_custom_command( - COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl_parser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl.y - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl_parser.cpp - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl_parser.hpp - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/src/parser/nmodl.y - COMMENT "-- NOCMODL : GENERATING NMODL PARSER WITH BISON! --" -) - -set(LEXER_SOURCE_FILES - src/lexer/init.cpp - src/lexer/list.cpp - src/lexer/nmodl_lexer.cpp - src/lexer/nmodl.l -) - -set(PARSER_SOURCE_FILES - src/parser/nmodl_parser.cpp - src/parser/nmodl.y - src/ast/astutils.hpp - src/ast/ast.hpp - src/ast/ast.cpp -) - - -add_executable(${PROJECT_NAME} - ${LEXER_SOURCE_FILES} - ${PARSER_SOURCE_FILES} +add_executable(nocmodl src/main.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) +target_link_libraries(nocmodl lexer) + add_custom_target(clangformat COMMAND clang-format -i - --style=file ${FILES_FOR_CLANG_FORMAT}) + --style=file ${FILES_FOR_CLANG_FORMAT}) \ No newline at end of file diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp index a96b4ecc75..7e7f4dc82d 100644 --- a/src/nmodl/ast/ast.hpp +++ b/src/nmodl/ast/ast.hpp @@ -5,8 +5,8 @@ #include #include "ast/ast_utils.hpp" +#include "utils/common_utils.hpp" #include "lexer/modtoken.hpp" -#include "utils/commonutils.hpp" /* all classes representing Abstract Syntax Tree (AST) nodes */ namespace ast { diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt new file mode 100644 index 0000000000..bf83bf77c4 --- /dev/null +++ b/src/nmodl/lexer/CMakeLists.txt @@ -0,0 +1,82 @@ +# Important : make sure to have latest flex header +# included otherwise get strange compilation issue +include_directories(${FLEX_INCLUDE_DIRS}) + +set (BISON_GENERATED_SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp +) + +set(AST_SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp +) + +set(NMODL_DRIVER_FILES + ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp +) + +# command to generate nmodl parser +add_custom_command ( + COMMAND ${BISON_EXECUTABLE} + ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/location.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/position.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/stack.hh + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + COMMENT "-- NOCMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" +) + +# command to generate verbatim parser +add_custom_command ( + COMMAND ${BISON_EXECUTABLE} + ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/verbatim.y + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.hpp + COMMENT "-- NOCMODL : GENERATING VERBATIM PARSER WITH BISON! --" +) + + +# command to generate nmodl lexer +add_custom_command( + COMMAND ${FLEX_EXECUTABLE} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.cpp + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.hpp + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + COMMENT "-- NOCMODL : GENERATING NMODL LEXER WITH FLEX! --" +) + +# command to generate verbatim lexer +add_custom_command( + COMMAND ${FLEX_EXECUTABLE} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.cpp + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.hpp + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + COMMENT "-- NOCMODL : GENERATING VERBATIM LEXER WITH FLEX! --" +) + +srt(LEXER_SOURCE_FILES + token_mapping.cpp + nmodl_utils.cpp + modtoken.cpp + nmodl_base_lexer.cpp + verbatim_lexer.cpp + ${NMODL_DRIVER_FILES} +) + +add_library(lexer + STATIC + ${LEXER_SOURCE_FILES} + ${BISON_GENERATED_SOURCE_FILES} + ${AST_SOURCE_FILES}) + +add_executable(nocmodl_lexer main.cpp) +target_link_libraries(nocmodl_lexer lexer) diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main.cpp index d70109ebbe..90d9b5e53e 100644 --- a/src/nmodl/lexer/main.cpp +++ b/src/nmodl/lexer/main.cpp @@ -17,7 +17,7 @@ int main(int argc, char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Lexer: Standalone lexer program for NMODL"); TCLAP::ValueArg filearg( - "", "file", "NMODL input file path", false, "../test/input/example1.mod", "string"); + "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); cmd.add(filearg); cmd.parse(argc, argv); diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 48d37eb3e0..66cf3cd977 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -5,7 +5,7 @@ #include "lexer/nmodl_utils.hpp" #include "lexer/token_mapping.hpp" #include "parser/nmodl_driver.hpp" - #include "utils/stringutils.hpp" + #include "utils/string_utils.hpp" /** YY_USER_ACTION is called before each of token actions and * we update columns by length of the token. Node that position diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 8d242c0149..afeb879160 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -5,7 +5,7 @@ #include "lexer/modtoken.hpp" #include "lexer/nmodl_utils.hpp" #include "lexer/token_mapping.hpp" -#include "utils/stringutils.hpp" +#include "utils/string_utils.hpp" namespace nmodl { diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt new file mode 100644 index 0000000000..65052a7f63 --- /dev/null +++ b/src/nmodl/parser/CMakeLists.txt @@ -0,0 +1,4 @@ +# lexer library links with all parser related files +# so need to have parser as a separate library +add_executable(nocmodl_parser main.cpp) +target_link_libraries(nocmodl_parser lexer) \ No newline at end of file diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp index 8717810856..86a60d6609 100644 --- a/src/nmodl/parser/main.cpp +++ b/src/nmodl/parser/main.cpp @@ -16,7 +16,7 @@ int main(int argc, char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Parser: Standalone parser program for NMODL"); TCLAP::ValueArg filearg( - "", "file", "NMODL input file path", false, "../test/input/example1.mod", "string"); + "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); cmd.add(filearg); cmd.parse(argc, argv); diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 477d50a738..65df2cde6c 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -1,78 +1,78 @@ -#ifndef _UTILS_H_ -#define _UTILS_H_ +#pragma once #include -#include -#include -#include -#include #include +#include -namespace StringUtils { +/** + * \brief String manipulation functions + * + * String trimming and manipulation functions based on + * stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring + */ - /*trim implementations from - * http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring */ +namespace stringutils { - /* trim from start */ + /// Trim from start static inline std::string& ltrim(std::string& s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + s.erase(s.begin(), + std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); return s; } - /* trim from end */ + /// Trim from end static inline std::string& rtrim(std::string& s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), s.end()); + s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))) + .base(), + s.end()); return s; } - /* trim from both ends */ + /// Trim from both ends static inline std::string& trim(std::string& s) { return ltrim(rtrim(s)); } - /* remove leading newline for the string read by grammar */ + inline void remove_character(std::string& str, const char c) { + str.erase(std::remove(str.begin(), str.end(), c), str.end()); + } + + /// Remove leading newline for the string read by grammar static inline std::string& trimnewline(std::string& s) { - s.erase(std::remove(s.begin(), s.end(), '\n'), s.end()); + remove_character(s, '\n'); return s; } - /* for printing json, we have to escape double quotes */ + /// for printing json, we have to escape double quotes static inline std::string escape_quotes(const std::string& before) { std::string after; - for (std::string::size_type i = 0; i < before.length(); ++i) { - switch (before[i]) { + for (auto c : before) { + switch (c) { case '"': case '\\': after += '\\'; + /// don't break here as we want to append actual character default: - after += before[i]; + after += c; } } return after; } - inline void split(const std::string& s, char delim, std::vector& elems) { + /// Spilt string with given delimiter and returns vector + inline std::vector split_string(const std::string& s, char delim) { + std::vector elems; std::stringstream ss(s); std::string item; while (std::getline(ss, item, delim)) { elems.push_back(item); } - } - /* spilt string with given delimiter and returns vector */ - inline std::vector split_string(const std::string& s, char delim) { - std::vector elems; - split(s, delim, elems); return elems; } - inline void remove_character(std::string& str, const char c) { - str.erase(std::remove(str.begin(), str.end(), c), str.end()); - } -} // namespace StringUtils - -#endif +} // namespace stringutils From 2391a59939411e20b728728ebdf24eb0ca6098bb Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Thu, 2 Nov 2017 18:43:13 +0100 Subject: [PATCH 010/871] Adding one synapse and one channel model for testing (NaTs2_t and ProbAMPANMDA_EMS) Change-Id: Ib6a5fea22ca82c85ee17043cad66b12021f947de NMODL Repo SHA: BlueBrain/nmodl@18a15a0303519b825c20a2eca59c98ca86340a00 --- test/nmodl/transpiler/input/channel.mod | 119 +++++++++ test/nmodl/transpiler/input/synapse.mod | 321 ++++++++++++++++++++++++ 2 files changed, 440 insertions(+) create mode 100644 test/nmodl/transpiler/input/channel.mod create mode 100644 test/nmodl/transpiler/input/synapse.mod diff --git a/test/nmodl/transpiler/input/channel.mod b/test/nmodl/transpiler/input/channel.mod new file mode 100644 index 0000000000..27ddc495a1 --- /dev/null +++ b/test/nmodl/transpiler/input/channel.mod @@ -0,0 +1,119 @@ +:Reference :Colbert and Pan 2002 +:comment: took the NaTa and shifted both activation/inactivation by 6 mv + +NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar +} + +UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) +} + +PARAMETER { + gNaTs2_tbar = 0.00001 (S/cm2) +} + +ASSIGNED { + v (mV) + ena (mV) +} + +STATE { + m + h +} + +BREAKPOINT { + LOCAL gNaTs2_t, ina + CONDUCTANCE gNaTs2_t USEION na + SOLVE states METHOD cnexp + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) +} + +INITIAL{ + LOCAL mAlpha, mBeta, mInf, hAlpha, hBeta, hInf, lv + + lv = v + + if(lv == -32){ + lv = lv+0.0001 + } + + mAlpha = mAlphaf(lv) + mBeta = mBetaf(lv) + mInf = mAlpha/(mAlpha+mBeta) + m = mInf + + if(lv == -60){ + lv = lv+0.0001 + } + + hAlpha = hAlphaf(lv) + hBeta = hBetaf(lv) + hInf = hAlpha/(hAlpha+hBeta) + h = hInf + + v = lv +} + + + +DERIVATIVE states { + LOCAL mAlpha, mBeta, mInf, mTau, hAlpha, hBeta, hInf, hTau, lv, qt + + :qt = 2.3^((34-21)/10) + qt = 2.952882641412121 + + lv = v + + if(lv == -32){ + lv = lv+0.0001 + } + + mAlpha = mAlphaf(lv) + mBeta = mBetaf(lv) + mInf = mAlpha/(mAlpha+mBeta) + mTau = (1/(mAlpha+mBeta))/qt + m' = (mInf-m)/mTau + + if(lv == -60){ + lv = lv+0.0001 + } + + hAlpha = hAlphaf(lv) + hBeta = hBetaf(lv) + hInf = hAlpha/(hAlpha+hBeta) + hTau = (1/(hAlpha+hBeta))/qt + h' = (hInf-h)/hTau + + v = lv +} + +FUNCTION mAlphaf(v) { + LOCAL a, b + a = 2 + 3.4 + b = a * a + + if (a == 12) { + a = a + 0.1 + } + + mAlphaf = (0.182 * (v- -32))/(1-(exp(-(v- -32)/6))) +} + +FUNCTION mBetaf(v) { + mBetaf = (0.124 * (-v -32))/(1-(exp(-(-v -32)/6))) +} + +FUNCTION hAlphaf(v) { + hAlphaf = (-0.015 * (v- -60))/(1-(exp((v- -60)/6))) +} + +FUNCTION hBetaf(v) { + hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) +} diff --git a/test/nmodl/transpiler/input/synapse.mod b/test/nmodl/transpiler/input/synapse.mod new file mode 100644 index 0000000000..1e1065baf1 --- /dev/null +++ b/test/nmodl/transpiler/input/synapse.mod @@ -0,0 +1,321 @@ +COMMENT +/** + * @file ProbAMPANMDA_EMS.mod + * @brief + * @author king, muller, reimann, ramaswamy + * @date 2011-08-17 + * @remark Copyright © BBP/EPFL 2005-2011; All rights reserved. Do not distribute without further notice. + */ +ENDCOMMENT + +:TITLE Probabilistic AMPA and NMDA receptor with presynaptic short-term plasticity + + +COMMENT +AMPA and NMDA receptor conductance using a dual-exponential profile +presynaptic short-term plasticity as in Fuhrmann et al. 2002 + +_EMS (Eilif Michael Srikanth) +Modification of ProbAMPANMDA: 2-State model by Eilif Muller, Michael Reimann, Srikanth Ramaswamy, Blue Brain Project, August 2011 +This new model was motivated by the following constraints: + +1) No consumption on failure. +2) No release just after release until recovery. +3) Same ensemble averaged trace as deterministic/canonical Tsodyks-Markram + using same parameters determined from experiment. +4) Same quantal size as present production probabilistic model. + +To satisfy these constaints, the synapse is implemented as a +uni-vesicular (generalization to multi-vesicular should be +straight-forward) 2-state Markov process. The states are +{1=recovered, 0=unrecovered}. + +For a pre-synaptic spike or external spontaneous release trigger +event, the synapse will only release if it is in the recovered state, +and with probability u (which follows facilitation dynamics). If it +releases, it will transition to the unrecovered state. Recovery is as +a Poisson process with rate 1/Dep. + +This model satisys all of (1)-(4). + +ENDCOMMENT + +NEURON { + THREADSAFE + POINT_PROCESS ProbAMPANMDA_EMS + RANGE tau_r_AMPA, tau_d_AMPA, tau_r_NMDA, tau_d_NMDA + RANGE Use, u, Dep, Fac, u0, mg, Rstate, tsyn_fac, u + RANGE e, NMDA_ratio + RANGE A_AMPA_step, B_AMPA_step, A_NMDA_step, B_NMDA_step + NONSPECIFIC_CURRENT i + BBCOREPOINTER rng + RANGE synapseID, verboseLevel +} + +PARAMETER { + + + tau_r_AMPA = 0.2 (ms) : dual-exponential conductance profile + tau_d_AMPA = 1.7 (ms) : IMPORTANT: tau_r < tau_d + tau_r_NMDA = 0.29 (ms) : dual-exponential conductance profile + tau_d_NMDA = 43 (ms) : IMPORTANT: tau_r < tau_d + Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) + Dep = 100 (ms) : relaxation time constant from depression + Fac = 10 (ms) : relaxation time constant from facilitation + e = 0 (mV) : AMPA and NMDA reversal potential + mg = 1 (mM) : initial concentration of mg2+ + gmax = .001 (uS) : weight conversion factor (from nS to uS) + u0 = 0 :initial value of u, which is the running value of release probability + synapseID = 0 + verboseLevel = 0 + NMDA_ratio = 0.71 (1) : The ratio of NMDA to AMPA +} + +COMMENT +The Verbatim block is needed to generate random nos. from a uniform distribution between 0 and 1 +for comparison with Pr to decide whether to activate the synapse or not +ENDCOMMENT + +VERBATIM +#include "nrnran123.h" +ENDVERBATIM + + +ASSIGNED { + + v (mV) + i_AMPA (nA) + i_NMDA (nA) + g_NMDA (uS) + factor_AMPA + factor_NMDA + A_AMPA_step + B_AMPA_step + A_NMDA_step + B_NMDA_step + rng + mggate + + : Recording these three, you can observe full state of model + : tsyn_fac gives you presynaptic times, Rstate gives you + : state transitions, + : u gives you the "release probability" at transitions + : (attention: u is event based based, so only valid at incoming events) + Rstate (1) : recovered state {0=unrecovered, 1=recovered} + tsyn_fac (ms) : the time of the last spike + u (1) : running release probability + +} + +STATE { + + A_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_r_AMPA + B_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_d_AMPA + A_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_r_NMDA + B_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_d_NMDA +} + +INITIAL{ + + LOCAL tp_AMPA, tp_NMDA + + VERBATIM + if (_p_rng) + { + nrnran123_setseq((nrnran123_State*)_p_rng, 0, 0); + } + ENDVERBATIM + + Rstate=1 + tsyn_fac=0 + u=u0 + + A_AMPA = 0 + B_AMPA = 0 + + A_NMDA = 0 + B_NMDA = 0 + + tp_AMPA = (tau_r_AMPA*tau_d_AMPA)/(tau_d_AMPA-tau_r_AMPA)*log(tau_d_AMPA/tau_r_AMPA) :time to peak of the conductance + tp_NMDA = (tau_r_NMDA*tau_d_NMDA)/(tau_d_NMDA-tau_r_NMDA)*log(tau_d_NMDA/tau_r_NMDA) :time to peak of the conductance + + factor_AMPA = -exp(-tp_AMPA/tau_r_AMPA)+exp(-tp_AMPA/tau_d_AMPA) :AMPA Normalization factor - so that when t = tp_AMPA, gsyn = gpeak + factor_AMPA = 1/factor_AMPA + + factor_NMDA = -exp(-tp_NMDA/tau_r_NMDA)+exp(-tp_NMDA/tau_d_NMDA) :NMDA Normalization factor - so that when t = tp_NMDA, gsyn = gpeak + factor_NMDA = 1/factor_NMDA + + A_AMPA_step = exp(dt*(( - 1.0 ) / tau_r_AMPA)) + B_AMPA_step = exp(dt*(( - 1.0 ) / tau_d_AMPA)) + A_NMDA_step = exp(dt*(( - 1.0 ) / tau_r_NMDA)) + B_NMDA_step = exp(dt*(( - 1.0 ) / tau_d_NMDA)) +} + +BREAKPOINT { + + LOCAL mggate, g_AMPA, g_NMDA, g, i_AMPA, i_NMDA, vv, i, vve + CONDUCTANCE g_AMPA + CONDUCTANCE g_NMDA + SOLVE state + + vv = v + + :mggate = 1 / (1 + exp(0.062 (/mV) * -(vv)) * (mg / 3.57 (mM))) :mggate kinetics - Jahr & Stevens 1990 + mggate = 1 / (1 + exp(0.062 * -(vv)) * (mg / 3.57 )) :mggate kinetics - Jahr & Stevens 1990 + g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA + g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics + g = g_AMPA + g_NMDA + vve = (vv-e) + i_AMPA = g_AMPA*vve :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal + i_NMDA = g_NMDA*vve :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal + i = i_AMPA + i_NMDA +} + +PROCEDURE state() { + A_AMPA = A_AMPA*A_AMPA_step + B_AMPA = B_AMPA*B_AMPA_step + A_NMDA = A_NMDA*A_NMDA_step + B_NMDA = B_NMDA*B_NMDA_step +} + + +:NET_RECEIVE (weight,weight_AMPA, weight_NMDA, Psurv, tsyn (ms)){ +NET_RECEIVE (weight,weight_AMPA, weight_NMDA, Psurv, tsyn){ + LOCAL result + weight_AMPA = weight + weight_NMDA = weight * NMDA_ratio + : Locals: + : Psurv - survival probability of unrecovered state + : tsyn - time since last surival evaluation. + + INITIAL{ + tsyn=t + } + + : calc u at event- + if (Fac > 0) { + u = u*exp(-(t - tsyn_fac)/Fac) :update facilitation variable if Fac>0 Eq. 2 in Fuhrmann et al. + } else { + u = Use + } + if(Fac > 0){ + u = u + Use*(1-u) :update facilitation variable if Fac>0 Eq. 2 in Fuhrmann et al. + } + + : tsyn_fac knows about all spikes, not only those that released + : i.e. each spike can increase the u, regardless of recovered state. + tsyn_fac = t + + : recovery + + if (Rstate == 0) { + : probability of survival of unrecovered state based on Poisson recovery with rate 1/tau + Psurv = exp(-(t-tsyn)/Dep) + result = urand() + if (result>Psurv) { + Rstate = 1 : recover + + if( verboseLevel > 0 ) { + printf( "Recovered! %f at time %g: Psurv = %g, urand=%g\n", synapseID, t, Psurv, result ) + } + + } + else { + : survival must now be from this interval + tsyn = t + if( verboseLevel > 0 ) { + printf( "Failed to recover! %f at time %g: Psurv = %g, urand=%g\n", synapseID, t, Psurv, result ) + } + } + } + + if (Rstate == 1) { + result = urand() + if (result 0 ) { + printf( "Release! %f at time %g: vals %g %g %g %g\n", synapseID, t, A_AMPA, weight_AMPA, factor_AMPA, weight ) + } + + } + else { + if( verboseLevel > 0 ) { + printf("Failure! %f at time %g: urand = %g\n", synapseID, t, result ) + } + + } + + } + +} + +PROCEDURE setRNG() { +VERBATIM + { +#if !NRNBBCORE + nrnran123_State** pv = (nrnran123_State**)(&_p_rng); + if (*pv) { + nrnran123_deletestream(*pv); + *pv = (nrnran123_State*)0; + } + if (ifarg(2)) { + *pv = nrnran123_newstream((uint32_t)*getarg(1), (uint32_t)*getarg(2)); + } +#endif + } +ENDVERBATIM +} + +FUNCTION urand() { +VERBATIM + double value; + if (_p_rng) { + /* + :Supports separate independent but reproducible streams for + : each instance. + */ + value = nrnran123_negexp((nrnran123_State*)_p_rng); + //printf("random stream for this simulation = %lf\n",value); + return value; + }else{ + value = 0.0; +// assert(0); + } + _lurand = value; +ENDVERBATIM +} + +VERBATIM +static void bbcore_write(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + if (d) { + uint32_t* di = ((uint32_t*)d) + *offset; + nrnran123_State** pv = (nrnran123_State**)(&_p_rng); + nrnran123_getids(*pv, di, di+1); +//printf("ProbAMPANMDA_EMS bbcore_write %d %d\n", di[0], di[1]); + } + *offset += 2; +} +static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + assert(!_p_rng); + uint32_t* di = ((uint32_t*)d) + *offset; + if (di[0] != 0 || di[1] != 0) + { + nrnran123_State** pv = (nrnran123_State**)(&_p_rng); + *pv = nrnran123_newstream(di[0], di[1]); + } +//printf("ProbAMPANMDA_EMS bbcore_read %d %d\n", di[0], di[1]); + *offset += 2; +} +ENDVERBATIM + + +FUNCTION toggleVerbose() { + verboseLevel = 1-verboseLevel +} From e1cada9d334c0de81581666299df312fd5729fc7 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 3 Nov 2017 15:46:36 +0100 Subject: [PATCH 011/871] Adding unit test for lexer and modtoken using CATCH framework Change-Id: I0ee6075b8e02eb52a5ee275e6a6e838779f57e4f NMODL Repo SHA: BlueBrain/nmodl@1e3ad3cb5e09af3262cf75841f3a94c00a53bf1a --- cmake/nmodl/CMakeLists.txt | 11 ++++ src/nmodl/lexer/CMakeLists.txt | 6 +- test/nmodl/transpiler/CMakeLists.txt | 9 +++ test/nmodl/transpiler/lexer/tokens.cpp | 71 +++++++++++++++++++++ test/nmodl/transpiler/modtoken/modtoken.cpp | 54 ++++++++++++++++ 5 files changed, 146 insertions(+), 5 deletions(-) create mode 100644 test/nmodl/transpiler/CMakeLists.txt create mode 100644 test/nmodl/transpiler/lexer/tokens.cpp create mode 100644 test/nmodl/transpiler/modtoken/modtoken.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index b23911db60..eb67d8b36e 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -23,6 +23,12 @@ include_directories( ${PROJECT_SOURCE_DIR}/src/ext/ ) +# Important : make sure to have flex header included +# at top level. Otherwise get strange compilation and +# runtime memory errors due to inclusion of older version +# from /usr/include (which is always present!) +include_directories(${FLEX_INCLUDE_DIRS}) + set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) # generate file with version number from git @@ -31,6 +37,11 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in add_subdirectory(src/lexer) add_subdirectory(src/parser) +add_subdirectory(test) + +enable_testing () +add_test (NAME ModToken COMMAND testmodtoken) +add_test (NAME Lexer COMMAND testlexer) add_executable(nocmodl src/main.cpp diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index bf83bf77c4..3011254578 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -1,7 +1,3 @@ -# Important : make sure to have latest flex header -# included otherwise get strange compilation issue -include_directories(${FLEX_INCLUDE_DIRS}) - set (BISON_GENERATED_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp @@ -63,7 +59,7 @@ add_custom_command( COMMENT "-- NOCMODL : GENERATING VERBATIM LEXER WITH FLEX! --" ) -srt(LEXER_SOURCE_FILES +set(LEXER_SOURCE_FILES token_mapping.cpp nmodl_utils.cpp modtoken.cpp diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt new file mode 100644 index 0000000000..374d22c675 --- /dev/null +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -0,0 +1,9 @@ +include_directories( + ${PROJECT_SOURCE_DIR}/src/ext/catch +) + +add_executable (testmodtoken modtoken/modtoken.cpp) +add_executable (testlexer lexer/tokens.cpp) + +target_link_libraries(testmodtoken lexer) +target_link_libraries(testlexer lexer) \ No newline at end of file diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp new file mode 100644 index 0000000000..2921598cf6 --- /dev/null +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -0,0 +1,71 @@ +#define CATCH_CONFIG_MAIN + +#include + +#include "catch.hpp" +#include "lexer/modtoken.hpp" +#include "lexer/nmodl_lexer.hpp" +#include "parser/nmodl_driver.hpp" + +using Token = nmodl::Parser::token; + +/// just retrieve token type from lexer +nmodl::Parser::token_type token_type(std::string name) { + std::istringstream ss(name); + std::istream& in = ss; + + nmodl::Driver driver; + nmodl::Lexer scanner(driver, &in); + + nmodl::Parser::symbol_type sym = scanner.next_token(); + return sym.token(); +} + +TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { + SECTION("Tests for some keywords") { + REQUIRE(token_type("VERBATIM Hello ENDVERBATIM") == Token::VERBATIM); + REQUIRE(token_type("INITIAL") == Token::INITIAL1); + REQUIRE(token_type("SOLVE") == Token::SOLVE); + } + + SECTION("Tests for language constructs") { + REQUIRE(token_type(" h' = (hInf-h)/hTau\n") == Token::PRIME); + REQUIRE(token_type("while") == Token::WHILE); + REQUIRE(token_type("if") == Token::IF); + REQUIRE(token_type("else") == Token::ELSE); + REQUIRE(token_type("WHILE") == Token::WHILE); + REQUIRE(token_type("IF") == Token::IF); + REQUIRE(token_type("ELSE") == Token::ELSE); + } + + SECTION("Tests for valid numbers") { + REQUIRE(token_type("123") == Token::INTEGER); + REQUIRE(token_type("123.32") == Token::REAL); + REQUIRE(token_type("1.32E+3") == Token::REAL); + REQUIRE(token_type("1.32e-3") == Token::REAL); + REQUIRE(token_type("32e-3") == Token::REAL); + } + + SECTION("Tests for Name/Strings") { + REQUIRE(token_type("neuron") == Token::NAME); + REQUIRE(token_type("\"Quoted String\"") == Token::STRING); + } + + SECTION("Tests for (math) operators") { + REQUIRE(token_type(">") == Token::GT); + REQUIRE(token_type(">=") == Token::GE); + REQUIRE(token_type("<") == Token::LT); + REQUIRE(token_type("==") == Token::EQ); + REQUIRE(token_type("!=") == Token::NE); + REQUIRE(token_type("<->") == Token::REACT1); + REQUIRE(token_type("~+") == Token::NONLIN1); + // REQUIRE( token_type("~") == Token::REACTION); + } + + SECTION("Tests for braces") { + REQUIRE(token_type("{") == Token::OPEN_BRACE); + REQUIRE(token_type("}") == Token::CLOSE_BRACE); + REQUIRE(token_type("(") == Token::OPEN_PARENTHESIS); + REQUIRE(token_type(")") != Token::OPEN_PARENTHESIS); + } +} diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp new file mode 100644 index 0000000000..3e5c7e360c --- /dev/null +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -0,0 +1,54 @@ +#define CATCH_CONFIG_MAIN + +#include + +#include "catch/catch.hpp" +#include "lexer/modtoken.hpp" +#include "lexer/nmodl_lexer.hpp" +#include "parser/nmodl_driver.hpp" + +/// retrieve token from lexer +template +void symbol_type(std::string name, T& value) { + std::istringstream ss(name); + std::istream& in = ss; + + nmodl::Driver driver; + nmodl::Lexer scanner(driver, &in); + + nmodl::Parser::symbol_type sym = scanner.next_token(); + value = sym.value.as(); +} + +/// test symbol type returned by lexer +TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { + SECTION("Symbol type : name ast class test") { + ast::name_ptr value = nullptr; + + { + std::stringstream ss; + symbol_type("text", value); + ss << *(value->getToken()); + REQUIRE(ss.str().compare(" text at [1.1-4] type 356") == 0); + } + + { + std::stringstream ss; + symbol_type(" some_text", value); + ss << *(value->getToken()); + REQUIRE(ss.str().compare(" some_text at [1.3-11] type 356") == 0); + } + } + + SECTION("Symbol type : prime ast class test") { + ast::primename_ptr value = nullptr; + + { + std::stringstream ss; + symbol_type("h'' = ", value); + ss << *(value->getToken()); + REQUIRE(ss.str().compare(" h'' at [1.1-3] type 362") == 0); + REQUIRE(value->getOrder() == 2); + } + } +} From 58a1f90522d55bd7311f1fa19dcdf4a1479acb72 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 3 Nov 2017 16:33:25 +0100 Subject: [PATCH 012/871] Update to README file with instruction for dependencies, building and running tests. Change-Id: I47435a81519c7793f5754e1564ebeba664b519a0 NMODL Repo SHA: BlueBrain/nmodl@417710a8997ae3670d8b096dc0ea38519a449700 --- README.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index da9df8e802..c846578905 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,67 @@ -PROJECT_SOURCE_DIR## NOCMODL : Code Generation Framework +## NOCMODL This is prototype implementation of code generation framework for NMODL. -### How To Build? +#### Cloning Source + +``` +git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl +``` + +#### Dependencies + +Make sure to have latest version of flex (>=2.6) and bison (>=3.0). For example, on OS X we do: + +``` +brew install flex bison +``` +This will install flex and bison in: + +``` +/usr/local/opt/flex +/usr/local/opt/bison +``` + +On Ubuntu 16.04 you should already have recent version of flex/bison. + +#### Build +Install NOCMODL as: ``` mkdir build cd build cmake .. -make +make -j ``` + +If you flex / bison is in non-standard location then add flex/bison binaries to `PATH` env variable or use `CMAKE_PREFIX_PATH`: + +``` + cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" + ``` + + #### Running NOCMODL + + You can independently run lexer, parser as: + + ``` +./bin/nocmodl_lexer --file ../test/input/channel.mod +./bin/nocmodl_parser --file ../test/input/channel.mod + ```` + + + #### Running Test + + You can run unit tests as: + + ``` + make test + ``` + + Or individual binaries with verbode output: + + ``` + ./bin/testlexer -s + ./bin/testmodtoken -s + ``` + From 286782501b976fb4bd8af80626a2036b91f705f3 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 4 Nov 2017 18:51:39 +0100 Subject: [PATCH 013/871] Fix memory leaks in both tests Change-Id: I5cc3ca5628dae6da517da3887d1c0c70d11cb946 NMODL Repo SHA: BlueBrain/nmodl@b0fc16d3d7b046d3d73d3d7d9756f81c34f52d88 --- README.md | 10 +++- test/nmodl/transpiler/lexer/tokens.cpp | 58 ++++++++++++++++++++- test/nmodl/transpiler/modtoken/modtoken.cpp | 3 ++ 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c846578905..13bbc6ef88 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ cmake .. make -j ``` -If you flex / bison is in non-standard location then add flex/bison binaries to `PATH` env variable or use `CMAKE_PREFIX_PATH`: +If flex / bison is installed in non-standard location then set `PATH` env variable to have latest flex/bison in `PATH` or use `CMAKE_PREFIX_PATH`: ``` cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" @@ -65,3 +65,11 @@ If you flex / bison is in non-standard location then add flex/bison binaries to ./bin/testmodtoken -s ``` + +#### Memory Leaks + +Test memory leaks using : + +``` +valgrind --leak-check=full --track-origins=yes ./bin/nocmodl_lexer +``` diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 2921598cf6..0d348c8e98 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -17,8 +17,62 @@ nmodl::Parser::token_type token_type(std::string name) { nmodl::Driver driver; nmodl::Lexer scanner(driver, &in); - nmodl::Parser::symbol_type sym = scanner.next_token(); - return sym.token(); + using TokenType = nmodl::Parser::token_type; + using SymbolType = nmodl::Parser::symbol_type; + + SymbolType sym = scanner.next_token(); + TokenType token = sym.token(); + + /** Lexer returns raw pointers for some AST types + * and we need to clean-up memory for those. + * Todo: add tests later for checking values */ + switch (token) { + case Token::NAME: + case Token::METHOD: + case Token::SUFFIX: + case Token::VALENCE: + case Token::DEL: + case Token::DEL2: { + auto value = sym.value.as(); + delete value; + break; + } + + case Token::PRIME: { + auto value = sym.value.as(); + delete value; + break; + } + + case Token::INTEGER: { + auto value = sym.value.as(); + delete value; + break; + } + + case Token::REAL: { + auto value = sym.value.as(); + delete value; + break; + } + + case Token::STRING: { + auto value = sym.value.as(); + delete value; + break; + } + + case Token::VERBATIM: + case Token::COMMENT: + case Token::LINE_PART: { + auto value = sym.value.as(); + break; + } + + default: { auto value = sym.value.as(); } + } + + return token; } TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 3e5c7e360c..0d090327fd 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -30,6 +30,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { symbol_type("text", value); ss << *(value->getToken()); REQUIRE(ss.str().compare(" text at [1.1-4] type 356") == 0); + delete value; } { @@ -37,6 +38,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { symbol_type(" some_text", value); ss << *(value->getToken()); REQUIRE(ss.str().compare(" some_text at [1.3-11] type 356") == 0); + delete value; } } @@ -49,6 +51,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { ss << *(value->getToken()); REQUIRE(ss.str().compare(" h'' at [1.1-3] type 362") == 0); REQUIRE(value->getOrder() == 2); + delete value; } } } From a12ff066c79a1841d3c6250536e2f15924d4306c Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sun, 5 Nov 2017 21:55:44 +0100 Subject: [PATCH 014/871] Add virtual destructors to avoid memory leaks Note that we don't have to add virtual destructors for every class but for only for certain base classes. Have to check this later into ast generator. Change-Id: I162e637926b21abf44143eb17ab3d98f00b9ec79 NMODL Repo SHA: BlueBrain/nmodl@5dd9a3d065ae32ba60d868b9d2648ea0f52a3099 --- src/nmodl/ast/ast.hpp | 130 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp index 7e7f4dc82d..8f3c35aa1d 100644 --- a/src/nmodl/ast/ast.hpp +++ b/src/nmodl/ast/ast.hpp @@ -5,8 +5,8 @@ #include #include "ast/ast_utils.hpp" -#include "utils/common_utils.hpp" #include "lexer/modtoken.hpp" +#include "utils/common_utils.hpp" /* all classes representing Abstract Syntax Tree (AST) nodes */ namespace ast { @@ -440,6 +440,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitStatement(this); } + virtual ~Statement() {} virtual std::string getType() { return "Statement"; } virtual Statement* clone() { return new Statement(*this); } }; @@ -448,6 +449,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitExpression(this); } + virtual ~Expression() {} virtual std::string getType() { return "Expression"; } virtual Expression* clone() { return new Expression(*this); } }; @@ -456,6 +458,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBlock(this); } + virtual ~Block() {} virtual std::string getType() { return "Block"; } virtual Block* clone() { return new Block(*this); } }; @@ -464,6 +467,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitIdentifier(this); } + virtual ~Identifier() {} virtual std::string getType() { return "Identifier"; } virtual Identifier* clone() { return new Identifier(*this); } virtual void setName(std::string name) { std::cout << "ERROR : setName() not implemented! "; abort(); } @@ -473,6 +477,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNumber(this); } + virtual ~Number() {} virtual std::string getType() { return "Number"; } virtual Number* clone() { return new Number(*this); } virtual void negate() { std::cout << "ERROR : negate() not implemented! "; abort(); } @@ -489,6 +494,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitString(this); } + virtual ~String() {} virtual std::string getType() { return "String"; } virtual String* clone() { return new String(*this); } virtual ModToken* getToken() { return token.get(); } @@ -509,6 +515,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitInteger(this); } + virtual ~Integer() {} virtual std::string getType() { return "Integer"; } virtual Integer* clone() { return new Integer(*this); } virtual ModToken* getToken() { return token.get(); } @@ -528,6 +535,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFloat(this); } + virtual ~Float() {} virtual std::string getType() { return "Float"; } virtual Float* clone() { return new Float(*this); } void negate() { value = -value; } @@ -546,6 +554,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDouble(this); } + virtual ~Double() {} virtual std::string getType() { return "Double"; } virtual Double* clone() { return new Double(*this); } virtual ModToken* getToken() { return token.get(); } @@ -565,6 +574,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBoolean(this); } + virtual ~Boolean() {} virtual std::string getType() { return "Boolean"; } virtual Boolean* clone() { return new Boolean(*this); } void negate() { value = !value; } @@ -584,6 +594,7 @@ namespace ast { virtual std::string getName() { return value->eval(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitName(this); } + virtual ~Name() {} virtual std::string getType() { return "Name"; } virtual Name* clone() { return new Name(*this); } virtual ModToken* getToken() { return token.get(); } @@ -605,6 +616,7 @@ namespace ast { virtual int getOrder() { return order->eval(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPrimeName(this); } + virtual ~PrimeName() {} virtual std::string getType() { return "PrimeName"; } virtual PrimeName* clone() { return new PrimeName(*this); } virtual ModToken* getToken() { return token.get(); } @@ -624,6 +636,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitVarName(this); } + virtual ~VarName() {} virtual std::string getType() { return "VarName"; } virtual VarName* clone() { return new VarName(*this); } }; @@ -641,6 +654,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitIndexedName(this); } + virtual ~IndexedName() {} virtual std::string getType() { return "IndexedName"; } virtual IndexedName* clone() { return new IndexedName(*this); } }; @@ -658,6 +672,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitArgument(this); } + virtual ~Argument() {} virtual std::string getType() { return "Argument"; } virtual Argument* clone() { return new Argument(*this); } }; @@ -675,6 +690,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitReactVarName(this); } + virtual ~ReactVarName() {} virtual std::string getType() { return "ReactVarName"; } virtual ReactVarName* clone() { return new ReactVarName(*this); } }; @@ -691,6 +707,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitReadIonVar(this); } + virtual ~ReadIonVar() {} virtual std::string getType() { return "ReadIonVar"; } virtual ReadIonVar* clone() { return new ReadIonVar(*this); } }; @@ -707,6 +724,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitWriteIonVar(this); } + virtual ~WriteIonVar() {} virtual std::string getType() { return "WriteIonVar"; } virtual WriteIonVar* clone() { return new WriteIonVar(*this); } }; @@ -723,6 +741,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNonspeCurVar(this); } + virtual ~NonspeCurVar() {} virtual std::string getType() { return "NonspeCurVar"; } virtual NonspeCurVar* clone() { return new NonspeCurVar(*this); } }; @@ -739,6 +758,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitElectrodeCurVar(this); } + virtual ~ElectrodeCurVar() {} virtual std::string getType() { return "ElectrodeCurVar"; } virtual ElectrodeCurVar* clone() { return new ElectrodeCurVar(*this); } }; @@ -755,6 +775,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitSectionVar(this); } + virtual ~SectionVar() {} virtual std::string getType() { return "SectionVar"; } virtual SectionVar* clone() { return new SectionVar(*this); } }; @@ -771,6 +792,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitRangeVar(this); } + virtual ~RangeVar() {} virtual std::string getType() { return "RangeVar"; } virtual RangeVar* clone() { return new RangeVar(*this); } }; @@ -787,6 +809,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitGlobalVar(this); } + virtual ~GlobalVar() {} virtual std::string getType() { return "GlobalVar"; } virtual GlobalVar* clone() { return new GlobalVar(*this); } }; @@ -803,6 +826,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPointerVar(this); } + virtual ~PointerVar() {} virtual std::string getType() { return "PointerVar"; } virtual PointerVar* clone() { return new PointerVar(*this); } }; @@ -819,6 +843,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBbcorePointerVar(this); } + virtual ~BbcorePointerVar() {} virtual std::string getType() { return "BbcorePointerVar"; } virtual BbcorePointerVar* clone() { return new BbcorePointerVar(*this); } }; @@ -835,6 +860,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitExternVar(this); } + virtual ~ExternVar() {} virtual std::string getType() { return "ExternVar"; } virtual ExternVar* clone() { return new ExternVar(*this); } }; @@ -851,6 +877,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitThreadsafeVar(this); } + virtual ~ThreadsafeVar() {} virtual std::string getType() { return "ThreadsafeVar"; } virtual ThreadsafeVar* clone() { return new ThreadsafeVar(*this); } }; @@ -868,6 +895,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitParamBlock(this); } + virtual ~ParamBlock() {} virtual std::string getType() { return "ParamBlock"; } virtual ParamBlock* clone() { return new ParamBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -887,6 +915,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitStepBlock(this); } + virtual ~StepBlock() {} virtual std::string getType() { return "StepBlock"; } virtual StepBlock* clone() { return new StepBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -906,6 +935,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitIndependentBlock(this); } + virtual ~IndependentBlock() {} virtual std::string getType() { return "IndependentBlock"; } virtual IndependentBlock* clone() { return new IndependentBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -925,6 +955,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDependentBlock(this); } + virtual ~DependentBlock() {} virtual std::string getType() { return "DependentBlock"; } virtual DependentBlock* clone() { return new DependentBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -944,6 +975,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitStateBlock(this); } + virtual ~StateBlock() {} virtual std::string getType() { return "StateBlock"; } virtual StateBlock* clone() { return new StateBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -963,6 +995,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPlotBlock(this); } + virtual ~PlotBlock() {} virtual std::string getType() { return "PlotBlock"; } virtual PlotBlock* clone() { return new PlotBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -982,6 +1015,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitInitialBlock(this); } + virtual ~InitialBlock() {} virtual std::string getType() { return "InitialBlock"; } virtual InitialBlock* clone() { return new InitialBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1001,6 +1035,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitConstructorBlock(this); } + virtual ~ConstructorBlock() {} virtual std::string getType() { return "ConstructorBlock"; } virtual ConstructorBlock* clone() { return new ConstructorBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1020,6 +1055,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDestructorBlock(this); } + virtual ~DestructorBlock() {} virtual std::string getType() { return "DestructorBlock"; } virtual DestructorBlock* clone() { return new DestructorBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1040,6 +1076,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitStatementBlock(this); } + virtual ~StatementBlock() {} virtual std::string getType() { return "StatementBlock"; } virtual StatementBlock* clone() { return new StatementBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1063,6 +1100,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDerivativeBlock(this); } + virtual ~DerivativeBlock() {} virtual std::string getType() { return "DerivativeBlock"; } virtual DerivativeBlock* clone() { return new DerivativeBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1087,6 +1125,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLinearBlock(this); } + virtual ~LinearBlock() {} virtual std::string getType() { return "LinearBlock"; } virtual LinearBlock* clone() { return new LinearBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1111,6 +1150,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNonLinearBlock(this); } + virtual ~NonLinearBlock() {} virtual std::string getType() { return "NonLinearBlock"; } virtual NonLinearBlock* clone() { return new NonLinearBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1134,6 +1174,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDiscreteBlock(this); } + virtual ~DiscreteBlock() {} virtual std::string getType() { return "DiscreteBlock"; } virtual DiscreteBlock* clone() { return new DiscreteBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1157,6 +1198,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPartialBlock(this); } + virtual ~PartialBlock() {} virtual std::string getType() { return "PartialBlock"; } virtual PartialBlock* clone() { return new PartialBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1181,6 +1223,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFunctionTableBlock(this); } + virtual ~FunctionTableBlock() {} virtual std::string getType() { return "FunctionTableBlock"; } virtual FunctionTableBlock* clone() { return new FunctionTableBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1206,6 +1249,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFunctionBlock(this); } + virtual ~FunctionBlock() {} virtual std::string getType() { return "FunctionBlock"; } virtual FunctionBlock* clone() { return new FunctionBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1231,6 +1275,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitProcedureBlock(this); } + virtual ~ProcedureBlock() {} virtual std::string getType() { return "ProcedureBlock"; } virtual ProcedureBlock* clone() { return new ProcedureBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1253,6 +1298,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNetReceiveBlock(this); } + virtual ~NetReceiveBlock() {} virtual std::string getType() { return "NetReceiveBlock"; } virtual NetReceiveBlock* clone() { return new NetReceiveBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1274,6 +1320,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitSolveBlock(this); } + virtual ~SolveBlock() {} virtual std::string getType() { return "SolveBlock"; } virtual SolveBlock* clone() { return new SolveBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1293,6 +1340,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBreakpointBlock(this); } + virtual ~BreakpointBlock() {} virtual std::string getType() { return "BreakpointBlock"; } virtual BreakpointBlock* clone() { return new BreakpointBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1312,6 +1360,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitTerminalBlock(this); } + virtual ~TerminalBlock() {} virtual std::string getType() { return "TerminalBlock"; } virtual TerminalBlock* clone() { return new TerminalBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1331,6 +1380,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBeforeBlock(this); } + virtual ~BeforeBlock() {} virtual std::string getType() { return "BeforeBlock"; } virtual BeforeBlock* clone() { return new BeforeBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1350,6 +1400,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitAfterBlock(this); } + virtual ~AfterBlock() {} virtual std::string getType() { return "AfterBlock"; } virtual AfterBlock* clone() { return new AfterBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1370,6 +1421,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBABlock(this); } + virtual ~BABlock() {} virtual std::string getType() { return "BABlock"; } virtual BABlock* clone() { return new BABlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1390,6 +1442,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitForNetcon(this); } + virtual ~ForNetcon() {} virtual std::string getType() { return "ForNetcon"; } virtual ForNetcon* clone() { return new ForNetcon(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1412,6 +1465,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitKineticBlock(this); } + virtual ~KineticBlock() {} virtual std::string getType() { return "KineticBlock"; } virtual KineticBlock* clone() { return new KineticBlock(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1433,6 +1487,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitMatchBlock(this); } + virtual ~MatchBlock() {} virtual std::string getType() { return "MatchBlock"; } virtual MatchBlock* clone() { return new MatchBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1452,6 +1507,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitUnitBlock(this); } + virtual ~UnitBlock() {} virtual std::string getType() { return "UnitBlock"; } virtual UnitBlock* clone() { return new UnitBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1471,6 +1527,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitConstantBlock(this); } + virtual ~ConstantBlock() {} virtual std::string getType() { return "ConstantBlock"; } virtual ConstantBlock* clone() { return new ConstantBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1490,6 +1547,7 @@ namespace ast { std::string getName() { return getType(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNeuronBlock(this); } + virtual ~NeuronBlock() {} virtual std::string getType() { return "NeuronBlock"; } virtual NeuronBlock* clone() { return new NeuronBlock(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } @@ -1508,6 +1566,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitUnit(this); } + virtual ~Unit() {} virtual std::string getType() { return "Unit"; } virtual Unit* clone() { return new Unit(*this); } }; @@ -1523,6 +1582,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDoubleUnit(this); } + virtual ~DoubleUnit() {} virtual std::string getType() { return "DoubleUnit"; } virtual DoubleUnit* clone() { return new DoubleUnit(*this); } }; @@ -1539,6 +1599,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLocalVariable(this); } + virtual ~LocalVariable() {} virtual std::string getType() { return "LocalVariable"; } virtual LocalVariable* clone() { return new LocalVariable(*this); } }; @@ -1554,6 +1615,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLimits(this); } + virtual ~Limits() {} virtual std::string getType() { return "Limits"; } virtual Limits* clone() { return new Limits(*this); } }; @@ -1569,6 +1631,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNumberRange(this); } + virtual ~NumberRange() {} virtual std::string getType() { return "NumberRange"; } virtual NumberRange* clone() { return new NumberRange(*this); } }; @@ -1584,6 +1647,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPlotVariable(this); } + virtual ~PlotVariable() {} virtual std::string getType() { return "PlotVariable"; } virtual PlotVariable* clone() { return new PlotVariable(*this); } }; @@ -1600,6 +1664,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBinaryOperator(this); } + virtual ~BinaryOperator() {} virtual std::string getType() { return "BinaryOperator"; } virtual BinaryOperator* clone() { return new BinaryOperator(*this); } std::string eval() { return BinaryOpNames[value]; } @@ -1617,6 +1682,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitUnaryOperator(this); } + virtual ~UnaryOperator() {} virtual std::string getType() { return "UnaryOperator"; } virtual UnaryOperator* clone() { return new UnaryOperator(*this); } std::string eval() { return UnaryOpNames[value]; } @@ -1634,6 +1700,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitReactionOperator(this); } + virtual ~ReactionOperator() {} virtual std::string getType() { return "ReactionOperator"; } virtual ReactionOperator* clone() { return new ReactionOperator(*this); } std::string eval() { return ReactionOpNames[value]; } @@ -1651,6 +1718,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBinaryExpression(this); } + virtual ~BinaryExpression() {} virtual std::string getType() { return "BinaryExpression"; } virtual BinaryExpression* clone() { return new BinaryExpression(*this); } }; @@ -1666,6 +1734,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitUnaryExpression(this); } + virtual ~UnaryExpression() {} virtual std::string getType() { return "UnaryExpression"; } virtual UnaryExpression* clone() { return new UnaryExpression(*this); } }; @@ -1681,6 +1750,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNonLinEuation(this); } + virtual ~NonLinEuation() {} virtual std::string getType() { return "NonLinEuation"; } virtual NonLinEuation* clone() { return new NonLinEuation(*this); } }; @@ -1696,6 +1766,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLinEquation(this); } + virtual ~LinEquation() {} virtual std::string getType() { return "LinEquation"; } virtual LinEquation* clone() { return new LinEquation(*this); } }; @@ -1711,6 +1782,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFunctionCall(this); } + virtual ~FunctionCall() {} virtual std::string getType() { return "FunctionCall"; } virtual FunctionCall* clone() { return new FunctionCall(*this); } }; @@ -1725,6 +1797,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFirstLastTypeIndex(this); } + virtual ~FirstLastTypeIndex() {} virtual std::string getType() { return "FirstLastTypeIndex"; } virtual FirstLastTypeIndex* clone() { return new FirstLastTypeIndex(*this); } std::string eval() { return FirstLastTypeNames[value]; } @@ -1741,6 +1814,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitWatch(this); } + virtual ~Watch() {} virtual std::string getType() { return "Watch"; } virtual Watch* clone() { return new Watch(*this); } }; @@ -1755,6 +1829,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitQueueExpressionType(this); } + virtual ~QueueExpressionType() {} virtual std::string getType() { return "QueueExpressionType"; } virtual QueueExpressionType* clone() { return new QueueExpressionType(*this); } std::string eval() { return QueueTypeNames[value]; } @@ -1771,6 +1846,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitMatch(this); } + virtual ~Match() {} virtual std::string getType() { return "Match"; } virtual Match* clone() { return new Match(*this); } }; @@ -1785,6 +1861,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitBABlockType(this); } + virtual ~BABlockType() {} virtual std::string getType() { return "BABlockType"; } virtual BABlockType* clone() { return new BABlockType(*this); } std::string eval() { return BATypeNames[value]; } @@ -1803,6 +1880,7 @@ namespace ast { virtual ModToken* getToken() { return unit1->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitUnitDef(this); } + virtual ~UnitDef() {} virtual std::string getType() { return "UnitDef"; } virtual UnitDef* clone() { return new UnitDef(*this); } }; @@ -1823,6 +1901,7 @@ namespace ast { virtual std::string getName() { return name->getName(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFactorDef(this); } + virtual ~FactorDef() {} virtual std::string getType() { return "FactorDef"; } virtual FactorDef* clone() { return new FactorDef(*this); } virtual ModToken* getToken() { return token.get(); } @@ -1840,6 +1919,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitValence(this); } + virtual ~Valence() {} virtual std::string getType() { return "Valence"; } virtual Valence* clone() { return new Valence(*this); } }; @@ -1854,6 +1934,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitUnitState(this); } + virtual ~UnitState() {} virtual std::string getType() { return "UnitState"; } virtual UnitState* clone() { return new UnitState(*this); } std::string eval() { return UnitStateTypeNames[value]; } @@ -1869,6 +1950,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLocalListStatement(this); } + virtual ~LocalListStatement() {} virtual std::string getType() { return "LocalListStatement"; } virtual LocalListStatement* clone() { return new LocalListStatement(*this); } }; @@ -1883,6 +1965,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitModel(this); } + virtual ~Model() {} virtual std::string getType() { return "Model"; } virtual Model* clone() { return new Model(*this); } }; @@ -1898,6 +1981,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDefine(this); } + virtual ~Define() {} virtual std::string getType() { return "Define"; } virtual Define* clone() { return new Define(*this); } }; @@ -1912,6 +1996,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitInclude(this); } + virtual ~Include() {} virtual std::string getType() { return "Include"; } virtual Include* clone() { return new Include(*this); } }; @@ -1931,6 +2016,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitParamAssign(this); } + virtual ~ParamAssign() {} virtual std::string getType() { return "ParamAssign"; } virtual ParamAssign* clone() { return new ParamAssign(*this); } }; @@ -1947,6 +2033,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitStepped(this); } + virtual ~Stepped() {} virtual std::string getType() { return "Stepped"; } virtual Stepped* clone() { return new Stepped(*this); } }; @@ -1967,6 +2054,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitIndependentDef(this); } + virtual ~IndependentDef() {} virtual std::string getType() { return "IndependentDef"; } virtual IndependentDef* clone() { return new IndependentDef(*this); } }; @@ -1989,6 +2077,7 @@ namespace ast { virtual ModToken* getToken() { return name->getToken(); } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitDependentDef(this); } + virtual ~DependentDef() {} virtual std::string getType() { return "DependentDef"; } virtual DependentDef* clone() { return new DependentDef(*this); } }; @@ -2004,6 +2093,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPlotDeclaration(this); } + virtual ~PlotDeclaration() {} virtual std::string getType() { return "PlotDeclaration"; } virtual PlotDeclaration* clone() { return new PlotDeclaration(*this); } }; @@ -2019,6 +2109,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitConductanceHint(this); } + virtual ~ConductanceHint() {} virtual std::string getType() { return "ConductanceHint"; } virtual ConductanceHint* clone() { return new ConductanceHint(*this); } }; @@ -2033,6 +2124,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitExpressionStatement(this); } + virtual ~ExpressionStatement() {} virtual std::string getType() { return "ExpressionStatement"; } virtual ExpressionStatement* clone() { return new ExpressionStatement(*this); } }; @@ -2047,6 +2139,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitProtectStatement(this); } + virtual ~ProtectStatement() {} virtual std::string getType() { return "ProtectStatement"; } virtual ProtectStatement* clone() { return new ProtectStatement(*this); } }; @@ -2065,6 +2158,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitFromStatement(this); } + virtual ~FromStatement() {} virtual std::string getType() { return "FromStatement"; } virtual FromStatement* clone() { return new FromStatement(*this); } }; @@ -2080,6 +2174,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitForAllStatement(this); } + virtual ~ForAllStatement() {} virtual std::string getType() { return "ForAllStatement"; } virtual ForAllStatement* clone() { return new ForAllStatement(*this); } }; @@ -2095,6 +2190,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitWhileStatement(this); } + virtual ~WhileStatement() {} virtual std::string getType() { return "WhileStatement"; } virtual WhileStatement* clone() { return new WhileStatement(*this); } }; @@ -2112,6 +2208,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitIfStatement(this); } + virtual ~IfStatement() {} virtual std::string getType() { return "IfStatement"; } virtual IfStatement* clone() { return new IfStatement(*this); } }; @@ -2127,6 +2224,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitElseIfStatement(this); } + virtual ~ElseIfStatement() {} virtual std::string getType() { return "ElseIfStatement"; } virtual ElseIfStatement* clone() { return new ElseIfStatement(*this); } }; @@ -2141,6 +2239,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitElseStatement(this); } + virtual ~ElseStatement() {} virtual std::string getType() { return "ElseStatement"; } virtual ElseStatement* clone() { return new ElseStatement(*this); } }; @@ -2158,6 +2257,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPartialEquation(this); } + virtual ~PartialEquation() {} virtual std::string getType() { return "PartialEquation"; } virtual PartialEquation* clone() { return new PartialEquation(*this); } }; @@ -2179,6 +2279,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitPartialBoundary(this); } + virtual ~PartialBoundary() {} virtual std::string getType() { return "PartialBoundary"; } virtual PartialBoundary* clone() { return new PartialBoundary(*this); } }; @@ -2196,6 +2297,7 @@ namespace ast { } virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitWatchStatement(this); } + virtual ~WatchStatement() {} virtual std::string getType() { return "WatchStatement"; } virtual WatchStatement* clone() { return new WatchStatement(*this); } }; @@ -2204,6 +2306,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitMutexLock(this); } + virtual ~MutexLock() {} virtual std::string getType() { return "MutexLock"; } virtual MutexLock* clone() { return new MutexLock(*this); } }; @@ -2212,6 +2315,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitMutexUnlock(this); } + virtual ~MutexUnlock() {} virtual std::string getType() { return "MutexUnlock"; } virtual MutexUnlock* clone() { return new MutexUnlock(*this); } }; @@ -2220,6 +2324,7 @@ namespace ast { public: virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitReset(this); } + virtual ~Reset() {} virtual std::string getType() { return "Reset"; } virtual Reset* clone() { return new Reset(*this); } }; @@ -2234,6 +2339,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitSens(this); } + virtual ~Sens() {} virtual std::string getType() { return "Sens"; } virtual Sens* clone() { return new Sens(*this); } }; @@ -2249,6 +2355,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitConserve(this); } + virtual ~Conserve() {} virtual std::string getType() { return "Conserve"; } virtual Conserve* clone() { return new Conserve(*this); } }; @@ -2265,6 +2372,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitCompartment(this); } + virtual ~Compartment() {} virtual std::string getType() { return "Compartment"; } virtual Compartment* clone() { return new Compartment(*this); } }; @@ -2281,6 +2389,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLDifuse(this); } + virtual ~LDifuse() {} virtual std::string getType() { return "LDifuse"; } virtual LDifuse* clone() { return new LDifuse(*this); } }; @@ -2299,6 +2408,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitReactionStatement(this); } + virtual ~ReactionStatement() {} virtual std::string getType() { return "ReactionStatement"; } virtual ReactionStatement* clone() { return new ReactionStatement(*this); } }; @@ -2314,6 +2424,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitLagStatement(this); } + virtual ~LagStatement() {} virtual std::string getType() { return "LagStatement"; } virtual LagStatement* clone() { return new LagStatement(*this); } }; @@ -2329,6 +2440,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitQueueStatement(this); } + virtual ~QueueStatement() {} virtual std::string getType() { return "QueueStatement"; } virtual QueueStatement* clone() { return new QueueStatement(*this); } }; @@ -2345,6 +2457,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitConstantStatement(this); } + virtual ~ConstantStatement() {} virtual std::string getType() { return "ConstantStatement"; } virtual ConstantStatement* clone() { return new ConstantStatement(*this); } }; @@ -2363,6 +2476,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitTableStatement(this); } + virtual ~TableStatement() {} virtual std::string getType() { return "TableStatement"; } virtual TableStatement* clone() { return new TableStatement(*this); } }; @@ -2378,6 +2492,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnSuffix(this); } + virtual ~NrnSuffix() {} virtual std::string getType() { return "NrnSuffix"; } virtual NrnSuffix* clone() { return new NrnSuffix(*this); } }; @@ -2395,6 +2510,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnUseion(this); } + virtual ~NrnUseion() {} virtual std::string getType() { return "NrnUseion"; } virtual NrnUseion* clone() { return new NrnUseion(*this); } }; @@ -2409,6 +2525,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnNonspecific(this); } + virtual ~NrnNonspecific() {} virtual std::string getType() { return "NrnNonspecific"; } virtual NrnNonspecific* clone() { return new NrnNonspecific(*this); } }; @@ -2423,6 +2540,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnElctrodeCurrent(this); } + virtual ~NrnElctrodeCurrent() {} virtual std::string getType() { return "NrnElctrodeCurrent"; } virtual NrnElctrodeCurrent* clone() { return new NrnElctrodeCurrent(*this); } }; @@ -2437,6 +2555,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnSection(this); } + virtual ~NrnSection() {} virtual std::string getType() { return "NrnSection"; } virtual NrnSection* clone() { return new NrnSection(*this); } }; @@ -2451,6 +2570,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnRange(this); } + virtual ~NrnRange() {} virtual std::string getType() { return "NrnRange"; } virtual NrnRange* clone() { return new NrnRange(*this); } }; @@ -2465,6 +2585,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnGlobal(this); } + virtual ~NrnGlobal() {} virtual std::string getType() { return "NrnGlobal"; } virtual NrnGlobal* clone() { return new NrnGlobal(*this); } }; @@ -2479,6 +2600,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnPointer(this); } + virtual ~NrnPointer() {} virtual std::string getType() { return "NrnPointer"; } virtual NrnPointer* clone() { return new NrnPointer(*this); } }; @@ -2493,6 +2615,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnBbcorePtr(this); } + virtual ~NrnBbcorePtr() {} virtual std::string getType() { return "NrnBbcorePtr"; } virtual NrnBbcorePtr* clone() { return new NrnBbcorePtr(*this); } }; @@ -2507,6 +2630,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnExternal(this); } + virtual ~NrnExternal() {} virtual std::string getType() { return "NrnExternal"; } virtual NrnExternal* clone() { return new NrnExternal(*this); } }; @@ -2521,6 +2645,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitNrnThreadSafe(this); } + virtual ~NrnThreadSafe() {} virtual std::string getType() { return "NrnThreadSafe"; } virtual NrnThreadSafe* clone() { return new NrnThreadSafe(*this); } }; @@ -2535,6 +2660,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitVerbatim(this); } + virtual ~Verbatim() {} virtual std::string getType() { return "Verbatim"; } virtual Verbatim* clone() { return new Verbatim(*this); } }; @@ -2549,6 +2675,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitComment(this); } + virtual ~Comment() {} virtual std::string getType() { return "Comment"; } virtual Comment* clone() { return new Comment(*this); } }; @@ -2574,6 +2701,7 @@ namespace ast { virtual void visitChildren(Visitor* v); virtual void accept(Visitor* v) { v->visitProgram(this); } + virtual ~Program() {} virtual std::string getType() { return "Program"; } virtual Program* clone() { return new Program(*this); } virtual void setBlockSymbolTable(void *s) { symtab = s; } From 23bcf9b35a030344ad060160d61717c846221135 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Wed, 8 Nov 2017 18:31:47 +0100 Subject: [PATCH 015/871] Enable CTest and include memory.h for c++11 Change-Id: I752fa2d3c93038a03553743c0169aba99b6a3c76 NMODL Repo SHA: BlueBrain/nmodl@816a1bb2f55e0f2fc7e4158671ba03b99115a0c0 --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/ast/ast.hpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index eb67d8b36e..4e31909e96 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -39,7 +39,7 @@ add_subdirectory(src/lexer) add_subdirectory(src/parser) add_subdirectory(test) -enable_testing () +include (CTest) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp index 8f3c35aa1d..c07ca6f8f8 100644 --- a/src/nmodl/ast/ast.hpp +++ b/src/nmodl/ast/ast.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include From edfeacacec5d790168d8d5d2e90e7d97d608acec Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Mon, 20 Nov 2017 22:42:23 +0100 Subject: [PATCH 016/871] Changed naming convention for variable suffix from List to Vector Change-Id: Idad50f3559977ef3580d1cd71a5e02040726d9ea NMODL Repo SHA: BlueBrain/nmodl@90c3b3d5c59845c3a48e9baf5a6adf0fa1106f26 --- src/nmodl/ast/ast.cpp | 76 +++--- src/nmodl/ast/ast.hpp | 480 +++++++++++++++++++------------------- src/nmodl/parser/nmodl.yy | 80 +++---- 3 files changed, 318 insertions(+), 318 deletions(-) diff --git a/src/nmodl/ast/ast.cpp b/src/nmodl/ast/ast.cpp index c88a4f7cb7..3a4eeceaf9 100644 --- a/src/nmodl/ast/ast.cpp +++ b/src/nmodl/ast/ast.cpp @@ -430,7 +430,7 @@ namespace ast { } /* constructor for ParamBlock ast node */ - ParamBlock::ParamBlock(ParamAssignList statements) + ParamBlock::ParamBlock(ParamAssignVector statements) : statements(statements) { } @@ -451,7 +451,7 @@ namespace ast { } /* constructor for StepBlock ast node */ - StepBlock::StepBlock(SteppedList statements) + StepBlock::StepBlock(SteppedVector statements) : statements(statements) { } @@ -472,7 +472,7 @@ namespace ast { } /* constructor for IndependentBlock ast node */ - IndependentBlock::IndependentBlock(IndependentDefList definitions) + IndependentBlock::IndependentBlock(IndependentDefVector definitions) : definitions(definitions) { } @@ -493,7 +493,7 @@ namespace ast { } /* constructor for DependentBlock ast node */ - DependentBlock::DependentBlock(DependentDefList definitions) + DependentBlock::DependentBlock(DependentDefVector definitions) : definitions(definitions) { } @@ -514,7 +514,7 @@ namespace ast { } /* constructor for StateBlock ast node */ - StateBlock::StateBlock(DependentDefList definitions) + StateBlock::StateBlock(DependentDefVector definitions) : definitions(definitions) { } @@ -613,7 +613,7 @@ namespace ast { } /* constructor for StatementBlock ast node */ - StatementBlock::StatementBlock(StatementList statements) + StatementBlock::StatementBlock(StatementVector statements) : statements(statements) { } @@ -666,7 +666,7 @@ namespace ast { } /* constructor for LinearBlock ast node */ - LinearBlock::LinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock) + LinearBlock::LinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock) : solvefor(solvefor) { this->name = std::shared_ptr(name); @@ -699,7 +699,7 @@ namespace ast { } /* constructor for NonLinearBlock ast node */ - NonLinearBlock::NonLinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock) + NonLinearBlock::NonLinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock) : solvefor(solvefor) { this->name = std::shared_ptr(name); @@ -784,7 +784,7 @@ namespace ast { } /* constructor for FunctionTableBlock ast node */ - FunctionTableBlock::FunctionTableBlock(Name* name, ArgumentList arguments, Unit* unit) + FunctionTableBlock::FunctionTableBlock(Name* name, ArgumentVector arguments, Unit* unit) : arguments(arguments) { this->name = std::shared_ptr(name); @@ -820,7 +820,7 @@ namespace ast { } /* constructor for FunctionBlock ast node */ - FunctionBlock::FunctionBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock) + FunctionBlock::FunctionBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock) : arguments(arguments) { this->name = std::shared_ptr(name); @@ -859,7 +859,7 @@ namespace ast { } /* constructor for ProcedureBlock ast node */ - ProcedureBlock::ProcedureBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock) + ProcedureBlock::ProcedureBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock) : arguments(arguments) { this->name = std::shared_ptr(name); @@ -894,7 +894,7 @@ namespace ast { } /* constructor for NetReceiveBlock ast node */ - NetReceiveBlock::NetReceiveBlock(ArgumentList arguments, StatementBlock* statementblock) + NetReceiveBlock::NetReceiveBlock(ArgumentVector arguments, StatementBlock* statementblock) : arguments(arguments) { this->statementblock = std::shared_ptr(statementblock); @@ -1051,7 +1051,7 @@ namespace ast { } /* constructor for ForNetcon ast node */ - ForNetcon::ForNetcon(ArgumentList arguments, StatementBlock* statementblock) + ForNetcon::ForNetcon(ArgumentVector arguments, StatementBlock* statementblock) : arguments(arguments) { this->statementblock = std::shared_ptr(statementblock); @@ -1079,7 +1079,7 @@ namespace ast { } /* constructor for KineticBlock ast node */ - KineticBlock::KineticBlock(Name* name, NameList solvefor, StatementBlock* statementblock) + KineticBlock::KineticBlock(Name* name, NameVector solvefor, StatementBlock* statementblock) : solvefor(solvefor) { this->name = std::shared_ptr(name); @@ -1108,7 +1108,7 @@ namespace ast { } /* constructor for MatchBlock ast node */ - MatchBlock::MatchBlock(MatchList matchs) + MatchBlock::MatchBlock(MatchVector matchs) : matchs(matchs) { } @@ -1129,7 +1129,7 @@ namespace ast { } /* constructor for UnitBlock ast node */ - UnitBlock::UnitBlock(ExpressionList definitions) + UnitBlock::UnitBlock(ExpressionVector definitions) : definitions(definitions) { } @@ -1150,7 +1150,7 @@ namespace ast { } /* constructor for ConstantBlock ast node */ - ConstantBlock::ConstantBlock(ConstantStatementList statements) + ConstantBlock::ConstantBlock(ConstantStatementVector statements) : statements(statements) { } @@ -1449,7 +1449,7 @@ namespace ast { } /* constructor for FunctionCall ast node */ - FunctionCall::FunctionCall(Name* name, ExpressionList arguments) + FunctionCall::FunctionCall(Name* name, ExpressionVector arguments) : arguments(arguments) { this->name = std::shared_ptr(name); @@ -1657,7 +1657,7 @@ namespace ast { } /* constructor for LocalListStatement ast node */ - LocalListStatement::LocalListStatement(LocalVariableList variables) + LocalListStatement::LocalListStatement(LocalVariableVector variables) : variables(variables) { } @@ -1774,7 +1774,7 @@ namespace ast { } /* constructor for Stepped ast node */ - Stepped::Stepped(Name* name, NumberList values, Unit* unit) + Stepped::Stepped(Name* name, NumberVector values, Unit* unit) : values(values) { this->name = std::shared_ptr(name); @@ -1902,7 +1902,7 @@ namespace ast { } /* constructor for PlotDeclaration ast node */ - PlotDeclaration::PlotDeclaration(PlotVariableList pvlist, PlotVariable* name) + PlotDeclaration::PlotDeclaration(PlotVariableVector pvlist, PlotVariable* name) : pvlist(pvlist) { this->name = std::shared_ptr(name); @@ -2079,7 +2079,7 @@ namespace ast { } /* constructor for IfStatement ast node */ - IfStatement::IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementList elseifs, ElseStatement* elses) + IfStatement::IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementVector elseifs, ElseStatement* elses) : elseifs(elseifs) { this->condition = std::shared_ptr(condition); @@ -2243,7 +2243,7 @@ namespace ast { } /* constructor for WatchStatement ast node */ - WatchStatement::WatchStatement(WatchList statements) + WatchStatement::WatchStatement(WatchVector statements) : statements(statements) { } @@ -2267,7 +2267,7 @@ namespace ast { } /* constructor for Sens ast node */ - Sens::Sens(VarNameList senslist) + Sens::Sens(VarNameVector senslist) : senslist(senslist) { } @@ -2314,7 +2314,7 @@ namespace ast { } /* constructor for Compartment ast node */ - Compartment::Compartment(Name* name, Expression* expression, NameList names) + Compartment::Compartment(Name* name, Expression* expression, NameVector names) : names(names) { this->name = std::shared_ptr(name); @@ -2345,7 +2345,7 @@ namespace ast { } /* constructor for LDifuse ast node */ - LDifuse::LDifuse(Name* name, Expression* expression, NameList names) + LDifuse::LDifuse(Name* name, Expression* expression, NameVector names) : names(names) { this->name = std::shared_ptr(name); @@ -2487,7 +2487,7 @@ namespace ast { } /* constructor for TableStatement ast node */ - TableStatement::TableStatement(NameList tablst, NameList dependlst, Expression* from, Expression* to, Integer* with) + TableStatement::TableStatement(NameVector tablst, NameVector dependlst, Expression* from, Expression* to, Integer* with) : tablst(tablst), dependlst(dependlst) { @@ -2550,7 +2550,7 @@ dependlst(dependlst) } /* constructor for NrnUseion ast node */ - NrnUseion::NrnUseion(Name* name, ReadIonVarList readlist, WriteIonVarList writelist, Valence* valence) + NrnUseion::NrnUseion(Name* name, ReadIonVarVector readlist, WriteIonVarVector writelist, Valence* valence) : readlist(readlist), writelist(writelist) { @@ -2581,7 +2581,7 @@ writelist(writelist) } /* constructor for NrnNonspecific ast node */ - NrnNonspecific::NrnNonspecific(NonspeCurVarList currents) + NrnNonspecific::NrnNonspecific(NonspeCurVarVector currents) : currents(currents) { } @@ -2602,7 +2602,7 @@ writelist(writelist) } /* constructor for NrnElctrodeCurrent ast node */ - NrnElctrodeCurrent::NrnElctrodeCurrent(ElectrodeCurVarList ecurrents) + NrnElctrodeCurrent::NrnElctrodeCurrent(ElectrodeCurVarVector ecurrents) : ecurrents(ecurrents) { } @@ -2623,7 +2623,7 @@ writelist(writelist) } /* constructor for NrnSection ast node */ - NrnSection::NrnSection(SectionVarList sections) + NrnSection::NrnSection(SectionVarVector sections) : sections(sections) { } @@ -2644,7 +2644,7 @@ writelist(writelist) } /* constructor for NrnRange ast node */ - NrnRange::NrnRange(RangeVarList range_vars) + NrnRange::NrnRange(RangeVarVector range_vars) : range_vars(range_vars) { } @@ -2665,7 +2665,7 @@ writelist(writelist) } /* constructor for NrnGlobal ast node */ - NrnGlobal::NrnGlobal(GlobalVarList global_vars) + NrnGlobal::NrnGlobal(GlobalVarVector global_vars) : global_vars(global_vars) { } @@ -2686,7 +2686,7 @@ writelist(writelist) } /* constructor for NrnPointer ast node */ - NrnPointer::NrnPointer(PointerVarList pointers) + NrnPointer::NrnPointer(PointerVarVector pointers) : pointers(pointers) { } @@ -2707,7 +2707,7 @@ writelist(writelist) } /* constructor for NrnBbcorePtr ast node */ - NrnBbcorePtr::NrnBbcorePtr(BbcorePointerVarList bbcore_pointers) + NrnBbcorePtr::NrnBbcorePtr(BbcorePointerVarVector bbcore_pointers) : bbcore_pointers(bbcore_pointers) { } @@ -2728,7 +2728,7 @@ writelist(writelist) } /* constructor for NrnExternal ast node */ - NrnExternal::NrnExternal(ExternVarList externals) + NrnExternal::NrnExternal(ExternVarVector externals) : externals(externals) { } @@ -2749,7 +2749,7 @@ writelist(writelist) } /* constructor for NrnThreadSafe ast node */ - NrnThreadSafe::NrnThreadSafe(ThreadsafeVarList threadsafe) + NrnThreadSafe::NrnThreadSafe(ThreadsafeVarVector threadsafe) : threadsafe(threadsafe) { } @@ -2809,7 +2809,7 @@ writelist(writelist) } /* constructor for Program ast node */ - Program::Program(StatementList statements, BlockList blocks) + Program::Program(StatementVector statements, BlockVector blocks) : statements(statements), blocks(blocks) { diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp index c07ca6f8f8..9f4cbabfbd 100644 --- a/src/nmodl/ast/ast.hpp +++ b/src/nmodl/ast/ast.hpp @@ -143,139 +143,139 @@ namespace ast { class Program; /* std::vector for convenience */ - using StatementList = std::vector>; - using ExpressionList = std::vector>; - using BlockList = std::vector>; - using IdentifierList = std::vector>; - using NumberList = std::vector>; - using StringList = std::vector>; - using IntegerList = std::vector>; - using FloatList = std::vector>; - using DoubleList = std::vector>; - using BooleanList = std::vector>; - using NameList = std::vector>; - using PrimeNameList = std::vector>; - using VarNameList = std::vector>; - using IndexedNameList = std::vector>; - using ArgumentList = std::vector>; - using ReactVarNameList = std::vector>; - using ReadIonVarList = std::vector>; - using WriteIonVarList = std::vector>; - using NonspeCurVarList = std::vector>; - using ElectrodeCurVarList = std::vector>; - using SectionVarList = std::vector>; - using RangeVarList = std::vector>; - using GlobalVarList = std::vector>; - using PointerVarList = std::vector>; - using BbcorePointerVarList = std::vector>; - using ExternVarList = std::vector>; - using ThreadsafeVarList = std::vector>; - using ParamBlockList = std::vector>; - using StepBlockList = std::vector>; - using IndependentBlockList = std::vector>; - using DependentBlockList = std::vector>; - using StateBlockList = std::vector>; - using PlotBlockList = std::vector>; - using InitialBlockList = std::vector>; - using ConstructorBlockList = std::vector>; - using DestructorBlockList = std::vector>; - using StatementBlockList = std::vector>; - using DerivativeBlockList = std::vector>; - using LinearBlockList = std::vector>; - using NonLinearBlockList = std::vector>; - using DiscreteBlockList = std::vector>; - using PartialBlockList = std::vector>; - using FunctionTableBlockList = std::vector>; - using FunctionBlockList = std::vector>; - using ProcedureBlockList = std::vector>; - using NetReceiveBlockList = std::vector>; - using SolveBlockList = std::vector>; - using BreakpointBlockList = std::vector>; - using TerminalBlockList = std::vector>; - using BeforeBlockList = std::vector>; - using AfterBlockList = std::vector>; - using BABlockList = std::vector>; - using ForNetconList = std::vector>; - using KineticBlockList = std::vector>; - using MatchBlockList = std::vector>; - using UnitBlockList = std::vector>; - using ConstantBlockList = std::vector>; - using NeuronBlockList = std::vector>; - using UnitList = std::vector>; - using DoubleUnitList = std::vector>; - using LocalVariableList = std::vector>; - using LimitsList = std::vector>; - using NumberRangeList = std::vector>; - using PlotVariableList = std::vector>; - using BinaryOperatorList = std::vector>; - using UnaryOperatorList = std::vector>; - using ReactionOperatorList = std::vector>; - using BinaryExpressionList = std::vector>; - using UnaryExpressionList = std::vector>; - using NonLinEuationList = std::vector>; - using LinEquationList = std::vector>; - using FunctionCallList = std::vector>; - using FirstLastTypeIndexList = std::vector>; - using WatchList = std::vector>; - using QueueExpressionTypeList = std::vector>; - using MatchList = std::vector>; - using BABlockTypeList = std::vector>; - using UnitDefList = std::vector>; - using FactorDefList = std::vector>; - using ValenceList = std::vector>; - using UnitStateList = std::vector>; - using LocalListStatementList = std::vector>; - using ModelList = std::vector>; - using DefineList = std::vector>; - using IncludeList = std::vector>; - using ParamAssignList = std::vector>; - using SteppedList = std::vector>; - using IndependentDefList = std::vector>; - using DependentDefList = std::vector>; - using PlotDeclarationList = std::vector>; - using ConductanceHintList = std::vector>; - using ExpressionStatementList = std::vector>; - using ProtectStatementList = std::vector>; - using FromStatementList = std::vector>; - using ForAllStatementList = std::vector>; - using WhileStatementList = std::vector>; - using IfStatementList = std::vector>; - using ElseIfStatementList = std::vector>; - using ElseStatementList = std::vector>; - using PartialEquationList = std::vector>; - using PartialBoundaryList = std::vector>; - using WatchStatementList = std::vector>; - using MutexLockList = std::vector>; - using MutexUnlockList = std::vector>; - using ResetList = std::vector>; - using SensList = std::vector>; - using ConserveList = std::vector>; - using CompartmentList = std::vector>; - using LDifuseList = std::vector>; - using ReactionStatementList = std::vector>; - using LagStatementList = std::vector>; - using QueueStatementList = std::vector>; - using ConstantStatementList = std::vector>; - using TableStatementList = std::vector>; - using NrnSuffixList = std::vector>; - using NrnUseionList = std::vector>; - using NrnNonspecificList = std::vector>; - using NrnElctrodeCurrentList = std::vector>; - using NrnSectionList = std::vector>; - using NrnRangeList = std::vector>; - using NrnGlobalList = std::vector>; - using NrnPointerList = std::vector>; - using NrnBbcorePtrList = std::vector>; - using NrnExternalList = std::vector>; - using NrnThreadSafeList = std::vector>; - using VerbatimList = std::vector>; - using CommentList = std::vector>; - using ProgramList = std::vector>; - using NumberList = std::vector>; - using IdentifierList = std::vector>; - using BlockList = std::vector>; - using ExpressionList = std::vector>; - using StatementList = std::vector>; + using StatementVector = std::vector>; + using ExpressionVector = std::vector>; + using BlockVector = std::vector>; + using IdentifierVector = std::vector>; + using NumberVector = std::vector>; + using StringVector = std::vector>; + using IntegerVector = std::vector>; + using FloatVector = std::vector>; + using DoubleVector = std::vector>; + using BooleanVector = std::vector>; + using NameVector = std::vector>; + using PrimeNameVector = std::vector>; + using VarNameVector = std::vector>; + using IndexedNameVector = std::vector>; + using ArgumentVector = std::vector>; + using ReactVarNameVector = std::vector>; + using ReadIonVarVector = std::vector>; + using WriteIonVarVector = std::vector>; + using NonspeCurVarVector = std::vector>; + using ElectrodeCurVarVector = std::vector>; + using SectionVarVector = std::vector>; + using RangeVarVector = std::vector>; + using GlobalVarVector = std::vector>; + using PointerVarVector = std::vector>; + using BbcorePointerVarVector = std::vector>; + using ExternVarVector = std::vector>; + using ThreadsafeVarVector = std::vector>; + using ParamBlockVector = std::vector>; + using StepBlockVector = std::vector>; + using IndependentBlockVector = std::vector>; + using DependentBlockVector = std::vector>; + using StateBlockVector = std::vector>; + using PlotBlockVector = std::vector>; + using InitialBlockVector = std::vector>; + using ConstructorBlockVector = std::vector>; + using DestructorBlockVector = std::vector>; + using StatementBlockVector = std::vector>; + using DerivativeBlockVector = std::vector>; + using LinearBlockVector = std::vector>; + using NonLinearBlockVector = std::vector>; + using DiscreteBlockVector = std::vector>; + using PartialBlockVector = std::vector>; + using FunctionTableBlockVector = std::vector>; + using FunctionBlockVector = std::vector>; + using ProcedureBlockVector = std::vector>; + using NetReceiveBlockVector = std::vector>; + using SolveBlockVector = std::vector>; + using BreakpointBlockVector = std::vector>; + using TerminalBlockVector = std::vector>; + using BeforeBlockVector = std::vector>; + using AfterBlockVector = std::vector>; + using BABlockVector = std::vector>; + using ForNetconVector = std::vector>; + using KineticBlockVector = std::vector>; + using MatchBlockVector = std::vector>; + using UnitBlockVector = std::vector>; + using ConstantBlockVector = std::vector>; + using NeuronBlockVector = std::vector>; + using UnitVector = std::vector>; + using DoubleUnitVector = std::vector>; + using LocalVariableVector = std::vector>; + using LimitsVector = std::vector>; + using NumberRangeVector = std::vector>; + using PlotVariableVector = std::vector>; + using BinaryOperatorVector = std::vector>; + using UnaryOperatorVector = std::vector>; + using ReactionOperatorVector = std::vector>; + using BinaryExpressionVector = std::vector>; + using UnaryExpressionVector = std::vector>; + using NonLinEuationVector = std::vector>; + using LinEquationVector = std::vector>; + using FunctionCallVector = std::vector>; + using FirstLastTypeIndexVector = std::vector>; + using WatchVector = std::vector>; + using QueueExpressionTypeVector = std::vector>; + using MatchVector = std::vector>; + using BABlockTypeVector = std::vector>; + using UnitDefVector = std::vector>; + using FactorDefVector = std::vector>; + using ValenceVector = std::vector>; + using UnitStateVector = std::vector>; + using LocalListStatementVector = std::vector>; + using ModelVector = std::vector>; + using DefineVector = std::vector>; + using IncludeVector = std::vector>; + using ParamAssignVector = std::vector>; + using SteppedVector = std::vector>; + using IndependentDefVector = std::vector>; + using DependentDefVector = std::vector>; + using PlotDeclarationVector = std::vector>; + using ConductanceHintVector = std::vector>; + using ExpressionStatementVector = std::vector>; + using ProtectStatementVector = std::vector>; + using FromStatementVector = std::vector>; + using ForAllStatementVector = std::vector>; + using WhileStatementVector = std::vector>; + using IfStatementVector = std::vector>; + using ElseIfStatementVector = std::vector>; + using ElseStatementVector = std::vector>; + using PartialEquationVector = std::vector>; + using PartialBoundaryVector = std::vector>; + using WatchStatementVector = std::vector>; + using MutexLockVector = std::vector>; + using MutexUnlockVector = std::vector>; + using ResetVector = std::vector>; + using SensVector = std::vector>; + using ConserveVector = std::vector>; + using CompartmentVector = std::vector>; + using LDifuseVector = std::vector>; + using ReactionStatementVector = std::vector>; + using LagStatementVector = std::vector>; + using QueueStatementVector = std::vector>; + using ConstantStatementVector = std::vector>; + using TableStatementVector = std::vector>; + using NrnSuffixVector = std::vector>; + using NrnUseionVector = std::vector>; + using NrnNonspecificVector = std::vector>; + using NrnElctrodeCurrentVector = std::vector>; + using NrnSectionVector = std::vector>; + using NrnRangeVector = std::vector>; + using NrnGlobalVector = std::vector>; + using NrnPointerVector = std::vector>; + using NrnBbcorePtrVector = std::vector>; + using NrnExternalVector = std::vector>; + using NrnThreadSafeVector = std::vector>; + using VerbatimVector = std::vector>; + using CommentVector = std::vector>; + using ProgramVector = std::vector>; + using NumberVector = std::vector>; + using IdentifierVector = std::vector>; + using BlockVector = std::vector>; + using ExpressionVector = std::vector>; + using StatementVector = std::vector>; using statement_ptr = Statement*; using expression_ptr = Expression*; using block_ptr = Block*; @@ -305,13 +305,13 @@ namespace ast { using externvar_ptr = ExternVar*; using threadsafevar_ptr = ThreadsafeVar*; using paramblock_ptr = ParamBlock*; - using paramassign_list = ParamAssignList; + using paramassign_list = ParamAssignVector; using stepblock_ptr = StepBlock*; - using stepped_list = SteppedList; + using stepped_list = SteppedVector; using independentblock_ptr = IndependentBlock*; - using independentdef_list = IndependentDefList; + using independentdef_list = IndependentDefVector; using dependentblock_ptr = DependentBlock*; - using dependentdef_list = DependentDefList; + using dependentdef_list = DependentDefVector; using stateblock_ptr = StateBlock*; using plotblock_ptr = PlotBlock*; using plotdeclaration_ptr = PlotDeclaration*; @@ -319,15 +319,15 @@ namespace ast { using statementblock_ptr = StatementBlock*; using constructorblock_ptr = ConstructorBlock*; using destructorblock_ptr = DestructorBlock*; - using statement_list = StatementList; + using statement_list = StatementVector; using derivativeblock_ptr = DerivativeBlock*; using linearblock_ptr = LinearBlock*; - using name_list = NameList; + using name_list = NameVector; using nonlinearblock_ptr = NonLinearBlock*; using discreteblock_ptr = DiscreteBlock*; using partialblock_ptr = PartialBlock*; using functiontableblock_ptr = FunctionTableBlock*; - using argument_list = ArgumentList; + using argument_list = ArgumentVector; using functionblock_ptr = FunctionBlock*; using procedureblock_ptr = ProcedureBlock*; using netreceiveblock_ptr = NetReceiveBlock*; @@ -341,11 +341,11 @@ namespace ast { using fornetcon_ptr = ForNetcon*; using kineticblock_ptr = KineticBlock*; using matchblock_ptr = MatchBlock*; - using match_list = MatchList; + using match_list = MatchVector; using unitblock_ptr = UnitBlock*; - using expression_list = ExpressionList; + using expression_list = ExpressionVector; using constantblock_ptr = ConstantBlock*; - using constantstatement_list = ConstantStatementList; + using constantstatement_list = ConstantStatementVector; using neuronblock_ptr = NeuronBlock*; using doubleunit_ptr = DoubleUnit*; using localvariable_ptr = LocalVariable*; @@ -369,16 +369,16 @@ namespace ast { using valence_ptr = Valence*; using unitstate_ptr = UnitState*; using localliststatement_ptr = LocalListStatement*; - using localvariable_list = LocalVariableList; + using localvariable_list = LocalVariableVector; using model_ptr = Model*; using define_ptr = Define*; using include_ptr = Include*; using paramassign_ptr = ParamAssign*; using stepped_ptr = Stepped*; - using number_list = NumberList; + using number_list = NumberVector; using independentdef_ptr = IndependentDef*; using dependentdef_ptr = DependentDef*; - using plotvariable_list = PlotVariableList; + using plotvariable_list = PlotVariableVector; using conductancehint_ptr = ConductanceHint*; using expressionstatement_ptr = ExpressionStatement*; using protectstatement_ptr = ProtectStatement*; @@ -386,18 +386,18 @@ namespace ast { using forallstatement_ptr = ForAllStatement*; using whilestatement_ptr = WhileStatement*; using ifstatement_ptr = IfStatement*; - using elseifstatement_list = ElseIfStatementList; + using elseifstatement_list = ElseIfStatementVector; using elsestatement_ptr = ElseStatement*; using elseifstatement_ptr = ElseIfStatement*; using partialequation_ptr = PartialEquation*; using partialboundary_ptr = PartialBoundary*; using watchstatement_ptr = WatchStatement*; - using watch_list = WatchList; + using watch_list = WatchVector; using mutexlock_ptr = MutexLock*; using mutexunlock_ptr = MutexUnlock*; using reset_ptr = Reset*; using sens_ptr = Sens*; - using varname_list = VarNameList; + using varname_list = VarNameVector; using conserve_ptr = Conserve*; using compartment_ptr = Compartment*; using ldifuse_ptr = LDifuse*; @@ -408,30 +408,30 @@ namespace ast { using tablestatement_ptr = TableStatement*; using nrnsuffix_ptr = NrnSuffix*; using nrnuseion_ptr = NrnUseion*; - using readionvar_list = ReadIonVarList; - using writeionvar_list = WriteIonVarList; + using readionvar_list = ReadIonVarVector; + using writeionvar_list = WriteIonVarVector; using nrnnonspecific_ptr = NrnNonspecific*; - using nonspecurvar_list = NonspeCurVarList; + using nonspecurvar_list = NonspeCurVarVector; using nrnelctrodecurrent_ptr = NrnElctrodeCurrent*; - using electrodecurvar_list = ElectrodeCurVarList; + using electrodecurvar_list = ElectrodeCurVarVector; using nrnsection_ptr = NrnSection*; - using sectionvar_list = SectionVarList; + using sectionvar_list = SectionVarVector; using nrnrange_ptr = NrnRange*; - using rangevar_list = RangeVarList; + using rangevar_list = RangeVarVector; using nrnglobal_ptr = NrnGlobal*; - using globalvar_list = GlobalVarList; + using globalvar_list = GlobalVarVector; using nrnpointer_ptr = NrnPointer*; - using pointervar_list = PointerVarList; + using pointervar_list = PointerVarVector; using nrnbbcoreptr_ptr = NrnBbcorePtr*; - using bbcorepointervar_list = BbcorePointerVarList; + using bbcorepointervar_list = BbcorePointerVarVector; using nrnexternal_ptr = NrnExternal*; - using externvar_list = ExternVarList; + using externvar_list = ExternVarVector; using nrnthreadsafe_ptr = NrnThreadSafe*; - using threadsafevar_list = ThreadsafeVarList; + using threadsafevar_list = ThreadsafeVarVector; using verbatim_ptr = Verbatim*; using comment_ptr = Comment*; using program_ptr = Program*; - using block_list = BlockList; + using block_list = BlockVector; #include @@ -886,11 +886,11 @@ namespace ast { class ParamBlock : public Block { public: /* member variables */ - ParamAssignList statements; + ParamAssignVector statements; void* symtab = nullptr; /* constructors */ - ParamBlock(ParamAssignList statements); + ParamBlock(ParamAssignVector statements); ParamBlock(const ParamBlock& obj); std::string getName() { return getType(); } @@ -906,11 +906,11 @@ namespace ast { class StepBlock : public Block { public: /* member variables */ - SteppedList statements; + SteppedVector statements; void* symtab = nullptr; /* constructors */ - StepBlock(SteppedList statements); + StepBlock(SteppedVector statements); StepBlock(const StepBlock& obj); std::string getName() { return getType(); } @@ -926,11 +926,11 @@ namespace ast { class IndependentBlock : public Block { public: /* member variables */ - IndependentDefList definitions; + IndependentDefVector definitions; void* symtab = nullptr; /* constructors */ - IndependentBlock(IndependentDefList definitions); + IndependentBlock(IndependentDefVector definitions); IndependentBlock(const IndependentBlock& obj); std::string getName() { return getType(); } @@ -946,11 +946,11 @@ namespace ast { class DependentBlock : public Block { public: /* member variables */ - DependentDefList definitions; + DependentDefVector definitions; void* symtab = nullptr; /* constructors */ - DependentBlock(DependentDefList definitions); + DependentBlock(DependentDefVector definitions); DependentBlock(const DependentBlock& obj); std::string getName() { return getType(); } @@ -966,11 +966,11 @@ namespace ast { class StateBlock : public Block { public: /* member variables */ - DependentDefList definitions; + DependentDefVector definitions; void* symtab = nullptr; /* constructors */ - StateBlock(DependentDefList definitions); + StateBlock(DependentDefVector definitions); StateBlock(const StateBlock& obj); std::string getName() { return getType(); } @@ -1066,12 +1066,12 @@ namespace ast { class StatementBlock : public Block { public: /* member variables */ - StatementList statements; + StatementVector statements; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - StatementBlock(StatementList statements); + StatementBlock(StatementVector statements); StatementBlock(const StatementBlock& obj); std::string getName() { return getType(); } @@ -1114,13 +1114,13 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - NameList solvefor; + NameVector solvefor; std::shared_ptr statementblock; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - LinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock); + LinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); LinearBlock(const LinearBlock& obj); virtual std::string getName() { return name->getName(); } @@ -1139,13 +1139,13 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - NameList solvefor; + NameVector solvefor; std::shared_ptr statementblock; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - NonLinearBlock(Name* name, NameList solvefor, StatementBlock* statementblock); + NonLinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); NonLinearBlock(const NonLinearBlock& obj); virtual std::string getName() { return name->getName(); } @@ -1212,13 +1212,13 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - ArgumentList arguments; + ArgumentVector arguments; std::shared_ptr unit; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - FunctionTableBlock(Name* name, ArgumentList arguments, Unit* unit); + FunctionTableBlock(Name* name, ArgumentVector arguments, Unit* unit); FunctionTableBlock(const FunctionTableBlock& obj); virtual std::string getName() { return name->getName(); } @@ -1237,14 +1237,14 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - ArgumentList arguments; + ArgumentVector arguments; std::shared_ptr unit; std::shared_ptr statementblock; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - FunctionBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock); + FunctionBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock); FunctionBlock(const FunctionBlock& obj); virtual std::string getName() { return name->getName(); } @@ -1263,14 +1263,14 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - ArgumentList arguments; + ArgumentVector arguments; std::shared_ptr unit; std::shared_ptr statementblock; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - ProcedureBlock(Name* name, ArgumentList arguments, Unit* unit, StatementBlock* statementblock); + ProcedureBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock); ProcedureBlock(const ProcedureBlock& obj); virtual std::string getName() { return name->getName(); } @@ -1288,12 +1288,12 @@ namespace ast { class NetReceiveBlock : public Block { public: /* member variables */ - ArgumentList arguments; + ArgumentVector arguments; std::shared_ptr statementblock; void* symtab = nullptr; /* constructors */ - NetReceiveBlock(ArgumentList arguments, StatementBlock* statementblock); + NetReceiveBlock(ArgumentVector arguments, StatementBlock* statementblock); NetReceiveBlock(const NetReceiveBlock& obj); std::string getName() { return getType(); } @@ -1432,12 +1432,12 @@ namespace ast { class ForNetcon : public Block { public: /* member variables */ - ArgumentList arguments; + ArgumentVector arguments; std::shared_ptr statementblock; void* symtab = nullptr; /* constructors */ - ForNetcon(ArgumentList arguments, StatementBlock* statementblock); + ForNetcon(ArgumentVector arguments, StatementBlock* statementblock); ForNetcon(const ForNetcon& obj); std::string getName() { return getType(); } @@ -1454,13 +1454,13 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - NameList solvefor; + NameVector solvefor; std::shared_ptr statementblock; std::shared_ptr token; void* symtab = nullptr; /* constructors */ - KineticBlock(Name* name, NameList solvefor, StatementBlock* statementblock); + KineticBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); KineticBlock(const KineticBlock& obj); virtual std::string getName() { return name->getName(); } @@ -1478,11 +1478,11 @@ namespace ast { class MatchBlock : public Block { public: /* member variables */ - MatchList matchs; + MatchVector matchs; void* symtab = nullptr; /* constructors */ - MatchBlock(MatchList matchs); + MatchBlock(MatchVector matchs); MatchBlock(const MatchBlock& obj); std::string getName() { return getType(); } @@ -1498,11 +1498,11 @@ namespace ast { class UnitBlock : public Block { public: /* member variables */ - ExpressionList definitions; + ExpressionVector definitions; void* symtab = nullptr; /* constructors */ - UnitBlock(ExpressionList definitions); + UnitBlock(ExpressionVector definitions); UnitBlock(const UnitBlock& obj); std::string getName() { return getType(); } @@ -1518,11 +1518,11 @@ namespace ast { class ConstantBlock : public Block { public: /* member variables */ - ConstantStatementList statements; + ConstantStatementVector statements; void* symtab = nullptr; /* constructors */ - ConstantBlock(ConstantStatementList statements); + ConstantBlock(ConstantStatementVector statements); ConstantBlock(const ConstantBlock& obj); std::string getName() { return getType(); } @@ -1776,9 +1776,9 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - ExpressionList arguments; + ExpressionVector arguments; /* constructors */ - FunctionCall(Name* name, ExpressionList arguments); + FunctionCall(Name* name, ExpressionVector arguments); FunctionCall(const FunctionCall& obj); virtual void visitChildren(Visitor* v); @@ -1944,9 +1944,9 @@ namespace ast { class LocalListStatement : public Statement { public: /* member variables */ - LocalVariableList variables; + LocalVariableVector variables; /* constructors */ - LocalListStatement(LocalVariableList variables); + LocalListStatement(LocalVariableVector variables); LocalListStatement(const LocalListStatement& obj); virtual void visitChildren(Visitor* v); @@ -2026,10 +2026,10 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - NumberList values; + NumberVector values; std::shared_ptr unit; /* constructors */ - Stepped(Name* name, NumberList values, Unit* unit); + Stepped(Name* name, NumberVector values, Unit* unit); Stepped(const Stepped& obj); virtual void visitChildren(Visitor* v); @@ -2086,10 +2086,10 @@ namespace ast { class PlotDeclaration : public Statement { public: /* member variables */ - PlotVariableList pvlist; + PlotVariableVector pvlist; std::shared_ptr name; /* constructors */ - PlotDeclaration(PlotVariableList pvlist, PlotVariable* name); + PlotDeclaration(PlotVariableVector pvlist, PlotVariable* name); PlotDeclaration(const PlotDeclaration& obj); virtual void visitChildren(Visitor* v); @@ -2201,10 +2201,10 @@ namespace ast { /* member variables */ std::shared_ptr condition; std::shared_ptr statementblock; - ElseIfStatementList elseifs; + ElseIfStatementVector elseifs; std::shared_ptr elses; /* constructors */ - IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementList elseifs, ElseStatement* elses); + IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementVector elseifs, ElseStatement* elses); IfStatement(const IfStatement& obj); virtual void visitChildren(Visitor* v); @@ -2288,9 +2288,9 @@ namespace ast { class WatchStatement : public Statement { public: /* member variables */ - WatchList statements; + WatchVector statements; /* constructors */ - WatchStatement(WatchList statements); + WatchStatement(WatchVector statements); WatchStatement(const WatchStatement& obj); void addWatch(Watch *s) { @@ -2333,9 +2333,9 @@ namespace ast { class Sens : public Statement { public: /* member variables */ - VarNameList senslist; + VarNameVector senslist; /* constructors */ - Sens(VarNameList senslist); + Sens(VarNameVector senslist); Sens(const Sens& obj); virtual void visitChildren(Visitor* v); @@ -2366,9 +2366,9 @@ namespace ast { /* member variables */ std::shared_ptr name; std::shared_ptr expression; - NameList names; + NameVector names; /* constructors */ - Compartment(Name* name, Expression* expression, NameList names); + Compartment(Name* name, Expression* expression, NameVector names); Compartment(const Compartment& obj); virtual void visitChildren(Visitor* v); @@ -2383,9 +2383,9 @@ namespace ast { /* member variables */ std::shared_ptr name; std::shared_ptr expression; - NameList names; + NameVector names; /* constructors */ - LDifuse(Name* name, Expression* expression, NameList names); + LDifuse(Name* name, Expression* expression, NameVector names); LDifuse(const LDifuse& obj); virtual void visitChildren(Visitor* v); @@ -2466,13 +2466,13 @@ namespace ast { class TableStatement : public Statement { public: /* member variables */ - NameList tablst; - NameList dependlst; + NameVector tablst; + NameVector dependlst; std::shared_ptr from; std::shared_ptr to; std::shared_ptr with; /* constructors */ - TableStatement(NameList tablst, NameList dependlst, Expression* from, Expression* to, Integer* with); + TableStatement(NameVector tablst, NameVector dependlst, Expression* from, Expression* to, Integer* with); TableStatement(const TableStatement& obj); virtual void visitChildren(Visitor* v); @@ -2502,11 +2502,11 @@ namespace ast { public: /* member variables */ std::shared_ptr name; - ReadIonVarList readlist; - WriteIonVarList writelist; + ReadIonVarVector readlist; + WriteIonVarVector writelist; std::shared_ptr valence; /* constructors */ - NrnUseion(Name* name, ReadIonVarList readlist, WriteIonVarList writelist, Valence* valence); + NrnUseion(Name* name, ReadIonVarVector readlist, WriteIonVarVector writelist, Valence* valence); NrnUseion(const NrnUseion& obj); virtual void visitChildren(Visitor* v); @@ -2519,9 +2519,9 @@ namespace ast { class NrnNonspecific : public Statement { public: /* member variables */ - NonspeCurVarList currents; + NonspeCurVarVector currents; /* constructors */ - NrnNonspecific(NonspeCurVarList currents); + NrnNonspecific(NonspeCurVarVector currents); NrnNonspecific(const NrnNonspecific& obj); virtual void visitChildren(Visitor* v); @@ -2534,9 +2534,9 @@ namespace ast { class NrnElctrodeCurrent : public Statement { public: /* member variables */ - ElectrodeCurVarList ecurrents; + ElectrodeCurVarVector ecurrents; /* constructors */ - NrnElctrodeCurrent(ElectrodeCurVarList ecurrents); + NrnElctrodeCurrent(ElectrodeCurVarVector ecurrents); NrnElctrodeCurrent(const NrnElctrodeCurrent& obj); virtual void visitChildren(Visitor* v); @@ -2549,9 +2549,9 @@ namespace ast { class NrnSection : public Statement { public: /* member variables */ - SectionVarList sections; + SectionVarVector sections; /* constructors */ - NrnSection(SectionVarList sections); + NrnSection(SectionVarVector sections); NrnSection(const NrnSection& obj); virtual void visitChildren(Visitor* v); @@ -2564,9 +2564,9 @@ namespace ast { class NrnRange : public Statement { public: /* member variables */ - RangeVarList range_vars; + RangeVarVector range_vars; /* constructors */ - NrnRange(RangeVarList range_vars); + NrnRange(RangeVarVector range_vars); NrnRange(const NrnRange& obj); virtual void visitChildren(Visitor* v); @@ -2579,9 +2579,9 @@ namespace ast { class NrnGlobal : public Statement { public: /* member variables */ - GlobalVarList global_vars; + GlobalVarVector global_vars; /* constructors */ - NrnGlobal(GlobalVarList global_vars); + NrnGlobal(GlobalVarVector global_vars); NrnGlobal(const NrnGlobal& obj); virtual void visitChildren(Visitor* v); @@ -2594,9 +2594,9 @@ namespace ast { class NrnPointer : public Statement { public: /* member variables */ - PointerVarList pointers; + PointerVarVector pointers; /* constructors */ - NrnPointer(PointerVarList pointers); + NrnPointer(PointerVarVector pointers); NrnPointer(const NrnPointer& obj); virtual void visitChildren(Visitor* v); @@ -2609,9 +2609,9 @@ namespace ast { class NrnBbcorePtr : public Statement { public: /* member variables */ - BbcorePointerVarList bbcore_pointers; + BbcorePointerVarVector bbcore_pointers; /* constructors */ - NrnBbcorePtr(BbcorePointerVarList bbcore_pointers); + NrnBbcorePtr(BbcorePointerVarVector bbcore_pointers); NrnBbcorePtr(const NrnBbcorePtr& obj); virtual void visitChildren(Visitor* v); @@ -2624,9 +2624,9 @@ namespace ast { class NrnExternal : public Statement { public: /* member variables */ - ExternVarList externals; + ExternVarVector externals; /* constructors */ - NrnExternal(ExternVarList externals); + NrnExternal(ExternVarVector externals); NrnExternal(const NrnExternal& obj); virtual void visitChildren(Visitor* v); @@ -2639,9 +2639,9 @@ namespace ast { class NrnThreadSafe : public Statement { public: /* member variables */ - ThreadsafeVarList threadsafe; + ThreadsafeVarVector threadsafe; /* constructors */ - NrnThreadSafe(ThreadsafeVarList threadsafe); + NrnThreadSafe(ThreadsafeVarVector threadsafe); NrnThreadSafe(const NrnThreadSafe& obj); virtual void visitChildren(Visitor* v); @@ -2684,12 +2684,12 @@ namespace ast { class Program : public AST { public: /* member variables */ - StatementList statements; - BlockList blocks; + StatementVector statements; + BlockVector blocks; void* symtab = nullptr; /* constructors */ - Program(StatementList statements, BlockList blocks); + Program(StatementVector statements, BlockVector blocks); Program(const Program& obj); void addStatement(Statement *s) { diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 3c3b50020b..926d34a5a7 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -512,7 +512,7 @@ parmblk : PARAMETER "{" parmbody"}" parmbody : { - $$ = ast::ParamAssignList(); + $$ = ast::ParamAssignVector(); } | parmbody parmasgn { @@ -579,7 +579,7 @@ stepblk : STEPPED "{" stepbdy "}" ; -stepbdy : { $$ = ast::SteppedList(); } +stepbdy : { $$ = ast::SteppedVector(); } | stepbdy stepped { $1.push_back(std::shared_ptr($2)); @@ -597,7 +597,7 @@ stepped : NAME "=" numlist units numlist : number "," number { - $$ = ast::NumberList(); + $$ = ast::NumberVector(); $$.push_back(std::shared_ptr($1)); $$.push_back(std::shared_ptr($3)); } @@ -650,7 +650,7 @@ indepblk : INDEPENDENT "{" indepbody "}" indepbody : { - $$ = ast::IndependentDefList(); + $$ = ast::IndependentDefVector(); } | indepbody indepdef { @@ -689,7 +689,7 @@ depblk : DEPENDENT "{" depbody"}" depbody : { - $$ = ast::DependentDefList(); + $$ = ast::DependentDefVector(); } | depbody depdef { @@ -752,7 +752,7 @@ plotdecl : PLOT pvlist VS name optindex pvlist : name optindex { - $$ = ast::PlotVariableList(); + $$ = ast::PlotVariableVector(); auto variable = new ast::PlotVariable($1, $2); $$.push_back(std::shared_ptr(variable)); } @@ -855,7 +855,7 @@ locallist : LOCAL locallist1 locallist1 : NAME locoptarray { - $$ = ast::LocalVariableList(); + $$ = ast::LocalVariableVector(); if($2) { auto variable = new ast::LocalVariable(new ast::IndexedName($1, $2)); $$.push_back(std::shared_ptr(variable)); @@ -884,7 +884,7 @@ locoptarray : { $$ = nullptr; } stmtlist1 : { - $$ = ast::StatementList(); + $$ = ast::StatementVector(); } | stmtlist1 ostmt { @@ -1176,16 +1176,16 @@ funccall : NAME "(" exprlist ")" exprlist : { - $$ = ast::ExpressionList(); + $$ = ast::ExpressionVector(); } | expr { - $$ = ast::ExpressionList(); + $$ = ast::ExpressionVector(); $$.push_back(std::shared_ptr($1)); } | STRING { - $$ = ast::ExpressionList(); + $$ = ast::ExpressionVector(); $$.push_back(std::shared_ptr($1)); } | exprlist "," expr @@ -1243,7 +1243,7 @@ ifstmt : IF "(" expr ")" stmtlist "}" optelseif optelse optelseif : { - $$ = ast::ElseIfStatementList(); + $$ = ast::ElseIfStatementVector(); } | optelseif ELSE IF "(" expr ")" stmtlist "}" { @@ -1349,7 +1349,7 @@ funcblk : FUNCTION1 NAME "(" arglist ")" units stmtlist "}" arglist : { - $$ = ast::ArgumentList(); + $$ = ast::ArgumentVector(); } | arglist1 { $$ = $1; } ; @@ -1357,7 +1357,7 @@ arglist : { arglist1 : name units { - $$ = ast::ArgumentList(); + $$ = ast::ArgumentVector(); $$.push_back(std::shared_ptr(new ast::Argument($1, $2))); } | arglist1 "," name units @@ -1413,14 +1413,14 @@ ifsolerr : { $$ = nullptr; } ; -solvefor : { $$ = ast::NameList(); } +solvefor : { $$ = ast::NameVector(); } | solvefor1 { $$ = $1; } ; solvefor1 : SOLVEFOR NAME { - $$ = ast::NameList(); + $$ = ast::NameVector(); $$.push_back(std::shared_ptr($2)); } | solvefor1 "," NAME @@ -1474,7 +1474,7 @@ bablk : BREAKPOINT stmtlist "}" watchstmt : WATCH watch1 { - $$ = new ast::WatchStatement(ast::WatchList()); + $$ = new ast::WatchStatement(ast::WatchVector()); $$->addWatch($2); } | watchstmt "," watch1 @@ -1568,7 +1568,7 @@ sens : SENS senslist senslist : varname { - $$ = ast::VarNameList(); + $$ = ast::VarNameVector(); $$.push_back(std::shared_ptr($1)); } | senslist "," varname @@ -1614,7 +1614,7 @@ ldifus : LONGDIFUS NAME "," expr "{" namelist "}" namelist : NAME { - $$ = ast::NameList(); + $$ = ast::NameVector(); $$.push_back(std::shared_ptr($1)); } | namelist NAME @@ -1705,7 +1705,7 @@ matchblk : MATCH "{" matchlist "}" matchlist : match { - $$ = ast::MatchList(); + $$ = ast::MatchVector(); $$.push_back(std::shared_ptr($1)); } | matchlist match @@ -1749,7 +1749,7 @@ unitblk : UNITS "{" unitbody "}" unitbody : { - $$ = ast::ExpressionList(); + $$ = ast::ExpressionVector(); } | unitbody unitdef { @@ -1805,7 +1805,7 @@ constblk : CONSTANT "{" conststmt "}" conststmt : { - $$ = ast::ConstantStatementList(); + $$ = ast::ConstantStatementVector(); } | conststmt NAME "=" number units { @@ -1827,14 +1827,14 @@ tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER ; -tablst : { $$ = ast::NameList(); } +tablst : { $$ = ast::NameVector(); } | tablst1 { $$ = $1; } ; tablst1 : Name { - $$ = ast::NameList(); + $$ = ast::NameVector(); $$.push_back(std::shared_ptr($1)); } | tablst1 "," Name @@ -1845,7 +1845,7 @@ tablst1 : Name ; -dependlst : { $$ = ast::NameList(); } +dependlst : { $$ = ast::NameVector(); } | DEPEND tablst1 { $$ = $2; } ; @@ -1859,7 +1859,7 @@ neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE ; -nrnstmt : { $$ = ast::StatementList(); } +nrnstmt : { $$ = ast::StatementVector(); } | nrnstmt SUFFIX NAME { auto statement = new ast::NrnSuffix($2, $3); @@ -1930,11 +1930,11 @@ nrnstmt : { $$ = ast::StatementList(); } nrnuse : USEION NAME READ nrnionrlist valence { - $$ = new ast::NrnUseion($2, $4, ast::WriteIonVarList(), $5); + $$ = new ast::NrnUseion($2, $4, ast::WriteIonVarVector(), $5); } | USEION NAME WRITE nrnionwlist valence { - $$ = new ast::NrnUseion($2, ast::ReadIonVarList(), $4, $5); + $$ = new ast::NrnUseion($2, ast::ReadIonVarVector(), $4, $5); } | USEION NAME READ nrnionrlist WRITE nrnionwlist valence { @@ -1949,7 +1949,7 @@ nrnuse : USEION NAME READ nrnionrlist valence nrnionrlist : NAME { - $$ = ast::ReadIonVarList(); + $$ = ast::ReadIonVarVector(); $$.push_back(std::shared_ptr(new ast::ReadIonVar($1))); } | nrnionrlist "," NAME @@ -1966,7 +1966,7 @@ nrnionrlist : NAME nrnionwlist : NAME { - $$ = ast::WriteIonVarList(); + $$ = ast::WriteIonVarVector(); $$.push_back(std::shared_ptr(new ast::WriteIonVar($1))); } | nrnionwlist "," NAME @@ -1983,7 +1983,7 @@ nrnionwlist : NAME nrnonspeclist : NAME { - $$ = ast::NonspeCurVarList(); + $$ = ast::NonspeCurVarVector(); auto var = new ast::NonspeCurVar($1); $$.push_back(std::shared_ptr(var)); } @@ -2002,7 +2002,7 @@ nrnonspeclist : NAME nrneclist : NAME { - $$ = ast::ElectrodeCurVarList(); + $$ = ast::ElectrodeCurVarVector(); auto var = new ast::ElectrodeCurVar($1); $$.push_back(std::shared_ptr(var)); } @@ -2021,7 +2021,7 @@ nrneclist : NAME nrnseclist : NAME { - $$ = ast::SectionVarList(); + $$ = ast::SectionVarVector(); auto var = new ast::SectionVar($1); $$.push_back(std::shared_ptr(var)); } @@ -2040,7 +2040,7 @@ nrnseclist : NAME nrnrangelist : NAME { - $$ = ast::RangeVarList(); + $$ = ast::RangeVarVector(); $$.push_back(std::shared_ptr(new ast::RangeVar($1))); } | nrnrangelist "," NAME @@ -2057,7 +2057,7 @@ nrnrangelist : NAME nrnglobalist : NAME { - $$ = ast::GlobalVarList(); + $$ = ast::GlobalVarVector(); $$.push_back(std::shared_ptr(new ast::GlobalVar($1))); } | nrnglobalist "," NAME @@ -2074,7 +2074,7 @@ nrnglobalist : NAME nrnptrlist : NAME { - $$ = ast::PointerVarList(); + $$ = ast::PointerVarVector(); auto var = new ast::PointerVar($1); $$.push_back(std::shared_ptr(var)); } @@ -2093,7 +2093,7 @@ nrnptrlist : NAME nrnbbptrlist : NAME { - $$ = ast::BbcorePointerVarList(); + $$ = ast::BbcorePointerVarVector(); auto var = new ast::BbcorePointerVar($1); $$.push_back(std::shared_ptr(var)); } @@ -2112,7 +2112,7 @@ nrnbbptrlist : NAME nrnextlist : NAME { - $$ = ast::ExternVarList(); + $$ = ast::ExternVarVector(); auto var = new ast::ExternVar($1); $$.push_back(std::shared_ptr(var)); } @@ -2129,14 +2129,14 @@ nrnextlist : NAME ; -opthsafelist : { $$ = ast::ThreadsafeVarList(); } +opthsafelist : { $$ = ast::ThreadsafeVarVector(); } | threadsafelist { $$ = $1; } ; threadsafelist : NAME { - $$ = ast::ThreadsafeVarList(); + $$ = ast::ThreadsafeVarVector(); auto var = new ast::ThreadsafeVar($1); $$.push_back(std::shared_ptr(var)); } From 0e2e301345dce4077574416e5a8b875c3d424897 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Tue, 21 Nov 2017 23:39:37 +0100 Subject: [PATCH 017/871] Refactor ast classes : use virtual and override properly Also remove unused nmodl_context.hpp file Change-Id: I9168f60fb16c62c143cf7c5126842fa52d49133a NMODL Repo SHA: BlueBrain/nmodl@4985ad129f715a1edf1fb26fe59851fb8e1ca503 --- src/nmodl/ast/ast.hpp | 1639 ++++++++++++++-------------- src/nmodl/ast/ast_utils.hpp | 3 +- src/nmodl/parser/nmodl_context.hpp | 66 -- 3 files changed, 823 insertions(+), 885 deletions(-) delete mode 100644 src/nmodl/parser/nmodl_context.hpp diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp index 9f4cbabfbd..5aff585413 100644 --- a/src/nmodl/ast/ast.hpp +++ b/src/nmodl/ast/ast.hpp @@ -271,11 +271,6 @@ namespace ast { using VerbatimVector = std::vector>; using CommentVector = std::vector>; using ProgramVector = std::vector>; - using NumberVector = std::vector>; - using IdentifierVector = std::vector>; - using BlockVector = std::vector>; - using ExpressionVector = std::vector>; - using StatementVector = std::vector>; using statement_ptr = Statement*; using expression_ptr = Expression*; using block_ptr = Block*; @@ -439,48 +434,53 @@ namespace ast { class Statement : public AST { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStatement(this); } virtual ~Statement() {} - virtual std::string getType() { return "Statement"; } - virtual Statement* clone() { return new Statement(*this); } + + virtual void visitChildren (Visitor* v) ; + virtual void accept(Visitor* v) { v->visitStatement(this); } + virtual std::string getType() { return "Statement"; } + virtual Statement* clone() { return new Statement(*this); } }; class Expression : public AST { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitExpression(this); } virtual ~Expression() {} - virtual std::string getType() { return "Expression"; } - virtual Expression* clone() { return new Expression(*this); } + + virtual void visitChildren (Visitor* v) ; + virtual void accept(Visitor* v) { v->visitExpression(this); } + virtual std::string getType() { return "Expression"; } + virtual Expression* clone() { return new Expression(*this); } }; class Block : public Expression { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBlock(this); } virtual ~Block() {} - virtual std::string getType() { return "Block"; } - virtual Block* clone() { return new Block(*this); } + + virtual void visitChildren (Visitor* v) ; + virtual void accept(Visitor* v) { v->visitBlock(this); } + virtual std::string getType() { return "Block"; } + virtual Block* clone() { return new Block(*this); } }; class Identifier : public Expression { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIdentifier(this); } virtual ~Identifier() {} - virtual std::string getType() { return "Identifier"; } - virtual Identifier* clone() { return new Identifier(*this); } + + virtual void visitChildren (Visitor* v) ; + virtual void accept(Visitor* v) { v->visitIdentifier(this); } + virtual std::string getType() { return "Identifier"; } + virtual Identifier* clone() { return new Identifier(*this); } virtual void setName(std::string name) { std::cout << "ERROR : setName() not implemented! "; abort(); } }; class Number : public Expression { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNumber(this); } virtual ~Number() {} - virtual std::string getType() { return "Number"; } - virtual Number* clone() { return new Number(*this); } + + virtual void visitChildren (Visitor* v) ; + virtual void accept(Visitor* v) { v->visitNumber(this); } + virtual std::string getType() { return "Number"; } + virtual Number* clone() { return new Number(*this); } virtual void negate() { std::cout << "ERROR : negate() not implemented! "; abort(); } }; @@ -492,16 +492,16 @@ namespace ast { /* constructors */ String(std::string value); String(const String& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitString(this); } virtual ~String() {} - virtual std::string getType() { return "String"; } - virtual String* clone() { return new String(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitString(this); } + std::string getType() override { return "String"; } + String* clone() override { return new String(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } std::string eval() { return value; } - virtual void set(std::string _value) { value = _value; } + void set(std::string _value) { value = _value; } }; class Integer : public Number { @@ -513,17 +513,17 @@ namespace ast { /* constructors */ Integer(int value, Name* macroname); Integer(const Integer& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitInteger(this); } virtual ~Integer() {} - virtual std::string getType() { return "Integer"; } - virtual Integer* clone() { return new Integer(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void negate() { value = -value; } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitInteger(this); } + std::string getType() override { return "Integer"; } + Integer* clone() override { return new Integer(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void negate() override { value = -value; } int eval() { return value; } - virtual void set(int _value) { value = _value; } + void set(int _value) { value = _value; } }; class Float : public Number { @@ -533,15 +533,15 @@ namespace ast { /* constructors */ Float(float value); Float(const Float& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFloat(this); } virtual ~Float() {} - virtual std::string getType() { return "Float"; } - virtual Float* clone() { return new Float(*this); } - void negate() { value = -value; } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFloat(this); } + std::string getType() override { return "Float"; } + Float* clone() override { return new Float(*this); } + void negate() override { value = -value; } float eval() { return value; } - virtual void set(float _value) { value = _value; } + void set(float _value) { value = _value; } }; class Double : public Number { @@ -552,17 +552,17 @@ namespace ast { /* constructors */ Double(double value); Double(const Double& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDouble(this); } virtual ~Double() {} - virtual std::string getType() { return "Double"; } - virtual Double* clone() { return new Double(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void negate() { value = -value; } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDouble(this); } + std::string getType() override { return "Double"; } + Double* clone() override { return new Double(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void negate() override { value = -value; } double eval() { return value; } - virtual void set(double _value) { value = _value; } + void set(double _value) { value = _value; } }; class Boolean : public Number { @@ -572,15 +572,15 @@ namespace ast { /* constructors */ Boolean(int value); Boolean(const Boolean& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBoolean(this); } virtual ~Boolean() {} - virtual std::string getType() { return "Boolean"; } - virtual Boolean* clone() { return new Boolean(*this); } - void negate() { value = !value; } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBoolean(this); } + std::string getType() override { return "Boolean"; } + Boolean* clone() override { return new Boolean(*this); } + void negate() override { value = !value; } bool eval() { return value; } - virtual void set(bool _value) { value = _value; } + void set(bool _value) { value = _value; } }; class Name : public Identifier { @@ -591,16 +591,16 @@ namespace ast { /* constructors */ Name(String* value); Name(const Name& obj); - - virtual std::string getName() { return value->eval(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitName(this); } virtual ~Name() {} - virtual std::string getType() { return "Name"; } - virtual Name* clone() { return new Name(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setName(std::string name) { value->set(name); } + + std::string getName() override { return value->eval(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitName(this); } + std::string getType() override { return "Name"; } + Name* clone() override { return new Name(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setName(std::string name) override { value->set(name); } }; class PrimeName : public Identifier { @@ -612,16 +612,16 @@ namespace ast { /* constructors */ PrimeName(String* value, Integer* order); PrimeName(const PrimeName& obj); - - virtual std::string getName() { return value->eval(); } - virtual int getOrder() { return order->eval(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPrimeName(this); } virtual ~PrimeName() {} - virtual std::string getType() { return "PrimeName"; } - virtual PrimeName* clone() { return new PrimeName(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + + std::string getName() override { return value->eval(); } + int getOrder() { return order->eval(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPrimeName(this); } + std::string getType() override { return "PrimeName"; } + PrimeName* clone() override { return new PrimeName(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } }; class VarName : public Identifier { @@ -632,14 +632,14 @@ namespace ast { /* constructors */ VarName(Identifier* name, Integer* at_index); VarName(const VarName& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitVarName(this); } virtual ~VarName() {} - virtual std::string getType() { return "VarName"; } - virtual VarName* clone() { return new VarName(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitVarName(this); } + std::string getType() override { return "VarName"; } + VarName* clone() override { return new VarName(*this); } }; class IndexedName : public Identifier { @@ -650,14 +650,14 @@ namespace ast { /* constructors */ IndexedName(Identifier* name, Expression* index); IndexedName(const IndexedName& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIndexedName(this); } virtual ~IndexedName() {} - virtual std::string getType() { return "IndexedName"; } - virtual IndexedName* clone() { return new IndexedName(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitIndexedName(this); } + std::string getType() override { return "IndexedName"; } + IndexedName* clone() override { return new IndexedName(*this); } }; class Argument : public Identifier { @@ -668,14 +668,14 @@ namespace ast { /* constructors */ Argument(Identifier* name, Unit* unit); Argument(const Argument& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitArgument(this); } virtual ~Argument() {} - virtual std::string getType() { return "Argument"; } - virtual Argument* clone() { return new Argument(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitArgument(this); } + std::string getType() override { return "Argument"; } + Argument* clone() override { return new Argument(*this); } }; class ReactVarName : public Identifier { @@ -686,14 +686,14 @@ namespace ast { /* constructors */ ReactVarName(Integer* value, VarName* name); ReactVarName(const ReactVarName& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReactVarName(this); } virtual ~ReactVarName() {} - virtual std::string getType() { return "ReactVarName"; } - virtual ReactVarName* clone() { return new ReactVarName(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitReactVarName(this); } + std::string getType() override { return "ReactVarName"; } + ReactVarName* clone() override { return new ReactVarName(*this); } }; class ReadIonVar : public Identifier { @@ -703,14 +703,14 @@ namespace ast { /* constructors */ ReadIonVar(Name* name); ReadIonVar(const ReadIonVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReadIonVar(this); } virtual ~ReadIonVar() {} - virtual std::string getType() { return "ReadIonVar"; } - virtual ReadIonVar* clone() { return new ReadIonVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitReadIonVar(this); } + std::string getType() override { return "ReadIonVar"; } + ReadIonVar* clone() override { return new ReadIonVar(*this); } }; class WriteIonVar : public Identifier { @@ -720,14 +720,14 @@ namespace ast { /* constructors */ WriteIonVar(Name* name); WriteIonVar(const WriteIonVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWriteIonVar(this); } virtual ~WriteIonVar() {} - virtual std::string getType() { return "WriteIonVar"; } - virtual WriteIonVar* clone() { return new WriteIonVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitWriteIonVar(this); } + std::string getType() override { return "WriteIonVar"; } + WriteIonVar* clone() override { return new WriteIonVar(*this); } }; class NonspeCurVar : public Identifier { @@ -737,14 +737,14 @@ namespace ast { /* constructors */ NonspeCurVar(Name* name); NonspeCurVar(const NonspeCurVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNonspeCurVar(this); } virtual ~NonspeCurVar() {} - virtual std::string getType() { return "NonspeCurVar"; } - virtual NonspeCurVar* clone() { return new NonspeCurVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNonspeCurVar(this); } + std::string getType() override { return "NonspeCurVar"; } + NonspeCurVar* clone() override { return new NonspeCurVar(*this); } }; class ElectrodeCurVar : public Identifier { @@ -754,14 +754,14 @@ namespace ast { /* constructors */ ElectrodeCurVar(Name* name); ElectrodeCurVar(const ElectrodeCurVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitElectrodeCurVar(this); } virtual ~ElectrodeCurVar() {} - virtual std::string getType() { return "ElectrodeCurVar"; } - virtual ElectrodeCurVar* clone() { return new ElectrodeCurVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitElectrodeCurVar(this); } + std::string getType() override { return "ElectrodeCurVar"; } + ElectrodeCurVar* clone() override { return new ElectrodeCurVar(*this); } }; class SectionVar : public Identifier { @@ -771,14 +771,14 @@ namespace ast { /* constructors */ SectionVar(Name* name); SectionVar(const SectionVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSectionVar(this); } virtual ~SectionVar() {} - virtual std::string getType() { return "SectionVar"; } - virtual SectionVar* clone() { return new SectionVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitSectionVar(this); } + std::string getType() override { return "SectionVar"; } + SectionVar* clone() override { return new SectionVar(*this); } }; class RangeVar : public Identifier { @@ -788,14 +788,14 @@ namespace ast { /* constructors */ RangeVar(Name* name); RangeVar(const RangeVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitRangeVar(this); } virtual ~RangeVar() {} - virtual std::string getType() { return "RangeVar"; } - virtual RangeVar* clone() { return new RangeVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitRangeVar(this); } + std::string getType() override { return "RangeVar"; } + RangeVar* clone() override { return new RangeVar(*this); } }; class GlobalVar : public Identifier { @@ -805,14 +805,14 @@ namespace ast { /* constructors */ GlobalVar(Name* name); GlobalVar(const GlobalVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitGlobalVar(this); } virtual ~GlobalVar() {} - virtual std::string getType() { return "GlobalVar"; } - virtual GlobalVar* clone() { return new GlobalVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitGlobalVar(this); } + std::string getType() override { return "GlobalVar"; } + GlobalVar* clone() override { return new GlobalVar(*this); } }; class PointerVar : public Identifier { @@ -822,14 +822,14 @@ namespace ast { /* constructors */ PointerVar(Name* name); PointerVar(const PointerVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPointerVar(this); } virtual ~PointerVar() {} - virtual std::string getType() { return "PointerVar"; } - virtual PointerVar* clone() { return new PointerVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPointerVar(this); } + std::string getType() override { return "PointerVar"; } + PointerVar* clone() override { return new PointerVar(*this); } }; class BbcorePointerVar : public Identifier { @@ -839,14 +839,14 @@ namespace ast { /* constructors */ BbcorePointerVar(Name* name); BbcorePointerVar(const BbcorePointerVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBbcorePointerVar(this); } virtual ~BbcorePointerVar() {} - virtual std::string getType() { return "BbcorePointerVar"; } - virtual BbcorePointerVar* clone() { return new BbcorePointerVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBbcorePointerVar(this); } + std::string getType() override { return "BbcorePointerVar"; } + BbcorePointerVar* clone() override { return new BbcorePointerVar(*this); } }; class ExternVar : public Identifier { @@ -856,14 +856,14 @@ namespace ast { /* constructors */ ExternVar(Name* name); ExternVar(const ExternVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitExternVar(this); } virtual ~ExternVar() {} - virtual std::string getType() { return "ExternVar"; } - virtual ExternVar* clone() { return new ExternVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitExternVar(this); } + std::string getType() override { return "ExternVar"; } + ExternVar* clone() override { return new ExternVar(*this); } }; class ThreadsafeVar : public Identifier { @@ -873,14 +873,14 @@ namespace ast { /* constructors */ ThreadsafeVar(Name* name); ThreadsafeVar(const ThreadsafeVar& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitThreadsafeVar(this); } virtual ~ThreadsafeVar() {} - virtual std::string getType() { return "ThreadsafeVar"; } - virtual ThreadsafeVar* clone() { return new ThreadsafeVar(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitThreadsafeVar(this); } + std::string getType() override { return "ThreadsafeVar"; } + ThreadsafeVar* clone() override { return new ThreadsafeVar(*this); } }; class ParamBlock : public Block { @@ -892,15 +892,15 @@ namespace ast { /* constructors */ ParamBlock(ParamAssignVector statements); ParamBlock(const ParamBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitParamBlock(this); } virtual ~ParamBlock() {} - virtual std::string getType() { return "ParamBlock"; } - virtual ParamBlock* clone() { return new ParamBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitParamBlock(this); } + std::string getType() override { return "ParamBlock"; } + ParamBlock* clone() override { return new ParamBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class StepBlock : public Block { @@ -912,15 +912,15 @@ namespace ast { /* constructors */ StepBlock(SteppedVector statements); StepBlock(const StepBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStepBlock(this); } virtual ~StepBlock() {} - virtual std::string getType() { return "StepBlock"; } - virtual StepBlock* clone() { return new StepBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitStepBlock(this); } + std::string getType() override { return "StepBlock"; } + StepBlock* clone() override { return new StepBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class IndependentBlock : public Block { @@ -932,15 +932,15 @@ namespace ast { /* constructors */ IndependentBlock(IndependentDefVector definitions); IndependentBlock(const IndependentBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIndependentBlock(this); } virtual ~IndependentBlock() {} - virtual std::string getType() { return "IndependentBlock"; } - virtual IndependentBlock* clone() { return new IndependentBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitIndependentBlock(this); } + std::string getType() override { return "IndependentBlock"; } + IndependentBlock* clone() override { return new IndependentBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class DependentBlock : public Block { @@ -952,15 +952,15 @@ namespace ast { /* constructors */ DependentBlock(DependentDefVector definitions); DependentBlock(const DependentBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDependentBlock(this); } virtual ~DependentBlock() {} - virtual std::string getType() { return "DependentBlock"; } - virtual DependentBlock* clone() { return new DependentBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDependentBlock(this); } + std::string getType() override { return "DependentBlock"; } + DependentBlock* clone() override { return new DependentBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class StateBlock : public Block { @@ -972,15 +972,15 @@ namespace ast { /* constructors */ StateBlock(DependentDefVector definitions); StateBlock(const StateBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStateBlock(this); } virtual ~StateBlock() {} - virtual std::string getType() { return "StateBlock"; } - virtual StateBlock* clone() { return new StateBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitStateBlock(this); } + std::string getType() override { return "StateBlock"; } + StateBlock* clone() override { return new StateBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class PlotBlock : public Block { @@ -992,15 +992,15 @@ namespace ast { /* constructors */ PlotBlock(PlotDeclaration* plot); PlotBlock(const PlotBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPlotBlock(this); } virtual ~PlotBlock() {} - virtual std::string getType() { return "PlotBlock"; } - virtual PlotBlock* clone() { return new PlotBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPlotBlock(this); } + std::string getType() override { return "PlotBlock"; } + PlotBlock* clone() override { return new PlotBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class InitialBlock : public Block { @@ -1012,15 +1012,15 @@ namespace ast { /* constructors */ InitialBlock(StatementBlock* statementblock); InitialBlock(const InitialBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitInitialBlock(this); } virtual ~InitialBlock() {} - virtual std::string getType() { return "InitialBlock"; } - virtual InitialBlock* clone() { return new InitialBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitInitialBlock(this); } + std::string getType() override { return "InitialBlock"; } + InitialBlock* clone() override { return new InitialBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class ConstructorBlock : public Block { @@ -1032,15 +1032,15 @@ namespace ast { /* constructors */ ConstructorBlock(StatementBlock* statementblock); ConstructorBlock(const ConstructorBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConstructorBlock(this); } virtual ~ConstructorBlock() {} - virtual std::string getType() { return "ConstructorBlock"; } - virtual ConstructorBlock* clone() { return new ConstructorBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitConstructorBlock(this); } + std::string getType() override { return "ConstructorBlock"; } + ConstructorBlock* clone() override { return new ConstructorBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class DestructorBlock : public Block { @@ -1052,15 +1052,15 @@ namespace ast { /* constructors */ DestructorBlock(StatementBlock* statementblock); DestructorBlock(const DestructorBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDestructorBlock(this); } virtual ~DestructorBlock() {} - virtual std::string getType() { return "DestructorBlock"; } - virtual DestructorBlock* clone() { return new DestructorBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDestructorBlock(this); } + std::string getType() override { return "DestructorBlock"; } + DestructorBlock* clone() override { return new DestructorBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class StatementBlock : public Block { @@ -1073,17 +1073,17 @@ namespace ast { /* constructors */ StatementBlock(StatementVector statements); StatementBlock(const StatementBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStatementBlock(this); } virtual ~StatementBlock() {} - virtual std::string getType() { return "StatementBlock"; } - virtual StatementBlock* clone() { return new StatementBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitStatementBlock(this); } + std::string getType() override { return "StatementBlock"; } + StatementBlock* clone() override { return new StatementBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class DerivativeBlock : public Block { @@ -1097,17 +1097,17 @@ namespace ast { /* constructors */ DerivativeBlock(Name* name, StatementBlock* statementblock); DerivativeBlock(const DerivativeBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDerivativeBlock(this); } virtual ~DerivativeBlock() {} - virtual std::string getType() { return "DerivativeBlock"; } - virtual DerivativeBlock* clone() { return new DerivativeBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDerivativeBlock(this); } + std::string getType() override { return "DerivativeBlock"; } + DerivativeBlock* clone() override { return new DerivativeBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class LinearBlock : public Block { @@ -1122,17 +1122,17 @@ namespace ast { /* constructors */ LinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); LinearBlock(const LinearBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLinearBlock(this); } virtual ~LinearBlock() {} - virtual std::string getType() { return "LinearBlock"; } - virtual LinearBlock* clone() { return new LinearBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLinearBlock(this); } + std::string getType() override { return "LinearBlock"; } + LinearBlock* clone() override { return new LinearBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class NonLinearBlock : public Block { @@ -1147,17 +1147,17 @@ namespace ast { /* constructors */ NonLinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); NonLinearBlock(const NonLinearBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNonLinearBlock(this); } virtual ~NonLinearBlock() {} - virtual std::string getType() { return "NonLinearBlock"; } - virtual NonLinearBlock* clone() { return new NonLinearBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNonLinearBlock(this); } + std::string getType() override { return "NonLinearBlock"; } + NonLinearBlock* clone() override { return new NonLinearBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class DiscreteBlock : public Block { @@ -1171,17 +1171,17 @@ namespace ast { /* constructors */ DiscreteBlock(Name* name, StatementBlock* statementblock); DiscreteBlock(const DiscreteBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDiscreteBlock(this); } virtual ~DiscreteBlock() {} - virtual std::string getType() { return "DiscreteBlock"; } - virtual DiscreteBlock* clone() { return new DiscreteBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDiscreteBlock(this); } + std::string getType() override { return "DiscreteBlock"; } + DiscreteBlock* clone() override { return new DiscreteBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class PartialBlock : public Block { @@ -1195,17 +1195,17 @@ namespace ast { /* constructors */ PartialBlock(Name* name, StatementBlock* statementblock); PartialBlock(const PartialBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPartialBlock(this); } virtual ~PartialBlock() {} - virtual std::string getType() { return "PartialBlock"; } - virtual PartialBlock* clone() { return new PartialBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPartialBlock(this); } + std::string getType() override { return "PartialBlock"; } + PartialBlock* clone() override { return new PartialBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class FunctionTableBlock : public Block { @@ -1220,17 +1220,17 @@ namespace ast { /* constructors */ FunctionTableBlock(Name* name, ArgumentVector arguments, Unit* unit); FunctionTableBlock(const FunctionTableBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFunctionTableBlock(this); } virtual ~FunctionTableBlock() {} - virtual std::string getType() { return "FunctionTableBlock"; } - virtual FunctionTableBlock* clone() { return new FunctionTableBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFunctionTableBlock(this); } + std::string getType() override { return "FunctionTableBlock"; } + FunctionTableBlock* clone() override { return new FunctionTableBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class FunctionBlock : public Block { @@ -1246,17 +1246,17 @@ namespace ast { /* constructors */ FunctionBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock); FunctionBlock(const FunctionBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFunctionBlock(this); } virtual ~FunctionBlock() {} - virtual std::string getType() { return "FunctionBlock"; } - virtual FunctionBlock* clone() { return new FunctionBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFunctionBlock(this); } + std::string getType() override { return "FunctionBlock"; } + FunctionBlock* clone() override { return new FunctionBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class ProcedureBlock : public Block { @@ -1272,17 +1272,17 @@ namespace ast { /* constructors */ ProcedureBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock); ProcedureBlock(const ProcedureBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitProcedureBlock(this); } virtual ~ProcedureBlock() {} - virtual std::string getType() { return "ProcedureBlock"; } - virtual ProcedureBlock* clone() { return new ProcedureBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitProcedureBlock(this); } + std::string getType() override { return "ProcedureBlock"; } + ProcedureBlock* clone() override { return new ProcedureBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class NetReceiveBlock : public Block { @@ -1295,15 +1295,15 @@ namespace ast { /* constructors */ NetReceiveBlock(ArgumentVector arguments, StatementBlock* statementblock); NetReceiveBlock(const NetReceiveBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNetReceiveBlock(this); } virtual ~NetReceiveBlock() {} - virtual std::string getType() { return "NetReceiveBlock"; } - virtual NetReceiveBlock* clone() { return new NetReceiveBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNetReceiveBlock(this); } + std::string getType() override { return "NetReceiveBlock"; } + NetReceiveBlock* clone() override { return new NetReceiveBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class SolveBlock : public Block { @@ -1317,15 +1317,15 @@ namespace ast { /* constructors */ SolveBlock(Name* name, Name* method, StatementBlock* ifsolerr); SolveBlock(const SolveBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSolveBlock(this); } virtual ~SolveBlock() {} - virtual std::string getType() { return "SolveBlock"; } - virtual SolveBlock* clone() { return new SolveBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitSolveBlock(this); } + std::string getType() override { return "SolveBlock"; } + SolveBlock* clone() override { return new SolveBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class BreakpointBlock : public Block { @@ -1337,15 +1337,15 @@ namespace ast { /* constructors */ BreakpointBlock(StatementBlock* statementblock); BreakpointBlock(const BreakpointBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBreakpointBlock(this); } virtual ~BreakpointBlock() {} - virtual std::string getType() { return "BreakpointBlock"; } - virtual BreakpointBlock* clone() { return new BreakpointBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBreakpointBlock(this); } + std::string getType() override { return "BreakpointBlock"; } + BreakpointBlock* clone() override { return new BreakpointBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class TerminalBlock : public Block { @@ -1357,15 +1357,15 @@ namespace ast { /* constructors */ TerminalBlock(StatementBlock* statementblock); TerminalBlock(const TerminalBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitTerminalBlock(this); } virtual ~TerminalBlock() {} - virtual std::string getType() { return "TerminalBlock"; } - virtual TerminalBlock* clone() { return new TerminalBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitTerminalBlock(this); } + std::string getType() override { return "TerminalBlock"; } + TerminalBlock* clone() override { return new TerminalBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class BeforeBlock : public Block { @@ -1377,15 +1377,15 @@ namespace ast { /* constructors */ BeforeBlock(BABlock* block); BeforeBlock(const BeforeBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBeforeBlock(this); } virtual ~BeforeBlock() {} - virtual std::string getType() { return "BeforeBlock"; } - virtual BeforeBlock* clone() { return new BeforeBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBeforeBlock(this); } + std::string getType() override { return "BeforeBlock"; } + BeforeBlock* clone() override { return new BeforeBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class AfterBlock : public Block { @@ -1397,15 +1397,15 @@ namespace ast { /* constructors */ AfterBlock(BABlock* block); AfterBlock(const AfterBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitAfterBlock(this); } virtual ~AfterBlock() {} - virtual std::string getType() { return "AfterBlock"; } - virtual AfterBlock* clone() { return new AfterBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitAfterBlock(this); } + std::string getType() override { return "AfterBlock"; } + AfterBlock* clone() override { return new AfterBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class BABlock : public Block { @@ -1418,15 +1418,15 @@ namespace ast { /* constructors */ BABlock(BABlockType* type, StatementBlock* statementblock); BABlock(const BABlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBABlock(this); } virtual ~BABlock() {} - virtual std::string getType() { return "BABlock"; } - virtual BABlock* clone() { return new BABlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBABlock(this); } + std::string getType() override { return "BABlock"; } + BABlock* clone() override { return new BABlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class ForNetcon : public Block { @@ -1439,15 +1439,15 @@ namespace ast { /* constructors */ ForNetcon(ArgumentVector arguments, StatementBlock* statementblock); ForNetcon(const ForNetcon& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitForNetcon(this); } virtual ~ForNetcon() {} - virtual std::string getType() { return "ForNetcon"; } - virtual ForNetcon* clone() { return new ForNetcon(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitForNetcon(this); } + std::string getType() override { return "ForNetcon"; } + ForNetcon* clone() override { return new ForNetcon(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class KineticBlock : public Block { @@ -1462,17 +1462,17 @@ namespace ast { /* constructors */ KineticBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); KineticBlock(const KineticBlock& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitKineticBlock(this); } virtual ~KineticBlock() {} - virtual std::string getType() { return "KineticBlock"; } - virtual KineticBlock* clone() { return new KineticBlock(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitKineticBlock(this); } + std::string getType() override { return "KineticBlock"; } + KineticBlock* clone() override { return new KineticBlock(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class MatchBlock : public Block { @@ -1484,15 +1484,15 @@ namespace ast { /* constructors */ MatchBlock(MatchVector matchs); MatchBlock(const MatchBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMatchBlock(this); } virtual ~MatchBlock() {} - virtual std::string getType() { return "MatchBlock"; } - virtual MatchBlock* clone() { return new MatchBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitMatchBlock(this); } + std::string getType() override { return "MatchBlock"; } + MatchBlock* clone() override { return new MatchBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class UnitBlock : public Block { @@ -1504,15 +1504,15 @@ namespace ast { /* constructors */ UnitBlock(ExpressionVector definitions); UnitBlock(const UnitBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitBlock(this); } virtual ~UnitBlock() {} - virtual std::string getType() { return "UnitBlock"; } - virtual UnitBlock* clone() { return new UnitBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitUnitBlock(this); } + std::string getType() override { return "UnitBlock"; } + UnitBlock* clone() override { return new UnitBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class ConstantBlock : public Block { @@ -1524,15 +1524,15 @@ namespace ast { /* constructors */ ConstantBlock(ConstantStatementVector statements); ConstantBlock(const ConstantBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConstantBlock(this); } virtual ~ConstantBlock() {} - virtual std::string getType() { return "ConstantBlock"; } - virtual ConstantBlock* clone() { return new ConstantBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitConstantBlock(this); } + std::string getType() override { return "ConstantBlock"; } + ConstantBlock* clone() override { return new ConstantBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class NeuronBlock : public Block { @@ -1544,15 +1544,15 @@ namespace ast { /* constructors */ NeuronBlock(StatementBlock* statementblock); NeuronBlock(const NeuronBlock& obj); - - std::string getName() { return getType(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNeuronBlock(this); } virtual ~NeuronBlock() {} - virtual std::string getType() { return "NeuronBlock"; } - virtual NeuronBlock* clone() { return new NeuronBlock(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + + std::string getName() override { return getType(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNeuronBlock(this); } + std::string getType() override { return "NeuronBlock"; } + NeuronBlock* clone() override { return new NeuronBlock(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; class Unit : public Expression { @@ -1562,14 +1562,14 @@ namespace ast { /* constructors */ Unit(String* name); Unit(const Unit& obj); - - virtual std::string getName() { return name->eval(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnit(this); } virtual ~Unit() {} - virtual std::string getType() { return "Unit"; } - virtual Unit* clone() { return new Unit(*this); } + + std::string getName() override { return name->eval(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitUnit(this); } + std::string getType() override { return "Unit"; } + Unit* clone() override { return new Unit(*this); } }; class DoubleUnit : public Expression { @@ -1580,12 +1580,12 @@ namespace ast { /* constructors */ DoubleUnit(Double* values, Unit* unit); DoubleUnit(const DoubleUnit& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDoubleUnit(this); } virtual ~DoubleUnit() {} - virtual std::string getType() { return "DoubleUnit"; } - virtual DoubleUnit* clone() { return new DoubleUnit(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDoubleUnit(this); } + std::string getType() override { return "DoubleUnit"; } + DoubleUnit* clone() override { return new DoubleUnit(*this); } }; class LocalVariable : public Expression { @@ -1595,14 +1595,14 @@ namespace ast { /* constructors */ LocalVariable(Identifier* name); LocalVariable(const LocalVariable& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLocalVariable(this); } virtual ~LocalVariable() {} - virtual std::string getType() { return "LocalVariable"; } - virtual LocalVariable* clone() { return new LocalVariable(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLocalVariable(this); } + std::string getType() override { return "LocalVariable"; } + LocalVariable* clone() override { return new LocalVariable(*this); } }; class Limits : public Expression { @@ -1613,12 +1613,12 @@ namespace ast { /* constructors */ Limits(Double* min, Double* max); Limits(const Limits& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLimits(this); } virtual ~Limits() {} - virtual std::string getType() { return "Limits"; } - virtual Limits* clone() { return new Limits(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLimits(this); } + std::string getType() override { return "Limits"; } + Limits* clone() override { return new Limits(*this); } }; class NumberRange : public Expression { @@ -1629,12 +1629,12 @@ namespace ast { /* constructors */ NumberRange(Number* min, Number* max); NumberRange(const NumberRange& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNumberRange(this); } virtual ~NumberRange() {} - virtual std::string getType() { return "NumberRange"; } - virtual NumberRange* clone() { return new NumberRange(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNumberRange(this); } + std::string getType() override { return "NumberRange"; } + NumberRange* clone() override { return new NumberRange(*this); } }; class PlotVariable : public Expression { @@ -1645,12 +1645,12 @@ namespace ast { /* constructors */ PlotVariable(Identifier* name, Integer* index); PlotVariable(const PlotVariable& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPlotVariable(this); } virtual ~PlotVariable() {} - virtual std::string getType() { return "PlotVariable"; } - virtual PlotVariable* clone() { return new PlotVariable(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPlotVariable(this); } + std::string getType() override { return "PlotVariable"; } + PlotVariable* clone() override { return new PlotVariable(*this); } }; class BinaryOperator : public Expression { @@ -1660,14 +1660,14 @@ namespace ast { /* constructors */ BinaryOperator(BinaryOp value); BinaryOperator(const BinaryOperator& obj); + virtual ~BinaryOperator() {} BinaryOperator() {} - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBinaryOperator(this); } - virtual ~BinaryOperator() {} - virtual std::string getType() { return "BinaryOperator"; } - virtual BinaryOperator* clone() { return new BinaryOperator(*this); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBinaryOperator(this); } + std::string getType() override { return "BinaryOperator"; } + BinaryOperator* clone() override { return new BinaryOperator(*this); } std::string eval() { return BinaryOpNames[value]; } }; @@ -1678,14 +1678,14 @@ namespace ast { /* constructors */ UnaryOperator(UnaryOp value); UnaryOperator(const UnaryOperator& obj); + virtual ~UnaryOperator() {} UnaryOperator() {} - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnaryOperator(this); } - virtual ~UnaryOperator() {} - virtual std::string getType() { return "UnaryOperator"; } - virtual UnaryOperator* clone() { return new UnaryOperator(*this); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitUnaryOperator(this); } + std::string getType() override { return "UnaryOperator"; } + UnaryOperator* clone() override { return new UnaryOperator(*this); } std::string eval() { return UnaryOpNames[value]; } }; @@ -1696,14 +1696,14 @@ namespace ast { /* constructors */ ReactionOperator(ReactionOp value); ReactionOperator(const ReactionOperator& obj); + virtual ~ReactionOperator() {} ReactionOperator() {} - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReactionOperator(this); } - virtual ~ReactionOperator() {} - virtual std::string getType() { return "ReactionOperator"; } - virtual ReactionOperator* clone() { return new ReactionOperator(*this); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitReactionOperator(this); } + std::string getType() override { return "ReactionOperator"; } + ReactionOperator* clone() override { return new ReactionOperator(*this); } std::string eval() { return ReactionOpNames[value]; } }; @@ -1716,12 +1716,12 @@ namespace ast { /* constructors */ BinaryExpression(Expression* lhs, BinaryOperator op, Expression* rhs); BinaryExpression(const BinaryExpression& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBinaryExpression(this); } virtual ~BinaryExpression() {} - virtual std::string getType() { return "BinaryExpression"; } - virtual BinaryExpression* clone() { return new BinaryExpression(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBinaryExpression(this); } + std::string getType() override { return "BinaryExpression"; } + BinaryExpression* clone() override { return new BinaryExpression(*this); } }; class UnaryExpression : public Expression { @@ -1732,12 +1732,12 @@ namespace ast { /* constructors */ UnaryExpression(UnaryOperator op, Expression* expression); UnaryExpression(const UnaryExpression& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnaryExpression(this); } virtual ~UnaryExpression() {} - virtual std::string getType() { return "UnaryExpression"; } - virtual UnaryExpression* clone() { return new UnaryExpression(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitUnaryExpression(this); } + std::string getType() override { return "UnaryExpression"; } + UnaryExpression* clone() override { return new UnaryExpression(*this); } }; class NonLinEuation : public Expression { @@ -1748,12 +1748,12 @@ namespace ast { /* constructors */ NonLinEuation(Expression* lhs, Expression* rhs); NonLinEuation(const NonLinEuation& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNonLinEuation(this); } virtual ~NonLinEuation() {} - virtual std::string getType() { return "NonLinEuation"; } - virtual NonLinEuation* clone() { return new NonLinEuation(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNonLinEuation(this); } + std::string getType() override { return "NonLinEuation"; } + NonLinEuation* clone() override { return new NonLinEuation(*this); } }; class LinEquation : public Expression { @@ -1764,12 +1764,12 @@ namespace ast { /* constructors */ LinEquation(Expression* leftlinexpr, Expression* linexpr); LinEquation(const LinEquation& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLinEquation(this); } virtual ~LinEquation() {} - virtual std::string getType() { return "LinEquation"; } - virtual LinEquation* clone() { return new LinEquation(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLinEquation(this); } + std::string getType() override { return "LinEquation"; } + LinEquation* clone() override { return new LinEquation(*this); } }; class FunctionCall : public Expression { @@ -1780,12 +1780,12 @@ namespace ast { /* constructors */ FunctionCall(Name* name, ExpressionVector arguments); FunctionCall(const FunctionCall& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFunctionCall(this); } virtual ~FunctionCall() {} - virtual std::string getType() { return "FunctionCall"; } - virtual FunctionCall* clone() { return new FunctionCall(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFunctionCall(this); } + std::string getType() override { return "FunctionCall"; } + FunctionCall* clone() override { return new FunctionCall(*this); } }; class FirstLastTypeIndex : public Expression { @@ -1795,12 +1795,12 @@ namespace ast { /* constructors */ FirstLastTypeIndex(FirstLastType value); FirstLastTypeIndex(const FirstLastTypeIndex& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFirstLastTypeIndex(this); } virtual ~FirstLastTypeIndex() {} - virtual std::string getType() { return "FirstLastTypeIndex"; } - virtual FirstLastTypeIndex* clone() { return new FirstLastTypeIndex(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFirstLastTypeIndex(this); } + std::string getType() override { return "FirstLastTypeIndex"; } + FirstLastTypeIndex* clone() override { return new FirstLastTypeIndex(*this); } std::string eval() { return FirstLastTypeNames[value]; } }; @@ -1812,12 +1812,12 @@ namespace ast { /* constructors */ Watch(Expression* expression, Expression* value); Watch(const Watch& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWatch(this); } virtual ~Watch() {} - virtual std::string getType() { return "Watch"; } - virtual Watch* clone() { return new Watch(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitWatch(this); } + std::string getType() override { return "Watch"; } + Watch* clone() override { return new Watch(*this); } }; class QueueExpressionType : public Expression { @@ -1827,12 +1827,12 @@ namespace ast { /* constructors */ QueueExpressionType(QueueType value); QueueExpressionType(const QueueExpressionType& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitQueueExpressionType(this); } virtual ~QueueExpressionType() {} - virtual std::string getType() { return "QueueExpressionType"; } - virtual QueueExpressionType* clone() { return new QueueExpressionType(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitQueueExpressionType(this); } + std::string getType() override { return "QueueExpressionType"; } + QueueExpressionType* clone() override { return new QueueExpressionType(*this); } std::string eval() { return QueueTypeNames[value]; } }; @@ -1844,12 +1844,12 @@ namespace ast { /* constructors */ Match(Identifier* name, Expression* expression); Match(const Match& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMatch(this); } virtual ~Match() {} - virtual std::string getType() { return "Match"; } - virtual Match* clone() { return new Match(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitMatch(this); } + std::string getType() override { return "Match"; } + Match* clone() override { return new Match(*this); } }; class BABlockType : public Expression { @@ -1859,12 +1859,12 @@ namespace ast { /* constructors */ BABlockType(BAType value); BABlockType(const BABlockType& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitBABlockType(this); } virtual ~BABlockType() {} - virtual std::string getType() { return "BABlockType"; } - virtual BABlockType* clone() { return new BABlockType(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitBABlockType(this); } + std::string getType() override { return "BABlockType"; } + BABlockType* clone() override { return new BABlockType(*this); } std::string eval() { return BATypeNames[value]; } }; @@ -1876,14 +1876,14 @@ namespace ast { /* constructors */ UnitDef(Unit* unit1, Unit* unit2); UnitDef(const UnitDef& obj); - - virtual std::string getName() { return unit1->getName(); } - virtual ModToken* getToken() { return unit1->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitDef(this); } virtual ~UnitDef() {} - virtual std::string getType() { return "UnitDef"; } - virtual UnitDef* clone() { return new UnitDef(*this); } + + std::string getName() override { return unit1->getName(); } + ModToken* getToken() override { return unit1->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitUnitDef(this); } + std::string getType() override { return "UnitDef"; } + UnitDef* clone() override { return new UnitDef(*this); } }; class FactorDef : public Expression { @@ -1898,15 +1898,15 @@ namespace ast { /* constructors */ FactorDef(Name* name, Double* value, Unit* unit1, Boolean* gt, Unit* unit2); FactorDef(const FactorDef& obj); - - virtual std::string getName() { return name->getName(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFactorDef(this); } virtual ~FactorDef() {} - virtual std::string getType() { return "FactorDef"; } - virtual FactorDef* clone() { return new FactorDef(*this); } - virtual ModToken* getToken() { return token.get(); } - virtual void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + + std::string getName() override { return name->getName(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFactorDef(this); } + std::string getType() override { return "FactorDef"; } + FactorDef* clone() override { return new FactorDef(*this); } + ModToken* getToken() override { return token.get(); } + void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } }; class Valence : public Expression { @@ -1917,12 +1917,12 @@ namespace ast { /* constructors */ Valence(Name* type, Double* value); Valence(const Valence& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitValence(this); } virtual ~Valence() {} - virtual std::string getType() { return "Valence"; } - virtual Valence* clone() { return new Valence(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitValence(this); } + std::string getType() override { return "Valence"; } + Valence* clone() override { return new Valence(*this); } }; class UnitState : public Statement { @@ -1932,12 +1932,12 @@ namespace ast { /* constructors */ UnitState(UnitStateType value); UnitState(const UnitState& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitUnitState(this); } virtual ~UnitState() {} - virtual std::string getType() { return "UnitState"; } - virtual UnitState* clone() { return new UnitState(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitUnitState(this); } + std::string getType() override { return "UnitState"; } + UnitState* clone() override { return new UnitState(*this); } std::string eval() { return UnitStateTypeNames[value]; } }; @@ -1948,12 +1948,12 @@ namespace ast { /* constructors */ LocalListStatement(LocalVariableVector variables); LocalListStatement(const LocalListStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLocalListStatement(this); } virtual ~LocalListStatement() {} - virtual std::string getType() { return "LocalListStatement"; } - virtual LocalListStatement* clone() { return new LocalListStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLocalListStatement(this); } + std::string getType() override { return "LocalListStatement"; } + LocalListStatement* clone() override { return new LocalListStatement(*this); } }; class Model : public Statement { @@ -1963,12 +1963,12 @@ namespace ast { /* constructors */ Model(String* title); Model(const Model& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitModel(this); } virtual ~Model() {} - virtual std::string getType() { return "Model"; } - virtual Model* clone() { return new Model(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitModel(this); } + std::string getType() override { return "Model"; } + Model* clone() override { return new Model(*this); } }; class Define : public Statement { @@ -1979,12 +1979,12 @@ namespace ast { /* constructors */ Define(Name* name, Integer* value); Define(const Define& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDefine(this); } virtual ~Define() {} - virtual std::string getType() { return "Define"; } - virtual Define* clone() { return new Define(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDefine(this); } + std::string getType() override { return "Define"; } + Define* clone() override { return new Define(*this); } }; class Include : public Statement { @@ -1994,12 +1994,12 @@ namespace ast { /* constructors */ Include(String* filename); Include(const Include& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitInclude(this); } virtual ~Include() {} - virtual std::string getType() { return "Include"; } - virtual Include* clone() { return new Include(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitInclude(this); } + std::string getType() override { return "Include"; } + Include* clone() override { return new Include(*this); } }; class ParamAssign : public Statement { @@ -2012,14 +2012,14 @@ namespace ast { /* constructors */ ParamAssign(Identifier* name, Number* value, Unit* unit, Limits* limit); ParamAssign(const ParamAssign& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitParamAssign(this); } virtual ~ParamAssign() {} - virtual std::string getType() { return "ParamAssign"; } - virtual ParamAssign* clone() { return new ParamAssign(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitParamAssign(this); } + std::string getType() override { return "ParamAssign"; } + ParamAssign* clone() override { return new ParamAssign(*this); } }; class Stepped : public Statement { @@ -2031,12 +2031,12 @@ namespace ast { /* constructors */ Stepped(Name* name, NumberVector values, Unit* unit); Stepped(const Stepped& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitStepped(this); } virtual ~Stepped() {} - virtual std::string getType() { return "Stepped"; } - virtual Stepped* clone() { return new Stepped(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitStepped(this); } + std::string getType() override { return "Stepped"; } + Stepped* clone() override { return new Stepped(*this); } }; class IndependentDef : public Statement { @@ -2052,12 +2052,12 @@ namespace ast { /* constructors */ IndependentDef(Boolean* sweep, Name* name, Number* from, Number* to, Integer* with, Number* opstart, Unit* unit); IndependentDef(const IndependentDef& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIndependentDef(this); } virtual ~IndependentDef() {} - virtual std::string getType() { return "IndependentDef"; } - virtual IndependentDef* clone() { return new IndependentDef(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitIndependentDef(this); } + std::string getType() override { return "IndependentDef"; } + IndependentDef* clone() override { return new IndependentDef(*this); } }; class DependentDef : public Statement { @@ -2073,14 +2073,14 @@ namespace ast { /* constructors */ DependentDef(Identifier* name, Integer* index, Number* from, Number* to, Number* opstart, Unit* unit, Double* abstol); DependentDef(const DependentDef& obj); - - virtual std::string getName() { return name->getName(); } - virtual ModToken* getToken() { return name->getToken(); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitDependentDef(this); } virtual ~DependentDef() {} - virtual std::string getType() { return "DependentDef"; } - virtual DependentDef* clone() { return new DependentDef(*this); } + + std::string getName() override { return name->getName(); } + ModToken* getToken() override { return name->getToken(); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitDependentDef(this); } + std::string getType() override { return "DependentDef"; } + DependentDef* clone() override { return new DependentDef(*this); } }; class PlotDeclaration : public Statement { @@ -2091,12 +2091,12 @@ namespace ast { /* constructors */ PlotDeclaration(PlotVariableVector pvlist, PlotVariable* name); PlotDeclaration(const PlotDeclaration& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPlotDeclaration(this); } virtual ~PlotDeclaration() {} - virtual std::string getType() { return "PlotDeclaration"; } - virtual PlotDeclaration* clone() { return new PlotDeclaration(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPlotDeclaration(this); } + std::string getType() override { return "PlotDeclaration"; } + PlotDeclaration* clone() override { return new PlotDeclaration(*this); } }; class ConductanceHint : public Statement { @@ -2107,12 +2107,12 @@ namespace ast { /* constructors */ ConductanceHint(Name* conductance, Name* ion); ConductanceHint(const ConductanceHint& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConductanceHint(this); } virtual ~ConductanceHint() {} - virtual std::string getType() { return "ConductanceHint"; } - virtual ConductanceHint* clone() { return new ConductanceHint(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitConductanceHint(this); } + std::string getType() override { return "ConductanceHint"; } + ConductanceHint* clone() override { return new ConductanceHint(*this); } }; class ExpressionStatement : public Statement { @@ -2122,12 +2122,12 @@ namespace ast { /* constructors */ ExpressionStatement(Expression* expression); ExpressionStatement(const ExpressionStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitExpressionStatement(this); } virtual ~ExpressionStatement() {} - virtual std::string getType() { return "ExpressionStatement"; } - virtual ExpressionStatement* clone() { return new ExpressionStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitExpressionStatement(this); } + std::string getType() override { return "ExpressionStatement"; } + ExpressionStatement* clone() override { return new ExpressionStatement(*this); } }; class ProtectStatement : public Statement { @@ -2137,12 +2137,12 @@ namespace ast { /* constructors */ ProtectStatement(Expression* expression); ProtectStatement(const ProtectStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitProtectStatement(this); } virtual ~ProtectStatement() {} - virtual std::string getType() { return "ProtectStatement"; } - virtual ProtectStatement* clone() { return new ProtectStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitProtectStatement(this); } + std::string getType() override { return "ProtectStatement"; } + ProtectStatement* clone() override { return new ProtectStatement(*this); } }; class FromStatement : public Statement { @@ -2156,12 +2156,12 @@ namespace ast { /* constructors */ FromStatement(Name* name, Expression* from, Expression* to, Expression* opinc, StatementBlock* statementblock); FromStatement(const FromStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitFromStatement(this); } virtual ~FromStatement() {} - virtual std::string getType() { return "FromStatement"; } - virtual FromStatement* clone() { return new FromStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitFromStatement(this); } + std::string getType() override { return "FromStatement"; } + FromStatement* clone() override { return new FromStatement(*this); } }; class ForAllStatement : public Statement { @@ -2172,12 +2172,12 @@ namespace ast { /* constructors */ ForAllStatement(Name* name, StatementBlock* statementblock); ForAllStatement(const ForAllStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitForAllStatement(this); } virtual ~ForAllStatement() {} - virtual std::string getType() { return "ForAllStatement"; } - virtual ForAllStatement* clone() { return new ForAllStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitForAllStatement(this); } + std::string getType() override { return "ForAllStatement"; } + ForAllStatement* clone() override { return new ForAllStatement(*this); } }; class WhileStatement : public Statement { @@ -2188,12 +2188,12 @@ namespace ast { /* constructors */ WhileStatement(Expression* condition, StatementBlock* statementblock); WhileStatement(const WhileStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWhileStatement(this); } virtual ~WhileStatement() {} - virtual std::string getType() { return "WhileStatement"; } - virtual WhileStatement* clone() { return new WhileStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitWhileStatement(this); } + std::string getType() override { return "WhileStatement"; } + WhileStatement* clone() override { return new WhileStatement(*this); } }; class IfStatement : public Statement { @@ -2206,12 +2206,12 @@ namespace ast { /* constructors */ IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementVector elseifs, ElseStatement* elses); IfStatement(const IfStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitIfStatement(this); } virtual ~IfStatement() {} - virtual std::string getType() { return "IfStatement"; } - virtual IfStatement* clone() { return new IfStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitIfStatement(this); } + std::string getType() override { return "IfStatement"; } + IfStatement* clone() override { return new IfStatement(*this); } }; class ElseIfStatement : public Statement { @@ -2222,12 +2222,12 @@ namespace ast { /* constructors */ ElseIfStatement(Expression* condition, StatementBlock* statementblock); ElseIfStatement(const ElseIfStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitElseIfStatement(this); } virtual ~ElseIfStatement() {} - virtual std::string getType() { return "ElseIfStatement"; } - virtual ElseIfStatement* clone() { return new ElseIfStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitElseIfStatement(this); } + std::string getType() override { return "ElseIfStatement"; } + ElseIfStatement* clone() override { return new ElseIfStatement(*this); } }; class ElseStatement : public Statement { @@ -2237,12 +2237,12 @@ namespace ast { /* constructors */ ElseStatement(StatementBlock* statementblock); ElseStatement(const ElseStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitElseStatement(this); } virtual ~ElseStatement() {} - virtual std::string getType() { return "ElseStatement"; } - virtual ElseStatement* clone() { return new ElseStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitElseStatement(this); } + std::string getType() override { return "ElseStatement"; } + ElseStatement* clone() override { return new ElseStatement(*this); } }; class PartialEquation : public Statement { @@ -2255,12 +2255,12 @@ namespace ast { /* constructors */ PartialEquation(PrimeName* prime, Name* name1, Name* name2, Name* name3); PartialEquation(const PartialEquation& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPartialEquation(this); } virtual ~PartialEquation() {} - virtual std::string getType() { return "PartialEquation"; } - virtual PartialEquation* clone() { return new PartialEquation(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPartialEquation(this); } + std::string getType() override { return "PartialEquation"; } + PartialEquation* clone() override { return new PartialEquation(*this); } }; class PartialBoundary : public Statement { @@ -2277,12 +2277,12 @@ namespace ast { /* constructors */ PartialBoundary(Name* del, Identifier* name, FirstLastTypeIndex* index, Expression* expression, Name* name1, Name* del2, Name* name2, Name* name3); PartialBoundary(const PartialBoundary& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitPartialBoundary(this); } virtual ~PartialBoundary() {} - virtual std::string getType() { return "PartialBoundary"; } - virtual PartialBoundary* clone() { return new PartialBoundary(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitPartialBoundary(this); } + std::string getType() override { return "PartialBoundary"; } + PartialBoundary* clone() override { return new PartialBoundary(*this); } }; class WatchStatement : public Statement { @@ -2292,42 +2292,45 @@ namespace ast { /* constructors */ WatchStatement(WatchVector statements); WatchStatement(const WatchStatement& obj); + virtual ~WatchStatement() {} void addWatch(Watch *s) { statements.push_back(std::shared_ptr(s)); } - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitWatchStatement(this); } - virtual ~WatchStatement() {} - virtual std::string getType() { return "WatchStatement"; } - virtual WatchStatement* clone() { return new WatchStatement(*this); } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitWatchStatement(this); } + std::string getType() override { return "WatchStatement"; } + WatchStatement* clone() override { return new WatchStatement(*this); } }; class MutexLock : public Statement { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMutexLock(this); } virtual ~MutexLock() {} - virtual std::string getType() { return "MutexLock"; } - virtual MutexLock* clone() { return new MutexLock(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitMutexLock(this); } + std::string getType() override { return "MutexLock"; } + MutexLock* clone() override { return new MutexLock(*this); } }; class MutexUnlock : public Statement { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitMutexUnlock(this); } virtual ~MutexUnlock() {} - virtual std::string getType() { return "MutexUnlock"; } - virtual MutexUnlock* clone() { return new MutexUnlock(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitMutexUnlock(this); } + std::string getType() override { return "MutexUnlock"; } + MutexUnlock* clone() override { return new MutexUnlock(*this); } }; class Reset : public Statement { public: - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReset(this); } virtual ~Reset() {} - virtual std::string getType() { return "Reset"; } - virtual Reset* clone() { return new Reset(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitReset(this); } + std::string getType() override { return "Reset"; } + Reset* clone() override { return new Reset(*this); } }; class Sens : public Statement { @@ -2337,12 +2340,12 @@ namespace ast { /* constructors */ Sens(VarNameVector senslist); Sens(const Sens& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitSens(this); } virtual ~Sens() {} - virtual std::string getType() { return "Sens"; } - virtual Sens* clone() { return new Sens(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitSens(this); } + std::string getType() override { return "Sens"; } + Sens* clone() override { return new Sens(*this); } }; class Conserve : public Statement { @@ -2353,12 +2356,12 @@ namespace ast { /* constructors */ Conserve(Expression* react, Expression* expr); Conserve(const Conserve& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConserve(this); } virtual ~Conserve() {} - virtual std::string getType() { return "Conserve"; } - virtual Conserve* clone() { return new Conserve(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitConserve(this); } + std::string getType() override { return "Conserve"; } + Conserve* clone() override { return new Conserve(*this); } }; class Compartment : public Statement { @@ -2370,12 +2373,12 @@ namespace ast { /* constructors */ Compartment(Name* name, Expression* expression, NameVector names); Compartment(const Compartment& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitCompartment(this); } virtual ~Compartment() {} - virtual std::string getType() { return "Compartment"; } - virtual Compartment* clone() { return new Compartment(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitCompartment(this); } + std::string getType() override { return "Compartment"; } + Compartment* clone() override { return new Compartment(*this); } }; class LDifuse : public Statement { @@ -2387,12 +2390,12 @@ namespace ast { /* constructors */ LDifuse(Name* name, Expression* expression, NameVector names); LDifuse(const LDifuse& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLDifuse(this); } virtual ~LDifuse() {} - virtual std::string getType() { return "LDifuse"; } - virtual LDifuse* clone() { return new LDifuse(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLDifuse(this); } + std::string getType() override { return "LDifuse"; } + LDifuse* clone() override { return new LDifuse(*this); } }; class ReactionStatement : public Statement { @@ -2406,12 +2409,12 @@ namespace ast { /* constructors */ ReactionStatement(Expression* react1, ReactionOperator op, Expression* react2, Expression* expr1, Expression* expr2); ReactionStatement(const ReactionStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitReactionStatement(this); } virtual ~ReactionStatement() {} - virtual std::string getType() { return "ReactionStatement"; } - virtual ReactionStatement* clone() { return new ReactionStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitReactionStatement(this); } + std::string getType() override { return "ReactionStatement"; } + ReactionStatement* clone() override { return new ReactionStatement(*this); } }; class LagStatement : public Statement { @@ -2422,12 +2425,12 @@ namespace ast { /* constructors */ LagStatement(Identifier* name, Name* byname); LagStatement(const LagStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitLagStatement(this); } virtual ~LagStatement() {} - virtual std::string getType() { return "LagStatement"; } - virtual LagStatement* clone() { return new LagStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitLagStatement(this); } + std::string getType() override { return "LagStatement"; } + LagStatement* clone() override { return new LagStatement(*this); } }; class QueueStatement : public Statement { @@ -2438,12 +2441,12 @@ namespace ast { /* constructors */ QueueStatement(QueueExpressionType* qype, Identifier* name); QueueStatement(const QueueStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitQueueStatement(this); } virtual ~QueueStatement() {} - virtual std::string getType() { return "QueueStatement"; } - virtual QueueStatement* clone() { return new QueueStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitQueueStatement(this); } + std::string getType() override { return "QueueStatement"; } + QueueStatement* clone() override { return new QueueStatement(*this); } }; class ConstantStatement : public Statement { @@ -2455,12 +2458,12 @@ namespace ast { /* constructors */ ConstantStatement(Name* name, Number* value, Unit* unit); ConstantStatement(const ConstantStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitConstantStatement(this); } virtual ~ConstantStatement() {} - virtual std::string getType() { return "ConstantStatement"; } - virtual ConstantStatement* clone() { return new ConstantStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitConstantStatement(this); } + std::string getType() override { return "ConstantStatement"; } + ConstantStatement* clone() override { return new ConstantStatement(*this); } }; class TableStatement : public Statement { @@ -2474,12 +2477,12 @@ namespace ast { /* constructors */ TableStatement(NameVector tablst, NameVector dependlst, Expression* from, Expression* to, Integer* with); TableStatement(const TableStatement& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitTableStatement(this); } virtual ~TableStatement() {} - virtual std::string getType() { return "TableStatement"; } - virtual TableStatement* clone() { return new TableStatement(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitTableStatement(this); } + std::string getType() override { return "TableStatement"; } + TableStatement* clone() override { return new TableStatement(*this); } }; class NrnSuffix : public Statement { @@ -2490,12 +2493,12 @@ namespace ast { /* constructors */ NrnSuffix(Name* type, Name* name); NrnSuffix(const NrnSuffix& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnSuffix(this); } virtual ~NrnSuffix() {} - virtual std::string getType() { return "NrnSuffix"; } - virtual NrnSuffix* clone() { return new NrnSuffix(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnSuffix(this); } + std::string getType() override { return "NrnSuffix"; } + NrnSuffix* clone() override { return new NrnSuffix(*this); } }; class NrnUseion : public Statement { @@ -2508,12 +2511,12 @@ namespace ast { /* constructors */ NrnUseion(Name* name, ReadIonVarVector readlist, WriteIonVarVector writelist, Valence* valence); NrnUseion(const NrnUseion& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnUseion(this); } virtual ~NrnUseion() {} - virtual std::string getType() { return "NrnUseion"; } - virtual NrnUseion* clone() { return new NrnUseion(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnUseion(this); } + std::string getType() override { return "NrnUseion"; } + NrnUseion* clone() override { return new NrnUseion(*this); } }; class NrnNonspecific : public Statement { @@ -2523,12 +2526,12 @@ namespace ast { /* constructors */ NrnNonspecific(NonspeCurVarVector currents); NrnNonspecific(const NrnNonspecific& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnNonspecific(this); } virtual ~NrnNonspecific() {} - virtual std::string getType() { return "NrnNonspecific"; } - virtual NrnNonspecific* clone() { return new NrnNonspecific(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnNonspecific(this); } + std::string getType() override { return "NrnNonspecific"; } + NrnNonspecific* clone() override { return new NrnNonspecific(*this); } }; class NrnElctrodeCurrent : public Statement { @@ -2538,12 +2541,12 @@ namespace ast { /* constructors */ NrnElctrodeCurrent(ElectrodeCurVarVector ecurrents); NrnElctrodeCurrent(const NrnElctrodeCurrent& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnElctrodeCurrent(this); } virtual ~NrnElctrodeCurrent() {} - virtual std::string getType() { return "NrnElctrodeCurrent"; } - virtual NrnElctrodeCurrent* clone() { return new NrnElctrodeCurrent(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnElctrodeCurrent(this); } + std::string getType() override { return "NrnElctrodeCurrent"; } + NrnElctrodeCurrent* clone() override { return new NrnElctrodeCurrent(*this); } }; class NrnSection : public Statement { @@ -2553,12 +2556,12 @@ namespace ast { /* constructors */ NrnSection(SectionVarVector sections); NrnSection(const NrnSection& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnSection(this); } virtual ~NrnSection() {} - virtual std::string getType() { return "NrnSection"; } - virtual NrnSection* clone() { return new NrnSection(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnSection(this); } + std::string getType() override { return "NrnSection"; } + NrnSection* clone() override { return new NrnSection(*this); } }; class NrnRange : public Statement { @@ -2568,12 +2571,12 @@ namespace ast { /* constructors */ NrnRange(RangeVarVector range_vars); NrnRange(const NrnRange& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnRange(this); } virtual ~NrnRange() {} - virtual std::string getType() { return "NrnRange"; } - virtual NrnRange* clone() { return new NrnRange(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnRange(this); } + std::string getType() override { return "NrnRange"; } + NrnRange* clone() override { return new NrnRange(*this); } }; class NrnGlobal : public Statement { @@ -2583,12 +2586,12 @@ namespace ast { /* constructors */ NrnGlobal(GlobalVarVector global_vars); NrnGlobal(const NrnGlobal& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnGlobal(this); } virtual ~NrnGlobal() {} - virtual std::string getType() { return "NrnGlobal"; } - virtual NrnGlobal* clone() { return new NrnGlobal(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnGlobal(this); } + std::string getType() override { return "NrnGlobal"; } + NrnGlobal* clone() override { return new NrnGlobal(*this); } }; class NrnPointer : public Statement { @@ -2598,12 +2601,12 @@ namespace ast { /* constructors */ NrnPointer(PointerVarVector pointers); NrnPointer(const NrnPointer& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnPointer(this); } virtual ~NrnPointer() {} - virtual std::string getType() { return "NrnPointer"; } - virtual NrnPointer* clone() { return new NrnPointer(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnPointer(this); } + std::string getType() override { return "NrnPointer"; } + NrnPointer* clone() override { return new NrnPointer(*this); } }; class NrnBbcorePtr : public Statement { @@ -2613,12 +2616,12 @@ namespace ast { /* constructors */ NrnBbcorePtr(BbcorePointerVarVector bbcore_pointers); NrnBbcorePtr(const NrnBbcorePtr& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnBbcorePtr(this); } virtual ~NrnBbcorePtr() {} - virtual std::string getType() { return "NrnBbcorePtr"; } - virtual NrnBbcorePtr* clone() { return new NrnBbcorePtr(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnBbcorePtr(this); } + std::string getType() override { return "NrnBbcorePtr"; } + NrnBbcorePtr* clone() override { return new NrnBbcorePtr(*this); } }; class NrnExternal : public Statement { @@ -2628,12 +2631,12 @@ namespace ast { /* constructors */ NrnExternal(ExternVarVector externals); NrnExternal(const NrnExternal& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnExternal(this); } virtual ~NrnExternal() {} - virtual std::string getType() { return "NrnExternal"; } - virtual NrnExternal* clone() { return new NrnExternal(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnExternal(this); } + std::string getType() override { return "NrnExternal"; } + NrnExternal* clone() override { return new NrnExternal(*this); } }; class NrnThreadSafe : public Statement { @@ -2643,12 +2646,12 @@ namespace ast { /* constructors */ NrnThreadSafe(ThreadsafeVarVector threadsafe); NrnThreadSafe(const NrnThreadSafe& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitNrnThreadSafe(this); } virtual ~NrnThreadSafe() {} - virtual std::string getType() { return "NrnThreadSafe"; } - virtual NrnThreadSafe* clone() { return new NrnThreadSafe(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitNrnThreadSafe(this); } + std::string getType() override { return "NrnThreadSafe"; } + NrnThreadSafe* clone() override { return new NrnThreadSafe(*this); } }; class Verbatim : public Statement { @@ -2658,12 +2661,12 @@ namespace ast { /* constructors */ Verbatim(String* statement); Verbatim(const Verbatim& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitVerbatim(this); } virtual ~Verbatim() {} - virtual std::string getType() { return "Verbatim"; } - virtual Verbatim* clone() { return new Verbatim(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitVerbatim(this); } + std::string getType() override { return "Verbatim"; } + Verbatim* clone() override { return new Verbatim(*this); } }; class Comment : public Statement { @@ -2673,12 +2676,12 @@ namespace ast { /* constructors */ Comment(String* comment); Comment(const Comment& obj); - - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitComment(this); } virtual ~Comment() {} - virtual std::string getType() { return "Comment"; } - virtual Comment* clone() { return new Comment(*this); } + + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitComment(this); } + std::string getType() override { return "Comment"; } + Comment* clone() override { return new Comment(*this); } }; class Program : public AST { @@ -2691,6 +2694,7 @@ namespace ast { /* constructors */ Program(StatementVector statements, BlockVector blocks); Program(const Program& obj); + virtual ~Program() {} void addStatement(Statement *s) { statements.push_back(std::shared_ptr(s)); @@ -2700,13 +2704,12 @@ namespace ast { } Program() {} - virtual void visitChildren(Visitor* v); - virtual void accept(Visitor* v) { v->visitProgram(this); } - virtual ~Program() {} - virtual std::string getType() { return "Program"; } - virtual Program* clone() { return new Program(*this); } - virtual void setBlockSymbolTable(void *s) { symtab = s; } - virtual void* getBlockSymbolTable() { return symtab; } + void visitChildren (Visitor* v) override; + void accept(Visitor* v) override { v->visitProgram(this); } + std::string getType() override { return "Program"; } + Program* clone() override { return new Program(*this); } + void setBlockSymbolTable(void *s) { symtab = s; } + void* getBlockSymbolTable() { return symtab; } }; diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index f5d7921c49..0b580e6fec 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -77,7 +77,8 @@ namespace ast { virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ return nullptr; } - // virtual AST* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } + + virtual AST* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } }; } // namespace ast diff --git a/src/nmodl/parser/nmodl_context.hpp b/src/nmodl/parser/nmodl_context.hpp deleted file mode 100644 index 4fc76a98ef..0000000000 --- a/src/nmodl/parser/nmodl_context.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef _NMODL_CONTEXT_ -#define _NMODL_CONTEXT_ - -#include -#include -#include "ast/ast.hpp" - -class NmodlContext { - public: - /* root of the ast */ - ast::ProgramNode* astRoot; - - /* main scanner for NMODL */ - void* scanner; - - /* input stream for parsing */ - std::istream* is; - - /* list of all macro defined variables */ - std::map defined_var; - - /* constructor */ - NmodlContext(std::istream* is = &std::cin) { - init_scanner(); - this->is = is; - this->astRoot = NULL; - } - - void add_defined_var(std::string var, int value) { - defined_var[var] = value; - } - - bool is_defined_var(std::string name) { - if (defined_var.find(name) == defined_var.end()) { - return false; - } - - return true; - } - - int get_defined_var_value(std::string name) { - if (is_defined_var(name)) { - return defined_var[name]; - } - - std::cout << "\n ERROR: Trying to get value of undefined value!"; - abort(); - } - - virtual ~NmodlContext() { - destroy_scanner(); - - if (astRoot) { - delete astRoot; - } - } - - protected: - /* defined in lexer.l */ - void init_scanner(); - void destroy_scanner(); -}; - -int Nmodl_parse(NmodlContext*); - -#endif // _NMODL_CONTEXT_ From c25e9cadf9e8089e7d50d7e05ec14785e1d863d9 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 24 Nov 2017 15:40:23 +0100 Subject: [PATCH 018/871] Implementing YAML based language definition and AST generation from Python instead of manual writing. This will allow to automatically generate/adapt ast very fast and write visitors as well. Also added abstract visitor class printer and delete previous hand-written file. (squash from branch sandbox/kumbhar/py-ast-gen) Change-Id: I14ccb10b5606a92fee52068c4209083251ab15b1 NMODL Repo SHA: BlueBrain/nmodl@6051c7d39e5640242f215060649dd162ed6e78d6 --- cmake/nmodl/CMakeLists.txt | 1 + src/nmodl/ast/ast.cpp | 2829 ------------------------ src/nmodl/ast/ast.hpp | 2717 ----------------------- src/nmodl/language/CMakeLists.txt | 20 + src/nmodl/language/argument.py | 23 + src/nmodl/language/ast.py | 262 +++ src/nmodl/language/ast_printer.py | 333 +++ src/nmodl/language/base_printer.py | 163 ++ src/nmodl/language/code_generator.py | 24 + src/nmodl/language/nmodl.yaml | 1618 ++++++++++++++ src/nmodl/language/node_types.py | 152 ++ src/nmodl/language/parser.py | 176 ++ src/nmodl/language/visitors_printer.py | 20 + src/nmodl/lexer/CMakeLists.txt | 9 +- src/nmodl/parser/nmodl.yy | 62 +- src/nmodl/visitors/.gitignore | 4 + src/nmodl/visitors/visitor.hpp | 136 -- 17 files changed, 2834 insertions(+), 5715 deletions(-) delete mode 100644 src/nmodl/ast/ast.cpp delete mode 100644 src/nmodl/ast/ast.hpp create mode 100644 src/nmodl/language/CMakeLists.txt create mode 100644 src/nmodl/language/argument.py create mode 100644 src/nmodl/language/ast.py create mode 100644 src/nmodl/language/ast_printer.py create mode 100644 src/nmodl/language/base_printer.py create mode 100644 src/nmodl/language/code_generator.py create mode 100644 src/nmodl/language/nmodl.yaml create mode 100644 src/nmodl/language/node_types.py create mode 100644 src/nmodl/language/parser.py create mode 100644 src/nmodl/language/visitors_printer.py create mode 100644 src/nmodl/visitors/.gitignore delete mode 100644 src/nmodl/visitors/visitor.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4e31909e96..843b56838a 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -35,6 +35,7 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) +add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) add_subdirectory(test) diff --git a/src/nmodl/ast/ast.cpp b/src/nmodl/ast/ast.cpp deleted file mode 100644 index 3a4eeceaf9..0000000000 --- a/src/nmodl/ast/ast.cpp +++ /dev/null @@ -1,2829 +0,0 @@ -#include "ast/ast.hpp" - -namespace ast { - void Statement::visitChildren(Visitor* v) {} - void Expression::visitChildren(Visitor* v) {} - void Block::visitChildren(Visitor* v) {} - void Identifier::visitChildren(Visitor* v) {} - void Number::visitChildren(Visitor* v) {} - void String::visitChildren(Visitor* v) {} - /* constructor for String ast node */ - String::String(std::string value) - : value(value) - { - } - - /* copy constructor for String ast node */ - String::String(const String& obj) - { - this->value = obj.value; - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for Integer ast node */ - void Integer::visitChildren(Visitor* v) { - if (this->macroname) { - this->macroname->accept(v); - } - } - - /* constructor for Integer ast node */ - Integer::Integer(int value, Name* macroname) - : value(value) - { - this->macroname = std::shared_ptr(macroname); - } - - /* copy constructor for Integer ast node */ - Integer::Integer(const Integer& obj) - { - this->value = obj.value; - if(obj.macroname) - this->macroname = std::shared_ptr(obj.macroname->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - void Float::visitChildren(Visitor* v) {} - /* constructor for Float ast node */ - Float::Float(float value) - : value(value) - { - } - - /* copy constructor for Float ast node */ - Float::Float(const Float& obj) - { - this->value = obj.value; - } - - void Double::visitChildren(Visitor* v) {} - /* constructor for Double ast node */ - Double::Double(double value) - : value(value) - { - } - - /* copy constructor for Double ast node */ - Double::Double(const Double& obj) - { - this->value = obj.value; - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - void Boolean::visitChildren(Visitor* v) {} - /* constructor for Boolean ast node */ - Boolean::Boolean(int value) - : value(value) - { - } - - /* copy constructor for Boolean ast node */ - Boolean::Boolean(const Boolean& obj) - { - this->value = obj.value; - } - - /* visit method for Name ast node */ - void Name::visitChildren(Visitor* v) { - value->accept(v); - } - - /* constructor for Name ast node */ - Name::Name(String* value) - { - this->value = std::shared_ptr(value); - } - - /* copy constructor for Name ast node */ - Name::Name(const Name& obj) - { - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for PrimeName ast node */ - void PrimeName::visitChildren(Visitor* v) { - value->accept(v); - order->accept(v); - } - - /* constructor for PrimeName ast node */ - PrimeName::PrimeName(String* value, Integer* order) - { - this->value = std::shared_ptr(value); - this->order = std::shared_ptr(order); - } - - /* copy constructor for PrimeName ast node */ - PrimeName::PrimeName(const PrimeName& obj) - { - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - if(obj.order) - this->order = std::shared_ptr(obj.order->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for VarName ast node */ - void VarName::visitChildren(Visitor* v) { - name->accept(v); - if (this->at_index) { - this->at_index->accept(v); - } - } - - /* constructor for VarName ast node */ - VarName::VarName(Identifier* name, Integer* at_index) - { - this->name = std::shared_ptr(name); - this->at_index = std::shared_ptr(at_index); - } - - /* copy constructor for VarName ast node */ - VarName::VarName(const VarName& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.at_index) - this->at_index = std::shared_ptr(obj.at_index->clone()); - } - - /* visit method for IndexedName ast node */ - void IndexedName::visitChildren(Visitor* v) { - name->accept(v); - index->accept(v); - } - - /* constructor for IndexedName ast node */ - IndexedName::IndexedName(Identifier* name, Expression* index) - { - this->name = std::shared_ptr(name); - this->index = std::shared_ptr(index); - } - - /* copy constructor for IndexedName ast node */ - IndexedName::IndexedName(const IndexedName& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.index) - this->index = std::shared_ptr(obj.index->clone()); - } - - /* visit method for Argument ast node */ - void Argument::visitChildren(Visitor* v) { - name->accept(v); - if (this->unit) { - this->unit->accept(v); - } - } - - /* constructor for Argument ast node */ - Argument::Argument(Identifier* name, Unit* unit) - { - this->name = std::shared_ptr(name); - this->unit = std::shared_ptr(unit); - } - - /* copy constructor for Argument ast node */ - Argument::Argument(const Argument& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - } - - /* visit method for ReactVarName ast node */ - void ReactVarName::visitChildren(Visitor* v) { - if (this->value) { - this->value->accept(v); - } - name->accept(v); - } - - /* constructor for ReactVarName ast node */ - ReactVarName::ReactVarName(Integer* value, VarName* name) - { - this->value = std::shared_ptr(value); - this->name = std::shared_ptr(name); - } - - /* copy constructor for ReactVarName ast node */ - ReactVarName::ReactVarName(const ReactVarName& obj) - { - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ReadIonVar ast node */ - void ReadIonVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ReadIonVar ast node */ - ReadIonVar::ReadIonVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for ReadIonVar ast node */ - ReadIonVar::ReadIonVar(const ReadIonVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for WriteIonVar ast node */ - void WriteIonVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for WriteIonVar ast node */ - WriteIonVar::WriteIonVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for WriteIonVar ast node */ - WriteIonVar::WriteIonVar(const WriteIonVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for NonspeCurVar ast node */ - void NonspeCurVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for NonspeCurVar ast node */ - NonspeCurVar::NonspeCurVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for NonspeCurVar ast node */ - NonspeCurVar::NonspeCurVar(const NonspeCurVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ElectrodeCurVar ast node */ - void ElectrodeCurVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ElectrodeCurVar ast node */ - ElectrodeCurVar::ElectrodeCurVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for ElectrodeCurVar ast node */ - ElectrodeCurVar::ElectrodeCurVar(const ElectrodeCurVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for SectionVar ast node */ - void SectionVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for SectionVar ast node */ - SectionVar::SectionVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for SectionVar ast node */ - SectionVar::SectionVar(const SectionVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for RangeVar ast node */ - void RangeVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for RangeVar ast node */ - RangeVar::RangeVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for RangeVar ast node */ - RangeVar::RangeVar(const RangeVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for GlobalVar ast node */ - void GlobalVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for GlobalVar ast node */ - GlobalVar::GlobalVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for GlobalVar ast node */ - GlobalVar::GlobalVar(const GlobalVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for PointerVar ast node */ - void PointerVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for PointerVar ast node */ - PointerVar::PointerVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for PointerVar ast node */ - PointerVar::PointerVar(const PointerVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for BbcorePointerVar ast node */ - void BbcorePointerVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for BbcorePointerVar ast node */ - BbcorePointerVar::BbcorePointerVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for BbcorePointerVar ast node */ - BbcorePointerVar::BbcorePointerVar(const BbcorePointerVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ExternVar ast node */ - void ExternVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ExternVar ast node */ - ExternVar::ExternVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for ExternVar ast node */ - ExternVar::ExternVar(const ExternVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ThreadsafeVar ast node */ - void ThreadsafeVar::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for ThreadsafeVar ast node */ - ThreadsafeVar::ThreadsafeVar(Name* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for ThreadsafeVar ast node */ - ThreadsafeVar::ThreadsafeVar(const ThreadsafeVar& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ParamBlock ast node */ - void ParamBlock::visitChildren(Visitor* v) { - for(auto& item : this->statements) { - item->accept(v); - } - } - - /* constructor for ParamBlock ast node */ - ParamBlock::ParamBlock(ParamAssignVector statements) - : statements(statements) - { - } - - /* copy constructor for ParamBlock ast node */ - ParamBlock::ParamBlock(const ParamBlock& obj) - { - for(auto& item : obj.statements) { - this->statements.push_back(std::shared_ptr< ParamAssign>(item->clone())); - } - } - - /* visit method for StepBlock ast node */ - void StepBlock::visitChildren(Visitor* v) { - for(auto& item : this->statements) { - item->accept(v); - } - } - - /* constructor for StepBlock ast node */ - StepBlock::StepBlock(SteppedVector statements) - : statements(statements) - { - } - - /* copy constructor for StepBlock ast node */ - StepBlock::StepBlock(const StepBlock& obj) - { - for(auto& item : obj.statements) { - this->statements.push_back(std::shared_ptr< Stepped>(item->clone())); - } - } - - /* visit method for IndependentBlock ast node */ - void IndependentBlock::visitChildren(Visitor* v) { - for(auto& item : this->definitions) { - item->accept(v); - } - } - - /* constructor for IndependentBlock ast node */ - IndependentBlock::IndependentBlock(IndependentDefVector definitions) - : definitions(definitions) - { - } - - /* copy constructor for IndependentBlock ast node */ - IndependentBlock::IndependentBlock(const IndependentBlock& obj) - { - for(auto& item : obj.definitions) { - this->definitions.push_back(std::shared_ptr< IndependentDef>(item->clone())); - } - } - - /* visit method for DependentBlock ast node */ - void DependentBlock::visitChildren(Visitor* v) { - for(auto& item : this->definitions) { - item->accept(v); - } - } - - /* constructor for DependentBlock ast node */ - DependentBlock::DependentBlock(DependentDefVector definitions) - : definitions(definitions) - { - } - - /* copy constructor for DependentBlock ast node */ - DependentBlock::DependentBlock(const DependentBlock& obj) - { - for(auto& item : obj.definitions) { - this->definitions.push_back(std::shared_ptr< DependentDef>(item->clone())); - } - } - - /* visit method for StateBlock ast node */ - void StateBlock::visitChildren(Visitor* v) { - for(auto& item : this->definitions) { - item->accept(v); - } - } - - /* constructor for StateBlock ast node */ - StateBlock::StateBlock(DependentDefVector definitions) - : definitions(definitions) - { - } - - /* copy constructor for StateBlock ast node */ - StateBlock::StateBlock(const StateBlock& obj) - { - for(auto& item : obj.definitions) { - this->definitions.push_back(std::shared_ptr< DependentDef>(item->clone())); - } - } - - /* visit method for PlotBlock ast node */ - void PlotBlock::visitChildren(Visitor* v) { - plot->accept(v); - } - - /* constructor for PlotBlock ast node */ - PlotBlock::PlotBlock(PlotDeclaration* plot) - { - this->plot = std::shared_ptr(plot); - } - - /* copy constructor for PlotBlock ast node */ - PlotBlock::PlotBlock(const PlotBlock& obj) - { - if(obj.plot) - this->plot = std::shared_ptr(obj.plot->clone()); - } - - /* visit method for InitialBlock ast node */ - void InitialBlock::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for InitialBlock ast node */ - InitialBlock::InitialBlock(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for InitialBlock ast node */ - InitialBlock::InitialBlock(const InitialBlock& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for ConstructorBlock ast node */ - void ConstructorBlock::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for ConstructorBlock ast node */ - ConstructorBlock::ConstructorBlock(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for ConstructorBlock ast node */ - ConstructorBlock::ConstructorBlock(const ConstructorBlock& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for DestructorBlock ast node */ - void DestructorBlock::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for DestructorBlock ast node */ - DestructorBlock::DestructorBlock(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for DestructorBlock ast node */ - DestructorBlock::DestructorBlock(const DestructorBlock& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for StatementBlock ast node */ - void StatementBlock::visitChildren(Visitor* v) { - for(auto& item : this->statements) { - item->accept(v); - } - } - - /* constructor for StatementBlock ast node */ - StatementBlock::StatementBlock(StatementVector statements) - : statements(statements) - { - } - - /* copy constructor for StatementBlock ast node */ - StatementBlock::StatementBlock(const StatementBlock& obj) - { - for(auto& item : obj.statements) { - this->statements.push_back(std::shared_ptr< Statement>(item->clone())); - } - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for DerivativeBlock ast node */ - void DerivativeBlock::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for DerivativeBlock ast node */ - DerivativeBlock::DerivativeBlock(Name* name, StatementBlock* statementblock) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for DerivativeBlock ast node */ - DerivativeBlock::DerivativeBlock(const DerivativeBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for LinearBlock ast node */ - void LinearBlock::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->solvefor) { - item->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for LinearBlock ast node */ - LinearBlock::LinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock) - : solvefor(solvefor) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for LinearBlock ast node */ - LinearBlock::LinearBlock(const LinearBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.solvefor) { - this->solvefor.push_back(std::shared_ptr< Name>(item->clone())); - } - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for NonLinearBlock ast node */ - void NonLinearBlock::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->solvefor) { - item->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for NonLinearBlock ast node */ - NonLinearBlock::NonLinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock) - : solvefor(solvefor) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for NonLinearBlock ast node */ - NonLinearBlock::NonLinearBlock(const NonLinearBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.solvefor) { - this->solvefor.push_back(std::shared_ptr< Name>(item->clone())); - } - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for DiscreteBlock ast node */ - void DiscreteBlock::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for DiscreteBlock ast node */ - DiscreteBlock::DiscreteBlock(Name* name, StatementBlock* statementblock) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for DiscreteBlock ast node */ - DiscreteBlock::DiscreteBlock(const DiscreteBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for PartialBlock ast node */ - void PartialBlock::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for PartialBlock ast node */ - PartialBlock::PartialBlock(Name* name, StatementBlock* statementblock) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for PartialBlock ast node */ - PartialBlock::PartialBlock(const PartialBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for FunctionTableBlock ast node */ - void FunctionTableBlock::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->arguments) { - item->accept(v); - } - if (this->unit) { - this->unit->accept(v); - } - } - - /* constructor for FunctionTableBlock ast node */ - FunctionTableBlock::FunctionTableBlock(Name* name, ArgumentVector arguments, Unit* unit) - : arguments(arguments) - { - this->name = std::shared_ptr(name); - this->unit = std::shared_ptr(unit); - } - - /* copy constructor for FunctionTableBlock ast node */ - FunctionTableBlock::FunctionTableBlock(const FunctionTableBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.arguments) { - this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); - } - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for FunctionBlock ast node */ - void FunctionBlock::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->arguments) { - item->accept(v); - } - if (this->unit) { - this->unit->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for FunctionBlock ast node */ - FunctionBlock::FunctionBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock) - : arguments(arguments) - { - this->name = std::shared_ptr(name); - this->unit = std::shared_ptr(unit); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for FunctionBlock ast node */ - FunctionBlock::FunctionBlock(const FunctionBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.arguments) { - this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); - } - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for ProcedureBlock ast node */ - void ProcedureBlock::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->arguments) { - item->accept(v); - } - if (this->unit) { - this->unit->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for ProcedureBlock ast node */ - ProcedureBlock::ProcedureBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock) - : arguments(arguments) - { - this->name = std::shared_ptr(name); - this->unit = std::shared_ptr(unit); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for ProcedureBlock ast node */ - ProcedureBlock::ProcedureBlock(const ProcedureBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.arguments) { - this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); - } - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for NetReceiveBlock ast node */ - void NetReceiveBlock::visitChildren(Visitor* v) { - for(auto& item : this->arguments) { - item->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for NetReceiveBlock ast node */ - NetReceiveBlock::NetReceiveBlock(ArgumentVector arguments, StatementBlock* statementblock) - : arguments(arguments) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for NetReceiveBlock ast node */ - NetReceiveBlock::NetReceiveBlock(const NetReceiveBlock& obj) - { - for(auto& item : obj.arguments) { - this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); - } - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for SolveBlock ast node */ - void SolveBlock::visitChildren(Visitor* v) { - name->accept(v); - if (this->method) { - this->method->accept(v); - } - if (this->ifsolerr) { - this->ifsolerr->accept(v); - } - } - - /* constructor for SolveBlock ast node */ - SolveBlock::SolveBlock(Name* name, Name* method, StatementBlock* ifsolerr) - { - this->name = std::shared_ptr(name); - this->method = std::shared_ptr(method); - this->ifsolerr = std::shared_ptr(ifsolerr); - } - - /* copy constructor for SolveBlock ast node */ - SolveBlock::SolveBlock(const SolveBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.method) - this->method = std::shared_ptr(obj.method->clone()); - if(obj.ifsolerr) - this->ifsolerr = std::shared_ptr(obj.ifsolerr->clone()); - } - - /* visit method for BreakpointBlock ast node */ - void BreakpointBlock::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for BreakpointBlock ast node */ - BreakpointBlock::BreakpointBlock(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for BreakpointBlock ast node */ - BreakpointBlock::BreakpointBlock(const BreakpointBlock& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for TerminalBlock ast node */ - void TerminalBlock::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for TerminalBlock ast node */ - TerminalBlock::TerminalBlock(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for TerminalBlock ast node */ - TerminalBlock::TerminalBlock(const TerminalBlock& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for BeforeBlock ast node */ - void BeforeBlock::visitChildren(Visitor* v) { - block->accept(v); - } - - /* constructor for BeforeBlock ast node */ - BeforeBlock::BeforeBlock(BABlock* block) - { - this->block = std::shared_ptr(block); - } - - /* copy constructor for BeforeBlock ast node */ - BeforeBlock::BeforeBlock(const BeforeBlock& obj) - { - if(obj.block) - this->block = std::shared_ptr(obj.block->clone()); - } - - /* visit method for AfterBlock ast node */ - void AfterBlock::visitChildren(Visitor* v) { - block->accept(v); - } - - /* constructor for AfterBlock ast node */ - AfterBlock::AfterBlock(BABlock* block) - { - this->block = std::shared_ptr(block); - } - - /* copy constructor for AfterBlock ast node */ - AfterBlock::AfterBlock(const AfterBlock& obj) - { - if(obj.block) - this->block = std::shared_ptr(obj.block->clone()); - } - - /* visit method for BABlock ast node */ - void BABlock::visitChildren(Visitor* v) { - type->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for BABlock ast node */ - BABlock::BABlock(BABlockType* type, StatementBlock* statementblock) - { - this->type = std::shared_ptr(type); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for BABlock ast node */ - BABlock::BABlock(const BABlock& obj) - { - if(obj.type) - this->type = std::shared_ptr(obj.type->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for ForNetcon ast node */ - void ForNetcon::visitChildren(Visitor* v) { - for(auto& item : this->arguments) { - item->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for ForNetcon ast node */ - ForNetcon::ForNetcon(ArgumentVector arguments, StatementBlock* statementblock) - : arguments(arguments) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for ForNetcon ast node */ - ForNetcon::ForNetcon(const ForNetcon& obj) - { - for(auto& item : obj.arguments) { - this->arguments.push_back(std::shared_ptr< Argument>(item->clone())); - } - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for KineticBlock ast node */ - void KineticBlock::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->solvefor) { - item->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for KineticBlock ast node */ - KineticBlock::KineticBlock(Name* name, NameVector solvefor, StatementBlock* statementblock) - : solvefor(solvefor) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for KineticBlock ast node */ - KineticBlock::KineticBlock(const KineticBlock& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.solvefor) { - this->solvefor.push_back(std::shared_ptr< Name>(item->clone())); - } - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for MatchBlock ast node */ - void MatchBlock::visitChildren(Visitor* v) { - for(auto& item : this->matchs) { - item->accept(v); - } - } - - /* constructor for MatchBlock ast node */ - MatchBlock::MatchBlock(MatchVector matchs) - : matchs(matchs) - { - } - - /* copy constructor for MatchBlock ast node */ - MatchBlock::MatchBlock(const MatchBlock& obj) - { - for(auto& item : obj.matchs) { - this->matchs.push_back(std::shared_ptr< Match>(item->clone())); - } - } - - /* visit method for UnitBlock ast node */ - void UnitBlock::visitChildren(Visitor* v) { - for(auto& item : this->definitions) { - item->accept(v); - } - } - - /* constructor for UnitBlock ast node */ - UnitBlock::UnitBlock(ExpressionVector definitions) - : definitions(definitions) - { - } - - /* copy constructor for UnitBlock ast node */ - UnitBlock::UnitBlock(const UnitBlock& obj) - { - for(auto& item : obj.definitions) { - this->definitions.push_back(std::shared_ptr< Expression>(item->clone())); - } - } - - /* visit method for ConstantBlock ast node */ - void ConstantBlock::visitChildren(Visitor* v) { - for(auto& item : this->statements) { - item->accept(v); - } - } - - /* constructor for ConstantBlock ast node */ - ConstantBlock::ConstantBlock(ConstantStatementVector statements) - : statements(statements) - { - } - - /* copy constructor for ConstantBlock ast node */ - ConstantBlock::ConstantBlock(const ConstantBlock& obj) - { - for(auto& item : obj.statements) { - this->statements.push_back(std::shared_ptr< ConstantStatement>(item->clone())); - } - } - - /* visit method for NeuronBlock ast node */ - void NeuronBlock::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for NeuronBlock ast node */ - NeuronBlock::NeuronBlock(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for NeuronBlock ast node */ - NeuronBlock::NeuronBlock(const NeuronBlock& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for Unit ast node */ - void Unit::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for Unit ast node */ - Unit::Unit(String* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for Unit ast node */ - Unit::Unit(const Unit& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for DoubleUnit ast node */ - void DoubleUnit::visitChildren(Visitor* v) { - values->accept(v); - if (this->unit) { - this->unit->accept(v); - } - } - - /* constructor for DoubleUnit ast node */ - DoubleUnit::DoubleUnit(Double* values, Unit* unit) - { - this->values = std::shared_ptr(values); - this->unit = std::shared_ptr(unit); - } - - /* copy constructor for DoubleUnit ast node */ - DoubleUnit::DoubleUnit(const DoubleUnit& obj) - { - if(obj.values) - this->values = std::shared_ptr(obj.values->clone()); - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - } - - /* visit method for LocalVariable ast node */ - void LocalVariable::visitChildren(Visitor* v) { - name->accept(v); - } - - /* constructor for LocalVariable ast node */ - LocalVariable::LocalVariable(Identifier* name) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for LocalVariable ast node */ - LocalVariable::LocalVariable(const LocalVariable& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for Limits ast node */ - void Limits::visitChildren(Visitor* v) { - min->accept(v); - max->accept(v); - } - - /* constructor for Limits ast node */ - Limits::Limits(Double* min, Double* max) - { - this->min = std::shared_ptr(min); - this->max = std::shared_ptr(max); - } - - /* copy constructor for Limits ast node */ - Limits::Limits(const Limits& obj) - { - if(obj.min) - this->min = std::shared_ptr(obj.min->clone()); - if(obj.max) - this->max = std::shared_ptr(obj.max->clone()); - } - - /* visit method for NumberRange ast node */ - void NumberRange::visitChildren(Visitor* v) { - min->accept(v); - max->accept(v); - } - - /* constructor for NumberRange ast node */ - NumberRange::NumberRange(Number* min, Number* max) - { - this->min = std::shared_ptr(min); - this->max = std::shared_ptr(max); - } - - /* copy constructor for NumberRange ast node */ - NumberRange::NumberRange(const NumberRange& obj) - { - if(obj.min) - this->min = std::shared_ptr(obj.min->clone()); - if(obj.max) - this->max = std::shared_ptr(obj.max->clone()); - } - - /* visit method for PlotVariable ast node */ - void PlotVariable::visitChildren(Visitor* v) { - name->accept(v); - if (this->index) { - this->index->accept(v); - } - } - - /* constructor for PlotVariable ast node */ - PlotVariable::PlotVariable(Identifier* name, Integer* index) - { - this->name = std::shared_ptr(name); - this->index = std::shared_ptr(index); - } - - /* copy constructor for PlotVariable ast node */ - PlotVariable::PlotVariable(const PlotVariable& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.index) - this->index = std::shared_ptr(obj.index->clone()); - } - - void BinaryOperator::visitChildren(Visitor* v) {} - /* constructor for BinaryOperator ast node */ - BinaryOperator::BinaryOperator(BinaryOp value) - : value(value) - { - } - - /* copy constructor for BinaryOperator ast node */ - BinaryOperator::BinaryOperator(const BinaryOperator& obj) - { - this->value = obj.value; - } - - void UnaryOperator::visitChildren(Visitor* v) {} - /* constructor for UnaryOperator ast node */ - UnaryOperator::UnaryOperator(UnaryOp value) - : value(value) - { - } - - /* copy constructor for UnaryOperator ast node */ - UnaryOperator::UnaryOperator(const UnaryOperator& obj) - { - this->value = obj.value; - } - - void ReactionOperator::visitChildren(Visitor* v) {} - /* constructor for ReactionOperator ast node */ - ReactionOperator::ReactionOperator(ReactionOp value) - : value(value) - { - } - - /* copy constructor for ReactionOperator ast node */ - ReactionOperator::ReactionOperator(const ReactionOperator& obj) - { - this->value = obj.value; - } - - /* visit method for BinaryExpression ast node */ - void BinaryExpression::visitChildren(Visitor* v) { - lhs->accept(v); - op.accept(v); - rhs->accept(v); - } - - /* constructor for BinaryExpression ast node */ - BinaryExpression::BinaryExpression(Expression* lhs, BinaryOperator op, Expression* rhs) - : op(op) - { - this->lhs = std::shared_ptr(lhs); - this->rhs = std::shared_ptr(rhs); - } - - /* copy constructor for BinaryExpression ast node */ - BinaryExpression::BinaryExpression(const BinaryExpression& obj) - { - if(obj.lhs) - this->lhs = std::shared_ptr(obj.lhs->clone()); - this->op = obj.op; - if(obj.rhs) - this->rhs = std::shared_ptr(obj.rhs->clone()); - } - - /* visit method for UnaryExpression ast node */ - void UnaryExpression::visitChildren(Visitor* v) { - op.accept(v); - expression->accept(v); - } - - /* constructor for UnaryExpression ast node */ - UnaryExpression::UnaryExpression(UnaryOperator op, Expression* expression) - : op(op) - { - this->expression = std::shared_ptr(expression); - } - - /* copy constructor for UnaryExpression ast node */ - UnaryExpression::UnaryExpression(const UnaryExpression& obj) - { - this->op = obj.op; - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - } - - /* visit method for NonLinEuation ast node */ - void NonLinEuation::visitChildren(Visitor* v) { - lhs->accept(v); - rhs->accept(v); - } - - /* constructor for NonLinEuation ast node */ - NonLinEuation::NonLinEuation(Expression* lhs, Expression* rhs) - { - this->lhs = std::shared_ptr(lhs); - this->rhs = std::shared_ptr(rhs); - } - - /* copy constructor for NonLinEuation ast node */ - NonLinEuation::NonLinEuation(const NonLinEuation& obj) - { - if(obj.lhs) - this->lhs = std::shared_ptr(obj.lhs->clone()); - if(obj.rhs) - this->rhs = std::shared_ptr(obj.rhs->clone()); - } - - /* visit method for LinEquation ast node */ - void LinEquation::visitChildren(Visitor* v) { - leftlinexpr->accept(v); - linexpr->accept(v); - } - - /* constructor for LinEquation ast node */ - LinEquation::LinEquation(Expression* leftlinexpr, Expression* linexpr) - { - this->leftlinexpr = std::shared_ptr(leftlinexpr); - this->linexpr = std::shared_ptr(linexpr); - } - - /* copy constructor for LinEquation ast node */ - LinEquation::LinEquation(const LinEquation& obj) - { - if(obj.leftlinexpr) - this->leftlinexpr = std::shared_ptr(obj.leftlinexpr->clone()); - if(obj.linexpr) - this->linexpr = std::shared_ptr(obj.linexpr->clone()); - } - - /* visit method for FunctionCall ast node */ - void FunctionCall::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->arguments) { - item->accept(v); - } - } - - /* constructor for FunctionCall ast node */ - FunctionCall::FunctionCall(Name* name, ExpressionVector arguments) - : arguments(arguments) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for FunctionCall ast node */ - FunctionCall::FunctionCall(const FunctionCall& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.arguments) { - this->arguments.push_back(std::shared_ptr< Expression>(item->clone())); - } - } - - void FirstLastTypeIndex::visitChildren(Visitor* v) {} - /* constructor for FirstLastTypeIndex ast node */ - FirstLastTypeIndex::FirstLastTypeIndex(FirstLastType value) - : value(value) - { - } - - /* copy constructor for FirstLastTypeIndex ast node */ - FirstLastTypeIndex::FirstLastTypeIndex(const FirstLastTypeIndex& obj) - { - this->value = obj.value; - } - - /* visit method for Watch ast node */ - void Watch::visitChildren(Visitor* v) { - expression->accept(v); - value->accept(v); - } - - /* constructor for Watch ast node */ - Watch::Watch(Expression* expression, Expression* value) - { - this->expression = std::shared_ptr(expression); - this->value = std::shared_ptr(value); - } - - /* copy constructor for Watch ast node */ - Watch::Watch(const Watch& obj) - { - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - } - - void QueueExpressionType::visitChildren(Visitor* v) {} - /* constructor for QueueExpressionType ast node */ - QueueExpressionType::QueueExpressionType(QueueType value) - : value(value) - { - } - - /* copy constructor for QueueExpressionType ast node */ - QueueExpressionType::QueueExpressionType(const QueueExpressionType& obj) - { - this->value = obj.value; - } - - /* visit method for Match ast node */ - void Match::visitChildren(Visitor* v) { - name->accept(v); - if (this->expression) { - this->expression->accept(v); - } - } - - /* constructor for Match ast node */ - Match::Match(Identifier* name, Expression* expression) - { - this->name = std::shared_ptr(name); - this->expression = std::shared_ptr(expression); - } - - /* copy constructor for Match ast node */ - Match::Match(const Match& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - } - - void BABlockType::visitChildren(Visitor* v) {} - /* constructor for BABlockType ast node */ - BABlockType::BABlockType(BAType value) - : value(value) - { - } - - /* copy constructor for BABlockType ast node */ - BABlockType::BABlockType(const BABlockType& obj) - { - this->value = obj.value; - } - - /* visit method for UnitDef ast node */ - void UnitDef::visitChildren(Visitor* v) { - unit1->accept(v); - unit2->accept(v); - } - - /* constructor for UnitDef ast node */ - UnitDef::UnitDef(Unit* unit1, Unit* unit2) - { - this->unit1 = std::shared_ptr(unit1); - this->unit2 = std::shared_ptr(unit2); - } - - /* copy constructor for UnitDef ast node */ - UnitDef::UnitDef(const UnitDef& obj) - { - if(obj.unit1) - this->unit1 = std::shared_ptr(obj.unit1->clone()); - if(obj.unit2) - this->unit2 = std::shared_ptr(obj.unit2->clone()); - } - - /* visit method for FactorDef ast node */ - void FactorDef::visitChildren(Visitor* v) { - name->accept(v); - if (this->value) { - this->value->accept(v); - } - unit1->accept(v); - if (this->gt) { - this->gt->accept(v); - } - if (this->unit2) { - this->unit2->accept(v); - } - } - - /* constructor for FactorDef ast node */ - FactorDef::FactorDef(Name* name, Double* value, Unit* unit1, Boolean* gt, Unit* unit2) - { - this->name = std::shared_ptr(name); - this->value = std::shared_ptr(value); - this->unit1 = std::shared_ptr(unit1); - this->gt = std::shared_ptr(gt); - this->unit2 = std::shared_ptr(unit2); - } - - /* copy constructor for FactorDef ast node */ - FactorDef::FactorDef(const FactorDef& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - if(obj.unit1) - this->unit1 = std::shared_ptr(obj.unit1->clone()); - if(obj.gt) - this->gt = std::shared_ptr(obj.gt->clone()); - if(obj.unit2) - this->unit2 = std::shared_ptr(obj.unit2->clone()); - if(obj.token) - this->token = std::shared_ptr(obj.token->clone()); - } - - /* visit method for Valence ast node */ - void Valence::visitChildren(Visitor* v) { - type->accept(v); - value->accept(v); - } - - /* constructor for Valence ast node */ - Valence::Valence(Name* type, Double* value) - { - this->type = std::shared_ptr(type); - this->value = std::shared_ptr(value); - } - - /* copy constructor for Valence ast node */ - Valence::Valence(const Valence& obj) - { - if(obj.type) - this->type = std::shared_ptr(obj.type->clone()); - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - } - - void UnitState::visitChildren(Visitor* v) {} - /* constructor for UnitState ast node */ - UnitState::UnitState(UnitStateType value) - : value(value) - { - } - - /* copy constructor for UnitState ast node */ - UnitState::UnitState(const UnitState& obj) - { - this->value = obj.value; - } - - /* visit method for LocalListStatement ast node */ - void LocalListStatement::visitChildren(Visitor* v) { - for(auto& item : this->variables) { - item->accept(v); - } - } - - /* constructor for LocalListStatement ast node */ - LocalListStatement::LocalListStatement(LocalVariableVector variables) - : variables(variables) - { - } - - /* copy constructor for LocalListStatement ast node */ - LocalListStatement::LocalListStatement(const LocalListStatement& obj) - { - for(auto& item : obj.variables) { - this->variables.push_back(std::shared_ptr< LocalVariable>(item->clone())); - } - } - - /* visit method for Model ast node */ - void Model::visitChildren(Visitor* v) { - title->accept(v); - } - - /* constructor for Model ast node */ - Model::Model(String* title) - { - this->title = std::shared_ptr(title); - } - - /* copy constructor for Model ast node */ - Model::Model(const Model& obj) - { - if(obj.title) - this->title = std::shared_ptr(obj.title->clone()); - } - - /* visit method for Define ast node */ - void Define::visitChildren(Visitor* v) { - name->accept(v); - value->accept(v); - } - - /* constructor for Define ast node */ - Define::Define(Name* name, Integer* value) - { - this->name = std::shared_ptr(name); - this->value = std::shared_ptr(value); - } - - /* copy constructor for Define ast node */ - Define::Define(const Define& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - } - - /* visit method for Include ast node */ - void Include::visitChildren(Visitor* v) { - filename->accept(v); - } - - /* constructor for Include ast node */ - Include::Include(String* filename) - { - this->filename = std::shared_ptr(filename); - } - - /* copy constructor for Include ast node */ - Include::Include(const Include& obj) - { - if(obj.filename) - this->filename = std::shared_ptr(obj.filename->clone()); - } - - /* visit method for ParamAssign ast node */ - void ParamAssign::visitChildren(Visitor* v) { - name->accept(v); - if (this->value) { - this->value->accept(v); - } - if (this->unit) { - this->unit->accept(v); - } - if (this->limit) { - this->limit->accept(v); - } - } - - /* constructor for ParamAssign ast node */ - ParamAssign::ParamAssign(Identifier* name, Number* value, Unit* unit, Limits* limit) - { - this->name = std::shared_ptr(name); - this->value = std::shared_ptr(value); - this->unit = std::shared_ptr(unit); - this->limit = std::shared_ptr(limit); - } - - /* copy constructor for ParamAssign ast node */ - ParamAssign::ParamAssign(const ParamAssign& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - if(obj.limit) - this->limit = std::shared_ptr(obj.limit->clone()); - } - - /* visit method for Stepped ast node */ - void Stepped::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->values) { - item->accept(v); - } - unit->accept(v); - } - - /* constructor for Stepped ast node */ - Stepped::Stepped(Name* name, NumberVector values, Unit* unit) - : values(values) - { - this->name = std::shared_ptr(name); - this->unit = std::shared_ptr(unit); - } - - /* copy constructor for Stepped ast node */ - Stepped::Stepped(const Stepped& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.values) { - this->values.push_back(std::shared_ptr< Number>(item->clone())); - } - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - } - - /* visit method for IndependentDef ast node */ - void IndependentDef::visitChildren(Visitor* v) { - if (this->sweep) { - this->sweep->accept(v); - } - name->accept(v); - from->accept(v); - to->accept(v); - with->accept(v); - if (this->opstart) { - this->opstart->accept(v); - } - unit->accept(v); - } - - /* constructor for IndependentDef ast node */ - IndependentDef::IndependentDef(Boolean* sweep, Name* name, Number* from, Number* to, Integer* with, Number* opstart, Unit* unit) - { - this->sweep = std::shared_ptr(sweep); - this->name = std::shared_ptr(name); - this->from = std::shared_ptr(from); - this->to = std::shared_ptr(to); - this->with = std::shared_ptr(with); - this->opstart = std::shared_ptr(opstart); - this->unit = std::shared_ptr(unit); - } - - /* copy constructor for IndependentDef ast node */ - IndependentDef::IndependentDef(const IndependentDef& obj) - { - if(obj.sweep) - this->sweep = std::shared_ptr(obj.sweep->clone()); - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.from) - this->from = std::shared_ptr(obj.from->clone()); - if(obj.to) - this->to = std::shared_ptr(obj.to->clone()); - if(obj.with) - this->with = std::shared_ptr(obj.with->clone()); - if(obj.opstart) - this->opstart = std::shared_ptr(obj.opstart->clone()); - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - } - - /* visit method for DependentDef ast node */ - void DependentDef::visitChildren(Visitor* v) { - name->accept(v); - if (this->index) { - this->index->accept(v); - } - if (this->from) { - this->from->accept(v); - } - if (this->to) { - this->to->accept(v); - } - if (this->opstart) { - this->opstart->accept(v); - } - if (this->unit) { - this->unit->accept(v); - } - if (this->abstol) { - this->abstol->accept(v); - } - } - - /* constructor for DependentDef ast node */ - DependentDef::DependentDef(Identifier* name, Integer* index, Number* from, Number* to, Number* opstart, Unit* unit, Double* abstol) - { - this->name = std::shared_ptr(name); - this->index = std::shared_ptr(index); - this->from = std::shared_ptr(from); - this->to = std::shared_ptr(to); - this->opstart = std::shared_ptr(opstart); - this->unit = std::shared_ptr(unit); - this->abstol = std::shared_ptr(abstol); - } - - /* copy constructor for DependentDef ast node */ - DependentDef::DependentDef(const DependentDef& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.index) - this->index = std::shared_ptr(obj.index->clone()); - if(obj.from) - this->from = std::shared_ptr(obj.from->clone()); - if(obj.to) - this->to = std::shared_ptr(obj.to->clone()); - if(obj.opstart) - this->opstart = std::shared_ptr(obj.opstart->clone()); - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - if(obj.abstol) - this->abstol = std::shared_ptr(obj.abstol->clone()); - } - - /* visit method for PlotDeclaration ast node */ - void PlotDeclaration::visitChildren(Visitor* v) { - for(auto& item : this->pvlist) { - item->accept(v); - } - name->accept(v); - } - - /* constructor for PlotDeclaration ast node */ - PlotDeclaration::PlotDeclaration(PlotVariableVector pvlist, PlotVariable* name) - : pvlist(pvlist) - { - this->name = std::shared_ptr(name); - } - - /* copy constructor for PlotDeclaration ast node */ - PlotDeclaration::PlotDeclaration(const PlotDeclaration& obj) - { - for(auto& item : obj.pvlist) { - this->pvlist.push_back(std::shared_ptr< PlotVariable>(item->clone())); - } - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ConductanceHint ast node */ - void ConductanceHint::visitChildren(Visitor* v) { - conductance->accept(v); - if (this->ion) { - this->ion->accept(v); - } - } - - /* constructor for ConductanceHint ast node */ - ConductanceHint::ConductanceHint(Name* conductance, Name* ion) - { - this->conductance = std::shared_ptr(conductance); - this->ion = std::shared_ptr(ion); - } - - /* copy constructor for ConductanceHint ast node */ - ConductanceHint::ConductanceHint(const ConductanceHint& obj) - { - if(obj.conductance) - this->conductance = std::shared_ptr(obj.conductance->clone()); - if(obj.ion) - this->ion = std::shared_ptr(obj.ion->clone()); - } - - /* visit method for ExpressionStatement ast node */ - void ExpressionStatement::visitChildren(Visitor* v) { - expression->accept(v); - } - - /* constructor for ExpressionStatement ast node */ - ExpressionStatement::ExpressionStatement(Expression* expression) - { - this->expression = std::shared_ptr(expression); - } - - /* copy constructor for ExpressionStatement ast node */ - ExpressionStatement::ExpressionStatement(const ExpressionStatement& obj) - { - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - } - - /* visit method for ProtectStatement ast node */ - void ProtectStatement::visitChildren(Visitor* v) { - expression->accept(v); - } - - /* constructor for ProtectStatement ast node */ - ProtectStatement::ProtectStatement(Expression* expression) - { - this->expression = std::shared_ptr(expression); - } - - /* copy constructor for ProtectStatement ast node */ - ProtectStatement::ProtectStatement(const ProtectStatement& obj) - { - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - } - - /* visit method for FromStatement ast node */ - void FromStatement::visitChildren(Visitor* v) { - name->accept(v); - from->accept(v); - to->accept(v); - if (this->opinc) { - this->opinc->accept(v); - } - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for FromStatement ast node */ - FromStatement::FromStatement(Name* name, Expression* from, Expression* to, Expression* opinc, StatementBlock* statementblock) - { - this->name = std::shared_ptr(name); - this->from = std::shared_ptr(from); - this->to = std::shared_ptr(to); - this->opinc = std::shared_ptr(opinc); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for FromStatement ast node */ - FromStatement::FromStatement(const FromStatement& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.from) - this->from = std::shared_ptr(obj.from->clone()); - if(obj.to) - this->to = std::shared_ptr(obj.to->clone()); - if(obj.opinc) - this->opinc = std::shared_ptr(obj.opinc->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for ForAllStatement ast node */ - void ForAllStatement::visitChildren(Visitor* v) { - name->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for ForAllStatement ast node */ - ForAllStatement::ForAllStatement(Name* name, StatementBlock* statementblock) - { - this->name = std::shared_ptr(name); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for ForAllStatement ast node */ - ForAllStatement::ForAllStatement(const ForAllStatement& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for WhileStatement ast node */ - void WhileStatement::visitChildren(Visitor* v) { - condition->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for WhileStatement ast node */ - WhileStatement::WhileStatement(Expression* condition, StatementBlock* statementblock) - { - this->condition = std::shared_ptr(condition); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for WhileStatement ast node */ - WhileStatement::WhileStatement(const WhileStatement& obj) - { - if(obj.condition) - this->condition = std::shared_ptr(obj.condition->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for IfStatement ast node */ - void IfStatement::visitChildren(Visitor* v) { - condition->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - for(auto& item : this->elseifs) { - item->accept(v); - } - if (this->elses) { - this->elses->accept(v); - } - } - - /* constructor for IfStatement ast node */ - IfStatement::IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementVector elseifs, ElseStatement* elses) - : elseifs(elseifs) - { - this->condition = std::shared_ptr(condition); - this->statementblock = std::shared_ptr(statementblock); - this->elses = std::shared_ptr(elses); - } - - /* copy constructor for IfStatement ast node */ - IfStatement::IfStatement(const IfStatement& obj) - { - if(obj.condition) - this->condition = std::shared_ptr(obj.condition->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - for(auto& item : obj.elseifs) { - this->elseifs.push_back(std::shared_ptr< ElseIfStatement>(item->clone())); - } - if(obj.elses) - this->elses = std::shared_ptr(obj.elses->clone()); - } - - /* visit method for ElseIfStatement ast node */ - void ElseIfStatement::visitChildren(Visitor* v) { - condition->accept(v); - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for ElseIfStatement ast node */ - ElseIfStatement::ElseIfStatement(Expression* condition, StatementBlock* statementblock) - { - this->condition = std::shared_ptr(condition); - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for ElseIfStatement ast node */ - ElseIfStatement::ElseIfStatement(const ElseIfStatement& obj) - { - if(obj.condition) - this->condition = std::shared_ptr(obj.condition->clone()); - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for ElseStatement ast node */ - void ElseStatement::visitChildren(Visitor* v) { - if (this->statementblock) { - this->statementblock->accept(v); - } - } - - /* constructor for ElseStatement ast node */ - ElseStatement::ElseStatement(StatementBlock* statementblock) - { - this->statementblock = std::shared_ptr(statementblock); - } - - /* copy constructor for ElseStatement ast node */ - ElseStatement::ElseStatement(const ElseStatement& obj) - { - if(obj.statementblock) - this->statementblock = std::shared_ptr(obj.statementblock->clone()); - } - - /* visit method for PartialEquation ast node */ - void PartialEquation::visitChildren(Visitor* v) { - prime->accept(v); - name1->accept(v); - name2->accept(v); - name3->accept(v); - } - - /* constructor for PartialEquation ast node */ - PartialEquation::PartialEquation(PrimeName* prime, Name* name1, Name* name2, Name* name3) - { - this->prime = std::shared_ptr(prime); - this->name1 = std::shared_ptr(name1); - this->name2 = std::shared_ptr(name2); - this->name3 = std::shared_ptr(name3); - } - - /* copy constructor for PartialEquation ast node */ - PartialEquation::PartialEquation(const PartialEquation& obj) - { - if(obj.prime) - this->prime = std::shared_ptr(obj.prime->clone()); - if(obj.name1) - this->name1 = std::shared_ptr(obj.name1->clone()); - if(obj.name2) - this->name2 = std::shared_ptr(obj.name2->clone()); - if(obj.name3) - this->name3 = std::shared_ptr(obj.name3->clone()); - } - - /* visit method for PartialBoundary ast node */ - void PartialBoundary::visitChildren(Visitor* v) { - if (this->del) { - this->del->accept(v); - } - name->accept(v); - if (this->index) { - this->index->accept(v); - } - if (this->expression) { - this->expression->accept(v); - } - if (this->name1) { - this->name1->accept(v); - } - if (this->del2) { - this->del2->accept(v); - } - if (this->name2) { - this->name2->accept(v); - } - if (this->name3) { - this->name3->accept(v); - } - } - - /* constructor for PartialBoundary ast node */ - PartialBoundary::PartialBoundary(Name* del, Identifier* name, FirstLastTypeIndex* index, Expression* expression, Name* name1, Name* del2, Name* name2, Name* name3) - { - this->del = std::shared_ptr(del); - this->name = std::shared_ptr(name); - this->index = std::shared_ptr(index); - this->expression = std::shared_ptr(expression); - this->name1 = std::shared_ptr(name1); - this->del2 = std::shared_ptr(del2); - this->name2 = std::shared_ptr(name2); - this->name3 = std::shared_ptr(name3); - } - - /* copy constructor for PartialBoundary ast node */ - PartialBoundary::PartialBoundary(const PartialBoundary& obj) - { - if(obj.del) - this->del = std::shared_ptr(obj.del->clone()); - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.index) - this->index = std::shared_ptr(obj.index->clone()); - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - if(obj.name1) - this->name1 = std::shared_ptr(obj.name1->clone()); - if(obj.del2) - this->del2 = std::shared_ptr(obj.del2->clone()); - if(obj.name2) - this->name2 = std::shared_ptr(obj.name2->clone()); - if(obj.name3) - this->name3 = std::shared_ptr(obj.name3->clone()); - } - - /* visit method for WatchStatement ast node */ - void WatchStatement::visitChildren(Visitor* v) { - for(auto& item : this->statements) { - item->accept(v); - } - } - - /* constructor for WatchStatement ast node */ - WatchStatement::WatchStatement(WatchVector statements) - : statements(statements) - { - } - - /* copy constructor for WatchStatement ast node */ - WatchStatement::WatchStatement(const WatchStatement& obj) - { - for(auto& item : obj.statements) { - this->statements.push_back(std::shared_ptr< Watch>(item->clone())); - } - } - - void MutexLock::visitChildren(Visitor* v) {} - void MutexUnlock::visitChildren(Visitor* v) {} - void Reset::visitChildren(Visitor* v) {} - /* visit method for Sens ast node */ - void Sens::visitChildren(Visitor* v) { - for(auto& item : this->senslist) { - item->accept(v); - } - } - - /* constructor for Sens ast node */ - Sens::Sens(VarNameVector senslist) - : senslist(senslist) - { - } - - /* copy constructor for Sens ast node */ - Sens::Sens(const Sens& obj) - { - for(auto& item : obj.senslist) { - this->senslist.push_back(std::shared_ptr< VarName>(item->clone())); - } - } - - /* visit method for Conserve ast node */ - void Conserve::visitChildren(Visitor* v) { - react->accept(v); - expr->accept(v); - } - - /* constructor for Conserve ast node */ - Conserve::Conserve(Expression* react, Expression* expr) - { - this->react = std::shared_ptr(react); - this->expr = std::shared_ptr(expr); - } - - /* copy constructor for Conserve ast node */ - Conserve::Conserve(const Conserve& obj) - { - if(obj.react) - this->react = std::shared_ptr(obj.react->clone()); - if(obj.expr) - this->expr = std::shared_ptr(obj.expr->clone()); - } - - /* visit method for Compartment ast node */ - void Compartment::visitChildren(Visitor* v) { - if (this->name) { - this->name->accept(v); - } - expression->accept(v); - for(auto& item : this->names) { - item->accept(v); - } - } - - /* constructor for Compartment ast node */ - Compartment::Compartment(Name* name, Expression* expression, NameVector names) - : names(names) - { - this->name = std::shared_ptr(name); - this->expression = std::shared_ptr(expression); - } - - /* copy constructor for Compartment ast node */ - Compartment::Compartment(const Compartment& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - for(auto& item : obj.names) { - this->names.push_back(std::shared_ptr< Name>(item->clone())); - } - } - - /* visit method for LDifuse ast node */ - void LDifuse::visitChildren(Visitor* v) { - if (this->name) { - this->name->accept(v); - } - expression->accept(v); - for(auto& item : this->names) { - item->accept(v); - } - } - - /* constructor for LDifuse ast node */ - LDifuse::LDifuse(Name* name, Expression* expression, NameVector names) - : names(names) - { - this->name = std::shared_ptr(name); - this->expression = std::shared_ptr(expression); - } - - /* copy constructor for LDifuse ast node */ - LDifuse::LDifuse(const LDifuse& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.expression) - this->expression = std::shared_ptr(obj.expression->clone()); - for(auto& item : obj.names) { - this->names.push_back(std::shared_ptr< Name>(item->clone())); - } - } - - /* visit method for ReactionStatement ast node */ - void ReactionStatement::visitChildren(Visitor* v) { - react1->accept(v); - op.accept(v); - if (this->react2) { - this->react2->accept(v); - } - expr1->accept(v); - if (this->expr2) { - this->expr2->accept(v); - } - } - - /* constructor for ReactionStatement ast node */ - ReactionStatement::ReactionStatement(Expression* react1, ReactionOperator op, Expression* react2, Expression* expr1, Expression* expr2) - : op(op) - { - this->react1 = std::shared_ptr(react1); - this->react2 = std::shared_ptr(react2); - this->expr1 = std::shared_ptr(expr1); - this->expr2 = std::shared_ptr(expr2); - } - - /* copy constructor for ReactionStatement ast node */ - ReactionStatement::ReactionStatement(const ReactionStatement& obj) - { - if(obj.react1) - this->react1 = std::shared_ptr(obj.react1->clone()); - this->op = obj.op; - if(obj.react2) - this->react2 = std::shared_ptr(obj.react2->clone()); - if(obj.expr1) - this->expr1 = std::shared_ptr(obj.expr1->clone()); - if(obj.expr2) - this->expr2 = std::shared_ptr(obj.expr2->clone()); - } - - /* visit method for LagStatement ast node */ - void LagStatement::visitChildren(Visitor* v) { - name->accept(v); - byname->accept(v); - } - - /* constructor for LagStatement ast node */ - LagStatement::LagStatement(Identifier* name, Name* byname) - { - this->name = std::shared_ptr(name); - this->byname = std::shared_ptr(byname); - } - - /* copy constructor for LagStatement ast node */ - LagStatement::LagStatement(const LagStatement& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.byname) - this->byname = std::shared_ptr(obj.byname->clone()); - } - - /* visit method for QueueStatement ast node */ - void QueueStatement::visitChildren(Visitor* v) { - qype->accept(v); - name->accept(v); - } - - /* constructor for QueueStatement ast node */ - QueueStatement::QueueStatement(QueueExpressionType* qype, Identifier* name) - { - this->qype = std::shared_ptr(qype); - this->name = std::shared_ptr(name); - } - - /* copy constructor for QueueStatement ast node */ - QueueStatement::QueueStatement(const QueueStatement& obj) - { - if(obj.qype) - this->qype = std::shared_ptr(obj.qype->clone()); - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for ConstantStatement ast node */ - void ConstantStatement::visitChildren(Visitor* v) { - name->accept(v); - value->accept(v); - if (this->unit) { - this->unit->accept(v); - } - } - - /* constructor for ConstantStatement ast node */ - ConstantStatement::ConstantStatement(Name* name, Number* value, Unit* unit) - { - this->name = std::shared_ptr(name); - this->value = std::shared_ptr(value); - this->unit = std::shared_ptr(unit); - } - - /* copy constructor for ConstantStatement ast node */ - ConstantStatement::ConstantStatement(const ConstantStatement& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - if(obj.value) - this->value = std::shared_ptr(obj.value->clone()); - if(obj.unit) - this->unit = std::shared_ptr(obj.unit->clone()); - } - - /* visit method for TableStatement ast node */ - void TableStatement::visitChildren(Visitor* v) { - for(auto& item : this->tablst) { - item->accept(v); - } - for(auto& item : this->dependlst) { - item->accept(v); - } - from->accept(v); - to->accept(v); - with->accept(v); - } - - /* constructor for TableStatement ast node */ - TableStatement::TableStatement(NameVector tablst, NameVector dependlst, Expression* from, Expression* to, Integer* with) - : tablst(tablst), -dependlst(dependlst) - { - this->from = std::shared_ptr(from); - this->to = std::shared_ptr(to); - this->with = std::shared_ptr(with); - } - - /* copy constructor for TableStatement ast node */ - TableStatement::TableStatement(const TableStatement& obj) - { - for(auto& item : obj.tablst) { - this->tablst.push_back(std::shared_ptr< Name>(item->clone())); - } - for(auto& item : obj.dependlst) { - this->dependlst.push_back(std::shared_ptr< Name>(item->clone())); - } - if(obj.from) - this->from = std::shared_ptr(obj.from->clone()); - if(obj.to) - this->to = std::shared_ptr(obj.to->clone()); - if(obj.with) - this->with = std::shared_ptr(obj.with->clone()); - } - - /* visit method for NrnSuffix ast node */ - void NrnSuffix::visitChildren(Visitor* v) { - type->accept(v); - name->accept(v); - } - - /* constructor for NrnSuffix ast node */ - NrnSuffix::NrnSuffix(Name* type, Name* name) - { - this->type = std::shared_ptr(type); - this->name = std::shared_ptr(name); - } - - /* copy constructor for NrnSuffix ast node */ - NrnSuffix::NrnSuffix(const NrnSuffix& obj) - { - if(obj.type) - this->type = std::shared_ptr(obj.type->clone()); - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - } - - /* visit method for NrnUseion ast node */ - void NrnUseion::visitChildren(Visitor* v) { - name->accept(v); - for(auto& item : this->readlist) { - item->accept(v); - } - for(auto& item : this->writelist) { - item->accept(v); - } - if (this->valence) { - this->valence->accept(v); - } - } - - /* constructor for NrnUseion ast node */ - NrnUseion::NrnUseion(Name* name, ReadIonVarVector readlist, WriteIonVarVector writelist, Valence* valence) - : readlist(readlist), -writelist(writelist) - { - this->name = std::shared_ptr(name); - this->valence = std::shared_ptr(valence); - } - - /* copy constructor for NrnUseion ast node */ - NrnUseion::NrnUseion(const NrnUseion& obj) - { - if(obj.name) - this->name = std::shared_ptr(obj.name->clone()); - for(auto& item : obj.readlist) { - this->readlist.push_back(std::shared_ptr< ReadIonVar>(item->clone())); - } - for(auto& item : obj.writelist) { - this->writelist.push_back(std::shared_ptr< WriteIonVar>(item->clone())); - } - if(obj.valence) - this->valence = std::shared_ptr(obj.valence->clone()); - } - - /* visit method for NrnNonspecific ast node */ - void NrnNonspecific::visitChildren(Visitor* v) { - for(auto& item : this->currents) { - item->accept(v); - } - } - - /* constructor for NrnNonspecific ast node */ - NrnNonspecific::NrnNonspecific(NonspeCurVarVector currents) - : currents(currents) - { - } - - /* copy constructor for NrnNonspecific ast node */ - NrnNonspecific::NrnNonspecific(const NrnNonspecific& obj) - { - for(auto& item : obj.currents) { - this->currents.push_back(std::shared_ptr< NonspeCurVar>(item->clone())); - } - } - - /* visit method for NrnElctrodeCurrent ast node */ - void NrnElctrodeCurrent::visitChildren(Visitor* v) { - for(auto& item : this->ecurrents) { - item->accept(v); - } - } - - /* constructor for NrnElctrodeCurrent ast node */ - NrnElctrodeCurrent::NrnElctrodeCurrent(ElectrodeCurVarVector ecurrents) - : ecurrents(ecurrents) - { - } - - /* copy constructor for NrnElctrodeCurrent ast node */ - NrnElctrodeCurrent::NrnElctrodeCurrent(const NrnElctrodeCurrent& obj) - { - for(auto& item : obj.ecurrents) { - this->ecurrents.push_back(std::shared_ptr< ElectrodeCurVar>(item->clone())); - } - } - - /* visit method for NrnSection ast node */ - void NrnSection::visitChildren(Visitor* v) { - for(auto& item : this->sections) { - item->accept(v); - } - } - - /* constructor for NrnSection ast node */ - NrnSection::NrnSection(SectionVarVector sections) - : sections(sections) - { - } - - /* copy constructor for NrnSection ast node */ - NrnSection::NrnSection(const NrnSection& obj) - { - for(auto& item : obj.sections) { - this->sections.push_back(std::shared_ptr< SectionVar>(item->clone())); - } - } - - /* visit method for NrnRange ast node */ - void NrnRange::visitChildren(Visitor* v) { - for(auto& item : this->range_vars) { - item->accept(v); - } - } - - /* constructor for NrnRange ast node */ - NrnRange::NrnRange(RangeVarVector range_vars) - : range_vars(range_vars) - { - } - - /* copy constructor for NrnRange ast node */ - NrnRange::NrnRange(const NrnRange& obj) - { - for(auto& item : obj.range_vars) { - this->range_vars.push_back(std::shared_ptr< RangeVar>(item->clone())); - } - } - - /* visit method for NrnGlobal ast node */ - void NrnGlobal::visitChildren(Visitor* v) { - for(auto& item : this->global_vars) { - item->accept(v); - } - } - - /* constructor for NrnGlobal ast node */ - NrnGlobal::NrnGlobal(GlobalVarVector global_vars) - : global_vars(global_vars) - { - } - - /* copy constructor for NrnGlobal ast node */ - NrnGlobal::NrnGlobal(const NrnGlobal& obj) - { - for(auto& item : obj.global_vars) { - this->global_vars.push_back(std::shared_ptr< GlobalVar>(item->clone())); - } - } - - /* visit method for NrnPointer ast node */ - void NrnPointer::visitChildren(Visitor* v) { - for(auto& item : this->pointers) { - item->accept(v); - } - } - - /* constructor for NrnPointer ast node */ - NrnPointer::NrnPointer(PointerVarVector pointers) - : pointers(pointers) - { - } - - /* copy constructor for NrnPointer ast node */ - NrnPointer::NrnPointer(const NrnPointer& obj) - { - for(auto& item : obj.pointers) { - this->pointers.push_back(std::shared_ptr< PointerVar>(item->clone())); - } - } - - /* visit method for NrnBbcorePtr ast node */ - void NrnBbcorePtr::visitChildren(Visitor* v) { - for(auto& item : this->bbcore_pointers) { - item->accept(v); - } - } - - /* constructor for NrnBbcorePtr ast node */ - NrnBbcorePtr::NrnBbcorePtr(BbcorePointerVarVector bbcore_pointers) - : bbcore_pointers(bbcore_pointers) - { - } - - /* copy constructor for NrnBbcorePtr ast node */ - NrnBbcorePtr::NrnBbcorePtr(const NrnBbcorePtr& obj) - { - for(auto& item : obj.bbcore_pointers) { - this->bbcore_pointers.push_back(std::shared_ptr< BbcorePointerVar>(item->clone())); - } - } - - /* visit method for NrnExternal ast node */ - void NrnExternal::visitChildren(Visitor* v) { - for(auto& item : this->externals) { - item->accept(v); - } - } - - /* constructor for NrnExternal ast node */ - NrnExternal::NrnExternal(ExternVarVector externals) - : externals(externals) - { - } - - /* copy constructor for NrnExternal ast node */ - NrnExternal::NrnExternal(const NrnExternal& obj) - { - for(auto& item : obj.externals) { - this->externals.push_back(std::shared_ptr< ExternVar>(item->clone())); - } - } - - /* visit method for NrnThreadSafe ast node */ - void NrnThreadSafe::visitChildren(Visitor* v) { - for(auto& item : this->threadsafe) { - item->accept(v); - } - } - - /* constructor for NrnThreadSafe ast node */ - NrnThreadSafe::NrnThreadSafe(ThreadsafeVarVector threadsafe) - : threadsafe(threadsafe) - { - } - - /* copy constructor for NrnThreadSafe ast node */ - NrnThreadSafe::NrnThreadSafe(const NrnThreadSafe& obj) - { - for(auto& item : obj.threadsafe) { - this->threadsafe.push_back(std::shared_ptr< ThreadsafeVar>(item->clone())); - } - } - - /* visit method for Verbatim ast node */ - void Verbatim::visitChildren(Visitor* v) { - statement->accept(v); - } - - /* constructor for Verbatim ast node */ - Verbatim::Verbatim(String* statement) - { - this->statement = std::shared_ptr(statement); - } - - /* copy constructor for Verbatim ast node */ - Verbatim::Verbatim(const Verbatim& obj) - { - if(obj.statement) - this->statement = std::shared_ptr(obj.statement->clone()); - } - - /* visit method for Comment ast node */ - void Comment::visitChildren(Visitor* v) { - comment->accept(v); - } - - /* constructor for Comment ast node */ - Comment::Comment(String* comment) - { - this->comment = std::shared_ptr(comment); - } - - /* copy constructor for Comment ast node */ - Comment::Comment(const Comment& obj) - { - if(obj.comment) - this->comment = std::shared_ptr(obj.comment->clone()); - } - - /* visit method for Program ast node */ - void Program::visitChildren(Visitor* v) { - for(auto& item : this->statements) { - item->accept(v); - } - for(auto& item : this->blocks) { - item->accept(v); - } - } - - /* constructor for Program ast node */ - Program::Program(StatementVector statements, BlockVector blocks) - : statements(statements), -blocks(blocks) - { - } - - /* copy constructor for Program ast node */ - Program::Program(const Program& obj) - { - for(auto& item : obj.statements) { - this->statements.push_back(std::shared_ptr< Statement>(item->clone())); - } - for(auto& item : obj.blocks) { - this->blocks.push_back(std::shared_ptr< Block>(item->clone())); - } - } - -} // namespace ast diff --git a/src/nmodl/ast/ast.hpp b/src/nmodl/ast/ast.hpp deleted file mode 100644 index 5aff585413..0000000000 --- a/src/nmodl/ast/ast.hpp +++ /dev/null @@ -1,2717 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "ast/ast_utils.hpp" -#include "lexer/modtoken.hpp" -#include "utils/common_utils.hpp" - -/* all classes representing Abstract Syntax Tree (AST) nodes */ -namespace ast { - - /* forward declarations of AST classes */ - class Statement; - class Expression; - class Block; - class Identifier; - class Number; - class String; - class Integer; - class Float; - class Double; - class Boolean; - class Name; - class PrimeName; - class VarName; - class IndexedName; - class Argument; - class ReactVarName; - class ReadIonVar; - class WriteIonVar; - class NonspeCurVar; - class ElectrodeCurVar; - class SectionVar; - class RangeVar; - class GlobalVar; - class PointerVar; - class BbcorePointerVar; - class ExternVar; - class ThreadsafeVar; - class ParamBlock; - class StepBlock; - class IndependentBlock; - class DependentBlock; - class StateBlock; - class PlotBlock; - class InitialBlock; - class ConstructorBlock; - class DestructorBlock; - class StatementBlock; - class DerivativeBlock; - class LinearBlock; - class NonLinearBlock; - class DiscreteBlock; - class PartialBlock; - class FunctionTableBlock; - class FunctionBlock; - class ProcedureBlock; - class NetReceiveBlock; - class SolveBlock; - class BreakpointBlock; - class TerminalBlock; - class BeforeBlock; - class AfterBlock; - class BABlock; - class ForNetcon; - class KineticBlock; - class MatchBlock; - class UnitBlock; - class ConstantBlock; - class NeuronBlock; - class Unit; - class DoubleUnit; - class LocalVariable; - class Limits; - class NumberRange; - class PlotVariable; - class BinaryOperator; - class UnaryOperator; - class ReactionOperator; - class BinaryExpression; - class UnaryExpression; - class NonLinEuation; - class LinEquation; - class FunctionCall; - class FirstLastTypeIndex; - class Watch; - class QueueExpressionType; - class Match; - class BABlockType; - class UnitDef; - class FactorDef; - class Valence; - class UnitState; - class LocalListStatement; - class Model; - class Define; - class Include; - class ParamAssign; - class Stepped; - class IndependentDef; - class DependentDef; - class PlotDeclaration; - class ConductanceHint; - class ExpressionStatement; - class ProtectStatement; - class FromStatement; - class ForAllStatement; - class WhileStatement; - class IfStatement; - class ElseIfStatement; - class ElseStatement; - class PartialEquation; - class PartialBoundary; - class WatchStatement; - class MutexLock; - class MutexUnlock; - class Reset; - class Sens; - class Conserve; - class Compartment; - class LDifuse; - class ReactionStatement; - class LagStatement; - class QueueStatement; - class ConstantStatement; - class TableStatement; - class NrnSuffix; - class NrnUseion; - class NrnNonspecific; - class NrnElctrodeCurrent; - class NrnSection; - class NrnRange; - class NrnGlobal; - class NrnPointer; - class NrnBbcorePtr; - class NrnExternal; - class NrnThreadSafe; - class Verbatim; - class Comment; - class Program; - - /* std::vector for convenience */ - using StatementVector = std::vector>; - using ExpressionVector = std::vector>; - using BlockVector = std::vector>; - using IdentifierVector = std::vector>; - using NumberVector = std::vector>; - using StringVector = std::vector>; - using IntegerVector = std::vector>; - using FloatVector = std::vector>; - using DoubleVector = std::vector>; - using BooleanVector = std::vector>; - using NameVector = std::vector>; - using PrimeNameVector = std::vector>; - using VarNameVector = std::vector>; - using IndexedNameVector = std::vector>; - using ArgumentVector = std::vector>; - using ReactVarNameVector = std::vector>; - using ReadIonVarVector = std::vector>; - using WriteIonVarVector = std::vector>; - using NonspeCurVarVector = std::vector>; - using ElectrodeCurVarVector = std::vector>; - using SectionVarVector = std::vector>; - using RangeVarVector = std::vector>; - using GlobalVarVector = std::vector>; - using PointerVarVector = std::vector>; - using BbcorePointerVarVector = std::vector>; - using ExternVarVector = std::vector>; - using ThreadsafeVarVector = std::vector>; - using ParamBlockVector = std::vector>; - using StepBlockVector = std::vector>; - using IndependentBlockVector = std::vector>; - using DependentBlockVector = std::vector>; - using StateBlockVector = std::vector>; - using PlotBlockVector = std::vector>; - using InitialBlockVector = std::vector>; - using ConstructorBlockVector = std::vector>; - using DestructorBlockVector = std::vector>; - using StatementBlockVector = std::vector>; - using DerivativeBlockVector = std::vector>; - using LinearBlockVector = std::vector>; - using NonLinearBlockVector = std::vector>; - using DiscreteBlockVector = std::vector>; - using PartialBlockVector = std::vector>; - using FunctionTableBlockVector = std::vector>; - using FunctionBlockVector = std::vector>; - using ProcedureBlockVector = std::vector>; - using NetReceiveBlockVector = std::vector>; - using SolveBlockVector = std::vector>; - using BreakpointBlockVector = std::vector>; - using TerminalBlockVector = std::vector>; - using BeforeBlockVector = std::vector>; - using AfterBlockVector = std::vector>; - using BABlockVector = std::vector>; - using ForNetconVector = std::vector>; - using KineticBlockVector = std::vector>; - using MatchBlockVector = std::vector>; - using UnitBlockVector = std::vector>; - using ConstantBlockVector = std::vector>; - using NeuronBlockVector = std::vector>; - using UnitVector = std::vector>; - using DoubleUnitVector = std::vector>; - using LocalVariableVector = std::vector>; - using LimitsVector = std::vector>; - using NumberRangeVector = std::vector>; - using PlotVariableVector = std::vector>; - using BinaryOperatorVector = std::vector>; - using UnaryOperatorVector = std::vector>; - using ReactionOperatorVector = std::vector>; - using BinaryExpressionVector = std::vector>; - using UnaryExpressionVector = std::vector>; - using NonLinEuationVector = std::vector>; - using LinEquationVector = std::vector>; - using FunctionCallVector = std::vector>; - using FirstLastTypeIndexVector = std::vector>; - using WatchVector = std::vector>; - using QueueExpressionTypeVector = std::vector>; - using MatchVector = std::vector>; - using BABlockTypeVector = std::vector>; - using UnitDefVector = std::vector>; - using FactorDefVector = std::vector>; - using ValenceVector = std::vector>; - using UnitStateVector = std::vector>; - using LocalListStatementVector = std::vector>; - using ModelVector = std::vector>; - using DefineVector = std::vector>; - using IncludeVector = std::vector>; - using ParamAssignVector = std::vector>; - using SteppedVector = std::vector>; - using IndependentDefVector = std::vector>; - using DependentDefVector = std::vector>; - using PlotDeclarationVector = std::vector>; - using ConductanceHintVector = std::vector>; - using ExpressionStatementVector = std::vector>; - using ProtectStatementVector = std::vector>; - using FromStatementVector = std::vector>; - using ForAllStatementVector = std::vector>; - using WhileStatementVector = std::vector>; - using IfStatementVector = std::vector>; - using ElseIfStatementVector = std::vector>; - using ElseStatementVector = std::vector>; - using PartialEquationVector = std::vector>; - using PartialBoundaryVector = std::vector>; - using WatchStatementVector = std::vector>; - using MutexLockVector = std::vector>; - using MutexUnlockVector = std::vector>; - using ResetVector = std::vector>; - using SensVector = std::vector>; - using ConserveVector = std::vector>; - using CompartmentVector = std::vector>; - using LDifuseVector = std::vector>; - using ReactionStatementVector = std::vector>; - using LagStatementVector = std::vector>; - using QueueStatementVector = std::vector>; - using ConstantStatementVector = std::vector>; - using TableStatementVector = std::vector>; - using NrnSuffixVector = std::vector>; - using NrnUseionVector = std::vector>; - using NrnNonspecificVector = std::vector>; - using NrnElctrodeCurrentVector = std::vector>; - using NrnSectionVector = std::vector>; - using NrnRangeVector = std::vector>; - using NrnGlobalVector = std::vector>; - using NrnPointerVector = std::vector>; - using NrnBbcorePtrVector = std::vector>; - using NrnExternalVector = std::vector>; - using NrnThreadSafeVector = std::vector>; - using VerbatimVector = std::vector>; - using CommentVector = std::vector>; - using ProgramVector = std::vector>; - using statement_ptr = Statement*; - using expression_ptr = Expression*; - using block_ptr = Block*; - using identifier_ptr = Identifier*; - using number_ptr = Number*; - using string_ptr = String*; - using integer_ptr = Integer*; - using name_ptr = Name*; - using float_ptr = Float*; - using double_ptr = Double*; - using boolean_ptr = Boolean*; - using primename_ptr = PrimeName*; - using varname_ptr = VarName*; - using indexedname_ptr = IndexedName*; - using argument_ptr = Argument*; - using unit_ptr = Unit*; - using reactvarname_ptr = ReactVarName*; - using readionvar_ptr = ReadIonVar*; - using writeionvar_ptr = WriteIonVar*; - using nonspecurvar_ptr = NonspeCurVar*; - using electrodecurvar_ptr = ElectrodeCurVar*; - using sectionvar_ptr = SectionVar*; - using rangevar_ptr = RangeVar*; - using globalvar_ptr = GlobalVar*; - using pointervar_ptr = PointerVar*; - using bbcorepointervar_ptr = BbcorePointerVar*; - using externvar_ptr = ExternVar*; - using threadsafevar_ptr = ThreadsafeVar*; - using paramblock_ptr = ParamBlock*; - using paramassign_list = ParamAssignVector; - using stepblock_ptr = StepBlock*; - using stepped_list = SteppedVector; - using independentblock_ptr = IndependentBlock*; - using independentdef_list = IndependentDefVector; - using dependentblock_ptr = DependentBlock*; - using dependentdef_list = DependentDefVector; - using stateblock_ptr = StateBlock*; - using plotblock_ptr = PlotBlock*; - using plotdeclaration_ptr = PlotDeclaration*; - using initialblock_ptr = InitialBlock*; - using statementblock_ptr = StatementBlock*; - using constructorblock_ptr = ConstructorBlock*; - using destructorblock_ptr = DestructorBlock*; - using statement_list = StatementVector; - using derivativeblock_ptr = DerivativeBlock*; - using linearblock_ptr = LinearBlock*; - using name_list = NameVector; - using nonlinearblock_ptr = NonLinearBlock*; - using discreteblock_ptr = DiscreteBlock*; - using partialblock_ptr = PartialBlock*; - using functiontableblock_ptr = FunctionTableBlock*; - using argument_list = ArgumentVector; - using functionblock_ptr = FunctionBlock*; - using procedureblock_ptr = ProcedureBlock*; - using netreceiveblock_ptr = NetReceiveBlock*; - using solveblock_ptr = SolveBlock*; - using breakpointblock_ptr = BreakpointBlock*; - using terminalblock_ptr = TerminalBlock*; - using beforeblock_ptr = BeforeBlock*; - using bablock_ptr = BABlock*; - using afterblock_ptr = AfterBlock*; - using bablocktype_ptr = BABlockType*; - using fornetcon_ptr = ForNetcon*; - using kineticblock_ptr = KineticBlock*; - using matchblock_ptr = MatchBlock*; - using match_list = MatchVector; - using unitblock_ptr = UnitBlock*; - using expression_list = ExpressionVector; - using constantblock_ptr = ConstantBlock*; - using constantstatement_list = ConstantStatementVector; - using neuronblock_ptr = NeuronBlock*; - using doubleunit_ptr = DoubleUnit*; - using localvariable_ptr = LocalVariable*; - using limits_ptr = Limits*; - using numberrange_ptr = NumberRange*; - using plotvariable_ptr = PlotVariable*; - using binaryoperator_ptr = BinaryOperator; - using unaryoperator_ptr = UnaryOperator; - using reactionoperator_ptr = ReactionOperator; - using binaryexpression_ptr = BinaryExpression*; - using unaryexpression_ptr = UnaryExpression*; - using nonlineuation_ptr = NonLinEuation*; - using linequation_ptr = LinEquation*; - using functioncall_ptr = FunctionCall*; - using firstlasttypeindex_ptr = FirstLastTypeIndex*; - using watch_ptr = Watch*; - using queueexpressiontype_ptr = QueueExpressionType*; - using match_ptr = Match*; - using unitdef_ptr = UnitDef*; - using factordef_ptr = FactorDef*; - using valence_ptr = Valence*; - using unitstate_ptr = UnitState*; - using localliststatement_ptr = LocalListStatement*; - using localvariable_list = LocalVariableVector; - using model_ptr = Model*; - using define_ptr = Define*; - using include_ptr = Include*; - using paramassign_ptr = ParamAssign*; - using stepped_ptr = Stepped*; - using number_list = NumberVector; - using independentdef_ptr = IndependentDef*; - using dependentdef_ptr = DependentDef*; - using plotvariable_list = PlotVariableVector; - using conductancehint_ptr = ConductanceHint*; - using expressionstatement_ptr = ExpressionStatement*; - using protectstatement_ptr = ProtectStatement*; - using fromstatement_ptr = FromStatement*; - using forallstatement_ptr = ForAllStatement*; - using whilestatement_ptr = WhileStatement*; - using ifstatement_ptr = IfStatement*; - using elseifstatement_list = ElseIfStatementVector; - using elsestatement_ptr = ElseStatement*; - using elseifstatement_ptr = ElseIfStatement*; - using partialequation_ptr = PartialEquation*; - using partialboundary_ptr = PartialBoundary*; - using watchstatement_ptr = WatchStatement*; - using watch_list = WatchVector; - using mutexlock_ptr = MutexLock*; - using mutexunlock_ptr = MutexUnlock*; - using reset_ptr = Reset*; - using sens_ptr = Sens*; - using varname_list = VarNameVector; - using conserve_ptr = Conserve*; - using compartment_ptr = Compartment*; - using ldifuse_ptr = LDifuse*; - using reactionstatement_ptr = ReactionStatement*; - using lagstatement_ptr = LagStatement*; - using queuestatement_ptr = QueueStatement*; - using constantstatement_ptr = ConstantStatement*; - using tablestatement_ptr = TableStatement*; - using nrnsuffix_ptr = NrnSuffix*; - using nrnuseion_ptr = NrnUseion*; - using readionvar_list = ReadIonVarVector; - using writeionvar_list = WriteIonVarVector; - using nrnnonspecific_ptr = NrnNonspecific*; - using nonspecurvar_list = NonspeCurVarVector; - using nrnelctrodecurrent_ptr = NrnElctrodeCurrent*; - using electrodecurvar_list = ElectrodeCurVarVector; - using nrnsection_ptr = NrnSection*; - using sectionvar_list = SectionVarVector; - using nrnrange_ptr = NrnRange*; - using rangevar_list = RangeVarVector; - using nrnglobal_ptr = NrnGlobal*; - using globalvar_list = GlobalVarVector; - using nrnpointer_ptr = NrnPointer*; - using pointervar_list = PointerVarVector; - using nrnbbcoreptr_ptr = NrnBbcorePtr*; - using bbcorepointervar_list = BbcorePointerVarVector; - using nrnexternal_ptr = NrnExternal*; - using externvar_list = ExternVarVector; - using nrnthreadsafe_ptr = NrnThreadSafe*; - using threadsafevar_list = ThreadsafeVarVector; - using verbatim_ptr = Verbatim*; - using comment_ptr = Comment*; - using program_ptr = Program*; - using block_list = BlockVector; - - #include - - /* Define all AST nodes */ - - class Statement : public AST { - public: - virtual ~Statement() {} - - virtual void visitChildren (Visitor* v) ; - virtual void accept(Visitor* v) { v->visitStatement(this); } - virtual std::string getType() { return "Statement"; } - virtual Statement* clone() { return new Statement(*this); } - }; - - class Expression : public AST { - public: - virtual ~Expression() {} - - virtual void visitChildren (Visitor* v) ; - virtual void accept(Visitor* v) { v->visitExpression(this); } - virtual std::string getType() { return "Expression"; } - virtual Expression* clone() { return new Expression(*this); } - }; - - class Block : public Expression { - public: - virtual ~Block() {} - - virtual void visitChildren (Visitor* v) ; - virtual void accept(Visitor* v) { v->visitBlock(this); } - virtual std::string getType() { return "Block"; } - virtual Block* clone() { return new Block(*this); } - }; - - class Identifier : public Expression { - public: - virtual ~Identifier() {} - - virtual void visitChildren (Visitor* v) ; - virtual void accept(Visitor* v) { v->visitIdentifier(this); } - virtual std::string getType() { return "Identifier"; } - virtual Identifier* clone() { return new Identifier(*this); } - virtual void setName(std::string name) { std::cout << "ERROR : setName() not implemented! "; abort(); } - }; - - class Number : public Expression { - public: - virtual ~Number() {} - - virtual void visitChildren (Visitor* v) ; - virtual void accept(Visitor* v) { v->visitNumber(this); } - virtual std::string getType() { return "Number"; } - virtual Number* clone() { return new Number(*this); } - virtual void negate() { std::cout << "ERROR : negate() not implemented! "; abort(); } - }; - - class String : public Expression { - public: - /* member variables */ - std::string value; - std::shared_ptr token; - /* constructors */ - String(std::string value); - String(const String& obj); - virtual ~String() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitString(this); } - std::string getType() override { return "String"; } - String* clone() override { return new String(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - std::string eval() { return value; } - void set(std::string _value) { value = _value; } - }; - - class Integer : public Number { - public: - /* member variables */ - int value; - std::shared_ptr macroname; - std::shared_ptr token; - /* constructors */ - Integer(int value, Name* macroname); - Integer(const Integer& obj); - virtual ~Integer() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitInteger(this); } - std::string getType() override { return "Integer"; } - Integer* clone() override { return new Integer(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void negate() override { value = -value; } - int eval() { return value; } - void set(int _value) { value = _value; } - }; - - class Float : public Number { - public: - /* member variables */ - float value; - /* constructors */ - Float(float value); - Float(const Float& obj); - virtual ~Float() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFloat(this); } - std::string getType() override { return "Float"; } - Float* clone() override { return new Float(*this); } - void negate() override { value = -value; } - float eval() { return value; } - void set(float _value) { value = _value; } - }; - - class Double : public Number { - public: - /* member variables */ - double value; - std::shared_ptr token; - /* constructors */ - Double(double value); - Double(const Double& obj); - virtual ~Double() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDouble(this); } - std::string getType() override { return "Double"; } - Double* clone() override { return new Double(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void negate() override { value = -value; } - double eval() { return value; } - void set(double _value) { value = _value; } - }; - - class Boolean : public Number { - public: - /* member variables */ - int value; - /* constructors */ - Boolean(int value); - Boolean(const Boolean& obj); - virtual ~Boolean() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBoolean(this); } - std::string getType() override { return "Boolean"; } - Boolean* clone() override { return new Boolean(*this); } - void negate() override { value = !value; } - bool eval() { return value; } - void set(bool _value) { value = _value; } - }; - - class Name : public Identifier { - public: - /* member variables */ - std::shared_ptr value; - std::shared_ptr token; - /* constructors */ - Name(String* value); - Name(const Name& obj); - virtual ~Name() {} - - std::string getName() override { return value->eval(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitName(this); } - std::string getType() override { return "Name"; } - Name* clone() override { return new Name(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setName(std::string name) override { value->set(name); } - }; - - class PrimeName : public Identifier { - public: - /* member variables */ - std::shared_ptr value; - std::shared_ptr order; - std::shared_ptr token; - /* constructors */ - PrimeName(String* value, Integer* order); - PrimeName(const PrimeName& obj); - virtual ~PrimeName() {} - - std::string getName() override { return value->eval(); } - int getOrder() { return order->eval(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPrimeName(this); } - std::string getType() override { return "PrimeName"; } - PrimeName* clone() override { return new PrimeName(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - }; - - class VarName : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr at_index; - /* constructors */ - VarName(Identifier* name, Integer* at_index); - VarName(const VarName& obj); - virtual ~VarName() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitVarName(this); } - std::string getType() override { return "VarName"; } - VarName* clone() override { return new VarName(*this); } - }; - - class IndexedName : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr index; - /* constructors */ - IndexedName(Identifier* name, Expression* index); - IndexedName(const IndexedName& obj); - virtual ~IndexedName() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitIndexedName(this); } - std::string getType() override { return "IndexedName"; } - IndexedName* clone() override { return new IndexedName(*this); } - }; - - class Argument : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr unit; - /* constructors */ - Argument(Identifier* name, Unit* unit); - Argument(const Argument& obj); - virtual ~Argument() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitArgument(this); } - std::string getType() override { return "Argument"; } - Argument* clone() override { return new Argument(*this); } - }; - - class ReactVarName : public Identifier { - public: - /* member variables */ - std::shared_ptr value; - std::shared_ptr name; - /* constructors */ - ReactVarName(Integer* value, VarName* name); - ReactVarName(const ReactVarName& obj); - virtual ~ReactVarName() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitReactVarName(this); } - std::string getType() override { return "ReactVarName"; } - ReactVarName* clone() override { return new ReactVarName(*this); } - }; - - class ReadIonVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - ReadIonVar(Name* name); - ReadIonVar(const ReadIonVar& obj); - virtual ~ReadIonVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitReadIonVar(this); } - std::string getType() override { return "ReadIonVar"; } - ReadIonVar* clone() override { return new ReadIonVar(*this); } - }; - - class WriteIonVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - WriteIonVar(Name* name); - WriteIonVar(const WriteIonVar& obj); - virtual ~WriteIonVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitWriteIonVar(this); } - std::string getType() override { return "WriteIonVar"; } - WriteIonVar* clone() override { return new WriteIonVar(*this); } - }; - - class NonspeCurVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - NonspeCurVar(Name* name); - NonspeCurVar(const NonspeCurVar& obj); - virtual ~NonspeCurVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNonspeCurVar(this); } - std::string getType() override { return "NonspeCurVar"; } - NonspeCurVar* clone() override { return new NonspeCurVar(*this); } - }; - - class ElectrodeCurVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - ElectrodeCurVar(Name* name); - ElectrodeCurVar(const ElectrodeCurVar& obj); - virtual ~ElectrodeCurVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitElectrodeCurVar(this); } - std::string getType() override { return "ElectrodeCurVar"; } - ElectrodeCurVar* clone() override { return new ElectrodeCurVar(*this); } - }; - - class SectionVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - SectionVar(Name* name); - SectionVar(const SectionVar& obj); - virtual ~SectionVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitSectionVar(this); } - std::string getType() override { return "SectionVar"; } - SectionVar* clone() override { return new SectionVar(*this); } - }; - - class RangeVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - RangeVar(Name* name); - RangeVar(const RangeVar& obj); - virtual ~RangeVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitRangeVar(this); } - std::string getType() override { return "RangeVar"; } - RangeVar* clone() override { return new RangeVar(*this); } - }; - - class GlobalVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - GlobalVar(Name* name); - GlobalVar(const GlobalVar& obj); - virtual ~GlobalVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitGlobalVar(this); } - std::string getType() override { return "GlobalVar"; } - GlobalVar* clone() override { return new GlobalVar(*this); } - }; - - class PointerVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - PointerVar(Name* name); - PointerVar(const PointerVar& obj); - virtual ~PointerVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPointerVar(this); } - std::string getType() override { return "PointerVar"; } - PointerVar* clone() override { return new PointerVar(*this); } - }; - - class BbcorePointerVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - BbcorePointerVar(Name* name); - BbcorePointerVar(const BbcorePointerVar& obj); - virtual ~BbcorePointerVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBbcorePointerVar(this); } - std::string getType() override { return "BbcorePointerVar"; } - BbcorePointerVar* clone() override { return new BbcorePointerVar(*this); } - }; - - class ExternVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - ExternVar(Name* name); - ExternVar(const ExternVar& obj); - virtual ~ExternVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitExternVar(this); } - std::string getType() override { return "ExternVar"; } - ExternVar* clone() override { return new ExternVar(*this); } - }; - - class ThreadsafeVar : public Identifier { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - ThreadsafeVar(Name* name); - ThreadsafeVar(const ThreadsafeVar& obj); - virtual ~ThreadsafeVar() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitThreadsafeVar(this); } - std::string getType() override { return "ThreadsafeVar"; } - ThreadsafeVar* clone() override { return new ThreadsafeVar(*this); } - }; - - class ParamBlock : public Block { - public: - /* member variables */ - ParamAssignVector statements; - void* symtab = nullptr; - - /* constructors */ - ParamBlock(ParamAssignVector statements); - ParamBlock(const ParamBlock& obj); - virtual ~ParamBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitParamBlock(this); } - std::string getType() override { return "ParamBlock"; } - ParamBlock* clone() override { return new ParamBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class StepBlock : public Block { - public: - /* member variables */ - SteppedVector statements; - void* symtab = nullptr; - - /* constructors */ - StepBlock(SteppedVector statements); - StepBlock(const StepBlock& obj); - virtual ~StepBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitStepBlock(this); } - std::string getType() override { return "StepBlock"; } - StepBlock* clone() override { return new StepBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class IndependentBlock : public Block { - public: - /* member variables */ - IndependentDefVector definitions; - void* symtab = nullptr; - - /* constructors */ - IndependentBlock(IndependentDefVector definitions); - IndependentBlock(const IndependentBlock& obj); - virtual ~IndependentBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitIndependentBlock(this); } - std::string getType() override { return "IndependentBlock"; } - IndependentBlock* clone() override { return new IndependentBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class DependentBlock : public Block { - public: - /* member variables */ - DependentDefVector definitions; - void* symtab = nullptr; - - /* constructors */ - DependentBlock(DependentDefVector definitions); - DependentBlock(const DependentBlock& obj); - virtual ~DependentBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDependentBlock(this); } - std::string getType() override { return "DependentBlock"; } - DependentBlock* clone() override { return new DependentBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class StateBlock : public Block { - public: - /* member variables */ - DependentDefVector definitions; - void* symtab = nullptr; - - /* constructors */ - StateBlock(DependentDefVector definitions); - StateBlock(const StateBlock& obj); - virtual ~StateBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitStateBlock(this); } - std::string getType() override { return "StateBlock"; } - StateBlock* clone() override { return new StateBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class PlotBlock : public Block { - public: - /* member variables */ - std::shared_ptr plot; - void* symtab = nullptr; - - /* constructors */ - PlotBlock(PlotDeclaration* plot); - PlotBlock(const PlotBlock& obj); - virtual ~PlotBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPlotBlock(this); } - std::string getType() override { return "PlotBlock"; } - PlotBlock* clone() override { return new PlotBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class InitialBlock : public Block { - public: - /* member variables */ - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - InitialBlock(StatementBlock* statementblock); - InitialBlock(const InitialBlock& obj); - virtual ~InitialBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitInitialBlock(this); } - std::string getType() override { return "InitialBlock"; } - InitialBlock* clone() override { return new InitialBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class ConstructorBlock : public Block { - public: - /* member variables */ - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - ConstructorBlock(StatementBlock* statementblock); - ConstructorBlock(const ConstructorBlock& obj); - virtual ~ConstructorBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitConstructorBlock(this); } - std::string getType() override { return "ConstructorBlock"; } - ConstructorBlock* clone() override { return new ConstructorBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class DestructorBlock : public Block { - public: - /* member variables */ - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - DestructorBlock(StatementBlock* statementblock); - DestructorBlock(const DestructorBlock& obj); - virtual ~DestructorBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDestructorBlock(this); } - std::string getType() override { return "DestructorBlock"; } - DestructorBlock* clone() override { return new DestructorBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class StatementBlock : public Block { - public: - /* member variables */ - StatementVector statements; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - StatementBlock(StatementVector statements); - StatementBlock(const StatementBlock& obj); - virtual ~StatementBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitStatementBlock(this); } - std::string getType() override { return "StatementBlock"; } - StatementBlock* clone() override { return new StatementBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class DerivativeBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - DerivativeBlock(Name* name, StatementBlock* statementblock); - DerivativeBlock(const DerivativeBlock& obj); - virtual ~DerivativeBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDerivativeBlock(this); } - std::string getType() override { return "DerivativeBlock"; } - DerivativeBlock* clone() override { return new DerivativeBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class LinearBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - NameVector solvefor; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - LinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); - LinearBlock(const LinearBlock& obj); - virtual ~LinearBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLinearBlock(this); } - std::string getType() override { return "LinearBlock"; } - LinearBlock* clone() override { return new LinearBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class NonLinearBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - NameVector solvefor; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - NonLinearBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); - NonLinearBlock(const NonLinearBlock& obj); - virtual ~NonLinearBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNonLinearBlock(this); } - std::string getType() override { return "NonLinearBlock"; } - NonLinearBlock* clone() override { return new NonLinearBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class DiscreteBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - DiscreteBlock(Name* name, StatementBlock* statementblock); - DiscreteBlock(const DiscreteBlock& obj); - virtual ~DiscreteBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDiscreteBlock(this); } - std::string getType() override { return "DiscreteBlock"; } - DiscreteBlock* clone() override { return new DiscreteBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class PartialBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - PartialBlock(Name* name, StatementBlock* statementblock); - PartialBlock(const PartialBlock& obj); - virtual ~PartialBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPartialBlock(this); } - std::string getType() override { return "PartialBlock"; } - PartialBlock* clone() override { return new PartialBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class FunctionTableBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - ArgumentVector arguments; - std::shared_ptr unit; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - FunctionTableBlock(Name* name, ArgumentVector arguments, Unit* unit); - FunctionTableBlock(const FunctionTableBlock& obj); - virtual ~FunctionTableBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFunctionTableBlock(this); } - std::string getType() override { return "FunctionTableBlock"; } - FunctionTableBlock* clone() override { return new FunctionTableBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class FunctionBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - ArgumentVector arguments; - std::shared_ptr unit; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - FunctionBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock); - FunctionBlock(const FunctionBlock& obj); - virtual ~FunctionBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFunctionBlock(this); } - std::string getType() override { return "FunctionBlock"; } - FunctionBlock* clone() override { return new FunctionBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class ProcedureBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - ArgumentVector arguments; - std::shared_ptr unit; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - ProcedureBlock(Name* name, ArgumentVector arguments, Unit* unit, StatementBlock* statementblock); - ProcedureBlock(const ProcedureBlock& obj); - virtual ~ProcedureBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitProcedureBlock(this); } - std::string getType() override { return "ProcedureBlock"; } - ProcedureBlock* clone() override { return new ProcedureBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class NetReceiveBlock : public Block { - public: - /* member variables */ - ArgumentVector arguments; - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - NetReceiveBlock(ArgumentVector arguments, StatementBlock* statementblock); - NetReceiveBlock(const NetReceiveBlock& obj); - virtual ~NetReceiveBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNetReceiveBlock(this); } - std::string getType() override { return "NetReceiveBlock"; } - NetReceiveBlock* clone() override { return new NetReceiveBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class SolveBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr method; - std::shared_ptr ifsolerr; - void* symtab = nullptr; - - /* constructors */ - SolveBlock(Name* name, Name* method, StatementBlock* ifsolerr); - SolveBlock(const SolveBlock& obj); - virtual ~SolveBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitSolveBlock(this); } - std::string getType() override { return "SolveBlock"; } - SolveBlock* clone() override { return new SolveBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class BreakpointBlock : public Block { - public: - /* member variables */ - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - BreakpointBlock(StatementBlock* statementblock); - BreakpointBlock(const BreakpointBlock& obj); - virtual ~BreakpointBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBreakpointBlock(this); } - std::string getType() override { return "BreakpointBlock"; } - BreakpointBlock* clone() override { return new BreakpointBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class TerminalBlock : public Block { - public: - /* member variables */ - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - TerminalBlock(StatementBlock* statementblock); - TerminalBlock(const TerminalBlock& obj); - virtual ~TerminalBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitTerminalBlock(this); } - std::string getType() override { return "TerminalBlock"; } - TerminalBlock* clone() override { return new TerminalBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class BeforeBlock : public Block { - public: - /* member variables */ - std::shared_ptr block; - void* symtab = nullptr; - - /* constructors */ - BeforeBlock(BABlock* block); - BeforeBlock(const BeforeBlock& obj); - virtual ~BeforeBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBeforeBlock(this); } - std::string getType() override { return "BeforeBlock"; } - BeforeBlock* clone() override { return new BeforeBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class AfterBlock : public Block { - public: - /* member variables */ - std::shared_ptr block; - void* symtab = nullptr; - - /* constructors */ - AfterBlock(BABlock* block); - AfterBlock(const AfterBlock& obj); - virtual ~AfterBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitAfterBlock(this); } - std::string getType() override { return "AfterBlock"; } - AfterBlock* clone() override { return new AfterBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class BABlock : public Block { - public: - /* member variables */ - std::shared_ptr type; - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - BABlock(BABlockType* type, StatementBlock* statementblock); - BABlock(const BABlock& obj); - virtual ~BABlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBABlock(this); } - std::string getType() override { return "BABlock"; } - BABlock* clone() override { return new BABlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class ForNetcon : public Block { - public: - /* member variables */ - ArgumentVector arguments; - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - ForNetcon(ArgumentVector arguments, StatementBlock* statementblock); - ForNetcon(const ForNetcon& obj); - virtual ~ForNetcon() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitForNetcon(this); } - std::string getType() override { return "ForNetcon"; } - ForNetcon* clone() override { return new ForNetcon(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class KineticBlock : public Block { - public: - /* member variables */ - std::shared_ptr name; - NameVector solvefor; - std::shared_ptr statementblock; - std::shared_ptr token; - void* symtab = nullptr; - - /* constructors */ - KineticBlock(Name* name, NameVector solvefor, StatementBlock* statementblock); - KineticBlock(const KineticBlock& obj); - virtual ~KineticBlock() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitKineticBlock(this); } - std::string getType() override { return "KineticBlock"; } - KineticBlock* clone() override { return new KineticBlock(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class MatchBlock : public Block { - public: - /* member variables */ - MatchVector matchs; - void* symtab = nullptr; - - /* constructors */ - MatchBlock(MatchVector matchs); - MatchBlock(const MatchBlock& obj); - virtual ~MatchBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitMatchBlock(this); } - std::string getType() override { return "MatchBlock"; } - MatchBlock* clone() override { return new MatchBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class UnitBlock : public Block { - public: - /* member variables */ - ExpressionVector definitions; - void* symtab = nullptr; - - /* constructors */ - UnitBlock(ExpressionVector definitions); - UnitBlock(const UnitBlock& obj); - virtual ~UnitBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitUnitBlock(this); } - std::string getType() override { return "UnitBlock"; } - UnitBlock* clone() override { return new UnitBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class ConstantBlock : public Block { - public: - /* member variables */ - ConstantStatementVector statements; - void* symtab = nullptr; - - /* constructors */ - ConstantBlock(ConstantStatementVector statements); - ConstantBlock(const ConstantBlock& obj); - virtual ~ConstantBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitConstantBlock(this); } - std::string getType() override { return "ConstantBlock"; } - ConstantBlock* clone() override { return new ConstantBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class NeuronBlock : public Block { - public: - /* member variables */ - std::shared_ptr statementblock; - void* symtab = nullptr; - - /* constructors */ - NeuronBlock(StatementBlock* statementblock); - NeuronBlock(const NeuronBlock& obj); - virtual ~NeuronBlock() {} - - std::string getName() override { return getType(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNeuronBlock(this); } - std::string getType() override { return "NeuronBlock"; } - NeuronBlock* clone() override { return new NeuronBlock(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - class Unit : public Expression { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - Unit(String* name); - Unit(const Unit& obj); - virtual ~Unit() {} - - std::string getName() override { return name->eval(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitUnit(this); } - std::string getType() override { return "Unit"; } - Unit* clone() override { return new Unit(*this); } - }; - - class DoubleUnit : public Expression { - public: - /* member variables */ - std::shared_ptr values; - std::shared_ptr unit; - /* constructors */ - DoubleUnit(Double* values, Unit* unit); - DoubleUnit(const DoubleUnit& obj); - virtual ~DoubleUnit() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDoubleUnit(this); } - std::string getType() override { return "DoubleUnit"; } - DoubleUnit* clone() override { return new DoubleUnit(*this); } - }; - - class LocalVariable : public Expression { - public: - /* member variables */ - std::shared_ptr name; - /* constructors */ - LocalVariable(Identifier* name); - LocalVariable(const LocalVariable& obj); - virtual ~LocalVariable() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLocalVariable(this); } - std::string getType() override { return "LocalVariable"; } - LocalVariable* clone() override { return new LocalVariable(*this); } - }; - - class Limits : public Expression { - public: - /* member variables */ - std::shared_ptr min; - std::shared_ptr max; - /* constructors */ - Limits(Double* min, Double* max); - Limits(const Limits& obj); - virtual ~Limits() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLimits(this); } - std::string getType() override { return "Limits"; } - Limits* clone() override { return new Limits(*this); } - }; - - class NumberRange : public Expression { - public: - /* member variables */ - std::shared_ptr min; - std::shared_ptr max; - /* constructors */ - NumberRange(Number* min, Number* max); - NumberRange(const NumberRange& obj); - virtual ~NumberRange() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNumberRange(this); } - std::string getType() override { return "NumberRange"; } - NumberRange* clone() override { return new NumberRange(*this); } - }; - - class PlotVariable : public Expression { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr index; - /* constructors */ - PlotVariable(Identifier* name, Integer* index); - PlotVariable(const PlotVariable& obj); - virtual ~PlotVariable() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPlotVariable(this); } - std::string getType() override { return "PlotVariable"; } - PlotVariable* clone() override { return new PlotVariable(*this); } - }; - - class BinaryOperator : public Expression { - public: - /* member variables */ - BinaryOp value; - /* constructors */ - BinaryOperator(BinaryOp value); - BinaryOperator(const BinaryOperator& obj); - virtual ~BinaryOperator() {} - - BinaryOperator() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBinaryOperator(this); } - std::string getType() override { return "BinaryOperator"; } - BinaryOperator* clone() override { return new BinaryOperator(*this); } - std::string eval() { return BinaryOpNames[value]; } - }; - - class UnaryOperator : public Expression { - public: - /* member variables */ - UnaryOp value; - /* constructors */ - UnaryOperator(UnaryOp value); - UnaryOperator(const UnaryOperator& obj); - virtual ~UnaryOperator() {} - - UnaryOperator() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitUnaryOperator(this); } - std::string getType() override { return "UnaryOperator"; } - UnaryOperator* clone() override { return new UnaryOperator(*this); } - std::string eval() { return UnaryOpNames[value]; } - }; - - class ReactionOperator : public Expression { - public: - /* member variables */ - ReactionOp value; - /* constructors */ - ReactionOperator(ReactionOp value); - ReactionOperator(const ReactionOperator& obj); - virtual ~ReactionOperator() {} - - ReactionOperator() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitReactionOperator(this); } - std::string getType() override { return "ReactionOperator"; } - ReactionOperator* clone() override { return new ReactionOperator(*this); } - std::string eval() { return ReactionOpNames[value]; } - }; - - class BinaryExpression : public Expression { - public: - /* member variables */ - std::shared_ptr lhs; - BinaryOperator op; - std::shared_ptr rhs; - /* constructors */ - BinaryExpression(Expression* lhs, BinaryOperator op, Expression* rhs); - BinaryExpression(const BinaryExpression& obj); - virtual ~BinaryExpression() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBinaryExpression(this); } - std::string getType() override { return "BinaryExpression"; } - BinaryExpression* clone() override { return new BinaryExpression(*this); } - }; - - class UnaryExpression : public Expression { - public: - /* member variables */ - UnaryOperator op; - std::shared_ptr expression; - /* constructors */ - UnaryExpression(UnaryOperator op, Expression* expression); - UnaryExpression(const UnaryExpression& obj); - virtual ~UnaryExpression() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitUnaryExpression(this); } - std::string getType() override { return "UnaryExpression"; } - UnaryExpression* clone() override { return new UnaryExpression(*this); } - }; - - class NonLinEuation : public Expression { - public: - /* member variables */ - std::shared_ptr lhs; - std::shared_ptr rhs; - /* constructors */ - NonLinEuation(Expression* lhs, Expression* rhs); - NonLinEuation(const NonLinEuation& obj); - virtual ~NonLinEuation() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNonLinEuation(this); } - std::string getType() override { return "NonLinEuation"; } - NonLinEuation* clone() override { return new NonLinEuation(*this); } - }; - - class LinEquation : public Expression { - public: - /* member variables */ - std::shared_ptr leftlinexpr; - std::shared_ptr linexpr; - /* constructors */ - LinEquation(Expression* leftlinexpr, Expression* linexpr); - LinEquation(const LinEquation& obj); - virtual ~LinEquation() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLinEquation(this); } - std::string getType() override { return "LinEquation"; } - LinEquation* clone() override { return new LinEquation(*this); } - }; - - class FunctionCall : public Expression { - public: - /* member variables */ - std::shared_ptr name; - ExpressionVector arguments; - /* constructors */ - FunctionCall(Name* name, ExpressionVector arguments); - FunctionCall(const FunctionCall& obj); - virtual ~FunctionCall() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFunctionCall(this); } - std::string getType() override { return "FunctionCall"; } - FunctionCall* clone() override { return new FunctionCall(*this); } - }; - - class FirstLastTypeIndex : public Expression { - public: - /* member variables */ - FirstLastType value; - /* constructors */ - FirstLastTypeIndex(FirstLastType value); - FirstLastTypeIndex(const FirstLastTypeIndex& obj); - virtual ~FirstLastTypeIndex() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFirstLastTypeIndex(this); } - std::string getType() override { return "FirstLastTypeIndex"; } - FirstLastTypeIndex* clone() override { return new FirstLastTypeIndex(*this); } - std::string eval() { return FirstLastTypeNames[value]; } - }; - - class Watch : public Expression { - public: - /* member variables */ - std::shared_ptr expression; - std::shared_ptr value; - /* constructors */ - Watch(Expression* expression, Expression* value); - Watch(const Watch& obj); - virtual ~Watch() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitWatch(this); } - std::string getType() override { return "Watch"; } - Watch* clone() override { return new Watch(*this); } - }; - - class QueueExpressionType : public Expression { - public: - /* member variables */ - QueueType value; - /* constructors */ - QueueExpressionType(QueueType value); - QueueExpressionType(const QueueExpressionType& obj); - virtual ~QueueExpressionType() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitQueueExpressionType(this); } - std::string getType() override { return "QueueExpressionType"; } - QueueExpressionType* clone() override { return new QueueExpressionType(*this); } - std::string eval() { return QueueTypeNames[value]; } - }; - - class Match : public Expression { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr expression; - /* constructors */ - Match(Identifier* name, Expression* expression); - Match(const Match& obj); - virtual ~Match() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitMatch(this); } - std::string getType() override { return "Match"; } - Match* clone() override { return new Match(*this); } - }; - - class BABlockType : public Expression { - public: - /* member variables */ - BAType value; - /* constructors */ - BABlockType(BAType value); - BABlockType(const BABlockType& obj); - virtual ~BABlockType() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitBABlockType(this); } - std::string getType() override { return "BABlockType"; } - BABlockType* clone() override { return new BABlockType(*this); } - std::string eval() { return BATypeNames[value]; } - }; - - class UnitDef : public Expression { - public: - /* member variables */ - std::shared_ptr unit1; - std::shared_ptr unit2; - /* constructors */ - UnitDef(Unit* unit1, Unit* unit2); - UnitDef(const UnitDef& obj); - virtual ~UnitDef() {} - - std::string getName() override { return unit1->getName(); } - ModToken* getToken() override { return unit1->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitUnitDef(this); } - std::string getType() override { return "UnitDef"; } - UnitDef* clone() override { return new UnitDef(*this); } - }; - - class FactorDef : public Expression { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr value; - std::shared_ptr unit1; - std::shared_ptr gt; - std::shared_ptr unit2; - std::shared_ptr token; - /* constructors */ - FactorDef(Name* name, Double* value, Unit* unit1, Boolean* gt, Unit* unit2); - FactorDef(const FactorDef& obj); - virtual ~FactorDef() {} - - std::string getName() override { return name->getName(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFactorDef(this); } - std::string getType() override { return "FactorDef"; } - FactorDef* clone() override { return new FactorDef(*this); } - ModToken* getToken() override { return token.get(); } - void setToken(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } - }; - - class Valence : public Expression { - public: - /* member variables */ - std::shared_ptr type; - std::shared_ptr value; - /* constructors */ - Valence(Name* type, Double* value); - Valence(const Valence& obj); - virtual ~Valence() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitValence(this); } - std::string getType() override { return "Valence"; } - Valence* clone() override { return new Valence(*this); } - }; - - class UnitState : public Statement { - public: - /* member variables */ - UnitStateType value; - /* constructors */ - UnitState(UnitStateType value); - UnitState(const UnitState& obj); - virtual ~UnitState() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitUnitState(this); } - std::string getType() override { return "UnitState"; } - UnitState* clone() override { return new UnitState(*this); } - std::string eval() { return UnitStateTypeNames[value]; } - }; - - class LocalListStatement : public Statement { - public: - /* member variables */ - LocalVariableVector variables; - /* constructors */ - LocalListStatement(LocalVariableVector variables); - LocalListStatement(const LocalListStatement& obj); - virtual ~LocalListStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLocalListStatement(this); } - std::string getType() override { return "LocalListStatement"; } - LocalListStatement* clone() override { return new LocalListStatement(*this); } - }; - - class Model : public Statement { - public: - /* member variables */ - std::shared_ptr title; - /* constructors */ - Model(String* title); - Model(const Model& obj); - virtual ~Model() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitModel(this); } - std::string getType() override { return "Model"; } - Model* clone() override { return new Model(*this); } - }; - - class Define : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr value; - /* constructors */ - Define(Name* name, Integer* value); - Define(const Define& obj); - virtual ~Define() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDefine(this); } - std::string getType() override { return "Define"; } - Define* clone() override { return new Define(*this); } - }; - - class Include : public Statement { - public: - /* member variables */ - std::shared_ptr filename; - /* constructors */ - Include(String* filename); - Include(const Include& obj); - virtual ~Include() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitInclude(this); } - std::string getType() override { return "Include"; } - Include* clone() override { return new Include(*this); } - }; - - class ParamAssign : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr value; - std::shared_ptr unit; - std::shared_ptr limit; - /* constructors */ - ParamAssign(Identifier* name, Number* value, Unit* unit, Limits* limit); - ParamAssign(const ParamAssign& obj); - virtual ~ParamAssign() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitParamAssign(this); } - std::string getType() override { return "ParamAssign"; } - ParamAssign* clone() override { return new ParamAssign(*this); } - }; - - class Stepped : public Statement { - public: - /* member variables */ - std::shared_ptr name; - NumberVector values; - std::shared_ptr unit; - /* constructors */ - Stepped(Name* name, NumberVector values, Unit* unit); - Stepped(const Stepped& obj); - virtual ~Stepped() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitStepped(this); } - std::string getType() override { return "Stepped"; } - Stepped* clone() override { return new Stepped(*this); } - }; - - class IndependentDef : public Statement { - public: - /* member variables */ - std::shared_ptr sweep; - std::shared_ptr name; - std::shared_ptr from; - std::shared_ptr to; - std::shared_ptr with; - std::shared_ptr opstart; - std::shared_ptr unit; - /* constructors */ - IndependentDef(Boolean* sweep, Name* name, Number* from, Number* to, Integer* with, Number* opstart, Unit* unit); - IndependentDef(const IndependentDef& obj); - virtual ~IndependentDef() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitIndependentDef(this); } - std::string getType() override { return "IndependentDef"; } - IndependentDef* clone() override { return new IndependentDef(*this); } - }; - - class DependentDef : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr index; - std::shared_ptr from; - std::shared_ptr to; - std::shared_ptr opstart; - std::shared_ptr unit; - std::shared_ptr abstol; - /* constructors */ - DependentDef(Identifier* name, Integer* index, Number* from, Number* to, Number* opstart, Unit* unit, Double* abstol); - DependentDef(const DependentDef& obj); - virtual ~DependentDef() {} - - std::string getName() override { return name->getName(); } - ModToken* getToken() override { return name->getToken(); } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitDependentDef(this); } - std::string getType() override { return "DependentDef"; } - DependentDef* clone() override { return new DependentDef(*this); } - }; - - class PlotDeclaration : public Statement { - public: - /* member variables */ - PlotVariableVector pvlist; - std::shared_ptr name; - /* constructors */ - PlotDeclaration(PlotVariableVector pvlist, PlotVariable* name); - PlotDeclaration(const PlotDeclaration& obj); - virtual ~PlotDeclaration() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPlotDeclaration(this); } - std::string getType() override { return "PlotDeclaration"; } - PlotDeclaration* clone() override { return new PlotDeclaration(*this); } - }; - - class ConductanceHint : public Statement { - public: - /* member variables */ - std::shared_ptr conductance; - std::shared_ptr ion; - /* constructors */ - ConductanceHint(Name* conductance, Name* ion); - ConductanceHint(const ConductanceHint& obj); - virtual ~ConductanceHint() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitConductanceHint(this); } - std::string getType() override { return "ConductanceHint"; } - ConductanceHint* clone() override { return new ConductanceHint(*this); } - }; - - class ExpressionStatement : public Statement { - public: - /* member variables */ - std::shared_ptr expression; - /* constructors */ - ExpressionStatement(Expression* expression); - ExpressionStatement(const ExpressionStatement& obj); - virtual ~ExpressionStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitExpressionStatement(this); } - std::string getType() override { return "ExpressionStatement"; } - ExpressionStatement* clone() override { return new ExpressionStatement(*this); } - }; - - class ProtectStatement : public Statement { - public: - /* member variables */ - std::shared_ptr expression; - /* constructors */ - ProtectStatement(Expression* expression); - ProtectStatement(const ProtectStatement& obj); - virtual ~ProtectStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitProtectStatement(this); } - std::string getType() override { return "ProtectStatement"; } - ProtectStatement* clone() override { return new ProtectStatement(*this); } - }; - - class FromStatement : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr from; - std::shared_ptr to; - std::shared_ptr opinc; - std::shared_ptr statementblock; - /* constructors */ - FromStatement(Name* name, Expression* from, Expression* to, Expression* opinc, StatementBlock* statementblock); - FromStatement(const FromStatement& obj); - virtual ~FromStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitFromStatement(this); } - std::string getType() override { return "FromStatement"; } - FromStatement* clone() override { return new FromStatement(*this); } - }; - - class ForAllStatement : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr statementblock; - /* constructors */ - ForAllStatement(Name* name, StatementBlock* statementblock); - ForAllStatement(const ForAllStatement& obj); - virtual ~ForAllStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitForAllStatement(this); } - std::string getType() override { return "ForAllStatement"; } - ForAllStatement* clone() override { return new ForAllStatement(*this); } - }; - - class WhileStatement : public Statement { - public: - /* member variables */ - std::shared_ptr condition; - std::shared_ptr statementblock; - /* constructors */ - WhileStatement(Expression* condition, StatementBlock* statementblock); - WhileStatement(const WhileStatement& obj); - virtual ~WhileStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitWhileStatement(this); } - std::string getType() override { return "WhileStatement"; } - WhileStatement* clone() override { return new WhileStatement(*this); } - }; - - class IfStatement : public Statement { - public: - /* member variables */ - std::shared_ptr condition; - std::shared_ptr statementblock; - ElseIfStatementVector elseifs; - std::shared_ptr elses; - /* constructors */ - IfStatement(Expression* condition, StatementBlock* statementblock, ElseIfStatementVector elseifs, ElseStatement* elses); - IfStatement(const IfStatement& obj); - virtual ~IfStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitIfStatement(this); } - std::string getType() override { return "IfStatement"; } - IfStatement* clone() override { return new IfStatement(*this); } - }; - - class ElseIfStatement : public Statement { - public: - /* member variables */ - std::shared_ptr condition; - std::shared_ptr statementblock; - /* constructors */ - ElseIfStatement(Expression* condition, StatementBlock* statementblock); - ElseIfStatement(const ElseIfStatement& obj); - virtual ~ElseIfStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitElseIfStatement(this); } - std::string getType() override { return "ElseIfStatement"; } - ElseIfStatement* clone() override { return new ElseIfStatement(*this); } - }; - - class ElseStatement : public Statement { - public: - /* member variables */ - std::shared_ptr statementblock; - /* constructors */ - ElseStatement(StatementBlock* statementblock); - ElseStatement(const ElseStatement& obj); - virtual ~ElseStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitElseStatement(this); } - std::string getType() override { return "ElseStatement"; } - ElseStatement* clone() override { return new ElseStatement(*this); } - }; - - class PartialEquation : public Statement { - public: - /* member variables */ - std::shared_ptr prime; - std::shared_ptr name1; - std::shared_ptr name2; - std::shared_ptr name3; - /* constructors */ - PartialEquation(PrimeName* prime, Name* name1, Name* name2, Name* name3); - PartialEquation(const PartialEquation& obj); - virtual ~PartialEquation() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPartialEquation(this); } - std::string getType() override { return "PartialEquation"; } - PartialEquation* clone() override { return new PartialEquation(*this); } - }; - - class PartialBoundary : public Statement { - public: - /* member variables */ - std::shared_ptr del; - std::shared_ptr name; - std::shared_ptr index; - std::shared_ptr expression; - std::shared_ptr name1; - std::shared_ptr del2; - std::shared_ptr name2; - std::shared_ptr name3; - /* constructors */ - PartialBoundary(Name* del, Identifier* name, FirstLastTypeIndex* index, Expression* expression, Name* name1, Name* del2, Name* name2, Name* name3); - PartialBoundary(const PartialBoundary& obj); - virtual ~PartialBoundary() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitPartialBoundary(this); } - std::string getType() override { return "PartialBoundary"; } - PartialBoundary* clone() override { return new PartialBoundary(*this); } - }; - - class WatchStatement : public Statement { - public: - /* member variables */ - WatchVector statements; - /* constructors */ - WatchStatement(WatchVector statements); - WatchStatement(const WatchStatement& obj); - virtual ~WatchStatement() {} - - void addWatch(Watch *s) { - statements.push_back(std::shared_ptr(s)); - } - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitWatchStatement(this); } - std::string getType() override { return "WatchStatement"; } - WatchStatement* clone() override { return new WatchStatement(*this); } - }; - - class MutexLock : public Statement { - public: - virtual ~MutexLock() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitMutexLock(this); } - std::string getType() override { return "MutexLock"; } - MutexLock* clone() override { return new MutexLock(*this); } - }; - - class MutexUnlock : public Statement { - public: - virtual ~MutexUnlock() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitMutexUnlock(this); } - std::string getType() override { return "MutexUnlock"; } - MutexUnlock* clone() override { return new MutexUnlock(*this); } - }; - - class Reset : public Statement { - public: - virtual ~Reset() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitReset(this); } - std::string getType() override { return "Reset"; } - Reset* clone() override { return new Reset(*this); } - }; - - class Sens : public Statement { - public: - /* member variables */ - VarNameVector senslist; - /* constructors */ - Sens(VarNameVector senslist); - Sens(const Sens& obj); - virtual ~Sens() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitSens(this); } - std::string getType() override { return "Sens"; } - Sens* clone() override { return new Sens(*this); } - }; - - class Conserve : public Statement { - public: - /* member variables */ - std::shared_ptr react; - std::shared_ptr expr; - /* constructors */ - Conserve(Expression* react, Expression* expr); - Conserve(const Conserve& obj); - virtual ~Conserve() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitConserve(this); } - std::string getType() override { return "Conserve"; } - Conserve* clone() override { return new Conserve(*this); } - }; - - class Compartment : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr expression; - NameVector names; - /* constructors */ - Compartment(Name* name, Expression* expression, NameVector names); - Compartment(const Compartment& obj); - virtual ~Compartment() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitCompartment(this); } - std::string getType() override { return "Compartment"; } - Compartment* clone() override { return new Compartment(*this); } - }; - - class LDifuse : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr expression; - NameVector names; - /* constructors */ - LDifuse(Name* name, Expression* expression, NameVector names); - LDifuse(const LDifuse& obj); - virtual ~LDifuse() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLDifuse(this); } - std::string getType() override { return "LDifuse"; } - LDifuse* clone() override { return new LDifuse(*this); } - }; - - class ReactionStatement : public Statement { - public: - /* member variables */ - std::shared_ptr react1; - ReactionOperator op; - std::shared_ptr react2; - std::shared_ptr expr1; - std::shared_ptr expr2; - /* constructors */ - ReactionStatement(Expression* react1, ReactionOperator op, Expression* react2, Expression* expr1, Expression* expr2); - ReactionStatement(const ReactionStatement& obj); - virtual ~ReactionStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitReactionStatement(this); } - std::string getType() override { return "ReactionStatement"; } - ReactionStatement* clone() override { return new ReactionStatement(*this); } - }; - - class LagStatement : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr byname; - /* constructors */ - LagStatement(Identifier* name, Name* byname); - LagStatement(const LagStatement& obj); - virtual ~LagStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitLagStatement(this); } - std::string getType() override { return "LagStatement"; } - LagStatement* clone() override { return new LagStatement(*this); } - }; - - class QueueStatement : public Statement { - public: - /* member variables */ - std::shared_ptr qype; - std::shared_ptr name; - /* constructors */ - QueueStatement(QueueExpressionType* qype, Identifier* name); - QueueStatement(const QueueStatement& obj); - virtual ~QueueStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitQueueStatement(this); } - std::string getType() override { return "QueueStatement"; } - QueueStatement* clone() override { return new QueueStatement(*this); } - }; - - class ConstantStatement : public Statement { - public: - /* member variables */ - std::shared_ptr name; - std::shared_ptr value; - std::shared_ptr unit; - /* constructors */ - ConstantStatement(Name* name, Number* value, Unit* unit); - ConstantStatement(const ConstantStatement& obj); - virtual ~ConstantStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitConstantStatement(this); } - std::string getType() override { return "ConstantStatement"; } - ConstantStatement* clone() override { return new ConstantStatement(*this); } - }; - - class TableStatement : public Statement { - public: - /* member variables */ - NameVector tablst; - NameVector dependlst; - std::shared_ptr from; - std::shared_ptr to; - std::shared_ptr with; - /* constructors */ - TableStatement(NameVector tablst, NameVector dependlst, Expression* from, Expression* to, Integer* with); - TableStatement(const TableStatement& obj); - virtual ~TableStatement() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitTableStatement(this); } - std::string getType() override { return "TableStatement"; } - TableStatement* clone() override { return new TableStatement(*this); } - }; - - class NrnSuffix : public Statement { - public: - /* member variables */ - std::shared_ptr type; - std::shared_ptr name; - /* constructors */ - NrnSuffix(Name* type, Name* name); - NrnSuffix(const NrnSuffix& obj); - virtual ~NrnSuffix() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnSuffix(this); } - std::string getType() override { return "NrnSuffix"; } - NrnSuffix* clone() override { return new NrnSuffix(*this); } - }; - - class NrnUseion : public Statement { - public: - /* member variables */ - std::shared_ptr name; - ReadIonVarVector readlist; - WriteIonVarVector writelist; - std::shared_ptr valence; - /* constructors */ - NrnUseion(Name* name, ReadIonVarVector readlist, WriteIonVarVector writelist, Valence* valence); - NrnUseion(const NrnUseion& obj); - virtual ~NrnUseion() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnUseion(this); } - std::string getType() override { return "NrnUseion"; } - NrnUseion* clone() override { return new NrnUseion(*this); } - }; - - class NrnNonspecific : public Statement { - public: - /* member variables */ - NonspeCurVarVector currents; - /* constructors */ - NrnNonspecific(NonspeCurVarVector currents); - NrnNonspecific(const NrnNonspecific& obj); - virtual ~NrnNonspecific() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnNonspecific(this); } - std::string getType() override { return "NrnNonspecific"; } - NrnNonspecific* clone() override { return new NrnNonspecific(*this); } - }; - - class NrnElctrodeCurrent : public Statement { - public: - /* member variables */ - ElectrodeCurVarVector ecurrents; - /* constructors */ - NrnElctrodeCurrent(ElectrodeCurVarVector ecurrents); - NrnElctrodeCurrent(const NrnElctrodeCurrent& obj); - virtual ~NrnElctrodeCurrent() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnElctrodeCurrent(this); } - std::string getType() override { return "NrnElctrodeCurrent"; } - NrnElctrodeCurrent* clone() override { return new NrnElctrodeCurrent(*this); } - }; - - class NrnSection : public Statement { - public: - /* member variables */ - SectionVarVector sections; - /* constructors */ - NrnSection(SectionVarVector sections); - NrnSection(const NrnSection& obj); - virtual ~NrnSection() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnSection(this); } - std::string getType() override { return "NrnSection"; } - NrnSection* clone() override { return new NrnSection(*this); } - }; - - class NrnRange : public Statement { - public: - /* member variables */ - RangeVarVector range_vars; - /* constructors */ - NrnRange(RangeVarVector range_vars); - NrnRange(const NrnRange& obj); - virtual ~NrnRange() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnRange(this); } - std::string getType() override { return "NrnRange"; } - NrnRange* clone() override { return new NrnRange(*this); } - }; - - class NrnGlobal : public Statement { - public: - /* member variables */ - GlobalVarVector global_vars; - /* constructors */ - NrnGlobal(GlobalVarVector global_vars); - NrnGlobal(const NrnGlobal& obj); - virtual ~NrnGlobal() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnGlobal(this); } - std::string getType() override { return "NrnGlobal"; } - NrnGlobal* clone() override { return new NrnGlobal(*this); } - }; - - class NrnPointer : public Statement { - public: - /* member variables */ - PointerVarVector pointers; - /* constructors */ - NrnPointer(PointerVarVector pointers); - NrnPointer(const NrnPointer& obj); - virtual ~NrnPointer() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnPointer(this); } - std::string getType() override { return "NrnPointer"; } - NrnPointer* clone() override { return new NrnPointer(*this); } - }; - - class NrnBbcorePtr : public Statement { - public: - /* member variables */ - BbcorePointerVarVector bbcore_pointers; - /* constructors */ - NrnBbcorePtr(BbcorePointerVarVector bbcore_pointers); - NrnBbcorePtr(const NrnBbcorePtr& obj); - virtual ~NrnBbcorePtr() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnBbcorePtr(this); } - std::string getType() override { return "NrnBbcorePtr"; } - NrnBbcorePtr* clone() override { return new NrnBbcorePtr(*this); } - }; - - class NrnExternal : public Statement { - public: - /* member variables */ - ExternVarVector externals; - /* constructors */ - NrnExternal(ExternVarVector externals); - NrnExternal(const NrnExternal& obj); - virtual ~NrnExternal() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnExternal(this); } - std::string getType() override { return "NrnExternal"; } - NrnExternal* clone() override { return new NrnExternal(*this); } - }; - - class NrnThreadSafe : public Statement { - public: - /* member variables */ - ThreadsafeVarVector threadsafe; - /* constructors */ - NrnThreadSafe(ThreadsafeVarVector threadsafe); - NrnThreadSafe(const NrnThreadSafe& obj); - virtual ~NrnThreadSafe() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitNrnThreadSafe(this); } - std::string getType() override { return "NrnThreadSafe"; } - NrnThreadSafe* clone() override { return new NrnThreadSafe(*this); } - }; - - class Verbatim : public Statement { - public: - /* member variables */ - std::shared_ptr statement; - /* constructors */ - Verbatim(String* statement); - Verbatim(const Verbatim& obj); - virtual ~Verbatim() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitVerbatim(this); } - std::string getType() override { return "Verbatim"; } - Verbatim* clone() override { return new Verbatim(*this); } - }; - - class Comment : public Statement { - public: - /* member variables */ - std::shared_ptr comment; - /* constructors */ - Comment(String* comment); - Comment(const Comment& obj); - virtual ~Comment() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitComment(this); } - std::string getType() override { return "Comment"; } - Comment* clone() override { return new Comment(*this); } - }; - - class Program : public AST { - public: - /* member variables */ - StatementVector statements; - BlockVector blocks; - void* symtab = nullptr; - - /* constructors */ - Program(StatementVector statements, BlockVector blocks); - Program(const Program& obj); - virtual ~Program() {} - - void addStatement(Statement *s) { - statements.push_back(std::shared_ptr(s)); - } - void addBlock(Block *s) { - blocks.push_back(std::shared_ptr(s)); - } - Program() {} - - void visitChildren (Visitor* v) override; - void accept(Visitor* v) override { v->visitProgram(this); } - std::string getType() override { return "Program"; } - Program* clone() override { return new Program(*this); } - void setBlockSymbolTable(void *s) { symtab = s; } - void* getBlockSymbolTable() { return symtab; } - }; - - - -} // namespace ast diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt new file mode 100644 index 0000000000..53a5605700 --- /dev/null +++ b/src/nmodl/language/CMakeLists.txt @@ -0,0 +1,20 @@ +# generate AST/Visitor classes from language definition +add_custom_command ( + COMMAND ${PYTHON_EXECUTABLE} + ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/argument.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/parser.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/node_types.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/base_printer.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast_printer.py + COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" +) + +add_custom_target (pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) \ No newline at end of file diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py new file mode 100644 index 0000000000..e9e7c46950 --- /dev/null +++ b/src/nmodl/language/argument.py @@ -0,0 +1,23 @@ +class Argument: + """Utility class for holding all arguments for node classes + + when force_prefix / force_suffix is true then prefix/suffix needs to be + printed even if node itself is null + """ + + def __init__(self): + self.base_class = "" + self.class_name = "" + self.nmodl_name = "" + self.prefix = "" + self.suffix = "" + self.separator = "" + self.typename = "" + self.varname = "" + self.is_vector = False + self.is_optional = False + self.add_method = False + self.getname_method = False + self.has_token = False + self.force_prefix = False + self.force_suffix = False \ No newline at end of file diff --git a/src/nmodl/language/ast.py b/src/nmodl/language/ast.py new file mode 100644 index 0000000000..19703c516d --- /dev/null +++ b/src/nmodl/language/ast.py @@ -0,0 +1,262 @@ +"""Define basic classes used from parser + +Node is used to represent a node parsed from every rule +in language definition file. Each member variable of Node +is represented by ChildNode. +""" + +from argument import Argument +from node_types import * + + +class BaseNode: + """base class for all node types (parent + child) """ + + def __init__(self, args): + self.class_name = args.class_name + self.nmodl_name = args.nmodl_name + self.prefix = args.prefix + self.suffix = args.suffix + self.separator = args.separator + self.force_prefix = args.force_prefix + self.force_suffix = args.force_suffix + self.is_abstract = False + + def get_data_type_name(self): + """ return type name for the node """ + return DATA_TYPES[self.class_name] + + def get_typename(self): + """returns type of the node for declaration + + When node is of base type then it is returned as it is. Depending + on pointer or list, appropriate suffix is added. Some of the examples + of typename are Expression, ExpressionList, Expression* etc. + + This is different than child node because childs are typically + class members and need to be + """ + + type_name = self.class_name + + if self.class_name not in BASE_TYPES and self.class_name not in PTR_EXCLUDE_TYPES: + type_name += "*" + + return type_name + + def is_expression_node(self): + return True if self.class_name == EXPRESSION_NODE else False + + def is_statement_block_node(self): + return True if self.class_name == STATEMENT_BLOCK_NODE else False + + def is_statement_node(self): + return True if self.class_name in STATEMENT_TYPES else False + + def is_global_block_node(self): + return True if self.class_name in GLOBAL_BLOCKS else False + + def is_prime_node(self): + return True if self.class_name == PRIME_NAME_NODE else False + + def is_program_node(self): + """ + check if current node is main program container node + :return: True if main program block otherwise False + """ + return True if self.class_name == PROGRAM_BLOCK else False + + def is_block_node(self): + """ + Check if current node is of type Block + + While translating AST to NMODL, we handle block nodes for printing. + + :return: True or False + """ + return True if self.class_name in BLOCK_TYPES else False + + def is_reaction_statement_node(self): + return True if self.class_name == REACTION_STATEMENT_NODE else False + + def is_conserve_node(self): + return True if self.class_name == CONSERVE_NODE else False + + def is_binary_expression_node(self): + return True if self.class_name == BINARY_EXPRESSION_NODE else False + + def is_unary_expression_node(self): + return True if self.class_name == UNARY_EXPRESSION_NODE else False + + def is_verbatim_node(self): + return True if self.class_name == VERBATIM_NODE else False + + def is_comment_node(self): + return True if self.class_name == COMMENT_NODE else False + + def is_independentdef_node(self): + return True if self.class_name == INDEPENDENTDEF_NODE else False + + def is_unit_block(self): + return True if self.class_name == UNIT_BLOCK else False + + def is_data_type_node(self): + return True if self.class_name in DATA_TYPES.keys() else False + + def is_symbol_var_node(self): + return True if self.class_name in SYMBOL_VAR_TYPES else False + + def is_symbol_block_node(self): + return True if self.class_name in SYMBOL_BLOCK_TYPES else False + + def is_base_type_node(self): + return True if self.class_name in BASE_TYPES else False + + def is_string_node(self): + return True if self.class_name == STRING_NODE else False + + def is_integer_node(self): + return True if self.class_name == INTEGER_NODE else False + + def is_number_node(self): + return True if self.class_name == NUMBER_NODE else False + + def is_boolean_node(self): + return True if self.class_name == BOOLEAN_NODE else False + + def is_identifier_node(self): + return True if self.class_name == IDENTIFIER_NODE else False + + def is_name_node(self): + return True if self.class_name == NAME_NODE else False + + def is_enum_node(self): + data_type = DATA_TYPES[self.class_name] + return True if data_type in ENUM_BASE_TYPES else False + + def is_pointer_node(self): + if self.class_name in PTR_EXCLUDE_TYPES: + return False + if self.is_base_type_node(): + return False + return True + + def is_ptr_excluded_node(self): + if self.class_name in PTR_EXCLUDE_TYPES: + return True + return False + + +class ChildNode(BaseNode): + """represent member variable for a Node""" + + def __init__(self, args): + BaseNode.__init__(self, args) + self.typename = args.typename + self.varname = args.varname + self.is_vector = args.is_vector + self.optional = args.is_optional + self.add_method = args.add_method + self.getname_method = args.getname_method + + def get_typename(self): + """returns type of the node for declaration + + When node is of base type then it is returned as it is. Depending + on pointer or list, appropriate suffix is added. Some of the examples + of typename are Expression, ExpressionList, Expression* etc. + """ + + type_name = self.class_name + + if self.is_vector: + type_name += "Vector" + elif self.class_name not in BASE_TYPES and self.class_name not in PTR_EXCLUDE_TYPES: + type_name += "*" + + return type_name + + def get_typename_as_member(self): + """returns type of the node to be a member of the class + + Check how to refactor this + """ + + type_name = self.class_name + + if self.is_vector: + type_name += "Vector" + elif self.class_name not in BASE_TYPES and self.class_name not in PTR_EXCLUDE_TYPES: + type_name = "std::shared_ptr<" + type_name + ">" + + return type_name + + +class Node(BaseNode): + """represent a class for every rule in language specification""" + + def __init__(self, args): + BaseNode.__init__(self, args) + self.base_class = args.base_class + self.has_token = args.has_token + self.children = [] + + def add_child(self, args): + """add new child i.e. member to the class""" + node = ChildNode(args) + self.children.append(node) + + def has_children(self): + return True if self.children else False + + def is_block_scoped_node(self): + """ + Check if node is derived from BASE_BLOCK + :return: True / False + """ + return True if self.base_class == BASE_BLOCK else False + + def has_parent_block_node(self): + """ + check is base or parent is structured base block + :return: True if parent is BASE_BLOCK otherwise False + """ + return True if self.base_class == BASE_BLOCK else False + + def is_symtab_needed(self): + """ + Check if symbol tabel needed for current node + + Only main program node and block scoped nodes needed Symbol table + :return: True or False + """ + # block scope nodes have symtab pointer + if self.is_program_node() or self.is_block_scoped_node(): + return True + return False + + def is_symtab_method_required(self): + """ + Symbols are not present in all block nodes e.g. Integer node + and may other nodes has standard "node->visitChildren(this);" + + As we are inheriting from AstVisitor, we don't have write empty + methods. Only override those where symbols are contained. + + :return: True if need to print visit method for node in symtabjsonvisitor + otherwise False + """ + method_required = False + + if self.has_children(): + + if self.class_name in SYMBOL_VAR_TYPES or self.class_name in SYMBOL_BLOCK_TYPES: + method_required = True + + if self.is_program_node() or self.has_parent_block_node(): + method_required = True + + return method_required + + def is_base_class_number_node(self): + return True if self.base_class == NUMBER_NODE else False diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py new file mode 100644 index 0000000000..e4217c4e14 --- /dev/null +++ b/src/nmodl/language/ast_printer.py @@ -0,0 +1,333 @@ +import itertools +from base_printer import * +from node_types import ORDER_VAR_NAME + + +class AstDeclarationPrinter(DeclarationPrinter): + """Prints all AST nodes class declarations""" + + def headers(self): + self.writer.write_line("#include ") + self.writer.write_line("#include ") + self.writer.write_line("#include ") + self.writer.write_line("#include ", newline=2) + + self.writer.write_line('#include "ast/ast_utils.hpp"') + self.writer.write_line('#include "lexer/modtoken.hpp"') + self.writer.write_line('#include "utils/common_utils.hpp"') + + def class_comment(self): + self.writer.write_line("/* all classes representing Abstract Syntax Tree (AST) nodes */") + + def forward_declarations(self): + self.writer.write_line("/* forward declarations of AST classes */") + for node in self.nodes: + self.writer.write_line("class " + node.class_name + ";") + + self.writer.write_line() + self.writer.write_line("/* std::vector for convenience */") + + for node in itertools.chain(self.nodes): + type_name = "std::vector>" + self.writer.write_line("using " + node.class_name + "Vector = " + type_name + ";") + + created_types = [] + + # iterate over all node types + for node in itertools.chain(self.nodes): + type = (node.get_typename(), node.class_name.lower() + "_ptr") + if type not in created_types: + created_types.append(type) + + for child in node.children: + ctype = child.get_typename() + cname = child.class_name.lower() + + # base types like int, string are not defined in YYSTYPE + if child.is_base_type_node(): + continue + + if child.is_vector: + cname = cname + "_list" + else: + cname += "_ptr"; + + type = (ctype, cname) + + if type not in created_types: + created_types.append(type) + + for tuple in created_types: + self.writer.write_line("using " + tuple[1] + " = " + tuple[0] + ";") + + + def ast_classes_declaration(self): + + # TODO: for demo, removing error messages in getToken method. + # need to look in detail whether we should abort in this case + # when ModToken in NULL. This was introduced when added SymtabVisitor + # pass to get Token information. + + # TODO: remove visitor related method definition from declaration + # to avoid this include + self.writer.write_line() + self.writer.write_line("#include ", newline=2) + + self.writer.write_line("/* Define all AST nodes */", newline=2) + + for node in self.nodes: + class_decl = "class " + node.class_name + " : public " + + if node.base_class: + class_decl += node.base_class + else: + class_decl += " AST" + + # depending on if node is abstract or not, we have to + # add virtual or override keyword to methods + if node.is_abstract: + virtual = "virtual " + override = "" + else: + virtual = "" + override = " override" + + self.writer.write_line(class_decl + " {", post_gutter=1) + self.writer.write_line("public:", post_gutter=1) + + members = [] + members_with_types = [] + + for child in node.children: + members.append(child.get_typename() + " " + child.varname) + members_with_types.append(child.get_typename_as_member() + " " + child.varname) + + if members: + self.writer.write_line("/* member variables */") + + # todo : need a way to setup location + # self.writer.write_line("void setLocation(nmodl::location loc) {}") + + for member in members_with_types: + self.writer.write_line(member + ";") + + if node.has_token: + self.writer.write_line("std::shared_ptr token;") + + + if node.is_symtab_needed(): + self.writer.write_line("void* symtab = nullptr;", newline=2) + + if members: + self.writer.write_line("/* constructors */") + self.writer.write_line(node.class_name + "(" + (", ".join(members)) + ");") + self.writer.write_line(node.class_name + "(const " + node.class_name + "& obj);") + + # program node holds statements and blocks and we instantiate it in driver + # also other nodes which we use as value type, parsor needs to return them + # as a value. And instantiation of those causes error if default constructor not there. + if node.is_program_node() or node.is_ptr_excluded_node(): + self.writer.write_line( node.class_name + "() {}", newline=2) + + # Todo : We need virtual destructor otherwise there will be memory leaks. + # But we need define which are virtual base classes that needs virtual function. + self.writer.write_line("virtual ~" + node.class_name + "() {}") + self.writer.write_line() + + get_method_added = False + + for child in node.children: + class_name = child.class_name + varname = child.varname + if child.add_method: + self.writer.write_line("void add" + class_name + "(" + class_name + " *s) {") + self.writer.write_line(" " + varname + ".push_back(std::shared_ptr<" + class_name + ">(s));") + self.writer.write_line("}") + + if child.getname_method: + # string node should be evaluated and hence eval() method + if child.is_string_node(): + method = "eval" + else: + method = "getName" + + self.writer.write_line("std::string getName() override {") + self.writer.write_line(" return " + varname + "->" + method + "();") + self.writer.write_line("}") + + if not node.has_token: + self.writer.write_line(virtual + "ModToken* getToken()" + override + " {") + self.writer.write_line(" return " + varname + "->getToken();") + self.writer.write_line("}") + + get_method_added = True + + if node.is_prime_node() and child.varname == ORDER_VAR_NAME: + self.writer.write_line("int getOrder() " + " { return " + ORDER_VAR_NAME + "->eval(); }") + + # TODO: returning typename for name? check the usage of this and fix in better way + if node.has_parent_block_node() and not get_method_added: + self.writer.write_line("std::string getName() override { return getType(); }") + + # all member functions + self.writer.write_line(virtual + "void visitChildren (Visitor* v) " + override + ";") + self.writer.write_line(virtual + "void accept(Visitor* v) " + override + " { v->visit" + node.class_name + "(this); }") + + # TODO: type should declared as enum class + self.writer.write_line(virtual + "std::string getType() " + override + " { return \"" + node.class_name + "\"; }") + self.writer.write_line(virtual + node.class_name + "* clone() " + override + " { return new " + node.class_name + "(*this); }") + + if node.has_token: + self.writer.write_line(virtual + "ModToken* getToken() " + override + " { return token.get(); }") + self.writer.write_line("void setToken(ModToken& tok) " + " { token = std::shared_ptr(new ModToken(tok)); }") + + if node.is_symtab_needed(): + self.writer.write_line("void setBlockSymbolTable(void *s) " + " { symtab = s; }") + self.writer.write_line("void* getBlockSymbolTable() " + " { return symtab; }") + + if node.is_number_node(): + self.writer.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") + + if node.is_base_class_number_node(): + if node.is_boolean_node(): + self.writer.write_line("void negate() override { value = !value; }") + else: + self.writer.write_line("void negate() override { value = -value; }") + + if node.is_identifier_node(): + self.writer.write_line(virtual + "void setName(std::string name)" + override + " { std::cout << \"ERROR : setName() not implemented! \"; abort(); }") + + if node.is_name_node(): + self.writer.write_line(virtual + "void setName(std::string name)" + override + " { value->set(name); }") + + # if node is of enum type then return enum value + # TODO: hardcoded Names + if node.is_data_type_node(): + data_type = node.get_data_type_name() + if node.is_enum_node(): + self.writer.write_line("std::string " + " eval() { return " + data_type + "Names[value]; }") + # But if basic data type then eval return their value + # TODO: value member is hardcoded + else: + self.writer.write_line(data_type + " eval() { return value; }") + self.writer.write_line("void set(" + data_type + " _value) " + " { value = _value; }") + + self.writer.write_line("};", pre_gutter=-2, newline=2) + + def class_name_declaration(self): + self.writer.write_line("namespace ast {", newline=2, post_gutter=1) + self.forward_declarations() + self.ast_classes_declaration() + + def declaration_end(self): + self.writer.write_line(pre_gutter=-1) + + def private_declaration(self): + pass + + def public_declaration(self): + pass + + def post_declaration(self): + self.writer.write_line("} // namespace ast", pre_gutter=-1) + + +class AstDefinitionPrinter(DefinitionPrinter): + """Prints AST class definitions""" + + def headers(self): + self.writer.write_line('#include "ast/ast.hpp"', newline=2) + + def definitions(self): + self.writer.write_line("namespace ast {", post_gutter=1) + + for node in self.nodes: + # first get types of all childrens into members vector + members = [] + + # in order to print initializer list, we need to know which + # members are no pointer types + ptr_members = [] + non_ptr_members = [] + + for child in node.children: + members.append((child.get_typename(), child.varname)) + + if child.is_pointer_node() and not child.is_vector: + ptr_members.append((child.get_typename(), child.varname)) + else: + non_ptr_members.append((child.get_typename(), child.varname)) + + if child.is_base_type_node(): + continue + + self.writer.increase_gutter() + if child.is_vector: + # TODO : remove this with C++11 style + self.writer.add_line("for(auto& item : this->" + child.varname + ") {") + #self.writer.add_line(" iter != this->" + child.varname + "->end(); iter++) {") + self.writer.add_line(" item->accept(v);") + self.writer.add_line("}") + elif child.optional or child.is_statement_block_node(): + self.writer.add_line("if (this->" + child.varname + ") {") + self.writer.add_line(" this->" + child.varname + "->accept(v);") + self.writer.add_line("}") + elif not child.is_pointer_node(): + self.writer.add_line(child.varname + ".accept(v);") + else: + self.writer.add_line(child.varname + "->accept(v);") + self.writer.decrease_gutter() + + if self.writer.num_buffered_lines(): + self.writer.write_line("/* visit method for " + node.class_name + " ast node */") + self.writer.write_line("void " + node.class_name + "::visitChildren(Visitor* v) {", post_gutter=1) + self.writer.flush_buffered_lines() + self.writer.write_line("}", pre_gutter=-1, newline=2) + else: + self.writer.write_line("void " + node.class_name + "::visitChildren(Visitor* v) {}") + + if members: + # TODO : constructor definition : remove this with C++11 style + self.writer.write_line("/* constructor for " + node.class_name + " ast node */") + arguments = (", ".join(map(lambda x: x[0] + " " + x[1], members))) + self.writer.write_line(node.class_name + "::" + node.class_name + "(" + arguments + ")") + + if non_ptr_members: + self.writer.write_line(":", newline=0) + self.writer.write_line(",\n".join(map(lambda x: x[1] + "(" + x[1] + ")", non_ptr_members))) + + self.writer.write_line("{") + + for member in ptr_members: + # bit hack here : remove pointer because we are creating smart pointer + type_name = member[0].replace("*", "") + self.writer.write_line(" this->" + member[1] + " = std::shared_ptr<" + type_name + ">(" + member[1] + ");") + + self.writer.write_line("}", newline=2) + + # copy construcotr definition : remove this with C++11 style + self.writer.write_line("/* copy constructor for " + node.class_name + " ast node */") + self.writer.write_line(node.class_name + "::" + node.class_name + "(const " + node.class_name + "& obj) ") + + self.writer.write_line("{", post_gutter=1) + + # TODO : more cleanup + for child in node.children: + if child.is_vector: + self.writer.write_line("for(auto& item : obj." + child.varname + ") {") + self.writer.write_line(" this->" + child.varname + ".push_back(std::shared_ptr< " + child.class_name + ">(item->clone()));") + self.writer.write_line("}") + else: + if child.is_pointer_node(): + self.writer.write_line("if(obj." + child.varname + ")") + self.writer.write_line(" this->" + child.varname + " = std::shared_ptr<" + child.class_name + ">(obj." + child.varname + "->clone());") + else: + self.writer.write_line("this->" + child.varname + " = obj." + child.varname + ";") + + if node.has_token: + self.writer.write_line("if(obj.token)") + self.writer.write_line(" this->token = std::shared_ptr(obj.token->clone());") + + self.writer.write_line("}", pre_gutter=-1, newline=2) + + self.writer.write_line("} // namespace ast", pre_gutter=-1) diff --git a/src/nmodl/language/base_printer.py b/src/nmodl/language/base_printer.py new file mode 100644 index 0000000000..f72ffdce25 --- /dev/null +++ b/src/nmodl/language/base_printer.py @@ -0,0 +1,163 @@ +from abc import ABCMeta, abstractmethod +import sys + + +class Writer(object): + """ Utility class for writing to file + + Common methods for writing code are provided by this + class. This handles opening/closing of file, indentation, + newlines, buffering etc. + """ + + # tab is four whitespaces + TAB = " " + + def __init__(self, filepath): + self.filepath = filepath + self.num_tabs = 0 + self.lines = [] + try: + self.fh = open(self.filepath, "w+") + except IOError as e: + print "Error :: I/O error while writing {0} : {1}".format(self.filepath, e.strerror) + sys.exit(1) + + def print_gutter(self): + for i in range(0, self.num_tabs): + self.fh.write(self.TAB) + + def increase_gutter(self): + self.num_tabs += 1 + + def decrease_gutter(self): + self.num_tabs -= 1 + + def write(self, string): + if string: + self.print_gutter() + self.fh.write(string) + + def write_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): + self.num_tabs += pre_gutter + self.write(string) + for i in range(newline): + self.fh.write("\n") + self.num_tabs += post_gutter + + def add_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): + self.num_tabs += pre_gutter + if string: + for i in range(self.num_tabs): + string = self.TAB + string + for i in range(newline): + string = string + "\n" + self.lines.append(string) + self.num_tabs += post_gutter + + def num_buffered_lines(self): + return len(self.lines) + + def flush_buffered_lines(self): + for line in self.lines: + self.fh.write(line) + self.lines = [] + + def __del__(self): + if not self.fh.closed: + self.fh.close() + + +class Printer(object): + """ Base class for code printer classes""" + __metaclass__ = ABCMeta + + def __init__(self, filepath, classname, nodes): + self.writer = Writer(filepath) + self.classname = classname + self.nodes = nodes + + @abstractmethod + def write(self): + pass + + +class DeclarationPrinter(Printer): + """ Utility class for writing declaration / header file (.hpp) + + Header file of the class typically consists of + - headers include section + - comment about the class + - class name / class declaration + - private member declarations + - public members declaration + - end of class declaration + - post declaration, e.g. end of namespace + """ + + def guard(self): + self.writer.write_line("#pragma once", newline=2) + + @abstractmethod + def headers(self): + pass + + def class_comment(self): + pass + + def class_name_declaration(self): + self.writer.write_line("class " + self.classname + " {") + + def declaration_start(self): + self.guard() + self.headers() + self.writer.write_line() + self.class_comment() + self.class_name_declaration() + + def private_declaration(self): + pass + + def public_declaration(self): + pass + + def body(self): + self.writer.increase_gutter() + self.writer.write_line() + self.private_declaration() + self.public_declaration() + self.writer.decrease_gutter() + + def declaration_end(self): + self.writer.write_line("};", pre_gutter=-1) + + def post_declaration(self): + pass + + def write(self): + self.declaration_start() + self.body() + self.declaration_end() + self.post_declaration() + + +class DefinitionPrinter(Printer): + """ Utility class for writing definition / implementation file (.cpp) + + Implementation file of the class typically consists of + - headers include section (.hpp file) + - class methods definition + """ + __metaclass__ = ABCMeta + + @abstractmethod + def headers(self): + pass + + @abstractmethod + def definitions(self): + pass + + def write(self): + self.headers() + self.definitions() \ No newline at end of file diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py new file mode 100644 index 0000000000..af3fd7be07 --- /dev/null +++ b/src/nmodl/language/code_generator.py @@ -0,0 +1,24 @@ +from parser import LanguageParser +from ast_printer import * +from visitors_printer import * + + +# parse nmodl definition file and get list of abstract nodes +nodes = LanguageParser("nmodl.yaml").parse_file() + +# print various header and code files + +AstDeclarationPrinter( + "../ast/ast.hpp", + "", + nodes).write() + +AstDefinitionPrinter( + "../ast/ast.cpp", + "", + nodes).write() + +AbstractVisitorPrinter( + "../visitors/visitor.hpp", + "Visitor", + nodes).write() diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml new file mode 100644 index 0000000000..423748c9d5 --- /dev/null +++ b/src/nmodl/language/nmodl.yaml @@ -0,0 +1,1618 @@ +######################### NMODL Abstract Language Definition ############################## +# +# PURPOSE +# ======= +# +# Lot of information about language constructs is necessary at various stages +# like AST definitions, visitors implementation, source-to-source transformations +# etc. In case of small changes in grammar or AST definition, especially +# during early development stage, we have to re-write considerable part of the code. +# The idea of "abstract" language definition is to define framework that can automatically +# generate part of this "mechanical" code. This approach has been used in some open source +# projects (e.g. compiler design courses, TableGen format in LLVM). +# +# During first prototyping phase we defined simple text rules inspired by compiler course +# assignment available at jazariethach/project6_CS160.git (no longer available on github). +# +# The basic format of the every construct specification was: +# +# BaseType:ChildType => MemberType +# +# Here "ChildType" is a new AST class that we are defining. "ParentType" is the base class, +# "MemberType" is the type of member variable with variable name "member_name". The name is +# specified in the angle braces "< >". This will generate C++ class definition like : +# +# class ChildType : public BaseType { +# private: +# MemberType member_name; +# }; +# +# This specification format was sufficient initial AST design but as we started implementing +# more features and compiler passes, we need to add more information to language definition. +# For example, we need to make certain members vector, some are optional, some need +# specific method (get/set) etc. In the future we may need to make certain members private. +# Above text definition is limiting if we have to accomodate future enhancements. +# +# To overcome above limitation, we are moving to YAML definition format. This allows easy +# extension to language construct. For example, new properties could be easily added to +# exisiting specification. +# +# +# IMPORTANT NOTE +# ============== +# +# Below documentation is from old text specification (i.e. nmod.def). We converted +# old text specification to YAML format which we will describe at the end. We should +# convert all below documentation/notes for YAML format. For now, keeping old documentation +# as it has valuable information about how/why specific types were choose for AST. +# +# +# TEXT BASED RULE SPECIFICATION (OLD, SKIP UP TO YAML SECTION) +# ============================================================ +# +# FORMT : +# ===== +# +# Here is brief information about the format of rules specification itself: +# +# To define an abstract class "ChildType", we use below specification: +# +# BaseType:ChildType => +# +# The member variable could be an optional or vector type. This is captured by symbol ? and * +# +# BaseType:ChildType => Member1Type ?Member2Type *Member3Type +# +# In above specification the ChildType class has three member variables: +# +# mname1 : is a member variable of type Member1Type +# mname2 : is an optional variable i.e. pointer of type Member2Type +# mname3 : is a vector variable of type Member3Type +# +# Above specification will generate class definition lik : +# +# class ChildType: public BaseType { +# private: +# Member1Type mname1; +# Member2Type* mname2; +# std::vector mname3; +# }; +# +# NOTE : Many member variables need to hold subclasses (due to various optimization). +# Hence all member variables are now of type pointer. +# +# Some parser actions add new node while parsing the input. This requires "addMemberType" +# method. This is specified using '+' decorator for that member as: +# +# BaseType:ChildType => *MemberType<+name> +# +# In order to print AST back to NMODL format, we need a way to map every AST node +# back to NMODL. In order to achieve this we introduced node "taging". Every node +# can have prefix, printing name, separator and suffix. For example, if we have +# following specification: +# +# BaseType:ChildType("MYNAME") => +# +# When we will print ChildType class, it will be written as "MYNAME" to NMODL file. +# This will be more clear when we will look the examples. +# +# The member variable can have prefix, suffix and separator specification. The +# simple syntax is ("prefix:suffix"). If the member is of type vector, then it +# could have separator specification as ("prefix:suffix:separator). The separator +# is used usually for vector data types. Note that the member could have value as +# well as prefix, suffix and separator. +# +# These rules are best illustrated by following example: +# +# BaseType:ChildType => Member1Type(":=") Member2Type(" USEION :") *Member3Type("::,") +# +# Above specification will be translated as: +# +# (":=") => prefix is empty, followed by value of name1, followed by suffix "=" +# (" USEION :") => prefix is " USEION " followed by value of name2 +# ("::,") => prefix is empty, every variable (except last) of name3 will be +# separated by comma, no suffix +# +# Consider function definition which has arguments as vector. Typically we will define '(' as prfix +# and ')' as suffix for the argument node. But if there are no arguments to function then we have to +# still print '()' i.e. prefix and suffix must be printed. This is not desirable in all cases but few. +# And not only for function call arguments but for reaction statements etc. +# To enforce this rule we prepend '!' to the prefix or suffix. For example: +# +# Expression:FunctionCall => Name *Expression("!(:!):,") +# +# This was so naive! But we have removed with YAML specification! +# +# REFERENCES: +# +# 1] Writing Your Own Toy Compiler Using Flex, Bison and LLVM +# http://gnuu.org/2009/09/18/writing-your-own-toy-compiler/ +# +# 2] Use of abstract language: https://github.com/jazariethach/project6_CS160.git +# Github repository is not available anymore. You cab look at +# /Users/kumbhar/workarena/repos/personal/git_compiler_projects/project6_CS160 +# or bitbucket fork) +# +# Some projects [1] use AstNode as a base class for all node types. The main base class AstNode +# is defined by in python code itself and rest are derived from it. +# +# Abstract base classes for AST specification: +# +# AST:Expression => +# AST:Statement => +# Expression:Identifier => +# Expression:Block => +# Expression:Number => +# +# Every node which can have "name" is of type String: +# +# Expression:String => std::string +# +# Basic data types like int, float and double. Note that NMODL supprts macro where users +# usually define flags or integer. Hence Integer can have associated macro name: +# +# Number:Integer => int ?Name +# Number:Float => float +# Number:Double => double +# Number:Boolean => int +# +# 'Name' and 'name' is used differently in the NMODL grammar specification. 'Name' could be +# just string whereas 'name' could be PRIME. 'Name' and 'NAME' are same in AST context: +# +# Identifier:Name => String +# +# Prime has name and order. Note that the variable name "order" is checked +# in the ast generator. It's hardcoded in node_types.py as PRIME_ORDER_VAR: +# +# Identifier:PrimeName => String Integer +# +# VarName could have name and Index as an Expression. +# Pair of Name, Expression could be represented by Indexname and hence Identifier +# is added as a member variable: +# +# Identifier:VarName => Identifier ?Integer("@:") +# +# Todo : Verify this is correctly working with IndexedName and being handled correctly +# +# IndexedName has Name and integer index but for 'matchname' grammar we need index as +# 'Name'. Hence 'index' is now Expression to take generic form: +# +# Identifier:IndexedName => Identifier Expression("[:]") +# +# Units is none or single Unit: +# +# Expression:Unit => String("(:)") +# +# UNITON and UNITOFF statements are represented by TRUE or FALSE. UnitStateType +# is enum to represent this state: +# +# Statement:UnitState => UnitStateType +# +# Unit definition has double value and unit name: +# +# Expression:DoubleUnit => Double ?Unit +# +# Argument coule be name with units or 'name' that could be prime. Hence +# adding Identifier as a member variable: +# +# Identifier:Argument => Identifier ?Unit +# +# Local variable: +# +# Expression:LocalVar => Identifier +# +# Local statement in NMODL to represent the list of local variables: +# +# Statement:LocalListStatement("LOCAL") => *LocalVar("::,") +# +# +# Limits is the range of double value: +# +# Expression:Limits => Double("<:,") Double(":>") +# +# Number range variable could int or double range: +# +# Expression:NumberRange => Number("<:,") Number(":>") +# +# Program represents entire NMODL file or module. This could have one or more +# blocks (like INITIAL, STATE), methods and statements like include, define: +# +# Program => *Statement<+statements> *Block<+blocks> +# +# Title, Macro definition and include statement: +# +# Statement:Model("TITLE") => String +# Statement:Define("DEFINE") => Name<name> Integer<value> +# Statement:Include("INCLUDE") => String<filename> +# +# Parameter block and statement within this block: +# +# Block:ParamBlock("PARAMETER") => *ParamAssign<statements> +# Statement:ParamAssign => Identifier<name> ?Number<value>("=:") ?Unit<unit> ?Limits<limit> +# +# Step block and statement within this block: +# +# Block:StepBlock("STEPPED") => *Stepped<statements> +# Statement:Stepped => Name<name> *Number<values>("=::,") Unit<unit> +# +# +# Independent block and statement within this block. Note that SWEEP is not part +# of IndependentDef but will be set from bison action: +# +# Block:IndependentBlock("INDEPENDENT") => *IndependentDef<definitions> +# Statement:IndependentDef => ?Boolean<sweep> +# Name<name> +# Number<from>(" FROM :") +# Number<to>(" TO :") +# Integer<with>(" WITH :") +# ?Number<opstart>(" START :") +# Unit<unit> +# +# Dependent block and statement within this block. The 'name' could be Name or +# PrimeName and hence using Identifier. DependentDef is also used in State +# block and hence except name everything needs to be optional: +# +# Statement:DependentDef => Identifier<name>(": ") +# ?Integer<index>("[:]") +# ?Number<from>(" FROM :") +# ?Number<to>(" TO :") +# ?Number<opstart>(" START : ") +# ?Unit<unit> +# ?Double<abstol>(" <:> ") +# Block:DependentBlock("ASSIGNED") => *DependentDef<definitions> +# Block:StateBlock("STATE") => *DependentDef<definitions> +# +# Plot declaration. PlotVar is 'name' and hence could just IndexedName or Name or prime: +# Todo : check if existing variable could be used +# +# Block:PlotBlock => PlotDeclaration<plot> +# Statement:PlotDeclaration("PLOT ") => *PlotVar<pvlist>("::,") PlotVar<name>(" VS :") +# Expression:PlotVar => Identifier<name>(" :") ?Integer<index>("[:]") +# +# Initial block: +# +# Block:InitialBlock("INITIAL") => StatementBlock<statementblock> +# +# Constructor block: +# +# Block:ConstructorBlock("CONSTRUCTOR") => StatementBlock<statementblock> +# +# Destructor block: +# +# Block:DestructorBlock("DESTRUCTOR") => StatementBlock<statementblock> +# +# Condustance staement added for CoreNeuronmoptimizations: +# +# Statement:ConductanceHint("CONDUCTANCE") => Name<conductance> ?Name<ion>(" USEION :") +# +# 1] is using ExpressionStatement class which holds Expression and used for may blocks in bison grammar : +# +# Statement:ExpressionStatement => Expression<expression> +# +# Protect statement: +# +# Statement:ProtectStatement("PROTECT") => Expression<expression> +# +# List of statements enclosed by opening and closing curly braces. +# Todo: One of the production of ostmt has stmtlist and check how to handle this: +# +# Block:StatementBlock => *Statement<statements> +# +# To translate AST back to NMODL, we need method to print operators and hence adding classes +# for binary operators: +# +# Expression:BinaryOperator => BinaryOp<value> +# Expression:UnaryOperator => UnaryOp<value> +# Expression:ReactionOperator => ReactionOp<value> +# +# Binary or Uniary expressions: +# +# Expression:BinaryExpression => Expression<lhs> BinaryOperator<op> Expression<rhs> +# Expression:UnaryExpression => UnaryOperator<op> Expression<expression> +# +# After reading all bison productions it is clear that two basic classes derived from Expression +# are needed : LinEquation and NonLinEuation: +# Todo : add more NMODL tests for validation +# +# Expression:NonLinEuation("~") => Expression<lhs>(":=") Expression<rhs> +# Expression:LinEquation("~") => Expression<leftlinexpr>(":=") Expression<linexpr> +# +# Function call (must be expression because it could be called in expression definition): +# +# Expression:FunctionCall => Name<name> *Expression<arguments>("!(:!):,") +# +# From statement: +# +# Statement:FromStatement("FROM") => Name<name>(" : ") +# Expression<from>("=:") +# Expression<to>(" TO :") +# ?Expression<opinc>(" BY :") +# StatementBlock<statementblock> +# +# Forall statement: +# +# Statement:ForAllStatement("FORALL") => Name<name>(" : ") StatementBlock<statementblock> +# +# While statement: +# +# Statement:WhileStatement("WHILE") => Expression<condition>("(:)") StatementBlock<statementblock> +# +# If statement: +# +# Statement:IfStatement("IF") => Expression<condition>("(:)") +# StatementBlock<statementblock> +# *ElseIfStatement<elseifs> +# ?ElseStatement<elses> +# +# Else if statement: +# +# Statement:ElseIfStatement("ELSE IF") => Expression<condition>("(:)") StatementBlock<statementblock> +# +# Else statement: +# +# Statement:ElseStatement("ELSE") => StatementBlock<statementblock> +# +# Derivative block: +# +# Block:DerivativeBlock("DERIVATIVE") => Name<name> StatementBlock<statementblock> +# +# Linear block: +# +# Block:LinearBlock("LINEAR") => Name<name> *Name<solvefor>(" SOLVEFOR :") StatementBlock<statementblock> +# +# Non-linear block: +# +# Block:NonLinearBlock("NONLINEAR") => Name<name> *Name<solvefor>(" SOLVEFOR :") StatementBlock<statementblock> +# +# Discrete block: +# +# Block:DiscreteBlock("DISCRETE") => Name<name> StatementBlock<statementblock> +# +# Partial block: +# +# Block:PartialBlock("PARTIAL") => Name<name> StatementBlock<statementblock> +# +# Partial equation: +# +# Statement:PartialEquation => PrimeName<prime> Name<name1> Name<name2> Name<name3> +# +# FirstLastType could be just an enum but added for convenience for printing NMODL: +# +# Expression:FirstLastTypeIndex => FirstLastType<value> +# Statement:PartialBoundary("~") => ?Name<del>(": ") +# Identifier<name> +# ?FirstLastTypeIndex<index>("[:]") +# ?Expression<expression>("=:") +# ?Name<name1>("=:*") +# ?Name<del2>(":(") +# ?Name<name2>(":)") +# ?Name<name3>("+:") +# +# Function and Function table. Note that argument is pair of <name, unit> for function table: +# +# Block:FunctionTableBlock("FUNCTION_TABLE") => Name<name> *Argument<arguments>("!(:!):,") ?Unit<unit> +# Block:FunctionBlock("FUNCTION") => Name<name> *Argument<arguments>("!(:!):,") +# ?Unit<unit> StatementBlock<statementblock> +# +# Procedure and NetReceive functions: +# +# Block:ProcedureBlock("PROCEDURE") => Name<name> +# *Argument<arguments>("!(:!):,") +# ?Unit<unit> StatementBlock<statementblock> +# Block:NetReceiveBlock("NET_RECEIVE") => *Argument<arguments>("!(:!):,") +# StatementBlock<statementblock> +# +# Usually solve block is a single statement but 'iferr' could be also a block +# and hence it is appropriate to define it as a Block like original bison +# specification: +# +# Block:SolveBlock("SOLVE") => Name<name> +# ?Name<method>(" METHOD :") +# StatementBlock<ifsolerr>("IFERROR:") +# +# Breakpoint block: +# +# Block:BreakpointBlock("BREAKPOINT") => StatementBlock<statementblock> +# +# Terminal block: +# +# Block:TerminalBlock("TERMINAL") => StatementBlock<statementblock> +# +# Before/After block could be of any type and hence added enum for BAType: +# +# Block:BeforeBlock("BEFORE") => BABlock<block> +# Block:AfterBlock("AFTER") => BABlock<block> +# Expression:BABlockType => BAType<value> +# Block:BABlock => BABlockType<type> StatementBlock<statementblock> +# +# WatchStatement has the form : WATCH (v < vh) down, (v > vth) up +# which is actually an expression. From current tests this seems sufficient. +# Watch statement usually use macro as an integer value (which is now handled by integer) : +# +# Statement:WatchStatement("WATCH") => *Watch<+statements>("::,") +# Expression:Watch => Expression<expression> Expression<value>(" :") +# +# ForNetcon is also a function but without name or units: +# +# Block:ForNetcon("FOR_NETCONS") => *Argument<arguments>("!(:!):,") StatementBlock<statementblock> +# +# Mutex lock / unlock and and Reset statement: +# +# Statement:MutexLock("MUTEXLOCK") => +# Statement:MutexUnlock("MUTEXUNLOCK") => +# Statement:Reset("RESET") => +# +# Sens statement: +# Todo: No test case added yet +# +# Statement:Sens("SENS") => *VarName<senslist>("::,") +# +# Conserve statement which is assignment expression with lhs and rhs: +# +# Statement:Conserve("CONSERVE") => Expression<react> Expression<expr>("=:") +# +# Compartment statement: +# +# Statement:Compartment("COMPARTMENT") => ?Name<name>(":,") +# Expression<expression> +# *Name<names>("{:}: ") +# +# Diffusion statement: +# +# Statement:LDifuse("LONGITUDINAL_DIFFUSION") => ?Name<name>(":,") +# Expression<expression> +# *Name<names>("{:}: ") +# Kinetic block: +# +# Block:KineticBlock("KINETIC") => Name<name> *Name<solvefor> StatementBlock<statementblock> +# +# Reaction statement: +# Todo: check test cases and discuss with Michael +# +# Statement:ReactionStatement("~") => Expression<react1>(": ") +# ReactionOperator<op>(" : ") +# ?Expression<react2>(" : ") +# Expression<expr1>("(:") +# ?Expression<expr2>(",:!)") +# +# Variable used in react production: +# +# Identifier:ReactVarName => ?Integer<value>(" :") VarName<name> +# +# Lag statement: +# +# Statement:LagStatement("LAG") => Identifier<name> Name<byname>(" BY :") +# +# Put or Get queue statement: +# +# Statement:QueueStatement => QueueExpressionType<qype> Identifier<name> +# Expression:QueueExpressionType => QueueType<value> +# +# Match block and match statements: +# Todo: Expression lhs has parenthesis and its better to represent lhs and rhs +# separately rather than binary expression (for printing) +# +# Block:MatchBlock("MATCH") => *Match<matchs> +# Expression:Match => Identifier<name> ?Expression<expression> +# +# Unit block and statements within this block +# +# Block:UnitBlock("UNITS") => *Expression<definitions> +# Expression:UnitDef => Unit<unit1> Unit<unit2>("=:") +# +# Factordef statement (boolean variable gt to decide if '->' symbol exist in expression) +# This is the only case where "gt" member needs a "nmodl name" as "->" +# +# Expression:FactorDef => Name<name>(":=") ?Double<value> Unit<unit1> ?Boolean<gt>("->") ?Unit<unit2> +# +# Constant blocks and statements within this block: +# +# Statement:ConstantStatement => Name<name> Number<value>("=:") ?Unit<unit> +# Block:ConstantBlock("CONSTANT") => *ConstantStatement<statements> +# +# Table statement: +# +# Statement:TableStatement("TABLE") => *Name<tablst>("::,") +# *Name<dependlst>(" DEPEND ::,") +# Expression<from>(" FROM :") +# Expression<to>(" TO :") +# Integer<with>(" WITH :") +# +# Neuron block and Various statement within this block: +# +# Initial design confusion was whether we need separate class for every +# statement type. This is required because when we construct AST, every node +# need to be interpreted differently. This could be though separate class or +# some enum property. It might be straightforward to generate code with different +# classes. +# +# Block:NeuronBlock("NEURON") => StatementBlock<statementblock> +# +# Identifier:ReadIonVar => Name<name> +# Identifier:WriteIonVar => Name<name> +# Identifier:NonspeCurVar => Name<name> +# Identifier:ElectrodeCurVar => Name<name> +# Identifier:SectionVar => Name<name> +# Identifier:RangeVar => Name<name> +# Identifier:GlobalVar => Name<name> +# Identifier:PointerVar => Name<name> +# Identifier:BbcorePointerVar => Name<name> +# Identifier:ExternVar => Name<name> +# Identifier:ThreadsafeVar => Name<name> +# +# Statement types +# Statement:Suffix => Name<type>(": ") Name<name> +# Statement:Useion("USEION") => Name<name> +# *ReadIonVar<readlist>(" READ ::,") +# *WriteIonVar<writelist>(" WRITE ::,") +# ?Valence<valence> +# Statement:Nonspecific("NONSPECIFIC_CURRENT") => *NonspeCurVar<currents>("::,") +# Statement:ElctrodeCurrent("ELECTRODE_CURRENT") => *ElectrodeCurVar<ecurrents>("::,") +# Statement:Section("SECTION") => *SectionVar<sections>("::,") +# Statement:Range("RANGE") => *RangeVar<range_vars>("::,") +# Statement:Global("GLOBAL") => *GlobalVar<global_vars>("::,") +# Statement:Pointer("POINTER") => *PointerVar<pointers>("::,") +# Statement:BbcorePtr("BBCOREPOINTER") => *BbcorePointerVar<bbcore_pointers>("::,") +# Statement:External("EXTERNAL") => *ExternVar<externals>("::,") +# Statement:ThreadSafe("THREADSAFE") => *ThreadsafeVar<threadsafe>("::,") +# +# Verbatim and Comment statements +# Todo : Need a way to distinguish inline comment and block comment +# +# Statement:Verbatim("VERBATIM") => String<statement>(":ENDVERBATIM") +# Statement:Comment("COMMENT") => String<comment>(":ENDCOMMENT") +# +# Valence expression for use ion statement +# +# Expression:Valence => Name<type>(" : ") Double<value> +# +# +# +# YAML BASED RULE SPECIFICATION +# ============================= +# +# Text based specification described above is very limiting in terms adding extra properties. +# YAML based specification allows easy addition of new properties. Below are some comments +# about the properties and specification : +# +# 1. Every key in YAML specification is AST node +# 2. The hierarchy defines parent-child class relationship +# 3. members is an array of member variables of the class +# 4. optional : indicates if variable is optional in bison specification (i.e. pointer) +# 5. type : type of the member +# 6. vector : if member variable is std::vector type +# 7. prefix/suffix : +# value : indicates the value (i.e. string here) of prefix/suffix itself +# force : print value always even if member if null pointer itself (e.g. arguments) +# 8. separator : for the vector data type, the separator for prinitng elements +# 9. getname : add getName() method to class and return corresponding value as name +# 10. add : need to have addMemberType method for corresponding variable (todo : remove this) +# 11. order of the members is important and determines how constructors/nmodl visitor get +# defined from python code generators. +# +# TODO : Add detailed information about YAML specification by porting old text based rule +# specification + +- AST: + - Expression: + - String: + members: + - value: + type: std::string + - Number: + - Integer: + members: + - value: + type: int + - macroname: + type: Name + optional: true + - Float: + members: + - value: + type: float + - Double: + members: + - value: + type: double + - Boolean: + members: + - value: + type: int + - Identifier: + - Name: + members: + - value: + type: String + getname: true + - PrimeName: + members: + - value: + type: String + getname: true + - order: + type: Integer + - VarName: + members: + - name: + type: Identifier + getname: true + - at_index: + type: Integer + optional: true + prefix: {value: "@"} + - IndexedName: + members: + - name: + type: Identifier + getname: true + - index: + type: Expression + prefix: {value: "["} + suffix: {value: "]"} + - Argument: + members: + - name: + type: Identifier + getname: true + - unit: + type: Unit + optional: true + - ReactVarName: + members: + - value: + type: Integer + optional: true + prefix: {value: " "} + - name: + type: VarName + getname: true + - ReadIonVar: + members: + - name: + type: Name + getname: true + - WriteIonVar: + members: + - name: + type: Name + getname: true + - NonspeCurVar: + members: + - name: + type: Name + getname: true + - ElectrodeCurVar: + members: + - name: + type: Name + getname: true + - SectionVar: + members: + - name: + type: Name + getname: true + - RangeVar: + members: + - name: + type: Name + getname: true + - GlobalVar: + members: + - name: + type: Name + getname: true + - PointerVar: + members: + - name: + type: Name + getname: true + - BbcorePointerVar: + members: + - name: + type: Name + getname: true + - ExternVar: + members: + - name: + type: Name + getname: true + - ThreadsafeVar: + members: + - name: + type: Name + getname: true + - Block: + - ParamBlock: + nmodl: PARAMETER + members: + - statements: + type: ParamAssign + vector: true + - StepBlock: + nmodl: STEPPED + members: + - statements: + type: Stepped + vector: true + - IndependentBlock: + nmodl: INDEPENDENT + members: + - definitions: + type: IndependentDef + vector: true + - DependentBlock: + nmodl: ASSIGNED + members: + - definitions: + type: DependentDef + vector: true + - StateBlock: + nmodl: STATE + members: + - definitions: + type: DependentDef + vector: true + - PlotBlock: + members: + - plot: + type: PlotDeclaration + - InitialBlock: + nmodl: INITIAL + members: + - statementblock: + type: StatementBlock + - ConstructorBlock: + nmodl: CONSTRUCTOR + members: + - statementblock: + type: StatementBlock + - DestructorBlock: + nmodl: DESTRUCTOR + members: + - statementblock: + type: StatementBlock + - StatementBlock: + members: + - statements: + type: Statement + vector: true + - DerivativeBlock: + nmodl: DERIVATIVE + members: + - name: + type: Name + getname: true + - statementblock: + type: StatementBlock + - LinearBlock: + nmodl: LINEAR + members: + - name: + type: Name + getname: true + - solvefor: + type: Name + vector: true + prefix: {value: " SOLVEFOR "} + - statementblock: + type: StatementBlock + - NonLinearBlock: + nmodl: NONLINEAR + members: + - name: + type: Name + getname: true + - solvefor: + type: Name + vector: true + prefix: {value: " SOLVEFOR "} + - statementblock: + type: StatementBlock + - DiscreteBlock: + nmodl: DISCRETE + members: + - name: + type: Name + getname: true + - statementblock: + type: StatementBlock + - PartialBlock: + nmodl: PARTIAL + members: + - name: + type: Name + getname: true + - statementblock: + type: StatementBlock + - FunctionTableBlock: + nmodl: FUNCTION_TABLE + members: + - name: + type: Name + getname: true + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: "," + - unit: + type: Unit + optional: true + - FunctionBlock: + nmodl: FUNCTION + members: + - name: + type: Name + getname: true + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: "," + - unit: + type: Unit + optional: true + - statementblock: + type: StatementBlock + - ProcedureBlock: + nmodl: PROCEDURE + members: + - name: + type: Name + getname: true + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: "," + - unit: + type: Unit + optional: true + - statementblock: + type: StatementBlock + - NetReceiveBlock: + nmodl: NET_RECEIVE + members: + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: "," + - statementblock: + type: StatementBlock + - SolveBlock: + nmodl: SOLVE + members: + - name: + type: Name + - method: + type: Name + optional: true + prefix: {value: " METHOD "} + - ifsolerr: + type: StatementBlock + prefix: {value: IFERROR} + - BreakpointBlock: + nmodl: BREAKPOINT + members: + - statementblock: + type: StatementBlock + - TerminalBlock: + nmodl: TERMINAL + members: + - statementblock: + type: StatementBlock + - BeforeBlock: + nmodl: BEFORE + members: + - block: + type: BABlock + - AfterBlock: + nmodl: AFTER + members: + - block: + type: BABlock + - BABlock: + members: + - type: + type: BABlockType + - statementblock: + type: StatementBlock + - ForNetcon: + nmodl: FOR_NETCONS + members: + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: "," + - statementblock: + type: StatementBlock + - KineticBlock: + nmodl: KINETIC + members: + - name: + type: Name + getname: true + - solvefor: + type: Name + vector: true + - statementblock: + type: StatementBlock + - MatchBlock: + nmodl: MATCH + members: + - matchs: + type: Match + vector: true + - UnitBlock: + nmodl: UNITS + members: + - definitions: + type: Expression + vector: true + - ConstantBlock: + nmodl: CONSTANT + members: + - statements: + type: ConstantStatement + vector: true + - NeuronBlock: + nmodl: NEURON + members: + - statementblock: + type: StatementBlock + - Unit: + members: + - name: + type: String + getname: true + prefix: {value: "("} + suffix: {value: ")"} + - DoubleUnit: + members: + - values: + type: Double + - unit: + type: Unit + optional: true + - LocalVar: + members: + - name: + type: Identifier + getname: true + - Limits: + members: + - min: + type: Double + prefix: {value: "<"} + suffix: {value: ","} + - max: + type: Double + suffix: {value: ">"} + - NumberRange: + members: + - min: + type: Number + prefix: {value: "<"} + suffix: {value: ","} + - max: + type: Number + suffix: {value: ">"} + + - PlotVar: + members: + - name: + type: Identifier + prefix: {value: " "} + - index: + type: Integer + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - BinaryOperator: + members: + - value: + type: BinaryOp + - UnaryOperator: + members: + - value: + type: UnaryOp + - ReactionOperator: + members: + - value: + type: ReactionOp + - BinaryExpression: + members: + - lhs: + type: Expression + - op: + type: BinaryOperator + - rhs: + type: Expression + - UnaryExpression: + members: + - op: + type: UnaryOperator + - expression: + type: Expression + - NonLinEuation: + nmodl: "~" + members: + - lhs: + type: Expression + suffix: {value: "="} + - rhs: + type: Expression + - LinEquation: + nmodl: "~" + members: + - leftlinexpr: + type: Expression + suffix: {value: "="} + - linexpr: + type: Expression + - FunctionCall: + members: + - name: + type: Name + - arguments: + type: Expression + vector: true + separator: "," + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + - FirstLastTypeIndex: + members: + - value: + type: FirstLastType + - Watch: + members: + - expression: + type: Expression + - value: + type: Expression + prefix: {value: " "} + - QueueExpressionType: + members: + - value: + type: QueueType + - Match: + members: + - name: + type: Identifier + - expression: + type: Expression + optional: true + - BABlockType: + members: + - value: + type: BAType + - UnitDef: + members: + - unit1: + type: Unit + getname: true + - unit2: + type: Unit + prefix: {value: "="} + - FactorDef: + members: + - name: + type: Name + getname: true + suffix: {value: "="} + - value: + type: Double + optional: true + - unit1: + type: Unit + - gt: + type: Boolean + nmodl: "->" + optional: true + - unit2: + type: Unit + optional: true + - Valence: + members: + - type: + type: Name + prefix: {value: " "} + suffix: {value: " "} + - value: + type: Double + + - Statement: + - UnitState: + members: + - value: + type: UnitStateType + - LocalListStatement: + nmodl: LOCAL + members: + - variables: + type: LocalVar + vector: true + separator: "," + - Model: + nmodl: TITLE + members: + - title: + type: String + - Define: + nmodl: DEFINE + members: + - name: + type: Name + suffix: {value: " "} + - value: + type: Integer + - Include: + nmodl: INCLUDE + members: + - filename: + type: String + - ParamAssign: + members: + - name: + type: Identifier + getname: true + - value: + type: Number + optional: true + prefix: {value: "="} + - unit: + type: Unit + optional: true + - limit: + type: Limits + optional: true + - Stepped: + members: + - name: + type: Name + - values: + type: Number + vector: true + prefix: {value: "="} + separator: "," + - unit: + type: Unit + - IndependentDef: + members: + - sweep: + type: Boolean + optional: true + - name: + type: Name + - from: + type: Number + prefix: {value: " FROM "} + - to: + type: Number + prefix: {value: " TO "} + - with: + type: Integer + prefix: {value: " WITH "} + - opstart: + type: Number + prefix: {value: " START "} + optional: true + - unit: + type: Unit + - DependentDef: + members: + - name: + type: Identifier + getname: true + suffix: {value: " "} + - index: + type: Integer + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - from: + type: Number + prefix: {value: " FROM "} + optional: true + - to: + type: Number + prefix: {value: " TO "} + optional: true + - opstart: + type: Number + prefix: {value: " START "} + suffix: {value: " "} + optional: true + - unit: + type: Unit + optional: true + - abstol: + type: Double + prefix: {value: " <"} + suffix: {value: "> "} + optional: true + - PlotDeclaration: + nmodl: "PLOT " + members: + - pvlist: + type: PlotVar + vector: true + separator: "," + - name: + type: PlotVar + prefix: {value: " VS "} + - ConductanceHint: + nmodl: "CONDUCTANCE" + members: + - conductance: + type: Name + - ion: + type: Name + optional: true + prefix: {value: " USEION "} + - ExpressionStatement: + members: + - expression: + type: Expression + - ProtectStatement: + nmodl: PROTECT + members: + - expression: + type: Expression + - FromStatement: + nmodl: FROM + members: + - name: + type: Name + prefix: {value: " "} + suffix: {value: " "} + - from: + type: Expression + prefix: {value: "="} + - to: + type: Expression + prefix: {value: " TO "} + - opinc: + type: Expression + prefix: {value: " BY "} + optional: true + - statementblock: + type: StatementBlock + - ForAllStatement: + nmodl: FORALL + members: + - name: + type: Name + prefix: {value: " "} + suffix: {value: " "} + - statementblock: + type: StatementBlock + - WhileStatement: + nmodl: WHILE + members: + - condition: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - statementblock: + type: StatementBlock + - IfStatement: + nmodl: IF + members: + - condition: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - statementblock: + type: StatementBlock + - elseifs: + type: ElseIfStatement + vector: true + - elses: + type: ElseStatement + optional: true + - ElseIfStatement: + nmodl: ELSE IF + members: + - condition: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - statementblock: + type: StatementBlock + - ElseStatement: + nmodl: ELSE + members: + - statementblock: + type: StatementBlock + - PartialEquation: + members: + - prime: + type: PrimeName + - name1: + type: Name + - name2: + type: Name + - name3: + type: Name + - PartialBoundary: + nmodl: "~" + members: + - del: + type: Name + optional: true + suffix: {value: " "} + - name: + type: Identifier + - index: + type: FirstLastTypeIndex + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - expression: + type: Expression + optional: true + prefix: {value: "="} + - name1: + type: Name + optional: true + prefix: {value: "="} + suffix: {value: "*"} + - del2: + type: Name + optional: true + suffix: {value: "("} + - name2: + type: Name + optional: true + suffix: {value: ")"} + - name3: + type: Name + optional: true + prefix: {value: "+"} + - WatchStatement: + nmodl: WATCH + members: + - statements: + type: Watch + vector: true + separator: "," + add: true + - MutexLock: + nmodl: MUTEXLOCK + - MutexUnlock: + nmodl: MUTEXUNLOCK + - Reset: + nmodl: RESET + - Sens: + nmodl: SENS + members: + - senslist: + type: VarName + vector: true + separator: "," + - Conserve: + nmodl: CONSERVE + members: + - react: + type: Expression + - expr: + type: Expression + prefix: {value: "="} + - Compartment: + nmodl: COMPARTMENT + members: + - name: + type: Name + optional: true + suffix: {value: ","} + - expression: + type: Expression + - names: + type: Name + vector: true + prefix: {value: "{"} + suffix: {value: "}"} + separator: " " + - LDifuse: + nmodl: LONGITUDINAL_DIFFUSION + members: + - name: + type: Name + optional: true + suffix: {value: ","} + - expression: + type: Expression + - names: + type: Name + vector: true + prefix: {value: "{"} + suffix: {value: "}"} + separator: " " + - ReactionStatement: + nmodl: "~" + members: + - react1: + type: Expression + suffix: {value: " "} + - op: + type: ReactionOperator + prefix: {value: " "} + suffix: {value: " "} + - react2: + type: Expression + prefix: {value: " "} + suffix: {value: " "} + optional: true + - expr1: + type: Expression + prefix: {value: "("} + - expr2: + type: Expression + prefix: {value: ","} + suffix: {value: ")", force: true} + optional: true + - LagStatement: + nmodl: LAG + members: + - name: + type: Identifier + - byname: + type: Name + prefix: {value: " BY "} + - QueueStatement: + members: + - qype: + type: QueueExpressionType + - name: + type: Identifier + - ConstantStatement: + members: + - name: + type: Name + - value: + type: Number + prefix: {value: "="} + - unit: + type: Unit + optional: true + - TableStatement: + nmodl: TABLE + members: + - tablst: + type: Name + vector: true + separator: "," + - dependlst: + type: Name + vector: true + prefix: {value: " DEPEND "} + separator: "," + - from: + type: Expression + prefix: {value: " FROM "} + - to: + type: Expression + prefix: {value: " TO "} + - with: + type: Integer + prefix: {value: " WITH "} + - Suffix: + members: + - type: + type: Name + suffix: {value: " "} + - name: + type: Name + - Useion: + nmodl: USEION + members: + - name: + type: Name + - readlist: + type: ReadIonVar + vector: true + prefix: {value: " READ "} + separator: "," + - writelist: + type: WriteIonVar + vector: true + prefix: {value: " WRITE "} + separator: "," + - valence: + type: Valence + optional: true + - Nonspecific: + nmodl: NONSPECIFIC_CURRENT + members: + - currents: + type: NonspeCurVar + vector: true + separator: "," + - ElctrodeCurrent: + nmodl: ELECTRODE_CURRENT + members: + - ecurrents: + type: ElectrodeCurVar + vector: true + separator: "," + - Section: + nmodl: SECTION + members: + - sections: + type: SectionVar + vector: true + separator: "," + - Range: + nmodl: RANGE + members: + - range_vars: + type: RangeVar + vector: true + separator: "," + - Global: + nmodl: GLOBAL + members: + - global_vars: + type: GlobalVar + vector: true + separator: "," + - Pointer: + nmodl: POINTER + members: + - pointers: + type: PointerVar + vector: true + separator: "," + - BbcorePtr: + nmodl: BBCOREPOINTER + members: + - bbcore_pointers: + type: BbcorePointerVar + vector: true + separator: "," + - External: + nmodl: EXTERNAL + members: + - externals: + type: ExternVar + vector: true + separator: "," + - ThreadSafe: + nmodl: THREADSAFE + members: + - threadsafe: + type: ThreadsafeVar + vector: true + separator: "," + - Verbatim: + nmodl: VERBATIM + members: + - statement: + type: String + suffix: {value: "ENDVERBATIM"} + - Comment: + nmodl: COMMENT + members: + - comment: + type: String + suffix: {value: "ENDCOMMENT"} + +- Program: + members: + - statements: + type: Statement + vector: true + add: true + - blocks: + type: Block + vector: true + add: true diff --git a/src/nmodl/language/node_types.py b/src/nmodl/language/node_types.py new file mode 100644 index 0000000000..7e59cf3b35 --- /dev/null +++ b/src/nmodl/language/node_types.py @@ -0,0 +1,152 @@ +"""Define node and data types used in parser and ast generator + +While generating ast and visitors we need to lookup for base data types, +nmodl blocks defining variables, nmodl constructs that define variables +etc. These all data types are defined in this file. + +\todo : Other way is to add extra attributes to YAML language definitions. +YAML will become more verbose but advantage would be that the YAML will be +self sufficien, single definition file instead of hard-coded names here. +We should properties like is_enum, data_type, is_symbol, is_global etc. +""" + +# yapf: disable +BASE_TYPES = ["short", + "int", + "float", + "double", + "std::string", + "BinaryOp", + "UnaryOp", + "ReactionOp", + "FirstLastType", + "QueueType", + "BAType", + "UnitStateType"] + +# base types which are enums +ENUM_BASE_TYPES = ["BinaryOp", + "UnaryOp", + "ReactionOp", + "FirstLastType", + "QueueType", + "BAType", + "UnitStateType"] + +# data types and then their return types +DATA_TYPES = {"Boolean": "bool", + "Integer": "int", + "Float": "float", + "Double": "double", + "String": "std::string", + "BinaryOperator": "BinaryOp", + "UnaryOperator": "UnaryOp", + "ReactionOperator": "ReactionOp", + "UnitState": "UnitStateType", + "BABlockType": "BAType", + "QueueExpressionType": "QueueType", + "FirstLastTypeIndex": "FirstLastType"} + +# nodes which will go into symbol table +SYMBOL_VAR_TYPES = ["LocalVar", + "ParamAssign", + "Argument", + "DependentDef", + "UnitDef", + "FactorDef", + "RangeVar", + "ReadIonVar", + "WriteIonVar", + "NonspeCurVar", + "ElectrodeCurVar", + "SectionVar", + "GlobalVar", + "PointerVar", + "BbcorePointerVar", + "ExternVar", + "PrimeName"] + +# block nodes which will go into symbol table +SYMBOL_BLOCK_TYPES = ["FunctionBlock", + "ProcedureBlock", + "DerivativeBlock", + "LinearBlock", + "NonLinearBlock", + "DiscreteBlock", + "PartialBlock", + "KineticBlock", + "FunctionTableBlock"] + +# blocks defining global variables +GLOBAL_BLOCKS = ["NeuronBlock", + "ParamBlock", + "UnitBlock", + "StepBlock", + "IndependentBlock", + "DependentBlock", + "StateBlock", + "ConstantBlock"] + +# when translating back to nmodl, we need print each statement +# to new line. Those nodes are are used from this list. +STATEMENT_TYPES=["Statement", + "IndependentDef", + "DependentDef", + "ParamAssign", + "ConstantStatement"] + +# data types which have token as an argument to the constructor +LEXER_DATA_TYPES = ["Name", + "PrimeName", + "Integer", + "Double", + "String", + "FactorDef"] + +# while printing symbol table we needed setToken() method for StatementBlock and +# hence need to add this +ADDITIONAL_TOKEN_BLOCKS = ["StatementBlock"] + +# for printing NMODL, we need to know which nodes are block types. +# TODO: NEURON block is removed because it has internal statement block +# and we don't want to print extra brace block for NMODL +# We are removing NeuronBlock because it has statement block which +# prints braces already. +BLOCK_TYPES = (GLOBAL_BLOCKS + ADDITIONAL_TOKEN_BLOCKS) +BLOCK_TYPES.remove("NeuronBlock") + +# Note that these are nodes which are not of type pointer in AST. +# This also means that they can't be optional because they will appear +# as value type in AST. +PTR_EXCLUDE_TYPES = ["BinaryOperator", "UnaryOperator", "ReactionOperator"] + +# these node names are explicitly added because they are used in ast/visitor +# printer classes. In otder to avoid hardcoding in the printer functions, they +# are defined here. +PROGRAM_BLOCK = "Program" +BASE_BLOCK = "Block" +PRIME_NAME_NODE = "PrimeName" +STRING_NODE = "String" +NUMBER_NODE = "Number" +IDENTIFIER_NODE = "Identifier" +NAME_NODE = "Name" +BOOLEAN_NODE = "Boolean" +INTEGER_NODE = "Integer" +REACTION_STATEMENT_NODE = "ReactionStatement" +CONSERVE_NODE = "Conserve" +EXPRESSION_NODE = "Expression" +BINARY_EXPRESSION_NODE = "BinaryExpression" +UNARY_EXPRESSION_NODE = "UnaryExpression" +VERBATIM_NODE = "Verbatim" +COMMENT_NODE = "Comment" +INDEPENDENTDEF_NODE = "IndependentDef" +STATEMENT_BLOCK_NODE = "StatementBlock" +UNIT_BLOCK = "UnitBlock" + +# name of variable in prime node which represent order of derivative +ORDER_VAR_NAME = "order" +REACT_VAR_NAME = "react" +REACT2_VAR_NAME = "react2" +SWEEP_VAR_NAME = "sweep" + +# yapf: enable diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py new file mode 100644 index 0000000000..48acd60786 --- /dev/null +++ b/src/nmodl/language/parser.py @@ -0,0 +1,176 @@ +"""Parser for parsing abstract NMODL language definition file + +Abstract definition file of NMODL has custom syntax for defining language +constructs. This module provides basic YAML parsing utility and creates +AST tree nodes. + +""" + +import sys +import yaml +from ast import Node +from argument import Argument +from node_types import * + + +class LanguageParser: + """class to parse abstract language definition YAML file""" + + def __init__(self, filename, debug=False): + self.filename = filename + self.debug = debug + + @classmethod + def is_token(self, name): + """check if the name (i.e. class) is a token type in lexer + + Lexims returned from Lexer have position and hence token object. + Return True if this node is returned by lexer otherwise False + """ + if name in LEXER_DATA_TYPES or name in SYMBOL_BLOCK_TYPES or name in ADDITIONAL_TOKEN_BLOCKS: + return True + else: + return False + + def parse_child_rule(self, child): + """parse child specification and return argument as properties + + Child specification has additional option like list, optional, + getName method etc. + """ + + # there is only one key and it has one value + varname = child.iterkeys().next() + properties = child.itervalues().next() + + # arguments holder for creating tree node + args = Argument() + + # node of the variable + args.varname = varname + + # type i.e. class of the variable + args.class_name = properties['type'] + + + if self.debug: + print 'Child {}, {}'.format(args.varname, args.class_name) + + # if there is add method for member in the class + if 'add' in properties: + args.add_method = properties['add'] + + # if variable is an optional member + if 'optional' in properties: + args.is_optional = properties['optional'] + + # if variable is vector, the separator to use while + # printing back to nmodl + if 'separator' in properties: + args.separator = properties['separator'] + + # if variable if of vector type + if 'vector' in properties: + args.is_vector = properties['vector'] + + # if getNmae method required + if 'getname' in properties: + args.getname_method = properties['getname'] + + # prefix while printing back to NMODL + if 'prefix' in properties: + args.prefix = properties['prefix']['value'] + + # if prefix is compulsory to print in NMODL + if 'force' in properties['prefix']: + args.force_prefix = properties['prefix']['force'] + + # suffix while printing back to NMODL + if 'suffix' in properties: + args.suffix = properties['suffix']['value'] + + # if suffix is compulsory to print in NMODL + if 'force' in properties['suffix']: + args.force_suffix = properties['suffix']['force'] + + return args + + + def parse_yaml_rules(self, nodelist, base_class=None): + abstract_nodes = [] + nodes = [] + + for node in nodelist: + # name of the ast class and it's properties as dictionary + class_name = node.iterkeys().next() + properties = node.itervalues().next() + + # yaml file has abstract classes and their subclasses (i.e. children) as a list + if isinstance(properties, list): + # recursively parse all sub-classes of current abstract class + child_abstract_nodes, child_nodes = self.parse_yaml_rules(properties, class_name) + + # append all parsed subclasses + abstract_nodes.extend(child_abstract_nodes) + nodes.extend(child_nodes) + + # classes like AST which don't have base class + # are not added (we print AST class separately) + if base_class: + args = Argument() + args.base_class = base_class + args.class_name = class_name + node = Node(args) + abstract_nodes.append(node) + nodes.insert(0, node) + if self.debug: + print 'Abstract {}, {}'.format(base_class, class_name) + else: + # name of the node while printing back to NMODL + nmodl_name = properties['nmodl'] if 'nmodl' in properties else None + + # check if we need token for the node + # todo : we will have token for every node + has_token = LanguageParser.is_token(class_name) + + args = Argument() + args.base_class = base_class + args.class_name = class_name + args.nmodl_name = nmodl_name + args.has_token = has_token + + # create tree node and add to the list + node = Node(args) + nodes.append(node) + + if self.debug: + print 'Class {}, {}, {}'.format(base_class, class_name, nmodl_name) + + # now process all children specification + childs = properties['members'] if 'members' in properties else [] + for child in childs: + args = self.parse_child_rule(child) + node.add_child(args) + + # update the abstract nodes + for absnode in abstract_nodes: + for node in nodes: + if absnode.class_name == node.class_name: + node.is_abstract = True + break + + return abstract_nodes, nodes + + def parse_file(self): + """ parse nmodl YAML specification file for AST creation """ + + with open(self.filename, 'r') as stream: + try: + rules = yaml.load(stream) + # abstract nodes are not used though + abstract_nodes, nodes = self.parse_yaml_rules(rules) + except yaml.YAMLError as e: + print "Error while parsing YAML definition file {0} : {1}".format(self.filename, e.strerror) + sys.exit(1) + + return nodes diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py new file mode 100644 index 0000000000..ba4c1e7da1 --- /dev/null +++ b/src/nmodl/language/visitors_printer.py @@ -0,0 +1,20 @@ +from base_printer import * + + +class AbstractVisitorPrinter(DeclarationPrinter): + """Prints abstract base class for all visitor implementations""" + + def headers(self): + pass + + def class_comment(self): + self.writer.write_line("/* Abstract base class for all visitor implementations */") + + def public_declaration(self): + self.writer.write_line("public:", post_gutter=1) + + for node in self.nodes: + line = "virtual void visit" + node.class_name + "(" + node.class_name + "* node) = 0;" + self.writer.write_line(line) + + self.writer.decrease_gutter() diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 3011254578..1a3590b2fc 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -11,6 +11,11 @@ set(NMODL_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp ) +set_source_files_properties( + ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + PROPERTIES GENERATED TRUE +) + # command to generate nmodl parser add_custom_command ( COMMAND ${BISON_EXECUTABLE} @@ -22,6 +27,7 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/position.hh OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/stack.hh DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS pyastgen COMMENT "-- NOCMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" ) @@ -35,7 +41,6 @@ add_custom_command ( COMMENT "-- NOCMODL : GENERATING VERBATIM PARSER WITH BISON! --" ) - # command to generate nmodl lexer add_custom_command( COMMAND ${FLEX_EXECUTABLE} @@ -75,4 +80,4 @@ add_library(lexer ${AST_SOURCE_FILES}) add_executable(nocmodl_lexer main.cpp) -target_link_libraries(nocmodl_lexer lexer) +target_link_libraries(nocmodl_lexer lexer) \ No newline at end of file diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 926d34a5a7..0561b26583 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -236,7 +236,7 @@ %type <ast::statement_ptr> astmt %type <ast::statementblock_ptr> stmtlist %type <ast::localliststatement_ptr> locallist -%type <ast::localvariable_list> locallist1 +%type <ast::localvar_list> locallist1 %type <ast::varname_ptr> varname %type <ast::expression_list> exprlist %type <ast::define_ptr> define1 @@ -275,7 +275,7 @@ %type <ast::watch_ptr> watch1 %type <ast::fornetcon_ptr> fornetcon %type <ast::plotdeclaration_ptr> plotdecl -%type <ast::plotvariable_list> pvlist +%type <ast::plotvar_list> pvlist %type <ast::constantblock_ptr> constblk %type <ast::constantstatement_list> conststmt %type <ast::matchblock_ptr> matchblk @@ -305,7 +305,7 @@ %type <ast::argument_list> arglist1 %type <ast::integer_ptr> locoptarray %type <ast::neuronblock_ptr> neuronblk -%type <ast::nrnuseion_ptr> nrnuse +%type <ast::useion_ptr> nrnuse %type <ast::statement_list> nrnstmt %type <ast::readionvar_list> nrnionrlist %type <ast::writeionvar_list> nrnionwlist @@ -741,7 +741,7 @@ stateblk : STATE "{" depbody "}" plotdecl : PLOT pvlist VS name optindex { - $$ = new ast::PlotDeclaration($2, new ast::PlotVariable($4,$5)); + $$ = new ast::PlotDeclaration($2, new ast::PlotVar($4,$5)); } | PLOT error { @@ -752,15 +752,15 @@ plotdecl : PLOT pvlist VS name optindex pvlist : name optindex { - $$ = ast::PlotVariableVector(); - auto variable = new ast::PlotVariable($1, $2); - $$.push_back(std::shared_ptr<ast::PlotVariable>(variable)); + $$ = ast::PlotVarVector(); + auto variable = new ast::PlotVar($1, $2); + $$.push_back(std::shared_ptr<ast::PlotVar>(variable)); } | pvlist "," name optindex { $$ = $1; - auto variable = new ast::PlotVariable($3, $4); - $$.push_back(std::shared_ptr<ast::PlotVariable>(variable)); + auto variable = new ast::PlotVar($3, $4); + $$.push_back(std::shared_ptr<ast::PlotVar>(variable)); } ; @@ -855,23 +855,23 @@ locallist : LOCAL locallist1 locallist1 : NAME locoptarray { - $$ = ast::LocalVariableVector(); + $$ = ast::LocalVarVector(); if($2) { - auto variable = new ast::LocalVariable(new ast::IndexedName($1, $2)); - $$.push_back(std::shared_ptr<ast::LocalVariable>(variable)); + auto variable = new ast::LocalVar(new ast::IndexedName($1, $2)); + $$.push_back(std::shared_ptr<ast::LocalVar>(variable)); } else { - auto variable = new ast::LocalVariable($1); - $$.push_back(std::shared_ptr<ast::LocalVariable>(variable)); + auto variable = new ast::LocalVar($1); + $$.push_back(std::shared_ptr<ast::LocalVar>(variable)); } } | locallist1 "," NAME locoptarray { if($4) { - auto variable = new ast::LocalVariable(new ast::IndexedName($3, $4)); - $1.push_back(std::shared_ptr<ast::LocalVariable>(variable)); + auto variable = new ast::LocalVar(new ast::IndexedName($3, $4)); + $1.push_back(std::shared_ptr<ast::LocalVar>(variable)); } else { - auto variable = new ast::LocalVariable($3); - $1.push_back(std::shared_ptr<ast::LocalVariable>(variable)); + auto variable = new ast::LocalVar($3); + $1.push_back(std::shared_ptr<ast::LocalVar>(variable)); } $$ = $1; } @@ -1862,7 +1862,7 @@ neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE nrnstmt : { $$ = ast::StatementVector(); } | nrnstmt SUFFIX NAME { - auto statement = new ast::NrnSuffix($2, $3); + auto statement = new ast::Suffix($2, $3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } @@ -1873,55 +1873,55 @@ nrnstmt : { $$ = ast::StatementVector(); } } | nrnstmt NONSPECIFIC nrnonspeclist { - auto statement = new ast::NrnNonspecific($3); + auto statement = new ast::Nonspecific($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt ELECTRODE_CURRENT nrneclist { - auto statement = new ast::NrnElctrodeCurrent($3); + auto statement = new ast::ElctrodeCurrent($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt SECTION nrnseclist { - auto statement = new ast::NrnSection($3); + auto statement = new ast::Section($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt RANGE nrnrangelist { - auto statement = new ast::NrnRange($3); + auto statement = new ast::Range($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt GLOBAL nrnglobalist { - auto statement = new ast::NrnGlobal($3); + auto statement = new ast::Global($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt POINTER nrnptrlist { - auto statement = new ast::NrnPointer($3); + auto statement = new ast::Pointer($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt BBCOREPOINTER nrnbbptrlist { - auto statement = new ast::NrnBbcorePtr($3); + auto statement = new ast::BbcorePtr($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt EXTERNAL nrnextlist { - auto statement = new ast::NrnExternal($3); + auto statement = new ast::External($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } | nrnstmt THREADSAFE opthsafelist { - auto statement = new ast::NrnThreadSafe($3); + auto statement = new ast::ThreadSafe($3); $1.push_back(std::shared_ptr<ast::Statement>(statement)); $$ = $1; } @@ -1930,15 +1930,15 @@ nrnstmt : { $$ = ast::StatementVector(); } nrnuse : USEION NAME READ nrnionrlist valence { - $$ = new ast::NrnUseion($2, $4, ast::WriteIonVarVector(), $5); + $$ = new ast::Useion($2, $4, ast::WriteIonVarVector(), $5); } | USEION NAME WRITE nrnionwlist valence { - $$ = new ast::NrnUseion($2, ast::ReadIonVarVector(), $4, $5); + $$ = new ast::Useion($2, ast::ReadIonVarVector(), $4, $5); } | USEION NAME READ nrnionrlist WRITE nrnionwlist valence { - $$ = new ast::NrnUseion($2, $4, $6, $7); + $$ = new ast::Useion($2, $4, $6, $7); } | USEION error { diff --git a/src/nmodl/visitors/.gitignore b/src/nmodl/visitors/.gitignore new file mode 100644 index 0000000000..5e7d2734cf --- /dev/null +++ b/src/nmodl/visitors/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/src/nmodl/visitors/visitor.hpp b/src/nmodl/visitors/visitor.hpp deleted file mode 100644 index 9b1e63601e..0000000000 --- a/src/nmodl/visitors/visitor.hpp +++ /dev/null @@ -1,136 +0,0 @@ -#pragma once - - -/* Abstract base class for all visitor implementations */ -class Visitor { - - public: - virtual void visitStatement(Statement* node) = 0; - virtual void visitExpression(Expression* node) = 0; - virtual void visitBlock(Block* node) = 0; - virtual void visitIdentifier(Identifier* node) = 0; - virtual void visitNumber(Number* node) = 0; - virtual void visitString(String* node) = 0; - virtual void visitInteger(Integer* node) = 0; - virtual void visitFloat(Float* node) = 0; - virtual void visitDouble(Double* node) = 0; - virtual void visitBoolean(Boolean* node) = 0; - virtual void visitName(Name* node) = 0; - virtual void visitPrimeName(PrimeName* node) = 0; - virtual void visitVarName(VarName* node) = 0; - virtual void visitIndexedName(IndexedName* node) = 0; - virtual void visitArgument(Argument* node) = 0; - virtual void visitReactVarName(ReactVarName* node) = 0; - virtual void visitReadIonVar(ReadIonVar* node) = 0; - virtual void visitWriteIonVar(WriteIonVar* node) = 0; - virtual void visitNonspeCurVar(NonspeCurVar* node) = 0; - virtual void visitElectrodeCurVar(ElectrodeCurVar* node) = 0; - virtual void visitSectionVar(SectionVar* node) = 0; - virtual void visitRangeVar(RangeVar* node) = 0; - virtual void visitGlobalVar(GlobalVar* node) = 0; - virtual void visitPointerVar(PointerVar* node) = 0; - virtual void visitBbcorePointerVar(BbcorePointerVar* node) = 0; - virtual void visitExternVar(ExternVar* node) = 0; - virtual void visitThreadsafeVar(ThreadsafeVar* node) = 0; - virtual void visitParamBlock(ParamBlock* node) = 0; - virtual void visitStepBlock(StepBlock* node) = 0; - virtual void visitIndependentBlock(IndependentBlock* node) = 0; - virtual void visitDependentBlock(DependentBlock* node) = 0; - virtual void visitStateBlock(StateBlock* node) = 0; - virtual void visitPlotBlock(PlotBlock* node) = 0; - virtual void visitInitialBlock(InitialBlock* node) = 0; - virtual void visitConstructorBlock(ConstructorBlock* node) = 0; - virtual void visitDestructorBlock(DestructorBlock* node) = 0; - virtual void visitStatementBlock(StatementBlock* node) = 0; - virtual void visitDerivativeBlock(DerivativeBlock* node) = 0; - virtual void visitLinearBlock(LinearBlock* node) = 0; - virtual void visitNonLinearBlock(NonLinearBlock* node) = 0; - virtual void visitDiscreteBlock(DiscreteBlock* node) = 0; - virtual void visitPartialBlock(PartialBlock* node) = 0; - virtual void visitFunctionTableBlock(FunctionTableBlock* node) = 0; - virtual void visitFunctionBlock(FunctionBlock* node) = 0; - virtual void visitProcedureBlock(ProcedureBlock* node) = 0; - virtual void visitNetReceiveBlock(NetReceiveBlock* node) = 0; - virtual void visitSolveBlock(SolveBlock* node) = 0; - virtual void visitBreakpointBlock(BreakpointBlock* node) = 0; - virtual void visitTerminalBlock(TerminalBlock* node) = 0; - virtual void visitBeforeBlock(BeforeBlock* node) = 0; - virtual void visitAfterBlock(AfterBlock* node) = 0; - virtual void visitBABlock(BABlock* node) = 0; - virtual void visitForNetcon(ForNetcon* node) = 0; - virtual void visitKineticBlock(KineticBlock* node) = 0; - virtual void visitMatchBlock(MatchBlock* node) = 0; - virtual void visitUnitBlock(UnitBlock* node) = 0; - virtual void visitConstantBlock(ConstantBlock* node) = 0; - virtual void visitNeuronBlock(NeuronBlock* node) = 0; - virtual void visitUnit(Unit* node) = 0; - virtual void visitDoubleUnit(DoubleUnit* node) = 0; - virtual void visitLocalVariable(LocalVariable* node) = 0; - virtual void visitLimits(Limits* node) = 0; - virtual void visitNumberRange(NumberRange* node) = 0; - virtual void visitPlotVariable(PlotVariable* node) = 0; - virtual void visitBinaryOperator(BinaryOperator* node) = 0; - virtual void visitUnaryOperator(UnaryOperator* node) = 0; - virtual void visitReactionOperator(ReactionOperator* node) = 0; - virtual void visitBinaryExpression(BinaryExpression* node) = 0; - virtual void visitUnaryExpression(UnaryExpression* node) = 0; - virtual void visitNonLinEuation(NonLinEuation* node) = 0; - virtual void visitLinEquation(LinEquation* node) = 0; - virtual void visitFunctionCall(FunctionCall* node) = 0; - virtual void visitFirstLastTypeIndex(FirstLastTypeIndex* node) = 0; - virtual void visitWatch(Watch* node) = 0; - virtual void visitQueueExpressionType(QueueExpressionType* node) = 0; - virtual void visitMatch(Match* node) = 0; - virtual void visitBABlockType(BABlockType* node) = 0; - virtual void visitUnitDef(UnitDef* node) = 0; - virtual void visitFactorDef(FactorDef* node) = 0; - virtual void visitValence(Valence* node) = 0; - virtual void visitUnitState(UnitState* node) = 0; - virtual void visitLocalListStatement(LocalListStatement* node) = 0; - virtual void visitModel(Model* node) = 0; - virtual void visitDefine(Define* node) = 0; - virtual void visitInclude(Include* node) = 0; - virtual void visitParamAssign(ParamAssign* node) = 0; - virtual void visitStepped(Stepped* node) = 0; - virtual void visitIndependentDef(IndependentDef* node) = 0; - virtual void visitDependentDef(DependentDef* node) = 0; - virtual void visitPlotDeclaration(PlotDeclaration* node) = 0; - virtual void visitConductanceHint(ConductanceHint* node) = 0; - virtual void visitExpressionStatement(ExpressionStatement* node) = 0; - virtual void visitProtectStatement(ProtectStatement* node) = 0; - virtual void visitFromStatement(FromStatement* node) = 0; - virtual void visitForAllStatement(ForAllStatement* node) = 0; - virtual void visitWhileStatement(WhileStatement* node) = 0; - virtual void visitIfStatement(IfStatement* node) = 0; - virtual void visitElseIfStatement(ElseIfStatement* node) = 0; - virtual void visitElseStatement(ElseStatement* node) = 0; - virtual void visitPartialEquation(PartialEquation* node) = 0; - virtual void visitPartialBoundary(PartialBoundary* node) = 0; - virtual void visitWatchStatement(WatchStatement* node) = 0; - virtual void visitMutexLock(MutexLock* node) = 0; - virtual void visitMutexUnlock(MutexUnlock* node) = 0; - virtual void visitReset(Reset* node) = 0; - virtual void visitSens(Sens* node) = 0; - virtual void visitConserve(Conserve* node) = 0; - virtual void visitCompartment(Compartment* node) = 0; - virtual void visitLDifuse(LDifuse* node) = 0; - virtual void visitReactionStatement(ReactionStatement* node) = 0; - virtual void visitLagStatement(LagStatement* node) = 0; - virtual void visitQueueStatement(QueueStatement* node) = 0; - virtual void visitConstantStatement(ConstantStatement* node) = 0; - virtual void visitTableStatement(TableStatement* node) = 0; - virtual void visitNrnSuffix(NrnSuffix* node) = 0; - virtual void visitNrnUseion(NrnUseion* node) = 0; - virtual void visitNrnNonspecific(NrnNonspecific* node) = 0; - virtual void visitNrnElctrodeCurrent(NrnElctrodeCurrent* node) = 0; - virtual void visitNrnSection(NrnSection* node) = 0; - virtual void visitNrnRange(NrnRange* node) = 0; - virtual void visitNrnGlobal(NrnGlobal* node) = 0; - virtual void visitNrnPointer(NrnPointer* node) = 0; - virtual void visitNrnBbcorePtr(NrnBbcorePtr* node) = 0; - virtual void visitNrnExternal(NrnExternal* node) = 0; - virtual void visitNrnThreadSafe(NrnThreadSafe* node) = 0; - virtual void visitVerbatim(Verbatim* node) = 0; - virtual void visitComment(Comment* node) = 0; - virtual void visitProgram(Program* node) = 0; -}; From 5a5bb41c3dec31f75b41da128b3ccfefe2cdb8c9 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 25 Nov 2017 00:17:02 +0100 Subject: [PATCH 019/871] Adding first concrete AST visitor The AstVisitor class is a concrete visitor which will be used for implementing all other visitorts. - Python code generator has been added - Simple example has been added in visitors/main.cpp - Renaming files for consistent naming - Simple example for AstVisitor usage is added - Driver now has verbose property to enable/disable messages Change-Id: Ibe48954fd17280b01133be117d9dc5b7c4a5414b NMODL Repo SHA: BlueBrain/nmodl@00821e0daf5250ab38c59e5df44d1d46087abded --- cmake/nmodl/CMakeLists.txt | 3 +- src/nmodl/language/CMakeLists.txt | 8 +-- src/nmodl/language/ast_printer.py | 4 +- src/nmodl/language/code_generator.py | 10 ++++ .../language/{node_types.py => node_info.py} | 0 src/nmodl/language/{ast.py => nodes.py} | 2 +- src/nmodl/language/parser.py | 4 +- .../language/{base_printer.py => printer.py} | 0 src/nmodl/language/visitors_printer.py | 43 +++++++++++++++- src/nmodl/lexer/nmodl.ll | 12 +++-- src/nmodl/parser/main.cpp | 2 + src/nmodl/parser/nmodl_driver.hpp | 22 +++++++-- src/nmodl/visitors/.gitignore | 4 -- src/nmodl/visitors/CMakeLists.txt | 19 +++++++ src/nmodl/visitors/main.cpp | 49 +++++++++++++++++++ 15 files changed, 160 insertions(+), 22 deletions(-) rename src/nmodl/language/{node_types.py => node_info.py} (100%) rename src/nmodl/language/{ast.py => nodes.py} (99%) rename src/nmodl/language/{base_printer.py => printer.py} (100%) delete mode 100644 src/nmodl/visitors/.gitignore create mode 100644 src/nmodl/visitors/CMakeLists.txt create mode 100644 src/nmodl/visitors/main.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 843b56838a..4db831ba96 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -38,6 +38,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) +add_subdirectory(src/visitors) add_subdirectory(test) include (CTest) @@ -48,7 +49,7 @@ add_executable(nocmodl src/main.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) -target_link_libraries(nocmodl lexer) +target_link_libraries(nocmodl lexer visitor) add_custom_target(clangformat COMMAND diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 53a5605700..e85d755c4e 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -5,14 +5,16 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.cpp WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PROJECT_SOURCE_DIR}/src/language/argument.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nodes.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/parser.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/node_types.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/node_info.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/base_printer.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/printer.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast_printer.py COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" ) diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index e4217c4e14..fa69a20917 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -1,6 +1,6 @@ import itertools -from base_printer import * -from node_types import ORDER_VAR_NAME +from printer import * +from node_info import ORDER_VAR_NAME class AstDeclarationPrinter(DeclarationPrinter): diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index af3fd7be07..53b4e6f8f2 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -22,3 +22,13 @@ "../visitors/visitor.hpp", "Visitor", nodes).write() + +AstVisitorDeclarationPrinter( + "../visitors/astvisitor.hpp", + "AstVisitor", + nodes).write() + +AstVisitorDefinitionPrinter( + "../visitors/astvisitor.cpp", + "AstVisitor", + nodes).write() \ No newline at end of file diff --git a/src/nmodl/language/node_types.py b/src/nmodl/language/node_info.py similarity index 100% rename from src/nmodl/language/node_types.py rename to src/nmodl/language/node_info.py diff --git a/src/nmodl/language/ast.py b/src/nmodl/language/nodes.py similarity index 99% rename from src/nmodl/language/ast.py rename to src/nmodl/language/nodes.py index 19703c516d..7ae5a239c4 100644 --- a/src/nmodl/language/ast.py +++ b/src/nmodl/language/nodes.py @@ -6,7 +6,7 @@ """ from argument import Argument -from node_types import * +from node_info import * class BaseNode: diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 48acd60786..5809148efc 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -8,9 +8,9 @@ import sys import yaml -from ast import Node from argument import Argument -from node_types import * +from nodes import Node +from node_info import * class LanguageParser: diff --git a/src/nmodl/language/base_printer.py b/src/nmodl/language/printer.py similarity index 100% rename from src/nmodl/language/base_printer.py rename to src/nmodl/language/printer.py diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index ba4c1e7da1..41b4e69fc9 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -1,4 +1,4 @@ -from base_printer import * +from printer import * class AbstractVisitorPrinter(DeclarationPrinter): @@ -18,3 +18,44 @@ def public_declaration(self): self.writer.write_line(line) self.writer.decrease_gutter() + + +class AstVisitorDeclarationPrinter(DeclarationPrinter): + """Prints base visitor class declaration""" + + def headers(self): + line = '#include "ast/ast.hpp"' + self.writer.write_line(line) + line = '#include "visitors/visitor.hpp"' + self.writer.write_line(line) + line = "using namespace ast;" + self.writer.write_line(line, newline=2) + + def class_comment(self): + self.writer.write_line("/* Basic visitor implementation */") + + def class_name_declaration(self): + self.writer.write_line("class " + self.classname + " : public Visitor {") + + def public_declaration(self): + self.writer.write_line("public:", post_gutter=1) + + for node in self.nodes: + line = "virtual void visit" + node.class_name + "(" + node.class_name + "* node) override;" + self.writer.write_line(line) + + self.writer.decrease_gutter() + + +class AstVisitorDefinitionPrinter(DefinitionPrinter): + """Prints base visitor class method definitions""" + + def headers(self): + self.writer.write_line('#include "visitors/astvisitor.hpp"', newline=2) + + def definitions(self): + for node in self.nodes: + line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" + self.writer.write_line(line, post_gutter=1) + self.writer.write_line("node->visitChildren(this);", post_gutter=-1) + self.writer.write_line("}", newline=2) \ No newline at end of file diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 66cf3cd977..fba09a0d41 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -364,11 +364,13 @@ ELSE { std::string str(yytext); stringutils::trim(str); - if(str.length()) { - stringutils::trimnewline(str); - std::cout << "LINE "<< yylineno << ": " << str << std::endl; - } else { - std::cout << "LINE " << yylineno << ": " << std::endl; + if (driver.is_verbose()) { + if(str.length()) { + stringutils::trimnewline(str); + std::cout << "LINE "<< yylineno << ": " << str << std::endl; + } else { + std::cout << "LINE " << yylineno << ": " << std::endl; + } } /** Pass back entire string except newline charactear */ diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp index 86a60d6609..bc30296c97 100644 --- a/src/nmodl/parser/main.cpp +++ b/src/nmodl/parser/main.cpp @@ -34,6 +34,8 @@ int main(int argc, char* argv[]) { /// driver object creates lexer and parser, just call parser method nmodl::Driver driver; + + driver.set_verbose(true); driver.parse_stream(in); // driver.parse_file(filename); diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 5199308bd1..af2a7961fe 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -50,19 +50,23 @@ namespace nmodl { /// enable debug output in the bison parser bool trace_parser = false; - public: /// pointer to the lexer instance being used Lexer* lexer = nullptr; /// pointer to the parser instance being used Parser* parser = nullptr; - /// root of the ast - std::shared_ptr<ast::Program> astRoot = nullptr; + /// print messages from lexer/parser + bool verbose = false; + + public: /// file or input stream name (used by scanner for position), see todo std::string streamname; + /// root of the ast + std::shared_ptr<ast::Program> astRoot = nullptr; + Driver(){}; Driver(bool strace, bool ptrace); @@ -78,6 +82,18 @@ namespace nmodl { bool parse_string(const std::string& input); bool parse_file(const std::string& filename); + void set_verbose(bool b) { + verbose = b; + } + + bool is_verbose() const { + return verbose; + } + + std::shared_ptr<ast::Program> ast() const { + return astRoot; + } + }; } // namespace nmodl diff --git a/src/nmodl/visitors/.gitignore b/src/nmodl/visitors/.gitignore deleted file mode 100644 index 5e7d2734cf..0000000000 --- a/src/nmodl/visitors/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Ignore everything in this directory -* -# Except this file -!.gitignore diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt new file mode 100644 index 0000000000..d245633d28 --- /dev/null +++ b/src/nmodl/visitors/CMakeLists.txt @@ -0,0 +1,19 @@ +set(VISITOR_SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.cpp +) + +set_source_files_properties( + ${VISITOR_SOURCE_FILES} + PROPERTIES GENERATED TRUE +) + +add_library(visitor + STATIC + ${VISITOR_SOURCE_FILES}) + +# visitors can not be compiled without parser files +# because modtoken does use location, position etc. +add_dependencies(visitor lexer) + +add_executable(nocmodl_visitor main.cpp) +target_link_libraries(nocmodl_visitor lexer visitor) \ No newline at end of file diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp new file mode 100644 index 0000000000..521d9cbfdc --- /dev/null +++ b/src/nmodl/visitors/main.cpp @@ -0,0 +1,49 @@ +#include <fstream> +#include <iostream> + +#include "parser/nmodl_driver.hpp" +#include "visitors/astvisitor.hpp" + +#include "tclap/CmdLine.h" + +/** + * Standlone visitor program for NMODL. This demonstrate basic + * usage of different visitors classes and driver class. + **/ + +int main(int argc, char* argv[]) { + try { + TCLAP::CmdLine cmd("NMODL Visitor: Standalone visitor program for NMODL"); + TCLAP::ValueArg<std::string> filearg( + "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); + + cmd.add(filearg); + cmd.parse(argc, argv); + + std::string filename = filearg.getValue(); + std::ifstream file(filename); + + if (!file.good()) { + throw std::runtime_error("Could not open file " + filename); + } + + /// driver object creates lexer and parser, just call parser method + nmodl::Driver driver; + driver.parse_file(filename); + + /// shared_ptr to ast constructed from parsing nmodl file + auto ast = driver.ast(); + + /// run basic AST visitor + AstVisitor v; + v.visitProgram(ast.get()); + + std::cout << "----AST VISITOR FINISHED----" << std::endl; + + } catch (TCLAP::ArgException& e) { + std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; + return 1; + } + + return 0; +} From e260eceb8cbc24bdb20a4c6f47d679b302d68e22 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 25 Nov 2017 14:39:39 +0100 Subject: [PATCH 020/871] Implemented Verbatim visitor that prints all verbatim blocks from mod file. - Test directory for visitors added - Removed extra space added by lexer for verbatim - ModToken has now to_string method - visitor app updated - moved add_test in sub-directory (squashed commit from branch sandbox/kumbhar/verbatim-visitor) Change-Id: I030f5d8987bc5980ce8fa92c5800e69d4d7dadc3 NMODL Repo SHA: BlueBrain/nmodl@ef3326b9dd3fabbf5cc6e99ae5521125a850f49c --- cmake/nmodl/CMakeLists.txt | 6 +-- src/nmodl/lexer/modtoken.cpp | 6 +++ src/nmodl/lexer/modtoken.hpp | 2 + src/nmodl/lexer/nmodl.ll | 4 +- src/nmodl/visitors/CMakeLists.txt | 1 + src/nmodl/visitors/main.cpp | 20 ++++++++-- src/nmodl/visitors/verbatim_visitor.cpp | 17 +++++++++ src/nmodl/visitors/verbatim_visitor.hpp | 46 +++++++++++++++++++++++ test/nmodl/transpiler/CMakeLists.txt | 11 +++++- test/nmodl/transpiler/visitor/visitor.cpp | 38 +++++++++++++++++++ 10 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 src/nmodl/visitors/verbatim_visitor.cpp create mode 100644 src/nmodl/visitors/verbatim_visitor.hpp create mode 100644 test/nmodl/transpiler/visitor/visitor.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4db831ba96..cfe0bf99c6 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -39,11 +39,9 @@ add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) add_subdirectory(src/visitors) -add_subdirectory(test) -include (CTest) -add_test (NAME ModToken COMMAND testmodtoken) -add_test (NAME Lexer COMMAND testlexer) +enable_testing() +add_subdirectory(test) add_executable(nocmodl src/main.cpp diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 4cf56ab382..509da98d08 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -22,4 +22,10 @@ std::string ModToken::position() const { std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { stream << std::setw(15) << mt.name << " at " << mt.position(); return stream << " type " << mt.token; +} + +std::string ModToken::to_string() const { + std::stringstream ss; + ss << *this; + return ss.str(); } \ No newline at end of file diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 0bc6a2c34c..a0874bb69b 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -68,6 +68,8 @@ class ModToken { std::string position() const; friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt); + + std::string to_string() const; }; #endif diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index fba09a0d41..ad09c12f47 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -412,14 +412,14 @@ ELSE { <COPY_MODE>"ENDVERBATIM" { /** For verbatim block we construct entire block again, resent end * column position to 0 and return token. We do same for comment. */ - auto str = "VERBATIM " + std::string(yytext); + auto str = "VERBATIM" + std::string(yytext); BEGIN(INITIAL); reset_end_position(); return nmodl::Parser::make_VERBATIM(str, loc); } <COPY_MODE>"ENDCOMMENT" { - auto str = "COMMENT " + std::string(yytext); + auto str = "COMMENT" + std::string(yytext); BEGIN(INITIAL); reset_end_position(); return nmodl::Parser::make_COMMENT(str, loc); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index d245633d28..0a7c45d31d 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -1,5 +1,6 @@ set(VISITOR_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/verbatim_visitor.cpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 521d9cbfdc..98f049319a 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -3,6 +3,7 @@ #include "parser/nmodl_driver.hpp" #include "visitors/astvisitor.hpp" +#include "visitors/verbatim_visitor.hpp" #include "tclap/CmdLine.h" @@ -34,11 +35,22 @@ int main(int argc, char* argv[]) { /// shared_ptr to ast constructed from parsing nmodl file auto ast = driver.ast(); - /// run basic AST visitor - AstVisitor v; - v.visitProgram(ast.get()); + { + /// run basic AST visitor + AstVisitor v; + v.visitProgram(ast.get()); - std::cout << "----AST VISITOR FINISHED----" << std::endl; + std::cout << "----AST VISITOR FINISHED----" << std::endl; + } + + { + /// run basic Verbatim visitor + /// constructor takes true/false argument for printing blocks + VerbatimVisitor v; + v.visitProgram(ast.get()); + + std::cout << "----VERBATIM VISITOR FINISHED----" << std::endl; + } } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp new file mode 100644 index 0000000000..6f9cfd8584 --- /dev/null +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -0,0 +1,17 @@ +#include "visitors/verbatim_visitor.hpp" + +void VerbatimVisitor::visitVerbatim(Verbatim* node) { + std::string block; + + if(node->statement) { + block = node->statement->eval(); + } + + if (block.size() && verbose) { + std::cout << "BLOCK START"; + std::cout << block; + std::cout << "\nBLOCK END \n\n"; + } + + blocks.push_back(block); +} \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp new file mode 100644 index 0000000000..6ae54243b1 --- /dev/null +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -0,0 +1,46 @@ +#ifndef _VERBATIM_VISITOR_HPP_ +#define _VERBATIM_VISITOR_HPP_ + +#include <iostream> +#include <vector> + +#include "ast/ast.hpp" +#include "visitors/astvisitor.hpp" + +/** + * \class VerbatimVisitor + * \brief Visitor for verbatim blocks of AST + * + * This is simple example of visitor that uses base AstVisitor + * interface. We override visitVerbatim method and store all + * verbatim blocks that we encounter. This could be used for + * generating report of all verbatim blocks from all mod files + * in ModelDB. + */ + + +class VerbatimVisitor : public AstVisitor { + + private: + /// flag to enable/disable printing blocks as we visit them + bool verbose = false; + + /// vector containing all verbatim blocks + std::vector<std::string> blocks; + + public: + VerbatimVisitor() = default; + + VerbatimVisitor(bool flag) { + verbose = flag; + } + + void visitVerbatim(Verbatim* node) override; + + std::vector<std::string> verbatim_blocks() { + return blocks; + } +}; + + +#endif diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 374d22c675..7fb0d07d98 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -1,9 +1,18 @@ +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/test) + include_directories( ${PROJECT_SOURCE_DIR}/src/ext/catch ) add_executable (testmodtoken modtoken/modtoken.cpp) add_executable (testlexer lexer/tokens.cpp) +add_executable (testvisitor visitor/visitor.cpp) target_link_libraries(testmodtoken lexer) -target_link_libraries(testlexer lexer) \ No newline at end of file +target_link_libraries(testlexer lexer) +target_link_libraries(testvisitor lexer visitor) + +include (CTest) +add_test (NAME ModToken COMMAND testmodtoken) +add_test (NAME Lexer COMMAND testlexer) +add_test (NAME Visitor COMMAND testvisitor) \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp new file mode 100644 index 0000000000..fd37fa0114 --- /dev/null +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -0,0 +1,38 @@ +#define CATCH_CONFIG_MAIN + +#include <string> + +#include "catch/catch.hpp" +#include "parser/nmodl_driver.hpp" +#include "visitors/verbatim_visitor.hpp" + + +std::vector<std::string> verbatim_visitor_process(std::string text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + VerbatimVisitor v; + v.visitProgram(ast.get()); + return v.verbatim_blocks(); +} + +/// test verbatim visitor +TEST_CASE("Verbatim Visitor") { + SECTION("Single Block") { + std::string text = "VERBATIM int a; ENDVERBATIM"; + auto blocks = verbatim_visitor_process(text); + + REQUIRE(blocks.size() == 1); + REQUIRE(blocks.front().compare(" int a; ") == 0); + } + + SECTION("Multiple Blocks") { + std::string text = "VERBATIM int a; ENDVERBATIM VERBATIM float b; ENDVERBATIM"; + auto blocks = verbatim_visitor_process(text); + + REQUIRE(blocks.size() == 2); + REQUIRE(blocks.front().compare(" int a; ") == 0); + REQUIRE(blocks.back().compare(" float b; ") == 0); + } +} \ No newline at end of file From 927a7bd7d4f266f4d2e523d15801aa934f780df2 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 25 Nov 2017 19:58:39 +0100 Subject: [PATCH 021/871] Added CMake helper for correctly including flex include directory to avoid compilation issues. - Flex helper cmake module added - check for gnu version added - README updated - Valgrind memory check options added for ctest - Ctest memcheck options were not used - Minor formating of CMake comments Change-Id: I195e98b338df1f35af51c85394bdfef734812ed6 NMODL Repo SHA: BlueBrain/nmodl@21e27eadf8ea6a2055fee9c49963e53051d75269 --- README.md | 13 +++++++++ cmake/nmodl/CMakeLists.txt | 41 ++++++++++++++++++++++------ cmake/nmodl/CompilerHelper.cmake | 7 +++++ cmake/nmodl/FlexHelper.cmake | 20 ++++++++++++++ cmake/nmodl/GitRevision.cmake | 2 +- src/nmodl/language/CMakeLists.txt | 7 ++++- src/nmodl/lexer/CMakeLists.txt | 27 ++++++++++++------ src/nmodl/parser/CMakeLists.txt | 7 ++++- src/nmodl/visitors/CMakeLists.txt | 8 ++++-- test/nmodl/transpiler/CMakeLists.txt | 6 ++-- 10 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 cmake/nmodl/CompilerHelper.cmake create mode 100644 cmake/nmodl/FlexHelper.cmake diff --git a/README.md b/README.md index 13bbc6ef88..c02cb48a02 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,13 @@ If flex / bison is installed in non-standard location then set `PATH` env variab cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" ``` + On Lugano system we have to use newer version using: + + ``` + export PATH=/gpfs/bbp.cscs.ch/project/proj16/software/viz/hpc/bison-3.0.4-/bin:$PATH + export PATH=/gpfs/bbp.cscs.ch/project/proj16/software/viz/hpc/flex-2.6.4/bin:$PATH + ``` + #### Running NOCMODL You can independently run lexer, parser as: @@ -73,3 +80,9 @@ Test memory leaks using : ``` valgrind --leak-check=full --track-origins=yes ./bin/nocmodl_lexer ``` + +Or using CTest as: + +``` +ctest -T memcheck +``` diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index cfe0bf99c6..b37a4f3a2a 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,6 +1,9 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) project(nocmodl CXX) +#============================================================================= +# CMake common project settings +#============================================================================= set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 1) set(CMAKE_BUILD_TYPE Debug) @@ -9,26 +12,32 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +#============================================================================= +# Find required packages +#============================================================================= message(STATUS "CHECKING FOR FLEX/BISON/PYTHON") find_package(PythonInterp REQUIRED) -find_package(BISON 3.0 REQUIRED) find_package(FLEX 2.6 REQUIRED) +find_package(BISON 3.0 REQUIRED) +#============================================================================= +# Include cmake modules +#============================================================================= list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(GitRevision) +include(FlexHelper) +include(CompilerHelper) + include_directories( ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/ext/ ) -# Important : make sure to have flex header included -# at top level. Otherwise get strange compilation and -# runtime memory errors due to inclusion of older version -# from /usr/include (which is always present!) -include_directories(${FLEX_INCLUDE_DIRS}) - +#============================================================================= +# Project version from git and project directories +#============================================================================= set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) # generate file with version number from git @@ -40,16 +49,30 @@ add_subdirectory(src/lexer) add_subdirectory(src/parser) add_subdirectory(src/visitors) -enable_testing() +#============================================================================= +# Memory checker options and add tests +#============================================================================= +find_program(MEMORYCHECK_COMMAND valgrind) +set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ + --leak-check=full \ + --track-origins=yes \ + --show-possibly-lost=no") +include (CTest) add_subdirectory(test) +#============================================================================= +# Add executables +#============================================================================= add_executable(nocmodl src/main.cpp ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) target_link_libraries(nocmodl lexer visitor) +#============================================================================= +# Clang format options +#============================================================================= add_custom_target(clangformat COMMAND clang-format -i - --style=file ${FILES_FOR_CLANG_FORMAT}) \ No newline at end of file + --style=file ${FILES_FOR_CLANG_FORMAT}) diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake new file mode 100644 index 0000000000..c9214a739b --- /dev/null +++ b/cmake/nmodl/CompilerHelper.cmake @@ -0,0 +1,7 @@ +# minimal check for c++11 compliant gnu compiler +if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) + if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) + message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.7 (for c++11 support)") + endif () +endif () diff --git a/cmake/nmodl/FlexHelper.cmake b/cmake/nmodl/FlexHelper.cmake new file mode 100644 index 0000000000..4240ae4cbe --- /dev/null +++ b/cmake/nmodl/FlexHelper.cmake @@ -0,0 +1,20 @@ +# Often older version of flex is available in /usr. +# Even we set PATH for newer flex, CMake will set +# FLEX_INCLUDE_DIRS to /usr/include. This will result +# in compilation errors. Hence we check for flex include +# directory for the corresponding FLEX_EXECUTABLE. +# If found, we add that first and then we include +# include path from CMake. + +get_filename_component(FLEX_BIN_DIR ${FLEX_EXECUTABLE} DIRECTORY) + +if(NOT FLEX_BIN_DIR MATCHES "/usr/bin") + get_filename_component(FLEX_INCLUDE_PATH ${FLEX_BIN_DIR} PATH) + set(FLEX_INCLUDE_PATH ${FLEX_INCLUDE_PATH}/include/) + if(EXISTS "${FLEX_INCLUDE_PATH}/FlexLexer.h") + message(STATUS " Adding Flex include path as : ${FLEX_INCLUDE_PATH}") + include_directories(${FLEX_INCLUDE_PATH}) + endif() +endif() + +include_directories(${FLEX_INCLUDE_DIRS}) diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index b70ad2aa87..2ff0980b3f 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -20,7 +20,7 @@ if(GIT_FOUND) ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # remove extra double quotes - string(REGEX REPLACE "\"" "" GIT_REVISION_DATE ${GIT_REVISION_DATE}) + string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") else() diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index e85d755c4e..db555a413d 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -1,4 +1,6 @@ -# generate AST/Visitor classes from language definition +#============================================================================= +# Command to generate AST/Visitor classes from language definition +#============================================================================= add_custom_command ( COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py @@ -19,4 +21,7 @@ add_custom_command ( COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" ) +#============================================================================= +# Target to propogate dependencies properly to lexer +#============================================================================= add_custom_target (pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) \ No newline at end of file diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 1a3590b2fc..df23531aec 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -1,3 +1,6 @@ +#============================================================================= +# Various project components and their source files +#============================================================================= set (BISON_GENERATED_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp @@ -16,6 +19,18 @@ set_source_files_properties( PROPERTIES GENERATED TRUE ) +set(LEXER_SOURCE_FILES + token_mapping.cpp + nmodl_utils.cpp + modtoken.cpp + nmodl_base_lexer.cpp + verbatim_lexer.cpp + ${NMODL_DRIVER_FILES} +) + +#============================================================================= +# Lexer & Parser commands +#============================================================================= # command to generate nmodl parser add_custom_command ( COMMAND ${BISON_EXECUTABLE} @@ -64,15 +79,9 @@ add_custom_command( COMMENT "-- NOCMODL : GENERATING VERBATIM LEXER WITH FLEX! --" ) -set(LEXER_SOURCE_FILES - token_mapping.cpp - nmodl_utils.cpp - modtoken.cpp - nmodl_base_lexer.cpp - verbatim_lexer.cpp - ${NMODL_DRIVER_FILES} -) - +#============================================================================= +# Libraries & executables +#============================================================================= add_library(lexer STATIC ${LEXER_SOURCE_FILES} diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 65052a7f63..a89a9cec7d 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -1,4 +1,9 @@ +#============================================================================= +# Parser executable +#============================================================================= + # lexer library links with all parser related files -# so need to have parser as a separate library +# so no eed to have parser as a separate library + add_executable(nocmodl_parser main.cpp) target_link_libraries(nocmodl_parser lexer) \ No newline at end of file diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 0a7c45d31d..ea27273dae 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -1,3 +1,6 @@ +#============================================================================= +# Visitor sources +#============================================================================= set(VISITOR_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/verbatim_visitor.cpp @@ -8,12 +11,13 @@ set_source_files_properties( PROPERTIES GENERATED TRUE ) +#============================================================================= +# Visitor library and executable +#============================================================================= add_library(visitor STATIC ${VISITOR_SOURCE_FILES}) -# visitors can not be compiled without parser files -# because modtoken does use location, position etc. add_dependencies(visitor lexer) add_executable(nocmodl_visitor main.cpp) diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 7fb0d07d98..cb27cb6902 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -4,6 +4,9 @@ include_directories( ${PROJECT_SOURCE_DIR}/src/ext/catch ) +#============================================================================= +# Test executables +#============================================================================= add_executable (testmodtoken modtoken/modtoken.cpp) add_executable (testlexer lexer/tokens.cpp) add_executable (testvisitor visitor/visitor.cpp) @@ -12,7 +15,6 @@ target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) target_link_libraries(testvisitor lexer visitor) -include (CTest) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) -add_test (NAME Visitor COMMAND testvisitor) \ No newline at end of file +add_test (NAME Visitor COMMAND testvisitor) From 3464eac7085ad92d9fde9c4c8bf8d6d554fb2271 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 26 Nov 2017 15:00:10 +0100 Subject: [PATCH 022/871] Added JSON visitor and JSON printer class - Can now print AST to JSON file/stringstream - Using nlohmann/json c++11 library - Rename files astvisitor to ast_visitor - Python code generator updated to print JSONVisitor class - Visitor app updated - Printer test added - Visitor test updated - Minor cleanup / renaming Change-Id: If08fbbe79b2d5062b445e652124b23fde20c9502 NMODL Repo SHA: BlueBrain/nmodl@c36d1f8a016e2fd07815e4ad9dc39be210d844cf --- cmake/nmodl/CMakeLists.txt | 1 + cmake/nmodl/CompilerHelper.cmake | 4 +- src/nmodl/ext/json/json.hpp | 14722 ++++++++++++++++++++ src/nmodl/language/CMakeLists.txt | 7 +- src/nmodl/language/code_generator.py | 14 +- src/nmodl/language/visitors_printer.py | 86 +- src/nmodl/lexer/nmodl.ll | 1 + src/nmodl/printer/CMakeLists.txt | 13 + src/nmodl/printer/json_printer.cpp | 76 + src/nmodl/printer/json_printer.hpp | 60 + src/nmodl/visitors/CMakeLists.txt | 5 +- src/nmodl/visitors/main.cpp | 13 +- src/nmodl/visitors/verbatim_visitor.hpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 5 +- test/nmodl/transpiler/printer/printer.cpp | 38 + test/nmodl/transpiler/visitor/visitor.cpp | 58 +- 16 files changed, 15086 insertions(+), 19 deletions(-) create mode 100644 src/nmodl/ext/json/json.hpp create mode 100644 src/nmodl/printer/CMakeLists.txt create mode 100644 src/nmodl/printer/json_printer.cpp create mode 100644 src/nmodl/printer/json_printer.hpp create mode 100644 test/nmodl/transpiler/printer/printer.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index b37a4f3a2a..e443ea9104 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -47,6 +47,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) +add_subdirectory(src/printer) add_subdirectory(src/visitors) #============================================================================= diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index c9214a739b..1e7e866ea1 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -1,7 +1,7 @@ # minimal check for c++11 compliant gnu compiler if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) - message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.7 (for c++11 support)") + if (NOT (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9)) + message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.9 (for c++11 support)") endif () endif () diff --git a/src/nmodl/ext/json/json.hpp b/src/nmodl/ext/json/json.hpp new file mode 100644 index 0000000000..d28325a66b --- /dev/null +++ b/src/nmodl/ext/json/json.hpp @@ -0,0 +1,14722 @@ +/* + __ _____ _____ _____ + __| | __| | | | JSON for Modern C++ +| | |__ | | | | | | version 2.1.1 +|_____|_____|_____|_|___| https://github.com/nlohmann/json + +Licensed under the MIT License <http://opensource.org/licenses/MIT>. +Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef NLOHMANN_JSON_HPP +#define NLOHMANN_JSON_HPP + +#include <algorithm> // all_of, copy, fill, find, for_each, generate_n, none_of, remove, reverse, transform +#include <array> // array +#include <cassert> // assert +#include <ciso646> // and, not, or +#include <clocale> // lconv, localeconv +#include <cmath> // isfinite, labs, ldexp, signbit +#include <cstddef> // nullptr_t, ptrdiff_t, size_t +#include <cstdint> // int64_t, uint64_t +#include <cstdlib> // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull +#include <cstring> // memcpy, strlen +#include <forward_list> // forward_list +#include <functional> // function, hash, less +#include <initializer_list> // initializer_list +#include <iomanip> // hex +#include <iosfwd> // istream, ostream +#include <iterator> // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator +#include <limits> // numeric_limits +#include <locale> // locale +#include <map> // map +#include <memory> // addressof, allocator, allocator_traits, unique_ptr +#include <numeric> // accumulate +#include <sstream> // stringstream +#include <string> // getline, stoi, string, to_string +#include <type_traits> // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type +#include <utility> // declval, forward, make_pair, move, pair, swap +#include <valarray> // valarray +#include <vector> // vector + +// exclude unsupported compilers +#if defined(__clang__) + #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 + #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) + #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 + #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" + #endif +#endif + +// disable float-equal warnings on GCC/clang +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + +// disable documentation warnings on clang +#if defined(__clang__) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wdocumentation" +#endif + +// allow for portable deprecation warnings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) + #define JSON_DEPRECATED __declspec(deprecated) +#else + #define JSON_DEPRECATED +#endif + +// allow to disable exceptions +#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && not defined(JSON_NOEXCEPTION) + #define JSON_THROW(exception) throw exception + #define JSON_TRY try + #define JSON_CATCH(exception) catch(exception) +#else + #define JSON_THROW(exception) std::abort() + #define JSON_TRY if(true) + #define JSON_CATCH(exception) if(false) +#endif + +// manual branch prediction +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) + #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) +#else + #define JSON_LIKELY(x) x + #define JSON_UNLIKELY(x) x +#endif + +// cpp language standard detection +#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 +#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 +#endif + +/*! +@brief namespace for Niels Lohmann +@see https://github.com/nlohmann +@since version 1.0.0 +*/ +namespace nlohmann +{ +template<typename = void, typename = void> +struct adl_serializer; + +// forward declaration of basic_json (required to split the class) +template<template<typename U, typename V, typename... Args> class ObjectType = + std::map, + template<typename U, typename... Args> class ArrayType = std::vector, + class StringType = std::string, class BooleanType = bool, + class NumberIntegerType = std::int64_t, + class NumberUnsignedType = std::uint64_t, + class NumberFloatType = double, + template<typename U> class AllocatorType = std::allocator, + template<typename T, typename SFINAE = void> class JSONSerializer = + adl_serializer> +class basic_json; + +// Ugly macros to avoid uglier copy-paste when specializing basic_json +// This is only temporary and will be removed in 3.0 + +#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ + template<template<typename, typename, typename...> class ObjectType, \ + template<typename, typename...> class ArrayType, \ + class StringType, class BooleanType, class NumberIntegerType, \ + class NumberUnsignedType, class NumberFloatType, \ + template<typename> class AllocatorType, \ + template<typename, typename = void> class JSONSerializer> + +#define NLOHMANN_BASIC_JSON_TPL \ + basic_json<ObjectType, ArrayType, StringType, BooleanType, \ + NumberIntegerType, NumberUnsignedType, NumberFloatType, \ + AllocatorType, JSONSerializer> + + +/*! +@brief unnamed namespace with internal helper functions + +This namespace collects some functions that could not be defined inside the +@ref basic_json class. + +@since version 2.1.0 +*/ +namespace detail +{ +//////////////// +// exceptions // +//////////////// + +/*! +@brief general exception of the @ref basic_json class + +This class is an extension of `std::exception` objects with a member @a id for +exception ids. It is used as the base class for all exceptions thrown by the +@ref basic_json class. This class can hence be used as "wildcard" to catch +exceptions. + +Subclasses: +- @ref parse_error for exceptions indicating a parse error +- @ref invalid_iterator for exceptions indicating errors with iterators +- @ref type_error for exceptions indicating executing a member function with + a wrong type +- @ref out_of_range for exceptions indicating access out of the defined range +- @ref other_error for exceptions indicating other library errors + +@internal +@note To have nothrow-copy-constructible exceptions, we internally use + `std::runtime_error` which can cope with arbitrary-length error messages. + Intermediate strings are built with static functions and then passed to + the actual constructor. +@endinternal + +@liveexample{The following code shows how arbitrary library exceptions can be +caught.,exception} + +@since version 3.0.0 +*/ +class exception : public std::exception +{ + public: + /// returns the explanatory string + const char* what() const noexcept override + { + return m.what(); + } + + /// the id of the exception + const int id; + + protected: + exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} + + static std::string name(const std::string& ename, int id_) + { + return "[json.exception." + ename + "." + std::to_string(id_) + "] "; + } + + private: + /// an exception object as storage for error messages + std::runtime_error m; +}; + +/*! +@brief exception indicating a parse error + +This excpetion is thrown by the library when a parse error occurs. Parse errors +can occur during the deserialization of JSON text, CBOR, MessagePack, as well +as when using JSON Patch. + +Member @a byte holds the byte index of the last read character in the input +file. + +Exceptions have ids 1xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. +json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. +json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. +json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. +json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. +json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. +json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. +json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. +json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. +json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. +json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. +json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. + +@note For an input with n bytes, 1 is the index of the first character and n+1 + is the index of the terminating null byte or the end of file. This also + holds true when reading a byte vector (CBOR or MessagePack). + +@liveexample{The following code shows how a `parse_error` exception can be +caught.,parse_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class parse_error : public exception +{ + public: + /*! + @brief create a parse error exception + @param[in] id_ the id of the exception + @param[in] byte_ the byte index where the error occurred (or 0 if the + position cannot be determined) + @param[in] what_arg the explanatory string + @return parse_error object + */ + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + { + std::string w = exception::name("parse_error", id_) + "parse error" + + (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + + ": " + what_arg; + return parse_error(id_, byte_, w.c_str()); + } + + /*! + @brief byte index of the parse error + + The byte index of the last read character in the input file. + + @note For an input with n bytes, 1 is the index of the first character and + n+1 is the index of the terminating null byte or the end of file. + This also holds true when reading a byte vector (CBOR or MessagePack). + */ + const std::size_t byte; + + private: + parse_error(int id_, std::size_t byte_, const char* what_arg) + : exception(id_, what_arg), byte(byte_) {} +}; + +/*! +@brief exception indicating errors with iterators + +This exception is thrown if iterators passed to a library function do not match +the expected semantics. + +Exceptions have ids 2xx. + +name / id | example message | description +----------------------------------- | --------------- | ------------------------- +json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. +json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. +json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. +json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. +json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. +json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. +json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. +json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. +json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. +json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. +json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. +json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). + +@liveexample{The following code shows how an `invalid_iterator` exception can be +caught.,invalid_iterator} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class invalid_iterator : public exception +{ + public: + static invalid_iterator create(int id_, const std::string& what_arg) + { + std::string w = exception::name("invalid_iterator", id_) + what_arg; + return invalid_iterator(id_, w.c_str()); + } + + private: + invalid_iterator(int id_, const char* what_arg) + : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating executing a member function with a wrong type + +This exception is thrown in case of a type error; that is, a library function is +executed on a JSON value whose type does not match the expected semantics. + +Exceptions have ids 3xx. + +name / id | example message | description +----------------------------- | --------------- | ------------------------- +json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. +json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. +json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. +json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. +json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. +json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. +json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. +json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. +json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. +json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. +json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. +json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. +json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. +json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. +json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. + +@liveexample{The following code shows how a `type_error` exception can be +caught.,type_error} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref out_of_range for exceptions indicating access out of the defined range +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class type_error : public exception +{ + public: + static type_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("type_error", id_) + what_arg; + return type_error(id_, w.c_str()); + } + + private: + type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating access out of the defined range + +This exception is thrown in case a library function is called on an input +parameter that exceeds the expected range, for instance in case of array +indices or nonexisting object keys. + +Exceptions have ids 4xx. + +name / id | example message | description +------------------------------- | --------------- | ------------------------- +json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. +json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. +json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. +json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. +json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. +json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. + +@liveexample{The following code shows how an `out_of_range` exception can be +caught.,out_of_range} + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref other_error for exceptions indicating other library errors + +@since version 3.0.0 +*/ +class out_of_range : public exception +{ + public: + static out_of_range create(int id_, const std::string& what_arg) + { + std::string w = exception::name("out_of_range", id_) + what_arg; + return out_of_range(id_, w.c_str()); + } + + private: + out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + +/*! +@brief exception indicating other library errors + +This exception is thrown in case of errors that cannot be classified with the +other exception types. + +Exceptions have ids 5xx. + +name / id | example message | description +------------------------------ | --------------- | ------------------------- +json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. +json.exception.other_error.502 | invalid object size for conversion | Some conversions to user-defined types impose constraints on the object size (e.g. std::pair) + +@sa @ref exception for the base class of the library exceptions +@sa @ref parse_error for exceptions indicating a parse error +@sa @ref invalid_iterator for exceptions indicating errors with iterators +@sa @ref type_error for exceptions indicating executing a member function with + a wrong type +@sa @ref out_of_range for exceptions indicating access out of the defined range + +@liveexample{The following code shows how an `other_error` exception can be +caught.,other_error} + +@since version 3.0.0 +*/ +class other_error : public exception +{ + public: + static other_error create(int id_, const std::string& what_arg) + { + std::string w = exception::name("other_error", id_) + what_arg; + return other_error(id_, w.c_str()); + } + + private: + other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} +}; + + + +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + discarded ///< discarded by the the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string +- furthermore, each type is not smaller than itself + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array<uint8_t, 8> order = {{ + 0, // null + 3, // object + 4, // array + 5, // string + 1, // boolean + 2, // integer + 2, // unsigned + 2, // float + } + }; + + // discarded values are not comparable + return lhs != value_t::discarded and rhs != value_t::discarded and + order[static_cast<std::size_t>(lhs)] < order[static_cast<std::size_t>(rhs)]; +} + + +///////////// +// helpers // +///////////// + +template<typename> struct is_basic_json : std::false_type {}; + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {}; + +// alias templates to reduce boilerplate +template<bool B, typename T = void> +using enable_if_t = typename std::enable_if<B, T>::type; + +template<typename T> +using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; + +// implementation of C++14 index_sequence and affiliates +// source: https://stackoverflow.com/a/32223343 +template<std::size_t... Ints> +struct index_sequence +{ + using type = index_sequence; + using value_type = std::size_t; + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } +}; + +template<class Sequence1, class Sequence2> +struct merge_and_renumber; + +template<std::size_t... I1, std::size_t... I2> +struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> + : index_sequence < I1..., (sizeof...(I1) + I2)... > + {}; + +template<std::size_t N> +struct make_index_sequence + : merge_and_renumber < typename make_index_sequence < N / 2 >::type, + typename make_index_sequence < N - N / 2 >::type > +{}; + +template<> struct make_index_sequence<0> : index_sequence<> { }; +template<> struct make_index_sequence<1> : index_sequence<0> { }; + +template<typename... Ts> +using index_sequence_for = make_index_sequence<sizeof...(Ts)>; + +/* +Implementation of two C++17 constructs: conjunction, negation. This is needed +to avoid evaluating all the traits in a condition + +For example: not std::is_same<void, T>::value and has_value_type<T>::value +will not compile when T = void (on MSVC at least). Whereas +conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will +stop evaluating if negation<...>::value == false + +Please note that those constructs must be used with caution, since symbols can +become very long quickly (which can slow down compilation and cause MSVC +internal compiler errors). Only use it when you have to (see example ahead). +*/ +template<class...> struct conjunction : std::true_type {}; +template<class B1> struct conjunction<B1> : B1 {}; +template<class B1, class... Bn> +struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {}; + +template<class B> struct negation : std::integral_constant < bool, !B::value > {}; + +// dispatch utility (taken from ranges-v3) +template<unsigned N> struct priority_tag : priority_tag < N - 1 > {}; +template<> struct priority_tag<0> {}; + + +////////////////// +// constructors // +////////////////// + +template<value_t> struct external_constructor; + +template<> +struct external_constructor<value_t::boolean> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept + { + j.m_type = value_t::boolean; + j.m_value = b; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::string> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) + { + j.m_type = value_t::string; + j.m_value = s; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) + { + j.m_type = value_t::string; + j.m_value = std::move(s); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_float> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept + { + j.m_type = value_t::number_float; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_unsigned> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept + { + j.m_type = value_t::number_unsigned; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::number_integer> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept + { + j.m_type = value_t::number_integer; + j.m_value = val; + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::array> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) + { + j.m_type = value_t::array; + j.m_value = arr; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) + { + j.m_type = value_t::array; + j.m_value = std::move(arr); + j.assert_invariant(); + } + + template<typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<not std::is_same<CompatibleArrayType, + typename BasicJsonType::array_t>::value, + int> = 0> + static void construct(BasicJsonType& j, const CompatibleArrayType& arr) + { + using std::begin; + using std::end; + j.m_type = value_t::array; + j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const std::vector<bool>& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->reserve(arr.size()); + for (bool x : arr) + { + j.m_value.array->push_back(x); + } + j.assert_invariant(); + } + + template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> + static void construct(BasicJsonType& j, const std::valarray<T>& arr) + { + j.m_type = value_t::array; + j.m_value = value_t::array; + j.m_value.array->resize(arr.size()); + std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); + j.assert_invariant(); + } +}; + +template<> +struct external_constructor<value_t::object> +{ + template<typename BasicJsonType> + static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) + { + j.m_type = value_t::object; + j.m_value = obj; + j.assert_invariant(); + } + + template<typename BasicJsonType> + static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) + { + j.m_type = value_t::object; + j.m_value = std::move(obj); + j.assert_invariant(); + } + + template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<not std::is_same<CompatibleObjectType, + typename BasicJsonType::object_t>::value, int> = 0> + static void construct(BasicJsonType& j, const CompatibleObjectType& obj) + { + using std::begin; + using std::end; + + j.m_type = value_t::object; + j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); + j.assert_invariant(); + } +}; + + +//////////////////////// +// has_/is_ functions // +//////////////////////// + +/*! +@brief Helper to determine whether there's a key_type for T. + +This helper is used to tell associative containers apart from other containers +such as sequence containers. For instance, `std::map` passes the test as it +contains a `mapped_type`, whereas `std::vector` fails the test. + +@sa http://stackoverflow.com/a/7728728/266378 +@since version 1.0.0, overworked in version 2.0.6 +*/ +#define NLOHMANN_JSON_HAS_HELPER(type) \ + template<typename T> struct has_##type { \ + private: \ + template<typename U, typename = typename U::type> \ + static int detect(U &&); \ + static void detect(...); \ + public: \ + static constexpr bool value = \ + std::is_integral<decltype(detect(std::declval<T>()))>::value; \ + } + +NLOHMANN_JSON_HAS_HELPER(mapped_type); +NLOHMANN_JSON_HAS_HELPER(key_type); +NLOHMANN_JSON_HAS_HELPER(value_type); +NLOHMANN_JSON_HAS_HELPER(iterator); + +#undef NLOHMANN_JSON_HAS_HELPER + + +template<bool B, class RealType, class CompatibleObjectType> +struct is_compatible_object_type_impl : std::false_type {}; + +template<class RealType, class CompatibleObjectType> +struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> +{ + static constexpr auto value = + std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and + std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value; +}; + +template<class BasicJsonType, class CompatibleObjectType> +struct is_compatible_object_type +{ + static auto constexpr value = is_compatible_object_type_impl < + conjunction<negation<std::is_same<void, CompatibleObjectType>>, + has_mapped_type<CompatibleObjectType>, + has_key_type<CompatibleObjectType>>::value, + typename BasicJsonType::object_t, CompatibleObjectType >::value; +}; + +template<typename BasicJsonType, typename T> +struct is_basic_json_nested_type +{ + static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or + std::is_same<T, typename BasicJsonType::const_iterator>::value or + std::is_same<T, typename BasicJsonType::reverse_iterator>::value or + std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value; +}; + +template<class BasicJsonType, class CompatibleArrayType> +struct is_compatible_array_type +{ + static auto constexpr value = + conjunction<negation<std::is_same<void, CompatibleArrayType>>, + negation<is_compatible_object_type< + BasicJsonType, CompatibleArrayType>>, + negation<std::is_constructible<typename BasicJsonType::string_t, + CompatibleArrayType>>, + negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, + has_value_type<CompatibleArrayType>, + has_iterator<CompatibleArrayType>>::value; +}; + +template<bool, typename, typename> +struct is_compatible_integer_type_impl : std::false_type {}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType> +{ + // is there an assert somewhere on overflows? + using RealLimits = std::numeric_limits<RealIntegerType>; + using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; + + static constexpr auto value = + std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and + CompatibleLimits::is_integer and + RealLimits::is_signed == CompatibleLimits::is_signed; +}; + +template<typename RealIntegerType, typename CompatibleNumberIntegerType> +struct is_compatible_integer_type +{ + static constexpr auto value = + is_compatible_integer_type_impl < + std::is_integral<CompatibleNumberIntegerType>::value and + not std::is_same<bool, CompatibleNumberIntegerType>::value, + RealIntegerType, CompatibleNumberIntegerType > ::value; +}; + + +// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists +template<typename BasicJsonType, typename T> +struct has_from_json +{ + private: + // also check the return type of from_json + template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json( + std::declval<BasicJsonType>(), std::declval<T&>()))>::value>> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral<decltype( + detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; +}; + +// This trait checks if JSONSerializer<T>::from_json(json const&) exists +// this overload is used for non-default-constructible user-defined-types +template<typename BasicJsonType, typename T> +struct has_non_default_from_json +{ + private: + template < + typename U, + typename = enable_if_t<std::is_same< + T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral<decltype(detect( + std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; +}; + +// This trait checks if BasicJsonType::json_serializer<T>::to_json exists +template<typename BasicJsonType, typename T> +struct has_to_json +{ + private: + template<typename U, typename = decltype(uncvref_t<U>::to_json( + std::declval<BasicJsonType&>(), std::declval<T>()))> + static int detect(U&&); + static void detect(...); + + public: + static constexpr bool value = std::is_integral<decltype(detect( + std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; +}; + + +///////////// +// to_json // +///////////// + +template<typename BasicJsonType, typename T, enable_if_t< + std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> +void to_json(BasicJsonType& j, T b) noexcept +{ + external_constructor<value_t::boolean>::construct(j, b); +} + +template<typename BasicJsonType, typename CompatibleString, + enable_if_t<std::is_constructible<typename BasicJsonType::string_t, + CompatibleString>::value, int> = 0> +void to_json(BasicJsonType& j, const CompatibleString& s) +{ + external_constructor<value_t::string>::construct(j, s); +} + +template <typename BasicJsonType> +void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +{ + external_constructor<value_t::string>::construct(j, std::move(s)); +} + +template<typename BasicJsonType, typename FloatType, + enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> +void to_json(BasicJsonType& j, FloatType val) noexcept +{ + external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val)); +} + +template < + typename BasicJsonType, typename CompatibleNumberUnsignedType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, + CompatibleNumberUnsignedType>::value, int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +{ + external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val)); +} + +template < + typename BasicJsonType, typename CompatibleNumberIntegerType, + enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, + CompatibleNumberIntegerType>::value, int> = 0 > +void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +{ + external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val)); +} + +template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +void to_json(BasicJsonType& j, EnumType e) noexcept +{ + using underlying_type = typename std::underlying_type<EnumType>::type; + external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); +} + +template<typename BasicJsonType> +void to_json(BasicJsonType& j, const std::vector<bool>& e) +{ + external_constructor<value_t::array>::construct(j, e); +} + +template < + typename BasicJsonType, typename CompatibleArrayType, + enable_if_t < + is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or + std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, + int > = 0 > +void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +{ + external_constructor<value_t::array>::construct(j, arr); +} + +template <typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> +void to_json(BasicJsonType& j, std::valarray<T> arr) +{ + external_constructor<value_t::array>::construct(j, std::move(arr)); +} + +template <typename BasicJsonType> +void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +{ + external_constructor<value_t::array>::construct(j, std::move(arr)); +} + +template < + typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, + int> = 0 > +void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +{ + external_constructor<value_t::object>::construct(j, obj); +} + +template <typename BasicJsonType> +void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +{ + external_constructor<value_t::object>::construct(j, std::move(obj)); +} + +template<typename BasicJsonType, typename T, std::size_t N, + enable_if_t<not std::is_constructible< + typename BasicJsonType::string_t, T (&)[N]>::value, + int> = 0> +void to_json(BasicJsonType& j, T (&arr)[N]) +{ + external_constructor<value_t::array>::construct(j, arr); +} + +template<typename BasicJsonType, typename... Args> +void to_json(BasicJsonType& j, const std::pair<Args...>& p) +{ + j = {p.first, p.second}; +} + +template<typename BasicJsonType, typename Tuple, std::size_t... Idx> +void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...>) +{ + j = {std::get<Idx>(t)...}; +} + +template<typename BasicJsonType, typename... Args> +void to_json(BasicJsonType& j, const std::tuple<Args...>& t) +{ + to_json_tuple_impl(j, t, index_sequence_for<Args...> {}); +} + +/////////////// +// from_json // +/////////////// + +// overloads for basic_json template parameters +template<typename BasicJsonType, typename ArithmeticType, + enable_if_t<std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, + typename BasicJsonType::boolean_t>::value, + int> = 0> +void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast<value_t>(j)) + { + case value_t::number_unsigned: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); + break; + } + case value_t::number_integer: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); + break; + } + case value_t::number_float: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +{ + if (JSON_UNLIKELY(not j.is_boolean())) + { + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); + } + b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>(); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +{ + if (JSON_UNLIKELY(not j.is_string())) + { + JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); + } + s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +{ + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +{ + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +{ + get_arithmetic_value(j, val); +} + +template<typename BasicJsonType, typename EnumType, + enable_if_t<std::is_enum<EnumType>::value, int> = 0> +void from_json(const BasicJsonType& j, EnumType& e) +{ + typename std::underlying_type<EnumType>::type val; + get_arithmetic_value(j, val); + e = static_cast<EnumType>(val); +} + +template<typename BasicJsonType> +void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + arr = *j.template get_ptr<const typename BasicJsonType::array_t*>(); +} + +// forward_list doesn't have an insert method +template<typename BasicJsonType, typename T, typename Allocator, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> +void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + std::transform(j.rbegin(), j.rend(), + std::front_inserter(l), [](const BasicJsonType & i) + { + return i.template get<T>(); + }); +} + +// valarray doesn't have an insert method +template<typename BasicJsonType, typename T, + enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> +void from_json(const BasicJsonType& j, std::valarray<T>& l) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + l.resize(j.size()); + std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); +} + +template<typename BasicJsonType, typename CompatibleArrayType> +void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) +{ + using std::end; + + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get<BasicJsonType>() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} + +template<typename BasicJsonType, typename CompatibleArrayType> +auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) +-> decltype( + arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), + void()) +{ + using std::end; + + arr.reserve(j.size()); + std::transform(j.begin(), j.end(), + std::inserter(arr, end(arr)), [](const BasicJsonType & i) + { + // get<BasicJsonType>() returns *this, this won't call a from_json + // method when value_type is BasicJsonType + return i.template get<typename CompatibleArrayType::value_type>(); + }); +} + +template<typename BasicJsonType, typename T, std::size_t N> +void from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr, priority_tag<2> /*unused*/) +{ + for (std::size_t i = 0; i < N; ++i) + { + arr[i] = j.at(i).template get<T>(); + } +} + +template<typename BasicJsonType, typename CompatibleArrayType, + enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and + std::is_convertible<BasicJsonType, typename CompatibleArrayType::value_type>::value and + not std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleArrayType& arr) +{ + if (JSON_UNLIKELY(not j.is_array())) + { + JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); + } + + from_json_array_impl(j, arr, priority_tag<2> {}); +} + +template<typename BasicJsonType, typename CompatibleObjectType, + enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> +void from_json(const BasicJsonType& j, CompatibleObjectType& obj) +{ + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); + } + + auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); + using value_type = typename CompatibleObjectType::value_type; + std::transform( + inner_object->begin(), inner_object->end(), + std::inserter(obj, obj.begin()), + [](typename BasicJsonType::object_t::value_type const & p) + { + return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>()); + }); +} + +// overload for arithmetic types, not chosen for basic_json template arguments +// (BooleanType, etc..); note: Is it really necessary to provide explicit +// overloads for boolean_t etc. in case of a custom BooleanType which is not +// an arithmetic type? +template<typename BasicJsonType, typename ArithmeticType, + enable_if_t < + std::is_arithmetic<ArithmeticType>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and + not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, + int> = 0> +void from_json(const BasicJsonType& j, ArithmeticType& val) +{ + switch (static_cast<value_t>(j)) + { + case value_t::number_unsigned: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); + break; + } + case value_t::number_integer: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); + break; + } + case value_t::number_float: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); + break; + } + case value_t::boolean: + { + val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>()); + break; + } + + default: + JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); + } +} + +template<typename BasicJsonType, typename A1, typename A2> +void from_json(const BasicJsonType& j, std::pair<A1, A2>& p) +{ + p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()}; +} + +template<typename BasicJsonType, typename Tuple, std::size_t... Idx> +void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...>) +{ + t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...); +} + +template<typename BasicJsonType, typename... Args> +void from_json(const BasicJsonType& j, std::tuple<Args...>& t) +{ + from_json_tuple_impl(j, t, index_sequence_for<Args...> {}); +} + +struct to_json_fn +{ + private: + template<typename BasicJsonType, typename T> + auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) + -> decltype(to_json(j, std::forward<T>(val)), void()) + { + return to_json(j, std::forward<T>(val)); + } + + template<typename BasicJsonType, typename T> + void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find to_json() method in T's namespace"); + } + + public: + template<typename BasicJsonType, typename T> + void operator()(BasicJsonType& j, T&& val) const + noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1> {}))) + { + return call(j, std::forward<T>(val), priority_tag<1> {}); + } +}; + +struct from_json_fn +{ + private: + template<typename BasicJsonType, typename T> + auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const + noexcept(noexcept(from_json(j, val))) + -> decltype(from_json(j, val), void()) + { + return from_json(j, val); + } + + template<typename BasicJsonType, typename T> + void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept + { + static_assert(sizeof(BasicJsonType) == 0, + "could not find from_json() method in T's namespace"); + } + + public: + template<typename BasicJsonType, typename T> + void operator()(const BasicJsonType& j, T& val) const + noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1> {}))) + { + return call(j, val, priority_tag<1> {}); + } +}; + +// taken from ranges-v3 +template<typename T> +struct static_const +{ + static constexpr T value{}; +}; + +template<typename T> +constexpr T static_const<T>::value; + +//////////////////// +// input adapters // +//////////////////// + +/*! +@brief abstract input adapter interface + +Produces a stream of std::char_traits<char>::int_type characters from a +std::istream, a buffer, or some other input type. Accepts the return of exactly +one non-EOF character for future input. The int_type characters returned +consist of all valid char values as positive values (typically unsigned char), +plus an EOF value outside that range, specified by the value of the function +std::char_traits<char>::eof(). This value is typically -1, but could be any +arbitrary value which is not a valid char value. +*/ +struct input_adapter_protocol +{ + /// get a character [0,255] or std::char_traits<char>::eof(). + virtual std::char_traits<char>::int_type get_character() = 0; + /// restore the last non-eof() character to input + virtual void unget_character() = 0; + virtual ~input_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +using input_adapter_t = std::shared_ptr<input_adapter_protocol>; + +/*! +Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at +beginning of input. Does not support changing the underlying std::streambuf +in mid-input. Maintains underlying std::istream and std::streambuf to support +subsequent use of standard std::istream operations to process any input +characters following those used in parsing the JSON input. Clears the +std::istream flags; any input errors (e.g., EOF) will be detected by the first +subsequent call for input from the std::istream. +*/ +class input_stream_adapter : public input_adapter_protocol +{ + public: + ~input_stream_adapter() override + { + // clear stream flags; we use underlying streambuf I/O, do not + // maintain ifstream flags + is.clear(); + } + + explicit input_stream_adapter(std::istream& i) + : is(i), sb(*i.rdbuf()) + { + // ignore Byte Order Mark at start of input + std::char_traits<char>::int_type c; + if ((c = get_character()) == 0xEF) + { + if ((c = get_character()) == 0xBB) + { + if ((c = get_character()) == 0xBF) + { + return; // Ignore BOM + } + else if (c != std::char_traits<char>::eof()) + { + is.unget(); + } + is.putback('\xBB'); + } + else if (c != std::char_traits<char>::eof()) + { + is.unget(); + } + is.putback('\xEF'); + } + else if (c != std::char_traits<char>::eof()) + { + is.unget(); // Not BOM. Process as usual. + } + } + + // delete because of pointer members + input_stream_adapter(const input_stream_adapter&) = delete; + input_stream_adapter& operator=(input_stream_adapter&) = delete; + + // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to + // ensure that std::char_traits<char>::eof() and the character 0xff do not + // end up as the same value, eg. 0xffffffff. + std::char_traits<char>::int_type get_character() override + { + return sb.sbumpc(); + } + + void unget_character() override + { + sb.sungetc(); // is.unget() avoided for performance + } + + private: + /// the associated input stream + std::istream& is; + std::streambuf& sb; +}; + +/// input adapter for buffer input +class input_buffer_adapter : public input_adapter_protocol +{ + public: + input_buffer_adapter(const char* b, const std::size_t l) + : cursor(b), limit(b + l), start(b) + { + // skip byte order mark + if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') + { + cursor += 3; + } + } + + // delete because of pointer members + input_buffer_adapter(const input_buffer_adapter&) = delete; + input_buffer_adapter& operator=(input_buffer_adapter&) = delete; + + std::char_traits<char>::int_type get_character() noexcept override + { + if (JSON_LIKELY(cursor < limit)) + { + return std::char_traits<char>::to_int_type(*(cursor++)); + } + + return std::char_traits<char>::eof(); + } + + void unget_character() noexcept override + { + if (JSON_LIKELY(cursor > start)) + { + --cursor; + } + } + + private: + /// pointer to the current character + const char* cursor; + /// pointer past the last character + const char* limit; + /// pointer to the first character + const char* start; +}; + +class input_adapter +{ + public: + // native support + + /// input adapter for input stream + input_adapter(std::istream& i) + : ia(std::make_shared<input_stream_adapter>(i)) {} + + /// input adapter for input stream + input_adapter(std::istream&& i) + : ia(std::make_shared<input_stream_adapter>(i)) {} + + /// input adapter for buffer + template<typename CharT, + typename std::enable_if< + std::is_pointer<CharT>::value and + std::is_integral< + typename std::remove_pointer<CharT>::type>::value and + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int>::type = 0> + input_adapter(CharT b, std::size_t l) + : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(b), l)) {} + + // derived support + + /// input adapter for string literal + template<typename CharT, + typename std::enable_if< + std::is_pointer<CharT>::value and + std::is_integral< + typename std::remove_pointer<CharT>::type>::value and + sizeof(typename std::remove_pointer<CharT>::type) == 1, + int>::type = 0> + input_adapter(CharT b) + : input_adapter(reinterpret_cast<const char*>(b), + std::strlen(reinterpret_cast<const char*>(b))) {} + + /// input adapter for iterator range with contiguous storage + template<class IteratorType, + typename std::enable_if< + std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, + std::random_access_iterator_tag>::value, + int>::type = 0> + input_adapter(IteratorType first, IteratorType last) + { + // assertion to check that the iterator range is indeed contiguous, + // see http://stackoverflow.com/a/35008842/266378 for more discussion + assert(std::accumulate( + first, last, std::pair<bool, int>(true, 0), + [&first](std::pair<bool, int> res, decltype(*first) val) + { + res.first &= (val == *(std::next(std::addressof(*first), res.second++))); + return res; + }).first); + + // assertion to check that each element is 1 byte long + static_assert( + sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1, + "each element in the iterator range must have the size of 1 byte"); + + const auto len = static_cast<size_t>(std::distance(first, last)); + if (JSON_LIKELY(len > 0)) + { + // there is at least one element: use the address of first + ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(&(*first)), len); + } + else + { + // the address of first cannot be used: use nullptr + ia = std::make_shared<input_buffer_adapter>(nullptr, len); + } + } + + /// input adapter for array + template<class T, std::size_t N> + input_adapter(T (&array)[N]) + : input_adapter(std::begin(array), std::end(array)) {} + + /// input adapter for contiguous container + template < + class ContiguousContainer, + typename std::enable_if < + not std::is_pointer<ContiguousContainer>::value and + std::is_base_of<std::random_access_iterator_tag, + typename std::iterator_traits<decltype(std::begin(std::declval<ContiguousContainer const>()))>::iterator_category>::value, + int >::type = 0 > + input_adapter(const ContiguousContainer& c) + : input_adapter(std::begin(c), std::end(c)) {} + + operator input_adapter_t() + { + return ia; + } + + private: + /// the actual adapter + input_adapter_t ia = nullptr; +}; + +////////////////////// +// lexer and parser // +////////////////////// + +/*! +@brief lexical analysis + +This class organizes the lexical analysis during JSON deserialization. +*/ +template<typename BasicJsonType> +class lexer +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + + public: + /// token types for the parser + enum class token_type + { + uninitialized, ///< indicating the scanner is uninitialized + literal_true, ///< the `true` literal + literal_false, ///< the `false` literal + literal_null, ///< the `null` literal + value_string, ///< a string -- use get_string() for actual value + value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value + value_integer, ///< a signed integer -- use get_number_integer() for actual value + value_float, ///< an floating point number -- use get_number_float() for actual value + begin_array, ///< the character for array begin `[` + begin_object, ///< the character for object begin `{` + end_array, ///< the character for array end `]` + end_object, ///< the character for object end `}` + name_separator, ///< the name separator `:` + value_separator, ///< the value separator `,` + parse_error, ///< indicating a parse error + end_of_input, ///< indicating the end of the input buffer + literal_or_value ///< a literal or the begin of a value (only for diagnostics) + }; + + /// return name of values of type token_type (only used for errors) + static const char* token_type_name(const token_type t) noexcept + { + switch (t) + { + case token_type::uninitialized: + return "<uninitialized>"; + case token_type::literal_true: + return "true literal"; + case token_type::literal_false: + return "false literal"; + case token_type::literal_null: + return "null literal"; + case token_type::value_string: + return "string literal"; + case lexer::token_type::value_unsigned: + case lexer::token_type::value_integer: + case lexer::token_type::value_float: + return "number literal"; + case token_type::begin_array: + return "'['"; + case token_type::begin_object: + return "'{'"; + case token_type::end_array: + return "']'"; + case token_type::end_object: + return "'}'"; + case token_type::name_separator: + return "':'"; + case token_type::value_separator: + return "','"; + case token_type::parse_error: + return "<parse error>"; + case token_type::end_of_input: + return "end of input"; + case token_type::literal_or_value: + return "'[', '{', or a literal"; + default: // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } + } + + explicit lexer(detail::input_adapter_t adapter) + : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} + + // delete because of pointer members + lexer(const lexer&) = delete; + lexer& operator=(lexer&) = delete; + + private: + ///////////////////// + // locales + ///////////////////// + + /// return the locale-dependent decimal point + static char get_decimal_point() noexcept + { + const auto loc = localeconv(); + assert(loc != nullptr); + return (loc->decimal_point == nullptr) ? '.' : loc->decimal_point[0]; + } + + ///////////////////// + // scan functions + ///////////////////// + + /*! + @brief get codepoint from 4 hex characters following `\u` + + For input "\u c1 c2 c3 c4" the codepoint is: + (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 + = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) + + Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' + must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The + conversion is done by subtracting the offset (0x30, 0x37, and 0x57) + between the ASCII value of the character and the desired integer value. + + @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or + non-hex character) + */ + int get_codepoint() + { + // this function only makes sense after reading `\u` + assert(current == 'u'); + int codepoint = 0; + + const auto factors = { 12, 8, 4, 0 }; + for (const auto factor : factors) + { + get(); + + if (current >= '0' and current <= '9') + { + codepoint += ((current - 0x30) << factor); + } + else if (current >= 'A' and current <= 'F') + { + codepoint += ((current - 0x37) << factor); + } + else if (current >= 'a' and current <= 'f') + { + codepoint += ((current - 0x57) << factor); + } + else + { + return -1; + } + } + + assert(0x0000 <= codepoint and codepoint <= 0xFFFF); + return codepoint; + } + + /*! + @brief check if the next byte(s) are inside a given range + + Adds the current byte and, for each passed range, reads a new byte and + checks if it is inside the range. If a violation was detected, set up an + error message and return false. Otherwise, return true. + + @return true if and only if no range violation was detected + */ + bool next_byte_in_range(std::initializer_list<int> ranges) + { + assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); + add(current); + + for (auto range = ranges.begin(); range != ranges.end(); ++range) + { + get(); + if (JSON_LIKELY(*range <= current and current <= *(++range))) + { + add(current); + } + else + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return false; + } + } + + return true; + } + + /*! + @brief scan a string literal + + This function scans a string according to Sect. 7 of RFC 7159. While + scanning, bytes are escaped and copied into buffer yytext. Then the function + returns successfully, yytext is *not* null-terminated (as it may contain \0 + bytes), and yytext.size() is the number of bytes in the string. + + @return token_type::value_string if string could be successfully scanned, + token_type::parse_error otherwise + + @note In case of errors, variable error_message contains a textual + description. + */ + token_type scan_string() + { + // reset yytext (ignore opening quote) + reset(); + + // we entered the function by reading an open quote + assert(current == '\"'); + + while (true) + { + // get next character + switch (get()) + { + // end of file while parsing string + case std::char_traits<char>::eof(): + { + error_message = "invalid string: missing closing quote"; + return token_type::parse_error; + } + + // closing quote + case '\"': + { + return token_type::value_string; + } + + // escapes + case '\\': + { + switch (get()) + { + // quotation mark + case '\"': + add('\"'); + break; + // reverse solidus + case '\\': + add('\\'); + break; + // solidus + case '/': + add('/'); + break; + // backspace + case 'b': + add('\b'); + break; + // form feed + case 'f': + add('\f'); + break; + // line feed + case 'n': + add('\n'); + break; + // carriage return + case 'r': + add('\r'); + break; + // tab + case 't': + add('\t'); + break; + + // unicode escapes + case 'u': + { + int codepoint; + const int codepoint1 = get_codepoint(); + + if (JSON_UNLIKELY(codepoint1 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if code point is a high surrogate + if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) + { + // expect next \uxxxx entry + if (JSON_LIKELY(get() == '\\' and get() == 'u')) + { + const int codepoint2 = get_codepoint(); + + if (JSON_UNLIKELY(codepoint2 == -1)) + { + error_message = "invalid string: '\\u' must be followed by 4 hex digits"; + return token_type::parse_error; + } + + // check if codepoint2 is a low surrogate + if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) + { + codepoint = + // high surrogate occupies the most significant 22 bits + (codepoint1 << 10) + // low surrogate occupies the least significant 15 bits + + codepoint2 + // there is still the 0xD800, 0xDC00 and 0x10000 noise + // in the result so we have to subtract with: + // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 + - 0x35FDC00; + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; + return token_type::parse_error; + } + } + else + { + if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) + { + error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; + return token_type::parse_error; + } + + // only work with first code point + codepoint = codepoint1; + } + + // result of the above calculation yields a proper codepoint + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + // translate code point to bytes + if (codepoint < 0x80) + { + // 1-byte characters: 0xxxxxxx (ASCII) + add(codepoint); + } + else if (codepoint <= 0x7ff) + { + // 2-byte characters: 110xxxxx 10xxxxxx + add(0xC0 | (codepoint >> 6)); + add(0x80 | (codepoint & 0x3F)); + } + else if (codepoint <= 0xffff) + { + // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx + add(0xE0 | (codepoint >> 12)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + else + { + // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + add(0xF0 | (codepoint >> 18)); + add(0x80 | ((codepoint >> 12) & 0x3F)); + add(0x80 | ((codepoint >> 6) & 0x3F)); + add(0x80 | (codepoint & 0x3F)); + } + + break; + } + + // other characters after escape + default: + error_message = "invalid string: forbidden character after backslash"; + return token_type::parse_error; + } + + break; + } + + // invalid control characters + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: + { + error_message = "invalid string: control character must be escaped"; + return token_type::parse_error; + } + + // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) + case 0x20: + case 0x21: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5a: + case 0x5b: + case 0x5d: + case 0x5e: + case 0x5f: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7a: + case 0x7b: + case 0x7c: + case 0x7d: + case 0x7e: + case 0x7f: + { + add(current); + break; + } + + // U+0080..U+07FF: bytes C2..DF 80..BF + case 0xc2: + case 0xc3: + case 0xc4: + case 0xc5: + case 0xc6: + case 0xc7: + case 0xc8: + case 0xc9: + case 0xca: + case 0xcb: + case 0xcc: + case 0xcd: + case 0xce: + case 0xcf: + case 0xd0: + case 0xd1: + case 0xd2: + case 0xd3: + case 0xd4: + case 0xd5: + case 0xd6: + case 0xd7: + case 0xd8: + case 0xd9: + case 0xda: + case 0xdb: + case 0xdc: + case 0xdd: + case 0xde: + case 0xdf: + { + if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) + { + return token_type::parse_error; + } + break; + } + + // U+0800..U+0FFF: bytes E0 A0..BF 80..BF + case 0xe0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF + // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF + case 0xe1: + case 0xe2: + case 0xe3: + case 0xe4: + case 0xe5: + case 0xe6: + case 0xe7: + case 0xe8: + case 0xe9: + case 0xea: + case 0xeb: + case 0xec: + case 0xee: + case 0xef: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+D000..U+D7FF: bytes ED 80..9F 80..BF + case 0xed: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF + case 0xf0: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF + case 0xf1: + case 0xf2: + case 0xf3: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF + case 0xf4: + { + if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) + { + return token_type::parse_error; + } + break; + } + + // remaining bytes (80..C1 and F5..FF) are ill-formed + default: + { + error_message = "invalid string: ill-formed UTF-8 byte"; + return token_type::parse_error; + } + } + } + } + + static void strtof(float& f, const char* str, char** endptr) noexcept + { + f = std::strtof(str, endptr); + } + + static void strtof(double& f, const char* str, char** endptr) noexcept + { + f = std::strtod(str, endptr); + } + + static void strtof(long double& f, const char* str, char** endptr) noexcept + { + f = std::strtold(str, endptr); + } + + /*! + @brief scan a number literal + + This function scans a string according to Sect. 6 of RFC 7159. + + The function is realized with a deterministic finite state machine derived + from the grammar described in RFC 7159. Starting in state "init", the + input is read and used to determined the next state. Only state "done" + accepts the number. State "error" is a trap state to model errors. In the + table below, "anything" means any character but the ones listed before. + + state | 0 | 1-9 | e E | + | - | . | anything + ---------|----------|----------|----------|---------|---------|----------|----------- + init | zero | any1 | [error] | [error] | minus | [error] | [error] + minus | zero | any1 | [error] | [error] | [error] | [error] | [error] + zero | done | done | exponent | done | done | decimal1 | done + any1 | any1 | any1 | exponent | done | done | decimal1 | done + decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] + decimal2 | decimal2 | decimal2 | exponent | done | done | done | done + exponent | any2 | any2 | [error] | sign | sign | [error] | [error] + sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] + any2 | any2 | any2 | done | done | done | done | done + + The state machine is realized with one label per state (prefixed with + "scan_number_") and `goto` statements between them. The state machine + contains cycles, but any cycle can be left when EOF is read. Therefore, + the function is guaranteed to terminate. + + During scanning, the read bytes are stored in yytext. This string is + then converted to a signed integer, an unsigned integer, or a + floating-point number. + + @return token_type::value_unsigned, token_type::value_integer, or + token_type::value_float if number could be successfully scanned, + token_type::parse_error otherwise + + @note The scanner is independent of the current locale. Internally, the + locale's decimal point is used instead of `.` to work with the + locale-dependent converters. + */ + token_type scan_number() + { + // reset yytext to store the number's bytes + reset(); + + // the type of the parsed number; initially set to unsigned; will be + // changed if minus sign, decimal point or exponent is read + token_type number_type = token_type::value_unsigned; + + // state (init): we just found out we need to scan a number + switch (current) + { + case '-': + { + add(current); + goto scan_number_minus; + } + + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + // all other characters are rejected outside scan_number() + assert(false); // LCOV_EXCL_LINE + } + } + +scan_number_minus: + // state: we just parsed a leading minus sign + number_type = token_type::value_integer; + switch (get()) + { + case '0': + { + add(current); + goto scan_number_zero; + } + + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + default: + { + error_message = "invalid number; expected digit after '-'"; + return token_type::parse_error; + } + } + +scan_number_zero: + // state: we just parse a zero (maybe with a leading minus sign) + switch (get()) + { + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_any1: + // state: we just parsed a number 0-9 (maybe with a leading minus sign) + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any1; + } + + case '.': + { + add(decimal_point_char); + goto scan_number_decimal1; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_decimal1: + // state: we just parsed a decimal point + number_type = token_type::value_float; + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + default: + { + error_message = "invalid number; expected digit after '.'"; + return token_type::parse_error; + } + } + +scan_number_decimal2: + // we just parsed at least one number after a decimal point + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_decimal2; + } + + case 'e': + case 'E': + { + add(current); + goto scan_number_exponent; + } + + default: + goto scan_number_done; + } + +scan_number_exponent: + // we just parsed an exponent + number_type = token_type::value_float; + switch (get()) + { + case '+': + case '-': + { + add(current); + goto scan_number_sign; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = + "invalid number; expected '+', '-', or digit after exponent"; + return token_type::parse_error; + } + } + +scan_number_sign: + // we just parsed an exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + { + error_message = "invalid number; expected digit after exponent sign"; + return token_type::parse_error; + } + } + +scan_number_any2: + // we just parsed a number after the exponent or exponent sign + switch (get()) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + add(current); + goto scan_number_any2; + } + + default: + goto scan_number_done; + } + +scan_number_done: + // unget the character after the number (we only read it to know that + // we are done scanning a number) + unget(); + + char* endptr = nullptr; + errno = 0; + + // try to parse integers first and fall back to floats + if (number_type == token_type::value_unsigned) + { + const auto x = std::strtoull(yytext.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == yytext.data() + yytext.size()); + + if (errno == 0) + { + value_unsigned = static_cast<number_unsigned_t>(x); + if (value_unsigned == x) + { + return token_type::value_unsigned; + } + } + } + else if (number_type == token_type::value_integer) + { + const auto x = std::strtoll(yytext.data(), &endptr, 10); + + // we checked the number format before + assert(endptr == yytext.data() + yytext.size()); + + if (errno == 0) + { + value_integer = static_cast<number_integer_t>(x); + if (value_integer == x) + { + return token_type::value_integer; + } + } + } + + // this code is reached if we parse a floating-point number or if an + // integer conversion above failed + strtof(value_float, yytext.data(), &endptr); + + // we checked the number format before + assert(endptr == yytext.data() + yytext.size()); + + return token_type::value_float; + } + + /*! + @param[in] literal_text the literal text to expect + @param[in] length the length of the passed literal text + @param[in] return_type the token type to return on success + */ + token_type scan_literal(const char* literal_text, const std::size_t length, + token_type return_type) + { + assert(current == literal_text[0]); + for (std::size_t i = 1; i < length; ++i) + { + if (JSON_UNLIKELY(get() != literal_text[i])) + { + error_message = "invalid literal"; + return token_type::parse_error; + } + } + return return_type; + } + + ///////////////////// + // input management + ///////////////////// + + /// reset yytext; current character is beginning of token + void reset() noexcept + { + yytext.clear(); + token_string.clear(); + token_string.push_back(std::char_traits<char>::to_char_type(current)); + } + + /* + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a + `std::char_traits<char>::eof()` in that case. Stores the scanned characters + for use in error messages. + + @return character read from the input + */ + std::char_traits<char>::int_type get() + { + ++chars_read; + current = ia->get_character(); + if (JSON_LIKELY(current != std::char_traits<char>::eof())) + { + token_string.push_back(std::char_traits<char>::to_char_type(current)); + } + return current; + } + + /// unget current character (return it again on next get) + void unget() + { + --chars_read; + if (JSON_LIKELY(current != std::char_traits<char>::eof())) + { + ia->unget_character(); + assert(token_string.size() != 0); + token_string.pop_back(); + } + } + + /// add a character to yytext + void add(int c) + { + yytext.push_back(std::char_traits<char>::to_char_type(c)); + } + + public: + ///////////////////// + // value getters + ///////////////////// + + /// return integer value + constexpr number_integer_t get_number_integer() const noexcept + { + return value_integer; + } + + /// return unsigned integer value + constexpr number_unsigned_t get_number_unsigned() const noexcept + { + return value_unsigned; + } + + /// return floating-point value + constexpr number_float_t get_number_float() const noexcept + { + return value_float; + } + + /// return current string value (implicitly resets the token; useful only once) + std::string move_string() + { + return std::move(yytext); + } + + ///////////////////// + // diagnostics + ///////////////////// + + /// return position of last read token + constexpr std::size_t get_position() const noexcept + { + return chars_read; + } + + /// return the last read token (for errors only). Will never contain EOF + /// (an arbitrary value that is not a valid char value, often -1), because + /// 255 may legitimately occur. May contain NUL, which should be escaped. + std::string get_token_string() const + { + // escape control characters + std::string result; + for (auto c : token_string) + { + if ('\x00' <= c and c <= '\x1f') + { + // escape control characters + std::stringstream ss; + ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0') + << std::hex << static_cast<int>(c) << ">"; + result += ss.str(); + } + else + { + // add character as is + result.push_back(c); + } + } + + return result; + } + + /// return syntax error message + constexpr const char* get_error_message() const noexcept + { + return error_message; + } + + ///////////////////// + // actual scanner + ///////////////////// + + token_type scan() + { + // read next character and ignore whitespace + do + { + get(); + } + while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); + + switch (current) + { + // structural characters + case '[': + return token_type::begin_array; + case ']': + return token_type::end_array; + case '{': + return token_type::begin_object; + case '}': + return token_type::end_object; + case ':': + return token_type::name_separator; + case ',': + return token_type::value_separator; + + // literals + case 't': + return scan_literal("true", 4, token_type::literal_true); + case 'f': + return scan_literal("false", 5, token_type::literal_false); + case 'n': + return scan_literal("null", 4, token_type::literal_null); + + // string + case '\"': + return scan_string(); + + // number + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + return scan_number(); + + // end of input (the null byte is needed when parsing from + // string literals) + case '\0': + case std::char_traits<char>::eof(): + return token_type::end_of_input; + + // error + default: + error_message = "invalid literal"; + return token_type::parse_error; + } + } + + private: + /// input adapter + detail::input_adapter_t ia = nullptr; + + /// the current character + std::char_traits<char>::int_type current = std::char_traits<char>::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// raw input token string (for error messages) + std::vector<char> token_string { }; + + /// buffer for variable-length tokens (numbers, strings) + std::string yytext { }; + + /// a description of occurred lexer errors + const char* error_message = ""; + + // number values + number_integer_t value_integer = 0; + number_unsigned_t value_unsigned = 0; + number_float_t value_float = 0; + + /// the decimal point + const char decimal_point_char = '.'; +}; + +/*! +@brief syntax analysis + +This class implements a recursive decent parser. +*/ +template<typename BasicJsonType> +class parser +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + using number_float_t = typename BasicJsonType::number_float_t; + using lexer_t = lexer<BasicJsonType>; + using token_type = typename lexer_t::token_type; + + public: + enum class parse_event_t : uint8_t + { + /// the parser read `{` and started to process a JSON object + object_start, + /// the parser read `}` and finished processing a JSON object + object_end, + /// the parser read `[` and started to process a JSON array + array_start, + /// the parser read `]` and finished processing a JSON array + array_end, + /// the parser read a key of a value in an object + key, + /// the parser finished reading a JSON value + value + }; + + using parser_callback_t = + std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>; + + /// a parser reading from an input adapter + explicit parser(detail::input_adapter_t adapter, + const parser_callback_t cb = nullptr, + const bool allow_exceptions_ = true) + : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) + {} + + /*! + @brief public parser interface + + @param[in] strict whether to expect the last token to be EOF + @param[in,out] result parsed JSON value + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse(const bool strict, BasicJsonType& result) + { + // read first token + get_token(); + + parse_internal(true, result); + result.assert_invariant(); + + // in strict mode, input must be completely read + if (strict) + { + get_token(); + expect(token_type::end_of_input); + } + + // in case of an error, return discarded value + if (errored) + { + result = value_t::discarded; + return; + } + + // set top-level value to null if it was discarded by the callback + // function + if (result.is_discarded()) + { + result = nullptr; + } + } + + /*! + @brief public accept interface + + @param[in] strict whether to expect the last token to be EOF + @return whether the input is a proper JSON text + */ + bool accept(const bool strict = true) + { + // read first token + get_token(); + + if (not accept_internal()) + { + return false; + } + + // strict => last token must be EOF + return not strict or (get_token() == token_type::end_of_input); + } + + private: + /*! + @brief the actual parser + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + */ + void parse_internal(bool keep, BasicJsonType& result) + { + // never parse after a parse error was detected + assert(not errored); + + // start with a discarded value + if (not result.is_discarded()) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + + switch (last_token) + { + case token_type::begin_object: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::object_start, result); + } + + if (not callback or keep) + { + // explicitly set result to object to cope with {} + result.m_type = value_t::object; + result.m_value = value_t::object; + } + } + + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + std::string key; + BasicJsonType value; + while (true) + { + // store key + if (not expect(token_type::value_string)) + { + return; + } + key = m_lexer.move_string(); + + bool keep_tag = false; + if (keep) + { + if (callback) + { + BasicJsonType k(key); + keep_tag = callback(depth, parse_event_t::key, k); + } + else + { + keep_tag = true; + } + } + + // parse separator (:) + get_token(); + if (not expect(token_type::name_separator)) + { + return; + } + + // parse and add value + get_token(); + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and keep_tag and not value.is_discarded()) + { + result.m_value.object->emplace(std::move(key), std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + if (not expect(token_type::end_object)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::begin_array: + { + if (keep) + { + if (callback) + { + keep = callback(depth++, parse_event_t::array_start, result); + } + + if (not callback or keep) + { + // explicitly set result to array to cope with [] + result.m_type = value_t::array; + result.m_value = value_t::array; + } + } + + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + if (callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + // parse values + BasicJsonType value; + while (true) + { + // parse value + value.m_value.destroy(value.m_type); + value.m_type = value_t::discarded; + parse_internal(keep, value); + + if (JSON_UNLIKELY(errored)) + { + return; + } + + if (keep and not value.is_discarded()) + { + result.m_value.array->push_back(std::move(value)); + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + if (not expect(token_type::end_array)) + { + return; + } + break; + } + + if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) + { + result.m_value.destroy(result.m_type); + result.m_type = value_t::discarded; + } + break; + } + + case token_type::literal_null: + { + result.m_type = value_t::null; + break; + } + + case token_type::value_string: + { + result.m_type = value_t::string; + result.m_value = m_lexer.move_string(); + break; + } + + case token_type::literal_true: + { + result.m_type = value_t::boolean; + result.m_value = true; + break; + } + + case token_type::literal_false: + { + result.m_type = value_t::boolean; + result.m_value = false; + break; + } + + case token_type::value_unsigned: + { + result.m_type = value_t::number_unsigned; + result.m_value = m_lexer.get_number_unsigned(); + break; + } + + case token_type::value_integer: + { + result.m_type = value_t::number_integer; + result.m_value = m_lexer.get_number_integer(); + break; + } + + case token_type::value_float: + { + result.m_type = value_t::number_float; + result.m_value = m_lexer.get_number_float(); + + // throw in case of infinity or NAN + if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) + { + if (allow_exceptions) + { + JSON_THROW(out_of_range::create(406, "number overflow parsing '" + + m_lexer.get_token_string() + "'")); + } + expect(token_type::uninitialized); + } + break; + } + + case token_type::parse_error: + { + // using "uninitialized" to avoid "expected" message + if (not expect(token_type::uninitialized)) + { + return; + } + break; // LCOV_EXCL_LINE + } + + default: + { + // the last token was unexpected; we expected a value + if (not expect(token_type::literal_or_value)) + { + return; + } + break; // LCOV_EXCL_LINE + } + } + + if (keep and callback and not callback(depth, parse_event_t::value, result)) + { + result.m_type = value_t::discarded; + } + } + + /*! + @brief the acutal acceptor + + @invariant 1. The last token is not yet processed. Therefore, the caller + of this function must make sure a token has been read. + 2. When this function returns, the last token is processed. + That is, the last read character was already considered. + + This invariant makes sure that no token needs to be "unput". + */ + bool accept_internal() + { + switch (last_token) + { + case token_type::begin_object: + { + // read next token + get_token(); + + // closing } -> we are done + if (last_token == token_type::end_object) + { + return true; + } + + // parse values + while (true) + { + // parse key + if (last_token != token_type::value_string) + { + return false; + } + + // parse separator (:) + get_token(); + if (last_token != token_type::name_separator) + { + return false; + } + + // parse value + get_token(); + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing } + return (last_token == token_type::end_object); + } + } + + case token_type::begin_array: + { + // read next token + get_token(); + + // closing ] -> we are done + if (last_token == token_type::end_array) + { + return true; + } + + // parse values + while (true) + { + // parse value + if (not accept_internal()) + { + return false; + } + + // comma -> next value + get_token(); + if (last_token == token_type::value_separator) + { + get_token(); + continue; + } + + // closing ] + return (last_token == token_type::end_array); + } + } + + case token_type::value_float: + { + // reject infinity or NAN + return std::isfinite(m_lexer.get_number_float()); + } + + case token_type::literal_false: + case token_type::literal_null: + case token_type::literal_true: + case token_type::value_integer: + case token_type::value_string: + case token_type::value_unsigned: + return true; + + default: // the last token was unexpected + return false; + } + } + + /// get next token from lexer + token_type get_token() + { + return (last_token = m_lexer.scan()); + } + + /*! + @throw parse_error.101 if expected token did not occur + */ + bool expect(token_type t) + { + if (JSON_UNLIKELY(t != last_token)) + { + errored = true; + expected = t; + if (allow_exceptions) + { + throw_exception(); + } + else + { + return false; + } + } + + return true; + } + + [[noreturn]] void throw_exception() const + { + std::string error_msg = "syntax error - "; + if (last_token == token_type::parse_error) + { + error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + + m_lexer.get_token_string() + "'"; + } + else + { + error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); + } + + if (expected != token_type::uninitialized) + { + error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); + } + + JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); + } + + private: + /// current level of recursion + int depth = 0; + /// callback function + const parser_callback_t callback = nullptr; + /// the type of the last read token + token_type last_token = token_type::uninitialized; + /// the lexer + lexer_t m_lexer; + /// whether a syntax error occurred + bool errored = false; + /// possible reason for the syntax error + token_type expected = token_type::uninitialized; + /// whether to throw exceptions in case of errors + const bool allow_exceptions = true; +}; + +/////////////// +// iterators // +/////////////// + +/*! +@brief an iterator for primitive JSON types + +This class models an iterator for primitive JSON types (boolean, number, +string). It's only purpose is to allow the iterator/const_iterator classes +to "iterate" over primitive values. Internally, the iterator is modeled by +a `difference_type` variable. Value begin_value (`0`) models the begin, +end_value (`1`) models past the end. +*/ +class primitive_iterator_t +{ + public: + using difference_type = std::ptrdiff_t; + + constexpr difference_type get_value() const noexcept + { + return m_it; + } + + /// set iterator to a defined beginning + void set_begin() noexcept + { + m_it = begin_value; + } + + /// set iterator to a defined past the end + void set_end() noexcept + { + m_it = end_value; + } + + /// return whether the iterator can be dereferenced + constexpr bool is_begin() const noexcept + { + return m_it == begin_value; + } + + /// return whether the iterator is at end + constexpr bool is_end() const noexcept + { + return m_it == end_value; + } + + friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it == rhs.m_it; + } + + friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it < rhs.m_it; + } + + primitive_iterator_t operator+(difference_type i) + { + auto result = *this; + result += i; + return result; + } + + friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept + { + return lhs.m_it - rhs.m_it; + } + + friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) + { + return os << it.m_it; + } + + primitive_iterator_t& operator++() + { + ++m_it; + return *this; + } + + primitive_iterator_t operator++(int) + { + auto result = *this; + m_it++; + return result; + } + + primitive_iterator_t& operator--() + { + --m_it; + return *this; + } + + primitive_iterator_t operator--(int) + { + auto result = *this; + m_it--; + return result; + } + + primitive_iterator_t& operator+=(difference_type n) + { + m_it += n; + return *this; + } + + primitive_iterator_t& operator-=(difference_type n) + { + m_it -= n; + return *this; + } + + private: + static constexpr difference_type begin_value = 0; + static constexpr difference_type end_value = begin_value + 1; + + /// iterator as signed integer type + difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)(); +}; + +/*! +@brief an iterator value + +@note This structure could easily be a union, but MSVC currently does not allow +unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. +*/ +template<typename BasicJsonType> struct internal_iterator +{ + /// iterator for JSON objects + typename BasicJsonType::object_t::iterator object_iterator {}; + /// iterator for JSON arrays + typename BasicJsonType::array_t::iterator array_iterator {}; + /// generic iterator for all other types + primitive_iterator_t primitive_iterator {}; +}; + +template<typename IteratorType> class iteration_proxy; + +/*! +@brief a template for a bidirectional iterator for the @ref basic_json class + +This class implements a both iterators (iterator and const_iterator) for the +@ref basic_json class. + +@note An iterator is called *initialized* when a pointer to a JSON value has + been set (e.g., by a constructor or a copy assignment). If the iterator is + default-constructed, it is *uninitialized* and most methods are undefined. + **The library uses assertions to detect calls on uninitialized iterators.** + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). + +@since version 1.0.0, simplified in version 2.0.9, change to bidirectional + iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) +*/ +template<typename BasicJsonType> +class iter_impl : public std::iterator<std::bidirectional_iterator_tag, BasicJsonType> +{ + /// allow basic_json to access private members + friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>; + friend BasicJsonType; + friend iteration_proxy<iter_impl>; + + using object_t = typename BasicJsonType::object_t; + using array_t = typename BasicJsonType::array_t; + // make sure BasicJsonType is basic_json or const basic_json + static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, + "iter_impl only accepts (const) basic_json"); + + public: + /// the type of the values when the iterator is dereferenced + using value_type = typename BasicJsonType::value_type; + /// a type to represent differences between iterators + using difference_type = typename BasicJsonType::difference_type; + /// defines a pointer to the type iterated over (value_type) + using pointer = typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_pointer, + typename BasicJsonType::pointer>::type; + /// defines a reference to the type iterated over (value_type) + using reference = + typename std::conditional<std::is_const<BasicJsonType>::value, + typename BasicJsonType::const_reference, + typename BasicJsonType::reference>::type; + + /// default constructor + iter_impl() = default; + + /*! + @brief constructor for a given JSON instance + @param[in] object pointer to a JSON object for this iterator + @pre object != nullptr + @post The iterator is initialized; i.e. `m_object != nullptr`. + */ + explicit iter_impl(pointer object) noexcept : m_object(object) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = typename object_t::iterator(); + break; + } + + case value_t::array: + { + m_it.array_iterator = typename array_t::iterator(); + break; + } + + default: + { + m_it.primitive_iterator = primitive_iterator_t(); + break; + } + } + } + + /*! + @note The conventional copy constructor and copy assignment are implicitly + defined. Combined with the following converting constructor and + assignment, they support: (1) copy from iterator to iterator, (2) + copy from const iterator to const iterator, and (3) conversion from + iterator to const iterator. However conversion from const iterator + to iterator is not defined. + */ + + /*! + @brief converting constructor + @param[in] other non-const iterator to copy from + @note It is not checked whether @a other is initialized. + */ + iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept + : m_object(other.m_object), m_it(other.m_it) {} + + /*! + @brief converting assignment + @param[in,out] other non-const iterator to copy from + @return const/non-const iterator + @note It is not checked whether @a other is initialized. + */ + iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept + { + m_object = other.m_object; + m_it = other.m_it; + return *this; + } + + private: + /*! + @brief set the iterator to the first value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_begin() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->begin(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->begin(); + break; + } + + case value_t::null: + { + // set to end so begin()==end() is true: null is empty + m_it.primitive_iterator.set_end(); + break; + } + + default: + { + m_it.primitive_iterator.set_begin(); + break; + } + } + } + + /*! + @brief set the iterator past the last value + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + void set_end() noexcept + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + m_it.object_iterator = m_object->m_value.object->end(); + break; + } + + case value_t::array: + { + m_it.array_iterator = m_object->m_value.array->end(); + break; + } + + default: + { + m_it.primitive_iterator.set_end(); + break; + } + } + } + + public: + /*! + @brief return a reference to the value pointed to by the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator*() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return m_it.object_iterator->second; + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return *m_it.array_iterator; + } + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief dereference the iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + pointer operator->() const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + assert(m_it.object_iterator != m_object->m_value.object->end()); + return &(m_it.object_iterator->second); + } + + case value_t::array: + { + assert(m_it.array_iterator != m_object->m_value.array->end()); + return &*m_it.array_iterator; + } + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) + { + return m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief post-increment (it++) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator++(int) + { + auto result = *this; + ++(*this); + return result; + } + + /*! + @brief pre-increment (++it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator++() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, 1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, 1); + break; + } + + default: + { + ++m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief post-decrement (it--) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator--(int) + { + auto result = *this; + --(*this); + return result; + } + + /*! + @brief pre-decrement (--it) + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator--() + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + { + std::advance(m_it.object_iterator, -1); + break; + } + + case value_t::array: + { + std::advance(m_it.array_iterator, -1); + break; + } + + default: + { + --m_it.primitive_iterator; + break; + } + } + + return *this; + } + + /*! + @brief comparison: equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator==(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + return (m_it.object_iterator == other.m_it.object_iterator); + + case value_t::array: + return (m_it.array_iterator == other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator == other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: not equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator!=(const iter_impl& other) const + { + return not operator==(other); + } + + /*! + @brief comparison: smaller + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<(const iter_impl& other) const + { + // if objects are not the same, the comparison is undefined + if (JSON_UNLIKELY(m_object != other.m_object)) + { + JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); + } + + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); + + case value_t::array: + return (m_it.array_iterator < other.m_it.array_iterator); + + default: + return (m_it.primitive_iterator < other.m_it.primitive_iterator); + } + } + + /*! + @brief comparison: less than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator<=(const iter_impl& other) const + { + return not other.operator < (*this); + } + + /*! + @brief comparison: greater than + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>(const iter_impl& other) const + { + return not operator<=(other); + } + + /*! + @brief comparison: greater than or equal + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + bool operator>=(const iter_impl& other) const + { + return not operator<(other); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator+=(difference_type i) + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + { + std::advance(m_it.array_iterator, i); + break; + } + + default: + { + m_it.primitive_iterator += i; + break; + } + } + + return *this; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl& operator-=(difference_type i) + { + return operator+=(-i); + } + + /*! + @brief add to iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator+(difference_type i) const + { + auto result = *this; + result += i; + return result; + } + + /*! + @brief addition of distance and iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + friend iter_impl operator+(difference_type i, const iter_impl& it) + { + auto result = it; + result += i; + return result; + } + + /*! + @brief subtract from iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + iter_impl operator-(difference_type i) const + { + auto result = *this; + result -= i; + return result; + } + + /*! + @brief return difference + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + difference_type operator-(const iter_impl& other) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); + + case value_t::array: + return m_it.array_iterator - other.m_it.array_iterator; + + default: + return m_it.primitive_iterator - other.m_it.primitive_iterator; + } + } + + /*! + @brief access to successor + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference operator[](difference_type n) const + { + assert(m_object != nullptr); + + switch (m_object->m_type) + { + case value_t::object: + JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); + + case value_t::array: + return *std::next(m_it.array_iterator, n); + + case value_t::null: + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + + default: + { + if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) + { + return *m_object; + } + + JSON_THROW(invalid_iterator::create(214, "cannot get value")); + } + } + } + + /*! + @brief return the key of an object iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + typename object_t::key_type key() const + { + assert(m_object != nullptr); + + if (JSON_LIKELY(m_object->is_object())) + { + return m_it.object_iterator->first; + } + + JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); + } + + /*! + @brief return the value of an iterator + @pre The iterator is initialized; i.e. `m_object != nullptr`. + */ + reference value() const + { + return operator*(); + } + + private: + /// associated JSON instance + pointer m_object = nullptr; + /// the actual iterator of the associated instance + internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it = {}; +}; + +/// proxy class for the iterator_wrapper functions +template<typename IteratorType> class iteration_proxy +{ + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + std::size_t array_index = 0; + + public: + explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!=(const iteration_proxy_internal& o) const noexcept + { + return anchor != o.anchor; + } + + /// return key of the iterator + std::string key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + return std::to_string(array_index); + + // use key from the object + case value_t::object: + return anchor.key(); + + // use an empty key for all primitive types + default: + return ""; + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + explicit iteration_proxy(typename IteratorType::reference cont) + : container(cont) {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() noexcept + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() noexcept + { + return iteration_proxy_internal(container.end()); + } +}; + +/*! +@brief a template for a reverse iterator class + +@tparam Base the base iterator type to reverse. Valid types are @ref +iterator (to create @ref reverse_iterator) and @ref const_iterator (to +create @ref const_reverse_iterator). + +@requirement The class satisfies the following concept requirements: +- +[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): + The iterator that can be moved can be moved in both directions (i.e. + incremented and decremented). +- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): + It is possible to write to the pointed-to element (only if @a Base is + @ref iterator). + +@since version 1.0.0 +*/ +template<typename Base> +class json_reverse_iterator : public std::reverse_iterator<Base> +{ + public: + using difference_type = std::ptrdiff_t; + /// shortcut to the reverse iterator adaptor + using base_iterator = std::reverse_iterator<Base>; + /// the reference type for the pointed-to element + using reference = typename Base::reference; + + /// create reverse iterator from iterator + json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept + : base_iterator(it) {} + + /// create reverse iterator from base class + json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} + + /// post-increment (it++) + json_reverse_iterator operator++(int) + { + return static_cast<json_reverse_iterator>(base_iterator::operator++(1)); + } + + /// pre-increment (++it) + json_reverse_iterator& operator++() + { + return static_cast<json_reverse_iterator&>(base_iterator::operator++()); + } + + /// post-decrement (it--) + json_reverse_iterator operator--(int) + { + return static_cast<json_reverse_iterator>(base_iterator::operator--(1)); + } + + /// pre-decrement (--it) + json_reverse_iterator& operator--() + { + return static_cast<json_reverse_iterator&>(base_iterator::operator--()); + } + + /// add to iterator + json_reverse_iterator& operator+=(difference_type i) + { + return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i)); + } + + /// add to iterator + json_reverse_iterator operator+(difference_type i) const + { + return static_cast<json_reverse_iterator>(base_iterator::operator+(i)); + } + + /// subtract from iterator + json_reverse_iterator operator-(difference_type i) const + { + return static_cast<json_reverse_iterator>(base_iterator::operator-(i)); + } + + /// return difference + difference_type operator-(const json_reverse_iterator& other) const + { + return base_iterator(*this) - base_iterator(other); + } + + /// access to successor + reference operator[](difference_type n) const + { + return *(this->operator+(n)); + } + + /// return the key of an object iterator + auto key() const -> decltype(std::declval<Base>().key()) + { + auto it = --this->base(); + return it.key(); + } + + /// return the value of an iterator + reference value() const + { + auto it = --this->base(); + return it.operator * (); + } +}; + +///////////////////// +// output adapters // +///////////////////// + +/// abstract output adapter interface +template<typename CharType> struct output_adapter_protocol +{ + virtual void write_character(CharType c) = 0; + virtual void write_characters(const CharType* s, std::size_t length) = 0; + virtual ~output_adapter_protocol() = default; +}; + +/// a type to simplify interfaces +template<typename CharType> +using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; + +/// output adapter for byte vectors +template<typename CharType> +class output_vector_adapter : public output_adapter_protocol<CharType> +{ + public: + explicit output_vector_adapter(std::vector<CharType>& vec) : v(vec) {} + + void write_character(CharType c) override + { + v.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + std::copy(s, s + length, std::back_inserter(v)); + } + + private: + std::vector<CharType>& v; +}; + +/// output adapter for output streams +template<typename CharType> +class output_stream_adapter : public output_adapter_protocol<CharType> +{ + public: + explicit output_stream_adapter(std::basic_ostream<CharType>& s) : stream(s) {} + + void write_character(CharType c) override + { + stream.put(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + stream.write(s, static_cast<std::streamsize>(length)); + } + + private: + std::basic_ostream<CharType>& stream; +}; + +/// output adapter for basic_string +template<typename CharType> +class output_string_adapter : public output_adapter_protocol<CharType> +{ + public: + explicit output_string_adapter(std::basic_string<CharType>& s) : str(s) {} + + void write_character(CharType c) override + { + str.push_back(c); + } + + void write_characters(const CharType* s, std::size_t length) override + { + str.append(s, length); + } + + private: + std::basic_string<CharType>& str; +}; + +template<typename CharType> +class output_adapter +{ + public: + output_adapter(std::vector<CharType>& vec) + : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {} + + output_adapter(std::basic_ostream<CharType>& s) + : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {} + + output_adapter(std::basic_string<CharType>& s) + : oa(std::make_shared<output_string_adapter<CharType>>(s)) {} + + operator output_adapter_t<CharType>() + { + return oa; + } + + private: + output_adapter_t<CharType> oa = nullptr; +}; + +////////////////////////////// +// binary reader and writer // +////////////////////////////// + +/*! +@brief deserialization of CBOR and MessagePack values +*/ +template<typename BasicJsonType> +class binary_reader +{ + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + + public: + /*! + @brief create a binary reader + + @param[in] adapter input adapter to read from + */ + explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) + { + assert(ia); + } + + /*! + @brief create a JSON value from CBOR input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from CBOR input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_cbor(const bool strict) + { + const auto res = parse_cbor_internal(); + if (strict) + { + get(); + check_eof(true); + } + return res; + } + + /*! + @brief create a JSON value from MessagePack input + + @param[in] strict whether to expect the input to be consumed completed + @return JSON value created from MessagePack input + + @throw parse_error.110 if input ended unexpectedly or the end of file was + not reached when @a strict was set to true + @throw parse_error.112 if unsupported byte was read + */ + BasicJsonType parse_msgpack(const bool strict) + { + const auto res = parse_msgpack_internal(); + if (strict) + { + get(); + check_eof(true); + } + return res; + } + + /*! + @brief determine system byte order + + @return true if and only if system's byte order is little endian + + @note from http://stackoverflow.com/a/1001328/266378 + */ + static constexpr bool little_endianess(int num = 1) noexcept + { + return (*reinterpret_cast<char*>(&num) == 1); + } + + private: + /*! + @param[in] get_char whether a new character should be retrieved from the + input (true, default) or whether the last read + character should be considered instead + */ + BasicJsonType parse_cbor_internal(const bool get_char = true) + { + switch (get_char ? get() : current) + { + // EOF + case std::char_traits<char>::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // Integer 0x00..0x17 (0..23) + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + return static_cast<number_unsigned_t>(current); + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + return get_number<uint8_t>(); + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + return get_number<uint16_t>(); + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + return get_number<uint32_t>(); + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + return get_number<uint64_t>(); + + // Negative integer -1-0x00..-1-0x17 (-1..-24) + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + return static_cast<int8_t>(0x20 - 1 - current); + + case 0x38: // Negative integer (one-byte uint8_t follows) + { + // must be uint8_t ! + return static_cast<number_integer_t>(-1) - get_number<uint8_t>(); + } + + case 0x39: // Negative integer -1-n (two-byte uint16_t follows) + { + return static_cast<number_integer_t>(-1) - get_number<uint16_t>(); + } + + case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) + { + return static_cast<number_integer_t>(-1) - get_number<uint32_t>(); + } + + case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) + { + return static_cast<number_integer_t>(-1) - + static_cast<number_integer_t>(get_number<uint64_t>()); + } + + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + case 0x7f: // UTF-8 string (indefinite length) + { + return get_cbor_string(); + } + + // array (0x00..0x17 data items follow) + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + { + return get_cbor_array(current & 0x1f); + } + + case 0x98: // array (one-byte uint8_t for n follows) + { + return get_cbor_array(get_number<uint8_t>()); + } + + case 0x99: // array (two-byte uint16_t for n follow) + { + return get_cbor_array(get_number<uint16_t>()); + } + + case 0x9a: // array (four-byte uint32_t for n follow) + { + return get_cbor_array(get_number<uint32_t>()); + } + + case 0x9b: // array (eight-byte uint64_t for n follow) + { + return get_cbor_array(get_number<uint64_t>()); + } + + case 0x9f: // array (indefinite length) + { + BasicJsonType result = value_t::array; + while (get() != 0xff) + { + result.push_back(parse_cbor_internal(false)); + } + return result; + } + + // map (0x00..0x17 pairs of data items follow) + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + { + return get_cbor_object(current & 0x1f); + } + + case 0xb8: // map (one-byte uint8_t for n follows) + { + return get_cbor_object(get_number<uint8_t>()); + } + + case 0xb9: // map (two-byte uint16_t for n follow) + { + return get_cbor_object(get_number<uint16_t>()); + } + + case 0xba: // map (four-byte uint32_t for n follow) + { + return get_cbor_object(get_number<uint32_t>()); + } + + case 0xbb: // map (eight-byte uint64_t for n follow) + { + return get_cbor_object(get_number<uint64_t>()); + } + + case 0xbf: // map (indefinite length) + { + BasicJsonType result = value_t::object; + while (get() != 0xff) + { + auto key = get_cbor_string(); + result[key] = parse_cbor_internal(); + } + return result; + } + + case 0xf4: // false + { + return false; + } + + case 0xf5: // true + { + return true; + } + + case 0xf6: // null + { + return value_t::null; + } + + case 0xf9: // Half-Precision Float (two-byte IEEE 754) + { + const int byte1 = get(); + check_eof(); + const int byte2 = get(); + check_eof(); + + // code from RFC 7049, Appendix D, Figure 3: + // As half-precision floating-point numbers were only added + // to IEEE 754 in 2008, today's programming platforms often + // still only have limited support for them. It is very + // easy to include at least decoding support for them even + // without such support. An example of a small decoder for + // half-precision floating-point numbers in the C language + // is shown in Fig. 3. + const int half = (byte1 << 8) + byte2; + const int exp = (half >> 10) & 0x1f; + const int mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = std::ldexp(mant, -24); + } + else if (exp != 31) + { + val = std::ldexp(mant + 1024, exp - 25); + } + else + { + val = (mant == 0) ? std::numeric_limits<double>::infinity() + : std::numeric_limits<double>::quiet_NaN(); + } + return (half & 0x8000) != 0 ? -val : val; + } + + case 0xfa: // Single-Precision Float (four-byte IEEE 754) + { + return get_number<float>(); + } + + case 0xfb: // Double-Precision Float (eight-byte IEEE 754) + { + return get_number<double>(); + } + + default: // anything else (0xFF is handled inside the other types) + { + std::stringstream ss; + ss << std::setw(2) << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); + } + } + } + + BasicJsonType parse_msgpack_internal() + { + switch (get()) + { + // EOF + case std::char_traits<char>::eof(): + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + + // positive fixint + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0a: + case 0x0b: + case 0x0c: + case 0x0d: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: + case 0x20: + case 0x21: + case 0x22: + case 0x23: + case 0x24: + case 0x25: + case 0x26: + case 0x27: + case 0x28: + case 0x29: + case 0x2a: + case 0x2b: + case 0x2c: + case 0x2d: + case 0x2e: + case 0x2f: + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + case 0x49: + case 0x4a: + case 0x4b: + case 0x4c: + case 0x4d: + case 0x4e: + case 0x4f: + case 0x50: + case 0x51: + case 0x52: + case 0x53: + case 0x54: + case 0x55: + case 0x56: + case 0x57: + case 0x58: + case 0x59: + case 0x5a: + case 0x5b: + case 0x5c: + case 0x5d: + case 0x5e: + case 0x5f: + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + case 0x78: + case 0x79: + case 0x7a: + case 0x7b: + case 0x7c: + case 0x7d: + case 0x7e: + case 0x7f: + return static_cast<number_unsigned_t>(current); + + // fixmap + case 0x80: + case 0x81: + case 0x82: + case 0x83: + case 0x84: + case 0x85: + case 0x86: + case 0x87: + case 0x88: + case 0x89: + case 0x8a: + case 0x8b: + case 0x8c: + case 0x8d: + case 0x8e: + case 0x8f: + { + return get_msgpack_object(current & 0x0f); + } + + // fixarray + case 0x90: + case 0x91: + case 0x92: + case 0x93: + case 0x94: + case 0x95: + case 0x96: + case 0x97: + case 0x98: + case 0x99: + case 0x9a: + case 0x9b: + case 0x9c: + case 0x9d: + case 0x9e: + case 0x9f: + { + return get_msgpack_array(current & 0x0f); + } + + // fixstr + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + case 0xb8: + case 0xb9: + case 0xba: + case 0xbb: + case 0xbc: + case 0xbd: + case 0xbe: + case 0xbf: + return get_msgpack_string(); + + case 0xc0: // nil + return value_t::null; + + case 0xc2: // false + return false; + + case 0xc3: // true + return true; + + case 0xca: // float 32 + return get_number<float>(); + + case 0xcb: // float 64 + return get_number<double>(); + + case 0xcc: // uint 8 + return get_number<uint8_t>(); + + case 0xcd: // uint 16 + return get_number<uint16_t>(); + + case 0xce: // uint 32 + return get_number<uint32_t>(); + + case 0xcf: // uint 64 + return get_number<uint64_t>(); + + case 0xd0: // int 8 + return get_number<int8_t>(); + + case 0xd1: // int 16 + return get_number<int16_t>(); + + case 0xd2: // int 32 + return get_number<int32_t>(); + + case 0xd3: // int 64 + return get_number<int64_t>(); + + case 0xd9: // str 8 + case 0xda: // str 16 + case 0xdb: // str 32 + return get_msgpack_string(); + + case 0xdc: // array 16 + { + return get_msgpack_array(get_number<uint16_t>()); + } + + case 0xdd: // array 32 + { + return get_msgpack_array(get_number<uint32_t>()); + } + + case 0xde: // map 16 + { + return get_msgpack_object(get_number<uint16_t>()); + } + + case 0xdf: // map 32 + { + return get_msgpack_object(get_number<uint32_t>()); + } + + // positive fixint + case 0xe0: + case 0xe1: + case 0xe2: + case 0xe3: + case 0xe4: + case 0xe5: + case 0xe6: + case 0xe7: + case 0xe8: + case 0xe9: + case 0xea: + case 0xeb: + case 0xec: + case 0xed: + case 0xee: + case 0xef: + case 0xf0: + case 0xf1: + case 0xf2: + case 0xf3: + case 0xf4: + case 0xf5: + case 0xf6: + case 0xf7: + case 0xf8: + case 0xf9: + case 0xfa: + case 0xfb: + case 0xfc: + case 0xfd: + case 0xfe: + case 0xff: + return static_cast<int8_t>(current); + + default: // anything else + { + std::stringstream ss; + ss << std::setw(2) << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(112, chars_read, + "error reading MessagePack; last byte: 0x" + ss.str())); + } + } + } + + /*! + @brief get next character from the input + + This function provides the interface to the used input adapter. It does + not throw in case the input reached EOF, but returns a -'ve valued + `std::char_traits<char>::eof()` in that case. + + @return character read from the input + */ + int get() + { + ++chars_read; + return (current = ia->get_character()); + } + + /* + @brief read a number from the input + + @tparam NumberType the type of the number + + @return number of type @a NumberType + + @note This function needs to respect the system's endianess, because + bytes in CBOR and MessagePack are stored in network order (big + endian) and therefore need reordering on little endian systems. + + @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes + */ + template<typename NumberType> NumberType get_number() + { + // step 1: read input into array with system's byte order + std::array<uint8_t, sizeof(NumberType)> vec; + for (std::size_t i = 0; i < sizeof(NumberType); ++i) + { + get(); + check_eof(); + + // reverse byte order prior to conversion if necessary + if (is_little_endian) + { + vec[sizeof(NumberType) - i - 1] = static_cast<uint8_t>(current); + } + else + { + vec[i] = static_cast<uint8_t>(current); // LCOV_EXCL_LINE + } + } + + // step 2: convert array into number of type T and return + NumberType result; + std::memcpy(&result, vec.data(), sizeof(NumberType)); + return result; + } + + /*! + @brief create a string by reading characters from the input + + @param[in] len number of bytes to read + + @note We can not reserve @a len bytes for the result, because @a len + may be too large. Usually, @ref check_eof() detects the end of + the input before we run out of string memory. + + @return string created by reading @a len bytes + + @throw parse_error.110 if input has less than @a len bytes + */ + template<typename NumberType> + std::string get_string(const NumberType len) + { + std::string result; + std::generate_n(std::back_inserter(result), len, [this]() + { + get(); + check_eof(); + return static_cast<char>(current); + }); + return result; + } + + /*! + @brief reads a CBOR string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + Additionally, CBOR's strings with indefinite lengths are supported. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + std::string get_cbor_string() + { + check_eof(); + + switch (current) + { + // UTF-8 string (0x00..0x17 bytes follow) + case 0x60: + case 0x61: + case 0x62: + case 0x63: + case 0x64: + case 0x65: + case 0x66: + case 0x67: + case 0x68: + case 0x69: + case 0x6a: + case 0x6b: + case 0x6c: + case 0x6d: + case 0x6e: + case 0x6f: + case 0x70: + case 0x71: + case 0x72: + case 0x73: + case 0x74: + case 0x75: + case 0x76: + case 0x77: + { + return get_string(current & 0x1f); + } + + case 0x78: // UTF-8 string (one-byte uint8_t for n follows) + { + return get_string(get_number<uint8_t>()); + } + + case 0x79: // UTF-8 string (two-byte uint16_t for n follow) + { + return get_string(get_number<uint16_t>()); + } + + case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) + { + return get_string(get_number<uint32_t>()); + } + + case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) + { + return get_string(get_number<uint64_t>()); + } + + case 0x7f: // UTF-8 string (indefinite length) + { + std::string result; + while (get() != 0xff) + { + check_eof(); + result.push_back(static_cast<char>(current)); + } + return result; + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); + } + } + } + + template<typename NumberType> + BasicJsonType get_cbor_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_cbor_internal(); + }); + return result; + } + + template<typename NumberType> + BasicJsonType get_cbor_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_cbor_string(); + auto val = parse_cbor_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief reads a MessagePack string + + This function first reads starting bytes to determine the expected + string length and then copies this number of bytes into a string. + + @return string + + @throw parse_error.110 if input ended + @throw parse_error.113 if an unexpected byte is read + */ + std::string get_msgpack_string() + { + check_eof(); + + switch (current) + { + // fixstr + case 0xa0: + case 0xa1: + case 0xa2: + case 0xa3: + case 0xa4: + case 0xa5: + case 0xa6: + case 0xa7: + case 0xa8: + case 0xa9: + case 0xaa: + case 0xab: + case 0xac: + case 0xad: + case 0xae: + case 0xaf: + case 0xb0: + case 0xb1: + case 0xb2: + case 0xb3: + case 0xb4: + case 0xb5: + case 0xb6: + case 0xb7: + case 0xb8: + case 0xb9: + case 0xba: + case 0xbb: + case 0xbc: + case 0xbd: + case 0xbe: + case 0xbf: + { + return get_string(current & 0x1f); + } + + case 0xd9: // str 8 + { + return get_string(get_number<uint8_t>()); + } + + case 0xda: // str 16 + { + return get_string(get_number<uint16_t>()); + } + + case 0xdb: // str 32 + { + return get_string(get_number<uint32_t>()); + } + + default: + { + std::stringstream ss; + ss << std::setw(2) << std::setfill('0') << std::hex << current; + JSON_THROW(parse_error::create(113, chars_read, + "expected a MessagePack string; last byte: 0x" + ss.str())); + } + } + } + + template<typename NumberType> + BasicJsonType get_msgpack_array(const NumberType len) + { + BasicJsonType result = value_t::array; + std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() + { + return parse_msgpack_internal(); + }); + return result; + } + + template<typename NumberType> + BasicJsonType get_msgpack_object(const NumberType len) + { + BasicJsonType result = value_t::object; + std::generate_n(std::inserter(*result.m_value.object, + result.m_value.object->end()), + len, [this]() + { + get(); + auto key = get_msgpack_string(); + auto val = parse_msgpack_internal(); + return std::make_pair(std::move(key), std::move(val)); + }); + return result; + } + + /*! + @brief check if input ended + @throw parse_error.110 if input ended + */ + void check_eof(const bool expect_eof = false) const + { + if (expect_eof) + { + if (JSON_UNLIKELY(current != std::char_traits<char>::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); + } + } + else + { + if (JSON_UNLIKELY(current == std::char_traits<char>::eof())) + { + JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); + } + } + } + + private: + /// input adapter + input_adapter_t ia = nullptr; + + /// the current character + int current = std::char_traits<char>::eof(); + + /// the number of characters read + std::size_t chars_read = 0; + + /// whether we can assume little endianess + const bool is_little_endian = little_endianess(); +}; + +/*! +@brief serialization to CBOR and MessagePack values +*/ +template<typename BasicJsonType, typename CharType> +class binary_writer +{ + public: + /*! + @brief create a binary writer + + @param[in] adapter output adapter to write to + */ + explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter) + { + assert(oa); + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_cbor(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: + { + oa->write_character(static_cast<CharType>(0xf6)); + break; + } + + case value_t::boolean: + { + oa->write_character(j.m_value.boolean + ? static_cast<CharType>(0xf5) + : static_cast<CharType>(0xf4)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // CBOR does not differentiate between positive signed + // integers and unsigned integers. Therefore, we used the + // code from the value_t::number_unsigned case here. + if (j.m_value.number_integer <= 0x17) + { + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x18)); + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x19)); + write_number(static_cast<uint16_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x1a)); + write_number(static_cast<uint32_t>(j.m_value.number_integer)); + } + else + { + oa->write_character(static_cast<CharType>(0x1b)); + write_number(static_cast<uint64_t>(j.m_value.number_integer)); + } + } + else + { + // The conversions below encode the sign in the first + // byte, and the value is converted to a positive number. + const auto positive_number = -1 - j.m_value.number_integer; + if (j.m_value.number_integer >= -24) + { + write_number(static_cast<uint8_t>(0x20 + positive_number)); + } + else if (positive_number <= (std::numeric_limits<uint8_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x38)); + write_number(static_cast<uint8_t>(positive_number)); + } + else if (positive_number <= (std::numeric_limits<uint16_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x39)); + write_number(static_cast<uint16_t>(positive_number)); + } + else if (positive_number <= (std::numeric_limits<uint32_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x3a)); + write_number(static_cast<uint32_t>(positive_number)); + } + else + { + oa->write_character(static_cast<CharType>(0x3b)); + write_number(static_cast<uint64_t>(positive_number)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned <= 0x17) + { + write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x18)); + write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x19)); + write_number(static_cast<uint16_t>(j.m_value.number_unsigned)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)()) + { + oa->write_character(static_cast<CharType>(0x1a)); + write_number(static_cast<uint32_t>(j.m_value.number_unsigned)); + } + else + { + oa->write_character(static_cast<CharType>(0x1b)); + write_number(static_cast<uint64_t>(j.m_value.number_unsigned)); + } + break; + } + + case value_t::number_float: // Double-Precision Float + { + oa->write_character(static_cast<CharType>(0xfb)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 0x17) + { + write_number(static_cast<uint8_t>(0x60 + N)); + } + else if (N <= 0xff) + { + oa->write_character(static_cast<CharType>(0x78)); + write_number(static_cast<uint8_t>(N)); + } + else if (N <= 0xffff) + { + oa->write_character(static_cast<CharType>(0x79)); + write_number(static_cast<uint16_t>(N)); + } + else if (N <= 0xffffffff) + { + oa->write_character(static_cast<CharType>(0x7a)); + write_number(static_cast<uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + oa->write_character(static_cast<CharType>(0x7b)); + write_number(static_cast<uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write the string + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 0x17) + { + write_number(static_cast<uint8_t>(0x80 + N)); + } + else if (N <= 0xff) + { + oa->write_character(static_cast<CharType>(0x98)); + write_number(static_cast<uint8_t>(N)); + } + else if (N <= 0xffff) + { + oa->write_character(static_cast<CharType>(0x99)); + write_number(static_cast<uint16_t>(N)); + } + else if (N <= 0xffffffff) + { + oa->write_character(static_cast<CharType>(0x9a)); + write_number(static_cast<uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + oa->write_character(static_cast<CharType>(0x9b)); + write_number(static_cast<uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_cbor(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 0x17) + { + write_number(static_cast<uint8_t>(0xa0 + N)); + } + else if (N <= 0xff) + { + oa->write_character(static_cast<CharType>(0xb8)); + write_number(static_cast<uint8_t>(N)); + } + else if (N <= 0xffff) + { + oa->write_character(static_cast<CharType>(0xb9)); + write_number(static_cast<uint16_t>(N)); + } + else if (N <= 0xffffffff) + { + oa->write_character(static_cast<CharType>(0xba)); + write_number(static_cast<uint32_t>(N)); + } + // LCOV_EXCL_START + else if (N <= 0xffffffffffffffff) + { + oa->write_character(static_cast<CharType>(0xbb)); + write_number(static_cast<uint64_t>(N)); + } + // LCOV_EXCL_STOP + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_cbor(el.first); + write_cbor(el.second); + } + break; + } + + default: + break; + } + } + + /*! + @brief[in] j JSON value to serialize + */ + void write_msgpack(const BasicJsonType& j) + { + switch (j.type()) + { + case value_t::null: // nil + { + oa->write_character(static_cast<CharType>(0xc0)); + break; + } + + case value_t::boolean: // true and false + { + oa->write_character(j.m_value.boolean + ? static_cast<CharType>(0xc3) + : static_cast<CharType>(0xc2)); + break; + } + + case value_t::number_integer: + { + if (j.m_value.number_integer >= 0) + { + // MessagePack does not differentiate between positive + // signed integers and unsigned integers. Therefore, we used + // the code from the value_t::number_unsigned case here. + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)()) + { + // uint 8 + oa->write_character(static_cast<CharType>(0xcc)); + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)()) + { + // uint 16 + oa->write_character(static_cast<CharType>(0xcd)); + write_number(static_cast<uint16_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)()) + { + // uint 32 + oa->write_character(static_cast<CharType>(0xce)); + write_number(static_cast<uint32_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)()) + { + // uint 64 + oa->write_character(static_cast<CharType>(0xcf)); + write_number(static_cast<uint64_t>(j.m_value.number_integer)); + } + } + else + { + if (j.m_value.number_integer >= -32) + { + // negative fixnum + write_number(static_cast<int8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() and + j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)()) + { + // int 8 + oa->write_character(static_cast<CharType>(0xd0)); + write_number(static_cast<int8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() and + j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)()) + { + // int 16 + oa->write_character(static_cast<CharType>(0xd1)); + write_number(static_cast<int16_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() and + j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)()) + { + // int 32 + oa->write_character(static_cast<CharType>(0xd2)); + write_number(static_cast<int32_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() and + j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)()) + { + // int 64 + oa->write_character(static_cast<CharType>(0xd3)); + write_number(static_cast<int64_t>(j.m_value.number_integer)); + } + } + break; + } + + case value_t::number_unsigned: + { + if (j.m_value.number_unsigned < 128) + { + // positive fixnum + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)()) + { + // uint 8 + oa->write_character(static_cast<CharType>(0xcc)); + write_number(static_cast<uint8_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)()) + { + // uint 16 + oa->write_character(static_cast<CharType>(0xcd)); + write_number(static_cast<uint16_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)()) + { + // uint 32 + oa->write_character(static_cast<CharType>(0xce)); + write_number(static_cast<uint32_t>(j.m_value.number_integer)); + } + else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)()) + { + // uint 64 + oa->write_character(static_cast<CharType>(0xcf)); + write_number(static_cast<uint64_t>(j.m_value.number_integer)); + } + break; + } + + case value_t::number_float: // float 64 + { + oa->write_character(static_cast<CharType>(0xcb)); + write_number(j.m_value.number_float); + break; + } + + case value_t::string: + { + // step 1: write control byte and the string length + const auto N = j.m_value.string->size(); + if (N <= 31) + { + // fixstr + write_number(static_cast<uint8_t>(0xa0 | N)); + } + else if (N <= 255) + { + // str 8 + oa->write_character(static_cast<CharType>(0xd9)); + write_number(static_cast<uint8_t>(N)); + } + else if (N <= 65535) + { + // str 16 + oa->write_character(static_cast<CharType>(0xda)); + write_number(static_cast<uint16_t>(N)); + } + else if (N <= 4294967295) + { + // str 32 + oa->write_character(static_cast<CharType>(0xdb)); + write_number(static_cast<uint32_t>(N)); + } + + // step 2: write the string + oa->write_characters( + reinterpret_cast<const CharType*>(j.m_value.string->c_str()), + j.m_value.string->size()); + break; + } + + case value_t::array: + { + // step 1: write control byte and the array size + const auto N = j.m_value.array->size(); + if (N <= 15) + { + // fixarray + write_number(static_cast<uint8_t>(0x90 | N)); + } + else if (N <= 0xffff) + { + // array 16 + oa->write_character(static_cast<CharType>(0xdc)); + write_number(static_cast<uint16_t>(N)); + } + else if (N <= 0xffffffff) + { + // array 32 + oa->write_character(static_cast<CharType>(0xdd)); + write_number(static_cast<uint32_t>(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.array) + { + write_msgpack(el); + } + break; + } + + case value_t::object: + { + // step 1: write control byte and the object size + const auto N = j.m_value.object->size(); + if (N <= 15) + { + // fixmap + write_number(static_cast<uint8_t>(0x80 | (N & 0xf))); + } + else if (N <= 65535) + { + // map 16 + oa->write_character(static_cast<CharType>(0xde)); + write_number(static_cast<uint16_t>(N)); + } + else if (N <= 4294967295) + { + // map 32 + oa->write_character(static_cast<CharType>(0xdf)); + write_number(static_cast<uint32_t>(N)); + } + + // step 2: write each element + for (const auto& el : *j.m_value.object) + { + write_msgpack(el.first); + write_msgpack(el.second); + } + break; + } + + default: + break; + } + } + + private: + /* + @brief write a number to output input + + @param[in] n number of type @a NumberType + @tparam NumberType the type of the number + + @note This function needs to respect the system's endianess, because bytes + in CBOR and MessagePack are stored in network order (big endian) and + therefore need reordering on little endian systems. + */ + template<typename NumberType> void write_number(NumberType n) + { + // step 1: write number to array of length NumberType + std::array<CharType, sizeof(NumberType)> vec; + std::memcpy(vec.data(), &n, sizeof(NumberType)); + + // step 2: write array to output (with possible reordering) + if (is_little_endian) + { + // reverse byte order prior to conversion if necessary + std::reverse(vec.begin(), vec.end()); + } + + oa->write_characters(vec.data(), sizeof(NumberType)); + } + + private: + /// whether we can assume little endianess + const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess(); + + /// the output + output_adapter_t<CharType> oa = nullptr; +}; + +/////////////////// +// serialization // +/////////////////// + +template<typename BasicJsonType> +class serializer +{ + using string_t = typename BasicJsonType::string_t; + using number_float_t = typename BasicJsonType::number_float_t; + using number_integer_t = typename BasicJsonType::number_integer_t; + using number_unsigned_t = typename BasicJsonType::number_unsigned_t; + public: + /*! + @param[in] s output stream to serialize to + @param[in] ichar indentation character to use + */ + serializer(output_adapter_t<char> s, const char ichar) + : o(std::move(s)), loc(std::localeconv()), + thousands_sep(loc->thousands_sep == nullptr ? '\0' : loc->thousands_sep[0]), + decimal_point(loc->decimal_point == nullptr ? '\0' : loc->decimal_point[0]), + indent_char(ichar), indent_string(512, indent_char) {} + + // delete because of pointer members + serializer(const serializer&) = delete; + serializer& operator=(const serializer&) = delete; + + /*! + @brief internal implementation of the serialization function + + This function is called by the public member function dump and organizes + the serialization internally. The indentation level is propagated as + additional parameter. In case of arrays and objects, the function is + called recursively. + + - strings and object keys are escaped using `escape_string()` + - integer numbers are converted implicitly via `operator<<` + - floating-point numbers are converted to a string using `"%g"` format + + @param[in] val value to serialize + @param[in] pretty_print whether the output shall be pretty-printed + @param[in] indent_step the indent level + @param[in] current_indent the current indent level (only used internally) + */ + void dump(const BasicJsonType& val, const bool pretty_print, + const bool ensure_ascii, + const unsigned int indent_step, + const unsigned int current_indent = 0) + { + switch (val.m_type) + { + case value_t::object: + { + if (val.m_value.object->empty()) + { + o->write_characters("{}", 2); + return; + } + + if (pretty_print) + { + o->write_characters("{\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_characters(indent_string.c_str(), new_indent); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\": ", 3); + dump(i->second, true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character('}'); + } + else + { + o->write_character('{'); + + // first n-1 elements + auto i = val.m_value.object->cbegin(); + for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) + { + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(i != val.m_value.object->cend()); + assert(std::next(i) == val.m_value.object->cend()); + o->write_character('\"'); + dump_escaped(i->first, ensure_ascii); + o->write_characters("\":", 2); + dump(i->second, false, ensure_ascii, indent_step, current_indent); + + o->write_character('}'); + } + + return; + } + + case value_t::array: + { + if (val.m_value.array->empty()) + { + o->write_characters("[]", 2); + return; + } + + if (pretty_print) + { + o->write_characters("[\n", 2); + + // variable to hold indentation for recursive calls + const auto new_indent = current_indent + indent_step; + if (JSON_UNLIKELY(indent_string.size() < new_indent)) + { + indent_string.resize(indent_string.size() * 2, ' '); + } + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + o->write_characters(indent_string.c_str(), new_indent); + dump(*i, true, ensure_ascii, indent_step, new_indent); + o->write_characters(",\n", 2); + } + + // last element + assert(not val.m_value.array->empty()); + o->write_characters(indent_string.c_str(), new_indent); + dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); + + o->write_character('\n'); + o->write_characters(indent_string.c_str(), current_indent); + o->write_character(']'); + } + else + { + o->write_character('['); + + // first n-1 elements + for (auto i = val.m_value.array->cbegin(); + i != val.m_value.array->cend() - 1; ++i) + { + dump(*i, false, ensure_ascii, indent_step, current_indent); + o->write_character(','); + } + + // last element + assert(not val.m_value.array->empty()); + dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); + + o->write_character(']'); + } + + return; + } + + case value_t::string: + { + o->write_character('\"'); + dump_escaped(*val.m_value.string, ensure_ascii); + o->write_character('\"'); + return; + } + + case value_t::boolean: + { + if (val.m_value.boolean) + { + o->write_characters("true", 4); + } + else + { + o->write_characters("false", 5); + } + return; + } + + case value_t::number_integer: + { + dump_integer(val.m_value.number_integer); + return; + } + + case value_t::number_unsigned: + { + dump_integer(val.m_value.number_unsigned); + return; + } + + case value_t::number_float: + { + dump_float(val.m_value.number_float); + return; + } + + case value_t::discarded: + { + o->write_characters("<discarded>", 11); + return; + } + + case value_t::null: + { + o->write_characters("null", 4); + return; + } + } + } + + private: + /*! + @brief returns the number of expected bytes following in UTF-8 string + + @param[in] u the first byte of a UTF-8 string + @return the number of expected bytes following + */ + static constexpr std::size_t bytes_following(const uint8_t u) + { + return ((u <= 127) ? 0 + : ((192 <= u and u <= 223) ? 1 + : ((224 <= u and u <= 239) ? 2 + : ((240 <= u and u <= 247) ? 3 : std::string::npos)))); + } + + /*! + @brief calculates the extra space to escape a JSON string + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + @return the number of characters required to escape string @a s + + @complexity Linear in the length of string @a s. + */ + static std::size_t extra_space(const string_t& s, + const bool ensure_ascii) noexcept + { + std::size_t res = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + switch (s[i]) + { + // control characters that can be escaped with a backslash + case '"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + { + // from c (1 byte) to \x (2 bytes) + res += 1; + break; + } + + // control characters that need \uxxxx escaping + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x0b: + case 0x0e: + case 0x0f: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x14: + case 0x15: + case 0x16: + case 0x17: + case 0x18: + case 0x19: + case 0x1a: + case 0x1b: + case 0x1c: + case 0x1d: + case 0x1e: + case 0x1f: + { + // from c (1 byte) to \uxxxx (6 bytes) + res += 5; + break; + } + + default: + { + if (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F)) + { + const auto bytes = bytes_following(static_cast<uint8_t>(s[i])); + if (bytes == std::string::npos) + { + // invalid characters are treated as is, so no + // additional space will be used + break; + } + + if (bytes == 3) + { + // codepoints that need 4 bytes (i.e., 3 additional + // bytes) in UTF-8 need a surrogate pair when \u + // escaping is used: from 4 bytes to \uxxxx\uxxxx + // (12 bytes) + res += (12 - bytes - 1); + } + else + { + // from x bytes to \uxxxx (6 bytes) + res += (6 - bytes - 1); + } + + // skip the additional bytes + i += bytes; + } + break; + } + } + } + + return res; + } + + static void escape_codepoint(int codepoint, string_t& result, std::size_t& pos) + { + // expecting a proper codepoint + assert(0x00 <= codepoint and codepoint <= 0x10FFFF); + + // the last written character was the backslash before the 'u' + assert(result[pos] == '\\'); + + // write the 'u' + result[++pos] = 'u'; + + // convert a number 0..15 to its hex representation (0..f) + static const std::array<char, 16> hexify = + { + { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + } + }; + + if (codepoint < 0x10000) + { + // codepoints U+0000..U+FFFF can be represented as \uxxxx. + result[++pos] = hexify[(codepoint >> 12) & 0x0F]; + result[++pos] = hexify[(codepoint >> 8) & 0x0F]; + result[++pos] = hexify[(codepoint >> 4) & 0x0F]; + result[++pos] = hexify[codepoint & 0x0F]; + } + else + { + // codepoints U+10000..U+10FFFF need a surrogate pair to be + // represented as \uxxxx\uxxxx. + // http://www.unicode.org/faq/utf_bom.html#utf16-4 + codepoint -= 0x10000; + const int high_surrogate = 0xD800 | ((codepoint >> 10) & 0x3FF); + const int low_surrogate = 0xDC00 | (codepoint & 0x3FF); + result[++pos] = hexify[(high_surrogate >> 12) & 0x0F]; + result[++pos] = hexify[(high_surrogate >> 8) & 0x0F]; + result[++pos] = hexify[(high_surrogate >> 4) & 0x0F]; + result[++pos] = hexify[high_surrogate & 0x0F]; + ++pos; // backslash is already in output + result[++pos] = 'u'; + result[++pos] = hexify[(low_surrogate >> 12) & 0x0F]; + result[++pos] = hexify[(low_surrogate >> 8) & 0x0F]; + result[++pos] = hexify[(low_surrogate >> 4) & 0x0F]; + result[++pos] = hexify[low_surrogate & 0x0F]; + } + + ++pos; + } + + /*! + @brief dump escaped string + + Escape a string by replacing certain special characters by a sequence of an + escape character (backslash) and another character and other control + characters by a sequence of "\u" followed by a four-digit hex + representation. The escaped string is written to output stream @a o. + + @param[in] s the string to escape + @param[in] ensure_ascii whether to escape non-ASCII characters with + \uXXXX sequences + + @complexity Linear in the length of string @a s. + */ + void dump_escaped(const string_t& s, const bool ensure_ascii) const + { + const auto space = extra_space(s, ensure_ascii); + if (space == 0) + { + o->write_characters(s.c_str(), s.size()); + return; + } + + // create a result string of necessary size + string_t result(s.size() + space, '\\'); + std::size_t pos = 0; + + for (std::size_t i = 0; i < s.size(); ++i) + { + switch (s[i]) + { + case '"': // quotation mark (0x22) + { + result[pos + 1] = '"'; + pos += 2; + break; + } + + case '\\': // reverse solidus (0x5c) + { + // nothing to change + pos += 2; + break; + } + + case '\b': // backspace (0x08) + { + result[pos + 1] = 'b'; + pos += 2; + break; + } + + case '\f': // formfeed (0x0c) + { + result[pos + 1] = 'f'; + pos += 2; + break; + } + + case '\n': // newline (0x0a) + { + result[pos + 1] = 'n'; + pos += 2; + break; + } + + case '\r': // carriage return (0x0d) + { + result[pos + 1] = 'r'; + pos += 2; + break; + } + + case '\t': // horizontal tab (0x09) + { + result[pos + 1] = 't'; + pos += 2; + break; + } + + default: + { + // escape control characters (0x00..0x1F) or, if + // ensure_ascii parameter is used, non-ASCII characters + if ((0x00 <= s[i] and s[i] <= 0x1F) or + (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F))) + { + const auto bytes = bytes_following(static_cast<uint8_t>(s[i])); + if (bytes == std::string::npos) + { + // copy invalid character as is + result[pos++] = s[i]; + break; + } + + // check that the additional bytes are present + assert(i + bytes < s.size()); + + // to use \uxxxx escaping, we first need to caluclate + // the codepoint from the UTF-8 bytes + int codepoint = 0; + + assert(0 <= bytes and bytes <= 3); + switch (bytes) + { + case 0: + { + codepoint = s[i] & 0xFF; + break; + } + + case 1: + { + codepoint = ((s[i] & 0x3F) << 6) + + (s[i + 1] & 0x7F); + break; + } + + case 2: + { + codepoint = ((s[i] & 0x1F) << 12) + + ((s[i + 1] & 0x7F) << 6) + + (s[i + 2] & 0x7F); + break; + } + + case 3: + { + codepoint = ((s[i] & 0xF) << 18) + + ((s[i + 1] & 0x7F) << 12) + + ((s[i + 2] & 0x7F) << 6) + + (s[i + 3] & 0x7F); + break; + } + + default: + break; // LCOV_EXCL_LINE + } + + escape_codepoint(codepoint, result, pos); + i += bytes; + } + else + { + // all other characters are added as-is + result[pos++] = s[i]; + } + break; + } + } + } + + assert(pos == result.size()); + o->write_characters(result.c_str(), result.size()); + } + + /*! + @brief dump an integer + + Dump a given integer to output stream @a o. Works internally with + @a number_buffer. + + @param[in] x integer number (signed or unsigned) to dump + @tparam NumberType either @a number_integer_t or @a number_unsigned_t + */ + template < + typename NumberType, + detail::enable_if_t<std::is_same<NumberType, number_unsigned_t>::value or + std::is_same<NumberType, number_integer_t>::value, + int> = 0 > + void dump_integer(NumberType x) + { + // special case for "0" + if (x == 0) + { + o->write_character('0'); + return; + } + + const bool is_negative = (x <= 0) and (x != 0); // see issue #755 + std::size_t i = 0; + + while (x != 0) + { + // spare 1 byte for '\0' + assert(i < number_buffer.size() - 1); + + const auto digit = std::labs(static_cast<long>(x % 10)); + number_buffer[i++] = static_cast<char>('0' + digit); + x /= 10; + } + + if (is_negative) + { + // make sure there is capacity for the '-' + assert(i < number_buffer.size() - 2); + number_buffer[i++] = '-'; + } + + std::reverse(number_buffer.begin(), number_buffer.begin() + i); + o->write_characters(number_buffer.data(), i); + } + + /*! + @brief dump a floating-point number + + Dump a given floating-point number to output stream @a o. Works internally + with @a number_buffer. + + @param[in] x floating-point number to dump + */ + void dump_float(number_float_t x) + { + // NaN / inf + if (not std::isfinite(x) or std::isnan(x)) + { + o->write_characters("null", 4); + return; + } + + // get number of digits for a text -> float -> text round-trip + static constexpr auto d = std::numeric_limits<number_float_t>::digits10; + + // the actual conversion + std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); + + // negative value indicates an error + assert(len > 0); + // check if buffer was large enough + assert(static_cast<std::size_t>(len) < number_buffer.size()); + + // erase thousands separator + if (thousands_sep != '\0') + { + const auto end = std::remove(number_buffer.begin(), + number_buffer.begin() + len, thousands_sep); + std::fill(end, number_buffer.end(), '\0'); + assert((end - number_buffer.begin()) <= len); + len = (end - number_buffer.begin()); + } + + // convert decimal point to '.' + if (decimal_point != '\0' and decimal_point != '.') + { + const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); + if (dec_pos != number_buffer.end()) + { + *dec_pos = '.'; + } + } + + o->write_characters(number_buffer.data(), static_cast<std::size_t>(len)); + + // determine if need to append ".0" + const bool value_is_int_like = + std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, + [](char c) + { + return (c == '.' or c == 'e'); + }); + + if (value_is_int_like) + { + o->write_characters(".0", 2); + } + } + + private: + /// the output of the serializer + output_adapter_t<char> o = nullptr; + + /// a (hopefully) large enough character buffer + std::array<char, 64> number_buffer{{}}; + + /// the locale + const std::lconv* loc = nullptr; + /// the locale's thousand separator character + const char thousands_sep = '\0'; + /// the locale's decimal point character + const char decimal_point = '\0'; + + /// the indentation character + const char indent_char; + + /// the indentation string + string_t indent_string; +}; + +template<typename BasicJsonType> +class json_ref +{ + public: + using value_type = BasicJsonType; + + json_ref(value_type&& value) + : owned_value(std::move(value)), + value_ref(&owned_value), + is_rvalue(true) + {} + + json_ref(const value_type& value) + : value_ref(const_cast<value_type*>(&value)), + is_rvalue(false) + {} + + json_ref(std::initializer_list<json_ref> init) + : owned_value(init), + value_ref(&owned_value), + is_rvalue(true) + {} + + template <class... Args> + json_ref(Args&& ... args) + : owned_value(std::forward<Args>(args)...), + value_ref(&owned_value), + is_rvalue(true) + {} + + // class should be movable only + json_ref(json_ref&&) = default; + json_ref(const json_ref&) = delete; + json_ref& operator=(const json_ref&) = delete; + + value_type moved_or_copied() const + { + if (is_rvalue) + { + return std::move(*value_ref); + } + return *value_ref; + } + + value_type const& operator*() const + { + return *static_cast<value_type const*>(value_ref); + } + + value_type const* operator->() const + { + return static_cast<value_type const*>(value_ref); + } + + private: + mutable value_type owned_value = nullptr; + value_type* value_ref = nullptr; + const bool is_rvalue; +}; + +} // namespace detail + +/// namespace to hold default `to_json` / `from_json` functions +namespace +{ +constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value; +constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value; +} + + +/*! +@brief default JSONSerializer template argument + +This serializer ignores the template arguments and uses ADL +([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) +for serialization. +*/ +template<typename, typename> +struct adl_serializer +{ + /*! + @brief convert a JSON value to any value type + + This function is usually called by the `get()` function of the + @ref basic_json class (either explicit or via conversion operators). + + @param[in] j JSON value to read from + @param[in,out] val value to write to + */ + template<typename BasicJsonType, typename ValueType> + static void from_json(BasicJsonType&& j, ValueType& val) noexcept( + noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) + { + ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); + } + + /*! + @brief convert any value type to a JSON value + + This function is usually called by the constructors of the @ref basic_json + class. + + @param[in,out] j JSON value to write to + @param[in] val value to read from + */ + template<typename BasicJsonType, typename ValueType> + static void to_json(BasicJsonType& j, ValueType&& val) noexcept( + noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val)))) + { + ::nlohmann::to_json(j, std::forward<ValueType>(val)); + } +}; + +/*! +@brief JSON Pointer + +A JSON pointer defines a string syntax for identifying a specific value +within a JSON document. It can be used with functions `at` and +`operator[]`. Furthermore, JSON pointers are the base for JSON patches. + +@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) + +@since version 2.0.0 +*/ +class json_pointer +{ + /// allow basic_json to access private members + NLOHMANN_BASIC_JSON_TPL_DECLARATION + friend class basic_json; + + public: + /*! + @brief create JSON pointer + + Create a JSON pointer according to the syntax described in + [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). + + @param[in] s string representing the JSON pointer; if omitted, the empty + string is assumed which references the whole JSON value + + @throw parse_error.107 if the given JSON pointer @a s is nonempty and + does not begin with a slash (`/`); see example below + + @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s + is not followed by `0` (representing `~`) or `1` (representing `/`); + see example below + + @liveexample{The example shows the construction several valid JSON + pointers as well as the exceptional behavior.,json_pointer} + + @since version 2.0.0 + */ + explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} + + /*! + @brief return a string representation of the JSON pointer + + @invariant For each JSON pointer `ptr`, it holds: + @code {.cpp} + ptr == json_pointer(ptr.to_string()); + @endcode + + @return a string representation of the JSON pointer + + @liveexample{The example shows the result of `to_string`., + json_pointer__to_string} + + @since version 2.0.0 + */ + std::string to_string() const noexcept + { + return std::accumulate(reference_tokens.begin(), reference_tokens.end(), + std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + escape(b); + }); + } + + /// @copydoc to_string() + operator std::string() const + { + return to_string(); + } + + private: + /*! + @brief remove and return last reference pointer + @throw out_of_range.405 if JSON pointer has no parent + */ + std::string pop_back() + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + auto last = reference_tokens.back(); + reference_tokens.pop_back(); + return last; + } + + /// return whether pointer points to the root document + bool is_root() const + { + return reference_tokens.empty(); + } + + json_pointer top() const + { + if (JSON_UNLIKELY(is_root())) + { + JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); + } + + json_pointer result = *this; + result.reference_tokens = {reference_tokens[0]}; + return result; + } + + + /*! + @brief create and return a reference to the pointed to value + + @complexity Linear in the number of reference tokens. + + @throw parse_error.109 if array index is not a number + @throw type_error.313 if value cannot be unflattened + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + NLOHMANN_BASIC_JSON_TPL& get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const; + + /*! + @brief return a reference to the pointed to value + + @note This version does not throw if a value is not present, but tries to + create nested values instead. For instance, calling this function + with pointer `"/this/that"` on a null value is equivalent to calling + `operator[]("this").operator[]("that")` on that value, effectively + changing the null value to an object. + + @param[in] ptr a JSON value + + @return reference to the JSON value pointed to by the JSON pointer + + @complexity Linear in the length of the JSON pointer. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + NLOHMANN_BASIC_JSON_TPL& get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const; + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + NLOHMANN_BASIC_JSON_TPL& get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const; + + /*! + @brief return a const reference to the pointed to value + + @param[in] ptr a JSON value + + @return const reference to the JSON value pointed to by the JSON + pointer + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + const NLOHMANN_BASIC_JSON_TPL& get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; + + /*! + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + const NLOHMANN_BASIC_JSON_TPL& get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; + + /*! + @brief split the string input to reference tokens + + @note This function is only called by the json_pointer constructor. + All exceptions below are documented there. + + @throw parse_error.107 if the pointer is not empty or begins with '/' + @throw parse_error.108 if character '~' is not followed by '0' or '1' + */ + static std::vector<std::string> split(const std::string& reference_string) + { + std::vector<std::string> result; + + // special case: empty reference string -> no reference tokens + if (reference_string.empty()) + { + return result; + } + + // check if nonempty reference string begins with slash + if (JSON_UNLIKELY(reference_string[0] != '/')) + { + JSON_THROW(detail::parse_error::create(107, 1, + "JSON pointer must be empty or begin with '/' - was: '" + + reference_string + "'")); + } + + // extract the reference tokens: + // - slash: position of the last read slash (or end of string) + // - start: position after the previous slash + for ( + // search for the first slash after the first character + std::size_t slash = reference_string.find_first_of('/', 1), + // set the beginning of the first reference token + start = 1; + // we can stop if start == string::npos+1 = 0 + start != 0; + // set the beginning of the next reference token + // (will eventually be 0 if slash == std::string::npos) + start = slash + 1, + // find next slash + slash = reference_string.find_first_of('/', start)) + { + // use the text between the beginning of the reference token + // (start) and the last slash (slash). + auto reference_token = reference_string.substr(start, slash - start); + + // check reference tokens are properly escaped + for (std::size_t pos = reference_token.find_first_of('~'); + pos != std::string::npos; + pos = reference_token.find_first_of('~', pos + 1)) + { + assert(reference_token[pos] == '~'); + + // ~ must be followed by 0 or 1 + if (JSON_UNLIKELY(pos == reference_token.size() - 1 or + (reference_token[pos + 1] != '0' and + reference_token[pos + 1] != '1'))) + { + JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); + } + } + + // finally, store the reference token + unescape(reference_token); + result.push_back(reference_token); + } + + return result; + } + + /*! + @brief replace all occurrences of a substring by another string + + @param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t + @param[in] f the substring to replace with @a t + @param[in] t the string to replace @a f + + @pre The search string @a f must not be empty. **This precondition is + enforced with an assertion.** + + @since version 2.0.0 + */ + static void replace_substring(std::string& s, const std::string& f, + const std::string& t) + { + assert(not f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} + } + + /// escape "~"" to "~0" and "/" to "~1" + static std::string escape(std::string s) + { + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; + } + + /// unescape "~1" to tilde and "~0" to slash (order is important!) + static void unescape(std::string& s) + { + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); + } + + /*! + @param[in] reference_string the reference string to the current value + @param[in] value the value to consider + @param[in,out] result the result object to insert values to + + @note Empty objects or arrays are flattened to `null`. + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + static void flatten(const std::string& reference_string, + const NLOHMANN_BASIC_JSON_TPL& value, + NLOHMANN_BASIC_JSON_TPL& result); + + /*! + @param[in] value flattened JSON + + @return unflattened JSON + + @throw parse_error.109 if array index is not a number + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + @throw type_error.313 if value cannot be unflattened + */ + NLOHMANN_BASIC_JSON_TPL_DECLARATION + static NLOHMANN_BASIC_JSON_TPL + unflatten(const NLOHMANN_BASIC_JSON_TPL& value); + + friend bool operator==(json_pointer const& lhs, + json_pointer const& rhs) noexcept; + + friend bool operator!=(json_pointer const& lhs, + json_pointer const& rhs) noexcept; + + /// the reference tokens + std::vector<std::string> reference_tokens; +}; + +/*! +@brief a class to store JSON values + +@tparam ObjectType type for JSON objects (`std::map` by default; will be used +in @ref object_t) +@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used +in @ref array_t) +@tparam StringType type for JSON strings and object keys (`std::string` by +default; will be used in @ref string_t) +@tparam BooleanType type for JSON booleans (`bool` by default; will be used +in @ref boolean_t) +@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by +default; will be used in @ref number_integer_t) +@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c +`uint64_t` by default; will be used in @ref number_unsigned_t) +@tparam NumberFloatType type for JSON floating-point numbers (`double` by +default; will be used in @ref number_float_t) +@tparam AllocatorType type of the allocator to use (`std::allocator` by +default) +@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` +and `from_json()` (@ref adl_serializer by default) + +@requirement The class satisfies the following concept requirements: +- Basic + - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): + JSON values can be default constructed. The result will be a JSON null + value. + - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): + A JSON value can be constructed from an rvalue argument. + - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): + A JSON value can be copy-constructed from an lvalue expression. + - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): + A JSON value van be assigned from an rvalue argument. + - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): + A JSON value can be copy-assigned from an lvalue expression. + - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): + JSON values can be destructed. +- Layout + - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): + JSON values have + [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): + All non-static data members are private and standard layout types, the + class has no virtual functions or (virtual) base classes. +- Library-wide + - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): + JSON values can be compared with `==`, see @ref + operator==(const_reference,const_reference). + - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): + JSON values can be compared with `<`, see @ref + operator<(const_reference,const_reference). + - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): + Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of + other compatible types, using unqualified function call @ref swap(). + - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): + JSON values can be compared against `std::nullptr_t` objects which are used + to model the `null` value. +- Container + - [Container](http://en.cppreference.com/w/cpp/concept/Container): + JSON values can be used like STL containers and provide iterator access. + - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); + JSON values can be used like STL containers and provide reverse iterator + access. + +@invariant The member variables @a m_value and @a m_type have the following +relationship: +- If `m_type == value_t::object`, then `m_value.object != nullptr`. +- If `m_type == value_t::array`, then `m_value.array != nullptr`. +- If `m_type == value_t::string`, then `m_value.string != nullptr`. +The invariants are checked by member function assert_invariant(). + +@internal +@note ObjectType trick from http://stackoverflow.com/a/9860911 +@endinternal + +@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange +Format](http://rfc7159.net/rfc7159) + +@since version 1.0.0 + +@nosubgrouping +*/ +NLOHMANN_BASIC_JSON_TPL_DECLARATION +class basic_json +{ + private: + template<detail::value_t> friend struct detail::external_constructor; + friend ::nlohmann::json_pointer; + friend ::nlohmann::detail::parser<basic_json>; + friend ::nlohmann::detail::serializer<basic_json>; + template<typename BasicJsonType> + friend class ::nlohmann::detail::iter_impl; + template<typename BasicJsonType, typename CharType> + friend class ::nlohmann::detail::binary_writer; + template<typename BasicJsonType> + friend class ::nlohmann::detail::binary_reader; + + /// workaround type for MSVC + using basic_json_t = NLOHMANN_BASIC_JSON_TPL; + + // convenience aliases for types residing in namespace detail; + using lexer = ::nlohmann::detail::lexer<basic_json>; + using parser = ::nlohmann::detail::parser<basic_json>; + + using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; + template<typename BasicJsonType> + using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>; + template<typename BasicJsonType> + using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>; + template<typename Iterator> + using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>; + template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>; + + template<typename CharType> + using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>; + + using binary_reader = ::nlohmann::detail::binary_reader<basic_json>; + template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>; + + using serializer = ::nlohmann::detail::serializer<basic_json>; + + public: + using value_t = detail::value_t; + // forward declarations + using json_pointer = ::nlohmann::json_pointer; + template<typename T, typename SFINAE> + using json_serializer = JSONSerializer<T, SFINAE>; + + using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; + + //////////////// + // exceptions // + //////////////// + + /// @name exceptions + /// Classes to implement user-defined exceptions. + /// @{ + + /// @copydoc detail::exception + using exception = detail::exception; + /// @copydoc detail::parse_error + using parse_error = detail::parse_error; + /// @copydoc detail::invalid_iterator + using invalid_iterator = detail::invalid_iterator; + /// @copydoc detail::type_error + using type_error = detail::type_error; + /// @copydoc detail::out_of_range + using out_of_range = detail::out_of_range; + /// @copydoc detail::other_error + using other_error = detail::other_error; + + /// @} + + + ///////////////////// + // container types // + ///////////////////// + + /// @name container types + /// The canonic container types to use @ref basic_json like any other STL + /// container. + /// @{ + + /// the type of elements in a basic_json container + using value_type = basic_json; + + /// the type of an element reference + using reference = value_type&; + /// the type of an element const reference + using const_reference = const value_type&; + + /// a type to represent differences between iterators + using difference_type = std::ptrdiff_t; + /// a type to represent container sizes + using size_type = std::size_t; + + /// the allocator type + using allocator_type = AllocatorType<basic_json>; + + /// the type of an element pointer + using pointer = typename std::allocator_traits<allocator_type>::pointer; + /// the type of an element const pointer + using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; + + /// an iterator for a basic_json container + using iterator = iter_impl<basic_json>; + /// a const iterator for a basic_json container + using const_iterator = iter_impl<const basic_json>; + /// a reverse iterator for a basic_json container + using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; + /// a const reverse iterator for a basic_json container + using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; + + /// @} + + + /*! + @brief returns the allocator associated with the container + */ + static allocator_type get_allocator() + { + return allocator_type(); + } + + /*! + @brief returns version information on the library + + This function returns a JSON object with information about the library, + including the version number and information on the platform and compiler. + + @return JSON object holding version information + key | description + ----------- | --------------- + `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). + `copyright` | The copyright line for the library as string. + `name` | The name of the library as string. + `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. + `url` | The URL of the project as string. + `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). + + @liveexample{The following code shows an example output of the `meta()` + function.,meta} + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @complexity Constant. + + @since 2.1.0 + */ + static basic_json meta() + { + basic_json result; + + result["copyright"] = "(C) 2013-2017 Niels Lohmann"; + result["name"] = "JSON for Modern C++"; + result["url"] = "https://github.com/nlohmann/json"; + result["version"] = + { + {"string", "2.1.1"}, {"major", 2}, {"minor", 1}, {"patch", 1} + }; + +#ifdef _WIN32 + result["platform"] = "win32"; +#elif defined __linux__ + result["platform"] = "linux"; +#elif defined __APPLE__ + result["platform"] = "apple"; +#elif defined __unix__ + result["platform"] = "unix"; +#else + result["platform"] = "unknown"; +#endif + +#if defined(__ICC) || defined(__INTEL_COMPILER) + result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; +#elif defined(__clang__) + result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; +#elif defined(__GNUC__) || defined(__GNUG__) + result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; +#elif defined(__HP_cc) || defined(__HP_aCC) + result["compiler"] = "hp" +#elif defined(__IBMCPP__) + result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; +#elif defined(_MSC_VER) + result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; +#elif defined(__PGI) + result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; +#elif defined(__SUNPRO_CC) + result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; +#else + result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; +#endif + +#ifdef __cplusplus + result["compiler"]["c++"] = std::to_string(__cplusplus); +#else + result["compiler"]["c++"] = "unknown"; +#endif + return result; + } + + + /////////////////////////// + // JSON value data types // + /////////////////////////// + + /// @name JSON value data types + /// The data types to store a JSON value. These types are derived from + /// the template arguments passed to class @ref basic_json. + /// @{ + + /*! + @brief a type for an object + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: + > An object is an unordered collection of zero or more name/value pairs, + > where a name is a string and a value is a string, number, boolean, null, + > object, or array. + + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). + The comparison function `std::less<StringType>` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) + + #### Default type + + With the default values for @a ObjectType (`std::map`), @a StringType + (`std::string`), and @a AllocatorType (`std::allocator`), the default + value for @a object_t is: + + @code {.cpp} + std::map< + std::string, // key_type + basic_json, // value_type + std::less<std::string>, // key_compare + std::allocator<std::pair<const std::string, basic_json>> // allocator_type + > + @endcode + + #### Behavior + + The choice of @a object_t influences the behavior of the JSON class. With + the default type, objects have the following behavior: + + - When all names are unique, objects will be interoperable in the sense + that all software implementations receiving that object will agree on + the name-value mappings. + - When the names within an object are not unique, later stored name/value + pairs overwrite previously stored name/value pairs, leaving the used + names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will + be treated as equal and both stored as `{"key": 1}`. + - Internally, name/value pairs are stored in lexicographical order of the + names. Objects will also be serialized (see @ref dump) in this order. + For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored + and serialized as `{"a": 2, "b": 1}`. + - When comparing objects, the order of the name/value pairs is irrelevant. + This makes objects interoperable in the sense that they will not be + affected by these differences. For instance, `{"b": 1, "a": 2}` and + `{"a": 2, "b": 1}` will be treated as equal. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the object's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON object. + + #### Storage + + Objects are stored as pointers in a @ref basic_json type. That is, for any + access to object values, a pointer of type `object_t*` must be + dereferenced. + + @sa @ref array_t -- type for an array value + + @since version 1.0.0 + + @note The order name/value pairs are added to the object is *not* + preserved by the library. Therefore, iterating an object may return + name/value pairs in a different order than they were originally stored. In + fact, keys will be traversed in alphabetical order as `std::map` with + `std::less` is used by default. Please note this behavior conforms to [RFC + 7159](http://rfc7159.net/rfc7159), because any order implements the + specified "unordered" nature of JSON objects. + */ + +#if defined(JSON_HAS_CPP_14) + // Use transparent comparator if possible, combined with perfect forwarding + // on find() and count() calls prevents unnecessary string construction. + using object_comparator_t = std::less<>; +#else + using object_comparator_t = std::less<StringType>; +#endif + using object_t = ObjectType<StringType, + basic_json, + object_comparator_t, + AllocatorType<std::pair<const StringType, + basic_json>>>; + + /*! + @brief a type for an array + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: + > An array is an ordered sequence of zero or more values. + + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) + + #### Default type + + With the default values for @a ArrayType (`std::vector`) and @a + AllocatorType (`std::allocator`), the default value for @a array_t is: + + @code {.cpp} + std::vector< + basic_json, // value_type + std::allocator<basic_json> // allocator_type + > + @endcode + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the maximum depth of nesting. + + In this class, the array's limit of nesting is not explicitly constrained. + However, a maximum depth of nesting may be introduced by the compiler or + runtime environment. A theoretical limit can be queried by calling the + @ref max_size function of a JSON array. + + #### Storage + + Arrays are stored as pointers in a @ref basic_json type. That is, for any + access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 + */ + using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; + + /*! + @brief a type for a string + + [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: + > A string is a sequence of zero or more Unicode characters. + + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into + byte-sized characters during deserialization. + + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. + + #### Default type + + With the default values for @a StringType (`std::string`), the default + value for @a string_t is: + + @code {.cpp} + std::string + @endcode + + #### Encoding + + Strings are stored in UTF-8 encoding. Therefore, functions like + `std::string::size()` or `std::string::length()` return the number of + bytes in the string rather than the number of characters or glyphs. + + #### String comparison + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > Software implementations are typically required to test names of object + > members for equality. Implementations that transform the textual + > representation into sequences of Unicode code units and then perform the + > comparison numerically, code unit by code unit, are interoperable in the + > sense that implementations will agree in all cases on equality or + > inequality of two strings. For example, implementations that compare + > strings with escaped characters unconverted may incorrectly find that + > `"a\\b"` and `"a\u005Cb"` are not equal. + + This implementation is interoperable as it does compare strings code unit + by code unit. + + #### Storage + + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be + dereferenced. + + @since version 1.0.0 + */ + using string_t = StringType; + + /*! + @brief a type for a boolean + + [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a + type which differentiates the two literals `true` and `false`. + + To store objects in C++, a type is defined by the template parameter @a + BooleanType which chooses the type to use. + + #### Default type + + With the default values for @a BooleanType (`bool`), the default value for + @a boolean_t is: + + @code {.cpp} + bool + @endcode + + #### Storage + + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 + */ + using boolean_t = BooleanType; + + /*! + @brief a type for a number (integer) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store integer numbers in C++, a type is defined by the template + parameter @a NumberIntegerType which chooses the type to use. + + #### Default type + + With the default values for @a NumberIntegerType (`int64_t`), the default + value for @a number_integer_t is: + + @code {.cpp} + int64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `9223372036854775807` (INT64_MAX) and the minimal integer number + that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers + that are out of range will yield over/underflow when used in a + constructor. During deserialization, too large or small integer numbers + will be automatically be stored as @ref number_unsigned_t or @ref + number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange of the exactly supported range [INT64_MIN, + INT64_MAX], this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_integer_t = NumberIntegerType; + + /*! + @brief a type for a number (unsigned) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store unsigned integer numbers in C++, a type is defined by the + template parameter @a NumberUnsignedType which chooses the type to use. + + #### Default type + + With the default values for @a NumberUnsignedType (`uint64_t`), the + default value for @a number_unsigned_t is: + + @code {.cpp} + uint64_t + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in integer literals lead to an interpretation as octal + number. Internally, the value will be stored as decimal number. For + instance, the C++ integer literal `010` will be serialized to `8`. + During deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) specifies: + > An implementation may set limits on the range and precision of numbers. + + When the default type is used, the maximal integer number that can be + stored is `18446744073709551615` (UINT64_MAX) and the minimal integer + number that can be stored is `0`. Integer numbers that are out of range + will yield over/underflow when used in a constructor. During + deserialization, too large or small integer numbers will be automatically + be stored as @ref number_integer_t or @ref number_float_t. + + [RFC 7159](http://rfc7159.net/rfc7159) further states: + > Note that when such software is used, numbers that are integers and are + > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense + > that implementations will agree exactly on their numeric values. + + As this range is a subrange (when considered in conjunction with the + number_integer_t type) of the exactly supported range [0, UINT64_MAX], + this class's integer type is interoperable. + + #### Storage + + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + @sa @ref number_integer_t -- type for number values (integer) + + @since version 2.0.0 + */ + using number_unsigned_t = NumberUnsignedType; + + /*! + @brief a type for a number (floating-point) + + [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: + > The representation of numbers is similar to that used in most + > programming languages. A number is represented in base 10 using decimal + > digits. It contains an integer component that may be prefixed with an + > optional minus sign, which may be followed by a fraction part and/or an + > exponent part. Leading zeros are not allowed. (...) Numeric values that + > cannot be represented in the grammar below (such as Infinity and NaN) + > are not permitted. + + This description includes both integer and floating-point numbers. + However, C++ allows more precise storage if it is known whether the number + is a signed integer, an unsigned integer or a floating-point number. + Therefore, three different types, @ref number_integer_t, @ref + number_unsigned_t and @ref number_float_t are used. + + To store floating-point numbers in C++, a type is defined by the template + parameter @a NumberFloatType which chooses the type to use. + + #### Default type + + With the default values for @a NumberFloatType (`double`), the default + value for @a number_float_t is: + + @code {.cpp} + double + @endcode + + #### Default behavior + + - The restrictions about leading zeros is not enforced in C++. Instead, + leading zeros in floating-point literals will be ignored. Internally, + the value will be stored as decimal number. For instance, the C++ + floating-point literal `01.2` will be serialized to `1.2`. During + deserialization, leading zeros yield an error. + - Not-a-number (NaN) values will be serialized to `null`. + + #### Limits + + [RFC 7159](http://rfc7159.net/rfc7159) states: + > This specification allows implementations to set limits on the range and + > precision of numbers accepted. Since software that implements IEEE + > 754-2008 binary64 (double precision) numbers is generally available and + > widely used, good interoperability can be achieved by implementations + > that expect no more precision or range than these provide, in the sense + > that implementations will approximate JSON numbers within the expected + > precision. + + This implementation does exactly follow this approach, as it uses double + precision floating-point numbers. Note values smaller than + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` + will be stored as NaN internally and be serialized to `null`. + + #### Storage + + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @sa @ref number_unsigned_t -- type for number values (unsigned integer) + + @since version 1.0.0 + */ + using number_float_t = NumberFloatType; + + /// @} + + private: + + /// helper for exception-safe object creation + template<typename T, typename... Args> + static T* create(Args&& ... args) + { + AllocatorType<T> alloc; + auto deleter = [&](T * object) + { + alloc.deallocate(object, 1); + }; + std::unique_ptr<T, decltype(deleter)> object(alloc.allocate(1), deleter); + alloc.construct(object.get(), std::forward<Args>(args)...); + assert(object != nullptr); + return object.release(); + } + + //////////////////////// + // JSON value storage // + //////////////////////// + + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. This + union combines the different storage types for the JSON value types + defined in @ref value_t. + + JSON type | value_t type | used type + --------- | --------------- | ------------------------ + object | object | pointer to @ref object_t + array | array | pointer to @ref array_t + string | string | pointer to @ref string_t + boolean | boolean | @ref boolean_t + number | number_integer | @ref number_integer_t + number | number_unsigned | @ref number_unsigned_t + number | number_float | @ref number_float_t + null | null | *no value is stored* + + @note Variable-length types (objects, arrays, and strings) are stored as + pointers. The size of the union should not exceed 64 bits if the default + value types are used. + + @since version 1.0.0 + */ + union json_value + { + /// object (stored with pointer to save storage) + object_t* object; + /// array (stored with pointer to save storage) + array_t* array; + /// string (stored with pointer to save storage) + string_t* string; + /// boolean + boolean_t boolean; + /// number (integer) + number_integer_t number_integer; + /// number (unsigned integer) + number_unsigned_t number_unsigned; + /// number (floating-point) + number_float_t number_float; + + /// default constructor (for null values) + json_value() = default; + /// constructor for booleans + json_value(boolean_t v) noexcept : boolean(v) {} + /// constructor for numbers (integer) + json_value(number_integer_t v) noexcept : number_integer(v) {} + /// constructor for numbers (unsigned) + json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} + /// constructor for numbers (floating-point) + json_value(number_float_t v) noexcept : number_float(v) {} + /// constructor for empty values of a given type + json_value(value_t t) + { + switch (t) + { + case value_t::object: + { + object = create<object_t>(); + break; + } + + case value_t::array: + { + array = create<array_t>(); + break; + } + + case value_t::string: + { + string = create<string_t>(""); + break; + } + + case value_t::boolean: + { + boolean = boolean_t(false); + break; + } + + case value_t::number_integer: + { + number_integer = number_integer_t(0); + break; + } + + case value_t::number_unsigned: + { + number_unsigned = number_unsigned_t(0); + break; + } + + case value_t::number_float: + { + number_float = number_float_t(0.0); + break; + } + + case value_t::null: + { + break; + } + + default: + { + if (JSON_UNLIKELY(t == value_t::null)) + { + JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 2.1.1")); // LCOV_EXCL_LINE + } + break; + } + } + } + + /// constructor for strings + json_value(const string_t& value) + { + string = create<string_t>(value); + } + + /// constructor for rvalue strings + json_value(string_t&& value) + { + string = create<string_t>(std::move(value)); + } + + /// constructor for objects + json_value(const object_t& value) + { + object = create<object_t>(value); + } + + /// constructor for rvalue objects + json_value(object_t&& value) + { + object = create<object_t>(std::move(value)); + } + + /// constructor for arrays + json_value(const array_t& value) + { + array = create<array_t>(value); + } + + /// constructor for rvalue arrays + json_value(array_t&& value) + { + array = create<array_t>(std::move(value)); + } + + void destroy(value_t t) + { + switch (t) + { + case value_t::object: + { + AllocatorType<object_t> alloc; + alloc.destroy(object); + alloc.deallocate(object, 1); + break; + } + + case value_t::array: + { + AllocatorType<array_t> alloc; + alloc.destroy(array); + alloc.deallocate(array, 1); + break; + } + + case value_t::string: + { + AllocatorType<string_t> alloc; + alloc.destroy(string); + alloc.deallocate(string, 1); + break; + } + + default: + { + break; + } + } + } + }; + + /*! + @brief checks the class invariants + + This function asserts the class invariants. It needs to be called at the + end of every constructor to make sure that created objects respect the + invariant. Furthermore, it has to be called each time the type of a JSON + value is changed, because the invariant expresses a relationship between + @a m_type and @a m_value. + */ + void assert_invariant() const + { + assert(m_type != value_t::object or m_value.object != nullptr); + assert(m_type != value_t::array or m_value.array != nullptr); + assert(m_type != value_t::string or m_value.string != nullptr); + } + + public: + ////////////////////////// + // JSON parser callback // + ////////////////////////// + + using parse_event_t = typename parser::parse_event_t; + + /*! + @brief per-element parser callback type + + With a parser callback function, the result of parsing a JSON text can be + influenced. When passed to @ref parse, it is called on certain events + (passed as @ref parse_event_t via parameter @a event) with a set recursion + depth @a depth and context JSON value @a parsed. The return value of the + callback function is a boolean indicating whether the element that emitted + the callback shall be kept or not. + + We distinguish six scenarios (determined by the event type) in which the + callback function can be called. The following table describes the values + of the parameters @a depth, @a event, and @a parsed. + + parameter @a event | description | parameter @a depth | parameter @a parsed + ------------------ | ----------- | ------------------ | ------------------- + parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded + parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key + parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object + parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded + parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array + parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value + + @image html callback_events.png "Example when certain parse events are triggered" + + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: + + - Discarded values in structured types are skipped. That is, the parser + will behave as if the discarded value was never read. + - In case a value outside a structured type is skipped, it is replaced + with `null`. This case happens if the top-level element is skipped. + + @param[in] depth the depth of the recursion during parsing + + @param[in] event an event of type parse_event_t indicating the context in + the callback function has been called + + @param[in,out] parsed the current intermediate parse result; note that + writing to this value has no effect for parse_event_t::key events + + @return Whether the JSON value which called the function during parsing + should be kept (`true`) or not (`false`). In the latter case, it is either + skipped completely or replaced by an empty discarded object. + + @sa @ref parse for examples + + @since version 1.0.0 + */ + using parser_callback_t = typename parser::parser_callback_t; + + + ////////////////// + // constructors // + ////////////////// + + /// @name constructors and destructors + /// Constructors of class @ref basic_json, copy/move constructor, copy + /// assignment, static functions creating objects, and the destructor. + /// @{ + + /*! + @brief create an empty value with a given type + + Create an empty JSON value with a given type. The value will be default + initialized with an empty value which depends on the type: + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @param[in] v the type of the value to create + + @complexity Constant. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows the constructor for different @ref + value_t values,basic_json__value_t} + + @sa @ref clear() -- restores the postcondition of this constructor + + @since version 1.0.0 + */ + basic_json(const value_t v) + : m_type(v), m_value(v) + { + assert_invariant(); + } + + /*! + @brief create a null object + + Create a `null` JSON value. It either takes a null pointer as parameter + (explicitly creating `null`) or no parameter (implicitly creating `null`). + The passed null pointer itself is not read -- it is only used to choose + the right constructor. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @liveexample{The following code shows the constructor with and without a + null pointer parameter.,basic_json__nullptr_t} + + @since version 1.0.0 + */ + basic_json(std::nullptr_t = nullptr) noexcept + : basic_json(value_t::null) + { + assert_invariant(); + } + + /*! + @brief create a JSON value + + This is a "catch all" constructor for all compatible JSON types; that is, + types for which a `to_json()` method exsits. The constructor forwards the + parameter @a val to that method (to `json_serializer<U>::to_json` method + with `U = uncvref_t<CompatibleType>`, to be exact). + + Template type @a CompatibleType includes, but is not limited to, the + following types: + - **arrays**: @ref array_t and all kinds of compatible containers such as + `std::vector`, `std::deque`, `std::list`, `std::forward_list`, + `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, + `std::multiset`, and `std::unordered_multiset` with a `value_type` from + which a @ref basic_json value can be constructed. + - **objects**: @ref object_t and all kinds of compatible associative + containers such as `std::map`, `std::unordered_map`, `std::multimap`, + and `std::unordered_multimap` with a `key_type` compatible to + @ref string_t and a `value_type` from which a @ref basic_json value can + be constructed. + - **strings**: @ref string_t, string literals, and all compatible string + containers can be used. + - **numbers**: @ref number_integer_t, @ref number_unsigned_t, + @ref number_float_t, and all convertible number types such as `int`, + `size_t`, `int64_t`, `float` or `double` can be used. + - **boolean**: @ref boolean_t / `bool` can be used. + + See the examples below. + + @tparam CompatibleType a type such that: + - @a CompatibleType is not derived from `std::istream`, + - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move + constructors), + - @a CompatibleType is not a @ref basic_json nested type (e.g., + @ref json_pointer, @ref iterator, etc ...) + - @ref @ref json_serializer<U> has a + `to_json(basic_json_t&, CompatibleType&&)` method + + @tparam U = `uncvref_t<CompatibleType>` + + @param[in] val the value to be forwarded to the respective constructor + + @complexity Usually linear in the size of the passed @a val, also + depending on the implementation of the called `to_json()` + method. + + @exceptionsafety Depends on the called constructor. For types directly + supported by the library (i.e., all types for which no `to_json()` function + was provided), strong guarantee holds: if an exception is thrown, there are + no changes to any JSON value. + + @liveexample{The following code shows the constructor with several + compatible types.,basic_json__CompatibleType} + + @since version 2.1.0 + */ + template<typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>, + detail::enable_if_t<not std::is_base_of<std::istream, U>::value and + not std::is_same<U, basic_json_t>::value and + not detail::is_basic_json_nested_type< + basic_json_t, U>::value and + detail::has_to_json<basic_json, U>::value, + int> = 0> + basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer<U>::to_json( + std::declval<basic_json_t&>(), std::forward<CompatibleType>(val)))) + { + JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); + assert_invariant(); + } + + /*! + @brief create a container (array or object) from an initializer list + + Creates a JSON value of type array or object from the passed initializer + list @a init. In case @a type_deduction is `true` (default), the type of + the JSON value to be created is deducted from the initializer list @a init + according to the following rules: + + 1. If the list is empty, an empty JSON object value `{}` is created. + 2. If the list consists of pairs whose first element is a string, a JSON + object value is created where the first elements of the pairs are + treated as keys and the second elements are as values. + 3. In all other cases, an array is created. + + The rules aim to create the best fit between a C++ initializer list and + JSON values. The rationale is as follows: + + 1. The empty initializer list is written as `{}` which is exactly an empty + JSON object. + 2. C++ has no way of describing mapped types other than to list a list of + pairs. As JSON requires that keys must be of type string, rule 2 is the + weakest constraint one can pose on initializer lists to interpret them + as an object. + 3. In all other cases, the initializer list could not be interpreted as + JSON object type, so interpreting it as JSON array type is safe. + + With the rules described above, the following JSON values cannot be + expressed by an initializer list: + + - the empty array (`[]`): use @ref array(initializer_list_t) + with an empty initializer list in this case + - arrays whose elements satisfy rule 2: use @ref + array(initializer_list_t) with the same initializer list + in this case + + @note When used without parentheses around an empty initializer list, @ref + basic_json() is called instead of this function, yielding the JSON null + value. + + @param[in] init initializer list with JSON values + + @param[in] type_deduction internal parameter; when set to `true`, the type + of the JSON value is deducted from the initializer list @a init; when set + to `false`, the type provided via @a manual_type is forced. This mode is + used by the functions @ref array(initializer_list_t) and + @ref object(initializer_list_t). + + @param[in] manual_type internal parameter; when @a type_deduction is set + to `false`, the created JSON value will use the provided type (only @ref + value_t::array and @ref value_t::object are valid); when @a type_deduction + is set to `true`, this parameter has no effect + + @throw type_error.301 if @a type_deduction is `false`, @a manual_type is + `value_t::object`, but @a init contains an element which is not a pair + whose first element is a string. In this case, the constructor could not + create an object. If @a type_deduction would have be `true`, an array + would have been created. See @ref object(initializer_list_t) + for an example. + + @complexity Linear in the size of the initializer list @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows how JSON values are created from + initializer lists.,basic_json__list_init_t} + + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + basic_json(initializer_list_t init, + bool type_deduction = true, + value_t manual_type = value_t::array) + { + // check if each element is an array with two elements whose first + // element is a string + bool is_an_object = std::all_of(init.begin(), init.end(), + [](const detail::json_ref<basic_json>& element_ref) + { + return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); + }); + + // adjust type if type deduction is not wanted + if (not type_deduction) + { + // if array is wanted, do not create an object though possible + if (manual_type == value_t::array) + { + is_an_object = false; + } + + // if object is wanted but impossible, throw an exception + if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) + { + JSON_THROW(type_error::create(301, "cannot create object from initializer list")); + } + } + + if (is_an_object) + { + // the initializer list is a list of pairs -> create object + m_type = value_t::object; + m_value = value_t::object; + + std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json>& element_ref) + { + auto element = element_ref.moved_or_copied(); + m_value.object->emplace( + std::move(*((*element.m_value.array)[0].m_value.string)), + std::move((*element.m_value.array)[1])); + }); + } + else + { + // the initializer list describes an array -> create array + m_type = value_t::array; + m_value.array = create<array_t>(init.begin(), init.end()); + } + + assert_invariant(); + } + + /*! + @brief explicitly create an array from an initializer list + + Creates a JSON array value from a given initializer list. That is, given a + list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the + initializer list is empty, the empty array `[]` is created. + + @note This function is only needed to express two edge cases that cannot + be realized with the initializer list constructor (@ref + basic_json(initializer_list_t, bool, value_t)). These cases + are: + 1. creating an array whose elements are all pairs whose first element is a + string -- in this case, the initializer list constructor would create an + object, taking the first elements as keys + 2. creating an empty array -- passing the empty initializer list to the + initializer list constructor yields an empty object + + @param[in] init initializer list with JSON values to create an array from + (optional) + + @return JSON array value + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `array` + function.,array} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(initializer_list_t) -- create a JSON object + value from an initializer list + + @since version 1.0.0 + */ + static basic_json array(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::array); + } + + /*! + @brief explicitly create an object from an initializer list + + Creates a JSON object value from a given initializer list. The initializer + lists elements must be pairs, and their first elements must be strings. If + the initializer list is empty, the empty object `{}` is created. + + @note This function is only added for symmetry reasons. In contrast to the + related function @ref array(initializer_list_t), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor @ref basic_json(initializer_list_t, bool, value_t). + + @param[in] init initializer list to create an object from (optional) + + @return JSON object value + + @throw type_error.301 if @a init is not a list of pairs whose first + elements are strings. In this case, no object can be created. When such a + value is passed to @ref basic_json(initializer_list_t, bool, value_t), + an array would have been created from the passed initializer list @a init. + See example below. + + @complexity Linear in the size of @a init. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows an example for the `object` + function.,object} + + @sa @ref basic_json(initializer_list_t, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(initializer_list_t) -- create a JSON array + value from an initializer list + + @since version 1.0.0 + */ + static basic_json object(initializer_list_t init = {}) + { + return basic_json(init, false, value_t::object); + } + + /*! + @brief construct an array with count copies of given value + + Constructs a JSON array value by creating @a cnt copies of a passed value. + In case @a cnt is `0`, an empty array is created. + + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy + + @post `std::distance(begin(),end()) == cnt` holds. + + @complexity Linear in @a cnt. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The following code shows examples for the @ref + basic_json(size_type\, const basic_json&) + constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 + */ + basic_json(size_type cnt, const basic_json& val) + : m_type(value_t::array) + { + m_value.array = create<array_t>(cnt, val); + assert_invariant(); + } + + /*! + @brief construct a JSON container given an iterator range + + Constructs the JSON value with the contents of the range `[first, last)`. + The semantics depends on the different types a JSON value can have: + - In case of a null type, invalid_iterator.206 is thrown. + - In case of other primitive types (number, boolean, or string), @a first + must be `begin()` and @a last must be `end()`. In this case, the value is + copied. Otherwise, invalid_iterator.204 is thrown. + - In case of structured types (array, object), the constructor behaves as + similar versions for `std::vector` or `std::map`; that is, a JSON array + or object is constructed from the values in the range. + + @tparam InputIT an input iterator type (@ref iterator or @ref + const_iterator) + + @param[in] first begin of the range to copy from (included) + @param[in] last end of the range to copy from (excluded) + + @pre Iterators @a first and @a last must be initialized. **This + precondition is enforced with an assertion (see warning).** If + assertions are switched off, a violation of this precondition yields + undefined behavior. + + @pre Range `[first, last)` is valid. Usually, this precondition cannot be + checked efficiently. Only certain edge cases are detected; see the + description of the exceptions below. A violation of this precondition + yields undefined behavior. + + @warning A precondition is enforced with a runtime assertion that will + result in calling `std::abort` if this precondition is not met. + Assertions can be disabled by defining `NDEBUG` at compile time. + See http://en.cppreference.com/w/cpp/error/assert for more + information. + + @throw invalid_iterator.201 if iterators @a first and @a last are not + compatible (i.e., do not belong to the same JSON value). In this case, + the range `[first, last)` is undefined. + @throw invalid_iterator.204 if iterators @a first and @a last belong to a + primitive type (number, boolean, or string), but @a first does not point + to the first element any more. In this case, the range `[first, last)` is + undefined. See example code below. + @throw invalid_iterator.206 if iterators @a first and @a last belong to a + null value. In this case, the range `[first, last)` is undefined. + + @complexity Linear in distance between @a first and @a last. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @liveexample{The example below shows several ways to create JSON values by + specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 + */ + template<class InputIT, typename std::enable_if< + std::is_same<InputIT, typename basic_json_t::iterator>::value or + std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0> + basic_json(InputIT first, InputIT last) + { + assert(first.m_object != nullptr); + assert(last.m_object != nullptr); + + // make sure iterator fits the current value + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); + } + + // copy type from first iterator + m_type = first.m_object->m_type; + + // check if iterator range is complete for primitive values + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + break; + } + + default: + break; + } + + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = first.m_object->m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = first.m_object->m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value.number_float = first.m_object->m_value.number_float; + break; + } + + case value_t::boolean: + { + m_value.boolean = first.m_object->m_value.boolean; + break; + } + + case value_t::string: + { + m_value = *first.m_object->m_value.string; + break; + } + + case value_t::object: + { + m_value.object = create<object_t>(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + m_value.array = create<array_t>(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + + std::string(first.m_object->type_name()))); + } + + assert_invariant(); + } + + + /////////////////////////////////////// + // other constructors and destructor // + /////////////////////////////////////// + + /// @private + basic_json(const detail::json_ref<basic_json>& ref) + : basic_json(ref.moved_or_copied()) + {} + + /*! + @brief copy constructor + + Creates a copy of a given JSON value. + + @param[in] other the JSON value to copy + + @post `*this == other` + + @complexity Linear in the size of @a other. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes to any JSON value. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - As postcondition, it holds: `other == basic_json(other)`. + + @liveexample{The following code shows an example for the copy + constructor.,basic_json__basic_json} + + @since version 1.0.0 + */ + basic_json(const basic_json& other) + : m_type(other.m_type) + { + // check of passed value is valid + other.assert_invariant(); + + switch (m_type) + { + case value_t::object: + { + m_value = *other.m_value.object; + break; + } + + case value_t::array: + { + m_value = *other.m_value.array; + break; + } + + case value_t::string: + { + m_value = *other.m_value.string; + break; + } + + case value_t::boolean: + { + m_value = other.m_value.boolean; + break; + } + + case value_t::number_integer: + { + m_value = other.m_value.number_integer; + break; + } + + case value_t::number_unsigned: + { + m_value = other.m_value.number_unsigned; + break; + } + + case value_t::number_float: + { + m_value = other.m_value.number_float; + break; + } + + default: + break; + } + + assert_invariant(); + } + + /*! + @brief move constructor + + Move constructor. Constructs a JSON value with the contents of the given + value @a other using move semantics. It "steals" the resources from @a + other and leaves it as JSON null value. + + @param[in,out] other value to move to this object + + @post `*this` has the same value as @a other before the call. + @post @a other is a JSON null value. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this constructor never throws + exceptions. + + @requirement This function helps `basic_json` satisfying the + [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) + requirements. + + @liveexample{The code below shows the move constructor explicitly called + via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 + */ + basic_json(basic_json&& other) noexcept + : m_type(std::move(other.m_type)), + m_value(std::move(other.m_value)) + { + // check that passed value is valid + other.assert_invariant(); + + // invalidate payload + other.m_type = value_t::null; + other.m_value = {}; + + assert_invariant(); + } + + /*! + @brief copy assignment + + Copy assignment operator. Copies a JSON value via the "copy and swap" + strategy: It is expressed in terms of the copy constructor, destructor, + and the `swap()` member function. + + @param[in] other value to copy from + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + + @liveexample{The code below shows and example for the copy assignment. It + creates a copy of value `a` which is then swapped with `b`. Finally\, the + copy of `a` (which is the null value after the swap) is + destroyed.,basic_json__copyassignment} + + @since version 1.0.0 + */ + reference& operator=(basic_json other) noexcept ( + std::is_nothrow_move_constructible<value_t>::value and + std::is_nothrow_move_assignable<value_t>::value and + std::is_nothrow_move_constructible<json_value>::value and + std::is_nothrow_move_assignable<json_value>::value + ) + { + // check that passed value is valid + other.assert_invariant(); + + using std::swap; + swap(m_type, other.m_type); + swap(m_value, other.m_value); + + assert_invariant(); + return *this; + } + + /*! + @brief destructor + + Destroys the JSON value and frees all allocated memory. + + @complexity Linear. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is linear. + - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 + */ + ~basic_json() + { + assert_invariant(); + m_value.destroy(m_type); + } + + /// @} + + public: + /////////////////////// + // object inspection // + /////////////////////// + + /// @name object inspection + /// Functions to inspect the type of a JSON value. + /// @{ + + /*! + @brief serialization + + Serialization function for JSON values. The function tries to mimic + Python's `json.dumps()` function, and currently supports its @a indent + and @a ensure_ascii parameters. + + @param[in] indent If indent is nonnegative, then array elements and object + members will be pretty-printed with that indent level. An indent level of + `0` will only insert newlines. `-1` (the default) selects the most compact + representation. + @param[in] indent_char The character to use for indentation if @a indent is + greater than `0`. The default is ` ` (space). + @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters + in the output are escaped with \uXXXX sequences, and the result consists + of ASCII characters only. + + @return string containing the serialization of the JSON value + + @complexity Linear. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @liveexample{The following example shows the effect of different @a indent\, + @a indent_char\, and @a ensure_ascii parameters to the result of the + serialization.,dump} + + @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0; indentation character @a indent_char and option + @a ensure_ascii added in version 3.0.0 + */ + string_t dump(const int indent = -1, const char indent_char = ' ', + const bool ensure_ascii = false) const + { + string_t result; + serializer s(detail::output_adapter<char>(result), indent_char); + + if (indent >= 0) + { + s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); + } + else + { + s.dump(*this, false, ensure_ascii, 0); + } + + return result; + } + + /*! + @brief return the type of the JSON value (explicit) + + Return the type of the JSON value as a value from the @ref value_t + enumeration. + + @return the type of the JSON value + Value type | return value + ------------------------- | ------------------------- + null | value_t::null + boolean | value_t::boolean + string | value_t::string + number (integer) | value_t::number_integer + number (unsigned integer) | value_t::number_unsigned + number (foating-point) | value_t::number_float + object | value_t::object + array | value_t::array + discarded | value_t::discarded + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `type()` for all JSON + types.,type} + + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr value_t type() const noexcept + { + return m_type; + } + + /*! + @brief return whether type is primitive + + This function returns true if and only if the JSON type is primitive + (string, number, boolean, or null). + + @return `true` if type is primitive (string, number, boolean, or null), + `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_primitive()` for all JSON + types.,is_primitive} + + @sa @ref is_structured() -- returns whether JSON value is structured + @sa @ref is_null() -- returns whether JSON value is `null` + @sa @ref is_string() -- returns whether JSON value is a string + @sa @ref is_boolean() -- returns whether JSON value is a boolean + @sa @ref is_number() -- returns whether JSON value is a number + + @since version 1.0.0 + */ + constexpr bool is_primitive() const noexcept + { + return is_null() or is_string() or is_boolean() or is_number(); + } + + /*! + @brief return whether type is structured + + This function returns true if and only if the JSON type is structured + (array or object). + + @return `true` if type is structured (array or object), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_structured()` for all JSON + types.,is_structured} + + @sa @ref is_primitive() -- returns whether value is primitive + @sa @ref is_array() -- returns whether value is an array + @sa @ref is_object() -- returns whether value is an object + + @since version 1.0.0 + */ + constexpr bool is_structured() const noexcept + { + return is_array() or is_object(); + } + + /*! + @brief return whether value is null + + This function returns true if and only if the JSON value is null. + + @return `true` if type is null, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_null()` for all JSON + types.,is_null} + + @since version 1.0.0 + */ + constexpr bool is_null() const noexcept + { + return (m_type == value_t::null); + } + + /*! + @brief return whether value is a boolean + + This function returns true if and only if the JSON value is a boolean. + + @return `true` if type is boolean, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_boolean()` for all JSON + types.,is_boolean} + + @since version 1.0.0 + */ + constexpr bool is_boolean() const noexcept + { + return (m_type == value_t::boolean); + } + + /*! + @brief return whether value is a number + + This function returns true if and only if the JSON value is a number. This + includes both integer (signed and unsigned) and floating-point values. + + @return `true` if type is number (regardless whether integer, unsigned + integer or floating-type), `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number()` for all JSON + types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number() const noexcept + { + return is_number_integer() or is_number_float(); + } + + /*! + @brief return whether value is an integer number + + This function returns true if and only if the JSON value is a signed or + unsigned integer number. This excludes floating-point values. + + @return `true` if type is an integer or unsigned integer number, `false` + otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_integer()` for all + JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 + */ + constexpr bool is_number_integer() const noexcept + { + return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is an unsigned integer number + + This function returns true if and only if the JSON value is an unsigned + integer number. This excludes floating-point and signed integer values. + + @return `true` if type is an unsigned integer number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_unsigned()` for all + JSON types.,is_number_unsigned} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_integer() -- check if value is an integer or unsigned + integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 2.0.0 + */ + constexpr bool is_number_unsigned() const noexcept + { + return (m_type == value_t::number_unsigned); + } + + /*! + @brief return whether value is a floating-point number + + This function returns true if and only if the JSON value is a + floating-point number. This excludes signed and unsigned integer values. + + @return `true` if type is a floating-point number, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_number_float()` for all + JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_unsigned() -- check if value is an unsigned integer + number + + @since version 1.0.0 + */ + constexpr bool is_number_float() const noexcept + { + return (m_type == value_t::number_float); + } + + /*! + @brief return whether value is an object + + This function returns true if and only if the JSON value is an object. + + @return `true` if type is object, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_object()` for all JSON + types.,is_object} + + @since version 1.0.0 + */ + constexpr bool is_object() const noexcept + { + return (m_type == value_t::object); + } + + /*! + @brief return whether value is an array + + This function returns true if and only if the JSON value is an array. + + @return `true` if type is array, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_array()` for all JSON + types.,is_array} + + @since version 1.0.0 + */ + constexpr bool is_array() const noexcept + { + return (m_type == value_t::array); + } + + /*! + @brief return whether value is a string + + This function returns true if and only if the JSON value is a string. + + @return `true` if type is string, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_string()` for all JSON + types.,is_string} + + @since version 1.0.0 + */ + constexpr bool is_string() const noexcept + { + return (m_type == value_t::string); + } + + /*! + @brief return whether value is discarded + + This function returns true if and only if the JSON value was discarded + during parsing with a callback function (see @ref parser_callback_t). + + @note This function will always be `false` for JSON values after parsing. + That is, discarded values can only occur during parsing, but will be + removed when inside a structured value or replaced by null in other cases. + + @return `true` if type is discarded, `false` otherwise. + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies `is_discarded()` for all JSON + types.,is_discarded} + + @since version 1.0.0 + */ + constexpr bool is_discarded() const noexcept + { + return (m_type == value_t::discarded); + } + + /*! + @brief return the type of the JSON value (implicit) + + Implicitly return the type of the JSON value as a value from the @ref + value_t enumeration. + + @return the type of the JSON value + + @complexity Constant. + + @exceptionsafety No-throw guarantee: this member function never throws + exceptions. + + @liveexample{The following code exemplifies the @ref value_t operator for + all JSON types.,operator__value_t} + + @sa @ref type() -- return the type of the JSON value (explicit) + @sa @ref type_name() -- return the type as string + + @since version 1.0.0 + */ + constexpr operator value_t() const noexcept + { + return m_type; + } + + /// @} + + private: + ////////////////// + // value access // + ////////////////// + + /// get a boolean (explicit) + boolean_t get_impl(boolean_t* /*unused*/) const + { + if (JSON_LIKELY(is_boolean())) + { + return m_value.boolean; + } + + JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); + } + + /// get a pointer to the value (object) + object_t* get_impl_ptr(object_t* /*unused*/) noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (object) + constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept + { + return is_object() ? m_value.object : nullptr; + } + + /// get a pointer to the value (array) + array_t* get_impl_ptr(array_t* /*unused*/) noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (array) + constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept + { + return is_array() ? m_value.array : nullptr; + } + + /// get a pointer to the value (string) + string_t* get_impl_ptr(string_t* /*unused*/) noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (string) + constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept + { + return is_string() ? m_value.string : nullptr; + } + + /// get a pointer to the value (boolean) + boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (boolean) + constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept + { + return is_boolean() ? &m_value.boolean : nullptr; + } + + /// get a pointer to the value (integer number) + number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (integer number) + constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept + { + return is_number_integer() ? &m_value.number_integer : nullptr; + } + + /// get a pointer to the value (unsigned number) + number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (unsigned number) + constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept + { + return is_number_unsigned() ? &m_value.number_unsigned : nullptr; + } + + /// get a pointer to the value (floating-point number) + number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /// get a pointer to the value (floating-point number) + constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept + { + return is_number_float() ? &m_value.number_float : nullptr; + } + + /*! + @brief helper function to implement get_ref() + + This function helps to implement get_ref() without code duplication for + const and non-const overloads + + @tparam ThisType will be deduced as `basic_json` or `const basic_json` + + @throw type_error.303 if ReferenceType does not match underlying value + type of the current JSON + */ + template<typename ReferenceType, typename ThisType> + static ReferenceType get_ref_impl(ThisType& obj) + { + // delegate the call to get_ptr<>() + auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>(); + + if (JSON_LIKELY(ptr != nullptr)) + { + return *ptr; + } + + JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); + } + + public: + /// @name value access + /// Direct access to the stored value of a JSON value. + /// @{ + + /*! + @brief get special-case overload + + This overloads avoids a lot of template boilerplate, it can be seen as the + identity method + + @tparam BasicJsonType == @ref basic_json + + @return a copy of *this + + @complexity Constant. + + @since version 2.1.0 + */ + template < + typename BasicJsonType, + detail::enable_if_t<std::is_same<typename std::remove_const<BasicJsonType>::type, + basic_json_t>::value, + int> = 0 > + basic_json get() const + { + return *this; + } + + /*! + @brief get a value (explicit) + + Explicit type conversion between the JSON value and a compatible value + which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer<ValueType> + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json, + - @ref json_serializer<ValueType> has a `from_json()` method of the form + `void from_json(const basic_json&, ValueType&)`, and + - @ref json_serializer<ValueType> does not have a `from_json()` method of + the form `ValueType from_json(const basic_json&)` + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer<ValueType> `from_json()` method throws + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector<short>`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map<std::string\, + json>`.,get__ValueType_const} + + @since version 2.1.0 + */ + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t < + not std::is_same<basic_json_t, ValueType>::value and + detail::has_from_json<basic_json_t, ValueType>::value and + not detail::has_non_default_from_json<basic_json_t, ValueType>::value, + int > = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>()))) + { + // we cannot static_assert on ValueTypeCV being non-const, because + // there is support for get<const basic_json_t>(), which is why we + // still need the uncvref + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + static_assert(std::is_default_constructible<ValueType>::value, + "types must be DefaultConstructible when used with get()"); + + ValueType ret; + JSONSerializer<ValueType>::from_json(*this, ret); + return ret; + } + + /*! + @brief get a value (explicit); special case + + Explicit type conversion between the JSON value and a compatible value + which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) + and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). + The value is converted by calling the @ref json_serializer<ValueType> + `from_json()` method. + + The function is equivalent to executing + @code {.cpp} + return JSONSerializer<ValueTypeCV>::from_json(*this); + @endcode + + This overloads is chosen if: + - @a ValueType is not @ref basic_json and + - @ref json_serializer<ValueType> has a `from_json()` method of the form + `ValueType from_json(const basic_json&)` + + @note If @ref json_serializer<ValueType> has both overloads of + `from_json()`, this one is chosen. + + @tparam ValueTypeCV the provided value type + @tparam ValueType the returned value type + + @return copy of the JSON value, converted to @a ValueType + + @throw what @ref json_serializer<ValueType> `from_json()` method throws + + @since version 2.1.0 + */ + template < + typename ValueTypeCV, + typename ValueType = detail::uncvref_t<ValueTypeCV>, + detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and + detail::has_non_default_from_json<basic_json_t, + ValueType>::value, int> = 0 > + ValueType get() const noexcept(noexcept( + JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>()))) + { + static_assert(not std::is_reference<ValueTypeCV>::value, + "get() cannot be used with reference types, you might want to use get_ref()"); + return JSONSerializer<ValueTypeCV>::from_json(*this); + } + + /*! + @brief get a pointer value (explicit) + + Explicit pointer access to the internally stored JSON value. No copies are + made. + + @warning The pointer becomes invalid if the underlying JSON object + changes. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get__PointerType} + + @sa @ref get_ptr() for explicit pointer-member access + + @since version 1.0.0 + */ + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + PointerType get() noexcept + { + // delegate the call to get_ptr + return get_ptr<PointerType>(); + } + + /*! + @brief get a pointer value (explicit) + @copydoc get() + */ + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + constexpr const PointerType get() const noexcept + { + // delegate the call to get_ptr + return get_ptr<PointerType>(); + } + + /*! + @brief get a pointer value (implicit) + + Implicit pointer access to the internally stored JSON value. No copies are + made. + + @warning Writing data to the pointee of the result yields an undefined + state. + + @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref + object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, + @ref number_unsigned_t, or @ref number_float_t. Enforced by a static + assertion. + + @return pointer to the internally stored JSON value if the requested + pointer type @a PointerType fits to the JSON value; `nullptr` otherwise + + @complexity Constant. + + @liveexample{The example below shows how pointers to internal values of a + JSON value can be requested. Note that no type conversions are made and a + `nullptr` is returned if the value and the requested pointer type does not + match.,get_ptr} + + @since version 1.0.0 + */ + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value, int>::type = 0> + PointerType get_ptr() noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const<typename + std::remove_pointer<typename + std::remove_const<PointerType>::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same<object_t, pointee_t>::value + or std::is_same<array_t, pointee_t>::value + or std::is_same<string_t, pointee_t>::value + or std::is_same<boolean_t, pointee_t>::value + or std::is_same<number_integer_t, pointee_t>::value + or std::is_same<number_unsigned_t, pointee_t>::value + or std::is_same<number_float_t, pointee_t>::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + /*! + @brief get a pointer value (implicit) + @copydoc get_ptr() + */ + template<typename PointerType, typename std::enable_if< + std::is_pointer<PointerType>::value and + std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0> + constexpr const PointerType get_ptr() const noexcept + { + // get the type of the PointerType (remove pointer and const) + using pointee_t = typename std::remove_const<typename + std::remove_pointer<typename + std::remove_const<PointerType>::type>::type>::type; + // make sure the type matches the allowed types + static_assert( + std::is_same<object_t, pointee_t>::value + or std::is_same<array_t, pointee_t>::value + or std::is_same<string_t, pointee_t>::value + or std::is_same<boolean_t, pointee_t>::value + or std::is_same<number_integer_t, pointee_t>::value + or std::is_same<number_unsigned_t, pointee_t>::value + or std::is_same<number_float_t, pointee_t>::value + , "incompatible pointer type"); + + // delegate the call to get_impl_ptr<>() const + return get_impl_ptr(static_cast<PointerType>(nullptr)); + } + + /*! + @brief get a reference value (implicit) + + Implicit reference access to the internally stored JSON value. No copies + are made. + + @warning Writing data to the referee of the result yields an undefined + state. + + @tparam ReferenceType reference type; must be a reference to @ref array_t, + @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or + @ref number_float_t. Enforced by static assertion. + + @return reference to the internally stored JSON value if the requested + reference type @a ReferenceType fits to the JSON value; throws + type_error.303 otherwise + + @throw type_error.303 in case passed type @a ReferenceType is incompatible + with the stored JSON value; see example below + + @complexity Constant. + + @liveexample{The example shows several calls to `get_ref()`.,get_ref} + + @since version 1.1.0 + */ + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value, int>::type = 0> + ReferenceType get_ref() + { + // delegate call to get_ref_impl + return get_ref_impl<ReferenceType>(*this); + } + + /*! + @brief get a reference value (implicit) + @copydoc get_ref() + */ + template<typename ReferenceType, typename std::enable_if< + std::is_reference<ReferenceType>::value and + std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0> + ReferenceType get_ref() const + { + // delegate call to get_ref_impl + return get_ref_impl<ReferenceType>(*this); + } + + /*! + @brief get a value (implicit) + + Implicit type conversion between the JSON value and a compatible value. + The call is realized by calling @ref get() const. + + @tparam ValueType non-pointer type compatible to the JSON value, for + instance `int` for JSON integer numbers, `bool` for JSON booleans, or + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. + + @return copy of the JSON value, converted to type @a ValueType + + @throw type_error.302 in case passed type @a ValueType is incompatible + to the JSON value type (e.g., the JSON value is of type boolean, but a + string is requested); see example below + + @complexity Linear in the size of the JSON value. + + @liveexample{The example below shows several conversions from JSON values + to other types. There a few things to note: (1) Floating-point numbers can + be converted to integers\, (2) A JSON array can be converted to a standard + `std::vector<short>`\, (3) A JSON object can be converted to C++ + associative containers such as `std::unordered_map<std::string\, + json>`.,operator__ValueType} + + @since version 1.0.0 + */ + template < typename ValueType, typename std::enable_if < + not std::is_pointer<ValueType>::value and + not std::is_same<ValueType, detail::json_ref<basic_json>>::value and + not std::is_same<ValueType, typename string_t::value_type>::value +#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 + and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value +#endif +#if defined(JSON_HAS_CPP_17) + and not std::is_same<ValueType, typename std::string_view>::value +#endif + , int >::type = 0 > + operator ValueType() const + { + // delegate the call to get<>() const + return get<ValueType>(); + } + + /// @} + + + //////////////////// + // element access // + //////////////////// + + /// @name element access + /// Access to the JSON value. + /// @{ + + /*! + @brief access specified array element with bounds checking + + Returns a reference to the element at specified location @a idx, with + bounds checking. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__size_type} + */ + reference at(size_type idx) + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element with bounds checking + + Returns a const reference to the element at specified location @a idx, + with bounds checking. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.304 if the JSON value is not an array; in this case, + calling `at` with an index makes no sense. See example below. + @throw out_of_range.401 if the index @a idx is out of range of the array; + that is, `idx >= size()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 1.0.0 + + @liveexample{The example below shows how array elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__size_type_const} + */ + const_reference at(size_type idx) const + { + // at only works for arrays + if (JSON_LIKELY(is_array())) + { + JSON_TRY + { + return m_value.array->at(idx); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a reference to the element at with specified key @a key, with + bounds checking. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read and + written using `at()`. It also demonstrates the different exceptions that + can be thrown.,at__object_t_key_type} + */ + reference at(const typename object_t::key_type& key) + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified object element with bounds checking + + Returns a const reference to the element at with specified key @a key, + with bounds checking. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @throw type_error.304 if the JSON value is not an object; in this case, + calling `at` with a key makes no sense. See example below. + @throw out_of_range.403 if the key @a key is is not stored in the object; + that is, `find(key) == end()`. See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Logarithmic in the size of the container. + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + + @liveexample{The example below shows how object elements can be read using + `at()`. It also demonstrates the different exceptions that can be thrown., + at__object_t_key_type_const} + */ + const_reference at(const typename object_t::key_type& key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + JSON_TRY + { + return m_value.object->at(key); + } + JSON_CATCH (std::out_of_range&) + { + // create better exception explanation + JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); + } + } + else + { + JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); + } + } + + /*! + @brief access specified array element + + Returns a reference to the element at specified location @a idx. + + @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), + then the array is silently filled up with `null` values to make `idx` a + valid reference to the last stored element. + + @param[in] idx index of the element to access + + @return reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array or null; in that + cases, using the [] operator with an index makes no sense. + + @complexity Constant if @a idx is in the range of the array. Otherwise + linear in `idx - size()`. + + @liveexample{The example below shows how array elements can be read and + written using `[]` operator. Note the addition of `null` + values.,operatorarray__size_type} + + @since version 1.0.0 + */ + reference operator[](size_type idx) + { + // implicitly convert null value to an empty array + if (is_null()) + { + m_type = value_t::array; + m_value.array = create<array_t>(); + assert_invariant(); + } + + // operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + // fill up array with null values if given idx is outside range + if (idx >= m_value.array->size()) + { + m_value.array->insert(m_value.array->end(), + idx - m_value.array->size() + 1, + basic_json()); + } + + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified array element + + Returns a const reference to the element at specified location @a idx. + + @param[in] idx index of the element to access + + @return const reference to the element at index @a idx + + @throw type_error.305 if the JSON value is not an array; in that cases, + using the [] operator with an index makes no sense. + + @complexity Constant. + + @liveexample{The example below shows how array elements can be read using + the `[]` operator.,operatorarray__size_type_const} + + @since version 1.0.0 + */ + const_reference operator[](size_type idx) const + { + // const operator[] only works for arrays + if (JSON_LIKELY(is_array())) + { + return m_value.array->operator[](idx); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + reference operator[](const typename object_t::key_type& key) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + // operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that cases, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 + */ + const_reference operator[](const typename object_t::key_type& key) const + { + // const operator[] only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element + + Returns a reference to the element at with specified key @a key. + + @note If @a key is not found in the object, then it is silently added to + the object and filled with a `null` value to make `key` a valid reference. + In case the value was `null` before, it is converted to an object. + + @param[in] key key of the element to access + + @return reference to the element at key @a key + + @throw type_error.305 if the JSON value is not an object or null; in that + cases, using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read and + written using the `[]` operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template<typename T> + reference operator[](T* key) + { + // implicitly convert null to object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // at only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->operator[](key); + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief read-only access specified object element + + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. + + @param[in] key key of the element to access + + @return const reference to the element at key @a key + + @pre The element with key @a key must exist. **This precondition is + enforced with an assertion.** + + @throw type_error.305 if the JSON value is not an object; in that cases, + using the [] operator with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be read using + the `[]` operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.1.0 + */ + template<typename T> + const_reference operator[](T* key) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + + JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); + } + + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an objec; in that cases, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template<class ValueType, typename std::enable_if< + std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> + ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + + return default_value; + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); + } + + /*! + @brief access specified object element via JSON Pointer with default value + + Returns either a copy of an object's element at the specified key @a key + or a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(ptr); + } catch(out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const json_pointer&), this function does not throw + if the given key @a key was not found. + + @param[in] ptr a JSON pointer to the element to access + @param[in] default_value the value to return if @a ptr found no value + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw type_error.306 if the JSON value is not an objec; in that cases, + using `value()` with a key makes no sense. + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value_ptr} + + @sa @ref operator[](const json_pointer&) for unchecked access by reference + + @since version 2.0.2 + */ + template<class ValueType, typename std::enable_if< + std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> + ValueType value(const json_pointer& ptr, const ValueType& default_value) const + { + // at only works for objects + if (JSON_LIKELY(is_object())) + { + // if pointer resolves a value, return it or use default value + JSON_TRY + { + return ptr.get_checked(this); + } + JSON_CATCH (out_of_range&) + { + return default_value; + } + } + + JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value(const json_pointer&, ValueType) const + */ + string_t value(const json_pointer& ptr, const char* default_value) const + { + return value(ptr, string_t(default_value)); + } + + /*! + @brief access the first element + + Returns a reference to the first element in the container. For a JSON + container `c`, the expression `c.front()` is equivalent to `*c.begin()`. + + @return In case of a structured type (array or object), a reference to the + first element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on `null` value + + @liveexample{The following code shows an example for `front()`.,front} + + @sa @ref back() -- access the last element + + @since version 1.0.0 + */ + reference front() + { + return *begin(); + } + + /*! + @copydoc basic_json::front() + */ + const_reference front() const + { + return *cbegin(); + } + + /*! + @brief access the last element + + Returns a reference to the last element in the container. For a JSON + container `c`, the expression `c.back()` is equivalent to + @code {.cpp} + auto tmp = c.end(); + --tmp; + return *tmp; + @endcode + + @return In case of a structured type (array or object), a reference to the + last element is returned. In case of number, string, or boolean values, a + reference to the value is returned. + + @complexity Constant. + + @pre The JSON value must not be `null` (would throw `std::out_of_range`) + or an empty array or object (undefined behavior, **guarded by + assertions**). + @post The JSON value remains unchanged. + + @throw invalid_iterator.214 when called on a `null` value. See example + below. + + @liveexample{The following code shows an example for `back()`.,back} + + @sa @ref front() -- access the first element + + @since version 1.0.0 + */ + reference back() + { + auto tmp = end(); + --tmp; + return *tmp; + } + + /*! + @copydoc basic_json::back() + */ + const_reference back() const + { + auto tmp = cend(); + --tmp; + return *tmp; + } + + /*! + @brief remove element given an iterator + + Removes the element specified by iterator @a pos. The iterator @a pos must + be valid and dereferenceable. Thus the `end()` iterator (which is valid, + but is not dereferenceable) cannot be used as a value for @a pos. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] pos iterator to the element to remove + @return Iterator following the last removed element. If the iterator @a + pos refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.202 if called on an iterator which does not belong + to the current JSON value; example: `"iterator does not fit current + value"` + @throw invalid_iterator.205 if called on a primitive type with invalid + iterator (i.e., any iterator which is not `begin()`); example: `"iterator + out of range"` + + @complexity The complexity depends on the type: + - objects: amortized constant + - arrays: linear in distance between @a pos and the end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType} + + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template<class IteratorType, typename std::enable_if< + std::is_same<IteratorType, typename basic_json_t::iterator>::value or + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type + = 0> + IteratorType erase(IteratorType pos) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != pos.m_object)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) + { + JSON_THROW(invalid_iterator::create(205, "iterator out of range")); + } + + if (is_string()) + { + AllocatorType<string_t> alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove elements given an iterator range + + Removes the element specified by the range `[first; last)`. The iterator + @a first does not need to be dereferenceable if `first == last`: erasing + an empty range is a no-op. + + If called on a primitive type other than `null`, the resulting JSON value + will be `null`. + + @param[in] first iterator to the beginning of the range to remove + @param[in] last iterator past the end of the range to remove + @return Iterator following the last removed element. If the iterator @a + second refers to the last element, the `end()` iterator is returned. + + @tparam IteratorType an @ref iterator or @ref const_iterator + + @post Invalidates iterators and references at or after the point of the + erase, including the `end()` iterator. + + @throw type_error.307 if called on a `null` value; example: `"cannot use + erase() with null"` + @throw invalid_iterator.203 if called on iterators which does not belong + to the current JSON value; example: `"iterators do not fit current value"` + @throw invalid_iterator.204 if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` + + @complexity The complexity depends on the type: + - objects: `log(size()) + std::distance(first, last)` + - arrays: linear in the distance between @a first and @a last, plus linear + in the distance between @a last and end of the container + - strings: linear in the length of the string + - other types: constant + + @liveexample{The example shows the result of `erase()` for different JSON + types.,erase__IteratorType_IteratorType} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + template<class IteratorType, typename std::enable_if< + std::is_same<IteratorType, typename basic_json_t::iterator>::value or + std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type + = 0> + IteratorType erase(IteratorType first, IteratorType last) + { + // make sure iterator fits the current value + if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) + { + JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); + } + + IteratorType result = end(); + + switch (m_type) + { + case value_t::boolean: + case value_t::number_float: + case value_t::number_integer: + case value_t::number_unsigned: + case value_t::string: + { + if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() + or not last.m_it.primitive_iterator.is_end())) + { + JSON_THROW(invalid_iterator::create(204, "iterators out of range")); + } + + if (is_string()) + { + AllocatorType<string_t> alloc; + alloc.destroy(m_value.string); + alloc.deallocate(m_value.string, 1); + m_value.string = nullptr; + } + + m_type = value_t::null; + assert_invariant(); + break; + } + + case value_t::object: + { + result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, + last.m_it.object_iterator); + break; + } + + case value_t::array: + { + result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, + last.m_it.array_iterator); + break; + } + + default: + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + return result; + } + + /*! + @brief remove element from a JSON object given a key + + Removes elements from a JSON object with the key value @a key. + + @param[in] key value of the elements to remove + + @return Number of elements removed. If @a ObjectType is the default + `std::map` type, the return value will always be `0` (@a key was not + found) or `1` (@a key was found). + + @post References and iterators to the erased elements are invalidated. + Other references and iterators are not affected. + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + + @complexity `log(size()) + count(key)` + + @liveexample{The example shows the effect of `erase()`.,erase__key_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const size_type) -- removes the element from an array at + the given index + + @since version 1.0.0 + */ + size_type erase(const typename object_t::key_type& key) + { + // this erase only works for objects + if (JSON_LIKELY(is_object())) + { + return m_value.object->erase(key); + } + + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + + /*! + @brief remove element from a JSON array given an index + + Removes element from a JSON array at the index @a idx. + + @param[in] idx index of the element to remove + + @throw type_error.307 when called on a type other than JSON object; + example: `"cannot use erase() with null"` + @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 + is out of range"` + + @complexity Linear in distance between @a idx and the end of the container. + + @liveexample{The example shows the effect of `erase()`.,erase__size_type} + + @sa @ref erase(IteratorType) -- removes the element at a given position + @sa @ref erase(IteratorType, IteratorType) -- removes the elements in + the given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 + */ + void erase(const size_type idx) + { + // this erase only works for arrays + if (JSON_LIKELY(is_array())) + { + if (JSON_UNLIKELY(idx >= size())) + { + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + + m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx)); + } + else + { + JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); + } + } + + /// @} + + + //////////// + // lookup // + //////////// + + /// @name lookup + /// @{ + + /*! + @brief find an element in a JSON object + + Finds an element in a JSON object with key equivalent to @a key. If the + element is not found or the JSON value is not an object, end() is + returned. + + @note This method always returns @ref end() when executed on a JSON type + that is not an object. + + @param[in] key key value of the element to search for. + + @return Iterator to an element with key equivalent to @a key. If no such + element is found or the JSON value is not an object, past-the-end (see + @ref end()) iterator is returned. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `find()` is used.,find__key_type} + + @since version 1.0.0 + */ + template<typename KeyT> + iterator find(KeyT&& key) + { + auto result = end(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + } + + return result; + } + + /*! + @brief find an element in a JSON object + @copydoc find(KeyT&&) + */ + template<typename KeyT> + const_iterator find(KeyT&& key) const + { + auto result = cend(); + + if (is_object()) + { + result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); + } + + return result; + } + + /*! + @brief returns the number of occurrences of a key in a JSON object + + Returns the number of elements with key @a key. If ObjectType is the + default `std::map` type, the return value will always be `0` (@a key was + not found) or `1` (@a key was found). + + @note This method always returns `0` when executed on a JSON type that is + not an object. + + @param[in] key key value of the element to count + + @return Number of elements with key @a key. If the JSON value is not an + object, the return value will be `0`. + + @complexity Logarithmic in the size of the JSON object. + + @liveexample{The example shows how `count()` is used.,count} + + @since version 1.0.0 + */ + template<typename KeyT> + size_type count(KeyT&& key) const + { + // return 0 for all nonobject types + return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0; + } + + /// @} + + + /////////////// + // iterators // + /////////////// + + /// @name iterators + /// @{ + + /*! + @brief returns an iterator to the first element + + Returns an iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `begin()`.,begin} + + @sa @ref cbegin() -- returns a const iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + iterator begin() noexcept + { + iterator result(this); + result.set_begin(); + return result; + } + + /*! + @copydoc basic_json::cbegin() + */ + const_iterator begin() const noexcept + { + return cbegin(); + } + + /*! + @brief returns a const iterator to the first element + + Returns a const iterator to the first element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator to the first element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast<const basic_json&>(*this).begin()`. + + @liveexample{The following code shows an example for `cbegin()`.,cbegin} + + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref end() -- returns an iterator to the end + @sa @ref cend() -- returns a const iterator to the end + + @since version 1.0.0 + */ + const_iterator cbegin() const noexcept + { + const_iterator result(this); + result.set_begin(); + return result; + } + + /*! + @brief returns an iterator to one past the last element + + Returns an iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + + @liveexample{The following code shows an example for `end()`.,end} + + @sa @ref cend() -- returns a const iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + iterator end() noexcept + { + iterator result(this); + result.set_end(); + return result; + } + + /*! + @copydoc basic_json::cend() + */ + const_iterator end() const noexcept + { + return cend(); + } + + /*! + @brief returns a const iterator to one past the last element + + Returns a const iterator to one past the last element. + + @image html range-begin-end.svg "Illustration from cppreference.com" + + @return const iterator one past the last element + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast<const basic_json&>(*this).end()`. + + @liveexample{The following code shows an example for `cend()`.,cend} + + @sa @ref end() -- returns an iterator to the end + @sa @ref begin() -- returns an iterator to the beginning + @sa @ref cbegin() -- returns a const iterator to the beginning + + @since version 1.0.0 + */ + const_iterator cend() const noexcept + { + const_iterator result(this); + result.set_end(); + return result; + } + + /*! + @brief returns an iterator to the reverse-beginning + + Returns an iterator to the reverse-beginning; that is, the last element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(end())`. + + @liveexample{The following code shows an example for `rbegin()`.,rbegin} + + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + /*! + @copydoc basic_json::crbegin() + */ + const_reverse_iterator rbegin() const noexcept + { + return crbegin(); + } + + /*! + @brief returns an iterator to the reverse-end + + Returns an iterator to the reverse-end; that is, one before the first + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `reverse_iterator(begin())`. + + @liveexample{The following code shows an example for `rend()`.,rend} + + @sa @ref crend() -- returns a const reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + /*! + @copydoc basic_json::crend() + */ + const_reverse_iterator rend() const noexcept + { + return crend(); + } + + /*! + @brief returns a const reverse iterator to the last element + + Returns a const iterator to the reverse-beginning; that is, the last + element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`. + + @liveexample{The following code shows an example for `crbegin()`.,crbegin} + + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref crend() -- returns a const reverse iterator to the end + + @since version 1.0.0 + */ + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(cend()); + } + + /*! + @brief returns a const reverse iterator to one before the first + + Returns a const reverse iterator to the reverse-end; that is, one before + the first element. + + @image html range-rbegin-rend.svg "Illustration from cppreference.com" + + @complexity Constant. + + @requirement This function helps `basic_json` satisfying the + [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) + requirements: + - The complexity is constant. + - Has the semantics of `const_cast<const basic_json&>(*this).rend()`. + + @liveexample{The following code shows an example for `crend()`.,crend} + + @sa @ref rend() -- returns a reverse iterator to the end + @sa @ref rbegin() -- returns a reverse iterator to the beginning + @sa @ref crbegin() -- returns a const reverse iterator to the beginning + + @since version 1.0.0 + */ + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(cbegin()); + } + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a + reference to the JSON values is returned, so there is no access to the + underlying iterator. + + @liveexample{The following code shows how the wrapper is used,iterator_wrapper} + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy<iterator> iterator_wrapper(reference cont) + { + return iteration_proxy<iterator>(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy<const_iterator> iterator_wrapper(const_reference cont) + { + return iteration_proxy<const_iterator>(cont); + } + + /// @} + + + ////////////// + // capacity // + ////////////// + + /// @name capacity + /// @{ + + /*! + @brief checks whether the container is empty. + + Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `true` + boolean | `false` + string | `false` + number | `false` + object | result of function `object_t::empty()` + array | result of function `array_t::empty()` + + @liveexample{The following code uses `empty()` to check if a JSON + object contains any elements.,empty} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `empty()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return whether a string stored as JSON value + is empty - it returns whether the JSON container itself is empty which is + false in the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `begin() == end()`. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + bool empty() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return true; + } + + case value_t::array: + { + // delegate call to array_t::empty() + return m_value.array->empty(); + } + + case value_t::object: + { + // delegate call to object_t::empty() + return m_value.object->empty(); + } + + default: + { + // all other types are nonempty + return false; + } + } + } + + /*! + @brief returns the number of elements + + Returns the number of elements in a JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` + boolean | `1` + string | `1` + number | `1` + object | result of function object_t::size() + array | result of function array_t::size() + + @liveexample{The following code calls `size()` on the different value + types.,size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their size() functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @note This function does not return the length of a string stored as JSON + value - it returns the number of elements in the JSON value which is 1 in + the case of a string. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of `std::distance(begin(), end())`. + + @sa @ref empty() -- checks whether the container is empty + @sa @ref max_size() -- returns the maximal number of elements + + @since version 1.0.0 + */ + size_type size() const noexcept + { + switch (m_type) + { + case value_t::null: + { + // null values are empty + return 0; + } + + case value_t::array: + { + // delegate call to array_t::size() + return m_value.array->size(); + } + + case value_t::object: + { + // delegate call to object_t::size() + return m_value.object->size(); + } + + default: + { + // all other types have size 1 + return 1; + } + } + } + + /*! + @brief returns the maximum possible number of elements + + Returns the maximum number of elements a JSON value is able to hold due to + system or library implementation limitations, i.e. `std::distance(begin(), + end())` for the JSON value. + + @return The return value depends on the different types and is + defined as follows: + Value type | return value + ----------- | ------------- + null | `0` (same as `size()`) + boolean | `1` (same as `size()`) + string | `1` (same as `size()`) + number | `1` (same as `size()`) + object | result of function `object_t::max_size()` + array | result of function `array_t::max_size()` + + @liveexample{The following code calls `max_size()` on the different value + types. Note the output is implementation specific.,max_size} + + @complexity Constant, as long as @ref array_t and @ref object_t satisfy + the Container concept; that is, their `max_size()` functions have constant + complexity. + + @iterators No changes. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @requirement This function helps `basic_json` satisfying the + [Container](http://en.cppreference.com/w/cpp/concept/Container) + requirements: + - The complexity is constant. + - Has the semantics of returning `b.size()` where `b` is the largest + possible JSON value. + + @sa @ref size() -- returns the number of elements + + @since version 1.0.0 + */ + size_type max_size() const noexcept + { + switch (m_type) + { + case value_t::array: + { + // delegate call to array_t::max_size() + return m_value.array->max_size(); + } + + case value_t::object: + { + // delegate call to object_t::max_size() + return m_value.object->max_size(); + } + + default: + { + // all other types have max_size() == size() + return size(); + } + } + } + + /// @} + + + /////////////// + // modifiers // + /////////////// + + /// @name modifiers + /// @{ + + /*! + @brief clears the contents + + Clears the content of a JSON value and resets it to the default value as + if @ref basic_json(value_t) would have been called with the current value + type from @ref type(): + + Value type | initial value + ----------- | ------------- + null | `null` + boolean | `false` + string | `""` + number | `0` + object | `{}` + array | `[]` + + @post Has the same effect as calling + @code {.cpp} + *this = basic_json(type()); + @endcode + + @liveexample{The example below shows the effect of `clear()` to different + JSON types.,clear} + + @complexity Linear in the size of the JSON value. + + @iterators All iterators, pointers and references related to this container + are invalidated. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @sa @ref basic_json(value_t) -- constructor that creates an object with the + same value than calling `clear()` + + @since version 1.0.0 + */ + void clear() noexcept + { + switch (m_type) + { + case value_t::number_integer: + { + m_value.number_integer = 0; + break; + } + + case value_t::number_unsigned: + { + m_value.number_unsigned = 0; + break; + } + + case value_t::number_float: + { + m_value.number_float = 0.0; + break; + } + + case value_t::boolean: + { + m_value.boolean = false; + break; + } + + case value_t::string: + { + m_value.string->clear(); + break; + } + + case value_t::array: + { + m_value.array->clear(); + break; + } + + case value_t::object: + { + m_value.object->clear(); + break; + } + + default: + break; + } + } + + /*! + @brief add an object to an array + + Appends the given element @a val to the end of the JSON value. If the + function is called on a JSON null value, an empty array is created before + appending @a val. + + @param[in] val the value to add to the JSON array + + @throw type_error.308 when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON array. Note how the `null` value was silently + converted to a JSON array.,push_back} + + @since version 1.0.0 + */ + void push_back(basic_json&& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (move semantics) + m_value.array->push_back(std::move(val)); + // invalidate object + val.m_type = value_t::null; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(basic_json&& val) + { + push_back(std::move(val)); + return *this; + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + void push_back(const basic_json& val) + { + // push_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array + m_value.array->push_back(val); + } + + /*! + @brief add an object to an array + @copydoc push_back(basic_json&&) + */ + reference operator+=(const basic_json& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + Inserts the given element @a val to the JSON object. If the function is + called on a JSON null value, an empty object is created before inserting + @a val. + + @param[in] val the value to add to the JSON object + + @throw type_error.308 when called on a type other than JSON object or + null; example: `"cannot use push_back() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `push_back()` and `+=` can be used to + add elements to a JSON object. Note how the `null` value was silently + converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 + */ + void push_back(const typename object_t::value_type& val) + { + // push_back only works for null objects or objects + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array + m_value.object->insert(val); + } + + /*! + @brief add an object to an object + @copydoc push_back(const typename object_t::value_type&) + */ + reference operator+=(const typename object_t::value_type& val) + { + push_back(val); + return *this; + } + + /*! + @brief add an object to an object + + This function allows to use `push_back` with an initializer list. In case + + 1. the current value is an object, + 2. the initializer list @a init contains only two elements, and + 3. the first element of @a init is a string, + + @a init is converted into an object element and added using + @ref push_back(const typename object_t::value_type&). Otherwise, @a init + is converted to a JSON value and added using @ref push_back(basic_json&&). + + @param[in] init an initializer list + + @complexity Linear in the size of the initializer list @a init. + + @note This function is required to resolve an ambiguous overload error, + because pairs like `{"key", "value"}` can be both interpreted as + `object_t::value_type` or `std::initializer_list<basic_json>`, see + https://github.com/nlohmann/json/issues/235 for more information. + + @liveexample{The example shows how initializer lists are treated as + objects when possible.,push_back__initializer_list} + */ + void push_back(initializer_list_t init) + { + if (is_object() and init.size() == 2 and (*init.begin())->is_string()) + { + basic_json&& key = init.begin()->moved_or_copied(); + push_back(typename object_t::value_type( + std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied())); + } + else + { + push_back(basic_json(init)); + } + } + + /*! + @brief add an object to an object + @copydoc push_back(initializer_list_t) + */ + reference operator+=(initializer_list_t init) + { + push_back(init); + return *this; + } + + /*! + @brief add an object to an array + + Creates a JSON value from the passed parameters @a args to the end of the + JSON value. If the function is called on a JSON null value, an empty array + is created before appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @throw type_error.311 when called on a type other than JSON array or + null; example: `"cannot use emplace_back() with number"` + + @complexity Amortized constant. + + @liveexample{The example shows how `push_back()` can be used to add + elements to a JSON array. Note how the `null` value was silently converted + to a JSON array.,emplace_back} + + @since version 2.0.8 + */ + template<class... Args> + void emplace_back(Args&& ... args) + { + // emplace_back only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_array()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); + } + + // transform null object into an array + if (is_null()) + { + m_type = value_t::array; + m_value = value_t::array; + assert_invariant(); + } + + // add element to array (perfect forwarding) + m_value.array->emplace_back(std::forward<Args>(args)...); + } + + /*! + @brief add an object to an object if key does not exist + + Inserts a new element into a JSON object constructed in-place with the + given @a args if there is no element with the key in the container. If the + function is called on a JSON null value, an empty object is created before + appending the value created from @a args. + + @param[in] args arguments to forward to a constructor of @ref basic_json + @tparam Args compatible types to create a @ref basic_json object + + @return a pair consisting of an iterator to the inserted element, or the + already-existing element if no insertion happened, and a bool + denoting whether the insertion took place. + + @throw type_error.311 when called on a type other than JSON object or + null; example: `"cannot use emplace() with number"` + + @complexity Logarithmic in the size of the container, O(log(`size()`)). + + @liveexample{The example shows how `emplace()` can be used to add elements + to a JSON object. Note how the `null` value was silently converted to a + JSON object. Further note how no value is added if there was already one + value stored with the same key.,emplace} + + @since version 2.0.8 + */ + template<class... Args> + std::pair<iterator, bool> emplace(Args&& ... args) + { + // emplace only works for null objects or arrays + if (JSON_UNLIKELY(not(is_null() or is_object()))) + { + JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); + } + + // transform null object into an object + if (is_null()) + { + m_type = value_t::object; + m_value = value_t::object; + assert_invariant(); + } + + // add element to array (perfect forwarding) + auto res = m_value.object->emplace(std::forward<Args>(args)...); + // create result iterator and set iterator to the result of emplace + auto it = begin(); + it.m_it.object_iterator = res.first; + + // return pair of iterator and boolean + return {it, res.second}; + } + + /*! + @brief inserts element + + Inserts element @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] val element to insert + @return iterator pointing to the inserted @a val. + + @throw type_error.309 if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Constant plus linear in the distance between @a pos and end of + the container. + + @liveexample{The example shows how `insert()` is used.,insert} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts element + @copydoc insert(const_iterator, const basic_json&) + */ + iterator insert(const_iterator pos, basic_json&& val) + { + return insert(pos, val); + } + + /*! + @brief inserts elements + + Inserts @a cnt copies of @a val before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert + @return iterator pointing to the first element inserted, or @a pos if + `cnt==0` + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @complexity Linear in @a cnt plus linear in the distance between @a pos + and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__count} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) + { + // insert only works for arrays + if (JSON_LIKELY(is_array())) + { + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)` before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + @throw invalid_iterator.211 if @a first or @a last are iterators into + container for which insert is called; example: `"passed iterators may not + belong to container"` + + @return iterator pointing to the first element inserted, or @a pos if + `first==last` + + @complexity Linear in `std::distance(first, last)` plus linear in the + distance between @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__range} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, const_iterator first, const_iterator last) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + if (JSON_UNLIKELY(first.m_object == this)) + { + JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from initializer list @a ilist before iterator @a pos. + + @param[in] pos iterator before which the content will be inserted; may be + the end() iterator + @param[in] ilist initializer list to insert the values from + + @throw type_error.309 if called on JSON values other than arrays; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if @a pos is not an iterator of *this; + example: `"iterator does not fit current value"` + + @return iterator pointing to the first element inserted, or @a pos if + `ilist` is empty + + @complexity Linear in `ilist.size()` plus linear in the distance between + @a pos and end of the container. + + @liveexample{The example shows how `insert()` is used.,insert__ilist} + + @since version 1.0.0 + */ + iterator insert(const_iterator pos, initializer_list_t ilist) + { + // insert only works for arrays + if (JSON_UNLIKELY(not is_array())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if iterator pos fits to this JSON value + if (JSON_UNLIKELY(pos.m_object != this)) + { + JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); + } + + // insert to array and return iterator + iterator result(this); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); + return result; + } + + /*! + @brief inserts elements + + Inserts elements from range `[first, last)`. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.309 if called on JSON values other than objects; example: + `"cannot use insert() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number + of elements to insert. + + @liveexample{The example shows how `insert()` is used.,insert__range_object} + + @since version 3.0.0 + */ + void insert(const_iterator first, const_iterator last) + { + // insert only works for objects + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from JSON object @a j and overwrites existing keys. + + @param[in] j JSON object to read values from + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_reference j) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + if (JSON_UNLIKELY(not j.is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); + } + + for (auto it = j.begin(); it != j.end(); ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief updates a JSON object from another object, overwriting existing keys + + Inserts all values from from range `[first, last)` and overwrites existing + keys. + + @param[in] first begin of the range of elements to insert + @param[in] last end of the range of elements to insert + + @throw type_error.312 if called on JSON values other than objects; example: + `"cannot use update() with string"` + @throw invalid_iterator.202 if iterator @a first or @a last does does not + point to an object; example: `"iterators first and last must point to + objects"` + @throw invalid_iterator.210 if @a first and @a last do not belong to the + same JSON value; example: `"iterators do not fit"` + + @complexity O(N*log(size() + N)), where N is the number of elements to + insert. + + @liveexample{The example shows how `update()` is used__range.,update} + + @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update + + @since version 3.0.0 + */ + void update(const_iterator first, const_iterator last) + { + // implicitly convert null value to an empty object + if (is_null()) + { + m_type = value_t::object; + m_value.object = create<object_t>(); + assert_invariant(); + } + + if (JSON_UNLIKELY(not is_object())) + { + JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); + } + + // check if range iterators belong to the same JSON object + if (JSON_UNLIKELY(first.m_object != last.m_object)) + { + JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); + } + + // passed iterators must belong to objects + if (JSON_UNLIKELY(not first.m_object->is_object() + or not first.m_object->is_object())) + { + JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); + } + + for (auto it = first; it != last; ++it) + { + m_value.object->operator[](it.key()) = it.value(); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of the JSON value with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other JSON value to exchange the contents with + + @complexity Constant. + + @liveexample{The example below shows how JSON values can be swapped with + `swap()`.,swap__reference} + + @since version 1.0.0 + */ + void swap(reference other) noexcept ( + std::is_nothrow_move_constructible<value_t>::value and + std::is_nothrow_move_assignable<value_t>::value and + std::is_nothrow_move_constructible<json_value>::value and + std::is_nothrow_move_assignable<json_value>::value + ) + { + std::swap(m_type, other.m_type); + std::swap(m_value, other.m_value); + assert_invariant(); + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON array with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other array to exchange the contents with + + @throw type_error.310 when JSON value is not an array; example: `"cannot + use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how arrays can be swapped with + `swap()`.,swap__array_t} + + @since version 1.0.0 + */ + void swap(array_t& other) + { + // swap only works for arrays + if (JSON_LIKELY(is_array())) + { + std::swap(*(m_value.array), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON object with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other object to exchange the contents with + + @throw type_error.310 when JSON value is not an object; example: + `"cannot use swap() with string"` + + @complexity Constant. + + @liveexample{The example below shows how objects can be swapped with + `swap()`.,swap__object_t} + + @since version 1.0.0 + */ + void swap(object_t& other) + { + // swap only works for objects + if (JSON_LIKELY(is_object())) + { + std::swap(*(m_value.object), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /*! + @brief exchanges the values + + Exchanges the contents of a JSON string with those of @a other. Does not + invoke any move, copy, or swap operations on individual elements. All + iterators and references remain valid. The past-the-end iterator is + invalidated. + + @param[in,out] other string to exchange the contents with + + @throw type_error.310 when JSON value is not a string; example: `"cannot + use swap() with boolean"` + + @complexity Constant. + + @liveexample{The example below shows how strings can be swapped with + `swap()`.,swap__string_t} + + @since version 1.0.0 + */ + void swap(string_t& other) + { + // swap only works for strings + if (JSON_LIKELY(is_string())) + { + std::swap(*(m_value.string), other); + } + else + { + JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); + } + } + + /// @} + + public: + ////////////////////////////////////////// + // lexicographical comparison operators // + ////////////////////////////////////////// + + /// @name lexicographical comparison operators + /// @{ + + /*! + @brief comparison: equal + + Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) they are from the same type and (2) + their stored values are the same according to their respective + `operator==`. + - Integer and floating-point numbers are automatically converted before + comparison. Note than two NaN values are always treated as unequal. + - Two JSON null values are equal. + + @note Floating-point inside JSON values numbers are compared with + `json::number_float_t::operator==` which is `double::operator==` by + default. To compare floating-point while respecting an epsilon, an alternative + [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) + could be used, for instance + @code {.cpp} + template <typename T, typename = typename std::enable_if<std::is_floating_point<T>::value, T>::type> + inline bool is_same(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept + { + return std::abs(a - b) <= epsilon; + } + @endcode + + @note NaN values never compare equal to themselves or to other NaN values. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are equal + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Linear. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__equal} + + @since version 1.0.0 + */ + friend bool operator==(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array == *rhs.m_value.array); + + case value_t::object: + return (*lhs.m_value.object == *rhs.m_value.object); + + case value_t::null: + return true; + + case value_t::string: + return (*lhs.m_value.string == *rhs.m_value.string); + + case value_t::boolean: + return (lhs.m_value.boolean == rhs.m_value.boolean); + + case value_t::number_integer: + return (lhs.m_value.number_integer == rhs.m_value.number_integer); + + case value_t::number_unsigned: + return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); + + case value_t::number_float: + return (lhs.m_value.number_float == rhs.m_value.number_float); + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float); + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned)); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned)); + } + + return false; + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs == basic_json(rhs)); + } + + /*! + @brief comparison: equal + @copydoc operator==(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) == rhs); + } + + /*! + @brief comparison: not equal + + Compares two JSON values for inequality by calculating `not (lhs == rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether the values @a lhs and @a rhs are not equal + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__notequal} + + @since version 1.0.0 + */ + friend bool operator!=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs == rhs); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs != basic_json(rhs)); + } + + /*! + @brief comparison: not equal + @copydoc operator!=(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) != rhs); + } + + /*! + @brief comparison: less than + + Compares whether one JSON value @a lhs is less than another JSON value @a + rhs according to the following rules: + - If @a lhs and @a rhs have the same type, the values are compared using + the default `<` operator. + - Integer and floating-point numbers are automatically converted before + comparison + - In case @a lhs and @a rhs have different types, the values are ignored + and the order of the types is considered, see + @ref operator<(const value_t, const value_t). + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__less} + + @since version 1.0.0 + */ + friend bool operator<(const_reference lhs, const_reference rhs) noexcept + { + const auto lhs_type = lhs.type(); + const auto rhs_type = rhs.type(); + + if (lhs_type == rhs_type) + { + switch (lhs_type) + { + case value_t::array: + return (*lhs.m_value.array) < (*rhs.m_value.array); + + case value_t::object: + return *lhs.m_value.object < *rhs.m_value.object; + + case value_t::null: + return false; + + case value_t::string: + return *lhs.m_value.string < *rhs.m_value.string; + + case value_t::boolean: + return lhs.m_value.boolean < rhs.m_value.boolean; + + case value_t::number_integer: + return lhs.m_value.number_integer < rhs.m_value.number_integer; + + case value_t::number_unsigned: + return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; + + case value_t::number_float: + return lhs.m_value.number_float < rhs.m_value.number_float; + + default: + return false; + } + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) + { + return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) + { + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) + { + return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float; + } + else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) + { + return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned); + } + else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) + { + return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; + } + + // We only reach this line if we cannot compare values. In that case, + // we compare types. Note we have to call the operator explicitly, + // because MSVC has problems otherwise. + return operator<(lhs_type, rhs_type); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs < basic_json(rhs)); + } + + /*! + @brief comparison: less than + @copydoc operator<(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) < rhs); + } + + /*! + @brief comparison: less than or equal + + Compares whether one JSON value @a lhs is less than or equal to another + JSON value by calculating `not (rhs < lhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is less than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greater} + + @since version 1.0.0 + */ + friend bool operator<=(const_reference lhs, const_reference rhs) noexcept + { + return not (rhs < lhs); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs <= basic_json(rhs)); + } + + /*! + @brief comparison: less than or equal + @copydoc operator<=(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) <= rhs); + } + + /*! + @brief comparison: greater than + + Compares whether one JSON value @a lhs is greater than another + JSON value by calculating `not (lhs <= rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__lessequal} + + @since version 1.0.0 + */ + friend bool operator>(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs <= rhs); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs > basic_json(rhs)); + } + + /*! + @brief comparison: greater than + @copydoc operator>(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) > rhs); + } + + /*! + @brief comparison: greater than or equal + + Compares whether one JSON value @a lhs is greater than or equal to another + JSON value by calculating `not (lhs < rhs)`. + + @param[in] lhs first JSON value to consider + @param[in] rhs second JSON value to consider + @return whether @a lhs is greater than or equal to @a rhs + + @complexity Linear. + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @liveexample{The example demonstrates comparing several JSON + types.,operator__greaterequal} + + @since version 1.0.0 + */ + friend bool operator>=(const_reference lhs, const_reference rhs) noexcept + { + return not (lhs < rhs); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept + { + return (lhs >= basic_json(rhs)); + } + + /*! + @brief comparison: greater than or equal + @copydoc operator>=(const_reference, const_reference) + */ + template<typename ScalarType, typename std::enable_if< + std::is_scalar<ScalarType>::value, int>::type = 0> + friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept + { + return (basic_json(lhs) >= rhs); + } + + /// @} + + /////////////////// + // serialization // + /////////////////// + + /// @name serialization + /// @{ + + /*! + @brief serialize to stream + + Serialize the given JSON value @a j to the output stream @a o. The JSON + value will be serialized using the @ref dump member function. + + - The indentation of the output can be controlled with the member variable + `width` of the output stream @a o. For instance, using the manipulator + `std::setw(4)` on @a o sets the indentation level to `4` and the + serialization result is the same as calling `dump(4)`. + + - The indentation characrer can be controlled with the member variable + `fill` of the output stream @a o. For instance, the manipulator + `std::setfill('\\t')` sets indentation to use a tab character rather than + the default space character. + + @param[in,out] o stream to serialize to + @param[in] j JSON value to serialize + + @return the stream @a o + + @complexity Linear. + + @liveexample{The example below shows the serialization with different + parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0; indentaction character added in version 3.0.0 + */ + friend std::ostream& operator<<(std::ostream& o, const basic_json& j) + { + // read width member and use it as indentation parameter if nonzero + const bool pretty_print = (o.width() > 0); + const auto indentation = (pretty_print ? o.width() : 0); + + // reset width to 0 for subsequent calls to this stream + o.width(0); + + // do the actual serialization + serializer s(detail::output_adapter<char>(o), o.fill()); + s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); + return o; + } + + /*! + @brief serialize to stream + @deprecated This stream operator is deprecated and will be removed in a + future version of the library. Please use + @ref operator<<(std::ostream&, const basic_json&) + instead; that is, replace calls like `j >> o;` with `o << j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::ostream& operator>>(const basic_json& j, std::ostream& o) + { + return o << j; + } + + /// @} + + + ///////////////////// + // deserialization // + ///////////////////// + + /// @name deserialization + /// @{ + + /*! + @brief deserialize from a compatible input + + This function reads from a compatible input. Examples are: + - an array of 1-byte values + - strings with character/literal type with size of 1 byte + - input streams + - container with contiguous storage of 1-byte values. Compatible container + types include `std::vector`, `std::string`, `std::array`, + `std::valarray`, and `std::initializer_list`. Furthermore, C-style + arrays can be used with `std::begin()`/`std::end()`. User-defined + containers can be used as long as they implement random-access iterators + and a contiguous storage. + + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @pre The container storage is contiguous. Violating this precondition + yields undefined behavior. **This precondition is enforced with an + assertion.** + @pre Each element of the container has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with a noncompliant container and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @param[in] i input to read from + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + + @return result of the deserialization + + @throw parse_error.101 if a parse error occurs; example: `""unexpected end + of input; expected string literal""` + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an array.,parse__array__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__string__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function with + and without callback function.,parse__istream__parser_callback_t} + + @liveexample{The example below demonstrates the `parse()` function reading + from a contiguous container.,parse__contiguouscontainer__parser_callback_t} + + @since version 2.0.3 (contiguous containers) + */ + static basic_json parse(detail::input_adapter i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + /*! + @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) + */ + static basic_json parse(detail::input_adapter& i, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(i, cb, allow_exceptions).parse(true, result); + return result; + } + + static bool accept(detail::input_adapter i) + { + return parser(i).accept(true); + } + + static bool accept(detail::input_adapter& i) + { + return parser(i).accept(true); + } + + /*! + @brief deserialize from an iterator range with contiguous storage + + This function reads from an iterator range of a container with contiguous + storage of 1-byte values. Compatible container types include + `std::vector`, `std::string`, `std::array`, `std::valarray`, and + `std::initializer_list`. Furthermore, C-style arrays can be used with + `std::begin()`/`std::end()`. User-defined containers can be used as long + as they implement random-access iterators and a contiguous storage. + + @pre The iterator range is contiguous. Violating this precondition yields + undefined behavior. **This precondition is enforced with an assertion.** + @pre Each element in the range has a size of 1 byte. Violating this + precondition yields undefined behavior. **This precondition is enforced + with a static assertion.** + + @warning There is no way to enforce all preconditions at compile-time. If + the function is called with noncompliant iterators and with + assertions switched off, the behavior is undefined and will most + likely yield segmentation violation. + + @tparam IteratorType iterator of container with contiguous storage + @param[in] first begin of the range to parse (included) + @param[in] last end of the range to parse (excluded) + @param[in] cb a parser callback function of type @ref parser_callback_t + which is used to control the deserialization by filtering unwanted values + (optional) + @param[in] allow_exceptions whether to throw exceptions in case of a + parse error (optional, true by default) + + @return result of the deserialization + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. The complexity can be higher if the parser callback function + @a cb has a super-linear complexity. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below demonstrates the `parse()` function reading + from an iterator range.,parse__iteratortype__parser_callback_t} + + @since version 2.0.3 + */ + template<class IteratorType, typename std::enable_if< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> + static basic_json parse(IteratorType first, IteratorType last, + const parser_callback_t cb = nullptr, + const bool allow_exceptions = true) + { + basic_json result; + parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); + return result; + } + + template<class IteratorType, typename std::enable_if< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> + static bool accept(IteratorType first, IteratorType last) + { + return parser(detail::input_adapter(first, last)).accept(true); + } + + /*! + @brief deserialize from stream + @deprecated This stream operator is deprecated and will be removed in a + future version of the library. Please use + @ref operator>>(std::istream&, basic_json&) + instead; that is, replace calls like `j << i;` with `i >> j;`. + @since version 1.0.0; deprecated since version 3.0.0 + */ + JSON_DEPRECATED + friend std::istream& operator<<(basic_json& j, std::istream& i) + { + return operator>>(i, j); + } + + /*! + @brief deserialize from stream + + Deserializes an input stream to a JSON value. + + @param[in,out] i input stream to read a serialized JSON value from + @param[in,out] j JSON value to write the deserialized input to + + @throw parse_error.101 in case of an unexpected token + @throw parse_error.102 if to_unicode fails or surrogate error + @throw parse_error.103 if to_unicode fails + + @complexity Linear in the length of the input. The parser is a predictive + LL(1) parser. + + @note A UTF-8 byte order mark is silently ignored. + + @liveexample{The example below shows how a JSON value is constructed by + reading a serialization from a stream.,operator_deserialize} + + @sa parse(std::istream&, const parser_callback_t) for a variant with a + parser callback function to filter values while parsing + + @since version 1.0.0 + */ + friend std::istream& operator>>(std::istream& i, basic_json& j) + { + parser(detail::input_adapter(i)).parse(false, j); + return i; + } + + /// @} + + /////////////////////////// + // convenience functions // + /////////////////////////// + + /*! + @brief return the type as string + + Returns the type name as string to be used in error messages - usually to + indicate that a function was called on a wrong JSON type. + + @return a string representation of a the @a m_type member: + Value type | return value + ----------- | ------------- + null | `"null"` + boolean | `"boolean"` + string | `"string"` + number | `"number"` (for all number types) + object | `"object"` + array | `"array"` + discarded | `"discarded"` + + @exceptionsafety No-throw guarantee: this function never throws exceptions. + + @complexity Constant. + + @liveexample{The following code exemplifies `type_name()` for all JSON + types.,type_name} + + @sa @ref type() -- return the type of the JSON value + @sa @ref operator value_t() -- return the type of the JSON value (implicit) + + @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` + since 3.0.0 + */ + const char* type_name() const noexcept + { + { + switch (m_type) + { + case value_t::null: + return "null"; + case value_t::object: + return "object"; + case value_t::array: + return "array"; + case value_t::string: + return "string"; + case value_t::boolean: + return "boolean"; + case value_t::discarded: + return "discarded"; + default: + return "number"; + } + } + } + + + private: + ////////////////////// + // member variables // + ////////////////////// + + /// the type of the current element + value_t m_type = value_t::null; + + /// the value of the current element + json_value m_value = {}; + + ////////////////////////////////////////// + // binary serialization/deserialization // + ////////////////////////////////////////// + + /// @name binary serialization/deserialization support + /// @{ + + public: + /*! + @brief create a CBOR serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the CBOR (Concise + Binary Object Representation) serialization format. CBOR is a binary + serialization format which aims to be more compact than JSON itself, yet + more efficient to parse. + + The library uses the following mapping from JSON values types to + CBOR types according to the CBOR specification (RFC 7049): + + JSON value type | value/range | CBOR type | first byte + --------------- | ------------------------------------------ | ---------------------------------- | --------------- + null | `null` | Null | 0xf6 + boolean | `true` | True | 0xf5 + boolean | `false` | False | 0xf4 + number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3b + number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3a + number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 + number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 + number_integer | -24..-1 | Negative integer | 0x20..0x37 + number_integer | 0..23 | Integer | 0x00..0x17 + number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a + number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b + number_unsigned | 0..23 | Integer | 0x00..0x17 + number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 + number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 + number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a + number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b + number_float | *any value* | Double-Precision Float | 0xfb + string | *length*: 0..23 | UTF-8 string | 0x60..0x77 + string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 + string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 + string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7a + string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7b + array | *size*: 0..23 | array | 0x80..0x97 + array | *size*: 23..255 | array (1 byte follow) | 0x98 + array | *size*: 256..65535 | array (2 bytes follow) | 0x99 + array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9a + array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9b + object | *size*: 0..23 | map | 0xa0..0xb7 + object | *size*: 23..255 | map (1 byte follow) | 0xb8 + object | *size*: 256..65535 | map (2 bytes follow) | 0xb9 + object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xba + object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xbb + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a CBOR value. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @note The following CBOR types are not used in the conversion: + - byte strings (0x40..0x5f) + - UTF-8 strings terminated by "break" (0x7f) + - arrays terminated by "break" (0x9f) + - maps terminated by "break" (0xbf) + - date/time (0xc0..0xc1) + - bignum (0xc2..0xc3) + - decimal fraction (0xc4) + - bigfloat (0xc5) + - tagged items (0xc6..0xd4, 0xd8..0xdb) + - expected conversions (0xd5..0xd7) + - simple values (0xe0..0xf3, 0xf8) + - undefined (0xf7) + - half and single-precision floats (0xf9-0xfa) + - break (0xff) + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in CBOR format.,to_cbor} + + @sa http://cbor.io + @sa @ref from_cbor(const std::vector<uint8_t>&, const size_t) for the + analogous deserialization + @sa @ref to_msgpack(const basic_json&) for the related MessagePack format + + @since version 2.0.9 + */ + static std::vector<uint8_t> to_cbor(const basic_json& j) + { + std::vector<uint8_t> result; + to_cbor(j, result); + return result; + } + + static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o) + { + binary_writer<uint8_t>(o).write_cbor(j); + } + + static void to_cbor(const basic_json& j, detail::output_adapter<char> o) + { + binary_writer<char>(o).write_cbor(j); + } + + /*! + @brief create a MessagePack serialization of a given JSON value + + Serializes a given JSON value @a j to a byte vector using the MessagePack + serialization format. MessagePack is a binary serialization format which + aims to be more compact than JSON itself, yet more efficient to parse. + + The library uses the following mapping from JSON values types to + MessagePack types according to the MessagePack specification: + + JSON value type | value/range | MessagePack type | first byte + --------------- | --------------------------------- | ---------------- | ---------- + null | `null` | nil | 0xc0 + boolean | `true` | true | 0xc3 + boolean | `false` | false | 0xc2 + number_integer | -9223372036854775808..-2147483649 | int64 | 0xd3 + number_integer | -2147483648..-32769 | int32 | 0xd2 + number_integer | -32768..-129 | int16 | 0xd1 + number_integer | -128..-33 | int8 | 0xd0 + number_integer | -32..-1 | negative fixint | 0xe0..0xff + number_integer | 0..127 | positive fixint | 0x00..0x7f + number_integer | 128..255 | uint 8 | 0xcc + number_integer | 256..65535 | uint 16 | 0xcd + number_integer | 65536..4294967295 | uint 32 | 0xce + number_integer | 4294967296..18446744073709551615 | uint 64 | 0xcf + number_unsigned | 0..127 | positive fixint | 0x00..0x7f + number_unsigned | 128..255 | uint 8 | 0xcc + number_unsigned | 256..65535 | uint 16 | 0xcd + number_unsigned | 65536..4294967295 | uint 32 | 0xce + number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xcf + number_float | *any value* | float 64 | 0xcb + string | *length*: 0..31 | fixstr | 0xa0..0xbf + string | *length*: 32..255 | str 8 | 0xd9 + string | *length*: 256..65535 | str 16 | 0xda + string | *length*: 65536..4294967295 | str 32 | 0xdb + array | *size*: 0..15 | fixarray | 0x90..0x9f + array | *size*: 16..65535 | array 16 | 0xdc + array | *size*: 65536..4294967295 | array 32 | 0xdd + object | *size*: 0..15 | fix map | 0x80..0x8f + object | *size*: 16..65535 | map 16 | 0xde + object | *size*: 65536..4294967295 | map 32 | 0xdf + + @note The mapping is **complete** in the sense that any JSON value type + can be converted to a MessagePack value. + + @note The following values can **not** be converted to a MessagePack value: + - strings with more than 4294967295 bytes + - arrays with more than 4294967295 elements + - objects with more than 4294967295 elements + + @note The following MessagePack types are not used in the conversion: + - bin 8 - bin 32 (0xc4..0xc6) + - ext 8 - ext 32 (0xc7..0xc9) + - float 32 (0xca) + - fixext 1 - fixext 16 (0xd4..0xd8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @note If NaN or Infinity are stored inside a JSON number, they are + serialized properly. This behavior differs from the @ref dump() + function which serializes NaN or Infinity to `null`. + + @param[in] j JSON value to serialize + @return MessagePack serialization as byte vector + + @complexity Linear in the size of the JSON value @a j. + + @liveexample{The example shows the serialization of a JSON value to a byte + vector in MessagePack format.,to_msgpack} + + @sa http://msgpack.org + @sa @ref from_msgpack(const std::vector<uint8_t>&, const size_t) for the + analogous deserialization + @sa @ref to_cbor(const basic_json& for the related CBOR format + + @since version 2.0.9 + */ + static std::vector<uint8_t> to_msgpack(const basic_json& j) + { + std::vector<uint8_t> result; + to_msgpack(j, result); + return result; + } + + static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o) + { + binary_writer<uint8_t>(o).write_msgpack(j); + } + + static void to_msgpack(const basic_json& j, detail::output_adapter<char> o) + { + binary_writer<char>(o).write_msgpack(j); + } + + /*! + @brief create a JSON value from an input in CBOR format + + Deserializes a given input @a i to a JSON value using the CBOR (Concise + Binary Object Representation) serialization format. + + The library maps CBOR types to JSON value types as follows: + + CBOR type | JSON value type | first byte + ---------------------- | --------------- | ---------- + Integer | number_unsigned | 0x00..0x17 + Unsigned integer | number_unsigned | 0x18 + Unsigned integer | number_unsigned | 0x19 + Unsigned integer | number_unsigned | 0x1a + Unsigned integer | number_unsigned | 0x1b + Negative integer | number_integer | 0x20..0x37 + Negative integer | number_integer | 0x38 + Negative integer | number_integer | 0x39 + Negative integer | number_integer | 0x3a + Negative integer | number_integer | 0x3b + Negative integer | number_integer | 0x40..0x57 + UTF-8 string | string | 0x60..0x77 + UTF-8 string | string | 0x78 + UTF-8 string | string | 0x79 + UTF-8 string | string | 0x7a + UTF-8 string | string | 0x7b + UTF-8 string | string | 0x7f + array | array | 0x80..0x97 + array | array | 0x98 + array | array | 0x99 + array | array | 0x9a + array | array | 0x9b + array | array | 0x9f + map | object | 0xa0..0xb7 + map | object | 0xb8 + map | object | 0xb9 + map | object | 0xba + map | object | 0xbb + map | object | 0xbf + False | `false` | 0xf4 + True | `true` | 0xf5 + Nill | `null` | 0xf6 + Half-Precision Float | number_float | 0xf9 + Single-Precision Float | number_float | 0xfa + Double-Precision Float | number_float | 0xfb + + @warning The mapping is **incomplete** in the sense that not all CBOR + types can be converted to a JSON value. The following CBOR types + are not supported and will yield parse errors (parse_error.112): + - byte strings (0x40..0x5f) + - date/time (0xc0..0xc1) + - bignum (0xc2..0xc3) + - decimal fraction (0xc4) + - bigfloat (0xc5) + - tagged items (0xc6..0xd4, 0xd8..0xdb) + - expected conversions (0xd5..0xd7) + - simple values (0xe0..0xf3, 0xf8) + - undefined (0xf7) + + @warning CBOR allows map keys of any type, whereas JSON only allows + strings as keys in object values. Therefore, CBOR maps with keys + other than UTF-8 strings are rejected (parse_error.113). + + @note Any CBOR output created @ref to_cbor can be successfully parsed by + @ref from_cbor. + + @param[in] i an input in CBOR format convertible to an input adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + @return deserialized JSON value + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from CBOR were + used in the given input @a v or if the input is not valid CBOR + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in CBOR + format to a JSON value.,from_cbor} + + @sa http://cbor.io + @sa @ref to_cbor(const basic_json&) for the analogous serialization + @sa @ref from_msgpack(detail::input_adapter, const bool) for the + related MessagePack format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_cbor(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_cbor(strict); + } + + /*! + @copydoc from_cbor(detail::input_adapter, const bool) + */ + template<typename A1, typename A2, + detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> + static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_cbor(strict); + } + + /*! + @brief create a JSON value from an input in MessagePack format + + Deserializes a given input @a i to a JSON value using the MessagePack + serialization format. + + The library maps MessagePack types to JSON value types as follows: + + MessagePack type | JSON value type | first byte + ---------------- | --------------- | ---------- + positive fixint | number_unsigned | 0x00..0x7f + fixmap | object | 0x80..0x8f + fixarray | array | 0x90..0x9f + fixstr | string | 0xa0..0xbf + nil | `null` | 0xc0 + false | `false` | 0xc2 + true | `true` | 0xc3 + float 32 | number_float | 0xca + float 64 | number_float | 0xcb + uint 8 | number_unsigned | 0xcc + uint 16 | number_unsigned | 0xcd + uint 32 | number_unsigned | 0xce + uint 64 | number_unsigned | 0xcf + int 8 | number_integer | 0xd0 + int 16 | number_integer | 0xd1 + int 32 | number_integer | 0xd2 + int 64 | number_integer | 0xd3 + str 8 | string | 0xd9 + str 16 | string | 0xda + str 32 | string | 0xdb + array 16 | array | 0xdc + array 32 | array | 0xdd + map 16 | object | 0xde + map 32 | object | 0xdf + negative fixint | number_integer | 0xe0-0xff + + @warning The mapping is **incomplete** in the sense that not all + MessagePack types can be converted to a JSON value. The following + MessagePack types are not supported and will yield parse errors: + - bin 8 - bin 32 (0xc4..0xc6) + - ext 8 - ext 32 (0xc7..0xc9) + - fixext 1 - fixext 16 (0xd4..0xd8) + + @note Any MessagePack output created @ref to_msgpack can be successfully + parsed by @ref from_msgpack. + + @param[in] i an input in MessagePack format convertible to an input + adapter + @param[in] strict whether to expect the input to be consumed until EOF + (true by default) + + @throw parse_error.110 if the given input ends prematurely or the end of + file was not reached when @a strict was set to true + @throw parse_error.112 if unsupported features from MessagePack were + used in the given input @a i or if the input is not valid MessagePack + @throw parse_error.113 if a string was expected as map key, but not found + + @complexity Linear in the size of the input @a i. + + @liveexample{The example shows the deserialization of a byte vector in + MessagePack format to a JSON value.,from_msgpack} + + @sa http://msgpack.org + @sa @ref to_msgpack(const basic_json&) for the analogous serialization + @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR + format + + @since version 2.0.9; parameter @a start_index since 2.1.1; changed to + consume input adapters, removed start_index parameter, and added + @a strict parameter since 3.0.0 + */ + static basic_json from_msgpack(detail::input_adapter i, + const bool strict = true) + { + return binary_reader(i).parse_msgpack(strict); + } + + /*! + @copydoc from_msgpack(detail::input_adapter, const bool) + */ + template<typename A1, typename A2, + detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> + static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) + { + return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_msgpack(strict); + } + + /// @} + + ////////////////////////// + // JSON Pointer support // + ////////////////////////// + + /// @name JSON Pointer functions + /// @{ + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. Similar to @ref operator[](const typename + object_t::key_type&), `null` values are created in arrays and objects if + necessary. + + In particular: + - If the JSON pointer points to an object key that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. + - If the JSON pointer points to an array index that does not exist, it + is created an filled with a `null` value before a reference to it + is returned. All indices between the current maximum and the given + index are also filled with `null`. + - The special value `-` is treated as a synonym for the index past the + end. + + @param[in] ptr a JSON pointer + + @return reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer} + + @since version 2.0.0 + */ + reference operator[](const json_pointer& ptr) + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Uses a JSON pointer to retrieve a reference to the respective JSON value. + No bound checking is performed. The function does not change the JSON + value; no `null` values are created. In particular, the the special value + `-` yields an exception. + + @param[in] ptr JSON pointer to the desired element + + @return const reference to the element pointed to by @a ptr + + @complexity Constant. + + @throw parse_error.106 if an array index begins with '0' + @throw parse_error.109 if an array index was not a number + @throw out_of_range.402 if the array index '-' is used + @throw out_of_range.404 if the JSON pointer can not be resolved + + @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} + + @since version 2.0.0 + */ + const_reference operator[](const json_pointer& ptr) const + { + return ptr.get_unchecked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a reference to the element at with specified JSON pointer @a ptr, + with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer} + */ + reference at(const json_pointer& ptr) + { + return ptr.get_checked(this); + } + + /*! + @brief access specified element via JSON Pointer + + Returns a const reference to the element at with specified JSON pointer @a + ptr, with bounds checking. + + @param[in] ptr JSON pointer to the desired element + + @return reference to the element pointed to by @a ptr + + @throw parse_error.106 if an array index in the passed JSON pointer @a ptr + begins with '0'. See example below. + + @throw parse_error.109 if an array index in the passed JSON pointer @a ptr + is not a number. See example below. + + @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr + is out of range. See example below. + + @throw out_of_range.402 if the array index '-' is used in the passed JSON + pointer @a ptr. As `at` provides checked access (and no elements are + implicitly inserted), the index '-' is always invalid. See example below. + + @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. + See example below. + + @exceptionsafety Strong guarantee: if an exception is thrown, there are no + changes in the JSON value. + + @complexity Constant. + + @since version 2.0.0 + + @liveexample{The behavior is shown in the example.,at_json_pointer_const} + */ + const_reference at(const json_pointer& ptr) const + { + return ptr.get_checked(this); + } + + /*! + @brief return flattened JSON value + + The function creates a JSON object whose keys are JSON pointers (see [RFC + 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all + primitive. The original JSON value can be restored using the @ref + unflatten() function. + + @return an object that maps JSON pointers to primitive values + + @note Empty objects and arrays are flattened to `null` and will not be + reconstructed correctly by the @ref unflatten() function. + + @complexity Linear in the size the JSON value. + + @liveexample{The following code shows how a JSON object is flattened to an + object whose keys consist of JSON pointers.,flatten} + + @sa @ref unflatten() for the reverse function + + @since version 2.0.0 + */ + basic_json flatten() const + { + basic_json result(value_t::object); + json_pointer::flatten("", *this, result); + return result; + } + + /*! + @brief unflatten a previously flattened JSON value + + The function restores the arbitrary nesting of a JSON value that has been + flattened before using the @ref flatten() function. The JSON value must + meet certain constraints: + 1. The value must be an object. + 2. The keys must be JSON pointers (see + [RFC 6901](https://tools.ietf.org/html/rfc6901)) + 3. The mapped values must be primitive JSON types. + + @return the original JSON from a flattened version + + @note Empty objects and arrays are flattened by @ref flatten() to `null` + values and can not unflattened to their original type. Apart from + this example, for a JSON value `j`, the following is always true: + `j == j.flatten().unflatten()`. + + @complexity Linear in the size the JSON value. + + @throw type_error.314 if value is not an object + @throw type_error.315 if object values are not primitive + + @liveexample{The following code shows how a flattened JSON object is + unflattened into the original nested JSON object.,unflatten} + + @sa @ref flatten() for the reverse function + + @since version 2.0.0 + */ + basic_json unflatten() const + { + return json_pointer::unflatten(*this); + } + + /// @} + + ////////////////////////// + // JSON Patch functions // + ////////////////////////// + + /// @name JSON Patch functions + /// @{ + + /*! + @brief applies a JSON patch + + [JSON Patch](http://jsonpatch.com) defines a JSON document structure for + expressing a sequence of operations to apply to a JSON) document. With + this function, a JSON Patch is applied to the current JSON value by + executing all operations from the patch. + + @param[in] json_patch JSON patch document + @return patched document + + @note The application of a patch is atomic: Either all operations succeed + and the patched document is returned or an exception is thrown. In + any case, the original value is not changed: the patch is applied + to a copy of the value. + + @throw parse_error.104 if the JSON patch does not consist of an array of + objects + + @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory + attributes are missing); example: `"operation add must have member path"` + + @throw out_of_range.401 if an array index is out of range. + + @throw out_of_range.403 if a JSON pointer inside the patch could not be + resolved successfully in the current JSON value; example: `"key baz not + found"` + + @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", + "move") + + @throw other_error.501 if "test" operation was unsuccessful + + @complexity Linear in the size of the JSON value and the length of the + JSON patch. As usually only a fraction of the JSON value is affected by + the patch, the complexity can usually be neglected. + + @liveexample{The following code shows how a JSON patch is applied to a + value.,patch} + + @sa @ref diff -- create a JSON patch by comparing two JSON values + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) + + @since version 2.0.0 + */ + basic_json patch(const basic_json& json_patch) const + { + // make a working copy to apply the patch to + basic_json result = *this; + + // the valid JSON Patch operations + enum class patch_operations {add, remove, replace, move, copy, test, invalid}; + + const auto get_op = [](const std::string & op) + { + if (op == "add") + { + return patch_operations::add; + } + if (op == "remove") + { + return patch_operations::remove; + } + if (op == "replace") + { + return patch_operations::replace; + } + if (op == "move") + { + return patch_operations::move; + } + if (op == "copy") + { + return patch_operations::copy; + } + if (op == "test") + { + return patch_operations::test; + } + + return patch_operations::invalid; + }; + + // wrapper for "add" operation; add value at ptr + const auto operation_add = [&result](json_pointer & ptr, basic_json val) + { + // adding to the root of the target document means replacing it + if (ptr.is_root()) + { + result = val; + } + else + { + // make sure the top element of the pointer exists + json_pointer top_pointer = ptr.top(); + if (top_pointer != ptr) + { + result.at(top_pointer); + } + + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result[ptr]; + + switch (parent.m_type) + { + case value_t::null: + case value_t::object: + { + // use operator[] to add value + parent[last_path] = val; + break; + } + + case value_t::array: + { + if (last_path == "-") + { + // special case: append to back + parent.push_back(val); + } + else + { + const auto idx = std::stoi(last_path); + if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size())) + { + // avoid undefined behavior + JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); + } + else + { + // default case: insert add offset + parent.insert(parent.begin() + static_cast<difference_type>(idx), val); + } + } + break; + } + + default: + { + // if there exists a parent it cannot be primitive + assert(false); // LCOV_EXCL_LINE + } + } + } + }; + + // wrapper for "remove" operation; remove value at ptr + const auto operation_remove = [&result](json_pointer & ptr) + { + // get reference to parent of JSON pointer ptr + const auto last_path = ptr.pop_back(); + basic_json& parent = result.at(ptr); + + // remove child + if (parent.is_object()) + { + // perform range check + auto it = parent.find(last_path); + if (JSON_LIKELY(it != parent.end())) + { + parent.erase(it); + } + else + { + JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); + } + } + else if (parent.is_array()) + { + // note erase performs range check + parent.erase(static_cast<size_type>(std::stoi(last_path))); + } + }; + + // type check: top level value must be an array + if (JSON_UNLIKELY(not json_patch.is_array())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // iterate and apply the operations + for (const auto& val : json_patch) + { + // wrapper to get a value for an operation + const auto get_value = [&val](const std::string & op, + const std::string & member, + bool string_type) -> basic_json& + { + // find value + auto it = val.m_value.object->find(member); + + // context-sensitive error message + const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; + + // check if desired value is present + if (JSON_UNLIKELY(it == val.m_value.object->end())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); + } + + // check if result is of type string + if (JSON_UNLIKELY(string_type and not it->second.is_string())) + { + JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); + } + + // no error: return value + return it->second; + }; + + // type check: every element of the array must be an object + if (JSON_UNLIKELY(not val.is_object())) + { + JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); + } + + // collect mandatory members + const std::string op = get_value("op", "op", true); + const std::string path = get_value(op, "path", true); + json_pointer ptr(path); + + switch (get_op(op)) + { + case patch_operations::add: + { + operation_add(ptr, get_value("add", "value", false)); + break; + } + + case patch_operations::remove: + { + operation_remove(ptr); + break; + } + + case patch_operations::replace: + { + // the "path" location must exist - use at() + result.at(ptr) = get_value("replace", "value", false); + break; + } + + case patch_operations::move: + { + const std::string from_path = get_value("move", "from", true); + json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + basic_json v = result.at(from_ptr); + + // The move operation is functionally identical to a + // "remove" operation on the "from" location, followed + // immediately by an "add" operation at the target + // location with the value that was just removed. + operation_remove(from_ptr); + operation_add(ptr, v); + break; + } + + case patch_operations::copy: + { + const std::string from_path = get_value("copy", "from", true); + const json_pointer from_ptr(from_path); + + // the "from" location must exist - use at() + result[ptr] = result.at(from_ptr); + break; + } + + case patch_operations::test: + { + bool success = false; + JSON_TRY + { + // check if "value" matches the one at "path" + // the "path" location must exist - use at() + success = (result.at(ptr) == get_value("test", "value", false)); + } + JSON_CATCH (out_of_range&) + { + // ignore out of range errors: success remains false + } + + // throw an exception if test fails + if (JSON_UNLIKELY(not success)) + { + JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); + } + + break; + } + + case patch_operations::invalid: + { + // op must be "add", "remove", "replace", "move", "copy", or + // "test" + JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); + } + } + } + + return result; + } + + /*! + @brief creates a diff as a JSON patch + + Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can + be changed into the value @a target by calling @ref patch function. + + @invariant For two JSON values @a source and @a target, the following code + yields always `true`: + @code {.cpp} + source.patch(diff(source, target)) == target; + @endcode + + @note Currently, only `remove`, `add`, and `replace` operations are + generated. + + @param[in] source JSON value to compare from + @param[in] target JSON value to compare against + @param[in] path helper value to create JSON pointers + + @return a JSON patch to convert the @a source to @a target + + @complexity Linear in the lengths of @a source and @a target. + + @liveexample{The following code shows how a JSON patch is created as a + diff for two JSON values.,diff} + + @sa @ref patch -- apply a JSON patch + + @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) + + @since version 2.0.0 + */ + static basic_json diff(const basic_json& source, const basic_json& target, + const std::string& path = "") + { + // the patch + basic_json result(value_t::array); + + // if the values are the same, return empty patch + if (source == target) + { + return result; + } + + if (source.type() != target.type()) + { + // different types: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + } + else + { + switch (source.type()) + { + case value_t::array: + { + // first pass: traverse common elements + std::size_t i = 0; + while (i < source.size() and i < target.size()) + { + // recursive call to compare array values at index i + auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + ++i; + } + + // i now reached the end of at least one array + // in a second pass, traverse the remaining elements + + // remove my remaining elements + const auto end_index = static_cast<difference_type>(result.size()); + while (i < source.size()) + { + // add operations in reverse order to avoid invalid + // indices + result.insert(result.begin() + end_index, object( + { + {"op", "remove"}, + {"path", path + "/" + std::to_string(i)} + })); + ++i; + } + + // add other remaining elements + while (i < target.size()) + { + result.push_back( + { + {"op", "add"}, + {"path", path + "/" + std::to_string(i)}, + {"value", target[i]} + }); + ++i; + } + + break; + } + + case value_t::object: + { + // first pass: traverse this object's elements + for (auto it = source.begin(); it != source.end(); ++it) + { + // escape the key name to be used in a JSON patch + const auto key = json_pointer::escape(it.key()); + + if (target.find(it.key()) != target.end()) + { + // recursive call to compare object values at key it + auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); + result.insert(result.end(), temp_diff.begin(), temp_diff.end()); + } + else + { + // found a key that is not in o -> remove it + result.push_back(object( + { + {"op", "remove"}, {"path", path + "/" + key} + })); + } + } + + // second pass: traverse other object's elements + for (auto it = target.begin(); it != target.end(); ++it) + { + if (source.find(it.key()) == source.end()) + { + // found a key that is not in this -> add it + const auto key = json_pointer::escape(it.key()); + result.push_back( + { + {"op", "add"}, {"path", path + "/" + key}, + {"value", it.value()} + }); + } + } + + break; + } + + default: + { + // both primitive type: replace value + result.push_back( + { + {"op", "replace"}, {"path", path}, {"value", target} + }); + break; + } + } + } + + return result; + } + + /// @} +}; + +///////////// +// presets // +///////////// + +/*! +@brief default JSON class + +This type is the default specialization of the @ref basic_json class which +uses the standard template types. + +@since version 1.0.0 +*/ +using json = basic_json<>; + +////////////////// +// json_pointer // +////////////////// + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +NLOHMANN_BASIC_JSON_TPL& +json_pointer::get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const +{ + using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; + auto result = &j; + + // in case no reference tokens exist, return a reference to the JSON value + // j which will be overwritten by a primitive value + for (const auto& reference_token : reference_tokens) + { + switch (result->m_type) + { + case detail::value_t::null: + { + if (reference_token == "0") + { + // start a new array if reference token is 0 + result = &result->operator[](0); + } + else + { + // start a new object otherwise + result = &result->operator[](reference_token); + } + break; + } + + case detail::value_t::object: + { + // create an entry in the object + result = &result->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // create an entry in the array + JSON_TRY + { + result = &result->operator[](static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + /* + The following code is only reached if there exists a reference + token _and_ the current value is primitive. In this case, we have + an error situation, because primitive values may only occur as + single value; that is, with an empty list of reference tokens. + */ + default: + JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); + } + } + + return *result; +} + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +NLOHMANN_BASIC_JSON_TPL& +json_pointer::get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const +{ + using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; + for (const auto& reference_token : reference_tokens) + { + // convert null values to arrays or objects before continuing + if (ptr->m_type == detail::value_t::null) + { + // check if reference token is a number + const bool nums = + std::all_of(reference_token.begin(), reference_token.end(), + [](const char x) + { + return (x >= '0' and x <= '9'); + }); + + // change value to array for numbers or "-" or to object otherwise + *ptr = (nums or reference_token == "-") + ? detail::value_t::array + : detail::value_t::object; + } + + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + if (reference_token == "-") + { + // explicitly treat "-" as index beyond the end + ptr = &ptr->operator[](ptr->m_value.array->size()); + } + else + { + // convert array index to number; unchecked access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; +} + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +NLOHMANN_BASIC_JSON_TPL& +json_pointer::get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const +{ + using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; +} + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +const NLOHMANN_BASIC_JSON_TPL& +json_pointer::get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const +{ + using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // use unchecked object access + ptr = &ptr->operator[](reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" cannot be used for const access + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // use unchecked array access + JSON_TRY + { + ptr = &ptr->operator[]( + static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; +} + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +const NLOHMANN_BASIC_JSON_TPL& +json_pointer::get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const +{ + using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; + for (const auto& reference_token : reference_tokens) + { + switch (ptr->m_type) + { + case detail::value_t::object: + { + // note: at performs range check + ptr = &ptr->at(reference_token); + break; + } + + case detail::value_t::array: + { + if (JSON_UNLIKELY(reference_token == "-")) + { + // "-" always fails the range check + JSON_THROW(detail::out_of_range::create(402, + "array index '-' (" + std::to_string(ptr->m_value.array->size()) + + ") is out of range")); + } + + // error condition (cf. RFC 6901, Sect. 4) + if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) + { + JSON_THROW(detail::parse_error::create(106, 0, + "array index '" + reference_token + + "' must not begin with '0'")); + } + + // note: at performs range check + JSON_TRY + { + ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token))); + } + JSON_CATCH(std::invalid_argument&) + { + JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); + } + break; + } + + default: + JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); + } + } + + return *ptr; +} + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +void json_pointer::flatten(const std::string& reference_string, + const NLOHMANN_BASIC_JSON_TPL& value, + NLOHMANN_BASIC_JSON_TPL& result) +{ + switch (value.m_type) + { + case detail::value_t::array: + { + if (value.m_value.array->empty()) + { + // flatten empty array as null + result[reference_string] = nullptr; + } + else + { + // iterate array and use index as reference string + for (std::size_t i = 0; i < value.m_value.array->size(); ++i) + { + flatten(reference_string + "/" + std::to_string(i), + value.m_value.array->operator[](i), result); + } + } + break; + } + + case detail::value_t::object: + { + if (value.m_value.object->empty()) + { + // flatten empty object as null + result[reference_string] = nullptr; + } + else + { + // iterate object and use keys as reference string + for (const auto& element : *value.m_value.object) + { + flatten(reference_string + "/" + escape(element.first), element.second, result); + } + } + break; + } + + default: + { + // add primitive value with its reference string + result[reference_string] = value; + break; + } + } +} + +NLOHMANN_BASIC_JSON_TPL_DECLARATION +NLOHMANN_BASIC_JSON_TPL +json_pointer::unflatten(const NLOHMANN_BASIC_JSON_TPL& value) +{ + if (JSON_UNLIKELY(not value.is_object())) + { + JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); + } + + NLOHMANN_BASIC_JSON_TPL result; + + // iterate the JSON object values + for (const auto& element : *value.m_value.object) + { + if (JSON_UNLIKELY(not element.second.is_primitive())) + { + JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); + } + + // assign value to reference pointed to by JSON pointer; Note that if + // the JSON pointer is "" (i.e., points to the whole value), function + // get_and_create returns a reference to result itself. An assignment + // will then create a primitive value. + json_pointer(element.first).get_and_create(result) = element.second; + } + + return result; +} + +inline bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept +{ + return (lhs.reference_tokens == rhs.reference_tokens); +} + +inline bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept +{ + return not (lhs == rhs); +} +} // namespace nlohmann + + +/////////////////////// +// nonmember support // +/////////////////////// + +// specialization of std::swap, and std::hash +namespace std +{ +/*! +@brief exchanges the values of two JSON objects + +@since version 1.0.0 +*/ +template<> +inline void swap(nlohmann::json& j1, + nlohmann::json& j2) noexcept( + is_nothrow_move_constructible<nlohmann::json>::value and + is_nothrow_move_assignable<nlohmann::json>::value + ) +{ + j1.swap(j2); +} + +/// hash value for JSON objects +template<> +struct hash<nlohmann::json> +{ + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ + std::size_t operator()(const nlohmann::json& j) const + { + // a naive hashing via the string representation + const auto& h = hash<nlohmann::json::string_t>(); + return h(j.dump()); + } +}; + +/// specialization for std::less<value_t> +/// @note: do not remove the space after '<', +/// see https://github.com/nlohmann/json/pull/679 +template<> +struct less< ::nlohmann::detail::value_t> +{ + /*! + @brief compare two value_t enum values + @since version 3.0.0 + */ + bool operator()(nlohmann::detail::value_t lhs, + nlohmann::detail::value_t rhs) const noexcept + { + return nlohmann::detail::operator<(lhs, rhs); + } +}; + +} // namespace std + +/*! +@brief user-defined string literal for JSON values + +This operator implements a user-defined string literal for JSON objects. It +can be used by adding `"_json"` to a string literal and returns a JSON object +if no parse error occurred. + +@param[in] s a string representation of a JSON object +@param[in] n the length of string @a s +@return a JSON object + +@since version 1.0.0 +*/ +inline nlohmann::json operator "" _json(const char* s, std::size_t n) +{ + return nlohmann::json::parse(s, s + n); +} + +/*! +@brief user-defined string literal for JSON pointer + +This operator implements a user-defined string literal for JSON Pointers. It +can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer +object if no parse error occurred. + +@param[in] s a string representation of a JSON Pointer +@param[in] n the length of string @a s +@return a JSON pointer object + +@since version 2.0.0 +*/ +inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) +{ + return nlohmann::json::json_pointer(std::string(s, n)); +} + +// restore GCC/clang diagnostic settings +#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) + #pragma GCC diagnostic pop +#endif +#if defined(__clang__) + #pragma GCC diagnostic pop +#endif + +// clean up +#undef JSON_CATCH +#undef JSON_THROW +#undef JSON_TRY +#undef JSON_LIKELY +#undef JSON_UNLIKELY +#undef JSON_DEPRECATED +#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION +#undef NLOHMANN_BASIC_JSON_TPL + +#endif diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index db555a413d..57d76e7694 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -7,8 +7,10 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PROJECT_SOURCE_DIR}/src/language/argument.py @@ -18,6 +20,7 @@ add_custom_command ( DEPENDS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/printer.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast_printer.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/visitors_printer.py COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" ) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 53b4e6f8f2..39b7b0863f 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -24,11 +24,21 @@ nodes).write() AstVisitorDeclarationPrinter( - "../visitors/astvisitor.hpp", + "../visitors/ast_visitor.hpp", "AstVisitor", nodes).write() AstVisitorDefinitionPrinter( - "../visitors/astvisitor.cpp", + "../visitors/ast_visitor.cpp", "AstVisitor", + nodes).write() + +JSONVisitorDeclarationPrinter( + "../visitors/json_visitor.hpp", + "JSONVisitor", + nodes).write() + +JSONVisitorDefinitionPrinter( + "../visitors/json_visitor.cpp", + "JSONVisitor", nodes).write() \ No newline at end of file diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 41b4e69fc9..b295aef990 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -51,11 +51,93 @@ class AstVisitorDefinitionPrinter(DefinitionPrinter): """Prints base visitor class method definitions""" def headers(self): - self.writer.write_line('#include "visitors/astvisitor.hpp"', newline=2) + self.writer.write_line('#include "visitors/ast_visitor.hpp"', newline=2) def definitions(self): for node in self.nodes: line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" self.writer.write_line(line, post_gutter=1) self.writer.write_line("node->visitChildren(this);", post_gutter=-1) - self.writer.write_line("}", newline=2) \ No newline at end of file + self.writer.write_line("}", newline=2) + + +class JSONVisitorDeclarationPrinter(DeclarationPrinter): + """Prints visitor class declaration for printing AST in JSON format""" + + def headers(self): + line = '#include "visitors/ast_visitor.hpp"' + self.writer.write_line(line) + line = '#include "printer/json_printer.hpp"' + self.writer.write_line(line, newline=2) + + def class_comment(self): + self.writer.write_line("/* Concrete visitor for printing AST in JSON format */") + + def class_name_declaration(self): + self.writer.write_line("class " + self.classname + " : public AstVisitor {") + + def private_declaration(self): + self.writer.write_line("private:", post_gutter=1) + line = "std::unique_ptr<JSONPrinter> printer;" + self.writer.write_line(line, newline=2, post_gutter=-1) + + def public_declaration(self): + self.writer.write_line("public:", post_gutter=1) + + line = self.classname + "() : printer(new JSONPrinter()) {} " + self.writer.write_line(line) + + line = self.classname + "(std::string filename) : printer(new JSONPrinter(filename)) {}" + self.writer.write_line(line) + + line = self.classname + "(std::stringstream &ss) : printer(new JSONPrinter(ss)) {}" + self.writer.write_line(line, newline=2) + + line = "void flush() { printer->flush(); }" + self.writer.write_line(line, newline=2) + + for node in self.nodes: + line = "void visit" + node.class_name + "(" + node.class_name + "* node) override;" + self.writer.write_line(line) + + self.writer.decrease_gutter() + + +class JSONVisitorDefinitionPrinter(DefinitionPrinter): + """Prints visitor class definition for printing AST in JSON format""" + + def headers(self): + line = '#include "visitors/json_visitor.hpp"' + self.writer.write_line(line, newline=2) + + def definitions(self): + for node in self.nodes: + line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" + self.writer.write_line(line, post_gutter=1) + + if node.has_children(): + self.writer.write_line("printer->pushBlock(node->getType());") + self.writer.write_line("node->visitChildren(this);") + + if node.is_data_type_node(): + if node.class_name == "Integer": + self.writer.write_line("if(!node->macroname) {", post_gutter=1) + self.writer.write_line("std::stringstream ss;") + self.writer.write_line("ss << node->eval();") + self.writer.write_line("printer->addNode(ss.str());", post_gutter=-1) + self.writer.write_line("}") + else: + self.writer.write_line("std::stringstream ss;") + self.writer.write_line("ss << node->eval();") + self.writer.write_line("printer->addNode(ss.str());") + + self.writer.write_line("printer->popBlock();") + + if node.class_name == "Program": + self.writer.write_line("flush();") + else: + self.writer.write_line("printer->addNode(\"" + node.class_name + "\");") + + self.writer.write_line("}", pre_gutter=-1, newline=2) + + self.writer.decrease_gutter() \ No newline at end of file diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index ad09c12f47..aa128f927f 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -281,6 +281,7 @@ ELSE { return token_symbol(yytext, loc, Token::REACTION); } + /* \todo : should be parser error instead of exception */ auto msg = "Lexer Error : Invalid context, no token matched for ~"; throw std::runtime_error(msg); } diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt new file mode 100644 index 0000000000..3818d9d6c4 --- /dev/null +++ b/src/nmodl/printer/CMakeLists.txt @@ -0,0 +1,13 @@ +#============================================================================= +# Printer sources +#============================================================================= +set(PRINTER_SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/printer/json_printer.cpp +) + +#============================================================================= +# Printer library +#============================================================================= +add_library(printer + STATIC + ${PRINTER_SOURCE_FILES}) \ No newline at end of file diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp new file mode 100644 index 0000000000..1aed46b003 --- /dev/null +++ b/src/nmodl/printer/json_printer.cpp @@ -0,0 +1,76 @@ +#include "printer/json_printer.hpp" + +/// By default dump output to std::cout +JSONPrinter::JSONPrinter() : + result_stream(new std::ostream(std::cout.rdbuf())) +{} + +// Dumpt output to stringstream +JSONPrinter::JSONPrinter(std::stringstream& ss) : + result_stream(new std::ostream(ss.rdbuf())) +{} + +/// Dump output to provided file +JSONPrinter::JSONPrinter(std::string fname) { + if(fname.empty()) { + throw std::runtime_error("Empty filename for JSONPrinter"); + } + + ofs.open(fname.c_str()); + + if(ofs.fail()) { + auto msg = "Error while opening file '" + fname + "' for JSONPrinter"; + throw std::runtime_error(msg); + } + + sbuf = ofs.rdbuf(); + result_stream = std::make_shared<std::ostream>(sbuf); +} + +/// Add node to json (typically basic type) +void JSONPrinter::addNode(std::string name) { + if(!block) { + auto text = "Block not initialized (pushBlock missing?)"; + throw std::logic_error(text); + } + + json j; + j["value"] = name; + block->front().push_back(j); +} + +/// Add new json object (typically start of new block) +/// name here is type of new block encountered +void JSONPrinter::pushBlock(std::string name) { + if(block) { + stack.push(block); + } + + json j; + j[name] = json::array(); + block = std::shared_ptr<json>(new json(j)); +} + +/// We finished processing a block, add processed block to it's parent block +void JSONPrinter::popBlock() { + if (!stack.empty()) { + auto current = block; + block = stack.top(); + block->front().push_back(*current); + stack.pop(); + } +} + +/// Dump json object to stream (typically at the end) +/// nspaces is number of spaces used for indentation +void JSONPrinter::flush(int nspaces) { + if(block) { + if(nspaces) { + *result_stream << (*block).dump(nspaces); + } else { + *result_stream << (*block).dump(); + } + ofs.close(); + block = nullptr; + } +} diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp new file mode 100644 index 0000000000..2bd56adc61 --- /dev/null +++ b/src/nmodl/printer/json_printer.hpp @@ -0,0 +1,60 @@ +#ifndef _JSON_PRINTER_HPP_ +#define _JSON_PRINTER_HPP_ + +#include <stack> +#include <fstream> +#include <iostream> + +#include "json/json.hpp" + +using json = nlohmann::json; + +/** + * \class JSONPrinter + * \brief Helper class for printing AST in JSON format + * + * We need to print AST in human readable format for + * debugging or visualization of in memory structure. + * This printer class provides simple interface to + * construct JSON object from AST like data structures. + * We use nlohmann's json library which considerably + * simplify implementation. + * + * \todo : We need to explicitly call flush() in order + * to get write/return results. We simply can't dump + * block in popBlock() because block itself will be + * part of other parent elements. Also we are writing + * results to file, stringstream and cout. And hence + * we can't simply reset/cler previously written text. + */ + +class JSONPrinter { + private: + std::ofstream ofs; + std::streambuf* sbuf = nullptr; + + /// common output stream for file, cout or stringstream + std::shared_ptr<std::ostream> result_stream; + + /// single (current) nmodl block / statement + std::shared_ptr<json> block; + + /// stack that holds all parent blocks / statements + std::stack<std::shared_ptr<json>> stack; + + public: + JSONPrinter(); + JSONPrinter(std::string filename); + JSONPrinter(std::stringstream& ss); + + ~JSONPrinter() { + flush(); + } + + void pushBlock(std::string); + void addNode(std::string); + void popBlock(); + void flush(int nspaces = 4); +}; + +#endif diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index ea27273dae..2bb2160494 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -2,7 +2,8 @@ # Visitor sources #============================================================================= set(VISITOR_SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/visitors/astvisitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/verbatim_visitor.cpp ) @@ -21,4 +22,4 @@ add_library(visitor add_dependencies(visitor lexer) add_executable(nocmodl_visitor main.cpp) -target_link_libraries(nocmodl_visitor lexer visitor) \ No newline at end of file +target_link_libraries(nocmodl_visitor lexer printer visitor) \ No newline at end of file diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 98f049319a..0a9b6ebd13 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -1,10 +1,13 @@ #include <fstream> #include <iostream> +#include <sstream> #include "parser/nmodl_driver.hpp" -#include "visitors/astvisitor.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/json_visitor.hpp" #include "visitors/verbatim_visitor.hpp" + #include "tclap/CmdLine.h" /** @@ -52,6 +55,14 @@ int main(int argc, char* argv[]) { std::cout << "----VERBATIM VISITOR FINISHED----" << std::endl; } + { + std::stringstream ss; + JSONVisitor v(ss); + v.visitProgram(ast.get()); + + std::cout << "----JSON VISITOR FINISHED----" << std::endl; + } + } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 6ae54243b1..565105b9a2 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -5,7 +5,7 @@ #include <vector> #include "ast/ast.hpp" -#include "visitors/astvisitor.hpp" +#include "visitors/ast_visitor.hpp" /** * \class VerbatimVisitor diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index cb27cb6902..3195785cbb 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -10,11 +10,14 @@ include_directories( add_executable (testmodtoken modtoken/modtoken.cpp) add_executable (testlexer lexer/tokens.cpp) add_executable (testvisitor visitor/visitor.cpp) +add_executable (testprinter printer/printer.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) -target_link_libraries(testvisitor lexer visitor) +target_link_libraries(testvisitor lexer printer visitor) +target_link_libraries(testprinter printer) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) add_test (NAME Visitor COMMAND testvisitor) +add_test (NAME Printer COMMAND testprinter) \ No newline at end of file diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp new file mode 100644 index 0000000000..27dd103f48 --- /dev/null +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -0,0 +1,38 @@ +#define CATCH_CONFIG_MAIN + +#include <string> + +#include "catch.hpp" +#include "printer/json_printer.hpp" + +TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { + SECTION("Stringstream test 1") { + std::stringstream ss; + JSONPrinter p(ss); + + p.pushBlock("A"); + p.addNode("B"); + p.popBlock(); + p.flush(0); + + auto result = "{\"A\":[{\"value\":\"B\"}]}"; + REQUIRE(ss.str() == result); + } + + SECTION("Stringstream test 2") { + std::stringstream ss; + JSONPrinter p(ss); + + p.pushBlock("A"); + p.addNode("B"); + p.addNode("C"); + p.pushBlock("D"); + p.addNode("E"); + p.popBlock(); + p.popBlock(); + p.flush(0); + + auto result = "{\"A\":[{\"value\":\"B\"},{\"value\":\"C\"},{\"D\":[{\"value\":\"E\"}]}]}"; + REQUIRE(ss.str() == result); + } +} diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index fd37fa0114..61c2d618a8 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -5,9 +5,17 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" #include "visitors/verbatim_visitor.hpp" +#include "visitors/json_visitor.hpp" +#include "json/json.hpp" -std::vector<std::string> verbatim_visitor_process(std::string text) { +using json = nlohmann::json; + +//============================================================================= +// Verbatim visitor tests +//============================================================================= + +std::vector<std::string> run_verbatim_visitor(std::string text) { nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -17,11 +25,10 @@ std::vector<std::string> verbatim_visitor_process(std::string text) { return v.verbatim_blocks(); } -/// test verbatim visitor TEST_CASE("Verbatim Visitor") { SECTION("Single Block") { std::string text = "VERBATIM int a; ENDVERBATIM"; - auto blocks = verbatim_visitor_process(text); + auto blocks = run_verbatim_visitor(text); REQUIRE(blocks.size() == 1); REQUIRE(blocks.front().compare(" int a; ") == 0); @@ -29,10 +36,49 @@ TEST_CASE("Verbatim Visitor") { SECTION("Multiple Blocks") { std::string text = "VERBATIM int a; ENDVERBATIM VERBATIM float b; ENDVERBATIM"; - auto blocks = verbatim_visitor_process(text); + auto blocks = run_verbatim_visitor(text); REQUIRE(blocks.size() == 2); - REQUIRE(blocks.front().compare(" int a; ") == 0); - REQUIRE(blocks.back().compare(" float b; ") == 0); + REQUIRE(blocks[0].compare(" int a; ") == 0); + REQUIRE(blocks[1].compare(" float b; ") == 0); + } +} + +//============================================================================= +// JSON visitor tests +//============================================================================= + +std::string run_json_visitor(std::string text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + std::stringstream ss; + JSONVisitor v(ss); + v.visitProgram(ast.get()); + return ss.str(); +} + +TEST_CASE("JSON Visitor") { + SECTION("Empty NEURON block") { + std::string nmodl_text = "NEURON {}"; + json expected = R"( + { + "Program": [ + { + "NeuronBlock": [ + { + "StatementBlock": [] + } + ] + } + ] + } + )"_json; + + auto json_text = run_json_visitor(nmodl_text); + json result = json::parse(json_text); + + REQUIRE( expected == result); } } \ No newline at end of file From f4a3956cdc9a33a5479d41589357c02cfe67bec1 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 26 Nov 2017 15:42:45 +0100 Subject: [PATCH 023/871] JSONVisitor now exposed compact_json function to control the output JSON format: - chnage compact_mode to compact_json - flush method updated - tests updated Change-Id: Ieca1a5e841f07d4282c233054735a86c60292278 NMODL Repo SHA: BlueBrain/nmodl@7cd5126b9636a899505356d97826979da60faa92 --- src/nmodl/language/visitors_printer.py | 2 ++ src/nmodl/printer/json_printer.cpp | 8 ++++---- src/nmodl/printer/json_printer.hpp | 10 +++++++++- src/nmodl/visitors/main.cpp | 14 ++++++++++++++ test/nmodl/transpiler/printer/printer.cpp | 6 ++++-- test/nmodl/transpiler/visitor/visitor.cpp | 16 ++++++++++++++-- 6 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index b295aef990..909ba90e5f 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -94,6 +94,8 @@ def public_declaration(self): self.writer.write_line(line, newline=2) line = "void flush() { printer->flush(); }" + self.writer.write_line(line) + line = "void compact_json(bool flag) { printer->compact_json(flag); } " self.writer.write_line(line, newline=2) for node in self.nodes: diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 1aed46b003..a74a5d5eae 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -63,12 +63,12 @@ void JSONPrinter::popBlock() { /// Dump json object to stream (typically at the end) /// nspaces is number of spaces used for indentation -void JSONPrinter::flush(int nspaces) { +void JSONPrinter::flush() { if(block) { - if(nspaces) { - *result_stream << (*block).dump(nspaces); - } else { + if(compact) { *result_stream << (*block).dump(); + } else { + *result_stream << (*block).dump(2); } ofs.close(); block = nullptr; diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 2bd56adc61..c79b71667d 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -42,6 +42,9 @@ class JSONPrinter { /// stack that holds all parent blocks / statements std::stack<std::shared_ptr<json>> stack; + /// true if need to print json in compact format + bool compact = false; + public: JSONPrinter(); JSONPrinter(std::string filename); @@ -54,7 +57,12 @@ class JSONPrinter { void pushBlock(std::string); void addNode(std::string); void popBlock(); - void flush(int nspaces = 4); + void flush(); + + /// print json in compact mode + void compact_json(bool flag) { + compact = flag; + } }; #endif diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 0a9b6ebd13..b5bd2da6ee 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -20,11 +20,16 @@ int main(int argc, char* argv[]) { TCLAP::CmdLine cmd("NMODL Visitor: Standalone visitor program for NMODL"); TCLAP::ValueArg<std::string> filearg( "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); + TCLAP::SwitchArg verbosearg("", "verbose", "Enable verbose output", false); cmd.add(filearg); + cmd.add(verbosearg); + cmd.parse(argc, argv); std::string filename = filearg.getValue(); + bool verbose = verbosearg.getValue(); + std::ifstream file(filename); if (!file.good()) { @@ -58,9 +63,18 @@ int main(int argc, char* argv[]) { { std::stringstream ss; JSONVisitor v(ss); + + /// to get compact json we can set compact mode + /// v.compact_json(true); + v.visitProgram(ast.get()); std::cout << "----JSON VISITOR FINISHED----" << std::endl; + + if(verbose) { + std::cout << "RESULT OF JSON VISITOR : " << std::endl; + std::cout << ss.str(); + } } } catch (TCLAP::ArgException& e) { diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 27dd103f48..e0df777947 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -9,11 +9,12 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Stringstream test 1") { std::stringstream ss; JSONPrinter p(ss); + p.compact_json(true); p.pushBlock("A"); p.addNode("B"); p.popBlock(); - p.flush(0); + p.flush(); auto result = "{\"A\":[{\"value\":\"B\"}]}"; REQUIRE(ss.str() == result); @@ -22,6 +23,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Stringstream test 2") { std::stringstream ss; JSONPrinter p(ss); + p.compact_json(true); p.pushBlock("A"); p.addNode("B"); @@ -30,7 +32,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { p.addNode("E"); p.popBlock(); p.popBlock(); - p.flush(0); + p.flush(); auto result = "{\"A\":[{\"value\":\"B\"},{\"value\":\"C\"},{\"D\":[{\"value\":\"E\"}]}]}"; REQUIRE(ss.str() == result); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 61c2d618a8..408a6ff1fe 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -48,19 +48,23 @@ TEST_CASE("Verbatim Visitor") { // JSON visitor tests //============================================================================= -std::string run_json_visitor(std::string text) { +std::string run_json_visitor(std::string text, bool compact = false) { nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); std::stringstream ss; JSONVisitor v(ss); + + /// if compact is true then we get compact json output + v.compact_json(compact); + v.visitProgram(ast.get()); return ss.str(); } TEST_CASE("JSON Visitor") { - SECTION("Empty NEURON block") { + SECTION("JSON object test") { std::string nmodl_text = "NEURON {}"; json expected = R"( { @@ -81,4 +85,12 @@ TEST_CASE("JSON Visitor") { REQUIRE( expected == result); } + + SECTION("JSON text test (compact format)") { + std::string nmodl_text = "NEURON {}"; + std::string expected = "{\"Program\":[{\"NeuronBlock\":[{\"StatementBlock\":[]}]}]}"; + + auto result = run_json_visitor(nmodl_text, true); + REQUIRE( result == expected); + } } \ No newline at end of file From 22923b7bad38cd69894a7199cab1e5791e538b18 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 26 Nov 2017 16:57:37 +0100 Subject: [PATCH 024/871] Updated target for clang-format and minor updates - Renamed clangformat to clang-format - We explicitly now add files These is still an issue with make-clangformat but it's working now. Change-Id: I9e8d61b7c5043b8f214267c4ca733059804f0fcd NMODL Repo SHA: BlueBrain/nmodl@769b942c96728d16eca8db1ed86b0f252c21ab2a --- .clang-format | 2 +- cmake/nmodl/CMakeLists.txt | 6 +++-- src/nmodl/ast/CMakeLists.txt | 8 +++++++ src/nmodl/ast/ast_utils.hpp | 6 +++-- src/nmodl/lexer/CMakeLists.txt | 27 ++++++++++++++++++----- src/nmodl/lexer/main.cpp | 5 ++--- src/nmodl/lexer/modtoken.hpp | 6 +++-- src/nmodl/lexer/nmodl_utils.cpp | 3 +-- src/nmodl/lexer/nmodl_utils.hpp | 3 +-- src/nmodl/lexer/token_mapping.cpp | 23 ++++++++++--------- src/nmodl/lexer/token_mapping.hpp | 2 +- src/nmodl/parser/CMakeLists.txt | 12 +++++++++- src/nmodl/parser/main.cpp | 5 ++--- src/nmodl/parser/nmodl_driver.cpp | 6 ++--- src/nmodl/parser/nmodl_driver.hpp | 5 +---- src/nmodl/printer/CMakeLists.txt | 14 ++++++++++-- src/nmodl/printer/json_printer.cpp | 22 +++++++++--------- src/nmodl/utils/CMakeLists.txt | 9 ++++++++ src/nmodl/utils/common_utils.hpp | 0 src/nmodl/utils/string_utils.hpp | 5 ++--- src/nmodl/visitors/CMakeLists.txt | 19 ++++++++++++---- src/nmodl/visitors/main.cpp | 8 +++---- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_visitor.hpp | 3 --- test/nmodl/transpiler/CMakeLists.txt | 14 +++++++++++- test/nmodl/transpiler/visitor/visitor.cpp | 4 ++-- 26 files changed, 143 insertions(+), 76 deletions(-) create mode 100644 src/nmodl/ast/CMakeLists.txt create mode 100644 src/nmodl/utils/CMakeLists.txt mode change 100755 => 100644 src/nmodl/utils/common_utils.hpp diff --git a/.clang-format b/.clang-format index d3599f260b..88e0406fbe 100644 --- a/.clang-format +++ b/.clang-format @@ -37,7 +37,7 @@ BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 120 +ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e443ea9104..0dc77b2f8c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -44,10 +44,12 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) +add_subdirectory(src/ast) add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) add_subdirectory(src/printer) +add_subdirectory(src/utils) add_subdirectory(src/visitors) #============================================================================= @@ -71,9 +73,9 @@ add_executable(nocmodl target_link_libraries(nocmodl lexer visitor) #============================================================================= -# Clang format options +# Clang format target #============================================================================= -add_custom_target(clangformat +add_custom_target(clang-format COMMAND clang-format -i --style=file ${FILES_FOR_CLANG_FORMAT}) diff --git a/src/nmodl/ast/CMakeLists.txt b/src/nmodl/ast/CMakeLists.txt new file mode 100644 index 0000000000..815d0e961c --- /dev/null +++ b/src/nmodl/ast/CMakeLists.txt @@ -0,0 +1,8 @@ +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${PROJECT_SOURCE_DIR}/src/ast/ast_utils.hpp + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 0b580e6fec..a0121dcf41 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -6,7 +6,6 @@ #include "lexer/modtoken.hpp" namespace ast { - /* enumaration of all binary operators in the language */ typedef enum { BOP_ADDITION, @@ -78,7 +77,10 @@ namespace ast { return nullptr; } - virtual AST* clone() { std::cout << "\n ERROR: clone() not implemented! \n"; abort(); } + virtual AST* clone() { + std::cout << "\n ERROR: clone() not implemented! \n"; + abort(); + } }; } // namespace ast diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index df23531aec..164654fdd6 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -11,6 +11,7 @@ set(AST_SOURCE_FILES ) set(NMODL_DRIVER_FILES + ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp ) @@ -20,11 +21,15 @@ set_source_files_properties( ) set(LEXER_SOURCE_FILES - token_mapping.cpp - nmodl_utils.cpp - modtoken.cpp - nmodl_base_lexer.cpp - verbatim_lexer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modl.h + ${CMAKE_CURRENT_SOURCE_DIR}/token_mapping.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/token_mapping.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.cpp ${NMODL_DRIVER_FILES} ) @@ -89,4 +94,14 @@ add_library(lexer ${AST_SOURCE_FILES}) add_executable(nocmodl_lexer main.cpp) -target_link_libraries(nocmodl_lexer lexer) \ No newline at end of file +target_link_libraries(nocmodl_lexer lexer) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${LEXER_SOURCE_FILES} + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main.cpp index 90d9b5e53e..a855ce21bf 100644 --- a/src/nmodl/lexer/main.cpp +++ b/src/nmodl/lexer/main.cpp @@ -16,8 +16,8 @@ int main(int argc, char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Lexer: Standalone lexer program for NMODL"); - TCLAP::ValueArg<std::string> filearg( - "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); + TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, + "../test/input/channel.mod", "string"); cmd.add(filearg); cmd.parse(argc, argv); @@ -118,7 +118,6 @@ int main(int argc, char* argv[]) { } } } - } catch (TCLAP::ArgException& e) { std::cout << std::endl << "Argument Error: " << e.error() << " for arg " << e.argId(); return 1; diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index a0874bb69b..0f22ab5521 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -42,8 +42,10 @@ class ModToken { public: ModToken() : pos(nullptr, 0){}; - explicit ModToken(bool ext) : pos(nullptr, 0), external(ext) {} - ModToken(std::string str, int tok, nmodl::location& loc) : name(str), token(tok), pos(loc) {} + explicit ModToken(bool ext) : pos(nullptr, 0), external(ext) { + } + ModToken(std::string str, int tok, nmodl::location& loc) : name(str), token(tok), pos(loc) { + } ModToken* clone() const { return new ModToken(*this); diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index afeb879160..b4fbb29443 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -8,7 +8,6 @@ #include "utils/string_utils.hpp" namespace nmodl { - /// create symbol for double/real ast class SymbolType double_symbol(double value, PositionType& pos) { ModToken token(std::to_string(value), Token::REAL, pos); @@ -319,4 +318,4 @@ namespace nmodl { } } -} // namespace nmodl +} // namespace nmodl diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index 8f66005175..ea2e34834e 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -14,7 +14,6 @@ */ namespace nmodl { - using PositionType = nmodl::location; using SymbolType = nmodl::Parser::symbol_type; using Token = nmodl::Parser::token; @@ -27,4 +26,4 @@ namespace nmodl { SymbolType string_symbol(std::string text, PositionType& pos); SymbolType token_symbol(std::string text, PositionType& pos, TokenType token = Token::UNKNOWN); -} // namespace nmodl \ No newline at end of file +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index a8decda830..ee53d00ef4 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -7,12 +7,11 @@ #include "parser/nmodl_parser.hpp" namespace nmodl { - using Token = nmodl::Parser::token; using TokenType = nmodl::Parser::token_type; namespace internal { - + // clang-format off /** Keywords from NMODL language : name and token pair * * \todo Some keywords have different token names, e.g. TITLE @@ -111,7 +110,7 @@ namespace nmodl { {"PROTECT", Token::PROTECT}, {"MUTEXLOCK", Token::NRNMUTEXLOCK}, {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; - + // clang-format on /// numerical methods supported in nmodl struct MethodInfo { @@ -121,10 +120,13 @@ namespace nmodl { /// if it's a variable timestep method int varstep = 0; - MethodInfo() {} - MethodInfo(long s, int v) : subtype(s), varstep(v) {} + MethodInfo() { + } + MethodInfo(long s, int v) : subtype(s), varstep(v) { + } }; + // clang-format off static std::map<std::string, MethodInfo> methods = {{"adams", MethodInfo(DERF | KINF, 0)}, {"runge", MethodInfo(DERF | KINF, 0)}, {"euler", MethodInfo(DERF | KINF, 0)}, @@ -144,7 +146,7 @@ namespace nmodl { {"after_cvode", MethodInfo(0, 0)}, {"cvode_t", MethodInfo(0, 0)}, {"cvode_t_v", MethodInfo(0, 0)}}; - + // clang-format on /** In the original implementation different vectors were created for * extdef, extdef2, extdef3, extdef4 etc. Instead of that we are changing @@ -167,6 +169,7 @@ namespace nmodl { enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; + // clang-format off static std::map<std::string, DefinitionType> extern_definitions = { {"first_time", DefinitionType::EXT_DOUBLE}, {"error", DefinitionType::EXT_DOUBLE}, @@ -270,7 +273,7 @@ namespace nmodl { {"schedule", DefinitionType::EXT_5}, {"set_seed", DefinitionType::EXT_5}, {"nrn_random_play", DefinitionType::EXT_5}}; - + // clang-format on /** Internal NEURON variables that can be used in nmod files. The compiler * passes like scope checker need to know if certain variable is undefined. @@ -297,12 +300,10 @@ namespace nmodl { return extern_definitions[name]; } - } // namespace internal - + } // namespace internal /// methods exposed to lexer, parser and compilers passes - bool is_keyword(std::string name) { return (internal::keywords.find(name) != internal::keywords.end()); } @@ -335,4 +336,4 @@ namespace nmodl { return result; } -} // namespace nmodl \ No newline at end of file +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 8a69c01173..63a7c49f20 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -9,4 +9,4 @@ namespace nmodl { nmodl::Parser::token_type token_type(std::string name); std::vector<std::string> all_external_variables(); -} // namespace nmodl \ No newline at end of file +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index a89a9cec7d..4732437daf 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -6,4 +6,14 @@ # so no eed to have parser as a separate library add_executable(nocmodl_parser main.cpp) -target_link_libraries(nocmodl_parser lexer) \ No newline at end of file +target_link_libraries(nocmodl_parser lexer) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_context.hpp + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp index bc30296c97..21c55ec926 100644 --- a/src/nmodl/parser/main.cpp +++ b/src/nmodl/parser/main.cpp @@ -15,8 +15,8 @@ int main(int argc, char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Parser: Standalone parser program for NMODL"); - TCLAP::ValueArg<std::string> filearg( - "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); + TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, + "../test/input/channel.mod", "string"); cmd.add(filearg); cmd.parse(argc, argv); @@ -41,7 +41,6 @@ int main(int argc, char* argv[]) { // driver.parse_file(filename); std::cout << "----PARSING FINISHED----" << std::endl; - } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 2e8c22d3cf..671a9659da 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -5,8 +5,8 @@ #include "parser/nmodl_driver.hpp" namespace nmodl { - - Driver::Driver(bool strace, bool ptrace) : trace_scanner(strace), trace_parser(ptrace) {} + Driver::Driver(bool strace, bool ptrace) : trace_scanner(strace), trace_parser(ptrace) { + } /// parse nmodl file provided as istream bool Driver::parse_stream(std::istream& in) { @@ -64,4 +64,4 @@ namespace nmodl { throw std::runtime_error("Trying to get undefined macro / define :" + name); } -} // namespace nmodl +} // namespace nmodl diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index af2a7961fe..8f86e68a22 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -8,7 +8,6 @@ /** The nmodl namespace encapsulates everything related to nmodl parsing * which includes lexer, parser, driver, keywords, token mapping etc. */ namespace nmodl { - /** * \class Driver * \brief Class that binds all pieces together for parsing nmodl file @@ -60,7 +59,6 @@ namespace nmodl { bool verbose = false; public: - /// file or input stream name (used by scanner for position), see todo std::string streamname; @@ -93,7 +91,6 @@ namespace nmodl { std::shared_ptr<ast::Program> ast() const { return astRoot; } - }; -} // namespace nmodl +} // namespace nmodl diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 3818d9d6c4..0666abc151 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -2,7 +2,8 @@ # Printer sources #============================================================================= set(PRINTER_SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/printer/json_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp ) #============================================================================= @@ -10,4 +11,13 @@ set(PRINTER_SOURCE_FILES #============================================================================= add_library(printer STATIC - ${PRINTER_SOURCE_FILES}) \ No newline at end of file + ${PRINTER_SOURCE_FILES}) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${PRINTER_SOURCE_FILES} + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index a74a5d5eae..4f04ddd6d9 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -1,24 +1,22 @@ #include "printer/json_printer.hpp" /// By default dump output to std::cout -JSONPrinter::JSONPrinter() : - result_stream(new std::ostream(std::cout.rdbuf())) -{} +JSONPrinter::JSONPrinter() : result_stream(new std::ostream(std::cout.rdbuf())) { +} // Dumpt output to stringstream -JSONPrinter::JSONPrinter(std::stringstream& ss) : - result_stream(new std::ostream(ss.rdbuf())) -{} +JSONPrinter::JSONPrinter(std::stringstream& ss) : result_stream(new std::ostream(ss.rdbuf())) { +} /// Dump output to provided file JSONPrinter::JSONPrinter(std::string fname) { - if(fname.empty()) { + if (fname.empty()) { throw std::runtime_error("Empty filename for JSONPrinter"); } ofs.open(fname.c_str()); - if(ofs.fail()) { + if (ofs.fail()) { auto msg = "Error while opening file '" + fname + "' for JSONPrinter"; throw std::runtime_error(msg); } @@ -29,7 +27,7 @@ JSONPrinter::JSONPrinter(std::string fname) { /// Add node to json (typically basic type) void JSONPrinter::addNode(std::string name) { - if(!block) { + if (!block) { auto text = "Block not initialized (pushBlock missing?)"; throw std::logic_error(text); } @@ -42,7 +40,7 @@ void JSONPrinter::addNode(std::string name) { /// Add new json object (typically start of new block) /// name here is type of new block encountered void JSONPrinter::pushBlock(std::string name) { - if(block) { + if (block) { stack.push(block); } @@ -64,8 +62,8 @@ void JSONPrinter::popBlock() { /// Dump json object to stream (typically at the end) /// nspaces is number of spaces used for indentation void JSONPrinter::flush() { - if(block) { - if(compact) { + if (block) { + if (compact) { *result_stream << (*block).dump(); } else { *result_stream << (*block).dump(2); diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt new file mode 100644 index 0000000000..262cd061aa --- /dev/null +++ b/src/nmodl/utils/CMakeLists.txt @@ -0,0 +1,9 @@ +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp old mode 100755 new mode 100644 diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 65df2cde6c..2bae5e20cd 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -12,7 +12,6 @@ */ namespace stringutils { - /// Trim from start static inline std::string& ltrim(std::string& s) { s.erase(s.begin(), @@ -52,7 +51,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; @@ -75,4 +74,4 @@ namespace stringutils { return elems; } -} // namespace stringutils +} // namespace stringutils diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 2bb2160494..c4c9d31299 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -2,9 +2,10 @@ # Visitor sources #============================================================================= set(VISITOR_SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/verbatim_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/ast_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp ) set_source_files_properties( @@ -22,4 +23,14 @@ add_library(visitor add_dependencies(visitor lexer) add_executable(nocmodl_visitor main.cpp) -target_link_libraries(nocmodl_visitor lexer printer visitor) \ No newline at end of file +target_link_libraries(nocmodl_visitor lexer printer visitor) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${VISITOR_SOURCE_FILES} + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index b5bd2da6ee..ca1a407c34 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -7,7 +7,6 @@ #include "visitors/json_visitor.hpp" #include "visitors/verbatim_visitor.hpp" - #include "tclap/CmdLine.h" /** @@ -18,8 +17,8 @@ int main(int argc, char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Visitor: Standalone visitor program for NMODL"); - TCLAP::ValueArg<std::string> filearg( - "", "file", "NMODL input file path", false, "../test/input/channel.mod", "string"); + TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, + "../test/input/channel.mod", "string"); TCLAP::SwitchArg verbosearg("", "verbose", "Enable verbose output", false); cmd.add(filearg); @@ -71,12 +70,11 @@ int main(int argc, char* argv[]) { std::cout << "----JSON VISITOR FINISHED----" << std::endl; - if(verbose) { + if (verbose) { std::cout << "RESULT OF JSON VISITOR : " << std::endl; std::cout << ss.str(); } } - } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 6f9cfd8584..35803801cb 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -3,7 +3,7 @@ void VerbatimVisitor::visitVerbatim(Verbatim* node) { std::string block; - if(node->statement) { + if (node->statement) { block = node->statement->eval(); } diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 565105b9a2..4eb370c926 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -18,9 +18,7 @@ * in ModelDB. */ - class VerbatimVisitor : public AstVisitor { - private: /// flag to enable/disable printing blocks as we visit them bool verbose = false; @@ -42,5 +40,4 @@ class VerbatimVisitor : public AstVisitor { } }; - #endif diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 3195785cbb..9ef90aecdd 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -20,4 +20,16 @@ target_link_libraries(testprinter printer) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) add_test (NAME Visitor COMMAND testvisitor) -add_test (NAME Printer COMMAND testprinter) \ No newline at end of file +add_test (NAME Printer COMMAND testprinter) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/modtoken/modtoken.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lexer/tokens.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor/visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/printer/printer.cpp + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 408a6ff1fe..2dacfef6a5 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -83,7 +83,7 @@ TEST_CASE("JSON Visitor") { auto json_text = run_json_visitor(nmodl_text); json result = json::parse(json_text); - REQUIRE( expected == result); + REQUIRE(expected == result); } SECTION("JSON text test (compact format)") { @@ -91,6 +91,6 @@ TEST_CASE("JSON Visitor") { std::string expected = "{\"Program\":[{\"NeuronBlock\":[{\"StatementBlock\":[]}]}]}"; auto result = run_json_visitor(nmodl_text, true); - REQUIRE( result == expected); + REQUIRE(result == expected); } } \ No newline at end of file From 2c0a57b7425c6abd33a5341dc11de664ccc32fb7 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 27 Nov 2017 23:16:52 +0100 Subject: [PATCH 025/871] Adding enum class for every AST node Previously we were returning string as AST type for every node. This was quickly added for symtab. We now add separate enum class. - getType and getTypeName added for convenience - abort replaced with logic_error in AST class (squashed from sandbox/kumbhar/ast-type) Change-Id: Idaa6ca12dd1133c1fa5ea377056170e54acb58d5 NMODL Repo SHA: BlueBrain/nmodl@a3aaa71c6cf1800d1fcdbe176799810e0bb5f44c --- src/nmodl/ast/ast_utils.hpp | 21 ++++++++---- src/nmodl/language/ast_printer.py | 44 +++++++++++++++++--------- src/nmodl/language/utils.py | 9 ++++++ src/nmodl/language/visitors_printer.py | 2 +- src/nmodl/parser/nmodl.yy | 2 +- 5 files changed, 54 insertions(+), 24 deletions(-) create mode 100644 src/nmodl/language/utils.py diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index a0121dcf41..b50300a131 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -54,6 +54,9 @@ namespace ast { /* base class for all visitors implementation */ class Visitor; + /* enum class for ast types */ + enum class Type; + /* define abstract base class for all AST nodes * this also serves to define the visitable objects. */ @@ -68,19 +71,23 @@ namespace ast { /* all AST nodes provide visit children and accept methods */ virtual void visitChildren(Visitor* v) = 0; virtual void accept(Visitor* v) = 0; - virtual std::string getType() = 0; - /* @todo: please revisit this. adding quickly for symtab */ + virtual Type getType() = 0; + virtual std::string getTypeName() = 0; + virtual std::string getName() { - return ""; + throw std::logic_error("getName() not implemented"); + } + + virtual AST* clone() { + throw std::logic_error("clone() not implemented"); } + + /* @todo: please revisit this. adding quickly for symtab */ virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ return nullptr; } - virtual AST* clone() { - std::cout << "\n ERROR: clone() not implemented! \n"; - abort(); - } + virtual ~AST() {} }; } // namespace ast diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index fa69a20917..c6940055c3 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -1,11 +1,21 @@ -import itertools from printer import * +from utils import * from node_info import ORDER_VAR_NAME class AstDeclarationPrinter(DeclarationPrinter): """Prints all AST nodes class declarations""" + def ast_types(self): + """ print ast type for every ast node """ + self.writer.write_line("enum class Type {", post_gutter=1) + for node in self.nodes[:-1]: + name = node_ast_type(node.class_name) + "," + self.writer.write_line(name) + name = node_ast_type(self.nodes[-1].class_name) + self.writer.write_line(name) + self.writer.write_line("};", pre_gutter=-1) + def headers(self): self.writer.write_line("#include <iostream>") self.writer.write_line("#include <memory>") @@ -24,17 +34,21 @@ def forward_declarations(self): for node in self.nodes: self.writer.write_line("class " + node.class_name + ";") + self.writer.write_line() + self.writer.write_line("/* Type for every ast node */") + self.ast_types() + self.writer.write_line() self.writer.write_line("/* std::vector for convenience */") - for node in itertools.chain(self.nodes): - type_name = "std::vector<std::shared_ptr<" + node.class_name + ">>" - self.writer.write_line("using " + node.class_name + "Vector = " + type_name + ";") + for node in self.nodes: + typename = "std::vector<std::shared_ptr<" + node.class_name + ">>" + self.writer.write_line("using " + node.class_name + "Vector = " + typename + ";") created_types = [] # iterate over all node types - for node in itertools.chain(self.nodes): + for node in self.nodes: type = (node.get_typename(), node.class_name.lower() + "_ptr") if type not in created_types: created_types.append(type) @@ -151,12 +165,12 @@ def ast_classes_declaration(self): else: method = "getName" - self.writer.write_line("std::string getName() override {") + self.writer.write_line("virtual std::string getName() override {") self.writer.write_line(" return " + varname + "->" + method + "();") self.writer.write_line("}") if not node.has_token: - self.writer.write_line(virtual + "ModToken* getToken()" + override + " {") + self.writer.write_line(virtual + "ModToken* getToken() override {") self.writer.write_line(" return " + varname + "->getToken();") self.writer.write_line("}") @@ -166,16 +180,16 @@ def ast_classes_declaration(self): self.writer.write_line("int getOrder() " + " { return " + ORDER_VAR_NAME + "->eval(); }") # TODO: returning typename for name? check the usage of this and fix in better way - if node.has_parent_block_node() and not get_method_added: - self.writer.write_line("std::string getName() override { return getType(); }") + self.writer.write_line("virtual std::string getTypeName() override { return \"" + node.class_name + "\"; }") # all member functions - self.writer.write_line(virtual + "void visitChildren (Visitor* v) " + override + ";") - self.writer.write_line(virtual + "void accept(Visitor* v) " + override + " { v->visit" + node.class_name + "(this); }") + self.writer.write_line(virtual + "void visitChildren (Visitor* v) override;") + self.writer.write_line(virtual + "void accept(Visitor* v) override { v->visit" + node.class_name + "(this); }") # TODO: type should declared as enum class - self.writer.write_line(virtual + "std::string getType() " + override + " { return \"" + node.class_name + "\"; }") - self.writer.write_line(virtual + node.class_name + "* clone() " + override + " { return new " + node.class_name + "(*this); }") + typename = node_ast_type(node.class_name) + self.writer.write_line(virtual + "Type getType() override { return Type::" + typename + "; }") + self.writer.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") if node.has_token: self.writer.write_line(virtual + "ModToken* getToken() " + override + " { return token.get(); }") @@ -300,8 +314,8 @@ def definitions(self): for member in ptr_members: # bit hack here : remove pointer because we are creating smart pointer - type_name = member[0].replace("*", "") - self.writer.write_line(" this->" + member[1] + " = std::shared_ptr<" + type_name + ">(" + member[1] + ");") + typename = member[0].replace("*", "") + self.writer.write_line(" this->" + member[1] + " = std::shared_ptr<" + typename + ">(" + member[1] + ");") self.writer.write_line("}", newline=2) diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py new file mode 100644 index 0000000000..6a6c5b3d56 --- /dev/null +++ b/src/nmodl/language/utils.py @@ -0,0 +1,9 @@ +import re + +def camel_case_to_underscore(name): + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) + typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).upper() + return typename + +def node_ast_type(name): + return camel_case_to_underscore(name) \ No newline at end of file diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 909ba90e5f..ed693f4e27 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -118,7 +118,7 @@ def definitions(self): self.writer.write_line(line, post_gutter=1) if node.has_children(): - self.writer.write_line("printer->pushBlock(node->getType());") + self.writer.write_line("printer->pushBlock(node->getTypeName());") self.writer.write_line("node->visitChildren(this);") if node.is_data_type_node(): diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 0561b26583..d09f52db6f 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -476,7 +476,7 @@ model : MODEL LINE_PART define1 : DEFINE1 NAME INTEGER { $$ = new ast::Define($2, $3); - driver.add_defined_var($2->getName(), $3->eval()); + driver.add_defined_var($2->getTypeName(), $3->eval()); } | DEFINE1 error { From eafe3c57403331946f54cfe5f806892f6f008f1d Mon Sep 17 00:00:00 2001 From: Jeremy FOURIAUX <jeremy.fouriaux@epfl.ch> Date: Sun, 3 Dec 2017 16:57:04 +0100 Subject: [PATCH 026/871] "correcting grammar: remove invalid statements as top level objects Change-Id: I72c0c3b6a957719436469f4941fd72b2f68cb27e NMODL Repo SHA: BlueBrain/nmodl@634fcf344cd43aed49f451a9adffb7f4113ef97f --- src/nmodl/parser/nmodl.yy | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index d09f52db6f..dc4425f464 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -402,11 +402,6 @@ top : all all : { $$ = new ast::Program(); } - | all astmt - { - $1->addStatement($2); - $$ = $1; - } | all model { $1->addStatement($2); From 42913d65ea6f37bb8c4488c0111e24103e0cf74d Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 10 Dec 2017 17:38:08 +0100 Subject: [PATCH 027/871] Upgrade to latest catch release : Catch2 Change-Id: Id8a11fc7de6c2ce2ba5c62547ea8b3ce5b706fdb NMODL Repo SHA: BlueBrain/nmodl@7f8db14daa892c34771430acc93f530a28f9639d --- src/nmodl/ext/catch/catch.hpp | 16210 ++++++++++++++++++-------------- 1 file changed, 9403 insertions(+), 6807 deletions(-) diff --git a/src/nmodl/ext/catch/catch.hpp b/src/nmodl/ext/catch/catch.hpp index de61226cf6..362f8693f7 100644 --- a/src/nmodl/ext/catch/catch.hpp +++ b/src/nmodl/ext/catch/catch.hpp @@ -1,17 +1,17 @@ /* - * Catch v1.2.1 - * Generated: 2015-06-30 18:23:27.961086 + * Catch v2.0.1 + * Generated: 2017-11-03 11:53:39.642003 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. + * Copyright (c) 2017 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp -#define TWOBLUECUBES_CATCH_HPP_INCLUDED #ifdef __clang__ # pragma clang system_header @@ -19,9 +19,7 @@ # pragma GCC system_header #endif -// #included from: internal/catch_suppress_warnings.h - -#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED +// start catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro @@ -34,21 +32,44 @@ # pragma clang diagnostic ignored "-Wunused-variable" # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wc++98-compat" -# pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" # pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wparentheses" + # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif - +// end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +#endif + +// start catch_platform.h + +#ifdef __APPLE__ +# include <TargetConditionals.h> +# if TARGET_OS_MAC == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) +# define CATCH_PLATFORM_WINDOWS #endif +// end catch_platform.h #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED @@ -56,270 +77,185 @@ # endif #endif -// #included from: internal/catch_notimplemented_exception.h -#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_H_INCLUDED - -// #included from: catch_common.h -#define TWOBLUECUBES_CATCH_COMMON_H_INCLUDED - -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) - -#define INTERNAL_CATCH_STRINGIFY2( expr ) #expr -#define INTERNAL_CATCH_STRINGIFY( expr ) INTERNAL_CATCH_STRINGIFY2( expr ) +// start catch_tag_alias_autoregistrar.h -#include <sstream> -#include <stdexcept> -#include <algorithm> +// start catch_common.h -// #included from: catch_compiler_capabilities.h -#define TWOBLUECUBES_CATCH_COMPILER_CAPABILITIES_HPP_INCLUDED +// start catch_compiler_capabilities.h -// Detect a number of compiler features - mostly C++11/14 conformance - by compiler +// Detect a number of compiler features - by compiler // The following features are defined: // -// CATCH_CONFIG_CPP11_NULLPTR : is nullptr supported? -// CATCH_CONFIG_CPP11_NOEXCEPT : is noexcept supported? -// CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods -// CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? -// CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported - -// CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? - -// CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** // In general each macro has a _NO_<feature name> form -// (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. -// All the C++11 features can be disabled with CATCH_CONFIG_NO_CPP11 - -#ifdef __clang__ +#ifdef __cplusplus -# if __has_feature(cxx_nullptr) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# if __cplusplus >= 201402L +# define CATCH_CPP14_OR_GREATER # endif -# if __has_feature(cxx_noexcept) -# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -# endif +#endif -#endif // __clang__ +#ifdef __clang__ -//////////////////////////////////////////////////////////////////////////////// -// Borland -#ifdef __BORLANDC__ +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic pop" ) -#endif // __BORLANDC__ +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic push" ) \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic pop" ) + +#endif // __clang__ //////////////////////////////////////////////////////////////////////////////// -// EDG -#ifdef __EDG_VERSION__ +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) -#endif // __EDG_VERSION__ +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# endif -//////////////////////////////////////////////////////////////////////////////// -// Digital Mars -#ifdef __DMC__ +#endif -#endif // __DMC__ +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif //////////////////////////////////////////////////////////////////////////////// -// GCC -#ifdef __GNUC__ +// Cygwin +#ifdef __CYGWIN__ -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -#endif +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE -#endif // __GNUC__ +#endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #ifdef _MSC_VER -#if (_MSC_VER >= 1600) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -#endif - -#if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) -#define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -#define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -#endif +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif #endif // _MSC_VER -// Use variadic macros if the compiler supports them -#if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ - ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ - ( defined __GNUC__ && __GNUC__ >= 3 ) || \ - ( !defined __cplusplus && __STDC_VERSION__ >= 199901L || __cplusplus >= 201103L ) - -#define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS - -#endif - //////////////////////////////////////////////////////////////////////////////// -// C++ language feature support - -// catch all support for C++11 -#if (__cplusplus >= 201103L) - -# define CATCH_CPP11_OR_GREATER - -# if !defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) -# define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR -# endif -# ifndef CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -# define CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT -# endif - -# ifndef CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -# define CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS -# endif - -# ifndef CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM -# define CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM -# endif - -# ifndef CATCH_INTERNAL_CONFIG_CPP11_TUPLE -# define CATCH_INTERNAL_CONFIG_CPP11_TUPLE -# endif - -# ifndef CATCH_INTERNAL_CONFIG_VARIADIC_MACROS -# define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS -# endif - -#endif // __cplusplus >= 201103L - -// Now set the actual defines based on the above + anything the user has configured -#if defined(CATCH_INTERNAL_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NO_NULLPTR) && !defined(CATCH_CONFIG_CPP11_NULLPTR) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_NULLPTR +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NO_NOEXCEPT) && !defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_NOEXCEPT + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_NO_GENERATED_METHODS) && !defined(CATCH_CONFIG_CPP11_GENERATED_METHODS) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_GENERATED_METHODS +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_NO_IS_ENUM) && !defined(CATCH_CONFIG_CPP11_IS_ENUM) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_IS_ENUM +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS #endif -#if defined(CATCH_INTERNAL_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_CPP11_NO_TUPLE) && !defined(CATCH_CONFIG_CPP11_TUPLE) && !defined(CATCH_CONFIG_NO_CPP11) -# define CATCH_CONFIG_CPP11_TUPLE + +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS #endif -#if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) -#define CATCH_CONFIG_VARIADIC_MACROS +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS #endif -// noexcept support: -#if defined(CATCH_CONFIG_CPP11_NOEXCEPT) && !defined(CATCH_NOEXCEPT) -# define CATCH_NOEXCEPT noexcept -# define CATCH_NOEXCEPT_IS(x) noexcept(x) +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else -# define CATCH_NOEXCEPT throw() -# define CATCH_NOEXCEPT_IS(x) +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif +#include <iosfwd> +#include <string> +#include <cstdint> + namespace Catch { + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + class NonCopyable { -#ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; -#else - NonCopyable( NonCopyable const& info ); - NonCopyable& operator = ( NonCopyable const& ); -#endif protected: - NonCopyable() {} + NonCopyable(); virtual ~NonCopyable(); }; - class SafeBool { - public: - typedef void (SafeBool::*type)() const; - - static type makeSafe( bool value ) { - return value ? &SafeBool::trueValue : 0; - } - private: - void trueValue() const {} - }; - - template<typename ContainerT> - inline void deleteAll( ContainerT& container ) { - typename ContainerT::const_iterator it = container.begin(); - typename ContainerT::const_iterator itEnd = container.end(); - for(; it != itEnd; ++it ) - delete *it; - } - template<typename AssociativeContainerT> - inline void deleteAllValues( AssociativeContainerT& container ) { - typename AssociativeContainerT::const_iterator it = container.begin(); - typename AssociativeContainerT::const_iterator itEnd = container.end(); - for(; it != itEnd; ++it ) - delete it->second; - } - - bool startsWith( std::string const& s, std::string const& prefix ); - bool endsWith( std::string const& s, std::string const& suffix ); - bool contains( std::string const& s, std::string const& infix ); - void toLowerInPlace( std::string& s ); - std::string toLower( std::string const& s ); - std::string trim( std::string const& str ); - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - - struct pluralise { - pluralise( std::size_t count, std::string const& label ); - - friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); - - std::size_t m_count; - std::string m_label; - }; - struct SourceLineInfo { - SourceLineInfo(); - SourceLineInfo( char const* _file, std::size_t _line ); - SourceLineInfo( SourceLineInfo const& other ); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept; + + SourceLineInfo( SourceLineInfo const& other ) = default; SourceLineInfo( SourceLineInfo && ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo& operator = ( SourceLineInfo && ) = default; -# endif - bool empty() const; - bool operator == ( SourceLineInfo const& other ) const; - bool operator < ( SourceLineInfo const& other ) const; - std::string file; + bool empty() const noexcept; + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // This is just here to avoid compiler warnings with macro constants and boolean literals - inline bool isTrue( bool value ){ return value; } - inline bool alwaysTrue() { return true; } - inline bool alwaysFalse() { return false; } - - void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + bool isTrue( bool value ); + bool alwaysTrue(); + bool alwaysFalse(); // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { - std::string operator+() { - return std::string(); - } + std::string operator+() const; }; template<typename T> T const& operator + ( T const& value, StreamEndStop ) { @@ -327,1088 +263,998 @@ namespace Catch { } } -#define CATCH_INTERNAL_LINEINFO ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) -#define CATCH_INTERNAL_ERROR( msg ) ::Catch::throwLogicError( msg, CATCH_INTERNAL_LINEINFO ); - -#include <ostream> +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) +// end catch_common.h namespace Catch { - class NotImplementedException : public std::exception - { - public: - NotImplementedException( SourceLineInfo const& lineInfo ); - NotImplementedException( NotImplementedException const& ) {} - - virtual ~NotImplementedException() CATCH_NOEXCEPT {} - - virtual const char* what() const CATCH_NOEXCEPT; - - private: - std::string m_what; - SourceLineInfo m_lineInfo; + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch -/////////////////////////////////////////////////////////////////////////////// -#define CATCH_NOT_IMPLEMENTED throw Catch::NotImplementedException( CATCH_INTERNAL_LINEINFO ) +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } -// #included from: internal/catch_context.h -#define TWOBLUECUBES_CATCH_CONTEXT_H_INCLUDED +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h -// #included from: catch_interfaces_generators.h -#define TWOBLUECUBES_CATCH_INTERFACES_GENERATORS_H_INCLUDED +// start catch_interfaces_testcase.h -#include <string> +#include <vector> +#include <memory> namespace Catch { - struct IGeneratorInfo { - virtual ~IGeneratorInfo(); - virtual bool moveNext() = 0; - virtual std::size_t getCurrentIndex() const = 0; - }; - - struct IGeneratorsForTest { - virtual ~IGeneratorsForTest(); + class TestSpec; - virtual IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) = 0; - virtual bool moveNext() = 0; + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); }; - IGeneratorsForTest* createGeneratorsForTest(); - -} // end namespace Catch - -// #included from: catch_ptr.hpp -#define TWOBLUECUBES_CATCH_PTR_HPP_INCLUDED - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -namespace Catch { - - // An intrusive reference counting smart pointer. - // T must implement addRef() and release() methods - // typically implementing the IShared interface - template<typename T> - class Ptr { - public: - Ptr() : m_p( NULL ){} - Ptr( T* p ) : m_p( p ){ - if( m_p ) - m_p->addRef(); - } - Ptr( Ptr const& other ) : m_p( other.m_p ){ - if( m_p ) - m_p->addRef(); - } - ~Ptr(){ - if( m_p ) - m_p->release(); - } - void reset() { - if( m_p ) - m_p->release(); - m_p = NULL; - } - Ptr& operator = ( T* p ){ - Ptr temp( p ); - swap( temp ); - return *this; - } - Ptr& operator = ( Ptr const& other ){ - Ptr temp( other ); - swap( temp ); - return *this; - } - void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } - T* get() { return m_p; } - const T* get() const{ return m_p; } - T& operator*() const { return *m_p; } - T* operator->() const { return m_p; } - bool operator !() const { return m_p == NULL; } - operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + using ITestCasePtr = std::shared_ptr<ITestInvoker>; - private: - T* m_p; - }; + class TestCase; + struct IConfig; - struct IShared : NonCopyable { - virtual ~IShared(); - virtual void addRef() const = 0; - virtual void release() const = 0; + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector<TestCase> const& getAllTests() const = 0; + virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; }; - template<typename T = IShared> - struct SharedImpl : T { - - SharedImpl() : m_rc( 0 ){} + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); - virtual void addRef() const { - ++m_rc; - } - virtual void release() const { - if( --m_rc == 0 ) - delete this; - } +} - mutable unsigned int m_rc; - }; +// end catch_interfaces_testcase.h +// start catch_stringref.h -} // end namespace Catch +#include <cstddef> +#include <string> +#include <iosfwd> -#ifdef __clang__ -#pragma clang diagnostic pop -#endif +namespace Catch { -#include <memory> -#include <vector> -#include <stdlib.h> + class StringData; -namespace Catch { + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. c_str() must return a null terminated + /// string, however, and so the StringRef will internally take ownership + /// (taking a copy), if necessary. In theory this ownership is not externally + /// visible - but it does mean (substring) StringRefs should not be shared between + /// threads. + class StringRef { + friend struct StringRefTestAccess; - class TestCase; - class Stream; - struct IResultCapture; - struct IRunner; - struct IGeneratorsForTest; - struct IConfig; + using size_type = std::size_t; - struct IContext - { - virtual ~IContext(); + char const* m_start; + size_type m_size; - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) = 0; - virtual bool advanceGeneratorsForCurrentTest() = 0; - virtual Ptr<IConfig const> getConfig() const = 0; - }; + char* m_data = nullptr; - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( Ptr<IConfig const> const& config ) = 0; - }; + void takeOwnership(); - IContext& getCurrentContext(); - IMutableContext& getCurrentMutableContext(); - void cleanUpContext(); - Stream createStream( std::string const& streamName ); + public: // construction/ assignment + StringRef() noexcept; + StringRef( StringRef const& other ) noexcept; + StringRef( StringRef&& other ) noexcept; + StringRef( char const* rawChars ) noexcept; + StringRef( char const* rawChars, size_type size ) noexcept; + StringRef( std::string const& stdString ) noexcept; + ~StringRef() noexcept; -} + auto operator = ( StringRef other ) noexcept -> StringRef&; + operator std::string() const; -// #included from: internal/catch_test_registry.hpp -#define TWOBLUECUBES_CATCH_TEST_REGISTRY_HPP_INCLUDED + void swap( StringRef& other ) noexcept; -// #included from: catch_interfaces_testcase.h -#define TWOBLUECUBES_CATCH_INTERFACES_TESTCASE_H_INCLUDED + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != ( StringRef const& other ) const noexcept -> bool; -#include <vector> + auto operator[] ( size_type index ) const noexcept -> char; -namespace Catch { + public: // named queries + auto empty() const noexcept -> bool; + auto size() const noexcept -> size_type; + auto numberOfCharacters() const noexcept -> size_type; + auto c_str() const -> char const*; - class TestSpec; + public: // substrings and searches + auto substr( size_type start, size_type size ) const noexcept -> StringRef; - struct ITestCase : IShared { - virtual void invoke () const = 0; - protected: - virtual ~ITestCase(); + private: // ownership queries - may not be consistent between calls + auto isOwned() const noexcept -> bool; + auto isSubstring() const noexcept -> bool; + auto data() const noexcept -> char const*; }; - class TestCase; - struct IConfig; + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; + auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector<TestCase> const& getAllTests() const = 0; - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const = 0; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - }; -} +} // namespace Catch +// end catch_stringref.h namespace Catch { template<typename C> -class MethodTestCase : public SharedImpl<ITestCase> { - +class TestInvokerAsMethod : public ITestInvoker { + void (C::*m_testAsMethod)(); public: - MethodTestCase( void (C::*method)() ) : m_method( method ) {} + TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} - virtual void invoke() const { + void invoke() const override { C obj; - (obj.*m_method)(); + (obj.*m_testAsMethod)(); } - -private: - virtual ~MethodTestCase() {} - - void (C::*m_method)(); }; -typedef void(*TestFunction)(); +auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; -struct NameAndDesc { - NameAndDesc( const char* _name = "", const char* _description= "" ) - : name( _name ), description( _description ) - {} +template<typename C> +auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod ); +} - const char* name; - const char* description; +struct NameAndTags { + NameAndTags( StringRef name_ = "", StringRef tags_ = "" ) noexcept; + StringRef name; + StringRef tags; }; -struct AutoReg { - - AutoReg( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ); - - template<typename C> - AutoReg( void (C::*method)(), - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { - registerTestCase( new MethodTestCase<C>( method ), - className, - nameAndDesc, - lineInfo ); - } - - void registerTestCase( ITestCase* testCase, - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ); - +struct AutoReg : NonCopyable { + AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; ~AutoReg(); - -private: - AutoReg( AutoReg const& ); - void operator= ( AutoReg const& ); }; } // end namespace Catch -#ifdef CATCH_CONFIG_VARIADIC_MACROS +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ + static void TestName() + #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ + namespace{ \ + struct TestName : ClassName { \ + void test(); \ + }; \ + } \ + void TestName::test() + +#endif + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ + static void TestName(); \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + static void TestName() #define INTERNAL_CATCH_TESTCASE( ... ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() + INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); } + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... )\ + #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ + struct TestName : ClassName{ \ void test(); \ }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( __VA_ARGS__ ), CATCH_INTERNAL_LINEINFO ); \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() - -#else - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )(); \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); }\ - static void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )() - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, Name, Desc ) \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( &QualifiedMethod, "&" #QualifiedMethod, Catch::NameAndDesc( Name, Desc ), CATCH_INTERNAL_LINEINFO ); } + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ + void TestName::test() + #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ + INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, TestName, Desc )\ - namespace{ \ - struct INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) : ClassName{ \ - void test(); \ - }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( &INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test, #ClassName, Catch::NameAndDesc( TestName, Desc ), CATCH_INTERNAL_LINEINFO ); \ - } \ - void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() - -#endif - -// #included from: internal/catch_capture.hpp -#define TWOBLUECUBES_CATCH_CAPTURE_HPP_INCLUDED - -// #included from: catch_result_builder.h -#define TWOBLUECUBES_CATCH_RESULT_BUILDER_H_INCLUDED + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS -// #included from: catch_result_type.h -#define TWOBLUECUBES_CATCH_RESULT_TYPE_H_INCLUDED +// end catch_test_registry.h +// start catch_capture.hpp -namespace Catch { +// start catch_assertionhandler.h - // ResultWas::OfType enum - struct ResultWas { enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, +// start catch_decomposer.h - FailureBit = 0x10, +// start catch_tostring.h - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, +#include <sstream> +#include <vector> +#include <cstddef> +#include <type_traits> +#include <string> - Exception = 0x100 | FailureBit, +#ifdef __OBJC__ +// start catch_objc_arc.hpp - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, +#import <Foundation/Foundation.h> - FatalErrorCondition = 0x200 | FailureBit +#ifdef __has_feature +#define CATCH_ARC_ENABLED __has_feature(objc_arc) +#else +#define CATCH_ARC_ENABLED 0 +#endif - }; }; +void arcSafeRelease( NSObject* obj ); +id performOptionalSelector( id obj, SEL sel ); - inline bool isOk( ResultWas::OfType resultType ) { - return ( resultType & ResultWas::FailureBit ) == 0; - } - inline bool isJustInfo( int flags ) { - return flags == ResultWas::Info; - } - - // ResultDisposition::Flags enum - struct ResultDisposition { enum Flags { - Normal = 0x01, +#if !CATCH_ARC_ENABLED +inline void arcSafeRelease( NSObject* obj ) { + [obj release]; +} +inline id performOptionalSelector( id obj, SEL sel ) { + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; + return nil; +} +#define CATCH_UNSAFE_UNRETAINED +#define CATCH_ARC_STRONG +#else +inline void arcSafeRelease( NSObject* ){} +inline id performOptionalSelector( id obj, SEL sel ) { +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" +#endif + if( [obj respondsToSelector: sel] ) + return [obj performSelector: sel]; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + return nil; +} +#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained +#define CATCH_ARC_STRONG __strong +#endif - ContinueOnFailure = 0x02, // Failures fail test, but execution continues - FalseTest = 0x04, // Prefix expression with ! - SuppressFail = 0x08 // Failures are reported but do not fail the test - }; }; +// end catch_objc_arc.hpp +#endif - inline ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { - return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); - } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless +#endif - inline bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } - inline bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } - inline bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); -} // end namespace Catch +namespace Catch { + // Bring in operator<< from global namespace into Catch namespace + using ::operator<<; -// #included from: catch_assertionresult.h -#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_H_INCLUDED + namespace Detail { -#include <string> + extern const std::string unprintableString; -namespace Catch { + std::string rawMemoryToString( const void *object, std::size_t size ); - struct AssertionInfo - { - AssertionInfo() {} - AssertionInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - std::string const& _capturedExpression, - ResultDisposition::Flags _resultDisposition ); + template<typename T> + std::string rawMemoryToString( const T& object ) { + return rawMemoryToString( &object, sizeof(object) ); + } - std::string macroName; - SourceLineInfo lineInfo; - std::string capturedExpression; - ResultDisposition::Flags resultDisposition; - }; + template<typename T> + class IsStreamInsertable { + template<typename SS, typename TT> + static auto test(int) + -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); - struct AssertionResultData - { - AssertionResultData() : resultType( ResultWas::Unknown ) {} + template<typename, typename> + static auto test(...)->std::false_type; - std::string reconstructedExpression; - std::string message; - ResultWas::OfType resultType; - }; + public: + static const bool value = decltype(test<std::ostream, const T&>(0))::value; + }; - class AssertionResult { - public: - AssertionResult(); - AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); - ~AssertionResult(); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - AssertionResult( AssertionResult const& ) = default; - AssertionResult( AssertionResult && ) = default; - AssertionResult& operator = ( AssertionResult const& ) = default; - AssertionResult& operator = ( AssertionResult && ) = default; -# endif + } // namespace Detail - bool isOk() const; - bool succeeded() const; - ResultWas::OfType getResultType() const; - bool hasExpression() const; - bool hasMessage() const; - std::string getExpression() const; - std::string getExpressionInMacro() const; - bool hasExpandedExpression() const; - std::string getExpandedExpression() const; - std::string getMessage() const; - SourceLineInfo getSourceInfo() const; - std::string getTestMacroName() const; + // If we decide for C++14, change these to enable_if_ts + template <typename T> + struct StringMaker { + template <typename Fake = T> + static + typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert(const Fake& t) { + std::ostringstream sstr; + sstr << t; + return sstr.str(); + } - protected: - AssertionInfo m_info; - AssertionResultData m_resultData; + template <typename Fake = T> + static + typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type + convert(const Fake&) { + return Detail::unprintableString; + } }; -} // end namespace Catch + namespace Detail { -namespace Catch { + // This function dispatches all stringification requests inside of Catch. + // Should be preferably called fully qualified, like ::Catch::Detail::stringify + template <typename T> + std::string stringify(const T& e) { + return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); + } - struct TestFailureException{}; + } // namespace Detail + + // Some predefined specializations - template<typename T> class ExpressionLhs; + template<> + struct StringMaker<std::string> { + static std::string convert(const std::string& str); + }; + template<> + struct StringMaker<std::wstring> { + static std::string convert(const std::wstring& wstr); + }; - struct STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison; + template<> + struct StringMaker<char const *> { + static std::string convert(char const * str); + }; + template<> + struct StringMaker<char *> { + static std::string convert(char * str); + }; + template<> + struct StringMaker<wchar_t const *> { + static std::string convert(wchar_t const * str); + }; + template<> + struct StringMaker<wchar_t *> { + static std::string convert(wchar_t * str); + }; - struct CopyableStream { - CopyableStream() {} - CopyableStream( CopyableStream const& other ) { - oss << other.oss.str(); + template<int SZ> + struct StringMaker<char[SZ]> { + static std::string convert(const char* str) { + return ::Catch::Detail::stringify(std::string{ str }); } - CopyableStream& operator=( CopyableStream const& other ) { - oss.str(""); - oss << other.oss.str(); - return *this; + }; + template<int SZ> + struct StringMaker<signed char[SZ]> { + static std::string convert(const char* str) { + return ::Catch::Detail::stringify(std::string{ str }); + } + }; + template<int SZ> + struct StringMaker<unsigned char[SZ]> { + static std::string convert(const char* str) { + return ::Catch::Detail::stringify(std::string{ str }); } - std::ostringstream oss; }; - class ResultBuilder { - public: - ResultBuilder( char const* macroName, - SourceLineInfo const& lineInfo, - char const* capturedExpression, - ResultDisposition::Flags resultDisposition ); + template<> + struct StringMaker<int> { + static std::string convert(int value); + }; + template<> + struct StringMaker<long> { + static std::string convert(long value); + }; + template<> + struct StringMaker<long long> { + static std::string convert(long long value); + }; + template<> + struct StringMaker<unsigned int> { + static std::string convert(unsigned int value); + }; + template<> + struct StringMaker<unsigned long> { + static std::string convert(unsigned long value); + }; + template<> + struct StringMaker<unsigned long long> { + static std::string convert(unsigned long long value); + }; - template<typename T> - ExpressionLhs<T const&> operator <= ( T const& operand ); - ExpressionLhs<bool> operator <= ( bool value ); + template<> + struct StringMaker<bool> { + static std::string convert(bool b); + }; - template<typename T> - ResultBuilder& operator << ( T const& value ) { - m_stream.oss << value; - return *this; - } + template<> + struct StringMaker<char> { + static std::string convert(char c); + }; + template<> + struct StringMaker<signed char> { + static std::string convert(signed char c); + }; + template<> + struct StringMaker<unsigned char> { + static std::string convert(unsigned char c); + }; - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + template<> + struct StringMaker<std::nullptr_t> { + static std::string convert(std::nullptr_t); + }; - ResultBuilder& setResultType( ResultWas::OfType result ); - ResultBuilder& setResultType( bool result ); - ResultBuilder& setLhs( std::string const& lhs ); - ResultBuilder& setRhs( std::string const& rhs ); - ResultBuilder& setOp( std::string const& op ); + template<> + struct StringMaker<float> { + static std::string convert(float value); + }; + template<> + struct StringMaker<double> { + static std::string convert(double value); + }; - void endExpression(); + template <typename T> + struct StringMaker<T*> { + template <typename U> + static std::string convert(U* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; - std::string reconstructExpression() const; - AssertionResult build() const; + template <typename R, typename C> + struct StringMaker<R C::*> { + static std::string convert(R C::* p) { + if (p) { + return ::Catch::Detail::rawMemoryToString(p); + } else { + return "nullptr"; + } + } + }; - void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); - void captureResult( ResultWas::OfType resultType ); - void captureExpression(); - void react(); - bool shouldDebugBreak() const; - bool allowThrows() const; + namespace Detail { + template<typename InputIterator> + std::string rangeToString(InputIterator first, InputIterator last) { + std::ostringstream oss; + oss << "{ "; + if (first != last) { + oss << ::Catch::Detail::stringify(*first); + for (++first; first != last; ++first) + oss << ", " << ::Catch::Detail::stringify(*first); + } + oss << " }"; + return oss.str(); + } + } - private: - AssertionInfo m_assertionInfo; - AssertionResultData m_data; - struct ExprComponents { - ExprComponents() : testFalse( false ) {} - bool testFalse; - std::string lhs, rhs, op; - } m_exprComponents; - CopyableStream m_stream; + template<typename T, typename Allocator> + struct StringMaker<std::vector<T, Allocator> > { + static std::string convert( std::vector<T,Allocator> const& v ) { + return ::Catch::Detail::rangeToString( v.begin(), v.end() ); + } + }; - bool m_shouldDebugBreak; - bool m_shouldThrow; + template<typename T> + struct EnumStringMaker { + static std::string convert(const T& t) { + return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<T>::type>(t)); + } }; -} // namespace Catch +#ifdef __OBJC__ + template<> + struct StringMaker<NSString*> { + static std::string convert(NSString * nsstring) { + if (!nsstring) + return "nil"; + return std::string("@") + [nsstring UTF8String]; + } + }; + template<> + struct StringMaker<NSObject*> { + static std::string convert(NSObject* nsObject) { + return ::Catch::Detail::stringify([nsObject description]); + } -// Include after due to circular dependency: -// #included from: catch_expression_lhs.hpp -#define TWOBLUECUBES_CATCH_EXPRESSION_LHS_HPP_INCLUDED + }; + namespace Detail { + inline std::string stringify( NSString* nsstring ) { + return StringMaker<NSString*>::convert( nsstring ); + } -// #included from: catch_evaluate.hpp -#define TWOBLUECUBES_CATCH_EVALUATE_HPP_INCLUDED + } // namespace Detail +#endif // __OBJC__ -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +} // namespace Catch + +////////////////////////////////////////////////////// +// Separate std-lib types stringification, so it can be selectively enabled +// This means that we do not bring in + +#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) +# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER #endif -#include <cstddef> +// Separate std::pair specialization +#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) +#include <utility> +namespace Catch { + template<typename T1, typename T2> + struct StringMaker<std::pair<T1, T2> > { + static std::string convert(const std::pair<T1, T2>& pair) { + std::ostringstream oss; + oss << "{ " + << ::Catch::Detail::stringify(pair.first) + << ", " + << ::Catch::Detail::stringify(pair.second) + << " }"; + return oss.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER +// Separate std::tuple specialization +#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) +#include <tuple> namespace Catch { -namespace Internal { + namespace Detail { + template< + typename Tuple, + std::size_t N = 0, + bool = (N < std::tuple_size<Tuple>::value) + > + struct TupleElementPrinter { + static void print(const Tuple& tuple, std::ostream& os) { + os << (N ? ", " : " ") + << ::Catch::Detail::stringify(std::get<N>(tuple)); + TupleElementPrinter<Tuple, N + 1>::print(tuple, os); + } + }; - enum Operator { - IsEqualTo, - IsNotEqualTo, - IsLessThan, - IsGreaterThan, - IsLessThanOrEqualTo, - IsGreaterThanOrEqualTo - }; + template< + typename Tuple, + std::size_t N + > + struct TupleElementPrinter<Tuple, N, false> { + static void print(const Tuple&, std::ostream&) {} + }; - template<Operator Op> struct OperatorTraits { static const char* getName(){ return "*error*"; } }; - template<> struct OperatorTraits<IsEqualTo> { static const char* getName(){ return "=="; } }; - template<> struct OperatorTraits<IsNotEqualTo> { static const char* getName(){ return "!="; } }; - template<> struct OperatorTraits<IsLessThan> { static const char* getName(){ return "<"; } }; - template<> struct OperatorTraits<IsGreaterThan> { static const char* getName(){ return ">"; } }; - template<> struct OperatorTraits<IsLessThanOrEqualTo> { static const char* getName(){ return "<="; } }; - template<> struct OperatorTraits<IsGreaterThanOrEqualTo>{ static const char* getName(){ return ">="; } }; + } - template<typename T> - inline T& opCast(T const& t) { return const_cast<T&>(t); } + template<typename ...Types> + struct StringMaker<std::tuple<Types...>> { + static std::string convert(const std::tuple<Types...>& tuple) { + std::ostringstream os; + os << '{'; + Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, os); + os << " }"; + return os.str(); + } + }; +} +#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER -// nullptr_t support based on pull request #154 from Konstantin Baumann -#ifdef CATCH_CONFIG_CPP11_NULLPTR - inline std::nullptr_t opCast(std::nullptr_t) { return nullptr; } -#endif // CATCH_CONFIG_CPP11_NULLPTR +// Separate std::chrono::duration specialization +#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +#include <ctime> +#include <ratio> +#include <chrono> - // So the compare overloads can be operator agnostic we convey the operator as a template - // enum, which is used to specialise an Evaluator for doing the comparison. - template<typename T1, typename T2, Operator Op> - class Evaluator{}; +template <class Ratio> +struct ratio_string { + static std::string symbol(); +}; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs) { - return opCast( lhs ) == opCast( rhs ); +template <class Ratio> +std::string ratio_string<Ratio>::symbol() { + std::ostringstream oss; + oss << '[' << Ratio::num << '/' + << Ratio::den << ']'; + return oss.str(); +} +template <> +struct ratio_string<std::atto> { + static std::string symbol() { return "a"; } +}; +template <> +struct ratio_string<std::femto> { + static std::string symbol() { return "f"; } +}; +template <> +struct ratio_string<std::pico> { + static std::string symbol() { return "p"; } +}; +template <> +struct ratio_string<std::nano> { + static std::string symbol() { return "n"; } +}; +template <> +struct ratio_string<std::micro> { + static std::string symbol() { return "u"; } +}; +template <> +struct ratio_string<std::milli> { + static std::string symbol() { return "m"; } +}; + +namespace Catch { + //////////// + // std::chrono::duration specializations + template<typename Value, typename Ratio> + struct StringMaker<std::chrono::duration<Value, Ratio>> { + static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { + std::ostringstream oss; + oss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; + return oss.str(); } }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsNotEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) != opCast( rhs ); + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { + std::ostringstream oss; + oss << duration.count() << " s"; + return oss.str(); } }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsLessThan> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) < opCast( rhs ); + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { + std::ostringstream oss; + oss << duration.count() << " m"; + return oss.str(); } }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsGreaterThan> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) > opCast( rhs ); + template<typename Value> + struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { + static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { + std::ostringstream oss; + oss << duration.count() << " h"; + return oss.str(); } }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsGreaterThanOrEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) >= opCast( rhs ); + + //////////// + // std::chrono::time_point specialization + // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> + template<typename Clock, typename Duration> + struct StringMaker<std::chrono::time_point<Clock, Duration>> { + static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { + return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; } }; - template<typename T1, typename T2> - struct Evaluator<T1, T2, IsLessThanOrEqualTo> { - static bool evaluate( T1 const& lhs, T2 const& rhs ) { - return opCast( lhs ) <= opCast( rhs ); + // std::chrono::time_point<system_clock> specialization + template<typename Duration> + struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { + static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { + auto converted = std::chrono::system_clock::to_time_t(time_point); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &converted); +#else + std::tm* timeInfo = std::gmtime(&converted); +#endif + + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); } }; +} +#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER - template<Operator Op, typename T1, typename T2> - bool applyEvaluator( T1 const& lhs, T2 const& rhs ) { - return Evaluator<T1, T2, Op>::evaluate( lhs, rhs ); - } +#ifdef _MSC_VER +#pragma warning(pop) +#endif - // This level of indirection allows us to specialise for integer types - // to avoid signed/ unsigned warnings +// end catch_tostring.h +#include <ostream> - // "base" overload - template<Operator Op, typename T1, typename T2> - bool compare( T1 const& lhs, T2 const& rhs ) { - return Evaluator<T1, T2, Op>::evaluate( lhs, rhs ); - } +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4389) // '==' : signed/unsigned mismatch +#pragma warning(disable:4018) // more "signed/unsigned mismatch" +#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) +#pragma warning(disable:4180) // qualifier applied to function type has no meaning +#endif - // unsigned X to int - template<Operator Op> bool compare( unsigned int lhs, int rhs ) { - return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) ); - } - template<Operator Op> bool compare( unsigned long lhs, int rhs ) { - return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) ); - } - template<Operator Op> bool compare( unsigned char lhs, int rhs ) { - return applyEvaluator<Op>( lhs, static_cast<unsigned int>( rhs ) ); - } +namespace Catch { - // unsigned X to long - template<Operator Op> bool compare( unsigned int lhs, long rhs ) { - return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) ); - } - template<Operator Op> bool compare( unsigned long lhs, long rhs ) { - return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) ); - } - template<Operator Op> bool compare( unsigned char lhs, long rhs ) { - return applyEvaluator<Op>( lhs, static_cast<unsigned long>( rhs ) ); - } + struct ITransientExpression { + virtual auto isBinaryExpression() const -> bool = 0; + virtual auto getResult() const -> bool = 0; + virtual void streamReconstructedExpression( std::ostream &os ) const = 0; - // int to unsigned X - template<Operator Op> bool compare( int lhs, unsigned int rhs ) { - return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs ); - } - template<Operator Op> bool compare( int lhs, unsigned long rhs ) { - return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs ); - } - template<Operator Op> bool compare( int lhs, unsigned char rhs ) { - return applyEvaluator<Op>( static_cast<unsigned int>( lhs ), rhs ); - } - - // long to unsigned X - template<Operator Op> bool compare( long lhs, unsigned int rhs ) { - return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); - } - template<Operator Op> bool compare( long lhs, unsigned long rhs ) { - return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); - } - template<Operator Op> bool compare( long lhs, unsigned char rhs ) { - return applyEvaluator<Op>( static_cast<unsigned long>( lhs ), rhs ); - } - - // pointer to long (when comparing against NULL) - template<Operator Op, typename T> bool compare( long lhs, T* rhs ) { - return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs ); - } - template<Operator Op, typename T> bool compare( T* lhs, long rhs ) { - return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) ); - } - - // pointer to int (when comparing against NULL) - template<Operator Op, typename T> bool compare( int lhs, T* rhs ) { - return Evaluator<T*, T*, Op>::evaluate( reinterpret_cast<T*>( lhs ), rhs ); - } - template<Operator Op, typename T> bool compare( T* lhs, int rhs ) { - return Evaluator<T*, T*, Op>::evaluate( lhs, reinterpret_cast<T*>( rhs ) ); - } - -#ifdef CATCH_CONFIG_CPP11_NULLPTR - // pointer to nullptr_t (when comparing against nullptr) - template<Operator Op, typename T> bool compare( std::nullptr_t, T* rhs ) { - return Evaluator<T*, T*, Op>::evaluate( NULL, rhs ); - } - template<Operator Op, typename T> bool compare( T* lhs, std::nullptr_t ) { - return Evaluator<T*, T*, Op>::evaluate( lhs, NULL ); - } -#endif // CATCH_CONFIG_CPP11_NULLPTR - -} // end of namespace Internal -} // end of namespace Catch - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// #included from: catch_tostring.h -#define TWOBLUECUBES_CATCH_TOSTRING_H_INCLUDED - -#include <sstream> -#include <iomanip> -#include <limits> -#include <vector> -#include <cstddef> - -#ifdef __OBJC__ -// #included from: catch_objc_arc.hpp -#define TWOBLUECUBES_CATCH_OBJC_ARC_HPP_INCLUDED - -#import <Foundation/Foundation.h> - -#ifdef __has_feature -#define CATCH_ARC_ENABLED __has_feature(objc_arc) -#else -#define CATCH_ARC_ENABLED 0 -#endif - -void arcSafeRelease( NSObject* obj ); -id performOptionalSelector( id obj, SEL sel ); - -#if !CATCH_ARC_ENABLED -inline void arcSafeRelease( NSObject* obj ) { - [obj release]; -} -inline id performOptionalSelector( id obj, SEL sel ) { - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; - return nil; -} -#define CATCH_UNSAFE_UNRETAINED -#define CATCH_ARC_STRONG -#else -inline void arcSafeRelease( NSObject* ){} -inline id performOptionalSelector( id obj, SEL sel ) { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -#endif - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - return nil; -} -#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained -#define CATCH_ARC_STRONG __strong -#endif + // We don't actually need a virtual destructore, but many static analysers + // complain if it's not here :-( + virtual ~ITransientExpression(); + }; -#endif + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); -#ifdef CATCH_CONFIG_CPP11_TUPLE -#include <tuple> -#endif + template<typename LhsT, typename RhsT> + class BinaryExpr : public ITransientExpression { + bool m_result; + LhsT m_lhs; + StringRef m_op; + RhsT m_rhs; -#ifdef CATCH_CONFIG_CPP11_IS_ENUM -#include <type_traits> -#endif + auto isBinaryExpression() const -> bool override { return true; } + auto getResult() const -> bool override { return m_result; } -namespace Catch { + void streamReconstructedExpression( std::ostream &os ) const override { + formatReconstructedExpression + ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); + } -// Why we're here. -template<typename T> -std::string toString( T const& value ); - -// Built in overloads - -std::string toString( std::string const& value ); -std::string toString( std::wstring const& value ); -std::string toString( const char* const value ); -std::string toString( char* const value ); -std::string toString( const wchar_t* const value ); -std::string toString( wchar_t* const value ); -std::string toString( int value ); -std::string toString( unsigned long value ); -std::string toString( unsigned int value ); -std::string toString( const double value ); -std::string toString( const float value ); -std::string toString( bool value ); -std::string toString( char value ); -std::string toString( signed char value ); -std::string toString( unsigned char value ); - -#ifdef CATCH_CONFIG_CPP11_NULLPTR -std::string toString( std::nullptr_t ); -#endif + public: + BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) + : m_result( comparisonResult ), + m_lhs( lhs ), + m_op( op ), + m_rhs( rhs ) + {} + }; -#ifdef __OBJC__ - std::string toString( NSString const * const& nsstring ); - std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ); - std::string toString( NSObject* const& nsObject ); -#endif + template<typename LhsT> + class UnaryExpr : public ITransientExpression { + LhsT m_lhs; -namespace Detail { + auto isBinaryExpression() const -> bool override { return false; } + auto getResult() const -> bool override { return m_lhs ? true : false; } - extern std::string unprintableString; + void streamReconstructedExpression( std::ostream &os ) const override { + os << Catch::Detail::stringify( m_lhs ); + } - struct BorgType { - template<typename T> BorgType( T const& ); + public: + UnaryExpr( LhsT lhs ) : m_lhs( lhs ) {} }; - struct TrueType { char sizer[1]; }; - struct FalseType { char sizer[2]; }; + // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) + template<typename LhsT, typename RhsT> + auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return lhs == rhs; }; + template<typename T> + auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } + template<typename T> + auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } - TrueType& testStreamable( std::ostream& ); - FalseType testStreamable( FalseType ); + template<typename LhsT, typename RhsT> + auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return lhs != rhs; }; + template<typename T> + auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } + template<typename T> + auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } + template<typename T> + auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } - FalseType operator<<( std::ostream const&, BorgType const& ); + template<typename LhsT> + class ExprLhs { + LhsT m_lhs; + public: + ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - template<typename T> - struct IsStreamInsertable { - static std::ostream &s; - static T const&t; - enum { value = sizeof( testStreamable(s << t) ) == sizeof( TrueType ) }; - }; + template<typename RhsT> + auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return BinaryExpr<LhsT, RhsT const&>( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs ); + } + auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return BinaryExpr<LhsT, bool>( m_lhs == rhs, m_lhs, "==", rhs ); + } -#if defined(CATCH_CONFIG_CPP11_IS_ENUM) - template<typename T, - bool IsEnum = std::is_enum<T>::value - > - struct EnumStringMaker - { - static std::string convert( T const& ) { return unprintableString; } - }; + template<typename RhsT> + auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return BinaryExpr<LhsT, RhsT const&>( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs ); + } + auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const { + return BinaryExpr<LhsT, bool>( m_lhs != rhs, m_lhs, "!=", rhs ); + } - template<typename T> - struct EnumStringMaker<T,true> - { - static std::string convert( T const& v ) - { - return ::Catch::toString( - static_cast<typename std::underlying_type<T>::type>(v) - ); + template<typename RhsT> + auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return BinaryExpr<LhsT, RhsT const&>( m_lhs > rhs, m_lhs, ">", rhs ); } - }; -#endif - template<bool C> - struct StringMakerBase { -#if defined(CATCH_CONFIG_CPP11_IS_ENUM) - template<typename T> - static std::string convert( T const& v ) - { - return EnumStringMaker<T>::convert( v ); + template<typename RhsT> + auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return BinaryExpr<LhsT, RhsT const&>( m_lhs < rhs, m_lhs, "<", rhs ); + } + template<typename RhsT> + auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return BinaryExpr<LhsT, RhsT const&>( m_lhs >= rhs, m_lhs, ">=", rhs ); + } + template<typename RhsT> + auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { + return BinaryExpr<LhsT, RhsT const&>( m_lhs <= rhs, m_lhs, "<=", rhs ); } -#else - template<typename T> - static std::string convert( T const& ) { return unprintableString; } -#endif - }; - template<> - struct StringMakerBase<true> { - template<typename T> - static std::string convert( T const& _value ) { - std::ostringstream oss; - oss << _value; - return oss.str(); + auto makeUnaryExpr() const -> UnaryExpr<LhsT> { + return UnaryExpr<LhsT>( m_lhs ); } }; - std::string rawMemoryToString( const void *object, std::size_t size ); + void handleExpression( ITransientExpression const& expr ); template<typename T> - inline std::string rawMemoryToString( const T& object ) { - return rawMemoryToString( &object, sizeof(object) ); + void handleExpression( ExprLhs<T> const& expr ) { + handleExpression( expr.makeUnaryExpr() ); } -} // end namespace Detail - -template<typename T> -struct StringMaker : - Detail::StringMakerBase<Detail::IsStreamInsertable<T>::value> {}; + struct Decomposer { + template<typename T> + auto operator <= ( T const& lhs ) -> ExprLhs<T const&> { + return ExprLhs<T const&>( lhs ); + } + auto operator <=( bool value ) -> ExprLhs<bool> { + return ExprLhs<bool>( value ); + } + }; -template<typename T> -struct StringMaker<T*> { - template<typename U> - static std::string convert( U* p ) { - if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); - else - return Detail::rawMemoryToString( p ); - } -}; +} // end namespace Catch -template<typename R, typename C> -struct StringMaker<R C::*> { - static std::string convert( R C::* p ) { - if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); - else - return Detail::rawMemoryToString( p ); - } -}; +#ifdef _MSC_VER +#pragma warning(pop) +#endif -namespace Detail { - template<typename InputIterator> - std::string rangeToString( InputIterator first, InputIterator last ); -} +// end catch_decomposer.h +// start catch_assertioninfo.h -//template<typename T, typename Allocator> -//struct StringMaker<std::vector<T, Allocator> > { -// static std::string convert( std::vector<T,Allocator> const& v ) { -// return Detail::rangeToString( v.begin(), v.end() ); -// } -//}; +// start catch_result_type.h -template<typename T, typename Allocator> -std::string toString( std::vector<T,Allocator> const& v ) { - return Detail::rangeToString( v.begin(), v.end() ); -} +namespace Catch { -#ifdef CATCH_CONFIG_CPP11_TUPLE - -// toString for tuples -namespace TupleDetail { - template< - typename Tuple, - std::size_t N = 0, - bool = (N < std::tuple_size<Tuple>::value) - > - struct ElementPrinter { - static void print( const Tuple& tuple, std::ostream& os ) - { - os << ( N ? ", " : " " ) - << Catch::toString(std::get<N>(tuple)); - ElementPrinter<Tuple,N+1>::print(tuple,os); - } - }; - - template< - typename Tuple, - std::size_t N - > - struct ElementPrinter<Tuple,N,false> { - static void print( const Tuple&, std::ostream& ) {} - }; + // ResultWas::OfType enum + struct ResultWas { enum OfType { + Unknown = -1, + Ok = 0, + Info = 1, + Warning = 2, -} + FailureBit = 0x10, -template<typename ...Types> -struct StringMaker<std::tuple<Types...>> { + ExpressionFailed = FailureBit | 1, + ExplicitFailure = FailureBit | 2, - static std::string convert( const std::tuple<Types...>& tuple ) - { - std::ostringstream os; - os << '{'; - TupleDetail::ElementPrinter<std::tuple<Types...>>::print( tuple, os ); - os << " }"; - return os.str(); - } -}; -#endif // CATCH_CONFIG_CPP11_TUPLE + Exception = 0x100 | FailureBit, -namespace Detail { - template<typename T> - std::string makeString( T const& value ) { - return StringMaker<T>::convert( value ); - } -} // end namespace Detail + ThrewException = Exception | 1, + DidntThrowException = Exception | 2, -/// \brief converts any type to a string -/// -/// The default template forwards on to ostringstream - except when an -/// ostringstream overload does not exist - in which case it attempts to detect -/// that and writes {?}. -/// Overload (not specialise) this template for custom typs that you don't want -/// to provide an ostream overload for. -template<typename T> -std::string toString( T const& value ) { - return StringMaker<T>::convert( value ); -} + FatalErrorCondition = 0x200 | FailureBit - namespace Detail { - template<typename InputIterator> - std::string rangeToString( InputIterator first, InputIterator last ) { - std::ostringstream oss; - oss << "{ "; - if( first != last ) { - oss << Catch::toString( *first ); - for( ++first ; first != last ; ++first ) - oss << ", " << Catch::toString( *first ); - } - oss << " }"; - return oss.str(); - } -} + }; }; -} // end namespace Catch + bool isOk( ResultWas::OfType resultType ); + bool isJustInfo( int flags ); -namespace Catch { + // ResultDisposition::Flags enum + struct ResultDisposition { enum Flags { + Normal = 0x01, -// Wraps the LHS of an expression and captures the operator and RHS (if any) - -// wrapping them all in a ResultBuilder object -template<typename T> -class ExpressionLhs { - ExpressionLhs& operator = ( ExpressionLhs const& ); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - ExpressionLhs& operator = ( ExpressionLhs && ) = delete; -# endif + ContinueOnFailure = 0x02, // Failures fail test, but execution continues + FalseTest = 0x04, // Prefix expression with ! + SuppressFail = 0x08 // Failures are reported but do not fail the test + }; }; -public: - ExpressionLhs( ResultBuilder& rb, T lhs ) : m_rb( rb ), m_lhs( lhs ) {} -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - ExpressionLhs( ExpressionLhs const& ) = default; - ExpressionLhs( ExpressionLhs && ) = default; -# endif + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); - template<typename RhsT> - ResultBuilder& operator == ( RhsT const& rhs ) { - return captureExpression<Internal::IsEqualTo>( rhs ); - } + bool shouldContinueOnFailure( int flags ); + bool isFalseTest( int flags ); + bool shouldSuppressFailure( int flags ); - template<typename RhsT> - ResultBuilder& operator != ( RhsT const& rhs ) { - return captureExpression<Internal::IsNotEqualTo>( rhs ); - } +} // end namespace Catch - template<typename RhsT> - ResultBuilder& operator < ( RhsT const& rhs ) { - return captureExpression<Internal::IsLessThan>( rhs ); - } +// end catch_result_type.h +namespace Catch { - template<typename RhsT> - ResultBuilder& operator > ( RhsT const& rhs ) { - return captureExpression<Internal::IsGreaterThan>( rhs ); - } + struct AssertionInfo + { + StringRef macroName; + SourceLineInfo lineInfo; + StringRef capturedExpression; + ResultDisposition::Flags resultDisposition; - template<typename RhsT> - ResultBuilder& operator <= ( RhsT const& rhs ) { - return captureExpression<Internal::IsLessThanOrEqualTo>( rhs ); - } + // We want to delete this constructor but a compiler bug in 4.8 means + // the struct is then treated as non-aggregate + //AssertionInfo() = delete; + }; - template<typename RhsT> - ResultBuilder& operator >= ( RhsT const& rhs ) { - return captureExpression<Internal::IsGreaterThanOrEqualTo>( rhs ); - } +} // end namespace Catch - ResultBuilder& operator == ( bool rhs ) { - return captureExpression<Internal::IsEqualTo>( rhs ); - } +// end catch_assertioninfo.h +namespace Catch { - ResultBuilder& operator != ( bool rhs ) { - return captureExpression<Internal::IsNotEqualTo>( rhs ); - } + struct TestFailureException{}; + struct AssertionResultData; - void endExpression() { - bool value = m_lhs ? true : false; - m_rb - .setLhs( Catch::toString( value ) ) - .setResultType( value ) - .endExpression(); - } + class LazyExpression { + friend class AssertionHandler; + friend struct AssertionStats; - // Only simple binary expressions are allowed on the LHS. - // If more complex compositions are required then place the sub expression in parentheses - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator + ( RhsT const& ); - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator - ( RhsT const& ); - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator / ( RhsT const& ); - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator * ( RhsT const& ); - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator && ( RhsT const& ); - template<typename RhsT> STATIC_ASSERT_Expression_Too_Complex_Please_Rewrite_As_Binary_Comparison& operator || ( RhsT const& ); + ITransientExpression const* m_transientExpression = nullptr; + bool m_isNegated; + public: + LazyExpression( bool isNegated ); + LazyExpression( LazyExpression const& other ); + LazyExpression& operator = ( LazyExpression const& ) = delete; -private: - template<Internal::Operator Op, typename RhsT> - ResultBuilder& captureExpression( RhsT const& rhs ) { - return m_rb - .setResultType( Internal::compare<Op>( m_lhs, rhs ) ) - .setLhs( Catch::toString( m_lhs ) ) - .setRhs( Catch::toString( rhs ) ) - .setOp( Internal::OperatorTraits<Op>::getName() ); - } + explicit operator bool() const; -private: - ResultBuilder& m_rb; - T m_lhs; -}; + friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; + }; -} // end namespace Catch + class AssertionHandler { + AssertionInfo m_assertionInfo; + bool m_shouldDebugBreak = false; + bool m_shouldThrow = false; + bool m_inExceptionGuard = false; + public: + AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ); + ~AssertionHandler(); -namespace Catch { + void handle( ITransientExpression const& expr ); - template<typename T> - inline ExpressionLhs<T const&> ResultBuilder::operator <= ( T const& operand ) { - return ExpressionLhs<T const&>( *this, operand ); - } + template<typename T> + void handle( ExprLhs<T> const& expr ) { + handle( expr.makeUnaryExpr() ); + } + void handle( ResultWas::OfType resultType ); + void handle( ResultWas::OfType resultType, StringRef const& message ); + void handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ); + void handle( AssertionResultData const& resultData, ITransientExpression const* expr ); + + auto shouldDebugBreak() const -> bool; + auto allowThrows() const -> bool; + void reactWithDebugBreak() const; + void reactWithoutDebugBreak() const; + void useActiveException(); + void setExceptionGuard(); + void unsetExceptionGuard(); + }; - inline ExpressionLhs<bool> ResultBuilder::operator <= ( bool value ) { - return ExpressionLhs<bool>( *this, value ); - } + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); } // namespace Catch -// #included from: catch_message.h -#define TWOBLUECUBES_CATCH_MESSAGE_H_INCLUDED +// end catch_assertionhandler.h +// start catch_message.h #include <string> +#include <sstream> namespace Catch { @@ -1418,27 +1264,33 @@ namespace Catch { ResultWas::OfType _type ); std::string macroName; + std::string message; SourceLineInfo lineInfo; ResultWas::OfType type; - std::string message; unsigned int sequence; - bool operator == ( MessageInfo const& other ) const { - return sequence == other.sequence; - } - bool operator < ( MessageInfo const& other ) const { - return sequence < other.sequence; - } + bool operator == ( MessageInfo const& other ) const; + bool operator < ( MessageInfo const& other ) const; private: static unsigned int globalCount; }; - struct MessageBuilder { + struct MessageStream { + + template<typename T> + MessageStream& operator << ( T const& value ) { + m_stream << value; + return *this; + } + + // !TBD reuse a global/ thread-local stream + std::ostringstream m_stream; + }; + + struct MessageBuilder : MessageStream { MessageBuilder( std::string const& macroName, SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - : m_info( macroName, lineInfo, type ) - {} + ResultWas::OfType type ); template<typename T> MessageBuilder& operator << ( T const& value ) { @@ -1447,13 +1299,11 @@ namespace Catch { } MessageInfo m_info; - std::ostringstream m_stream; }; class ScopedMessage { public: ScopedMessage( MessageBuilder const& builder ); - ScopedMessage( ScopedMessage const& other ); ~ScopedMessage(); MessageInfo m_info; @@ -1461,342 +1311,321 @@ namespace Catch { } // end namespace Catch -// #included from: catch_interfaces_capture.h -#define TWOBLUECUBES_CATCH_INTERFACES_CAPTURE_H_INCLUDED +// end catch_message.h +// start catch_interfaces_capture.h #include <string> namespace Catch { - class TestCase; class AssertionResult; struct AssertionInfo; struct SectionInfo; + struct SectionEndInfo; struct MessageInfo; - class ScopedMessageBuilder; struct Counts; + struct BenchmarkInfo; + struct BenchmarkStats; struct IResultCapture { virtual ~IResultCapture(); + virtual void assertionStarting( AssertionInfo const& info ) = 0; virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; - virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; + + virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; + virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; + virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; virtual std::string getCurrentTestName() const = 0; virtual const AssertionResult* getLastResult() const = 0; - virtual void handleFatalErrorCondition( std::string const& message ) = 0; + virtual void exceptionEarlyReported() = 0; + + virtual void handleFatalErrorCondition( StringRef message ) = 0; + + virtual bool lastAssertionPassed() = 0; + virtual void assertionPassed() = 0; + virtual void assertionRun() = 0; }; IResultCapture& getResultCapture(); } -// #included from: catch_debugger.h -#define TWOBLUECUBES_CATCH_DEBUGGER_H_INCLUDED - -// #included from: catch_platform.h -#define TWOBLUECUBES_CATCH_PLATFORM_H_INCLUDED - -#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) -#define CATCH_PLATFORM_MAC -#elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) -#define CATCH_PLATFORM_IPHONE -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) -#define CATCH_PLATFORM_WINDOWS -#endif - -#include <string> - -namespace Catch{ +// end catch_interfaces_capture.h +// start catch_debugger.h +namespace Catch { bool isDebuggerActive(); - void writeToDebugConsole( std::string const& text ); } #ifdef CATCH_PLATFORM_MAC - // The following code snippet based on: - // http://cocoawithlove.com/2008/03/break-into-debugger.html - #ifdef DEBUG - #if defined(__ppc64__) || defined(__ppc__) - #define CATCH_BREAK_INTO_DEBUGGER() \ - if( Catch::isDebuggerActive() ) { \ - __asm__("li r0, 20\nsc\nnop\nli r0, 37\nli r4, 2\nsc\nnop\n" \ - : : : "memory","r0","r3","r4" ); \ - } - #else - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) {__asm__("int $3\n" : : );} - #endif - #endif + #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ +#elif defined(CATCH_PLATFORM_LINUX) + // If we can use inline assembler, do it because this allows us to break + // directly at the location of the failing check instead of breaking inside + // raise() called from it, i.e. one stack frame below. + #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) + #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ + #else // Fall back to the generic way. + #include <signal.h> + + #define CATCH_TRAP() raise(SIGTRAP) + #endif #elif defined(_MSC_VER) - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { __debugbreak(); } + #define CATCH_TRAP() __debugbreak() #elif defined(__MINGW32__) extern "C" __declspec(dllimport) void __stdcall DebugBreak(); - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { DebugBreak(); } + #define CATCH_TRAP() DebugBreak() #endif -#ifndef CATCH_BREAK_INTO_DEBUGGER -#define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); +#ifdef CATCH_TRAP + #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } +#else + #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); #endif -// #included from: catch_interfaces_runner.h -#define TWOBLUECUBES_CATCH_INTERFACES_RUNNER_H_INCLUDED +// end catch_debugger.h +#if !defined(CATCH_CONFIG_DISABLE) -namespace Catch { - class TestCase; +#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) + #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ +#else + #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" +#endif - struct IRunner { - virtual ~IRunner(); - virtual bool aborting() const = 0; - }; -} +#if defined(CATCH_CONFIG_FAST_COMPILE) +/////////////////////////////////////////////////////////////////////////////// +// We can speedup compilation significantly by breaking into debugger lower in +// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER +// macro in each assertion +#define INTERNAL_CATCH_REACT( handler ) \ + handler.reactWithDebugBreak(); + +/////////////////////////////////////////////////////////////////////////////// +// Another way to speed-up compilation is to omit local try-catch for REQUIRE* +// macros. +// This can potentially cause false negative, if the test code catches +// the exception before it propagates back up to the runner. +#define INTERNAL_CATCH_TRY( capturer ) capturer.setExceptionGuard(); +#define INTERNAL_CATCH_CATCH( capturer ) capturer.unsetExceptionGuard(); + +#else // CATCH_CONFIG_FAST_COMPILE /////////////////////////////////////////////////////////////////////////////// // In the event of a failure works out if the debugger needs to be invoked // and/or an exception thrown and takes appropriate action. // This needs to be done as a macro so the debugger will stop in the user // source code rather than in Catch library code -#define INTERNAL_CATCH_REACT( resultBuilder ) \ - if( resultBuilder.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ - resultBuilder.react(); +#define INTERNAL_CATCH_REACT( handler ) \ + if( handler.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ + handler.reactWithoutDebugBreak(); + +#define INTERNAL_CATCH_TRY( capturer ) try +#define INTERNAL_CATCH_CATCH( capturer ) catch(...) { capturer.useActiveException(); } + +#endif /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ - try { \ - ( __catchResult <= expr ).endExpression(); \ - } \ - catch( ... ) { \ - __catchResult.useActiveException( Catch::ResultDisposition::Normal ); \ - } \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::isTrue( false && (expr) ) ) // expr here is never evaluated at runtime but it forces the compiler to give it a look + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ + INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ + CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + catchAssertionHandler.handle( Catch::Decomposer() <= __VA_ARGS__ ); \ + CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( Catch::isTrue( false && static_cast<bool>( !!(__VA_ARGS__) ) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look + // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_IF( expr, resultDisposition, macroName ) \ - INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ - if( Catch::getResultCapture().getLastResult()->succeeded() ) +#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( Catch::getResultCapture().lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE( expr, resultDisposition, macroName ) \ - INTERNAL_CATCH_TEST( expr, resultDisposition, macroName ); \ - if( !Catch::getResultCapture().getLastResult()->succeeded() ) +#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ + INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ + if( !Catch::getResultCapture().lastAssertionPassed() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ try { \ - expr; \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ - __catchResult.useActiveException( resultDisposition ); \ + catchAssertionHandler.useActiveException(); \ } \ - INTERNAL_CATCH_REACT( __catchResult ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ - if( __catchResult.allowThrows() ) \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ + if( catchAssertionHandler.allowThrows() ) \ try { \ - expr; \ - __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ } \ else \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS( expr, exceptionType, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ - if( __catchResult.allowThrows() ) \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ try { \ - expr; \ - __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ + static_cast<void>(expr); \ + catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ } \ - catch( exceptionType ) { \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + catch( exceptionType const& ) { \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ } \ catch( ... ) { \ - __catchResult.useActiveException( resultDisposition ); \ + catchAssertionHandler.useActiveException(); \ } \ else \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#ifdef CATCH_CONFIG_VARIADIC_MACROS - #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, ... ) \ - do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - __catchResult << __VA_ARGS__ + ::Catch::StreamEndStop(); \ - __catchResult.captureResult( messageType ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) -#else - #define INTERNAL_CATCH_MSG( messageType, resultDisposition, macroName, log ) \ - do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - __catchResult << log + ::Catch::StreamEndStop(); \ - __catchResult.captureResult( messageType ); \ - INTERNAL_CATCH_REACT( __catchResult ) \ - } while( Catch::alwaysFalse() ) -#endif +#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ + catchAssertionHandler.handle( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO( log, macroName ) \ +#define INTERNAL_CATCH_INFO( macroName, log ) \ Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT( arg, matcher, resultDisposition, macroName ) \ +// Although this is matcher-based, it can be used with just a string +#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ - try { \ - std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ - __catchResult \ - .setLhs( Catch::toString( arg ) ) \ - .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ - .setOp( "matches" ) \ - .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ - __catchResult.captureExpression(); \ - } catch( ... ) { \ - __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ - } \ - INTERNAL_CATCH_REACT( __catchResult ) \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__); \ + catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( ... ) { \ + handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ + } \ + else \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ } while( Catch::alwaysFalse() ) -// #included from: internal/catch_section.h -#define TWOBLUECUBES_CATCH_SECTION_H_INCLUDED - -// #included from: catch_section_info.h -#define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED - -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); +#endif // CATCH_CONFIG_DISABLE - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; +// end catch_capture.hpp +// start catch_section.h -} // end namespace Catch +// start catch_section_info.h -// #included from: catch_totals.hpp -#define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED +// start catch_totals.h #include <cstddef> namespace Catch { struct Counts { - Counts() : passed( 0 ), failed( 0 ), failedButOk( 0 ) {} - - Counts operator - ( Counts const& other ) const { - Counts diff; - diff.passed = passed - other.passed; - diff.failed = failed - other.failed; - diff.failedButOk = failedButOk - other.failedButOk; - return diff; - } - Counts& operator += ( Counts const& other ) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - return *this; - } + Counts operator - ( Counts const& other ) const; + Counts& operator += ( Counts const& other ); - std::size_t total() const { - return passed + failed + failedButOk; - } - bool allPassed() const { - return failed == 0 && failedButOk == 0; - } - bool allOk() const { - return failed == 0; - } + std::size_t total() const; + bool allPassed() const; + bool allOk() const; - std::size_t passed; - std::size_t failed; - std::size_t failedButOk; + std::size_t passed = 0; + std::size_t failed = 0; + std::size_t failedButOk = 0; }; struct Totals { - Totals operator - ( Totals const& other ) const { - Totals diff; - diff.assertions = assertions - other.assertions; - diff.testCases = testCases - other.testCases; - return diff; - } - - Totals delta( Totals const& prevTotals ) const { - Totals diff = *this - prevTotals; - if( diff.assertions.failed > 0 ) - ++diff.testCases.failed; - else if( diff.assertions.failedButOk > 0 ) - ++diff.testCases.failedButOk; - else - ++diff.testCases.passed; - return diff; - } + Totals operator - ( Totals const& other ) const; + Totals& operator += ( Totals const& other ); - Totals& operator += ( Totals const& other ) { - assertions += other.assertions; - testCases += other.testCases; - return *this; - } + Totals delta( Totals const& prevTotals ) const; Counts assertions; Counts testCases; }; } -// #included from: catch_timer.h -#define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED +// end catch_totals.h +#include <string> + +namespace Catch { -#ifdef CATCH_PLATFORM_WINDOWS -typedef unsigned long long uint64_t; -#else -#include <stdint.h> -#endif + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ); + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + +// end catch_section_info.h +// start catch_timer.h + +#include <cstdint> namespace Catch { + auto getCurrentNanosecondsSinceEpoch() -> uint64_t; + auto getEstimatedClockResolution() -> uint64_t; + class Timer { + uint64_t m_nanoseconds = 0; public: - Timer() : m_ticks( 0 ) {} void start(); - unsigned int getElapsedMicroseconds() const; - unsigned int getElapsedMilliseconds() const; - double getElapsedSeconds() const; - - private: - uint64_t m_ticks; + auto getElapsedNanoseconds() const -> unsigned int; + auto getElapsedMicroseconds() const -> unsigned int; + auto getElapsedMilliseconds() const -> unsigned int; + auto getElapsedSeconds() const -> double; }; } // namespace Catch +// end catch_timer.h #include <string> namespace Catch { @@ -1807,7 +1636,7 @@ namespace Catch { ~Section(); // This indicates whether the section should be executed or not - operator bool() const; + explicit operator bool() const; private: SectionInfo m_info; @@ -1820,202 +1649,62 @@ namespace Catch { } // end namespace Catch -#ifdef CATCH_CONFIG_VARIADIC_MACROS #define INTERNAL_CATCH_SECTION( ... ) \ if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) -#else - #define INTERNAL_CATCH_SECTION( name, desc ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, name, desc ) ) -#endif -// #included from: internal/catch_generators.hpp -#define TWOBLUECUBES_CATCH_GENERATORS_HPP_INCLUDED +// end catch_section.h +// start catch_benchmark.h -#include <iterator> -#include <vector> +#include <cstdint> #include <string> -#include <stdlib.h> namespace Catch { -template<typename T> -struct IGenerator { - virtual ~IGenerator() {} - virtual T getValue( std::size_t index ) const = 0; - virtual std::size_t size () const = 0; -}; - -template<typename T> -class BetweenGenerator : public IGenerator<T> { -public: - BetweenGenerator( T from, T to ) : m_from( from ), m_to( to ){} - - virtual T getValue( std::size_t index ) const { - return m_from+static_cast<int>( index ); - } - - virtual std::size_t size() const { - return static_cast<std::size_t>( 1+m_to-m_from ); - } - -private: + class BenchmarkLooper { - T m_from; - T m_to; -}; - -template<typename T> -class ValuesGenerator : public IGenerator<T> { -public: - ValuesGenerator(){} - - void add( T value ) { - m_values.push_back( value ); - } - - virtual T getValue( std::size_t index ) const { - return m_values[index]; - } - - virtual std::size_t size() const { - return m_values.size(); - } - -private: - std::vector<T> m_values; -}; - -template<typename T> -class CompositeGenerator { -public: - CompositeGenerator() : m_totalSize( 0 ) {} - - // *** Move semantics, similar to auto_ptr *** - CompositeGenerator( CompositeGenerator& other ) - : m_fileInfo( other.m_fileInfo ), - m_totalSize( 0 ) - { - move( other ); - } - - CompositeGenerator& setFileInfo( const char* fileInfo ) { - m_fileInfo = fileInfo; - return *this; - } - - ~CompositeGenerator() { - deleteAll( m_composed ); - } - - operator T () const { - size_t overallIndex = getCurrentContext().getGeneratorIndex( m_fileInfo, m_totalSize ); + std::string m_name; + std::size_t m_count = 0; + std::size_t m_iterationsToRun = 1; + uint64_t m_resolution; + Timer m_timer; - typename std::vector<const IGenerator<T>*>::const_iterator it = m_composed.begin(); - typename std::vector<const IGenerator<T>*>::const_iterator itEnd = m_composed.end(); - for( size_t index = 0; it != itEnd; ++it ) + static auto getResolution() -> uint64_t; + public: + // Keep most of this inline as it's on the code path that is being timed + BenchmarkLooper( StringRef name ) + : m_name( name ), + m_resolution( getResolution() ) { - const IGenerator<T>* generator = *it; - if( overallIndex >= index && overallIndex < index + generator->size() ) - { - return generator->getValue( overallIndex-index ); - } - index += generator->size(); + reportStart(); + m_timer.start(); } - CATCH_INTERNAL_ERROR( "Indexed past end of generated range" ); - return T(); // Suppress spurious "not all control paths return a value" warning in Visual Studio - if you know how to fix this please do so - } - - void add( const IGenerator<T>* generator ) { - m_totalSize += generator->size(); - m_composed.push_back( generator ); - } - CompositeGenerator& then( CompositeGenerator& other ) { - move( other ); - return *this; - } - - CompositeGenerator& then( T value ) { - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( value ); - add( valuesGen ); - return *this; - } - -private: - - void move( CompositeGenerator& other ) { - std::copy( other.m_composed.begin(), other.m_composed.end(), std::back_inserter( m_composed ) ); - m_totalSize += other.m_totalSize; - other.m_composed.clear(); - } - - std::vector<const IGenerator<T>*> m_composed; - std::string m_fileInfo; - size_t m_totalSize; -}; - -namespace Generators -{ - template<typename T> - CompositeGenerator<T> between( T from, T to ) { - CompositeGenerator<T> generators; - generators.add( new BetweenGenerator<T>( from, to ) ); - return generators; - } - - template<typename T> - CompositeGenerator<T> values( T val1, T val2 ) { - CompositeGenerator<T> generators; - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( val1 ); - valuesGen->add( val2 ); - generators.add( valuesGen ); - return generators; - } - - template<typename T> - CompositeGenerator<T> values( T val1, T val2, T val3 ){ - CompositeGenerator<T> generators; - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( val1 ); - valuesGen->add( val2 ); - valuesGen->add( val3 ); - generators.add( valuesGen ); - return generators; - } - - template<typename T> - CompositeGenerator<T> values( T val1, T val2, T val3, T val4 ) { - CompositeGenerator<T> generators; - ValuesGenerator<T>* valuesGen = new ValuesGenerator<T>(); - valuesGen->add( val1 ); - valuesGen->add( val2 ); - valuesGen->add( val3 ); - valuesGen->add( val4 ); - generators.add( valuesGen ); - return generators; - } + explicit operator bool() { + if( m_count < m_iterationsToRun ) + return true; + return needsMoreIterations(); + } -} // end namespace Generators + void increment() { + ++m_count; + } -using namespace Generators; + void reportStart(); + auto needsMoreIterations() -> bool; + }; } // end namespace Catch -#define INTERNAL_CATCH_LINESTR2( line ) #line -#define INTERNAL_CATCH_LINESTR( line ) INTERNAL_CATCH_LINESTR2( line ) - -#define INTERNAL_CATCH_GENERATE( expr ) expr.setFileInfo( __FILE__ "(" INTERNAL_CATCH_LINESTR( __LINE__ ) ")" ) +#define BENCHMARK( name ) \ + for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) -// #included from: internal/catch_interfaces_exception.h -#define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED +// end catch_benchmark.h +// start catch_interfaces_exception.h -#include <string> -// #included from: catch_interfaces_registry_hub.h -#define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED +// start catch_interfaces_registry_hub.h #include <string> +#include <memory> namespace Catch { @@ -2025,20 +1714,31 @@ namespace Catch { struct IExceptionTranslator; struct IReporterRegistry; struct IReporterFactory; + struct ITagAliasRegistry; + class StartupExceptionRegistry; + + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; struct IRegistryHub { virtual ~IRegistryHub(); virtual IReporterRegistry const& getReporterRegistry() const = 0; virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; + virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; + + virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; }; struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; + virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; + virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; + virtual void registerStartupException() noexcept = 0; }; IRegistryHub& getRegistryHub(); @@ -2048,14 +1748,25 @@ namespace Catch { } +// end catch_interfaces_registry_hub.h +#if defined(CATCH_CONFIG_DISABLE) + #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ + static std::string translatorName( signature ) +#endif + +#include <exception> +#include <string> +#include <vector> namespace Catch { + using exceptionTranslateFunction = std::string(*)(); - typedef std::string(*exceptionTranslateFunction)(); + struct IExceptionTranslator; + using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>; struct IExceptionTranslator { virtual ~IExceptionTranslator(); - virtual std::string translate() const = 0; + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { @@ -2073,9 +1784,12 @@ namespace Catch { : m_translateFunction( translateFunction ) {} - virtual std::string translate() const { + std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { try { - throw; + if( it == itEnd ) + std::rethrow_exception(std::current_exception()); + else + return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); @@ -2096,425 +1810,564 @@ namespace Catch { } /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) \ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ) ); }\ - static std::string INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator )( signature ) +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ + static std::string translatorName( signature ); \ + namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ + static std::string translatorName( signature ) -// #included from: internal/catch_approx.hpp -#define TWOBLUECUBES_CATCH_APPROX_HPP_INCLUDED +#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) -#include <cmath> -#include <limits> +// end catch_interfaces_exception.h +// start catch_approx.h + +// start catch_enforce.h + +#include <sstream> +#include <stdexcept> + +#define CATCH_PREPARE_EXCEPTION( type, msg ) \ + type( static_cast<std::ostringstream&&>( std::ostringstream() << msg ).str() ) +#define CATCH_INTERNAL_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); +#define CATCH_ERROR( msg ) \ + throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) +#define CATCH_ENFORCE( condition, msg ) \ + do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) + +// end catch_enforce.h +#include <type_traits> namespace Catch { namespace Detail { class Approx { - public: - explicit Approx ( double value ) - : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), - m_scale( 1.0 ), - m_value( value ) - {} + private: + bool equalityComparisonImpl(double other) const; - Approx( Approx const& other ) - : m_epsilon( other.m_epsilon ), - m_scale( other.m_scale ), - m_value( other.m_value ) - {} + public: + explicit Approx ( double value ); - static Approx custom() { - return Approx( 0 ); - } + static Approx custom(); - Approx operator()( double value ) { - Approx approx( value ); + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx operator()( T const& value ) { + Approx approx( static_cast<double>(value) ); approx.epsilon( m_epsilon ); + approx.margin( m_margin ); approx.scale( m_scale ); return approx; } - friend bool operator == ( double lhs, Approx const& rhs ) { - // Thanks to Richard Harris for his help refining this formula - return fabs( lhs - rhs.m_value ) < rhs.m_epsilon * (rhs.m_scale + (std::max)( fabs(lhs), fabs(rhs.m_value) ) ); + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + explicit Approx( T const& value ): Approx(static_cast<double>(value)) + {} + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( const T& lhs, Approx const& rhs ) { + auto lhs_v = static_cast<double>(lhs); + return rhs.equalityComparisonImpl(lhs_v); } - friend bool operator == ( Approx const& lhs, double rhs ) { + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator == ( Approx const& lhs, const T& rhs ) { return operator==( rhs, lhs ); } - friend bool operator != ( double lhs, Approx const& rhs ) { + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( T const& lhs, Approx const& rhs ) { return !operator==( lhs, rhs ); } - friend bool operator != ( Approx const& lhs, double rhs ) { + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator != ( Approx const& lhs, T const& rhs ) { return !operator==( rhs, lhs ); } - Approx& epsilon( double newEpsilon ) { - m_epsilon = newEpsilon; + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator <= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( T const& lhs, Approx const& rhs ) { + return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + friend bool operator >= ( Approx const& lhs, T const& rhs ) { + return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; + } + + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& epsilon( T const& newEpsilon ) { + double epsilonAsDouble = static_cast<double>(newEpsilon); + CATCH_ENFORCE(epsilonAsDouble >= 0 && epsilonAsDouble <= 1.0, + "Invalid Approx::epsilon: " << epsilonAsDouble + << ", Approx::epsilon has to be between 0 and 1"); + m_epsilon = epsilonAsDouble; return *this; } - Approx& scale( double newScale ) { - m_scale = newScale; + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& margin( T const& newMargin ) { + double marginAsDouble = static_cast<double>(newMargin); + CATCH_ENFORCE(marginAsDouble >= 0, + "Invalid Approx::margin: " << marginAsDouble + << ", Approx::Margin has to be non-negative."); + m_margin = marginAsDouble; return *this; } - std::string toString() const { - std::ostringstream oss; - oss << "Approx( " << Catch::toString( m_value ) << " )"; - return oss.str(); + template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> + Approx& scale( T const& newScale ) { + m_scale = static_cast<double>(newScale); + return *this; } + std::string toString() const; + private: double m_epsilon; + double m_margin; double m_scale; double m_value; }; } template<> -inline std::string toString<Detail::Approx>( Detail::Approx const& value ) { - return value.toString(); -} +struct StringMaker<Catch::Detail::Approx> { + static std::string convert(Catch::Detail::Approx const& value); +}; } // end namespace Catch -// #included from: internal/catch_matchers.hpp -#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED +// end catch_approx.h +// start catch_string_manip.h + +#include <string> +#include <iosfwd> namespace Catch { -namespace Matchers { - namespace Impl { - template<typename ExpressionT> - struct Matcher : SharedImpl<IShared> - { - typedef ExpressionT ExpressionType; + bool startsWith( std::string const& s, std::string const& prefix ); + bool startsWith( std::string const& s, char prefix ); + bool endsWith( std::string const& s, std::string const& suffix ); + bool endsWith( std::string const& s, char suffix ); + bool contains( std::string const& s, std::string const& infix ); + void toLowerInPlace( std::string& s ); + std::string toLower( std::string const& s ); + std::string trim( std::string const& str ); + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - virtual ~Matcher() {} - virtual Ptr<Matcher> clone() const = 0; - virtual bool match( ExpressionT const& expr ) const = 0; - virtual std::string toString() const = 0; - }; + struct pluralise { + pluralise( std::size_t count, std::string const& label ); - template<typename DerivedT, typename ExpressionT> - struct MatcherImpl : Matcher<ExpressionT> { + friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); - virtual Ptr<Matcher<ExpressionT> > clone() const { - return Ptr<Matcher<ExpressionT> >( new DerivedT( static_cast<DerivedT const&>( *this ) ) ); - } + std::size_t m_count; + std::string m_label; }; +} + +// end catch_string_manip.h +#ifndef CATCH_CONFIG_DISABLE_MATCHERS +// start catch_capture_matchers.h + +// start catch_matchers.h + +#include <string> +#include <vector> - namespace Generic { +namespace Catch { +namespace Matchers { + namespace Impl { + + template<typename ArgT> struct MatchAllOf; + template<typename ArgT> struct MatchAnyOf; + template<typename ArgT> struct MatchNotOf; - template<typename ExpressionT> - class AllOf : public MatcherImpl<AllOf<ExpressionT>, ExpressionT> { + class MatcherUntypedBase { public: + MatcherUntypedBase() = default; + MatcherUntypedBase ( MatcherUntypedBase const& ) = default; + MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; + std::string toString() const; - AllOf() {} - AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + protected: + virtual ~MatcherUntypedBase(); + virtual std::string describe() const = 0; + mutable std::string m_cachedToString; + }; - AllOf& add( Matcher<ExpressionT> const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( !m_matchers[i]->match( expr ) ) + template<typename ObjectT> + struct MatcherMethod { + virtual bool match( ObjectT const& arg ) const = 0; + }; + template<typename PtrT> + struct MatcherMethod<PtrT*> { + virtual bool match( PtrT* arg ) const = 0; + }; + + template<typename ObjectT, typename ComparatorT = ObjectT> + struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> { + + MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const; + MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const; + MatchNotOf<ComparatorT> operator ! () const; + }; + + template<typename ArgT> + struct MatchAllOf : MatcherBase<ArgT> { + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (!matcher->match(arg)) return false; + } return true; } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " and "; - oss << m_matchers[i]->toString(); + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " and "; + description += matcher->toString(); } - oss << " )"; - return oss.str(); + description += " )"; + return description; } - private: - std::vector<Ptr<Matcher<ExpressionT> > > m_matchers; - }; - - template<typename ExpressionT> - class AnyOf : public MatcherImpl<AnyOf<ExpressionT>, ExpressionT> { - public: - - AnyOf() {} - AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} - - AnyOf& add( Matcher<ExpressionT> const& matcher ) { - m_matchers.push_back( matcher.clone() ); + MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); return *this; } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( m_matchers[i]->match( expr ) ) + + std::vector<MatcherBase<ArgT> const*> m_matchers; + }; + template<typename ArgT> + struct MatchAnyOf : MatcherBase<ArgT> { + + bool match( ArgT const& arg ) const override { + for( auto matcher : m_matchers ) { + if (matcher->match(arg)) return true; + } return false; } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " or "; - oss << m_matchers[i]->toString(); + std::string describe() const override { + std::string description; + description.reserve( 4 + m_matchers.size()*32 ); + description += "( "; + bool first = true; + for( auto matcher : m_matchers ) { + if( first ) + first = false; + else + description += " or "; + description += matcher->toString(); } - oss << " )"; - return oss.str(); + description += " )"; + return description; } - private: - std::vector<Ptr<Matcher<ExpressionT> > > m_matchers; + MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { + m_matchers.push_back( &other ); + return *this; + } + + std::vector<MatcherBase<ArgT> const*> m_matchers; }; - } + template<typename ArgT> + struct MatchNotOf : MatcherBase<ArgT> { - namespace StdString { + MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} - inline std::string makeString( std::string const& str ) { return str; } - inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + bool match( ArgT const& arg ) const override { + return !m_underlyingMatcher.match( arg ); + } - struct Equals : MatcherImpl<Equals, std::string> { - Equals( std::string const& str ) : m_str( str ){} - Equals( Equals const& other ) : m_str( other.m_str ){} + std::string describe() const override { + return "not " + m_underlyingMatcher.toString(); + } + MatcherBase<ArgT> const& m_underlyingMatcher; + }; - virtual ~Equals(); + template<typename ObjectT, typename ComparatorT> + MatchAllOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator && ( MatcherBase const& other ) const { + return MatchAllOf<ComparatorT>() && *this && other; + } + template<typename ObjectT, typename ComparatorT> + MatchAnyOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator || ( MatcherBase const& other ) const { + return MatchAnyOf<ComparatorT>() || *this || other; + } + template<typename ObjectT, typename ComparatorT> + MatchNotOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator ! () const { + return MatchNotOf<ComparatorT>( *this ); + } - virtual bool match( std::string const& expr ) const { - return m_str == expr; - } - virtual std::string toString() const { - return "equals: \"" + m_str + "\""; - } + } // namespace Impl - std::string m_str; - }; +} // namespace Matchers - struct Contains : MatcherImpl<Contains, std::string> { - Contains( std::string const& substr ) : m_substr( substr ){} - Contains( Contains const& other ) : m_substr( other.m_substr ){} +using namespace Matchers; +using Matchers::Impl::MatcherBase; - virtual ~Contains(); +} // namespace Catch - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) != std::string::npos; - } - virtual std::string toString() const { - return "contains: \"" + m_substr + "\""; - } +// end catch_matchers.h +// start catch_matchers_string.h - std::string m_substr; - }; +#include <string> - struct StartsWith : MatcherImpl<StartsWith, std::string> { - StartsWith( std::string const& substr ) : m_substr( substr ){} - StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} +namespace Catch { +namespace Matchers { - virtual ~StartsWith(); + namespace StdString { - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == 0; - } - virtual std::string toString() const { - return "starts with: \"" + m_substr + "\""; - } + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); + std::string adjustString( std::string const& str ) const; + std::string caseSensitivitySuffix() const; - std::string m_substr; + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; }; - struct EndsWith : MatcherImpl<EndsWith, std::string> { - EndsWith( std::string const& substr ) : m_substr( substr ){} - EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} + struct StringMatcherBase : MatcherBase<std::string> { + StringMatcherBase( std::string const& operation, CasedString const& comparator ); + std::string describe() const override; - virtual ~EndsWith(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == expr.size() - m_substr.size(); - } - virtual std::string toString() const { - return "ends with: \"" + m_substr + "\""; - } + CasedString m_comparator; + std::string m_operation; + }; - std::string m_substr; + struct EqualsMatcher : StringMatcherBase { + EqualsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; }; + struct ContainsMatcher : StringMatcherBase { + ContainsMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct StartsWithMatcher : StringMatcherBase { + StartsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + struct EndsWithMatcher : StringMatcherBase { + EndsWithMatcher( CasedString const& comparator ); + bool match( std::string const& source ) const override; + }; + } // namespace StdString - } // namespace Impl // The following functions create the actual matcher objects. // This allows the types to be inferred - template<typename ExpressionT> - inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1, - Impl::Matcher<ExpressionT> const& m2 ) { - return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 ); - } - template<typename ExpressionT> - inline Impl::Generic::AllOf<ExpressionT> AllOf( Impl::Matcher<ExpressionT> const& m1, - Impl::Matcher<ExpressionT> const& m2, - Impl::Matcher<ExpressionT> const& m3 ) { - return Impl::Generic::AllOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 ); - } - template<typename ExpressionT> - inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1, - Impl::Matcher<ExpressionT> const& m2 ) { - return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 ); - } - template<typename ExpressionT> - inline Impl::Generic::AnyOf<ExpressionT> AnyOf( Impl::Matcher<ExpressionT> const& m1, - Impl::Matcher<ExpressionT> const& m2, - Impl::Matcher<ExpressionT> const& m3 ) { - return Impl::Generic::AnyOf<ExpressionT>().add( m1 ).add( m2 ).add( m3 ); - } - inline Impl::StdString::Equals Equals( std::string const& str ) { - return Impl::StdString::Equals( str ); - } - inline Impl::StdString::Equals Equals( const char* str ) { - return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); - } - inline Impl::StdString::Contains Contains( std::string const& substr ) { - return Impl::StdString::Contains( substr ); - } - inline Impl::StdString::Contains Contains( const char* substr ) { - return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { - return Impl::StdString::StartsWith( substr ); - } - inline Impl::StdString::StartsWith StartsWith( const char* substr ) { - return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { - return Impl::StdString::EndsWith( substr ); - } - inline Impl::StdString::EndsWith EndsWith( const char* substr ) { - return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); - } + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); } // namespace Matchers +} // namespace Catch -using namespace Matchers; +// end catch_matchers_string.h +// start catch_matchers_vector.h -} // namespace Catch +namespace Catch { +namespace Matchers { -// #included from: internal/catch_interfaces_tag_alias_registry.h -#define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED + namespace Vector { -// #included from: catch_tag_alias.h -#define TWOBLUECUBES_CATCH_TAG_ALIAS_H_INCLUDED + template<typename T> + struct ContainsElementMatcher : MatcherBase<std::vector<T>, T> { -#include <string> + ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} -namespace Catch { + bool match(std::vector<T> const &v) const override { + for (auto const& el : v) { + if (el == m_comparator) { + return true; + } + } + return false; + } - struct TagAlias { - TagAlias( std::string _tag, SourceLineInfo _lineInfo ) : tag( _tag ), lineInfo( _lineInfo ) {} + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } - std::string tag; - SourceLineInfo lineInfo; - }; + T const& m_comparator; + }; - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; + template<typename T> + struct ContainsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { -} // end namespace Catch + ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } -// #included from: catch_option.hpp -#define TWOBLUECUBES_CATCH_OPTION_HPP_INCLUDED + bool match(std::vector<T> const &v) const override { + // !TBD: see note in EqualsMatcher + if (m_comparator.size() > v.size()) + return false; + for (auto const& comparator : m_comparator) { + auto present = false; + for (const auto& el : v) { + if (el == comparator) { + present = true; + break; + } + } + if (!present) { + return false; + } + } + return true; + } + std::string describe() const override { + return "Contains: " + ::Catch::Detail::stringify( m_comparator ); + } -namespace Catch { + std::vector<T> const& m_comparator; + }; - // An optional type - template<typename T> - class Option { - public: - Option() : nullableValue( NULL ) {} - Option( T const& _value ) - : nullableValue( new( storage ) T( _value ) ) - {} - Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) - {} + template<typename T> + struct EqualsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { - ~Option() { - reset(); - } + EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} - Option& operator= ( Option const& _other ) { - if( &_other != this ) { - reset(); - if( _other ) - nullableValue = new( storage ) T( *_other ); + bool match(std::vector<T> const &v) const override { + // !TBD: This currently works if all elements can be compared using != + // - a more general approach would be via a compare template that defaults + // to using !=. but could be specialised for, e.g. std::vector<T> etc + // - then just call that directly + if (m_comparator.size() != v.size()) + return false; + for (std::size_t i = 0; i < v.size(); ++i) + if (m_comparator[i] != v[i]) + return false; + return true; } - return *this; - } - Option& operator = ( T const& _value ) { - reset(); - nullableValue = new( storage ) T( _value ); - return *this; - } - - void reset() { - if( nullableValue ) - nullableValue->~T(); - nullableValue = NULL; - } + std::string describe() const override { + return "Equals: " + ::Catch::Detail::stringify( m_comparator ); + } + std::vector<T> const& m_comparator; + }; - T& operator*() { return *nullableValue; } - T const& operator*() const { return *nullableValue; } - T* operator->() { return nullableValue; } - const T* operator->() const { return nullableValue; } + } // namespace Vector - T valueOr( T const& defaultValue ) const { - return nullableValue ? *nullableValue : defaultValue; - } + // The following functions create the actual matcher objects. + // This allows the types to be inferred - bool some() const { return nullableValue != NULL; } - bool none() const { return nullableValue == NULL; } + template<typename T> + Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { + return Vector::ContainsMatcher<T>( comparator ); + } - bool operator !() const { return nullableValue == NULL; } - operator SafeBool::type() const { - return SafeBool::makeSafe( some() ); - } + template<typename T> + Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { + return Vector::ContainsElementMatcher<T>( comparator ); + } - private: - T* nullableValue; - char storage[sizeof(T)]; - }; + template<typename T> + Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { + return Vector::EqualsMatcher<T>( comparator ); + } -} // end namespace Catch +} // namespace Matchers +} // namespace Catch +// end catch_matchers_vector.h namespace Catch { - struct ITagAliasRegistry { - virtual ~ITagAliasRegistry(); - virtual Option<TagAlias> find( std::string const& alias ) const = 0; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + template<typename ArgT, typename MatcherT> + class MatchExpr : public ITransientExpression { + ArgT const& m_arg; + MatcherT m_matcher; + StringRef m_matcherString; + bool m_result; + public: + MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) + : m_arg( arg ), + m_matcher( matcher ), + m_matcherString( matcherString ), + m_result( matcher.match( arg ) ) + {} - static ITagAliasRegistry const& get(); + auto isBinaryExpression() const -> bool override { return true; } + auto getResult() const -> bool override { return m_result; } + + void streamReconstructedExpression( std::ostream &os ) const override { + auto matcherAsString = m_matcher.toString(); + os << Catch::Detail::stringify( m_arg ) << ' '; + if( matcherAsString == Detail::unprintableString ) + os << m_matcherString; + else + os << matcherAsString; + } }; -} // end namespace Catch + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; + + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); + + template<typename ArgT, typename MatcherT> + auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr<ArgT, MatcherT> { + return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString ); + } + +} // namespace Catch + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ + catchAssertionHandler.handle( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ + } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( Catch::alwaysFalse() ) + +/////////////////////////////////////////////////////////////////////////////// +#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ + do { \ + Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ + if( catchAssertionHandler.allowThrows() ) \ + try { \ + static_cast<void>(__VA_ARGS__ ); \ + catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ + } \ + catch( exceptionType const& ex ) { \ + catchAssertionHandler.handle( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ + } \ + catch( ... ) { \ + catchAssertionHandler.useActiveException(); \ + } \ + else \ + catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ + INTERNAL_CATCH_REACT( catchAssertionHandler ) \ + } while( Catch::alwaysFalse() ) + +// end catch_capture_matchers.h +#endif // These files are included here so the single_include script doesn't put them // in the conditionally compiled sections -// #included from: internal/catch_test_case_info.h -#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_H_INCLUDED +// start catch_test_case_info.h #include <string> -#include <set> +#include <vector> +#include <memory> #ifdef __clang__ #pragma clang diagnostic push @@ -2523,7 +2376,7 @@ namespace Catch { namespace Catch { - struct ITestCase; + struct ITestInvoker; struct TestCaseInfo { enum SpecialProperties{ @@ -2531,28 +2384,31 @@ namespace Catch { IsHidden = 1 << 1, ShouldFail = 1 << 2, MayFail = 1 << 3, - Throws = 1 << 4 + Throws = 1 << 4, + NonPortable = 1 << 5, + Benchmark = 1 << 6 }; TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, - std::set<std::string> const& _tags, + std::vector<std::string> const& _tags, SourceLineInfo const& _lineInfo ); - TestCaseInfo( TestCaseInfo const& other ); + friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ); bool isHidden() const; bool throws() const; bool okToFail() const; bool expectedToFail() const; + std::string tagsAsString() const; + std::string name; std::string className; std::string description; - std::set<std::string> tags; - std::set<std::string> lcaseTags; - std::string tagsAsString; + std::vector<std::string> tags; + std::vector<std::string> lcaseTags; SourceLineInfo lineInfo; SpecialProperties properties; }; @@ -2560,8 +2416,7 @@ namespace Catch { class TestCase : public TestCaseInfo { public: - TestCase( ITestCase* testCase, TestCaseInfo const& info ); - TestCase( TestCase const& other ); + TestCase( ITestInvoker* testCase, TestCaseInfo const& info ); TestCase withName( std::string const& _newName ) const; @@ -2569,16 +2424,14 @@ namespace Catch { TestCaseInfo const& getTestCaseInfo() const; - void swap( TestCase& other ); bool operator == ( TestCase const& other ) const; bool operator < ( TestCase const& other ) const; - TestCase& operator = ( TestCase const& other ); private: - Ptr<ITestCase> test; + std::shared_ptr<ITestInvoker> test; }; - TestCase makeTestCase( ITestCase* testCase, + TestCase makeTestCase( ITestInvoker* testCase, std::string const& className, std::string const& name, std::string const& description, @@ -2589,10 +2442,21 @@ namespace Catch { #pragma clang diagnostic pop #endif +// end catch_test_case_info.h +// start catch_interfaces_runner.h + +namespace Catch { + + struct IRunner { + virtual ~IRunner(); + virtual bool aborting() const = 0; + }; +} + +// end catch_interfaces_runner.h #ifdef __OBJC__ -// #included from: internal/catch_objc.hpp -#define TWOBLUECUBES_CATCH_OBJC_HPP_INCLUDED +// start catch_objc.hpp #import <objc/runtime.h> @@ -2616,7 +2480,7 @@ namespace Catch { namespace Catch { - class OcMethod : public SharedImpl<ITestCase> { + class OcMethod : public ITestInvoker { public: OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} @@ -2652,9 +2516,9 @@ namespace Catch { } } - inline size_t registerTestMethods() { - size_t noTestMethods = 0; - int noClasses = objc_getClassList( NULL, 0 ); + inline std::size_t registerTestMethods() { + std::size_t noTestMethods = 0; + int noClasses = objc_getClassList( nullptr, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); @@ -2673,7 +2537,7 @@ namespace Catch { std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); const char* className = class_getName( cls ); - getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo() ) ); + getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo("",0) ) ); noTestMethods++; } } @@ -2683,69 +2547,74 @@ namespace Catch { return noTestMethods; } +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) + namespace Matchers { namespace Impl { namespace NSStringMatchers { - template<typename MatcherT> - struct StringHolder : MatcherImpl<MatcherT, NSString*>{ + struct StringHolder : MatcherBase<NSString*>{ StringHolder( NSString* substr ) : m_substr( [substr copy] ){} StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} StringHolder() { arcSafeRelease( m_substr ); } - NSString* m_substr; + bool match( NSString* arg ) const override { + return false; + } + + NSString* CATCH_ARC_STRONG m_substr; }; - struct Equals : StringHolder<Equals> { + struct Equals : StringHolder { Equals( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str isEqualToString:m_substr]; } - virtual std::string toString() const { - return "equals string: " + Catch::toString( m_substr ); + std::string describe() const override { + return "equals string: " + Catch::Detail::stringify( m_substr ); } }; - struct Contains : StringHolder<Contains> { + struct Contains : StringHolder { Contains( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + bool match( NSString* str ) const { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location != NSNotFound; } - virtual std::string toString() const { - return "contains string: " + Catch::toString( m_substr ); + std::string describe() const override { + return "contains string: " + Catch::Detail::stringify( m_substr ); } }; - struct StartsWith : StringHolder<StartsWith> { + struct StartsWith : StringHolder { StartsWith( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == 0; } - virtual std::string toString() const { - return "starts with: " + Catch::toString( m_substr ); + std::string describe() const override { + return "starts with: " + Catch::Detail::stringify( m_substr ); } }; - struct EndsWith : StringHolder<EndsWith> { + struct EndsWith : StringHolder { EndsWith( NSString* substr ) : StringHolder( substr ){} - virtual bool match( ExpressionType const& str ) const { + bool match( NSString* str ) const override { return (str != nil || m_substr == nil ) && [str rangeOfString:m_substr].location == [str length] - [m_substr length]; } - virtual std::string toString() const { - return "ends with: " + Catch::toString( m_substr ); + std::string describe() const override { + return "ends with: " + Catch::Detail::stringify( m_substr ); } }; @@ -2768,156 +2637,127 @@ namespace Catch { using namespace Matchers; +#endif // CATCH_CONFIG_DISABLE_MATCHERS + } // namespace Catch /////////////////////////////////////////////////////////////////////////////// -#define OC_TEST_CASE( name, desc )\ -+(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Name_test ) \ -{\ +#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix +#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ +{ \ return @ name; \ -}\ -+(NSString*) INTERNAL_CATCH_UNIQUE_NAME( Catch_Description_test ) \ +} \ ++(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ { \ return @ desc; \ } \ --(void) INTERNAL_CATCH_UNIQUE_NAME( Catch_TestCase_test ) - -#endif - -#ifdef CATCH_IMPL -// #included from: internal/catch_impl.hpp -#define TWOBLUECUBES_CATCH_IMPL_HPP_INCLUDED +-(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) -// Collect all the implementation files together here -// These are the equivalent of what would usually be cpp files +#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" +// end catch_objc.hpp #endif -// #included from: ../catch_runner.hpp -#define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED +#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES +// start catch_external_interfaces.h -// #included from: internal/catch_commandline.hpp -#define TWOBLUECUBES_CATCH_COMMANDLINE_HPP_INCLUDED +// start catch_reporter_bases.hpp -// #included from: catch_config.hpp -#define TWOBLUECUBES_CATCH_CONFIG_HPP_INCLUDED +// start catch_interfaces_reporter.h -// #included from: catch_test_spec_parser.hpp -#define TWOBLUECUBES_CATCH_TEST_SPEC_PARSER_HPP_INCLUDED +// start catch_config.hpp + +// start catch_test_spec_parser.h #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif -// #included from: catch_test_spec.hpp -#define TWOBLUECUBES_CATCH_TEST_SPEC_HPP_INCLUDED +// start catch_test_spec.h #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wpadded" #endif +// start catch_wildcard_pattern.h + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); + virtual ~WildcardPattern() = default; + virtual bool matches( std::string const& str ) const; + + private: + std::string adjustCase( std::string const& str ) const; + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard = NoWildcard; + std::string m_pattern; + }; +} + +// end catch_wildcard_pattern.h #include <string> #include <vector> +#include <memory> namespace Catch { class TestSpec { - struct Pattern : SharedImpl<> { + struct Pattern { virtual ~Pattern(); virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; - class NamePattern : public Pattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; + using PatternPtr = std::shared_ptr<Pattern>; + class NamePattern : public Pattern { public: - NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { - if( startsWith( m_name, "*" ) ) { - m_name = m_name.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_name, "*" ) ) { - m_name = m_name.substr( 0, m_name.size()-1 ); - m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); - } - } + NamePattern( std::string const& name ); virtual ~NamePattern(); - virtual bool matches( TestCaseInfo const& testCase ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_name == toLower( testCase.name ); - case WildcardAtStart: - return endsWith( toLower( testCase.name ), m_name ); - case WildcardAtEnd: - return startsWith( toLower( testCase.name ), m_name ); - case WildcardAtBothEnds: - return contains( toLower( testCase.name ), m_name ); - } - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - throw std::logic_error( "Unknown enum" ); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - } + virtual bool matches( TestCaseInfo const& testCase ) const override; private: - std::string m_name; - WildcardPosition m_wildcard; + WildcardPattern m_wildcardPattern; }; + class TagPattern : public Pattern { public: - TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + TagPattern( std::string const& tag ); virtual ~TagPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const { - return testCase.lcaseTags.find( m_tag ) != testCase.lcaseTags.end(); - } + virtual bool matches( TestCaseInfo const& testCase ) const override; private: std::string m_tag; }; + class ExcludedPattern : public Pattern { public: - ExcludedPattern( Ptr<Pattern> const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + ExcludedPattern( PatternPtr const& underlyingPattern ); virtual ~ExcludedPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + virtual bool matches( TestCaseInfo const& testCase ) const override; private: - Ptr<Pattern> m_underlyingPattern; + PatternPtr m_underlyingPattern; }; struct Filter { - std::vector<Ptr<Pattern> > m_patterns; + std::vector<PatternPtr> m_patterns; - bool matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( std::vector<Ptr<Pattern> >::const_iterator it = m_patterns.begin(), itEnd = m_patterns.end(); it != itEnd; ++it ) - if( !(*it)->matches( testCase ) ) - return false; - return true; - } + bool matches( TestCaseInfo const& testCase ) const; }; public: - bool hasFilters() const { - return !m_filters.empty(); - } - bool matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( std::vector<Filter>::const_iterator it = m_filters.begin(), itEnd = m_filters.end(); it != itEnd; ++it ) - if( it->matches( testCase ) ) - return true; - return false; - } + bool hasFilters() const; + bool matches( TestCaseInfo const& testCase ) const; private: std::vector<Filter> m_filters; @@ -2930,96 +2770,75 @@ namespace Catch { #pragma clang diagnostic pop #endif -namespace Catch { +// end catch_test_spec.h +// start catch_interfaces_tag_alias_registry.h - class TestSpecParser { - enum Mode{ None, Name, QuotedName, Tag }; - Mode m_mode; - bool m_exclusion; - std::size_t m_start, m_pos; - std::string m_arg; - TestSpec::Filter m_currentFilter; - TestSpec m_testSpec; - ITagAliasRegistry const* m_tagAliases; +#include <string> - public: - TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} +namespace Catch { + + struct TagAlias; + + struct ITagAliasRegistry { + virtual ~ITagAliasRegistry(); + // Nullptr if not present + virtual TagAlias const* find( std::string const& alias ) const = 0; + virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; + + static ITagAliasRegistry const& get(); + }; + +} // end namespace Catch + +// end catch_interfaces_tag_alias_registry.h +namespace Catch { + + class TestSpecParser { + enum Mode{ None, Name, QuotedName, Tag, EscapedName }; + Mode m_mode = None; + bool m_exclusion = false; + std::size_t m_start = std::string::npos, m_pos = 0; + std::string m_arg; + std::vector<std::size_t> m_escapeChars; + TestSpec::Filter m_currentFilter; + TestSpec m_testSpec; + ITagAliasRegistry const* m_tagAliases = nullptr; + + public: + TestSpecParser( ITagAliasRegistry const& tagAliases ); + + TestSpecParser& parse( std::string const& arg ); + TestSpec testSpec(); - TestSpecParser& parse( std::string const& arg ) { - m_mode = None; - m_exclusion = false; - m_start = std::string::npos; - m_arg = m_tagAliases->expandAliases( arg ); - for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) - visitChar( m_arg[m_pos] ); - if( m_mode == Name ) - addPattern<TestSpec::NamePattern>(); - return *this; - } - TestSpec testSpec() { - addFilter(); - return m_testSpec; - } private: - void visitChar( char c ) { - if( m_mode == None ) { - switch( c ) { - case ' ': return; - case '~': m_exclusion = true; return; - case '[': return startNewMode( Tag, ++m_pos ); - case '"': return startNewMode( QuotedName, ++m_pos ); - default: startNewMode( Name, m_pos ); break; - } - } - if( m_mode == Name ) { - if( c == ',' ) { - addPattern<TestSpec::NamePattern>(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern<TestSpec::NamePattern>(); - startNewMode( Tag, ++m_pos ); - } - } - else if( m_mode == QuotedName && c == '"' ) - addPattern<TestSpec::NamePattern>(); - else if( m_mode == Tag && c == ']' ) - addPattern<TestSpec::TagPattern>(); - } - void startNewMode( Mode mode, std::size_t start ) { - m_mode = mode; - m_start = start; - } - std::string subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + void visitChar( char c ); + void startNewMode( Mode mode, std::size_t start ); + void escape(); + std::string subString() const; + template<typename T> void addPattern() { std::string token = subString(); + for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) + token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); + m_escapeChars.clear(); if( startsWith( token, "exclude:" ) ) { m_exclusion = true; token = token.substr( 8 ); } if( !token.empty() ) { - Ptr<TestSpec::Pattern> pattern = new T( token ); + TestSpec::PatternPtr pattern = std::make_shared<T>( token ); if( m_exclusion ) - pattern = new TestSpec::ExcludedPattern( pattern ); + pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern ); m_currentFilter.m_patterns.push_back( pattern ); } m_exclusion = false; m_mode = None; } - void addFilter() { - if( !m_currentFilter.m_patterns.empty() ) { - m_testSpec.m_filters.push_back( m_currentFilter ); - m_currentFilter = TestSpec::Filter(); - } - } + + void addFilter(); }; - inline TestSpec parseTestSpec( std::string const& arg ) { - return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); - } + TestSpec parseTestSpec( std::string const& arg ); } // namespace Catch @@ -3027,20 +2846,21 @@ namespace Catch { #pragma clang diagnostic pop #endif -// #included from: catch_interfaces_config.h -#define TWOBLUECUBES_CATCH_INTERFACES_CONFIG_H_INCLUDED +// end catch_test_spec_parser.h +// start catch_interfaces_config.h -#include <iostream> +#include <iosfwd> #include <string> #include <vector> +#include <memory> namespace Catch { - struct Verbosity { enum Level { - NoOutput = 0, - Quiet, - Normal - }; }; + enum class Verbosity { + Quiet = 0, + Normal, + High + }; struct WarnAbout { enum What { Nothing = 0x00, @@ -3057,10 +2877,21 @@ namespace Catch { InLexicographicalOrder, InRandomOrder }; }; + struct UseColour { enum YesOrNo { + Auto, + Yes, + No + }; }; + struct WaitForKeypress { enum When { + Never, + BeforeStart = 1, + BeforeExit = 2, + BeforeStartAndExit = BeforeStart | BeforeExit + }; }; class TestSpec; - struct IConfig : IShared { + struct IConfig : NonCopyable { virtual ~IConfig(); @@ -3076,42 +2907,84 @@ namespace Catch { virtual TestSpec const& testSpec() const = 0; virtual RunTests::InWhatOrder runOrder() const = 0; virtual unsigned int rngSeed() const = 0; - virtual bool forceColour() const = 0; + virtual int benchmarkResolutionMultiple() const = 0; + virtual UseColour::YesOrNo useColour() const = 0; + virtual std::vector<std::string> const& getSectionsToRun() const = 0; + virtual Verbosity verbosity() const = 0; }; + + using IConfigPtr = std::shared_ptr<IConfig const>; } -// #included from: catch_stream.h -#define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED +// end catch_interfaces_config.h +// Libstdc++ doesn't like incomplete classes for unique_ptr +// start catch_stream.h -#include <streambuf> +// start catch_streambuf.h -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wpadded" -#endif +#include <streambuf> namespace Catch { - class Stream { + class StreamBufBase : public std::streambuf { public: - Stream(); - Stream( std::streambuf* _streamBuf, bool _isOwned ); - void release(); + virtual ~StreamBufBase(); + }; +} - std::streambuf* streamBuf; +// end catch_streambuf.h +#include <streambuf> +#include <ostream> +#include <fstream> +#include <memory> - private: - bool isOwned; - }; +namespace Catch { std::ostream& cout(); std::ostream& cerr(); + std::ostream& clog(); + + struct IStream { + virtual ~IStream(); + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + ~FileStream() override = default; + public: // IStream + std::ostream& stream() const override; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + ~CoutStream() override = default; + + public: // IStream + std::ostream& stream() const override; + }; + + class DebugOutStream : public IStream { + std::unique_ptr<StreamBufBase> m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + ~DebugOutStream() override = default; + + public: // IStream + std::ostream& stream() const override; + }; } +// end catch_stream.h + #include <memory> #include <vector> #include <string> -#include <iostream> -#include <ctime> #ifndef CATCH_CONFIG_CONSOLE_WIDTH #define CATCH_CONFIG_CONSOLE_WIDTH 80 @@ -3119,1341 +2992,674 @@ namespace Catch { namespace Catch { - struct ConfigData { - - ConfigData() - : listTests( false ), - listTags( false ), - listReporters( false ), - listTestNamesOnly( false ), - showSuccessfulTests( false ), - shouldDebugBreak( false ), - noThrow( false ), - showHelp( false ), - showInvisibles( false ), - forceColour( false ), - abortAfter( -1 ), - rngSeed( 0 ), - verbosity( Verbosity::Normal ), - warnings( WarnAbout::Nothing ), - showDurations( ShowDurations::DefaultForReporter ), - runOrder( RunTests::InDeclarationOrder ) - {} - - bool listTests; - bool listTags; - bool listReporters; - bool listTestNamesOnly; - - bool showSuccessfulTests; - bool shouldDebugBreak; - bool noThrow; - bool showHelp; - bool showInvisibles; - bool forceColour; - - int abortAfter; - unsigned int rngSeed; + struct IStream; - Verbosity::Level verbosity; - WarnAbout::What warnings; - ShowDurations::OrNot showDurations; - RunTests::InWhatOrder runOrder; + struct ConfigData { + bool listTests = false; + bool listTags = false; + bool listReporters = false; + bool listTestNamesOnly = false; + + bool showSuccessfulTests = false; + bool shouldDebugBreak = false; + bool noThrow = false; + bool showHelp = false; + bool showInvisibles = false; + bool filenamesAsTags = false; + bool libIdentify = false; + + int abortAfter = -1; + unsigned int rngSeed = 0; + int benchmarkResolutionMultiple = 100; + + Verbosity verbosity = Verbosity::Normal; + WarnAbout::What warnings = WarnAbout::Nothing; + ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; + RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; + UseColour::YesOrNo useColour = UseColour::Auto; + WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - std::string reporterName; std::string outputFilename; std::string name; std::string processName; + std::vector<std::string> reporterNames; std::vector<std::string> testsOrTags; + std::vector<std::string> sectionsToRun; }; - class Config : public SharedImpl<IConfig> { - private: - Config( Config const& other ); - Config& operator = ( Config const& other ); - virtual void dummy(); + class Config : public IConfig { public: - Config() - : m_os( Catch::cout().rdbuf() ) - {} - - Config( ConfigData const& data ) - : m_data( data ), - m_os( Catch::cout().rdbuf() ) - { - if( !data.testsOrTags.empty() ) { - TestSpecParser parser( ITagAliasRegistry::get() ); - for( std::size_t i = 0; i < data.testsOrTags.size(); ++i ) - parser.parse( data.testsOrTags[i] ); - m_testSpec = parser.testSpec(); - } - } - - virtual ~Config() { - m_os.rdbuf( Catch::cout().rdbuf() ); - m_stream.release(); - } - - void setFilename( std::string const& filename ) { - m_data.outputFilename = filename; - } - - std::string const& getFilename() const { - return m_data.outputFilename ; - } - - bool listTests() const { return m_data.listTests; } - bool listTestNamesOnly() const { return m_data.listTestNamesOnly; } - bool listTags() const { return m_data.listTags; } - bool listReporters() const { return m_data.listReporters; } - - std::string getProcessName() const { return m_data.processName; } - - bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } + Config() = default; + Config( ConfigData const& data ); + virtual ~Config() = default; - void setStreamBuf( std::streambuf* buf ) { - m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); - } + std::string const& getFilename() const; - void useStream( std::string const& streamName ) { - Stream stream = createStream( streamName ); - setStreamBuf( stream.streamBuf ); - m_stream.release(); - m_stream = stream; - } + bool listTests() const; + bool listTestNamesOnly() const; + bool listTags() const; + bool listReporters() const; - std::string getReporterName() const { return m_data.reporterName; } + std::string getProcessName() const; - int abortAfter() const { return m_data.abortAfter; } + std::vector<std::string> const& getReporterNames() const; + std::vector<std::string> const& getSectionsToRun() const override; - TestSpec const& testSpec() const { return m_testSpec; } + virtual TestSpec const& testSpec() const override; - bool showHelp() const { return m_data.showHelp; } - bool showInvisibles() const { return m_data.showInvisibles; } + bool showHelp() const; // IConfig interface - virtual bool allowThrows() const { return !m_data.noThrow; } - virtual std::ostream& stream() const { return m_os; } - virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } - virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } - virtual ShowDurations::OrNot showDurations() const { return m_data.showDurations; } - virtual RunTests::InWhatOrder runOrder() const { return m_data.runOrder; } - virtual unsigned int rngSeed() const { return m_data.rngSeed; } - virtual bool forceColour() const { return m_data.forceColour; } + bool allowThrows() const override; + std::ostream& stream() const override; + std::string name() const override; + bool includeSuccessfulResults() const override; + bool warnAboutMissingAssertions() const override; + ShowDurations::OrNot showDurations() const override; + RunTests::InWhatOrder runOrder() const override; + unsigned int rngSeed() const override; + int benchmarkResolutionMultiple() const override; + UseColour::YesOrNo useColour() const override; + bool shouldDebugBreak() const override; + int abortAfter() const override; + bool showInvisibles() const override; + Verbosity verbosity() const override; private: + + IStream const* openStream(); ConfigData m_data; - Stream m_stream; - mutable std::ostream m_os; + std::unique_ptr<IStream const> m_stream; TestSpec m_testSpec; }; } // end namespace Catch -// #included from: catch_clara.h -#define TWOBLUECUBES_CATCH_CLARA_H_INCLUDED - -// Use Catch's value for console width (store Clara's off to the side, if present) -#ifdef CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH -#undef CLARA_CONFIG_CONSOLE_WIDTH -#endif -#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH - -// Declare Clara inside the Catch namespace -#define STITCH_CLARA_OPEN_NAMESPACE namespace Catch { -// #included from: ../external/clara.h +// end catch_config.hpp +// start catch_assertionresult.h -// Only use header guard if we are not using an outer namespace -#if !defined(TWOBLUECUBES_CLARA_H_INCLUDED) || defined(STITCH_CLARA_OPEN_NAMESPACE) +#include <string> -#ifndef STITCH_CLARA_OPEN_NAMESPACE -#define TWOBLUECUBES_CLARA_H_INCLUDED -#define STITCH_CLARA_OPEN_NAMESPACE -#define STITCH_CLARA_CLOSE_NAMESPACE -#else -#define STITCH_CLARA_CLOSE_NAMESPACE } -#endif +namespace Catch { -#define STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE STITCH_CLARA_OPEN_NAMESPACE + struct AssertionResultData + { + AssertionResultData() = delete; -// ----------- #included from tbc_text_format.h ----------- + AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); -// Only use header guard if we are not using an outer namespace -#if !defined(TBC_TEXT_FORMAT_H_INCLUDED) || defined(STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE) -#ifndef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -#define TBC_TEXT_FORMAT_H_INCLUDED -#endif + std::string message; + mutable std::string reconstructedExpression; + LazyExpression lazyExpression; + ResultWas::OfType resultType; -#include <string> -#include <vector> -#include <sstream> + std::string reconstructExpression() const; + }; -// Use optional outer namespace -#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -namespace STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE { -#endif + class AssertionResult { + public: + AssertionResult() = delete; + AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); -namespace Tbc { + bool isOk() const; + bool succeeded() const; + ResultWas::OfType getResultType() const; + bool hasExpression() const; + bool hasMessage() const; + std::string getExpression() const; + std::string getExpressionInMacro() const; + bool hasExpandedExpression() const; + std::string getExpandedExpression() const; + std::string getMessage() const; + SourceLineInfo getSourceInfo() const; + std::string getTestMacroName() const; -#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH - const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; -#endif + //protected: + AssertionInfo m_info; + AssertionResultData m_resultData; + }; - struct TextAttributes { - TextAttributes() - : initialIndent( std::string::npos ), - indent( 0 ), - width( consoleWidth-1 ), - tabChar( '\t' ) - {} +} // end namespace Catch - TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } - TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } - TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } - TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } +// end catch_assertionresult.h +// start catch_option.hpp - std::size_t initialIndent; // indent of first line, or npos - std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos - std::size_t width; // maximum width of text, including indent. Longer text will wrap - char tabChar; // If this char is seen the indent is changed to current pos - }; +namespace Catch { - class Text { + // An optional type + template<typename T> + class Option { public: - Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) - : attr( _attr ) - { - std::string wrappableChars = " [({.,/|\\-"; - std::size_t indent = _attr.initialIndent != std::string::npos - ? _attr.initialIndent - : _attr.indent; - std::string remainder = _str; - - while( !remainder.empty() ) { - if( lines.size() >= 1000 ) { - lines.push_back( "... message truncated due to excessive size" ); - return; - } - std::size_t tabPos = std::string::npos; - std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); - std::size_t pos = remainder.find_first_of( '\n' ); - if( pos <= width ) { - width = pos; - } - pos = remainder.find_last_of( _attr.tabChar, width ); - if( pos != std::string::npos ) { - tabPos = pos; - if( remainder[width] == '\n' ) - width--; - remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); - } + Option() : nullableValue( nullptr ) {} + Option( T const& _value ) + : nullableValue( new( storage ) T( _value ) ) + {} + Option( Option const& _other ) + : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) + {} - if( width == remainder.size() ) { - spliceLine( indent, remainder, width ); - } - else if( remainder[width] == '\n' ) { - spliceLine( indent, remainder, width ); - if( width <= 1 || remainder.size() != 1 ) - remainder = remainder.substr( 1 ); - indent = _attr.indent; - } - else { - pos = remainder.find_last_of( wrappableChars, width ); - if( pos != std::string::npos && pos > 0 ) { - spliceLine( indent, remainder, pos ); - if( remainder[0] == ' ' ) - remainder = remainder.substr( 1 ); - } - else { - spliceLine( indent, remainder, width-1 ); - lines.back() += "-"; - } - if( lines.size() == 1 ) - indent = _attr.indent; - if( tabPos != std::string::npos ) - indent += tabPos; - } + ~Option() { + reset(); + } + + Option& operator= ( Option const& _other ) { + if( &_other != this ) { + reset(); + if( _other ) + nullableValue = new( storage ) T( *_other ); } + return *this; + } + Option& operator = ( T const& _value ) { + reset(); + nullableValue = new( storage ) T( _value ); + return *this; } - void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { - lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); - _remainder = _remainder.substr( _pos ); + void reset() { + if( nullableValue ) + nullableValue->~T(); + nullableValue = nullptr; } - typedef std::vector<std::string>::const_iterator const_iterator; + T& operator*() { return *nullableValue; } + T const& operator*() const { return *nullableValue; } + T* operator->() { return nullableValue; } + const T* operator->() const { return nullableValue; } - const_iterator begin() const { return lines.begin(); } - const_iterator end() const { return lines.end(); } - std::string const& last() const { return lines.back(); } - std::size_t size() const { return lines.size(); } - std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } - std::string toString() const { - std::ostringstream oss; - oss << *this; - return oss.str(); + T valueOr( T const& defaultValue ) const { + return nullableValue ? *nullableValue : defaultValue; } - inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { - for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); - it != itEnd; ++it ) { - if( it != _text.begin() ) - _stream << "\n"; - _stream << *it; - } - return _stream; + bool some() const { return nullableValue != nullptr; } + bool none() const { return nullableValue == nullptr; } + + bool operator !() const { return nullableValue == nullptr; } + explicit operator bool() const { + return some(); } private: - std::string str; - TextAttributes attr; - std::vector<std::string> lines; + T *nullableValue; + alignas(alignof(T)) char storage[sizeof(T)]; }; -} // end namespace Tbc - -#ifdef STITCH_TBC_TEXT_FORMAT_OUTER_NAMESPACE -} // end outer namespace -#endif - -#endif // TBC_TEXT_FORMAT_H_INCLUDED - -// ----------- end of #include from tbc_text_format.h ----------- -// ........... back in /Users/philnash/Dev/OSS/Clara/srcs/clara.h - -#undef STITCH_TBC_TEXT_FORMAT_OPEN_NAMESPACE +} // end namespace Catch +// end catch_option.hpp +#include <string> +#include <iosfwd> #include <map> -#include <algorithm> -#include <stdexcept> +#include <set> #include <memory> -// Use optional outer namespace -#ifdef STITCH_CLARA_OPEN_NAMESPACE -STITCH_CLARA_OPEN_NAMESPACE -#endif - -namespace Clara { - - struct UnpositionalTag {}; +namespace Catch { - extern UnpositionalTag _; + struct ReporterConfig { + explicit ReporterConfig( IConfigPtr const& _fullConfig ); -#ifdef CLARA_CONFIG_MAIN - UnpositionalTag _; -#endif + ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); - namespace Detail { + std::ostream& stream() const; + IConfigPtr fullConfig() const; -#ifdef CLARA_CONSOLE_WIDTH - const unsigned int consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; -#endif + private: + std::ostream* m_stream; + IConfigPtr m_fullConfig; + }; - using namespace Tbc; + struct ReporterPreferences { + bool shouldRedirectStdOut = false; + }; - inline bool startsWith( std::string const& str, std::string const& prefix ) { - return str.size() >= prefix.size() && str.substr( 0, prefix.size() ) == prefix; + template<typename T> + struct LazyStat : Option<T> { + LazyStat& operator=( T const& _value ) { + Option<T>::operator=( _value ); + used = false; + return *this; } + void reset() { + Option<T>::reset(); + used = false; + } + bool used = false; + }; - template<typename T> struct RemoveConstRef{ typedef T type; }; - template<typename T> struct RemoveConstRef<T&>{ typedef T type; }; - template<typename T> struct RemoveConstRef<T const&>{ typedef T type; }; - template<typename T> struct RemoveConstRef<T const>{ typedef T type; }; + struct TestRunInfo { + TestRunInfo( std::string const& _name ); + std::string name; + }; + struct GroupInfo { + GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ); - template<typename T> struct IsBool { static const bool value = false; }; - template<> struct IsBool<bool> { static const bool value = true; }; + std::string name; + std::size_t groupIndex; + std::size_t groupsCounts; + }; - template<typename T> - void convertInto( std::string const& _source, T& _dest ) { - std::stringstream ss; - ss << _source; - ss >> _dest; - if( ss.fail() ) - throw std::runtime_error( "Unable to convert " + _source + " to destination type" ); - } - inline void convertInto( std::string const& _source, std::string& _dest ) { - _dest = _source; - } - inline void convertInto( std::string const& _source, bool& _dest ) { - std::string sourceLC = _source; - std::transform( sourceLC.begin(), sourceLC.end(), sourceLC.begin(), ::tolower ); - if( sourceLC == "y" || sourceLC == "1" || sourceLC == "true" || sourceLC == "yes" || sourceLC == "on" ) - _dest = true; - else if( sourceLC == "n" || sourceLC == "0" || sourceLC == "false" || sourceLC == "no" || sourceLC == "off" ) - _dest = false; - else - throw std::runtime_error( "Expected a boolean value but did not recognise:\n '" + _source + "'" ); - } - inline void convertInto( bool _source, bool& _dest ) { - _dest = _source; - } - template<typename T> - inline void convertInto( bool, T& ) { - throw std::runtime_error( "Invalid conversion" ); - } + struct AssertionStats { + AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ); - template<typename ConfigT> - struct IArgFunction { - virtual ~IArgFunction() {} -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - IArgFunction() = default; - IArgFunction( IArgFunction const& ) = default; -# endif - virtual void set( ConfigT& config, std::string const& value ) const = 0; - virtual void setFlag( ConfigT& config ) const = 0; - virtual bool takesArg() const = 0; - virtual IArgFunction* clone() const = 0; - }; - - template<typename ConfigT> - class BoundArgFunction { - public: - BoundArgFunction() : functionObj( NULL ) {} - BoundArgFunction( IArgFunction<ConfigT>* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} - BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction<ConfigT>* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; - delete functionObj; - functionObj = newFunctionObj; - return *this; - } - ~BoundArgFunction() { delete functionObj; } - - void set( ConfigT& config, std::string const& value ) const { - functionObj->set( config, value ); - } - void setFlag( ConfigT& config ) const { - functionObj->setFlag( config ); - } - bool takesArg() const { return functionObj->takesArg(); } + AssertionStats( AssertionStats const& ) = default; + AssertionStats( AssertionStats && ) = default; + AssertionStats& operator = ( AssertionStats const& ) = default; + AssertionStats& operator = ( AssertionStats && ) = default; + virtual ~AssertionStats(); - bool isSet() const { - return functionObj != NULL; - } - private: - IArgFunction<ConfigT>* functionObj; - }; + AssertionResult assertionResult; + std::vector<MessageInfo> infoMessages; + Totals totals; + }; - template<typename C> - struct NullBinder : IArgFunction<C>{ - virtual void set( C&, std::string const& ) const {} - virtual void setFlag( C& ) const {} - virtual bool takesArg() const { return true; } - virtual IArgFunction<C>* clone() const { return new NullBinder( *this ); } - }; + struct SectionStats { + SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ); + SectionStats( SectionStats const& ) = default; + SectionStats( SectionStats && ) = default; + SectionStats& operator = ( SectionStats const& ) = default; + SectionStats& operator = ( SectionStats && ) = default; + virtual ~SectionStats(); - template<typename C, typename M> - struct BoundDataMember : IArgFunction<C>{ - BoundDataMember( M C::* _member ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - convertInto( stringValue, p.*member ); - } - virtual void setFlag( C& p ) const { - convertInto( true, p.*member ); - } - virtual bool takesArg() const { return !IsBool<M>::value; } - virtual IArgFunction<C>* clone() const { return new BoundDataMember( *this ); } - M C::* member; - }; - template<typename C, typename M> - struct BoundUnaryMethod : IArgFunction<C>{ - BoundUnaryMethod( void (C::*_member)( M ) ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - typename RemoveConstRef<M>::type value; - convertInto( stringValue, value ); - (p.*member)( value ); - } - virtual void setFlag( C& p ) const { - typename RemoveConstRef<M>::type value; - convertInto( true, value ); - (p.*member)( value ); - } - virtual bool takesArg() const { return !IsBool<M>::value; } - virtual IArgFunction<C>* clone() const { return new BoundUnaryMethod( *this ); } - void (C::*member)( M ); - }; - template<typename C> - struct BoundNullaryMethod : IArgFunction<C>{ - BoundNullaryMethod( void (C::*_member)() ) : member( _member ) {} - virtual void set( C& p, std::string const& stringValue ) const { - bool value; - convertInto( stringValue, value ); - if( value ) - (p.*member)(); - } - virtual void setFlag( C& p ) const { - (p.*member)(); - } - virtual bool takesArg() const { return false; } - virtual IArgFunction<C>* clone() const { return new BoundNullaryMethod( *this ); } - void (C::*member)(); - }; + SectionInfo sectionInfo; + Counts assertions; + double durationInSeconds; + bool missingAssertions; + }; - template<typename C> - struct BoundUnaryFunction : IArgFunction<C>{ - BoundUnaryFunction( void (*_function)( C& ) ) : function( _function ) {} - virtual void set( C& obj, std::string const& stringValue ) const { - bool value; - convertInto( stringValue, value ); - if( value ) - function( obj ); - } - virtual void setFlag( C& p ) const { - function( p ); - } - virtual bool takesArg() const { return false; } - virtual IArgFunction<C>* clone() const { return new BoundUnaryFunction( *this ); } - void (*function)( C& ); - }; + struct TestCaseStats { + TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ); - template<typename C, typename T> - struct BoundBinaryFunction : IArgFunction<C>{ - BoundBinaryFunction( void (*_function)( C&, T ) ) : function( _function ) {} - virtual void set( C& obj, std::string const& stringValue ) const { - typename RemoveConstRef<T>::type value; - convertInto( stringValue, value ); - function( obj, value ); - } - virtual void setFlag( C& obj ) const { - typename RemoveConstRef<T>::type value; - convertInto( true, value ); - function( obj, value ); - } - virtual bool takesArg() const { return !IsBool<T>::value; } - virtual IArgFunction<C>* clone() const { return new BoundBinaryFunction( *this ); } - void (*function)( C&, T ); - }; + TestCaseStats( TestCaseStats const& ) = default; + TestCaseStats( TestCaseStats && ) = default; + TestCaseStats& operator = ( TestCaseStats const& ) = default; + TestCaseStats& operator = ( TestCaseStats && ) = default; + virtual ~TestCaseStats(); - } // namespace Detail + TestCaseInfo testInfo; + Totals totals; + std::string stdOut; + std::string stdErr; + bool aborting; + }; - struct Parser { - Parser() : separators( " \t=:" ) {} + struct TestGroupStats { + TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ); + TestGroupStats( GroupInfo const& _groupInfo ); - struct Token { - enum Type { Positional, ShortOpt, LongOpt }; - Token( Type _type, std::string const& _data ) : type( _type ), data( _data ) {} - Type type; - std::string data; - }; + TestGroupStats( TestGroupStats const& ) = default; + TestGroupStats( TestGroupStats && ) = default; + TestGroupStats& operator = ( TestGroupStats const& ) = default; + TestGroupStats& operator = ( TestGroupStats && ) = default; + virtual ~TestGroupStats(); - void parseIntoTokens( int argc, char const * const * argv, std::vector<Parser::Token>& tokens ) const { - const std::string doubleDash = "--"; - for( int i = 1; i < argc && argv[i] != doubleDash; ++i ) - parseIntoTokens( argv[i] , tokens); - } - void parseIntoTokens( std::string arg, std::vector<Parser::Token>& tokens ) const { - while( !arg.empty() ) { - Parser::Token token( Parser::Token::Positional, arg ); - arg = ""; - if( token.data[0] == '-' ) { - if( token.data.size() > 1 && token.data[1] == '-' ) { - token = Parser::Token( Parser::Token::LongOpt, token.data.substr( 2 ) ); - } - else { - token = Parser::Token( Parser::Token::ShortOpt, token.data.substr( 1 ) ); - if( token.data.size() > 1 && separators.find( token.data[1] ) == std::string::npos ) { - arg = "-" + token.data.substr( 1 ); - token.data = token.data.substr( 0, 1 ); - } - } - } - if( token.type != Parser::Token::Positional ) { - std::size_t pos = token.data.find_first_of( separators ); - if( pos != std::string::npos ) { - arg = token.data.substr( pos+1 ); - token.data = token.data.substr( 0, pos ); - } - } - tokens.push_back( token ); - } - } - std::string separators; + GroupInfo groupInfo; + Totals totals; + bool aborting; }; - template<typename ConfigT> - struct CommonArgProperties { - CommonArgProperties() {} - CommonArgProperties( Detail::BoundArgFunction<ConfigT> const& _boundField ) : boundField( _boundField ) {} + struct TestRunStats { + TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ); - Detail::BoundArgFunction<ConfigT> boundField; - std::string description; - std::string detail; - std::string placeholder; // Only value if boundField takes an arg + TestRunStats( TestRunStats const& ) = default; + TestRunStats( TestRunStats && ) = default; + TestRunStats& operator = ( TestRunStats const& ) = default; + TestRunStats& operator = ( TestRunStats && ) = default; + virtual ~TestRunStats(); - bool takesArg() const { - return !placeholder.empty(); - } - void validate() const { - if( !boundField.isSet() ) - throw std::logic_error( "option not bound" ); - } + TestRunInfo runInfo; + Totals totals; + bool aborting; }; - struct OptionArgProperties { - std::vector<std::string> shortNames; - std::string longName; - bool hasShortName( std::string const& shortName ) const { - return std::find( shortNames.begin(), shortNames.end(), shortName ) != shortNames.end(); - } - bool hasLongName( std::string const& _longName ) const { - return _longName == longName; - } + struct BenchmarkInfo { + std::string name; }; - struct PositionalArgProperties { - PositionalArgProperties() : position( -1 ) {} - int position; // -1 means non-positional (floating) - - bool isFixedPositional() const { - return position != -1; - } + struct BenchmarkStats { + BenchmarkInfo info; + std::size_t iterations; + uint64_t elapsedTimeInNanoseconds; }; - template<typename ConfigT> - class CommandLine { + struct IStreamingReporter { + virtual ~IStreamingReporter() = default; - struct Arg : CommonArgProperties<ConfigT>, OptionArgProperties, PositionalArgProperties { - Arg() {} - Arg( Detail::BoundArgFunction<ConfigT> const& _boundField ) : CommonArgProperties<ConfigT>( _boundField ) {} + // Implementing class must also provide the following static methods: + // static std::string getDescription(); + // static std::set<Verbosity> getSupportedVerbosities() - using CommonArgProperties<ConfigT>::placeholder; // !TBD + virtual ReporterPreferences getPreferences() const = 0; - std::string dbgName() const { - if( !longName.empty() ) - return "--" + longName; - if( !shortNames.empty() ) - return "-" + shortNames[0]; - return "positional args"; - } - std::string commands() const { - std::ostringstream oss; - bool first = true; - std::vector<std::string>::const_iterator it = shortNames.begin(), itEnd = shortNames.end(); - for(; it != itEnd; ++it ) { - if( first ) - first = false; - else - oss << ", "; - oss << "-" << *it; - } - if( !longName.empty() ) { - if( !first ) - oss << ", "; - oss << "--" << longName; - } - if( !placeholder.empty() ) - oss << " <" << placeholder << ">"; - return oss.str(); - } - }; + virtual void noMatchingTestCases( std::string const& spec ) = 0; - // NOTE: std::auto_ptr is deprecated in c++11/c++0x -#if defined(__cplusplus) && __cplusplus > 199711L - typedef std::unique_ptr<Arg> ArgAutoPtr; -#else - typedef std::auto_ptr<Arg> ArgAutoPtr; -#endif + virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; + virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; - friend void addOptName( Arg& arg, std::string const& optName ) - { - if( optName.empty() ) - return; - if( Detail::startsWith( optName, "--" ) ) { - if( !arg.longName.empty() ) - throw std::logic_error( "Only one long opt may be specified. '" - + arg.longName - + "' already specified, now attempting to add '" - + optName + "'" ); - arg.longName = optName.substr( 2 ); - } - else if( Detail::startsWith( optName, "-" ) ) - arg.shortNames.push_back( optName.substr( 1 ) ); - else - throw std::logic_error( "option must begin with - or --. Option was: '" + optName + "'" ); - } - friend void setPositionalArg( Arg& arg, int position ) - { - arg.position = position; - } + virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; + virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; - class ArgBuilder { - public: - ArgBuilder( Arg* arg ) : m_arg( arg ) {} + // *** experimental *** + virtual void benchmarkStarting( BenchmarkInfo const& ) {} - // Bind a non-boolean data member (requires placeholder string) - template<typename C, typename M> - void bind( M C::* field, std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundDataMember<C,M>( field ); - m_arg->placeholder = placeholder; - } - // Bind a boolean data member (no placeholder required) - template<typename C> - void bind( bool C::* field ) { - m_arg->boundField = new Detail::BoundDataMember<C,bool>( field ); - } + virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; - // Bind a method taking a single, non-boolean argument (requires a placeholder string) - template<typename C, typename M> - void bind( void (C::* unaryMethod)( M ), std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundUnaryMethod<C,M>( unaryMethod ); - m_arg->placeholder = placeholder; - } + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; - // Bind a method taking a single, boolean argument (no placeholder string required) - template<typename C> - void bind( void (C::* unaryMethod)( bool ) ) { - m_arg->boundField = new Detail::BoundUnaryMethod<C,bool>( unaryMethod ); - } + // *** experimental *** + virtual void benchmarkEnded( BenchmarkStats const& ) {} - // Bind a method that takes no arguments (will be called if opt is present) - template<typename C> - void bind( void (C::* nullaryMethod)() ) { - m_arg->boundField = new Detail::BoundNullaryMethod<C>( nullaryMethod ); - } + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; + virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; - // Bind a free function taking a single argument - the object to operate on (no placeholder string required) - template<typename C> - void bind( void (* unaryFunction)( C& ) ) { - m_arg->boundField = new Detail::BoundUnaryFunction<C>( unaryFunction ); - } + virtual void skipTest( TestCaseInfo const& testInfo ) = 0; - // Bind a free function taking a single argument - the object to operate on (requires a placeholder string) - template<typename C, typename T> - void bind( void (* binaryFunction)( C&, T ), std::string const& placeholder ) { - m_arg->boundField = new Detail::BoundBinaryFunction<C, T>( binaryFunction ); - m_arg->placeholder = placeholder; - } + // Default empty implementation provided + virtual void fatalErrorEncountered( StringRef name ); - ArgBuilder& describe( std::string const& description ) { - m_arg->description = description; - return *this; - } - ArgBuilder& detail( std::string const& detail ) { - m_arg->detail = detail; - return *this; - } + virtual bool isMulti() const; + }; + using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>; - protected: - Arg* m_arg; - }; + struct IReporterFactory { + virtual ~IReporterFactory(); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; + virtual std::string getDescription() const = 0; + }; + using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; - class OptBuilder : public ArgBuilder { - public: - OptBuilder( Arg* arg ) : ArgBuilder( arg ) {} - OptBuilder( OptBuilder& other ) : ArgBuilder( other ) {} + struct IReporterRegistry { + using FactoryMap = std::map<std::string, IReporterFactoryPtr>; + using Listeners = std::vector<IReporterFactoryPtr>; - OptBuilder& operator[]( std::string const& optName ) { - addOptName( *ArgBuilder::m_arg, optName ); - return *this; - } - }; + virtual ~IReporterRegistry(); + virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; + virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; + }; - public: + void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ); - CommandLine() - : m_boundProcessName( new Detail::NullBinder<ConfigT>() ), - m_highestSpecifiedArgPosition( 0 ), - m_throwOnUnrecognisedTokens( false ) - {} - CommandLine( CommandLine const& other ) - : m_boundProcessName( other.m_boundProcessName ), - m_options ( other.m_options ), - m_positionalArgs( other.m_positionalArgs ), - m_highestSpecifiedArgPosition( other.m_highestSpecifiedArgPosition ), - m_throwOnUnrecognisedTokens( other.m_throwOnUnrecognisedTokens ) - { - if( other.m_floatingArg.get() ) - m_floatingArg.reset( new Arg( *other.m_floatingArg ) ); - } +} // end namespace Catch - CommandLine& setThrowOnUnrecognisedTokens( bool shouldThrow = true ) { - m_throwOnUnrecognisedTokens = shouldThrow; - return *this; - } +// end catch_interfaces_reporter.h +#include <algorithm> +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <assert.h> +#include <memory> - OptBuilder operator[]( std::string const& optName ) { - m_options.push_back( Arg() ); - addOptName( m_options.back(), optName ); - OptBuilder builder( &m_options.back() ); - return builder; - } +namespace Catch { + void prepareExpandedExpression(AssertionResult& result); - ArgBuilder operator[]( int position ) { - m_positionalArgs.insert( std::make_pair( position, Arg() ) ); - if( position > m_highestSpecifiedArgPosition ) - m_highestSpecifiedArgPosition = position; - setPositionalArg( m_positionalArgs[position], position ); - ArgBuilder builder( &m_positionalArgs[position] ); - return builder; - } + // Returns double formatted as %.3f (format expected on output) + std::string getFormattedDuration( double duration ); - // Invoke this with the _ instance - ArgBuilder operator[]( UnpositionalTag ) { - if( m_floatingArg.get() ) - throw std::logic_error( "Only one unpositional argument can be added" ); - m_floatingArg.reset( new Arg() ); - ArgBuilder builder( m_floatingArg.get() ); - return builder; - } + template<typename DerivedT> + struct StreamingReporterBase : IStreamingReporter { - template<typename C, typename M> - void bindProcessName( M C::* field ) { - m_boundProcessName = new Detail::BoundDataMember<C,M>( field ); + StreamingReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); } - template<typename C, typename M> - void bindProcessName( void (C::*_unaryMethod)( M ) ) { - m_boundProcessName = new Detail::BoundUnaryMethod<C,M>( _unaryMethod ); + + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; } - void optUsage( std::ostream& os, std::size_t indent = 0, std::size_t width = Detail::consoleWidth ) const { - typename std::vector<Arg>::const_iterator itBegin = m_options.begin(), itEnd = m_options.end(), it; - std::size_t maxWidth = 0; - for( it = itBegin; it != itEnd; ++it ) - maxWidth = (std::max)( maxWidth, it->commands().size() ); + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } - for( it = itBegin; it != itEnd; ++it ) { - Detail::Text usage( it->commands(), Detail::TextAttributes() - .setWidth( maxWidth+indent ) - .setIndent( indent ) ); - Detail::Text desc( it->description, Detail::TextAttributes() - .setWidth( width - maxWidth - 3 ) ); + ~StreamingReporterBase() override = default; - for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { - std::string usageCol = i < usage.size() ? usage[i] : ""; - os << usageCol; + void noMatchingTestCases(std::string const&) override {} - if( i < desc.size() && !desc[i].empty() ) - os << std::string( indent + 2 + maxWidth - usageCol.size(), ' ' ) - << desc[i]; - os << "\n"; - } - } + void testRunStarting(TestRunInfo const& _testRunInfo) override { + currentTestRunInfo = _testRunInfo; } - std::string optUsage() const { - std::ostringstream oss; - optUsage( oss ); - return oss.str(); + void testGroupStarting(GroupInfo const& _groupInfo) override { + currentGroupInfo = _groupInfo; } - void argSynopsis( std::ostream& os ) const { - for( int i = 1; i <= m_highestSpecifiedArgPosition; ++i ) { - if( i > 1 ) - os << " "; - typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( i ); - if( it != m_positionalArgs.end() ) - os << "<" << it->second.placeholder << ">"; - else if( m_floatingArg.get() ) - os << "<" << m_floatingArg->placeholder << ">"; - else - throw std::logic_error( "non consecutive positional arguments with no floating args" ); - } - // !TBD No indication of mandatory args - if( m_floatingArg.get() ) { - if( m_highestSpecifiedArgPosition > 1 ) - os << " "; - os << "[<" << m_floatingArg->placeholder << "> ...]"; - } + void testCaseStarting(TestCaseInfo const& _testInfo) override { + currentTestCaseInfo = _testInfo; } - std::string argSynopsis() const { - std::ostringstream oss; - argSynopsis( oss ); - return oss.str(); + void sectionStarting(SectionInfo const& _sectionInfo) override { + m_sectionStack.push_back(_sectionInfo); } - void usage( std::ostream& os, std::string const& procName ) const { - validate(); - os << "usage:\n " << procName << " "; - argSynopsis( os ); - if( !m_options.empty() ) { - os << " [options]\n\nwhere options are: \n"; - optUsage( os, 2 ); - } - os << "\n"; + void sectionEnded(SectionStats const& /* _sectionStats */) override { + m_sectionStack.pop_back(); } - std::string usage( std::string const& procName ) const { - std::ostringstream oss; - usage( oss, procName ); - return oss.str(); + void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { + currentTestCaseInfo.reset(); + } + void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { + currentGroupInfo.reset(); + } + void testRunEnded(TestRunStats const& /* _testRunStats */) override { + currentTestCaseInfo.reset(); + currentGroupInfo.reset(); + currentTestRunInfo.reset(); } - ConfigT parse( int argc, char const * const * argv ) const { - ConfigT config; - parseInto( argc, argv, config ); - return config; - } - - std::vector<Parser::Token> parseInto( int argc, char const * const * argv, ConfigT& config ) const { - std::string processName = argv[0]; - std::size_t lastSlash = processName.find_last_of( "/\\" ); - if( lastSlash != std::string::npos ) - processName = processName.substr( lastSlash+1 ); - m_boundProcessName.set( config, processName ); - std::vector<Parser::Token> tokens; - Parser parser; - parser.parseIntoTokens( argc, argv, tokens ); - return populate( tokens, config ); - } - - std::vector<Parser::Token> populate( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - validate(); - std::vector<Parser::Token> unusedTokens = populateOptions( tokens, config ); - unusedTokens = populateFixedArgs( unusedTokens, config ); - unusedTokens = populateFloatingArgs( unusedTokens, config ); - return unusedTokens; - } - - std::vector<Parser::Token> populateOptions( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - std::vector<Parser::Token> unusedTokens; - std::vector<std::string> errors; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - typename std::vector<Arg>::const_iterator it = m_options.begin(), itEnd = m_options.end(); - for(; it != itEnd; ++it ) { - Arg const& arg = *it; - - try { - if( ( token.type == Parser::Token::ShortOpt && arg.hasShortName( token.data ) ) || - ( token.type == Parser::Token::LongOpt && arg.hasLongName( token.data ) ) ) { - if( arg.takesArg() ) { - if( i == tokens.size()-1 || tokens[i+1].type != Parser::Token::Positional ) - errors.push_back( "Expected argument to option: " + token.data ); - else - arg.boundField.set( config, tokens[++i].data ); - } - else { - arg.boundField.setFlag( config ); - } - break; - } - } - catch( std::exception& ex ) { - errors.push_back( std::string( ex.what() ) + "\n- while parsing: (" + arg.commands() + ")" ); - } - } - if( it == itEnd ) { - if( token.type == Parser::Token::Positional || !m_throwOnUnrecognisedTokens ) - unusedTokens.push_back( token ); - else if( errors.empty() && m_throwOnUnrecognisedTokens ) - errors.push_back( "unrecognised option: " + token.data ); - } - } - if( !errors.empty() ) { - std::ostringstream oss; - for( std::vector<std::string>::const_iterator it = errors.begin(), itEnd = errors.end(); - it != itEnd; - ++it ) { - if( it != errors.begin() ) - oss << "\n"; - oss << *it; - } - throw std::runtime_error( oss.str() ); - } - return unusedTokens; - } - std::vector<Parser::Token> populateFixedArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - std::vector<Parser::Token> unusedTokens; - int position = 1; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - typename std::map<int, Arg>::const_iterator it = m_positionalArgs.find( position ); - if( it != m_positionalArgs.end() ) - it->second.boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - if( token.type == Parser::Token::Positional ) - position++; - } - return unusedTokens; - } - std::vector<Parser::Token> populateFloatingArgs( std::vector<Parser::Token> const& tokens, ConfigT& config ) const { - if( !m_floatingArg.get() ) - return tokens; - std::vector<Parser::Token> unusedTokens; - for( std::size_t i = 0; i < tokens.size(); ++i ) { - Parser::Token const& token = tokens[i]; - if( token.type == Parser::Token::Positional ) - m_floatingArg->boundField.set( config, token.data ); - else - unusedTokens.push_back( token ); - } - return unusedTokens; + void skipTest(TestCaseInfo const&) override { + // Don't do anything with this by default. + // It can optionally be overridden in the derived class. } - void validate() const - { - if( m_options.empty() && m_positionalArgs.empty() && !m_floatingArg.get() ) - throw std::logic_error( "No options or arguments specified" ); + IConfigPtr m_config; + std::ostream& stream; - for( typename std::vector<Arg>::const_iterator it = m_options.begin(), - itEnd = m_options.end(); - it != itEnd; ++it ) - it->validate(); - } + LazyStat<TestRunInfo> currentTestRunInfo; + LazyStat<GroupInfo> currentGroupInfo; + LazyStat<TestCaseInfo> currentTestCaseInfo; - private: - Detail::BoundArgFunction<ConfigT> m_boundProcessName; - std::vector<Arg> m_options; - std::map<int, Arg> m_positionalArgs; - ArgAutoPtr m_floatingArg; - int m_highestSpecifiedArgPosition; - bool m_throwOnUnrecognisedTokens; + std::vector<SectionInfo> m_sectionStack; + ReporterPreferences m_reporterPrefs; }; -} // end namespace Clara + template<typename DerivedT> + struct CumulativeReporterBase : IStreamingReporter { + template<typename T, typename ChildNodeT> + struct Node { + explicit Node( T const& _value ) : value( _value ) {} + virtual ~Node() {} -STITCH_CLARA_CLOSE_NAMESPACE -#undef STITCH_CLARA_OPEN_NAMESPACE -#undef STITCH_CLARA_CLOSE_NAMESPACE + using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>; + T value; + ChildNodes children; + }; + struct SectionNode { + explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} + virtual ~SectionNode() = default; -#endif // TWOBLUECUBES_CLARA_H_INCLUDED -#undef STITCH_CLARA_OPEN_NAMESPACE + bool operator == (SectionNode const& other) const { + return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; + } + bool operator == (std::shared_ptr<SectionNode> const& other) const { + return operator==(*other); + } -// Restore Clara's value for console width, if present -#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#define CLARA_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#endif + SectionStats stats; + using ChildSections = std::vector<std::shared_ptr<SectionNode>>; + using Assertions = std::vector<AssertionStats>; + ChildSections childSections; + Assertions assertions; + std::string stdOut; + std::string stdErr; + }; -#include <fstream> + struct BySectionInfo { + BySectionInfo( SectionInfo const& other ) : m_other( other ) {} + BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} + bool operator() (std::shared_ptr<SectionNode> const& node) const { + return ((node->stats.sectionInfo.name == m_other.name) && + (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); + } + void operator=(BySectionInfo const&) = delete; -namespace Catch { + private: + SectionInfo const& m_other; + }; - inline void abortAfterFirst( ConfigData& config ) { config.abortAfter = 1; } - inline void abortAfterX( ConfigData& config, int x ) { - if( x < 1 ) - throw std::runtime_error( "Value after -x or --abortAfter must be greater than zero" ); - config.abortAfter = x; - } - inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + using TestCaseNode = Node<TestCaseStats, SectionNode>; + using TestGroupNode = Node<TestGroupStats, TestCaseNode>; + using TestRunNode = Node<TestRunStats, TestGroupNode>; - inline void addWarning( ConfigData& config, std::string const& _warning ) { - if( _warning == "NoAssertions" ) - config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions ); - else - throw std::runtime_error( "Unrecognised warning: '" + _warning + "'" ); - } - inline void setOrder( ConfigData& config, std::string const& order ) { - if( startsWith( "declared", order ) ) - config.runOrder = RunTests::InDeclarationOrder; - else if( startsWith( "lexical", order ) ) - config.runOrder = RunTests::InLexicographicalOrder; - else if( startsWith( "random", order ) ) - config.runOrder = RunTests::InRandomOrder; - else - throw std::runtime_error( "Unrecognised ordering: '" + order + "'" ); - } - inline void setRngSeed( ConfigData& config, std::string const& seed ) { - if( seed == "time" ) { - config.rngSeed = static_cast<unsigned int>( std::time(0) ); - } - else { - std::stringstream ss; - ss << seed; - ss >> config.rngSeed; - if( ss.fail() ) - throw std::runtime_error( "Argment to --rng-seed should be the word 'time' or a number" ); + CumulativeReporterBase( ReporterConfig const& _config ) + : m_config( _config.fullConfig() ), + stream( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = false; + CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); } - } - inline void setVerbosity( ConfigData& config, int level ) { - // !TBD: accept strings? - config.verbosity = static_cast<Verbosity::Level>( level ); - } - inline void setShowDurations( ConfigData& config, bool _showDurations ) { - config.showDurations = _showDurations - ? ShowDurations::Always - : ShowDurations::Never; - } - inline void loadTestNamesFromFile( ConfigData& config, std::string const& _filename ) { - std::ifstream f( _filename.c_str() ); - if( !f.is_open() ) - throw std::domain_error( "Unable to load input file: " + _filename ); + ~CumulativeReporterBase() override = default; - std::string line; - while( std::getline( f, line ) ) { - line = trim(line); - if( !line.empty() && !startsWith( line, "#" ) ) - addTestOrTags( config, "\"" + line + "\"," ); + ReporterPreferences getPreferences() const override { + return m_reporterPrefs; } - } - - inline Clara::CommandLine<ConfigData> makeCommandLineParser() { - - using namespace Clara; - CommandLine<ConfigData> cli; - - cli.bindProcessName( &ConfigData::processName ); - - cli["-?"]["-h"]["--help"] - .describe( "display usage information" ) - .bind( &ConfigData::showHelp ); - - cli["-l"]["--list-tests"] - .describe( "list all/matching test cases" ) - .bind( &ConfigData::listTests ); - - cli["-t"]["--list-tags"] - .describe( "list all/matching tags" ) - .bind( &ConfigData::listTags ); - - cli["-s"]["--success"] - .describe( "include successful tests in output" ) - .bind( &ConfigData::showSuccessfulTests ); - - cli["-b"]["--break"] - .describe( "break into debugger on failure" ) - .bind( &ConfigData::shouldDebugBreak ); - - cli["-e"]["--nothrow"] - .describe( "skip exception tests" ) - .bind( &ConfigData::noThrow ); - - cli["-i"]["--invisibles"] - .describe( "show invisibles (tabs, newlines)" ) - .bind( &ConfigData::showInvisibles ); - - cli["-o"]["--out"] - .describe( "output filename" ) - .bind( &ConfigData::outputFilename, "filename" ); - - cli["-r"]["--reporter"] -// .placeholder( "name[:filename]" ) - .describe( "reporter to use (defaults to console)" ) - .bind( &ConfigData::reporterName, "name" ); - - cli["-n"]["--name"] - .describe( "suite name" ) - .bind( &ConfigData::name, "name" ); - - cli["-a"]["--abort"] - .describe( "abort at first failure" ) - .bind( &abortAfterFirst ); - - cli["-x"]["--abortx"] - .describe( "abort after x failures" ) - .bind( &abortAfterX, "no. failures" ); - - cli["-w"]["--warn"] - .describe( "enable warnings" ) - .bind( &addWarning, "warning name" ); - -// - needs updating if reinstated -// cli.into( &setVerbosity ) -// .describe( "level of verbosity (0=no output)" ) -// .shortOpt( "v") -// .longOpt( "verbosity" ) -// .placeholder( "level" ); - - cli[_] - .describe( "which test or tests to use" ) - .bind( &addTestOrTags, "test name, pattern or tags" ); - cli["-d"]["--durations"] - .describe( "show test durations" ) - .bind( &setShowDurations, "yes/no" ); - - cli["-f"]["--input-file"] - .describe( "load test names to run from a file" ) - .bind( &loadTestNamesFromFile, "filename" ); - - // Less common commands which don't have a short form - cli["--list-test-names-only"] - .describe( "list all/matching test cases names only" ) - .bind( &ConfigData::listTestNamesOnly ); - - cli["--list-reporters"] - .describe( "list all reporters" ) - .bind( &ConfigData::listReporters ); - - cli["--order"] - .describe( "test case order (defaults to decl)" ) - .bind( &setOrder, "decl|lex|rand" ); - - cli["--rng-seed"] - .describe( "set a specific seed for random numbers" ) - .bind( &setRngSeed, "'time'|number" ); - - cli["--force-colour"] - .describe( "force colourised output" ) - .bind( &ConfigData::forceColour ); - - return cli; - } - -} // end namespace Catch - -// #included from: internal/catch_list.hpp -#define TWOBLUECUBES_CATCH_LIST_HPP_INCLUDED - -// #included from: catch_text.h -#define TWOBLUECUBES_CATCH_TEXT_H_INCLUDED - -#define TBC_TEXT_FORMAT_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH - -#define CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE Catch -// #included from: ../external/tbc_text_format.h -// Only use header guard if we are not using an outer namespace -#ifndef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE -# ifdef TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED -# ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -# define TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -# endif -# else -# define TWOBLUECUBES_TEXT_FORMAT_H_INCLUDED -# endif -#endif -#ifndef TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -#include <string> -#include <vector> -#include <sstream> - -// Use optional outer namespace -#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE -namespace CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE { -#endif - -namespace Tbc { - -#ifdef TBC_TEXT_FORMAT_CONSOLE_WIDTH - const unsigned int consoleWidth = TBC_TEXT_FORMAT_CONSOLE_WIDTH; -#else - const unsigned int consoleWidth = 80; -#endif - - struct TextAttributes { - TextAttributes() - : initialIndent( std::string::npos ), - indent( 0 ), - width( consoleWidth-1 ), - tabChar( '\t' ) - {} - - TextAttributes& setInitialIndent( std::size_t _value ) { initialIndent = _value; return *this; } - TextAttributes& setIndent( std::size_t _value ) { indent = _value; return *this; } - TextAttributes& setWidth( std::size_t _value ) { width = _value; return *this; } - TextAttributes& setTabChar( char _value ) { tabChar = _value; return *this; } + static std::set<Verbosity> getSupportedVerbosities() { + return { Verbosity::Normal }; + } - std::size_t initialIndent; // indent of first line, or npos - std::size_t indent; // indent of subsequent lines, or all if initialIndent is npos - std::size_t width; // maximum width of text, including indent. Longer text will wrap - char tabChar; // If this char is seen the indent is changed to current pos - }; + void testRunStarting( TestRunInfo const& ) override {} + void testGroupStarting( GroupInfo const& ) override {} - class Text { - public: - Text( std::string const& _str, TextAttributes const& _attr = TextAttributes() ) - : attr( _attr ) - { - std::string wrappableChars = " [({.,/|\\-"; - std::size_t indent = _attr.initialIndent != std::string::npos - ? _attr.initialIndent - : _attr.indent; - std::string remainder = _str; - - while( !remainder.empty() ) { - if( lines.size() >= 1000 ) { - lines.push_back( "... message truncated due to excessive size" ); - return; - } - std::size_t tabPos = std::string::npos; - std::size_t width = (std::min)( remainder.size(), _attr.width - indent ); - std::size_t pos = remainder.find_first_of( '\n' ); - if( pos <= width ) { - width = pos; - } - pos = remainder.find_last_of( _attr.tabChar, width ); - if( pos != std::string::npos ) { - tabPos = pos; - if( remainder[width] == '\n' ) - width--; - remainder = remainder.substr( 0, tabPos ) + remainder.substr( tabPos+1 ); - } + void testCaseStarting( TestCaseInfo const& ) override {} - if( width == remainder.size() ) { - spliceLine( indent, remainder, width ); - } - else if( remainder[width] == '\n' ) { - spliceLine( indent, remainder, width ); - if( width <= 1 || remainder.size() != 1 ) - remainder = remainder.substr( 1 ); - indent = _attr.indent; - } - else { - pos = remainder.find_last_of( wrappableChars, width ); - if( pos != std::string::npos && pos > 0 ) { - spliceLine( indent, remainder, pos ); - if( remainder[0] == ' ' ) - remainder = remainder.substr( 1 ); - } - else { - spliceLine( indent, remainder, width-1 ); - lines.back() += "-"; - } - if( lines.size() == 1 ) - indent = _attr.indent; - if( tabPos != std::string::npos ) - indent += tabPos; + void sectionStarting( SectionInfo const& sectionInfo ) override { + SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); + std::shared_ptr<SectionNode> node; + if( m_sectionStack.empty() ) { + if( !m_rootSection ) + m_rootSection = std::make_shared<SectionNode>( incompleteStats ); + node = m_rootSection; + } + else { + SectionNode& parentNode = *m_sectionStack.back(); + auto it = + std::find_if( parentNode.childSections.begin(), + parentNode.childSections.end(), + BySectionInfo( sectionInfo ) ); + if( it == parentNode.childSections.end() ) { + node = std::make_shared<SectionNode>( incompleteStats ); + parentNode.childSections.push_back( node ); } + else + node = *it; } + m_sectionStack.push_back( node ); + m_deepestSection = std::move(node); } - void spliceLine( std::size_t _indent, std::string& _remainder, std::size_t _pos ) { - lines.push_back( std::string( _indent, ' ' ) + _remainder.substr( 0, _pos ) ); - _remainder = _remainder.substr( _pos ); - } - - typedef std::vector<std::string>::const_iterator const_iterator; + void assertionStarting(AssertionInfo const&) override {} - const_iterator begin() const { return lines.begin(); } - const_iterator end() const { return lines.end(); } - std::string const& last() const { return lines.back(); } - std::size_t size() const { return lines.size(); } - std::string const& operator[]( std::size_t _index ) const { return lines[_index]; } - std::string toString() const { - std::ostringstream oss; - oss << *this; - return oss.str(); + bool assertionEnded(AssertionStats const& assertionStats) override { + assert(!m_sectionStack.empty()); + // AssertionResult holds a pointer to a temporary DecomposedExpression, + // which getExpandedExpression() calls to build the expression string. + // Our section stack copy of the assertionResult will likely outlive the + // temporary, so it must be expanded or discarded now to avoid calling + // a destroyed object later. + prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) ); + SectionNode& sectionNode = *m_sectionStack.back(); + sectionNode.assertions.push_back(assertionStats); + return true; + } + void sectionEnded(SectionStats const& sectionStats) override { + assert(!m_sectionStack.empty()); + SectionNode& node = *m_sectionStack.back(); + node.stats = sectionStats; + m_sectionStack.pop_back(); } + void testCaseEnded(TestCaseStats const& testCaseStats) override { + auto node = std::make_shared<TestCaseNode>(testCaseStats); + assert(m_sectionStack.size() == 0); + node->children.push_back(m_rootSection); + m_testCases.push_back(node); + m_rootSection.reset(); - inline friend std::ostream& operator << ( std::ostream& _stream, Text const& _text ) { - for( Text::const_iterator it = _text.begin(), itEnd = _text.end(); - it != itEnd; ++it ) { - if( it != _text.begin() ) - _stream << "\n"; - _stream << *it; - } - return _stream; + assert(m_deepestSection); + m_deepestSection->stdOut = testCaseStats.stdOut; + m_deepestSection->stdErr = testCaseStats.stdErr; + } + void testGroupEnded(TestGroupStats const& testGroupStats) override { + auto node = std::make_shared<TestGroupNode>(testGroupStats); + node->children.swap(m_testCases); + m_testGroups.push_back(node); + } + void testRunEnded(TestRunStats const& testRunStats) override { + auto node = std::make_shared<TestRunNode>(testRunStats); + node->children.swap(m_testGroups); + m_testRuns.push_back(node); + testRunEndedCumulative(); } + virtual void testRunEndedCumulative() = 0; - private: - std::string str; - TextAttributes attr; - std::vector<std::string> lines; + void skipTest(TestCaseInfo const&) override {} + + IConfigPtr m_config; + std::ostream& stream; + std::vector<AssertionStats> m_assertions; + std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections; + std::vector<std::shared_ptr<TestCaseNode>> m_testCases; + std::vector<std::shared_ptr<TestGroupNode>> m_testGroups; + + std::vector<std::shared_ptr<TestRunNode>> m_testRuns; + + std::shared_ptr<SectionNode> m_rootSection; + std::shared_ptr<SectionNode> m_deepestSection; + std::vector<std::shared_ptr<SectionNode>> m_sectionStack; + ReporterPreferences m_reporterPrefs; }; -} // end namespace Tbc + template<char C> + char const* getLineOfChars() { + static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; + if( !*line ) { + std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); + line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; + } + return line; + } -#ifdef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE -} // end outer namespace -#endif + struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> { + TestEventListenerBase( ReporterConfig const& _config ); -#endif // TWOBLUECUBES_TEXT_FORMAT_H_ALREADY_INCLUDED -#undef CLICHE_TBC_TEXT_FORMAT_OUTER_NAMESPACE + void assertionStarting(AssertionInfo const&) override; + bool assertionEnded(AssertionStats const&) override; + }; -namespace Catch { - using Tbc::Text; - using Tbc::TextAttributes; -} +} // end namespace Catch -// #included from: catch_console_colour.hpp -#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_HPP_INCLUDED +// end catch_reporter_bases.hpp +// start catch_console_colour.h namespace Catch { @@ -4495,2695 +3701,2495 @@ namespace Catch { // Use constructed object for RAII guard Colour( Code _colourCode ); - Colour( Colour const& other ); + Colour( Colour&& other ) noexcept; + Colour& operator=( Colour&& other ) noexcept; ~Colour(); // Use static method for one-shot changes static void use( Code _colourCode ); private: - bool m_moved; + bool m_moved = false; }; - inline std::ostream& operator << ( std::ostream& os, Colour const& ) { return os; } + std::ostream& operator << ( std::ostream& os, Colour const& ); } // end namespace Catch -// #included from: catch_interfaces_reporter.h -#define TWOBLUECUBES_CATCH_INTERFACES_REPORTER_H_INCLUDED +// end catch_console_colour.h +// start catch_reporter_registrars.hpp -#include <string> -#include <ostream> -#include <map> -#include <assert.h> -namespace Catch -{ - struct ReporterConfig { - explicit ReporterConfig( Ptr<IConfig> const& _fullConfig ) - : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} +namespace Catch { - ReporterConfig( Ptr<IConfig> const& _fullConfig, std::ostream& _stream ) - : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} + template<typename T> + class ReporterRegistrar { - std::ostream& stream() const { return *m_stream; } - Ptr<IConfig> fullConfig() const { return m_fullConfig; } + class ReporterFactory : public IReporterFactory { - private: - std::ostream* m_stream; - Ptr<IConfig> m_fullConfig; - }; + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); + } - struct ReporterPreferences { - ReporterPreferences() - : shouldRedirectStdOut( false ) - {} + virtual std::string getDescription() const override { + return T::getDescription(); + } + }; - bool shouldRedirectStdOut; - }; + public: - template<typename T> - struct LazyStat : Option<T> { - LazyStat() : used( false ) {} - LazyStat& operator=( T const& _value ) { - Option<T>::operator=( _value ); - used = false; - return *this; - } - void reset() { - Option<T>::reset(); - used = false; + ReporterRegistrar( std::string const& name ) { + getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() ); } - bool used; - }; - - struct TestRunInfo { - TestRunInfo( std::string const& _name ) : name( _name ) {} - std::string name; }; - struct GroupInfo { - GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ) - : name( _name ), - groupIndex( _groupIndex ), - groupsCounts( _groupsCount ) - {} - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; - }; + template<typename T> + class ListenerRegistrar { - struct AssertionStats { - AssertionStats( AssertionResult const& _assertionResult, - std::vector<MessageInfo> const& _infoMessages, - Totals const& _totals ) - : assertionResult( _assertionResult ), - infoMessages( _infoMessages ), - totals( _totals ) - { - if( assertionResult.hasMessage() ) { - // Copy message into messages list. - // !TBD This should have been done earlier, somewhere - MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); - builder << assertionResult.getMessage(); - builder.m_info.message = builder.m_stream.str(); + class ListenerFactory : public IReporterFactory { - infoMessages.push_back( builder.m_info ); + virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { + return std::unique_ptr<T>( new T( config ) ); } - } - virtual ~AssertionStats(); + virtual std::string getDescription() const override { + return std::string(); + } + }; -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - AssertionStats( AssertionStats const& ) = default; - AssertionStats( AssertionStats && ) = default; - AssertionStats& operator = ( AssertionStats const& ) = default; - AssertionStats& operator = ( AssertionStats && ) = default; -# endif + public: - AssertionResult assertionResult; - std::vector<MessageInfo> infoMessages; - Totals totals; + ListenerRegistrar() { + getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() ); + } }; +} - struct SectionStats { - SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ) - : sectionInfo( _sectionInfo ), - assertions( _assertions ), - durationInSeconds( _durationInSeconds ), - missingAssertions( _missingAssertions ) - {} - virtual ~SectionStats(); -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - SectionStats( SectionStats const& ) = default; - SectionStats( SectionStats && ) = default; - SectionStats& operator = ( SectionStats const& ) = default; - SectionStats& operator = ( SectionStats && ) = default; -# endif +#if !defined(CATCH_CONFIG_DISABLE) - SectionInfo sectionInfo; - Counts assertions; - double durationInSeconds; - bool missingAssertions; - }; +#define CATCH_REGISTER_REPORTER( name, reporterType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ + CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - struct TestCaseStats { - TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ) - : testInfo( _testInfo ), - totals( _totals ), - stdOut( _stdOut ), - stdErr( _stdErr ), - aborting( _aborting ) - {} - virtual ~TestCaseStats(); +#define CATCH_REGISTER_LISTENER( listenerType ) \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#else // CATCH_CONFIG_DISABLE -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - TestCaseStats( TestCaseStats const& ) = default; - TestCaseStats( TestCaseStats && ) = default; - TestCaseStats& operator = ( TestCaseStats const& ) = default; - TestCaseStats& operator = ( TestCaseStats && ) = default; -# endif +#define CATCH_REGISTER_REPORTER(name, reporterType) +#define CATCH_REGISTER_LISTENER(listenerType) - TestCaseInfo testInfo; - Totals totals; - std::string stdOut; - std::string stdErr; - bool aborting; - }; +#endif // CATCH_CONFIG_DISABLE - struct TestGroupStats { - TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ) - : groupInfo( _groupInfo ), - totals( _totals ), - aborting( _aborting ) - {} - TestGroupStats( GroupInfo const& _groupInfo ) - : groupInfo( _groupInfo ), - aborting( false ) - {} - virtual ~TestGroupStats(); +// end catch_reporter_registrars.hpp +// end catch_external_interfaces.h +#endif -# ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS - TestGroupStats( TestGroupStats const& ) = default; - TestGroupStats( TestGroupStats && ) = default; - TestGroupStats& operator = ( TestGroupStats const& ) = default; - TestGroupStats& operator = ( TestGroupStats && ) = default; -# endif +#ifdef CATCH_IMPL +// start catch_impl.hpp - GroupInfo groupInfo; - Totals totals; - bool aborting; - }; +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#endif - struct TestRunStats { - TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ) - : runInfo( _runInfo ), - totals( _totals ), - aborting( _aborting ) - {} - virtual ~TestRunStats(); +// Keep these here for external reporters +// start catch_test_case_tracker.h -# ifndef CATCH_CONFIG_CPP11_GENERATED_METHODS - TestRunStats( TestRunStats const& _other ) - : runInfo( _other.runInfo ), - totals( _other.totals ), - aborting( _other.aborting ) - {} -# else - TestRunStats( TestRunStats const& ) = default; - TestRunStats( TestRunStats && ) = default; - TestRunStats& operator = ( TestRunStats const& ) = default; - TestRunStats& operator = ( TestRunStats && ) = default; -# endif - - TestRunInfo runInfo; - Totals totals; - bool aborting; - }; +#include <string> +#include <vector> +#include <memory> - struct IStreamingReporter : IShared { - virtual ~IStreamingReporter(); +namespace Catch { +namespace TestCaseTracking { - // Implementing class must also provide the following static method: - // static std::string getDescription(); + struct NameAndLocation { + std::string name; + SourceLineInfo location; - virtual ReporterPreferences getPreferences() const = 0; + NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); + }; - virtual void noMatchingTestCases( std::string const& spec ) = 0; + struct ITracker; - virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; - virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; + using ITrackerPtr = std::shared_ptr<ITracker>; - virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; - virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; + struct ITracker { + virtual ~ITracker(); - virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; + // static queries + virtual NameAndLocation const& nameAndLocation() const = 0; - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; - virtual void sectionEnded( SectionStats const& sectionStats ) = 0; - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; - virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; - virtual void skipTest( TestCaseInfo const& testInfo ) = 0; - }; + virtual ITracker& parent() = 0; - struct IReporterFactory { - virtual ~IReporterFactory(); - virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; - virtual std::string getDescription() const = 0; - }; + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; - struct IReporterRegistry { - typedef std::map<std::string, IReporterFactory*> FactoryMap; + virtual void addChild( ITrackerPtr const& child ) = 0; + virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; + virtual void openChild() = 0; - virtual ~IReporterRegistry(); - virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig> const& config ) const = 0; - virtual FactoryMap const& getFactories() const = 0; + // Debug/ checking + virtual bool isSectionTracker() const = 0; + virtual bool isIndexTracker() const = 0; }; -} + class TrackerContext { -#include <limits> -#include <algorithm> + enum RunState { + NotStarted, + Executing, + CompletedCycle + }; -namespace Catch { + ITrackerPtr m_rootTracker; + ITracker* m_currentTracker = nullptr; + RunState m_runState = NotStarted; - inline std::size_t listTests( Config const& config ) { + public: - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Matching test cases:\n"; - else { - Catch::cout() << "All available test cases:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - } + static TrackerContext& instance(); - std::size_t matchedTests = 0; - TextAttributes nameAttr, tagsAttr; - nameAttr.setInitialIndent( 2 ).setIndent( 4 ); - tagsAttr.setIndent( 6 ); - - std::vector<TestCase> matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); - for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); - it != itEnd; - ++it ) { - matchedTests++; - TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); - Colour::Code colour = testCaseInfo.isHidden() - ? Colour::SecondaryText - : Colour::None; - Colour colourGuard( colour ); + ITracker& startRun(); + void endRun(); - Catch::cout() << Text( testCaseInfo.name, nameAttr ) << std::endl; - if( !testCaseInfo.tags.empty() ) - Catch::cout() << Text( testCaseInfo.tagsAsString, tagsAttr ) << std::endl; - } + void startCycle(); + void completeCycle(); - if( !config.testSpec().hasFilters() ) - Catch::cout() << pluralise( matchedTests, "test case" ) << "\n" << std::endl; - else - Catch::cout() << pluralise( matchedTests, "matching test case" ) << "\n" << std::endl; - return matchedTests; - } + bool completedCycle() const; + ITracker& currentTracker(); + void setCurrentTracker( ITracker* tracker ); + }; - inline std::size_t listTestsNamesOnly( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( !config.testSpec().hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - std::size_t matchedTests = 0; - std::vector<TestCase> matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); - for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); - it != itEnd; - ++it ) { - matchedTests++; - TestCaseInfo const& testCaseInfo = it->getTestCaseInfo(); - Catch::cout() << testCaseInfo.name << std::endl; - } - return matchedTests; - } + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; - struct TagInfo { - TagInfo() : count ( 0 ) {} - void add( std::string const& spelling ) { - ++count; - spellings.insert( spelling ); - } - std::string all() const { - std::string out; - for( std::set<std::string>::const_iterator it = spellings.begin(), itEnd = spellings.end(); - it != itEnd; - ++it ) - out += "[" + *it + "]"; - return out; - } - std::set<std::string> spellings; - std::size_t count; - }; + class TrackerHasName { + NameAndLocation m_nameAndLocation; + public: + TrackerHasName( NameAndLocation const& nameAndLocation ); + bool operator ()( ITrackerPtr const& tracker ) const; + }; - inline std::size_t listTags( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Tags for matching test cases:\n"; - else { - Catch::cout() << "All available tags:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - } + using Children = std::vector<ITrackerPtr>; + NameAndLocation m_nameAndLocation; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState = NotStarted; - std::map<std::string, TagInfo> tagCounts; + public: + TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - std::vector<TestCase> matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); - for( std::vector<TestCase>::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); - it != itEnd; - ++it ) { - for( std::set<std::string>::const_iterator tagIt = it->getTestCaseInfo().tags.begin(), - tagItEnd = it->getTestCaseInfo().tags.end(); - tagIt != tagItEnd; - ++tagIt ) { - std::string tagName = *tagIt; - std::string lcaseTagName = toLower( tagName ); - std::map<std::string, TagInfo>::iterator countIt = tagCounts.find( lcaseTagName ); - if( countIt == tagCounts.end() ) - countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; - countIt->second.add( tagName ); - } - } + NameAndLocation const& nameAndLocation() const override; + bool isComplete() const override; + bool isSuccessfullyCompleted() const override; + bool isOpen() const override; + bool hasChildren() const override; - for( std::map<std::string, TagInfo>::const_iterator countIt = tagCounts.begin(), - countItEnd = tagCounts.end(); - countIt != countItEnd; - ++countIt ) { - std::ostringstream oss; - oss << " " << std::setw(2) << countIt->second.count << " "; - Text wrapper( countIt->second.all(), TextAttributes() - .setInitialIndent( 0 ) - .setIndent( oss.str().size() ) - .setWidth( CATCH_CONFIG_CONSOLE_WIDTH-10 ) ); - Catch::cout() << oss.str() << wrapper << "\n"; - } - Catch::cout() << pluralise( tagCounts.size(), "tag" ) << "\n" << std::endl; - return tagCounts.size(); - } + void addChild( ITrackerPtr const& child ) override; - inline std::size_t listReporters( Config const& /*config*/ ) { - Catch::cout() << "Available reporters:\n"; - IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); - IReporterRegistry::FactoryMap::const_iterator itBegin = factories.begin(), itEnd = factories.end(), it; - std::size_t maxNameLen = 0; - for(it = itBegin; it != itEnd; ++it ) - maxNameLen = (std::max)( maxNameLen, it->first.size() ); - - for(it = itBegin; it != itEnd; ++it ) { - Text wrapper( it->second->getDescription(), TextAttributes() - .setInitialIndent( 0 ) - .setIndent( 7+maxNameLen ) - .setWidth( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) ); - Catch::cout() << " " - << it->first - << ":" - << std::string( maxNameLen - it->first.size() + 2, ' ' ) - << wrapper << "\n"; - } - Catch::cout() << std::endl; - return factories.size(); - } + ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; + ITracker& parent() override; - inline Option<std::size_t> list( Config const& config ) { - Option<std::size_t> listedCount; - if( config.listTests() ) - listedCount = listedCount.valueOr(0) + listTests( config ); - if( config.listTestNamesOnly() ) - listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); - if( config.listTags() ) - listedCount = listedCount.valueOr(0) + listTags( config ); - if( config.listReporters() ) - listedCount = listedCount.valueOr(0) + listReporters( config ); - return listedCount; - } + void openChild() override; -} // end namespace Catch + bool isSectionTracker() const override; + bool isIndexTracker() const override; -// #included from: internal/catch_runner_impl.hpp -#define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED + void open(); -// #included from: catch_test_case_tracker.hpp -#define TWOBLUECUBES_CATCH_TEST_CASE_TRACKER_HPP_INCLUDED + void close() override; + void fail() override; + void markAsNeedingAnotherRun() override; -#include <map> -#include <string> -#include <assert.h> + private: + void moveToParent(); + void moveToThis(); + }; -namespace Catch { -namespace SectionTracking { + class SectionTracker : public TrackerBase { + std::vector<std::string> m_filters; + public: + SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - class TrackedSection { + bool isSectionTracker() const override; - typedef std::map<std::string, TrackedSection> TrackedSections; + static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); - public: - enum RunState { - NotStarted, - Executing, - ExecutingChildren, - Completed - }; + void tryOpen(); - TrackedSection( std::string const& name, TrackedSection* parent ) - : m_name( name ), m_runState( NotStarted ), m_parent( parent ) - {} + void addInitialFilters( std::vector<std::string> const& filters ); + void addNextFilters( std::vector<std::string> const& filters ); + }; - RunState runState() const { return m_runState; } + class IndexTracker : public TrackerBase { + int m_size; + int m_index = -1; + public: + IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); - TrackedSection* findChild( std::string const& childName ); - TrackedSection* acquireChild( std::string const& childName ); + bool isIndexTracker() const override; + void close() override; - void enter() { - if( m_runState == NotStarted ) - m_runState = Executing; - } - void leave(); + static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); - TrackedSection* getParent() { - return m_parent; - } - bool hasChildren() const { - return !m_children.empty(); - } + int index() const; - private: - std::string m_name; - RunState m_runState; - TrackedSections m_children; - TrackedSection* m_parent; + void moveNext(); }; - inline TrackedSection* TrackedSection::findChild( std::string const& childName ) { - TrackedSections::iterator it = m_children.find( childName ); - return it != m_children.end() - ? &it->second - : NULL; - } - inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { - if( TrackedSection* child = findChild( childName ) ) - return child; - m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); - return findChild( childName ); - } - inline void TrackedSection::leave() { - for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); - it != itEnd; - ++it ) - if( it->second.runState() != Completed ) { - m_runState = ExecutingChildren; - return; - } - m_runState = Completed; - } - - class TestCaseTracker { - public: - TestCaseTracker( std::string const& testCaseName ) - : m_testCase( testCaseName, NULL ), - m_currentSection( &m_testCase ), - m_completedASectionThisRun( false ) - {} +} // namespace TestCaseTracking - bool enterSection( std::string const& name ) { - TrackedSection* child = m_currentSection->acquireChild( name ); - if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) - return false; +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; - m_currentSection = child; - m_currentSection->enter(); - return true; - } - void leaveSection() { - m_currentSection->leave(); - m_currentSection = m_currentSection->getParent(); - assert( m_currentSection != NULL ); - m_completedASectionThisRun = true; - } +} // namespace Catch - bool currentSectionHasChildren() const { - return m_currentSection->hasChildren(); - } - bool isCompleted() const { - return m_testCase.runState() == TrackedSection::Completed; - } +// end catch_test_case_tracker.h - class Guard { - public: - Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { - m_tracker.enterTestCase(); - } - ~Guard() { - m_tracker.leaveTestCase(); - } - private: - Guard( Guard const& ); - void operator = ( Guard const& ); - TestCaseTracker& m_tracker; - }; +// start catch_leak_detector.h - private: - void enterTestCase() { - m_currentSection = &m_testCase; - m_completedASectionThisRun = false; - m_testCase.enter(); - } - void leaveTestCase() { - m_testCase.leave(); - } +namespace Catch { - TrackedSection m_testCase; - TrackedSection* m_currentSection; - bool m_completedASectionThisRun; + struct LeakDetector { + LeakDetector(); }; -} // namespace SectionTracking +} +// end catch_leak_detector.h +// Cpp files will be included in the single-header file here +// start catch_approx.cpp + +#include <cmath> +#include <limits> -using SectionTracking::TestCaseTracker; +namespace { -} // namespace Catch +// Performs equivalent check of std::fabs(lhs - rhs) <= margin +// But without the subtraction to allow for INFINITY in comparison +bool marginComparison(double lhs, double rhs, double margin) { + return (lhs + margin >= rhs) && (rhs + margin >= lhs); +} -// #included from: catch_fatal_condition.hpp -#define TWOBLUECUBES_CATCH_FATAL_CONDITION_H_INCLUDED +} namespace Catch { +namespace Detail { - // Report the error condition then exit the process - inline void fatal( std::string const& message, int exitCode ) { - IContext& context = Catch::getCurrentContext(); - IResultCapture* resultCapture = context.getResultCapture(); - resultCapture->handleFatalErrorCondition( message ); + Approx::Approx ( double value ) + : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), + m_margin( 0.0 ), + m_scale( 0.0 ), + m_value( value ) + {} - if( Catch::alwaysTrue() ) // avoids "no return" warnings - exit( exitCode ); + Approx Approx::custom() { + return Approx( 0 ); } -} // namespace Catch + std::string Approx::toString() const { + std::ostringstream oss; + oss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; + return oss.str(); + } -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// + bool Approx::equalityComparisonImpl(const double other) const { + // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value + // Thanks to Richard Harris for his help refining the scaled margin value + return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); + } -namespace Catch { +} // end namespace Detail - struct FatalConditionHandler { - void reset() {} - }; +std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) { + return value.toString(); +} -} // namespace Catch +} // end namespace Catch +// end catch_approx.cpp +// start catch_assertionhandler.cpp -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// +// start catch_context.h -#include <signal.h> +#include <memory> namespace Catch { - struct SignalDefs { int id; const char* name; }; - extern SignalDefs signalDefs[]; - SignalDefs signalDefs[] = { - { SIGINT, "SIGINT - Terminal interrupt signal" }, - { SIGILL, "SIGILL - Illegal instruction signal" }, - { SIGFPE, "SIGFPE - Floating point error signal" }, - { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, - { SIGTERM, "SIGTERM - Termination request signal" }, - { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } - }; - - struct FatalConditionHandler { + struct IResultCapture; + struct IRunner; + struct IConfig; - static void handleSignal( int sig ) { - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) - if( sig == signalDefs[i].id ) - fatal( signalDefs[i].name, -sig ); - fatal( "<unknown signal>", -sig ); - } + using IConfigPtr = std::shared_ptr<IConfig const>; - FatalConditionHandler() : m_isSet( true ) { - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) - signal( signalDefs[i].id, handleSignal ); - } - ~FatalConditionHandler() { - reset(); - } - void reset() { - if( m_isSet ) { - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) - signal( signalDefs[i].id, SIG_DFL ); - m_isSet = false; - } - } + struct IContext + { + virtual ~IContext(); - bool m_isSet; + virtual IResultCapture* getResultCapture() = 0; + virtual IRunner* getRunner() = 0; + virtual IConfigPtr getConfig() const = 0; }; -} // namespace Catch + struct IMutableContext : IContext + { + virtual ~IMutableContext(); + virtual void setResultCapture( IResultCapture* resultCapture ) = 0; + virtual void setRunner( IRunner* runner ) = 0; + virtual void setConfig( IConfigPtr const& config ) = 0; + }; -#endif // not Windows + IContext& getCurrentContext(); + IMutableContext& getCurrentMutableContext(); + void cleanUpContext(); +} -#include <set> -#include <string> +// end catch_context.h +#include <cassert> namespace Catch { - class StreamRedirect { - - public: - StreamRedirect( std::ostream& stream, std::string& targetString ) - : m_stream( stream ), - m_prevBuf( stream.rdbuf() ), - m_targetString( targetString ) - { - stream.rdbuf( m_oss.rdbuf() ); - } - - ~StreamRedirect() { - m_targetString += m_oss.str(); - m_stream.rdbuf( m_prevBuf ); - } - - private: - std::ostream& m_stream; - std::streambuf* m_prevBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; + auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { + expr.streamReconstructedExpression( os ); + return os; + } - /////////////////////////////////////////////////////////////////////////// + LazyExpression::LazyExpression( bool isNegated ) + : m_isNegated( isNegated ) + {} - class RunContext : public IResultCapture, public IRunner { + LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} - RunContext( RunContext const& ); - void operator =( RunContext const& ); + LazyExpression::operator bool() const { + return m_transientExpression != nullptr; + } - public: + auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { + if( lazyExpr.m_isNegated ) + os << "!"; - explicit RunContext( Ptr<IConfig const> const& config, Ptr<IStreamingReporter> const& reporter ) - : m_runInfo( config->name() ), - m_context( getCurrentMutableContext() ), - m_activeTestCase( NULL ), - m_config( config ), - m_reporter( reporter ), - m_prevRunner( m_context.getRunner() ), - m_prevResultCapture( m_context.getResultCapture() ), - m_prevConfig( m_context.getConfig() ) - { - m_context.setRunner( this ); - m_context.setConfig( m_config ); - m_context.setResultCapture( this ); - m_reporter->testRunStarting( m_runInfo ); + if( lazyExpr ) { + if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) + os << "(" << *lazyExpr.m_transientExpression << ")"; + else + os << *lazyExpr.m_transientExpression; } - - virtual ~RunContext() { - m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); - m_context.setRunner( m_prevRunner ); - m_context.setConfig( NULL ); - m_context.setResultCapture( m_prevResultCapture ); - m_context.setConfig( m_prevConfig ); + else { + os << "{** error - unchecked empty expression requested **}"; } + return os; + } - void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { - m_reporter->testGroupStarting( GroupInfo( testSpec, groupIndex, groupsCount ) ); + AssertionHandler::AssertionHandler + ( StringRef macroName, + SourceLineInfo const& lineInfo, + StringRef capturedExpression, + ResultDisposition::Flags resultDisposition ) + : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition } + { + getCurrentContext().getResultCapture()->assertionStarting( m_assertionInfo ); + } + AssertionHandler::~AssertionHandler() { + if ( m_inExceptionGuard ) { + handle( ResultWas::ThrewException, "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE" ); + getCurrentContext().getResultCapture()->exceptionEarlyReported(); } - void testGroupEnded( std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount ) { - m_reporter->testGroupEnded( TestGroupStats( GroupInfo( testSpec, groupIndex, groupsCount ), totals, aborting() ) ); - } - - Totals runTest( TestCase const& testCase ) { - Totals prevTotals = m_totals; - - std::string redirectedCout; - std::string redirectedCerr; - - TestCaseInfo testInfo = testCase.getTestCaseInfo(); + } - m_reporter->testCaseStarting( testInfo ); + void AssertionHandler::handle( ITransientExpression const& expr ) { - m_activeTestCase = &testCase; - m_testCaseTracker = TestCaseTracker( testInfo.name ); + bool negated = isFalseTest( m_assertionInfo.resultDisposition ); + bool result = expr.getResult() != negated; - do { - do { - runCurrentTest( redirectedCout, redirectedCerr ); - } - while( !m_testCaseTracker->isCompleted() && !aborting() ); - } - while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); + handle( result ? ResultWas::Ok : ResultWas::ExpressionFailed, &expr, negated ); + } + void AssertionHandler::handle( ResultWas::OfType resultType ) { + handle( resultType, nullptr, false ); + } + void AssertionHandler::handle( ResultWas::OfType resultType, StringRef const& message ) { + AssertionResultData data( resultType, LazyExpression( false ) ); + data.message = message; + handle( data, nullptr ); + } + void AssertionHandler::handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ) { + AssertionResultData data( resultType, LazyExpression( negated ) ); + handle( data, expr ); + } + void AssertionHandler::handle( AssertionResultData const& resultData, ITransientExpression const* expr ) { - Totals deltaTotals = m_totals.delta( prevTotals ); - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded( TestCaseStats( testInfo, - deltaTotals, - redirectedCout, - redirectedCerr, - aborting() ) ); + getResultCapture().assertionRun(); - m_activeTestCase = NULL; - m_testCaseTracker.reset(); + AssertionResult assertionResult{ m_assertionInfo, resultData }; + assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - return deltaTotals; - } + getResultCapture().assertionEnded( assertionResult ); - Ptr<IConfig const> config() const { - return m_config; + if( !assertionResult.isOk() ) { + m_shouldDebugBreak = getCurrentContext().getConfig()->shouldDebugBreak(); + m_shouldThrow = + getCurrentContext().getRunner()->aborting() || + (m_assertionInfo.resultDisposition & ResultDisposition::Normal); } + } - private: // IResultCapture - - virtual void assertionEnded( AssertionResult const& result ) { - if( result.getResultType() == ResultWas::Ok ) { - m_totals.assertions.passed++; - } - else if( !result.isOk() ) { - m_totals.assertions.failed++; - } - - if( m_reporter->assertionEnded( AssertionStats( result, m_messages, m_totals ) ) ) - m_messages.clear(); + auto AssertionHandler::allowThrows() const -> bool { + return getCurrentContext().getConfig()->allowThrows(); + } - // Reset working state - m_lastAssertionInfo = AssertionInfo( "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}" , m_lastAssertionInfo.resultDisposition ); - m_lastResult = result; + auto AssertionHandler::shouldDebugBreak() const -> bool { + return m_shouldDebugBreak; + } + void AssertionHandler::reactWithDebugBreak() const { + if (m_shouldDebugBreak) { + /////////////////////////////////////////////////////////////////// + // To inspect the state during test, you need to go one level up the callstack + // To go back to the test and change execution, jump over the reactWithoutDebugBreak() call + /////////////////////////////////////////////////////////////////// + CATCH_BREAK_INTO_DEBUGGER(); } + reactWithoutDebugBreak(); + } + void AssertionHandler::reactWithoutDebugBreak() const { + if( m_shouldThrow ) + throw Catch::TestFailureException(); + } - virtual bool sectionStarted ( - SectionInfo const& sectionInfo, - Counts& assertions - ) - { - std::ostringstream oss; - oss << sectionInfo.name << "@" << sectionInfo.lineInfo; + void AssertionHandler::useActiveException() { + handle( ResultWas::ThrewException, Catch::translateActiveException() ); + } - if( !m_testCaseTracker->enterSection( oss.str() ) ) - return false; + void AssertionHandler::setExceptionGuard() { + assert( m_inExceptionGuard == false ); + m_inExceptionGuard = true; + } + void AssertionHandler::unsetExceptionGuard() { + assert( m_inExceptionGuard == true ); + m_inExceptionGuard = false; + } - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + // This is the overload that takes a string and infers the Equals matcher from it + // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp + void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { + handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); + } - m_reporter->sectionStarting( sectionInfo ); +} // namespace Catch +// end catch_assertionhandler.cpp +// start catch_assertionresult.cpp - assertions = m_totals.assertions; +namespace Catch { + AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): + lazyExpression(_lazyExpression), + resultType(_resultType) {} - return true; - } - bool testForMissingAssertions( Counts& assertions ) { - if( assertions.total() != 0 || - !m_config->warnAboutMissingAssertions() || - m_testCaseTracker->currentSectionHasChildren() ) - return false; - m_totals.assertions.failed++; - assertions.failed++; - return true; - } + std::string AssertionResultData::reconstructExpression() const { - virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { - if( std::uncaught_exception() ) { - m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); - return; + if( reconstructedExpression.empty() ) { + if( lazyExpression ) { + // !TBD Use stringstream for now, but rework above to pass stream in + std::ostringstream oss; + oss << lazyExpression; + reconstructedExpression = oss.str(); } - - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions( assertions ); - - m_testCaseTracker->leaveSection(); - - m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); - m_messages.clear(); } + return reconstructedExpression; + } - virtual void pushScopedMessage( MessageInfo const& message ) { - m_messages.push_back( message ); - } + AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) + : m_info( info ), + m_resultData( data ) + {} - virtual void popScopedMessage( MessageInfo const& message ) { - m_messages.erase( std::remove( m_messages.begin(), m_messages.end(), message ), m_messages.end() ); - } + // Result was a success + bool AssertionResult::succeeded() const { + return Catch::isOk( m_resultData.resultType ); + } - virtual std::string getCurrentTestName() const { - return m_activeTestCase - ? m_activeTestCase->getTestCaseInfo().name - : ""; - } + // Result was a success, or failure is suppressed + bool AssertionResult::isOk() const { + return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); + } - virtual const AssertionResult* getLastResult() const { - return &m_lastResult; - } + ResultWas::OfType AssertionResult::getResultType() const { + return m_resultData.resultType; + } - virtual void handleFatalErrorCondition( std::string const& message ) { - ResultBuilder resultBuilder = makeUnexpectedResultBuilder(); - resultBuilder.setResultType( ResultWas::FatalErrorCondition ); - resultBuilder << message; - resultBuilder.captureExpression(); + bool AssertionResult::hasExpression() const { + return m_info.capturedExpression[0] != 0; + } - handleUnfinishedSections(); + bool AssertionResult::hasMessage() const { + return !m_resultData.message.empty(); + } - // Recreate section for test case (as we will lose the one that was in scope) - TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); + std::string AssertionResult::getExpression() const { + if( isFalseTest( m_info.resultDisposition ) ) + return "!(" + std::string(m_info.capturedExpression) + ")"; + else + return m_info.capturedExpression; + } - Counts assertions; - assertions.failed = 1; - SectionStats testCaseSectionStats( testCaseSection, assertions, 0, false ); - m_reporter->sectionEnded( testCaseSectionStats ); + std::string AssertionResult::getExpressionInMacro() const { + std::string expr; + if( m_info.macroName[0] == 0 ) + expr = m_info.capturedExpression; + else { + expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); + expr += m_info.macroName; + expr += "( "; + expr += m_info.capturedExpression; + expr += " )"; + } + return expr; + } - TestCaseInfo testInfo = m_activeTestCase->getTestCaseInfo(); + bool AssertionResult::hasExpandedExpression() const { + return hasExpression() && getExpandedExpression() != getExpression(); + } - Totals deltaTotals; - deltaTotals.testCases.failed = 1; - m_reporter->testCaseEnded( TestCaseStats( testInfo, - deltaTotals, - "", - "", - false ) ); - m_totals.testCases.failed++; - testGroupEnded( "", m_totals, 1, 1 ); - m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, false ) ); - } + std::string AssertionResult::getExpandedExpression() const { + std::string expr = m_resultData.reconstructExpression(); + return expr.empty() + ? getExpression() + : expr; + } - public: - // !TBD We need to do this another way! - bool aborting() const { - return m_totals.assertions.failed == static_cast<std::size_t>( m_config->abortAfter() ); - } + std::string AssertionResult::getMessage() const { + return m_resultData.message; + } + SourceLineInfo AssertionResult::getSourceInfo() const { + return m_info.lineInfo; + } - private: + std::string AssertionResult::getTestMacroName() const { + return m_info.macroName; + } - void runCurrentTest( std::string& redirectedCout, std::string& redirectedCerr ) { - TestCaseInfo const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection( testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description ); - m_reporter->sectionStarting( testCaseSection ); - Counts prevAssertions = m_totals.assertions; - double duration = 0; - try { - m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); - TestCaseTracker::Guard guard( *m_testCaseTracker ); - - Timer timer; - timer.start(); - if( m_reporter->getPreferences().shouldRedirectStdOut ) { - StreamRedirect coutRedir( Catch::cout(), redirectedCout ); - StreamRedirect cerrRedir( Catch::cerr(), redirectedCerr ); - invokeActiveTestCase(); - } - else { - invokeActiveTestCase(); - } - duration = timer.getElapsedSeconds(); - } - catch( TestFailureException& ) { - // This just means the test was aborted due to failure - } - catch(...) { - makeUnexpectedResultBuilder().useActiveException(); - } - handleUnfinishedSections(); - m_messages.clear(); +} // end namespace Catch +// end catch_assertionresult.cpp +// start catch_benchmark.cpp - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions( assertions ); +namespace Catch { - if( testCaseInfo.okToFail() ) { - std::swap( assertions.failedButOk, assertions.failed ); - m_totals.assertions.failed -= assertions.failedButOk; - m_totals.assertions.failedButOk += assertions.failedButOk; - } + auto BenchmarkLooper::getResolution() -> uint64_t { + return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); + } - SectionStats testCaseSectionStats( testCaseSection, assertions, duration, missingAssertions ); - m_reporter->sectionEnded( testCaseSectionStats ); - } + void BenchmarkLooper::reportStart() { + getResultCapture().benchmarkStarting( { m_name } ); + } + auto BenchmarkLooper::needsMoreIterations() -> bool { + auto elapsed = m_timer.getElapsedNanoseconds(); - void invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals - m_activeTestCase->invoke(); - fatalConditionHandler.reset(); + // Exponentially increasing iterations until we're confident in our timer resolution + if( elapsed < m_resolution ) { + m_iterationsToRun *= 10; + return true; } - private: + getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); + return false; + } - ResultBuilder makeUnexpectedResultBuilder() const { - return ResultBuilder( m_lastAssertionInfo.macroName.c_str(), - m_lastAssertionInfo.lineInfo, - m_lastAssertionInfo.capturedExpression.c_str(), - m_lastAssertionInfo.resultDisposition ); - } +} // end namespace Catch +// end catch_benchmark.cpp +// start catch_capture_matchers.cpp - void handleUnfinishedSections() { - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for( std::vector<UnfinishedSections>::const_reverse_iterator it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it ) - sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); - m_unfinishedSections.clear(); - } +namespace Catch { - struct UnfinishedSections { - UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) - : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} + using StringMatcher = Matchers::Impl::MatcherBase<std::string>; - SectionInfo info; - Counts prevAssertions; - double durationInSeconds; - }; + // This is the general overload that takes a any string matcher + // There is another overload, in catch_assertinhandler.h/.cpp, that only takes a string and infers + // the Equals matcher (so the header does not mention matchers) + void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { + std::string exceptionMessage = Catch::translateActiveException(); + MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString ); + handler.handle( expr ); + } - TestRunInfo m_runInfo; - IMutableContext& m_context; - TestCase const* m_activeTestCase; - Option<TestCaseTracker> m_testCaseTracker; - AssertionResult m_lastResult; +} // namespace Catch +// end catch_capture_matchers.cpp +// start catch_commandline.cpp - Ptr<IConfig const> m_config; - Totals m_totals; - Ptr<IStreamingReporter> m_reporter; - std::vector<MessageInfo> m_messages; - IRunner* m_prevRunner; - IResultCapture* m_prevResultCapture; - Ptr<IConfig const> m_prevConfig; - AssertionInfo m_lastAssertionInfo; - std::vector<UnfinishedSections> m_unfinishedSections; - }; +// start catch_commandline.h - IResultCapture& getResultCapture() { - if( IResultCapture* capture = getCurrentContext().getResultCapture() ) - return *capture; - else - throw std::logic_error( "No result capture instance" ); - } +// start catch_clara.h -} // end namespace Catch +// Use Catch's value for console width (store Clara's off to the side, if present) +#ifdef CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#endif +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 -// #included from: internal/catch_version.h -#define TWOBLUECUBES_CATCH_VERSION_H_INCLUDED +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wweak-vtables" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wshadow" +#endif -namespace Catch { +// start clara.hpp +// v1.0-develop.2 +// See https://github.com/philsquared/Clara - // Versioning information - struct Version { - Version( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - std::string const& _branchName, - unsigned int _buildNumber ); - unsigned int const majorVersion; - unsigned int const minorVersion; - unsigned int const patchNumber; +#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 +#endif - // buildNumber is only used if branchName is not null - std::string const branchName; - unsigned int const buildNumber; +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH +#endif - friend std::ostream& operator << ( std::ostream& os, Version const& version ); +// ----------- #included from clara_textflow.hpp ----------- - private: - void operator=( Version const& ); - }; +// TextFlowCpp +// +// A single-header library for wrapping and laying out basic text, by Phil Nash +// +// This work is licensed under the BSD 2-Clause license. +// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause +// +// This project is hosted at https://github.com/philsquared/textflowcpp - extern Version libraryVersion; -} -#include <fstream> -#include <stdlib.h> -#include <limits> +#include <cassert> +#include <ostream> +#include <sstream> +#include <vector> -namespace Catch { +#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 +#endif - class Runner { +namespace Catch { namespace clara { namespace TextFlow { - public: - Runner( Ptr<Config> const& config ) - : m_config( config ) - { - openStream(); - makeReporter(); - } + inline auto isWhitespace( char c ) -> bool { + static std::string chars = " \t\n\r"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableBefore( char c ) -> bool { + static std::string chars = "[({<|"; + return chars.find( c ) != std::string::npos; + } + inline auto isBreakableAfter( char c ) -> bool { + static std::string chars = "])}>.,:;*+-=&/\\"; + return chars.find( c ) != std::string::npos; + } - Totals runTests() { + class Columns; - RunContext context( m_config.get(), m_reporter ); + class Column { + std::vector<std::string> m_strings; + size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; + size_t m_indent = 0; + size_t m_initialIndent = std::string::npos; - Totals totals; + public: + class iterator { + friend Column; - context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + Column const& m_column; + size_t m_stringIndex = 0; + size_t m_pos = 0; - TestSpec testSpec = m_config->testSpec(); - if( !testSpec.hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + size_t m_len = 0; + size_t m_end = 0; + bool m_suffix = false; - std::vector<TestCase> testCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); + iterator( Column const& column, size_t stringIndex ) + : m_column( column ), + m_stringIndex( stringIndex ) + {} - int testsRunForGroup = 0; - for( std::vector<TestCase>::const_iterator it = testCases.begin(), itEnd = testCases.end(); - it != itEnd; - ++it ) { - testsRunForGroup++; - if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { + auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } - if( context.aborting() ) - break; + auto isBoundary( size_t at ) const -> bool { + assert( at > 0 ); + assert( at <= line().size() ); - totals += context.runTest( *it ); - m_testsAlreadyRun.insert( *it ); - } + return at == line().size() || + ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || + isBreakableBefore( line()[at] ) || + isBreakableAfter( line()[at-1] ); } - std::vector<TestCase> skippedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); - for( std::vector<TestCase>::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); - it != itEnd; - ++it ) - m_reporter->skipTest( *it ); + void calcLength() { + assert( m_stringIndex < m_column.m_strings.size() ); - context.testGroupEnded( "all tests", totals, 1, 1 ); - return totals; - } + m_suffix = false; + auto width = m_column.m_width-indent(); + m_end = m_pos; + while( m_end < line().size() && line()[m_end] != '\n' ) + ++m_end; - private: - void openStream() { - // Open output file, if specified - if( !m_config->getFilename().empty() ) { - m_ofs.open( m_config->getFilename().c_str() ); - if( m_ofs.fail() ) { - std::ostringstream oss; - oss << "Unable to open file: '" << m_config->getFilename() << "'"; - throw std::domain_error( oss.str() ); + if( m_end < m_pos + width ) { + m_len = m_end - m_pos; + } + else { + size_t len = width; + while (len > 0 && !isBoundary(m_pos + len)) + --len; + while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) + --len; + + if (len > 0) { + m_len = len; + } else { + m_suffix = true; + m_len = width - 1; + } } - m_config->setStreamBuf( m_ofs.rdbuf() ); } - } - void makeReporter() { - std::string reporterName = m_config->getReporterName().empty() - ? "console" - : m_config->getReporterName(); - m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); - if( !m_reporter ) { - std::ostringstream oss; - oss << "No reporter registered with name: '" << reporterName << "'"; - throw std::domain_error( oss.str() ); + auto indent() const -> size_t { + auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; + return initial == std::string::npos ? m_column.m_indent : initial; } - } - private: - Ptr<Config> m_config; - std::ofstream m_ofs; - Ptr<IStreamingReporter> m_reporter; - std::set<TestCase> m_testsAlreadyRun; - }; + auto addIndentAndSuffix(std::string const &plain) const -> std::string { + return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); + } - class Session : NonCopyable { - static bool alreadyInstantiated; + public: + explicit iterator( Column const& column ) : m_column( column ) { + assert( m_column.m_width > m_column.m_indent ); + assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); + calcLength(); + if( m_len == 0 ) + m_stringIndex++; // Empty string + } + + auto operator *() const -> std::string { + assert( m_stringIndex < m_column.m_strings.size() ); + assert( m_pos <= m_end ); + if( m_pos + m_column.m_width < m_end ) + return addIndentAndSuffix(line().substr(m_pos, m_len)); + else + return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); + } - public: + auto operator ++() -> iterator& { + m_pos += m_len; + if( m_pos < line().size() && line()[m_pos] == '\n' ) + m_pos += 1; + else + while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) + ++m_pos; - struct OnUnusedOptions { enum DoWhat { Ignore, Fail }; }; + if( m_pos == line().size() ) { + m_pos = 0; + ++m_stringIndex; + } + if( m_stringIndex < m_column.m_strings.size() ) + calcLength(); + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } - Session() - : m_cli( makeCommandLineParser() ) { - if( alreadyInstantiated ) { - std::string msg = "Only one instance of Catch::Session can ever be used"; - Catch::cerr() << msg << std::endl; - throw std::logic_error( msg ); + auto operator ==( iterator const& other ) const -> bool { + return + m_pos == other.m_pos && + m_stringIndex == other.m_stringIndex && + &m_column == &other.m_column; } - alreadyInstantiated = true; - } - ~Session() { - Catch::cleanUp(); - } + auto operator !=( iterator const& other ) const -> bool { + return !operator==( other ); + } + }; + using const_iterator = iterator; - void showHelp( std::string const& processName ) { - Catch::cout() << "\nCatch v" << libraryVersion << "\n"; + explicit Column( std::string const& text ) { m_strings.push_back( text ); } - m_cli.usage( Catch::cout(), processName ); - Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; + auto width( size_t newWidth ) -> Column& { + assert( newWidth > 0 ); + m_width = newWidth; + return *this; } - - int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { - try { - m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); - m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); - if( m_configData.showHelp ) - showHelp( m_configData.processName ); - m_config.reset(); - } - catch( std::exception& ex ) { - { - Colour colourGuard( Colour::Red ); - Catch::cerr() - << "\nError(s) in input:\n" - << Text( ex.what(), TextAttributes().setIndent(2) ) - << "\n\n"; - } - m_cli.usage( Catch::cout(), m_configData.processName ); - return (std::numeric_limits<int>::max)(); - } - return 0; + auto indent( size_t newIndent ) -> Column& { + m_indent = newIndent; + return *this; } - - void useConfigData( ConfigData const& _configData ) { - m_configData = _configData; - m_config.reset(); + auto initialIndent( size_t newIndent ) -> Column& { + m_initialIndent = newIndent; + return *this; } - int run( int argc, char* const argv[] ) { + auto width() const -> size_t { return m_width; } + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, m_strings.size() }; } - int returnCode = applyCommandLine( argc, argv ); - if( returnCode == 0 ) - returnCode = run(); - return returnCode; + inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { + bool first = true; + for( auto line : col ) { + if( first ) + first = false; + else + os << "\n"; + os << line; + } + return os; } - int run() { - if( m_configData.showHelp ) - return 0; - - try - { - config(); // Force config to be constructed - - std::srand( m_configData.rngSeed ); - - Runner runner( m_config ); + auto operator + ( Column const& other ) -> Columns; - // Handle list request - if( Option<std::size_t> listed = list( config() ) ) - return static_cast<int>( *listed ); - - return static_cast<int>( runner.runTests().assertions.failed ); - } - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << std::endl; - return (std::numeric_limits<int>::max)(); - } + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); } + }; - Clara::CommandLine<ConfigData> const& cli() const { - return m_cli; - } - std::vector<Clara::Parser::Token> const& unusedTokens() const { - return m_unusedTokens; - } - ConfigData& configData() { - return m_configData; - } - Config& config() { - if( !m_config ) - m_config = new Config( m_configData ); - return *m_config; - } + class Spacer : public Column { - private: - Clara::CommandLine<ConfigData> m_cli; - std::vector<Clara::Parser::Token> m_unusedTokens; - ConfigData m_configData; - Ptr<Config> m_config; + public: + explicit Spacer( size_t spaceWidth ) : Column( "" ) { + width( spaceWidth ); + } }; - bool Session::alreadyInstantiated = false; - -} // end namespace Catch + class Columns { + std::vector<Column> m_columns; -// #included from: catch_registry_hub.hpp -#define TWOBLUECUBES_CATCH_REGISTRY_HUB_HPP_INCLUDED + public: -// #included from: catch_test_case_registry_impl.hpp -#define TWOBLUECUBES_CATCH_TEST_CASE_REGISTRY_IMPL_HPP_INCLUDED + class iterator { + friend Columns; + struct EndTag {}; -#include <vector> -#include <set> -#include <sstream> -#include <iostream> -#include <algorithm> + std::vector<Column> const& m_columns; + std::vector<Column::iterator> m_iterators; + size_t m_activeIterators; -namespace Catch { + iterator( Columns const& columns, EndTag ) + : m_columns( columns.m_columns ), + m_activeIterators( 0 ) + { + m_iterators.reserve( m_columns.size() ); - class TestRegistry : public ITestCaseRegistry { - struct LexSort { - bool operator() (TestCase i,TestCase j) const { return (i<j);} - }; - struct RandomNumberGenerator { - int operator()( int n ) const { return std::rand() % n; } - }; + for( auto const& col : m_columns ) + m_iterators.push_back( col.end() ); + } - public: - TestRegistry() : m_unnamedCount( 0 ) {} - virtual ~TestRegistry(); + public: + explicit iterator( Columns const& columns ) + : m_columns( columns.m_columns ), + m_activeIterators( m_columns.size() ) + { + m_iterators.reserve( m_columns.size() ); - virtual void registerTest( TestCase const& testCase ) { - std::string name = testCase.getTestCaseInfo().name; - if( name == "" ) { - std::ostringstream oss; - oss << "Anonymous test case " << ++m_unnamedCount; - return registerTest( testCase.withName( oss.str() ) ); + for( auto const& col : m_columns ) + m_iterators.push_back( col.begin() ); } - if( m_functions.find( testCase ) == m_functions.end() ) { - m_functions.insert( testCase ); - m_functionsInOrder.push_back( testCase ); - if( !testCase.isHidden() ) - m_nonHiddenFunctions.push_back( testCase ); + auto operator ==( iterator const& other ) const -> bool { + return m_iterators == other.m_iterators; } - else { - TestCase const& prev = *m_functions.find( testCase ); - { - Colour colourGuard( Colour::Red ); - Catch::cerr() << "error: TEST_CASE( \"" << name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl; - } - exit(1); + auto operator !=( iterator const& other ) const -> bool { + return m_iterators != other.m_iterators; } - } + auto operator *() const -> std::string { + std::string row, padding; - virtual std::vector<TestCase> const& getAllTests() const { - return m_functionsInOrder; - } + for( size_t i = 0; i < m_columns.size(); ++i ) { + auto width = m_columns[i].width(); + if( m_iterators[i] != m_columns[i].end() ) { + std::string col = *m_iterators[i]; + row += padding + col; + if( col.size() < width ) + padding = std::string( width - col.size(), ' ' ); + else + padding = ""; + } + else { + padding += std::string( width, ' ' ); + } + } + return row; + } + auto operator ++() -> iterator& { + for( size_t i = 0; i < m_columns.size(); ++i ) { + if (m_iterators[i] != m_columns[i].end()) + ++m_iterators[i]; + } + return *this; + } + auto operator ++(int) -> iterator { + iterator prev( *this ); + operator++(); + return prev; + } + }; + using const_iterator = iterator; + + auto begin() const -> iterator { return iterator( *this ); } + auto end() const -> iterator { return { *this, iterator::EndTag() }; } - virtual std::vector<TestCase> const& getAllNonHiddenTests() const { - return m_nonHiddenFunctions; + auto operator += ( Column const& col ) -> Columns& { + m_columns.push_back( col ); + return *this; + } + auto operator + ( Column const& col ) -> Columns { + Columns combined = *this; + combined += col; + return combined; } - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector<TestCase>& matchingTestCases, bool negated = false ) const { + inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { - for( std::vector<TestCase>::const_iterator it = m_functionsInOrder.begin(), - itEnd = m_functionsInOrder.end(); - it != itEnd; - ++it ) { - bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); - if( includeTest != negated ) - matchingTestCases.push_back( *it ); + bool first = true; + for( auto line : cols ) { + if( first ) + first = false; + else + os << "\n"; + os << line; } - sortTests( config, matchingTestCases ); + return os; } - private: - - static void sortTests( IConfig const& config, std::vector<TestCase>& matchingTestCases ) { - - switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); - break; - case RunTests::InRandomOrder: - { - RandomNumberGenerator rng; - std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); - } - break; - case RunTests::InDeclarationOrder: - // already in declaration order - break; - } + auto toString() const -> std::string { + std::ostringstream oss; + oss << *this; + return oss.str(); } - std::set<TestCase> m_functions; - std::vector<TestCase> m_functionsInOrder; - std::vector<TestCase> m_nonHiddenFunctions; - size_t m_unnamedCount; }; - /////////////////////////////////////////////////////////////////////////// - - class FreeFunctionTestCase : public SharedImpl<ITestCase> { - public: - - FreeFunctionTestCase( TestFunction fun ) : m_fun( fun ) {} + inline auto Column::operator + ( Column const& other ) -> Columns { + Columns cols; + cols += *this; + cols += other; + return cols; + } +}}} // namespace Catch::clara::TextFlow - virtual void invoke() const { - m_fun(); - } +// ----------- end of #include from clara_textflow.hpp ----------- +// ........... back in clara.hpp - private: - virtual ~FreeFunctionTestCase(); +#include <memory> +#include <set> +#include <algorithm> - TestFunction m_fun; - }; +#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) +#define CATCH_PLATFORM_WINDOWS +#endif - inline std::string extractClassName( std::string const& classOrQualifiedMethodName ) { - std::string className = classOrQualifiedMethodName; - if( startsWith( className, "&" ) ) - { - std::size_t lastColons = className.rfind( "::" ); - std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); - if( penultimateColons == std::string::npos ) - penultimateColons = 1; - className = className.substr( penultimateColons, lastColons-penultimateColons ); - } - return className; - } +namespace Catch { namespace clara { +namespace detail { - /////////////////////////////////////////////////////////////////////////// + // Traits for extracting arg and return type of lambdas (for single argument lambdas) + template<typename L> + struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {}; - AutoReg::AutoReg( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ) { - registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); - } + template<typename ClassT, typename ReturnT, typename... Args> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> { + static const bool isValid = false; + }; - AutoReg::~AutoReg() {} + template<typename ClassT, typename ReturnT, typename ArgT> + struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> { + static const bool isValid = true; + using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;; + using ReturnType = ReturnT; + }; - void AutoReg::registerTestCase( ITestCase* testCase, - char const* classOrQualifiedMethodName, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { + class TokenStream; - getMutableRegistryHub().registerTest - ( makeTestCase( testCase, - extractClassName( classOrQualifiedMethodName ), - nameAndDesc.name, - nameAndDesc.description, - lineInfo ) ); - } + // Transport for raw args (copied from main args, or supplied via init list for testing) + class Args { + friend TokenStream; + std::string m_exeName; + std::vector<std::string> m_args; -} // end namespace Catch + public: + Args( int argc, char *argv[] ) { + m_exeName = argv[0]; + for( int i = 1; i < argc; ++i ) + m_args.push_back( argv[i] ); + } -// #included from: catch_reporter_registry.hpp -#define TWOBLUECUBES_CATCH_REPORTER_REGISTRY_HPP_INCLUDED + Args( std::initializer_list<std::string> args ) + : m_exeName( *args.begin() ), + m_args( args.begin()+1, args.end() ) + {} -#include <map> + auto exeName() const -> std::string { + return m_exeName; + } + }; -namespace Catch { + // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string + // may encode an option + its argument if the : or = form is used + enum class TokenType { + Option, Argument + }; + struct Token { + TokenType type; + std::string token; + }; - class ReporterRegistry : public IReporterRegistry { + inline auto isOptPrefix( char c ) -> bool { + return c == '-' +#ifdef CATCH_PLATFORM_WINDOWS + || c == '/' +#endif + ; + } + + // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled + class TokenStream { + using Iterator = std::vector<std::string>::const_iterator; + Iterator it; + Iterator itEnd; + std::vector<Token> m_tokenBuffer; + + void loadBuffer() { + m_tokenBuffer.resize( 0 ); + + // Skip any empty strings + while( it != itEnd && it->empty() ) + ++it; + + if( it != itEnd ) { + auto const &next = *it; + if( isOptPrefix( next[0] ) ) { + auto delimiterPos = next.find_first_of( " :=" ); + if( delimiterPos != std::string::npos ) { + m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); + m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); + } else { + if( next[1] != '-' && next.size() > 2 ) { + std::string opt = "- "; + for( size_t i = 1; i < next.size(); ++i ) { + opt[1] = next[i]; + m_tokenBuffer.push_back( { TokenType::Option, opt } ); + } + } else { + m_tokenBuffer.push_back( { TokenType::Option, next } ); + } + } + } else { + m_tokenBuffer.push_back( { TokenType::Argument, next } ); + } + } + } public: + explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} - virtual ~ReporterRegistry() { - deleteAllValues( m_factories ); + TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { + loadBuffer(); } - virtual IStreamingReporter* create( std::string const& name, Ptr<IConfig> const& config ) const { - FactoryMap::const_iterator it = m_factories.find( name ); - if( it == m_factories.end() ) - return NULL; - return it->second->create( ReporterConfig( config ) ); + explicit operator bool() const { + return !m_tokenBuffer.empty() || it != itEnd; } - void registerReporter( std::string const& name, IReporterFactory* factory ) { - m_factories.insert( std::make_pair( name, factory ) ); + auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } + + auto operator*() const -> Token { + assert( !m_tokenBuffer.empty() ); + return m_tokenBuffer.front(); } - FactoryMap const& getFactories() const { - return m_factories; + auto operator->() const -> Token const * { + assert( !m_tokenBuffer.empty() ); + return &m_tokenBuffer.front(); } - private: - FactoryMap m_factories; + auto operator++() -> TokenStream & { + if( m_tokenBuffer.size() >= 2 ) { + m_tokenBuffer.erase( m_tokenBuffer.begin() ); + } else { + if( it != itEnd ) + ++it; + loadBuffer(); + } + return *this; + } }; -} -// #included from: catch_exception_translator_registry.hpp -#define TWOBLUECUBES_CATCH_EXCEPTION_TRANSLATOR_REGISTRY_HPP_INCLUDED + class ResultBase { + public: + enum Type { + Ok, LogicError, RuntimeError + }; + + protected: + ResultBase( Type type ) : m_type( type ) {} + virtual ~ResultBase() = default; -#ifdef __OBJC__ -#import "Foundation/Foundation.h" -#endif + virtual void enforceOk() const = 0; -namespace Catch { + Type m_type; + }; - class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + template<typename T> + class ResultValueBase : public ResultBase { public: - ~ExceptionTranslatorRegistry() { - deleteAll( m_translators ); + auto value() const -> T const & { + enforceOk(); + return m_value; } - virtual void registerTranslator( const IExceptionTranslator* translator ) { - m_translators.push_back( translator ); + protected: + ResultValueBase( Type type ) : ResultBase( type ) {} + + ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); } - virtual std::string translateActiveException() const { - try { -#ifdef __OBJC__ - // In Objective-C try objective-c exceptions first - @try { - throw; - } - @catch (NSException *exception) { - return Catch::toString( [exception description] ); - } -#else - throw; -#endif - } - catch( TestFailureException& ) { - throw; - } - catch( std::exception& ex ) { - return ex.what(); - } - catch( std::string& msg ) { - return msg; - } - catch( const char* msg ) { - return msg; - } - catch(...) { - return tryTranslators( m_translators.begin() ); - } + ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { + new( &m_value ) T( value ); } - std::string tryTranslators( std::vector<const IExceptionTranslator*>::const_iterator it ) const { - if( it == m_translators.end() ) - return "Unknown exception"; + auto operator=( ResultValueBase const &other ) -> ResultValueBase & { + if( m_type == ResultBase::Ok ) + m_value.~T(); + ResultBase::operator=(other); + if( m_type == ResultBase::Ok ) + new( &m_value ) T( other.m_value ); + return *this; + } - try { - return (*it)->translate(); - } - catch(...) { - return tryTranslators( it+1 ); - } + ~ResultValueBase() { + if( m_type == Ok ) + m_value.~T(); } - private: - std::vector<const IExceptionTranslator*> m_translators; + union { + T m_value; + }; }; -} - -namespace Catch { - namespace { + template<> + class ResultValueBase<void> : public ResultBase { + protected: + using ResultBase::ResultBase; + }; - class RegistryHub : public IRegistryHub, public IMutableRegistryHub { + template<typename T = void> + class BasicResult : public ResultValueBase<T> { + public: + template<typename U> + explicit BasicResult( BasicResult<U> const &other ) + : ResultValueBase<T>( other.type() ), + m_errorMessage( other.errorMessage() ) + { + assert( type() != ResultBase::Ok ); + } - RegistryHub( RegistryHub const& ); - void operator=( RegistryHub const& ); + template<typename U> + static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } + static auto ok() -> BasicResult { return { ResultBase::Ok }; } + static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } + static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } - public: // IRegistryHub - RegistryHub() { - } - virtual IReporterRegistry const& getReporterRegistry() const { - return m_reporterRegistry; - } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { - return m_testCaseRegistry; - } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { - return m_exceptionTranslatorRegistry; - } + explicit operator bool() const { return m_type == ResultBase::Ok; } + auto type() const -> ResultBase::Type { return m_type; } + auto errorMessage() const -> std::string { return m_errorMessage; } - public: // IMutableRegistryHub - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { - m_reporterRegistry.registerReporter( name, factory ); - } - virtual void registerTest( TestCase const& testInfo ) { - m_testCaseRegistry.registerTest( testInfo ); - } - virtual void registerTranslator( const IExceptionTranslator* translator ) { - m_exceptionTranslatorRegistry.registerTranslator( translator ); + protected: + virtual void enforceOk() const { + // !TBD: If no exceptions, std::terminate here or something + switch( m_type ) { + case ResultBase::LogicError: + throw std::logic_error( m_errorMessage ); + case ResultBase::RuntimeError: + throw std::runtime_error( m_errorMessage ); + case ResultBase::Ok: + break; } + } - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - }; + std::string m_errorMessage; // Only populated if resultType is an error - // Single, global, instance - inline RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = NULL; - if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); - return theRegistryHub; + BasicResult( ResultBase::Type type, std::string const &message ) + : ResultValueBase<T>(type), + m_errorMessage(message) + { + assert( m_type != ResultBase::Ok ); } - } - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); - } - IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); - } - void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = NULL; - cleanUpContext(); - } - std::string translateActiveException() { - return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); - } + using ResultValueBase<T>::ResultValueBase; + using ResultBase::m_type; + }; -} // end namespace Catch + enum class ParseResultType { + Matched, NoMatch, ShortCircuitAll, ShortCircuitSame + }; -// #included from: catch_notimplemented_exception.hpp -#define TWOBLUECUBES_CATCH_NOTIMPLEMENTED_EXCEPTION_HPP_INCLUDED + class ParseState { + public: -#include <ostream> + ParseState( ParseResultType type, TokenStream const &remainingTokens ) + : m_type(type), + m_remainingTokens( remainingTokens ) + {} -namespace Catch { + auto type() const -> ParseResultType { return m_type; } + auto remainingTokens() const -> TokenStream { return m_remainingTokens; } - NotImplementedException::NotImplementedException( SourceLineInfo const& lineInfo ) - : m_lineInfo( lineInfo ) { - std::ostringstream oss; - oss << lineInfo << ": function "; - oss << "not implemented"; - m_what = oss.str(); - } + private: + ParseResultType m_type; + TokenStream m_remainingTokens; + }; + + using Result = BasicResult<void>; + using ParserResult = BasicResult<ParseResultType>; + using InternalParseResult = BasicResult<ParseState>; + + struct HelpColumns { + std::string left; + std::string right; + }; - const char* NotImplementedException::what() const CATCH_NOEXCEPT { - return m_what.c_str(); + template<typename T> + inline auto convertInto( std::string const &source, T& target ) -> ParserResult { + std::stringstream ss; + ss << source; + ss >> target; + if( ss.fail() ) + return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); + else + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { + target = source; + return ParserResult::ok( ParseResultType::Matched ); + } + inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { + std::string srcLC = source; + std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } ); + if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") + target = true; + else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") + target = false; + else + return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); + return ParserResult::ok( ParseResultType::Matched ); } -} // end namespace Catch + struct BoundRefBase { + BoundRefBase() = default; + BoundRefBase( BoundRefBase const & ) = delete; + BoundRefBase( BoundRefBase && ) = delete; + BoundRefBase &operator=( BoundRefBase const & ) = delete; + BoundRefBase &operator=( BoundRefBase && ) = delete; -// #included from: catch_context_impl.hpp -#define TWOBLUECUBES_CATCH_CONTEXT_IMPL_HPP_INCLUDED + virtual ~BoundRefBase() = default; -// #included from: catch_stream.hpp -#define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED + virtual auto isFlag() const -> bool = 0; + virtual auto isContainer() const -> bool { return false; } + virtual auto setValue( std::string const &arg ) -> ParserResult = 0; + virtual auto setFlag( bool flag ) -> ParserResult = 0; + }; -// #included from: catch_streambuf.h -#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED + struct BoundValueRefBase : BoundRefBase { + auto isFlag() const -> bool override { return false; } -#include <streambuf> + auto setFlag( bool ) -> ParserResult override { + return ParserResult::logicError( "Flags can only be set on boolean fields" ); + } + }; -namespace Catch { + struct BoundFlagRefBase : BoundRefBase { + auto isFlag() const -> bool override { return true; } - class StreamBufBase : public std::streambuf { - public: - virtual ~StreamBufBase() CATCH_NOEXCEPT; + auto setValue( std::string const &arg ) -> ParserResult override { + bool flag; + auto result = convertInto( arg, flag ); + if( result ) + setFlag( flag ); + return result; + } }; -} -#include <stdexcept> -#include <cstdio> -#include <iostream> + template<typename T> + struct BoundRef : BoundValueRefBase { + T &m_ref; -namespace Catch { + explicit BoundRef( T &ref ) : m_ref( ref ) {} - template<typename WriterF, size_t bufferSize=256> - class StreamBufImpl : public StreamBufBase { - char data[bufferSize]; - WriterF m_writer; + auto setValue( std::string const &arg ) -> ParserResult override { + return convertInto( arg, m_ref ); + } + }; - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); + template<typename T> + struct BoundRef<std::vector<T>> : BoundValueRefBase { + std::vector<T> &m_ref; + + explicit BoundRef( std::vector<T> &ref ) : m_ref( ref ) {} + + auto isContainer() const -> bool override { return true; } + + auto setValue( std::string const &arg ) -> ParserResult override { + T temp; + auto result = convertInto( arg, temp ); + if( result ) + m_ref.push_back( temp ); + return result; } + }; - ~StreamBufImpl() CATCH_NOEXCEPT { - sync(); + struct BoundFlagRef : BoundFlagRefBase { + bool &m_ref; + + explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} + + auto setFlag( bool flag ) -> ParserResult override { + m_ref = flag; + return ParserResult::ok( ParseResultType::Matched ); } + }; - private: - int overflow( int c ) { - sync(); + template<typename ReturnType> + struct LambdaInvoker { + static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" ); - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast<char>( c ) ) ); - else - sputc( static_cast<char>( c ) ); - } - return 0; + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + return lambda( arg ); } + }; - int sync() { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); - } - return 0; + template<> + struct LambdaInvoker<void> { + template<typename L, typename ArgType> + static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { + lambda( arg ); + return ParserResult::ok( ParseResultType::Matched ); } }; - /////////////////////////////////////////////////////////////////////////// + template<typename ArgType, typename L> + inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { + ArgType temp; + auto result = convertInto( arg, temp ); + return !result + ? result + : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp ); + }; - struct OutputDebugWriter { + template<typename L> + struct BoundLambda : BoundValueRefBase { + L m_lambda; - void operator()( std::string const&str ) { - writeToDebugConsole( str ); + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} + + auto setValue( std::string const &arg ) -> ParserResult override { + return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg ); } }; - Stream::Stream() - : streamBuf( NULL ), isOwned( false ) - {} + template<typename L> + struct BoundFlagLambda : BoundFlagRefBase { + L m_lambda; - Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) - : streamBuf( _streamBuf ), isOwned( _isOwned ) - {} + static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); + static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" ); - void Stream::release() { - if( isOwned ) { - delete streamBuf; - streamBuf = NULL; - isOwned = false; - } - } + explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions - std::ostream& cout() { - return std::cout; - } - std::ostream& cerr() { - return std::cerr; - } -#endif -} + auto setFlag( bool flag ) -> ParserResult override { + return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag ); + } + }; -namespace Catch { + enum class Optionality { Optional, Required }; - class Context : public IMutableContext { + struct Parser; - Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} - Context( Context const& ); - void operator=( Context const& ); + class ParserBase { + public: + virtual ~ParserBase() = default; + virtual auto validate() const -> Result { return Result::ok(); } + virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; + virtual auto cardinality() const -> size_t { return 1; } - public: // IContext - virtual IResultCapture* getResultCapture() { - return m_resultCapture; - } - virtual IRunner* getRunner() { - return m_runner; - } - virtual size_t getGeneratorIndex( std::string const& fileInfo, size_t totalSize ) { - return getGeneratorsForCurrentTest() - .getGeneratorInfo( fileInfo, totalSize ) - .getCurrentIndex(); - } - virtual bool advanceGeneratorsForCurrentTest() { - IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); - return generators && generators->moveNext(); + auto parse( Args const &args ) const -> InternalParseResult { + return parse( args.exeName(), TokenStream( args ) ); } + }; - virtual Ptr<IConfig const> getConfig() const { - return m_config; - } + template<typename DerivedT> + class ComposableParserImpl : public ParserBase { + public: + template<typename T> + auto operator|( T const &other ) const -> Parser; + }; - public: // IMutableContext - virtual void setResultCapture( IResultCapture* resultCapture ) { - m_resultCapture = resultCapture; - } - virtual void setRunner( IRunner* runner ) { - m_runner = runner; - } - virtual void setConfig( Ptr<IConfig const> const& config ) { - m_config = config; - } + // Common code and state for Args and Opts + template<typename DerivedT> + class ParserRefImpl : public ComposableParserImpl<DerivedT> { + protected: + Optionality m_optionality = Optionality::Optional; + std::shared_ptr<BoundRefBase> m_ref; + std::string m_hint; + std::string m_description; - friend IMutableContext& getCurrentMutableContext(); + explicit ParserRefImpl( std::shared_ptr<BoundRefBase> const &ref ) : m_ref( ref ) {} - private: - IGeneratorsForTest* findGeneratorsForCurrentTest() { - std::string testName = getResultCapture()->getCurrentTestName(); + public: + template<typename T> + ParserRefImpl( T &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundRef<T>>( ref ) ), + m_hint( hint ) + {} - std::map<std::string, IGeneratorsForTest*>::const_iterator it = - m_generatorsByTestName.find( testName ); - return it != m_generatorsByTestName.end() - ? it->second - : NULL; - } + template<typename LambdaT> + ParserRefImpl( LambdaT const &ref, std::string const &hint ) + : m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), + m_hint(hint) + {} - IGeneratorsForTest& getGeneratorsForCurrentTest() { - IGeneratorsForTest* generators = findGeneratorsForCurrentTest(); - if( !generators ) { - std::string testName = getResultCapture()->getCurrentTestName(); - generators = createGeneratorsForTest(); - m_generatorsByTestName.insert( std::make_pair( testName, generators ) ); - } - return *generators; + auto operator()( std::string const &description ) -> DerivedT & { + m_description = description; + return static_cast<DerivedT &>( *this ); } - private: - Ptr<IConfig const> m_config; - IRunner* m_runner; - IResultCapture* m_resultCapture; - std::map<std::string, IGeneratorsForTest*> m_generatorsByTestName; - }; - - namespace { - Context* currentContext = NULL; - } - IMutableContext& getCurrentMutableContext() { - if( !currentContext ) - currentContext = new Context(); - return *currentContext; - } - IContext& getCurrentContext() { - return getCurrentMutableContext(); - } - - Stream createStream( std::string const& streamName ) { - if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); - if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); - if( streamName == "debug" ) return Stream( new StreamBufImpl<OutputDebugWriter>, true ); - - throw std::domain_error( "Unknown stream: " + streamName ); - } + auto optional() -> DerivedT & { + m_optionality = Optionality::Optional; + return static_cast<DerivedT &>( *this ); + }; - void cleanUpContext() { - delete currentContext; - currentContext = NULL; - } -} + auto required() -> DerivedT & { + m_optionality = Optionality::Required; + return static_cast<DerivedT &>( *this ); + }; -// #included from: catch_console_colour_impl.hpp -#define TWOBLUECUBES_CATCH_CONSOLE_COLOUR_IMPL_HPP_INCLUDED + auto isOptional() const -> bool { + return m_optionality == Optionality::Optional; + } -namespace Catch { - namespace { + auto cardinality() const -> size_t override { + if( m_ref->isContainer() ) + return 0; + else + return 1; + } - struct IColourImpl { - virtual ~IColourImpl() {} - virtual void use( Colour::Code _colourCode ) = 0; - }; + auto hint() const -> std::string { return m_hint; } + }; - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) {} + class ExeName : public ComposableParserImpl<ExeName> { + std::shared_ptr<std::string> m_name; + std::shared_ptr<BoundRefBase> m_ref; - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; - } - }; + template<typename LambdaT> + static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundRefBase> { + return std::make_shared<BoundLambda<LambdaT>>( lambda) ; + } - } // anon namespace -} // namespace Catch + public: + ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {} -#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) -# ifdef CATCH_PLATFORM_WINDOWS -# define CATCH_CONFIG_COLOUR_WINDOWS -# else -# define CATCH_CONFIG_COLOUR_ANSI -# endif -#endif + explicit ExeName( std::string &ref ) : ExeName() { + m_ref = std::make_shared<BoundRef<std::string>>( ref ); + } -#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// + template<typename LambdaT> + explicit ExeName( LambdaT const& lambda ) : ExeName() { + m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda ); + } -#ifndef NOMINMAX -#define NOMINMAX -#endif + // The exe name is not parsed out of the normal tokens, but is handled specially + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + } -#ifdef __AFXDLL -#include <AfxWin.h> -#else -#include <windows.h> -#endif + auto name() const -> std::string { return *m_name; } + auto set( std::string const& newName ) -> ParserResult { -namespace Catch { -namespace { + auto lastSlash = newName.find_last_of( "\\/" ); + auto filename = ( lastSlash == std::string::npos ) + ? newName + : newName.substr( lastSlash+1 ); - class Win32ColourImpl : public IColourImpl { - public: - Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) - { - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalAttributes = csbiInfo.wAttributes; + *m_name = filename; + if( m_ref ) + return m_ref->setValue( filename ); + else + return ParserResult::ok( ParseResultType::Matched ); } + }; - virtual void use( Colour::Code _colourCode ) { - switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalAttributes ); - case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); - case Colour::Red: return setTextAttribute( FOREGROUND_RED ); - case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); - case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); - case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); - case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); - case Colour::Grey: return setTextAttribute( 0 ); + class Arg : public ParserRefImpl<Arg> { + public: + using ParserRefImpl::ParserRefImpl; - case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); - case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); - case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); - case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); - case Colour::Bright: throw std::logic_error( "not a colour" ); - } - } + auto remainingTokens = tokens; + auto const &token = *remainingTokens; + if( token.type != TokenType::Argument ) + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - private: - void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + auto result = m_ref->setValue( remainingTokens->token ); + if( !result ) + return InternalParseResult( result ); + else + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); } - HANDLE stdoutHandle; - WORD originalAttributes; }; - IColourImpl* platformColourInstance() { - static Win32ColourImpl s_instance; - return &s_instance; + inline auto normaliseOpt( std::string const &optName ) -> std::string { +#ifdef CATCH_PLATFORM_WINDOWS + if( optName[0] == '/' ) + return "-" + optName.substr( 1 ); + else +#endif + return optName; } -} // end anon namespace -} // end namespace Catch + class Opt : public ParserRefImpl<Opt> { + protected: + std::vector<std::string> m_optNames; -#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// + public: + template<typename LambdaT> + explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {} -#include <unistd.h> + explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {} -namespace Catch { -namespace { + template<typename LambdaT> + Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - // use POSIX/ ANSI console terminal codes - // Thanks to Adam Strzelecki for original contribution - // (http://github.com/nanoant) - // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public IColourImpl { - public: - virtual void use( Colour::Code _colourCode ) { - switch( _colourCode ) { - case Colour::None: - case Colour::White: return setColour( "[0m" ); - case Colour::Red: return setColour( "[0;31m" ); - case Colour::Green: return setColour( "[0;32m" ); - case Colour::Blue: return setColour( "[0:34m" ); - case Colour::Cyan: return setColour( "[0;36m" ); - case Colour::Yellow: return setColour( "[0;33m" ); - case Colour::Grey: return setColour( "[1;30m" ); + template<typename T> + Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - case Colour::LightGrey: return setColour( "[0;37m" ); - case Colour::BrightRed: return setColour( "[1;31m" ); - case Colour::BrightGreen: return setColour( "[1;32m" ); - case Colour::BrightWhite: return setColour( "[1;37m" ); + auto operator[]( std::string const &optName ) -> Opt & { + m_optNames.push_back( optName ); + return *this; + } - case Colour::Bright: throw std::logic_error( "not a colour" ); + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::ostringstream oss; + bool first = true; + for( auto const &opt : m_optNames ) { + if (first) + first = false; + else + oss << ", "; + oss << opt; } - } - static IColourImpl* instance() { - static PosixColourImpl s_instance; - return &s_instance; + if( !m_hint.empty() ) + oss << " <" << m_hint << ">"; + return { { oss.str(), m_description } }; } - private: - void setColour( const char* _escapeCode ) { - Catch::cout() << '\033' << _escapeCode; + auto isMatch( std::string const &optToken ) const -> bool { + auto normalisedToken = normaliseOpt( optToken ); + for( auto const &name : m_optNames ) { + if( normaliseOpt( name ) == normalisedToken ) + return true; + } + return false; } - }; - - IColourImpl* platformColourInstance() { - Ptr<IConfig const> config = getCurrentContext().getConfig(); - return (config && config->forceColour()) || isatty(STDOUT_FILENO) - ? PosixColourImpl::instance() - : NoColourImpl::instance(); - } - -} // end anon namespace -} // end namespace Catch - -#else // not Windows or ANSI /////////////////////////////////////////////// -namespace Catch { + using ParserBase::parse; + + auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { + auto validationResult = validate(); + if( !validationResult ) + return InternalParseResult( validationResult ); + + auto remainingTokens = tokens; + if( remainingTokens && remainingTokens->type == TokenType::Option ) { + auto const &token = *remainingTokens; + if( isMatch(token.token ) ) { + if( m_ref->isFlag() ) { + auto result = m_ref->setFlag( true ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } else { + ++remainingTokens; + if( !remainingTokens ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto const &argToken = *remainingTokens; + if( argToken.type != TokenType::Argument ) + return InternalParseResult::runtimeError( "Expected argument following " + token.token ); + auto result = m_ref->setValue( argToken.token ); + if( !result ) + return InternalParseResult( result ); + if( result.value() == ParseResultType::ShortCircuitAll ) + return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); + } + return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); + } + } + return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); + } - static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } + auto validate() const -> Result override { + if( m_optNames.empty() ) + return Result::logicError( "No options supplied to Opt" ); + for( auto const &name : m_optNames ) { + if( name.empty() ) + return Result::logicError( "Option name cannot be empty" ); +#ifdef CATCH_PLATFORM_WINDOWS + if( name[0] != '-' && name[0] != '/' ) + return Result::logicError( "Option name must begin with '-' or '/'" ); +#else + if( name[0] != '-' ) + return Result::logicError( "Option name must begin with '-'" ); +#endif + } + return ParserRefImpl::validate(); + } + }; -} // end namespace Catch + struct Help : Opt { + Help( bool &showHelpFlag ) + : Opt([&]( bool flag ) { + showHelpFlag = flag; + return ParserResult::ok( ParseResultType::ShortCircuitAll ); + }) + { + static_cast<Opt &>( *this ) + ("display usage information") + ["-?"]["-h"]["--help"] + .optional(); + } + }; -#endif // Windows/ ANSI/ None + struct Parser : ParserBase { -namespace Catch { + mutable ExeName m_exeName; + std::vector<Opt> m_options; + std::vector<Arg> m_args; - Colour::Colour( Code _colourCode ) : m_moved( false ) { use( _colourCode ); } - Colour::Colour( Colour const& _other ) : m_moved( false ) { const_cast<Colour&>( _other ).m_moved = true; } - Colour::~Colour(){ if( !m_moved ) use( None ); } + auto operator|=( ExeName const &exeName ) -> Parser & { + m_exeName = exeName; + return *this; + } - void Colour::use( Code _colourCode ) { - static IColourImpl* impl = isDebuggerActive() - ? NoColourImpl::instance() - : platformColourInstance(); - impl->use( _colourCode ); - } + auto operator|=( Arg const &arg ) -> Parser & { + m_args.push_back(arg); + return *this; + } -} // end namespace Catch + auto operator|=( Opt const &opt ) -> Parser & { + m_options.push_back(opt); + return *this; + } -// #included from: catch_generators_impl.hpp -#define TWOBLUECUBES_CATCH_GENERATORS_IMPL_HPP_INCLUDED + auto operator|=( Parser const &other ) -> Parser & { + m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); + m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); + return *this; + } -#include <vector> -#include <string> -#include <map> + template<typename T> + auto operator|( T const &other ) const -> Parser { + return Parser( *this ) |= other; + } -namespace Catch { + auto getHelpColumns() const -> std::vector<HelpColumns> { + std::vector<HelpColumns> cols; + for (auto const &o : m_options) { + auto childCols = o.getHelpColumns(); + cols.insert( cols.end(), childCols.begin(), childCols.end() ); + } + return cols; + } - struct GeneratorInfo : IGeneratorInfo { + void writeToStream( std::ostream &os ) const { + if (!m_exeName.name().empty()) { + os << "usage:\n" << " " << m_exeName.name() << " "; + bool required = true, first = true; + for( auto const &arg : m_args ) { + if (first) + first = false; + else + os << " "; + if( arg.isOptional() && required ) { + os << "["; + required = false; + } + os << "<" << arg.hint() << ">"; + if( arg.cardinality() == 0 ) + os << " ... "; + } + if( !required ) + os << "]"; + if( !m_options.empty() ) + os << " options"; + os << "\n\nwhere options are:" << std::endl; + } - GeneratorInfo( std::size_t size ) - : m_size( size ), - m_currentIndex( 0 ) - {} + auto rows = getHelpColumns(); + size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; + size_t optWidth = 0; + for( auto const &cols : rows ) + optWidth = (std::max)(optWidth, cols.left.size() + 2); - bool moveNext() { - if( ++m_currentIndex == m_size ) { - m_currentIndex = 0; - return false; + for( auto const &cols : rows ) { + auto row = + TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + + TextFlow::Spacer(4) + + TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); + os << row << std::endl; } - return true; } - std::size_t getCurrentIndex() const { - return m_currentIndex; + friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { + parser.writeToStream( os ); + return os; } - std::size_t m_size; - std::size_t m_currentIndex; - }; + auto validate() const -> Result override { + for( auto const &opt : m_options ) { + auto result = opt.validate(); + if( !result ) + return result; + } + for( auto const &arg : m_args ) { + auto result = arg.validate(); + if( !result ) + return result; + } + return Result::ok(); + } - /////////////////////////////////////////////////////////////////////////// + using ParserBase::parse; - class GeneratorsForTest : public IGeneratorsForTest { + auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { - public: - ~GeneratorsForTest() { - deleteAll( m_generatorsInOrder ); - } + struct ParserInfo { + ParserBase const* parser = nullptr; + size_t count = 0; + }; + const size_t totalParsers = m_options.size() + m_args.size(); + assert( totalParsers < 512 ); + // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do + ParserInfo parseInfos[512]; - IGeneratorInfo& getGeneratorInfo( std::string const& fileInfo, std::size_t size ) { - std::map<std::string, IGeneratorInfo*>::const_iterator it = m_generatorsByName.find( fileInfo ); - if( it == m_generatorsByName.end() ) { - IGeneratorInfo* info = new GeneratorInfo( size ); - m_generatorsByName.insert( std::make_pair( fileInfo, info ) ); - m_generatorsInOrder.push_back( info ); - return *info; - } - return *it->second; - } + { + size_t i = 0; + for (auto const &opt : m_options) parseInfos[i++].parser = &opt; + for (auto const &arg : m_args) parseInfos[i++].parser = &arg; + } + + m_exeName.set( exeName ); + + auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); + while( result.value().remainingTokens() ) { + bool tokenParsed = false; + + for( size_t i = 0; i < totalParsers; ++i ) { + auto& parseInfo = parseInfos[i]; + if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { + result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); + if (!result) + return result; + if (result.value().type() != ParseResultType::NoMatch) { + tokenParsed = true; + ++parseInfo.count; + break; + } + } + } - bool moveNext() { - std::vector<IGeneratorInfo*>::const_iterator it = m_generatorsInOrder.begin(); - std::vector<IGeneratorInfo*>::const_iterator itEnd = m_generatorsInOrder.end(); - for(; it != itEnd; ++it ) { - if( (*it)->moveNext() ) - return true; + if( result.value().type() == ParseResultType::ShortCircuitAll ) + return result; + if( !tokenParsed ) + return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); } - return false; + // !TBD Check missing required options + return result; } - - private: - std::map<std::string, IGeneratorInfo*> m_generatorsByName; - std::vector<IGeneratorInfo*> m_generatorsInOrder; }; - IGeneratorsForTest* createGeneratorsForTest() - { - return new GeneratorsForTest(); + template<typename DerivedT> + template<typename T> + auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser { + return Parser() | static_cast<DerivedT const &>( *this ) | other; } +} // namespace detail -} // end namespace Catch +// A Combined parser +using detail::Parser; -// #included from: catch_assertionresult.hpp -#define TWOBLUECUBES_CATCH_ASSERTIONRESULT_HPP_INCLUDED +// A parser for options +using detail::Opt; -namespace Catch { +// A parser for arguments +using detail::Arg; - AssertionInfo::AssertionInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - std::string const& _capturedExpression, - ResultDisposition::Flags _resultDisposition ) - : macroName( _macroName ), - lineInfo( _lineInfo ), - capturedExpression( _capturedExpression ), - resultDisposition( _resultDisposition ) - {} +// Wrapper for argc, argv from main() +using detail::Args; - AssertionResult::AssertionResult() {} +// Specifies the name of the executable +using detail::ExeName; - AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) - : m_info( info ), - m_resultData( data ) - {} +// Convenience wrapper for option parser that specifies the help option +using detail::Help; - AssertionResult::~AssertionResult() {} +// enum of result types from a parse +using detail::ParseResultType; - // Result was a success - bool AssertionResult::succeeded() const { - return Catch::isOk( m_resultData.resultType ); - } +// Result type for parser operation +using detail::ParserResult; - // Result was a success, or failure is suppressed - bool AssertionResult::isOk() const { - return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); - } - - ResultWas::OfType AssertionResult::getResultType() const { - return m_resultData.resultType; - } - - bool AssertionResult::hasExpression() const { - return !m_info.capturedExpression.empty(); - } - - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); - } - - std::string AssertionResult::getExpression() const { - if( isFalseTest( m_info.resultDisposition ) ) - return "!" + m_info.capturedExpression; - else - return m_info.capturedExpression; - } - std::string AssertionResult::getExpressionInMacro() const { - if( m_info.macroName.empty() ) - return m_info.capturedExpression; - else - return m_info.macroName + "( " + m_info.capturedExpression + " )"; - } +}} // namespace Catch::clara - bool AssertionResult::hasExpandedExpression() const { - return hasExpression() && getExpandedExpression() != getExpression(); - } +// end clara.hpp +#ifdef __clang__ +#pragma clang diagnostic pop +#endif - std::string AssertionResult::getExpandedExpression() const { - return m_resultData.reconstructedExpression; - } +// Restore Clara's value for console width, if present +#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH +#endif - std::string AssertionResult::getMessage() const { - return m_resultData.message; - } - SourceLineInfo AssertionResult::getSourceInfo() const { - return m_info.lineInfo; - } +// end catch_clara.h +namespace Catch { - std::string AssertionResult::getTestMacroName() const { - return m_info.macroName; - } + clara::Parser makeCommandLineParser( ConfigData& config ); } // end namespace Catch -// #included from: catch_test_case_info.hpp -#define TWOBLUECUBES_CATCH_TEST_CASE_INFO_HPP_INCLUDED +// end catch_commandline.h +#include <fstream> +#include <ctime> namespace Catch { - inline TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { - if( startsWith( tag, "." ) || - tag == "hide" || - tag == "!hide" ) - return TestCaseInfo::IsHidden; - else if( tag == "!throws" ) - return TestCaseInfo::Throws; - else if( tag == "!shouldfail" ) - return TestCaseInfo::ShouldFail; - else if( tag == "!mayfail" ) - return TestCaseInfo::MayFail; - else - return TestCaseInfo::None; - } - inline bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !isalnum( tag[0] ); - } - inline void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { - if( isReservedTag( tag ) ) { - { - Colour colourGuard( Colour::Red ); - Catch::cerr() - << "Tag name [" << tag << "] not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n"; - } - { - Colour colourGuard( Colour::FileName ); - Catch::cerr() << _lineInfo << std::endl; - } - exit(1); - } - } - - TestCase makeTestCase( ITestCase* _testCase, - std::string const& _className, - std::string const& _name, - std::string const& _descOrTags, - SourceLineInfo const& _lineInfo ) - { - bool isHidden( startsWith( _name, "./" ) ); // Legacy support + clara::Parser makeCommandLineParser( ConfigData& config ) { - // Parse out tags - std::set<std::string> tags; - std::string desc, tag; - bool inTag = false; - for( std::size_t i = 0; i < _descOrTags.size(); ++i ) { - char c = _descOrTags[i]; - if( !inTag ) { - if( c == '[' ) - inTag = true; - else - desc += c; - } - else { - if( c == ']' ) { - TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); - if( prop == TestCaseInfo::IsHidden ) - isHidden = true; - else if( prop == TestCaseInfo::None ) - enforceNotReservedTag( tag, _lineInfo ); + using namespace clara; - tags.insert( tag ); - tag.clear(); - inTag = false; + auto const setWarning = [&]( std::string const& warning ) { + if( warning != "NoAssertions" ) + return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); + config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const loadTestNamesFromFile = [&]( std::string const& filename ) { + std::ifstream f( filename.c_str() ); + if( !f.is_open() ) + return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); + + std::string line; + while( std::getline( f, line ) ) { + line = trim(line); + if( !line.empty() && !startsWith( line, '#' ) ) { + if( !startsWith( line, '"' ) ) + line = '"' + line + '"'; + config.testsOrTags.push_back( line + ',' ); + } } + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setTestOrder = [&]( std::string const& order ) { + if( startsWith( "declared", order ) ) + config.runOrder = RunTests::InDeclarationOrder; + else if( startsWith( "lexical", order ) ) + config.runOrder = RunTests::InLexicographicalOrder; + else if( startsWith( "random", order ) ) + config.runOrder = RunTests::InRandomOrder; else - tag += c; - } - } - if( isHidden ) { - tags.insert( "hide" ); - tags.insert( "." ); - } - - TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); - return TestCase( _testCase, info ); - } + return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setRngSeed = [&]( std::string const& seed ) { + if( seed != "time" ) + return clara::detail::convertInto( seed, config.rngSeed ); + config.rngSeed = static_cast<unsigned int>( std::time(nullptr) ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setColourUsage = [&]( std::string const& useColour ) { + auto mode = toLower( useColour ); + + if( mode == "yes" ) + config.useColour = UseColour::Yes; + else if( mode == "no" ) + config.useColour = UseColour::No; + else if( mode == "auto" ) + config.useColour = UseColour::Auto; + else + return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setWaitForKeypress = [&]( std::string const& keypress ) { + auto keypressLc = toLower( keypress ); + if( keypressLc == "start" ) + config.waitForKeypress = WaitForKeypress::BeforeStart; + else if( keypressLc == "exit" ) + config.waitForKeypress = WaitForKeypress::BeforeExit; + else if( keypressLc == "both" ) + config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; + else + return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); + return ParserResult::ok( ParseResultType::Matched ); + }; + auto const setVerbosity = [&]( std::string const& verbosity ) { + auto lcVerbosity = toLower( verbosity ); + if( lcVerbosity == "quiet" ) + config.verbosity = Verbosity::Quiet; + else if( lcVerbosity == "normal" ) + config.verbosity = Verbosity::Normal; + else if( lcVerbosity == "high" ) + config.verbosity = Verbosity::High; + else + return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); + return ParserResult::ok( ParseResultType::Matched ); + }; - TestCaseInfo::TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::set<std::string> const& _tags, - SourceLineInfo const& _lineInfo ) - : name( _name ), - className( _className ), - description( _description ), - tags( _tags ), - lineInfo( _lineInfo ), - properties( None ) - { - std::ostringstream oss; - for( std::set<std::string>::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { - oss << "[" << *it << "]"; - std::string lcaseTag = toLower( *it ); - properties = static_cast<SpecialProperties>( properties | parseSpecialTag( lcaseTag ) ); - lcaseTags.insert( lcaseTag ); - } - tagsAsString = oss.str(); - } - - TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) - : name( other.name ), - className( other.className ), - description( other.description ), - tags( other.tags ), - lcaseTags( other.lcaseTags ), - tagsAsString( other.tagsAsString ), - lineInfo( other.lineInfo ), - properties( other.properties ) - {} + auto cli + = ExeName( config.processName ) + | Help( config.showHelp ) + | Opt( config.listTests ) + ["-l"]["--list-tests"] + ( "list all/matching test cases" ) + | Opt( config.listTags ) + ["-t"]["--list-tags"] + ( "list all/matching tags" ) + | Opt( config.showSuccessfulTests ) + ["-s"]["--success"] + ( "include successful tests in output" ) + | Opt( config.shouldDebugBreak ) + ["-b"]["--break"] + ( "break into debugger on failure" ) + | Opt( config.noThrow ) + ["-e"]["--nothrow"] + ( "skip exception tests" ) + | Opt( config.showInvisibles ) + ["-i"]["--invisibles"] + ( "show invisibles (tabs, newlines)" ) + | Opt( config.outputFilename, "filename" ) + ["-o"]["--out"] + ( "output filename" ) + | Opt( config.reporterNames, "name" ) + ["-r"]["--reporter"] + ( "reporter to use (defaults to console)" ) + | Opt( config.name, "name" ) + ["-n"]["--name"] + ( "suite name" ) + | Opt( [&]( bool ){ config.abortAfter = 1; } ) + ["-a"]["--abort"] + ( "abort at first failure" ) + | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) + ["-x"]["--abortx"] + ( "abort after x failures" ) + | Opt( setWarning, "warning name" ) + ["-w"]["--warn"] + ( "enable warnings" ) + | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) + ["-d"]["--durations"] + ( "show test durations" ) + | Opt( loadTestNamesFromFile, "filename" ) + ["-f"]["--input-file"] + ( "load test names to run from a file" ) + | Opt( config.filenamesAsTags ) + ["-#"]["--filenames-as-tags"] + ( "adds a tag for the filename" ) + | Opt( config.sectionsToRun, "section name" ) + ["-c"]["--section"] + ( "specify section to run" ) + | Opt( setVerbosity, "quiet|normal|high" ) + ["-v"]["--verbosity"] + ( "set output verbosity" ) + | Opt( config.listTestNamesOnly ) + ["--list-test-names-only"] + ( "list all/matching test cases names only" ) + | Opt( config.listReporters ) + ["--list-reporters"] + ( "list all reporters" ) + | Opt( setTestOrder, "decl|lex|rand" ) + ["--order"] + ( "test case order (defaults to decl)" ) + | Opt( setRngSeed, "'time'|number" ) + ["--rng-seed"] + ( "set a specific seed for random numbers" ) + | Opt( setColourUsage, "yes|no" ) + ["--use-colour"] + ( "should output be colourised" ) + | Opt( config.libIdentify ) + ["--libidentify"] + ( "report name and version according to libidentify standard" ) + | Opt( setWaitForKeypress, "start|exit|both" ) + ["--wait-for-keypress"] + ( "waits for a keypress before exiting" ) + | Opt( config.benchmarkResolutionMultiple, "multiplier" ) + ["--benchmark-resolution-multiple"] + ( "multiple of clock resolution to run benchmarks" ) + + | Arg( config.testsOrTags, "test name|pattern|tags" ) + ( "which test or tests to use" ); - bool TestCaseInfo::isHidden() const { - return ( properties & IsHidden ) != 0; - } - bool TestCaseInfo::throws() const { - return ( properties & Throws ) != 0; - } - bool TestCaseInfo::okToFail() const { - return ( properties & (ShouldFail | MayFail ) ) != 0; - } - bool TestCaseInfo::expectedToFail() const { - return ( properties & (ShouldFail ) ) != 0; + return cli; } - TestCase::TestCase( ITestCase* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} - - TestCase::TestCase( TestCase const& other ) - : TestCaseInfo( other ), - test( other.test ) - {} - - TestCase TestCase::withName( std::string const& _newName ) const { - TestCase other( *this ); - other.name = _newName; - return other; - } +} // end namespace Catch +// end catch_commandline.cpp +// start catch_common.cpp - void TestCase::swap( TestCase& other ) { - test.swap( other.test ); - name.swap( other.name ); - className.swap( other.className ); - description.swap( other.description ); - tags.swap( other.tags ); - lcaseTags.swap( other.lcaseTags ); - tagsAsString.swap( other.tagsAsString ); - std::swap( TestCaseInfo::properties, static_cast<TestCaseInfo&>( other ).properties ); - std::swap( lineInfo, other.lineInfo ); - } +#include <cstring> +#include <ostream> - void TestCase::invoke() const { - test->invoke(); - } +namespace Catch { - bool TestCase::operator == ( TestCase const& other ) const { - return test.get() == other.test.get() && - name == other.name && - className == other.className; + SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + bool SourceLineInfo::empty() const noexcept { + return file[0] == '\0'; } - - bool TestCase::operator < ( TestCase const& other ) const { - return name < other.name; + bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { + return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); } - TestCase& TestCase::operator = ( TestCase const& other ) { - TestCase temp( other ); - swap( temp ); - return *this; + bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { + return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); } - TestCaseInfo const& TestCase::getTestCaseInfo() const - { - return *this; + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { +#ifndef __GNUG__ + os << info.file << '(' << info.line << ')'; +#else + os << info.file << ':' << info.line; +#endif + return os; } -} // end namespace Catch - -// #included from: catch_version.hpp -#define TWOBLUECUBES_CATCH_VERSION_HPP_INCLUDED - -namespace Catch { - - Version::Version - ( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - std::string const& _branchName, - unsigned int _buildNumber ) - : majorVersion( _majorVersion ), - minorVersion( _minorVersion ), - patchNumber( _patchNumber ), - branchName( _branchName ), - buildNumber( _buildNumber ) - {} - - std::ostream& operator << ( std::ostream& os, Version const& version ) { - os << version.majorVersion << "." - << version.minorVersion << "." - << version.patchNumber; + bool isTrue( bool value ){ return value; } + bool alwaysTrue() { return true; } + bool alwaysFalse() { return false; } - if( !version.branchName.empty() ) { - os << "-" << version.branchName - << "." << version.buildNumber; - } - return os; + std::string StreamEndStop::operator+() const { + return std::string(); } - Version libraryVersion( 1, 2, 1, "", 0 ); + NonCopyable::NonCopyable() = default; + NonCopyable::~NonCopyable() = default; } - -// #included from: catch_message.hpp -#define TWOBLUECUBES_CATCH_MESSAGE_HPP_INCLUDED +// end catch_common.cpp +// start catch_config.cpp namespace Catch { - MessageInfo::MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) - : macroName( _macroName ), - lineInfo( _lineInfo ), - type( _type ), - sequence( ++globalCount ) - {} - - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - - //////////////////////////////////////////////////////////////////////////// - - ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) + Config::Config( ConfigData const& data ) + : m_data( data ), + m_stream( openStream() ) { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); - } - ScopedMessage::ScopedMessage( ScopedMessage const& other ) - : m_info( other.m_info ) - {} - - ScopedMessage::~ScopedMessage() { - getResultCapture().popScopedMessage( m_info ); + if( !data.testsOrTags.empty() ) { + TestSpecParser parser( ITagAliasRegistry::get() ); + for( auto const& testOrTags : data.testsOrTags ) + parser.parse( testOrTags ); + m_testSpec = parser.testSpec(); + } + } + + std::string const& Config::getFilename() const { + return m_data.outputFilename ; + } + + bool Config::listTests() const { return m_data.listTests; } + bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } + bool Config::listTags() const { return m_data.listTags; } + bool Config::listReporters() const { return m_data.listReporters; } + + std::string Config::getProcessName() const { return m_data.processName; } + + std::vector<std::string> const& Config::getReporterNames() const { return m_data.reporterNames; } + std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } + + TestSpec const& Config::testSpec() const { return m_testSpec; } + + bool Config::showHelp() const { return m_data.showHelp; } + + // IConfig interface + bool Config::allowThrows() const { return !m_data.noThrow; } + std::ostream& Config::stream() const { return m_stream->stream(); } + std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } + bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } + bool Config::warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } + ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } + RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } + unsigned int Config::rngSeed() const { return m_data.rngSeed; } + int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } + UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } + bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } + int Config::abortAfter() const { return m_data.abortAfter; } + bool Config::showInvisibles() const { return m_data.showInvisibles; } + Verbosity Config::verbosity() const { return m_data.verbosity; } + + IStream const* Config::openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + CATCH_ERROR( "Unrecognised stream: '" << m_data.outputFilename << "'" ); + } + else + return new FileStream( m_data.outputFilename ); } } // end namespace Catch +// end catch_config.cpp +// start catch_console_colour.cpp -// #included from: catch_legacy_reporter_adapter.hpp -#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_HPP_INCLUDED +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif -// #included from: catch_legacy_reporter_adapter.h -#define TWOBLUECUBES_CATCH_LEGACY_REPORTER_ADAPTER_H_INCLUDED +// start catch_errno_guard.h -namespace Catch -{ - // Deprecated - struct IReporter : IShared { - virtual ~IReporter(); - - virtual bool shouldRedirectStdout() const = 0; - - virtual void StartTesting() = 0; - virtual void EndTesting( Totals const& totals ) = 0; - virtual void StartGroup( std::string const& groupName ) = 0; - virtual void EndGroup( std::string const& groupName, Totals const& totals ) = 0; - virtual void StartTestCase( TestCaseInfo const& testInfo ) = 0; - virtual void EndTestCase( TestCaseInfo const& testInfo, Totals const& totals, std::string const& stdOut, std::string const& stdErr ) = 0; - virtual void StartSection( std::string const& sectionName, std::string const& description ) = 0; - virtual void EndSection( std::string const& sectionName, Counts const& assertions ) = 0; - virtual void NoAssertionsInSection( std::string const& sectionName ) = 0; - virtual void NoAssertionsInTestCase( std::string const& testName ) = 0; - virtual void Aborted() = 0; - virtual void Result( AssertionResult const& result ) = 0; - }; - - class LegacyReporterAdapter : public SharedImpl<IStreamingReporter> - { - public: - LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter ); - virtual ~LegacyReporterAdapter(); - - virtual ReporterPreferences getPreferences() const; - virtual void noMatchingTestCases( std::string const& ); - virtual void testRunStarting( TestRunInfo const& ); - virtual void testGroupStarting( GroupInfo const& groupInfo ); - virtual void testCaseStarting( TestCaseInfo const& testInfo ); - virtual void sectionStarting( SectionInfo const& sectionInfo ); - virtual void assertionStarting( AssertionInfo const& ); - virtual bool assertionEnded( AssertionStats const& assertionStats ); - virtual void sectionEnded( SectionStats const& sectionStats ); - virtual void testCaseEnded( TestCaseStats const& testCaseStats ); - virtual void testGroupEnded( TestGroupStats const& testGroupStats ); - virtual void testRunEnded( TestRunStats const& testRunStats ); - virtual void skipTest( TestCaseInfo const& ); +namespace Catch { + class ErrnoGuard { + public: + ErrnoGuard(); + ~ErrnoGuard(); private: - Ptr<IReporter> m_legacyReporter; + int m_oldErrno; }; -} - -namespace Catch -{ - LegacyReporterAdapter::LegacyReporterAdapter( Ptr<IReporter> const& legacyReporter ) - : m_legacyReporter( legacyReporter ) - {} - LegacyReporterAdapter::~LegacyReporterAdapter() {} - ReporterPreferences LegacyReporterAdapter::getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = m_legacyReporter->shouldRedirectStdout(); - return prefs; - } +} - void LegacyReporterAdapter::noMatchingTestCases( std::string const& ) {} - void LegacyReporterAdapter::testRunStarting( TestRunInfo const& ) { - m_legacyReporter->StartTesting(); - } - void LegacyReporterAdapter::testGroupStarting( GroupInfo const& groupInfo ) { - m_legacyReporter->StartGroup( groupInfo.name ); - } - void LegacyReporterAdapter::testCaseStarting( TestCaseInfo const& testInfo ) { - m_legacyReporter->StartTestCase( testInfo ); - } - void LegacyReporterAdapter::sectionStarting( SectionInfo const& sectionInfo ) { - m_legacyReporter->StartSection( sectionInfo.name, sectionInfo.description ); - } - void LegacyReporterAdapter::assertionStarting( AssertionInfo const& ) { - // Not on legacy interface - } +// end catch_errno_guard.h +// start catch_windows_h_proxy.h - bool LegacyReporterAdapter::assertionEnded( AssertionStats const& assertionStats ) { - if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { - for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); - it != itEnd; - ++it ) { - if( it->type == ResultWas::Info ) { - ResultBuilder rb( it->macroName.c_str(), it->lineInfo, "", ResultDisposition::Normal ); - rb << it->message; - rb.setResultType( ResultWas::Info ); - AssertionResult result = rb.build(); - m_legacyReporter->Result( result ); - } - } - } - m_legacyReporter->Result( assertionStats.assertionResult ); - return true; - } - void LegacyReporterAdapter::sectionEnded( SectionStats const& sectionStats ) { - if( sectionStats.missingAssertions ) - m_legacyReporter->NoAssertionsInSection( sectionStats.sectionInfo.name ); - m_legacyReporter->EndSection( sectionStats.sectionInfo.name, sectionStats.assertions ); - } - void LegacyReporterAdapter::testCaseEnded( TestCaseStats const& testCaseStats ) { - m_legacyReporter->EndTestCase - ( testCaseStats.testInfo, - testCaseStats.totals, - testCaseStats.stdOut, - testCaseStats.stdErr ); - } - void LegacyReporterAdapter::testGroupEnded( TestGroupStats const& testGroupStats ) { - if( testGroupStats.aborting ) - m_legacyReporter->Aborted(); - m_legacyReporter->EndGroup( testGroupStats.groupInfo.name, testGroupStats.totals ); - } - void LegacyReporterAdapter::testRunEnded( TestRunStats const& testRunStats ) { - m_legacyReporter->EndTesting( testRunStats.totals ); - } - void LegacyReporterAdapter::skipTest( TestCaseInfo const& ) { - } -} -// #included from: catch_timer.hpp +#if defined(CATCH_PLATFORM_WINDOWS) -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wc++11-long-long" +#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) +# define CATCH_DEFINED_NOMINMAX +# define NOMINMAX +#endif +#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) +# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN #endif -#ifdef CATCH_PLATFORM_WINDOWS -#include <windows.h> +#ifdef __AFXDLL +#include <AfxWin.h> #else -#include <sys/time.h> +#include <windows.h> #endif -namespace Catch { +#ifdef CATCH_DEFINED_NOMINMAX +# undef NOMINMAX +#endif +#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN +# undef WIN32_LEAN_AND_MEAN +#endif - namespace { -#ifdef CATCH_PLATFORM_WINDOWS - uint64_t getCurrentTicks() { - static uint64_t hz=0, hzo=0; - if (!hz) { - QueryPerformanceFrequency( reinterpret_cast<LARGE_INTEGER*>( &hz ) ); - QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &hzo ) ); - } - uint64_t t; - QueryPerformanceCounter( reinterpret_cast<LARGE_INTEGER*>( &t ) ); - return ((t-hzo)*1000000)/hz; - } -#else - uint64_t getCurrentTicks() { - timeval t; - gettimeofday(&t,NULL); - return static_cast<uint64_t>( t.tv_sec ) * 1000000ull + static_cast<uint64_t>( t.tv_usec ); - } -#endif - } +#endif // defined(CATCH_PLATFORM_WINDOWS) - void Timer::start() { - m_ticks = getCurrentTicks(); - } - unsigned int Timer::getElapsedMicroseconds() const { - return static_cast<unsigned int>(getCurrentTicks() - m_ticks); - } - unsigned int Timer::getElapsedMilliseconds() const { - return static_cast<unsigned int>(getElapsedMicroseconds()/1000); - } - double Timer::getElapsedSeconds() const { - return getElapsedMicroseconds()/1000000.0; - } +// end catch_windows_h_proxy.h +namespace Catch { + namespace { + struct IColourImpl { + virtual ~IColourImpl() = default; + virtual void use( Colour::Code _colourCode ) = 0; + }; + + struct NoColourImpl : IColourImpl { + void use( Colour::Code ) {} + + static IColourImpl* instance() { + static NoColourImpl s_instance; + return &s_instance; + } + }; + + } // anon namespace } // namespace Catch -#ifdef __clang__ -#pragma clang diagnostic pop +#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) +# ifdef CATCH_PLATFORM_WINDOWS +# define CATCH_CONFIG_COLOUR_WINDOWS +# else +# define CATCH_CONFIG_COLOUR_ANSI +# endif #endif -// #included from: catch_common.hpp -#define TWOBLUECUBES_CATCH_COMMON_HPP_INCLUDED + +#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// namespace Catch { +namespace { - bool startsWith( std::string const& s, std::string const& prefix ) { - return s.size() >= prefix.size() && s.substr( 0, prefix.size() ) == prefix; - } - bool endsWith( std::string const& s, std::string const& suffix ) { - return s.size() >= suffix.size() && s.substr( s.size()-suffix.size(), suffix.size() ) == suffix; - } - bool contains( std::string const& s, std::string const& infix ) { - return s.find( infix ) != std::string::npos; - } - void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), ::tolower ); - } - std::string toLower( std::string const& s ) { - std::string lc = s; - toLowerInPlace( lc ); - return lc; - } - std::string trim( std::string const& str ) { - static char const* whitespaceChars = "\n\r\t "; - std::string::size_type start = str.find_first_not_of( whitespaceChars ); - std::string::size_type end = str.find_last_not_of( whitespaceChars ); + class Win32ColourImpl : public IColourImpl { + public: + Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) + { + CONSOLE_SCREEN_BUFFER_INFO csbiInfo; + GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); + } - return start != std::string::npos ? str.substr( start, 1+end-start ) : ""; - } + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: return setTextAttribute( originalForegroundAttributes ); + case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + case Colour::Red: return setTextAttribute( FOREGROUND_RED ); + case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); + case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); + case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); + case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); + case Colour::Grey: return setTextAttribute( 0 ); - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { - bool replaced = false; - std::size_t i = str.find( replaceThis ); - while( i != std::string::npos ) { - replaced = true; - str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); - if( i < str.size()-withThis.size() ) - i = str.find( replaceThis, i+withThis.size() ); - else - i = std::string::npos; + case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); + case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); + case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); + case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); + + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + } } - return replaced; - } - pluralise::pluralise( std::size_t count, std::string const& label ) - : m_count( count ), - m_label( label ) - {} + private: + void setTextAttribute( WORD _textAttribute ) { + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); + } + HANDLE stdoutHandle; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; + }; - std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { - os << pluraliser.m_count << " " << pluraliser.m_label; - if( pluraliser.m_count != 1 ) - os << "s"; - return os; - } + IColourImpl* platformColourInstance() { + static Win32ColourImpl s_instance; - SourceLineInfo::SourceLineInfo() : line( 0 ){} - SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) - : file( _file ), - line( _line ) - {} - SourceLineInfo::SourceLineInfo( SourceLineInfo const& other ) - : file( other.file ), - line( other.line ) - {} - bool SourceLineInfo::empty() const { - return file.empty(); - } - bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const { - return line == other.line && file == other.file; - } - bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const { - return line < other.line || ( line == other.line && file < other.file ); + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = UseColour::Yes; + return colourMode == UseColour::Yes + ? &s_instance + : NoColourImpl::instance(); } - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { -#ifndef __GNUG__ - os << info.file << "(" << info.line << ")"; -#else - os << info.file << ":" << info.line; -#endif - return os; - } +} // end anon namespace +} // end namespace Catch - void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ) { - std::ostringstream oss; - oss << locationInfo << ": Internal Catch error: '" << message << "'"; - if( alwaysTrue() ) - throw std::logic_error( oss.str() ); - } -} +#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// -// #included from: catch_section.hpp -#define TWOBLUECUBES_CATCH_SECTION_HPP_INCLUDED +#include <unistd.h> namespace Catch { +namespace { - SectionInfo::SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description ) - : name( _name ), - description( _description ), - lineInfo( _lineInfo ) - {} + // use POSIX/ ANSI console terminal codes + // Thanks to Adam Strzelecki for original contribution + // (http://github.com/nanoant) + // https://github.com/philsquared/Catch/pull/131 + class PosixColourImpl : public IColourImpl { + public: + virtual void use( Colour::Code _colourCode ) override { + switch( _colourCode ) { + case Colour::None: + case Colour::White: return setColour( "[0m" ); + case Colour::Red: return setColour( "[0;31m" ); + case Colour::Green: return setColour( "[0;32m" ); + case Colour::Blue: return setColour( "[0;34m" ); + case Colour::Cyan: return setColour( "[0;36m" ); + case Colour::Yellow: return setColour( "[0;33m" ); + case Colour::Grey: return setColour( "[1;30m" ); - Section::Section( SectionInfo const& info ) - : m_info( info ), - m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) - { - m_timer.start(); - } + case Colour::LightGrey: return setColour( "[0;37m" ); + case Colour::BrightRed: return setColour( "[1;31m" ); + case Colour::BrightGreen: return setColour( "[1;32m" ); + case Colour::BrightWhite: return setColour( "[1;37m" ); - Section::~Section() { - if( m_sectionIncluded ) - getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); - } + case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); + } + } + static IColourImpl* instance() { + static PosixColourImpl s_instance; + return &s_instance; + } - // This indicates whether the section should be executed or not - Section::operator bool() const { - return m_sectionIncluded; + private: + void setColour( const char* _escapeCode ) { + Catch::cout() << '\033' << _escapeCode; + } + }; + + bool useColourOnPlatform() { + return +#ifdef CATCH_PLATFORM_MAC + !isDebuggerActive() && +#endif + isatty(STDOUT_FILENO); + } + IColourImpl* platformColourInstance() { + ErrnoGuard guard; + IConfigPtr config = getCurrentContext().getConfig(); + UseColour::YesOrNo colourMode = config + ? config->useColour() + : UseColour::Auto; + if( colourMode == UseColour::Auto ) + colourMode = useColourOnPlatform() + ? UseColour::Yes + : UseColour::No; + return colourMode == UseColour::Yes + ? PosixColourImpl::instance() + : NoColourImpl::instance(); } +} // end anon namespace } // end namespace Catch -// #included from: catch_debugger.hpp -#define TWOBLUECUBES_CATCH_DEBUGGER_HPP_INCLUDED +#else // not Windows or ANSI /////////////////////////////////////////////// -#include <iostream> +namespace Catch { -#ifdef CATCH_PLATFORM_MAC + static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } - #include <assert.h> - #include <stdbool.h> - #include <sys/types.h> - #include <unistd.h> - #include <sys/sysctl.h> +} // end namespace Catch - namespace Catch{ +#endif // Windows/ ANSI/ None - // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html +namespace Catch { - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive(){ + Colour::Colour( Code _colourCode ) { use( _colourCode ); } + Colour::Colour( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + } + Colour& Colour::operator=( Colour&& rhs ) noexcept { + m_moved = rhs.m_moved; + rhs.m_moved = true; + return *this; + } - int mib[4]; - struct kinfo_proc info; - size_t size; + Colour::~Colour(){ if( !m_moved ) use( None ); } - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. + void Colour::use( Code _colourCode ) { + static IColourImpl* impl = platformColourInstance(); + impl->use( _colourCode ); + } - info.kp_proc.p_flag = 0; + std::ostream& operator << ( std::ostream& os, Colour const& ) { + return os; + } - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. +} // end namespace Catch - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); +#if defined(__clang__) +# pragma clang diagnostic pop +#endif - // Call sysctl. +// end catch_console_colour.cpp +// start catch_context.cpp - size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { - Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; - return false; - } +namespace Catch { - // We're being debugged if the P_TRACED flag is set. + class Context : public IMutableContext, NonCopyable { - return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + public: // IContext + virtual IResultCapture* getResultCapture() override { + return m_resultCapture; + } + virtual IRunner* getRunner() override { + return m_runner; } - } // namespace Catch -#elif defined(_MSC_VER) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; + virtual IConfigPtr getConfig() const override { + return m_config; } - } -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; + + virtual ~Context() override; + + public: // IMutableContext + virtual void setResultCapture( IResultCapture* resultCapture ) override { + m_resultCapture = resultCapture; } + virtual void setRunner( IRunner* runner ) override { + m_runner = runner; + } + virtual void setConfig( IConfigPtr const& config ) override { + m_config = config; + } + + friend IMutableContext& getCurrentMutableContext(); + + private: + IConfigPtr m_config; + IRunner* m_runner = nullptr; + IResultCapture* m_resultCapture = nullptr; + }; + + namespace { + Context* currentContext = nullptr; } -#else - namespace Catch { - inline bool isDebuggerActive() { return false; } - } -#endif // Platform + IMutableContext& getCurrentMutableContext() { + if( !currentContext ) + currentContext = new Context(); + return *currentContext; + } + IContext& getCurrentContext() { + return getCurrentMutableContext(); + } + + void cleanUpContext() { + delete currentContext; + currentContext = nullptr; + } + IContext::~IContext() = default; + IMutableContext::~IMutableContext() = default; + Context::~Context() = default; +} +// end catch_context.cpp +// start catch_debug_console.cpp + +// start catch_debug_console.h + +#include <string> + +namespace Catch { + void writeToDebugConsole( std::string const& text ); +} +// end catch_debug_console.h #ifdef CATCH_PLATFORM_WINDOWS - extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA( const char* ); + namespace Catch { void writeToDebugConsole( std::string const& text ) { ::OutputDebugStringA( text.c_str() ); @@ -7197,1305 +6203,4525 @@ namespace Catch { } } #endif // Platform +// end catch_debug_console.cpp +// start catch_debugger.cpp -// #included from: catch_tostring.hpp -#define TWOBLUECUBES_CATCH_TOSTRING_HPP_INCLUDED +#ifdef CATCH_PLATFORM_MAC -namespace Catch { + #include <assert.h> + #include <stdbool.h> + #include <sys/types.h> + #include <unistd.h> + #include <sys/sysctl.h> -namespace Detail { + namespace Catch { - std::string unprintableString = "{?}"; + // The following function is taken directly from the following technical note: + // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html - namespace { - struct Endianness { - enum Arch { Big, Little }; + // Returns true if the current process is being debugged (either + // running under the debugger or has a debugger attached post facto). + bool isDebuggerActive(){ - static Arch which() { - union _{ - int asInt; - char asChar[sizeof (int)]; - } u; + int mib[4]; + struct kinfo_proc info; + std::size_t size; - u.asInt = 1; - return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; - } - }; - } + // Initialize the flags so that, if sysctl fails for some bizarre + // reason, we get a predictable result. - std::string rawMemoryToString( const void *object, std::size_t size ) - { - // Reverse order for little endian architectures - int i = 0, end = static_cast<int>( size ), inc = 1; - if( Endianness::which() == Endianness::Little ) { - i = end-1; - end = inc = -1; - } + info.kp_proc.p_flag = 0; - unsigned char const *bytes = static_cast<unsigned char const *>(object); - std::ostringstream os; - os << "0x" << std::setfill('0') << std::hex; - for( ; i != end; i += inc ) - os << std::setw(2) << static_cast<unsigned>(bytes[i]); - return os.str(); - } -} + // Initialize mib, which tells sysctl the info we want, in this case + // we're looking for information about a specific process ID. -std::string toString( std::string const& value ) { - std::string s = value; - if( getCurrentContext().getConfig()->showInvisibles() ) { - for(size_t i = 0; i < s.size(); ++i ) { - std::string subs; - switch( s[i] ) { - case '\n': subs = "\\n"; break; - case '\t': subs = "\\t"; break; - default: break; - } - if( !subs.empty() ) { - s = s.substr( 0, i ) + subs + s.substr( i+1 ); - ++i; - } - } - } - return "\"" + s + "\""; -} -std::string toString( std::wstring const& value ) { + mib[0] = CTL_KERN; + mib[1] = KERN_PROC; + mib[2] = KERN_PROC_PID; + mib[3] = getpid(); - std::string s; - s.reserve( value.size() ); - for(size_t i = 0; i < value.size(); ++i ) - s += value[i] <= 0xff ? static_cast<char>( value[i] ) : '?'; - return Catch::toString( s ); -} + // Call sysctl. -std::string toString( const char* const value ) { - return value ? Catch::toString( std::string( value ) ) : std::string( "{null string}" ); -} + size = sizeof(info); + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { + Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; + return false; + } -std::string toString( char* const value ) { - return Catch::toString( static_cast<const char*>( value ) ); -} + // We're being debugged if the P_TRACED flag is set. -std::string toString( const wchar_t* const value ) -{ - return value ? Catch::toString( std::wstring(value) ) : std::string( "{null string}" ); -} + return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); + } + } // namespace Catch -std::string toString( wchar_t* const value ) -{ - return Catch::toString( static_cast<const wchar_t*>( value ) ); -} +#elif defined(CATCH_PLATFORM_LINUX) + #include <fstream> + #include <string> -std::string toString( int value ) { - std::ostringstream oss; - oss << value; - if( value >= 255 ) - oss << " (0x" << std::hex << value << ")"; - return oss.str(); -} + namespace Catch{ + // The standard POSIX way of detecting a debugger is to attempt to + // ptrace() the process, but this needs to be done from a child and not + // this process itself to still allow attaching to this process later + // if wanted, so is rather heavy. Under Linux we have the PID of the + // "debugger" (which doesn't need to be gdb, of course, it could also + // be strace, for example) in /proc/$PID/status, so just get it from + // there instead. + bool isDebuggerActive(){ + // Libstdc++ has a bug, where std::ifstream sets errno to 0 + // This way our users can properly assert over errno values + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for( std::string line; std::getline(in, line); ) { + static const int PREFIX_LEN = 11; + if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { + // We're traced if the PID is not 0 and no other PID starts + // with 0 digit, so it's enough to check for just a single + // character. + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } -std::string toString( unsigned long value ) { - std::ostringstream oss; - oss << value; - if( value >= 255 ) - oss << " (0x" << std::hex << value << ")"; - return oss.str(); -} + return false; + } + } // namespace Catch +#elif defined(_MSC_VER) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#elif defined(__MINGW32__) + extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); + namespace Catch { + bool isDebuggerActive() { + return IsDebuggerPresent() != 0; + } + } +#else + namespace Catch { + bool isDebuggerActive() { return false; } + } +#endif // Platform +// end catch_debugger.cpp +// start catch_decomposer.cpp -std::string toString( unsigned int value ) { - return Catch::toString( static_cast<unsigned long>( value ) ); -} +namespace Catch { -template<typename T> -std::string fpToString( T value, int precision ) { - std::ostringstream oss; - oss << std::setprecision( precision ) - << std::fixed - << value; - std::string d = oss.str(); - std::size_t i = d.find_last_not_of( '0' ); - if( i != std::string::npos && i != d.size()-1 ) { - if( d[i] == '.' ) - i++; - d = d.substr( 0, i+1 ); + ITransientExpression::~ITransientExpression() = default; + + void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { + if( lhs.size() + rhs.size() < 40 && + lhs.find('\n') == std::string::npos && + rhs.find('\n') == std::string::npos ) + os << lhs << " " << op << " " << rhs; + else + os << lhs << "\n" << op << "\n" << rhs; } - return d; } +// end catch_decomposer.cpp +// start catch_errno_guard.cpp -std::string toString( const double value ) { - return fpToString( value, 10 ); -} -std::string toString( const float value ) { - return fpToString( value, 5 ) + "f"; -} +#include <cerrno> -std::string toString( bool value ) { - return value ? "true" : "false"; +namespace Catch { + ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} + ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } } +// end catch_errno_guard.cpp +// start catch_exception_translator_registry.cpp -std::string toString( char value ) { - return value < ' ' - ? toString( static_cast<unsigned int>( value ) ) - : Detail::makeString( value ); -} +// start catch_exception_translator_registry.h -std::string toString( signed char value ) { - return toString( static_cast<char>( value ) ); -} +#include <vector> +#include <string> +#include <memory> -std::string toString( unsigned char value ) { - return toString( static_cast<char>( value ) ); -} +namespace Catch { -#ifdef CATCH_CONFIG_CPP11_NULLPTR -std::string toString( std::nullptr_t ) { - return "nullptr"; + class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { + public: + ~ExceptionTranslatorRegistry(); + virtual void registerTranslator( const IExceptionTranslator* translator ); + virtual std::string translateActiveException() const override; + std::string tryTranslators() const; + + private: + std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators; + }; } -#endif +// end catch_exception_translator_registry.h #ifdef __OBJC__ - std::string toString( NSString const * const& nsstring ) { - if( !nsstring ) - return "nil"; - return "@" + toString([nsstring UTF8String]); - } - std::string toString( NSString * CATCH_ARC_STRONG const& nsstring ) { - if( !nsstring ) - return "nil"; - return "@" + toString([nsstring UTF8String]); - } - std::string toString( NSObject* const& nsObject ) { - return toString( [nsObject description] ); - } +#import "Foundation/Foundation.h" #endif -} // end namespace Catch - -// #included from: catch_result_builder.hpp -#define TWOBLUECUBES_CATCH_RESULT_BUILDER_HPP_INCLUDED - namespace Catch { - ResultBuilder::ResultBuilder( char const* macroName, - SourceLineInfo const& lineInfo, - char const* capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), - m_shouldDebugBreak( false ), - m_shouldThrow( false ) - {} - - ResultBuilder& ResultBuilder::setResultType( ResultWas::OfType result ) { - m_data.resultType = result; - return *this; - } - ResultBuilder& ResultBuilder::setResultType( bool result ) { - m_data.resultType = result ? ResultWas::Ok : ResultWas::ExpressionFailed; - return *this; - } - ResultBuilder& ResultBuilder::setLhs( std::string const& lhs ) { - m_exprComponents.lhs = lhs; - return *this; - } - ResultBuilder& ResultBuilder::setRhs( std::string const& rhs ) { - m_exprComponents.rhs = rhs; - return *this; - } - ResultBuilder& ResultBuilder::setOp( std::string const& op ) { - m_exprComponents.op = op; - return *this; + ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { } - void ResultBuilder::endExpression() { - m_exprComponents.testFalse = isFalseTest( m_assertionInfo.resultDisposition ); - captureExpression(); + void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { + m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) ); } - void ResultBuilder::useActiveException( ResultDisposition::Flags resultDisposition ) { - m_assertionInfo.resultDisposition = resultDisposition; - m_stream.oss << Catch::translateActiveException(); - captureResult( ResultWas::ThrewException ); + std::string ExceptionTranslatorRegistry::translateActiveException() const { + try { +#ifdef __OBJC__ + // In Objective-C try objective-c exceptions first + @try { + return tryTranslators(); + } + @catch (NSException *exception) { + return Catch::Detail::stringify( [exception description] ); + } +#else + return tryTranslators(); +#endif + } + catch( TestFailureException& ) { + std::rethrow_exception(std::current_exception()); + } + catch( std::exception& ex ) { + return ex.what(); + } + catch( std::string& msg ) { + return msg; + } + catch( const char* msg ) { + return msg; + } + catch(...) { + return "Unknown exception"; + } } - void ResultBuilder::captureResult( ResultWas::OfType resultType ) { - setResultType( resultType ); - captureExpression(); + std::string ExceptionTranslatorRegistry::tryTranslators() const { + if( m_translators.empty() ) + std::rethrow_exception(std::current_exception()); + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } +} +// end catch_exception_translator_registry.cpp +// start catch_fatal_condition.cpp - void ResultBuilder::captureExpression() { - AssertionResult result = build(); - getResultCapture().assertionEnded( result ); +// start catch_fatal_condition.h - if( !result.isOk() ) { - if( getCurrentContext().getConfig()->shouldDebugBreak() ) - m_shouldDebugBreak = true; - if( getCurrentContext().getRunner()->aborting() || (m_assertionInfo.resultDisposition & ResultDisposition::Normal) ) - m_shouldThrow = true; - } - } - void ResultBuilder::react() { - if( m_shouldThrow ) - throw Catch::TestFailureException(); - } +#include <string> - bool ResultBuilder::shouldDebugBreak() const { return m_shouldDebugBreak; } - bool ResultBuilder::allowThrows() const { return getCurrentContext().getConfig()->allowThrows(); } +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// - AssertionResult ResultBuilder::build() const - { - assert( m_data.resultType != ResultWas::Unknown ); +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) - AssertionResultData data = m_data; +namespace Catch { + struct FatalConditionHandler { + void reset(); + }; +} - // Flip bool results if testFalse is set - if( m_exprComponents.testFalse ) { - if( data.resultType == ResultWas::Ok ) - data.resultType = ResultWas::ExpressionFailed; - else if( data.resultType == ResultWas::ExpressionFailed ) - data.resultType = ResultWas::Ok; - } +# else // CATCH_CONFIG_WINDOWS_SEH is defined - data.message = m_stream.oss.str(); - data.reconstructedExpression = reconstructExpression(); - if( m_exprComponents.testFalse ) { - if( m_exprComponents.op == "" ) - data.reconstructedExpression = "!" + data.reconstructedExpression; - else - data.reconstructedExpression = "!(" + data.reconstructedExpression + ")"; - } - return AssertionResult( m_assertionInfo, data ); - } - std::string ResultBuilder::reconstructExpression() const { - if( m_exprComponents.op == "" ) - return m_exprComponents.lhs.empty() ? m_assertionInfo.capturedExpression : m_exprComponents.op + m_exprComponents.lhs; - else if( m_exprComponents.op == "matches" ) - return m_exprComponents.lhs + " " + m_exprComponents.rhs; - else if( m_exprComponents.op != "!" ) { - if( m_exprComponents.lhs.size() + m_exprComponents.rhs.size() < 40 && - m_exprComponents.lhs.find("\n") == std::string::npos && - m_exprComponents.rhs.find("\n") == std::string::npos ) - return m_exprComponents.lhs + " " + m_exprComponents.op + " " + m_exprComponents.rhs; - else - return m_exprComponents.lhs + "\n" + m_exprComponents.op + "\n" + m_exprComponents.rhs; - } - else - return "{can't expand - use " + m_assertionInfo.macroName + "_FALSE( " + m_assertionInfo.capturedExpression.substr(1) + " ) instead of " + m_assertionInfo.macroName + "( " + m_assertionInfo.capturedExpression + " ) for better diagnostics}"; - } +namespace Catch { -} // end namespace Catch + struct FatalConditionHandler { -// #included from: catch_tag_alias_registry.hpp -#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_HPP_INCLUDED + static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); + FatalConditionHandler(); + static void reset(); + ~FatalConditionHandler(); -// #included from: catch_tag_alias_registry.h -#define TWOBLUECUBES_CATCH_TAG_ALIAS_REGISTRY_H_INCLUDED + private: + static bool isSet; + static ULONG guaranteeSize; + static PVOID exceptionHandlerHandle; + }; -#include <map> +} // namespace Catch -namespace Catch { +# endif // CATCH_CONFIG_WINDOWS_SEH - class TagAliasRegistry : public ITagAliasRegistry { - public: - virtual ~TagAliasRegistry(); - virtual Option<TagAlias> find( std::string const& alias ) const; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const; - void add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - static TagAliasRegistry& get(); +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - private: - std::map<std::string, TagAlias> m_registry; +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) + +namespace Catch { + struct FatalConditionHandler { + void reset(); }; +} -} // end namespace Catch +# else // CATCH_CONFIG_POSIX_SIGNALS is defined -#include <map> -#include <iostream> +#include <signal.h> namespace Catch { - TagAliasRegistry::~TagAliasRegistry() {} + struct FatalConditionHandler { - Option<TagAlias> TagAliasRegistry::find( std::string const& alias ) const { - std::map<std::string, TagAlias>::const_iterator it = m_registry.find( alias ); - if( it != m_registry.end() ) - return it->second; - else - return Option<TagAlias>(); - } + static bool isSet; + static struct sigaction oldSigActions[];// [sizeof(signalDefs) / sizeof(SignalDefs)]; + static stack_t oldSigStack; + static char altStackMem[]; - std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { - std::string expandedTestSpec = unexpandedTestSpec; - for( std::map<std::string, TagAlias>::const_iterator it = m_registry.begin(), itEnd = m_registry.end(); - it != itEnd; - ++it ) { - std::size_t pos = expandedTestSpec.find( it->first ); - if( pos != std::string::npos ) { - expandedTestSpec = expandedTestSpec.substr( 0, pos ) + - it->second.tag + - expandedTestSpec.substr( pos + it->first.size() ); - } - } - return expandedTestSpec; - } + static void handleSignal( int sig ); - void TagAliasRegistry::add( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { - - if( !startsWith( alias, "[@" ) || !endsWith( alias, "]" ) ) { - std::ostringstream oss; - oss << "error: tag alias, \"" << alias << "\" is not of the form [@alias name].\n" << lineInfo; - throw std::domain_error( oss.str().c_str() ); - } - if( !m_registry.insert( std::make_pair( alias, TagAlias( tag, lineInfo ) ) ).second ) { - std::ostringstream oss; - oss << "error: tag alias, \"" << alias << "\" already registered.\n" - << "\tFirst seen at " << find(alias)->lineInfo << "\n" - << "\tRedefined at " << lineInfo; - throw std::domain_error( oss.str().c_str() ); - } - } + FatalConditionHandler(); + ~FatalConditionHandler(); + static void reset(); + }; - TagAliasRegistry& TagAliasRegistry::get() { - static TagAliasRegistry instance; - return instance; +} // namespace Catch - } +# endif // CATCH_CONFIG_POSIX_SIGNALS - ITagAliasRegistry::~ITagAliasRegistry() {} - ITagAliasRegistry const& ITagAliasRegistry::get() { return TagAliasRegistry::get(); } +#endif // not Windows - RegistrarForTagAliases::RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ) { - try { - TagAliasRegistry::get().add( alias, tag, lineInfo ); - } - catch( std::exception& ex ) { - Colour colourGuard( Colour::Red ); - Catch::cerr() << ex.what() << std::endl; - exit(1); - } +// end catch_fatal_condition.h +namespace { + // Report the error condition + void reportFatal( char const * const message ) { + Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); } +} -} // end namespace Catch - -// #included from: ../reporters/catch_reporter_xml.hpp -#define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED - -// #included from: catch_reporter_bases.hpp -#define TWOBLUECUBES_CATCH_REPORTER_BASES_HPP_INCLUDED +#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// -#include <cstring> +# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) namespace Catch { + void FatalConditionHandler::reset() {} +} - struct StreamingReporterBase : SharedImpl<IStreamingReporter> { - - StreamingReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - {} - - virtual ~StreamingReporterBase(); - - virtual void noMatchingTestCases( std::string const& ) {} +# else // CATCH_CONFIG_WINDOWS_SEH is defined - virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { - currentTestRunInfo = _testRunInfo; - } - virtual void testGroupStarting( GroupInfo const& _groupInfo ) { - currentGroupInfo = _groupInfo; - } +namespace Catch { + struct SignalDefs { DWORD id; const char* name; }; + + // There is no 1-1 mapping between signals and windows exceptions. + // Windows can easily distinguish between SO and SigSegV, + // but SigInt, SigTerm, etc are handled differently. + static SignalDefs signalDefs[] = { + { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, + { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, + { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, + { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, + }; - virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { - currentTestCaseInfo = _testInfo; - } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { - m_sectionStack.push_back( _sectionInfo ); + LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { + for (auto const& def : signalDefs) { + if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { + reportFatal(def.name); + } } + // If its not an exception we care about, pass it along. + // This stops us from eating debugger breaks etc. + return EXCEPTION_CONTINUE_SEARCH; + } - virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { - m_sectionStack.pop_back(); - } - virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { - currentTestCaseInfo.reset(); - } - virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { - currentGroupInfo.reset(); - } - virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { - currentTestCaseInfo.reset(); - currentGroupInfo.reset(); - currentTestRunInfo.reset(); - } + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + // 32k seems enough for Catch to handle stack overflow, + // but the value was found experimentally, so there is no strong guarantee + guaranteeSize = 32 * 1024; + exceptionHandlerHandle = nullptr; + // Register as first handler in current chain + exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); + // Pass in guarantee size to be filled + SetThreadStackGuarantee(&guaranteeSize); + } - virtual void skipTest( TestCaseInfo const& ) { - // Don't do anything with this by default. - // It can optionally be overridden in the derived class. + void FatalConditionHandler::reset() { + if (isSet) { + // Unregister handler and restore the old guarantee + RemoveVectoredExceptionHandler(exceptionHandlerHandle); + SetThreadStackGuarantee(&guaranteeSize); + exceptionHandlerHandle = nullptr; + isSet = false; } + } - Ptr<IConfig> m_config; - std::ostream& stream; - - LazyStat<TestRunInfo> currentTestRunInfo; - LazyStat<GroupInfo> currentGroupInfo; - LazyStat<TestCaseInfo> currentTestCaseInfo; + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } - std::vector<SectionInfo> m_sectionStack; - }; +bool FatalConditionHandler::isSet = false; +ULONG FatalConditionHandler::guaranteeSize = 0; +PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; - struct CumulativeReporterBase : SharedImpl<IStreamingReporter> { - template<typename T, typename ChildNodeT> - struct Node : SharedImpl<> { - explicit Node( T const& _value ) : value( _value ) {} - virtual ~Node() {} +} // namespace Catch - typedef std::vector<Ptr<ChildNodeT> > ChildNodes; - T value; - ChildNodes children; - }; - struct SectionNode : SharedImpl<> { - explicit SectionNode( SectionStats const& _stats ) : stats( _stats ) {} - virtual ~SectionNode(); +# endif // CATCH_CONFIG_WINDOWS_SEH - bool operator == ( SectionNode const& other ) const { - return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; - } - bool operator == ( Ptr<SectionNode> const& other ) const { - return operator==( *other ); - } +#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - SectionStats stats; - typedef std::vector<Ptr<SectionNode> > ChildSections; - typedef std::vector<AssertionStats> Assertions; - ChildSections childSections; - Assertions assertions; - std::string stdOut; - std::string stdErr; - }; +# if !defined(CATCH_CONFIG_POSIX_SIGNALS) - struct BySectionInfo { - BySectionInfo( SectionInfo const& other ) : m_other( other ) {} - BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} - bool operator() ( Ptr<SectionNode> const& node ) const { - return node->stats.sectionInfo.lineInfo == m_other.lineInfo; - } - private: - void operator=( BySectionInfo const& ); - SectionInfo const& m_other; - }; +namespace Catch { + void FatalConditionHandler::reset() {} +} - typedef Node<TestCaseStats, SectionNode> TestCaseNode; - typedef Node<TestGroupStats, TestCaseNode> TestGroupNode; - typedef Node<TestRunStats, TestGroupNode> TestRunNode; +# else // CATCH_CONFIG_POSIX_SIGNALS is defined - CumulativeReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - {} - ~CumulativeReporterBase(); +#include <signal.h> - virtual void testRunStarting( TestRunInfo const& ) {} - virtual void testGroupStarting( GroupInfo const& ) {} +namespace Catch { - virtual void testCaseStarting( TestCaseInfo const& ) {} + struct SignalDefs { + int id; + const char* name; + }; + static SignalDefs signalDefs[] = { + { SIGINT, "SIGINT - Terminal interrupt signal" }, + { SIGILL, "SIGILL - Illegal instruction signal" }, + { SIGFPE, "SIGFPE - Floating point error signal" }, + { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, + { SIGTERM, "SIGTERM - Termination request signal" }, + { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } + }; - virtual void sectionStarting( SectionInfo const& sectionInfo ) { - SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); - Ptr<SectionNode> node; - if( m_sectionStack.empty() ) { - if( !m_rootSection ) - m_rootSection = new SectionNode( incompleteStats ); - node = m_rootSection; - } - else { - SectionNode& parentNode = *m_sectionStack.back(); - SectionNode::ChildSections::const_iterator it = - std::find_if( parentNode.childSections.begin(), - parentNode.childSections.end(), - BySectionInfo( sectionInfo ) ); - if( it == parentNode.childSections.end() ) { - node = new SectionNode( incompleteStats ); - parentNode.childSections.push_back( node ); - } - else - node = *it; + void FatalConditionHandler::handleSignal( int sig ) { + char const * name = "<unknown signal>"; + for (auto const& def : signalDefs) { + if (sig == def.id) { + name = def.name; + break; } - m_sectionStack.push_back( node ); - m_deepestSection = node; } + reset(); + reportFatal(name); + raise( sig ); + } - virtual void assertionStarting( AssertionInfo const& ) {} + FatalConditionHandler::FatalConditionHandler() { + isSet = true; + stack_t sigStack; + sigStack.ss_sp = altStackMem; + sigStack.ss_size = SIGSTKSZ; + sigStack.ss_flags = 0; + sigaltstack(&sigStack, &oldSigStack); + struct sigaction sa = { }; - virtual bool assertionEnded( AssertionStats const& assertionStats ) { - assert( !m_sectionStack.empty() ); - SectionNode& sectionNode = *m_sectionStack.back(); - sectionNode.assertions.push_back( assertionStats ); - return true; - } - virtual void sectionEnded( SectionStats const& sectionStats ) { - assert( !m_sectionStack.empty() ); - SectionNode& node = *m_sectionStack.back(); - node.stats = sectionStats; - m_sectionStack.pop_back(); + sa.sa_handler = handleSignal; + sa.sa_flags = SA_ONSTACK; + for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { + sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { - Ptr<TestCaseNode> node = new TestCaseNode( testCaseStats ); - assert( m_sectionStack.size() == 0 ); - node->children.push_back( m_rootSection ); - m_testCases.push_back( node ); - m_rootSection.reset(); + } - assert( m_deepestSection ); - m_deepestSection->stdOut = testCaseStats.stdOut; - m_deepestSection->stdErr = testCaseStats.stdErr; - } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { - Ptr<TestGroupNode> node = new TestGroupNode( testGroupStats ); - node->children.swap( m_testCases ); - m_testGroups.push_back( node ); - } - virtual void testRunEnded( TestRunStats const& testRunStats ) { - Ptr<TestRunNode> node = new TestRunNode( testRunStats ); - node->children.swap( m_testGroups ); - m_testRuns.push_back( node ); - testRunEndedCumulative(); - } - virtual void testRunEndedCumulative() = 0; + FatalConditionHandler::~FatalConditionHandler() { + reset(); + } - virtual void skipTest( TestCaseInfo const& ) {} + void FatalConditionHandler::reset() { + if( isSet ) { + // Set signals back to previous values -- hopefully nobody overwrote them in the meantime + for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { + sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); + } + // Return the old stack + sigaltstack(&oldSigStack, nullptr); + isSet = false; + } + } - Ptr<IConfig> m_config; - std::ostream& stream; - std::vector<AssertionStats> m_assertions; - std::vector<std::vector<Ptr<SectionNode> > > m_sections; - std::vector<Ptr<TestCaseNode> > m_testCases; - std::vector<Ptr<TestGroupNode> > m_testGroups; + bool FatalConditionHandler::isSet = false; + struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; - std::vector<Ptr<TestRunNode> > m_testRuns; +} // namespace Catch - Ptr<SectionNode> m_rootSection; - Ptr<SectionNode> m_deepestSection; - std::vector<Ptr<SectionNode> > m_sectionStack; +# endif // CATCH_CONFIG_POSIX_SIGNALS - }; +#endif // not Windows +// end catch_fatal_condition.cpp +// start catch_interfaces_capture.cpp - template<char C> - char const* getLineOfChars() { - static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; - if( !*line ) { - memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); - line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; - } - return line; - } +namespace Catch { + IResultCapture::~IResultCapture() = default; +} +// end catch_interfaces_capture.cpp +// start catch_interfaces_config.cpp -} // end namespace Catch +namespace Catch { + IConfig::~IConfig() = default; +} +// end catch_interfaces_config.cpp +// start catch_interfaces_exception.cpp -// #included from: ../internal/catch_reporter_registrars.hpp -#define TWOBLUECUBES_CATCH_REPORTER_REGISTRARS_HPP_INCLUDED +namespace Catch { + IExceptionTranslator::~IExceptionTranslator() = default; + IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; +} +// end catch_interfaces_exception.cpp +// start catch_interfaces_registry_hub.cpp namespace Catch { + IRegistryHub::~IRegistryHub() = default; + IMutableRegistryHub::~IMutableRegistryHub() = default; +} +// end catch_interfaces_registry_hub.cpp +// start catch_interfaces_reporter.cpp - template<typename T> - class LegacyReporterRegistrar { +// start catch_reporter_multi.h - class ReporterFactory : public IReporterFactory { - virtual IStreamingReporter* create( ReporterConfig const& config ) const { - return new LegacyReporterAdapter( new T( config ) ); - } +namespace Catch { - virtual std::string getDescription() const { - return T::getDescription(); - } - }; + class MultipleReporters : public IStreamingReporter { + using Reporters = std::vector<IStreamingReporterPtr>; + Reporters m_reporters; public: + void add( IStreamingReporterPtr&& reporter ); - LegacyReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); - } - }; - - template<typename T> - class ReporterRegistrar { + public: // IStreamingReporter - class ReporterFactory : public IReporterFactory { + ReporterPreferences getPreferences() const override; - // *** Please Note ***: - // - If you end up here looking at a compiler error because it's trying to register - // your custom reporter class be aware that the native reporter interface has changed - // to IStreamingReporter. The "legacy" interface, IReporter, is still supported via - // an adapter. Just use REGISTER_LEGACY_REPORTER to take advantage of the adapter. - // However please consider updating to the new interface as the old one is now - // deprecated and will probably be removed quite soon! - // Please contact me via github if you have any questions at all about this. - // In fact, ideally, please contact me anyway to let me know you've hit this - as I have - // no idea who is actually using custom reporters at all (possibly no-one!). - // The new interface is designed to minimise exposure to interface changes in the future. - virtual IStreamingReporter* create( ReporterConfig const& config ) const { - return new T( config ); - } - - virtual std::string getDescription() const { - return T::getDescription(); - } - }; + void noMatchingTestCases( std::string const& spec ) override; - public: + static std::set<Verbosity> getSupportedVerbosities(); - ReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); - } - }; -} + void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; + void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; -#define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ - namespace{ Catch::LegacyReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } -#define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ - namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } + void testRunStarting( TestRunInfo const& testRunInfo ) override; + void testGroupStarting( GroupInfo const& groupInfo ) override; + void testCaseStarting( TestCaseInfo const& testInfo ) override; + void sectionStarting( SectionInfo const& sectionInfo ) override; + void assertionStarting( AssertionInfo const& assertionInfo ) override; -// #included from: ../internal/catch_xmlwriter.hpp -#define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED + // The return value indicates if the messages buffer should be cleared: + bool assertionEnded( AssertionStats const& assertionStats ) override; + void sectionEnded( SectionStats const& sectionStats ) override; + void testCaseEnded( TestCaseStats const& testCaseStats ) override; + void testGroupEnded( TestGroupStats const& testGroupStats ) override; + void testRunEnded( TestRunStats const& testRunStats ) override; -#include <sstream> -#include <string> -#include <vector> + void skipTest( TestCaseInfo const& testInfo ) override; + bool isMulti() const override; -namespace Catch { + }; - class XmlWriter { - public: +} // end namespace Catch - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} +// end catch_reporter_multi.h +namespace Catch { - ScopedElement( ScopedElement const& other ) - : m_writer( other.m_writer ){ - other.m_writer = NULL; - } + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) + : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} - ~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } + ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) + : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} - ScopedElement& writeText( std::string const& text, bool indent = true ) { - m_writer->writeText( text, indent ); - return *this; - } + std::ostream& ReporterConfig::stream() const { return *m_stream; } + IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } - template<typename T> - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } + TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} - private: - mutable XmlWriter* m_writer; - }; + GroupInfo::GroupInfo( std::string const& _name, + std::size_t _groupIndex, + std::size_t _groupsCount ) + : name( _name ), + groupIndex( _groupIndex ), + groupsCounts( _groupsCount ) + {} - XmlWriter() - : m_tagIsOpen( false ), - m_needsNewline( false ), - m_os( &Catch::cout() ) - {} + AssertionStats::AssertionStats( AssertionResult const& _assertionResult, + std::vector<MessageInfo> const& _infoMessages, + Totals const& _totals ) + : assertionResult( _assertionResult ), + infoMessages( _infoMessages ), + totals( _totals ) + { + assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; - XmlWriter( std::ostream& os ) - : m_tagIsOpen( false ), - m_needsNewline( false ), - m_os( &os ) - {} + if( assertionResult.hasMessage() ) { + // Copy message into messages list. + // !TBD This should have been done earlier, somewhere + MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); + builder << assertionResult.getMessage(); + builder.m_info.message = builder.m_stream.str(); - ~XmlWriter() { - while( !m_tags.empty() ) - endElement(); + infoMessages.push_back( builder.m_info ); } + } - XmlWriter& startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - stream() << m_indent << "<" << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; - } + AssertionStats::~AssertionStats() = default; - ScopedElement scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; - } + SectionStats::SectionStats( SectionInfo const& _sectionInfo, + Counts const& _assertions, + double _durationInSeconds, + bool _missingAssertions ) + : sectionInfo( _sectionInfo ), + assertions( _assertions ), + durationInSeconds( _durationInSeconds ), + missingAssertions( _missingAssertions ) + {} - XmlWriter& endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - stream() << "/>\n"; - m_tagIsOpen = false; - } - else { - stream() << m_indent << "</" << m_tags.back() << ">\n"; - } - m_tags.pop_back(); - return *this; - } + SectionStats::~SectionStats() = default; + + TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, + Totals const& _totals, + std::string const& _stdOut, + std::string const& _stdErr, + bool _aborting ) + : testInfo( _testInfo ), + totals( _totals ), + stdOut( _stdOut ), + stdErr( _stdErr ), + aborting( _aborting ) + {} - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) { - stream() << " " << name << "=\""; - writeEncodedText( attribute ); - stream() << "\""; - } - return *this; - } + TestCaseStats::~TestCaseStats() = default; - XmlWriter& writeAttribute( std::string const& name, bool attribute ) { - stream() << " " << name << "=\"" << ( attribute ? "true" : "false" ) << "\""; - return *this; - } + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, + Totals const& _totals, + bool _aborting ) + : groupInfo( _groupInfo ), + totals( _totals ), + aborting( _aborting ) + {} - template<typename T> - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - if( !name.empty() ) - stream() << " " << name << "=\"" << attribute << "\""; - return *this; - } + TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) + : groupInfo( _groupInfo ), + aborting( false ) + {} - XmlWriter& writeText( std::string const& text, bool indent = true ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - stream() << m_indent; - writeEncodedText( text ); - m_needsNewline = true; - } - return *this; - } + TestGroupStats::~TestGroupStats() = default; - XmlWriter& writeComment( std::string const& text ) { - ensureTagClosed(); - stream() << m_indent << "<!--" << text << "-->"; - m_needsNewline = true; - return *this; - } + TestRunStats::TestRunStats( TestRunInfo const& _runInfo, + Totals const& _totals, + bool _aborting ) + : runInfo( _runInfo ), + totals( _totals ), + aborting( _aborting ) + {} - XmlWriter& writeBlankLine() { - ensureTagClosed(); - stream() << "\n"; - return *this; - } + TestRunStats::~TestRunStats() = default; - void setStream( std::ostream& os ) { - m_os = &os; - } + void IStreamingReporter::fatalErrorEncountered( StringRef ) {} + bool IStreamingReporter::isMulti() const { return false; } - private: - XmlWriter( XmlWriter const& ); - void operator=( XmlWriter const& ); + IReporterFactory::~IReporterFactory() = default; + IReporterRegistry::~IReporterRegistry() = default; - std::ostream& stream() { - return *m_os; - } + void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ) { - void ensureTagClosed() { - if( m_tagIsOpen ) { - stream() << ">\n"; - m_tagIsOpen = false; - } + if( !existingReporter ) { + existingReporter = std::move( additionalReporter ); + return; } - void newlineIfNecessary() { - if( m_needsNewline ) { - stream() << "\n"; - m_needsNewline = false; - } + MultipleReporters* multi = nullptr; + + if( existingReporter->isMulti() ) { + multi = static_cast<MultipleReporters*>( existingReporter.get() ); } + else { + auto newMulti = std::unique_ptr<MultipleReporters>( new MultipleReporters ); + newMulti->add( std::move( existingReporter ) ); + multi = newMulti.get(); + existingReporter = std::move( newMulti ); + } + multi->add( std::move( additionalReporter ) ); + } - void writeEncodedText( std::string const& text ) { - static const char* charsToEncode = "<&\""; - std::string mtext = text; - std::string::size_type pos = mtext.find_first_of( charsToEncode ); - while( pos != std::string::npos ) { - stream() << mtext.substr( 0, pos ); +} // end namespace Catch +// end catch_interfaces_reporter.cpp +// start catch_interfaces_runner.cpp - switch( mtext[pos] ) { - case '<': - stream() << "<"; - break; - case '&': - stream() << "&"; - break; - case '\"': - stream() << """; - break; - } - mtext = mtext.substr( pos+1 ); - pos = mtext.find_first_of( charsToEncode ); - } - stream() << mtext; - } - - bool m_tagIsOpen; - bool m_needsNewline; - std::vector<std::string> m_tags; - std::string m_indent; - std::ostream* m_os; - }; +namespace Catch { + IRunner::~IRunner() = default; +} +// end catch_interfaces_runner.cpp +// start catch_interfaces_testcase.cpp +namespace Catch { + ITestInvoker::~ITestInvoker() = default; + ITestCaseRegistry::~ITestCaseRegistry() = default; } +// end catch_interfaces_testcase.cpp +// start catch_leak_detector.cpp + namespace Catch { - class XmlReporter : public StreamingReporterBase { - public: - XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_sectionDepth( 0 ) - {} - virtual ~XmlReporter(); +#ifdef CATCH_CONFIG_WINDOWS_CRTDBG +#include <crtdbg.h> - static std::string getDescription() { - return "Reports test results as an XML document"; - } + LeakDetector::LeakDetector() { + int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flag |= _CRTDBG_LEAK_CHECK_DF; + flag |= _CRTDBG_ALLOC_MEM_DF; + _CrtSetDbgFlag(flag); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + // Change this to leaking allocation's number to break there + _CrtSetBreakAlloc(-1); + } - public: // StreamingReporterBase - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; - } +#else - virtual void noMatchingTestCases( std::string const& s ) { - StreamingReporterBase::noMatchingTestCases( s ); - } + LeakDetector::LeakDetector(){} - virtual void testRunStarting( TestRunInfo const& testInfo ) { - StreamingReporterBase::testRunStarting( testInfo ); - m_xml.setStream( stream ); - m_xml.startElement( "Catch" ); - if( !m_config->name().empty() ) - m_xml.writeAttribute( "name", m_config->name() ); - } +#endif - virtual void testGroupStarting( GroupInfo const& groupInfo ) { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); - } +} +// end catch_leak_detector.cpp +// start catch_list.cpp - virtual void testCaseStarting( TestCaseInfo const& testInfo ) { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); +// start catch_list.h - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); - } +#include <set> - virtual void sectionStarting( SectionInfo const& sectionInfo ) { - StreamingReporterBase::sectionStarting( sectionInfo ); - if( m_sectionDepth++ > 0 ) { - m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ) - .writeAttribute( "description", sectionInfo.description ); - } - } +namespace Catch { - virtual void assertionStarting( AssertionInfo const& ) { } + std::size_t listTests( Config const& config ); - virtual bool assertionEnded( AssertionStats const& assertionStats ) { - const AssertionResult& assertionResult = assertionStats.assertionResult; + std::size_t listTestsNamesOnly( Config const& config ); - // Print any info messages in <Info> tags. - if( assertionStats.assertionResult.getResultType() != ResultWas::Ok ) { - for( std::vector<MessageInfo>::const_iterator it = assertionStats.infoMessages.begin(), itEnd = assertionStats.infoMessages.end(); - it != itEnd; - ++it ) { - if( it->type == ResultWas::Info ) { - m_xml.scopedElement( "Info" ) - .writeText( it->message ); - } else if ( it->type == ResultWas::Warning ) { - m_xml.scopedElement( "Warning" ) - .writeText( it->message ); - } - } - } + struct TagInfo { + void add( std::string const& spelling ); + std::string all() const; - // Drop out if result was successful but we're not printing them. - if( !m_config->includeSuccessfulResults() && isOk(assertionResult.getResultType()) ) - return true; + std::set<std::string> spellings; + std::size_t count = 0; + }; - // Print the expression if there is one. - if( assertionResult.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success", assertionResult.succeeded() ) - .writeAttribute( "type", assertionResult.getTestMacroName() ) - .writeAttribute( "filename", assertionResult.getSourceInfo().file ) - .writeAttribute( "line", assertionResult.getSourceInfo().line ); + std::size_t listTags( Config const& config ); - m_xml.scopedElement( "Original" ) - .writeText( assertionResult.getExpression() ); - m_xml.scopedElement( "Expanded" ) - .writeText( assertionResult.getExpandedExpression() ); - } + std::size_t listReporters( Config const& /*config*/ ); - // And... Print a result applicable to each result type. - switch( assertionResult.getResultType() ) { - case ResultWas::ThrewException: - m_xml.scopedElement( "Exception" ) - .writeAttribute( "filename", assertionResult.getSourceInfo().file ) - .writeAttribute( "line", assertionResult.getSourceInfo().line ) - .writeText( assertionResult.getMessage() ); - break; - case ResultWas::FatalErrorCondition: - m_xml.scopedElement( "Fatal Error Condition" ) - .writeAttribute( "filename", assertionResult.getSourceInfo().file ) - .writeAttribute( "line", assertionResult.getSourceInfo().line ) - .writeText( assertionResult.getMessage() ); - break; - case ResultWas::Info: - m_xml.scopedElement( "Info" ) - .writeText( assertionResult.getMessage() ); - break; - case ResultWas::Warning: - // Warning will already have been written - break; - case ResultWas::ExplicitFailure: - m_xml.scopedElement( "Failure" ) - .writeText( assertionResult.getMessage() ); - break; - default: - break; - } + Option<std::size_t> list( Config const& config ); - if( assertionResult.hasExpression() ) - m_xml.endElement(); +} // end namespace Catch - return true; - } +// end catch_list.h +// start catch_text.h - virtual void sectionEnded( SectionStats const& sectionStats ) { - StreamingReporterBase::sectionEnded( sectionStats ); - if( --m_sectionDepth > 0 ) { - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes", sectionStats.assertions.passed ); - e.writeAttribute( "failures", sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); +namespace Catch { + using namespace clara::TextFlow; +} - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); +// end catch_text.h +#include <limits> +#include <algorithm> +#include <iomanip> - m_xml.endElement(); - } +namespace Catch { + + std::size_t listTests( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Matching test cases:\n"; + else { + Catch::cout() << "All available test cases:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); + auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + Colour::Code colour = testCaseInfo.isHidden() + ? Colour::SecondaryText + : Colour::None; + Colour colourGuard( colour ); - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; + if( config.verbosity() >= Verbosity::High ) { + Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; + std::string description = testCaseInfo.description; + if( description.empty() ) + description = "(NO DESCRIPTION)"; + Catch::cout() << Column( description ).indent(4) << std::endl; + } + if( !testCaseInfo.tags.empty() ) + Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; + } - m_xml.endElement(); + if( !config.testSpec().hasFilters() ) + Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; + else + Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; + return matchedTestCases.size(); + } + + std::size_t listTestsNamesOnly( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( !config.testSpec().hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); + std::size_t matchedTests = 0; + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCaseInfo : matchedTestCases ) { + matchedTests++; + if( startsWith( testCaseInfo.name, '#' ) ) + Catch::cout() << '"' << testCaseInfo.name << '"'; + else + Catch::cout() << testCaseInfo.name; + if ( config.verbosity() >= Verbosity::High ) + Catch::cout() << "\t@" << testCaseInfo.lineInfo; + Catch::cout() << std::endl; } + return matchedTests; + } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { - StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) - .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); - m_xml.endElement(); + void TagInfo::add( std::string const& spelling ) { + ++count; + spellings.insert( spelling ); + } + + std::string TagInfo::all() const { + std::string out; + for( auto const& spelling : spellings ) + out += "[" + spelling + "]"; + return out; + } + + std::size_t listTags( Config const& config ) { + TestSpec testSpec = config.testSpec(); + if( config.testSpec().hasFilters() ) + Catch::cout() << "Tags for matching test cases:\n"; + else { + Catch::cout() << "All available tags:\n"; + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); } - virtual void testRunEnded( TestRunStats const& testRunStats ) { - StreamingReporterBase::testRunEnded( testRunStats ); - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testRunStats.totals.assertions.passed ) - .writeAttribute( "failures", testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); - m_xml.endElement(); + std::map<std::string, TagInfo> tagCounts; + + std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); + for( auto const& testCase : matchedTestCases ) { + for( auto const& tagName : testCase.getTestCaseInfo().tags ) { + std::string lcaseTagName = toLower( tagName ); + auto countIt = tagCounts.find( lcaseTagName ); + if( countIt == tagCounts.end() ) + countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; + countIt->second.add( tagName ); + } } - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth; - }; + for( auto const& tagCount : tagCounts ) { + std::ostringstream oss; + oss << " " << std::setw(2) << tagCount.second.count << " "; + auto wrapper = Column( tagCount.second.all() ) + .initialIndent( 0 ) + .indent( oss.str().size() ) + .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); + Catch::cout() << oss.str() << wrapper << '\n'; + } + Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; + return tagCounts.size(); + } + + std::size_t listReporters( Config const& /*config*/ ) { + Catch::cout() << "Available reporters:\n"; + IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); + std::size_t maxNameLen = 0; + for( auto const& factoryKvp : factories ) + maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); + + for( auto const& factoryKvp : factories ) { + Catch::cout() + << Column( factoryKvp.first + ":" ) + .indent(2) + .width( 5+maxNameLen ) + + Column( factoryKvp.second->getDescription() ) + .initialIndent(0) + .indent(2) + .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) + << "\n"; + } + Catch::cout() << std::endl; + return factories.size(); + } - INTERNAL_CATCH_REGISTER_REPORTER( "xml", XmlReporter ) + Option<std::size_t> list( Config const& config ) { + Option<std::size_t> listedCount; + if( config.listTests() ) + listedCount = listedCount.valueOr(0) + listTests( config ); + if( config.listTestNamesOnly() ) + listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); + if( config.listTags() ) + listedCount = listedCount.valueOr(0) + listTags( config ); + if( config.listReporters() ) + listedCount = listedCount.valueOr(0) + listReporters( config ); + return listedCount; + } } // end namespace Catch +// end catch_list.cpp +// start catch_matchers.cpp + +namespace Catch { +namespace Matchers { + namespace Impl { -// #included from: ../reporters/catch_reporter_junit.hpp -#define TWOBLUECUBES_CATCH_REPORTER_JUNIT_HPP_INCLUDED + std::string MatcherUntypedBase::toString() const { + if( m_cachedToString.empty() ) + m_cachedToString = describe(); + return m_cachedToString; + } -#include <assert.h> + MatcherUntypedBase::~MatcherUntypedBase() = default; -namespace Catch { + } // namespace Impl +} // namespace Matchers - class JunitReporter : public CumulativeReporterBase { - public: - JunitReporter( ReporterConfig const& _config ) - : CumulativeReporterBase( _config ), - xml( _config.stream() ) - {} +using namespace Matchers; +using Matchers::Impl::MatcherBase; - ~JunitReporter(); +} // namespace Catch +// end catch_matchers.cpp +// start catch_matchers_string.cpp - static std::string getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } +namespace Catch { +namespace Matchers { - virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + namespace StdString { - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; + CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string CasedString::adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; } - - virtual void testRunStarting( TestRunInfo const& runInfo ) { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); + std::string CasedString::caseSensitivitySuffix() const { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : std::string(); } - virtual void testGroupStarting( GroupInfo const& groupInfo ) { - suiteTimer.start(); - stdOutForSuite.str(""); - stdErrForSuite.str(""); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); + StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) + : m_comparator( comparator ), + m_operation( operation ) { } - virtual bool assertionEnded( AssertionStats const& assertionStats ) { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); + std::string StringMatcherBase::describe() const { + std::string description; + description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + + m_comparator.caseSensitivitySuffix().size()); + description += m_operation; + description += ": \""; + description += m_comparator.m_str; + description += "\""; + description += m_comparator.caseSensitivitySuffix(); + return description; } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { - stdOutForSuite << testCaseStats.stdOut; - stdErrForSuite << testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); - } + EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); + bool EqualsMatcher::match( std::string const& source ) const { + return m_comparator.adjustString( source ) == m_comparator.m_str; } - virtual void testRunEndedCumulative() { - xml.endElement(); + ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} + + bool ContainsMatcher::match( std::string const& source ) const { + return contains( m_comparator.adjustString( source ), m_comparator.m_str ); } - void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - TestGroupStats const& stats = groupNode.value; - xml.writeAttribute( "name", stats.groupInfo.name ); - xml.writeAttribute( "errors", unexpectedExceptions ); - xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "tests", stats.totals.assertions.total() ); - xml.writeAttribute( "hostname", "tbd" ); // !TBD - if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time", "" ); - else - xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", "tbd" ); // !TBD + StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} - // Write test cases - for( TestGroupNode::ChildNodes::const_iterator - it = groupNode.children.begin(), itEnd = groupNode.children.end(); - it != itEnd; - ++it ) - writeTestCase( **it ); + bool StartsWithMatcher::match( std::string const& source ) const { + return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); + } - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); + EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} + + bool EndsWithMatcher::match( std::string const& source ) const { + return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); } - void writeTestCase( TestCaseNode const& testCaseNode ) { - TestCaseStats const& stats = testCaseNode.value; + } // namespace StdString - // All test cases have exactly one section - which represents the - // test case itself. That section may have 0-n nested sections - assert( testCaseNode.children.size() == 1 ); - SectionNode const& rootSection = *testCaseNode.children.front(); + StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { + return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); + } + +} // namespace Matchers +} // namespace Catch +// end catch_matchers_string.cpp +// start catch_message.cpp + +namespace Catch { + + MessageInfo::MessageInfo( std::string const& _macroName, + SourceLineInfo const& _lineInfo, + ResultWas::OfType _type ) + : macroName( _macroName ), + lineInfo( _lineInfo ), + type( _type ), + sequence( ++globalCount ) + {} + + bool MessageInfo::operator==( MessageInfo const& other ) const { + return sequence == other.sequence; + } + + bool MessageInfo::operator<( MessageInfo const& other ) const { + return sequence < other.sequence; + } + + // This may need protecting if threading support is added + unsigned int MessageInfo::globalCount = 0; + + //////////////////////////////////////////////////////////////////////////// + + Catch::MessageBuilder::MessageBuilder( std::string const& macroName, + SourceLineInfo const& lineInfo, + ResultWas::OfType type ) + :m_info(macroName, lineInfo, type) {} + + //////////////////////////////////////////////////////////////////////////// + + ScopedMessage::ScopedMessage( MessageBuilder const& builder ) + : m_info( builder.m_info ) + { + m_info.message = builder.m_stream.str(); + getResultCapture().pushScopedMessage( m_info ); + } + + ScopedMessage::~ScopedMessage() { + if ( !std::uncaught_exception() ){ + getResultCapture().popScopedMessage(m_info); + } + } + +} // end namespace Catch +// end catch_message.cpp +// start catch_random_number_generator.cpp + +// start catch_random_number_generator.h + +#include <algorithm> + +namespace Catch { + + struct IConfig; + + void seedRng( IConfig const& config ); + + unsigned int rngSeed(); + + struct RandomNumberGenerator { + using result_type = unsigned int; + + static constexpr result_type (min)() { return 0; } + static constexpr result_type (max)() { return 1000000; } + + result_type operator()( result_type n ) const; + result_type operator()() const; + + template<typename V> + static void shuffle( V& vector ) { + RandomNumberGenerator rng; + std::shuffle( vector.begin(), vector.end(), rng ); + } + }; + +} + +// end catch_random_number_generator.h +#include <cstdlib> + +namespace Catch { + + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + + RandomNumberGenerator::result_type RandomNumberGenerator::operator()( result_type n ) const { + return std::rand() % n; + } + RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const { + return std::rand() % (max)(); + } + +} +// end catch_random_number_generator.cpp +// start catch_registry_hub.cpp + +// start catch_test_case_registry_impl.h + +#include <vector> +#include <set> +#include <algorithm> +#include <ios> + +namespace Catch { + + class TestCase; + struct IConfig; + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ); + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); + + class TestRegistry : public ITestCaseRegistry { + public: + virtual ~TestRegistry() = default; + + virtual void registerTest( TestCase const& testCase ); + + std::vector<TestCase> const& getAllTests() const override; + std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override; + + private: + std::vector<TestCase> m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; + mutable std::vector<TestCase> m_sortedFunctions; + std::size_t m_unnamedCount = 0; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised + }; + + /////////////////////////////////////////////////////////////////////////// + + class TestInvokerAsFunction : public ITestInvoker { + void(*m_testAsFunction)(); + public: + TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; + + void invoke() const override; + }; + + std::string extractClassName( std::string const& classOrQualifiedMethodName ); + + /////////////////////////////////////////////////////////////////////////// + +} // end namespace Catch + +// end catch_test_case_registry_impl.h +// start catch_reporter_registry.h + +#include <map> + +namespace Catch { + + class ReporterRegistry : public IReporterRegistry { + + public: + + ~ReporterRegistry() override; + + IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; + + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); + void registerListener( IReporterFactoryPtr const& factory ); + + FactoryMap const& getFactories() const override; + Listeners const& getListeners() const override; + + private: + FactoryMap m_factories; + Listeners m_listeners; + }; +} + +// end catch_reporter_registry.h +// start catch_tag_alias_registry.h + +// start catch_tag_alias.h + +#include <string> + +namespace Catch { + + struct TagAlias { + TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); + + std::string tag; + SourceLineInfo lineInfo; + }; + +} // end namespace Catch + +// end catch_tag_alias.h +#include <map> + +namespace Catch { + + class TagAliasRegistry : public ITagAliasRegistry { + public: + ~TagAliasRegistry() override; + TagAlias const* find( std::string const& alias ) const override; + std::string expandAliases( std::string const& unexpandedTestSpec ) const override; + void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); + + private: + std::map<std::string, TagAlias> m_registry; + }; + +} // end namespace Catch + +// end catch_tag_alias_registry.h +// start catch_startup_exception_registry.h + +#include <vector> +#include <exception> + +namespace Catch { + + class StartupExceptionRegistry { + public: + void add(std::exception_ptr const& exception) noexcept; + std::vector<std::exception_ptr> const& getExceptions() const noexcept; + private: + std::vector<std::exception_ptr> m_exceptions; + }; + +} // end namespace Catch + +// end catch_startup_exception_registry.h +namespace Catch { + + namespace { + + class RegistryHub : public IRegistryHub, public IMutableRegistryHub, + private NonCopyable { + + public: // IRegistryHub + RegistryHub() = default; + IReporterRegistry const& getReporterRegistry() const override { + return m_reporterRegistry; + } + ITestCaseRegistry const& getTestCaseRegistry() const override { + return m_testCaseRegistry; + } + IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { + return m_exceptionTranslatorRegistry; + } + ITagAliasRegistry const& getTagAliasRegistry() const override { + return m_tagAliasRegistry; + } + StartupExceptionRegistry const& getStartupExceptionRegistry() const override { + return m_exceptionRegistry; + } + + public: // IMutableRegistryHub + void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerReporter( name, factory ); + } + void registerListener( IReporterFactoryPtr const& factory ) override { + m_reporterRegistry.registerListener( factory ); + } + void registerTest( TestCase const& testInfo ) override { + m_testCaseRegistry.registerTest( testInfo ); + } + void registerTranslator( const IExceptionTranslator* translator ) override { + m_exceptionTranslatorRegistry.registerTranslator( translator ); + } + void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { + m_tagAliasRegistry.add( alias, tag, lineInfo ); + } + void registerStartupException() noexcept override { + m_exceptionRegistry.add(std::current_exception()); + } + + private: + TestRegistry m_testCaseRegistry; + ReporterRegistry m_reporterRegistry; + ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; + TagAliasRegistry m_tagAliasRegistry; + StartupExceptionRegistry m_exceptionRegistry; + }; + + // Single, global, instance + RegistryHub*& getTheRegistryHub() { + static RegistryHub* theRegistryHub = nullptr; + if( !theRegistryHub ) + theRegistryHub = new RegistryHub(); + return theRegistryHub; + } + } + + IRegistryHub& getRegistryHub() { + return *getTheRegistryHub(); + } + IMutableRegistryHub& getMutableRegistryHub() { + return *getTheRegistryHub(); + } + void cleanUp() { + delete getTheRegistryHub(); + getTheRegistryHub() = nullptr; + cleanUpContext(); + } + std::string translateActiveException() { + return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); + } + +} // end namespace Catch +// end catch_registry_hub.cpp +// start catch_reporter_registry.cpp + +namespace Catch { + + ReporterRegistry::~ReporterRegistry() = default; + + IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { + auto it = m_factories.find( name ); + if( it == m_factories.end() ) + return nullptr; + return it->second->create( ReporterConfig( config ) ); + } + + void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { + m_factories.emplace(name, factory); + } + void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { + m_listeners.push_back( factory ); + } + + IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { + return m_factories; + } + IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { + return m_listeners; + } + +} +// end catch_reporter_registry.cpp +// start catch_result_type.cpp + +namespace Catch { + + bool isOk( ResultWas::OfType resultType ) { + return ( resultType & ResultWas::FailureBit ) == 0; + } + bool isJustInfo( int flags ) { + return flags == ResultWas::Info; + } + + ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { + return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); + } + + bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } + bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } + bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } + +} // end namespace Catch +// end catch_result_type.cpp +// start catch_run_context.cpp +// start catch_run_context.h + +#include <string> + +namespace Catch { + + struct IMutableContext; + + class StreamRedirect { + + public: + StreamRedirect(std::ostream& stream, std::string& targetString); + + ~StreamRedirect(); + + private: + std::ostream& m_stream; + std::streambuf* m_prevBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + // StdErr has two constituent streams in C++, std::cerr and std::clog + // This means that we need to redirect 2 streams into 1 to keep proper + // order of writes and cannot use StreamRedirect on its own + class StdErrRedirect { + public: + StdErrRedirect(std::string& targetString); + ~StdErrRedirect(); + private: + std::streambuf* m_cerrBuf; + std::streambuf* m_clogBuf; + std::ostringstream m_oss; + std::string& m_targetString; + }; + + /////////////////////////////////////////////////////////////////////////// + + class RunContext : public IResultCapture, public IRunner { + + public: + RunContext( RunContext const& ) = delete; + RunContext& operator =( RunContext const& ) = delete; + + explicit RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter); + + virtual ~RunContext(); + + void testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount); + void testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount); + + Totals runTest(TestCase const& testCase); + + IConfigPtr config() const; + IStreamingReporter& reporter() const; + + private: // IResultCapture + + void assertionStarting(AssertionInfo const& info) override; + void assertionEnded(AssertionResult const& result) override; + + bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; + bool testForMissingAssertions(Counts& assertions); + + void sectionEnded(SectionEndInfo const& endInfo) override; + void sectionEndedEarly(SectionEndInfo const& endInfo) override; + + void benchmarkStarting( BenchmarkInfo const& info ) override; + void benchmarkEnded( BenchmarkStats const& stats ) override; + + void pushScopedMessage(MessageInfo const& message) override; + void popScopedMessage(MessageInfo const& message) override; + + std::string getCurrentTestName() const override; + + const AssertionResult* getLastResult() const override; + + void exceptionEarlyReported() override; + + void handleFatalErrorCondition( StringRef message ) override; + + bool lastAssertionPassed() override; + + void assertionPassed() override; + + void assertionRun() override; + + public: + // !TBD We need to do this another way! + bool aborting() const override; + + private: + + void runCurrentTest(std::string& redirectedCout, std::string& redirectedCerr); + void invokeActiveTestCase(); + + private: + + void handleUnfinishedSections(); + + TestRunInfo m_runInfo; + IMutableContext& m_context; + TestCase const* m_activeTestCase = nullptr; + ITracker* m_testCaseTracker; + Option<AssertionResult> m_lastResult; + + IConfigPtr m_config; + Totals m_totals; + IStreamingReporterPtr m_reporter; + std::vector<MessageInfo> m_messages; + AssertionInfo m_lastAssertionInfo; + std::vector<SectionEndInfo> m_unfinishedSections; + std::vector<ITracker*> m_activeSections; + TrackerContext m_trackerContext; + std::size_t m_prevPassed = 0; + bool m_shouldReportUnexpected = true; + }; + + IResultCapture& getResultCapture(); + +} // end namespace Catch + +// end catch_run_context.h + +#include <cassert> +#include <algorithm> + +namespace Catch { + + StreamRedirect::StreamRedirect(std::ostream& stream, std::string& targetString) + : m_stream(stream), + m_prevBuf(stream.rdbuf()), + m_targetString(targetString) { + stream.rdbuf(m_oss.rdbuf()); + } + + StreamRedirect::~StreamRedirect() { + m_targetString += m_oss.str(); + m_stream.rdbuf(m_prevBuf); + } + + StdErrRedirect::StdErrRedirect(std::string & targetString) + :m_cerrBuf(cerr().rdbuf()), m_clogBuf(clog().rdbuf()), + m_targetString(targetString) { + cerr().rdbuf(m_oss.rdbuf()); + clog().rdbuf(m_oss.rdbuf()); + } + + StdErrRedirect::~StdErrRedirect() { + m_targetString += m_oss.str(); + cerr().rdbuf(m_cerrBuf); + clog().rdbuf(m_clogBuf); + } + + RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) + : m_runInfo(_config->name()), + m_context(getCurrentMutableContext()), + m_config(_config), + m_reporter(std::move(reporter)), + m_lastAssertionInfo{ "", SourceLineInfo("",0), "", ResultDisposition::Normal } + { + m_context.setRunner(this); + m_context.setConfig(m_config); + m_context.setResultCapture(this); + m_reporter->testRunStarting(m_runInfo); + } + + RunContext::~RunContext() { + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); + } + + void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); + } + + void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { + m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); + } + + Totals RunContext::runTest(TestCase const& testCase) { + Totals prevTotals = m_totals; + + std::string redirectedCout; + std::string redirectedCerr; + + TestCaseInfo testInfo = testCase.getTestCaseInfo(); + + m_reporter->testCaseStarting(testInfo); + + m_activeTestCase = &testCase; + + ITracker& rootTracker = m_trackerContext.startRun(); + assert(rootTracker.isSectionTracker()); + static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); + do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); + runCurrentTest(redirectedCout, redirectedCerr); + } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); + + Totals deltaTotals = m_totals.delta(prevTotals); + if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { + deltaTotals.assertions.failed++; + deltaTotals.testCases.passed--; + deltaTotals.testCases.failed++; + } + m_totals.testCases += deltaTotals.testCases; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + redirectedCout, + redirectedCerr, + aborting())); + + m_activeTestCase = nullptr; + m_testCaseTracker = nullptr; + + return deltaTotals; + } + + IConfigPtr RunContext::config() const { + return m_config; + } + + IStreamingReporter& RunContext::reporter() const { + return *m_reporter; + } + + void RunContext::assertionStarting(AssertionInfo const& info) { + m_reporter->assertionStarting( info ); + } + void RunContext::assertionEnded(AssertionResult const & result) { + if (result.getResultType() == ResultWas::Ok) { + m_totals.assertions.passed++; + } else if (!result.isOk()) { + if( m_activeTestCase->getTestCaseInfo().okToFail() ) + m_totals.assertions.failedButOk++; + else + m_totals.assertions.failed++; + } + + // We have no use for the return value (whether messages should be cleared), because messages were made scoped + // and should be let to clear themselves out. + static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); + + // Reset working state + m_lastAssertionInfo = { "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}", m_lastAssertionInfo.resultDisposition }; + m_lastResult = result; + } + + bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { + ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); + if (!sectionTracker.isOpen()) + return false; + m_activeSections.push_back(§ionTracker); + + m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; + + m_reporter->sectionStarting(sectionInfo); + + assertions = m_totals.assertions; + + return true; + } + + bool RunContext::testForMissingAssertions(Counts& assertions) { + if (assertions.total() != 0) + return false; + if (!m_config->warnAboutMissingAssertions()) + return false; + if (m_trackerContext.currentTracker().hasChildren()) + return false; + m_totals.assertions.failed++; + assertions.failed++; + return true; + } + + void RunContext::sectionEnded(SectionEndInfo const & endInfo) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + + if (!m_activeSections.empty()) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } + + m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); + m_messages.clear(); + } + + void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { + if (m_unfinishedSections.empty()) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back(endInfo); + } + void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { + m_reporter->benchmarkStarting( info ); + } + void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { + m_reporter->benchmarkEnded( stats ); + } + + void RunContext::pushScopedMessage(MessageInfo const & message) { + m_messages.push_back(message); + } + + void RunContext::popScopedMessage(MessageInfo const & message) { + m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); + } + + std::string RunContext::getCurrentTestName() const { + return m_activeTestCase + ? m_activeTestCase->getTestCaseInfo().name + : std::string(); + } + + const AssertionResult * RunContext::getLastResult() const { + return &(*m_lastResult); + } + + void RunContext::exceptionEarlyReported() { + m_shouldReportUnexpected = false; + } + + void RunContext::handleFatalErrorCondition( StringRef message ) { + // First notify reporter that bad things happened + m_reporter->fatalErrorEncountered(message); + + // Don't rebuild the result -- the stringification itself can cause more fatal errors + // Instead, fake a result data. + AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); + tempResult.message = message; + AssertionResult result(m_lastAssertionInfo, tempResult); + + getResultCapture().assertionEnded(result); + + handleUnfinishedSections(); + + // Recreate section for test case (as we will lose the one that was in scope) + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); + + Counts assertions; + assertions.failed = 1; + SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); + m_reporter->sectionEnded(testCaseSectionStats); + + auto const& testInfo = m_activeTestCase->getTestCaseInfo(); + + Totals deltaTotals; + deltaTotals.testCases.failed = 1; + deltaTotals.assertions.failed = 1; + m_reporter->testCaseEnded(TestCaseStats(testInfo, + deltaTotals, + std::string(), + std::string(), + false)); + m_totals.testCases.failed++; + testGroupEnded(std::string(), m_totals, 1, 1); + m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); + } + + bool RunContext::lastAssertionPassed() { + return m_totals.assertions.passed == (m_prevPassed + 1); + } + + void RunContext::assertionPassed() { + ++m_totals.assertions.passed; + m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; + m_lastAssertionInfo.macroName = ""; + } + + void RunContext::assertionRun() { + m_prevPassed = m_totals.assertions.passed; + } + + bool RunContext::aborting() const { + return m_totals.assertions.failed == static_cast<std::size_t>(m_config->abortAfter()); + } + + void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { + auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); + SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); + m_reporter->sectionStarting(testCaseSection); + Counts prevAssertions = m_totals.assertions; + double duration = 0; + m_shouldReportUnexpected = true; + try { + m_lastAssertionInfo = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal }; + + seedRng(*m_config); + + Timer timer; + timer.start(); + if (m_reporter->getPreferences().shouldRedirectStdOut) { + StreamRedirect coutRedir(cout(), redirectedCout); + StdErrRedirect errRedir(redirectedCerr); + invokeActiveTestCase(); + } else { + invokeActiveTestCase(); + } + duration = timer.getElapsedSeconds(); + } catch (TestFailureException&) { + // This just means the test was aborted due to failure + } catch (...) { + // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions + // are reported without translation at the point of origin. + if (m_shouldReportUnexpected) { + AssertionHandler + ( m_lastAssertionInfo.macroName, + m_lastAssertionInfo.lineInfo, + m_lastAssertionInfo.capturedExpression, + m_lastAssertionInfo.resultDisposition ).useActiveException(); + } + } + m_testCaseTracker->close(); + handleUnfinishedSections(); + m_messages.clear(); + + Counts assertions = m_totals.assertions - prevAssertions; + bool missingAssertions = testForMissingAssertions(assertions); + SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); + m_reporter->sectionEnded(testCaseSectionStats); + } + + void RunContext::invokeActiveTestCase() { + FatalConditionHandler fatalConditionHandler; // Handle signals + m_activeTestCase->invoke(); + fatalConditionHandler.reset(); + } + + void RunContext::handleUnfinishedSections() { + // If sections ended prematurely due to an exception we stored their + // infos here so we can tear them down outside the unwind process. + for (auto it = m_unfinishedSections.rbegin(), + itEnd = m_unfinishedSections.rend(); + it != itEnd; + ++it) + sectionEnded(*it); + m_unfinishedSections.clear(); + } + + IResultCapture& getResultCapture() { + if (auto* capture = getCurrentContext().getResultCapture()) + return *capture; + else + CATCH_INTERNAL_ERROR("No result capture instance"); + } +} +// end catch_run_context.cpp +// start catch_section.cpp + +namespace Catch { + + Section::Section( SectionInfo const& info ) + : m_info( info ), + m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) + { + m_timer.start(); + } + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 +#endif + Section::~Section() { + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } + } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + + // This indicates whether the section should be executed or not + Section::operator bool() const { + return m_sectionIncluded; + } + +} // end namespace Catch +// end catch_section.cpp +// start catch_section_info.cpp + +namespace Catch { + + SectionInfo::SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description ) + : name( _name ), + description( _description ), + lineInfo( _lineInfo ) + {} + + SectionEndInfo::SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + +} // end namespace Catch +// end catch_section_info.cpp +// start catch_session.cpp + +// start catch_session.h + +#include <memory> + +namespace Catch { + + class Session : NonCopyable { + public: + + Session(); + ~Session() override; + + void showHelp() const; + void libIdentify(); + + int applyCommandLine( int argc, char* argv[] ); + + void useConfigData( ConfigData const& configData ); + + int run( int argc, char* argv[] ); + #if defined(WIN32) && defined(UNICODE) + int run( int argc, wchar_t* const argv[] ); + #endif + int run(); + + clara::Parser const& cli() const; + void cli( clara::Parser const& newParser ); + ConfigData& configData(); + Config& config(); + private: + int runInternal(); + + clara::Parser m_cli; + ConfigData m_configData; + std::shared_ptr<Config> m_config; + bool m_startupExceptions = false; + }; + +} // end namespace Catch + +// end catch_session.h +// start catch_version.h + +#include <iosfwd> + +namespace Catch { + + // Versioning information + struct Version { + Version( Version const& ) = delete; + Version& operator=( Version const& ) = delete; + Version( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ); + + unsigned int const majorVersion; + unsigned int const minorVersion; + unsigned int const patchNumber; + + // buildNumber is only used if branchName is not null + char const * const branchName; + unsigned int const buildNumber; + + friend std::ostream& operator << ( std::ostream& os, Version const& version ); + }; + + Version const& libraryVersion(); +} + +// end catch_version.h +#include <cstdlib> +#include <iomanip> + +namespace { + const int MaxExitCode = 255; + using Catch::IStreamingReporterPtr; + using Catch::IConfigPtr; + using Catch::Config; + + IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { + auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); + CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); + + return reporter; + } + +#ifndef CATCH_CONFIG_DEFAULT_REPORTER +#define CATCH_CONFIG_DEFAULT_REPORTER "console" +#endif + + IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { + auto const& reporterNames = config->getReporterNames(); + if (reporterNames.empty()) + return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); + + IStreamingReporterPtr reporter; + for (auto const& name : reporterNames) + addReporter(reporter, createReporter(name, config)); + return reporter; + } + +#undef CATCH_CONFIG_DEFAULT_REPORTER + + void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { + auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); + for (auto const& listener : listeners) + addReporter(reporters, listener->create(Catch::ReporterConfig(config))); + } + + Catch::Totals runTests(std::shared_ptr<Config> const& config) { + using namespace Catch; + IStreamingReporterPtr reporter = makeReporter(config); + addListeners(reporter, config); + + RunContext context(config, std::move(reporter)); + + Totals totals; + + context.testGroupStarting(config->name(), 1, 1); + + TestSpec testSpec = config->testSpec(); + if (!testSpec.hasFilters()) + testSpec = TestSpecParser(ITagAliasRegistry::get()).parse("~[.]").testSpec(); // All not hidden tests + + auto const& allTestCases = getAllTestCasesSorted(*config); + for (auto const& testCase : allTestCases) { + if (!context.aborting() && matchTest(testCase, testSpec, *config)) + totals += context.runTest(testCase); + else + context.reporter().skipTest(testCase); + } + + context.testGroupEnded(config->name(), totals, 1, 1); + return totals; + } + + void applyFilenamesAsTags(Catch::IConfig const& config) { + using namespace Catch; + auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); + for (auto& testCase : tests) { + auto tags = testCase.tags; + + std::string filename = testCase.lineInfo.file; + auto lastSlash = filename.find_last_of("\\/"); + if (lastSlash != std::string::npos) { + filename.erase(0, lastSlash); + filename[0] = '#'; + } + + auto lastDot = filename.find_last_of('.'); + if (lastDot != std::string::npos) { + filename.erase(lastDot); + } + + tags.push_back(std::move(filename)); + setTags(testCase, tags); + } + } + +} + +namespace Catch { + + Session::Session() { + static bool alreadyInstantiated = false; + if( alreadyInstantiated ) { + try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } + catch(...) { getMutableRegistryHub().registerStartupException(); } + } + + const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); + if ( !exceptions.empty() ) { + m_startupExceptions = true; + Colour colourGuard( Colour::Red ); + Catch::cerr() << "Errors occured during startup!" << '\n'; + // iterate over all exceptions and notify user + for ( const auto& ex_ptr : exceptions ) { + try { + std::rethrow_exception(ex_ptr); + } catch ( std::exception const& ex ) { + Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; + } + } + } + + alreadyInstantiated = true; + m_cli = makeCommandLineParser( m_configData ); + } + Session::~Session() { + Catch::cleanUp(); + } + + void Session::showHelp() const { + Catch::cout() + << "\nCatch v" << libraryVersion() << "\n" + << m_cli << std::endl + << "For more detailed usage please see the project docs\n" << std::endl; + } + void Session::libIdentify() { + Catch::cout() + << std::left << std::setw(16) << "description: " << "A Catch test executable\n" + << std::left << std::setw(16) << "category: " << "testframework\n" + << std::left << std::setw(16) << "framework: " << "Catch Test\n" + << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; + } + + int Session::applyCommandLine( int argc, char* argv[] ) { + if( m_startupExceptions ) + return 1; + + auto result = m_cli.parse( clara::Args( argc, argv ) ); + if( !result ) { + Catch::cerr() + << Colour( Colour::Red ) + << "\nError(s) in input:\n" + << Column( result.errorMessage() ).indent( 2 ) + << "\n\n"; + Catch::cerr() << "Run with -? for usage\n" << std::endl; + return MaxExitCode; + } + + if( m_configData.showHelp ) + showHelp(); + if( m_configData.libIdentify ) + libIdentify(); + m_config.reset(); + return 0; + } + + void Session::useConfigData( ConfigData const& configData ) { + m_configData = configData; + m_config.reset(); + } + + int Session::run( int argc, char* argv[] ) { + if( m_startupExceptions ) + return 1; + int returnCode = applyCommandLine( argc, argv ); + if( returnCode == 0 ) + returnCode = run(); + return returnCode; + } + +#if defined(WIN32) && defined(UNICODE) + int Session::run( int argc, wchar_t* const argv[] ) { + + char **utf8Argv = new char *[ argc ]; + + for ( int i = 0; i < argc; ++i ) { + int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); + + utf8Argv[ i ] = new char[ bufSize ]; + + WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); + } + + int returnCode = run( argc, utf8Argv ); + + for ( int i = 0; i < argc; ++i ) + delete [] utf8Argv[ i ]; + + delete [] utf8Argv; + + return returnCode; + } +#endif + int Session::run() { + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before starting" << std::endl; + static_cast<void>(std::getchar()); + } + int exitCode = runInternal(); + if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { + Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; + static_cast<void>(std::getchar()); + } + return exitCode; + } + + clara::Parser const& Session::cli() const { + return m_cli; + } + void Session::cli( clara::Parser const& newParser ) { + m_cli = newParser; + } + ConfigData& Session::configData() { + return m_configData; + } + Config& Session::config() { + if( !m_config ) + m_config = std::make_shared<Config>( m_configData ); + return *m_config; + } + + int Session::runInternal() { + if( m_startupExceptions ) + return 1; + + if( m_configData.showHelp || m_configData.libIdentify ) + return 0; + + try + { + config(); // Force config to be constructed + + seedRng( *m_config ); + + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); + + // Handle list request + if( Option<std::size_t> listed = list( config() ) ) + return static_cast<int>( *listed ); + + return (std::min)( MaxExitCode, static_cast<int>( runTests( m_config ).assertions.failed ) ); + } + catch( std::exception& ex ) { + Catch::cerr() << ex.what() << std::endl; + return MaxExitCode; + } + } + +} // end namespace Catch +// end catch_session.cpp +// start catch_startup_exception_registry.cpp + +namespace Catch { + void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { + try { + m_exceptions.push_back(exception); + } + catch(...) { + // If we run out of memory during start-up there's really not a lot more we can do about it + std::terminate(); + } + } + + std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept { + return m_exceptions; + } + +} // end namespace Catch +// end catch_startup_exception_registry.cpp +// start catch_stream.cpp + +#include <stdexcept> +#include <cstdio> +#include <iostream> + +namespace Catch { + + template<typename WriterF, std::size_t bufferSize=256> + class StreamBufImpl : public StreamBufBase { + char data[bufferSize]; + WriterF m_writer; + + public: + StreamBufImpl() { + setp( data, data + sizeof(data) ); + } + + ~StreamBufImpl() noexcept { + StreamBufImpl::sync(); + } + + private: + int overflow( int c ) override { + sync(); + + if( c != EOF ) { + if( pbase() == epptr() ) + m_writer( std::string( 1, static_cast<char>( c ) ) ); + else + sputc( static_cast<char>( c ) ); + } + return 0; + } + + int sync() override { + if( pbase() != pptr() ) { + m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); + setp( pbase(), epptr() ); + } + return 0; + } + }; + + /////////////////////////////////////////////////////////////////////////// + + Catch::IStream::~IStream() = default; + + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + + struct OutputDebugWriter { + + void operator()( std::string const&str ) { + writeToDebugConsole( str ); + } + }; + + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), + m_os( m_streamBuf.get() ) + {} + + std::ostream& DebugOutStream::stream() const { + return m_os; + } + + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions + std::ostream& cout() { + return std::cout; + } + std::ostream& cerr() { + return std::cerr; + } + std::ostream& clog() { + return std::clog; + } +#endif +} +// end catch_stream.cpp +// start catch_streambuf.cpp + +namespace Catch { + StreamBufBase::~StreamBufBase() = default; +} +// end catch_streambuf.cpp +// start catch_string_manip.cpp + +#include <algorithm> +#include <ostream> +#include <cstring> +#include <cctype> + +namespace Catch { + + bool startsWith( std::string const& s, std::string const& prefix ) { + return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); + } + bool startsWith( std::string const& s, char prefix ) { + return !s.empty() && s[0] == prefix; + } + bool endsWith( std::string const& s, std::string const& suffix ) { + return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); + } + bool endsWith( std::string const& s, char suffix ) { + return !s.empty() && s[s.size()-1] == suffix; + } + bool contains( std::string const& s, std::string const& infix ) { + return s.find( infix ) != std::string::npos; + } + char toLowerCh(char c) { + return static_cast<char>( std::tolower( c ) ); + } + void toLowerInPlace( std::string& s ) { + std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); + } + std::string toLower( std::string const& s ) { + std::string lc = s; + toLowerInPlace( lc ); + return lc; + } + std::string trim( std::string const& str ) { + static char const* whitespaceChars = "\n\r\t "; + std::string::size_type start = str.find_first_not_of( whitespaceChars ); + std::string::size_type end = str.find_last_not_of( whitespaceChars ); + + return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); + } + + bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { + bool replaced = false; + std::size_t i = str.find( replaceThis ); + while( i != std::string::npos ) { + replaced = true; + str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); + if( i < str.size()-withThis.size() ) + i = str.find( replaceThis, i+withThis.size() ); + else + i = std::string::npos; + } + return replaced; + } + + pluralise::pluralise( std::size_t count, std::string const& label ) + : m_count( count ), + m_label( label ) + {} + + std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { + os << pluraliser.m_count << ' ' << pluraliser.m_label; + if( pluraliser.m_count != 1 ) + os << 's'; + return os; + } + +} +// end catch_string_manip.cpp +// start catch_stringref.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +#include <ostream> +#include <cassert> +#include <cstring> + +namespace Catch { + + auto getEmptyStringRef() -> StringRef { + static StringRef s_emptyStringRef(""); + return s_emptyStringRef; + } + + StringRef::StringRef() noexcept + : StringRef( getEmptyStringRef() ) + {} + + StringRef::StringRef( StringRef const& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ) + {} + + StringRef::StringRef( StringRef&& other ) noexcept + : m_start( other.m_start ), + m_size( other.m_size ), + m_data( other.m_data ) + { + other.m_data = nullptr; + } + + StringRef::StringRef( char const* rawChars ) noexcept + : m_start( rawChars ), + m_size( static_cast<size_type>( std::strlen( rawChars ) ) ) + { + assert( rawChars != nullptr ); + } + + StringRef::StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + { + size_type rawSize = rawChars == nullptr ? 0 : static_cast<size_type>( std::strlen( rawChars ) ); + if( rawSize < size ) + m_size = rawSize; + } + + StringRef::StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + StringRef::~StringRef() noexcept { + delete[] m_data; + } + + auto StringRef::operator = ( StringRef other ) noexcept -> StringRef& { + swap( other ); + return *this; + } + StringRef::operator std::string() const { + return std::string( m_start, m_size ); + } + + void StringRef::swap( StringRef& other ) noexcept { + std::swap( m_start, other.m_start ); + std::swap( m_size, other.m_size ); + std::swap( m_data, other.m_data ); + } + + auto StringRef::c_str() const -> char const* { + if( isSubstring() ) + const_cast<StringRef*>( this )->takeOwnership(); + return m_start; + } + auto StringRef::data() const noexcept -> char const* { + return m_start; + } + + auto StringRef::isOwned() const noexcept -> bool { + return m_data != nullptr; + } + auto StringRef::isSubstring() const noexcept -> bool { + return m_start[m_size] != '\0'; + } + + void StringRef::takeOwnership() { + if( !isOwned() ) { + m_data = new char[m_size+1]; + memcpy( m_data, m_start, m_size ); + m_data[m_size] = '\0'; + m_start = m_data; + } + } + auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { + if( start < m_size ) + return StringRef( m_start+start, size ); + else + return StringRef(); + } + auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { + return + size() == other.size() && + (std::strncmp( m_start, other.m_start, size() ) == 0); + } + auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { + return !operator==( other ); + } + + auto StringRef::operator[](size_type index) const noexcept -> char { + return m_start[index]; + } + + auto StringRef::empty() const noexcept -> bool { + return m_size == 0; + } + + auto StringRef::size() const noexcept -> size_type { + return m_size; + } + auto StringRef::numberOfCharacters() const noexcept -> size_type { + size_type noChars = m_size; + // Make adjustments for uft encodings + for( size_type i=0; i < m_size; ++i ) { + char c = m_start[i]; + if( ( c & 0b11000000 ) == 0b11000000 ) { + if( ( c & 0b11100000 ) == 0b11000000 ) + noChars--; + else if( ( c & 0b11110000 ) == 0b11100000 ) + noChars-=2; + else if( ( c & 0b11111000 ) == 0b11110000 ) + noChars-=3; + } + } + return noChars; + } + + auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { + std::string str; + str.reserve( lhs.size() + rhs.size() ); + str += lhs; + str += rhs; + return str; + } + auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { + return std::string( lhs ) + std::string( rhs ); + } + + auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { + return os << str.c_str(); + } + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_stringref.cpp +// start catch_tag_alias.cpp + +namespace Catch { + TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} +} +// end catch_tag_alias.cpp +// start catch_tag_alias_autoregistrar.cpp + +namespace Catch { + + RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { + try { + getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); + } catch (...) { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + +} +// end catch_tag_alias_autoregistrar.cpp +// start catch_tag_alias_registry.cpp + +namespace Catch { + + TagAliasRegistry::~TagAliasRegistry() {} + + TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { + auto it = m_registry.find( alias ); + if( it != m_registry.end() ) + return &(it->second); + else + return nullptr; + } + + std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { + std::string expandedTestSpec = unexpandedTestSpec; + for( auto const& registryKvp : m_registry ) { + std::size_t pos = expandedTestSpec.find( registryKvp.first ); + if( pos != std::string::npos ) { + expandedTestSpec = expandedTestSpec.substr( 0, pos ) + + registryKvp.second.tag + + expandedTestSpec.substr( pos + registryKvp.first.size() ); + } + } + return expandedTestSpec; + } + + void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { + CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), + "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); + + CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, + "error: tag alias, '" << alias << "' already registered.\n" + << "\tFirst seen at: " << find(alias)->lineInfo << "\n" + << "\tRedefined at: " << lineInfo ); + } + + ITagAliasRegistry::~ITagAliasRegistry() {} + + ITagAliasRegistry const& ITagAliasRegistry::get() { + return getRegistryHub().getTagAliasRegistry(); + } + +} // end namespace Catch +// end catch_tag_alias_registry.cpp +// start catch_test_case_info.cpp + +#include <cctype> +#include <exception> +#include <algorithm> + +namespace Catch { + + TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { + if( startsWith( tag, '.' ) || + tag == "!hide" ) + return TestCaseInfo::IsHidden; + else if( tag == "!throws" ) + return TestCaseInfo::Throws; + else if( tag == "!shouldfail" ) + return TestCaseInfo::ShouldFail; + else if( tag == "!mayfail" ) + return TestCaseInfo::MayFail; + else if( tag == "!nonportable" ) + return TestCaseInfo::NonPortable; + else if( tag == "!benchmark" ) + return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); + else + return TestCaseInfo::None; + } + bool isReservedTag( std::string const& tag ) { + return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); + } + void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { + CATCH_ENFORCE( !isReservedTag(tag), + "Tag name: [" << tag << "] is not allowed.\n" + << "Tag names starting with non alpha-numeric characters are reserved\n" + << _lineInfo ); + } + + TestCase makeTestCase( ITestInvoker* _testCase, + std::string const& _className, + std::string const& _name, + std::string const& _descOrTags, + SourceLineInfo const& _lineInfo ) + { + bool isHidden = false; + + // Parse out tags + std::vector<std::string> tags; + std::string desc, tag; + bool inTag = false; + for (char c : _descOrTags) { + if( !inTag ) { + if( c == '[' ) + inTag = true; + else + desc += c; + } + else { + if( c == ']' ) { + TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); + if( ( prop & TestCaseInfo::IsHidden ) != 0 ) + isHidden = true; + else if( prop == TestCaseInfo::None ) + enforceNotReservedTag( tag, _lineInfo ); + + tags.push_back( tag ); + tag.clear(); + inTag = false; + } + else + tag += c; + } + } + if( isHidden ) { + tags.push_back( "." ); + } + + TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); + return TestCase( _testCase, info ); + } + + void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { + std::sort(begin(tags), end(tags)); + tags.erase(std::unique(begin(tags), end(tags)), end(tags)); + testCaseInfo.lcaseTags.clear(); + + for( auto const& tag : tags ) { + std::string lcaseTag = toLower( tag ); + testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.push_back( lcaseTag ); + } + testCaseInfo.tags = std::move(tags); + } + + TestCaseInfo::TestCaseInfo( std::string const& _name, + std::string const& _className, + std::string const& _description, + std::vector<std::string> const& _tags, + SourceLineInfo const& _lineInfo ) + : name( _name ), + className( _className ), + description( _description ), + lineInfo( _lineInfo ), + properties( None ) + { + setTags( *this, _tags ); + } + + bool TestCaseInfo::isHidden() const { + return ( properties & IsHidden ) != 0; + } + bool TestCaseInfo::throws() const { + return ( properties & Throws ) != 0; + } + bool TestCaseInfo::okToFail() const { + return ( properties & (ShouldFail | MayFail ) ) != 0; + } + bool TestCaseInfo::expectedToFail() const { + return ( properties & (ShouldFail ) ) != 0; + } + + std::string TestCaseInfo::tagsAsString() const { + std::string ret; + // '[' and ']' per tag + std::size_t full_size = 2 * tags.size(); + for (const auto& tag : tags) { + full_size += tag.size(); + } + ret.reserve(full_size); + for (const auto& tag : tags) { + ret.push_back('['); + ret.append(tag); + ret.push_back(']'); + } + + return ret; + } + + TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} + + TestCase TestCase::withName( std::string const& _newName ) const { + TestCase other( *this ); + other.name = _newName; + return other; + } + + void TestCase::invoke() const { + test->invoke(); + } + + bool TestCase::operator == ( TestCase const& other ) const { + return test.get() == other.test.get() && + name == other.name && + className == other.className; + } + + bool TestCase::operator < ( TestCase const& other ) const { + return name < other.name; + } + + TestCaseInfo const& TestCase::getTestCaseInfo() const + { + return *this; + } + +} // end namespace Catch +// end catch_test_case_info.cpp +// start catch_test_case_registry_impl.cpp + +#include <sstream> + +namespace Catch { + + std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { + + std::vector<TestCase> sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end() ); + break; + case RunTests::InRandomOrder: + seedRng( config ); + RandomNumberGenerator::shuffle( sorted ); + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { + std::set<TestCase> seenFunctions; + for( auto const& function : functions ) { + auto prev = seenFunctions.insert( function ); + CATCH_ENFORCE( prev.second, + "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); + } + } + + std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector<TestCase> filtered; + filtered.reserve( testCases.size() ); + for( auto const& testCase : testCases ) + if( matchTest( testCase, testSpec, config ) ) + filtered.push_back( testCase ); + return filtered; + } + std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + void TestRegistry::registerTest( TestCase const& testCase ) { + std::string name = testCase.getTestCaseInfo().name; + if( name.empty() ) { + std::ostringstream oss; + oss << "Anonymous test case " << ++m_unnamedCount; + return registerTest( testCase.withName( oss.str() ) ); + } + m_functions.push_back( testCase ); + } + + std::vector<TestCase> const& TestRegistry::getAllTests() const { + return m_functions; + } + std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); + + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); + } + return m_sortedFunctions; + } + + /////////////////////////////////////////////////////////////////////////// + TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} + + void TestInvokerAsFunction::invoke() const { + m_testAsFunction(); + } + + std::string extractClassName( std::string const& classOrQualifiedMethodName ) { + std::string className = classOrQualifiedMethodName; + if( startsWith( className, '&' ) ) + { + std::size_t lastColons = className.rfind( "::" ); + std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); + if( penultimateColons == std::string::npos ) + penultimateColons = 1; + className = className.substr( penultimateColons, lastColons-penultimateColons ); + } + return className; + } + +} // end namespace Catch +// end catch_test_case_registry_impl.cpp +// start catch_test_case_tracker.cpp + +#include <algorithm> +#include <assert.h> +#include <stdexcept> +#include <memory> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif + +namespace Catch { +namespace TestCaseTracking { + + NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) + : name( _name ), + location( _location ) + {} + + ITracker::~ITracker() = default; + + TrackerContext& TrackerContext::instance() { + static TrackerContext s_instance; + return s_instance; + } + + ITracker& TrackerContext::startRun() { + m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); + m_currentTracker = nullptr; + m_runState = Executing; + return *m_rootTracker; + } + + void TrackerContext::endRun() { + m_rootTracker.reset(); + m_currentTracker = nullptr; + m_runState = NotStarted; + } + + void TrackerContext::startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; + } + void TrackerContext::completeCycle() { + m_runState = CompletedCycle; + } + + bool TrackerContext::completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& TrackerContext::currentTracker() { + return *m_currentTracker; + } + void TrackerContext::setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + + TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} + bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { + return + tracker->nameAndLocation().name == m_nameAndLocation.name && + tracker->nameAndLocation().location == m_nameAndLocation.location; + } + + TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : m_nameAndLocation( nameAndLocation ), + m_ctx( ctx ), + m_parent( parent ) + {} + + NameAndLocation const& TrackerBase::nameAndLocation() const { + return m_nameAndLocation; + } + bool TrackerBase::isComplete() const { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + bool TrackerBase::isSuccessfullyCompleted() const { + return m_runState == CompletedSuccessfully; + } + bool TrackerBase::isOpen() const { + return m_runState != NotStarted && !isComplete(); + } + bool TrackerBase::hasChildren() const { + return !m_children.empty(); + } + + void TrackerBase::addChild( ITrackerPtr const& child ) { + m_children.push_back( child ); + } + + ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { + auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); + return( it != m_children.end() ) + ? *it + : nullptr; + } + ITracker& TrackerBase::parent() { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + void TrackerBase::openChild() { + if( m_runState != ExecutingChildren ) { + m_runState = ExecutingChildren; + if( m_parent ) + m_parent->openChild(); + } + } + + bool TrackerBase::isSectionTracker() const { return false; } + bool TrackerBase::isIndexTracker() const { return false; } + + void TrackerBase::open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); + } + + void TrackerBase::close() { + + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NeedsAnotherRun: + break; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + case NotStarted: + case CompletedSuccessfully: + case Failed: + CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); + + default: + CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); + } + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::fail() { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + void TrackerBase::markAsNeedingAnotherRun() { + m_runState = NeedsAnotherRun; + } + + void TrackerBase::moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); + } + void TrackerBase::moveToThis() { + m_ctx.setCurrentTracker( this ); + } + + SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( nameAndLocation, ctx, parent ) + { + if( parent ) { + while( !parent->isSectionTracker() ) + parent = &parent->parent(); + + SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); + addNextFilters( parentSection.m_filters ); + } + } + + bool SectionTracker::isSectionTracker() const { return true; } + + SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { + std::shared_ptr<SectionTracker> section; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isSectionTracker() ); + section = std::static_pointer_cast<SectionTracker>( childTracker ); + } + else { + section = std::make_shared<SectionTracker>( nameAndLocation, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() ) + section->tryOpen(); + return *section; + } + + void SectionTracker::tryOpen() { + if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) + open(); + } + + void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { + if( !filters.empty() ) { + m_filters.push_back(""); // Root - should never be consulted + m_filters.push_back(""); // Test Case - not a section filter + m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); + } + } + void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) { + if( filters.size() > 1 ) + m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); + } + + IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( nameAndLocation, ctx, parent ), + m_size( size ) + {} + + bool IndexTracker::isIndexTracker() const { return true; } + + IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { + std::shared_ptr<IndexTracker> tracker; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { + assert( childTracker ); + assert( childTracker->isIndexTracker() ); + tracker = std::static_pointer_cast<IndexTracker>( childTracker ); + } + else { + tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int IndexTracker::index() const { return m_index; } + + void IndexTracker::moveNext() { + m_index++; + m_children.clear(); + } + + void IndexTracker::close() { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; + +} // namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif +// end catch_test_case_tracker.cpp +// start catch_test_registry.cpp + +namespace Catch { + + auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { + return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); + } + + NameAndTags::NameAndTags( StringRef name_ , StringRef tags_ ) noexcept : name( name_ ), tags( tags_ ) {} + + AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { + try { + getMutableRegistryHub() + .registerTest( + makeTestCase( + invoker, + extractClassName( classOrMethod ), + nameAndTags.name, + nameAndTags.tags, + lineInfo)); + } catch (...) { + // Do not throw when constructing global objects, instead register the exception to be processed later + getMutableRegistryHub().registerStartupException(); + } + } + + AutoReg::~AutoReg() = default; +} +// end catch_test_registry.cpp +// start catch_test_spec.cpp + +#include <algorithm> +#include <string> +#include <vector> +#include <memory> + +namespace Catch { + + TestSpec::Pattern::~Pattern() = default; + TestSpec::NamePattern::~NamePattern() = default; + TestSpec::TagPattern::~TagPattern() = default; + TestSpec::ExcludedPattern::~ExcludedPattern() = default; + + TestSpec::NamePattern::NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} + bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { + return m_wildcardPattern.matches( toLower( testCase.name ) ); + } + + TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} + bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { + return std::find(begin(testCase.lcaseTags), + end(testCase.lcaseTags), + m_tag) != end(testCase.lcaseTags); + } + + TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} + bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } + + bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { + // All patterns in a filter must match for the filter to be a match + for( auto const& pattern : m_patterns ) { + if( !pattern->matches( testCase ) ) + return false; + } + return true; + } + + bool TestSpec::hasFilters() const { + return !m_filters.empty(); + } + bool TestSpec::matches( TestCaseInfo const& testCase ) const { + // A TestSpec matches if any filter matches + for( auto const& filter : m_filters ) + if( filter.matches( testCase ) ) + return true; + return false; + } +} +// end catch_test_spec.cpp +// start catch_test_spec_parser.cpp + +namespace Catch { + + TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} + + TestSpecParser& TestSpecParser::parse( std::string const& arg ) { + m_mode = None; + m_exclusion = false; + m_start = std::string::npos; + m_arg = m_tagAliases->expandAliases( arg ); + m_escapeChars.clear(); + for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) + visitChar( m_arg[m_pos] ); + if( m_mode == Name ) + addPattern<TestSpec::NamePattern>(); + return *this; + } + TestSpec TestSpecParser::testSpec() { + addFilter(); + return m_testSpec; + } + + void TestSpecParser::visitChar( char c ) { + if( m_mode == None ) { + switch( c ) { + case ' ': return; + case '~': m_exclusion = true; return; + case '[': return startNewMode( Tag, ++m_pos ); + case '"': return startNewMode( QuotedName, ++m_pos ); + case '\\': return escape(); + default: startNewMode( Name, m_pos ); break; + } + } + if( m_mode == Name ) { + if( c == ',' ) { + addPattern<TestSpec::NamePattern>(); + addFilter(); + } + else if( c == '[' ) { + if( subString() == "exclude:" ) + m_exclusion = true; + else + addPattern<TestSpec::NamePattern>(); + startNewMode( Tag, ++m_pos ); + } + else if( c == '\\' ) + escape(); + } + else if( m_mode == EscapedName ) + m_mode = Name; + else if( m_mode == QuotedName && c == '"' ) + addPattern<TestSpec::NamePattern>(); + else if( m_mode == Tag && c == ']' ) + addPattern<TestSpec::TagPattern>(); + } + void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { + m_mode = mode; + m_start = start; + } + void TestSpecParser::escape() { + if( m_mode == None ) + m_start = m_pos; + m_mode = EscapedName; + m_escapeChars.push_back( m_pos ); + } + std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } + + void TestSpecParser::addFilter() { + if( !m_currentFilter.m_patterns.empty() ) { + m_testSpec.m_filters.push_back( m_currentFilter ); + m_currentFilter = TestSpec::Filter(); + } + } + + TestSpec parseTestSpec( std::string const& arg ) { + return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); + } + +} // namespace Catch +// end catch_test_spec_parser.cpp +// start catch_timer.cpp + +#include <chrono> + +namespace Catch { + + auto getCurrentNanosecondsSinceEpoch() -> uint64_t { + return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); + } + + auto estimateClockResolution() -> uint64_t { + uint64_t sum = 0; + static const uint64_t iterations = 1000000; + + for( std::size_t i = 0; i < iterations; ++i ) { + + uint64_t ticks; + uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); + do { + ticks = getCurrentNanosecondsSinceEpoch(); + } + while( ticks == baseTicks ); + + auto delta = ticks - baseTicks; + sum += delta; + } + + // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers + // - and potentially do more iterations if there's a high variance. + return sum/iterations; + } + auto getEstimatedClockResolution() -> uint64_t { + static auto s_resolution = estimateClockResolution(); + return s_resolution; + } + + void Timer::start() { + m_nanoseconds = getCurrentNanosecondsSinceEpoch(); + } + auto Timer::getElapsedNanoseconds() const -> unsigned int { + return static_cast<unsigned int>(getCurrentNanosecondsSinceEpoch() - m_nanoseconds); + } + auto Timer::getElapsedMicroseconds() const -> unsigned int { + return static_cast<unsigned int>(getElapsedNanoseconds()/1000); + } + auto Timer::getElapsedMilliseconds() const -> unsigned int { + return static_cast<unsigned int>(getElapsedMicroseconds()/1000); + } + auto Timer::getElapsedSeconds() const -> double { + return getElapsedMicroseconds()/1000000.0; + } + +} // namespace Catch +// end catch_timer.cpp +// start catch_tostring.cpp + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wexit-time-destructors" +# pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +#include <iomanip> + +namespace Catch { + +namespace Detail { + + const std::string unprintableString = "{?}"; + + namespace { + const int hexThreshold = 255; + + struct Endianness { + enum Arch { Big, Little }; + + static Arch which() { + union _{ + int asInt; + char asChar[sizeof (int)]; + } u; + + u.asInt = 1; + return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; + } + }; + } + + std::string rawMemoryToString( const void *object, std::size_t size ) { + // Reverse order for little endian architectures + int i = 0, end = static_cast<int>( size ), inc = 1; + if( Endianness::which() == Endianness::Little ) { + i = end-1; + end = inc = -1; + } + + unsigned char const *bytes = static_cast<unsigned char const *>(object); + std::ostringstream os; + os << "0x" << std::setfill('0') << std::hex; + for( ; i != end; i += inc ) + os << std::setw(2) << static_cast<unsigned>(bytes[i]); + return os.str(); + } +} + +template<typename T> +std::string fpToString( T value, int precision ) { + std::ostringstream oss; + oss << std::setprecision( precision ) + << std::fixed + << value; + std::string d = oss.str(); + std::size_t i = d.find_last_not_of( '0' ); + if( i != std::string::npos && i != d.size()-1 ) { + if( d[i] == '.' ) + i++; + d = d.substr( 0, i+1 ); + } + return d; +} + +//// ======================================================= //// +// +// Out-of-line defs for full specialization of StringMaker +// +//// ======================================================= //// + +std::string StringMaker<std::string>::convert(const std::string& str) { + if (!getCurrentContext().getConfig()->showInvisibles()) { + return '"' + str + '"'; + } + + std::string s("\""); + for (char c : str) { + switch (c) { + case '\n': + s.append("\\n"); + break; + case '\t': + s.append("\\t"); + break; + default: + s.push_back(c); + break; + } + } + s.append("\""); + return s; +} + +std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) { + std::string s; + s.reserve(wstr.size()); + for (auto c : wstr) { + s += (c <= 0xff) ? static_cast<char>(c) : '?'; + } + return ::Catch::Detail::stringify(s); +} + +std::string StringMaker<char const*>::convert(char const* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<char*>::convert(char* str) { + if (str) { + return ::Catch::Detail::stringify(std::string{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} +std::string StringMaker<wchar_t *>::convert(wchar_t * str) { + if (str) { + return ::Catch::Detail::stringify(std::wstring{ str }); + } else { + return{ "{null string}" }; + } +} + +std::string StringMaker<int>::convert(int value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long>::convert(long value) { + return ::Catch::Detail::stringify(static_cast<long long>(value)); +} +std::string StringMaker<long long>::convert(long long value) { + std::ostringstream oss; + oss << value; + if (value > Detail::hexThreshold) { + oss << " (0x" << std::hex << value << ')'; + } + return oss.str(); +} + +std::string StringMaker<unsigned int>::convert(unsigned int value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long>::convert(unsigned long value) { + return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); +} +std::string StringMaker<unsigned long long>::convert(unsigned long long value) { + std::ostringstream oss; + oss << value; + if (value > Detail::hexThreshold) { + oss << " (0x" << std::hex << value << ')'; + } + return oss.str(); +} + +std::string StringMaker<bool>::convert(bool b) { + return b ? "true" : "false"; +} + +std::string StringMaker<char>::convert(char value) { + if (value == '\r') { + return "'\\r'"; + } else if (value == '\f') { + return "'\\f'"; + } else if (value == '\n') { + return "'\\n'"; + } else if (value == '\t') { + return "'\\t'"; + } else if ('\0' <= value && value < ' ') { + return ::Catch::Detail::stringify(static_cast<unsigned int>(value)); + } else { + char chstr[] = "' '"; + chstr[1] = value; + return chstr; + } +} +std::string StringMaker<signed char>::convert(signed char c) { + return ::Catch::Detail::stringify(static_cast<char>(c)); +} +std::string StringMaker<unsigned char>::convert(unsigned char c) { + return ::Catch::Detail::stringify(static_cast<char>(c)); +} + +std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { + return "nullptr"; +} + +std::string StringMaker<float>::convert(float value) { + return fpToString(value, 5) + 'f'; +} +std::string StringMaker<double>::convert(double value) { + return fpToString(value, 10); +} + +} // end namespace Catch + +#if defined(__clang__) +# pragma clang diagnostic pop +#endif + +// end catch_tostring.cpp +// start catch_totals.cpp + +namespace Catch { + + Counts Counts::operator - ( Counts const& other ) const { + Counts diff; + diff.passed = passed - other.passed; + diff.failed = failed - other.failed; + diff.failedButOk = failedButOk - other.failedButOk; + return diff; + } + + Counts& Counts::operator += ( Counts const& other ) { + passed += other.passed; + failed += other.failed; + failedButOk += other.failedButOk; + return *this; + } + + std::size_t Counts::total() const { + return passed + failed + failedButOk; + } + bool Counts::allPassed() const { + return failed == 0 && failedButOk == 0; + } + bool Counts::allOk() const { + return failed == 0; + } + + Totals Totals::operator - ( Totals const& other ) const { + Totals diff; + diff.assertions = assertions - other.assertions; + diff.testCases = testCases - other.testCases; + return diff; + } + + Totals& Totals::operator += ( Totals const& other ) { + assertions += other.assertions; + testCases += other.testCases; + return *this; + } + + Totals Totals::delta( Totals const& prevTotals ) const { + Totals diff = *this - prevTotals; + if( diff.assertions.failed > 0 ) + ++diff.testCases.failed; + else if( diff.assertions.failedButOk > 0 ) + ++diff.testCases.failedButOk; + else + ++diff.testCases.passed; + return diff; + } + +} +// end catch_totals.cpp +// start catch_version.cpp + +#include <ostream> + +namespace Catch { + + Version::Version + ( unsigned int _majorVersion, + unsigned int _minorVersion, + unsigned int _patchNumber, + char const * const _branchName, + unsigned int _buildNumber ) + : majorVersion( _majorVersion ), + minorVersion( _minorVersion ), + patchNumber( _patchNumber ), + branchName( _branchName ), + buildNumber( _buildNumber ) + {} + + std::ostream& operator << ( std::ostream& os, Version const& version ) { + os << version.majorVersion << '.' + << version.minorVersion << '.' + << version.patchNumber; + // branchName is never null -> 0th char is \0 if it is empty + if (version.branchName[0]) { + os << '-' << version.branchName + << '.' << version.buildNumber; + } + return os; + } + + Version const& libraryVersion() { + static Version version( 2, 0, 1, "", 0 ); + return version; + } + +} +// end catch_version.cpp +// start catch_wildcard_pattern.cpp + +namespace Catch { + + WildcardPattern::WildcardPattern( std::string const& pattern, + CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, '*' ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); + } + } + + bool WildcardPattern::matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + default: + CATCH_INTERNAL_ERROR( "Unknown enum" ); + } + } + + std::string WildcardPattern::adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } +} +// end catch_wildcard_pattern.cpp +// start catch_xmlwriter.cpp + +// start catch_xmlwriter.h + +#include <sstream> +#include <vector> + +namespace Catch { + + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); + + void encodeTo( std::ostream& os ) const; + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); + + private: + std::string m_str; + ForWhat m_forWhat; + }; + + class XmlWriter { + public: + + class ScopedElement { + public: + ScopedElement( XmlWriter* writer ); + + ScopedElement( ScopedElement&& other ) noexcept; + ScopedElement& operator=( ScopedElement&& other ) noexcept; + + ~ScopedElement(); + + ScopedElement& writeText( std::string const& text, bool indent = true ); + + template<typename T> + ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { + m_writer->writeAttribute( name, attribute ); + return *this; + } + + private: + mutable XmlWriter* m_writer = nullptr; + }; + + XmlWriter( std::ostream& os = Catch::cout() ); + ~XmlWriter(); + + XmlWriter( XmlWriter const& ) = delete; + XmlWriter& operator=( XmlWriter const& ) = delete; + + XmlWriter& startElement( std::string const& name ); + + ScopedElement scopedElement( std::string const& name ); + + XmlWriter& endElement(); + + XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); + + XmlWriter& writeAttribute( std::string const& name, bool attribute ); + + template<typename T> + XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { + m_oss.clear(); + m_oss.str(std::string()); + m_oss << attribute; + return writeAttribute( name, m_oss.str() ); + } + + XmlWriter& writeText( std::string const& text, bool indent = true ); + + XmlWriter& writeComment( std::string const& text ); + + void writeStylesheetRef( std::string const& url ); + + XmlWriter& writeBlankLine(); + + void ensureTagClosed(); + + private: + + void writeDeclaration(); + + void newlineIfNecessary(); + + bool m_tagIsOpen = false; + bool m_needsNewline = false; + std::vector<std::string> m_tags; + std::string m_indent; + std::ostream& m_os; + std::ostringstream m_oss; + }; + +} + +// end catch_xmlwriter.h +#include <iomanip> + +namespace Catch { + + XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) + : m_str( str ), + m_forWhat( forWhat ) + {} - std::string className = stats.testInfo.className; + void XmlEncode::encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 and + // by @mrpi PR #588 + if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { + // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 + os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) + << static_cast<int>( c ); + } + else + os << c; + } + } + } + + std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) + : m_writer( writer ) + {} + + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + : m_writer( other.m_writer ){ + other.m_writer = nullptr; + } + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + if ( m_writer ) { + m_writer->endElement(); + } + m_writer = other.m_writer; + other.m_writer = nullptr; + return *this; + } + + XmlWriter::ScopedElement::~ScopedElement() { + if( m_writer ) + m_writer->endElement(); + } + + XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { + m_writer->writeText( text, indent ); + return *this; + } + + XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) + { + writeDeclaration(); + } + + XmlWriter::~XmlWriter() { + while( !m_tags.empty() ) + endElement(); + } + + XmlWriter& XmlWriter::startElement( std::string const& name ) { + ensureTagClosed(); + newlineIfNecessary(); + m_os << m_indent << '<' << name; + m_tags.push_back( name ); + m_indent += " "; + m_tagIsOpen = true; + return *this; + } + + XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { + ScopedElement scoped( this ); + startElement( name ); + return scoped; + } + + XmlWriter& XmlWriter::endElement() { + newlineIfNecessary(); + m_indent = m_indent.substr( 0, m_indent.size()-2 ); + if( m_tagIsOpen ) { + m_os << "/>"; + m_tagIsOpen = false; + } + else { + m_os << m_indent << "</" << m_tags.back() << ">"; + } + m_os << std::endl; + m_tags.pop_back(); + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { + if( !name.empty() && !attribute.empty() ) + m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { + m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; + return *this; + } + + XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { + if( !text.empty() ){ + bool tagWasOpen = m_tagIsOpen; + ensureTagClosed(); + if( tagWasOpen && indent ) + m_os << m_indent; + m_os << XmlEncode( text ); + m_needsNewline = true; + } + return *this; + } + + XmlWriter& XmlWriter::writeComment( std::string const& text ) { + ensureTagClosed(); + m_os << m_indent << "<!--" << text << "-->"; + m_needsNewline = true; + return *this; + } + + void XmlWriter::writeStylesheetRef( std::string const& url ) { + m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; + } + + XmlWriter& XmlWriter::writeBlankLine() { + ensureTagClosed(); + m_os << '\n'; + return *this; + } + + void XmlWriter::ensureTagClosed() { + if( m_tagIsOpen ) { + m_os << ">" << std::endl; + m_tagIsOpen = false; + } + } + + void XmlWriter::writeDeclaration() { + m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; + } + + void XmlWriter::newlineIfNecessary() { + if( m_needsNewline ) { + m_os << std::endl; + m_needsNewline = false; + } + } +} +// end catch_xmlwriter.cpp +// start catch_reporter_bases.cpp + +#include <cstring> +#include <cfloat> +#include <cstdio> +#include <assert.h> +#include <memory> + +namespace Catch { + void prepareExpandedExpression(AssertionResult& result) { + result.getExpandedExpression(); + } + + // Because formatting using c++ streams is stateful, drop down to C is required + // Alternatively we could use stringstream, but its performance is... not good. + std::string getFormattedDuration( double duration ) { + // Max exponent + 1 is required to represent the whole part + // + 1 for decimal point + // + 3 for the 3 decimal places + // + 1 for null terminator + const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; + char buffer[maxDoubleSize]; + + // Save previous errno, to prevent sprintf from overwriting it + ErrnoGuard guard; +#ifdef _MSC_VER + sprintf_s(buffer, "%.3f", duration); +#else + sprintf(buffer, "%.3f", duration); +#endif + return std::string(buffer); + } + + TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) + :StreamingReporterBase(_config) {} + + void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} + + bool TestEventListenerBase::assertionEnded(AssertionStats const &) { + return false; + } + +} // end namespace Catch +// end catch_reporter_bases.cpp +// start catch_reporter_compact.cpp + +namespace { + +#ifdef CATCH_PLATFORM_MAC + const char* failedString() { return "FAILED"; } + const char* passedString() { return "PASSED"; } +#else + const char* failedString() { return "failed"; } + const char* passedString() { return "passed"; } +#endif + + // Colour::LightGrey + Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } + + std::string bothOrAll( std::size_t count ) { + return count == 1 ? std::string() : + count == 2 ? "both " : "all " ; + } +} + +namespace Catch { + + struct CompactReporter : StreamingReporterBase<CompactReporter> { + + using StreamingReporterBase::StreamingReporterBase; + + ~CompactReporter() override; + + static std::string getDescription() { + return "Reports test results on a single line, suitable for IDEs"; + } + + ReporterPreferences getPreferences() const override { + ReporterPreferences prefs; + prefs.shouldRedirectStdOut = false; + return prefs; + } + + void noMatchingTestCases( std::string const& spec ) override { + stream << "No test cases matched '" << spec << '\'' << std::endl; + } + + void assertionStarting( AssertionInfo const& ) override {} + + bool assertionEnded( AssertionStats const& _assertionStats ) override { + AssertionResult const& result = _assertionStats.assertionResult; + + bool printInfoMessages = true; + + // Drop out if result was successful and we're not printing those + if( !m_config->includeSuccessfulResults() && result.isOk() ) { + if( result.getResultType() != ResultWas::Warning ) + return false; + printInfoMessages = false; + } + + AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + printer.print(); + + stream << std::endl; + return true; + } + + void sectionEnded(SectionStats const& _sectionStats) override { + if (m_config->showDurations() == ShowDurations::Always) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } + } + + void testRunEnded( TestRunStats const& _testRunStats ) override { + printTotals( _testRunStats.totals ); + stream << '\n' << std::endl; + StreamingReporterBase::testRunEnded( _testRunStats ); + } + + private: + class AssertionPrinter { + public: + AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; + AssertionPrinter( AssertionPrinter const& ) = delete; + AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) + : stream( _stream ) + , result( _stats.assertionResult ) + , messages( _stats.infoMessages ) + , itMessage( _stats.infoMessages.begin() ) + , printInfoMessages( _printInfoMessages ) + {} + + void print() { + printSourceInfo(); + + itMessage = messages.begin(); + + switch( result.getResultType() ) { + case ResultWas::Ok: + printResultType( Colour::ResultSuccess, passedString() ); + printOriginalExpression(); + printReconstructedExpression(); + if ( ! result.hasExpression() ) + printRemainingMessages( Colour::None ); + else + printRemainingMessages(); + break; + case ResultWas::ExpressionFailed: + if( result.isOk() ) + printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); + else + printResultType( Colour::Error, failedString() ); + printOriginalExpression(); + printReconstructedExpression(); + printRemainingMessages(); + break; + case ResultWas::ThrewException: + printResultType( Colour::Error, failedString() ); + printIssue( "unexpected exception with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::FatalErrorCondition: + printResultType( Colour::Error, failedString() ); + printIssue( "fatal error condition with message:" ); + printMessage(); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::DidntThrowException: + printResultType( Colour::Error, failedString() ); + printIssue( "expected exception, got none" ); + printExpressionWas(); + printRemainingMessages(); + break; + case ResultWas::Info: + printResultType( Colour::None, "info" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::Warning: + printResultType( Colour::None, "warning" ); + printMessage(); + printRemainingMessages(); + break; + case ResultWas::ExplicitFailure: + printResultType( Colour::Error, failedString() ); + printIssue( "explicitly" ); + printRemainingMessages( Colour::None ); + break; + // These cases are here to prevent compiler warnings + case ResultWas::Unknown: + case ResultWas::FailureBit: + case ResultWas::Exception: + printResultType( Colour::Error, "** internal error **" ); + break; + } + } + + private: + void printSourceInfo() const { + Colour colourGuard( Colour::FileName ); + stream << result.getSourceInfo() << ':'; + } + + void printResultType( Colour::Code colour, std::string const& passOrFail ) const { + if( !passOrFail.empty() ) { + { + Colour colourGuard( colour ); + stream << ' ' << passOrFail; + } + stream << ':'; + } + } + + void printIssue( std::string const& issue ) const { + stream << ' ' << issue; + } + + void printExpressionWas() { + if( result.hasExpression() ) { + stream << ';'; + { + Colour colour( dimColour() ); + stream << " expression was:"; + } + printOriginalExpression(); + } + } + + void printOriginalExpression() const { + if( result.hasExpression() ) { + stream << ' ' << result.getExpression(); + } + } + + void printReconstructedExpression() const { + if( result.hasExpandedExpression() ) { + { + Colour colour( dimColour() ); + stream << " for: "; + } + stream << result.getExpandedExpression(); + } + } - if( className.empty() ) { - if( rootSection.childSections.empty() ) - className = "global"; + void printMessage() { + if ( itMessage != messages.end() ) { + stream << " '" << itMessage->message << '\''; + ++itMessage; + } } - writeSection( className, "", rootSection ); - } - void writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { - std::string name = trim( sectionNode.stats.sectionInfo.name ); - if( !rootName.empty() ) - name = rootName + "/" + name; + void printRemainingMessages( Colour::Code colour = dimColour() ) { + if ( itMessage == messages.end() ) + return; - if( !sectionNode.assertions.empty() || - !sectionNode.stdOut.empty() || - !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - if( className.empty() ) { - xml.writeAttribute( "classname", name ); - xml.writeAttribute( "name", "root" ); + // using messages.end() directly yields (or auto) compilation error: + std::vector<MessageInfo>::const_iterator itEnd = messages.end(); + const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) ); + + { + Colour colourGuard( colour ); + stream << " with " << pluralise( N, "message" ) << ':'; } - else { - xml.writeAttribute( "classname", className ); - xml.writeAttribute( "name", name ); + + for(; itMessage != itEnd; ) { + // If this assertion is a warning ignore any INFO messages + if( printInfoMessages || itMessage->type != ResultWas::Info ) { + stream << " '" << itMessage->message << '\''; + if ( ++itMessage != itEnd ) { + Colour colourGuard( dimColour() ); + stream << " and"; + } + } } - xml.writeAttribute( "time", Catch::toString( sectionNode.stats.durationInSeconds ) ); + } - writeAssertions( sectionNode ); + private: + std::ostream& stream; + AssertionResult const& result; + std::vector<MessageInfo> messages; + std::vector<MessageInfo>::const_iterator itMessage; + bool printInfoMessages; + }; - if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); - if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + // Colour, message variants: + // - white: No tests ran. + // - red: Failed [both/all] N test cases, failed [both/all] M assertions. + // - white: Passed [both/all] N test cases (no assertions). + // - red: Failed N tests cases, failed M assertions. + // - green: Passed [both/all] N tests cases with M assertions. + + void printTotals( const Totals& totals ) const { + if( totals.testCases.total() == 0 ) { + stream << "No tests ran."; + } + else if( totals.testCases.failed == totals.testCases.total() ) { + Colour colour( Colour::ResultError ); + const std::string qualify_assertions_failed = + totals.assertions.failed == totals.assertions.total() ? + bothOrAll( totals.assertions.failed ) : std::string(); + stream << + "Failed " << bothOrAll( totals.testCases.failed ) + << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << qualify_assertions_failed << + pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else if( totals.assertions.total() == 0 ) { + stream << + "Passed " << bothOrAll( totals.testCases.total() ) + << pluralise( totals.testCases.total(), "test case" ) + << " (no assertions)."; + } + else if( totals.assertions.failed ) { + Colour colour( Colour::ResultError ); + stream << + "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " + "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; + } + else { + Colour colour( Colour::ResultSuccess ); + stream << + "Passed " << bothOrAll( totals.testCases.passed ) + << pluralise( totals.testCases.passed, "test case" ) << + " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; } - for( SectionNode::ChildSections::const_iterator - it = sectionNode.childSections.begin(), - itEnd = sectionNode.childSections.end(); - it != itEnd; - ++it ) - if( className.empty() ) - writeSection( name, "", **it ); - else - writeSection( className, name, **it ); } + }; - void writeAssertions( SectionNode const& sectionNode ) { - for( SectionNode::Assertions::const_iterator - it = sectionNode.assertions.begin(), itEnd = sectionNode.assertions.end(); - it != itEnd; - ++it ) - writeAssertion( *it ); + CompactReporter::~CompactReporter() {} + + CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + +} // end namespace Catch +// end catch_reporter_compact.cpp +// start catch_reporter_console.cpp + +#include <cfloat> +#include <cstdio> + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled +#endif + +namespace Catch { + + namespace { + std::size_t makeRatio( std::size_t number, std::size_t total ) { + std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; + return ( ratio == 0 && number > 0 ) ? 1 : ratio; } - void writeAssertion( AssertionStats const& stats ) { - AssertionResult const& result = stats.assertionResult; - if( !result.isOk() ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - elementName = "failure"; - break; - case ResultWas::ExpressionFailed: - elementName = "failure"; - break; - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } + std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { + if( i > j && i > k ) + return i; + else if( j > k ) + return j; + else + return k; + } - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + struct ColumnInfo { + enum Justification { Left, Right }; + std::string name; + int width; + Justification justification; + }; + struct ColumnBreak {}; + struct RowBreak {}; - xml.writeAttribute( "message", result.getExpandedExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); + class TablePrinter { + std::ostream& m_os; + std::vector<ColumnInfo> m_columnInfos; + std::ostringstream m_oss; + int m_currentColumn = -1; + bool m_isOpen = false; - std::ostringstream oss; - if( !result.getMessage().empty() ) - oss << result.getMessage() << "\n"; - for( std::vector<MessageInfo>::const_iterator - it = stats.infoMessages.begin(), - itEnd = stats.infoMessages.end(); - it != itEnd; - ++it ) - if( it->type == ResultWas::Info ) - oss << it->message << "\n"; + public: + TablePrinter( std::ostream& os, std::vector<ColumnInfo> const& columnInfos ) + : m_os( os ), + m_columnInfos( columnInfos ) + {} - oss << "at " << result.getSourceInfo(); - xml.writeText( oss.str(), false ); + auto columnInfos() const -> std::vector<ColumnInfo> const& { + return m_columnInfos; } - } - XmlWriter xml; - Timer suiteTimer; - std::ostringstream stdOutForSuite; - std::ostringstream stdErrForSuite; - unsigned int unexpectedExceptions; - }; + void open() { + if( !m_isOpen ) { + m_isOpen = true; + *this << RowBreak(); + for( auto const& info : m_columnInfos ) + *this << info.name << ColumnBreak(); + *this << RowBreak(); + m_os << Catch::getLineOfChars<'-'>() << "\n"; + } + } + void close() { + if( m_isOpen ) { + *this << RowBreak(); + m_os << std::endl; + m_isOpen = false; + } + } - INTERNAL_CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + template<typename T> + friend TablePrinter& operator << ( TablePrinter& tp, T const& value ) { + tp.m_oss << value; + return tp; + } + + friend TablePrinter& operator << ( TablePrinter& tp, ColumnBreak ) { + auto colStr = tp.m_oss.str(); + // This takes account of utf8 encodings + auto strSize = Catch::StringRef( colStr ).numberOfCharacters(); + tp.m_oss.str(""); + tp.open(); + if( tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size()-1) ) { + tp.m_currentColumn = -1; + tp.m_os << "\n"; + } + tp.m_currentColumn++; + + auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; + auto padding = ( strSize+2 < static_cast<std::size_t>( colInfo.width ) ) + ? std::string( colInfo.width-(strSize+2), ' ' ) + : std::string(); + if( colInfo.justification == ColumnInfo::Left ) + tp.m_os << colStr << padding << " "; + else + tp.m_os << padding << colStr << " "; + return tp; + } -} // end namespace Catch + friend TablePrinter& operator << ( TablePrinter& tp, RowBreak ) { + if( tp.m_currentColumn > 0 ) { + tp.m_os << "\n"; + tp.m_currentColumn = -1; + } + return tp; + } + }; -// #included from: ../reporters/catch_reporter_console.hpp -#define TWOBLUECUBES_CATCH_REPORTER_CONSOLE_HPP_INCLUDED + class Duration { + enum class Unit { + Auto, + Nanoseconds, + Microseconds, + Milliseconds, + Seconds, + Minutes + }; + static const uint64_t s_nanosecondsInAMicrosecond = 1000; + static const uint64_t s_nanosecondsInAMillisecond = 1000*s_nanosecondsInAMicrosecond; + static const uint64_t s_nanosecondsInASecond = 1000*s_nanosecondsInAMillisecond; + static const uint64_t s_nanosecondsInAMinute = 60*s_nanosecondsInASecond; -namespace Catch { + uint64_t m_inNanoseconds; + Unit m_units; - struct ConsoleReporter : StreamingReporterBase { - ConsoleReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_headerPrinted( false ) - {} + public: + Duration( uint64_t inNanoseconds, Unit units = Unit::Auto ) + : m_inNanoseconds( inNanoseconds ), + m_units( units ) + { + if( m_units == Unit::Auto ) { + if( m_inNanoseconds < s_nanosecondsInAMicrosecond ) + m_units = Unit::Nanoseconds; + else if( m_inNanoseconds < s_nanosecondsInAMillisecond ) + m_units = Unit::Microseconds; + else if( m_inNanoseconds < s_nanosecondsInASecond ) + m_units = Unit::Milliseconds; + else if( m_inNanoseconds < s_nanosecondsInAMinute ) + m_units = Unit::Seconds; + else + m_units = Unit::Minutes; + } + + } + + auto value() const -> double { + switch( m_units ) { + case Unit::Microseconds: + return m_inNanoseconds / static_cast<double>( s_nanosecondsInAMicrosecond ); + case Unit::Milliseconds: + return m_inNanoseconds / static_cast<double>( s_nanosecondsInAMillisecond ); + case Unit::Seconds: + return m_inNanoseconds / static_cast<double>( s_nanosecondsInASecond ); + case Unit::Minutes: + return m_inNanoseconds / static_cast<double>( s_nanosecondsInAMinute ); + default: + return static_cast<double>( m_inNanoseconds ); + } + } + auto unitsAsString() const -> std::string { + switch( m_units ) { + case Unit::Nanoseconds: + return "ns"; + case Unit::Microseconds: + return "µs"; + case Unit::Milliseconds: + return "ms"; + case Unit::Seconds: + return "s"; + case Unit::Minutes: + return "m"; + default: + return "** internal error **"; + } - virtual ~ConsoleReporter(); + } + friend auto operator << ( std::ostream& os, Duration const& duration ) -> std::ostream& { + return os << duration.value() << " " << duration.unitsAsString(); + } + }; + } // end anon namespace + + struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> { + TablePrinter m_tablePrinter; + + ConsoleReporter( ReporterConfig const& config ) + : StreamingReporterBase( config ), + m_tablePrinter( config.stream(), + { + { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH-32, ColumnInfo::Left }, + { "iters", 8, ColumnInfo::Right }, + { "elapsed ns", 14, ColumnInfo::Right }, + { "average", 14, ColumnInfo::Right } + } ) + {} + ~ConsoleReporter() override; static std::string getDescription() { return "Reports test results as plain lines of text"; } - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } - virtual void noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << "'" << std::endl; + void noMatchingTestCases( std::string const& spec ) override { + stream << "No test cases matched '" << spec << '\'' << std::endl; } - virtual void assertionStarting( AssertionInfo const& ) { + void assertionStarting( AssertionInfo const& ) override { } - virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + bool assertionEnded( AssertionStats const& _assertionStats ) override { AssertionResult const& result = _assertionStats.assertionResult; - bool printInfoMessages = true; + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - // Drop out if result was successful and we're not printing those - if( !m_config->includeSuccessfulResults() && result.isOk() ) { - if( result.getResultType() != ResultWas::Warning ) - return false; - printInfoMessages = false; - } + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return false; lazyPrint(); - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); + AssertionPrinter printer( stream, _assertionStats, includeResults ); printer.print(); stream << std::endl; return true; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + void sectionStarting( SectionInfo const& _sectionInfo ) override { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& _sectionStats ) { + void sectionEnded( SectionStats const& _sectionStats ) override { + m_tablePrinter.close(); if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); @@ -8505,32 +10731,53 @@ namespace Catch { stream << "\nNo assertions in test case"; stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; } + if( m_config->showDurations() == ShowDurations::Always ) { + stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; + } if( m_headerPrinted ) { - if( m_config->showDurations() == ShowDurations::Always ) - stream << "Completed in " << _sectionStats.durationInSeconds << "s" << std::endl; m_headerPrinted = false; } - else { - if( m_config->showDurations() == ShowDurations::Always ) - stream << _sectionStats.sectionInfo.name << " completed in " << _sectionStats.durationInSeconds << "s" << std::endl; - } StreamingReporterBase::sectionEnded( _sectionStats ); } - virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + void benchmarkStarting( BenchmarkInfo const& info ) override { + lazyPrintWithoutClosingBenchmarkTable(); + + auto nameCol = Column( info.name ).width( m_tablePrinter.columnInfos()[0].width-2 ); + + bool firstLine = true; + for( auto line : nameCol ) { + if( !firstLine ) + m_tablePrinter << ColumnBreak() << ColumnBreak() << ColumnBreak(); + else + firstLine = false; + + m_tablePrinter << line << ColumnBreak(); + } + } + void benchmarkEnded( BenchmarkStats const& stats ) override { + Duration average( stats.elapsedTimeInNanoseconds/stats.iterations ); + m_tablePrinter + << stats.iterations << ColumnBreak() + << stats.elapsedTimeInNanoseconds << ColumnBreak() + << average << ColumnBreak(); + } + + void testCaseEnded( TestCaseStats const& _testCaseStats ) override { + m_tablePrinter.close(); StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } - virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + void testGroupEnded( TestGroupStats const& _testGroupStats ) override { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; printTotals( _testGroupStats.totals ); - stream << "\n" << std::endl; + stream << '\n' << std::endl; } StreamingReporterBase::testGroupEnded( _testGroupStats ); } - virtual void testRunEnded( TestRunStats const& _testRunStats ) { + void testRunEnded( TestRunStats const& _testRunStats ) override { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; @@ -8540,8 +10787,9 @@ namespace Catch { private: class AssertionPrinter { - void operator= ( AssertionPrinter const& ); public: + AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; + AssertionPrinter( AssertionPrinter const& ) = delete; AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) : stream( _stream ), stats( _stats ), @@ -8578,7 +10826,11 @@ namespace Catch { case ResultWas::ThrewException: colour = Colour::Error; passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with message"; + messageLabel = "due to unexpected exception with "; + if (_stats.infoMessages.size() == 1) + messageLabel += "message"; + if (_stats.infoMessages.size() > 1) + messageLabel += "messages"; break; case ResultWas::FatalErrorCondition: colour = Colour::Error; @@ -8618,13 +10870,13 @@ namespace Catch { printSourceInfo(); if( stats.totals.assertions.total() > 0 ) { if( result.isOk() ) - stream << "\n"; + stream << '\n'; printResultType(); printOriginalExpression(); printReconstructedExpression(); } else { - stream << "\n"; + stream << '\n'; } printMessage(); } @@ -8641,25 +10893,23 @@ namespace Catch { Colour colourGuard( Colour::OriginalExpression ); stream << " "; stream << result.getExpressionInMacro(); - stream << "\n"; + stream << '\n'; } } void printReconstructedExpression() const { if( result.hasExpandedExpression() ) { stream << "with expansion:\n"; Colour colourGuard( Colour::ReconstructedExpression ); - stream << Text( result.getExpandedExpression(), TextAttributes().setIndent(2) ) << "\n"; + stream << Column( result.getExpandedExpression() ).indent(2) << '\n'; } } void printMessage() const { if( !messageLabel.empty() ) - stream << messageLabel << ":" << "\n"; - for( std::vector<MessageInfo>::const_iterator it = messages.begin(), itEnd = messages.end(); - it != itEnd; - ++it ) { + stream << messageLabel << ':' << '\n'; + for( auto const& msg : messages ) { // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || it->type != ResultWas::Info ) - stream << Text( it->message, TextAttributes().setIndent(2) ) << "\n"; + if( printInfoMessages || msg.type != ResultWas::Info ) + stream << Column( msg.message ).indent(2) << '\n'; } } void printSourceInfo() const { @@ -8680,6 +10930,12 @@ namespace Catch { void lazyPrint() { + m_tablePrinter.close(); + lazyPrintWithoutClosingBenchmarkTable(); + } + + void lazyPrintWithoutClosingBenchmarkTable() { + if( !currentTestRunInfo.used ) lazyPrintRunInfo(); if( !currentGroupInfo.used ) @@ -8691,10 +10947,10 @@ namespace Catch { } } void lazyPrintRunInfo() { - stream << "\n" << getLineOfChars<'~'>() << "\n"; + stream << '\n' << getLineOfChars<'~'>() << '\n'; Colour colour( Colour::SecondaryText ); stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion << " host application.\n" + << " is a Catch v" << libraryVersion() << " host application.\n" << "Run with -? for options\n\n"; if( m_config->rngSeed() != 0 ) @@ -8715,29 +10971,29 @@ namespace Catch { if( m_sectionStack.size() > 1 ) { Colour colourGuard( Colour::Headers ); - std::vector<SectionInfo>::const_iterator + auto it = m_sectionStack.begin()+1, // Skip first section (test case) itEnd = m_sectionStack.end(); for( ; it != itEnd; ++it ) printHeaderString( it->name, 2 ); } - SourceLineInfo lineInfo = m_sectionStack.front().lineInfo; + SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; if( !lineInfo.empty() ){ - stream << getLineOfChars<'-'>() << "\n"; + stream << getLineOfChars<'-'>() << '\n'; Colour colourGuard( Colour::FileName ); - stream << lineInfo << "\n"; + stream << lineInfo << '\n'; } - stream << getLineOfChars<'.'>() << "\n" << std::endl; + stream << getLineOfChars<'.'>() << '\n' << std::endl; } void printClosedHeader( std::string const& _name ) { printOpenHeader( _name ); - stream << getLineOfChars<'.'>() << "\n"; + stream << getLineOfChars<'.'>() << '\n'; } void printOpenHeader( std::string const& _name ) { - stream << getLineOfChars<'-'>() << "\n"; + stream << getLineOfChars<'-'>() << '\n'; { Colour colourGuard( Colour::Headers ); printHeaderString( _name ); @@ -8752,9 +11008,7 @@ namespace Catch { i+=2; else i = 0; - stream << Text( _string, TextAttributes() - .setIndent( indent+i) - .setInitialIndent( indent ) ) << "\n"; + stream << Column( _string ).indent( indent+i ).initialIndent( indent ) << '\n'; } struct SummaryColumn { @@ -8767,11 +11021,11 @@ namespace Catch { std::ostringstream oss; oss << count; std::string row = oss.str(); - for( std::vector<std::string>::iterator it = rows.begin(); it != rows.end(); ++it ) { - while( it->size() < row.size() ) - *it = " " + *it; - while( it->size() > row.size() ) - row = " " + row; + for( auto& oldRow : rows ) { + while( oldRow.size() < row.size() ) + oldRow = ' ' + oldRow; + while( oldRow.size() > row.size() ) + row = ' ' + row; } rows.push_back( row ); return *this; @@ -8787,12 +11041,12 @@ namespace Catch { if( totals.testCases.total() == 0 ) { stream << Colour( Colour::Warning ) << "No tests ran\n"; } - else if( totals.assertions.total() > 0 && totals.assertions.allPassed() ) { + else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { stream << Colour( Colour::ResultSuccess ) << "All tests passed"; stream << " (" << pluralise( totals.assertions.passed, "assertion" ) << " in " - << pluralise( totals.testCases.passed, "test case" ) << ")" - << "\n"; + << pluralise( totals.testCases.passed, "test case" ) << ')' + << '\n'; } else { @@ -8815,9 +11069,9 @@ namespace Catch { } } void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) { - for( std::vector<SummaryColumn>::const_iterator it = cols.begin(); it != cols.end(); ++it ) { - std::string value = it->rows[row]; - if( it->label.empty() ) { + for( auto col : cols ) { + std::string value = col.rows[row]; + if( col.label.empty() ) { stream << label << ": "; if( value != "0" ) stream << value; @@ -8826,24 +11080,11 @@ namespace Catch { } else if( value != "0" ) { stream << Colour( Colour::LightGrey ) << " | "; - stream << Colour( it->colour ) - << value << " " << it->label; + stream << Colour( col.colour ) + << value << ' ' << col.label; } } - stream << "\n"; - } - - static std::size_t makeRatio( std::size_t number, std::size_t total ) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; - return ( ratio == 0 && number > 0 ) ? 1 : ratio; - } - static std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { - if( i > j && i > k ) - return i; - else if( j > k ) - return j; - else - return k; + stream << '\n'; } void printTotalsDivider( Totals const& totals ) { @@ -8866,371 +11107,612 @@ namespace Catch { else { stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); } - stream << "\n"; + stream << '\n'; } void printSummaryDivider() { - stream << getLineOfChars<'-'>() << "\n"; + stream << getLineOfChars<'-'>() << '\n'; + } + + private: + bool m_headerPrinted = false; + }; + + CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + + ConsoleReporter::~ConsoleReporter() {} + +} // end namespace Catch + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_console.cpp +// start catch_reporter_junit.cpp + +#include <assert.h> + +#include <ctime> +#include <algorithm> + +namespace Catch { + + namespace { + std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + +#ifdef _MSC_VER + std::tm timeInfo = {}; + gmtime_s(&timeInfo, &rawtime); +#else + std::tm* timeInfo; + timeInfo = std::gmtime(&rawtime); +#endif + + char timeStamp[timeStampSize]; + const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; + +#ifdef _MSC_VER + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); +#else + std::strftime(timeStamp, timeStampSize, fmt, timeInfo); +#endif + return std::string(timeStamp); } - private: - bool m_headerPrinted; - }; + std::string fileNameTag(const std::vector<std::string> &tags) { + auto it = std::find_if(begin(tags), + end(tags), + [] (std::string const& tag) {return tag.front() == '#'; }); + if (it != tags.end()) + return it->substr(1); + return std::string(); + } + } - INTERNAL_CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) + class JunitReporter : public CumulativeReporterBase<JunitReporter> { + public: + JunitReporter( ReporterConfig const& _config ) + : CumulativeReporterBase( _config ), + xml( _config.stream() ) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } -} // end namespace Catch + ~JunitReporter() override; -// #included from: ../reporters/catch_reporter_compact.hpp -#define TWOBLUECUBES_CATCH_REPORTER_COMPACT_HPP_INCLUDED + static std::string getDescription() { + return "Reports test results in an XML format that looks like Ant's junitreport target"; + } -namespace Catch { + void noMatchingTestCases( std::string const& /*spec*/ ) override {} - struct CompactReporter : StreamingReporterBase { + void testRunStarting( TestRunInfo const& runInfo ) override { + CumulativeReporterBase::testRunStarting( runInfo ); + xml.startElement( "testsuites" ); + } - CompactReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ) - {} + void testGroupStarting( GroupInfo const& groupInfo ) override { + suiteTimer.start(); + stdOutForSuite.str(""); + stdErrForSuite.str(""); + unexpectedExceptions = 0; + CumulativeReporterBase::testGroupStarting( groupInfo ); + } - virtual ~CompactReporter(); + void testCaseStarting( TestCaseInfo const& testCaseInfo ) override { + m_okToFail = testCaseInfo.okToFail(); + } + bool assertionEnded( AssertionStats const& assertionStats ) override { + if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) + unexpectedExceptions++; + return CumulativeReporterBase::assertionEnded( assertionStats ); + } - static std::string getDescription() { - return "Reports test results on a single line, suitable for IDEs"; + void testCaseEnded( TestCaseStats const& testCaseStats ) override { + stdOutForSuite << testCaseStats.stdOut; + stdErrForSuite << testCaseStats.stdErr; + CumulativeReporterBase::testCaseEnded( testCaseStats ); } - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; + void testGroupEnded( TestGroupStats const& testGroupStats ) override { + double suiteTime = suiteTimer.getElapsedSeconds(); + CumulativeReporterBase::testGroupEnded( testGroupStats ); + writeGroup( *m_testGroups.back(), suiteTime ); } - virtual void noMatchingTestCases( std::string const& spec ) { - stream << "No test cases matched '" << spec << "'" << std::endl; + void testRunEndedCumulative() override { + xml.endElement(); } - virtual void assertionStarting( AssertionInfo const& ) { + void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); + TestGroupStats const& stats = groupNode.value; + xml.writeAttribute( "name", stats.groupInfo.name ); + xml.writeAttribute( "errors", unexpectedExceptions ); + xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); + xml.writeAttribute( "tests", stats.totals.assertions.total() ); + xml.writeAttribute( "hostname", "tbd" ); // !TBD + if( m_config->showDurations() == ShowDurations::Never ) + xml.writeAttribute( "time", "" ); + else + xml.writeAttribute( "time", suiteTime ); + xml.writeAttribute( "timestamp", getCurrentTimestamp() ); + + // Write test cases + for( auto const& child : groupNode.children ) + writeTestCase( *child ); + + xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); + xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); } - virtual bool assertionEnded( AssertionStats const& _assertionStats ) { - AssertionResult const& result = _assertionStats.assertionResult; + void writeTestCase( TestCaseNode const& testCaseNode ) { + TestCaseStats const& stats = testCaseNode.value; - bool printInfoMessages = true; + // All test cases have exactly one section - which represents the + // test case itself. That section may have 0-n nested sections + assert( testCaseNode.children.size() == 1 ); + SectionNode const& rootSection = *testCaseNode.children.front(); - // Drop out if result was successful and we're not printing those - if( !m_config->includeSuccessfulResults() && result.isOk() ) { - if( result.getResultType() != ResultWas::Warning ) - return false; - printInfoMessages = false; + std::string className = stats.testInfo.className; + + if( className.empty() ) { + className = fileNameTag(stats.testInfo.tags); + if ( className.empty() ) + className = "global"; } - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); - printer.print(); + if ( !m_config->name().empty() ) + className = m_config->name() + "." + className; - stream << std::endl; - return true; + writeSection( className, "", rootSection ); } - virtual void testRunEnded( TestRunStats const& _testRunStats ) { - printTotals( _testRunStats.totals ); - stream << "\n" << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } + void writeSection( std::string const& className, + std::string const& rootName, + SectionNode const& sectionNode ) { + std::string name = trim( sectionNode.stats.sectionInfo.name ); + if( !rootName.empty() ) + name = rootName + '/' + name; - private: - class AssertionPrinter { - void operator= ( AssertionPrinter const& ); - public: - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ) - , stats( _stats ) - , result( _stats.assertionResult ) - , messages( _stats.infoMessages ) - , itMessage( _stats.infoMessages.begin() ) - , printInfoMessages( _printInfoMessages ) - {} + if( !sectionNode.assertions.empty() || + !sectionNode.stdOut.empty() || + !sectionNode.stdErr.empty() ) { + XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); + if( className.empty() ) { + xml.writeAttribute( "classname", name ); + xml.writeAttribute( "name", "root" ); + } + else { + xml.writeAttribute( "classname", className ); + xml.writeAttribute( "name", name ); + } + xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); - void print() { - printSourceInfo(); + writeAssertions( sectionNode ); - itMessage = messages.begin(); + if( !sectionNode.stdOut.empty() ) + xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); + if( !sectionNode.stdErr.empty() ) + xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); + } + for( auto const& childNode : sectionNode.childSections ) + if( className.empty() ) + writeSection( name, "", *childNode ); + else + writeSection( className, name, *childNode ); + } + void writeAssertions( SectionNode const& sectionNode ) { + for( auto const& assertion : sectionNode.assertions ) + writeAssertion( assertion ); + } + void writeAssertion( AssertionStats const& stats ) { + AssertionResult const& result = stats.assertionResult; + if( !result.isOk() ) { + std::string elementName; switch( result.getResultType() ) { - case ResultWas::Ok: - printResultType( Colour::ResultSuccess, passedString() ); - printOriginalExpression(); - printReconstructedExpression(); - if ( ! result.hasExpression() ) - printRemainingMessages( Colour::None ); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) - printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); - else - printResultType( Colour::Error, failedString() ); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; case ResultWas::ThrewException: - printResultType( Colour::Error, failedString() ); - printIssue( "unexpected exception with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; case ResultWas::FatalErrorCondition: - printResultType( Colour::Error, failedString() ); - printIssue( "fatal error condition with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); + elementName = "error"; + break; + case ResultWas::ExplicitFailure: + elementName = "failure"; + break; + case ResultWas::ExpressionFailed: + elementName = "failure"; break; case ResultWas::DidntThrowException: - printResultType( Colour::Error, failedString() ); - printIssue( "expected exception, got none" ); - printExpressionWas(); - printRemainingMessages(); + elementName = "failure"; break; + + // We should never see these here: case ResultWas::Info: - printResultType( Colour::None, "info" ); - printMessage(); - printRemainingMessages(); - break; case ResultWas::Warning: - printResultType( Colour::None, "warning" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType( Colour::Error, failedString() ); - printIssue( "explicitly" ); - printRemainingMessages( Colour::None ); - break; - // These cases are here to prevent compiler warnings + case ResultWas::Ok: case ResultWas::Unknown: case ResultWas::FailureBit: case ResultWas::Exception: - printResultType( Colour::Error, "** internal error **" ); + elementName = "internalError"; break; } + + XmlWriter::ScopedElement e = xml.scopedElement( elementName ); + + xml.writeAttribute( "message", result.getExpandedExpression() ); + xml.writeAttribute( "type", result.getTestMacroName() ); + + std::ostringstream oss; + if( !result.getMessage().empty() ) + oss << result.getMessage() << '\n'; + for( auto const& msg : stats.infoMessages ) + if( msg.type == ResultWas::Info ) + oss << msg.message << '\n'; + + oss << "at " << result.getSourceInfo(); + xml.writeText( oss.str(), false ); } + } + + XmlWriter xml; + Timer suiteTimer; + std::ostringstream stdOutForSuite; + std::ostringstream stdErrForSuite; + unsigned int unexpectedExceptions = 0; + bool m_okToFail = false; + }; + + JunitReporter::~JunitReporter() {} + CATCH_REGISTER_REPORTER( "junit", JunitReporter ) + +} // end namespace Catch +// end catch_reporter_junit.cpp +// start catch_reporter_multi.cpp + +namespace Catch { + + void MultipleReporters::add( IStreamingReporterPtr&& reporter ) { + m_reporters.push_back( std::move( reporter ) ); + } + + ReporterPreferences MultipleReporters::getPreferences() const { + return m_reporters[0]->getPreferences(); + } + + std::set<Verbosity> MultipleReporters::getSupportedVerbosities() { + return std::set<Verbosity>{ }; + } + + void MultipleReporters::noMatchingTestCases( std::string const& spec ) { + for( auto const& reporter : m_reporters ) + reporter->noMatchingTestCases( spec ); + } + + void MultipleReporters::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { + for( auto const& reporter : m_reporters ) + reporter->benchmarkStarting( benchmarkInfo ); + } + void MultipleReporters::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { + for( auto const& reporter : m_reporters ) + reporter->benchmarkEnded( benchmarkStats ); + } + + void MultipleReporters::testRunStarting( TestRunInfo const& testRunInfo ) { + for( auto const& reporter : m_reporters ) + reporter->testRunStarting( testRunInfo ); + } + + void MultipleReporters::testGroupStarting( GroupInfo const& groupInfo ) { + for( auto const& reporter : m_reporters ) + reporter->testGroupStarting( groupInfo ); + } + + void MultipleReporters::testCaseStarting( TestCaseInfo const& testInfo ) { + for( auto const& reporter : m_reporters ) + reporter->testCaseStarting( testInfo ); + } + + void MultipleReporters::sectionStarting( SectionInfo const& sectionInfo ) { + for( auto const& reporter : m_reporters ) + reporter->sectionStarting( sectionInfo ); + } + + void MultipleReporters::assertionStarting( AssertionInfo const& assertionInfo ) { + for( auto const& reporter : m_reporters ) + reporter->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + bool MultipleReporters::assertionEnded( AssertionStats const& assertionStats ) { + bool clearBuffer = false; + for( auto const& reporter : m_reporters ) + clearBuffer |= reporter->assertionEnded( assertionStats ); + return clearBuffer; + } + + void MultipleReporters::sectionEnded( SectionStats const& sectionStats ) { + for( auto const& reporter : m_reporters ) + reporter->sectionEnded( sectionStats ); + } + + void MultipleReporters::testCaseEnded( TestCaseStats const& testCaseStats ) { + for( auto const& reporter : m_reporters ) + reporter->testCaseEnded( testCaseStats ); + } + + void MultipleReporters::testGroupEnded( TestGroupStats const& testGroupStats ) { + for( auto const& reporter : m_reporters ) + reporter->testGroupEnded( testGroupStats ); + } + + void MultipleReporters::testRunEnded( TestRunStats const& testRunStats ) { + for( auto const& reporter : m_reporters ) + reporter->testRunEnded( testRunStats ); + } + + void MultipleReporters::skipTest( TestCaseInfo const& testInfo ) { + for( auto const& reporter : m_reporters ) + reporter->skipTest( testInfo ); + } - private: - // Colour::LightGrey + bool MultipleReporters::isMulti() const { + return true; + } - static Colour::Code dimColour() { return Colour::FileName; } +} // end namespace Catch +// end catch_reporter_multi.cpp +// start catch_reporter_xml.cpp -#ifdef CATCH_PLATFORM_MAC - static const char* failedString() { return "FAILED"; } - static const char* passedString() { return "PASSED"; } -#else - static const char* failedString() { return "failed"; } - static const char* passedString() { return "passed"; } +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch + // Note that 4062 (not all labels are handled + // and default is missing) is enabled #endif - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ":"; - } +namespace Catch { + class XmlReporter : public StreamingReporterBase<XmlReporter> { + public: + XmlReporter( ReporterConfig const& _config ) + : StreamingReporterBase( _config ), + m_xml(_config.stream()) + { + m_reporterPrefs.shouldRedirectStdOut = true; + } - void printResultType( Colour::Code colour, std::string passOrFail ) const { - if( !passOrFail.empty() ) { - { - Colour colourGuard( colour ); - stream << " " << passOrFail; - } - stream << ":"; - } - } + ~XmlReporter() override; + + static std::string getDescription() { + return "Reports test results as an XML document"; + } + + virtual std::string getStylesheetRef() const { + return std::string(); + } + + void writeSourceInfo( SourceLineInfo const& sourceInfo ) { + m_xml + .writeAttribute( "filename", sourceInfo.file ) + .writeAttribute( "line", sourceInfo.line ); + } + + public: // StreamingReporterBase + + void noMatchingTestCases( std::string const& s ) override { + StreamingReporterBase::noMatchingTestCases( s ); + } + + void testRunStarting( TestRunInfo const& testInfo ) override { + StreamingReporterBase::testRunStarting( testInfo ); + std::string stylesheetRef = getStylesheetRef(); + if( !stylesheetRef.empty() ) + m_xml.writeStylesheetRef( stylesheetRef ); + m_xml.startElement( "Catch" ); + if( !m_config->name().empty() ) + m_xml.writeAttribute( "name", m_config->name() ); + } + + void testGroupStarting( GroupInfo const& groupInfo ) override { + StreamingReporterBase::testGroupStarting( groupInfo ); + m_xml.startElement( "Group" ) + .writeAttribute( "name", groupInfo.name ); + } + + void testCaseStarting( TestCaseInfo const& testInfo ) override { + StreamingReporterBase::testCaseStarting(testInfo); + m_xml.startElement( "TestCase" ) + .writeAttribute( "name", trim( testInfo.name ) ) + .writeAttribute( "description", testInfo.description ) + .writeAttribute( "tags", testInfo.tagsAsString() ); + + writeSourceInfo( testInfo.lineInfo ); + + if ( m_config->showDurations() == ShowDurations::Always ) + m_testCaseTimer.start(); + m_xml.ensureTagClosed(); + } - void printIssue( std::string issue ) const { - stream << " " << issue; + void sectionStarting( SectionInfo const& sectionInfo ) override { + StreamingReporterBase::sectionStarting( sectionInfo ); + if( m_sectionDepth++ > 0 ) { + m_xml.startElement( "Section" ) + .writeAttribute( "name", trim( sectionInfo.name ) ) + .writeAttribute( "description", sectionInfo.description ); + writeSourceInfo( sectionInfo.lineInfo ); + m_xml.ensureTagClosed(); } + } - void printExpressionWas() { - if( result.hasExpression() ) { - stream << ";"; - { - Colour colour( dimColour() ); - stream << " expression was:"; + void assertionStarting( AssertionInfo const& ) override { } + + bool assertionEnded( AssertionStats const& assertionStats ) override { + + AssertionResult const& result = assertionStats.assertionResult; + + bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); + + if( includeResults ) { + // Print any info messages in <Info> tags. + for( auto const& msg : assertionStats.infoMessages ) { + if( msg.type == ResultWas::Info ) { + m_xml.scopedElement( "Info" ) + .writeText( msg.message ); + } else if ( msg.type == ResultWas::Warning ) { + m_xml.scopedElement( "Warning" ) + .writeText( msg.message ); } - printOriginalExpression(); } } - void printOriginalExpression() const { - if( result.hasExpression() ) { - stream << " " << result.getExpression(); - } - } + // Drop out if result was successful but we're not printing them. + if( !includeResults && result.getResultType() != ResultWas::Warning ) + return true; - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - { - Colour colour( dimColour() ); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } + // Print the expression if there is one. + if( result.hasExpression() ) { + m_xml.startElement( "Expression" ) + .writeAttribute( "success", result.succeeded() ) + .writeAttribute( "type", result.getTestMacroName() ); + + writeSourceInfo( result.getSourceInfo() ); + + m_xml.scopedElement( "Original" ) + .writeText( result.getExpression() ); + m_xml.scopedElement( "Expanded" ) + .writeText( result.getExpandedExpression() ); } - void printMessage() { - if ( itMessage != messages.end() ) { - stream << " '" << itMessage->message << "'"; - ++itMessage; - } + // And... Print a result applicable to each result type. + switch( result.getResultType() ) { + case ResultWas::ThrewException: + m_xml.startElement( "Exception" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::FatalErrorCondition: + m_xml.startElement( "FatalErrorCondition" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + case ResultWas::Info: + m_xml.scopedElement( "Info" ) + .writeText( result.getMessage() ); + break; + case ResultWas::Warning: + // Warning will already have been written + break; + case ResultWas::ExplicitFailure: + m_xml.startElement( "Failure" ); + writeSourceInfo( result.getSourceInfo() ); + m_xml.writeText( result.getMessage() ); + m_xml.endElement(); + break; + default: + break; } - void printRemainingMessages( Colour::Code colour = dimColour() ) { - if ( itMessage == messages.end() ) - return; + if( result.hasExpression() ) + m_xml.endElement(); - // using messages.end() directly yields compilation error: - std::vector<MessageInfo>::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) ); + return true; + } - { - Colour colourGuard( colour ); - stream << " with " << pluralise( N, "message" ) << ":"; - } + void sectionEnded( SectionStats const& sectionStats ) override { + StreamingReporterBase::sectionEnded( sectionStats ); + if( --m_sectionDepth > 0 ) { + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); + e.writeAttribute( "successes", sectionStats.assertions.passed ); + e.writeAttribute( "failures", sectionStats.assertions.failed ); + e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); - for(; itMessage != itEnd; ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || itMessage->type != ResultWas::Info ) { - stream << " '" << itMessage->message << "'"; - if ( ++itMessage != itEnd ) { - Colour colourGuard( dimColour() ); - stream << " and"; - } - } - } + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); + + m_xml.endElement(); } + } - private: - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - std::vector<MessageInfo> messages; - std::vector<MessageInfo>::const_iterator itMessage; - bool printInfoMessages; - }; + void testCaseEnded( TestCaseStats const& testCaseStats ) override { + StreamingReporterBase::testCaseEnded( testCaseStats ); + XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); + e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); - // Colour, message variants: - // - white: No tests ran. - // - red: Failed [both/all] N test cases, failed [both/all] M assertions. - // - white: Passed [both/all] N test cases (no assertions). - // - red: Failed N tests cases, failed M assertions. - // - green: Passed [both/all] N tests cases with M assertions. + if ( m_config->showDurations() == ShowDurations::Always ) + e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); + + if( !testCaseStats.stdOut.empty() ) + m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); + if( !testCaseStats.stdErr.empty() ) + m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); - std::string bothOrAll( std::size_t count ) const { - return count == 1 ? "" : count == 2 ? "both " : "all " ; + m_xml.endElement(); } - void printTotals( const Totals& totals ) const { - if( totals.testCases.total() == 0 ) { - stream << "No tests ran."; - } - else if( totals.testCases.failed == totals.testCases.total() ) { - Colour colour( Colour::ResultError ); - const std::string qualify_assertions_failed = - totals.assertions.failed == totals.assertions.total() ? - bothOrAll( totals.assertions.failed ) : ""; - stream << - "Failed " << bothOrAll( totals.testCases.failed ) - << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << qualify_assertions_failed << - pluralise( totals.assertions.failed, "assertion" ) << "."; - } - else if( totals.assertions.total() == 0 ) { - stream << - "Passed " << bothOrAll( totals.testCases.total() ) - << pluralise( totals.testCases.total(), "test case" ) - << " (no assertions)."; - } - else if( totals.assertions.failed ) { - Colour colour( Colour::ResultError ); - stream << - "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << pluralise( totals.assertions.failed, "assertion" ) << "."; - } - else { - Colour colour( Colour::ResultSuccess ); - stream << - "Passed " << bothOrAll( totals.testCases.passed ) - << pluralise( totals.testCases.passed, "test case" ) << - " with " << pluralise( totals.assertions.passed, "assertion" ) << "."; - } + void testGroupEnded( TestGroupStats const& testGroupStats ) override { + StreamingReporterBase::testGroupEnded( testGroupStats ); + // TODO: Check testGroupStats.aborting and act accordingly. + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) + .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); + m_xml.endElement(); + } + + void testRunEnded( TestRunStats const& testRunStats ) override { + StreamingReporterBase::testRunEnded( testRunStats ); + m_xml.scopedElement( "OverallResults" ) + .writeAttribute( "successes", testRunStats.totals.assertions.passed ) + .writeAttribute( "failures", testRunStats.totals.assertions.failed ) + .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); + m_xml.endElement(); } + + private: + Timer m_testCaseTimer; + XmlWriter m_xml; + int m_sectionDepth = 0; }; - INTERNAL_CATCH_REGISTER_REPORTER( "compact", CompactReporter ) + XmlReporter::~XmlReporter() {} + CATCH_REGISTER_REPORTER( "xml", XmlReporter ) } // end namespace Catch +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +// end catch_reporter_xml.cpp + namespace Catch { - NonCopyable::~NonCopyable() {} - IShared::~IShared() {} - StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} - IContext::~IContext() {} - IResultCapture::~IResultCapture() {} - ITestCase::~ITestCase() {} - ITestCaseRegistry::~ITestCaseRegistry() {} - IRegistryHub::~IRegistryHub() {} - IMutableRegistryHub::~IMutableRegistryHub() {} - IExceptionTranslator::~IExceptionTranslator() {} - IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() {} - IReporter::~IReporter() {} - IReporterFactory::~IReporterFactory() {} - IReporterRegistry::~IReporterRegistry() {} - IStreamingReporter::~IStreamingReporter() {} - AssertionStats::~AssertionStats() {} - SectionStats::~SectionStats() {} - TestCaseStats::~TestCaseStats() {} - TestGroupStats::~TestGroupStats() {} - TestRunStats::~TestRunStats() {} - CumulativeReporterBase::SectionNode::~SectionNode() {} - CumulativeReporterBase::~CumulativeReporterBase() {} - - StreamingReporterBase::~StreamingReporterBase() {} - ConsoleReporter::~ConsoleReporter() {} - CompactReporter::~CompactReporter() {} - IRunner::~IRunner() {} - IMutableContext::~IMutableContext() {} - IConfig::~IConfig() {} - XmlReporter::~XmlReporter() {} - JunitReporter::~JunitReporter() {} - TestRegistry::~TestRegistry() {} - FreeFunctionTestCase::~FreeFunctionTestCase() {} - IGeneratorInfo::~IGeneratorInfo() {} - IGeneratorsForTest::~IGeneratorsForTest() {} - TestSpec::Pattern::~Pattern() {} - TestSpec::NamePattern::~NamePattern() {} - TestSpec::TagPattern::~TagPattern() {} - TestSpec::ExcludedPattern::~ExcludedPattern() {} - - Matchers::Impl::StdString::Equals::~Equals() {} - Matchers::Impl::StdString::Contains::~Contains() {} - Matchers::Impl::StdString::StartsWith::~StartsWith() {} - Matchers::Impl::StdString::EndsWith::~EndsWith() {} - - void Config::dummy() {} + LeakDetector leakDetector; } #ifdef __clang__ #pragma clang diagnostic pop #endif +// end catch_impl.hpp #endif #ifdef CATCH_CONFIG_MAIN -// #included from: internal/catch_default_main.hpp -#define TWOBLUECUBES_CATCH_DEFAULT_MAIN_HPP_INCLUDED +// start catch_default_main.hpp #ifndef __OBJC__ +#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) +// Standard C/C++ Win32 Unicode wmain entry point +extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { +#else // Standard C/C++ main entry point -int main (int argc, char * const argv[]) { +int main (int argc, char * argv[]) { +#endif + return Catch::Session().run( argc, argv ); } @@ -9243,7 +11725,7 @@ int main (int argc, char * const argv[]) { #endif Catch::registerTestMethods(); - int result = Catch::Session().run( argc, (char* const*)argv ); + int result = Catch::Session().run( argc, (char**)argv ); #if !CATCH_ARC_ENABLED [pool drain]; @@ -9254,153 +11736,265 @@ int main (int argc, char * const argv[]) { #endif // __OBJC__ +// end catch_default_main.hpp #endif #ifdef CLARA_CONFIG_MAIN_NOT_DEFINED # undef CLARA_CONFIG_MAIN #endif +#if !defined(CATCH_CONFIG_DISABLE) ////// - // If this config identifier is defined then all CATCH macros are prefixed with CATCH_ #ifdef CATCH_CONFIG_PREFIX_ALL -#define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) -#define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) - -#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) -#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) -#define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) - -#define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) -#define CATCH_CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CATCH_CHECK_FALSE" ) -#define CATCH_CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_IF" ) -#define CATCH_CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECKED_ELSE" ) -#define CATCH_CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CATCH_CHECK_NOFAIL" ) - -#define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) -#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) -#define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) - -#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) -#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THAT" ) - -#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) -#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "CATCH_WARN", msg ) -#define CATCH_SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "CATCH_INFO" ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) -#define CATCH_SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CATCH_CAPTURE" ) - -#ifdef CATCH_CONFIG_VARIADIC_MACROS - #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) - #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) - #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) - #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) - #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) -#else - #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) - #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) - #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) - #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) - #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) -#endif -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) - -#define CATCH_REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) -#define CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) - -#define CATCH_GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) +#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) +#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() // "BDD-style" convenience wrappers -#ifdef CATCH_CONFIG_VARIADIC_MACROS #define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) #define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#else -#define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) -#define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) -#endif -#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) -#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) -#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else -#define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) -#define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) - -#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) -#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) -#define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) - -#define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) -#define CHECK_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, "CHECK_FALSE" ) -#define CHECKED_IF( expr ) INTERNAL_CATCH_IF( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_IF" ) -#define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) -#define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) - -#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) -#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) -#define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) - -#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) -#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::Normal, "REQUIRE_THAT" ) - -#define INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) -#define WARN( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, "WARN", msg ) -#define SCOPED_INFO( msg ) INTERNAL_CATCH_INFO( msg, "INFO" ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) -#define SCOPED_CAPTURE( msg ) INTERNAL_CATCH_INFO( #msg " := " << msg, "CAPTURE" ) - -#ifdef CATCH_CONFIG_VARIADIC_MACROS - #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) - #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) - #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) - #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) - #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) - #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) -#else - #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) - #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) - #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) - #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) - #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) - #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) -#endif -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE( "", "" ) - -#define REGISTER_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) -#define REGISTER_LEGACY_REPORTER( name, reporterType ) INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) - -#define GENERATE( expr) INTERNAL_CATCH_GENERATE( expr ) +#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) + +#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) + +#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) +#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) + +#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) + +#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) +#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) +#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) +#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) +#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) +#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) +#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) +#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() #endif #define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) // "BDD-style" convenience wrappers -#ifdef CATCH_CONFIG_VARIADIC_MACROS #define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) #define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) + +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc ) + +using Catch::Detail::Approx; + +#else +////// +// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ +#ifdef CATCH_CONFIG_PREFIX_ALL + +#define CATCH_REQUIRE( ... ) (void)(0) +#define CATCH_REQUIRE_FALSE( ... ) (void)(0) + +#define CATCH_REQUIRE_THROWS( ... ) (void)(0) +#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif// CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) + +#define CATCH_CHECK( ... ) (void)(0) +#define CATCH_CHECK_FALSE( ... ) (void)(0) +#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) +#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CATCH_CHECK_NOFAIL( ... ) (void)(0) + +#define CATCH_CHECK_THROWS( ... ) (void)(0) +#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CATCH_CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) + +#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define CATCH_INFO( msg ) (void)(0) +#define CATCH_WARN( msg ) (void)(0) +#define CATCH_CAPTURE( msg ) (void)(0) + +#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_METHOD_AS_TEST_CASE( method, ... ) +#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define CATCH_SECTION( ... ) +#define CATCH_FAIL( ... ) (void)(0) +#define CATCH_FAIL_CHECK( ... ) (void)(0) +#define CATCH_SUCCEED( ... ) (void)(0) + +#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + +// "BDD-style" convenience wrappers +#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) +#define CATCH_GIVEN( desc ) +#define CATCH_WHEN( desc ) +#define CATCH_AND_WHEN( desc ) +#define CATCH_THEN( desc ) +#define CATCH_AND_THEN( desc ) + +// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else -#define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) -#define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) + +#define REQUIRE( ... ) (void)(0) +#define REQUIRE_FALSE( ... ) (void)(0) + +#define REQUIRE_THROWS( ... ) (void)(0) +#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) +#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define REQUIRE_NOTHROW( ... ) (void)(0) + +#define CHECK( ... ) (void)(0) +#define CHECK_FALSE( ... ) (void)(0) +#define CHECKED_IF( ... ) if (__VA_ARGS__) +#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) +#define CHECK_NOFAIL( ... ) (void)(0) + +#define CHECK_THROWS( ... ) (void)(0) +#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) +#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS +#define CHECK_NOTHROW( ... ) (void)(0) + +#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) +#define CHECK_THAT( arg, matcher ) (void)(0) + +#define REQUIRE_THAT( arg, matcher ) (void)(0) +#endif // CATCH_CONFIG_DISABLE_MATCHERS + +#define INFO( msg ) (void)(0) +#define WARN( msg ) (void)(0) +#define CAPTURE( msg ) (void)(0) + +#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) +#define METHOD_AS_TEST_CASE( method, ... ) +#define REGISTER_TEST_CASE( Function, ... ) (void)(0) +#define SECTION( ... ) +#define FAIL( ... ) (void)(0) +#define FAIL_CHECK( ... ) (void)(0) +#define SUCCEED( ... ) (void)(0) +#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) + #endif -#define GIVEN( desc ) SECTION( " Given: " desc, "" ) -#define WHEN( desc ) SECTION( " When: " desc, "" ) -#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) -#define THEN( desc ) SECTION( " Then: " desc, "" ) -#define AND_THEN( desc ) SECTION( " And: " desc, "" ) + +#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) + +// "BDD-style" convenience wrappers +#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) +#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) + +#define GIVEN( desc ) +#define WHEN( desc ) +#define AND_WHEN( desc ) +#define THEN( desc ) +#define AND_THEN( desc ) using Catch::Detail::Approx; -// #included from: internal/catch_reenable_warnings.h +#endif + +// start catch_reenable_warnings.h -#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro @@ -9412,5 +12006,7 @@ using Catch::Detail::Approx; # pragma GCC diagnostic pop #endif +// end catch_reenable_warnings.h +// end catch.hpp #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED From 2b71e8922dbd51798e4c42c2b4b6feb2306721c1 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 29 Nov 2017 10:19:06 +0100 Subject: [PATCH 028/871] Implemented symbol table and visitor for populating symbol table from AST. - Every AST node now has isXXX method so that we can check node types instead of setting that as a property again in Symbol table. - Added enum-flags library for c++11 enums as bitfields - Added symbol_properties, symbol and symbol table classes - Symbol table pretty printer added (squashed from local branch sandbox/kumbhar/symtab) Change-Id: Ic35028b4463e87dac92fa1f18d7bddeb7eaa2ecd NMODL Repo SHA: BlueBrain/nmodl@6f4090b475c98cd2fd396d0c124fd82c26eca25a --- cmake/nmodl/CMakeLists.txt | 1 + src/nmodl/ast/ast_utils.hpp | 519 +++++++++++++++++- src/nmodl/ext/flags/LICENSE | 21 + src/nmodl/ext/flags/allow_flags.hpp | 24 + src/nmodl/ext/flags/flags.hpp | 309 +++++++++++ src/nmodl/ext/flags/flagsfwd.hpp | 8 + src/nmodl/ext/flags/iterator.hpp | 86 +++ src/nmodl/language/CMakeLists.txt | 2 + src/nmodl/language/ast_printer.py | 20 +- src/nmodl/language/code_generator.py | 10 + src/nmodl/language/utils.py | 7 +- src/nmodl/language/visitors_printer.py | 102 +++- src/nmodl/lexer/main.cpp | 2 +- src/nmodl/lexer/modtoken.cpp | 8 +- src/nmodl/lexer/modtoken.hpp | 4 +- src/nmodl/symtab/CMakeLists.txt | 30 + src/nmodl/symtab/symbol.cpp | 32 ++ src/nmodl/symtab/symbol.hpp | 122 ++++ src/nmodl/symtab/symbol_properties.cpp | 142 +++++ src/nmodl/symtab/symbol_properties.hpp | 185 +++++++ src/nmodl/symtab/symbol_table.cpp | 357 ++++++++++++ src/nmodl/symtab/symbol_table.hpp | 209 +++++++ src/nmodl/visitors/CMakeLists.txt | 7 +- src/nmodl/visitors/main.cpp | 20 + src/nmodl/visitors/symtab_visitor_helper.hpp | 57 ++ test/nmodl/transpiler/CMakeLists.txt | 3 + .../transpiler/symtab/symbol_properties.cpp | 43 ++ 27 files changed, 2314 insertions(+), 16 deletions(-) create mode 100644 src/nmodl/ext/flags/LICENSE create mode 100644 src/nmodl/ext/flags/allow_flags.hpp create mode 100644 src/nmodl/ext/flags/flags.hpp create mode 100644 src/nmodl/ext/flags/flagsfwd.hpp create mode 100644 src/nmodl/ext/flags/iterator.hpp create mode 100644 src/nmodl/symtab/CMakeLists.txt create mode 100644 src/nmodl/symtab/symbol.cpp create mode 100644 src/nmodl/symtab/symbol.hpp create mode 100644 src/nmodl/symtab/symbol_properties.cpp create mode 100644 src/nmodl/symtab/symbol_properties.hpp create mode 100644 src/nmodl/symtab/symbol_table.cpp create mode 100644 src/nmodl/symtab/symbol_table.hpp create mode 100644 src/nmodl/visitors/symtab_visitor_helper.hpp create mode 100644 test/nmodl/transpiler/symtab/symbol_properties.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 0dc77b2f8c..34a7fde097 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -49,6 +49,7 @@ add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) add_subdirectory(src/printer) +add_subdirectory(src/symtab) add_subdirectory(src/utils) add_subdirectory(src/visitors) diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index b50300a131..6a543cb2de 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -87,7 +87,524 @@ namespace ast { return nullptr; } - virtual ~AST() {} + virtual ~AST() { + } + + virtual bool isAST() { + return true; + } + + virtual bool isStatement() { + return false; + } + + virtual bool isExpression() { + return false; + } + + virtual bool isBlock() { + return false; + } + + virtual bool isIdentifier() { + return false; + } + + virtual bool isNumber() { + return false; + } + + virtual bool isString() { + return false; + } + + virtual bool isInteger() { + return false; + } + + virtual bool isFloat() { + return false; + } + + virtual bool isDouble() { + return false; + } + + virtual bool isBoolean() { + return false; + } + + virtual bool isName() { + return false; + } + + virtual bool isPrimeName() { + return false; + } + + virtual bool isVarName() { + return false; + } + + virtual bool isIndexedName() { + return false; + } + + virtual bool isArgument() { + return false; + } + + virtual bool isReactVarName() { + return false; + } + + virtual bool isReadIonVar() { + return false; + } + + virtual bool isWriteIonVar() { + return false; + } + + virtual bool isNonspeCurVar() { + return false; + } + + virtual bool isElectrodeCurVar() { + return false; + } + + virtual bool isSectionVar() { + return false; + } + + virtual bool isRangeVar() { + return false; + } + + virtual bool isGlobalVar() { + return false; + } + + virtual bool isPointerVar() { + return false; + } + + virtual bool isBbcorePointerVar() { + return false; + } + + virtual bool isExternVar() { + return false; + } + + virtual bool isThreadsafeVar() { + return false; + } + + virtual bool isParamBlock() { + return false; + } + + virtual bool isStepBlock() { + return false; + } + + virtual bool isIndependentBlock() { + return false; + } + + virtual bool isDependentBlock() { + return false; + } + + virtual bool isStateBlock() { + return false; + } + + virtual bool isPlotBlock() { + return false; + } + + virtual bool isInitialBlock() { + return false; + } + + virtual bool isConstructorBlock() { + return false; + } + + virtual bool isDestructorBlock() { + return false; + } + + virtual bool isStatementBlock() { + return false; + } + + virtual bool isDerivativeBlock() { + return false; + } + + virtual bool isLinearBlock() { + return false; + } + + virtual bool isNonLinearBlock() { + return false; + } + + virtual bool isDiscreteBlock() { + return false; + } + + virtual bool isPartialBlock() { + return false; + } + + virtual bool isFunctionTableBlock() { + return false; + } + + virtual bool isFunctionBlock() { + return false; + } + + virtual bool isProcedureBlock() { + return false; + } + + virtual bool isNetReceiveBlock() { + return false; + } + + virtual bool isSolveBlock() { + return false; + } + + virtual bool isBreakpointBlock() { + return false; + } + + virtual bool isTerminalBlock() { + return false; + } + + virtual bool isBeforeBlock() { + return false; + } + + virtual bool isAfterBlock() { + return false; + } + + virtual bool isBABlock() { + return false; + } + + virtual bool isForNetcon() { + return false; + } + + virtual bool isKineticBlock() { + return false; + } + + virtual bool isMatchBlock() { + return false; + } + + virtual bool isUnitBlock() { + return false; + } + + virtual bool isConstantBlock() { + return false; + } + + virtual bool isNeuronBlock() { + return false; + } + + virtual bool isUnit() { + return false; + } + + virtual bool isDoubleUnit() { + return false; + } + + virtual bool isLocalVar() { + return false; + } + + virtual bool isLimits() { + return false; + } + + virtual bool isNumberRange() { + return false; + } + + virtual bool isPlotVar() { + return false; + } + + virtual bool isBinaryOperator() { + return false; + } + + virtual bool isUnaryOperator() { + return false; + } + + virtual bool isReactionOperator() { + return false; + } + + virtual bool isBinaryExpression() { + return false; + } + + virtual bool isUnaryExpression() { + return false; + } + + virtual bool isNonLinEuation() { + return false; + } + + virtual bool isLinEquation() { + return false; + } + + virtual bool isFunctionCall() { + return false; + } + + virtual bool isFirstLastTypeIndex() { + return false; + } + + virtual bool isWatch() { + return false; + } + + virtual bool isQueueExpressionType() { + return false; + } + + virtual bool isMatch() { + return false; + } + + virtual bool isBABlockType() { + return false; + } + + virtual bool isUnitDef() { + return false; + } + + virtual bool isFactorDef() { + return false; + } + + virtual bool isValence() { + return false; + } + + virtual bool isUnitState() { + return false; + } + + virtual bool isLocalListStatement() { + return false; + } + + virtual bool isModel() { + return false; + } + + virtual bool isDefine() { + return false; + } + + virtual bool isInclude() { + return false; + } + + virtual bool isParamAssign() { + return false; + } + + virtual bool isStepped() { + return false; + } + + virtual bool isIndependentDef() { + return false; + } + + virtual bool isDependentDef() { + return false; + } + + virtual bool isPlotDeclaration() { + return false; + } + + virtual bool isConductanceHint() { + return false; + } + + virtual bool isExpressionStatement() { + return false; + } + + virtual bool isProtectStatement() { + return false; + } + + virtual bool isFromStatement() { + return false; + } + + virtual bool isForAllStatement() { + return false; + } + + virtual bool isWhileStatement() { + return false; + } + + virtual bool isIfStatement() { + return false; + } + + virtual bool isElseIfStatement() { + return false; + } + + virtual bool isElseStatement() { + return false; + } + + virtual bool isPartialEquation() { + return false; + } + + virtual bool isPartialBoundary() { + return false; + } + + virtual bool isWatchStatement() { + return false; + } + + virtual bool isMutexLock() { + return false; + } + + virtual bool isMutexUnlock() { + return false; + } + + virtual bool isReset() { + return false; + } + + virtual bool isSens() { + return false; + } + + virtual bool isConserve() { + return false; + } + + virtual bool isCompartment() { + return false; + } + + virtual bool isLDifuse() { + return false; + } + + virtual bool isReactionStatement() { + return false; + } + + virtual bool isLagStatement() { + return false; + } + + virtual bool isQueueStatement() { + return false; + } + + virtual bool isConstantStatement() { + return false; + } + + virtual bool isTableStatement() { + return false; + } + + virtual bool isSuffix() { + return false; + } + + virtual bool isUseion() { + return false; + } + + virtual bool isNonspecific() { + return false; + } + + virtual bool isElctrodeCurrent() { + return false; + } + + virtual bool isSection() { + return false; + } + + virtual bool isRange() { + return false; + } + + virtual bool isGlobal() { + return false; + } + + virtual bool isPointer() { + return false; + } + + virtual bool isBbcorePtr() { + return false; + } + + virtual bool isExternal() { + return false; + } + + virtual bool isThreadSafe() { + return false; + } + + virtual bool isVerbatim() { + return false; + } + + virtual bool isComment() { + return false; + } + + virtual bool isProgram() { + return false; + } }; } // namespace ast diff --git a/src/nmodl/ext/flags/LICENSE b/src/nmodl/ext/flags/LICENSE new file mode 100644 index 0000000000..9c0f1f8112 --- /dev/null +++ b/src/nmodl/ext/flags/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Dmitry Arkhipov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/nmodl/ext/flags/allow_flags.hpp b/src/nmodl/ext/flags/allow_flags.hpp new file mode 100644 index 0000000000..57ec11e22e --- /dev/null +++ b/src/nmodl/ext/flags/allow_flags.hpp @@ -0,0 +1,24 @@ +#ifndef ENUM_CLASS_ALLOW_FLAGS_HPP +#define ENUM_CLASS_ALLOW_FLAGS_HPP + + +#include <type_traits> + + +namespace flags { + + +template <class E, class Enabler = void> struct is_flags +: public std::false_type {}; + + +} // namespace flags + + +#define ALLOW_FLAGS_FOR_ENUM(name) \ +namespace flags { \ +template <> struct is_flags< name > : std::true_type {}; \ +} + + +#endif // ENUM_CLASS_ALLOW_FLAGS_HPP diff --git a/src/nmodl/ext/flags/flags.hpp b/src/nmodl/ext/flags/flags.hpp new file mode 100644 index 0000000000..e6811ca46b --- /dev/null +++ b/src/nmodl/ext/flags/flags.hpp @@ -0,0 +1,309 @@ +#ifndef ENUM_CLASS_FLAGS_HPP +#define ENUM_CLASS_FLAGS_HPP + + +#include "allow_flags.hpp" +#include "iterator.hpp" + +#include <bitset> +#include <initializer_list> +#include <numeric> +#include <utility> + + +namespace flags { + + +constexpr struct empty_t { + constexpr empty_t() noexcept {} +} empty; + + +template <class E> class flags { +public: + static_assert(is_flags<E>::value, + "flags::flags is disallowed for this type; " + "use ALLOW_FLAGS_FOR_ENUM macro."); + + using enum_type = typename std::decay<E>::type; + using underlying_type = typename std::underlying_type<enum_type>::type; + using impl_type = typename std::make_unsigned<underlying_type>::type; + + using iterator = FlagsIterator<enum_type>; + using const_iterator = iterator; + using value_type = typename iterator::value_type; + using reference = typename iterator::reference; + using const_reference = typename iterator::reference; + using pointer = enum_type *; + using const_pointer = const enum_type *; + using size_type = std::size_t; + using difference_type = typename iterator::difference_type; + + + constexpr static std::size_t bit_size() { return sizeof(impl_type) * 8; } + + +private: + template <class T, class Res = std::nullptr_t> + using convertible = std::enable_if<std::is_convertible<T, enum_type>::value, + Res>; + + +public: + flags() noexcept = default; + flags(const flags &fl) noexcept = default; + flags &operator=(const flags &fl) noexcept = default; + flags(flags &&fl) noexcept = default; + flags &operator=(flags &&fl) noexcept= default; + + + explicit constexpr flags(empty_t) noexcept : val_(0) {} + + +#ifdef ENUM_CLASS_FLAGS_FORBID_IMPLICT_CONVERSION + explicit +#endif + constexpr flags(enum_type e) noexcept + : val_(static_cast<impl_type>(e)) {} + + flags &operator=(enum_type e) noexcept { + val_ = static_cast<impl_type>(e); + return *this; + } + + + flags(std::initializer_list<enum_type> il) noexcept : val_(0) { insert(il); } + + flags &operator=(std::initializer_list<enum_type> il) noexcept { + clear(); + insert(il); + return *this; + } + + template <class ... Args> + flags(enum_type e, Args ... args) noexcept : flags{e, args...} {} + + + template <class FwIter> + flags(FwIter b, FwIter e, + typename convertible<decltype(*b)>::type = nullptr) + noexcept(noexcept(std::declval<flags>().insert(std::declval<FwIter>(), + std::declval<FwIter>()))) + : val_(0) + { insert(b, e); } + + + constexpr explicit operator bool() const noexcept { return val_; } + + constexpr bool operator!() const noexcept { return !val_; } + + friend constexpr bool operator==(flags fl1, flags fl2) { + return fl1.val_ == fl2.val_; + } + + friend constexpr bool operator!=(flags fl1, flags fl2) { + return fl1.val_ != fl2.val_; + } + + + constexpr flags operator~() const noexcept { return flags(~val_); } + + flags &operator|=(const flags &fl) noexcept { + val_ |= fl.val_; + return *this; + } + + flags &operator&=(const flags &fl) noexcept { + val_ &= fl.val_; + return *this; + } + + flags &operator^=(const flags &fl) noexcept { + val_ ^= fl.val_; + return *this; + } + + + flags &operator|=(enum_type e) noexcept { + val_ |= static_cast<impl_type>(e); + return *this; + } + + flags &operator&=(enum_type e) noexcept { + val_ &= static_cast<impl_type>(e); + return *this; + } + + flags &operator^=(enum_type e) noexcept { + val_ ^= static_cast<impl_type>(e); + return *this; + } + + friend constexpr flags operator|(flags f1, flags f2) noexcept { + return flags{static_cast<impl_type>(f1.val_ | f2.val_)}; + } + + friend constexpr flags operator&(flags f1, flags f2) noexcept { + return flags{static_cast<impl_type>(f1.val_ & f2.val_)}; + } + + friend constexpr flags operator^(flags f1, flags f2) noexcept { + return flags{static_cast<impl_type>(f1.val_ ^ f2.val_)}; + } + + + void swap(flags &fl) noexcept { std::swap(val_, fl.val_); } + + + constexpr underlying_type underlying_value() const noexcept { + return static_cast<underlying_type>(val_); + } + + void set_underlying_value(underlying_type newval) noexcept { + val_ = static_cast<impl_type>(newval); + } + + + constexpr explicit operator std::bitset<bit_size()>() const noexcept { + return to_bitset(); + } + + constexpr std::bitset<bit_size()> to_bitset() const noexcept { + return {val_}; + } + + + constexpr bool empty() const noexcept { return !val_; } + + size_type size() const noexcept { + return std::distance(this->begin(), this->end()); + } + + constexpr size_type max_size() const noexcept { return bit_size(); } + + + iterator begin() const noexcept { return cbegin(); } + iterator cbegin() const noexcept { return iterator{val_}; } + + constexpr iterator end() const noexcept { return cend(); } + constexpr iterator cend() const noexcept { return {}; } + + + constexpr iterator find(enum_type e) const noexcept { return {val_, e}; } + + constexpr size_type count(enum_type e) const noexcept { + return find(e) != end() ? 1 : 0; + } + + + std::pair<iterator, iterator> equal_range(enum_type e) const noexcept { + auto i = find(e); + auto j = i; + return {i, ++j}; + } + + + template <class... Args> + std::pair<iterator, bool> emplace(Args && ... args) noexcept { + return insert(enum_type{args...}); + } + + template <class... Args> + iterator emplace_hint(iterator, Args && ... args) noexcept { + return emplace(args...).first; + } + + + std::pair<iterator, bool> insert(enum_type e) noexcept { + auto i = find(e); + if (i == end()) { + i.mask_ = static_cast<impl_type>(e); + val_ |= i.mask_; + update_uvalue(i); + return {i, true}; + } + return {i, false}; + } + + std::pair<iterator, bool> insert(iterator, enum_type e) noexcept { + return insert(e); + } + + template <class FwIter> + auto insert(FwIter i1, FwIter i2) + noexcept(noexcept(++i1) && noexcept(*i1) && noexcept(i1 == i2)) + -> typename convertible<decltype(*i1), void>::type { + val_ |= std::accumulate(i1, i2, impl_type{0}, [](impl_type i, enum_type e) { + return i | static_cast<impl_type>(e); + }); + } + + template <class Container> + auto insert(const Container &ctn) noexcept + -> decltype(std::begin(ctn), std::end(ctn), void()) { + insert(std::begin(ctn), std::end(ctn)); + } + + + iterator erase(iterator i) noexcept { + val_ ^= i.mask_; + update_uvalue(i); + return ++i; + } + + size_type erase(enum_type e) noexcept { + auto e_count = count(e); + val_ ^= static_cast<impl_type>(e); + return e_count; + } + + iterator erase(iterator i1, iterator i2) noexcept { + val_ ^= flags(i1, i2).val_; + update_uvalue(i2); + return ++i2; + } + + + void clear() noexcept { val_ = 0; } + +private: + constexpr explicit flags(impl_type val) noexcept : val_(val) {} + + void update_uvalue(iterator &it) const noexcept { it.uvalue_ = val_; } + + impl_type val_; +}; + + +template <class E> +void swap(flags<E> &fl1, flags<E> &fl2) noexcept { fl1.swap(fl2); } + + +} // namespace flags + + +template <class E> +constexpr auto operator|(E e1, E e2) noexcept +-> typename std::enable_if<flags::is_flags<E>::value, + flags::flags<E>>::type { + return flags::flags<E>(e1) | e2; +} + + +template <class E> +constexpr auto operator&(E e1, E e2) noexcept +-> typename std::enable_if<flags::is_flags<E>::value, + flags::flags<E>>::type { + return flags::flags<E>(e1) & e2; +} + + +template <class E> +constexpr auto operator^(E e1, E e2) noexcept +-> typename std::enable_if<flags::is_flags<E>::value, + flags::flags<E>>::type { + return flags::flags<E>(e1) ^ e2; +} + + +#endif // ENUM_CLASS_FLAGS_HPP diff --git a/src/nmodl/ext/flags/flagsfwd.hpp b/src/nmodl/ext/flags/flagsfwd.hpp new file mode 100644 index 0000000000..0fdfbe73ee --- /dev/null +++ b/src/nmodl/ext/flags/flagsfwd.hpp @@ -0,0 +1,8 @@ +#ifndef ENUM_CLASS_FLAGSFWD_HPP +#define ENUM_CLASS_FLAGSFWD_HPP + + +namespace flags { template <class E> class flags; } + + +#endif // ENUM_CLASS_FLAGSFWD_HPP diff --git a/src/nmodl/ext/flags/iterator.hpp b/src/nmodl/ext/flags/iterator.hpp new file mode 100644 index 0000000000..732a1e19d7 --- /dev/null +++ b/src/nmodl/ext/flags/iterator.hpp @@ -0,0 +1,86 @@ +#ifndef ENUM_CLASS_ITERATOR_HPP +#define ENUM_CLASS_ITERATOR_HPP + + +#include "flagsfwd.hpp" + +#include <iterator> + + +namespace flags { + + +template <class E> +class FlagsIterator { +public: + using flags_type = flags<E>; + using difference_type = std::ptrdiff_t; + using value_type = E; + using pointer = value_type *; + using reference = const value_type; + using iterator_category = std::forward_iterator_tag; + + + constexpr FlagsIterator() noexcept : uvalue_(0), mask_(0) {} + + constexpr FlagsIterator(const FlagsIterator &other) noexcept + : uvalue_(other.uvalue_), mask_(other.mask_) {} + + + FlagsIterator &operator++() noexcept { + nextMask(); + return *this; + } + FlagsIterator operator++(int) noexcept { + auto copy = *this; + ++(*this); + return copy; + } + + + constexpr reference operator*() const noexcept { + return static_cast<value_type>(mask_); + } + + + friend inline constexpr bool operator==(const FlagsIterator &i, + const FlagsIterator &j) noexcept { + return i.mask_ == j.mask_; + } + + friend inline constexpr bool operator!=(const FlagsIterator &i, + const FlagsIterator &j) noexcept { + return i.mask_ != j.mask_; + } + + +private: + template <class E_> friend class flags; + + using impl_type = typename flags_type::impl_type; + + + explicit FlagsIterator(impl_type uv) noexcept : uvalue_(uv), mask_(1) { + if (!(mask_ & uvalue_)) { nextMask(); } + } + + constexpr FlagsIterator(impl_type uv, E e) noexcept + : uvalue_(uv) + , mask_(static_cast<impl_type>(static_cast<impl_type>(e) & uv)) + {} + + + void nextMask() noexcept { + do { mask_ <<= 1; } while (mask_ && !(mask_ & uvalue_)); + } + + + impl_type uvalue_; + impl_type mask_; +}; + + +} // namespace flags + + +#endif // ENUM_CLASS_ITERATOR_HPP diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 57d76e7694..0b4db4eab7 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -11,6 +11,8 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PROJECT_SOURCE_DIR}/src/language/argument.py diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index c6940055c3..8c0c4572e0 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -24,7 +24,11 @@ def headers(self): self.writer.write_line('#include "ast/ast_utils.hpp"') self.writer.write_line('#include "lexer/modtoken.hpp"') - self.writer.write_line('#include "utils/common_utils.hpp"') + self.writer.write_line('#include "utils/common_utils.hpp"', newline=2) + + self.writer.write_line('namespace symtab {') + self.writer.write_line(' class SymbolTable;') + self.writer.write_line('}') def class_comment(self): self.writer.write_line("/* all classes representing Abstract Syntax Tree (AST) nodes */") @@ -130,7 +134,7 @@ def ast_classes_declaration(self): if node.is_symtab_needed(): - self.writer.write_line("void* symtab = nullptr;", newline=2) + self.writer.write_line("std::shared_ptr<symtab::SymbolTable> symtab;", newline=2) if members: self.writer.write_line("/* constructors */") @@ -182,6 +186,13 @@ def ast_classes_declaration(self): # TODO: returning typename for name? check the usage of this and fix in better way self.writer.write_line("virtual std::string getTypeName() override { return \"" + node.class_name + "\"; }") + # TODO : symbol table needs to know the name of every node + # For nodes without any children with name, name is typename itself + if not get_method_added: + self.writer.write_line("virtual std::string getName() override {") + self.writer.write_line(" return getTypeName();") + self.writer.write_line("}") + # all member functions self.writer.write_line(virtual + "void visitChildren (Visitor* v) override;") self.writer.write_line(virtual + "void accept(Visitor* v) override { v->visit" + node.class_name + "(this); }") @@ -189,6 +200,7 @@ def ast_classes_declaration(self): # TODO: type should declared as enum class typename = node_ast_type(node.class_name) self.writer.write_line(virtual + "Type getType() override { return Type::" + typename + "; }") + self.writer.write_line("bool is" + node.class_name + " () override { return true; }") self.writer.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") if node.has_token: @@ -196,8 +208,8 @@ def ast_classes_declaration(self): self.writer.write_line("void setToken(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") if node.is_symtab_needed(): - self.writer.write_line("void setBlockSymbolTable(void *s) " + " { symtab = s; }") - self.writer.write_line("void* getBlockSymbolTable() " + " { return symtab; }") + self.writer.write_line("void setBlockSymbolTable(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") + self.writer.write_line("std::shared_ptr<symtab::SymbolTable> getBlockSymbolTable() " + " { return symtab; }") if node.is_number_node(): self.writer.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 39b7b0863f..fd49e5d3fc 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -41,4 +41,14 @@ JSONVisitorDefinitionPrinter( "../visitors/json_visitor.cpp", "JSONVisitor", + nodes).write() + +SymtabVisitorDeclarationPrinter( + "../visitors/symtab_visitor.hpp", + "SymtabVisitor", + nodes).write() + +SymtabVisitorDefinitionPrinter( + "../visitors/symtab_visitor.cpp", + "SymtabVisitor", nodes).write() \ No newline at end of file diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index 6a6c5b3d56..1e26b2f1ce 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -2,8 +2,11 @@ def camel_case_to_underscore(name): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).upper() + typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) return typename def node_ast_type(name): - return camel_case_to_underscore(name) \ No newline at end of file + return camel_case_to_underscore(name).upper() + +def node_property_type(name): + return camel_case_to_underscore(name).lower() diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index ed693f4e27..1defce4734 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -1,4 +1,5 @@ from printer import * +from utils import * class AbstractVisitorPrinter(DeclarationPrinter): @@ -142,4 +143,103 @@ def definitions(self): self.writer.write_line("}", pre_gutter=-1, newline=2) - self.writer.decrease_gutter() \ No newline at end of file + self.writer.decrease_gutter() + + +class SymtabVisitorDeclarationPrinter(DeclarationPrinter): + """Prints visitor class declaration for printing Symbol table in JSON format""" + + def headers(self): + line = '#include "visitors/json_visitor.hpp"' + self.writer.write_line(line) + line = '#include "visitors/ast_visitor.hpp"' + self.writer.write_line(line) + line = '#include "symtab/symbol_table.hpp"' + self.writer.write_line(line, newline=2) + + def class_comment(self): + self.writer.write_line("/* Concrete visitor for printing Symbol table in JSON format */") + + def class_name_declaration(self): + + self.writer.write_line("using namespace symtab;") + self.writer.write_line("class " + self.classname + " : public AstVisitor {") + + def private_declaration(self): + self.writer.write_line("private:", post_gutter=1) + self.writer.write_line("std::unique_ptr<JSONPrinter> printer;") + self.writer.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) + + def public_declaration(self): + self.writer.write_line("public:", post_gutter=1) + + line = self.classname + "(ModelSymbolTable* symtab) : modsymtab(symtab), printer(new JSONPrinter()) {} " + self.writer.write_line(line) + + line = self.classname + "( ModelSymbolTable* symtab, std::stringstream &ss) : modsymtab(symtab), printer(new JSONPrinter(ss)) {}" + self.writer.write_line(line) + + line = self.classname + "( ModelSymbolTable* symtab, std::string filename) : modsymtab(symtab), printer(new JSONPrinter(filename)) {}" + self.writer.write_line(line, newline=2) + + self.writer.write_line("template<typename T>") + self.writer.write_line("void setupSymbol(T* node, symtab::details::NmodlInfo property, int order = 0);", newline=2) + + self.writer.write_line("template<typename T>") + line = "void setupSymbolTable(T *node, bool global_block);" + self.writer.write_line(line) + + self.writer.write_line("template<typename T>") + line = "void setupSymbolTable(T *node, symtab::details::NmodlInfo property, bool global_block);" + self.writer.write_line(line, newline=2) + + for node in self.nodes: + if node.is_symtab_method_required(): + line = "virtual void visit" + node.class_name + "(" + node.class_name + "* node) override;" + self.writer.write_line(line) + + + self.writer.decrease_gutter() + + def post_declaration(self): + self.writer.write_line() + self.writer.write_line('#include "visitors/symtab_visitor_helper.hpp"') + + +class SymtabVisitorDefinitionPrinter(DefinitionPrinter): + """Prints visitor class definition for printing Symbol table in JSON format""" + + def headers(self): + line = '#include "symtab/symbol_table.hpp"' + self.writer.write_line(line) + line = '#include "visitors/symtab_visitor.hpp"' + self.writer.write_line(line, newline=2) + + def definitions(self): + for node in self.nodes: + if node.is_symtab_method_required(): + + line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" + self.writer.write_line(line, post_gutter=1) + + type_name = node_property_type(node.class_name) + property_name = "symtab::details::NmodlInfo::" + type_name + + if node.is_symbol_var_node() or node.is_prime_node(): + is_prime = ", node->getOrder()" if node.is_prime_node() else ""; + self.writer.write_line("setupSymbol(node, " + property_name + is_prime + ");") + + else: + """ setupBlock has node*, symbol*, symbol_block, program_block, global_block""" + if node.is_symbol_block_node(): + fun_call = "setupSymbolTable(node, " + property_name + ", false);" + + elif node.is_program_node() or node.is_global_block_node(): + fun_call = "setupSymbolTable(node, true);" + + else: + """this is for nodes which has parent class as Block node""" + fun_call = "setupSymbolTable(node, false);" + + self.writer.write_line(fun_call) + self.writer.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main.cpp index a855ce21bf..e1834f3adc 100644 --- a/src/nmodl/lexer/main.cpp +++ b/src/nmodl/lexer/main.cpp @@ -44,7 +44,7 @@ int main(int argc, char* argv[]) { using SymbolType = nmodl::Parser::symbol_type; /// parse nmodl file untile EOF, print each token - while (1) { + while (true) { SymbolType sym = scanner.next_token(); TokenType token = sym.token(); diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 509da98d08..87695db11a 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -8,11 +8,11 @@ std::string ModToken::position() const { std::stringstream ss; if (external) { - ss << "[EXTERNAL]"; + ss << "EXTERNAL"; } else if (start_line() == 0) { - ss << "[UNKNOWN]"; + ss << "UNKNOWN"; } else { - ss << "[" << pos << "]"; + ss << pos; } return ss.str(); } @@ -20,7 +20,7 @@ std::string ModToken::position() const { /** Print token as : token at [line.start_column-end_column] type token * Example: v at [118.9-14] type 376 */ std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { - stream << std::setw(15) << mt.name << " at " << mt.position(); + stream << std::setw(15) << mt.name << " at [" << mt.position() << "]"; return stream << " type " << mt.token; } diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 0f22ab5521..e65a04b589 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -22,7 +22,7 @@ * can distinguish them from other tokens produced by lexer. * * \todo location object is copyable except if we specify the - * stream name. It would be good to trackfilenames when we go + * stream name. It would be good to track filename when we go * for multi-channel optimization and code generation. */ @@ -42,8 +42,10 @@ class ModToken { public: ModToken() : pos(nullptr, 0){}; + explicit ModToken(bool ext) : pos(nullptr, 0), external(ext) { } + ModToken(std::string str, int tok, nmodl::location& loc) : name(str), token(tok), pos(loc) { } diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt new file mode 100644 index 0000000000..870582ad6d --- /dev/null +++ b/src/nmodl/symtab/CMakeLists.txt @@ -0,0 +1,30 @@ +#============================================================================= +# Visitor sources +#============================================================================= +set(SYMTAB_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp +) + +#============================================================================= +# Symbol table library +#============================================================================= +add_library(symtab + STATIC + ${SYMTAB_SOURCE_FILES} +) + +add_dependencies(symtab lexer) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${SYMTAB_SOURCE_FILES} + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) \ No newline at end of file diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp new file mode 100644 index 0000000000..2db0920bca --- /dev/null +++ b/src/nmodl/symtab/symbol.cpp @@ -0,0 +1,32 @@ +#include "symtab/symbol.hpp" + +namespace symtab { + + bool Symbol::is_extern_token_only() { + return (properties == details::NmodlInfo::extern_token); + } + + bool Symbol::has_common_properties(SymbolInfo& new_properties) { + return static_cast<bool>(properties & new_properties); + } + + void Symbol::combine_properties(SymbolInfo& new_properties) { + properties = properties | new_properties; + } + + void Symbol::add_property(NmodlInfo property) { + properties = properties | property; + } + + /** Prime variable will appear in different block and could have + * multiple derivative orders. We have to store highest order. + * + * \todo Check if analysis passes need more information. + */ + void Symbol::set_order(int new_order) { + if (new_order > order) { + order = new_order; + } + } + +} // namespace symtab \ No newline at end of file diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp new file mode 100644 index 0000000000..dba7790fab --- /dev/null +++ b/src/nmodl/symtab/symbol.hpp @@ -0,0 +1,122 @@ +#ifndef _NMODL_SYMBOL_HPP_ +#define _NMODL_SYMBOL_HPP_ + +#include <map> + +#include "ast/ast.hpp" +#include "symtab/symbol_properties.hpp" + +namespace symtab { + + using namespace ast; + using namespace details; + + /** + * \class Symbol + * \brief Represent symbol in symbol table + * + * Symbol table generator pass visit the AST and insert symbol for + * each node into symbol table. Symbol could appear multiple times + * in a block or different global blocks. SymbolInfo object has all + * nmodl properties information. + * + * \todo Multiple tokens (i.e. location information) for symbol should + * be tracked + * \todo Scope information should be more than just string + * \todo Perf block should track information about all usage of the symbol + * (would be helpful for perf modeling) + */ + + class Symbol { + private: + /// name of the symbol + std::string name; + + /// ast node where symbol encountered first time + /// node is passed from visitor and hence we have + /// raw pointer instead of shared_ptr + AST* node = nullptr; + + /// token for position information + ModToken token; + + /// properties of symbol from whole mod file + SymbolInfo properties{flags::empty}; + + /// scope of the symbol (block name) + std::string scope; + + /// number of times symbol is read + int read_count = 0; + + /// number of times symbol is written + int write_count = 0; + + /// order of derivative (for prime node) + int order = 0; + + public: + Symbol() = delete; + + Symbol(std::string name, ModToken token) : name(name), token(token) { + } + + Symbol(std::string name, AST* node, ModToken token) : name(name), node(node), token(token) { + } + + void set_scope(std::string s) { + scope = s; + } + + std::string get_name() { + return name; + } + + std::string get_scope() { + return scope; + } + + SymbolInfo& get_properties() { + return properties; + } + + void read() { + read_count++; + } + + void write() { + write_count++; + } + + AST* get_node() { + return node; + } + + ModToken get_token() { + return token; + } + + int get_read_count() const { + return read_count; + } + + int get_write_count() const { + return write_count; + } + + void set_as_extern_token(); + + bool is_extern_token_only(); + + bool has_common_properties(SymbolInfo& prop); + + void combine_properties(SymbolInfo& prop); + + void add_property(NmodlInfo property); + + void set_order(int order); + }; + +} // namespace symtab + +#endif \ No newline at end of file diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp new file mode 100644 index 0000000000..0a07d2dcb8 --- /dev/null +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -0,0 +1,142 @@ +#include <vector> +#include <string> + +#include "utils/string_utils.hpp" +#include "symtab/symbol_properties.hpp" + +using namespace symtab::details; + +bool has_property(const SymbolInfo& obj, NmodlInfo property) { + return static_cast<bool>(obj & property); +} + +std::vector<std::string> to_string_vector(const SymbolInfo& obj) { + std::vector<std::string> properties; + + if (has_property(obj, NmodlInfo::local_var)) { + properties.push_back("local"); + } + + if (has_property(obj, NmodlInfo::global_var)) { + properties.push_back("global"); + } + + if (has_property(obj, NmodlInfo::range_var)) { + properties.push_back("range"); + } + + if (has_property(obj, NmodlInfo::param_assign)) { + properties.push_back("parameter"); + } + + if (has_property(obj, NmodlInfo::pointer_var)) { + properties.push_back("pointer"); + } + + if (has_property(obj, NmodlInfo::bbcore_pointer_var)) { + properties.push_back("bbcore_pointer"); + } + + if (has_property(obj, NmodlInfo::extern_var)) { + properties.push_back("extern"); + } + + if (has_property(obj, NmodlInfo::prime_name)) { + properties.push_back("prime_name"); + } + + if (has_property(obj, NmodlInfo::dependent_def)) { + properties.push_back("dependent_def"); + } + + if (has_property(obj, NmodlInfo::unit_def)) { + properties.push_back("unit_def"); + } + + if (has_property(obj, NmodlInfo::read_ion_var)) { + properties.push_back("read_ion"); + } + + if (has_property(obj, NmodlInfo::write_ion_var)) { + properties.push_back("write_ion"); + } + + if (has_property(obj, NmodlInfo::nonspe_cur_var)) { + properties.push_back("nonspe_cur"); + } + + if (has_property(obj, NmodlInfo::electrode_cur_var)) { + properties.push_back("electrode_cur"); + } + + if (has_property(obj, NmodlInfo::section_var)) { + properties.push_back("section"); + } + + if (has_property(obj, NmodlInfo::argument)) { + properties.push_back("argument"); + } + + if (has_property(obj, NmodlInfo::function_block)) { + properties.push_back("function_block"); + } + + if (has_property(obj, NmodlInfo::procedure_block)) { + properties.push_back("procedure_block"); + } + + if (has_property(obj, NmodlInfo::derivative_block)) { + properties.push_back("derivative_block"); + } + + if (has_property(obj, NmodlInfo::linear_block)) { + properties.push_back("linear_block"); + } + + if (has_property(obj, NmodlInfo::non_linear_block)) { + properties.push_back("non_linear_block"); + } + + if (has_property(obj, NmodlInfo::discrete_block)) { + properties.push_back("discrete_block"); + } + + if (has_property(obj, NmodlInfo::partial_block)) { + properties.push_back("partial_block"); + } + + if (has_property(obj, NmodlInfo::kinetic_block)) { + properties.push_back("kinetic_block"); + } + + if (has_property(obj, NmodlInfo::function_table_block)) { + properties.push_back("function_table_block"); + } + + if (has_property(obj, NmodlInfo::factor_def)) { + properties.push_back("factor_def"); + } + + if (has_property(obj, NmodlInfo::extern_token)) { + properties.push_back("extern_token"); + } + + return properties; +} + +std::ostream& operator<<(std::ostream& os, const SymbolInfo& obj) { + os << to_string(obj); + return os; +} + +std::string to_string(const SymbolInfo& obj) { + auto properties = to_string_vector(obj); + std::string text; + for (const auto& property : properties) { + text += property + " "; + } + + // remove extra whitespace at the end + stringutils::trim(text); + return text; +} diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp new file mode 100644 index 0000000000..5db5f0bdc9 --- /dev/null +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -0,0 +1,185 @@ +#ifndef _SYMBOL_PROPERTIES_HPP_ +#define _SYMBOL_PROPERTIES_HPP_ + +#include <sstream> + +#include "flags/flags.hpp" + +namespace symtab { + + namespace details { + + /** kind of symbol */ + enum class DeclarationType { + /** variable */ + variable, + + /** function */ + function + }; + + /** scope within a mod file */ + enum class Scope { + /** local variable */ + local, + + /** global variable */ + global, + + /** neuron variable */ + neuron, + + /** extern variable */ + external + }; + + /** state during various compiler passes */ + enum class Status { + /** unmodified */ + unmodified, + + /** converted to local */ + to_local, + + /** converted to global */ + to_global, + + /** inlined */ + inlined, + + /** renamed */ + renamed + }; + + /** usage of mod file as array or scalar */ + enum class VariableType { + /** scalar / single value */ + scalar, + + /** vector type */ + array + }; + + /** variable usage within a mod file */ + enum class Access { + /** variable is ready only */ + read = 1 << 0, + + /** variable is written only */ + write = 1 << 1 + }; + + /** @brief nmodl variable properties + * + * Certain variables/symbols specified in various places in the + * same mod file. For example, RANGE variable could be in PARAMETER + * bloc, ASSIGNED block etc. In this case, the symbol in global + * scope need to have multiple properties. This is also important + * while code generation. Hence, in addition to AST node pointer, + * we define NmodlInfo so that we can set multiple properties. + * Note that AST pointer is no longer pointing to all pointers. + * Same applies for ModToken. + * + * These is some redundancy between NmodlInfo and other enums like + * DeclarationType and Scope. But as AST will become more abstract + * from NMODL (towards C/C++) then other types will be useful. + * + * \todo Rename param_assign to parameter_var + */ + enum class NmodlInfo { + /** Local Variable */ + local_var = 1 << 0, + + /** Global Variable */ + global_var = 1 << 1, + + /** Range Variable */ + range_var = 1 << 2, + + /** Parameter Variable */ + param_assign = 1 << 3, + + /** Pointer Type */ + pointer_var = 1 << 4, + + /** Bbcorepointer Type */ + bbcore_pointer_var = 1 << 5, + + /** Extern Type */ + extern_var = 1 << 6, + + /** Prime Type */ + prime_name = 1 << 7, + + /** Dependent Def */ + dependent_def = 1 << 8, + + /** Unit Def */ + unit_def = 1 << 9, + + /** Read Ion */ + read_ion_var = 1 << 10, + + /** Write Ion */ + write_ion_var = 1 << 11, + + /** Non Specific Current */ + nonspe_cur_var = 1 << 12, + + /** Electrode Current */ + electrode_cur_var = 1 << 13, + + /** Section Type */ + section_var = 1 << 14, + + /** Argument Type */ + argument = 1 << 15, + + /** Function Type */ + function_block = 1 << 16, + + /** Procedure Type */ + procedure_block = 1 << 17, + + /** Derivative Block */ + derivative_block = 1 << 18, + + /** Linear Block */ + linear_block = 1 << 19, + + /** NonLinear Block */ + non_linear_block = 1 << 20, + + /** Discrete Block */ + discrete_block = 1 << 21, + + /** Partial Block */ + partial_block = 1 << 22, + + /** Kinetic Block */ + kinetic_block = 1 << 23, + + /** FunctionTable Block */ + function_table_block = 1 << 24, + + /** factor in unit block */ + factor_def = 1 << 25, + + /** extern token */ + extern_token = 1 << 26 + }; + + } // namespace details + +} // namespace symtab + +ALLOW_FLAGS_FOR_ENUM(symtab::details::NmodlInfo) +ALLOW_FLAGS_FOR_ENUM(symtab::details::Access) + +using SymbolInfo = flags::flags<symtab::details::NmodlInfo>; + +std::ostream& operator<<(std::ostream& os, const SymbolInfo& obj); + +std::string to_string(const SymbolInfo& obj); + +#endif diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp new file mode 100644 index 0000000000..345a1c1cdb --- /dev/null +++ b/src/nmodl/symtab/symbol_table.cpp @@ -0,0 +1,357 @@ +#include "symtab/symbol_table.hpp" +#include "lexer/token_mapping.hpp" + +namespace symtab { + + /** Insert symbol into current symbol table. There are certain + * cases where we were getting re-insertion errors. + * + * \todo Revisit the error handling + */ + void Table::insert(std::shared_ptr<Symbol> symbol) { + std::string name = symbol->get_name(); + if (symbols.find(name) != symbols.end()) { + throw std::runtime_error("Trying to re-insert symbol " + name); + } + symbols[name] = symbol; + } + + std::shared_ptr<Symbol> Table::lookup(std::string sname) { + if (symbols.find(sname) == symbols.end()) { + return nullptr; + } + return symbols[sname]; + } + + SymbolTable::SymbolTable(const SymbolTable& table) { + symtab_name = table.name(); + global = table.global_scope(); + node = nullptr; + parent = nullptr; + } + + /** Get all symbols which can be used in global scope. Note that + * this is different from GLOBAL variable type in the mod file. Here + * global meaning all variables that can be used in entire mod file. + * This is used from optimization passes. + * + * \todo Voltage v can be global variable as well as external. In order + * to avoid optimizations, we need to handle this case properly + * + * \todo Instead of ast node, use symbol properties to check variable type + */ + std::vector<std::shared_ptr<Symbol>> SymbolTable::get_global_variables() { + std::vector<std::shared_ptr<Symbol>> variables; + for (auto& syminfo : table.symbols) { + auto symbol = syminfo.second; + auto node = symbol->get_node(); + if ((node->isRangeVar() || node->isDependentDef() || node->isParamAssign())) { + variables.push_back(symbol); + } + } + return variables; + } + + /** Check if current symbol table is in global scope + * We create program scope at the top level and it has global scope. + * It contains neuron variables like t, dt, celsius etc. Then each + * nmodl block defining variables are added under this program's symbol + * table. Hence there are multiple levels of global scopes. In this + * helper function we make sure current block as well as it's parent are + * under global scopes. + * + * \todo It seems not necessary to traverse all parents if current block + * itself is with global scope. + */ + bool SymbolTable::under_global_scope() { + bool global_scope = global; + auto parent_table = parent; + + // traverse all parent blocks to make sure everyone is global + while (global_scope && parent_table) { + parent_table = parent_table->parent; + if (parent_table) { + global_scope = parent_table->global_scope(); + } + } + return global_scope; + } + + /// lookup for symbol in current scope as well as all parents + std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(std::string name) { + auto symbol = table.lookup(name); + if (!symbol && parent) { + symbol = parent->lookup_in_scope(name); + } + return symbol; + } + + /// lookup in all parents symbol table + /// \todo not sure why we are searching in parent symtab only. Also we could + /// also use lookup_in_scope partly. revisit this usage. + std::shared_ptr<Symbol> ModelSymbolTable::lookup(std::string name) { + if (!parent_symtab) { + throw std::logic_error("Lookup wit previous symtab = nullptr "); + } + + auto symbol = parent_symtab->lookup(name); + if (symbol) { + return symbol; + } + + // check into all parents + auto parent = parent_symtab->get_parent_table(); + while (parent) { + symbol = parent->lookup(name); + if (symbol) { + break; + } + parent = parent->get_parent_table(); + } + return symbol; + } + + void SymbolTable::insert_table(std::string name, std::shared_ptr<SymbolTable> table) { + if (childrens.find(name) != childrens.end()) { + throw std::runtime_error("Trying to re-insert SymbolTable " + name); + } + childrens[name] = table; + } + + void ModelSymbolTable::insert(std::shared_ptr<Symbol> symbol) { + symbol->set_scope(parent_symtab->name()); + std::string name = symbol->get_name(); + auto search_symbol = lookup(name); + + // if no symbol found then safe to insert + if (search_symbol == nullptr) { + parent_symtab->insert(symbol); + return; + } + + // properties and type information for error reporting + auto properties = to_string(search_symbol->get_properties()); + auto type = symbol->get_node()->getTypeName(); + + /** For global symbol tables, same variable can appear in multiple + * nmodl "global" blocks. It's an error if it appears multiple times + * in the same nmodl block. To check this we compare symbol properties + * which are bit flags. If they are not same that means, we have to + * add new properties to existing symbol. If the properties are same + * that means symbol are duplicate. + */ + if (parent_symtab->global_scope()) { + if (search_symbol->has_common_properties(symbol->get_properties())) { + std::string msg = "SYMTAB ERROR: _NMODL_GLOBAL_ has re-declaration of "; + msg += name + " <" + type + "> " + properties; + std::cout << msg << "\n"; + } else { + search_symbol->combine_properties(symbol->get_properties()); + } + } else { + /** For non-global scopes, check if symbol that we found has same + * scope name as symbol table. This means variable is being re-declared + * within the same scope. Otherwise, there is variable with same name + * in parent scopes and it will shadow the definition. In this case just + * emit the warning and insert the symbol. + */ + if (search_symbol->get_scope() == parent_symtab->name()) { + std::string msg = "SYMTAB ERROR: Re-declaration of " + name; + msg += " [" + type + "]" + " <" + properties + "> in "; + msg += parent_symtab->name() + " with one in " + search_symbol->get_scope(); + std::cout << msg << "\n"; + } else { + std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; + msg += parent_symtab->name() + " shadows <" + properties; + msg += "> definition in " + search_symbol->get_scope(); + std::cout << msg << "\n"; + parent_symtab->insert(symbol); + } + } + } + + /** Some blocks can appear multiple times in the nmodl file. In order to distinguish + * them we simple append counter. + * \todo We should add position information to make name unique + */ + std::string ModelSymbolTable::get_unique_name(std::string name, AST* node) { + static int block_counter = 0; + if (node->isStatementBlock() || node->isSolveBlock() || node->isBeforeBlock() || + node->isAfterBlock()) { + name += std::to_string(block_counter++); + } + return name; + } + + /// \todo : Remove this: used from setupBlock which could also use enter_scope + std::shared_ptr<SymbolTable> ModelSymbolTable::initialize_scope(std::string scope, + AST* node, + bool global) { + if (global) { + scope = "_NMODL_GLOBAL_"; + } + return enter_scope(scope, node, global); + } + + /** Callback at the entry of every block in nmodl file + * Every block starts a new scope and hence new symbol table is created. + * The same symbol table is returned so that visitor can store pointer to + * symbol table within a node. + */ + std::shared_ptr<SymbolTable> ModelSymbolTable::enter_scope(std::string name, + AST* node, + bool global) { + /// all global blocks in mod file have same symbol table + if (symtab && name == "_NMODL_GLOBAL_") { + parent_symtab = symtab; + return parent_symtab; + } + + /// statement block within global scope is part of global block itself + if (symtab && node->isStatementBlock() && parent_symtab->under_global_scope()) { + parent_symtab = symtab; + return parent_symtab; + } + + // new symbol table for current block with unique name + name = get_unique_name(name, node); + auto new_symtab = std::make_shared<SymbolTable>(name, node, global); + + // symtab is nullptr when we are entering into very fitst block + // otherwise we have to inset new symbol table, set parent symbol + // table and then new symbol table becomes parent for future blocks + if (symtab == nullptr) { + symtab = new_symtab; + parent_symtab = new_symtab; + } else { + parent_symtab->insert_table(name, new_symtab); + new_symtab->set_parent_table(parent_symtab); + parent_symtab = new_symtab; + } + return new_symtab; + } + + /** Callback at the exit of every block in nmodl file When we reach + * program node (top most level), there is no parent block and use + * same symbol table. Otherwise traverse back to prent. + */ + void ModelSymbolTable::leave_scope() { + if (parent_symtab) { + parent_symtab = parent_symtab->get_parent_table(); + } + + if (parent_symtab == nullptr) { + parent_symtab = symtab; + } + } + + //============================================================================= + // Revisit implementation of all below symbol table printing functions + //============================================================================= + + enum class alignment { left, right, center }; + + /// Left/Right/Center-aligns string within a field of width "width" + std::string format_text(std::string text, int width, alignment type) { + std::string left, right; + // count excess room to pad + auto padding = width - text.size(); + if (type == alignment::left) { + right = std::string(padding, ' '); + } else if (type == alignment::right) { + left = std::string(padding, ' '); + } else { + left = std::string(padding / 2, ' '); + right = std::string(padding / 2, ' '); + // if odd #, add one more space + if (padding > 0 && padding % 2 != 0) { + right += " "; + } + } + return left + text + right; + } + + void Table::print(std::stringstream& ss, std::string title, int n) { + if (symbols.size()) { + std::vector<int> width{15, 15, 15, 15, 15}; + std::vector<std::vector<std::string>> rows; + + for (const auto& syminfo : symbols) { + auto symbol = syminfo.second; + + // do not print external symbols which are not used in the current model + if (symbol->is_extern_token_only() && symbol->get_read_count() == 0 && + symbol->get_write_count() == 0) { + continue; + } + + auto name = syminfo.first; + auto position = symbol->get_token().position(); + auto properties = to_string(symbol->get_properties()); + auto reads = std::to_string(symbol->get_read_count()); + auto writes = std::to_string(symbol->get_write_count()); + + std::vector<std::string> row{name, properties, position, reads, writes}; + rows.push_back(row); + + for (int i = 0; i < row.size(); i++) { + if (width[i] < (row[i].length() + 3)) + width[i] = row[i].length() + 3; + } + } + + std::stringstream header; + header << "| "; + header << format_text("NAME", width[0], alignment::center) << " | "; + header << format_text("PROPERTIES", width[1], alignment::center) << " | "; + header << format_text("LOCATION", width[2], alignment::center) << " | "; + header << format_text("# READS", width[3], alignment::center) << " | "; + header << format_text("# WRITES", width[4], alignment::center) << " |"; + + auto header_len = header.str().length(); + auto spaces = std::string(n * 4, ' '); + auto separator_line = std::string(header_len, '-'); + + ss << "\n" << spaces << separator_line; + ss << "\n" << spaces; + ss << "|" << format_text(title, header_len - 2, alignment::center) << "|"; + ss << "\n" << spaces << separator_line; + ss << "\n" << spaces << header.str(); + ss << "\n" << spaces << separator_line; + + for (const auto& row : rows) { + ss << "\n" << spaces << "| "; + ss << format_text(row[0], width[0], alignment::left) << " | "; + ss << format_text(row[1], width[1], alignment::left) << " | "; + ss << format_text(row[2], width[2], alignment::right) << " | "; + ss << format_text(row[3], width[3], alignment::right) << " | "; + ss << format_text(row[4], width[4], alignment::right) << " |"; + } + ss << "\n" << spaces << separator_line << "\n"; + } + } + + void SymbolTable::print(std::stringstream& ss, int level) { + auto title = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; + title += "POSITION : " + position(); + title += global ? " SCOPE : GLOBAL" : " SCOPE : LOCAL"; + + table.print(ss, title, level); + + auto next_level = level; + if (table.symbols.size() == 0) { + next_level--; + } + + for (const auto& item : childrens) { + if (item.second->symbol_count() >= 0) { + (item.second)->print(ss, ++next_level); + next_level--; + } + } + } + + void ModelSymbolTable::print(std::stringstream& ss) { + symtab->print(ss, 0); + } +} // namespace symtab \ No newline at end of file diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp new file mode 100644 index 0000000000..367ef023f6 --- /dev/null +++ b/src/nmodl/symtab/symbol_table.hpp @@ -0,0 +1,209 @@ +#ifndef _NMODL_SYMTAB_HPP_ +#define _NMODL_SYMTAB_HPP_ + +#include <map> + +#include "symtab/symbol.hpp" + +namespace symtab { + + using namespace details; + using namespace ast; + + /** + * \class Table + * \brief Helper class for implementing symbol table + * + * Table is used to store information about every block construct + * encountered in the nmodl file. Each symbol has name but for fast lookup, + * we create map with the associated name. + * + * \todo Re-implement pretty printing + */ + class Table { + public: + /// map of symbol name and associated symbol for faster lookup + std::map<std::string, std::shared_ptr<Symbol>> symbols; + + /// insert new symbol into table + void insert(std::shared_ptr<Symbol> sym); + + /// check if symbol with given name exist + std::shared_ptr<Symbol> lookup(std::string name); + + /// pretty print + void print(std::stringstream& ss, std::string title, int indent); + }; + + /** + * \class SymbolTable + * \brief Represent symbol table for nmodl block + * + * Symbol Table is used to track information about every block construct + * encountered in the nmodl. In NMODL, block constructs are NEURON, + * PARAMETER NET_RECEIVE etc. Each block is considered a new scope. + * + * NMODL supports nested block definitions (i.e. nested blocks). One + * specific example of this is INITIAL block in NET_RECEIVE block. In this + * case we need multiple scopes for single top level block of NMODL. In + * the future if we enable block level scopes, we will need symbol tables + * per block. Hence we are implementing BlockSymbolTable which stores + * all symbol table information for specific NMODL block. Note that each + * BlockSymbolTable implementation is recursive because while symbol + * lookup we have to search first into local/current block. If lookup is + * unsuccessfull then we have to traverse parent blocks until the end. + * + * \todo Revisit when clone method is used and implementation of copy + * constructor + * \todo Name may not require as we have added AST node + */ + class SymbolTable { + private: + /// name of the block + std::string symtab_name; + + /// true if current symbol table is global + /// (blocks like NEURON, PARAMETER defines global variables) + bool global; + + /// table holding all symbols in the current block + Table table; + + /// pointer to ast node for which current block symbol created + AST* node = nullptr; + + /// pointer to the symbol table of parent block in the mod file + std::shared_ptr<SymbolTable> parent = nullptr; + + /// symbol table for each enclosing block in the current nmodl block + /// construct. for example, for every block statement (like if, while, + /// for) within function we append new symbol table. note that this is + /// also required for nested blocks like INITIAL in NET_RECEIVE. + std::map<std::string, std::shared_ptr<SymbolTable>> childrens; + + /// pretty print table + void print_table(std::stringstream& ss, int indent); + + public: + SymbolTable(std::string name, AST* node, bool global = false) + : symtab_name(name), node(node), global(global) { + } + + SymbolTable(const SymbolTable& table); + + std::string name() const { + return symtab_name; + } + + std::string type() const { + return node->getTypeName(); + } + + /// todo: set token for every block from parser + std::string position() const { + auto token = node->getToken(); + if (token) + return token->position(); + else + return ModToken().position(); + } + + bool global_scope() const { + return global; + } + + std::shared_ptr<SymbolTable> get_parent_table() const { + return parent; + } + + std::string get_parent_table_name() { + return parent ? parent->name() : "None"; + } + + std::shared_ptr<Symbol> lookup(std::string name) { + return table.lookup(name); + } + + void insert(std::shared_ptr<Symbol> symbol) { + table.insert(symbol); + } + + void set_parent_table(std::shared_ptr<SymbolTable> block) { + parent = block; + } + + int symbol_count() const { + return table.symbols.size(); + } + + SymbolTable* clone() { + return new SymbolTable(*this); + } + + std::shared_ptr<Symbol> lookup_in_scope(std::string name); + + std::vector<std::shared_ptr<Symbol>> get_global_variables(); + + bool under_global_scope(); + + void insert_table(std::string name, std::shared_ptr<SymbolTable> table); + + void print(std::stringstream& ss, int indent); + }; + + /** + * \class ModelSymbolTable + * \brief Represent symbol table for nmodl block + * + * SymbolTable is sufficinet to hold information about all symbols in the + * mod file. It might be sufficient to keep track of global symbol tables + * and local symbol tables. But we construct symbol table using visitor + * pass. In this case we visit ast and recursively create symbol table for + * each block scope. In this case, ModelSymbolTable is provide interface + * to build symbol table with visitor. + * + * \note For included mod file it's not clear yet whether we need to maintain + * separate ModelSymbolTable. + * \note See command project in compiler teaching course for details + * + * \todo Unique name should be based on location. Use ModToken to get position. + */ + class ModelSymbolTable { + private: + /// symbol table for mod file (always top level symbol table) + std::shared_ptr<SymbolTable> symtab = nullptr; + + /// symbol table for parent block (used during symbol table construction) + std::shared_ptr<SymbolTable> parent_symtab = nullptr; + + /// Return unique name by appending some counter value + std::string get_unique_name(std::string name, AST* node); + + /// todo : remove this + void finalize_scope() { + leave_scope(); + } + + public: + /// entering into new nmodl block + std::shared_ptr<SymbolTable> enter_scope(std::string name, AST* node, bool global); + + /// leaving current nmodl block + void leave_scope(); + + void insert(std::shared_ptr<Symbol> symbol); + + std::shared_ptr<Symbol> lookup(std::string name); + + /// todo : remove this + std::shared_ptr<SymbolTable> initialize_scope(std::string name, + AST* node, + bool global = false); + + /// pretty print + void print(std::stringstream& ss); + }; + +} // namespace symtab + +#endif \ No newline at end of file diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index c4c9d31299..e06d774e4c 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -6,6 +6,9 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ) set_source_files_properties( @@ -23,7 +26,7 @@ add_library(visitor add_dependencies(visitor lexer) add_executable(nocmodl_visitor main.cpp) -target_link_libraries(nocmodl_visitor lexer printer visitor) +target_link_libraries(nocmodl_visitor printer visitor symtab lexer) #============================================================================= # Files for clang-format @@ -33,4 +36,4 @@ set(FILES_FOR_CLANG_FORMAT ${VISITOR_SOURCE_FILES} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE -) \ No newline at end of file +) diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index ca1a407c34..734356cf90 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -6,9 +6,12 @@ #include "visitors/ast_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #include "tclap/CmdLine.h" +using namespace symtab; + /** * Standlone visitor program for NMODL. This demonstrate basic * usage of different visitors classes and driver class. @@ -75,6 +78,23 @@ int main(int argc, char* argv[]) { std::cout << ss.str(); } } + + { + ModelSymbolTable symtab; + std::stringstream ss1; + + SymtabVisitor v(&symtab, ss1); + v.visitProgram(ast.get()); + + // std::cout << ss1.str(); + + std::stringstream ss2; + symtab.print(ss2); + std::cout << ss2.str(); + + std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; + } + } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp new file mode 100644 index 0000000000..5028ea2f60 --- /dev/null +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -0,0 +1,57 @@ +#ifndef _SYMTAB_VISITOR_HELPER_HPP_ +#define _SYMTAB_VISITOR_HELPER_HPP_ + +#include "lexer/token_mapping.hpp" +#include "symtab/symbol_table.hpp" + +template <typename T> +void SymtabVisitor::setupSymbol(T* node, symtab::details::NmodlInfo property, int order) { + std::shared_ptr<Symbol> symbol; + auto token = node->getToken(); + + if (order) { + symbol = modsymtab->lookup(node->getName()); + if (symbol) { + symbol->set_order(order); + symbol->add_property(property); + return; + } + } + + symbol = std::make_shared<Symbol>(node->getName(), node, *token); + symbol->add_property(property); + modsymtab->insert(symbol); + node->visitChildren(this); +} + +template <typename T> +void SymtabVisitor::setupSymbolTable(T* node, + symtab::details::NmodlInfo property, + bool global_block) { + auto token = node->getToken(); + auto symbol = std::make_shared<Symbol>(node->getName(), node, *token); + symbol->add_property(property); + modsymtab->insert(symbol); + setupSymbolTable(node, global_block); +} + +template <typename T> +void SymtabVisitor::setupSymbolTable(T* node, bool global_block) { + auto symtab = modsymtab->initialize_scope(node->getName(), node, global_block); + node->setBlockSymbolTable(symtab); + + if (node->isProgram()) { + auto variables = nmodl::all_external_variables(); + for (auto variable : variables) { + ModToken tok(true); + auto symbol = std::make_shared<Symbol>(variable, nullptr, tok); + symbol->add_property(symtab::details::NmodlInfo::extern_token); + modsymtab->insert(symbol); + } + } + + node->visitChildren(this); + modsymtab->leave_scope(); +} + +#endif \ No newline at end of file diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 9ef90aecdd..ec18caf2bb 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -11,16 +11,19 @@ add_executable (testmodtoken modtoken/modtoken.cpp) add_executable (testlexer lexer/tokens.cpp) add_executable (testvisitor visitor/visitor.cpp) add_executable (testprinter printer/printer.cpp) +add_executable (testsymtab symtab/symbol_properties.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) target_link_libraries(testvisitor lexer printer visitor) target_link_libraries(testprinter printer) +target_link_libraries(testsymtab symtab) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) add_test (NAME Visitor COMMAND testvisitor) add_test (NAME Printer COMMAND testprinter) +add_test (NAME Symtab COMMAND testsymtab) #============================================================================= # Files for clang-format diff --git a/test/nmodl/transpiler/symtab/symbol_properties.cpp b/test/nmodl/transpiler/symtab/symbol_properties.cpp new file mode 100644 index 0000000000..8817706e45 --- /dev/null +++ b/test/nmodl/transpiler/symtab/symbol_properties.cpp @@ -0,0 +1,43 @@ +#define CATCH_CONFIG_MAIN + +#include <string> + +#include "catch/catch.hpp" +#include "symtab/symbol_properties.hpp" + +using namespace symtab::details; + +extern bool has_property(const SymbolInfo& obj, NmodlInfo property); + +//============================================================================= +// Symbol properties tests +//============================================================================= + +TEST_CASE("Symbol Properties") { + + SECTION("Properties Operation 1") { + SymbolInfo properties; + REQUIRE(to_string(properties) == ""); + + properties |= NmodlInfo::local_var; + REQUIRE(to_string(properties) == "local"); + + properties |= NmodlInfo::write_ion_var; + REQUIRE(to_string(properties) == "local write_ion"); + + REQUIRE(has_property(properties, NmodlInfo::local_var) == true); + REQUIRE(has_property(properties, NmodlInfo::global_var) == false); + } + + SECTION("Properties Operation 2") { + SymbolInfo prop1 = (NmodlInfo::local_var | NmodlInfo::global_var); + SymbolInfo prop2 = NmodlInfo::local_var; + + REQUIRE(prop1 != prop2); + + prop2 |= NmodlInfo::global_var; + + REQUIRE(prop1 == prop2); + } + +} \ No newline at end of file From 119eb5e76a1e5b4cfe0bc2006eb6be96cc2c644f Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 10 Dec 2017 18:49:48 +0100 Subject: [PATCH 029/871] Converted symbol table test to BDD style - Added test for symbol class - Added test for symbol table class - Added test for model symbol table class More information here: https://github.com/catchorg/Catch2/blob/master/docs/tutorial.md#bdd-style Change-Id: I29f990d39a7d27557ba0e961da64fe2d21d2a63a NMODL Repo SHA: BlueBrain/nmodl@d6a73f9ac1df32d0ff735250e765c60ba17c83c0 --- src/nmodl/language/visitors_printer.py | 9 +- src/nmodl/symtab/symbol.cpp | 4 + src/nmodl/symtab/symbol.hpp | 4 +- src/nmodl/symtab/symbol_table.cpp | 106 +++++--- src/nmodl/symtab/symbol_table.hpp | 11 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 5 +- .../transpiler/symtab/symbol_properties.cpp | 43 --- test/nmodl/transpiler/symtab/symbol_table.cpp | 254 ++++++++++++++++++ 9 files changed, 336 insertions(+), 102 deletions(-) delete mode 100644 test/nmodl/transpiler/symtab/symbol_properties.cpp create mode 100644 test/nmodl/transpiler/symtab/symbol_table.cpp diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 1defce4734..5f3473d193 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -186,19 +186,18 @@ def public_declaration(self): self.writer.write_line("void setupSymbol(T* node, symtab::details::NmodlInfo property, int order = 0);", newline=2) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, bool global_block);" + line = "void setupSymbolTable(T *node, bool global);" self.writer.write_line(line) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, symtab::details::NmodlInfo property, bool global_block);" + line = "void setupSymbolTable(T *node, symtab::details::NmodlInfo property, bool global);" self.writer.write_line(line, newline=2) for node in self.nodes: if node.is_symtab_method_required(): - line = "virtual void visit" + node.class_name + "(" + node.class_name + "* node) override;" + line = "void visit" + node.class_name + "(" + node.class_name + "* node) override;" self.writer.write_line(line) - self.writer.decrease_gutter() def post_declaration(self): @@ -230,7 +229,7 @@ def definitions(self): self.writer.write_line("setupSymbol(node, " + property_name + is_prime + ");") else: - """ setupBlock has node*, symbol*, symbol_block, program_block, global_block""" + """ setupBlock has node*, properties, global_block""" if node.is_symbol_block_node(): fun_call = "setupSymbolTable(node, " + property_name + ", false);" diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 2db0920bca..dac0589701 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -18,6 +18,10 @@ namespace symtab { properties = properties | property; } + void Symbol::add_property(SymbolInfo& property) { + properties = properties | property; + } + /** Prime variable will appear in different block and could have * multiple derivative orders. We have to store highest order. * diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index dba7790fab..3babcd4280 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -104,8 +104,6 @@ namespace symtab { return write_count; } - void set_as_extern_token(); - bool is_extern_token_only(); bool has_common_properties(SymbolInfo& prop); @@ -114,6 +112,8 @@ namespace symtab { void add_property(NmodlInfo property); + void add_property(SymbolInfo& property); + void set_order(int order); }; diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 345a1c1cdb..650a61d5fc 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -30,6 +30,13 @@ namespace symtab { parent = nullptr; } + void SymbolTable::insert_table(std::string name, std::shared_ptr<SymbolTable> table) { + if (childrens.find(name) != childrens.end()) { + throw std::runtime_error("Trying to re-insert SymbolTable " + name); + } + childrens[name] = table; + } + /** Get all symbols which can be used in global scope. Note that * this is different from GLOBAL variable type in the mod file. Here * global meaning all variables that can be used in entire mod file. @@ -44,8 +51,9 @@ namespace symtab { std::vector<std::shared_ptr<Symbol>> variables; for (auto& syminfo : table.symbols) { auto symbol = syminfo.second; - auto node = symbol->get_node(); - if ((node->isRangeVar() || node->isDependentDef() || node->isParamAssign())) { + SymbolInfo property = NmodlInfo::range_var; + property |= NmodlInfo::dependent_def | NmodlInfo::param_assign; + if (symbol->has_common_properties(property)) { variables.push_back(symbol); } } @@ -111,14 +119,11 @@ namespace symtab { return symbol; } - void SymbolTable::insert_table(std::string name, std::shared_ptr<SymbolTable> table) { - if (childrens.find(name) != childrens.end()) { - throw std::runtime_error("Trying to re-insert SymbolTable " + name); + void ModelSymbolTable::insert(std::shared_ptr<Symbol> symbol) { + if (parent_symtab == nullptr) { + throw std::logic_error("Can not insert symbol without entering scope"); } - childrens[name] = table; - } - void ModelSymbolTable::insert(std::shared_ptr<Symbol> symbol) { symbol->set_scope(parent_symtab->name()); std::string name = symbol->get_name(); auto search_symbol = lookup(name); @@ -131,7 +136,11 @@ namespace symtab { // properties and type information for error reporting auto properties = to_string(search_symbol->get_properties()); - auto type = symbol->get_node()->getTypeName(); + auto node = symbol->get_node(); + std::string type = "UNKNOWN"; + if (node) { + type = node->getTypeName(); + } /** For global symbol tables, same variable can appear in multiple * nmodl "global" blocks. It's an error if it appears multiple times @@ -139,12 +148,14 @@ namespace symtab { * which are bit flags. If they are not same that means, we have to * add new properties to existing symbol. If the properties are same * that means symbol are duplicate. + * + * \todo Error handling should go to logger instead of exception */ if (parent_symtab->global_scope()) { if (search_symbol->has_common_properties(symbol->get_properties())) { - std::string msg = "SYMTAB ERROR: _NMODL_GLOBAL_ has re-declaration of "; + std::string msg = "_NMODL_GLOBAL_ has re-declaration of "; msg += name + " <" + type + "> " + properties; - std::cout << msg << "\n"; + throw std::runtime_error(msg); } else { search_symbol->combine_properties(symbol->get_properties()); } @@ -156,10 +167,10 @@ namespace symtab { * emit the warning and insert the symbol. */ if (search_symbol->get_scope() == parent_symtab->name()) { - std::string msg = "SYMTAB ERROR: Re-declaration of " + name; - msg += " [" + type + "]" + " <" + properties + "> in "; - msg += parent_symtab->name() + " with one in " + search_symbol->get_scope(); - std::cout << msg << "\n"; + std::string msg = "Re-declaration of " + name + " [" + type + "]"; + msg += "<" + properties + "> in " + parent_symtab->name(); + msg += " with one in " + search_symbol->get_scope(); + throw std::runtime_error(msg); } else { std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; msg += parent_symtab->name() + " shadows <" + properties; @@ -183,16 +194,6 @@ namespace symtab { return name; } - /// \todo : Remove this: used from setupBlock which could also use enter_scope - std::shared_ptr<SymbolTable> ModelSymbolTable::initialize_scope(std::string scope, - AST* node, - bool global) { - if (global) { - scope = "_NMODL_GLOBAL_"; - } - return enter_scope(scope, node, global); - } - /** Callback at the entry of every block in nmodl file * Every block starts a new scope and hence new symbol table is created. * The same symbol table is returned so that visitor can store pointer to @@ -201,12 +202,21 @@ namespace symtab { std::shared_ptr<SymbolTable> ModelSymbolTable::enter_scope(std::string name, AST* node, bool global) { + if (node == nullptr) { + throw std::runtime_error("Can't enter with empty node"); + } + /// all global blocks in mod file have same symbol table - if (symtab && name == "_NMODL_GLOBAL_") { + if (symtab && global) { parent_symtab = symtab; return parent_symtab; } + /// change name for global symbol table + if (global) { + name = global_symtab_name; + } + /// statement block within global scope is part of global block itself if (symtab && node->isStatementBlock() && parent_symtab->under_global_scope()) { parent_symtab = symtab; @@ -236,10 +246,18 @@ namespace symtab { * same symbol table. Otherwise traverse back to prent. */ void ModelSymbolTable::leave_scope() { + if (parent_symtab == nullptr && symtab == nullptr) { + throw std::logic_error("Trying leave scope without entering"); + } + + /// if has parent scope, setup new parent symbol table if (parent_symtab) { parent_symtab = parent_symtab->get_parent_table(); } + /// \todo : when parent is nullptr, parent should not be reset to + /// current symbol table. this is happening for global + /// scope symbol table if (parent_symtab == nullptr) { parent_symtab = symtab; } @@ -255,17 +273,19 @@ namespace symtab { std::string format_text(std::string text, int width, alignment type) { std::string left, right; // count excess room to pad - auto padding = width - text.size(); - if (type == alignment::left) { - right = std::string(padding, ' '); - } else if (type == alignment::right) { - left = std::string(padding, ' '); - } else { - left = std::string(padding / 2, ' '); - right = std::string(padding / 2, ' '); - // if odd #, add one more space - if (padding > 0 && padding % 2 != 0) { - right += " "; + int padding = width - text.size(); + if (padding > 0) { + if (type == alignment::left) { + right = std::string(padding, ' '); + } else if (type == alignment::right) { + left = std::string(padding, ' '); + } else { + left = std::string(padding / 2, ' '); + right = std::string(padding / 2, ' '); + // if odd #, add one more space + if (padding > 0 && padding % 2 != 0) { + right += " "; + } } } return left + text + right; @@ -332,17 +352,23 @@ namespace symtab { } void SymbolTable::print(std::stringstream& ss, int level) { - auto title = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; - title += "POSITION : " + position(); - title += global ? " SCOPE : GLOBAL" : " SCOPE : LOCAL"; + /// construct title for symbol table + auto name = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; + auto location = "POSITION : " + position(); + auto scope = global ? "GLOBAL" : "LOCAL"; + auto title = name + location + " SCOPE : " + scope; table.print(ss, title, level); + /// when current symbol table is empty, the childrens + /// can be printed from the same indentation level + /// (this is to avoid unnecessary empty indentations) auto next_level = level; if (table.symbols.size() == 0) { next_level--; } + /// recursively print all childrens for (const auto& item : childrens) { if (item.second->symbol_count() >= 0) { (item.second)->print(ss, ++next_level); diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 367ef023f6..f7d4a14915 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -179,10 +179,8 @@ namespace symtab { /// Return unique name by appending some counter value std::string get_unique_name(std::string name, AST* node); - /// todo : remove this - void finalize_scope() { - leave_scope(); - } + /// name of top level global symbol table + const std::string global_symtab_name = "_NMODL_GLOBAL_"; public: /// entering into new nmodl block @@ -195,11 +193,6 @@ namespace symtab { std::shared_ptr<Symbol> lookup(std::string name); - /// todo : remove this - std::shared_ptr<SymbolTable> initialize_scope(std::string name, - AST* node, - bool global = false); - /// pretty print void print(std::stringstream& ss); }; diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 5028ea2f60..067f64b8c2 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -37,7 +37,7 @@ void SymtabVisitor::setupSymbolTable(T* node, template <typename T> void SymtabVisitor::setupSymbolTable(T* node, bool global_block) { - auto symtab = modsymtab->initialize_scope(node->getName(), node, global_block); + auto symtab = modsymtab->enter_scope(node->getName(), node, global_block); node->setBlockSymbolTable(symtab); if (node->isProgram()) { diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index ec18caf2bb..11d3fa064d 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -11,13 +11,13 @@ add_executable (testmodtoken modtoken/modtoken.cpp) add_executable (testlexer lexer/tokens.cpp) add_executable (testvisitor visitor/visitor.cpp) add_executable (testprinter printer/printer.cpp) -add_executable (testsymtab symtab/symbol_properties.cpp) +add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) target_link_libraries(testvisitor lexer printer visitor) target_link_libraries(testprinter printer) -target_link_libraries(testsymtab symtab) +target_link_libraries(testsymtab symtab lexer) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) @@ -33,6 +33,7 @@ set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/lexer/tokens.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor/visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/printer/printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab/symbol_table.cpp ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) \ No newline at end of file diff --git a/test/nmodl/transpiler/symtab/symbol_properties.cpp b/test/nmodl/transpiler/symtab/symbol_properties.cpp deleted file mode 100644 index 8817706e45..0000000000 --- a/test/nmodl/transpiler/symtab/symbol_properties.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#define CATCH_CONFIG_MAIN - -#include <string> - -#include "catch/catch.hpp" -#include "symtab/symbol_properties.hpp" - -using namespace symtab::details; - -extern bool has_property(const SymbolInfo& obj, NmodlInfo property); - -//============================================================================= -// Symbol properties tests -//============================================================================= - -TEST_CASE("Symbol Properties") { - - SECTION("Properties Operation 1") { - SymbolInfo properties; - REQUIRE(to_string(properties) == ""); - - properties |= NmodlInfo::local_var; - REQUIRE(to_string(properties) == "local"); - - properties |= NmodlInfo::write_ion_var; - REQUIRE(to_string(properties) == "local write_ion"); - - REQUIRE(has_property(properties, NmodlInfo::local_var) == true); - REQUIRE(has_property(properties, NmodlInfo::global_var) == false); - } - - SECTION("Properties Operation 2") { - SymbolInfo prop1 = (NmodlInfo::local_var | NmodlInfo::global_var); - SymbolInfo prop2 = NmodlInfo::local_var; - - REQUIRE(prop1 != prop2); - - prop2 |= NmodlInfo::global_var; - - REQUIRE(prop1 == prop2); - } - -} \ No newline at end of file diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp new file mode 100644 index 0000000000..e0450a2dbe --- /dev/null +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -0,0 +1,254 @@ +#define CATCH_CONFIG_MAIN + +#include <string> + +#include "catch/catch.hpp" +#include "symtab/symbol.hpp" +#include "symtab/symbol_table.hpp" + +using namespace symtab; +using namespace symtab::details; + +extern bool has_property(const SymbolInfo& obj, NmodlInfo property); + +//============================================================================= +// Symbol properties test +//============================================================================= + +SCENARIO("Symbol properties can be added and converted to string") { + SymbolInfo prop1{flags::empty}; + SymbolInfo prop2 = NmodlInfo::local_var; + SymbolInfo prop3 = NmodlInfo::global_var; + + GIVEN("A empty property") { + WHEN("converted to string") { + THEN("returns empty string") { + REQUIRE(to_string(prop1) == ""); + } + } + WHEN("checked for property") { + THEN("doesn't have any property") { + REQUIRE_FALSE(has_property(prop1, NmodlInfo::local_var)); + } + } + WHEN("adding another empty property") { + SymbolInfo result = prop1 | prop1; + THEN("to_string still returns empty string") { + REQUIRE(to_string(result) == ""); + } + } + WHEN("added some other property") { + SymbolInfo result = prop1 | prop2; + THEN("to_string returns added property") { + REQUIRE(to_string(result) == "local"); + } + WHEN("checked for property") { + THEN("has required property") { + REQUIRE(has_property(result, NmodlInfo::local_var) == true); + } + } + } + WHEN("added multiple properties") { + SymbolInfo result = prop1 | prop2 | prop3; + result |= NmodlInfo::write_ion_var; + THEN("to_string returns all added properties") { + REQUIRE_THAT(to_string(result), Catch::Contains("local")); + REQUIRE_THAT(to_string(result), Catch::Contains("global")); + REQUIRE_THAT(to_string(result), Catch::Contains("write_ion")); + } + WHEN("checked for property") { + THEN("has all added properties") { + REQUIRE(has_property(result, NmodlInfo::local_var) == true); + REQUIRE(has_property(result, NmodlInfo::global_var) == true); + REQUIRE(has_property(result, NmodlInfo::write_ion_var) == true); + REQUIRE_FALSE(has_property(result, NmodlInfo::read_ion_var)); + } + } + } + } +} + +//============================================================================= +// Symbol test +//============================================================================= + +SCENARIO("Symbol operations") { + SymbolInfo property1 = NmodlInfo::argument; + SymbolInfo property2 = NmodlInfo::range_var; + GIVEN("A symbol") { + ModToken token(true); + Symbol symbol("alpha", token); + WHEN("added external property") { + symbol.add_property(NmodlInfo::extern_token); + THEN("symbol becomes external") { + REQUIRE(symbol.is_extern_token_only() == true); + } + } + WHEN("added multiple properties to symbol") { + symbol.add_property(property1); + symbol.add_property(property2); + THEN("symbol has multiple properties") { + REQUIRE(symbol.has_common_properties(property1) == true); + + SymbolInfo property = NmodlInfo::param_assign; + REQUIRE(symbol.has_common_properties(property) == false); + + symbol.add_property(NmodlInfo::param_assign); + REQUIRE(symbol.has_common_properties(property) == true); + } + } + WHEN("combined properties") { + SymbolInfo property = NmodlInfo::factor_def | NmodlInfo::global_var; + THEN("symbol has union of all properties") { + REQUIRE(symbol.has_common_properties(property) == false); + symbol.combine_properties(property); + REQUIRE(symbol.has_common_properties(property) == true); + property |= symbol.get_properties(); + REQUIRE(symbol.get_properties() == property); + } + } + } +} + +//============================================================================= +// Symbol table test +//============================================================================= + +SCENARIO("Symbol table operations") { + GIVEN("A global SymbolTable") { + auto program = std::make_shared<ast::Program>(); + auto table = std::make_shared<SymbolTable>("Na", program.get(), true); + auto symbol = std::make_shared<Symbol>("alpha", ModToken()); + + WHEN("checked methods and member variables") { + THEN("all members are initialized") { + REQUIRE(table->under_global_scope()); + REQUIRE_THAT(table->name(), Catch::Contains("Na")); + REQUIRE_THAT(table->type(), Catch::Contains("Program")); + REQUIRE_THAT(table->get_parent_table_name(), Catch::Contains("None")); + REQUIRE_THAT(table->position(), Catch::Contains("UNKNOWN")); + } + } + WHEN("insert symbol") { + table->insert(symbol); + THEN("table size increases") { + REQUIRE(table->symbol_count() == 1); + } + THEN("lookup returns a inserted symbol") { + REQUIRE(table->lookup("alpha") != nullptr); + REQUIRE(table->lookup("beta") == nullptr); + } + WHEN("re-inserting the same symbol") { + THEN("throws an exception") { + REQUIRE_THROWS_WITH(table->insert(symbol), Catch::Contains("re-insert")); + } + } + WHEN("inserting another symbol") { + auto next_symbol = std::make_shared<Symbol>("beta", ModToken()); + table->insert(next_symbol); + THEN("symbol gets added and table size increases") { + REQUIRE(table->symbol_count() == 2); + REQUIRE(table->lookup("beta") != nullptr); + } + } + } + WHEN("checked for global variables") { + table->insert(symbol); + auto variables = table->get_global_variables(); + THEN("table doesn't have any global variables") { + REQUIRE(variables.size() == 0); + WHEN("added global symbol") { + auto next_symbol = std::make_shared<Symbol>("gamma", ModToken()); + next_symbol->add_property(NmodlInfo::dependent_def); + table->insert(next_symbol); + auto variables = table->get_global_variables(); + THEN("table has global variable") { + REQUIRE(variables.size() == 1); + } + } + } + } + WHEN("added another symbol table as children") { + table->insert(symbol); + auto next_program = std::make_shared<ast::Program>(); + auto next_table = std::make_shared<SymbolTable>("Ca", next_program.get(), true); + next_table->set_parent_table(table); + THEN("children symbol table can lookup into parent table scope") { + REQUIRE(next_table->lookup("alpha") == nullptr); + REQUIRE(next_table->lookup_in_scope("alpha") != nullptr); + } + } + } +} + +//============================================================================= +// Model symbol table test +//============================================================================= + +SCENARIO("Model symbol table operations") { + GIVEN("A Model symbolTable") { + ModelSymbolTable mod_symtab; + auto program = std::make_shared<ast::Program>(); + auto symbol1 = std::make_shared<Symbol>("alpha", ModToken()); + auto symbol2 = std::make_shared<Symbol>("alpha", ModToken()); + auto symbol3 = std::make_shared<Symbol>("alpha", ModToken()); + symbol1->add_property(NmodlInfo::param_assign); + symbol2->add_property(NmodlInfo::range_var); + symbol3->add_property(NmodlInfo::range_var); + + WHEN("trying to exit scope without entering") { + THEN("throws an exception") { + REQUIRE_THROWS_WITH(mod_symtab.leave_scope(), Catch::Contains("without entering")); + } + } + WHEN("trying to enter scope without valid node") { + THEN("throws an exception") { + REQUIRE_THROWS_WITH(mod_symtab.enter_scope("scope", nullptr, true), + Catch::Contains("empty node")); + } + } + WHEN("trying to insert without entering scope") { + THEN("throws an exception") { + auto symbol = std::make_shared<Symbol>("alpha", ModToken()); + REQUIRE_THROWS_WITH(mod_symtab.insert(symbol), Catch::Contains("Can not insert")); + } + } + WHEN("enter scope multipel times") { + auto program1 = std::make_shared<ast::Program>(); + auto program2 = std::make_shared<ast::Program>(); + mod_symtab.enter_scope("scope1", program1.get(), false); + mod_symtab.enter_scope("scope2", program2.get(), false); + THEN("can leave scope multiple times") { + mod_symtab.leave_scope(); + mod_symtab.leave_scope(); + } + } + WHEN("added same symbol with different properties in global scope") { + mod_symtab.enter_scope("scope", program.get(), true); + mod_symtab.insert(symbol1); + mod_symtab.insert(symbol2); + THEN("only one symbol gets added with combined properties") { + auto symbol = mod_symtab.lookup("alpha"); + auto properties = NmodlInfo::param_assign | NmodlInfo::range_var; + REQUIRE(symbol->get_properties() == properties); + } + } + WHEN("added same symbol with exisiting property") { + mod_symtab.enter_scope("scope", program.get(), true); + mod_symtab.insert(symbol1); + mod_symtab.insert(symbol2); + THEN("throws an exception") { + REQUIRE_THROWS_WITH(mod_symtab.insert(symbol3), Catch::Contains("re-declaration")); + } + } + WHEN("added same symbol in children scope") { + mod_symtab.enter_scope("scope1", program.get(), true); + mod_symtab.insert(symbol2); + THEN("it's ok, just get overshadow warning") { + mod_symtab.enter_scope("scope2", program.get(), false); + mod_symtab.insert(symbol3); + ///\todo : not sure how to capture std::cout + } + } + } +} \ No newline at end of file From e1cc6b6e86c3e7f537f90c63f424befe73063a22 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 12 Dec 2017 10:14:41 +0100 Subject: [PATCH 030/871] Refactored and cleanup : - Removed un-necessary getName() method implemented by every node which was returning typename. - Symbol table visitor updated to provide correct name. - Documentation and refactoring Change-Id: I0c54eadce03446d073c332f7895ce6f3a33cd7fc NMODL Repo SHA: BlueBrain/nmodl@9e311c432bf6fe59de50304079bba10f1f317ab1 --- src/nmodl/language/ast_printer.py | 9 +----- src/nmodl/language/utils.py | 5 ++- src/nmodl/language/visitors_printer.py | 22 +++++++++---- src/nmodl/symtab/symbol.cpp | 13 +++++--- src/nmodl/symtab/symbol.hpp | 2 +- src/nmodl/symtab/symbol_properties.cpp | 2 ++ src/nmodl/symtab/symbol_table.cpp | 32 ++++++++++++------- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 31 ++++++++++++++---- test/nmodl/transpiler/input/synapse.mod | 2 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 10 +++--- 11 files changed, 84 insertions(+), 46 deletions(-) diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 8c0c4572e0..555c229551 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -183,16 +183,9 @@ def ast_classes_declaration(self): if node.is_prime_node() and child.varname == ORDER_VAR_NAME: self.writer.write_line("int getOrder() " + " { return " + ORDER_VAR_NAME + "->eval(); }") - # TODO: returning typename for name? check the usage of this and fix in better way + # add method to return typename self.writer.write_line("virtual std::string getTypeName() override { return \"" + node.class_name + "\"; }") - # TODO : symbol table needs to know the name of every node - # For nodes without any children with name, name is typename itself - if not get_method_added: - self.writer.write_line("virtual std::string getName() override {") - self.writer.write_line(" return getTypeName();") - self.writer.write_line("}") - # all member functions self.writer.write_line(virtual + "void visitChildren (Visitor* v) override;") self.writer.write_line(virtual + "void accept(Visitor* v) override { v->visit" + node.class_name + "(this); }") diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index 1e26b2f1ce..14f6ad65bc 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -1,12 +1,15 @@ import re +# convert string of the form "AabcDef" to "Abc_Def" def camel_case_to_underscore(name): s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) return typename +# convert string of the form "AabcDef" to "ABC_DEF" def node_ast_type(name): return camel_case_to_underscore(name).upper() +# convert string of the form "AabcDef" to "abc_def" def node_property_type(name): - return camel_case_to_underscore(name).lower() + return camel_case_to_underscore(name).lower() \ No newline at end of file diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 5f3473d193..965221cb49 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -158,7 +158,7 @@ def headers(self): self.writer.write_line(line, newline=2) def class_comment(self): - self.writer.write_line("/* Concrete visitor for printing Symbol table in JSON format */") + self.writer.write_line("/* Concrete visitor for constructing symbol table from AST */") def class_name_declaration(self): @@ -182,17 +182,24 @@ def public_declaration(self): line = self.classname + "( ModelSymbolTable* symtab, std::string filename) : modsymtab(symtab), printer(new JSONPrinter(filename)) {}" self.writer.write_line(line, newline=2) + # helper function for creating symbol for variables self.writer.write_line("template<typename T>") - self.writer.write_line("void setupSymbol(T* node, symtab::details::NmodlInfo property, int order = 0);", newline=2) + self.writer.write_line("void setupSymbol(T* node, SymbolInfo property, int order = 0);", newline=2) + # helper function for creating symbol table for blocks + # without name (e.g. parameter, unit, breakpoint) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, bool global);" + line = "void setupSymbolTable(T *node, std::string name, bool global);" self.writer.write_line(line) + # helper function for creating symbol table for blocks + # with name (e.g. procedure, function, derivative) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, symtab::details::NmodlInfo property, bool global);" + line = "void setupSymbolTable(T *node, std::string name, SymbolInfo property, bool global);" self.writer.write_line(line, newline=2) + # we have to override visitor methods for the nodes + # which goes into symbol table for node in self.nodes: if node.is_symtab_method_required(): line = "void visit" + node.class_name + "(" + node.class_name + "* node) override;" @@ -201,6 +208,7 @@ def public_declaration(self): self.writer.decrease_gutter() def post_declaration(self): + # helper function definitions self.writer.write_line() self.writer.write_line('#include "visitors/symtab_visitor_helper.hpp"') @@ -231,14 +239,14 @@ def definitions(self): else: """ setupBlock has node*, properties, global_block""" if node.is_symbol_block_node(): - fun_call = "setupSymbolTable(node, " + property_name + ", false);" + fun_call = "setupSymbolTable(node, node->getName(), " + property_name + ", false);" elif node.is_program_node() or node.is_global_block_node(): - fun_call = "setupSymbolTable(node, true);" + fun_call = "setupSymbolTable(node, node->getTypeName(), true);" else: """this is for nodes which has parent class as Block node""" - fun_call = "setupSymbolTable(node, false);" + fun_call = "setupSymbolTable(node, node->getTypeName(), false);" self.writer.write_line(fun_call) self.writer.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index dac0589701..ef98ce5bbb 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -2,24 +2,29 @@ namespace symtab { + /// if symbol has only extern_token property : this is true + /// for symbols which are external variables from neuron + /// like v, t, dt etc. bool Symbol::is_extern_token_only() { return (properties == details::NmodlInfo::extern_token); } - bool Symbol::has_common_properties(SymbolInfo& new_properties) { + /// check if symbol has any of the common properties + bool Symbol::has_properties(SymbolInfo& new_properties) { return static_cast<bool>(properties & new_properties); } + /// add new properties to symbol with bitwise or void Symbol::combine_properties(SymbolInfo& new_properties) { - properties = properties | new_properties; + properties |= new_properties; } void Symbol::add_property(NmodlInfo property) { - properties = properties | property; + properties |= property; } void Symbol::add_property(SymbolInfo& property) { - properties = properties | property; + properties |= property; } /** Prime variable will appear in different block and could have diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 3babcd4280..3e064f7d26 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -106,7 +106,7 @@ namespace symtab { bool is_extern_token_only(); - bool has_common_properties(SymbolInfo& prop); + bool has_properties(SymbolInfo& prop); void combine_properties(SymbolInfo& prop); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 0a07d2dcb8..4c014ac111 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -6,10 +6,12 @@ using namespace symtab::details; +/// check if any property is set bool has_property(const SymbolInfo& obj, NmodlInfo property) { return static_cast<bool>(obj & property); } +/// helper function to convert properties to string std::vector<std::string> to_string_vector(const SymbolInfo& obj) { std::vector<std::string> properties; diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 650a61d5fc..3085b26e79 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -16,11 +16,11 @@ namespace symtab { symbols[name] = symbol; } - std::shared_ptr<Symbol> Table::lookup(std::string sname) { - if (symbols.find(sname) == symbols.end()) { + std::shared_ptr<Symbol> Table::lookup(std::string name) { + if (symbols.find(name) == symbols.end()) { return nullptr; } - return symbols[sname]; + return symbols[name]; } SymbolTable::SymbolTable(const SymbolTable& table) { @@ -30,6 +30,7 @@ namespace symtab { parent = nullptr; } + /// insert new symbol table of one of the children block void SymbolTable::insert_table(std::string name, std::shared_ptr<SymbolTable> table) { if (childrens.find(name) != childrens.end()) { throw std::runtime_error("Trying to re-insert SymbolTable " + name); @@ -53,7 +54,7 @@ namespace symtab { auto symbol = syminfo.second; SymbolInfo property = NmodlInfo::range_var; property |= NmodlInfo::dependent_def | NmodlInfo::param_assign; - if (symbol->has_common_properties(property)) { + if (symbol->has_properties(property)) { variables.push_back(symbol); } } @@ -95,9 +96,15 @@ namespace symtab { } /// lookup in all parents symbol table - /// \todo not sure why we are searching in parent symtab only. Also we could - /// also use lookup_in_scope partly. revisit this usage. + /// \todo parent_symtab is somewhat misleading. it's actually + /// current symbol table that is under process. we should + /// rename it. note that we still need parent symbol table + /// filed to traverse the parents. revisit this usage. std::shared_ptr<Symbol> ModelSymbolTable::lookup(std::string name) { + + /// parent symbol is not set means symbol table is + /// is not used with visitor at all. it would be ok + // to just return nullptr? if (!parent_symtab) { throw std::logic_error("Lookup wit previous symtab = nullptr "); } @@ -107,7 +114,7 @@ namespace symtab { return symbol; } - // check into all parents + // check into all parent symbol tables auto parent = parent_symtab->get_parent_table(); while (parent) { symbol = parent->lookup(name); @@ -152,8 +159,8 @@ namespace symtab { * \todo Error handling should go to logger instead of exception */ if (parent_symtab->global_scope()) { - if (search_symbol->has_common_properties(symbol->get_properties())) { - std::string msg = "_NMODL_GLOBAL_ has re-declaration of "; + if (search_symbol->has_properties(symbol->get_properties())) { + std::string msg = GLOBAL_SYMTAB_BANE + " has re-declaration of "; msg += name + " <" + type + "> " + properties; throw std::runtime_error(msg); } else { @@ -194,7 +201,7 @@ namespace symtab { return name; } - /** Callback at the entry of every block in nmodl file + /** Function callback at the entry of every block in nmodl file * Every block starts a new scope and hence new symbol table is created. * The same symbol table is returned so that visitor can store pointer to * symbol table within a node. @@ -214,7 +221,7 @@ namespace symtab { /// change name for global symbol table if (global) { - name = global_symtab_name; + name = GLOBAL_SYMTAB_BANE; } /// statement block within global scope is part of global block itself @@ -264,7 +271,8 @@ namespace symtab { } //============================================================================= - // Revisit implementation of all below symbol table printing functions + // Revisit implementation of symbol table pretty printing functions : should + // be refactored into separate table printer function. //============================================================================= enum class alignment { left, right, center }; diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index f7d4a14915..54eaddd2c7 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -180,7 +180,7 @@ namespace symtab { std::string get_unique_name(std::string name, AST* node); /// name of top level global symbol table - const std::string global_symtab_name = "_NMODL_GLOBAL_"; + const std::string GLOBAL_SYMTAB_BANE = "NMODL_GLOBAL"; public: /// entering into new nmodl block diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 067f64b8c2..ce7e8335a4 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -4,11 +4,15 @@ #include "lexer/token_mapping.hpp" #include "symtab/symbol_table.hpp" +/// helper function to setup/insert symbol into symbol table +/// for the ast nodes which are of variable types template <typename T> -void SymtabVisitor::setupSymbol(T* node, symtab::details::NmodlInfo property, int order) { +void SymtabVisitor::setupSymbol(T* node, SymbolInfo property, int order) { std::shared_ptr<Symbol> symbol; auto token = node->getToken(); + /// if prime variable is already exist in symbol table + /// then just update the order if (order) { symbol = modsymtab->lookup(node->getName()); if (symbol) { @@ -18,28 +22,40 @@ void SymtabVisitor::setupSymbol(T* node, symtab::details::NmodlInfo property, in } } + /// add new symbol symbol = std::make_shared<Symbol>(node->getName(), node, *token); symbol->add_property(property); modsymtab->insert(symbol); + + /// visit childrens, most likely variables are already + /// leaf nodes, not necessary to visit node->visitChildren(this); } template <typename T> void SymtabVisitor::setupSymbolTable(T* node, - symtab::details::NmodlInfo property, + std::string name, + SymbolInfo property, bool global_block) { auto token = node->getToken(); - auto symbol = std::make_shared<Symbol>(node->getName(), node, *token); + auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); modsymtab->insert(symbol); - setupSymbolTable(node, global_block); + setupSymbolTable(node, name, global_block); } template <typename T> -void SymtabVisitor::setupSymbolTable(T* node, bool global_block) { - auto symtab = modsymtab->enter_scope(node->getName(), node, global_block); +void SymtabVisitor::setupSymbolTable(T* node, std::string name, bool global_block) { + + /// entering into new nmodl block + auto symtab = modsymtab->enter_scope(name, node, global_block); + + /// not required at the moment but every node + /// has pointer to associated symbol table node->setBlockSymbolTable(symtab); + /// when visiting highest level node i.e. Program, we insert + /// all global variables to the global symbol table if (node->isProgram()) { auto variables = nmodl::all_external_variables(); for (auto variable : variables) { @@ -50,7 +66,10 @@ void SymtabVisitor::setupSymbolTable(T* node, bool global_block) { } } + /// look for all children blocks recursively node->visitChildren(this); + + /// exisiting nmodl block modsymtab->leave_scope(); } diff --git a/test/nmodl/transpiler/input/synapse.mod b/test/nmodl/transpiler/input/synapse.mod index 1e1065baf1..ef40f2ccb5 100644 --- a/test/nmodl/transpiler/input/synapse.mod +++ b/test/nmodl/transpiler/input/synapse.mod @@ -44,7 +44,7 @@ NEURON { THREADSAFE POINT_PROCESS ProbAMPANMDA_EMS RANGE tau_r_AMPA, tau_d_AMPA, tau_r_NMDA, tau_d_NMDA - RANGE Use, u, Dep, Fac, u0, mg, Rstate, tsyn_fac, u + RANGE Use, u, Dep, Fac, u0, mg, Rstate, tsyn_fac RANGE e, NMDA_ratio RANGE A_AMPA_step, B_AMPA_step, A_NMDA_step, B_NMDA_step NONSPECIFIC_CURRENT i diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index e0450a2dbe..e7cf4494cf 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -88,21 +88,21 @@ SCENARIO("Symbol operations") { symbol.add_property(property1); symbol.add_property(property2); THEN("symbol has multiple properties") { - REQUIRE(symbol.has_common_properties(property1) == true); + REQUIRE(symbol.has_properties(property1) == true); SymbolInfo property = NmodlInfo::param_assign; - REQUIRE(symbol.has_common_properties(property) == false); + REQUIRE(symbol.has_properties(property) == false); symbol.add_property(NmodlInfo::param_assign); - REQUIRE(symbol.has_common_properties(property) == true); + REQUIRE(symbol.has_properties(property) == true); } } WHEN("combined properties") { SymbolInfo property = NmodlInfo::factor_def | NmodlInfo::global_var; THEN("symbol has union of all properties") { - REQUIRE(symbol.has_common_properties(property) == false); + REQUIRE(symbol.has_properties(property) == false); symbol.combine_properties(property); - REQUIRE(symbol.has_common_properties(property) == true); + REQUIRE(symbol.has_properties(property) == true); property |= symbol.get_properties(); REQUIRE(symbol.get_properties() == property); } From b43cde3282f85fa76bccd1f6b6d73dd26b74ef11 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 28 Dec 2017 16:50:58 +0100 Subject: [PATCH 031/871] First implementation of performance visitor pass : perfstat is calculated for each top level blocks of mod file. - Perf visitor generates output in text and json format - TableData helper class to print tabular data - Added test for symtab visitor pass - Added test for perfvisitor pass - NOTES.md added with instructions for plot generator - JSONPrinter class updated to add non-array object - libutil created for perfstat and table data class - Table printing code refactored from symbol table to printer utils. - Removed unused to_string method from ModToken Changes to fix all warnings for flags -Wall -Wextra -pedantic - initialization order of member variables - verbatim lexer unused function disabled (options: input, unput) - unused paramaters are commented (seems to be practical option rather than macros or gcc specific attributes) (Squashed commit of sandbox/kumbhar/perf-visitor) Change-Id: I82cce04c0ea162a6e8820fa19c370a0f99a7f049 NMODL Repo SHA: BlueBrain/nmodl@828dae667fbbba070bb1639d56a04fcb1c47b630 --- NOTES.md | 56 ++++ README.md | 2 +- src/nmodl/ast/ast_utils.hpp | 8 + src/nmodl/language/ast_printer.py | 14 +- src/nmodl/language/visitors_printer.py | 7 +- src/nmodl/lexer/modtoken.cpp | 6 - src/nmodl/lexer/modtoken.hpp | 8 +- src/nmodl/lexer/nmodl_lexer.hpp | 2 +- src/nmodl/lexer/token_mapping.cpp | 19 +- src/nmodl/lexer/token_mapping.hpp | 4 +- src/nmodl/lexer/verbatim.l | 14 +- src/nmodl/main.cpp | 2 +- src/nmodl/parser/main.cpp | 2 +- src/nmodl/parser/nmodl.yy | 3 +- src/nmodl/parser/verbatim.y | 3 +- src/nmodl/printer/json_printer.cpp | 18 +- src/nmodl/printer/json_printer.hpp | 18 +- src/nmodl/symtab/symbol.cpp | 22 +- src/nmodl/symtab/symbol.hpp | 10 +- src/nmodl/symtab/symbol_properties.cpp | 8 +- src/nmodl/symtab/symbol_properties.hpp | 7 +- src/nmodl/symtab/symbol_table.cpp | 99 ++---- src/nmodl/symtab/symbol_table.hpp | 15 +- src/nmodl/utils/CMakeLists.txt | 19 ++ src/nmodl/utils/common_utils.hpp | 18 +- src/nmodl/utils/perf_stat.cpp | 85 +++++ src/nmodl/utils/perf_stat.hpp | 78 +++++ src/nmodl/utils/string_utils.hpp | 43 ++- src/nmodl/utils/table_data.cpp | 108 ++++++ src/nmodl/utils/table_data.hpp | 36 ++ src/nmodl/visitors/CMakeLists.txt | 6 +- src/nmodl/visitors/main.cpp | 25 +- src/nmodl/visitors/perf_visitor.cpp | 312 ++++++++++++++++++ src/nmodl/visitors/perf_visitor.hpp | 223 +++++++++++++ src/nmodl/visitors/symtab_visitor_helper.hpp | 23 +- test/nmodl/transpiler/CMakeLists.txt | 4 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 4 +- test/nmodl/transpiler/visitor/visitor.cpp | 105 +++++- 38 files changed, 1248 insertions(+), 188 deletions(-) create mode 100644 NOTES.md create mode 100644 src/nmodl/utils/perf_stat.cpp create mode 100644 src/nmodl/utils/perf_stat.hpp create mode 100644 src/nmodl/utils/table_data.cpp create mode 100644 src/nmodl/utils/table_data.hpp create mode 100644 src/nmodl/visitors/perf_visitor.cpp create mode 100644 src/nmodl/visitors/perf_visitor.hpp diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000000..33d1be3569 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,56 @@ +#### Generating bar charts from PerfStat visitor + +These are steps followed for generating bar plot for meeting on 18th Dec 2017. + +First, get mod files for BBP model (e.g. savestate branch of neurodamus). We are only interested in mod files that are used in simulation. Copy them to some temporary directory : + +``` +cd $HOME/workarena/repos/bbp/neurodamus/lib/modlib +git checkout sandbox/kumbhar/coreneuron_plasticity + +MOD_DIR=$HOME/workarena/repos/bbp/incubator/plots_dec_21_fs_meeting/bbp_mod + +mkdir -p $MOD_DIR +while read -u 10 file; do cp $file $MOD_DIR/; done 10<coreneuron_modlist.txt +``` + +The function/procedure inlining pass is not implemented into nocmodl yet. Get inlined mod files for performance counting: + +``` +cd $MOD_DIR +mkdir inlined +for file in *.mod; do + $NOCMODLX_DIR/bin/nocmodl -f $file -a a -i -s s -n inlined/${file}; +done +``` + +Now we can run nocmodl visitor executable: + +``` +mkdir -p $MOD_DIR/jsons +cd $MOD_DIR/jsons +export NOCMODL=~/workarena/repos/bbp/incubator/nocmodl/cmake-build-debug + +for file in $MOD_DIR/inlined/*.mod.in; do + $NOCMODL/bin/nocmodl_visitor --file $file; +done +``` + +We can generate JSON data required for plotting as: + +``` +cd $MOD_DIR/jsons +python ../../json_generator.py +``` + +Above script print JSON data required for plotting on stdout. We have to copy the output to: + +``` +../../html_plotting/data.js +``` + +We can now open the plot from `html_plotting/plots.html`. Material/Scripts for the plotting is stored in: + +``` +$HOME/workarena/repos/bbp/incubator/plots_dec_21_fs_meeting +``` \ No newline at end of file diff --git a/README.md b/README.md index c02cb48a02..665b63d501 100644 --- a/README.md +++ b/README.md @@ -85,4 +85,4 @@ Or using CTest as: ``` ctest -T memcheck -``` +``` \ No newline at end of file diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 6a543cb2de..f4d7966695 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -5,6 +5,10 @@ #include "lexer/modtoken.hpp" +namespace symtab { + class SymbolTable; +} + namespace ast { /* enumaration of all binary operators in the language */ typedef enum { @@ -87,6 +91,10 @@ namespace ast { return nullptr; } + virtual std::shared_ptr<symtab::SymbolTable> getSymbolTable() { + throw std::runtime_error("getSymbolTable() not implemented"); + } + virtual ~AST() { } diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 555c229551..0be877cdff 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -26,10 +26,6 @@ def headers(self): self.writer.write_line('#include "lexer/modtoken.hpp"') self.writer.write_line('#include "utils/common_utils.hpp"', newline=2) - self.writer.write_line('namespace symtab {') - self.writer.write_line(' class SymbolTable;') - self.writer.write_line('}') - def class_comment(self): self.writer.write_line("/* all classes representing Abstract Syntax Tree (AST) nodes */") @@ -201,8 +197,8 @@ def ast_classes_declaration(self): self.writer.write_line("void setToken(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") if node.is_symtab_needed(): - self.writer.write_line("void setBlockSymbolTable(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") - self.writer.write_line("std::shared_ptr<symtab::SymbolTable> getBlockSymbolTable() " + " { return symtab; }") + self.writer.write_line("void setSymbolTable(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") + self.writer.write_line("std::shared_ptr<symtab::SymbolTable> getSymbolTable() override " + " { return symtab; }") if node.is_number_node(): self.writer.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") @@ -214,7 +210,7 @@ def ast_classes_declaration(self): self.writer.write_line("void negate() override { value = -value; }") if node.is_identifier_node(): - self.writer.write_line(virtual + "void setName(std::string name)" + override + " { std::cout << \"ERROR : setName() not implemented! \"; abort(); }") + self.writer.write_line(virtual + "void setName(std::string /*name*/)" + override + " { std::cout << \"ERROR : setName() not implemented! \"; abort(); }") if node.is_name_node(): self.writer.write_line(virtual + "void setName(std::string name)" + override + " { value->set(name); }") @@ -303,7 +299,7 @@ def definitions(self): self.writer.flush_buffered_lines() self.writer.write_line("}", pre_gutter=-1, newline=2) else: - self.writer.write_line("void " + node.class_name + "::visitChildren(Visitor* v) {}") + self.writer.write_line("void " + node.class_name + "::visitChildren(Visitor* /*v*/) {}") if members: # TODO : constructor definition : remove this with C++11 style @@ -318,7 +314,7 @@ def definitions(self): self.writer.write_line("{") for member in ptr_members: - # bit hack here : remove pointer because we are creating smart pointer + # todo : bit hack here, need to remove pointer because we are creating smart pointer typename = member[0].replace("*", "") self.writer.write_line(" this->" + member[1] + " = std::shared_ptr<" + typename + ">(" + member[1] + ");") diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 965221cb49..fbedadb7da 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -139,6 +139,7 @@ def definitions(self): if node.class_name == "Program": self.writer.write_line("flush();") else: + self.writer.write_line("(void)node;") self.writer.write_line("printer->addNode(\"" + node.class_name + "\");") self.writer.write_line("}", pre_gutter=-1, newline=2) @@ -167,8 +168,8 @@ def class_name_declaration(self): def private_declaration(self): self.writer.write_line("private:", post_gutter=1) - self.writer.write_line("std::unique_ptr<JSONPrinter> printer;") self.writer.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) + self.writer.write_line("std::unique_ptr<JSONPrinter> printer;") def public_declaration(self): self.writer.write_line("public:", post_gutter=1) @@ -189,13 +190,13 @@ def public_declaration(self): # helper function for creating symbol table for blocks # without name (e.g. parameter, unit, breakpoint) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, std::string name, bool global);" + line = "void setupSymbolTable(T *node, std::string name, bool is_global);" self.writer.write_line(line) # helper function for creating symbol table for blocks # with name (e.g. procedure, function, derivative) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, std::string name, SymbolInfo property, bool global);" + line = "void setupSymbolTable(T *node, std::string name, SymbolInfo property, bool is_global);" self.writer.write_line(line, newline=2) # we have to override visitor methods for the nodes diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 87695db11a..08848eda6e 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -23,9 +23,3 @@ std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { stream << std::setw(15) << mt.name << " at [" << mt.position() << "]"; return stream << " type " << mt.token; } - -std::string ModToken::to_string() const { - std::stringstream ss; - ss << *this; - return ss.str(); -} \ No newline at end of file diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index e65a04b589..83aa9fcc74 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -34,12 +34,12 @@ class ModToken { /// token value returned by lexer int token = -1; - /// if token is externally defined symbol - bool external = false; - /// position of the token in mod file nmodl::location pos; + /// if token is externally defined symbol + bool external = false; + public: ModToken() : pos(nullptr, 0){}; @@ -72,8 +72,6 @@ class ModToken { std::string position() const; friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt); - - std::string to_string() const; }; #endif diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 3a928ab147..1908f9d2fc 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -51,7 +51,7 @@ namespace nmodl { /** The streams in and out default to cin and cout, but that assignment * is only made when initializing in yylex(). */ explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) - : driver(drv), NmodlFlexLexer(in, out) {} + : NmodlFlexLexer(in, out), driver(drv) {} ~Lexer() override= default;; diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index ee53d00ef4..d0836eb930 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -285,7 +285,10 @@ namespace nmodl { return keywords[name]; } - TokenType method_type(std::string name) { + /** \todo: revisit implementation, this is no longer + * necessary as token_type is sufficient + */ + TokenType method_type(std::string /*name*/) { return Token::METHOD; } @@ -323,15 +326,21 @@ namespace nmodl { throw std::runtime_error("get_token_type called for non-existent token " + name); } - /// return all external variables (required for symbol table registration) - std::vector<std::string> all_external_variables() { + /// return all external variables + std::vector<std::string> get_external_variables() { std::vector<std::string> result; result.insert(result.end(), internal::neuron_vars.begin(), internal::neuron_vars.end()); + return result; + } + + /// return all solver methods as well as commonly used math functions + std::vector<std::string> get_external_functions() { + std::vector<std::string> result; for (auto& method : internal::methods) { result.push_back(method.first); } - for (auto& extdef : internal::extern_definitions) { - result.push_back(extdef.first); + for (auto& definition : internal::extern_definitions) { + result.push_back(definition.first); } return result; } diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 63a7c49f20..f69d5b5a5c 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -7,6 +7,6 @@ namespace nmodl { bool is_keyword(std::string name); bool is_method(std::string name); nmodl::Parser::token_type token_type(std::string name); - std::vector<std::string> all_external_variables(); - + std::vector<std::string> get_external_variables(); + std::vector<std::string> get_external_functions(); } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 9474e13ab4..6183b2d70c 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -53,11 +53,17 @@ /* no need for includes */ %option noyywrap -/* need to unput in buffer for custom routines */ -%option unput +/* need to unput in buffer for custom routines + * \todo : due to unused function warning this + * option is disabled + */ +%option nounput -/* need to input in buffer for custom routines */ -%option input +/* need to input in buffer for custom routines + * \todo : due to unused function warning this + * option is disabled + */ +%option noinput /* not an interactive lexer, takes a file */ %option batch diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 0ec75a8722..983eb3bcea 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -3,7 +3,7 @@ using namespace nocmodl; -int main(int argc, char* argv[]) { +int main(int /*argc*/, char const** /*argv*/) { std::cout << " NOCMODL " << version::NOCMODL_VERSION << " [" << version::GIT_REVISION << "]" << std::endl; diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp index 21c55ec926..2ada6d1075 100644 --- a/src/nmodl/parser/main.cpp +++ b/src/nmodl/parser/main.cpp @@ -12,7 +12,7 @@ * hook up json/yaml writer in this example to dump ast. */ -int main(int argc, char* argv[]) { +int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Parser: Standalone parser program for NMODL"); TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index dc4425f464..f7a16713fd 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -358,7 +358,8 @@ #include "parser/verbatim_context.hpp" /// yylex takes scanner as well as driver reference - static nmodl::Parser::symbol_type yylex(nmodl::Lexer &scanner, nmodl::Driver &driver) { + /// \todo: check if driver argument is required + static nmodl::Parser::symbol_type yylex(nmodl::Lexer &scanner, nmodl::Driver &/*driver*/) { return scanner.next_token(); } diff --git a/src/nmodl/parser/verbatim.y b/src/nmodl/parser/verbatim.y index b1eb1d1b13..609a421b57 100644 --- a/src/nmodl/parser/verbatim.y +++ b/src/nmodl/parser/verbatim.y @@ -85,7 +85,8 @@ charlist : { $$ = new std::string(""); } %% -void yyerror(YYLTYPE* locp, VerbatimContext* context, const char *s) { +/** \todo : better error handling required */ +void yyerror(YYLTYPE* /*locp*/, VerbatimContext* /*context*/, const char *s) { std::printf("\n Error in verbatim parser : %s \n", s); std::exit(1); } diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 4f04ddd6d9..2aa7952347 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -1,13 +1,5 @@ #include "printer/json_printer.hpp" -/// By default dump output to std::cout -JSONPrinter::JSONPrinter() : result_stream(new std::ostream(std::cout.rdbuf())) { -} - -// Dumpt output to stringstream -JSONPrinter::JSONPrinter(std::stringstream& ss) : result_stream(new std::ostream(ss.rdbuf())) { -} - /// Dump output to provided file JSONPrinter::JSONPrinter(std::string fname) { if (fname.empty()) { @@ -22,18 +14,18 @@ JSONPrinter::JSONPrinter(std::string fname) { } sbuf = ofs.rdbuf(); - result_stream = std::make_shared<std::ostream>(sbuf); + result = std::make_shared<std::ostream>(sbuf); } /// Add node to json (typically basic type) -void JSONPrinter::addNode(std::string name) { +void JSONPrinter::addNode(std::string value, std::string name) { if (!block) { auto text = "Block not initialized (pushBlock missing?)"; throw std::logic_error(text); } json j; - j["value"] = name; + j[name] = value; block->front().push_back(j); } @@ -64,9 +56,9 @@ void JSONPrinter::popBlock() { void JSONPrinter::flush() { if (block) { if (compact) { - *result_stream << (*block).dump(); + *result << block->dump(); } else { - *result_stream << (*block).dump(2); + *result << block->dump(2); } ofs.close(); block = nullptr; diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index c79b71667d..9e0b36f25c 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -25,7 +25,7 @@ using json = nlohmann::json; * block in popBlock() because block itself will be * part of other parent elements. Also we are writing * results to file, stringstream and cout. And hence - * we can't simply reset/cler previously written text. + * we can't simply reset/clear previously written text. */ class JSONPrinter { @@ -34,7 +34,7 @@ class JSONPrinter { std::streambuf* sbuf = nullptr; /// common output stream for file, cout or stringstream - std::shared_ptr<std::ostream> result_stream; + std::shared_ptr<std::ostream> result; /// single (current) nmodl block / statement std::shared_ptr<json> block; @@ -46,16 +46,22 @@ class JSONPrinter { bool compact = false; public: - JSONPrinter(); JSONPrinter(std::string filename); - JSONPrinter(std::stringstream& ss); + + /// By default dump output to std::cout + JSONPrinter() : result(new std::ostream(std::cout.rdbuf())) { + } + + // Dump output to stringstream + JSONPrinter(std::stringstream& ss) : result(new std::ostream(ss.rdbuf())) { + } ~JSONPrinter() { flush(); } - void pushBlock(std::string); - void addNode(std::string); + void pushBlock(std::string name); + void addNode(std::string value, std::string name="value"); void popBlock(); void flush(); diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index ef98ce5bbb..befa6a0570 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -2,20 +2,24 @@ namespace symtab { - /// if symbol has only extern_token property : this is true - /// for symbols which are external variables from neuron - /// like v, t, dt etc. - bool Symbol::is_extern_token_only() { - return (properties == details::NmodlInfo::extern_token); + /** if symbol has only extern_token property : this is true + * for symbols which are external variables from neuron + * like v, t, dt etc. + * \todo: check if we should check two properties using has_property + * instead of exact comparisons + */ + bool Symbol::is_external_symbol_only() { + return (properties == details::NmodlInfo::extern_neuron_variable || + properties == details::NmodlInfo::extern_method); } /// check if symbol has any of the common properties - bool Symbol::has_properties(SymbolInfo& new_properties) { + bool Symbol::has_properties(SymbolInfo new_properties) { return static_cast<bool>(properties & new_properties); } /// add new properties to symbol with bitwise or - void Symbol::combine_properties(SymbolInfo& new_properties) { + void Symbol::combine_properties(SymbolInfo new_properties) { properties |= new_properties; } @@ -23,7 +27,7 @@ namespace symtab { properties |= property; } - void Symbol::add_property(SymbolInfo& property) { + void Symbol::add_property(SymbolInfo property) { properties |= property; } @@ -38,4 +42,4 @@ namespace symtab { } } -} // namespace symtab \ No newline at end of file +} // namespace symtab diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 3e064f7d26..4d60108002 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -76,7 +76,7 @@ namespace symtab { return scope; } - SymbolInfo& get_properties() { + SymbolInfo get_properties() { return properties; } @@ -104,15 +104,15 @@ namespace symtab { return write_count; } - bool is_extern_token_only(); + bool is_external_symbol_only(); - bool has_properties(SymbolInfo& prop); + bool has_properties(SymbolInfo prop); - void combine_properties(SymbolInfo& prop); + void combine_properties(SymbolInfo prop); void add_property(NmodlInfo property); - void add_property(SymbolInfo& property); + void add_property(SymbolInfo property); void set_order(int order); }; diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 4c014ac111..bc775180e4 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -119,8 +119,12 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { properties.push_back("factor_def"); } - if (has_property(obj, NmodlInfo::extern_token)) { - properties.push_back("extern_token"); + if (has_property(obj, NmodlInfo::extern_neuron_variable)) { + properties.push_back("extern_neuron_var"); + } + + if (has_property(obj, NmodlInfo::extern_method)) { + properties.push_back("extern_method"); } return properties; diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 5db5f0bdc9..eec6f6cd17 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -165,8 +165,11 @@ namespace symtab { /** factor in unit block */ factor_def = 1 << 25, - /** extern token */ - extern_token = 1 << 26 + /** neuron variable accessible in mod file */ + extern_neuron_variable = 1 << 26, + + /** neuron solver methods and math functions */ + extern_method = 1 << 27 }; } // namespace details diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 3085b26e79..7bd9ee6c35 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -1,5 +1,7 @@ -#include "symtab/symbol_table.hpp" #include "lexer/token_mapping.hpp" +#include "symtab/symbol_table.hpp" +#include "utils/table_data.hpp" +#include "utils/string_utils.hpp" namespace symtab { @@ -32,10 +34,10 @@ namespace symtab { /// insert new symbol table of one of the children block void SymbolTable::insert_table(std::string name, std::shared_ptr<SymbolTable> table) { - if (childrens.find(name) != childrens.end()) { + if (children.find(name) != children.end()) { throw std::runtime_error("Trying to re-insert SymbolTable " + name); } - childrens[name] = table; + children[name] = table; } /** Get all symbols which can be used in global scope. Note that @@ -101,7 +103,6 @@ namespace symtab { /// rename it. note that we still need parent symbol table /// filed to traverse the parents. revisit this usage. std::shared_ptr<Symbol> ModelSymbolTable::lookup(std::string name) { - /// parent symbol is not set means symbol table is /// is not used with visitor at all. it would be ok // to just return nullptr? @@ -275,41 +276,22 @@ namespace symtab { // be refactored into separate table printer function. //============================================================================= - enum class alignment { left, right, center }; - - /// Left/Right/Center-aligns string within a field of width "width" - std::string format_text(std::string text, int width, alignment type) { - std::string left, right; - // count excess room to pad - int padding = width - text.size(); - if (padding > 0) { - if (type == alignment::left) { - right = std::string(padding, ' '); - } else if (type == alignment::right) { - left = std::string(padding, ' '); - } else { - left = std::string(padding / 2, ' '); - right = std::string(padding / 2, ' '); - // if odd #, add one more space - if (padding > 0 && padding % 2 != 0) { - right += " "; - } - } - } - return left + text + right; - } - - void Table::print(std::stringstream& ss, std::string title, int n) { + void Table::print(std::stringstream& stream, std::string title, int indent) { if (symbols.size()) { - std::vector<int> width{15, 15, 15, 15, 15}; - std::vector<std::vector<std::string>> rows; + TableData table; + table.title = title; + table.headers = {"NAME", "PROPERTIES", "LOCATION", "# READS", "# WRITES"}; + table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, + text_alignment::right, text_alignment::right}; for (const auto& syminfo : symbols) { auto symbol = syminfo.second; + auto is_external = symbol->is_external_symbol_only(); + auto read_count = symbol->get_read_count(); + auto write_count = symbol->get_write_count(); // do not print external symbols which are not used in the current model - if (symbol->is_extern_token_only() && symbol->get_read_count() == 0 && - symbol->get_write_count() == 0) { + if (is_external && read_count == 0 && write_count == 0) { continue; } @@ -318,55 +300,22 @@ namespace symtab { auto properties = to_string(symbol->get_properties()); auto reads = std::to_string(symbol->get_read_count()); auto writes = std::to_string(symbol->get_write_count()); - - std::vector<std::string> row{name, properties, position, reads, writes}; - rows.push_back(row); - - for (int i = 0; i < row.size(); i++) { - if (width[i] < (row[i].length() + 3)) - width[i] = row[i].length() + 3; - } - } - - std::stringstream header; - header << "| "; - header << format_text("NAME", width[0], alignment::center) << " | "; - header << format_text("PROPERTIES", width[1], alignment::center) << " | "; - header << format_text("LOCATION", width[2], alignment::center) << " | "; - header << format_text("# READS", width[3], alignment::center) << " | "; - header << format_text("# WRITES", width[4], alignment::center) << " |"; - - auto header_len = header.str().length(); - auto spaces = std::string(n * 4, ' '); - auto separator_line = std::string(header_len, '-'); - - ss << "\n" << spaces << separator_line; - ss << "\n" << spaces; - ss << "|" << format_text(title, header_len - 2, alignment::center) << "|"; - ss << "\n" << spaces << separator_line; - ss << "\n" << spaces << header.str(); - ss << "\n" << spaces << separator_line; - - for (const auto& row : rows) { - ss << "\n" << spaces << "| "; - ss << format_text(row[0], width[0], alignment::left) << " | "; - ss << format_text(row[1], width[1], alignment::left) << " | "; - ss << format_text(row[2], width[2], alignment::right) << " | "; - ss << format_text(row[3], width[3], alignment::right) << " | "; - ss << format_text(row[4], width[4], alignment::right) << " |"; + table.rows.push_back({name, properties, position, reads, writes}); } - ss << "\n" << spaces << separator_line << "\n"; + table.print(stream, indent); } } - void SymbolTable::print(std::stringstream& ss, int level) { + std::string SymbolTable::title() { /// construct title for symbol table auto name = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; auto location = "POSITION : " + position(); auto scope = global ? "GLOBAL" : "LOCAL"; - auto title = name + location + " SCOPE : " + scope; + return name + location + " SCOPE : " + scope; + } - table.print(ss, title, level); + void SymbolTable::print(std::stringstream& ss, int level) { + table.print(ss, title(), level); /// when current symbol table is empty, the childrens /// can be printed from the same indentation level @@ -376,8 +325,8 @@ namespace symtab { next_level--; } - /// recursively print all childrens - for (const auto& item : childrens) { + /// recursively print all children tables + for (const auto& item : children) { if (item.second->symbol_count() >= 0) { (item.second)->print(ss, ++next_level); next_level--; diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 54eaddd2c7..31d0aa8ed0 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -32,7 +32,7 @@ namespace symtab { std::shared_ptr<Symbol> lookup(std::string name); /// pretty print - void print(std::stringstream& ss, std::string title, int indent); + void print(std::stringstream& stream, std::string title, int indent); }; /** @@ -62,16 +62,17 @@ namespace symtab { /// name of the block std::string symtab_name; - /// true if current symbol table is global - /// (blocks like NEURON, PARAMETER defines global variables) - bool global; - /// table holding all symbols in the current block Table table; /// pointer to ast node for which current block symbol created AST* node = nullptr; + /// true if current symbol table is global. blocks like neuron, + /// parameter defines global variables and hence they go into + /// single global symbol table + bool global = false; + /// pointer to the symbol table of parent block in the mod file std::shared_ptr<SymbolTable> parent = nullptr; @@ -79,7 +80,7 @@ namespace symtab { /// construct. for example, for every block statement (like if, while, /// for) within function we append new symbol table. note that this is /// also required for nested blocks like INITIAL in NET_RECEIVE. - std::map<std::string, std::shared_ptr<SymbolTable>> childrens; + std::map<std::string, std::shared_ptr<SymbolTable>> children; /// pretty print table void print_table(std::stringstream& ss, int indent); @@ -99,6 +100,8 @@ namespace symtab { return node->getTypeName(); } + std::string title(); + /// todo: set token for every block from parser std::string position() const { auto token = node->getToken(); diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 262cd061aa..1fd1da5541 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -1,7 +1,26 @@ +#============================================================================= +# Utility sources +#============================================================================= +set(UTIL_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/table_data.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp +) + +#============================================================================= +# Symbol table library +#============================================================================= +add_library(util + STATIC + ${UTIL_SOURCE_FILES} +) + #============================================================================= # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT + ${UTIL_SOURCE_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp ${FILES_FOR_CLANG_FORMAT} diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index b1f1bc39a7..83c00855f6 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -1,9 +1,23 @@ -#ifndef _COMMON_H_ -#define _COMMON_H_ +#ifndef _COMMON_UTILS_H_ +#define _COMMON_UTILS_H_ +/** Check if the iterator is pointing to last element in the container */ template <typename Iter, typename Cont> bool is_last(Iter iter, const Cont* cont) { return ((iter != cont->end()) && (next(iter) == cont->end())); } +/** Given full file path, returns only name of the file */ +template <class T> +T base_name(T const& path, T const& delims = "/\\") { + return path.substr(path.find_last_of(delims) + 1); +} + +/** Given the file name, returns name of the file without extension */ +template <class T> +T remove_extension(T const& filename) { + typename T::size_type const p(filename.find_last_of('.')); + return p > 0 && p != T::npos ? filename.substr(0, p) : filename; +} + #endif diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp new file mode 100644 index 0000000000..ca5b9e083c --- /dev/null +++ b/src/nmodl/utils/perf_stat.cpp @@ -0,0 +1,85 @@ +#include <iostream> + +#include <vector> + +#include "utils/perf_stat.hpp" +#include "utils/table_data.hpp" + +PerfStat operator+(const PerfStat& first, const PerfStat& second) { + PerfStat result; + + result.assign_count = first.assign_count + second.assign_count; + + result.add_count = first.add_count + second.add_count; + result.sub_count = first.sub_count + second.sub_count; + result.mul_count = first.mul_count + second.mul_count; + result.div_count = first.div_count + second.div_count; + + result.exp_count = first.exp_count + second.exp_count; + result.log_count = first.log_count + second.log_count; + result.pow_count = first.pow_count + second.pow_count; + result.func_call_count = first.func_call_count + second.func_call_count; + + result.and_count = first.and_count + second.and_count; + result.or_count = first.or_count + second.or_count; + + result.gt_count = first.gt_count + second.gt_count; + result.lt_count = first.lt_count + second.lt_count; + result.ge_count = first.ge_count + second.ge_count; + result.le_count = first.le_count + second.le_count; + result.ne_count = first.ne_count + second.ne_count; + result.ee_count = first.ee_count + second.ee_count; + + result.not_count = first.not_count + second.not_count; + result.neg_count = first.neg_count + second.neg_count; + + result.if_count = first.if_count + second.if_count; + result.elif_count = first.elif_count + second.elif_count; + + result.global_read_count = first.global_read_count + second.global_read_count; + result.global_write_count = first.global_write_count + second.global_write_count; + result.local_read_count = first.local_read_count + second.local_read_count; + result.local_write_count = first.local_write_count + second.local_write_count; + + return result; +} + +void PerfStat::print(std::stringstream& stream) { + TableData table; + table.headers = keys(); + table.rows.push_back(values()); + if (!title.empty()) { + table.title = title; + } + table.print(stream); +} + +std::vector<std::string> PerfStat::keys() { + return {"+", "-", "x", "/", "exp", "GM(R)", "GM(W)", + "LM(R)", "LM(W)", "call", "compare", "unary", "conditional"}; +} + +std::vector<std::string> PerfStat::values() { + std::vector<std::string> row; + + int compares = gt_count + lt_count + ge_count + le_count + ne_count + ee_count; + int conditionals = if_count + elif_count; + + row.push_back(std::to_string(add_count)); + row.push_back(std::to_string(sub_count)); + row.push_back(std::to_string(mul_count)); + row.push_back(std::to_string(div_count)); + row.push_back(std::to_string(exp_count)); + + row.push_back(std::to_string(global_read_count)); + row.push_back(std::to_string(global_write_count)); + row.push_back(std::to_string(local_read_count)); + row.push_back(std::to_string(local_write_count)); + row.push_back(std::to_string(func_call_count)); + + row.push_back(std::to_string(compares)); + row.push_back(std::to_string(not_count + neg_count)); + row.push_back(std::to_string(conditionals)); + + return row; +} diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp new file mode 100644 index 0000000000..eb789db986 --- /dev/null +++ b/src/nmodl/utils/perf_stat.hpp @@ -0,0 +1,78 @@ +#ifndef _NMODL_PERF_STAT_HPP_ +#define _NMODL_PERF_STAT_HPP_ + +#include <sstream> + +/** + * \class PerfStat + * \brief Helper class to collect performance statistics + * + * For code generation it is useful to know the performance + * characteristics of every block in nmodl. The PerfStat class + * groups performance characteristics of a single block in + * nmodl. + */ + +class PerfStat { + public: + /// name for pretty-printing + std::string title; + + /// write ops + int assign_count = 0; + + /// basic ops (<= 1 cycle) + int add_count = 0; + int sub_count = 0; + int mul_count = 0; + + /// expensive ops + int div_count = 0; + + /// expensive functions : commonly + /// used functions in mod files + int exp_count = 0; + int log_count = 0; + int pow_count = 0; + /// could be external math funcs + /// or mod functions (before inlining) + int func_call_count = 0; + + /// bitwise ops + int and_count = 0; + int or_count = 0; + + /// comparisons ops + int gt_count = 0; + int lt_count = 0; + int ge_count = 0; + int le_count = 0; + int ne_count = 0; + int ee_count = 0; + + /// unary ops + int not_count = 0; + int neg_count = 0; + + /// conditional ops + int if_count = 0; + int elif_count = 0; + + /// expensive : typically access to + /// dynamically allocated memory + int global_read_count = 0; + int global_write_count = 0; + + /// cheap : typically local variables + // in mod file means registers + int local_read_count = 0; + int local_write_count = 0; + + friend PerfStat operator+(const PerfStat& first, const PerfStat& second); + void print(std::stringstream& stream); + + std::vector<std::string> keys(); + std::vector<std::string> values(); +}; + +#endif diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 2bae5e20cd..a5e429fa6b 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -11,6 +11,8 @@ * stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring */ +enum class text_alignment { left, right, center }; + namespace stringutils { /// Trim from start static inline std::string& ltrim(std::string& s) { @@ -32,7 +34,7 @@ namespace stringutils { return ltrim(rtrim(s)); } - inline void remove_character(std::string& str, const char c) { + static inline void remove_character(std::string& str, const char c) { str.erase(std::remove(str.begin(), str.end(), c), str.end()); } @@ -51,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; @@ -62,16 +64,41 @@ namespace stringutils { } /// Spilt string with given delimiter and returns vector - inline std::vector<std::string> split_string(const std::string& s, char delim) { - std::vector<std::string> elems; - std::stringstream ss(s); + static inline std::vector<std::string> split_string(const std::string& text, char delimiter) { + std::vector<std::string> elements; + std::stringstream ss(text); std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); + while (std::getline(ss, item, delimiter)) { + elements.push_back(item); } - return elems; + return elements; + } + + /// Left/Right/Center-aligns string within a field of width "width" + static inline std::string align_text(std::string text, int width, text_alignment type) { + /// left and right spacing + std::string left, right; + + /// count excess room to pad + int padding = width - text.size(); + + if (padding > 0) { + if (type == text_alignment::left) { + right = std::string(padding, ' '); + } else if (type == text_alignment::right) { + left = std::string(padding, ' '); + } else { + left = std::string(padding / 2, ' '); + right = std::string(padding / 2, ' '); + /// if odd #, add one more space + if (padding > 0 && padding % 2 != 0) { + right += " "; + } + } + } + return left + text + right; } } // namespace stringutils diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp new file mode 100644 index 0000000000..2ad22d4ed9 --- /dev/null +++ b/src/nmodl/utils/table_data.cpp @@ -0,0 +1,108 @@ +#include <numeric> +#include <iostream> + +#include "utils/table_data.hpp" +#include "utils/string_utils.hpp" + +/** + * Print table data in below shown format: title as first row (centrally aligned), + * second row is header for individual column (centrally aligned) and then all data + * rows (with associated alignments). + * + * ---------------------------------------------------------------------------------------- + * | mBetaf [FunctionBlock IN NMODL_GLOBAL] POSITION : 109.1-8 SCOPE : LOCAL | + * ---------------------------------------------------------------------------------------- + * | NAME | PROPERTIES | LOCATION | # READS | # WRITES | + * ---------------------------------------------------------------------------------------- + * | v | argument | 109.17 | 0 | 0 | + * ---------------------------------------------------------------------------------------- + */ + +using namespace stringutils; + +void TableData::print(std::stringstream& stream, int indent) { + const int PADDING = 1; + + /// not necessary to print empty table + if (rows.size() == 0 || headers.size() == 0) { + return; + } + + /// based on indentation level, spaces to prefix + auto gutter = std::string(indent * 4, ' '); + + auto ncolumns = headers.size(); + std::vector<unsigned> col_width(ncolumns); + + /// alignment is optional, so fill remaining withh right alignment + for (unsigned i = alignments.size(); i < ncolumns; i++) + alignments.push_back(text_alignment::center); + + /// calculate space required for each column + unsigned row_width = 0; + for (unsigned i = 0; i < headers.size(); i++) { + col_width[i] = headers[i].length() + PADDING; + row_width += col_width[i]; + } + + /// if title is larger than headers then every column + /// width needs to be scaled + if (title.length() > row_width) { + int extra_size = title.length() - row_width; + int column_pad = extra_size / ncolumns; + if (extra_size % ncolumns) { + column_pad++; + } + for (auto& column : col_width) { + column += column_pad; + } + } + + /// check length of columns in each row to find max length required + for (const auto& row : rows) { + for (unsigned i = 0; i < row.size(); i++) { + if (col_width[i] < (row[i].length()) + PADDING) { + col_width[i] = row[i].length() + PADDING; + } + } + } + + std::stringstream header; + header << "| "; + for (size_t i = 0; i < headers.size(); i++) { + auto text = align_text(headers[i], col_width[i], text_alignment::center); + header << text << " | "; + } + + row_width = header.str().length(); + auto separator_line = std::string(row_width - 1, '-'); + + /// title row + if (!title.empty()) { + title = stringutils::align_text(title, row_width - 3, text_alignment::center); + stream << "\n" << gutter << separator_line; + stream << "\n" << gutter << "|" << title << "|"; + } + + /// header row + stream << "\n" << gutter << separator_line; + stream << "\n" << gutter << header.str(); + stream << "\n" << gutter << separator_line; + + /// data rows + for (const auto& row : rows) { + stream << "\n" << gutter << "| "; + for (unsigned i = 0; i < row.size(); i++) { + stream << stringutils::align_text(row[i], col_width[i], alignments[i]) << " | "; + } + } + + /// bottom separator line + stream << "\n" << gutter << separator_line << "\n"; +} + +void TableData::print(int indent) { + std::stringstream ss; + print(ss, indent); + std::cout << ss.str(); +} \ No newline at end of file diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp new file mode 100644 index 0000000000..5523b1cc6a --- /dev/null +++ b/src/nmodl/utils/table_data.hpp @@ -0,0 +1,36 @@ +#ifndef _NMODL_TABLE_DATA_HPP_ +#define _NMODL_TABLE_DATA_HPP_ + +#include <sstream> +#include <vector> + +#include "utils/string_utils.hpp" + +/** + * \class TableData + * \brief Class to construct and pretty-print tabular data + * + * This class is used to construct and print tables (like symbol + * table and performance tables). + */ + +struct TableData { + using TableRowType = std::vector<std::string>; + + /// title of the table + std::string title; + + /// top header/keys + TableRowType headers; + + /// data + std::vector<TableRowType> rows; + + /// alignment for every column of data rows + std::vector<text_alignment> alignments; + + void print(int indent = 0); + void print(std::stringstream& stream, int indent = 0); +}; + +#endif \ No newline at end of file diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index e06d774e4c..272dc83dc7 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -9,6 +9,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ) set_source_files_properties( @@ -23,10 +25,10 @@ add_library(visitor STATIC ${VISITOR_SOURCE_FILES}) -add_dependencies(visitor lexer) +add_dependencies(visitor lexer util) add_executable(nocmodl_visitor main.cpp) -target_link_libraries(nocmodl_visitor printer visitor symtab lexer) +target_link_libraries(nocmodl_visitor printer visitor symtab util lexer) #============================================================================= # Files for clang-format diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 734356cf90..7aee25819f 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -5,19 +5,20 @@ #include "parser/nmodl_driver.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/json_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" +#include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" #include "tclap/CmdLine.h" using namespace symtab; /** - * Standlone visitor program for NMODL. This demonstrate basic + * Standalone visitor program for NMODL. This demonstrate basic * usage of different visitors classes and driver class. **/ -int main(int argc, char* argv[]) { +int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Visitor: Standalone visitor program for NMODL"); TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, @@ -38,6 +39,8 @@ int main(int argc, char* argv[]) { throw std::runtime_error("Could not open file " + filename); } + std::string channel_name = remove_extension(base_name(filename)); + /// driver object creates lexer and parser, just call parser method nmodl::Driver driver; driver.parse_file(filename); @@ -80,6 +83,7 @@ int main(int argc, char* argv[]) { } { + // todo : we should pass this or use get method to retrieve? ModelSymbolTable symtab; std::stringstream ss1; @@ -95,6 +99,21 @@ int main(int argc, char* argv[]) { std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; } + { + PerfVisitor v(channel_name + ".perf.json"); + v.visitProgram(ast.get()); + + auto symtab = ast->getSymbolTable(); + std::stringstream ss; + symtab->print(ss, 0); + std::cout << ss.str(); + + ss.str(""); + v.print(ss); + std::cout << ss.str() << std::endl; + std::cout << "----PERF VISITOR FINISHED----" << std::endl; + } + } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp new file mode 100644 index 0000000000..96275fc29b --- /dev/null +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -0,0 +1,312 @@ +#include "visitors/perf_visitor.hpp" + +using namespace symtab; + +PerfVisitor::PerfVisitor(std::string filename) : printer(new JSONPrinter(filename)) { +} + +/// count math operations from all binary expressions +void PerfVisitor::visitBinaryExpression(BinaryExpression* node) { + bool assign_op = false; + + if (start_measurement) { + switch (node->op.value) { + case BOP_ADDITION: + current_block_perf.add_count++; + break; + + case BOP_SUBTRACTION: + current_block_perf.sub_count++; + break; + + case BOP_MULTIPLICATION: + current_block_perf.mul_count++; + break; + + case BOP_DIVISION: + current_block_perf.div_count++; + break; + + case BOP_POWER: + current_block_perf.pow_count++; + break; + + case BOP_AND: + current_block_perf.and_count++; + break; + + case BOP_OR: + current_block_perf.or_count++; + break; + + case BOP_GREATER: + current_block_perf.gt_count++; + break; + + case BOP_GREATER_EQUAL: + current_block_perf.ge_count++; + break; + + case BOP_LESS: + current_block_perf.lt_count++; + break; + + case BOP_LESS_EQUAL: + current_block_perf.le_count++; + break; + + case BOP_ASSIGN: + current_block_perf.assign_count++; + assign_op = true; + break; + + case BOP_NOT_EQUAL: + current_block_perf.ne_count++; + break; + + case BOP_EXACT_EQUAL: + current_block_perf.ee_count++; + break; + + default: + throw std::logic_error("Binary operator not handled in perf visitor"); + } + } + + /// if visiting assignment expression, symbols from lhs + /// are written and hence need flag to track + if (assign_op) { + visiting_lhs_expression = true; + } + + node->lhs->accept(this); + + /// lhs is done (rhs is read only) + visiting_lhs_expression = false; + + node->rhs->accept(this); +} + +/// add performance stats to json printer +void PerfVisitor::add_perf_to_printer(PerfStat& perf) { + auto keys = perf.keys(); + auto values = perf.values(); + assert(keys.size() == values.size()); + + for (size_t i = 0; i < keys.size(); i++) { + printer->addNode(values[i], keys[i]); + } +} + +/** Helper function used by all ast nodes : visit all children + * recursively and performance stats get added on stack. Once + * all children visited, we get total performance by summing + * perfstat of all children. + */ +void PerfVisitor::measure_performance(AST* node) { + start_measurement = true; + + node->visitChildren(this); + + PerfStat perf; + while (!children_blocks_perf.empty()) { + perf = perf + children_blocks_perf.top(); + children_blocks_perf.pop(); + } + + auto symtab = node->getSymbolTable(); + if (symtab == nullptr) { + throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); + } + + if (printer) { + printer->pushBlock(symtab->name()); + } + + perf.title = "Performance Statistics of " + symtab->name(); + perf.print(stream); + + if (printer) { + add_perf_to_printer(perf); + printer->popBlock(); + } + + start_measurement = false; +} + +/// count function calls and "most useful" or "commonly used" math functions +void PerfVisitor::visitFunctionCall(FunctionCall* node) { + under_function_call = true; + + if (start_measurement) { + auto name = node->name->getName(); + if (name.compare("exp") == 0) { + current_block_perf.exp_count++; + } else if (name.compare("log") == 0) { + current_block_perf.log_count++; + } else if (name.compare("pow") == 0) { + current_block_perf.pow_count++; + } + node->visitChildren(this); + current_block_perf.func_call_count++; + } + + under_function_call = false; +} + +/// every variable used is of type name, update counters +void PerfVisitor::visitName(Name* node) { + update_memory_ops(node->getName()); + node->visitChildren(this); +} + +/// prime name derived from identifier and hence need to be handled here +void PerfVisitor::visitPrimeName(PrimeName* node) { + update_memory_ops(node->getName()); + node->visitChildren(this); +} + +void PerfVisitor::visitIfStatement(IfStatement* /*node*/) { + if (start_measurement) { + current_block_perf.if_count++; + } +} + +void PerfVisitor::visitElseIfStatement(ElseIfStatement* /*node*/) { + if (start_measurement) { + current_block_perf.elif_count++; + } +} + +void PerfVisitor::visitProgram(Program* node) { + if (printer) { + printer->pushBlock("NMODL"); + } + + node->visitChildren(this); + std::string title = "Total Performance Statistics"; + total_perf.title = title; + total_perf.print(stream); + + if (printer) { + printer->pushBlock("total"); + add_perf_to_printer(total_perf); + printer->popBlock(); + printer->popBlock(); + } +} + +/** Blocks like function can have multiple statement blocks and + * blocks like net receive has nested initial blocks. Hence need + * to maintain separate stack. + */ +void PerfVisitor::visitStatementBlock(StatementBlock* node) { + /// starting new block, store current state + blocks_perf.push(current_block_perf); + + current_symtab = node->getSymbolTable(); + + if (current_symtab == nullptr) { + throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); + } + + /// new block perf starts from zero + current_block_perf = PerfStat(); + + node->visitChildren(this); + + /// add performance of all visited children + total_perf = total_perf + current_block_perf; + + children_blocks_perf.push(current_block_perf); + + // go back to parent block's state + current_block_perf = blocks_perf.top(); + blocks_perf.pop(); +} + +/// solve is not a statement but could have associated block +/// and hence could/should not be skipped completely +void PerfVisitor::visitSolveBlock(SolveBlock* node) { + under_solve_block = true; + node->visitChildren(this); + under_solve_block = false; +} + +void PerfVisitor::visitUnaryExpression(UnaryExpression* node) { + if (start_measurement) { + switch (node->op.value) { + case UOP_NEGATION: + current_block_perf.neg_count++; + break; + + case UOP_NOT: + current_block_perf.not_count++; + break; + + default: + throw std::logic_error("Unary operator not handled in perf visitor"); + } + } + node->visitChildren(this); +} + +/** Certain statements / symbols needs extra check while measuring + * read/write operations. For example, for expression "exp(a+b)", + * "exp" is an external math function and we should not increment read + * count for "exp" symbol. Same for solve statement where name will + * be neuron solver method. + */ +bool PerfVisitor::symbol_to_skip(std::shared_ptr<Symbol> symbol) { + bool skip = false; + + auto is_method = symbol->has_properties(NmodlInfo::extern_method); + if (is_method && under_function_call) { + skip = true; + } + + if (is_method && under_solve_block) { + skip = true; + } + + return skip; +} + +bool PerfVisitor::is_local_variable(std::shared_ptr<symtab::Symbol> symbol) { + bool is_local = false; + auto properties = NmodlInfo::local_var | NmodlInfo::argument | NmodlInfo::function_block; + if (symbol->has_properties(properties)) { + is_local = true; + } + return is_local; +} + +/** Find symbol in closest scope (up to parent) and update + * read/write count. Also update ops count in current block. + */ +void PerfVisitor::update_memory_ops(std::string name) { + if (start_measurement) { + auto symbol = current_symtab->lookup_in_scope(name); + if (symbol == nullptr || symbol_to_skip(symbol)) { + return; + } + + if (is_local_variable(symbol)) { + if (visiting_lhs_expression) { + symbol->write(); + current_block_perf.local_write_count++; + } else { + symbol->read(); + current_block_perf.local_read_count++; + } + } else { + if (visiting_lhs_expression) { + symbol->write(); + current_block_perf.global_write_count++; + } else { + symbol->read(); + current_block_perf.global_read_count++; + } + } + } +} diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp new file mode 100644 index 0000000000..9321a55162 --- /dev/null +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -0,0 +1,223 @@ +#ifndef _NMODL_PERF_VISITOR_HPP_ +#define _NMODL_PERF_VISITOR_HPP_ + +#include <stack> + +#include "printer/json_printer.hpp" +#include "symtab/symbol_table.hpp" +#include "utils/perf_stat.hpp" +#include "visitors/ast_visitor.hpp" + +/** + * \class PerfVisitor + * \brief Visitor for measuring performance related information + * + * This visitor used to visit the ast and associated symbol tables + * to measure the performance of every block in nmodl file. For + * every symbol in associated symbol table, read/write count + * is updated which will be used during code generation (to select + * memory types). Certain statements like useion, valence etc. + * are not executed in the translated C code and hence need to + * be skipped (i.e. without visiting children). Note that this + * pass must be run after symbol table generation pass. + * + * \todo : To measure the performance of statements like if, elseif + * and else, we have to find maximum performance from if,elseif,else + * and then use it to calculate total performance. In the current + * implementation we are doing sum of all blocks. We need to override + * IfStatement (which has all sub-blocks) and get maximum performance + * of all statements recursively. + * + * \todo : In order to avoid empty implementations and checking + * start_measurement, there should be "empty" ast visitor from + * which PerfVisitor should be inherited. + */ + +class PerfVisitor : public AstVisitor { + private: + /// symbol table of current block being visited + std::shared_ptr<symtab::SymbolTable> current_symtab; + + /// performance stats of all blocks being visited + /// in recursive chain + std::stack<PerfStat> blocks_perf; + + /// total performance of mod file + PerfStat total_perf; + + /// performance of current block + PerfStat current_block_perf; + + /// performance of current all childrens + std::stack<PerfStat> children_blocks_perf; + + /// whether to measure performance for current block + bool start_measurement = false; + + /// true while visiting lhs of binary expression + /// (to count write operations) + bool visiting_lhs_expression = false; + + /// whether function call is being visited + bool under_function_call = false; + + /// whether solve block is being visited + bool under_solve_block = false; + + /// to print to json file + std::unique_ptr<JSONPrinter> printer; + + /// if not json, all goes to string + std::stringstream stream; + + void update_memory_ops(std::string name); + + bool symbol_to_skip(std::shared_ptr<symtab::Symbol> symbol); + + bool is_local_variable(std::shared_ptr<symtab::Symbol> symbol); + + void measure_performance(AST* node); + + void add_perf_to_printer(PerfStat& perf); + + public: + PerfVisitor() = default; + + explicit PerfVisitor(std::string filename); + + void compact_json(bool flag) { + printer->compact_json(flag); + } + + PerfStat& get_total_perfstat() { + return total_perf; + } + + void visitBinaryExpression(BinaryExpression* node) override; + + void visitFunctionCall(FunctionCall* node) override; + + void visitName(Name* node) override; + + void visitPrimeName(PrimeName* node) override; + + void visitSolveBlock(SolveBlock* node) override; + + void visitStatementBlock(StatementBlock* node) override; + + void visitUnaryExpression(UnaryExpression* node) override; + + void visitIfStatement(IfStatement* node) override; + + void visitElseIfStatement(ElseIfStatement* node) override; + + void visitProgram(Program* node) override; + + void visitPlotBlock(PlotBlock* node) override { + measure_performance(node); + } + + void visitInitialBlock(InitialBlock* node) override { + measure_performance(node); + } + + void visitConstructorBlock(ConstructorBlock* node) override { + measure_performance(node); + } + + void visitDestructorBlock(DestructorBlock* node) override { + measure_performance(node); + } + + void visitDerivativeBlock(DerivativeBlock* node) override { + measure_performance(node); + } + + void visitLinearBlock(LinearBlock* node) override { + measure_performance(node); + } + + void visitNonLinearBlock(NonLinearBlock* node) override { + measure_performance(node); + } + + void visitDiscreteBlock(DiscreteBlock* node) override { + measure_performance(node); + } + + void visitPartialBlock(PartialBlock* node) override { + measure_performance(node); + } + + void visitFunctionTableBlock(FunctionTableBlock* node) override { + measure_performance(node); + } + + void visitFunctionBlock(FunctionBlock* node) override { + measure_performance(node); + } + + void visitProcedureBlock(ProcedureBlock* node) override { + measure_performance(node); + } + + void visitNetReceiveBlock(NetReceiveBlock* node) override { + measure_performance(node); + } + + void visitBreakpointBlock(BreakpointBlock* node) override { + measure_performance(node); + } + + void visitTerminalBlock(TerminalBlock* node) override { + measure_performance(node); + } + + void visitBeforeBlock(BeforeBlock* node) override { + measure_performance(node); + } + + void visitAfterBlock(AfterBlock* node) override { + measure_performance(node); + } + + void visitBABlock(BABlock* node) override { + measure_performance(node); + } + + void visitForNetcon(ForNetcon* node) override { + measure_performance(node); + } + + void visitKineticBlock(KineticBlock* node) override { + measure_performance(node); + } + + void visitMatchBlock(MatchBlock* node) override { + measure_performance(node); + } + + /// certain constructs needs to be excluded from usage counting + /// and hence need to provide empty implementations + + void visitConductanceHint(ConductanceHint* /*node*/) override { + } + + void visitLocalListStatement(LocalListStatement* /*node*/) override { + } + + void visitSuffix(Suffix* /*node*/) override { + } + + void visitUseion(Useion* /*node*/) override { + } + + void visitValence(Valence* /*node*/) override { + } + + void print(std::stringstream& ss) { + ss << stream.str(); + } +}; + +#endif diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index ce7e8335a4..be953ff569 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -36,32 +36,37 @@ template <typename T> void SymtabVisitor::setupSymbolTable(T* node, std::string name, SymbolInfo property, - bool global_block) { + bool is_global) { auto token = node->getToken(); auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); modsymtab->insert(symbol); - setupSymbolTable(node, name, global_block); + setupSymbolTable(node, name, is_global); } template <typename T> -void SymtabVisitor::setupSymbolTable(T* node, std::string name, bool global_block) { - +void SymtabVisitor::setupSymbolTable(T* node, std::string name, bool is_global) { /// entering into new nmodl block - auto symtab = modsymtab->enter_scope(name, node, global_block); + auto symtab = modsymtab->enter_scope(name, node, is_global); /// not required at the moment but every node /// has pointer to associated symbol table - node->setBlockSymbolTable(symtab); + node->setSymbolTable(symtab); /// when visiting highest level node i.e. Program, we insert /// all global variables to the global symbol table if (node->isProgram()) { - auto variables = nmodl::all_external_variables(); + ModToken tok(true); + auto variables = nmodl::get_external_variables(); for (auto variable : variables) { - ModToken tok(true); auto symbol = std::make_shared<Symbol>(variable, nullptr, tok); - symbol->add_property(symtab::details::NmodlInfo::extern_token); + symbol->add_property(symtab::details::NmodlInfo::extern_neuron_variable); + modsymtab->insert(symbol); + } + auto methods = nmodl::get_external_functions(); + for (auto method : methods) { + auto symbol = std::make_shared<Symbol>(method, nullptr, tok); + symbol->add_property(symtab::details::NmodlInfo::extern_method); modsymtab->insert(symbol); } } diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 11d3fa064d..6f8a965caa 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -15,9 +15,9 @@ add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) -target_link_libraries(testvisitor lexer printer visitor) +target_link_libraries(testvisitor printer symtab lexer visitor util) target_link_libraries(testprinter printer) -target_link_libraries(testsymtab symtab lexer) +target_link_libraries(testsymtab symtab lexer util) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index e7cf4494cf..2d86e12e63 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -79,9 +79,9 @@ SCENARIO("Symbol operations") { ModToken token(true); Symbol symbol("alpha", token); WHEN("added external property") { - symbol.add_property(NmodlInfo::extern_token); + symbol.add_property(NmodlInfo::extern_neuron_variable); THEN("symbol becomes external") { - REQUIRE(symbol.is_extern_token_only() == true); + REQUIRE(symbol.is_external_symbol_only() == true); } } WHEN("added multiple properties to symbol") { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 2dacfef6a5..44820a448e 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -6,8 +6,8 @@ #include "parser/nmodl_driver.hpp" #include "visitors/verbatim_visitor.hpp" #include "visitors/json_visitor.hpp" - -#include "json/json.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" using json = nlohmann::json; @@ -93,4 +93,105 @@ TEST_CASE("JSON Visitor") { auto result = run_json_visitor(nmodl_text, true); REQUIRE(result == expected); } +} + +//============================================================================= +// Symtab and Perf visitor tests +//============================================================================= + +SCENARIO("Symbol table generation and Perf stat visitor pass") { + GIVEN("A mod file and associated ast") { + std::string nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PARAMETER { + gNaTs2_tbar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + ena (mV) + } + + STATE { + m + h + } + + BREAKPOINT { + LOCAL gNaTs2_t + CONDUCTANCE gNaTs2_t USEION na + SOLVE states METHOD cnexp + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + } + + FUNCTION hBetaf(v) { + hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) + } + )"; + + nmodl::Driver driver; + driver.parse_string(nmodl_text); + auto ast = driver.ast(); + + WHEN("Symbol table generator pass runs") { + ModelSymbolTable symtab; + SymtabVisitor v(&symtab); + v.visitProgram(ast.get()); + + using namespace symtab::details; + + THEN("Can lookup for defined variables") { + auto symbol = symtab.lookup("m"); + REQUIRE(symbol->has_properties(NmodlInfo::dependent_def)); + REQUIRE_FALSE(symbol->has_properties(NmodlInfo::local_var)); + + symbol = symtab.lookup("gNaTs2_tbar"); + REQUIRE(symbol->has_properties(NmodlInfo::param_assign)); + REQUIRE(symbol->has_properties(NmodlInfo::range_var)); + + symbol = symtab.lookup("ena"); + REQUIRE(symbol->has_properties(NmodlInfo::read_ion_var)); + } + THEN("Can lookup for defined functions") { + auto symbol = symtab.lookup("hBetaf"); + REQUIRE(symbol->has_properties(NmodlInfo::function_block)); + } + THEN("Non existent variable lookup returns nullptr") { + REQUIRE(symtab.lookup("xyz") == nullptr); + } + + WHEN("Perf visitor pass runs after symtab visitor") { + PerfVisitor v; + v.visitProgram(ast.get()); + auto result = v.get_total_perfstat(); + + THEN("Performance counters are updated") { + REQUIRE(result.add_count == 0); + REQUIRE(result.sub_count == 4); + REQUIRE(result.mul_count == 6); + REQUIRE(result.div_count == 2); + REQUIRE(result.exp_count == 1); + REQUIRE(result.global_read_count == 7); + REQUIRE(result.global_write_count == 1); + REQUIRE(result.local_read_count == 3); + REQUIRE(result.local_write_count == 2); + REQUIRE(result.func_call_count == 1); + REQUIRE(result.neg_count == 3); + } + } + } + + WHEN("Perf visitor pass runs before symtab visitor") { + PerfVisitor v; + THEN("exception is thrown") { + REQUIRE_THROWS_WITH(v.visitProgram(ast.get()), Catch::Contains("table not setup")); + } + } + } } \ No newline at end of file From 8098da00dd4c2623500182b7a79a35e04ea1b596 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Fri, 29 Dec 2017 17:25:13 +0100 Subject: [PATCH 032/871] First integration of clang-tidy with CMake which is enabled using cmake option -DENABLE_CLANG_TIDY=ON : - README update - Changes to enable clang-tidy checks: -*,modernize-*,readability-*,performance-*" - Enable additional checks "cppcoreguidelines-*,clang-analyzer-core*,google-*" (Squashed commit of sandbox/kumbhar/clang-tidy-integration) Change-Id: I3548861ac5f4c104ea6228afdb69c215e7106e0b NMODL Repo SHA: BlueBrain/nmodl@139cfee53bf88e36bea6258eac3d622d64f2e360 --- README.md | 56 ++++++++++++++----- cmake/nmodl/CMakeLists.txt | 1 + cmake/nmodl/ClangTidyHelper.cmake | 14 +++++ src/nmodl/lexer/nmodl_utils.cpp | 10 ++-- src/nmodl/lexer/nmodl_utils.hpp | 12 ++-- src/nmodl/lexer/token_mapping.cpp | 26 ++++----- src/nmodl/lexer/token_mapping.hpp | 6 +- src/nmodl/printer/json_printer.cpp | 12 ++-- src/nmodl/printer/json_printer.hpp | 6 +- src/nmodl/symtab/symbol.hpp | 6 +- src/nmodl/symtab/symbol_properties.cpp | 56 +++++++++---------- src/nmodl/symtab/symbol_table.cpp | 25 +++++---- src/nmodl/symtab/symbol_table.hpp | 14 ++--- src/nmodl/utils/table_data.cpp | 5 +- src/nmodl/visitors/perf_visitor.cpp | 10 ++-- src/nmodl/visitors/perf_visitor.hpp | 8 +-- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- test/nmodl/transpiler/lexer/tokens.cpp | 2 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 2 +- test/nmodl/transpiler/printer/printer.cpp | 4 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 6 +- test/nmodl/transpiler/visitor/visitor.cpp | 6 +- 22 files changed, 169 insertions(+), 120 deletions(-) create mode 100644 cmake/nmodl/ClangTidyHelper.cmake diff --git a/README.md b/README.md index 665b63d501..441fd2e7e0 100644 --- a/README.md +++ b/README.md @@ -10,11 +10,18 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl #### Dependencies -Make sure to have latest version of flex (>=2.6) and bison (>=3.0). For example, on OS X we do: +- flex (>=2.6) +- bison (>=3.0) +- CMake (>=3.1) +- C++ compiler (with c++11 support) +- Python2 (>=2.7) + +Make sure to have latest version of flex (>=2.6) and bison (>=3.0). For example, on OS X we typically install packages via brew or macport as: ``` brew install flex bison ``` + This will install flex and bison in: ``` @@ -22,10 +29,18 @@ This will install flex and bison in: /usr/local/opt/bison ``` -On Ubuntu 16.04 you should already have recent version of flex/bison. +On Ubuntu (>=16.04) you should already have recent version of flex/bison: + +``` +$ flex --version +flex 2.6.4 + +$ bison --version +bison (GNU Bison) 3.0.4 +``` #### Build -Install NOCMODL as: +Build/Compile NOCMODL as: ``` mkdir build @@ -38,38 +53,49 @@ If flex / bison is installed in non-standard location then set `PATH` env variab ``` cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" - ``` +``` - On Lugano system we have to use newer version using: + On Lugano BBP IV system we have to use newer versions installed in below path: ``` export PATH=/gpfs/bbp.cscs.ch/project/proj16/software/viz/hpc/bison-3.0.4-/bin:$PATH export PATH=/gpfs/bbp.cscs.ch/project/proj16/software/viz/hpc/flex-2.6.4/bin:$PATH ``` - #### Running NOCMODL +If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: + +``` +cmake .. -DENABLE_CLANG_TIDY=ON +``` + - You can independently run lexer, parser as: +#### Running NOCMODL - ``` +You can independently run lexer, parser or visitors as: + +``` ./bin/nocmodl_lexer --file ../test/input/channel.mod ./bin/nocmodl_parser --file ../test/input/channel.mod - ```` +./bin/nocmodl_visitor --file ../test/input/channel.mod +``` - #### Running Test +#### Running Test You can run unit tests as: - ``` +``` make test - ``` +``` - Or individual binaries with verbode output: + Or individual test binaries with verbode output: ``` - ./bin/testlexer -s - ./bin/testmodtoken -s + ./bin/test/testlexer -s + ./bin/test/testmodtoken -s + ./bin/test/testprinter -s + ./bin/test/testsymtab -s + ./bin/test/testvisitor -s ``` diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 34a7fde097..2b14fb3d8b 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -29,6 +29,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(GitRevision) include(FlexHelper) include(CompilerHelper) +include(ClangTidyHelper) include_directories( ${PROJECT_SOURCE_DIR}/src diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake new file mode 100644 index 0000000000..7ea94bd85f --- /dev/null +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -0,0 +1,14 @@ +if ( CMAKE_VERSION GREATER "3.5" ) + set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") + if (ENABLE_CLANG_TIDY) + find_program (CLANG_TIDY_EXE NAMES "clang-tidy") + if (CLANG_TIDY_EXE) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + set(CLANG_TIDY_CHECKS "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*") + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" CACHE STRING "" FORCE) + else() + message(AUTHOR_WARNING "clang-tidy not found!") + set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it + endif() + endif() +endif() \ No newline at end of file diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index b4fbb29443..cdc5dcc51e 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -1,4 +1,4 @@ -#include <string.h> +#include <cstring> #include <iostream> #include "ast/ast.hpp" @@ -37,7 +37,7 @@ namespace nmodl { * \todo In addition to keywords and methods, there are also external * definitions for math and neuron specific functions/variables. In the * token we should mark those as external. */ - SymbolType name_symbol(std::string text, PositionType& pos, TokenType type) { + SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type) { ModToken token(text, type, pos); auto value = new ast::Name(new ast::String(text)); value->setToken(token); @@ -53,14 +53,14 @@ namespace nmodl { stringutils::remove_character(text, '\''); auto prime_name = new ast::String(text); - auto prime_order = new ast::Integer(order, NULL); + auto prime_order = new ast::Integer(order, nullptr); auto value = new ast::PrimeName(prime_name, prime_order); value->setToken(token); return Parser::make_PRIME(value, pos); } /// create symbol for string ast class - SymbolType string_symbol(std::string text, PositionType& pos) { + SymbolType string_symbol(const std::string& text, PositionType& pos) { ModToken token(text, Token::STRING, pos); auto value = new ast::String(text); value->setToken(token); @@ -74,7 +74,7 @@ namespace nmodl { * reaction parsing where we have "lexical context". Hence, if token * type is passed then we don't check/search for the token type. */ - SymbolType token_symbol(std::string key, PositionType& pos, TokenType type) { + SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType type) { /// if token type is not passed, check if it is keyword or method if (type == Token::UNKNOWN) { if (is_keyword(key) || is_method(key)) { diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index ea2e34834e..df8cb3deed 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -20,10 +20,14 @@ namespace nmodl { using TokenType = nmodl::Parser::token_type; SymbolType double_symbol(double value, PositionType& pos); - SymbolType integer_symbol(int value, PositionType& pos, const char* name = nullptr); - SymbolType name_symbol(std::string text, PositionType& pos, TokenType token = Token::NAME); + SymbolType integer_symbol(int value, PositionType& pos, const char* text = nullptr); + SymbolType name_symbol(const std::string& text, + PositionType& pos, + TokenType type = Token::NAME); SymbolType prime_symbol(std::string text, PositionType& pos); - SymbolType string_symbol(std::string text, PositionType& pos); - SymbolType token_symbol(std::string text, PositionType& pos, TokenType token = Token::UNKNOWN); + SymbolType string_symbol(const std::string& text, PositionType& pos); + SymbolType token_symbol(const std::string& key, + PositionType& pos, + TokenType type = Token::UNKNOWN); } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index d0836eb930..4687d70c67 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -1,4 +1,4 @@ -#include <string.h> +#include <cstring> #include <map> #include <vector> @@ -115,14 +115,13 @@ namespace nmodl { /// numerical methods supported in nmodl struct MethodInfo { /// method types that will work with this method - long subtype = 0; + int64_t subtype = 0; /// if it's a variable timestep method int varstep = 0; - MethodInfo() { - } - MethodInfo(long s, int v) : subtype(s), varstep(v) { + MethodInfo() = default; + MethodInfo(int64_t s, int v) : subtype(s), varstep(v) { } }; @@ -281,22 +280,22 @@ namespace nmodl { static std::vector<std::string> neuron_vars = {"t", "dt", "celsius", "v", "diam", "area"}; - TokenType keyword_type(std::string name) { + TokenType keyword_type(const std::string& name) { return keywords[name]; } /** \todo: revisit implementation, this is no longer * necessary as token_type is sufficient */ - TokenType method_type(std::string /*name*/) { + TokenType method_type(const std::string& /*name*/) { return Token::METHOD; } - bool is_externdef(std::string name) { + bool is_externdef(const std::string& name) { return (extern_definitions.find(name) != extern_definitions.end()); } - DefinitionType extdef_type(std::string name) { + DefinitionType extdef_type(const std::string& name) { if (!is_externdef(name)) { throw std::runtime_error("Can't find " + name + " in external definitions!"); } @@ -307,16 +306,16 @@ namespace nmodl { /// methods exposed to lexer, parser and compilers passes - bool is_keyword(std::string name) { + bool is_keyword(const std::string& name) { return (internal::keywords.find(name) != internal::keywords.end()); } - bool is_method(std::string name) { + bool is_method(const std::string& name) { return (internal::methods.find(name) != internal::methods.end()); } /// return token type for associated name (used by nmodl scanner) - TokenType token_type(std::string name) { + TokenType token_type(const std::string& name) { if (is_keyword(name)) { return internal::keyword_type(name); } @@ -336,6 +335,7 @@ namespace nmodl { /// return all solver methods as well as commonly used math functions std::vector<std::string> get_external_functions() { std::vector<std::string> result; + result.reserve(internal::methods.size()); for (auto& method : internal::methods) { result.push_back(method.first); } @@ -345,4 +345,4 @@ namespace nmodl { return result; } -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index f69d5b5a5c..487c452be1 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -4,9 +4,9 @@ #include "parser/nmodl_parser.hpp" namespace nmodl { - bool is_keyword(std::string name); - bool is_method(std::string name); - nmodl::Parser::token_type token_type(std::string name); + bool is_keyword(const std::string& name); + bool is_method(const std::string& name); + nmodl::Parser::token_type token_type(const std::string& name); std::vector<std::string> get_external_variables(); std::vector<std::string> get_external_functions(); } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 2aa7952347..ee51da940f 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -1,15 +1,15 @@ #include "printer/json_printer.hpp" /// Dump output to provided file -JSONPrinter::JSONPrinter(std::string fname) { - if (fname.empty()) { +JSONPrinter::JSONPrinter(const std::string& filename) { + if (filename.empty()) { throw std::runtime_error("Empty filename for JSONPrinter"); } - ofs.open(fname.c_str()); + ofs.open(filename.c_str()); if (ofs.fail()) { - auto msg = "Error while opening file '" + fname + "' for JSONPrinter"; + auto msg = "Error while opening file '" + filename + "' for JSONPrinter"; throw std::runtime_error(msg); } @@ -18,7 +18,7 @@ JSONPrinter::JSONPrinter(std::string fname) { } /// Add node to json (typically basic type) -void JSONPrinter::addNode(std::string value, std::string name) { +void JSONPrinter::addNode(std::string value, const std::string& name) { if (!block) { auto text = "Block not initialized (pushBlock missing?)"; throw std::logic_error(text); @@ -31,7 +31,7 @@ void JSONPrinter::addNode(std::string value, std::string name) { /// Add new json object (typically start of new block) /// name here is type of new block encountered -void JSONPrinter::pushBlock(std::string name) { +void JSONPrinter::pushBlock(const std::string& name) { if (block) { stack.push(block); } diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 9e0b36f25c..f456e990f5 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -46,7 +46,7 @@ class JSONPrinter { bool compact = false; public: - JSONPrinter(std::string filename); + JSONPrinter(const std::string& filename); /// By default dump output to std::cout JSONPrinter() : result(new std::ostream(std::cout.rdbuf())) { @@ -60,8 +60,8 @@ class JSONPrinter { flush(); } - void pushBlock(std::string name); - void addNode(std::string value, std::string name="value"); + void pushBlock(const std::string& name); + void addNode(std::string value, const std::string& name = "value"); void popBlock(); void flush(); diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 4d60108002..bb012e1309 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -106,15 +106,15 @@ namespace symtab { bool is_external_symbol_only(); - bool has_properties(SymbolInfo prop); + bool has_properties(SymbolInfo new_properties); - void combine_properties(SymbolInfo prop); + void combine_properties(SymbolInfo new_properties); void add_property(NmodlInfo property); void add_property(SymbolInfo property); - void set_order(int order); + void set_order(int new_order); }; } // namespace symtab diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index bc775180e4..039ff80d73 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -16,115 +16,115 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { std::vector<std::string> properties; if (has_property(obj, NmodlInfo::local_var)) { - properties.push_back("local"); + properties.emplace_back("local"); } if (has_property(obj, NmodlInfo::global_var)) { - properties.push_back("global"); + properties.emplace_back("global"); } if (has_property(obj, NmodlInfo::range_var)) { - properties.push_back("range"); + properties.emplace_back("range"); } if (has_property(obj, NmodlInfo::param_assign)) { - properties.push_back("parameter"); + properties.emplace_back("parameter"); } if (has_property(obj, NmodlInfo::pointer_var)) { - properties.push_back("pointer"); + properties.emplace_back("pointer"); } if (has_property(obj, NmodlInfo::bbcore_pointer_var)) { - properties.push_back("bbcore_pointer"); + properties.emplace_back("bbcore_pointer"); } if (has_property(obj, NmodlInfo::extern_var)) { - properties.push_back("extern"); + properties.emplace_back("extern"); } if (has_property(obj, NmodlInfo::prime_name)) { - properties.push_back("prime_name"); + properties.emplace_back("prime_name"); } if (has_property(obj, NmodlInfo::dependent_def)) { - properties.push_back("dependent_def"); + properties.emplace_back("dependent_def"); } if (has_property(obj, NmodlInfo::unit_def)) { - properties.push_back("unit_def"); + properties.emplace_back("unit_def"); } if (has_property(obj, NmodlInfo::read_ion_var)) { - properties.push_back("read_ion"); + properties.emplace_back("read_ion"); } if (has_property(obj, NmodlInfo::write_ion_var)) { - properties.push_back("write_ion"); + properties.emplace_back("write_ion"); } if (has_property(obj, NmodlInfo::nonspe_cur_var)) { - properties.push_back("nonspe_cur"); + properties.emplace_back("nonspe_cur"); } if (has_property(obj, NmodlInfo::electrode_cur_var)) { - properties.push_back("electrode_cur"); + properties.emplace_back("electrode_cur"); } if (has_property(obj, NmodlInfo::section_var)) { - properties.push_back("section"); + properties.emplace_back("section"); } if (has_property(obj, NmodlInfo::argument)) { - properties.push_back("argument"); + properties.emplace_back("argument"); } if (has_property(obj, NmodlInfo::function_block)) { - properties.push_back("function_block"); + properties.emplace_back("function_block"); } if (has_property(obj, NmodlInfo::procedure_block)) { - properties.push_back("procedure_block"); + properties.emplace_back("procedure_block"); } if (has_property(obj, NmodlInfo::derivative_block)) { - properties.push_back("derivative_block"); + properties.emplace_back("derivative_block"); } if (has_property(obj, NmodlInfo::linear_block)) { - properties.push_back("linear_block"); + properties.emplace_back("linear_block"); } if (has_property(obj, NmodlInfo::non_linear_block)) { - properties.push_back("non_linear_block"); + properties.emplace_back("non_linear_block"); } if (has_property(obj, NmodlInfo::discrete_block)) { - properties.push_back("discrete_block"); + properties.emplace_back("discrete_block"); } if (has_property(obj, NmodlInfo::partial_block)) { - properties.push_back("partial_block"); + properties.emplace_back("partial_block"); } if (has_property(obj, NmodlInfo::kinetic_block)) { - properties.push_back("kinetic_block"); + properties.emplace_back("kinetic_block"); } if (has_property(obj, NmodlInfo::function_table_block)) { - properties.push_back("function_table_block"); + properties.emplace_back("function_table_block"); } if (has_property(obj, NmodlInfo::factor_def)) { - properties.push_back("factor_def"); + properties.emplace_back("factor_def"); } if (has_property(obj, NmodlInfo::extern_neuron_variable)) { - properties.push_back("extern_neuron_var"); + properties.emplace_back("extern_neuron_var"); } if (has_property(obj, NmodlInfo::extern_method)) { - properties.push_back("extern_method"); + properties.emplace_back("extern_method"); } return properties; diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 7bd9ee6c35..85ec592dc3 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -1,7 +1,8 @@ +#include <utility> + #include "lexer/token_mapping.hpp" #include "symtab/symbol_table.hpp" #include "utils/table_data.hpp" -#include "utils/string_utils.hpp" namespace symtab { @@ -10,7 +11,7 @@ namespace symtab { * * \todo Revisit the error handling */ - void Table::insert(std::shared_ptr<Symbol> symbol) { + void Table::insert(const std::shared_ptr<Symbol>& symbol) { std::string name = symbol->get_name(); if (symbols.find(name) != symbols.end()) { throw std::runtime_error("Trying to re-insert symbol " + name); @@ -18,7 +19,7 @@ namespace symtab { symbols[name] = symbol; } - std::shared_ptr<Symbol> Table::lookup(std::string name) { + std::shared_ptr<Symbol> Table::lookup(const std::string& name) { if (symbols.find(name) == symbols.end()) { return nullptr; } @@ -33,11 +34,11 @@ namespace symtab { } /// insert new symbol table of one of the children block - void SymbolTable::insert_table(std::string name, std::shared_ptr<SymbolTable> table) { + void SymbolTable::insert_table(const std::string& name, std::shared_ptr<SymbolTable> table) { if (children.find(name) != children.end()) { throw std::runtime_error("Trying to re-insert SymbolTable " + name); } - children[name] = table; + children[name] = std::move(table); } /** Get all symbols which can be used in global scope. Note that @@ -89,7 +90,7 @@ namespace symtab { } /// lookup for symbol in current scope as well as all parents - std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(std::string name) { + std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(const std::string& name) { auto symbol = table.lookup(name); if (!symbol && parent) { symbol = parent->lookup_in_scope(name); @@ -102,7 +103,7 @@ namespace symtab { /// current symbol table that is under process. we should /// rename it. note that we still need parent symbol table /// filed to traverse the parents. revisit this usage. - std::shared_ptr<Symbol> ModelSymbolTable::lookup(std::string name) { + std::shared_ptr<Symbol> ModelSymbolTable::lookup(const std::string& name) { /// parent symbol is not set means symbol table is /// is not used with visitor at all. it would be ok // to just return nullptr? @@ -127,7 +128,7 @@ namespace symtab { return symbol; } - void ModelSymbolTable::insert(std::shared_ptr<Symbol> symbol) { + void ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { if (parent_symtab == nullptr) { throw std::logic_error("Can not insert symbol without entering scope"); } @@ -146,7 +147,7 @@ namespace symtab { auto properties = to_string(search_symbol->get_properties()); auto node = symbol->get_node(); std::string type = "UNKNOWN"; - if (node) { + if (node != nullptr) { type = node->getTypeName(); } @@ -277,9 +278,9 @@ namespace symtab { //============================================================================= void Table::print(std::stringstream& stream, std::string title, int indent) { - if (symbols.size()) { + if (!symbols.empty()) { TableData table; - table.title = title; + table.title = std::move(title); table.headers = {"NAME", "PROPERTIES", "LOCATION", "# READS", "# WRITES"}; table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, text_alignment::right, text_alignment::right}; @@ -321,7 +322,7 @@ namespace symtab { /// can be printed from the same indentation level /// (this is to avoid unnecessary empty indentations) auto next_level = level; - if (table.symbols.size() == 0) { + if (table.symbols.empty()) { next_level--; } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 31d0aa8ed0..a985e47376 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -26,10 +26,10 @@ namespace symtab { std::map<std::string, std::shared_ptr<Symbol>> symbols; /// insert new symbol into table - void insert(std::shared_ptr<Symbol> sym); + void insert(const std::shared_ptr<Symbol>& symbol); /// check if symbol with given name exist - std::shared_ptr<Symbol> lookup(std::string name); + std::shared_ptr<Symbol> lookup(const std::string& name); /// pretty print void print(std::stringstream& stream, std::string title, int indent); @@ -143,15 +143,15 @@ namespace symtab { return new SymbolTable(*this); } - std::shared_ptr<Symbol> lookup_in_scope(std::string name); + std::shared_ptr<Symbol> lookup_in_scope(const std::string& name); std::vector<std::shared_ptr<Symbol>> get_global_variables(); bool under_global_scope(); - void insert_table(std::string name, std::shared_ptr<SymbolTable> table); + void insert_table(const std::string& name, std::shared_ptr<SymbolTable> table); - void print(std::stringstream& ss, int indent); + void print(std::stringstream& ss, int level); }; /** @@ -192,9 +192,9 @@ namespace symtab { /// leaving current nmodl block void leave_scope(); - void insert(std::shared_ptr<Symbol> symbol); + void insert(const std::shared_ptr<Symbol>& symbol); - std::shared_ptr<Symbol> lookup(std::string name); + std::shared_ptr<Symbol> lookup(const std::string& name); /// pretty print void print(std::stringstream& ss); diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 2ad22d4ed9..0167ad0058 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -24,7 +24,7 @@ void TableData::print(std::stringstream& stream, int indent) { const int PADDING = 1; /// not necessary to print empty table - if (rows.size() == 0 || headers.size() == 0) { + if (rows.empty() || headers.empty()) { return; } @@ -35,8 +35,9 @@ void TableData::print(std::stringstream& stream, int indent) { std::vector<unsigned> col_width(ncolumns); /// alignment is optional, so fill remaining withh right alignment - for (unsigned i = alignments.size(); i < ncolumns; i++) + for (unsigned i = alignments.size(); i < ncolumns; i++) { alignments.push_back(text_alignment::center); + } /// calculate space required for each column unsigned row_width = 0; diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 96275fc29b..9edeb2e1f8 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -1,8 +1,10 @@ +#include <utility> + #include "visitors/perf_visitor.hpp" using namespace symtab; -PerfVisitor::PerfVisitor(std::string filename) : printer(new JSONPrinter(filename)) { +PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) { } /// count math operations from all binary expressions @@ -257,7 +259,7 @@ void PerfVisitor::visitUnaryExpression(UnaryExpression* node) { * count for "exp" symbol. Same for solve statement where name will * be neuron solver method. */ -bool PerfVisitor::symbol_to_skip(std::shared_ptr<Symbol> symbol) { +bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { bool skip = false; auto is_method = symbol->has_properties(NmodlInfo::extern_method); @@ -272,7 +274,7 @@ bool PerfVisitor::symbol_to_skip(std::shared_ptr<Symbol> symbol) { return skip; } -bool PerfVisitor::is_local_variable(std::shared_ptr<symtab::Symbol> symbol) { +bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbol) { bool is_local = false; auto properties = NmodlInfo::local_var | NmodlInfo::argument | NmodlInfo::function_block; if (symbol->has_properties(properties)) { @@ -284,7 +286,7 @@ bool PerfVisitor::is_local_variable(std::shared_ptr<symtab::Symbol> symbol) { /** Find symbol in closest scope (up to parent) and update * read/write count. Also update ops count in current block. */ -void PerfVisitor::update_memory_ops(std::string name) { +void PerfVisitor::update_memory_ops(const std::string& name) { if (start_measurement) { auto symbol = current_symtab->lookup_in_scope(name); if (symbol == nullptr || symbol_to_skip(symbol)) { diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 9321a55162..f24d1727fe 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -70,11 +70,11 @@ class PerfVisitor : public AstVisitor { /// if not json, all goes to string std::stringstream stream; - void update_memory_ops(std::string name); + void update_memory_ops(const std::string& name); - bool symbol_to_skip(std::shared_ptr<symtab::Symbol> symbol); + bool symbol_to_skip(const std::shared_ptr<symtab::Symbol>& symbol); - bool is_local_variable(std::shared_ptr<symtab::Symbol> symbol); + bool is_local_variable(const std::shared_ptr<symtab::Symbol>& symbol); void measure_performance(AST* node); @@ -83,7 +83,7 @@ class PerfVisitor : public AstVisitor { public: PerfVisitor() = default; - explicit PerfVisitor(std::string filename); + explicit PerfVisitor(const std::string& filename); void compact_json(bool flag) { printer->compact_json(flag); diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 35803801cb..4ac534cd24 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -7,7 +7,7 @@ void VerbatimVisitor::visitVerbatim(Verbatim* node) { block = node->statement->eval(); } - if (block.size() && verbose) { + if (!block.empty() && verbose) { std::cout << "BLOCK START"; std::cout << block; std::cout << "\nBLOCK END \n\n"; diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 0d348c8e98..43428b953c 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -10,7 +10,7 @@ using Token = nmodl::Parser::token; /// just retrieve token type from lexer -nmodl::Parser::token_type token_type(std::string name) { +nmodl::Parser::token_type token_type(const std::string& name) { std::istringstream ss(name); std::istream& in = ss; diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 0d090327fd..0825819ffb 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -9,7 +9,7 @@ /// retrieve token from lexer template <typename T> -void symbol_type(std::string name, T& value) { +void symbol_type(const std::string& name, T& value) { std::istringstream ss(name); std::istream& in = ss; diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index e0df777947..2fb653939d 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -16,7 +16,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { p.popBlock(); p.flush(); - auto result = "{\"A\":[{\"value\":\"B\"}]}"; + auto result = R"({"A":[{"value":"B"}]})"; REQUIRE(ss.str() == result); } @@ -34,7 +34,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { p.popBlock(); p.flush(); - auto result = "{\"A\":[{\"value\":\"B\"},{\"value\":\"C\"},{\"D\":[{\"value\":\"E\"}]}]}"; + auto result = R"({"A":[{"value":"B"},{"value":"C"},{"D":[{"value":"E"}]}]})"; REQUIRE(ss.str() == result); } } diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 2d86e12e63..940b2dff8d 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -23,7 +23,7 @@ SCENARIO("Symbol properties can be added and converted to string") { GIVEN("A empty property") { WHEN("converted to string") { THEN("returns empty string") { - REQUIRE(to_string(prop1) == ""); + REQUIRE(to_string(prop1).empty()); } } WHEN("checked for property") { @@ -34,7 +34,7 @@ SCENARIO("Symbol properties can be added and converted to string") { WHEN("adding another empty property") { SymbolInfo result = prop1 | prop1; THEN("to_string still returns empty string") { - REQUIRE(to_string(result) == ""); + REQUIRE(to_string(result).empty()); } } WHEN("added some other property") { @@ -156,7 +156,7 @@ SCENARIO("Symbol table operations") { table->insert(symbol); auto variables = table->get_global_variables(); THEN("table doesn't have any global variables") { - REQUIRE(variables.size() == 0); + REQUIRE(variables.empty()); WHEN("added global symbol") { auto next_symbol = std::make_shared<Symbol>("gamma", ModToken()); next_symbol->add_property(NmodlInfo::dependent_def); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 44820a448e..75edd03e5e 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -15,7 +15,7 @@ using json = nlohmann::json; // Verbatim visitor tests //============================================================================= -std::vector<std::string> run_verbatim_visitor(std::string text) { +std::vector<std::string> run_verbatim_visitor(const std::string& text) { nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -48,7 +48,7 @@ TEST_CASE("Verbatim Visitor") { // JSON visitor tests //============================================================================= -std::string run_json_visitor(std::string text, bool compact = false) { +std::string run_json_visitor(const std::string& text, bool compact = false) { nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -88,7 +88,7 @@ TEST_CASE("JSON Visitor") { SECTION("JSON text test (compact format)") { std::string nmodl_text = "NEURON {}"; - std::string expected = "{\"Program\":[{\"NeuronBlock\":[{\"StatementBlock\":[]}]}]}"; + std::string expected = R"({"Program":[{"NeuronBlock":[{"StatementBlock":[]}]}]})"; auto result = run_json_visitor(nmodl_text, true); REQUIRE(result == expected); From c8d40b7bcf19f79c4f1f9e13edae7a8f004024bd Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 30 Dec 2017 18:20:36 +0100 Subject: [PATCH 033/871] Change all CamelCase to snake_case in order to make coding style consistent: - Rename LDifuse to LonDifuse - Remove node_ast_type method, added to_snake_case method (Squashed commit of sandbox/kumbhar/snake_case) Change-Id: I7a6b36dda358032dac71fdb3eb76db2640a66dfe NMODL Repo SHA: BlueBrain/nmodl@e9214645c02bed22acfbb600e80519e6a43c50e1 --- src/nmodl/ast/ast_utils.hpp | 276 ++++++++++--------- src/nmodl/language/ast_printer.py | 45 ++- src/nmodl/language/nmodl.yaml | 4 +- src/nmodl/language/utils.py | 6 +- src/nmodl/language/visitors_printer.py | 46 ++-- src/nmodl/lexer/main.cpp | 10 +- src/nmodl/lexer/nmodl.ll | 8 +- src/nmodl/lexer/nmodl_utils.cpp | 12 +- src/nmodl/parser/nmodl.yy | 38 +-- src/nmodl/parser/nmodl_driver.hpp | 2 +- src/nmodl/printer/json_printer.cpp | 8 +- src/nmodl/printer/json_printer.hpp | 6 +- src/nmodl/symtab/symbol_table.cpp | 8 +- src/nmodl/symtab/symbol_table.hpp | 4 +- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/visitors/main.cpp | 12 +- src/nmodl/visitors/perf_visitor.cpp | 60 ++-- src/nmodl/visitors/perf_visitor.hpp | 72 ++--- src/nmodl/visitors/symtab_visitor_helper.hpp | 30 +- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_visitor.hpp | 2 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 8 +- test/nmodl/transpiler/printer/printer.cpp | 20 +- test/nmodl/transpiler/visitor/visitor.cpp | 10 +- 24 files changed, 344 insertions(+), 347 deletions(-) diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index f4d7966695..5f3055a7c3 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -73,13 +73,13 @@ namespace ast { */ /* all AST nodes provide visit children and accept methods */ - virtual void visitChildren(Visitor* v) = 0; + virtual void visit_children(Visitor* v) = 0; virtual void accept(Visitor* v) = 0; - virtual Type getType() = 0; - virtual std::string getTypeName() = 0; + virtual Type get_type() = 0; + virtual std::string get_type_name() = 0; - virtual std::string getName() { - throw std::logic_error("getName() not implemented"); + virtual std::string get_name() { + throw std::logic_error("get_name() not implemented"); } virtual AST* clone() { @@ -87,530 +87,532 @@ namespace ast { } /* @todo: please revisit this. adding quickly for symtab */ - virtual ModToken* getToken() { /*std::cout << "\n ERROR: getToken not implemented!";*/ + virtual ModToken* get_token() { /*std::cout << "\n ERROR: get_token not implemented!";*/ return nullptr; } - virtual std::shared_ptr<symtab::SymbolTable> getSymbolTable() { - throw std::runtime_error("getSymbolTable() not implemented"); + virtual std::shared_ptr<symtab::SymbolTable> get_symbol_table() { + throw std::runtime_error("get_symbol_table() not implemented"); } virtual ~AST() { } - virtual bool isAST() { + virtual bool is_ast() { return true; } - virtual bool isStatement() { + virtual bool is_statement() { return false; } - virtual bool isExpression() { + virtual bool is_expression() { return false; } - virtual bool isBlock() { + virtual bool is_block() { return false; } - virtual bool isIdentifier() { + virtual bool is_identifier() { return false; } - virtual bool isNumber() { + virtual bool is_number() { return false; } - virtual bool isString() { + virtual bool is_string() { return false; } - virtual bool isInteger() { + virtual bool is_integer() { return false; } - virtual bool isFloat() { + virtual bool is_float() { return false; } - virtual bool isDouble() { + virtual bool is_double() { return false; } - virtual bool isBoolean() { + virtual bool is_boolean() { return false; } - virtual bool isName() { + virtual bool is_name() { return false; } - virtual bool isPrimeName() { + virtual bool is_prime_name() { return false; } - virtual bool isVarName() { + virtual bool is_var_name() { return false; } - virtual bool isIndexedName() { + virtual bool is_indexed_name() { return false; } - virtual bool isArgument() { + virtual bool is_argument() { return false; } - virtual bool isReactVarName() { + virtual bool is_react_var_name() { return false; } - virtual bool isReadIonVar() { + virtual bool is_read_ion_var() { return false; } - virtual bool isWriteIonVar() { + virtual bool is_write_ion_var() { return false; } - virtual bool isNonspeCurVar() { + virtual bool is_nonspe_cur_var() { return false; } - virtual bool isElectrodeCurVar() { + virtual bool is_electrode_cur_var() { return false; } - virtual bool isSectionVar() { + virtual bool is_section_var() { return false; } - virtual bool isRangeVar() { + virtual bool is_range_var() { return false; } - virtual bool isGlobalVar() { + virtual bool is_global_var() { return false; } - virtual bool isPointerVar() { + virtual bool is_pointer_var() { return false; } - virtual bool isBbcorePointerVar() { + virtual bool is_bbcore_pointer_var() { return false; } - virtual bool isExternVar() { + virtual bool is_extern_var() { return false; } - virtual bool isThreadsafeVar() { + virtual bool is_threadsafe_var() { return false; } - virtual bool isParamBlock() { + virtual bool is_param_block() { return false; } - virtual bool isStepBlock() { + virtual bool is_step_block() { return false; } - virtual bool isIndependentBlock() { + virtual bool is_independent_block() { return false; } - virtual bool isDependentBlock() { + virtual bool is_dependent_block() { return false; } - virtual bool isStateBlock() { + virtual bool is_state_block() { return false; } - virtual bool isPlotBlock() { + virtual bool is_plot_block() { return false; } - virtual bool isInitialBlock() { + virtual bool is_initial_block() { return false; } - virtual bool isConstructorBlock() { + virtual bool is_constructor_block() { return false; } - virtual bool isDestructorBlock() { + virtual bool is_destructor_block() { return false; } - virtual bool isStatementBlock() { + virtual bool is_statement_block() { return false; } - virtual bool isDerivativeBlock() { + virtual bool is_derivative_block() { return false; } - virtual bool isLinearBlock() { + virtual bool is_linear_block() { return false; } - virtual bool isNonLinearBlock() { + virtual bool is_non_linear_block() { return false; } - virtual bool isDiscreteBlock() { + virtual bool is_discrete_block() { return false; } - virtual bool isPartialBlock() { + virtual bool is_partial_block() { return false; } - virtual bool isFunctionTableBlock() { + virtual bool is_function_table_block() { return false; } - virtual bool isFunctionBlock() { + virtual bool is_function_block() { return false; } - virtual bool isProcedureBlock() { + virtual bool is_procedure_block() { return false; } - virtual bool isNetReceiveBlock() { + virtual bool is_net_receive_block() { return false; } - virtual bool isSolveBlock() { + virtual bool is_solve_block() { return false; } - virtual bool isBreakpointBlock() { + virtual bool is_breakpoint_block() { return false; } - virtual bool isTerminalBlock() { + virtual bool is_terminal_block() { return false; } - virtual bool isBeforeBlock() { + virtual bool is_before_block() { return false; } - virtual bool isAfterBlock() { + virtual bool is_after_block() { return false; } - virtual bool isBABlock() { + virtual bool is_ba_block() { return false; } - virtual bool isForNetcon() { + virtual bool is_for_netcon() { return false; } - virtual bool isKineticBlock() { + virtual bool is_kinetic_block() { return false; } - virtual bool isMatchBlock() { + virtual bool is_match_block() { return false; } - virtual bool isUnitBlock() { + virtual bool is_unit_block() { return false; } - virtual bool isConstantBlock() { + virtual bool is_constant_block() { return false; } - virtual bool isNeuronBlock() { + virtual bool is_neuron_block() { return false; } - virtual bool isUnit() { + virtual bool is_unit() { return false; } - virtual bool isDoubleUnit() { + virtual bool is_double_unit() { return false; } - virtual bool isLocalVar() { + virtual bool is_local_var() { return false; } - virtual bool isLimits() { + virtual bool is_limits() { return false; } - virtual bool isNumberRange() { + virtual bool is_number_range() { return false; } - virtual bool isPlotVar() { + virtual bool is_plot_var() { return false; } - virtual bool isBinaryOperator() { + virtual bool is_binary_operator() { return false; } - virtual bool isUnaryOperator() { + virtual bool is_unary_operator() { return false; } - virtual bool isReactionOperator() { + virtual bool is_reaction_operator() { return false; } - virtual bool isBinaryExpression() { + virtual bool is_binary_expression() { return false; } - virtual bool isUnaryExpression() { + virtual bool is_unary_expression() { return false; } - virtual bool isNonLinEuation() { + virtual bool is_non_lin_euation() { return false; } - virtual bool isLinEquation() { + virtual bool is_lin_equation() { return false; } - virtual bool isFunctionCall() { + virtual bool is_function_call() { return false; } - virtual bool isFirstLastTypeIndex() { + virtual bool is_first_last_type_index() { return false; } - virtual bool isWatch() { + virtual bool is_watch() { return false; } - virtual bool isQueueExpressionType() { + virtual bool is_queue_expression_type() { return false; } - virtual bool isMatch() { + virtual bool is_match() { return false; } - virtual bool isBABlockType() { + virtual bool is_ba_block_type() { return false; } - virtual bool isUnitDef() { + virtual bool is_unit_def() { return false; } - virtual bool isFactorDef() { + virtual bool is_factor_def() { return false; } - virtual bool isValence() { + virtual bool is_valence() { return false; } - virtual bool isUnitState() { + virtual bool is_unit_state() { return false; } - virtual bool isLocalListStatement() { + virtual bool is_local_list_statement() { return false; } - virtual bool isModel() { + virtual bool is_model() { return false; } - virtual bool isDefine() { + virtual bool is_define() { return false; } - virtual bool isInclude() { + virtual bool is_include() { return false; } - virtual bool isParamAssign() { + virtual bool is_param_assign() { return false; } - virtual bool isStepped() { + virtual bool is_stepped() { return false; } - virtual bool isIndependentDef() { + virtual bool is_independent_def() { return false; } - virtual bool isDependentDef() { + virtual bool is_dependent_def() { return false; } - virtual bool isPlotDeclaration() { + virtual bool is_plot_declaration() { return false; } - virtual bool isConductanceHint() { + virtual bool is_conductance_hint() { return false; } - virtual bool isExpressionStatement() { + virtual bool is_expression_statement() { return false; } - virtual bool isProtectStatement() { + virtual bool is_protect_statement() { return false; } - virtual bool isFromStatement() { + virtual bool is_from_statement() { return false; } - virtual bool isForAllStatement() { + virtual bool is_for_all_statement() { return false; } - virtual bool isWhileStatement() { + virtual bool is_while_statement() { return false; } - virtual bool isIfStatement() { + virtual bool is_if_statement() { return false; } - virtual bool isElseIfStatement() { + virtual bool is_else_if_statement() { return false; } - virtual bool isElseStatement() { + virtual bool is_else_statement() { return false; } - virtual bool isPartialEquation() { + virtual bool is_partial_equation() { return false; } - virtual bool isPartialBoundary() { + virtual bool is_partial_boundary() { return false; } - virtual bool isWatchStatement() { + virtual bool is_watch_statement() { return false; } - virtual bool isMutexLock() { + virtual bool is_mutex_lock() { return false; } - virtual bool isMutexUnlock() { + virtual bool is_mutex_unlock() { return false; } - virtual bool isReset() { + virtual bool is_reset() { return false; } - virtual bool isSens() { + virtual bool is_sens() { return false; } - virtual bool isConserve() { + virtual bool is_conserve() { return false; } - virtual bool isCompartment() { + virtual bool is_compartment() { return false; } - virtual bool isLDifuse() { + virtual bool is_lon_difuse() { return false; } - virtual bool isReactionStatement() { + virtual bool is_reaction_statement() { return false; } - virtual bool isLagStatement() { + virtual bool is_lag_statement() { return false; } - virtual bool isQueueStatement() { + virtual bool is_queue_statement() { return false; } - virtual bool isConstantStatement() { + virtual bool is_constant_statement() { return false; } - virtual bool isTableStatement() { + virtual bool is_table_statement() { return false; } - virtual bool isSuffix() { + virtual bool is_suffix() { return false; } - virtual bool isUseion() { + virtual bool is_useion() { return false; } - virtual bool isNonspecific() { + /// \todo : how is this different from is_nonspe_cur_var ? + virtual bool is_nonspecific() { return false; } - virtual bool isElctrodeCurrent() { + virtual bool is_elctrode_current() { return false; } - virtual bool isSection() { + virtual bool is_section() { return false; } - virtual bool isRange() { + virtual bool is_range() { return false; } - virtual bool isGlobal() { + virtual bool is_global() { return false; } - virtual bool isPointer() { + /// \todo : how these are different from is_pointer_var ? + virtual bool is_pointer() { return false; } - virtual bool isBbcorePtr() { + virtual bool is_bbcore_ptr() { return false; } - virtual bool isExternal() { + virtual bool is_external() { return false; } - virtual bool isThreadSafe() { + virtual bool is_thread_safe() { return false; } - virtual bool isVerbatim() { + virtual bool is_verbatim() { return false; } - virtual bool isComment() { + virtual bool is_comment() { return false; } - virtual bool isProgram() { + virtual bool is_program() { return false; } }; diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 0be877cdff..80ec929ff2 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -10,9 +10,9 @@ def ast_types(self): """ print ast type for every ast node """ self.writer.write_line("enum class Type {", post_gutter=1) for node in self.nodes[:-1]: - name = node_ast_type(node.class_name) + "," + name = to_snake_case(node.class_name).upper() + "," self.writer.write_line(name) - name = node_ast_type(self.nodes[-1].class_name) + name = to_snake_case(self.nodes[-1].class_name).upper() self.writer.write_line(name) self.writer.write_line("};", pre_gutter=-1) @@ -77,7 +77,7 @@ def forward_declarations(self): def ast_classes_declaration(self): - # TODO: for demo, removing error messages in getToken method. + # TODO: for demo, removing error messages in get_token method. # need to look in detail whether we should abort in this case # when ModToken in NULL. This was introduced when added SymtabVisitor # pass to get Token information. @@ -163,42 +163,42 @@ def ast_classes_declaration(self): if child.is_string_node(): method = "eval" else: - method = "getName" + method = "get_name" - self.writer.write_line("virtual std::string getName() override {") + self.writer.write_line("virtual std::string get_name() override {") self.writer.write_line(" return " + varname + "->" + method + "();") self.writer.write_line("}") if not node.has_token: - self.writer.write_line(virtual + "ModToken* getToken() override {") - self.writer.write_line(" return " + varname + "->getToken();") + self.writer.write_line(virtual + "ModToken* get_token() override {") + self.writer.write_line(" return " + varname + "->get_token();") self.writer.write_line("}") get_method_added = True if node.is_prime_node() and child.varname == ORDER_VAR_NAME: - self.writer.write_line("int getOrder() " + " { return " + ORDER_VAR_NAME + "->eval(); }") + self.writer.write_line("int get_order() " + " { return " + ORDER_VAR_NAME + "->eval(); }") # add method to return typename - self.writer.write_line("virtual std::string getTypeName() override { return \"" + node.class_name + "\"; }") + self.writer.write_line("virtual std::string get_type_name() override { return \"" + node.class_name + "\"; }") # all member functions - self.writer.write_line(virtual + "void visitChildren (Visitor* v) override;") - self.writer.write_line(virtual + "void accept(Visitor* v) override { v->visit" + node.class_name + "(this); }") + self.writer.write_line(virtual + "void visit_children (Visitor* v) override;") + self.writer.write_line(virtual + "void accept(Visitor* v) override { v->visit_" + to_snake_case(node.class_name) + "(this); }") # TODO: type should declared as enum class - typename = node_ast_type(node.class_name) - self.writer.write_line(virtual + "Type getType() override { return Type::" + typename + "; }") - self.writer.write_line("bool is" + node.class_name + " () override { return true; }") + typename = to_snake_case(node.class_name).upper() + self.writer.write_line(virtual + "Type get_type() override { return Type::" + typename + "; }") + self.writer.write_line("bool is_" + to_snake_case(node.class_name) + " () override { return true; }") self.writer.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") if node.has_token: - self.writer.write_line(virtual + "ModToken* getToken() " + override + " { return token.get(); }") - self.writer.write_line("void setToken(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") + self.writer.write_line(virtual + "ModToken* get_token() " + override + " { return token.get(); }") + self.writer.write_line("void set_token(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") if node.is_symtab_needed(): - self.writer.write_line("void setSymbolTable(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") - self.writer.write_line("std::shared_ptr<symtab::SymbolTable> getSymbolTable() override " + " { return symtab; }") + self.writer.write_line("void set_symbol_table(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") + self.writer.write_line("std::shared_ptr<symtab::SymbolTable> get_symbol_table() override " + " { return symtab; }") if node.is_number_node(): self.writer.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") @@ -210,10 +210,10 @@ def ast_classes_declaration(self): self.writer.write_line("void negate() override { value = -value; }") if node.is_identifier_node(): - self.writer.write_line(virtual + "void setName(std::string /*name*/)" + override + " { std::cout << \"ERROR : setName() not implemented! \"; abort(); }") + self.writer.write_line(virtual + "void set_name(std::string /*name*/)" + override + " { std::cout << \"ERROR : set_name() not implemented! \"; abort(); }") if node.is_name_node(): - self.writer.write_line(virtual + "void setName(std::string name)" + override + " { value->set(name); }") + self.writer.write_line(virtual + "void set_name(std::string name)" + override + " { value->set(name); }") # if node is of enum type then return enum value # TODO: hardcoded Names @@ -280,7 +280,6 @@ def definitions(self): if child.is_vector: # TODO : remove this with C++11 style self.writer.add_line("for(auto& item : this->" + child.varname + ") {") - #self.writer.add_line(" iter != this->" + child.varname + "->end(); iter++) {") self.writer.add_line(" item->accept(v);") self.writer.add_line("}") elif child.optional or child.is_statement_block_node(): @@ -295,11 +294,11 @@ def definitions(self): if self.writer.num_buffered_lines(): self.writer.write_line("/* visit method for " + node.class_name + " ast node */") - self.writer.write_line("void " + node.class_name + "::visitChildren(Visitor* v) {", post_gutter=1) + self.writer.write_line("void " + node.class_name + "::visit_children(Visitor* v) {", post_gutter=1) self.writer.flush_buffered_lines() self.writer.write_line("}", pre_gutter=-1, newline=2) else: - self.writer.write_line("void " + node.class_name + "::visitChildren(Visitor* /*v*/) {}") + self.writer.write_line("void " + node.class_name + "::visit_children(Visitor* /*v*/) {}") if members: # TODO : constructor definition : remove this with C++11 style diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 423748c9d5..12d89726be 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -459,7 +459,7 @@ # # Diffusion statement: # -# Statement:LDifuse("LONGITUDINAL_DIFFUSION") => ?Name<name>(":,") +# Statement:LonDifuse("LONGITUDINAL_DIFFUSION") => ?Name<name>(":,") # Expression<expression> # *Name<names>("{:}: ") # Kinetic block: @@ -1422,7 +1422,7 @@ prefix: {value: "{"} suffix: {value: "}"} separator: " " - - LDifuse: + - LonDifuse: nmodl: LONGITUDINAL_DIFFUSION members: - name: diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index 14f6ad65bc..a31b97f930 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -6,10 +6,6 @@ def camel_case_to_underscore(name): typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) return typename -# convert string of the form "AabcDef" to "ABC_DEF" -def node_ast_type(name): - return camel_case_to_underscore(name).upper() - # convert string of the form "AabcDef" to "abc_def" -def node_property_type(name): +def to_snake_case(name): return camel_case_to_underscore(name).lower() \ No newline at end of file diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index fbedadb7da..6ee969d0a9 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -15,7 +15,7 @@ def public_declaration(self): self.writer.write_line("public:", post_gutter=1) for node in self.nodes: - line = "virtual void visit" + node.class_name + "(" + node.class_name + "* node) = 0;" + line = "virtual void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) = 0;" self.writer.write_line(line) self.writer.decrease_gutter() @@ -42,7 +42,7 @@ def public_declaration(self): self.writer.write_line("public:", post_gutter=1) for node in self.nodes: - line = "virtual void visit" + node.class_name + "(" + node.class_name + "* node) override;" + line = "virtual void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" self.writer.write_line(line) self.writer.decrease_gutter() @@ -56,9 +56,9 @@ def headers(self): def definitions(self): for node in self.nodes: - line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" + line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" self.writer.write_line(line, post_gutter=1) - self.writer.write_line("node->visitChildren(this);", post_gutter=-1) + self.writer.write_line("node->visit_children(this);", post_gutter=-1) self.writer.write_line("}", newline=2) @@ -100,7 +100,7 @@ def public_declaration(self): self.writer.write_line(line, newline=2) for node in self.nodes: - line = "void visit" + node.class_name + "(" + node.class_name + "* node) override;" + line = "void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" self.writer.write_line(line) self.writer.decrease_gutter() @@ -115,32 +115,32 @@ def headers(self): def definitions(self): for node in self.nodes: - line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" + line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" self.writer.write_line(line, post_gutter=1) if node.has_children(): - self.writer.write_line("printer->pushBlock(node->getTypeName());") - self.writer.write_line("node->visitChildren(this);") + self.writer.write_line("printer->push_block(node->get_type_name());") + self.writer.write_line("node->visit_children(this);") if node.is_data_type_node(): if node.class_name == "Integer": self.writer.write_line("if(!node->macroname) {", post_gutter=1) self.writer.write_line("std::stringstream ss;") self.writer.write_line("ss << node->eval();") - self.writer.write_line("printer->addNode(ss.str());", post_gutter=-1) + self.writer.write_line("printer->add_node(ss.str());", post_gutter=-1) self.writer.write_line("}") else: self.writer.write_line("std::stringstream ss;") self.writer.write_line("ss << node->eval();") - self.writer.write_line("printer->addNode(ss.str());") + self.writer.write_line("printer->add_node(ss.str());") - self.writer.write_line("printer->popBlock();") + self.writer.write_line("printer->pop_block();") if node.class_name == "Program": self.writer.write_line("flush();") else: self.writer.write_line("(void)node;") - self.writer.write_line("printer->addNode(\"" + node.class_name + "\");") + self.writer.write_line("printer->add_node(\"" + node.class_name + "\");") self.writer.write_line("}", pre_gutter=-1, newline=2) @@ -185,25 +185,25 @@ def public_declaration(self): # helper function for creating symbol for variables self.writer.write_line("template<typename T>") - self.writer.write_line("void setupSymbol(T* node, SymbolInfo property, int order = 0);", newline=2) + self.writer.write_line("void setup_symbol(T* node, SymbolInfo property, int order = 0);", newline=2) # helper function for creating symbol table for blocks # without name (e.g. parameter, unit, breakpoint) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, std::string name, bool is_global);" + line = "void setup_symbol_table(T *node, std::string name, bool is_global);" self.writer.write_line(line) # helper function for creating symbol table for blocks # with name (e.g. procedure, function, derivative) self.writer.write_line("template<typename T>") - line = "void setupSymbolTable(T *node, std::string name, SymbolInfo property, bool is_global);" + line = "void setup_symbol_table(T *node, std::string name, SymbolInfo property, bool is_global);" self.writer.write_line(line, newline=2) # we have to override visitor methods for the nodes # which goes into symbol table for node in self.nodes: if node.is_symtab_method_required(): - line = "void visit" + node.class_name + "(" + node.class_name + "* node) override;" + line = "void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" self.writer.write_line(line) self.writer.decrease_gutter() @@ -227,27 +227,27 @@ def definitions(self): for node in self.nodes: if node.is_symtab_method_required(): - line = "void " + self.classname + "::visit" + node.class_name + "(" + node.class_name + "* node) {" + line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" self.writer.write_line(line, post_gutter=1) - type_name = node_property_type(node.class_name) + type_name = to_snake_case(node.class_name) property_name = "symtab::details::NmodlInfo::" + type_name if node.is_symbol_var_node() or node.is_prime_node(): - is_prime = ", node->getOrder()" if node.is_prime_node() else ""; - self.writer.write_line("setupSymbol(node, " + property_name + is_prime + ");") + is_prime = ", node->get_order()" if node.is_prime_node() else ""; + self.writer.write_line("setup_symbol(node, " + property_name + is_prime + ");") else: """ setupBlock has node*, properties, global_block""" if node.is_symbol_block_node(): - fun_call = "setupSymbolTable(node, node->getName(), " + property_name + ", false);" + fun_call = "setup_symbol_table(node, node->get_name(), " + property_name + ", false);" elif node.is_program_node() or node.is_global_block_node(): - fun_call = "setupSymbolTable(node, node->getTypeName(), true);" + fun_call = "setup_symbol_table(node, node->get_type_name(), true);" else: """this is for nodes which has parent class as Block node""" - fun_call = "setupSymbolTable(node, node->getTypeName(), false);" + fun_call = "setup_symbol_table(node, node->get_type_name(), false);" self.writer.write_line(fun_call) self.writer.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main.cpp index e1834f3adc..45328eed60 100644 --- a/src/nmodl/lexer/main.cpp +++ b/src/nmodl/lexer/main.cpp @@ -65,7 +65,7 @@ int main(int argc, char* argv[]) { case Token::DEL: case Token::DEL2: { auto value = sym.value.as<ast::name_ptr>(); - std::cout << *(value->getToken()) << std::endl; + std::cout << *(value->get_token()) << std::endl; delete value; break; } @@ -73,7 +73,7 @@ int main(int argc, char* argv[]) { /// token with prime ast class case Token::PRIME: { auto value = sym.value.as<ast::primename_ptr>(); - std::cout << *(value->getToken()) << std::endl; + std::cout << *(value->get_token()) << std::endl; delete value; break; } @@ -81,7 +81,7 @@ int main(int argc, char* argv[]) { /// token with integer ast class case Token::INTEGER: { auto value = sym.value.as<ast::integer_ptr>(); - std::cout << *(value->getToken()) << std::endl; + std::cout << *(value->get_token()) << std::endl; delete value; break; } @@ -89,7 +89,7 @@ int main(int argc, char* argv[]) { /// token with double/float ast class case Token::REAL: { auto value = sym.value.as<ast::double_ptr>(); - std::cout << *(value->getToken()) << std::endl; + std::cout << *(value->get_token()) << std::endl; delete value; break; } @@ -97,7 +97,7 @@ int main(int argc, char* argv[]) { /// token with string ast class case Token::STRING: { auto value = sym.value.as<ast::string_ptr>(); - std::cout << *(value->getToken()) << std::endl; + std::cout << *(value->get_token()) << std::endl; delete value; break; } diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index aa128f927f..911111fafe 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -163,7 +163,7 @@ ELSE { auto type = token_type(yytext); ModToken tok(yytext, type, loc); auto value = new ast::Name( new ast::String(yytext) ); - value->setToken(tok); + value->set_token(tok); switch (static_cast<int>(type)) { /** Tokens requiring name_ptr as value */ @@ -367,7 +367,7 @@ ELSE { if (driver.is_verbose()) { if(str.length()) { - stringutils::trimnewline(str); + stringutils::trim_newline(str); std::cout << "LINE "<< yylineno << ": " << str << std::endl; } else { std::cout << "LINE " << yylineno << ": " << std::endl; @@ -440,7 +440,7 @@ ELSE { /** For title return string without new line character */ loc.lines(1); std::string str(yytext); - stringutils::trimnewline(str); + stringutils::trim_newline(str); BEGIN(INITIAL); return nmodl::Parser::make_LINE_PART(str, loc); } @@ -500,7 +500,7 @@ void nmodl::Lexer::scan_unit() { ModToken tok(str, Token::UNITS, loc); last_unit = new ast::String(str); - last_unit->setToken(tok); + last_unit->set_token(tok); } /** return last scanned unit, it shouln't be null pointer */ diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index cdc5dcc51e..d6dbd1777c 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -12,7 +12,7 @@ namespace nmodl { SymbolType double_symbol(double value, PositionType& pos) { ModToken token(std::to_string(value), Token::REAL, pos); auto floatvalue = new ast::Double(value); - floatvalue->setToken(token); + floatvalue->set_token(token); return Parser::make_REAL(floatvalue, pos); } @@ -24,11 +24,11 @@ namespace nmodl { if (text != nullptr) { macro = new ast::Name(new ast::String(text)); - macro->setToken(token); + macro->set_token(token); } auto intvalue = new ast::Integer(value, macro); - intvalue->setToken(token); + intvalue->set_token(token); return Parser::make_INTEGER(intvalue, pos); } @@ -40,7 +40,7 @@ namespace nmodl { SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type) { ModToken token(text, type, pos); auto value = new ast::Name(new ast::String(text)); - value->setToken(token); + value->set_token(token); return Parser::make_NAME(value, pos); } @@ -55,7 +55,7 @@ namespace nmodl { auto prime_name = new ast::String(text); auto prime_order = new ast::Integer(order, nullptr); auto value = new ast::PrimeName(prime_name, prime_order); - value->setToken(token); + value->set_token(token); return Parser::make_PRIME(value, pos); } @@ -63,7 +63,7 @@ namespace nmodl { SymbolType string_symbol(const std::string& text, PositionType& pos) { ModToken token(text, Token::STRING, pos); auto value = new ast::String(text); - value->setToken(token); + value->set_token(token); return Parser::make_STRING(value, pos); } diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index f7a16713fd..b68519e8b3 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -288,7 +288,7 @@ %type <ast::conserve_ptr> conserve %type <ast::expression_ptr> react %type <ast::compartment_ptr> compart -%type <ast::ldifuse_ptr> ldifus +%type <ast::londifuse_ptr> ldifus %type <ast::name_list> namelist %type <ast::unitblock_ptr> unitblk %type <ast::expression_list> unitbody @@ -472,7 +472,7 @@ model : MODEL LINE_PART define1 : DEFINE1 NAME INTEGER { $$ = new ast::Define($2, $3); - driver.add_defined_var($2->getTypeName(), $3->eval()); + driver.add_defined_var($2->get_type_name(), $3->eval()); } | DEFINE1 error { @@ -816,13 +816,13 @@ destructblk : DESTRUCTOR stmtlist "}" stmtlist : "{" stmtlist1 { $$ = new ast::StatementBlock($2); - $$->setToken($1); + $$->set_token($1); } | "{" locallist stmtlist1 { $3.insert($3.begin(), std::shared_ptr<ast::LocalListStatement>($2)); $$ = new ast::StatementBlock($3); - $$->setToken($1); + $$->set_token($1); } ; @@ -1261,7 +1261,7 @@ optelse : { $$ = nullptr; } derivblk : DERIVATIVE NAME stmtlist "}" { $$ = new ast::DerivativeBlock($2, $3); - $$->setToken($1); + $$->set_token($1); } ; @@ -1269,7 +1269,7 @@ derivblk : DERIVATIVE NAME stmtlist "}" linblk : LINEAR NAME solvefor stmtlist "}" { $$ = new ast::LinearBlock($2, $3, $4); - $$->setToken($1); + $$->set_token($1); } ; @@ -1277,7 +1277,7 @@ linblk : LINEAR NAME solvefor stmtlist "}" nonlinblk : NONLINEAR NAME solvefor stmtlist "}" { $$ = new ast::NonLinearBlock($2, $3, $4); - $$->setToken($1); + $$->set_token($1); } ; @@ -1285,7 +1285,7 @@ nonlinblk : NONLINEAR NAME solvefor stmtlist "}" discretblk : DISCRETE NAME stmtlist "}" { $$ = new ast::DiscreteBlock($2, $3); - $$->setToken($1); + $$->set_token($1); } ; @@ -1293,7 +1293,7 @@ discretblk : DISCRETE NAME stmtlist "}" partialblk : PARTIAL NAME stmtlist "}" { $$ = new ast::PartialBlock($2, $3); - $$->setToken($1); + $$->set_token($1); } | PARTIAL error { @@ -1331,7 +1331,7 @@ firstlast : FIRST functableblk : FUNCTION_TABLE NAME "(" arglist ")" units { $$ = new ast::FunctionTableBlock($2, $4, $6); - $$->setToken($1); + $$->set_token($1); } ; @@ -1339,7 +1339,7 @@ functableblk : FUNCTION_TABLE NAME "(" arglist ")" units funcblk : FUNCTION1 NAME "(" arglist ")" units stmtlist "}" { $$ = new ast::FunctionBlock($2, $4, $6, $7); - $$->setToken($1); + $$->set_token($1); } ; @@ -1366,7 +1366,7 @@ arglist1 : name units procedblk : PROCEDURE NAME "(" arglist ")" units stmtlist "}" { - $$ = new ast::ProcedureBlock($2, $4, $6, $7); $$->setToken($1); + $$ = new ast::ProcedureBlock($2, $4, $6, $7); $$->set_token($1); } ; @@ -1599,11 +1599,11 @@ compart : COMPARTMENT NAME "," expr "{" namelist "}" ldifus : LONGDIFUS NAME "," expr "{" namelist "}" { - $$ = new ast::LDifuse($2, $4, $6); + $$ = new ast::LonDifuse($2, $4, $6); } | LONGDIFUS expr "{" namelist "}" { - $$ = new ast::LDifuse(NULL, $2, $4); + $$ = new ast::LonDifuse(NULL, $2, $4); } ; @@ -1624,7 +1624,7 @@ namelist : NAME kineticblk : KINETIC NAME solvefor stmtlist "}" { $$ = new ast::KineticBlock($2, $3, $4); - $$->setToken($1); + $$->set_token($1); } ; @@ -1774,17 +1774,17 @@ unitdef : unit "=" unit factordef : NAME "=" real unit { $$ = new ast::FactorDef($1, $3, $4, NULL, NULL); - $$->setToken(*($1->getToken())); + $$->set_token(*($1->get_token())); } | NAME "=" unit unit { $$ = new ast::FactorDef($1, NULL, $3, NULL, $4); - $$->setToken(*($1->getToken())); + $$->set_token(*($1->get_token())); } | NAME "=" unit "-" GT unit { $$ = new ast::FactorDef($1, NULL, $3, new ast::Boolean(1), $6); - $$->setToken(*($1->getToken())); + $$->set_token(*($1->get_token())); } | error { @@ -1849,7 +1849,7 @@ dependlst : { $$ = ast::NameVector(); } neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE { auto block = new ast::StatementBlock($3); - block->setToken($2); + block->set_token($2); $$ = new ast::NeuronBlock(block); } ; diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 8f86e68a22..80fb1b1e8c 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -65,7 +65,7 @@ namespace nmodl { /// root of the ast std::shared_ptr<ast::Program> astRoot = nullptr; - Driver(){}; + Driver() = default; Driver(bool strace, bool ptrace); void error(const std::string& m, const class location& l); diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index ee51da940f..e06a958cf3 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -18,9 +18,9 @@ JSONPrinter::JSONPrinter(const std::string& filename) { } /// Add node to json (typically basic type) -void JSONPrinter::addNode(std::string value, const std::string& name) { +void JSONPrinter::add_node(std::string value, const std::string& name) { if (!block) { - auto text = "Block not initialized (pushBlock missing?)"; + auto text = "Block not initialized (push_block missing?)"; throw std::logic_error(text); } @@ -31,7 +31,7 @@ void JSONPrinter::addNode(std::string value, const std::string& name) { /// Add new json object (typically start of new block) /// name here is type of new block encountered -void JSONPrinter::pushBlock(const std::string& name) { +void JSONPrinter::push_block(const std::string& name) { if (block) { stack.push(block); } @@ -42,7 +42,7 @@ void JSONPrinter::pushBlock(const std::string& name) { } /// We finished processing a block, add processed block to it's parent block -void JSONPrinter::popBlock() { +void JSONPrinter::pop_block() { if (!stack.empty()) { auto current = block; block = stack.top(); diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index f456e990f5..0150507077 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -60,9 +60,9 @@ class JSONPrinter { flush(); } - void pushBlock(const std::string& name); - void addNode(std::string value, const std::string& name = "value"); - void popBlock(); + void push_block(const std::string& name); + void add_node(std::string value, const std::string& name = "value"); + void pop_block(); void flush(); /// print json in compact mode diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 85ec592dc3..2c0d6dae8b 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -148,7 +148,7 @@ namespace symtab { auto node = symbol->get_node(); std::string type = "UNKNOWN"; if (node != nullptr) { - type = node->getTypeName(); + type = node->get_type_name(); } /** For global symbol tables, same variable can appear in multiple @@ -196,8 +196,8 @@ namespace symtab { */ std::string ModelSymbolTable::get_unique_name(std::string name, AST* node) { static int block_counter = 0; - if (node->isStatementBlock() || node->isSolveBlock() || node->isBeforeBlock() || - node->isAfterBlock()) { + if (node->is_statement_block() || node->is_solve_block() || node->is_before_block() || + node->is_after_block()) { name += std::to_string(block_counter++); } return name; @@ -227,7 +227,7 @@ namespace symtab { } /// statement block within global scope is part of global block itself - if (symtab && node->isStatementBlock() && parent_symtab->under_global_scope()) { + if (symtab && node->is_statement_block() && parent_symtab->under_global_scope()) { parent_symtab = symtab; return parent_symtab; } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index a985e47376..f62555ff49 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -97,14 +97,14 @@ namespace symtab { } std::string type() const { - return node->getTypeName(); + return node->get_type_name(); } std::string title(); /// todo: set token for every block from parser std::string position() const { - auto token = node->getToken(); + auto token = node->get_token(); if (token) return token->position(); else diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index a5e429fa6b..3cdabf82c0 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -39,7 +39,7 @@ namespace stringutils { } /// Remove leading newline for the string read by grammar - static inline std::string& trimnewline(std::string& s) { + static inline std::string& trim_newline(std::string& s) { remove_character(s, '\n'); return s; } diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 7aee25819f..99634a9c10 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -51,7 +51,7 @@ int main(int argc, const char* argv[]) { { /// run basic AST visitor AstVisitor v; - v.visitProgram(ast.get()); + v.visit_program(ast.get()); std::cout << "----AST VISITOR FINISHED----" << std::endl; } @@ -60,7 +60,7 @@ int main(int argc, const char* argv[]) { /// run basic Verbatim visitor /// constructor takes true/false argument for printing blocks VerbatimVisitor v; - v.visitProgram(ast.get()); + v.visit_program(ast.get()); std::cout << "----VERBATIM VISITOR FINISHED----" << std::endl; } @@ -72,7 +72,7 @@ int main(int argc, const char* argv[]) { /// to get compact json we can set compact mode /// v.compact_json(true); - v.visitProgram(ast.get()); + v.visit_program(ast.get()); std::cout << "----JSON VISITOR FINISHED----" << std::endl; @@ -88,7 +88,7 @@ int main(int argc, const char* argv[]) { std::stringstream ss1; SymtabVisitor v(&symtab, ss1); - v.visitProgram(ast.get()); + v.visit_program(ast.get()); // std::cout << ss1.str(); @@ -101,9 +101,9 @@ int main(int argc, const char* argv[]) { { PerfVisitor v(channel_name + ".perf.json"); - v.visitProgram(ast.get()); + v.visit_program(ast.get()); - auto symtab = ast->getSymbolTable(); + auto symtab = ast->get_symbol_table(); std::stringstream ss; symtab->print(ss, 0); std::cout << ss.str(); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 9edeb2e1f8..f86dbdeb81 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -8,7 +8,7 @@ PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter( } /// count math operations from all binary expressions -void PerfVisitor::visitBinaryExpression(BinaryExpression* node) { +void PerfVisitor::visit_binary_expression(BinaryExpression* node) { bool assign_op = false; if (start_measurement) { @@ -96,7 +96,7 @@ void PerfVisitor::add_perf_to_printer(PerfStat& perf) { assert(keys.size() == values.size()); for (size_t i = 0; i < keys.size(); i++) { - printer->addNode(values[i], keys[i]); + printer->add_node(values[i], keys[i]); } } @@ -108,7 +108,7 @@ void PerfVisitor::add_perf_to_printer(PerfStat& perf) { void PerfVisitor::measure_performance(AST* node) { start_measurement = true; - node->visitChildren(this); + node->visit_children(this); PerfStat perf; while (!children_blocks_perf.empty()) { @@ -116,13 +116,13 @@ void PerfVisitor::measure_performance(AST* node) { children_blocks_perf.pop(); } - auto symtab = node->getSymbolTable(); + auto symtab = node->get_symbol_table(); if (symtab == nullptr) { throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); } if (printer) { - printer->pushBlock(symtab->name()); + printer->push_block(symtab->name()); } perf.title = "Performance Statistics of " + symtab->name(); @@ -130,18 +130,18 @@ void PerfVisitor::measure_performance(AST* node) { if (printer) { add_perf_to_printer(perf); - printer->popBlock(); + printer->pop_block(); } start_measurement = false; } /// count function calls and "most useful" or "commonly used" math functions -void PerfVisitor::visitFunctionCall(FunctionCall* node) { +void PerfVisitor::visit_function_call(FunctionCall* node) { under_function_call = true; if (start_measurement) { - auto name = node->name->getName(); + auto name = node->name->get_name(); if (name.compare("exp") == 0) { current_block_perf.exp_count++; } else if (name.compare("log") == 0) { @@ -149,7 +149,7 @@ void PerfVisitor::visitFunctionCall(FunctionCall* node) { } else if (name.compare("pow") == 0) { current_block_perf.pow_count++; } - node->visitChildren(this); + node->visit_children(this); current_block_perf.func_call_count++; } @@ -157,44 +157,44 @@ void PerfVisitor::visitFunctionCall(FunctionCall* node) { } /// every variable used is of type name, update counters -void PerfVisitor::visitName(Name* node) { - update_memory_ops(node->getName()); - node->visitChildren(this); +void PerfVisitor::visit_name(Name* node) { + update_memory_ops(node->get_name()); + node->visit_children(this); } /// prime name derived from identifier and hence need to be handled here -void PerfVisitor::visitPrimeName(PrimeName* node) { - update_memory_ops(node->getName()); - node->visitChildren(this); +void PerfVisitor::visit_prime_name(PrimeName* node) { + update_memory_ops(node->get_name()); + node->visit_children(this); } -void PerfVisitor::visitIfStatement(IfStatement* /*node*/) { +void PerfVisitor::visit_if_statement(IfStatement* /*node*/) { if (start_measurement) { current_block_perf.if_count++; } } -void PerfVisitor::visitElseIfStatement(ElseIfStatement* /*node*/) { +void PerfVisitor::visit_else_if_statement(ElseIfStatement* /*node*/) { if (start_measurement) { current_block_perf.elif_count++; } } -void PerfVisitor::visitProgram(Program* node) { +void PerfVisitor::visit_program(Program* node) { if (printer) { - printer->pushBlock("NMODL"); + printer->push_block("NMODL"); } - node->visitChildren(this); + node->visit_children(this); std::string title = "Total Performance Statistics"; total_perf.title = title; total_perf.print(stream); if (printer) { - printer->pushBlock("total"); + printer->push_block("total"); add_perf_to_printer(total_perf); - printer->popBlock(); - printer->popBlock(); + printer->pop_block(); + printer->pop_block(); } } @@ -202,11 +202,11 @@ void PerfVisitor::visitProgram(Program* node) { * blocks like net receive has nested initial blocks. Hence need * to maintain separate stack. */ -void PerfVisitor::visitStatementBlock(StatementBlock* node) { +void PerfVisitor::visit_statement_block(StatementBlock* node) { /// starting new block, store current state blocks_perf.push(current_block_perf); - current_symtab = node->getSymbolTable(); + current_symtab = node->get_symbol_table(); if (current_symtab == nullptr) { throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); @@ -215,7 +215,7 @@ void PerfVisitor::visitStatementBlock(StatementBlock* node) { /// new block perf starts from zero current_block_perf = PerfStat(); - node->visitChildren(this); + node->visit_children(this); /// add performance of all visited children total_perf = total_perf + current_block_perf; @@ -229,13 +229,13 @@ void PerfVisitor::visitStatementBlock(StatementBlock* node) { /// solve is not a statement but could have associated block /// and hence could/should not be skipped completely -void PerfVisitor::visitSolveBlock(SolveBlock* node) { +void PerfVisitor::visit_solve_block(SolveBlock* node) { under_solve_block = true; - node->visitChildren(this); + node->visit_children(this); under_solve_block = false; } -void PerfVisitor::visitUnaryExpression(UnaryExpression* node) { +void PerfVisitor::visit_unary_expression(UnaryExpression* node) { if (start_measurement) { switch (node->op.value) { case UOP_NEGATION: @@ -250,7 +250,7 @@ void PerfVisitor::visitUnaryExpression(UnaryExpression* node) { throw std::logic_error("Unary operator not handled in perf visitor"); } } - node->visitChildren(this); + node->visit_children(this); } /** Certain statements / symbols needs extra check while measuring diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index f24d1727fe..a2f55abddb 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -93,126 +93,126 @@ class PerfVisitor : public AstVisitor { return total_perf; } - void visitBinaryExpression(BinaryExpression* node) override; + void visit_binary_expression(BinaryExpression* node) override; - void visitFunctionCall(FunctionCall* node) override; + void visit_function_call(FunctionCall* node) override; - void visitName(Name* node) override; + void visit_name(Name* node) override; - void visitPrimeName(PrimeName* node) override; + void visit_prime_name(PrimeName* node) override; - void visitSolveBlock(SolveBlock* node) override; + void visit_solve_block(SolveBlock* node) override; - void visitStatementBlock(StatementBlock* node) override; + void visit_statement_block(StatementBlock* node) override; - void visitUnaryExpression(UnaryExpression* node) override; + void visit_unary_expression(UnaryExpression* node) override; - void visitIfStatement(IfStatement* node) override; + void visit_if_statement(IfStatement* node) override; - void visitElseIfStatement(ElseIfStatement* node) override; + void visit_else_if_statement(ElseIfStatement* node) override; - void visitProgram(Program* node) override; + void visit_program(Program* node) override; - void visitPlotBlock(PlotBlock* node) override { + void visit_plot_block(PlotBlock* node) override { measure_performance(node); } - void visitInitialBlock(InitialBlock* node) override { + void visit_initial_block(InitialBlock* node) override { measure_performance(node); } - void visitConstructorBlock(ConstructorBlock* node) override { + void visit_constructor_block(ConstructorBlock* node) override { measure_performance(node); } - void visitDestructorBlock(DestructorBlock* node) override { + void visit_destructor_block(DestructorBlock* node) override { measure_performance(node); } - void visitDerivativeBlock(DerivativeBlock* node) override { + void visit_derivative_block(DerivativeBlock* node) override { measure_performance(node); } - void visitLinearBlock(LinearBlock* node) override { + void visit_linear_block(LinearBlock* node) override { measure_performance(node); } - void visitNonLinearBlock(NonLinearBlock* node) override { + void visit_non_linear_block(NonLinearBlock* node) override { measure_performance(node); } - void visitDiscreteBlock(DiscreteBlock* node) override { + void visit_discrete_block(DiscreteBlock* node) override { measure_performance(node); } - void visitPartialBlock(PartialBlock* node) override { + void visit_partial_block(PartialBlock* node) override { measure_performance(node); } - void visitFunctionTableBlock(FunctionTableBlock* node) override { + void visit_function_table_block(FunctionTableBlock* node) override { measure_performance(node); } - void visitFunctionBlock(FunctionBlock* node) override { + void visit_function_block(FunctionBlock* node) override { measure_performance(node); } - void visitProcedureBlock(ProcedureBlock* node) override { + void visit_procedure_block(ProcedureBlock* node) override { measure_performance(node); } - void visitNetReceiveBlock(NetReceiveBlock* node) override { + void visit_net_receive_block(NetReceiveBlock* node) override { measure_performance(node); } - void visitBreakpointBlock(BreakpointBlock* node) override { + void visit_breakpoint_block(BreakpointBlock* node) override { measure_performance(node); } - void visitTerminalBlock(TerminalBlock* node) override { + void visit_terminal_block(TerminalBlock* node) override { measure_performance(node); } - void visitBeforeBlock(BeforeBlock* node) override { + void visit_before_block(BeforeBlock* node) override { measure_performance(node); } - void visitAfterBlock(AfterBlock* node) override { + void visit_after_block(AfterBlock* node) override { measure_performance(node); } - void visitBABlock(BABlock* node) override { + void visit_ba_block(BABlock* node) override { measure_performance(node); } - void visitForNetcon(ForNetcon* node) override { + void visit_for_netcon(ForNetcon* node) override { measure_performance(node); } - void visitKineticBlock(KineticBlock* node) override { + void visit_kinetic_block(KineticBlock* node) override { measure_performance(node); } - void visitMatchBlock(MatchBlock* node) override { + void visit_match_block(MatchBlock* node) override { measure_performance(node); } /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations - void visitConductanceHint(ConductanceHint* /*node*/) override { + void visit_conductance_hint(ConductanceHint* /*node*/) override { } - void visitLocalListStatement(LocalListStatement* /*node*/) override { + void visit_local_list_statement(LocalListStatement* /*node*/) override { } - void visitSuffix(Suffix* /*node*/) override { + void visit_suffix(Suffix* /*node*/) override { } - void visitUseion(Useion* /*node*/) override { + void visit_useion(Useion* /*node*/) override { } - void visitValence(Valence* /*node*/) override { + void visit_valence(Valence* /*node*/) override { } void print(std::stringstream& ss) { diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index be953ff569..6ffc13442e 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -7,14 +7,14 @@ /// helper function to setup/insert symbol into symbol table /// for the ast nodes which are of variable types template <typename T> -void SymtabVisitor::setupSymbol(T* node, SymbolInfo property, int order) { +void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { std::shared_ptr<Symbol> symbol; - auto token = node->getToken(); + auto token = node->get_token(); /// if prime variable is already exist in symbol table /// then just update the order if (order) { - symbol = modsymtab->lookup(node->getName()); + symbol = modsymtab->lookup(node->get_name()); if (symbol) { symbol->set_order(order); symbol->add_property(property); @@ -23,39 +23,39 @@ void SymtabVisitor::setupSymbol(T* node, SymbolInfo property, int order) { } /// add new symbol - symbol = std::make_shared<Symbol>(node->getName(), node, *token); + symbol = std::make_shared<Symbol>(node->get_name(), node, *token); symbol->add_property(property); modsymtab->insert(symbol); /// visit childrens, most likely variables are already /// leaf nodes, not necessary to visit - node->visitChildren(this); + node->visit_children(this); } template <typename T> -void SymtabVisitor::setupSymbolTable(T* node, - std::string name, - SymbolInfo property, - bool is_global) { - auto token = node->getToken(); +void SymtabVisitor::setup_symbol_table(T* node, + std::string name, + SymbolInfo property, + bool is_global) { + auto token = node->get_token(); auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); modsymtab->insert(symbol); - setupSymbolTable(node, name, is_global); + setup_symbol_table(node, name, is_global); } template <typename T> -void SymtabVisitor::setupSymbolTable(T* node, std::string name, bool is_global) { +void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global) { /// entering into new nmodl block auto symtab = modsymtab->enter_scope(name, node, is_global); /// not required at the moment but every node /// has pointer to associated symbol table - node->setSymbolTable(symtab); + node->set_symbol_table(symtab); /// when visiting highest level node i.e. Program, we insert /// all global variables to the global symbol table - if (node->isProgram()) { + if (node->is_program()) { ModToken tok(true); auto variables = nmodl::get_external_variables(); for (auto variable : variables) { @@ -72,7 +72,7 @@ void SymtabVisitor::setupSymbolTable(T* node, std::string name, bool is_global) } /// look for all children blocks recursively - node->visitChildren(this); + node->visit_children(this); /// exisiting nmodl block modsymtab->leave_scope(); diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 4ac534cd24..a39afd317d 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,6 +1,6 @@ #include "visitors/verbatim_visitor.hpp" -void VerbatimVisitor::visitVerbatim(Verbatim* node) { +void VerbatimVisitor::visit_verbatim(Verbatim* node) { std::string block; if (node->statement) { diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 4eb370c926..cc21331f90 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -33,7 +33,7 @@ class VerbatimVisitor : public AstVisitor { verbose = flag; } - void visitVerbatim(Verbatim* node) override; + void visit_verbatim(Verbatim* node) override; std::vector<std::string> verbatim_blocks() { return blocks; diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 0825819ffb..55a23b9c73 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -28,7 +28,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { { std::stringstream ss; symbol_type("text", value); - ss << *(value->getToken()); + ss << *(value->get_token()); REQUIRE(ss.str().compare(" text at [1.1-4] type 356") == 0); delete value; } @@ -36,7 +36,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { { std::stringstream ss; symbol_type(" some_text", value); - ss << *(value->getToken()); + ss << *(value->get_token()); REQUIRE(ss.str().compare(" some_text at [1.3-11] type 356") == 0); delete value; } @@ -48,9 +48,9 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { { std::stringstream ss; symbol_type("h'' = ", value); - ss << *(value->getToken()); + ss << *(value->get_token()); REQUIRE(ss.str().compare(" h'' at [1.1-3] type 362") == 0); - REQUIRE(value->getOrder() == 2); + REQUIRE(value->get_order() == 2); delete value; } } diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 2fb653939d..efa4e13977 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -11,9 +11,9 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { JSONPrinter p(ss); p.compact_json(true); - p.pushBlock("A"); - p.addNode("B"); - p.popBlock(); + p.push_block("A"); + p.add_node("B"); + p.pop_block(); p.flush(); auto result = R"({"A":[{"value":"B"}]})"; @@ -25,13 +25,13 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { JSONPrinter p(ss); p.compact_json(true); - p.pushBlock("A"); - p.addNode("B"); - p.addNode("C"); - p.pushBlock("D"); - p.addNode("E"); - p.popBlock(); - p.popBlock(); + p.push_block("A"); + p.add_node("B"); + p.add_node("C"); + p.push_block("D"); + p.add_node("E"); + p.pop_block(); + p.pop_block(); p.flush(); auto result = R"({"A":[{"value":"B"},{"value":"C"},{"D":[{"value":"E"}]}]})"; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 75edd03e5e..82b7556ce5 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -21,7 +21,7 @@ std::vector<std::string> run_verbatim_visitor(const std::string& text) { auto ast = driver.ast(); VerbatimVisitor v; - v.visitProgram(ast.get()); + v.visit_program(ast.get()); return v.verbatim_blocks(); } @@ -59,7 +59,7 @@ std::string run_json_visitor(const std::string& text, bool compact = false) { /// if compact is true then we get compact json output v.compact_json(compact); - v.visitProgram(ast.get()); + v.visit_program(ast.get()); return ss.str(); } @@ -142,7 +142,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { WHEN("Symbol table generator pass runs") { ModelSymbolTable symtab; SymtabVisitor v(&symtab); - v.visitProgram(ast.get()); + v.visit_program(ast.get()); using namespace symtab::details; @@ -168,7 +168,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { WHEN("Perf visitor pass runs after symtab visitor") { PerfVisitor v; - v.visitProgram(ast.get()); + v.visit_program(ast.get()); auto result = v.get_total_perfstat(); THEN("Performance counters are updated") { @@ -190,7 +190,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { WHEN("Perf visitor pass runs before symtab visitor") { PerfVisitor v; THEN("exception is thrown") { - REQUIRE_THROWS_WITH(v.visitProgram(ast.get()), Catch::Contains("table not setup")); + REQUIRE_THROWS_WITH(v.visit_program(ast.get()), Catch::Contains("table not setup")); } } } From 7416a93e2edf0a2dccd22a204c13e1b3148f8ec9 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 30 Dec 2017 19:07:17 +0100 Subject: [PATCH 034/871] Added wite_line and add_line methods to printer class. We use now self.write_line instead of self.writer.write_line. Change-Id: I3f0293068c91ce66554d2bd13ec5e510aeefeebf NMODL Repo SHA: BlueBrain/nmodl@44ee98240202f0e47b1d2d66eaa31df45eb814db --- src/nmodl/language/ast_printer.py | 204 ++++++++++++------------- src/nmodl/language/printer.py | 20 ++- src/nmodl/language/visitors_printer.py | 144 ++++++++--------- 3 files changed, 187 insertions(+), 181 deletions(-) diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 80ec929ff2..ffbdcff2d0 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -8,42 +8,42 @@ class AstDeclarationPrinter(DeclarationPrinter): def ast_types(self): """ print ast type for every ast node """ - self.writer.write_line("enum class Type {", post_gutter=1) + self.write_line("enum class Type {", post_gutter=1) for node in self.nodes[:-1]: name = to_snake_case(node.class_name).upper() + "," - self.writer.write_line(name) + self.write_line(name) name = to_snake_case(self.nodes[-1].class_name).upper() - self.writer.write_line(name) - self.writer.write_line("};", pre_gutter=-1) + self.write_line(name) + self.write_line("};", pre_gutter=-1) def headers(self): - self.writer.write_line("#include <iostream>") - self.writer.write_line("#include <memory>") - self.writer.write_line("#include <string>") - self.writer.write_line("#include <vector>", newline=2) + self.write_line("#include <iostream>") + self.write_line("#include <memory>") + self.write_line("#include <string>") + self.write_line("#include <vector>", newline=2) - self.writer.write_line('#include "ast/ast_utils.hpp"') - self.writer.write_line('#include "lexer/modtoken.hpp"') - self.writer.write_line('#include "utils/common_utils.hpp"', newline=2) + self.write_line('#include "ast/ast_utils.hpp"') + self.write_line('#include "lexer/modtoken.hpp"') + self.write_line('#include "utils/common_utils.hpp"', newline=2) def class_comment(self): - self.writer.write_line("/* all classes representing Abstract Syntax Tree (AST) nodes */") + self.write_line("/* all classes representing Abstract Syntax Tree (AST) nodes */") def forward_declarations(self): - self.writer.write_line("/* forward declarations of AST classes */") + self.write_line("/* forward declarations of AST classes */") for node in self.nodes: - self.writer.write_line("class " + node.class_name + ";") + self.write_line("class " + node.class_name + ";") - self.writer.write_line() - self.writer.write_line("/* Type for every ast node */") + self.write_line() + self.write_line("/* Type for every ast node */") self.ast_types() - self.writer.write_line() - self.writer.write_line("/* std::vector for convenience */") + self.write_line() + self.write_line("/* std::vector for convenience */") for node in self.nodes: typename = "std::vector<std::shared_ptr<" + node.class_name + ">>" - self.writer.write_line("using " + node.class_name + "Vector = " + typename + ";") + self.write_line("using " + node.class_name + "Vector = " + typename + ";") created_types = [] @@ -72,7 +72,7 @@ def forward_declarations(self): created_types.append(type) for tuple in created_types: - self.writer.write_line("using " + tuple[1] + " = " + tuple[0] + ";") + self.write_line("using " + tuple[1] + " = " + tuple[0] + ";") def ast_classes_declaration(self): @@ -84,10 +84,10 @@ def ast_classes_declaration(self): # TODO: remove visitor related method definition from declaration # to avoid this include - self.writer.write_line() - self.writer.write_line("#include <visitors/visitor.hpp>", newline=2) + self.write_line() + self.write_line("#include <visitors/visitor.hpp>", newline=2) - self.writer.write_line("/* Define all AST nodes */", newline=2) + self.write_line("/* Define all AST nodes */", newline=2) for node in self.nodes: class_decl = "class " + node.class_name + " : public " @@ -106,8 +106,8 @@ def ast_classes_declaration(self): virtual = "" override = " override" - self.writer.write_line(class_decl + " {", post_gutter=1) - self.writer.write_line("public:", post_gutter=1) + self.write_line(class_decl + " {", post_gutter=1) + self.write_line("public:", post_gutter=1) members = [] members_with_types = [] @@ -117,36 +117,36 @@ def ast_classes_declaration(self): members_with_types.append(child.get_typename_as_member() + " " + child.varname) if members: - self.writer.write_line("/* member variables */") + self.write_line("/* member variables */") # todo : need a way to setup location - # self.writer.write_line("void setLocation(nmodl::location loc) {}") + # self.write_line("void setLocation(nmodl::location loc) {}") for member in members_with_types: - self.writer.write_line(member + ";") + self.write_line(member + ";") if node.has_token: - self.writer.write_line("std::shared_ptr<ModToken> token;") + self.write_line("std::shared_ptr<ModToken> token;") if node.is_symtab_needed(): - self.writer.write_line("std::shared_ptr<symtab::SymbolTable> symtab;", newline=2) + self.write_line("std::shared_ptr<symtab::SymbolTable> symtab;", newline=2) if members: - self.writer.write_line("/* constructors */") - self.writer.write_line(node.class_name + "(" + (", ".join(members)) + ");") - self.writer.write_line(node.class_name + "(const " + node.class_name + "& obj);") + self.write_line("/* constructors */") + self.write_line(node.class_name + "(" + (", ".join(members)) + ");") + self.write_line(node.class_name + "(const " + node.class_name + "& obj);") # program node holds statements and blocks and we instantiate it in driver # also other nodes which we use as value type, parsor needs to return them # as a value. And instantiation of those causes error if default constructor not there. if node.is_program_node() or node.is_ptr_excluded_node(): - self.writer.write_line( node.class_name + "() {}", newline=2) + self.write_line( node.class_name + "() {}", newline=2) # Todo : We need virtual destructor otherwise there will be memory leaks. # But we need define which are virtual base classes that needs virtual function. - self.writer.write_line("virtual ~" + node.class_name + "() {}") - self.writer.write_line() + self.write_line("virtual ~" + node.class_name + "() {}") + self.write_line() get_method_added = False @@ -154,9 +154,9 @@ def ast_classes_declaration(self): class_name = child.class_name varname = child.varname if child.add_method: - self.writer.write_line("void add" + class_name + "(" + class_name + " *s) {") - self.writer.write_line(" " + varname + ".push_back(std::shared_ptr<" + class_name + ">(s));") - self.writer.write_line("}") + self.write_line("void add" + class_name + "(" + class_name + " *s) {") + self.write_line(" " + varname + ".push_back(std::shared_ptr<" + class_name + ">(s));") + self.write_line("}") if child.getname_method: # string node should be evaluated and hence eval() method @@ -165,77 +165,77 @@ def ast_classes_declaration(self): else: method = "get_name" - self.writer.write_line("virtual std::string get_name() override {") - self.writer.write_line(" return " + varname + "->" + method + "();") - self.writer.write_line("}") + self.write_line("virtual std::string get_name() override {") + self.write_line(" return " + varname + "->" + method + "();") + self.write_line("}") if not node.has_token: - self.writer.write_line(virtual + "ModToken* get_token() override {") - self.writer.write_line(" return " + varname + "->get_token();") - self.writer.write_line("}") + self.write_line(virtual + "ModToken* get_token() override {") + self.write_line(" return " + varname + "->get_token();") + self.write_line("}") get_method_added = True if node.is_prime_node() and child.varname == ORDER_VAR_NAME: - self.writer.write_line("int get_order() " + " { return " + ORDER_VAR_NAME + "->eval(); }") + self.write_line("int get_order() " + " { return " + ORDER_VAR_NAME + "->eval(); }") # add method to return typename - self.writer.write_line("virtual std::string get_type_name() override { return \"" + node.class_name + "\"; }") + self.write_line("virtual std::string get_type_name() override { return \"" + node.class_name + "\"; }") # all member functions - self.writer.write_line(virtual + "void visit_children (Visitor* v) override;") - self.writer.write_line(virtual + "void accept(Visitor* v) override { v->visit_" + to_snake_case(node.class_name) + "(this); }") + self.write_line(virtual + "void visit_children (Visitor* v) override;") + self.write_line(virtual + "void accept(Visitor* v) override { v->visit_" + to_snake_case(node.class_name) + "(this); }") # TODO: type should declared as enum class typename = to_snake_case(node.class_name).upper() - self.writer.write_line(virtual + "Type get_type() override { return Type::" + typename + "; }") - self.writer.write_line("bool is_" + to_snake_case(node.class_name) + " () override { return true; }") - self.writer.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") + self.write_line(virtual + "Type get_type() override { return Type::" + typename + "; }") + self.write_line("bool is_" + to_snake_case(node.class_name) + " () override { return true; }") + self.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") if node.has_token: - self.writer.write_line(virtual + "ModToken* get_token() " + override + " { return token.get(); }") - self.writer.write_line("void set_token(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") + self.write_line(virtual + "ModToken* get_token() " + override + " { return token.get(); }") + self.write_line("void set_token(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") if node.is_symtab_needed(): - self.writer.write_line("void set_symbol_table(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") - self.writer.write_line("std::shared_ptr<symtab::SymbolTable> get_symbol_table() override " + " { return symtab; }") + self.write_line("void set_symbol_table(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") + self.write_line("std::shared_ptr<symtab::SymbolTable> get_symbol_table() override " + " { return symtab; }") if node.is_number_node(): - self.writer.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") + self.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") if node.is_base_class_number_node(): if node.is_boolean_node(): - self.writer.write_line("void negate() override { value = !value; }") + self.write_line("void negate() override { value = !value; }") else: - self.writer.write_line("void negate() override { value = -value; }") + self.write_line("void negate() override { value = -value; }") if node.is_identifier_node(): - self.writer.write_line(virtual + "void set_name(std::string /*name*/)" + override + " { std::cout << \"ERROR : set_name() not implemented! \"; abort(); }") + self.write_line(virtual + "void set_name(std::string /*name*/)" + override + " { std::cout << \"ERROR : set_name() not implemented! \"; abort(); }") if node.is_name_node(): - self.writer.write_line(virtual + "void set_name(std::string name)" + override + " { value->set(name); }") + self.write_line(virtual + "void set_name(std::string name)" + override + " { value->set(name); }") # if node is of enum type then return enum value # TODO: hardcoded Names if node.is_data_type_node(): data_type = node.get_data_type_name() if node.is_enum_node(): - self.writer.write_line("std::string " + " eval() { return " + data_type + "Names[value]; }") + self.write_line("std::string " + " eval() { return " + data_type + "Names[value]; }") # But if basic data type then eval return their value # TODO: value member is hardcoded else: - self.writer.write_line(data_type + " eval() { return value; }") - self.writer.write_line("void set(" + data_type + " _value) " + " { value = _value; }") + self.write_line(data_type + " eval() { return value; }") + self.write_line("void set(" + data_type + " _value) " + " { value = _value; }") - self.writer.write_line("};", pre_gutter=-2, newline=2) + self.write_line("};", pre_gutter=-2, newline=2) def class_name_declaration(self): - self.writer.write_line("namespace ast {", newline=2, post_gutter=1) + self.write_line("namespace ast {", newline=2, post_gutter=1) self.forward_declarations() self.ast_classes_declaration() def declaration_end(self): - self.writer.write_line(pre_gutter=-1) + self.write_line(pre_gutter=-1) def private_declaration(self): pass @@ -244,17 +244,17 @@ def public_declaration(self): pass def post_declaration(self): - self.writer.write_line("} // namespace ast", pre_gutter=-1) + self.write_line("} // namespace ast", pre_gutter=-1) class AstDefinitionPrinter(DefinitionPrinter): """Prints AST class definitions""" def headers(self): - self.writer.write_line('#include "ast/ast.hpp"', newline=2) + self.write_line('#include "ast/ast.hpp"', newline=2) def definitions(self): - self.writer.write_line("namespace ast {", post_gutter=1) + self.write_line("namespace ast {", post_gutter=1) for node in self.nodes: # first get types of all childrens into members vector @@ -279,69 +279,69 @@ def definitions(self): self.writer.increase_gutter() if child.is_vector: # TODO : remove this with C++11 style - self.writer.add_line("for(auto& item : this->" + child.varname + ") {") - self.writer.add_line(" item->accept(v);") - self.writer.add_line("}") + self.add_line("for(auto& item : this->" + child.varname + ") {") + self.add_line(" item->accept(v);") + self.add_line("}") elif child.optional or child.is_statement_block_node(): - self.writer.add_line("if (this->" + child.varname + ") {") - self.writer.add_line(" this->" + child.varname + "->accept(v);") - self.writer.add_line("}") + self.add_line("if (this->" + child.varname + ") {") + self.add_line(" this->" + child.varname + "->accept(v);") + self.add_line("}") elif not child.is_pointer_node(): - self.writer.add_line(child.varname + ".accept(v);") + self.add_line(child.varname + ".accept(v);") else: - self.writer.add_line(child.varname + "->accept(v);") + self.add_line(child.varname + "->accept(v);") self.writer.decrease_gutter() if self.writer.num_buffered_lines(): - self.writer.write_line("/* visit method for " + node.class_name + " ast node */") - self.writer.write_line("void " + node.class_name + "::visit_children(Visitor* v) {", post_gutter=1) + self.write_line("/* visit method for " + node.class_name + " ast node */") + self.write_line("void " + node.class_name + "::visit_children(Visitor* v) {", post_gutter=1) self.writer.flush_buffered_lines() - self.writer.write_line("}", pre_gutter=-1, newline=2) + self.write_line("}", pre_gutter=-1, newline=2) else: - self.writer.write_line("void " + node.class_name + "::visit_children(Visitor* /*v*/) {}") + self.write_line("void " + node.class_name + "::visit_children(Visitor* /*v*/) {}") if members: # TODO : constructor definition : remove this with C++11 style - self.writer.write_line("/* constructor for " + node.class_name + " ast node */") + self.write_line("/* constructor for " + node.class_name + " ast node */") arguments = (", ".join(map(lambda x: x[0] + " " + x[1], members))) - self.writer.write_line(node.class_name + "::" + node.class_name + "(" + arguments + ")") + self.write_line(node.class_name + "::" + node.class_name + "(" + arguments + ")") if non_ptr_members: - self.writer.write_line(":", newline=0) - self.writer.write_line(",\n".join(map(lambda x: x[1] + "(" + x[1] + ")", non_ptr_members))) + self.write_line(":", newline=0) + self.write_line(",\n".join(map(lambda x: x[1] + "(" + x[1] + ")", non_ptr_members))) - self.writer.write_line("{") + self.write_line("{") for member in ptr_members: # todo : bit hack here, need to remove pointer because we are creating smart pointer typename = member[0].replace("*", "") - self.writer.write_line(" this->" + member[1] + " = std::shared_ptr<" + typename + ">(" + member[1] + ");") + self.write_line(" this->" + member[1] + " = std::shared_ptr<" + typename + ">(" + member[1] + ");") - self.writer.write_line("}", newline=2) + self.write_line("}", newline=2) # copy construcotr definition : remove this with C++11 style - self.writer.write_line("/* copy constructor for " + node.class_name + " ast node */") - self.writer.write_line(node.class_name + "::" + node.class_name + "(const " + node.class_name + "& obj) ") + self.write_line("/* copy constructor for " + node.class_name + " ast node */") + self.write_line(node.class_name + "::" + node.class_name + "(const " + node.class_name + "& obj) ") - self.writer.write_line("{", post_gutter=1) + self.write_line("{", post_gutter=1) # TODO : more cleanup for child in node.children: if child.is_vector: - self.writer.write_line("for(auto& item : obj." + child.varname + ") {") - self.writer.write_line(" this->" + child.varname + ".push_back(std::shared_ptr< " + child.class_name + ">(item->clone()));") - self.writer.write_line("}") + self.write_line("for(auto& item : obj." + child.varname + ") {") + self.write_line(" this->" + child.varname + ".push_back(std::shared_ptr< " + child.class_name + ">(item->clone()));") + self.write_line("}") else: if child.is_pointer_node(): - self.writer.write_line("if(obj." + child.varname + ")") - self.writer.write_line(" this->" + child.varname + " = std::shared_ptr<" + child.class_name + ">(obj." + child.varname + "->clone());") + self.write_line("if(obj." + child.varname + ")") + self.write_line(" this->" + child.varname + " = std::shared_ptr<" + child.class_name + ">(obj." + child.varname + "->clone());") else: - self.writer.write_line("this->" + child.varname + " = obj." + child.varname + ";") + self.write_line("this->" + child.varname + " = obj." + child.varname + ";") if node.has_token: - self.writer.write_line("if(obj.token)") - self.writer.write_line(" this->token = std::shared_ptr<ModToken>(obj.token->clone());") + self.write_line("if(obj.token)") + self.write_line(" this->token = std::shared_ptr<ModToken>(obj.token->clone());") - self.writer.write_line("}", pre_gutter=-1, newline=2) + self.write_line("}", pre_gutter=-1, newline=2) - self.writer.write_line("} // namespace ast", pre_gutter=-1) + self.write_line("} // namespace ast", pre_gutter=-1) diff --git a/src/nmodl/language/printer.py b/src/nmodl/language/printer.py index f72ffdce25..682f720604 100644 --- a/src/nmodl/language/printer.py +++ b/src/nmodl/language/printer.py @@ -38,14 +38,14 @@ def write(self, string): self.print_gutter() self.fh.write(string) - def write_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): + def write_line(self, string, newline, pre_gutter, post_gutter): self.num_tabs += pre_gutter self.write(string) for i in range(newline): self.fh.write("\n") self.num_tabs += post_gutter - def add_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): + def add_line(self, string, newline, pre_gutter, post_gutter): self.num_tabs += pre_gutter if string: for i in range(self.num_tabs): @@ -77,6 +77,12 @@ def __init__(self, filepath, classname, nodes): self.classname = classname self.nodes = nodes + def write_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): + self.writer.write_line(string, newline, pre_gutter, post_gutter) + + def add_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): + self.writer.add_line(string, newline, pre_gutter, post_gutter) + @abstractmethod def write(self): pass @@ -96,7 +102,7 @@ class DeclarationPrinter(Printer): """ def guard(self): - self.writer.write_line("#pragma once", newline=2) + self.write_line("#pragma once", newline=2) @abstractmethod def headers(self): @@ -106,12 +112,12 @@ def class_comment(self): pass def class_name_declaration(self): - self.writer.write_line("class " + self.classname + " {") + self.write_line("class " + self.classname + " {") def declaration_start(self): self.guard() self.headers() - self.writer.write_line() + self.write_line() self.class_comment() self.class_name_declaration() @@ -123,13 +129,13 @@ def public_declaration(self): def body(self): self.writer.increase_gutter() - self.writer.write_line() + self.write_line() self.private_declaration() self.public_declaration() self.writer.decrease_gutter() def declaration_end(self): - self.writer.write_line("};", pre_gutter=-1) + self.write_line("};", pre_gutter=-1) def post_declaration(self): pass diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 6ee969d0a9..c81263e8d5 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -9,14 +9,14 @@ def headers(self): pass def class_comment(self): - self.writer.write_line("/* Abstract base class for all visitor implementations */") + self.write_line("/* Abstract base class for all visitor implementations */") def public_declaration(self): - self.writer.write_line("public:", post_gutter=1) + self.write_line("public:", post_gutter=1) for node in self.nodes: line = "virtual void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) = 0;" - self.writer.write_line(line) + self.write_line(line) self.writer.decrease_gutter() @@ -26,24 +26,24 @@ class AstVisitorDeclarationPrinter(DeclarationPrinter): def headers(self): line = '#include "ast/ast.hpp"' - self.writer.write_line(line) + self.write_line(line) line = '#include "visitors/visitor.hpp"' - self.writer.write_line(line) + self.write_line(line) line = "using namespace ast;" - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) def class_comment(self): - self.writer.write_line("/* Basic visitor implementation */") + self.write_line("/* Basic visitor implementation */") def class_name_declaration(self): - self.writer.write_line("class " + self.classname + " : public Visitor {") + self.write_line("class " + self.classname + " : public Visitor {") def public_declaration(self): - self.writer.write_line("public:", post_gutter=1) + self.write_line("public:", post_gutter=1) for node in self.nodes: line = "virtual void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" - self.writer.write_line(line) + self.write_line(line) self.writer.decrease_gutter() @@ -52,14 +52,14 @@ class AstVisitorDefinitionPrinter(DefinitionPrinter): """Prints base visitor class method definitions""" def headers(self): - self.writer.write_line('#include "visitors/ast_visitor.hpp"', newline=2) + self.write_line('#include "visitors/ast_visitor.hpp"', newline=2) def definitions(self): for node in self.nodes: line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" - self.writer.write_line(line, post_gutter=1) - self.writer.write_line("node->visit_children(this);", post_gutter=-1) - self.writer.write_line("}", newline=2) + self.write_line(line, post_gutter=1) + self.write_line("node->visit_children(this);", post_gutter=-1) + self.write_line("}", newline=2) class JSONVisitorDeclarationPrinter(DeclarationPrinter): @@ -67,41 +67,41 @@ class JSONVisitorDeclarationPrinter(DeclarationPrinter): def headers(self): line = '#include "visitors/ast_visitor.hpp"' - self.writer.write_line(line) + self.write_line(line) line = '#include "printer/json_printer.hpp"' - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) def class_comment(self): - self.writer.write_line("/* Concrete visitor for printing AST in JSON format */") + self.write_line("/* Concrete visitor for printing AST in JSON format */") def class_name_declaration(self): - self.writer.write_line("class " + self.classname + " : public AstVisitor {") + self.write_line("class " + self.classname + " : public AstVisitor {") def private_declaration(self): - self.writer.write_line("private:", post_gutter=1) + self.write_line("private:", post_gutter=1) line = "std::unique_ptr<JSONPrinter> printer;" - self.writer.write_line(line, newline=2, post_gutter=-1) + self.write_line(line, newline=2, post_gutter=-1) def public_declaration(self): - self.writer.write_line("public:", post_gutter=1) + self.write_line("public:", post_gutter=1) line = self.classname + "() : printer(new JSONPrinter()) {} " - self.writer.write_line(line) + self.write_line(line) line = self.classname + "(std::string filename) : printer(new JSONPrinter(filename)) {}" - self.writer.write_line(line) + self.write_line(line) line = self.classname + "(std::stringstream &ss) : printer(new JSONPrinter(ss)) {}" - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) line = "void flush() { printer->flush(); }" - self.writer.write_line(line) + self.write_line(line) line = "void compact_json(bool flag) { printer->compact_json(flag); } " - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) for node in self.nodes: line = "void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" - self.writer.write_line(line) + self.write_line(line) self.writer.decrease_gutter() @@ -111,38 +111,38 @@ class JSONVisitorDefinitionPrinter(DefinitionPrinter): def headers(self): line = '#include "visitors/json_visitor.hpp"' - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) def definitions(self): for node in self.nodes: line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" - self.writer.write_line(line, post_gutter=1) + self.write_line(line, post_gutter=1) if node.has_children(): - self.writer.write_line("printer->push_block(node->get_type_name());") - self.writer.write_line("node->visit_children(this);") + self.write_line("printer->push_block(node->get_type_name());") + self.write_line("node->visit_children(this);") if node.is_data_type_node(): if node.class_name == "Integer": - self.writer.write_line("if(!node->macroname) {", post_gutter=1) - self.writer.write_line("std::stringstream ss;") - self.writer.write_line("ss << node->eval();") - self.writer.write_line("printer->add_node(ss.str());", post_gutter=-1) - self.writer.write_line("}") + self.write_line("if(!node->macroname) {", post_gutter=1) + self.write_line("std::stringstream ss;") + self.write_line("ss << node->eval();") + self.write_line("printer->add_node(ss.str());", post_gutter=-1) + self.write_line("}") else: - self.writer.write_line("std::stringstream ss;") - self.writer.write_line("ss << node->eval();") - self.writer.write_line("printer->add_node(ss.str());") + self.write_line("std::stringstream ss;") + self.write_line("ss << node->eval();") + self.write_line("printer->add_node(ss.str());") - self.writer.write_line("printer->pop_block();") + self.write_line("printer->pop_block();") if node.class_name == "Program": - self.writer.write_line("flush();") + self.write_line("flush();") else: - self.writer.write_line("(void)node;") - self.writer.write_line("printer->add_node(\"" + node.class_name + "\");") + self.write_line("(void)node;") + self.write_line("printer->add_node(\"" + node.class_name + "\");") - self.writer.write_line("}", pre_gutter=-1, newline=2) + self.write_line("}", pre_gutter=-1, newline=2) self.writer.decrease_gutter() @@ -152,66 +152,66 @@ class SymtabVisitorDeclarationPrinter(DeclarationPrinter): def headers(self): line = '#include "visitors/json_visitor.hpp"' - self.writer.write_line(line) + self.write_line(line) line = '#include "visitors/ast_visitor.hpp"' - self.writer.write_line(line) + self.write_line(line) line = '#include "symtab/symbol_table.hpp"' - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) def class_comment(self): - self.writer.write_line("/* Concrete visitor for constructing symbol table from AST */") + self.write_line("/* Concrete visitor for constructing symbol table from AST */") def class_name_declaration(self): - self.writer.write_line("using namespace symtab;") - self.writer.write_line("class " + self.classname + " : public AstVisitor {") + self.write_line("using namespace symtab;") + self.write_line("class " + self.classname + " : public AstVisitor {") def private_declaration(self): - self.writer.write_line("private:", post_gutter=1) - self.writer.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) - self.writer.write_line("std::unique_ptr<JSONPrinter> printer;") + self.write_line("private:", post_gutter=1) + self.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) + self.write_line("std::unique_ptr<JSONPrinter> printer;") def public_declaration(self): - self.writer.write_line("public:", post_gutter=1) + self.write_line("public:", post_gutter=1) line = self.classname + "(ModelSymbolTable* symtab) : modsymtab(symtab), printer(new JSONPrinter()) {} " - self.writer.write_line(line) + self.write_line(line) line = self.classname + "( ModelSymbolTable* symtab, std::stringstream &ss) : modsymtab(symtab), printer(new JSONPrinter(ss)) {}" - self.writer.write_line(line) + self.write_line(line) line = self.classname + "( ModelSymbolTable* symtab, std::string filename) : modsymtab(symtab), printer(new JSONPrinter(filename)) {}" - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) # helper function for creating symbol for variables - self.writer.write_line("template<typename T>") - self.writer.write_line("void setup_symbol(T* node, SymbolInfo property, int order = 0);", newline=2) + self.write_line("template<typename T>") + self.write_line("void setup_symbol(T* node, SymbolInfo property, int order = 0);", newline=2) # helper function for creating symbol table for blocks # without name (e.g. parameter, unit, breakpoint) - self.writer.write_line("template<typename T>") + self.write_line("template<typename T>") line = "void setup_symbol_table(T *node, std::string name, bool is_global);" - self.writer.write_line(line) + self.write_line(line) # helper function for creating symbol table for blocks # with name (e.g. procedure, function, derivative) - self.writer.write_line("template<typename T>") + self.write_line("template<typename T>") line = "void setup_symbol_table(T *node, std::string name, SymbolInfo property, bool is_global);" - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) # we have to override visitor methods for the nodes # which goes into symbol table for node in self.nodes: if node.is_symtab_method_required(): line = "void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" - self.writer.write_line(line) + self.write_line(line) self.writer.decrease_gutter() def post_declaration(self): # helper function definitions - self.writer.write_line() - self.writer.write_line('#include "visitors/symtab_visitor_helper.hpp"') + self.write_line() + self.write_line('#include "visitors/symtab_visitor_helper.hpp"') class SymtabVisitorDefinitionPrinter(DefinitionPrinter): @@ -219,23 +219,23 @@ class SymtabVisitorDefinitionPrinter(DefinitionPrinter): def headers(self): line = '#include "symtab/symbol_table.hpp"' - self.writer.write_line(line) + self.write_line(line) line = '#include "visitors/symtab_visitor.hpp"' - self.writer.write_line(line, newline=2) + self.write_line(line, newline=2) def definitions(self): for node in self.nodes: if node.is_symtab_method_required(): line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" - self.writer.write_line(line, post_gutter=1) + self.write_line(line, post_gutter=1) type_name = to_snake_case(node.class_name) property_name = "symtab::details::NmodlInfo::" + type_name if node.is_symbol_var_node() or node.is_prime_node(): is_prime = ", node->get_order()" if node.is_prime_node() else ""; - self.writer.write_line("setup_symbol(node, " + property_name + is_prime + ");") + self.write_line("setup_symbol(node, " + property_name + is_prime + ");") else: """ setupBlock has node*, properties, global_block""" @@ -249,5 +249,5 @@ def definitions(self): """this is for nodes which has parent class as Block node""" fun_call = "setup_symbol_table(node, node->get_type_name(), false);" - self.writer.write_line(fun_call) - self.writer.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file + self.write_line(fun_call) + self.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file From 68f25f32315533922b77b9eb940c3a6ed1430781 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 31 Dec 2017 09:55:32 +0100 Subject: [PATCH 035/871] Minor style update for ast printer Change-Id: Ife4fd611a222bc53b2bed3c99f60322cca5158c3 NMODL Repo SHA: BlueBrain/nmodl@0b7cf64311a1645fae6f9766167e2f6a79b21c61 --- src/nmodl/language/ast_printer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index ffbdcff2d0..53f496720e 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -279,7 +279,7 @@ def definitions(self): self.writer.increase_gutter() if child.is_vector: # TODO : remove this with C++11 style - self.add_line("for(auto& item : this->" + child.varname + ") {") + self.add_line("for (auto& item : this->" + child.varname + ") {") self.add_line(" item->accept(v);") self.add_line("}") elif child.optional or child.is_statement_block_node(): @@ -328,18 +328,18 @@ def definitions(self): # TODO : more cleanup for child in node.children: if child.is_vector: - self.write_line("for(auto& item : obj." + child.varname + ") {") + self.write_line("for (auto& item : obj." + child.varname + ") {") self.write_line(" this->" + child.varname + ".push_back(std::shared_ptr< " + child.class_name + ">(item->clone()));") self.write_line("}") else: if child.is_pointer_node(): - self.write_line("if(obj." + child.varname + ")") + self.write_line("if (obj." + child.varname + ")") self.write_line(" this->" + child.varname + " = std::shared_ptr<" + child.class_name + ">(obj." + child.varname + "->clone());") else: self.write_line("this->" + child.varname + " = obj." + child.varname + ";") if node.has_token: - self.write_line("if(obj.token)") + self.write_line("if (obj.token)") self.write_line(" this->token = std::shared_ptr<ModToken>(obj.token->clone());") self.write_line("}", pre_gutter=-1, newline=2) From 9d17d996b36a1d6bb93dea5c8b24b65d6ce0a8f4 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 2 Jan 2018 01:00:39 +0100 Subject: [PATCH 036/871] Bug fix for macros definition: - typename was added instead of macro name itself - scanner was giving "jammed" error, fixed with the patch from Fouriaux's parser test branch Added parser tests for macro definition. Change-Id: I141a62b78029313e423d76e7d60154232dfb79c7 NMODL Repo SHA: BlueBrain/nmodl@e4797275ead1a4869f2b3288de78d62eb3450c61 --- src/nmodl/lexer/nmodl.ll | 9 ++- src/nmodl/parser/nmodl.yy | 3 +- test/nmodl/transpiler/CMakeLists.txt | 4 ++ test/nmodl/transpiler/parser/parser.cpp | 89 +++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/parser/parser.cpp diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 911111fafe..140e279c29 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -142,7 +142,6 @@ ELSE { } <MACRO_NAME_MODE>[a-zA-Z][a-zA-Z0-9_]* { - /** macro name (typically string) */ BEGIN(MACRO_VALUE_MODE); return name_symbol(yytext, loc, Token::INTEGER); @@ -449,6 +448,14 @@ ELSE { yymore(); } +<MACRO_NAME_MODE>[^a-zA-Z_ \t]+ | +<MACRO_VALUE_MODE>[^ \t0-9]+ { + /** If macro name doesn't start with character or value + * is not an integer then it's invalid macro definition + */ + return nmodl::Parser::make_INVALID_TOKEN(loc); + } + \"[^\"\n]*$ { std::cout << "\n ERROR: Unterminated string (e.g. for printf) \n"; } diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index b68519e8b3..72d7cfe3af 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -201,6 +201,7 @@ %token <ModToken> PERIOD "." %token END 0 "End of file" %token UNKNOWN +%token INVALID_TOKEN /** Define terminal and nonterminal symbols : Instead of using AST classes * directly, we are using typedefs like program_ptr. This is useful when we @@ -472,7 +473,7 @@ model : MODEL LINE_PART define1 : DEFINE1 NAME INTEGER { $$ = new ast::Define($2, $3); - driver.add_defined_var($2->get_type_name(), $3->eval()); + driver.add_defined_var($2->get_name(), $3->eval()); } | DEFINE1 error { diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 6f8a965caa..10fe1d14d9 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -9,18 +9,21 @@ include_directories( #============================================================================= add_executable (testmodtoken modtoken/modtoken.cpp) add_executable (testlexer lexer/tokens.cpp) +add_executable (testparser parser/parser.cpp) add_executable (testvisitor visitor/visitor.cpp) add_executable (testprinter printer/printer.cpp) add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) +target_link_libraries(testparser lexer) target_link_libraries(testvisitor printer symtab lexer visitor util) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) add_test (NAME ModToken COMMAND testmodtoken) add_test (NAME Lexer COMMAND testlexer) +add_test (NAME Parser COMMAND testparser) add_test (NAME Visitor COMMAND testvisitor) add_test (NAME Printer COMMAND testprinter) add_test (NAME Symtab COMMAND testsymtab) @@ -31,6 +34,7 @@ add_test (NAME Symtab COMMAND testsymtab) set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/modtoken/modtoken.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lexer/tokens.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/parser/parser.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor/visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/printer/printer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab/symbol_table.cpp diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp new file mode 100644 index 0000000000..5226748a57 --- /dev/null +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -0,0 +1,89 @@ +#define CATCH_CONFIG_MAIN + +#include <string> + +#include "catch/catch.hpp" +#include "parser/nmodl_driver.hpp" + +//============================================================================= +// Parser tests +//============================================================================= + +bool is_valid_construct(const std::string& construct) { + nmodl::Driver driver; + return driver.parse_string(construct); +} + +SCENARIO("NMODL can define macros using DEFINE keyword") { + GIVEN("A valid macro definition") { + WHEN("DEFINE NSTEP 6") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("DEFINE NSTEP 6")); + } + } + WHEN("DEFINE NSTEP 6") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("DEFINE NSTEP 6")); + } + } + } + + GIVEN("A macro with nested definition is not supported") { + WHEN("DEFINE SIX 6 DEFINE NSTEP SIX") { + THEN("parser throws an error") { + REQUIRE_THROWS_WITH(is_valid_construct("DEFINE SIX 6 DEFINE NSTEP SIX"), + Catch::Contains("unexpected INVALID_TOKEN")); + } + } + } + + GIVEN("A invalid macro definition with float value") { + WHEN("DEFINE NSTEP 6.0") { + THEN("parser throws an exception") { + REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP 6.0"), + Catch::Contains("unexpected REAL")); + } + } + } + + GIVEN("A invalid macro definition with name and without value") { + WHEN("DEFINE NSTEP") { + THEN("parser throws an exception") { + REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP"), + Catch::Contains("expecting INTEGER")); + } + } + } + + GIVEN("A invalid macro definition with name and value as a name") { + WHEN("DEFINE NSTEP SIX") { + THEN("parser throws an exception") { + REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP SIX"), + Catch::Contains("expecting INTEGER")); + } + } + } + + GIVEN("A invalid macro definition without name but with value") { + WHEN("DEFINE 6") { + THEN("parser throws an exception") { + REQUIRE_THROWS_WITH(is_valid_construct("DEFINE 6"), + Catch::Contains("expecting NAME")); + } + } + } +} + +SCENARIO("Macros can be used anywhere in NMODL program") { + std::string nmodl_text = R"( + DEFINE NSTEP 6 + PARAMETER { + amp[NSTEP] (mV) + } + )"; + WHEN("macro is used in parameter definition") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct(nmodl_text)); + } + } +} \ No newline at end of file From 43b05379e24bf11b9a5915c1125e5d77258cba88 Mon Sep 17 00:00:00 2001 From: fouriaux <jeremy.fouriaux@epfl.ch> Date: Thu, 11 Jan 2018 13:32:39 +0100 Subject: [PATCH 037/871] update documentation Change-Id: I24b9cee00473a123a34a77eede60b7418e85b8b4 NMODL Repo SHA: BlueBrain/nmodl@0c86d81f073df64fc320facc556c13dd696292ca --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 441fd2e7e0..0990a0fe1a 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - CMake (>=3.1) - C++ compiler (with c++11 support) - Python2 (>=2.7) +- Python yaml Make sure to have latest version of flex (>=2.6) and bison (>=3.0). For example, on OS X we typically install packages via brew or macport as: @@ -39,6 +40,11 @@ $ bison --version bison (GNU Bison) 3.0.4 ``` +Python yaml can be installed on Ubuntu using: +``` +sudo apt-get install python-yaml +``` + #### Build Build/Compile NOCMODL as: @@ -111,4 +117,4 @@ Or using CTest as: ``` ctest -T memcheck -``` \ No newline at end of file +``` From 9cd7b8f96eaa98e6cebb0d9f5f4759fa5b6977de Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 13 Jan 2018 19:08:39 +0100 Subject: [PATCH 038/871] Re-organize test constructs: - nmodl constructs required for testing added into separate file - parser test updated to use new constructs (squash from sandbox/kumbhar/parser-tests) Change-Id: I947bd7c7d5ee46240a4f172ce2c0142993ae4dcd NMODL Repo SHA: BlueBrain/nmodl@98dbd7a52c4723829e62b930db46da30f965b573 --- test/nmodl/transpiler/CMakeLists.txt | 13 +- .../transpiler/input/nmodl_constructs.cpp | 329 ++++++++++++++++++ .../nmodl/transpiler/input/nmodl_constructs.h | 16 + test/nmodl/transpiler/parser/parser.cpp | 25 +- 4 files changed, 381 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/input/nmodl_constructs.cpp create mode 100644 test/nmodl/transpiler/input/nmodl_constructs.h diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 10fe1d14d9..f9b3fe06ab 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -2,6 +2,15 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/test) include_directories( ${PROJECT_SOURCE_DIR}/src/ext/catch + ${PROJECT_SOURCE_DIR}/test +) + +#============================================================================= +# Common input data library +#============================================================================= +add_library(test_input + STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/input/nmodl_constructs.cpp ) #============================================================================= @@ -16,7 +25,7 @@ add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) -target_link_libraries(testparser lexer) +target_link_libraries(testparser lexer test_input) target_link_libraries(testvisitor printer symtab lexer visitor util) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) @@ -32,6 +41,8 @@ add_test (NAME Symtab COMMAND testsymtab) # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/input/nmodl_constructs.h + ${CMAKE_CURRENT_SOURCE_DIR}/input/nmodl_constructs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modtoken/modtoken.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lexer/tokens.cpp ${CMAKE_CURRENT_SOURCE_DIR}/parser/parser.cpp diff --git a/test/nmodl/transpiler/input/nmodl_constructs.cpp b/test/nmodl/transpiler/input/nmodl_constructs.cpp new file mode 100644 index 0000000000..17ea077ad8 --- /dev/null +++ b/test/nmodl/transpiler/input/nmodl_constructs.cpp @@ -0,0 +1,329 @@ +#include "input/nmodl_constructs.h" + +std::map<std::string, NmodlTestCase> nmdol_invalid_constructs{ + // clang-format off + { + "title_1", + { + "Title statement without any text", + "TITLE" + } + }, + + { + "local_list_1", + { + "LOCAL statement without any variable", + "LOCAL" + } + }, + + { + "local_list_2", + { + "LOCAL statement with empty index", + "LOCAL gbar[]" + } + }, + + { + "local_list_3", + { + "LOCAL statement with invalid index", + "LOCAL gbar[2.0]" + } + }, + + { + "define_1", + { + "Incomplete macro definition without value", + "DEFINE NSTEP" + } + }, + + { + "model_level_1", + { + "Model level without any block", + "MODEL_LEVEL 2" + } + }, + + { + "verbatim_block_1", + { + "Nested verbatim blocks", + R"( + VERBATIM + VERBATIM + #include <cuda.h> + ENDVERBATIM + ENDVERBATIM + )" + } + }, + + { + "comment_block_1", + { + "Nested comment blocks", + R"( + COMMENT + COMMENT + some code comment here + ENDCOMMENT + ENDCOMMENT + )" + } + }, + + { + "include_statement_1", + { + "Incomplete include statements", + "INCLUDE " + } + }, + + { + "parameter_block_1", + { + "Incomplete parameter declaration", + R"( + PARAMETER { + ampa = + } + )" + } + }, + + { + "parameter_block_2", + { + "Invalid parameter declaration", + R"( + PARAMETER { + ampa = 2 (ms> + } + )" + } + }, + // clang-format on +}; + +std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ + // clang-format off + { + "title_1", + { + "Title statement", + "TITLE nmodl title\n" + } + }, + + { + "local_list_1", + { + "standalone LOCAL statement with single scalar variable", + "LOCAL gbar" + } + }, + + { + "local_list_2", + { + "standalone LOCAL statement with single vector variable", + "LOCAL gbar[2]" + } + }, + + { + "local_list_3", + { + "standalone LOCAL statement with multiple variables", + "LOCAL gbar, ek[2], ik" + } + }, + + { + "define_1", + { + "Macro definition", + "DEFINE NSTEP 10" + } + }, + + { + "model_level_1", + { + "Model level followed by block", + "MODEL_LEVEL 2 NEURON {}" + } + }, + + { + "verbatim_block_1", + { + "Stanadlone empty verbatim block", + R"( + VERBATIM + ENDVERBATIM + )" + } + }, + + { + "verbatim_block_2", + { + "Standalone verbatim block", + R"( + VERBATIM + #include <cuda.h> + ENDVERBATIM + )" + } + }, + + { + "comment_block_1", + { + "Standalone comment block", + R"( + COMMENT + some comment here + ENDCOMMENT + )" + } + }, + + { + "unit_statement_1", + { + "Standalone unit on/off statements", + R"( + UNITSON + UNITSOFF + UNITSON + UNITSOFF + )" + } + }, + + { + "include_statement_1", + { + "Standalone include statements", + R"(INCLUDE "Unit.inc" )" + } + }, + + { + "parameter_block_1", + { + "Empty parameter block", + R"( + PARAMETER { + } + )" + } + }, + + { + "parameter_block_2", + { + "PARAMETER block with all statement types", + R"( + PARAMETER { + tau_r_AMPA = 10 + tau_d_AMPA = 10.0 (mV) + tau_r_NMDA = 10 (mV) <1,2> + tau_d_NMDA = 10 (mV) <1.1,2.2> + Use (mV) + Dep [1] <1,2> + Fac[1] (mV) <1,2> + g + } + )" + } + }, + + { + "parameter_block_3", + { + "PARAMETER statement can use macro definition as a number", + R"( + DEFINE SIX 6 + PARAMETER { + tau_r_AMPA = SIX + } + )" + } + }, + + { + "step_block_1", + { + "STEP block with all statement types", + R"( + STEPPED { + tau_r_AMPA = 1 , -2 + tau_d_AMPA = 1.0,-2.0 + tau_r_NMDA = 1,2.0,3 (mV) + } + )" + } + }, + + { + "independent_block_1", + { + "INDEPENDENT block with all statement types", + R"( + INDEPENDENT { + t FROM 0 TO 1 WITH 1 (ms) + SWEEP u FROM 0 TO 1 WITH 1 (ms) + } + )" + } + }, + + { + "dependent_block_1", + { + "ASSIGNED block with all statement types", + R"( + ASSIGNED { + v + i_AMPA (nA) + i_NMDA START 2.0 (nA) <1.0> + A_NMDA_step[1] START 1 <2.0> + factor_AMPA FROM 0 TO 1 + B_NMDA_step[2] FROM 1 TO 2 START 0 (ms) <1> + } + )" + } + }, + + { + "neuron_block_1", + { + "NEURON block with many statement types", + R"( + NEURON { + SUFFIX ProbAMPANMDA + USEION na READ ena WRITE ina + NONSPECIFIC_CURRENT i + ELECTRODE_CURRENT i + RANGE tau_r_AMPA, tau_d_AMPA + GLOBAL gNa, xNa + POINTER rng1, rng2 + BBCOREPOINTER rng3 + EXTERNAL extvar + THREADSAFE + } + )" + } + }, + // clang-format on +}; \ No newline at end of file diff --git a/test/nmodl/transpiler/input/nmodl_constructs.h b/test/nmodl/transpiler/input/nmodl_constructs.h new file mode 100644 index 0000000000..97dcea12c7 --- /dev/null +++ b/test/nmodl/transpiler/input/nmodl_constructs.h @@ -0,0 +1,16 @@ +#ifndef NMODL_TEST_CONSTRUCTS +#define NMODL_TEST_CONSTRUCTS + +#include <string> +#include <map> + +struct NmodlTestCase { + std::string name; + std::string nmodl_text; + /// \todo : add associated json (to use in visitor test) +}; + +extern std::map<std::string, NmodlTestCase> nmdol_invalid_constructs; +extern std::map<std::string, NmodlTestCase> nmodl_valid_constructs; + +#endif \ No newline at end of file diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 5226748a57..b3996c4c1e 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -4,6 +4,7 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" +#include "input/nmodl_constructs.h" //============================================================================= // Parser tests @@ -86,4 +87,26 @@ SCENARIO("Macros can be used anywhere in NMODL program") { REQUIRE(is_valid_construct(nmodl_text)); } } -} \ No newline at end of file +} + +SCENARIO("Parser test for valid NMODL grammar constructs") { + for (const auto& construct : nmodl_valid_constructs) { + auto test_case = construct.second; + GIVEN(test_case.name) { + THEN("Parser successfully parses : " + test_case.nmodl_text) { + REQUIRE(is_valid_construct(test_case.nmodl_text)); + } + } + } +} + +SCENARIO("Parser test for invalid NMODL grammar constructs") { + for (const auto& construct : nmdol_invalid_constructs) { + auto test_case = construct.second; + GIVEN(test_case.name) { + THEN("Parser throws an exception while parsing : " + test_case.nmodl_text) { + REQUIRE_THROWS(is_valid_construct(test_case.nmodl_text)); + } + } + } +} From 02966103bfe8267ac46c82f1b13e2fede363f150 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 16 Jan 2018 18:11:01 +0100 Subject: [PATCH 039/871] Implementation of NMODLPrinter visitor that transforms/print AST back to NMODL: - Added tests for almost all nmodl constructs - Tests enabled for parsed and ast-to-nmodl visitor - Bugfix in varname where IndexedName was incorrectly used - a@2[1+2] varname was being printed as a[1+2]@2 = 21.1 - YAML definitions are significantly updated/tested - Empty unit bug fix: - grammar was accepting empty unit - Childrens in yaml specification can have associated nmodl text - it's limited at the moment : children is considered as Boolean - NMODL printer class added (similar to JSON printer) - Added new ast node called "Node": - removed statement member from Program - helps to preserve the order of statements and blocks - nmodl printer updated to avoid extra whitespaces and newline - nmodl test constructs updated - Added Stepped as statement type - Test for MODEL_LEVEL disabled as it's not handled properly (deprecated) - Changed force_prefix from bool to string - New ast node ParenExpression added to represent expression surrounding parenthesis - Python scripts minor cleanup - Remove use of rtrim, added guidelines/notes (Squashed commit of the branch : sandbox/kumbhar/nmodl_printer) Change-Id: Ia47bc51dadd22b14189d2ea7d2ed9fb7d9084a5d NMODL Repo SHA: BlueBrain/nmodl@1312c93630367ea0b11a9d2b49780b5b18ca7e78 --- src/nmodl/ast/ast_utils.hpp | 8 + src/nmodl/language/CMakeLists.txt | 3 + src/nmodl/language/argument.py | 12 +- src/nmodl/language/code_generator.py | 11 + src/nmodl/language/nmodl.yaml | 1826 +++++++++-------- src/nmodl/language/nmodl_printer.py | 163 ++ src/nmodl/language/node_info.py | 8 +- src/nmodl/language/parser.py | 16 +- src/nmodl/parser/nmodl.yy | 49 +- src/nmodl/printer/CMakeLists.txt | 2 + src/nmodl/printer/nmodl_printer.cpp | 42 + src/nmodl/printer/nmodl_printer.hpp | 53 + src/nmodl/utils/common_utils.hpp | 4 +- src/nmodl/visitors/CMakeLists.txt | 3 + src/nmodl/visitors/main.cpp | 6 + src/nmodl/visitors/nmodl_visitor_helper.hpp | 48 + test/nmodl/transpiler/CMakeLists.txt | 4 +- .../transpiler/input/nmodl_constructs.cpp | 985 ++++++++- .../nmodl/transpiler/input/nmodl_constructs.h | 19 +- test/nmodl/transpiler/parser/parser.cpp | 10 +- test/nmodl/transpiler/visitor/visitor.cpp | 102 +- 21 files changed, 2411 insertions(+), 963 deletions(-) create mode 100644 src/nmodl/language/nmodl_printer.py create mode 100644 src/nmodl/printer/nmodl_printer.cpp create mode 100644 src/nmodl/printer/nmodl_printer.hpp create mode 100644 src/nmodl/visitors/nmodl_visitor_helper.hpp diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 5f3055a7c3..370a333f72 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -362,6 +362,10 @@ namespace ast { return false; } + virtual bool is_paren_expression() { + return false; + } + virtual bool is_unary_operator() { return false; } @@ -612,6 +616,10 @@ namespace ast { return false; } + virtual bool is_node() { + return false; + } + virtual bool is_program() { return false; } diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 0b4db4eab7..24903bb453 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -13,6 +13,8 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.cpp WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PROJECT_SOURCE_DIR}/src/language/argument.py @@ -23,6 +25,7 @@ add_custom_command ( DEPENDS ${PROJECT_SOURCE_DIR}/src/language/printer.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast_printer.py DEPENDS ${PROJECT_SOURCE_DIR}/src/language/visitors_printer.py + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl_printer.py COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" ) diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index e9e7c46950..5dafcf0fec 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -1,9 +1,5 @@ class Argument: - """Utility class for holding all arguments for node classes - - when force_prefix / force_suffix is true then prefix/suffix needs to be - printed even if node itself is null - """ + """Utility class for holding all arguments for node classes""" def __init__(self): self.base_class = "" @@ -11,6 +7,8 @@ def __init__(self): self.nmodl_name = "" self.prefix = "" self.suffix = "" + self.force_prefix = "" + self.force_suffix = "" self.separator = "" self.typename = "" self.varname = "" @@ -18,6 +16,4 @@ def __init__(self): self.is_optional = False self.add_method = False self.getname_method = False - self.has_token = False - self.force_prefix = False - self.force_suffix = False \ No newline at end of file + self.has_token = False \ No newline at end of file diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index fd49e5d3fc..58f27e78eb 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,6 +1,7 @@ from parser import LanguageParser from ast_printer import * from visitors_printer import * +from nmodl_printer import * # parse nmodl definition file and get list of abstract nodes @@ -51,4 +52,14 @@ SymtabVisitorDefinitionPrinter( "../visitors/symtab_visitor.cpp", "SymtabVisitor", + nodes).write() + +NmodlVisitorDeclarationPrinter( + "../visitors/nmodl_visitor.hpp", + "NmodlPrintVisitor", + nodes).write() + +NmodlVisitorDefinitionPrinter( + "../visitors/nmodl_visitor.cpp", + "NmodlPrintVisitor", nodes).write() \ No newline at end of file diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 12d89726be..fdb7b43faf 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -594,1025 +594,1053 @@ # specification - AST: - - Expression: - - String: - members: - - value: - type: std::string - - Number: - - Integer: + - Node: + - Expression: + - String: members: - value: - type: int - - macroname: - type: Name - optional: true - - Float: - members: - - value: - type: float - - Double: - members: - - value: - type: double - - Boolean: + type: std::string + - Number: + - Integer: + members: + - value: + type: int + - macroname: + type: Name + optional: true + - Float: + members: + - value: + type: float + - Double: + members: + - value: + type: double + - Boolean: + members: + - value: + type: int + - Identifier: + - Name: + members: + - value: + type: String + getname: true + - PrimeName: + members: + - value: + type: String + getname: true + - order: + type: Integer + - VarName: + members: + - name: + type: Identifier + getname: true + - at_index: + type: Integer + optional: true + prefix: {value: "@"} + - index: + type: Expression + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - IndexedName: + members: + - name: + type: Identifier + getname: true + - index: + type: Expression + prefix: {value: "["} + suffix: {value: "]"} + - Argument: + members: + - name: + type: Identifier + getname: true + - unit: + type: Unit + optional: true + - ReactVarName: + members: + - value: + type: Integer + optional: true + prefix: {value: " "} + - name: + type: VarName + getname: true + - ReadIonVar: + members: + - name: + type: Name + getname: true + - WriteIonVar: + members: + - name: + type: Name + getname: true + - NonspeCurVar: + members: + - name: + type: Name + getname: true + - ElectrodeCurVar: + members: + - name: + type: Name + getname: true + - SectionVar: + members: + - name: + type: Name + getname: true + - RangeVar: + members: + - name: + type: Name + getname: true + - GlobalVar: + members: + - name: + type: Name + getname: true + - PointerVar: + members: + - name: + type: Name + getname: true + - BbcorePointerVar: + members: + - name: + type: Name + getname: true + - ExternVar: + members: + - name: + type: Name + getname: true + - ThreadsafeVar: + members: + - name: + type: Name + getname: true + - Block: + - ParamBlock: + nmodl: PARAMETER + members: + - statements: + type: ParamAssign + vector: true + - StepBlock: + nmodl: STEPPED + members: + - statements: + type: Stepped + vector: true + - IndependentBlock: + nmodl: INDEPENDENT + members: + - definitions: + type: IndependentDef + vector: true + - DependentBlock: + nmodl: ASSIGNED + members: + - definitions: + type: DependentDef + vector: true + - StateBlock: + nmodl: STATE + members: + - definitions: + type: DependentDef + vector: true + - PlotBlock: + members: + - plot: + type: PlotDeclaration + - InitialBlock: + nmodl: INITIAL + members: + - statementblock: + type: StatementBlock + - ConstructorBlock: + nmodl: CONSTRUCTOR + members: + - statementblock: + type: StatementBlock + - DestructorBlock: + nmodl: DESTRUCTOR + members: + - statementblock: + type: StatementBlock + - StatementBlock: + members: + - statements: + type: Statement + vector: true + - DerivativeBlock: + nmodl: "DERIVATIVE " + members: + - name: + type: Name + getname: true + - statementblock: + type: StatementBlock + - LinearBlock: + nmodl: "LINEAR " + members: + - name: + type: Name + getname: true + - solvefor: + type: Name + vector: true + separator: "," + prefix: {value: " SOLVEFOR "} + - statementblock: + type: StatementBlock + - NonLinearBlock: + nmodl: "NONLINEAR " + members: + - name: + type: Name + getname: true + - solvefor: + type: Name + vector: true + separator: "," + prefix: {value: " SOLVEFOR "} + - statementblock: + type: StatementBlock + - DiscreteBlock: + nmodl: "DISCRETE " + members: + - name: + type: Name + getname: true + - statementblock: + type: StatementBlock + - PartialBlock: + nmodl: "PARTIAL " + members: + - name: + type: Name + getname: true + - statementblock: + type: StatementBlock + - FunctionTableBlock: + nmodl: "FUNCTION_TABLE " + members: + - name: + type: Name + getname: true + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + - unit: + type: Unit + optional: true + prefix: {value: " "} + - FunctionBlock: + nmodl: "FUNCTION " + members: + - name: + type: Name + getname: true + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + - unit: + type: Unit + optional: true + prefix: {value: " "} + - statementblock: + type: StatementBlock + - ProcedureBlock: + nmodl: "PROCEDURE " + members: + - name: + type: Name + getname: true + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + - unit: + type: Unit + optional: true + - statementblock: + type: StatementBlock + - NetReceiveBlock: + nmodl: "NET_RECEIVE " + members: + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + - statementblock: + type: StatementBlock + - SolveBlock: + nmodl: SOLVE + members: + - name: + type: Name + prefix: {value: " "} + - method: + type: Name + optional: true + prefix: {value: " METHOD "} + - ifsolerr: + type: StatementBlock + prefix: {value: " IFERROR"} + - BreakpointBlock: + nmodl: BREAKPOINT + members: + - statementblock: + type: StatementBlock + - TerminalBlock: + nmodl: TERMINAL + members: + - statementblock: + type: StatementBlock + - BeforeBlock: + nmodl: "BEFORE " + members: + - block: + type: BABlock + - AfterBlock: + nmodl: "AFTER " + members: + - block: + type: BABlock + - BABlock: + members: + - type: + type: BABlockType + - statementblock: + type: StatementBlock + - ForNetcon: + nmodl: "FOR_NETCONS " + members: + - arguments: + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + - statementblock: + type: StatementBlock + - KineticBlock: + nmodl: "KINETIC " + members: + - name: + type: Name + getname: true + - solvefor: + type: Name + vector: true + separator: "," + - statementblock: + type: StatementBlock + - MatchBlock: + nmodl: MATCH + members: + - matchs: + type: Match + vector: true + separator: " " + prefix: {value: " { "} + suffix: {value: " }"} + - UnitBlock: + nmodl: UNITS + members: + - definitions: + type: Expression + vector: true + - ConstantBlock: + nmodl: CONSTANT + members: + - statements: + type: ConstantStatement + vector: true + - NeuronBlock: + nmodl: NEURON + members: + - statementblock: + type: StatementBlock + - Unit: members: - - value: - type: int - - Identifier: - - Name: - members: - - value: + - name: type: String getname: true - - PrimeName: + prefix: {value: "("} + suffix: {value: ")"} + - DoubleUnit: members: - - value: - type: String - getname: true - - order: - type: Integer - - VarName: + - values: + type: Double + - unit: + type: Unit + optional: true + - LocalVar: members: - name: type: Identifier getname: true - - at_index: - type: Integer - optional: true - prefix: {value: "@"} - - IndexedName: + - Limits: + members: + - min: + type: Double + prefix: {value: "<"} + suffix: {value: ","} + - max: + type: Double + suffix: {value: ">"} + - NumberRange: + members: + - min: + type: Number + prefix: {value: "<"} + suffix: {value: ","} + - max: + type: Number + suffix: {value: ">"} + + - PlotVar: members: - name: type: Identifier - getname: true - index: - type: Expression + type: Integer + optional: true prefix: {value: "["} suffix: {value: "]"} - - Argument: + - BinaryOperator: members: - - name: - type: Identifier - getname: true - - unit: - type: Unit - optional: true - - ReactVarName: + - value: + type: BinaryOp + - UnaryOperator: members: - value: - type: Integer - optional: true - prefix: {value: " "} - - name: - type: VarName - getname: true - - ReadIonVar: + type: UnaryOp + - ReactionOperator: members: - - name: - type: Name - getname: true - - WriteIonVar: + - value: + type: ReactionOp + - ParenExpression: members: - - name: - type: Name - getname: true - - NonspeCurVar: + - expr: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - BinaryExpression: + members: + - lhs: + type: Expression + - op: + type: BinaryOperator + - rhs: + type: Expression + - UnaryExpression: + members: + - op: + type: UnaryOperator + - expression: + type: Expression + - NonLinEuation: + nmodl: "~ " + members: + - lhs: + type: Expression + suffix: {value: " = "} + - rhs: + type: Expression + - LinEquation: + nmodl: "~ " + members: + - leftlinexpr: + type: Expression + suffix: {value: " = "} + - linexpr: + type: Expression + - FunctionCall: members: - name: type: Name - getname: true - - ElectrodeCurVar: + - arguments: + type: Expression + vector: true + separator: ", " + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + - FirstLastTypeIndex: + members: + - value: + type: FirstLastType + - Watch: + members: + - expression: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - value: + type: Expression + prefix: {value: " "} + - QueueExpressionType: + members: + - value: + type: QueueType + - Match: members: - name: - type: Name + type: Identifier + - expression: + type: Expression + optional: true + - BABlockType: + members: + - value: + type: BAType + - UnitDef: + members: + - unit1: + type: Unit getname: true - - SectionVar: + - unit2: + type: Unit + prefix: {value: " = "} + - FactorDef: members: - name: type: Name getname: true - - RangeVar: + suffix: {value: " ="} + - value: + type: Double + optional: true + prefix: {value: " "} + - unit1: + type: Unit + prefix: {value: " "} + - gt: + type: Boolean + nmodl: " ->" + optional: true + - unit2: + type: Unit + optional: true + prefix: {value: " "} + - Valence: members: - - name: + - type: type: Name - getname: true - - GlobalVar: + prefix: {value: " "} + suffix: {value: " "} + - value: + type: Double + + - Statement: + - UnitState: + members: + - value: + type: UnitStateType + - LocalListStatement: + nmodl: "LOCAL " + members: + - variables: + type: LocalVar + vector: true + separator: ", " + - Model: + nmodl: TITLE + members: + - title: + type: String + - Define: + nmodl: "DEFINE " members: - name: type: Name - getname: true - - PointerVar: + - value: + type: Integer + prefix: {value: " "} + - Include: + nmodl: "INCLUDE " + members: + - filename: + type: String + - ParamAssign: members: - name: - type: Name + type: Identifier getname: true - - BbcorePointerVar: + - value: + type: Number + optional: true + prefix: {value: " = "} + - unit: + type: Unit + optional: true + prefix: {value: " "} + - limit: + type: Limits + optional: true + prefix: {value: " "} + - Stepped: members: - name: type: Name - getname: true - - ExternVar: + - values: + type: Number + vector: true + prefix: {value: " = "} + separator: ", " + - unit: + type: Unit + optional: true + prefix: {value: " "} + - IndependentDef: members: + - sweep: + type: Boolean + optional: true + nmodl: "SWEEP " - name: type: Name - getname: true - - ThreadsafeVar: + - from: + type: Number + prefix: {value: " FROM "} + - to: + type: Number + prefix: {value: " TO "} + - with: + type: Integer + prefix: {value: " WITH "} + - opstart: + type: Number + prefix: {value: " START "} + optional: true + - unit: + type: Unit + optional: true + prefix: {value: " "} + - DependentDef: members: - name: - type: Name + type: Identifier getname: true - - Block: - - ParamBlock: - nmodl: PARAMETER - members: - - statements: - type: ParamAssign - vector: true - - StepBlock: - nmodl: STEPPED - members: - - statements: - type: Stepped - vector: true - - IndependentBlock: - nmodl: INDEPENDENT + - index: + type: Integer + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - from: + type: Number + prefix: {value: " FROM "} + optional: true + - to: + type: Number + prefix: {value: " TO "} + optional: true + - opstart: + type: Number + prefix: {value: " START "} + optional: true + - unit: + type: Unit + optional: true + prefix: {value: " "} + - abstol: + type: Double + prefix: {value: " <"} + suffix: {value: ">"} + optional: true + - PlotDeclaration: + nmodl: "PLOT " members: - - definitions: - type: IndependentDef + - pvlist: + type: PlotVar vector: true - - DependentBlock: - nmodl: ASSIGNED + separator: ", " + - name: + type: PlotVar + prefix: {value: " VS "} + - ConductanceHint: + nmodl: "CONDUCTANCE " members: - - definitions: - type: DependentDef - vector: true - - StateBlock: - nmodl: STATE + - conductance: + type: Name + - ion: + type: Name + optional: true + prefix: {value: " USEION "} + - ExpressionStatement: members: - - definitions: - type: DependentDef - vector: true - - PlotBlock: + - expression: + type: Expression + - ProtectStatement: + nmodl: "PROTECT " members: - - plot: - type: PlotDeclaration - - InitialBlock: - nmodl: INITIAL + - expression: + type: Expression + - FromStatement: + nmodl: "FROM " members: + - name: + type: Name + - from: + type: Expression + prefix: {value: " = "} + - to: + type: Expression + prefix: {value: " TO "} + - opinc: + type: Expression + prefix: {value: " BY "} + optional: true - statementblock: type: StatementBlock - - ConstructorBlock: - nmodl: CONSTRUCTOR + - ForAllStatement: + nmodl: "FORALL " members: + - name: + type: Name - statementblock: type: StatementBlock - - DestructorBlock: - nmodl: DESTRUCTOR + - WhileStatement: + nmodl: "WHILE " members: + - condition: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} - statementblock: type: StatementBlock - - StatementBlock: + - IfStatement: + nmodl: "IF " members: - - statements: - type: Statement + - condition: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - statementblock: + type: StatementBlock + - elseifs: + type: ElseIfStatement vector: true - - DerivativeBlock: - nmodl: DERIVATIVE + - elses: + type: ElseStatement + optional: true + - ElseIfStatement: + nmodl: " ELSE IF " members: - - name: - type: Name - getname: true + - condition: + type: Expression + prefix: {value: "("} + suffix: {value: ")"} - statementblock: type: StatementBlock - - LinearBlock: - nmodl: LINEAR + - ElseStatement: + nmodl: " ELSE" members: - - name: - type: Name - getname: true - - solvefor: - type: Name - vector: true - prefix: {value: " SOLVEFOR "} - statementblock: type: StatementBlock - - NonLinearBlock: - nmodl: NONLINEAR + - PartialEquation: members: - - name: + - prime: + type: PrimeName + - name1: type: Name - getname: true - - solvefor: + - name2: type: Name - vector: true - prefix: {value: " SOLVEFOR "} - - statementblock: - type: StatementBlock - - DiscreteBlock: - nmodl: DISCRETE + - name3: + type: Name + - PartialBoundary: + nmodl: "~ " members: + - del: + type: Name + optional: true + suffix: {value: " "} - name: + type: Identifier + - index: + type: FirstLastTypeIndex + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - expression: + type: Expression + optional: true + prefix: {value: " = "} + - name1: type: Name - getname: true - - statementblock: - type: StatementBlock - - PartialBlock: - nmodl: PARTIAL + optional: true + prefix: {value: " = "} + suffix: {value: "*"} + - del2: + type: Name + optional: true + suffix: {value: "("} + - name2: + type: Name + optional: true + suffix: {value: ")"} + - name3: + type: Name + optional: true + prefix: {value: "+"} + - WatchStatement: + nmodl: "WATCH " + members: + - statements: + type: Watch + vector: true + separator: "," + add: true + - MutexLock: + nmodl: MUTEXLOCK + - MutexUnlock: + nmodl: MUTEXUNLOCK + - Reset: + nmodl: RESET + - Sens: + nmodl: "SENS " + members: + - senslist: + type: VarName + vector: true + separator: ", " + - Conserve: + nmodl: CONSERVE + members: + - react: + type: Expression + prefix: {value: " "} + - expr: + type: Expression + prefix: {value: " = "} + - Compartment: + nmodl: COMPARTMENT members: - name: type: Name - getname: true - - statementblock: - type: StatementBlock - - FunctionTableBlock: - nmodl: FUNCTION_TABLE + optional: true + prefix: {value: " "} + suffix: {value: ","} + - expression: + type: Expression + prefix: {value: " "} + - names: + type: Name + vector: true + prefix: {value: " {"} + suffix: {value: "}"} + separator: " " + - LonDifuse: + nmodl: LONGITUDINAL_DIFFUSION members: - name: type: Name - getname: true - - arguments: - type: Argument + optional: true + prefix: {value: " "} + suffix: {value: ","} + - expression: + type: Expression + prefix: {value: " "} + - names: + type: Name vector: true - prefix: {value: "(", force: true} + prefix: {value: " {"} + suffix: {value: "}"} + separator: " " + - ReactionStatement: + nmodl: "~ " + members: + - react1: + type: Expression + - op: + type: ReactionOperator + prefix: {value: " "} + - react2: + type: Expression + prefix: {value: " "} + optional: true + - expr1: + type: Expression + prefix: {value: " ("} + - expr2: + type: Expression + prefix: {value: ", "} suffix: {value: ")", force: true} - separator: "," - - unit: - type: Unit optional: true - - FunctionBlock: - nmodl: FUNCTION + - LagStatement: + nmodl: "LAG " members: - name: + type: Identifier + - byname: type: Name - getname: true - - arguments: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - separator: "," - - unit: - type: Unit - optional: true - - statementblock: - type: StatementBlock - - ProcedureBlock: - nmodl: PROCEDURE + prefix: {value: " BY "} + - QueueStatement: + members: + - qype: + type: QueueExpressionType + - name: + type: Identifier + prefix: {value: " "} + - ConstantStatement: members: - name: type: Name - getname: true - - arguments: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - separator: "," + - value: + type: Number + prefix: {value: " = "} - unit: type: Unit optional: true - - statementblock: - type: StatementBlock - - NetReceiveBlock: - nmodl: NET_RECEIVE + prefix: {value: " "} + - TableStatement: + nmodl: "TABLE " members: - - arguments: - type: Argument + - tablst: + type: Name vector: true - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} separator: "," - - statementblock: - type: StatementBlock - - SolveBlock: - nmodl: SOLVE + - dependlst: + type: Name + vector: true + prefix: {value: " DEPEND "} + separator: "," + - from: + type: Expression + prefix: {value: " FROM "} + - to: + type: Expression + prefix: {value: " TO "} + - with: + type: Integer + prefix: {value: " WITH "} + - Suffix: members: + - type: + type: Name + suffix: {value: " "} - name: type: Name - - method: + - Useion: + nmodl: "USEION " + members: + - name: type: Name + - readlist: + type: ReadIonVar + vector: true + prefix: {value: " READ "} + separator: ", " + - writelist: + type: WriteIonVar + vector: true + prefix: {value: " WRITE "} + separator: ", " + - valence: + type: Valence optional: true - prefix: {value: " METHOD "} - - ifsolerr: - type: StatementBlock - prefix: {value: IFERROR} - - BreakpointBlock: - nmodl: BREAKPOINT + - Nonspecific: + nmodl: "NONSPECIFIC_CURRENT " members: - - statementblock: - type: StatementBlock - - TerminalBlock: - nmodl: TERMINAL - members: - - statementblock: - type: StatementBlock - - BeforeBlock: - nmodl: BEFORE + - currents: + type: NonspeCurVar + vector: true + separator: ", " + - ElctrodeCurrent: + nmodl: "ELECTRODE_CURRENT " members: - - block: - type: BABlock - - AfterBlock: - nmodl: AFTER + - ecurrents: + type: ElectrodeCurVar + vector: true + separator: ", " + - Section: + nmodl: "SECTION " members: - - block: - type: BABlock - - BABlock: + - sections: + type: SectionVar + vector: true + separator: ", " + - Range: + nmodl: "RANGE " members: - - type: - type: BABlockType - - statementblock: - type: StatementBlock - - ForNetcon: - nmodl: FOR_NETCONS + - range_vars: + type: RangeVar + vector: true + separator: ", " + - Global: + nmodl: "GLOBAL " members: - - arguments: - type: Argument + - global_vars: + type: GlobalVar vector: true - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - separator: "," - - statementblock: - type: StatementBlock - - KineticBlock: - nmodl: KINETIC + separator: ", " + - Pointer: + nmodl: "POINTER " members: - - name: - type: Name - getname: true - - solvefor: - type: Name + - pointers: + type: PointerVar vector: true - - statementblock: - type: StatementBlock - - MatchBlock: - nmodl: MATCH + separator: ", " + - BbcorePtr: + nmodl: "BBCOREPOINTER " members: - - matchs: - type: Match + - bbcore_pointers: + type: BbcorePointerVar vector: true - - UnitBlock: - nmodl: UNITS + separator: ", " + - External: + nmodl: "EXTERNAL " members: - - definitions: - type: Expression + - externals: + type: ExternVar vector: true - - ConstantBlock: - nmodl: CONSTANT + separator: ", " + - ThreadSafe: + nmodl: THREADSAFE members: - - statements: - type: ConstantStatement + - threadsafe: + type: ThreadsafeVar vector: true - - NeuronBlock: - nmodl: NEURON + separator: ", " + prefix: {value: " "} + - Verbatim: + nmodl: VERBATIM members: - - statementblock: - type: StatementBlock - - Unit: - members: - - name: - type: String - getname: true - prefix: {value: "("} - suffix: {value: ")"} - - DoubleUnit: - members: - - values: - type: Double - - unit: - type: Unit - optional: true - - LocalVar: - members: - - name: - type: Identifier - getname: true - - Limits: - members: - - min: - type: Double - prefix: {value: "<"} - suffix: {value: ","} - - max: - type: Double - suffix: {value: ">"} - - NumberRange: - members: - - min: - type: Number - prefix: {value: "<"} - suffix: {value: ","} - - max: - type: Number - suffix: {value: ">"} - - - PlotVar: - members: - - name: - type: Identifier - prefix: {value: " "} - - index: - type: Integer - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - BinaryOperator: - members: - - value: - type: BinaryOp - - UnaryOperator: - members: - - value: - type: UnaryOp - - ReactionOperator: - members: - - value: - type: ReactionOp - - BinaryExpression: - members: - - lhs: - type: Expression - - op: - type: BinaryOperator - - rhs: - type: Expression - - UnaryExpression: - members: - - op: - type: UnaryOperator - - expression: - type: Expression - - NonLinEuation: - nmodl: "~" - members: - - lhs: - type: Expression - suffix: {value: "="} - - rhs: - type: Expression - - LinEquation: - nmodl: "~" - members: - - leftlinexpr: - type: Expression - suffix: {value: "="} - - linexpr: - type: Expression - - FunctionCall: - members: - - name: - type: Name - - arguments: - type: Expression - vector: true - separator: "," - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - - FirstLastTypeIndex: - members: - - value: - type: FirstLastType - - Watch: - members: - - expression: - type: Expression - - value: - type: Expression - prefix: {value: " "} - - QueueExpressionType: - members: - - value: - type: QueueType - - Match: - members: - - name: - type: Identifier - - expression: - type: Expression - optional: true - - BABlockType: - members: - - value: - type: BAType - - UnitDef: - members: - - unit1: - type: Unit - getname: true - - unit2: - type: Unit - prefix: {value: "="} - - FactorDef: - members: - - name: - type: Name - getname: true - suffix: {value: "="} - - value: - type: Double - optional: true - - unit1: - type: Unit - - gt: - type: Boolean - nmodl: "->" - optional: true - - unit2: - type: Unit - optional: true - - Valence: - members: - - type: - type: Name - prefix: {value: " "} - suffix: {value: " "} - - value: - type: Double - - - Statement: - - UnitState: - members: - - value: - type: UnitStateType - - LocalListStatement: - nmodl: LOCAL - members: - - variables: - type: LocalVar - vector: true - separator: "," - - Model: - nmodl: TITLE - members: - - title: - type: String - - Define: - nmodl: DEFINE - members: - - name: - type: Name - suffix: {value: " "} - - value: - type: Integer - - Include: - nmodl: INCLUDE - members: - - filename: - type: String - - ParamAssign: - members: - - name: - type: Identifier - getname: true - - value: - type: Number - optional: true - prefix: {value: "="} - - unit: - type: Unit - optional: true - - limit: - type: Limits - optional: true - - Stepped: - members: - - name: - type: Name - - values: - type: Number - vector: true - prefix: {value: "="} - separator: "," - - unit: - type: Unit - - IndependentDef: - members: - - sweep: - type: Boolean - optional: true - - name: - type: Name - - from: - type: Number - prefix: {value: " FROM "} - - to: - type: Number - prefix: {value: " TO "} - - with: - type: Integer - prefix: {value: " WITH "} - - opstart: - type: Number - prefix: {value: " START "} - optional: true - - unit: - type: Unit - - DependentDef: - members: - - name: - type: Identifier - getname: true - suffix: {value: " "} - - index: - type: Integer - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - from: - type: Number - prefix: {value: " FROM "} - optional: true - - to: - type: Number - prefix: {value: " TO "} - optional: true - - opstart: - type: Number - prefix: {value: " START "} - suffix: {value: " "} - optional: true - - unit: - type: Unit - optional: true - - abstol: - type: Double - prefix: {value: " <"} - suffix: {value: "> "} - optional: true - - PlotDeclaration: - nmodl: "PLOT " - members: - - pvlist: - type: PlotVar - vector: true - separator: "," - - name: - type: PlotVar - prefix: {value: " VS "} - - ConductanceHint: - nmodl: "CONDUCTANCE" - members: - - conductance: - type: Name - - ion: - type: Name - optional: true - prefix: {value: " USEION "} - - ExpressionStatement: - members: - - expression: - type: Expression - - ProtectStatement: - nmodl: PROTECT - members: - - expression: - type: Expression - - FromStatement: - nmodl: FROM - members: - - name: - type: Name - prefix: {value: " "} - suffix: {value: " "} - - from: - type: Expression - prefix: {value: "="} - - to: - type: Expression - prefix: {value: " TO "} - - opinc: - type: Expression - prefix: {value: " BY "} - optional: true - - statementblock: - type: StatementBlock - - ForAllStatement: - nmodl: FORALL - members: - - name: - type: Name - prefix: {value: " "} - suffix: {value: " "} - - statementblock: - type: StatementBlock - - WhileStatement: - nmodl: WHILE - members: - - condition: - type: Expression - prefix: {value: "("} - suffix: {value: ")"} - - statementblock: - type: StatementBlock - - IfStatement: - nmodl: IF - members: - - condition: - type: Expression - prefix: {value: "("} - suffix: {value: ")"} - - statementblock: - type: StatementBlock - - elseifs: - type: ElseIfStatement - vector: true - - elses: - type: ElseStatement - optional: true - - ElseIfStatement: - nmodl: ELSE IF - members: - - condition: - type: Expression - prefix: {value: "("} - suffix: {value: ")"} - - statementblock: - type: StatementBlock - - ElseStatement: - nmodl: ELSE - members: - - statementblock: - type: StatementBlock - - PartialEquation: - members: - - prime: - type: PrimeName - - name1: - type: Name - - name2: - type: Name - - name3: - type: Name - - PartialBoundary: - nmodl: "~" - members: - - del: - type: Name - optional: true - suffix: {value: " "} - - name: - type: Identifier - - index: - type: FirstLastTypeIndex - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - expression: - type: Expression - optional: true - prefix: {value: "="} - - name1: - type: Name - optional: true - prefix: {value: "="} - suffix: {value: "*"} - - del2: - type: Name - optional: true - suffix: {value: "("} - - name2: - type: Name - optional: true - suffix: {value: ")"} - - name3: - type: Name - optional: true - prefix: {value: "+"} - - WatchStatement: - nmodl: WATCH - members: - - statements: - type: Watch - vector: true - separator: "," - add: true - - MutexLock: - nmodl: MUTEXLOCK - - MutexUnlock: - nmodl: MUTEXUNLOCK - - Reset: - nmodl: RESET - - Sens: - nmodl: SENS - members: - - senslist: - type: VarName - vector: true - separator: "," - - Conserve: - nmodl: CONSERVE - members: - - react: - type: Expression - - expr: - type: Expression - prefix: {value: "="} - - Compartment: - nmodl: COMPARTMENT - members: - - name: - type: Name - optional: true - suffix: {value: ","} - - expression: - type: Expression - - names: - type: Name - vector: true - prefix: {value: "{"} - suffix: {value: "}"} - separator: " " - - LonDifuse: - nmodl: LONGITUDINAL_DIFFUSION - members: - - name: - type: Name - optional: true - suffix: {value: ","} - - expression: - type: Expression - - names: - type: Name - vector: true - prefix: {value: "{"} - suffix: {value: "}"} - separator: " " - - ReactionStatement: - nmodl: "~" - members: - - react1: - type: Expression - suffix: {value: " "} - - op: - type: ReactionOperator - prefix: {value: " "} - suffix: {value: " "} - - react2: - type: Expression - prefix: {value: " "} - suffix: {value: " "} - optional: true - - expr1: - type: Expression - prefix: {value: "("} - - expr2: - type: Expression - prefix: {value: ","} - suffix: {value: ")", force: true} - optional: true - - LagStatement: - nmodl: LAG - members: - - name: - type: Identifier - - byname: - type: Name - prefix: {value: " BY "} - - QueueStatement: - members: - - qype: - type: QueueExpressionType - - name: - type: Identifier - - ConstantStatement: - members: - - name: - type: Name - - value: - type: Number - prefix: {value: "="} - - unit: - type: Unit - optional: true - - TableStatement: - nmodl: TABLE - members: - - tablst: - type: Name - vector: true - separator: "," - - dependlst: - type: Name - vector: true - prefix: {value: " DEPEND "} - separator: "," - - from: - type: Expression - prefix: {value: " FROM "} - - to: - type: Expression - prefix: {value: " TO "} - - with: - type: Integer - prefix: {value: " WITH "} - - Suffix: - members: - - type: - type: Name - suffix: {value: " "} - - name: - type: Name - - Useion: - nmodl: USEION - members: - - name: - type: Name - - readlist: - type: ReadIonVar - vector: true - prefix: {value: " READ "} - separator: "," - - writelist: - type: WriteIonVar - vector: true - prefix: {value: " WRITE "} - separator: "," - - valence: - type: Valence - optional: true - - Nonspecific: - nmodl: NONSPECIFIC_CURRENT - members: - - currents: - type: NonspeCurVar - vector: true - separator: "," - - ElctrodeCurrent: - nmodl: ELECTRODE_CURRENT - members: - - ecurrents: - type: ElectrodeCurVar - vector: true - separator: "," - - Section: - nmodl: SECTION - members: - - sections: - type: SectionVar - vector: true - separator: "," - - Range: - nmodl: RANGE - members: - - range_vars: - type: RangeVar - vector: true - separator: "," - - Global: - nmodl: GLOBAL - members: - - global_vars: - type: GlobalVar - vector: true - separator: "," - - Pointer: - nmodl: POINTER - members: - - pointers: - type: PointerVar - vector: true - separator: "," - - BbcorePtr: - nmodl: BBCOREPOINTER - members: - - bbcore_pointers: - type: BbcorePointerVar - vector: true - separator: "," - - External: - nmodl: EXTERNAL - members: - - externals: - type: ExternVar - vector: true - separator: "," - - ThreadSafe: - nmodl: THREADSAFE - members: - - threadsafe: - type: ThreadsafeVar - vector: true - separator: "," - - Verbatim: - nmodl: VERBATIM - members: - - statement: - type: String - suffix: {value: "ENDVERBATIM"} - - Comment: - nmodl: COMMENT - members: - - comment: - type: String - suffix: {value: "ENDCOMMENT"} + - statement: + type: String + suffix: {value: "ENDVERBATIM"} + - Comment: + nmodl: COMMENT + members: + - comment: + type: String + suffix: {value: "ENDCOMMENT"} - Program: members: - - statements: - type: Statement - vector: true - add: true - blocks: - type: Block + type: Node vector: true add: true diff --git a/src/nmodl/language/nmodl_printer.py b/src/nmodl/language/nmodl_printer.py new file mode 100644 index 0000000000..8b26fcc49a --- /dev/null +++ b/src/nmodl/language/nmodl_printer.py @@ -0,0 +1,163 @@ +from printer import * +from utils import * +from node_info import ORDER_VAR_NAME, BINARY_OPERATOR_NAME, BINARY_EXPRESSION_NODE + +class NmodlVisitorDeclarationPrinter(DeclarationPrinter): + """Visitor class declaration for printing AST back to NMODL""" + + def headers(self): + self.write_line('#include "ast/ast.hpp"') + self.write_line('#include "printer/nmodl_printer.hpp"', newline=2) + + def class_comment(self): + self.write_line("/* Visitor for printing AST back to NMODL */") + + def class_name_declaration(self): + self.write_line("class " + self.classname + " : public ast::Visitor {") + + def private_declaration(self): + self.write_line("private:") + self.write_line(" std::unique_ptr<NMODLPrinter> printer;") + + def public_declaration(self): + self.write_line("public:", post_gutter=1) + line = self.classname + "() : printer(new NMODLPrinter()) {}" + self.write_line(line) + + line = self.classname + "(std::string filename) : printer(new NMODLPrinter(filename)) {}" + self.write_line(line) + + line = self.classname + "(std::stringstream& stream) : printer(new NMODLPrinter(stream)) {}" + self.write_line(line, newline=2) + + for node in self.nodes: + line = "virtual void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" + self.write_line(line) + + line = "template<typename T>" + self.write_line(line) + line = "void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement);" + self.write_line(line) + + +class NmodlVisitorDefinitionPrinter(DefinitionPrinter): + """Print visitor class definitions for printing AST back to NMODL""" + + def headers(self): + self.write_line('#include "visitors/nmodl_visitor.hpp"') + self.write_line('#include "visitors/nmodl_visitor_helper.hpp"', newline=2) + self.write_line("using namespace ast;", newline=2) + + def add_element(self, name): + if name: + name = '"' + name + '"' + self.write_line("printer->add_element(" + name + ");") + + def definitions(self): + for node in self.nodes: + + self.write_line("/* concrete visit function for " + node.class_name + " nodes */") + self.write_line("void NmodlPrintVisitor::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {", post_gutter=1) + + if node.nmodl_name: + name = '"' + node.nmodl_name + '"' + self.write_line("printer->add_element(" + name + ");") + + self.add_element(node.prefix) + + if node.is_block_node(): + self.write_line("printer->push_level();") + + # for basic data types we just have to eval and they will return their value + # but for integer node we have to check if it is represented as macro + if node.is_data_type_node(): + if node.is_integer_node(): + self.write_line("if(node->macroname == nullptr) {") + self.write_line(" printer->add_element(std::to_string(node->eval()));") + self.write_line("}") + else: + self.write_line("std::stringstream ss;") + self.write_line("ss << node->eval();") + self.write_line("printer->add_element(ss.str());") + + for child in node.children: + self.add_element(child.force_prefix) + + is_program = "false" + is_statement = "false" + + # unit block has expressions as children and hence need to be considered as statements + if child.is_statement_node() or node.is_unit_block(): + is_statement = "true" + + if node.is_program_node(): + is_program = "true" + + # In the current implementation all childrens are non-base-data-type nodes + # i.e. int, double are wrapped into Integer, Double classes. Hence it is + # not tested in non data types nodes. This restriction could be easily avoided + # if required. + if child.is_base_type_node(): + if not node.is_data_type_node(): + raise "Base data type members not in non data type nodes handled/tested!" + else: + self.write_line("// Processing " + child.class_name + " " + child.varname) + + # optional members or statement blocks are in brace (could be nullptr) + # start of a brace + if child.optional or child.is_statement_block_node(): + line = 'if(node->' + child.varname + ') {' + self.write_line(line, post_gutter=1) + + # todo : assugming member with nmodl of type Boolean. currently there + # are only two use cases: SWEEP and "->"" + if child.nmodl_name: + self.write_line('if(' + 'node->' + child.varname + '->eval()) {') + name = '"' + child.nmodl_name + '"' + self.write_line(" printer->add_element(" + name + ");") + self.write_line('}') + + # vector members could be empty and pre-defined method to iterate/visit + elif child.is_vector: + if child.prefix or child.suffix: + self.write_line("if (!node->" + child.varname + ".empty()) {", post_gutter=1) + self.add_element(child.prefix) + separator = '"' + child.separator + '"' + self.write_line("visit_element(node->" + child.varname + "," + separator + "," + is_program + "," + is_statement + ");"); + self.add_element(child.suffix) + if child.prefix or child.suffix: + self.write_line("}", pre_gutter=-1) + + # prime need to be printed with "'" as suffix + elif node.is_prime_node() and child.varname == ORDER_VAR_NAME: + self.write_line("auto order = node->" + child.varname + "->eval();") + self.write_line("auto symbol = std::string(order, '\\'');") + self.write_line("printer->add_element(symbol);"); + + # add space surrounding certain binary operators for readability + elif node.class_name == BINARY_EXPRESSION_NODE and child.varname == BINARY_OPERATOR_NAME: + self.write_line('std::string op = node->op.eval();') + self.write_line('if(op == "=" || op == "&&" || op == "||" || op == "==")') + self.write_line(' op = " " + op + " ";') + self.write_line('printer->add_element(op);') + + else: + self.add_element(child.prefix) + if child.is_pointer_node(): + self.write_line("node->" + child.varname + "->accept(this);") + else: + self.write_line("node->" + child.varname + ".accept(this);") + self.add_element(child.suffix) + + # end of a brace + if child.optional or child.is_statement_block_node(): + self.write_line("}", pre_gutter=-1) + + self.add_element(child.force_suffix) + + self.add_element(node.suffix) + + if node.is_block_node(): + self.write_line("printer->pop_level();") + + self.write_line("}", pre_gutter=-1, newline=2) diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 7e59cf3b35..1818ff4b35 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -93,7 +93,8 @@ "IndependentDef", "DependentDef", "ParamAssign", - "ConstantStatement"] + "ConstantStatement", + "Stepped"] # data types which have token as an argument to the constructor LEXER_DATA_TYPES = ["Name", @@ -145,8 +146,5 @@ # name of variable in prime node which represent order of derivative ORDER_VAR_NAME = "order" -REACT_VAR_NAME = "react" -REACT2_VAR_NAME = "react2" -SWEEP_VAR_NAME = "sweep" - +BINARY_OPERATOR_NAME = "op" # yapf: enable diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 5809148efc..76655d10b9 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -77,21 +77,29 @@ def parse_child_rule(self, child): if 'getname' in properties: args.getname_method = properties['getname'] + # if there is nmodl name + if 'nmodl' in properties: + args.nmodl_name = properties['nmodl'] + # prefix while printing back to NMODL if 'prefix' in properties: args.prefix = properties['prefix']['value'] - # if prefix is compulsory to print in NMODL + # if prefix is compulsory to print in NMODL then make suffix empty if 'force' in properties['prefix']: - args.force_prefix = properties['prefix']['force'] + if properties['prefix']['force']: + args.force_prefix = args.prefix + args.prefix = "" # suffix while printing back to NMODL if 'suffix' in properties: args.suffix = properties['suffix']['value'] - # if suffix is compulsory to print in NMODL + # if suffix is compulsory to print in NMODL then make suffix empty if 'force' in properties['suffix']: - args.force_suffix = properties['suffix']['force'] + if properties['suffix']['force']: + args.force_suffix = args.suffix + args.suffix = "" return args diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 72d7cfe3af..e47961a476 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -132,7 +132,8 @@ %token <ModToken> GE %token <ModToken> PLOT %token <ModToken> VS -%token <ModToken> LAG RESET +%token <ModToken> LAG +%token <ModToken> RESET %token <ModToken> MATCH %token <ModToken> MODEL_LEVEL %token <ModToken> SWEEP @@ -406,22 +407,22 @@ all : { } | all model { - $1->addStatement($2); + $1->addNode($2); $$ = $1; } | all locallist { - $1->addStatement($2); + $1->addNode($2); $$ = $1; } | all define1 { - $1->addStatement($2); + $1->addNode($2); $$ = $1; } | all declare { - $1->addBlock($2); + $1->addNode($2); $$ = $1; } | all MODEL_LEVEL INTEGER declare @@ -433,31 +434,32 @@ all : { } | all proc { - $1->addBlock($2); + $1->addNode($2); $$ = $1; } | all VERBATIM { auto text = parse_with_verbatim_parser($2); auto statement = new ast::Verbatim(new ast::String(text)); - $1->addStatement(statement); + $1->addNode(statement); $$ = $1; } | all COMMENT { auto text = parse_with_verbatim_parser($2); auto statement = new ast::Comment(new ast::String(text)); - $1->addStatement(statement); + $1->addNode(statement); $$ = $1; } | all uniton { - $1->addStatement($2); + $1->addNode($2); $$ = $1; } | all INCLUDE1 STRING { - $1->addStatement(new ast::Include($3)); + auto statement = new ast::Include($3); + $1->addNode(statement); $$ = $1; } ; @@ -545,7 +547,12 @@ units : { $$ = nullptr; } unit : "(" { scanner.scan_unit(); } ")" { - $$ = new ast::Unit(scanner.get_unit()); + auto unit = scanner.get_unit(); + auto text = unit->eval(); + if (text.size() == 0) { + error(scanner.loc, "empty unit"); + } + $$ = new ast::Unit(unit); } ; @@ -988,26 +995,26 @@ asgn : varname "=" expr varname : name { - $$ = new ast::VarName($1, NULL); + $$ = new ast::VarName($1, nullptr, nullptr); } | name "[" intexpr "]" { - $$ = new ast::VarName(new ast::IndexedName($1, $3), NULL); + $$ = new ast::VarName(new ast::IndexedName($1, $3), nullptr, nullptr); } | NAME "@" integer { - $$ = new ast::VarName($1, $3); + $$ = new ast::VarName($1, $3, nullptr); } | NAME "@" integer "[" intexpr "]" { - $$ = new ast::VarName(new ast::IndexedName($1, $5), $3); + $$ = new ast::VarName($1, $3, $5); } ; intexpr : Name { $$ = $1; } | integer { $$ = $1; } - | "(" intexpr ")" { $$ = $2; } + | "(" intexpr ")" { $$ = new ast::ParenExpression($2); } | intexpr "+" intexpr { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); @@ -1040,7 +1047,7 @@ expr : varname { $$ = $1; } $$ = $1; } | funccall { $$ = $1; } - | "(" expr ")" { $$ = $2; } + | "(" expr ")" { $$ = new ast::ParenExpression($2); } | expr "+" expr { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); @@ -1157,7 +1164,7 @@ primary : term { $$ = $1; } term : varname { $$ = $1; } | real { $$ = $1; } | funccall { $$ = $1; } - | "(" expr ")" { $$ = $2; } + | "(" expr ")" { $$ = new ast::ParenExpression($2); } | error { error(scanner.loc, "term"); @@ -1520,7 +1527,7 @@ aexpr : varname { $$ = $1; } $$ = new ast::DoubleUnit($1, $2); } | funccall { $$ = $1; } - | "(" aexpr ")" { $$ = $2; } + | "(" aexpr ")" { $$ = new ast::ParenExpression($2); } | aexpr "+" aexpr { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); @@ -1720,7 +1727,9 @@ match : name | matchname "(" expr ")" "=" expr { auto op = ast::BinaryOperator(ast::BOP_ASSIGN); - auto expr = new ast::BinaryExpression($3, op, $6); + auto lhs = new ast::ParenExpression($3); + auto rhs = $6; + auto expr = new ast::BinaryExpression(lhs, op, rhs); $$ = new ast::Match($1, expr); } | error diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 0666abc151..5de2055f06 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -4,6 +4,8 @@ set(PRINTER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp ) #============================================================================= diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp new file mode 100644 index 0000000000..c879213aac --- /dev/null +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -0,0 +1,42 @@ +#include "printer/nmodl_printer.hpp" +#include "utils/string_utils.hpp" + +NMODLPrinter::NMODLPrinter(const std::string& filename) { + if (filename.empty()) { + throw std::runtime_error("Empty filename for NMODLPrinter"); + } + + ofs.open(filename.c_str()); + + if (ofs.fail()) { + auto msg = "Error while opening file '" + filename + "' for NMODLPrinter"; + throw std::runtime_error(msg); + } + + sbuf = ofs.rdbuf(); + result = std::make_shared<std::ostream>(sbuf); +} + +void NMODLPrinter::push_level() { + indent_level++; + *result << " {"; + add_newline(); +} + +void NMODLPrinter::add_indent() { + *result << std::string(indent_level * 4, ' '); +} + +void NMODLPrinter::add_element(const std::string& name) { + *result << name << ""; +} + +void NMODLPrinter::add_newline() { + *result << std::endl; +} + +void NMODLPrinter::pop_level() { + indent_level--; + add_indent(); + *result << "}"; +} diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp new file mode 100644 index 0000000000..197177306e --- /dev/null +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -0,0 +1,53 @@ +#ifndef _NMODL_PRINTER_HPP_ +#define _NMODL_PRINTER_HPP_ + +#include <fstream> +#include <iostream> +#include <sstream> +#include <memory> + +/** + * \class NMODLPrinter + * \brief Helper class for printing AST back to NMDOL test + * + * NmodlPrintVisitor transforms AST back to NMODL. This class + * provided common functionality required by visitor to print + * nmodl ascii file. + * + * \todo : Implement Printer as base class to avoid duplication + * code between JSONPrinter and NMODLPrinter. + */ +class NMODLPrinter { + private: + std::ofstream ofs; + std::streambuf* sbuf = nullptr; + std::shared_ptr<std::ostream> result; + size_t indent_level = 0; + + public: + NMODLPrinter() : result(new std::ostream(std::cout.rdbuf())) { + } + NMODLPrinter(std::stringstream& stream) : result(new std::ostream(stream.rdbuf())) { + } + NMODLPrinter(const std::string& filename); + + ~NMODLPrinter() { + ofs.close(); + } + + /// print whitespaces for indentation + void add_indent(); + + /// start of new block scope (i.e. start with "{") + /// and increases indentation level + void push_level(); + + void add_element(const std::string&); + void add_newline(); + + /// end of current block scope (i.e. end with "}") + /// and decreases indentation level + void pop_level(); +}; + +#endif diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 83c00855f6..c3bba975fa 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -3,8 +3,8 @@ /** Check if the iterator is pointing to last element in the container */ template <typename Iter, typename Cont> -bool is_last(Iter iter, const Cont* cont) { - return ((iter != cont->end()) && (next(iter) == cont->end())); +bool is_last(Iter iter, const Cont& cont) { + return ((iter != cont.end()) && (next(iter) == cont.end())); } /** Given full file path, returns only name of the file */ diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 272dc83dc7..271949ae96 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -11,6 +11,9 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 99634a9c10..81a0facc11 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -8,6 +8,7 @@ #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" #include "tclap/CmdLine.h" @@ -114,6 +115,11 @@ int main(int argc, const char* argv[]) { std::cout << "----PERF VISITOR FINISHED----" << std::endl; } + { + NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); + v.visit_program(ast.get()); + } + } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.hpp new file mode 100644 index 0000000000..ff1c14f28a --- /dev/null +++ b/src/nmodl/visitors/nmodl_visitor_helper.hpp @@ -0,0 +1,48 @@ +#ifndef _NMODL_VISITOR_HELPER_HPP_ +#define _NMODL_VISITOR_HELPER_HPP_ + +#include "visitors/nmodl_visitor.hpp" + +/** Helper function to visit vector elements + * + * @tparam T + * @param separator separator to print for individual vector element + * @param program true if provided elements belong to program node + * @param statement true if elements in vector of statement type + */ + +template <typename T> +void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, + std::string separator, + bool program, + bool statement) { + for (auto iter = elements.begin(); iter != elements.end(); iter++) { + + /// statements need indentation at the start + if (statement) { + printer->add_indent(); + } + + (*iter)->accept(this); + + /// print separator (e.g. comma, space) + if (!separator.empty() && !is_last(iter, elements)) { + printer->add_element(separator); + } + + /// newline at the end of statement + if (statement) { + printer->add_newline(); + } + + /// program blocks need two newlines except last one + if (program) { + printer->add_newline(); + if (!is_last(iter, elements)) { + printer->add_newline(); + } + } + } +} + +#endif diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index f9b3fe06ab..86f188648d 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -26,7 +26,7 @@ add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) target_link_libraries(testparser lexer test_input) -target_link_libraries(testvisitor printer symtab lexer visitor util) +target_link_libraries(testvisitor symtab lexer visitor util test_input printer) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) @@ -51,4 +51,4 @@ set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/symtab/symbol_table.cpp ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE -) \ No newline at end of file +) diff --git a/test/nmodl/transpiler/input/nmodl_constructs.cpp b/test/nmodl/transpiler/input/nmodl_constructs.cpp index 17ea077ad8..ade7d5bb82 100644 --- a/test/nmodl/transpiler/input/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/input/nmodl_constructs.cpp @@ -1,5 +1,46 @@ #include "input/nmodl_constructs.h" +/** Guidelines for adding nmodl text constructs + * + * As nmodl constructs are used to for testing ast to nmodl transformations, + * consider following points: + * + * - Leading whitespaces or empty lines are removed + * + * - Use string literal to define nmodl text + * When ast is transformed back to nmodl, each statement has newline. + * Hence for easy comparison, input nmodl should be null terminated. + * One way to use format: + + R"( + TITLE nmodl title + )" + + * - Do not use extra spaces (even though it's valid) + + LOCAL a,b + + instead of + + LOCAL a, b, c + + * - Use well indented blocks + + NEURON { + RANGE x + } + + instead of + + NEURON { + RANGE x + } + + * + * If nmodl transformation is different from input, third argument could be + * provided with the expected nmodl. + */ + std::map<std::string, NmodlTestCase> nmdol_invalid_constructs{ // clang-format off { @@ -109,6 +150,19 @@ std::map<std::string, NmodlTestCase> nmdol_invalid_constructs{ )" } }, + + { + "unit_block_1", + { + "UNITS block with empty unit", + R"( + UNITS { + () = (millivolt) + } + )" + } + }, + // clang-format on }; @@ -118,31 +172,52 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ "title_1", { "Title statement", - "TITLE nmodl title\n" + R"( + TITLE nmodl title + )" } }, { "local_list_1", { - "standalone LOCAL statement with single scalar variable", - "LOCAL gbar" + "Standalone LOCAL statement with single scalar variable", + R"( + LOCAL gbar + )" } }, { "local_list_2", { - "standalone LOCAL statement with single vector variable", - "LOCAL gbar[2]" + "Standalone LOCAL statement with single vector variable", + R"( + LOCAL gbar[2] + )" } }, { "local_list_3", { - "standalone LOCAL statement with multiple variables", - "LOCAL gbar, ek[2], ik" + "Standalone LOCAL statement with multiple variables", + R"( + LOCAL gbar, ek[2], ik + )" + } + }, + + { + "local_list_4", + { + "Standalone LOCAL statement with multiple variables with/without whitespaces", + R"( + LOCAL gbar, ek[2],ik, gk + )", + R"( + LOCAL gbar, ek[2], ik, gk + )" } }, @@ -150,10 +225,40 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ "define_1", { "Macro definition", - "DEFINE NSTEP 10" + R"( + DEFINE NSTEP 10 + )" + } + }, + + { + "statement_block_1", + { + "Statements should preserve order with blocks", + R"( + DEFINE ASTEP 10 + + NEURON { + RANGE x + } + + DEFINE BSTEP 20 + )" } }, + { + "artificial_cell_1", + { + "Artificial Statement usage", + R"( + NEURON { + ARTIFICIAL_CELL NSLOC + } + )" + } + }, +/** \todo : MODEL_LEVEL is not handled in parser as it's deprecated { "model_level_1", { @@ -161,6 +266,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ "MODEL_LEVEL 2 NEURON {}" } }, +*/ { "verbatim_block_1", @@ -203,8 +309,11 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ "Standalone unit on/off statements", R"( UNITSON + UNITSOFF + UNITSON + UNITSOFF )" } @@ -214,7 +323,9 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ "include_statement_1", { "Standalone include statements", - R"(INCLUDE "Unit.inc" )" + R"( + INCLUDE "Unit.inc" + )" } }, @@ -236,11 +347,11 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ R"( PARAMETER { tau_r_AMPA = 10 - tau_d_AMPA = 10.0 (mV) + tau_d_AMPA = 10.1 (mV) tau_r_NMDA = 10 (mV) <1,2> tau_d_NMDA = 10 (mV) <1.1,2.2> Use (mV) - Dep [1] <1,2> + Dep[1] <1,2> Fac[1] (mV) <1,2> g } @@ -254,6 +365,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ "PARAMETER statement can use macro definition as a number", R"( DEFINE SIX 6 + PARAMETER { tau_r_AMPA = SIX } @@ -261,15 +373,46 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + { + "parameter_block_4", + { + "PARAMETER block with inconsistent whitespaces", + R"( + PARAMETER { + tau_r_AMPA=10.0 + tau_d_AMPA = 10.1(mV) + tau_r_NMDA = 10 (mV) <1, 2> + tau_d_NMDA= 10 (mV)<1.1,2.2> + Use (mV) + Dep [1] <1,2> + Fac[1](mV)<1,2> + g + } + )", + R"( + PARAMETER { + tau_r_AMPA = 10 + tau_d_AMPA = 10.1 (mV) + tau_r_NMDA = 10 (mV) <1,2> + tau_d_NMDA = 10 (mV) <1.1,2.2> + Use (mV) + Dep[1] <1,2> + Fac[1] (mV) <1,2> + g + } + )" + } + }, + { "step_block_1", { "STEP block with all statement types", R"( STEPPED { - tau_r_AMPA = 1 , -2 - tau_d_AMPA = 1.0,-2.0 - tau_r_NMDA = 1,2.0,3 (mV) + tau_r_AMPA = 1, -2 + tau_d_AMPA = 1.1, -2.1 + tau_r_NMDA = 1, 2.1, 3 (mV) } )" } @@ -296,8 +439,8 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ ASSIGNED { v i_AMPA (nA) - i_NMDA START 2.0 (nA) <1.0> - A_NMDA_step[1] START 1 <2.0> + i_NMDA START 2.1 (nA) <0.1> + A_NMDA_step[1] START 1 <0.2> factor_AMPA FROM 0 TO 1 B_NMDA_step[2] FROM 1 TO 2 START 0 (ms) <1> } @@ -312,8 +455,12 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ R"( NEURON { SUFFIX ProbAMPANMDA + USEION na READ ena + USEION na READ ena, kna + USEION na WRITE ena, kna VALENCE 2.1 USEION na READ ena WRITE ina NONSPECIFIC_CURRENT i + NONSPECIFIC_CURRENT i, j ELECTRODE_CURRENT i RANGE tau_r_AMPA, tau_d_AMPA GLOBAL gNa, xNa @@ -325,5 +472,811 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ )" } }, + + { + "unit_block_1", + { + "UNITS block with unit definitions", + R"( + UNITS { + (mA) = (milliamp) + (mV) = (millivolt) + } + )" + } + }, + + { + "unit_block_2", + { + "UNITS block with unit and factor definitions", + R"( + UNITS { + aa = 1 (bb) + cc = 1.1 (dd) + ee = (ff) (gg) + hh = (ii) -> (jj) + } + )" + } + }, + + { + "constant_block_1", + { + "CONSTANT block with all statement types", + R"( + CONSTANT { + xx = 1 + yy2 = 1.1 + ee = 1e-06 (gg) + } + )" + } + }, + + { + "plot_declare_1", + { + "PLOT declaration with single variables", + R"( + PLOT x VS y + )" + } + }, + + { + "plot_declare_2", + { + "PLOT declaration with multiple variables", + R"( + PLOT x, y, z VS a + )" + } + }, + + { + "plot_declare_3", + { + "PLOT declaration with indexed variables", + R"( + PLOT x[1], y[2], z VS a[1] + )" + } + }, + + + { + "statement_list_1", + { + "Empty statement list", + R"( + INITIAL { + } + )" + } + }, + + { + "statement_list_1", + { + "Statement list with local variables", + R"( + INITIAL { + LOCAL a, b + } + )" + } + }, + + { + "fromstmt_1", + { + "From statement", + R"( + INITIAL { + LOCAL a, b + FROM i = 0 TO 1 { + tau[i] = 1 + } + } + )" + } + }, + + { + "fromstmt_2", + { + "From statement with integer expressions", + R"( + INITIAL { + LOCAL a, b + FROM i = (0+0) TO (1+b) BY X { + tau[i] = 1 + } + } + )" + } + }, + + { + "forall_statement_1", + { + "FORALL statement", + R"( + INITIAL { + FORALL some_name { + a = 1 + tau = 2.1 + } + } + )" + } + }, + + { + "while_statement_1", + { + "Empty while statement", + R"( + CONSTRUCTOR { + WHILE (1) { + } + } + )" + } + }, + + { + "while_statement_2", + { + "While statement with expression as condition", + R"( + CONSTRUCTOR { + WHILE ((a+1)<2 && b>100) { + x = 10 + y = 20 + } + } + )" + } + }, + + + { + "if_statement_1", + { + "Empty if statement", + R"( + DESTRUCTOR { + IF (1) { + } + } + )" + } + }, + + { + "if_else_statement_1", + { + "If else statement", + R"( + DESTRUCTOR { + IF ((2.1+1)<x) { + a = (a+1)/2.1 + } ELSE { + b = (2.1+1) + } + } + )" + } + }, + + { + "if_elseif_statement_1", + { + "If multiple else if statements", + R"( + FUNCTION test() { + IF ((2)<x) { + a = (a+1)/2.1 + } ELSE IF (a<2) { + b = 1 + } ELSE IF (b>2) { + c = 1.1 + } + } + )" + } + }, + + { + "if_elseif_else_statement_1", + { + "If with multiple else if nested statements", + R"( + FUNCTION test(v, x) { + IF (2<x) { + a = (a+1) + } ELSE IF (a<2) { + b = 1 + } ELSE IF (b>2) { + c = 1.1 + } ELSE { + IF (A) { + } ELSE { + x = 1 + } + } + } + )" + } + }, + + { + "solve_block_1", + { + "Solve statement without method", + R"( + BREAKPOINT { + SOLVE states + } + )" + } + }, + + { + "solve_block_2", + { + "Solve statement with method", + R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + )" + } + }, + + { + "solve_block_3", + { + "Solve statement with iferor block", + R"( + BREAKPOINT { + SOLVE states METHOD cnexp IFERROR { + a = 1 + } + } + )" + } + }, + + { + "conduct_hint_1", + { + "Conductance statement", + R"( + BREAKPOINT { + CONDUCTANCE gIm + } + )" + } + }, + + { + "conduct_hint_2", + { + "Conductance statement with ion name", + R"( + BREAKPOINT { + CONDUCTANCE gIm USEION k + } + )" + } + }, + + { + "sens_1", + { + "SENS statement", + R"( + BREAKPOINT { + SENS a, b + } + )" + } + }, + + { + "conserve_1", + { + "CONSERVE statement", + R"( + KINETIC ihkin { + CONSERVE C+o = 1 + CONSERVE pump+pumpca = TotalPump*parea*(1e+10) + } + )" + } + }, + + { + "compartment_1", + { + "COMPARTMENT statement", + R"( + KINETIC ihkin { + COMPARTMENT voli {cai} + COMPARTMENT diam*diam*PI/4 {qk} + COMPARTMENT (1e+10)*area1 {pump pumpca} + COMPARTMENT i, diam*diam*vol[i]*1(um) {ca CaBuffer Buffer} + } + )" + } + }, + + { + "long_diffuse_1", + { + "LONGITUDINAL_DIFFUSION statement", + R"( + KINETIC ihkin { + LONGITUDINAL_DIFFUSION D {nai} + LONGITUDINAL_DIFFUSION Dk*crossSectionalArea {ko} + LONGITUDINAL_DIFFUSION i, DIP3*diam*diam*vrat[i] {bufm cabufm} + } + )" + } + }, + + { + "reaction_1", + { + "REACTION statement", + R"( + KINETIC kstates { + ~ c1 <-> i1 (Con, Coff) + ~ ca[i] <-> ca[i+1] (DCa*frat[i+1], DCa*frat[i+1]) + ~ ca[0] << (in*PI*diam*(0.1)*1(um)) + ~ nai << (-f*ina*PI*diam*(10000)/(FARADAY)) + ~ g -> (1/tau) + } + )" + } + }, + + { + "lag_statement_1", + { + "LAG statement", + R"( + PROCEDURE lates() { + LAG ina BY tau + neo = lag_ina_tau + } + )" + } + }, + + { + "queue_statement_1", + { + "PUTQ and GETQ statement", + R"( + PROCEDURE lates() { + PUTQ one_name + GETQ another_name + } + )" + } + }, + + { + "reset_statement_1", + { + "RESET statement", + R"( + PROCEDURE lates() { + RESET + } + )" + } + }, + + { + "match_block_1", + { + "MATCH block", + R"( + PROCEDURE lates() { + MATCH { name1 } + MATCH { name1 name2 } + MATCH { name1[INDEX](expr1+expr2) = (expr3+expr4) } + } + )" + } + }, + + { + "partial_block_partial_equation_1", + { + "PARTIAL block and partial equation statements", + R"( + PARTIAL some_name { + ~ a' = a*DEL2(b)+c + ~ DEL abc[FIRST] = (a*b/c) + ~ abc[LAST] = (a*b/c) + } + )" + } + }, + + { + "linear_block_1", + { + "LINEAR block", + R"( + LINEAR some_name { + ~ I1*bi1+C2*b01-C1*(fi1+f01) = 0 + ~ C1+C2+C3+C4+C5+O+I1+I2+I3+I4+I5+I6 = 1 + } + )" + } + }, + + { + "nonlinear_block_1", + { + "NONLINEAR block with solver for", + R"( + NONLINEAR some_name SOLVEFOR a,b { + ~ I1*bi1+C2*b01-C1*(fi1+f01) = 0 + ~ C1+C2+C3+C4+C5+O+I1+I2+I3+I4+I5+I6 = 1 + } + )" + } + }, + + { + "table_statement_1", + { + "TABLE statements", + R"( + PROCEDURE rates(v) { + TABLE RES FROM -20 TO 20 WITH 5000 + TABLE einf,eexp,etau DEPEND dt,celsius FROM -100 TO 100 WITH 200 + } + )" + } + }, + + { + "watch_statement_1", + { + "WATCH statements", + R"( + NET_RECEIVE (w) { + IF (celltype == 4) { + WATCH (v>(vpeak-0.1*u)) 2 + } ELSE { + WATCH (v>vpeak) 2 + } + } + )" + } + }, + + { + "watch_statement_2", + { + "WATCH statements with multiple expressions", + R"( + NET_RECEIVE (w) { + IF (celltype == 4) { + WATCH (v>vpeak) 2,(v>vpeak) 3.4 + } + } + )" + } + }, + + { + "for_netcon_statement_1", + { + "FOR_NETCONS statement", + R"( + NET_RECEIVE (w(uS), A, tpre(ms)) { + FOR_NETCONS (w1, A1, tp) { + A1 = A1+(wmax-w1-A1)*p*exp((tp-t)/ptau) + } + } + )" + } + }, + + { + "mutex_statement_1", + { + "MUTEX lock/unlock statements", + R"( + PROCEDURE fun(w, A, tpre) { + MUTEXLOCK + MUTEXUNLOCK + } + )" + } + }, + + { + "protect_statement_1", + { + "PROTECT statements", + R"( + INITIAL { + PROTECT Rtau = 1/(Alpha+Beta) + } + )" + } + }, + + + { + "nonlinear_equation", + { + "NONLINEAR block and equation", + R"( + NONLINEAR peak { + ~ 1/taurise*exp(-tmax/taurise)-afast/taufast*exp(-tmax/taufast)-aslow/tauslow*exp(-tmax/tauslow) = 0 + } + )" + } + }, + + { + "state_block_1", + { + "STATE block with variable names", + R"( + STATE { + m + h + } + )" + } + }, + + { + "state_block_2", + { + "STATE block with variable and associated unit", + R"( + STATE { + a (microsiemens) + g (uS) + } + )" + } + }, + + { + "function_call_1", + { + "FUNCTION call", + R"( + TERMINAL { + a = fun1() + b = fun2(a, 2) + fun3() + } + )" + } + }, + + { + "kinetic_block_1", + { + "KINETIC block taken from mod file", + R"( + KINETIC kin { + LOCAL qa + qa = q10a^((celsius-22(degC))/10(degC)) + rates(v) + ~ c <-> o (alpha, beta) + ~ c <-> cac (kon*qa*ai/bf, koff*qa*b/bf) + ~ o <-> cao (kon*qa*ai, koff*qa) + ~ cac <-> cao (alphaa, betaa) + CONSERVE c+cac+o+cao = 1 + } + )" + } + }, + + { + "thread_safe_1", + { + "Theadsafe statement", + R"( + NEURON { + THREADSAFE + THREADSAFE a, b + } + )" + } + }, + + { + "function_table_1", + { + "FUNCTION_TABLE example", + R"( + FUNCTION_TABLE tabmtau(v(mV)) (ms) + )" + } + }, + + { + "discrete_block_1", + { + "DISCRETE block example", + R"( + DISCRETE test { + x = 1 + } + )" + } + }, + + { + "derivative_block_1", + { + "DERIVATIVE block example", + R"( + DERIVATIVE states { + rates() + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )" + } + }, + + { + "derivative_block_2", + { + "DERIVATIVE block with mixed equations", + R"( + DERIVATIVE states { + cri' = ((cui-cri)/180-0.5*icr)/(1+Csqn*Kmcsqn/((cri+Kmcsqn)*(cri+Kmcsqn))) + } + )" + } + }, + + { + "function_1", + { + "FUNCTION definition with other function calls", + R"( + FUNCTION test(v, x) (ms) { + IF (2<x) { + hello(1, 2) + } + } + )" + } + }, + + { + "before_block_1", + { + "BEFORE block example", + R"( + BEFORE BREAKPOINT { + } + )" + } + }, + + { + "after_block_1", + { + "AFTER block example", + R"( + AFTER STEP { + x = 1 + } + )" + } + }, + + { + "assignment_statement_1", + { + "Various assignement statement types", + R"( + AFTER STEP { + x = 1 + y[1+2] = z + a@1 = 12 + a@2[1+2] = 21.1 + } + )" + } + }, + + { + "steadystate_statement_1", + { + "SOLVE statement using STEADYSTATE (which gets replaced with METHOD)", + R"( + INITIAL { + SOLVE kin STEADYSTATE sparse + } + )", + R"( + INITIAL { + SOLVE kin METHOD sparse + } + )" + } + }, + + { + "netreceive_block_1", + { + "NET_RECEIVE block containing INITIAL block", + R"( + NET_RECEIVE (weight, weight_AMPA, weight_NMDA, Psurv, tsyn) { + LOCAL result + weight_AMPA = weight + weight_NMDA = weight*NMDA_ratio + INITIAL { + tsyn = t + } + } + )" + } + }, + + { + "inline_comment_1", + { + "Comments with ? or : are not handled yet", + R"( + ? comment here + FUNCTION urand() { + VERBATIM + printf("Hello World!\n"); + ENDVERBATIM + } + )", + R"( + FUNCTION urand() { + VERBATIM + printf("Hello World!\n"); + ENDVERBATIM + } + )" + } + }, + + { + "inline_comment_2", + { + "Inline comments with ? or : are not handled yet", + R"( + FUNCTION urand() { + a = b+c : some comment here + c = d*e ? another comment here + } + )", + R"( + FUNCTION urand() { + a = b+c + c = d*e + } + )" + } + } // clang-format on }; \ No newline at end of file diff --git a/test/nmodl/transpiler/input/nmodl_constructs.h b/test/nmodl/transpiler/input/nmodl_constructs.h index 97dcea12c7..c760c5af1c 100644 --- a/test/nmodl/transpiler/input/nmodl_constructs.h +++ b/test/nmodl/transpiler/input/nmodl_constructs.h @@ -5,9 +5,26 @@ #include <map> struct NmodlTestCase { + /// name of the test std::string name; - std::string nmodl_text; + + /// input nmodl construct + std::string input; + + /// expected nmodl output + std::string output; + /// \todo : add associated json (to use in visitor test) + + NmodlTestCase() = delete; + + NmodlTestCase(std::string name, std::string input) + : name(name), input(input), output(input) { + } + + NmodlTestCase(std::string name, std::string input, std::string output) + : name(name), input(input), output(output) { + } }; extern std::map<std::string, NmodlTestCase> nmdol_invalid_constructs; diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index b3996c4c1e..396a83294a 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -3,8 +3,8 @@ #include <string> #include "catch/catch.hpp" -#include "parser/nmodl_driver.hpp" #include "input/nmodl_constructs.h" +#include "parser/nmodl_driver.hpp" //============================================================================= // Parser tests @@ -93,8 +93,8 @@ SCENARIO("Parser test for valid NMODL grammar constructs") { for (const auto& construct : nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { - THEN("Parser successfully parses : " + test_case.nmodl_text) { - REQUIRE(is_valid_construct(test_case.nmodl_text)); + THEN("Parser successfully parses : " + test_case.input) { + REQUIRE(is_valid_construct(test_case.input)); } } } @@ -104,8 +104,8 @@ SCENARIO("Parser test for invalid NMODL grammar constructs") { for (const auto& construct : nmdol_invalid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { - THEN("Parser throws an exception while parsing : " + test_case.nmodl_text) { - REQUIRE_THROWS(is_valid_construct(test_case.nmodl_text)); + THEN("Parser throws an exception while parsing : " + test_case.input) { + REQUIRE_THROWS(is_valid_construct(test_case.input)); } } } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 82b7556ce5..c2f437dc42 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -3,11 +3,14 @@ #include <string> #include "catch/catch.hpp" +#include "input/nmodl_constructs.h" #include "parser/nmodl_driver.hpp" -#include "visitors/verbatim_visitor.hpp" +#include "utils/string_utils.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" using json = nlohmann::json; @@ -194,4 +197,101 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { } } } +} + +//============================================================================= +// AST to NMODL printer tests +//============================================================================= + +int count_leading_spaces(std::string text) { + int length = text.size(); + stringutils::ltrim(text); + int num_whitespaces = length - text.size(); + return num_whitespaces; +} + +/// check if string has only whitespaces +bool is_empty(std::string text) { + stringutils::trim(text); + return text.empty(); +} + +/** Reindent nmodl text for text-to-text comparison + * + * Nmodl constructs defined in test database has extra leading whitespace. + * This is done for readability reason in nmodl_constructs.cpp. For example, + * we have following nmodl text with 8 leading whitespaces: + + + NEURON { + RANGE x + } + + * We convert above paragraph to: + +NEURON { + RANGE x +} + + * i.e. we get first non-empty line and count number of leading whitespaces (X). + * Then for every sub-sequent line, we remove first X characters (assuing those + * all are whitespaces). This is done because when ast is transformed back to + * nmodl, the nmodl output is without "extra" whitespaces in the provided input. + */ + +std::string reindent_text(const std::string& text) { + std::string indented_text; + int num_whitespaces = 0; + bool flag = false; + std::string line; + std::stringstream stream(text); + + while (std::getline(stream, line)) { + if (!line.empty()) { + /// count whitespaces for first non-empty line only + if (!flag) { + flag = true; + num_whitespaces = count_leading_spaces(line); + } + + /// make sure we don't remove non whitespaces characters + if (!is_empty(line.substr(0, num_whitespaces))) { + throw std::runtime_error("Test nmodl input not correctly formatted"); + } + + line.erase(0, num_whitespaces); + indented_text += line; + } + /// discard empty lines at very beginning + if (!stream.eof() && flag) { + indented_text += "\n"; + } + } + return indented_text; +} + +std::string run_nmodl_visitor(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + std::stringstream stream; + NmodlPrintVisitor v(stream); + + v.visit_program(ast.get()); + return stream.str(); +} + +SCENARIO("Test for AST back to NMODL transformation") { + for (const auto& construct : nmodl_valid_constructs) { + auto test_case = construct.second; + std::string input_nmodl_text = reindent_text(test_case.input); + std::string output_nmodl_text = reindent_text(test_case.output); + GIVEN(test_case.name) { + THEN("Visitor successfully returns : " + input_nmodl_text) { + auto result = run_nmodl_visitor(input_nmodl_text); + REQUIRE(result == output_nmodl_text); + } + } + } } \ No newline at end of file From 1691bf9878ed1a7d66c1e4f74e71b35a7e89e0d8 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 18 Jan 2018 11:45:46 +0100 Subject: [PATCH 040/871] First draft for generic variable renaming pass Change-Id: I6864ab2448102a261689e6d580b81e0fc8f491d6 NMODL Repo SHA: BlueBrain/nmodl@709481751467ca951ed956f6b4c5d5340970c31c --- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/nmodl_visitor_helper.hpp | 1 - src/nmodl/visitors/var_rename_visitor.cpp | 18 +++ src/nmodl/visitors/var_rename_visitor.hpp | 47 +++++++ .../nmodl/transpiler/input/nmodl_constructs.h | 5 +- test/nmodl/transpiler/visitor/visitor.cpp | 123 +++++++++++++++++- 7 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 src/nmodl/visitors/var_rename_visitor.cpp create mode 100644 src/nmodl/visitors/var_rename_visitor.hpp diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 3cdabf82c0..c2fb728dec 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -53,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 271949ae96..0a106f8a26 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -14,6 +14,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_rename_visitor.cpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.hpp index ff1c14f28a..73d4a843ef 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.hpp +++ b/src/nmodl/visitors/nmodl_visitor_helper.hpp @@ -17,7 +17,6 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, bool program, bool statement) { for (auto iter = elements.begin(); iter != elements.end(); iter++) { - /// statements need indentation at the start if (statement) { printer->add_indent(); diff --git a/src/nmodl/visitors/var_rename_visitor.cpp b/src/nmodl/visitors/var_rename_visitor.cpp new file mode 100644 index 0000000000..8f00750b1c --- /dev/null +++ b/src/nmodl/visitors/var_rename_visitor.cpp @@ -0,0 +1,18 @@ +#include "visitors/var_rename_visitor.hpp" + +/// rename matching variable +void VarRenameVisitor::visit_name(ast::Name* node) { + std::string name = node->get_name(); + if (name == var_name) { + node->value->set(new_var_name); + } +} + +/** Prime name has member order which is an integer. In theory + * integer could be "macro name" and hence could end-up renaming + * macro. In practice this won't be an issue as we order is set + * by parser. To be safe we are only renaming prime variable. + */ +void VarRenameVisitor::visit_prime_name(ast::PrimeName* node) { + node->visit_children(this); +} diff --git a/src/nmodl/visitors/var_rename_visitor.hpp b/src/nmodl/visitors/var_rename_visitor.hpp new file mode 100644 index 0000000000..ed6b57b502 --- /dev/null +++ b/src/nmodl/visitors/var_rename_visitor.hpp @@ -0,0 +1,47 @@ +#ifndef VAR_RENAME_VISITOR_HPP +#define VAR_RENAME_VISITOR_HPP + +#include <string> +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" +#include "symtab/symbol_table.hpp" + +/** + * \class VarRenameVisitor + * \brief "Blindly" nename given variable to new name + * + * During inlining related passes we have to rename variables + * to avoid name conflicts. This pass "blindly" rename any given + * variable to new name. The error handling / legality checks are + * supposed to be done by other higher level passes. For example, + * local renaming pass should be done from inner-most block to top + * level block; + * + * \todo : Add log/warning messages. + */ + +class VarRenameVisitor : public AstVisitor { + private: + /// variable to rename + std::string var_name; + + /// new name + std::string new_var_name; + + public: + VarRenameVisitor() = default; + + VarRenameVisitor(std::string old_name, std::string new_name) + : var_name(old_name), new_var_name(new_name) { + } + + void set(std::string old_name, std::string new_name) { + var_name = old_name; + new_var_name = new_name; + } + + virtual void visit_name(ast::Name* node) override; + virtual void visit_prime_name(ast::PrimeName* node) override; +}; + +#endif diff --git a/test/nmodl/transpiler/input/nmodl_constructs.h b/test/nmodl/transpiler/input/nmodl_constructs.h index c760c5af1c..e4c5b5c52a 100644 --- a/test/nmodl/transpiler/input/nmodl_constructs.h +++ b/test/nmodl/transpiler/input/nmodl_constructs.h @@ -18,12 +18,11 @@ struct NmodlTestCase { NmodlTestCase() = delete; - NmodlTestCase(std::string name, std::string input) - : name(name), input(input), output(input) { + NmodlTestCase(std::string name, std::string input) : name(name), input(input), output(input) { } NmodlTestCase(std::string name, std::string input, std::string output) - : name(name), input(input), output(output) { + : name(name), input(input), output(output) { } }; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index c2f437dc42..35f6d9434d 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -10,6 +10,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" using json = nlohmann::json; @@ -285,13 +286,121 @@ std::string run_nmodl_visitor(const std::string& text) { SCENARIO("Test for AST back to NMODL transformation") { for (const auto& construct : nmodl_valid_constructs) { auto test_case = construct.second; - std::string input_nmodl_text = reindent_text(test_case.input); - std::string output_nmodl_text = reindent_text(test_case.output); - GIVEN(test_case.name) { - THEN("Visitor successfully returns : " + input_nmodl_text) { - auto result = run_nmodl_visitor(input_nmodl_text); - REQUIRE(result == output_nmodl_text); - } + std::string input_nmodl_text = reindent_text(test_case.input); + std::string output_nmodl_text = reindent_text(test_case.output); + GIVEN(test_case.name) { + THEN("Visitor successfully returns : " + input_nmodl_text) { + auto result = run_nmodl_visitor(input_nmodl_text); + REQUIRE(result == output_nmodl_text); + } + } + } +} + +//============================================================================= +// Variable rename tests +//============================================================================= +std::string run_var_rename_visitor(const std::string& text, + std::vector<std::pair<std::string, std::string>> variables) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + { + for (const auto& variable : variables) { + VarRenameVisitor v(variable.first, variable.second); + v.visit_program(ast.get()); + } + } + std::stringstream stream; + + { + NmodlPrintVisitor v(stream); + v.visit_program(ast.get()); + } + return stream.str(); +} + +SCENARIO("Renaming any variable in mod file with VarRenameVisitor") { + GIVEN("A mod file") { + // sample nmodl text + std::string input_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar } + + PARAMETER { + gNaTs2_tbar = 0.1 (S/cm2) + } + + STATE { + m + h + } + + COMMENT + m and gNaTs2_tbar remain same here + ENDCOMMENT + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + } + + FUNCTION mAlpha() { + } + )"; + + /// expected result after renaming m, gNaTs2_tbar and mAlpha + std::string output_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE new_gNaTs2_tbar + } + + PARAMETER { + new_gNaTs2_tbar = 0.1 (S/cm2) + } + + STATE { + mm + h + } + + COMMENT + m and gNaTs2_tbar remain same here + ENDCOMMENT + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = new_gNaTs2_tbar*mm*mm*mm*h + ina = gNaTs2_t*(v-ena) + } + + FUNCTION mBeta() { + } + )"; + + std::string input = reindent_text(input_nmodl_text); + std::string expected_output = reindent_text(output_nmodl_text); + + THEN("existing variables could be renamed") { + std::vector<std::pair<std::string, std::string>> variables = { + {"m", "mm"}, {"gNaTs2_tbar", "new_gNaTs2_tbar"}, {"mAlpha", "mBeta"}, + }; + auto result = run_var_rename_visitor(input, variables); + REQUIRE(result == expected_output); + } + + THEN("non-existing variables will be ignored") { + std::vector<std::pair<std::string, std::string>> variables = { + {"unknown_variable", "doesnot_matter"}}; + auto result = run_var_rename_visitor(input, variables); + REQUIRE(result == input); + } } } \ No newline at end of file From a38012b3b259fe0039613b155318d8ac8c3507a1 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 18 Jan 2018 14:28:55 +0100 Subject: [PATCH 041/871] First draft for local rename visitor: - if there is variable name conflicts with parent scope then rename the variable. - done from innermost children and hence safe to do - done for statement blocks (i.e. local statements) - check if variable in parent scope is of variable type - updated document - New method is_variable() added in Symbol class Change-Id: I0ad7e31234b736ed46eee7bdec3407fe73c28f04 NMODL Repo SHA: BlueBrain/nmodl@e830290a5e394d6b9b4376c9260e68932d268b91 --- src/nmodl/symtab/symbol.cpp | 23 +++++ src/nmodl/symtab/symbol.hpp | 2 + src/nmodl/visitors/CMakeLists.txt | 6 +- .../visitors/local_var_rename_visitor.cpp | 87 +++++++++++++++++++ .../visitors/local_var_rename_visitor.hpp | 64 ++++++++++++++ src/nmodl/visitors/main.cpp | 6 ++ ..._rename_visitor.cpp => rename_visitor.cpp} | 6 +- ..._rename_visitor.hpp => rename_visitor.hpp} | 9 +- test/nmodl/transpiler/visitor/visitor.cpp | 6 +- 9 files changed, 197 insertions(+), 12 deletions(-) create mode 100644 src/nmodl/visitors/local_var_rename_visitor.cpp create mode 100644 src/nmodl/visitors/local_var_rename_visitor.hpp rename src/nmodl/visitors/{var_rename_visitor.cpp => rename_visitor.cpp} (73%) rename src/nmodl/visitors/{var_rename_visitor.hpp => rename_visitor.hpp} (84%) diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index befa6a0570..2e1d622121 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -14,6 +14,7 @@ namespace symtab { } /// check if symbol has any of the common properties + /// \todo : rename to has_any_property bool Symbol::has_properties(SymbolInfo new_properties) { return static_cast<bool>(properties & new_properties); } @@ -42,4 +43,26 @@ namespace symtab { } } + /// check if symbol is of variable type in nmodl + bool Symbol::is_variable() { + // clang-format off + SymbolInfo var_properties = NmodlInfo::local_var + | NmodlInfo::global_var + | NmodlInfo::range_var + | NmodlInfo::param_assign + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::extern_var + | NmodlInfo::dependent_def + | NmodlInfo::read_ion_var + | NmodlInfo::write_ion_var + | NmodlInfo::nonspe_cur_var + | NmodlInfo::electrode_cur_var + | NmodlInfo::section_var + | NmodlInfo::argument + | NmodlInfo::extern_neuron_variable; + // clang-format on + return has_properties(var_properties); + } + } // namespace symtab diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index bb012e1309..e1a15ae269 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -115,6 +115,8 @@ namespace symtab { void add_property(SymbolInfo property); void set_order(int new_order); + + bool is_variable(); }; } // namespace symtab diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 0a106f8a26..c0ad65273c 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -14,8 +14,10 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp new file mode 100644 index 0000000000..21253737c3 --- /dev/null +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -0,0 +1,87 @@ +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/rename_visitor.hpp" + +using namespace ast; +using namespace symtab; + +/** Return new name variable by appending "_r_COUNT" where COUNT is number + * of times the given variable is already used. + */ +std::string LocalVarRenameVisitor::get_new_name(std::string name) { + int suffix = 0; + if (renamed_variables.find(name) != renamed_variables.end()) { + suffix = renamed_variables[name]; + } + + /// increase counter for next usage + renamed_variables[name] = suffix + 1; + return (name + "_r_" + std::to_string(suffix)); +} + +LocalVarVector* LocalVarRenameVisitor::get_local_variables(StatementBlock* node) { + LocalVarVector* variables = nullptr; + for (const auto& statement : node->statements) { + /// we are only interested in local variable definition statement + if (statement->get_type() == Type::LOCAL_LIST_STATEMENT) { + auto local_statement = std::static_pointer_cast<LocalListStatement>(statement); + variables = &(local_statement->variables); + break; + } + } + return variables; +} + +/// rename name conflicting variables in the statement block and it's all children +void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { + + /// nothing to do + if (node->statements.empty()) { + return; + } + + auto current_symtab = node->get_symbol_table(); + if (current_symtab) { + symtab = current_symtab; + } + + // Some statetements like forall, from, while are of type expression statement type. + // These statements contain statement block but do not have symbol table. And hence + // we push last non-null symbol table on the stack. + symtab_stack.push(symtab); + + // first need to process all children : perform recursively from innermost block + for (auto& item : node->statements) { + item->visit_children(this); + } + + auto variables = get_local_variables(node); + + std::shared_ptr<SymbolTable> parent_symtab; + if (symtab) { + parent_symtab = symtab->get_parent_table(); + } + + /// global blocks are not changes : if there is no parent symbol + /// table or no variables in the block then there is nothing to do + if (parent_symtab == nullptr || variables == nullptr) { + return; + } + + RenameVisitor rename_visitor; + + for (auto& var : *variables) { + std::string name = var->get_name(); + auto s = parent_symtab->lookup_in_scope(name); + + /// if parent has symbol and it's of type variable + if (s && s->is_variable()) { + std::string new_name = get_new_name(name); + rename_visitor.set(name, new_name); + rename_visitor.visit_statement_block(node); + } + } + + /// go back to previous block in hierarchy + symtab = symtab_stack.top(); + symtab_stack.pop(); +} diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp new file mode 100644 index 0000000000..a6c0bc3bde --- /dev/null +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -0,0 +1,64 @@ +#ifndef LOCAL_VAR_RENAME_VISITOR_HPP +#define LOCAL_VAR_RENAME_VISITOR_HPP + +#include <stack> +#include <map> + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" +#include "symtab/symbol_table.hpp" + +/** + * \class LocalVarRenameVisitor + * \brief Visitor to rename local variables conflicting with global scope + * + * Motivation: During inlining we have to do data-flow-analysis. Consider + * below example: + * + * NEURON { + * RANGE tau, beta + * } + * + * DERIVATIVE states() { + * LOCAL tau + * ... + * rates() + * } + * + * PROCEDURE rates() { + * tau = beta * 0.12 * some_var + * } + * + * When rates() will be inlined into states(), local definition of tau will + * conflict with range variable tau. Hence we can't just copy the statements. + * Dataflow analysis could be done at the time of inlining. Other way is to run + * this pass before inlining and pre-rename any local-global variable conflicts. + * As we are renaming local variables only, it's safe and there are no side effects. + * + * \todo: currently we are renaming variables even if there is no inlining candidates. + * In this case ideally we should not rename. + */ + +class LocalVarRenameVisitor : public AstVisitor { + private: + /// non-null symbol table in the scope hierarchy + std::shared_ptr<symtab::SymbolTable> symtab; + + /// symbol tables in case of nested blocks + std::stack<std::shared_ptr<symtab::SymbolTable>> symtab_stack; + + /// variables currently being renamed and their count + std::map<std::string, int> renamed_variables; + + /// return local variables for given statement block + ast::LocalVarVector* get_local_variables(ast::StatementBlock* node); + + /// based on usage frequency return new name for given variable + std::string get_new_name(std::string var_name); + + public: + LocalVarRenameVisitor() = default; + virtual void visit_statement_block(ast::StatementBlock* node) override; +}; + +#endif diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 81a0facc11..ac095071cd 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -5,6 +5,7 @@ #include "parser/nmodl_driver.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -115,6 +116,11 @@ int main(int argc, const char* argv[]) { std::cout << "----PERF VISITOR FINISHED----" << std::endl; } + { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + } + { NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/var_rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp similarity index 73% rename from src/nmodl/visitors/var_rename_visitor.cpp rename to src/nmodl/visitors/rename_visitor.cpp index 8f00750b1c..6fbddf3bbe 100644 --- a/src/nmodl/visitors/var_rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -1,7 +1,7 @@ -#include "visitors/var_rename_visitor.hpp" +#include "visitors/rename_visitor.hpp" /// rename matching variable -void VarRenameVisitor::visit_name(ast::Name* node) { +void RenameVisitor::visit_name(ast::Name* node) { std::string name = node->get_name(); if (name == var_name) { node->value->set(new_var_name); @@ -13,6 +13,6 @@ void VarRenameVisitor::visit_name(ast::Name* node) { * macro. In practice this won't be an issue as we order is set * by parser. To be safe we are only renaming prime variable. */ -void VarRenameVisitor::visit_prime_name(ast::PrimeName* node) { +void RenameVisitor::visit_prime_name(ast::PrimeName* node) { node->visit_children(this); } diff --git a/src/nmodl/visitors/var_rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp similarity index 84% rename from src/nmodl/visitors/var_rename_visitor.hpp rename to src/nmodl/visitors/rename_visitor.hpp index ed6b57b502..ce60d17b67 100644 --- a/src/nmodl/visitors/var_rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -2,13 +2,14 @@ #define VAR_RENAME_VISITOR_HPP #include <string> + #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" /** * \class VarRenameVisitor - * \brief "Blindly" nename given variable to new name + * \brief "Blindly" rename given variable to new name * * During inlining related passes we have to rename variables * to avoid name conflicts. This pass "blindly" rename any given @@ -20,7 +21,7 @@ * \todo : Add log/warning messages. */ -class VarRenameVisitor : public AstVisitor { +class RenameVisitor : public AstVisitor { private: /// variable to rename std::string var_name; @@ -29,9 +30,9 @@ class VarRenameVisitor : public AstVisitor { std::string new_var_name; public: - VarRenameVisitor() = default; + RenameVisitor() = default; - VarRenameVisitor(std::string old_name, std::string new_name) + RenameVisitor(std::string old_name, std::string new_name) : var_name(old_name), new_var_name(new_name) { } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 35f6d9434d..80f3fdce50 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -10,7 +10,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" -#include "visitors/var_rename_visitor.hpp" +#include "visitors/rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" using json = nlohmann::json; @@ -308,7 +308,7 @@ std::string run_var_rename_visitor(const std::string& text, { for (const auto& variable : variables) { - VarRenameVisitor v(variable.first, variable.second); + RenameVisitor v(variable.first, variable.second); v.visit_program(ast.get()); } } @@ -321,7 +321,7 @@ std::string run_var_rename_visitor(const std::string& text, return stream.str(); } -SCENARIO("Renaming any variable in mod file with VarRenameVisitor") { +SCENARIO("Renaming any variable in mod file with RenameVisitor") { GIVEN("A mod file") { // sample nmodl text std::string input_nmodl_text = R"( From 8c40d9a305bbf10397181617265ace2b8a251d28 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 18 Jan 2018 21:18:47 +0100 Subject: [PATCH 042/871] Added test, restructured code and cleanup: - nmodl constructs moved to utils sub-directory - test_util librart added - added test for local variable rename visitor Change-Id: Idae8da9f78809ba2cd97b1a6d249057eb3240ab3 NMODL Repo SHA: BlueBrain/nmodl@f60f7d99e0f12624e1ece7ffa3fbc23f9ab4def0 --- cmake/nmodl/CMakeLists.txt | 1 + .../visitors/local_var_rename_visitor.cpp | 9 +- .../visitors/local_var_rename_visitor.hpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 14 +- test/nmodl/transpiler/parser/parser.cpp | 2 +- .../{input => utils}/nmodl_constructs.cpp | 2 +- .../{input => utils}/nmodl_constructs.h | 0 test/nmodl/transpiler/utils/test_utils.cpp | 68 +++++ test/nmodl/transpiler/utils/test_utils.hpp | 6 + test/nmodl/transpiler/visitor/visitor.cpp | 266 +++++++++++++----- 10 files changed, 284 insertions(+), 86 deletions(-) rename test/nmodl/transpiler/{input => utils}/nmodl_constructs.cpp (99%) rename test/nmodl/transpiler/{input => utils}/nmodl_constructs.h (100%) create mode 100644 test/nmodl/transpiler/utils/test_utils.cpp create mode 100644 test/nmodl/transpiler/utils/test_utils.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 2b14fb3d8b..15448d365d 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -32,6 +32,7 @@ include(CompilerHelper) include(ClangTidyHelper) include_directories( + ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src ${PROJECT_SOURCE_DIR}/src/ext/ ) diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 21253737c3..0bb988c8c4 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -7,7 +7,7 @@ using namespace symtab; /** Return new name variable by appending "_r_COUNT" where COUNT is number * of times the given variable is already used. */ -std::string LocalVarRenameVisitor::get_new_name(std::string name) { +std::string LocalVarRenameVisitor::get_new_name(const std::string& name) { int suffix = 0; if (renamed_variables.find(name) != renamed_variables.end()) { suffix = renamed_variables[name]; @@ -33,7 +33,6 @@ LocalVarVector* LocalVarRenameVisitor::get_local_variables(StatementBlock* node) /// rename name conflicting variables in the statement block and it's all children void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { - /// nothing to do if (node->statements.empty()) { return; @@ -61,8 +60,8 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { parent_symtab = symtab->get_parent_table(); } - /// global blocks are not changes : if there is no parent symbol - /// table or no variables in the block then there is nothing to do + /// global blocks do not change (do no have parent symbol table) + /// if no variables in the block then there is nothing to do if (parent_symtab == nullptr || variables == nullptr) { return; } @@ -73,7 +72,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { std::string name = var->get_name(); auto s = parent_symtab->lookup_in_scope(name); - /// if parent has symbol and it's of type variable + /// if symbol represents variable (to avoid renaming use of units like mV) if (s && s->is_variable()) { std::string new_name = get_new_name(name); rename_visitor.set(name, new_name); diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index a6c0bc3bde..33424a7b13 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -54,7 +54,7 @@ class LocalVarRenameVisitor : public AstVisitor { ast::LocalVarVector* get_local_variables(ast::StatementBlock* node); /// based on usage frequency return new name for given variable - std::string get_new_name(std::string var_name); + std::string get_new_name(const std::string& name); public: LocalVarRenameVisitor() = default; diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 86f188648d..64adfc1928 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -8,9 +8,10 @@ include_directories( #============================================================================= # Common input data library #============================================================================= -add_library(test_input +add_library(test_util STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/input/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp ) #============================================================================= @@ -25,8 +26,8 @@ add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) -target_link_libraries(testparser lexer test_input) -target_link_libraries(testvisitor symtab lexer visitor util test_input printer) +target_link_libraries(testparser lexer test_util) +target_link_libraries(testvisitor symtab lexer visitor util test_util printer) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) @@ -41,8 +42,9 @@ add_test (NAME Symtab COMMAND testsymtab) # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/input/nmodl_constructs.h - ${CMAKE_CURRENT_SOURCE_DIR}/input/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.h + ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modtoken/modtoken.cpp ${CMAKE_CURRENT_SOURCE_DIR}/lexer/tokens.cpp ${CMAKE_CURRENT_SOURCE_DIR}/parser/parser.cpp diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 396a83294a..d20366f328 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -3,8 +3,8 @@ #include <string> #include "catch/catch.hpp" -#include "input/nmodl_constructs.h" #include "parser/nmodl_driver.hpp" +#include "test/utils/nmodl_constructs.h" //============================================================================= // Parser tests diff --git a/test/nmodl/transpiler/input/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp similarity index 99% rename from test/nmodl/transpiler/input/nmodl_constructs.cpp rename to test/nmodl/transpiler/utils/nmodl_constructs.cpp index ade7d5bb82..dd75313039 100644 --- a/test/nmodl/transpiler/input/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1,4 +1,4 @@ -#include "input/nmodl_constructs.h" +#include "test/utils/nmodl_constructs.h" /** Guidelines for adding nmodl text constructs * diff --git a/test/nmodl/transpiler/input/nmodl_constructs.h b/test/nmodl/transpiler/utils/nmodl_constructs.h similarity index 100% rename from test/nmodl/transpiler/input/nmodl_constructs.h rename to test/nmodl/transpiler/utils/nmodl_constructs.h diff --git a/test/nmodl/transpiler/utils/test_utils.cpp b/test/nmodl/transpiler/utils/test_utils.cpp new file mode 100644 index 0000000000..4d57f1290e --- /dev/null +++ b/test/nmodl/transpiler/utils/test_utils.cpp @@ -0,0 +1,68 @@ +#include "utils/string_utils.hpp" + +int count_leading_spaces(std::string text) { + int length = text.size(); + stringutils::ltrim(text); + int num_whitespaces = length - text.size(); + return num_whitespaces; +} + +/// check if string has only whitespaces +bool is_empty(std::string text) { + stringutils::trim(text); + return text.empty(); +} + +/** Reindent nmodl text for text-to-text comparison + * + * Nmodl constructs defined in test database has extra leading whitespace. + * This is done for readability reason in nmodl_constructs.cpp. For example, + * we have following nmodl text with 8 leading whitespaces: + + + NEURON { + RANGE x + } + + * We convert above paragraph to: + +NEURON { + RANGE x +} + + * i.e. we get first non-empty line and count number of leading whitespaces (X). + * Then for every sub-sequent line, we remove first X characters (assuing those + * all are whitespaces). This is done because when ast is transformed back to + * nmodl, the nmodl output is without "extra" whitespaces in the provided input. + */ + +std::string reindent_text(const std::string& text) { + std::string indented_text; + int num_whitespaces = 0; + bool flag = false; + std::string line; + std::stringstream stream(text); + + while (std::getline(stream, line)) { + if (!line.empty()) { + /// count whitespaces for first non-empty line only + if (!flag) { + flag = true; + num_whitespaces = count_leading_spaces(line); + } + + /// make sure we don't remove non whitespaces characters + if (!is_empty(line.substr(0, num_whitespaces))) { + throw std::runtime_error("Test nmodl input not correctly formatted"); + } + + line.erase(0, num_whitespaces); + indented_text += line; + } + /// discard empty lines at very beginning + if (!stream.eof() && flag) { + indented_text += "\n"; + } + } + return indented_text; +} \ No newline at end of file diff --git a/test/nmodl/transpiler/utils/test_utils.hpp b/test/nmodl/transpiler/utils/test_utils.hpp new file mode 100644 index 0000000000..f0070534bd --- /dev/null +++ b/test/nmodl/transpiler/utils/test_utils.hpp @@ -0,0 +1,6 @@ +#ifndef NMODL_TEST_UTILS +#define NMODL_TEST_UTILS + +std::string reindent_text(const std::string& text); + +#endif \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 80f3fdce50..f2f1c9d382 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -3,15 +3,16 @@ #include <string> #include "catch/catch.hpp" -#include "input/nmodl_constructs.h" #include "parser/nmodl_driver.hpp" -#include "utils/string_utils.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +#include "test/utils/nmodl_constructs.h" +#include "test/utils/test_utils.hpp" using json = nlohmann::json; @@ -204,73 +205,6 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { // AST to NMODL printer tests //============================================================================= -int count_leading_spaces(std::string text) { - int length = text.size(); - stringutils::ltrim(text); - int num_whitespaces = length - text.size(); - return num_whitespaces; -} - -/// check if string has only whitespaces -bool is_empty(std::string text) { - stringutils::trim(text); - return text.empty(); -} - -/** Reindent nmodl text for text-to-text comparison - * - * Nmodl constructs defined in test database has extra leading whitespace. - * This is done for readability reason in nmodl_constructs.cpp. For example, - * we have following nmodl text with 8 leading whitespaces: - - - NEURON { - RANGE x - } - - * We convert above paragraph to: - -NEURON { - RANGE x -} - - * i.e. we get first non-empty line and count number of leading whitespaces (X). - * Then for every sub-sequent line, we remove first X characters (assuing those - * all are whitespaces). This is done because when ast is transformed back to - * nmodl, the nmodl output is without "extra" whitespaces in the provided input. - */ - -std::string reindent_text(const std::string& text) { - std::string indented_text; - int num_whitespaces = 0; - bool flag = false; - std::string line; - std::stringstream stream(text); - - while (std::getline(stream, line)) { - if (!line.empty()) { - /// count whitespaces for first non-empty line only - if (!flag) { - flag = true; - num_whitespaces = count_leading_spaces(line); - } - - /// make sure we don't remove non whitespaces characters - if (!is_empty(line.substr(0, num_whitespaces))) { - throw std::runtime_error("Test nmodl input not correctly formatted"); - } - - line.erase(0, num_whitespaces); - indented_text += line; - } - /// discard empty lines at very beginning - if (!stream.eof() && flag) { - indented_text += "\n"; - } - } - return indented_text; -} - std::string run_nmodl_visitor(const std::string& text) { nmodl::Driver driver; driver.parse_string(text); @@ -305,7 +239,6 @@ std::string run_var_rename_visitor(const std::string& text, nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); - { for (const auto& variable : variables) { RenameVisitor v(variable.first, variable.second); @@ -313,7 +246,6 @@ std::string run_var_rename_visitor(const std::string& text, } } std::stringstream stream; - { NmodlPrintVisitor v(stream); v.visit_program(ast.get()); @@ -403,4 +335,194 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { REQUIRE(result == input); } } -} \ No newline at end of file +} + +//============================================================================= +// Local variable rename tests +//============================================================================= + +std::string run_local_var_rename_visitor(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + { + ModelSymbolTable symtab; + SymtabVisitor v(&symtab); + v.visit_program(ast.get()); + } + + { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + } + std::stringstream stream; + { + NmodlPrintVisitor v(stream); + v.visit_program(ast.get()); + } + return stream.str(); +} + +SCENARIO("Presence of local and global variables in same block") { + GIVEN("A neuron block and procedure with same variable name") { + std::string nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar + gNaTs2_tbar = 2.1 + ena + } + )"; + + std::string expected_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar_r_0 + gNaTs2_tbar_r_0 = 2.1+ena + } + )"; + + THEN("var renaming pass changes only local variables in procedure") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(expected_nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Absence of global blocks") { + GIVEN("Procedures containing same variables") { + std::string nmodl_text = R"( + PROCEDURE rates_1() { + LOCAL gNaTs2_tbar + gNaTs2_tbar = 2.1+ena + } + + PROCEDURE rates_2() { + LOCAL gNaTs2_tbar + gNaTs2_tbar = 2.1+ena + } + )"; + + THEN("nothing gets renamed") { + std::string input = reindent_text(nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == input); + } + } +} + +SCENARIO("Variable renaming in nested blocks") { + GIVEN("Mod file containing procedures with nested blocks") { + std::string input_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PARAMETER { + gNaTs2_tbar = 0.1 (S/cm2) + tau = 11.1 + } + + STATE { + m + h + } + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + { + LOCAL gNaTs2_t, h + gNaTs2_t = m + h + { + LOCAL m + m = gNaTs2_t + h + { + LOCAL m, h + } + } + } + } + + PROCEDURE rates() { + LOCAL x, m + m = x + gNaTs2_tbar + { + { + LOCAL h, x, gNaTs2_tbar + m = h * x * gNaTs2_tbar + tau + } + } + } + )"; + + // \todo : open brace without any keyword starts with an extra empty space + std::string expected_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PARAMETER { + gNaTs2_tbar = 0.1 (S/cm2) + tau = 11.1 + } + + STATE { + m + h + } + + BREAKPOINT { + LOCAL gNaTs2_t_r_1 + gNaTs2_t_r_1 = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t_r_1*(v-ena) + { + LOCAL gNaTs2_t_r_0, h_r_1 + gNaTs2_t_r_0 = m+h_r_1 + { + LOCAL m_r_1 + m_r_1 = gNaTs2_t_r_0+h_r_1 + { + LOCAL m_r_0, h_r_0 + } + } + } + } + + PROCEDURE rates() { + LOCAL x_r_1, m_r_2 + m_r_2 = x_r_1+gNaTs2_tbar + { + { + LOCAL h_r_2, x_r_0, gNaTs2_tbar_r_0 + m_r_2 = h_r_2*x_r_0*gNaTs2_tbar_r_0+tau + } + } + } + )"; + + THEN("variables conflicting with global variables get renamed starting from inner block") { + std::string input = reindent_text(input_nmodl_text); + auto expected_result = reindent_text(expected_nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == expected_result); + } + } +} From 0baa137a5873836c7a7db9cc1a50bbc1fb565d95 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Fri, 19 Jan 2018 14:45:13 +0100 Subject: [PATCH 043/871] Bug fix : perf visitor was segfaulting when mod file with only procedure or function exist (nocmodl/NOCMODL-43). Change-Id: Ia1146eea60eeb597f1a732c57178f518581e290c NMODL Repo SHA: BlueBrain/nmodl@1203bf482bb8eaa6913432bcfe83433e4906daca --- src/nmodl/visitors/perf_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index f86dbdeb81..46a011f0e3 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -287,7 +287,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbo * read/write count. Also update ops count in current block. */ void PerfVisitor::update_memory_ops(const std::string& name) { - if (start_measurement) { + if (start_measurement && current_symtab) { auto symbol = current_symtab->lookup_in_scope(name); if (symbol == nullptr || symbol_to_skip(symbol)) { return; From 3d302c6e2313d223d197275ec330209d01eecd55 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 23 Jan 2018 14:58:07 +0100 Subject: [PATCH 044/871] Implementation of FUNCTION and PROCEDURE inlining pass for NMODL: - InlineVisitor pass added that supports nested function calls - Inlining of functions containing table and lag statement is currently disabled - Visitor utils file added to implement common functions across passes - New ast node WrappedExpression added which helps to easily capture and replace interesting expressions like function calls - Tests added for function as well as procedure inlining (Squashed commit from local branch sandbox/kumbhar/inilining_local) Change-Id: I692cd96b90a1d914bcbf0d44d7807585646f8c7c NMODL Repo SHA: BlueBrain/nmodl@b3f61dcdb7293a5428c7a0d32eb561730dde2020 --- src/nmodl/ast/ast_utils.hpp | 4 + src/nmodl/language/nmodl.yaml | 4 + src/nmodl/parser/nmodl.yy | 5 +- src/nmodl/symtab/symbol_table.hpp | 4 +- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/visitors/CMakeLists.txt | 4 + src/nmodl/visitors/inline_visitor.cpp | 221 ++++++++ src/nmodl/visitors/inline_visitor.hpp | 246 +++++++++ .../visitors/local_var_rename_visitor.cpp | 30 +- .../visitors/local_var_rename_visitor.hpp | 6 - src/nmodl/visitors/main.cpp | 6 + src/nmodl/visitors/visitor_utils.cpp | 27 + src/nmodl/visitors/visitor_utils.hpp | 17 + test/nmodl/transpiler/visitor/visitor.cpp | 519 +++++++++++++++++- 14 files changed, 1054 insertions(+), 41 deletions(-) create mode 100644 src/nmodl/visitors/inline_visitor.cpp create mode 100644 src/nmodl/visitors/inline_visitor.hpp create mode 100644 src/nmodl/visitors/visitor_utils.cpp create mode 100644 src/nmodl/visitors/visitor_utils.hpp diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 370a333f72..cfbd096ee4 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -362,6 +362,10 @@ namespace ast { return false; } + virtual bool is_wrapped_expression() { + return true; + } + virtual bool is_paren_expression() { return false; } diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index fdb7b43faf..7dc1fb5526 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1042,6 +1042,10 @@ members: - value: type: ReactionOp + - WrappedExpression: + members: + - expression: + type: Expression - ParenExpression: members: - expr: diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index e47961a476..f41c3c8bde 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -250,7 +250,7 @@ %type <ast::elseifstatement_list> optelseif %type <ast::elsestatement_ptr> optelse %type <ast::solveblock_ptr> solveblk -%type <ast::functioncall_ptr> funccall +%type <ast::wrappedexpression_ptr> funccall %type <ast::statementblock_ptr> ifsolerr %type <ast::expression_ptr> opinc %type <ast::number_ptr> opstart @@ -1174,7 +1174,8 @@ term : varname { $$ = $1; } funccall : NAME "(" exprlist ")" { - $$ = new ast::FunctionCall($1, $3); + auto expression = new ast::FunctionCall($1, $3); + $$ = new ast::WrappedExpression(expression); } ; diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index f62555ff49..479c5efdc3 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -82,9 +82,6 @@ namespace symtab { /// also required for nested blocks like INITIAL in NET_RECEIVE. std::map<std::string, std::shared_ptr<SymbolTable>> children; - /// pretty print table - void print_table(std::stringstream& ss, int indent); - public: SymbolTable(std::string name, AST* node, bool global = false) : symtab_name(name), node(node), global(global) { @@ -139,6 +136,7 @@ namespace symtab { return table.symbols.size(); } + /// \todo: revisit the usage as tokens will be pointing to old nodes SymbolTable* clone() { return new SymbolTable(*this); } diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index c2fb728dec..3cdabf82c0 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -53,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index c0ad65273c..84593eff19 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -11,6 +11,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp @@ -18,6 +20,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp new file mode 100644 index 0000000000..5198af839c --- /dev/null +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -0,0 +1,221 @@ +#include "visitors/inline_visitor.hpp" + +using namespace ast; + +bool InlineVisitor::can_inline_block(StatementBlock* block) { + bool to_inline = true; + for (const auto& statement : block->statements) { + /// inlining is disabled if function/procedure contains table or lag statement + if (statement->is_table_statement() || statement->is_lag_statement()) { + to_inline = false; + break; + } + } + return to_inline; +} + +void InlineVisitor::add_local_statement(StatementBlock* node) { + auto variables = get_local_variables(node); + if (variables == nullptr) { + auto statement = std::make_shared<LocalListStatement>(LocalVarVector()); + node->statements.insert(node->statements.begin(), statement); + } +} + +void InlineVisitor::add_return_variable(StatementBlock* block, std::string& varname) { + auto lhs = new Name(new String(varname)); + auto rhs = new Integer(0, nullptr); + auto expression = new BinaryExpression(lhs, BinaryOperator(BOP_ASSIGN), rhs); + auto statement = std::make_shared<ExpressionStatement>(expression); + block->statements.push_back(statement); +} + +/** We can replace statement if the entire statement itself is a function call. + * In this case we check if: + * - given statement is expression statement + * - if expression is wrapped expression + * - if wrapped expression is a function call + * + * \todo: add method to ast itself to simplify this implementation + */ +bool InlineVisitor::can_replace_statement(const std::shared_ptr<Statement>& statement) { + if (!statement->is_expression_statement()) { + return false; + } + + bool to_replace = false; + auto es = static_cast<ExpressionStatement*>(statement.get()); + auto e = es->expression; + if (e->is_wrapped_expression()) { + auto wrapped_expression = static_cast<WrappedExpression*>(e.get()); + if (wrapped_expression->expression->is_function_call()) { + to_replace = true; + } + } + return to_replace; +} + +void InlineVisitor::inline_arguments(StatementBlock* inlined_block, + const ArgumentVector& callee_arguments, + const ExpressionVector& caller_expressions) { + /// nothing to inline if no arguments for function call + if (caller_expressions.empty()) { + return; + } + + /// add local statement in the block if missing + add_local_statement(inlined_block); + + auto local_variables = get_local_variables(inlined_block); + auto& statements = inlined_block->statements; + + size_t counter = 0; + + for (const auto& argument : callee_arguments) { + auto name = argument->name->clone(); + auto old_name = name->get_name(); + auto new_name = get_new_name(old_name, "in", inlined_variables); + name->set_name(new_name); + + /// for argument add new variable to local statement + local_variables->push_back(std::make_shared<LocalVar>(name)); + + /// variables in cloned block needs to be renamed + RenameVisitor visitor(old_name, new_name); + inlined_block->visit_children(&visitor); + + auto lhs = new VarName(name->clone(), nullptr, nullptr); + auto rhs = caller_expressions.at(counter)->clone(); + + /// create assignment statement and insert after the local variables + auto expression = new BinaryExpression(lhs, BinaryOperator(ast::BOP_ASSIGN), rhs); + auto statement = std::make_shared<ExpressionStatement>(expression); + statements.insert(statements.begin() + counter + 1, statement); + counter++; + } +} + +void InlineVisitor::visit_function_call(FunctionCall* node) { + /// argument can be function call itself + node->visit_children(this); + + std::string function_name = node->name->get_name(); + auto symbol = caller_symtab->lookup_in_scope(function_name); + + /// nothing to do if called function is not defined or it's external + if (symbol == nullptr || symbol->is_external_symbol_only()) { + return; + } + + auto function_definition = symbol->get_node(); + if (function_definition == nullptr) { + throw std::runtime_error("symbol table doesn't have ast node for " + function_name); + } + + /// first inline called function + function_definition->visit_children(this); + + if (function_definition->is_procedure_block()) { + auto proc = (ProcedureBlock*)function_definition; + inline_function_call(proc, node, caller_block); + } else if (function_definition->is_function_block()) { + auto func = (FunctionBlock*)function_definition; + inline_function_call(func, node, caller_block); + } +} + +void InlineVisitor::visit_statement_block(StatementBlock* node) { + /** While inlining we have to add new statements before call site. + * In order to return result we also have to add new local variable + * to the caller block. Hence we have to keep track of caller block, + * caller block's symbol table and caller statement. + */ + caller_block = node; + statementblock_stack.push(node); + + /** Some statements like forall, from, while are statements but contain + * statement block. They don't have symbol table. Hence we check if there + * is a symbol table and then only we push it on stack. In case of nullptr, + * we use parent's symbol table. + */ + auto symtab = node->get_symbol_table(); + if (symtab) { + caller_symtab = symtab; + } + symtab_stack.push(caller_symtab); + + /** Add empty local statement at the begining of block if already doesn't exist. + * Why? When we iterate over statements and inline function call, we have to add + * local variable to return the result. As we can't modify vector while iterating, + * we pre-add local statement without any local variables. If inlining pass doesn't + * add any variable then this statement will be removed. + */ + add_local_statement(node); + + auto& statements = node->statements; + + for (auto& statement : statements) { + caller_statement = statement; + statement_stack.push(statement); + caller_statement->visit_children(this); + statement_stack.pop(); + } + + /// if nothing was added into local statement, remove it + LocalVarVector* local_variables = get_local_variables(node); + if (local_variables->empty()) { + statements.erase(statements.begin()); + } + + /// check if any statement is candidate for replacement due to inlining + /// this is typicall case of procedure calls + for (auto& statement : statements) { + if (replaced_statements.find(statement) != replaced_statements.end()) { + statement.reset(replaced_statements[statement]); + } + } + + /// all statements from inlining needs to be added before the caller statement + for (auto& element : inlined_statements) { + auto it = std::find(statements.begin(), statements.end(), element.first); + if (it != statements.end()) { + statements.insert(it, element.second.begin(), element.second.end()); + element.second.clear(); + } + } + + /** Restore the caller context : consider call chain A() -> B() -> none. + * When we finishes processing B's statements, even we pop the stack, + * caller_* variables still point to B's context. Hence first we have + * to pop elements and then use top() to get context of A(). We have to + * check for non-empty() stack because if there is only A() -> none then + * stack is already empty. + */ + statementblock_stack.pop(); + symtab_stack.pop(); + + if (!statement_stack.empty()) { + caller_statement = statement_stack.top(); + } + if (!statementblock_stack.empty()) { + caller_block = statementblock_stack.top(); + } + if (!symtab_stack.empty()) { + caller_symtab = symtab_stack.top(); + } +} + +/** Visit all wrapped expressions which can contain function calls. + * If a function call is replaced then the wrapped expression is + * also replaced with new variable node from the inlining result. + */ +void InlineVisitor::visit_wrapped_expression(WrappedExpression* node) { + node->visit_children(this); + if (node->expression->is_function_call()) { + auto expression = static_cast<FunctionCall*>(node->expression.get()); + if (replaced_fun_calls.find(expression) != replaced_fun_calls.end()) { + auto var = replaced_fun_calls[expression]; + node->expression = std::make_shared<Name>(new String(var)); + } + } +} diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp new file mode 100644 index 0000000000..132291dd5f --- /dev/null +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -0,0 +1,246 @@ +#ifndef NMODL_INLINE_VISITOR_HPP +#define NMODL_INLINE_VISITOR_HPP + +#include <map> +#include <stack> + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/visitor_utils.hpp" +#include "symtab/symbol_table.hpp" + +/** + * \class InlineVisitor + * \brief Visitor to inline local procedure and function calls + * + * Motivation: Mod files often have function and procedure calls. Procedure + * typically has use of range variables like: + * + * NEURON { + * RANGE tau, alpha, beta + * } + * + * DERIVATIVE states() { + * ... + * rates() + * alpha = tau + beta + * } + * + * PROCEDURE rates() { + * tau = xx * 0.12 * some_var + * beta = yy * 0.11 + * } + * + * One can reduce the memory bandwidth pressure by inlining rates() and then + * replacing tau and beta with local variables. Many mod files from BlueBrain + * and other open source projects could be hugely benefited by inlining pass. + * The goal of this pass is to implement procedure and function inlining in + * the nmodl programs. After inlining we should be asble to translate AST back + * to "transformed" nmodl program which can be compiled and run by NEURON or + * CoreNEURON simulator. + * + * Implementation Notes: + * - We start with iterating statements in the statement block + * - Function or procedure calls are treated in same way + * - We visit the childrens of a statement and "trap" function calls + * - New ast node WrappedExpression has been added to YAML specification + * to facilitate easily "capturing" function calls. For example, function + * call can appear in argument, condition, binary expression, unary expression + * etc. When inlining happens, we have to replace FunctionCall node with new + * variable name. As visitor receives the pointer to function call, we can't + * replace the node. Hence WrappedExpression node has added. With this we + * can just override visit function of a single node and then can easily + * replace the expression to whatever the result will be + * - In case of lag and table statement inlining is disabled (haven't carefully + * looked into side effects) + * - Inlining pass needs to be run after symbol table pass + * - Inlining is done in recursive way i.e. if A() calls B() then first B() + * is checked for function calls/inlining and so on. This is dony by looking + * ast node of B() in symbol table and visiting it recursively + * - Procedure calls are typically standalone statements i.e. expression statements. + * But note that the nmodl grammar allows procedures to appear as part of expression. + * Procedures always return 0 + * - Standalone procedure or function calls are replaced with the procedure/function body + * + * Examples: + * + PROCEDURE rates_1() { + LOCAL x + rates_2(23.1) + } + + PROCEDURE rates_2(y) { + LOCAL x + x = 21.1*v+y + } + + * The result of inlining : + + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL x, y_in_0 + y_in_0 = 23.1 + x = 21.1*v+y_in_0 + rates_2_in_0 = 0 + } + } + + PROCEDURE rates_2(y) { + LOCAL x + x = 21.1*v+y + } + + * - Arguments for function call are copied into local variables + * - Local statement gets added to callee block (if doesn't exist) + * - Procedure body gets appended with extra assignment statement with variable + * used for returning value. + * + * \todo: + * - Recursive function calls are not supported and need to add checks to avoid stack explosion + * - Currently we rename variables more than necessary, this could be improved [low priority] + * - Function calls as part of an argument of function call itself are not completely inlined [low + priority] + * - Symbol table pass needs to be re-run in order to update the definitions/usage + * - Location of symbol/nodes after inlining still points to old nodes + */ + +class InlineVisitor : public AstVisitor { + private: + /// statement block containing current function call + ast::StatementBlock* caller_block = nullptr; + + /// statement containing current function call + std::shared_ptr<ast::Statement> caller_statement; + + /// symbol table for current statement block (or of parent block if doesn't have) + std::shared_ptr<symtab::SymbolTable> caller_symtab; + + /// symbol tables in call hierarchy + std::stack<std::shared_ptr<symtab::SymbolTable>> symtab_stack; + + /// statement blocks in call hierarchy + std::stack<ast::StatementBlock*> statementblock_stack; + + /// statements being executed in call hierarchy + std::stack<std::shared_ptr<ast::Statement>> statement_stack; + + /// map to track the statements being replaces (typically for procedure calls) + std::map<std::shared_ptr<ast::Statement>, ast::ExpressionStatement*> replaced_statements; + + /// map to track statements being prepended before function call (typically for function calls) + std::map<std::shared_ptr<ast::Statement>, + std::vector<std::shared_ptr<ast::ExpressionStatement>>> + inlined_statements; + + /// map to track replaced function calls (typically for function calls) + std::map<ast::FunctionCall*, std::string> replaced_fun_calls; + + /// variables currently being renamed and their count (for renaming) + std::map<std::string, int> inlined_variables; + + /// true if given statement block can be inlined + bool can_inline_block(ast::StatementBlock* block); + + /// true if statement can be replaced with inlined body + /// this is possible for standalone function/procedure call as statement + bool can_replace_statement(const std::shared_ptr<ast::Statement>& statement); + + /// inline function/procedure into caller block + template <typename T> + void inline_function_call(T* callee, ast::FunctionCall* node, ast::StatementBlock* caller); + + /// add assignement statements into given statement block to inline arguments + void inline_arguments(ast::StatementBlock* inlined_block, + const ast::ArgumentVector& callee_arguments, + const ast::ExpressionVector& caller_expressions); + + /// add assignment statement at end of block (to use as a return statement + /// in case of procedure blocks) + void add_return_variable(ast::StatementBlock* block, std::string& varname); + + /// add local statement to the block if doesn't exist already + void add_local_statement(ast::StatementBlock* node); + + public: + InlineVisitor() = default; + + virtual void visit_function_call(ast::FunctionCall* node) override; + + virtual void visit_statement_block(ast::StatementBlock* node) override; + + virtual void visit_wrapped_expression(ast::WrappedExpression* node) override; +}; + +/** + * Inline function/procedure call + * @tparam T + * @param callee : ast node representing definition of function/procedure being called + * @param node : function/procedure call node + * @param caller : statement block containing function call + */ +template <typename T> +void InlineVisitor::inline_function_call(T* callee, + ast::FunctionCall* node, + ast::StatementBlock* caller) { + std::string function_name = callee->name->get_name(); + + /// do nothing if we can't inline given procedure/function + if (!can_inline_block(callee->statementblock.get())) { + std::cerr << "Can not inline function call to " + function_name; + return; + } + + auto local_variables = get_local_variables(caller); + auto& caller_arguments = node->arguments; + + /// each block should already have local statement (added in statement block's visit function) + if (local_variables == nullptr) { + throw std::logic_error("got local statement as nullptr"); + } + + std::string new_varname = get_new_name(function_name, "in", inlined_variables); + + /// create new variable which will be used for returning value from inlined block + auto name = new ast::Name(new ast::String(new_varname)); + ModToken tok; + name->set_token(tok); + + local_variables->push_back(std::make_shared<ast::LocalVar>(name)); + + /// get a copy of function/procedure body + auto inlined_block = callee->statementblock->clone(); + + /// function definition has function name as return value. we have to rename + /// it with new variable name + RenameVisitor visitor(function_name, new_varname); + inlined_block->visit_children(&visitor); + + /// \todo: have to re-run symtab visitor pass to update symbol table + inlined_block->set_symbol_table(nullptr); + + /// each argument is added as new assignment statement + inline_arguments(inlined_block, callee->arguments, caller_arguments); + + /// to return value from procedure we have to add new variable + if (callee->is_procedure_block()) { + add_return_variable(inlined_block, new_varname); + } + + /// check if caller statement could be replaced and add new statement to appropriate map + bool to_replace = can_replace_statement(caller_statement); + auto statement = new ast::ExpressionStatement(inlined_block); + + if (to_replace) { + replaced_statements[caller_statement] = statement; + } else { + inlined_statements[caller_statement].push_back( + std::shared_ptr<ast::ExpressionStatement>(statement)); + } + + /// variable name which will replace the function call that we just inlined + replaced_fun_calls[node] = new_varname; +} + +#endif // diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 0bb988c8c4..944a5f1796 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -1,36 +1,10 @@ #include "visitors/local_var_rename_visitor.hpp" #include "visitors/rename_visitor.hpp" +#include "visitors/visitor_utils.hpp" using namespace ast; using namespace symtab; -/** Return new name variable by appending "_r_COUNT" where COUNT is number - * of times the given variable is already used. - */ -std::string LocalVarRenameVisitor::get_new_name(const std::string& name) { - int suffix = 0; - if (renamed_variables.find(name) != renamed_variables.end()) { - suffix = renamed_variables[name]; - } - - /// increase counter for next usage - renamed_variables[name] = suffix + 1; - return (name + "_r_" + std::to_string(suffix)); -} - -LocalVarVector* LocalVarRenameVisitor::get_local_variables(StatementBlock* node) { - LocalVarVector* variables = nullptr; - for (const auto& statement : node->statements) { - /// we are only interested in local variable definition statement - if (statement->get_type() == Type::LOCAL_LIST_STATEMENT) { - auto local_statement = std::static_pointer_cast<LocalListStatement>(statement); - variables = &(local_statement->variables); - break; - } - } - return variables; -} - /// rename name conflicting variables in the statement block and it's all children void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { /// nothing to do @@ -74,7 +48,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { /// if symbol represents variable (to avoid renaming use of units like mV) if (s && s->is_variable()) { - std::string new_name = get_new_name(name); + std::string new_name = get_new_name(name, "r", renamed_variables); rename_visitor.set(name, new_name); rename_visitor.visit_statement_block(node); } diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index 33424a7b13..caae9690f4 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -50,12 +50,6 @@ class LocalVarRenameVisitor : public AstVisitor { /// variables currently being renamed and their count std::map<std::string, int> renamed_variables; - /// return local variables for given statement block - ast::LocalVarVector* get_local_variables(ast::StatementBlock* node); - - /// based on usage frequency return new name for given variable - std::string get_new_name(const std::string& name); - public: LocalVarRenameVisitor() = default; virtual void visit_statement_block(ast::StatementBlock* node) override; diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index ac095071cd..b7f8caccef 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -4,6 +4,7 @@ #include "parser/nmodl_driver.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/perf_visitor.hpp" @@ -121,6 +122,11 @@ int main(int argc, const char* argv[]) { v.visit_program(ast.get()); } + { + InlineVisitor v; + v.visit_program(ast.get()); + } + { NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp new file mode 100644 index 0000000000..e2881c9e5b --- /dev/null +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -0,0 +1,27 @@ +#include <string> +#include <map> + +#include "ast/ast.hpp" + +using namespace ast; + +std::string get_new_name(const std::string& name, + const std::string& suffix, + std::map<std::string, int>& variables) { + int counter = 0; + if (variables.find(name) != variables.end()) { + counter = variables[name]; + } + variables[name] = counter + 1; + return (name + "_" + suffix + "_" + std::to_string(counter)); +} + +LocalVarVector* get_local_variables(const StatementBlock* node) { + for (const auto& statement : node->statements) { + if (statement->get_type() == Type::LOCAL_LIST_STATEMENT) { + auto local_statement = std::static_pointer_cast<LocalListStatement>(statement); + return &(local_statement->variables); + } + } + return nullptr; +} diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp new file mode 100644 index 0000000000..c0eb058222 --- /dev/null +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -0,0 +1,17 @@ +#ifndef NMODL_VISITOR_UTILS +#define NMODL_VISITOR_UTILS + +#include <string> +#include <map> + +#include "ast/ast.hpp" + +/** Return new name variable by appending "_suffix_COUNT" where COUNT is number + * of times the given variable is already used. + */ +std::string get_new_name(const std::string& name, + const std::string& suffix, + std::map<std::string, int>& variables); +ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); + +#endif \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index f2f1c9d382..d25468393c 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -4,6 +4,7 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" +#include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -322,7 +323,9 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { THEN("existing variables could be renamed") { std::vector<std::pair<std::string, std::string>> variables = { - {"m", "mm"}, {"gNaTs2_tbar", "new_gNaTs2_tbar"}, {"mAlpha", "mBeta"}, + {"m", "mm"}, + {"gNaTs2_tbar", "new_gNaTs2_tbar"}, + {"mAlpha", "mBeta"}, }; auto result = run_var_rename_visitor(input, variables); REQUIRE(result == expected_output); @@ -526,3 +529,517 @@ SCENARIO("Variable renaming in nested blocks") { } } } + +//============================================================================= +// Procedure/Function inlining tests +//============================================================================= + +std::string run_inline_visitor(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + { + ModelSymbolTable symtab; + SymtabVisitor v(&symtab); + v.visit_program(ast.get()); + } + + { + InlineVisitor v; + v.visit_program(ast.get()); + } + std::stringstream stream; + { + NmodlPrintVisitor v(stream); + v.visit_program(ast.get()); + } + return stream.str(); +} + +SCENARIO("External procedure calls") { + GIVEN("Procedures with external procedure call") { + std::string nmodl_text = R"( + PROCEDURE rates_1() { + hello() + } + + PROCEDURE rates_2() { + bye() + } + )"; + + THEN("nothing gets inlinine") { + std::string input = reindent_text(nmodl_text); + auto result = run_inline_visitor(input); + REQUIRE(result == input); + } + } +} + +SCENARIO("Simple procedure inlining") { + GIVEN("A procedure calling another procedure") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + rates_2(23.1) + } + + PROCEDURE rates_2(y) { + LOCAL x + x = 21.1*v+y + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL x, y_in_0 + y_in_0 = 23.1 + x = 21.1*v+y_in_0 + rates_2_in_0 = 0 + } + } + + PROCEDURE rates_2(y) { + LOCAL x + x = 21.1*v+y + } + )"; + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Nested procedure inlining") { + GIVEN("A procedure with nested call chain and arguments") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, y + rates_2() + rates_3(x, y) + } + + PROCEDURE rates_2() { + LOCAL x + x = 21.1*v + rates_3(x, x+1.1) + } + + PROCEDURE rates_3(a, b) { + LOCAL c + c = 21.1*v+a*b + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, y, rates_2_in_0, rates_3_in_1 + { + LOCAL x, rates_3_in_0 + x = 21.1*v + { + LOCAL c, a_in_0, b_in_0 + a_in_0 = x + b_in_0 = x+1.1 + c = 21.1*v+a_in_0*b_in_0 + rates_3_in_0 = 0 + } + rates_2_in_0 = 0 + } + { + LOCAL c, a_in_1, b_in_1 + a_in_1 = x + b_in_1 = y + c = 21.1*v+a_in_1*b_in_1 + rates_3_in_1 = 0 + } + } + + PROCEDURE rates_2() { + LOCAL x, rates_3_in_0 + x = 21.1*v + { + LOCAL c, a_in_0, b_in_0 + a_in_0 = x + b_in_0 = x+1.1 + c = 21.1*v+a_in_0*b_in_0 + rates_3_in_0 = 0 + } + } + + PROCEDURE rates_3(a, b) { + LOCAL c + c = 21.1*v+a*b + } + )"; + THEN("Nested procedure gets inlined with variables renaming") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inline function call in procedure") { + GIVEN("A procedure with function call") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + x = 12.1+rates_2() + } + + FUNCTION rates_2() { + LOCAL x + x = 21.1*12.1+11 + rates_2 = x + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL x + x = 21.1*12.1+11 + rates_2_in_0 = x + } + x = 12.1+rates_2_in_0 + } + + FUNCTION rates_2() { + LOCAL x + x = 21.1*12.1+11 + rates_2 = x + } + )"; + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Function call within conditional statement") { + GIVEN("A procedure with function call in if statement") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + IF (rates_2()) { + rates_1 = 10 + } ELSE { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL rates_2_in_0 + { + rates_2_in_0 = 10 + } + IF (rates_2_in_0) { + rates_1 = 10 + } ELSE { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + THEN("Procedure body gets inlined and return value is used in if condition") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Multiple function calls in same statement") { + GIVEN("A procedure with two function calls in binary expression") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + IF (rates_2()-rates_2()) { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL rates_2_in_0, rates_2_in_1 + { + rates_2_in_0 = 10 + } + { + rates_2_in_1 = 10 + } + IF (rates_2_in_0-rates_2_in_1) { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("A procedure with multiple function calls in an expression") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + LOCAL x + x = (rates_2()+(rates_2()/rates_2())) + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL x, rates_2_in_0, rates_2_in_1, rates_2_in_2 + { + rates_2_in_0 = 10 + } + { + rates_2_in_1 = 10 + } + { + rates_2_in_2 = 10 + } + x = (rates_2_in_0+(rates_2_in_1/rates_2_in_2)) + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + THEN("Procedure body gets inlined and return values are used in an expression") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Nested function calls withing arguments") { + GIVEN("A procedure with function call") { + std::string input_nmodl = R"( + FUNCTION rates_2() { + IF (rates_3(11,21)) { + rates_2 = 10.1 + } + rates_2 = rates_3(12,22) + } + + FUNCTION rates_1() { + rates_1 = 12.1+rates_2()+exp(12.1) + } + + FUNCTION rates_3(x, y) { + rates_3 = x+y + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_2() { + LOCAL rates_3_in_0, rates_3_in_1 + { + LOCAL x_in_0, y_in_0 + x_in_0 = 11 + y_in_0 = 21 + rates_3_in_0 = x_in_0+y_in_0 + } + IF (rates_3_in_0) { + rates_2 = 10.1 + } + { + LOCAL x_in_1, y_in_1 + x_in_1 = 12 + y_in_1 = 22 + rates_3_in_1 = x_in_1+y_in_1 + } + rates_2 = rates_3_in_1 + } + + FUNCTION rates_1() { + LOCAL rates_2_in_0 + { + LOCAL rates_3_in_0, rates_3_in_1 + { + LOCAL x_in_0, y_in_0 + x_in_0 = 11 + y_in_0 = 21 + rates_3_in_0 = x_in_0+y_in_0 + } + IF (rates_3_in_0) { + rates_2_in_0 = 10.1 + } + { + LOCAL x_in_1, y_in_1 + x_in_1 = 12 + y_in_1 = 22 + rates_3_in_1 = x_in_1+y_in_1 + } + rates_2_in_0 = rates_3_in_1 + } + rates_1 = 12.1+rates_2_in_0+exp(12.1) + } + + FUNCTION rates_3(x, y) { + rates_3 = x+y + } + )"; + + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Function call in non-binary expression") { + GIVEN("A function call in unary expression") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + x = (-rates_2(23.1)) + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL y_in_0 + y_in_0 = 23.1 + rates_2_in_0 = 21.1*v+y_in_0 + } + x = (-rates_2_in_0) + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + THEN("Function gets inlined in the unary expression") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("A function call as part of function argument itself") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + rates_1 = 10 + rates_2( rates_2(11) ) + } + + FUNCTION rates_2(x) { + rates_2 = 10+x + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL rates_2_in_0, rates_2_in_1 + { + LOCAL x_in_0 + x_in_0 = 11 + rates_2_in_0 = 10+x_in_0 + } + { + LOCAL x_in_1 + x_in_1 = rates_2_in_0 + rates_2_in_1 = 10+x_in_1 + } + rates_1 = 10+rates_2_in_1 + } + + FUNCTION rates_2(x) { + rates_2 = 10+x + } + )"; + THEN("Function and function arguments gets inlined recursively") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Procedure call as standalone statement as well as part of expression") { + GIVEN("A procedure call in expression and statement") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + x = 10 + rates_2() + rates_2() + } + + PROCEDURE rates_2() { + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0, rates_2_in_1 + { + rates_2_in_0 = 0 + } + x = 10+rates_2_in_0 + { + rates_2_in_1 = 0 + } + } + + PROCEDURE rates_2() { + } + )"; + THEN("Return statement from procedure (with zero value) is used") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} From fc5beb0f43c84b8416f669e932200764d0ff41a0 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 24 Jan 2018 11:54:07 +0100 Subject: [PATCH 045/871] Run local rename visitor as part of inlining: - not necessary to rename all variables especially from blocks those not containing any procedure calls - local renamer not necessary to run before inline pass now Change-Id: I8eaf97068d708139ea1aa51c46960496e51536e6 NMODL Repo SHA: BlueBrain/nmodl@a4cc308d269c234deae17fd164f8b8b365f9e445 --- src/nmodl/visitors/inline_visitor.hpp | 7 +++ test/nmodl/transpiler/visitor/visitor.cpp | 52 +++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 132291dd5f..6b2cf03858 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -8,6 +8,7 @@ #include "visitors/ast_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" +#include "visitors/local_var_rename_visitor.hpp" #include "symtab/symbol_table.hpp" /** @@ -192,6 +193,12 @@ void InlineVisitor::inline_function_call(T* callee, return; } + /// make sure to rename conflicting local variable in caller block + /// because in case of procedure inlining they can conflict with + /// global variables used in procedure being inlined + LocalVarRenameVisitor v; + v.visit_statement_block(caller); + auto local_variables = get_local_variables(caller); auto& caller_arguments = node->arguments; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index d25468393c..5b4d7c374b 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1043,3 +1043,55 @@ SCENARIO("Procedure call as standalone statement as well as part of expression") } } } + +SCENARIO("Procedure inlining handles local-global name conflict") { + GIVEN("A procedure with local variable that exist in global scope") { + + /// note that x in rates_2 should still update global x after inlining + std::string input_nmodl = R"( + NEURON { + RANGE x + } + + PROCEDURE rates_1() { + LOCAL x + x = 12 + rates_2(x) + x = 11 + } + + PROCEDURE rates_2(y) { + x = 10+y + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE x + } + + PROCEDURE rates_1() { + LOCAL x_r_0, rates_2_in_0 + x_r_0 = 12 + { + LOCAL y_in_0 + y_in_0 = x_r_0 + x = 10+y_in_0 + rates_2_in_0 = 0 + } + x_r_0 = 11 + } + + PROCEDURE rates_2(y) { + x = 10+y + } + )"; + + THEN("Caller variables get renamed first and then inlining is done") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} \ No newline at end of file From 51d5895cdbf60b7d9bfab69550de5325ecb96339 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 24 Jan 2018 12:11:52 +0100 Subject: [PATCH 046/871] Remove tracking of caller block symtab because we need symtab to only lookup procedure or function definitions. It is sufficient to use symtab from program node. Change-Id: I95d695ddb6df2f6a20d62328ed096e51b2ad1257 NMODL Repo SHA: BlueBrain/nmodl@2e23a1160375ba95238731b08705dea017dfa22a --- src/nmodl/visitors/inline_visitor.cpp | 25 +++++++++---------------- src/nmodl/visitors/inline_visitor.hpp | 9 ++++----- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 5198af839c..3da9877bef 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -100,7 +100,7 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { node->visit_children(this); std::string function_name = node->name->get_name(); - auto symbol = caller_symtab->lookup_in_scope(function_name); + auto symbol = program_symtab->lookup_in_scope(function_name); /// nothing to do if called function is not defined or it's external if (symbol == nullptr || symbol->is_external_symbol_only()) { @@ -133,17 +133,6 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { caller_block = node; statementblock_stack.push(node); - /** Some statements like forall, from, while are statements but contain - * statement block. They don't have symbol table. Hence we check if there - * is a symbol table and then only we push it on stack. In case of nullptr, - * we use parent's symbol table. - */ - auto symtab = node->get_symbol_table(); - if (symtab) { - caller_symtab = symtab; - } - symtab_stack.push(caller_symtab); - /** Add empty local statement at the begining of block if already doesn't exist. * Why? When we iterate over statements and inline function call, we have to add * local variable to return the result. As we can't modify vector while iterating, @@ -192,7 +181,6 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { * stack is already empty. */ statementblock_stack.pop(); - symtab_stack.pop(); if (!statement_stack.empty()) { caller_statement = statement_stack.top(); @@ -200,9 +188,6 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { if (!statementblock_stack.empty()) { caller_block = statementblock_stack.top(); } - if (!symtab_stack.empty()) { - caller_symtab = symtab_stack.top(); - } } /** Visit all wrapped expressions which can contain function calls. @@ -219,3 +204,11 @@ void InlineVisitor::visit_wrapped_expression(WrappedExpression* node) { } } } + +void InlineVisitor::visit_program(Program* node) { + program_symtab = node->get_symbol_table(); + if(program_symtab == nullptr) { + throw std::runtime_error("Program node doesn't have symbol table"); + } + node->visit_children(this); +} \ No newline at end of file diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 6b2cf03858..dbe08a31a4 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -115,11 +115,8 @@ class InlineVisitor : public AstVisitor { /// statement containing current function call std::shared_ptr<ast::Statement> caller_statement; - /// symbol table for current statement block (or of parent block if doesn't have) - std::shared_ptr<symtab::SymbolTable> caller_symtab; - - /// symbol tables in call hierarchy - std::stack<std::shared_ptr<symtab::SymbolTable>> symtab_stack; + /// symbol table for program node + std::shared_ptr<symtab::SymbolTable> program_symtab; /// statement blocks in call hierarchy std::stack<ast::StatementBlock*> statementblock_stack; @@ -172,6 +169,8 @@ class InlineVisitor : public AstVisitor { virtual void visit_statement_block(ast::StatementBlock* node) override; virtual void visit_wrapped_expression(ast::WrappedExpression* node) override; + + virtual void visit_program(Program* node) override; }; /** From cabf447799ac731d40b6cebb692aaae3cff3ed9b Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 31 Jan 2018 19:32:47 +0100 Subject: [PATCH 047/871] Memory leak fix in symbol table implementation: - Model symbol table had shared_ptr for child and parent links resulted into cyclic reference and hence memory leak. - Multiple ast nodes could get same symbol table (e.g. statement block). It makes more sense to store model symbol table in program node and ast nodes only have raw pointers. - Symtab visitor pass doesnt need ModelSymbolTable argument to constructor - Valgrind now doesnt show memory leak Change-Id: Icb1ade217fd9a5c8466deeb5418f3ae001acdbc2 NMODL Repo SHA: BlueBrain/nmodl@dc9415ee9fe3384983059871e2d6e164c1ed4cae --- src/nmodl/ast/ast_utils.hpp | 7 ++--- src/nmodl/language/ast_printer.py | 24 +++++++++++---- src/nmodl/language/visitors_printer.py | 18 +++++++---- src/nmodl/symtab/symbol.hpp | 6 +++- src/nmodl/symtab/symbol_table.cpp | 30 ++++++++++++------- src/nmodl/symtab/symbol_table.hpp | 25 ++++++---------- src/nmodl/visitors/inline_visitor.cpp | 8 ++--- src/nmodl/visitors/inline_visitor.hpp | 2 +- .../visitors/local_var_rename_visitor.cpp | 2 +- .../visitors/local_var_rename_visitor.hpp | 4 +-- src/nmodl/visitors/main.cpp | 6 ++-- src/nmodl/visitors/perf_visitor.hpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 10 +++++++ test/nmodl/transpiler/symtab/symbol_table.cpp | 5 ++-- test/nmodl/transpiler/visitor/visitor.cpp | 21 ++++++------- 15 files changed, 102 insertions(+), 68 deletions(-) diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index cfbd096ee4..2a74ce4439 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -4,10 +4,7 @@ #include <string> #include "lexer/modtoken.hpp" - -namespace symtab { - class SymbolTable; -} +#include "symtab/symbol_table.hpp" namespace ast { /* enumaration of all binary operators in the language */ @@ -91,7 +88,7 @@ namespace ast { return nullptr; } - virtual std::shared_ptr<symtab::SymbolTable> get_symbol_table() { + virtual symtab::SymbolTable* get_symbol_table() { throw std::runtime_error("get_symbol_table() not implemented"); } diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 53f496720e..b6702dabec 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -128,9 +128,13 @@ def ast_classes_declaration(self): if node.has_token: self.write_line("std::shared_ptr<ModToken> token;") - if node.is_symtab_needed(): - self.write_line("std::shared_ptr<symtab::SymbolTable> symtab;", newline=2) + self.write_line("symtab::SymbolTable* symtab = nullptr;") + + if node.is_program_node(): + self.write_line("std::shared_ptr<symtab::ModelSymbolTable> model_symtab;") + + self.write_line("") if members: self.write_line("/* constructors */") @@ -197,8 +201,12 @@ def ast_classes_declaration(self): self.write_line("void set_token(ModToken& tok) " + " { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") if node.is_symtab_needed(): - self.write_line("void set_symbol_table(std::shared_ptr<symtab::SymbolTable> newsymtab) " + " { symtab = newsymtab; }") - self.write_line("std::shared_ptr<symtab::SymbolTable> get_symbol_table() override " + " { return symtab; }") + self.write_line("void set_symbol_table(symtab::SymbolTable* newsymtab) " + " { symtab = newsymtab; }") + self.write_line("symtab::SymbolTable* get_symbol_table() override " + " { return symtab; }") + + if node.is_program_node(): + self.write_line("void init_model_symbol_table();") + self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() " + " { return model_symtab.get(); }") if node.is_number_node(): self.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") @@ -251,7 +259,8 @@ class AstDefinitionPrinter(DefinitionPrinter): """Prints AST class definitions""" def headers(self): - self.write_line('#include "ast/ast.hpp"', newline=2) + self.write_line('#include "ast/ast.hpp"') + self.write_line('#include "symtab/symbol_table.hpp"', newline=2) def definitions(self): self.write_line("namespace ast {", post_gutter=1) @@ -300,6 +309,11 @@ def definitions(self): else: self.write_line("void " + node.class_name + "::visit_children(Visitor* /*v*/) {}") + if node.is_program_node(): + self.write_line("void Program::init_model_symbol_table() " + " {") + self.write_line(" model_symtab = std::make_shared<symtab::ModelSymbolTable>();") + self.write_line("}") + if members: # TODO : constructor definition : remove this with C++11 style self.write_line("/* constructor for " + node.class_name + " ast node */") diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index c81263e8d5..566dda0162 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -174,13 +174,13 @@ def private_declaration(self): def public_declaration(self): self.write_line("public:", post_gutter=1) - line = self.classname + "(ModelSymbolTable* symtab) : modsymtab(symtab), printer(new JSONPrinter()) {} " + line = self.classname + "() : printer(new JSONPrinter()) {} " self.write_line(line) - line = self.classname + "( ModelSymbolTable* symtab, std::stringstream &ss) : modsymtab(symtab), printer(new JSONPrinter(ss)) {}" + line = self.classname + "(std::stringstream &ss) : printer(new JSONPrinter(ss)) {}" self.write_line(line) - line = self.classname + "( ModelSymbolTable* symtab, std::string filename) : modsymtab(symtab), printer(new JSONPrinter(filename)) {}" + line = self.classname + "(std::string filename) : printer(new JSONPrinter(filename)) {}" self.write_line(line, newline=2) # helper function for creating symbol for variables @@ -191,7 +191,7 @@ def public_declaration(self): # without name (e.g. parameter, unit, breakpoint) self.write_line("template<typename T>") line = "void setup_symbol_table(T *node, std::string name, bool is_global);" - self.write_line(line) + self.write_line(line, newline=2) # helper function for creating symbol table for blocks # with name (e.g. procedure, function, derivative) @@ -199,6 +199,11 @@ def public_declaration(self): line = "void setup_symbol_table(T *node, std::string name, SymbolInfo property, bool is_global);" self.write_line(line, newline=2) + # helper function to setup program symbol table + self.write_line("template<typename T>") + line = "void setup_program_symbol_table(T *node, std::string name, bool is_global);" + self.write_line(line, newline=2) + # we have to override visitor methods for the nodes # which goes into symbol table for node in self.nodes: @@ -242,7 +247,10 @@ def definitions(self): if node.is_symbol_block_node(): fun_call = "setup_symbol_table(node, node->get_name(), " + property_name + ", false);" - elif node.is_program_node() or node.is_global_block_node(): + elif node.is_program_node(): + fun_call = "setup_program_symbol_table(node, node->get_type_name(), true);" + + elif node.is_global_block_node(): fun_call = "setup_symbol_table(node, node->get_type_name(), true);" else: diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index e1a15ae269..198f01adaf 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -3,9 +3,13 @@ #include <map> -#include "ast/ast.hpp" +#include "lexer/modtoken.hpp" #include "symtab/symbol_properties.hpp" +namespace ast { + class AST; +} + namespace symtab { using namespace ast; diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 2c0d6dae8b..6335095cbf 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -1,6 +1,6 @@ #include <utility> -#include "lexer/token_mapping.hpp" +#include "ast/ast.hpp" #include "symtab/symbol_table.hpp" #include "utils/table_data.hpp" @@ -33,6 +33,18 @@ namespace symtab { parent = nullptr; } + std::string SymbolTable::type() const { + return node->get_type_name(); + } + + std::string SymbolTable::position() const { + auto token = node->get_token(); + if (token) + return token->position(); + else + return ModToken().position(); + } + /// insert new symbol table of one of the children block void SymbolTable::insert_table(const std::string& name, std::shared_ptr<SymbolTable> table) { if (children.find(name) != children.end()) { @@ -208,16 +220,14 @@ namespace symtab { * The same symbol table is returned so that visitor can store pointer to * symbol table within a node. */ - std::shared_ptr<SymbolTable> ModelSymbolTable::enter_scope(std::string name, - AST* node, - bool global) { + SymbolTable* ModelSymbolTable::enter_scope(std::string name, AST* node, bool global) { if (node == nullptr) { throw std::runtime_error("Can't enter with empty node"); } /// all global blocks in mod file have same symbol table if (symtab && global) { - parent_symtab = symtab; + parent_symtab = symtab.get(); return parent_symtab; } @@ -228,7 +238,7 @@ namespace symtab { /// statement block within global scope is part of global block itself if (symtab && node->is_statement_block() && parent_symtab->under_global_scope()) { - parent_symtab = symtab; + parent_symtab = symtab.get(); return parent_symtab; } @@ -241,13 +251,13 @@ namespace symtab { // table and then new symbol table becomes parent for future blocks if (symtab == nullptr) { symtab = new_symtab; - parent_symtab = new_symtab; + parent_symtab = new_symtab.get(); } else { parent_symtab->insert_table(name, new_symtab); new_symtab->set_parent_table(parent_symtab); - parent_symtab = new_symtab; + parent_symtab = new_symtab.get(); } - return new_symtab; + return new_symtab.get(); } /** Callback at the exit of every block in nmodl file When we reach @@ -268,7 +278,7 @@ namespace symtab { /// current symbol table. this is happening for global /// scope symbol table if (parent_symtab == nullptr) { - parent_symtab = symtab; + parent_symtab = symtab.get(); } } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 479c5efdc3..f6b6196a95 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -2,6 +2,7 @@ #define _NMODL_SYMTAB_HPP_ #include <map> +#include <memory> #include "symtab/symbol.hpp" @@ -74,7 +75,7 @@ namespace symtab { bool global = false; /// pointer to the symbol table of parent block in the mod file - std::shared_ptr<SymbolTable> parent = nullptr; + SymbolTable* parent = nullptr; /// symbol table for each enclosing block in the current nmodl block /// construct. for example, for every block statement (like if, while, @@ -93,26 +94,18 @@ namespace symtab { return symtab_name; } - std::string type() const { - return node->get_type_name(); - } + std::string type() const; std::string title(); /// todo: set token for every block from parser - std::string position() const { - auto token = node->get_token(); - if (token) - return token->position(); - else - return ModToken().position(); - } + std::string position() const; bool global_scope() const { return global; } - std::shared_ptr<SymbolTable> get_parent_table() const { + SymbolTable* get_parent_table() const { return parent; } @@ -128,7 +121,7 @@ namespace symtab { table.insert(symbol); } - void set_parent_table(std::shared_ptr<SymbolTable> block) { + void set_parent_table(SymbolTable* block) { parent = block; } @@ -175,7 +168,7 @@ namespace symtab { std::shared_ptr<SymbolTable> symtab = nullptr; /// symbol table for parent block (used during symbol table construction) - std::shared_ptr<SymbolTable> parent_symtab = nullptr; + SymbolTable* parent_symtab = nullptr; /// Return unique name by appending some counter value std::string get_unique_name(std::string name, AST* node); @@ -185,7 +178,7 @@ namespace symtab { public: /// entering into new nmodl block - std::shared_ptr<SymbolTable> enter_scope(std::string name, AST* node, bool global); + SymbolTable* enter_scope(std::string name, AST* node, bool global); /// leaving current nmodl block void leave_scope(); @@ -200,4 +193,4 @@ namespace symtab { } // namespace symtab -#endif \ No newline at end of file +#endif diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 3da9877bef..415b7fa096 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -206,9 +206,9 @@ void InlineVisitor::visit_wrapped_expression(WrappedExpression* node) { } void InlineVisitor::visit_program(Program* node) { - program_symtab = node->get_symbol_table(); - if(program_symtab == nullptr) { - throw std::runtime_error("Program node doesn't have symbol table"); - } + program_symtab = node->get_symbol_table(); + if (program_symtab == nullptr) { + throw std::runtime_error("Program node doesn't have symbol table"); + } node->visit_children(this); } \ No newline at end of file diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index dbe08a31a4..e499376d2e 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -116,7 +116,7 @@ class InlineVisitor : public AstVisitor { std::shared_ptr<ast::Statement> caller_statement; /// symbol table for program node - std::shared_ptr<symtab::SymbolTable> program_symtab; + symtab::SymbolTable* program_symtab = nullptr; /// statement blocks in call hierarchy std::stack<ast::StatementBlock*> statementblock_stack; diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 944a5f1796..11f35d23c2 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -29,7 +29,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { auto variables = get_local_variables(node); - std::shared_ptr<SymbolTable> parent_symtab; + SymbolTable* parent_symtab = nullptr; if (symtab) { parent_symtab = symtab->get_parent_table(); } diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index caae9690f4..1037babd3f 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -42,10 +42,10 @@ class LocalVarRenameVisitor : public AstVisitor { private: /// non-null symbol table in the scope hierarchy - std::shared_ptr<symtab::SymbolTable> symtab; + symtab::SymbolTable* symtab = nullptr; /// symbol tables in case of nested blocks - std::stack<std::shared_ptr<symtab::SymbolTable>> symtab_stack; + std::stack<symtab::SymbolTable*> symtab_stack; /// variables currently being renamed and their count std::map<std::string, int> renamed_variables; diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index b7f8caccef..ced5ce65be 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -87,16 +87,16 @@ int main(int argc, const char* argv[]) { { // todo : we should pass this or use get method to retrieve? - ModelSymbolTable symtab; std::stringstream ss1; - SymtabVisitor v(&symtab, ss1); + SymtabVisitor v(ss1); v.visit_program(ast.get()); // std::cout << ss1.str(); std::stringstream ss2; - symtab.print(ss2); + auto symtab = ast->get_model_symbol_table(); + symtab->print(ss2); std::cout << ss2.str(); std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index a2f55abddb..2a46702282 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -36,7 +36,7 @@ class PerfVisitor : public AstVisitor { private: /// symbol table of current block being visited - std::shared_ptr<symtab::SymbolTable> current_symtab; + symtab::SymbolTable* current_symtab = nullptr; /// performance stats of all blocks being visited /// in recursive chain diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 6ffc13442e..825bf62aa3 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -44,6 +44,16 @@ void SymtabVisitor::setup_symbol_table(T* node, setup_symbol_table(node, name, is_global); } +template <typename T> +void SymtabVisitor::setup_program_symbol_table(T* node, std::string name, bool is_global) { + modsymtab = node->get_model_symbol_table(); + if (modsymtab == nullptr) { + node->init_model_symbol_table(); + modsymtab = node->get_model_symbol_table(); + } + setup_symbol_table(node, name, is_global); +} + template <typename T> void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global) { /// entering into new nmodl block diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 940b2dff8d..b6bb0c57b5 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -3,6 +3,7 @@ #include <string> #include "catch/catch.hpp" +#include "ast/ast.hpp" #include "symtab/symbol.hpp" #include "symtab/symbol_table.hpp" @@ -172,7 +173,7 @@ SCENARIO("Symbol table operations") { table->insert(symbol); auto next_program = std::make_shared<ast::Program>(); auto next_table = std::make_shared<SymbolTable>("Ca", next_program.get(), true); - next_table->set_parent_table(table); + next_table->set_parent_table(table.get()); THEN("children symbol table can lookup into parent table scope") { REQUIRE(next_table->lookup("alpha") == nullptr); REQUIRE(next_table->lookup_in_scope("alpha") != nullptr); @@ -251,4 +252,4 @@ SCENARIO("Model symbol table operations") { } } } -} \ No newline at end of file +} diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 5b4d7c374b..7806abb421 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -146,30 +146,30 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { auto ast = driver.ast(); WHEN("Symbol table generator pass runs") { - ModelSymbolTable symtab; - SymtabVisitor v(&symtab); + SymtabVisitor v; v.visit_program(ast.get()); + auto symtab = ast->get_model_symbol_table(); using namespace symtab::details; THEN("Can lookup for defined variables") { - auto symbol = symtab.lookup("m"); + auto symbol = symtab->lookup("m"); REQUIRE(symbol->has_properties(NmodlInfo::dependent_def)); REQUIRE_FALSE(symbol->has_properties(NmodlInfo::local_var)); - symbol = symtab.lookup("gNaTs2_tbar"); + symbol = symtab->lookup("gNaTs2_tbar"); REQUIRE(symbol->has_properties(NmodlInfo::param_assign)); REQUIRE(symbol->has_properties(NmodlInfo::range_var)); - symbol = symtab.lookup("ena"); + symbol = symtab->lookup("ena"); REQUIRE(symbol->has_properties(NmodlInfo::read_ion_var)); } THEN("Can lookup for defined functions") { - auto symbol = symtab.lookup("hBetaf"); + auto symbol = symtab->lookup("hBetaf"); REQUIRE(symbol->has_properties(NmodlInfo::function_block)); } THEN("Non existent variable lookup returns nullptr") { - REQUIRE(symtab.lookup("xyz") == nullptr); + REQUIRE(symtab->lookup("xyz") == nullptr); } WHEN("Perf visitor pass runs after symtab visitor") { @@ -350,8 +350,7 @@ std::string run_local_var_rename_visitor(const std::string& text) { auto ast = driver.ast(); { - ModelSymbolTable symtab; - SymtabVisitor v(&symtab); + SymtabVisitor v; v.visit_program(ast.get()); } @@ -540,8 +539,7 @@ std::string run_inline_visitor(const std::string& text) { auto ast = driver.ast(); { - ModelSymbolTable symtab; - SymtabVisitor v(&symtab); + SymtabVisitor v; v.visit_program(ast.get()); } @@ -1046,7 +1044,6 @@ SCENARIO("Procedure call as standalone statement as well as part of expression") SCENARIO("Procedure inlining handles local-global name conflict") { GIVEN("A procedure with local variable that exist in global scope") { - /// note that x in rates_2 should still update global x after inlining std::string input_nmodl = R"( NEURON { From e182e818521f22d4ccae35f3ee3bab76f2d47f62 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 23 Jan 2018 14:58:07 +0100 Subject: [PATCH 048/871] First draft implementation of localizer and def-use chain analysis passes: - Implemented DefUseAnalyzer as new pass - Visitor utils have new functions: add local statement, add local variable - Added test for def-use analysis and localizer passes Support for adding getter method for members from YAML specification - get_statement_block method added for Block node - added test for verbatim block in localizer - rebased on inlining branch Change-Id: I97470c64e6ab922c740429c6cb51ef4ccecf553e NMODL Repo SHA: BlueBrain/nmodl@1924a82e382b5fcaaa75fdaf394ba7adfaa60f43 --- .clang-format | 2 +- src/nmodl/ast/ast_utils.hpp | 2 +- src/nmodl/language/argument.py | 4 +- src/nmodl/language/ast_printer.py | 21 +- src/nmodl/language/nmodl.yaml | 22 +- src/nmodl/language/nodes.py | 9 + src/nmodl/language/parser.py | 6 + src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/visitors/CMakeLists.txt | 4 + src/nmodl/visitors/defuse_analyze_visitor.cpp | 308 ++++++++++++ src/nmodl/visitors/defuse_analyze_visitor.hpp | 253 ++++++++++ src/nmodl/visitors/inline_visitor.cpp | 16 +- src/nmodl/visitors/inline_visitor.hpp | 3 - src/nmodl/visitors/localize_visitor.cpp | 91 ++++ src/nmodl/visitors/localize_visitor.hpp | 88 ++++ src/nmodl/visitors/main.cpp | 23 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 4 - src/nmodl/visitors/visitor_utils.cpp | 20 + src/nmodl/visitors/visitor_utils.hpp | 3 + test/nmodl/transpiler/visitor/visitor.cpp | 453 +++++++++++++++++- 20 files changed, 1295 insertions(+), 39 deletions(-) create mode 100644 src/nmodl/visitors/defuse_analyze_visitor.cpp create mode 100644 src/nmodl/visitors/defuse_analyze_visitor.hpp create mode 100644 src/nmodl/visitors/localize_visitor.cpp create mode 100644 src/nmodl/visitors/localize_visitor.hpp diff --git a/.clang-format b/.clang-format index 88e0406fbe..2bd8848b20 100644 --- a/.clang-format +++ b/.clang-format @@ -61,7 +61,7 @@ IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' -MaxEmptyLinesToKeep: 1 +MaxEmptyLinesToKeep: 2 NamespaceIndentation: All ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 2a74ce4439..721a023c52 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -383,7 +383,7 @@ namespace ast { return false; } - virtual bool is_non_lin_euation() { + virtual bool is_non_lin_equation() { return false; } diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 5dafcf0fec..7be8c75e04 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -16,4 +16,6 @@ def __init__(self): self.is_optional = False self.add_method = False self.getname_method = False - self.has_token = False \ No newline at end of file + self.has_token = False + self.getter_method = False + self.getter_override = False \ No newline at end of file diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index b6702dabec..d031353f35 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -132,7 +132,7 @@ def ast_classes_declaration(self): self.write_line("symtab::SymbolTable* symtab = nullptr;") if node.is_program_node(): - self.write_line("std::shared_ptr<symtab::ModelSymbolTable> model_symtab;") + self.write_line("symtab::ModelSymbolTable model_symtab;") self.write_line("") @@ -180,6 +180,12 @@ def ast_classes_declaration(self): get_method_added = True + if child.getter_method: + getter_method = child.getter_method + getter_override = " override" if child.getter_override else "" + return_type = "std::shared_ptr<" + class_name + "> " + self.write_line(return_type + getter_method + "()" + getter_override + " { return " + varname + "; }") + if node.is_prime_node() and child.varname == ORDER_VAR_NAME: self.write_line("int get_order() " + " { return " + ORDER_VAR_NAME + "->eval(); }") @@ -205,8 +211,7 @@ def ast_classes_declaration(self): self.write_line("symtab::SymbolTable* get_symbol_table() override " + " { return symtab; }") if node.is_program_node(): - self.write_line("void init_model_symbol_table();") - self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() " + " { return model_symtab.get(); }") + self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() " + " { return &model_symtab; }") if node.is_number_node(): self.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") @@ -223,6 +228,11 @@ def ast_classes_declaration(self): if node.is_name_node(): self.write_line(virtual + "void set_name(std::string name)" + override + " { value->set(name); }") + if node.is_base_block_node(): + self.write_line("virtual std::shared_ptr<StatementBlock> get_statement_block() {") + self.write_line(' throw std::runtime_error("get_statement_node not implemented");') + self.write_line("}") + # if node is of enum type then return enum value # TODO: hardcoded Names if node.is_data_type_node(): @@ -309,11 +319,6 @@ def definitions(self): else: self.write_line("void " + node.class_name + "::visit_children(Visitor* /*v*/) {}") - if node.is_program_node(): - self.write_line("void Program::init_model_symbol_table() " + " {") - self.write_line(" model_symtab = std::make_shared<symtab::ModelSymbolTable>();") - self.write_line("}") - if members: # TODO : constructor definition : remove this with C++11 style self.write_line("/* constructor for " + node.class_name + " ast node */") diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 7dc1fb5526..34a1e91379 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -311,10 +311,10 @@ # Expression:UnaryExpression => UnaryOperator<op> Expression<expression> # # After reading all bison productions it is clear that two basic classes derived from Expression -# are needed : LinEquation and NonLinEuation: +# are needed : LinEquation and NonLinEquation: # Todo : add more NMODL tests for validation # -# Expression:NonLinEuation("~") => Expression<lhs>(":=") Expression<rhs> +# Expression:NonLinEquation("~") => Expression<lhs>(":=") Expression<rhs> # Expression:LinEquation("~") => Expression<leftlinexpr>(":=") Expression<linexpr> # # Function call (must be expression because it could be called in expression definition): @@ -768,16 +768,19 @@ members: - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - ConstructorBlock: nmodl: CONSTRUCTOR members: - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - DestructorBlock: nmodl: DESTRUCTOR members: - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - StatementBlock: members: - statements: @@ -791,6 +794,7 @@ getname: true - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - LinearBlock: nmodl: "LINEAR " members: @@ -804,6 +808,7 @@ prefix: {value: " SOLVEFOR "} - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - NonLinearBlock: nmodl: "NONLINEAR " members: @@ -817,6 +822,7 @@ prefix: {value: " SOLVEFOR "} - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - DiscreteBlock: nmodl: "DISCRETE " members: @@ -825,6 +831,7 @@ getname: true - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - PartialBlock: nmodl: "PARTIAL " members: @@ -833,6 +840,7 @@ getname: true - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - FunctionTableBlock: nmodl: "FUNCTION_TABLE " members: @@ -867,6 +875,7 @@ prefix: {value: " "} - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - ProcedureBlock: nmodl: "PROCEDURE " members: @@ -884,6 +893,7 @@ optional: true - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - NetReceiveBlock: nmodl: "NET_RECEIVE " members: @@ -895,6 +905,7 @@ separator: ", " - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - SolveBlock: nmodl: SOLVE members: @@ -913,11 +924,13 @@ members: - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - TerminalBlock: nmodl: TERMINAL members: - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - BeforeBlock: nmodl: "BEFORE " members: @@ -934,6 +947,7 @@ type: BABlockType - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - ForNetcon: nmodl: "FOR_NETCONS " members: @@ -945,6 +959,7 @@ separator: ", " - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - KineticBlock: nmodl: "KINETIC " members: @@ -957,6 +972,7 @@ separator: "," - statementblock: type: StatementBlock + getter: {name: get_statement_block, override: true} - MatchBlock: nmodl: MATCH members: @@ -1066,7 +1082,7 @@ type: UnaryOperator - expression: type: Expression - - NonLinEuation: + - NonLinEquation: nmodl: "~ " members: - lhs: diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 7ae5a239c4..1badc0b1fb 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -158,6 +158,8 @@ def __init__(self, args): self.optional = args.is_optional self.add_method = args.add_method self.getname_method = args.getname_method + self.getter_method = args.getter_method + self.getter_override = args.getter_override def get_typename(self): """returns type of the node for declaration @@ -223,6 +225,13 @@ def has_parent_block_node(self): """ return True if self.base_class == BASE_BLOCK else False + def is_base_block_node(self): + """ + check if node is Block + :return: True if node type/name is BASE_BLOCK + """ + return True if self.class_name == BASE_BLOCK else False + def is_symtab_needed(self): """ Check if symbol tabel needed for current node diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 76655d10b9..12acaf41e8 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -77,6 +77,12 @@ def parse_child_rule(self, child): if 'getname' in properties: args.getname_method = properties['getname'] + # if getter method required + if 'getter' in properties: + args.getter_method = properties['getter']['name'] + if 'override' in properties['getter']: + args.getter_override = properties['getter']['override'] + # if there is nmodl name if 'nmodl' in properties: args.nmodl_name = properties['nmodl'] diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index f41c3c8bde..c54ab41e4e 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -984,7 +984,7 @@ asgn : varname "=" expr } | nonlineqn expr "=" expr { - $$ = new ast::NonLinEuation($2, $4); + $$ = new ast::NonLinEquation($2, $4); } | lineqn leftlinexpr "=" linexpr { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 84593eff19..0965d75b37 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -20,6 +20,10 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ) diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp new file mode 100644 index 0000000000..b07f34268d --- /dev/null +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -0,0 +1,308 @@ +#include <algorithm> +#include <utility> + +#include "visitors/defuse_analyze_visitor.hpp" + +using namespace ast; + +/// DUState to string conversion for pretty-printing +std::string to_string(DUState state) { + switch (state) { + case DUState::U: + return "U"; + case DUState::D: + return "D"; + case DUState::LU: + return "LU"; + case DUState::LD: + return "LD"; + case DUState::CONDITIONAL_BLOCK: + return "CONDITIONAL_BLOCK"; + case DUState::IF: + return "IF"; + case DUState::ELSEIF: + return "ELSEIF"; + case DUState::ELSE: + return "ELSE"; + case DUState::UNKNOWN: + return "UNKNOWN"; + case DUState::NONE: + return "NONE"; + default: + throw std::runtime_error("Unhandled DUState?"); + } +} + +std::ostream& operator<<(std::ostream& os, DUState state) { + os << to_string(state); + return os; +} + +/// DUInstance to JSON string +void DUInstance::print(JSONPrinter& printer) { + if (children.empty()) { + printer.add_node(to_string(state)); + } else { + printer.push_block(to_string(state)); + for (auto& inst : children) { + inst.print(printer); + } + printer.pop_block(); + } +} + +/// DUChain to JSON string +std::string DUChain::to_string(bool compact) { + std::stringstream stream; + JSONPrinter printer(stream); + printer.compact_json(compact); + + printer.push_block(name); + for (auto& instance : chain) { + instance.print(printer); + } + printer.pop_block(); + + printer.flush(); + return stream.str(); +} + +/** Evaluate sub-blocks like if, elseif and else + * As these are innermost blocks, we have to just check first use + * of variable in this block and that's the result of this block. + */ +DUState DUInstance::sub_block_eval() { + DUState result = DUState::NONE; + for (auto& chain : children) { + auto child_state = chain.eval(); + if (child_state == DUState::U || child_state == DUState::D) { + result = child_state; + break; + } + } + return result; +} + +/** Evaluate conditional block that contain sub-blocks like if, elseif and else. + * Note that sub-blocks are already evaluated by sub_block_eval() and has only + * single value. In order to find effective usage, we are using following rules: + * - If variable is "used" in any of the sub-block then it's effectively + * "U". This is because any branch can be taken. + * - If variable is "defined" in all sub-blocks doesn't mean that it's + * effectively "D". This is because if we can just have "if-elseif" + * which could be never be taken. Same for empty "if". In order to + * decide if it is "D", we make sure there is no empty block and there + * must be "else" block with "D". Note that "U" definitions are already + * covered in 1) and hence this rule is safe. + * - If there is an "if" with "D" or empty "if" followed by "D" in "else" + * block, we can't say it's definition. In this case we return "NONE" + * which is safe. + * - If there is empty "if" followed by "U" in "else" block, we can say + * it's "use". This is because for optimizations we don't want to "localize" + * this type of variable. This needs to be changed. + * + * \todo: Need to introduce new states like "conditional definition" to make that + * the variable is "can be" definition. And then we have to return appropriate + * state so that more analysis can be enabled. + */ +DUState DUInstance::conditional_block_eval() { + DUState result = DUState::NONE; + bool block_with_none = false; + + for (auto& chain : children) { + auto child_state = chain.eval(); + if (child_state == DUState::U) { + result = child_state; + break; + } + if (child_state == DUState::NONE) { + block_with_none = true; + } + if (chain.state == DUState::ELSE && child_state == DUState::D) { + if (block_with_none) { + result = DUState::NONE; + } else { + result = child_state; + } + break; + } + } + return result; +} + +/** Find "effective" usage of variable from def-use chain. + * Note that we are interested in "global" variable usage + * and hence we consider only [U,D] states and not [LU, LD] + */ +DUState DUInstance::eval() { + auto result = state; + if (state == DUState::IF || state == DUState::ELSEIF || state == DUState::ELSE) { + result = sub_block_eval(); + } else if (state == DUState::CONDITIONAL_BLOCK) { + result = conditional_block_eval(); + } + return result; +} + +/// first usage of a variable in a block decides whether it's definition +/// or usage. Note that if-else blocks already evaluated. +DUState DUChain::eval() { + auto result = DUState::NONE; + for (auto& inst : chain) { + auto re = inst.eval(); + if (re == DUState::U || re == DUState::D) { + result = re; + break; + } + } + return result; +} + +void DefUseAnalyzeVisitor::visit_unsupported_node(Node* node) { + unsupported_node = true; + node->visit_children(this); + unsupported_node = false; +} + +/** Nothing to do if called function is not defined or it's external + * but if there is a function call for internal function that means + * there is no inlining happened. In this case we mark the call as + * unsupported. + */ +void DefUseAnalyzeVisitor::visit_function_call(FunctionCall* node) { + std::string function_name = node->name->get_name(); + auto symbol = global_symtab->lookup_in_scope(function_name); + if (symbol == nullptr || symbol->is_external_symbol_only()) { + node->visit_children(this); + } else { + visit_unsupported_node(node); + } +} + +void DefUseAnalyzeVisitor::visit_statement_block(StatementBlock* node) { + auto symtab = node->get_symbol_table(); + if (symtab) { + current_symtab = symtab; + } + + symtab_stack.push(current_symtab); + node->visit_children(this); + symtab_stack.pop(); + current_symtab = symtab_stack.top(); +} + +/** Nmodl grammar doesn't allow assignment operator on rhs (e.g. a = b + (b=c) + * and hence not necessary to keep track of assignment operator using stack. + */ +void DefUseAnalyzeVisitor::visit_binary_expression(BinaryExpression* node) { + node->rhs->visit_children(this); + if (node->op.value == BOP_ASSIGN) { + visiting_lhs = true; + } + node->lhs->visit_children(this); + visiting_lhs = false; +} + +void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { + /// store previous chain + auto previous_chain = current_chain; + + /// starting new if block + previous_chain->push_back(DUInstance(DUState::CONDITIONAL_BLOCK)); + current_chain = &previous_chain->back().children; + + /// visiting if sub-block + auto last_chain = current_chain; + start_new_chain(DUState::IF); + node->condition->accept(this); + if (node->statementblock) { + node->statementblock->accept(this); + } + current_chain = last_chain; + + /// visiting else if sub-blocks + for (auto& item : node->elseifs) { + visit_with_new_chain(item.get(), DUState::ELSEIF); + } + + /// visiting else sub-block + if (node->elses) { + visit_with_new_chain(node->elses.get(), DUState::ELSE); + } + + /// restore to previous chain + current_chain = previous_chain; +} + +/** We are not analyzing verbatim blocks yet and hence if there is + * a verbatim block we assume there is variable usage. + * + * \todo: one simple way would be to look for p_name in the string + * of verbatim block to find the variable usage. + */ +void DefUseAnalyzeVisitor::visit_verbatim(Verbatim* node) { + current_chain->push_back(DUInstance(DUState::U)); +} + +/** Update def-use chain if we encounter a variable that we are looking for. + * If we encounter non-supported construct then we mark that variable as "use" + * because we haven't completely analyzed the usage. Marking that variable "U" + * make sures that won't get optimized. Then we distinguish between local and + * non-local variables. All variables that appear on lhs are maked as "definitions" + * whereas the one on rhs are marked as "usages". + */ +void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { + if (name == variable_name) { + auto symbol = current_symtab->lookup_in_scope(name); + // variable properties that make it local + auto properties = symtab::NmodlInfo::local_var | symtab::NmodlInfo::argument; + auto is_local = symbol->has_properties(properties); + + if (unsupported_node) { + current_chain->push_back(DUInstance(DUState::U)); + } else if (visiting_lhs) { + if (is_local) { + current_chain->push_back(DUInstance(DUState::LD)); + } else { + current_chain->push_back(DUInstance(DUState::D)); + } + } else { + if (is_local) { + current_chain->push_back(DUInstance(DUState::LU)); + } else { + current_chain->push_back(DUInstance(DUState::U)); + } + } + } +} + +void DefUseAnalyzeVisitor::visit_with_new_chain(Node* node, DUState state) { + auto last_chain = current_chain; + start_new_chain(state); + node->visit_children(this); + current_chain = last_chain; +} + +void DefUseAnalyzeVisitor::start_new_chain(DUState state) { + current_chain->push_back(DUInstance(state)); + current_chain = ¤t_chain->back().children; +} + +DUChain DefUseAnalyzeVisitor::analyze(ast::Node* node, const std::string& name) { + /// re-initialize state + variable_name = name; + visiting_lhs = false; + current_symtab = global_symtab; + unsupported_node = false; + + /// new chain + DUChain usage(node->get_type_name()); + current_chain = &usage.chain; + + /// analyze given node + symtab_stack.push(current_symtab); + node->visit_children(this); + symtab_stack.pop(); + + return usage; +} \ No newline at end of file diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp new file mode 100644 index 0000000000..c8af4d634f --- /dev/null +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -0,0 +1,253 @@ +#ifndef NMODL_DEFUSE_ANALYZE_VISITOR_HPP +#define NMODL_DEFUSE_ANALYZE_VISITOR_HPP + +#include <map> +#include <stack> + +#include "ast/ast.hpp" +#include "printer/json_printer.hpp" +#include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" + + +/// state in def-use chain +enum class DUState { + /// global variable is used + U, + /// global variable is defined + D, + /// local variable is used + LU, + /// local variable is used + LD, + /// state not known + UNKNOWN, + /// conditional block + CONDITIONAL_BLOCK, + /// if sub-block + IF, + /// elseif sub-block + ELSEIF, + /// else sub-block + ELSE, + /// variable is not used + NONE +}; + +std::ostream& operator<<(std::ostream& os, DUState state); + +/** + * \class DUInstance + * \brief Represent use of a variable in the statement + * + * For a given variable, say tau, when we have statement like a = tau + c + tau + * then def-use is simply [DUState::U, DUState::U]. But if we have if-else block like: + * + * IF (...) { + * a = tau + * tau = c + d + * } ELSE { + * tau = b + * } + * + * Then to know the effective result, we have to analyze def-use in IF and ELSE + * blocks i.e. if variable is used in any of the if-elseif-else block then it needs + * to mark as "DUState::U". Hence we keep the track of all children in case of + * statements like if-else. + */ +class DUInstance { + public: + /// state of the usage + DUState state; + + /// usage of variable in case of if like statements + std::vector<DUInstance> children; + + DUInstance(DUState state) : state(state) { + } + + /// analyze all children and return "effective" usage + DUState eval(); + + /// if, elseif and else evaluation + DUState sub_block_eval(); + + /// evaluate global usage i.e. with [D,U] states of children + DUState conditional_block_eval(); + + void print(JSONPrinter& printer); +}; + + +/** + * \class DUChain + * \brief Def-Use chain for ast node + */ +class DUChain { + public: + /// name of the node + std::string name; + + /// def-use chain for a variable + std::vector<DUInstance> chain; + + DUChain() = default; + DUChain(std::string name) : name(name) { + } + + /// return "effective" usage of a variable + DUState eval(); + + /// return json representation + std::string to_string(bool compact = true); +}; + + +/** + * \class DefUseAnalyzeVisitor + * \brief Visitor to return Def-Use chain for a given variable in the block/node + * + * Motivation: For global to local variable transformation aka localizer + * pass, we need to compute Def-Use chains for all global variables. For + * example, if we have variable usage like: + * + * NEURON { + * RANGE tau, beta + * } + * + * DERIVATIVE states() { + * ... + * x = alpha + * beta = x + y + * .... + * z = beta + * } + * + * INITIAL { + * ... + * beta = x + y + * z = beta * 0.1 + * alpha = x * y + * } + * + * + * In the above example if we look at variable beta then it's defined before it's + * usage and hence it can be safely made local. But variable alpha is used in first + * block and defined in second block. Hence it can't be made local. A variable can + * be made local if it is defined before it's usage (in all global blocks). We exclude + * procedures/functions because we expect inlining to be done prior to this pass. + * + * The analysis of if-elseif-else needs special attention because the def-use chain + * needs re-cursive evaluation in order to find end-result. For example: + * + * IF(...) { + * beta = y + * } ELSE { + * IF(...) { + * beta = x + * } ELSE { + * x = beta + * } + * } + * + * For if-else statements, in the above example, if the variable is used + * in any of the if-elseif-else part then it is considered as "used". And + * this is done recursively from innermost level to the top. + */ +class DefUseAnalyzeVisitor : public AstVisitor { + private: + /// symbol table containing global variables + symtab::SymbolTable* global_symtab = nullptr; + + /// def-use chain currently being constructed + std::vector<DUInstance>* current_chain = nullptr; + + /// symbol table for current statement block (or of parent block if doesn't have) + /// should be initialized by caller somehow + symtab::SymbolTable* current_symtab = nullptr; + + /// symbol tables in call hierarchy + std::stack<symtab::SymbolTable*> symtab_stack; + + /// variable for which to construct def-use chain + std::string variable_name; + + /// indicate that there is unsupported construct encountered + bool unsupported_node = false; + + /// indicate that there is verbatim block encountered + bool verbatim_node = false; + + /// starting visiting lhs of assignment statement + bool visiting_lhs = false; + + void update_defuse_chain(const std::string& name); + void visit_unsupported_node(Node* node); + void visit_with_new_chain(Node* node, DUState state); + void start_new_chain(DUState state); + + public: + DefUseAnalyzeVisitor() = delete; + + explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) + : global_symtab(symtab) { + } + + virtual void visit_binary_expression(BinaryExpression* node) override; + virtual void visit_if_statement(IfStatement* node) override; + virtual void visit_function_call(ast::FunctionCall* node) override; + virtual void visit_statement_block(ast::StatementBlock* node) override; + virtual void visit_verbatim(ast::Verbatim* node) override; + + /// unsupported statements : we aren't sure how to handle this "yet" and + /// hence variables used in any of the below statements are handled separately + + virtual void visit_reaction_statement(ReactionStatement* node) override { + visit_unsupported_node(node); + } + + virtual void visit_non_lin_equation(NonLinEquation* node) override { + visit_unsupported_node(node); + } + + virtual void visit_lin_equation(LinEquation* node) override { + visit_unsupported_node(node); + } + + virtual void visit_partial_boundary(PartialBoundary* node) override { + visit_unsupported_node(node); + } + + virtual void visit_from_statement(FromStatement* node) override { + visit_unsupported_node(node); + } + + virtual void visit_conserve(Conserve* node) override { + visit_unsupported_node(node); + } + + virtual void visit_var_name(ast::VarName* node) override { + update_defuse_chain(node->get_name()); + }; + + virtual void visit_name(ast::Name* node) override { + update_defuse_chain(node->get_name()); + }; + + + /// statements / nodes that should not be used for def-use chain analysis + + virtual void visit_conductance_hint(ConductanceHint* /*node*/) override { + } + + virtual void visit_local_list_statement(LocalListStatement* /*node*/) override { + } + + virtual void visit_argument(ast::Argument* /*node*/) override { + } + + /// compute def-use chain for a variable within the node + DUChain analyze(ast::Node* node, const std::string& name); +}; + +#endif diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 415b7fa096..f4497a01f4 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -14,14 +14,6 @@ bool InlineVisitor::can_inline_block(StatementBlock* block) { return to_inline; } -void InlineVisitor::add_local_statement(StatementBlock* node) { - auto variables = get_local_variables(node); - if (variables == nullptr) { - auto statement = std::make_shared<LocalListStatement>(LocalVarVector()); - node->statements.insert(node->statements.begin(), statement); - } -} - void InlineVisitor::add_return_variable(StatementBlock* block, std::string& varname) { auto lhs = new Name(new String(varname)); auto rhs = new Integer(0, nullptr); @@ -63,10 +55,6 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, return; } - /// add local statement in the block if missing - add_local_statement(inlined_block); - - auto local_variables = get_local_variables(inlined_block); auto& statements = inlined_block->statements; size_t counter = 0; @@ -78,7 +66,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, name->set_name(new_name); /// for argument add new variable to local statement - local_variables->push_back(std::make_shared<LocalVar>(name)); + add_local_variable(inlined_block, name); /// variables in cloned block needs to be renamed RenameVisitor visitor(old_name, new_name); @@ -211,4 +199,4 @@ void InlineVisitor::visit_program(Program* node) { throw std::runtime_error("Program node doesn't have symbol table"); } node->visit_children(this); -} \ No newline at end of file +} diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index e499376d2e..6f2875f10f 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -158,9 +158,6 @@ class InlineVisitor : public AstVisitor { /// in case of procedure blocks) void add_return_variable(ast::StatementBlock* block, std::string& varname); - /// add local statement to the block if doesn't exist already - void add_local_statement(ast::StatementBlock* node); - public: InlineVisitor() = default; diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp new file mode 100644 index 0000000000..6a80f6c85c --- /dev/null +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -0,0 +1,91 @@ +#include <algorithm> + +#include "visitors/localize_visitor.hpp" +#include "visitors/defuse_analyze_visitor.hpp" + +using namespace ast; + +bool LocalizeVisitor::node_to_localize(ast::Node* node) { + auto type = node->get_type(); + // clang-format off + const std::vector<ast::Type> blocks_to_localize = { + ast::Type::INITIAL_BLOCK, + ast::Type::BREAKPOINT_BLOCK, + ast::Type::CONSTRUCTOR_BLOCK, + ast::Type::DESTRUCTOR_BLOCK, + ast::Type::DERIVATIVE_BLOCK, + ast::Type::LINEAR_BLOCK, + ast::Type::NON_LINEAR_BLOCK, + ast::Type::DISCRETE_BLOCK, + ast::Type::PARTIAL_BLOCK, + ast::Type::NET_RECEIVE_BLOCK, + ast::Type::TERMINAL_BLOCK, + ast::Type::BA_BLOCK, + ast::Type::FOR_NETCON, + ast::Type::BEFORE_BLOCK, + ast::Type::AFTER_BLOCK, + }; + // clang-format on + auto it = std::find(blocks_to_localize.begin(), blocks_to_localize.end(), type); + return !(it == blocks_to_localize.end()); +} + +std::vector<std::string> LocalizeVisitor::variables_to_optimize() { + // clang-format off + using NmodlInfo = symtab::details::NmodlInfo; + const SymbolInfo excluded_var_properties = NmodlInfo::extern_var + | NmodlInfo::extern_neuron_variable + | NmodlInfo::read_ion_var + | NmodlInfo::write_ion_var + | NmodlInfo::prime_name + | NmodlInfo::nonspe_cur_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::electrode_cur_var + | NmodlInfo::section_var; + // clang-format on + auto variables = program_symtab->get_global_variables(); + std::vector<std::string> result; + for (auto& variable : variables) { + if (!variable->has_properties(excluded_var_properties)) { + result.push_back(variable->get_name()); + } + } + return result; +} + +void LocalizeVisitor::visit_program(Program* node) { + /// symtab visitor pass must be run before + program_symtab = node->get_symbol_table(); + if (program_symtab == nullptr) { + throw std::runtime_error("localizer error : program node doesn't have symbol table"); + } + + auto variables = variables_to_optimize(); + for (const auto& variable : variables) { + auto blocks = node->blocks; + std::map<DUState, std::vector<std::shared_ptr<ast::Node>>> block_usage; + + /// compute def use chains + for (auto& block : blocks) { + if (node_to_localize(block.get())) { + DefUseAnalyzeVisitor v(program_symtab); + auto usages = v.analyze(block.get(), variable); + auto result = usages.eval(); + block_usage[result].push_back(block); + } + } + + /// as we are doing global analysis, if any global block is "using" + /// variable then we can't localize the variable + auto it = block_usage.find(DUState::U); + if (it == block_usage.end()) { + /// all blocks that are using variable should get local variable + for (auto& block : block_usage[DUState::D]) { + auto block_ptr = dynamic_cast<Block*>(block.get()); + auto statement_block = block_ptr->get_statement_block(); + add_local_variable(statement_block.get(), variable); + } + } + } +} diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp new file mode 100644 index 0000000000..c134810a71 --- /dev/null +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -0,0 +1,88 @@ +#ifndef NMODL_LOCALIZE_VISITOR_HPP +#define NMODL_LOCALIZE_VISITOR_HPP + +#include <map> +#include <stack> + +#include "ast/ast.hpp" +#include "printer/json_printer.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/visitor_utils.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "symtab/symbol_table.hpp" + +/** + * \class LocalizeVisitor + * \brief Visitor to transform global variable usage to local + * + * Motivation: As NMODL doesn't support returning multiple values, + * procedures are often written with use of range variables that + * can be made local. For example: + * + * NEURON { + * RANGE tau, alpha, beta + * } + * + * DERIVATIVE states() { + * ... + * rates() + * alpha = tau + beta + * } + * + * PROCEDURE rates() { + * tau = xx * 0.12 * some_var + * beta = yy * 0.11 + * } + * + * In above example we are only interested in variable alpha computed in + * DERIVATIVE block. If rates() is inlined into DERIVATIVE block then we + * get: + * + * DERIVATIVE states() { + * ... + * { + * tau = xx * 0.12 * some_var + * beta = yy * 0.11 + * } + * alpha = tau + beta + * } + * + * Now tau and beta could become local variables provided that their values + * are not used in any other global blocks. + * + * Implementation Notes: + * - For every global variable in the mod file we have to compute + * def-use chains in global blocks (except procedure and functions, which should be + * already inlined). + * - If every block has "definition" first then that variable is safe to "localize" + * + * \todo: + * - We are excluding procedures/functions because they will be still using global + * variables. We need to have dead-code removal pass to eliminate unused procedures/ + * functions before localizer pass. + * - For conditional block like below we are returning usage as NONE. May be better to + * return COND_D so that localizer can declare tau as LOCAL (this is artificial use + * case though) : + * BREAKPOINT { + * IF (1) { + * tau = 11 + * } + * } + */ + +class LocalizeVisitor : public AstVisitor { + private: + symtab::SymbolTable* program_symtab = nullptr; + + std::vector<std::string> variables_to_optimize(); + + bool node_to_localize(ast::Node* node); + + public: + LocalizeVisitor() = default; + + virtual void visit_program(ast::Program* node) override; +}; + +#endif diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index ced5ce65be..b73b659012 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -11,6 +11,7 @@ #include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_visitor.hpp" #include "visitors/nmodl_visitor.hpp" +#include "visitors/localize_visitor.hpp" #include "tclap/CmdLine.h" @@ -128,7 +129,27 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); + NmodlPrintVisitor v(channel_name + ".nocmodl.in.mod"); + v.visit_program(ast.get()); + } + + { + LocalizeVisitor v; + v.visit_program(ast.get()); + } + + { + NmodlPrintVisitor v(channel_name + ".nocmodl.loc.mod"); + v.visit_program(ast.get()); + } + + { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + } + + { + NmodlPrintVisitor v(channel_name + ".nocmodl.loc.ren.mod"); v.visit_program(ast.get()); } diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 825bf62aa3..d0c98ac4f6 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -47,10 +47,6 @@ void SymtabVisitor::setup_symbol_table(T* node, template <typename T> void SymtabVisitor::setup_program_symbol_table(T* node, std::string name, bool is_global) { modsymtab = node->get_model_symbol_table(); - if (modsymtab == nullptr) { - node->init_model_symbol_table(); - modsymtab = node->get_model_symbol_table(); - } setup_symbol_table(node, name, is_global); } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index e2881c9e5b..7f320d71bf 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -1,5 +1,6 @@ #include <string> #include <map> +#include <utility> #include "ast/ast.hpp" @@ -25,3 +26,22 @@ LocalVarVector* get_local_variables(const StatementBlock* node) { } return nullptr; } + +void add_local_statement(StatementBlock* node) { + auto variables = get_local_variables(node); + if (variables == nullptr) { + auto statement = std::make_shared<LocalListStatement>(LocalVarVector()); + node->statements.insert(node->statements.begin(), statement); + } +} + +void add_local_variable(ast::StatementBlock* node, ast::Identifier* varname) { + add_local_statement(node); + auto local_variables = get_local_variables(node); + local_variables->push_back(std::make_shared<LocalVar>(varname)); +} + +void add_local_variable(ast::StatementBlock* node, const std::string& varname) { + auto name = new ast::Name(new ast::String(varname)); + add_local_variable(node, name); +} diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index c0eb058222..554dd22506 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -13,5 +13,8 @@ std::string get_new_name(const std::string& name, const std::string& suffix, std::map<std::string, int>& variables); ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); +void add_local_statement(ast::StatementBlock* node); +void add_local_variable(ast::StatementBlock* node, const std::string& varname); +void add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); #endif \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 7806abb421..5e08df719e 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -12,6 +12,8 @@ #include "visitors/symtab_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +#include "visitors/defuse_analyze_visitor.hpp" +#include "visitors/localize_visitor.hpp" #include "test/utils/nmodl_constructs.h" #include "test/utils/test_utils.hpp" @@ -996,7 +998,7 @@ SCENARIO("Function call in non-binary expression") { rates_2 = 10+x } )"; - THEN("Function and function arguments gets inlined recursively") { + THEN("Function and it's arguments gets inlined recursively") { std::string input = reindent_text(input_nmodl); auto expected_result = reindent_text(output_nmodl); auto result = run_inline_visitor(input); @@ -1091,4 +1093,451 @@ SCENARIO("Procedure inlining handles local-global name conflict") { REQUIRE(result == expected_result); } } -} \ No newline at end of file +} + + +//============================================================================= +// DefUseAnalyze visitor tests +//============================================================================= + +std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string variable) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + { + SymtabVisitor v; + v.visit_program(ast.get()); + } + + { + InlineVisitor v1; + v1.visit_program(ast.get()); + } + + { + std::vector<DUChain> chains; + DefUseAnalyzeVisitor v(ast->get_symbol_table()); + + for (auto& block : ast->blocks) { + if (block->get_type() != ast::Type::NEURON_BLOCK) { + chains.push_back(v.analyze(block.get(), variable)); + } + } + return chains; + } +} + +SCENARIO("Running defuse analyzer") { + GIVEN("global variable usage in assignment statements") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + tau = 1 + tau = 1 + tau + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"value":"D"},{"value":"U"},{"value":"D"}]})"; + + THEN("Def-Use chains for individual usage is printed") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string(true) == expected_text); + REQUIRE(chains[0].eval() == DUState::D); + } + } + + GIVEN("block with use of verbatim block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + VERBATIM ENDVERBATIM + tau = 1 + VERBATIM ENDVERBATIM + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"value":"U"},{"value":"D"},{"value":"U"}]})"; + + THEN("Verbatim block is considered as use of the variable") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string(true) == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("global variable definition in else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + LOCAL tau + tau = 1 + } ELSE { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"LD"}]},{"ELSE":[{"value":"D"}]}]}]})"; + + THEN("Def-Use chains should return NONE") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::NONE); + } + } + + GIVEN("global variable usage in else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + } ELSE { + tau = 1 + tau + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSE":[{"value":"U"},{"value":"D"}]}]}]})"; + + THEN("Def-Use chains should return USE") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("global variable definition in if-else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + IF (1) { + tau = 11.1 + exp(tau) + } ELSE { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"D"},{"value":"U"}]},{"ELSE":[{"value":"D"}]}]}]})"; + + THEN("Def-Use chains should return DEF") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::D); + } + } + + + GIVEN("global variable usage in if-elseif-else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + tau = 1 + } + tau = 1 + tau + IF (0) { + beta = 1 + } ELSE IF (2) { + tau = 1 + } + } + + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"D"}]}]},{"value":"U"},{"value":"D"},{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSEIF":[{"value":"D"}]}]}]})"; + + THEN("Def-Use chains for individual usage is printed") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("global variable used in nested if-elseif-else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + LOCAL tau + tau = 1 + } + IF (0) { + IF (1) { + beta = 1 + } ELSE { + tau = 1 + } + } ELSE IF (2) { + IF (1) { + beta = 1 + IF (0) { + } ELSE { + beta = 1 + exp(tau) + } + } + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"LD"}]}]},{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSE":[{"value":"D"}]}]}]},{"ELSEIF":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSE":[{"value":"U"}]}]}]}]},{"value":"D"}]}]}]})"; + + THEN("Def-Use chains for nested statements calculated") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } +} + + +//============================================================================= +// Localizer visitor tests +//============================================================================= + +std::string run_localize_visitor(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + { + SymtabVisitor v1; + v1.visit_program(ast.get()); + InlineVisitor v2; + v2.visit_program(ast.get()); + LocalizeVisitor v3; + v3.visit_program(ast.get()); + } + + std::stringstream stream; + { + NmodlPrintVisitor v(stream); + v.visit_program(ast.get()); + } + return stream.str(); +} + + +SCENARIO("Localizer test with single global block") { + GIVEN("Single derivative block with variable definition") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + LOCAL tau + tau = 11.1 + exp(tau) + } + )"; + + THEN("tau variable gets localized") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Localizer test with use of verbatim block") { + GIVEN("Verbatim block usage in one of the global block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + VERBATIM ENDVERBATIM + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + VERBATIM ENDVERBATIM + } + )"; + + THEN("Localization is disabled") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } +} + + +SCENARIO("Localizer test with multiple global blocks") { + GIVEN("Multiple global blocks with definition of variable") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + INITIAL { + LOCAL tau + tau = beta + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + IF (1) { + tau = beta + } ELSE { + tau = 11 + } + + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau, beta + } + + INITIAL { + LOCAL tau + tau = beta + } + + DERIVATIVE states { + LOCAL tau + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + LOCAL tau + IF (1) { + tau = beta + } ELSE { + tau = 11 + } + } + )"; + + THEN("Localization across multiple blocks is done") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } + + + GIVEN("Two global blocks with definition and use of the variable") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + } + + BREAKPOINT { + IF (1) { + tau = 22 + } ELSE { + tau = exp(tau) + 11 + } + + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + } + + BREAKPOINT { + IF (1) { + tau = 22 + } ELSE { + tau = exp(tau)+11 + } + } + )"; + + THEN("Localization is not done due to use of variable") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } +} From aa06d01dcf4b3639392b929f93576eaa4a4958fd Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 1 Feb 2018 13:32:09 +0100 Subject: [PATCH 049/871] Inlining pass improvements: - Add return statement in the inlined block of procedure only if the procedure is used as part of an expression (which is typically not) - Function inlining still always have local variable - Update tests, added test for standalone function call Change-Id: I267a9af8c6ca425d10dea10229d0cdbd2646854b NMODL Repo SHA: BlueBrain/nmodl@dcc988947fcf8de4cd1afd2a6df16f0aee405964 --- src/nmodl/visitors/defuse_analyze_visitor.hpp | 3 +- src/nmodl/visitors/inline_visitor.hpp | 33 ++++++----- test/nmodl/transpiler/visitor/visitor.cpp | 57 ++++++++++++++----- 3 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index c8af4d634f..e0323c0f93 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -189,8 +189,7 @@ class DefUseAnalyzeVisitor : public AstVisitor { public: DefUseAnalyzeVisitor() = delete; - explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) - : global_symtab(symtab) { + explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) : global_symtab(symtab) { } virtual void visit_binary_expression(BinaryExpression* node) override; diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 6f2875f10f..6f6980202a 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -195,22 +195,27 @@ void InlineVisitor::inline_function_call(T* callee, LocalVarRenameVisitor v; v.visit_statement_block(caller); - auto local_variables = get_local_variables(caller); auto& caller_arguments = node->arguments; - - /// each block should already have local statement (added in statement block's visit function) - if (local_variables == nullptr) { - throw std::logic_error("got local statement as nullptr"); - } - std::string new_varname = get_new_name(function_name, "in", inlined_variables); - /// create new variable which will be used for returning value from inlined block - auto name = new ast::Name(new ast::String(new_varname)); - ModToken tok; - name->set_token(tok); + /// check if caller statement could be replaced + bool to_replace = can_replace_statement(caller_statement); - local_variables->push_back(std::make_shared<ast::LocalVar>(name)); + /// need to add local variable for function calls or for procedure call if it is part of + /// expression (standalone procedure calls don't need return statement) + if (callee->is_function_block() || to_replace == false) { + /// create new variable which will be used for returning value from inlined block + auto name = new ast::Name(new ast::String(new_varname)); + ModToken tok; + name->set_token(tok); + + auto local_variables = get_local_variables(caller); + /// each block should already have local statement + if (local_variables == nullptr) { + throw std::logic_error("got local statement as nullptr"); + } + local_variables->push_back(std::make_shared<ast::LocalVar>(name)); + } /// get a copy of function/procedure body auto inlined_block = callee->statementblock->clone(); @@ -227,12 +232,10 @@ void InlineVisitor::inline_function_call(T* callee, inline_arguments(inlined_block, callee->arguments, caller_arguments); /// to return value from procedure we have to add new variable - if (callee->is_procedure_block()) { + if (callee->is_procedure_block() && to_replace == false) { add_return_variable(inlined_block, new_varname); } - /// check if caller statement could be replaced and add new statement to appropriate map - bool to_replace = can_replace_statement(caller_statement); auto statement = new ast::ExpressionStatement(inlined_block); if (to_replace) { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 5e08df719e..21dcd98b5a 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -593,12 +593,11 @@ SCENARIO("Simple procedure inlining") { std::string output_nmodl = R"( PROCEDURE rates_1() { - LOCAL x, rates_2_in_0 + LOCAL x { LOCAL x, y_in_0 y_in_0 = 23.1 x = 21.1*v+y_in_0 - rates_2_in_0 = 0 } } @@ -627,8 +626,7 @@ SCENARIO("Nested procedure inlining") { PROCEDURE rates_2() { LOCAL x - x = 21.1*v - rates_3(x, x+1.1) + x = 21.1*v + rates_3(x, x+1.1) } PROCEDURE rates_3(a, b) { @@ -639,10 +637,9 @@ SCENARIO("Nested procedure inlining") { std::string output_nmodl = R"( PROCEDURE rates_1() { - LOCAL x, y, rates_2_in_0, rates_3_in_1 + LOCAL x, y { LOCAL x, rates_3_in_0 - x = 21.1*v { LOCAL c, a_in_0, b_in_0 a_in_0 = x @@ -650,20 +647,18 @@ SCENARIO("Nested procedure inlining") { c = 21.1*v+a_in_0*b_in_0 rates_3_in_0 = 0 } - rates_2_in_0 = 0 + x = 21.1*v+rates_3_in_0 } { LOCAL c, a_in_1, b_in_1 a_in_1 = x b_in_1 = y c = 21.1*v+a_in_1*b_in_1 - rates_3_in_1 = 0 } } PROCEDURE rates_2() { LOCAL x, rates_3_in_0 - x = 21.1*v { LOCAL c, a_in_0, b_in_0 a_in_0 = x @@ -671,6 +666,7 @@ SCENARIO("Nested procedure inlining") { c = 21.1*v+a_in_0*b_in_0 rates_3_in_0 = 0 } + x = 21.1*v+rates_3_in_0 } PROCEDURE rates_3(a, b) { @@ -1007,6 +1003,43 @@ SCENARIO("Function call in non-binary expression") { } } + +SCENARIO("Function call as standalone expression") { + GIVEN("Function call as a statement") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + rates_2(23.1) + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL y_in_0 + y_in_0 = 23.1 + rates_2_in_0 = 21.1*v+y_in_0 + } + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + THEN("Function gets inlined but it's value is not used") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + SCENARIO("Procedure call as standalone statement as well as part of expression") { GIVEN("A procedure call in expression and statement") { std::string input_nmodl = R"( @@ -1022,13 +1055,12 @@ SCENARIO("Procedure call as standalone statement as well as part of expression") std::string output_nmodl = R"( PROCEDURE rates_1() { - LOCAL x, rates_2_in_0, rates_2_in_1 + LOCAL x, rates_2_in_0 { rates_2_in_0 = 0 } x = 10+rates_2_in_0 { - rates_2_in_1 = 0 } } @@ -1070,13 +1102,12 @@ SCENARIO("Procedure inlining handles local-global name conflict") { } PROCEDURE rates_1() { - LOCAL x_r_0, rates_2_in_0 + LOCAL x_r_0 x_r_0 = 12 { LOCAL y_in_0 y_in_0 = x_r_0 x = 10+y_in_0 - rates_2_in_0 = 0 } x_r_0 = 11 } From f79e1b343799a26bdc003b2e00eda16b281f6b12 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 1 Feb 2018 14:52:08 +0100 Subject: [PATCH 050/871] Improvement to nmodl printer: - nmodl printer was adding " {" to every block node instead of "{" - yaml specification updated with necessary " " suffix - this avoids extra whitespaces for standalone blocks aftern inlining - tests updated accordingly Change-Id: I902b77d2e018825c91cb32dff12a7998f862712e NMODL Repo SHA: BlueBrain/nmodl@f279d633e609d8b811d10017ba591ca345916625 --- src/nmodl/language/nmodl.yaml | 52 ++++++++++++-------- src/nmodl/printer/nmodl_printer.cpp | 2 +- test/nmodl/transpiler/visitor/visitor.cpp | 58 +++++++++++------------ 3 files changed, 61 insertions(+), 51 deletions(-) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 34a1e91379..15c5565fe7 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -730,31 +730,31 @@ getname: true - Block: - ParamBlock: - nmodl: PARAMETER + nmodl: "PARAMETER " members: - statements: type: ParamAssign vector: true - StepBlock: - nmodl: STEPPED + nmodl: "STEPPED " members: - statements: type: Stepped vector: true - IndependentBlock: - nmodl: INDEPENDENT + nmodl: "INDEPENDENT " members: - definitions: type: IndependentDef vector: true - DependentBlock: - nmodl: ASSIGNED + nmodl: "ASSIGNED " members: - definitions: type: DependentDef vector: true - StateBlock: - nmodl: STATE + nmodl: "STATE " members: - definitions: type: DependentDef @@ -764,19 +764,19 @@ - plot: type: PlotDeclaration - InitialBlock: - nmodl: INITIAL + nmodl: "INITIAL " members: - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} - ConstructorBlock: - nmodl: CONSTRUCTOR + nmodl: "CONSTRUCTOR " members: - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} - DestructorBlock: - nmodl: DESTRUCTOR + nmodl: "DESTRUCTOR " members: - statementblock: type: StatementBlock @@ -792,6 +792,7 @@ - name: type: Name getname: true + suffix: {value: " "} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -801,6 +802,7 @@ - name: type: Name getname: true + suffix: {value: " "} - solvefor: type: Name vector: true @@ -820,6 +822,7 @@ vector: true separator: "," prefix: {value: " SOLVEFOR "} + suffix: {value: " ", force: true} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -829,6 +832,7 @@ - name: type: Name getname: true + suffix: {value: " "} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -838,6 +842,7 @@ - name: type: Name getname: true + suffix: {value: " "} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -873,6 +878,7 @@ type: Unit optional: true prefix: {value: " "} + suffix: {value: " ", force: true} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -886,7 +892,7 @@ type: Argument vector: true prefix: {value: "(", force: true} - suffix: {value: ")", force: true} + suffix: {value: ") ", force: true} separator: ", " - unit: type: Unit @@ -901,7 +907,7 @@ type: Argument vector: true prefix: {value: "(", force: true} - suffix: {value: ")", force: true} + suffix: {value: ") ", force: true} separator: ", " - statementblock: type: StatementBlock @@ -918,15 +924,15 @@ prefix: {value: " METHOD "} - ifsolerr: type: StatementBlock - prefix: {value: " IFERROR"} + prefix: {value: " IFERROR "} - BreakpointBlock: - nmodl: BREAKPOINT + nmodl: "BREAKPOINT " members: - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} - TerminalBlock: - nmodl: TERMINAL + nmodl: "TERMINAL " members: - statementblock: type: StatementBlock @@ -945,6 +951,7 @@ members: - type: type: BABlockType + suffix: {value: " "} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -955,7 +962,7 @@ type: Argument vector: true prefix: {value: "(", force: true} - suffix: {value: ")", force: true} + suffix: {value: ") ", force: true} separator: ", " - statementblock: type: StatementBlock @@ -966,6 +973,7 @@ - name: type: Name getname: true + suffix: {value: " "} - solvefor: type: Name vector: true @@ -983,19 +991,19 @@ prefix: {value: " { "} suffix: {value: " }"} - UnitBlock: - nmodl: UNITS + nmodl: "UNITS " members: - definitions: type: Expression vector: true - ConstantBlock: - nmodl: CONSTANT + nmodl: "CONSTANT " members: - statements: type: ConstantStatement vector: true - NeuronBlock: - nmodl: NEURON + nmodl: "NEURON " members: - statementblock: type: StatementBlock @@ -1332,6 +1340,7 @@ - opinc: type: Expression prefix: {value: " BY "} + suffix: {value: " ", force: true} optional: true - statementblock: type: StatementBlock @@ -1340,6 +1349,7 @@ members: - name: type: Name + suffix: {value: " "} - statementblock: type: StatementBlock - WhileStatement: @@ -1348,7 +1358,7 @@ - condition: type: Expression prefix: {value: "("} - suffix: {value: ")"} + suffix: {value: ") "} - statementblock: type: StatementBlock - IfStatement: @@ -1357,7 +1367,7 @@ - condition: type: Expression prefix: {value: "("} - suffix: {value: ")"} + suffix: {value: ") "} - statementblock: type: StatementBlock - elseifs: @@ -1372,11 +1382,11 @@ - condition: type: Expression prefix: {value: "("} - suffix: {value: ")"} + suffix: {value: ") "} - statementblock: type: StatementBlock - ElseStatement: - nmodl: " ELSE" + nmodl: " ELSE " members: - statementblock: type: StatementBlock diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index c879213aac..8c0c746eea 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -19,7 +19,7 @@ NMODLPrinter::NMODLPrinter(const std::string& filename) { void NMODLPrinter::push_level() { indent_level++; - *result << " {"; + *result << "{"; add_newline(); } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 21dcd98b5a..d4b7ddfeb2 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -497,13 +497,13 @@ SCENARIO("Variable renaming in nested blocks") { LOCAL gNaTs2_t_r_1 gNaTs2_t_r_1 = gNaTs2_tbar*m*m*m*h ina = gNaTs2_t_r_1*(v-ena) - { + { LOCAL gNaTs2_t_r_0, h_r_1 gNaTs2_t_r_0 = m+h_r_1 - { + { LOCAL m_r_1 m_r_1 = gNaTs2_t_r_0+h_r_1 - { + { LOCAL m_r_0, h_r_0 } } @@ -513,8 +513,8 @@ SCENARIO("Variable renaming in nested blocks") { PROCEDURE rates() { LOCAL x_r_1, m_r_2 m_r_2 = x_r_1+gNaTs2_tbar - { - { + { + { LOCAL h_r_2, x_r_0, gNaTs2_tbar_r_0 m_r_2 = h_r_2*x_r_0*gNaTs2_tbar_r_0+tau } @@ -594,7 +594,7 @@ SCENARIO("Simple procedure inlining") { std::string output_nmodl = R"( PROCEDURE rates_1() { LOCAL x - { + { LOCAL x, y_in_0 y_in_0 = 23.1 x = 21.1*v+y_in_0 @@ -638,9 +638,9 @@ SCENARIO("Nested procedure inlining") { std::string output_nmodl = R"( PROCEDURE rates_1() { LOCAL x, y - { + { LOCAL x, rates_3_in_0 - { + { LOCAL c, a_in_0, b_in_0 a_in_0 = x b_in_0 = x+1.1 @@ -649,7 +649,7 @@ SCENARIO("Nested procedure inlining") { } x = 21.1*v+rates_3_in_0 } - { + { LOCAL c, a_in_1, b_in_1 a_in_1 = x b_in_1 = y @@ -659,7 +659,7 @@ SCENARIO("Nested procedure inlining") { PROCEDURE rates_2() { LOCAL x, rates_3_in_0 - { + { LOCAL c, a_in_0, b_in_0 a_in_0 = x b_in_0 = x+1.1 @@ -701,7 +701,7 @@ SCENARIO("Inline function call in procedure") { std::string output_nmodl = R"( PROCEDURE rates_1() { LOCAL x, rates_2_in_0 - { + { LOCAL x x = 21.1*12.1+11 rates_2_in_0 = x @@ -743,7 +743,7 @@ SCENARIO("Function call within conditional statement") { std::string output_nmodl = R"( FUNCTION rates_1() { LOCAL rates_2_in_0 - { + { rates_2_in_0 = 10 } IF (rates_2_in_0) { @@ -784,10 +784,10 @@ SCENARIO("Multiple function calls in same statement") { std::string output_nmodl = R"( FUNCTION rates_1() { LOCAL rates_2_in_0, rates_2_in_1 - { + { rates_2_in_0 = 10 } - { + { rates_2_in_1 = 10 } IF (rates_2_in_0-rates_2_in_1) { @@ -823,13 +823,13 @@ SCENARIO("Multiple function calls in same statement") { std::string output_nmodl = R"( FUNCTION rates_1() { LOCAL x, rates_2_in_0, rates_2_in_1, rates_2_in_2 - { + { rates_2_in_0 = 10 } - { + { rates_2_in_1 = 10 } - { + { rates_2_in_2 = 10 } x = (rates_2_in_0+(rates_2_in_1/rates_2_in_2)) @@ -871,7 +871,7 @@ SCENARIO("Nested function calls withing arguments") { std::string output_nmodl = R"( FUNCTION rates_2() { LOCAL rates_3_in_0, rates_3_in_1 - { + { LOCAL x_in_0, y_in_0 x_in_0 = 11 y_in_0 = 21 @@ -880,7 +880,7 @@ SCENARIO("Nested function calls withing arguments") { IF (rates_3_in_0) { rates_2 = 10.1 } - { + { LOCAL x_in_1, y_in_1 x_in_1 = 12 y_in_1 = 22 @@ -891,9 +891,9 @@ SCENARIO("Nested function calls withing arguments") { FUNCTION rates_1() { LOCAL rates_2_in_0 - { + { LOCAL rates_3_in_0, rates_3_in_1 - { + { LOCAL x_in_0, y_in_0 x_in_0 = 11 y_in_0 = 21 @@ -902,7 +902,7 @@ SCENARIO("Nested function calls withing arguments") { IF (rates_3_in_0) { rates_2_in_0 = 10.1 } - { + { LOCAL x_in_1, y_in_1 x_in_1 = 12 y_in_1 = 22 @@ -943,7 +943,7 @@ SCENARIO("Function call in non-binary expression") { std::string output_nmodl = R"( PROCEDURE rates_1() { LOCAL x, rates_2_in_0 - { + { LOCAL y_in_0 y_in_0 = 23.1 rates_2_in_0 = 21.1*v+y_in_0 @@ -977,12 +977,12 @@ SCENARIO("Function call in non-binary expression") { std::string output_nmodl = R"( FUNCTION rates_1() { LOCAL rates_2_in_0, rates_2_in_1 - { + { LOCAL x_in_0 x_in_0 = 11 rates_2_in_0 = 10+x_in_0 } - { + { LOCAL x_in_1 x_in_1 = rates_2_in_0 rates_2_in_1 = 10+x_in_1 @@ -1020,7 +1020,7 @@ SCENARIO("Function call as standalone expression") { std::string output_nmodl = R"( PROCEDURE rates_1() { LOCAL x, rates_2_in_0 - { + { LOCAL y_in_0 y_in_0 = 23.1 rates_2_in_0 = 21.1*v+y_in_0 @@ -1056,11 +1056,11 @@ SCENARIO("Procedure call as standalone statement as well as part of expression") std::string output_nmodl = R"( PROCEDURE rates_1() { LOCAL x, rates_2_in_0 - { + { rates_2_in_0 = 0 } x = 10+rates_2_in_0 - { + { } } @@ -1104,7 +1104,7 @@ SCENARIO("Procedure inlining handles local-global name conflict") { PROCEDURE rates_1() { LOCAL x_r_0 x_r_0 = 12 - { + { LOCAL y_in_0 y_in_0 = x_r_0 x = 10+y_in_0 From 236fc3be2bcc540b6d78a1fc33a2b017667f02b0 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 1 Feb 2018 21:16:04 +0100 Subject: [PATCH 051/871] Added support for inline/single support: - single line comments are now preserved and translated back to nmodl - drawback is that the inline comments get translated to new line i.e. a + b : comment here gets translated a + b : comment here Change-Id: Ie42402076c8abeb4c19b1a73d21ea6c8af441fa7 NMODL Repo SHA: BlueBrain/nmodl@5d915d4e9fdc6f5083b8f23bffe5a418f5d63616 --- src/nmodl/language/nmodl.yaml | 8 +++++-- src/nmodl/lexer/nmodl.ll | 5 ++-- src/nmodl/parser/nmodl.yy | 24 ++++++++++++++++--- src/nmodl/visitors/nmodl_visitor_helper.hpp | 14 ++++++++++- .../transpiler/utils/nmodl_constructs.cpp | 4 ++++ 5 files changed, 47 insertions(+), 8 deletions(-) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 15c5565fe7..9678c745c0 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1662,11 +1662,15 @@ type: String suffix: {value: "ENDVERBATIM"} - Comment: - nmodl: COMMENT members: - - comment: + - block_comment: type: String + prefix: {value: "COMMENT"} suffix: {value: "ENDCOMMENT"} + optional: true + - line_comment: + type: String + optional: true - Program: members: diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 140e279c29..ae37c29927 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -382,8 +382,9 @@ ELSE { :.* | \?.* { - /** Todo : add grammar support for single line comment. - * Here yytext already has entire part of string */ + /** Todo : add grammar support for inline vs single-line comments */ + auto str = std::string(yytext); + return nmodl::Parser::make_INLINE_COMMENT(str, loc); } . { diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index c54ab41e4e..2a360ed25a 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -180,6 +180,7 @@ %token <ast::primename_ptr> PRIME %token <std::string> VERBATIM %token <std::string> COMMENT +%token <std::string> INLINE_COMMENT %token <std::string> LINE_PART %token <ast::string_ptr> STRING %token <ast::string_ptr> UNIT_STR @@ -389,7 +390,12 @@ * * \todo ModToken is set for the symbols returned by Lexer. But we need to * set accurate location for each production. We need to add method in AST - * classes to handle this. */ + * classes to handle this. + * + * \todo INLINE_COMMENT adds comment as separate statement and hence they + * go into separate line in nmodl printer. Need to update grammar to distinguish + * standalone single line comment vs. inline comment. + */ top : all { @@ -447,7 +453,13 @@ all : { | all COMMENT { auto text = parse_with_verbatim_parser($2); - auto statement = new ast::Comment(new ast::String(text)); + auto statement = new ast::Comment(new ast::String(text), nullptr); + $1->addNode(statement); + $$ = $1; + } + | all INLINE_COMMENT + { + auto statement = new ast::Comment(nullptr, new ast::String($2)); $1->addNode(statement); $$ = $1; } @@ -900,6 +912,12 @@ stmtlist1 : { $1.push_back(std::shared_ptr<ast::Statement>($2)); $$ = $1; } + | stmtlist1 INLINE_COMMENT + { + auto statement = new ast::Comment(nullptr, new ast::String($2)); + $1.push_back(std::shared_ptr<ast::Statement>(statement)); + $$ = $1; + } ; @@ -922,7 +940,7 @@ ostmt : fromstmt { $$ = $1; } } | COMMENT { auto text = parse_with_verbatim_parser($1); - $$ = new ast::Comment(new ast::String(text)); + $$ = new ast::Comment(new ast::String(text), nullptr); } | sens { $$ = $1; } | compart { $$ = $1; } diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.hpp index 73d4a843ef..0989b45f7c 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.hpp +++ b/src/nmodl/visitors/nmodl_visitor_helper.hpp @@ -3,6 +3,7 @@ #include "visitors/nmodl_visitor.hpp" + /** Helper function to visit vector elements * * @tparam T @@ -16,6 +17,7 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement) { + for (auto iter = elements.begin(); iter != elements.end(); iter++) { /// statements need indentation at the start if (statement) { @@ -34,10 +36,20 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, printer->add_newline(); } + /// if there are multiple inline comments then we want them to be + /// contiguous and only last comment should have extra line. + bool extra_newline = false; + if(!is_last(iter, elements)) { + extra_newline = true; + if((*iter)->is_comment() && (*(iter+1))->is_comment()) { + extra_newline = false; + } + } + /// program blocks need two newlines except last one if (program) { printer->add_newline(); - if (!is_last(iter, elements)) { + if (extra_newline) { printer->add_newline(); } } diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index dd75313039..8bd94508f6 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1251,6 +1251,8 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } )", R"( + ? comment here + FUNCTION urand() { VERBATIM printf("Hello World!\n"); @@ -1273,7 +1275,9 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ R"( FUNCTION urand() { a = b+c + : some comment here c = d*e + ? another comment here } )" } From ff67272182590d53990d5d3a543abccda8351791 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 4 Feb 2018 18:59:36 +0100 Subject: [PATCH 052/871] Implement differential equation solver (lexer+parser) based on NEURON implementation: - tested with cnexp, derivimplicit and euler method - mod files from all bluebrain tested with above method - different types of equations are extracted and added for testing - lexer and parsers are now generated in sub-directory under parser directory to avoid conflict of stack.hh (and there is no other option) - verbatim context clean-up - tests added In terms of implementation, here are changes: - ported diffeq.y to c++ based parser - changed c based lexer to flex based implementation - implemented/ported routines for derivative calculations Change-Id: I9ef2ea322eae80586fb458a242dba7e09b77aef8 NMODL Repo SHA: BlueBrain/nmodl@870fdde6e25898322d107329a69fd88e8062f4dc --- src/nmodl/lexer/CMakeLists.txt | 64 +++- src/nmodl/lexer/diffeq.ll | 97 +++++ src/nmodl/lexer/diffeq_lexer.hpp | 53 +++ src/nmodl/lexer/main.cpp | 2 +- src/nmodl/lexer/modtoken.hpp | 2 +- src/nmodl/lexer/nmodl.ll | 1 + src/nmodl/lexer/nmodl_lexer.hpp | 2 +- src/nmodl/lexer/nmodl_utils.hpp | 6 +- src/nmodl/lexer/token_mapping.cpp | 2 +- src/nmodl/lexer/token_mapping.hpp | 4 +- src/nmodl/parser/CMakeLists.txt | 1 + src/nmodl/parser/diffeq.yy | 212 +++++++++++ src/nmodl/parser/diffeq_context.cpp | 157 +++++++++ src/nmodl/parser/diffeq_context.hpp | 123 +++++++ src/nmodl/parser/diffeq_driver.cpp | 44 +++ src/nmodl/parser/diffeq_driver.hpp | 39 +++ src/nmodl/parser/diffeq_helper.hpp | 169 +++++++++ src/nmodl/parser/main.cpp | 2 - src/nmodl/parser/nmodl.yy | 5 +- src/nmodl/parser/nmodl_driver.hpp | 2 +- src/nmodl/parser/verbatim_context.hpp | 16 +- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/visitors/nmodl_visitor_helper.hpp | 5 +- test/nmodl/transpiler/parser/parser.cpp | 29 ++ .../transpiler/utils/nmodl_constructs.cpp | 330 +++++++++++++++++- .../nmodl/transpiler/utils/nmodl_constructs.h | 18 + test/nmodl/transpiler/visitor/visitor.cpp | 4 +- 27 files changed, 1350 insertions(+), 41 deletions(-) create mode 100755 src/nmodl/lexer/diffeq.ll create mode 100644 src/nmodl/lexer/diffeq_lexer.hpp create mode 100644 src/nmodl/parser/diffeq.yy create mode 100644 src/nmodl/parser/diffeq_context.cpp create mode 100644 src/nmodl/parser/diffeq_context.hpp create mode 100644 src/nmodl/parser/diffeq_driver.cpp create mode 100644 src/nmodl/parser/diffeq_driver.hpp create mode 100644 src/nmodl/parser/diffeq_helper.hpp diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 164654fdd6..2da3c75e2b 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -2,11 +2,13 @@ # Various project components and their source files #============================================================================= set (BISON_GENERATED_SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp ) set(AST_SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp ) @@ -15,8 +17,16 @@ set(NMODL_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp ) +set(DIFFEQ_DRIVER_FILES + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp +) + set_source_files_properties( - ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + ${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE ) @@ -30,22 +40,30 @@ set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.cpp ${NMODL_DRIVER_FILES} + ${DIFFEQ_DRIVER_FILES} ) +#============================================================================= +# Directories for parsers (as they need to be in separate directories) +#============================================================================= +file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/nmodl) +file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/diffeq) + #============================================================================= # Lexer & Parser commands #============================================================================= # command to generate nmodl parser add_custom_command ( COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp + ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl_parser.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/location.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/position.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/stack.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/location.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/position.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/stack.hh DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy DEPENDS pyastgen COMMENT "-- NOCMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" @@ -58,9 +76,27 @@ add_custom_command ( ${PROJECT_SOURCE_DIR}/src/parser/verbatim.y OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.hpp + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.y COMMENT "-- NOCMODL : GENERATING VERBATIM PARSER WITH BISON! --" ) +# command to generate differential equation parser +add_custom_command ( + COMMAND ${BISON_EXECUTABLE} + ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/location.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/position.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/stack.hh + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + COMMENT "-- NOCMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --" +) + # command to generate nmodl lexer add_custom_command( COMMAND ${FLEX_EXECUTABLE} @@ -84,6 +120,17 @@ add_custom_command( COMMENT "-- NOCMODL : GENERATING VERBATIM LEXER WITH FLEX! --" ) +# command to generate differential equation lexer +add_custom_command( + COMMAND ${FLEX_EXECUTABLE} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.cpp + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.hpp + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + COMMENT "-- NOCMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --" +) + #============================================================================= # Libraries & executables #============================================================================= @@ -102,6 +149,7 @@ target_link_libraries(nocmodl_lexer lexer) set(FILES_FOR_CLANG_FORMAT ${LEXER_SOURCE_FILES} ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${DIFFEQ_DRIVER_FILES} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) \ No newline at end of file diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll new file mode 100755 index 0000000000..73e1287c34 --- /dev/null +++ b/src/nmodl/lexer/diffeq.ll @@ -0,0 +1,97 @@ +%{ + #include <cstdio> + #include <cstdlib> + #include <iostream> + #include <stdlib.h> + #include "lexer/diffeq_lexer.hpp" + #include "parser/diffeq_driver.hpp" + + #define yyterminate() return Parser::make_END(loc); + + /// need to store original equation for non-linear solution and hence we add all tokens + #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } + + #define YY_NO_UNISTD_H + +%} + +D [0-9] +E [Ee][-+]?{D}+ + +/** we do use yymore feature in copy modes */ +%option yymore + +/** name of the lexer header file */ +%option header-file="diffeq_base_lexer.hpp" + +/** name of the lexer implementation file */ +%option outfile="diffeq_base_lexer.cpp" + +/** change the name of the scanner class (to "DiffEqFlexLexer") */ +%option prefix="DiffEq" + +/** enable C++ scanner which is also reentrant */ +%option c++ + +/* no need for includes */ +%option noyywrap + +/** need to unput characters back to buffer for custom routines */ +%option unput + +/** need to put in buffer for custom routines */ +%option input + +/* not an interactive lexer, takes a file */ +%option batch + +/** enable debug mode (disable for production) */ +%option debug + +/** instructs flex to generate an 8-bit scanner, i.e., + * one which can recognize 8-bit characters. */ +%option 8bit + +/** show warning messages */ +%option warn + +/* to insure there are no holes in scanner rules */ +%option nodefault + + +%% + +"+" { return Parser::make_ADD(yytext, loc); } + +"-" { return Parser::make_SUB(yytext, loc); } + +"*" { return Parser::make_MUL(yytext, loc); } + +"/" { return Parser::make_DIV(yytext, loc); } + +"(" { return Parser::make_OPEN_PARENTHESIS(yytext, loc); } + +")" { return Parser::make_CLOSE_PARENTHESIS(yytext, loc); } + +"," { return Parser::make_COMMA(yytext, loc); } + +:.* { /*ignore inline comments */ } + +[ \t] { /* ignore spacing characters */ } + +{D}+ | +{D}+"."{D}*({E})? | +{D}*"."{D}+({E})? | +{D}+{E} | +[a-zA-Z][a-zA-Z0-9_]* { return Parser::make_ATOM(yytext, loc); } + +"\n" { return Parser::make_NEWLINE(yytext, loc); } + + +%% + + +int DiffEqFlexLexer::yylex() { + throw std::runtime_error("next_token() should be used instead of yylex()"); +} + diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp new file mode 100644 index 0000000000..c1e7209a3b --- /dev/null +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -0,0 +1,53 @@ +#ifndef DIFFEQ_LEXER_HPP +#define DIFFEQ_LEXER_HPP + +#include "parser/diffeq/diffeq_parser.hpp" + +/** Flex expects the declaration of yylex to be defined in the macro YY_DECL + * and C++ parser class expects it to be declared. */ +#ifndef YY_DECL +#define YY_DECL diffeq::Parser::symbol_type diffeq::Lexer::next_token() +#endif + +/** For creating multiple (different) lexer classes, we can use '-P' flag + * (or prefix option) to rename each yyFlexLexer to some other name like + * ‘xxFlexLexer’. And then include <FlexLexer.h> in other sources once per + * lexer class, first renaming yyFlexLexer as shown below. */ +#ifndef __FLEX_LEXER_H +#define yyFlexLexer DiffEqFlexLexer +#include "FlexLexer.h" +#endif + +namespace diffeq { + + /** + * \class Lexer + * \brief Represent Lexer/Scanner class for differential equation parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * At the moment we are using basic functionality but it could be easily + * extended for further development. + */ + class Lexer : public DiffEqFlexLexer { + public: + /// for tracking location of the tokens + location loc; + + /** The streams in and out default to cin and cout, but that assignment + * is only made when initializing in yylex(). */ + Lexer(std::istream* in = nullptr, std::ostream* out = nullptr) + : DiffEqFlexLexer(in, out) {} + + ~Lexer() override = default;; + + /** Main lexing function generated by flex according to the macro declaration + * YY_DECL above. The generated bison parser then calls this virtual function + * to fetch new tokens. Note that yylex() has different declaration and hence + * it can't be used for new lexer. */ + virtual Parser::symbol_type next_token(); + + }; + +} // namespace diffeq + +#endif diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main.cpp index 45328eed60..a790cd602a 100644 --- a/src/nmodl/lexer/main.cpp +++ b/src/nmodl/lexer/main.cpp @@ -13,7 +13,7 @@ * location. */ -int main(int argc, char* argv[]) { +int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Lexer: Standalone lexer program for NMODL"); TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 83aa9fcc74..0ded98cbd4 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -6,7 +6,7 @@ #include <sstream> #include <string> -#include "parser/location.hh" +#include "parser/nmodl/location.hh" /** * \class ModToken diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index ae37c29927..ae70d56945 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -1,5 +1,6 @@ %{ #include <iostream> + #include "ast/ast.hpp" #include "lexer/nmodl_lexer.hpp" #include "lexer/nmodl_utils.hpp" diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 1908f9d2fc..7b1365a10f 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -2,7 +2,7 @@ #define NMODL_LEXER_HPP #include "ast/ast.hpp" -#include "parser/nmodl_parser.hpp" +#include "parser/nmodl/nmodl_parser.hpp" /** Flex expects the declaration of yylex to be defined in the macro YY_DECL * and C++ parser class expects it to be declared. */ diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index df8cb3deed..f7fa6c7dc5 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -1,7 +1,7 @@ #pragma once -#include "parser/location.hh" -#include "parser/nmodl_parser.hpp" +#include "parser/nmodl/location.hh" +#include "parser/nmodl/nmodl_parser.hpp" /** * \brief Utility functions for nmodl lexer @@ -30,4 +30,4 @@ namespace nmodl { PositionType& pos, TokenType type = Token::UNKNOWN); -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 4687d70c67..72d45e6656 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -4,7 +4,7 @@ #include "ast/ast.hpp" #include "lexer/modl.h" -#include "parser/nmodl_parser.hpp" +#include "parser/nmodl/nmodl_parser.hpp" namespace nmodl { using Token = nmodl::Parser::token; diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 487c452be1..7316fea7a7 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -1,7 +1,7 @@ #pragma once #include <string> -#include "parser/nmodl_parser.hpp" +#include "parser/nmodl/nmodl_parser.hpp" namespace nmodl { bool is_keyword(const std::string& name); @@ -9,4 +9,4 @@ namespace nmodl { nmodl::Parser::token_type token_type(const std::string& name); std::vector<std::string> get_external_variables(); std::vector<std::string> get_external_functions(); -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 4732437daf..17b3bf8092 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -14,6 +14,7 @@ target_link_libraries(nocmodl_parser lexer) set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_context.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_context.hpp ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) \ No newline at end of file diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy new file mode 100644 index 0000000000..581d78d9bf --- /dev/null +++ b/src/nmodl/parser/diffeq.yy @@ -0,0 +1,212 @@ +/* Bison specification for parsing derivative expressions in DERIVATIVE block */ + + %code requires + { + #include <string> + #include "parser/diffeq_context.hpp" + #include "parser/diffeq_helper.hpp" +} + +/** use C++ parser interface of bison */ +%skeleton "lalr1.cc" + +/** require modern bison version */ +%require "3.0.2" + +/** print verbose error messages instead of just message 'syntax error' */ +%define parse.error verbose + +/** enable tracing parser for debugging */ +%define parse.trace + +/** enable location tracking */ +%locations + +/** add extra arguments to yyparse() and yylexe() methods */ +%parse-param {class Lexer& scanner} +%parse-param {diffeq::DiffEqContext& context} +%lex-param { diffeq::Scanner &scanner } + +/** use variant based implementation of semantic values */ +%define api.value.type variant + +/** assert correct cleanup of semantic value objects */ +%define parse.assert + +/** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ +%define api.token.constructor + +/** namespace to enclose parser */ +%name-prefix "diffeq" + +/** set the parser's class identifier */ +%define parser_class_name {Parser} + +/** keep track of the current position within the input */ +%locations + + +%token <std::string> ATOM +%token <std::string> NEWLINE +%token <std::string> ADD "+" +%token <std::string> MUL "*" +%token <std::string> SUB "-" +%token <std::string> DIV "/" +%token <std::string> OPEN_PARENTHESIS "(" +%token <std::string> CLOSE_PARENTHESIS ")" +%token <std::string> COMMA "," +%token END 0 + + +%type <std::string> top +%type <Term> expression e arglist arg + + +/* operator associativity */ +%left "+" "-" +%left "*" "/" +%left UNARYMINUS + + +%{ + #include <sstream> + #include "lexer/diffeq_lexer.hpp" + + static diffeq::Parser::symbol_type yylex(diffeq::Lexer &scanner) { + return scanner.next_token(); + } +%} + + +%start top + + +%% + +/** + * + * \todo We pass the differential equation context to the parser to keep track of current + * solution and and also some global states like equation/derivative valid or not. We could + * add extra properties in Term class and avoid passing context to parser class. Need to + * check the implementation of other solvers in neuron before making changes. + */ + +top : expression { + context.solution.b = "-(" + context.solution.b + ")/(" + context.solution.a + ")"; + } + | error { + throw std::runtime_error("Invalid differential equation"); + } + ; + + +expression : e { $$ = $1; } + + | expression NEWLINE { $$ = $1; } + ; + + + +e : ATOM { + context.solution = Term($1, context.state_variable()); + $$ = context.solution; + } + + | "(" e ")" { + context.solution = Term(); + context.solution.expr = "(" + $2.expr + ")"; + + if($2.deriv_nonzero()) + context.solution.deriv = "(" + $2.deriv + ")"; + if($2.a_nonzero()) + context.solution.a = "(" + $2.a + ")"; + if($2.b_nonzero()) + context.solution.b = "(" + $2.b + ")"; + + $$ = context.solution; + } + + | ATOM "(" arglist ")" { + context.solution = Term(); + context.solution.expr = $1 + "(" + $3.expr + ")"; + context.solution.b = $1 + "(" + $3.expr + ")"; + $$ = context.solution; + } + + | "-" e %prec UNARYMINUS { + context.solution = Term(); + context.solution.expr = "-" + $2.expr; + + if($2.deriv_nonzero()) + context.solution.deriv = "-" + $2.deriv; + if($2.a_nonzero()) + context.solution.a = "-" + $2.a; + if($2.b_nonzero()) + context.solution.b = "-" +$2.b; + + $$ = context.solution; + } + + | e "+" e { + context.solution = eval_derivative<MathOp::add>($1, $3, context.deriv_invalid, context.eqn_invalid); + $$ = context.solution; + } + + | e "-" e { + context.solution = eval_derivative<MathOp::sub>($1, $3, context.deriv_invalid, context.eqn_invalid); + $$ = context.solution; + } + + | e "*" e { + context.solution = eval_derivative<MathOp::mul>($1, $3, context.deriv_invalid, context.eqn_invalid); + $$ = context.solution; + } + + | e "/" e { + context.solution = eval_derivative<MathOp::div>($1, $3, context.deriv_invalid, context.eqn_invalid); + $$ = context.solution; + + } + ; + + + +arglist : /*nothing*/ { $$ = Term("", "0.0", "0.0", ""); } + + | arg { $$ = $1; } + + | arglist arg { + context.solution.expr = $1.expr + " " + $2.expr; + context.solution.b = $1.expr + " " + $2.expr; + $$ = context.solution; + } + + | arglist "," arg { + context.solution.expr = $1.expr + "," + $3.expr; + context.solution.b = $1.expr + "," + $3.expr; + $$ = context.solution; + } + ; + + + +arg : e { + context.solution = Term(); + context.solution.expr = $1.expr; + context.solution.b = $1.expr; + if($1.deriv_nonzero()) { + context.deriv_invalid = true; + context.eqn_invalid = true; + } + $$ = context.solution; + } + ; + +%% + + +void diffeq::Parser::error(const location &loc , const std::string &message) { + std::stringstream ss; + ss << "DiffEq Parser Error : " << message << " [Location : " << loc << "]"; + throw std::runtime_error(ss.str()); +} diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp new file mode 100644 index 0000000000..3c6fc82c9c --- /dev/null +++ b/src/nmodl/parser/diffeq_context.cpp @@ -0,0 +1,157 @@ +#include <iostream> + +#include "lexer/diffeq_lexer.hpp" +#include "utils/string_utils.hpp" + +using namespace diffeq; + + +Term::Term(std::string expr, std::string state) : expr(expr), b(expr) { + if (expr == state) { + deriv = "1.0"; + a = "1.0"; + b = "0.0"; + } +} + + +void Term::print() { + std::cout << "Term [expr, deriv, a, b] : "; + std::cout << expr << ", " << deriv << ", " << a << ", " << b << std::endl; +} + + +void DiffEqContext::print() { + std::cout << "-----------------DiffEq Context----------------" << std::endl; + std::cout << "deriv_invalid = " << deriv_invalid << std::endl; + std::cout << "eqn_invalid = " << eqn_invalid << std::endl; + std::cout << "expr = " << solution.expr << std::endl; + std::cout << "deriv = " << solution.deriv << std::endl; + std::cout << "a = " << solution.a << std::endl; + std::cout << "b = " << solution.b << std::endl; + std::cout << "-----------------------------------------------" << std::endl; +} + + +std::string DiffEqContext::cvode_deriv() { + std::string result; + if (!deriv_invalid) { + result = solution.deriv; + } + return result; +} + + +std::string DiffEqContext::cvode_eqnrhs() { + std::string result; + if (!eqn_invalid) { + result = solution.b; + } + return result; +} + + +/** + * When non-cnexp method is used, for solving non-linear equations we need original + * expression but with replacing every state variable with (state+0.001). In order + * to do this we scan original expression and build nw by replacing only state variable. + */ +std::string DiffEqContext::get_expr_for_nonlinear() { + std::string expression; + + /// build lexer instance + std::istringstream in(rhs); + Lexer scanner(&in); + + /// scan entire expression + while (true) { + auto sym = scanner.next_token(); + auto token = sym.token(); + if (token == Parser::token::END) { + break; + } + /// extract value of the token and check if it is a token + auto value = sym.value.as<std::string>(); + if (value == state) { + expression += "(" + value + "+0.001)"; + } else { + expression += value; + } + } + return expression; +} + + +/** + * Return solution for non-cnexp method and when equation is linear + */ +std::string DiffEqContext::get_cvode_linear_diffeq() { + auto result = "D" + state + " = " + "D" + state + "/(1.0-dt*(" + solution.deriv + "))"; + return result; +} + + +/** + * Return solution for non-cnexp method and when equation is non-linear. + */ +std::string DiffEqContext::get_cvode_nonlinear_diffeq() { + std::string expr = get_expr_for_nonlinear(); + std::string sol = "D" + state + " = " + "D" + state + "/(1.0-dt*("; + sol += "((" + expr + ")-(" + solution.expr + "))/0.001))"; + return sol; +} + + +/** + * Return solution for cnexp method + */ +std::string DiffEqContext::get_cnexp_solution() { + auto a = cvode_deriv(); + auto b = cvode_eqnrhs(); + /** + * a is zero typically when rhs doesn't have state variable + * in this case we have to remove "last" term /(0.0) from b + * and then return : state = state - dt*(b) + */ + std::string result; + if(a == "0.0") { + std::string suffix = "/(0.0)"; + b = b.substr(0, b.size()-suffix.size()); + result = state + " = " + state + "-dt*(" + b + ")"; + } else { + result = state + " = " + state + "+(1.0-exp(dt*(" + a + ")))*(" + b + "-" + state + ")"; + } + return result; +} + + +/** + * Return solution for non-cnexp method + */ +std::string DiffEqContext::get_non_cnexp_solution() { + std::string result; + if (!deriv_invalid) { + result = get_cvode_linear_diffeq(); + } else if (deriv_invalid and eqn_invalid) { + result = get_cvode_nonlinear_diffeq(); + } else { + throw std::runtime_error("Error in differential equation solver with non-cnexp"); + } + return result; +} + + +/** + * Return the solution for differential equation based on method used. + * + * \todo: Currently we have tested cnexp, euler and derivimplicit methods with + * all equations from BBP models. Need to test this against various other mod + * files, especially kinetic schemes, reaction-diffusion etc. + */ +std::string DiffEqContext::get_solution() { + if ( method == "cnexp" && !(deriv_invalid && eqn_invalid)) { + return get_cnexp_solution(); + } else { + return get_non_cnexp_solution(); + } +} diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp new file mode 100644 index 0000000000..665ac32f50 --- /dev/null +++ b/src/nmodl/parser/diffeq_context.hpp @@ -0,0 +1,123 @@ +#ifndef NMODL_DIFFEQ_CONTEXT +#define NMODL_DIFFEQ_CONTEXT + +#include <string> + +namespace diffeq { + + /** + * \class Term + * \brief Represent a term in differential equation and it's "current" solution + * + * When differential equation is parser, each variable/term is represented + * by this class. As expressions are formed, like a+b, the solution gets + * updated. + */ + + struct Term { + /// expression being solved + std::string expr; + + /// derivative of the expression + std::string deriv = "0.0"; + + /// \todo : need to check in neuron implementation? + std::string a = "0.0"; + std::string b = "0.0"; + + Term() = default; + + Term(std::string expr, std::string state); + + Term(std::string expr, std::string deriv, std::string a, std::string b) + : expr(expr), deriv(deriv), a(a), b(b) { + } + + /// helper routines used in parser + + bool deriv_nonzero() { + return deriv != "0.0"; + } + + bool a_nonzero() { + return a != "0.0"; + } + + bool b_nonzero() { + return b != "0.0"; + } + + void print(); + }; + + + /** + * \class DiffEqContext + * \brief Helper class used by driver and parser while solving diff equation + * + */ + + class DiffEqContext { + /// name of the method + std::string method; + + /// name of the state variable + std::string state; + + /// rhs of equation + std::string rhs; + + /// order of the diff equation + int order = 0; + + /// if equation is non-linear then expression to use during code generation + std::string expr_for_nonlinear; + + /// return solution for cnexp method + std::string get_cnexp_solution(); + + /// return solution for non-cnexp method + std::string get_non_cnexp_solution(); + + /// for non-cnexp methods : return the solution based on if equation is linear or not + std::string get_cvode_linear_diffeq(); + std::string get_cvode_nonlinear_diffeq(); + + /// methods similar to neuron implementation (not used at the moment) + std::string cvode_deriv(); + std::string cvode_eqnrhs(); + + public: + /// "final" solution of the equation + Term solution; + + /// if derivative of the equation is invalid + bool deriv_invalid = false; + + /// if equation itself is invalid + bool eqn_invalid = false; + + DiffEqContext() = default; + + DiffEqContext(std::string state, int order, std::string rhs, std::string method) + : state(state), order(order), rhs(rhs), method(method) { + } + + /// return the state variable + std::string state_variable() const { + return state; + } + + /// return solution of the differential equation + std::string get_solution(); + + /// return expression with Dstate added + std::string get_expr_for_nonlinear(); + + /// print the context (for debugging) + void print(); + }; + +} // namespace diffeq + +#endif // NMODL_DIFFEQ_CONTEXT diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp new file mode 100644 index 0000000000..86c7e93bab --- /dev/null +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -0,0 +1,44 @@ +#include <sstream> + +#include "lexer/diffeq_lexer.hpp" +#include "parser/diffeq_driver.hpp" +#include "utils/string_utils.hpp" + +namespace diffeq { + + std::string Driver::solve(std::string equation, std::string method, bool debug) { + /// split equation into lhs and rhs (lhs is a state variable) + auto parts = stringutils::split_string(equation, '='); + auto state = stringutils::trim(parts[0]); + auto rhs = stringutils::trim(parts[1]); + + /// expect prime on lhs, find order and remove quote + int order = std::count(state.begin(), state.end(), '\''); + stringutils::remove_character(state, '\''); + + /// error if no prime in equation or not a binary expression + if (order == 0 || state.size() == 0) { + throw std::runtime_error("Invalid equation, no prime on rhs? " + equation); + } + + return solve_equation(state, order, rhs, method, debug); + } + + std::string Driver::solve_equation(std::string& state, + int order, + std::string& rhs, + std::string& method, + bool debug) { + std::istringstream in(rhs); + DiffEqContext context(state, order, rhs, method); + Lexer scanner(&in); + Parser parser(scanner, context); + parser.parse(); + + if (debug) { + context.print(); + } + return context.get_solution(); + } + +} // namespace diffeq diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp new file mode 100644 index 0000000000..7adf5e472d --- /dev/null +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -0,0 +1,39 @@ +#ifndef DIFFEQ_DRIVER_HPP +#define DIFFEQ_DRIVER_HPP + +#include <string> + +#include "parser/diffeq_context.hpp" + + +namespace diffeq { + /** + * \class Driver + * \brief Class that binds all pieces together for parsing differential equations + * + * Driver class bind components required for lexing, parsing and ast + * generation for differential equations. + */ + + /// flex generated scanner class (extends base lexer class of flex) + class Lexer; + + /// parser class generated by bison + class Parser; + + class Driver { + private: + std::string solve_equation(std::string& state, + int order, + std::string& rhs, + std::string& method, + bool debug); + + public: + Driver() = default; + std::string solve(std::string equation, std::string method, bool debug = false); + }; + +} // namespace diffeq + +#endif diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp new file mode 100644 index 0000000000..d24281ef05 --- /dev/null +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -0,0 +1,169 @@ +#ifndef NMODL_DIFFEQ_HELPER +#define NMODL_DIFFEQ_HELPER + +#include "parser/diffeq_context.hpp" + +/** + * \brief Helper functions for solving differential equations + * + * Differential euqation parser (diffeq.yy) needs to solve derivative + * of various binary expressions and those are provided below. This + * implementation is based on original neuron implementation. + * + * \todo : The implementations here are verbose and has duplicate code. + * Need to revisit this, may be using better library like symengine + * altogether. + */ + +namespace diffeq { + + /// operators beign supported as part of binary expressions + enum class MathOp { add = 1, sub, mul, div }; + + template <MathOp Op> + inline Term eval_derivative(Term& first, Term& second, bool& deriv_invalid, bool& eqn_invalid); + + + /// implement (f(x) + g(x))' = f'(x) + g'(x) + template <> + inline Term eval_derivative<MathOp::add>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "+" + second.expr; + + if (first.deriv_nonzero() && second.deriv_nonzero()) { + solution.deriv = first.deriv + "+" + second.deriv; + } else if (first.deriv_nonzero()) { + solution.deriv = first.deriv; + } else if (second.deriv_nonzero()) { + solution.deriv = second.deriv; + } + + if (first.a_nonzero() && second.a_nonzero()) { + solution.a = first.a + "+" + second.a; + } else if (first.a_nonzero()) { + solution.a = first.a; + } else if (second.a_nonzero()) { + solution.a = second.a; + } + + if (first.b_nonzero() && second.b_nonzero()) { + solution.b = first.b + "+" + second.b; + } else if (first.b_nonzero()) { + solution.b = first.b; + } else if (second.b_nonzero()) { + solution.b = second.b; + } + + return solution; + } + + + /// implement (f(x) - g(x))' = f'(x) - g'(x) + template <> + inline Term eval_derivative<MathOp::sub>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "-" + second.expr; + + if (first.deriv_nonzero() && second.deriv_nonzero()) { + solution.deriv = first.deriv + "-" + second.deriv; + } else if (first.deriv_nonzero()) { + solution.deriv = first.deriv; + } else if (second.deriv_nonzero()) { + solution.deriv = "(-" + second.deriv + ")"; + } + + if (first.a_nonzero() && second.a_nonzero()) { + solution.a = first.a + "-" + second.a; + } else if (first.a_nonzero()) { + solution.a = first.a; + } else if (second.a_nonzero()) { + solution.a = "(-" + second.a + ")"; + } + + if (first.b_nonzero() && second.b_nonzero()) { + solution.b = first.b + "-" + second.b; + } else if (first.b_nonzero()) { + solution.b = first.b; + } else if (second.b_nonzero()) { + solution.b = "(-" + second.b + ")"; + } + + return solution; + } + + + /// implement (f(x) * g(x))' = f'(x)g(x) + f(x)g'(x) + template <> + inline Term eval_derivative<MathOp::mul>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "*" + second.expr; + + if (first.deriv_nonzero() && second.deriv_nonzero()) { + solution.deriv = "((" + first.deriv + ")*(" + second.expr + ")"; + solution.deriv += "+(" + first.expr + ")*(" + second.deriv + "))"; + } else if (first.deriv_nonzero()) { + solution.deriv = "(" + first.deriv + ")*(" + second.expr + ")"; + } else if (second.deriv_nonzero()) { + solution.deriv = "(" + first.expr + ")*(" + second.deriv + ")"; + } + + if (first.a_nonzero() && second.a_nonzero()) { + eqn_invalid = true; + } else if (first.a_nonzero() && second.b_nonzero()) { + solution.a = "(" + first.a + ")*(" + second.b + ")"; + } else if (second.a_nonzero() && first.b_nonzero()) { + solution.a = "(" + first.b + ")*(" + second.a + ")"; + } + + if (first.b_nonzero() && second.b_nonzero()) { + solution.b = "(" + first.b + ")*(" + second.b + ")"; + } + + return solution; + } + + + /** + * Implement (f(x) / g(x))' = (f'(x)g(x) - f(x)g'(x))/g^2(x) + * Note that the implementation is very limited for the g(x) + * and this needs to be discussed with Michael. + */ + template <> + inline Term eval_derivative<MathOp::div>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "/" + second.expr; + + if (second.deriv_nonzero()) { + deriv_invalid = true; + } else if (first.deriv_nonzero()) { + solution.deriv = "(" + first.deriv + ")/" + second.expr; + } + + if (second.a_nonzero()) { + eqn_invalid = true; + } else if (first.a_nonzero()) { + solution.a = "(" + first.a + ")/" + second.expr; + } + + if (first.b_nonzero()) { + solution.b = "(" + first.b + ")/" + second.expr; + } + + return solution; + } + +} // namespace diffeq + +#endif // NMODL_DIFFEQ_HELPER diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp index 2ada6d1075..78b6b8fe8f 100644 --- a/src/nmodl/parser/main.cpp +++ b/src/nmodl/parser/main.cpp @@ -45,6 +45,4 @@ int main(int argc, const char* argv[]) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; } - - return 0; } diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 2a360ed25a..2b4c1ce09c 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -35,9 +35,6 @@ /** enable tracing parser for debugging */ %define parse.trace -/** enable location tracking */ -%locations - /** add extra arguments to yyparse() and yylexe() methods */ %parse-param {class Lexer& scanner} %parse-param {class Driver& driver} @@ -56,6 +53,8 @@ /** namespace to enclose parser */ %name-prefix "nmodl" +%define api.namespace {nmodl} + /** set the parser's class identifier */ %define parser_class_name {Parser} diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 80fb1b1e8c..f69daeb93c 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -23,7 +23,7 @@ namespace nmodl { * class. * * \todo lexer, parser and ast member variables are used inside lexer/ - * parser instaces. The local instaces are created inside parse_stream + * parser instances. The local instaces are created inside parse_stream * and hence the pointers are no longer valid except ast. Need better * way to handle this. * diff --git a/src/nmodl/parser/verbatim_context.hpp b/src/nmodl/parser/verbatim_context.hpp index 800a16a4ba..529d306b5c 100644 --- a/src/nmodl/parser/verbatim_context.hpp +++ b/src/nmodl/parser/verbatim_context.hpp @@ -1,25 +1,21 @@ -#ifndef _NMODL_VERBATIM_CONTEXT_ -#define _NMODL_VERBATIM_CONTEXT_ +#ifndef NMODL_VERBATIM_CONTEXT +#define NMODL_VERBATIM_CONTEXT #include <iostream> class VerbatimContext { public: - void* scanner; - std::istream* is; - - std::string* result; + void* scanner = nullptr; + std::istream* is = nullptr; + std::string* result = nullptr; VerbatimContext(std::istream* is = &std::cin) { - scanner = NULL; - result = NULL; init_scanner(); this->is = is; } virtual ~VerbatimContext() { destroy_scanner(); - if (result) { delete result; } @@ -33,4 +29,4 @@ class VerbatimContext { int Verbatim_parse(VerbatimContext*); -#endif // _NMODL_VERBATIM_CONTEXT_ +#endif // NMODL_VERBATIM_CONTEXT diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 3cdabf82c0..c2fb728dec 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -53,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.hpp index 0989b45f7c..67111a6d16 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.hpp +++ b/src/nmodl/visitors/nmodl_visitor_helper.hpp @@ -17,7 +17,6 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement) { - for (auto iter = elements.begin(); iter != elements.end(); iter++) { /// statements need indentation at the start if (statement) { @@ -39,9 +38,9 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, /// if there are multiple inline comments then we want them to be /// contiguous and only last comment should have extra line. bool extra_newline = false; - if(!is_last(iter, elements)) { + if (!is_last(iter, elements)) { extra_newline = true; - if((*iter)->is_comment() && (*(iter+1))->is_comment()) { + if ((*iter)->is_comment() && (*(iter + 1))->is_comment()) { extra_newline = false; } } diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index d20366f328..8c3067e283 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -4,6 +4,7 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" +#include "parser/diffeq_driver.hpp" #include "test/utils/nmodl_constructs.h" //============================================================================= @@ -110,3 +111,31 @@ SCENARIO("Parser test for invalid NMODL grammar constructs") { } } } + + +//============================================================================= +// Differential Equation Parser tests +//============================================================================= + +std::string solve_construct(const std::string& equation, std::string method) { + diffeq::Driver driver; + auto solution = driver.solve(equation, method); + return solution; +} + +SCENARIO("Solving differential equations using NEURON's implementation") { + GIVEN("A differential equation") { + int counter = 0; + for (const auto& test_case : diff_eq_constructs) { + auto prefix = "." + std::to_string(counter); + WHEN(prefix + " EQUATION = " + test_case.equation + " METHOD = " + test_case.method) { + THEN(prefix + " SOLUTION = " + test_case.solution) { + auto expected_result = test_case.solution; + auto result = solve_construct(test_case.equation, test_case.method); + REQUIRE(result == expected_result); + } + } + counter++; + } + } +} diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 8bd94508f6..a3b8e62ba1 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1283,4 +1283,332 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } } // clang-format on -}; \ No newline at end of file +}; + + +std::vector<DiffEqTestCase> diff_eq_constructs{ + // clang-format off + + /// differential equations from BlueBrain mod files including latest V6 branch + + { + "NaTs2_t.mod", + "m' = (mInf-m)/mTau", + "m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m)", + "cnexp" + }, + + { + "tmgExSyn.mod", + "A' = -A/tau_r", + "A = A+(1.0-exp(dt*((-1.0)/tau_r)))*(-(0.0)/((-1.0)/tau_r)-A)", + "cnexp" + }, + + { + "CaDynamics_DC0.mod", + "cai' = -(10000)*ica*surftovol*gamma/(2*FARADAY) - (cai - minCai)/decay", + "cai = cai+(1.0-exp(dt*((-((1.0))/decay))))*(-(((((-(10000))*(ica))*(surftovol))*(gamma))/(2*FARADAY)-(((-minCai)))/decay)/((-((1.0))/decay))-cai)", + "cnexp" + }, + + { + "CaDynamics_DC1.mod", + "cai' = -(10000)*ica*surftovol*gamma/(2*FARADAY) - (cai - minCai)/decay * diamref * surftovol", + "cai = cai+(1.0-exp(dt*((-((((1.0))/decay)*(diamref))*(surftovol)))))*(-(((((-(10000))*(ica))*(surftovol))*(gamma))/(2*FARADAY)-(((((-minCai)))/decay)*(diamref))*(surftovol))/((-((((1.0))/decay)*(diamref))*(surftovol)))-cai)", + "cnexp" + }, + + { + "DetAMPANMDA.mod", + "A_AMPA' = -A_AMPA/tau_r_AMPA", + "A_AMPA = A_AMPA+(1.0-exp(dt*((-1.0)/tau_r_AMPA)))*(-(0.0)/((-1.0)/tau_r_AMPA)-A_AMPA)", + "cnexp" + }, + + { + "StochKv.mod", + "n' = a - (a + b)*n", + "n = n+(1.0-exp(dt*((-((a+b))*(1.0)))))*(-(a)/((-((a+b))*(1.0)))-n)", + "cnexp" + }, + + { + "StochKv2.mod", + "l' = al - (al + bl)*l", + "l = l+(1.0-exp(dt*((-((al+bl))*(1.0)))))*(-(al)/((-((al+bl))*(1.0)))-l)", + "cnexp" + }, + + { + "_calcium.mod", + "mcal' = ((inf-mcal)/tau)", + "mcal = mcal+(1.0-exp(dt*(((((-1.0)))/tau))))*(-((((inf))/tau))/(((((-1.0)))/tau))-mcal)", + "cnexp" + }, + + { + "_calciumc_concentration.mod", + "cai' = -(ica*surftovol/FARADAY) - cai/decay", + "cai = cai+(1.0-exp(dt*((-(1.0)/decay))))*(-(-(((ica)*(surftovol))/FARADAY))/((-(1.0)/decay))-cai)", + "cnexp" + }, + + { + "_outside_calcium_concentration.mod", + "cao' = ica*(1e8)/(fhspace*FARADAY) + (cabath-cao)/trans", + "cao = cao+(1.0-exp(dt*((((-1.0)))/trans)))*(-(((ica)*((1e8)))/(fhspace*FARADAY)+((cabath))/trans)/((((-1.0)))/trans)-cao)", + "cnexp" + }, + + + /// glusynapse.mod for plasticity simulations + + + /// using cnexp method + + { + "GluSynapse.mod", + "A_AMPA' = -A_AMPA/tau_r_AMPA", + "A_AMPA = A_AMPA+(1.0-exp(dt*((-1.0)/tau_r_AMPA)))*(-(0.0)/((-1.0)/tau_r_AMPA)-A_AMPA)", + "cnexp" + }, + + { + "GluSynapse.mod", + "m_VDCC' = (minf_VDCC-m_VDCC)/mtau_VDCC", + "m_VDCC = m_VDCC+(1.0-exp(dt*((((-1.0)))/mtau_VDCC)))*(-(((minf_VDCC))/mtau_VDCC)/((((-1.0)))/mtau_VDCC)-m_VDCC)", + "cnexp" + }, + + { + "GluSynapse.mod", + "cai_CR' = -(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR", + "cai_CR = cai_CR+(1.0-exp(dt*((-((1.0))/tau_ca_CR))))*(-((((-(1e-9))*((ica_NMDA+ica_VDCC)))*(gamma_ca_CR))/((1e-15)*volume_CR*2*FARADAY)-(((-min_ca_CR)))/tau_ca_CR)/((-((1.0))/tau_ca_CR))-cai_CR)", + "cnexp" + }, + + { + "GluSynapse.mod", + "effcai_GB' = -0.005*effcai_GB + (cai_CR - min_ca_CR)", + "effcai_GB = effcai_GB+(1.0-exp(dt*((-0.005)*(1.0))))*(-((cai_CR-min_ca_CR))/((-0.005)*(1.0))-effcai_GB)", + "cnexp" + }, + + { + "GluSynapse.mod", + "Rho_GB' = ( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB* gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB)", + "Rho_GB = Rho_GB+(1.0-exp(dt*(((((((-1.0)*((1-Rho_GB))+(-Rho_GB)*(((-1.0)))))*((rho_star_GB-Rho_GB))+(-Rho_GB*(1-Rho_GB))*(((-1.0))))+(potentiate_GB*gamma_p_GB)*(((-1.0)))-(depress_GB*gamma_d_GB)*(1.0)))/((1e3)*tau_GB))))*(-Rho_GB)", + "cnexp" + }, + + { + "GluSynapse.mod", + "Use_GB' = (Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB)", + "Use_GB = Use_GB+(1.0-exp(dt*((((-1.0)))/((1e3)*tau_Use_GB))))*(-(((Use_d_GB+(Rho_GB)*((Use_p_GB-Use_d_GB))))/((1e3)*tau_Use_GB))/((((-1.0)))/((1e3)*tau_Use_GB))-Use_GB)", + "cnexp" + }, + + /// some made-up examples to test cnexp solver implementation + + { + "GluSynapse.mod", + "A_AMPA' = fun(tau_r_AMPA)", + "A_AMPA = A_AMPA-dt*(-(fun(tau_r_AMPA)))", + "cnexp" + }, + + { + "GluSynapse.mod", + "A_AMPA' = fun(tau_r_AMPA, B_AMPA) + B_AMPA", + "A_AMPA = A_AMPA-dt*(-(fun(tau_r_AMPA,B_AMPA)+B_AMPA))", + "cnexp" + }, + + { + "GluSynapse.mod", + "A_AMPA' = tau_r_AMPA/A_AMPA", + "DA_AMPA = DA_AMPA/(1.0-dt*(((tau_r_AMPA/(A_AMPA+0.001))-(tau_r_AMPA/A_AMPA))/0.001))", + "cnexp" + }, + + { + "GluSynapse.mod", + "A_AMPA' = A_AMPA*A_AMPA", + "A_AMPA = A_AMPA+(1.0-exp(dt*(((1.0)*(A_AMPA)+(A_AMPA)*(1.0)))))*(-A_AMPA)", + "cnexp" + }, + + { + "GluSynapse.mod", + "A_AMPA' = tau_r_AMPA/A_AMPA", + "DA_AMPA = DA_AMPA/(1.0-dt*(((tau_r_AMPA/(A_AMPA+0.001))-(tau_r_AMPA/A_AMPA))/0.001))", + "derivimplicit" + }, + + { + "GluSynapse.mod", + "A_AMPA' = A_AMPA*A_AMPA", + "DA_AMPA = DA_AMPA/(1.0-dt*(((1.0)*(A_AMPA)+(A_AMPA)*(1.0))))", + "euler" + }, + /// using derivimplicit method + /// note that the equation in state block gets changed by replacing state variable with Dstate. + /// below expressions are from ode_matsol1 method + + { + "GluSynapse.mod", + "A_AMPA' = -A_AMPA/tau_r_AMPA", + "DA_AMPA = DA_AMPA/(1.0-dt*((-1.0)/tau_r_AMPA))", + "derivimplicit" + }, + + { + "GluSynapse.mod", + "m_VDCC' = (minf_VDCC-m_VDCC)/mtau_VDCC", + "Dm_VDCC = Dm_VDCC/(1.0-dt*((((-1.0)))/mtau_VDCC))", + "derivimplicit" + }, + + { + "GluSynapse.mod", + "cai_CR' = -(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR", + "Dcai_CR = Dcai_CR/(1.0-dt*((-((1.0))/tau_ca_CR)))", + "derivimplicit" + }, + + { + "GluSynapse.mod", + "effcai_GB' = -0.005*effcai_GB + (cai_CR - min_ca_CR)", + "Deffcai_GB = Deffcai_GB/(1.0-dt*((-0.005)*(1.0)))", + "derivimplicit" + }, + + { + "GluSynapse.mod", + "Rho_GB' = ( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB)", + "DRho_GB = DRho_GB/(1.0-dt*(((((((-1.0)*((1-Rho_GB))+(-Rho_GB)*(((-1.0)))))*((rho_star_GB-Rho_GB))+(-Rho_GB*(1-Rho_GB))*(((-1.0))))+(potentiate_GB*gamma_p_GB)*(((-1.0)))-(depress_GB*gamma_d_GB)*(1.0)))/((1e3)*tau_GB)))", + "derivimplicit" + }, + + { + "GluSynapse.mod", + "Use_GB' = (Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB)", + "DUse_GB = DUse_GB/(1.0-dt*((((-1.0)))/((1e3)*tau_Use_GB)))", + "derivimplicit" + }, + + + /// using euler method : solutions are same as derivimplicit method + + { + "GluSynapse.mod", + "A_AMPA' = -A_AMPA/tau_r_AMPA", + "DA_AMPA = DA_AMPA/(1.0-dt*((-1.0)/tau_r_AMPA))", + "euler" + }, + + { + "GluSynapse.mod", + "m_VDCC' = (minf_VDCC-m_VDCC)/mtau_VDCC", + "Dm_VDCC = Dm_VDCC/(1.0-dt*((((-1.0)))/mtau_VDCC))", + "euler" + }, + + { + "GluSynapse.mod", + "cai_CR' = -(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR", + "Dcai_CR = Dcai_CR/(1.0-dt*((-((1.0))/tau_ca_CR)))", + "euler" + }, + + { + "GluSynapse.mod", + "effcai_GB' = -0.005*effcai_GB + (cai_CR - min_ca_CR)", + "Deffcai_GB = Deffcai_GB/(1.0-dt*((-0.005)*(1.0)))", + "euler" + }, + + { + "GluSynapse.mod", + "Rho_GB' = ( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB)", + "DRho_GB = DRho_GB/(1.0-dt*(((((((-1.0)*((1-Rho_GB))+(-Rho_GB)*(((-1.0)))))*((rho_star_GB-Rho_GB))+(-Rho_GB*(1-Rho_GB))*(((-1.0))))+(potentiate_GB*gamma_p_GB)*(((-1.0)))-(depress_GB*gamma_d_GB)*(1.0)))/((1e3)*tau_GB)))", + "euler" + }, + + { + "GluSynapse.mod", + "Use_GB' = (Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB)", + "DUse_GB = DUse_GB/(1.0-dt*((((-1.0)))/((1e3)*tau_Use_GB)))", + "euler" + }, + + + /// equations of nonlinear from taken from nocmodlx/test/input/usecases/nonlinear + /// using derivimplicit and euler method + + { + "wc.mod", + "uu' = -uu+f(aee*uu-aie*vv-ze+i_e)", + "Duu = Duu/(1.0-dt*(((-(uu+0.001)+f(aee*(uu+0.001)-aie*vv-ze+i_e))-(-uu+f(aee*uu-aie*vv-ze+i_e)))/0.001))", + "derivimplicit" + }, + + { + "wc.mod", + "vv' = (-vv+f(aei*uu-aii*vv-zi+i_i))/tau", + "Dvv = Dvv/(1.0-dt*((((-(vv+0.001)+f(aei*uu-aii*(vv+0.001)-zi+i_i))/tau)-((-vv+f(aei*uu-aii*vv-zi+i_i))/tau))/0.001))", + "derivimplicit" + }, + + { + "ER.mod", + "caer' = -(0.001)*( Jip3(cali,caer, ip3ip, Vip3, dact, dinh, dip3, ddis)+errel(cali,caer,kerrel,kerm)-erfil(cali,caer,kerfila,kerfilb)+erlek(cali,caer,kerlek))/(rhover/fer)", + "Dcaer = Dcaer/(1.0-dt*(((-(0.001)*(Jip3(cali,(caer+0.001),ip3ip,Vip3,dact,dinh,dip3,ddis)+errel(cali,(caer+0.001),kerrel,kerm)-erfil(cali,(caer+0.001),kerfila,kerfilb)+erlek(cali,(caer+0.001),kerlek))/(rhover/fer))-(-(0.001)*(Jip3(cali,caer,ip3ip,Vip3,dact,dinh,dip3,ddis)+errel(cali,caer,kerrel,kerm)-erfil(cali,caer,kerfila,kerfilb)+erlek(cali,caer,kerlek))/(rhover/fer)))/0.001))", + "derivimplicit" + }, + + { + "ER.mod", + "Jip3h' = (Jip3hinf(ip3ip, cali, dinh, dip3, ddis)-Jip3h)/Jip3th(ip3ip, ainh, cali, dinh, dip3, ddis)", + "DJip3h = DJip3h/(1.0-dt*((((-1.0)))/Jip3th(ip3ip,ainh,cali,dinh,dip3,ddis)))", + "derivimplicit" + }, + + { + "gr_ltp1.mod", + "messenger' = -gamma*picanmda - eta*messenger", + "Dmessenger = Dmessenger/(1.0-dt*((-(eta)*(1.0))))", + "derivimplicit" + }, + + { + "gr_ltp1.mod", + "Np' = nu1*messenger - (pp - picanmda*gdel1)*Np +(Mp*Np*Np)/(Ap+Np*Np)", + "DNp = DNp/(1.0-dt*(((nu1*messenger-(pp-picanmda*gdel1)*(Np+0.001)+(Mp*(Np+0.001)*(Np+0.001))/(Ap+(Np+0.001)*(Np+0.001)))-(nu1*messenger-(pp-picanmda*gdel1)*Np+(Mp*Np*Np)/(Ap+Np*Np)))/0.001))", + "derivimplicit" + }, + + { + "cajsracc.mod", + "cri' = ((cui - cri)/180 - 0.5*icr)/(1 + Csqn*Kmcsqn/((cri + Kmcsqn)*(cri + Kmcsqn)))", + "Dcri = Dcri/(1.0-dt*(((((cui-(cri+0.001))/180-0.5*icr)/(1+Csqn*Kmcsqn/(((cri+0.001)+Kmcsqn)*((cri+0.001)+Kmcsqn))))-(((cui-cri)/180-0.5*icr)/(1+Csqn*Kmcsqn/((cri+Kmcsqn)*(cri+Kmcsqn)))))/0.001))", + "derivimplicit" + }, + + { + "syn_bip_gan.mod", + "s' = (s_inf-s)/((1-s_inf)*tau*s)", + "Ds = Ds/(1.0-dt*((((s_inf-(s+0.001))/((1-s_inf)*tau*(s+0.001)))-((s_inf-s)/((1-s_inf)*tau*s)))/0.001))", + "euler" + }, + + { + "syn_rod_bip.mod", + "s' = (s_inf-s)/((1-s_inf)*tau*s)", + "Ds = Ds/(1.0-dt*((((s_inf-(s+0.001))/((1-s_inf)*tau*(s+0.001)))-((s_inf-s)/((1-s_inf)*tau*s)))/0.001))", + "euler" + }, + + // clang-format on +}; diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.h b/test/nmodl/transpiler/utils/nmodl_constructs.h index e4c5b5c52a..389c4932f2 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.h +++ b/test/nmodl/transpiler/utils/nmodl_constructs.h @@ -2,8 +2,10 @@ #define NMODL_TEST_CONSTRUCTS #include <string> +#include <vector> #include <map> +/// represent nmodl construct test struct NmodlTestCase { /// name of the test std::string name; @@ -26,7 +28,23 @@ struct NmodlTestCase { } }; +/// represent differential equation test +struct DiffEqTestCase { + /// name of the mod file + std::string name; + + /// differential equation to solve + std::string equation; + + /// expected solution + std::string solution; + + /// solve method + std::string method; +}; + extern std::map<std::string, NmodlTestCase> nmdol_invalid_constructs; extern std::map<std::string, NmodlTestCase> nmodl_valid_constructs; +extern std::vector<DiffEqTestCase> diff_eq_constructs; #endif \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index d4b7ddfeb2..77fc5b1790 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -325,9 +325,7 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { THEN("existing variables could be renamed") { std::vector<std::pair<std::string, std::string>> variables = { - {"m", "mm"}, - {"gNaTs2_tbar", "new_gNaTs2_tbar"}, - {"mAlpha", "mBeta"}, + {"m", "mm"}, {"gNaTs2_tbar", "new_gNaTs2_tbar"}, {"mAlpha", "mBeta"}, }; auto result = run_var_rename_visitor(input, variables); REQUIRE(result == expected_output); From a40f1cc58ba62884ee48f99d23cf9daeb3df8b23 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 6 Feb 2018 11:39:56 +0100 Subject: [PATCH 053/871] Implementation of CnexpSolveVisitor that does in-place replacement of ODE with it's solution: - transformation is done at ast level - only done if cnexp method is specified - cnexp_possible method added to diffeq::Driver - create_statement function added to retun ast::Statement from arbitrary code string - cleanup / refactoring with clang-tidy - added test for cnexpsolve visitor Change-Id: Id1a92dc6cc97529e71aa57016940ff1ebc927fd8 NMODL Repo SHA: BlueBrain/nmodl@2d1212132df46dbcd843f6700549afbeff1abf49 --- src/nmodl/lexer/diffeq.ll | 9 +- src/nmodl/parser/diffeq.yy | 9 +- src/nmodl/parser/diffeq_context.cpp | 24 +-- src/nmodl/parser/diffeq_context.hpp | 12 +- src/nmodl/parser/diffeq_driver.cpp | 40 +++-- src/nmodl/parser/diffeq_driver.hpp | 14 +- src/nmodl/parser/main.cpp | 2 + src/nmodl/symtab/symbol_table.cpp | 24 +-- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/utils/table_data.cpp | 2 +- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/cnexp_solve_visitor.cpp | 53 ++++++ src/nmodl/visitors/cnexp_solve_visitor.hpp | 39 +++++ src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- .../visitors/local_var_rename_visitor.cpp | 4 +- src/nmodl/visitors/main.cpp | 13 +- src/nmodl/visitors/perf_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_visitor.cpp | 1 + src/nmodl/visitors/verbatim_visitor.hpp | 5 +- src/nmodl/visitors/visitor_utils.cpp | 23 ++- src/nmodl/visitors/visitor_utils.hpp | 8 + test/nmodl/transpiler/CMakeLists.txt | 2 +- test/nmodl/transpiler/parser/parser.cpp | 2 + .../transpiler/utils/nmodl_constructs.cpp | 2 + test/nmodl/transpiler/visitor/visitor.cpp | 153 +++++++++++++++++- 25 files changed, 392 insertions(+), 57 deletions(-) create mode 100644 src/nmodl/visitors/cnexp_solve_visitor.cpp create mode 100644 src/nmodl/visitors/cnexp_solve_visitor.hpp diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index 73e1287c34..4cfda9ed37 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -8,7 +8,6 @@ #define yyterminate() return Parser::make_END(loc); - /// need to store original equation for non-linear solution and hence we add all tokens #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } #define YY_NO_UNISTD_H @@ -18,7 +17,7 @@ D [0-9] E [Ee][-+]?{D}+ -/** we do use yymore feature in copy modes */ +/** if want to use yymore feature in copy modes */ %option yymore /** name of the lexer header file */ @@ -42,7 +41,7 @@ E [Ee][-+]?{D}+ /** need to put in buffer for custom routines */ %option input -/* not an interactive lexer, takes a file */ +/* not an interactive lexer, takes a file or stream */ %option batch /** enable debug mode (disable for production) */ @@ -75,9 +74,9 @@ E [Ee][-+]?{D}+ "," { return Parser::make_COMMA(yytext, loc); } -:.* { /*ignore inline comments */ } +:.* { /* ignore inline comments */ } -[ \t] { /* ignore spacing characters */ } +[ \t] { /* ignore spacing characters */ } {D}+ | {D}+"."{D}*({E})? | diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 581d78d9bf..257bb2e339 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -1,4 +1,11 @@ -/* Bison specification for parsing derivative expressions in DERIVATIVE block */ +/****************************************************************************** + * + * @brief Bison grammar and parser implementation for ODEs + * + * ODEs specified in DERIVATIVE block needs to be solved during code generation. + * This parser implementation is based on original NEURON's nocmodl implementation + * and solves ODEs using helper routines provided. + *****************************************************************************/ %code requires { diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 3c6fc82c9c..fe31b93b67 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -6,7 +6,7 @@ using namespace diffeq; -Term::Term(std::string expr, std::string state) : expr(expr), b(expr) { +Term::Term(const std::string& expr, const std::string& state) : expr(expr), b(expr) { if (expr == state) { deriv = "1.0"; a = "1.0"; @@ -54,7 +54,7 @@ std::string DiffEqContext::cvode_eqnrhs() { /** * When non-cnexp method is used, for solving non-linear equations we need original * expression but with replacing every state variable with (state+0.001). In order - * to do this we scan original expression and build nw by replacing only state variable. + * to do this we scan original expression and build new by replacing only state variable. */ std::string DiffEqContext::get_expr_for_nonlinear() { std::string expression; @@ -110,13 +110,13 @@ std::string DiffEqContext::get_cnexp_solution() { auto b = cvode_eqnrhs(); /** * a is zero typically when rhs doesn't have state variable - * in this case we have to remove "last" term /(0.0) from b + * in this case we have to remove "last" term "/(0.0)" from b * and then return : state = state - dt*(b) */ std::string result; - if(a == "0.0") { - std::string suffix = "/(0.0)"; - b = b.substr(0, b.size()-suffix.size()); + if (a == "0.0") { + std::string suffix = "/(0.0)"; + b = b.substr(0, b.size() - suffix.size()); result = state + " = " + state + "-dt*(" + b + ")"; } else { result = state + " = " + state + "+(1.0-exp(dt*(" + a + ")))*(" + b + "-" + state + ")"; @@ -148,10 +148,14 @@ std::string DiffEqContext::get_non_cnexp_solution() { * all equations from BBP models. Need to test this against various other mod * files, especially kinetic schemes, reaction-diffusion etc. */ -std::string DiffEqContext::get_solution() { - if ( method == "cnexp" && !(deriv_invalid && eqn_invalid)) { - return get_cnexp_solution(); +std::string DiffEqContext::get_solution(bool& cnexp_possible) { + std::string solution; + if (method == "cnexp" && !(deriv_invalid && eqn_invalid)) { + cnexp_possible = true; + solution = get_cnexp_solution(); } else { - return get_non_cnexp_solution(); + cnexp_possible = false; + solution = get_non_cnexp_solution(); } + return solution; } diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 665ac32f50..4ed82f98b9 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -9,9 +9,9 @@ namespace diffeq { * \class Term * \brief Represent a term in differential equation and it's "current" solution * - * When differential equation is parser, each variable/term is represented + * When differential equation is parsed, each variable/term is represented * by this class. As expressions are formed, like a+b, the solution gets - * updated. + * updated */ struct Term { @@ -27,7 +27,7 @@ namespace diffeq { Term() = default; - Term(std::string expr, std::string state); + Term(const std::string& expr, const std::string& state); Term(std::string expr, std::string deriv, std::string a, std::string b) : expr(expr), deriv(deriv), a(a), b(b) { @@ -58,7 +58,7 @@ namespace diffeq { */ class DiffEqContext { - /// name of the method + /// name of the solve method std::string method; /// name of the state variable @@ -83,7 +83,7 @@ namespace diffeq { std::string get_cvode_linear_diffeq(); std::string get_cvode_nonlinear_diffeq(); - /// methods similar to neuron implementation (not used at the moment) + /// \todo: methods inherited neuron implementation std::string cvode_deriv(); std::string cvode_eqnrhs(); @@ -109,7 +109,7 @@ namespace diffeq { } /// return solution of the differential equation - std::string get_solution(); + std::string get_solution(bool& cnexp_possible); /// return expression with Dstate added std::string get_expr_for_nonlinear(); diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 86c7e93bab..2e5966b37b 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -1,4 +1,5 @@ #include <sstream> +#include <utility> #include "lexer/diffeq_lexer.hpp" #include "parser/diffeq_driver.hpp" @@ -6,39 +7,58 @@ namespace diffeq { - std::string Driver::solve(std::string equation, std::string method, bool debug) { - /// split equation into lhs and rhs (lhs is a state variable) + void Driver::parse_equation(const std::string& equation, + std::string& state, + std::string& rhs, + int& order) { auto parts = stringutils::split_string(equation, '='); - auto state = stringutils::trim(parts[0]); - auto rhs = stringutils::trim(parts[1]); + state = stringutils::trim(parts[0]); + rhs = stringutils::trim(parts[1]); /// expect prime on lhs, find order and remove quote - int order = std::count(state.begin(), state.end(), '\''); + order = std::count(state.begin(), state.end(), '\''); stringutils::remove_character(state, '\''); - /// error if no prime in equation or not a binary expression - if (order == 0 || state.size() == 0) { + /// error if no prime in equation or not an assignment statement + if (order == 0 || state.empty()) { throw std::runtime_error("Invalid equation, no prime on rhs? " + equation); } + } - return solve_equation(state, order, rhs, method, debug); + std::string Driver::solve(std::string equation, std::string method, bool debug) { + std::string state, rhs; + int order = 0; + bool cnexp_possible; + parse_equation(equation, state, rhs, order); + return solve_equation(state, order, rhs, method, cnexp_possible, debug); } std::string Driver::solve_equation(std::string& state, int order, std::string& rhs, std::string& method, + bool& cnexp_possible, bool debug) { std::istringstream in(rhs); DiffEqContext context(state, order, rhs, method); Lexer scanner(&in); Parser parser(scanner, context); parser.parse(); - if (debug) { context.print(); } - return context.get_solution(); + return context.get_solution(cnexp_possible); + } + + /// \todo : instead of using neuron like api, we need to refactor + bool Driver::cnexp_possible(std::string equation, std::string& solution) { + std::string state, rhs; + int order = 0; + bool cnexp_possible; + std::string method = "cnexp"; + parse_equation(equation, state, rhs, order); + solution = solve_equation(state, order, rhs, method, cnexp_possible); + return cnexp_possible; } } // namespace diffeq diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index 7adf5e472d..55ec4e6241 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -27,11 +27,23 @@ namespace diffeq { int order, std::string& rhs, std::string& method, - bool debug); + bool& cnexp_possible, + bool debug = false); + + /// parse given equation into lhs, rhs and find it's order and state variable + void parse_equation(const std::string& equation, + std::string& state, + std::string& rhs, + int& order); public: Driver() = default; + + /// solve equation using provided method std::string solve(std::string equation, std::string method, bool debug = false); + + /// check if given equation can be solved using cnexp method + bool cnexp_possible(std::string equation, std::string& solution); }; } // namespace diffeq diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main.cpp index 78b6b8fe8f..2ada6d1075 100644 --- a/src/nmodl/parser/main.cpp +++ b/src/nmodl/parser/main.cpp @@ -45,4 +45,6 @@ int main(int argc, const char* argv[]) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; } + + return 0; } diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 6335095cbf..8d634da7af 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -37,12 +37,16 @@ namespace symtab { return node->get_type_name(); } + /// \todo: should return unknown position ? std::string SymbolTable::position() const { auto token = node->get_token(); - if (token) - return token->position(); - else - return ModToken().position(); + std::string position; + if (token != nullptr) { + position = token->position(); + } else { + position = ModToken().position(); + } + return position; } /// insert new symbol table of one of the children block @@ -92,9 +96,9 @@ namespace symtab { auto parent_table = parent; // traverse all parent blocks to make sure everyone is global - while (global_scope && parent_table) { + while (global_scope && (parent_table != nullptr)) { parent_table = parent_table->parent; - if (parent_table) { + if (parent_table != nullptr) { global_scope = parent_table->global_scope(); } } @@ -104,7 +108,7 @@ namespace symtab { /// lookup for symbol in current scope as well as all parents std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(const std::string& name) { auto symbol = table.lookup(name); - if (!symbol && parent) { + if (!symbol && (parent != nullptr)) { symbol = parent->lookup_in_scope(name); } return symbol; @@ -119,7 +123,7 @@ namespace symtab { /// parent symbol is not set means symbol table is /// is not used with visitor at all. it would be ok // to just return nullptr? - if (!parent_symtab) { + if (parent_symtab == nullptr) { throw std::logic_error("Lookup wit previous symtab = nullptr "); } @@ -130,7 +134,7 @@ namespace symtab { // check into all parent symbol tables auto parent = parent_symtab->get_parent_table(); - while (parent) { + while (parent != nullptr) { symbol = parent->lookup(name); if (symbol) { break; @@ -270,7 +274,7 @@ namespace symtab { } /// if has parent scope, setup new parent symbol table - if (parent_symtab) { + if (parent_symtab != nullptr) { parent_symtab = parent_symtab->get_parent_table(); } diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index c2fb728dec..3cdabf82c0 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -53,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 0167ad0058..b237b854ae 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -51,7 +51,7 @@ void TableData::print(std::stringstream& stream, int indent) { if (title.length() > row_width) { int extra_size = title.length() - row_width; int column_pad = extra_size / ncolumns; - if (extra_size % ncolumns) { + if ((extra_size % ncolumns) != 0) { column_pad++; } for (auto& column : col_width) { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 0965d75b37..ed5a576cf9 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -24,6 +24,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ) diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp new file mode 100644 index 0000000000..6c2781e196 --- /dev/null +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -0,0 +1,53 @@ +#include <sstream> + +#include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "parser/diffeq_driver.hpp" +#include "visitors/visitor_utils.hpp" + +void CnexpSolveVisitor::visit_solve_block(SolveBlock* node) { + if (node->method) { + solve_method = node->method->value->eval(); + } +} + +void CnexpSolveVisitor::visit_derivative_block(DerivativeBlock* node) { + derivative_block = true; + node->visit_children(this); + derivative_block = false; +} + +void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { + auto& lhs = node->lhs; + auto& rhs = node->rhs; + + /// we have to only solve binary expressions in derivative block if cnexp method specified + if (!derivative_block || (node->op.value != BOP_ASSIGN) || (solve_method != cnexp_method)) { + return; + } + + /// lhs of the expression should be variable + if (!lhs->is_var_name()) { + return; + } + + auto name = std::dynamic_pointer_cast<VarName>(lhs)->name; + if (name->is_prime_name()) { + /// convert ode into string format + std::stringstream stream; + NmodlPrintVisitor v(stream); + node->visit_children(&v); + auto equation = stream.str(); + + /// check if ode can be solved with cnexp method + diffeq::Driver diffeq_driver; + std::string solution; + if (diffeq_driver.cnexp_possible(equation, solution)) { + auto statement = create_statement(solution); + auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); + auto bin_expr = std::dynamic_pointer_cast<BinaryExpression>(expr_statement->expression); + lhs.reset(bin_expr->lhs->clone()); + rhs.reset(bin_expr->rhs->clone()); + } + } +} diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp new file mode 100644 index 0000000000..1b5f3a96b3 --- /dev/null +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -0,0 +1,39 @@ +#ifndef CNEXP_SOLVE_VISITOR_HPP +#define CNEXP_SOLVE_VISITOR_HPP + +#include <string> + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" + +/** + * \class CnexpSolveVisitor + * \brief Visitor that solves and replaces ODEs using cnexp method + * + * This pass solves ODEs in derivative block if cnexp method is used. + * The original ODEs get replaced with the solution. This transformation + * is performed at ast level. This is useful for performance modeling + * purpose where we want to measure performance metrics using perfvisitor + * pass. + */ + +class CnexpSolveVisitor : public AstVisitor { + private: + /// method specified in solve block + std::string solve_method; + + /// true while visiting derivative block + bool derivative_block = false; + + /// name of the cnexp methoda in nmodl + const std::string cnexp_method = "cnexp"; + + public: + CnexpSolveVisitor() = default; + + void visit_solve_block(SolveBlock* node) override; + void visit_derivative_block(DerivativeBlock* node) override; + void visit_binary_expression(BinaryExpression* node) override; +}; + +#endif diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index b07f34268d..429f14a483 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -181,7 +181,7 @@ void DefUseAnalyzeVisitor::visit_function_call(FunctionCall* node) { void DefUseAnalyzeVisitor::visit_statement_block(StatementBlock* node) { auto symtab = node->get_symbol_table(); - if (symtab) { + if (symtab != nullptr) { current_symtab = symtab; } diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 11f35d23c2..08c2ae3b03 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -13,7 +13,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { } auto current_symtab = node->get_symbol_table(); - if (current_symtab) { + if (current_symtab != nullptr) { symtab = current_symtab; } @@ -30,7 +30,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { auto variables = get_local_variables(node); SymbolTable* parent_symtab = nullptr; - if (symtab) { + if (symtab != nullptr) { parent_symtab = symtab->get_parent_table(); } diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index b73b659012..b8e3490cdf 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -12,6 +12,7 @@ #include "visitors/verbatim_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/localize_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" #include "tclap/CmdLine.h" @@ -103,6 +104,12 @@ int main(int argc, const char* argv[]) { std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; } + { + CnexpSolveVisitor v; + v.visit_program(ast.get()); + std::cout << "----CNEXP SOLVE VISITOR FINISHED----" << std::endl; + } + { PerfVisitor v(channel_name + ".perf.json"); v.visit_program(ast.get()); @@ -118,6 +125,11 @@ int main(int argc, const char* argv[]) { std::cout << "----PERF VISITOR FINISHED----" << std::endl; } + { + NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.mod"); + v.visit_program(ast.get()); + } + { LocalVarRenameVisitor v; v.visit_program(ast.get()); @@ -152,7 +164,6 @@ int main(int argc, const char* argv[]) { NmodlPrintVisitor v(channel_name + ".nocmodl.loc.ren.mod"); v.visit_program(ast.get()); } - } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 46a011f0e3..1699cb7f97 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -287,7 +287,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbo * read/write count. Also update ops count in current block. */ void PerfVisitor::update_memory_ops(const std::string& name) { - if (start_measurement && current_symtab) { + if (start_measurement && (current_symtab != nullptr)) { auto symbol = current_symtab->lookup_in_scope(name); if (symbol == nullptr || symbol_to_skip(symbol)) { return; diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index a39afd317d..ce436bbdd2 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,3 +1,4 @@ +#include <iostream> #include "visitors/verbatim_visitor.hpp" void VerbatimVisitor::visit_verbatim(Verbatim* node) { diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index cc21331f90..8be5063a93 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -1,7 +1,6 @@ -#ifndef _VERBATIM_VISITOR_HPP_ -#define _VERBATIM_VISITOR_HPP_ +#ifndef VERBATIM_VISITOR_HPP +#define VERBATIM_VISITOR_HPP -#include <iostream> #include <vector> #include "ast/ast.hpp" diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 7f320d71bf..9f8699b74e 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -1,8 +1,9 @@ #include <string> #include <map> -#include <utility> +#include <memory> #include "ast/ast.hpp" +#include "parser/nmodl_driver.hpp" using namespace ast; @@ -45,3 +46,23 @@ void add_local_variable(ast::StatementBlock* node, const std::string& varname) { auto name = new ast::Name(new ast::String(varname)); add_local_variable(node, name); } + +/** + * Convert given code statement (in string format) to corresponding ast node + * + * We create dummy nmodl procedure containing given code statement and then + * parse it using NMODL parser. As there will be only one block with single + * statement, we return first statement. + * + * \todo : Need to revisit this during code generation passes to make sure + * if all statements can be part of procedure block. + */ +std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement) { + nmodl::Driver driver; + auto nmodl_text = "PROCEDURE dummy() { " + code_statement + " }"; + driver.parse_string(nmodl_text); + auto ast = driver.ast(); + auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); + auto statement = std::shared_ptr<Statement>(procedure->statementblock->statements[0]->clone()); + return statement; +} diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 554dd22506..ee592d94ed 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -12,9 +12,17 @@ std::string get_new_name(const std::string& name, const std::string& suffix, std::map<std::string, int>& variables); + +/** Return pointer to local statement in the given block, otherwise nullptr */ ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); + +/** Add empty local statement to given block if already doesn't exist */ void add_local_statement(ast::StatementBlock* node); + +/** Add new local variable to the block */ void add_local_variable(ast::StatementBlock* node, const std::string& varname); void add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); +/** Create ast statement node from given code in string format */ +std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); #endif \ No newline at end of file diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 64adfc1928..c0896d13cc 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -27,7 +27,7 @@ add_executable (testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) target_link_libraries(testparser lexer test_util) -target_link_libraries(testvisitor symtab lexer visitor util test_util printer) +target_link_libraries(testvisitor symtab visitor lexer util test_util printer) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 8c3067e283..f9980f9bae 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -1,6 +1,7 @@ #define CATCH_CONFIG_MAIN #include <string> +#include <utility> #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" @@ -16,6 +17,7 @@ bool is_valid_construct(const std::string& construct) { return driver.parse_string(construct); } + SCENARIO("NMODL can define macros using DEFINE keyword") { GIVEN("A valid macro definition") { WHEN("DEFINE NSTEP 6") { diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index a3b8e62ba1..e3a280afc8 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1452,6 +1452,8 @@ std::vector<DiffEqTestCase> diff_eq_constructs{ "DA_AMPA = DA_AMPA/(1.0-dt*(((1.0)*(A_AMPA)+(A_AMPA)*(1.0))))", "euler" }, + + /// using derivimplicit method /// note that the equation in state block gets changed by replacing state variable with Dstate. /// below expressions are from ode_matsol1 method diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 77fc5b1790..f3ac3cf63a 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -14,6 +14,7 @@ #include "visitors/verbatim_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/localize_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" #include "test/utils/nmodl_constructs.h" #include "test/utils/test_utils.hpp" @@ -325,7 +326,9 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { THEN("existing variables could be renamed") { std::vector<std::pair<std::string, std::string>> variables = { - {"m", "mm"}, {"gNaTs2_tbar", "new_gNaTs2_tbar"}, {"mAlpha", "mBeta"}, + {"m", "mm"}, + {"gNaTs2_tbar", "new_gNaTs2_tbar"}, + {"mAlpha", "mBeta"}, }; auto result = run_var_rename_visitor(input, variables); REQUIRE(result == expected_output); @@ -547,6 +550,7 @@ std::string run_inline_visitor(const std::string& text) { InlineVisitor v; v.visit_program(ast.get()); } + std::stringstream stream; { NmodlPrintVisitor v(stream); @@ -1129,7 +1133,7 @@ SCENARIO("Procedure inlining handles local-global name conflict") { // DefUseAnalyze visitor tests //============================================================================= -std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string variable) { +std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string& variable) { nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -1570,3 +1574,148 @@ SCENARIO("Localizer test with multiple global blocks") { } } } + + +//============================================================================= +// CnexpSolve visitor tests +//============================================================================= + +std::string run_cnexp_solve_visitor(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + { + SymtabVisitor v1; + v1.visit_program(ast.get()); + CnexpSolveVisitor v2; + v2.visit_program(ast.get()); + } + + std::stringstream stream; + { + NmodlPrintVisitor v(stream); + v.visit_program(ast.get()); + } + return stream.str(); +} + + +SCENARIO("CnexpSolver visitor solving ODEs") { + + GIVEN("Derivative block with cnexp method in breakpoint block") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + m = m + h + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + h = h+(1-exp(dt*((((-1)))/hTau)))*(-(((hInf))/hTau)/((((-1)))/hTau)-h) + m = m+h + } + )"; + + THEN("ODEs get replaced with solution") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("Derivative block without any solve method specification") { + std::string nmodl_text = R"( + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + std::string output_nmodl = R"( + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + THEN("ODEs don't get solved") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("Derivative block with non-cnexp method in breakpoint block") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + THEN("ODEs don't get solved / replaced ") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("Derivative block with ODEs that needs non-cnexp method to solve") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + A_AMPA' = tau_r_AMPA/A_AMPA + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + A_AMPA' = tau_r_AMPA/A_AMPA + } + )"; + + THEN("ODEs don't get replaced as cnexp is not possible") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } +} \ No newline at end of file From 4a27f60e2376edf9acd6945b60e602c87863d944 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 8 Feb 2018 11:47:09 +0100 Subject: [PATCH 054/871] Update perf visitor pass to: - add constant read memory/write count: total dram access = global memory read + constant memory - added separate counts for range, global and state variables - derivative block is renamed - tests updated Change-Id: Ieb698520c79ff06c14ae9f896b11b3026b99be9c NMODL Repo SHA: BlueBrain/nmodl@b10c5c6b6dc062a37c80f076e6230621150bcef7 --- src/nmodl/language/visitors_printer.py | 1 + src/nmodl/symtab/symbol_properties.cpp | 3 + src/nmodl/symtab/symbol_properties.hpp | 5 +- src/nmodl/symtab/symbol_table.cpp | 20 ++--- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/utils/perf_stat.cpp | 8 +- src/nmodl/utils/perf_stat.hpp | 11 ++- src/nmodl/visitors/localize_visitor.cpp | 14 +++- src/nmodl/visitors/main.cpp | 40 ++++++---- src/nmodl/visitors/perf_visitor.cpp | 79 +++++++++++++++++-- src/nmodl/visitors/perf_visitor.hpp | 29 ++++++- src/nmodl/visitors/symtab_visitor_helper.hpp | 13 +++ test/nmodl/transpiler/symtab/symbol_table.cpp | 4 +- test/nmodl/transpiler/visitor/visitor.cpp | 27 ++++++- 14 files changed, 205 insertions(+), 51 deletions(-) diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 566dda0162..6a1d5ef51b 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -170,6 +170,7 @@ def private_declaration(self): self.write_line("private:", post_gutter=1) self.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) self.write_line("std::unique_ptr<JSONPrinter> printer;") + self.write_line("bool state_block = false;", newline=2) def public_declaration(self): self.write_line("public:", post_gutter=1) diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 039ff80d73..9f5c0a43d5 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -127,6 +127,9 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { properties.emplace_back("extern_method"); } + if (has_property(obj, NmodlInfo::state_var)) { + properties.emplace_back("state_var"); + } return properties; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index eec6f6cd17..4854363f17 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -169,7 +169,10 @@ namespace symtab { extern_neuron_variable = 1 << 26, /** neuron solver methods and math functions */ - extern_method = 1 << 27 + extern_method = 1 << 27, + + /** state variable */ + state_var = 1 << 28 }; } // namespace details diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 8d634da7af..2f28f1d671 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -57,23 +57,13 @@ namespace symtab { children[name] = std::move(table); } - /** Get all symbols which can be used in global scope. Note that - * this is different from GLOBAL variable type in the mod file. Here - * global meaning all variables that can be used in entire mod file. - * This is used from optimization passes. - * - * \todo Voltage v can be global variable as well as external. In order - * to avoid optimizations, we need to handle this case properly - * - * \todo Instead of ast node, use symbol properties to check variable type - */ - std::vector<std::shared_ptr<Symbol>> SymbolTable::get_global_variables() { + /// return all symbol having any of the provided properties + std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( + SymbolInfo properties) { std::vector<std::shared_ptr<Symbol>> variables; for (auto& syminfo : table.symbols) { auto symbol = syminfo.second; - SymbolInfo property = NmodlInfo::range_var; - property |= NmodlInfo::dependent_def | NmodlInfo::param_assign; - if (symbol->has_properties(property)) { + if (symbol->has_properties(properties)) { variables.push_back(symbol); } } @@ -124,7 +114,7 @@ namespace symtab { /// is not used with visitor at all. it would be ok // to just return nullptr? if (parent_symtab == nullptr) { - throw std::logic_error("Lookup wit previous symtab = nullptr "); + throw std::logic_error("Lookup with previous symtab = nullptr "); } auto symbol = parent_symtab->lookup(name); diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index f6b6196a95..36bd8235e9 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -136,7 +136,7 @@ namespace symtab { std::shared_ptr<Symbol> lookup_in_scope(const std::string& name); - std::vector<std::shared_ptr<Symbol>> get_global_variables(); + std::vector<std::shared_ptr<Symbol>> get_variables_with_properties(SymbolInfo properties); bool under_global_scope(); diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index ca5b9e083c..4977492334 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -41,6 +41,8 @@ PerfStat operator+(const PerfStat& first, const PerfStat& second) { result.local_read_count = first.local_read_count + second.local_read_count; result.local_write_count = first.local_write_count + second.local_write_count; + result.constant_read_count = first.constant_read_count + second.constant_read_count; + result.constant_write_count = first.constant_write_count + second.constant_write_count; return result; } @@ -55,8 +57,8 @@ void PerfStat::print(std::stringstream& stream) { } std::vector<std::string> PerfStat::keys() { - return {"+", "-", "x", "/", "exp", "GM(R)", "GM(W)", - "LM(R)", "LM(W)", "call", "compare", "unary", "conditional"}; + return {"+", "-", "x", "/", "exp", "GM(R)", "GM(W)", "CM(R)", + "CM(W)", "LM(R)", "LM(W)", "call", "compare", "unary", "conditional"}; } std::vector<std::string> PerfStat::values() { @@ -73,6 +75,8 @@ std::vector<std::string> PerfStat::values() { row.push_back(std::to_string(global_read_count)); row.push_back(std::to_string(global_write_count)); + row.push_back(std::to_string(constant_read_count)); + row.push_back(std::to_string(constant_write_count)); row.push_back(std::to_string(local_read_count)); row.push_back(std::to_string(local_write_count)); row.push_back(std::to_string(func_call_count)); diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index eb789db986..69169aab5a 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -58,16 +58,19 @@ class PerfStat { int if_count = 0; int elif_count = 0; - /// expensive : typically access to - /// dynamically allocated memory + /// expensive : typically access to dynamically allocated memory int global_read_count = 0; int global_write_count = 0; - /// cheap : typically local variables - // in mod file means registers + /// cheap : typically local variables in mod file means registers int local_read_count = 0; int local_write_count = 0; + /// could be optimized : access to variables that could be read-only + /// in this case write counts are typically from initialization + int constant_read_count = 0; + int constant_write_count = 0; + friend PerfStat operator+(const PerfStat& first, const PerfStat& second); void print(std::stringstream& stream); diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 6a80f6c85c..0b470d2ba0 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -43,8 +43,20 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { | NmodlInfo::bbcore_pointer_var | NmodlInfo::electrode_cur_var | NmodlInfo::section_var; + + SymbolInfo global_var_properties = NmodlInfo::range_var + | NmodlInfo::dependent_def + | NmodlInfo::param_assign; // clang-format on - auto variables = program_symtab->get_global_variables(); + + + /** + * \todo Voltage v can be global variable as well as external. In order + * to avoid optimizations, we need to handle this case properly + * \todo Instead of ast node, use symbol properties to check variable type + */ + auto variables = program_symtab->get_variables_with_properties(global_var_properties); + std::vector<std::string> result; for (auto& variable : variables) { if (!variable->has_properties(excluded_var_properties)) { diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index b8e3490cdf..3982b158bb 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -110,20 +110,6 @@ int main(int argc, const char* argv[]) { std::cout << "----CNEXP SOLVE VISITOR FINISHED----" << std::endl; } - { - PerfVisitor v(channel_name + ".perf.json"); - v.visit_program(ast.get()); - - auto symtab = ast->get_symbol_table(); - std::stringstream ss; - symtab->print(ss, 0); - std::cout << ss.str(); - - ss.str(""); - v.print(ss); - std::cout << ss.str() << std::endl; - std::cout << "----PERF VISITOR FINISHED----" << std::endl; - } { NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.mod"); @@ -137,7 +123,13 @@ int main(int argc, const char* argv[]) { { InlineVisitor v; - v.visit_program(ast.get()); + //v.visit_program(ast.get()); + } + + { + // BUG: WE SHOULD BE ABLE TO RE-RUN SYMTAB AGAIN HERE!! + SymtabVisitor v; + //v.visit_program(ast.get()); } { @@ -164,6 +156,24 @@ int main(int argc, const char* argv[]) { NmodlPrintVisitor v(channel_name + ".nocmodl.loc.ren.mod"); v.visit_program(ast.get()); } + + { + /// NEED TO RUN SYMTAB VISITOR AFTER INLINING WHICH IS NOT RUNNING + PerfVisitor v(channel_name + ".perf.json"); + v.visit_program(ast.get()); + + auto symtab = ast->get_symbol_table(); + std::stringstream ss; + symtab->print(ss, 0); + std::cout << ss.str(); + + ss.str(""); + v.print(ss); + std::cout << ss.str() << std::endl; + std::cout << "----PERF VISITOR FINISHED----" << std::endl; + } + + } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 1699cb7f97..c6ee7a8f43 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -122,7 +122,11 @@ void PerfVisitor::measure_performance(AST* node) { } if (printer) { - printer->push_block(symtab->name()); + auto name = symtab->name(); + if(node->is_derivative_block()) { + name = node->get_type_name(); + } + printer->push_block(name); } perf.title = "Performance Statistics of " + symtab->name(); @@ -180,9 +184,50 @@ void PerfVisitor::visit_else_if_statement(ElseIfStatement* /*node*/) { } } +void PerfVisitor::count_variables() { + /// number of instance variables: range or assigned variables + /// \todo: one caveat is that the global variables appearing in + /// assigned block variable are not treated as range! + SymbolInfo property = NmodlInfo::range_var | NmodlInfo::dependent_def; + auto variables = current_symtab->get_variables_with_properties(property); + num_instance_variables = variables.size(); + + /// state variables + property = NmodlInfo::state_var; + variables = current_symtab->get_variables_with_properties(property); + num_state_variables = variables.size(); + + /// number of global variables : parameters could appear also as range + /// variables and hence need to filter out + property = NmodlInfo::global_var | NmodlInfo::param_assign; + variables = current_symtab->get_variables_with_properties(property); + num_global_variables = 0; + for (auto& variable : variables) { + property = NmodlInfo::range_var | NmodlInfo::dependent_def; + if (!variable->has_properties(property)) { + num_global_variables++; + } + } +} + +void PerfVisitor::print_memory_usage() { + stream << std::endl; + stream << "#INSTANCE VARIABLES : " << num_instance_variables << " "; + stream << "#GLOBAL VARIABLES : " << num_global_variables << " "; + stream << "#STATE VARIABLES : " << num_state_variables << std::endl; + + if(printer) { + printer->push_block("MemroyInfo"); + printer->add_node(std::to_string(num_instance_variables), "InstanceVariables"); + printer->add_node(std::to_string(num_global_variables), "GlobalVariables") ; + printer->add_node(std::to_string(num_state_variables), "StateVariables") ; + printer->pop_block(); + } +} + void PerfVisitor::visit_program(Program* node) { if (printer) { - printer->push_block("NMODL"); + printer->push_block("BlockPerf"); } node->visit_children(this); @@ -194,8 +239,13 @@ void PerfVisitor::visit_program(Program* node) { printer->push_block("total"); add_perf_to_printer(total_perf); printer->pop_block(); + /// BlockPerf printer->pop_block(); } + + current_symtab = node->get_symbol_table(); + count_variables(); + print_memory_usage(); } /** Blocks like function can have multiple statement blocks and @@ -262,7 +312,7 @@ void PerfVisitor::visit_unary_expression(UnaryExpression* node) { bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { bool skip = false; - auto is_method = symbol->has_properties(NmodlInfo::extern_method); + auto is_method = symbol->has_properties(NmodlInfo::extern_method | NmodlInfo::function_block); if (is_method && under_function_call) { skip = true; } @@ -276,6 +326,7 @@ bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbol) { bool is_local = false; + /// in the function when we write to function variable then consider it as local variable auto properties = NmodlInfo::local_var | NmodlInfo::argument | NmodlInfo::function_block; if (symbol->has_properties(properties)) { is_local = true; @@ -283,6 +334,16 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbo return is_local; } +bool PerfVisitor::is_constant_variable(const std::shared_ptr<symtab::Symbol>& symbol) { + bool is_constant = false; + auto properties = NmodlInfo::param_assign; + if (symbol->has_properties(properties)) { + is_constant = true; + } + return is_constant; +} + + /** Find symbol in closest scope (up to parent) and update * read/write count. Also update ops count in current block. */ @@ -304,10 +365,18 @@ void PerfVisitor::update_memory_ops(const std::string& name) { } else { if (visiting_lhs_expression) { symbol->write(); - current_block_perf.global_write_count++; + if (is_constant_variable(symbol)) { + current_block_perf.constant_write_count++; + } else { + current_block_perf.global_write_count++; + } } else { symbol->read(); - current_block_perf.global_read_count++; + if (is_constant_variable(symbol)) { + current_block_perf.constant_read_count++; + } else { + current_block_perf.global_read_count++; + } } } } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 2a46702282..e89caf9ecf 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -70,14 +70,29 @@ class PerfVisitor : public AstVisitor { /// if not json, all goes to string std::stringstream stream; + /// count of per channel instance variables + int num_instance_variables = 0; + + /// count of global variables + int num_global_variables = 0; + + /// count of state variables + int num_state_variables = 0; + void update_memory_ops(const std::string& name); bool symbol_to_skip(const std::shared_ptr<symtab::Symbol>& symbol); bool is_local_variable(const std::shared_ptr<symtab::Symbol>& symbol); + bool is_constant_variable(const std::shared_ptr<symtab::Symbol>& symbol); + + void count_variables(); + void measure_performance(AST* node); + void print_memory_usage(); + void add_perf_to_printer(PerfStat& perf); public: @@ -89,10 +104,22 @@ class PerfVisitor : public AstVisitor { printer->compact_json(flag); } - PerfStat& get_total_perfstat() { + PerfStat get_total_perfstat() { return total_perf; } + int get_instance_variable_count() { + return num_instance_variables; + } + + int get_global_variable_count() { + return num_global_variables; + } + + int get_state_variable_count() { + return num_state_variables; + } + void visit_binary_expression(BinaryExpression* node) override; void visit_function_call(FunctionCall* node) override; diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index d0c98ac4f6..074adbfc34 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -27,6 +27,11 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { symbol->add_property(property); modsymtab->insert(symbol); + /// extra property for state variables + if (state_block) { + symbol->add_property(NmodlInfo::state_var); + } + /// visit childrens, most likely variables are already /// leaf nodes, not necessary to visit node->visit_children(this); @@ -55,6 +60,10 @@ void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global /// entering into new nmodl block auto symtab = modsymtab->enter_scope(name, node, is_global); + if (node->is_state_block()) { + state_block = true; + } + /// not required at the moment but every node /// has pointer to associated symbol table node->set_symbol_table(symtab); @@ -82,6 +91,10 @@ void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global /// exisiting nmodl block modsymtab->leave_scope(); + + if (node->is_state_block()) { + state_block = false; + } } #endif \ No newline at end of file diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index b6bb0c57b5..7733086a00 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -155,14 +155,14 @@ SCENARIO("Symbol table operations") { } WHEN("checked for global variables") { table->insert(symbol); - auto variables = table->get_global_variables(); + auto variables = table->get_variables_with_properties(NmodlInfo::range_var); THEN("table doesn't have any global variables") { REQUIRE(variables.empty()); WHEN("added global symbol") { auto next_symbol = std::make_shared<Symbol>("gamma", ModToken()); next_symbol->add_property(NmodlInfo::dependent_def); table->insert(next_symbol); - auto variables = table->get_global_variables(); + auto variables = table->get_variables_with_properties(NmodlInfo::dependent_def); THEN("table has global variable") { REQUIRE(variables.size() == 1); } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index f3ac3cf63a..9205deeb6e 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -114,16 +114,22 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { NEURON { SUFFIX NaTs2_t USEION na READ ena WRITE ina - RANGE gNaTs2_tbar + RANGE gNaTs2_tbar, A_AMPA_step + GLOBAL Rstate } PARAMETER { gNaTs2_tbar = 0.00001 (S/cm2) + tau_r = 0.2 (ms) + tau_d_AMPA = 1.0 } ASSIGNED { v (mV) ena (mV) + tau_r + tsyn_fac + A_AMPA_step } STATE { @@ -176,9 +182,18 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { } WHEN("Perf visitor pass runs after symtab visitor") { - PerfVisitor v; + PerfVisitor v, v2("a.a"); v.visit_program(ast.get()); + auto result = v.get_total_perfstat(); + auto num_instance_var = v.get_instance_variable_count(); + auto num_global_var = v.get_global_variable_count(); + auto num_state_var = v.get_state_variable_count(); + + v2.visit_program(ast.get()); + std::stringstream s; + result.print(s); + std::cout << s.str(); THEN("Performance counters are updated") { REQUIRE(result.add_count == 0); @@ -186,12 +201,17 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { REQUIRE(result.mul_count == 6); REQUIRE(result.div_count == 2); REQUIRE(result.exp_count == 1); - REQUIRE(result.global_read_count == 7); + REQUIRE(result.global_read_count == 6); REQUIRE(result.global_write_count == 1); + REQUIRE(result.constant_read_count == 1); + REQUIRE(result.constant_write_count == 0); REQUIRE(result.local_read_count == 3); REQUIRE(result.local_write_count == 2); REQUIRE(result.func_call_count == 1); REQUIRE(result.neg_count == 3); + REQUIRE(num_instance_var == 8); + REQUIRE(num_global_var == 2); + REQUIRE(num_state_var == 2); } } } @@ -1602,7 +1622,6 @@ std::string run_cnexp_solve_visitor(const std::string& text) { SCENARIO("CnexpSolver visitor solving ODEs") { - GIVEN("Derivative block with cnexp method in breakpoint block") { std::string nmodl_text = R"( BREAKPOINT { From 7a4bd208d5754946114effeb4ff04aabd35acd97 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 8 Feb 2018 15:20:21 +0100 Subject: [PATCH 055/871] Disable support for inline comment tokens in nmodl lexer: - support added for inline comment in nmodl lexer and parser is in-adequate for comments appearing on { or } of a block: DERIVATIVE states { :comment } - this requires larger/substantive changes for lexer/parser. - for now disable support for commnets as it's not important for code generation Change-Id: I724e09b3e919db7af31601e95e5b42b7a0652eee NMODL Repo SHA: BlueBrain/nmodl@b1a6e63e83727bf5e98bef6de619afd608ffd367 --- src/nmodl/lexer/nmodl.ll | 3 ++- test/nmodl/transpiler/utils/nmodl_constructs.cpp | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index ae70d56945..a7d6e0f0a6 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -383,9 +383,10 @@ ELSE { :.* | \?.* { - /** Todo : add grammar support for inline vs single-line comments */ + /** Todo : add grammar support for inline vs single-line comments auto str = std::string(yytext); return nmodl::Parser::make_INLINE_COMMENT(str, loc); + */ } . { diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index e3a280afc8..ad71996e1b 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1251,8 +1251,6 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } )", R"( - ? comment here - FUNCTION urand() { VERBATIM printf("Hello World!\n"); @@ -1275,9 +1273,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ R"( FUNCTION urand() { a = b+c - : some comment here c = d*e - ? another comment here } )" } From 8bbf641ff67ec140bba06a59462d7f7e26c7c2dc Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 8 Feb 2018 17:01:38 +0100 Subject: [PATCH 056/871] Symbol table visitor bug fixes: - symtab visitor can now be run multiple times, clear previous symbol tables at the begining - symtab visitor was deferencing nullptr while inserting symbol created from localzier pass - added test for both issues Change-Id: Ide4713c644c06dff229317446540f80d57e4922b NMODL Repo SHA: BlueBrain/nmodl@b9f9fe40f91edec565279fa2663042f4f5414e75 --- src/nmodl/symtab/symbol_table.hpp | 7 +++ src/nmodl/visitors/main.cpp | 6 +-- src/nmodl/visitors/perf_visitor.cpp | 23 +++++----- src/nmodl/visitors/symtab_visitor_helper.hpp | 16 ++++++- test/nmodl/transpiler/visitor/visitor.cpp | 47 +++++++++++++++++++- 5 files changed, 81 insertions(+), 18 deletions(-) diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 36bd8235e9..64a273007b 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -189,6 +189,13 @@ namespace symtab { /// pretty print void print(std::stringstream& ss); + + /// re-initialize members to throw away old symbol tables + /// this is required as symtab visitor pass runs multiple time + void initialize() { + symtab = nullptr; + parent_symtab = nullptr; + } }; } // namespace symtab diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 3982b158bb..47564ba06e 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -123,13 +123,12 @@ int main(int argc, const char* argv[]) { { InlineVisitor v; - //v.visit_program(ast.get()); + v.visit_program(ast.get()); } { - // BUG: WE SHOULD BE ABLE TO RE-RUN SYMTAB AGAIN HERE!! SymtabVisitor v; - //v.visit_program(ast.get()); + v.visit_program(ast.get()); } { @@ -158,7 +157,6 @@ int main(int argc, const char* argv[]) { } { - /// NEED TO RUN SYMTAB VISITOR AFTER INLINING WHICH IS NOT RUNNING PerfVisitor v(channel_name + ".perf.json"); v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index c6ee7a8f43..0a2d71c763 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -121,15 +121,16 @@ void PerfVisitor::measure_performance(AST* node) { throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); } + auto name = symtab->name(); + if (node->is_derivative_block()) { + name = node->get_type_name(); + } + if (printer) { - auto name = symtab->name(); - if(node->is_derivative_block()) { - name = node->get_type_name(); - } printer->push_block(name); } - perf.title = "Performance Statistics of " + symtab->name(); + perf.title = "Performance Statistics of " + name; perf.print(stream); if (printer) { @@ -212,15 +213,15 @@ void PerfVisitor::count_variables() { void PerfVisitor::print_memory_usage() { stream << std::endl; - stream << "#INSTANCE VARIABLES : " << num_instance_variables << " "; - stream << "#GLOBAL VARIABLES : " << num_global_variables << " "; - stream << "#STATE VARIABLES : " << num_state_variables << std::endl; + stream << "#INSTANCE VARIABLES : " << num_instance_variables << " "; + stream << "#GLOBAL VARIABLES : " << num_global_variables << " "; + stream << "#STATE VARIABLES : " << num_state_variables << std::endl; - if(printer) { + if (printer) { printer->push_block("MemroyInfo"); printer->add_node(std::to_string(num_instance_variables), "InstanceVariables"); - printer->add_node(std::to_string(num_global_variables), "GlobalVariables") ; - printer->add_node(std::to_string(num_state_variables), "StateVariables") ; + printer->add_node(std::to_string(num_global_variables), "GlobalVariables"); + printer->add_node(std::to_string(num_state_variables), "StateVariables"); printer->pop_block(); } } diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 074adbfc34..3ddaa626c9 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -9,7 +9,6 @@ template <typename T> void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { std::shared_ptr<Symbol> symbol; - auto token = node->get_token(); /// if prime variable is already exist in symbol table /// then just update the order @@ -22,8 +21,15 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { } } + ModToken token; + auto token_ptr = node->get_token(); + if (token_ptr != nullptr) { + token = *token_ptr; + } + /// add new symbol - symbol = std::make_shared<Symbol>(node->get_name(), node, *token); + symbol = std::make_shared<Symbol>(node->get_name(), node, token); + symbol->add_property(property); modsymtab->insert(symbol); @@ -49,9 +55,15 @@ void SymtabVisitor::setup_symbol_table(T* node, setup_symbol_table(node, name, is_global); } + +/** + * Symtab visitor could be called multiple times, after optimization passes, + * in which case we have to throw awayold symbol tables and setup new ones. + */ template <typename T> void SymtabVisitor::setup_program_symbol_table(T* node, std::string name, bool is_global) { modsymtab = node->get_model_symbol_table(); + modsymtab->initialize(); setup_symbol_table(node, name, is_global); } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 9205deeb6e..642c09a9c4 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1737,4 +1737,49 @@ SCENARIO("CnexpSolver visitor solving ODEs") { REQUIRE(result == expected_result); } } -} \ No newline at end of file +} + + +//============================================================================= +// Passes can run multiple times +//============================================================================= + +void run_visitor_passes(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + { + SymtabVisitor v1; + InlineVisitor v2; + LocalizeVisitor v3; + v1.visit_program(ast.get()); + v2.visit_program(ast.get()); + v3.visit_program(ast.get()); + v1.visit_program(ast.get()); + v1.visit_program(ast.get()); + v2.visit_program(ast.get()); + v3.visit_program(ast.get()); + v2.visit_program(ast.get()); + } +} + + +SCENARIO("Running visitor passes multiple time") { + GIVEN("A mod file") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + )"; + + THEN("Passes can run multiple times") { + std::string input = reindent_text(nmodl_text); + REQUIRE_NOTHROW(run_visitor_passes(input)); + } + } +} From 81b3b2aefa0c6bd7abb15e371b99aa00915b86f6 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 8 Feb 2018 23:32:59 +0100 Subject: [PATCH 057/871] Update to symbol table and perf visitor pass: - Added SymbolStatus as property to Symbol class This will be used to show updates like inlined, created, localized etc - Perf visitor prints information about memory usage of instance, global and state variables - Symbol table has new column SymbolStatus - Empty breakpoint block with solve statement was showing GM(1) SOLVE states USING METHOD cnexp Change-Id: Ie1937c4b89b6effbf43c76da26b7783b1d40b561 NMODL Repo SHA: BlueBrain/nmodl@547e5867fd9f88d3235f9cbd6c405058deec1704 --- src/nmodl/symtab/symbol.cpp | 5 +++ src/nmodl/symtab/symbol.hpp | 21 ++++++++++ src/nmodl/symtab/symbol_properties.cpp | 41 ++++++++++++++----- src/nmodl/symtab/symbol_properties.hpp | 39 +++++++++++++------ src/nmodl/symtab/symbol_table.cpp | 5 ++- src/nmodl/visitors/inline_visitor.cpp | 10 ++++- src/nmodl/visitors/inline_visitor.hpp | 7 ++-- src/nmodl/visitors/localize_visitor.cpp | 2 + src/nmodl/visitors/main.cpp | 7 ++++ src/nmodl/visitors/perf_visitor.cpp | 52 +++++++++++++++++++++---- src/nmodl/visitors/perf_visitor.hpp | 12 ++++++ 11 files changed, 166 insertions(+), 35 deletions(-) diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 2e1d622121..abb51a9151 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -19,6 +19,11 @@ namespace symtab { return static_cast<bool>(properties & new_properties); } + /// check if symbol has any of the status + bool Symbol::has_any_status(SymbolStatus new_status) { + return static_cast<bool>(status & new_status); + } + /// add new properties to symbol with bitwise or void Symbol::combine_properties(SymbolInfo new_properties) { properties |= new_properties; diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 198f01adaf..fb3633c0fc 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -47,6 +47,9 @@ namespace symtab { /// properties of symbol from whole mod file SymbolInfo properties{flags::empty}; + /// status of symbol during various passes + SymbolStatus status{flags::empty}; + /// scope of the symbol (block name) std::string scope; @@ -84,6 +87,10 @@ namespace symtab { return properties; } + SymbolStatus get_status() { + return status; + } + void read() { read_count++; } @@ -112,12 +119,26 @@ namespace symtab { bool has_properties(SymbolInfo new_properties); + bool has_any_status(SymbolStatus new_status); + void combine_properties(SymbolInfo new_properties); void add_property(NmodlInfo property); void add_property(SymbolInfo property); + void inlined() { + status |= Status::inlined; + } + + void renamed() { + status |= Status::renamed; + } + + void localized() { + status |= Status::localized; + } + void set_order(int new_order); bool is_variable(); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 9f5c0a43d5..c5d2998426 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -11,6 +11,10 @@ bool has_property(const SymbolInfo& obj, NmodlInfo property) { return static_cast<bool>(obj & property); } +bool has_status(const SymbolStatus &obj, Status state) { + return static_cast<bool>(obj & state); +} + /// helper function to convert properties to string std::vector<std::string> to_string_vector(const SymbolInfo& obj) { std::vector<std::string> properties; @@ -133,19 +137,38 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { return properties; } +std::vector<std::string> to_string_vector(const SymbolStatus& obj) { + std::vector<std::string> status; + + if (has_status(obj, Status::localized)) { + status.emplace_back("localized"); + } + + if (has_status(obj, Status::globalized)) { + status.emplace_back("globalized"); + } + + if (has_status(obj, Status::inlined)) { + status.emplace_back("inlined"); + } + + if (has_status(obj, Status::renamed)) { + status.emplace_back("renamed"); + } + + if (has_status(obj, Status::created)) { + status.emplace_back("created"); + } + return status; +} + std::ostream& operator<<(std::ostream& os, const SymbolInfo& obj) { os << to_string(obj); return os; } -std::string to_string(const SymbolInfo& obj) { - auto properties = to_string_vector(obj); - std::string text; - for (const auto& property : properties) { - text += property + " "; - } - // remove extra whitespace at the end - stringutils::trim(text); - return text; +std::ostream& operator<<(std::ostream& os, const SymbolStatus& obj) { + os << to_string(obj); + return os; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 4854363f17..f00da5ae65 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -1,9 +1,10 @@ -#ifndef _SYMBOL_PROPERTIES_HPP_ -#define _SYMBOL_PROPERTIES_HPP_ +#ifndef SYMBOL_PROPERTIES_HPP +#define SYMBOL_PROPERTIES_HPP #include <sstream> #include "flags/flags.hpp" +#include "utils/string_utils.hpp" namespace symtab { @@ -35,20 +36,20 @@ namespace symtab { /** state during various compiler passes */ enum class Status { - /** unmodified */ - unmodified, - /** converted to local */ - to_local, + localized = 1 << 0, /** converted to global */ - to_global, + globalized = 1 << 1, /** inlined */ - inlined, + inlined = 1 << 2, /** renamed */ - renamed + renamed = 1 << 3, + + /** created */ + created = 1 << 4 }; /** usage of mod file as array or scalar */ @@ -181,11 +182,27 @@ namespace symtab { ALLOW_FLAGS_FOR_ENUM(symtab::details::NmodlInfo) ALLOW_FLAGS_FOR_ENUM(symtab::details::Access) +ALLOW_FLAGS_FOR_ENUM(symtab::details::Status) using SymbolInfo = flags::flags<symtab::details::NmodlInfo>; +using SymbolStatus = flags::flags<symtab::details::Status>; std::ostream& operator<<(std::ostream& os, const SymbolInfo& obj); - -std::string to_string(const SymbolInfo& obj); +std::ostream& operator<<(std::ostream& os, const SymbolStatus& obj); + +std::vector<std::string> to_string_vector(const SymbolInfo& obj); +std::vector<std::string> to_string_vector(const SymbolStatus& obj); + +template <typename T> +std::string to_string(const T& obj) { + auto elements = to_string_vector(obj); + std::string text; + for (const auto& element : elements) { + text += element + " "; + } + // remove extra whitespace at the end + stringutils::trim(text); + return text; +} #endif diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 2f28f1d671..3ee6726b50 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -285,7 +285,7 @@ namespace symtab { if (!symbols.empty()) { TableData table; table.title = std::move(title); - table.headers = {"NAME", "PROPERTIES", "LOCATION", "# READS", "# WRITES"}; + table.headers = {"NAME", "PROPERTIES", "STATUS", "LOCATION", "# READS", "# WRITES"}; table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, text_alignment::right, text_alignment::right}; @@ -303,9 +303,10 @@ namespace symtab { auto name = syminfo.first; auto position = symbol->get_token().position(); auto properties = to_string(symbol->get_properties()); + auto status = to_string(symbol->get_status()); auto reads = std::to_string(symbol->get_read_count()); auto writes = std::to_string(symbol->get_write_count()); - table.rows.push_back({name, properties, position, reads, writes}); + table.rows.push_back({name, properties, status, position, reads, writes}); } table.print(stream, indent); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index f4497a01f4..57824cc9c6 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -103,12 +103,18 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { /// first inline called function function_definition->visit_children(this); + bool inlined = false; + if (function_definition->is_procedure_block()) { auto proc = (ProcedureBlock*)function_definition; - inline_function_call(proc, node, caller_block); + inlined = inline_function_call(proc, node, caller_block); } else if (function_definition->is_function_block()) { auto func = (FunctionBlock*)function_definition; - inline_function_call(func, node, caller_block); + inlined = inline_function_call(func, node, caller_block); + } + + if (inlined) { + symbol->inlined(); } } diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 6f6980202a..1d67e2c977 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -147,7 +147,7 @@ class InlineVisitor : public AstVisitor { /// inline function/procedure into caller block template <typename T> - void inline_function_call(T* callee, ast::FunctionCall* node, ast::StatementBlock* caller); + bool inline_function_call(T* callee, ast::FunctionCall* node, ast::StatementBlock* caller); /// add assignement statements into given statement block to inline arguments void inline_arguments(ast::StatementBlock* inlined_block, @@ -178,7 +178,7 @@ class InlineVisitor : public AstVisitor { * @param caller : statement block containing function call */ template <typename T> -void InlineVisitor::inline_function_call(T* callee, +bool InlineVisitor::inline_function_call(T* callee, ast::FunctionCall* node, ast::StatementBlock* caller) { std::string function_name = callee->name->get_name(); @@ -186,7 +186,7 @@ void InlineVisitor::inline_function_call(T* callee, /// do nothing if we can't inline given procedure/function if (!can_inline_block(callee->statementblock.get())) { std::cerr << "Can not inline function call to " + function_name; - return; + return false; } /// make sure to rename conflicting local variable in caller block @@ -247,6 +247,7 @@ void InlineVisitor::inline_function_call(T* callee, /// variable name which will replace the function call that we just inlined replaced_fun_calls[node] = new_varname; + return true; } #endif // diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 0b470d2ba0..8223125221 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -97,6 +97,8 @@ void LocalizeVisitor::visit_program(Program* node) { auto block_ptr = dynamic_cast<Block*>(block.get()); auto statement_block = block_ptr->get_statement_block(); add_local_variable(statement_block.get(), variable); + auto symbol = program_symtab->lookup(variable); + symbol->localized(); } } } diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 47564ba06e..7212900298 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -156,6 +156,13 @@ int main(int argc, const char* argv[]) { v.visit_program(ast.get()); } + { + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); + } + { PerfVisitor v(channel_name + ".perf.json"); v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 0a2d71c763..d8434c55be 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -189,10 +189,19 @@ void PerfVisitor::count_variables() { /// number of instance variables: range or assigned variables /// \todo: one caveat is that the global variables appearing in /// assigned block variable are not treated as range! - SymbolInfo property = NmodlInfo::range_var | NmodlInfo::dependent_def; + SymbolInfo property = NmodlInfo::range_var | NmodlInfo::dependent_def | NmodlInfo::state_var; auto variables = current_symtab->get_variables_with_properties(property); num_instance_variables = variables.size(); + for (auto& variable : variables) { + if (variable->has_properties(NmodlInfo::param_assign)) { + num_constant_instance_variables++; + } + if (variable->has_any_status(Status::localized)) { + num_localized_instance_variables++; + } + } + /// state variables property = NmodlInfo::state_var; variables = current_symtab->get_variables_with_properties(property); @@ -207,21 +216,48 @@ void PerfVisitor::count_variables() { property = NmodlInfo::range_var | NmodlInfo::dependent_def; if (!variable->has_properties(property)) { num_global_variables++; + if (variable->has_properties(NmodlInfo::param_assign)) { + num_constant_global_variables++; + } + if (variable->has_any_status(Status::localized)) { + num_localized_global_variables++; + } } } } void PerfVisitor::print_memory_usage() { stream << std::endl; + stream << "#INSTANCE VARIABLES : " << num_instance_variables << " "; - stream << "#GLOBAL VARIABLES : " << num_global_variables << " "; - stream << "#STATE VARIABLES : " << num_state_variables << std::endl; + stream << "[ CONSTANT " << num_constant_instance_variables << ", "; + stream << "LOCALIZED " << num_localized_instance_variables << " ]"; + + stream << " #GLOBAL VARIABLES : " << num_global_variables << " "; + stream << "[ CONSTANT " << num_constant_global_variables << ", "; + stream << "LOCALIZED " << num_localized_global_variables << " ]"; + + stream << " #STATE VARIABLES : " << num_state_variables << std::endl; if (printer) { - printer->push_block("MemroyInfo"); - printer->add_node(std::to_string(num_instance_variables), "InstanceVariables"); - printer->add_node(std::to_string(num_global_variables), "GlobalVariables"); - printer->add_node(std::to_string(num_state_variables), "StateVariables"); + printer->push_block("MemoryInfo"); + + printer->push_block("Instance"); + printer->add_node(std::to_string(num_instance_variables), "total"); + printer->add_node(std::to_string(num_constant_instance_variables), "const"); + printer->add_node(std::to_string(num_localized_instance_variables), "localized"); + printer->pop_block(); + + printer->push_block("Global"); + printer->add_node(std::to_string(num_global_variables), "total"); + printer->add_node(std::to_string(num_global_variables), "const"); + printer->add_node(std::to_string(num_localized_global_variables), "localized"); + printer->pop_block(); + + printer->push_block("State"); + printer->add_node(std::to_string(num_state_variables), "total"); + printer->pop_block(); + printer->pop_block(); } } @@ -240,7 +276,6 @@ void PerfVisitor::visit_program(Program* node) { printer->push_block("total"); add_perf_to_printer(total_perf); printer->pop_block(); - /// BlockPerf printer->pop_block(); } @@ -318,6 +353,7 @@ bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { skip = true; } + is_method = symbol->has_properties(NmodlInfo::derivative_block | NmodlInfo::extern_method); if (is_method && under_solve_block) { skip = true; } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index e89caf9ecf..6625af484c 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -73,9 +73,21 @@ class PerfVisitor : public AstVisitor { /// count of per channel instance variables int num_instance_variables = 0; + /// subset of instance variables which are constant + int num_constant_instance_variables = 0; + + /// subset of instance variables which are localized + int num_localized_instance_variables = 0; + /// count of global variables int num_global_variables = 0; + /// subset of global variables which are constant + int num_constant_global_variables = 0; + + /// subset of global variables which are localized + int num_localized_global_variables = 0; + /// count of state variables int num_state_variables = 0; From cb3a8c22dc29c94ba59c0b779668083597d306fe Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Fri, 9 Feb 2018 14:48:18 +0100 Subject: [PATCH 058/871] Perf visitor updates: - added separate external and internal function calls counter - defuse analyzer and loalizer pass has extra argument to ignore verbatim blocks currently we assume evry verbatim block "Use" given variable - if and elseif childrens were not analyzed (bug fix) - global variables which are assigned are not considered instance variables anymore - added field for pointer counting - initial block in net receive block is ignored now (because its called only once) - measuring pointer variables (global or assigned) - test update Change-Id: Ie04ac9713d364b545f5bc3388d4867b5e4fea10a NMODL Repo SHA: BlueBrain/nmodl@dac3c0b32979859961dc6d578f98734b360ae507 --- src/nmodl/symtab/symbol_properties.cpp | 2 +- src/nmodl/utils/perf_stat.cpp | 16 +++- src/nmodl/utils/perf_stat.hpp | 7 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 4 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 8 +- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/localize_visitor.hpp | 6 ++ src/nmodl/visitors/main.cpp | 4 +- src/nmodl/visitors/perf_visitor.cpp | 77 +++++++++++++------ src/nmodl/visitors/perf_visitor.hpp | 21 ++++- .../transpiler/utils/nmodl_constructs.cpp | 1 + test/nmodl/transpiler/visitor/visitor.cpp | 43 +++++++---- 12 files changed, 140 insertions(+), 51 deletions(-) diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index c5d2998426..20cac46df0 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -11,7 +11,7 @@ bool has_property(const SymbolInfo& obj, NmodlInfo property) { return static_cast<bool>(obj & property); } -bool has_status(const SymbolStatus &obj, Status state) { +bool has_status(const SymbolStatus& obj, Status state) { return static_cast<bool>(obj & state); } diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index 4977492334..395ee5317f 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -18,7 +18,11 @@ PerfStat operator+(const PerfStat& first, const PerfStat& second) { result.exp_count = first.exp_count + second.exp_count; result.log_count = first.log_count + second.log_count; result.pow_count = first.pow_count + second.pow_count; - result.func_call_count = first.func_call_count + second.func_call_count; + + result.external_func_call_count = + first.external_func_call_count + second.external_func_call_count; + result.internal_func_call_count = + first.internal_func_call_count + second.internal_func_call_count; result.and_count = first.and_count + second.and_count; result.or_count = first.or_count + second.or_count; @@ -57,8 +61,9 @@ void PerfStat::print(std::stringstream& stream) { } std::vector<std::string> PerfStat::keys() { - return {"+", "-", "x", "/", "exp", "GM(R)", "GM(W)", "CM(R)", - "CM(W)", "LM(R)", "LM(W)", "call", "compare", "unary", "conditional"}; + return {"+", "-", "x", "/", "exp", "log", + "GM(R)", "GM(W)", "CM(R)", "CM(W)", "LM(R)", "LM(W)", + "calls(ext)", "calls(int)", "compare", "unary", "conditional"}; } std::vector<std::string> PerfStat::values() { @@ -72,6 +77,7 @@ std::vector<std::string> PerfStat::values() { row.push_back(std::to_string(mul_count)); row.push_back(std::to_string(div_count)); row.push_back(std::to_string(exp_count)); + row.push_back(std::to_string(log_count)); row.push_back(std::to_string(global_read_count)); row.push_back(std::to_string(global_write_count)); @@ -79,7 +85,9 @@ std::vector<std::string> PerfStat::values() { row.push_back(std::to_string(constant_write_count)); row.push_back(std::to_string(local_read_count)); row.push_back(std::to_string(local_write_count)); - row.push_back(std::to_string(func_call_count)); + + row.push_back(std::to_string(external_func_call_count)); + row.push_back(std::to_string(internal_func_call_count)); row.push_back(std::to_string(compares)); row.push_back(std::to_string(not_count + neg_count)); diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index 69169aab5a..f7858b9cb3 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -34,9 +34,12 @@ class PerfStat { int exp_count = 0; int log_count = 0; int pow_count = 0; + /// could be external math funcs - /// or mod functions (before inlining) - int func_call_count = 0; + int external_func_call_count = 0; + + /// mod functions (before/after inlining) + int internal_func_call_count = 0; /// bitwise ops int and_count = 0; diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 429f14a483..ec05da7c73 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -241,7 +241,9 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { * of verbatim block to find the variable usage. */ void DefUseAnalyzeVisitor::visit_verbatim(Verbatim* node) { - current_chain->push_back(DUInstance(DUState::U)); + if (!ignore_verbatim) { + current_chain->push_back(DUInstance(DUState::U)); + } } /** Update def-use chain if we encounter a variable that we are looking for. diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index e0323c0f93..680b897e70 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -175,8 +175,8 @@ class DefUseAnalyzeVisitor : public AstVisitor { /// indicate that there is unsupported construct encountered bool unsupported_node = false; - /// indicate that there is verbatim block encountered - bool verbatim_node = false; + /// ignore verbatim blocks + bool ignore_verbatim = false; /// starting visiting lhs of assignment statement bool visiting_lhs = false; @@ -192,6 +192,10 @@ class DefUseAnalyzeVisitor : public AstVisitor { explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) : global_symtab(symtab) { } + DefUseAnalyzeVisitor(symtab::SymbolTable* symtab, bool ignore_verbatim) + : global_symtab(symtab), ignore_verbatim(ignore_verbatim) { + } + virtual void visit_binary_expression(BinaryExpression* node) override; virtual void visit_if_statement(IfStatement* node) override; virtual void visit_function_call(ast::FunctionCall* node) override; diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 8223125221..43e7c255f9 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -81,7 +81,7 @@ void LocalizeVisitor::visit_program(Program* node) { /// compute def use chains for (auto& block : blocks) { if (node_to_localize(block.get())) { - DefUseAnalyzeVisitor v(program_symtab); + DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); auto usages = v.analyze(block.get(), variable); auto result = usages.eval(); block_usage[result].push_back(block); diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index c134810a71..360d822397 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -73,6 +73,9 @@ class LocalizeVisitor : public AstVisitor { private: + /// ignore verbatim blocks while localizing + bool ignore_verbatim = false; + symtab::SymbolTable* program_symtab = nullptr; std::vector<std::string> variables_to_optimize(); @@ -82,6 +85,9 @@ class LocalizeVisitor : public AstVisitor { public: LocalizeVisitor() = default; + explicit LocalizeVisitor(bool ignore_verbatim) : ignore_verbatim(ignore_verbatim) { + } + virtual void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 7212900298..4bb2694cc2 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -137,7 +137,9 @@ int main(int argc, const char* argv[]) { } { - LocalizeVisitor v; + /// for benchmarking/plotting purpose we want to enable unsafe mode + bool ignore_verbatim = true; + LocalizeVisitor v(ignore_verbatim); v.visit_program(ast.get()); } diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index d8434c55be..47c5a9cb78 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -118,7 +118,8 @@ void PerfVisitor::measure_performance(AST* node) { auto symtab = node->get_symbol_table(); if (symtab == nullptr) { - throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); + throw std::runtime_error("Perfvisitor : symbol table not setup for " + + node->get_type_name()); } auto name = symtab->name(); @@ -155,7 +156,14 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { current_block_perf.pow_count++; } node->visit_children(this); - current_block_perf.func_call_count++; + + auto symbol = current_symtab->lookup_in_scope(name); + auto method_property = NmodlInfo::procedure_block | NmodlInfo::function_block; + if (symbol != nullptr && symbol->has_properties(method_property)) { + current_block_perf.internal_func_call_count++; + } else { + current_block_perf.external_func_call_count++; + } } under_function_call = false; @@ -173,48 +181,63 @@ void PerfVisitor::visit_prime_name(PrimeName* node) { node->visit_children(this); } -void PerfVisitor::visit_if_statement(IfStatement* /*node*/) { +void PerfVisitor::visit_if_statement(IfStatement* node) { if (start_measurement) { current_block_perf.if_count++; + node->visit_children(this); } } -void PerfVisitor::visit_else_if_statement(ElseIfStatement* /*node*/) { +void PerfVisitor::visit_else_if_statement(ElseIfStatement* node) { if (start_measurement) { current_block_perf.elif_count++; + node->visit_children(this); } } void PerfVisitor::count_variables() { /// number of instance variables: range or assigned variables - /// \todo: one caveat is that the global variables appearing in - /// assigned block variable are not treated as range! + /// one caveat is that the global variables appearing in + /// assigned block are not treated as range + num_instance_variables = 0; + SymbolInfo property = NmodlInfo::range_var | NmodlInfo::dependent_def | NmodlInfo::state_var; auto variables = current_symtab->get_variables_with_properties(property); - num_instance_variables = variables.size(); for (auto& variable : variables) { - if (variable->has_properties(NmodlInfo::param_assign)) { - num_constant_instance_variables++; - } - if (variable->has_any_status(Status::localized)) { - num_localized_instance_variables++; + if (!variable->has_properties(NmodlInfo::global_var)) { + num_instance_variables++; + if (variable->has_properties(NmodlInfo::param_assign)) { + num_constant_instance_variables++; + } + if (variable->has_any_status(Status::localized)) { + num_localized_instance_variables++; + } } } - /// state variables + /// state variables have state_var property property = NmodlInfo::state_var; variables = current_symtab->get_variables_with_properties(property); num_state_variables = variables.size(); - /// number of global variables : parameters could appear also as range - /// variables and hence need to filter out - property = NmodlInfo::global_var | NmodlInfo::param_assign; + /// pointer variables have pointer/bbcorepointer + property = NmodlInfo::pointer_var | NmodlInfo::bbcore_pointer_var; + variables = current_symtab->get_variables_with_properties(property); + num_pointer_variables = variables.size(); + + + /// number of global variables : parameters and pointers could appear also + /// as range variables and hence need to filter out. But if anything declared + /// as global is always global. + property = NmodlInfo::global_var | NmodlInfo::param_assign | NmodlInfo::bbcore_pointer_var | + NmodlInfo::pointer_var; variables = current_symtab->get_variables_with_properties(property); num_global_variables = 0; for (auto& variable : variables) { + auto is_global = variable->has_properties(NmodlInfo::global_var); property = NmodlInfo::range_var | NmodlInfo::dependent_def; - if (!variable->has_properties(property)) { + if (!variable->has_properties(property) || is_global) { num_global_variables++; if (variable->has_properties(NmodlInfo::param_assign)) { num_constant_global_variables++; @@ -229,15 +252,18 @@ void PerfVisitor::count_variables() { void PerfVisitor::print_memory_usage() { stream << std::endl; - stream << "#INSTANCE VARIABLES : " << num_instance_variables << " "; + + stream << "#VARIABLES :: "; + stream << " INSTANCE : " << num_instance_variables << " "; stream << "[ CONSTANT " << num_constant_instance_variables << ", "; stream << "LOCALIZED " << num_localized_instance_variables << " ]"; - stream << " #GLOBAL VARIABLES : " << num_global_variables << " "; + stream << " GLOBAL VARIABLES : " << num_global_variables << " "; stream << "[ CONSTANT " << num_constant_global_variables << ", "; stream << "LOCALIZED " << num_localized_global_variables << " ]"; - stream << " #STATE VARIABLES : " << num_state_variables << std::endl; + stream << " STATE : " << num_state_variables; + stream << " POINTER : " << num_pointer_variables << std::endl; if (printer) { printer->push_block("MemoryInfo"); @@ -258,6 +284,10 @@ void PerfVisitor::print_memory_usage() { printer->add_node(std::to_string(num_state_variables), "total"); printer->pop_block(); + printer->push_block("Pointer"); + printer->add_node(std::to_string(num_pointer_variables), "total"); + printer->pop_block(); + printer->pop_block(); } } @@ -295,7 +325,8 @@ void PerfVisitor::visit_statement_block(StatementBlock* node) { current_symtab = node->get_symbol_table(); if (current_symtab == nullptr) { - throw std::runtime_error("Symbol table not setup, Symtab visitor pass did not run?"); + throw std::runtime_error("Perfvisitor : symbol table not setup for " + + node->get_type_name()); } /// new block perf starts from zero @@ -315,6 +346,8 @@ void PerfVisitor::visit_statement_block(StatementBlock* node) { /// solve is not a statement but could have associated block /// and hence could/should not be skipped completely +/// we can't ignore the block because it could have associated +/// statement block (in theory) void PerfVisitor::visit_solve_block(SolveBlock* node) { under_solve_block = true; node->visit_children(this); @@ -343,7 +376,7 @@ void PerfVisitor::visit_unary_expression(UnaryExpression* node) { * read/write operations. For example, for expression "exp(a+b)", * "exp" is an external math function and we should not increment read * count for "exp" symbol. Same for solve statement where name will - * be neuron solver method. + * be derivative block name and neuron solver method. */ bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { bool skip = false; diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 6625af484c..46a49488df 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -64,6 +64,9 @@ class PerfVisitor : public AstVisitor { /// whether solve block is being visited bool under_solve_block = false; + /// whether net receive block is being visited + bool under_net_receive_block = false; + /// to print to json file std::unique_ptr<JSONPrinter> printer; @@ -91,6 +94,9 @@ class PerfVisitor : public AstVisitor { /// count of state variables int num_state_variables = 0; + /// count of pointer / bbcorepointer variables + int num_pointer_variables = 0; + void update_memory_ops(const std::string& name); bool symbol_to_skip(const std::shared_ptr<symtab::Symbol>& symbol); @@ -124,6 +130,14 @@ class PerfVisitor : public AstVisitor { return num_instance_variables; } + int get_const_instance_variable_count() { + return num_constant_instance_variables; + } + + int get_const_global_variable_count() { + return num_constant_global_variables; + } + int get_global_variable_count() { return num_global_variables; } @@ -156,8 +170,11 @@ class PerfVisitor : public AstVisitor { measure_performance(node); } + /// skip initial block under net_receive block void visit_initial_block(InitialBlock* node) override { - measure_performance(node); + if (!under_net_receive_block) { + measure_performance(node); + } } void visit_constructor_block(ConstructorBlock* node) override { @@ -201,7 +218,9 @@ class PerfVisitor : public AstVisitor { } void visit_net_receive_block(NetReceiveBlock* node) override { + under_net_receive_block = true; measure_performance(node); + under_net_receive_block = false; } void visit_breakpoint_block(BreakpointBlock* node) override { diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index ad71996e1b..9f54b6e23e 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1238,6 +1238,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + /// \todo : support for comment parsing is not working { "inline_comment_1", { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 642c09a9c4..2a5f5d33b0 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -114,27 +114,32 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { NEURON { SUFFIX NaTs2_t USEION na READ ena WRITE ina - RANGE gNaTs2_tbar, A_AMPA_step - GLOBAL Rstate + RANGE gNaTs2_tbar, A_AMPA_step : range + anything = range (2) + GLOBAL Rstate : global + anything = global + POINTER rng : pointer = global + BBCOREPOINTER coreRng : pointer + assigned = range } PARAMETER { - gNaTs2_tbar = 0.00001 (S/cm2) - tau_r = 0.2 (ms) - tau_d_AMPA = 1.0 + gNaTs2_tbar = 0.00001 (S/cm2) : range + parameter = range already + tau_r = 0.2 (ms) : parameter = global + tau_d_AMPA = 1.0 : parameter = global + tsyn_fac = 11.1 : parameter + assigned = range } ASSIGNED { - v (mV) - ena (mV) - tau_r - tsyn_fac - A_AMPA_step + v (mV) : only assigned = range + ena (mV) : only assigned = range + tsyn_fac : parameter + assigned = range already + A_AMPA_step : range + assigned = range already + AmState : only assigned = range + Rstate : global + assigned == global already + coreRng : pointer + assigned = range already } STATE { - m - h + m : state = range + h : state = range } BREAKPOINT { @@ -143,6 +148,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { SOLVE states METHOD cnexp gNaTs2_t = gNaTs2_tbar*m*m*m*h ina = gNaTs2_t*(v-ena) + m = hBetaf(11) } FUNCTION hBetaf(v) { @@ -189,6 +195,8 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { auto num_instance_var = v.get_instance_variable_count(); auto num_global_var = v.get_global_variable_count(); auto num_state_var = v.get_state_variable_count(); + auto num_const_instance_var = v.get_const_instance_variable_count(); + auto num_const_global_var = v.get_const_global_variable_count(); v2.visit_program(ast.get()); std::stringstream s; @@ -202,16 +210,19 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { REQUIRE(result.div_count == 2); REQUIRE(result.exp_count == 1); REQUIRE(result.global_read_count == 6); - REQUIRE(result.global_write_count == 1); + REQUIRE(result.global_write_count == 2); REQUIRE(result.constant_read_count == 1); REQUIRE(result.constant_write_count == 0); REQUIRE(result.local_read_count == 3); REQUIRE(result.local_write_count == 2); - REQUIRE(result.func_call_count == 1); + REQUIRE(result.external_func_call_count == 1); + REQUIRE(result.internal_func_call_count == 1); REQUIRE(result.neg_count == 3); - REQUIRE(num_instance_var == 8); - REQUIRE(num_global_var == 2); + REQUIRE(num_instance_var == 9); + REQUIRE(num_global_var == 4); REQUIRE(num_state_var == 2); + REQUIRE(num_const_instance_var == 2); + REQUIRE(num_const_global_var == 2); } } } From 3c7f32d475c741f1b3aa775f1f935697b060ff09 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 14 Feb 2018 12:15:03 +0100 Subject: [PATCH 059/871] Bug fix in defuse analysis pass: - procedure used in SOLVE statement was not analyzed in defuse analysis which resulted into wrong mod file. - Adding new property "to_solve" to mark procedure to be solved. Localized pass now analyze such procedures. Examples are ProbAMPANMDA_EMS.mod and ProbGABAAB_EMS.mod) Change-Id: Ib1fd8b613b7041b20e0ff3b04b5746a2d4986cd8 NMODL Repo SHA: BlueBrain/nmodl@93a7ca14b5dab0b7932c8b57fdae076c57d47db2 --- src/nmodl/language/visitors_printer.py | 1 + src/nmodl/symtab/symbol_properties.cpp | 4 +++ src/nmodl/symtab/symbol_properties.hpp | 5 ++- src/nmodl/visitors/localize_visitor.cpp | 34 +++++++++++++++++--- src/nmodl/visitors/localize_visitor.hpp | 4 ++- src/nmodl/visitors/main.cpp | 5 +++ src/nmodl/visitors/symtab_visitor_helper.hpp | 14 +++++++- 7 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 6a1d5ef51b..a155b93718 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -170,6 +170,7 @@ def private_declaration(self): self.write_line("private:", post_gutter=1) self.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) self.write_line("std::unique_ptr<JSONPrinter> printer;") + self.write_line("std::string block_to_solve;") self.write_line("bool state_block = false;", newline=2) def public_declaration(self): diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 20cac46df0..efa74a5795 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -134,6 +134,10 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { if (has_property(obj, NmodlInfo::state_var)) { properties.emplace_back("state_var"); } + + if (has_property(obj, NmodlInfo::to_solve)) { + properties.emplace_back("to_solve"); + } return properties; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index f00da5ae65..9683d7f518 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -173,7 +173,10 @@ namespace symtab { extern_method = 1 << 27, /** state variable */ - state_var = 1 << 28 + state_var = 1 << 28, + + /** need to solve : used in solve statement */ + to_solve = 1 << 29 }; } // namespace details diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 43e7c255f9..ca17e3750c 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -4,11 +4,19 @@ #include "visitors/defuse_analyze_visitor.hpp" using namespace ast; +using namespace symtab; -bool LocalizeVisitor::node_to_localize(ast::Node* node) { +bool LocalizeVisitor::node_for_def_use_analysis(ast::Node *node) { auto type = node->get_type(); + + /** + * Blocks where we should compute def-use chains. We are excluding + * procedures and functions because we expect those to be "inlined". + * If procedure/function is not inlined then DefUse pass returns + * result as "Use". So it's safe. + */ // clang-format off - const std::vector<ast::Type> blocks_to_localize = { + const std::vector<ast::Type> blocks_to_analyze = { ast::Type::INITIAL_BLOCK, ast::Type::BREAKPOINT_BLOCK, ast::Type::CONSTRUCTOR_BLOCK, @@ -26,8 +34,24 @@ bool LocalizeVisitor::node_to_localize(ast::Node* node) { ast::Type::AFTER_BLOCK, }; // clang-format on - auto it = std::find(blocks_to_localize.begin(), blocks_to_localize.end(), type); - return !(it == blocks_to_localize.end()); + auto it = std::find(blocks_to_analyze.begin(), blocks_to_analyze.end(), type); + auto node_to_analyze = !(it == blocks_to_analyze.end()); + auto solve_procedure = is_solve_procedure(node); + return (node_to_analyze || solve_procedure); +} + +/* + * Check if given node is a procedure block and if it is used + * in the solve statement. + */ +bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { + if(node->is_procedure_block()) { + auto symbol = program_symtab->lookup(node->get_name()); + if (symbol && symbol->has_properties(NmodlInfo::to_solve)) { + return true; + } + } + return false; } std::vector<std::string> LocalizeVisitor::variables_to_optimize() { @@ -80,7 +104,7 @@ void LocalizeVisitor::visit_program(Program* node) { /// compute def use chains for (auto& block : blocks) { - if (node_to_localize(block.get())) { + if (node_for_def_use_analysis(block.get())) { DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); auto usages = v.analyze(block.get(), variable); auto result = usages.eval(); diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 360d822397..8d0c5e35ac 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -80,7 +80,9 @@ class LocalizeVisitor : public AstVisitor { std::vector<std::string> variables_to_optimize(); - bool node_to_localize(ast::Node* node); + bool node_for_def_use_analysis(ast::Node *node); + + bool is_solve_procedure(ast::Node* node); public: LocalizeVisitor() = default; diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 4bb2694cc2..dc64b01255 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -165,6 +165,11 @@ int main(int argc, const char* argv[]) { std::cout << stream.str(); } + { + SymtabVisitor v; + v.visit_program(ast.get()); + } + { PerfVisitor v(channel_name + ".perf.json"); v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 3ddaa626c9..9563a09af8 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -28,7 +28,8 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { } /// add new symbol - symbol = std::make_shared<Symbol>(node->get_name(), node, token); + auto name = node->get_name(); + symbol = std::make_shared<Symbol>(name, node, token); symbol->add_property(property); modsymtab->insert(symbol); @@ -51,6 +52,11 @@ void SymtabVisitor::setup_symbol_table(T* node, auto token = node->get_token(); auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); + + if(name == block_to_solve) { + symbol->add_property(NmodlInfo::to_solve); + } + modsymtab->insert(symbol); setup_symbol_table(node, name, is_global); } @@ -76,6 +82,12 @@ void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global state_block = true; } + /// there is only one solve statement allowed in mod file + if (node->is_solve_block()) { + auto solve_block = dynamic_cast<SolveBlock*> (node); + block_to_solve = solve_block->name->get_name(); + } + /// not required at the moment but every node /// has pointer to associated symbol table node->set_symbol_table(symtab); From 925a0cf5fb76d7dfd1ef2e71ca12f90028f18b12 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 17 Feb 2018 11:21:04 +0100 Subject: [PATCH 060/871] Added unique memory counts for global and parameters - renamed longer counter variable names in PerfStat - perf stat test updated Change-Id: I0f5b34db8ecd2e53c038cba05ef3175bcd59408f NMODL Repo SHA: BlueBrain/nmodl@ca43548e3225ec400878c5c3b4f674968038c05a --- src/nmodl/utils/perf_stat.cpp | 109 ++++++++-------- src/nmodl/utils/perf_stat.hpp | 60 ++++----- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 4 +- src/nmodl/visitors/localize_visitor.hpp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 124 ++++++++++++------- src/nmodl/visitors/perf_visitor.hpp | 13 ++ src/nmodl/visitors/symtab_visitor_helper.hpp | 4 +- test/nmodl/transpiler/visitor/visitor.cpp | 64 +++++----- 9 files changed, 222 insertions(+), 160 deletions(-) diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index 395ee5317f..e43f5e9648 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -8,45 +8,48 @@ PerfStat operator+(const PerfStat& first, const PerfStat& second) { PerfStat result; - result.assign_count = first.assign_count + second.assign_count; + result.n_assign = first.n_assign + second.n_assign; - result.add_count = first.add_count + second.add_count; - result.sub_count = first.sub_count + second.sub_count; - result.mul_count = first.mul_count + second.mul_count; - result.div_count = first.div_count + second.div_count; + result.n_add = first.n_add + second.n_add; + result.n_sub = first.n_sub + second.n_sub; + result.n_mul = first.n_mul + second.n_mul; + result.n_div = first.n_div + second.n_div; - result.exp_count = first.exp_count + second.exp_count; - result.log_count = first.log_count + second.log_count; - result.pow_count = first.pow_count + second.pow_count; + result.n_exp = first.n_exp + second.n_exp; + result.n_log = first.n_log + second.n_log; + result.n_pow = first.n_pow + second.n_pow; - result.external_func_call_count = - first.external_func_call_count + second.external_func_call_count; - result.internal_func_call_count = - first.internal_func_call_count + second.internal_func_call_count; + result.n_ext_func_call = first.n_ext_func_call + second.n_ext_func_call; + result.n_int_func_call = first.n_int_func_call + second.n_int_func_call; - result.and_count = first.and_count + second.and_count; - result.or_count = first.or_count + second.or_count; + result.n_and = first.n_and + second.n_and; + result.n_or = first.n_or + second.n_or; - result.gt_count = first.gt_count + second.gt_count; - result.lt_count = first.lt_count + second.lt_count; - result.ge_count = first.ge_count + second.ge_count; - result.le_count = first.le_count + second.le_count; - result.ne_count = first.ne_count + second.ne_count; - result.ee_count = first.ee_count + second.ee_count; + result.n_gt = first.n_gt + second.n_gt; + result.n_lt = first.n_lt + second.n_lt; + result.n_ge = first.n_ge + second.n_ge; + result.n_le = first.n_le + second.n_le; + result.n_ne = first.n_ne + second.n_ne; + result.n_ee = first.n_ee + second.n_ee; - result.not_count = first.not_count + second.not_count; - result.neg_count = first.neg_count + second.neg_count; + result.n_not = first.n_not + second.n_not; + result.n_neg = first.n_neg + second.n_neg; - result.if_count = first.if_count + second.if_count; - result.elif_count = first.elif_count + second.elif_count; + result.n_if = first.n_if + second.n_if; + result.n_elif = first.n_elif + second.n_elif; - result.global_read_count = first.global_read_count + second.global_read_count; - result.global_write_count = first.global_write_count + second.global_write_count; - result.local_read_count = first.local_read_count + second.local_read_count; - result.local_write_count = first.local_write_count + second.local_write_count; + result.n_global_read = first.n_global_read + second.n_global_read; + result.n_global_write = first.n_global_write + second.n_global_write; + result.n_unique_global_read = first.n_unique_global_read + second.n_unique_global_read; + result.n_unique_global_write = first.n_unique_global_write + second.n_unique_global_write; - result.constant_read_count = first.constant_read_count + second.constant_read_count; - result.constant_write_count = first.constant_write_count + second.constant_write_count; + result.n_local_read = first.n_local_read + second.n_local_read; + result.n_local_write = first.n_local_write + second.n_local_write; + + result.n_constant_read = first.n_constant_read + second.n_constant_read; + result.n_constant_write = first.n_constant_write + second.n_constant_write; + result.n_unique_constant_read = first.n_unique_constant_read + second.n_unique_constant_read; + result.n_unique_constant_write = first.n_unique_constant_write + second.n_unique_constant_write; return result; } @@ -61,36 +64,42 @@ void PerfStat::print(std::stringstream& stream) { } std::vector<std::string> PerfStat::keys() { - return {"+", "-", "x", "/", "exp", "log", - "GM(R)", "GM(W)", "CM(R)", "CM(W)", "LM(R)", "LM(W)", - "calls(ext)", "calls(int)", "compare", "unary", "conditional"}; + return {"+", "-", "x", "/", "exp", "log", "GM-R(T)", + "GM-R(U)", "GM-W(T)", "GM-W(U)", "CM-R(T)", "CM-R(U)", "CM-W(T)", "CM-W(U)", + "LM-R(T)", "LM-W(T)", "calls(ext)", "calls(int)", "compare", "unary", "conditional"}; } std::vector<std::string> PerfStat::values() { std::vector<std::string> row; - int compares = gt_count + lt_count + ge_count + le_count + ne_count + ee_count; - int conditionals = if_count + elif_count; + int compares = n_gt + n_lt + n_ge + n_le + n_ne + n_ee; + int conditionals = n_if + n_elif; + + row.push_back(std::to_string(n_add)); + row.push_back(std::to_string(n_sub)); + row.push_back(std::to_string(n_mul)); + row.push_back(std::to_string(n_div)); + row.push_back(std::to_string(n_exp)); + row.push_back(std::to_string(n_log)); + + row.push_back(std::to_string(n_global_read)); + row.push_back(std::to_string(n_unique_global_read)); + row.push_back(std::to_string(n_global_write)); + row.push_back(std::to_string(n_unique_global_write)); - row.push_back(std::to_string(add_count)); - row.push_back(std::to_string(sub_count)); - row.push_back(std::to_string(mul_count)); - row.push_back(std::to_string(div_count)); - row.push_back(std::to_string(exp_count)); - row.push_back(std::to_string(log_count)); + row.push_back(std::to_string(n_constant_read)); + row.push_back(std::to_string(n_unique_constant_read)); + row.push_back(std::to_string(n_constant_write)); + row.push_back(std::to_string(n_unique_constant_write)); - row.push_back(std::to_string(global_read_count)); - row.push_back(std::to_string(global_write_count)); - row.push_back(std::to_string(constant_read_count)); - row.push_back(std::to_string(constant_write_count)); - row.push_back(std::to_string(local_read_count)); - row.push_back(std::to_string(local_write_count)); + row.push_back(std::to_string(n_local_read)); + row.push_back(std::to_string(n_local_write)); - row.push_back(std::to_string(external_func_call_count)); - row.push_back(std::to_string(internal_func_call_count)); + row.push_back(std::to_string(n_ext_func_call)); + row.push_back(std::to_string(n_int_func_call)); row.push_back(std::to_string(compares)); - row.push_back(std::to_string(not_count + neg_count)); + row.push_back(std::to_string(n_not + n_neg)); row.push_back(std::to_string(conditionals)); return row; diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index f7858b9cb3..ff6cb15b94 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -19,60 +19,64 @@ class PerfStat { std::string title; /// write ops - int assign_count = 0; + int n_assign = 0; /// basic ops (<= 1 cycle) - int add_count = 0; - int sub_count = 0; - int mul_count = 0; + int n_add = 0; + int n_sub = 0; + int n_mul = 0; /// expensive ops - int div_count = 0; + int n_div = 0; /// expensive functions : commonly /// used functions in mod files - int exp_count = 0; - int log_count = 0; - int pow_count = 0; + int n_exp = 0; + int n_log = 0; + int n_pow = 0; /// could be external math funcs - int external_func_call_count = 0; + int n_ext_func_call = 0; /// mod functions (before/after inlining) - int internal_func_call_count = 0; + int n_int_func_call = 0; /// bitwise ops - int and_count = 0; - int or_count = 0; + int n_and = 0; + int n_or = 0; /// comparisons ops - int gt_count = 0; - int lt_count = 0; - int ge_count = 0; - int le_count = 0; - int ne_count = 0; - int ee_count = 0; + int n_gt = 0; + int n_lt = 0; + int n_ge = 0; + int n_le = 0; + int n_ne = 0; + int n_ee = 0; /// unary ops - int not_count = 0; - int neg_count = 0; + int n_not = 0; + int n_neg = 0; /// conditional ops - int if_count = 0; - int elif_count = 0; + int n_if = 0; + int n_elif = 0; /// expensive : typically access to dynamically allocated memory - int global_read_count = 0; - int global_write_count = 0; + int n_global_read = 0; + int n_global_write = 0; + int n_unique_global_read = 0; + int n_unique_global_write = 0; /// cheap : typically local variables in mod file means registers - int local_read_count = 0; - int local_write_count = 0; + int n_local_read = 0; + int n_local_write = 0; /// could be optimized : access to variables that could be read-only /// in this case write counts are typically from initialization - int constant_read_count = 0; - int constant_write_count = 0; + int n_constant_read = 0; + int n_constant_write = 0; + int n_unique_constant_read = 0; + int n_unique_constant_write = 0; friend PerfStat operator+(const PerfStat& first, const PerfStat& second); void print(std::stringstream& stream); diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 3cdabf82c0..c2fb728dec 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -53,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index ca17e3750c..7cc2f3a1e0 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -6,7 +6,7 @@ using namespace ast; using namespace symtab; -bool LocalizeVisitor::node_for_def_use_analysis(ast::Node *node) { +bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { auto type = node->get_type(); /** @@ -45,7 +45,7 @@ bool LocalizeVisitor::node_for_def_use_analysis(ast::Node *node) { * in the solve statement. */ bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { - if(node->is_procedure_block()) { + if (node->is_procedure_block()) { auto symbol = program_symtab->lookup(node->get_name()); if (symbol && symbol->has_properties(NmodlInfo::to_solve)) { return true; diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 8d0c5e35ac..5d1718e69e 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -80,7 +80,7 @@ class LocalizeVisitor : public AstVisitor { std::vector<std::string> variables_to_optimize(); - bool node_for_def_use_analysis(ast::Node *node); + bool node_for_def_use_analysis(ast::Node* node); bool is_solve_procedure(ast::Node* node); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 47c5a9cb78..2b9837d98e 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -3,6 +3,7 @@ #include "visitors/perf_visitor.hpp" using namespace symtab; +using namespace ast; PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) { } @@ -14,60 +15,60 @@ void PerfVisitor::visit_binary_expression(BinaryExpression* node) { if (start_measurement) { switch (node->op.value) { case BOP_ADDITION: - current_block_perf.add_count++; + current_block_perf.n_add++; break; case BOP_SUBTRACTION: - current_block_perf.sub_count++; + current_block_perf.n_sub++; break; case BOP_MULTIPLICATION: - current_block_perf.mul_count++; + current_block_perf.n_mul++; break; case BOP_DIVISION: - current_block_perf.div_count++; + current_block_perf.n_div++; break; case BOP_POWER: - current_block_perf.pow_count++; + current_block_perf.n_pow++; break; case BOP_AND: - current_block_perf.and_count++; + current_block_perf.n_and++; break; case BOP_OR: - current_block_perf.or_count++; + current_block_perf.n_or++; break; case BOP_GREATER: - current_block_perf.gt_count++; + current_block_perf.n_gt++; break; case BOP_GREATER_EQUAL: - current_block_perf.ge_count++; + current_block_perf.n_ge++; break; case BOP_LESS: - current_block_perf.lt_count++; + current_block_perf.n_lt++; break; case BOP_LESS_EQUAL: - current_block_perf.le_count++; + current_block_perf.n_le++; break; case BOP_ASSIGN: - current_block_perf.assign_count++; + current_block_perf.n_assign++; assign_op = true; break; case BOP_NOT_EQUAL: - current_block_perf.ne_count++; + current_block_perf.n_ne++; break; case BOP_EXACT_EQUAL: - current_block_perf.ee_count++; + current_block_perf.n_ee++; break; default: @@ -140,6 +141,11 @@ void PerfVisitor::measure_performance(AST* node) { } start_measurement = false; + + /// clear var usage map + for (auto& var_set : var_usage) { + var_set.second.clear(); + } } /// count function calls and "most useful" or "commonly used" math functions @@ -149,20 +155,20 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { if (start_measurement) { auto name = node->name->get_name(); if (name.compare("exp") == 0) { - current_block_perf.exp_count++; + current_block_perf.n_exp++; } else if (name.compare("log") == 0) { - current_block_perf.log_count++; + current_block_perf.n_log++; } else if (name.compare("pow") == 0) { - current_block_perf.pow_count++; + current_block_perf.n_pow++; } node->visit_children(this); auto symbol = current_symtab->lookup_in_scope(name); auto method_property = NmodlInfo::procedure_block | NmodlInfo::function_block; if (symbol != nullptr && symbol->has_properties(method_property)) { - current_block_perf.internal_func_call_count++; + current_block_perf.n_int_func_call++; } else { - current_block_perf.external_func_call_count++; + current_block_perf.n_ext_func_call++; } } @@ -183,14 +189,14 @@ void PerfVisitor::visit_prime_name(PrimeName* node) { void PerfVisitor::visit_if_statement(IfStatement* node) { if (start_measurement) { - current_block_perf.if_count++; + current_block_perf.n_if++; node->visit_children(this); } } void PerfVisitor::visit_else_if_statement(ElseIfStatement* node) { if (start_measurement) { - current_block_perf.elif_count++; + current_block_perf.n_elif++; node->visit_children(this); } } @@ -358,11 +364,11 @@ void PerfVisitor::visit_unary_expression(UnaryExpression* node) { if (start_measurement) { switch (node->op.value) { case UOP_NEGATION: - current_block_perf.neg_count++; + current_block_perf.n_neg++; break; case UOP_NOT: - current_block_perf.not_count++; + current_block_perf.n_not++; break; default: @@ -418,36 +424,58 @@ bool PerfVisitor::is_constant_variable(const std::shared_ptr<symtab::Symbol>& sy * read/write count. Also update ops count in current block. */ void PerfVisitor::update_memory_ops(const std::string& name) { - if (start_measurement && (current_symtab != nullptr)) { - auto symbol = current_symtab->lookup_in_scope(name); - if (symbol == nullptr || symbol_to_skip(symbol)) { - return; + if (start_measurement == false || current_symtab == nullptr) { + return; + } + + auto symbol = current_symtab->lookup_in_scope(name); + if (symbol == nullptr || symbol_to_skip(symbol)) { + return; + } + + if (is_local_variable(symbol)) { + if (visiting_lhs_expression) { + symbol->write(); + current_block_perf.n_local_write++; + } else { + symbol->read(); + current_block_perf.n_local_read++; } + return; + } - if (is_local_variable(symbol)) { - if (visiting_lhs_expression) { - symbol->write(); - current_block_perf.local_write_count++; - } else { - symbol->read(); - current_block_perf.local_read_count++; + /// lhs symbols get written + if (visiting_lhs_expression) { + symbol->write(); + if (is_constant_variable(symbol)) { + current_block_perf.n_constant_write++; + if (var_usage[const_memw_key].find(name) == var_usage[const_memw_key].end()) { + current_block_perf.n_unique_constant_write++; + var_usage[const_memw_key].insert(name); } } else { - if (visiting_lhs_expression) { - symbol->write(); - if (is_constant_variable(symbol)) { - current_block_perf.constant_write_count++; - } else { - current_block_perf.global_write_count++; - } - } else { - symbol->read(); - if (is_constant_variable(symbol)) { - current_block_perf.constant_read_count++; - } else { - current_block_perf.global_read_count++; - } + current_block_perf.n_global_write++; + if (var_usage[global_memw_key].find(name) == var_usage[global_memw_key].end()) { + current_block_perf.n_unique_global_write++; + var_usage[global_memw_key].insert(name); } } + return; + } + + /// rhs symbols get read + symbol->read(); + if (is_constant_variable(symbol)) { + current_block_perf.n_constant_read++; + if (var_usage[const_memr_key].find(name) == var_usage[const_memr_key].end()) { + current_block_perf.n_unique_constant_read++; + var_usage[const_memr_key].insert(name); + } + } else { + current_block_perf.n_global_read++; + if (var_usage[global_memr_key].find(name) == var_usage[global_memr_key].end()) { + current_block_perf.n_unique_global_read++; + var_usage[global_memr_key].insert(name); + } } } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 46a49488df..b61c7627d1 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -2,6 +2,7 @@ #define _NMODL_PERF_VISITOR_HPP_ #include <stack> +#include <set> #include "printer/json_printer.hpp" #include "symtab/symbol_table.hpp" @@ -97,6 +98,18 @@ class PerfVisitor : public AstVisitor { /// count of pointer / bbcorepointer variables int num_pointer_variables = 0; + /// keys used in map to track var usage + std::string const_memr_key = "cm_r_u"; + std::string const_memw_key = "cm_w_u"; + std::string global_memr_key = "gm_r_u"; + std::string global_memw_key = "gm_w_u"; + + /// map of variables to count unique read-writes + std::map<std::string, std::set<std::string>> var_usage = {{const_memr_key, {}}, + {const_memw_key, {}}, + {global_memr_key, {}}, + {global_memw_key, {}}}; + void update_memory_ops(const std::string& name); bool symbol_to_skip(const std::shared_ptr<symtab::Symbol>& symbol); diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 9563a09af8..c9a1e099a6 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -53,7 +53,7 @@ void SymtabVisitor::setup_symbol_table(T* node, auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); - if(name == block_to_solve) { + if (name == block_to_solve) { symbol->add_property(NmodlInfo::to_solve); } @@ -84,7 +84,7 @@ void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global /// there is only one solve statement allowed in mod file if (node->is_solve_block()) { - auto solve_block = dynamic_cast<SolveBlock*> (node); + auto solve_block = dynamic_cast<SolveBlock*>(node); block_to_solve = solve_block->name->get_name(); } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 2a5f5d33b0..3f55e20714 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -143,16 +143,27 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { } BREAKPOINT { - LOCAL gNaTs2_t CONDUCTANCE gNaTs2_t USEION na SOLVE states METHOD cnexp - gNaTs2_t = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t*(v-ena) - m = hBetaf(11) + { + LOCAL gNaTs2_t + { + gNaTs2_t = gNaTs2_tbar*m*m*m*h + } + ina = gNaTs2_t*(v-ena) + } + { + m = hBetaf(11+v) + m = 12/gNaTs2_tbar + } + { + gNaTs2_tbar = gNaTs2_tbar*gNaTs2_tbar + 11.0 + gNaTs2_tbar = 12.0 + } } FUNCTION hBetaf(v) { - hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) + hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) } )"; @@ -188,7 +199,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { } WHEN("Perf visitor pass runs after symtab visitor") { - PerfVisitor v, v2("a.a"); + PerfVisitor v; v.visit_program(ast.get()); auto result = v.get_total_perfstat(); @@ -198,26 +209,25 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { auto num_const_instance_var = v.get_const_instance_variable_count(); auto num_const_global_var = v.get_const_global_variable_count(); - v2.visit_program(ast.get()); - std::stringstream s; - result.print(s); - std::cout << s.str(); - THEN("Performance counters are updated") { - REQUIRE(result.add_count == 0); - REQUIRE(result.sub_count == 4); - REQUIRE(result.mul_count == 6); - REQUIRE(result.div_count == 2); - REQUIRE(result.exp_count == 1); - REQUIRE(result.global_read_count == 6); - REQUIRE(result.global_write_count == 2); - REQUIRE(result.constant_read_count == 1); - REQUIRE(result.constant_write_count == 0); - REQUIRE(result.local_read_count == 3); - REQUIRE(result.local_write_count == 2); - REQUIRE(result.external_func_call_count == 1); - REQUIRE(result.internal_func_call_count == 1); - REQUIRE(result.neg_count == 3); + REQUIRE(result.n_add == 2); + REQUIRE(result.n_sub == 4); + REQUIRE(result.n_mul == 7); + REQUIRE(result.n_div == 3); + REQUIRE(result.n_exp == 1); + REQUIRE(result.n_global_read == 7); + REQUIRE(result.n_unique_global_read == 4); + REQUIRE(result.n_global_write == 3); + REQUIRE(result.n_unique_global_write == 2); + REQUIRE(result.n_constant_read == 4); + REQUIRE(result.n_unique_constant_read == 1); + REQUIRE(result.n_constant_write == 2); + REQUIRE(result.n_unique_constant_write == 1); + REQUIRE(result.n_local_read == 3); + REQUIRE(result.n_local_write == 2); + REQUIRE(result.n_ext_func_call == 1); + REQUIRE(result.n_int_func_call == 1); + REQUIRE(result.n_neg == 3); REQUIRE(num_instance_var == 9); REQUIRE(num_global_var == 4); REQUIRE(num_state_var == 2); @@ -357,9 +367,7 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { THEN("existing variables could be renamed") { std::vector<std::pair<std::string, std::string>> variables = { - {"m", "mm"}, - {"gNaTs2_tbar", "new_gNaTs2_tbar"}, - {"mAlpha", "mBeta"}, + {"m", "mm"}, {"gNaTs2_tbar", "new_gNaTs2_tbar"}, {"mAlpha", "mBeta"}, }; auto result = run_var_rename_visitor(input, variables); REQUIRE(result == expected_output); From 97888d873238b1d0aa3a257b31fbcca8ba2d0f3b Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 17 Feb 2018 19:45:51 +0100 Subject: [PATCH 061/871] Allow symtab to run multiple times without creating new symbol table for every run: - new method set_mode is provided to reuse or create new symbol table - symbol table refactored (e.g. parent_symtab renamed to current_symtab) - rename and creation status added to symbols - bug fix in local renamed pass that was renaming un-necessary variable - Table now uses vector instead of map to allow renaming symbols - localizer pass insert symbol into symbol table - enter_scope now accept the exisiting symbol table of the node Change-Id: I5ae1f32f9935426d183cbe0c3fc875c2f6529181 NMODL Repo SHA: BlueBrain/nmodl@c6dbc666420de1fb2aaec2ae1463481beea27ba9 --- src/nmodl/language/visitors_printer.py | 7 +- src/nmodl/symtab/symbol.hpp | 11 + src/nmodl/symtab/symbol_table.cpp | 312 +++++++++++------- src/nmodl/symtab/symbol_table.hpp | 38 ++- .../visitors/local_var_rename_visitor.cpp | 19 +- src/nmodl/visitors/localize_visitor.cpp | 17 +- src/nmodl/visitors/main.cpp | 67 ++-- src/nmodl/visitors/symtab_visitor_helper.hpp | 4 +- src/nmodl/visitors/visitor_utils.cpp | 10 +- src/nmodl/visitors/visitor_utils.hpp | 4 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 20 +- test/nmodl/transpiler/visitor/visitor.cpp | 10 +- 12 files changed, 317 insertions(+), 202 deletions(-) diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index a155b93718..bdc43ce15c 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -171,18 +171,19 @@ def private_declaration(self): self.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) self.write_line("std::unique_ptr<JSONPrinter> printer;") self.write_line("std::string block_to_solve;") + self.write_line("bool update = false;") self.write_line("bool state_block = false;", newline=2) def public_declaration(self): self.write_line("public:", post_gutter=1) - line = self.classname + "() : printer(new JSONPrinter()) {} " + line = "explicit " + self.classname + "(bool update = false) : printer(new JSONPrinter()), update(update) {} " self.write_line(line) - line = self.classname + "(std::stringstream &ss) : printer(new JSONPrinter(ss)) {}" + line = self.classname + "(std::stringstream &ss, bool update = false) : printer(new JSONPrinter(ss)), update(update) {}" self.write_line(line) - line = self.classname + "(std::string filename) : printer(new JSONPrinter(filename)) {}" + line = self.classname + "(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {}" self.write_line(line, newline=2) # helper function for creating symbol for variables diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index fb3633c0fc..4e282fdf8c 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -65,6 +65,9 @@ namespace symtab { public: Symbol() = delete; + Symbol(std::string name, AST* node) : name(name), node(node) { + } + Symbol(std::string name, ModToken token) : name(name), token(token) { } @@ -79,6 +82,10 @@ namespace symtab { return name; } + void set_name(std::string new_name) { + name = new_name; + } + std::string get_scope() { return scope; } @@ -131,6 +138,10 @@ namespace symtab { status |= Status::inlined; } + void created() { + status |= Status::created; + } + void renamed() { status |= Status::renamed; } diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 3ee6726b50..8bcf95e4d9 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -6,26 +6,29 @@ namespace symtab { - /** Insert symbol into current symbol table. There are certain + /** + * Insert symbol into current symbol table. There are certain * cases where we were getting re-insertion errors. - * - * \todo Revisit the error handling */ void Table::insert(const std::shared_ptr<Symbol>& symbol) { std::string name = symbol->get_name(); - if (symbols.find(name) != symbols.end()) { + if (lookup(name) != nullptr) { throw std::runtime_error("Trying to re-insert symbol " + name); } - symbols[name] = symbol; + symbols.push_back(symbol); } + std::shared_ptr<Symbol> Table::lookup(const std::string& name) { - if (symbols.find(name) == symbols.end()) { - return nullptr; + for (const auto& symbol : symbols) { + if (symbol->get_name() == name) { + return symbol; + } } - return symbols[name]; + return nullptr; } + SymbolTable::SymbolTable(const SymbolTable& table) { symtab_name = table.name(); global = table.global_scope(); @@ -33,11 +36,12 @@ namespace symtab { parent = nullptr; } + std::string SymbolTable::type() const { return node->get_type_name(); } - /// \todo: should return unknown position ? + std::string SymbolTable::position() const { auto token = node->get_token(); std::string position; @@ -49,6 +53,7 @@ namespace symtab { return position; } + /// insert new symbol table of one of the children block void SymbolTable::insert_table(const std::string& name, std::shared_ptr<SymbolTable> table) { if (children.find(name) != children.end()) { @@ -57,12 +62,12 @@ namespace symtab { children[name] = std::move(table); } + /// return all symbol having any of the provided properties std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( SymbolInfo properties) { std::vector<std::shared_ptr<Symbol>> variables; - for (auto& syminfo : table.symbols) { - auto symbol = syminfo.second; + for (auto& symbol : table.symbols) { if (symbol->has_properties(properties)) { variables.push_back(symbol); } @@ -70,16 +75,16 @@ namespace symtab { return variables; } - /** Check if current symbol table is in global scope + + /** + * Check if current symbol table is in global scope + * * We create program scope at the top level and it has global scope. * It contains neuron variables like t, dt, celsius etc. Then each * nmodl block defining variables are added under this program's symbol * table. Hence there are multiple levels of global scopes. In this * helper function we make sure current block as well as it's parent are * under global scopes. - * - * \todo It seems not necessary to traverse all parents if current block - * itself is with global scope. */ bool SymbolTable::under_global_scope() { bool global_scope = global; @@ -95,6 +100,7 @@ namespace symtab { return global_scope; } + /// lookup for symbol in current scope as well as all parents std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(const std::string& name) { auto symbol = table.lookup(name); @@ -104,26 +110,19 @@ namespace symtab { return symbol; } - /// lookup in all parents symbol table - /// \todo parent_symtab is somewhat misleading. it's actually - /// current symbol table that is under process. we should - /// rename it. note that we still need parent symbol table - /// filed to traverse the parents. revisit this usage. + /// lookup in current sytab as well as all parent symbol tables std::shared_ptr<Symbol> ModelSymbolTable::lookup(const std::string& name) { - /// parent symbol is not set means symbol table is - /// is not used with visitor at all. it would be ok - // to just return nullptr? - if (parent_symtab == nullptr) { + if (current_symtab == nullptr) { throw std::logic_error("Lookup with previous symtab = nullptr "); } - auto symbol = parent_symtab->lookup(name); + auto symbol = current_symtab->lookup(name); if (symbol) { return symbol; } // check into all parent symbol tables - auto parent = parent_symtab->get_parent_table(); + auto parent = current_symtab->get_parent_table(); while (parent != nullptr) { symbol = parent->lookup(name); if (symbol) { @@ -134,153 +133,233 @@ namespace symtab { return symbol; } - void ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { - if (parent_symtab == nullptr) { - throw std::logic_error("Can not insert symbol without entering scope"); + + /** + * Emit warning message for shadowing definition or throw an exception + * if variable is being redefined in the same block. + */ + void ModelSymbolTable::emit_message(const std::shared_ptr<Symbol>& first, + const std::shared_ptr<Symbol>& second, + bool redefinition) { + auto node = first->get_node(); + std::string name = first->get_name(); + auto properties = to_string(second->get_properties()); + std::string type = "UNKNOWN"; + if (node != nullptr) { + type = node->get_type_name(); } - symbol->set_scope(parent_symtab->name()); + if (redefinition) { + std::string msg = "Re-declaration of " + name + " [" + type + "]"; + msg += "<" + properties + "> in " + current_symtab->name(); + msg += " with one in " + second->get_scope(); + throw std::runtime_error(msg); + } else { + std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; + msg += current_symtab->name() + " shadows <" + properties; + msg += "> definition in " + second->get_scope(); + std::cout << msg << std::endl; + } + } + + + /** + * Insert symbol in the update mode i.e. symbol table is previously created + * and we are adding new symbol table. + * + * We set status as "created" because missing symbol means the variable is + * added by some intermediate passes. + * + * Consider inlining pass which creates a block like: + * + * DERIVATIVE states() { + * LOCAL xx + * { + * LOCAL xx + * } + * } + * + * Second xx is not added into symbol table (and hence not visible + * to other passes like local renamer). In this case we have to do + * local lookup in the current symtab and insert if doesn't exist. + */ + void ModelSymbolTable::update_mode_insert(const std::shared_ptr<Symbol>& symbol) { + symbol->set_scope(current_symtab->name()); + symbol->created(); + std::string name = symbol->get_name(); auto search_symbol = lookup(name); - // if no symbol found then safe to insert + /// if no symbol found then safe to insert if (search_symbol == nullptr) { - parent_symtab->insert(symbol); + current_symtab->insert(symbol); return; } - // properties and type information for error reporting - auto properties = to_string(search_symbol->get_properties()); - auto node = symbol->get_node(); - std::string type = "UNKNOWN"; - if (node != nullptr) { - type = node->get_type_name(); + /// for global scope just combine properties + if (current_symtab->global_scope()) { + search_symbol->combine_properties(symbol->get_properties()); + return; + } + + /// insert into current block's symbol table + if (current_symtab->lookup(name) == nullptr) { + current_symtab->insert(symbol); } + } + - /** For global symbol tables, same variable can appear in multiple + void ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { + if (current_symtab == nullptr) { + throw std::logic_error("Can not insert symbol without entering scope"); + } + + /// handle update mode insertion + if (update_table == true) { + update_mode_insert(symbol); + return; + } + + symbol->set_scope(current_symtab->name()); + auto search_symbol = lookup(symbol->get_name()); + + // if no symbol found then safe to insert + if (search_symbol == nullptr) { + current_symtab->insert(symbol); + return; + } + + /** + * For global symbol tables, same variable can appear in multiple * nmodl "global" blocks. It's an error if it appears multiple times * in the same nmodl block. To check this we compare symbol properties * which are bit flags. If they are not same that means, we have to * add new properties to existing symbol. If the properties are same * that means symbol are duplicate. - * - * \todo Error handling should go to logger instead of exception */ - if (parent_symtab->global_scope()) { + if (current_symtab->global_scope()) { if (search_symbol->has_properties(symbol->get_properties())) { - std::string msg = GLOBAL_SYMTAB_BANE + " has re-declaration of "; - msg += name + " <" + type + "> " + properties; - throw std::runtime_error(msg); + emit_message(symbol, search_symbol, true); } else { search_symbol->combine_properties(symbol->get_properties()); } + return; + } + + /** + * For non-global scopes, check if symbol that we found has same + * scope name as symbol table. This means variable is being re-declared + * within the same scope. Otherwise, there is variable with same name + * in parent scopes and it will shadow the definition. In this case just + * emit the warning and insert the symbol. + */ + if (search_symbol->get_scope() == current_symtab->name()) { + emit_message(symbol, search_symbol, true); } else { - /** For non-global scopes, check if symbol that we found has same - * scope name as symbol table. This means variable is being re-declared - * within the same scope. Otherwise, there is variable with same name - * in parent scopes and it will shadow the definition. In this case just - * emit the warning and insert the symbol. - */ - if (search_symbol->get_scope() == parent_symtab->name()) { - std::string msg = "Re-declaration of " + name + " [" + type + "]"; - msg += "<" + properties + "> in " + parent_symtab->name(); - msg += " with one in " + search_symbol->get_scope(); - throw std::runtime_error(msg); - } else { - std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; - msg += parent_symtab->name() + " shadows <" + properties; - msg += "> definition in " + search_symbol->get_scope(); - std::cout << msg << "\n"; - parent_symtab->insert(symbol); - } + emit_message(symbol, search_symbol, false); + current_symtab->insert(symbol); } } - /** Some blocks can appear multiple times in the nmodl file. In order to distinguish - * them we simple append counter. + + /** + * Some blocks can appear multiple times in the nmodl file. In order to distinguish + * them we simply append counter. * \todo We should add position information to make name unique */ - std::string ModelSymbolTable::get_unique_name(std::string name, AST* node) { + std::string ModelSymbolTable::get_unique_name(std::string name, AST* node, bool is_global) { static int block_counter = 0; - if (node->is_statement_block() || node->is_solve_block() || node->is_before_block() || - node->is_after_block()) { + + if (is_global) { + name = GLOBAL_SYMTAB_NAME; + } else if (node->is_statement_block() || node->is_solve_block() || + node->is_before_block() || node->is_after_block()) { name += std::to_string(block_counter++); } return name; } + /** Function callback at the entry of every block in nmodl file * Every block starts a new scope and hence new symbol table is created. * The same symbol table is returned so that visitor can store pointer to * symbol table within a node. */ - SymbolTable* ModelSymbolTable::enter_scope(std::string name, AST* node, bool global) { + SymbolTable* ModelSymbolTable::enter_scope(std::string name, + AST* node, + bool global, + SymbolTable* node_symtab) { + if (node == nullptr) { throw std::runtime_error("Can't enter with empty node"); } - /// all global blocks in mod file have same symbol table + /// all global blocks in mod file have same global symbol table if (symtab && global) { - parent_symtab = symtab.get(); - return parent_symtab; - } - - /// change name for global symbol table - if (global) { - name = GLOBAL_SYMTAB_BANE; + return symtab.get(); } /// statement block within global scope is part of global block itself - if (symtab && node->is_statement_block() && parent_symtab->under_global_scope()) { - parent_symtab = symtab.get(); - return parent_symtab; + if (symtab && node->is_statement_block() && current_symtab->under_global_scope()) { + return symtab.get(); } - // new symbol table for current block with unique name - name = get_unique_name(name, node); - auto new_symtab = std::make_shared<SymbolTable>(name, node, global); - - // symtab is nullptr when we are entering into very fitst block - // otherwise we have to inset new symbol table, set parent symbol - // table and then new symbol table becomes parent for future blocks - if (symtab == nullptr) { - symtab = new_symtab; - parent_symtab = new_symtab.get(); - } else { - parent_symtab->insert_table(name, new_symtab); - new_symtab->set_parent_table(parent_symtab); - parent_symtab = new_symtab.get(); + if (node_symtab == nullptr || update_table == false) { + name = get_unique_name(name, node, global); + auto new_symtab = std::make_shared<SymbolTable>(name, node, global); + new_symtab->set_parent_table(current_symtab); + if (symtab == nullptr) { + symtab = new_symtab; + } + if (current_symtab) { + current_symtab->insert_table(name, new_symtab); + } + node_symtab = new_symtab.get(); } - return new_symtab.get(); + current_symtab = node_symtab; + return current_symtab; } - /** Callback at the exit of every block in nmodl file When we reach - * program node (top most level), there is no parent block and use - * same symbol table. Otherwise traverse back to prent. + + /** + * Callback at the exit of every block in nmodl file. When we reach + * program node (top most level), there is no parent block and hence + * use top level symbol table. Otherwise traverse back to parent. */ void ModelSymbolTable::leave_scope() { - if (parent_symtab == nullptr && symtab == nullptr) { + if (current_symtab == nullptr) { throw std::logic_error("Trying leave scope without entering"); } - - /// if has parent scope, setup new parent symbol table - if (parent_symtab != nullptr) { - parent_symtab = parent_symtab->get_parent_table(); + if (current_symtab != nullptr) { + current_symtab = current_symtab->get_parent_table(); + } + if (current_symtab == nullptr) { + current_symtab = symtab.get(); } + } + - /// \todo : when parent is nullptr, parent should not be reset to - /// current symbol table. this is happening for global - /// scope symbol table - if (parent_symtab == nullptr) { - parent_symtab = symtab.get(); + /** + * Update mode is true if we want to re-use exisiting symbol table. + * If there is no symbol table constructed then we toggle mode. + */ + void ModelSymbolTable::set_mode(bool update_mode) { + if (update_mode == true && symtab == nullptr) { + update_mode = false; + } + update_table = update_mode; + if (update_table == false) { + symtab = nullptr; + current_symtab = nullptr; } } + //============================================================================= - // Revisit implementation of symbol table pretty printing functions : should - // be refactored into separate table printer function. + // Symbol table pretty-printing functions //============================================================================= + void Table::print(std::stringstream& stream, std::string title, int indent) { if (!symbols.empty()) { TableData table; @@ -289,8 +368,7 @@ namespace symtab { table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, text_alignment::right, text_alignment::right}; - for (const auto& syminfo : symbols) { - auto symbol = syminfo.second; + for (const auto& symbol : symbols) { auto is_external = symbol->is_external_symbol_only(); auto read_count = symbol->get_read_count(); auto write_count = symbol->get_write_count(); @@ -300,7 +378,7 @@ namespace symtab { continue; } - auto name = syminfo.first; + auto name = symbol->get_name(); auto position = symbol->get_token().position(); auto properties = to_string(symbol->get_properties()); auto status = to_string(symbol->get_status()); @@ -312,18 +390,20 @@ namespace symtab { } } + + /// construct title for symbol table std::string SymbolTable::title() { - /// construct title for symbol table auto name = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; auto location = "POSITION : " + position(); auto scope = global ? "GLOBAL" : "LOCAL"; return name + location + " SCOPE : " + scope; } + void SymbolTable::print(std::stringstream& ss, int level) { table.print(ss, title(), level); - /// when current symbol table is empty, the childrens + /// when current symbol table is empty, the children /// can be printed from the same indentation level /// (this is to avoid unnecessary empty indentations) auto next_level = level; @@ -340,7 +420,9 @@ namespace symtab { } } + void ModelSymbolTable::print(std::stringstream& ss) { symtab->print(ss, 0); } + } // namespace symtab \ No newline at end of file diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 64a273007b..48df5baa2e 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -3,6 +3,7 @@ #include <map> #include <memory> +#include <vector> #include "symtab/symbol.hpp" @@ -24,7 +25,7 @@ namespace symtab { class Table { public: /// map of symbol name and associated symbol for faster lookup - std::map<std::string, std::shared_ptr<Symbol>> symbols; + std::vector<std::shared_ptr<Symbol>> symbols; /// insert new symbol into table void insert(const std::shared_ptr<Symbol>& symbol); @@ -149,7 +150,7 @@ namespace symtab { * \class ModelSymbolTable * \brief Represent symbol table for nmodl block * - * SymbolTable is sufficinet to hold information about all symbols in the + * SymbolTable is sufficient to hold information about all symbols in the * mod file. It might be sufficient to keep track of global symbol tables * and local symbol tables. But we construct symbol table using visitor * pass. In this case we visit ast and recursively create symbol table for @@ -167,24 +168,40 @@ namespace symtab { /// symbol table for mod file (always top level symbol table) std::shared_ptr<SymbolTable> symtab = nullptr; - /// symbol table for parent block (used during symbol table construction) - SymbolTable* parent_symtab = nullptr; + /// current symbol table being constructed + SymbolTable* current_symtab = nullptr; - /// Return unique name by appending some counter value - std::string get_unique_name(std::string name, AST* node); + /// return unique name by appending some counter value + std::string get_unique_name(std::string name, AST* node, bool is_global); /// name of top level global symbol table - const std::string GLOBAL_SYMTAB_BANE = "NMODL_GLOBAL"; + const std::string GLOBAL_SYMTAB_NAME = "NMODL_GLOBAL"; + + /// default mode of symbol table: if update is true then we update exisiting + /// symbols otherwise we throw away old table and construct new one + bool update_table = false; + + /// insert symbol table in update mode + void update_mode_insert(const std::shared_ptr<Symbol>& symbol); + + void emit_message(const std::shared_ptr<Symbol>& first, + const std::shared_ptr<Symbol>& second, + bool redefinition); public: /// entering into new nmodl block - SymbolTable* enter_scope(std::string name, AST* node, bool global); + SymbolTable* enter_scope(std::string name, + AST* node, + bool global, + SymbolTable* node_symtab); /// leaving current nmodl block void leave_scope(); + /// insert new symbol into current table void insert(const std::shared_ptr<Symbol>& symbol); + /// lookup for symbol into current as well as all parent tables std::shared_ptr<Symbol> lookup(const std::string& name); /// pretty print @@ -192,10 +209,7 @@ namespace symtab { /// re-initialize members to throw away old symbol tables /// this is required as symtab visitor pass runs multiple time - void initialize() { - symtab = nullptr; - parent_symtab = nullptr; - } + void set_mode(bool mode); }; } // namespace symtab diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 08c2ae3b03..c18d412603 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -17,7 +17,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { symtab = current_symtab; } - // Some statetements like forall, from, while are of type expression statement type. + // Some statements like forall, from, while are of type expression statement type. // These statements contain statement block but do not have symbol table. And hence // we push last non-null symbol table on the stack. symtab_stack.push(symtab); @@ -27,34 +27,37 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { item->visit_children(this); } - auto variables = get_local_variables(node); + /// go back to previous block in hierarchy + symtab = symtab_stack.top(); + symtab_stack.pop(); SymbolTable* parent_symtab = nullptr; if (symtab != nullptr) { parent_symtab = symtab->get_parent_table(); } + auto variables = get_local_variables(node); + /// global blocks do not change (do no have parent symbol table) /// if no variables in the block then there is nothing to do if (parent_symtab == nullptr || variables == nullptr) { return; } + RenameVisitor rename_visitor; for (auto& var : *variables) { std::string name = var->get_name(); auto s = parent_symtab->lookup_in_scope(name); - - /// if symbol represents variable (to avoid renaming use of units like mV) + /// if symbol is a variable name (avoid renaming use of units like mV) if (s && s->is_variable()) { std::string new_name = get_new_name(name, "r", renamed_variables); rename_visitor.set(name, new_name); rename_visitor.visit_statement_block(node); + auto symbol = symtab->lookup_in_scope(name); + symbol->set_name(new_name); + symbol->renamed(); } } - - /// go back to previous block in hierarchy - symtab = symtab_stack.top(); - symtab_stack.pop(); } diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 7cc2f3a1e0..3d3cc391db 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -98,7 +98,7 @@ void LocalizeVisitor::visit_program(Program* node) { } auto variables = variables_to_optimize(); - for (const auto& variable : variables) { + for (const auto& varname : variables) { auto blocks = node->blocks; std::map<DUState, std::vector<std::shared_ptr<ast::Node>>> block_usage; @@ -106,7 +106,7 @@ void LocalizeVisitor::visit_program(Program* node) { for (auto& block : blocks) { if (node_for_def_use_analysis(block.get())) { DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); - auto usages = v.analyze(block.get(), variable); + auto usages = v.analyze(block.get(), varname); auto result = usages.eval(); block_usage[result].push_back(block); } @@ -120,9 +120,18 @@ void LocalizeVisitor::visit_program(Program* node) { for (auto& block : block_usage[DUState::D]) { auto block_ptr = dynamic_cast<Block*>(block.get()); auto statement_block = block_ptr->get_statement_block(); - add_local_variable(statement_block.get(), variable); - auto symbol = program_symtab->lookup(variable); + auto variable = add_local_variable(statement_block.get(), varname); + + /// mark variable as localized in global symbol table + auto symbol = program_symtab->lookup(varname); symbol->localized(); + + /// insert new symbol into symbol table + auto symtab = statement_block->get_symbol_table(); + auto new_symbol = std::make_shared<Symbol>(varname, variable); + new_symbol->add_property(NmodlInfo::local_var); + new_symbol->created(); + symtab->insert(new_symbol); } } } diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index dc64b01255..271e4c29fd 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -54,85 +54,82 @@ int main(int argc, const char* argv[]) { auto ast = driver.ast(); { - /// run basic AST visitor AstVisitor v; v.visit_program(ast.get()); - std::cout << "----AST VISITOR FINISHED----" << std::endl; } { - /// run basic Verbatim visitor /// constructor takes true/false argument for printing blocks VerbatimVisitor v; v.visit_program(ast.get()); - std::cout << "----VERBATIM VISITOR FINISHED----" << std::endl; } { std::stringstream ss; JSONVisitor v(ss); - - /// to get compact json we can set compact mode - /// v.compact_json(true); - v.visit_program(ast.get()); - - std::cout << "----JSON VISITOR FINISHED----" << std::endl; - if (verbose) { - std::cout << "RESULT OF JSON VISITOR : " << std::endl; - std::cout << ss.str(); + std::cout << "RESULT OF JSON VISITOR : " << std::endl << ss.str(); } + std::cout << "----JSON VISITOR FINISHED----" << std::endl; } { - // todo : we should pass this or use get method to retrieve? - std::stringstream ss1; - - SymtabVisitor v(ss1); + SymtabVisitor v(false); v.visit_program(ast.get()); + } - // std::cout << ss1.str(); - - std::stringstream ss2; + { + std::stringstream stream; auto symtab = ast->get_model_symbol_table(); - symtab->print(ss2); - std::cout << ss2.str(); - + symtab->print(stream); + std::cout << stream.str(); std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; } + { + NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); + v.visit_program(ast.get()); + } + { CnexpSolveVisitor v; v.visit_program(ast.get()); std::cout << "----CNEXP SOLVE VISITOR FINISHED----" << std::endl; } - { NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.mod"); v.visit_program(ast.get()); } + { - LocalVarRenameVisitor v; + InlineVisitor v; v.visit_program(ast.get()); } + { - InlineVisitor v; + NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.mod"); v.visit_program(ast.get()); } { - SymtabVisitor v; + LocalVarRenameVisitor v; v.visit_program(ast.get()); + std::cout << "----LOCAL VAR RENAME VISITOR FINISHED----" << std::endl; } { - NmodlPrintVisitor v(channel_name + ".nocmodl.in.mod"); + NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.ren.mod"); + v.visit_program(ast.get()); + } + + { + SymtabVisitor v(true); v.visit_program(ast.get()); } @@ -144,7 +141,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.loc.mod"); + NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.ren.loc.mod"); v.visit_program(ast.get()); } @@ -154,19 +151,12 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.loc.ren.mod"); + SymtabVisitor v(true); v.visit_program(ast.get()); } { - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - } - - { - SymtabVisitor v; + NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.ren.loc.ren.mod"); v.visit_program(ast.get()); } @@ -185,7 +175,6 @@ int main(int argc, const char* argv[]) { std::cout << "----PERF VISITOR FINISHED----" << std::endl; } - } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index c9a1e099a6..79442d4f91 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -69,14 +69,14 @@ void SymtabVisitor::setup_symbol_table(T* node, template <typename T> void SymtabVisitor::setup_program_symbol_table(T* node, std::string name, bool is_global) { modsymtab = node->get_model_symbol_table(); - modsymtab->initialize(); + modsymtab->set_mode(update); setup_symbol_table(node, name, is_global); } template <typename T> void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global) { /// entering into new nmodl block - auto symtab = modsymtab->enter_scope(name, node, is_global); + auto symtab = modsymtab->enter_scope(name, node, is_global, node->get_symbol_table()); if (node->is_state_block()) { state_block = true; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 9f8699b74e..b1b703848b 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -36,15 +36,17 @@ void add_local_statement(StatementBlock* node) { } } -void add_local_variable(ast::StatementBlock* node, ast::Identifier* varname) { +LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname) { add_local_statement(node); auto local_variables = get_local_variables(node); - local_variables->push_back(std::make_shared<LocalVar>(varname)); + auto var = std::make_shared<LocalVar>(varname); + local_variables->push_back(var); + return var.get(); } -void add_local_variable(ast::StatementBlock* node, const std::string& varname) { +LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname) { auto name = new ast::Name(new ast::String(varname)); - add_local_variable(node, name); + return add_local_variable(node, name); } /** diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index ee592d94ed..661d100954 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -20,8 +20,8 @@ ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); void add_local_statement(ast::StatementBlock* node); /** Add new local variable to the block */ -void add_local_variable(ast::StatementBlock* node, const std::string& varname); -void add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); +LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); +LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); /** Create ast statement node from given code in string format */ std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 7733086a00..4bf9947181 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -189,14 +189,18 @@ SCENARIO("Symbol table operations") { SCENARIO("Model symbol table operations") { GIVEN("A Model symbolTable") { ModelSymbolTable mod_symtab; + auto program = std::make_shared<ast::Program>(); auto symbol1 = std::make_shared<Symbol>("alpha", ModToken()); auto symbol2 = std::make_shared<Symbol>("alpha", ModToken()); auto symbol3 = std::make_shared<Symbol>("alpha", ModToken()); + symbol1->add_property(NmodlInfo::param_assign); symbol2->add_property(NmodlInfo::range_var); symbol3->add_property(NmodlInfo::range_var); + SymbolTable* old_symtab = nullptr; + WHEN("trying to exit scope without entering") { THEN("throws an exception") { REQUIRE_THROWS_WITH(mod_symtab.leave_scope(), Catch::Contains("without entering")); @@ -204,7 +208,7 @@ SCENARIO("Model symbol table operations") { } WHEN("trying to enter scope without valid node") { THEN("throws an exception") { - REQUIRE_THROWS_WITH(mod_symtab.enter_scope("scope", nullptr, true), + REQUIRE_THROWS_WITH(mod_symtab.enter_scope("scope", nullptr, true, old_symtab), Catch::Contains("empty node")); } } @@ -217,15 +221,15 @@ SCENARIO("Model symbol table operations") { WHEN("enter scope multipel times") { auto program1 = std::make_shared<ast::Program>(); auto program2 = std::make_shared<ast::Program>(); - mod_symtab.enter_scope("scope1", program1.get(), false); - mod_symtab.enter_scope("scope2", program2.get(), false); + mod_symtab.enter_scope("scope1", program1.get(), false, old_symtab); + mod_symtab.enter_scope("scope2", program2.get(), false, old_symtab); THEN("can leave scope multiple times") { mod_symtab.leave_scope(); mod_symtab.leave_scope(); } } WHEN("added same symbol with different properties in global scope") { - mod_symtab.enter_scope("scope", program.get(), true); + mod_symtab.enter_scope("scope", program.get(), true, old_symtab); mod_symtab.insert(symbol1); mod_symtab.insert(symbol2); THEN("only one symbol gets added with combined properties") { @@ -235,18 +239,18 @@ SCENARIO("Model symbol table operations") { } } WHEN("added same symbol with exisiting property") { - mod_symtab.enter_scope("scope", program.get(), true); + mod_symtab.enter_scope("scope", program.get(), true, old_symtab); mod_symtab.insert(symbol1); mod_symtab.insert(symbol2); THEN("throws an exception") { - REQUIRE_THROWS_WITH(mod_symtab.insert(symbol3), Catch::Contains("re-declaration")); + REQUIRE_THROWS_WITH(mod_symtab.insert(symbol3), Catch::Contains("Re-declaration")); } } WHEN("added same symbol in children scope") { - mod_symtab.enter_scope("scope1", program.get(), true); + mod_symtab.enter_scope("scope1", program.get(), true, old_symtab); mod_symtab.insert(symbol2); THEN("it's ok, just get overshadow warning") { - mod_symtab.enter_scope("scope2", program.get(), false); + mod_symtab.enter_scope("scope2", program.get(), false, old_symtab); mod_symtab.insert(symbol3); ///\todo : not sure how to capture std::cout } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 3f55e20714..a085aabf6a 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -534,9 +534,9 @@ SCENARIO("Variable renaming in nested blocks") { } BREAKPOINT { - LOCAL gNaTs2_t_r_1 - gNaTs2_t_r_1 = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t_r_1*(v-ena) + LOCAL gNaTs2_t + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) { LOCAL gNaTs2_t_r_0, h_r_1 gNaTs2_t_r_0 = m+h_r_1 @@ -551,8 +551,8 @@ SCENARIO("Variable renaming in nested blocks") { } PROCEDURE rates() { - LOCAL x_r_1, m_r_2 - m_r_2 = x_r_1+gNaTs2_tbar + LOCAL x, m_r_2 + m_r_2 = x+gNaTs2_tbar { { LOCAL h_r_2, x_r_0, gNaTs2_tbar_r_0 From 22985dc339e0306d32654b6ff1ea6a8555ea4486 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 2 Apr 2018 16:43:08 +0200 Subject: [PATCH 062/871] Draft implementation of C lexer and parser for parsing verbatim blocks Change-Id: Iafe6ef9626ac7ceb4a2b53c1325c88fc00f712ba NMODL Repo SHA: BlueBrain/nmodl@380cdaa238357c7455085fdf6c10fec3ed882afe --- src/nmodl/lexer/CMakeLists.txt | 40 +- src/nmodl/lexer/c11.ll | 580 +++++++++++++++ src/nmodl/lexer/c11_lexer.hpp | 64 ++ src/nmodl/lexer/main_c.cpp | 56 ++ src/nmodl/lexer/{main.cpp => main_nmodl.cpp} | 0 src/nmodl/parser/CMakeLists.txt | 7 +- src/nmodl/parser/c11.yy | 687 ++++++++++++++++++ src/nmodl/parser/c11_driver.cpp | 55 ++ src/nmodl/parser/c11_driver.hpp | 83 +++ src/nmodl/parser/main_c.cpp | 47 ++ src/nmodl/parser/{main.cpp => main_nmodl.cpp} | 0 src/nmodl/symtab/symbol_table.cpp | 1 - 12 files changed, 1615 insertions(+), 5 deletions(-) create mode 100644 src/nmodl/lexer/c11.ll create mode 100644 src/nmodl/lexer/c11_lexer.hpp create mode 100644 src/nmodl/lexer/main_c.cpp rename src/nmodl/lexer/{main.cpp => main_nmodl.cpp} (100%) create mode 100644 src/nmodl/parser/c11.yy create mode 100644 src/nmodl/parser/c11_driver.cpp create mode 100644 src/nmodl/parser/c11_driver.hpp create mode 100644 src/nmodl/parser/main_c.cpp rename src/nmodl/parser/{main.cpp => main_nmodl.cpp} (100%) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 2da3c75e2b..705b1a3dc1 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -5,6 +5,7 @@ set (BISON_GENERATED_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp ) set(AST_SOURCE_FILES @@ -25,6 +26,11 @@ set(DIFFEQ_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp ) +set(C_DRIVER_FILES + ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp + ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp +) + set_source_files_properties( ${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE @@ -41,8 +47,10 @@ set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.cpp ${NMODL_DRIVER_FILES} ${DIFFEQ_DRIVER_FILES} + ${C_DRIVER_FILES} ) #============================================================================= @@ -50,6 +58,7 @@ set(LEXER_SOURCE_FILES #============================================================================= file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/nmodl) file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/diffeq) +file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/c) #============================================================================= # Lexer & Parser commands @@ -97,6 +106,17 @@ add_custom_command ( COMMENT "-- NOCMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --" ) +# command to generate C (11) parser +add_custom_command ( + COMMAND ${BISON_EXECUTABLE} + ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/c11.yy + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.hpp + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy + COMMENT "-- NOCMODL : GENERATING C (11) PARSER WITH BISON! --" +) + # command to generate nmodl lexer add_custom_command( COMMAND ${FLEX_EXECUTABLE} @@ -131,6 +151,17 @@ add_custom_command( COMMENT "-- NOCMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --" ) +# command to generate C (11) lexer +add_custom_command( + COMMAND ${FLEX_EXECUTABLE} + ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.cpp + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.hpp + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + COMMENT "-- NOCMODL : GENERATING C(11) LEXER WITH FLEX! --" +) + #============================================================================= # Libraries & executables #============================================================================= @@ -140,16 +171,21 @@ add_library(lexer ${BISON_GENERATED_SOURCE_FILES} ${AST_SOURCE_FILES}) -add_executable(nocmodl_lexer main.cpp) +add_executable(nocmodl_lexer main_nmodl.cpp) +add_executable(c_lexer main_c.cpp) + target_link_libraries(nocmodl_lexer lexer) +target_link_libraries(c_lexer lexer) #============================================================================= # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT ${LEXER_SOURCE_FILES} - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/main_nmodl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/main_c.cpp ${DIFFEQ_DRIVER_FILES} + ${C_DRIVER_FILES} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) \ No newline at end of file diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll new file mode 100644 index 0000000000..86782f94e8 --- /dev/null +++ b/src/nmodl/lexer/c11.ll @@ -0,0 +1,580 @@ +%{ + #include <iostream> + + #include "lexer/c11_lexer.hpp" + #include "parser/c11_driver.hpp" + #include "parser/c/c11_parser.hpp" + #include "utils/string_utils.hpp" + + /** YY_USER_ACTION is called before each of token actions and + * we update columns by length of the token. Node that position + * starts at column 1 and increasing by yyleng gives extra position + * larger than exact columns span of the token. Hence in ModToken + * class we reduce the length by one column. */ + #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); driver.process(yytext); } + + /** By default yylex returns int, we use token_type. Unfortunately + * yyterminate by default returns 0, which is not of token_type. */ + #define yyterminate() return c11::Parser::make_END(loc); + + /** Disables inclusion of unistd.h, which is not available under Visual + * C++ on Win32. The C++ scanner uses STL streams instead. */ + #define YY_NO_UNISTD_H + +%} + +%e 1019 +%p 2807 +%n 371 +%k 284 +%a 1213 +%o 1117 + +O [0-7] +D [0-9] +NZ [1-9] +L [a-zA-Z_] +A [a-zA-Z_0-9] +H [a-fA-F0-9] +HP (0[xX]) +E ([Ee][+-]?{D}+) +P ([Pp][+-]?{D}+) +FS (f|F|l|L) +IS (((u|U)(l|L|ll|LL)?)|((l|L|ll|LL)(u|U)?)) +CP (u|U|L) +SP (u8|u|U|L) +ES (\\(['"\?\\abfnrtv]|[0-7]{1,3}|x[a-fA-F0-9]+)) +WS [ \t\v\n\f] + +/** we do use yymore feature in copy modes */ +%option yymore + +/** name of the lexer header file */ +%option header-file="c11_base_lexer.hpp" + +/** name of the lexer implementation file */ +%option outfile="c11_base_lexer.cpp" + +/** change the name of the scanner class (to "C11FlexLexer") */ +%option prefix="C11" + +/** enable C++ scanner which is also reentrant */ +%option c++ + +/** no plan for include files for now */ +%option noyywrap + +/** need to unput characters back to buffer for custom routines */ +%option unput + +/** need to put in buffer for custom routines */ +%option input + +/** not an interactive lexer, takes a file instead */ +%option batch + +/** enable debug mode (disable for production) */ +%option debug + +/** instructs flex to generate an 8-bit scanner, i.e., + * one which can recognize 8-bit characters. */ +%option 8bit + +/** show warning messages */ +%option warn + +/* to insure there are no holes in scanner rules */ +%option nodefault + +/* keep line information */ +%option yylineno + +/* enable use of start condition stacks */ +%option stack + +/* mode for preprocessor directive */ +%x P_P_DIRECTIVE + +/* mode for multi-line comment */ +%x COMMENT + + +%% + + +^"#" { + BEGIN(P_P_DIRECTIVE); + } + +<P_P_DIRECTIVE>. { + } + +<P_P_DIRECTIVE>\n { + BEGIN(INITIAL); + } + +"/*" { + BEGIN(COMMENT); + } + +<COMMENT>"*/" { + BEGIN(INITIAL); + } + +<COMMENT>. { + } + +<COMMENT>\n { + loc.lines(1); + } + +"//".* { + /* consume //-comment */ + } + +"auto" { + return c11::Parser::make_AUTO(yytext, loc); + } + +"break" { + return c11::Parser::make_BREAK(yytext, loc); + } + +"case" { + return c11::Parser::make_CASE(yytext, loc); + } + +"char" { + return c11::Parser::make_CHAR(yytext, loc); + } + +"const" { + return c11::Parser::make_CONST(yytext, loc); + } + +"continue" { + return c11::Parser::make_CONTINUE(yytext, loc); + } + +"default" { + return c11::Parser::make_DEFAULT(yytext, loc); + } + +"do" { + return c11::Parser::make_DO(yytext, loc); + } + +"double" { + return c11::Parser::make_DOUBLE(yytext, loc); + } + +"else" { + return c11::Parser::make_ELSE(yytext, loc); + } + +"enum" { + return c11::Parser::make_ENUM(yytext, loc); + } + +"extern" { + return c11::Parser::make_EXTERN(yytext, loc); + } + +"float" { + return c11::Parser::make_FLOAT(yytext, loc); + } + +"for" { + return c11::Parser::make_FOR(yytext, loc); + } + +"goto" { + return c11::Parser::make_GOTO(yytext, loc); + } + +"if" { + return c11::Parser::make_IF(yytext, loc); + } + +"inline" { + return c11::Parser::make_INLINE(yytext, loc); + } + +"int" { + return c11::Parser::make_INT(yytext, loc); + } + +"long" { + return c11::Parser::make_LONG(yytext, loc); + } + +"register" { + return c11::Parser::make_REGISTER(yytext, loc); + } + +"restrict" { + return c11::Parser::make_RESTRICT(yytext, loc); + } + +"return" { + return c11::Parser::make_RETURN(yytext, loc); + } + +"short" { + return c11::Parser::make_SHORT(yytext, loc); + } + +"signed" { + return c11::Parser::make_SIGNED(yytext, loc); + } + +"sizeof" { + return c11::Parser::make_SIZEOF(yytext, loc); + } + +"static" { + return c11::Parser::make_STATIC(yytext, loc); + } + +"struct" { + return c11::Parser::make_STRUCT(yytext, loc); + } + +"switch" { + return c11::Parser::make_SWITCH(yytext, loc); + } + +"typedef" { + return c11::Parser::make_TYPEDEF(yytext, loc); + } + +"union" { + return c11::Parser::make_UNION(yytext, loc); + } + +"unsigned" { + return c11::Parser::make_UNSIGNED(yytext, loc); + } + +"void" { + return c11::Parser::make_VOID(yytext, loc); + } + +"volatile" { + return c11::Parser::make_VOLATILE(yytext, loc); + } + +"while" { + return c11::Parser::make_WHILE(yytext, loc); + } + +"_Alignas" { + return c11::Parser::make_ALIGNAS(yytext, loc); + } + +"_Alignof" { + return c11::Parser::make_ALIGNOF(yytext, loc); + } + +"_Atomic" { + return c11::Parser::make_ATOMIC(yytext, loc); + } + +"_Bool" { + return c11::Parser::make_BOOL(yytext, loc); + } + +"_Complex" { + return c11::Parser::make_COMPLEX(yytext, loc); + } + +"_Generic" { + return c11::Parser::make_GENERIC(yytext, loc); + } + +"_Imaginary" { + return c11::Parser::make_IMAGINARY(yytext, loc); + } + +"_Noreturn" { + return c11::Parser::make_NORETURN(yytext, loc); + } + +"_Static_assert" { + return c11::Parser::make_STATIC_ASSERT(yytext, loc); + } + +"_Thread_local" { + return c11::Parser::make_THREAD_LOCAL(yytext, loc); + } + +"__func__" { + return c11::Parser::make_FUNC_NAME(yytext, loc); + } + +{L}{A}* { + return check_type(); + } + +{HP}{H}+{IS}? { + return c11::Parser::make_I_CONSTANT(yytext, loc); + } + +{NZ}{D}*{IS}? { + return c11::Parser::make_I_CONSTANT(yytext, loc); + } + +"0"{O}*{IS}? { + return c11::Parser::make_I_CONSTANT(yytext, loc); + } + +{CP}?"'"([^'\\\n]|{ES})+"'" { + return c11::Parser::make_I_CONSTANT(yytext, loc); + } + +{D}+{E}{FS}? { + return c11::Parser::make_F_CONSTANT(yytext, loc); + } + +{D}*"."{D}+{E}?{FS}? { + return c11::Parser::make_F_CONSTANT(yytext, loc); + } + +{D}+"."{E}?{FS}? { + return c11::Parser::make_F_CONSTANT(yytext, loc); + } + +{HP}{H}+{P}{FS}? { + return c11::Parser::make_F_CONSTANT(yytext, loc); + } + +{HP}{H}*"."{H}+{P}{FS}? { + return c11::Parser::make_F_CONSTANT(yytext, loc); + } + +{HP}{H}+"."{P}{FS}? { + return c11::Parser::make_F_CONSTANT(yytext, loc); + } + +({SP}?\"([^"\\\n]|{ES})*\"{WS}*)+ { + return c11::Parser::make_STRING_LITERAL(yytext, loc); + } + +"..." { + return c11::Parser::make_ELLIPSIS(yytext, loc); + } + +">>=" { + return c11::Parser::make_RIGHT_ASSIGN(yytext, loc); + } + +"<<=" { + return c11::Parser::make_LEFT_ASSIGN(yytext, loc); + } + +"+=" { + return c11::Parser::make_ADD_ASSIGN(yytext, loc); + } + +"-=" { + return c11::Parser::make_SUB_ASSIGN(yytext, loc); + } + +"*=" { + return c11::Parser::make_MUL_ASSIGN(yytext, loc); + } + +"/=" { + return c11::Parser::make_DIV_ASSIGN(yytext, loc); + } + +"%=" { + return c11::Parser::make_MOD_ASSIGN(yytext, loc); + } + +"&=" { + return c11::Parser::make_AND_ASSIGN(yytext, loc); + } + +"^=" { + return c11::Parser::make_XOR_ASSIGN(yytext, loc); + } + +"|=" { + return c11::Parser::make_OR_ASSIGN(yytext, loc); + } + +">>" { + return c11::Parser::make_RIGHT_OP(yytext, loc); + } + +"<<" { + return c11::Parser::make_LEFT_OP(yytext, loc); + } + +"++" { + return c11::Parser::make_INC_OP(yytext, loc); + } + +"--" { + return c11::Parser::make_DEC_OP(yytext, loc); + } + +"->" { + return c11::Parser::make_PTR_OP(yytext, loc); + } + +"&&" { + return c11::Parser::make_AND_OP(yytext, loc); + } + +"||" { + return c11::Parser::make_OR_OP(yytext, loc); + } + +"<=" { + return c11::Parser::make_LE_OP(yytext, loc); + } + +">=" { + return c11::Parser::make_GE_OP(yytext, loc); + } + +"==" { + return c11::Parser::make_EQ_OP(yytext, loc); + } + +"!=" { + return c11::Parser::make_NE_OP(yytext, loc); + } + +";" { + return c11::Parser::make_SEMICOLON(yytext, loc); + } + +("{"|"<%") { + return c11::Parser::make_OPEN_BRACE(yytext, loc); + } + +("}"|"%>") { + return c11::Parser::make_CLOSE_BRACE(yytext, loc); + } + +"," { + return c11::Parser::make_COMMA(yytext, loc); + } + +":" { + return c11::Parser::make_COLON(yytext, loc); + } + +"=" { + return c11::Parser::make_ASSIGN(yytext, loc); + } + +"(" { + return c11::Parser::make_OPEN_PARENTHESIS(yytext, loc); + } + +")" { + return c11::Parser::make_CLOSE_PARENTHESIS(yytext, loc); + } + +("["|"<:") { + return c11::Parser::make_OPEN_BRACKET(yytext, loc); + } + +("]"|":>") { + return c11::Parser::make_CLOSE_BRACKET(yytext, loc); + } + +"." { + return c11::Parser::make_PERIOD(yytext, loc); + } + +"&" { + return c11::Parser::make_AMPERSTAND(yytext, loc); + } + +"!" { + return c11::Parser::make_NEGATION(yytext, loc); + } + +"~" { + return c11::Parser::make_NEGATION(yytext, loc); + } + +"-" { + return c11::Parser::make_MINUS(yytext, loc); + } + +"+" { + return c11::Parser::make_ADD(yytext, loc); + } + +"*" { + return c11::Parser::make_MULTIPLY(yytext, loc); + } + +"/" { + return c11::Parser::make_DIVIDE(yytext, loc); + } + +"%" { + return c11::Parser::make_PERCENT(yytext, loc); + } + +"<" { + return c11::Parser::make_LT(yytext, loc); + } + +">" { + return c11::Parser::make_GT(yytext, loc); + } + +"^" { + return c11::Parser::make_CARET(yytext, loc); + } + +"|" { + return c11::Parser::make_OR(yytext, loc); + } + +"?" { + return c11::Parser::make_QUESTION(yytext, loc); + } + +{WS}+ { + std::string str(yytext); + stringutils::remove_character(str, ' '); + if (str == "\n") { + loc.lines(1); + } else { + loc.step(); + } + } + +. { + /* discard bad characters */ + yymore(); + } + +%% + + +int C11FlexLexer::yylex() { + throw std::runtime_error("next_token() should be used instead of yylex()"); +} + + +c11::Parser::symbol_type c11::Lexer::check_type() { + if (driver.is_typedef(yytext)) { + return c11::Parser::make_TYPEDEF_NAME(yytext, loc); + } + + if (driver.is_enum_constant(yytext)) { + return c11::Parser::make_ENUMERATION_CONSTANT(yytext, loc); + } + + return c11::Parser::make_IDENTIFIER(yytext, loc); +} diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp new file mode 100644 index 0000000000..479cdf724e --- /dev/null +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -0,0 +1,64 @@ +#ifndef C11_LEXER_HPP +#define C11_LEXER_HPP + +#include "parser/c/c11_parser.hpp" + +/** Flex expects the declaration of yylex to be defined in the macro YY_DECL + * and C++ parser class expects it to be declared. */ +#ifndef YY_DECL +#define YY_DECL c11::Parser::symbol_type c11::Lexer::next_token() +#endif + +/** For creating multiple (different) lexer classes, we can use '-P' flag + * (or prefix option) to rename each yyFlexLexer to some other name like + * ‘xxFlexLexer’. And then include <FlexLexer.h> in other sources once per + * lexer class, first renaming yyFlexLexer as shown below. */ +#ifndef __FLEX_LEXER_H +#define yyFlexLexer C11FlexLexer +#include "FlexLexer.h" +#endif + +namespace c11 { + + /** + * \class Lexer + * \brief Represent Lexer/Scanner class for C (11) language parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * Flex itself creates yyFlexLexer class, which we renamed using macros to + * C11FlexLexer. But we change the context of the generated yylex() function + * because the yylex() defined in C11FlexLexer has no parameters. Also, note + * that implementation of the member functions are in c11.ll file due to use + * of macros. */ + class Lexer : public C11FlexLexer { + public: + /** Reference to driver object which contains this lexer instance. This is + * used for error reporting and checking macro definitions. */ + Driver& driver; + + /// For tracking location of the tokens + location loc; + + /** The streams in and out default to cin and cout, but that assignment + * is only made when initializing in yylex(). */ + explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + : C11FlexLexer(in, out), driver(drv) {} + + ~Lexer() override= default;; + + /** Main lexing function generated by flex according to the macro declaration + * YY_DECL above. The generated bison parser then calls this virtual function + * to fetch new tokens. Note that yylex() has different declaration and hence + * it can't be used for new lexer. */ + virtual Parser::symbol_type next_token(); + + /** Return type of the word : could be typedef, identifier or enum constant */ + Parser::symbol_type check_type(); + + /// consume comment + std::string input_comment(); + }; + +} // namespace c11 + +#endif diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp new file mode 100644 index 0000000000..f4546c33db --- /dev/null +++ b/src/nmodl/lexer/main_c.cpp @@ -0,0 +1,56 @@ +#include <fstream> +#include <iostream> + +#include "lexer/c11_lexer.hpp" +#include "parser/c11_driver.hpp" +#include "tclap/CmdLine.h" + +/** + * Standlone lexer program for C. This demonstrate basic + * usage of scanner and driver class. + */ + +int main(int argc, const char* argv[]) { + try { + TCLAP::CmdLine cmd("C Lexer: Standalone lexer program for C"); + TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, + "../test/input/channel.c", "string"); + + cmd.add(filearg); + cmd.parse(argc, argv); + + std::string filename = filearg.getValue(); + std::ifstream file(filename); + + if (!file) { + throw std::runtime_error("Could not open file " + filename); + } + + std::cout << "\n C Lexer : Processing file : " << filename << std::endl; + + std::istream& in(file); + c11::Driver driver; + c11::Lexer scanner(driver, &in); + + using Token = c11::Parser::token; + using TokenType = c11::Parser::token_type; + using SymbolType = c11::Parser::symbol_type; + + /// parse C file untile EOF, print each token + while (true) { + SymbolType sym = scanner.next_token(); + TokenType token = sym.token(); + + /// end of file + if (token == Token::END) { + break; + } + std::cout << sym.value.as<std::string>(); + } + } catch (TCLAP::ArgException& e) { + std::cout << std::endl << "Argument Error: " << e.error() << " for arg " << e.argId(); + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/src/nmodl/lexer/main.cpp b/src/nmodl/lexer/main_nmodl.cpp similarity index 100% rename from src/nmodl/lexer/main.cpp rename to src/nmodl/lexer/main_nmodl.cpp diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 17b3bf8092..772a9126d5 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -5,14 +5,17 @@ # lexer library links with all parser related files # so no eed to have parser as a separate library -add_executable(nocmodl_parser main.cpp) +add_executable(nocmodl_parser main_nmodl.cpp) +add_executable(c_parser main_c.cpp) + target_link_libraries(nocmodl_parser lexer) +target_link_libraries(c_parser lexer) #============================================================================= # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/main_nmodl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_context.hpp ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_context.hpp ${FILES_FOR_CLANG_FORMAT} diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy new file mode 100644 index 0000000000..f497ed1257 --- /dev/null +++ b/src/nmodl/parser/c11.yy @@ -0,0 +1,687 @@ +/****************************************************************************** + * + * @brief Bison grammar and parser implementation for C (11) + * + * NMODL has verbatim constructs that allow to specify C code sections within + * nmodl implementation. + *****************************************************************************/ + +%code requires +{ + #include <string> + #include "parser/c11_driver.hpp" +} + +/** use C++ parser interface of bison */ +%skeleton "lalr1.cc" + +/** require modern bison version */ +%require "3.0.2" + +/** print verbose error messages instead of just message 'syntax error' */ +%define parse.error verbose + +/** enable tracing parser for debugging */ +%define parse.trace + +/** add extra arguments to yyparse() and yylexe() methods */ +%parse-param {class Lexer& scanner} +%parse-param {class Driver& driver} +%lex-param {c11::Scanner &scanner} +%lex-param {c11::Driver &driver} + +/** use variant based implementation of semantic values */ +%define api.value.type variant + +/** assert correct cleanup of semantic value objects */ +%define parse.assert + +/** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ +%define api.token.constructor + +/** namespace to enclose parser */ +%name-prefix "c11" + +/** set the parser's class identifier */ +%define parser_class_name {Parser} + +/** keep track of the current position within the input */ +%locations + +%token <std::string> IDENTIFIER +%token <std::string> I_CONSTANT +%token <std::string> F_CONSTANT +%token <std::string> STRING_LITERAL +%token <std::string> FUNC_NAME +%token <std::string> SIZEOF +%token <std::string> PTR_OP +%token <std::string> INC_OP +%token <std::string> DEC_OP +%token <std::string> LEFT_OP +%token <std::string> RIGHT_OP +%token <std::string> LE_OP +%token <std::string> GE_OP +%token <std::string> EQ_OP +%token <std::string> NE_OP +%token <std::string> AND_OP +%token <std::string> OR_OP +%token <std::string> MUL_ASSIGN +%token <std::string> DIV_ASSIGN +%token <std::string> MOD_ASSIGN +%token <std::string> ADD_ASSIGN +%token <std::string> SUB_ASSIGN +%token <std::string> LEFT_ASSIGN +%token <std::string> RIGHT_ASSIGN +%token <std::string> AND_ASSIGN +%token <std::string> XOR_ASSIGN +%token <std::string> OR_ASSIGN +%token <std::string> TYPEDEF_NAME +%token <std::string> ENUMERATION_CONSTANT + +%token <std::string> TYPEDEF +%token <std::string> EXTERN +%token <std::string> STATIC +%token <std::string> AUTO +%token <std::string> REGISTER +%token <std::string> INLINE +%token <std::string> CONST +%token <std::string> RESTRICT +%token <std::string> VOLATILE +%token <std::string> BOOL +%token <std::string> CHAR +%token <std::string> SHORT +%token <std::string> INT +%token <std::string> LONG +%token <std::string> SIGNED +%token <std::string> UNSIGNED +%token <std::string> FLOAT +%token <std::string> DOUBLE +%token <std::string> VOID +%token <std::string> COMPLEX +%token <std::string> IMAGINARY +%token <std::string> STRUCT +%token <std::string> UNION +%token <std::string> ENUM +%token <std::string> ELLIPSIS + +%token <std::string> CASE +%token <std::string> DEFAULT +%token <std::string> IF +%token <std::string> ELSE +%token <std::string> SWITCH +%token <std::string> WHILE +%token <std::string> DO +%token <std::string> FOR +%token <std::string> GOTO +%token <std::string> CONTINUE +%token <std::string> BREAK +%token <std::string> RETURN + +%token <std::string> ALIGNAS +%token <std::string> ALIGNOF +%token <std::string> ATOMIC +%token <std::string> GENERIC +%token <std::string> NORETURN +%token <std::string> STATIC_ASSERT +%token <std::string> THREAD_LOCAL + +%token <std::string> SEMICOLON ";" +%token <std::string> OPEN_BRACE "{" +%token <std::string> CLOSE_BRACE "}" +%token <std::string> COMMA "," +%token <std::string> COLON ":" +%token <std::string> ASSIGN "=" +%token <std::string> OPEN_PARENTHESIS "(" +%token <std::string> CLOSE_PARENTHESIS ")" +%token <std::string> OPEN_BRACKET "[" +%token <std::string> CLOSE_BRACKET "]" +%token <std::string> PERIOD "." +%token <std::string> AMPERSTAND "&" +%token <std::string> NEGATION "!" +%token <std::string> TILDE "~" +%token <std::string> ADD "+" +%token <std::string> MULTIPLY "*" +%token <std::string> MINUS "-" +%token <std::string> DIVIDE "/" +%token <std::string> PERCENT "%" +%token <std::string> LT "<" +%token <std::string> GT ">" +%token <std::string> CARET "^" +%token <std::string> OR "|" +%token <std::string> QUESTION "?" +%token END 0 "End of file" + +%{ + #include "lexer/c11_lexer.hpp" + #include "parser/c11_driver.hpp" + + /// yylex takes scanner as well as driver reference + /// \todo: check if driver argument is required + static c11::Parser::symbol_type yylex(c11::Lexer &scanner, c11::Driver &/*driver*/) { + return scanner.next_token(); + } +%} + + +%start translation_unit + +%% + +primary_expression + : IDENTIFIER + | constant + | string + | "(" expression ")" + | generic_selection + ; + +constant + : I_CONSTANT /* includes character_constant */ + | F_CONSTANT + | ENUMERATION_CONSTANT /* after it has been defined as such */ + ; + +enumeration_constant /* before it has been defined as such */ + : IDENTIFIER + ; + +string + : STRING_LITERAL + | FUNC_NAME + ; + +generic_selection + : GENERIC "(" assignment_expression "," generic_assoc_list ")" + ; + +generic_assoc_list + : generic_association + | generic_assoc_list "," generic_association + ; + +generic_association + : type_name ":" assignment_expression + | DEFAULT ":" assignment_expression + ; + +postfix_expression + : primary_expression + | postfix_expression "[" expression "]" + | postfix_expression "(" ")" + | postfix_expression "(" argument_expression_list ")" + | postfix_expression "." IDENTIFIER + | postfix_expression PTR_OP IDENTIFIER + | postfix_expression INC_OP + | postfix_expression DEC_OP + | "(" type_name ")" "{" initializer_list "}" + | "(" type_name ")" "{" initializer_list "," "}" + ; + +argument_expression_list + : assignment_expression + | argument_expression_list "," assignment_expression + ; + +unary_expression + : postfix_expression + | INC_OP unary_expression + | DEC_OP unary_expression + | unary_operator cast_expression + | SIZEOF unary_expression + | SIZEOF "(" type_name ")" + | ALIGNOF "(" type_name ")" + ; + +unary_operator + : "&" + | "*" + | "+" + | "-" + | "~" + | "!" + ; + +cast_expression + : unary_expression + | "(" type_name ")" cast_expression + ; + +multiplicative_expression + : cast_expression + | multiplicative_expression "*" cast_expression + | multiplicative_expression "/" cast_expression + | multiplicative_expression "%" cast_expression + ; + +additive_expression + : multiplicative_expression + | additive_expression "+" multiplicative_expression + | additive_expression "-" multiplicative_expression + ; + +shift_expression + : additive_expression + | shift_expression LEFT_OP additive_expression + | shift_expression RIGHT_OP additive_expression + ; + +relational_expression + : shift_expression + | relational_expression "<" shift_expression + | relational_expression ">" shift_expression + | relational_expression LE_OP shift_expression + | relational_expression GE_OP shift_expression + ; + +equality_expression + : relational_expression + | equality_expression EQ_OP relational_expression + | equality_expression NE_OP relational_expression + ; + +and_expression + : equality_expression + | and_expression "&" equality_expression + ; + +exclusive_or_expression + : and_expression + | exclusive_or_expression "^" and_expression + ; + +inclusive_or_expression + : exclusive_or_expression + | inclusive_or_expression "|" exclusive_or_expression + ; + +logical_and_expression + : inclusive_or_expression + | logical_and_expression AND_OP inclusive_or_expression + ; + +logical_or_expression + : logical_and_expression + | logical_or_expression OR_OP logical_and_expression + ; + +conditional_expression + : logical_or_expression + | logical_or_expression "?" expression ":" conditional_expression + ; + +assignment_expression + : conditional_expression + | unary_expression assignment_operator assignment_expression + ; + +assignment_operator + : "=" + | MUL_ASSIGN + | DIV_ASSIGN + | MOD_ASSIGN + | ADD_ASSIGN + | SUB_ASSIGN + | LEFT_ASSIGN + | RIGHT_ASSIGN + | AND_ASSIGN + | XOR_ASSIGN + | OR_ASSIGN + ; + +expression + : assignment_expression + | expression "," assignment_expression + ; + +constant_expression + : conditional_expression /* with constraints */ + ; + +declaration + : declaration_specifiers ";" + | declaration_specifiers init_declarator_list ";" + | static_assert_declaration + ; + +declaration_specifiers + : storage_class_specifier declaration_specifiers + { + std::cout << "-------> \n"; + } + | storage_class_specifier + | type_specifier declaration_specifiers + | type_specifier + | type_qualifier declaration_specifiers + | type_qualifier + | function_specifier declaration_specifiers + | function_specifier + | alignment_specifier declaration_specifiers + | alignment_specifier + ; + +init_declarator_list + : init_declarator + | init_declarator_list "," init_declarator + ; + +init_declarator + : declarator "=" initializer + | declarator + ; + +storage_class_specifier + : TYPEDEF /* identifiers must be flagged as TYPEDEF_NAME */ + | EXTERN + | STATIC + | THREAD_LOCAL + | AUTO + | REGISTER + ; + +type_specifier + : VOID + | CHAR + | SHORT + | INT + | LONG + | FLOAT + | DOUBLE + | SIGNED + | UNSIGNED + | BOOL + | COMPLEX + | IMAGINARY /* non-mandated extension */ + | atomic_type_specifier + | struct_or_union_specifier + | enum_specifier + | TYPEDEF_NAME /* after it has been defined as such */ + ; + +struct_or_union_specifier + : struct_or_union "{" struct_declaration_list "}" + | struct_or_union IDENTIFIER "{" struct_declaration_list "}" + | struct_or_union IDENTIFIER + ; + +struct_or_union + : STRUCT + | UNION + ; + +struct_declaration_list + : struct_declaration + | struct_declaration_list struct_declaration + ; + +struct_declaration + : specifier_qualifier_list ";" /* for anonymous struct/union */ + | specifier_qualifier_list struct_declarator_list ";" + | static_assert_declaration + ; + +specifier_qualifier_list + : type_specifier specifier_qualifier_list + | type_specifier + | type_qualifier specifier_qualifier_list + | type_qualifier + ; + +struct_declarator_list + : struct_declarator + | struct_declarator_list "," struct_declarator + ; + +struct_declarator + : ":" constant_expression + | declarator ":" constant_expression + | declarator + ; + +enum_specifier + : ENUM "{" enumerator_list "}" + | ENUM "{" enumerator_list "," "}" + | ENUM IDENTIFIER "{" enumerator_list "}" + | ENUM IDENTIFIER "{" enumerator_list "," "}" + | ENUM IDENTIFIER + ; + +enumerator_list + : enumerator + | enumerator_list "," enumerator + ; + +enumerator /* identifiers must be flagged as ENUMERATION_CONSTANT */ + : enumeration_constant "=" constant_expression + | enumeration_constant + ; + +atomic_type_specifier + : ATOMIC "(" type_name ")" + ; + +type_qualifier + : CONST + | RESTRICT + | VOLATILE + | ATOMIC + ; + +function_specifier + : INLINE + | NORETURN + ; + +alignment_specifier + : ALIGNAS "(" type_name ")" + | ALIGNAS "(" constant_expression ")" + ; + +declarator + : pointer direct_declarator + | direct_declarator + ; + +direct_declarator + : IDENTIFIER + | "(" declarator ")" + | direct_declarator "[" "]" + | direct_declarator "[" "*" "]" + | direct_declarator "[" STATIC type_qualifier_list assignment_expression "]" + | direct_declarator "[" STATIC assignment_expression "]" + | direct_declarator "[" type_qualifier_list "*" "]" + | direct_declarator "[" type_qualifier_list STATIC assignment_expression "]" + | direct_declarator "[" type_qualifier_list assignment_expression "]" + | direct_declarator "[" type_qualifier_list "]" + | direct_declarator "[" assignment_expression "]" + | direct_declarator "(" parameter_type_list ")" + | direct_declarator "(" ")" + | direct_declarator "(" identifier_list ")" + ; + +pointer + : "*" type_qualifier_list pointer + | "*" type_qualifier_list + | "*" pointer + | "*" + ; + +type_qualifier_list + : type_qualifier + | type_qualifier_list type_qualifier + ; + + +parameter_type_list + : parameter_list "," ELLIPSIS + | parameter_list + ; + +parameter_list + : parameter_declaration + | parameter_list "," parameter_declaration + ; + +parameter_declaration + : declaration_specifiers declarator + | declaration_specifiers abstract_declarator + | declaration_specifiers + ; + +identifier_list + : IDENTIFIER + | identifier_list "," IDENTIFIER + ; + +type_name + : specifier_qualifier_list abstract_declarator + | specifier_qualifier_list + ; + +abstract_declarator + : pointer direct_abstract_declarator + | pointer + | direct_abstract_declarator + ; + +direct_abstract_declarator + : "(" abstract_declarator ")" + | "[" "]" + | "[" "*" "]" + | "[" STATIC type_qualifier_list assignment_expression "]" + | "[" STATIC assignment_expression "]" + | "[" type_qualifier_list STATIC assignment_expression "]" + | "[" type_qualifier_list assignment_expression "]" + | "[" type_qualifier_list "]" + | "[" assignment_expression "]" + | direct_abstract_declarator "[" "]" + | direct_abstract_declarator "[" "*" "]" + | direct_abstract_declarator "[" STATIC type_qualifier_list assignment_expression "]" + | direct_abstract_declarator "[" STATIC assignment_expression "]" + | direct_abstract_declarator "[" type_qualifier_list assignment_expression "]" + | direct_abstract_declarator "[" type_qualifier_list STATIC assignment_expression "]" + | direct_abstract_declarator "[" type_qualifier_list "]" + | direct_abstract_declarator "[" assignment_expression "]" + | "(" ")" + | "(" parameter_type_list ")" + | direct_abstract_declarator "(" ")" + | direct_abstract_declarator "(" parameter_type_list ")" + ; + +initializer + : "{" initializer_list "}" + | "{" initializer_list "," "}" + | assignment_expression + ; + +initializer_list + : designation initializer + | initializer + | initializer_list "," designation initializer + | initializer_list "," initializer + ; + +designation + : designator_list "=" + ; + +designator_list + : designator + | designator_list designator + ; + +designator + : "[" constant_expression "]" + | "." IDENTIFIER + ; + +static_assert_declaration + : STATIC_ASSERT "(" constant_expression "," STRING_LITERAL ")" ";" + ; + +statement + : labeled_statement + | compound_statement + | expression_statement + | selection_statement + | iteration_statement + | jump_statement + ; + +labeled_statement + : IDENTIFIER ":" statement + | CASE constant_expression ":" statement + | DEFAULT ":" statement + ; + +compound_statement + : "{" "}" + | "{" block_item_list "}" + ; + +block_item_list + : block_item + | block_item_list block_item + ; + +block_item + : declaration + | statement + ; + +expression_statement + : ";" + | expression ";" + ; + +selection_statement + : IF "(" expression ")" statement ELSE statement + | IF "(" expression ")" statement + | SWITCH "(" expression ")" statement + ; + +iteration_statement + : WHILE "(" expression ")" statement + | DO statement WHILE "(" expression ")" ";" + | FOR "(" expression_statement expression_statement ")" statement + | FOR "(" expression_statement expression_statement expression ")" statement + | FOR "(" declaration expression_statement ")" statement + | FOR "(" declaration expression_statement expression ")" statement + ; + +jump_statement + : GOTO IDENTIFIER ";" + | CONTINUE ";" + | BREAK ";" + | RETURN ";" + | RETURN expression ";" + ; + +translation_unit + : external_declaration + | translation_unit external_declaration + ; + +external_declaration + : function_definition + | declaration + ; + +function_definition + : declaration_specifiers declarator declaration_list compound_statement + | declaration_specifiers declarator compound_statement + ; + +declaration_list + : declaration + | declaration_list declaration + ; + +%% + +/** Bison expects error handler for parser */ + +void c11::Parser::error(const location &loc , const std::string &message) { + std::stringstream ss; + ss << "C Parser Error : " << message << " [Location : " << loc << "]"; + throw std::runtime_error(ss.str()); +} diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp new file mode 100644 index 0000000000..e25077b9ea --- /dev/null +++ b/src/nmodl/parser/c11_driver.cpp @@ -0,0 +1,55 @@ +#include <fstream> +#include <sstream> + +#include "lexer/c11_lexer.hpp" +#include "parser/c11_driver.hpp" + +namespace c11 { + + Driver::Driver(bool strace, bool ptrace) : trace_scanner(strace), trace_parser(ptrace) { + } + + /// parse c file provided as istream + bool Driver::parse_stream(std::istream& in) { + Lexer scanner(*this, &in); + Parser parser(scanner, *this); + + this->lexer = &scanner; + this->parser = &parser; + + scanner.set_debug(trace_scanner); + parser.set_debug_level(trace_parser); + return (parser.parse() == 0); + } + + //// parse c file + bool Driver::parse_file(const std::string& filename) { + std::ifstream in(filename.c_str()); + streamname = filename; + + if (!in.good()) { + return false; + } + return parse_stream(in); + } + + /// parser c provided as string (used for testing) + bool Driver::parse_string(const std::string& input) { + std::istringstream iss(input); + return parse_stream(iss); + } + + void Driver::error(const std::string& m, const class location& l) { + std::cerr << l << " : " << m << std::endl; + } + + void Driver::error(const std::string& m) { + std::cerr << m << std::endl; + } + + void Driver::process(std::string text) { + // here we will query and look into symbol table or register callback + // std::cout << text; + } + +} // namespace c11 diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp new file mode 100644 index 0000000000..17ef6f3f16 --- /dev/null +++ b/src/nmodl/parser/c11_driver.hpp @@ -0,0 +1,83 @@ +#ifndef C11_DRIVER_HPP +#define C11_DRIVER_HPP + + +#include <algorithm> +#include <map> +#include <string> + +#include "ast/ast.hpp" + +/** The c11 namespace encapsulates everything related to C (11) parsing */ +namespace c11 { + /** + * \class Driver + * \brief Class that binds all pieces together for parsing C verbatim blocks + */ + + /// flex generated scanner class (extends base lexer class of flex) + class Lexer; + + /// parser class generated by bison + class Parser; + + class Driver { + private: + /// all typedefs + std::map<std::string, std::string> typedefs; + + /// constants defined in enum + std::vector<std::string> enum_constants; + + /// enable debug output in the flex scanner + bool trace_scanner = false; + + /// enable debug output in the bison parser + bool trace_parser = false; + + /// pointer to the lexer instance being used + Lexer* lexer = nullptr; + + /// pointer to the parser instance being used + Parser* parser = nullptr; + + /// print messages from lexer/parser + bool verbose = false; + + public: + /// file or input stream name (used by scanner for position), see todo + std::string streamname; + + Driver() = default; + Driver(bool strace, bool ptrace); + + void error(const std::string& m, const class location& l); + void error(const std::string& m); + + bool parse_stream(std::istream& in); + bool parse_string(const std::string& input); + bool parse_file(const std::string& filename); + void process(std::string); + + void set_verbose(bool b) { + verbose = b; + } + + bool is_verbose() const { + return verbose; + } + + bool is_typedef(std::string type) const { + return typedefs.find(type) != typedefs.end(); + } + + bool is_enum_constant(std::string constant) const { + return std::find(enum_constants.begin(), enum_constants.end(), constant) != + enum_constants.end(); + } + }; + +} // namespace c11 + + +#endif diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp new file mode 100644 index 0000000000..1f0b5e5fef --- /dev/null +++ b/src/nmodl/parser/main_c.cpp @@ -0,0 +1,47 @@ +#include <fstream> +#include <iostream> + +#include "parser/c11_driver.hpp" +#include "tclap/CmdLine.h" + +/** + * Standlone parser program for C. This demonstrate basic + * usage of psrser and driver class. + */ + +int main(int argc, const char* argv[]) { + try { + TCLAP::CmdLine cmd("C Parser: Standalone parser program for C"); + TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, + "../test/input/channel.c", "string"); + + cmd.add(filearg); + cmd.parse(argc, argv); + + std::string filename = filearg.getValue(); + std::ifstream file(filename); + + if (!file) { + throw std::runtime_error("Could not open file " + filename); + } + + std::cout << "\n C Parser : Processing file : " << filename << std::endl; + + std::istream& in(file); + + /// driver object creates lexer and parser, just call parser method + c11::Driver driver; + + driver.set_verbose(true); + driver.parse_stream(in); + + // driver.parse_file(filename); + + std::cout << "----PARSING FINISHED----" << std::endl; + } catch (TCLAP::ArgException& e) { + std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; + return 1; + } + + return 0; +} diff --git a/src/nmodl/parser/main.cpp b/src/nmodl/parser/main_nmodl.cpp similarity index 100% rename from src/nmodl/parser/main.cpp rename to src/nmodl/parser/main_nmodl.cpp diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 8bcf95e4d9..1dd65e0588 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -289,7 +289,6 @@ namespace symtab { AST* node, bool global, SymbolTable* node_symtab) { - if (node == nullptr) { throw std::runtime_error("Can't enter with empty node"); } From 2830e26540d5975208f3d241f51a9f99501ae45c Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 2 Apr 2018 19:26:57 +0200 Subject: [PATCH 063/871] Draft pass to rename variable used in verbatim block to their "original" name i.e. removing _l and _p_ prefixes: - run this pass in nocmodl_visitor - update tests Change-Id: I448c28a31aa7b8a99ecda9387b998aef1fdadac4 NMODL Repo SHA: BlueBrain/nmodl@f3025d2e528a8cd3a7d2875ae3a5a8c29aa863fe --- src/nmodl/language/nmodl.yaml | 1 + src/nmodl/parser/c11_driver.cpp | 16 ++++ src/nmodl/parser/c11_driver.hpp | 8 ++ src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/main.cpp | 11 +++ .../visitors/verbatim_var_rename_visitor.cpp | 77 +++++++++++++++++++ .../visitors/verbatim_var_rename_visitor.hpp | 54 +++++++++++++ 7 files changed, 169 insertions(+) create mode 100644 src/nmodl/visitors/verbatim_var_rename_visitor.cpp create mode 100644 src/nmodl/visitors/verbatim_var_rename_visitor.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 9678c745c0..179ed857f7 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1660,6 +1660,7 @@ members: - statement: type: String + getter: {name: get_statement} suffix: {value: "ENDVERBATIM"} - Comment: members: diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index e25077b9ea..be6382a629 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -48,8 +48,24 @@ namespace c11 { } void Driver::process(std::string text) { + tokens.push_back(text); // here we will query and look into symbol table or register callback // std::cout << text; } + void Driver::scan_string(std::string& text) { + std::istringstream in(text); + Lexer scanner(*this, &in); + Parser parser(scanner, *this); + this->lexer = &scanner; + this->parser = &parser; + while (true) { + auto sym = lexer->next_token(); + auto token = sym.token(); + if (token == Parser::token::END) { + break; + } + } + } + } // namespace c11 diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 17ef6f3f16..8128fad8db 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -29,6 +29,9 @@ namespace c11 { /// constants defined in enum std::vector<std::string> enum_constants; + /// all tokens encountered + std::vector<std::string> tokens; + /// enable debug output in the flex scanner bool trace_scanner = false; @@ -57,6 +60,7 @@ namespace c11 { bool parse_stream(std::istream& in); bool parse_string(const std::string& input); bool parse_file(const std::string& filename); + void scan_string(std::string& text); void process(std::string); void set_verbose(bool b) { @@ -75,6 +79,10 @@ namespace c11 { return std::find(enum_constants.begin(), enum_constants.end(), constant) != enum_constants.end(); } + + std::vector<std::string> all_tokens() const { + return tokens; + } }; } // namespace c11 diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index ed5a576cf9..e0db965c2a 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -28,6 +28,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 271e4c29fd..99e478414d 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -13,6 +13,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/verbatim_var_rename_visitor.hpp" #include "tclap/CmdLine.h" @@ -89,6 +90,16 @@ int main(int argc, const char* argv[]) { std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; } + { + VerbatimVarRenameVisitor v; + v.visit_program(ast.get()); + } + + { + NmodlPrintVisitor v(channel_name + ".nocmodl.verbrename.mod"); + v.visit_program(ast.get()); + } + { NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp new file mode 100644 index 0000000000..b48d51b448 --- /dev/null +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -0,0 +1,77 @@ +#include "visitors/verbatim_var_rename_visitor.hpp" +#include "parser/c11_driver.hpp" + +using namespace ast; +using namespace symtab; + +void VerbatimVarRenameVisitor::visit_statement_block(StatementBlock* node) { + if (node->statements.empty()) { + return; + } + + auto current_symtab = node->get_symbol_table(); + if (current_symtab != nullptr) { + symtab = current_symtab; + } + + // some statements like forall, from, while are of type expression statement type. + // These statements contain statement block but do not have symbol table. And hence + // we push last non-null symbol table on the stack. + symtab_stack.push(symtab); + + // first need to process all children : perform recursively from innermost block + for (auto& item : node->statements) { + item->accept(this); + } + + /// go back to previous block in hierarchy + symtab = symtab_stack.top(); + symtab_stack.pop(); +} + +/** + * Rename variable used in verbatim block if defined in NMODL scope + * + * Check if variable is candidate for renaming and check if it is + * defined in the nmodl blocks. If so, return "original" name of the + * variable. + */ +std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { + bool rename_plausible = false; + auto new_name = name; + if (name.find(local_prefix) == 0) { + new_name.erase(0,2); + rename_plausible = true; + } + if (name.find(range_prefix) == 0) { + new_name.erase(0,3); + rename_plausible = true; + } + if (rename_plausible) { + auto symbol = symtab->lookup_in_scope(new_name); + if (symbol != nullptr) { + return new_name; + } + std::cerr << "Warning : could not find " << name << " definition in nmodl" << std::endl; + } + return name; +} + + +/** + * Parse verbatim blocks and rename variables used + */ +void VerbatimVarRenameVisitor::visit_verbatim(Verbatim* node) { + auto statement = node->get_statement(); + auto text = statement->eval(); + c11::Driver driver; + + driver.scan_string(text); + auto tokens = driver.all_tokens(); + + std::string result; + for(auto& token: tokens) { + result += rename_variable(token); + } + statement->set(result); +} diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp new file mode 100644 index 0000000000..cc3b39ed87 --- /dev/null +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -0,0 +1,54 @@ +#ifndef VERBATIM_VAR_RENAME_VISITOR_HPP +#define VERBATIM_VAR_RENAME_VISITOR_HPP + +#include <string> +#include <stack> + +#include "visitors/ast_visitor.hpp" +#include "symtab/symbol_table.hpp" + +/** + * \class VerbatimVarRenameVisitor + * \brief Rename variable in verbatim block + * + * Verbatim blocks in NMODL use different names for local + * and range variables: + * + * - if local variable is xx then translated name of variable + * in C file is _lxx + * - if range (or any other global) variable is xx then translated + * name of the variable is _p_xx + * + * This naming convention is based on NEURON code generation convention. + * As part of this pass, we revert such usages of the variable to original + * names. We do this only if variable is present in symbol table. + * + * \todo : check if symbol table lookup is ok or there are cases where this + * could be error prone. + */ + +class VerbatimVarRenameVisitor : public AstVisitor { + private: + + /// non-null symbol table in the scope hierarchy + symtab::SymbolTable* symtab = nullptr; + + /// symbol tables in nested blocks + std::stack<symtab::SymbolTable*> symtab_stack; + + /// prefix used for local variable + std::string local_prefix = "_l"; + + /// prefix used for range variables + std::string range_prefix = "_p_"; + + std::string rename_variable(std::string); + + public: + VerbatimVarRenameVisitor() = default; + + virtual void visit_verbatim(ast::Verbatim* node) override; + virtual void visit_statement_block(ast::StatementBlock* node) override; +}; + +#endif From c888315df57c4b3fc647d2f2b7ecf8c2bdf5c8e8 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 3 Apr 2018 19:15:39 +0200 Subject: [PATCH 064/871] Rename visitor pass now renames variable used in verbatim blocks. Note that the variables are renamed to original names by verbatim renamer pass. Change-Id: I22ebd97d8e19deb822856b50ded0395843ddcc9b NMODL Repo SHA: BlueBrain/nmodl@fc7d291129daa186ba33959f4e68b7b83bde2c88 --- .../visitors/local_var_rename_visitor.cpp | 1 - src/nmodl/visitors/rename_visitor.cpp | 27 ++++++++ src/nmodl/visitors/rename_visitor.hpp | 8 +++ test/nmodl/transpiler/visitor/visitor.cpp | 63 ++++++++++++++++++- 4 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index c18d412603..1d7080e40d 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -44,7 +44,6 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { return; } - RenameVisitor rename_visitor; for (auto& var : *variables) { diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 6fbddf3bbe..e88d1eda77 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -1,4 +1,5 @@ #include "visitors/rename_visitor.hpp" +#include "parser/c11_driver.hpp" /// rename matching variable void RenameVisitor::visit_name(ast::Name* node) { @@ -16,3 +17,29 @@ void RenameVisitor::visit_name(ast::Name* node) { void RenameVisitor::visit_prime_name(ast::PrimeName* node) { node->visit_children(this); } + +/** + * Parse verbatim blocks and rename variable if it is used. + */ +void RenameVisitor::visit_verbatim(Verbatim* node) { + if (!rename_verbatim) { + return; + } + + auto statement = node->get_statement(); + auto text = statement->eval(); + c11::Driver driver; + + driver.scan_string(text); + auto tokens = driver.all_tokens(); + + std::string result; + for(auto& token: tokens) { + if (token == var_name) { + result += new_var_name; + } else { + result += token; + } + } + statement->set(result); +} \ No newline at end of file diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index ce60d17b67..ba026dda53 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -29,6 +29,9 @@ class RenameVisitor : public AstVisitor { /// new name std::string new_var_name; + // rename verbatim blocks as well + bool rename_verbatim = true; + public: RenameVisitor() = default; @@ -41,8 +44,13 @@ class RenameVisitor : public AstVisitor { new_var_name = new_name; } + void enable_verbatim(bool state) { + rename_verbatim = state; + } + virtual void visit_name(ast::Name* node) override; virtual void visit_prime_name(ast::PrimeName* node) override; + virtual void visit_verbatim(ast::Verbatim* node) override; }; #endif diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index a085aabf6a..e7243643da 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -14,6 +14,7 @@ #include "visitors/verbatim_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/localize_visitor.hpp" +#include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "test/utils/nmodl_constructs.h" #include "test/utils/test_utils.hpp" @@ -396,6 +397,11 @@ std::string run_local_var_rename_visitor(const std::string& text) { v.visit_program(ast.get()); } + { + VerbatimVarRenameVisitor v; + v.visit_program(ast.get()); + } + { LocalVarRenameVisitor v; v.visit_program(ast.get()); @@ -498,6 +504,9 @@ SCENARIO("Variable renaming in nested blocks") { m = gNaTs2_t + h { LOCAL m, h + VERBATIM + _lm = 12 + ENDVERBATIM } } } @@ -515,7 +524,6 @@ SCENARIO("Variable renaming in nested blocks") { } )"; - // \todo : open brace without any keyword starts with an extra empty space std::string expected_nmodl_text = R"( NEURON { SUFFIX NaTs2_t @@ -545,6 +553,9 @@ SCENARIO("Variable renaming in nested blocks") { m_r_1 = gNaTs2_t_r_0+h_r_1 { LOCAL m_r_0, h_r_0 + VERBATIM + m_r_0 = 12 + ENDVERBATIM } } } @@ -571,6 +582,56 @@ SCENARIO("Variable renaming in nested blocks") { } } + +SCENARIO("Presence of local variable in verbatim block") { + GIVEN("A neuron block and procedure with same variable name") { + std::string nmodl_text = R"( + NEURON { + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar, x + VERBATIM + _lx = _lgNaTs2_tbar + ENDVERBATIM + } + + PROCEDURE alpha() { + VERBATIM + _p_gNaTs2_tbar = 12 + ENDVERBATIM + } + )"; + + std::string expected_nmodl_text = R"( + NEURON { + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar_r_0, x + VERBATIM + x = gNaTs2_tbar_r_0 + ENDVERBATIM + } + + PROCEDURE alpha() { + VERBATIM + gNaTs2_tbar = 12 + ENDVERBATIM + } + )"; + + THEN("var renaming pass changes local & global variable in verbatim block") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(expected_nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == expected_result); + } + } +} + //============================================================================= // Procedure/Function inlining tests //============================================================================= From 641676a9e4f63dee163c586af1912bc21db99424 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 22 Feb 2018 23:26:30 +0100 Subject: [PATCH 065/871] First draft of C code generation backends serial, openmp and openacc : - adding notes.md with comments/information from mod2c - integration of fmt : Formatting library for C++ - bug fix and major change in c lexer: To better control handling of tokens, add_token now done in each action instead of USER_ACTION. This was causing an issue for preprocessing directives. - Bug fix for comment handling in C lexer Change-Id: Iec4863f1c5357d6a3756e9876d60939ded09ed38 NMODL Repo SHA: BlueBrain/nmodl@c1387c8d81c47962ab56605b7008b7b71d4d3eac --- .clang-format | 2 +- CodeGen.md | 1105 +++++ cmake/nmodl/CMakeLists.txt | 9 +- src/nmodl/ast/ast_utils.hpp | 4 + src/nmodl/codegen/CMakeLists.txt | 39 + .../codegen/base/codegen_base_visitor.cpp | 738 +++ .../codegen/base/codegen_base_visitor.hpp | 444 ++ .../codegen/base/codegen_helper_visitor.cpp | 545 +++ .../codegen/base/codegen_helper_visitor.hpp | 92 + .../c-openacc/codegen_c_acc_visitor.cpp | 113 + .../c-openacc/codegen_c_acc_visitor.hpp | 72 + .../c-openmp/codegen_c_omp_visitor.cpp | 98 + .../c-openmp/codegen_c_omp_visitor.hpp | 69 + src/nmodl/codegen/c/codegen_c_visitor.cpp | 2566 ++++++++++ src/nmodl/codegen/c/codegen_c_visitor.hpp | 541 +++ src/nmodl/codegen/codegen_info.cpp | 60 + src/nmodl/codegen/codegen_info.hpp | 334 ++ src/nmodl/codegen/main.cpp | 226 + src/nmodl/ext/fmt/CMakeLists.txt | 15 + src/nmodl/ext/fmt/format.cc | 495 ++ src/nmodl/ext/fmt/format.h | 4173 +++++++++++++++++ src/nmodl/language/ast_printer.py | 13 +- src/nmodl/language/nmodl.yaml | 57 +- src/nmodl/language/node_info.py | 7 +- src/nmodl/lexer/c11.ll | 129 +- src/nmodl/lexer/main_c.cpp | 10 +- src/nmodl/parser/c11.yy | 3 - src/nmodl/parser/c11_driver.cpp | 3 +- src/nmodl/parser/c11_driver.hpp | 2 +- src/nmodl/parser/main_c.cpp | 8 +- src/nmodl/parser/nmodl.yy | 6 +- src/nmodl/printer/CMakeLists.txt | 2 + src/nmodl/printer/code_printer.cpp | 57 + src/nmodl/printer/code_printer.hpp | 68 + src/nmodl/symtab/symbol.cpp | 13 +- src/nmodl/symtab/symbol.hpp | 80 + src/nmodl/symtab/symbol_properties.cpp | 13 + src/nmodl/symtab/symbol_properties.hpp | 20 +- src/nmodl/symtab/symbol_table.cpp | 100 +- src/nmodl/symtab/symbol_table.hpp | 33 +- src/nmodl/utils/CMakeLists.txt | 1 + src/nmodl/utils/string_utils.hpp | 6 + src/nmodl/visitors/CMakeLists.txt | 4 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 44 +- src/nmodl/visitors/cnexp_solve_visitor.hpp | 11 +- src/nmodl/visitors/localize_visitor.cpp | 10 +- src/nmodl/visitors/main.cpp | 27 +- src/nmodl/visitors/rename_visitor.cpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 57 +- src/nmodl/visitors/var_usage_visitor.cpp | 16 + src/nmodl/visitors/var_usage_visitor.hpp | 30 + .../visitors/verbatim_var_rename_visitor.cpp | 6 +- .../visitors/verbatim_var_rename_visitor.hpp | 5 +- src/nmodl/visitors/verbatim_visitor.cpp | 7 +- src/nmodl/visitors/visitor_utils.cpp | 13 +- src/nmodl/visitors/visitor_utils.hpp | 1 + test/nmodl/transpiler/symtab/symbol_table.cpp | 63 +- test/nmodl/transpiler/visitor/visitor.cpp | 6 +- 58 files changed, 12565 insertions(+), 108 deletions(-) create mode 100644 CodeGen.md create mode 100644 src/nmodl/codegen/CMakeLists.txt create mode 100644 src/nmodl/codegen/base/codegen_base_visitor.cpp create mode 100644 src/nmodl/codegen/base/codegen_base_visitor.hpp create mode 100644 src/nmodl/codegen/base/codegen_helper_visitor.cpp create mode 100644 src/nmodl/codegen/base/codegen_helper_visitor.hpp create mode 100644 src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp create mode 100644 src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp create mode 100644 src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp create mode 100644 src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp create mode 100644 src/nmodl/codegen/c/codegen_c_visitor.cpp create mode 100644 src/nmodl/codegen/c/codegen_c_visitor.hpp create mode 100644 src/nmodl/codegen/codegen_info.cpp create mode 100644 src/nmodl/codegen/codegen_info.hpp create mode 100644 src/nmodl/codegen/main.cpp create mode 100644 src/nmodl/ext/fmt/CMakeLists.txt create mode 100644 src/nmodl/ext/fmt/format.cc create mode 100644 src/nmodl/ext/fmt/format.h create mode 100644 src/nmodl/printer/code_printer.cpp create mode 100644 src/nmodl/printer/code_printer.hpp create mode 100644 src/nmodl/visitors/var_usage_visitor.cpp create mode 100644 src/nmodl/visitors/var_usage_visitor.hpp diff --git a/.clang-format b/.clang-format index 2bd8848b20..66c88799e0 100644 --- a/.clang-format +++ b/.clang-format @@ -61,7 +61,7 @@ IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' -MaxEmptyLinesToKeep: 2 +MaxEmptyLinesToKeep: 3 NamespaceIndentation: All ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false diff --git a/CodeGen.md b/CodeGen.md new file mode 100644 index 0000000000..1d46658f26 --- /dev/null +++ b/CodeGen.md @@ -0,0 +1,1105 @@ +#### Nodes for Code Generation + +###### when vectorize is true and what is relation with threadsafe? + +nocpout.c@99 has following fragment: + +``` +#if VECTORIZE +int vectorize = 1; +/* +the idea is to put all variables into a vector of vectors so there +there is no static data. Every function has an implicit argument, underbar ix +which tells which set of data _p[ix][...] to use. There are going to have +to be limits on the kinds of scopmath functions called since many of them +need static data. We can have special versions of the most useful of these. +ie sparse.c. +Above is obsolete in detail , underbar ix is no longer used. +When vectorize = 1 then we believe the code is thread safe and most functions +pass around _p, _ppvar, _thread. When vectorize is 0 then we are definitely +not thread safe and _p and _ppvar are static. +*/ +``` + +So by default code is compiled with `-DVECTORIZE`. Then solvehandler in solve.c turn off vectorize in various cases: + +``` +void solvhandler() { + + case DERF: + if (method == SYM0) { + method = lookup("adrunge"); + } + + if (btype == BREAKPOINT && !steadystate) { + /* derivatives recalculated after while loop */ + if (strcmp(method - > name, "cnexp") != 0 && strcmp(method - > name, "derivimplicit") != 0) { + fprintf(stderr, "Notice: %s is not thread safe. Complain to Hines\n", method - > name); + vectorize = 0; + } + + case KINF: + if (method == SYM0) { + method = lookup("_advance"); + } + if (btype == BREAKPOINT && (method - > subtype & DERF)) {# + if VECTORIZE + fprintf(stderr, "Notice: KINETIC with is thread safe only with METHOD sparse. Complain to Hines\n"); + vectorize = 0;# + endif + + case NLINF: + #if VECTORIZE + fprintf(stderr, "Notice: NONLINEAR is not thread safe.\n"); + vectorize = 0;# + endif + if (method == SYM0) { + method = lookup("newton"); + } + + case LINF: + #if VECTORIZE + fprintf(stderr, "Notice: LINEAR is not thread safe.\n"); + vectorize = 0;# + endif + if (method == SYM0) { + method = lookup("simeq"); + } + + case DISCF: + #if VECTORIZE + fprintf(stderr, "Notice: DISCRETE is not thread safe.\n"); + vectorize = 0;# + endif + if (btype == BREAKPOINT) whileloop(qsol, (long) DISCRETE, 0); + Sprintf(buf, "0; %s += d%s; %s();\n", + indepsym - > name, indepsym - > name, fun - > name); + replacstr(qsol, buf); + break; + + # + if 1 /* really wanted? */ + case PROCED: + if (btype == BREAKPOINT) { + whileloop(qsol, (long) DERF, 0);# + if CVODE + if (cvodemethod_ == 1) { /*after_cvode*/ + cvode_interface(fun, listnum, 0); + } + if (cvodemethod_ == 2) { /*cvode_t*/ + cvode_interface(fun, listnum, 0); + insertstr(qsol, "if (!cvode_active_)"); + cvode_nrn_cur_solve_ = fun; + linsertstr(procfunc, "extern int cvode_active_;\n"); + } + if (cvodemethod_ == 3) { /*cvode_t_v*/ + cvode_interface(fun, listnum, 0); + insertstr(qsol, "if (!cvode_active_)"); + cvode_nrn_current_solve_ = fun; + linsertstr(procfunc, "extern int cvode_active_;\n"); + }# + endif + } + Sprintf(buf, " %s();\n", fun - > name); + replacstr(qsol, buf);# + if VECTORIZE + Sprintf(buf, "{ %s(_threadargs_); }\n", + fun - > name); + vectorize_substitute(qsol, buf);# + endif + break;# + endif + case PARF: + #if VECTORIZE + fprintf(stderr, "Notice: PARTIAL is not thread safe.\n"); + vectorize = 0;# + endif + if (btype == BREAKPOINT) whileloop(qsol, (long) DERF, 0); + solv_partial(qsol, fun); + break; + default: + diag("Illegal or unimplemented SOLVE type: ", fun - > name); + break; + } + #if CVODE + if (btype == BREAKPOINT) { + cvode_valid(); + } + #endif + /* add the error check */ + Insertstr(qsol, "error ="); + move(errstmt - > next, errstmt - > prev, qsol - > next); + #if VECTORIZE + if (errstmt - > next == errstmt - > prev) { + vectorize_substitute(qsol - > next, ""); + vectorize_substitute(qsol - > prev, ""); + } else { + fprintf(stderr, "Notice: SOLVE with ERROR is not thread safe.\n"); + vectorize = 0; + } + #endif + + } +``` + +So various blocks are not thread safe. Also, nocpcout.c has: + +``` + if (!mechname) { + sprintf(suffix,"_%s", modbase); + mechname = modbase; + } else if (strcmp(mechname, "nothing") == 0) { + vectorize = 0; + suffix[0] = '\0'; + mechname = modbase; + }else{ + sprintf(suffix, "_%s", mechname); + } + + ..... + + if (artificial_cell && vectorize && (thread_data_index || toplocal_)) { + fprintf(stderr, "Notice: ARTIFICIAL_CELL models that would require thread specific data are not thread safe.\n"); + vectorize = 0; + } +``` + +parsact.c has : + +``` +void vectorize_use_func(qname, qpar1, qexpr, qpar2, blocktype) +Item * qname, * qpar1, * qexpr, * qpar2; +int blocktype; { + Item * q; + if (SYM(qname) - > subtype & EXTDEF) { + if (strcmp(SYM(qname) - > name, "nrn_pointing") == 0) { + Insertstr(qpar1 - > next, "&"); + } else if (strcmp(SYM(qname) - > name, "state_discontinuity") == 0) {# + if CVODE + fprintf(stderr, "Notice: Use of state_discontinuity is not thread safe"); + vectorize = 0; + if (blocktype == NETRECEIVE) { + Fprintf(stderr, "Notice: Use of state_discontinuity in a NET_RECEIVE block is unnecessary and prevents use of this mechanism in a multithreaded simulation.\n"); + } +``` +`vectorize_use_func` function is called from parser for every function call. So `state_discontinuity` call is not thread safe. + +parse1.y has following in function call grammer block: + +``` +funccall: NAME '(' + if (SYM($1)->subtype & EXTDEF5) { + if (!assert_threadsafe) { +fprintf(stderr, "Notice: %s is not thread safe\n", SYM($1)->name); + vectorize = 0; + } + .... + | all VERBATIM + /* read everything and move as is to end of procfunc */ + {inblock(SYM($2)->name); replacstr($2, "\n/*parse1.y:163: VERBATIM*/\n"); + if (!assert_threadsafe && !saw_verbatim_) { + fprintf(stderr, "Notice: VERBATIM blocks are not thread safe\n"); + saw_verbatim_ = 1; + vectorize = 0; + } +``` +`EXTDEF5` are functions/methods defined in token_mapping. These methods are also not thread safe. Verbatim bocks are also not thrad-safe unless mod file is marked thread safe explicitly. + + + + +If following variables are encountered and if mod file is not marked THREADSAFE then vectorize becomes false. This is how it is done: + +``` + case EXTERNAL: + #if VECTORIZE + threadsafe("Use of EXTERNAL is not thread safe.");# + endif + for (q = q1 - > next; q != q2 - > next; q = q - > next) { + SYM(q) - > nrntype |= NRNEXTRN | NRNNOTP; + } + plist = (List * * ) 0; + break; + case POINTER: + threadsafe("Use of POINTER is not thread safe."); + plist = & nrnpointers; + for (q = q1 - > next; q != q2 - > next; q = q - > next) { + SYM(q) - > nrntype |= NRNNOTP | NRNPOINTER; + } + break; + case BBCOREPOINTER: + threadsafe("Use of BBCOREPOINTER is not thread safe."); + plist = & nrnpointers; + for (q = q1 - > next; q != q2 - > next; q = q - > next) { + SYM(q) - > nrntype |= NRNNOTP | NRNBBCOREPOINTER; + } + use_bbcorepointer = 1; + break; + } +``` +and threadsafe function call looks like below: + +``` +void threadsafe(char* s) { + if (!assert_threadsafe) { + fprintf(stderr, "Notice: %s\n", s); + vectorize = 0; + } +} +``` + +When THREADSAFE keyword is encountered in mod file, parser calls following function: + + +``` +void threadsafe_seen(Item* q1, Item* q2) { + Item* q; + assert_threadsafe = 1; + if (q2) { + for (q = q1->next; q != q2->next; q = q->next) { + SYM(q)->assigned_to_ = 2; + } + } +} +``` + +Some helper functions like: + +``` +void chk_thread_safe() { + Symbol* s; + int i; + Item* q; + SYMLISTITER { /* globals are now global with respect to C as well as hoc */ + s = SYM(q); + if (s->nrntype & (NRNGLOBAL) && s->assigned_to_ == 1) { + sprintf(buf, "Assignment to the GLOBAL variable, \"%s\", is not thread safe", s->name); + threadsafe(buf); + } + } +} +``` + + + +###### when _thread_mem init and cleanup callbacks generated? + +nocpout.c@625 has following conditions: + +``` + if (vectorize && toplocal_) { + int cnt; + cnt = 0; + ITERATE(q, toplocal_) { + if (SYM(q) - > assigned_to_ != 2) { + if (SYM(q) - > subtype & ARRAY) { + cnt += SYM(q) - > araydim; + } else { + ++cnt; + } + } + } + sprintf(buf, " _thread[%d]._pval = (double*)ecalloc(%d, sizeof(double));\n", thread_data_index, cnt); + lappendstr(thread_mem_init_list, buf); + sprintf(buf, " free((void*)(_thread[%d]._pval));\n", thread_data_index); + lappendstr(thread_cleanup_list, buf); + cnt = 0; + ITERATE(q, toplocal_) { + if (SYM(q) - > assigned_to_ != 2) { + if (SYM(q) - > subtype & ARRAY) { + sprintf(buf, "#define %s (_thread[%d]._pval + %d)\n", SYM(q) - > name, thread_data_index, cnt); + cnt += SYM(q) - > araydim; + } else { + sprintf(buf, "#define %s _thread[%d]._pval[%d]\n", SYM(q) - > name, thread_data_index, cnt); + ++cnt; + } + } else { /* stay file static */ + if (SYM(q) - > subtype & ARRAY) { + sprintf(buf, "static double %s[%d];\n", SYM(q) - > name, SYM(q) - > araydim); + } else { + sprintf(buf, "static double %s;\n", SYM(q) - > name); + } + } + lappendstr(defs_list, buf); + } + ++thread_data_index; + } +``` + +toplocal_ list non-empty when there are local variables in the top level global scopes. In that case we initialize `thread_mem_init_list` and `thread_cleanup_list.` Also in below context: + +``` + /* per thread global data */ + gind = 0; + if (vectorize) SYMLISTITER { + s = SYM(q); + if (s->nrntype & (NRNGLOBAL) && s->assigned_to_ == 1) { + if (s->subtype & ARRAY) { + gind += s->araydim; + }else{ + ++gind; + } + } + } + /* double scalars declared internally */ + Lappendstr(defs_list, "/* declare global and static user variables : nocpout.c 675 */\n"); + /* @todo: not being used by any model? */ + if (gind) { + sprintf(buf, "static int _thread1data_inuse = 0;\nstatic double _thread1data[%d];\n#define _gth %d\n", gind, thread_data_index); + Lappendstr(defs_list, buf); + sprintf(buf, " if (_thread1data_inuse) {_thread[_gth]._pval = (double*)ecalloc(%d, sizeof(double));\n }else{\n _thread[_gth]._pval = _thread1data; _thread1data_inuse = 1;\n }\n", gind); + lappendstr(thread_mem_init_list, buf); + lappendstr(thread_cleanup_list, " if (_thread[_gth]._pval == _thread1data) {\n _thread1data_inuse = 0;\n }else{\n free((void*)_thread[_gth]._pval);\n }\n"); + ++thread_data_index; + } +``` + +We loop over all global variables and see if any of the global variable is used as `assigned` i.e. shoul dbe written once. In that case those global variables get promited to thread variable. But note that this happens only in THREADSAFE marked mod file. + + +##### Confusions + + +###### Can ion variable appear in read as well as write list? + +``` +USEION ttx READ ttxo, ttxi WRITE ittx, ttxo VALENCE 1 +``` + +mod2c/neuron only consider ttxo in read list but not in write list. But note that `_ion_dittxdv` does appear and style variable. Check if above condition is semantically correct. + + +####### v as static when model is not thread safe + +Sometime I see `v` being declared as static variable: + +``` + static double delta_t = 0.01; + static double h0 = 0; + static double m0 = 0; + static double v = 0; +``` + + and that is used in kernels: + +``` + static double _nrn_current(double _v){double _current=0.;v=_v;{ { + gNaTs2_t = gNaTs2_tbar * m * m * m * h ; + ina = gNaTs2_t * ( v - ena ) + asecond [ 1 ] ; +``` + +Why this works? If model is marked threadsafe then v is passed as parameter to function calls where v is derived from: + + ``` + _v = _vec_v[_nd_idx]; + ``` +file:/Users/kumbhar/workarena/repos/bbp/mod2c_debug/src/mod2c_core/init.c +But in case of non-thread-safe models, v can be just static. This is done in nocpout.c@1994 where we see: + +``` +#if VECTORIZE + if (vectorize) { + s = ifnew_install("v"); + s->nrntype = NRNNOTP; /* this is a lie, it goes in at end specially */ + /* no it is not a lie. Use an optimization where voltage passed via arguments */ + }else +#endif + { + s = ifnew_install("v"); + s->nrntype |= NRNSTATIC | NRNNOTP; + } + s = ifnew_install("t"); + s->nrntype |= NRNEXTRN | NRNNOTP; + s = ifnew_install("dt"); + s->nrntype |= NRNEXTRN | NRNNOTP; + /*@todo: why this vectorize_substitute? */ + //printer->print_pragma_t_dt(); +``` + +So, declare v as static if model is not thread-safe. + + + +#### Order of variables in p array? + +This is driving me nuts! Initially I thought the order is dected by NEURON block order. But certainly it's not the case! +For example: + +- if we have RANGE variable declared in the NEURON block doesn't appear anywhere then it's not appear in translated c file! + +It looks like NEURON block is just declaration and doesn't decide the order. The variable must appear in definition blocks like parameter or assigned etc. For example, in above case if is mentioned in PARAMETER then only it appeared in the C file. + +``` +NEURON { + SUFFIX kca + RANGE gk, gbar, m_inf, tau_m,ik, PPP, QQQ + USEION ca READ cai + USEION k READ ek WRITE ik + GLOBAL beta, cac +} + +PARAMETER { + PPP +} + +``` + +`PPP` appeared first in p array. QQQ is not appeared anywhere then it's not used at all. Oh boy! if variable is mentioned as RANGE and used in one of the block, then it's not defined at all. It must be "defined" in parameter or assigned block. + +Consider below example: + + +``` + NEURON { + SUFFIX kca + USEION ca READ cai + } + + PARAMETER { + ek = -80 (mV) + cai + } +``` + +In this case ek comes first and then cai. This order is preserved across blocks i.e. : + +``` + NEURON { + SUFFIX kca + USEION ca READ cai + } + + ASSIGNED { + cai + + } + + PARAMETER { + ek = -80 (mV) + } + +``` + +Now cai becomes firsr variable. So definitions are important for order. + +Also, suppose STATE block appears before PARAMETER and ASSIGNED block. In this case, Dstate variables must come before other variables "in symorder" block. Here symorder means remaining variables that we print after range+state type. + +CONCLUSION: we can keep global order by excluding NEURON block definitions. + +Oh Wait: More caveats! For thread variables (global variables get converted to thread specific in threadsafe model), they don't appear in the order of their definition. This is because when we split out we iterate over symbol table in alphabetical order (?): + +``` + SYMLISTITER { /* globals are now global with respect to C as well as hoc */ + s = SYM(q); + if (s - > nrntype & (NRNGLOBAL)) { + if (vectorize && s - > assigned_to_ == 1) { + if (s - > subtype & ARRAY) { + sprintf(buf, "#define %s%s (_thread1data + %d)\n\ +#define %s (_thread[_gth]._pval + %d)\n", + s - > name, suffix, gind, s - > name, gind); + } else { + sprintf(buf, "#define %s%s _thread1data[%d]\n\ +#define %s _thread[_gth]._pval[%d]\n", + s - > name, suffix, gind, s - > name, gind); + } + q1 = Lappendstr(defs_list, buf); + q1 - > itemtype = VERBATIM; + if (s - > subtype & ARRAY) { + gind += s - > araydim; + } else { + ++gind; + } +``` + +Due to this thread variable declaration is not in the order of definitions. Here is mod file example: + +``` +NEURON { + THREADSAFE + SUFFIX NaTs2_t + GLOBAL hfirst, hlast, gsecond, gthird +} + +PARAMETER { + hlast + hfirst + glast + gsecond + gfirtst + gthird +} + +LOCAL abc, abd + +BREAKPOINT { + hlast = 1 + hfirst = 11 + gfirtst= 11 + glast = 1 + gthird = 11 + abc = 1 + abd = 2 +} +``` + +In this case, when global variables are encountered, they get inserted into symbol list with symbol[0] i.e. first character is used to select a bucket. Then subsequent symbols with same symbol[0] get prepended and hence the order becomes: + +``` +gfirst, glast, gthird, gsecond # note: symbol not found get prepended to the bucket +hfirst # this is separate bucket + +``` + +and hence generated order is: + + +``` + #define _zabc _thread[0]._pval[0] + #define _zabd _thread[0]._pval[1] + + /* declare global and static user variables */ + static int _thread1data_inuse = 0; +static double _thread1data[5]; +#define _gth 1 +#define gfirtst_NaTs2_t _thread1data[0] +#define gfirtst _thread[_gth]._pval[0] +#define glast_NaTs2_t _thread1data[1] +#define glast _thread[_gth]._pval[1] +#define gthird_NaTs2_t _thread1data[2] +#define gthird _thread[_gth]._pval[2] +#define gsecond gsecond_NaTs2_t + double gsecond = 0; + #pragma acc declare copyin (gsecond) +#define hlast_NaTs2_t _thread1data[3] +#define hlast _thread[_gth]._pval[3] +#define hfirst_NaTs2_t _thread1data[4] +#define hfirst _thread[_gth]._pval[4] +``` + +Note that top local thread variables are in order because they are simply appended from parser. + + +####### ppvar semantics + +PPVAR semantics are registred by following function: + +``` +void ppvar_semantics(int i, const char* name) { + Item* q; + nmp_add_ppvar_semantics(printer, name, i); + if (!ppvar_semantics_) { ppvar_semantics_ = newlist(); } + q = Lappendstr(ppvar_semantics_, name); + q->itemtype = (short)i; +} +``` + +and this is called by following functions: + +``` +void parout() { + ....funciton that emil all header part.... + + if (net_send_seen_) { + tqitem_index = ppvar_cnt; + ppvar_semantics(ppvar_cnt, "netsend"); + ppvar_cnt++; + } + if (watch_seen_) { + watch_index = ppvar_cnt; + for (i=0; i < watch_seen_ ; ++i) { + ppvar_semantics(i+ppvar_cnt, "watch"); + } + ppvar_cnt += watch_seen_; + sprintf(buf, "\n#define _watch_array _ppvar + %d", watch_index); + Lappendstr(defs_list, buf); + Lappendstr(defs_list, "\n"); + } + if (for_netcons_) { + sprintf(buf, "\n#define _fnc_index %d\n", ppvar_cnt); + Lappendstr(defs_list, buf); + ppvar_semantics(ppvar_cnt, "fornetcon"); + ppvar_cnt += 1; + } + if (cvode_emit) { + cvode_ieq_index = ppvar_cnt; + ppvar_semantics(ppvar_cnt, "cvodeieq"); + ppvar_cnt++; + } + + if (diamdec) { + ppvar_semantics(ioncount + pointercount, "diam"); + } + if (areadec) { + ppvar_semantics(ioncount + pointercount + diamdec, "area"); + } + +} + +/* note: _nt_data is only defined in nrn_init, nrn_cur and nrn_state where ions are used in the current mod files */ +static int iondef(p_pointercount) int * p_pointercount; { + int ioncount, it, need_style; + Item * q, * q1, * q2; + Symbol * sion; + char ionname[100]; + char pionname[100]; + + lappendstr(defs_list, "/*ion definitions in nocpout.c:2077 in iondef() */\n"); + + ioncount = 0; + if (point_process) { + ioncount = 2; + q = lappendstr(defs_list, "#define _nd_area _nt_data[_ppvar[0*_STRIDE]]\n"); + //printer->print_pragma_ion_var("_nd_area", 0, ION_REGULAR); + nmp_add_ion_variable(printer, "_nd_area", VAR_DOUBLE, 0, ION_REGULAR); + q - > itemtype = VERBATIM; + ppvar_semantics(0, "area"); + ppvar_semantics(1, "pntproc"); + } + ITERATE(q, useion) { + int dcurdef = 0; + if (!uip) { + uip = newlist(); + lappendstr(uip, "static void _update_ion_pointer(Datum* _ppvar) {\n"); + } + need_style = 0; + sion = SYM(q); + sprintf(ionname, "%s_ion", sion - > name); + nmp_add_ion_name(printer, sion - > name); + q = q - > next; + ITERATE(q1, LST(q)) { + SYM(q1) - > nrntype |= NRNIONFLAG; + Sprintf(buf, "#define _ion_%s _nt_data[_ppvar[%d*_STRIDE]]\n", + SYM(q1) - > name, ioncount); + q2 = lappendstr(defs_list, buf); + sprintf(pionname, "_ion_%s", SYM(q1) - > name); + //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); + q2 - > itemtype = VERBATIM; + sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, %d);\n", + sion - > name, ioncount, iontype(SYM(q1) - > name, sion - > name));# + if 0 /*BBCORE*/ + lappendstr(uip, buf);# + endif /*BBCORE*/ + SYM(q1) - > ioncount_ = ioncount; + ppvar_semantics(ioncount, ionname); + ioncount++; + } + q = q - > next; + ITERATE(q1, LST(q)) { + if (SYM(q1) - > nrntype & NRNIONFLAG) { + SYM(q1) - > nrntype &= ~NRNIONFLAG; + } else { + Sprintf(buf, "#define _ion_%s _nt_data[_ppvar[%d*_STRIDE]]\n", + SYM(q1) - > name, ioncount); + q2 = lappendstr(defs_list, buf); + sprintf(pionname, "_ion_%s", SYM(q1) - > name); + //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); + q2 - > itemtype = VERBATIM; + sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, %d);\n", + sion - > name, ioncount, iontype(SYM(q1) - > name, sion - > name));# + if 0 /*BBCORE*/ + lappendstr(uip, buf);# + endif /*BBCORE*/ + SYM(q1) - > ioncount_ = ioncount; + ppvar_semantics(ioncount, ionname); + ioncount++; + } + it = iontype(SYM(q1) - > name, sion - > name); + if (it == IONCUR) { + dcurdef = 1; + Sprintf(buf, "#define _ion_di%sdv\t_nt_data[_ppvar[%d*_STRIDE]]\n", sion - > name, ioncount); + q2 = lappendstr(defs_list, buf); + sprintf(pionname, "_ion_di%sdv", sion - > name); + //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); + q2 - > itemtype = VERBATIM; + sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, 4);\n", + sion - > name, ioncount);# + if 0 /*BBCORE*/ + lappendstr(uip, buf);# + endif /*BBCORE*/ + ppvar_semantics(ioncount, ionname); + ioncount++; + } + if (it == IONIN || it == IONOUT) { /* would have wrote_ion_conc */ + need_style = 1; + } + } + if (need_style) { + Sprintf(buf, "#define _style_%s\t_ppvar[%d]\n", sion - > name, ioncount); + q2 = lappendstr(defs_list, buf); + sprintf(pionname, "_style_%s", sion - > name); + //printer->print_pragma_ion_var(pionname, ioncount, ION_NEED_STYLE); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_NEED_STYLE); + q2 - > itemtype = VERBATIM; + sprintf(buf, "#%s", ionname); + ppvar_semantics(ioncount, buf); + ioncount++; + } + q = q - > next; + if (!dcurdef && ldifuslist) { + Sprintf(buf, "#define _ion_di%sdv\t_nt_data[_ppvar[%d*_STRIDE]]\n", sion - > name, ioncount); + q2 = lappendstr(defs_list, buf); + sprintf(pionname, "_ion_di%sdv", sion - > name); + //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); + q2 - > itemtype = VERBATIM; + sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, 4);\n", + sion - > name, ioncount);# + if 0 /*BBCORE*/ + lappendstr(uip, buf);# + endif /*BBCORE*/ + ppvar_semantics(ioncount, ionname); + ioncount++; + } + } * p_pointercount = 0; + ITERATE(q, nrnpointers) { + sion = SYM(q); + if (sion - > nrntype & NRNPOINTER) { + Sprintf(buf, "#define %s _nt_data[_ppvar[%d*_STRIDE]]\n", + sion - > name, ioncount + * p_pointercount); + ppvar_semantics(ioncount + * p_pointercount, "pointer"); + sprintf(pionname, "%s", sion - > name); + //printer->print_pragma_ion_var(pionname, ioncount + *p_pointercount, ION_REGULAR); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount + * p_pointercount, ION_REGULAR); + } + /*@todo: forgot or doesnt matter ? */ + if (sion - > nrntype & NRNBBCOREPOINTER) { + Sprintf(buf, "#define _p_%s _nt->_vdata[_ppvar[%d*_STRIDE]]\n", + sion - > name, ioncount + * p_pointercount); + ppvar_semantics(ioncount + * p_pointercount, "bbcorepointer"); + sprintf(pionname, "_p_%s", sion - > name); + //printer->print_pragma_ion_var(pionname, ioncount + *p_pointercount, ION_BBCORE_PTR); + nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount + * p_pointercount, ION_BBCORE_PTR); + } + sion - > used = ioncount + * p_pointercount; + q2 = lappendstr(defs_list, buf); + q2 - > itemtype = VERBATIM; + ( * p_pointercount) ++; + } + + if (diamdec) { /* must be last */ + Sprintf(buf, "#define diam *_ppvar[%d]._pval\n", ioncount + * p_pointercount); + //printer->print_error("diamdec ion declaration not possible!"); + + q2 = lappendstr(defs_list, buf); + q2 - > itemtype = VERBATIM; + } /* notice that ioncount is not incremented */ + if (areadec) { + /* must be last, if we add any more the administrative + procedures must be redone */ + Sprintf(buf, "#define area _nt->_data[_ppvar[%d*_STRIDE]]\n", ioncount + * p_pointercount + diamdec); + q2 = lappendstr(defs_list, buf); + //printer->print_pragma_ion_var("area", ioncount+ * p_pointercount + diamdec, ION_REGULAR); + nmp_add_ion_variable(printer, "area", VAR_DOUBLE, ioncount + * p_pointercount + diamdec, ION_REGULAR); + q2 - > itemtype = VERBATIM; + } /* notice that ioncount is not incremented */ + if (uip) { + lappendstr(uip, "}\n"); + } + return ioncount; +} +``` + + + +####### USEION statement ordering + +If we take following (fake) example: + +``` + USEION na READ ena, ina WRITE ina, nai +``` + + +ina appears on read as well as write list. Is that possible? Neuron generate code as: + +``` +#define _ion_ena _nt_data[_ppvar[0*_STRIDE]] +#define _ion_ina _nt_data[_ppvar[1*_STRIDE]] +#define _ion_nai _nt_data[_ppvar[2*_STRIDE]] +#define _ion_dinadv _nt_data[_ppvar[3*_STRIDE]] +``` + +So ina doesnt appear twice. After looking at the mod file, the ASSUMPTION is particular variable appears either in read list +or write list but not in both. If it appears in both list, then currently the order in symbol table is used and which case ina will appear later in write list. Hence the wrong indexes! + + +######### When write_conc gets written and different ionic type macros are defined? + +Here q->next->next is write_ion list where we search for intra/extra conc. + +``` + /* Models that write concentration need their INITIAL blocks called + before those that read the concentration or reversal potential. */ + i = 0; + ITERATE(q, useion) { + ITERATE(q1, LST(q->next->next)) { + int type; + type = iontype(SYM(q1)->name, SYM(q)->name); + if (type == IONIN || type == IONOUT) { + i += 1; + } + } + q = q->next->next->next; + } + + nmp_set_writes_conc(printer, i); + + if (i) { + Lappendstr(defs_list, "\tnrn_writes_conc(_mechtype, 0);\n"); + } +``` + +Note that IONIN or IONOUT is determined by: + +``` +static int iontype(s1, s2) /* returns index of variable in ion mechanism */ + char *s1, *s2; +{ + Sprintf(buf, "i%s", s2); + if (strcmp(buf, s1) == 0) { + return IONCUR; + } + Sprintf(buf, "e%s", s2); + if (strcmp(buf, s1) == 0) { + return IONEREV; + } + Sprintf(buf, "%si", s2); + if (strcmp(buf, s1) == 0) { + return IONIN; + } + Sprintf(buf, "%so", s2); + if (strcmp(buf, s1) == 0) { + return IONOUT; + } + Sprintf(buf, "%s is not a valid ionic variable for %s", s1, s2); + diag(buf, (char *)0); + return -1; +} +``` + +The loop that processes the use ion statements is following: + +``` +void nrn_use(q1, q2, q3, q4) + Item *q1, *q2, *q3, *q4; +{ + int used, i; + Item *q, *qr, *qw; + List *readlist, *writelist; + Symbol *ion; + + ion = SYM(q1); + /* is it already used */ + used = ion_declared(SYM(q1)); + if (used) { /* READ gets promoted to WRITE */ + diag("mergeing of neuron models not supported yet", (char *)0); + }else{ /* create all the ionic variables */ + Lappendsym(useion, ion); + readlist = newlist(); + writelist = newlist(); + qr = lappendsym(useion, SYM0); + qw = lappendsym(useion, SYM0); + if (q4) { + lappendstr(useion, STR(q4)); + }else{ + lappendstr(useion, "-10000."); + } + LST(qr) = readlist; + LST(qw) = writelist; + if (q2) { Item *qt = q2->next; + move(q1->next->next, q2, readlist); + if (q3) { + move(qt->next, q3, writelist); + } + }else if (q3) { + move(q1->next->next, q3, writelist); + } + ITERATE(q, readlist) { + i = iontype(SYM(q)->name, ion->name); + if (i == IONCUR) { + SYM(q)->nrntype |= NRNCURIN; + }else{ + SYM(q)->nrntype |= NRNPRANGEIN; + if (i == IONIN || i == IONOUT) { + SYM(q)->nrntype |= IONCONC; + } + } + } + ITERATE(q, writelist) { + i = iontype(SYM(q)->name, ion->name); + if (i == IONCUR) { + if (!currents) { + currents = newlist(); + } + Lappendsym(currents, SYM(q)); + SYM(q)->nrntype |= NRNCUROUT; + }else{ + SYM(q)->nrntype |= NRNPRANGEOUT; + if (i == IONIN || i == IONOUT) { + SYM(q)->nrntype |= IONCONC; + } + } + } + } +} +``` + + +######### Does derivX_advance (X is 1, 2) can appear multiple times? + +main() in modl.c calls solvhandler() which in turn calls solvhandler() in solve.c. There we loop over all solve blocks: + +``` + ITERATE(lq, solvq) { /* remember it goes by 3's */ + steadystate=0; + btype = lq->itemtype; + if (btype < 0) { + btype = lq->itemtype = -btype; + + case DERF: + if (method == SYM0) { + method = lookup("adrunge"); + } + if (btype == BREAKPOINT && !steadystate) { + /* derivatives recalculated after while loop */ + if (strcmp(method->name, "cnexp") != 0 && strcmp(method->name, "derivimplicit") != 0 && strcmp(method->name, "euler") != 0) { + fprintf(stderr, "Notice: %s is not thread safe. Complain to Hines\n", method->name); + vectorize = 0; + Sprintf(buf, " %s();\n", fun->name); + Insertstr(follow, buf); + } + /* envelope calls go after the while loop */ + sens_nonlin_out(follow, fun); + #if CVODE + cvode_interface(fun, listnum, numeqn); + #endif + } + if (btype == BREAKPOINT) whileloop(qsol, (long)DERF, steadystate); + solv_diffeq(qsol, fun, method, numeqn, listnum, steadystate, btype); +``` + +And solv_diffeq() is the one who print out derivimplicit related code: + +``` +if (deriv_imp_list) { /* make sure deriv block translation matches method */ + Item *q; int found=0; + ITERATE(q, deriv_imp_list) { + if (strcmp(STR(q), fun->name) == 0) { + found = 1; + } + } + if ((strcmp(method->name, Derivimplicit) == 0) ^ (found == 1)) { + diag("To use the derivimplicit method the SOLVE statement must\ + precede the DERIVATIVE block\n", + " and all SOLVEs using that block must use the derivimplicit method\n"); + } + derivimplic_listnum = listnum; + Sprintf(buf, "static int _deriv%d_advance = 1;\n", listnum); + q = linsertstr(procfunc, buf); + Sprintf(buf, "\n#define _deriv%d_advance _thread[%d]._i\n\ + #define _dith%d %d\n#define _newtonspace%d _thread[%d]._pvoid\nextern void* nrn_cons_newtonspace(int, int);\n\ +", listnum, thread_data_index, listnum, thread_data_index+1, listnum, thread_data_index+2); +``` + +So if there are multiple solve blocks appear in the MOD file then only this can appear twice. + + +######### where nrn_newton_thread(_newtonspac.. get printed? + +mixed_eqns is where the callbacks get printed: + +``` +Item *mixed_eqns(q2, q3, q4) /* name, '{', '}' */ + Item *q2, *q3, *q4; +{ + int counts; + Item *qret; + Item* q; + + if (!eqnq) { + return ITEM0; /* no nonlinear algebraic equations */ + } + /* makes use of old massagenonlin split into the guts and + the header stuff */ + numlist++; + counts = nonlin_common(q4, 0); + q = insertstr(q3, "{\n"); + sprintf(buf, "{ double* _savstate%d = (double*)_thread[_dith%d]._pval;\n\ + double* _dlist%d = (double*)(_thread[_dith%d]._pval) + (%d*_cntml_padded);\n", +numlist-1, numlist-1, +numlist, numlist-1, counts); + vectorize_substitute(q, buf); + + Sprintf(buf, "error = newton(%d,_slist%d, _p, _newton_%s%s, _dlist%d);\n", + counts, numlist, SYM(q2)->name, suffix, numlist); + qret = insertstr(q3, buf); + Sprintf(buf, + "#pragma acc routine(nrn_newton_thread) seq\n" +#if 0 + "_reset = nrn_newton_thread(_newtonspace%d, %d,_slist%d, _newton_%s%s, _dlist%d, _threadargs_);\n" +#else + "_reset = nrn_newton_thread(_newtonspace%d, %d,_slist%d, _derivimplicit_%s%s, _dlist%d, _threadargs_);\n" +#endif + , numlist-1, counts, numlist, SYM(q2)->name, suffix, numlist); + vectorize_substitute(qret, buf); + Insertstr(q3, "/*if(_reset) {abort_run(_reset);}*/ }\n"); + Sprintf(buf, + "extern int _newton_%s%s(_threadargsproto_);\n" + , SYM(q2)->name, suffix); + Linsertstr(procfunc, buf); + + Sprintf(buf, + "\n" + "/* _derivimplicit_ %s %s */\n" + "#ifndef INSIDE_NMODL\n" + "#define INSIDE_NMODL\n" + "#endif\n" + "#include \"_kinderiv.h\"\n" + , SYM(q2)->name, suffix); + Linsertstr(procfunc, buf); + + Sprintf(buf, "\n return _reset;\n}\n\nint _newton_%s%s (_threadargsproto_) { int _reset=0;\n", SYM(q2)->name, suffix); + Insertstr(q3, buf); + q = insertstr(q3, "{ int _counte = -1;\n"); + sprintf(buf, "{ double* _savstate%d = (double*)_thread[_dith%d]._pval;\n\ + double* _dlist%d = (double*)(_thread[_dith%d]._pval) + (%d*_cntml_padded);\n int _counte = -1;\n", +numlist-1, numlist-1, +numlist, numlist-1, counts); + vectorize_substitute(q, buf); + + Insertstr(q4, "\n }"); + return qret; +} +``` + + +##### Thread promoted variables may not appear in the thread in NOCMODL and code will be different ! + +See below example in old ProbAMPANMDA_EMS.mod: + +``` + +PARAMETER { + mggate +} + +BREAKPOINT { + + SOLVE state + mggate = 1 / (1 + exp(0.062 (/mV) * -(v)) * (mg / 3.57 (mM))) :mggate kinetics - Jahr & Stevens 1990 + g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA + g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics + g = g_AMPA + g_NMDA + i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal + i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal + i = i_AMPA + i_NMDA +} + +``` + +As mggate is parameter and being written, NEURON will promote this to thread variable. But, nocmodl localizer pass will convert the mggate to local variable and then mggate in global symbol table is not written and hence wont be promoted to Thread variable. + +It seems this is ok because we should use optimized mod file for NEURON as well. And in that case code generated by NEURON will be compatible with CoreNEURON. \ No newline at end of file diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 15448d365d..cd0d868934 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -34,7 +34,7 @@ include(ClangTidyHelper) include_directories( ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src - ${PROJECT_SOURCE_DIR}/src/ext/ + ${PROJECT_SOURCE_DIR}/src/ext ) #============================================================================= @@ -54,6 +54,8 @@ add_subdirectory(src/printer) add_subdirectory(src/symtab) add_subdirectory(src/utils) add_subdirectory(src/visitors) +add_subdirectory(src/codegen) +add_subdirectory(src/ext/fmt) #============================================================================= # Memory checker options and add tests @@ -70,10 +72,9 @@ add_subdirectory(test) # Add executables #============================================================================= add_executable(nocmodl - src/main.cpp - ${CMAKE_CURRENT_BINARY_DIR}/version.cpp) + src/main.cpp) -target_link_libraries(nocmodl lexer visitor) +target_link_libraries(nocmodl lexer util visitor) #============================================================================= # Clang format target diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 721a023c52..ccc7c478b6 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -624,6 +624,10 @@ namespace ast { virtual bool is_program() { return false; } + + virtual bool is_constant_var() { + return false; + } }; } // namespace ast diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt new file mode 100644 index 0000000000..ffcd970f16 --- /dev/null +++ b/src/nmodl/codegen/CMakeLists.txt @@ -0,0 +1,39 @@ +#============================================================================= +# Codegen sources +#============================================================================= +set(CODEGEN_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_base_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_base_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_helper_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_helper_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c/codegen_c_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/c/codegen_c_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c-openmp/codegen_c_omp_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/c-openmp/codegen_c_omp_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c-openacc/codegen_c_acc_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/c-openacc/codegen_c_acc_visitor.cpp +) + +#============================================================================= +# Codegen library and executable +#============================================================================= +add_library(codegen + STATIC + ${CODEGEN_SOURCE_FILES}) + +add_dependencies(codegen lexer util) + +add_executable(nocmodl_codegen main.cpp) +target_link_libraries(nocmodl_codegen printer codegen visitor symtab util lexer fmt) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CODEGEN_SOURCE_FILES} + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp new file mode 100644 index 0000000000..5d03dcba82 --- /dev/null +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -0,0 +1,738 @@ +#include <algorithm> +#include <math.h> +#include <time.h> + +#include "visitors/rename_visitor.hpp" +#include "codegen/base/codegen_helper_visitor.hpp" +#include "codegen/c/codegen_c_visitor.hpp" +#include "visitors/var_usage_visitor.hpp" +#include "utils/string_utils.hpp" +#include "version/version.h" +#include "parser/c11_driver.hpp" + + +using namespace symtab; +using namespace fmt::literals; +using SymbolType = std::shared_ptr<symtab::Symbol>; + + +/****************************************************************************************/ +/* All visitor routines */ +/****************************************************************************************/ + + +void CodegenBaseVisitor::visit_string(String* node) { + if (!codegen) { + return; + } + std::string name = node->eval(); + if (enable_variable_name_lookup) { + name = get_variable_name(name); + } + printer->add_text(name); +} + + +void CodegenBaseVisitor::visit_integer(Integer* node) { + if (!codegen) { + return; + } + auto macro = node->get_macro_name(); + auto value = node->get_value(); + if (macro) { + macro->accept(this); + } else { + printer->add_text(std::to_string(value)); + } +} + + +void CodegenBaseVisitor::visit_float(Float* node) { + if (!codegen) { + return; + } + printer->add_text(std::to_string(node->eval())); +} + + +void CodegenBaseVisitor::visit_double(Double* node) { + if (!codegen) { + return; + } + auto value = node->eval(); + printer->add_text(double_to_string(value)); +} + + +void CodegenBaseVisitor::visit_boolean(Boolean* node) { + if (!codegen) { + return; + } + printer->add_text(std::to_string(node->eval())); +} + + +void CodegenBaseVisitor::visit_name(Name* node) { + if (!codegen) { + return; + } + node->visit_children(this); +} + + +void CodegenBaseVisitor::visit_unit(ast::Unit* node) { + // do not print units +} + + +void CodegenBaseVisitor::visit_prime_name(PrimeName* node) { + throw std::runtime_error("Prime encountered, ODEs not solved?"); +} + + +/// \todo : validate how @ is being handled in neuron implementation +void CodegenBaseVisitor::visit_var_name(VarName* node) { + if (!codegen) { + return; + } + node->name->accept(this); + if (node->at_index) { + printer->add_text("@"); + node->at_index->accept(this); + } + if (node->index) { + printer->add_text("["); + node->index->accept(this); + printer->add_text("]"); + } +} + + +void CodegenBaseVisitor::visit_indexed_name(IndexedName* node) { + if (!codegen) { + return; + } + node->name->accept(this); + printer->add_text("["); + node->length->accept(this); + printer->add_text("]"); +} + + +void CodegenBaseVisitor::visit_local_list_statement(LocalListStatement* node) { + if (!codegen) { + return; + } + auto type = local_var_type() + " "; + printer->add_text(type); + print_vector_elements(node->variables, ", "); +} + + +void CodegenBaseVisitor::visit_if_statement(IfStatement* node) { + if (!codegen) { + return; + } + printer->add_text("if ("); + node->condition->accept(this); + printer->add_text(") "); + node->statementblock->accept(this); + print_vector_elements(node->elseifs, ""); + if (node->elses) { + node->elses->accept(this); + } +} + + +void CodegenBaseVisitor::visit_else_if_statement(ElseIfStatement* node) { + if (!codegen) { + return; + } + printer->add_text(" else if ("); + node->condition->accept(this); + printer->add_text(") "); + node->statementblock->accept(this); +} + + +void CodegenBaseVisitor::visit_else_statement(ElseStatement* node) { + if (!codegen) { + return; + } + printer->add_text(" else "); + node->visit_children(this); +} + + +void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { + if (!codegen) { + return; + } + auto name = node->get_from_name()->get_name(); + auto from = node->get_from_expression(); + auto to = node->get_to_expression(); + auto inc = node->get_inc_expression(); + auto block = node->get_block(); + printer->add_text("for(int {}="_format(name)); + from->accept(this); + printer->add_text("; {}<="_format(name)); + to->accept(this); + if (inc) { + printer->add_text("; {}+="_format(name)); + inc->accept(this); + } else { + printer->add_text("; {}++"_format(name)); + } + printer->add_text(")"); + block->accept(this); +} + + +void CodegenBaseVisitor::visit_paren_expression(ParenExpression* node) { + if (!codegen) { + return; + } + printer->add_text("("); + node->expr->accept(this); + printer->add_text(")"); +} + + +void CodegenBaseVisitor::visit_binary_expression(BinaryExpression* node) { + if (!codegen) { + return; + } + auto op = node->op.eval(); + auto lhs = node->get_lhs(); + auto rhs = node->get_rhs(); + if (op == "^") { + printer->add_text("pow("); + lhs->accept(this); + printer->add_text(","); + rhs->accept(this); + printer->add_text(")"); + } else { + if (op == "=" || op == "&&" || op == "||" || op == "==") { + op = " " + op + " "; + } + lhs->accept(this); + printer->add_text(op); + rhs->accept(this); + } +} + + +void CodegenBaseVisitor::visit_binary_operator(BinaryOperator* node) { + if (!codegen) { + return; + } + printer->add_text(node->eval()); +} + + +void CodegenBaseVisitor::visit_unary_operator(UnaryOperator* node) { + if (!codegen) { + return; + } + printer->add_text(" " + node->eval()); +} + + +/** + * Statement block is top level construct (for every nmodl block). + * Sometime we want to analyse ast nodes even if code generation is + * false. Hence we visit children even if code generation is false. + */ +void CodegenBaseVisitor::visit_statement_block(StatementBlock* node) { + if (!codegen) { + node->visit_children(this); + return; + } + print_statement_block(node); +} + + +void CodegenBaseVisitor::visit_program(Program* node) { + program_symtab = node->get_symbol_table(); + + CodegenHelperVisitor v; + info = v.get_code_info(node); + info.mod_file = mod_file_suffix; + + float_variables = get_float_variables(); + int_variables = get_int_variables(); + shadow_variables = get_shadow_variables(); + + update_index_semantics(); +} + + +/****************************************************************************************/ +/* Common helper routines */ +/****************************************************************************************/ + + +/** + * Check if given statement needs to be skipped during code generation + * + * Certain statements like unit, comment, solve can/need to be skipped + * during code generation. Note that solve block is wrapped in expression + * statement and hence we have to check inner expression. It's also true + * for the initial block defined inside net receive block. + */ +bool CodegenBaseVisitor::skip_statement(Statement* node) { + // clang-format off + if (node->is_unit_state() + || node->is_comment() + || node->is_solve_block() + || node->is_conductance_hint()) { + return true; + } + // clang-format on + if (node->is_expression_statement()) { + auto expression = dynamic_cast<ExpressionStatement*>(node)->get_expression(); + if (expression->is_solve_block()) { + return true; + } + if (expression->is_initial_block()) { + return true; + } + } + return false; +} + + +bool CodegenBaseVisitor::net_send_buffer_required() { + if (net_receive_required() && !info.artificial_cell) { + if (info.net_event_used || info.net_send_used) { + return true; + } + } + return false; +} + + +bool CodegenBaseVisitor::net_receive_buffering_required() { + if (info.point_process && !info.artificial_cell && info.net_receive_node != nullptr) { + return true; + } + return false; +} + + +bool CodegenBaseVisitor::nrn_state_required() { + if (info.artificial_cell) { + return false; + } + if (info.solve_node != nullptr || info.currents.empty()) { + return true; + } + return false; +} + + +bool CodegenBaseVisitor::nrn_cur_required() { + if (info.breakpoint_node != nullptr && !info.currents.empty()) { + return true; + } + return false; +} + + +bool CodegenBaseVisitor::net_receive_exist() { + if (info.net_receive_node != nullptr) { + return true; + } + return false; +} + + +bool CodegenBaseVisitor::breakpoint_exist() { + if (info.breakpoint_node != nullptr) { + return true; + } + return false; +} + + +bool CodegenBaseVisitor::net_receive_required() { + return net_receive_exist(); +} + + +bool CodegenBaseVisitor::state_variable(std::string name) { + // clang-format off + auto result = std::find_if(info.state_vars.begin(), + info.state_vars.end(), + [&name](const SymbolType& sym) { + return name == sym->get_name(); + } + ); + // clang-format on + return result != info.state_vars.end(); +} + + +int CodegenBaseVisitor::position_of_float_var(std::string name) { + int index = 0; + for (auto& var : float_variables) { + if (var->get_name() == name) { + return index; + } + index += var->get_length(); + } + throw std::logic_error(name + " variable not found"); +} + + +int CodegenBaseVisitor::position_of_int_var(std::string name) { + int index = 0; + for (auto& var : int_variables) { + if (var.symbol->get_name() == name) { + return index; + } + index += var.symbol->get_length(); + } + throw std::logic_error(name + " variable not found"); +} + + +/** + * Convert double value to string + * + * We can directly use to_string method but if user specify 7.0 then it gets + * printed as 7 (as integer). To avoid this, we use below wrapper. But note + * that there are still issues. For example, if 1.1 is not exactly represented + * in floating point, then it gets printed as 1.0999999999999. May be better + * to use std::to_string in else part? + */ +std::string CodegenBaseVisitor::double_to_string(double value) { + if (ceilf(value) == value) { + return "{:.1f}"_format(value); + } else { + return "{:.16g}"_format(value); + } +} + + +/** + * Check if given statement needs semicolon at the end of statement + * + * Statements like if, else etc. don't need semicolon at the end. + * (Note that it's valid to have "extraneous" semicolon). Also, statement + * block can appear as statement using expression statement which need to + * be inspected. + */ +bool CodegenBaseVisitor::need_semicolon(Statement* node) { + // clang-format off + if (node->is_if_statement() + || node->is_else_if_statement() + || node->is_else_statement() + || node->is_from_statement() + || node->is_verbatim() + || node->is_for_all_statement() + || node->is_from_statement() + || node->is_conductance_hint() + || node->is_while_statement()) { + return false; + } + // clang-format on + if (node->is_expression_statement()) { + auto statement = dynamic_cast<ExpressionStatement*>(node); + if (statement->get_expression()->is_statement_block()) { + return false; + } + } + return true; +} + + +// check if there is a function or procedure defined with given name +bool CodegenBaseVisitor::defined_method(std::string name) { + auto function = program_symtab->lookup(name); + auto properties = NmodlInfo::function_block | NmodlInfo::procedure_block; + if (function && function->has_properties(properties)) { + return true; + } + return false; +} + + +/** + * Return "current" for variable name used in breakpoint block + * + * Current variable used in breakpoint block could be local variable. + * In this case, neuron has already renamed the variable name by prepending + * "_l". In our implementation, the variable could have been renamed by + * one of the pass. And hence, we search all local variables and check if + * the variable is renamed. Note that we have to look into the symbol table + * of statement block and not breakpoint. + */ +std::string CodegenBaseVisitor::breakpoint_current(std::string current) { + auto breakpoint = info.breakpoint_node; + if (breakpoint == nullptr) { + return current; + } + auto symtab = breakpoint->get_statement_block()->get_symbol_table(); + auto variables = symtab->get_variables_with_properties(NmodlInfo::local_var); + for (auto& var : variables) { + auto renamed_name = var->get_name(); + auto original_name = var->get_original_name(); + if (current == original_name) { + current = renamed_name; + break; + } + } + return current; +} + + +int CodegenBaseVisitor::num_float_variable() { + auto count_length = [](std::vector<SymbolType>& variables) -> int { + int length = 0; + for (const auto& variable : variables) { + length += variable->get_length(); + } + return length; + }; + + int num_variables = count_length(info.range_parameter_vars); + num_variables += count_length(info.range_dependent_vars); + num_variables += count_length(info.state_vars); + num_variables += count_length(info.dependent_vars); + + /// for state variables we add Dstate variables + num_variables += info.state_vars.size(); + num_variables += info.ion_state_vars.size(); + + /// for v_unused variable + if (info.vectorize) { + num_variables++; + } + /// for g_unused variable + if (breakpoint_exist()) { + num_variables++; + } + /// for tsave variable + if (net_receive_exist()) { + num_variables++; + } + return num_variables; +} + + +int CodegenBaseVisitor::num_int_variable() { + int num_variables = 0; + for (auto& semantic : info.semantics) { + num_variables += semantic.size; + } + return num_variables; +} + + +/****************************************************************************************/ +/* Non-code-specific printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenBaseVisitor::print_statement_block(ast::StatementBlock* node, + bool open_brace, + bool close_brace) { + if (open_brace) { + printer->start_block(); + } + + auto statements = node->get_statements(); + for (auto& statement : statements) { + if (skip_statement(statement.get())) { + continue; + } + /// not necessary to add indent for vetbatim block (pretty-printing) + if (!statement->is_verbatim()) { + printer->add_indent(); + } + statement->accept(this); + if (need_semicolon(statement.get())) { + printer->add_text(";"); + } + printer->add_newline(); + } + + if (close_brace) { + printer->end_block(); + } +} + + + +/** + * Once variables are populated, update index semantics to register with coreneuron + */ +void CodegenBaseVisitor::update_index_semantics() { + int index = 0; + info.semantics.clear(); + if (info.point_process) { + info.semantics.push_back({index++, "area", 1}); + info.semantics.push_back({index++, "pntproc", 1}); + } + for (const auto& ion : info.ions) { + for (auto& var : ion.reads) { + info.semantics.push_back({index++, ion.name + "_ion", 1}); + } + for (auto& var : ion.writes) { + info.semantics.push_back({index++, ion.name + "_ion", 1}); + if (ion.is_ionic_current(var)) { + info.semantics.push_back({index++, ion.name + "_ion", 1}); + } + } + if (ion.need_style) { + info.semantics.push_back({index++, "#{}_ion"_format(ion.name), 1}); + } + } + for (auto& var : info.pointer_variables) { + if (info.first_pointer_var_index == -1) { + info.first_pointer_var_index = index; + } + int size = var->get_length(); + if (var->has_properties(NmodlInfo::pointer_var)) { + info.semantics.push_back({index, "pointer", size}); + } else { + info.semantics.push_back({index, "bbcorepointer", size}); + } + index += size; + } + if (info.net_send_used) { + info.semantics.push_back({index++, "netsend", 1}); + } +} + + +/** + * Return all floating point variables required for code generation + */ +std::vector<SymbolType> CodegenBaseVisitor::get_float_variables() { + /// sort with definition order + auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { + return first->get_definition_order() < second->get_definition_order(); + }; + + auto dependents = info.dependent_vars; + auto states = info.state_vars; + states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); + + /// each state variable has corresponding Dstate variable + for (auto& variable : states) { + auto name = "D" + variable->get_name(); + auto symbol = make_symbol(name); + symbol->set_definition_order(variable->get_definition_order()); + dependents.push_back(symbol); + } + std::sort(dependents.begin(), dependents.end(), comparator); + + auto variables = info.range_parameter_vars; + variables.insert(variables.end(), info.range_dependent_vars.begin(), + info.range_dependent_vars.end()); + variables.insert(variables.end(), info.state_vars.begin(), info.state_vars.end()); + variables.insert(variables.end(), dependents.begin(), dependents.end()); + + if (info.vectorize) { + variables.push_back(make_symbol("v_unused")); + } + if (breakpoint_exist()) { + std::string name = info.vectorize ? "g_unused" : "_g"; + variables.push_back(make_symbol(name)); + } + if (net_receive_exist()) { + variables.push_back(make_symbol("tsave")); + } + return variables; +} + + +/** + * Return all integer variables required for code generation + * + * IndexVariableInfo has following constructor arguments: + * - symbol + * - is_vdata (false) + * - is_index (false + * - is_integer (false) + */ +std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { + std::vector<IndexVariableInfo> variables; + if (info.point_process) { + variables.push_back({make_symbol(node_area)}); + if (info.artificial_cell) { + variables.push_back({make_symbol("point_process"), true}); + } else { + variables.push_back({make_symbol("point_process"), false, false, true}); + } + } + + for (auto& ion : info.ions) { + bool need_style = false; + for (auto& var : ion.reads) { + variables.push_back({make_symbol("ion_" + var)}); + } + for (auto& var : ion.writes) { + variables.push_back({make_symbol("ion_" + var)}); + if (ion.is_ionic_current(var)) { + variables.push_back({make_symbol("ion_di" + ion.name + "dv")}); + } + if (ion.is_intra_cell_conc(var) || ion.is_extra_cell_conc(var)) { + need_style = true; + } + } + if (need_style) { + variables.push_back({make_symbol("style_" + ion.name), false, true}); + } + } + + for (auto& var : info.pointer_variables) { + auto name = var->get_name(); + if (var->has_properties(NmodlInfo::pointer_var)) { + variables.push_back({make_symbol(name)}); + } else { + variables.push_back({make_symbol(name), true}); + } + } + + // for non-artificial cell, when net_receive buffering is enabled + // then tqitem is an offset + if (info.net_send_used) { + if (info.artificial_cell) { + variables.push_back({make_symbol("tqitem"), true}); + } else { + variables.push_back({make_symbol("tqitem"), false, false, true}); + } + info.tqitem_index = variables.size() - 1; + } + return variables; +} + + + +/** + * Return all ion write variables that require shadow vectors during code generation + * + * When we enable fine level parallelism at channel level, we have do updates + * to ion variables in atomic way. As cpus don't have atomic instructions in + * simd loop, we have to use shadow vectors for every ion variables. Here + * we return list of all such variables. + * + * \todo: if conductances are specified, we don't need all below variables + */ +std::vector<SymbolType> CodegenBaseVisitor::get_shadow_variables() { + std::vector<SymbolType> variables; + for (auto& ion : info.ions) { + for (auto& var : ion.writes) { + variables.push_back({make_symbol(shadow_varname("ion_" + var))}); + if (ion.is_ionic_current(var)) { + variables.push_back({make_symbol(shadow_varname("ion_di" + ion.name + "dv"))}); + } + } + } + variables.push_back({make_symbol("ml_rhs")}); + variables.push_back({make_symbol("ml_d")}); + return variables; +} diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp new file mode 100644 index 0000000000..f197d034d5 --- /dev/null +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -0,0 +1,444 @@ +#ifndef NMODL_CODEGEN_BASE_VISITOR_HPP +#define NMODL_CODEGEN_BASE_VISITOR_HPP + + +#include <string> +#include <algorithm> + +#include "fmt/format.h" +#include "codegen/codegen_info.hpp" +#include "printer/code_printer.hpp" +#include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" + + +using namespace fmt::literals; + + +/** + * \enum BlockType + * \brief Helper to represent various block types (similar to NEURON) + * + */ +enum class BlockType { + /// initial block + Initial, + + /// breakpoint block + Equation, + + /// ode_* routines block (not used) + Ode, + + /// derivative block + State +}; + + +/** + * \enum MemberType + * \brief Helper to represent various variables types + * + */ +enum class MemberType { + /// index / int variables + index, + + /// range / double variables + range, + + /// global variables + global, + + /// thread variables + thread +}; + + +/** + * \class IndexVariableInfo + * \brief Helper to represent information about index/int variables + * + */ +struct IndexVariableInfo { + /// symbol for the variable + std::shared_ptr<symtab::Symbol> symbol; + + /// if variable reside in vdata field of NrnThread + /// typically true for bbcore pointer + bool is_vdata = false; + + /// if this is pure index (e.g. style_ion) variables is directly + /// index and shouldn't be printed with data/vdata + bool is_index = false; + + /// if this is an integer (e.g. tqitem, point_process) variable which + /// is printed as array accesses + bool is_integer = false; + + IndexVariableInfo(std::shared_ptr<symtab::Symbol> symbol, + bool is_vdata = false, + bool is_index = false, + bool is_integer = false) + : symbol(symbol), is_vdata(is_vdata), is_index(is_index), is_integer(is_integer) { + } +}; + + +/** + * \enum LayoutType + * \brief Represent memory layout to use for code generation + * + */ +enum class LayoutType { + /// array of structure + aos, + + /// structure of array + soa +}; + + +/** + * \class ShadowUseStatement + * \brief Represent ion write statement during code generation + * + * Ion update statement need use of shadow vectors for certain backends + * as atomics can be done in vector loops on cpu. + * + * \todo : if shadow_lhs is empty then we assume shadow statement not required. + */ +struct ShadowUseStatement { + std::string lhs; + std::string op; + std::string rhs; +}; + + +/** + * \class CodegenBaseVisitor + * \brief Visitor for printing c code compatible with legacy api + * + * \todo : + * - handle define i.e. macro statement printing + * - return statement in the verbatim block of inlined function not handled (e.g. netstim.mod) + */ +class CodegenBaseVisitor : public AstVisitor { + protected: + using SymbolType = std::shared_ptr<symtab::Symbol>; + + /// memory layout for code generation + LayoutType layout; + + /// name of mod file (without .mod suffix) + std::string mod_file_suffix; + + /// flag to indicate is visitor should print the visited nodes + bool codegen = false; + + /// variable name should be converted to instance name (but not for function arguments) + bool enable_variable_name_lookup = true; + + /// symbol table for the program + symtab::SymbolTable* program_symtab = nullptr; + + /// all float variables for the model + std::vector<SymbolType> float_variables; + + /// all int variables for the model + std::vector<IndexVariableInfo> int_variables; + + /// all global variables for the model + std::vector<SymbolType> global_variables; + + /// all ion variables that could be possibly written + std::vector<SymbolType> shadow_variables; + + /// all ast information for code generation + codegen::CodegenInfo info; + + /// code printer object + std::unique_ptr<CodePrinter> printer; + + /// list of shadow statements in the current block + std::vector<ShadowUseStatement> shadow_statements; + + /// node area variable + const std::string node_area = "node_area"; + + + /// nmodl language version + std::string nmodl_version() { + return "6.2.0"; + } + + + std::string add_escape_quote(const std::string& text) { + return "\"" + text + "\""; + } + + + /// operator for rhs vector update (matrix update) + std::string operator_for_rhs() { + return info.electorde_current ? "+=" : "-="; + } + + + /// operator for diagonal vector update (matrix update) + std::string operator_for_d() { + return info.electorde_current ? "-=" : "+="; + } + + + /// data type for the local variables + std::string local_var_type() { + return "double"; + } + + + /// data type for floating point elements + std::string float_data_type() { + return "double"; + } + + + /// data type for ineteger (offset) elemenets + std::string int_data_type() { + return "int"; + } + + + /// function name for net send + bool is_net_send(const std::string& name) { + return name == "net_send"; + } + + + /// function name for net event + bool is_net_event(const std::string& name) { + return name == "net_event"; + } + + + /// name of structure that wraps range variables + std::string instance_struct() { + return "{}_Instance"_format(info.mod_suffix); + } + + + /// name of structure that wraps range variables + std::string global_struct() { + return "{}_Store"_format(info.mod_suffix); + } + + + /// name of function or procedure + std::string method_name(const std::string& name) { + auto suffix = info.mod_suffix; + return name + "_" + suffix; + } + + + /// name for shadow variable + std::string shadow_varname(const std::string& name) { + return "shadow_" + name; + } + + + /// create temporary symbol + SymbolType make_symbol(std::string name) { + return std::make_shared<symtab::Symbol>(name, ModToken()); + } + + + /// check if given variable is state variable + bool state_variable(std::string name); + + + /// check if net receive/send buffering kernels required + bool net_receive_buffering_required(); + + + /// check if nrn_state function is required + bool nrn_state_required(); + + + /// check if nrn_cur function is required + bool nrn_cur_required(); + + + /// check if net_receive function is required + bool net_receive_required(); + + + /// check if net_send_buffer is required + bool net_send_buffer_required(); + + + /// check if net_receive node exist + bool net_receive_exist(); + + + /// check if breakpoint node exist + bool breakpoint_exist(); + + + /// if method is defined the mod file + bool defined_method(std::string name); + + + /// check if give statement should be skipped during code generation + bool skip_statement(ast::Statement* node); + + + /// check if semicolon required at the end of given statement + bool need_semicolon(ast::Statement* node); + + + /// number of threads to allocate + int num_thread_objects() { + return info.vectorize ? (info.thread_data_index + 1) : 0; + } + + + /// num of float variables in the model + int num_float_variable(); + + + /// num of integer variables in the model + int num_int_variable(); + + + /// for given float variable name, index position in the data array + int position_of_float_var(std::string name); + + + /// for given int variable name, index position in the data array + int position_of_int_var(std::string name); + + + /// when ion variable copies optimized then change name (e.g. ena to ion_ena) + std::string update_if_ion_variable_name(std::string name); + + + /// name of the code generation backend + std::string backend_name(); + + + /// convert given double value to string (for printing) + std::string double_to_string(double value); + + + /// get variable name for float variable + std::string float_variable_name(SymbolType& symbol, bool use_instance); + + + /// get variable name for int variable + std::string int_variable_name(IndexVariableInfo& symbol, std::string name, bool use_instance); + + + /// get variable name for global variable + std::string global_variable_name(SymbolType& symbol); + + + /// get ion shadow variable name + std::string ion_shadow_variable_name(SymbolType& symbol); + + + /// get variable name for given name. if use_instance is true then "Instance" + /// structure is used while returning name (implemented by derived classes) + virtual std::string get_variable_name(std::string name, bool use_instance = true) = 0; + + + /// name of the current variable used in the breakpoint bock + std::string breakpoint_current(std::string current); + + + /// populate all index semantics needed for registration with coreneuron + void update_index_semantics(); + + + /// return all float variables required during code generation + std::vector<SymbolType> get_float_variables(); + + + /// return all int variables required during code generation + std::vector<IndexVariableInfo> get_int_variables(); + + + /// return all ion write variables that require shadow vectors during code generation + std::vector<SymbolType> get_shadow_variables(); + + + /// vector elements from all types + template <typename T> + void print_vector_elements(const std::vector<T>& elements, + std::string separator, + std::string prefix = ""); + + + /// any statement block in nmodl with option to (not) print braces + void print_statement_block(ast::StatementBlock* node, + bool open_brace = true, + bool close_brace = true); + + + /// common init for constructors + void init(bool aos, std::string filename) { + layout = aos ? LayoutType::aos : LayoutType::soa; + mod_file_suffix = filename; + } + + public: + CodegenBaseVisitor(std::string mod_file, bool aos) + : printer(new CodePrinter(mod_file + ".cpp")) { + init(aos, mod_file); + } + + CodegenBaseVisitor(std::string mod_file, std::stringstream& stream, bool aos) + : printer(new CodePrinter(stream)) { + init(aos, mod_file); + } + + virtual void visit_unit(ast::Unit* node) override; + virtual void visit_string(ast::String* node) override; + virtual void visit_integer(ast::Integer* node) override; + virtual void visit_float(ast::Float* node) override; + virtual void visit_double(ast::Double* node) override; + virtual void visit_boolean(ast::Boolean* node) override; + virtual void visit_name(ast::Name* node) override; + virtual void visit_prime_name(ast::PrimeName* node) override; + virtual void visit_var_name(ast::VarName* node) override; + virtual void visit_indexed_name(ast::IndexedName* node) override; + virtual void visit_local_list_statement(ast::LocalListStatement* node) override; + virtual void visit_if_statement(ast::IfStatement* node) override; + virtual void visit_else_if_statement(ast::ElseIfStatement* node) override; + virtual void visit_else_statement(ast::ElseStatement* node) override; + virtual void visit_from_statement(ast::FromStatement* node) override; + virtual void visit_paren_expression(ast::ParenExpression* node) override; + virtual void visit_binary_expression(ast::BinaryExpression* node) override; + virtual void visit_binary_operator(ast::BinaryOperator* node) override; + virtual void visit_unary_operator(ast::UnaryOperator* node) override; + virtual void visit_statement_block(ast::StatementBlock* node) override; + virtual void visit_program(ast::Program* node) override; +}; + + +/** + * Print elements of vector with given separator and prefix string + */ +template <typename T> +void CodegenBaseVisitor::print_vector_elements(const std::vector<T>& elements, + std::string separator, + std::string prefix) { + for (auto iter = elements.begin(); iter != elements.end(); iter++) { + printer->add_text(prefix); + (*iter)->accept(this); + if (!separator.empty() && !is_last(iter, elements)) { + printer->add_text(separator); + } + } +} + + +#endif diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp new file mode 100644 index 0000000000..1f944dc9be --- /dev/null +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -0,0 +1,545 @@ +#include <algorithm> +#include <math.h> + +#include "visitors/rename_visitor.hpp" +#include "codegen/base/codegen_helper_visitor.hpp" + +#include <fmt/format.h> + +using namespace codegen; +using namespace symtab; +using namespace fmt::literals; + +/** + * How symbols are stored in NEURON? See notes written in markdown file. + * + * Some variables get printed by iterating over symbol table in mod2c. + * The example of this is thread variables (and also ions?). In this + * case we must have to arrange order if we are going keep compatibility + * with NEURON. + * + * Suppose there are three global variables: bcd, abc, abd, abe + * They will be in the 'a' bucket in order: + * abe, abd, abc + * and in 'b' bucket + * bcd + * So when we print thread variables, we first have to sort in the opposite + * order in which they come and then again order by first character in increasing + * order. + * + * Note that variables in double array do not need this transformation + * and it seems like they should just follow definition order. + */ +void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector<SymbolType>& symbols) { + /// first sort by global id to get in reverse order + std::sort(symbols.begin(), symbols.end(), + [](const SymbolType& first, const SymbolType& second) -> bool { + return first->get_id() > second->get_id(); + }); + + /// now order by name (to be same as neuron's bucket) + std::sort(symbols.begin(), symbols.end(), + [](const SymbolType& first, const SymbolType& second) -> bool { + return first->get_name()[0] < second->get_name()[0]; + }); +} + + +/** + * Find all ions used in mod file + */ +void CodegenHelperVisitor::find_ion_variables() { + /// name of the ions used + auto ion_vars = psymtab->get_variables_with_properties(NmodlInfo::useion); + /// read variables from all ions + auto read_ion_vars = psymtab->get_variables_with_properties(NmodlInfo::read_ion_var); + /// write variables from all ions + auto write_ion_vars = psymtab->get_variables_with_properties(NmodlInfo::write_ion_var); + + /** + * Check if given variable belongs to given ion. + * For example, eca belongs to ca ion, nai belongs to na ion. + * We just check if we exclude first/last char, if that is ion name. + */ + auto ion_variable = [](std::string var, std::string ion) -> bool { + auto len = var.size() - 1; + return (var.substr(1, len) == ion || var.substr(0, len) == ion); + }; + + /// iterate over all ion types and construct the Ion objects + for (auto& ion_var : ion_vars) { + auto ion_name = ion_var->get_name(); + Ion ion(ion_name); + for (auto& read_var : read_ion_vars) { + auto var = read_var->get_name(); + if (ion_variable(var, ion_name)) { + ion.reads.push_back(var); + } + } + for (auto& write_var : write_ion_vars) { + auto varname = write_var->get_name(); + if (ion_variable(varname, ion_name)) { + ion.writes.push_back(varname); + if (ion.is_intra_cell_conc(varname) || ion.is_extra_cell_conc(varname)) { + ion.need_style = true; + info.write_concentration = true; + } + } + } + info.ions.push_back(std::move(ion)); + } + + /// once ions are populated, we can find all currents + auto vars = psymtab->get_variables_with_properties(NmodlInfo::nonspe_cur_var); + for (auto& var : vars) { + info.currents.push_back(var->get_name()); + } + vars = psymtab->get_variables_with_properties(NmodlInfo::electrode_cur_var); + for (auto& var : vars) { + info.currents.push_back(var->get_name()); + } + for (auto& ion : info.ions) { + for (auto& var : ion.writes) { + if (ion.is_ionic_current(var)) { + info.currents.push_back(var); + } + } + } +} + + +/** + * Find non-range variables i.e. ones that are not belong to per instance allocation + * + * Certain variables like pointers, global, parameters are not necessary to be per + * instance variables. NEURON apply certain rules to determine which variables become + * thread, static or global variables. Here we construct those variables. + */ +void CodegenHelperVisitor::find_non_range_variables() { + /** + * Top local variables are local variables appear in global scope. All local + * variables in program symbol table are in global scope. + */ + info.constant_variables = psymtab->get_variables_with_properties(NmodlInfo::constant_var); + info.top_local_variables = psymtab->get_variables_with_properties(NmodlInfo::local_var); + + /** + * All global variables remain global if mod file is not marked thread safe. + * Otherwise, global variables written at least once gets promoted to thread variables. + */ + auto vars = psymtab->get_variables_with_properties(NmodlInfo::global_var); + for (auto& var : vars) { + if (info.thread_safe && var->get_write_count() > 0) { + info.thread_variables.push_back(var); + info.thread_var_data_size += var->get_length(); + } else { + info.global_variables.push_back(var); + } + } + + /** + * If parameter is not a range and used only as read variable then it becomes global + * variable. To qualify it as thread variable it must be be written at least once and + * mod file must be marked as thread safe. + * To exclusively get parameters only, we exclude all other variables (in without) + * and then sort them with neuron/mod2c order. + */ + // clang-format off + auto with = NmodlInfo::param_assign; + auto without = NmodlInfo::range_var + | NmodlInfo::dependent_def + | NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var; + // clang-format on + vars = psymtab->get_variables(with, without); + for (auto& var : vars) { + if (info.thread_safe && var->get_write_count() > 0) { + info.thread_variables.push_back(var); + info.thread_var_data_size += var->get_length(); + } else { + info.global_variables.push_back(var); + } + } + sort_with_mod2c_symbol_order(info.thread_variables); + + /** + * \todo: Below we calculate thread related id and sizes. This will + * need to do from global analysis pass as here we are handling + * top local variables, global variables, derivimplicit method. + * There might be more use cases with other solver methods. + */ + + /** + * If derivimplicit is used, then first three thread ids get assigned to: + * 1st thread is used for: deriv_advance + * 2nd thread is used for: dith + * 3rd thread is used for: newtonspace + * + * slist and dlist represent the offsets for prime variables used. For + * euler or derivimplicit methods its always first number. + */ + if (info.derivimplicit_used) { + info.derivimplicit_var_thread_id = 0; + info.thread_data_index = 3; + info.derivimplicit_list_num = 1; + } + + if (info.euler_used) { + info.euler_list_num = 1; + } + + /// next thread id is allocated for top local variables + if (info.vectorize && !info.top_local_variables.empty()) { + info.top_local_thread_id = info.thread_data_index++; + info.thread_callback_register = true; + } + + /// next thread id is allocated for thread promoted variables + if (info.vectorize && !info.thread_variables.empty()) { + info.thread_var_thread_id = info.thread_data_index++; + info.thread_callback_register = true; + } + + /// find total size of local variables in global scope + for (auto& var : info.top_local_variables) { + info.top_local_thread_size += var->get_length(); + } + + /// find number of prime variables and total size + auto primes = psymtab->get_variables_with_properties(NmodlInfo::prime_name); + info.num_primes = primes.size(); + for (auto& variable : primes) { + info.primes_size += variable->get_length(); + } + + /// find pointer or bbcore pointer variables + // clang-format off + auto properties = NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var; + // clang-format on + info.pointer_variables = psymtab->get_variables_with_properties(properties); +} + +/** + * Find range variables i.e. ones that are belong to per instance allocation + * + * In order to be compatible with NEURON, we need to print range variables in + * certain order. For example, range variables which are parameters comes first. + * Also, there is difference between declaration order vs. definition order. For + * example, POINTER variable in NEURON block is just declaration and doesn't + * determine the order in which they will get printed. Below we query symbol table + * and order all instance variables into certain order. + */ +void CodegenHelperVisitor::find_range_variables() { + /// comparator to decide the order based on definition + auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { + return first->get_definition_order() < second->get_definition_order(); + }; + + /** + * First come parameters which are range variables. + */ + // clang-format off + auto with = NmodlInfo::range_var + | NmodlInfo::param_assign; + auto without = NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::state_var; + // clang-format on + info.range_parameter_vars = psymtab->get_variables(with, without); + std::sort(info.range_parameter_vars.begin(), info.range_parameter_vars.end(), comparator); + + /** + * Second come dependent variables which are range variables. + */ + // clang-format off + with = NmodlInfo::range_var + | NmodlInfo::dependent_def; + without = NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::state_var + | NmodlInfo::param_assign; + // clang-format on + info.range_dependent_vars = psymtab->get_variables(with, without); + std::sort(info.range_dependent_vars.begin(), info.range_dependent_vars.end(), comparator); + + /** + * Third come state variables. All state variables are kind of range by default. + * Note that some mod files like CaDynamics_E2.mod use cai as state variable + * and those are not considered as range+state variables while printing instance + * variables. Such read/write ion variables are dependent variables and hence they + * will be printed at laster stage. + * \todo: need to validate with more models and mod2c details. + */ + // clang-format off + with = NmodlInfo::state_var; + without = NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::read_ion_var + | NmodlInfo::write_ion_var; + // clang-format on + info.state_vars = psymtab->get_variables(with, without); + std::sort(info.state_vars.begin(), info.state_vars.end(), comparator); + + /** + * Remaining variables are: + * - all dependent variables without range + * - read ion variables which appear in parameter or dependent block + * - state variables which are not range but with ion variable of read/write type + */ + + /** + * first get dependent definition without read ion variables + */ + // clang-format off + with = NmodlInfo::dependent_def; + without = NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::state_var + | NmodlInfo::range_var + | NmodlInfo::extern_neuron_variable + | NmodlInfo::read_ion_var; + // clang-format on + info.dependent_vars = psymtab->get_variables(with, without); + + /** + * Now just use read-ion variables because every read-ion variable + * must be part of either assigned or parameter block. Otherwise code is not + * compiled anyway. + */ + // clang-format off + with = NmodlInfo::read_ion_var; + without = NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::state_var + | NmodlInfo::range_var + | NmodlInfo::extern_neuron_variable; + // clang-format on + auto variables = psymtab->get_variables(with, without); + info.dependent_vars.insert(info.dependent_vars.end(), variables.begin(), variables.end()); + + /* + * We want to have state variables which are read or write ion variables. + * This needs to be separated from other state variables because mod2c + * treat them separately for ordering. + */ + // clang-format off + with = NmodlInfo::state_var; + without = NmodlInfo::global_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::range_var + | NmodlInfo::extern_neuron_variable; + // clang-format on + variables = psymtab->get_variables(with, without); + for (auto& variable : variables) { + // clang-format off + auto properties = NmodlInfo::read_ion_var + | NmodlInfo::write_ion_var; + // clang-format on + if (variable->has_properties(properties)) { + info.ion_state_vars.push_back(variable); + info.dependent_vars.push_back(variable); + } + } +} + + +void CodegenHelperVisitor::visit_suffix(Suffix* node) { + auto type = node->get_suffix_type()->get_name(); + if (type == point_process) { + info.point_process = true; + } + if (type == artificial_cell) { + info.artificial_cell = true; + info.point_process = true; + } + info.mod_suffix = node->get_suffix_name()->get_name(); +} + + +void CodegenHelperVisitor::visit_elctrode_current(ElctrodeCurrent* node) { + info.electorde_current = true; +} + + +void CodegenHelperVisitor::visit_initial_block(InitialBlock* node) { + if (under_net_receive_block) { + info.net_receive_initial_node = node; + } else { + info.initial_node = node; + } + node->visit_children(this); +} + + +void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock* node) { + under_net_receive_block = true; + info.net_receive_node = node; + info.num_net_receive_arguments = node->get_arguments().size(); + node->visit_children(this); + under_net_receive_block = false; +} + + +void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock* node) { + under_derivative_block = true; + info.solve_node = node; + node->visit_children(this); + under_derivative_block = false; +} + + +void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { + under_breakpoint_block = true; + info.breakpoint_node = node; + node->visit_children(this); + under_breakpoint_block = false; +} + + +void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { + info.procedures.push_back(node); +} + + +void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { + info.functions.push_back(node); +} + + +void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { + auto name = node->get_function_name()->get_name(); + if (name == net_send_method) { + info.net_send_used = true; + } + if (name == net_event_method) { + info.net_event_used = true; + } +} + + +void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { + auto ion = node->get_ion(); + auto variable = node->get_variable(); + std::string ion_name; + if (ion) { + ion_name = ion->get_name(); + } + info.conductances.push_back({ion_name, variable->get_name()}); +} + + +void CodegenHelperVisitor::visit_solve_block(SolveBlock* node) { + info.num_solve_blocks++; + if (under_breakpoint_block) { + info.solve_block_name = node->get_solve_block()->get_name(); + if (node->get_method()) { + info.solve_method = node->get_method()->get_name(); + if (info.solve_method == derivimplicit_method) { + info.derivimplicit_used = true; + } else if (info.solve_method == euler_method) { + info.euler_used = true; + } else if (info.solve_method == cnexp_method) { + info.cnexp_used = true; + } + } + } +} + + +/** + * Visit statement block and find prime symbols appear in derivative block + * + * Equation statements in derivative block has prime on the lhs. The order + * of primes could be different that declaration state block. Also, not all + * state variables need to appear in equation block. In this case, we want + * to find out the the primes in the order of equation definition. This is + * just to keep the same order as neuron implementation. + * + * The primes are already solved and replaced by Dstate or name. And hence + * we need to check if the lhs variable is derived from prime name. If it's + * Dstate then we have to lookup state to find out corresponding symbol. This + * is because prime_variables_by_order should contain state variable name and + * not the one replaced by solver pass. + */ +void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { + auto statements = node->get_statements(); + for (auto& statement : statements) { + statement->accept(this); + if (under_derivative_block && assign_lhs && + (assign_lhs->is_name() || assign_lhs->is_var_name())) { + auto name = assign_lhs->get_name(); + auto symbol = psymtab->lookup(name); + if (symbol != nullptr) { + auto is_prime = symbol->has_properties(NmodlInfo::prime_name); + auto from_state = symbol->has_any_status(Status::from_state); + if (is_prime || from_state) { + if (from_state) { + symbol = psymtab->lookup(name.substr(1, name.size())); + } + info.prime_variables_by_order.push_back(symbol); + info.num_equations++; + } + } + } + assign_lhs = nullptr; + } +} + + +void CodegenHelperVisitor::visit_binary_expression(BinaryExpression* node) { + if (node->get_op().eval() == "=") { + assign_lhs = node->get_lhs(); + } + node->get_lhs()->accept(this); + node->get_rhs()->accept(this); +} + + +void CodegenHelperVisitor::visit_bbcore_ptr(BbcorePtr* node) { + info.bbcore_pointer_used = true; +} + + +void CodegenHelperVisitor::find_solve_node() { + if (info.solve_node != nullptr) { + return; + } + auto symbols = psymtab->get_variables_with_properties(NmodlInfo::to_solve); + if (!symbols.empty()) { + assert(symbols.size() == 1); + info.solve_node = dynamic_cast<Block*>(symbols.at(0)->get_node()); + info.solve_block_name = symbols.at(0)->get_name(); + } +} + + +void CodegenHelperVisitor::visit_program(Program* node) { + psymtab = node->get_symbol_table(); + model_symtab = node->get_model_symbol_table(); + auto blocks = node->get_blocks(); + for (auto& block : blocks) { + info.top_blocks.push_back(block.get()); + if (block->is_verbatim()) { + info.top_verbatim_blocks.push_back(block.get()); + } + } + node->visit_children(this); + find_range_variables(); + find_non_range_variables(); + find_ion_variables(); + find_solve_node(); +} + + +codegen::CodegenInfo CodegenHelperVisitor::get_code_info(ast::Program* node) { + node->accept(this); + return info; +} \ No newline at end of file diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp new file mode 100644 index 0000000000..c75e99cb3a --- /dev/null +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -0,0 +1,92 @@ +#ifndef CODEGEN_HELPER_VISITOR_HPP +#define CODEGEN_HELPER_VISITOR_HPP + +#include <string> + +#include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" +#include "codegen/codegen_info.hpp" + + +/** + * \class CodegenHelperVisitor + * \brief Helper visitor to gather iformation for code generation + * + * Code generation pass needs various information from AST and symbol + * table. Different code generation backends will need this information. + * This helper pass visit ast and collect all information into single + * class object of CodegenInfo. + * + * \todo: + * - determine vectorize as part of the pass + * - determine threadsafe as part of the pass + * - global variable order is not preserved, for example, below gives different order: + * NEURON block: GLOBAL gq, gp + * PARAMETER block: gp = 11, gq[2] + * - POINTER rng and if it's also assigned rng[4] then it is printed as one value. + * Need to check what is correct value. + */ +class CodegenHelperVisitor : public AstVisitor { + /// holds all codegen related information + codegen::CodegenInfo info; + + /// if visiting net receive block + bool under_net_receive_block = false; + + /// if visiting derivative block + bool under_derivative_block = false; + + /// if visiting breakpoint block + bool under_breakpoint_block = false; + + /// symbol table for the program + symtab::SymbolTable* psymtab = nullptr; + + /// symbol table for the entire model + symtab::ModelSymbolTable* model_symtab = nullptr; + + /// if we are visiting lhs of assignment statement + bool visiting_lhs_assignment = false; + + using SymbolType = std::shared_ptr<symtab::Symbol>; + + /// lhs of assignment in derivative block + std::shared_ptr<ast::Expression> assign_lhs = nullptr; + + /// name of the derivimplicit, euler and cnexp methods + const std::string derivimplicit_method = "derivimplicit"; + const std::string euler_method = "euler"; + const std::string cnexp_method = "cnexp"; + const std::string net_send_method = "net_send"; + const std::string net_event_method = "net_event"; + const std::string artificial_cell = "ARTIFICIAL_CELL"; + const std::string point_process = "POINT_PROCESS"; + + void find_solve_node(); + void find_ion_variables(); + void find_range_variables(); + void find_non_range_variables(); + void sort_with_mod2c_symbol_order(std::vector<SymbolType>& symbols); + + public: + CodegenHelperVisitor() = default; + codegen::CodegenInfo get_code_info(ast::Program* node); + + virtual void visit_elctrode_current(ast::ElctrodeCurrent* node) override; + virtual void visit_suffix(ast::Suffix* node) override; + virtual void visit_function_call(ast::FunctionCall* node) override; + virtual void visit_binary_expression(ast::BinaryExpression* node) override; + virtual void visit_conductance_hint(ast::ConductanceHint* node) override; + virtual void visit_procedure_block(ast::ProcedureBlock* node) override; + virtual void visit_function_block(ast::FunctionBlock* node) override; + virtual void visit_solve_block(ast::SolveBlock* node) override; + virtual void visit_statement_block(ast::StatementBlock* node) override; + virtual void visit_initial_block(ast::InitialBlock* node) override; + virtual void visit_breakpoint_block(ast::BreakpointBlock* node) override; + virtual void visit_derivative_block(ast::DerivativeBlock* node) override; + virtual void visit_net_receive_block(ast::NetReceiveBlock* node) override; + virtual void visit_bbcore_ptr(ast::BbcorePtr* node) override; + virtual void visit_program(ast::Program* node) override; +}; + +#endif \ No newline at end of file diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp new file mode 100644 index 0000000000..cb492b55a2 --- /dev/null +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp @@ -0,0 +1,113 @@ +#include <fmt/format.h> +#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" + + +using namespace fmt::literals; + + +/****************************************************************************************/ +/* Routines must be overloaded in backend */ +/****************************************************************************************/ + + +/** + * Depending programming model and compiler, we print compiler hint + * for parallelization. For example: + * + * #pragma ivdep + * for(int id=0; id<nodecount; id++) { + * + * #pragma acc parallel loop + * for(int id=0; id<nodecount; id++) { + * + */ +void CodegenCAccVisitor::print_channel_iteration_block_parallel_hint() { + printer->add_line("#pragma acc parallel loop"); +} + + +void CodegenCAccVisitor::print_atomic_reduction_pragma() { + printer->add_line("#pragma acc atomic update"); +} + + +void CodegenCAccVisitor::print_backend_includes() { + printer->add_line("#include <cuda.h>"); + printer->add_line("#include <openacc.h>"); +} + + +std::string CodegenCAccVisitor::backend_name() { + return "C-OpenAcc (api-compatibility)"; +} + + +void CodegenCAccVisitor::print_memory_allocation_routine() { + printer->add_newline(2); + auto args = "size_t num, size_t size, size_t alignment = 16"; + printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); + printer->add_line(" void* ptr;"); + printer->add_line(" cudaMallocManaged(&ptr, num*size);"); + printer->add_line(" cudaMemset(ptr, 0, num*size);"); + printer->add_line(" return ptr;"); + printer->add_line("}"); + + printer->add_newline(2); + printer->add_line("static inline void mem_free(void* ptr) {"); + printer->add_line(" cudaFree(ptr);"); + printer->add_line("}"); +} + + +/** + * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded + * to accelerator. In this case, at very top level, we print pragma + * for data present. For example: + * + * void nrn_state(...) { + * #pragma acc data present (nt, ml...) + * { + * + * } + * } + */ +void CodegenCAccVisitor::print_kernel_data_present_annotation_block_begin() { + auto global_variable = "{}_global"_format(info.mod_suffix); + printer->add_line("#pragma acc data present(nt, ml, {})"_format(global_variable)); + printer->add_line("{"); + printer->increase_indent(); +} + + +void CodegenCAccVisitor::print_nrn_cur_matrix_shadow_update() { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + print_atomic_reduction_pragma(); + printer->add_line("vec_rhs[node_id] {} rhs;"_format(rhs_op)); + print_atomic_reduction_pragma(); + printer->add_line("vec_d[node_id] {} g;"_format(d_op)); +} + + +void CodegenCAccVisitor::print_nrn_cur_matrix_shadow_reduction() { + // do nothing +} + + +/** + * End of print_kernel_enter_data_begin + */ +void CodegenCAccVisitor::print_kernel_data_present_annotation_block_end() { + printer->decrease_indent(); + printer->add_line("}"); +} + + +void CodegenCAccVisitor::print_rhs_d_shadow_variables() { + // do nothing +} + + +bool CodegenCAccVisitor::nrn_cur_reduction_loop_required() { + return false; +} \ No newline at end of file diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp new file mode 100644 index 0000000000..748e4c82a6 --- /dev/null +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp @@ -0,0 +1,72 @@ +#ifndef NMODL_CODEGEN_C_ACC_VISITOR_HPP +#define NMODL_CODEGEN_C_ACC_VISITOR_HPP + +#include "codegen/c/codegen_c_visitor.hpp" + + +/** + * \class CodegenCAccVisitor + * \brief Visitor for printing c code with OpenMP backend + * + * \todo : + * - handle define i.e. macro statement printing + * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) + */ +class CodegenCAccVisitor : public CodegenCVisitor { + protected: + /// name of the code generation backend + std::string backend_name() override; + + + /// common includes : standard c/c++, coreneuron and backend specific + void print_backend_includes() override; + + + /// ivdep like annotation for channel iterations + void print_channel_iteration_block_parallel_hint() override; + + + /// atomic update pragma for reduction statements + void print_atomic_reduction_pragma() override; + + + /// memory allocation routine + void print_memory_allocation_routine() override; + + + /// annotations like "acc enter data present(...)" for main kernel + void print_kernel_data_present_annotation_block_begin() override; + + + /// end of annotation like "acc enter data" + void print_kernel_data_present_annotation_block_end() override; + + + /// update to matrix elements with/without shadow vectors + void print_nrn_cur_matrix_shadow_update() override; + + + /// reduction to matrix elements from shadow vectors + void print_nrn_cur_matrix_shadow_reduction() override; + + + /// setup method for setting matrix shadow vectors + void print_rhs_d_shadow_variables() override; + + /// if reduction block in nrn_cur required + bool nrn_cur_reduction_loop_required() override; + + + public: + CodegenCAccVisitor(std::string mod_file, bool aos) : CodegenCVisitor(mod_file, aos) { + init(aos, mod_file); + } + + CodegenCAccVisitor(std::string mod_file, std::stringstream& stream, bool aos) + : CodegenCVisitor(mod_file, stream, aos) { + init(aos, mod_file); + } +}; + + +#endif diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp new file mode 100644 index 0000000000..421e5e5f9b --- /dev/null +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp @@ -0,0 +1,98 @@ +#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" + + +using namespace symtab; +using namespace fmt::literals; +using SymbolType = std::shared_ptr<symtab::Symbol>; + + +/****************************************************************************************/ +/* Routines must be overloaded in backend */ +/****************************************************************************************/ + + + +void CodegenCOmpVisitor::print_channel_iteration_task_begin(BlockType type) { + std::string vars; + if (type == BlockType::Equation) { + vars = "start, end, node_index, indexes, voltage, vec_rhs, vec_d, inst, thread, nt"; + } else { + vars = "start, end, node_index, indexes, voltage, inst, thread, nt"; + } + printer->add_line("#pragma omp task default(shared) firstprivate({})"_format(vars)); + printer->add_line("{"); + printer->increase_indent(); +} + + +void CodegenCOmpVisitor::print_channel_iteration_task_end() { + printer->decrease_indent(); + printer->add_line("}"); +} + + +/* + * Depending on the backend, print loop for tiling channel iterations + */ +void CodegenCOmpVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { + printer->add_line("const int TILE = 3;"); + printer->start_block("for (int block = 0; block < nodecount;) "); + printer->add_line("int start = block;"); + printer->add_line("block = (block+TILE) < nodecount ? (block+TILE) : nodecount;"); + printer->add_line("int end = block;"); + print_channel_iteration_task_begin(type); +} + + +/** + * End of tiled channel iteration block + */ +void CodegenCOmpVisitor::print_channel_iteration_tiling_block_end() { + print_channel_iteration_task_end(); + printer->end_block(); + printer->add_newline(); +} + + +/** + * Depending programming model and compiler, we print compiler hint + * for parallelization. For example: + * + * #pragma ivdep + * for(int id=0; id<nodecount; id++) { + * + * #pragma acc parallel loop + * for(int id=0; id<nodecount; id++) { + * + */ +void CodegenCOmpVisitor::print_channel_iteration_block_parallel_hint() { + printer->add_line("#pragma omp simd"); +} + + +void CodegenCOmpVisitor::print_atomic_reduction_pragma() { + printer->add_line("#pragma omp atomic update"); +} + + +void CodegenCOmpVisitor::print_backend_includes() { + printer->add_line("#include <omp.h>"); +} + + +std::string CodegenCOmpVisitor::backend_name() { + return "C-OpenMP (api-compatibility)"; +} + + +bool CodegenCOmpVisitor::channel_task_dependency_enabled() { + return true; +} + + +bool CodegenCOmpVisitor::block_require_shadow_update(BlockType type) { + if (!channel_task_dependency_enabled() || type == BlockType::Initial) { + return false; + } + return true; +} \ No newline at end of file diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp new file mode 100644 index 0000000000..3124148b21 --- /dev/null +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp @@ -0,0 +1,69 @@ +#ifndef NMODL_CODEGEN_C_OMP_VISITOR_HPP +#define NMODL_CODEGEN_C_OMP_VISITOR_HPP + +#include "codegen/c/codegen_c_visitor.hpp" + + +/** + * \class CodegenCOmpVisitor + * \brief Visitor for printing c code with OpenMP backend + * + * \todo : + * - handle define i.e. macro statement printing + * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) + */ +class CodegenCOmpVisitor : public CodegenCVisitor { + protected: + /// name of the code generation backend + std::string backend_name() override; + + + /// common includes : standard c/c++, coreneuron and backend specific + void print_backend_includes() override; + + + /// channel execution with dependency (backend specific) + bool channel_task_dependency_enabled() override; + + + /// channel iterations from which task can be created + void print_channel_iteration_task_begin(BlockType type) override; + + + /// end of task for channel iteration + void print_channel_iteration_task_end() override; + + + /// backend specific block start for tiling on channel iteration + void print_channel_iteration_tiling_block_begin(BlockType type) override; + + + /// backend specific block end for tiling on channel iteration + void print_channel_iteration_tiling_block_end() override; + + + /// ivdep like annotation for channel iterations + void print_channel_iteration_block_parallel_hint() override; + + + /// atomic update pragma for reduction statements + void print_atomic_reduction_pragma() override; + + + /// use of shadow updates at channel level required + bool block_require_shadow_update(BlockType type) override; + + + public: + CodegenCOmpVisitor(std::string mod_file, bool aos) : CodegenCVisitor(mod_file, aos) { + init(aos, mod_file); + } + + CodegenCOmpVisitor(std::string mod_file, std::stringstream& stream, bool aos) + : CodegenCVisitor(mod_file, stream, aos) { + init(aos, mod_file); + } +}; + + +#endif diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp new file mode 100644 index 0000000000..13799edf72 --- /dev/null +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -0,0 +1,2566 @@ +#include <algorithm> +#include <math.h> +#include <time.h> + +#include "visitors/rename_visitor.hpp" +#include "codegen/base/codegen_helper_visitor.hpp" +#include "codegen/c/codegen_c_visitor.hpp" +#include "visitors/var_usage_visitor.hpp" +#include "utils/string_utils.hpp" +#include "version/version.h" +#include "parser/c11_driver.hpp" + + +using namespace symtab; +using namespace fmt::literals; +using SymbolType = std::shared_ptr<symtab::Symbol>; + + + +/****************************************************************************************/ +/* Overloaded visitor routines */ +/****************************************************************************************/ + + + +void CodegenCVisitor::visit_function_call(FunctionCall* node) { + if (!codegen) { + return; + } + print_function_call(node); +} + + +void CodegenCVisitor::visit_verbatim(Verbatim* node) { + if (!codegen) { + return; + } + auto text = node->get_statement()->eval(); + auto result = process_verbatim_text(text); + + auto statements = stringutils::split_string(result, '\n'); + for (auto& statement : statements) { + stringutils::trim_newline(statement); + if (statement.find_first_not_of(' ') != std::string::npos) { + printer->add_line(statement); + } + } +} + + +/****************************************************************************************/ +/* Common helper routines */ +/****************************************************************************************/ + + + +/** + * Return "current" for variable name used in breakpoint block + * + * Current variable used in breakpoint block could be local variable. + * In this case, neuron has already renamed the variable name by prepending + * "_l". In our implementation, the variable could have been renamed by + * one of the pass. And hence, we search all local variables and check if + * the variable is renamed. Note that we have to look into the symbol table + * of statement block and not breakpoint. + */ +std::string CodegenCVisitor::breakpoint_current(std::string current) { + auto breakpoint = info.breakpoint_node; + if (breakpoint == nullptr) { + return current; + } + auto symtab = breakpoint->get_statement_block()->get_symbol_table(); + auto variables = symtab->get_variables_with_properties(NmodlInfo::local_var); + for (auto& var : variables) { + auto renamed_name = var->get_name(); + auto original_name = var->get_original_name(); + if (current == original_name) { + current = renamed_name; + break; + } + } + return current; +} + + +/** + * For given block type, return statements for all read ion variables + * + * Depending upon the block type, we have to print read/write ion variables + * during code generation. Depending on block/procedure being printed, this + * method return statements as vector. As different code backends could have + * different variable names, we rely on backend-specific read_ion_variable_name + * and write_ion_variable_name method which will be overloaded. + * + * \todo: After looking into mod2c and neuron implementation, it seems like + * Ode block type is not used (?). Need to look into implementation details. + */ +std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { + if (optimize_ion_variable_copies()) { + return ion_read_statements_optimal(type); + } + std::vector<std::string> statements; + for (auto& ion : info.ions) { + auto name = ion.name; + for (auto& var : ion.reads) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { + continue; + } + auto variable_names = read_ion_variable_name(var); + auto first = get_variable_name(variable_names.first); + auto second = get_variable_name(variable_names.second); + statements.push_back("{} = {};"_format(first, second)); + } + for (auto& var : ion.writes) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { + continue; + } + if (ion.is_ionic_conc(var)) { + auto variables = read_ion_variable_name(var); + auto first = get_variable_name(variables.first); + auto second = get_variable_name(variables.second); + statements.push_back("{} = {};"_format(first, second)); + } + } + } + return statements; +} + + +std::vector<std::string> CodegenCVisitor::ion_read_statements_optimal(BlockType type) { + std::vector<std::string> statements; + for (auto& ion : info.ions) { + for (auto& var : ion.writes) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { + continue; + } + if (ion.is_ionic_conc(var)) { + auto variables = read_ion_variable_name(var); + auto first = "ionvar." + variables.first; + auto second = get_variable_name(variables.second); + statements.push_back("{} = {};"_format(first, second)); + } + } + } + return statements; +} + + +std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType type) { + std::vector<ShadowUseStatement> statements; + for (auto& ion : info.ions) { + std::string concentration; + auto name = ion.name; + for (auto& var : ion.writes) { + auto variable_names = write_ion_variable_name(var); + if (ion.is_ionic_current(var)) { + if (type == BlockType::Equation) { + auto current = breakpoint_current(var); + auto lhs = variable_names.first; + auto op = "+="; + auto rhs = get_variable_name(current); + if (info.point_process) { + auto area = get_variable_name(node_area); + rhs += "*(1.e2/{})"_format(area); + } + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + } + } else { + if (!ion.is_rev_potential(var)) { + concentration = var; + } + auto lhs = variable_names.first; + auto op = "="; + auto rhs = get_variable_name(variable_names.second); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + } + } + + if (type == BlockType::Initial && !concentration.empty()) { + int index = 0; + if (ion.is_intra_cell_conc(concentration)) { + index = 1; + } else if (ion.is_extra_cell_conc(concentration)) { + index = 2; + } else { + /// \todo: unhandled case in neuron implementation + throw std::logic_error("codegen error for {} ion"_format(ion.name)); + } + auto ion_type_name = "{}_type"_format(ion.name); + auto lhs = "int {}"_format(ion_type_name); + auto op = "="; + auto rhs = get_variable_name(ion_type_name); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + auto statement = conc_write_statement(ion.name, concentration, index); + statements.push_back(ShadowUseStatement{statement, "", ""}); + } + } + return statements; +} + + +/** + * Often top level verbatim blocks use variables with old names. + * Here we process if we are processing verbatim block at global scope. + */ +std::string CodegenCVisitor::process_verbatim_token(std::string token) { + if (printing_top_verbatim_blocks) { + std::string name = token; + if (verbatim_variables_mapping.find(token) != verbatim_variables_mapping.end()) { + name = verbatim_variables_mapping[token]; + } + return get_variable_name(name, false); + } + return get_variable_name(token); +} + + +bool CodegenCVisitor::ion_variable_struct_required() { + if (optimize_ion_variable_copies() && info.ion_has_write_variable()) { + return true; + } + return false; +} + + +/****************************************************************************************/ +/* Routines must be overloaded in backend */ +/****************************************************************************************/ + + +void CodegenCVisitor::print_channel_iteration_task_begin(BlockType type) { + // backend specific, do nothing +} + + +void CodegenCVisitor::print_channel_iteration_task_end() { + // backend specific, do nothing +} + + +/* + * Depending on the backend, print loop for tiling channel iterations + */ +void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { + // backend specific, do nothing +} + + +/** + * End of tiled channel iteration block + */ +void CodegenCVisitor::print_channel_iteration_tiling_block_end() { + // backend specific, do nothing +} + + +/** + * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded + * to accelerator. In this case, at very top level, we print pragma + * for data present. For example: + * + * void nrn_state(...) { + * #pragma acc data present (nt, ml...) + * { + * + * } + * } + */ +void CodegenCVisitor::print_kernel_data_present_annotation_block_begin() { + // backend specific, do nothing +} + + +/** + * End of print_kernel_enter_data_begin + */ +void CodegenCVisitor::print_kernel_data_present_annotation_block_end() { + // backend specific, do nothing +} + + +/** + * Depending programming model and compiler, we print compiler hint + * for parallelization. For example: + * + * #pragma ivdep + * for(int id=0; id<nodecount; id++) { + * + * #pragma acc parallel loop + * for(int id=0; id<nodecount; id++) { + * + */ +void CodegenCVisitor::print_channel_iteration_block_parallel_hint() { + printer->add_line("#pragma ivdep"); +} + + + +/// if reduction block in nrn_cur required +bool CodegenCVisitor::nrn_cur_reduction_loop_required() { + if (channel_task_dependency_enabled() || info.point_process) { + return true; + } + return false; +} + + + +/* + * Depending on the backend, print condition/loop for iterating over channels + * + * For CPU backend we iterate over all node counts. For cuda we use thread + * index to check if block needs to be executed or not. + */ +void CodegenCVisitor::print_channel_iteration_block_begin() { + print_channel_iteration_block_parallel_hint(); + printer->start_block("for (int id = start; id < end; id++) "); +} + + +void CodegenCVisitor::print_channel_iteration_block_end() { + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_rhs_d_shadow_variables() { + if (info.point_process) { + printer->add_line("double* shadow_rhs = nt->_shadow_rhs;"); + printer->add_line("double* shadow_d = nt->_shadow_d;"); + } +} + + +void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { + if (channel_task_dependency_enabled()) { + auto rhs = get_variable_name("ml_rhs"); + auto d = get_variable_name("ml_d"); + printer->add_line("{} = rhs;"_format(rhs)); + printer->add_line("{} = g;"_format(d)); + } else { + if (info.point_process) { + printer->add_line("shadow_rhs[id] = rhs;"); + printer->add_line("shadow_d[id] = g;"); + } else { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + print_atomic_reduction_pragma(); + printer->add_line("vec_rhs[node_id] {} rhs;"_format(rhs_op)); + print_atomic_reduction_pragma(); + printer->add_line("vec_d[node_id] {} g;"_format(d_op)); + } + } +} + + +void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + if (channel_task_dependency_enabled()) { + auto rhs = get_variable_name("ml_rhs"); + auto d = get_variable_name("ml_d"); + printer->add_line("int node_id = node_index[id];"); + print_atomic_reduction_pragma(); + printer->add_line("vec_rhs[node_id] {} {};"_format(rhs_op, rhs)); + print_atomic_reduction_pragma(); + printer->add_line("vec_d[node_id] {} {};"_format(d_op, d)); + } else { + if (info.point_process) { + printer->add_line("int node_id = node_index[id];"); + print_atomic_reduction_pragma(); + printer->add_line("vec_rhs[node_id] {} shadow_rhs[id];"_format(rhs_op)); + print_atomic_reduction_pragma(); + printer->add_line("vec_d[node_id] {} shadow_d[id];"_format(d_op)); + } + } +} + + +void CodegenCVisitor::print_atomic_reduction_pragma() { + // backend specific, do nothing +} + + +void CodegenCVisitor::print_shadow_reduction_block_begin() { + printer->start_block("for (int id = start; id < end; id++) "); +} + + +void CodegenCVisitor::print_shadow_reduction_statements() { + for (auto& statement : shadow_statements) { + print_atomic_reduction_pragma(); + auto lhs = get_variable_name(statement.lhs); + auto rhs = get_variable_name(shadow_varname(statement.lhs)); + auto text = "{} {} {};"_format(lhs, statement.op, rhs); + printer->add_line(text); + } + shadow_statements.clear(); +} + + +void CodegenCVisitor::print_shadow_reduction_block_end() { + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_device_method_annotation() { + // backend specific, nothing for cpu +} + + +void CodegenCVisitor::print_global_method_annotation() { + // backend specific, nothing for cpu +} + + +void CodegenCVisitor::print_backend_namespace_start() { + // no separate namespace for C (cpu) backend +} + + +void CodegenCVisitor::print_backend_namespace_end() { + // no separate namespace for C (cpu) backend +} + + +void CodegenCVisitor::print_backend_includes() { + // backend specific, do nothing +} + + +std::string CodegenCVisitor::backend_name() { + return "C (api-compatibility)"; +} + + +bool CodegenCVisitor::block_require_shadow_update(BlockType type) { + return false; +} + + +bool CodegenCVisitor::channel_task_dependency_enabled() { + return false; +} + + +bool CodegenCVisitor::optimize_ion_variable_copies() { + return true; +} + + +void CodegenCVisitor::print_memory_allocation_routine() { + printer->add_newline(2); + auto args = "size_t num, size_t size, size_t alignment = 16"; + printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); + printer->add_line(" void* ptr;"); + printer->add_line(" posix_memalign(&ptr, alignment, num*size);"); + printer->add_line(" memset(ptr, 0, size);"); + printer->add_line(" return ptr;"); + printer->add_line("}"); + + printer->add_newline(2); + printer->add_line("static inline void mem_free(void* ptr) {"); + printer->add_line(" free(ptr);"); + printer->add_line("}"); +} + + +/****************************************************************************************/ +/* Non-code-specific printing routines for code generation */ +/****************************************************************************************/ + + + +void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, + bool open_brace, + bool close_brace) { + if (open_brace) { + printer->start_block(); + } + + auto statements = node->get_statements(); + for (auto& statement : statements) { + if (skip_statement(statement.get())) { + continue; + } + /// not necessary to add indent for vebatim block (pretty-printing) + if (!statement->is_verbatim()) { + printer->add_indent(); + } + statement->accept(this); + if (need_semicolon(statement.get())) { + printer->add_text(";"); + } + printer->add_newline(); + } + + if (close_brace) { + printer->end_block(); + } +} + + +void CodegenCVisitor::print_function_call(FunctionCall* node) { + auto name = node->get_function_name()->get_name(); + auto function_name = name; + if (defined_method(name)) { + function_name = method_name(name); + } + + if (is_net_send(name)) { + print_net_send_call(node); + return; + } + if (is_net_event(name)) { + print_net_event_call(node); + return; + } + auto arguments = node->get_arguments(); + printer->add_text("{}("_format(function_name)); + if (defined_method(name)) { + printer->add_text(internal_method_arguments()); + if (!arguments.empty()) { + printer->add_text(", "); + } + } + print_vector_elements(arguments, ", "); + printer->add_text(")"); +} + + +void CodegenCVisitor::print_top_verbatim_blocks() { + if (info.top_verbatim_blocks.empty()) { + return; + } + print_namespace_end(); + + printer->add_newline(2); + printer->add_line("using namespace coreneuron;"); + codegen = true; + printing_top_verbatim_blocks = true; + + for (auto& block : info.top_blocks) { + if (block->is_verbatim()) { + printer->add_newline(2); + block->accept(this); + } + } + + printing_top_verbatim_blocks = false; + codegen = false; + print_namespace_start(); +} + + +/** + * Rename function arguments that have same name with default inbuilt arguments + * + * \todo: issue with verbatim renaming. e.g. pattern.mod has info struct with + * index variable. If we use "index" instead of "indexes" as default argument + * then during verbatim replacement we don't know the index is which one. This + * is because verbatim renaming pass has already stripped out prefixes from + * the text. + */ +void CodegenCVisitor::rename_function_arguments() { + auto default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); + for (auto& arg : default_arguments) { + stringutils::trim(arg); + RenameVisitor v(arg, "arg_" + arg); + for (const auto& function : info.functions) { + function->accept(&v); + } + for (const auto& function : info.procedures) { + function->accept(&v); + } + } +} + + +void CodegenCVisitor::print_function_prototypes() { + if (info.functions.empty() && info.procedures.empty()) { + return; + } + codegen = true; + printer->add_newline(2); + for (const auto& function : info.functions) { + print_function_declaration(function); + printer->add_text(";"); + printer->add_newline(); + } + for (const auto& function : info.procedures) { + print_function_declaration(function); + printer->add_text(";"); + printer->add_newline(); + } + codegen = false; +} + + +void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { + codegen = true; + auto parameters = node->get_arguments(); + + printer->add_newline(2); + print_function_declaration(node); + printer->add_text(" "); + printer->start_block(); + print_statement_block(node->get_statement_block().get(), false, false); + printer->add_line("return 0;"); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + +void CodegenCVisitor::print_function(ast::FunctionBlock* node) { + codegen = true; + auto name = node->get_name(); + auto return_var = "ret_" + name; + auto type = float_data_type() + " "; + + /// first rename return variable name + auto block = node->get_statement_block().get(); + RenameVisitor v(name, return_var); + block->accept(&v); + + printer->add_newline(2); + print_function_declaration(node); + printer->add_text(" "); + printer->start_block(); + printer->add_line("{}{} = 0.0;"_format(type, return_var)); + print_statement_block(block, false, false); + printer->add_line("return {};"_format(return_var)); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + + +/****************************************************************************************/ +/* Code-specific helper routines */ +/****************************************************************************************/ + + + +std::string CodegenCVisitor::process_verbatim_text(std::string text) { + c11::Driver driver; + driver.scan_string(text); + auto tokens = driver.all_tokens(); + std::string result; + for (auto& token : tokens) { + auto name = process_verbatim_token(token); + if (token == "_tqitem") { + name = "&" + name; + } + if (token == "_STRIDE") { + name = (layout == LayoutType::soa) ? "pnodecount+id" : "1"; + } + result += name; + } + return result; +} + + +std::string CodegenCVisitor::internal_method_arguments() { + if (ion_variable_struct_required()) { + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; + } else { + return "id, pnodecount, inst, data, indexes, thread, nt, v"; + } +} + + +std::string CodegenCVisitor::internal_method_parameters() { + std::string ion_var_arg; + if (ion_variable_struct_required()) { + ion_var_arg = " IonCurVar& ionvar,"; + } + return "int id, int pnodecount, {}* inst,{} double* data, " + "Datum* indexes, ThreadDatum* thread, " + "NrnThread* nt, double v"_format(instance_struct(), ion_var_arg); +} + + +std::string CodegenCVisitor::external_method_arguments() { + return "id, pnodecount, data, indexes, thread, nt, v"; +} + + +std::string CodegenCVisitor::external_method_parameters() { + return "int id, int pnodecount, double* data, Datum* indexes, " + "ThreadDatum* thread, NrnThread* nt, double v"; +} + + +std::string CodegenCVisitor::nrn_thread_arguments() { + return "id, pnodecount, data, indexes, thread, nt, v"; +} + + +std::string CodegenCVisitor::register_mechanism_arguments() { + auto nrn_cur = nrn_cur_required() ? method_name("nrn_cur") : "NULL"; + auto nrn_state = nrn_state_required() ? method_name("nrn_state") : "NULL"; + auto nrn_alloc = method_name("nrn_alloc"); + auto nrn_init = method_name("nrn_init"); + return "mechanism, {}, {}, NULL, {}, {}, first_pointer_var_index()" + ""_format(nrn_alloc, nrn_cur, nrn_state, nrn_init); +} + + +std::pair<std::string, std::string> CodegenCVisitor::read_ion_variable_name(std::string name) { + return {name, "ion_" + name}; +} + + +std::pair<std::string, std::string> CodegenCVisitor::write_ion_variable_name(std::string name) { + return {"ion_" + name, name}; +} + + +std::string CodegenCVisitor::conc_write_statement(std::string ion_name, + std::string concentration, + int index) { + auto conc_var_name = get_variable_name("ion_" + concentration); + auto style_var_name = get_variable_name("style_" + ion_name); + return "nrn_wrote_conc({}_type," + " &({})," + " {}," + " {}," + " nrn_ion_global_map," + " celsius," + " nt->_ml_list[{}_type]->_nodecount_padded)" + ""_format(ion_name, conc_var_name, index, style_var_name, ion_name); +} + + + +/** + * If mechanisms dependency level execution is enabled then certain updates + * like ionic current contributions needs to be atomically updated. In this + * case we first update current mechanism's shadow vector and then add statement + * to queue that will be used in reduction queue. + * + * @param statement Statement that might require reduction + * @param type Type of the block + * @return Original statement is reduction requires otherwise original statement + */ +std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& statement, + BlockType type) { + /// when there is no operator or rhs then that statement doesn't need shadow update + if (statement.op.empty() && statement.rhs.empty()) { + auto text = statement.lhs + ";"; + return text; + } + + /// blocks like initial doesn't use shadow update (e.g. due to wrote_conc call) + if (block_require_shadow_update(type)) { + shadow_statements.push_back(statement); + auto lhs = get_variable_name(shadow_varname(statement.lhs)); + auto rhs = statement.rhs; + auto text = "{} = {};"_format(lhs, rhs); + return text; + } + + /// return regular statement + auto lhs = get_variable_name(statement.lhs); + auto text = "{} {} {};"_format(lhs, statement.op, statement.rhs); + return text; +} + + +/****************************************************************************************/ +/* Code-specific printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenCVisitor::print_memory_layout_getter() { + printer->add_newline(2); + printer->add_line("static inline int get_memory_layout() {"); + if (layout == LayoutType::aos) { + printer->add_line(" return 1; //aos"); + } else { + printer->add_line(" return 0; //soa"); + } + printer->add_line("}"); +} + + +void CodegenCVisitor::print_first_pointer_var_index_getter() { + printer->add_newline(2); + printer->add_line("static inline int first_pointer_var_index() {"); + printer->add_line(" return {};"_format(info.first_pointer_var_index)); + printer->add_line("}"); +} + + +void CodegenCVisitor::print_num_variable_getter() { + printer->add_newline(2); + printer->add_line("static inline int num_float_variable() {"); + printer->add_line(" return {};"_format(num_float_variable())); + printer->add_line("}"); + + printer->add_newline(2); + printer->add_line("static inline int num_int_variable() {"); + printer->add_line(" return {};"_format(num_int_variable())); + printer->add_line("}"); +} + + +void CodegenCVisitor::print_net_receive_arg_size_getter() { + if (!net_receive_exist()) { + return; + } + printer->add_newline(2); + printer->add_line("static inline int num_net_receive_args() {"); + printer->add_line(" return {};"_format(info.num_net_receive_arguments)); + printer->add_line("}"); +} + + +void CodegenCVisitor::print_mech_type_getter() { + printer->add_newline(2); + printer->add_line("static inline int get_mech_type() {"); + printer->add_line(" return {};"_format(get_variable_name("mech_type"))); + printer->add_line("}"); +} + + +void CodegenCVisitor::print_memb_list_getter() { + printer->add_newline(2); + printer->add_line("static inline Memb_list* get_memb_list(NrnThread* nt) {"); + printer->add_line(" if (nt->_ml_list == NULL) {"); + printer->add_line(" return NULL;"); + printer->add_line(" }"); + printer->add_line(" return nt->_ml_list[get_mech_type()];"); + printer->add_line("}"); +} + + +void CodegenCVisitor::print_post_channel_iteration_common_code() { + if (layout == LayoutType::aos) { + printer->add_line("data = ml->data + id*{};"_format(num_float_variable())); + printer->add_line("indexes = ml->pdata + id*{};"_format(num_int_variable())); + } +} + + +void CodegenCVisitor::print_namespace_start() { + printer->add_newline(2); + printer->start_block("namespace coreneuron"); +} + + +void CodegenCVisitor::print_namespace_end() { + printer->end_block(); + printer->add_newline(); +} + + +/** + * Print getter methods used for accessing thread variables + * + * There are three types of thread variables currently considered: + * - top local thread variables + * - thread variables in the mod file + * - thread variables for solver + * + * These variables are allocated into different thread structures and have + * corresponding thread ids. Thread id start from 0. In mod2c implementation, + * thread_data_index is increased at various places and it is used to + * decide the index of thread. + */ + +void CodegenCVisitor::print_thread_getters() { + if (info.vectorize && info.derivimplicit_used) { + int tid = info.derivimplicit_var_thread_id; + int list = info.derivimplicit_list_num; + + // clang-format off + printer->add_newline(2); + printer->add_line("/** thread specific helper routines for derivimplicit */"); + + printer->add_newline(1); + printer->add_line("static inline int* deriv{}_advance(ThreadDatum* thread) {}"_format(list, "{")); + printer->add_line(" return &(thread[{}].i);"_format(tid)); + printer->add_line("}"); + + printer->add_newline(1); + printer->add_line("static inline int dith{}() {}"_format(list, "{")); + printer->add_line(" return {};"_format(tid+1)); + printer->add_line("}"); + + printer->add_newline(1); + printer->add_line("static inline void** newtonspace{}(ThreadDatum* thread) {}"_format(list, "{")); + printer->add_line(" return &(thread[{}]._pvoid);"_format(tid+2)); + printer->add_line("}"); + } + + if (info.vectorize && !info.thread_variables.empty()) { + printer->add_newline(2); + printer->add_line("/** tid for thread variables */"); + printer->add_line("static inline int thread_var_tid() {"); + printer->add_line(" return {};"_format(info.thread_var_thread_id)); + printer->add_line("}"); + } + + if (info.vectorize && !info.top_local_variables.empty()) { + printer->add_newline(2); + printer->add_line("/** tid for top local tread variables */"); + printer->add_line("static inline int top_local_var_tid() {"); + printer->add_line(" return {};"_format(info.top_local_thread_id)); + printer->add_line("}"); + } + // clang-format on +} + + + +/****************************************************************************************/ +/* Routines for returning variable name */ +/****************************************************************************************/ + + + +std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_instance) { + auto name = symbol->get_name(); + auto dimension = symbol->get_length(); + auto num_float = num_float_variable(); + auto position = position_of_float_var(name); + // clang-format off + if (symbol->is_array()) { + if (use_instance) { + auto stride = (layout == LayoutType::soa) ? dimension : num_float; + return "(inst->{}+id*{})"_format(name, stride); + } else { + auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id*{}"_format(position, dimension) : "{}"_format(position); + return "(data+{})"_format(stride); + } + } else { + if (use_instance) { + auto stride = (layout == LayoutType::soa) ? "id" : "id*{}"_format(num_float); + return "inst->{}[{}]"_format(name, stride); + } else { + auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); + return "data[{}]"_format(stride); + } + } + // clang-format on +} + + +std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, + std::string name, + bool use_instance) { + auto position = position_of_int_var(name); + auto num_int = num_int_variable(); + std::string offset; + // clang-format off + if (symbol.is_index) { + offset = std::to_string(position); + if (use_instance) { + return "inst->{}[{}]"_format(name, offset); + } else { + return "indexes[{}]"_format(offset); + } + } else if (symbol.is_integer) { + if (use_instance) { + offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id*{}+{}"_format(num_int, position); + return "inst->{}[{}]"_format(name, offset); + } else { + offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id"; + return "indexes[{}]"_format(offset); + } + } else { + offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); + if (use_instance) { + return "inst->{}[indexes[{}]]"_format(name, offset); + } else { + auto data = symbol.is_vdata ? "_vdata" : "_data"; + return "nt->{}[indexes[{}]]"_format(data, offset); + } + } + // clang-format on +} + + +std::string CodegenCVisitor::global_variable_name(SymbolType& symbol) { + return "{}_global->{}"_format(info.mod_suffix, symbol->get_name()); +} + + +std::string CodegenCVisitor::ion_shadow_variable_name(SymbolType& symbol) { + return "inst->{}[id]"_format(symbol->get_name()); +} + + +std::string CodegenCVisitor::update_if_ion_variable_name(std::string name) { + if (ion_variable_struct_required()) { + if (info.is_ion_read_variable(name)) { + return "ion_" + name; + } + if (info.is_ion_write_variable(name)) { + return "ionvar." + name; + } + if (info.is_current(name)) { + return "ionvar." + name; + } + } + return name; +} + + +/** + * Return variable name in the structure of mechanism properties + * + * @param name variable name that is being printed + * @return use_instance whether print name using Instance structure (or data array if false) + */ +std::string CodegenCVisitor::get_variable_name(std::string name, bool use_instance) { + name = update_if_ion_variable_name(name); + + // clang-format off + auto l_symbol = [&name](const SymbolType& sym) { + return name == sym->get_name(); + }; + + auto i_symbol = [&name](const IndexVariableInfo& var) { + return name == var.symbol->get_name(); + }; + // clang-format on + + /// float variable + auto f = std::find_if(float_variables.begin(), float_variables.end(), l_symbol); + if (f != float_variables.end()) { + return float_variable_name(*f, use_instance); + } + + /// integer variable + auto i = std::find_if(int_variables.begin(), int_variables.end(), i_symbol); + if (i != int_variables.end()) { + return int_variable_name(*i, name, use_instance); + } + + /// global variable + auto g = std::find_if(global_variables.begin(), global_variables.end(), l_symbol); + if (g != global_variables.end()) { + return global_variable_name(*g); + } + + /// shadow variable + auto s = std::find_if(shadow_variables.begin(), shadow_variables.end(), l_symbol); + if (s != shadow_variables.end()) { + return ion_shadow_variable_name(*s); + } + + /// otherwise return original name + return name; +} + + +/****************************************************************************************/ +/* Main printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenCVisitor::print_backend_info() { + time_t tr; + time(&tr); + auto date = std::string(asctime(localtime(&tr))); + auto version = nocmodl::version::NOCMODL_VERSION + " [" + nocmodl::version::GIT_REVISION + "]"; + + printer->add_line("/*********************************************************"); + printer->add_line("Model Name : {}"_format(info.mod_suffix)); + printer->add_line("Filename : {}"_format(info.mod_file + ".mod")); + printer->add_line("NMODL Version : {}"_format(nmodl_version())); + printer->add_line("Vectorized : {}"_format(info.vectorize)); + printer->add_line("Threadsafe : {}"_format(info.thread_safe)); + printer->add_line("Created : {}"_format(stringutils::trim(date))); + printer->add_line("Backend : {}"_format(backend_name())); + printer->add_line("NMODL Compiler : {}"_format(version)); + printer->add_line("*********************************************************/"); +} + + +void CodegenCVisitor::print_standard_includes() { + printer->add_newline(); + printer->add_line("#include <stdio.h>"); + printer->add_line("#include <stdlib.h>"); + printer->add_line("#include <math.h>"); +} + + +void CodegenCVisitor::print_coreneuron_includes() { + printer->add_newline(); + printer->add_line("#include <coreneuron/mech/cfile/scoplib.h>"); + printer->add_line("#include <coreneuron/nrnconf.h>"); + printer->add_line("#include <coreneuron/nrnoc/multicore.h>"); + printer->add_line("#include <coreneuron/nrnoc/register_mech.hpp>"); + printer->add_line("#include <coreneuron/nrniv/nrn_acc_manager.h>"); + printer->add_line("#include <coreneuron/utils/randoms/nrnran123.h>"); + printer->add_line("#include <coreneuron/nrniv/nrniv_decl.h>"); +} + + +/** + * Print all static variables at file scope + * + * Variables required for type of ion, type of point process etc. are + * of static int type. For any backend type (C,C++), it's ok to have + * these variables as file scoped static variables. + * + * Initial values of state variables (h0) are also defined as static + * variables. Note that the state could be ion variable and it could + * be also range variable. Hence lookup into symbol table before. + * + * When model is not vectorized (shouldn't be the case in coreneuron) + * the top local variables become static variables. + * + * Note that static variables are already initialized to 0. We do the + * same for some variables to keep same code as neuron. + */ +void CodegenCVisitor::print_mechanism_global_structure() { + auto float_type = float_data_type(); + printer->add_newline(2); + printer->add_line("/** all global variables */"); + printer->add_line("struct {} {}"_format(global_struct(), "{")); + printer->increase_indent(); + + if (!info.ions.empty()) { + for (const auto& ion : info.ions) { + auto name = "{}_type"_format(ion.name); + printer->add_line("int {};"_format(name)); + global_variables.push_back(make_symbol(name)); + } + } + + if (info.point_process) { + printer->add_line("int point_type;"); + global_variables.push_back(make_symbol("point_type")); + } + + if (!info.state_vars.empty()) { + for (auto& var : info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + printer->add_line("{} {};"_format(float_type, name)); + global_variables.push_back(make_symbol(name)); + } + } + } + + if (!info.vectorize) { + printer->add_line("{} v;"_format(float_type)); + global_variables.push_back(make_symbol("v")); + } + + auto& top_locals = info.top_local_variables; + if (!info.vectorize && !top_locals.empty()) { + for (auto& var : top_locals) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->add_line("{} {}[{}];"_format(float_type, name, length)); + } else { + printer->add_line("{} {};"_format(float_type, name)); + } + global_variables.push_back(var); + } + } + + if (!info.thread_variables.empty()) { + printer->add_line("int thread_data_in_use;"); + printer->add_line("{} thread_data[{}];"_format(float_type, info.thread_var_data_size)); + global_variables.push_back(make_symbol("thread_data_in_use")); + auto symbol = make_symbol("thread_data"); + symbol->set_as_array(info.thread_var_data_size); + global_variables.push_back(symbol); + } + + if (info.vectorize) { + printer->add_line("ThreadDatum* ext_call_thread;"); + global_variables.push_back(make_symbol("ext_call_thread")); + } + + printer->add_line("int reset;"); + global_variables.push_back(make_symbol("reset")); + + auto& globals = info.global_variables; + auto& constants = info.constant_variables; + + if (!globals.empty()) { + for (auto& var : globals) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->add_line("{} {}[{}];"_format(float_type, name, length)); + } else { + printer->add_line("{} {};"_format(float_type, name)); + } + global_variables.push_back(var); + } + } + + if (!constants.empty()) { + for (auto& var : constants) { + auto name = var->get_name(); + auto value_ptr = var->get_value(); + printer->add_line("{} {};"_format(float_type, name)); + global_variables.push_back(var); + } + } + + if (info.primes_size) { + printer->add_line("int* slist1;"); + printer->add_line("int* dlist1;"); + global_variables.push_back(make_symbol("slist1")); + global_variables.push_back(make_symbol("dlist1")); + if (info.derivimplicit_used) { + printer->add_line("int* slist2;"); + global_variables.push_back(make_symbol("slist2")); + } + } + + printer->add_line("int mech_type;"); + global_variables.push_back(make_symbol("mech_type")); + printer->decrease_indent(); + printer->add_line("};"); + + printer->add_newline(1); + printer->add_line("/** holds object of global variable */"); + printer->add_line("{}* {}_global;"_format(global_struct(), info.mod_suffix)); +} + + +void CodegenCVisitor::print_mechanism_info_array() { + auto variable_printer = [this](std::vector<SymbolType>& variables) { + for (auto& v : variables) { + auto name = v->get_name(); + if (!info.point_process) { + name += "_" + info.mod_suffix; + } + if (v->is_array()) { + name += "[{}]"_format(v->get_length()); + } + printer->add_line(add_escape_quote(name) + ","); + } + }; + + printer->add_newline(2); + printer->add_line("/** channel information */"); + printer->add_line("static const char *mechanism[] = {"); + printer->increase_indent(); + printer->add_line(add_escape_quote(nmodl_version()) + ","); + printer->add_line(add_escape_quote(info.mod_suffix) + ","); + variable_printer(info.range_parameter_vars); + printer->add_line("0,"); + variable_printer(info.range_dependent_vars); + printer->add_line("0,"); + variable_printer(info.state_vars); + printer->add_line("0,"); + variable_printer(info.pointer_variables); + printer->add_line("0"); + printer->decrease_indent(); + printer->add_line("};"); +} + + +/** + * Print structs that encapsulate information about scalar and + * vector elements of type global and thread variables. + */ +void CodegenCVisitor::print_global_variables_for_hoc() { + auto variable_printer = [this](std::vector<SymbolType>& variables, bool if_array, + bool if_vector) { + for (auto& variable : variables) { + if (variable->is_array() == if_array) { + auto name = get_variable_name(variable->get_name()); + auto ename = add_escape_quote(variable->get_name()); + auto length = variable->get_length(); + if (if_vector) { + printer->add_line("{}, {}, {},"_format(ename, name, length)); + } else { + printer->add_line("{}, &{},"_format(ename, name)); + } + } + } + }; + + auto globals = info.global_variables; + auto thread_vars = info.thread_variables; + + printer->add_newline(2); + printer->add_line("/** connect global (scalar) variables to hoc -- */"); + printer->add_line("static DoubScal hoc_scalar_double[] = {"); + printer->increase_indent(); + variable_printer(globals, false, false); + variable_printer(thread_vars, false, false); + printer->add_line("0, 0"); + printer->decrease_indent(); + printer->add_line("};"); + + printer->add_newline(2); + printer->add_line("/** connect global (array) variables to hoc -- */"); + printer->add_line("static DoubVec hoc_vector_double[] = {"); + printer->increase_indent(); + variable_printer(globals, true, true); + variable_printer(thread_vars, true, true); + printer->add_line("0, 0, 0"); + printer->decrease_indent(); + printer->add_line("};"); +} + + +/** + * Print register function for mechanisms + * + * Every mod file has register function to connect with the simulator. + * Various information about mechanism and callbacks get registered with + * the simulator using suffix_reg() function. + * + * Here are details: + * - setup_global_variables function used to create vectors necessary for specific + * solvers like euler and derivimplicit. All global variables are initialized as well. + * We should exclude that callback based on the solver, watch statements. + * - If nrn_get_mechtype is < -1 means that mechanism is not used in the + * context of neuron execution and hence could be ignored in coreneuron + * execution. + * - Each mechanism could have different layout and hence we register the + * layout with the simulator. In practice all mechanisms have same layout. + * - Ions are internally defined and their types can be queried similar to + * other mechanisms. + * - hoc_register_var may not be needed in the context of coreneuron + * - We assume net receive buffer is on. This is because generated code is + * compatible for cpu as well as gpu target. + */ +void CodegenCVisitor::print_mechanism_register() { + printer->add_newline(2); + printer->add_line("/** register channel with the simulator */"); + printer->start_block("void _{}_reg() "_format(info.mod_file)); + + /// allocate global variables + printer->add_line("setup_global_variables();"); + + /// type related information + auto mech_type = get_variable_name("mech_type"); + auto suffix = add_escape_quote(info.mod_suffix); + printer->add_newline(); + printer->add_line("int mech_type = nrn_get_mechtype({});"_format(suffix)); + printer->add_line("{} = mech_type;"_format(mech_type)); + printer->add_line("if (mech_type == -1) {"); + printer->add_line(" return;"); + printer->add_line("}"); + + printer->add_newline(); + printer->add_line("_nrn_layout_reg(mech_type, get_memory_layout());"); + + /// register mechanism + auto args = register_mechanism_arguments(); + auto nobjects = num_thread_objects(); + if (info.point_process) { + printer->add_line("point_register_mech({}, NULL, NULL, {});"_format(args, nobjects)); + } else { + printer->add_line("register_mech({}, {});"_format(args, nobjects)); + } + + /// types for ion + for (const auto& ion : info.ions) { + auto type = get_variable_name(ion.name + "_type"); + auto name = add_escape_quote(ion.name + "_ion"); + printer->add_line(type + " = nrn_get_mechtype(" + name + ");"); + } + printer->add_newline(); + + /** + * If threads are used then memory is allocated in setup_global_variables. + * Register callbacks for thread allocation and cleanup. Note that thread_data_index + * represent total number of thread used minus 1 (i.e. index of last thread). + */ + if (info.vectorize && info.thread_data_index) { + auto name = get_variable_name("ext_call_thread"); + printer->add_line("thread_mem_init({});"_format(name)); + printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); + } + if (info.thread_callback_register) { + printer->add_line("_nrn_thread_reg0(mech_type, thread_mem_cleanup);"); + printer->add_line("_nrn_thread_reg1(mech_type, thread_mem_init);"); + } + if (info.emit_table_thread) { + printer->add_line("nrn_thread_table_reg(mech_type, check_table_thread);"); + } + + /// register read/write callbacks for pointers + if (info.bbcore_pointer_used) { + printer->add_line("hoc_reg_bbcore_read(mech_type, bbcore_read);"); + printer->add_line("hoc_reg_bbcore_write(mech_type, bbcore_write);"); + } + + /// register size of double and int elements + // clang-format off + printer->add_line("hoc_register_prop_size(mech_type, num_float_variable(), num_int_variable());"); + // clang-format on + + /// register semantics for index variables + for (auto& semantic : info.semantics) { + auto args = "mech_type, {}, {}"_format(semantic.index, add_escape_quote(semantic.name)); + printer->add_line("hoc_register_dparam_semantics({});"_format(args)); + } + + if (info.write_concentration) { + printer->add_line("nrn_writes_conc(mech_type, 0);"); + } + + /// register various information for point process type + if (info.net_event_used) { + printer->add_line("add_nrn_has_net_event(mech_type);"); + } + if (info.artificial_cell) { + printer->add_line("add_nrn_artcell(mech_type, {});"_format(info.tqitem_index)); + } + if (net_receive_buffering_required()) { + printer->add_line("hoc_register_net_receive_buffering(net_buf_receive, mech_type);"); + } + if (info.num_net_receive_arguments) { + printer->add_line("pnt_receive[mech_type] = {};"_format(method_name("net_receive"))); + printer->add_line("pnt_receive_size[mech_type] = num_net_receive_args();"); + if (info.net_receive_initial_node != nullptr) { + printer->add_line("pnt_receive_init[mech_type] = net_init;"); + } + } + if (info.net_event_used || info.net_send_used) { + printer->add_line("hoc_register_net_send_buffering(mech_type);"); + } + + /// register variables for hoc + printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_thread_memory_callbacks() { + if (!info.thread_callback_register) { + return; + } + + /// thread_mem_init callback + printer->add_newline(2); + printer->add_line("/** thread memory allocation callback */"); + printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); + + if (info.vectorize && info.derivimplicit_used) { + printer->add_line("thread[dith{}()].pval = NULL;"_format(info.derivimplicit_list_num)); + } + if (info.vectorize && info.top_local_thread_size) { + auto length = info.top_local_thread_size; + auto allocation = "(double*)mem_alloc({}, sizeof(double))"_format(length); + auto line = "thread[top_local_var_tid()].pval = {};"_format(allocation); + printer->add_line(line); + } + if (info.thread_var_data_size) { + auto length = info.thread_var_data_size; + auto thread_data = get_variable_name("thread_data"); + auto thread_data_in_use = get_variable_name("thread_data_in_use"); + auto allocation = "(double*)mem_alloc({}, sizeof(double))"_format(length); + printer->add_line("if ({}) {}"_format(thread_data_in_use, "{")); + printer->add_line(" thread[thread_var_tid()].pval = {};"_format(allocation)); + printer->add_line("} else {"); + printer->add_line(" thread[thread_var_tid()].pval = {};"_format(thread_data)); + printer->add_line(" {} = 1;"_format(thread_data_in_use)); + printer->add_line("}"); + } + printer->end_block(); + printer->add_newline(); + + + /// thread_mem_cleanup callback + printer->add_newline(2); + printer->add_line("/** thread memory cleanup callback */"); + printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); + + if (info.vectorize && info.derivimplicit_used) { + int n = info.derivimplicit_list_num; + printer->add_line("free(thread[dith{}()].pval);"_format(n)); + printer->add_line("nrn_destroy_newtonspace(*newtonspace{}(thread));"_format(n)); + } + if (info.top_local_thread_size) { + auto line = "free(thread[top_local_var_tid()].pval);"; + printer->add_line(line); + } + if (info.thread_var_data_size) { + auto thread_data = get_variable_name("thread_data"); + auto thread_data_in_use = get_variable_name("thread_data_in_use"); + printer->add_line("if (thread[thread_var_tid()].pval == {}) {}"_format(thread_data, "{")); + printer->add_line(" {} = 0;"_format(thread_data_in_use)); + printer->add_line("} else {"); + printer->add_line(" free(thread[thread_var_tid()].pval);"); + printer->add_line("}"); + } + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_mechanism_var_structure() { + auto float_type = float_data_type(); + auto int_type = int_data_type(); + printer->add_newline(2); + printer->add_line("/** all mechanism instance variables */"); + printer->start_block("struct {} "_format(instance_struct())); + for (auto& var : float_variables) { + auto name = var->get_name(); + printer->add_line("{}* {};"_format(float_type, name)); + } + for (auto& var : int_variables) { + auto name = var.symbol->get_name(); + if (var.is_index | var.is_integer) { + printer->add_line("{}* {};"_format(int_type, name)); + } else { + auto type = var.is_vdata ? "void*" : float_data_type(); + printer->add_line("{}* {};"_format(type, name)); + } + } + if (channel_task_dependency_enabled()) { + for (auto& var : shadow_variables) { + auto name = var->get_name(); + printer->add_line("{}* {};"_format(float_type, name)); + } + } + printer->end_block(); + printer->add_text(";"); + printer->add_newline(); +} + + +void CodegenCVisitor::print_ion_variables_structure() { + if (!ion_variable_struct_required()) { + return; + } + printer->add_newline(2); + printer->add_line("/** ion write variables */"); + printer->start_block("struct IonCurVar"); + for (auto& ion : info.ions) { + for (auto& var : ion.writes) { + printer->add_line("{} {};"_format(float_data_type(), var)); + } + } + for (auto& var : info.currents) { + if (!info.is_ion_variable(var)) { + printer->add_line("{} {};"_format(float_data_type(), var)); + } + } + printer->end_block(); + printer->add_text(";"); + printer->add_newline(); +} + + + +void CodegenCVisitor::print_global_variable_setup() { + std::vector<std::string> allocated_variables; + + printer->add_newline(2); + printer->add_line("/** initialize global variables */"); + printer->start_block("static inline void setup_global_variables() "); + + printer->add_line("static int setup_done = 0;"); + printer->add_line("if (setup_done) {"); + printer->add_line(" return;"); + printer->add_line("}"); + + printer->add_newline(1); + auto allocation = "({0}*) mem_alloc(1, sizeof({0}))"_format(global_struct()); + printer->add_line("{0}_global = {1};"_format(info.mod_suffix, allocation)); + + /// offsets for state variables + if (info.primes_size) { + auto slist1 = get_variable_name("slist1"); + auto dlist1 = get_variable_name("dlist1"); + auto n = info.primes_size; + printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(slist1, n)); + printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(dlist1, n)); + allocated_variables.push_back(slist1); + allocated_variables.push_back(dlist1); + + int id = 0; + for (auto& prime : info.prime_variables_by_order) { + auto name = prime->get_name(); + printer->add_line("{}[{}] = {};"_format(slist1, id, position_of_float_var(name))); + printer->add_line("{}[{}] = {};"_format(dlist1, id, position_of_float_var("D" + name))); + id++; + } + } + + /// additional list for derivimplicit method + if (info.derivimplicit_used) { + auto primes = program_symtab->get_variables_with_properties(NmodlInfo::prime_name); + auto slist2 = get_variable_name("slist2"); + auto nprimes = info.primes_size; + printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(slist2, nprimes)); + int id = 0; + for (auto& variable : primes) { + auto name = variable->get_name(); + printer->add_line("{}[{}] = {};"_format(slist2, id, position_of_float_var(name))); + id++; + } + allocated_variables.push_back(slist2); + } + + /// memory for thread member + if (info.vectorize && info.thread_data_index) { + auto n = info.thread_data_index; + auto alloc = "(ThreadDatum*) mem_alloc({}, sizeof(ThreadDatum))"_format(n); + auto name = get_variable_name("ext_call_thread"); + printer->add_line("{} = {};"_format(name, alloc)); + allocated_variables.push_back(name); + } + + /// initialize global variables + for (auto& var : info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + auto global_name = get_variable_name(name); + printer->add_line("{} = 0.0;"_format(global_name)); + } + } + if (!info.vectorize) { + printer->add_line("{} = 0.0;"_format(get_variable_name("v"))); + } + if (!info.thread_variables.empty()) { + printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); + } + + /// initialize global variables + for (auto& var : info.global_variables) { + if (!var->is_array()) { + auto name = get_variable_name(var->get_name()); + double value = 0; + auto value_ptr = var->get_value(); + if (value_ptr != nullptr) { + value = *value_ptr; + } + printer->add_line("{} = {};"_format(name, double_to_string(value))); + } + } + + /// initialize constant variables + for (auto& var : info.constant_variables) { + auto name = get_variable_name(var->get_name()); + auto value_ptr = var->get_value(); + double value = 0; + if (value_ptr != nullptr) { + value = *value_ptr; + } + printer->add_line("{} = {};"_format(name, double_to_string(value))); + } + + printer->add_newline(); + printer->add_line("setup_done = 1;"); + + printer->end_block(); + printer->add_newline(); + + printer->add_newline(2); + printer->add_line("/** free global variables */"); + printer->start_block("static inline void free_global_variables() "); + if (allocated_variables.empty()) { + printer->add_line("// do nothing"); + } else { + for (auto& var : allocated_variables) { + printer->add_line("mem_free({});"_format(var)); + } + } + printer->end_block(); + printer->add_newline(); +} + +void CodegenCVisitor::print_shadow_vector_setup() { + printer->add_newline(2); + printer->add_line("/** allocate and initialize shadow vector */"); + auto args = "{}* inst, Memb_list* ml"_format(instance_struct()); + printer->start_block("static inline void setup_shadow_vectors({}) "_format(args)); + if (channel_task_dependency_enabled()) { + printer->add_line("int nodecount = ml->nodecount;"); + for (auto& var : shadow_variables) { + auto name = var->get_name(); + auto type = float_data_type(); + auto allocation = "({0}*) mem_alloc(nodecount, sizeof({0}))"_format(type); + printer->add_line("inst->{0} = {1};"_format(name, allocation)); + } + } + printer->end_block(); + printer->add_newline(); + + printer->add_newline(2); + printer->add_line("/** free shadow vector */"); + args = "{}* inst"_format(instance_struct()); + printer->start_block("static inline void free_shadow_vectors({}) "_format(args)); + if (channel_task_dependency_enabled()) { + for (auto& var : shadow_variables) { + auto name = var->get_name(); + printer->add_line("mem_free(inst->{});"_format(name)); + } + } + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_instance_variable_setup() { + if (channel_task_dependency_enabled() && !shadow_variables.empty()) { + print_shadow_vector_setup(); + } + printer->add_newline(2); + printer->add_line("/** initialize mechanism instance variables */"); + printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml) "); + printer->add_line("{0}* inst = ({0}*) mem_alloc(1, sizeof({0}));"_format(instance_struct())); + if (channel_task_dependency_enabled() && !shadow_variables.empty()) { + printer->add_line("setup_shadow_vectors(inst, ml);"); + } + + std::string stride; + if (layout == LayoutType::soa) { + printer->add_line("int pnodecount = ml->_nodecount_padded;"); + stride = "*pnodecount"; + } + + printer->add_line("Datum* indexes = ml->pdata;"); + int id = 0; + for (auto& var : float_variables) { + printer->add_line("inst->{} = ml->data + {}{};"_format(var->get_name(), id, stride)); + id += var->get_length(); + } + + for (auto& var : int_variables) { + auto name = var.symbol->get_name(); + if (var.is_index | var.is_integer) { + printer->add_line("inst->{} = ml->pdata;"_format(name)); + } else { + auto data = var.is_vdata ? "_vdata" : "_data"; + printer->add_line("inst->{} = nt->{};"_format(name, data)); + } + } + + printer->add_line("ml->instance = (void*) inst;"); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_initial_block(InitialBlock* node) { + if (info.artificial_cell) { + printer->add_line("double v = 0.0;"); + } else { + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + } + + if (ion_variable_struct_required()) { + printer->add_line("IonCurVar ionvar = {0};"); + } + + /// read ion statements + auto read_statements = ion_read_statements(BlockType::Initial); + for (auto& statement : read_statements) { + printer->add_line(statement); + } + + /// initialize state variables (excluding ion state) + for (auto& var : info.state_vars) { + auto name = var->get_name(); + auto lhs = get_variable_name(name); + auto rhs = get_variable_name(name + "0"); + printer->add_line("{} = {};"_format(lhs, rhs)); + } + + /// initial block + if (node) { + auto block = node->get_statement_block(); + print_statement_block(block.get(), false, false); + } + + /// write ion statements + auto write_statements = ion_write_statements(BlockType::Initial); + for (auto& statement : write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Initial); + printer->add_line(text); + } +} + + +void CodegenCVisitor::print_global_function_common_code(BlockType type) { + std::string method; + if (type == BlockType::Initial) { + method = method_name("nrn_init"); + } else if (type == BlockType::State) { + method = method_name("nrn_state"); + } else if (type == BlockType::Equation) { + method = method_name("nrn_cur"); + } + auto args = "NrnThread* nt, Memb_list* ml, int type"; + + print_global_method_annotation(); + printer->start_block("void {}({})"_format(method, args)); + print_kernel_data_present_annotation_block_begin(); + printer->add_line("int nodecount = ml->nodecount;"); + printer->add_line("int pnodecount = ml->_nodecount_padded;"); + printer->add_line("int* node_index = ml->nodeindices;"); + printer->add_line("double* data = ml->data;"); + printer->add_line("double* voltage = nt->_actual_v;"); + + if (type == BlockType::Equation) { + printer->add_line("double* vec_rhs = nt->_actual_rhs;"); + printer->add_line("double* vec_d = nt->_actual_d;"); + print_rhs_d_shadow_variables(); + } + printer->add_line("Datum* indexes = ml->pdata;"); + printer->add_line("ThreadDatum* thread = ml->_thread;"); + + if (type == BlockType::Initial) { + printer->add_newline(); + printer->add_line("setup_instance(nt, ml);"); + } + printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + printer->add_newline(1); +} + + +void CodegenCVisitor::print_nrn_init() { + codegen = true; + printer->add_newline(2); + printer->add_line("/** initialize channel */"); + print_global_function_common_code(BlockType::Initial); + if (info.derivimplicit_used) { + printer->add_newline(); + int nequation = info.num_equations; + int list_num = info.derivimplicit_list_num; + // clang-format off + printer->add_line("*deriv{}_advance(thread) = 0;"_format(list_num)); + printer->add_line("if (*newtonspace{}(thread) == NULL) {}"_format(list_num, "{")); + printer->add_line(" *newtonspace{}(thread) = nrn_cons_newtonspace({}, pnodecount);"_format(list_num, nequation)); + printer->add_line(" thread[dith{}()].pval = makevector(2*{}*pnodecount*sizeof(double));"_format(list_num, nequation)); + printer->add_line("}"); + // clang-format on + } + + print_channel_iteration_tiling_block_begin(BlockType::Initial); + print_channel_iteration_block_begin(); + print_post_channel_iteration_common_code(); + if (info.net_receive_node != nullptr) { + printer->add_line("{} = -1e20;"_format(get_variable_name("tsave"))); + } + print_initial_block(info.initial_node); + print_channel_iteration_block_end(); + print_shadow_reduction_statements(); + print_channel_iteration_tiling_block_end(); + + if (info.derivimplicit_used) { + printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); + } + print_kernel_data_present_annotation_block_end(); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + +void CodegenCVisitor::print_nrn_alloc() { + printer->add_newline(2); + auto method = method_name("nrn_alloc"); + printer->start_block("static void {}(double* data, Datum* indexes, int type) "_format(method)); + printer->add_line("// do nothing"); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_net_receive_common_code(Block* node) { + printer->add_line("int tid = pnt->_tid;"); + printer->add_line("int id = pnt->_i_instance;"); + printer->add_line("double v = 0;"); + printer->add_line("NrnThread* nt = nrn_threads + tid;"); + printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); + printer->add_line("int nodecount = ml->nodecount;"); + printer->add_line("int pnodecount = ml->_nodecount_padded;"); + printer->add_line("double* data = ml->data;"); + printer->add_line("double* weights = nt->weights;"); + printer->add_line("Datum* indexes = ml->pdata;"); + printer->add_line("ThreadDatum* thread = ml->_thread;"); + printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + + /// rename variables but need to see if they are actually used + auto arguments = info.net_receive_node->get_arguments(); + if (!arguments.empty()) { + int i = 0; + printer->add_newline(); + for (auto& argument : arguments) { + auto name = argument->get_name(); + VarUsageVisitor vu; + auto var_used = vu.variable_used(node, "(*" + name + ")"); + if (var_used) { + auto statement = "double* {} = weights + weight_index + {};"_format(name, i++); + printer->add_line(statement); + RenameVisitor vr(name, "*" + name); + node->visit_children(&vr); + } + } + } +} + + +void CodegenCVisitor::print_net_send_call(FunctionCall* node) { + auto arguments = node->get_arguments(); + auto tqitem = get_variable_name("tqitem"); + std::string weight_index = "weight_index"; + std::string pnt = "pnt"; + + /// for non-net-receieve functions there is no weight index argument + /// and artificial cell is in vdata which is void** + if (!printing_net_receive) { + weight_index = "-1"; + auto var = get_variable_name("point_process"); + if (info.artificial_cell) { + pnt = "(Point_process*)" + var; + } + } + + /// artificial cells don't use spike buffering + // clang-format off + if (info.artificial_cell) { + printer->add_text("artcell_net_send(&{}, {}, {}, t+"_format(tqitem, weight_index, pnt)); + } else { + auto point_process = get_variable_name("point_process"); + printer->add_text("net_send_buffering("); + printer->add_text("ml->_net_send_buffer, 0, {}, {}, {}, t+"_format(tqitem, weight_index, point_process)); + } + // clang-format off + print_vector_elements(arguments, ", "); + printer->add_text(")"); +} + + +void CodegenCVisitor::print_net_event_call(FunctionCall* node) { + auto arguments = node->get_arguments(); + if (info.artificial_cell) { + printer->add_text("net_event(pnt, "); + print_vector_elements(arguments, ", "); + } else { + auto point_process = get_variable_name("point_process"); + printer->add_text("net_send_buffering("); + printer->add_text("ml->_net_send_buffer, 1, -1, -1, {}, "_format(point_process)); + print_vector_elements(arguments, ", "); + printer->add_text(", 0.0"); + } + printer->add_text(")"); +} + + +void CodegenCVisitor::print_net_init_kernel() { + auto node = info.net_receive_initial_node; + if (node == nullptr) { + return; + } + codegen = true; + auto args = "Point_process* pnt, int weight_index, double flag"; + printer->add_newline(2); + printer->add_line("/** initialize block for net receive */"); + printer->start_block("static void net_init({}) "_format(args)); + auto block = node->get_statement_block().get(); + if (block->get_statements().empty()) { + printer->add_line("// do nothing"); + } else { + print_net_receive_common_code(node); + print_statement_block(block, false, false); + } + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + +void CodegenCVisitor::print_net_receive_buffer_kernel() { + if (!net_receive_required() || info.artificial_cell) { + return; + } + printer->add_newline(2); + printer->start_block("static inline void net_buf_receive(NrnThread* nt)"); + printer->add_line("Memb_list* ml = get_memb_list(nt);"); + printer->add_line("if (ml == NULL) {"); + printer->add_line(" return;"); + printer->add_line("}"); + printer->add_newline(); + + auto net_receive = method_name("net_receive_kernel"); + printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); + printer->add_line("int count = nrb->_displ_cnt;"); + printer->add_line("for (int i = 0; i < count; i++) {"); + printer->add_line(" int start = nrb->_displ[i];"); + printer->add_line(" int end = nrb->_displ[i+1];"); + printer->add_line(" for (int j = start; j < end; j++) {"); + printer->add_line(" int index = nrb->_nrb_index[j];"); + printer->add_line(" int offset = nrb->_pnt_index[index];"); + printer->add_line(" double t = nrb->_nrb_t[i];"); + printer->add_line(" int weight_index = nrb->_weight_index[i];"); + printer->add_line(" double flag = nrb->_nrb_flag[i];"); + printer->add_line(" Point_process* point_process = nt->pntprocs + offset;"); + printer->add_line(" {}(t, point_process, weight_index, flag);"_format(net_receive)); + printer->add_line(" }"); + printer->add_line("}"); + + if (info.net_send_used || info.net_event_used) { + printer->add_newline(); + printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); + printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); + printer->add_line(" int type = nsb->_sendtype[i];"); + printer->add_line(" int tid = nt->id;"); + printer->add_line(" double t = nsb->_nsb_t[i];"); + printer->add_line(" double flag = nsb->_nsb_flag[i];"); + printer->add_line(" int vdata_index = nsb->_vdata_index[i];"); + printer->add_line(" int weight_index = nsb->_weight_index[i];"); + printer->add_line(" int point_index = nsb->_pnt_index[i];"); + // clang-format off + printer->add_line(" net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag);"); + // clang-format on + printer->add_line("}"); + } + + printer->add_newline(); + if (info.net_send_used || info.net_event_used) { + printer->add_line("nsb->_cnt = 0;"); + } + printer->add_line("nrb->_displ_cnt = 0;"); + printer->add_line("nrb->_cnt = 0;"); + + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_net_send_buffer_kernel() { + if (!net_send_buffer_required()) { + return; + } + + auto error = add_escape_quote("Error : netsend buffer size (%d) exceeded\\n"); + printer->add_newline(2); + print_device_method_annotation(); + auto args = + "NetSendBuffer_t* nsb, int type, int vdata_index, " + "int weight_index, int point_index, double t, double flag"; + printer->start_block("static inline void net_send_buffering({}) "_format(args)); + printer->add_line("int i = nsb->_cnt;"); + printer->add_line("nsb->_cnt++;"); + printer->add_line("if(nsb->_cnt >= nsb->_size) {"); + printer->add_line(" printf({}, nsb->_cnt);"_format(error)); + printer->add_line(" abort();"); + printer->add_line("}"); + printer->add_line("nsb->_sendtype[i] = type;"); + printer->add_line("nsb->_vdata_index[i] = vdata_index;"); + printer->add_line("nsb->_weight_index[i] = weight_index;"); + printer->add_line("nsb->_pnt_index[i] = point_index;"); + printer->add_line("nsb->_nsb_t[i] = t;"); + printer->add_line("nsb->_nsb_flag[i] = flag;"); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_net_receive() { + if (!net_receive_required()) { + return; + } + auto node = info.net_receive_node; + + /// rename arguments but need to see if they are actually used + auto arguments = node->get_arguments(); + for (auto& argument : arguments) { + auto name = argument->get_name(); + auto var_used = VarUsageVisitor().variable_used(node, name); + if (var_used) { + RenameVisitor vr(name, "(*" + name + ")"); + node->get_statement_block()->visit_children(&vr); + } + } + + codegen = true; + printing_net_receive = true; + std::string function_name = method_name("net_receive_kernel"); + std::string function_arguments = "double t, Point_process* pnt, int weight_index, double flag"; + if (info.artificial_cell) { + function_name = method_name("net_receive"); + function_arguments = "Point_process* pnt, int weight_index, double flag"; + } + + /// net receive kernel + printer->add_newline(2); + printer->start_block("static void {}({}) "_format(function_name, function_arguments)); + print_net_receive_common_code(node); + printer->add_indent(); + node->get_statement_block()->accept(this); + printer->add_newline(); + printer->end_block(); + printer->add_newline(); + + /// net receive function + if (!info.artificial_cell) { + function_name = method_name("net_receive"); + function_arguments = "Point_process* pnt, int weight_index, double flag"; + printer->add_newline(2); + printer->start_block("static void {}({}) "_format(function_name, function_arguments)); + printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); + printer->add_line("Memb_list* ml = get_memb_list(nt);"); + printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); + printer->add_line("if (nrb->_cnt >= nrb->_size) {"); + printer->add_line(" realloc_net_receive_buffer(nt, ml);"); + printer->add_line("}"); + printer->add_line("int id = nrb->_cnt;"); + printer->add_line("nrb->_pnt_index[id] = pnt-nt->pntprocs;"); + printer->add_line("nrb->_weight_index[id] = weight_index;"); + printer->add_line("nrb->_nrb_t[id] = nt->_t;"); + printer->add_line("nrb->_nrb_flag[id] = flag;"); + printer->add_line("nrb->_cnt++;"); + printer->end_block(); + printer->add_newline(); + } + printing_net_receive = false; + codegen = false; +} + + +/** + * When euler method is used then derivative block is printed as separate function + * which will be used for the callback from euler_thread function. Otherwise, derivative + * block is printed as part of nrn_state itself. + */ +void CodegenCVisitor::print_derivative_kernel_for_euler() { + assert(info.solve_node->is_derivative_block()); + auto node = info.solve_node; + codegen = true; + auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); + auto arguments = external_method_parameters(); + + printer->add_newline(2); + printer->start_block("int {}({})"_format(node->get_name(), arguments)); + printer->add_line(instance); + print_statement_block(node->get_statement_block().get(), false, false); + printer->add_line("return 0;"); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + + +/** + * Todo: data is not derived. Need to add instance into instance struct? + * data used here is wrong in AoS because as in original implementation, + * data is not incremented every iteration for AoS. May be better to derive + * actual variable names? [resolved now?] + * slist needs to added as local variable + */ +void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { + assert(info.solve_node->is_derivative_block()); + auto node = info.solve_node; + codegen = true; + + auto ext_args = external_method_arguments(); + auto ext_params = external_method_parameters(); + auto suffix = info.mod_suffix; + auto list_num = info.derivimplicit_list_num; + auto solve_block_name = info.solve_block_name; + auto primes_size = info.primes_size; + auto stride = (layout == LayoutType::aos) ? "" : "*pnodecount+id"; + + printer->add_newline(2); + // clang-format off + printer->start_block("int {}_{}({})"_format(node->get_name(), suffix, ext_params)); + auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); + auto slist1 = "int* slist{} = {};"_format(list_num, get_variable_name("slist{}"_format(list_num))); + auto slist2 = "int* slist{} = {};"_format(list_num+1, get_variable_name("slist{}"_format(list_num+1))); + auto dlist1 = "int* dlist{} = {};"_format(list_num, get_variable_name("dlist{}"_format(list_num))); + auto dlist2 = "double* dlist{} = (double*) thread[dith{}()].pval + ({}*pnodecount);"_format(list_num + 1, list_num, info.primes_size); + + printer->add_line(instance); + printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); + printer->add_line(slist1); + printer->add_line(slist2); + printer->add_line(dlist2); + printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); + printer->add_line(" savstate{}[i{}] = data[slist{}[i]{}];"_format(list_num, stride, list_num, stride)); + printer->add_line("}"); + + auto argument = "{}, slist{}, derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, solve_block_name, suffix, list_num + 1, ext_args); + printer->add_line("int reset = nrn_newton_thread(*newtonspace{}(thread), {});"_format(list_num, argument)); + printer->add_line("return reset;"); + printer->end_block(); + printer->add_newline(); + + printer->add_newline(2); + printer->start_block("int newton_{}_{}({}) "_format(node->get_name(), info.mod_suffix, external_method_parameters())); + printer->add_line(instance); + printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); + printer->add_line(slist1); + printer->add_line(dlist1); + printer->add_line(dlist2); + print_statement_block(node->get_statement_block().get(), false, false); + printer->add_line("int counter = -1;"); + printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); + printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); + printer->add_line(" dlist{0}[(++counter){1}] = data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/dt;"_format(list_num + 1, stride, list_num)); + printer->add_line(" }"); + printer->add_line(" else {"); + printer->add_line(" dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];"_format(list_num + 1, stride, list_num)); + printer->add_line(" }"); + printer->add_line("}"); + printer->add_line("return 0;"); + printer->end_block(); + // clang-format on + codegen = false; +} + + + +/****************************************************************************************/ +/* Print nrn_state routine */ +/****************************************************************************************/ + + +void CodegenCVisitor::print_nrn_state() { + if (!nrn_state_required()) { + return; + } + codegen = true; + + if (info.euler_used) { + print_derivative_kernel_for_euler(); + } + + if (info.derivimplicit_used) { + print_derivative_kernel_for_derivimplicit(); + } + + printer->add_newline(2); + printer->add_line("/** update state */"); + print_global_function_common_code(BlockType::State); + print_channel_iteration_tiling_block_begin(BlockType::State); + print_channel_iteration_block_begin(); + print_post_channel_iteration_common_code(); + + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + if (ion_variable_struct_required()) { + printer->add_line("IonCurVar ionvar = {0};"); + } + + auto read_statements = ion_read_statements(BlockType::State); + for (auto& statement : read_statements) { + printer->add_line(statement); + } + + auto thread_args = external_method_arguments(); + auto num_primes = info.num_primes; + auto suffix = info.mod_suffix; + auto block_name = info.solve_block_name; + auto num = info.derivimplicit_used ? info.derivimplicit_list_num : info.euler_list_num; + auto slist = get_variable_name("slist{}"_format(num)); + auto dlist = get_variable_name("dlist{}"_format(num)); + + if (info.derivimplicit_used) { + auto args = + "{}, {}, {}, derivimplicit_{}_{}, {}" + ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); + auto statement = "derivimplicit_thread({});"_format(args); + printer->add_line(statement); + } else if (info.euler_used) { + auto args = + "{}, {}, {}, euler_{}_{}, {}" + ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); + auto statement = "euler_thread({});"_format(args); + printer->add_line(statement); + } else { + if (info.solve_node != nullptr) { + auto block = info.solve_node->get_statement_block(); + print_statement_block(block.get(), false, false); + } + } + + if (info.currents.empty() && info.breakpoint_node != nullptr) { + auto block = info.breakpoint_node->get_statement_block(); + print_statement_block(block.get(), false, false); + } + + auto write_statements = ion_write_statements(BlockType::State); + for (auto& statement : write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::State); + printer->add_line(text); + } + print_channel_iteration_block_end(); + if (!shadow_statements.empty()) { + print_shadow_reduction_block_begin(); + print_shadow_reduction_statements(); + print_shadow_reduction_block_end(); + } + print_channel_iteration_tiling_block_end(); + + print_kernel_data_present_annotation_block_end(); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + +/****************************************************************************************/ +/* Print nrn_cur related routines */ +/****************************************************************************************/ + + +void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { + auto args = internal_method_parameters(); + auto block = node->get_statement_block().get(); + printer->add_newline(2); + printer->start_block("static inline double nrn_current({})"_format(args)); + printer->add_line("double current = 0.0;"); + print_statement_block(block, false, false); + for (auto& current : info.currents) { + auto name = get_variable_name(current); + printer->add_line("current += {};"_format(name)); + } + printer->add_line("return current;"); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { + auto block = node->get_statement_block(); + print_statement_block(block.get(), false, false); + if (!info.currents.empty()) { + std::string sum; + for (const auto& current : info.currents) { + sum += breakpoint_current(current); + if (¤t != &info.currents.back()) { + sum += "+"; + } + } + printer->add_line("double rhs = {};"_format(sum)); + } + if (!info.conductances.empty()) { + std::string sum; + for (const auto& conductance : info.conductances) { + sum += breakpoint_current(conductance.variable); + if (&conductance != &info.conductances.back()) { + sum += "+"; + } + } + printer->add_line("double g = {};"_format(sum)); + } + for (const auto& conductance : info.conductances) { + if (!conductance.ion.empty()) { + auto lhs = "ion_di" + conductance.ion + "dv"; + auto rhs = get_variable_name(conductance.variable); + auto statement = ShadowUseStatement{lhs, "+=", rhs}; + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + } +} + + +void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { + printer->add_line("double g = nrn_current({}+0.001);"_format(internal_method_arguments())); + for (auto& ion : info.ions) { + for (auto& var : ion.writes) { + if (ion.is_ionic_current(var)) { + auto name = get_variable_name(var); + printer->add_line("double di{} = {};"_format(ion.name, name)); + } + } + } + printer->add_line("double rhs = nrn_current({});"_format(internal_method_arguments())); + printer->add_line("g = (g-rhs)/0.001;"); + for (auto& ion : info.ions) { + for (auto& var : ion.writes) { + if (ion.is_ionic_current(var)) { + auto lhs = "ion_di" + ion.name + "dv"; + auto rhs = "(di{}-{})/0.001"_format(ion.name, get_variable_name(var)); + if (info.point_process) { + auto area = get_variable_name(node_area); + rhs += "*1.e2/{}"_format(area); + } + auto statement = ShadowUseStatement{lhs, "+=", rhs}; + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + } + } +} + + +void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + if (ion_variable_struct_required()) { + printer->add_line("IonCurVar ionvar = {0};"); + } + + auto read_statements = ion_read_statements(BlockType::Equation); + for (auto& statement : read_statements) { + printer->add_line(statement); + } + + if (info.conductances.empty()) { + print_nrn_cur_non_conductance_kernel(); + } else { + print_nrn_cur_conductance_kernel(node); + } + + auto write_statements = ion_write_statements(BlockType::Equation); + for (auto& statement : write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + + if (info.point_process) { + auto area = get_variable_name(node_area); + printer->add_line("double mfactor = 1.e2/{};"_format(area)); + printer->add_line("g = g*mfactor;"); + printer->add_line("rhs = rhs*mfactor;"); + } +} + + +void CodegenCVisitor::print_nrn_cur() { + if (!nrn_cur_required()) { + return; + } + + codegen = true; + if (info.conductances.empty()) { + print_nrn_current(info.breakpoint_node); + } + + printer->add_newline(2); + printer->add_line("/** update current */"); + print_global_function_common_code(BlockType::Equation); + print_channel_iteration_tiling_block_begin(BlockType::Equation); + print_channel_iteration_block_begin(); + print_post_channel_iteration_common_code(); + print_nrn_cur_kernel(info.breakpoint_node); + print_nrn_cur_matrix_shadow_update(); + print_channel_iteration_block_end(); + + if (nrn_cur_reduction_loop_required()) { + print_shadow_reduction_block_begin(); + print_nrn_cur_matrix_shadow_reduction(); + print_shadow_reduction_statements(); + print_shadow_reduction_block_end(); + } + + print_channel_iteration_tiling_block_end(); + print_kernel_data_present_annotation_block_end(); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + + +/****************************************************************************************/ +/* Main code printing entry points */ +/****************************************************************************************/ + + +void CodegenCVisitor::codegen_includes() { + print_standard_includes(); + print_backend_includes(); + print_coreneuron_includes(); +} + + +void CodegenCVisitor::codegen_namespace_begin() { + print_namespace_start(); + print_backend_namespace_start(); +} + + +void CodegenCVisitor::codegen_namespace_end() { + print_backend_namespace_end(); + print_namespace_end(); +} + + +void CodegenCVisitor::codegen_common_getters() { + print_first_pointer_var_index_getter(); + print_memory_layout_getter(); + print_net_receive_arg_size_getter(); + print_thread_getters(); + print_num_variable_getter(); + print_mech_type_getter(); + print_memb_list_getter(); +} + + +void CodegenCVisitor::codegen_data_structures() { + print_mechanism_global_structure(); + print_mechanism_var_structure(); + print_ion_variables_structure(); +} + + +void CodegenCVisitor::codegen_compute_functions() { + print_top_verbatim_blocks(); + print_function_prototypes(); + + for (const auto& procedure : info.procedures) { + print_procedure(procedure); + } + + for (const auto& function : info.functions) { + print_function(function); + } + + print_net_init_kernel(); + print_net_send_buffer_kernel(); + print_net_receive(); + print_net_receive_buffer_kernel(); + print_nrn_init(); + print_nrn_cur(); + print_nrn_state(); +} + + +void CodegenCVisitor::codegen_all() { + codegen = true; + print_backend_info(); + codegen_includes(); + codegen_namespace_begin(); + + print_mechanism_info_array(); + print_global_variables_for_hoc(); + + codegen_common_getters(); + + print_thread_memory_callbacks(); + print_memory_allocation_routine(); + + codegen_data_structures(); + + print_global_variable_setup(); + print_instance_variable_setup(); + print_nrn_alloc(); + + codegen_compute_functions(); + print_mechanism_register(); + + codegen_namespace_end(); +} + + +void CodegenCVisitor::visit_program(Program* node) { + CodegenBaseVisitor::visit_program(node); + rename_function_arguments(); + codegen_all(); +} \ No newline at end of file diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp new file mode 100644 index 0000000000..2446d11a2a --- /dev/null +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -0,0 +1,541 @@ +#ifndef NMODL_CODEGEN_C_VISITOR_HPP +#define NMODL_CODEGEN_C_VISITOR_HPP + +#include <string> +#include <algorithm> + +#include "fmt/format.h" +#include "codegen/codegen_info.hpp" +#include "printer/code_printer.hpp" +#include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" +#include "codegen/base/codegen_base_visitor.hpp" + + +using namespace fmt::literals; + + +/** + * \class CodegenCVisitor + * \brief Visitor for printing c code compatible with legacy api + * + * \todo : + * - handle define i.e. macro statement printing + * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) + */ +class CodegenCVisitor : public CodegenBaseVisitor { + protected: + using SymbolType = std::shared_ptr<symtab::Symbol>; + + /// currently net_receive block being printed + bool printing_net_receive = false; + + /// currently printing verbatim blocks in top level block + bool printing_top_verbatim_blocks = false; + + /** + * Commonly used variables in the verbatim blocks and their corresponding + * variable name in the new code generation backend. + */ + std::map<std::string, std::string> verbatim_variables_mapping{ + {"_nt", "nt"}, + {"_p", "data"}, + {"_ppvar", "indexes"}, + {"_thread", "thread"}, + {"_iml", "id"}, + {"_cntml_padded", "pnodecount"}, + {"_cntml", "nodecount"}, + {"_tqitem", "tqitem"}, + {"_threadargs_", nrn_thread_arguments()}, + {"_threadargsproto_", external_method_parameters()}}; + + + /// number of threads to allocate + int num_thread_objects() { + return info.vectorize ? (info.thread_data_index + 1) : 0; + } + + + /// create temporary symbol + SymbolType make_symbol(std::string name) { + return std::make_shared<symtab::Symbol>(name, ModToken()); + } + + + /// check if structure for ion variables required + bool ion_variable_struct_required(); + + + /// when ion variable copies optimized then change name (e.g. ena to ion_ena) + std::string update_if_ion_variable_name(std::string name); + + + /// name of the code generation backend + virtual std::string backend_name(); + + + /// get variable name for float variable + std::string float_variable_name(SymbolType& symbol, bool use_instance); + + + /// get variable name for int variable + std::string int_variable_name(IndexVariableInfo& symbol, std::string name, bool use_instance); + + + /// get variable name for global variable + std::string global_variable_name(SymbolType& symbol); + + + /// get ion shadow variable name + std::string ion_shadow_variable_name(SymbolType& symbol); + + + /// get variable name for given name. if use_instance is true then "Instance" + /// structure is used while returning name + std::string get_variable_name(std::string name, bool use_instance = true) override; + + + /// name of the current variable used in the breakpoint bock + std::string breakpoint_current(std::string current); + + + /// process verbatim block for possible variable renaming + std::string process_verbatim_text(std::string text); + + + /// process token in verbatim block for possible variable renaming + std::string process_verbatim_token(std::string token); + + + /// rename function/procedure arguments that conflict with default arguments + void rename_function_arguments(); + + + //// statements for reading ion values + std::vector<std::string> ion_read_statements(BlockType type); + + + //// minimal statements for reading ion values + std::vector<std::string> ion_read_statements_optimal(BlockType type); + + + //// statements for writing ion values + std::vector<ShadowUseStatement> ion_write_statements(BlockType type); + + + /// return variable name and corresponding ion read variable name + std::pair<std::string, std::string> read_ion_variable_name(std::string name); + + + /// return variable name and corresponding ion write variable name + std::pair<std::string, std::string> write_ion_variable_name(std::string name); + + + /// function call / statement for nrn_wrote_conc + std::string conc_write_statement(std::string ion_name, std::string concentration, int index); + + + /// arguments for internally defined functions + std::string internal_method_arguments(); + + + /// parameters for internally defined functions + std::string internal_method_parameters(); + + + /// arguments for external functions + std::string external_method_arguments(); + + + /// parameters for external functions + std::string external_method_parameters(); + + + /// arguments for register_mech or point_register_mech function + std::string register_mechanism_arguments(); + + + /// arguments for "_threadargs_" macro in neuron implementation + std::string nrn_thread_arguments(); + + + /// list of shadow statements in the current block + std::vector<ShadowUseStatement> shadow_statements; + + + /// start of coreneuron namespace + void print_namespace_start(); + + + /// end of coreneuron namespace + void print_namespace_end(); + + + /// start of backend namespace + void print_backend_namespace_start(); + + + /// end of backend namespace + void print_backend_namespace_end(); + + + /// top header printed in generated code + void print_backend_info(); + + + /// memory allocation routine + virtual void print_memory_allocation_routine(); + + + /// common includes : standard c/c++, coreneuron and backend specific + void print_standard_includes(); + void print_coreneuron_includes(); + virtual void print_backend_includes(); + + + /// use of shadow updates at channel level required + virtual bool block_require_shadow_update(BlockType type); + + + /// channel execution with dependency (backend specific) + virtual bool channel_task_dependency_enabled(); + + + /// ion variable copies are avoided + bool optimize_ion_variable_copies(); + + + /// if reduction block in nrn_cur required + virtual bool nrn_cur_reduction_loop_required(); + + + /// char array that has mechanism information (to be registered with coreneuron) + void print_mechanism_info_array(); + + + /// structure that wraps all global variables in the mod file + void print_mechanism_global_structure(); + + + /// structure that wraps all range and int variables required for mod file + void print_mechanism_var_structure(); + + + /// structure of ion variables used for local copies + void print_ion_variables_structure(); + + + /// function that initialized instance structure + void print_instance_variable_setup(); + + + /// char arrays that registers scalar and vector variables for hoc interface + void print_global_variables_for_hoc(); + + + /// getter method for thread variables and ids + void print_thread_getters(); + + + /// getter method for memory layout + void print_memory_layout_getter(); + + + /// getter method for index position of first pointer variable + void print_first_pointer_var_index_getter(); + + + /// getter methods for float and integer variables count + void print_num_variable_getter(); + + + /// getter method for getting number of arguments for net_receive + void print_net_receive_arg_size_getter(); + + + /// getter method for returning membrane list from NrnThread + void print_memb_list_getter(); + + + /// getter method for returning mechtype + void print_mech_type_getter(); + + + /// setup method that initializes all global variables + void print_global_variable_setup(); + + + /// setup method for allocation of shadow vectors + void print_shadow_vector_setup(); + + + /// setup method for setting matrix shadow vectors + virtual void print_rhs_d_shadow_variables(); + + + /// backend specific device method annotation + void print_device_method_annotation(); + + + /// backend specific global method annotation + void print_global_method_annotation(); + + + /// call to internal or external function + void print_function_call(ast::FunctionCall* node); + + + /// net_send call + void print_net_send_call(ast::FunctionCall* node); + + + /// net_event call + void print_net_event_call(ast::FunctionCall* node); + + + /// channel iterations from which task can be created + virtual void print_channel_iteration_task_begin(BlockType type); + + + /// end of task for channel iteration + virtual void print_channel_iteration_task_end(); + + + /// backend specific block start for tiling on channel iteration + virtual void print_channel_iteration_tiling_block_begin(BlockType type); + + + /// backend specific block end for tiling on channel iteration + virtual void print_channel_iteration_tiling_block_end(); + + + /// ivdep like annotation for channel iterations + virtual void print_channel_iteration_block_parallel_hint(); + + + /// annotations like "acc enter data present(...)" for main kernel + virtual void print_kernel_data_present_annotation_block_begin(); + + + /// end of annotation like "acc enter data" + virtual void print_kernel_data_present_annotation_block_end(); + + + /// backend specific channel instance iteration block start + virtual void print_channel_iteration_block_begin(); + + + /// backend specific channel instance iteration block end + virtual void print_channel_iteration_block_end(); + + + /// common code post channel instance iteration + void print_post_channel_iteration_common_code(); + + + /// function and procedures prototype declaration + void print_function_prototypes(); + + + /// nmodl function definition + void print_function(ast::FunctionBlock* node); + + + /// nmodl procedure definition + void print_procedure(ast::ProcedureBlock* node); + + + /// thread related memory allocation and deallocation callbacks + void print_thread_memory_callbacks(); + + + /// top level (global scope) verbatim blocks + void print_top_verbatim_blocks(); + + + /// any statement block in nmodl with option to (not) print braces + void print_statement_block(ast::StatementBlock* node, + bool open_brace = true, + bool close_brace = true); + + + /// prototype declarations of functions and procedures + template <typename T> + void print_function_declaration(T& node); + + + /// initial block + void print_initial_block(ast::InitialBlock* node); + + + /// initial block in the net receive block + void print_net_init_kernel(); + + + /// common code section for net receive related methods + void print_net_receive_common_code(ast::Block* node); + + + /// kernel for buffering net_send events + void print_net_send_buffer_kernel(); + + + /// kernel for buffering net_receive events + void print_net_receive_buffer_kernel(); + + + /// net_receive function definition + void print_net_receive(); + + + /// derivative kernel when euler method is used + void print_derivative_kernel_for_euler(); + + + /// derivative kernel when derivimplicit method is used + void print_derivative_kernel_for_derivimplicit(); + + + /// block / loop for statement requiring reduction + void print_shadow_reduction_block_begin(); + + + /// end of block / loop for statement requiring reduction + void print_shadow_reduction_block_end(); + + + /// atomic update pragma for reduction statements + virtual void print_atomic_reduction_pragma(); + + + /// print all reduction statements + void print_shadow_reduction_statements(); + + + /// process shadow update statement : if statement requires reduction then + /// add it to vector of reduction statement and return statement using shadow update + std::string process_shadow_update_statement(ShadowUseStatement& statement, BlockType type); + + + /// main body of nrn_cur function + void print_nrn_cur_kernel(ast::BreakpointBlock* node); + + + /// nrn_cur_kernel will use this kernel if conductance keywords are specified + void print_nrn_cur_conductance_kernel(ast::BreakpointBlock* node); + + + /// nrn_cur_kernel will use this kernel if no conductance keywords are specified + void print_nrn_cur_non_conductance_kernel(); + + + /// nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified + void print_nrn_current(ast::BreakpointBlock* node); + + + /// update to matrix elements with/without shadow vectors + virtual void print_nrn_cur_matrix_shadow_update(); + + + /// reduction to matrix elements from shadow vectors + virtual void print_nrn_cur_matrix_shadow_reduction(); + + + /// nrn_alloc function definition + void print_nrn_alloc(); + + + /// common code for global functions like nrn_init, nrn_cur and nrn_state + void print_global_function_common_code(BlockType type); + + /// nrn_init function definition + void print_nrn_init(); + + + /// nrn_state / state update function definition + void print_nrn_state(); + + + /// nrn_cur / current update function definition + void print_nrn_cur(); + + + /// mechanism registration function + void print_mechanism_register(); + + /// all includes + void codegen_includes(); + + /// start of namespaces + void codegen_namespace_begin(); + + /// end of namespaces + void codegen_namespace_end(); + + /// common getter + void codegen_common_getters(); + + /// all classes + void codegen_data_structures(); + + /// all compute functions for every backend + void codegen_compute_functions(); + + /// entry point to code generation + void codegen_all(); + + public: + CodegenCVisitor(std::string mod_file, bool aos) : CodegenBaseVisitor(mod_file, aos) { + init(aos, mod_file); + } + + CodegenCVisitor(std::string mod_file, std::stringstream& stream, bool aos) + : CodegenBaseVisitor(mod_file, stream, aos) { + init(aos, mod_file); + } + + virtual void visit_function_call(ast::FunctionCall* node) override; + virtual void visit_verbatim(ast::Verbatim* node) override; + virtual void visit_program(ast::Program* node) override; +}; + + + +/** + * Print prototype declaration for function and procedures + * + * If there is an argument with name (say alpha) same as range variable (say alpha), + * we want avoid it being printed as instance->alpha. And hence we disable variable + * name lookup during prototype declaration. + */ +template <typename T> +void CodegenCVisitor::print_function_declaration(T& node) { + enable_variable_name_lookup = false; + + /// name, internal and user provided arguments + auto name = node->get_name(); + auto internal_params = internal_method_parameters(); + auto params = node->get_arguments(); + + /// procedures have "int" return type by default + std::string return_type = "int"; + if (node->is_function_block()) { + return_type = float_data_type(); + } + + print_device_method_annotation(); + printer->add_indent(); + printer->add_text("inline {} {}({}"_format(return_type, method_name(name), internal_params)); + + /// print remaining of arguments to the function + if (!params.empty() && !internal_params.empty()) { + printer->add_text(", "); + } + auto type = float_data_type() + " "; + print_vector_elements(params, ", ", type); + printer->add_text(")"); + + enable_variable_name_lookup = true; +} + +#endif diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp new file mode 100644 index 0000000000..0cd5dbf15c --- /dev/null +++ b/src/nmodl/codegen/codegen_info.cpp @@ -0,0 +1,60 @@ +#include "codegen/codegen_info.hpp" + +using namespace codegen; + + +/// if any ion has write variable +bool CodegenInfo::ion_has_write_variable() { + for (const auto& ion : ions) { + if (!ion.writes.empty()) { + return true; + } + } + return false; +} + + +/// if given variable is ion write variable +bool CodegenInfo::is_ion_write_variable(std::string name) { + for (const auto& ion : ions) { + for (auto& var : ion.writes) { + if (var == name) { + return true; + } + } + } + return false; +} + + +/// if given variable is ion read variable +bool CodegenInfo::is_ion_read_variable(std::string name) { + for (const auto& ion : ions) { + for (auto& var : ion.reads) { + if (var == name) { + return true; + } + } + } + return false; +} + + +/// if either read or write variable +bool CodegenInfo::is_ion_variable(std::string name) { + if (is_ion_read_variable(name) || is_ion_write_variable(name)) { + return true; + } + return false; +} + + +/// if a current +bool CodegenInfo::is_current(std::string name) { + for (auto& var : currents) { + if (var == name) { + return true; + } + } + return false; +} diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp new file mode 100644 index 0000000000..c068a35543 --- /dev/null +++ b/src/nmodl/codegen/codegen_info.hpp @@ -0,0 +1,334 @@ +#ifndef CODEGEN_INFO_HPP +#define CODEGEN_INFO_HPP + +#include <map> +#include <string> + +#include "ast/ast.hpp" +#include "symtab/symbol_table.hpp" + +namespace codegen { + + /** + * \class Conductance + * \brief Represent conductance statements used in mod file + */ + struct Conductance { + /// name of the ion + std::string ion; + + /// ion variable like intra/extra concentration + std::string variable; + }; + + + /** + * \class Ion + * \brief Represent ions used in mod file + */ + struct Ion { + /// name of the ion + std::string name; + + /// variables that are being read + std::vector<std::string> reads; + + /// variables that are being written + std::vector<std::string> writes; + + /// if style semantic needed + bool need_style = false; + + Ion() = delete; + + Ion(std::string name) : name(name) { + } + + /** + * Check if variable name is a ionic current + * + * This is equivalent of IONCUR flag in mod2c. + * If it is read variable then also get NRNCURIN flag. + * If it is write variables then also get NRNCUROUT flag. + */ + bool is_ionic_current(std::string text) const { + return text == ("i" + name); + } + + /** + * Check if variable name is internal cell concentration + * + * This is equivalent of IONIN flag in mod2c. + */ + bool is_intra_cell_conc(std::string text) const { + return text == (name + "i"); + } + + /** + * Check if variable name is external cell concentration + * + * This is equivalent of IONOUT flag in mod2c. + */ + bool is_extra_cell_conc(std::string text) const { + return text == (name + "o"); + } + + /** + * Check if variable name is reveral potential + * + * This is equivalent of IONEREV flag in mod2c. + */ + bool is_rev_potential(std::string text) const { + return text == ("e" + name); + } + + /// check if it is either internal or external concentration + bool is_ionic_conc(std::string text) const { + return is_intra_cell_conc(text) || is_extra_cell_conc(text); + } + }; + + + /** + * \class IndexSemantics + * \brief Represent semantic information for index variable + */ + struct IndexSemantics { + /// start position in the int array + int index; + + /// name/type of the variable (i.e. semantics) + std::string name; + + /// number of elements (typically one) + int size; + + IndexSemantics() = delete; + IndexSemantics(int index, std::string name, int size) + : index(index), name(name), size(size) { + } + }; + + + /** + * \class CodegenInfo + * \brief Represent information collected from AST for code generation + * + * Code generation passes require different information from AST. This + * information is gathered in this single class. + * + * \todo : need to store all Define i.e. macro definitions? + */ + struct CodegenInfo { + /// name of mod file + std::string mod_file; + + /// name of the suffix + std::string mod_suffix; + + /// if mod file is vectorizable (always true for coreneuron) + bool vectorize = true; + + /// if mod file is thread safe (always true for coreneuron) + bool thread_safe = true; + + /// if mod file is point process + bool point_process = false; + + /// if mod file is artificial cell + bool artificial_cell = false; + + /// if electrode current specified + bool electorde_current = false; + + /// if thread thread call back routines need to register + bool thread_callback_register = false; + + /// \todo : if table routines need to be printed + bool emit_table_thread = false; + + /// if bbcore pointer is used + bool bbcore_pointer_used = false; + + /// if write concentration call required in initial block + bool write_concentration = false; + + /// if net_send function is used + bool net_send_used = false; + + /// if net_even function is used + bool net_event_used = false; + + /** + * thread_data_index indicates number of threads being allocated. + * For example, if there is derivimplicit method used, then two thread + * structures are created. When we print global variables then + * thread_data_index is used to indicate whats next thread id to use. + */ + int thread_data_index = 0; + + /** + * Top local variables are those local variables that appear in global scope. + * Thread structure is created for top local variables and doesn't thread id + * 0. For example, if derivimplicit method is used then thread id 0 is assigned + * to those structures first. And then next thread id is assigned for top local + * variables. The idea of thread is assignement is to have same order for variables + * between neuron and coreneuron. + */ + + /// thread id for top local variables + int top_local_thread_id = 0; + + /// total length of all top local variables + int top_local_thread_size = 0; + + /// thread id for thread promoted variables + int thread_var_thread_id = 0; + + /// sum of length of thread promoted variables + int thread_var_data_size = 0; + + /// thread id for derivimplicit variables + int derivimplicit_var_thread_id = -1; + + /// slist/dlist id for derivimplicit block + int derivimplicit_list_num = -1; + + /// slist/dlist id for for euler block + int euler_list_num = -1; + + /// number of solve blocks in mod file + int num_solve_blocks = 0; + + /// number of primes (all state variables not necessary to be prime) + int num_primes = 0; + + /// sum of length of all prime variables + int primes_size = 0; + + /// number of equations (i.e. statements) in derivative block + /// typically equal to number of primes + int num_equations = 0; + + /// block used in solve statement + /// typically derivative block or procedure + ast::Block* solve_node = nullptr; + + /// \todo: name of the solve block + std::string solve_block_name; + + /// solve method used + std::string solve_method; + + /// derivative block + ast::BreakpointBlock* breakpoint_node = nullptr; + + /// net receive block for point process + ast::NetReceiveBlock* net_receive_node = nullptr; + + /// number of arguments to net_receive block + int num_net_receive_arguments = 0; + + /// initial block within net receive block + ast::InitialBlock* net_receive_initial_node = nullptr; + + /// initial block + ast::InitialBlock* initial_node = nullptr; + + /// all procedures defined in the mod file + std::vector<ast::ProcedureBlock*> procedures; + + /// all functions defined in the mod file + std::vector<ast::FunctionBlock*> functions; + + /// ions used in the mod file + std::vector<Ion> ions; + + using SymbolType = std::shared_ptr<symtab::Symbol>; + + /// range variables which are parameter as well + std::vector<SymbolType> range_parameter_vars; + + /// range variables which are dependent variables as well + std::vector<SymbolType> range_dependent_vars; + + /// reamining dependent variables + std::vector<SymbolType> dependent_vars; + + /// state variables + std::vector<SymbolType> state_vars; + + /// ion variables which are also state variables + std::vector<SymbolType> ion_state_vars; + + /// local variables in the global scope + std::vector<SymbolType> top_local_variables; + + /// pointer or bbcore pointer variables + std::vector<SymbolType> pointer_variables; + + /// index/offset for first pointer variable if exist + int first_pointer_var_index = -1; + + /// global variables + std::vector<SymbolType> global_variables; + + /// constant variables + std::vector<SymbolType> constant_variables; + + /// thread variables (e.g. global variables promoted to thread) + std::vector<SymbolType> thread_variables; + + /// new one used in print_ion_types + std::vector<SymbolType> use_ion_variables; + + /// this is the order in which they appear in derivative block + /// this is required while printing them in initlist function + std::vector<SymbolType> prime_variables_by_order; + + /// represent conductance statements used in mod file + std::vector<Conductance> conductances; + + /// index variable semantic information + std::vector<IndexSemantics> semantics; + + /// non specific and ionic currents + std::vector<std::string> currents; + + /// if mod file used dervimplicit method + bool derivimplicit_used = false; + + /// if mod file used euler method + bool euler_used = false; + + /// if mod file used cnexp method + bool cnexp_used = false; + + /// all top level global blocks + std::vector<ast::Node*> top_blocks; + + /// all top level verbatim blocks + std::vector<ast::Node*> top_verbatim_blocks; + + /// tqitem index in int variables + /// note that if tqitem doesn't exist then default value should be 0 + int tqitem_index = 0; + + /// if any ion has write variable + bool ion_has_write_variable(); + + /// if given variable is ion write variable + bool is_ion_write_variable(std::string name); + + /// if given variable is ion read variable + bool is_ion_read_variable(std::string name); + + /// if either read or write variable + bool is_ion_variable(std::string name); + + /// if a current + bool is_current(std::string name); + }; +}; // namespace codegen + +#endif diff --git a/src/nmodl/codegen/main.cpp b/src/nmodl/codegen/main.cpp new file mode 100644 index 0000000000..337a724393 --- /dev/null +++ b/src/nmodl/codegen/main.cpp @@ -0,0 +1,226 @@ +#include <fstream> +#include <iostream> +#include <sstream> + +#include <tclap/CmdLine.h> +#include "parser/nmodl_driver.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/json_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/verbatim_var_rename_visitor.hpp" +#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" +#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" + +using namespace symtab; + +/** + * Standalone visitor program for NMODL. This demonstrate basic + * usage of different visitors classes and driver class. + **/ + +int main(int argc, const char* argv[]) { + try { + TCLAP::CmdLine cmd("NMODL Code Generator for NMODL"); + + // clang-format off + TCLAP::ValueArg<std::string> modfile_arg( "", + "file", + "NMODL input file path", + false, + "../test/input/channel.mod", + "string"); + TCLAP::ValueArg<std::string> host_arg( "", + "host", + "Host backend [c, c-openmp, c-openacc]", + false, + "c", + "string"); + TCLAP::ValueArg<std::string> accel_arg( "", + "accelerator", + "Accelerator backend [cuda]", + false, + "cuda", + "string"); + TCLAP::SwitchArg verbose_arg( "", + "verbose", + "Enable verbose output", + false); + TCLAP::SwitchArg aos_layout_arg( "", + "aos", + "Enable AoS layout (default SoA)", + false); + TCLAP::SwitchArg inline_arg( "", + "inline", + "Enable inlining in NMODL implementation", + false); + // clang-format on + + cmd.add(modfile_arg); + cmd.add(verbose_arg); + cmd.add(aos_layout_arg); + cmd.add(inline_arg); + cmd.add(host_arg); + cmd.add(accel_arg); + cmd.parse(argc, argv); + + std::string filename = modfile_arg.getValue(); + std::string host_backend = host_arg.getValue(); + bool verbose = verbose_arg.getValue(); + bool aos_code = aos_layout_arg.getValue(); + bool mod_inline = inline_arg.getValue(); + + std::ifstream file(filename); + + if (!file.good()) { + throw std::runtime_error("Could not open file " + filename); + } + + std::string mod_filename = remove_extension(base_name(filename)); + + /// driver object creates lexer and parser, just call parser method + nmodl::Driver driver; + driver.parse_file(filename); + + /// shared_ptr to ast constructed from parsing nmodl file + auto ast = driver.ast(); + + { + AstVisitor v; + v.visit_program(ast.get()); + } + + { + SymtabVisitor v(false); + v.visit_program(ast.get()); + } + + { + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); + std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; + } + + { + VerbatimVarRenameVisitor v; + v.visit_program(ast.get()); + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.verbrename.mod"); + v.visit_program(ast.get()); + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.mod"); + v.visit_program(ast.get()); + } + + { + CnexpSolveVisitor v; + v.visit_program(ast.get()); + std::cout << "----CNEXP SOLVE VISITOR FINISHED----" << std::endl; + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.mod"); + v.visit_program(ast.get()); + } + + + { + if (mod_inline) { + InlineVisitor v; + v.visit_program(ast.get()); + } + } + + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.mod"); + v.visit_program(ast.get()); + } + + { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + std::cout << "----LOCAL VAR RENAME VISITOR FINISHED----" << std::endl; + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.mod"); + v.visit_program(ast.get()); + } + + { + SymtabVisitor v(true); + v.visit_program(ast.get()); + } + + { + /// for benchmarking/plotting purpose we want to enable unsafe mode + bool ignore_verbatim = true; + LocalizeVisitor v(ignore_verbatim); + v.visit_program(ast.get()); + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.mod"); + v.visit_program(ast.get()); + } + + { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + } + + { + SymtabVisitor v(true); + v.visit_program(ast.get()); + } + + { + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); + std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.ren.mod"); + v.visit_program(ast.get()); + } + + { + if (host_backend == "c") { + CodegenCVisitor visitor(mod_filename, aos_code); + visitor.visit_program(ast.get()); + } else if (host_backend == "c-openmp") { + CodegenCOmpVisitor visitor(mod_filename, aos_code); + visitor.visit_program(ast.get()); + } else if (host_backend == "c-openacc") { + CodegenCAccVisitor visitor(mod_filename, aos_code); + visitor.visit_program(ast.get()); + } else { + std::cerr << "Argument Error: Unknown host backend " << host_backend << std::endl; + return 1; + } + } + + } catch (TCLAP::ArgException& e) { + std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; + return 1; + } + + return 0; +} diff --git a/src/nmodl/ext/fmt/CMakeLists.txt b/src/nmodl/ext/fmt/CMakeLists.txt new file mode 100644 index 0000000000..2a4f98883a --- /dev/null +++ b/src/nmodl/ext/fmt/CMakeLists.txt @@ -0,0 +1,15 @@ +#============================================================================= +# Fmt library sources +#============================================================================= +set(FMT_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/format.h + ${CMAKE_CURRENT_SOURCE_DIR}/format.cc +) + +#============================================================================= +# Symbol table library +#============================================================================= +add_library(fmt + STATIC + ${FMT_SOURCE_FILES} +) \ No newline at end of file diff --git a/src/nmodl/ext/fmt/format.cc b/src/nmodl/ext/fmt/format.cc new file mode 100644 index 0000000000..2d236bc641 --- /dev/null +++ b/src/nmodl/ext/fmt/format.cc @@ -0,0 +1,495 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "format.h" + +#include <string.h> + +#include <cctype> +#include <cerrno> +#include <climits> +#include <cmath> +#include <cstdarg> +#include <cstddef> // for std::ptrdiff_t + +#if defined(_WIN32) && defined(__MINGW32__) +# include <cstring> +#endif + +#if FMT_USE_WINDOWS_H +# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +# include <windows.h> +# else +# define NOMINMAX +# include <windows.h> +# undef NOMINMAX +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +# pragma warning(disable: 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +FMT_MAYBE_UNUSED +static inline fmt::internal::Null<> strerror_r(int, char *, ...) { + return fmt::internal::Null<>(); +} +FMT_MAYBE_UNUSED +static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { + return fmt::internal::Null<>(); +} + +namespace fmt { + +FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {} +FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {} +FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {} + +namespace { + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +# define FMT_SWPRINTF snwprintf +#else +# define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +const char RESET_COLOR[] = "\x1b[0m"; + +typedef void (*FormatFunc)(Writer &, int, StringRef); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { + FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); + + class StrError { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const StrError &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::Null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? + ERANGE : result; + } + +#ifdef __c2__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdeprecated-declarations" +#endif + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::Null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + +#ifdef __c2__ +# pragma clang diagnostic pop +#endif + + public: + StrError(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return StrError(error_code, buffer, buffer_size).run(); +} + +void format_error_code(Writer &out, int error_code, + StringRef message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential + // bad_alloc. + out.clear(); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::IntTraits<int>::MainType MainType; + MainType abs_value = static_cast<MainType>(error_code); + if (internal::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) + out << message << SEP; + out << ERROR_STR << error_code; + assert(out.size() <= internal::INLINE_BUFFER_SIZE); +} + +void report_error(FormatFunc func, int error_code, + StringRef message) FMT_NOEXCEPT { + MemoryWriter full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +FMT_FUNC void SystemError::init( + int err_code, CStringRef format_str, ArgList args) { + error_code_ = err_code; + MemoryWriter w; + format_system_error(w, err_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +template <typename T> +int internal::CharTraits<char>::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, value) : + FMT_SNPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, width, value) : + FMT_SNPRINTF(buffer, size, format, width, precision, value); +} + +template <typename T> +int internal::CharTraits<wchar_t>::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, T value) { + if (width == 0) { + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, value) : + FMT_SWPRINTF(buffer, size, format, precision, value); + } + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, width, value) : + FMT_SWPRINTF(buffer, size, format, width, precision, value); +} + +template <typename T> +const char internal::BasicData<T>::DIGITS[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 + +template <typename T> +const uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template <typename T> +const uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(ULongLong(1000000000)), + // Multiply several constants instead of using a single long long constant + // to avoid warnings about C++98 not supporting long long. + ULongLong(1000000000) * ULongLong(1000000000) * 10 +}; + +FMT_FUNC void internal::report_unknown_type(char code, const char *type) { + (void)type; + if (std::isprint(static_cast<unsigned char>(code))) { + FMT_THROW(FormatError( + format("unknown format code '{}' for {}", code, type))); + } + FMT_THROW(FormatError( + format("unknown format code '\\x{:02x}' for {}", + static_cast<unsigned>(code), type))); +} + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) { + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast<int>(s.size()); + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) { + if (int error_code = convert(s)) { + FMT_THROW(WindowsError(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) { + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast<int>(s.size()); + int length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void WindowsError::init( + int err_code, CStringRef format_str, ArgList args) { + error_code_ = err_code; + MemoryWriter w; + internal::format_windows_error(w, err_code, format(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(w.str()); +} + +FMT_FUNC void internal::format_windows_error( + Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { + FMT_TRY { + MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer; + buffer.resize(INLINE_BUFFER_SIZE); + for (;;) { + wchar_t *system_message = &buffer[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL); + if (result != 0) { + UTF16ToUTF8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + out << message << ": " << utf8_message; + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error( + Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { + FMT_TRY { + internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer; + buffer.resize(internal::INLINE_BUFFER_SIZE); + for (;;) { + char *system_message = &buffer[0]; + int result = safe_strerror(error_code, system_message, buffer.size()); + if (result == 0) { + out << message << ": " << system_message; + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buffer.resize(buffer.size() * 2); + } + } FMT_CATCH(...) {} + fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. +} + +template <typename Char> +void internal::FixedBuffer<Char>::grow(std::size_t) { + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg( + unsigned arg_index, const char *&error) { + internal::Arg arg = args_[arg_index]; + switch (arg.type) { + case internal::Arg::NONE: + error = "argument index out of range"; + break; + case internal::Arg::NAMED_ARG: + arg = *static_cast<const internal::Arg*>(arg.pointer); + break; + default: + /*nothing*/; + } + return arg; +} + +FMT_FUNC void report_system_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT { + // 'fmt::' is for bcc32. + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error( + int error_code, fmt::StringRef message) FMT_NOEXCEPT { + // 'fmt::' is for bcc32. + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + std::fwrite(w.data(), 1, w.size(), f); +} + +FMT_FUNC void print(CStringRef format_str, ArgList args) { + print(stdout, format_str, args); +} + +FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) { + char escape[] = "\x1b[30m"; + escape[3] = static_cast<char>('0' + c); + std::fputs(escape, stdout); + print(format, args); + std::fputs(RESET_COLOR, stdout); +} + +#ifndef FMT_HEADER_ONLY + +template struct internal::BasicData<void>; + +// Explicit instantiations for char. + +template void internal::FixedBuffer<char>::grow(std::size_t); + +template FMT_API int internal::CharTraits<char>::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, double value); + +template FMT_API int internal::CharTraits<char>::format_float( + char *buffer, std::size_t size, const char *format, + unsigned width, int precision, long double value); + +// Explicit instantiations for wchar_t. + +template void internal::FixedBuffer<wchar_t>::grow(std::size_t); + +template FMT_API int internal::CharTraits<wchar_t>::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, double value); + +template FMT_API int internal::CharTraits<wchar_t>::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, + unsigned width, int precision, long double value); + +#endif // FMT_HEADER_ONLY + +} // namespace fmt + +#ifdef _MSC_VER +# pragma warning(pop) +#endif diff --git a/src/nmodl/ext/fmt/format.h b/src/nmodl/ext/fmt/format.h new file mode 100644 index 0000000000..561a9e0798 --- /dev/null +++ b/src/nmodl/ext/fmt/format.h @@ -0,0 +1,4173 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - 2016, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#define FMT_INCLUDE +#include <cassert> +#include <clocale> +#include <cmath> +#include <cstdio> +#include <cstring> +#include <limits> +#include <memory> +#include <stdexcept> +#include <string> +#include <vector> +#include <utility> // for std::pair +#undef FMT_INCLUDE + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 40100 + +#if defined(__has_include) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#if (FMT_HAS_INCLUDE(<string_view>) && __cplusplus > 201402L) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +# include <string_view> +# define FMT_HAS_STRING_VIEW 1 +#else +# define FMT_HAS_STRING_VIEW 0 +#endif + +#if defined _SECURE_SCL && _SECURE_SCL +# define FMT_SECURE_SCL _SECURE_SCL +#else +# define FMT_SECURE_SCL 0 +#endif + +#if FMT_SECURE_SCL +# include <iterator> +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +#else +# define FMT_MSC_VER 0 +#endif + +#if FMT_MSC_VER && FMT_MSC_VER <= 1500 +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef __int64 intmax_t; +#else +#include <stdint.h> +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifdef __GNUC__ +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +# define FMT_GCC_EXTENSION __extension__ +# if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic push +// Disable the warning about "long long" which is sometimes reported even +// when using __extension__. +# pragma GCC diagnostic ignored "-Wlong-long" +// Disable the warning about declaration shadowing because it affects too +// many valid cases. +# pragma GCC diagnostic ignored "-Wshadow" +// Disable the warning about implicit conversions that may change the sign of +// an integer; silencing it otherwise would require many explicit casts. +# pragma GCC diagnostic ignored "-Wsign-conversion" +# endif +# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__ +# define FMT_HAS_GXX_CXX11 1 +# endif +#else +# define FMT_GCC_VERSION 0 +# define FMT_GCC_EXTENSION +# define FMT_HAS_GXX_CXX11 0 +#endif + +#if defined(__INTEL_COMPILER) +# define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +# define FMT_ICC_VERSION __ICL +#endif + +#if defined(__clang__) && !defined(FMT_ICC_VERSION) +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" +# pragma clang diagnostic ignored "-Wpadded" +#endif + +#ifdef __GNUC_LIBSTD__ +# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#endif + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#if FMT_HAS_CPP_ATTRIBUTE(maybe_unused) +# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED +// VC++ 1910 support /std: option and that will set _MSVC_LANG macro +// Clang with Microsoft CodeGen doesn't define _MSVC_LANG macro +#elif defined(_MSVC_LANG) && _MSVC_LANG > 201402 && _MSC_VER >= 1910 +# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED +#endif + +#ifdef FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED +# define FMT_MAYBE_UNUSED [[maybe_unused]] +// g++/clang++ also support [[gnu::unused]]. However, we don't use it. +#elif defined(__GNUC__) +# define FMT_MAYBE_UNUSED __attribute__((unused)) +#else +# define FMT_MAYBE_UNUSED +#endif + +// Use the compiler's attribute noreturn +#if defined(__MINGW32__) || defined(__MINGW64__) +# define FMT_NORETURN __attribute__((noreturn)) +#elif FMT_HAS_CPP_ATTRIBUTE(noreturn) && __cplusplus >= 201103L +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +#ifndef FMT_USE_VARIADIC_TEMPLATES +// Variadic templates are available in GCC since version 4.4 +// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ +// since version 2013. +# define FMT_USE_VARIADIC_TEMPLATES \ + (FMT_HAS_FEATURE(cxx_variadic_templates) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800) +#endif + +#ifndef FMT_USE_RVALUE_REFERENCES +// Don't use rvalue references when compiling with clang and an old libstdc++ +// as the latter doesn't provide std::move. +# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 +# define FMT_USE_RVALUE_REFERENCES 0 +# else +# define FMT_USE_RVALUE_REFERENCES \ + (FMT_HAS_FEATURE(cxx_rvalue_references) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600) +# endif +#endif + +#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 +# define FMT_USE_ALLOCATOR_TRAITS 1 +#else +# define FMT_USE_ALLOCATOR_TRAITS 0 +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#endif +#if FMT_MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +# define FMT_EXCEPTIONS 1 +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# define FMT_THROW(x) throw x +# else +# define FMT_THROW(x) assert(false) +# endif +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +# define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_DETECTED_NOEXCEPT noexcept +#else +# define FMT_DETECTED_NOEXCEPT throw() +#endif + +#ifndef FMT_NOEXCEPT +# if FMT_EXCEPTIONS +# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +# else +# define FMT_NOEXCEPT +# endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_OVERRIDE +# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + +#ifndef FMT_NULL +# if FMT_HAS_FEATURE(cxx_nullptr) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ + FMT_MSC_VER >= 1600 +# define FMT_NULL nullptr +# else +# define FMT_NULL NULL +# endif +#endif + +// A macro to disallow the copy constructor and operator= functions +// This should be used in the private: declarations for a class +#ifndef FMT_USE_DELETED_FUNCTIONS +# define FMT_USE_DELETED_FUNCTIONS 0 +#endif + +#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 +# define FMT_DELETED_OR_UNDEFINED = delete +# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + TypeName& operator=(const TypeName&) = delete +#else +# define FMT_DELETED_OR_UNDEFINED +# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&); \ + TypeName& operator=(const TypeName&) +#endif + +#ifndef FMT_USE_DEFAULTED_FUNCTIONS +# define FMT_USE_DEFAULTED_FUNCTIONS 0 +#endif + +#ifndef FMT_DEFAULTED_COPY_CTOR +# if FMT_USE_DEFAULTED_FUNCTIONS || FMT_HAS_FEATURE(cxx_defaulted_functions) || \ + (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 +# define FMT_DEFAULTED_COPY_CTOR(TypeName) \ + TypeName(const TypeName&) = default; +# else +# define FMT_DEFAULTED_COPY_CTOR(TypeName) +# endif +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// All compilers which support UDLs also support variadic templates. This +// makes the fmt::literals implementation easier. However, an explicit check +// for variadic templates is added here just in case. +// For Intel's compiler both it and the system gcc/msc must support UDLs. +# if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ + (FMT_HAS_FEATURE(cxx_user_literals) || \ + (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \ + (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500) +# define FMT_USE_USER_DEFINED_LITERALS 1 +# else +# define FMT_USE_USER_DEFINED_LITERALS 0 +# endif +#endif + +#ifndef FMT_USE_EXTERN_TEMPLATES +# define FMT_USE_EXTERN_TEMPLATES \ + (FMT_CLANG_VERSION >= 209 || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) +#endif + +#ifdef FMT_HEADER_ONLY +// If header only do not use extern templates. +# undef FMT_USE_EXTERN_TEMPLATES +# define FMT_USE_EXTERN_TEMPLATES 0 +#endif + +#ifndef FMT_ASSERT +# define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#ifndef _MSC_VER +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif + +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or +// otherwise support __builtin_clz and __builtin_clzll, so +// only define FMT_BUILTIN_CLZ using the MSVC intrinsics +// if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) +# include <intrin.h> // _BitScanReverse, _BitScanReverse64 + +namespace fmt { +namespace internal { +// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning +# ifndef __clang__ +# pragma intrinsic(_BitScanReverse) +# endif +inline uint32_t clz(uint32_t x) { + unsigned long r = 0; + _BitScanReverse(&r, x); + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 31 - r; +} +# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) + +// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning +# if defined(_WIN64) && !defined(__clang__) +# pragma intrinsic(_BitScanReverse64) +# endif + +inline uint32_t clzll(uint64_t x) { + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast<uint32_t>(x)); +# endif + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 63 - r; +} +# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) +} +} +#endif + +namespace fmt { +namespace internal { +struct DummyInt { + int data[2]; + operator int() const { return 0; } +}; +typedef std::numeric_limits<fmt::internal::DummyInt> FPUtil; + +// Dummy implementations of system functions such as signbit and ecvt called +// if the latter are not available. +inline DummyInt signbit(...) { return DummyInt(); } +inline DummyInt _ecvt_s(...) { return DummyInt(); } +inline DummyInt isinf(...) { return DummyInt(); } +inline DummyInt _finite(...) { return DummyInt(); } +inline DummyInt isnan(...) { return DummyInt(); } +inline DummyInt _isnan(...) { return DummyInt(); } + +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template <typename T> +inline T const_check(T value) { return value; } +} +} // namespace fmt + +namespace std { +// Standard permits specialization of std::numeric_limits. This specialization +// is used to resolve ambiguity between isinf and std::isinf in glibc: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 +// and the same for isnan and signbit. +template <> +class numeric_limits<fmt::internal::DummyInt> : + public std::numeric_limits<int> { + public: + // Portable version of isinf. + template <typename T> + static bool isinfinity(T x) { + using namespace fmt::internal; + // The resolution "priority" is: + // isinf macro > std::isinf > ::isinf > fmt::internal::isinf + if (const_check(sizeof(isinf(x)) == sizeof(bool) || + sizeof(isinf(x)) == sizeof(int))) { + return isinf(x) != 0; + } + return !_finite(static_cast<double>(x)); + } + + // Portable version of isnan. + template <typename T> + static bool isnotanumber(T x) { + using namespace fmt::internal; + if (const_check(sizeof(isnan(x)) == sizeof(bool) || + sizeof(isnan(x)) == sizeof(int))) { + return isnan(x) != 0; + } + return _isnan(static_cast<double>(x)) != 0; + } + + // Portable version of signbit. + static bool isnegative(double x) { + using namespace fmt::internal; + if (const_check(sizeof(signbit(x)) == sizeof(bool) || + sizeof(signbit(x)) == sizeof(int))) { + return signbit(x) != 0; + } + if (x < 0) return true; + if (!isnotanumber(x)) return false; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); + return sign != 0; + } +}; +} // namespace std + +namespace fmt { + +// Fix the warning about long long on older versions of GCC +// that don't support the diagnostic pragma. +FMT_GCC_EXTENSION typedef long long LongLong; +FMT_GCC_EXTENSION typedef unsigned long long ULongLong; + +#if FMT_USE_RVALUE_REFERENCES +using std::move; +#endif + +template <typename Char> +class BasicWriter; + +typedef BasicWriter<char> Writer; +typedef BasicWriter<wchar_t> WWriter; + +template <typename Char> +class ArgFormatter; + +struct FormatSpec; + +template <typename Impl, typename Char, typename Spec = fmt::FormatSpec> +class BasicPrintfArgFormatter; + +template <typename CharType, + typename ArgFormatter = fmt::ArgFormatter<CharType> > +class BasicFormatter; + +/** + \rst + A string reference. It can be constructed from a C string or + ``std::basic_string``. + + You can use one of the following typedefs for common character types: + + +------------+-------------------------+ + | Type | Definition | + +============+=========================+ + | StringRef | BasicStringRef<char> | + +------------+-------------------------+ + | WStringRef | BasicStringRef<wchar_t> | + +------------+-------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template <typename... Args> + std::string format(StringRef format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template <typename Char> +class BasicStringRef { + private: + const Char *data_; + std::size_t size_; + + public: + /** Constructs a string reference object from a C string and a size. */ + BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits<Char>::length``. + \endrst + */ + BasicStringRef(const Char *s) + : data_(s), size_(std::char_traits<Char>::length(s)) {} + + /** + \rst + Constructs a string reference from a ``std::basic_string`` object. + \endrst + */ + template <typename Allocator> + BasicStringRef( + const std::basic_string<Char, std::char_traits<Char>, Allocator> &s) + : data_(s.c_str()), size_(s.size()) {} + +#if FMT_HAS_STRING_VIEW + /** + \rst + Constructs a string reference from a ``std::basic_string_view`` object. + \endrst + */ + BasicStringRef( + const std::basic_string_view<Char, std::char_traits<Char>> &s) + : data_(s.data()), size_(s.size()) {} + + /** + \rst + Converts a string reference to an ``std::string_view`` object. + \endrst + */ + explicit operator std::basic_string_view<Char>() const FMT_NOEXCEPT { + return std::basic_string_view<Char>(data_, size_); + } +#endif + + /** + \rst + Converts a string reference to an ``std::string`` object. + \endrst + */ + std::basic_string<Char> to_string() const { + return std::basic_string<Char>(data_, size_); + } + + /** Returns a pointer to the string data. */ + const Char *data() const { return data_; } + + /** Returns the string size. */ + std::size_t size() const { return size_; } + + // Lexicographically compare this string reference to other. + int compare(BasicStringRef other) const { + std::size_t size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits<Char>::compare(data_, other.data_, size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) { + return lhs.compare(rhs) != 0; + } + friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) { + return lhs.compare(rhs) >= 0; + } +}; + +typedef BasicStringRef<char> StringRef; +typedef BasicStringRef<wchar_t> WStringRef; + +/** + \rst + A reference to a null terminated string. It can be constructed from a C + string or ``std::basic_string``. + + You can use one of the following typedefs for common character types: + + +-------------+--------------------------+ + | Type | Definition | + +=============+==========================+ + | CStringRef | BasicCStringRef<char> | + +-------------+--------------------------+ + | WCStringRef | BasicCStringRef<wchar_t> | + +-------------+--------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template <typename... Args> + std::string format(CStringRef format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template <typename Char> +class BasicCStringRef { + private: + const Char *data_; + + public: + /** Constructs a string reference object from a C string. */ + BasicCStringRef(const Char *s) : data_(s) {} + + /** + \rst + Constructs a string reference from a ``std::basic_string`` object. + \endrst + */ + template <typename Allocator> + BasicCStringRef( + const std::basic_string<Char, std::char_traits<Char>, Allocator> &s) + : data_(s.c_str()) {} + + /** Returns the pointer to a C string. */ + const Char *c_str() const { return data_; } +}; + +typedef BasicCStringRef<char> CStringRef; +typedef BasicCStringRef<wchar_t> WCStringRef; + +/** A formatting error such as invalid format string. */ +class FormatError : public std::runtime_error { + public: + explicit FormatError(CStringRef message) + : std::runtime_error(message.c_str()) {} + FormatError(const FormatError &ferr) : std::runtime_error(ferr) {} + FMT_API ~FormatError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; +}; + +namespace internal { + +// MakeUnsigned<T>::Type gives an unsigned type corresponding to integer type T. +template <typename T> +struct MakeUnsigned { typedef T Type; }; + +#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ + template <> \ + struct MakeUnsigned<T> { typedef U Type; } + +FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); +FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); +FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); +FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); +FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); + +// Casts nonnegative integer to unsigned. +template <typename Int> +inline typename MakeUnsigned<Int>::Type to_unsigned(Int value) { + FMT_ASSERT(value >= 0, "negative value"); + return static_cast<typename MakeUnsigned<Int>::Type>(value); +} + +// The number of characters to store in the MemoryBuffer object itself +// to avoid dynamic memory allocation. +enum { INLINE_BUFFER_SIZE = 500 }; + +#if FMT_SECURE_SCL +// Use checked iterator to avoid warnings on MSVC. +template <typename T> +inline stdext::checked_array_iterator<T*> make_ptr(T *ptr, std::size_t size) { + return stdext::checked_array_iterator<T*>(ptr, size); +} +#else +template <typename T> +inline T *make_ptr(T *ptr, std::size_t) { return ptr; } +#endif +} // namespace internal + +/** + \rst + A buffer supporting a subset of ``std::vector``'s operations. + \endrst + */ +template <typename T> +class Buffer { + private: + FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); + + protected: + T *ptr_; + std::size_t size_; + std::size_t capacity_; + + Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0) + : ptr_(ptr), size_(0), capacity_(capacity) {} + + /** + \rst + Increases the buffer capacity to hold at least *size* elements updating + ``ptr_`` and ``capacity_``. + \endrst + */ + virtual void grow(std::size_t size) = 0; + + public: + virtual ~Buffer() {} + + /** Returns the size of this buffer. */ + std::size_t size() const { return size_; } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const { return capacity_; } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) { + if (new_size > capacity_) + grow(new_size); + size_ = new_size; + } + + /** + \rst + Reserves space to store at least *capacity* elements. + \endrst + */ + void reserve(std::size_t capacity) { + if (capacity > capacity_) + grow(capacity); + } + + void clear() FMT_NOEXCEPT { size_ = 0; } + + void push_back(const T &value) { + if (size_ == capacity_) + grow(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template <typename U> + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) { return ptr_[index]; } + const T &operator[](std::size_t index) const { return ptr_[index]; } +}; + +template <typename T> +template <typename U> +void Buffer<T>::append(const U *begin, const U *end) { + FMT_ASSERT(end >= begin, "negative value"); + std::size_t new_size = size_ + static_cast<std::size_t>(end - begin); + if (new_size > capacity_) + grow(new_size); + std::uninitialized_copy(begin, end, + internal::make_ptr(ptr_, capacity_) + size_); + size_ = new_size; +} + +namespace internal { + +// A memory buffer for trivially copyable/constructible types with the first +// SIZE elements stored in the object itself. +template <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> > +class MemoryBuffer : private Allocator, public Buffer<T> { + private: + T data_[SIZE]; + + // Deallocate memory allocated by the buffer. + void deallocate() { + if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_); + } + + protected: + void grow(std::size_t size) FMT_OVERRIDE; + + public: + explicit MemoryBuffer(const Allocator &alloc = Allocator()) + : Allocator(alloc), Buffer<T>(data_, SIZE) {} + ~MemoryBuffer() FMT_OVERRIDE { deallocate(); } + +#if FMT_USE_RVALUE_REFERENCES + private: + // Move data from other to this buffer. + void move(MemoryBuffer &other) { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + this->size_ = other.size_; + this->capacity_ = other.capacity_; + if (other.ptr_ == other.data_) { + this->ptr_ = data_; + std::uninitialized_copy(other.data_, other.data_ + this->size_, + make_ptr(data_, this->capacity_)); + } else { + this->ptr_ = other.ptr_; + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.ptr_ = other.data_; + } + } + + public: + MemoryBuffer(MemoryBuffer &&other) { + move(other); + } + + MemoryBuffer &operator=(MemoryBuffer &&other) { + assert(this != &other); + deallocate(); + move(other); + return *this; + } +#endif + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const { return *this; } +}; + +template <typename T, std::size_t SIZE, typename Allocator> +void MemoryBuffer<T, SIZE, Allocator>::grow(std::size_t size) { + std::size_t new_capacity = this->capacity_ + this->capacity_ / 2; + if (size > new_capacity) + new_capacity = size; +#if FMT_USE_ALLOCATOR_TRAITS + T *new_ptr = + std::allocator_traits<Allocator>::allocate(*this, new_capacity, FMT_NULL); +#else + T *new_ptr = this->allocate(new_capacity, FMT_NULL); +#endif + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_, + make_ptr(new_ptr, new_capacity)); + std::size_t old_capacity = this->capacity_; + T *old_ptr = this->ptr_; + this->capacity_ = new_capacity; + this->ptr_ = new_ptr; + // deallocate may throw (at least in principle), but it doesn't matter since + // the buffer already uses the new storage and will deallocate it in case + // of exception. + if (old_ptr != data_) + Allocator::deallocate(old_ptr, old_capacity); +} + +// A fixed-size buffer. +template <typename Char> +class FixedBuffer : public fmt::Buffer<Char> { + public: + FixedBuffer(Char *array, std::size_t size) : fmt::Buffer<Char>(array, size) {} + + protected: + FMT_API void grow(std::size_t size) FMT_OVERRIDE; +}; + +template <typename Char> +class BasicCharTraits { + public: +#if FMT_SECURE_SCL + typedef stdext::checked_array_iterator<Char*> CharPtr; +#else + typedef Char *CharPtr; +#endif + static Char cast(int value) { return static_cast<Char>(value); } +}; + +template <typename Char> +class CharTraits; + +template <> +class CharTraits<char> : public BasicCharTraits<char> { + private: + // Conversion from wchar_t to char is not allowed. + static char convert(wchar_t); + + public: + static char convert(char value) { return value; } + + // Formats a floating-point number. + template <typename T> + FMT_API static int format_float(char *buffer, std::size_t size, + const char *format, unsigned width, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int CharTraits<char>::format_float<double> + (char *buffer, std::size_t size, + const char* format, unsigned width, int precision, double value); +extern template int CharTraits<char>::format_float<long double> + (char *buffer, std::size_t size, + const char* format, unsigned width, int precision, long double value); +#endif + +template <> +class CharTraits<wchar_t> : public BasicCharTraits<wchar_t> { + public: + static wchar_t convert(char value) { return value; } + static wchar_t convert(wchar_t value) { return value; } + + template <typename T> + FMT_API static int format_float(wchar_t *buffer, std::size_t size, + const wchar_t *format, unsigned width, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int CharTraits<wchar_t>::format_float<double> + (wchar_t *buffer, std::size_t size, + const wchar_t* format, unsigned width, int precision, double value); +extern template int CharTraits<wchar_t>::format_float<long double> + (wchar_t *buffer, std::size_t size, + const wchar_t* format, unsigned width, int precision, long double value); +#endif + +// Checks if a number is negative - used to avoid warnings. +template <bool IsSigned> +struct SignChecker { + template <typename T> + static bool is_negative(T value) { return value < 0; } +}; + +template <> +struct SignChecker<false> { + template <typename T> + static bool is_negative(T) { return false; } +}; + +// Returns true if value is negative, false otherwise. +// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. +template <typename T> +inline bool is_negative(T value) { + return SignChecker<std::numeric_limits<T>::is_signed>::is_negative(value); +} + +// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. +template <bool FitsIn32Bits> +struct TypeSelector { typedef uint32_t Type; }; + +template <> +struct TypeSelector<false> { typedef uint64_t Type; }; + +template <typename T> +struct IntTraits { + // Smallest of uint32_t and uint64_t that is large enough to represent + // all values of T. + typedef typename + TypeSelector<std::numeric_limits<T>::digits <= 32>::Type MainType; +}; + +FMT_API FMT_NORETURN void report_unknown_type(char code, const char *type); + +// Static data is placed in this class template to allow header-only +// configuration. +template <typename T = void> +struct FMT_API BasicData { + static const uint32_t POWERS_OF_10_32[]; + static const uint64_t POWERS_OF_10_64[]; + static const char DIGITS[]; +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template struct BasicData<void>; +#endif + +typedef BasicData<> Data; + +#ifdef FMT_BUILTIN_CLZLL +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +inline unsigned count_digits(uint64_t n) { + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1; +} +#else +// Fallback version of count_digits used when __builtin_clz is not available. +inline unsigned count_digits(uint64_t n) { + unsigned count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#endif + +#ifdef FMT_BUILTIN_CLZ +// Optional version of count_digits for better performance on 32-bit platforms. +inline unsigned count_digits(uint32_t n) { + int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1; +} +#endif + +// A functor that doesn't add a thousands separator. +struct NoThousandsSep { + template <typename Char> + void operator()(Char *) {} +}; + +// A functor that adds a thousands separator. +class ThousandsSep { + private: + fmt::StringRef sep_; + + // Index of a decimal digit with the least significant digit having index 0. + unsigned digit_index_; + + public: + explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {} + + template <typename Char> + void operator()(Char *&buffer) { + if (++digit_index_ % 3 != 0) + return; + buffer -= sep_.size(); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), + internal::make_ptr(buffer, sep_.size())); + } +}; + +// Formats a decimal unsigned integer value writing into buffer. +// thousands_sep is a functor that is called after writing each char to +// add a thousands separator if necessary. +template <typename UInt, typename Char, typename ThousandsSep> +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, + ThousandsSep thousands_sep) { + buffer += num_digits; + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast<unsigned>((value % 100) * 2); + value /= 100; + *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = Data::DIGITS[index]; + thousands_sep(buffer); + } + if (value < 10) { + *--buffer = static_cast<char>('0' + value); + return; + } + unsigned index = static_cast<unsigned>(value * 2); + *--buffer = Data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = Data::DIGITS[index]; +} + +template <typename UInt, typename Char> +inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { + format_decimal(buffer, value, num_digits, NoThousandsSep()); + return; +} + +#ifndef _WIN32 +# define FMT_USE_WINDOWS_H 0 +#elif !defined(FMT_USE_WINDOWS_H) +# define FMT_USE_WINDOWS_H 1 +#endif + +// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. +// All the functionality that relies on it will be disabled too. +#if FMT_USE_WINDOWS_H +// A converter from UTF-8 to UTF-16. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF8ToUTF16 { + private: + MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer_; + + public: + FMT_API explicit UTF8ToUTF16(StringRef s); + operator WStringRef() const { return WStringRef(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const wchar_t *c_str() const { return &buffer_[0]; } + std::wstring str() const { return std::wstring(&buffer_[0], size()); } +}; + +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class UTF16ToUTF8 { + private: + MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer_; + + public: + UTF16ToUTF8() {} + FMT_API explicit UTF16ToUTF8(WStringRef s); + operator StringRef() const { return StringRef(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const char *c_str() const { return &buffer_[0]; } + std::string str() const { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(WStringRef s); +}; + +FMT_API void format_windows_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; +#endif + +// A formatting argument value. +struct Value { + template <typename Char> + struct StringValue { + const Char *value; + std::size_t size; + }; + + typedef void (*FormatFunc)( + void *formatter, const void *arg, void *format_str_ptr); + + struct CustomValue { + const void *value; + FormatFunc format; + }; + + union { + int int_value; + unsigned uint_value; + LongLong long_long_value; + ULongLong ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + StringValue<char> string; + StringValue<signed char> sstring; + StringValue<unsigned char> ustring; + StringValue<wchar_t> wstring; + CustomValue custom; + }; + + enum Type { + NONE, NAMED_ARG, + // Integer types should go first, + INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR, + // followed by floating-point types. + DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, + CSTRING, STRING, WSTRING, POINTER, CUSTOM + }; +}; + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in internal::MemoryBuffer. +struct Arg : Value { + Type type; +}; + +template <typename Char> +struct NamedArg; +template <typename Char, typename T> +struct NamedArgWithType; + +template <typename T = void> +struct Null {}; + +// A helper class template to enable or disable overloads taking wide +// characters and strings in MakeValue. +template <typename T, typename Char> +struct WCharHelper { + typedef Null<T> Supported; + typedef T Unsupported; +}; + +template <typename T> +struct WCharHelper<T, wchar_t> { + typedef T Supported; + typedef Null<T> Unsupported; +}; + +typedef char Yes[1]; +typedef char No[2]; + +template <typename T> +T &get(); + +// These are non-members to workaround an overload resolution bug in bcc32. +Yes &convert(fmt::ULongLong); +No &convert(...); + +template <typename T, bool ENABLE_CONVERSION> +struct ConvertToIntImpl { + enum { value = ENABLE_CONVERSION }; +}; + +template <typename T, bool ENABLE_CONVERSION> +struct ConvertToIntImpl2 { + enum { value = false }; +}; + +template <typename T> +struct ConvertToIntImpl2<T, true> { + enum { + // Don't convert numeric types. + value = ConvertToIntImpl<T, !std::numeric_limits<T>::is_specialized>::value + }; +}; + +template <typename T> +struct ConvertToInt { + enum { + enable_conversion = sizeof(fmt::internal::convert(get<T>())) == sizeof(Yes) + }; + enum { value = ConvertToIntImpl2<T, enable_conversion>::value }; +}; + +#define FMT_DISABLE_CONVERSION_TO_INT(Type) \ + template <> \ + struct ConvertToInt<Type> { enum { value = 0 }; } + +// Silence warnings about convering float to int. +FMT_DISABLE_CONVERSION_TO_INT(float); +FMT_DISABLE_CONVERSION_TO_INT(double); +FMT_DISABLE_CONVERSION_TO_INT(long double); + +template <bool B, class T = void> +struct EnableIf {}; + +template <class T> +struct EnableIf<true, T> { typedef T type; }; + +template <bool B, class T, class F> +struct Conditional { typedef T type; }; + +template <class T, class F> +struct Conditional<false, T, F> { typedef F type; }; + +// For bcc32 which doesn't understand ! in template arguments. +template <bool> +struct Not { enum { value = 0 }; }; + +template <> +struct Not<false> { enum { value = 1 }; }; + +template <typename T> +struct FalseType { enum { value = 0 }; }; + +template <typename T, T> struct LConvCheck { + LConvCheck(int) {} +}; + +// Returns the thousands separator for the current locale. +// We check if ``lconv`` contains ``thousands_sep`` because on Android +// ``lconv`` is stubbed as an empty struct. +template <typename LConv> +inline StringRef thousands_sep( + LConv *lc, LConvCheck<char *LConv::*, &LConv::thousands_sep> = 0) { + return lc->thousands_sep; +} + +inline fmt::StringRef thousands_sep(...) { return ""; } + +#define FMT_CONCAT(a, b) a##b + +#if FMT_GCC_VERSION >= 303 +# define FMT_UNUSED __attribute__((unused)) +#else +# define FMT_UNUSED +#endif + +#ifndef FMT_USE_STATIC_ASSERT +# define FMT_USE_STATIC_ASSERT 0 +#endif + +#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ + (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 +# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) +#else +# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) +# define FMT_STATIC_ASSERT(cond, message) \ + typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED +#endif + +template <typename Formatter> +void format_arg(Formatter&, ...) { + FMT_STATIC_ASSERT(FalseType<Formatter>::value, + "Cannot format argument. To enable the use of ostream " + "operator<< include fmt/ostream.h. Otherwise provide " + "an overload of format_arg."); +} + +// Makes an Arg object from any type. +template <typename Formatter> +class MakeValue : public Arg { + public: + typedef typename Formatter::Char Char; + + private: + // The following two methods are private to disallow formatting of + // arbitrary pointers. If you want to output a pointer cast it to + // "void *" or "const void *". In particular, this forbids formatting + // of "[const] volatile char *" which is printed as bool by iostreams. + // Do not implement! + template <typename T> + MakeValue(const T *value); + template <typename T> + MakeValue(T *value); + + // The following methods are private to disallow formatting of wide + // characters and strings into narrow strings as in + // fmt::format("{}", L"test"); + // To fix this, use a wide format string: fmt::format(L"{}", L"test"). +#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED) + MakeValue(typename WCharHelper<wchar_t, Char>::Unsupported); +#endif + MakeValue(typename WCharHelper<wchar_t *, Char>::Unsupported); + MakeValue(typename WCharHelper<const wchar_t *, Char>::Unsupported); + MakeValue(typename WCharHelper<const std::wstring &, Char>::Unsupported); +#if FMT_HAS_STRING_VIEW + MakeValue(typename WCharHelper<const std::wstring_view &, Char>::Unsupported); +#endif + MakeValue(typename WCharHelper<WStringRef, Char>::Unsupported); + + void set_string(StringRef str) { + string.value = str.data(); + string.size = str.size(); + } + + void set_string(WStringRef str) { + wstring.value = str.data(); + wstring.size = str.size(); + } + + // Formats an argument of a custom type, such as a user-defined class. + template <typename T> + static void format_custom_arg( + void *formatter, const void *arg, void *format_str_ptr) { + format_arg(*static_cast<Formatter*>(formatter), + *static_cast<const Char**>(format_str_ptr), + *static_cast<const T*>(arg)); + } + + public: + MakeValue() {} + +#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \ + MakeValue(Type value) { field = rhs; } \ + static uint64_t type(Type) { return Arg::TYPE; } + +#define FMT_MAKE_VALUE(Type, field, TYPE) \ + FMT_MAKE_VALUE_(Type, field, TYPE, value) + + FMT_MAKE_VALUE(bool, int_value, BOOL) + FMT_MAKE_VALUE(short, int_value, INT) + FMT_MAKE_VALUE(unsigned short, uint_value, UINT) + FMT_MAKE_VALUE(int, int_value, INT) + FMT_MAKE_VALUE(unsigned, uint_value, UINT) + + MakeValue(long value) { + // To minimize the number of types we need to deal with, long is + // translated either to int or to long long depending on its size. + if (const_check(sizeof(long) == sizeof(int))) + int_value = static_cast<int>(value); + else + long_long_value = value; + } + static uint64_t type(long) { + return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG; + } + + MakeValue(unsigned long value) { + if (const_check(sizeof(unsigned long) == sizeof(unsigned))) + uint_value = static_cast<unsigned>(value); + else + ulong_long_value = value; + } + static uint64_t type(unsigned long) { + return sizeof(unsigned long) == sizeof(unsigned) ? + Arg::UINT : Arg::ULONG_LONG; + } + + FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG) + FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG) + FMT_MAKE_VALUE(float, double_value, DOUBLE) + FMT_MAKE_VALUE(double, double_value, DOUBLE) + FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE) + FMT_MAKE_VALUE(signed char, int_value, INT) + FMT_MAKE_VALUE(unsigned char, uint_value, UINT) + FMT_MAKE_VALUE(char, int_value, CHAR) + +#if __cplusplus >= 201103L + template < + typename T, + typename = typename std::enable_if< + std::is_enum<T>::value && ConvertToInt<T>::value>::type> + MakeValue(T value) { int_value = value; } + + template < + typename T, + typename = typename std::enable_if< + std::is_enum<T>::value && ConvertToInt<T>::value>::type> + static uint64_t type(T) { return Arg::INT; } +#endif + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) + MakeValue(typename WCharHelper<wchar_t, Char>::Supported value) { + int_value = value; + } + static uint64_t type(wchar_t) { return Arg::CHAR; } +#endif + +#define FMT_MAKE_STR_VALUE(Type, TYPE) \ + MakeValue(Type value) { set_string(value); } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_VALUE(char *, string.value, CSTRING) + FMT_MAKE_VALUE(const char *, string.value, CSTRING) + FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING) + FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING) + FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) + FMT_MAKE_STR_VALUE(const std::string &, STRING) +#if FMT_HAS_STRING_VIEW + FMT_MAKE_STR_VALUE(const std::string_view &, STRING) +#endif + FMT_MAKE_STR_VALUE(StringRef, STRING) + FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str()) + +#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \ + MakeValue(typename WCharHelper<Type, Char>::Supported value) { \ + set_string(value); \ + } \ + static uint64_t type(Type) { return Arg::TYPE; } + + FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING) + FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING) + FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING) +#if FMT_HAS_STRING_VIEW + FMT_MAKE_WSTR_VALUE(const std::wstring_view &, WSTRING) +#endif + FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING) + + FMT_MAKE_VALUE(void *, pointer, POINTER) + FMT_MAKE_VALUE(const void *, pointer, POINTER) + + template <typename T> + MakeValue(const T &value, + typename EnableIf<Not< + ConvertToInt<T>::value>::value, int>::type = 0) { + custom.value = &value; + custom.format = &format_custom_arg<T>; + } + + template <typename T> + static typename EnableIf<Not<ConvertToInt<T>::value>::value, uint64_t>::type + type(const T &) { + return Arg::CUSTOM; + } + + // Additional template param `Char_` is needed here because make_type always + // uses char. + template <typename Char_> + MakeValue(const NamedArg<Char_> &value) { pointer = &value; } + template <typename Char_, typename T> + MakeValue(const NamedArgWithType<Char_, T> &value) { pointer = &value; } + + template <typename Char_> + static uint64_t type(const NamedArg<Char_> &) { return Arg::NAMED_ARG; } + template <typename Char_, typename T> + static uint64_t type(const NamedArgWithType<Char_, T> &) { return Arg::NAMED_ARG; } +}; + +template <typename Formatter> +class MakeArg : public Arg { +public: + MakeArg() { + type = Arg::NONE; + } + + template <typename T> + MakeArg(const T &value) + : Arg(MakeValue<Formatter>(value)) { + type = static_cast<Arg::Type>(MakeValue<Formatter>::type(value)); + } +}; + +template <typename Char> +struct NamedArg : Arg { + BasicStringRef<Char> name; + + template <typename T> + NamedArg(BasicStringRef<Char> argname, const T &value) + : Arg(MakeArg< BasicFormatter<Char> >(value)), name(argname) {} +}; + +template <typename Char, typename T> +struct NamedArgWithType : NamedArg<Char> { + NamedArgWithType(BasicStringRef<Char> argname, const T &value) + : NamedArg<Char>(argname, value) {} +}; + +class RuntimeError : public std::runtime_error { + protected: + RuntimeError() : std::runtime_error("") {} + RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {} + FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; +}; + +template <typename Char> +class ArgMap; +} // namespace internal + +/** An argument list. */ +class ArgList { + private: + // To reduce compiled code size per formatting function call, types of first + // MAX_PACKED_ARGS arguments are passed in the types_ field. + uint64_t types_; + union { + // If the number of arguments is less than MAX_PACKED_ARGS, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::Value *values_; + const internal::Arg *args_; + }; + + internal::Arg::Type type(unsigned index) const { + return type(types_, index); + } + + template <typename Char> + friend class internal::ArgMap; + + public: + // Maximum number of arguments with packed types. + enum { MAX_PACKED_ARGS = 16 }; + + ArgList() : types_(0) {} + + ArgList(ULongLong types, const internal::Value *values) + : types_(types), values_(values) {} + ArgList(ULongLong types, const internal::Arg *args) + : types_(types), args_(args) {} + + uint64_t types() const { return types_; } + + /** Returns the argument at specified index. */ + internal::Arg operator[](unsigned index) const { + using internal::Arg; + Arg arg; + bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE; + if (index < MAX_PACKED_ARGS) { + Arg::Type arg_type = type(index); + internal::Value &val = arg; + if (arg_type != Arg::NONE) + val = use_values ? values_[index] : args_[index]; + arg.type = arg_type; + return arg; + } + if (use_values) { + // The index is greater than the number of arguments that can be stored + // in values, so return a "none" argument. + arg.type = Arg::NONE; + return arg; + } + for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) { + if (args_[i].type == Arg::NONE) + return args_[i]; + } + return args_[index]; + } + + static internal::Arg::Type type(uint64_t types, unsigned index) { + unsigned shift = index * 4; + uint64_t mask = 0xf; + return static_cast<internal::Arg::Type>( + (types & (mask << shift)) >> shift); + } +}; + +#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call + +/** + \rst + An argument visitor based on the `curiously recurring template pattern + <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_. + + To use `~fmt::ArgVisitor` define a subclass that implements some or all of the + visit methods with the same signatures as the methods in `~fmt::ArgVisitor`, + for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. Then calling + `~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::ArgVisitor` will be called. + + **Example**:: + + class MyArgVisitor : public fmt::ArgVisitor<MyArgVisitor, void> { + public: + void visit_int(int value) { fmt::print("{}", value); } + void visit_double(double value) { fmt::print("{}", value ); } + }; + \endrst + */ +template <typename Impl, typename Result> +class ArgVisitor { + private: + typedef internal::Arg Arg; + + public: + void report_unhandled_arg() {} + + Result visit_unhandled_arg() { + FMT_DISPATCH(report_unhandled_arg()); + return Result(); + } + + /** Visits an ``int`` argument. **/ + Result visit_int(int value) { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``long long`` argument. **/ + Result visit_long_long(LongLong value) { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an ``unsigned`` argument. **/ + Result visit_uint(unsigned value) { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an ``unsigned long long`` argument. **/ + Result visit_ulong_long(ULongLong value) { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``bool`` argument. **/ + Result visit_bool(bool value) { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits a ``char`` or ``wchar_t`` argument. **/ + Result visit_char(int value) { + return FMT_DISPATCH(visit_any_int(value)); + } + + /** Visits an argument of any integral type. **/ + template <typename T> + Result visit_any_int(T) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a ``double`` argument. **/ + Result visit_double(double value) { + return FMT_DISPATCH(visit_any_double(value)); + } + + /** Visits a ``long double`` argument. **/ + Result visit_long_double(long double value) { + return FMT_DISPATCH(visit_any_double(value)); + } + + /** Visits a ``double`` or ``long double`` argument. **/ + template <typename T> + Result visit_any_double(T) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a null-terminated C string (``const char *``) argument. **/ + Result visit_cstring(const char *) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a string argument. **/ + Result visit_string(Arg::StringValue<char>) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a wide string argument. **/ + Result visit_wstring(Arg::StringValue<wchar_t>) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits a pointer argument. **/ + Result visit_pointer(const void *) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** Visits an argument of a custom (user-defined) type. **/ + Result visit_custom(Arg::CustomValue) { + return FMT_DISPATCH(visit_unhandled_arg()); + } + + /** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be + called. + \endrst + */ + Result visit(const Arg &arg) { + switch (arg.type) { + case Arg::NONE: + case Arg::NAMED_ARG: + FMT_ASSERT(false, "invalid argument type"); + break; + case Arg::INT: + return FMT_DISPATCH(visit_int(arg.int_value)); + case Arg::UINT: + return FMT_DISPATCH(visit_uint(arg.uint_value)); + case Arg::LONG_LONG: + return FMT_DISPATCH(visit_long_long(arg.long_long_value)); + case Arg::ULONG_LONG: + return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); + case Arg::BOOL: + return FMT_DISPATCH(visit_bool(arg.int_value != 0)); + case Arg::CHAR: + return FMT_DISPATCH(visit_char(arg.int_value)); + case Arg::DOUBLE: + return FMT_DISPATCH(visit_double(arg.double_value)); + case Arg::LONG_DOUBLE: + return FMT_DISPATCH(visit_long_double(arg.long_double_value)); + case Arg::CSTRING: + return FMT_DISPATCH(visit_cstring(arg.string.value)); + case Arg::STRING: + return FMT_DISPATCH(visit_string(arg.string)); + case Arg::WSTRING: + return FMT_DISPATCH(visit_wstring(arg.wstring)); + case Arg::POINTER: + return FMT_DISPATCH(visit_pointer(arg.pointer)); + case Arg::CUSTOM: + return FMT_DISPATCH(visit_custom(arg.custom)); + } + return Result(); + } +}; + +enum Alignment { + ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC +}; + +// Flags. +enum { + SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, + CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. +}; + +// An empty format specifier. +struct EmptySpec {}; + +// A type specifier. +template <char TYPE> +struct TypeSpec : EmptySpec { + Alignment align() const { return ALIGN_DEFAULT; } + unsigned width() const { return 0; } + int precision() const { return -1; } + bool flag(unsigned) const { return false; } + char type() const { return TYPE; } + char type_prefix() const { return TYPE; } + char fill() const { return ' '; } +}; + +// A width specifier. +struct WidthSpec { + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of WidthSpec and its subclasses. + wchar_t fill_; + + WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {} + + unsigned width() const { return width_; } + wchar_t fill() const { return fill_; } +}; + +// An alignment specifier. +struct AlignSpec : WidthSpec { + Alignment align_; + + AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) + : WidthSpec(width, fill), align_(align) {} + + Alignment align() const { return align_; } + + int precision() const { return -1; } +}; + +// An alignment and type specifier. +template <char TYPE> +struct AlignTypeSpec : AlignSpec { + AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {} + + bool flag(unsigned) const { return false; } + char type() const { return TYPE; } + char type_prefix() const { return TYPE; } +}; + +// A full format specifier. +struct FormatSpec : AlignSpec { + unsigned flags_; + int precision_; + char type_; + + FormatSpec( + unsigned width = 0, char type = 0, wchar_t fill = ' ') + : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {} + + bool flag(unsigned f) const { return (flags_ & f) != 0; } + int precision() const { return precision_; } + char type() const { return type_; } + char type_prefix() const { return type_; } +}; + +// An integer format specifier. +template <typename T, typename SpecT = TypeSpec<0>, typename Char = char> +class IntFormatSpec : public SpecT { + private: + T value_; + + public: + IntFormatSpec(T val, const SpecT &spec = SpecT()) + : SpecT(spec), value_(val) {} + + T value() const { return value_; } +}; + +// A string format specifier. +template <typename Char> +class StrFormatSpec : public AlignSpec { + private: + const Char *str_; + + public: + template <typename FillChar> + StrFormatSpec(const Char *str, unsigned width, FillChar fill) + : AlignSpec(width, fill), str_(str) { + internal::CharTraits<Char>::convert(FillChar()); + } + + const Char *str() const { return str_; } +}; + +/** + Returns an integer format specifier to format the value in base 2. + */ +IntFormatSpec<int, TypeSpec<'b'> > bin(int value); + +/** + Returns an integer format specifier to format the value in base 8. + */ +IntFormatSpec<int, TypeSpec<'o'> > oct(int value); + +/** + Returns an integer format specifier to format the value in base 16 using + lower-case letters for the digits above 9. + */ +IntFormatSpec<int, TypeSpec<'x'> > hex(int value); + +/** + Returns an integer formatter format specifier to format in base 16 using + upper-case letters for the digits above 9. + */ +IntFormatSpec<int, TypeSpec<'X'> > hexu(int value); + +/** + \rst + Returns an integer format specifier to pad the formatted argument with the + fill character to the specified width using the default (right) numeric + alignment. + + **Example**:: + + MemoryWriter out; + out << pad(hex(0xcafe), 8, '0'); + // out.str() == "0000cafe" + + \endrst + */ +template <char TYPE_CODE, typename Char> +IntFormatSpec<int, AlignTypeSpec<TYPE_CODE>, Char> pad( + int value, unsigned width, Char fill = ' '); + +#define FMT_DEFINE_INT_FORMATTERS(TYPE) \ +inline IntFormatSpec<TYPE, TypeSpec<'b'> > bin(TYPE value) { \ + return IntFormatSpec<TYPE, TypeSpec<'b'> >(value, TypeSpec<'b'>()); \ +} \ + \ +inline IntFormatSpec<TYPE, TypeSpec<'o'> > oct(TYPE value) { \ + return IntFormatSpec<TYPE, TypeSpec<'o'> >(value, TypeSpec<'o'>()); \ +} \ + \ +inline IntFormatSpec<TYPE, TypeSpec<'x'> > hex(TYPE value) { \ + return IntFormatSpec<TYPE, TypeSpec<'x'> >(value, TypeSpec<'x'>()); \ +} \ + \ +inline IntFormatSpec<TYPE, TypeSpec<'X'> > hexu(TYPE value) { \ + return IntFormatSpec<TYPE, TypeSpec<'X'> >(value, TypeSpec<'X'>()); \ +} \ + \ +template <char TYPE_CODE> \ +inline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> > pad( \ + IntFormatSpec<TYPE, TypeSpec<TYPE_CODE> > f, unsigned width) { \ + return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> >( \ + f.value(), AlignTypeSpec<TYPE_CODE>(width, ' ')); \ +} \ + \ +/* For compatibility with older compilers we provide two overloads for pad, */ \ +/* one that takes a fill character and one that doesn't. In the future this */ \ +/* can be replaced with one overload making the template argument Char */ \ +/* default to char (C++11). */ \ +template <char TYPE_CODE, typename Char> \ +inline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char> pad( \ + IntFormatSpec<TYPE, TypeSpec<TYPE_CODE>, Char> f, \ + unsigned width, Char fill) { \ + return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char>( \ + f.value(), AlignTypeSpec<TYPE_CODE>(width, fill)); \ +} \ + \ +inline IntFormatSpec<TYPE, AlignTypeSpec<0> > pad( \ + TYPE value, unsigned width) { \ + return IntFormatSpec<TYPE, AlignTypeSpec<0> >( \ + value, AlignTypeSpec<0>(width, ' ')); \ +} \ + \ +template <typename Char> \ +inline IntFormatSpec<TYPE, AlignTypeSpec<0>, Char> pad( \ + TYPE value, unsigned width, Char fill) { \ + return IntFormatSpec<TYPE, AlignTypeSpec<0>, Char>( \ + value, AlignTypeSpec<0>(width, fill)); \ +} + +FMT_DEFINE_INT_FORMATTERS(int) +FMT_DEFINE_INT_FORMATTERS(long) +FMT_DEFINE_INT_FORMATTERS(unsigned) +FMT_DEFINE_INT_FORMATTERS(unsigned long) +FMT_DEFINE_INT_FORMATTERS(LongLong) +FMT_DEFINE_INT_FORMATTERS(ULongLong) + +/** + \rst + Returns a string formatter that pads the formatted argument with the fill + character to the specified width using the default (left) string alignment. + + **Example**:: + + std::string s = str(MemoryWriter() << pad("abc", 8)); + // s == "abc " + + \endrst + */ +template <typename Char> +inline StrFormatSpec<Char> pad( + const Char *str, unsigned width, Char fill = ' ') { + return StrFormatSpec<Char>(str, width, fill); +} + +inline StrFormatSpec<wchar_t> pad( + const wchar_t *str, unsigned width, char fill = ' ') { + return StrFormatSpec<wchar_t>(str, width, fill); +} + +namespace internal { + +template <typename Char> +class ArgMap { + private: + typedef std::vector< + std::pair<fmt::BasicStringRef<Char>, internal::Arg> > MapType; + typedef typename MapType::value_type Pair; + + MapType map_; + + public: + void init(const ArgList &args); + + const internal::Arg *find(const fmt::BasicStringRef<Char> &name) const { + // The list is unsorted, so just return the first matching name. + for (typename MapType::const_iterator it = map_.begin(), end = map_.end(); + it != end; ++it) { + if (it->first == name) + return &it->second; + } + return FMT_NULL; + } +}; + +template <typename Char> +void ArgMap<Char>::init(const ArgList &args) { + if (!map_.empty()) + return; + typedef internal::NamedArg<Char> NamedArg; + const NamedArg *named_arg = FMT_NULL; + bool use_values = + args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; + if (use_values) { + for (unsigned i = 0;/*nothing*/; ++i) { + internal::Arg::Type arg_type = args.type(i); + switch (arg_type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast<const NamedArg*>(args.values_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } + return; + } + for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { + internal::Arg::Type arg_type = args.type(i); + if (arg_type == internal::Arg::NAMED_ARG) { + named_arg = static_cast<const NamedArg*>(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + } + } + for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { + switch (args.args_[i].type) { + case internal::Arg::NONE: + return; + case internal::Arg::NAMED_ARG: + named_arg = static_cast<const NamedArg*>(args.args_[i].pointer); + map_.push_back(Pair(named_arg->name, *named_arg)); + break; + default: + /*nothing*/; + } + } +} + +template <typename Impl, typename Char, typename Spec = fmt::FormatSpec> +class ArgFormatterBase : public ArgVisitor<Impl, void> { + private: + BasicWriter<Char> &writer_; + Spec &spec_; + + FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase); + + void write_pointer(const void *p) { + spec_.flags_ = HASH_FLAG; + spec_.type_ = 'x'; + writer_.write_int(reinterpret_cast<uintptr_t>(p), spec_); + } + + // workaround MSVC two-phase lookup issue + typedef internal::Arg Arg; + + protected: + BasicWriter<Char> &writer() { return writer_; } + Spec &spec() { return spec_; } + + void write(bool value) { + const char *str_value = value ? "true" : "false"; + Arg::StringValue<char> str = { str_value, std::strlen(str_value) }; + writer_.write_str(str, spec_); + } + + void write(const char *value) { + Arg::StringValue<char> str = {value, value ? std::strlen(value) : 0}; + writer_.write_str(str, spec_); + } + + public: + typedef Spec SpecType; + + ArgFormatterBase(BasicWriter<Char> &w, Spec &s) + : writer_(w), spec_(s) {} + + template <typename T> + void visit_any_int(T value) { writer_.write_int(value, spec_); } + + template <typename T> + void visit_any_double(T value) { writer_.write_double(value, spec_); } + + void visit_bool(bool value) { + if (spec_.type_) { + visit_any_int(value); + return; + } + write(value); + } + + void visit_char(int value) { + if (spec_.type_ && spec_.type_ != 'c') { + spec_.flags_ |= CHAR_FLAG; + writer_.write_int(value, spec_); + return; + } + if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) + FMT_THROW(FormatError("invalid format specifier for char")); + typedef typename BasicWriter<Char>::CharPtr CharPtr; + Char fill = internal::CharTraits<Char>::cast(spec_.fill()); + CharPtr out = CharPtr(); + const unsigned CHAR_SIZE = 1; + if (spec_.width_ > CHAR_SIZE) { + out = writer_.grow_buffer(spec_.width_); + if (spec_.align_ == ALIGN_RIGHT) { + std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill); + out += spec_.width_ - CHAR_SIZE; + } else if (spec_.align_ == ALIGN_CENTER) { + out = writer_.fill_padding(out, spec_.width_, + internal::const_check(CHAR_SIZE), fill); + } else { + std::uninitialized_fill_n(out + CHAR_SIZE, + spec_.width_ - CHAR_SIZE, fill); + } + } else { + out = writer_.grow_buffer(CHAR_SIZE); + } + *out = internal::CharTraits<Char>::cast(value); + } + + void visit_cstring(const char *value) { + if (spec_.type_ == 'p') + return write_pointer(value); + write(value); + } + + // Qualification with "internal" here and below is a workaround for nvcc. + void visit_string(internal::Arg::StringValue<char> value) { + writer_.write_str(value, spec_); + } + + using ArgVisitor<Impl, void>::visit_wstring; + + void visit_wstring(internal::Arg::StringValue<Char> value) { + writer_.write_str(value, spec_); + } + + void visit_pointer(const void *value) { + if (spec_.type_ && spec_.type_ != 'p') + report_unknown_type(spec_.type_, "pointer"); + write_pointer(value); + } +}; + +class FormatterBase { + private: + ArgList args_; + int next_arg_index_; + + // Returns the argument with specified index. + FMT_API Arg do_get_arg(unsigned arg_index, const char *&error); + + protected: + const ArgList &args() const { return args_; } + + explicit FormatterBase(const ArgList &args) { + args_ = args; + next_arg_index_ = 0; + } + + // Returns the next argument. + Arg next_arg(const char *&error) { + if (next_arg_index_ >= 0) + return do_get_arg(internal::to_unsigned(next_arg_index_++), error); + error = "cannot switch from manual to automatic argument indexing"; + return Arg(); + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + Arg get_arg(unsigned arg_index, const char *&error) { + return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg(); + } + + bool check_no_auto_index(const char *&error) { + if (next_arg_index_ > 0) { + error = "cannot switch from automatic to manual argument indexing"; + return false; + } + next_arg_index_ = -1; + return true; + } + + template <typename Char> + void write(BasicWriter<Char> &w, const Char *start, const Char *end) { + if (start != end) + w << BasicStringRef<Char>(start, internal::to_unsigned(end - start)); + } +}; +} // namespace internal + +/** + \rst + An argument formatter based on the `curiously recurring template pattern + <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_. + + To use `~fmt::BasicArgFormatter` define a subclass that implements some or + all of the visit methods with the same signatures as the methods in + `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. + Pass the subclass as the *Impl* template parameter. When a formatting + function processes an argument, it will dispatch to a visit method + specific to the argument type. For example, if the argument type is + ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass + will be called. If the subclass doesn't contain a method with this signature, + then a corresponding method of `~fmt::BasicArgFormatter` or its superclass + will be called. + \endrst + */ +template <typename Impl, typename Char, typename Spec = fmt::FormatSpec> +class BasicArgFormatter : public internal::ArgFormatterBase<Impl, Char, Spec> { + private: + BasicFormatter<Char, Impl> &formatter_; + const Char *format_; + + public: + /** + \rst + Constructs an argument formatter object. + *formatter* is a reference to the main formatter object, *spec* contains + format specifier information for standard argument types, and *fmt* points + to the part of the format string being parsed for custom argument types. + \endrst + */ + BasicArgFormatter(BasicFormatter<Char, Impl> &formatter, + Spec &spec, const Char *fmt) + : internal::ArgFormatterBase<Impl, Char, Spec>(formatter.writer(), spec), + formatter_(formatter), format_(fmt) {} + + /** Formats an argument of a custom (user-defined) type. */ + void visit_custom(internal::Arg::CustomValue c) { + c.format(&formatter_, c.value, &format_); + } +}; + +/** The default argument formatter. */ +template <typename Char> +class ArgFormatter : + public BasicArgFormatter<ArgFormatter<Char>, Char, FormatSpec> { + public: + /** Constructs an argument formatter object. */ + ArgFormatter(BasicFormatter<Char> &formatter, + FormatSpec &spec, const Char *fmt) + : BasicArgFormatter<ArgFormatter<Char>, + Char, FormatSpec>(formatter, spec, fmt) {} +}; + +/** This template formats data and writes the output to a writer. */ +template <typename CharType, typename ArgFormatter> +class BasicFormatter : private internal::FormatterBase { + public: + /** The character type for the output. */ + typedef CharType Char; + + private: + BasicWriter<Char> &writer_; + internal::ArgMap<Char> map_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter); + + using internal::FormatterBase::get_arg; + + // Checks if manual indexing is used and returns the argument with + // specified name. + internal::Arg get_arg(BasicStringRef<Char> arg_name, const char *&error); + + // Parses argument index and returns corresponding argument. + internal::Arg parse_arg_index(const Char *&s); + + // Parses argument name and returns corresponding argument. + internal::Arg parse_arg_name(const Char *&s); + + public: + /** + \rst + Constructs a ``BasicFormatter`` object. References to the arguments and + the writer are stored in the formatter object so make sure they have + appropriate lifetimes. + \endrst + */ + BasicFormatter(const ArgList &args, BasicWriter<Char> &w) + : internal::FormatterBase(args), writer_(w) {} + + /** Returns a reference to the writer associated with this formatter. */ + BasicWriter<Char> &writer() { return writer_; } + + /** Formats stored arguments and writes the output to the writer. */ + void format(BasicCStringRef<Char> format_str); + + // Formats a single argument and advances format_str, a format string pointer. + const Char *format(const Char *&format_str, const internal::Arg &arg); +}; + +// Generates a comma-separated list with results of applying f to +// numbers 0..n-1. +# define FMT_GEN(n, f) FMT_GEN##n(f) +# define FMT_GEN1(f) f(0) +# define FMT_GEN2(f) FMT_GEN1(f), f(1) +# define FMT_GEN3(f) FMT_GEN2(f), f(2) +# define FMT_GEN4(f) FMT_GEN3(f), f(3) +# define FMT_GEN5(f) FMT_GEN4(f), f(4) +# define FMT_GEN6(f) FMT_GEN5(f), f(5) +# define FMT_GEN7(f) FMT_GEN6(f), f(6) +# define FMT_GEN8(f) FMT_GEN7(f), f(7) +# define FMT_GEN9(f) FMT_GEN8(f), f(8) +# define FMT_GEN10(f) FMT_GEN9(f), f(9) +# define FMT_GEN11(f) FMT_GEN10(f), f(10) +# define FMT_GEN12(f) FMT_GEN11(f), f(11) +# define FMT_GEN13(f) FMT_GEN12(f), f(12) +# define FMT_GEN14(f) FMT_GEN13(f), f(13) +# define FMT_GEN15(f) FMT_GEN14(f), f(14) + +namespace internal { +inline uint64_t make_type() { return 0; } + +template <typename T> +inline uint64_t make_type(const T &arg) { + return MakeValue< BasicFormatter<char> >::type(arg); +} + +template <std::size_t N, bool/*IsPacked*/= (N < ArgList::MAX_PACKED_ARGS)> +struct ArgArray; + +template <std::size_t N> +struct ArgArray<N, true/*IsPacked*/> { + // '+' is used to silence GCC -Wduplicated-branches warning. + typedef Value Type[N > 0 ? N : +1]; + + template <typename Formatter, typename T> + static Value make(const T &value) { +#ifdef __clang__ + Value result = MakeValue<Formatter>(value); + // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang: + // https://github.com/fmtlib/fmt/issues/276 + (void)result.custom.format; + return result; +#else + return MakeValue<Formatter>(value); +#endif + } +}; + +template <std::size_t N> +struct ArgArray<N, false/*IsPacked*/> { + typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE + + template <typename Formatter, typename T> + static Arg make(const T &value) { return MakeArg<Formatter>(value); } +}; + +#if FMT_USE_VARIADIC_TEMPLATES +template <typename Arg, typename... Args> +inline uint64_t make_type(const Arg &first, const Args & ... tail) { + return make_type(first) | (make_type(tail...) << 4); +} + +#else + +struct ArgType { + uint64_t type; + + ArgType() : type(0) {} + + template <typename T> + ArgType(const T &arg) : type(make_type(arg)) {} +}; + +# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType() + +inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) { + return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) | + (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) | + (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) | + (t12.type << 48) | (t13.type << 52) | (t14.type << 56); +} +#endif +} // namespace internal + +# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n +# define FMT_MAKE_ARG_TYPE(n) T##n +# define FMT_MAKE_ARG(n) const T##n &v##n +# define FMT_ASSIGN_char(n) \ + arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<char> >(v##n) +# define FMT_ASSIGN_wchar_t(n) \ + arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<wchar_t> >(v##n) + +#if FMT_USE_VARIADIC_TEMPLATES +// Defines a variadic function returning void. +# define FMT_VARIADIC_VOID(func, arg_type) \ + template <typename... Args> \ + void func(arg_type arg0, const Args & ... args) { \ + typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \ + func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } + +// Defines a variadic constructor. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + template <typename... Args> \ + ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ + typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \ + func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } + +#else + +# define FMT_MAKE_REF(n) \ + fmt::internal::MakeValue< fmt::BasicFormatter<Char> >(v##n) +# define FMT_MAKE_REF2(n) v##n + +// Defines a wrapper for a function taking one argument of type arg_type +// and n additional arguments of arbitrary types. +# define FMT_WRAP1(func, arg_type, n) \ + template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \ + inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ + } + +// Emulates a variadic function returning void on a pre-C++11 compiler. +# define FMT_VARIADIC_VOID(func, arg_type) \ + inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \ + FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ + FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ + FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ + FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ + FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) + +# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ + template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \ + ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ + const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ + func(arg0, arg1, fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ + } + +// Emulates a variadic constructor on a pre-C++11 compiler. +# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ + FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) +#endif + +// Generates a comma-separated list with results of applying f to pairs +// (argument, index). +#define FMT_FOR_EACH1(f, x0) f(x0, 0) +#define FMT_FOR_EACH2(f, x0, x1) \ + FMT_FOR_EACH1(f, x0), f(x1, 1) +#define FMT_FOR_EACH3(f, x0, x1, x2) \ + FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) +#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ + FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) +#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ + FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) +#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ + FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) +#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ + FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) +#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ + FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) +#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ + FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) +#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ + FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) + +/** + An error returned by an operating system or a language runtime, + for example a file opening error. +*/ +class SystemError : public internal::RuntimeError { + private: + FMT_API void init(int err_code, CStringRef format_str, ArgList args); + + protected: + int error_code_; + + typedef char Char; // For FMT_VARIADIC_CTOR. + + SystemError() {} + + public: + /** + \rst + Constructs a :class:`fmt::SystemError` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a SystemError with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::SystemError(errno, "cannot open file '{}'", filename); + \endrst + */ + SystemError(int error_code, CStringRef message) { + init(error_code, message, ArgList()); + } + FMT_DEFAULTED_COPY_CTOR(SystemError) + FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef) + + FMT_API ~SystemError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; + + int error_code() const { return error_code_; } +}; + +/** + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + *<message>*: *<system-message>* + + where *<message>* is the passed message and *<system-message>* is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(fmt::Writer &out, int error_code, + fmt::StringRef message) FMT_NOEXCEPT; + +/** + \rst + This template provides operations for formatting and writing data into + a character stream. The output is stored in a buffer provided by a subclass + such as :class:`fmt::BasicMemoryWriter`. + + You can use one of the following typedefs for common character types: + + +---------+----------------------+ + | Type | Definition | + +=========+======================+ + | Writer | BasicWriter<char> | + +---------+----------------------+ + | WWriter | BasicWriter<wchar_t> | + +---------+----------------------+ + + \endrst + */ +template <typename Char> +class BasicWriter { + private: + // Output buffer. + Buffer<Char> &buffer_; + + FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); + + typedef typename internal::CharTraits<Char>::CharPtr CharPtr; + +#if FMT_SECURE_SCL + // Returns pointer value. + static Char *get(CharPtr p) { return p.base(); } +#else + static Char *get(Char *p) { return p; } +#endif + + // Fills the padding around the content and returns the pointer to the + // content area. + static CharPtr fill_padding(CharPtr buffer, + unsigned total_size, std::size_t content_size, wchar_t fill); + + // Grows the buffer by n characters and returns a pointer to the newly + // allocated area. + CharPtr grow_buffer(std::size_t n) { + std::size_t size = buffer_.size(); + buffer_.resize(size + n); + return internal::make_ptr(&buffer_[size], n); + } + + // Writes an unsigned decimal integer. + template <typename UInt> + Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) { + unsigned num_digits = internal::count_digits(value); + Char *ptr = get(grow_buffer(prefix_size + num_digits)); + internal::format_decimal(ptr + prefix_size, value, num_digits); + return ptr; + } + + // Writes a decimal integer. + template <typename Int> + void write_decimal(Int value) { + typedef typename internal::IntTraits<Int>::MainType MainType; + MainType abs_value = static_cast<MainType>(value); + if (internal::is_negative(value)) { + abs_value = 0 - abs_value; + *write_unsigned_decimal(abs_value, 1) = '-'; + } else { + write_unsigned_decimal(abs_value, 0); + } + } + + // Prepare a buffer for integer formatting. + CharPtr prepare_int_buffer(unsigned num_digits, + const EmptySpec &, const char *prefix, unsigned prefix_size) { + unsigned size = prefix_size + num_digits; + CharPtr p = grow_buffer(size); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + + template <typename Spec> + CharPtr prepare_int_buffer(unsigned num_digits, + const Spec &spec, const char *prefix, unsigned prefix_size); + + // Formats an integer. + template <typename T, typename Spec> + void write_int(T value, Spec spec); + + // Formats a floating-point number (double or long double). + template <typename T, typename Spec> + void write_double(T value, const Spec &spec); + + // Writes a formatted string. + template <typename StrChar> + CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec); + + template <typename StrChar, typename Spec> + void write_str(const internal::Arg::StringValue<StrChar> &str, + const Spec &spec); + + // This following methods are private to disallow writing wide characters + // and strings to a char stream. If you want to print a wide string as a + // pointer as std::ostream does, cast it to const void*. + // Do not implement! + void operator<<(typename internal::WCharHelper<wchar_t, Char>::Unsupported); + void operator<<( + typename internal::WCharHelper<const wchar_t *, Char>::Unsupported); + + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(Char *&format_ptr, long double) { + *format_ptr++ = 'L'; + } + + template<typename T> + void append_float_length(Char *&, T) {} + + template <typename Impl, typename Char_, typename Spec_> + friend class internal::ArgFormatterBase; + + template <typename Impl, typename Char_, typename Spec_> + friend class BasicPrintfArgFormatter; + + protected: + /** + Constructs a ``BasicWriter`` object. + */ + explicit BasicWriter(Buffer<Char> &b) : buffer_(b) {} + + public: + /** + \rst + Destroys a ``BasicWriter`` object. + \endrst + */ + virtual ~BasicWriter() {} + + /** + Returns the total number of characters written. + */ + std::size_t size() const { return buffer_.size(); } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const Char *data() const FMT_NOEXCEPT { return &buffer_[0]; } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const Char *c_str() const { + std::size_t size = buffer_.size(); + buffer_.reserve(size + 1); + buffer_[size] = '\0'; + return &buffer_[0]; + } + + /** + \rst + Returns the content of the output buffer as an `std::string`. + \endrst + */ + std::basic_string<Char> str() const { + return std::basic_string<Char>(&buffer_[0], buffer_.size()); + } + + /** + \rst + Writes formatted data. + + *args* is an argument list representing arbitrary arguments. + + **Example**:: + + MemoryWriter out; + out.write("Current point:\n"); + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + Current point: + (-3.140000, +3.140000) + + The output can be accessed using :func:`data()`, :func:`c_str` or + :func:`str` methods. + + See also :ref:`syntax`. + \endrst + */ + void write(BasicCStringRef<Char> format, ArgList args) { + BasicFormatter<Char>(args, *this).format(format); + } + FMT_VARIADIC_VOID(write, BasicCStringRef<Char>) + + BasicWriter &operator<<(int value) { + write_decimal(value); + return *this; + } + BasicWriter &operator<<(unsigned value) { + return *this << IntFormatSpec<unsigned>(value); + } + BasicWriter &operator<<(long value) { + write_decimal(value); + return *this; + } + BasicWriter &operator<<(unsigned long value) { + return *this << IntFormatSpec<unsigned long>(value); + } + BasicWriter &operator<<(LongLong value) { + write_decimal(value); + return *this; + } + + /** + \rst + Formats *value* and writes it to the stream. + \endrst + */ + BasicWriter &operator<<(ULongLong value) { + return *this << IntFormatSpec<ULongLong>(value); + } + + BasicWriter &operator<<(double value) { + write_double(value, FormatSpec()); + return *this; + } + + /** + \rst + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the stream. + \endrst + */ + BasicWriter &operator<<(long double value) { + write_double(value, FormatSpec()); + return *this; + } + + /** + Writes a character to the stream. + */ + BasicWriter &operator<<(char value) { + buffer_.push_back(value); + return *this; + } + + BasicWriter &operator<<( + typename internal::WCharHelper<wchar_t, Char>::Supported value) { + buffer_.push_back(value); + return *this; + } + + /** + \rst + Writes *value* to the stream. + \endrst + */ + BasicWriter &operator<<(fmt::BasicStringRef<Char> value) { + const Char *str = value.data(); + buffer_.append(str, str + value.size()); + return *this; + } + + BasicWriter &operator<<( + typename internal::WCharHelper<StringRef, Char>::Supported value) { + const char *str = value.data(); + buffer_.append(str, str + value.size()); + return *this; + } + + template <typename T, typename Spec, typename FillChar> + BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec) { + internal::CharTraits<Char>::convert(FillChar()); + write_int(spec.value(), spec); + return *this; + } + + template <typename StrChar> + BasicWriter &operator<<(const StrFormatSpec<StrChar> &spec) { + const StrChar *s = spec.str(); + write_str(s, std::char_traits<Char>::length(s), spec); + return *this; + } + + void clear() FMT_NOEXCEPT { buffer_.clear(); } + + Buffer<Char> &buffer() FMT_NOEXCEPT { return buffer_; } +}; + +template <typename Char> +template <typename StrChar> +typename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str( + const StrChar *s, std::size_t size, const AlignSpec &spec) { + CharPtr out = CharPtr(); + if (spec.width() > size) { + out = grow_buffer(spec.width()); + Char fill = internal::CharTraits<Char>::cast(spec.fill()); + if (spec.align() == ALIGN_RIGHT) { + std::uninitialized_fill_n(out, spec.width() - size, fill); + out += spec.width() - size; + } else if (spec.align() == ALIGN_CENTER) { + out = fill_padding(out, spec.width(), size, fill); + } else { + std::uninitialized_fill_n(out + size, spec.width() - size, fill); + } + } else { + out = grow_buffer(size); + } + std::uninitialized_copy(s, s + size, out); + return out; +} + +template <typename Char> +template <typename StrChar, typename Spec> +void BasicWriter<Char>::write_str( + const internal::Arg::StringValue<StrChar> &s, const Spec &spec) { + // Check if StrChar is convertible to Char. + internal::CharTraits<Char>::convert(StrChar()); + if (spec.type_ && spec.type_ != 's') + internal::report_unknown_type(spec.type_, "string"); + const StrChar *str_value = s.value; + std::size_t str_size = s.size; + if (str_size == 0) { + if (!str_value) { + FMT_THROW(FormatError("string pointer is null")); + } + } + std::size_t precision = static_cast<std::size_t>(spec.precision_); + if (spec.precision_ >= 0 && precision < str_size) + str_size = precision; + write_str(str_value, str_size, spec); +} + +template <typename Char> +typename BasicWriter<Char>::CharPtr + BasicWriter<Char>::fill_padding( + CharPtr buffer, unsigned total_size, + std::size_t content_size, wchar_t fill) { + std::size_t padding = total_size - content_size; + std::size_t left_padding = padding / 2; + Char fill_char = internal::CharTraits<Char>::cast(fill); + std::uninitialized_fill_n(buffer, left_padding, fill_char); + buffer += left_padding; + CharPtr content = buffer; + std::uninitialized_fill_n(buffer + content_size, + padding - left_padding, fill_char); + return content; +} + +template <typename Char> +template <typename Spec> +typename BasicWriter<Char>::CharPtr + BasicWriter<Char>::prepare_int_buffer( + unsigned num_digits, const Spec &spec, + const char *prefix, unsigned prefix_size) { + unsigned width = spec.width(); + Alignment align = spec.align(); + Char fill = internal::CharTraits<Char>::cast(spec.fill()); + if (spec.precision() > static_cast<int>(num_digits)) { + // Octal prefix '0' is counted as a digit, so ignore it if precision + // is specified. + if (prefix_size > 0 && prefix[prefix_size - 1] == '0') + --prefix_size; + unsigned number_size = + prefix_size + internal::to_unsigned(spec.precision()); + AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); + if (number_size >= width) + return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); + buffer_.reserve(width); + unsigned fill_size = width - number_size; + if (align != ALIGN_LEFT) { + CharPtr p = grow_buffer(fill_size); + std::uninitialized_fill(p, p + fill_size, fill); + } + CharPtr result = prepare_int_buffer( + num_digits, subspec, prefix, prefix_size); + if (align == ALIGN_LEFT) { + CharPtr p = grow_buffer(fill_size); + std::uninitialized_fill(p, p + fill_size, fill); + } + return result; + } + unsigned size = prefix_size + num_digits; + if (width <= size) { + CharPtr p = grow_buffer(size); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + return p + size - 1; + } + CharPtr p = grow_buffer(width); + CharPtr end = p + width; + if (align == ALIGN_LEFT) { + std::uninitialized_copy(prefix, prefix + prefix_size, p); + p += size; + std::uninitialized_fill(p, end, fill); + } else if (align == ALIGN_CENTER) { + p = fill_padding(p, width, size, fill); + std::uninitialized_copy(prefix, prefix + prefix_size, p); + p += size; + } else { + if (align == ALIGN_NUMERIC) { + if (prefix_size != 0) { + p = std::uninitialized_copy(prefix, prefix + prefix_size, p); + size -= prefix_size; + } + } else { + std::uninitialized_copy(prefix, prefix + prefix_size, end - size); + } + std::uninitialized_fill(p, end - size, fill); + p = end; + } + return p - 1; +} + +template <typename Char> +template <typename T, typename Spec> +void BasicWriter<Char>::write_int(T value, Spec spec) { + unsigned prefix_size = 0; + typedef typename internal::IntTraits<T>::MainType UnsignedType; + UnsignedType abs_value = static_cast<UnsignedType>(value); + char prefix[4] = ""; + if (internal::is_negative(value)) { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } else if (spec.flag(SIGN_FLAG)) { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + switch (spec.type()) { + case 0: case 'd': { + unsigned num_digits = internal::count_digits(abs_value); + CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0); + break; + } + case 'x': case 'X': { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type_prefix(); + } + unsigned num_digits = 0; + do { + ++num_digits; + } while ((n >>= 4) != 0); + Char *p = get(prepare_int_buffer( + num_digits, spec, prefix, prefix_size)); + n = abs_value; + const char *digits = spec.type() == 'x' ? + "0123456789abcdef" : "0123456789ABCDEF"; + do { + *p-- = digits[n & 0xf]; + } while ((n >>= 4) != 0); + break; + } + case 'b': case 'B': { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = spec.type_prefix(); + } + unsigned num_digits = 0; + do { + ++num_digits; + } while ((n >>= 1) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do { + *p-- = static_cast<Char>('0' + (n & 1)); + } while ((n >>= 1) != 0); + break; + } + case 'o': { + UnsignedType n = abs_value; + if (spec.flag(HASH_FLAG)) + prefix[prefix_size++] = '0'; + unsigned num_digits = 0; + do { + ++num_digits; + } while ((n >>= 3) != 0); + Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); + n = abs_value; + do { + *p-- = static_cast<Char>('0' + (n & 7)); + } while ((n >>= 3) != 0); + break; + } + case 'n': { + unsigned num_digits = internal::count_digits(abs_value); + fmt::StringRef sep = ""; +#if !(defined(ANDROID) || defined(__ANDROID__)) + sep = internal::thousands_sep(std::localeconv()); +#endif + unsigned size = static_cast<unsigned>( + num_digits + sep.size() * ((num_digits - 1) / 3)); + CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1; + internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep)); + break; + } + default: + internal::report_unknown_type( + spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); + break; + } +} + +template <typename Char> +template <typename T, typename Spec> +void BasicWriter<Char>::write_double(T value, const Spec &spec) { + // Check type. + char type = spec.type(); + bool upper = false; + switch (type) { + case 0: + type = 'g'; + break; + case 'e': case 'f': case 'g': case 'a': + break; + case 'F': +#if FMT_MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + // Fall through. + case 'E': case 'G': case 'A': + upper = true; + break; + default: + internal::report_unknown_type(type, "double"); + break; + } + + char sign = 0; + // Use isnegative instead of value < 0 because the latter is always + // false for NaN. + if (internal::FPUtil::isnegative(static_cast<double>(value))) { + sign = '-'; + value = -value; + } else if (spec.flag(SIGN_FLAG)) { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + if (internal::FPUtil::isnotanumber(value)) { + // Format NaN ourselves because sprintf's output is not consistent + // across platforms. + std::size_t nan_size = 4; + const char *nan = upper ? " NAN" : " nan"; + if (!sign) { + --nan_size; + ++nan; + } + CharPtr out = write_str(nan, nan_size, spec); + if (sign) + *out = sign; + return; + } + + if (internal::FPUtil::isinfinity(value)) { + // Format infinity ourselves because sprintf's output is not consistent + // across platforms. + std::size_t inf_size = 4; + const char *inf = upper ? " INF" : " inf"; + if (!sign) { + --inf_size; + ++inf; + } + CharPtr out = write_str(inf, inf_size, spec); + if (sign) + *out = sign; + return; + } + + std::size_t offset = buffer_.size(); + unsigned width = spec.width(); + if (sign) { + buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u)); + if (width > 0) + --width; + ++offset; + } + + // Build format string. + enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg + Char format[MAX_FORMAT_SIZE]; + Char *format_ptr = format; + *format_ptr++ = '%'; + unsigned width_for_sprintf = width; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.align() == ALIGN_CENTER) { + width_for_sprintf = 0; + } else { + if (spec.align() == ALIGN_LEFT) + *format_ptr++ = '-'; + if (width != 0) + *format_ptr++ = '*'; + } + if (spec.precision() >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + + append_float_length(format_ptr, value); + *format_ptr++ = type; + *format_ptr = '\0'; + + // Format using snprintf. + Char fill = internal::CharTraits<Char>::cast(spec.fill()); + unsigned n = 0; + Char *start = FMT_NULL; + for (;;) { + std::size_t buffer_size = buffer_.capacity() - offset; +#if FMT_MSC_VER + // MSVC's vsnprintf_s doesn't work with zero size, so reserve + // space for at least one extra character to make the size non-zero. + // Note that the buffer's capacity will increase by more than 1. + if (buffer_size == 0) { + buffer_.reserve(offset + 1); + buffer_size = buffer_.capacity() - offset; + } +#endif + start = &buffer_[offset]; + int result = internal::CharTraits<Char>::format_float( + start, buffer_size, format, width_for_sprintf, spec.precision(), value); + if (result >= 0) { + n = internal::to_unsigned(result); + if (offset + n < buffer_.capacity()) + break; // The buffer is large enough - continue with formatting. + buffer_.reserve(offset + n + 1); + } else { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer_.reserve(buffer_.capacity() + 1); + } + } + if (sign) { + if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || + *start != ' ') { + *(start - 1) = sign; + sign = 0; + } else { + *(start - 1) = fill; + } + ++n; + } + if (spec.align() == ALIGN_CENTER && spec.width() > n) { + width = spec.width(); + CharPtr p = grow_buffer(width); + std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char)); + fill_padding(p, spec.width(), n, fill); + return; + } + if (spec.fill() != ' ' || sign) { + while (*start == ' ') + *start++ = fill; + if (sign) + *(start - 1) = sign; + } + grow_buffer(n); +} + +/** + \rst + This class template provides operations for formatting and writing data + into a character stream. The output is stored in a memory buffer that grows + dynamically. + + You can use one of the following typedefs for common character types + and the standard allocator: + + +---------------+-----------------------------------------------------+ + | Type | Definition | + +===============+=====================================================+ + | MemoryWriter | BasicMemoryWriter<char, std::allocator<char>> | + +---------------+-----------------------------------------------------+ + | WMemoryWriter | BasicMemoryWriter<wchar_t, std::allocator<wchar_t>> | + +---------------+-----------------------------------------------------+ + + **Example**:: + + MemoryWriter out; + out << "The answer is " << 42 << "\n"; + out.write("({:+f}, {:+f})", -3.14, 3.14); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42 + (-3.140000, +3.140000) + + The output can be converted to an ``std::string`` with ``out.str()`` or + accessed as a C string with ``out.c_str()``. + \endrst + */ +template <typename Char, typename Allocator = std::allocator<Char> > +class BasicMemoryWriter : public BasicWriter<Char> { + private: + internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_; + + public: + explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) + : BasicWriter<Char>(buffer_), buffer_(alloc) {} + +#if FMT_USE_RVALUE_REFERENCES + /** + \rst + Constructs a :class:`fmt::BasicMemoryWriter` object moving the content + of the other object to it. + \endrst + */ + BasicMemoryWriter(BasicMemoryWriter &&other) + : BasicWriter<Char>(buffer_), buffer_(std::move(other.buffer_)) { + } + + /** + \rst + Moves the content of the other ``BasicMemoryWriter`` object to this one. + \endrst + */ + BasicMemoryWriter &operator=(BasicMemoryWriter &&other) { + buffer_ = std::move(other.buffer_); + return *this; + } +#endif +}; + +typedef BasicMemoryWriter<char> MemoryWriter; +typedef BasicMemoryWriter<wchar_t> WMemoryWriter; + +/** + \rst + This class template provides operations for formatting and writing data + into a fixed-size array. For writing into a dynamically growing buffer + use :class:`fmt::BasicMemoryWriter`. + + Any write method will throw ``std::runtime_error`` if the output doesn't fit + into the array. + + You can use one of the following typedefs for common character types: + + +--------------+---------------------------+ + | Type | Definition | + +==============+===========================+ + | ArrayWriter | BasicArrayWriter<char> | + +--------------+---------------------------+ + | WArrayWriter | BasicArrayWriter<wchar_t> | + +--------------+---------------------------+ + \endrst + */ +template <typename Char> +class BasicArrayWriter : public BasicWriter<Char> { + private: + internal::FixedBuffer<Char> buffer_; + + public: + /** + \rst + Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the + given size. + \endrst + */ + BasicArrayWriter(Char *array, std::size_t size) + : BasicWriter<Char>(buffer_), buffer_(array, size) {} + + /** + \rst + Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the + size known at compile time. + \endrst + */ + template <std::size_t SIZE> + explicit BasicArrayWriter(Char (&array)[SIZE]) + : BasicWriter<Char>(buffer_), buffer_(array, SIZE) {} +}; + +typedef BasicArrayWriter<char> ArrayWriter; +typedef BasicArrayWriter<wchar_t> WArrayWriter; + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, + StringRef message) FMT_NOEXCEPT; + +#if FMT_USE_WINDOWS_H + +/** A Windows error. */ +class WindowsError : public SystemError { + private: + FMT_API void init(int error_code, CStringRef format_str, ArgList args); + + public: + /** + \rst + Constructs a :class:`fmt::WindowsError` object with the description + of the form + + .. parsed-literal:: + *<message>*: *<system-message>* + + where *<message>* is the formatted message and *<system-message>* is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a WindowsError with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::WindowsError(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + WindowsError(int error_code, CStringRef message) { + init(error_code, message, ArgList()); + } + FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef) +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, + StringRef message) FMT_NOEXCEPT; + +#endif + +enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; + +/** + Formats a string and prints it to stdout using ANSI escape sequences + to specify color (experimental). + Example: + print_colored(fmt::RED, "Elapsed time: {0:.2f} seconds", 1.23); + */ +FMT_API void print_colored(Color c, CStringRef format, ArgList args); + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = format("The answer is {}", 42); + \endrst +*/ +inline std::string format(CStringRef format_str, ArgList args) { + MemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +inline std::wstring format(WCStringRef format_str, ArgList args) { + WMemoryWriter w; + w.write(format_str, args); + return w.str(); +} + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + print(stderr, "Don't {}!", "panic"); + \endrst + */ +FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +FMT_API void print(CStringRef format_str, ArgList args); + +/** + Fast integer formatter. + */ +class FormatInt { + private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum {BUFFER_SIZE = std::numeric_limits<ULongLong>::digits10 + 3}; + mutable char buffer_[BUFFER_SIZE]; + char *str_; + + // Formats value in reverse and returns the number of digits. + char *format_decimal(ULongLong value) { + char *buffer_end = buffer_ + BUFFER_SIZE - 1; + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast<unsigned>((value % 100) * 2); + value /= 100; + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + } + if (value < 10) { + *--buffer_end = static_cast<char>('0' + value); + return buffer_end; + } + unsigned index = static_cast<unsigned>(value * 2); + *--buffer_end = internal::Data::DIGITS[index + 1]; + *--buffer_end = internal::Data::DIGITS[index]; + return buffer_end; + } + + void FormatSigned(LongLong value) { + ULongLong abs_value = static_cast<ULongLong>(value); + bool negative = value < 0; + if (negative) + abs_value = 0 - abs_value; + str_ = format_decimal(abs_value); + if (negative) + *--str_ = '-'; + } + + public: + explicit FormatInt(int value) { FormatSigned(value); } + explicit FormatInt(long value) { FormatSigned(value); } + explicit FormatInt(LongLong value) { FormatSigned(value); } + explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} + explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} + explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {} + + /** Returns the number of characters written to the output buffer. */ + std::size_t size() const { + return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char *data() const { return str_; } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char *c_str() const { + buffer_[BUFFER_SIZE - 1] = '\0'; + return str_; + } + + /** + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ + std::string str() const { return std::string(str_, size()); } +}; + +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template <typename T> +inline void format_decimal(char *&buffer, T value) { + typedef typename internal::IntTraits<T>::MainType MainType; + MainType abs_value = static_cast<MainType>(value); + if (internal::is_negative(value)) { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + if (abs_value < 100) { + if (abs_value < 10) { + *buffer++ = static_cast<char>('0' + abs_value); + return; + } + unsigned index = static_cast<unsigned>(abs_value * 2); + *buffer++ = internal::Data::DIGITS[index]; + *buffer++ = internal::Data::DIGITS[index + 1]; + return; + } + unsigned num_digits = internal::count_digits(abs_value); + internal::format_decimal(buffer, abs_value, num_digits); + buffer += num_digits; +} + +/** + \rst + Returns a named argument for formatting functions. + + **Example**:: + + print("Elapsed time: {s:.2f} seconds", arg("s", 1.23)); + + \endrst + */ +template <typename T> +inline internal::NamedArgWithType<char, T> arg(StringRef name, const T &arg) { + return internal::NamedArgWithType<char, T>(name, arg); +} + +template <typename T> +inline internal::NamedArgWithType<wchar_t, T> arg(WStringRef name, const T &arg) { + return internal::NamedArgWithType<wchar_t, T>(name, arg); +} + +// The following two functions are deleted intentionally to disable +// nested named arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template <typename Char> +void arg(StringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED; +template <typename Char> +void arg(WStringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED; +} + +#if FMT_GCC_VERSION +// Use the system_header pragma to suppress warnings about variadic macros +// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't +// work. It is used at the end because we want to suppress as little warnings +// as possible. +# pragma GCC system_header +#endif + +// This is used to work around VC++ bugs in handling variadic macros. +#define FMT_EXPAND(args) args + +// Returns the number of arguments. +// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. +#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) +#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) +#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N +#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 + +#define FMT_FOR_EACH_(N, f, ...) \ + FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) +#define FMT_FOR_EACH(f, ...) \ + FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) + +#define FMT_ADD_ARG_NAME(type, index) type arg##index +#define FMT_GET_ARG_NAME(type, index) arg##index + +#if FMT_USE_VARIADIC_TEMPLATES +# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \ + template <typename... Args> \ + ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + const Args & ... args) Const { \ + typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \ + typename ArgArray::Type array{ \ + ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ + fmt::ArgList(fmt::internal::make_type(args...), array)); \ + } +#else +// Defines a wrapper for a function taking __VA_ARGS__ arguments +// and n additional arguments of arbitrary types. +# define FMT_WRAP(Const, Char, ReturnType, func, call, n, ...) \ + template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ + FMT_GEN(n, FMT_MAKE_ARG)) Const { \ + fmt::internal::ArgArray<n>::Type arr; \ + FMT_GEN(n, FMT_ASSIGN_##Char); \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ + fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \ + } + +# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \ + inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) Const { \ + call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ + } \ + FMT_WRAP(Const, Char, ReturnType, func, call, 1, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 2, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 3, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 4, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 5, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 6, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 7, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 8, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 9, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 10, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 11, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 12, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 13, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 14, __VA_ARGS__) \ + FMT_WRAP(Const, Char, ReturnType, func, call, 15, __VA_ARGS__) +#endif // FMT_USE_VARIADIC_TEMPLATES + +/** + \rst + Defines a variadic function with the specified return type, function name + and argument types passed as variable arguments to this macro. + + **Example**:: + + void print_error(const char *file, int line, const char *format, + fmt::ArgList args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args); + } + FMT_VARIADIC(void, print_error, const char *, int, const char *) + + ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that + don't implement variadic templates. You don't have to use this macro if + you don't need legacy compiler support and can use variadic templates + directly:: + + template <typename... Args> + void print_error(const char *file, int line, const char *format, + const Args & ... args) { + fmt::print("{}: {}: ", file, line); + fmt::print(format, args...); + } + \endrst + */ +#define FMT_VARIADIC(ReturnType, func, ...) \ + FMT_VARIADIC_(, char, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_CONST(ReturnType, func, ...) \ + FMT_VARIADIC_(const, char, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_W(ReturnType, func, ...) \ + FMT_VARIADIC_(, wchar_t, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_VARIADIC_CONST_W(ReturnType, func, ...) \ + FMT_VARIADIC_(const, wchar_t, ReturnType, func, return func, __VA_ARGS__) + +#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) + +#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id) + +/** + \rst + Convenient macro to capture the arguments' names and values into several + ``fmt::arg(name, value)``. + + **Example**:: + + int x = 1, y = 2; + print("point: ({x}, {y})", FMT_CAPTURE(x, y)); + // same as: + // print("point: ({x}, {y})", arg("x", x), arg("y", y)); + + \endrst + */ +#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__) + +#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__) + +namespace fmt { +FMT_VARIADIC(std::string, format, CStringRef) +FMT_VARIADIC_W(std::wstring, format, WCStringRef) +FMT_VARIADIC(void, print, CStringRef) +FMT_VARIADIC(void, print, std::FILE *, CStringRef) +FMT_VARIADIC(void, print_colored, Color, CStringRef) + +namespace internal { +template <typename Char> +inline bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +// Parses an unsigned integer advancing s to the end of the parsed input. +// This function assumes that the first character of s is a digit. +template <typename Char> +unsigned parse_nonnegative_int(const Char *&s) { + assert('0' <= *s && *s <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits<int>::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + (*s - '0'); + ++s; + } while ('0' <= *s && *s <= '9'); + // Convert to unsigned to prevent a warning. + if (value > max_int) + FMT_THROW(FormatError("number is too big")); + return value; +} + +inline void require_numeric_argument(const Arg &arg, char spec) { + if (arg.type > Arg::LAST_NUMERIC_TYPE) { + std::string message = + fmt::format("format specifier '{}' requires numeric argument", spec); + FMT_THROW(fmt::FormatError(message)); + } +} + +template <typename Char> +void check_sign(const Char *&s, const Arg &arg) { + char sign = static_cast<char>(*s); + require_numeric_argument(arg, sign); + if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) { + FMT_THROW(FormatError(fmt::format( + "format specifier '{}' requires signed argument", sign))); + } + ++s; +} +} // namespace internal + +template <typename Char, typename AF> +inline internal::Arg BasicFormatter<Char, AF>::get_arg( + BasicStringRef<Char> arg_name, const char *&error) { + if (check_no_auto_index(error)) { + map_.init(args()); + const internal::Arg *arg = map_.find(arg_name); + if (arg) + return *arg; + error = "argument not found"; + } + return internal::Arg(); +} + +template <typename Char, typename AF> +inline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) { + const char *error = FMT_NULL; + internal::Arg arg = *s < '0' || *s > '9' ? + next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error); + if (error) { + FMT_THROW(FormatError( + *s != '}' && *s != ':' ? "invalid format string" : error)); + } + return arg; +} + +template <typename Char, typename AF> +inline internal::Arg BasicFormatter<Char, AF>::parse_arg_name(const Char *&s) { + assert(internal::is_name_start(*s)); + const Char *start = s; + Char c; + do { + c = *++s; + } while (internal::is_name_start(c) || ('0' <= c && c <= '9')); + const char *error = FMT_NULL; + internal::Arg arg = get_arg(BasicStringRef<Char>(start, s - start), error); + if (error) + FMT_THROW(FormatError(error)); + return arg; +} + +template <typename Char, typename ArgFormatter> +const Char *BasicFormatter<Char, ArgFormatter>::format( + const Char *&format_str, const internal::Arg &arg) { + using internal::Arg; + const Char *s = format_str; + typename ArgFormatter::SpecType spec; + if (*s == ':') { + if (arg.type == Arg::CUSTOM) { + arg.custom.format(this, arg.custom.value, &s); + return s; + } + ++s; + // Parse fill and alignment. + if (Char c = *s) { + const Char *p = s + 1; + spec.align_ = ALIGN_DEFAULT; + do { + switch (*p) { + case '<': + spec.align_ = ALIGN_LEFT; + break; + case '>': + spec.align_ = ALIGN_RIGHT; + break; + case '=': + spec.align_ = ALIGN_NUMERIC; + break; + case '^': + spec.align_ = ALIGN_CENTER; + break; + } + if (spec.align_ != ALIGN_DEFAULT) { + if (p != s) { + if (c == '}') break; + if (c == '{') + FMT_THROW(FormatError("invalid fill character '{'")); + s += 2; + spec.fill_ = c; + } else ++s; + if (spec.align_ == ALIGN_NUMERIC) + require_numeric_argument(arg, '='); + break; + } + } while (--p >= s); + } + + // Parse sign. + switch (*s) { + case '+': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '-': + check_sign(s, arg); + spec.flags_ |= MINUS_FLAG; + break; + case ' ': + check_sign(s, arg); + spec.flags_ |= SIGN_FLAG; + break; + } + + if (*s == '#') { + require_numeric_argument(arg, '#'); + spec.flags_ |= HASH_FLAG; + ++s; + } + + // Parse zero flag. + if (*s == '0') { + require_numeric_argument(arg, '0'); + spec.align_ = ALIGN_NUMERIC; + spec.fill_ = '0'; + ++s; + } + + // Parse width. + if ('0' <= *s && *s <= '9') { + spec.width_ = internal::parse_nonnegative_int(s); + } else if (*s == '{') { + ++s; + Arg width_arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (width_arg.type) { + case Arg::INT: + if (width_arg.int_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.int_value; + break; + case Arg::UINT: + value = width_arg.uint_value; + break; + case Arg::LONG_LONG: + if (width_arg.long_long_value < 0) + FMT_THROW(FormatError("negative width")); + value = width_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = width_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("width is not integer")); + } + unsigned max_int = (std::numeric_limits<int>::max)(); + if (value > max_int) + FMT_THROW(FormatError("number is too big")); + spec.width_ = static_cast<int>(value); + } + + // Parse precision. + if (*s == '.') { + ++s; + spec.precision_ = 0; + if ('0' <= *s && *s <= '9') { + spec.precision_ = internal::parse_nonnegative_int(s); + } else if (*s == '{') { + ++s; + Arg precision_arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + if (*s++ != '}') + FMT_THROW(FormatError("invalid format string")); + ULongLong value = 0; + switch (precision_arg.type) { + case Arg::INT: + if (precision_arg.int_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.int_value; + break; + case Arg::UINT: + value = precision_arg.uint_value; + break; + case Arg::LONG_LONG: + if (precision_arg.long_long_value < 0) + FMT_THROW(FormatError("negative precision")); + value = precision_arg.long_long_value; + break; + case Arg::ULONG_LONG: + value = precision_arg.ulong_long_value; + break; + default: + FMT_THROW(FormatError("precision is not integer")); + } + unsigned max_int = (std::numeric_limits<int>::max)(); + if (value > max_int) + FMT_THROW(FormatError("number is too big")); + spec.precision_ = static_cast<int>(value); + } else { + FMT_THROW(FormatError("missing precision specifier")); + } + if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) { + FMT_THROW(FormatError( + fmt::format("precision not allowed in {} format specifier", + arg.type == Arg::POINTER ? "pointer" : "integer"))); + } + } + + // Parse type. + if (*s != '}' && *s) + spec.type_ = static_cast<char>(*s++); + } + + if (*s++ != '}') + FMT_THROW(FormatError("missing '}' in format string")); + + // Format argument. + ArgFormatter(*this, spec, s - 1).visit(arg); + return s; +} + +template <typename Char, typename AF> +void BasicFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) { + const Char *s = format_str.c_str(); + const Char *start = s; + while (*s) { + Char c = *s++; + if (c != '{' && c != '}') continue; + if (*s == c) { + write(writer_, start, s); + start = ++s; + continue; + } + if (c == '}') + FMT_THROW(FormatError("unmatched '}' in format string")); + write(writer_, start, s - 1); + internal::Arg arg = internal::is_name_start(*s) ? + parse_arg_name(s) : parse_arg_index(s); + start = s = format(s, arg); + } + write(writer_, start, s); +} + +template <typename Char, typename It> +struct ArgJoin { + It first; + It last; + BasicCStringRef<Char> sep; + + ArgJoin(It first, It last, const BasicCStringRef<Char>& sep) : + first(first), + last(last), + sep(sep) {} +}; + +template <typename It> +ArgJoin<char, It> join(It first, It last, const BasicCStringRef<char>& sep) { + return ArgJoin<char, It>(first, last, sep); +} + +template <typename It> +ArgJoin<wchar_t, It> join(It first, It last, const BasicCStringRef<wchar_t>& sep) { + return ArgJoin<wchar_t, It>(first, last, sep); +} + +#if FMT_HAS_GXX_CXX11 +template <typename Range> +auto join(const Range& range, const BasicCStringRef<char>& sep) + -> ArgJoin<char, decltype(std::begin(range))> { + return join(std::begin(range), std::end(range), sep); +} + +template <typename Range> +auto join(const Range& range, const BasicCStringRef<wchar_t>& sep) + -> ArgJoin<wchar_t, decltype(std::begin(range))> { + return join(std::begin(range), std::end(range), sep); +} +#endif + +template <typename ArgFormatter, typename Char, typename It> +void format_arg(fmt::BasicFormatter<Char, ArgFormatter> &f, + const Char *&format_str, const ArgJoin<Char, It>& e) { + const Char* end = format_str; + if (*end == ':') + ++end; + while (*end && *end != '}') + ++end; + if (*end != '}') + FMT_THROW(FormatError("missing '}' in format string")); + + It it = e.first; + if (it != e.last) { + const Char* save = format_str; + f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++)); + while (it != e.last) { + f.writer().write(e.sep); + format_str = save; + f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++)); + } + } + format_str = end + 1; +} +} // namespace fmt + +#if FMT_USE_USER_DEFINED_LITERALS +namespace fmt { +namespace internal { + +template <typename Char> +struct UdlFormat { + const Char *str; + + template <typename... Args> + auto operator()(Args && ... args) const + -> decltype(format(str, std::forward<Args>(args)...)) { + return format(str, std::forward<Args>(args)...); + } +}; + +template <typename Char> +struct UdlArg { + const Char *str; + + template <typename T> + NamedArgWithType<Char, T> operator=(T &&value) const { + return {str, std::forward<T>(value)}; + } +}; + +} // namespace internal + +inline namespace literals { + +/** + \rst + C++11 literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::UdlFormat<char> +operator"" _format(const char *s, std::size_t) { return {s}; } +inline internal::UdlFormat<wchar_t> +operator"" _format(const wchar_t *s, std::size_t) { return {s}; } + +/** + \rst + C++11 literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::UdlArg<char> +operator"" _a(const char *s, std::size_t) { return {s}; } +inline internal::UdlArg<wchar_t> +operator"" _a(const wchar_t *s, std::size_t) { return {s}; } + +} // inline namespace literals +} // namespace fmt +#endif // FMT_USE_USER_DEFINED_LITERALS + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 +# pragma GCC diagnostic pop +#endif + +#if defined(__clang__) && !defined(FMT_ICC_VERSION) +# pragma clang diagnostic pop +#endif + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format.cc" +#else +# define FMT_FUNC +#endif + +#endif // FMT_FORMAT_H_ diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index d031353f35..b0c3bc23a7 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -183,7 +183,13 @@ def ast_classes_declaration(self): if child.getter_method: getter_method = child.getter_method getter_override = " override" if child.getter_override else "" - return_type = "std::shared_ptr<" + class_name + "> " + if child.is_vector: + return_type = class_name + "Vector& " + else: + if child.is_ptr_excluded_node() or child.is_base_type_node(): + return_type = class_name + " " + else: + return_type = "std::shared_ptr<" + class_name + "> " self.write_line(return_type + getter_method + "()" + getter_override + " { return " + varname + "; }") if node.is_prime_node() and child.varname == ORDER_VAR_NAME: @@ -221,10 +227,15 @@ def ast_classes_declaration(self): self.write_line("void negate() override { value = !value; }") else: self.write_line("void negate() override { value = -value; }") + self.write_line("double number_value() override { return value; }") + if node.is_identifier_node(): self.write_line(virtual + "void set_name(std::string /*name*/)" + override + " { std::cout << \"ERROR : set_name() not implemented! \"; abort(); }") + if node.is_number_node(): + self.write_line(virtual + "double number_value()" + override + " { std::cout << \"ERROR : number_value() not implemented! \"; abort(); }") + if node.is_name_node(): self.write_line(virtual + "void set_name(std::string name)" + override + " { value->set(name); }") diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 179ed857f7..1356674e57 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -605,9 +605,11 @@ members: - value: type: int + getter: {name: get_value} - macroname: type: Name optional: true + getter: {name: get_macro_name} - Float: members: - value: @@ -626,6 +628,7 @@ - value: type: String getname: true + getter: {name: get_value} - PrimeName: members: - value: @@ -652,10 +655,11 @@ - name: type: Identifier getname: true - - index: + - length: type: Expression prefix: {value: "["} suffix: {value: "]"} + getter: {name: get_length} - Argument: members: - name: @@ -786,6 +790,7 @@ - statements: type: Statement vector: true + getter: {name: get_statements} - DerivativeBlock: nmodl: "DERIVATIVE " members: @@ -874,6 +879,7 @@ prefix: {value: "(", force: true} suffix: {value: ")", force: true} separator: ", " + getter: {name: get_arguments} - unit: type: Unit optional: true @@ -894,6 +900,7 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " + getter: {name: get_arguments} - unit: type: Unit optional: true @@ -909,6 +916,7 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " + getter: {name: get_arguments} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -918,10 +926,12 @@ - name: type: Name prefix: {value: " "} + getter: {name: get_solve_block} - method: type: Name optional: true prefix: {value: " METHOD "} + getter: {name: get_method} - ifsolerr: type: StatementBlock prefix: {value: " IFERROR "} @@ -1054,6 +1064,18 @@ optional: true prefix: {value: "["} suffix: {value: "]"} + - ConstantVar: + members: + - name: + type: Name + getname: true + - value: + type: Number + prefix: {value: " = "} + - unit: + type: Unit + optional: true + prefix: {value: " "} - BinaryOperator: members: - value: @@ -1080,10 +1102,13 @@ members: - lhs: type: Expression + getter: {name: get_lhs} - op: type: BinaryOperator + getter: {name: get_op} - rhs: type: Expression + getter: {name: get_rhs} - UnaryExpression: members: - op: @@ -1110,12 +1135,14 @@ members: - name: type: Name + getter: {name: get_function_name} - arguments: type: Expression vector: true separator: ", " prefix: {value: "(", force: true} suffix: {value: ")", force: true} + getter: {name: get_arguments} - FirstLastTypeIndex: members: - value: @@ -1272,11 +1299,12 @@ - name: type: Identifier getname: true - - index: + - length: type: Integer optional: true prefix: {value: "["} suffix: {value: "]"} + getter: {name: get_length} - from: type: Number prefix: {value: " FROM "} @@ -1313,14 +1341,17 @@ members: - conductance: type: Name + getter: {name: get_variable} - ion: type: Name optional: true prefix: {value: " USEION "} + getter: {name: get_ion} - ExpressionStatement: members: - expression: type: Expression + getter: {name: get_expression} - ProtectStatement: nmodl: "PROTECT " members: @@ -1331,19 +1362,24 @@ members: - name: type: Name + getter: {name: get_from_name} - from: type: Expression prefix: {value: " = "} + getter: {name: get_from_expression} - to: type: Expression prefix: {value: " TO "} + getter: {name: get_to_expression} - opinc: type: Expression prefix: {value: " BY "} suffix: {value: " ", force: true} optional: true + getter: {name: get_inc_expression} - statementblock: type: StatementBlock + getter: {name: get_block} - ForAllStatement: nmodl: "FORALL " members: @@ -1536,15 +1572,8 @@ prefix: {value: " "} - ConstantStatement: members: - - name: - type: Name - - value: - type: Number - prefix: {value: " = "} - - unit: - type: Unit - optional: true - prefix: {value: " "} + - constant: + type: ConstantVar - TableStatement: nmodl: "TABLE " members: @@ -1571,13 +1600,16 @@ - type: type: Name suffix: {value: " "} + getter: {name: get_suffix_type} - name: type: Name + getter: {name: get_suffix_name} - Useion: nmodl: "USEION " members: - name: type: Name + getname: true - readlist: type: ReadIonVar vector: true @@ -1658,7 +1690,7 @@ - Verbatim: nmodl: VERBATIM members: - - statement: + - statements: type: String getter: {name: get_statement} suffix: {value: "ENDVERBATIM"} @@ -1679,3 +1711,4 @@ type: Node vector: true add: true + getter: {name: get_blocks} diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 1818ff4b35..af09a076c4 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -55,6 +55,7 @@ "UnitDef", "FactorDef", "RangeVar", + "Useion", "ReadIonVar", "WriteIonVar", "NonspeCurVar", @@ -64,15 +65,17 @@ "PointerVar", "BbcorePointerVar", "ExternVar", - "PrimeName"] + "PrimeName", + "ConstantVar"] # block nodes which will go into symbol table +# todo : skipping discrete block due to limits in SYMBOL_BLOCK_TYPES = ["FunctionBlock", "ProcedureBlock", "DerivativeBlock", "LinearBlock", "NonLinearBlock", - "DiscreteBlock", + #"DiscreteBlock", "PartialBlock", "KineticBlock", "FunctionTableBlock"] diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index 86782f94e8..8cf6b8057d 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -11,7 +11,7 @@ * starts at column 1 and increasing by yyleng gives extra position * larger than exact columns span of the token. Hence in ModToken * class we reduce the length by one column. */ - #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); driver.process(yytext); } + #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } /** By default yylex returns int, we use token_type. Unfortunately * yyterminate by default returns 0, which is not of token_type. */ @@ -101,450 +101,565 @@ WS [ \t\v\n\f] %% +[ \t]*"#define" { + /** macros also need to be processed / re-defined */ + driver.add_token(yytext); + } -^"#" { +[ \t]*"#" { BEGIN(P_P_DIRECTIVE); + yymore(); } <P_P_DIRECTIVE>. { + yymore(); } <P_P_DIRECTIVE>\n { + driver.add_token(yytext); BEGIN(INITIAL); } "/*" { BEGIN(COMMENT); + yymore(); } <COMMENT>"*/" { + driver.add_token(yytext); BEGIN(INITIAL); } -<COMMENT>. { - } - <COMMENT>\n { loc.lines(1); + yymore(); + } + +<COMMENT>. { + yymore(); } "//".* { - /* consume //-comment */ + /* consume single liine comment */ } "auto" { + driver.add_token(yytext); return c11::Parser::make_AUTO(yytext, loc); } "break" { + driver.add_token(yytext); return c11::Parser::make_BREAK(yytext, loc); } "case" { + driver.add_token(yytext); return c11::Parser::make_CASE(yytext, loc); } "char" { + driver.add_token(yytext); return c11::Parser::make_CHAR(yytext, loc); } "const" { + driver.add_token(yytext); return c11::Parser::make_CONST(yytext, loc); } "continue" { + driver.add_token(yytext); return c11::Parser::make_CONTINUE(yytext, loc); } "default" { + driver.add_token(yytext); return c11::Parser::make_DEFAULT(yytext, loc); } "do" { + driver.add_token(yytext); return c11::Parser::make_DO(yytext, loc); } "double" { + driver.add_token(yytext); return c11::Parser::make_DOUBLE(yytext, loc); } "else" { + driver.add_token(yytext); return c11::Parser::make_ELSE(yytext, loc); } "enum" { + driver.add_token(yytext); return c11::Parser::make_ENUM(yytext, loc); } "extern" { + driver.add_token(yytext); return c11::Parser::make_EXTERN(yytext, loc); } "float" { + driver.add_token(yytext); return c11::Parser::make_FLOAT(yytext, loc); } "for" { + driver.add_token(yytext); return c11::Parser::make_FOR(yytext, loc); } "goto" { + driver.add_token(yytext); return c11::Parser::make_GOTO(yytext, loc); } "if" { + driver.add_token(yytext); return c11::Parser::make_IF(yytext, loc); } "inline" { + driver.add_token(yytext); return c11::Parser::make_INLINE(yytext, loc); } "int" { + driver.add_token(yytext); return c11::Parser::make_INT(yytext, loc); } "long" { + driver.add_token(yytext); return c11::Parser::make_LONG(yytext, loc); } "register" { + driver.add_token(yytext); return c11::Parser::make_REGISTER(yytext, loc); } "restrict" { + driver.add_token(yytext); return c11::Parser::make_RESTRICT(yytext, loc); } "return" { + driver.add_token(yytext); return c11::Parser::make_RETURN(yytext, loc); } "short" { + driver.add_token(yytext); return c11::Parser::make_SHORT(yytext, loc); } "signed" { + driver.add_token(yytext); return c11::Parser::make_SIGNED(yytext, loc); } "sizeof" { + driver.add_token(yytext); return c11::Parser::make_SIZEOF(yytext, loc); } "static" { + driver.add_token(yytext); return c11::Parser::make_STATIC(yytext, loc); } "struct" { + driver.add_token(yytext); return c11::Parser::make_STRUCT(yytext, loc); } "switch" { + driver.add_token(yytext); return c11::Parser::make_SWITCH(yytext, loc); } "typedef" { + driver.add_token(yytext); return c11::Parser::make_TYPEDEF(yytext, loc); } "union" { + driver.add_token(yytext); return c11::Parser::make_UNION(yytext, loc); } "unsigned" { + driver.add_token(yytext); return c11::Parser::make_UNSIGNED(yytext, loc); } "void" { + driver.add_token(yytext); return c11::Parser::make_VOID(yytext, loc); } "volatile" { + driver.add_token(yytext); return c11::Parser::make_VOLATILE(yytext, loc); } "while" { + driver.add_token(yytext); return c11::Parser::make_WHILE(yytext, loc); } "_Alignas" { + driver.add_token(yytext); return c11::Parser::make_ALIGNAS(yytext, loc); } "_Alignof" { + driver.add_token(yytext); return c11::Parser::make_ALIGNOF(yytext, loc); } "_Atomic" { + driver.add_token(yytext); return c11::Parser::make_ATOMIC(yytext, loc); } "_Bool" { + driver.add_token(yytext); return c11::Parser::make_BOOL(yytext, loc); } "_Complex" { + driver.add_token(yytext); return c11::Parser::make_COMPLEX(yytext, loc); } "_Generic" { + driver.add_token(yytext); return c11::Parser::make_GENERIC(yytext, loc); } "_Imaginary" { + driver.add_token(yytext); return c11::Parser::make_IMAGINARY(yytext, loc); } "_Noreturn" { + driver.add_token(yytext); return c11::Parser::make_NORETURN(yytext, loc); } "_Static_assert" { + driver.add_token(yytext); return c11::Parser::make_STATIC_ASSERT(yytext, loc); } "_Thread_local" { + driver.add_token(yytext); return c11::Parser::make_THREAD_LOCAL(yytext, loc); } "__func__" { + driver.add_token(yytext); return c11::Parser::make_FUNC_NAME(yytext, loc); } {L}{A}* { + driver.add_token(yytext); return check_type(); } {HP}{H}+{IS}? { + driver.add_token(yytext); return c11::Parser::make_I_CONSTANT(yytext, loc); } {NZ}{D}*{IS}? { + driver.add_token(yytext); return c11::Parser::make_I_CONSTANT(yytext, loc); } "0"{O}*{IS}? { + driver.add_token(yytext); return c11::Parser::make_I_CONSTANT(yytext, loc); } {CP}?"'"([^'\\\n]|{ES})+"'" { + driver.add_token(yytext); return c11::Parser::make_I_CONSTANT(yytext, loc); } {D}+{E}{FS}? { + driver.add_token(yytext); return c11::Parser::make_F_CONSTANT(yytext, loc); } {D}*"."{D}+{E}?{FS}? { + driver.add_token(yytext); return c11::Parser::make_F_CONSTANT(yytext, loc); } {D}+"."{E}?{FS}? { + driver.add_token(yytext); return c11::Parser::make_F_CONSTANT(yytext, loc); } {HP}{H}+{P}{FS}? { + driver.add_token(yytext); return c11::Parser::make_F_CONSTANT(yytext, loc); } {HP}{H}*"."{H}+{P}{FS}? { + driver.add_token(yytext); return c11::Parser::make_F_CONSTANT(yytext, loc); } {HP}{H}+"."{P}{FS}? { + driver.add_token(yytext); return c11::Parser::make_F_CONSTANT(yytext, loc); } ({SP}?\"([^"\\\n]|{ES})*\"{WS}*)+ { + driver.add_token(yytext); return c11::Parser::make_STRING_LITERAL(yytext, loc); } "..." { + driver.add_token(yytext); return c11::Parser::make_ELLIPSIS(yytext, loc); } ">>=" { + driver.add_token(yytext); return c11::Parser::make_RIGHT_ASSIGN(yytext, loc); } "<<=" { + driver.add_token(yytext); return c11::Parser::make_LEFT_ASSIGN(yytext, loc); } "+=" { + driver.add_token(yytext); return c11::Parser::make_ADD_ASSIGN(yytext, loc); } "-=" { + driver.add_token(yytext); return c11::Parser::make_SUB_ASSIGN(yytext, loc); } "*=" { + driver.add_token(yytext); return c11::Parser::make_MUL_ASSIGN(yytext, loc); } "/=" { + driver.add_token(yytext); return c11::Parser::make_DIV_ASSIGN(yytext, loc); } "%=" { + driver.add_token(yytext); return c11::Parser::make_MOD_ASSIGN(yytext, loc); } "&=" { + driver.add_token(yytext); return c11::Parser::make_AND_ASSIGN(yytext, loc); } "^=" { + driver.add_token(yytext); return c11::Parser::make_XOR_ASSIGN(yytext, loc); } "|=" { + driver.add_token(yytext); return c11::Parser::make_OR_ASSIGN(yytext, loc); } ">>" { + driver.add_token(yytext); return c11::Parser::make_RIGHT_OP(yytext, loc); } "<<" { + driver.add_token(yytext); return c11::Parser::make_LEFT_OP(yytext, loc); } "++" { + driver.add_token(yytext); return c11::Parser::make_INC_OP(yytext, loc); } "--" { + driver.add_token(yytext); return c11::Parser::make_DEC_OP(yytext, loc); } "->" { + driver.add_token(yytext); return c11::Parser::make_PTR_OP(yytext, loc); } "&&" { + driver.add_token(yytext); return c11::Parser::make_AND_OP(yytext, loc); } "||" { + driver.add_token(yytext); return c11::Parser::make_OR_OP(yytext, loc); } "<=" { + driver.add_token(yytext); return c11::Parser::make_LE_OP(yytext, loc); } ">=" { + driver.add_token(yytext); return c11::Parser::make_GE_OP(yytext, loc); } "==" { + driver.add_token(yytext); return c11::Parser::make_EQ_OP(yytext, loc); } "!=" { + driver.add_token(yytext); return c11::Parser::make_NE_OP(yytext, loc); } ";" { + driver.add_token(yytext); return c11::Parser::make_SEMICOLON(yytext, loc); } ("{"|"<%") { + driver.add_token(yytext); return c11::Parser::make_OPEN_BRACE(yytext, loc); } ("}"|"%>") { + driver.add_token(yytext); return c11::Parser::make_CLOSE_BRACE(yytext, loc); } "," { + driver.add_token(yytext); return c11::Parser::make_COMMA(yytext, loc); } ":" { + driver.add_token(yytext); return c11::Parser::make_COLON(yytext, loc); } "=" { + driver.add_token(yytext); return c11::Parser::make_ASSIGN(yytext, loc); } "(" { + driver.add_token(yytext); return c11::Parser::make_OPEN_PARENTHESIS(yytext, loc); } ")" { + driver.add_token(yytext); return c11::Parser::make_CLOSE_PARENTHESIS(yytext, loc); } ("["|"<:") { + driver.add_token(yytext); return c11::Parser::make_OPEN_BRACKET(yytext, loc); } ("]"|":>") { + driver.add_token(yytext); return c11::Parser::make_CLOSE_BRACKET(yytext, loc); } "." { + driver.add_token(yytext); return c11::Parser::make_PERIOD(yytext, loc); } "&" { + driver.add_token(yytext); return c11::Parser::make_AMPERSTAND(yytext, loc); } "!" { + driver.add_token(yytext); return c11::Parser::make_NEGATION(yytext, loc); } "~" { + driver.add_token(yytext); return c11::Parser::make_NEGATION(yytext, loc); } "-" { + driver.add_token(yytext); return c11::Parser::make_MINUS(yytext, loc); } "+" { + driver.add_token(yytext); return c11::Parser::make_ADD(yytext, loc); } "*" { + driver.add_token(yytext); return c11::Parser::make_MULTIPLY(yytext, loc); } "/" { + driver.add_token(yytext); return c11::Parser::make_DIVIDE(yytext, loc); } "%" { + driver.add_token(yytext); return c11::Parser::make_PERCENT(yytext, loc); } "<" { + driver.add_token(yytext); return c11::Parser::make_LT(yytext, loc); } ">" { + driver.add_token(yytext); return c11::Parser::make_GT(yytext, loc); } "^" { + driver.add_token(yytext); return c11::Parser::make_CARET(yytext, loc); } "|" { + driver.add_token(yytext); return c11::Parser::make_OR(yytext, loc); } "?" { + driver.add_token(yytext); return c11::Parser::make_QUESTION(yytext, loc); } {WS}+ { + driver.add_token(yytext); std::string str(yytext); stringutils::remove_character(str, ' '); if (str == "\n") { @@ -555,7 +670,7 @@ WS [ \t\v\n\f] } . { - /* discard bad characters */ + /* if nothing match, scan more characters ? */ yymore(); } diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index f4546c33db..e1b4e0ef60 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -13,13 +13,19 @@ int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("C Lexer: Standalone lexer program for C"); - TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, - "../test/input/channel.c", "string"); + TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, "", "string"); cmd.add(filearg); cmd.parse(argc, argv); std::string filename = filearg.getValue(); + + if (filename.empty()) { + std::cerr << "Error : Pass input C file, see --help" << std::endl; + return 1; + } + + std::ifstream file(filename); if (!file) { diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index f497ed1257..ad604fa936 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -345,9 +345,6 @@ declaration declaration_specifiers : storage_class_specifier declaration_specifiers - { - std::cout << "-------> \n"; - } | storage_class_specifier | type_specifier declaration_specifiers | type_specifier diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index be6382a629..dd8c25845a 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -47,10 +47,9 @@ namespace c11 { std::cerr << m << std::endl; } - void Driver::process(std::string text) { + void Driver::add_token(std::string text) { tokens.push_back(text); // here we will query and look into symbol table or register callback - // std::cout << text; } void Driver::scan_string(std::string& text) { diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 8128fad8db..7318ed4757 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -61,7 +61,7 @@ namespace c11 { bool parse_string(const std::string& input); bool parse_file(const std::string& filename); void scan_string(std::string& text); - void process(std::string); + void add_token(std::string); void set_verbose(bool b) { verbose = b; diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index 1f0b5e5fef..1a01f1fe06 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -13,12 +13,18 @@ int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("C Parser: Standalone parser program for C"); TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, - "../test/input/channel.c", "string"); + "", "string"); cmd.add(filearg); cmd.parse(argc, argv); std::string filename = filearg.getValue(); + + if (filename.empty()) { + std::cerr << "Error : Pass input C file, see --help" << std::endl; + return 1; + } + std::ifstream file(filename); if (!file) { diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 2b4c1ce09c..09d345339b 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1311,7 +1311,8 @@ nonlinblk : NONLINEAR NAME solvefor stmtlist "}" discretblk : DISCRETE NAME stmtlist "}" { $$ = new ast::DiscreteBlock($2, $3); - $$->set_token($1); + // todo : disabled symbol table, remove this + //$$->set_token($1); } ; @@ -1833,7 +1834,8 @@ conststmt : { } | conststmt NAME "=" number units { - auto statement = new ast::ConstantStatement($2, $4, $5); + auto constant = new ast::ConstantVar($2, $4, $5); + auto statement = new ast::ConstantStatement(constant); $1.push_back(std::shared_ptr<ast::ConstantStatement>(statement)); $$ = $1; } diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 5de2055f06..a6b6646341 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -6,6 +6,8 @@ set(PRINTER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp ) #============================================================================= diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp new file mode 100644 index 0000000000..60c37faa1d --- /dev/null +++ b/src/nmodl/printer/code_printer.cpp @@ -0,0 +1,57 @@ +#include "printer/code_printer.hpp" +#include "utils/string_utils.hpp" + +CodePrinter::CodePrinter(const std::string& filename) { + if (filename.empty()) { + throw std::runtime_error("Empty filename for CodePrinter"); + } + + ofs.open(filename.c_str()); + + if (ofs.fail()) { + auto msg = "Error while opening file '" + filename + "' for CodePrinter"; + throw std::runtime_error(msg); + } + + sbuf = ofs.rdbuf(); + result = std::make_shared<std::ostream>(sbuf); +} + +void CodePrinter::start_block() { + *result << "{"; + add_newline(); + indent_level++; +} + +void CodePrinter::start_block(std::string&& text) { + add_indent(); + *result << text << " {"; + add_newline(); + indent_level++; +} + +void CodePrinter::add_indent() { + *result << std::string(indent_level * NUM_SPACES, ' '); +} + +void CodePrinter::add_text(const std::string& text) { + *result << text; +} + +void CodePrinter::add_line(const std::string& text) { + add_indent(); + *result << text; + add_newline(); +} + +void CodePrinter::add_newline(int n) { + for (int i = 0; i < n; i++) { + *result << std::endl; + } +} + +void CodePrinter::end_block() { + indent_level--; + add_indent(); + *result << "}"; +} diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp new file mode 100644 index 0000000000..94387e5d17 --- /dev/null +++ b/src/nmodl/printer/code_printer.hpp @@ -0,0 +1,68 @@ +#ifndef NMODL_CODE_PRINTER_HPP +#define NMODL_CODE_PRINTER_HPP + +#include <fstream> +#include <iostream> +#include <sstream> +#include <memory> + +/** + * \class CodePrinter + * \brief Helper class for printing C/C++ code + * + * This class provides common functionality required by code generation + * visitor to print C/C++/Cuda code. + */ + +class CodePrinter { + private: + std::ofstream ofs; + std::streambuf* sbuf = nullptr; + std::shared_ptr<std::ostream> result; + size_t indent_level = 0; + const int NUM_SPACES = 4; + + public: + CodePrinter() : result(new std::ostream(std::cout.rdbuf())) { + } + CodePrinter(std::stringstream& stream) : result(new std::ostream(stream.rdbuf())) { + } + CodePrinter(const std::string& filename); + + ~CodePrinter() { + ofs.close(); + } + + /// print whitespaces for indentation + void add_indent(); + + /// start of new block scope (i.e. start with "{") + /// and increases indentation level + void start_block(); + + void start_block(std::string&&); + + void add_text(const std::string&); + + void add_line(const std::string&); + + void add_newline(int n = 1); + + void increase_indent() { + indent_level++; + } + + void decrease_indent() { + indent_level--; + } + + /// end of current block scope (i.e. end with "}") + /// and decreases indentation level + void end_block(); + + int indent_spaces() { + return NUM_SPACES * indent_level; + } +}; + +#endif diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index abb51a9151..f1071638d2 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -14,16 +14,27 @@ namespace symtab { } /// check if symbol has any of the common properties - /// \todo : rename to has_any_property + bool Symbol::has_properties(SymbolInfo new_properties) { return static_cast<bool>(properties & new_properties); } + /// check if symbol has all of the given properties + bool Symbol::has_all_properties(SymbolInfo new_properties) { + return ((properties & new_properties) == new_properties); + } + + /// check if symbol has any of the status bool Symbol::has_any_status(SymbolStatus new_status) { return static_cast<bool>(status & new_status); } + /// check if symbol has all of the status + bool Symbol::has_all_status(SymbolStatus new_status) { + return ((status & new_status) == new_status); + } + /// add new properties to symbol with bitwise or void Symbol::combine_properties(SymbolInfo new_properties) { properties |= new_properties; diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 4e282fdf8c..edd38d3aea 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -36,6 +36,12 @@ namespace symtab { /// name of the symbol std::string name; + /// original name in case of renaming + std::string renamed_from; + + /// unique id or index position when symbol is inserted into specific table + int id = 0; + /// ast node where symbol encountered first time /// node is passed from visitor and hence we have /// raw pointer instead of shared_ptr @@ -62,6 +68,18 @@ namespace symtab { /// order of derivative (for prime node) int order = 0; + /// order in which symbol arrives + int definition_order = -1; + + /// value (for parameters, constant etc) + std::shared_ptr<double> value; + + /// true if array variable + bool array = false; + + /// number of elements + int length = 1; + public: Symbol() = delete; @@ -82,7 +100,18 @@ namespace symtab { return name; } + int get_id() { + return id; + } + + void set_id(int i) { + id = i; + } + void set_name(std::string new_name) { + if (renamed_from.empty()) { + renamed_from = name; + } name = new_name; } @@ -122,12 +151,21 @@ namespace symtab { return write_count; } + int get_definition_order() const { + return definition_order; + } + bool is_external_symbol_only(); + /// \todo : rename to has_any_property bool has_properties(SymbolInfo new_properties); + bool has_all_properties(SymbolInfo new_properties); + bool has_any_status(SymbolStatus new_status); + bool has_all_status(SymbolStatus new_status); + void combine_properties(SymbolInfo new_properties); void add_property(NmodlInfo property); @@ -150,8 +188,50 @@ namespace symtab { status |= Status::localized; } + void created_from_state() { + created(); + status |= Status::from_state; + } + void set_order(int new_order); + void set_definition_order(int order) { + definition_order = order; + } + + void set_value(double val) { + value = std::make_shared<double>(val); + } + + void set_as_array(int len) { + array = true; + length = len; + } + + bool is_array() { + return array; + } + + void set_length(int len) { + length = len; + } + + int get_length() { + return length; + } + + std::string get_original_name() { + return renamed_from; + } + + std::shared_ptr<double> get_value() { + return value; + } + + void set_original_name(std::string new_name) { + renamed_from = new_name; + } + bool is_variable(); }; diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index efa74a5795..336f9d955f 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -103,9 +103,15 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { properties.emplace_back("non_linear_block"); } + /** todo: temporarily commented out if (has_property(obj, NmodlInfo::discrete_block)) { properties.emplace_back("discrete_block"); } + */ + + if (has_property(obj, NmodlInfo::constant_var)) { + properties.emplace_back("constant"); + } if (has_property(obj, NmodlInfo::partial_block)) { properties.emplace_back("partial_block"); @@ -138,6 +144,10 @@ std::vector<std::string> to_string_vector(const SymbolInfo& obj) { if (has_property(obj, NmodlInfo::to_solve)) { properties.emplace_back("to_solve"); } + + if (has_property(obj, NmodlInfo::useion)) { + properties.emplace_back("ion"); + } return properties; } @@ -163,6 +173,9 @@ std::vector<std::string> to_string_vector(const SymbolStatus& obj) { if (has_status(obj, Status::created)) { status.emplace_back("created"); } + if (has_status(obj, Status::from_state)) { + status.emplace_back("from_state"); + } return status; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 9683d7f518..f67eac37fc 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -49,7 +49,10 @@ namespace symtab { renamed = 1 << 3, /** created */ - created = 1 << 4 + created = 1 << 4, + + /** derived from state */ + from_state = 1 << 5 }; /** usage of mod file as array or scalar */ @@ -86,8 +89,9 @@ namespace symtab { * from NMODL (towards C/C++) then other types will be useful. * * \todo Rename param_assign to parameter_var + * \todo Reaching max limit (31), need to refactor all block types */ - enum class NmodlInfo { + enum class NmodlInfo : long long { /** Local Variable */ local_var = 1 << 0, @@ -151,8 +155,8 @@ namespace symtab { /** NonLinear Block */ non_linear_block = 1 << 20, - /** Discrete Block */ - discrete_block = 1 << 21, + /** constant variable */ + constant_var = 1 << 21, /** Partial Block */ partial_block = 1 << 22, @@ -176,7 +180,13 @@ namespace symtab { state_var = 1 << 28, /** need to solve : used in solve statement */ - to_solve = 1 << 29 + to_solve = 1 << 29, + + /** ion type */ + useion = 1 << 30 + + /** Discrete Block */ + // discrete_block = 1 << 31, }; } // namespace details diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 1dd65e0588..17bef9b149 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -6,6 +6,8 @@ namespace symtab { + int Table::counter = 0; + /** * Insert symbol into current symbol table. There are certain * cases where we were getting re-insertion errors. @@ -15,6 +17,7 @@ namespace symtab { if (lookup(name) != nullptr) { throw std::runtime_error("Trying to re-insert symbol " + name); } + symbol->set_id(counter++); symbols.push_back(symbol); } @@ -65,11 +68,49 @@ namespace symtab { /// return all symbol having any of the provided properties std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( - SymbolInfo properties) { + SymbolInfo properties, + bool all) { std::vector<std::shared_ptr<Symbol>> variables; for (auto& symbol : table.symbols) { - if (symbol->has_properties(properties)) { - variables.push_back(symbol); + if (all) { + if (symbol->has_all_properties(properties)) { + variables.push_back(symbol); + } + } else { + if (symbol->has_properties(properties)) { + variables.push_back(symbol); + } + } + } + return variables; + } + + /// return all symbol which has all "with" properties and none of the "without" properties + std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables(SymbolInfo with, + SymbolInfo without) { + auto variables = get_variables_with_properties(with, true); + decltype(variables) result; + for (auto& variable : variables) { + if (!variable->has_properties(without)) { + result.push_back(variable); + } + } + return result; + } + + + std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_status(SymbolStatus status, + bool all) { + std::vector<std::shared_ptr<Symbol>> variables; + for (auto& symbol : table.symbols) { + if (all) { + if (symbol->has_all_status(status)) { + variables.push_back(symbol); + } + } else { + if (symbol->has_any_status(status)) { + variables.push_back(symbol); + } } } return variables; @@ -183,7 +224,8 @@ namespace symtab { * to other passes like local renamer). In this case we have to do * local lookup in the current symtab and insert if doesn't exist. */ - void ModelSymbolTable::update_mode_insert(const std::shared_ptr<Symbol>& symbol) { + std::shared_ptr<Symbol> ModelSymbolTable::update_mode_insert( + const std::shared_ptr<Symbol>& symbol) { symbol->set_scope(current_symtab->name()); symbol->created(); @@ -193,40 +235,55 @@ namespace symtab { /// if no symbol found then safe to insert if (search_symbol == nullptr) { current_symtab->insert(symbol); - return; + return symbol; } /// for global scope just combine properties if (current_symtab->global_scope()) { search_symbol->combine_properties(symbol->get_properties()); - return; + return search_symbol; } /// insert into current block's symbol table if (current_symtab->lookup(name) == nullptr) { current_symtab->insert(symbol); } + return symbol; } + void ModelSymbolTable::update_order(const std::shared_ptr<Symbol>& present_symbol, + const std::shared_ptr<Symbol>& new_symbol) { + auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; - void ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { + bool is_parameter = new_symbol->has_properties(NmodlInfo::param_assign); + bool is_dependent_def = new_symbol->has_properties(NmodlInfo::dependent_def); + + if (symbol->get_definition_order() == -1) { + if (is_parameter || is_dependent_def) { + symbol->set_definition_order(definition_order++); + } + } + } + + std::shared_ptr<Symbol> ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { if (current_symtab == nullptr) { throw std::logic_error("Can not insert symbol without entering scope"); } + auto search_symbol = lookup(symbol->get_name()); + update_order(search_symbol, symbol); + /// handle update mode insertion if (update_table == true) { - update_mode_insert(symbol); - return; + return update_mode_insert(symbol); } symbol->set_scope(current_symtab->name()); - auto search_symbol = lookup(symbol->get_name()); // if no symbol found then safe to insert if (search_symbol == nullptr) { current_symtab->insert(symbol); - return; + return symbol; } /** @@ -243,7 +300,7 @@ namespace symtab { } else { search_symbol->combine_properties(symbol->get_properties()); } - return; + return search_symbol; } /** @@ -259,6 +316,7 @@ namespace symtab { emit_message(symbol, search_symbol, false); current_symtab->insert(symbol); } + return symbol; } @@ -293,6 +351,10 @@ namespace symtab { throw std::runtime_error("Can't enter with empty node"); } + if (node->is_breakpoint_block()) { + breakpoint_exist = true; + } + /// all global blocks in mod file have same global symbol table if (symtab && global) { return symtab.get(); @@ -351,6 +413,7 @@ namespace symtab { symtab = nullptr; current_symtab = nullptr; } + definition_order = 0; } @@ -363,7 +426,8 @@ namespace symtab { if (!symbols.empty()) { TableData table; table.title = std::move(title); - table.headers = {"NAME", "PROPERTIES", "STATUS", "LOCATION", "# READS", "# WRITES"}; + table.headers = {"NAME", "PROPERTIES", "STATUS", "LOCATION", + "VALUE", "# READS", "# WRITES"}; table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, text_alignment::right, text_alignment::right}; @@ -378,12 +442,20 @@ namespace symtab { } auto name = symbol->get_name(); + if (symbol->is_array()) { + name += "[" + std::to_string(symbol->get_length()) + "]"; + } auto position = symbol->get_token().position(); auto properties = to_string(symbol->get_properties()); auto status = to_string(symbol->get_status()); auto reads = std::to_string(symbol->get_read_count()); + std::string value = ""; + auto sym_value = symbol->get_value(); + if (sym_value) { + value = std::to_string(*sym_value); + } auto writes = std::to_string(symbol->get_write_count()); - table.rows.push_back({name, properties, status, position, reads, writes}); + table.rows.push_back({name, properties, status, position, value, reads, writes}); } table.print(stream, indent); } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 48df5baa2e..f2119cf674 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -23,6 +23,8 @@ namespace symtab { * \todo Re-implement pretty printing */ class Table { + static int counter; + public: /// map of symbol name and associated symbol for faster lookup std::vector<std::shared_ptr<Symbol>> symbols; @@ -137,7 +139,13 @@ namespace symtab { std::shared_ptr<Symbol> lookup_in_scope(const std::string& name); - std::vector<std::shared_ptr<Symbol>> get_variables_with_properties(SymbolInfo properties); + std::vector<std::shared_ptr<Symbol>> get_variables(SymbolInfo with, SymbolInfo without); + + std::vector<std::shared_ptr<Symbol>> get_variables_with_properties(SymbolInfo properties, + bool all = false); + + std::vector<std::shared_ptr<Symbol>> get_variables_with_status(SymbolStatus status, + bool all = false); bool under_global_scope(); @@ -181,13 +189,24 @@ namespace symtab { /// symbols otherwise we throw away old table and construct new one bool update_table = false; + /// current order of variable being defined + int definition_order = 0; + + /// if breakpoint block exist in the model + bool breakpoint_exist = false; + + std::string model_suffix = ""; + /// insert symbol table in update mode - void update_mode_insert(const std::shared_ptr<Symbol>& symbol); + std::shared_ptr<Symbol> update_mode_insert(const std::shared_ptr<Symbol>& symbol); void emit_message(const std::shared_ptr<Symbol>& first, const std::shared_ptr<Symbol>& second, bool redefinition); + void update_order(const std::shared_ptr<Symbol>& present_symbol, + const std::shared_ptr<Symbol>& new_symbol); + public: /// entering into new nmodl block SymbolTable* enter_scope(std::string name, @@ -199,7 +218,7 @@ namespace symtab { void leave_scope(); /// insert new symbol into current table - void insert(const std::shared_ptr<Symbol>& symbol); + std::shared_ptr<Symbol> insert(const std::shared_ptr<Symbol>& symbol); /// lookup for symbol into current as well as all parent tables std::shared_ptr<Symbol> lookup(const std::string& name); @@ -210,6 +229,14 @@ namespace symtab { /// re-initialize members to throw away old symbol tables /// this is required as symtab visitor pass runs multiple time void set_mode(bool mode); + + bool has_breakpoint() const { + return breakpoint_exist; + } + + std::string model_name() const { + return model_suffix; + } }; } // namespace symtab diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 1fd1da5541..1367168de5 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -6,6 +6,7 @@ set(UTIL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/table_data.hpp ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp + ${CMAKE_BINARY_DIR}/version.cpp ) #============================================================================= diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index c2fb728dec..5fede5f91a 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -101,4 +101,10 @@ namespace stringutils { return left + text + right; } + /// To lower case + static inline std::string tolower(std::string text) { + std::transform(text.begin(), text.end(), text.begin(), ::tolower); + return text; + } + } // namespace stringutils diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index e0db965c2a..b546dac8e7 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -30,6 +30,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp ) set_source_files_properties( @@ -47,7 +49,7 @@ add_library(visitor add_dependencies(visitor lexer util) add_executable(nocmodl_visitor main.cpp) -target_link_libraries(nocmodl_visitor printer visitor symtab util lexer) +target_link_libraries(nocmodl_visitor printer visitor symtab util lexer fmt) #============================================================================= # Files for clang-format diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 6c2781e196..fd79156136 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -4,6 +4,7 @@ #include "visitors/nmodl_visitor.hpp" #include "parser/diffeq_driver.hpp" #include "visitors/visitor_utils.hpp" +#include "symtab/symbol.hpp" void CnexpSolveVisitor::visit_solve_block(SolveBlock* node) { if (node->method) { @@ -21,8 +22,8 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { auto& lhs = node->lhs; auto& rhs = node->rhs; - /// we have to only solve binary expressions in derivative block if cnexp method specified - if (!derivative_block || (node->op.value != BOP_ASSIGN) || (solve_method != cnexp_method)) { + /// we have to only solve binary expressions in derivative block + if (!derivative_block || (node->op.value != BOP_ASSIGN)) { return; } @@ -32,22 +33,43 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { } auto name = std::dynamic_pointer_cast<VarName>(lhs)->name; + if (name->is_prime_name()) { /// convert ode into string format std::stringstream stream; NmodlPrintVisitor v(stream); node->visit_children(&v); auto equation = stream.str(); - - /// check if ode can be solved with cnexp method diffeq::Driver diffeq_driver; - std::string solution; - if (diffeq_driver.cnexp_possible(equation, solution)) { - auto statement = create_statement(solution); - auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); - auto bin_expr = std::dynamic_pointer_cast<BinaryExpression>(expr_statement->expression); - lhs.reset(bin_expr->lhs->clone()); - rhs.reset(bin_expr->rhs->clone()); + + if (solve_method == cnexp_method) { + std::string solution; + /// check if ode can be solved with cnexp method + if (diffeq_driver.cnexp_possible(equation, solution)) { + auto statement = create_statement(solution); + auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); + auto bin_expr = + std::dynamic_pointer_cast<BinaryExpression>(expr_statement->expression); + lhs.reset(bin_expr->lhs->clone()); + rhs.reset(bin_expr->rhs->clone()); + } else { + std::cerr << "cnexp solver not possible"; + } + } else if (solve_method == derivimplicit_method || solve_method == euler_method) { + auto varname = "D" + name->get_name(); + auto variable = new ast::Name(new ast::String(varname)); + lhs.reset(variable); + auto symbol = std::make_shared<symtab::Symbol>(varname, ModToken()); + symbol->set_original_name(name->get_name()); + symbol->created_from_state(); + program_symtab->insert(symbol); + } else { + std::cerr << "solver method '" + solve_method + "' not supported"; } } } + +void CnexpSolveVisitor::visit_program(Program* node) { + program_symtab = node->get_symbol_table(); + node->visit_children(this); +} \ No newline at end of file diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp index 1b5f3a96b3..256bb6e8ce 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -25,15 +25,24 @@ class CnexpSolveVisitor : public AstVisitor { /// true while visiting derivative block bool derivative_block = false; - /// name of the cnexp methoda in nmodl + /// name of the cnexp method const std::string cnexp_method = "cnexp"; + /// name of the derivimplicit method + const std::string derivimplicit_method = "derivimplicit"; + + /// name of the euler method + const std::string euler_method = "euler"; + + symtab::SymbolTable* program_symtab = nullptr; + public: CnexpSolveVisitor() = default; void visit_solve_block(SolveBlock* node) override; void visit_derivative_block(DerivativeBlock* node) override; void visit_binary_expression(BinaryExpression* node) override; + void visit_program(Program* node) override; }; #endif diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 3d3cc391db..6b843474b7 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -120,10 +120,16 @@ void LocalizeVisitor::visit_program(Program* node) { for (auto& block : block_usage[DUState::D]) { auto block_ptr = dynamic_cast<Block*>(block.get()); auto statement_block = block_ptr->get_statement_block(); - auto variable = add_local_variable(statement_block.get(), varname); + LocalVar* variable; + auto symbol = program_symtab->lookup(varname); + if (symbol->is_array()) { + variable = + add_local_variable(statement_block.get(), varname, symbol->get_length()); + } else { + variable = add_local_variable(statement_block.get(), varname); + } /// mark variable as localized in global symbol table - auto symbol = program_symtab->lookup(varname); symbol->localized(); /// insert new symbol into symbol table diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 99e478414d..4f4d97c48a 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -45,7 +45,7 @@ int main(int argc, const char* argv[]) { throw std::runtime_error("Could not open file " + filename); } - std::string channel_name = remove_extension(base_name(filename)); + std::string mod_filename = remove_extension(base_name(filename)); /// driver object creates lexer and parser, just call parser method nmodl::Driver driver; @@ -96,12 +96,12 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.verbrename.mod"); + NmodlPrintVisitor v(mod_filename + ".nocmodl.verbrename.mod"); v.visit_program(ast.get()); } { - NmodlPrintVisitor v(channel_name + ".nocmodl.mod"); + NmodlPrintVisitor v(mod_filename + ".nocmodl.mod"); v.visit_program(ast.get()); } @@ -112,7 +112,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.mod"); + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.mod"); v.visit_program(ast.get()); } @@ -124,7 +124,7 @@ int main(int argc, const char* argv[]) { { - NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.mod"); + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.mod"); v.visit_program(ast.get()); } @@ -135,7 +135,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.ren.mod"); + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.mod"); v.visit_program(ast.get()); } @@ -152,7 +152,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.ren.loc.mod"); + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.mod"); v.visit_program(ast.get()); } @@ -167,12 +167,20 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(channel_name + ".nocmodl.cnexp.in.ren.loc.ren.mod"); + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); + std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; + } + + { + NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.ren.mod"); v.visit_program(ast.get()); } { - PerfVisitor v(channel_name + ".perf.json"); + PerfVisitor v(mod_filename + ".perf.json"); v.visit_program(ast.get()); auto symtab = ast->get_symbol_table(); @@ -186,6 +194,7 @@ int main(int argc, const char* argv[]) { std::cout << "----PERF VISITOR FINISHED----" << std::endl; } + } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; return 1; diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index e88d1eda77..70353cbd16 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -34,7 +34,7 @@ void RenameVisitor::visit_verbatim(Verbatim* node) { auto tokens = driver.all_tokens(); std::string result; - for(auto& token: tokens) { + for (auto& token : tokens) { if (token == var_name) { result += new_var_name; } else { diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 79442d4f91..61cdec3491 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -32,13 +32,68 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { symbol = std::make_shared<Symbol>(name, node, token); symbol->add_property(property); - modsymtab->insert(symbol); + + + if (property == NmodlInfo::nonspe_cur_var) { + symbol->add_property(NmodlInfo::range_var); + } + + /// range and non_spec_cur can appear in any order in neuron block. + /// for both properties, we have to check if symbol is already exist. + /// if so we have to return to avoid duplicate definition error. + if (property == NmodlInfo::range_var || property == NmodlInfo::nonspe_cur_var) { + auto s = modsymtab->lookup(name); + if (s && s->has_properties(NmodlInfo::nonspe_cur_var | NmodlInfo::range_var)) { + s->add_property(property); + return; + } + } + + + /// insert might return different symbol if already exist in the same scope + symbol = modsymtab->insert(symbol); /// extra property for state variables if (state_block) { symbol->add_property(NmodlInfo::state_var); } + if (node->is_param_assign()) { + auto parameter = dynamic_cast<ParamAssign*>(node); + if (parameter->value) { + symbol->set_value(parameter->value->number_value()); + } + if (parameter->name->is_indexed_name()) { + auto name = dynamic_cast<IndexedName*>(parameter->name.get()); + auto length = dynamic_cast<Integer*>(name->get_length().get()); + symbol->set_as_array(length->eval()); + } + } + + if (node->is_dependent_def()) { + auto variable = dynamic_cast<DependentDef*>(node); + auto length = variable->get_length(); + if (length) { + symbol->set_as_array(length->eval()); + } + } + + if (node->is_constant_var()) { + auto constant = dynamic_cast<ConstantVar*>(node); + if (constant->value) { + symbol->set_value(constant->value->number_value()); + } + } + + if (node->is_local_var()) { + auto local_var = dynamic_cast<LocalVar*>(node); + if (local_var->name->is_indexed_name()) { + auto name = dynamic_cast<IndexedName*>(local_var->name.get()); + auto length = dynamic_cast<Integer*>(name->get_length().get()); + symbol->set_as_array(length->eval()); + } + } + /// visit childrens, most likely variables are already /// leaf nodes, not necessary to visit node->visit_children(this); diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp new file mode 100644 index 0000000000..404168a91d --- /dev/null +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -0,0 +1,16 @@ +#include "visitors/var_usage_visitor.hpp" + +/// rename matching variable +void VarUsageVisitor::visit_name(ast::Name* node) { + std::string name = node->get_name(); + if (name == var_name) { + used = true; + } +} + +bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { + used = false; + var_name = name; + node->visit_children(this); + return used; +} \ No newline at end of file diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp new file mode 100644 index 0000000000..079462d748 --- /dev/null +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -0,0 +1,30 @@ +#ifndef VAR_USAGE_VISITOR_HPP +#define VAR_USAGE_VISITOR_HPP + +#include <string> + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" + +/** + * \class VarUsageVisitor + * \brief Check if variable is used in given block + * + * \todo : check if macro is considered as variable + */ + +class VarUsageVisitor : public AstVisitor { + private: + /// variable to check usage + std::string var_name; + bool used = false; + + public: + VarUsageVisitor() = default; + + bool variable_used(ast::Node* node, std::string name); + + virtual void visit_name(ast::Name* node) override; +}; + +#endif diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index b48d51b448..55479afd25 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -40,11 +40,11 @@ std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { bool rename_plausible = false; auto new_name = name; if (name.find(local_prefix) == 0) { - new_name.erase(0,2); + new_name.erase(0, 2); rename_plausible = true; } if (name.find(range_prefix) == 0) { - new_name.erase(0,3); + new_name.erase(0, 3); rename_plausible = true; } if (rename_plausible) { @@ -70,7 +70,7 @@ void VerbatimVarRenameVisitor::visit_verbatim(Verbatim* node) { auto tokens = driver.all_tokens(); std::string result; - for(auto& token: tokens) { + for (auto& token : tokens) { result += rename_variable(token); } statement->set(result); diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index cc3b39ed87..9f81809d03 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -29,7 +29,6 @@ class VerbatimVarRenameVisitor : public AstVisitor { private: - /// non-null symbol table in the scope hierarchy symtab::SymbolTable* symtab = nullptr; @@ -37,10 +36,10 @@ class VerbatimVarRenameVisitor : public AstVisitor { std::stack<symtab::SymbolTable*> symtab_stack; /// prefix used for local variable - std::string local_prefix = "_l"; + const std::string local_prefix = "_l"; /// prefix used for range variables - std::string range_prefix = "_p_"; + const std::string range_prefix = "_p_"; std::string rename_variable(std::string); diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index ce436bbdd2..718d16e635 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -3,11 +3,10 @@ void VerbatimVisitor::visit_verbatim(Verbatim* node) { std::string block; - - if (node->statement) { - block = node->statement->eval(); + auto statement = node->get_statement(); + if (statement) { + block = statement->eval(); } - if (!block.empty() && verbose) { std::cout << "BLOCK START"; std::cout << block; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index b1b703848b..4619c306c4 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -36,7 +36,7 @@ void add_local_statement(StatementBlock* node) { } } -LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname) { +LocalVar* add_local_variable(StatementBlock* node, Identifier* varname) { add_local_statement(node); auto local_variables = get_local_variables(node); auto var = std::make_shared<LocalVar>(varname); @@ -44,8 +44,13 @@ LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname return var.get(); } -LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname) { - auto name = new ast::Name(new ast::String(varname)); +LocalVar* add_local_variable(StatementBlock* node, const std::string& varname) { + auto name = new Name(new String(varname)); + return add_local_variable(node, name); +} + +LocalVar* add_local_variable(StatementBlock* node, const std::string& varname, int dim) { + auto name = new IndexedName(new Name(new String(varname)), new Integer(dim, nullptr)); return add_local_variable(node, name); } @@ -59,7 +64,7 @@ LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varna * \todo : Need to revisit this during code generation passes to make sure * if all statements can be part of procedure block. */ -std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement) { +std::shared_ptr<Statement> create_statement(const std::string& code_statement) { nmodl::Driver driver; auto nmodl_text = "PROCEDURE dummy() { " + code_statement + " }"; driver.parse_string(nmodl_text); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 661d100954..f971308154 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -22,6 +22,7 @@ void add_local_statement(ast::StatementBlock* node); /** Add new local variable to the block */ LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); +LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname, int dim); /** Create ast statement node from given code in string format */ std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 4bf9947181..2721ab7ed5 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -76,6 +76,7 @@ SCENARIO("Symbol properties can be added and converted to string") { SCENARIO("Symbol operations") { SymbolInfo property1 = NmodlInfo::argument; SymbolInfo property2 = NmodlInfo::range_var; + SymbolInfo property3 = NmodlInfo::param_assign; GIVEN("A symbol") { ModToken token(true); Symbol symbol("alpha", token); @@ -91,11 +92,22 @@ SCENARIO("Symbol operations") { THEN("symbol has multiple properties") { REQUIRE(symbol.has_properties(property1) == true); - SymbolInfo property = NmodlInfo::param_assign; - REQUIRE(symbol.has_properties(property) == false); + REQUIRE(symbol.has_properties(property3) == false); - symbol.add_property(NmodlInfo::param_assign); - REQUIRE(symbol.has_properties(property) == true); + symbol.add_property(property3); + REQUIRE(symbol.has_properties(property3) == true); + + auto property = property1 | property2; + REQUIRE(symbol.has_all_properties(property) == true); + + property |= property3; + REQUIRE(symbol.has_all_properties(property) == true); + + property = property2 | property3; + REQUIRE(symbol.has_all_properties(property) == true); + + property |= NmodlInfo::to_solve; + REQUIRE(symbol.has_all_properties(property) == false); } } WHEN("combined properties") { @@ -179,6 +191,49 @@ SCENARIO("Symbol table operations") { REQUIRE(next_table->lookup_in_scope("alpha") != nullptr); } } + WHEN("query for symbol with and without properties") { + auto symbol1 = std::make_shared<Symbol>("alpha", ModToken()); + auto symbol2 = std::make_shared<Symbol>("beta", ModToken()); + auto symbol3 = std::make_shared<Symbol>("gamma", ModToken()); + auto symbol4 = std::make_shared<Symbol>("delta", ModToken()); + + symbol1->add_property(NmodlInfo::range_var | NmodlInfo::param_assign); + symbol2->add_property(NmodlInfo::range_var | NmodlInfo::param_assign | + NmodlInfo::state_var); + symbol3->add_property(NmodlInfo::range_var | NmodlInfo::dependent_def | + NmodlInfo::pointer_var); + symbol4->add_property(NmodlInfo::range_var); + + table->insert(symbol1); + table->insert(symbol2); + table->insert(symbol3); + table->insert(symbol4); + + auto result = table->get_variables_with_properties(NmodlInfo::range_var); + REQUIRE(result.size() == 4); + + result = + table->get_variables_with_properties(NmodlInfo::range_var | NmodlInfo::pointer_var); + REQUIRE(result.size() == 4); + + auto with = NmodlInfo::range_var | NmodlInfo::param_assign; + auto without = NmodlInfo::state_var | NmodlInfo::pointer_var; + result = table->get_variables(with, without); + REQUIRE(result.size() == 1); + REQUIRE(result[0]->get_name() == "alpha"); + + + with = NmodlInfo::range_var; + without = NmodlInfo::param_assign | NmodlInfo::dependent_def; + result = table->get_variables(with, without); + REQUIRE(result.size() == 1); + REQUIRE(result[0]->get_name() == "delta"); + + with = NmodlInfo::range_var; + without = NmodlInfo::range_var; + result = table->get_variables(with, without); + REQUIRE(result.size() == 0); + } } } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index e7243643da..7109d1ea70 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1776,12 +1776,12 @@ SCENARIO("CnexpSolver visitor solving ODEs") { } DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau + Dm = (mInf-m)/mTau + Dh = (hInf-h)/hTau } )"; - THEN("ODEs don't get solved / replaced ") { + THEN("ODEs don't get solved but state variables get replaced with Dstate ") { std::string input = reindent_text(nmodl_text); auto expected_result = reindent_text(output_nmodl); auto result = run_cnexp_solve_visitor(input); From be610967822738ba58baec981a2ba8d49f363ac7 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 15 Apr 2018 19:18:07 +0200 Subject: [PATCH 066/871] Added cuda backend and clang-tidy cleanup Change-Id: I549a627ebee098e69224f9961b35e55d1bfb6c57 NMODL Repo SHA: BlueBrain/nmodl@8c49f25a3be020df4bb5d2a88be40047f787ea27 --- src/nmodl/codegen/CMakeLists.txt | 2 + .../codegen/base/codegen_base_visitor.cpp | 86 ++++----- .../codegen/base/codegen_base_visitor.hpp | 8 +- .../codegen/base/codegen_helper_visitor.cpp | 2 +- .../codegen/c-cuda/codegen_c_cuda_visitor.cpp | 174 ++++++++++++++++++ .../codegen/c-cuda/codegen_c_cuda_visitor.hpp | 96 ++++++++++ .../c-openmp/codegen_c_omp_visitor.cpp | 5 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 101 +++++----- src/nmodl/codegen/c/codegen_c_visitor.hpp | 23 ++- src/nmodl/codegen/codegen_info.cpp | 13 +- src/nmodl/codegen/codegen_info.hpp | 8 +- src/nmodl/codegen/main.cpp | 15 +- src/nmodl/parser/c11_driver.cpp | 2 +- src/nmodl/parser/c11_driver.hpp | 2 +- src/nmodl/parser/diffeq_driver.cpp | 4 +- src/nmodl/parser/diffeq_driver.hpp | 4 +- src/nmodl/symtab/symbol_table.cpp | 23 ++- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/version/version.cpp.in | 6 +- src/nmodl/visitors/perf_visitor.cpp | 2 +- src/nmodl/visitors/var_usage_visitor.cpp | 4 +- test/nmodl/transpiler/parser/parser.cpp | 2 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 2 +- 23 files changed, 424 insertions(+), 162 deletions(-) create mode 100644 src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp create mode 100644 src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index ffcd970f16..82943d9b1c 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -14,6 +14,8 @@ set(CODEGEN_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/c-openmp/codegen_c_omp_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/c-openacc/codegen_c_acc_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/c-openacc/codegen_c_acc_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/c-cuda/codegen_c_cuda_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/c-cuda/codegen_c_cuda_visitor.cpp ) #============================================================================= diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 5d03dcba82..bc3d517586 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -1,6 +1,7 @@ #include <algorithm> -#include <math.h> -#include <time.h> +#include <utility> +#include <cmath> +#include <ctime> #include "visitors/rename_visitor.hpp" #include "codegen/base/codegen_helper_visitor.hpp" @@ -68,7 +69,7 @@ void CodegenBaseVisitor::visit_boolean(Boolean* node) { if (!codegen) { return; } - printer->add_text(std::to_string(node->eval())); + printer->add_text(std::to_string(static_cast<int>(node->eval()))); } @@ -313,10 +314,7 @@ bool CodegenBaseVisitor::net_send_buffer_required() { bool CodegenBaseVisitor::net_receive_buffering_required() { - if (info.point_process && !info.artificial_cell && info.net_receive_node != nullptr) { - return true; - } - return false; + return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; } @@ -324,34 +322,22 @@ bool CodegenBaseVisitor::nrn_state_required() { if (info.artificial_cell) { return false; } - if (info.solve_node != nullptr || info.currents.empty()) { - return true; - } - return false; + return info.solve_node != nullptr || info.currents.empty(); } bool CodegenBaseVisitor::nrn_cur_required() { - if (info.breakpoint_node != nullptr && !info.currents.empty()) { - return true; - } - return false; + return info.breakpoint_node != nullptr && !info.currents.empty(); } bool CodegenBaseVisitor::net_receive_exist() { - if (info.net_receive_node != nullptr) { - return true; - } - return false; + return info.net_receive_node != nullptr; } bool CodegenBaseVisitor::breakpoint_exist() { - if (info.breakpoint_node != nullptr) { - return true; - } - return false; + return info.breakpoint_node != nullptr; } @@ -373,7 +359,7 @@ bool CodegenBaseVisitor::state_variable(std::string name) { } -int CodegenBaseVisitor::position_of_float_var(std::string name) { +int CodegenBaseVisitor::position_of_float_var(const std::string& name) { int index = 0; for (auto& var : float_variables) { if (var->get_name() == name) { @@ -385,7 +371,7 @@ int CodegenBaseVisitor::position_of_float_var(std::string name) { } -int CodegenBaseVisitor::position_of_int_var(std::string name) { +int CodegenBaseVisitor::position_of_int_var(const std::string& name) { int index = 0; for (auto& var : int_variables) { if (var.symbol->get_name() == name) { @@ -409,9 +395,8 @@ int CodegenBaseVisitor::position_of_int_var(std::string name) { std::string CodegenBaseVisitor::double_to_string(double value) { if (ceilf(value) == value) { return "{:.1f}"_format(value); - } else { - return "{:.16g}"_format(value); } + return "{:.16g}"_format(value); } @@ -449,12 +434,9 @@ bool CodegenBaseVisitor::need_semicolon(Statement* node) { // check if there is a function or procedure defined with given name bool CodegenBaseVisitor::defined_method(std::string name) { - auto function = program_symtab->lookup(name); + auto function = program_symtab->lookup(std::move(name)); auto properties = NmodlInfo::function_block | NmodlInfo::procedure_block; - if (function && function->has_properties(properties)) { - return true; - } - return false; + return function && function->has_properties(properties); } @@ -572,21 +554,21 @@ void CodegenBaseVisitor::update_index_semantics() { int index = 0; info.semantics.clear(); if (info.point_process) { - info.semantics.push_back({index++, "area", 1}); - info.semantics.push_back({index++, "pntproc", 1}); + info.semantics.emplace_back(index++, "area", 1); + info.semantics.emplace_back(index++, "pntproc", 1); } for (const auto& ion : info.ions) { for (auto& var : ion.reads) { - info.semantics.push_back({index++, ion.name + "_ion", 1}); + info.semantics.emplace_back(index++, ion.name + "_ion", 1); } for (auto& var : ion.writes) { - info.semantics.push_back({index++, ion.name + "_ion", 1}); + info.semantics.emplace_back(index++, ion.name + "_ion", 1); if (ion.is_ionic_current(var)) { - info.semantics.push_back({index++, ion.name + "_ion", 1}); + info.semantics.emplace_back(index++, ion.name + "_ion", 1); } } if (ion.need_style) { - info.semantics.push_back({index++, "#{}_ion"_format(ion.name), 1}); + info.semantics.emplace_back(index++, "#{}_ion"_format(ion.name), 1); } } for (auto& var : info.pointer_variables) { @@ -595,14 +577,14 @@ void CodegenBaseVisitor::update_index_semantics() { } int size = var->get_length(); if (var->has_properties(NmodlInfo::pointer_var)) { - info.semantics.push_back({index, "pointer", size}); + info.semantics.emplace_back(index, "pointer", size); } else { - info.semantics.push_back({index, "bbcorepointer", size}); + info.semantics.emplace_back(index, "bbcorepointer", size); } index += size; } if (info.net_send_used) { - info.semantics.push_back({index++, "netsend", 1}); + info.semantics.emplace_back(index++, "netsend", 1); } } @@ -661,39 +643,39 @@ std::vector<SymbolType> CodegenBaseVisitor::get_float_variables() { std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { std::vector<IndexVariableInfo> variables; if (info.point_process) { - variables.push_back({make_symbol(node_area)}); + variables.emplace_back(make_symbol(node_area)); if (info.artificial_cell) { - variables.push_back({make_symbol("point_process"), true}); + variables.emplace_back(make_symbol("point_process"), true); } else { - variables.push_back({make_symbol("point_process"), false, false, true}); + variables.emplace_back(make_symbol("point_process"), false, false, true); } } for (auto& ion : info.ions) { bool need_style = false; for (auto& var : ion.reads) { - variables.push_back({make_symbol("ion_" + var)}); + variables.emplace_back(make_symbol("ion_" + var)); } for (auto& var : ion.writes) { - variables.push_back({make_symbol("ion_" + var)}); + variables.emplace_back(make_symbol("ion_" + var)); if (ion.is_ionic_current(var)) { - variables.push_back({make_symbol("ion_di" + ion.name + "dv")}); + variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); } if (ion.is_intra_cell_conc(var) || ion.is_extra_cell_conc(var)) { need_style = true; } } if (need_style) { - variables.push_back({make_symbol("style_" + ion.name), false, true}); + variables.emplace_back(make_symbol("style_" + ion.name), false, true); } } for (auto& var : info.pointer_variables) { auto name = var->get_name(); if (var->has_properties(NmodlInfo::pointer_var)) { - variables.push_back({make_symbol(name)}); + variables.emplace_back(make_symbol(name)); } else { - variables.push_back({make_symbol(name), true}); + variables.emplace_back(make_symbol(name), true); } } @@ -701,9 +683,9 @@ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { // then tqitem is an offset if (info.net_send_used) { if (info.artificial_cell) { - variables.push_back({make_symbol("tqitem"), true}); + variables.emplace_back(make_symbol("tqitem"), true); } else { - variables.push_back({make_symbol("tqitem"), false, false, true}); + variables.emplace_back(make_symbol("tqitem"), false, false, true); } info.tqitem_index = variables.size() - 1; } diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index f197d034d5..e3f4f28be5 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -310,11 +310,11 @@ class CodegenBaseVisitor : public AstVisitor { /// for given float variable name, index position in the data array - int position_of_float_var(std::string name); + int position_of_float_var(const std::string& name); /// for given int variable name, index position in the data array - int position_of_int_var(std::string name); + int position_of_int_var(const std::string& name); /// when ion variable copies optimized then change name (e.g. ena to ion_ena) @@ -390,8 +390,8 @@ class CodegenBaseVisitor : public AstVisitor { } public: - CodegenBaseVisitor(std::string mod_file, bool aos) - : printer(new CodePrinter(mod_file + ".cpp")) { + CodegenBaseVisitor(std::string mod_file, bool aos, std::string extension = ".cpp") + : printer(new CodePrinter(mod_file + extension)) { init(aos, mod_file); } diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 1f944dc9be..0301261ba8 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -1,5 +1,5 @@ #include <algorithm> -#include <math.h> +#include <cmath> #include "visitors/rename_visitor.hpp" #include "codegen/base/codegen_helper_visitor.hpp" diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp new file mode 100644 index 0000000000..542b97a12c --- /dev/null +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp @@ -0,0 +1,174 @@ +#include <fmt/format.h> + +#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" +#include "utils/string_utils.hpp" + +using namespace fmt::literals; + + +/****************************************************************************************/ +/* Routines must be overloaded in backend */ +/****************************************************************************************/ + + +std::string CodegenCCudaVisitor::compute_method_name(BlockType type) { + if (type == BlockType::Initial) { + return method_name("nrn_init"); + } + if (type == BlockType::State) { + return method_name("cuda_nrn_state"); + } + if (type == BlockType::Equation) { + return method_name("cuda_nrn_cur"); + } + throw std::runtime_error("compute_method_name not implemented"); +} + + +void CodegenCCudaVisitor::print_atomic_op(std::string lhs, std::string op, std::string rhs) { + std::string function; + if (op == "+") { + function = "atomicAdd"; + } else if (op == "-") { + function = "atomicSub"; + } else { + throw std::runtime_error("CUDA backend error : {} not supported"_format(op)); + } + printer->add_line("{}(&{}, {});"_format(function, lhs, rhs)); +} + + +void CodegenCCudaVisitor::print_backend_includes() { + printer->add_line("#include <cuda.h>"); +} + + +std::string CodegenCCudaVisitor::backend_name() { + return "C-CUDA (api-compatibility)"; +} + + +void CodegenCCudaVisitor::print_global_method_annotation() { + printer->add_line("__global__"); +} + + +void CodegenCCudaVisitor::print_device_method_annotation() { + printer->add_line("__device__"); +} + + +void CodegenCCudaVisitor::print_nrn_cur_matrix_shadow_update() { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + stringutils::remove_character(rhs_op, '='); + stringutils::remove_character(d_op, '='); + print_atomic_op("vec_rhs[node_id]", rhs_op, "rhs"); + print_atomic_op("vec_d[node_id]", d_op, "g"); +} + + +/* + * Depending on the backend, print condition/loop for iterating over channels + * + * For GPU backend its thread id less than total channel instances. Below we + * assume we launch 1-d grid. + */ +void CodegenCCudaVisitor::print_channel_iteration_block_begin() { + printer->add_line("int id = blockIdx.x * blockDim.x + threadIdx.x;"); + printer->start_block("if (id < end) "); +} + + +void CodegenCCudaVisitor::print_channel_iteration_block_end() { + printer->end_block(); + printer->add_newline(); +} + + + +void CodegenCCudaVisitor::print_nrn_cur_matrix_shadow_reduction() { + // do nothing +} + + +void CodegenCCudaVisitor::print_rhs_d_shadow_variables() { + // do nothing +} + + +bool CodegenCCudaVisitor::nrn_cur_reduction_loop_required() { + return false; +} + + +void CodegenCCudaVisitor::print_backend_namespace_start() { + printer->add_newline(1); + printer->start_block("namespace cuda"); +} + + +void CodegenCCudaVisitor::print_backend_namespace_end() { + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCCudaVisitor::codegen_compute_functions() { + print_top_verbatim_blocks(); + print_function_prototypes(); + + for (const auto& procedure : info.procedures) { + print_procedure(procedure); + } + + for (const auto& function : info.functions) { + print_function(function); + } + + print_net_send_buffer_kernel(); + print_net_receive(); + print_net_receive_buffer_kernel(); + print_nrn_cur(); + print_nrn_state(); +} + + +void CodegenCCudaVisitor::print_wrapper_routine(std::string wraper_function, BlockType type) { + auto args = "NrnThread* nt, Memb_list* ml, int type"; + wraper_function = method_name(wraper_function); + auto compute_function = compute_method_name(type); + + printer->add_newline(2); + printer->start_block("void {}({})"_format(wraper_function, args)); + printer->add_line("int nodecount = ml->nodecount;"); + printer->add_line("int nthread = 256;"); + printer->add_line("int nblock = (nodecount+nthread-1)/nthread;"); + printer->add_line("{}<<<nblock, nthread>>>(nt, ml, type);"_format(compute_function)); + printer->add_line("cudaDeviceSynchronize();"); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenCCudaVisitor::codegen_wrapper_routines() { + print_wrapper_routine("nrn_cur", BlockType::Equation); + print_wrapper_routine("nrn_sate", BlockType::State); +} + + +void CodegenCCudaVisitor::codegen_all() { + codegen = true; + print_backend_info(); + codegen_includes(); + codegen_namespace_begin(); + + codegen_data_structures(); + codegen_common_getters(); + + codegen_compute_functions(); + + codegen_wrapper_routines(); + + codegen_namespace_end(); +} diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp new file mode 100644 index 0000000000..238004301d --- /dev/null +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp @@ -0,0 +1,96 @@ +#ifndef NMODL_CODEGEN_C_CUDA_VISITOR_HPP +#define NMODL_CODEGEN_C_CUDA_VISITOR_HPP + +#include "codegen/c/codegen_c_visitor.hpp" + + +/** + * \class CodegenCCudaVisitor + * \brief Visitor for printing CUDA backend + * + * \todo : + * - handle define i.e. macro statement printing + * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) + */ +class CodegenCCudaVisitor : public CodegenCVisitor { + void print_atomic_op(std::string lhs, std::string op, std::string rhs); + + protected: + /// name of the code generation backend + std::string backend_name() override; + + + /// return name of main compute kernels + std::string compute_method_name(BlockType type) override; + + + /// common includes : standard c/c++, coreneuron and backend specific + void print_backend_includes() override; + + + /// update to matrix elements with/without shadow vectors + void print_nrn_cur_matrix_shadow_update() override; + + + /// reduction to matrix elements from shadow vectors + void print_nrn_cur_matrix_shadow_reduction() override; + + + /// setup method for setting matrix shadow vectors + void print_rhs_d_shadow_variables() override; + + /// if reduction block in nrn_cur required + bool nrn_cur_reduction_loop_required() override; + + + /// backend specific channel instance iteration block start + void print_channel_iteration_block_begin() override; + + + /// backend specific channel instance iteration block end + void print_channel_iteration_block_end() override; + + + /// start of backend namespace + void print_backend_namespace_start() override; + + + /// end of backend namespace + void print_backend_namespace_end() override; + + + /// backend specific global method annotation + void print_global_method_annotation() override; + + + /// backend specific device method annotation + void print_device_method_annotation() override; + + + /// all compute functions for every backend + void codegen_compute_functions() override; + + + /// print wrapper function that calls cuda kernel + void print_wrapper_routine(std::string wraper_function, BlockType type); + + + /// wrapper/caller routines for nrn_state and nrn_cur + void codegen_wrapper_routines(); + + + /// entry point to code generation + void codegen_all() override; + + public: + CodegenCCudaVisitor(std::string mod_file, bool aos) : CodegenCVisitor(mod_file, aos, ".cu") { + init(aos, mod_file); + } + + CodegenCCudaVisitor(std::string mod_file, std::stringstream& stream, bool aos) + : CodegenCVisitor(mod_file, stream, aos) { + init(aos, mod_file); + } +}; + +#endif diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp index 421e5e5f9b..aa93ea8a24 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp @@ -91,8 +91,5 @@ bool CodegenCOmpVisitor::channel_task_dependency_enabled() { bool CodegenCOmpVisitor::block_require_shadow_update(BlockType type) { - if (!channel_task_dependency_enabled() || type == BlockType::Initial) { - return false; - } - return true; + return !(!channel_task_dependency_enabled() || type == BlockType::Initial); } \ No newline at end of file diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 13799edf72..cc7e0eaf0c 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1,6 +1,6 @@ #include <algorithm> -#include <math.h> -#include <time.h> +#include <cmath> +#include <ctime> #include "visitors/rename_visitor.hpp" #include "codegen/base/codegen_helper_visitor.hpp" @@ -203,7 +203,7 @@ std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType * Often top level verbatim blocks use variables with old names. * Here we process if we are processing verbatim block at global scope. */ -std::string CodegenCVisitor::process_verbatim_token(std::string token) { +std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { if (printing_top_verbatim_blocks) { std::string name = token; if (verbatim_variables_mapping.find(token) != verbatim_variables_mapping.end()) { @@ -216,10 +216,7 @@ std::string CodegenCVisitor::process_verbatim_token(std::string token) { bool CodegenCVisitor::ion_variable_struct_required() { - if (optimize_ion_variable_copies() && info.ion_has_write_variable()) { - return true; - } - return false; + return optimize_ion_variable_copies() && info.ion_has_write_variable(); } @@ -242,7 +239,9 @@ void CodegenCVisitor::print_channel_iteration_task_end() { * Depending on the backend, print loop for tiling channel iterations */ void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { - // backend specific, do nothing + // no tiling for cpu backend, just get loop bounds + printer->add_line("int start = 0;"); + printer->add_line("int end = nodecount;"); } @@ -298,10 +297,7 @@ void CodegenCVisitor::print_channel_iteration_block_parallel_hint() { /// if reduction block in nrn_cur required bool CodegenCVisitor::nrn_cur_reduction_loop_required() { - if (channel_task_dependency_enabled() || info.point_process) { - return true; - } - return false; + return channel_task_dependency_enabled() || info.point_process; } @@ -467,6 +463,20 @@ void CodegenCVisitor::print_memory_allocation_routine() { } +std::string CodegenCVisitor::compute_method_name(BlockType type) { + if (type == BlockType::Initial) { + return method_name("nrn_init"); + } + if (type == BlockType::State) { + return method_name("nrn_state"); + } + if (type == BlockType::Equation) { + return method_name("nrn_cur"); + } + throw std::logic_error("compute_method_name not implemented"); +} + + /****************************************************************************************/ /* Non-code-specific printing routines for code generation */ /****************************************************************************************/ @@ -667,9 +677,8 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { std::string CodegenCVisitor::internal_method_arguments() { if (ion_variable_struct_required()) { return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; - } else { - return "id, pnodecount, inst, data, indexes, thread, nt, v"; } + return "id, pnodecount, inst, data, indexes, thread, nt, v"; } @@ -721,7 +730,7 @@ std::pair<std::string, std::string> CodegenCVisitor::write_ion_variable_name(std std::string CodegenCVisitor::conc_write_statement(std::string ion_name, - std::string concentration, + const std::string& concentration, int index) { auto conc_var_name = get_variable_name("ion_" + concentration); auto style_var_name = get_variable_name("style_" + ion_name); @@ -934,18 +943,16 @@ std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_in if (use_instance) { auto stride = (layout == LayoutType::soa) ? dimension : num_float; return "(inst->{}+id*{})"_format(name, stride); - } else { - auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id*{}"_format(position, dimension) : "{}"_format(position); - return "(data+{})"_format(stride); } + auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id*{}"_format(position, dimension) : "{}"_format(position); + return "(data+{})"_format(stride); } else { if (use_instance) { auto stride = (layout == LayoutType::soa) ? "id" : "id*{}"_format(num_float); return "inst->{}[{}]"_format(name, stride); - } else { - auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); - return "data[{}]"_format(stride); } + auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); + return "data[{}]"_format(stride); } // clang-format on } @@ -962,25 +969,22 @@ std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, offset = std::to_string(position); if (use_instance) { return "inst->{}[{}]"_format(name, offset); - } else { - return "indexes[{}]"_format(offset); } + return "indexes[{}]"_format(offset); } else if (symbol.is_integer) { if (use_instance) { offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id*{}+{}"_format(num_int, position); return "inst->{}[{}]"_format(name, offset); - } else { - offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id"; - return "indexes[{}]"_format(offset); } + offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id"; + return "indexes[{}]"_format(offset); } else { offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); if (use_instance) { return "inst->{}[indexes[{}]]"_format(name, offset); - } else { - auto data = symbol.is_vdata ? "_vdata" : "_data"; - return "nt->{}[indexes[{}]]"_format(data, offset); } + auto data = symbol.is_vdata ? "_vdata" : "_data"; + return "nt->{}[indexes[{}]]"_format(data, offset); } // clang-format on } @@ -1213,7 +1217,7 @@ void CodegenCVisitor::print_mechanism_global_structure() { } } - if (info.primes_size) { + if (info.primes_size != 0) { printer->add_line("int* slist1;"); printer->add_line("int* dlist1;"); global_variables.push_back(make_symbol("slist1")); @@ -1379,7 +1383,7 @@ void CodegenCVisitor::print_mechanism_register() { * Register callbacks for thread allocation and cleanup. Note that thread_data_index * represent total number of thread used minus 1 (i.e. index of last thread). */ - if (info.vectorize && info.thread_data_index) { + if (info.vectorize && (info.thread_data_index != 0)) { auto name = get_variable_name("ext_call_thread"); printer->add_line("thread_mem_init({});"_format(name)); printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); @@ -1423,7 +1427,7 @@ void CodegenCVisitor::print_mechanism_register() { if (net_receive_buffering_required()) { printer->add_line("hoc_register_net_receive_buffering(net_buf_receive, mech_type);"); } - if (info.num_net_receive_arguments) { + if (info.num_net_receive_arguments != 0) { printer->add_line("pnt_receive[mech_type] = {};"_format(method_name("net_receive"))); printer->add_line("pnt_receive_size[mech_type] = num_net_receive_args();"); if (info.net_receive_initial_node != nullptr) { @@ -1454,13 +1458,13 @@ void CodegenCVisitor::print_thread_memory_callbacks() { if (info.vectorize && info.derivimplicit_used) { printer->add_line("thread[dith{}()].pval = NULL;"_format(info.derivimplicit_list_num)); } - if (info.vectorize && info.top_local_thread_size) { + if (info.vectorize && (info.top_local_thread_size != 0)) { auto length = info.top_local_thread_size; auto allocation = "(double*)mem_alloc({}, sizeof(double))"_format(length); auto line = "thread[top_local_var_tid()].pval = {};"_format(allocation); printer->add_line(line); } - if (info.thread_var_data_size) { + if (info.thread_var_data_size != 0) { auto length = info.thread_var_data_size; auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); @@ -1486,11 +1490,11 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->add_line("free(thread[dith{}()].pval);"_format(n)); printer->add_line("nrn_destroy_newtonspace(*newtonspace{}(thread));"_format(n)); } - if (info.top_local_thread_size) { + if (info.top_local_thread_size != 0) { auto line = "free(thread[top_local_var_tid()].pval);"; printer->add_line(line); } - if (info.thread_var_data_size) { + if (info.thread_var_data_size != 0) { auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); printer->add_line("if (thread[thread_var_tid()].pval == {}) {}"_format(thread_data, "{")); @@ -1516,7 +1520,7 @@ void CodegenCVisitor::print_mechanism_var_structure() { } for (auto& var : int_variables) { auto name = var.symbol->get_name(); - if (var.is_index | var.is_integer) { + if (var.is_index || var.is_integer) { printer->add_line("{}* {};"_format(int_type, name)); } else { auto type = var.is_vdata ? "void*" : float_data_type(); @@ -1576,7 +1580,7 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line("{0}_global = {1};"_format(info.mod_suffix, allocation)); /// offsets for state variables - if (info.primes_size) { + if (info.primes_size != 0) { auto slist1 = get_variable_name("slist1"); auto dlist1 = get_variable_name("dlist1"); auto n = info.primes_size; @@ -1610,7 +1614,7 @@ void CodegenCVisitor::print_global_variable_setup() { } /// memory for thread member - if (info.vectorize && info.thread_data_index) { + if (info.vectorize && (info.thread_data_index != 0)) { auto n = info.thread_data_index; auto alloc = "(ThreadDatum*) mem_alloc({}, sizeof(ThreadDatum))"_format(n); auto name = get_variable_name("ext_call_thread"); @@ -1737,7 +1741,7 @@ void CodegenCVisitor::print_instance_variable_setup() { for (auto& var : int_variables) { auto name = var.symbol->get_name(); - if (var.is_index | var.is_integer) { + if (var.is_index || var.is_integer) { printer->add_line("inst->{} = ml->pdata;"_format(name)); } else { auto data = var.is_vdata ? "_vdata" : "_data"; @@ -1778,7 +1782,7 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { } /// initial block - if (node) { + if (node != nullptr) { auto block = node->get_statement_block(); print_statement_block(block.get(), false, false); } @@ -1793,14 +1797,7 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { void CodegenCVisitor::print_global_function_common_code(BlockType type) { - std::string method; - if (type == BlockType::Initial) { - method = method_name("nrn_init"); - } else if (type == BlockType::State) { - method = method_name("nrn_state"); - } else if (type == BlockType::Equation) { - method = method_name("nrn_cur"); - } + std::string method = compute_method_name(type); auto args = "NrnThread* nt, Memb_list* ml, int type"; print_global_method_annotation(); @@ -2541,13 +2538,13 @@ void CodegenCVisitor::codegen_all() { print_mechanism_info_array(); print_global_variables_for_hoc(); + codegen_data_structures(); + codegen_common_getters(); print_thread_memory_callbacks(); print_memory_allocation_routine(); - codegen_data_structures(); - print_global_variable_setup(); print_instance_variable_setup(); print_nrn_alloc(); @@ -2563,4 +2560,4 @@ void CodegenCVisitor::visit_program(Program* node) { CodegenBaseVisitor::visit_program(node); rename_function_arguments(); codegen_all(); -} \ No newline at end of file +} diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 2446d11a2a..5554feee48 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -104,7 +104,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// process token in verbatim block for possible variable renaming - std::string process_verbatim_token(std::string token); + std::string process_verbatim_token(const std::string& token); /// rename function/procedure arguments that conflict with default arguments @@ -132,7 +132,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// function call / statement for nrn_wrote_conc - std::string conc_write_statement(std::string ion_name, std::string concentration, int index); + std::string conc_write_statement(std::string ion_name, const std::string& concentration, int index); /// arguments for internally defined functions @@ -163,6 +163,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { std::vector<ShadowUseStatement> shadow_statements; + /// return name of main compute kernels + virtual std::string compute_method_name(BlockType type); + + /// start of coreneuron namespace void print_namespace_start(); @@ -172,11 +176,11 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// start of backend namespace - void print_backend_namespace_start(); + virtual void print_backend_namespace_start(); /// end of backend namespace - void print_backend_namespace_end(); + virtual void print_backend_namespace_end(); /// top header printed in generated code @@ -274,11 +278,11 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// backend specific device method annotation - void print_device_method_annotation(); + virtual void print_device_method_annotation(); /// backend specific global method annotation - void print_global_method_annotation(); + virtual void print_global_method_annotation(); /// call to internal or external function @@ -479,13 +483,14 @@ class CodegenCVisitor : public CodegenBaseVisitor { void codegen_data_structures(); /// all compute functions for every backend - void codegen_compute_functions(); + virtual void codegen_compute_functions(); /// entry point to code generation - void codegen_all(); + virtual void codegen_all(); public: - CodegenCVisitor(std::string mod_file, bool aos) : CodegenBaseVisitor(mod_file, aos) { + CodegenCVisitor(std::string mod_file, bool aos, std::string extension = ".cpp") + : CodegenBaseVisitor(mod_file, aos, extension) { init(aos, mod_file); } diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 0cd5dbf15c..e2b08f3a3f 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -15,7 +15,7 @@ bool CodegenInfo::ion_has_write_variable() { /// if given variable is ion write variable -bool CodegenInfo::is_ion_write_variable(std::string name) { +bool CodegenInfo::is_ion_write_variable(const std::string& name) { for (const auto& ion : ions) { for (auto& var : ion.writes) { if (var == name) { @@ -28,7 +28,7 @@ bool CodegenInfo::is_ion_write_variable(std::string name) { /// if given variable is ion read variable -bool CodegenInfo::is_ion_read_variable(std::string name) { +bool CodegenInfo::is_ion_read_variable(const std::string& name) { for (const auto& ion : ions) { for (auto& var : ion.reads) { if (var == name) { @@ -41,16 +41,13 @@ bool CodegenInfo::is_ion_read_variable(std::string name) { /// if either read or write variable -bool CodegenInfo::is_ion_variable(std::string name) { - if (is_ion_read_variable(name) || is_ion_write_variable(name)) { - return true; - } - return false; +bool CodegenInfo::is_ion_variable(const std::string& name) { + return is_ion_read_variable(name) || is_ion_write_variable(name); } /// if a current -bool CodegenInfo::is_current(std::string name) { +bool CodegenInfo::is_current(const std::string& name) { for (auto& var : currents) { if (var == name) { return true; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index c068a35543..908fd858c0 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -318,16 +318,16 @@ namespace codegen { bool ion_has_write_variable(); /// if given variable is ion write variable - bool is_ion_write_variable(std::string name); + bool is_ion_write_variable(const std::string& name); /// if given variable is ion read variable - bool is_ion_read_variable(std::string name); + bool is_ion_read_variable(const std::string& name); /// if either read or write variable - bool is_ion_variable(std::string name); + bool is_ion_variable(const std::string& name); /// if a current - bool is_current(std::string name); + bool is_current(const std::string& name); }; }; // namespace codegen diff --git a/src/nmodl/codegen/main.cpp b/src/nmodl/codegen/main.cpp index 337a724393..5d46b7f11b 100644 --- a/src/nmodl/codegen/main.cpp +++ b/src/nmodl/codegen/main.cpp @@ -18,6 +18,7 @@ #include "codegen/c/codegen_c_visitor.hpp" #include "codegen/c-openmp/codegen_c_omp_visitor.hpp" #include "codegen/c-openacc/codegen_c_acc_visitor.hpp" +#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" using namespace symtab; @@ -47,7 +48,7 @@ int main(int argc, const char* argv[]) { "accelerator", "Accelerator backend [cuda]", false, - "cuda", + "", "string"); TCLAP::SwitchArg verbose_arg( "", "verbose", @@ -73,6 +74,7 @@ int main(int argc, const char* argv[]) { std::string filename = modfile_arg.getValue(); std::string host_backend = host_arg.getValue(); + std::string accel_backend = accel_arg.getValue(); bool verbose = verbose_arg.getValue(); bool aos_code = aos_layout_arg.getValue(); bool mod_inline = inline_arg.getValue(); @@ -215,6 +217,17 @@ int main(int argc, const char* argv[]) { std::cerr << "Argument Error: Unknown host backend " << host_backend << std::endl; return 1; } + + if (!accel_backend.empty()) { + if (accel_backend == "cuda") { + CodegenCCudaVisitor visitor(mod_filename, aos_code); + visitor.visit_program(ast.get()); + } else { + std::cerr << "Argument Error: Unknown accelerator backend " << accel_backend + << std::endl; + return 1; + } + } } } catch (TCLAP::ArgException& e) { diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index dd8c25845a..c41b39bcda 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -47,7 +47,7 @@ namespace c11 { std::cerr << m << std::endl; } - void Driver::add_token(std::string text) { + void Driver::add_token(const std::string& text) { tokens.push_back(text); // here we will query and look into symbol table or register callback } diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 7318ed4757..18ebbc4cc5 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -61,7 +61,7 @@ namespace c11 { bool parse_string(const std::string& input); bool parse_file(const std::string& filename); void scan_string(std::string& text); - void add_token(std::string); + void add_token(const std::string&); void set_verbose(bool b) { verbose = b; diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 2e5966b37b..a722fa5573 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -25,7 +25,7 @@ namespace diffeq { } } - std::string Driver::solve(std::string equation, std::string method, bool debug) { + std::string Driver::solve(const std::string& equation, std::string method, bool debug) { std::string state, rhs; int order = 0; bool cnexp_possible; @@ -51,7 +51,7 @@ namespace diffeq { } /// \todo : instead of using neuron like api, we need to refactor - bool Driver::cnexp_possible(std::string equation, std::string& solution) { + bool Driver::cnexp_possible(const std::string& equation, std::string& solution) { std::string state, rhs; int order = 0; bool cnexp_possible; diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index 55ec4e6241..ae7e5ec339 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -40,10 +40,10 @@ namespace diffeq { Driver() = default; /// solve equation using provided method - std::string solve(std::string equation, std::string method, bool debug = false); + std::string solve(const std::string& equation, std::string method, bool debug = false); /// check if given equation can be solved using cnexp method - bool cnexp_possible(std::string equation, std::string& solution); + bool cnexp_possible(const std::string& equation, std::string& solution); }; } // namespace diffeq diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 17bef9b149..41dcc40b12 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -195,12 +195,11 @@ namespace symtab { msg += "<" + properties + "> in " + current_symtab->name(); msg += " with one in " + second->get_scope(); throw std::runtime_error(msg); - } else { - std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; - msg += current_symtab->name() + " shadows <" + properties; - msg += "> definition in " + second->get_scope(); - std::cout << msg << std::endl; } + std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; + msg += current_symtab->name() + " shadows <" + properties; + msg += "> definition in " + second->get_scope(); + std::cout << msg << std::endl; } @@ -274,7 +273,7 @@ namespace symtab { update_order(search_symbol, symbol); /// handle update mode insertion - if (update_table == true) { + if (update_table) { return update_mode_insert(symbol); } @@ -365,14 +364,14 @@ namespace symtab { return symtab.get(); } - if (node_symtab == nullptr || update_table == false) { + if (node_symtab == nullptr || !update_table) { name = get_unique_name(name, node, global); auto new_symtab = std::make_shared<SymbolTable>(name, node, global); new_symtab->set_parent_table(current_symtab); if (symtab == nullptr) { symtab = new_symtab; } - if (current_symtab) { + if (current_symtab != nullptr) { current_symtab->insert_table(name, new_symtab); } node_symtab = new_symtab.get(); @@ -405,11 +404,11 @@ namespace symtab { * If there is no symbol table constructed then we toggle mode. */ void ModelSymbolTable::set_mode(bool update_mode) { - if (update_mode == true && symtab == nullptr) { + if (update_mode && symtab == nullptr) { update_mode = false; } update_table = update_mode; - if (update_table == false) { + if (!update_table) { symtab = nullptr; current_symtab = nullptr; } @@ -449,7 +448,7 @@ namespace symtab { auto properties = to_string(symbol->get_properties()); auto status = to_string(symbol->get_status()); auto reads = std::to_string(symbol->get_read_count()); - std::string value = ""; + std::string value; auto sym_value = symbol->get_value(); if (sym_value) { value = std::to_string(*sym_value); @@ -496,4 +495,4 @@ namespace symtab { symtab->print(ss, 0); } -} // namespace symtab \ No newline at end of file +} // namespace symtab diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index f2119cf674..01572cc453 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -228,7 +228,7 @@ namespace symtab { /// re-initialize members to throw away old symbol tables /// this is required as symtab visitor pass runs multiple time - void set_mode(bool mode); + void set_mode(bool update_mode); bool has_breakpoint() const { return breakpoint_exist; diff --git a/src/nmodl/version/version.cpp.in b/src/nmodl/version/version.cpp.in index 244db73770..83030bb902 100644 --- a/src/nmodl/version/version.cpp.in +++ b/src/nmodl/version/version.cpp.in @@ -1,6 +1,4 @@ #include "version/version.h" -using namespace nocmodl; - -const std::string version::GIT_REVISION = "@GIT_REVISION@"; -const std::string version::NOCMODL_VERSION = "@PROJECT_VERSION@"; +const std::string nocmodl::version::GIT_REVISION = "@GIT_REVISION@"; +const std::string nocmodl::version::NOCMODL_VERSION = "@PROJECT_VERSION@"; diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 2b9837d98e..d2922b4d56 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -424,7 +424,7 @@ bool PerfVisitor::is_constant_variable(const std::shared_ptr<symtab::Symbol>& sy * read/write count. Also update ops count in current block. */ void PerfVisitor::update_memory_ops(const std::string& name) { - if (start_measurement == false || current_symtab == nullptr) { + if (!start_measurement || current_symtab == nullptr) { return; } diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 404168a91d..87df9eaa6d 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -1,5 +1,7 @@ #include "visitors/var_usage_visitor.hpp" +#include <utility> + /// rename matching variable void VarUsageVisitor::visit_name(ast::Name* node) { std::string name = node->get_name(); @@ -10,7 +12,7 @@ void VarUsageVisitor::visit_name(ast::Name* node) { bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { used = false; - var_name = name; + var_name = std::move(name); node->visit_children(this); return used; } \ No newline at end of file diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index f9980f9bae..629db261d7 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -121,7 +121,7 @@ SCENARIO("Parser test for invalid NMODL grammar constructs") { std::string solve_construct(const std::string& equation, std::string method) { diffeq::Driver driver; - auto solution = driver.solve(equation, method); + auto solution = driver.solve(equation, std::move(method)); return solution; } diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 2721ab7ed5..8d6833ac1b 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -232,7 +232,7 @@ SCENARIO("Symbol table operations") { with = NmodlInfo::range_var; without = NmodlInfo::range_var; result = table->get_variables(with, without); - REQUIRE(result.size() == 0); + REQUIRE(result.empty()); } } } From ddbdf6db76bc6607409874a2c9ef6a643f17af5d Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 15 Apr 2018 23:41:15 +0200 Subject: [PATCH 067/871] Added restrict qualifier and const qualifier to variables Change-Id: I96d611c1d2ff42c3c65a4863a7d34a05b3157cf6 NMODL Repo SHA: BlueBrain/nmodl@da896fd561a69135b0913ff0891db9ef9c29c960 --- .../codegen/base/codegen_base_visitor.cpp | 10 +++ .../codegen/base/codegen_base_visitor.hpp | 3 + src/nmodl/codegen/c/codegen_c_visitor.cpp | 88 +++++++++++++------ src/nmodl/codegen/c/codegen_c_visitor.hpp | 15 +++- src/nmodl/codegen/main.cpp | 5 ++ 5 files changed, 95 insertions(+), 26 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index bc3d517586..5d2eb0a647 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -639,11 +639,19 @@ std::vector<SymbolType> CodegenBaseVisitor::get_float_variables() { * - is_vdata (false) * - is_index (false * - is_integer (false) + * + * Which variables are constant qualified? + * + * - node area is read only + * - read ion variables are read only + * - style_ionname is index / offset */ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { std::vector<IndexVariableInfo> variables; if (info.point_process) { variables.emplace_back(make_symbol(node_area)); + variables.back().is_constant = true; + /// note that this variable is not printed in neuron implementation if (info.artificial_cell) { variables.emplace_back(make_symbol("point_process"), true); } else { @@ -655,6 +663,7 @@ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { bool need_style = false; for (auto& var : ion.reads) { variables.emplace_back(make_symbol("ion_" + var)); + variables.back().is_constant = true; } for (auto& var : ion.writes) { variables.emplace_back(make_symbol("ion_" + var)); @@ -667,6 +676,7 @@ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { } if (need_style) { variables.emplace_back(make_symbol("style_" + ion.name), false, true); + variables.back().is_constant = true; } } diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index e3f4f28be5..252a872ced 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -76,6 +76,9 @@ struct IndexVariableInfo { /// is printed as array accesses bool is_integer = false; + /// if the variable is qualified as constant (this is propery of IndexVariable) + bool is_constant = false; + IndexVariableInfo(std::shared_ptr<symtab::Symbol> symbol, bool is_vdata = false, bool is_index = false, diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index cc7e0eaf0c..92c1732a34 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -220,6 +220,23 @@ bool CodegenCVisitor::ion_variable_struct_required() { } +/// if variable is qualified as constant +bool CodegenCVisitor::is_constant_variable(std::string name) { + auto symbol = program_symtab->lookup_in_scope(name); + bool is_constant = false; + if (symbol != nullptr) { + if (symbol->has_properties(NmodlInfo::read_ion_var)) { + is_constant = true; + } + if (symbol->has_properties(NmodlInfo::param_assign) && symbol->get_write_count() == 0) { + is_constant = true; + } + } + return is_constant; +} + + + /****************************************************************************************/ /* Routines must be overloaded in backend */ /****************************************************************************************/ @@ -477,6 +494,17 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) { } +// note extra empty space for pretty-printing if we skip the symbol +std::string CodegenCVisitor::k_restrict() { + return "__restrict__ "; +} + + +std::string CodegenCVisitor::k_const() { + return "const "; +} + + /****************************************************************************************/ /* Non-code-specific printing routines for code generation */ /****************************************************************************************/ @@ -688,8 +716,8 @@ std::string CodegenCVisitor::internal_method_parameters() { ion_var_arg = " IonCurVar& ionvar,"; } return "int id, int pnodecount, {}* inst,{} double* data, " - "Datum* indexes, ThreadDatum* thread, " - "NrnThread* nt, double v"_format(instance_struct(), ion_var_arg); + "{}Datum* indexes, ThreadDatum* thread, " + "NrnThread* nt, double v"_format(instance_struct(), ion_var_arg, k_const()); } @@ -799,6 +827,7 @@ void CodegenCVisitor::print_memory_layout_getter() { void CodegenCVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); + print_device_method_annotation(); printer->add_line("static inline int first_pointer_var_index() {"); printer->add_line(" return {};"_format(info.first_pointer_var_index)); printer->add_line("}"); @@ -807,11 +836,13 @@ void CodegenCVisitor::print_first_pointer_var_index_getter() { void CodegenCVisitor::print_num_variable_getter() { printer->add_newline(2); + print_device_method_annotation(); printer->add_line("static inline int num_float_variable() {"); printer->add_line(" return {};"_format(num_float_variable())); printer->add_line("}"); printer->add_newline(2); + print_device_method_annotation(); printer->add_line("static inline int num_int_variable() {"); printer->add_line(" return {};"_format(num_int_variable())); printer->add_line("}"); @@ -823,6 +854,7 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { return; } printer->add_newline(2); + print_device_method_annotation(); printer->add_line("static inline int num_net_receive_args() {"); printer->add_line(" return {};"_format(info.num_net_receive_arguments)); printer->add_line("}"); @@ -831,6 +863,7 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { void CodegenCVisitor::print_mech_type_getter() { printer->add_newline(2); + print_device_method_annotation(); printer->add_line("static inline int get_mech_type() {"); printer->add_line(" return {};"_format(get_variable_name("mech_type"))); printer->add_line("}"); @@ -839,6 +872,7 @@ void CodegenCVisitor::print_mech_type_getter() { void CodegenCVisitor::print_memb_list_getter() { printer->add_newline(2); + print_device_method_annotation(); printer->add_line("static inline Memb_list* get_memb_list(NrnThread* nt) {"); printer->add_line(" if (nt->_ml_list == NULL) {"); printer->add_line(" return NULL;"); @@ -926,13 +960,11 @@ void CodegenCVisitor::print_thread_getters() { } - /****************************************************************************************/ /* Routines for returning variable name */ /****************************************************************************************/ - std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_instance) { auto name = symbol->get_name(); auto dimension = symbol->get_length(); @@ -1184,14 +1216,12 @@ void CodegenCVisitor::print_mechanism_global_structure() { global_variables.push_back(symbol); } - if (info.vectorize) { - printer->add_line("ThreadDatum* ext_call_thread;"); - global_variables.push_back(make_symbol("ext_call_thread")); - } - printer->add_line("int reset;"); global_variables.push_back(make_symbol("reset")); + printer->add_line("int mech_type;"); + global_variables.push_back(make_symbol("mech_type")); + auto& globals = info.global_variables; auto& constants = info.constant_variables; @@ -1228,14 +1258,17 @@ void CodegenCVisitor::print_mechanism_global_structure() { } } - printer->add_line("int mech_type;"); - global_variables.push_back(make_symbol("mech_type")); + if (info.vectorize) { + printer->add_line("ThreadDatum* {}ext_call_thread;"_format(k_restrict())); + global_variables.push_back(make_symbol("ext_call_thread")); + } + printer->decrease_indent(); printer->add_line("};"); printer->add_newline(1); printer->add_line("/** holds object of global variable */"); - printer->add_line("{}* {}_global;"_format(global_struct(), info.mod_suffix)); + printer->add_line("{}* {}{}_global;"_format(global_struct(), k_restrict(), info.mod_suffix)); } @@ -1516,21 +1549,23 @@ void CodegenCVisitor::print_mechanism_var_structure() { printer->start_block("struct {} "_format(instance_struct())); for (auto& var : float_variables) { auto name = var->get_name(); - printer->add_line("{}* {};"_format(float_type, name)); + auto qualifier = is_constant_variable(name) ? k_const() : ""; + printer->add_line("{}{}* {}{};"_format(qualifier, float_type, k_restrict(), name)); } for (auto& var : int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { - printer->add_line("{}* {};"_format(int_type, name)); + printer->add_line("{}{}* {}{};"_format(k_const(), int_type, k_restrict(), name)); } else { + auto qualifier = var.is_constant ? k_const() : ""; auto type = var.is_vdata ? "void*" : float_data_type(); - printer->add_line("{}* {};"_format(type, name)); + printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); } } if (channel_task_dependency_enabled()) { for (auto& var : shadow_variables) { auto name = var->get_name(); - printer->add_line("{}* {};"_format(float_type, name)); + printer->add_line("{}* {}{};"_format(float_type, k_restrict(), name)); } } printer->end_block(); @@ -1682,6 +1717,7 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_newline(); } + void CodegenCVisitor::print_shadow_vector_setup() { printer->add_newline(2); printer->add_line("/** allocate and initialize shadow vector */"); @@ -1805,23 +1841,25 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { print_kernel_data_present_annotation_block_begin(); printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); - printer->add_line("int* node_index = ml->nodeindices;"); - printer->add_line("double* data = ml->data;"); - printer->add_line("double* voltage = nt->_actual_v;"); + printer->add_line("{}int* {}node_index = ml->nodeindices;"_format(k_const(), k_restrict())); + printer->add_line("double* {}data = ml->data;"_format(k_restrict())); + printer->add_line("{}double* {}voltage = nt->_actual_v;"_format(k_const(), k_restrict())); if (type == BlockType::Equation) { - printer->add_line("double* vec_rhs = nt->_actual_rhs;"); - printer->add_line("double* vec_d = nt->_actual_d;"); + printer->add_line("double* {} vec_rhs = nt->_actual_rhs;"_format(k_restrict())); + printer->add_line("double* {} vec_d = nt->_actual_d;"_format(k_restrict())); print_rhs_d_shadow_variables(); } - printer->add_line("Datum* indexes = ml->pdata;"); - printer->add_line("ThreadDatum* thread = ml->_thread;"); + printer->add_line("{}Datum* {}indexes = ml->pdata;"_format(k_const(), k_restrict())); + printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(k_restrict())); if (type == BlockType::Initial) { printer->add_newline(); printer->add_line("setup_instance(nt, ml);"); } - printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + // clang-format off + printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), k_restrict())); + // clang-format on printer->add_newline(1); } @@ -2220,7 +2258,6 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { } - /****************************************************************************************/ /* Print nrn_state routine */ /****************************************************************************************/ @@ -2319,6 +2356,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { auto args = internal_method_parameters(); auto block = node->get_statement_block().get(); printer->add_newline(2); + print_device_method_annotation(); printer->start_block("static inline double nrn_current({})"_format(args)); printer->add_line("double current = 0.0;"); print_statement_block(block, false, false); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 5554feee48..b9e2176713 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -132,7 +132,9 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// function call / statement for nrn_wrote_conc - std::string conc_write_statement(std::string ion_name, const std::string& concentration, int index); + std::string conc_write_statement(std::string ion_name, + const std::string& concentration, + int index); /// arguments for internally defined functions @@ -167,6 +169,13 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual std::string compute_method_name(BlockType type); + /// retstrict keyword + virtual std::string k_restrict(); + + /// const keyword + virtual std::string k_const(); + + /// start of coreneuron namespace void print_namespace_start(); @@ -213,6 +222,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual bool nrn_cur_reduction_loop_required(); + /// if variable is qualified as constant + virtual bool is_constant_variable(std::string name); + + /// char array that has mechanism information (to be registered with coreneuron) void print_mechanism_info_array(); diff --git a/src/nmodl/codegen/main.cpp b/src/nmodl/codegen/main.cpp index 5d46b7f11b..251311add2 100644 --- a/src/nmodl/codegen/main.cpp +++ b/src/nmodl/codegen/main.cpp @@ -190,6 +190,11 @@ int main(int argc, const char* argv[]) { v.visit_program(ast.get()); } + { + PerfVisitor v(mod_filename + ".perf.json"); + v.visit_program(ast.get()); + } + { std::stringstream stream; auto symtab = ast->get_model_symbol_table(); From da2bc56ba9f99b7538ed3b974e2e427e157593fd Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 17 Apr 2018 10:42:37 +0200 Subject: [PATCH 068/871] GPU backens mark parameters const even if write count > 0 Change-Id: Iaa96686291f0f5a437ac082c08e61f169c241f0a NMODL Repo SHA: BlueBrain/nmodl@773dc3b753d2860faeb3a74ef8cf61949c226922 --- .../codegen/c-cuda/codegen_c_cuda_visitor.cpp | 22 +++++++++++++++++++ .../codegen/c-cuda/codegen_c_cuda_visitor.hpp | 2 ++ src/nmodl/codegen/c/codegen_c_visitor.cpp | 8 ++++++- 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp index 542b97a12c..dc4db8e0ea 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp @@ -1,9 +1,11 @@ #include <fmt/format.h> +#include "symtab/symbol_table.hpp" #include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" #include "utils/string_utils.hpp" using namespace fmt::literals; +using namespace symtab; /****************************************************************************************/ @@ -11,6 +13,26 @@ using namespace fmt::literals; /****************************************************************************************/ +/** + * As initial block is/can be executed on c/cpu backend, gpu/cuda + * backend can mark the parameter as constant even if they have + * write count > 0 (typically due to initial block). + */ +bool CodegenCCudaVisitor::is_constant_variable(std::string name) { + auto symbol = program_symtab->lookup_in_scope(name); + bool is_constant = false; + if (symbol != nullptr) { + if (symbol->has_properties(NmodlInfo::read_ion_var)) { + is_constant = true; + } + if (symbol->has_properties(NmodlInfo::param_assign)) { + is_constant = true; + } + } + return is_constant; +} + + std::string CodegenCCudaVisitor::compute_method_name(BlockType type) { if (type == BlockType::Initial) { return method_name("nrn_init"); diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp index 238004301d..9f65ae8ef3 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp @@ -19,6 +19,8 @@ class CodegenCCudaVisitor : public CodegenCVisitor { /// name of the code generation backend std::string backend_name() override; + /// if variable is qualified as constant + bool is_constant_variable(std::string name) override; /// return name of main compute kernels std::string compute_method_name(BlockType type) override; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 92c1732a34..d2c9e57b04 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -220,7 +220,13 @@ bool CodegenCVisitor::ion_variable_struct_required() { } -/// if variable is qualified as constant +/** + * Check if variable is qualified as constant + * + * This can be override in the backend. For example, parameters can be constant + * except in INITIAL block where they are set to 0. As initial block is/can be + * executed on c/cpu backend, gpu/cuda backend can mark the parameter as constnat. + */ bool CodegenCVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; From a37120aba250348655ab36fd4037506cf5c875f5 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 18 Apr 2018 14:38:55 +0200 Subject: [PATCH 069/871] Bug fix : when conductance keyword is specified, ionic current variable names were not used correctly. Change-Id: I43e21d0b222e4728c3bead7728f8b804893d5e4d NMODL Repo SHA: BlueBrain/nmodl@ae2a24327ead47b2e32c11d8dc85589f0ca1d435 --- src/nmodl/codegen/c/codegen_c_visitor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index d2c9e57b04..3fb0ab0fd9 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -2382,7 +2382,8 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { if (!info.currents.empty()) { std::string sum; for (const auto& current : info.currents) { - sum += breakpoint_current(current); + auto var = breakpoint_current(current); + sum += get_variable_name(var); if (¤t != &info.currents.back()) { sum += "+"; } @@ -2392,7 +2393,8 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { if (!info.conductances.empty()) { std::string sum; for (const auto& conductance : info.conductances) { - sum += breakpoint_current(conductance.variable); + auto var = breakpoint_current(conductance.variable); + sum += get_variable_name(var); if (&conductance != &info.conductances.back()) { sum += "+"; } From 46a8ae06580b6d0c35737c1d4680360391649dfa Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 18 Apr 2018 16:02:53 +0200 Subject: [PATCH 070/871] Bug fix: function renaming was happening even if argument was not exist for function or procedure. Add extra argument "--datatype float" to change all floating variables from double to float. (wip) Change-Id: I0dde3d139080069b8c5a4c4d78844f62245d2fd6 NMODL Repo SHA: BlueBrain/nmodl@414f077c0685ac789d86c9c31cf3927f8269b33f --- .../codegen/base/codegen_base_visitor.hpp | 49 ++++++++++++++++--- .../codegen/c-cuda/codegen_c_cuda_visitor.hpp | 12 +++-- .../c-openacc/codegen_c_acc_visitor.hpp | 12 +++-- .../c-openmp/codegen_c_omp_visitor.hpp | 12 +++-- src/nmodl/codegen/c/codegen_c_visitor.cpp | 11 +++-- src/nmodl/codegen/c/codegen_c_visitor.hpp | 16 +++--- src/nmodl/codegen/main.cpp | 21 ++++++-- 7 files changed, 97 insertions(+), 36 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 252a872ced..c8c8e2b945 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -130,6 +130,9 @@ class CodegenBaseVisitor : public AstVisitor { protected: using SymbolType = std::shared_ptr<symtab::Symbol>; + /// data type of floating point variables + std::string float_type = "double"; + /// memory layout for code generation LayoutType layout; @@ -201,7 +204,7 @@ class CodegenBaseVisitor : public AstVisitor { /// data type for floating point elements std::string float_data_type() { - return "double"; + return float_type; } @@ -373,13 +376,17 @@ class CodegenBaseVisitor : public AstVisitor { std::vector<SymbolType> get_shadow_variables(); - /// vector elements from all types + /// print vector elements (all types) template <typename T> void print_vector_elements(const std::vector<T>& elements, std::string separator, std::string prefix = ""); + /// check if function or procedure has argument with same name + template <typename T> + bool has_argument_with_name(const T& node, std::string name); + /// any statement block in nmodl with option to (not) print braces void print_statement_block(ast::StatementBlock* node, bool open_brace = true, @@ -387,20 +394,27 @@ class CodegenBaseVisitor : public AstVisitor { /// common init for constructors - void init(bool aos, std::string filename) { - layout = aos ? LayoutType::aos : LayoutType::soa; + void init(std::string filename, bool aos, std::string type) { mod_file_suffix = filename; + layout = aos ? LayoutType::aos : LayoutType::soa; + float_type = type; } public: - CodegenBaseVisitor(std::string mod_file, bool aos, std::string extension = ".cpp") + CodegenBaseVisitor(std::string mod_file, + bool aos, + std::string float_type, + std::string extension = ".cpp") : printer(new CodePrinter(mod_file + extension)) { - init(aos, mod_file); + init(mod_file, aos, float_type); } - CodegenBaseVisitor(std::string mod_file, std::stringstream& stream, bool aos) + CodegenBaseVisitor(std::string mod_file, + std::stringstream& stream, + bool aos, + std::string float_type) : printer(new CodePrinter(stream)) { - init(aos, mod_file); + init(mod_file, aos, float_type); } virtual void visit_unit(ast::Unit* node) override; @@ -444,4 +458,23 @@ void CodegenBaseVisitor::print_vector_elements(const std::vector<T>& elements, } +/** + * Check if function or procedure node has argument with given name + * + * @tparam T Node type (either procedure or function) + * @param node AST node (either procedure or function) + * @param name Name of argument + * @return True if argument with name exist + */ +template <typename T> +bool has_argument_of_name(const T& node, std::string name) { + auto arguments = node->get_arguments(); + for (const auto& argument : arguments) { + if (argument->get_name() == name) { + return true; + } + } + return false; +} + #endif diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp index 9f65ae8ef3..901759ab5a 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp @@ -85,13 +85,15 @@ class CodegenCCudaVisitor : public CodegenCVisitor { void codegen_all() override; public: - CodegenCCudaVisitor(std::string mod_file, bool aos) : CodegenCVisitor(mod_file, aos, ".cu") { - init(aos, mod_file); + CodegenCCudaVisitor(std::string mod_file, bool aos, std::string float_type) + : CodegenCVisitor(mod_file, aos, float_type, ".cu") { } - CodegenCCudaVisitor(std::string mod_file, std::stringstream& stream, bool aos) - : CodegenCVisitor(mod_file, stream, aos) { - init(aos, mod_file); + CodegenCCudaVisitor(std::string mod_file, + std::stringstream& stream, + bool aos, + std::string float_type) + : CodegenCVisitor(mod_file, stream, aos, float_type) { } }; diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp index 748e4c82a6..d7c0e73732 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp @@ -58,13 +58,15 @@ class CodegenCAccVisitor : public CodegenCVisitor { public: - CodegenCAccVisitor(std::string mod_file, bool aos) : CodegenCVisitor(mod_file, aos) { - init(aos, mod_file); + CodegenCAccVisitor(std::string mod_file, bool aos, std::string float_type) + : CodegenCVisitor(mod_file, aos, float_type) { } - CodegenCAccVisitor(std::string mod_file, std::stringstream& stream, bool aos) - : CodegenCVisitor(mod_file, stream, aos) { - init(aos, mod_file); + CodegenCAccVisitor(std::string mod_file, + std::stringstream& stream, + bool aos, + std::string float_type) + : CodegenCVisitor(mod_file, stream, aos, float_type) { } }; diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp index 3124148b21..d738f1aa84 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp @@ -55,13 +55,15 @@ class CodegenCOmpVisitor : public CodegenCVisitor { public: - CodegenCOmpVisitor(std::string mod_file, bool aos) : CodegenCVisitor(mod_file, aos) { - init(aos, mod_file); + CodegenCOmpVisitor(std::string mod_file, bool aos, std::string float_type) + : CodegenCVisitor(mod_file, aos, float_type) { } - CodegenCOmpVisitor(std::string mod_file, std::stringstream& stream, bool aos) - : CodegenCVisitor(mod_file, stream, aos) { - init(aos, mod_file); + CodegenCOmpVisitor(std::string mod_file, + std::stringstream& stream, + bool aos, + std::string float_type) + : CodegenCVisitor(mod_file, stream, aos, float_type) { } }; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 3fb0ab0fd9..2289611257 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -613,10 +613,14 @@ void CodegenCVisitor::rename_function_arguments() { stringutils::trim(arg); RenameVisitor v(arg, "arg_" + arg); for (const auto& function : info.functions) { - function->accept(&v); + if (has_argument_of_name(function, arg)) { + function->accept(&v); + } } for (const auto& function : info.procedures) { - function->accept(&v); + if (has_argument_of_name(function, arg)) { + function->accept(&v); + } } } } @@ -1128,9 +1132,10 @@ void CodegenCVisitor::print_backend_info() { void CodegenCVisitor::print_standard_includes() { printer->add_newline(); + printer->add_line("#include <math.h>"); printer->add_line("#include <stdio.h>"); printer->add_line("#include <stdlib.h>"); - printer->add_line("#include <math.h>"); + printer->add_line("#include <string.h>"); } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index b9e2176713..564ad484fb 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -502,14 +502,18 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual void codegen_all(); public: - CodegenCVisitor(std::string mod_file, bool aos, std::string extension = ".cpp") - : CodegenBaseVisitor(mod_file, aos, extension) { - init(aos, mod_file); + CodegenCVisitor(std::string mod_file, + bool aos, + std::string float_type, + std::string extension = ".cpp") + : CodegenBaseVisitor(mod_file, aos, float_type, extension) { } - CodegenCVisitor(std::string mod_file, std::stringstream& stream, bool aos) - : CodegenBaseVisitor(mod_file, stream, aos) { - init(aos, mod_file); + CodegenCVisitor(std::string mod_file, + std::stringstream& stream, + bool aos, + std::string float_type) + : CodegenBaseVisitor(mod_file, stream, aos, float_type) { } virtual void visit_function_call(ast::FunctionCall* node) override; diff --git a/src/nmodl/codegen/main.cpp b/src/nmodl/codegen/main.cpp index 251311add2..0e2a9e75c9 100644 --- a/src/nmodl/codegen/main.cpp +++ b/src/nmodl/codegen/main.cpp @@ -50,6 +50,12 @@ int main(int argc, const char* argv[]) { false, "", "string"); + TCLAP::ValueArg<std::string> dtype_arg( "", + "datatype", + "Data type [float, double]", + false, + "double", + "string"); TCLAP::SwitchArg verbose_arg( "", "verbose", "Enable verbose output", @@ -70,11 +76,13 @@ int main(int argc, const char* argv[]) { cmd.add(inline_arg); cmd.add(host_arg); cmd.add(accel_arg); + cmd.add(dtype_arg); cmd.parse(argc, argv); std::string filename = modfile_arg.getValue(); std::string host_backend = host_arg.getValue(); std::string accel_backend = accel_arg.getValue(); + std::string data_type = dtype_arg.getValue(); bool verbose = verbose_arg.getValue(); bool aos_code = aos_layout_arg.getValue(); bool mod_inline = inline_arg.getValue(); @@ -209,14 +217,19 @@ int main(int argc, const char* argv[]) { } { + if (data_type != "double" && data_type != "float") { + std::cerr << "Argument Error: Unknown data type " << data_type << std::endl; + return 1; + } + if (host_backend == "c") { - CodegenCVisitor visitor(mod_filename, aos_code); + CodegenCVisitor visitor(mod_filename, aos_code, data_type); visitor.visit_program(ast.get()); } else if (host_backend == "c-openmp") { - CodegenCOmpVisitor visitor(mod_filename, aos_code); + CodegenCOmpVisitor visitor(mod_filename, aos_code, data_type); visitor.visit_program(ast.get()); } else if (host_backend == "c-openacc") { - CodegenCAccVisitor visitor(mod_filename, aos_code); + CodegenCAccVisitor visitor(mod_filename, aos_code, data_type); visitor.visit_program(ast.get()); } else { std::cerr << "Argument Error: Unknown host backend " << host_backend << std::endl; @@ -225,7 +238,7 @@ int main(int argc, const char* argv[]) { if (!accel_backend.empty()) { if (accel_backend == "cuda") { - CodegenCCudaVisitor visitor(mod_filename, aos_code); + CodegenCCudaVisitor visitor(mod_filename, aos_code, data_type); visitor.visit_program(ast.get()); } else { std::cerr << "Argument Error: Unknown accelerator backend " << accel_backend From 3aaf1825b4e9ddef0c7f965e3a6bae3b3cb57ad4 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 18 Apr 2018 23:18:51 +0200 Subject: [PATCH 071/871] Necessary code to support float data type - Copy double vectors to new type (e.g. float) if given RANGE variable is convertible. - Tested with ring test and test pass in coreneuron. (gap test fails mostly because of vgap variable which is used in gap junction voltage transfer). - Changes from clang-tidy. Change-Id: I908dde71a8146d3a0336f9bb5f2e069432b404cd NMODL Repo SHA: BlueBrain/nmodl@332aa35ada6eac3b32b2018d3ff13d8a247c9c14 --- .../codegen/base/codegen_base_visitor.cpp | 9 ++ .../codegen/base/codegen_base_visitor.hpp | 16 ++- src/nmodl/codegen/c/codegen_c_visitor.cpp | 114 +++++++++++++++--- src/nmodl/codegen/c/codegen_c_visitor.hpp | 20 ++- 4 files changed, 133 insertions(+), 26 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 5d2eb0a647..42ead9ef2b 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -346,6 +346,15 @@ bool CodegenBaseVisitor::net_receive_required() { } +/** + * When floating point data type is not default (i.e. double) then we + * have to copy old array to new type (for range variables). + */ +bool CodegenBaseVisitor::range_variable_setup_required() { + return default_float_data_type() != float_data_type(); +} + + bool CodegenBaseVisitor::state_variable(std::string name) { // clang-format off auto result = std::find_if(info.state_vars.begin(), diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index c8c8e2b945..8d115287e0 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -202,14 +202,20 @@ class CodegenBaseVisitor : public AstVisitor { } - /// data type for floating point elements + /// default data type for floating point elements + std::string default_float_data_type() { + return "double"; + } + + + /// data type for floating point elements specified on command line std::string float_data_type() { return float_type; } - /// data type for ineteger (offset) elemenets - std::string int_data_type() { + /// default data type for integer (offset) elements + std::string default_int_data_type() { return "int"; } @@ -281,6 +287,10 @@ class CodegenBaseVisitor : public AstVisitor { bool net_send_buffer_required(); + /// check if setup_range_variable function is required + bool range_variable_setup_required(); + + /// check if net_receive node exist bool net_receive_exist(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 2289611257..4c358b8130 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -323,6 +323,10 @@ bool CodegenCVisitor::nrn_cur_reduction_loop_required() { return channel_task_dependency_enabled() || info.point_process; } +/// if shadow vectors required +bool CodegenCVisitor::shadow_vector_setup_required() { + return (channel_task_dependency_enabled() && !shadow_variables.empty()); +} /* @@ -666,7 +670,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = true; auto name = node->get_name(); auto return_var = "ret_" + name; - auto type = float_data_type() + " "; + auto type = default_float_data_type() + " "; /// first rename return variable name auto block = node->get_statement_block().get(); @@ -988,14 +992,13 @@ std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_in } auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id*{}"_format(position, dimension) : "{}"_format(position); return "(data+{})"_format(stride); - } else { - if (use_instance) { - auto stride = (layout == LayoutType::soa) ? "id" : "id*{}"_format(num_float); - return "inst->{}[{}]"_format(name, stride); - } - auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); - return "data[{}]"_format(stride); } + if (use_instance) { + auto stride = (layout == LayoutType::soa) ? "id" : "id*{}"_format(num_float); + return "inst->{}[{}]"_format(name, stride); + } + auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); + return "data[{}]"_format(stride); // clang-format on } @@ -1013,7 +1016,8 @@ std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, return "inst->{}[{}]"_format(name, offset); } return "indexes[{}]"_format(offset); - } else if (symbol.is_integer) { + } + if (symbol.is_integer) { if (use_instance) { offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id*{}+{}"_format(num_int, position); return "inst->{}[{}]"_format(name, offset); @@ -1169,7 +1173,7 @@ void CodegenCVisitor::print_coreneuron_includes() { * same for some variables to keep same code as neuron. */ void CodegenCVisitor::print_mechanism_global_structure() { - auto float_type = float_data_type(); + auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); printer->add_line("struct {} {}"_format(global_struct(), "{")); @@ -1553,15 +1557,16 @@ void CodegenCVisitor::print_thread_memory_callbacks() { void CodegenCVisitor::print_mechanism_var_structure() { - auto float_type = float_data_type(); - auto int_type = int_data_type(); + auto float_type = default_float_data_type(); + auto int_type = default_int_data_type(); printer->add_newline(2); printer->add_line("/** all mechanism instance variables */"); printer->start_block("struct {} "_format(instance_struct())); for (auto& var : float_variables) { auto name = var->get_name(); + auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? k_const() : ""; - printer->add_line("{}{}* {}{};"_format(qualifier, float_type, k_restrict(), name)); + printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); } for (auto& var : int_variables) { auto name = var.symbol->get_name(); @@ -1569,7 +1574,7 @@ void CodegenCVisitor::print_mechanism_var_structure() { printer->add_line("{}{}* {}{};"_format(k_const(), int_type, k_restrict(), name)); } else { auto qualifier = var.is_constant ? k_const() : ""; - auto type = var.is_vdata ? "void*" : float_data_type(); + auto type = var.is_vdata ? "void*" : default_float_data_type(); printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); } } @@ -1594,12 +1599,12 @@ void CodegenCVisitor::print_ion_variables_structure() { printer->start_block("struct IonCurVar"); for (auto& ion : info.ions) { for (auto& var : ion.writes) { - printer->add_line("{} {};"_format(float_data_type(), var)); + printer->add_line("{} {};"_format(default_float_data_type(), var)); } } for (auto& var : info.currents) { if (!info.is_ion_variable(var)) { - printer->add_line("{} {};"_format(float_data_type(), var)); + printer->add_line("{} {};"_format(default_float_data_type(), var)); } } printer->end_block(); @@ -1738,7 +1743,7 @@ void CodegenCVisitor::print_shadow_vector_setup() { printer->add_line("int nodecount = ml->nodecount;"); for (auto& var : shadow_variables) { auto name = var->get_name(); - auto type = float_data_type(); + auto type = default_float_data_type(); auto allocation = "({0}*) mem_alloc(nodecount, sizeof({0}))"_format(type); printer->add_line("inst->{0} = {1};"_format(name, allocation)); } @@ -1761,8 +1766,54 @@ void CodegenCVisitor::print_shadow_vector_setup() { } +void CodegenCVisitor::print_setup_range_variable() { + auto type = float_data_type(); + printer->add_newline(2); + printer->add_line("/** allocate and setup array for range variable */"); + printer->start_block("static inline {}* setup_range_variable(double* variable, int n) "_format(type)); + printer->add_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));"_format(type)); + printer->add_line("for(size_t i = 0; i < n; i++) {"); + printer->add_line(" data[i] = variable[i];"); + printer->add_line("}"); + printer->add_line("return data;"); + printer->end_block(); + printer->add_newline(); +} + + +/** + * Floating point type for the given range variable (symbol) + * + * If floating point type like "float" is specified on command line then + * we can't turn all variables to new type. This is because certain variables + * are pointers to internal variables (e.g. ions). Hence, we check if given + * variable can be safely converted to new type. If so, return new type. + * + * @param symbol Symbol for the range variable + * @return Floating point type (float/double) + */ +std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) { + /// clang-format off + auto with = NmodlInfo::read_ion_var + | NmodlInfo::write_ion_var + | NmodlInfo::pointer_var + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::extern_neuron_variable; + /// clang-format on + bool need_default_type = symbol->has_properties(with); + if (need_default_type) { + return default_float_data_type(); + } + return float_data_type(); +} + + void CodegenCVisitor::print_instance_variable_setup() { - if (channel_task_dependency_enabled() && !shadow_variables.empty()) { + if (range_variable_setup_required()) { + print_setup_range_variable(); + } + + if (shadow_vector_setup_required()) { print_shadow_vector_setup(); } printer->add_newline(2); @@ -1781,8 +1832,17 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("Datum* indexes = ml->pdata;"); int id = 0; + std::vector<std::string> variables_to_free; for (auto& var : float_variables) { - printer->add_line("inst->{} = ml->data + {}{};"_format(var->get_name(), id, stride)); + auto name = var->get_name(); + auto default_type = default_float_data_type(); + auto range_var_type = get_range_var_float_type(var); + if (default_type == range_var_type) { + printer->add_line("inst->{} = ml->data+{}{};"_format(name, id, stride)); + } else { + printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format(name, id, stride)); + variables_to_free.push_back(name); + } id += var->get_length(); } @@ -1799,6 +1859,22 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("ml->instance = (void*) inst;"); printer->end_block(); printer->add_newline(); + + if (range_variable_setup_required()) { + printer->add_newline(2); + printer->add_line("/** cleanup mechanism instance variables */"); + printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); + if (variables_to_free.empty()) { + printer->add_line("// do nothing"); + } else { + printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + for (auto &var : variables_to_free) { + printer->add_line("mem_free((void*)inst->{});"_format(var)); + } + } + printer->end_block(); + printer->add_newline(); + } } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 564ad484fb..9ff935dd45 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -169,7 +169,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual std::string compute_method_name(BlockType type); - /// retstrict keyword + /// restrict keyword virtual std::string k_restrict(); /// const keyword @@ -214,6 +214,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual bool channel_task_dependency_enabled(); + /// check if shadow_vector_setup function is required + bool shadow_vector_setup_required(); + + /// ion variable copies are avoided bool optimize_ion_variable_copies(); @@ -242,7 +246,15 @@ class CodegenCVisitor : public CodegenBaseVisitor { void print_ion_variables_structure(); - /// function that initialized instance structure + /// return floating point type for given range variable symbol + std::string get_range_var_float_type(const SymbolType& symbol); + + + /// function that initialize range variable with different data type + void print_setup_range_variable(); + + + /// function that initialize instance structure void print_instance_variable_setup(); @@ -542,7 +554,7 @@ void CodegenCVisitor::print_function_declaration(T& node) { /// procedures have "int" return type by default std::string return_type = "int"; if (node->is_function_block()) { - return_type = float_data_type(); + return_type = default_float_data_type(); } print_device_method_annotation(); @@ -553,7 +565,7 @@ void CodegenCVisitor::print_function_declaration(T& node) { if (!params.empty() && !internal_params.empty()) { printer->add_text(", "); } - auto type = float_data_type() + " "; + auto type = default_float_data_type() + " "; print_vector_elements(params, ", ", type); printer->add_text(")"); From 3126d444101186cd41e277cb047be6d94cf7dc7a Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 17 May 2018 11:35:10 +0200 Subject: [PATCH 072/871] memory.h include was missing - more clang-format Change-Id: I99d3a57f1a7ce0736d5bc5017d8044abf9a23666 NMODL Repo SHA: BlueBrain/nmodl@8b83c483aacfefd7051d65aff1b166c58bd3d19e --- src/nmodl/codegen/c/codegen_c_visitor.cpp | 13 ++++++------- src/nmodl/symtab/symbol.hpp | 3 ++- src/nmodl/visitors/perf_visitor.cpp | 6 +++--- test/nmodl/transpiler/modtoken/modtoken.cpp | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 4c358b8130..a6e27acf61 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1024,14 +1024,13 @@ std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, } offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id"; return "indexes[{}]"_format(offset); - } else { - offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); - if (use_instance) { - return "inst->{}[indexes[{}]]"_format(name, offset); - } - auto data = symbol.is_vdata ? "_vdata" : "_data"; - return "nt->{}[indexes[{}]]"_format(data, offset); } + offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); + if (use_instance) { + return "inst->{}[indexes[{}]]"_format(name, offset); + } + auto data = symbol.is_vdata ? "_vdata" : "_data"; + return "nt->{}[indexes[{}]]"_format(data, offset); // clang-format on } diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index edd38d3aea..d5f7702daf 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -2,6 +2,7 @@ #define _NMODL_SYMBOL_HPP_ #include <map> +#include <memory> #include "lexer/modtoken.hpp" #include "symtab/symbol_properties.hpp" @@ -237,4 +238,4 @@ namespace symtab { } // namespace symtab -#endif \ No newline at end of file +#endif diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index d2922b4d56..2a9b202ef0 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -154,11 +154,11 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { if (start_measurement) { auto name = node->name->get_name(); - if (name.compare("exp") == 0) { + if (name == "exp") { current_block_perf.n_exp++; - } else if (name.compare("log") == 0) { + } else if (name == "log") { current_block_perf.n_log++; - } else if (name.compare("pow") == 0) { + } else if (name == "pow") { current_block_perf.n_pow++; } node->visit_children(this); diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 55a23b9c73..6a483dc0b0 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -29,7 +29,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { std::stringstream ss; symbol_type("text", value); ss << *(value->get_token()); - REQUIRE(ss.str().compare(" text at [1.1-4] type 356") == 0); + REQUIRE(ss.str() == " text at [1.1-4] type 356"); delete value; } @@ -37,7 +37,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { std::stringstream ss; symbol_type(" some_text", value); ss << *(value->get_token()); - REQUIRE(ss.str().compare(" some_text at [1.3-11] type 356") == 0); + REQUIRE(ss.str() == " some_text at [1.3-11] type 356"); delete value; } } @@ -49,7 +49,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value->get_token()); - REQUIRE(ss.str().compare(" h'' at [1.1-3] type 362") == 0); + REQUIRE(ss.str() == " h'' at [1.1-3] type 362"); REQUIRE(value->get_order() == 2); delete value; } From 52e379a603a0ee0035a93ec557ef1e92d7ac365a Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 13 Oct 2018 23:21:20 +0200 Subject: [PATCH 073/871] Rename nocmodl to nmodl in all targets Change-Id: I929aa88320c28f6a8dabcca845921c0727455d2c NMODL Repo SHA: BlueBrain/nmodl@6466568bbd1fe263ae379e1da9fe3f1a8c0dc0dd --- NOTES.md | 12 +++++------ README.md | 14 ++++++------- cmake/nmodl/CMakeLists.txt | 6 +++--- src/nmodl/codegen/CMakeLists.txt | 4 ++-- src/nmodl/codegen/c/codegen_c_visitor.cpp | 2 +- src/nmodl/codegen/main.cpp | 14 ++++++------- src/nmodl/lexer/CMakeLists.txt | 25 +++++++++++++---------- src/nmodl/lexer/modl.h | 4 ++-- src/nmodl/lexer/nmodl.ll | 2 +- src/nmodl/main.cpp | 4 ++-- src/nmodl/parser/CMakeLists.txt | 6 +++--- src/nmodl/parser/diffeq.yy | 2 +- src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/version/version.cpp.in | 4 ++-- src/nmodl/version/version.h | 6 +++--- src/nmodl/visitors/CMakeLists.txt | 4 ++-- src/nmodl/visitors/main.cpp | 14 ++++++------- 17 files changed, 64 insertions(+), 61 deletions(-) diff --git a/NOTES.md b/NOTES.md index 33d1be3569..861f76bb26 100644 --- a/NOTES.md +++ b/NOTES.md @@ -14,25 +14,25 @@ mkdir -p $MOD_DIR while read -u 10 file; do cp $file $MOD_DIR/; done 10<coreneuron_modlist.txt ``` -The function/procedure inlining pass is not implemented into nocmodl yet. Get inlined mod files for performance counting: +The function/procedure inlining pass is not implemented into nmodl yet. Get inlined mod files for performance counting: ``` cd $MOD_DIR mkdir inlined for file in *.mod; do - $NOCMODLX_DIR/bin/nocmodl -f $file -a a -i -s s -n inlined/${file}; + $NMODL_DIR/bin/nmodl -f $file -a a -i -s s -n inlined/${file}; done ``` -Now we can run nocmodl visitor executable: +Now we can run nmodl visitor executable: ``` mkdir -p $MOD_DIR/jsons cd $MOD_DIR/jsons -export NOCMODL=~/workarena/repos/bbp/incubator/nocmodl/cmake-build-debug +export NMODL=~/workarena/repos/bbp/incubator/nmodl/cmake-build-debug for file in $MOD_DIR/inlined/*.mod.in; do - $NOCMODL/bin/nocmodl_visitor --file $file; + $NMODL/bin/nmodl_visitor --file $file; done ``` @@ -53,4 +53,4 @@ We can now open the plot from `html_plotting/plots.html`. Material/Scripts for t ``` $HOME/workarena/repos/bbp/incubator/plots_dec_21_fs_meeting -``` \ No newline at end of file +``` diff --git a/README.md b/README.md index 0990a0fe1a..fd6cba7b98 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -## NOCMODL +## NMODL This is prototype implementation of code generation framework for NMODL. @@ -46,7 +46,7 @@ sudo apt-get install python-yaml ``` #### Build -Build/Compile NOCMODL as: +Build/Compile NMODL as: ``` mkdir build @@ -75,14 +75,14 @@ cmake .. -DENABLE_CLANG_TIDY=ON ``` -#### Running NOCMODL +#### Running NMODL You can independently run lexer, parser or visitors as: ``` -./bin/nocmodl_lexer --file ../test/input/channel.mod -./bin/nocmodl_parser --file ../test/input/channel.mod -./bin/nocmodl_visitor --file ../test/input/channel.mod +./bin/nmodl_lexer --file ../test/input/channel.mod +./bin/nmodl_parser --file ../test/input/channel.mod +./bin/nmodl_visitor --file ../test/input/channel.mod ``` @@ -110,7 +110,7 @@ You can independently run lexer, parser or visitors as: Test memory leaks using : ``` -valgrind --leak-check=full --track-origins=yes ./bin/nocmodl_lexer +valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer ``` Or using CTest as: diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index cd0d868934..8ccb98f311 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) -project(nocmodl CXX) +project(nmodl CXX) #============================================================================= # CMake common project settings @@ -71,10 +71,10 @@ add_subdirectory(test) #============================================================================= # Add executables #============================================================================= -add_executable(nocmodl +add_executable(nmodl src/main.cpp) -target_link_libraries(nocmodl lexer util visitor) +target_link_libraries(nmodl lexer util visitor) #============================================================================= # Clang format target diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 82943d9b1c..f5408c1442 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -27,8 +27,8 @@ add_library(codegen add_dependencies(codegen lexer util) -add_executable(nocmodl_codegen main.cpp) -target_link_libraries(nocmodl_codegen printer codegen visitor symtab util lexer fmt) +add_executable(nmodl_codegen main.cpp) +target_link_libraries(nmodl_codegen printer codegen visitor symtab util lexer fmt) #============================================================================= # Files for clang-format diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index a6e27acf61..ea7089a1c4 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1118,7 +1118,7 @@ void CodegenCVisitor::print_backend_info() { time_t tr; time(&tr); auto date = std::string(asctime(localtime(&tr))); - auto version = nocmodl::version::NOCMODL_VERSION + " [" + nocmodl::version::GIT_REVISION + "]"; + auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; printer->add_line("/*********************************************************"); printer->add_line("Model Name : {}"_format(info.mod_suffix)); diff --git a/src/nmodl/codegen/main.cpp b/src/nmodl/codegen/main.cpp index 0e2a9e75c9..5fefb40776 100644 --- a/src/nmodl/codegen/main.cpp +++ b/src/nmodl/codegen/main.cpp @@ -126,12 +126,12 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.verbrename.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.verbrename.mod"); v.visit_program(ast.get()); } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.mod"); v.visit_program(ast.get()); } @@ -142,7 +142,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.mod"); v.visit_program(ast.get()); } @@ -156,7 +156,7 @@ int main(int argc, const char* argv[]) { { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.mod"); v.visit_program(ast.get()); } @@ -167,7 +167,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.mod"); v.visit_program(ast.get()); } @@ -184,7 +184,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.mod"); v.visit_program(ast.get()); } @@ -212,7 +212,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.ren.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.ren.mod"); v.visit_program(ast.get()); } diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 705b1a3dc1..2a26f76009 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -75,7 +75,7 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/stack.hh DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy DEPENDS pyastgen - COMMENT "-- NOCMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" + COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" ) # command to generate verbatim parser @@ -86,7 +86,7 @@ add_custom_command ( OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.hpp DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.y - COMMENT "-- NOCMODL : GENERATING VERBATIM PARSER WITH BISON! --" + COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --" ) # command to generate differential equation parser @@ -103,7 +103,7 @@ add_custom_command ( DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp - COMMENT "-- NOCMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --" + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --" ) # command to generate C (11) parser @@ -113,8 +113,11 @@ add_custom_command ( ${PROJECT_SOURCE_DIR}/src/parser/c11.yy OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.hpp + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/location.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/position.hh + OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/stack.hh DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy - COMMENT "-- NOCMODL : GENERATING C (11) PARSER WITH BISON! --" + COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --" ) # command to generate nmodl lexer @@ -126,7 +129,7 @@ add_custom_command( WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp - COMMENT "-- NOCMODL : GENERATING NMODL LEXER WITH FLEX! --" + COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --" ) # command to generate verbatim lexer @@ -137,7 +140,7 @@ add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.hpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - COMMENT "-- NOCMODL : GENERATING VERBATIM LEXER WITH FLEX! --" + COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --" ) # command to generate differential equation lexer @@ -148,7 +151,7 @@ add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.hpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - COMMENT "-- NOCMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --" + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --" ) # command to generate C (11) lexer @@ -159,7 +162,7 @@ add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.hpp WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - COMMENT "-- NOCMODL : GENERATING C(11) LEXER WITH FLEX! --" + COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --" ) #============================================================================= @@ -171,10 +174,10 @@ add_library(lexer ${BISON_GENERATED_SOURCE_FILES} ${AST_SOURCE_FILES}) -add_executable(nocmodl_lexer main_nmodl.cpp) +add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) -target_link_libraries(nocmodl_lexer lexer) +target_link_libraries(nmodl_lexer lexer) target_link_libraries(c_lexer lexer) #============================================================================= @@ -188,4 +191,4 @@ set(FILES_FOR_CLANG_FORMAT ${C_DRIVER_FILES} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE -) \ No newline at end of file +) diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index be38137db5..138da57e62 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -1,11 +1,11 @@ #pragma once /** - * Original implementation of nocmodl use various flags to help + * Original implementation of nmodl use various flags to help * code generation. These flags are implemented as bit masks in * the token whic are later checked during code printing. We are * using ast and hence don't need all bit masks. These are defined - * in modl.h file of original nocmodl implementation. + * in modl.h file of original nmodl implementation. * * \todo Remove these bit masks as we incorporate type information * into corresponding ast types. */ diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index a7d6e0f0a6..049714aed1 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -467,7 +467,7 @@ ELSE { /** Some of the utility functions can't be defined outside due to macros. - * These are utility functions ported from original nocmodl implementation. */ + * These are utility functions ported from original nmodl implementation. */ /** This implementation of NmodlFlexLexer::yylex() is required to fill the diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 983eb3bcea..084435cce3 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -1,10 +1,10 @@ #include <iostream> #include "version/version.h" -using namespace nocmodl; +using namespace nmodl; int main(int /*argc*/, char const** /*argv*/) { - std::cout << " NOCMODL " << version::NOCMODL_VERSION << " [" << version::GIT_REVISION << "]" << std::endl; + std::cout << " NMODL " << version::NMODL_VERSION << " [" << version::GIT_REVISION << "]" << std::endl; } diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 772a9126d5..e4c6f7f64d 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -5,10 +5,10 @@ # lexer library links with all parser related files # so no eed to have parser as a separate library -add_executable(nocmodl_parser main_nmodl.cpp) +add_executable(nmodl_parser main_nmodl.cpp) add_executable(c_parser main_c.cpp) -target_link_libraries(nocmodl_parser lexer) +target_link_libraries(nmodl_parser lexer) target_link_libraries(c_parser lexer) #============================================================================= @@ -20,4 +20,4 @@ set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_context.hpp ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE -) \ No newline at end of file +) diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 257bb2e339..8594f37df0 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -3,7 +3,7 @@ * @brief Bison grammar and parser implementation for ODEs * * ODEs specified in DERIVATIVE block needs to be solved during code generation. - * This parser implementation is based on original NEURON's nocmodl implementation + * This parser implementation is based on original NEURON's nmodl implementation * and solves ODEs using helper routines provided. *****************************************************************************/ diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 09d345339b..60fdf73d03 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -2,7 +2,7 @@ * * @brief Bison grammar and parser implementation for NMODL * - * This implementation is based NEURON's nocmodl program. The grammar rules, + * This implementation is based NEURON's nmodl program. The grammar rules, * symbols, terminals and non-terminals closely resember to NEURON * implementation. This is to support entire NMODL language for NEURON as well * as CoreNEURON. As opposed to non-reentrant C parser, this implementation diff --git a/src/nmodl/version/version.cpp.in b/src/nmodl/version/version.cpp.in index 83030bb902..f9677c8d83 100644 --- a/src/nmodl/version/version.cpp.in +++ b/src/nmodl/version/version.cpp.in @@ -1,4 +1,4 @@ #include "version/version.h" -const std::string nocmodl::version::GIT_REVISION = "@GIT_REVISION@"; -const std::string nocmodl::version::NOCMODL_VERSION = "@PROJECT_VERSION@"; +const std::string nmodl::version::GIT_REVISION = "@GIT_REVISION@"; +const std::string nmodl::version::NMODL_VERSION = "@PROJECT_VERSION@"; diff --git a/src/nmodl/version/version.h b/src/nmodl/version/version.h index d429ec1a5f..6831047050 100644 --- a/src/nmodl/version/version.h +++ b/src/nmodl/version/version.h @@ -2,9 +2,9 @@ #include <string> -namespace nocmodl { +namespace nmodl { struct version { static const std::string GIT_REVISION; - static const std::string NOCMODL_VERSION; + static const std::string NMODL_VERSION; }; -} // namespace nocmodl +} // namespace nmodl diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index b546dac8e7..70a52a24aa 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -48,8 +48,8 @@ add_library(visitor add_dependencies(visitor lexer util) -add_executable(nocmodl_visitor main.cpp) -target_link_libraries(nocmodl_visitor printer visitor symtab util lexer fmt) +add_executable(nmodl_visitor main.cpp) +target_link_libraries(nmodl_visitor printer visitor symtab util lexer fmt) #============================================================================= # Files for clang-format diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 4f4d97c48a..d21ece3f86 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -96,12 +96,12 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.verbrename.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.verbrename.mod"); v.visit_program(ast.get()); } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.mod"); v.visit_program(ast.get()); } @@ -112,7 +112,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.mod"); v.visit_program(ast.get()); } @@ -124,7 +124,7 @@ int main(int argc, const char* argv[]) { { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.mod"); v.visit_program(ast.get()); } @@ -135,7 +135,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.mod"); v.visit_program(ast.get()); } @@ -152,7 +152,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.mod"); v.visit_program(ast.get()); } @@ -175,7 +175,7 @@ int main(int argc, const char* argv[]) { } { - NmodlPrintVisitor v(mod_filename + ".nocmodl.cnexp.in.ren.loc.ren.mod"); + NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.ren.mod"); v.visit_program(ast.get()); } From 54121598dea1e1a9e46a755cc8fbab2f0808bc80 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 14 Oct 2018 00:38:19 +0200 Subject: [PATCH 074/871] Add find_python_module, make code compatible with Python 2/3 Change-Id: I15be25a6936f05bf7731963511ac6ffd30e25481 NMODL Repo SHA: BlueBrain/nmodl@caa7a8711b1d08261f9a7c6b8ef08c515d84ebda --- README.md | 14 ++++++++++++-- cmake/nmodl/CMakeLists.txt | 13 +++++++++---- cmake/nmodl/FindPythonModule.cmake | 28 ++++++++++++++++++++++++++++ src/nmodl/language/parser.py | 16 ++++++++-------- src/nmodl/language/printer.py | 4 ++-- 5 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 cmake/nmodl/FindPythonModule.cmake diff --git a/README.md b/README.md index fd6cba7b98..1611fe3e75 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - bison (>=3.0) - CMake (>=3.1) - C++ compiler (with c++11 support) -- Python2 (>=2.7) -- Python yaml +- Python2/3 (>=2.7) +- Python yaml (pyyaml) Make sure to have latest version of flex (>=2.6) and bison (>=3.0). For example, on OS X we typically install packages via brew or macport as: @@ -41,10 +41,20 @@ bison (GNU Bison) 3.0.4 ``` Python yaml can be installed on Ubuntu using: + ``` sudo apt-get install python-yaml ``` +On OS X: + +``` +pip install pyyaml +pip3 install pyyaml + +``` + + #### Build Build/Compile NMODL as: diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 8ccb98f311..c4697b6a74 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -15,9 +15,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) #============================================================================= # Find required packages #============================================================================= -message(STATUS "CHECKING FOR FLEX/BISON/PYTHON") - -find_package(PythonInterp REQUIRED) +message(STATUS "CHECKING FOR FLEX/BISON") find_package(FLEX 2.6 REQUIRED) find_package(BISON 3.0 REQUIRED) @@ -25,11 +23,18 @@ find_package(BISON 3.0 REQUIRED) # Include cmake modules #============================================================================= list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) - include(GitRevision) include(FlexHelper) include(CompilerHelper) include(ClangTidyHelper) +include(FindPythonModule) + +#============================================================================= +# Find required python packages +#============================================================================= +message(STATUS "CHECKING FOR PYTHON") +find_package(PythonInterp REQUIRED) +find_python_module(yaml REQUIRED) include_directories( ${PROJECT_SOURCE_DIR} diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake new file mode 100644 index 0000000000..d8525918b2 --- /dev/null +++ b/cmake/nmodl/FindPythonModule.cmake @@ -0,0 +1,28 @@ +# Find if a Python module is installed +# Found at http://www.cmake.org/pipermail/cmake/2011-January/041666.html +# To use do: find_python_module(PyQt4 REQUIRED) +function(find_python_module module) + string(TOUPPER ${module} module_upper) + if(NOT PY_${module_upper}) + if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") + set(${module}_FIND_REQUIRED TRUE) + endif() + # A module's location is usually a directory, but for binary modules + # it's a .so file. + execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" + "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_location + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT _${module}_status) + set(PY_${module_upper} ${_${module}_location} CACHE STRING + "Location of Python module ${module}") + endif(NOT _${module}_status) + endif(NOT PY_${module_upper}) + find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) + if(NOT PY_${module_upper} AND ${module}_FIND_REQUIRED) + message(FATAL_ERROR "Could not find ${module}") + endif() +endfunction(find_python_module) + diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 12acaf41e8..b0e181aacd 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -40,8 +40,8 @@ def parse_child_rule(self, child): """ # there is only one key and it has one value - varname = child.iterkeys().next() - properties = child.itervalues().next() + varname = next(iter(list(child.keys()))) + properties = next(iter(list(child.values()))) # arguments holder for creating tree node args = Argument() @@ -54,7 +54,7 @@ def parse_child_rule(self, child): if self.debug: - print 'Child {}, {}'.format(args.varname, args.class_name) + print(('Child {}, {}'.format(args.varname, args.class_name))) # if there is add method for member in the class if 'add' in properties: @@ -116,8 +116,8 @@ def parse_yaml_rules(self, nodelist, base_class=None): for node in nodelist: # name of the ast class and it's properties as dictionary - class_name = node.iterkeys().next() - properties = node.itervalues().next() + class_name = next(iter(list(node.keys()))) + properties = next(iter(list(node.values()))) # yaml file has abstract classes and their subclasses (i.e. children) as a list if isinstance(properties, list): @@ -138,7 +138,7 @@ def parse_yaml_rules(self, nodelist, base_class=None): abstract_nodes.append(node) nodes.insert(0, node) if self.debug: - print 'Abstract {}, {}'.format(base_class, class_name) + print(('Abstract {}, {}'.format(base_class, class_name))) else: # name of the node while printing back to NMODL nmodl_name = properties['nmodl'] if 'nmodl' in properties else None @@ -158,7 +158,7 @@ def parse_yaml_rules(self, nodelist, base_class=None): nodes.append(node) if self.debug: - print 'Class {}, {}, {}'.format(base_class, class_name, nmodl_name) + print(('Class {}, {}, {}'.format(base_class, class_name, nmodl_name))) # now process all children specification childs = properties['members'] if 'members' in properties else [] @@ -184,7 +184,7 @@ def parse_file(self): # abstract nodes are not used though abstract_nodes, nodes = self.parse_yaml_rules(rules) except yaml.YAMLError as e: - print "Error while parsing YAML definition file {0} : {1}".format(self.filename, e.strerror) + print(("Error while parsing YAML definition file {0} : {1}".format(self.filename, e.strerror))) sys.exit(1) return nodes diff --git a/src/nmodl/language/printer.py b/src/nmodl/language/printer.py index 682f720604..d120bda441 100644 --- a/src/nmodl/language/printer.py +++ b/src/nmodl/language/printer.py @@ -20,7 +20,7 @@ def __init__(self, filepath): try: self.fh = open(self.filepath, "w+") except IOError as e: - print "Error :: I/O error while writing {0} : {1}".format(self.filepath, e.strerror) + print("Error :: I/O error while writing {0} : {1}".format(self.filepath, e.strerror)) sys.exit(1) def print_gutter(self): @@ -166,4 +166,4 @@ def definitions(self): def write(self): self.headers() - self.definitions() \ No newline at end of file + self.definitions() From 20660516a10cc746a12163eb894c37f1e429fd7a Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 14 Oct 2018 13:32:53 +0200 Subject: [PATCH 075/871] Changes for CoreNEURON integration : - Moved src/codegen/nmodl_codegen to src/nmodl - Revamped command line interface - Fixed issues to compile ring test Change-Id: I0bc7ace17f534b30e043a8d0c6062dedf9ecd259 NMODL Repo SHA: BlueBrain/nmodl@08bc391086054518cf2eae25e54d2722afcd5bba --- cmake/nmodl/CMakeLists.txt | 3 +- src/nmodl/codegen/CMakeLists.txt | 3 - .../codegen/base/codegen_base_visitor.hpp | 3 +- .../codegen/c-cuda/codegen_c_cuda_visitor.hpp | 4 +- .../c-openacc/codegen_c_acc_visitor.hpp | 4 +- .../c-openmp/codegen_c_omp_visitor.hpp | 4 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 2 +- src/nmodl/codegen/c/codegen_c_visitor.hpp | 3 +- src/nmodl/codegen/main.cpp | 257 -------------- src/nmodl/main.cpp | 322 +++++++++++++++++- src/nmodl/parser/c11_driver.hpp | 7 + src/nmodl/utils/CMakeLists.txt | 3 +- src/nmodl/utils/common_utils.cpp | 44 +++ src/nmodl/utils/common_utils.hpp | 3 + src/nmodl/visitors/inline_visitor.cpp | 13 + src/nmodl/visitors/inline_visitor.hpp | 2 +- 16 files changed, 401 insertions(+), 276 deletions(-) delete mode 100644 src/nmodl/codegen/main.cpp create mode 100644 src/nmodl/utils/common_utils.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index c4697b6a74..c0975c075c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -78,8 +78,7 @@ add_subdirectory(test) #============================================================================= add_executable(nmodl src/main.cpp) - -target_link_libraries(nmodl lexer util visitor) +target_link_libraries(nmodl printer codegen visitor symtab util lexer fmt) #============================================================================= # Clang format target diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index f5408c1442..d00f1bb757 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -27,9 +27,6 @@ add_library(codegen add_dependencies(codegen lexer util) -add_executable(nmodl_codegen main.cpp) -target_link_libraries(nmodl_codegen printer codegen visitor symtab util lexer fmt) - #============================================================================= # Files for clang-format #============================================================================= diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 8d115287e0..38ed31315a 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -412,10 +412,11 @@ class CodegenBaseVisitor : public AstVisitor { public: CodegenBaseVisitor(std::string mod_file, + std::string output_dir, bool aos, std::string float_type, std::string extension = ".cpp") - : printer(new CodePrinter(mod_file + extension)) { + : printer(new CodePrinter(output_dir + "/" + mod_file + extension)) { init(mod_file, aos, float_type); } diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp index 901759ab5a..96675e909d 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp @@ -85,8 +85,8 @@ class CodegenCCudaVisitor : public CodegenCVisitor { void codegen_all() override; public: - CodegenCCudaVisitor(std::string mod_file, bool aos, std::string float_type) - : CodegenCVisitor(mod_file, aos, float_type, ".cu") { + CodegenCCudaVisitor(std::string mod_file, std::string output_dir, bool aos, std::string float_type) + : CodegenCVisitor(mod_file, output_dir, aos, float_type, ".cu") { } CodegenCCudaVisitor(std::string mod_file, diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp index d7c0e73732..cabab24f83 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp @@ -58,8 +58,8 @@ class CodegenCAccVisitor : public CodegenCVisitor { public: - CodegenCAccVisitor(std::string mod_file, bool aos, std::string float_type) - : CodegenCVisitor(mod_file, aos, float_type) { + CodegenCAccVisitor(std::string mod_file, std::string output_dir, bool aos, std::string float_type) + : CodegenCVisitor(mod_file, output_dir, aos, float_type) { } CodegenCAccVisitor(std::string mod_file, diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp index d738f1aa84..2a43001192 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp @@ -55,8 +55,8 @@ class CodegenCOmpVisitor : public CodegenCVisitor { public: - CodegenCOmpVisitor(std::string mod_file, bool aos, std::string float_type) - : CodegenCVisitor(mod_file, aos, float_type) { + CodegenCOmpVisitor(std::string mod_file, std::string output_dir, bool aos, std::string float_type) + : CodegenCVisitor(mod_file, output_dir, aos, float_type) { } CodegenCOmpVisitor(std::string mod_file, diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index ea7089a1c4..2fcf174fa4 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1867,7 +1867,7 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("// do nothing"); } else { printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); - for (auto &var : variables_to_free) { + for (auto& var : variables_to_free) { printer->add_line("mem_free((void*)inst->{});"_format(var)); } } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 9ff935dd45..ae7eb93133 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -515,10 +515,11 @@ class CodegenCVisitor : public CodegenBaseVisitor { public: CodegenCVisitor(std::string mod_file, + std::string output_dir, bool aos, std::string float_type, std::string extension = ".cpp") - : CodegenBaseVisitor(mod_file, aos, float_type, extension) { + : CodegenBaseVisitor(mod_file, output_dir, aos, float_type, extension) { } CodegenCVisitor(std::string mod_file, diff --git a/src/nmodl/codegen/main.cpp b/src/nmodl/codegen/main.cpp deleted file mode 100644 index 5fefb40776..0000000000 --- a/src/nmodl/codegen/main.cpp +++ /dev/null @@ -1,257 +0,0 @@ -#include <fstream> -#include <iostream> -#include <sstream> - -#include <tclap/CmdLine.h> -#include "parser/nmodl_driver.hpp" -#include "visitors/ast_visitor.hpp" -#include "visitors/inline_visitor.hpp" -#include "visitors/json_visitor.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "visitors/perf_visitor.hpp" -#include "visitors/symtab_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" -#include "visitors/localize_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" -#include "visitors/verbatim_var_rename_visitor.hpp" -#include "codegen/c/codegen_c_visitor.hpp" -#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" -#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" -#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" - -using namespace symtab; - -/** - * Standalone visitor program for NMODL. This demonstrate basic - * usage of different visitors classes and driver class. - **/ - -int main(int argc, const char* argv[]) { - try { - TCLAP::CmdLine cmd("NMODL Code Generator for NMODL"); - - // clang-format off - TCLAP::ValueArg<std::string> modfile_arg( "", - "file", - "NMODL input file path", - false, - "../test/input/channel.mod", - "string"); - TCLAP::ValueArg<std::string> host_arg( "", - "host", - "Host backend [c, c-openmp, c-openacc]", - false, - "c", - "string"); - TCLAP::ValueArg<std::string> accel_arg( "", - "accelerator", - "Accelerator backend [cuda]", - false, - "", - "string"); - TCLAP::ValueArg<std::string> dtype_arg( "", - "datatype", - "Data type [float, double]", - false, - "double", - "string"); - TCLAP::SwitchArg verbose_arg( "", - "verbose", - "Enable verbose output", - false); - TCLAP::SwitchArg aos_layout_arg( "", - "aos", - "Enable AoS layout (default SoA)", - false); - TCLAP::SwitchArg inline_arg( "", - "inline", - "Enable inlining in NMODL implementation", - false); - // clang-format on - - cmd.add(modfile_arg); - cmd.add(verbose_arg); - cmd.add(aos_layout_arg); - cmd.add(inline_arg); - cmd.add(host_arg); - cmd.add(accel_arg); - cmd.add(dtype_arg); - cmd.parse(argc, argv); - - std::string filename = modfile_arg.getValue(); - std::string host_backend = host_arg.getValue(); - std::string accel_backend = accel_arg.getValue(); - std::string data_type = dtype_arg.getValue(); - bool verbose = verbose_arg.getValue(); - bool aos_code = aos_layout_arg.getValue(); - bool mod_inline = inline_arg.getValue(); - - std::ifstream file(filename); - - if (!file.good()) { - throw std::runtime_error("Could not open file " + filename); - } - - std::string mod_filename = remove_extension(base_name(filename)); - - /// driver object creates lexer and parser, just call parser method - nmodl::Driver driver; - driver.parse_file(filename); - - /// shared_ptr to ast constructed from parsing nmodl file - auto ast = driver.ast(); - - { - AstVisitor v; - v.visit_program(ast.get()); - } - - { - SymtabVisitor v(false); - v.visit_program(ast.get()); - } - - { - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; - } - - { - VerbatimVarRenameVisitor v; - v.visit_program(ast.get()); - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.verbrename.mod"); - v.visit_program(ast.get()); - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.mod"); - v.visit_program(ast.get()); - } - - { - CnexpSolveVisitor v; - v.visit_program(ast.get()); - std::cout << "----CNEXP SOLVE VISITOR FINISHED----" << std::endl; - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.mod"); - v.visit_program(ast.get()); - } - - - { - if (mod_inline) { - InlineVisitor v; - v.visit_program(ast.get()); - } - } - - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.mod"); - v.visit_program(ast.get()); - } - - { - LocalVarRenameVisitor v; - v.visit_program(ast.get()); - std::cout << "----LOCAL VAR RENAME VISITOR FINISHED----" << std::endl; - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.mod"); - v.visit_program(ast.get()); - } - - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - { - /// for benchmarking/plotting purpose we want to enable unsafe mode - bool ignore_verbatim = true; - LocalizeVisitor v(ignore_verbatim); - v.visit_program(ast.get()); - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.mod"); - v.visit_program(ast.get()); - } - - { - LocalVarRenameVisitor v; - v.visit_program(ast.get()); - } - - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - { - PerfVisitor v(mod_filename + ".perf.json"); - v.visit_program(ast.get()); - } - - { - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.ren.mod"); - v.visit_program(ast.get()); - } - - { - if (data_type != "double" && data_type != "float") { - std::cerr << "Argument Error: Unknown data type " << data_type << std::endl; - return 1; - } - - if (host_backend == "c") { - CodegenCVisitor visitor(mod_filename, aos_code, data_type); - visitor.visit_program(ast.get()); - } else if (host_backend == "c-openmp") { - CodegenCOmpVisitor visitor(mod_filename, aos_code, data_type); - visitor.visit_program(ast.get()); - } else if (host_backend == "c-openacc") { - CodegenCAccVisitor visitor(mod_filename, aos_code, data_type); - visitor.visit_program(ast.get()); - } else { - std::cerr << "Argument Error: Unknown host backend " << host_backend << std::endl; - return 1; - } - - if (!accel_backend.empty()) { - if (accel_backend == "cuda") { - CodegenCCudaVisitor visitor(mod_filename, aos_code, data_type); - visitor.visit_program(ast.get()); - } else { - std::cerr << "Argument Error: Unknown accelerator backend " << accel_backend - << std::endl; - return 1; - } - } - } - - } catch (TCLAP::ArgException& e) { - std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; - return 1; - } - - return 0; -} diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 084435cce3..f6b4efb913 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -1,10 +1,326 @@ +#include <fstream> #include <iostream> +#include <sstream> + +#include "tclap/CmdLine.h" +#include "src/version/version.h" +#include "parser/nmodl_driver.hpp" #include "version/version.h" +#include "visitors/ast_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/json_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/verbatim_var_rename_visitor.hpp" +#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" +#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" +#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" +#include "utils/common_utils.hpp" + +/** + * Standalone visitor program for NMODL. This demonstrate basic + * usage of different visitors classes and driver class. + **/ + +void ast_to_nmodl(ast::Program* ast, std::string filename) { + NmodlPrintVisitor v(filename); + v.visit_program(ast); +} + +int main(int argc, const char* argv[]) { + // version string + auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; + + try { + using string_vector_t = std::vector<std::string>; + using value_constraint_t = TCLAP::ValuesConstraint<std::string>; + using value_arg_t = TCLAP::ValueArg<std::string>; + using switch_arg_t = TCLAP::SwitchArg; + using unlabel_arg_t = TCLAP::UnlabeledMultiArg<std::string>; + + TCLAP::CmdLine cmd("NMODL Code Generator for NMODL", ' ', version); + + // clang-format off + string_vector_t host_val = {"SERIAL", "OPENMP", "OPENACC"}; + value_constraint_t host_constr(host_val); + value_arg_t host_arg( "", + "host", + "Code generation backend for host [" + host_val[0] +"]", + false, + host_val[0], + &host_constr, + cmd); + + string_vector_t accel_val = {"CUDA"}; + value_constraint_t accel_constr(accel_val); + value_arg_t accel_arg( "", + "accelerator", + "Accelerator backend [" + accel_val[0] + "]", + false, + "", + &accel_constr, + cmd); + + string_vector_t dtype_val = {"DOUBLE", "FLOAT"}; + value_constraint_t dtype_constr(dtype_val); + value_arg_t dtype_arg( "", + "datatype", + "Floating point data type [" + dtype_val[0] + "]", + false, + dtype_val[0], + &dtype_constr, + cmd); + + string_vector_t layout_val = {"SOA", "AOS"}; + value_constraint_t layout_constr(layout_val); + value_arg_t layout_arg( "", + "layout", + "Memory layout for channel data [" + layout_val[0] +"]", + false, + layout_val[0], + &layout_constr, + cmd); + + value_arg_t output_dir_arg( "", + "output-dir", + "Output directory for code generator output [.]", + false, + ".", + "string", + cmd); + + value_arg_t scratch_dir_arg( "", + "scratch-dir", + "Output directory for intermediate output [tmp]", + false, + "tmp", + "string", + cmd); + + switch_arg_t verbose_arg( "", + "verbose", + "Enable verbose output", + cmd, + false); + + switch_arg_t inline_arg( "", + "nmodl-inline", + "Enable NMODL inlining", + cmd, + false); + + switch_arg_t localize_arg( "", + "localize", + "Localize RANGE, GLOBAL, ASSIGNED variables", + cmd, + false); + + switch_arg_t localize_verbatim_arg( + "", + "localize-verbatim", + "Localize even with VERBATIM blocks (unsafe optimizations)", + cmd, + false); + + switch_arg_t local_rename_arg( + "", + "local-rename", + "Rename LOCAL variables if necessary", + cmd, + false); + + switch_arg_t perf_stats_arg( + "", + "dump-perf-stats", + "Run performance visitor and dump stats into JSON file", + cmd, + false); + + switch_arg_t nmodl_state_arg( + "", + "dump-nmodl-state", + "Dump intermediate AST states into NMODL", + cmd, + false); + + switch_arg_t no_verbatim_rename_arg( + "", + "no-verbatim-rename", + "Disable renaming variables in VERBATIM block", + cmd, + false); + + switch_arg_t show_symtab_arg( + "", + "show-symtab", + "Show symbol table to stdout", + cmd, + false); + + unlabel_arg_t nmodl_arg( "files", + "NMODL input models path", + true, + "string", + cmd); + // clang-format on + + cmd.parse(argc, argv); + + auto nmodl_files = nmodl_arg.getValue(); + auto host_backend = host_arg.getValue(); + auto accel_backend = accel_arg.getValue(); + auto data_type = stringutils::tolower(dtype_arg.getValue()); + auto mem_layout = layout_arg.getValue(); + auto verbose = verbose_arg.getValue(); + auto enable_inline = inline_arg.getValue(); + auto ignore_verbatim = localize_verbatim_arg.getValue(); + auto local_rename = local_rename_arg.getValue(); + auto verbatim_rename = !no_verbatim_rename_arg.getValue(); + auto enable_localize = localize_arg.getValue(); + auto dump_perf_stats = perf_stats_arg.getValue(); + auto show_symtab = show_symtab_arg.getValue(); + auto output_dir = output_dir_arg.getValue(); + auto scratch_dir = scratch_dir_arg.getValue(); + auto dump_nmodl = nmodl_state_arg.getValue(); + + make_path(output_dir); + make_path(scratch_dir); + + for (auto& nmodl_file : nmodl_files) { + std::ifstream file(nmodl_file); + + if (!file.good()) { + throw std::runtime_error("Could not open file " + nmodl_file); + } + + std::string mod_file = remove_extension(base_name(nmodl_file)); + + /// driver object creates lexer and parser, just call parser method + nmodl::Driver driver; + driver.parse_file(nmodl_file); + + /// shared_ptr to ast constructed from parsing nmodl file + auto ast = driver.ast(); + + { + AstVisitor v; + v.visit_program(ast.get()); + } + + { + SymtabVisitor v(false); + v.visit_program(ast.get()); + } + + if (dump_nmodl) { + ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.mod"); + } + + if (verbatim_rename) { + VerbatimVarRenameVisitor v; + v.visit_program(ast.get()); + if (dump_nmodl) { + ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.verbrename.mod"); + } + } + + { + CnexpSolveVisitor v; + v.visit_program(ast.get()); + } + + if (dump_nmodl) { + ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.cnexp.mod"); + } + + if (enable_inline) { + InlineVisitor v; + v.visit_program(ast.get()); + if (dump_nmodl) { + ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.in.mod"); + } + } + + if (local_rename) { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + if (dump_nmodl) { + ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.locrename.mod"); + } + } + + { + SymtabVisitor v(true); + v.visit_program(ast.get()); + } + + if (enable_localize) { + // localize pass must be followed by renaming in order to avoid conflict + // with global scope variables + LocalizeVisitor v1(ignore_verbatim); + v1.visit_program(ast.get()); + LocalVarRenameVisitor v2; + v2.visit_program(ast.get()); + if (dump_nmodl) { + ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.localize.mod"); + } + } + + { + SymtabVisitor v(true); + v.visit_program(ast.get()); + } + + if (dump_perf_stats) { + PerfVisitor v(scratch_dir + "/" + mod_file + ".perf.json"); + v.visit_program(ast.get()); + } + + if (show_symtab) { + std::cout << "----SYMBOL TABLE ----" << std::endl; + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); + std::cout << "---------------------" << std::endl; + } + + { + // make sure to run perf visitor because code generator + // looks for read/write counts const/non-const declaration + PerfVisitor v; + v.visit_program(ast.get()); + + bool aos_layout = (mem_layout == "AOS"); -using namespace nmodl; + if (host_backend == "SERIAL") { + CodegenCVisitor visitor(mod_file, output_dir, aos_layout, data_type); + visitor.visit_program(ast.get()); + } else if (host_backend == "OPENMP") { + CodegenCOmpVisitor visitor(mod_file, output_dir, aos_layout, data_type); + visitor.visit_program(ast.get()); + } else if (host_backend == "OPENACC") { + CodegenCAccVisitor visitor(mod_file, output_dir, aos_layout, data_type); + visitor.visit_program(ast.get()); + } -int main(int /*argc*/, char const** /*argv*/) { + if (accel_backend == "CUDA") { + CodegenCCudaVisitor visitor(mod_file, output_dir, aos_layout, data_type); + visitor.visit_program(ast.get()); + } + } + } - std::cout << " NMODL " << version::NMODL_VERSION << " [" << version::GIT_REVISION << "]" << std::endl; + } catch (TCLAP::ArgException& e) { + std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; + return 1; + } + return 0; } diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 18ebbc4cc5..ffd28617e2 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -83,6 +83,13 @@ namespace c11 { std::vector<std::string> all_tokens() const { return tokens; } + + bool has_token(std::string token) { + if(std::find(tokens.begin(), tokens.end(), token) != tokens.end()) { + return true; + } + return false; + } }; } // namespace c11 diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 1367168de5..4ed7fc6594 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -2,6 +2,7 @@ # Utility sources #============================================================================= set(UTIL_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/table_data.hpp @@ -26,4 +27,4 @@ set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE -) \ No newline at end of file +) diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp new file mode 100644 index 0000000000..e98529963c --- /dev/null +++ b/src/nmodl/utils/common_utils.cpp @@ -0,0 +1,44 @@ +#include <iostream> +#include <string> +#include <sys/stat.h> +#include <errno.h> + +bool is_dir_exist(const std::string& path) { + struct stat info; + if (stat(path.c_str(), &info) != 0) { + return false; + } + return (info.st_mode & S_IFDIR) != 0; +} + +bool make_path(const std::string& path) { + mode_t mode = 0755; + int ret = mkdir(path.c_str(), mode); + if (ret == 0) { + return true; + } + + switch (errno) { + case ENOENT: + // parent didn't exist, try to create it + { + int pos = path.find_last_of('/'); + if (pos == std::string::npos) { + return false; + } + if (!make_path(path.substr(0, pos))) { + return false; + } + } + // now, try to create again + return 0 == mkdir(path.c_str(), mode); + + case EEXIST: + // done! + return is_dir_exist(path); + + default: + auto msg = "Can not create directory " + path; + throw std::runtime_error(msg); + } +} diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index c3bba975fa..e2846e69c3 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -20,4 +20,7 @@ T remove_extension(T const& filename) { return p > 0 && p != T::npos ? filename.substr(0, p) : filename; } +/** Given directory path, create sub-directories */ +bool make_path(const std::string& path); + #endif diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 57824cc9c6..f12f56c6eb 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -1,4 +1,5 @@ #include "visitors/inline_visitor.hpp" +#include "parser/c11_driver.hpp" using namespace ast; @@ -10,6 +11,18 @@ bool InlineVisitor::can_inline_block(StatementBlock* block) { to_inline = false; break; } + // verbatim blocks with return statement are not safe to inline + // especially for net_receive block + if (statement->is_verbatim()) { + auto node = static_cast<Verbatim*>(statement.get()); + auto text = node->get_statement()->eval(); + c11::Driver driver; + driver.scan_string(text); + if (driver.has_token("return")) { + to_inline = false; + break; + } + } } return to_inline; } diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 1d67e2c977..8203c18589 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -185,7 +185,7 @@ bool InlineVisitor::inline_function_call(T* callee, /// do nothing if we can't inline given procedure/function if (!can_inline_block(callee->statementblock.get())) { - std::cerr << "Can not inline function call to " + function_name; + std::cerr << "Can not inline function call to " + function_name << std::endl; return false; } From 83087c4ade1fa424f7623e2c64d247dec10912b1 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 20 Oct 2018 10:01:55 +0200 Subject: [PATCH 076/871] Fixed make clang-format target Change-Id: Ifbe840a8fb7bfb904d326e2760504ec37421fbd9 NMODL Repo SHA: BlueBrain/nmodl@d0384ab7c6bfa54a09c26c391ca4e9ca421fd65b --- cmake/nmodl/CMakeLists.txt | 5 +++-- src/nmodl/CMakeLists.txt | 8 ++++++++ src/nmodl/codegen/CMakeLists.txt | 1 - src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp | 5 ++++- src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp | 5 ++++- src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp | 5 ++++- src/nmodl/codegen/c/codegen_c_visitor.cpp | 10 ++++++---- src/nmodl/parser/c11_driver.hpp | 2 +- 8 files changed, 30 insertions(+), 11 deletions(-) create mode 100644 src/nmodl/CMakeLists.txt diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index c0975c075c..6869a34b00 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -51,7 +51,10 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) +add_subdirectory(src) add_subdirectory(src/ast) +add_subdirectory(src/codegen) +add_subdirectory(src/ext/fmt) add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/parser) @@ -59,8 +62,6 @@ add_subdirectory(src/printer) add_subdirectory(src/symtab) add_subdirectory(src/utils) add_subdirectory(src/visitors) -add_subdirectory(src/codegen) -add_subdirectory(src/ext/fmt) #============================================================================= # Memory checker options and add tests diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt new file mode 100644 index 0000000000..5a7c74543d --- /dev/null +++ b/src/nmodl/CMakeLists.txt @@ -0,0 +1,8 @@ +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index d00f1bb757..ffdf4ede7f 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -31,7 +31,6 @@ add_dependencies(codegen lexer util) # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CODEGEN_SOURCE_FILES} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp index 96675e909d..5a51c46f07 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp @@ -85,7 +85,10 @@ class CodegenCCudaVisitor : public CodegenCVisitor { void codegen_all() override; public: - CodegenCCudaVisitor(std::string mod_file, std::string output_dir, bool aos, std::string float_type) + CodegenCCudaVisitor(std::string mod_file, + std::string output_dir, + bool aos, + std::string float_type) : CodegenCVisitor(mod_file, output_dir, aos, float_type, ".cu") { } diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp index cabab24f83..8d56ef0e12 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp @@ -58,7 +58,10 @@ class CodegenCAccVisitor : public CodegenCVisitor { public: - CodegenCAccVisitor(std::string mod_file, std::string output_dir, bool aos, std::string float_type) + CodegenCAccVisitor(std::string mod_file, + std::string output_dir, + bool aos, + std::string float_type) : CodegenCVisitor(mod_file, output_dir, aos, float_type) { } diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp index 2a43001192..b21eb3cf2c 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp @@ -55,7 +55,10 @@ class CodegenCOmpVisitor : public CodegenCVisitor { public: - CodegenCOmpVisitor(std::string mod_file, std::string output_dir, bool aos, std::string float_type) + CodegenCOmpVisitor(std::string mod_file, + std::string output_dir, + bool aos, + std::string float_type) : CodegenCVisitor(mod_file, output_dir, aos, float_type) { } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 2fcf174fa4..f37f03c29b 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1769,7 +1769,8 @@ void CodegenCVisitor::print_setup_range_variable() { auto type = float_data_type(); printer->add_newline(2); printer->add_line("/** allocate and setup array for range variable */"); - printer->start_block("static inline {}* setup_range_variable(double* variable, int n) "_format(type)); + printer->start_block( + "static inline {}* setup_range_variable(double* variable, int n) "_format(type)); printer->add_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));"_format(type)); printer->add_line("for(size_t i = 0; i < n; i++) {"); printer->add_line(" data[i] = variable[i];"); @@ -1792,13 +1793,13 @@ void CodegenCVisitor::print_setup_range_variable() { * @return Floating point type (float/double) */ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) { - /// clang-format off + // clang-format off auto with = NmodlInfo::read_ion_var | NmodlInfo::write_ion_var | NmodlInfo::pointer_var | NmodlInfo::bbcore_pointer_var | NmodlInfo::extern_neuron_variable; - /// clang-format on + // clang-format on bool need_default_type = symbol->has_properties(with); if (need_default_type) { return default_float_data_type(); @@ -1839,7 +1840,8 @@ void CodegenCVisitor::print_instance_variable_setup() { if (default_type == range_var_type) { printer->add_line("inst->{} = ml->data+{}{};"_format(name, id, stride)); } else { - printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format(name, id, stride)); + printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format( + name, id, stride)); variables_to_free.push_back(name); } id += var->get_length(); diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index ffd28617e2..dd47030be2 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -85,7 +85,7 @@ namespace c11 { } bool has_token(std::string token) { - if(std::find(tokens.begin(), tokens.end(), token) != tokens.end()) { + if (std::find(tokens.begin(), tokens.end(), token) != tokens.end()) { return true; } return false; From 2606ea6897fe2625dc51a1198e3ca1be2ab2987e Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 20 Oct 2018 11:54:31 +0200 Subject: [PATCH 077/871] Separate argument parsing from main.cpp Change-Id: I883df660cc97dfa04f5aae39a8732d990f3e6689 NMODL Repo SHA: BlueBrain/nmodl@1d60323c590b231a72accd00c8434fbaf728c288 --- cmake/nmodl/CMakeLists.txt | 9 +- src/nmodl/CMakeLists.txt | 8 - src/nmodl/main.cpp | 326 -------------------------------- src/nmodl/nmodl/CMakeLists.txt | 24 +++ src/nmodl/nmodl/arg_handler.cpp | 176 +++++++++++++++++ src/nmodl/nmodl/arg_handler.hpp | 84 ++++++++ src/nmodl/nmodl/main.cpp | 165 ++++++++++++++++ 7 files changed, 450 insertions(+), 342 deletions(-) delete mode 100644 src/nmodl/CMakeLists.txt delete mode 100644 src/nmodl/main.cpp create mode 100644 src/nmodl/nmodl/CMakeLists.txt create mode 100644 src/nmodl/nmodl/arg_handler.cpp create mode 100644 src/nmodl/nmodl/arg_handler.hpp create mode 100644 src/nmodl/nmodl/main.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 6869a34b00..b8715d5613 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -51,12 +51,12 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) -add_subdirectory(src) add_subdirectory(src/ast) add_subdirectory(src/codegen) add_subdirectory(src/ext/fmt) add_subdirectory(src/language) add_subdirectory(src/lexer) +add_subdirectory(src/nmodl) add_subdirectory(src/parser) add_subdirectory(src/printer) add_subdirectory(src/symtab) @@ -74,13 +74,6 @@ set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ include (CTest) add_subdirectory(test) -#============================================================================= -# Add executables -#============================================================================= -add_executable(nmodl - src/main.cpp) -target_link_libraries(nmodl printer codegen visitor symtab util lexer fmt) - #============================================================================= # Clang format target #============================================================================= diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt deleted file mode 100644 index 5a7c74543d..0000000000 --- a/src/nmodl/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp deleted file mode 100644 index f6b4efb913..0000000000 --- a/src/nmodl/main.cpp +++ /dev/null @@ -1,326 +0,0 @@ -#include <fstream> -#include <iostream> -#include <sstream> - -#include "tclap/CmdLine.h" -#include "src/version/version.h" -#include "parser/nmodl_driver.hpp" -#include "version/version.h" -#include "visitors/ast_visitor.hpp" -#include "visitors/inline_visitor.hpp" -#include "visitors/json_visitor.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "visitors/perf_visitor.hpp" -#include "visitors/symtab_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" -#include "visitors/localize_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" -#include "visitors/verbatim_var_rename_visitor.hpp" -#include "codegen/c/codegen_c_visitor.hpp" -#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" -#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" -#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" -#include "utils/common_utils.hpp" - -/** - * Standalone visitor program for NMODL. This demonstrate basic - * usage of different visitors classes and driver class. - **/ - -void ast_to_nmodl(ast::Program* ast, std::string filename) { - NmodlPrintVisitor v(filename); - v.visit_program(ast); -} - -int main(int argc, const char* argv[]) { - // version string - auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; - - try { - using string_vector_t = std::vector<std::string>; - using value_constraint_t = TCLAP::ValuesConstraint<std::string>; - using value_arg_t = TCLAP::ValueArg<std::string>; - using switch_arg_t = TCLAP::SwitchArg; - using unlabel_arg_t = TCLAP::UnlabeledMultiArg<std::string>; - - TCLAP::CmdLine cmd("NMODL Code Generator for NMODL", ' ', version); - - // clang-format off - string_vector_t host_val = {"SERIAL", "OPENMP", "OPENACC"}; - value_constraint_t host_constr(host_val); - value_arg_t host_arg( "", - "host", - "Code generation backend for host [" + host_val[0] +"]", - false, - host_val[0], - &host_constr, - cmd); - - string_vector_t accel_val = {"CUDA"}; - value_constraint_t accel_constr(accel_val); - value_arg_t accel_arg( "", - "accelerator", - "Accelerator backend [" + accel_val[0] + "]", - false, - "", - &accel_constr, - cmd); - - string_vector_t dtype_val = {"DOUBLE", "FLOAT"}; - value_constraint_t dtype_constr(dtype_val); - value_arg_t dtype_arg( "", - "datatype", - "Floating point data type [" + dtype_val[0] + "]", - false, - dtype_val[0], - &dtype_constr, - cmd); - - string_vector_t layout_val = {"SOA", "AOS"}; - value_constraint_t layout_constr(layout_val); - value_arg_t layout_arg( "", - "layout", - "Memory layout for channel data [" + layout_val[0] +"]", - false, - layout_val[0], - &layout_constr, - cmd); - - value_arg_t output_dir_arg( "", - "output-dir", - "Output directory for code generator output [.]", - false, - ".", - "string", - cmd); - - value_arg_t scratch_dir_arg( "", - "scratch-dir", - "Output directory for intermediate output [tmp]", - false, - "tmp", - "string", - cmd); - - switch_arg_t verbose_arg( "", - "verbose", - "Enable verbose output", - cmd, - false); - - switch_arg_t inline_arg( "", - "nmodl-inline", - "Enable NMODL inlining", - cmd, - false); - - switch_arg_t localize_arg( "", - "localize", - "Localize RANGE, GLOBAL, ASSIGNED variables", - cmd, - false); - - switch_arg_t localize_verbatim_arg( - "", - "localize-verbatim", - "Localize even with VERBATIM blocks (unsafe optimizations)", - cmd, - false); - - switch_arg_t local_rename_arg( - "", - "local-rename", - "Rename LOCAL variables if necessary", - cmd, - false); - - switch_arg_t perf_stats_arg( - "", - "dump-perf-stats", - "Run performance visitor and dump stats into JSON file", - cmd, - false); - - switch_arg_t nmodl_state_arg( - "", - "dump-nmodl-state", - "Dump intermediate AST states into NMODL", - cmd, - false); - - switch_arg_t no_verbatim_rename_arg( - "", - "no-verbatim-rename", - "Disable renaming variables in VERBATIM block", - cmd, - false); - - switch_arg_t show_symtab_arg( - "", - "show-symtab", - "Show symbol table to stdout", - cmd, - false); - - unlabel_arg_t nmodl_arg( "files", - "NMODL input models path", - true, - "string", - cmd); - // clang-format on - - cmd.parse(argc, argv); - - auto nmodl_files = nmodl_arg.getValue(); - auto host_backend = host_arg.getValue(); - auto accel_backend = accel_arg.getValue(); - auto data_type = stringutils::tolower(dtype_arg.getValue()); - auto mem_layout = layout_arg.getValue(); - auto verbose = verbose_arg.getValue(); - auto enable_inline = inline_arg.getValue(); - auto ignore_verbatim = localize_verbatim_arg.getValue(); - auto local_rename = local_rename_arg.getValue(); - auto verbatim_rename = !no_verbatim_rename_arg.getValue(); - auto enable_localize = localize_arg.getValue(); - auto dump_perf_stats = perf_stats_arg.getValue(); - auto show_symtab = show_symtab_arg.getValue(); - auto output_dir = output_dir_arg.getValue(); - auto scratch_dir = scratch_dir_arg.getValue(); - auto dump_nmodl = nmodl_state_arg.getValue(); - - make_path(output_dir); - make_path(scratch_dir); - - for (auto& nmodl_file : nmodl_files) { - std::ifstream file(nmodl_file); - - if (!file.good()) { - throw std::runtime_error("Could not open file " + nmodl_file); - } - - std::string mod_file = remove_extension(base_name(nmodl_file)); - - /// driver object creates lexer and parser, just call parser method - nmodl::Driver driver; - driver.parse_file(nmodl_file); - - /// shared_ptr to ast constructed from parsing nmodl file - auto ast = driver.ast(); - - { - AstVisitor v; - v.visit_program(ast.get()); - } - - { - SymtabVisitor v(false); - v.visit_program(ast.get()); - } - - if (dump_nmodl) { - ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.mod"); - } - - if (verbatim_rename) { - VerbatimVarRenameVisitor v; - v.visit_program(ast.get()); - if (dump_nmodl) { - ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.verbrename.mod"); - } - } - - { - CnexpSolveVisitor v; - v.visit_program(ast.get()); - } - - if (dump_nmodl) { - ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.cnexp.mod"); - } - - if (enable_inline) { - InlineVisitor v; - v.visit_program(ast.get()); - if (dump_nmodl) { - ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.in.mod"); - } - } - - if (local_rename) { - LocalVarRenameVisitor v; - v.visit_program(ast.get()); - if (dump_nmodl) { - ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.locrename.mod"); - } - } - - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - if (enable_localize) { - // localize pass must be followed by renaming in order to avoid conflict - // with global scope variables - LocalizeVisitor v1(ignore_verbatim); - v1.visit_program(ast.get()); - LocalVarRenameVisitor v2; - v2.visit_program(ast.get()); - if (dump_nmodl) { - ast_to_nmodl(ast.get(), scratch_dir + "/" + mod_file + ".nmodl.localize.mod"); - } - } - - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - if (dump_perf_stats) { - PerfVisitor v(scratch_dir + "/" + mod_file + ".perf.json"); - v.visit_program(ast.get()); - } - - if (show_symtab) { - std::cout << "----SYMBOL TABLE ----" << std::endl; - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - std::cout << "---------------------" << std::endl; - } - - { - // make sure to run perf visitor because code generator - // looks for read/write counts const/non-const declaration - PerfVisitor v; - v.visit_program(ast.get()); - - bool aos_layout = (mem_layout == "AOS"); - - if (host_backend == "SERIAL") { - CodegenCVisitor visitor(mod_file, output_dir, aos_layout, data_type); - visitor.visit_program(ast.get()); - } else if (host_backend == "OPENMP") { - CodegenCOmpVisitor visitor(mod_file, output_dir, aos_layout, data_type); - visitor.visit_program(ast.get()); - } else if (host_backend == "OPENACC") { - CodegenCAccVisitor visitor(mod_file, output_dir, aos_layout, data_type); - visitor.visit_program(ast.get()); - } - - if (accel_backend == "CUDA") { - CodegenCCudaVisitor visitor(mod_file, output_dir, aos_layout, data_type); - visitor.visit_program(ast.get()); - } - } - } - - } catch (TCLAP::ArgException& e) { - std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; - return 1; - } - - return 0; -} diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt new file mode 100644 index 0000000000..093c6ed2df --- /dev/null +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -0,0 +1,24 @@ +#============================================================================= +# NMODL sources +#============================================================================= +set(NMODL_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.cpp +) + +#============================================================================= +# Files for clang-format +#============================================================================= +set(FILES_FOR_CLANG_FORMAT + ${NMODL_SOURCE_FILES} + ${FILES_FOR_CLANG_FORMAT} + PARENT_SCOPE +) + +#============================================================================= +# Add executables +#============================================================================= +add_executable(nmodl + ${NMODL_SOURCE_FILES}) +target_link_libraries(nmodl printer codegen visitor symtab util lexer fmt) diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp new file mode 100644 index 0000000000..d8d3b96d31 --- /dev/null +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -0,0 +1,176 @@ + +#include "arg_handler.hpp" +#include "utils/string_utils.hpp" +#include "tclap/CmdLine.h" +#include "version/version.h" + + +ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { + // version string + auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; + + try { + using string_vector_type = std::vector<std::string>; + using value_constraint_type = TCLAP::ValuesConstraint<std::string>; + using value_arg_type = TCLAP::ValueArg<std::string>; + using switch_arg_type = TCLAP::SwitchArg; + using unlabel_arg_type = TCLAP::UnlabeledMultiArg<std::string>; + + TCLAP::CmdLine cmd("NMODL :: Code Generator Toolkit for NMODL", ' ', version); + + // clang-format off + string_vector_type host_val = {"SERIAL", "OPENMP", "OPENACC"}; + value_constraint_type host_constr(host_val); + value_arg_type host_arg( + "", + "host", + "Host / CPU backend [" + host_val[0] +"]", + false, + host_val[0], + &host_constr, + cmd); + + string_vector_type accel_val = {"CUDA"}; + value_constraint_type accel_constr(accel_val); + value_arg_type accel_arg( + "", + "accelerator", + "Accelerator / Device backend [" + accel_val[0] + "]", + false, + "", + &accel_constr, + cmd); + + string_vector_type dtype_val = {"DOUBLE", "FLOAT"}; + value_constraint_type dtype_constr(dtype_val); + value_arg_type dtype_arg( + "", + "datatype", + "Floating point data type [" + dtype_val[0] + "]", + false, + dtype_val[0], + &dtype_constr, + cmd); + + string_vector_type layout_val = {"SOA", "AOS"}; + value_constraint_type layout_constr(layout_val); + value_arg_type layout_arg( + "", + "layout", + "Memory layout for channel/synapse [" + layout_val[0] +"]", + false, + layout_val[0], + &layout_constr, + cmd); + + value_arg_type output_dir_arg( + "", + "output-dir", + "Output directory for code generator [.]", + false, + ".", + "string", + cmd); + + value_arg_type scratch_dir_arg( + "", + "scratch-dir", + "Output directory for intermediate results [tmp]", + false, + "tmp", + "string", + cmd); + + switch_arg_type verbose_arg( + "", + "verbose", + "Enable verbose output", + cmd, + false); + + switch_arg_type inline_arg( + "", + "nmodl-inline", + "Enable NMODL inlining", + cmd, + false); + + switch_arg_type localize_arg( + "", + "localize", + "Localize RANGE, GLOBAL, ASSIGNED variables", + cmd, + false); + + switch_arg_type localize_verbatim_arg( + "", + "localize-verbatim", + "Localize even with VERBATIM blocks (unsafe optimizations)", + cmd, + false); + + switch_arg_type local_rename_arg( + "", + "local-rename", + "Rename LOCAL variables if necessary", + cmd, + false); + + switch_arg_type perf_stats_arg( + "", + "dump-perf-stats", + "Run performance visitor and dump stats into JSON file", + cmd, + false); + + switch_arg_type nmodl_state_arg( + "", + "dump-nmodl-state", + "Dump intermediate AST states into NMODL", + cmd, + false); + + switch_arg_type no_verbatim_rename_arg( + "", + "no-verbatim-rename", + "Disable renaming variables in VERBATIM block", + cmd, + false); + + switch_arg_type show_symtab_arg( + "", + "show-symtab", + "Show symbol table to stdout", + cmd, + false); + + unlabel_arg_type nmodl_arg( + "files", + "NMODL input models path", + true, + "string", + cmd); + // clang-format on + + cmd.parse(argc, argv); + + nmodl_files = nmodl_arg.getValue(); + host_backend = host_arg.getValue(); + accel_backend = accel_arg.getValue(); + dtype = stringutils::tolower(dtype_arg.getValue()); + mlayout = layout_arg.getValue(); + verbose = verbose_arg.getValue(); + inlining = inline_arg.getValue(); + localize_with_verbatim = localize_verbatim_arg.getValue(); + local_rename = local_rename_arg.getValue(); + verbatim_rename = !no_verbatim_rename_arg.getValue(); + localize = localize_arg.getValue(); + perf_stats = perf_stats_arg.getValue(); + show_symtab = show_symtab_arg.getValue(); + output_dir = output_dir_arg.getValue(); + scratch_dir = scratch_dir_arg.getValue(); + ast_to_nmodl = nmodl_state_arg.getValue(); + } catch (TCLAP::ArgException& e) { + std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; + } +} diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp new file mode 100644 index 0000000000..3376cb9443 --- /dev/null +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -0,0 +1,84 @@ +#ifndef NMODL_ARG_HANDLER_HPP +#define NMODL_ARG_HANDLER_HPP + +#include <vector> +#include <string> + +/** + * \class ArgumentHandler + * \brief Parser comamnd line arguments + */ + +struct ArgumentHandler { + /// input files for code generation + std::vector<std::string> nmodl_files; + + /// host code generation backend + std::string host_backend; + + /// device code generation backend + std::string accel_backend; + + /// floating data type to use + std::string dtype; + + /// memory layout to use + std::string mlayout; + + /// enable nmodl level inlining + bool inlining; + + /// enable inlining even if verbatim block exisits + bool localize_with_verbatim; + + /// enable renaming local variables + bool local_rename; + + /// enable conversion of RANGE variables to LOCAL + bool localize; + + /// dump performance statistics + bool perf_stats; + + /// show symbol table information + bool show_symtab; + + /// generate nmodl from ast + bool ast_to_nmodl; + + /// enable verbose (todo: replace by log) + bool verbose; + + /// enable renaming inside verbatim block + bool verbatim_rename; + + /// directory for code generation + std::string output_dir; + + /// directory for intermediate files from code generation + std::string scratch_dir; + + ArgumentHandler(const int& argc, const char* argv[]); + + bool aos_memory_layout() { + return mlayout == "AOS"; + } + + bool host_c_backend() { + return host_backend == "SERIAL"; + } + + bool host_omp_backend() { + return host_backend == "OPENMP"; + } + + bool host_acc_backend() { + return host_backend == "OPENACC"; + } + + bool device_cuda_backend() { + return accel_backend == "CUDA"; + } +}; + +#endif diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp new file mode 100644 index 0000000000..e5c677ebc4 --- /dev/null +++ b/src/nmodl/nmodl/main.cpp @@ -0,0 +1,165 @@ +#include <fstream> +#include <iostream> +#include <sstream> + +#include "arg_handler.hpp" +#include "parser/nmodl_driver.hpp" +#include "version/version.h" +#include "visitors/ast_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/json_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/verbatim_var_rename_visitor.hpp" +#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" +#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" +#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" +#include "utils/common_utils.hpp" + +void ast_to_nmodl(ast::Program* ast, std::string filename) { + NmodlPrintVisitor v(filename); + v.visit_program(ast); +} + +int main(int argc, const char* argv[]) { + // version string + auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; + + ArgumentHandler arg(argc, argv); + + make_path(arg.output_dir); + make_path(arg.scratch_dir); + + for (auto& nmodl_file : arg.nmodl_files) { + std::ifstream file(nmodl_file); + + if (!file.good()) { + throw std::runtime_error("Could not open file " + nmodl_file); + } + + std::string mod_file = remove_extension(base_name(nmodl_file)); + + /// driver object creates lexer and parser, just call parser method + nmodl::Driver driver; + driver.parse_file(nmodl_file); + + /// shared_ptr to ast constructed from parsing nmodl file + auto ast = driver.ast(); + + { + AstVisitor v; + v.visit_program(ast.get()); + } + + { + SymtabVisitor v(false); + v.visit_program(ast.get()); + } + + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.mod"); + } + + if (arg.verbatim_rename) { + VerbatimVarRenameVisitor v; + v.visit_program(ast.get()); + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.verbrename.mod"); + } + } + + { + CnexpSolveVisitor v; + v.visit_program(ast.get()); + } + + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.cnexp.mod"); + } + + if (arg.inlining) { + InlineVisitor v; + v.visit_program(ast.get()); + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.in.mod"); + } + } + + if (arg.local_rename) { + LocalVarRenameVisitor v; + v.visit_program(ast.get()); + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.locrename.mod"); + } + } + + { + SymtabVisitor v(true); + v.visit_program(ast.get()); + } + + if (arg.localize) { + // localize pass must be followed by renaming in order to avoid conflict + // with global scope variables + LocalizeVisitor v1(arg.localize_with_verbatim); + v1.visit_program(ast.get()); + LocalVarRenameVisitor v2; + v2.visit_program(ast.get()); + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.localize.mod"); + } + } + + { + SymtabVisitor v(true); + v.visit_program(ast.get()); + } + + if (arg.perf_stats) { + PerfVisitor v(arg.scratch_dir + "/" + mod_file + ".perf.json"); + v.visit_program(ast.get()); + } + + if (arg.show_symtab) { + std::cout << "----SYMBOL TABLE ----" << std::endl; + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); + std::cout << "---------------------" << std::endl; + } + + { + // make sure to run perf visitor because code generator + // looks for read/write counts const/non-const declaration + PerfVisitor v; + v.visit_program(ast.get()); + + bool aos_layout = arg.aos_memory_layout(); + + if (arg.host_c_backend()) { + CodegenCVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + visitor.visit_program(ast.get()); + } else if (arg.host_omp_backend()) { + CodegenCOmpVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + visitor.visit_program(ast.get()); + } else if (arg.host_acc_backend()) { + CodegenCAccVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + visitor.visit_program(ast.get()); + } + + if (arg.device_cuda_backend()) { + CodegenCCudaVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + visitor.visit_program(ast.get()); + } + } + } + + return 0; +} From 149ac1b7319420976da11cfbbab9c223de786848 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 20 Oct 2018 12:48:15 +0200 Subject: [PATCH 078/871] Cmake clang-tidy fix Change-Id: I6e9aabcbd92cf1939b356b599c0b0f0ae755e5ff NMODL Repo SHA: BlueBrain/nmodl@2e50c7796b178e378460839ce5365bde489533e3 --- cmake/nmodl/ClangTidyHelper.cmake | 4 ++-- src/nmodl/utils/common_utils.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake index 7ea94bd85f..d6985a4b86 100644 --- a/cmake/nmodl/ClangTidyHelper.cmake +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -1,4 +1,4 @@ -if ( CMAKE_VERSION GREATER "3.5" ) +if ( CMAKE_VERSION VERSION_GREATER "3.5" ) set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") if (ENABLE_CLANG_TIDY) find_program (CLANG_TIDY_EXE NAMES "clang-tidy") @@ -11,4 +11,4 @@ if ( CMAKE_VERSION GREATER "3.5" ) set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it endif() endif() -endif() \ No newline at end of file +endif() diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index e98529963c..dbc38cca1e 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -1,10 +1,10 @@ #include <iostream> #include <string> #include <sys/stat.h> -#include <errno.h> +#include <cerrno> bool is_dir_exist(const std::string& path) { - struct stat info; + struct stat info{}; if (stat(path.c_str(), &info) != 0) { return false; } From 55c183abbb734f242979fe7db2bb32d21464ca98 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 20 Oct 2018 15:09:11 +0200 Subject: [PATCH 079/871] Introduced spdlog logging library Change-Id: I2c80020508e174711336f7e315381b94b62388e7 NMODL Repo SHA: BlueBrain/nmodl@1fddca14777d6598e4c8d51bdb4c2fc7b6626257 --- cmake/nmodl/CMakeLists.txt | 1 - src/nmodl/ext/fmt/CMakeLists.txt | 15 - src/nmodl/ext/fmt/core.h | 1502 +++++ src/nmodl/ext/fmt/format-inl.h | 866 +++ src/nmodl/ext/fmt/format.cc | 495 -- src/nmodl/ext/fmt/format.h | 5801 ++++++++--------- src/nmodl/ext/spdlog/async.h | 87 + src/nmodl/ext/spdlog/async_logger.h | 73 + src/nmodl/ext/spdlog/common.h | 186 + .../ext/spdlog/details/async_logger_impl.h | 110 + src/nmodl/ext/spdlog/details/circular_q.h | 72 + .../ext/spdlog/details/console_globals.h | 74 + src/nmodl/ext/spdlog/details/file_helper.h | 152 + src/nmodl/ext/spdlog/details/fmt_helper.h | 127 + src/nmodl/ext/spdlog/details/log_msg.h | 49 + src/nmodl/ext/spdlog/details/logger_impl.h | 383 ++ .../ext/spdlog/details/mpmc_blocking_q.h | 121 + src/nmodl/ext/spdlog/details/null_mutex.h | 45 + src/nmodl/ext/spdlog/details/os.h | 422 ++ .../ext/spdlog/details/pattern_formatter.h | 777 +++ .../ext/spdlog/details/periodic_worker.h | 71 + src/nmodl/ext/spdlog/details/registry.h | 275 + src/nmodl/ext/spdlog/details/thread_pool.h | 228 + src/nmodl/ext/spdlog/fmt/bin_to_hex.h | 172 + src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst | 23 + src/nmodl/ext/spdlog/fmt/bundled/colors.h | 257 + src/nmodl/ext/spdlog/fmt/bundled/core.h | 1502 +++++ src/nmodl/ext/spdlog/fmt/bundled/format-inl.h | 866 +++ src/nmodl/ext/spdlog/fmt/bundled/format.h | 3720 +++++++++++ src/nmodl/ext/spdlog/fmt/bundled/ostream.h | 157 + src/nmodl/ext/spdlog/fmt/bundled/posix.h | 324 + src/nmodl/ext/spdlog/fmt/bundled/printf.h | 726 +++ src/nmodl/ext/spdlog/fmt/bundled/ranges.h | 308 + src/nmodl/ext/spdlog/fmt/bundled/time.h | 156 + src/nmodl/ext/spdlog/fmt/fmt.h | 25 + src/nmodl/ext/spdlog/fmt/ostr.h | 18 + src/nmodl/ext/spdlog/formatter.h | 20 + src/nmodl/ext/spdlog/logger.h | 167 + src/nmodl/ext/spdlog/sinks/android_sink.h | 121 + src/nmodl/ext/spdlog/sinks/ansicolor_sink.h | 161 + src/nmodl/ext/spdlog/sinks/base_sink.h | 69 + src/nmodl/ext/spdlog/sinks/basic_file_sink.h | 70 + src/nmodl/ext/spdlog/sinks/daily_file_sink.h | 136 + src/nmodl/ext/spdlog/sinks/dist_sink.h | 94 + src/nmodl/ext/spdlog/sinks/msvc_sink.h | 54 + src/nmodl/ext/spdlog/sinks/null_sink.h | 49 + src/nmodl/ext/spdlog/sinks/ostream_sink.h | 57 + .../ext/spdlog/sinks/rotating_file_sink.h | 154 + src/nmodl/ext/spdlog/sinks/sink.h | 59 + .../ext/spdlog/sinks/stdout_color_sinks.h | 56 + src/nmodl/ext/spdlog/sinks/stdout_sinks.h | 102 + src/nmodl/ext/spdlog/sinks/syslog_sink.h | 94 + src/nmodl/ext/spdlog/sinks/wincolor_sink.h | 143 + src/nmodl/ext/spdlog/spdlog.h | 320 + src/nmodl/ext/spdlog/tweakme.h | 130 + src/nmodl/ext/spdlog/version.h | 12 + src/nmodl/nmodl/CMakeLists.txt | 2 +- src/nmodl/nmodl/arg_handler.cpp | 2 +- src/nmodl/nmodl/main.cpp | 26 +- src/nmodl/utils/CMakeLists.txt | 2 + src/nmodl/utils/logger.cpp | 17 + src/nmodl/utils/logger.hpp | 10 + src/nmodl/version/version.h | 3 + src/nmodl/visitors/CMakeLists.txt | 2 +- 64 files changed, 18670 insertions(+), 3648 deletions(-) delete mode 100644 src/nmodl/ext/fmt/CMakeLists.txt create mode 100644 src/nmodl/ext/fmt/core.h create mode 100644 src/nmodl/ext/fmt/format-inl.h delete mode 100644 src/nmodl/ext/fmt/format.cc create mode 100644 src/nmodl/ext/spdlog/async.h create mode 100644 src/nmodl/ext/spdlog/async_logger.h create mode 100644 src/nmodl/ext/spdlog/common.h create mode 100644 src/nmodl/ext/spdlog/details/async_logger_impl.h create mode 100644 src/nmodl/ext/spdlog/details/circular_q.h create mode 100644 src/nmodl/ext/spdlog/details/console_globals.h create mode 100644 src/nmodl/ext/spdlog/details/file_helper.h create mode 100644 src/nmodl/ext/spdlog/details/fmt_helper.h create mode 100644 src/nmodl/ext/spdlog/details/log_msg.h create mode 100644 src/nmodl/ext/spdlog/details/logger_impl.h create mode 100644 src/nmodl/ext/spdlog/details/mpmc_blocking_q.h create mode 100644 src/nmodl/ext/spdlog/details/null_mutex.h create mode 100644 src/nmodl/ext/spdlog/details/os.h create mode 100644 src/nmodl/ext/spdlog/details/pattern_formatter.h create mode 100644 src/nmodl/ext/spdlog/details/periodic_worker.h create mode 100644 src/nmodl/ext/spdlog/details/registry.h create mode 100644 src/nmodl/ext/spdlog/details/thread_pool.h create mode 100644 src/nmodl/ext/spdlog/fmt/bin_to_hex.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/colors.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/core.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/format-inl.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/format.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/ostream.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/posix.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/printf.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/ranges.h create mode 100644 src/nmodl/ext/spdlog/fmt/bundled/time.h create mode 100644 src/nmodl/ext/spdlog/fmt/fmt.h create mode 100644 src/nmodl/ext/spdlog/fmt/ostr.h create mode 100644 src/nmodl/ext/spdlog/formatter.h create mode 100644 src/nmodl/ext/spdlog/logger.h create mode 100644 src/nmodl/ext/spdlog/sinks/android_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/ansicolor_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/base_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/basic_file_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/daily_file_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/dist_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/msvc_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/null_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/ostream_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/rotating_file_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h create mode 100644 src/nmodl/ext/spdlog/sinks/stdout_sinks.h create mode 100644 src/nmodl/ext/spdlog/sinks/syslog_sink.h create mode 100644 src/nmodl/ext/spdlog/sinks/wincolor_sink.h create mode 100644 src/nmodl/ext/spdlog/spdlog.h create mode 100644 src/nmodl/ext/spdlog/tweakme.h create mode 100644 src/nmodl/ext/spdlog/version.h create mode 100644 src/nmodl/utils/logger.cpp create mode 100644 src/nmodl/utils/logger.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index b8715d5613..4a953c13d7 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -53,7 +53,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in add_subdirectory(src/ast) add_subdirectory(src/codegen) -add_subdirectory(src/ext/fmt) add_subdirectory(src/language) add_subdirectory(src/lexer) add_subdirectory(src/nmodl) diff --git a/src/nmodl/ext/fmt/CMakeLists.txt b/src/nmodl/ext/fmt/CMakeLists.txt deleted file mode 100644 index 2a4f98883a..0000000000 --- a/src/nmodl/ext/fmt/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -#============================================================================= -# Fmt library sources -#============================================================================= -set(FMT_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/format.h - ${CMAKE_CURRENT_SOURCE_DIR}/format.cc -) - -#============================================================================= -# Symbol table library -#============================================================================= -add_library(fmt - STATIC - ${FMT_SOURCE_FILES} -) \ No newline at end of file diff --git a/src/nmodl/ext/fmt/core.h b/src/nmodl/ext/fmt/core.h new file mode 100644 index 0000000000..5912afef82 --- /dev/null +++ b/src/nmodl/ext/fmt/core.h @@ -0,0 +1,1502 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CORE_H_ +#define FMT_CORE_H_ + +#include <cassert> +#include <cstdio> // std::FILE +#include <cstring> +#include <iterator> +#include <string> +#include <type_traits> + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 50201 + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#if defined(__has_include) && !defined(__INTELLISENSE__) && \ + (!defined(__INTEL_COMPILER) || __INTEL_COMPILER >= 1600) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#else +# define FMT_HAS_GXX_CXX11 0 +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +#else +# define FMT_MSC_VER 0 +#endif + +// Check if relaxed C++14 constexpr is supported. +// GCC doesn't allow throw in constexpr until version 6 (bug 67371). +#ifndef FMT_USE_CONSTEXPR +# define FMT_USE_CONSTEXPR \ + (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ + (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +# define FMT_CONSTEXPR_DECL constexpr +#else +# define FMT_CONSTEXPR inline +# define FMT_CONSTEXPR_DECL +#endif + +#ifndef FMT_USE_CONSTEXPR11 +# define FMT_USE_CONSTEXPR11 \ + (FMT_MSC_VER >= 1900 || FMT_GCC_VERSION >= 406 || FMT_USE_CONSTEXPR) +#endif +#if FMT_USE_CONSTEXPR11 +# define FMT_CONSTEXPR11 constexpr +#else +# define FMT_CONSTEXPR11 +#endif + +#ifndef FMT_OVERRIDE +# if FMT_HAS_FEATURE(cxx_override) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + +#if FMT_HAS_FEATURE(cxx_explicit_conversions) || FMT_MSC_VER >= 1800 +# define FMT_EXPLICIT explicit +#else +# define FMT_EXPLICIT +#endif + +#ifndef FMT_NULL +# if FMT_HAS_FEATURE(cxx_nullptr) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600 +# define FMT_NULL nullptr +# define FMT_USE_NULLPTR 1 +# else +# define FMT_NULL NULL +# endif +#endif + +#ifndef FMT_USE_NULLPTR +# define FMT_USE_NULLPTR 0 +#endif + +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#elif FMT_MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +# define FMT_EXCEPTIONS 1 +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +# define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +# define FMT_DETECTED_NOEXCEPT noexcept +# define FMT_HAS_CXX11_NOEXCEPT 1 +#else +# define FMT_DETECTED_NOEXCEPT throw() +# define FMT_HAS_CXX11_NOEXCEPT 0 +#endif + +#ifndef FMT_NOEXCEPT +# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT +# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +# else +# define FMT_NOEXCEPT +# endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ + FMT_MSC_VER >= 1900 +# define FMT_INLINE_NAMESPACE inline namespace +# define FMT_END_NAMESPACE }} +# else +# define FMT_INLINE_NAMESPACE namespace +# define FMT_END_NAMESPACE } using namespace v5; } +# endif +# define FMT_BEGIN_NAMESPACE namespace fmt { FMT_INLINE_NAMESPACE v5 { +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_ASSERT +# define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +// libc++ supports string_view in pre-c++17. +#if (FMT_HAS_INCLUDE(<string_view>) && \ + (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +# include <string_view> +# define FMT_USE_STD_STRING_VIEW +#elif (FMT_HAS_INCLUDE(<experimental/string_view>) && \ + __cplusplus >= 201402L) +# include <experimental/string_view> +# define FMT_USE_EXPERIMENTAL_STRING_VIEW +#endif + +// std::result_of is defined in <functional> in gcc 4.4. +#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 +# include <functional> +#endif + +FMT_BEGIN_NAMESPACE +namespace internal { + +// An implementation of declval for pre-C++11 compilers such as gcc 4. +template <typename T> +typename std::add_rvalue_reference<T>::type declval() FMT_NOEXCEPT; + +template <typename> +struct result_of; + +template <typename F, typename... Args> +struct result_of<F(Args...)> { + // A workaround for gcc 4.4 that doesn't allow F to be a reference. + typedef typename std::result_of< + typename std::remove_reference<F>::type(Args...)>::type type; +}; + +// Casts nonnegative integer to unsigned. +template <typename Int> +FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) { + FMT_ASSERT(value >= 0, "negative value"); + return static_cast<typename std::make_unsigned<Int>::type>(value); +} + +// A constexpr std::char_traits::length replacement for pre-C++17. +template <typename Char> +FMT_CONSTEXPR size_t length(const Char *s) { + const Char *start = s; + while (*s) ++s; + return s - start; +} +#if FMT_GCC_VERSION +FMT_CONSTEXPR size_t length(const char *s) { return std::strlen(s); } +#endif +} // namespace internal + +/** + An implementation of ``std::basic_string_view`` for pre-C++17. It provides a + subset of the API. ``fmt::basic_string_view`` is used for format strings even + if ``std::string_view`` is available to prevent issues when a library is + compiled with a different ``-std`` option than the client code (which is not + recommended). + */ +template <typename Char> +class basic_string_view { + private: + const Char *data_; + size_t size_; + + public: + typedef Char char_type; + typedef const Char *iterator; + + // Standard basic_string_view type. +#if defined(FMT_USE_STD_STRING_VIEW) + typedef std::basic_string_view<Char> type; +#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) + typedef std::experimental::basic_string_view<Char> type; +#else + struct type { + const char *data() const { return FMT_NULL; } + size_t size() const { return 0; } + }; +#endif + + FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(FMT_NULL), size_(0) {} + + /** Constructs a string reference object from a C string and a size. */ + FMT_CONSTEXPR basic_string_view(const Char *s, size_t count) FMT_NOEXCEPT + : data_(s), size_(count) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits<Char>::length``. + \endrst + */ + FMT_CONSTEXPR basic_string_view(const Char *s) + : data_(s), size_(internal::length(s)) {} + + /** Constructs a string reference from a ``std::basic_string`` object. */ + template <typename Alloc> + FMT_CONSTEXPR basic_string_view( + const std::basic_string<Char, Alloc> &s) FMT_NOEXCEPT + : data_(s.data()), size_(s.size()) {} + + FMT_CONSTEXPR basic_string_view(type s) FMT_NOEXCEPT + : data_(s.data()), size_(s.size()) {} + + /** Returns a pointer to the string data. */ + FMT_CONSTEXPR const Char *data() const { return data_; } + + /** Returns the string size. */ + FMT_CONSTEXPR size_t size() const { return size_; } + + FMT_CONSTEXPR iterator begin() const { return data_; } + FMT_CONSTEXPR iterator end() const { return data_ + size_; } + + FMT_CONSTEXPR void remove_prefix(size_t n) { + data_ += n; + size_ -= n; + } + + // Lexicographically compare this string reference to other. + int compare(basic_string_view other) const { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits<Char>::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) != 0; + } + friend bool operator<(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) >= 0; + } +}; + +typedef basic_string_view<char> string_view; +typedef basic_string_view<wchar_t> wstring_view; + +template <typename Context> +class basic_format_arg; + +template <typename Context> +class basic_format_args; + +template <typename T> +struct no_formatter_error : std::false_type {}; + +// A formatter for objects of type T. +template <typename T, typename Char = char, typename Enable = void> +struct formatter { + static_assert(no_formatter_error<T>::value, + "don't know how to format the type, include fmt/ostream.h if it provides " + "an operator<< that should be used"); + + // The following functions are not defined intentionally. + template <typename ParseContext> + typename ParseContext::iterator parse(ParseContext &); + template <typename FormatContext> + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()); +}; + +template <typename T, typename Char, typename Enable = void> +struct convert_to_int { + enum { + value = !std::is_arithmetic<T>::value && std::is_convertible<T, int>::value + }; +}; + +namespace internal { + +/** A contiguous memory buffer with an optional growing ability. */ +template <typename T> +class basic_buffer { + private: + basic_buffer(const basic_buffer &) = delete; + void operator=(const basic_buffer &) = delete; + + T *ptr_; + std::size_t size_; + std::size_t capacity_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + basic_buffer(std::size_t sz) FMT_NOEXCEPT: size_(sz), capacity_(sz) {} + + basic_buffer(T *p = FMT_NULL, std::size_t sz = 0, std::size_t cap = 0) + FMT_NOEXCEPT: ptr_(p), size_(sz), capacity_(cap) {} + + /** Sets the buffer data and capacity. */ + void set(T *buf_data, std::size_t buf_capacity) FMT_NOEXCEPT { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + /** Increases the buffer capacity to hold at least *capacity* elements. */ + virtual void grow(std::size_t capacity) = 0; + + public: + typedef T value_type; + typedef const T &const_reference; + + virtual ~basic_buffer() {} + + T *begin() FMT_NOEXCEPT { return ptr_; } + T *end() FMT_NOEXCEPT { return ptr_ + size_; } + + /** Returns the size of this buffer. */ + std::size_t size() const FMT_NOEXCEPT { return size_; } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const FMT_NOEXCEPT { return capacity_; } + + /** Returns a pointer to the buffer data. */ + T *data() FMT_NOEXCEPT { return ptr_; } + + /** Returns a pointer to the buffer data. */ + const T *data() const FMT_NOEXCEPT { return ptr_; } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) { + reserve(new_size); + size_ = new_size; + } + + /** Clears this buffer. */ + void clear() { size_ = 0; } + + /** Reserves space to store at least *capacity* elements. */ + void reserve(std::size_t new_capacity) { + if (new_capacity > capacity_) + grow(new_capacity); + } + + void push_back(const T &value) { + reserve(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template <typename U> + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) { return ptr_[index]; } + const T &operator[](std::size_t index) const { return ptr_[index]; } +}; + +typedef basic_buffer<char> buffer; +typedef basic_buffer<wchar_t> wbuffer; + +// A container-backed buffer. +template <typename Container> +class container_buffer : public basic_buffer<typename Container::value_type> { + private: + Container &container_; + + protected: + void grow(std::size_t capacity) FMT_OVERRIDE { + container_.resize(capacity); + this->set(&container_[0], capacity); + } + + public: + explicit container_buffer(Container &c) + : basic_buffer<typename Container::value_type>(c.size()), container_(c) {} +}; + +struct error_handler { + FMT_CONSTEXPR error_handler() {} + FMT_CONSTEXPR error_handler(const error_handler &) {} + + // This function is intentionally not constexpr to give a compile-time error. + FMT_API void on_error(const char *message); +}; + +// Formatting of wide characters and strings into a narrow output is disallowed: +// fmt::format("{}", L"test"); // error +// To fix this, use a wide format string: +// fmt::format(L"{}", L"test"); +template <typename Char> +inline void require_wchar() { + static_assert( + std::is_same<wchar_t, Char>::value, + "formatting of wide characters into a narrow output is disallowed"); +} + +template <typename Char> +struct named_arg_base; + +template <typename T, typename Char> +struct named_arg; + +template <typename T> +struct is_named_arg : std::false_type {}; + +template <typename T, typename Char> +struct is_named_arg<named_arg<T, Char>> : std::true_type {}; + +enum type { + none_type, named_arg_type, + // Integer types should go first, + int_type, uint_type, long_long_type, ulong_long_type, bool_type, char_type, + last_integer_type = char_type, + // followed by floating-point types. + double_type, long_double_type, last_numeric_type = long_double_type, + cstring_type, string_type, pointer_type, custom_type +}; + +FMT_CONSTEXPR bool is_integral(type t) { + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_integer_type; +} + +FMT_CONSTEXPR bool is_arithmetic(type t) { + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_numeric_type; +} + +template <typename Char> +struct string_value { + const Char *value; + std::size_t size; +}; + +template <typename Context> +struct custom_value { + const void *value; + void (*format)(const void *arg, Context &ctx); +}; + +// A formatting argument value. +template <typename Context> +class value { + public: + typedef typename Context::char_type char_type; + + union { + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + string_value<char_type> string; + string_value<signed char> sstring; + string_value<unsigned char> ustring; + custom_value<Context> custom; + }; + + FMT_CONSTEXPR value(int val = 0) : int_value(val) {} + value(unsigned val) { uint_value = val; } + value(long long val) { long_long_value = val; } + value(unsigned long long val) { ulong_long_value = val; } + value(double val) { double_value = val; } + value(long double val) { long_double_value = val; } + value(const char_type *val) { string.value = val; } + value(const signed char *val) { + static_assert(std::is_same<char, char_type>::value, + "incompatible string types"); + sstring.value = val; + } + value(const unsigned char *val) { + static_assert(std::is_same<char, char_type>::value, + "incompatible string types"); + ustring.value = val; + } + value(basic_string_view<char_type> val) { + string.value = val.data(); + string.size = val.size(); + } + value(const void *val) { pointer = val; } + + template <typename T> + explicit value(const T &val) { + custom.value = &val; + custom.format = &format_custom_arg<T>; + } + + const named_arg_base<char_type> &as_named_arg() { + return *static_cast<const named_arg_base<char_type>*>(pointer); + } + + private: + // Formats an argument of a custom type, such as a user-defined class. + template <typename T> + static void format_custom_arg(const void *arg, Context &ctx) { + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter<T>` for `format` and + // `printf_formatter<T>` for `printf`. + typename Context::template formatter_type<T>::type f; + auto &&parse_ctx = ctx.parse_context(); + parse_ctx.advance_to(f.parse(parse_ctx)); + ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx)); + } +}; + +// Value initializer used to delay conversion to value and reduce memory churn. +template <typename Context, typename T, type TYPE> +struct init { + T val; + static const type type_tag = TYPE; + + FMT_CONSTEXPR init(const T &v) : val(v) {} + FMT_CONSTEXPR operator value<Context>() const { return value<Context>(val); } +}; + +template <typename Context, typename T> +FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value); + +#define FMT_MAKE_VALUE(TAG, ArgType, ValueType) \ + template <typename C> \ + FMT_CONSTEXPR init<C, ValueType, TAG> make_value(ArgType val) { \ + return static_cast<ValueType>(val); \ + } + +#define FMT_MAKE_VALUE_SAME(TAG, Type) \ + template <typename C> \ + FMT_CONSTEXPR init<C, Type, TAG> make_value(Type val) { return val; } + +FMT_MAKE_VALUE(bool_type, bool, int) +FMT_MAKE_VALUE(int_type, short, int) +FMT_MAKE_VALUE(uint_type, unsigned short, unsigned) +FMT_MAKE_VALUE_SAME(int_type, int) +FMT_MAKE_VALUE_SAME(uint_type, unsigned) + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +typedef std::conditional<sizeof(long) == sizeof(int), int, long long>::type + long_type; +FMT_MAKE_VALUE( + (sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) +typedef std::conditional<sizeof(unsigned long) == sizeof(unsigned), + unsigned, unsigned long long>::type ulong_type; +FMT_MAKE_VALUE( + (sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), + unsigned long, ulong_type) + +FMT_MAKE_VALUE_SAME(long_long_type, long long) +FMT_MAKE_VALUE_SAME(ulong_long_type, unsigned long long) +FMT_MAKE_VALUE(int_type, signed char, int) +FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) +FMT_MAKE_VALUE(char_type, char, int) + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +template <typename C> +inline init<C, int, char_type> make_value(wchar_t val) { + require_wchar<typename C::char_type>(); + return static_cast<int>(val); +} +#endif + +FMT_MAKE_VALUE(double_type, float, double) +FMT_MAKE_VALUE_SAME(double_type, double) +FMT_MAKE_VALUE_SAME(long_double_type, long double) + +// Formatting of wide strings into a narrow buffer and multibyte strings +// into a wide buffer is disallowed (https://github.com/fmtlib/fmt/pull/606). +FMT_MAKE_VALUE(cstring_type, typename C::char_type*, + const typename C::char_type*) +FMT_MAKE_VALUE(cstring_type, const typename C::char_type*, + const typename C::char_type*) + +FMT_MAKE_VALUE(cstring_type, signed char*, const signed char*) +FMT_MAKE_VALUE_SAME(cstring_type, const signed char*) +FMT_MAKE_VALUE(cstring_type, unsigned char*, const unsigned char*) +FMT_MAKE_VALUE_SAME(cstring_type, const unsigned char*) +FMT_MAKE_VALUE_SAME(string_type, basic_string_view<typename C::char_type>) +FMT_MAKE_VALUE(string_type, + typename basic_string_view<typename C::char_type>::type, + basic_string_view<typename C::char_type>) +FMT_MAKE_VALUE(string_type, const std::basic_string<typename C::char_type>&, + basic_string_view<typename C::char_type>) +FMT_MAKE_VALUE(pointer_type, void*, const void*) +FMT_MAKE_VALUE_SAME(pointer_type, const void*) + +#if FMT_USE_NULLPTR +FMT_MAKE_VALUE(pointer_type, std::nullptr_t, const void*) +#endif + +// Formatting of arbitrary pointers is disallowed. If you want to output a +// pointer cast it to "void *" or "const void *". In particular, this forbids +// formatting of "[const] volatile char *" which is printed as bool by +// iostreams. +template <typename C, typename T> +typename std::enable_if<!std::is_same<T, typename C::char_type>::value>::type + make_value(const T *) { + static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); +} + +template <typename C, typename T> +inline typename std::enable_if< + std::is_enum<T>::value && convert_to_int<T, typename C::char_type>::value, + init<C, int, int_type>>::type + make_value(const T &val) { return static_cast<int>(val); } + +template <typename C, typename T, typename Char = typename C::char_type> +inline typename std::enable_if< + std::is_constructible<basic_string_view<Char>, T>::value, + init<C, basic_string_view<Char>, string_type>>::type + make_value(const T &val) { return basic_string_view<Char>(val); } + +template <typename C, typename T, typename Char = typename C::char_type> +inline typename std::enable_if< + !convert_to_int<T, Char>::value && + !std::is_convertible<T, basic_string_view<Char>>::value && + !std::is_constructible<basic_string_view<Char>, T>::value, + // Implicit conversion to std::string is not handled here because it's + // unsafe: https://github.com/fmtlib/fmt/issues/729 + init<C, const T &, custom_type>>::type + make_value(const T &val) { return val; } + +template <typename C, typename T> +init<C, const void*, named_arg_type> + make_value(const named_arg<T, typename C::char_type> &val) { + basic_format_arg<C> arg = make_arg<C>(val.value); + std::memcpy(val.data, &arg, sizeof(arg)); + return static_cast<const void*>(&val); +} + +// Maximum number of arguments with packed types. +enum { max_packed_args = 15 }; + +template <typename Context> +class arg_map; +} // namespace internal + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in basic_memory_buffer. +template <typename Context> +class basic_format_arg { + private: + internal::value<Context> value_; + internal::type type_; + + template <typename ContextType, typename T> + friend FMT_CONSTEXPR basic_format_arg<ContextType> + internal::make_arg(const T &value); + + template <typename Visitor, typename Ctx> + friend FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type + visit(Visitor &&vis, const basic_format_arg<Ctx> &arg); + + friend class basic_format_args<Context>; + friend class internal::arg_map<Context>; + + typedef typename Context::char_type char_type; + + public: + class handle { + public: + explicit handle(internal::custom_value<Context> custom): custom_(custom) {} + + void format(Context &ctx) const { custom_.format(custom_.value, ctx); } + + private: + internal::custom_value<Context> custom_; + }; + + FMT_CONSTEXPR basic_format_arg() : type_(internal::none_type) {} + + FMT_EXPLICIT operator bool() const FMT_NOEXCEPT { + return type_ != internal::none_type; + } + + internal::type type() const { return type_; } + + bool is_integral() const { return internal::is_integral(type_); } + bool is_arithmetic() const { return internal::is_arithmetic(type_); } +}; + +struct monostate {}; + +/** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + ``vis(value)`` will be called with the value of type ``double``. + \endrst + */ +template <typename Visitor, typename Context> +FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type + visit(Visitor &&vis, const basic_format_arg<Context> &arg) { + typedef typename Context::char_type char_type; + switch (arg.type_) { + case internal::none_type: + break; + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + return vis(arg.value_.int_value); + case internal::uint_type: + return vis(arg.value_.uint_value); + case internal::long_long_type: + return vis(arg.value_.long_long_value); + case internal::ulong_long_type: + return vis(arg.value_.ulong_long_value); + case internal::bool_type: + return vis(arg.value_.int_value != 0); + case internal::char_type: + return vis(static_cast<char_type>(arg.value_.int_value)); + case internal::double_type: + return vis(arg.value_.double_value); + case internal::long_double_type: + return vis(arg.value_.long_double_value); + case internal::cstring_type: + return vis(arg.value_.string.value); + case internal::string_type: + return vis(basic_string_view<char_type>( + arg.value_.string.value, arg.value_.string.size)); + case internal::pointer_type: + return vis(arg.value_.pointer); + case internal::custom_type: + return vis(typename basic_format_arg<Context>::handle(arg.value_.custom)); + } + return vis(monostate()); +} + +// Parsing context consisting of a format string range being parsed and an +// argument counter for automatic indexing. +template <typename Char, typename ErrorHandler = internal::error_handler> +class basic_parse_context : private ErrorHandler { + private: + basic_string_view<Char> format_str_; + int next_arg_id_; + + public: + typedef Char char_type; + typedef typename basic_string_view<Char>::iterator iterator; + + explicit FMT_CONSTEXPR basic_parse_context( + basic_string_view<Char> format_str, ErrorHandler eh = ErrorHandler()) + : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} + + // Returns an iterator to the beginning of the format string range being + // parsed. + FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT { + return format_str_.begin(); + } + + // Returns an iterator past the end of the format string range being parsed. + FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + + // Advances the begin iterator to ``it``. + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(internal::to_unsigned(it - begin())); + } + + // Returns the next argument index. + FMT_CONSTEXPR unsigned next_arg_id(); + + FMT_CONSTEXPR bool check_arg_id(unsigned) { + if (next_arg_id_ > 0) { + on_error("cannot switch from automatic to manual argument indexing"); + return false; + } + next_arg_id_ = -1; + return true; + } + void check_arg_id(basic_string_view<Char>) {} + + FMT_CONSTEXPR void on_error(const char *message) { + ErrorHandler::on_error(message); + } + + FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; } +}; + +typedef basic_parse_context<char> parse_context; +typedef basic_parse_context<wchar_t> wparse_context; + +namespace internal { +// A map from argument names to their values for named arguments. +template <typename Context> +class arg_map { + private: + arg_map(const arg_map &) = delete; + void operator=(const arg_map &) = delete; + + typedef typename Context::char_type char_type; + + struct entry { + basic_string_view<char_type> name; + basic_format_arg<Context> arg; + }; + + entry *map_; + unsigned size_; + + void push_back(value<Context> val) { + const internal::named_arg_base<char_type> &named = val.as_named_arg(); + map_[size_] = entry{named.name, named.template deserialize<Context>()}; + ++size_; + } + + public: + arg_map() : map_(FMT_NULL), size_(0) {} + void init(const basic_format_args<Context> &args); + ~arg_map() { delete [] map_; } + + basic_format_arg<Context> find(basic_string_view<char_type> name) const { + // The list is unsorted, so just return the first matching name. + for (entry *it = map_, *end = map_ + size_; it != end; ++it) { + if (it->name == name) + return it->arg; + } + return basic_format_arg<Context>(); + } +}; + +template <typename OutputIt, typename Context, typename Char> +class context_base { + public: + typedef OutputIt iterator; + + private: + basic_parse_context<Char> parse_context_; + iterator out_; + basic_format_args<Context> args_; + + protected: + typedef Char char_type; + typedef basic_format_arg<Context> format_arg; + + context_base(OutputIt out, basic_string_view<char_type> format_str, + basic_format_args<Context> ctx_args) + : parse_context_(format_str), out_(out), args_(ctx_args) {} + + // Returns the argument with specified index. + format_arg do_get_arg(unsigned arg_id) { + format_arg arg = args_.get(arg_id); + if (!arg) + parse_context_.on_error("argument index out of range"); + return arg; + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + format_arg get_arg(unsigned arg_id) { + return this->parse_context().check_arg_id(arg_id) ? + this->do_get_arg(arg_id) : format_arg(); + } + + public: + basic_parse_context<char_type> &parse_context() { + return parse_context_; + } + + internal::error_handler error_handler() { + return parse_context_.error_handler(); + } + + void on_error(const char *message) { parse_context_.on_error(message); } + + // Returns an iterator to the beginning of the output range. + iterator out() { return out_; } + iterator begin() { return out_; } // deprecated + + // Advances the begin iterator to ``it``. + void advance_to(iterator it) { out_ = it; } + + basic_format_args<Context> args() const { return args_; } +}; + +// Extracts a reference to the container from back_insert_iterator. +template <typename Container> +inline Container &get_container(std::back_insert_iterator<Container> it) { + typedef std::back_insert_iterator<Container> bi_iterator; + struct accessor: bi_iterator { + accessor(bi_iterator iter) : bi_iterator(iter) {} + using bi_iterator::container; + }; + return *accessor(it).container; +} +} // namespace internal + +// Formatting context. +template <typename OutputIt, typename Char> +class basic_format_context : + public internal::context_base< + OutputIt, basic_format_context<OutputIt, Char>, Char> { + public: + /** The character type for the output. */ + typedef Char char_type; + + // using formatter_type = formatter<T, char_type>; + template <typename T> + struct formatter_type { typedef formatter<T, char_type> type; }; + + private: + internal::arg_map<basic_format_context> map_; + + basic_format_context(const basic_format_context &) = delete; + void operator=(const basic_format_context &) = delete; + + typedef internal::context_base<OutputIt, basic_format_context, Char> base; + typedef typename base::format_arg format_arg; + using base::get_arg; + + public: + using typename base::iterator; + + /** + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. + */ + basic_format_context(OutputIt out, basic_string_view<char_type> format_str, + basic_format_args<basic_format_context> ctx_args) + : base(out, format_str, ctx_args) {} + + format_arg next_arg() { + return this->do_get_arg(this->parse_context().next_arg_id()); + } + format_arg get_arg(unsigned arg_id) { return this->do_get_arg(arg_id); } + + // Checks if manual indexing is used and returns the argument with the + // specified name. + format_arg get_arg(basic_string_view<char_type> name); +}; + +template <typename Char> +struct buffer_context { + typedef basic_format_context< + std::back_insert_iterator<internal::basic_buffer<Char>>, Char> type; +}; +typedef buffer_context<char>::type format_context; +typedef buffer_context<wchar_t>::type wformat_context; + +namespace internal { +template <typename Context, typename T> +struct get_type { + typedef decltype(make_value<Context>( + declval<typename std::decay<T>::type&>())) value_type; + static const type value = value_type::type_tag; +}; + +template <typename Context> +FMT_CONSTEXPR11 unsigned long long get_types() { return 0; } + +template <typename Context, typename Arg, typename... Args> +FMT_CONSTEXPR11 unsigned long long get_types() { + return get_type<Context, Arg>::value | (get_types<Context, Args...>() << 4); +} + +template <typename Context, typename T> +FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value) { + basic_format_arg<Context> arg; + arg.type_ = get_type<Context, T>::value; + arg.value_ = make_value<Context>(value); + return arg; +} + +template <bool IS_PACKED, typename Context, typename T> +inline typename std::enable_if<IS_PACKED, value<Context>>::type + make_arg(const T &value) { + return make_value<Context>(value); +} + +template <bool IS_PACKED, typename Context, typename T> +inline typename std::enable_if<!IS_PACKED, basic_format_arg<Context>>::type + make_arg(const T &value) { + return make_arg<Context>(value); +} +} // namespace internal + +/** + \rst + An array of references to arguments. It can be implicitly converted into + `~fmt::basic_format_args` for passing into type-erased formatting functions + such as `~fmt::vformat`. + \endrst + */ +template <typename Context, typename ...Args> +class format_arg_store { + private: + static const size_t NUM_ARGS = sizeof...(Args); + + // Packed is a macro on MinGW so use IS_PACKED instead. + static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; + + typedef typename std::conditional<IS_PACKED, + internal::value<Context>, basic_format_arg<Context>>::type value_type; + + // If the arguments are not packed, add one more element to mark the end. + static const size_t DATA_SIZE = + NUM_ARGS + (IS_PACKED && NUM_ARGS != 0 ? 0 : 1); + value_type data_[DATA_SIZE]; + + friend class basic_format_args<Context>; + + static FMT_CONSTEXPR11 long long get_types() { + return IS_PACKED ? + static_cast<long long>(internal::get_types<Context, Args...>()) : + -static_cast<long long>(NUM_ARGS); + } + + public: +#if FMT_USE_CONSTEXPR11 + static FMT_CONSTEXPR11 long long TYPES = get_types(); +#else + static const long long TYPES; +#endif + +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 405) || \ + (FMT_MSC_VER && FMT_MSC_VER <= 1800) + // Workaround array initialization issues in gcc <= 4.5 and MSVC <= 2013. + format_arg_store(const Args &... args) { + value_type init[DATA_SIZE] = + {internal::make_arg<IS_PACKED, Context>(args)...}; + std::memcpy(data_, init, sizeof(init)); + } +#else + format_arg_store(const Args &... args) + : data_{internal::make_arg<IS_PACKED, Context>(args)...} {} +#endif +}; + +#if !FMT_USE_CONSTEXPR11 +template <typename Context, typename ...Args> +const long long format_arg_store<Context, Args...>::TYPES = get_types(); +#endif + +/** + \rst + Constructs an `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::format_args`. `Context` + can be omitted in which case it defaults to `~fmt::context`. + \endrst + */ +template <typename Context, typename ...Args> +inline format_arg_store<Context, Args...> + make_format_args(const Args & ... args) { + return format_arg_store<Context, Args...>(args...); +} + +template <typename ...Args> +inline format_arg_store<format_context, Args...> + make_format_args(const Args & ... args) { + return format_arg_store<format_context, Args...>(args...); +} + +/** Formatting arguments. */ +template <typename Context> +class basic_format_args { + public: + typedef unsigned size_type; + typedef basic_format_arg<Context> format_arg; + + private: + // To reduce compiled code size per formatting function call, types of first + // max_packed_args arguments are passed in the types_ field. + unsigned long long types_; + union { + // If the number of arguments is less than max_packed_args, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::value<Context> *values_; + const format_arg *args_; + }; + + typename internal::type type(unsigned index) const { + unsigned shift = index * 4; + unsigned long long mask = 0xf; + return static_cast<typename internal::type>( + (types_ & (mask << shift)) >> shift); + } + + friend class internal::arg_map<Context>; + + void set_data(const internal::value<Context> *values) { values_ = values; } + void set_data(const format_arg *args) { args_ = args; } + + format_arg do_get(size_type index) const { + format_arg arg; + long long signed_types = static_cast<long long>(types_); + if (signed_types < 0) { + unsigned long long num_args = + static_cast<unsigned long long>(-signed_types); + if (index < num_args) + arg = args_[index]; + return arg; + } + if (index > internal::max_packed_args) + return arg; + arg.type_ = type(index); + if (arg.type_ == internal::none_type) + return arg; + internal::value<Context> &val = arg.value_; + val = values_[index]; + return arg; + } + + public: + basic_format_args() : types_(0) {} + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template <typename... Args> + basic_format_args(const format_arg_store<Context, Args...> &store) + : types_(static_cast<unsigned long long>(store.TYPES)) { + set_data(store.data_); + } + + /** + \rst + Constructs a `basic_format_args` object from a dynamic set of arguments. + \endrst + */ + basic_format_args(const format_arg *args, size_type count) + : types_(-static_cast<int64_t>(count)) { + set_data(args); + } + + /** Returns the argument at specified index. */ + format_arg get(size_type index) const { + format_arg arg = do_get(index); + if (arg.type_ == internal::named_arg_type) + arg = arg.value_.as_named_arg().template deserialize<Context>(); + return arg; + } + + unsigned max_size() const { + long long signed_types = static_cast<long long>(types_); + return static_cast<unsigned>( + signed_types < 0 ? + -signed_types : static_cast<long long>(internal::max_packed_args)); + } +}; + +/** An alias to ``basic_format_args<context>``. */ +// It is a separate type rather than a typedef to make symbols readable. +struct format_args: basic_format_args<format_context> { + template <typename ...Args> + format_args(Args && ... arg) + : basic_format_args<format_context>(std::forward<Args>(arg)...) {} +}; +struct wformat_args : basic_format_args<wformat_context> { + template <typename ...Args> + wformat_args(Args && ... arg) + : basic_format_args<wformat_context>(std::forward<Args>(arg)...) {} +}; + +namespace internal { +template <typename Char> +struct named_arg_base { + basic_string_view<Char> name; + + // Serialized value<context>. + mutable char data[sizeof(basic_format_arg<format_context>)]; + + named_arg_base(basic_string_view<Char> nm) : name(nm) {} + + template <typename Context> + basic_format_arg<Context> deserialize() const { + basic_format_arg<Context> arg; + std::memcpy(&arg, data, sizeof(basic_format_arg<Context>)); + return arg; + } +}; + +template <typename T, typename Char> +struct named_arg : named_arg_base<Char> { + const T &value; + + named_arg(basic_string_view<Char> name, const T &val) + : named_arg_base<Char>(name), value(val) {} +}; +} + +/** + \rst + Returns a named argument to be used in a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template <typename T> +inline internal::named_arg<T, char> arg(string_view name, const T &arg) { + return internal::named_arg<T, char>(name, arg); +} + +template <typename T> +inline internal::named_arg<T, wchar_t> arg(wstring_view name, const T &arg) { + return internal::named_arg<T, wchar_t>(name, arg); +} + +// This function template is deleted intentionally to disable nested named +// arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template <typename S, typename T, typename Char> +void arg(S, internal::named_arg<T, Char>) = delete; + +// A base class for compile-time strings. It is defined in the fmt namespace to +// make formatting functions visible via ADL, e.g. format(fmt("{}"), 42). +struct compile_string {}; + +namespace internal { +// If S is a format string type, format_string_traints<S>::char_type gives its +// character type. +template <typename S, typename Enable = void> +struct format_string_traits { + private: + // Use constructability as a way to detect if format_string_traits is + // specialized because other methods are broken on MSVC2013. + format_string_traits(); +}; + +template <typename Char> +struct format_string_traits_base { typedef Char char_type; }; + +template <typename Char> +struct format_string_traits<Char *> : format_string_traits_base<Char> {}; + +template <typename Char> +struct format_string_traits<const Char *> : format_string_traits_base<Char> {}; + +template <typename Char, std::size_t N> +struct format_string_traits<Char[N]> : format_string_traits_base<Char> {}; + +template <typename Char, std::size_t N> +struct format_string_traits<const Char[N]> : format_string_traits_base<Char> {}; + +template <typename Char> +struct format_string_traits<std::basic_string<Char>> : + format_string_traits_base<Char> {}; + +template <typename S> +struct format_string_traits< + S, typename std::enable_if<std::is_base_of< + basic_string_view<typename S::char_type>, S>::value>::type> : + format_string_traits_base<typename S::char_type> {}; + +template <typename S> +struct is_format_string : + std::integral_constant< + bool, std::is_constructible<format_string_traits<S>>::value> {}; + +template <typename S> +struct is_compile_string : + std::integral_constant<bool, std::is_base_of<compile_string, S>::value> {}; + +template <typename... Args, typename S> +inline typename std::enable_if<!is_compile_string<S>::value>::type + check_format_string(const S &) {} +template <typename... Args, typename S> +typename std::enable_if<is_compile_string<S>::value>::type + check_format_string(S); + +template <typename Char> +std::basic_string<Char> vformat( + basic_string_view<Char> format_str, + basic_format_args<typename buffer_context<Char>::type> args); +} // namespace internal + +format_context::iterator vformat_to( + internal::buffer &buf, string_view format_str, format_args args); +wformat_context::iterator vformat_to( + internal::wbuffer &buf, wstring_view format_str, wformat_args args); + +template <typename Container> +struct is_contiguous : std::false_type {}; + +template <typename Char> +struct is_contiguous<std::basic_string<Char>> : std::true_type {}; + +template <typename Char> +struct is_contiguous<internal::basic_buffer<Char>> : std::true_type {}; + +/** Formats a string and writes the output to ``out``. */ +template <typename Container> +typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + vformat_to(std::back_insert_iterator<Container> out, + string_view format_str, format_args args) { + internal::container_buffer<Container> buf(internal::get_container(out)); + vformat_to(buf, format_str, args); + return out; +} + +template <typename Container> +typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + vformat_to(std::back_insert_iterator<Container> out, + wstring_view format_str, wformat_args args) { + internal::container_buffer<Container> buf(internal::get_container(out)); + vformat_to(buf, format_str, args); + return out; +} + +template <typename Container, typename... Args> +inline typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + format_to(std::back_insert_iterator<Container> out, + string_view format_str, const Args & ... args) { + format_arg_store<format_context, Args...> as{args...}; + return vformat_to(out, format_str, as); +} + +template <typename Container, typename... Args> +inline typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + format_to(std::back_insert_iterator<Container> out, + wstring_view format_str, const Args & ... args) { + return vformat_to(out, format_str, + make_format_args<wformat_context>(args...)); +} + +template < + typename String, + typename Char = typename internal::format_string_traits<String>::char_type> +inline std::basic_string<Char> vformat( + const String &format_str, + basic_format_args<typename buffer_context<Char>::type> args) { + // Convert format string to string_view to reduce the number of overloads. + return internal::vformat(basic_string_view<Char>(format_str), args); +} + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + #include <fmt/core.h> + std::string message = fmt::format("The answer is {}", 42); + \endrst +*/ +template <typename String, typename... Args> +inline std::basic_string< + typename internal::format_string_traits<String>::char_type> + format(const String &format_str, const Args & ... args) { + internal::check_format_string<Args...>(format_str); + // This should be just + // return vformat(format_str, make_format_args(args...)); + // but gcc has trouble optimizing the latter, so break it down. + typedef typename internal::format_string_traits<String>::char_type char_t; + typedef typename buffer_context<char_t>::type context_t; + format_arg_store<context_t, Args...> as{args...}; + return internal::vformat( + basic_string_view<char_t>(format_str), basic_format_args<context_t>(as)); +} + +FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); +FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template <typename... Args> +inline void print(std::FILE *f, string_view format_str, const Args & ... args) { + format_arg_store<format_context, Args...> as(args...); + vprint(f, format_str, as); +} +/** + Prints formatted data to the file *f* which should be in wide-oriented mode + set via ``fwide(f, 1)`` or ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. + */ +template <typename... Args> +inline void print(std::FILE *f, wstring_view format_str, + const Args & ... args) { + format_arg_store<wformat_context, Args...> as(args...); + vprint(f, format_str, as); +} + +FMT_API void vprint(string_view format_str, format_args args); +FMT_API void vprint(wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template <typename... Args> +inline void print(string_view format_str, const Args & ... args) { + format_arg_store<format_context, Args...> as{args...}; + vprint(format_str, as); +} + +template <typename... Args> +inline void print(wstring_view format_str, const Args & ... args) { + format_arg_store<wformat_context, Args...> as(args...); + vprint(format_str, as); +} +FMT_END_NAMESPACE + +#endif // FMT_CORE_H_ diff --git a/src/nmodl/ext/fmt/format-inl.h b/src/nmodl/ext/fmt/format-inl.h new file mode 100644 index 0000000000..56c4d581df --- /dev/null +++ b/src/nmodl/ext/fmt/format-inl.h @@ -0,0 +1,866 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#include "format.h" + +#include <string.h> + +#include <cctype> +#include <cerrno> +#include <climits> +#include <cmath> +#include <cstdarg> +#include <cstddef> // for std::ptrdiff_t +#include <cstring> // for std::memmove +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +# include <locale> +#endif + +#if FMT_USE_WINDOWS_H +# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +# include <windows.h> +# else +# define NOMINMAX +# include <windows.h> +# undef NOMINMAX +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +# pragma warning(disable: 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +inline fmt::internal::null<> strerror_r(int, char *, ...) { + return fmt::internal::null<>(); +} +inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) { + return fmt::internal::null<>(); +} + +FMT_BEGIN_NAMESPACE + +namespace { + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +# define FMT_SWPRINTF snwprintf +#else +# define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +typedef void (*FormatFunc)(internal::buffer &, int, string_view); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { + FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); + + class dispatcher { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const dispatcher &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? + ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + dispatcher(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return dispatcher(error_code, buffer, buffer_size).run(); +} + +void format_error_code(internal::buffer &out, int error_code, + string_view message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::int_traits<int>::main_type main_type; + main_type abs_value = static_cast<main_type>(error_code); + if (internal::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + writer w(out); + if (message.size() <= inline_buffer_size - error_code_size) { + w.write(message); + w.write(SEP); + } + w.write(ERROR_STR); + w.write(error_code); + assert(out.size() <= inline_buffer_size); +} + +void report_error(FormatFunc func, int error_code, + string_view message) FMT_NOEXCEPT { + memory_buffer full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +class locale { + private: + std::locale locale_; + + public: + explicit locale(std::locale loc = std::locale()) : locale_(loc) {} + std::locale get() { return locale_; } +}; + +FMT_FUNC size_t internal::count_code_points(u8string_view s) { + const char8_t *data = s.data(); + int num_code_points = 0; + for (size_t i = 0, size = s.size(); i != size; ++i) { + if ((data[i].value & 0xc0) != 0x80) + ++num_code_points; + } + return num_code_points; +} + +template <typename Char> +FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { + std::locale loc = lp ? lp->locale().get() : std::locale(); + return std::use_facet<std::numpunct<Char>>(loc).thousands_sep(); +} +#else +template <typename Char> +FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { + return FMT_STATIC_THOUSANDS_SEPARATOR; +} +#endif + +FMT_FUNC void system_error::init( + int err_code, string_view format_str, format_args args) { + error_code_ = err_code; + memory_buffer buffer; + format_system_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +namespace internal { +template <typename T> +int char_traits<char>::format_float( + char *buffer, std::size_t size, const char *format, int precision, T value) { + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, value) : + FMT_SNPRINTF(buffer, size, format, precision, value); +} + +template <typename T> +int char_traits<wchar_t>::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, + T value) { + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, value) : + FMT_SWPRINTF(buffer, size, format, precision, value); +} + +template <typename T> +const char basic_data<T>::DIGITS[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 + +template <typename T> +const uint32_t basic_data<T>::POWERS_OF_10_32[] = { + 1, FMT_POWERS_OF_10(1) +}; + +template <typename T> +const uint32_t basic_data<T>::ZERO_OR_POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template <typename T> +const uint64_t basic_data<T>::ZERO_OR_POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(1000000000ull), + 10000000000000000000ull +}; + +// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. +// These are generated by support/compute-powers.py. +template <typename T> +const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = { + 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, + 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, + 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, + 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, + 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, + 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, + 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, + 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, + 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, + 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, + 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, + 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, + 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, + 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, + 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, + 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, + 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, + 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, + 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, + 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, + 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, + 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, + 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, + 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, + 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, + 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, + 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, + 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, + 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, +}; + +// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding +// to significands above. +template <typename T> +const int16_t basic_data<T>::POW10_EXPONENTS[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, + -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, + -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, + -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, + -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, + 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, + 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, + 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066 +}; + +template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m"; +template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m"; + +// A handmade floating-point number f * pow(2, e). +class fp { + private: + typedef uint64_t significand_type; + + // All sizes are in bits. + static FMT_CONSTEXPR_DECL const int char_size = + std::numeric_limits<unsigned char>::digits; + // Subtract 1 to account for an implicit most significant bit in the + // normalized form. + static FMT_CONSTEXPR_DECL const int double_significand_size = + std::numeric_limits<double>::digits - 1; + static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = + 1ull << double_significand_size; + + public: + significand_type f; + int e; + + static FMT_CONSTEXPR_DECL const int significand_size = + sizeof(significand_type) * char_size; + + fp(): f(0), e(0) {} + fp(uint64_t f, int e): f(f), e(e) {} + + // Constructs fp from an IEEE754 double. It is a template to prevent compile + // errors on platforms where double is not IEEE754. + template <typename Double> + explicit fp(Double d) { + // Assume double is in the format [sign][exponent][significand]. + typedef std::numeric_limits<Double> limits; + const int double_size = static_cast<int>(sizeof(Double) * char_size); + const int exponent_size = + double_size - double_significand_size - 1; // -1 for sign + const uint64_t significand_mask = implicit_bit - 1; + const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; + const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; + auto u = bit_cast<uint64_t>(d); + auto biased_e = (u & exponent_mask) >> double_significand_size; + f = u & significand_mask; + if (biased_e != 0) + f += implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + e = static_cast<int>(biased_e - exponent_bias - double_significand_size); + } + + // Normalizes the value converted from double and multiplied by (1 << SHIFT). + template <int SHIFT = 0> + void normalize() { + // Handle subnormals. + auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((f & shifted_implicit_bit) == 0) { + f <<= 1; + --e; + } + // Subtract 1 to account for hidden bit. + auto offset = significand_size - double_significand_size - SHIFT - 1; + f <<= offset; + e -= offset; + } + + // Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where + // a boundary is a value half way between the number and its predecessor + // (lower) or successor (upper). The upper boundary is normalized and lower + // has the same exponent but may be not normalized. + void compute_boundaries(fp &lower, fp &upper) const { + lower = f == implicit_bit ? + fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); + upper = fp((f << 1) + 1, e - 1); + upper.normalize<1>(); // 1 is to account for the exponent shift above. + lower.f <<= lower.e - upper.e; + lower.e = upper.e; + } +}; + +// Returns an fp number representing x - y. Result may not be normalized. +inline fp operator-(fp x, fp y) { + FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands"); + return fp(x.f - y.f, x.e); +} + +// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest +// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be normalized. +FMT_API fp operator*(fp x, fp y); + +// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its +// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3. +FMT_API fp get_cached_power(int min_exponent, int &pow10_exponent); + +FMT_FUNC fp operator*(fp x, fp y) { + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = x.f >> 32, b = x.f & mask; + uint64_t c = y.f >> 32, d = y.f & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64); +} + +FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) { + const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10) + int index = static_cast<int>(std::ceil( + (min_exponent + fp::significand_size - 1) * one_over_log2_10)); + // Decimal exponent of the first (smallest) cached power of 10. + const int first_dec_exp = -348; + // Difference between 2 consecutive decimal exponents in cached powers of 10. + const int dec_exp_step = 8; + index = (index - first_dec_exp - 1) / dec_exp_step + 1; + pow10_exponent = first_dec_exp + index * dec_exp_step; + return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]); +} + +// Generates output using Grisu2 digit-gen algorithm. +FMT_FUNC void grisu2_gen_digits( + const fp &scaled_value, const fp &scaled_upper, uint64_t delta, + char *buffer, size_t &size, int &dec_exp) { + internal::fp one(1ull << -scaled_upper.e, scaled_upper.e); + // hi (p1 in Grisu) contains the most significant digits of scaled_upper. + // hi = floor(scaled_upper / one). + uint32_t hi = static_cast<uint32_t>(scaled_upper.f >> -one.e); + // lo (p2 in Grisu) contains the least significants digits of scaled_upper. + // lo = scaled_upper mod 1. + uint64_t lo = scaled_upper.f & (one.f - 1); + size = 0; + auto exp = count_digits(hi); // kappa in Grisu. + while (exp > 0) { + uint32_t digit = 0; + // This optimization by miloyip reduces the number of integer divisions by + // one per iteration. + switch (exp) { + case 10: digit = hi / 1000000000; hi %= 1000000000; break; + case 9: digit = hi / 100000000; hi %= 100000000; break; + case 8: digit = hi / 10000000; hi %= 10000000; break; + case 7: digit = hi / 1000000; hi %= 1000000; break; + case 6: digit = hi / 100000; hi %= 100000; break; + case 5: digit = hi / 10000; hi %= 10000; break; + case 4: digit = hi / 1000; hi %= 1000; break; + case 3: digit = hi / 100; hi %= 100; break; + case 2: digit = hi / 10; hi %= 10; break; + case 1: digit = hi; hi = 0; break; + default: + FMT_ASSERT(false, "invalid number of digits"); + } + if (digit != 0 || size != 0) + buffer[size++] = static_cast<char>('0' + digit); + --exp; + uint64_t remainder = (static_cast<uint64_t>(hi) << -one.e) + lo; + if (remainder <= delta) { + dec_exp += exp; + // TODO: use scaled_value + (void)scaled_value; + return; + } + } + for (;;) { + lo *= 10; + delta *= 10; + char digit = static_cast<char>(lo >> -one.e); + if (digit != 0 || size != 0) + buffer[size++] = static_cast<char>('0' + digit); + lo &= one.f - 1; + --exp; + if (lo < delta) { + dec_exp += exp; + return; + } + } +} + +FMT_FUNC void grisu2_format_positive(double value, char *buffer, size_t &size, + int &dec_exp) { + FMT_ASSERT(value > 0, "value is nonpositive"); + fp fp_value(value); + fp lower, upper; // w^- and w^+ in the Grisu paper. + fp_value.compute_boundaries(lower, upper); + // Find a cached power of 10 close to 1 / upper. + const int min_exp = -60; // alpha in Grisu. + auto dec_pow = get_cached_power( // \tilde{c}_{-k} in Grisu. + min_exp - (upper.e + fp::significand_size), dec_exp); + dec_exp = -dec_exp; + fp_value.normalize(); + fp scaled_value = fp_value * dec_pow; + fp scaled_lower = lower * dec_pow; // \tilde{M}^- in Grisu. + fp scaled_upper = upper * dec_pow; // \tilde{M}^+ in Grisu. + ++scaled_lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}. + --scaled_upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}. + uint64_t delta = scaled_upper.f - scaled_lower.f; + grisu2_gen_digits(scaled_value, scaled_upper, delta, buffer, size, dec_exp); +} + +FMT_FUNC void round(char *buffer, size_t &size, int &exp, + int digits_to_remove) { + size -= to_unsigned(digits_to_remove); + exp += digits_to_remove; + int digit = buffer[size] - '0'; + // TODO: proper rounding and carry + if (digit > 5 || (digit == 5 && (digits_to_remove > 1 || + (buffer[size - 1] - '0') % 2) != 0)) { + ++buffer[size - 1]; + } +} + +// Writes the exponent exp in the form "[+-]d{1,3}" to buffer. +FMT_FUNC char *write_exponent(char *buffer, int exp) { + FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range"); + if (exp < 0) { + *buffer++ = '-'; + exp = -exp; + } else { + *buffer++ = '+'; + } + if (exp >= 100) { + *buffer++ = static_cast<char>('0' + exp / 100); + exp %= 100; + const char *d = data::DIGITS + exp * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } else { + const char *d = data::DIGITS + exp * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + return buffer; +} + +FMT_FUNC void format_exp_notation( + char *buffer, size_t &size, int exp, int precision, bool upper) { + // Insert a decimal point after the first digit and add an exponent. + std::memmove(buffer + 2, buffer + 1, size - 1); + buffer[1] = '.'; + exp += static_cast<int>(size) - 1; + int num_digits = precision - static_cast<int>(size) + 1; + if (num_digits > 0) { + std::uninitialized_fill_n(buffer + size + 1, num_digits, '0'); + size += to_unsigned(num_digits); + } else if (num_digits < 0) { + round(buffer, size, exp, -num_digits); + } + char *p = buffer + size + 1; + *p++ = upper ? 'E' : 'e'; + size = to_unsigned(write_exponent(p, exp) - buffer); +} + +// Prettifies the output of the Grisu2 algorithm. +// The number is given as v = buffer * 10^exp. +FMT_FUNC void grisu2_prettify(char *buffer, size_t &size, int exp, + int precision, bool upper) { + // pow(10, full_exp - 1) <= v <= pow(10, full_exp). + int int_size = static_cast<int>(size); + int full_exp = int_size + exp; + const int exp_threshold = 21; + if (int_size <= full_exp && full_exp <= exp_threshold) { + // 1234e7 -> 12340000000[.0+] + std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0'); + char *p = buffer + full_exp; + if (precision > 0) { + *p++ = '.'; + std::uninitialized_fill_n(p, precision, '0'); + p += precision; + } + size = to_unsigned(p - buffer); + } else if (0 < full_exp && full_exp <= exp_threshold) { + // 1234e-2 -> 12.34[0+] + int fractional_size = -exp; + std::memmove(buffer + full_exp + 1, buffer + full_exp, + to_unsigned(fractional_size)); + buffer[full_exp] = '.'; + int num_zeros = precision - fractional_size; + if (num_zeros > 0) { + std::uninitialized_fill_n(buffer + size + 1, num_zeros, '0'); + size += to_unsigned(num_zeros); + } + ++size; + } else if (-6 < full_exp && full_exp <= 0) { + // 1234e-6 -> 0.001234 + int offset = 2 - full_exp; + std::memmove(buffer + offset, buffer, size); + buffer[0] = '0'; + buffer[1] = '.'; + std::uninitialized_fill_n(buffer + 2, -full_exp, '0'); + size = to_unsigned(int_size + offset); + } else { + format_exp_notation(buffer, size, exp, precision, upper); + } +} + +#if FMT_CLANG_VERSION +# define FMT_FALLTHROUGH [[clang::fallthrough]]; +#elif FMT_GCC_VERSION >= 700 +# define FMT_FALLTHROUGH [[gnu::fallthrough]]; +#else +# define FMT_FALLTHROUGH +#endif + +// Formats a nonnegative value using Grisu2 algorithm. Grisu2 doesn't give any +// guarantees on the shortness of the result. +FMT_FUNC void grisu2_format(double value, char *buffer, size_t &size, char type, + int precision, bool write_decimal_point) { + FMT_ASSERT(value >= 0, "value is negative"); + int dec_exp = 0; // K in Grisu. + if (value > 0) { + grisu2_format_positive(value, buffer, size, dec_exp); + } else { + *buffer = '0'; + size = 1; + } + const int default_precision = 6; + if (precision < 0) + precision = default_precision; + bool upper = false; + switch (type) { + case 'G': + upper = true; + FMT_FALLTHROUGH + case '\0': case 'g': { + int digits_to_remove = static_cast<int>(size) - precision; + if (digits_to_remove > 0) { + round(buffer, size, dec_exp, digits_to_remove); + // Remove trailing zeros. + while (size > 0 && buffer[size - 1] == '0') { + --size; + ++dec_exp; + } + } + precision = 0; + break; + } + case 'F': + upper = true; + FMT_FALLTHROUGH + case 'f': { + int digits_to_remove = -dec_exp - precision; + if (digits_to_remove > 0) { + if (digits_to_remove >= static_cast<int>(size)) + digits_to_remove = static_cast<int>(size) - 1; + round(buffer, size, dec_exp, digits_to_remove); + } + break; + } + case 'e': case 'E': + format_exp_notation(buffer, size, dec_exp, precision, type == 'E'); + return; + } + if (write_decimal_point && precision < 1) + precision = 1; + grisu2_prettify(buffer, size, dec_exp, precision, upper); +} +} // namespace internal + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast<int>(s.size()); + if (s_size == 0) { + // MultiByteToWideChar does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return; + } + + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { + if (int error_code = convert(s)) { + FMT_THROW(windows_error(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) { + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast<int>(s.size()); + if (s_size == 0) { + // WideCharToMultiByte does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return 0; + } + + int length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void windows_error::init( + int err_code, string_view format_str, format_args args) { + error_code_ = err_code; + memory_buffer buffer; + internal::format_windows_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +FMT_FUNC void internal::format_windows_error( + internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { + FMT_TRY { + wmemory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + wchar_t *system_message = &buf[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_message, static_cast<uint32_t>(buf.size()), FMT_NULL); + if (result != 0) { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + writer w(out); + w.write(message); + w.write(": "); + w.write(utf8_message); + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error( + internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { + FMT_TRY { + memory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + char *system_message = &buf[0]; + int result = safe_strerror(error_code, system_message, buf.size()); + if (result == 0) { + writer w(out); + w.write(message); + w.write(": "); + w.write(system_message); + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +template <typename Char> +void basic_fixed_buffer<Char>::grow(std::size_t) { + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC void internal::error_handler::on_error(const char *message) { + FMT_THROW(format_error(message)); +} + +FMT_FUNC void report_system_error( + int error_code, fmt::string_view message) FMT_NOEXCEPT { + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error( + int error_code, fmt::string_view message) FMT_NOEXCEPT { + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) { + memory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), 1, buffer.size(), f); +} + +FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) { + wmemory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f); +} + +FMT_FUNC void vprint(string_view format_str, format_args args) { + vprint(stdout, format_str, args); +} + +FMT_FUNC void vprint(wstring_view format_str, wformat_args args) { + vprint(stdout, format_str, args); +} + +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +FMT_FUNC locale locale_provider::locale() { return fmt::locale(); } +#endif + +FMT_END_NAMESPACE + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif // FMT_FORMAT_INL_H_ diff --git a/src/nmodl/ext/fmt/format.cc b/src/nmodl/ext/fmt/format.cc deleted file mode 100644 index 2d236bc641..0000000000 --- a/src/nmodl/ext/fmt/format.cc +++ /dev/null @@ -1,495 +0,0 @@ -/* - Formatting library for C++ - - Copyright (c) 2012 - 2016, Victor Zverovich - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "format.h" - -#include <string.h> - -#include <cctype> -#include <cerrno> -#include <climits> -#include <cmath> -#include <cstdarg> -#include <cstddef> // for std::ptrdiff_t - -#if defined(_WIN32) && defined(__MINGW32__) -# include <cstring> -#endif - -#if FMT_USE_WINDOWS_H -# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) -# define WIN32_LEAN_AND_MEAN -# endif -# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) -# include <windows.h> -# else -# define NOMINMAX -# include <windows.h> -# undef NOMINMAX -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4702) // unreachable code -// Disable deprecation warning for strerror. The latter is not called but -// MSVC fails to detect it. -# pragma warning(disable: 4996) -#endif - -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -FMT_MAYBE_UNUSED -static inline fmt::internal::Null<> strerror_r(int, char *, ...) { - return fmt::internal::Null<>(); -} -FMT_MAYBE_UNUSED -static inline fmt::internal::Null<> strerror_s(char *, std::size_t, ...) { - return fmt::internal::Null<>(); -} - -namespace fmt { - -FMT_FUNC internal::RuntimeError::~RuntimeError() FMT_DTOR_NOEXCEPT {} -FMT_FUNC FormatError::~FormatError() FMT_DTOR_NOEXCEPT {} -FMT_FUNC SystemError::~SystemError() FMT_DTOR_NOEXCEPT {} - -namespace { - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER -inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; -} -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) -# define FMT_SWPRINTF snwprintf -#else -# define FMT_SWPRINTF swprintf -#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) - -const char RESET_COLOR[] = "\x1b[0m"; - -typedef void (*FormatFunc)(Writer &, int, StringRef); - -// Portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -int safe_strerror( - int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); - - class StrError { - private: - int error_code_; - char *&buffer_; - std::size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const StrError &) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - int handle(char *message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - int handle(internal::Null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? - ERANGE : result; - } - -#ifdef __c2__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdeprecated-declarations" -#endif - - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::Null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } - -#ifdef __c2__ -# pragma clang diagnostic pop -#endif - - public: - StrError(int err_code, char *&buf, std::size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { - return handle(strerror_r(error_code_, buffer_, buffer_size_)); - } - }; - return StrError(error_code, buffer, buffer_size).run(); -} - -void format_error_code(Writer &out, int error_code, - StringRef message) FMT_NOEXCEPT { - // Report error code making sure that the output fits into - // INLINE_BUFFER_SIZE to avoid dynamic memory allocation and potential - // bad_alloc. - out.clear(); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - typedef internal::IntTraits<int>::MainType MainType; - MainType abs_value = static_cast<MainType>(error_code); - if (internal::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += internal::count_digits(abs_value); - if (message.size() <= internal::INLINE_BUFFER_SIZE - error_code_size) - out << message << SEP; - out << ERROR_STR << error_code; - assert(out.size() <= internal::INLINE_BUFFER_SIZE); -} - -void report_error(FormatFunc func, int error_code, - StringRef message) FMT_NOEXCEPT { - MemoryWriter full_message; - func(full_message, error_code, message); - // Use Writer::data instead of Writer::c_str to avoid potential memory - // allocation. - std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); -} -} // namespace - -FMT_FUNC void SystemError::init( - int err_code, CStringRef format_str, ArgList args) { - error_code_ = err_code; - MemoryWriter w; - format_system_error(w, err_code, format(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(w.str()); -} - -template <typename T> -int internal::CharTraits<char>::format_float( - char *buffer, std::size_t size, const char *format, - unsigned width, int precision, T value) { - if (width == 0) { - return precision < 0 ? - FMT_SNPRINTF(buffer, size, format, value) : - FMT_SNPRINTF(buffer, size, format, precision, value); - } - return precision < 0 ? - FMT_SNPRINTF(buffer, size, format, width, value) : - FMT_SNPRINTF(buffer, size, format, width, precision, value); -} - -template <typename T> -int internal::CharTraits<wchar_t>::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, - unsigned width, int precision, T value) { - if (width == 0) { - return precision < 0 ? - FMT_SWPRINTF(buffer, size, format, value) : - FMT_SWPRINTF(buffer, size, format, precision, value); - } - return precision < 0 ? - FMT_SWPRINTF(buffer, size, format, width, value) : - FMT_SWPRINTF(buffer, size, format, width, precision, value); -} - -template <typename T> -const char internal::BasicData<T>::DIGITS[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; - -#define FMT_POWERS_OF_10(factor) \ - factor * 10, \ - factor * 100, \ - factor * 1000, \ - factor * 10000, \ - factor * 100000, \ - factor * 1000000, \ - factor * 10000000, \ - factor * 100000000, \ - factor * 1000000000 - -template <typename T> -const uint32_t internal::BasicData<T>::POWERS_OF_10_32[] = { - 0, FMT_POWERS_OF_10(1) -}; - -template <typename T> -const uint64_t internal::BasicData<T>::POWERS_OF_10_64[] = { - 0, - FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(ULongLong(1000000000)), - // Multiply several constants instead of using a single long long constant - // to avoid warnings about C++98 not supporting long long. - ULongLong(1000000000) * ULongLong(1000000000) * 10 -}; - -FMT_FUNC void internal::report_unknown_type(char code, const char *type) { - (void)type; - if (std::isprint(static_cast<unsigned char>(code))) { - FMT_THROW(FormatError( - format("unknown format code '{}' for {}", code, type))); - } - FMT_THROW(FormatError( - format("unknown format code '\\x{:02x}' for {}", - static_cast<unsigned>(code), type))); -} - -#if FMT_USE_WINDOWS_H - -FMT_FUNC internal::UTF8ToUTF16::UTF8ToUTF16(StringRef s) { - static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; - if (s.size() > INT_MAX) - FMT_THROW(WindowsError(ERROR_INVALID_PARAMETER, ERROR_MSG)); - int s_size = static_cast<int>(s.size()); - int length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); - if (length == 0) - FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); - buffer_.resize(length + 1); - length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); - if (length == 0) - FMT_THROW(WindowsError(GetLastError(), ERROR_MSG)); - buffer_[length] = 0; -} - -FMT_FUNC internal::UTF16ToUTF8::UTF16ToUTF8(WStringRef s) { - if (int error_code = convert(s)) { - FMT_THROW(WindowsError(error_code, - "cannot convert string from UTF-16 to UTF-8")); - } -} - -FMT_FUNC int internal::UTF16ToUTF8::convert(WStringRef s) { - if (s.size() > INT_MAX) - return ERROR_INVALID_PARAMETER; - int s_size = static_cast<int>(s.size()); - int length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_.resize(length + 1); - length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_[length] = 0; - return 0; -} - -FMT_FUNC void WindowsError::init( - int err_code, CStringRef format_str, ArgList args) { - error_code_ = err_code; - MemoryWriter w; - internal::format_windows_error(w, err_code, format(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(w.str()); -} - -FMT_FUNC void internal::format_windows_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { - FMT_TRY { - MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer; - buffer.resize(INLINE_BUFFER_SIZE); - for (;;) { - wchar_t *system_message = &buffer[0]; - int result = FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - system_message, static_cast<uint32_t>(buffer.size()), FMT_NULL); - if (result != 0) { - UTF16ToUTF8 utf8_message; - if (utf8_message.convert(system_message) == ERROR_SUCCESS) { - out << message << ": " << utf8_message; - return; - } - break; - } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - break; // Can't get error message, report error code instead. - buffer.resize(buffer.size() * 2); - } - } FMT_CATCH(...) {} - fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. -} - -#endif // FMT_USE_WINDOWS_H - -FMT_FUNC void format_system_error( - Writer &out, int error_code, StringRef message) FMT_NOEXCEPT { - FMT_TRY { - internal::MemoryBuffer<char, internal::INLINE_BUFFER_SIZE> buffer; - buffer.resize(internal::INLINE_BUFFER_SIZE); - for (;;) { - char *system_message = &buffer[0]; - int result = safe_strerror(error_code, system_message, buffer.size()); - if (result == 0) { - out << message << ": " << system_message; - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buffer.resize(buffer.size() * 2); - } - } FMT_CATCH(...) {} - fmt::format_error_code(out, error_code, message); // 'fmt::' is for bcc32. -} - -template <typename Char> -void internal::FixedBuffer<Char>::grow(std::size_t) { - FMT_THROW(std::runtime_error("buffer overflow")); -} - -FMT_FUNC internal::Arg internal::FormatterBase::do_get_arg( - unsigned arg_index, const char *&error) { - internal::Arg arg = args_[arg_index]; - switch (arg.type) { - case internal::Arg::NONE: - error = "argument index out of range"; - break; - case internal::Arg::NAMED_ARG: - arg = *static_cast<const internal::Arg*>(arg.pointer); - break; - default: - /*nothing*/; - } - return arg; -} - -FMT_FUNC void report_system_error( - int error_code, fmt::StringRef message) FMT_NOEXCEPT { - // 'fmt::' is for bcc32. - report_error(format_system_error, error_code, message); -} - -#if FMT_USE_WINDOWS_H -FMT_FUNC void report_windows_error( - int error_code, fmt::StringRef message) FMT_NOEXCEPT { - // 'fmt::' is for bcc32. - report_error(internal::format_windows_error, error_code, message); -} -#endif - -FMT_FUNC void print(std::FILE *f, CStringRef format_str, ArgList args) { - MemoryWriter w; - w.write(format_str, args); - std::fwrite(w.data(), 1, w.size(), f); -} - -FMT_FUNC void print(CStringRef format_str, ArgList args) { - print(stdout, format_str, args); -} - -FMT_FUNC void print_colored(Color c, CStringRef format, ArgList args) { - char escape[] = "\x1b[30m"; - escape[3] = static_cast<char>('0' + c); - std::fputs(escape, stdout); - print(format, args); - std::fputs(RESET_COLOR, stdout); -} - -#ifndef FMT_HEADER_ONLY - -template struct internal::BasicData<void>; - -// Explicit instantiations for char. - -template void internal::FixedBuffer<char>::grow(std::size_t); - -template FMT_API int internal::CharTraits<char>::format_float( - char *buffer, std::size_t size, const char *format, - unsigned width, int precision, double value); - -template FMT_API int internal::CharTraits<char>::format_float( - char *buffer, std::size_t size, const char *format, - unsigned width, int precision, long double value); - -// Explicit instantiations for wchar_t. - -template void internal::FixedBuffer<wchar_t>::grow(std::size_t); - -template FMT_API int internal::CharTraits<wchar_t>::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, - unsigned width, int precision, double value); - -template FMT_API int internal::CharTraits<wchar_t>::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, - unsigned width, int precision, long double value); - -#endif // FMT_HEADER_ONLY - -} // namespace fmt - -#ifdef _MSC_VER -# pragma warning(pop) -#endif diff --git a/src/nmodl/ext/fmt/format.h b/src/nmodl/ext/fmt/format.h index 561a9e0798..9f522f39b7 100644 --- a/src/nmodl/ext/fmt/format.h +++ b/src/nmodl/ext/fmt/format.h @@ -1,7 +1,7 @@ /* Formatting library for C++ - Copyright (c) 2012 - 2016, Victor Zverovich + Copyright (c) 2012 - present, Victor Zverovich All rights reserved. Redistribution and use in source and binary forms, with or without @@ -28,117 +28,61 @@ #ifndef FMT_FORMAT_H_ #define FMT_FORMAT_H_ -#define FMT_INCLUDE +#include <algorithm> #include <cassert> -#include <clocale> #include <cmath> -#include <cstdio> #include <cstring> #include <limits> #include <memory> #include <stdexcept> -#include <string> -#include <vector> -#include <utility> // for std::pair -#undef FMT_INCLUDE - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 40100 - -#if defined(__has_include) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif +#include <stdint.h> -#if (FMT_HAS_INCLUDE(<string_view>) && __cplusplus > 201402L) || \ - (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) -# include <string_view> -# define FMT_HAS_STRING_VIEW 1 +#ifdef __clang__ +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) #else -# define FMT_HAS_STRING_VIEW 0 +# define FMT_CLANG_VERSION 0 #endif -#if defined _SECURE_SCL && _SECURE_SCL -# define FMT_SECURE_SCL _SECURE_SCL +#ifdef __INTEL_COMPILER +# define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +# define FMT_ICC_VERSION __ICL #else -# define FMT_SECURE_SCL 0 -#endif - -#if FMT_SECURE_SCL -# include <iterator> +# define FMT_ICC_VERSION 0 #endif -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER +#ifdef __NVCC__ +# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) #else -# define FMT_MSC_VER 0 +# define FMT_CUDA_VERSION 0 #endif -#if FMT_MSC_VER && FMT_MSC_VER <= 1500 -typedef unsigned __int32 uint32_t; -typedef unsigned __int64 uint64_t; -typedef __int64 intmax_t; -#else -#include <stdint.h> -#endif +#include "core.h" -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +# pragma GCC diagnostic push -#ifdef __GNUC__ -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -# define FMT_GCC_EXTENSION __extension__ -# if FMT_GCC_VERSION >= 406 -# pragma GCC diagnostic push -// Disable the warning about "long long" which is sometimes reported even -// when using __extension__. -# pragma GCC diagnostic ignored "-Wlong-long" // Disable the warning about declaration shadowing because it affects too // many valid cases. -# pragma GCC diagnostic ignored "-Wshadow" +# pragma GCC diagnostic ignored "-Wshadow" + // Disable the warning about implicit conversions that may change the sign of // an integer; silencing it otherwise would require many explicit casts. -# pragma GCC diagnostic ignored "-Wsign-conversion" -# endif -# if __cplusplus >= 201103L || defined __GXX_EXPERIMENTAL_CXX0X__ -# define FMT_HAS_GXX_CXX11 1 -# endif -#else -# define FMT_GCC_VERSION 0 -# define FMT_GCC_EXTENSION -# define FMT_HAS_GXX_CXX11 0 -#endif - -#if defined(__INTEL_COMPILER) -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL +# pragma GCC diagnostic ignored "-Wsign-conversion" #endif -#if defined(__clang__) && !defined(FMT_ICC_VERSION) -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdocumentation-unknown-command" -# pragma clang diagnostic ignored "-Wpadded" -#endif +# if FMT_CLANG_VERSION +# pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template" +# endif -#ifdef __GNUC_LIBSTD__ -# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#ifdef _SECURE_SCL +# define FMT_SECURE_SCL _SECURE_SCL +#else +# define FMT_SECURE_SCL 0 #endif -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 +#if FMT_SECURE_SCL +# include <iterator> #endif #ifdef __has_builtin @@ -147,194 +91,78 @@ typedef __int64 intmax_t; # define FMT_HAS_BUILTIN(x) 0 #endif -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#if FMT_HAS_CPP_ATTRIBUTE(maybe_unused) -# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED -// VC++ 1910 support /std: option and that will set _MSVC_LANG macro -// Clang with Microsoft CodeGen doesn't define _MSVC_LANG macro -#elif defined(_MSVC_LANG) && _MSVC_LANG > 201402 && _MSC_VER >= 1910 -# define FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED -#endif - -#ifdef FMT_HAS_CXX17_ATTRIBUTE_MAYBE_UNUSED -# define FMT_MAYBE_UNUSED [[maybe_unused]] -// g++/clang++ also support [[gnu::unused]]. However, we don't use it. -#elif defined(__GNUC__) -# define FMT_MAYBE_UNUSED __attribute__((unused)) -#else -# define FMT_MAYBE_UNUSED -#endif - -// Use the compiler's attribute noreturn -#if defined(__MINGW32__) || defined(__MINGW64__) -# define FMT_NORETURN __attribute__((noreturn)) -#elif FMT_HAS_CPP_ATTRIBUTE(noreturn) && __cplusplus >= 201103L -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -#ifndef FMT_USE_VARIADIC_TEMPLATES -// Variadic templates are available in GCC since version 4.4 -// (http://gcc.gnu.org/projects/cxx0x.html) and in Visual C++ -// since version 2013. -# define FMT_USE_VARIADIC_TEMPLATES \ - (FMT_HAS_FEATURE(cxx_variadic_templates) || \ - (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800) -#endif - -#ifndef FMT_USE_RVALUE_REFERENCES -// Don't use rvalue references when compiling with clang and an old libstdc++ -// as the latter doesn't provide std::move. -# if defined(FMT_GNUC_LIBSTD_VERSION) && FMT_GNUC_LIBSTD_VERSION <= 402 -# define FMT_USE_RVALUE_REFERENCES 0 -# else -# define FMT_USE_RVALUE_REFERENCES \ - (FMT_HAS_FEATURE(cxx_rvalue_references) || \ - (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600) -# endif -#endif - -#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 -# define FMT_USE_ALLOCATOR_TRAITS 1 -#else -# define FMT_USE_ALLOCATOR_TRAITS 0 -#endif - -// Check if exceptions are disabled. -#if defined(__GNUC__) && !defined(__EXCEPTIONS) -# define FMT_EXCEPTIONS 0 -#endif -#if FMT_MSC_VER && !_HAS_EXCEPTIONS -# define FMT_EXCEPTIONS 0 -#endif -#ifndef FMT_EXCEPTIONS -# define FMT_EXCEPTIONS 1 +#ifdef __GNUC_LIBSTD__ +# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) #endif #ifndef FMT_THROW # if FMT_EXCEPTIONS -# define FMT_THROW(x) throw x +# if FMT_MSC_VER +FMT_BEGIN_NAMESPACE +namespace internal { +template <typename Exception> +inline void do_throw(const Exception &x) { + // Silence unreachable code warnings in MSVC because these are nearly + // impossible to fix in a generic code. + volatile bool b = true; + if (b) + throw x; +} +} +FMT_END_NAMESPACE +# define FMT_THROW(x) fmt::internal::do_throw(x) +# else +# define FMT_THROW(x) throw x +# endif # else -# define FMT_THROW(x) assert(false) +# define FMT_THROW(x) do { static_cast<void>(sizeof(x)); assert(false); } while(false); # endif #endif -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 -#endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ - FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept -#else -# define FMT_DETECTED_NOEXCEPT throw() -#endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +#ifndef FMT_USE_USER_DEFINED_LITERALS +// For Intel's compiler and NVIDIA's compiler both it and the system gcc/msc +// must support UDLs. +# if (FMT_HAS_FEATURE(cxx_user_literals) || \ + FMT_GCC_VERSION >= 407 || FMT_MSC_VER >= 1900) && \ + (!(FMT_ICC_VERSION || FMT_CUDA_VERSION) || \ + FMT_ICC_VERSION >= 1500 || FMT_CUDA_VERSION >= 700) +# define FMT_USE_USER_DEFINED_LITERALS 1 # else -# define FMT_NOEXCEPT +# define FMT_USE_USER_DEFINED_LITERALS 0 # endif #endif -// This is needed because GCC still uses throw() in its headers when exceptions -// are disabled. -#if FMT_GCC_VERSION -# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +// EDG C++ Front End based compilers (icc, nvcc) do not currently support UDL +// templates. +#if FMT_USE_USER_DEFINED_LITERALS && \ + FMT_ICC_VERSION == 0 && \ + FMT_CUDA_VERSION == 0 && \ + ((FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L) || \ + (defined(FMT_CLANG_VERSION) && FMT_CLANG_VERSION >= 304)) +# define FMT_UDL_TEMPLATE 1 #else -# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT -#endif - -#ifndef FMT_OVERRIDE -# if (defined(FMT_USE_OVERRIDE) && FMT_USE_OVERRIDE) || FMT_HAS_FEATURE(cxx_override) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ - FMT_MSC_VER >= 1900 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE -# endif +# define FMT_UDL_TEMPLATE 0 #endif -#ifndef FMT_NULL -# if FMT_HAS_FEATURE(cxx_nullptr) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || \ - FMT_MSC_VER >= 1600 -# define FMT_NULL nullptr +#ifndef FMT_USE_EXTERN_TEMPLATES +# ifndef FMT_HEADER_ONLY +# define FMT_USE_EXTERN_TEMPLATES \ + ((FMT_CLANG_VERSION >= 209 && __cplusplus >= 201103L) || \ + (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) # else -# define FMT_NULL NULL +# define FMT_USE_EXTERN_TEMPLATES 0 # endif #endif -// A macro to disallow the copy constructor and operator= functions -// This should be used in the private: declarations for a class -#ifndef FMT_USE_DELETED_FUNCTIONS -# define FMT_USE_DELETED_FUNCTIONS 0 -#endif - -#if FMT_USE_DELETED_FUNCTIONS || FMT_HAS_FEATURE(cxx_deleted_functions) || \ - (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 -# define FMT_DELETED_OR_UNDEFINED = delete -# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&) = delete; \ - TypeName& operator=(const TypeName&) = delete +#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_trailing_return) || \ + FMT_MSC_VER >= 1600 +# define FMT_USE_TRAILING_RETURN 1 #else -# define FMT_DELETED_OR_UNDEFINED -# define FMT_DISALLOW_COPY_AND_ASSIGN(TypeName) \ - TypeName(const TypeName&); \ - TypeName& operator=(const TypeName&) -#endif - -#ifndef FMT_USE_DEFAULTED_FUNCTIONS -# define FMT_USE_DEFAULTED_FUNCTIONS 0 -#endif - -#ifndef FMT_DEFAULTED_COPY_CTOR -# if FMT_USE_DEFAULTED_FUNCTIONS || FMT_HAS_FEATURE(cxx_defaulted_functions) || \ - (FMT_GCC_VERSION >= 404 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1800 -# define FMT_DEFAULTED_COPY_CTOR(TypeName) \ - TypeName(const TypeName&) = default; -# else -# define FMT_DEFAULTED_COPY_CTOR(TypeName) -# endif -#endif - -#ifndef FMT_USE_USER_DEFINED_LITERALS -// All compilers which support UDLs also support variadic templates. This -// makes the fmt::literals implementation easier. However, an explicit check -// for variadic templates is added here just in case. -// For Intel's compiler both it and the system gcc/msc must support UDLs. -# if FMT_USE_VARIADIC_TEMPLATES && FMT_USE_RVALUE_REFERENCES && \ - (FMT_HAS_FEATURE(cxx_user_literals) || \ - (FMT_GCC_VERSION >= 407 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900) && \ - (!defined(FMT_ICC_VERSION) || FMT_ICC_VERSION >= 1500) -# define FMT_USE_USER_DEFINED_LITERALS 1 -# else -# define FMT_USE_USER_DEFINED_LITERALS 0 -# endif -#endif - -#ifndef FMT_USE_EXTERN_TEMPLATES -# define FMT_USE_EXTERN_TEMPLATES \ - (FMT_CLANG_VERSION >= 209 || (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) -#endif - -#ifdef FMT_HEADER_ONLY -// If header only do not use extern templates. -# undef FMT_USE_EXTERN_TEMPLATES -# define FMT_USE_EXTERN_TEMPLATES 0 +# define FMT_USE_TRAILING_RETURN 0 #endif -#ifndef FMT_ASSERT -# define FMT_ASSERT(condition, message) assert((condition) && message) +#ifndef FMT_USE_GRISU +# define FMT_USE_GRISU 0 #endif // __builtin_clz is broken in clang with Microsoft CodeGen: @@ -349,16 +177,23 @@ typedef __int64 intmax_t; # endif #endif -// Some compilers masquerade as both MSVC and GCC-likes or -// otherwise support __builtin_clz and __builtin_clzll, so -// only define FMT_BUILTIN_CLZ using the MSVC intrinsics -// if the clz and clzll builtins are not available. +// A workaround for gcc 4.4 that doesn't support union members with ctors. +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 404) || \ + (FMT_MSC_VER && FMT_MSC_VER <= 1800) +# define FMT_UNION struct +#else +# define FMT_UNION union +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. #if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) # include <intrin.h> // _BitScanReverse, _BitScanReverse64 -namespace fmt { +FMT_BEGIN_NAMESPACE namespace internal { -// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. # ifndef __clang__ # pragma intrinsic(_BitScanReverse) # endif @@ -375,7 +210,6 @@ inline uint32_t clz(uint32_t x) { } # define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) -// avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning # if defined(_WIN64) && !defined(__clang__) # pragma intrinsic(_BitScanReverse64) # endif @@ -402,32 +236,81 @@ inline uint32_t clzll(uint64_t x) { } # define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) } -} +FMT_END_NAMESPACE #endif -namespace fmt { +FMT_BEGIN_NAMESPACE namespace internal { -struct DummyInt { + +// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't produce +// undefined behavior (e.g. due to type aliasing). +// Example: uint64_t d = bit_cast<uint64_t>(2.718); +template <typename Dest, typename Source> +inline Dest bit_cast(const Source& source) { + static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); + Dest dest; + std::memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// An implementation of begin and end for pre-C++11 compilers such as gcc 4. +template <typename C> +FMT_CONSTEXPR auto begin(const C &c) -> decltype(c.begin()) { + return c.begin(); +} +template <typename T, std::size_t N> +FMT_CONSTEXPR T *begin(T (&array)[N]) FMT_NOEXCEPT { return array; } +template <typename C> +FMT_CONSTEXPR auto end(const C &c) -> decltype(c.end()) { return c.end(); } +template <typename T, std::size_t N> +FMT_CONSTEXPR T *end(T (&array)[N]) FMT_NOEXCEPT { return array + N; } + +// For std::result_of in gcc 4.4. +template <typename Result> +struct function { + template <typename T> + struct result { typedef Result type; }; +}; + +struct dummy_int { int data[2]; operator int() const { return 0; } }; -typedef std::numeric_limits<fmt::internal::DummyInt> FPUtil; +typedef std::numeric_limits<internal::dummy_int> fputil; // Dummy implementations of system functions such as signbit and ecvt called // if the latter are not available. -inline DummyInt signbit(...) { return DummyInt(); } -inline DummyInt _ecvt_s(...) { return DummyInt(); } -inline DummyInt isinf(...) { return DummyInt(); } -inline DummyInt _finite(...) { return DummyInt(); } -inline DummyInt isnan(...) { return DummyInt(); } -inline DummyInt _isnan(...) { return DummyInt(); } +inline dummy_int signbit(...) { return dummy_int(); } +inline dummy_int _ecvt_s(...) { return dummy_int(); } +inline dummy_int isinf(...) { return dummy_int(); } +inline dummy_int _finite(...) { return dummy_int(); } +inline dummy_int isnan(...) { return dummy_int(); } +inline dummy_int _isnan(...) { return dummy_int(); } + +inline bool use_grisu() { + return FMT_USE_GRISU && std::numeric_limits<double>::is_iec559; +} + +// Formats value using Grisu2 algorithm: +// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf +FMT_API void grisu2_format(double value, char *buffer, size_t &size, char type, + int precision, bool write_decimal_point); + +template <typename Allocator> +typename Allocator::value_type *allocate(Allocator& alloc, std::size_t n) { +#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 + return std::allocator_traits<Allocator>::allocate(alloc, n); +#else + return alloc.allocate(n); +#endif +} // A helper function to suppress bogus "conditional expression is constant" // warnings. template <typename T> inline T const_check(T value) { return value; } -} -} // namespace fmt +} // namespace internal +FMT_END_NAMESPACE namespace std { // Standard permits specialization of std::numeric_limits. This specialization @@ -435,7 +318,7 @@ namespace std { // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 // and the same for isnan and signbit. template <> -class numeric_limits<fmt::internal::DummyInt> : +class numeric_limits<fmt::internal::dummy_int> : public std::numeric_limits<int> { public: // Portable version of isinf. @@ -444,10 +327,8 @@ class numeric_limits<fmt::internal::DummyInt> : using namespace fmt::internal; // The resolution "priority" is: // isinf macro > std::isinf > ::isinf > fmt::internal::isinf - if (const_check(sizeof(isinf(x)) == sizeof(bool) || - sizeof(isinf(x)) == sizeof(int))) { + if (const_check(sizeof(isinf(x)) != sizeof(dummy_int))) return isinf(x) != 0; - } return !_finite(static_cast<double>(x)); } @@ -455,20 +336,16 @@ class numeric_limits<fmt::internal::DummyInt> : template <typename T> static bool isnotanumber(T x) { using namespace fmt::internal; - if (const_check(sizeof(isnan(x)) == sizeof(bool) || - sizeof(isnan(x)) == sizeof(int))) { + if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) return isnan(x) != 0; - } return _isnan(static_cast<double>(x)) != 0; } // Portable version of signbit. static bool isnegative(double x) { using namespace fmt::internal; - if (const_check(sizeof(signbit(x)) == sizeof(bool) || - sizeof(signbit(x)) == sizeof(int))) { + if (const_check(sizeof(signbit(x)) != sizeof(fmt::internal::dummy_int))) return signbit(x) != 0; - } if (x < 0) return true; if (!isnotanumber(x)) return false; int dec = 0, sign = 0; @@ -479,541 +356,547 @@ class numeric_limits<fmt::internal::DummyInt> : }; } // namespace std -namespace fmt { +FMT_BEGIN_NAMESPACE +template <typename Range> +class basic_writer; -// Fix the warning about long long on older versions of GCC -// that don't support the diagnostic pragma. -FMT_GCC_EXTENSION typedef long long LongLong; -FMT_GCC_EXTENSION typedef unsigned long long ULongLong; +template <typename OutputIt, typename T = typename OutputIt::value_type> +class output_range { + private: + OutputIt it_; -#if FMT_USE_RVALUE_REFERENCES -using std::move; -#endif + // Unused yet. + typedef void sentinel; + sentinel end() const; -template <typename Char> -class BasicWriter; + public: + typedef OutputIt iterator; + typedef T value_type; -typedef BasicWriter<char> Writer; -typedef BasicWriter<wchar_t> WWriter; + explicit output_range(OutputIt it): it_(it) {} + OutputIt begin() const { return it_; } +}; -template <typename Char> -class ArgFormatter; +// A range where begin() returns back_insert_iterator. +template <typename Container> +class back_insert_range: + public output_range<std::back_insert_iterator<Container>> { + typedef output_range<std::back_insert_iterator<Container>> base; + public: + typedef typename Container::value_type value_type; -struct FormatSpec; + back_insert_range(Container &c): base(std::back_inserter(c)) {} + back_insert_range(typename base::iterator it): base(it) {} +}; -template <typename Impl, typename Char, typename Spec = fmt::FormatSpec> -class BasicPrintfArgFormatter; +typedef basic_writer<back_insert_range<internal::buffer>> writer; +typedef basic_writer<back_insert_range<internal::wbuffer>> wwriter; -template <typename CharType, - typename ArgFormatter = fmt::ArgFormatter<CharType> > -class BasicFormatter; +/** A formatting error such as invalid format string. */ +class format_error : public std::runtime_error { + public: + explicit format_error(const char *message) + : std::runtime_error(message) {} -/** - \rst - A string reference. It can be constructed from a C string or - ``std::basic_string``. + explicit format_error(const std::string &message) + : std::runtime_error(message) {} +}; - You can use one of the following typedefs for common character types: +namespace internal { - +------------+-------------------------+ - | Type | Definition | - +============+=========================+ - | StringRef | BasicStringRef<char> | - +------------+-------------------------+ - | WStringRef | BasicStringRef<wchar_t> | - +------------+-------------------------+ +#if FMT_SECURE_SCL +template <typename T> +struct checked { typedef stdext::checked_array_iterator<T*> type; }; - This class is most useful as a parameter type to allow passing - different types of strings to a function, for example:: +// Make a checked iterator to avoid warnings on MSVC. +template <typename T> +inline stdext::checked_array_iterator<T*> make_checked(T *p, std::size_t size) { + return {p, size}; +} +#else +template <typename T> +struct checked { typedef T *type; }; +template <typename T> +inline T *make_checked(T *p, std::size_t) { return p; } +#endif - template <typename... Args> - std::string format(StringRef format_str, const Args & ... args); +template <typename T> +template <typename U> +void basic_buffer<T>::append(const U *begin, const U *end) { + std::size_t new_size = size_ + internal::to_unsigned(end - begin); + reserve(new_size); + std::uninitialized_copy(begin, end, + internal::make_checked(ptr_, capacity_) + size_); + size_ = new_size; +} +} // namespace internal - format("{}", 42); - format(std::string("{}"), 42); - \endrst - */ -template <typename Char> -class BasicStringRef { +// A UTF-8 code unit type. +struct char8_t { + char value; + FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { + return value != 0; + } +}; + +// A UTF-8 string view. +class u8string_view : public basic_string_view<char8_t> { private: - const Char *data_; - std::size_t size_; + typedef basic_string_view<char8_t> base; public: - /** Constructs a string reference object from a C string and a size. */ - BasicStringRef(const Char *s, std::size_t size) : data_(s), size_(size) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits<Char>::length``. - \endrst - */ - BasicStringRef(const Char *s) - : data_(s), size_(std::char_traits<Char>::length(s)) {} + using basic_string_view::basic_string_view; + using basic_string_view::char_type; - /** - \rst - Constructs a string reference from a ``std::basic_string`` object. - \endrst - */ - template <typename Allocator> - BasicStringRef( - const std::basic_string<Char, std::char_traits<Char>, Allocator> &s) - : data_(s.c_str()), size_(s.size()) {} + u8string_view(const char *s) + : base(reinterpret_cast<const char8_t*>(s)) {} -#if FMT_HAS_STRING_VIEW - /** - \rst - Constructs a string reference from a ``std::basic_string_view`` object. - \endrst - */ - BasicStringRef( - const std::basic_string_view<Char, std::char_traits<Char>> &s) - : data_(s.data()), size_(s.size()) {} + u8string_view(const char *s, size_t count) FMT_NOEXCEPT + : base(reinterpret_cast<const char8_t*>(s), count) {} +}; - /** - \rst - Converts a string reference to an ``std::string_view`` object. - \endrst - */ - explicit operator std::basic_string_view<Char>() const FMT_NOEXCEPT { - return std::basic_string_view<Char>(data_, size_); - } +#if FMT_USE_USER_DEFINED_LITERALS +inline namespace literals { +inline u8string_view operator"" _u(const char *s, std::size_t n) { + return u8string_view(s, n); +} +} #endif - /** - \rst - Converts a string reference to an ``std::string`` object. - \endrst - */ - std::basic_string<Char> to_string() const { - return std::basic_string<Char>(data_, size_); - } - - /** Returns a pointer to the string data. */ - const Char *data() const { return data_; } - - /** Returns the string size. */ - std::size_t size() const { return size_; } - - // Lexicographically compare this string reference to other. - int compare(BasicStringRef other) const { - std::size_t size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits<Char>::compare(data_, other.data_, size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } +// A wrapper around std::locale used to reduce compile times since <locale> +// is very heavy. +class locale; - friend bool operator==(BasicStringRef lhs, BasicStringRef rhs) { - return lhs.compare(rhs) == 0; - } - friend bool operator!=(BasicStringRef lhs, BasicStringRef rhs) { - return lhs.compare(rhs) != 0; - } - friend bool operator<(BasicStringRef lhs, BasicStringRef rhs) { - return lhs.compare(rhs) < 0; - } - friend bool operator<=(BasicStringRef lhs, BasicStringRef rhs) { - return lhs.compare(rhs) <= 0; - } - friend bool operator>(BasicStringRef lhs, BasicStringRef rhs) { - return lhs.compare(rhs) > 0; - } - friend bool operator>=(BasicStringRef lhs, BasicStringRef rhs) { - return lhs.compare(rhs) >= 0; - } +class locale_provider { + public: + virtual ~locale_provider() {} + virtual fmt::locale locale(); }; -typedef BasicStringRef<char> StringRef; -typedef BasicStringRef<wchar_t> WStringRef; +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; /** \rst - A reference to a null terminated string. It can be constructed from a C - string or ``std::basic_string``. + A dynamically growing memory buffer for trivially copyable/constructible types + with the first ``SIZE`` elements stored in the object itself. You can use one of the following typedefs for common character types: - +-------------+--------------------------+ - | Type | Definition | - +=============+==========================+ - | CStringRef | BasicCStringRef<char> | - +-------------+--------------------------+ - | WCStringRef | BasicCStringRef<wchar_t> | - +-------------+--------------------------+ + +----------------+------------------------------+ + | Type | Definition | + +================+==============================+ + | memory_buffer | basic_memory_buffer<char> | + +----------------+------------------------------+ + | wmemory_buffer | basic_memory_buffer<wchar_t> | + +----------------+------------------------------+ + + **Example**:: + + fmt::memory_buffer out; + format_to(out, "The answer is {}.", 42); - This class is most useful as a parameter type to allow passing - different types of strings to a function, for example:: + This will write the following output to the ``out`` object: + + .. code-block:: none - template <typename... Args> - std::string format(CStringRef format_str, const Args & ... args); + The answer is 42. - format("{}", 42); - format(std::string("{}"), 42); + The output can be converted to an ``std::string`` with ``to_string(out)``. \endrst */ -template <typename Char> -class BasicCStringRef { +template <typename T, std::size_t SIZE = inline_buffer_size, + typename Allocator = std::allocator<T> > +class basic_memory_buffer: private Allocator, public internal::basic_buffer<T> { private: - const Char *data_; + T store_[SIZE]; + + // Deallocate memory allocated by the buffer. + void deallocate() { + T* data = this->data(); + if (data != store_) Allocator::deallocate(data, this->capacity()); + } + + protected: + void grow(std::size_t size) FMT_OVERRIDE; public: - /** Constructs a string reference object from a C string. */ - BasicCStringRef(const Char *s) : data_(s) {} + explicit basic_memory_buffer(const Allocator &alloc = Allocator()) + : Allocator(alloc) { + this->set(store_, SIZE); + } + ~basic_memory_buffer() { deallocate(); } + + private: + // Move data from other to this buffer. + void move(basic_memory_buffer &other) { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + T* data = other.data(); + std::size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { + this->set(store_, capacity); + std::uninitialized_copy(other.store_, other.store_ + size, + internal::make_checked(store_, capacity)); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + } + this->resize(size); + } + public: /** \rst - Constructs a string reference from a ``std::basic_string`` object. + Constructs a :class:`fmt::basic_memory_buffer` object moving the content + of the other object to it. \endrst */ - template <typename Allocator> - BasicCStringRef( - const std::basic_string<Char, std::char_traits<Char>, Allocator> &s) - : data_(s.c_str()) {} - - /** Returns the pointer to a C string. */ - const Char *c_str() const { return data_; } -}; + basic_memory_buffer(basic_memory_buffer &&other) { + move(other); + } -typedef BasicCStringRef<char> CStringRef; -typedef BasicCStringRef<wchar_t> WCStringRef; + /** + \rst + Moves the content of the other ``basic_memory_buffer`` object to this one. + \endrst + */ + basic_memory_buffer &operator=(basic_memory_buffer &&other) { + assert(this != &other); + deallocate(); + move(other); + return *this; + } -/** A formatting error such as invalid format string. */ -class FormatError : public std::runtime_error { - public: - explicit FormatError(CStringRef message) - : std::runtime_error(message.c_str()) {} - FormatError(const FormatError &ferr) : std::runtime_error(ferr) {} - FMT_API ~FormatError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const { return *this; } }; -namespace internal { - -// MakeUnsigned<T>::Type gives an unsigned type corresponding to integer type T. -template <typename T> -struct MakeUnsigned { typedef T Type; }; - -#define FMT_SPECIALIZE_MAKE_UNSIGNED(T, U) \ - template <> \ - struct MakeUnsigned<T> { typedef U Type; } - -FMT_SPECIALIZE_MAKE_UNSIGNED(char, unsigned char); -FMT_SPECIALIZE_MAKE_UNSIGNED(signed char, unsigned char); -FMT_SPECIALIZE_MAKE_UNSIGNED(short, unsigned short); -FMT_SPECIALIZE_MAKE_UNSIGNED(int, unsigned); -FMT_SPECIALIZE_MAKE_UNSIGNED(long, unsigned long); -FMT_SPECIALIZE_MAKE_UNSIGNED(LongLong, ULongLong); - -// Casts nonnegative integer to unsigned. -template <typename Int> -inline typename MakeUnsigned<Int>::Type to_unsigned(Int value) { - FMT_ASSERT(value >= 0, "negative value"); - return static_cast<typename MakeUnsigned<Int>::Type>(value); +template <typename T, std::size_t SIZE, typename Allocator> +void basic_memory_buffer<T, SIZE, Allocator>::grow(std::size_t size) { + std::size_t old_capacity = this->capacity(); + std::size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + T *old_data = this->data(); + T *new_data = internal::allocate<Allocator>(*this, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), + internal::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) + Allocator::deallocate(old_data, old_capacity); } -// The number of characters to store in the MemoryBuffer object itself -// to avoid dynamic memory allocation. -enum { INLINE_BUFFER_SIZE = 500 }; - -#if FMT_SECURE_SCL -// Use checked iterator to avoid warnings on MSVC. -template <typename T> -inline stdext::checked_array_iterator<T*> make_ptr(T *ptr, std::size_t size) { - return stdext::checked_array_iterator<T*>(ptr, size); -} -#else -template <typename T> -inline T *make_ptr(T *ptr, std::size_t) { return ptr; } -#endif -} // namespace internal +typedef basic_memory_buffer<char> memory_buffer; +typedef basic_memory_buffer<wchar_t> wmemory_buffer; /** \rst - A buffer supporting a subset of ``std::vector``'s operations. + A fixed-size memory buffer. For a dynamically growing buffer use + :class:`fmt::basic_memory_buffer`. + + Trying to increase the buffer size past the initial capacity will throw + ``std::runtime_error``. \endrst */ -template <typename T> -class Buffer { - private: - FMT_DISALLOW_COPY_AND_ASSIGN(Buffer); - - protected: - T *ptr_; - std::size_t size_; - std::size_t capacity_; - - Buffer(T *ptr = FMT_NULL, std::size_t capacity = 0) - : ptr_(ptr), size_(0), capacity_(capacity) {} - - /** - \rst - Increases the buffer capacity to hold at least *size* elements updating - ``ptr_`` and ``capacity_``. - \endrst - */ - virtual void grow(std::size_t size) = 0; - +template <typename Char> +class basic_fixed_buffer : public internal::basic_buffer<Char> { public: - virtual ~Buffer() {} - - /** Returns the size of this buffer. */ - std::size_t size() const { return size_; } - - /** Returns the capacity of this buffer. */ - std::size_t capacity() const { return capacity_; } - /** - Resizes the buffer. If T is a POD type new elements may not be initialized. + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + given size. + \endrst */ - void resize(std::size_t new_size) { - if (new_size > capacity_) - grow(new_size); - size_ = new_size; + basic_fixed_buffer(Char *array, std::size_t size) { + this->set(array, size); } /** - \rst - Reserves space to store at least *capacity* elements. - \endrst + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + size known at compile time. + \endrst */ - void reserve(std::size_t capacity) { - if (capacity > capacity_) - grow(capacity); + template <std::size_t SIZE> + explicit basic_fixed_buffer(Char (&array)[SIZE]) { + this->set(array, SIZE); } - void clear() FMT_NOEXCEPT { size_ = 0; } + protected: + FMT_API void grow(std::size_t size) FMT_OVERRIDE; +}; - void push_back(const T &value) { - if (size_ == capacity_) - grow(size_ + 1); - ptr_[size_++] = value; - } +namespace internal { - /** Appends data to the end of the buffer. */ - template <typename U> - void append(const U *begin, const U *end); +template <typename Char> +struct char_traits; - T &operator[](std::size_t index) { return ptr_[index]; } - const T &operator[](std::size_t index) const { return ptr_[index]; } +template <> +struct char_traits<char> { + // Formats a floating-point number. + template <typename T> + FMT_API static int format_float(char *buffer, std::size_t size, + const char *format, int precision, T value); }; -template <typename T> -template <typename U> -void Buffer<T>::append(const U *begin, const U *end) { - FMT_ASSERT(end >= begin, "negative value"); - std::size_t new_size = size_ + static_cast<std::size_t>(end - begin); - if (new_size > capacity_) - grow(new_size); - std::uninitialized_copy(begin, end, - internal::make_ptr(ptr_, capacity_) + size_); - size_ = new_size; +template <> +struct char_traits<wchar_t> { + template <typename T> + FMT_API static int format_float(wchar_t *buffer, std::size_t size, + const wchar_t *format, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int char_traits<char>::format_float<double>( + char *buffer, std::size_t size, const char* format, int precision, + double value); +extern template int char_traits<char>::format_float<long double>( + char *buffer, std::size_t size, const char* format, int precision, + long double value); + +extern template int char_traits<wchar_t>::format_float<double>( + wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, + double value); +extern template int char_traits<wchar_t>::format_float<long double>( + wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, + long double value); +#endif + +template <typename Container> +inline typename std::enable_if< + is_contiguous<Container>::value, + typename checked<typename Container::value_type>::type>::type + reserve(std::back_insert_iterator<Container> &it, std::size_t n) { + Container &c = internal::get_container(it); + std::size_t size = c.size(); + c.resize(size + n); + return make_checked(&c[size], n); } -namespace internal { +template <typename Iterator> +inline Iterator &reserve(Iterator &it, std::size_t) { return it; } -// A memory buffer for trivially copyable/constructible types with the first -// SIZE elements stored in the object itself. -template <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> > -class MemoryBuffer : private Allocator, public Buffer<T> { - private: - T data_[SIZE]; +template <typename Char> +class null_terminating_iterator; - // Deallocate memory allocated by the buffer. - void deallocate() { - if (this->ptr_ != data_) Allocator::deallocate(this->ptr_, this->capacity_); +template <typename Char> +FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator<Char> it); + +// An iterator that produces a null terminator on *end. This simplifies parsing +// and allows comparing the performance of processing a null-terminated string +// vs string_view. +template <typename Char> +class null_terminating_iterator { + public: + typedef std::ptrdiff_t difference_type; + typedef Char value_type; + typedef const Char* pointer; + typedef const Char& reference; + typedef std::random_access_iterator_tag iterator_category; + + null_terminating_iterator() : ptr_(0), end_(0) {} + + FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) + : ptr_(ptr), end_(end) {} + + template <typename Range> + FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) + : ptr_(r.begin()), end_(r.end()) {} + + FMT_CONSTEXPR null_terminating_iterator &operator=(const Char *ptr) { + assert(ptr <= end_); + ptr_ = ptr; + return *this; } - protected: - void grow(std::size_t size) FMT_OVERRIDE; + FMT_CONSTEXPR Char operator*() const { + return ptr_ != end_ ? *ptr_ : 0; + } - public: - explicit MemoryBuffer(const Allocator &alloc = Allocator()) - : Allocator(alloc), Buffer<T>(data_, SIZE) {} - ~MemoryBuffer() FMT_OVERRIDE { deallocate(); } + FMT_CONSTEXPR null_terminating_iterator operator++() { + ++ptr_; + return *this; + } -#if FMT_USE_RVALUE_REFERENCES - private: - // Move data from other to this buffer. - void move(MemoryBuffer &other) { - Allocator &this_alloc = *this, &other_alloc = other; - this_alloc = std::move(other_alloc); - this->size_ = other.size_; - this->capacity_ = other.capacity_; - if (other.ptr_ == other.data_) { - this->ptr_ = data_; - std::uninitialized_copy(other.data_, other.data_ + this->size_, - make_ptr(data_, this->capacity_)); - } else { - this->ptr_ = other.ptr_; - // Set pointer to the inline array so that delete is not called - // when deallocating. - other.ptr_ = other.data_; - } + FMT_CONSTEXPR null_terminating_iterator operator++(int) { + null_terminating_iterator result(*this); + ++ptr_; + return result; } - public: - MemoryBuffer(MemoryBuffer &&other) { - move(other); + FMT_CONSTEXPR null_terminating_iterator operator--() { + --ptr_; + return *this; } - MemoryBuffer &operator=(MemoryBuffer &&other) { - assert(this != &other); - deallocate(); - move(other); + FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) { + return null_terminating_iterator(ptr_ + n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) { + return null_terminating_iterator(ptr_ - n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) { + ptr_ += n; return *this; } -#endif - // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return *this; } -}; + FMT_CONSTEXPR difference_type operator-( + null_terminating_iterator other) const { + return ptr_ - other.ptr_; + } -template <typename T, std::size_t SIZE, typename Allocator> -void MemoryBuffer<T, SIZE, Allocator>::grow(std::size_t size) { - std::size_t new_capacity = this->capacity_ + this->capacity_ / 2; - if (size > new_capacity) - new_capacity = size; -#if FMT_USE_ALLOCATOR_TRAITS - T *new_ptr = - std::allocator_traits<Allocator>::allocate(*this, new_capacity, FMT_NULL); -#else - T *new_ptr = this->allocate(new_capacity, FMT_NULL); -#endif - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(this->ptr_, this->ptr_ + this->size_, - make_ptr(new_ptr, new_capacity)); - std::size_t old_capacity = this->capacity_; - T *old_ptr = this->ptr_; - this->capacity_ = new_capacity; - this->ptr_ = new_ptr; - // deallocate may throw (at least in principle), but it doesn't matter since - // the buffer already uses the new storage and will deallocate it in case - // of exception. - if (old_ptr != data_) - Allocator::deallocate(old_ptr, old_capacity); -} + FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const { + return ptr_ != other.ptr_; + } -// A fixed-size buffer. -template <typename Char> -class FixedBuffer : public fmt::Buffer<Char> { - public: - FixedBuffer(Char *array, std::size_t size) : fmt::Buffer<Char>(array, size) {} + bool operator>=(null_terminating_iterator other) const { + return ptr_ >= other.ptr_; + } - protected: - FMT_API void grow(std::size_t size) FMT_OVERRIDE; -}; + // This should be a friend specialization pointer_from<Char> but the latter + // doesn't compile by gcc 5.1 due to a compiler bug. + template <typename CharT> + friend FMT_CONSTEXPR_DECL const CharT *pointer_from( + null_terminating_iterator<CharT> it); -template <typename Char> -class BasicCharTraits { - public: -#if FMT_SECURE_SCL - typedef stdext::checked_array_iterator<Char*> CharPtr; -#else - typedef Char *CharPtr; -#endif - static Char cast(int value) { return static_cast<Char>(value); } + private: + const Char *ptr_; + const Char *end_; }; +template <typename T> +FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; } + template <typename Char> -class CharTraits; +FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator<Char> it) { + return it.ptr_; +} -template <> -class CharTraits<char> : public BasicCharTraits<char> { +// An output iterator that counts the number of objects written to it and +// discards them. +template <typename T> +class counting_iterator { private: - // Conversion from wchar_t to char is not allowed. - static char convert(wchar_t); + std::size_t count_; + mutable T blackhole_; public: - static char convert(char value) { return value; } + typedef std::output_iterator_tag iterator_category; + typedef T value_type; + typedef std::ptrdiff_t difference_type; + typedef T* pointer; + typedef T& reference; + typedef counting_iterator _Unchecked_type; // Mark iterator as checked. - // Formats a floating-point number. - template <typename T> - FMT_API static int format_float(char *buffer, std::size_t size, - const char *format, unsigned width, int precision, T value); -}; + counting_iterator(): count_(0) {} -#if FMT_USE_EXTERN_TEMPLATES -extern template int CharTraits<char>::format_float<double> - (char *buffer, std::size_t size, - const char* format, unsigned width, int precision, double value); -extern template int CharTraits<char>::format_float<long double> - (char *buffer, std::size_t size, - const char* format, unsigned width, int precision, long double value); -#endif + std::size_t count() const { return count_; } -template <> -class CharTraits<wchar_t> : public BasicCharTraits<wchar_t> { - public: - static wchar_t convert(char value) { return value; } - static wchar_t convert(wchar_t value) { return value; } + counting_iterator& operator++() { + ++count_; + return *this; + } - template <typename T> - FMT_API static int format_float(wchar_t *buffer, std::size_t size, - const wchar_t *format, unsigned width, int precision, T value); + counting_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + T &operator*() const { return blackhole_; } }; -#if FMT_USE_EXTERN_TEMPLATES -extern template int CharTraits<wchar_t>::format_float<double> - (wchar_t *buffer, std::size_t size, - const wchar_t* format, unsigned width, int precision, double value); -extern template int CharTraits<wchar_t>::format_float<long double> - (wchar_t *buffer, std::size_t size, - const wchar_t* format, unsigned width, int precision, long double value); -#endif +// An output iterator that truncates the output and counts the number of objects +// written to it. +template <typename OutputIt> +class truncating_iterator { + private: + typedef std::iterator_traits<OutputIt> traits; -// Checks if a number is negative - used to avoid warnings. -template <bool IsSigned> -struct SignChecker { - template <typename T> - static bool is_negative(T value) { return value < 0; } -}; + OutputIt out_; + std::size_t limit_; + std::size_t count_; + mutable typename traits::value_type blackhole_; -template <> -struct SignChecker<false> { - template <typename T> - static bool is_negative(T) { return false; } + public: + typedef std::output_iterator_tag iterator_category; + typedef typename traits::value_type value_type; + typedef typename traits::difference_type difference_type; + typedef typename traits::pointer pointer; + typedef typename traits::reference reference; + typedef truncating_iterator _Unchecked_type; // Mark iterator as checked. + + truncating_iterator(OutputIt out, std::size_t limit) + : out_(out), limit_(limit), count_(0) {} + + OutputIt base() const { return out_; } + std::size_t count() const { return count_; } + + truncating_iterator& operator++() { + if (count_++ < limit_) + ++out_; + return *this; + } + + truncating_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + reference operator*() const { return count_ < limit_ ? *out_ : blackhole_; } }; // Returns true if value is negative, false otherwise. // Same as (value < 0) but doesn't produce warnings if T is an unsigned type. template <typename T> -inline bool is_negative(T value) { - return SignChecker<std::numeric_limits<T>::is_signed>::is_negative(value); +FMT_CONSTEXPR typename std::enable_if< + std::numeric_limits<T>::is_signed, bool>::type is_negative(T value) { + return value < 0; +} +template <typename T> +FMT_CONSTEXPR typename std::enable_if< + !std::numeric_limits<T>::is_signed, bool>::type is_negative(T) { + return false; } - -// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise. -template <bool FitsIn32Bits> -struct TypeSelector { typedef uint32_t Type; }; - -template <> -struct TypeSelector<false> { typedef uint64_t Type; }; template <typename T> -struct IntTraits { +struct int_traits { // Smallest of uint32_t and uint64_t that is large enough to represent // all values of T. - typedef typename - TypeSelector<std::numeric_limits<T>::digits <= 32>::Type MainType; + typedef typename std::conditional< + std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>::type main_type; }; -FMT_API FMT_NORETURN void report_unknown_type(char code, const char *type); - // Static data is placed in this class template to allow header-only // configuration. template <typename T = void> -struct FMT_API BasicData { +struct FMT_API basic_data { static const uint32_t POWERS_OF_10_32[]; - static const uint64_t POWERS_OF_10_64[]; + static const uint32_t ZERO_OR_POWERS_OF_10_32[]; + static const uint64_t ZERO_OR_POWERS_OF_10_64[]; + static const uint64_t POW10_SIGNIFICANDS[]; + static const int16_t POW10_EXPONENTS[]; static const char DIGITS[]; + static const char RESET_COLOR[]; + static const wchar_t WRESET_COLOR[]; }; #if FMT_USE_EXTERN_TEMPLATES -extern template struct BasicData<void>; +extern template struct basic_data<void>; #endif -typedef BasicData<> Data; +typedef basic_data<> data; #ifdef FMT_BUILTIN_CLZLL // Returns the number of decimal digits in n. Leading zeros are not counted @@ -1022,7 +905,7 @@ inline unsigned count_digits(uint64_t n) { // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < Data::POWERS_OF_10_64[t]) + 1; + return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_64[t]) + 1; } #else // Fallback version of count_digits used when __builtin_clz is not available. @@ -1042,73 +925,194 @@ inline unsigned count_digits(uint64_t n) { } #endif +// Counts the number of code points in a UTF-8 string. +FMT_API size_t count_code_points(u8string_view s); + +#if FMT_HAS_CPP_ATTRIBUTE(always_inline) +# define FMT_ALWAYS_INLINE __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE +#endif + +template <typename Handler> +inline char *lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE; + +// Computes g = floor(log10(n)) and calls h.on<g>(n); +template <typename Handler> +inline char *lg(uint32_t n, Handler h) { + return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n) + : n < 1000000 + ? n < 10000 ? n < 1000 ? h.template on<2>(n) + : h.template on<3>(n) + : n < 100000 ? h.template on<4>(n) + : h.template on<5>(n) + : n < 100000000 ? n < 10000000 ? h.template on<6>(n) + : h.template on<7>(n) + : n < 1000000000 ? h.template on<8>(n) + : h.template on<9>(n); +} + +// An lg handler that formats a decimal number. +// Usage: lg(n, decimal_formatter(buffer)); +class decimal_formatter { + private: + char *buffer_; + + void write_pair(unsigned N, uint32_t index) { + std::memcpy(buffer_ + N, data::DIGITS + index * 2, 2); + } + + public: + explicit decimal_formatter(char *buf) : buffer_(buf) {} + + template <unsigned N> char *on(uint32_t u) { + if (N == 0) { + *buffer_ = static_cast<char>(u) + '0'; + } else if (N == 1) { + write_pair(0, u); + } else { + // The idea of using 4.32 fixed-point numbers is based on + // https://github.com/jeaiii/itoa + unsigned n = N - 1; + unsigned a = n / 5 * n * 53 / 16; + uint64_t t = ((1ULL << (32 + a)) / + data::ZERO_OR_POWERS_OF_10_32[n] + 1 - n / 9); + t = ((t * u) >> a) + n / 5 * 4; + write_pair(0, t >> 32); + for (unsigned i = 2; i < N; i += 2) { + t = 100ULL * static_cast<uint32_t>(t); + write_pair(i, t >> 32); + } + if (N % 2 == 0) { + buffer_[N] = static_cast<char>( + (10ULL * static_cast<uint32_t>(t)) >> 32) + '0'; + } + } + return buffer_ += N + 1; + } +}; + +// An lg handler that formats a decimal number with a terminating null. +class decimal_formatter_null : public decimal_formatter { + public: + explicit decimal_formatter_null(char *buf) : decimal_formatter(buf) {} + + template <unsigned N> char *on(uint32_t u) { + char *buf = decimal_formatter::on<N>(u); + *buf = '\0'; + return buf; + } +}; + #ifdef FMT_BUILTIN_CLZ // Optional version of count_digits for better performance on 32-bit platforms. inline unsigned count_digits(uint32_t n) { int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < Data::POWERS_OF_10_32[t]) + 1; + return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_32[t]) + 1; } #endif // A functor that doesn't add a thousands separator. -struct NoThousandsSep { +struct no_thousands_sep { + typedef char char_type; + template <typename Char> void operator()(Char *) {} }; // A functor that adds a thousands separator. -class ThousandsSep { +template <typename Char> +class add_thousands_sep { private: - fmt::StringRef sep_; + basic_string_view<Char> sep_; // Index of a decimal digit with the least significant digit having index 0. unsigned digit_index_; public: - explicit ThousandsSep(fmt::StringRef sep) : sep_(sep), digit_index_(0) {} + typedef Char char_type; + + explicit add_thousands_sep(basic_string_view<Char> sep) + : sep_(sep), digit_index_(0) {} - template <typename Char> void operator()(Char *&buffer) { if (++digit_index_ % 3 != 0) return; buffer -= sep_.size(); std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), - internal::make_ptr(buffer, sep_.size())); + internal::make_checked(buffer, sep_.size())); } }; +template <typename Char> +FMT_API Char thousands_sep(locale_provider *lp); + // Formats a decimal unsigned integer value writing into buffer. // thousands_sep is a functor that is called after writing each char to // add a thousands separator if necessary. template <typename UInt, typename Char, typename ThousandsSep> -inline void format_decimal(Char *buffer, UInt value, unsigned num_digits, - ThousandsSep thousands_sep) { +inline Char *format_decimal(Char *buffer, UInt value, unsigned num_digits, + ThousandsSep thousands_sep) { buffer += num_digits; + Char *end = buffer; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = static_cast<unsigned>((value % 100) * 2); value /= 100; - *--buffer = Data::DIGITS[index + 1]; + *--buffer = data::DIGITS[index + 1]; thousands_sep(buffer); - *--buffer = Data::DIGITS[index]; + *--buffer = data::DIGITS[index]; thousands_sep(buffer); } if (value < 10) { *--buffer = static_cast<char>('0' + value); - return; + return end; } unsigned index = static_cast<unsigned>(value * 2); - *--buffer = Data::DIGITS[index + 1]; + *--buffer = data::DIGITS[index + 1]; thousands_sep(buffer); - *--buffer = Data::DIGITS[index]; + *--buffer = data::DIGITS[index]; + return end; +} + +template <typename UInt, typename Iterator, typename ThousandsSep> +inline Iterator format_decimal( + Iterator out, UInt value, unsigned num_digits, ThousandsSep sep) { + typedef typename ThousandsSep::char_type char_type; + // Buffer should be large enough to hold all digits (digits10 + 1) and null. + char_type buffer[std::numeric_limits<UInt>::digits10 + 2]; + format_decimal(buffer, value, num_digits, sep); + return std::copy_n(buffer, num_digits, out); +} + +template <typename It, typename UInt> +inline It format_decimal(It out, UInt value, unsigned num_digits) { + return format_decimal(out, value, num_digits, no_thousands_sep()); +} + +template <unsigned BASE_BITS, typename Char, typename UInt> +inline Char *format_uint(Char *buffer, UInt value, unsigned num_digits, + bool upper = false) { + buffer += num_digits; + Char *end = buffer; + do { + const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = (value & ((1 << BASE_BITS) - 1)); + *--buffer = BASE_BITS < 4 ? static_cast<char>('0' + digit) : digits[digit]; + } while ((value >>= BASE_BITS) != 0); + return end; } -template <typename UInt, typename Char> -inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { - format_decimal(buffer, value, num_digits, NoThousandsSep()); - return; +template <unsigned BASE_BITS, typename It, typename UInt> +inline It format_uint(It out, UInt value, unsigned num_digits, + bool upper = false) { + // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) + // and null. + char buffer[std::numeric_limits<UInt>::digits / BASE_BITS + 2]; + format_uint<BASE_BITS>(buffer, value, num_digits, upper); + return std::copy_n(buffer, num_digits, out); } #ifndef _WIN32 @@ -1122,13 +1126,13 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) { #if FMT_USE_WINDOWS_H // A converter from UTF-8 to UTF-16. // It is only provided for Windows since other systems support UTF-8 natively. -class UTF8ToUTF16 { +class utf8_to_utf16 { private: - MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer_; + wmemory_buffer buffer_; public: - FMT_API explicit UTF8ToUTF16(StringRef s); - operator WStringRef() const { return WStringRef(&buffer_[0], size()); } + FMT_API explicit utf8_to_utf16(string_view s); + operator wstring_view() const { return wstring_view(&buffer_[0], size()); } size_t size() const { return buffer_.size() - 1; } const wchar_t *c_str() const { return &buffer_[0]; } std::wstring str() const { return std::wstring(&buffer_[0], size()); } @@ -1136,14 +1140,14 @@ class UTF8ToUTF16 { // A converter from UTF-16 to UTF-8. // It is only provided for Windows since other systems support UTF-8 natively. -class UTF16ToUTF8 { +class utf16_to_utf8 { private: - MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer_; + memory_buffer buffer_; public: - UTF16ToUTF8() {} - FMT_API explicit UTF16ToUTF8(WStringRef s); - operator StringRef() const { return StringRef(&buffer_[0], size()); } + utf16_to_utf8() {} + FMT_API explicit utf16_to_utf8(wstring_view s); + operator string_view() const { return string_view(&buffer_[0], size()); } size_t size() const { return buffer_.size() - 1; } const char *c_str() const { return &buffer_[0]; } std::string str() const { return std::string(&buffer_[0], size()); } @@ -1151,1417 +1155,1230 @@ class UTF16ToUTF8 { // Performs conversion returning a system error code instead of // throwing exception on conversion error. This method may still throw // in case of memory allocation error. - FMT_API int convert(WStringRef s); + FMT_API int convert(wstring_view s); }; -FMT_API void format_windows_error(fmt::Writer &out, int error_code, - fmt::StringRef message) FMT_NOEXCEPT; +FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, + fmt::string_view message) FMT_NOEXCEPT; #endif -// A formatting argument value. -struct Value { - template <typename Char> - struct StringValue { - const Char *value; - std::size_t size; - }; - - typedef void (*FormatFunc)( - void *formatter, const void *arg, void *format_str_ptr); - - struct CustomValue { - const void *value; - FormatFunc format; - }; - - union { - int int_value; - unsigned uint_value; - LongLong long_long_value; - ULongLong ulong_long_value; - double double_value; - long double long_double_value; - const void *pointer; - StringValue<char> string; - StringValue<signed char> sstring; - StringValue<unsigned char> ustring; - StringValue<wchar_t> wstring; - CustomValue custom; - }; - - enum Type { - NONE, NAMED_ARG, - // Integer types should go first, - INT, UINT, LONG_LONG, ULONG_LONG, BOOL, CHAR, LAST_INTEGER_TYPE = CHAR, - // followed by floating-point types. - DOUBLE, LONG_DOUBLE, LAST_NUMERIC_TYPE = LONG_DOUBLE, - CSTRING, STRING, WSTRING, POINTER, CUSTOM - }; -}; - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in internal::MemoryBuffer. -struct Arg : Value { - Type type; -}; - -template <typename Char> -struct NamedArg; -template <typename Char, typename T> -struct NamedArgWithType; - template <typename T = void> -struct Null {}; - -// A helper class template to enable or disable overloads taking wide -// characters and strings in MakeValue. -template <typename T, typename Char> -struct WCharHelper { - typedef Null<T> Supported; - typedef T Unsupported; -}; +struct null {}; +} // namespace internal -template <typename T> -struct WCharHelper<T, wchar_t> { - typedef T Supported; - typedef Null<T> Unsupported; +enum alignment { + ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC }; -typedef char Yes[1]; -typedef char No[2]; +// Flags. +enum {SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8}; -template <typename T> -T &get(); +enum format_spec_tag {fill_tag, align_tag, width_tag, type_tag}; -// These are non-members to workaround an overload resolution bug in bcc32. -Yes &convert(fmt::ULongLong); -No &convert(...); +// Format specifier. +template <typename T, format_spec_tag> +class format_spec { + private: + T value_; -template <typename T, bool ENABLE_CONVERSION> -struct ConvertToIntImpl { - enum { value = ENABLE_CONVERSION }; -}; + public: + typedef T value_type; -template <typename T, bool ENABLE_CONVERSION> -struct ConvertToIntImpl2 { - enum { value = false }; -}; + explicit format_spec(T value) : value_(value) {} -template <typename T> -struct ConvertToIntImpl2<T, true> { - enum { - // Don't convert numeric types. - value = ConvertToIntImpl<T, !std::numeric_limits<T>::is_specialized>::value - }; + T value() const { return value_; } }; -template <typename T> -struct ConvertToInt { - enum { - enable_conversion = sizeof(fmt::internal::convert(get<T>())) == sizeof(Yes) - }; - enum { value = ConvertToIntImpl2<T, enable_conversion>::value }; +// template <typename Char> +// typedef format_spec<Char, fill_tag> fill_spec; +template <typename Char> +class fill_spec : public format_spec<Char, fill_tag> { + public: + explicit fill_spec(Char value) : format_spec<Char, fill_tag>(value) {} }; -#define FMT_DISABLE_CONVERSION_TO_INT(Type) \ - template <> \ - struct ConvertToInt<Type> { enum { value = 0 }; } - -// Silence warnings about convering float to int. -FMT_DISABLE_CONVERSION_TO_INT(float); -FMT_DISABLE_CONVERSION_TO_INT(double); -FMT_DISABLE_CONVERSION_TO_INT(long double); +typedef format_spec<unsigned, width_tag> width_spec; +typedef format_spec<char, type_tag> type_spec; -template <bool B, class T = void> -struct EnableIf {}; +// An empty format specifier. +struct empty_spec {}; -template <class T> -struct EnableIf<true, T> { typedef T type; }; +// An alignment specifier. +struct align_spec : empty_spec { + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of AlignSpec and its subclasses. + wchar_t fill_; + alignment align_; -template <bool B, class T, class F> -struct Conditional { typedef T type; }; + FMT_CONSTEXPR align_spec( + unsigned width, wchar_t fill, alignment align = ALIGN_DEFAULT) + : width_(width), fill_(fill), align_(align) {} -template <class T, class F> -struct Conditional<false, T, F> { typedef F type; }; + FMT_CONSTEXPR unsigned width() const { return width_; } + FMT_CONSTEXPR wchar_t fill() const { return fill_; } + FMT_CONSTEXPR alignment align() const { return align_; } -// For bcc32 which doesn't understand ! in template arguments. -template <bool> -struct Not { enum { value = 0 }; }; + int precision() const { return -1; } +}; -template <> -struct Not<false> { enum { value = 1 }; }; +// Format specifiers. +template <typename Char> +class basic_format_specs : public align_spec { + public: + unsigned flags_; + int precision_; + Char type_; -template <typename T> -struct FalseType { enum { value = 0 }; }; + FMT_CONSTEXPR basic_format_specs( + unsigned width = 0, char type = 0, wchar_t fill = ' ') + : align_spec(width, fill), flags_(0), precision_(-1), type_(type) {} -template <typename T, T> struct LConvCheck { - LConvCheck(int) {} + FMT_CONSTEXPR bool flag(unsigned f) const { return (flags_ & f) != 0; } + FMT_CONSTEXPR int precision() const { return precision_; } + FMT_CONSTEXPR Char type() const { return type_; } }; -// Returns the thousands separator for the current locale. -// We check if ``lconv`` contains ``thousands_sep`` because on Android -// ``lconv`` is stubbed as an empty struct. -template <typename LConv> -inline StringRef thousands_sep( - LConv *lc, LConvCheck<char *LConv::*, &LConv::thousands_sep> = 0) { - return lc->thousands_sep; -} - -inline fmt::StringRef thousands_sep(...) { return ""; } - -#define FMT_CONCAT(a, b) a##b +typedef basic_format_specs<char> format_specs; -#if FMT_GCC_VERSION >= 303 -# define FMT_UNUSED __attribute__((unused)) -#else -# define FMT_UNUSED -#endif +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR unsigned basic_parse_context<Char, ErrorHandler>::next_arg_id() { + if (next_arg_id_ >= 0) + return internal::to_unsigned(next_arg_id_++); + on_error("cannot switch from manual to automatic argument indexing"); + return 0; +} -#ifndef FMT_USE_STATIC_ASSERT -# define FMT_USE_STATIC_ASSERT 0 -#endif +namespace internal { -#if FMT_USE_STATIC_ASSERT || FMT_HAS_FEATURE(cxx_static_assert) || \ - (FMT_GCC_VERSION >= 403 && FMT_HAS_GXX_CXX11) || _MSC_VER >= 1600 -# define FMT_STATIC_ASSERT(cond, message) static_assert(cond, message) -#else -# define FMT_CONCAT_(a, b) FMT_CONCAT(a, b) -# define FMT_STATIC_ASSERT(cond, message) \ - typedef int FMT_CONCAT_(Assert, __LINE__)[(cond) ? 1 : -1] FMT_UNUSED -#endif +template <typename S> +struct format_string_traits< + S, typename std::enable_if<std::is_base_of<compile_string, S>::value>::type>: + format_string_traits_base<char> {}; -template <typename Formatter> -void format_arg(Formatter&, ...) { - FMT_STATIC_ASSERT(FalseType<Formatter>::value, - "Cannot format argument. To enable the use of ostream " - "operator<< include fmt/ostream.h. Otherwise provide " - "an overload of format_arg."); -} +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_int_type_spec(Char spec, Handler &&handler) { + switch (spec) { + case 0: case 'd': + handler.on_dec(); + break; + case 'x': case 'X': + handler.on_hex(); + break; + case 'b': case 'B': + handler.on_bin(); + break; + case 'o': + handler.on_oct(); + break; + case 'n': + handler.on_num(); + break; + default: + handler.on_error(); + } +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_float_type_spec(Char spec, Handler &&handler) { + switch (spec) { + case 0: case 'g': case 'G': + handler.on_general(); + break; + case 'e': case 'E': + handler.on_exp(); + break; + case 'f': case 'F': + handler.on_fixed(); + break; + case 'a': case 'A': + handler.on_hex(); + break; + default: + handler.on_error(); + break; + } +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_char_specs( + const basic_format_specs<Char> *specs, Handler &&handler) { + if (!specs) return handler.on_char(); + if (specs->type() && specs->type() != 'c') return handler.on_int(); + if (specs->align() == ALIGN_NUMERIC || specs->flag(~0u) != 0) + handler.on_error("invalid format specifier for char"); + handler.on_char(); +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler &&handler) { + if (spec == 0 || spec == 's') + handler.on_string(); + else if (spec == 'p') + handler.on_pointer(); + else + handler.on_error("invalid type specifier"); +} + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler &&eh) { + if (spec != 0 && spec != 's') + eh.on_error("invalid type specifier"); +} + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler &&eh) { + if (spec != 0 && spec != 'p') + eh.on_error("invalid type specifier"); +} -// Makes an Arg object from any type. -template <typename Formatter> -class MakeValue : public Arg { +template <typename ErrorHandler> +class int_type_checker : private ErrorHandler { public: - typedef typename Formatter::Char Char; + FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} - private: - // The following two methods are private to disallow formatting of - // arbitrary pointers. If you want to output a pointer cast it to - // "void *" or "const void *". In particular, this forbids formatting - // of "[const] volatile char *" which is printed as bool by iostreams. - // Do not implement! - template <typename T> - MakeValue(const T *value); - template <typename T> - MakeValue(T *value); - - // The following methods are private to disallow formatting of wide - // characters and strings into narrow strings as in - // fmt::format("{}", L"test"); - // To fix this, use a wide format string: fmt::format(L"{}", L"test"). -#if !FMT_MSC_VER || defined(_NATIVE_WCHAR_T_DEFINED) - MakeValue(typename WCharHelper<wchar_t, Char>::Unsupported); -#endif - MakeValue(typename WCharHelper<wchar_t *, Char>::Unsupported); - MakeValue(typename WCharHelper<const wchar_t *, Char>::Unsupported); - MakeValue(typename WCharHelper<const std::wstring &, Char>::Unsupported); -#if FMT_HAS_STRING_VIEW - MakeValue(typename WCharHelper<const std::wstring_view &, Char>::Unsupported); -#endif - MakeValue(typename WCharHelper<WStringRef, Char>::Unsupported); + FMT_CONSTEXPR void on_dec() {} + FMT_CONSTEXPR void on_hex() {} + FMT_CONSTEXPR void on_bin() {} + FMT_CONSTEXPR void on_oct() {} + FMT_CONSTEXPR void on_num() {} - void set_string(StringRef str) { - string.value = str.data(); - string.size = str.size(); + FMT_CONSTEXPR void on_error() { + ErrorHandler::on_error("invalid type specifier"); } +}; + +template <typename ErrorHandler> +class float_type_checker : private ErrorHandler { + public: + FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh) + : ErrorHandler(eh) {} + + FMT_CONSTEXPR void on_general() {} + FMT_CONSTEXPR void on_exp() {} + FMT_CONSTEXPR void on_fixed() {} + FMT_CONSTEXPR void on_hex() {} - void set_string(WStringRef str) { - wstring.value = str.data(); - wstring.size = str.size(); + FMT_CONSTEXPR void on_error() { + ErrorHandler::on_error("invalid type specifier"); } +}; - // Formats an argument of a custom type, such as a user-defined class. - template <typename T> - static void format_custom_arg( - void *formatter, const void *arg, void *format_str_ptr) { - format_arg(*static_cast<Formatter*>(formatter), - *static_cast<const Char**>(format_str_ptr), - *static_cast<const T*>(arg)); +template <typename ErrorHandler, typename CharType> +class char_specs_checker : public ErrorHandler { + private: + CharType type_; + + public: + FMT_CONSTEXPR char_specs_checker(CharType type, ErrorHandler eh) + : ErrorHandler(eh), type_(type) {} + + FMT_CONSTEXPR void on_int() { + handle_int_type_spec(type_, int_type_checker<ErrorHandler>(*this)); } + FMT_CONSTEXPR void on_char() {} +}; +template <typename ErrorHandler> +class cstring_type_checker : public ErrorHandler { public: - MakeValue() {} - -#define FMT_MAKE_VALUE_(Type, field, TYPE, rhs) \ - MakeValue(Type value) { field = rhs; } \ - static uint64_t type(Type) { return Arg::TYPE; } - -#define FMT_MAKE_VALUE(Type, field, TYPE) \ - FMT_MAKE_VALUE_(Type, field, TYPE, value) - - FMT_MAKE_VALUE(bool, int_value, BOOL) - FMT_MAKE_VALUE(short, int_value, INT) - FMT_MAKE_VALUE(unsigned short, uint_value, UINT) - FMT_MAKE_VALUE(int, int_value, INT) - FMT_MAKE_VALUE(unsigned, uint_value, UINT) - - MakeValue(long value) { - // To minimize the number of types we need to deal with, long is - // translated either to int or to long long depending on its size. - if (const_check(sizeof(long) == sizeof(int))) - int_value = static_cast<int>(value); - else - long_long_value = value; + FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) + : ErrorHandler(eh) {} + + FMT_CONSTEXPR void on_string() {} + FMT_CONSTEXPR void on_pointer() {} +}; + +template <typename Context> +void arg_map<Context>::init(const basic_format_args<Context> &args) { + if (map_) + return; + map_ = new entry[args.max_size()]; + bool use_values = args.type(max_packed_args - 1) == internal::none_type; + if (use_values) { + for (unsigned i = 0;/*nothing*/; ++i) { + internal::type arg_type = args.type(i); + switch (arg_type) { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.values_[i]); + break; + default: + break; // Do nothing. + } + } } - static uint64_t type(long) { - return sizeof(long) == sizeof(int) ? Arg::INT : Arg::LONG_LONG; + for (unsigned i = 0; ; ++i) { + switch (args.args_[i].type_) { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.args_[i].value_); + break; + default: + break; // Do nothing. + } } +} + +template <typename Range> +class arg_formatter_base { + public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval<Range>().begin()) iterator; + typedef basic_format_specs<char_type> format_specs; - MakeValue(unsigned long value) { - if (const_check(sizeof(unsigned long) == sizeof(unsigned))) - uint_value = static_cast<unsigned>(value); + private: + typedef basic_writer<Range> writer_type; + writer_type writer_; + format_specs *specs_; + + struct char_writer { + char_type value; + template <typename It> + void operator()(It &&it) const { *it++ = value; } + }; + + void write_char(char_type value) { + if (specs_) + writer_.write_padded(1, *specs_, char_writer{value}); else - ulong_long_value = value; - } - static uint64_t type(unsigned long) { - return sizeof(unsigned long) == sizeof(unsigned) ? - Arg::UINT : Arg::ULONG_LONG; - } - - FMT_MAKE_VALUE(LongLong, long_long_value, LONG_LONG) - FMT_MAKE_VALUE(ULongLong, ulong_long_value, ULONG_LONG) - FMT_MAKE_VALUE(float, double_value, DOUBLE) - FMT_MAKE_VALUE(double, double_value, DOUBLE) - FMT_MAKE_VALUE(long double, long_double_value, LONG_DOUBLE) - FMT_MAKE_VALUE(signed char, int_value, INT) - FMT_MAKE_VALUE(unsigned char, uint_value, UINT) - FMT_MAKE_VALUE(char, int_value, CHAR) - -#if __cplusplus >= 201103L - template < - typename T, - typename = typename std::enable_if< - std::is_enum<T>::value && ConvertToInt<T>::value>::type> - MakeValue(T value) { int_value = value; } - - template < - typename T, - typename = typename std::enable_if< - std::is_enum<T>::value && ConvertToInt<T>::value>::type> - static uint64_t type(T) { return Arg::INT; } -#endif + writer_.write(value); + } -#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) - MakeValue(typename WCharHelper<wchar_t, Char>::Supported value) { - int_value = value; + void write_pointer(const void *p) { + format_specs specs = specs_ ? *specs_ : format_specs(); + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + writer_.write_int(reinterpret_cast<uintptr_t>(p), specs); } - static uint64_t type(wchar_t) { return Arg::CHAR; } -#endif -#define FMT_MAKE_STR_VALUE(Type, TYPE) \ - MakeValue(Type value) { set_string(value); } \ - static uint64_t type(Type) { return Arg::TYPE; } - - FMT_MAKE_VALUE(char *, string.value, CSTRING) - FMT_MAKE_VALUE(const char *, string.value, CSTRING) - FMT_MAKE_VALUE(signed char *, sstring.value, CSTRING) - FMT_MAKE_VALUE(const signed char *, sstring.value, CSTRING) - FMT_MAKE_VALUE(unsigned char *, ustring.value, CSTRING) - FMT_MAKE_VALUE(const unsigned char *, ustring.value, CSTRING) - FMT_MAKE_STR_VALUE(const std::string &, STRING) -#if FMT_HAS_STRING_VIEW - FMT_MAKE_STR_VALUE(const std::string_view &, STRING) -#endif - FMT_MAKE_STR_VALUE(StringRef, STRING) - FMT_MAKE_VALUE_(CStringRef, string.value, CSTRING, value.c_str()) - -#define FMT_MAKE_WSTR_VALUE(Type, TYPE) \ - MakeValue(typename WCharHelper<Type, Char>::Supported value) { \ - set_string(value); \ - } \ - static uint64_t type(Type) { return Arg::TYPE; } - - FMT_MAKE_WSTR_VALUE(wchar_t *, WSTRING) - FMT_MAKE_WSTR_VALUE(const wchar_t *, WSTRING) - FMT_MAKE_WSTR_VALUE(const std::wstring &, WSTRING) -#if FMT_HAS_STRING_VIEW - FMT_MAKE_WSTR_VALUE(const std::wstring_view &, WSTRING) -#endif - FMT_MAKE_WSTR_VALUE(WStringRef, WSTRING) + protected: + writer_type &writer() { return writer_; } + format_specs *spec() { return specs_; } + iterator out() { return writer_.out(); } - FMT_MAKE_VALUE(void *, pointer, POINTER) - FMT_MAKE_VALUE(const void *, pointer, POINTER) + void write(bool value) { + string_view sv(value ? "true" : "false"); + specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); + } - template <typename T> - MakeValue(const T &value, - typename EnableIf<Not< - ConvertToInt<T>::value>::value, int>::type = 0) { - custom.value = &value; - custom.format = &format_custom_arg<T>; + void write(const char_type *value) { + if (!value) + FMT_THROW(format_error("string pointer is null")); + auto length = std::char_traits<char_type>::length(value); + basic_string_view<char_type> sv(value, length); + specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); } - template <typename T> - static typename EnableIf<Not<ConvertToInt<T>::value>::value, uint64_t>::type - type(const T &) { - return Arg::CUSTOM; - } - - // Additional template param `Char_` is needed here because make_type always - // uses char. - template <typename Char_> - MakeValue(const NamedArg<Char_> &value) { pointer = &value; } - template <typename Char_, typename T> - MakeValue(const NamedArgWithType<Char_, T> &value) { pointer = &value; } - - template <typename Char_> - static uint64_t type(const NamedArg<Char_> &) { return Arg::NAMED_ARG; } - template <typename Char_, typename T> - static uint64_t type(const NamedArgWithType<Char_, T> &) { return Arg::NAMED_ARG; } -}; + public: + arg_formatter_base(Range r, format_specs *s): writer_(r), specs_(s) {} -template <typename Formatter> -class MakeArg : public Arg { -public: - MakeArg() { - type = Arg::NONE; + iterator operator()(monostate) { + FMT_ASSERT(false, "invalid argument type"); + return out(); } template <typename T> - MakeArg(const T &value) - : Arg(MakeValue<Formatter>(value)) { - type = static_cast<Arg::Type>(MakeValue<Formatter>::type(value)); + typename std::enable_if<std::is_integral<T>::value, iterator>::type + operator()(T value) { + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same<T, bool>::value) { + if (specs_ && specs_->type_) + return (*this)(value ? 1 : 0); + write(value != 0); + } else if (std::is_same<T, char_type>::value) { + internal::handle_char_specs( + specs_, char_spec_handler(*this, static_cast<char_type>(value))); + } else { + specs_ ? writer_.write_int(value, *specs_) : writer_.write(value); + } + return out(); } -}; - -template <typename Char> -struct NamedArg : Arg { - BasicStringRef<Char> name; template <typename T> - NamedArg(BasicStringRef<Char> argname, const T &value) - : Arg(MakeArg< BasicFormatter<Char> >(value)), name(argname) {} -}; + typename std::enable_if<std::is_floating_point<T>::value, iterator>::type + operator()(T value) { + writer_.write_double(value, specs_ ? *specs_ : format_specs()); + return out(); + } -template <typename Char, typename T> -struct NamedArgWithType : NamedArg<Char> { - NamedArgWithType(BasicStringRef<Char> argname, const T &value) - : NamedArg<Char>(argname, value) {} -}; + struct char_spec_handler : internal::error_handler { + arg_formatter_base &formatter; + char_type value; -class RuntimeError : public std::runtime_error { - protected: - RuntimeError() : std::runtime_error("") {} - RuntimeError(const RuntimeError &rerr) : std::runtime_error(rerr) {} - FMT_API ~RuntimeError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; -}; + char_spec_handler(arg_formatter_base& f, char_type val) + : formatter(f), value(val) {} -template <typename Char> -class ArgMap; -} // namespace internal + void on_int() { + if (formatter.specs_) + formatter.writer_.write_int(value, *formatter.specs_); + else + formatter.writer_.write(value); + } + void on_char() { formatter.write_char(value); } + }; -/** An argument list. */ -class ArgList { - private: - // To reduce compiled code size per formatting function call, types of first - // MAX_PACKED_ARGS arguments are passed in the types_ field. - uint64_t types_; - union { - // If the number of arguments is less than MAX_PACKED_ARGS, the argument - // values are stored in values_, otherwise they are stored in args_. - // This is done to reduce compiled code size as storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const internal::Value *values_; - const internal::Arg *args_; + struct cstring_spec_handler : internal::error_handler { + arg_formatter_base &formatter; + const char_type *value; + + cstring_spec_handler(arg_formatter_base &f, const char_type *val) + : formatter(f), value(val) {} + + void on_string() { formatter.write(value); } + void on_pointer() { formatter.write_pointer(value); } }; - internal::Arg::Type type(unsigned index) const { - return type(types_, index); + iterator operator()(const char_type *value) { + if (!specs_) return write(value), out(); + internal::handle_cstring_type_spec( + specs_->type_, cstring_spec_handler(*this, value)); + return out(); } - template <typename Char> - friend class internal::ArgMap; - - public: - // Maximum number of arguments with packed types. - enum { MAX_PACKED_ARGS = 16 }; - - ArgList() : types_(0) {} - - ArgList(ULongLong types, const internal::Value *values) - : types_(types), values_(values) {} - ArgList(ULongLong types, const internal::Arg *args) - : types_(types), args_(args) {} - - uint64_t types() const { return types_; } - - /** Returns the argument at specified index. */ - internal::Arg operator[](unsigned index) const { - using internal::Arg; - Arg arg; - bool use_values = type(MAX_PACKED_ARGS - 1) == Arg::NONE; - if (index < MAX_PACKED_ARGS) { - Arg::Type arg_type = type(index); - internal::Value &val = arg; - if (arg_type != Arg::NONE) - val = use_values ? values_[index] : args_[index]; - arg.type = arg_type; - return arg; - } - if (use_values) { - // The index is greater than the number of arguments that can be stored - // in values, so return a "none" argument. - arg.type = Arg::NONE; - return arg; - } - for (unsigned i = MAX_PACKED_ARGS; i <= index; ++i) { - if (args_[i].type == Arg::NONE) - return args_[i]; + iterator operator()(basic_string_view<char_type> value) { + if (specs_) { + internal::check_string_type_spec( + specs_->type_, internal::error_handler()); + writer_.write_str(value, *specs_); + } else { + writer_.write(value); } - return args_[index]; + return out(); } - static internal::Arg::Type type(uint64_t types, unsigned index) { - unsigned shift = index * 4; - uint64_t mask = 0xf; - return static_cast<internal::Arg::Type>( - (types & (mask << shift)) >> shift); + iterator operator()(const void *value) { + if (specs_) + check_pointer_type_spec(specs_->type_, internal::error_handler()); + write_pointer(value); + return out(); } }; -#define FMT_DISPATCH(call) static_cast<Impl*>(this)->call +template <typename Char> +FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} -/** - \rst - An argument visitor based on the `curiously recurring template pattern - <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_. - - To use `~fmt::ArgVisitor` define a subclass that implements some or all of the - visit methods with the same signatures as the methods in `~fmt::ArgVisitor`, - for example, `~fmt::ArgVisitor::visit_int()`. - Pass the subclass as the *Impl* template parameter. Then calling - `~fmt::ArgVisitor::visit` for some argument will dispatch to a visit method - specific to the argument type. For example, if the argument type is - ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass - will be called. If the subclass doesn't contain a method with this signature, - then a corresponding method of `~fmt::ArgVisitor` will be called. +// DEPRECATED: Parses the input as an unsigned integer. This function assumes +// that the first character is a digit and presence of a non-digit character at +// the end. +// it: an iterator pointing to the beginning of the input range. +template <typename Iterator, typename ErrorHandler> +FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { + assert('0' <= *it && *it <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits<int>::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*it - '0'); + // Workaround for MSVC "setup_exception stack overflow" error: + auto next = it; + ++next; + it = next; + } while ('0' <= *it && *it <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} - **Example**:: +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR unsigned parse_nonnegative_int( + const Char *&begin, const Char *end, ErrorHandler &&eh) { + assert(begin != end && '0' <= *begin && *begin <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits<int>::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*begin++ - '0'); + } while (begin != end && '0' <= *begin && *begin <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} - class MyArgVisitor : public fmt::ArgVisitor<MyArgVisitor, void> { - public: - void visit_int(int value) { fmt::print("{}", value); } - void visit_double(double value) { fmt::print("{}", value ); } - }; - \endrst - */ -template <typename Impl, typename Result> -class ArgVisitor { +template <typename Char, typename Context> +class custom_formatter: public function<bool> { private: - typedef internal::Arg Arg; + Context &ctx_; public: - void report_unhandled_arg() {} + explicit custom_formatter(Context &ctx): ctx_(ctx) {} - Result visit_unhandled_arg() { - FMT_DISPATCH(report_unhandled_arg()); - return Result(); + bool operator()(typename basic_format_arg<Context>::handle h) const { + h.format(ctx_); + return true; } - /** Visits an ``int`` argument. **/ - Result visit_int(int value) { - return FMT_DISPATCH(visit_any_int(value)); - } + template <typename T> + bool operator()(T) const { return false; } +}; + +template <typename T> +struct is_integer { + enum { + value = std::is_integral<T>::value && !std::is_same<T, bool>::value && + !std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value + }; +}; - /** Visits a ``long long`` argument. **/ - Result visit_long_long(LongLong value) { - return FMT_DISPATCH(visit_any_int(value)); +template <typename ErrorHandler> +class width_checker: public function<unsigned long long> { + public: + explicit FMT_CONSTEXPR width_checker(ErrorHandler &eh) : handler_(eh) {} + + template <typename T> + FMT_CONSTEXPR + typename std::enable_if< + is_integer<T>::value, unsigned long long>::type operator()(T value) { + if (is_negative(value)) + handler_.on_error("negative width"); + return static_cast<unsigned long long>(value); } - /** Visits an ``unsigned`` argument. **/ - Result visit_uint(unsigned value) { - return FMT_DISPATCH(visit_any_int(value)); + template <typename T> + FMT_CONSTEXPR typename std::enable_if< + !is_integer<T>::value, unsigned long long>::type operator()(T) { + handler_.on_error("width is not integer"); + return 0; } - /** Visits an ``unsigned long long`` argument. **/ - Result visit_ulong_long(ULongLong value) { - return FMT_DISPATCH(visit_any_int(value)); + private: + ErrorHandler &handler_; +}; + +template <typename ErrorHandler> +class precision_checker: public function<unsigned long long> { + public: + explicit FMT_CONSTEXPR precision_checker(ErrorHandler &eh) : handler_(eh) {} + + template <typename T> + FMT_CONSTEXPR typename std::enable_if< + is_integer<T>::value, unsigned long long>::type operator()(T value) { + if (is_negative(value)) + handler_.on_error("negative precision"); + return static_cast<unsigned long long>(value); } - /** Visits a ``bool`` argument. **/ - Result visit_bool(bool value) { - return FMT_DISPATCH(visit_any_int(value)); + template <typename T> + FMT_CONSTEXPR typename std::enable_if< + !is_integer<T>::value, unsigned long long>::type operator()(T) { + handler_.on_error("precision is not integer"); + return 0; } - /** Visits a ``char`` or ``wchar_t`` argument. **/ - Result visit_char(int value) { - return FMT_DISPATCH(visit_any_int(value)); + private: + ErrorHandler &handler_; +}; + +// A format specifier handler that sets fields in basic_format_specs. +template <typename Char> +class specs_setter { + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char> &specs): + specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter &other) : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(alignment align) { specs_.align_ = align; } + FMT_CONSTEXPR void on_fill(Char fill) { specs_.fill_ = fill; } + FMT_CONSTEXPR void on_plus() { specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; } + FMT_CONSTEXPR void on_minus() { specs_.flags_ |= MINUS_FLAG; } + FMT_CONSTEXPR void on_space() { specs_.flags_ |= SIGN_FLAG; } + FMT_CONSTEXPR void on_hash() { specs_.flags_ |= HASH_FLAG; } + + FMT_CONSTEXPR void on_zero() { + specs_.align_ = ALIGN_NUMERIC; + specs_.fill_ = '0'; } - /** Visits an argument of any integral type. **/ - template <typename T> - Result visit_any_int(T) { - return FMT_DISPATCH(visit_unhandled_arg()); + FMT_CONSTEXPR void on_width(unsigned width) { specs_.width_ = width; } + FMT_CONSTEXPR void on_precision(unsigned precision) { + specs_.precision_ = static_cast<int>(precision); } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(Char type) { specs_.type_ = type; } + + protected: + basic_format_specs<Char> &specs_; +}; - /** Visits a ``double`` argument. **/ - Result visit_double(double value) { - return FMT_DISPATCH(visit_any_double(value)); +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template <typename Handler> +class specs_checker : public Handler { + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + FMT_CONSTEXPR specs_checker(const specs_checker &other) + : Handler(other), arg_type_(other.arg_type_) {} + + FMT_CONSTEXPR void on_align(alignment align) { + if (align == ALIGN_NUMERIC) + require_numeric_argument(); + Handler::on_align(align); } - /** Visits a ``long double`` argument. **/ - Result visit_long_double(long double value) { - return FMT_DISPATCH(visit_any_double(value)); + FMT_CONSTEXPR void on_plus() { + check_sign(); + Handler::on_plus(); } - /** Visits a ``double`` or ``long double`` argument. **/ - template <typename T> - Result visit_any_double(T) { - return FMT_DISPATCH(visit_unhandled_arg()); + FMT_CONSTEXPR void on_minus() { + check_sign(); + Handler::on_minus(); } - /** Visits a null-terminated C string (``const char *``) argument. **/ - Result visit_cstring(const char *) { - return FMT_DISPATCH(visit_unhandled_arg()); + FMT_CONSTEXPR void on_space() { + check_sign(); + Handler::on_space(); } - /** Visits a string argument. **/ - Result visit_string(Arg::StringValue<char>) { - return FMT_DISPATCH(visit_unhandled_arg()); + FMT_CONSTEXPR void on_hash() { + require_numeric_argument(); + Handler::on_hash(); } - /** Visits a wide string argument. **/ - Result visit_wstring(Arg::StringValue<wchar_t>) { - return FMT_DISPATCH(visit_unhandled_arg()); + FMT_CONSTEXPR void on_zero() { + require_numeric_argument(); + Handler::on_zero(); } - /** Visits a pointer argument. **/ - Result visit_pointer(const void *) { - return FMT_DISPATCH(visit_unhandled_arg()); + FMT_CONSTEXPR void end_precision() { + if (is_integral(arg_type_) || arg_type_ == pointer_type) + this->on_error("precision not allowed for this argument type"); } - /** Visits an argument of a custom (user-defined) type. **/ - Result visit_custom(Arg::CustomValue) { - return FMT_DISPATCH(visit_unhandled_arg()); + private: + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic(arg_type_)) + this->on_error("format specifier requires numeric argument"); } - /** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - the `~fmt::ArgVisitor::visit_double()` method of the *Impl* class will be - called. - \endrst - */ - Result visit(const Arg &arg) { - switch (arg.type) { - case Arg::NONE: - case Arg::NAMED_ARG: - FMT_ASSERT(false, "invalid argument type"); - break; - case Arg::INT: - return FMT_DISPATCH(visit_int(arg.int_value)); - case Arg::UINT: - return FMT_DISPATCH(visit_uint(arg.uint_value)); - case Arg::LONG_LONG: - return FMT_DISPATCH(visit_long_long(arg.long_long_value)); - case Arg::ULONG_LONG: - return FMT_DISPATCH(visit_ulong_long(arg.ulong_long_value)); - case Arg::BOOL: - return FMT_DISPATCH(visit_bool(arg.int_value != 0)); - case Arg::CHAR: - return FMT_DISPATCH(visit_char(arg.int_value)); - case Arg::DOUBLE: - return FMT_DISPATCH(visit_double(arg.double_value)); - case Arg::LONG_DOUBLE: - return FMT_DISPATCH(visit_long_double(arg.long_double_value)); - case Arg::CSTRING: - return FMT_DISPATCH(visit_cstring(arg.string.value)); - case Arg::STRING: - return FMT_DISPATCH(visit_string(arg.string)); - case Arg::WSTRING: - return FMT_DISPATCH(visit_wstring(arg.wstring)); - case Arg::POINTER: - return FMT_DISPATCH(visit_pointer(arg.pointer)); - case Arg::CUSTOM: - return FMT_DISPATCH(visit_custom(arg.custom)); + FMT_CONSTEXPR void check_sign() { + require_numeric_argument(); + if (is_integral(arg_type_) && arg_type_ != int_type && + arg_type_ != long_long_type && arg_type_ != internal::char_type) { + this->on_error("format specifier requires signed argument"); } - return Result(); } -}; -enum Alignment { - ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC + internal::type arg_type_; }; -// Flags. -enum { - SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8, - CHAR_FLAG = 0x10 // Argument has char type - used in error reporting. -}; +template <template <typename> class Handler, typename T, + typename Context, typename ErrorHandler> +FMT_CONSTEXPR void set_dynamic_spec( + T &value, basic_format_arg<Context> arg, ErrorHandler eh) { + unsigned long long big_value = fmt::visit(Handler<ErrorHandler>(eh), arg); + if (big_value > (std::numeric_limits<int>::max)()) + eh.on_error("number is too big"); + value = static_cast<T>(big_value); +} -// An empty format specifier. -struct EmptySpec {}; +struct auto_id {}; -// A type specifier. -template <char TYPE> -struct TypeSpec : EmptySpec { - Alignment align() const { return ALIGN_DEFAULT; } - unsigned width() const { return 0; } - int precision() const { return -1; } - bool flag(unsigned) const { return false; } - char type() const { return TYPE; } - char type_prefix() const { return TYPE; } - char fill() const { return ' '; } -}; +// The standard format specifier handler with checking. +template <typename Context> +class specs_handler: public specs_setter<typename Context::char_type> { + public: + typedef typename Context::char_type char_type; -// A width specifier. -struct WidthSpec { - unsigned width_; - // Fill is always wchar_t and cast to char if necessary to avoid having - // two specialization of WidthSpec and its subclasses. - wchar_t fill_; + FMT_CONSTEXPR specs_handler( + basic_format_specs<char_type> &specs, Context &ctx) + : specs_setter<char_type>(specs), context_(ctx) {} - WidthSpec(unsigned width, wchar_t fill) : width_(width), fill_(fill) {} + template <typename Id> + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + set_dynamic_spec<width_checker>( + this->specs_.width_, get_arg(arg_id), context_.error_handler()); + } - unsigned width() const { return width_; } - wchar_t fill() const { return fill_; } -}; + template <typename Id> + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + set_dynamic_spec<precision_checker>( + this->specs_.precision_, get_arg(arg_id), context_.error_handler()); + } -// An alignment specifier. -struct AlignSpec : WidthSpec { - Alignment align_; + void on_error(const char *message) { + context_.on_error(message); + } - AlignSpec(unsigned width, wchar_t fill, Alignment align = ALIGN_DEFAULT) - : WidthSpec(width, fill), align_(align) {} + private: + FMT_CONSTEXPR basic_format_arg<Context> get_arg(auto_id) { + return context_.next_arg(); + } - Alignment align() const { return align_; } + template <typename Id> + FMT_CONSTEXPR basic_format_arg<Context> get_arg(Id arg_id) { + context_.parse_context().check_arg_id(arg_id); + return context_.get_arg(arg_id); + } - int precision() const { return -1; } + Context &context_; }; -// An alignment and type specifier. -template <char TYPE> -struct AlignTypeSpec : AlignSpec { - AlignTypeSpec(unsigned width, wchar_t fill) : AlignSpec(width, fill) {} - - bool flag(unsigned) const { return false; } - char type() const { return TYPE; } - char type_prefix() const { return TYPE; } -}; +// An argument reference. +template <typename Char> +struct arg_ref { + enum Kind { NONE, INDEX, NAME }; -// A full format specifier. -struct FormatSpec : AlignSpec { - unsigned flags_; - int precision_; - char type_; + FMT_CONSTEXPR arg_ref() : kind(NONE), index(0) {} + FMT_CONSTEXPR explicit arg_ref(unsigned index) : kind(INDEX), index(index) {} + explicit arg_ref(basic_string_view<Char> name) : kind(NAME), name(name) {} - FormatSpec( - unsigned width = 0, char type = 0, wchar_t fill = ' ') - : AlignSpec(width, fill), flags_(0), precision_(-1), type_(type) {} + FMT_CONSTEXPR arg_ref &operator=(unsigned idx) { + kind = INDEX; + index = idx; + return *this; + } - bool flag(unsigned f) const { return (flags_ & f) != 0; } - int precision() const { return precision_; } - char type() const { return type_; } - char type_prefix() const { return type_; } + Kind kind; + FMT_UNION { + unsigned index; + basic_string_view<Char> name; + }; }; -// An integer format specifier. -template <typename T, typename SpecT = TypeSpec<0>, typename Char = char> -class IntFormatSpec : public SpecT { - private: - T value_; +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// differents sets of arguments (precompilation of format strings). +template <typename Char> +struct dynamic_format_specs : basic_format_specs<Char> { + arg_ref<Char> width_ref; + arg_ref<Char> precision_ref; +}; +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template <typename ParseContext> +class dynamic_specs_handler : + public specs_setter<typename ParseContext::char_type> { public: - IntFormatSpec(T val, const SpecT &spec = SpecT()) - : SpecT(spec), value_(val) {} + typedef typename ParseContext::char_type char_type; - T value() const { return value_; } -}; + FMT_CONSTEXPR dynamic_specs_handler( + dynamic_format_specs<char_type> &specs, ParseContext &ctx) + : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} -// A string format specifier. -template <typename Char> -class StrFormatSpec : public AlignSpec { - private: - const Char *str_; + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler &other) + : specs_setter<char_type>(other), + specs_(other.specs_), context_(other.context_) {} - public: - template <typename FillChar> - StrFormatSpec(const Char *str, unsigned width, FillChar fill) - : AlignSpec(width, fill), str_(str) { - internal::CharTraits<Char>::convert(FillChar()); + template <typename Id> + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); } - const Char *str() const { return str_; } -}; - -/** - Returns an integer format specifier to format the value in base 2. - */ -IntFormatSpec<int, TypeSpec<'b'> > bin(int value); - -/** - Returns an integer format specifier to format the value in base 8. - */ -IntFormatSpec<int, TypeSpec<'o'> > oct(int value); + template <typename Id> + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } -/** - Returns an integer format specifier to format the value in base 16 using - lower-case letters for the digits above 9. - */ -IntFormatSpec<int, TypeSpec<'x'> > hex(int value); + FMT_CONSTEXPR void on_error(const char *message) { + context_.on_error(message); + } -/** - Returns an integer formatter format specifier to format in base 16 using - upper-case letters for the digits above 9. - */ -IntFormatSpec<int, TypeSpec<'X'> > hexu(int value); + private: + typedef arg_ref<char_type> arg_ref_type; -/** - \rst - Returns an integer format specifier to pad the formatted argument with the - fill character to the specified width using the default (right) numeric - alignment. + template <typename Id> + FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } - **Example**:: + FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) { + return arg_ref_type(context_.next_arg_id()); + } - MemoryWriter out; - out << pad(hex(0xcafe), 8, '0'); - // out.str() == "0000cafe" + dynamic_format_specs<char_type> &specs_; + ParseContext &context_; +}; - \endrst - */ -template <char TYPE_CODE, typename Char> -IntFormatSpec<int, AlignTypeSpec<TYPE_CODE>, Char> pad( - int value, unsigned width, Char fill = ' '); - -#define FMT_DEFINE_INT_FORMATTERS(TYPE) \ -inline IntFormatSpec<TYPE, TypeSpec<'b'> > bin(TYPE value) { \ - return IntFormatSpec<TYPE, TypeSpec<'b'> >(value, TypeSpec<'b'>()); \ -} \ - \ -inline IntFormatSpec<TYPE, TypeSpec<'o'> > oct(TYPE value) { \ - return IntFormatSpec<TYPE, TypeSpec<'o'> >(value, TypeSpec<'o'>()); \ -} \ - \ -inline IntFormatSpec<TYPE, TypeSpec<'x'> > hex(TYPE value) { \ - return IntFormatSpec<TYPE, TypeSpec<'x'> >(value, TypeSpec<'x'>()); \ -} \ - \ -inline IntFormatSpec<TYPE, TypeSpec<'X'> > hexu(TYPE value) { \ - return IntFormatSpec<TYPE, TypeSpec<'X'> >(value, TypeSpec<'X'>()); \ -} \ - \ -template <char TYPE_CODE> \ -inline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> > pad( \ - IntFormatSpec<TYPE, TypeSpec<TYPE_CODE> > f, unsigned width) { \ - return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE> >( \ - f.value(), AlignTypeSpec<TYPE_CODE>(width, ' ')); \ -} \ - \ -/* For compatibility with older compilers we provide two overloads for pad, */ \ -/* one that takes a fill character and one that doesn't. In the future this */ \ -/* can be replaced with one overload making the template argument Char */ \ -/* default to char (C++11). */ \ -template <char TYPE_CODE, typename Char> \ -inline IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char> pad( \ - IntFormatSpec<TYPE, TypeSpec<TYPE_CODE>, Char> f, \ - unsigned width, Char fill) { \ - return IntFormatSpec<TYPE, AlignTypeSpec<TYPE_CODE>, Char>( \ - f.value(), AlignTypeSpec<TYPE_CODE>(width, fill)); \ -} \ - \ -inline IntFormatSpec<TYPE, AlignTypeSpec<0> > pad( \ - TYPE value, unsigned width) { \ - return IntFormatSpec<TYPE, AlignTypeSpec<0> >( \ - value, AlignTypeSpec<0>(width, ' ')); \ -} \ - \ -template <typename Char> \ -inline IntFormatSpec<TYPE, AlignTypeSpec<0>, Char> pad( \ - TYPE value, unsigned width, Char fill) { \ - return IntFormatSpec<TYPE, AlignTypeSpec<0>, Char>( \ - value, AlignTypeSpec<0>(width, fill)); \ +template <typename Iterator, typename IDHandler> +FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { + typedef typename std::iterator_traits<Iterator>::value_type char_type; + char_type c = *it; + if (c == '}' || c == ':') { + handler(); + return it; + } + if (c >= '0' && c <= '9') { + unsigned index = parse_nonnegative_int(it, handler); + if (*it != '}' && *it != ':') { + handler.on_error("invalid format string"); + return it; + } + handler(index); + return it; + } + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return it; + } + auto start = it; + do { + c = *++it; + } while (is_name_start(c) || ('0' <= c && c <= '9')); + handler(basic_string_view<char_type>( + pointer_from(start), to_unsigned(it - start))); + return it; } -FMT_DEFINE_INT_FORMATTERS(int) -FMT_DEFINE_INT_FORMATTERS(long) -FMT_DEFINE_INT_FORMATTERS(unsigned) -FMT_DEFINE_INT_FORMATTERS(unsigned long) -FMT_DEFINE_INT_FORMATTERS(LongLong) -FMT_DEFINE_INT_FORMATTERS(ULongLong) +template <typename Char, typename IDHandler> +FMT_CONSTEXPR const Char *parse_arg_id( + const Char *begin, const Char *end, IDHandler &&handler) { + assert(begin != end); + Char c = *begin; + if (c == '}' || c == ':') + return handler(), begin; + if (c >= '0' && c <= '9') { + unsigned index = parse_nonnegative_int(begin, end, handler); + if (begin == end || (*begin != '}' && *begin != ':')) + return handler.on_error("invalid format string"), begin; + handler(index); + return begin; + } + if (!is_name_start(c)) + return handler.on_error("invalid format string"), begin; + auto it = begin; + do { + c = *++it; + } while (it != end && (is_name_start(c) || ('0' <= c && c <= '9'))); + handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); + return it; +} -/** - \rst - Returns a string formatter that pads the formatted argument with the fill - character to the specified width using the default (left) string alignment. +// Adapts SpecHandler to IDHandler API for dynamic width. +template <typename SpecHandler, typename Char> +struct width_adapter { + explicit FMT_CONSTEXPR width_adapter(SpecHandler &h) : handler(h) {} - **Example**:: + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(unsigned id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_width(id); + } - std::string s = str(MemoryWriter() << pad("abc", 8)); - // s == "abc " + FMT_CONSTEXPR void on_error(const char *message) { + handler.on_error(message); + } - \endrst - */ -template <typename Char> -inline StrFormatSpec<Char> pad( - const Char *str, unsigned width, Char fill = ' ') { - return StrFormatSpec<Char>(str, width, fill); -} + SpecHandler &handler; +}; -inline StrFormatSpec<wchar_t> pad( - const wchar_t *str, unsigned width, char fill = ' ') { - return StrFormatSpec<wchar_t>(str, width, fill); -} +// Adapts SpecHandler to IDHandler API for dynamic precision. +template <typename SpecHandler, typename Char> +struct precision_adapter { + explicit FMT_CONSTEXPR precision_adapter(SpecHandler &h) : handler(h) {} -namespace internal { - -template <typename Char> -class ArgMap { - private: - typedef std::vector< - std::pair<fmt::BasicStringRef<Char>, internal::Arg> > MapType; - typedef typename MapType::value_type Pair; + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(unsigned id) { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_precision(id); + } - MapType map_; + FMT_CONSTEXPR void on_error(const char *message) { handler.on_error(message); } - public: - void init(const ArgList &args); - - const internal::Arg *find(const fmt::BasicStringRef<Char> &name) const { - // The list is unsorted, so just return the first matching name. - for (typename MapType::const_iterator it = map_.begin(), end = map_.end(); - it != end; ++it) { - if (it->first == name) - return &it->second; - } - return FMT_NULL; - } + SpecHandler &handler; }; -template <typename Char> -void ArgMap<Char>::init(const ArgList &args) { - if (!map_.empty()) - return; - typedef internal::NamedArg<Char> NamedArg; - const NamedArg *named_arg = FMT_NULL; - bool use_values = - args.type(ArgList::MAX_PACKED_ARGS - 1) == internal::Arg::NONE; - if (use_values) { - for (unsigned i = 0;/*nothing*/; ++i) { - internal::Arg::Type arg_type = args.type(i); - switch (arg_type) { - case internal::Arg::NONE: - return; - case internal::Arg::NAMED_ARG: - named_arg = static_cast<const NamedArg*>(args.values_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +// it: an iterator pointing to the beginning of a null-terminated range of +// characters, possibly emulated via null_terminating_iterator, representing +// format specifiers. +template <typename Iterator, typename SpecHandler> +FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { + typedef typename std::iterator_traits<Iterator>::value_type char_type; + char_type c = *it; + if (c == '}' || !c) + return it; + + // Parse fill and alignment. + alignment align = ALIGN_DEFAULT; + int i = 1; + do { + auto p = it + i; + switch (*p) { + case '<': + align = ALIGN_LEFT; + break; + case '>': + align = ALIGN_RIGHT; + break; + case '=': + align = ALIGN_NUMERIC; + break; + case '^': + align = ALIGN_CENTER; break; - default: - /*nothing*/; - } - } - return; - } - for (unsigned i = 0; i != ArgList::MAX_PACKED_ARGS; ++i) { - internal::Arg::Type arg_type = args.type(i); - if (arg_type == internal::Arg::NAMED_ARG) { - named_arg = static_cast<const NamedArg*>(args.args_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); } - } - for (unsigned i = ArgList::MAX_PACKED_ARGS;/*nothing*/; ++i) { - switch (args.args_[i].type) { - case internal::Arg::NONE: - return; - case internal::Arg::NAMED_ARG: - named_arg = static_cast<const NamedArg*>(args.args_[i].pointer); - map_.push_back(Pair(named_arg->name, *named_arg)); + if (align != ALIGN_DEFAULT) { + if (p != it) { + if (c == '{') { + handler.on_error("invalid fill character '{'"); + return it; + } + it += 2; + handler.on_fill(c); + } else ++it; + handler.on_align(align); break; - default: - /*nothing*/; } - } -} - -template <typename Impl, typename Char, typename Spec = fmt::FormatSpec> -class ArgFormatterBase : public ArgVisitor<Impl, void> { - private: - BasicWriter<Char> &writer_; - Spec &spec_; + } while (--i >= 0); - FMT_DISALLOW_COPY_AND_ASSIGN(ArgFormatterBase); - - void write_pointer(const void *p) { - spec_.flags_ = HASH_FLAG; - spec_.type_ = 'x'; - writer_.write_int(reinterpret_cast<uintptr_t>(p), spec_); + // Parse sign. + switch (*it) { + case '+': + handler.on_plus(); + ++it; + break; + case '-': + handler.on_minus(); + ++it; + break; + case ' ': + handler.on_space(); + ++it; + break; } - // workaround MSVC two-phase lookup issue - typedef internal::Arg Arg; - - protected: - BasicWriter<Char> &writer() { return writer_; } - Spec &spec() { return spec_; } - - void write(bool value) { - const char *str_value = value ? "true" : "false"; - Arg::StringValue<char> str = { str_value, std::strlen(str_value) }; - writer_.write_str(str, spec_); + if (*it == '#') { + handler.on_hash(); + ++it; } - void write(const char *value) { - Arg::StringValue<char> str = {value, value ? std::strlen(value) : 0}; - writer_.write_str(str, spec_); + // Parse zero flag. + if (*it == '0') { + handler.on_zero(); + ++it; } - public: - typedef Spec SpecType; - - ArgFormatterBase(BasicWriter<Char> &w, Spec &s) - : writer_(w), spec_(s) {} - - template <typename T> - void visit_any_int(T value) { writer_.write_int(value, spec_); } - - template <typename T> - void visit_any_double(T value) { writer_.write_double(value, spec_); } - - void visit_bool(bool value) { - if (spec_.type_) { - visit_any_int(value); - return; + // Parse width. + if ('0' <= *it && *it <= '9') { + handler.on_width(parse_nonnegative_int(it, handler)); + } else if (*it == '{') { + it = parse_arg_id(it + 1, width_adapter<SpecHandler, char_type>(handler)); + if (*it++ != '}') { + handler.on_error("invalid format string"); + return it; } - write(value); } - void visit_char(int value) { - if (spec_.type_ && spec_.type_ != 'c') { - spec_.flags_ |= CHAR_FLAG; - writer_.write_int(value, spec_); - return; - } - if (spec_.align_ == ALIGN_NUMERIC || spec_.flags_ != 0) - FMT_THROW(FormatError("invalid format specifier for char")); - typedef typename BasicWriter<Char>::CharPtr CharPtr; - Char fill = internal::CharTraits<Char>::cast(spec_.fill()); - CharPtr out = CharPtr(); - const unsigned CHAR_SIZE = 1; - if (spec_.width_ > CHAR_SIZE) { - out = writer_.grow_buffer(spec_.width_); - if (spec_.align_ == ALIGN_RIGHT) { - std::uninitialized_fill_n(out, spec_.width_ - CHAR_SIZE, fill); - out += spec_.width_ - CHAR_SIZE; - } else if (spec_.align_ == ALIGN_CENTER) { - out = writer_.fill_padding(out, spec_.width_, - internal::const_check(CHAR_SIZE), fill); - } else { - std::uninitialized_fill_n(out + CHAR_SIZE, - spec_.width_ - CHAR_SIZE, fill); + // Parse precision. + if (*it == '.') { + ++it; + if ('0' <= *it && *it <= '9') { + handler.on_precision(parse_nonnegative_int(it, handler)); + } else if (*it == '{') { + it = parse_arg_id( + it + 1, precision_adapter<SpecHandler, char_type>(handler)); + if (*it++ != '}') { + handler.on_error("invalid format string"); + return it; } } else { - out = writer_.grow_buffer(CHAR_SIZE); + handler.on_error("missing precision specifier"); + return it; } - *out = internal::CharTraits<Char>::cast(value); + handler.end_precision(); } - void visit_cstring(const char *value) { - if (spec_.type_ == 'p') - return write_pointer(value); - write(value); - } + // Parse type. + if (*it != '}' && *it) + handler.on_type(*it++); + return it; +} - // Qualification with "internal" here and below is a workaround for nvcc. - void visit_string(internal::Arg::StringValue<char> value) { - writer_.write_str(value, spec_); +// Return the result via the out param to workaround gcc bug 77539. +template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*> +FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr &out) { + for (out = first; out != last; ++out) { + if (*out == value) + return true; } + return false; +} - using ArgVisitor<Impl, void>::visit_wstring; +template <> +inline bool find<false, char>( + const char *first, const char *last, char value, const char *&out) { + out = static_cast<const char*>(std::memchr(first, value, last - first)); + return out != FMT_NULL; +} - void visit_wstring(internal::Arg::StringValue<Char> value) { - writer_.write_str(value, spec_); +template <typename Handler, typename Char> +struct id_adapter { + FMT_CONSTEXPR void operator()() { handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(unsigned id) { handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_arg_id(id); } - - void visit_pointer(const void *value) { - if (spec_.type_ && spec_.type_ != 'p') - report_unknown_type(spec_.type_, "pointer"); - write_pointer(value); + FMT_CONSTEXPR void on_error(const char *message) { + handler.on_error(message); } + Handler &handler; }; -class FormatterBase { - private: - ArgList args_; - int next_arg_index_; - - // Returns the argument with specified index. - FMT_API Arg do_get_arg(unsigned arg_index, const char *&error); +template <bool IS_CONSTEXPR, typename Char, typename Handler> +FMT_CONSTEXPR void parse_format_string( + basic_string_view<Char> format_str, Handler &&handler) { + struct writer { + FMT_CONSTEXPR void operator()(const Char *begin, const Char *end) { + if (begin == end) return; + for (;;) { + const Char *p = FMT_NULL; + if (!find<IS_CONSTEXPR>(begin, end, '}', p)) + return handler_.on_text(begin, end); + ++p; + if (p == end || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(begin, p); + begin = p + 1; + } + } + Handler &handler_; + } write{handler}; + auto begin = format_str.data(), end = begin + format_str.size(); + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char *p = begin; + if (*begin != '{' && !find<IS_CONSTEXPR>(begin, end, '{', p)) + return write(begin, end); + write(begin, p); + ++p; + if (p == end) + return handler.on_error("invalid format string"); + if (*p == '}') { + handler.on_arg_id(); + handler.on_replacement_field(p); + } else if (*p == '{') { + handler.on_text(p, p + 1); + } else { + p = parse_arg_id(p, end, id_adapter<Handler, Char>{handler}); + Char c = p != end ? *p : 0; + if (c == '}') { + handler.on_replacement_field(p); + } else if (c == ':') { + internal::null_terminating_iterator<Char> it(p + 1, end); + it = handler.on_format_specs(it); + if (*it != '}') + return handler.on_error("unknown format specifier"); + p = pointer_from(it); + } else { + return handler.on_error("missing '}' in format string"); + } + } + begin = p + 1; + } +} - protected: - const ArgList &args() const { return args_; } +template <typename T, typename ParseContext> +FMT_CONSTEXPR const typename ParseContext::char_type * + parse_format_specs(ParseContext &ctx) { + // GCC 7.2 requires initializer. + formatter<T, typename ParseContext::char_type> f{}; + return f.parse(ctx); +} - explicit FormatterBase(const ArgList &args) { - args_ = args; - next_arg_index_ = 0; - } +template <typename Char, typename ErrorHandler, typename... Args> +class format_string_checker { + public: + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view<Char> format_str, ErrorHandler eh) + : arg_id_(-1), context_(format_str, eh), + parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} - // Returns the next argument. - Arg next_arg(const char *&error) { - if (next_arg_index_ >= 0) - return do_get_arg(internal::to_unsigned(next_arg_index_++), error); - error = "cannot switch from manual to automatic argument indexing"; - return Arg(); - } + typedef internal::null_terminating_iterator<Char> iterator; - // Checks if manual indexing is used and returns the argument with - // specified index. - Arg get_arg(unsigned arg_index, const char *&error) { - return check_no_auto_index(error) ? do_get_arg(arg_index, error) : Arg(); - } + FMT_CONSTEXPR void on_text(const Char *, const Char *) {} - bool check_no_auto_index(const char *&error) { - if (next_arg_index_ > 0) { - error = "cannot switch from automatic to manual argument indexing"; - return false; - } - next_arg_index_ = -1; - return true; + FMT_CONSTEXPR void on_arg_id() { + arg_id_ = context_.next_arg_id(); + check_arg_id(); } - - template <typename Char> - void write(BasicWriter<Char> &w, const Char *start, const Char *end) { - if (start != end) - w << BasicStringRef<Char>(start, internal::to_unsigned(end - start)); + FMT_CONSTEXPR void on_arg_id(unsigned id) { + arg_id_ = id; + context_.check_arg_id(id); + check_arg_id(); } -}; -} // namespace internal - -/** - \rst - An argument formatter based on the `curiously recurring template pattern - <http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern>`_. - - To use `~fmt::BasicArgFormatter` define a subclass that implements some or - all of the visit methods with the same signatures as the methods in - `~fmt::ArgVisitor`, for example, `~fmt::ArgVisitor::visit_int()`. - Pass the subclass as the *Impl* template parameter. When a formatting - function processes an argument, it will dispatch to a visit method - specific to the argument type. For example, if the argument type is - ``double`` then the `~fmt::ArgVisitor::visit_double()` method of a subclass - will be called. If the subclass doesn't contain a method with this signature, - then a corresponding method of `~fmt::BasicArgFormatter` or its superclass - will be called. - \endrst - */ -template <typename Impl, typename Char, typename Spec = fmt::FormatSpec> -class BasicArgFormatter : public internal::ArgFormatterBase<Impl, Char, Spec> { - private: - BasicFormatter<Char, Impl> &formatter_; - const Char *format_; + FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) {} - public: - /** - \rst - Constructs an argument formatter object. - *formatter* is a reference to the main formatter object, *spec* contains - format specifier information for standard argument types, and *fmt* points - to the part of the format string being parsed for custom argument types. - \endrst - */ - BasicArgFormatter(BasicFormatter<Char, Impl> &formatter, - Spec &spec, const Char *fmt) - : internal::ArgFormatterBase<Impl, Char, Spec>(formatter.writer(), spec), - formatter_(formatter), format_(fmt) {} + FMT_CONSTEXPR void on_replacement_field(const Char *) {} - /** Formats an argument of a custom (user-defined) type. */ - void visit_custom(internal::Arg::CustomValue c) { - c.format(&formatter_, c.value, &format_); + FMT_CONSTEXPR const Char *on_format_specs(iterator it) { + auto p = pointer_from(it); + context_.advance_to(p); + return to_unsigned(arg_id_) < NUM_ARGS ? + parse_funcs_[arg_id_](context_) : p; } -}; -/** The default argument formatter. */ -template <typename Char> -class ArgFormatter : - public BasicArgFormatter<ArgFormatter<Char>, Char, FormatSpec> { - public: - /** Constructs an argument formatter object. */ - ArgFormatter(BasicFormatter<Char> &formatter, - FormatSpec &spec, const Char *fmt) - : BasicArgFormatter<ArgFormatter<Char>, - Char, FormatSpec>(formatter, spec, fmt) {} -}; - -/** This template formats data and writes the output to a writer. */ -template <typename CharType, typename ArgFormatter> -class BasicFormatter : private internal::FormatterBase { - public: - /** The character type for the output. */ - typedef CharType Char; + FMT_CONSTEXPR void on_error(const char *message) { + context_.on_error(message); + } private: - BasicWriter<Char> &writer_; - internal::ArgMap<Char> map_; - - FMT_DISALLOW_COPY_AND_ASSIGN(BasicFormatter); - - using internal::FormatterBase::get_arg; - - // Checks if manual indexing is used and returns the argument with - // specified name. - internal::Arg get_arg(BasicStringRef<Char> arg_name, const char *&error); - - // Parses argument index and returns corresponding argument. - internal::Arg parse_arg_index(const Char *&s); - - // Parses argument name and returns corresponding argument. - internal::Arg parse_arg_name(const Char *&s); - - public: - /** - \rst - Constructs a ``BasicFormatter`` object. References to the arguments and - the writer are stored in the formatter object so make sure they have - appropriate lifetimes. - \endrst - */ - BasicFormatter(const ArgList &args, BasicWriter<Char> &w) - : internal::FormatterBase(args), writer_(w) {} + typedef basic_parse_context<Char, ErrorHandler> parse_context_type; + enum { NUM_ARGS = sizeof...(Args) }; - /** Returns a reference to the writer associated with this formatter. */ - BasicWriter<Char> &writer() { return writer_; } + FMT_CONSTEXPR void check_arg_id() { + if (internal::to_unsigned(arg_id_) >= NUM_ARGS) + context_.on_error("argument index out of range"); + } - /** Formats stored arguments and writes the output to the writer. */ - void format(BasicCStringRef<Char> format_str); + // Format specifier parsing function. + typedef const Char *(*parse_func)(parse_context_type &); - // Formats a single argument and advances format_str, a format string pointer. - const Char *format(const Char *&format_str, const internal::Arg &arg); + int arg_id_; + parse_context_type context_; + parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; }; -// Generates a comma-separated list with results of applying f to -// numbers 0..n-1. -# define FMT_GEN(n, f) FMT_GEN##n(f) -# define FMT_GEN1(f) f(0) -# define FMT_GEN2(f) FMT_GEN1(f), f(1) -# define FMT_GEN3(f) FMT_GEN2(f), f(2) -# define FMT_GEN4(f) FMT_GEN3(f), f(3) -# define FMT_GEN5(f) FMT_GEN4(f), f(4) -# define FMT_GEN6(f) FMT_GEN5(f), f(5) -# define FMT_GEN7(f) FMT_GEN6(f), f(6) -# define FMT_GEN8(f) FMT_GEN7(f), f(7) -# define FMT_GEN9(f) FMT_GEN8(f), f(8) -# define FMT_GEN10(f) FMT_GEN9(f), f(9) -# define FMT_GEN11(f) FMT_GEN10(f), f(10) -# define FMT_GEN12(f) FMT_GEN11(f), f(11) -# define FMT_GEN13(f) FMT_GEN12(f), f(12) -# define FMT_GEN14(f) FMT_GEN13(f), f(13) -# define FMT_GEN15(f) FMT_GEN14(f), f(14) - -namespace internal { -inline uint64_t make_type() { return 0; } - -template <typename T> -inline uint64_t make_type(const T &arg) { - return MakeValue< BasicFormatter<char> >::type(arg); +template <typename Char, typename ErrorHandler, typename... Args> +FMT_CONSTEXPR bool check_format_string( + basic_string_view<Char> s, ErrorHandler eh = ErrorHandler()) { + format_string_checker<Char, ErrorHandler, Args...> checker(s, eh); + parse_format_string<true>(s, checker); + return true; } -template <std::size_t N, bool/*IsPacked*/= (N < ArgList::MAX_PACKED_ARGS)> -struct ArgArray; - -template <std::size_t N> -struct ArgArray<N, true/*IsPacked*/> { - // '+' is used to silence GCC -Wduplicated-branches warning. - typedef Value Type[N > 0 ? N : +1]; +template <typename... Args, typename String> +typename std::enable_if<is_compile_string<String>::value>::type + check_format_string(String format_str) { + FMT_CONSTEXPR_DECL bool invalid_format = + internal::check_format_string<char, internal::error_handler, Args...>( + string_view(format_str.data(), format_str.size())); + (void)invalid_format; +} - template <typename Formatter, typename T> - static Value make(const T &value) { -#ifdef __clang__ - Value result = MakeValue<Formatter>(value); - // Workaround a bug in Apple LLVM version 4.2 (clang-425.0.28) of clang: - // https://github.com/fmtlib/fmt/issues/276 - (void)result.custom.format; - return result; -#else - return MakeValue<Formatter>(value); -#endif +// Specifies whether to format T using the standard formatter. +// It is not possible to use get_type in formatter specialization directly +// because of a bug in MSVC. +template <typename Context, typename T> +struct format_type : + std::integral_constant<bool, get_type<Context, T>::value != custom_type> {}; + +template <template <typename> class Handler, typename Spec, typename Context> +void handle_dynamic_spec( + Spec &value, arg_ref<typename Context::char_type> ref, Context &ctx) { + typedef typename Context::char_type char_type; + switch (ref.kind) { + case arg_ref<char_type>::NONE: + break; + case arg_ref<char_type>::INDEX: + internal::set_dynamic_spec<Handler>( + value, ctx.get_arg(ref.index), ctx.error_handler()); + break; + case arg_ref<char_type>::NAME: + internal::set_dynamic_spec<Handler>( + value, ctx.get_arg(ref.name), ctx.error_handler()); + break; } -}; - -template <std::size_t N> -struct ArgArray<N, false/*IsPacked*/> { - typedef Arg Type[N + 1]; // +1 for the list end Arg::NONE - - template <typename Formatter, typename T> - static Arg make(const T &value) { return MakeArg<Formatter>(value); } -}; - -#if FMT_USE_VARIADIC_TEMPLATES -template <typename Arg, typename... Args> -inline uint64_t make_type(const Arg &first, const Args & ... tail) { - return make_type(first) | (make_type(tail...) << 4); } +} // namespace internal -#else +/** The default argument formatter. */ +template <typename Range> +class arg_formatter: + public internal::function< + typename internal::arg_formatter_base<Range>::iterator>, + public internal::arg_formatter_base<Range> { + private: + typedef typename Range::value_type char_type; + typedef internal::arg_formatter_base<Range> base; + typedef basic_format_context<typename base::iterator, char_type> context_type; -struct ArgType { - uint64_t type; + context_type &ctx_; - ArgType() : type(0) {} + public: + typedef Range range; + typedef typename base::iterator iterator; + typedef typename base::format_specs format_specs; - template <typename T> - ArgType(const T &arg) : type(make_type(arg)) {} -}; + /** + \rst + Constructs an argument formatter object. + *ctx* is a reference to the formatting context, + *spec* contains format specifier information for standard argument types. + \endrst + */ + explicit arg_formatter(context_type &ctx, format_specs *spec = {}) + : base(Range(ctx.out()), spec), ctx_(ctx) {} -# define FMT_ARG_TYPE_DEFAULT(n) ArgType t##n = ArgType() + // Deprecated. + arg_formatter(context_type &ctx, format_specs &spec) + : base(Range(ctx.out()), &spec), ctx_(ctx) {} -inline uint64_t make_type(FMT_GEN15(FMT_ARG_TYPE_DEFAULT)) { - return t0.type | (t1.type << 4) | (t2.type << 8) | (t3.type << 12) | - (t4.type << 16) | (t5.type << 20) | (t6.type << 24) | (t7.type << 28) | - (t8.type << 32) | (t9.type << 36) | (t10.type << 40) | (t11.type << 44) | - (t12.type << 48) | (t13.type << 52) | (t14.type << 56); -} -#endif -} // namespace internal + using base::operator(); -# define FMT_MAKE_TEMPLATE_ARG(n) typename T##n -# define FMT_MAKE_ARG_TYPE(n) T##n -# define FMT_MAKE_ARG(n) const T##n &v##n -# define FMT_ASSIGN_char(n) \ - arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<char> >(v##n) -# define FMT_ASSIGN_wchar_t(n) \ - arr[n] = fmt::internal::MakeValue< fmt::BasicFormatter<wchar_t> >(v##n) - -#if FMT_USE_VARIADIC_TEMPLATES -// Defines a variadic function returning void. -# define FMT_VARIADIC_VOID(func, arg_type) \ - template <typename... Args> \ - void func(arg_type arg0, const Args & ... args) { \ - typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \ - typename ArgArray::Type array{ \ - ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \ - func(arg0, fmt::ArgList(fmt::internal::make_type(args...), array)); \ - } - -// Defines a variadic constructor. -# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ - template <typename... Args> \ - ctor(arg0_type arg0, arg1_type arg1, const Args & ... args) { \ - typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \ - typename ArgArray::Type array{ \ - ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \ - func(arg0, arg1, fmt::ArgList(fmt::internal::make_type(args...), array)); \ + /** Formats an argument of a user-defined type. */ + iterator operator()(typename basic_format_arg<context_type>::handle handle) { + handle.format(ctx_); + return this->out(); } - -#else - -# define FMT_MAKE_REF(n) \ - fmt::internal::MakeValue< fmt::BasicFormatter<Char> >(v##n) -# define FMT_MAKE_REF2(n) v##n - -// Defines a wrapper for a function taking one argument of type arg_type -// and n additional arguments of arbitrary types. -# define FMT_WRAP1(func, arg_type, n) \ - template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \ - inline void func(arg_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ - const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ - func(arg1, fmt::ArgList( \ - fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ - } - -// Emulates a variadic function returning void on a pre-C++11 compiler. -# define FMT_VARIADIC_VOID(func, arg_type) \ - inline void func(arg_type arg) { func(arg, fmt::ArgList()); } \ - FMT_WRAP1(func, arg_type, 1) FMT_WRAP1(func, arg_type, 2) \ - FMT_WRAP1(func, arg_type, 3) FMT_WRAP1(func, arg_type, 4) \ - FMT_WRAP1(func, arg_type, 5) FMT_WRAP1(func, arg_type, 6) \ - FMT_WRAP1(func, arg_type, 7) FMT_WRAP1(func, arg_type, 8) \ - FMT_WRAP1(func, arg_type, 9) FMT_WRAP1(func, arg_type, 10) - -# define FMT_CTOR(ctor, func, arg0_type, arg1_type, n) \ - template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \ - ctor(arg0_type arg0, arg1_type arg1, FMT_GEN(n, FMT_MAKE_ARG)) { \ - const fmt::internal::ArgArray<n>::Type array = {FMT_GEN(n, FMT_MAKE_REF)}; \ - func(arg0, arg1, fmt::ArgList( \ - fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), array)); \ - } - -// Emulates a variadic constructor on a pre-C++11 compiler. -# define FMT_VARIADIC_CTOR(ctor, func, arg0_type, arg1_type) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 1) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 2) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 3) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 4) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 5) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 6) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 7) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 8) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 9) \ - FMT_CTOR(ctor, func, arg0_type, arg1_type, 10) -#endif - -// Generates a comma-separated list with results of applying f to pairs -// (argument, index). -#define FMT_FOR_EACH1(f, x0) f(x0, 0) -#define FMT_FOR_EACH2(f, x0, x1) \ - FMT_FOR_EACH1(f, x0), f(x1, 1) -#define FMT_FOR_EACH3(f, x0, x1, x2) \ - FMT_FOR_EACH2(f, x0 ,x1), f(x2, 2) -#define FMT_FOR_EACH4(f, x0, x1, x2, x3) \ - FMT_FOR_EACH3(f, x0, x1, x2), f(x3, 3) -#define FMT_FOR_EACH5(f, x0, x1, x2, x3, x4) \ - FMT_FOR_EACH4(f, x0, x1, x2, x3), f(x4, 4) -#define FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5) \ - FMT_FOR_EACH5(f, x0, x1, x2, x3, x4), f(x5, 5) -#define FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6) \ - FMT_FOR_EACH6(f, x0, x1, x2, x3, x4, x5), f(x6, 6) -#define FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7) \ - FMT_FOR_EACH7(f, x0, x1, x2, x3, x4, x5, x6), f(x7, 7) -#define FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8) \ - FMT_FOR_EACH8(f, x0, x1, x2, x3, x4, x5, x6, x7), f(x8, 8) -#define FMT_FOR_EACH10(f, x0, x1, x2, x3, x4, x5, x6, x7, x8, x9) \ - FMT_FOR_EACH9(f, x0, x1, x2, x3, x4, x5, x6, x7, x8), f(x9, 9) +}; /** An error returned by an operating system or a language runtime, for example a file opening error. */ -class SystemError : public internal::RuntimeError { +class system_error : public std::runtime_error { private: - FMT_API void init(int err_code, CStringRef format_str, ArgList args); + FMT_API void init(int err_code, string_view format_str, format_args args); protected: int error_code_; - typedef char Char; // For FMT_VARIADIC_CTOR. - - SystemError() {} + system_error() : std::runtime_error("") {} public: /** \rst - Constructs a :class:`fmt::SystemError` object with a description + Constructs a :class:`fmt::system_error` object with a description formatted with `fmt::format_system_error`. *message* and additional arguments passed into the constructor are formatted similarly to `fmt::format`. **Example**:: - // This throws a SystemError with the description + // This throws a system_error with the description // cannot open file 'madeup': No such file or directory // or similar (system message may vary). const char *filename = "madeup"; std::FILE *file = std::fopen(filename, "r"); if (!file) - throw fmt::SystemError(errno, "cannot open file '{}'", filename); + throw fmt::system_error(errno, "cannot open file '{}'", filename); \endrst */ - SystemError(int error_code, CStringRef message) { - init(error_code, message, ArgList()); + template <typename... Args> + system_error(int error_code, string_view message, const Args & ... args) + : std::runtime_error("") { + init(error_code, message, make_format_args(args...)); } - FMT_DEFAULTED_COPY_CTOR(SystemError) - FMT_VARIADIC_CTOR(SystemError, init, int, CStringRef) - - FMT_API ~SystemError() FMT_DTOR_NOEXCEPT FMT_OVERRIDE; int error_code() const { return error_code_; } }; @@ -2582,807 +2399,591 @@ class SystemError : public internal::RuntimeError { may look like "Unknown error -1" and is platform-dependent. \endrst */ -FMT_API void format_system_error(fmt::Writer &out, int error_code, - fmt::StringRef message) FMT_NOEXCEPT; +FMT_API void format_system_error(internal::buffer &out, int error_code, + fmt::string_view message) FMT_NOEXCEPT; /** - \rst - This template provides operations for formatting and writing data into - a character stream. The output is stored in a buffer provided by a subclass - such as :class:`fmt::BasicMemoryWriter`. - - You can use one of the following typedefs for common character types: - - +---------+----------------------+ - | Type | Definition | - +=========+======================+ - | Writer | BasicWriter<char> | - +---------+----------------------+ - | WWriter | BasicWriter<wchar_t> | - +---------+----------------------+ - - \endrst + This template provides operations for formatting and writing data into a + character range. */ -template <typename Char> -class BasicWriter { - private: - // Output buffer. - Buffer<Char> &buffer_; - - FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter); - - typedef typename internal::CharTraits<Char>::CharPtr CharPtr; +template <typename Range> +class basic_writer { + public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval<Range>().begin()) iterator; + typedef basic_format_specs<char_type> format_specs; -#if FMT_SECURE_SCL - // Returns pointer value. - static Char *get(CharPtr p) { return p.base(); } -#else - static Char *get(Char *p) { return p; } -#endif + private: + iterator out_; // Output iterator. + std::unique_ptr<locale_provider> locale_; + + iterator out() const { return out_; } + + // Attempts to reserve space for n extra characters in the output range. + // Returns a pointer to the reserved range or a reference to out_. + auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { + return internal::reserve(out_, n); + } + + // Writes a value in the format + // <left-padding><value><right-padding> + // where <value> is written by f(it). + template <typename F> + void write_padded(std::size_t size, const align_spec &spec, F &&f); + + template <typename F> + struct padded_int_writer { + string_view prefix; + char_type fill; + std::size_t padding; + F f; + + template <typename It> + void operator()(It &&it) const { + if (prefix.size() != 0) + it = std::copy_n(prefix.data(), prefix.size(), it); + it = std::fill_n(it, padding, fill); + f(it); + } + }; - // Fills the padding around the content and returns the pointer to the - // content area. - static CharPtr fill_padding(CharPtr buffer, - unsigned total_size, std::size_t content_size, wchar_t fill); - - // Grows the buffer by n characters and returns a pointer to the newly - // allocated area. - CharPtr grow_buffer(std::size_t n) { - std::size_t size = buffer_.size(); - buffer_.resize(size + n); - return internal::make_ptr(&buffer_[size], n); - } - - // Writes an unsigned decimal integer. - template <typename UInt> - Char *write_unsigned_decimal(UInt value, unsigned prefix_size = 0) { - unsigned num_digits = internal::count_digits(value); - Char *ptr = get(grow_buffer(prefix_size + num_digits)); - internal::format_decimal(ptr + prefix_size, value, num_digits); - return ptr; + // Writes an integer in the format + // <left-padding><prefix><numeric-padding><digits><right-padding> + // where <digits> are written by f(it). + template <typename Spec, typename F> + void write_int(unsigned num_digits, string_view prefix, + const Spec &spec, F f) { + std::size_t size = prefix.size() + num_digits; + char_type fill = static_cast<char_type>(spec.fill()); + std::size_t padding = 0; + if (spec.align() == ALIGN_NUMERIC) { + if (spec.width() > size) { + padding = spec.width() - size; + size = spec.width(); + } + } else if (spec.precision() > static_cast<int>(num_digits)) { + size = prefix.size() + static_cast<std::size_t>(spec.precision()); + padding = static_cast<std::size_t>(spec.precision()) - num_digits; + fill = '0'; + } + align_spec as = spec; + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + write_padded(size, as, padded_int_writer<F>{prefix, fill, padding, f}); } // Writes a decimal integer. template <typename Int> void write_decimal(Int value) { - typedef typename internal::IntTraits<Int>::MainType MainType; - MainType abs_value = static_cast<MainType>(value); - if (internal::is_negative(value)) { + typedef typename internal::int_traits<Int>::main_type main_type; + main_type abs_value = static_cast<main_type>(value); + bool is_negative = internal::is_negative(value); + if (is_negative) abs_value = 0 - abs_value; - *write_unsigned_decimal(abs_value, 1) = '-'; - } else { - write_unsigned_decimal(abs_value, 0); + unsigned num_digits = internal::count_digits(abs_value); + auto &&it = reserve((is_negative ? 1 : 0) + num_digits); + if (is_negative) + *it++ = '-'; + it = internal::format_decimal(it, abs_value, num_digits); + } + + // The handle_int_type_spec handler that writes an integer. + template <typename Int, typename Spec> + struct int_writer { + typedef typename internal::int_traits<Int>::main_type unsigned_type; + + basic_writer<Range> &writer; + const Spec &spec; + unsigned_type abs_value; + char prefix[4]; + unsigned prefix_size; + + string_view get_prefix() const { return string_view(prefix, prefix_size); } + + // Counts the number of digits in abs_value. BITS = log2(radix). + template <unsigned BITS> + unsigned count_digits() const { + unsigned_type n = abs_value; + unsigned num_digits = 0; + do { + ++num_digits; + } while ((n >>= BITS) != 0); + return num_digits; } - } - // Prepare a buffer for integer formatting. - CharPtr prepare_int_buffer(unsigned num_digits, - const EmptySpec &, const char *prefix, unsigned prefix_size) { - unsigned size = prefix_size + num_digits; - CharPtr p = grow_buffer(size); - std::uninitialized_copy(prefix, prefix + prefix_size, p); - return p + size - 1; - } + int_writer(basic_writer<Range> &w, Int value, const Spec &s) + : writer(w), spec(s), abs_value(static_cast<unsigned_type>(value)), + prefix_size(0) { + if (internal::is_negative(value)) { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } else if (spec.flag(SIGN_FLAG)) { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + } - template <typename Spec> - CharPtr prepare_int_buffer(unsigned num_digits, - const Spec &spec, const char *prefix, unsigned prefix_size); + struct dec_writer { + unsigned_type abs_value; + unsigned num_digits; - // Formats an integer. - template <typename T, typename Spec> - void write_int(T value, Spec spec); + template <typename It> + void operator()(It &&it) const { + it = internal::format_decimal(it, abs_value, num_digits); + } + }; - // Formats a floating-point number (double or long double). - template <typename T, typename Spec> - void write_double(T value, const Spec &spec); + void on_dec() { + unsigned num_digits = internal::count_digits(abs_value); + writer.write_int(num_digits, get_prefix(), spec, + dec_writer{abs_value, num_digits}); + } - // Writes a formatted string. - template <typename StrChar> - CharPtr write_str(const StrChar *s, std::size_t size, const AlignSpec &spec); + struct hex_writer { + int_writer &self; + unsigned num_digits; - template <typename StrChar, typename Spec> - void write_str(const internal::Arg::StringValue<StrChar> &str, - const Spec &spec); + template <typename It> + void operator()(It &&it) const { + it = internal::format_uint<4>(it, self.abs_value, num_digits, + self.spec.type() != 'x'); + } + }; - // This following methods are private to disallow writing wide characters - // and strings to a char stream. If you want to print a wide string as a - // pointer as std::ostream does, cast it to const void*. - // Do not implement! - void operator<<(typename internal::WCharHelper<wchar_t, Char>::Unsupported); - void operator<<( - typename internal::WCharHelper<const wchar_t *, Char>::Unsupported); + void on_hex() { + if (spec.flag(HASH_FLAG)) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast<char>(spec.type()); + } + unsigned num_digits = count_digits<4>(); + writer.write_int(num_digits, get_prefix(), spec, + hex_writer{*this, num_digits}); + } - // Appends floating-point length specifier to the format string. - // The second argument is only used for overload resolution. - void append_float_length(Char *&format_ptr, long double) { - *format_ptr++ = 'L'; - } + template <int BITS> + struct bin_writer { + unsigned_type abs_value; + unsigned num_digits; - template<typename T> - void append_float_length(Char *&, T) {} + template <typename It> + void operator()(It &&it) const { + it = internal::format_uint<BITS>(it, abs_value, num_digits); + } + }; - template <typename Impl, typename Char_, typename Spec_> - friend class internal::ArgFormatterBase; + void on_bin() { + if (spec.flag(HASH_FLAG)) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast<char>(spec.type()); + } + unsigned num_digits = count_digits<1>(); + writer.write_int(num_digits, get_prefix(), spec, + bin_writer<1>{abs_value, num_digits}); + } - template <typename Impl, typename Char_, typename Spec_> - friend class BasicPrintfArgFormatter; + void on_oct() { + unsigned num_digits = count_digits<3>(); + if (spec.flag(HASH_FLAG) && + spec.precision() <= static_cast<int>(num_digits)) { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix[prefix_size++] = '0'; + } + writer.write_int(num_digits, get_prefix(), spec, + bin_writer<3>{abs_value, num_digits}); + } - protected: - /** - Constructs a ``BasicWriter`` object. - */ - explicit BasicWriter(Buffer<Char> &b) : buffer_(b) {} + enum { SEP_SIZE = 1 }; - public: - /** - \rst - Destroys a ``BasicWriter`` object. - \endrst - */ - virtual ~BasicWriter() {} + struct num_writer { + unsigned_type abs_value; + unsigned size; + char_type sep; - /** - Returns the total number of characters written. - */ - std::size_t size() const { return buffer_.size(); } + template <typename It> + void operator()(It &&it) const { + basic_string_view<char_type> s(&sep, SEP_SIZE); + it = format_decimal(it, abs_value, size, + internal::add_thousands_sep<char_type>(s)); + } + }; - /** - Returns a pointer to the output buffer content. No terminating null - character is appended. - */ - const Char *data() const FMT_NOEXCEPT { return &buffer_[0]; } + void on_num() { + unsigned num_digits = internal::count_digits(abs_value); + char_type sep = internal::thousands_sep<char_type>(writer.locale_.get()); + unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); + writer.write_int(size, get_prefix(), spec, + num_writer{abs_value, size, sep}); + } - /** - Returns a pointer to the output buffer content with terminating null - character appended. - */ - const Char *c_str() const { - std::size_t size = buffer_.size(); - buffer_.reserve(size + 1); - buffer_[size] = '\0'; - return &buffer_[0]; - } + void on_error() { + FMT_THROW(format_error("invalid type specifier")); + } + }; - /** - \rst - Returns the content of the output buffer as an `std::string`. - \endrst - */ - std::basic_string<Char> str() const { - return std::basic_string<Char>(&buffer_[0], buffer_.size()); + // Writes a formatted integer. + template <typename T, typename Spec> + void write_int(T value, const Spec &spec) { + internal::handle_int_type_spec(spec.type(), + int_writer<T, Spec>(*this, value, spec)); } - /** - \rst - Writes formatted data. + enum {INF_SIZE = 3}; // This is an enum to workaround a bug in MSVC. - *args* is an argument list representing arbitrary arguments. + struct inf_or_nan_writer { + char sign; + const char *str; - **Example**:: + template <typename It> + void operator()(It &&it) const { + if (sign) + *it++ = sign; + it = std::copy_n(str, static_cast<std::size_t>(INF_SIZE), it); + } + }; - MemoryWriter out; - out.write("Current point:\n"); - out.write("({:+f}, {:+f})", -3.14, 3.14); + struct double_writer { + size_t n; + char sign; + basic_memory_buffer<char_type> &buffer; - This will write the following output to the ``out`` object: + template <typename It> + void operator()(It &&it) { + if (sign) { + *it++ = sign; + --n; + } + it = std::copy_n(buffer.begin(), n, it); + } + }; - .. code-block:: none + // Formats a floating-point number (double or long double). + template <typename T> + void write_double(T value, const format_specs &spec); + template <typename T> + void write_double_sprintf(T value, const format_specs &spec, + internal::basic_buffer<char_type>& buffer); - Current point: - (-3.140000, +3.140000) + template <typename Char> + struct str_writer { + const Char *s; + std::size_t size; - The output can be accessed using :func:`data()`, :func:`c_str` or - :func:`str` methods. + template <typename It> + void operator()(It &&it) const { + it = std::copy_n(s, size, it); + } + }; - See also :ref:`syntax`. - \endrst - */ - void write(BasicCStringRef<Char> format, ArgList args) { - BasicFormatter<Char>(args, *this).format(format); + // Writes a formatted string. + template <typename Char> + void write_str(const Char *s, std::size_t size, const align_spec &spec) { + write_padded(size, spec, str_writer<Char>{s, size}); } - FMT_VARIADIC_VOID(write, BasicCStringRef<Char>) - BasicWriter &operator<<(int value) { - write_decimal(value); - return *this; - } - BasicWriter &operator<<(unsigned value) { - return *this << IntFormatSpec<unsigned>(value); - } - BasicWriter &operator<<(long value) { - write_decimal(value); - return *this; - } - BasicWriter &operator<<(unsigned long value) { - return *this << IntFormatSpec<unsigned long>(value); - } - BasicWriter &operator<<(LongLong value) { - write_decimal(value); - return *this; + template <typename Char> + void write_str(basic_string_view<Char> str, const format_specs &spec); + + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(char_type *&format_ptr, long double) { + *format_ptr++ = 'L'; } + template<typename T> + void append_float_length(char_type *&, T) {} + + template <typename Char> + friend class internal::arg_formatter_base; + + public: + /** Constructs a ``basic_writer`` object. */ + explicit basic_writer(Range out): out_(out.begin()) {} + + void write(int value) { write_decimal(value); } + void write(long value) { write_decimal(value); } + void write(long long value) { write_decimal(value); } + + void write(unsigned value) { write_decimal(value); } + void write(unsigned long value) { write_decimal(value); } + void write(unsigned long long value) { write_decimal(value); } + /** \rst - Formats *value* and writes it to the stream. + Formats *value* and writes it to the buffer. \endrst */ - BasicWriter &operator<<(ULongLong value) { - return *this << IntFormatSpec<ULongLong>(value); + template <typename T, typename FormatSpec, typename... FormatSpecs> + typename std::enable_if<std::is_integral<T>::value, void>::type + write(T value, FormatSpec spec, FormatSpecs... specs) { + format_specs s(spec, specs...); + s.align_ = ALIGN_RIGHT; + write_int(value, s); } - BasicWriter &operator<<(double value) { - write_double(value, FormatSpec()); - return *this; + void write(double value) { + write_double(value, format_specs()); } /** \rst Formats *value* using the general format for floating-point numbers - (``'g'``) and writes it to the stream. + (``'g'``) and writes it to the buffer. \endrst */ - BasicWriter &operator<<(long double value) { - write_double(value, FormatSpec()); - return *this; + void write(long double value) { + write_double(value, format_specs()); } - /** - Writes a character to the stream. - */ - BasicWriter &operator<<(char value) { - buffer_.push_back(value); - return *this; + /** Writes a character to the buffer. */ + void write(char value) { + *reserve(1) = value; } - BasicWriter &operator<<( - typename internal::WCharHelper<wchar_t, Char>::Supported value) { - buffer_.push_back(value); - return *this; + void write(wchar_t value) { + internal::require_wchar<char_type>(); + *reserve(1) = value; } /** \rst - Writes *value* to the stream. + Writes *value* to the buffer. \endrst */ - BasicWriter &operator<<(fmt::BasicStringRef<Char> value) { - const Char *str = value.data(); - buffer_.append(str, str + value.size()); - return *this; + void write(string_view value) { + auto &&it = reserve(value.size()); + it = std::copy(value.begin(), value.end(), it); } - BasicWriter &operator<<( - typename internal::WCharHelper<StringRef, Char>::Supported value) { - const char *str = value.data(); - buffer_.append(str, str + value.size()); - return *this; + void write(wstring_view value) { + internal::require_wchar<char_type>(); + auto &&it = reserve(value.size()); + it = std::copy(value.begin(), value.end(), it); } - template <typename T, typename Spec, typename FillChar> - BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec) { - internal::CharTraits<Char>::convert(FillChar()); - write_int(spec.value(), spec); - return *this; + template <typename... FormatSpecs> + void write(basic_string_view<char_type> str, FormatSpecs... specs) { + write_str(str, format_specs(specs...)); } - template <typename StrChar> - BasicWriter &operator<<(const StrFormatSpec<StrChar> &spec) { - const StrChar *s = spec.str(); - write_str(s, std::char_traits<Char>::length(s), spec); - return *this; + template <typename T> + typename std::enable_if<std::is_same<T, void>::value>::type + write(const T *p) { + format_specs specs; + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + write_int(reinterpret_cast<uintptr_t>(p), specs); } - - void clear() FMT_NOEXCEPT { buffer_.clear(); } - - Buffer<Char> &buffer() FMT_NOEXCEPT { return buffer_; } }; -template <typename Char> -template <typename StrChar> -typename BasicWriter<Char>::CharPtr BasicWriter<Char>::write_str( - const StrChar *s, std::size_t size, const AlignSpec &spec) { - CharPtr out = CharPtr(); - if (spec.width() > size) { - out = grow_buffer(spec.width()); - Char fill = internal::CharTraits<Char>::cast(spec.fill()); - if (spec.align() == ALIGN_RIGHT) { - std::uninitialized_fill_n(out, spec.width() - size, fill); - out += spec.width() - size; - } else if (spec.align() == ALIGN_CENTER) { - out = fill_padding(out, spec.width(), size, fill); - } else { - std::uninitialized_fill_n(out + size, spec.width() - size, fill); - } - } else { - out = grow_buffer(size); - } - std::uninitialized_copy(s, s + size, out); - return out; -} - -template <typename Char> -template <typename StrChar, typename Spec> -void BasicWriter<Char>::write_str( - const internal::Arg::StringValue<StrChar> &s, const Spec &spec) { - // Check if StrChar is convertible to Char. - internal::CharTraits<Char>::convert(StrChar()); - if (spec.type_ && spec.type_ != 's') - internal::report_unknown_type(spec.type_, "string"); - const StrChar *str_value = s.value; - std::size_t str_size = s.size; - if (str_size == 0) { - if (!str_value) { - FMT_THROW(FormatError("string pointer is null")); - } - } - std::size_t precision = static_cast<std::size_t>(spec.precision_); - if (spec.precision_ >= 0 && precision < str_size) - str_size = precision; - write_str(str_value, str_size, spec); -} - -template <typename Char> -typename BasicWriter<Char>::CharPtr - BasicWriter<Char>::fill_padding( - CharPtr buffer, unsigned total_size, - std::size_t content_size, wchar_t fill) { - std::size_t padding = total_size - content_size; - std::size_t left_padding = padding / 2; - Char fill_char = internal::CharTraits<Char>::cast(fill); - std::uninitialized_fill_n(buffer, left_padding, fill_char); - buffer += left_padding; - CharPtr content = buffer; - std::uninitialized_fill_n(buffer + content_size, - padding - left_padding, fill_char); - return content; -} - -template <typename Char> -template <typename Spec> -typename BasicWriter<Char>::CharPtr - BasicWriter<Char>::prepare_int_buffer( - unsigned num_digits, const Spec &spec, - const char *prefix, unsigned prefix_size) { +template <typename Range> +template <typename F> +void basic_writer<Range>::write_padded( + std::size_t size, const align_spec &spec, F &&f) { unsigned width = spec.width(); - Alignment align = spec.align(); - Char fill = internal::CharTraits<Char>::cast(spec.fill()); - if (spec.precision() > static_cast<int>(num_digits)) { - // Octal prefix '0' is counted as a digit, so ignore it if precision - // is specified. - if (prefix_size > 0 && prefix[prefix_size - 1] == '0') - --prefix_size; - unsigned number_size = - prefix_size + internal::to_unsigned(spec.precision()); - AlignSpec subspec(number_size, '0', ALIGN_NUMERIC); - if (number_size >= width) - return prepare_int_buffer(num_digits, subspec, prefix, prefix_size); - buffer_.reserve(width); - unsigned fill_size = width - number_size; - if (align != ALIGN_LEFT) { - CharPtr p = grow_buffer(fill_size); - std::uninitialized_fill(p, p + fill_size, fill); - } - CharPtr result = prepare_int_buffer( - num_digits, subspec, prefix, prefix_size); - if (align == ALIGN_LEFT) { - CharPtr p = grow_buffer(fill_size); - std::uninitialized_fill(p, p + fill_size, fill); - } - return result; - } - unsigned size = prefix_size + num_digits; - if (width <= size) { - CharPtr p = grow_buffer(size); - std::uninitialized_copy(prefix, prefix + prefix_size, p); - return p + size - 1; - } - CharPtr p = grow_buffer(width); - CharPtr end = p + width; - if (align == ALIGN_LEFT) { - std::uninitialized_copy(prefix, prefix + prefix_size, p); - p += size; - std::uninitialized_fill(p, end, fill); - } else if (align == ALIGN_CENTER) { - p = fill_padding(p, width, size, fill); - std::uninitialized_copy(prefix, prefix + prefix_size, p); - p += size; + if (width <= size) + return f(reserve(size)); + auto &&it = reserve(width); + char_type fill = static_cast<char_type>(spec.fill()); + std::size_t padding = width - size; + if (spec.align() == ALIGN_RIGHT) { + it = std::fill_n(it, padding, fill); + f(it); + } else if (spec.align() == ALIGN_CENTER) { + std::size_t left_padding = padding / 2; + it = std::fill_n(it, left_padding, fill); + f(it); + it = std::fill_n(it, padding - left_padding, fill); } else { - if (align == ALIGN_NUMERIC) { - if (prefix_size != 0) { - p = std::uninitialized_copy(prefix, prefix + prefix_size, p); - size -= prefix_size; - } - } else { - std::uninitialized_copy(prefix, prefix + prefix_size, end - size); - } - std::uninitialized_fill(p, end - size, fill); - p = end; + f(it); + it = std::fill_n(it, padding, fill); } - return p - 1; } +template <typename Range> template <typename Char> -template <typename T, typename Spec> -void BasicWriter<Char>::write_int(T value, Spec spec) { - unsigned prefix_size = 0; - typedef typename internal::IntTraits<T>::MainType UnsignedType; - UnsignedType abs_value = static_cast<UnsignedType>(value); - char prefix[4] = ""; - if (internal::is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (spec.flag(SIGN_FLAG)) { - prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; - ++prefix_size; - } - switch (spec.type()) { - case 0: case 'd': { - unsigned num_digits = internal::count_digits(abs_value); - CharPtr p = prepare_int_buffer(num_digits, spec, prefix, prefix_size) + 1; - internal::format_decimal(get(p), abs_value, 0); - break; - } - case 'x': case 'X': { - UnsignedType n = abs_value; - if (spec.flag(HASH_FLAG)) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = spec.type_prefix(); - } - unsigned num_digits = 0; - do { - ++num_digits; - } while ((n >>= 4) != 0); - Char *p = get(prepare_int_buffer( - num_digits, spec, prefix, prefix_size)); - n = abs_value; - const char *digits = spec.type() == 'x' ? - "0123456789abcdef" : "0123456789ABCDEF"; - do { - *p-- = digits[n & 0xf]; - } while ((n >>= 4) != 0); - break; - } - case 'b': case 'B': { - UnsignedType n = abs_value; - if (spec.flag(HASH_FLAG)) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = spec.type_prefix(); - } - unsigned num_digits = 0; - do { - ++num_digits; - } while ((n >>= 1) != 0); - Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); - n = abs_value; - do { - *p-- = static_cast<Char>('0' + (n & 1)); - } while ((n >>= 1) != 0); - break; - } - case 'o': { - UnsignedType n = abs_value; - if (spec.flag(HASH_FLAG)) - prefix[prefix_size++] = '0'; - unsigned num_digits = 0; - do { - ++num_digits; - } while ((n >>= 3) != 0); - Char *p = get(prepare_int_buffer(num_digits, spec, prefix, prefix_size)); - n = abs_value; - do { - *p-- = static_cast<Char>('0' + (n & 7)); - } while ((n >>= 3) != 0); - break; - } - case 'n': { - unsigned num_digits = internal::count_digits(abs_value); - fmt::StringRef sep = ""; -#if !(defined(ANDROID) || defined(__ANDROID__)) - sep = internal::thousands_sep(std::localeconv()); -#endif - unsigned size = static_cast<unsigned>( - num_digits + sep.size() * ((num_digits - 1) / 3)); - CharPtr p = prepare_int_buffer(size, spec, prefix, prefix_size) + 1; - internal::format_decimal(get(p), abs_value, 0, internal::ThousandsSep(sep)); - break; - } - default: - internal::report_unknown_type( - spec.type(), spec.flag(CHAR_FLAG) ? "char" : "integer"); - break; - } +void basic_writer<Range>::write_str( + basic_string_view<Char> s, const format_specs &spec) { + const Char *data = s.data(); + std::size_t size = s.size(); + std::size_t precision = static_cast<std::size_t>(spec.precision_); + if (spec.precision_ >= 0 && precision < size) + size = precision; + write_str(data, size, spec); } template <typename Char> -template <typename T, typename Spec> -void BasicWriter<Char>::write_double(T value, const Spec &spec) { - // Check type. - char type = spec.type(); - bool upper = false; - switch (type) { - case 0: - type = 'g'; - break; - case 'e': case 'f': case 'g': case 'a': - break; - case 'F': -#if FMT_MSC_VER - // MSVC's printf doesn't support 'F'. - type = 'f'; -#endif - // Fall through. - case 'E': case 'G': case 'A': - upper = true; - break; - default: - internal::report_unknown_type(type, "double"); - break; - } - - char sign = 0; - // Use isnegative instead of value < 0 because the latter is always - // false for NaN. - if (internal::FPUtil::isnegative(static_cast<double>(value))) { - sign = '-'; - value = -value; - } else if (spec.flag(SIGN_FLAG)) { - sign = spec.flag(PLUS_FLAG) ? '+' : ' '; - } +struct float_spec_handler { + Char type; + bool upper; - if (internal::FPUtil::isnotanumber(value)) { - // Format NaN ourselves because sprintf's output is not consistent - // across platforms. - std::size_t nan_size = 4; - const char *nan = upper ? " NAN" : " nan"; - if (!sign) { - --nan_size; - ++nan; - } - CharPtr out = write_str(nan, nan_size, spec); - if (sign) - *out = sign; - return; - } + explicit float_spec_handler(Char t) : type(t), upper(false) {} - if (internal::FPUtil::isinfinity(value)) { - // Format infinity ourselves because sprintf's output is not consistent - // across platforms. - std::size_t inf_size = 4; - const char *inf = upper ? " INF" : " inf"; - if (!sign) { - --inf_size; - ++inf; - } - CharPtr out = write_str(inf, inf_size, spec); - if (sign) - *out = sign; - return; + void on_general() { + if (type == 'G') + upper = true; + else + type = 'g'; } - std::size_t offset = buffer_.size(); - unsigned width = spec.width(); - if (sign) { - buffer_.reserve(buffer_.size() + (width > 1u ? width : 1u)); - if (width > 0) - --width; - ++offset; + void on_exp() { + if (type == 'E') + upper = true; } - // Build format string. - enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg - Char format[MAX_FORMAT_SIZE]; - Char *format_ptr = format; - *format_ptr++ = '%'; - unsigned width_for_sprintf = width; - if (spec.flag(HASH_FLAG)) - *format_ptr++ = '#'; - if (spec.align() == ALIGN_CENTER) { - width_for_sprintf = 0; - } else { - if (spec.align() == ALIGN_LEFT) - *format_ptr++ = '-'; - if (width != 0) - *format_ptr++ = '*'; - } - if (spec.precision() >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - - append_float_length(format_ptr, value); - *format_ptr++ = type; - *format_ptr = '\0'; - - // Format using snprintf. - Char fill = internal::CharTraits<Char>::cast(spec.fill()); - unsigned n = 0; - Char *start = FMT_NULL; - for (;;) { - std::size_t buffer_size = buffer_.capacity() - offset; + void on_fixed() { + if (type == 'F') { + upper = true; #if FMT_MSC_VER - // MSVC's vsnprintf_s doesn't work with zero size, so reserve - // space for at least one extra character to make the size non-zero. - // Note that the buffer's capacity will increase by more than 1. - if (buffer_size == 0) { - buffer_.reserve(offset + 1); - buffer_size = buffer_.capacity() - offset; - } + // MSVC's printf doesn't support 'F'. + type = 'f'; #endif - start = &buffer_[offset]; - int result = internal::CharTraits<Char>::format_float( - start, buffer_size, format, width_for_sprintf, spec.precision(), value); - if (result >= 0) { - n = internal::to_unsigned(result); - if (offset + n < buffer_.capacity()) - break; // The buffer is large enough - continue with formatting. - buffer_.reserve(offset + n + 1); - } else { - // If result is negative we ask to increase the capacity by at least 1, - // but as std::vector, the buffer grows exponentially. - buffer_.reserve(buffer_.capacity() + 1); - } - } - if (sign) { - if ((spec.align() != ALIGN_RIGHT && spec.align() != ALIGN_DEFAULT) || - *start != ' ') { - *(start - 1) = sign; - sign = 0; - } else { - *(start - 1) = fill; } - ++n; - } - if (spec.align() == ALIGN_CENTER && spec.width() > n) { - width = spec.width(); - CharPtr p = grow_buffer(width); - std::memmove(get(p) + (width - n) / 2, get(p), n * sizeof(Char)); - fill_padding(p, spec.width(), n, fill); - return; - } - if (spec.fill() != ' ' || sign) { - while (*start == ' ') - *start++ = fill; - if (sign) - *(start - 1) = sign; } - grow_buffer(n); -} - -/** - \rst - This class template provides operations for formatting and writing data - into a character stream. The output is stored in a memory buffer that grows - dynamically. - - You can use one of the following typedefs for common character types - and the standard allocator: - - +---------------+-----------------------------------------------------+ - | Type | Definition | - +===============+=====================================================+ - | MemoryWriter | BasicMemoryWriter<char, std::allocator<char>> | - +---------------+-----------------------------------------------------+ - | WMemoryWriter | BasicMemoryWriter<wchar_t, std::allocator<wchar_t>> | - +---------------+-----------------------------------------------------+ - - **Example**:: - - MemoryWriter out; - out << "The answer is " << 42 << "\n"; - out.write("({:+f}, {:+f})", -3.14, 3.14); - - This will write the following output to the ``out`` object: - - .. code-block:: none - The answer is 42 - (-3.140000, +3.140000) - - The output can be converted to an ``std::string`` with ``out.str()`` or - accessed as a C string with ``out.c_str()``. - \endrst - */ -template <typename Char, typename Allocator = std::allocator<Char> > -class BasicMemoryWriter : public BasicWriter<Char> { - private: - internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_; - - public: - explicit BasicMemoryWriter(const Allocator& alloc = Allocator()) - : BasicWriter<Char>(buffer_), buffer_(alloc) {} - -#if FMT_USE_RVALUE_REFERENCES - /** - \rst - Constructs a :class:`fmt::BasicMemoryWriter` object moving the content - of the other object to it. - \endrst - */ - BasicMemoryWriter(BasicMemoryWriter &&other) - : BasicWriter<Char>(buffer_), buffer_(std::move(other.buffer_)) { + void on_hex() { + if (type == 'A') + upper = true; } - /** - \rst - Moves the content of the other ``BasicMemoryWriter`` object to this one. - \endrst - */ - BasicMemoryWriter &operator=(BasicMemoryWriter &&other) { - buffer_ = std::move(other.buffer_); - return *this; + void on_error() { + FMT_THROW(format_error("invalid type specifier")); } -#endif }; -typedef BasicMemoryWriter<char> MemoryWriter; -typedef BasicMemoryWriter<wchar_t> WMemoryWriter; - -/** - \rst - This class template provides operations for formatting and writing data - into a fixed-size array. For writing into a dynamically growing buffer - use :class:`fmt::BasicMemoryWriter`. +template <typename Range> +template <typename T> +void basic_writer<Range>::write_double(T value, const format_specs &spec) { + // Check type. + float_spec_handler<char_type> handler(spec.type()); + internal::handle_float_type_spec(spec.type(), handler); - Any write method will throw ``std::runtime_error`` if the output doesn't fit - into the array. + char sign = 0; + // Use isnegative instead of value < 0 because the latter is always + // false for NaN. + if (internal::fputil::isnegative(static_cast<double>(value))) { + sign = '-'; + value = -value; + } else if (spec.flag(SIGN_FLAG)) { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } - You can use one of the following typedefs for common character types: + struct write_inf_or_nan_t { + basic_writer &writer; + format_specs spec; + char sign; + void operator()(const char *str) const { + writer.write_padded(INF_SIZE + (sign ? 1 : 0), spec, + inf_or_nan_writer{sign, str}); + } + } write_inf_or_nan = {*this, spec, sign}; + + // Format NaN and ininity ourselves because sprintf's output is not consistent + // across platforms. + if (internal::fputil::isnotanumber(value)) + return write_inf_or_nan(handler.upper ? "NAN" : "nan"); + if (internal::fputil::isinfinity(value)) + return write_inf_or_nan(handler.upper ? "INF" : "inf"); + + basic_memory_buffer<char_type> buffer; + char type = static_cast<char>(spec.type()); + if (internal::const_check( + internal::use_grisu() && sizeof(T) <= sizeof(double)) && + type != 'a' && type != 'A') { + char buf[100]; // TODO: correct buffer size + size_t size = 0; + internal::grisu2_format(static_cast<double>(value), buf, size, type, + spec.precision(), spec.flag(HASH_FLAG)); + FMT_ASSERT(size <= 100, "buffer overflow"); + buffer.append(buf, buf + size); // TODO: avoid extra copy + } else { + format_specs normalized_spec(spec); + normalized_spec.type_ = handler.type; + write_double_sprintf(value, normalized_spec, buffer); + } + size_t n = buffer.size(); + align_spec as = spec; + if (spec.align() == ALIGN_NUMERIC) { + if (sign) { + auto &&it = reserve(1); + *it++ = sign; + sign = 0; + if (as.width_) + --as.width_; + } + as.align_ = ALIGN_RIGHT; + } else { + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + if (sign) + ++n; + } + write_padded(n, as, double_writer{n, sign, buffer}); +} - +--------------+---------------------------+ - | Type | Definition | - +==============+===========================+ - | ArrayWriter | BasicArrayWriter<char> | - +--------------+---------------------------+ - | WArrayWriter | BasicArrayWriter<wchar_t> | - +--------------+---------------------------+ - \endrst - */ -template <typename Char> -class BasicArrayWriter : public BasicWriter<Char> { - private: - internal::FixedBuffer<Char> buffer_; +template <typename Range> +template <typename T> +void basic_writer<Range>::write_double_sprintf( + T value, const format_specs &spec, + internal::basic_buffer<char_type>& buffer) { + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buffer.capacity() != 0, "empty buffer"); - public: - /** - \rst - Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the - given size. - \endrst - */ - BasicArrayWriter(Char *array, std::size_t size) - : BasicWriter<Char>(buffer_), buffer_(array, size) {} + // Build format string. + enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg + char_type format[MAX_FORMAT_SIZE]; + char_type *format_ptr = format; + *format_ptr++ = '%'; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.precision() >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } - /** - \rst - Constructs a :class:`fmt::BasicArrayWriter` object for *array* of the - size known at compile time. - \endrst - */ - template <std::size_t SIZE> - explicit BasicArrayWriter(Char (&array)[SIZE]) - : BasicWriter<Char>(buffer_), buffer_(array, SIZE) {} -}; + append_float_length(format_ptr, value); + *format_ptr++ = spec.type(); + *format_ptr = '\0'; -typedef BasicArrayWriter<char> ArrayWriter; -typedef BasicArrayWriter<wchar_t> WArrayWriter; + // Format using snprintf. + char_type *start = FMT_NULL; + for (;;) { + std::size_t buffer_size = buffer.capacity(); + start = &buffer[0]; + int result = internal::char_traits<char_type>::format_float( + start, buffer_size, format, spec.precision(), value); + if (result >= 0) { + unsigned n = internal::to_unsigned(result); + if (n < buffer.capacity()) { + buffer.resize(n); + break; // The buffer is large enough - continue with formatting. + } + buffer.reserve(n + 1); + } else { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer.reserve(buffer.capacity() + 1); + } + } +} // Reports a system error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_system_error(int error_code, - StringRef message) FMT_NOEXCEPT; + string_view message) FMT_NOEXCEPT; #if FMT_USE_WINDOWS_H /** A Windows error. */ -class WindowsError : public SystemError { +class windows_error : public system_error { private: - FMT_API void init(int error_code, CStringRef format_str, ArgList args); + FMT_API void init(int error_code, string_view format_str, format_args args); public: /** \rst - Constructs a :class:`fmt::WindowsError` object with the description + Constructs a :class:`fmt::windows_error` object with the description of the form .. parsed-literal:: @@ -3396,119 +2997,64 @@ class WindowsError : public SystemError { **Example**:: - // This throws a WindowsError with the description + // This throws a windows_error with the description // cannot open file 'madeup': The system cannot find the file specified. // or similar (system message may vary). const char *filename = "madeup"; LPOFSTRUCT of = LPOFSTRUCT(); HFILE file = OpenFile(filename, &of, OF_READ); if (file == HFILE_ERROR) { - throw fmt::WindowsError(GetLastError(), - "cannot open file '{}'", filename); + throw fmt::windows_error(GetLastError(), + "cannot open file '{}'", filename); } \endrst */ - WindowsError(int error_code, CStringRef message) { - init(error_code, message, ArgList()); + template <typename... Args> + windows_error(int error_code, string_view message, const Args & ... args) { + init(error_code, message, make_format_args(args...)); } - FMT_VARIADIC_CTOR(WindowsError, init, int, CStringRef) }; // Reports a Windows error without throwing an exception. // Can be used to report errors from destructors. FMT_API void report_windows_error(int error_code, - StringRef message) FMT_NOEXCEPT; + string_view message) FMT_NOEXCEPT; #endif -enum Color { BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE }; - -/** - Formats a string and prints it to stdout using ANSI escape sequences - to specify color (experimental). - Example: - print_colored(fmt::RED, "Elapsed time: {0:.2f} seconds", 1.23); - */ -FMT_API void print_colored(Color c, CStringRef format, ArgList args); - -/** - \rst - Formats arguments and returns the result as a string. - - **Example**:: - - std::string message = format("The answer is {}", 42); - \endrst -*/ -inline std::string format(CStringRef format_str, ArgList args) { - MemoryWriter w; - w.write(format_str, args); - return w.str(); -} - -inline std::wstring format(WCStringRef format_str, ArgList args) { - WMemoryWriter w; - w.write(format_str, args); - return w.str(); -} - -/** - \rst - Prints formatted data to the file *f*. - - **Example**:: - - print(stderr, "Don't {}!", "panic"); - \endrst - */ -FMT_API void print(std::FILE *f, CStringRef format_str, ArgList args); - -/** - \rst - Prints formatted data to ``stdout``. - - **Example**:: - - print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -FMT_API void print(CStringRef format_str, ArgList args); - -/** - Fast integer formatter. - */ -class FormatInt { +/** Fast integer formatter. */ +class format_int { private: // Buffer should be large enough to hold all digits (digits10 + 1), // a sign and a null character. - enum {BUFFER_SIZE = std::numeric_limits<ULongLong>::digits10 + 3}; + enum {BUFFER_SIZE = std::numeric_limits<unsigned long long>::digits10 + 3}; mutable char buffer_[BUFFER_SIZE]; char *str_; - // Formats value in reverse and returns the number of digits. - char *format_decimal(ULongLong value) { - char *buffer_end = buffer_ + BUFFER_SIZE - 1; + // Formats value in reverse and returns a pointer to the beginning. + char *format_decimal(unsigned long long value) { + char *ptr = buffer_ + BUFFER_SIZE - 1; while (value >= 100) { // Integer division is slow so do it for a group of two digits instead // of for every digit. The idea comes from the talk by Alexandrescu // "Three Optimization Tips for C++". See speed-test for a comparison. unsigned index = static_cast<unsigned>((value % 100) * 2); value /= 100; - *--buffer_end = internal::Data::DIGITS[index + 1]; - *--buffer_end = internal::Data::DIGITS[index]; + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; } if (value < 10) { - *--buffer_end = static_cast<char>('0' + value); - return buffer_end; + *--ptr = static_cast<char>('0' + value); + return ptr; } unsigned index = static_cast<unsigned>(value * 2); - *--buffer_end = internal::Data::DIGITS[index + 1]; - *--buffer_end = internal::Data::DIGITS[index]; - return buffer_end; + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; + return ptr; } - void FormatSigned(LongLong value) { - ULongLong abs_value = static_cast<ULongLong>(value); + void format_signed(long long value) { + unsigned long long abs_value = static_cast<unsigned long long>(value); bool negative = value < 0; if (negative) abs_value = 0 - abs_value; @@ -3518,12 +3064,12 @@ class FormatInt { } public: - explicit FormatInt(int value) { FormatSigned(value); } - explicit FormatInt(long value) { FormatSigned(value); } - explicit FormatInt(LongLong value) { FormatSigned(value); } - explicit FormatInt(unsigned value) : str_(format_decimal(value)) {} - explicit FormatInt(unsigned long value) : str_(format_decimal(value)) {} - explicit FormatInt(ULongLong value) : str_(format_decimal(value)) {} + explicit format_int(int value) { format_signed(value); } + explicit format_int(long value) { format_signed(value); } + explicit format_int(long long value) { format_signed(value); } + explicit format_int(unsigned value) : str_(format_decimal(value)) {} + explicit format_int(unsigned long value) : str_(format_decimal(value)) {} + explicit format_int(unsigned long long value) : str_(format_decimal(value)) {} /** Returns the number of characters written to the output buffer. */ std::size_t size() const { @@ -3558,8 +3104,8 @@ class FormatInt { // write a terminating null character. template <typename T> inline void format_decimal(char *&buffer, T value) { - typedef typename internal::IntTraits<T>::MainType MainType; - MainType abs_value = static_cast<MainType>(value); + typedef typename internal::int_traits<T>::main_type main_type; + main_type abs_value = static_cast<main_type>(value); if (internal::is_negative(value)) { *buffer++ = '-'; abs_value = 0 - abs_value; @@ -3570,8 +3116,8 @@ inline void format_decimal(char *&buffer, T value) { return; } unsigned index = static_cast<unsigned>(abs_value * 2); - *buffer++ = internal::Data::DIGITS[index]; - *buffer++ = internal::Data::DIGITS[index + 1]; + *buffer++ = internal::data::DIGITS[index]; + *buffer++ = internal::data::DIGITS[index + 1]; return; } unsigned num_digits = internal::count_digits(abs_value); @@ -3579,524 +3125,494 @@ inline void format_decimal(char *&buffer, T value) { buffer += num_digits; } -/** - \rst - Returns a named argument for formatting functions. +// Formatter of objects of type T. +template <typename T, typename Char> +struct formatter< + T, Char, + typename std::enable_if<internal::format_type< + typename buffer_context<Char>::type, T>::value>::type> { + + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template <typename ParseContext> + FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) { + auto it = internal::null_terminating_iterator<Char>(ctx); + typedef internal::dynamic_specs_handler<ParseContext> handler_type; + auto type = internal::get_type< + typename buffer_context<Char>::type, T>::value; + internal::specs_checker<handler_type> + handler(handler_type(specs_, ctx), type); + it = parse_format_specs(it, handler); + auto type_spec = specs_.type(); + auto eh = ctx.error_handler(); + switch (type) { + case internal::none_type: + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + case internal::uint_type: + case internal::long_long_type: + case internal::ulong_long_type: + case internal::bool_type: + handle_int_type_spec( + type_spec, internal::int_type_checker<decltype(eh)>(eh)); + break; + case internal::char_type: + handle_char_specs( + &specs_, + internal::char_specs_checker<decltype(eh), decltype(type_spec)>( + type_spec, eh)); + break; + case internal::double_type: + case internal::long_double_type: + handle_float_type_spec( + type_spec, internal::float_type_checker<decltype(eh)>(eh)); + break; + case internal::cstring_type: + internal::handle_cstring_type_spec( + type_spec, internal::cstring_type_checker<decltype(eh)>(eh)); + break; + case internal::string_type: + internal::check_string_type_spec(type_spec, eh); + break; + case internal::pointer_type: + internal::check_pointer_type_spec(type_spec, eh); + break; + case internal::custom_type: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; + } + return pointer_from(it); + } - **Example**:: + template <typename FormatContext> + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { + internal::handle_dynamic_spec<internal::width_checker>( + specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec<internal::precision_checker>( + specs_.precision_, specs_.precision_ref, ctx); + typedef output_range<typename FormatContext::iterator, + typename FormatContext::char_type> range_type; + return fmt::visit(arg_formatter<range_type>(ctx, &specs_), + internal::make_arg<FormatContext>(val)); + } - print("Elapsed time: {s:.2f} seconds", arg("s", 1.23)); + private: + internal::dynamic_format_specs<Char> specs_; +}; - \endrst - */ -template <typename T> -inline internal::NamedArgWithType<char, T> arg(StringRef name, const T &arg) { - return internal::NamedArgWithType<char, T>(name, arg); -} +// A formatter for types known only at run time such as variant alternatives. +// +// Usage: +// typedef std::variant<int, std::string> variant; +// template <> +// struct formatter<variant>: dynamic_formatter<> { +// void format(buffer &buf, const variant &v, context &ctx) { +// visit([&](const auto &val) { format(buf, val, ctx); }, v); +// } +// }; +template <typename Char = char> +class dynamic_formatter { + private: + struct null_handler: internal::error_handler { + void on_align(alignment) {} + void on_plus() {} + void on_minus() {} + void on_space() {} + void on_hash() {} + }; -template <typename T> -inline internal::NamedArgWithType<wchar_t, T> arg(WStringRef name, const T &arg) { - return internal::NamedArgWithType<wchar_t, T>(name, arg); -} + public: + template <typename ParseContext> + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + auto it = internal::null_terminating_iterator<Char>(ctx); + // Checks are deferred to formatting time when the argument type is known. + internal::dynamic_specs_handler<ParseContext> handler(specs_, ctx); + it = parse_format_specs(it, handler); + return pointer_from(it); + } + + template <typename T, typename FormatContext> + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { + handle_specs(ctx); + internal::specs_checker<null_handler> + checker(null_handler(), internal::get_type<FormatContext, T>::value); + checker.on_align(specs_.align()); + if (specs_.flags_ == 0) { + // Do nothing. + } else if (specs_.flag(SIGN_FLAG)) { + if (specs_.flag(PLUS_FLAG)) + checker.on_plus(); + else + checker.on_space(); + } else if (specs_.flag(MINUS_FLAG)) { + checker.on_minus(); + } else if (specs_.flag(HASH_FLAG)) { + checker.on_hash(); + } + if (specs_.precision_ != -1) + checker.end_precision(); + typedef output_range<typename FormatContext::iterator, + typename FormatContext::char_type> range; + fmt::visit(arg_formatter<range>(ctx, &specs_), + internal::make_arg<FormatContext>(val)); + return ctx.out(); + } -// The following two functions are deleted intentionally to disable -// nested named arguments as in ``format("{}", arg("a", arg("b", 42)))``. -template <typename Char> -void arg(StringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED; -template <typename Char> -void arg(WStringRef, const internal::NamedArg<Char>&) FMT_DELETED_OR_UNDEFINED; + private: + template <typename Context> + void handle_specs(Context &ctx) { + internal::handle_dynamic_spec<internal::width_checker>( + specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec<internal::precision_checker>( + specs_.precision_, specs_.precision_ref, ctx); + } + + internal::dynamic_format_specs<Char> specs_; +}; + +template <typename Range, typename Char> +typename basic_format_context<Range, Char>::format_arg + basic_format_context<Range, Char>::get_arg( + basic_string_view<char_type> name) { + map_.init(this->args()); + format_arg arg = map_.find(name); + if (arg.type() == internal::none_type) + this->on_error("argument not found"); + return arg; } -#if FMT_GCC_VERSION -// Use the system_header pragma to suppress warnings about variadic macros -// because suppressing -Wvariadic-macros with the diagnostic pragma doesn't -// work. It is used at the end because we want to suppress as little warnings -// as possible. -# pragma GCC system_header -#endif +template <typename ArgFormatter, typename Char, typename Context> +struct format_handler : internal::error_handler { + typedef internal::null_terminating_iterator<Char> iterator; + typedef typename ArgFormatter::range range; + + format_handler(range r, basic_string_view<Char> str, + basic_format_args<Context> format_args) + : context(r.begin(), str, format_args) {} + + void on_text(const Char *begin, const Char *end) { + auto size = internal::to_unsigned(end - begin); + auto out = context.out(); + auto &&it = internal::reserve(out, size); + it = std::copy_n(begin, size, it); + context.advance_to(out); + } + + void on_arg_id() { arg = context.next_arg(); } + void on_arg_id(unsigned id) { + context.parse_context().check_arg_id(id); + arg = context.get_arg(id); + } + void on_arg_id(basic_string_view<Char> id) { + arg = context.get_arg(id); + } + + void on_replacement_field(const Char *p) { + context.parse_context().advance_to(p); + if (!fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) + context.advance_to(fmt::visit(ArgFormatter(context), arg)); + } + + iterator on_format_specs(iterator it) { + auto& parse_ctx = context.parse_context(); + parse_ctx.advance_to(pointer_from(it)); + if (fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) + return iterator(parse_ctx); + basic_format_specs<Char> specs; + using internal::specs_handler; + internal::specs_checker<specs_handler<Context>> + handler(specs_handler<Context>(specs, context), arg.type()); + it = parse_format_specs(it, handler); + if (*it != '}') + on_error("missing '}' in format string"); + parse_ctx.advance_to(pointer_from(it)); + context.advance_to(fmt::visit(ArgFormatter(context, &specs), arg)); + return it; + } + + Context context; + basic_format_arg<Context> arg; +}; -// This is used to work around VC++ bugs in handling variadic macros. -#define FMT_EXPAND(args) args - -// Returns the number of arguments. -// Based on https://groups.google.com/forum/#!topic/comp.std.c/d-6Mj5Lko_s. -#define FMT_NARG(...) FMT_NARG_(__VA_ARGS__, FMT_RSEQ_N()) -#define FMT_NARG_(...) FMT_EXPAND(FMT_ARG_N(__VA_ARGS__)) -#define FMT_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N -#define FMT_RSEQ_N() 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 - -#define FMT_FOR_EACH_(N, f, ...) \ - FMT_EXPAND(FMT_CONCAT(FMT_FOR_EACH, N)(f, __VA_ARGS__)) -#define FMT_FOR_EACH(f, ...) \ - FMT_EXPAND(FMT_FOR_EACH_(FMT_NARG(__VA_ARGS__), f, __VA_ARGS__)) - -#define FMT_ADD_ARG_NAME(type, index) type arg##index -#define FMT_GET_ARG_NAME(type, index) arg##index - -#if FMT_USE_VARIADIC_TEMPLATES -# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \ - template <typename... Args> \ - ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ - const Args & ... args) Const { \ - typedef fmt::internal::ArgArray<sizeof...(Args)> ArgArray; \ - typename ArgArray::Type array{ \ - ArgArray::template make<fmt::BasicFormatter<Char> >(args)...}; \ - call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), \ - fmt::ArgList(fmt::internal::make_type(args...), array)); \ - } -#else -// Defines a wrapper for a function taking __VA_ARGS__ arguments -// and n additional arguments of arbitrary types. -# define FMT_WRAP(Const, Char, ReturnType, func, call, n, ...) \ - template <FMT_GEN(n, FMT_MAKE_TEMPLATE_ARG)> \ - inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__), \ - FMT_GEN(n, FMT_MAKE_ARG)) Const { \ - fmt::internal::ArgArray<n>::Type arr; \ - FMT_GEN(n, FMT_ASSIGN_##Char); \ - call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList( \ - fmt::internal::make_type(FMT_GEN(n, FMT_MAKE_REF2)), arr)); \ - } - -# define FMT_VARIADIC_(Const, Char, ReturnType, func, call, ...) \ - inline ReturnType func(FMT_FOR_EACH(FMT_ADD_ARG_NAME, __VA_ARGS__)) Const { \ - call(FMT_FOR_EACH(FMT_GET_ARG_NAME, __VA_ARGS__), fmt::ArgList()); \ - } \ - FMT_WRAP(Const, Char, ReturnType, func, call, 1, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 2, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 3, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 4, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 5, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 6, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 7, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 8, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 9, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 10, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 11, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 12, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 13, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 14, __VA_ARGS__) \ - FMT_WRAP(Const, Char, ReturnType, func, call, 15, __VA_ARGS__) -#endif // FMT_USE_VARIADIC_TEMPLATES +/** Formats arguments and writes the output to the range. */ +template <typename ArgFormatter, typename Char, typename Context> +typename Context::iterator vformat_to(typename ArgFormatter::range out, + basic_string_view<Char> format_str, + basic_format_args<Context> args) { + format_handler<ArgFormatter, Char, Context> h(out, format_str, args); + internal::parse_format_string<false>(format_str, h); + return h.context.out(); +} -/** - \rst - Defines a variadic function with the specified return type, function name - and argument types passed as variable arguments to this macro. +// Casts ``p`` to ``const void*`` for pointer formatting. +// Example: +// auto s = format("{}", ptr(p)); +template <typename T> +inline const void *ptr(const T *p) { return p; } - **Example**:: +template <typename It, typename Char> +struct arg_join { + It begin; + It end; + basic_string_view<Char> sep; - void print_error(const char *file, int line, const char *format, - fmt::ArgList args) { - fmt::print("{}: {}: ", file, line); - fmt::print(format, args); - } - FMT_VARIADIC(void, print_error, const char *, int, const char *) - - ``FMT_VARIADIC`` is used for compatibility with legacy C++ compilers that - don't implement variadic templates. You don't have to use this macro if - you don't need legacy compiler support and can use variadic templates - directly:: - - template <typename... Args> - void print_error(const char *file, int line, const char *format, - const Args & ... args) { - fmt::print("{}: {}: ", file, line); - fmt::print(format, args...); - } - \endrst - */ -#define FMT_VARIADIC(ReturnType, func, ...) \ - FMT_VARIADIC_(, char, ReturnType, func, return func, __VA_ARGS__) + arg_join(It begin, It end, basic_string_view<Char> sep) + : begin(begin), end(end), sep(sep) {} +}; -#define FMT_VARIADIC_CONST(ReturnType, func, ...) \ - FMT_VARIADIC_(const, char, ReturnType, func, return func, __VA_ARGS__) +template <typename It, typename Char> +struct formatter<arg_join<It, Char>, Char>: + formatter<typename std::iterator_traits<It>::value_type, Char> { + template <typename FormatContext> + auto format(const arg_join<It, Char> &value, FormatContext &ctx) + -> decltype(ctx.out()) { + typedef formatter<typename std::iterator_traits<It>::value_type, Char> base; + auto it = value.begin; + auto out = ctx.out(); + if (it != value.end) { + out = base::format(*it++, ctx); + while (it != value.end) { + out = std::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = base::format(*it++, ctx); + } + } + return out; + } +}; -#define FMT_VARIADIC_W(ReturnType, func, ...) \ - FMT_VARIADIC_(, wchar_t, ReturnType, func, return func, __VA_ARGS__) +template <typename It> +arg_join<It, char> join(It begin, It end, string_view sep) { + return arg_join<It, char>(begin, end, sep); +} -#define FMT_VARIADIC_CONST_W(ReturnType, func, ...) \ - FMT_VARIADIC_(const, wchar_t, ReturnType, func, return func, __VA_ARGS__) +template <typename It> +arg_join<It, wchar_t> join(It begin, It end, wstring_view sep) { + return arg_join<It, wchar_t>(begin, end, sep); +} -#define FMT_CAPTURE_ARG_(id, index) ::fmt::arg(#id, id) +// The following causes ICE in gcc 4.4. +#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) +template <typename Range> +auto join(const Range &range, string_view sep) + -> arg_join<decltype(internal::begin(range)), char> { + return join(internal::begin(range), internal::end(range), sep); +} -#define FMT_CAPTURE_ARG_W_(id, index) ::fmt::arg(L###id, id) +template <typename Range> +auto join(const Range &range, wstring_view sep) + -> arg_join<decltype(internal::begin(range)), wchar_t> { + return join(internal::begin(range), internal::end(range), sep); +} +#endif /** \rst - Convenient macro to capture the arguments' names and values into several - ``fmt::arg(name, value)``. + Converts *value* to ``std::string`` using the default format for type *T*. + It doesn't support user-defined types with custom formatters. **Example**:: - int x = 1, y = 2; - print("point: ({x}, {y})", FMT_CAPTURE(x, y)); - // same as: - // print("point: ({x}, {y})", arg("x", x), arg("y", y)); + #include <fmt/format.h> + std::string answer = fmt::to_string(42); \endrst */ -#define FMT_CAPTURE(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_, __VA_ARGS__) - -#define FMT_CAPTURE_W(...) FMT_FOR_EACH(FMT_CAPTURE_ARG_W_, __VA_ARGS__) - -namespace fmt { -FMT_VARIADIC(std::string, format, CStringRef) -FMT_VARIADIC_W(std::wstring, format, WCStringRef) -FMT_VARIADIC(void, print, CStringRef) -FMT_VARIADIC(void, print, std::FILE *, CStringRef) -FMT_VARIADIC(void, print_colored, Color, CStringRef) - -namespace internal { -template <typename Char> -inline bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +template <typename T> +std::string to_string(const T &value) { + std::string str; + internal::container_buffer<std::string> buf(str); + writer(buf).write(value); + return str; } -// Parses an unsigned integer advancing s to the end of the parsed input. -// This function assumes that the first character of s is a digit. -template <typename Char> -unsigned parse_nonnegative_int(const Char *&s) { - assert('0' <= *s && *s <= '9'); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits<int>::max)(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + (*s - '0'); - ++s; - } while ('0' <= *s && *s <= '9'); - // Convert to unsigned to prevent a warning. - if (value > max_int) - FMT_THROW(FormatError("number is too big")); - return value; +/** + Converts *value* to ``std::wstring`` using the default format for type *T*. + */ +template <typename T> +std::wstring to_wstring(const T &value) { + std::wstring str; + internal::container_buffer<std::wstring> buf(str); + wwriter(buf).write(value); + return str; } -inline void require_numeric_argument(const Arg &arg, char spec) { - if (arg.type > Arg::LAST_NUMERIC_TYPE) { - std::string message = - fmt::format("format specifier '{}' requires numeric argument", spec); - FMT_THROW(fmt::FormatError(message)); - } +template <typename Char, std::size_t SIZE> +std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE> &buf) { + return std::basic_string<Char>(buf.data(), buf.size()); } -template <typename Char> -void check_sign(const Char *&s, const Arg &arg) { - char sign = static_cast<char>(*s); - require_numeric_argument(arg, sign); - if (arg.type == Arg::UINT || arg.type == Arg::ULONG_LONG) { - FMT_THROW(FormatError(fmt::format( - "format specifier '{}' requires signed argument", sign))); - } - ++s; +inline format_context::iterator vformat_to( + internal::buffer &buf, string_view format_str, format_args args) { + typedef back_insert_range<internal::buffer> range; + return vformat_to<arg_formatter<range>>(buf, format_str, args); } -} // namespace internal -template <typename Char, typename AF> -inline internal::Arg BasicFormatter<Char, AF>::get_arg( - BasicStringRef<Char> arg_name, const char *&error) { - if (check_no_auto_index(error)) { - map_.init(args()); - const internal::Arg *arg = map_.find(arg_name); - if (arg) - return *arg; - error = "argument not found"; - } - return internal::Arg(); +inline wformat_context::iterator vformat_to( + internal::wbuffer &buf, wstring_view format_str, wformat_args args) { + typedef back_insert_range<internal::wbuffer> range; + return vformat_to<arg_formatter<range>>(buf, format_str, args); } -template <typename Char, typename AF> -inline internal::Arg BasicFormatter<Char, AF>::parse_arg_index(const Char *&s) { - const char *error = FMT_NULL; - internal::Arg arg = *s < '0' || *s > '9' ? - next_arg(error) : get_arg(internal::parse_nonnegative_int(s), error); - if (error) { - FMT_THROW(FormatError( - *s != '}' && *s != ':' ? "invalid format string" : error)); - } - return arg; +template < + typename String, typename... Args, + std::size_t SIZE = inline_buffer_size, + typename Char = typename internal::format_string_traits<String>::char_type> +inline typename buffer_context<Char>::type::iterator format_to( + basic_memory_buffer<Char, SIZE> &buf, const String &format_str, + const Args & ... args) { + internal::check_format_string<Args...>(format_str); + return vformat_to( + buf, basic_string_view<Char>(format_str), + make_format_args<typename buffer_context<Char>::type>(args...)); } -template <typename Char, typename AF> -inline internal::Arg BasicFormatter<Char, AF>::parse_arg_name(const Char *&s) { - assert(internal::is_name_start(*s)); - const Char *start = s; - Char c; - do { - c = *++s; - } while (internal::is_name_start(c) || ('0' <= c && c <= '9')); - const char *error = FMT_NULL; - internal::Arg arg = get_arg(BasicStringRef<Char>(start, s - start), error); - if (error) - FMT_THROW(FormatError(error)); - return arg; -} +template <typename OutputIt, typename Char = char> +//using format_context_t = basic_format_context<OutputIt, Char>; +struct format_context_t { typedef basic_format_context<OutputIt, Char> type; }; -template <typename Char, typename ArgFormatter> -const Char *BasicFormatter<Char, ArgFormatter>::format( - const Char *&format_str, const internal::Arg &arg) { - using internal::Arg; - const Char *s = format_str; - typename ArgFormatter::SpecType spec; - if (*s == ':') { - if (arg.type == Arg::CUSTOM) { - arg.custom.format(this, arg.custom.value, &s); - return s; - } - ++s; - // Parse fill and alignment. - if (Char c = *s) { - const Char *p = s + 1; - spec.align_ = ALIGN_DEFAULT; - do { - switch (*p) { - case '<': - spec.align_ = ALIGN_LEFT; - break; - case '>': - spec.align_ = ALIGN_RIGHT; - break; - case '=': - spec.align_ = ALIGN_NUMERIC; - break; - case '^': - spec.align_ = ALIGN_CENTER; - break; - } - if (spec.align_ != ALIGN_DEFAULT) { - if (p != s) { - if (c == '}') break; - if (c == '{') - FMT_THROW(FormatError("invalid fill character '{'")); - s += 2; - spec.fill_ = c; - } else ++s; - if (spec.align_ == ALIGN_NUMERIC) - require_numeric_argument(arg, '='); - break; - } - } while (--p >= s); - } +template <typename OutputIt, typename Char = char> +//using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>; +struct format_args_t { + typedef basic_format_args< + typename format_context_t<OutputIt, Char>::type> type; +}; - // Parse sign. - switch (*s) { - case '+': - check_sign(s, arg); - spec.flags_ |= SIGN_FLAG | PLUS_FLAG; - break; - case '-': - check_sign(s, arg); - spec.flags_ |= MINUS_FLAG; - break; - case ' ': - check_sign(s, arg); - spec.flags_ |= SIGN_FLAG; - break; - } +template <typename OutputIt, typename... Args> +inline OutputIt vformat_to(OutputIt out, string_view format_str, + typename format_args_t<OutputIt>::type args) { + typedef output_range<OutputIt, char> range; + return vformat_to<arg_formatter<range>>(range(out), format_str, args); +} +template <typename OutputIt, typename... Args> +inline OutputIt vformat_to( + OutputIt out, wstring_view format_str, + typename format_args_t<OutputIt, wchar_t>::type args) { + typedef output_range<OutputIt, wchar_t> range; + return vformat_to<arg_formatter<range>>(range(out), format_str, args); +} - if (*s == '#') { - require_numeric_argument(arg, '#'); - spec.flags_ |= HASH_FLAG; - ++s; - } +/** + \rst + Formats arguments, writes the result to the output iterator ``out`` and returns + the iterator past the end of the output range. - // Parse zero flag. - if (*s == '0') { - require_numeric_argument(arg, '0'); - spec.align_ = ALIGN_NUMERIC; - spec.fill_ = '0'; - ++s; - } + **Example**:: - // Parse width. - if ('0' <= *s && *s <= '9') { - spec.width_ = internal::parse_nonnegative_int(s); - } else if (*s == '{') { - ++s; - Arg width_arg = internal::is_name_start(*s) ? - parse_arg_name(s) : parse_arg_index(s); - if (*s++ != '}') - FMT_THROW(FormatError("invalid format string")); - ULongLong value = 0; - switch (width_arg.type) { - case Arg::INT: - if (width_arg.int_value < 0) - FMT_THROW(FormatError("negative width")); - value = width_arg.int_value; - break; - case Arg::UINT: - value = width_arg.uint_value; - break; - case Arg::LONG_LONG: - if (width_arg.long_long_value < 0) - FMT_THROW(FormatError("negative width")); - value = width_arg.long_long_value; - break; - case Arg::ULONG_LONG: - value = width_arg.ulong_long_value; - break; - default: - FMT_THROW(FormatError("width is not integer")); - } - unsigned max_int = (std::numeric_limits<int>::max)(); - if (value > max_int) - FMT_THROW(FormatError("number is too big")); - spec.width_ = static_cast<int>(value); - } + std::vector<char> out; + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template <typename OutputIt, typename... Args> +inline OutputIt format_to(OutputIt out, string_view format_str, + const Args & ... args) { + return vformat_to(out, format_str, + make_format_args<typename format_context_t<OutputIt>::type>(args...)); +} - // Parse precision. - if (*s == '.') { - ++s; - spec.precision_ = 0; - if ('0' <= *s && *s <= '9') { - spec.precision_ = internal::parse_nonnegative_int(s); - } else if (*s == '{') { - ++s; - Arg precision_arg = internal::is_name_start(*s) ? - parse_arg_name(s) : parse_arg_index(s); - if (*s++ != '}') - FMT_THROW(FormatError("invalid format string")); - ULongLong value = 0; - switch (precision_arg.type) { - case Arg::INT: - if (precision_arg.int_value < 0) - FMT_THROW(FormatError("negative precision")); - value = precision_arg.int_value; - break; - case Arg::UINT: - value = precision_arg.uint_value; - break; - case Arg::LONG_LONG: - if (precision_arg.long_long_value < 0) - FMT_THROW(FormatError("negative precision")); - value = precision_arg.long_long_value; - break; - case Arg::ULONG_LONG: - value = precision_arg.ulong_long_value; - break; - default: - FMT_THROW(FormatError("precision is not integer")); - } - unsigned max_int = (std::numeric_limits<int>::max)(); - if (value > max_int) - FMT_THROW(FormatError("number is too big")); - spec.precision_ = static_cast<int>(value); - } else { - FMT_THROW(FormatError("missing precision specifier")); - } - if (arg.type <= Arg::LAST_INTEGER_TYPE || arg.type == Arg::POINTER) { - FMT_THROW(FormatError( - fmt::format("precision not allowed in {} format specifier", - arg.type == Arg::POINTER ? "pointer" : "integer"))); - } - } +template <typename OutputIt> +struct format_to_n_result { + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + std::size_t size; +}; - // Parse type. - if (*s != '}' && *s) - spec.type_ = static_cast<char>(*s++); - } +template <typename OutputIt> +using format_to_n_context = typename fmt::format_context_t< + fmt::internal::truncating_iterator<OutputIt>>::type; - if (*s++ != '}') - FMT_THROW(FormatError("missing '}' in format string")); +template <typename OutputIt> +using format_to_n_args = fmt::basic_format_args<format_to_n_context<OutputIt>>; - // Format argument. - ArgFormatter(*this, spec, s - 1).visit(arg); - return s; +template <typename OutputIt, typename ...Args> +inline format_arg_store<format_to_n_context<OutputIt>, Args...> + make_format_to_n_args(const Args & ... args) { + return format_arg_store<format_to_n_context<OutputIt>, Args...>(args...); } -template <typename Char, typename AF> -void BasicFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) { - const Char *s = format_str.c_str(); - const Char *start = s; - while (*s) { - Char c = *s++; - if (c != '{' && c != '}') continue; - if (*s == c) { - write(writer_, start, s); - start = ++s; - continue; - } - if (c == '}') - FMT_THROW(FormatError("unmatched '}' in format string")); - write(writer_, start, s - 1); - internal::Arg arg = internal::is_name_start(*s) ? - parse_arg_name(s) : parse_arg_index(s); - start = s = format(s, arg); - } - write(writer_, start, s); +template <typename OutputIt, typename... Args> +inline format_to_n_result<OutputIt> vformat_to_n( + OutputIt out, std::size_t n, string_view format_str, + format_to_n_args<OutputIt> args) { + typedef internal::truncating_iterator<OutputIt> It; + auto it = vformat_to(It(out, n), format_str, args); + return {it.base(), it.count()}; } -template <typename Char, typename It> -struct ArgJoin { - It first; - It last; - BasicCStringRef<Char> sep; - - ArgJoin(It first, It last, const BasicCStringRef<Char>& sep) : - first(first), - last(last), - sep(sep) {} -}; - -template <typename It> -ArgJoin<char, It> join(It first, It last, const BasicCStringRef<char>& sep) { - return ArgJoin<char, It>(first, last, sep); +/** + \rst + Formats arguments, writes up to ``n`` characters of the result to the output + iterator ``out`` and returns the total output size and the iterator past the + end of the output range. + \endrst + */ +template <typename OutputIt, typename... Args> +inline format_to_n_result<OutputIt> format_to_n( + OutputIt out, std::size_t n, string_view format_str, const Args &... args) { + return vformat_to_n<OutputIt>( + out, n, format_str, make_format_to_n_args<OutputIt>(args...)); } - -template <typename It> -ArgJoin<wchar_t, It> join(It first, It last, const BasicCStringRef<wchar_t>& sep) { - return ArgJoin<wchar_t, It>(first, last, sep); +template <typename OutputIt, typename... Args> +inline format_to_n_result<OutputIt> format_to_n( + OutputIt out, std::size_t n, wstring_view format_str, + const Args &... args) { + typedef internal::truncating_iterator<OutputIt> It; + auto it = vformat_to(It(out, n), format_str, + make_format_args<typename format_context_t<It, wchar_t>::type>(args...)); + return {it.base(), it.count()}; } -#if FMT_HAS_GXX_CXX11 -template <typename Range> -auto join(const Range& range, const BasicCStringRef<char>& sep) - -> ArgJoin<char, decltype(std::begin(range))> { - return join(std::begin(range), std::end(range), sep); +template <typename Char> +inline std::basic_string<Char> internal::vformat( + basic_string_view<Char> format_str, + basic_format_args<typename buffer_context<Char>::type> args) { + basic_memory_buffer<Char> buffer; + vformat_to(buffer, format_str, args); + return fmt::to_string(buffer); } -template <typename Range> -auto join(const Range& range, const BasicCStringRef<wchar_t>& sep) - -> ArgJoin<wchar_t, decltype(std::begin(range))> { - return join(std::begin(range), std::end(range), sep); +template <typename String, typename... Args> +inline typename std::enable_if<internal::is_compile_string<String>::value>::type + print(String format_str, const Args & ... args) { + internal::check_format_string<Args...>(format_str); + return vprint(format_str.data(), make_format_args(args...)); } -#endif -template <typename ArgFormatter, typename Char, typename It> -void format_arg(fmt::BasicFormatter<Char, ArgFormatter> &f, - const Char *&format_str, const ArgJoin<Char, It>& e) { - const Char* end = format_str; - if (*end == ':') - ++end; - while (*end && *end != '}') - ++end; - if (*end != '}') - FMT_THROW(FormatError("missing '}' in format string")); - - It it = e.first; - if (it != e.last) { - const Char* save = format_str; - f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++)); - while (it != e.last) { - f.writer().write(e.sep); - format_str = save; - f.format(format_str, internal::MakeArg<fmt::BasicFormatter<Char, ArgFormatter> >(*it++)); - } - } - format_str = end + 1; +/** + Returns the number of characters in the output of + ``format(format_str, args...)``. + */ +template <typename... Args> +inline std::size_t formatted_size(string_view format_str, + const Args & ... args) { + auto it = format_to(internal::counting_iterator<char>(), format_str, args...); + return it.count(); } -} // namespace fmt #if FMT_USE_USER_DEFINED_LITERALS -namespace fmt { namespace internal { +# if FMT_UDL_TEMPLATE +template <typename Char, Char... CHARS> +class udl_formatter { + public: + template <typename... Args> + std::basic_string<Char> operator()(const Args &... args) const { + FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'}; + FMT_CONSTEXPR_DECL bool invalid_format = + check_format_string<Char, error_handler, Args...>( + basic_string_view<Char>(s, sizeof...(CHARS))); + (void)invalid_format; + return format(s, args...); + } +}; +# else template <typename Char> -struct UdlFormat { +struct udl_formatter { const Char *str; template <typename... Args> @@ -4105,13 +3621,14 @@ struct UdlFormat { return format(str, std::forward<Args>(args)...); } }; +# endif // FMT_UDL_TEMPLATE template <typename Char> -struct UdlArg { +struct udl_arg { const Char *str; template <typename T> - NamedArgWithType<Char, T> operator=(T &&value) const { + named_arg<T, Char> operator=(T &&value) const { return {str, std::forward<T>(value)}; } }; @@ -4120,9 +3637,15 @@ struct UdlArg { inline namespace literals { +# if FMT_UDL_TEMPLATE +template <typename Char, Char... CHARS> +FMT_CONSTEXPR internal::udl_formatter<Char, CHARS...> operator""_format() { + return {}; +} +# else /** \rst - C++11 literal equivalent of :func:`fmt::format`. + User-defined literal equivalent of :func:`fmt::format`. **Example**:: @@ -4130,44 +3653,68 @@ inline namespace literals { std::string message = "The answer is {}"_format(42); \endrst */ -inline internal::UdlFormat<char> +inline internal::udl_formatter<char> operator"" _format(const char *s, std::size_t) { return {s}; } -inline internal::UdlFormat<wchar_t> +inline internal::udl_formatter<wchar_t> operator"" _format(const wchar_t *s, std::size_t) { return {s}; } +# endif // FMT_UDL_TEMPLATE /** \rst - C++11 literal equivalent of :func:`fmt::arg`. + User-defined literal equivalent of :func:`fmt::arg`. **Example**:: using namespace fmt::literals; - print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); \endrst */ -inline internal::UdlArg<char> +inline internal::udl_arg<char> operator"" _a(const char *s, std::size_t) { return {s}; } -inline internal::UdlArg<wchar_t> +inline internal::udl_arg<wchar_t> operator"" _a(const wchar_t *s, std::size_t) { return {s}; } - } // inline namespace literals -} // namespace fmt #endif // FMT_USE_USER_DEFINED_LITERALS +FMT_END_NAMESPACE + +#define FMT_STRING(s) [] { \ + typedef typename std::decay<decltype(s)>::type pointer; \ + struct S : fmt::compile_string { \ + static FMT_CONSTEXPR pointer data() { return s; } \ + static FMT_CONSTEXPR size_t size() { return sizeof(s); } \ + explicit operator fmt::string_view() const { return s; } \ + }; \ + return S{}; \ + }() + +#if defined(FMT_STRING_ALIAS) && FMT_STRING_ALIAS +/** + \rst + Constructs a compile-time format string. This macro is disabled by default to + prevent potential name collisions. To enable it define ``FMT_STRING_ALIAS`` to + 1 before including ``fmt/format.h``. -// Restore warnings. -#if FMT_GCC_VERSION >= 406 -# pragma GCC diagnostic pop -#endif + **Example**:: -#if defined(__clang__) && !defined(FMT_ICC_VERSION) -# pragma clang diagnostic pop + #define FMT_STRING_ALIAS 1 + #include <fmt/format.h> + // A compile-time error because 'd' is an invalid specifier for strings. + std::string s = format(fmt("{:d}"), "foo"); + \endrst + */ +# define fmt(s) FMT_STRING(s) #endif #ifdef FMT_HEADER_ONLY # define FMT_FUNC inline -# include "format.cc" +# include "format-inl.h" #else # define FMT_FUNC #endif +// Restore warnings. +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +# pragma GCC diagnostic pop +#endif + #endif // FMT_FORMAT_H_ diff --git a/src/nmodl/ext/spdlog/async.h b/src/nmodl/ext/spdlog/async.h new file mode 100644 index 0000000000..9264a4e3f4 --- /dev/null +++ b/src/nmodl/ext/spdlog/async.h @@ -0,0 +1,87 @@ + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Async logging using global thread pool +// All loggers created here share same global thread pool. +// Each log message is pushed to a queue along withe a shared pointer to the +// logger. +// If a logger deleted while having pending messages in the queue, it's actual +// destruction will defer +// until all its messages are processed by the thread pool. +// This is because each message in the queue holds a shared_ptr to the +// originating logger. + +#include "spdlog/async_logger.h" +#include "spdlog/details/registry.h" +#include "spdlog/details/thread_pool.h" + +#include <memory> +#include <mutex> + +namespace spdlog { + +namespace details { +static const size_t default_async_q_size = 8192; +} + +// async logger factory - creates async loggers backed with thread pool. +// if a global thread pool doesn't already exist, create it with default queue +// size of 8192 items and single thread. +template<async_overflow_policy OverflowPolicy = async_overflow_policy::block> +struct async_factory_impl +{ + template<typename Sink, typename... SinkArgs> + static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&... args) + { + auto ®istry_inst = details::registry::instance(); + + // create global thread pool if not already exists.. + std::lock_guard<std::recursive_mutex> tp_lock(registry_inst.tp_mutex()); + auto tp = registry_inst.get_tp(); + if (tp == nullptr) + { + tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1); + registry_inst.set_tp(tp); + } + + auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); + auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy); + registry_inst.register_and_init(new_logger); + return new_logger; + } +}; + +using async_factory = async_factory_impl<async_overflow_policy::block>; +using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; + +template<typename Sink, typename... SinkArgs> +inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args) +{ + return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); +} + +template<typename Sink, typename... SinkArgs> +inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args) +{ + return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); +} + +// set global thread pool. +inline void init_thread_pool(size_t q_size, size_t thread_count) +{ + auto tp = std::make_shared<details::thread_pool>(q_size, thread_count); + details::registry::instance().set_tp(std::move(tp)); +} + +// get the global thread pool. +inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() +{ + return details::registry::instance().get_tp(); +} +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/async_logger.h b/src/nmodl/ext/spdlog/async_logger.h new file mode 100644 index 0000000000..a7ecb78737 --- /dev/null +++ b/src/nmodl/ext/spdlog/async_logger.h @@ -0,0 +1,73 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Very fast asynchronous logger (millions of logs per second on an average +// desktop) +// Uses pre allocated lockfree queue for maximum throughput even under large +// number of threads. +// Creates a single back thread to pop messages from the queue and log them. +// +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message +// 2. Push a new copy of the message to a queue (or block the caller until +// space is available in the queue) +// 3. will throw spdlog_ex upon log exceptions +// Upon destruction, logs all remaining messages in the queue before +// destructing.. + +#include "spdlog/common.h" +#include "spdlog/logger.h" + +#include <chrono> +#include <memory> +#include <string> + +namespace spdlog { + +// Async overflow policy - block by default. +enum class async_overflow_policy +{ + block, // Block until message can be enqueued + overrun_oldest // Discard oldest message in the queue if full when trying to + // add new item. +}; + +namespace details { +class thread_pool; +} + +class async_logger final : public std::enable_shared_from_this<async_logger>, public logger +{ + friend class details::thread_pool; + +public: + template<typename It> + async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, + async_overflow_policy overflow_policy = async_overflow_policy::block); + + std::shared_ptr<logger> clone(std::string new_name) override; + +protected: + void sink_it_(details::log_msg &msg) override; + void flush_() override; + + void backend_log_(const details::log_msg &incoming_log_msg); + void backend_flush_(); + +private: + std::weak_ptr<details::thread_pool> thread_pool_; + async_overflow_policy overflow_policy_; +}; +} // namespace spdlog + +#include "details/async_logger_impl.h" diff --git a/src/nmodl/ext/spdlog/common.h b/src/nmodl/ext/spdlog/common.h new file mode 100644 index 0000000000..b614734624 --- /dev/null +++ b/src/nmodl/ext/spdlog/common.h @@ -0,0 +1,186 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/tweakme.h" + +#include <atomic> +#include <chrono> +#include <functional> +#include <initializer_list> +#include <memory> +#include <stdexcept> +#include <string> +#include <type_traits> +#include <unordered_map> + +#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) +#include <codecvt> +#include <locale> +#endif + +#include "spdlog/details/null_mutex.h" + +// visual studio upto 2013 does not support noexcept nor constexpr +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define SPDLOG_NOEXCEPT throw() +#define SPDLOG_CONSTEXPR +#else +#define SPDLOG_NOEXCEPT noexcept +#define SPDLOG_CONSTEXPR constexpr +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define SPDLOG_DEPRECATED __attribute__((deprecated)) +#elif defined(_MSC_VER) +#define SPDLOG_DEPRECATED __declspec(deprecated) +#else +#define SPDLOG_DEPRECATED +#endif + +#include "spdlog/fmt/fmt.h" + +namespace spdlog { + +class formatter; + +namespace sinks { +class sink; +} + +using log_clock = std::chrono::system_clock; +using sink_ptr = std::shared_ptr<sinks::sink>; +using sinks_init_list = std::initializer_list<sink_ptr>; +using log_err_handler = std::function<void(const std::string &err_msg)>; + +#if defined(SPDLOG_NO_ATOMIC_LEVELS) +using level_t = details::null_atomic_int; +#else +using level_t = std::atomic<int>; +#endif + +// Log level enum +namespace level { +enum level_enum +{ + trace = 0, + debug = 1, + info = 2, + warn = 3, + err = 4, + critical = 5, + off = 6 +}; + +#if !defined(SPDLOG_LEVEL_NAMES) +#define SPDLOG_LEVEL_NAMES \ + { \ + "trace", "debug", "info", "warning", "error", "critical", "off" \ + } +#endif +static const char *level_names[] SPDLOG_LEVEL_NAMES; + +static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"}; + +inline const char *to_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT +{ + return level_names[l]; +} + +inline const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT +{ + return short_level_names[l]; +} + +inline spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT +{ + static std::unordered_map<std::string, level_enum> name_to_level = // map string->level + {{level_names[0], level::trace}, // trace + {level_names[1], level::debug}, // debug + {level_names[2], level::info}, // info + {level_names[3], level::warn}, // warn + {level_names[4], level::err}, // err + {level_names[5], level::critical}, // critical + {level_names[6], level::off}}; // off + + auto lvl_it = name_to_level.find(name); + return lvl_it != name_to_level.end() ? lvl_it->second : level::off; +} + +using level_hasher = std::hash<int>; +} // namespace level + +// +// Pattern time - specific time getting to use for pattern_formatter. +// local time by default +// +enum class pattern_time_type +{ + local, // log localtime + utc // log utc +}; + +// +// Log exception +// +class spdlog_ex : public std::exception +{ +public: + explicit spdlog_ex(std::string msg) + : msg_(std::move(msg)) + { + } + + spdlog_ex(const std::string &msg, int last_errno) + { + fmt::memory_buffer outbuf; + fmt::format_system_error(outbuf, last_errno, msg); + msg_ = fmt::to_string(outbuf); + } + + const char *what() const SPDLOG_NOEXCEPT override + { + return msg_.c_str(); + } + +private: + std::string msg_; +}; + +// +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +// +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +using filename_t = std::wstring; +#else +using filename_t = std::string; +#endif + +#define SPDLOG_CATCH_AND_HANDLE \ + catch (const std::exception &ex) \ + { \ + err_handler_(ex.what()); \ + } \ + catch (...) \ + { \ + err_handler_("Unknown exeption in logger"); \ + } + +namespace details { +// make_unique support for pre c++14 + +#if __cplusplus >= 201402L // C++14 and beyond +using std::make_unique; +#else +template<typename T, typename... Args> +std::unique_ptr<T> make_unique(Args &&... args) +{ + static_assert(!std::is_array<T>::value, "arrays not supported"); + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} +#endif +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/async_logger_impl.h b/src/nmodl/ext/spdlog/details/async_logger_impl.h new file mode 100644 index 0000000000..2841ab2b27 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/async_logger_impl.h @@ -0,0 +1,110 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// async logger implementation +// uses a thread pool to perform the actual logging + +#include "spdlog/details/thread_pool.h" + +#include <chrono> +#include <memory> +#include <string> + +template<typename It> +inline spdlog::async_logger::async_logger( + std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) + : logger(std::move(logger_name), begin, end) + , thread_pool_(std::move(tp)) + , overflow_policy_(overflow_policy) +{ +} + +inline spdlog::async_logger::async_logger( + std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy) +{ +} + +inline spdlog::async_logger::async_logger( + std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) + : async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) +{ +} + +// send the log message to the thread pool +inline void spdlog::async_logger::sink_it_(details::log_msg &msg) +{ +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + incr_msg_counter_(msg); +#endif + if (auto pool_ptr = thread_pool_.lock()) + { + pool_ptr->post_log(shared_from_this(), std::move(msg), overflow_policy_); + } + else + { + throw spdlog_ex("async log: thread pool doesn't exist anymore"); + } +} + +// send flush request to the thread pool +inline void spdlog::async_logger::flush_() +{ + if (auto pool_ptr = thread_pool_.lock()) + { + pool_ptr->post_flush(shared_from_this(), overflow_policy_); + } + else + { + throw spdlog_ex("async flush: thread pool doesn't exist anymore"); + } +} + +// +// backend functions - called from the thread pool to do the actual job +// +inline void spdlog::async_logger::backend_log_(const details::log_msg &incoming_log_msg) +{ + try + { + for (auto &s : sinks_) + { + if (s->should_log(incoming_log_msg.level)) + { + s->log(incoming_log_msg); + } + } + } + SPDLOG_CATCH_AND_HANDLE + + if (should_flush_(incoming_log_msg)) + { + backend_flush_(); + } +} + +inline void spdlog::async_logger::backend_flush_() +{ + try + { + for (auto &sink : sinks_) + { + sink->flush(); + } + } + SPDLOG_CATCH_AND_HANDLE +} + +inline std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) +{ + auto cloned = std::make_shared<spdlog::async_logger>(std::move(new_name), sinks_.begin(), sinks_.end(), thread_pool_, overflow_policy_); + + cloned->set_level(this->level()); + cloned->flush_on(this->flush_level()); + cloned->set_error_handler(this->error_handler()); + return std::move(cloned); +} diff --git a/src/nmodl/ext/spdlog/details/circular_q.h b/src/nmodl/ext/spdlog/details/circular_q.h new file mode 100644 index 0000000000..b01325bb75 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/circular_q.h @@ -0,0 +1,72 @@ +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// cirucal q view of std::vector. +#pragma once + +#include <vector> + +namespace spdlog { +namespace details { +template<typename T> +class circular_q +{ +public: + using item_type = T; + + explicit circular_q(size_t max_items) + : max_items_(max_items + 1) // one item is reserved as marker for full q + , v_(max_items_) + { + } + + // push back, overrun (oldest) item if no room left + void push_back(T &&item) + { + v_[tail_] = std::move(item); + tail_ = (tail_ + 1) % max_items_; + + if (tail_ == head_) // overrun last item if full + { + head_ = (head_ + 1) % max_items_; + ++overrun_counter_; + } + } + + // Pop item from front. + // If there are no elements in the container, the behavior is undefined. + void pop_front(T &popped_item) + { + popped_item = std::move(v_[head_]); + head_ = (head_ + 1) % max_items_; + } + + bool empty() + { + return tail_ == head_; + } + + bool full() + { + // head is ahead of the tail by 1 + return ((tail_ + 1) % max_items_) == head_; + } + + size_t overrun_counter() const + { + return overrun_counter_; + } + +private: + size_t max_items_; + typename std::vector<T>::size_type head_ = 0; + typename std::vector<T>::size_type tail_ = 0; + + std::vector<T> v_; + + size_t overrun_counter_ = 0; +}; +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/console_globals.h b/src/nmodl/ext/spdlog/details/console_globals.h new file mode 100644 index 0000000000..e2afb6bf01 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/console_globals.h @@ -0,0 +1,74 @@ +#pragma once +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#include "spdlog/details/null_mutex.h" +#include <cstdio> +#include <mutex> + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX // prevent windows redefining min/max +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include <windows.h> +#endif + +namespace spdlog { +namespace details { +struct console_stdout +{ + static std::FILE *stream() + { + return stdout; + } +#ifdef _WIN32 + static HANDLE handle() + { + return ::GetStdHandle(STD_OUTPUT_HANDLE); + } +#endif +}; + +struct console_stderr +{ + static std::FILE *stream() + { + return stderr; + } +#ifdef _WIN32 + static HANDLE handle() + { + return ::GetStdHandle(STD_ERROR_HANDLE); + } +#endif +}; + +struct console_mutex +{ + using mutex_t = std::mutex; + static mutex_t &mutex() + { + static mutex_t s_mutex; + return s_mutex; + } +}; + +struct console_nullmutex +{ + using mutex_t = null_mutex; + static mutex_t &mutex() + { + static mutex_t s_mutex; + return s_mutex; + } +}; +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/file_helper.h b/src/nmodl/ext/spdlog/details/file_helper.h new file mode 100644 index 0000000000..f72820004d --- /dev/null +++ b/src/nmodl/ext/spdlog/details/file_helper.h @@ -0,0 +1,152 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Helper class for file sinks. +// When failing to open a file, retry several times(5) with a delay interval(10 ms). +// Throw spdlog_ex exception on errors. + +#include "spdlog/details/log_msg.h" +#include "spdlog/details/os.h" + +#include <cerrno> +#include <chrono> +#include <cstdio> +#include <string> +#include <thread> +#include <tuple> + +namespace spdlog { +namespace details { + +class file_helper +{ + +public: + const int open_tries = 5; + const int open_interval = 10; + + explicit file_helper() = default; + + file_helper(const file_helper &) = delete; + file_helper &operator=(const file_helper &) = delete; + + ~file_helper() + { + close(); + } + + void open(const filename_t &fname, bool truncate = false) + { + close(); + auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); + _filename = fname; + for (int tries = 0; tries < open_tries; ++tries) + { + if (!os::fopen_s(&fd_, fname, mode)) + { + return; + } + + details::os::sleep_for_millis(open_interval); + } + + throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); + } + + void reopen(bool truncate) + { + if (_filename.empty()) + { + throw spdlog_ex("Failed re opening file - was not opened before"); + } + open(_filename, truncate); + } + + void flush() + { + std::fflush(fd_); + } + + void close() + { + if (fd_ != nullptr) + { + std::fclose(fd_); + fd_ = nullptr; + } + } + + void write(const fmt::memory_buffer &buf) + { + size_t msg_size = buf.size(); + auto data = buf.data(); + if (std::fwrite(data, 1, msg_size, fd_) != msg_size) + { + throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); + } + } + + size_t size() const + { + if (fd_ == nullptr) + { + throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); + } + return os::filesize(fd_); + } + + const filename_t &filename() const + { + return _filename; + } + + static bool file_exists(const filename_t &fname) + { + return os::file_exists(fname); + } + + // + // return file path and its extension: + // + // "mylog.txt" => ("mylog", ".txt") + // "mylog" => ("mylog", "") + // "mylog." => ("mylog.", "") + // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") + // + // the starting dot in filenames is ignored (hidden files): + // + // ".mylog" => (".mylog". "") + // "my_folder/.mylog" => ("my_folder/.mylog", "") + // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") + static std::tuple<filename_t, filename_t> split_by_extenstion(const spdlog::filename_t &fname) + { + auto ext_index = fname.rfind('.'); + + // no valid extension found - return whole path and empty string as + // extension + if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) + { + return std::make_tuple(fname, spdlog::filename_t()); + } + + // treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" + auto folder_index = fname.rfind(details::os::folder_sep); + if (folder_index != filename_t::npos && folder_index >= ext_index - 1) + { + return std::make_tuple(fname, spdlog::filename_t()); + } + + // finally - return a valid base and extension tuple + return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); + } + +private: + std::FILE *fd_{nullptr}; + filename_t _filename; +}; +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/fmt_helper.h b/src/nmodl/ext/spdlog/details/fmt_helper.h new file mode 100644 index 0000000000..1518b2c0ff --- /dev/null +++ b/src/nmodl/ext/spdlog/details/fmt_helper.h @@ -0,0 +1,127 @@ +// +// Created by gabi on 6/15/18. +// + +#pragma once + +#include "chrono" +#include "spdlog/fmt/fmt.h" + +// Some fmt helpers to efficiently format and pad ints and strings +namespace spdlog { +namespace details { +namespace fmt_helper { + +template<size_t Buffer_Size> +inline void append_str(const std::string &str, fmt::basic_memory_buffer<char, Buffer_Size> &dest) +{ + auto *str_ptr = str.data(); + dest.append(str_ptr, str_ptr + str.size()); +} + +template<size_t Buffer_Size> +inline void append_c_str(const char *c_str, fmt::basic_memory_buffer<char, Buffer_Size> &dest) +{ + auto len = std::char_traits<char>::length(c_str); + dest.append(c_str, c_str + len); +} + +template<size_t Buffer_Size1, size_t Buffer_Size2> +inline void append_buf(const fmt::basic_memory_buffer<char, Buffer_Size1> &buf, fmt::basic_memory_buffer<char, Buffer_Size2> &dest) +{ + auto *buf_ptr = buf.data(); + dest.append(buf_ptr, buf_ptr + buf.size()); +} + +template<typename T, size_t Buffer_Size> +inline void append_int(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) +{ + fmt::format_int i(n); + dest.append(i.data(), i.data() + i.size()); +} + +template<size_t Buffer_Size> +inline void pad2(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) +{ + if (n > 99) + { + append_int(n, dest); + return; + } + if (n > 9) // 10-99 + { + dest.push_back(static_cast<char>('0' + n / 10)); + dest.push_back(static_cast<char>('0' + n % 10)); + return; + } + if (n >= 0) // 0-9 + { + dest.push_back('0'); + dest.push_back(static_cast<char>('0' + n)); + return; + } + // negatives (unlikely, but just in case, let fmt deal with it) + fmt::format_to(dest, "{:02}", n); +} + +template<size_t Buffer_Size> +inline void pad3(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) +{ + if (n > 999) + { + append_int(n, dest); + return; + } + + if (n > 99) // 100-999 + { + dest.push_back(static_cast<char>('0' + n / 100)); + pad2(n % 100, dest); + return; + } + if (n > 9) // 10-99 + { + dest.push_back('0'); + dest.push_back(static_cast<char>('0' + n / 10)); + dest.push_back(static_cast<char>('0' + n % 10)); + return; + } + if (n >= 0) + { + dest.push_back('0'); + dest.push_back('0'); + dest.push_back(static_cast<char>('0' + n)); + return; + } + // negatives (unlikely, but just in case let fmt deal with it) + fmt::format_to(dest, "{:03}", n); +} + +template<size_t Buffer_Size> +inline void pad6(size_t n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) +{ + if (n > 99999) + { + append_int(n, dest); + return; + } + pad3(static_cast<int>(n / 1000), dest); + pad3(static_cast<int>(n % 1000), dest); +} + +// return fraction of a second of the given time_point. +// e.g. +// fraction<std::milliseconds>(tp) -> will return the millis part of the second +template<typename ToDuration> +inline ToDuration time_fraction(const log_clock::time_point &tp) +{ + using std::chrono::duration_cast; + using std::chrono::seconds; + auto duration = tp.time_since_epoch(); + auto secs = duration_cast<seconds>(duration); + return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); +} + +} // namespace fmt_helper +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/log_msg.h b/src/nmodl/ext/spdlog/details/log_msg.h new file mode 100644 index 0000000000..49987515b7 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/log_msg.h @@ -0,0 +1,49 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/common.h" +#include "spdlog/details/os.h" + +#include <string> +#include <utility> + +namespace spdlog { +namespace details { +struct log_msg +{ + log_msg() = default; + + log_msg(const std::string *loggers_name, level::level_enum lvl) + : logger_name(loggers_name) + , level(lvl) +#ifndef SPDLOG_NO_DATETIME + , time(os::now()) +#endif + +#ifndef SPDLOG_NO_THREAD_ID + , thread_id(os::thread_id()) +#endif + { + } + + log_msg(const log_msg &other) = delete; + log_msg(log_msg &&other) = delete; + log_msg &operator=(log_msg &&other) = delete; + + const std::string *logger_name{nullptr}; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::memory_buffer raw; + size_t msg_id; + + // info about wrapping the formatted text with color (updated by pattern_formatter). + mutable size_t color_range_start{0}; + mutable size_t color_range_end{0}; +}; +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/logger_impl.h b/src/nmodl/ext/spdlog/details/logger_impl.h new file mode 100644 index 0000000000..46301ea1e1 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/logger_impl.h @@ -0,0 +1,383 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/fmt_helper.h" + +#include <memory> +#include <string> + +// create logger with given name, sinks and the default pattern formatter +// all other ctors will call this one +template<typename It> +inline spdlog::logger::logger(std::string logger_name, It begin, It end) + : name_(std::move(logger_name)) + , sinks_(begin, end) + , level_(level::info) + , flush_level_(level::off) + , last_err_time_(0) + , msg_counter_(1) // message counter will start from 1. 0-message id will be + // reserved for controll messages +{ + err_handler_ = [this](const std::string &msg) { this->default_err_handler_(msg); }; +} + +// ctor with sinks as init list +inline spdlog::logger::logger(std::string logger_name, sinks_init_list sinks_list) + : logger(std::move(logger_name), sinks_list.begin(), sinks_list.end()) +{ +} + +// ctor with single sink +inline spdlog::logger::logger(std::string logger_name, spdlog::sink_ptr single_sink) + : logger(std::move(logger_name), {std::move(single_sink)}) +{ +} + +inline spdlog::logger::~logger() = default; + +inline void spdlog::logger::set_formatter(std::unique_ptr<spdlog::formatter> f) +{ + for (auto &sink : sinks_) + { + sink->set_formatter(f->clone()); + } +} + +inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type) +{ + auto new_formatter = details::make_unique<spdlog::pattern_formatter>(std::move(pattern), time_type); + set_formatter(std::move(new_formatter)); +} + +template<typename... Args> +inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args) +{ + if (!should_log(lvl)) + { + return; + } + + try + { + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, fmt, args...); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template<typename... Args> +inline void spdlog::logger::log(level::level_enum lvl, const char *msg) +{ + if (!should_log(lvl)) + { + return; + } + try + { + details::log_msg log_msg(&name_, lvl); + details::fmt_helper::append_c_str(msg, log_msg.raw); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template<typename T> +inline void spdlog::logger::log(level::level_enum lvl, const T &msg) +{ + if (!should_log(lvl)) + { + return; + } + try + { + details::log_msg log_msg(&name_, lvl); + fmt::format_to(log_msg.raw, "{}", msg); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template<typename... Args> +inline void spdlog::logger::trace(const char *fmt, const Args &... args) +{ + log(level::trace, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::debug(const char *fmt, const Args &... args) +{ + log(level::debug, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::info(const char *fmt, const Args &... args) +{ + log(level::info, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::warn(const char *fmt, const Args &... args) +{ + log(level::warn, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::error(const char *fmt, const Args &... args) +{ + log(level::err, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::critical(const char *fmt, const Args &... args) +{ + log(level::critical, fmt, args...); +} + +template<typename T> +inline void spdlog::logger::trace(const T &msg) +{ + log(level::trace, msg); +} + +template<typename T> +inline void spdlog::logger::debug(const T &msg) +{ + log(level::debug, msg); +} + +template<typename T> +inline void spdlog::logger::info(const T &msg) +{ + log(level::info, msg); +} + +template<typename T> +inline void spdlog::logger::warn(const T &msg) +{ + log(level::warn, msg); +} + +template<typename T> +inline void spdlog::logger::error(const T &msg) +{ + log(level::err, msg); +} + +template<typename T> +inline void spdlog::logger::critical(const T &msg) +{ + log(level::critical, msg); +} + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT + +inline void wbuf_to_utf8buf(const fmt::wmemory_buffer &wbuf, fmt::memory_buffer &target) +{ + int wbuf_size = static_cast<int>(wbuf.size()); + if (wbuf_size == 0) + { + return; + } + + auto result_size = ::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, NULL, 0, NULL, NULL); + + if (result_size > 0) + { + target.resize(result_size); + ::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, &target.data()[0], result_size, NULL, NULL); + } + else + { + throw spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError())); + } +} + +template<typename... Args> +inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) +{ + if (!should_log(lvl)) + { + return; + } + + try + { + // format to wmemory_buffer and convert to utf8 + details::log_msg log_msg(&name_, lvl); + fmt::wmemory_buffer wbuf; + fmt::format_to(wbuf, fmt, args...); + wbuf_to_utf8buf(wbuf, log_msg.raw); + sink_it_(log_msg); + } + SPDLOG_CATCH_AND_HANDLE +} + +template<typename... Args> +inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args) +{ + log(level::trace, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::debug(const wchar_t *fmt, const Args &... args) +{ + log(level::debug, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::info(const wchar_t *fmt, const Args &... args) +{ + log(level::info, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::warn(const wchar_t *fmt, const Args &... args) +{ + log(level::warn, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::error(const wchar_t *fmt, const Args &... args) +{ + log(level::err, fmt, args...); +} + +template<typename... Args> +inline void spdlog::logger::critical(const wchar_t *fmt, const Args &... args) +{ + log(level::critical, fmt, args...); +} + +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + +// +// name and level +// +inline const std::string &spdlog::logger::name() const +{ + return name_; +} + +inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) +{ + level_.store(log_level); +} + +inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler) +{ + err_handler_ = std::move(err_handler); +} + +inline spdlog::log_err_handler spdlog::logger::error_handler() +{ + return err_handler_; +} + +inline void spdlog::logger::flush() +{ + try + { + flush_(); + } + SPDLOG_CATCH_AND_HANDLE +} + +inline void spdlog::logger::flush_on(level::level_enum log_level) +{ + flush_level_.store(log_level); +} + +inline spdlog::level::level_enum spdlog::logger::flush_level() const +{ + return static_cast<spdlog::level::level_enum>(flush_level_.load(std::memory_order_relaxed)); +} + +inline bool spdlog::logger::should_flush_(const details::log_msg &msg) +{ + auto flush_level = flush_level_.load(std::memory_order_relaxed); + return (msg.level >= flush_level) && (msg.level != level::off); +} + +inline spdlog::level::level_enum spdlog::logger::level() const +{ + return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); +} + +inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const +{ + return msg_level >= level_.load(std::memory_order_relaxed); +} + +// +// protected virtual called at end of each user log call (if enabled) by the +// line_logger +// +inline void spdlog::logger::sink_it_(details::log_msg &msg) +{ +#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) + incr_msg_counter_(msg); +#endif + for (auto &sink : sinks_) + { + if (sink->should_log(msg.level)) + { + sink->log(msg); + } + } + + if (should_flush_(msg)) + { + flush(); + } +} + +inline void spdlog::logger::flush_() +{ + for (auto &sink : sinks_) + { + sink->flush(); + } +} + +inline void spdlog::logger::default_err_handler_(const std::string &msg) +{ + auto now = time(nullptr); + if (now - last_err_time_ < 60) + { + return; + } + last_err_time_ = now; + auto tm_time = details::os::localtime(now); + char date_buf[100]; + std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); + fmt::print(stderr, "[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, name(), msg); +} + +inline void spdlog::logger::incr_msg_counter_(details::log_msg &msg) +{ + msg.msg_id = msg_counter_.fetch_add(1, std::memory_order_relaxed); +} + +inline const std::vector<spdlog::sink_ptr> &spdlog::logger::sinks() const +{ + return sinks_; +} + +inline std::vector<spdlog::sink_ptr> &spdlog::logger::sinks() +{ + return sinks_; +} + +inline std::shared_ptr<spdlog::logger> spdlog::logger::clone(std::string logger_name) +{ + auto cloned = std::make_shared<spdlog::logger>(std::move(logger_name), sinks_.begin(), sinks_.end()); + cloned->set_level(this->level()); + cloned->flush_on(this->flush_level()); + cloned->set_error_handler(this->error_handler()); + return cloned; +} diff --git a/src/nmodl/ext/spdlog/details/mpmc_blocking_q.h b/src/nmodl/ext/spdlog/details/mpmc_blocking_q.h new file mode 100644 index 0000000000..ca789fc660 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/mpmc_blocking_q.h @@ -0,0 +1,121 @@ +#pragma once + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +// multi producer-multi consumer blocking queue. +// enqueue(..) - will block until room found to put the new message. +// enqueue_nowait(..) - will return immediately with false if no room left in +// the queue. +// dequeue_for(..) - will block until the queue is not empty or timeout have +// passed. + +#include "spdlog/details/circular_q.h" + +#include <condition_variable> +#include <mutex> + +namespace spdlog { +namespace details { + +template<typename T> +class mpmc_blocking_queue +{ +public: + using item_type = T; + explicit mpmc_blocking_queue(size_t max_items) + : q_(max_items) + { + } + +#ifndef __MINGW32__ + // try to enqueue and block if no room left + void enqueue(T &&item) + { + { + std::unique_lock<std::mutex> lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) + { + { + std::unique_lock<std::mutex> lock(queue_mutex_); + q_.push_back(std::move(item)); + } + push_cv_.notify_one(); + } + + // try to dequeue item. if no item found. wait upto timeout and try again + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) + { + { + std::unique_lock<std::mutex> lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) + { + return false; + } + q_.pop_front(popped_item); + } + pop_cv_.notify_one(); + return true; + } + +#else + // apparently mingw deadlocks if the mutex is released before cv.notify_one(), + // so release the mutex at the very end each function. + + // try to enqueue and block if no room left + void enqueue(T &&item) + { + std::unique_lock<std::mutex> lock(queue_mutex_); + pop_cv_.wait(lock, [this] { return !this->q_.full(); }); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // enqueue immediately. overrun oldest message in the queue if no room left. + void enqueue_nowait(T &&item) + { + std::unique_lock<std::mutex> lock(queue_mutex_); + q_.push_back(std::move(item)); + push_cv_.notify_one(); + } + + // try to dequeue item. if no item found. wait upto timeout and try again + // Return true, if succeeded dequeue item, false otherwise + bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) + { + std::unique_lock<std::mutex> lock(queue_mutex_); + if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) + { + return false; + } + q_.pop_front(popped_item); + pop_cv_.notify_one(); + return true; + } + +#endif + + size_t overrun_counter() + { + std::unique_lock<std::mutex> lock(queue_mutex_); + return q_.overrun_counter(); + } + +private: + std::mutex queue_mutex_; + std::condition_variable push_cv_; + std::condition_variable pop_cv_; + spdlog::details::circular_q<T> q_; +}; +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/null_mutex.h b/src/nmodl/ext/spdlog/details/null_mutex.h new file mode 100644 index 0000000000..3f495bd98a --- /dev/null +++ b/src/nmodl/ext/spdlog/details/null_mutex.h @@ -0,0 +1,45 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include <atomic> +// null, no cost dummy "mutex" and dummy "atomic" int + +namespace spdlog { +namespace details { +struct null_mutex +{ + void lock() {} + void unlock() {} + bool try_lock() + { + return true; + } +}; + +struct null_atomic_int +{ + int value; + null_atomic_int() = default; + + explicit null_atomic_int(int val) + : value(val) + { + } + + int load(std::memory_order) const + { + return value; + } + + void store(int val) + { + value = val; + } +}; + +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/os.h b/src/nmodl/ext/spdlog/details/os.h new file mode 100644 index 0000000000..23e011be92 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/os.h @@ -0,0 +1,422 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +#pragma once + +#include "../common.h" + +#include <algorithm> +#include <chrono> +#include <cstdio> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <functional> +#include <string> +#include <sys/stat.h> +#include <sys/types.h> +#include <thread> + +#ifdef _WIN32 + +#ifndef NOMINMAX +#define NOMINMAX // prevent windows redefining min/max +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include <io.h> // _get_osfhandle and _isatty support +#include <process.h> // _get_pid support +#include <windows.h> + +#ifdef __MINGW32__ +#include <share.h> +#endif + +#else // unix + +#include <fcntl.h> +#include <unistd.h> + +#ifdef __linux__ +#include <sys/syscall.h> //Use gettid() syscall under linux to get thread id + +#elif __FreeBSD__ +#include <sys/thr.h> //Use thr_self() syscall under FreeBSD to get thread id +#endif + +#endif // unix + +#ifndef __has_feature // Clang - feature checking macros. +#define __has_feature(x) 0 // Compatibility with non-clang compilers. +#endif + +namespace spdlog { +namespace details { +namespace os { + +inline spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT +{ + +#if defined __linux__ && defined SPDLOG_CLOCK_COARSE + timespec ts; + ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); + return std::chrono::time_point<log_clock, typename log_clock::duration>( + std::chrono::duration_cast<typename log_clock::duration>(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); + +#else + return log_clock::now(); +#endif +} +inline std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT +{ + +#ifdef _WIN32 + std::tm tm; + localtime_s(&tm, &time_tt); +#else + std::tm tm; + localtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm localtime() SPDLOG_NOEXCEPT +{ + std::time_t now_t = time(nullptr); + return localtime(now_t); +} + +inline std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT +{ + +#ifdef _WIN32 + std::tm tm; + gmtime_s(&tm, &time_tt); +#else + std::tm tm; + gmtime_r(&time_tt, &tm); +#endif + return tm; +} + +inline std::tm gmtime() SPDLOG_NOEXCEPT +{ + std::time_t now_t = time(nullptr); + return gmtime(now_t); +} + +// eol definition +#if !defined(SPDLOG_EOL) +#ifdef _WIN32 +#define SPDLOG_EOL "\r\n" +#else +#define SPDLOG_EOL "\n" +#endif +#endif + +SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; + +// folder separator +#ifdef _WIN32 +SPDLOG_CONSTEXPR static const char folder_sep = '\\'; +#else +SPDLOG_CONSTEXPR static const char folder_sep = '/'; +#endif + +inline void prevent_child_fd(FILE *f) +{ + +#ifdef _WIN32 +#if !defined(__cplusplus_winrt) + auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); + if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) + throw spdlog_ex("SetHandleInformation failed", errno); +#endif +#else + auto fd = fileno(f); + if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) + { + throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); + } +#endif +} + +// fopen_s on non windows for writing +inline bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + *fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); +#else + *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); +#endif +#else // unix + *fp = fopen((filename.c_str()), mode.c_str()); +#endif + +#ifdef SPDLOG_PREVENT_CHILD_FD + if (*fp != nullptr) + { + prevent_child_fd(*fp); + } +#endif + return *fp == nullptr; +} + +inline int remove(const filename_t &filename) SPDLOG_NOEXCEPT +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wremove(filename.c_str()); +#else + return std::remove(filename.c_str()); +#endif +} + +inline int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT +{ +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) + return _wrename(filename1.c_str(), filename2.c_str()); +#else + return std::rename(filename1.c_str(), filename2.c_str()); +#endif +} + +// Return if file exists +inline bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT +{ +#ifdef _WIN32 +#ifdef SPDLOG_WCHAR_FILENAMES + auto attribs = GetFileAttributesW(filename.c_str()); +#else + auto attribs = GetFileAttributesA(filename.c_str()); +#endif + return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); +#else // common linux/unix all have the stat system call + struct stat buffer; + return (stat(filename.c_str(), &buffer) == 0); +#endif +} + +// Return file size according to open FILE* object +inline size_t filesize(FILE *f) +{ + if (f == nullptr) + { + throw spdlog_ex("Failed getting file size. fd is null"); + } +#if defined(_WIN32) && !defined(__CYGWIN__) + int fd = _fileno(f); +#if _WIN64 // 64 bits + struct _stat64 st; + if (_fstat64(fd, &st) == 0) + { + return st.st_size; + } + +#else // windows 32 bits + long ret = _filelength(fd); + if (ret >= 0) + { + return static_cast<size_t>(ret); + } +#endif + +#else // unix + int fd = fileno(f); +// 64 bits(but not in osx or cygwin, where fstat64 is deprecated) +#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) + struct stat64 st; + if (fstat64(fd, &st) == 0) + { + return static_cast<size_t>(st.st_size); + } +#else // unix 32 bits or cygwin + struct stat st; + + if (fstat(fd, &st) == 0) + { + return static_cast<size_t>(st.st_size); + } +#endif +#endif + throw spdlog_ex("Failed getting file size from fd", errno); +} + +// Return utc offset in minutes or throw spdlog_ex on failure +inline int utc_minutes_offset(const std::tm &tm = details::os::localtime()) +{ + +#ifdef _WIN32 +#if _WIN32_WINNT < _WIN32_WINNT_WS08 + TIME_ZONE_INFORMATION tzinfo; + auto rv = GetTimeZoneInformation(&tzinfo); +#else + DYNAMIC_TIME_ZONE_INFORMATION tzinfo; + auto rv = GetDynamicTimeZoneInformation(&tzinfo); +#endif + if (rv == TIME_ZONE_ID_INVALID) + throw spdlog::spdlog_ex("Failed getting timezone info. ", errno); + + int offset = -tzinfo.Bias; + if (tm.tm_isdst) + { + offset -= tzinfo.DaylightBias; + } + else + { + offset -= tzinfo.StandardBias; + } + return offset; +#else + +#if defined(sun) || defined(__sun) || defined(_AIX) + // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris + struct helper + { + static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) + { + int local_year = localtm.tm_year + (1900 - 1); + int gmt_year = gmtm.tm_year + (1900 - 1); + + long int days = ( + // difference in day of year + localtm.tm_yday - + gmtm.tm_yday + + // + intervening leap days + + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + + ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) + + // + difference in years * 365 */ + + (long int)(local_year - gmt_year) * 365); + + long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); + long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); + long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); + + return secs; + } + }; + + auto offset_seconds = helper::calculate_gmt_offset(tm); +#else + auto offset_seconds = tm.tm_gmtoff; +#endif + + return static_cast<int>(offset_seconds / 60); +#endif +} + +// Return current thread id as size_t +// It exists because the std::this_thread::get_id() is much slower(especially +// under VS 2013) +inline size_t _thread_id() SPDLOG_NOEXCEPT +{ +#ifdef _WIN32 + return static_cast<size_t>(::GetCurrentThreadId()); +#elif __linux__ +#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) +#define SYS_gettid __NR_gettid +#endif + return static_cast<size_t>(syscall(SYS_gettid)); +#elif __FreeBSD__ + long tid; + thr_self(&tid); + return static_cast<size_t>(tid); +#elif __APPLE__ + uint64_t tid; + pthread_threadid_np(nullptr, &tid); + return static_cast<size_t>(tid); +#else // Default to standard C++11 (other Unix) + return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id())); +#endif +} + +// Return current thread id as size_t (from thread local storage) +inline size_t thread_id() SPDLOG_NOEXCEPT +{ +#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) || \ + (defined(__clang__) && !__has_feature(cxx_thread_local)) + return _thread_id(); +#else // cache thread id in tls + static thread_local const size_t tid = _thread_id(); + return tid; +#endif +} + +// This is avoid msvc issue in sleep_for that happens if the clock changes. +// See https://github.com/gabime/spdlog/issues/609 +inline void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT +{ +#if defined(_WIN32) + ::Sleep(milliseconds); +#else + std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); +#endif +} + +// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) +#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) +#define SPDLOG_FILENAME_T(s) L##s +inline std::string filename_to_str(const filename_t &filename) +{ + std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> c; + return c.to_bytes(filename); +} +#else +#define SPDLOG_FILENAME_T(s) s +inline std::string filename_to_str(const filename_t &filename) +{ + return filename; +} +#endif + +inline int pid() +{ + +#ifdef _WIN32 + return static_cast<int>(::GetCurrentProcessId()); +#else + return static_cast<int>(::getpid()); +#endif +} + +// Determine if the terminal supports colors +// Source: https://github.com/agauniyal/rang/ +inline bool is_color_terminal() SPDLOG_NOEXCEPT +{ +#ifdef _WIN32 + return true; +#else + static constexpr const char *Terms[] = { + "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}; + + const char *env_p = std::getenv("TERM"); + if (env_p == nullptr) + { + return false; + } + + static const bool result = + std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); + return result; +#endif +} + +// Detrmine if the terminal attached +// Source: https://github.com/agauniyal/rang/ +inline bool in_terminal(FILE *file) SPDLOG_NOEXCEPT +{ + +#ifdef _WIN32 + return _isatty(_fileno(file)) != 0; +#else + return isatty(fileno(file)) != 0; +#endif +} +} // namespace os +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/pattern_formatter.h b/src/nmodl/ext/spdlog/details/pattern_formatter.h new file mode 100644 index 0000000000..f5a8406e99 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/pattern_formatter.h @@ -0,0 +1,777 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/os.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/formatter.h" + +#include <array> +#include <chrono> +#include <ctime> +#include <memory> +#include <mutex> +#include <string> +#include <thread> +#include <utility> +#include <vector> + +namespace spdlog { +namespace details { + +class flag_formatter +{ +public: + virtual ~flag_formatter() = default; + virtual void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) = 0; +}; + +/////////////////////////////////////////////////////////////////////// +// name & level pattern appenders +/////////////////////////////////////////////////////////////////////// +class name_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_str(*msg.logger_name, dest); + } +}; + +// log level appender +class level_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + } +}; + +// short log level appender +class short_level_formatter : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(level::to_short_c_str(msg.level), dest); + } +}; + +/////////////////////////////////////////////////////////////////////// +// Date time pattern appenders +/////////////////////////////////////////////////////////////////////// + +static const char *ampm(const tm &t) +{ + return t.tm_hour >= 12 ? "PM" : "AM"; +} + +static int to12h(const tm &t) +{ + return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; +} + +// Abbreviated weekday name +static const char *days[]{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; +class a_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(days[tm_time.tm_wday], dest); + } +}; + +// Full weekday name +static const char *full_days[]{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; +class A_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(full_days[tm_time.tm_wday], dest); + } +}; + +// Abbreviated month +static const char *months[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}; +class b_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(months[tm_time.tm_mon], dest); + } +}; + +// Full month name +static const char *full_months[]{ + "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; +class B_formatter : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(full_months[tm_time.tm_mon], dest); + } +}; + +// Date and time representation (Thu Aug 23 15:35:46 2014) +class c_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + // fmt::format_to(dest, "{} {} {} ", days[tm_time.tm_wday], + // months[tm_time.tm_mon], tm_time.tm_mday); + // date + fmt_helper::append_c_str(days[tm_time.tm_wday], dest); + dest.push_back(' '); + fmt_helper::append_c_str(months[tm_time.tm_mon], dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_mday, dest); + dest.push_back(' '); + // time + + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// year - 2 digit +class C_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 +class D_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_mday, dest); + dest.push_back('/'); + fmt_helper::pad2(tm_time.tm_year % 100, dest); + } +}; + +// year - 4 digit +class Y_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_int(tm_time.tm_year + 1900, dest); + } +}; + +// month 1-12 +class m_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mon + 1, dest); + } +}; + +// day of month 1-31 +class d_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_mday, dest); + } +}; + +// hours in 24 format 0-23 +class H_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_hour, dest); + } +}; + +// hours in 12 format 1-12 +class I_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(to12h(tm_time), dest); + } +}; + +// minutes 0-59 +class M_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// seconds 0-59 +class S_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// milliseconds +class e_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto millis = fmt_helper::time_fraction<std::chrono::milliseconds>(msg.time); + fmt_helper::pad3(static_cast<int>(millis.count()), dest); + } +}; + +// microseconds +class f_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto micros = fmt_helper::time_fraction<std::chrono::microseconds>(msg.time); + fmt_helper::pad6(static_cast<size_t>(micros.count()), dest); + } +}; + +// nanoseconds +class F_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto ns = fmt_helper::time_fraction<std::chrono::nanoseconds>(msg.time); + fmt::format_to(dest, "{:09}", ns.count()); + } +}; + +// seconds since epoch +class E_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + auto duration = msg.time.time_since_epoch(); + auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration).count(); + fmt_helper::append_int(seconds, dest); + } +}; + +// AM/PM +class p_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::append_c_str(ampm(tm_time), dest); + } +}; + +// 12 hour clock 02:55:02 pm +class r_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(to12h(tm_time), dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + dest.push_back(' '); + fmt_helper::append_c_str(ampm(tm_time), dest); + } +}; + +// 24-hour HH:MM time, equivalent to %H:%M +class R_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + } +}; + +// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S +class T_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + // fmt::format_to(dest, "{:02}:{:02}:{:02}", tm_time.tm_hour, + // tm_time.tm_min, tm_time.tm_sec); + fmt_helper::pad2(tm_time.tm_hour, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_min, dest); + dest.push_back(':'); + fmt_helper::pad2(tm_time.tm_sec, dest); + } +}; + +// ISO 8601 offset from UTC in timezone (+-HH:MM) +class z_formatter final : public flag_formatter +{ +public: + const std::chrono::seconds cache_refresh = std::chrono::seconds(5); + + z_formatter() = default; + z_formatter(const z_formatter &) = delete; + z_formatter &operator=(const z_formatter &) = delete; + + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override + { +#ifdef _WIN32 + int total_minutes = get_cached_offset(msg, tm_time); +#else + // No need to chache under gcc, + // it is very fast (already stored in tm.tm_gmtoff) + (void)(msg); + int total_minutes = os::utc_minutes_offset(tm_time); +#endif + bool is_negative = total_minutes < 0; + if (is_negative) + { + total_minutes = -total_minutes; + dest.push_back('-'); + } + else + { + dest.push_back('+'); + } + + fmt_helper::pad2(total_minutes / 60, dest); // hours + dest.push_back(':'); + fmt_helper::pad2(total_minutes % 60, dest); // minutes + } + +private: + log_clock::time_point last_update_{std::chrono::seconds(0)}; +#ifdef _WIN32 + int offset_minutes_{0}; + + int get_cached_offset(const log_msg &msg, const std::tm &tm_time) + { + if (msg.time - last_update_ >= cache_refresh) + { + offset_minutes_ = os::utc_minutes_offset(tm_time); + last_update_ = msg.time; + } + return offset_minutes_; + } +#endif +}; + +// Thread id +class t_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::pad6(msg.thread_id, dest); + } +}; + +// Current pid +class pid_formatter final : public flag_formatter +{ + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_int(details::os::pid(), dest); + } +}; + +// message counter formatter +class i_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::pad6(msg.msg_id, dest); + } +}; + +class v_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_buf(msg.raw, dest); + } +}; + +class ch_formatter final : public flag_formatter +{ +public: + explicit ch_formatter(char ch) + : ch_(ch) + { + } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + dest.push_back(ch_); + } + +private: + char ch_; +}; + +// aggregate user chars to display as is +class aggregate_formatter final : public flag_formatter +{ +public: + aggregate_formatter() = default; + + void add_ch(char ch) + { + str_ += ch; + } + void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override + { + fmt_helper::append_str(str_, dest); + } + +private: + std::string str_; +}; + +// mark the color range. expect it to be in the form of "%^colored text%$" +class color_start_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + msg.color_range_start = dest.size(); + } +}; +class color_stop_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override + { + msg.color_range_end = dest.size(); + } +}; + +// Full info formatter +// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v +class full_formatter final : public flag_formatter +{ + void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override + { + using std::chrono::duration_cast; + using std::chrono::milliseconds; + using std::chrono::seconds; + +#ifndef SPDLOG_NO_DATETIME + + // cache the date/time part for the next second. + auto duration = msg.time.time_since_epoch(); + auto secs = duration_cast<seconds>(duration); + + if (cache_timestamp_ != secs || cached_datetime_.size() == 0) + { + cached_datetime_.clear(); + cached_datetime_.push_back('['); + fmt_helper::append_int(tm_time.tm_year + 1900, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mon + 1, cached_datetime_); + cached_datetime_.push_back('-'); + + fmt_helper::pad2(tm_time.tm_mday, cached_datetime_); + cached_datetime_.push_back(' '); + + fmt_helper::pad2(tm_time.tm_hour, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_min, cached_datetime_); + cached_datetime_.push_back(':'); + + fmt_helper::pad2(tm_time.tm_sec, cached_datetime_); + cached_datetime_.push_back('.'); + + cache_timestamp_ = secs; + } + fmt_helper::append_buf(cached_datetime_, dest); + + auto millis = fmt_helper::time_fraction<milliseconds>(msg.time); + fmt_helper::pad3(static_cast<int>(millis.count()), dest); + dest.push_back(']'); + dest.push_back(' '); + +#else // no datetime needed + (void)tm_time; +#endif + +#ifndef SPDLOG_NO_NAME + if (!msg.logger_name->empty()) + { + dest.push_back('['); + fmt_helper::append_str(*msg.logger_name, dest); + dest.push_back(']'); + dest.push_back(' '); + } +#endif + + dest.push_back('['); + // wrap the level name with color + msg.color_range_start = dest.size(); + fmt_helper::append_c_str(level::to_c_str(msg.level), dest); + msg.color_range_end = dest.size(); + dest.push_back(']'); + dest.push_back(' '); + fmt_helper::append_buf(msg.raw, dest); + } + +private: + std::chrono::seconds cache_timestamp_{0}; + fmt::basic_memory_buffer<char, 128> cached_datetime_; +}; + +} // namespace details + +class pattern_formatter final : public formatter +{ +public: + explicit pattern_formatter( + std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol) + : pattern_(std::move(pattern)) + , eol_(std::move(eol)) + , pattern_time_type_(time_type) + , last_log_secs_(0) + { + std::memset(&cached_tm_, 0, sizeof(cached_tm_)); + compile_pattern_(pattern_); + } + + pattern_formatter(const pattern_formatter &other) = delete; + pattern_formatter &operator=(const pattern_formatter &other) = delete; + + std::unique_ptr<formatter> clone() const override + { + return details::make_unique<pattern_formatter>(pattern_, pattern_time_type_, eol_); + } + + void format(const details::log_msg &msg, fmt::memory_buffer &dest) override + { +#ifndef SPDLOG_NO_DATETIME + auto secs = std::chrono::duration_cast<std::chrono::seconds>(msg.time.time_since_epoch()); + if (secs != last_log_secs_) + { + cached_tm_ = get_time_(msg); + last_log_secs_ = secs; + } +#endif + for (auto &f : formatters_) + { + f->format(msg, cached_tm_, dest); + } + // write eol + details::fmt_helper::append_c_str(eol_.c_str(), dest); + } + +private: + std::string pattern_; + std::string eol_; + pattern_time_type pattern_time_type_; + std::tm cached_tm_; + std::chrono::seconds last_log_secs_; + + std::vector<std::unique_ptr<details::flag_formatter>> formatters_; + + std::tm get_time_(const details::log_msg &msg) + { + if (pattern_time_type_ == pattern_time_type::local) + { + return details::os::localtime(log_clock::to_time_t(msg.time)); + } + return details::os::gmtime(log_clock::to_time_t(msg.time)); + } + + void handle_flag_(char flag) + { + switch (flag) + { + // logger name + case 'n': + formatters_.push_back(details::make_unique<details::name_formatter>()); + break; + + case 'l': + formatters_.push_back(details::make_unique<details::level_formatter>()); + break; + + case 'L': + formatters_.push_back(details::make_unique<details::short_level_formatter>()); + break; + + case ('t'): + formatters_.push_back(details::make_unique<details::t_formatter>()); + break; + + case ('v'): + formatters_.push_back(details::make_unique<details::v_formatter>()); + break; + + case ('a'): + formatters_.push_back(details::make_unique<details::a_formatter>()); + break; + + case ('A'): + formatters_.push_back(details::make_unique<details::A_formatter>()); + break; + + case ('b'): + case ('h'): + formatters_.push_back(details::make_unique<details::b_formatter>()); + break; + + case ('B'): + formatters_.push_back(details::make_unique<details::B_formatter>()); + break; + case ('c'): + formatters_.push_back(details::make_unique<details::c_formatter>()); + break; + + case ('C'): + formatters_.push_back(details::make_unique<details::C_formatter>()); + break; + + case ('Y'): + formatters_.push_back(details::make_unique<details::Y_formatter>()); + break; + + case ('D'): + case ('x'): + formatters_.push_back(details::make_unique<details::D_formatter>()); + break; + + case ('m'): + formatters_.push_back(details::make_unique<details::m_formatter>()); + break; + + case ('d'): + formatters_.push_back(details::make_unique<details::d_formatter>()); + break; + + case ('H'): + formatters_.push_back(details::make_unique<details::H_formatter>()); + break; + + case ('I'): + formatters_.push_back(details::make_unique<details::I_formatter>()); + break; + + case ('M'): + formatters_.push_back(details::make_unique<details::M_formatter>()); + break; + + case ('S'): + formatters_.push_back(details::make_unique<details::S_formatter>()); + break; + + case ('e'): + formatters_.push_back(details::make_unique<details::e_formatter>()); + break; + + case ('f'): + formatters_.push_back(details::make_unique<details::f_formatter>()); + break; + case ('F'): + formatters_.push_back(details::make_unique<details::F_formatter>()); + break; + + case ('E'): + formatters_.push_back(details::make_unique<details::E_formatter>()); + break; + + case ('p'): + formatters_.push_back(details::make_unique<details::p_formatter>()); + break; + + case ('r'): + formatters_.push_back(details::make_unique<details::r_formatter>()); + break; + + case ('R'): + formatters_.push_back(details::make_unique<details::R_formatter>()); + break; + + case ('T'): + case ('X'): + formatters_.push_back(details::make_unique<details::T_formatter>()); + break; + + case ('z'): + formatters_.push_back(details::make_unique<details::z_formatter>()); + break; + + case ('+'): + formatters_.push_back(details::make_unique<details::full_formatter>()); + break; + + case ('P'): + formatters_.push_back(details::make_unique<details::pid_formatter>()); + break; +#ifdef SPDLOG_ENABLE_MESSAGE_COUNTER + case ('i'): + formatters_.push_back(details::make_unique<details::i_formatter>()); + break; +#endif + case ('^'): + formatters_.push_back(details::make_unique<details::color_start_formatter>()); + break; + + case ('$'): + formatters_.push_back(details::make_unique<details::color_stop_formatter>()); + break; + + default: // Unknown flag appears as is + formatters_.push_back(details::make_unique<details::ch_formatter>('%')); + formatters_.push_back(details::make_unique<details::ch_formatter>(flag)); + break; + } + } + + void compile_pattern_(const std::string &pattern) + { + auto end = pattern.end(); + std::unique_ptr<details::aggregate_formatter> user_chars; + formatters_.clear(); + for (auto it = pattern.begin(); it != end; ++it) + { + if (*it == '%') + { + if (user_chars) // append user chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + if (++it != end) + { + handle_flag_(*it); + } + else + { + break; + } + } + else // chars not following the % sign should be displayed as is + { + if (!user_chars) + { + user_chars = details::make_unique<details::aggregate_formatter>(); + } + user_chars->add_ch(*it); + } + } + if (user_chars) // append raw chars found so far + { + formatters_.push_back(std::move(user_chars)); + } + } +}; +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/periodic_worker.h b/src/nmodl/ext/spdlog/details/periodic_worker.h new file mode 100644 index 0000000000..57e5fa7717 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/periodic_worker.h @@ -0,0 +1,71 @@ + +// +// Copyright(c) 2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// periodic worker thread - periodically executes the given callback function. +// +// RAII over the owned thread: +// creates the thread on construction. +// stops and joins the thread on destruction. + +#include <chrono> +#include <condition_variable> +#include <functional> +#include <mutex> +#include <thread> +namespace spdlog { +namespace details { + +class periodic_worker +{ +public: + periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval) + { + active_ = (interval > std::chrono::seconds::zero()); + if (!active_) + { + return; + } + + worker_thread_ = std::thread([this, callback_fun, interval]() { + for (;;) + { + std::unique_lock<std::mutex> lock(this->mutex_); + if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) + { + return; // active_ == false, so exit this thread + } + callback_fun(); + } + }); + } + + periodic_worker(const periodic_worker &) = delete; + periodic_worker &operator=(const periodic_worker &) = delete; + + // stop the worker thread and join it + ~periodic_worker() + { + if (worker_thread_.joinable()) + { + { + std::lock_guard<std::mutex> lock(mutex_); + active_ = false; + } + cv_.notify_one(); + worker_thread_.join(); + } + } + +private: + bool active_; + std::thread worker_thread_; + std::mutex mutex_; + std::condition_variable cv_; +}; +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/registry.h b/src/nmodl/ext/spdlog/details/registry.h new file mode 100644 index 0000000000..439395f704 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/registry.h @@ -0,0 +1,275 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Loggers registy of unique name->logger pointer +// An attempt to create a logger with an already existing name will be ignored +// If user requests a non existing logger, nullptr will be returned +// This class is thread safe + +#include "spdlog/common.h" +#include "spdlog/details/periodic_worker.h" +#include "spdlog/logger.h" + +#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER +// support for the default stdout color logger +#ifdef _WIN32 +#include "spdlog/sinks/wincolor_sink.h" +#else +#include "spdlog/sinks/ansicolor_sink.h" +#endif +#endif // SPDLOG_DISABLE_DEFAULT_LOGGER + +#include <chrono> +#include <functional> +#include <memory> +#include <string> +#include <unordered_map> + +namespace spdlog { +namespace details { +class thread_pool; + +class registry +{ +public: + registry(const registry &) = delete; + registry &operator=(const registry &) = delete; + + void register_logger(std::shared_ptr<logger> new_logger) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + auto logger_name = new_logger->name(); + throw_if_exists_(logger_name); + loggers_[logger_name] = std::move(new_logger); + } + + void register_and_init(std::shared_ptr<logger> new_logger) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + auto logger_name = new_logger->name(); + throw_if_exists_(logger_name); + + // set the global formatter pattern + new_logger->set_formatter(formatter_->clone()); + + if (err_handler_) + { + new_logger->set_error_handler(err_handler_); + } + + new_logger->set_level(level_); + new_logger->flush_on(flush_level_); + + // add to registry + loggers_[logger_name] = std::move(new_logger); + } + + std::shared_ptr<logger> get(const std::string &logger_name) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + auto found = loggers_.find(logger_name); + return found == loggers_.end() ? nullptr : found->second; + } + + std::shared_ptr<logger> default_logger() + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + return default_logger_; + } + + // Return raw ptr to the default logger. + // To be used directly by the spdlog default api (e.g. spdlog::info) + // This make the default API faster, but cannot be used concurrently with set_default_logger(). + // e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. + logger *get_default_raw() + { + return default_logger_.get(); + } + + // set default logger. + // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. + void set_default_logger(std::shared_ptr<logger> new_default_logger) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + // remove previous default logger from the map + if (default_logger_ != nullptr) + { + loggers_.erase(default_logger_->name()); + } + if (new_default_logger != nullptr) + { + loggers_[new_default_logger->name()] = new_default_logger; + } + default_logger_ = std::move(new_default_logger); + } + + void set_tp(std::shared_ptr<thread_pool> tp) + { + std::lock_guard<std::recursive_mutex> lock(tp_mutex_); + tp_ = std::move(tp); + } + + std::shared_ptr<thread_pool> get_tp() + { + std::lock_guard<std::recursive_mutex> lock(tp_mutex_); + return tp_; + } + + // Set global formatter. Each sink in each logger will get a clone of this object + void set_formatter(std::unique_ptr<formatter> formatter) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + formatter_ = std::move(formatter); + for (auto &l : loggers_) + { + l.second->set_formatter(formatter_->clone()); + } + } + + void set_level(level::level_enum log_level) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->set_level(log_level); + } + level_ = log_level; + } + + void flush_on(level::level_enum log_level) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->flush_on(log_level); + } + flush_level_ = log_level; + } + + void flush_every(std::chrono::seconds interval) + { + std::lock_guard<std::mutex> lock(flusher_mutex_); + std::function<void()> clbk = std::bind(®istry::flush_all, this); + periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval); + } + + void set_error_handler(log_err_handler handler) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->set_error_handler(handler); + } + err_handler_ = handler; + } + + void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + for (auto &l : loggers_) + { + fun(l.second); + } + } + + void flush_all() + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + for (auto &l : loggers_) + { + l.second->flush(); + } + } + + void drop(const std::string &logger_name) + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + loggers_.erase(logger_name); + if (default_logger_ && default_logger_->name() == logger_name) + { + default_logger_.reset(); + } + } + + void drop_all() + { + std::lock_guard<std::mutex> lock(logger_map_mutex_); + loggers_.clear(); + default_logger_.reset(); + } + + // clean all resources and threads started by the registry + void shutdown() + { + { + std::lock_guard<std::mutex> lock(flusher_mutex_); + periodic_flusher_.reset(); + } + + drop_all(); + + { + std::lock_guard<std::recursive_mutex> lock(tp_mutex_); + tp_.reset(); + } + } + + std::recursive_mutex &tp_mutex() + { + return tp_mutex_; + } + + static registry &instance() + { + static registry s_instance; + return s_instance; + } + +private: + registry() + : formatter_(new pattern_formatter("%+")) + { + +#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER + // create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows). +#ifdef _WIN32 + auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>(); +#else + auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>(); +#endif + + const char *default_logger_name = ""; + default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink)); + loggers_[default_logger_name] = default_logger_; + +#endif // SPDLOG_DISABLE_DEFAULT_LOGGER + } + + ~registry() = default; + + void throw_if_exists_(const std::string &logger_name) + { + if (loggers_.find(logger_name) != loggers_.end()) + { + throw spdlog_ex("logger with name '" + logger_name + "' already exists"); + } + } + + std::mutex logger_map_mutex_, flusher_mutex_; + std::recursive_mutex tp_mutex_; + std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; + std::unique_ptr<formatter> formatter_; + level::level_enum level_ = level::info; + level::level_enum flush_level_ = level::off; + log_err_handler err_handler_; + std::shared_ptr<thread_pool> tp_; + std::unique_ptr<periodic_worker> periodic_flusher_; + std::shared_ptr<logger> default_logger_; +}; + +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/thread_pool.h b/src/nmodl/ext/spdlog/details/thread_pool.h new file mode 100644 index 0000000000..0c716c3b22 --- /dev/null +++ b/src/nmodl/ext/spdlog/details/thread_pool.h @@ -0,0 +1,228 @@ +#pragma once + +#include "spdlog/details/log_msg.h" +#include "spdlog/details/mpmc_blocking_q.h" +#include "spdlog/details/os.h" + +#include <chrono> +#include <memory> +#include <thread> +#include <vector> + +namespace spdlog { +namespace details { + +using async_logger_ptr = std::shared_ptr<spdlog::async_logger>; + +enum class async_msg_type +{ + log, + flush, + terminate +}; + +// Async msg to move to/from the queue +// Movable only. should never be copied +struct async_msg +{ + async_msg_type msg_type; + level::level_enum level; + log_clock::time_point time; + size_t thread_id; + fmt::basic_memory_buffer<char, 176> raw; + + size_t msg_id; + async_logger_ptr worker_ptr; + + async_msg() = default; + ~async_msg() = default; + + // should only be moved in or out of the queue.. + async_msg(const async_msg &) = delete; + +// support for vs2013 move +#if defined(_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&other) SPDLOG_NOEXCEPT : msg_type(other.msg_type), + level(other.level), + time(other.time), + thread_id(other.thread_id), + raw(move(other.raw)), + msg_id(other.msg_id), + worker_ptr(std::move(other.worker_ptr)) + { + } + + async_msg &operator=(async_msg &&other) SPDLOG_NOEXCEPT + { + msg_type = other.msg_type; + level = other.level; + time = other.time; + thread_id = other.thread_id; + raw = std::move(other.raw); + msg_id = other.msg_id; + worker_ptr = std::move(other.worker_ptr); + return *this; + } +#else // (_MSC_VER) && _MSC_VER <= 1800 + async_msg(async_msg &&) = default; + async_msg &operator=(async_msg &&) = default; +#endif + + // construct from log_msg with given type + async_msg(async_logger_ptr &&worker, async_msg_type the_type, details::log_msg &&m) + : msg_type(the_type) + , level(m.level) + , time(m.time) + , thread_id(m.thread_id) + , msg_id(m.msg_id) + , worker_ptr(std::move(worker)) + { + fmt_helper::append_buf(m.raw, raw); + } + + async_msg(async_logger_ptr &&worker, async_msg_type the_type) + : async_msg(std::move(worker), the_type, details::log_msg()) + { + } + + explicit async_msg(async_msg_type the_type) + : async_msg(nullptr, the_type, details::log_msg()) + { + } + + // copy into log_msg + void to_log_msg(log_msg &msg) + { + msg.logger_name = &worker_ptr->name(); + msg.level = level; + msg.time = time; + msg.thread_id = thread_id; + fmt_helper::append_buf(raw, msg.raw); + msg.msg_id = msg_id; + msg.color_range_start = 0; + msg.color_range_end = 0; + } +}; + +class thread_pool +{ +public: + using item_type = async_msg; + using q_type = details::mpmc_blocking_queue<item_type>; + + thread_pool(size_t q_max_items, size_t threads_n) + : q_(q_max_items) + { + // std::cout << "thread_pool() q_size_bytes: " << q_size_bytes << + // "\tthreads_n: " << threads_n << std::endl; + if (threads_n == 0 || threads_n > 1000) + { + throw spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " + "range is 1-1000)"); + } + for (size_t i = 0; i < threads_n; i++) + { + threads_.emplace_back(&thread_pool::worker_loop_, this); + } + } + + // message all threads to terminate gracefully join them + ~thread_pool() + { + try + { + for (size_t i = 0; i < threads_.size(); i++) + { + post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); + } + + for (auto &t : threads_) + { + t.join(); + } + } + catch (...) + { + } + } + + thread_pool(const thread_pool &) = delete; + thread_pool &operator=(thread_pool &&) = delete; + + void post_log(async_logger_ptr &&worker_ptr, details::log_msg &&msg, async_overflow_policy overflow_policy) + { + async_msg async_m(std::move(worker_ptr), async_msg_type::log, std::move(msg)); + post_async_msg_(std::move(async_m), overflow_policy); + } + + void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy) + { + post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); + } + + size_t overrun_counter() + { + return q_.overrun_counter(); + } + +private: + q_type q_; + + std::vector<std::thread> threads_; + + void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) + { + if (overflow_policy == async_overflow_policy::block) + { + q_.enqueue(std::move(new_msg)); + } + else + { + q_.enqueue_nowait(std::move(new_msg)); + } + } + + void worker_loop_() + { + while (process_next_msg_()) {}; + } + + // process next message in the queue + // return true if this thread should still be active (while no terminate msg + // was received) + bool process_next_msg_() + { + async_msg incoming_async_msg; + bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); + if (!dequeued) + { + return true; + } + + switch (incoming_async_msg.msg_type) + { + case async_msg_type::log: + { + log_msg msg; + incoming_async_msg.to_log_msg(msg); + incoming_async_msg.worker_ptr->backend_log_(msg); + return true; + } + case async_msg_type::flush: + { + incoming_async_msg.worker_ptr->backend_flush_(); + return true; + } + + case async_msg_type::terminate: + { + return false; + } + } + assert(false && "Unexpected async_msg_type"); + return true; + } +}; + +} // namespace details +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/fmt/bin_to_hex.h b/src/nmodl/ext/spdlog/fmt/bin_to_hex.h new file mode 100644 index 0000000000..3523380204 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bin_to_hex.h @@ -0,0 +1,172 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Support for logging binary data as hex +// format flags: +// {:X} - print in uppercase. +// {:s} - don't separate each byte with space. +// {:p} - don't print the position on each line start. +// {:n} - don't split the output to lines. + +// +// Examples: +// +// std::vector<char> v(200, 0x0b); +// logger->info("Some buffer {}", spdlog::to_hex(v)); +// char buf[128]; +// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); + +namespace spdlog { +namespace details { + +template<typename It> +class bytes_range +{ +public: + bytes_range(It range_begin, It range_end) + : begin_(range_begin) + , end_(range_end) + { + } + + It begin() const + { + return begin_; + } + It end() const + { + return end_; + } + +private: + It begin_, end_; +}; +} // namespace details + +// create a bytes_range that wraps the given container +template<typename Container> +inline details::bytes_range<typename Container::const_iterator> to_hex(const Container &container) +{ + static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); + using Iter = typename Container::const_iterator; + return details::bytes_range<Iter>(std::begin(container), std::end(container)); +} + +// create bytes_range from ranges +template<typename It> +inline details::bytes_range<It> to_hex(const It range_begin, const It range_end) +{ + return details::bytes_range<It>(range_begin, range_end); +} + +} // namespace spdlog + +namespace fmt { + +template<typename T> +struct formatter<spdlog::details::bytes_range<T>> +{ + const std::size_t line_size = 100; + const char delimiter = ' '; + + bool put_newlines = true; + bool put_delimiters = true; + bool use_uppercase = false; + bool put_positions = true; // position on start of each line + + // parse the format string flags + template<typename ParseContext> + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) + { + auto it = ctx.begin(); + while (*it && *it != '}') + { + switch (*it) + { + case 'X': + use_uppercase = true; + break; + case 's': + put_delimiters = false; + break; + case 'p': + put_positions = false; + break; + case 'n': + put_newlines = false; + break; + } + + ++it; + } + return it; + } + + // format the given bytes range as hex + template<typename FormatContext, typename Container> + auto format(const spdlog::details::bytes_range<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out()) + { + SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; + SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; + const char *hex_chars = use_uppercase ? hex_upper : hex_lower; + + std::size_t pos = 0; + std::size_t column = line_size; + auto inserter = ctx.begin(); + + for (auto &item : the_range) + { + auto ch = static_cast<unsigned char>(item); + pos++; + + if (put_newlines && column >= line_size) + { + column = put_newline(inserter, pos); + + // put first byte without delimiter in front of it + *inserter++ = hex_chars[(ch >> 4) & 0x0f]; + *inserter++ = hex_chars[ch & 0x0f]; + column += 2; + continue; + } + + if (put_delimiters) + { + *inserter++ = delimiter; + ++column; + } + + *inserter++ = hex_chars[(ch >> 4) & 0x0f]; + *inserter++ = hex_chars[ch & 0x0f]; + column += 2; + } + return inserter; + } + + // put newline(and position header) + // return the next column + template<typename It> + std::size_t put_newline(It inserter, std::size_t pos) + { +#ifdef _WIN32 + *inserter++ = '\r'; +#endif + *inserter++ = '\n'; + + if (put_positions) + { + fmt::format_to(inserter, "{:<04X}: ", pos - 1); + return 7; + } + else + { + return 1; + } + } +}; +} // namespace fmt diff --git a/src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst b/src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst new file mode 100644 index 0000000000..eb6be6503e --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst @@ -0,0 +1,23 @@ +Copyright (c) 2012 - 2016, Victor Zverovich + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/nmodl/ext/spdlog/fmt/bundled/colors.h b/src/nmodl/ext/spdlog/fmt/bundled/colors.h new file mode 100644 index 0000000000..003a6679ee --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/colors.h @@ -0,0 +1,257 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for rgb color output. + +#ifndef FMT_COLORS_H_ +#define FMT_COLORS_H_ + +#include "format.h" + +FMT_BEGIN_NAMESPACE + +// rgb is a struct for red, green and blue colors. +// We use rgb as name because some editors will show it as color direct in the +// editor. +struct rgb +{ + FMT_CONSTEXPR_DECL rgb() + : r(0) + , g(0) + , b(0) + { + } + FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_) + : r(r_) + , g(g_) + , b(b_) + { + } + FMT_CONSTEXPR_DECL rgb(uint32_t hex) + : r((hex >> 16) & 0xFF) + , g((hex >> 8) & 0xFF) + , b((hex)&0xFF) + { + } + uint8_t r; + uint8_t g; + uint8_t b; +}; + +namespace internal { + +FMT_CONSTEXPR inline void to_esc(uint8_t c, char out[], int offset) +{ + out[offset + 0] = static_cast<char>('0' + c / 100); + out[offset + 1] = static_cast<char>('0' + c / 10 % 10); + out[offset + 2] = static_cast<char>('0' + c % 10); +} + +} // namespace internal + +FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; + static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + std::fputs(escape_fd, stdout); + vprint(format, args); + std::fputs(RESET_COLOR, stdout); +} + +FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) +{ + char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color + char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color + static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; + internal::to_esc(fd.r, escape_fd, 7); + internal::to_esc(fd.g, escape_fd, 11); + internal::to_esc(fd.b, escape_fd, 15); + + internal::to_esc(bg.r, escape_bg, 7); + internal::to_esc(bg.g, escape_bg, 11); + internal::to_esc(bg.b, escape_bg, 15); + + std::fputs(escape_fd, stdout); + std::fputs(escape_bg, stdout); + vprint(format, args); + std::fputs(RESET_COLOR, stdout); +} + +template<typename... Args> +inline void print_rgb(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +// rgb foreground color +template<typename... Args> +inline void print(rgb fd, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, format_str, make_format_args(args...)); +} + +// rgb foreground color and background color +template<typename... Args> +inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args) +{ + vprint_rgb(fd, bg, format_str, make_format_args(args...)); +} + +enum class color : uint32_t +{ + alice_blue = 0xF0F8FF, // rgb(240,248,255) + antique_white = 0xFAEBD7, // rgb(250,235,215) + aqua = 0x00FFFF, // rgb(0,255,255) + aquamarine = 0x7FFFD4, // rgb(127,255,212) + azure = 0xF0FFFF, // rgb(240,255,255) + beige = 0xF5F5DC, // rgb(245,245,220) + bisque = 0xFFE4C4, // rgb(255,228,196) + black = 0x000000, // rgb(0,0,0) + blanched_almond = 0xFFEBCD, // rgb(255,235,205) + blue = 0x0000FF, // rgb(0,0,255) + blue_violet = 0x8A2BE2, // rgb(138,43,226) + brown = 0xA52A2A, // rgb(165,42,42) + burly_wood = 0xDEB887, // rgb(222,184,135) + cadet_blue = 0x5F9EA0, // rgb(95,158,160) + chartreuse = 0x7FFF00, // rgb(127,255,0) + chocolate = 0xD2691E, // rgb(210,105,30) + coral = 0xFF7F50, // rgb(255,127,80) + cornflower_blue = 0x6495ED, // rgb(100,149,237) + cornsilk = 0xFFF8DC, // rgb(255,248,220) + crimson = 0xDC143C, // rgb(220,20,60) + cyan = 0x00FFFF, // rgb(0,255,255) + dark_blue = 0x00008B, // rgb(0,0,139) + dark_cyan = 0x008B8B, // rgb(0,139,139) + dark_golden_rod = 0xB8860B, // rgb(184,134,11) + dark_gray = 0xA9A9A9, // rgb(169,169,169) + dark_green = 0x006400, // rgb(0,100,0) + dark_khaki = 0xBDB76B, // rgb(189,183,107) + dark_magenta = 0x8B008B, // rgb(139,0,139) + dark_olive_green = 0x556B2F, // rgb(85,107,47) + dark_orange = 0xFF8C00, // rgb(255,140,0) + dark_orchid = 0x9932CC, // rgb(153,50,204) + dark_red = 0x8B0000, // rgb(139,0,0) + dark_salmon = 0xE9967A, // rgb(233,150,122) + dark_sea_green = 0x8FBC8F, // rgb(143,188,143) + dark_slate_blue = 0x483D8B, // rgb(72,61,139) + dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) + dark_turquoise = 0x00CED1, // rgb(0,206,209) + dark_violet = 0x9400D3, // rgb(148,0,211) + deep_pink = 0xFF1493, // rgb(255,20,147) + deep_sky_blue = 0x00BFFF, // rgb(0,191,255) + dim_gray = 0x696969, // rgb(105,105,105) + dodger_blue = 0x1E90FF, // rgb(30,144,255) + fire_brick = 0xB22222, // rgb(178,34,34) + floral_white = 0xFFFAF0, // rgb(255,250,240) + forest_green = 0x228B22, // rgb(34,139,34) + fuchsia = 0xFF00FF, // rgb(255,0,255) + gainsboro = 0xDCDCDC, // rgb(220,220,220) + ghost_white = 0xF8F8FF, // rgb(248,248,255) + gold = 0xFFD700, // rgb(255,215,0) + golden_rod = 0xDAA520, // rgb(218,165,32) + gray = 0x808080, // rgb(128,128,128) + green = 0x008000, // rgb(0,128,0) + green_yellow = 0xADFF2F, // rgb(173,255,47) + honey_dew = 0xF0FFF0, // rgb(240,255,240) + hot_pink = 0xFF69B4, // rgb(255,105,180) + indian_red = 0xCD5C5C, // rgb(205,92,92) + indigo = 0x4B0082, // rgb(75,0,130) + ivory = 0xFFFFF0, // rgb(255,255,240) + khaki = 0xF0E68C, // rgb(240,230,140) + lavender = 0xE6E6FA, // rgb(230,230,250) + lavender_blush = 0xFFF0F5, // rgb(255,240,245) + lawn_green = 0x7CFC00, // rgb(124,252,0) + lemon_chiffon = 0xFFFACD, // rgb(255,250,205) + light_blue = 0xADD8E6, // rgb(173,216,230) + light_coral = 0xF08080, // rgb(240,128,128) + light_cyan = 0xE0FFFF, // rgb(224,255,255) + light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) + light_gray = 0xD3D3D3, // rgb(211,211,211) + light_green = 0x90EE90, // rgb(144,238,144) + light_pink = 0xFFB6C1, // rgb(255,182,193) + light_salmon = 0xFFA07A, // rgb(255,160,122) + light_sea_green = 0x20B2AA, // rgb(32,178,170) + light_sky_blue = 0x87CEFA, // rgb(135,206,250) + light_slate_gray = 0x778899, // rgb(119,136,153) + light_steel_blue = 0xB0C4DE, // rgb(176,196,222) + light_yellow = 0xFFFFE0, // rgb(255,255,224) + lime = 0x00FF00, // rgb(0,255,0) + lime_green = 0x32CD32, // rgb(50,205,50) + linen = 0xFAF0E6, // rgb(250,240,230) + magenta = 0xFF00FF, // rgb(255,0,255) + maroon = 0x800000, // rgb(128,0,0) + medium_aqua_marine = 0x66CDAA, // rgb(102,205,170) + medium_blue = 0x0000CD, // rgb(0,0,205) + medium_orchid = 0xBA55D3, // rgb(186,85,211) + medium_purple = 0x9370DB, // rgb(147,112,219) + medium_sea_green = 0x3CB371, // rgb(60,179,113) + medium_slate_blue = 0x7B68EE, // rgb(123,104,238) + medium_spring_green = 0x00FA9A, // rgb(0,250,154) + medium_turquoise = 0x48D1CC, // rgb(72,209,204) + medium_violet_red = 0xC71585, // rgb(199,21,133) + midnight_blue = 0x191970, // rgb(25,25,112) + mint_cream = 0xF5FFFA, // rgb(245,255,250) + misty_rose = 0xFFE4E1, // rgb(255,228,225) + moccasin = 0xFFE4B5, // rgb(255,228,181) + navajo_white = 0xFFDEAD, // rgb(255,222,173) + navy = 0x000080, // rgb(0,0,128) + old_lace = 0xFDF5E6, // rgb(253,245,230) + olive = 0x808000, // rgb(128,128,0) + olive_drab = 0x6B8E23, // rgb(107,142,35) + orange = 0xFFA500, // rgb(255,165,0) + orange_red = 0xFF4500, // rgb(255,69,0) + orchid = 0xDA70D6, // rgb(218,112,214) + pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) + pale_green = 0x98FB98, // rgb(152,251,152) + pale_turquoise = 0xAFEEEE, // rgb(175,238,238) + pale_violet_red = 0xDB7093, // rgb(219,112,147) + papaya_whip = 0xFFEFD5, // rgb(255,239,213) + peach_puff = 0xFFDAB9, // rgb(255,218,185) + peru = 0xCD853F, // rgb(205,133,63) + pink = 0xFFC0CB, // rgb(255,192,203) + plum = 0xDDA0DD, // rgb(221,160,221) + powder_blue = 0xB0E0E6, // rgb(176,224,230) + purple = 0x800080, // rgb(128,0,128) + rebecca_purple = 0x663399, // rgb(102,51,153) + red = 0xFF0000, // rgb(255,0,0) + rosy_brown = 0xBC8F8F, // rgb(188,143,143) + royal_blue = 0x4169E1, // rgb(65,105,225) + saddle_brown = 0x8B4513, // rgb(139,69,19) + salmon = 0xFA8072, // rgb(250,128,114) + sandy_brown = 0xF4A460, // rgb(244,164,96) + sea_green = 0x2E8B57, // rgb(46,139,87) + sea_shell = 0xFFF5EE, // rgb(255,245,238) + sienna = 0xA0522D, // rgb(160,82,45) + silver = 0xC0C0C0, // rgb(192,192,192) + sky_blue = 0x87CEEB, // rgb(135,206,235) + slate_blue = 0x6A5ACD, // rgb(106,90,205) + slate_gray = 0x708090, // rgb(112,128,144) + snow = 0xFFFAFA, // rgb(255,250,250) + spring_green = 0x00FF7F, // rgb(0,255,127) + steel_blue = 0x4682B4, // rgb(70,130,180) + tan = 0xD2B48C, // rgb(210,180,140) + teal = 0x008080, // rgb(0,128,128) + thistle = 0xD8BFD8, // rgb(216,191,216) + tomato = 0xFF6347, // rgb(255,99,71) + turquoise = 0x40E0D0, // rgb(64,224,208) + violet = 0xEE82EE, // rgb(238,130,238) + wheat = 0xF5DEB3, // rgb(245,222,179) + white = 0xFFFFFF, // rgb(255,255,255) + white_smoke = 0xF5F5F5, // rgb(245,245,245) + yellow = 0xFFFF00, // rgb(255,255,0) + yellow_green = 0x9ACD32, // rgb(154,205,50) +}; // enum class colors + +FMT_END_NAMESPACE + +#endif // FMT_COLORS_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/core.h b/src/nmodl/ext/spdlog/fmt/bundled/core.h new file mode 100644 index 0000000000..5912afef82 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/core.h @@ -0,0 +1,1502 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_CORE_H_ +#define FMT_CORE_H_ + +#include <cassert> +#include <cstdio> // std::FILE +#include <cstring> +#include <iterator> +#include <string> +#include <type_traits> + +// The fmt library version in the form major * 10000 + minor * 100 + patch. +#define FMT_VERSION 50201 + +#ifdef __has_feature +# define FMT_HAS_FEATURE(x) __has_feature(x) +#else +# define FMT_HAS_FEATURE(x) 0 +#endif + +#if defined(__has_include) && !defined(__INTELLISENSE__) && \ + (!defined(__INTEL_COMPILER) || __INTEL_COMPILER >= 1600) +# define FMT_HAS_INCLUDE(x) __has_include(x) +#else +# define FMT_HAS_INCLUDE(x) 0 +#endif + +#ifdef __has_cpp_attribute +# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) +#else +# define FMT_HAS_CPP_ATTRIBUTE(x) 0 +#endif + +#if defined(__GNUC__) && !defined(__clang__) +# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) +#else +# define FMT_GCC_VERSION 0 +#endif + +#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION +#else +# define FMT_HAS_GXX_CXX11 0 +#endif + +#ifdef _MSC_VER +# define FMT_MSC_VER _MSC_VER +#else +# define FMT_MSC_VER 0 +#endif + +// Check if relaxed C++14 constexpr is supported. +// GCC doesn't allow throw in constexpr until version 6 (bug 67371). +#ifndef FMT_USE_CONSTEXPR +# define FMT_USE_CONSTEXPR \ + (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ + (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) +#endif +#if FMT_USE_CONSTEXPR +# define FMT_CONSTEXPR constexpr +# define FMT_CONSTEXPR_DECL constexpr +#else +# define FMT_CONSTEXPR inline +# define FMT_CONSTEXPR_DECL +#endif + +#ifndef FMT_USE_CONSTEXPR11 +# define FMT_USE_CONSTEXPR11 \ + (FMT_MSC_VER >= 1900 || FMT_GCC_VERSION >= 406 || FMT_USE_CONSTEXPR) +#endif +#if FMT_USE_CONSTEXPR11 +# define FMT_CONSTEXPR11 constexpr +#else +# define FMT_CONSTEXPR11 +#endif + +#ifndef FMT_OVERRIDE +# if FMT_HAS_FEATURE(cxx_override) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +# define FMT_OVERRIDE override +# else +# define FMT_OVERRIDE +# endif +#endif + +#if FMT_HAS_FEATURE(cxx_explicit_conversions) || FMT_MSC_VER >= 1800 +# define FMT_EXPLICIT explicit +#else +# define FMT_EXPLICIT +#endif + +#ifndef FMT_NULL +# if FMT_HAS_FEATURE(cxx_nullptr) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600 +# define FMT_NULL nullptr +# define FMT_USE_NULLPTR 1 +# else +# define FMT_NULL NULL +# endif +#endif + +#ifndef FMT_USE_NULLPTR +# define FMT_USE_NULLPTR 0 +#endif + +#if FMT_HAS_CPP_ATTRIBUTE(noreturn) +# define FMT_NORETURN [[noreturn]] +#else +# define FMT_NORETURN +#endif + +// Check if exceptions are disabled. +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +# define FMT_EXCEPTIONS 0 +#elif FMT_MSC_VER && !_HAS_EXCEPTIONS +# define FMT_EXCEPTIONS 0 +#endif +#ifndef FMT_EXCEPTIONS +# define FMT_EXCEPTIONS 1 +#endif + +// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). +#ifndef FMT_USE_NOEXCEPT +# define FMT_USE_NOEXCEPT 0 +#endif + +#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ + (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 +# define FMT_DETECTED_NOEXCEPT noexcept +# define FMT_HAS_CXX11_NOEXCEPT 1 +#else +# define FMT_DETECTED_NOEXCEPT throw() +# define FMT_HAS_CXX11_NOEXCEPT 0 +#endif + +#ifndef FMT_NOEXCEPT +# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT +# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT +# else +# define FMT_NOEXCEPT +# endif +#endif + +// This is needed because GCC still uses throw() in its headers when exceptions +// are disabled. +#if FMT_GCC_VERSION +# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT +#else +# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT +#endif + +#ifndef FMT_BEGIN_NAMESPACE +# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ + FMT_MSC_VER >= 1900 +# define FMT_INLINE_NAMESPACE inline namespace +# define FMT_END_NAMESPACE }} +# else +# define FMT_INLINE_NAMESPACE namespace +# define FMT_END_NAMESPACE } using namespace v5; } +# endif +# define FMT_BEGIN_NAMESPACE namespace fmt { FMT_INLINE_NAMESPACE v5 { +#endif + +#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) +# ifdef FMT_EXPORT +# define FMT_API __declspec(dllexport) +# elif defined(FMT_SHARED) +# define FMT_API __declspec(dllimport) +# endif +#endif +#ifndef FMT_API +# define FMT_API +#endif + +#ifndef FMT_ASSERT +# define FMT_ASSERT(condition, message) assert((condition) && message) +#endif + +// libc++ supports string_view in pre-c++17. +#if (FMT_HAS_INCLUDE(<string_view>) && \ + (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ + (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) +# include <string_view> +# define FMT_USE_STD_STRING_VIEW +#elif (FMT_HAS_INCLUDE(<experimental/string_view>) && \ + __cplusplus >= 201402L) +# include <experimental/string_view> +# define FMT_USE_EXPERIMENTAL_STRING_VIEW +#endif + +// std::result_of is defined in <functional> in gcc 4.4. +#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 +# include <functional> +#endif + +FMT_BEGIN_NAMESPACE +namespace internal { + +// An implementation of declval for pre-C++11 compilers such as gcc 4. +template <typename T> +typename std::add_rvalue_reference<T>::type declval() FMT_NOEXCEPT; + +template <typename> +struct result_of; + +template <typename F, typename... Args> +struct result_of<F(Args...)> { + // A workaround for gcc 4.4 that doesn't allow F to be a reference. + typedef typename std::result_of< + typename std::remove_reference<F>::type(Args...)>::type type; +}; + +// Casts nonnegative integer to unsigned. +template <typename Int> +FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) { + FMT_ASSERT(value >= 0, "negative value"); + return static_cast<typename std::make_unsigned<Int>::type>(value); +} + +// A constexpr std::char_traits::length replacement for pre-C++17. +template <typename Char> +FMT_CONSTEXPR size_t length(const Char *s) { + const Char *start = s; + while (*s) ++s; + return s - start; +} +#if FMT_GCC_VERSION +FMT_CONSTEXPR size_t length(const char *s) { return std::strlen(s); } +#endif +} // namespace internal + +/** + An implementation of ``std::basic_string_view`` for pre-C++17. It provides a + subset of the API. ``fmt::basic_string_view`` is used for format strings even + if ``std::string_view`` is available to prevent issues when a library is + compiled with a different ``-std`` option than the client code (which is not + recommended). + */ +template <typename Char> +class basic_string_view { + private: + const Char *data_; + size_t size_; + + public: + typedef Char char_type; + typedef const Char *iterator; + + // Standard basic_string_view type. +#if defined(FMT_USE_STD_STRING_VIEW) + typedef std::basic_string_view<Char> type; +#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) + typedef std::experimental::basic_string_view<Char> type; +#else + struct type { + const char *data() const { return FMT_NULL; } + size_t size() const { return 0; } + }; +#endif + + FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(FMT_NULL), size_(0) {} + + /** Constructs a string reference object from a C string and a size. */ + FMT_CONSTEXPR basic_string_view(const Char *s, size_t count) FMT_NOEXCEPT + : data_(s), size_(count) {} + + /** + \rst + Constructs a string reference object from a C string computing + the size with ``std::char_traits<Char>::length``. + \endrst + */ + FMT_CONSTEXPR basic_string_view(const Char *s) + : data_(s), size_(internal::length(s)) {} + + /** Constructs a string reference from a ``std::basic_string`` object. */ + template <typename Alloc> + FMT_CONSTEXPR basic_string_view( + const std::basic_string<Char, Alloc> &s) FMT_NOEXCEPT + : data_(s.data()), size_(s.size()) {} + + FMT_CONSTEXPR basic_string_view(type s) FMT_NOEXCEPT + : data_(s.data()), size_(s.size()) {} + + /** Returns a pointer to the string data. */ + FMT_CONSTEXPR const Char *data() const { return data_; } + + /** Returns the string size. */ + FMT_CONSTEXPR size_t size() const { return size_; } + + FMT_CONSTEXPR iterator begin() const { return data_; } + FMT_CONSTEXPR iterator end() const { return data_ + size_; } + + FMT_CONSTEXPR void remove_prefix(size_t n) { + data_ += n; + size_ -= n; + } + + // Lexicographically compare this string reference to other. + int compare(basic_string_view other) const { + size_t str_size = size_ < other.size_ ? size_ : other.size_; + int result = std::char_traits<Char>::compare(data_, other.data_, str_size); + if (result == 0) + result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); + return result; + } + + friend bool operator==(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) == 0; + } + friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) != 0; + } + friend bool operator<(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) < 0; + } + friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) <= 0; + } + friend bool operator>(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) > 0; + } + friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { + return lhs.compare(rhs) >= 0; + } +}; + +typedef basic_string_view<char> string_view; +typedef basic_string_view<wchar_t> wstring_view; + +template <typename Context> +class basic_format_arg; + +template <typename Context> +class basic_format_args; + +template <typename T> +struct no_formatter_error : std::false_type {}; + +// A formatter for objects of type T. +template <typename T, typename Char = char, typename Enable = void> +struct formatter { + static_assert(no_formatter_error<T>::value, + "don't know how to format the type, include fmt/ostream.h if it provides " + "an operator<< that should be used"); + + // The following functions are not defined intentionally. + template <typename ParseContext> + typename ParseContext::iterator parse(ParseContext &); + template <typename FormatContext> + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()); +}; + +template <typename T, typename Char, typename Enable = void> +struct convert_to_int { + enum { + value = !std::is_arithmetic<T>::value && std::is_convertible<T, int>::value + }; +}; + +namespace internal { + +/** A contiguous memory buffer with an optional growing ability. */ +template <typename T> +class basic_buffer { + private: + basic_buffer(const basic_buffer &) = delete; + void operator=(const basic_buffer &) = delete; + + T *ptr_; + std::size_t size_; + std::size_t capacity_; + + protected: + // Don't initialize ptr_ since it is not accessed to save a few cycles. + basic_buffer(std::size_t sz) FMT_NOEXCEPT: size_(sz), capacity_(sz) {} + + basic_buffer(T *p = FMT_NULL, std::size_t sz = 0, std::size_t cap = 0) + FMT_NOEXCEPT: ptr_(p), size_(sz), capacity_(cap) {} + + /** Sets the buffer data and capacity. */ + void set(T *buf_data, std::size_t buf_capacity) FMT_NOEXCEPT { + ptr_ = buf_data; + capacity_ = buf_capacity; + } + + /** Increases the buffer capacity to hold at least *capacity* elements. */ + virtual void grow(std::size_t capacity) = 0; + + public: + typedef T value_type; + typedef const T &const_reference; + + virtual ~basic_buffer() {} + + T *begin() FMT_NOEXCEPT { return ptr_; } + T *end() FMT_NOEXCEPT { return ptr_ + size_; } + + /** Returns the size of this buffer. */ + std::size_t size() const FMT_NOEXCEPT { return size_; } + + /** Returns the capacity of this buffer. */ + std::size_t capacity() const FMT_NOEXCEPT { return capacity_; } + + /** Returns a pointer to the buffer data. */ + T *data() FMT_NOEXCEPT { return ptr_; } + + /** Returns a pointer to the buffer data. */ + const T *data() const FMT_NOEXCEPT { return ptr_; } + + /** + Resizes the buffer. If T is a POD type new elements may not be initialized. + */ + void resize(std::size_t new_size) { + reserve(new_size); + size_ = new_size; + } + + /** Clears this buffer. */ + void clear() { size_ = 0; } + + /** Reserves space to store at least *capacity* elements. */ + void reserve(std::size_t new_capacity) { + if (new_capacity > capacity_) + grow(new_capacity); + } + + void push_back(const T &value) { + reserve(size_ + 1); + ptr_[size_++] = value; + } + + /** Appends data to the end of the buffer. */ + template <typename U> + void append(const U *begin, const U *end); + + T &operator[](std::size_t index) { return ptr_[index]; } + const T &operator[](std::size_t index) const { return ptr_[index]; } +}; + +typedef basic_buffer<char> buffer; +typedef basic_buffer<wchar_t> wbuffer; + +// A container-backed buffer. +template <typename Container> +class container_buffer : public basic_buffer<typename Container::value_type> { + private: + Container &container_; + + protected: + void grow(std::size_t capacity) FMT_OVERRIDE { + container_.resize(capacity); + this->set(&container_[0], capacity); + } + + public: + explicit container_buffer(Container &c) + : basic_buffer<typename Container::value_type>(c.size()), container_(c) {} +}; + +struct error_handler { + FMT_CONSTEXPR error_handler() {} + FMT_CONSTEXPR error_handler(const error_handler &) {} + + // This function is intentionally not constexpr to give a compile-time error. + FMT_API void on_error(const char *message); +}; + +// Formatting of wide characters and strings into a narrow output is disallowed: +// fmt::format("{}", L"test"); // error +// To fix this, use a wide format string: +// fmt::format(L"{}", L"test"); +template <typename Char> +inline void require_wchar() { + static_assert( + std::is_same<wchar_t, Char>::value, + "formatting of wide characters into a narrow output is disallowed"); +} + +template <typename Char> +struct named_arg_base; + +template <typename T, typename Char> +struct named_arg; + +template <typename T> +struct is_named_arg : std::false_type {}; + +template <typename T, typename Char> +struct is_named_arg<named_arg<T, Char>> : std::true_type {}; + +enum type { + none_type, named_arg_type, + // Integer types should go first, + int_type, uint_type, long_long_type, ulong_long_type, bool_type, char_type, + last_integer_type = char_type, + // followed by floating-point types. + double_type, long_double_type, last_numeric_type = long_double_type, + cstring_type, string_type, pointer_type, custom_type +}; + +FMT_CONSTEXPR bool is_integral(type t) { + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_integer_type; +} + +FMT_CONSTEXPR bool is_arithmetic(type t) { + FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); + return t > internal::none_type && t <= internal::last_numeric_type; +} + +template <typename Char> +struct string_value { + const Char *value; + std::size_t size; +}; + +template <typename Context> +struct custom_value { + const void *value; + void (*format)(const void *arg, Context &ctx); +}; + +// A formatting argument value. +template <typename Context> +class value { + public: + typedef typename Context::char_type char_type; + + union { + int int_value; + unsigned uint_value; + long long long_long_value; + unsigned long long ulong_long_value; + double double_value; + long double long_double_value; + const void *pointer; + string_value<char_type> string; + string_value<signed char> sstring; + string_value<unsigned char> ustring; + custom_value<Context> custom; + }; + + FMT_CONSTEXPR value(int val = 0) : int_value(val) {} + value(unsigned val) { uint_value = val; } + value(long long val) { long_long_value = val; } + value(unsigned long long val) { ulong_long_value = val; } + value(double val) { double_value = val; } + value(long double val) { long_double_value = val; } + value(const char_type *val) { string.value = val; } + value(const signed char *val) { + static_assert(std::is_same<char, char_type>::value, + "incompatible string types"); + sstring.value = val; + } + value(const unsigned char *val) { + static_assert(std::is_same<char, char_type>::value, + "incompatible string types"); + ustring.value = val; + } + value(basic_string_view<char_type> val) { + string.value = val.data(); + string.size = val.size(); + } + value(const void *val) { pointer = val; } + + template <typename T> + explicit value(const T &val) { + custom.value = &val; + custom.format = &format_custom_arg<T>; + } + + const named_arg_base<char_type> &as_named_arg() { + return *static_cast<const named_arg_base<char_type>*>(pointer); + } + + private: + // Formats an argument of a custom type, such as a user-defined class. + template <typename T> + static void format_custom_arg(const void *arg, Context &ctx) { + // Get the formatter type through the context to allow different contexts + // have different extension points, e.g. `formatter<T>` for `format` and + // `printf_formatter<T>` for `printf`. + typename Context::template formatter_type<T>::type f; + auto &&parse_ctx = ctx.parse_context(); + parse_ctx.advance_to(f.parse(parse_ctx)); + ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx)); + } +}; + +// Value initializer used to delay conversion to value and reduce memory churn. +template <typename Context, typename T, type TYPE> +struct init { + T val; + static const type type_tag = TYPE; + + FMT_CONSTEXPR init(const T &v) : val(v) {} + FMT_CONSTEXPR operator value<Context>() const { return value<Context>(val); } +}; + +template <typename Context, typename T> +FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value); + +#define FMT_MAKE_VALUE(TAG, ArgType, ValueType) \ + template <typename C> \ + FMT_CONSTEXPR init<C, ValueType, TAG> make_value(ArgType val) { \ + return static_cast<ValueType>(val); \ + } + +#define FMT_MAKE_VALUE_SAME(TAG, Type) \ + template <typename C> \ + FMT_CONSTEXPR init<C, Type, TAG> make_value(Type val) { return val; } + +FMT_MAKE_VALUE(bool_type, bool, int) +FMT_MAKE_VALUE(int_type, short, int) +FMT_MAKE_VALUE(uint_type, unsigned short, unsigned) +FMT_MAKE_VALUE_SAME(int_type, int) +FMT_MAKE_VALUE_SAME(uint_type, unsigned) + +// To minimize the number of types we need to deal with, long is translated +// either to int or to long long depending on its size. +typedef std::conditional<sizeof(long) == sizeof(int), int, long long>::type + long_type; +FMT_MAKE_VALUE( + (sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) +typedef std::conditional<sizeof(unsigned long) == sizeof(unsigned), + unsigned, unsigned long long>::type ulong_type; +FMT_MAKE_VALUE( + (sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), + unsigned long, ulong_type) + +FMT_MAKE_VALUE_SAME(long_long_type, long long) +FMT_MAKE_VALUE_SAME(ulong_long_type, unsigned long long) +FMT_MAKE_VALUE(int_type, signed char, int) +FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) +FMT_MAKE_VALUE(char_type, char, int) + +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +template <typename C> +inline init<C, int, char_type> make_value(wchar_t val) { + require_wchar<typename C::char_type>(); + return static_cast<int>(val); +} +#endif + +FMT_MAKE_VALUE(double_type, float, double) +FMT_MAKE_VALUE_SAME(double_type, double) +FMT_MAKE_VALUE_SAME(long_double_type, long double) + +// Formatting of wide strings into a narrow buffer and multibyte strings +// into a wide buffer is disallowed (https://github.com/fmtlib/fmt/pull/606). +FMT_MAKE_VALUE(cstring_type, typename C::char_type*, + const typename C::char_type*) +FMT_MAKE_VALUE(cstring_type, const typename C::char_type*, + const typename C::char_type*) + +FMT_MAKE_VALUE(cstring_type, signed char*, const signed char*) +FMT_MAKE_VALUE_SAME(cstring_type, const signed char*) +FMT_MAKE_VALUE(cstring_type, unsigned char*, const unsigned char*) +FMT_MAKE_VALUE_SAME(cstring_type, const unsigned char*) +FMT_MAKE_VALUE_SAME(string_type, basic_string_view<typename C::char_type>) +FMT_MAKE_VALUE(string_type, + typename basic_string_view<typename C::char_type>::type, + basic_string_view<typename C::char_type>) +FMT_MAKE_VALUE(string_type, const std::basic_string<typename C::char_type>&, + basic_string_view<typename C::char_type>) +FMT_MAKE_VALUE(pointer_type, void*, const void*) +FMT_MAKE_VALUE_SAME(pointer_type, const void*) + +#if FMT_USE_NULLPTR +FMT_MAKE_VALUE(pointer_type, std::nullptr_t, const void*) +#endif + +// Formatting of arbitrary pointers is disallowed. If you want to output a +// pointer cast it to "void *" or "const void *". In particular, this forbids +// formatting of "[const] volatile char *" which is printed as bool by +// iostreams. +template <typename C, typename T> +typename std::enable_if<!std::is_same<T, typename C::char_type>::value>::type + make_value(const T *) { + static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); +} + +template <typename C, typename T> +inline typename std::enable_if< + std::is_enum<T>::value && convert_to_int<T, typename C::char_type>::value, + init<C, int, int_type>>::type + make_value(const T &val) { return static_cast<int>(val); } + +template <typename C, typename T, typename Char = typename C::char_type> +inline typename std::enable_if< + std::is_constructible<basic_string_view<Char>, T>::value, + init<C, basic_string_view<Char>, string_type>>::type + make_value(const T &val) { return basic_string_view<Char>(val); } + +template <typename C, typename T, typename Char = typename C::char_type> +inline typename std::enable_if< + !convert_to_int<T, Char>::value && + !std::is_convertible<T, basic_string_view<Char>>::value && + !std::is_constructible<basic_string_view<Char>, T>::value, + // Implicit conversion to std::string is not handled here because it's + // unsafe: https://github.com/fmtlib/fmt/issues/729 + init<C, const T &, custom_type>>::type + make_value(const T &val) { return val; } + +template <typename C, typename T> +init<C, const void*, named_arg_type> + make_value(const named_arg<T, typename C::char_type> &val) { + basic_format_arg<C> arg = make_arg<C>(val.value); + std::memcpy(val.data, &arg, sizeof(arg)); + return static_cast<const void*>(&val); +} + +// Maximum number of arguments with packed types. +enum { max_packed_args = 15 }; + +template <typename Context> +class arg_map; +} // namespace internal + +// A formatting argument. It is a trivially copyable/constructible type to +// allow storage in basic_memory_buffer. +template <typename Context> +class basic_format_arg { + private: + internal::value<Context> value_; + internal::type type_; + + template <typename ContextType, typename T> + friend FMT_CONSTEXPR basic_format_arg<ContextType> + internal::make_arg(const T &value); + + template <typename Visitor, typename Ctx> + friend FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type + visit(Visitor &&vis, const basic_format_arg<Ctx> &arg); + + friend class basic_format_args<Context>; + friend class internal::arg_map<Context>; + + typedef typename Context::char_type char_type; + + public: + class handle { + public: + explicit handle(internal::custom_value<Context> custom): custom_(custom) {} + + void format(Context &ctx) const { custom_.format(custom_.value, ctx); } + + private: + internal::custom_value<Context> custom_; + }; + + FMT_CONSTEXPR basic_format_arg() : type_(internal::none_type) {} + + FMT_EXPLICIT operator bool() const FMT_NOEXCEPT { + return type_ != internal::none_type; + } + + internal::type type() const { return type_; } + + bool is_integral() const { return internal::is_integral(type_); } + bool is_arithmetic() const { return internal::is_arithmetic(type_); } +}; + +struct monostate {}; + +/** + \rst + Visits an argument dispatching to the appropriate visit method based on + the argument type. For example, if the argument type is ``double`` then + ``vis(value)`` will be called with the value of type ``double``. + \endrst + */ +template <typename Visitor, typename Context> +FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type + visit(Visitor &&vis, const basic_format_arg<Context> &arg) { + typedef typename Context::char_type char_type; + switch (arg.type_) { + case internal::none_type: + break; + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + return vis(arg.value_.int_value); + case internal::uint_type: + return vis(arg.value_.uint_value); + case internal::long_long_type: + return vis(arg.value_.long_long_value); + case internal::ulong_long_type: + return vis(arg.value_.ulong_long_value); + case internal::bool_type: + return vis(arg.value_.int_value != 0); + case internal::char_type: + return vis(static_cast<char_type>(arg.value_.int_value)); + case internal::double_type: + return vis(arg.value_.double_value); + case internal::long_double_type: + return vis(arg.value_.long_double_value); + case internal::cstring_type: + return vis(arg.value_.string.value); + case internal::string_type: + return vis(basic_string_view<char_type>( + arg.value_.string.value, arg.value_.string.size)); + case internal::pointer_type: + return vis(arg.value_.pointer); + case internal::custom_type: + return vis(typename basic_format_arg<Context>::handle(arg.value_.custom)); + } + return vis(monostate()); +} + +// Parsing context consisting of a format string range being parsed and an +// argument counter for automatic indexing. +template <typename Char, typename ErrorHandler = internal::error_handler> +class basic_parse_context : private ErrorHandler { + private: + basic_string_view<Char> format_str_; + int next_arg_id_; + + public: + typedef Char char_type; + typedef typename basic_string_view<Char>::iterator iterator; + + explicit FMT_CONSTEXPR basic_parse_context( + basic_string_view<Char> format_str, ErrorHandler eh = ErrorHandler()) + : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} + + // Returns an iterator to the beginning of the format string range being + // parsed. + FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT { + return format_str_.begin(); + } + + // Returns an iterator past the end of the format string range being parsed. + FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); } + + // Advances the begin iterator to ``it``. + FMT_CONSTEXPR void advance_to(iterator it) { + format_str_.remove_prefix(internal::to_unsigned(it - begin())); + } + + // Returns the next argument index. + FMT_CONSTEXPR unsigned next_arg_id(); + + FMT_CONSTEXPR bool check_arg_id(unsigned) { + if (next_arg_id_ > 0) { + on_error("cannot switch from automatic to manual argument indexing"); + return false; + } + next_arg_id_ = -1; + return true; + } + void check_arg_id(basic_string_view<Char>) {} + + FMT_CONSTEXPR void on_error(const char *message) { + ErrorHandler::on_error(message); + } + + FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; } +}; + +typedef basic_parse_context<char> parse_context; +typedef basic_parse_context<wchar_t> wparse_context; + +namespace internal { +// A map from argument names to their values for named arguments. +template <typename Context> +class arg_map { + private: + arg_map(const arg_map &) = delete; + void operator=(const arg_map &) = delete; + + typedef typename Context::char_type char_type; + + struct entry { + basic_string_view<char_type> name; + basic_format_arg<Context> arg; + }; + + entry *map_; + unsigned size_; + + void push_back(value<Context> val) { + const internal::named_arg_base<char_type> &named = val.as_named_arg(); + map_[size_] = entry{named.name, named.template deserialize<Context>()}; + ++size_; + } + + public: + arg_map() : map_(FMT_NULL), size_(0) {} + void init(const basic_format_args<Context> &args); + ~arg_map() { delete [] map_; } + + basic_format_arg<Context> find(basic_string_view<char_type> name) const { + // The list is unsorted, so just return the first matching name. + for (entry *it = map_, *end = map_ + size_; it != end; ++it) { + if (it->name == name) + return it->arg; + } + return basic_format_arg<Context>(); + } +}; + +template <typename OutputIt, typename Context, typename Char> +class context_base { + public: + typedef OutputIt iterator; + + private: + basic_parse_context<Char> parse_context_; + iterator out_; + basic_format_args<Context> args_; + + protected: + typedef Char char_type; + typedef basic_format_arg<Context> format_arg; + + context_base(OutputIt out, basic_string_view<char_type> format_str, + basic_format_args<Context> ctx_args) + : parse_context_(format_str), out_(out), args_(ctx_args) {} + + // Returns the argument with specified index. + format_arg do_get_arg(unsigned arg_id) { + format_arg arg = args_.get(arg_id); + if (!arg) + parse_context_.on_error("argument index out of range"); + return arg; + } + + // Checks if manual indexing is used and returns the argument with + // specified index. + format_arg get_arg(unsigned arg_id) { + return this->parse_context().check_arg_id(arg_id) ? + this->do_get_arg(arg_id) : format_arg(); + } + + public: + basic_parse_context<char_type> &parse_context() { + return parse_context_; + } + + internal::error_handler error_handler() { + return parse_context_.error_handler(); + } + + void on_error(const char *message) { parse_context_.on_error(message); } + + // Returns an iterator to the beginning of the output range. + iterator out() { return out_; } + iterator begin() { return out_; } // deprecated + + // Advances the begin iterator to ``it``. + void advance_to(iterator it) { out_ = it; } + + basic_format_args<Context> args() const { return args_; } +}; + +// Extracts a reference to the container from back_insert_iterator. +template <typename Container> +inline Container &get_container(std::back_insert_iterator<Container> it) { + typedef std::back_insert_iterator<Container> bi_iterator; + struct accessor: bi_iterator { + accessor(bi_iterator iter) : bi_iterator(iter) {} + using bi_iterator::container; + }; + return *accessor(it).container; +} +} // namespace internal + +// Formatting context. +template <typename OutputIt, typename Char> +class basic_format_context : + public internal::context_base< + OutputIt, basic_format_context<OutputIt, Char>, Char> { + public: + /** The character type for the output. */ + typedef Char char_type; + + // using formatter_type = formatter<T, char_type>; + template <typename T> + struct formatter_type { typedef formatter<T, char_type> type; }; + + private: + internal::arg_map<basic_format_context> map_; + + basic_format_context(const basic_format_context &) = delete; + void operator=(const basic_format_context &) = delete; + + typedef internal::context_base<OutputIt, basic_format_context, Char> base; + typedef typename base::format_arg format_arg; + using base::get_arg; + + public: + using typename base::iterator; + + /** + Constructs a ``basic_format_context`` object. References to the arguments are + stored in the object so make sure they have appropriate lifetimes. + */ + basic_format_context(OutputIt out, basic_string_view<char_type> format_str, + basic_format_args<basic_format_context> ctx_args) + : base(out, format_str, ctx_args) {} + + format_arg next_arg() { + return this->do_get_arg(this->parse_context().next_arg_id()); + } + format_arg get_arg(unsigned arg_id) { return this->do_get_arg(arg_id); } + + // Checks if manual indexing is used and returns the argument with the + // specified name. + format_arg get_arg(basic_string_view<char_type> name); +}; + +template <typename Char> +struct buffer_context { + typedef basic_format_context< + std::back_insert_iterator<internal::basic_buffer<Char>>, Char> type; +}; +typedef buffer_context<char>::type format_context; +typedef buffer_context<wchar_t>::type wformat_context; + +namespace internal { +template <typename Context, typename T> +struct get_type { + typedef decltype(make_value<Context>( + declval<typename std::decay<T>::type&>())) value_type; + static const type value = value_type::type_tag; +}; + +template <typename Context> +FMT_CONSTEXPR11 unsigned long long get_types() { return 0; } + +template <typename Context, typename Arg, typename... Args> +FMT_CONSTEXPR11 unsigned long long get_types() { + return get_type<Context, Arg>::value | (get_types<Context, Args...>() << 4); +} + +template <typename Context, typename T> +FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value) { + basic_format_arg<Context> arg; + arg.type_ = get_type<Context, T>::value; + arg.value_ = make_value<Context>(value); + return arg; +} + +template <bool IS_PACKED, typename Context, typename T> +inline typename std::enable_if<IS_PACKED, value<Context>>::type + make_arg(const T &value) { + return make_value<Context>(value); +} + +template <bool IS_PACKED, typename Context, typename T> +inline typename std::enable_if<!IS_PACKED, basic_format_arg<Context>>::type + make_arg(const T &value) { + return make_arg<Context>(value); +} +} // namespace internal + +/** + \rst + An array of references to arguments. It can be implicitly converted into + `~fmt::basic_format_args` for passing into type-erased formatting functions + such as `~fmt::vformat`. + \endrst + */ +template <typename Context, typename ...Args> +class format_arg_store { + private: + static const size_t NUM_ARGS = sizeof...(Args); + + // Packed is a macro on MinGW so use IS_PACKED instead. + static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; + + typedef typename std::conditional<IS_PACKED, + internal::value<Context>, basic_format_arg<Context>>::type value_type; + + // If the arguments are not packed, add one more element to mark the end. + static const size_t DATA_SIZE = + NUM_ARGS + (IS_PACKED && NUM_ARGS != 0 ? 0 : 1); + value_type data_[DATA_SIZE]; + + friend class basic_format_args<Context>; + + static FMT_CONSTEXPR11 long long get_types() { + return IS_PACKED ? + static_cast<long long>(internal::get_types<Context, Args...>()) : + -static_cast<long long>(NUM_ARGS); + } + + public: +#if FMT_USE_CONSTEXPR11 + static FMT_CONSTEXPR11 long long TYPES = get_types(); +#else + static const long long TYPES; +#endif + +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 405) || \ + (FMT_MSC_VER && FMT_MSC_VER <= 1800) + // Workaround array initialization issues in gcc <= 4.5 and MSVC <= 2013. + format_arg_store(const Args &... args) { + value_type init[DATA_SIZE] = + {internal::make_arg<IS_PACKED, Context>(args)...}; + std::memcpy(data_, init, sizeof(init)); + } +#else + format_arg_store(const Args &... args) + : data_{internal::make_arg<IS_PACKED, Context>(args)...} {} +#endif +}; + +#if !FMT_USE_CONSTEXPR11 +template <typename Context, typename ...Args> +const long long format_arg_store<Context, Args...>::TYPES = get_types(); +#endif + +/** + \rst + Constructs an `~fmt::format_arg_store` object that contains references to + arguments and can be implicitly converted to `~fmt::format_args`. `Context` + can be omitted in which case it defaults to `~fmt::context`. + \endrst + */ +template <typename Context, typename ...Args> +inline format_arg_store<Context, Args...> + make_format_args(const Args & ... args) { + return format_arg_store<Context, Args...>(args...); +} + +template <typename ...Args> +inline format_arg_store<format_context, Args...> + make_format_args(const Args & ... args) { + return format_arg_store<format_context, Args...>(args...); +} + +/** Formatting arguments. */ +template <typename Context> +class basic_format_args { + public: + typedef unsigned size_type; + typedef basic_format_arg<Context> format_arg; + + private: + // To reduce compiled code size per formatting function call, types of first + // max_packed_args arguments are passed in the types_ field. + unsigned long long types_; + union { + // If the number of arguments is less than max_packed_args, the argument + // values are stored in values_, otherwise they are stored in args_. + // This is done to reduce compiled code size as storing larger objects + // may require more code (at least on x86-64) even if the same amount of + // data is actually copied to stack. It saves ~10% on the bloat test. + const internal::value<Context> *values_; + const format_arg *args_; + }; + + typename internal::type type(unsigned index) const { + unsigned shift = index * 4; + unsigned long long mask = 0xf; + return static_cast<typename internal::type>( + (types_ & (mask << shift)) >> shift); + } + + friend class internal::arg_map<Context>; + + void set_data(const internal::value<Context> *values) { values_ = values; } + void set_data(const format_arg *args) { args_ = args; } + + format_arg do_get(size_type index) const { + format_arg arg; + long long signed_types = static_cast<long long>(types_); + if (signed_types < 0) { + unsigned long long num_args = + static_cast<unsigned long long>(-signed_types); + if (index < num_args) + arg = args_[index]; + return arg; + } + if (index > internal::max_packed_args) + return arg; + arg.type_ = type(index); + if (arg.type_ == internal::none_type) + return arg; + internal::value<Context> &val = arg.value_; + val = values_[index]; + return arg; + } + + public: + basic_format_args() : types_(0) {} + + /** + \rst + Constructs a `basic_format_args` object from `~fmt::format_arg_store`. + \endrst + */ + template <typename... Args> + basic_format_args(const format_arg_store<Context, Args...> &store) + : types_(static_cast<unsigned long long>(store.TYPES)) { + set_data(store.data_); + } + + /** + \rst + Constructs a `basic_format_args` object from a dynamic set of arguments. + \endrst + */ + basic_format_args(const format_arg *args, size_type count) + : types_(-static_cast<int64_t>(count)) { + set_data(args); + } + + /** Returns the argument at specified index. */ + format_arg get(size_type index) const { + format_arg arg = do_get(index); + if (arg.type_ == internal::named_arg_type) + arg = arg.value_.as_named_arg().template deserialize<Context>(); + return arg; + } + + unsigned max_size() const { + long long signed_types = static_cast<long long>(types_); + return static_cast<unsigned>( + signed_types < 0 ? + -signed_types : static_cast<long long>(internal::max_packed_args)); + } +}; + +/** An alias to ``basic_format_args<context>``. */ +// It is a separate type rather than a typedef to make symbols readable. +struct format_args: basic_format_args<format_context> { + template <typename ...Args> + format_args(Args && ... arg) + : basic_format_args<format_context>(std::forward<Args>(arg)...) {} +}; +struct wformat_args : basic_format_args<wformat_context> { + template <typename ...Args> + wformat_args(Args && ... arg) + : basic_format_args<wformat_context>(std::forward<Args>(arg)...) {} +}; + +namespace internal { +template <typename Char> +struct named_arg_base { + basic_string_view<Char> name; + + // Serialized value<context>. + mutable char data[sizeof(basic_format_arg<format_context>)]; + + named_arg_base(basic_string_view<Char> nm) : name(nm) {} + + template <typename Context> + basic_format_arg<Context> deserialize() const { + basic_format_arg<Context> arg; + std::memcpy(&arg, data, sizeof(basic_format_arg<Context>)); + return arg; + } +}; + +template <typename T, typename Char> +struct named_arg : named_arg_base<Char> { + const T &value; + + named_arg(basic_string_view<Char> name, const T &val) + : named_arg_base<Char>(name), value(val) {} +}; +} + +/** + \rst + Returns a named argument to be used in a formatting function. + + **Example**:: + + fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); + \endrst + */ +template <typename T> +inline internal::named_arg<T, char> arg(string_view name, const T &arg) { + return internal::named_arg<T, char>(name, arg); +} + +template <typename T> +inline internal::named_arg<T, wchar_t> arg(wstring_view name, const T &arg) { + return internal::named_arg<T, wchar_t>(name, arg); +} + +// This function template is deleted intentionally to disable nested named +// arguments as in ``format("{}", arg("a", arg("b", 42)))``. +template <typename S, typename T, typename Char> +void arg(S, internal::named_arg<T, Char>) = delete; + +// A base class for compile-time strings. It is defined in the fmt namespace to +// make formatting functions visible via ADL, e.g. format(fmt("{}"), 42). +struct compile_string {}; + +namespace internal { +// If S is a format string type, format_string_traints<S>::char_type gives its +// character type. +template <typename S, typename Enable = void> +struct format_string_traits { + private: + // Use constructability as a way to detect if format_string_traits is + // specialized because other methods are broken on MSVC2013. + format_string_traits(); +}; + +template <typename Char> +struct format_string_traits_base { typedef Char char_type; }; + +template <typename Char> +struct format_string_traits<Char *> : format_string_traits_base<Char> {}; + +template <typename Char> +struct format_string_traits<const Char *> : format_string_traits_base<Char> {}; + +template <typename Char, std::size_t N> +struct format_string_traits<Char[N]> : format_string_traits_base<Char> {}; + +template <typename Char, std::size_t N> +struct format_string_traits<const Char[N]> : format_string_traits_base<Char> {}; + +template <typename Char> +struct format_string_traits<std::basic_string<Char>> : + format_string_traits_base<Char> {}; + +template <typename S> +struct format_string_traits< + S, typename std::enable_if<std::is_base_of< + basic_string_view<typename S::char_type>, S>::value>::type> : + format_string_traits_base<typename S::char_type> {}; + +template <typename S> +struct is_format_string : + std::integral_constant< + bool, std::is_constructible<format_string_traits<S>>::value> {}; + +template <typename S> +struct is_compile_string : + std::integral_constant<bool, std::is_base_of<compile_string, S>::value> {}; + +template <typename... Args, typename S> +inline typename std::enable_if<!is_compile_string<S>::value>::type + check_format_string(const S &) {} +template <typename... Args, typename S> +typename std::enable_if<is_compile_string<S>::value>::type + check_format_string(S); + +template <typename Char> +std::basic_string<Char> vformat( + basic_string_view<Char> format_str, + basic_format_args<typename buffer_context<Char>::type> args); +} // namespace internal + +format_context::iterator vformat_to( + internal::buffer &buf, string_view format_str, format_args args); +wformat_context::iterator vformat_to( + internal::wbuffer &buf, wstring_view format_str, wformat_args args); + +template <typename Container> +struct is_contiguous : std::false_type {}; + +template <typename Char> +struct is_contiguous<std::basic_string<Char>> : std::true_type {}; + +template <typename Char> +struct is_contiguous<internal::basic_buffer<Char>> : std::true_type {}; + +/** Formats a string and writes the output to ``out``. */ +template <typename Container> +typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + vformat_to(std::back_insert_iterator<Container> out, + string_view format_str, format_args args) { + internal::container_buffer<Container> buf(internal::get_container(out)); + vformat_to(buf, format_str, args); + return out; +} + +template <typename Container> +typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + vformat_to(std::back_insert_iterator<Container> out, + wstring_view format_str, wformat_args args) { + internal::container_buffer<Container> buf(internal::get_container(out)); + vformat_to(buf, format_str, args); + return out; +} + +template <typename Container, typename... Args> +inline typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + format_to(std::back_insert_iterator<Container> out, + string_view format_str, const Args & ... args) { + format_arg_store<format_context, Args...> as{args...}; + return vformat_to(out, format_str, as); +} + +template <typename Container, typename... Args> +inline typename std::enable_if< + is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type + format_to(std::back_insert_iterator<Container> out, + wstring_view format_str, const Args & ... args) { + return vformat_to(out, format_str, + make_format_args<wformat_context>(args...)); +} + +template < + typename String, + typename Char = typename internal::format_string_traits<String>::char_type> +inline std::basic_string<Char> vformat( + const String &format_str, + basic_format_args<typename buffer_context<Char>::type> args) { + // Convert format string to string_view to reduce the number of overloads. + return internal::vformat(basic_string_view<Char>(format_str), args); +} + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + #include <fmt/core.h> + std::string message = fmt::format("The answer is {}", 42); + \endrst +*/ +template <typename String, typename... Args> +inline std::basic_string< + typename internal::format_string_traits<String>::char_type> + format(const String &format_str, const Args & ... args) { + internal::check_format_string<Args...>(format_str); + // This should be just + // return vformat(format_str, make_format_args(args...)); + // but gcc has trouble optimizing the latter, so break it down. + typedef typename internal::format_string_traits<String>::char_type char_t; + typedef typename buffer_context<char_t>::type context_t; + format_arg_store<context_t, Args...> as{args...}; + return internal::vformat( + basic_string_view<char_t>(format_str), basic_format_args<context_t>(as)); +} + +FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); +FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::print(stderr, "Don't {}!", "panic"); + \endrst + */ +template <typename... Args> +inline void print(std::FILE *f, string_view format_str, const Args & ... args) { + format_arg_store<format_context, Args...> as(args...); + vprint(f, format_str, as); +} +/** + Prints formatted data to the file *f* which should be in wide-oriented mode + set via ``fwide(f, 1)`` or ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. + */ +template <typename... Args> +inline void print(std::FILE *f, wstring_view format_str, + const Args & ... args) { + format_arg_store<wformat_context, Args...> as(args...); + vprint(f, format_str, as); +} + +FMT_API void vprint(string_view format_str, format_args args); +FMT_API void vprint(wstring_view format_str, wformat_args args); + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::print("Elapsed time: {0:.2f} seconds", 1.23); + \endrst + */ +template <typename... Args> +inline void print(string_view format_str, const Args & ... args) { + format_arg_store<format_context, Args...> as{args...}; + vprint(format_str, as); +} + +template <typename... Args> +inline void print(wstring_view format_str, const Args & ... args) { + format_arg_store<wformat_context, Args...> as(args...); + vprint(format_str, as); +} +FMT_END_NAMESPACE + +#endif // FMT_CORE_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/format-inl.h b/src/nmodl/ext/spdlog/fmt/bundled/format-inl.h new file mode 100644 index 0000000000..56c4d581df --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/format-inl.h @@ -0,0 +1,866 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_FORMAT_INL_H_ +#define FMT_FORMAT_INL_H_ + +#include "format.h" + +#include <string.h> + +#include <cctype> +#include <cerrno> +#include <climits> +#include <cmath> +#include <cstdarg> +#include <cstddef> // for std::ptrdiff_t +#include <cstring> // for std::memmove +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +# include <locale> +#endif + +#if FMT_USE_WINDOWS_H +# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) +# include <windows.h> +# else +# define NOMINMAX +# include <windows.h> +# undef NOMINMAX +# endif +#endif + +#if FMT_EXCEPTIONS +# define FMT_TRY try +# define FMT_CATCH(x) catch (x) +#else +# define FMT_TRY if (true) +# define FMT_CATCH(x) if (false) +#endif + +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4127) // conditional expression is constant +# pragma warning(disable: 4702) // unreachable code +// Disable deprecation warning for strerror. The latter is not called but +// MSVC fails to detect it. +# pragma warning(disable: 4996) +#endif + +// Dummy implementations of strerror_r and strerror_s called if corresponding +// system functions are not available. +inline fmt::internal::null<> strerror_r(int, char *, ...) { + return fmt::internal::null<>(); +} +inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) { + return fmt::internal::null<>(); +} + +FMT_BEGIN_NAMESPACE + +namespace { + +#ifndef _MSC_VER +# define FMT_SNPRINTF snprintf +#else // _MSC_VER +inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { + va_list args; + va_start(args, format); + int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); + va_end(args); + return result; +} +# define FMT_SNPRINTF fmt_snprintf +#endif // _MSC_VER + +#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) +# define FMT_SWPRINTF snwprintf +#else +# define FMT_SWPRINTF swprintf +#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) + +typedef void (*FormatFunc)(internal::buffer &, int, string_view); + +// Portable thread-safe version of strerror. +// Sets buffer to point to a string describing the error code. +// This can be either a pointer to a string stored in buffer, +// or a pointer to some static immutable string. +// Returns one of the following values: +// 0 - success +// ERANGE - buffer is not large enough to store the error message +// other - failure +// Buffer should be at least of size 1. +int safe_strerror( + int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { + FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); + + class dispatcher { + private: + int error_code_; + char *&buffer_; + std::size_t buffer_size_; + + // A noop assignment operator to avoid bogus warnings. + void operator=(const dispatcher &) {} + + // Handle the result of XSI-compliant version of strerror_r. + int handle(int result) { + // glibc versions before 2.13 return result in errno. + return result == -1 ? errno : result; + } + + // Handle the result of GNU-specific version of strerror_r. + int handle(char *message) { + // If the buffer is full then the message is probably truncated. + if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) + return ERANGE; + buffer_ = message; + return 0; + } + + // Handle the case when strerror_r is not available. + int handle(internal::null<>) { + return fallback(strerror_s(buffer_, buffer_size_, error_code_)); + } + + // Fallback to strerror_s when strerror_r is not available. + int fallback(int result) { + // If the buffer is full then the message is probably truncated. + return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? + ERANGE : result; + } + + // Fallback to strerror if strerror_r and strerror_s are not available. + int fallback(internal::null<>) { + errno = 0; + buffer_ = strerror(error_code_); + return errno; + } + + public: + dispatcher(int err_code, char *&buf, std::size_t buf_size) + : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} + + int run() { + return handle(strerror_r(error_code_, buffer_, buffer_size_)); + } + }; + return dispatcher(error_code, buffer, buffer_size).run(); +} + +void format_error_code(internal::buffer &out, int error_code, + string_view message) FMT_NOEXCEPT { + // Report error code making sure that the output fits into + // inline_buffer_size to avoid dynamic memory allocation and potential + // bad_alloc. + out.resize(0); + static const char SEP[] = ": "; + static const char ERROR_STR[] = "error "; + // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. + std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; + typedef internal::int_traits<int>::main_type main_type; + main_type abs_value = static_cast<main_type>(error_code); + if (internal::is_negative(error_code)) { + abs_value = 0 - abs_value; + ++error_code_size; + } + error_code_size += internal::count_digits(abs_value); + writer w(out); + if (message.size() <= inline_buffer_size - error_code_size) { + w.write(message); + w.write(SEP); + } + w.write(ERROR_STR); + w.write(error_code); + assert(out.size() <= inline_buffer_size); +} + +void report_error(FormatFunc func, int error_code, + string_view message) FMT_NOEXCEPT { + memory_buffer full_message; + func(full_message, error_code, message); + // Use Writer::data instead of Writer::c_str to avoid potential memory + // allocation. + std::fwrite(full_message.data(), full_message.size(), 1, stderr); + std::fputc('\n', stderr); +} +} // namespace + +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +class locale { + private: + std::locale locale_; + + public: + explicit locale(std::locale loc = std::locale()) : locale_(loc) {} + std::locale get() { return locale_; } +}; + +FMT_FUNC size_t internal::count_code_points(u8string_view s) { + const char8_t *data = s.data(); + int num_code_points = 0; + for (size_t i = 0, size = s.size(); i != size; ++i) { + if ((data[i].value & 0xc0) != 0x80) + ++num_code_points; + } + return num_code_points; +} + +template <typename Char> +FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { + std::locale loc = lp ? lp->locale().get() : std::locale(); + return std::use_facet<std::numpunct<Char>>(loc).thousands_sep(); +} +#else +template <typename Char> +FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { + return FMT_STATIC_THOUSANDS_SEPARATOR; +} +#endif + +FMT_FUNC void system_error::init( + int err_code, string_view format_str, format_args args) { + error_code_ = err_code; + memory_buffer buffer; + format_system_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +namespace internal { +template <typename T> +int char_traits<char>::format_float( + char *buffer, std::size_t size, const char *format, int precision, T value) { + return precision < 0 ? + FMT_SNPRINTF(buffer, size, format, value) : + FMT_SNPRINTF(buffer, size, format, precision, value); +} + +template <typename T> +int char_traits<wchar_t>::format_float( + wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, + T value) { + return precision < 0 ? + FMT_SWPRINTF(buffer, size, format, value) : + FMT_SWPRINTF(buffer, size, format, precision, value); +} + +template <typename T> +const char basic_data<T>::DIGITS[] = + "0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"; + +#define FMT_POWERS_OF_10(factor) \ + factor * 10, \ + factor * 100, \ + factor * 1000, \ + factor * 10000, \ + factor * 100000, \ + factor * 1000000, \ + factor * 10000000, \ + factor * 100000000, \ + factor * 1000000000 + +template <typename T> +const uint32_t basic_data<T>::POWERS_OF_10_32[] = { + 1, FMT_POWERS_OF_10(1) +}; + +template <typename T> +const uint32_t basic_data<T>::ZERO_OR_POWERS_OF_10_32[] = { + 0, FMT_POWERS_OF_10(1) +}; + +template <typename T> +const uint64_t basic_data<T>::ZERO_OR_POWERS_OF_10_64[] = { + 0, + FMT_POWERS_OF_10(1), + FMT_POWERS_OF_10(1000000000ull), + 10000000000000000000ull +}; + +// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. +// These are generated by support/compute-powers.py. +template <typename T> +const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = { + 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, + 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, + 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, + 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, + 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, + 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, + 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, + 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, + 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, + 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, + 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, + 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, + 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, + 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, + 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, + 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, + 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, + 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, + 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, + 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, + 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, + 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, + 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, + 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, + 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, + 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, + 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, + 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, + 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, +}; + +// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding +// to significands above. +template <typename T> +const int16_t basic_data<T>::POW10_EXPONENTS[] = { + -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, + -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, + -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, + -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, + -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, + 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, + 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, + 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066 +}; + +template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m"; +template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m"; + +// A handmade floating-point number f * pow(2, e). +class fp { + private: + typedef uint64_t significand_type; + + // All sizes are in bits. + static FMT_CONSTEXPR_DECL const int char_size = + std::numeric_limits<unsigned char>::digits; + // Subtract 1 to account for an implicit most significant bit in the + // normalized form. + static FMT_CONSTEXPR_DECL const int double_significand_size = + std::numeric_limits<double>::digits - 1; + static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = + 1ull << double_significand_size; + + public: + significand_type f; + int e; + + static FMT_CONSTEXPR_DECL const int significand_size = + sizeof(significand_type) * char_size; + + fp(): f(0), e(0) {} + fp(uint64_t f, int e): f(f), e(e) {} + + // Constructs fp from an IEEE754 double. It is a template to prevent compile + // errors on platforms where double is not IEEE754. + template <typename Double> + explicit fp(Double d) { + // Assume double is in the format [sign][exponent][significand]. + typedef std::numeric_limits<Double> limits; + const int double_size = static_cast<int>(sizeof(Double) * char_size); + const int exponent_size = + double_size - double_significand_size - 1; // -1 for sign + const uint64_t significand_mask = implicit_bit - 1; + const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; + const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; + auto u = bit_cast<uint64_t>(d); + auto biased_e = (u & exponent_mask) >> double_significand_size; + f = u & significand_mask; + if (biased_e != 0) + f += implicit_bit; + else + biased_e = 1; // Subnormals use biased exponent 1 (min exponent). + e = static_cast<int>(biased_e - exponent_bias - double_significand_size); + } + + // Normalizes the value converted from double and multiplied by (1 << SHIFT). + template <int SHIFT = 0> + void normalize() { + // Handle subnormals. + auto shifted_implicit_bit = implicit_bit << SHIFT; + while ((f & shifted_implicit_bit) == 0) { + f <<= 1; + --e; + } + // Subtract 1 to account for hidden bit. + auto offset = significand_size - double_significand_size - SHIFT - 1; + f <<= offset; + e -= offset; + } + + // Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where + // a boundary is a value half way between the number and its predecessor + // (lower) or successor (upper). The upper boundary is normalized and lower + // has the same exponent but may be not normalized. + void compute_boundaries(fp &lower, fp &upper) const { + lower = f == implicit_bit ? + fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); + upper = fp((f << 1) + 1, e - 1); + upper.normalize<1>(); // 1 is to account for the exponent shift above. + lower.f <<= lower.e - upper.e; + lower.e = upper.e; + } +}; + +// Returns an fp number representing x - y. Result may not be normalized. +inline fp operator-(fp x, fp y) { + FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands"); + return fp(x.f - y.f, x.e); +} + +// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest +// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be normalized. +FMT_API fp operator*(fp x, fp y); + +// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its +// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3. +FMT_API fp get_cached_power(int min_exponent, int &pow10_exponent); + +FMT_FUNC fp operator*(fp x, fp y) { + // Multiply 32-bit parts of significands. + uint64_t mask = (1ULL << 32) - 1; + uint64_t a = x.f >> 32, b = x.f & mask; + uint64_t c = y.f >> 32, d = y.f & mask; + uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; + // Compute mid 64-bit of result and round. + uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); + return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64); +} + +FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) { + const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10) + int index = static_cast<int>(std::ceil( + (min_exponent + fp::significand_size - 1) * one_over_log2_10)); + // Decimal exponent of the first (smallest) cached power of 10. + const int first_dec_exp = -348; + // Difference between 2 consecutive decimal exponents in cached powers of 10. + const int dec_exp_step = 8; + index = (index - first_dec_exp - 1) / dec_exp_step + 1; + pow10_exponent = first_dec_exp + index * dec_exp_step; + return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]); +} + +// Generates output using Grisu2 digit-gen algorithm. +FMT_FUNC void grisu2_gen_digits( + const fp &scaled_value, const fp &scaled_upper, uint64_t delta, + char *buffer, size_t &size, int &dec_exp) { + internal::fp one(1ull << -scaled_upper.e, scaled_upper.e); + // hi (p1 in Grisu) contains the most significant digits of scaled_upper. + // hi = floor(scaled_upper / one). + uint32_t hi = static_cast<uint32_t>(scaled_upper.f >> -one.e); + // lo (p2 in Grisu) contains the least significants digits of scaled_upper. + // lo = scaled_upper mod 1. + uint64_t lo = scaled_upper.f & (one.f - 1); + size = 0; + auto exp = count_digits(hi); // kappa in Grisu. + while (exp > 0) { + uint32_t digit = 0; + // This optimization by miloyip reduces the number of integer divisions by + // one per iteration. + switch (exp) { + case 10: digit = hi / 1000000000; hi %= 1000000000; break; + case 9: digit = hi / 100000000; hi %= 100000000; break; + case 8: digit = hi / 10000000; hi %= 10000000; break; + case 7: digit = hi / 1000000; hi %= 1000000; break; + case 6: digit = hi / 100000; hi %= 100000; break; + case 5: digit = hi / 10000; hi %= 10000; break; + case 4: digit = hi / 1000; hi %= 1000; break; + case 3: digit = hi / 100; hi %= 100; break; + case 2: digit = hi / 10; hi %= 10; break; + case 1: digit = hi; hi = 0; break; + default: + FMT_ASSERT(false, "invalid number of digits"); + } + if (digit != 0 || size != 0) + buffer[size++] = static_cast<char>('0' + digit); + --exp; + uint64_t remainder = (static_cast<uint64_t>(hi) << -one.e) + lo; + if (remainder <= delta) { + dec_exp += exp; + // TODO: use scaled_value + (void)scaled_value; + return; + } + } + for (;;) { + lo *= 10; + delta *= 10; + char digit = static_cast<char>(lo >> -one.e); + if (digit != 0 || size != 0) + buffer[size++] = static_cast<char>('0' + digit); + lo &= one.f - 1; + --exp; + if (lo < delta) { + dec_exp += exp; + return; + } + } +} + +FMT_FUNC void grisu2_format_positive(double value, char *buffer, size_t &size, + int &dec_exp) { + FMT_ASSERT(value > 0, "value is nonpositive"); + fp fp_value(value); + fp lower, upper; // w^- and w^+ in the Grisu paper. + fp_value.compute_boundaries(lower, upper); + // Find a cached power of 10 close to 1 / upper. + const int min_exp = -60; // alpha in Grisu. + auto dec_pow = get_cached_power( // \tilde{c}_{-k} in Grisu. + min_exp - (upper.e + fp::significand_size), dec_exp); + dec_exp = -dec_exp; + fp_value.normalize(); + fp scaled_value = fp_value * dec_pow; + fp scaled_lower = lower * dec_pow; // \tilde{M}^- in Grisu. + fp scaled_upper = upper * dec_pow; // \tilde{M}^+ in Grisu. + ++scaled_lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}. + --scaled_upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}. + uint64_t delta = scaled_upper.f - scaled_lower.f; + grisu2_gen_digits(scaled_value, scaled_upper, delta, buffer, size, dec_exp); +} + +FMT_FUNC void round(char *buffer, size_t &size, int &exp, + int digits_to_remove) { + size -= to_unsigned(digits_to_remove); + exp += digits_to_remove; + int digit = buffer[size] - '0'; + // TODO: proper rounding and carry + if (digit > 5 || (digit == 5 && (digits_to_remove > 1 || + (buffer[size - 1] - '0') % 2) != 0)) { + ++buffer[size - 1]; + } +} + +// Writes the exponent exp in the form "[+-]d{1,3}" to buffer. +FMT_FUNC char *write_exponent(char *buffer, int exp) { + FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range"); + if (exp < 0) { + *buffer++ = '-'; + exp = -exp; + } else { + *buffer++ = '+'; + } + if (exp >= 100) { + *buffer++ = static_cast<char>('0' + exp / 100); + exp %= 100; + const char *d = data::DIGITS + exp * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } else { + const char *d = data::DIGITS + exp * 2; + *buffer++ = d[0]; + *buffer++ = d[1]; + } + return buffer; +} + +FMT_FUNC void format_exp_notation( + char *buffer, size_t &size, int exp, int precision, bool upper) { + // Insert a decimal point after the first digit and add an exponent. + std::memmove(buffer + 2, buffer + 1, size - 1); + buffer[1] = '.'; + exp += static_cast<int>(size) - 1; + int num_digits = precision - static_cast<int>(size) + 1; + if (num_digits > 0) { + std::uninitialized_fill_n(buffer + size + 1, num_digits, '0'); + size += to_unsigned(num_digits); + } else if (num_digits < 0) { + round(buffer, size, exp, -num_digits); + } + char *p = buffer + size + 1; + *p++ = upper ? 'E' : 'e'; + size = to_unsigned(write_exponent(p, exp) - buffer); +} + +// Prettifies the output of the Grisu2 algorithm. +// The number is given as v = buffer * 10^exp. +FMT_FUNC void grisu2_prettify(char *buffer, size_t &size, int exp, + int precision, bool upper) { + // pow(10, full_exp - 1) <= v <= pow(10, full_exp). + int int_size = static_cast<int>(size); + int full_exp = int_size + exp; + const int exp_threshold = 21; + if (int_size <= full_exp && full_exp <= exp_threshold) { + // 1234e7 -> 12340000000[.0+] + std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0'); + char *p = buffer + full_exp; + if (precision > 0) { + *p++ = '.'; + std::uninitialized_fill_n(p, precision, '0'); + p += precision; + } + size = to_unsigned(p - buffer); + } else if (0 < full_exp && full_exp <= exp_threshold) { + // 1234e-2 -> 12.34[0+] + int fractional_size = -exp; + std::memmove(buffer + full_exp + 1, buffer + full_exp, + to_unsigned(fractional_size)); + buffer[full_exp] = '.'; + int num_zeros = precision - fractional_size; + if (num_zeros > 0) { + std::uninitialized_fill_n(buffer + size + 1, num_zeros, '0'); + size += to_unsigned(num_zeros); + } + ++size; + } else if (-6 < full_exp && full_exp <= 0) { + // 1234e-6 -> 0.001234 + int offset = 2 - full_exp; + std::memmove(buffer + offset, buffer, size); + buffer[0] = '0'; + buffer[1] = '.'; + std::uninitialized_fill_n(buffer + 2, -full_exp, '0'); + size = to_unsigned(int_size + offset); + } else { + format_exp_notation(buffer, size, exp, precision, upper); + } +} + +#if FMT_CLANG_VERSION +# define FMT_FALLTHROUGH [[clang::fallthrough]]; +#elif FMT_GCC_VERSION >= 700 +# define FMT_FALLTHROUGH [[gnu::fallthrough]]; +#else +# define FMT_FALLTHROUGH +#endif + +// Formats a nonnegative value using Grisu2 algorithm. Grisu2 doesn't give any +// guarantees on the shortness of the result. +FMT_FUNC void grisu2_format(double value, char *buffer, size_t &size, char type, + int precision, bool write_decimal_point) { + FMT_ASSERT(value >= 0, "value is negative"); + int dec_exp = 0; // K in Grisu. + if (value > 0) { + grisu2_format_positive(value, buffer, size, dec_exp); + } else { + *buffer = '0'; + size = 1; + } + const int default_precision = 6; + if (precision < 0) + precision = default_precision; + bool upper = false; + switch (type) { + case 'G': + upper = true; + FMT_FALLTHROUGH + case '\0': case 'g': { + int digits_to_remove = static_cast<int>(size) - precision; + if (digits_to_remove > 0) { + round(buffer, size, dec_exp, digits_to_remove); + // Remove trailing zeros. + while (size > 0 && buffer[size - 1] == '0') { + --size; + ++dec_exp; + } + } + precision = 0; + break; + } + case 'F': + upper = true; + FMT_FALLTHROUGH + case 'f': { + int digits_to_remove = -dec_exp - precision; + if (digits_to_remove > 0) { + if (digits_to_remove >= static_cast<int>(size)) + digits_to_remove = static_cast<int>(size) - 1; + round(buffer, size, dec_exp, digits_to_remove); + } + break; + } + case 'e': case 'E': + format_exp_notation(buffer, size, dec_exp, precision, type == 'E'); + return; + } + if (write_decimal_point && precision < 1) + precision = 1; + grisu2_prettify(buffer, size, dec_exp, precision, upper); +} +} // namespace internal + +#if FMT_USE_WINDOWS_H + +FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { + static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; + if (s.size() > INT_MAX) + FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG)); + int s_size = static_cast<int>(s.size()); + if (s_size == 0) { + // MultiByteToWideChar does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return; + } + + int length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_.resize(length + 1); + length = MultiByteToWideChar( + CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); + if (length == 0) + FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); + buffer_[length] = 0; +} + +FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { + if (int error_code = convert(s)) { + FMT_THROW(windows_error(error_code, + "cannot convert string from UTF-16 to UTF-8")); + } +} + +FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) { + if (s.size() > INT_MAX) + return ERROR_INVALID_PARAMETER; + int s_size = static_cast<int>(s.size()); + if (s_size == 0) { + // WideCharToMultiByte does not support zero length, handle separately. + buffer_.resize(1); + buffer_[0] = 0; + return 0; + } + + int length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_.resize(length + 1); + length = WideCharToMultiByte( + CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); + if (length == 0) + return GetLastError(); + buffer_[length] = 0; + return 0; +} + +FMT_FUNC void windows_error::init( + int err_code, string_view format_str, format_args args) { + error_code_ = err_code; + memory_buffer buffer; + internal::format_windows_error(buffer, err_code, vformat(format_str, args)); + std::runtime_error &base = *this; + base = std::runtime_error(to_string(buffer)); +} + +FMT_FUNC void internal::format_windows_error( + internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { + FMT_TRY { + wmemory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + wchar_t *system_message = &buf[0]; + int result = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + system_message, static_cast<uint32_t>(buf.size()), FMT_NULL); + if (result != 0) { + utf16_to_utf8 utf8_message; + if (utf8_message.convert(system_message) == ERROR_SUCCESS) { + writer w(out); + w.write(message); + w.write(": "); + w.write(utf8_message); + return; + } + break; + } + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +#endif // FMT_USE_WINDOWS_H + +FMT_FUNC void format_system_error( + internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { + FMT_TRY { + memory_buffer buf; + buf.resize(inline_buffer_size); + for (;;) { + char *system_message = &buf[0]; + int result = safe_strerror(error_code, system_message, buf.size()); + if (result == 0) { + writer w(out); + w.write(message); + w.write(": "); + w.write(system_message); + return; + } + if (result != ERANGE) + break; // Can't get error message, report error code instead. + buf.resize(buf.size() * 2); + } + } FMT_CATCH(...) {} + format_error_code(out, error_code, message); +} + +template <typename Char> +void basic_fixed_buffer<Char>::grow(std::size_t) { + FMT_THROW(std::runtime_error("buffer overflow")); +} + +FMT_FUNC void internal::error_handler::on_error(const char *message) { + FMT_THROW(format_error(message)); +} + +FMT_FUNC void report_system_error( + int error_code, fmt::string_view message) FMT_NOEXCEPT { + report_error(format_system_error, error_code, message); +} + +#if FMT_USE_WINDOWS_H +FMT_FUNC void report_windows_error( + int error_code, fmt::string_view message) FMT_NOEXCEPT { + report_error(internal::format_windows_error, error_code, message); +} +#endif + +FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) { + memory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), 1, buffer.size(), f); +} + +FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) { + wmemory_buffer buffer; + vformat_to(buffer, format_str, args); + std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f); +} + +FMT_FUNC void vprint(string_view format_str, format_args args) { + vprint(stdout, format_str, args); +} + +FMT_FUNC void vprint(wstring_view format_str, wformat_args args) { + vprint(stdout, format_str, args); +} + +#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) +FMT_FUNC locale locale_provider::locale() { return fmt::locale(); } +#endif + +FMT_END_NAMESPACE + +#ifdef _MSC_VER +# pragma warning(pop) +#endif + +#endif // FMT_FORMAT_INL_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/format.h b/src/nmodl/ext/spdlog/fmt/bundled/format.h new file mode 100644 index 0000000000..9f522f39b7 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/format.h @@ -0,0 +1,3720 @@ +/* + Formatting library for C++ + + Copyright (c) 2012 - present, Victor Zverovich + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef FMT_FORMAT_H_ +#define FMT_FORMAT_H_ + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <limits> +#include <memory> +#include <stdexcept> +#include <stdint.h> + +#ifdef __clang__ +# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) +#else +# define FMT_CLANG_VERSION 0 +#endif + +#ifdef __INTEL_COMPILER +# define FMT_ICC_VERSION __INTEL_COMPILER +#elif defined(__ICL) +# define FMT_ICC_VERSION __ICL +#else +# define FMT_ICC_VERSION 0 +#endif + +#ifdef __NVCC__ +# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) +#else +# define FMT_CUDA_VERSION 0 +#endif + +#include "core.h" + +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +# pragma GCC diagnostic push + +// Disable the warning about declaration shadowing because it affects too +// many valid cases. +# pragma GCC diagnostic ignored "-Wshadow" + +// Disable the warning about implicit conversions that may change the sign of +// an integer; silencing it otherwise would require many explicit casts. +# pragma GCC diagnostic ignored "-Wsign-conversion" +#endif + +# if FMT_CLANG_VERSION +# pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template" +# endif + +#ifdef _SECURE_SCL +# define FMT_SECURE_SCL _SECURE_SCL +#else +# define FMT_SECURE_SCL 0 +#endif + +#if FMT_SECURE_SCL +# include <iterator> +#endif + +#ifdef __has_builtin +# define FMT_HAS_BUILTIN(x) __has_builtin(x) +#else +# define FMT_HAS_BUILTIN(x) 0 +#endif + +#ifdef __GNUC_LIBSTD__ +# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) +#endif + +#ifndef FMT_THROW +# if FMT_EXCEPTIONS +# if FMT_MSC_VER +FMT_BEGIN_NAMESPACE +namespace internal { +template <typename Exception> +inline void do_throw(const Exception &x) { + // Silence unreachable code warnings in MSVC because these are nearly + // impossible to fix in a generic code. + volatile bool b = true; + if (b) + throw x; +} +} +FMT_END_NAMESPACE +# define FMT_THROW(x) fmt::internal::do_throw(x) +# else +# define FMT_THROW(x) throw x +# endif +# else +# define FMT_THROW(x) do { static_cast<void>(sizeof(x)); assert(false); } while(false); +# endif +#endif + +#ifndef FMT_USE_USER_DEFINED_LITERALS +// For Intel's compiler and NVIDIA's compiler both it and the system gcc/msc +// must support UDLs. +# if (FMT_HAS_FEATURE(cxx_user_literals) || \ + FMT_GCC_VERSION >= 407 || FMT_MSC_VER >= 1900) && \ + (!(FMT_ICC_VERSION || FMT_CUDA_VERSION) || \ + FMT_ICC_VERSION >= 1500 || FMT_CUDA_VERSION >= 700) +# define FMT_USE_USER_DEFINED_LITERALS 1 +# else +# define FMT_USE_USER_DEFINED_LITERALS 0 +# endif +#endif + +// EDG C++ Front End based compilers (icc, nvcc) do not currently support UDL +// templates. +#if FMT_USE_USER_DEFINED_LITERALS && \ + FMT_ICC_VERSION == 0 && \ + FMT_CUDA_VERSION == 0 && \ + ((FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L) || \ + (defined(FMT_CLANG_VERSION) && FMT_CLANG_VERSION >= 304)) +# define FMT_UDL_TEMPLATE 1 +#else +# define FMT_UDL_TEMPLATE 0 +#endif + +#ifndef FMT_USE_EXTERN_TEMPLATES +# ifndef FMT_HEADER_ONLY +# define FMT_USE_EXTERN_TEMPLATES \ + ((FMT_CLANG_VERSION >= 209 && __cplusplus >= 201103L) || \ + (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) +# else +# define FMT_USE_EXTERN_TEMPLATES 0 +# endif +#endif + +#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_trailing_return) || \ + FMT_MSC_VER >= 1600 +# define FMT_USE_TRAILING_RETURN 1 +#else +# define FMT_USE_TRAILING_RETURN 0 +#endif + +#ifndef FMT_USE_GRISU +# define FMT_USE_GRISU 0 +#endif + +// __builtin_clz is broken in clang with Microsoft CodeGen: +// https://github.com/fmtlib/fmt/issues/519 +#ifndef _MSC_VER +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) +# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) +# endif + +# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) +# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) +# endif +#endif + +// A workaround for gcc 4.4 that doesn't support union members with ctors. +#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 404) || \ + (FMT_MSC_VER && FMT_MSC_VER <= 1800) +# define FMT_UNION struct +#else +# define FMT_UNION union +#endif + +// Some compilers masquerade as both MSVC and GCC-likes or otherwise support +// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the +// MSVC intrinsics if the clz and clzll builtins are not available. +#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) +# include <intrin.h> // _BitScanReverse, _BitScanReverse64 + +FMT_BEGIN_NAMESPACE +namespace internal { +// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. +# ifndef __clang__ +# pragma intrinsic(_BitScanReverse) +# endif +inline uint32_t clz(uint32_t x) { + unsigned long r = 0; + _BitScanReverse(&r, x); + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 31 - r; +} +# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) + +# if defined(_WIN64) && !defined(__clang__) +# pragma intrinsic(_BitScanReverse64) +# endif + +inline uint32_t clzll(uint64_t x) { + unsigned long r = 0; +# ifdef _WIN64 + _BitScanReverse64(&r, x); +# else + // Scan the high 32 bits. + if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) + return 63 - (r + 32); + + // Scan the low 32 bits. + _BitScanReverse(&r, static_cast<uint32_t>(x)); +# endif + + assert(x != 0); + // Static analysis complains about using uninitialized data + // "r", but the only way that can happen is if "x" is 0, + // which the callers guarantee to not happen. +# pragma warning(suppress: 6102) + return 63 - r; +} +# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) +} +FMT_END_NAMESPACE +#endif + +FMT_BEGIN_NAMESPACE +namespace internal { + +// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't produce +// undefined behavior (e.g. due to type aliasing). +// Example: uint64_t d = bit_cast<uint64_t>(2.718); +template <typename Dest, typename Source> +inline Dest bit_cast(const Source& source) { + static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); + Dest dest; + std::memcpy(&dest, &source, sizeof(dest)); + return dest; +} + +// An implementation of begin and end for pre-C++11 compilers such as gcc 4. +template <typename C> +FMT_CONSTEXPR auto begin(const C &c) -> decltype(c.begin()) { + return c.begin(); +} +template <typename T, std::size_t N> +FMT_CONSTEXPR T *begin(T (&array)[N]) FMT_NOEXCEPT { return array; } +template <typename C> +FMT_CONSTEXPR auto end(const C &c) -> decltype(c.end()) { return c.end(); } +template <typename T, std::size_t N> +FMT_CONSTEXPR T *end(T (&array)[N]) FMT_NOEXCEPT { return array + N; } + +// For std::result_of in gcc 4.4. +template <typename Result> +struct function { + template <typename T> + struct result { typedef Result type; }; +}; + +struct dummy_int { + int data[2]; + operator int() const { return 0; } +}; +typedef std::numeric_limits<internal::dummy_int> fputil; + +// Dummy implementations of system functions such as signbit and ecvt called +// if the latter are not available. +inline dummy_int signbit(...) { return dummy_int(); } +inline dummy_int _ecvt_s(...) { return dummy_int(); } +inline dummy_int isinf(...) { return dummy_int(); } +inline dummy_int _finite(...) { return dummy_int(); } +inline dummy_int isnan(...) { return dummy_int(); } +inline dummy_int _isnan(...) { return dummy_int(); } + +inline bool use_grisu() { + return FMT_USE_GRISU && std::numeric_limits<double>::is_iec559; +} + +// Formats value using Grisu2 algorithm: +// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf +FMT_API void grisu2_format(double value, char *buffer, size_t &size, char type, + int precision, bool write_decimal_point); + +template <typename Allocator> +typename Allocator::value_type *allocate(Allocator& alloc, std::size_t n) { +#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 + return std::allocator_traits<Allocator>::allocate(alloc, n); +#else + return alloc.allocate(n); +#endif +} + +// A helper function to suppress bogus "conditional expression is constant" +// warnings. +template <typename T> +inline T const_check(T value) { return value; } +} // namespace internal +FMT_END_NAMESPACE + +namespace std { +// Standard permits specialization of std::numeric_limits. This specialization +// is used to resolve ambiguity between isinf and std::isinf in glibc: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 +// and the same for isnan and signbit. +template <> +class numeric_limits<fmt::internal::dummy_int> : + public std::numeric_limits<int> { + public: + // Portable version of isinf. + template <typename T> + static bool isinfinity(T x) { + using namespace fmt::internal; + // The resolution "priority" is: + // isinf macro > std::isinf > ::isinf > fmt::internal::isinf + if (const_check(sizeof(isinf(x)) != sizeof(dummy_int))) + return isinf(x) != 0; + return !_finite(static_cast<double>(x)); + } + + // Portable version of isnan. + template <typename T> + static bool isnotanumber(T x) { + using namespace fmt::internal; + if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) + return isnan(x) != 0; + return _isnan(static_cast<double>(x)) != 0; + } + + // Portable version of signbit. + static bool isnegative(double x) { + using namespace fmt::internal; + if (const_check(sizeof(signbit(x)) != sizeof(fmt::internal::dummy_int))) + return signbit(x) != 0; + if (x < 0) return true; + if (!isnotanumber(x)) return false; + int dec = 0, sign = 0; + char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. + _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); + return sign != 0; + } +}; +} // namespace std + +FMT_BEGIN_NAMESPACE +template <typename Range> +class basic_writer; + +template <typename OutputIt, typename T = typename OutputIt::value_type> +class output_range { + private: + OutputIt it_; + + // Unused yet. + typedef void sentinel; + sentinel end() const; + + public: + typedef OutputIt iterator; + typedef T value_type; + + explicit output_range(OutputIt it): it_(it) {} + OutputIt begin() const { return it_; } +}; + +// A range where begin() returns back_insert_iterator. +template <typename Container> +class back_insert_range: + public output_range<std::back_insert_iterator<Container>> { + typedef output_range<std::back_insert_iterator<Container>> base; + public: + typedef typename Container::value_type value_type; + + back_insert_range(Container &c): base(std::back_inserter(c)) {} + back_insert_range(typename base::iterator it): base(it) {} +}; + +typedef basic_writer<back_insert_range<internal::buffer>> writer; +typedef basic_writer<back_insert_range<internal::wbuffer>> wwriter; + +/** A formatting error such as invalid format string. */ +class format_error : public std::runtime_error { + public: + explicit format_error(const char *message) + : std::runtime_error(message) {} + + explicit format_error(const std::string &message) + : std::runtime_error(message) {} +}; + +namespace internal { + +#if FMT_SECURE_SCL +template <typename T> +struct checked { typedef stdext::checked_array_iterator<T*> type; }; + +// Make a checked iterator to avoid warnings on MSVC. +template <typename T> +inline stdext::checked_array_iterator<T*> make_checked(T *p, std::size_t size) { + return {p, size}; +} +#else +template <typename T> +struct checked { typedef T *type; }; +template <typename T> +inline T *make_checked(T *p, std::size_t) { return p; } +#endif + +template <typename T> +template <typename U> +void basic_buffer<T>::append(const U *begin, const U *end) { + std::size_t new_size = size_ + internal::to_unsigned(end - begin); + reserve(new_size); + std::uninitialized_copy(begin, end, + internal::make_checked(ptr_, capacity_) + size_); + size_ = new_size; +} +} // namespace internal + +// A UTF-8 code unit type. +struct char8_t { + char value; + FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { + return value != 0; + } +}; + +// A UTF-8 string view. +class u8string_view : public basic_string_view<char8_t> { + private: + typedef basic_string_view<char8_t> base; + + public: + using basic_string_view::basic_string_view; + using basic_string_view::char_type; + + u8string_view(const char *s) + : base(reinterpret_cast<const char8_t*>(s)) {} + + u8string_view(const char *s, size_t count) FMT_NOEXCEPT + : base(reinterpret_cast<const char8_t*>(s), count) {} +}; + +#if FMT_USE_USER_DEFINED_LITERALS +inline namespace literals { +inline u8string_view operator"" _u(const char *s, std::size_t n) { + return u8string_view(s, n); +} +} +#endif + +// A wrapper around std::locale used to reduce compile times since <locale> +// is very heavy. +class locale; + +class locale_provider { + public: + virtual ~locale_provider() {} + virtual fmt::locale locale(); +}; + +// The number of characters to store in the basic_memory_buffer object itself +// to avoid dynamic memory allocation. +enum { inline_buffer_size = 500 }; + +/** + \rst + A dynamically growing memory buffer for trivially copyable/constructible types + with the first ``SIZE`` elements stored in the object itself. + + You can use one of the following typedefs for common character types: + + +----------------+------------------------------+ + | Type | Definition | + +================+==============================+ + | memory_buffer | basic_memory_buffer<char> | + +----------------+------------------------------+ + | wmemory_buffer | basic_memory_buffer<wchar_t> | + +----------------+------------------------------+ + + **Example**:: + + fmt::memory_buffer out; + format_to(out, "The answer is {}.", 42); + + This will write the following output to the ``out`` object: + + .. code-block:: none + + The answer is 42. + + The output can be converted to an ``std::string`` with ``to_string(out)``. + \endrst + */ +template <typename T, std::size_t SIZE = inline_buffer_size, + typename Allocator = std::allocator<T> > +class basic_memory_buffer: private Allocator, public internal::basic_buffer<T> { + private: + T store_[SIZE]; + + // Deallocate memory allocated by the buffer. + void deallocate() { + T* data = this->data(); + if (data != store_) Allocator::deallocate(data, this->capacity()); + } + + protected: + void grow(std::size_t size) FMT_OVERRIDE; + + public: + explicit basic_memory_buffer(const Allocator &alloc = Allocator()) + : Allocator(alloc) { + this->set(store_, SIZE); + } + ~basic_memory_buffer() { deallocate(); } + + private: + // Move data from other to this buffer. + void move(basic_memory_buffer &other) { + Allocator &this_alloc = *this, &other_alloc = other; + this_alloc = std::move(other_alloc); + T* data = other.data(); + std::size_t size = other.size(), capacity = other.capacity(); + if (data == other.store_) { + this->set(store_, capacity); + std::uninitialized_copy(other.store_, other.store_ + size, + internal::make_checked(store_, capacity)); + } else { + this->set(data, capacity); + // Set pointer to the inline array so that delete is not called + // when deallocating. + other.set(other.store_, 0); + } + this->resize(size); + } + + public: + /** + \rst + Constructs a :class:`fmt::basic_memory_buffer` object moving the content + of the other object to it. + \endrst + */ + basic_memory_buffer(basic_memory_buffer &&other) { + move(other); + } + + /** + \rst + Moves the content of the other ``basic_memory_buffer`` object to this one. + \endrst + */ + basic_memory_buffer &operator=(basic_memory_buffer &&other) { + assert(this != &other); + deallocate(); + move(other); + return *this; + } + + // Returns a copy of the allocator associated with this buffer. + Allocator get_allocator() const { return *this; } +}; + +template <typename T, std::size_t SIZE, typename Allocator> +void basic_memory_buffer<T, SIZE, Allocator>::grow(std::size_t size) { + std::size_t old_capacity = this->capacity(); + std::size_t new_capacity = old_capacity + old_capacity / 2; + if (size > new_capacity) + new_capacity = size; + T *old_data = this->data(); + T *new_data = internal::allocate<Allocator>(*this, new_capacity); + // The following code doesn't throw, so the raw pointer above doesn't leak. + std::uninitialized_copy(old_data, old_data + this->size(), + internal::make_checked(new_data, new_capacity)); + this->set(new_data, new_capacity); + // deallocate must not throw according to the standard, but even if it does, + // the buffer already uses the new storage and will deallocate it in + // destructor. + if (old_data != store_) + Allocator::deallocate(old_data, old_capacity); +} + +typedef basic_memory_buffer<char> memory_buffer; +typedef basic_memory_buffer<wchar_t> wmemory_buffer; + +/** + \rst + A fixed-size memory buffer. For a dynamically growing buffer use + :class:`fmt::basic_memory_buffer`. + + Trying to increase the buffer size past the initial capacity will throw + ``std::runtime_error``. + \endrst + */ +template <typename Char> +class basic_fixed_buffer : public internal::basic_buffer<Char> { + public: + /** + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + given size. + \endrst + */ + basic_fixed_buffer(Char *array, std::size_t size) { + this->set(array, size); + } + + /** + \rst + Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the + size known at compile time. + \endrst + */ + template <std::size_t SIZE> + explicit basic_fixed_buffer(Char (&array)[SIZE]) { + this->set(array, SIZE); + } + + protected: + FMT_API void grow(std::size_t size) FMT_OVERRIDE; +}; + +namespace internal { + +template <typename Char> +struct char_traits; + +template <> +struct char_traits<char> { + // Formats a floating-point number. + template <typename T> + FMT_API static int format_float(char *buffer, std::size_t size, + const char *format, int precision, T value); +}; + +template <> +struct char_traits<wchar_t> { + template <typename T> + FMT_API static int format_float(wchar_t *buffer, std::size_t size, + const wchar_t *format, int precision, T value); +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template int char_traits<char>::format_float<double>( + char *buffer, std::size_t size, const char* format, int precision, + double value); +extern template int char_traits<char>::format_float<long double>( + char *buffer, std::size_t size, const char* format, int precision, + long double value); + +extern template int char_traits<wchar_t>::format_float<double>( + wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, + double value); +extern template int char_traits<wchar_t>::format_float<long double>( + wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, + long double value); +#endif + +template <typename Container> +inline typename std::enable_if< + is_contiguous<Container>::value, + typename checked<typename Container::value_type>::type>::type + reserve(std::back_insert_iterator<Container> &it, std::size_t n) { + Container &c = internal::get_container(it); + std::size_t size = c.size(); + c.resize(size + n); + return make_checked(&c[size], n); +} + +template <typename Iterator> +inline Iterator &reserve(Iterator &it, std::size_t) { return it; } + +template <typename Char> +class null_terminating_iterator; + +template <typename Char> +FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator<Char> it); + +// An iterator that produces a null terminator on *end. This simplifies parsing +// and allows comparing the performance of processing a null-terminated string +// vs string_view. +template <typename Char> +class null_terminating_iterator { + public: + typedef std::ptrdiff_t difference_type; + typedef Char value_type; + typedef const Char* pointer; + typedef const Char& reference; + typedef std::random_access_iterator_tag iterator_category; + + null_terminating_iterator() : ptr_(0), end_(0) {} + + FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) + : ptr_(ptr), end_(end) {} + + template <typename Range> + FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) + : ptr_(r.begin()), end_(r.end()) {} + + FMT_CONSTEXPR null_terminating_iterator &operator=(const Char *ptr) { + assert(ptr <= end_); + ptr_ = ptr; + return *this; + } + + FMT_CONSTEXPR Char operator*() const { + return ptr_ != end_ ? *ptr_ : 0; + } + + FMT_CONSTEXPR null_terminating_iterator operator++() { + ++ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator++(int) { + null_terminating_iterator result(*this); + ++ptr_; + return result; + } + + FMT_CONSTEXPR null_terminating_iterator operator--() { + --ptr_; + return *this; + } + + FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) { + return null_terminating_iterator(ptr_ + n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) { + return null_terminating_iterator(ptr_ - n, end_); + } + + FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) { + ptr_ += n; + return *this; + } + + FMT_CONSTEXPR difference_type operator-( + null_terminating_iterator other) const { + return ptr_ - other.ptr_; + } + + FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const { + return ptr_ != other.ptr_; + } + + bool operator>=(null_terminating_iterator other) const { + return ptr_ >= other.ptr_; + } + + // This should be a friend specialization pointer_from<Char> but the latter + // doesn't compile by gcc 5.1 due to a compiler bug. + template <typename CharT> + friend FMT_CONSTEXPR_DECL const CharT *pointer_from( + null_terminating_iterator<CharT> it); + + private: + const Char *ptr_; + const Char *end_; +}; + +template <typename T> +FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; } + +template <typename Char> +FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator<Char> it) { + return it.ptr_; +} + +// An output iterator that counts the number of objects written to it and +// discards them. +template <typename T> +class counting_iterator { + private: + std::size_t count_; + mutable T blackhole_; + + public: + typedef std::output_iterator_tag iterator_category; + typedef T value_type; + typedef std::ptrdiff_t difference_type; + typedef T* pointer; + typedef T& reference; + typedef counting_iterator _Unchecked_type; // Mark iterator as checked. + + counting_iterator(): count_(0) {} + + std::size_t count() const { return count_; } + + counting_iterator& operator++() { + ++count_; + return *this; + } + + counting_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + T &operator*() const { return blackhole_; } +}; + +// An output iterator that truncates the output and counts the number of objects +// written to it. +template <typename OutputIt> +class truncating_iterator { + private: + typedef std::iterator_traits<OutputIt> traits; + + OutputIt out_; + std::size_t limit_; + std::size_t count_; + mutable typename traits::value_type blackhole_; + + public: + typedef std::output_iterator_tag iterator_category; + typedef typename traits::value_type value_type; + typedef typename traits::difference_type difference_type; + typedef typename traits::pointer pointer; + typedef typename traits::reference reference; + typedef truncating_iterator _Unchecked_type; // Mark iterator as checked. + + truncating_iterator(OutputIt out, std::size_t limit) + : out_(out), limit_(limit), count_(0) {} + + OutputIt base() const { return out_; } + std::size_t count() const { return count_; } + + truncating_iterator& operator++() { + if (count_++ < limit_) + ++out_; + return *this; + } + + truncating_iterator operator++(int) { + auto it = *this; + ++*this; + return it; + } + + reference operator*() const { return count_ < limit_ ? *out_ : blackhole_; } +}; + +// Returns true if value is negative, false otherwise. +// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. +template <typename T> +FMT_CONSTEXPR typename std::enable_if< + std::numeric_limits<T>::is_signed, bool>::type is_negative(T value) { + return value < 0; +} +template <typename T> +FMT_CONSTEXPR typename std::enable_if< + !std::numeric_limits<T>::is_signed, bool>::type is_negative(T) { + return false; +} + +template <typename T> +struct int_traits { + // Smallest of uint32_t and uint64_t that is large enough to represent + // all values of T. + typedef typename std::conditional< + std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>::type main_type; +}; + +// Static data is placed in this class template to allow header-only +// configuration. +template <typename T = void> +struct FMT_API basic_data { + static const uint32_t POWERS_OF_10_32[]; + static const uint32_t ZERO_OR_POWERS_OF_10_32[]; + static const uint64_t ZERO_OR_POWERS_OF_10_64[]; + static const uint64_t POW10_SIGNIFICANDS[]; + static const int16_t POW10_EXPONENTS[]; + static const char DIGITS[]; + static const char RESET_COLOR[]; + static const wchar_t WRESET_COLOR[]; +}; + +#if FMT_USE_EXTERN_TEMPLATES +extern template struct basic_data<void>; +#endif + +typedef basic_data<> data; + +#ifdef FMT_BUILTIN_CLZLL +// Returns the number of decimal digits in n. Leading zeros are not counted +// except for n == 0 in which case count_digits returns 1. +inline unsigned count_digits(uint64_t n) { + // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 + // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. + int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_64[t]) + 1; +} +#else +// Fallback version of count_digits used when __builtin_clz is not available. +inline unsigned count_digits(uint64_t n) { + unsigned count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + if (n < 10) return count; + if (n < 100) return count + 1; + if (n < 1000) return count + 2; + if (n < 10000) return count + 3; + n /= 10000u; + count += 4; + } +} +#endif + +// Counts the number of code points in a UTF-8 string. +FMT_API size_t count_code_points(u8string_view s); + +#if FMT_HAS_CPP_ATTRIBUTE(always_inline) +# define FMT_ALWAYS_INLINE __attribute__((always_inline)) +#else +# define FMT_ALWAYS_INLINE +#endif + +template <typename Handler> +inline char *lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE; + +// Computes g = floor(log10(n)) and calls h.on<g>(n); +template <typename Handler> +inline char *lg(uint32_t n, Handler h) { + return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n) + : n < 1000000 + ? n < 10000 ? n < 1000 ? h.template on<2>(n) + : h.template on<3>(n) + : n < 100000 ? h.template on<4>(n) + : h.template on<5>(n) + : n < 100000000 ? n < 10000000 ? h.template on<6>(n) + : h.template on<7>(n) + : n < 1000000000 ? h.template on<8>(n) + : h.template on<9>(n); +} + +// An lg handler that formats a decimal number. +// Usage: lg(n, decimal_formatter(buffer)); +class decimal_formatter { + private: + char *buffer_; + + void write_pair(unsigned N, uint32_t index) { + std::memcpy(buffer_ + N, data::DIGITS + index * 2, 2); + } + + public: + explicit decimal_formatter(char *buf) : buffer_(buf) {} + + template <unsigned N> char *on(uint32_t u) { + if (N == 0) { + *buffer_ = static_cast<char>(u) + '0'; + } else if (N == 1) { + write_pair(0, u); + } else { + // The idea of using 4.32 fixed-point numbers is based on + // https://github.com/jeaiii/itoa + unsigned n = N - 1; + unsigned a = n / 5 * n * 53 / 16; + uint64_t t = ((1ULL << (32 + a)) / + data::ZERO_OR_POWERS_OF_10_32[n] + 1 - n / 9); + t = ((t * u) >> a) + n / 5 * 4; + write_pair(0, t >> 32); + for (unsigned i = 2; i < N; i += 2) { + t = 100ULL * static_cast<uint32_t>(t); + write_pair(i, t >> 32); + } + if (N % 2 == 0) { + buffer_[N] = static_cast<char>( + (10ULL * static_cast<uint32_t>(t)) >> 32) + '0'; + } + } + return buffer_ += N + 1; + } +}; + +// An lg handler that formats a decimal number with a terminating null. +class decimal_formatter_null : public decimal_formatter { + public: + explicit decimal_formatter_null(char *buf) : decimal_formatter(buf) {} + + template <unsigned N> char *on(uint32_t u) { + char *buf = decimal_formatter::on<N>(u); + *buf = '\0'; + return buf; + } +}; + +#ifdef FMT_BUILTIN_CLZ +// Optional version of count_digits for better performance on 32-bit platforms. +inline unsigned count_digits(uint32_t n) { + int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; + return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_32[t]) + 1; +} +#endif + +// A functor that doesn't add a thousands separator. +struct no_thousands_sep { + typedef char char_type; + + template <typename Char> + void operator()(Char *) {} +}; + +// A functor that adds a thousands separator. +template <typename Char> +class add_thousands_sep { + private: + basic_string_view<Char> sep_; + + // Index of a decimal digit with the least significant digit having index 0. + unsigned digit_index_; + + public: + typedef Char char_type; + + explicit add_thousands_sep(basic_string_view<Char> sep) + : sep_(sep), digit_index_(0) {} + + void operator()(Char *&buffer) { + if (++digit_index_ % 3 != 0) + return; + buffer -= sep_.size(); + std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), + internal::make_checked(buffer, sep_.size())); + } +}; + +template <typename Char> +FMT_API Char thousands_sep(locale_provider *lp); + +// Formats a decimal unsigned integer value writing into buffer. +// thousands_sep is a functor that is called after writing each char to +// add a thousands separator if necessary. +template <typename UInt, typename Char, typename ThousandsSep> +inline Char *format_decimal(Char *buffer, UInt value, unsigned num_digits, + ThousandsSep thousands_sep) { + buffer += num_digits; + Char *end = buffer; + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast<unsigned>((value % 100) * 2); + value /= 100; + *--buffer = data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = data::DIGITS[index]; + thousands_sep(buffer); + } + if (value < 10) { + *--buffer = static_cast<char>('0' + value); + return end; + } + unsigned index = static_cast<unsigned>(value * 2); + *--buffer = data::DIGITS[index + 1]; + thousands_sep(buffer); + *--buffer = data::DIGITS[index]; + return end; +} + +template <typename UInt, typename Iterator, typename ThousandsSep> +inline Iterator format_decimal( + Iterator out, UInt value, unsigned num_digits, ThousandsSep sep) { + typedef typename ThousandsSep::char_type char_type; + // Buffer should be large enough to hold all digits (digits10 + 1) and null. + char_type buffer[std::numeric_limits<UInt>::digits10 + 2]; + format_decimal(buffer, value, num_digits, sep); + return std::copy_n(buffer, num_digits, out); +} + +template <typename It, typename UInt> +inline It format_decimal(It out, UInt value, unsigned num_digits) { + return format_decimal(out, value, num_digits, no_thousands_sep()); +} + +template <unsigned BASE_BITS, typename Char, typename UInt> +inline Char *format_uint(Char *buffer, UInt value, unsigned num_digits, + bool upper = false) { + buffer += num_digits; + Char *end = buffer; + do { + const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + unsigned digit = (value & ((1 << BASE_BITS) - 1)); + *--buffer = BASE_BITS < 4 ? static_cast<char>('0' + digit) : digits[digit]; + } while ((value >>= BASE_BITS) != 0); + return end; +} + +template <unsigned BASE_BITS, typename It, typename UInt> +inline It format_uint(It out, UInt value, unsigned num_digits, + bool upper = false) { + // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) + // and null. + char buffer[std::numeric_limits<UInt>::digits / BASE_BITS + 2]; + format_uint<BASE_BITS>(buffer, value, num_digits, upper); + return std::copy_n(buffer, num_digits, out); +} + +#ifndef _WIN32 +# define FMT_USE_WINDOWS_H 0 +#elif !defined(FMT_USE_WINDOWS_H) +# define FMT_USE_WINDOWS_H 1 +#endif + +// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. +// All the functionality that relies on it will be disabled too. +#if FMT_USE_WINDOWS_H +// A converter from UTF-8 to UTF-16. +// It is only provided for Windows since other systems support UTF-8 natively. +class utf8_to_utf16 { + private: + wmemory_buffer buffer_; + + public: + FMT_API explicit utf8_to_utf16(string_view s); + operator wstring_view() const { return wstring_view(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const wchar_t *c_str() const { return &buffer_[0]; } + std::wstring str() const { return std::wstring(&buffer_[0], size()); } +}; + +// A converter from UTF-16 to UTF-8. +// It is only provided for Windows since other systems support UTF-8 natively. +class utf16_to_utf8 { + private: + memory_buffer buffer_; + + public: + utf16_to_utf8() {} + FMT_API explicit utf16_to_utf8(wstring_view s); + operator string_view() const { return string_view(&buffer_[0], size()); } + size_t size() const { return buffer_.size() - 1; } + const char *c_str() const { return &buffer_[0]; } + std::string str() const { return std::string(&buffer_[0], size()); } + + // Performs conversion returning a system error code instead of + // throwing exception on conversion error. This method may still throw + // in case of memory allocation error. + FMT_API int convert(wstring_view s); +}; + +FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, + fmt::string_view message) FMT_NOEXCEPT; +#endif + +template <typename T = void> +struct null {}; +} // namespace internal + +enum alignment { + ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC +}; + +// Flags. +enum {SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8}; + +enum format_spec_tag {fill_tag, align_tag, width_tag, type_tag}; + +// Format specifier. +template <typename T, format_spec_tag> +class format_spec { + private: + T value_; + + public: + typedef T value_type; + + explicit format_spec(T value) : value_(value) {} + + T value() const { return value_; } +}; + +// template <typename Char> +// typedef format_spec<Char, fill_tag> fill_spec; +template <typename Char> +class fill_spec : public format_spec<Char, fill_tag> { + public: + explicit fill_spec(Char value) : format_spec<Char, fill_tag>(value) {} +}; + +typedef format_spec<unsigned, width_tag> width_spec; +typedef format_spec<char, type_tag> type_spec; + +// An empty format specifier. +struct empty_spec {}; + +// An alignment specifier. +struct align_spec : empty_spec { + unsigned width_; + // Fill is always wchar_t and cast to char if necessary to avoid having + // two specialization of AlignSpec and its subclasses. + wchar_t fill_; + alignment align_; + + FMT_CONSTEXPR align_spec( + unsigned width, wchar_t fill, alignment align = ALIGN_DEFAULT) + : width_(width), fill_(fill), align_(align) {} + + FMT_CONSTEXPR unsigned width() const { return width_; } + FMT_CONSTEXPR wchar_t fill() const { return fill_; } + FMT_CONSTEXPR alignment align() const { return align_; } + + int precision() const { return -1; } +}; + +// Format specifiers. +template <typename Char> +class basic_format_specs : public align_spec { + public: + unsigned flags_; + int precision_; + Char type_; + + FMT_CONSTEXPR basic_format_specs( + unsigned width = 0, char type = 0, wchar_t fill = ' ') + : align_spec(width, fill), flags_(0), precision_(-1), type_(type) {} + + FMT_CONSTEXPR bool flag(unsigned f) const { return (flags_ & f) != 0; } + FMT_CONSTEXPR int precision() const { return precision_; } + FMT_CONSTEXPR Char type() const { return type_; } +}; + +typedef basic_format_specs<char> format_specs; + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR unsigned basic_parse_context<Char, ErrorHandler>::next_arg_id() { + if (next_arg_id_ >= 0) + return internal::to_unsigned(next_arg_id_++); + on_error("cannot switch from manual to automatic argument indexing"); + return 0; +} + +namespace internal { + +template <typename S> +struct format_string_traits< + S, typename std::enable_if<std::is_base_of<compile_string, S>::value>::type>: + format_string_traits_base<char> {}; + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_int_type_spec(Char spec, Handler &&handler) { + switch (spec) { + case 0: case 'd': + handler.on_dec(); + break; + case 'x': case 'X': + handler.on_hex(); + break; + case 'b': case 'B': + handler.on_bin(); + break; + case 'o': + handler.on_oct(); + break; + case 'n': + handler.on_num(); + break; + default: + handler.on_error(); + } +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_float_type_spec(Char spec, Handler &&handler) { + switch (spec) { + case 0: case 'g': case 'G': + handler.on_general(); + break; + case 'e': case 'E': + handler.on_exp(); + break; + case 'f': case 'F': + handler.on_fixed(); + break; + case 'a': case 'A': + handler.on_hex(); + break; + default: + handler.on_error(); + break; + } +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_char_specs( + const basic_format_specs<Char> *specs, Handler &&handler) { + if (!specs) return handler.on_char(); + if (specs->type() && specs->type() != 'c') return handler.on_int(); + if (specs->align() == ALIGN_NUMERIC || specs->flag(~0u) != 0) + handler.on_error("invalid format specifier for char"); + handler.on_char(); +} + +template <typename Char, typename Handler> +FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler &&handler) { + if (spec == 0 || spec == 's') + handler.on_string(); + else if (spec == 'p') + handler.on_pointer(); + else + handler.on_error("invalid type specifier"); +} + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler &&eh) { + if (spec != 0 && spec != 's') + eh.on_error("invalid type specifier"); +} + +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler &&eh) { + if (spec != 0 && spec != 'p') + eh.on_error("invalid type specifier"); +} + +template <typename ErrorHandler> +class int_type_checker : private ErrorHandler { + public: + FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} + + FMT_CONSTEXPR void on_dec() {} + FMT_CONSTEXPR void on_hex() {} + FMT_CONSTEXPR void on_bin() {} + FMT_CONSTEXPR void on_oct() {} + FMT_CONSTEXPR void on_num() {} + + FMT_CONSTEXPR void on_error() { + ErrorHandler::on_error("invalid type specifier"); + } +}; + +template <typename ErrorHandler> +class float_type_checker : private ErrorHandler { + public: + FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh) + : ErrorHandler(eh) {} + + FMT_CONSTEXPR void on_general() {} + FMT_CONSTEXPR void on_exp() {} + FMT_CONSTEXPR void on_fixed() {} + FMT_CONSTEXPR void on_hex() {} + + FMT_CONSTEXPR void on_error() { + ErrorHandler::on_error("invalid type specifier"); + } +}; + +template <typename ErrorHandler, typename CharType> +class char_specs_checker : public ErrorHandler { + private: + CharType type_; + + public: + FMT_CONSTEXPR char_specs_checker(CharType type, ErrorHandler eh) + : ErrorHandler(eh), type_(type) {} + + FMT_CONSTEXPR void on_int() { + handle_int_type_spec(type_, int_type_checker<ErrorHandler>(*this)); + } + FMT_CONSTEXPR void on_char() {} +}; + +template <typename ErrorHandler> +class cstring_type_checker : public ErrorHandler { + public: + FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) + : ErrorHandler(eh) {} + + FMT_CONSTEXPR void on_string() {} + FMT_CONSTEXPR void on_pointer() {} +}; + +template <typename Context> +void arg_map<Context>::init(const basic_format_args<Context> &args) { + if (map_) + return; + map_ = new entry[args.max_size()]; + bool use_values = args.type(max_packed_args - 1) == internal::none_type; + if (use_values) { + for (unsigned i = 0;/*nothing*/; ++i) { + internal::type arg_type = args.type(i); + switch (arg_type) { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.values_[i]); + break; + default: + break; // Do nothing. + } + } + } + for (unsigned i = 0; ; ++i) { + switch (args.args_[i].type_) { + case internal::none_type: + return; + case internal::named_arg_type: + push_back(args.args_[i].value_); + break; + default: + break; // Do nothing. + } + } +} + +template <typename Range> +class arg_formatter_base { + public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval<Range>().begin()) iterator; + typedef basic_format_specs<char_type> format_specs; + + private: + typedef basic_writer<Range> writer_type; + writer_type writer_; + format_specs *specs_; + + struct char_writer { + char_type value; + template <typename It> + void operator()(It &&it) const { *it++ = value; } + }; + + void write_char(char_type value) { + if (specs_) + writer_.write_padded(1, *specs_, char_writer{value}); + else + writer_.write(value); + } + + void write_pointer(const void *p) { + format_specs specs = specs_ ? *specs_ : format_specs(); + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + writer_.write_int(reinterpret_cast<uintptr_t>(p), specs); + } + + protected: + writer_type &writer() { return writer_; } + format_specs *spec() { return specs_; } + iterator out() { return writer_.out(); } + + void write(bool value) { + string_view sv(value ? "true" : "false"); + specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); + } + + void write(const char_type *value) { + if (!value) + FMT_THROW(format_error("string pointer is null")); + auto length = std::char_traits<char_type>::length(value); + basic_string_view<char_type> sv(value, length); + specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); + } + + public: + arg_formatter_base(Range r, format_specs *s): writer_(r), specs_(s) {} + + iterator operator()(monostate) { + FMT_ASSERT(false, "invalid argument type"); + return out(); + } + + template <typename T> + typename std::enable_if<std::is_integral<T>::value, iterator>::type + operator()(T value) { + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same<T, bool>::value) { + if (specs_ && specs_->type_) + return (*this)(value ? 1 : 0); + write(value != 0); + } else if (std::is_same<T, char_type>::value) { + internal::handle_char_specs( + specs_, char_spec_handler(*this, static_cast<char_type>(value))); + } else { + specs_ ? writer_.write_int(value, *specs_) : writer_.write(value); + } + return out(); + } + + template <typename T> + typename std::enable_if<std::is_floating_point<T>::value, iterator>::type + operator()(T value) { + writer_.write_double(value, specs_ ? *specs_ : format_specs()); + return out(); + } + + struct char_spec_handler : internal::error_handler { + arg_formatter_base &formatter; + char_type value; + + char_spec_handler(arg_formatter_base& f, char_type val) + : formatter(f), value(val) {} + + void on_int() { + if (formatter.specs_) + formatter.writer_.write_int(value, *formatter.specs_); + else + formatter.writer_.write(value); + } + void on_char() { formatter.write_char(value); } + }; + + struct cstring_spec_handler : internal::error_handler { + arg_formatter_base &formatter; + const char_type *value; + + cstring_spec_handler(arg_formatter_base &f, const char_type *val) + : formatter(f), value(val) {} + + void on_string() { formatter.write(value); } + void on_pointer() { formatter.write_pointer(value); } + }; + + iterator operator()(const char_type *value) { + if (!specs_) return write(value), out(); + internal::handle_cstring_type_spec( + specs_->type_, cstring_spec_handler(*this, value)); + return out(); + } + + iterator operator()(basic_string_view<char_type> value) { + if (specs_) { + internal::check_string_type_spec( + specs_->type_, internal::error_handler()); + writer_.write_str(value, *specs_); + } else { + writer_.write(value); + } + return out(); + } + + iterator operator()(const void *value) { + if (specs_) + check_pointer_type_spec(specs_->type_, internal::error_handler()); + write_pointer(value); + return out(); + } +}; + +template <typename Char> +FMT_CONSTEXPR bool is_name_start(Char c) { + return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; +} + +// DEPRECATED: Parses the input as an unsigned integer. This function assumes +// that the first character is a digit and presence of a non-digit character at +// the end. +// it: an iterator pointing to the beginning of the input range. +template <typename Iterator, typename ErrorHandler> +FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { + assert('0' <= *it && *it <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits<int>::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*it - '0'); + // Workaround for MSVC "setup_exception stack overflow" error: + auto next = it; + ++next; + it = next; + } while ('0' <= *it && *it <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} + +// Parses the range [begin, end) as an unsigned integer. This function assumes +// that the range is non-empty and the first character is a digit. +template <typename Char, typename ErrorHandler> +FMT_CONSTEXPR unsigned parse_nonnegative_int( + const Char *&begin, const Char *end, ErrorHandler &&eh) { + assert(begin != end && '0' <= *begin && *begin <= '9'); + unsigned value = 0; + // Convert to unsigned to prevent a warning. + unsigned max_int = (std::numeric_limits<int>::max)(); + unsigned big = max_int / 10; + do { + // Check for overflow. + if (value > big) { + value = max_int + 1; + break; + } + value = value * 10 + unsigned(*begin++ - '0'); + } while (begin != end && '0' <= *begin && *begin <= '9'); + if (value > max_int) + eh.on_error("number is too big"); + return value; +} + +template <typename Char, typename Context> +class custom_formatter: public function<bool> { + private: + Context &ctx_; + + public: + explicit custom_formatter(Context &ctx): ctx_(ctx) {} + + bool operator()(typename basic_format_arg<Context>::handle h) const { + h.format(ctx_); + return true; + } + + template <typename T> + bool operator()(T) const { return false; } +}; + +template <typename T> +struct is_integer { + enum { + value = std::is_integral<T>::value && !std::is_same<T, bool>::value && + !std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value + }; +}; + +template <typename ErrorHandler> +class width_checker: public function<unsigned long long> { + public: + explicit FMT_CONSTEXPR width_checker(ErrorHandler &eh) : handler_(eh) {} + + template <typename T> + FMT_CONSTEXPR + typename std::enable_if< + is_integer<T>::value, unsigned long long>::type operator()(T value) { + if (is_negative(value)) + handler_.on_error("negative width"); + return static_cast<unsigned long long>(value); + } + + template <typename T> + FMT_CONSTEXPR typename std::enable_if< + !is_integer<T>::value, unsigned long long>::type operator()(T) { + handler_.on_error("width is not integer"); + return 0; + } + + private: + ErrorHandler &handler_; +}; + +template <typename ErrorHandler> +class precision_checker: public function<unsigned long long> { + public: + explicit FMT_CONSTEXPR precision_checker(ErrorHandler &eh) : handler_(eh) {} + + template <typename T> + FMT_CONSTEXPR typename std::enable_if< + is_integer<T>::value, unsigned long long>::type operator()(T value) { + if (is_negative(value)) + handler_.on_error("negative precision"); + return static_cast<unsigned long long>(value); + } + + template <typename T> + FMT_CONSTEXPR typename std::enable_if< + !is_integer<T>::value, unsigned long long>::type operator()(T) { + handler_.on_error("precision is not integer"); + return 0; + } + + private: + ErrorHandler &handler_; +}; + +// A format specifier handler that sets fields in basic_format_specs. +template <typename Char> +class specs_setter { + public: + explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char> &specs): + specs_(specs) {} + + FMT_CONSTEXPR specs_setter(const specs_setter &other) : specs_(other.specs_) {} + + FMT_CONSTEXPR void on_align(alignment align) { specs_.align_ = align; } + FMT_CONSTEXPR void on_fill(Char fill) { specs_.fill_ = fill; } + FMT_CONSTEXPR void on_plus() { specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; } + FMT_CONSTEXPR void on_minus() { specs_.flags_ |= MINUS_FLAG; } + FMT_CONSTEXPR void on_space() { specs_.flags_ |= SIGN_FLAG; } + FMT_CONSTEXPR void on_hash() { specs_.flags_ |= HASH_FLAG; } + + FMT_CONSTEXPR void on_zero() { + specs_.align_ = ALIGN_NUMERIC; + specs_.fill_ = '0'; + } + + FMT_CONSTEXPR void on_width(unsigned width) { specs_.width_ = width; } + FMT_CONSTEXPR void on_precision(unsigned precision) { + specs_.precision_ = static_cast<int>(precision); + } + FMT_CONSTEXPR void end_precision() {} + + FMT_CONSTEXPR void on_type(Char type) { specs_.type_ = type; } + + protected: + basic_format_specs<Char> &specs_; +}; + +// A format specifier handler that checks if specifiers are consistent with the +// argument type. +template <typename Handler> +class specs_checker : public Handler { + public: + FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) + : Handler(handler), arg_type_(arg_type) {} + + FMT_CONSTEXPR specs_checker(const specs_checker &other) + : Handler(other), arg_type_(other.arg_type_) {} + + FMT_CONSTEXPR void on_align(alignment align) { + if (align == ALIGN_NUMERIC) + require_numeric_argument(); + Handler::on_align(align); + } + + FMT_CONSTEXPR void on_plus() { + check_sign(); + Handler::on_plus(); + } + + FMT_CONSTEXPR void on_minus() { + check_sign(); + Handler::on_minus(); + } + + FMT_CONSTEXPR void on_space() { + check_sign(); + Handler::on_space(); + } + + FMT_CONSTEXPR void on_hash() { + require_numeric_argument(); + Handler::on_hash(); + } + + FMT_CONSTEXPR void on_zero() { + require_numeric_argument(); + Handler::on_zero(); + } + + FMT_CONSTEXPR void end_precision() { + if (is_integral(arg_type_) || arg_type_ == pointer_type) + this->on_error("precision not allowed for this argument type"); + } + + private: + FMT_CONSTEXPR void require_numeric_argument() { + if (!is_arithmetic(arg_type_)) + this->on_error("format specifier requires numeric argument"); + } + + FMT_CONSTEXPR void check_sign() { + require_numeric_argument(); + if (is_integral(arg_type_) && arg_type_ != int_type && + arg_type_ != long_long_type && arg_type_ != internal::char_type) { + this->on_error("format specifier requires signed argument"); + } + } + + internal::type arg_type_; +}; + +template <template <typename> class Handler, typename T, + typename Context, typename ErrorHandler> +FMT_CONSTEXPR void set_dynamic_spec( + T &value, basic_format_arg<Context> arg, ErrorHandler eh) { + unsigned long long big_value = fmt::visit(Handler<ErrorHandler>(eh), arg); + if (big_value > (std::numeric_limits<int>::max)()) + eh.on_error("number is too big"); + value = static_cast<T>(big_value); +} + +struct auto_id {}; + +// The standard format specifier handler with checking. +template <typename Context> +class specs_handler: public specs_setter<typename Context::char_type> { + public: + typedef typename Context::char_type char_type; + + FMT_CONSTEXPR specs_handler( + basic_format_specs<char_type> &specs, Context &ctx) + : specs_setter<char_type>(specs), context_(ctx) {} + + template <typename Id> + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + set_dynamic_spec<width_checker>( + this->specs_.width_, get_arg(arg_id), context_.error_handler()); + } + + template <typename Id> + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + set_dynamic_spec<precision_checker>( + this->specs_.precision_, get_arg(arg_id), context_.error_handler()); + } + + void on_error(const char *message) { + context_.on_error(message); + } + + private: + FMT_CONSTEXPR basic_format_arg<Context> get_arg(auto_id) { + return context_.next_arg(); + } + + template <typename Id> + FMT_CONSTEXPR basic_format_arg<Context> get_arg(Id arg_id) { + context_.parse_context().check_arg_id(arg_id); + return context_.get_arg(arg_id); + } + + Context &context_; +}; + +// An argument reference. +template <typename Char> +struct arg_ref { + enum Kind { NONE, INDEX, NAME }; + + FMT_CONSTEXPR arg_ref() : kind(NONE), index(0) {} + FMT_CONSTEXPR explicit arg_ref(unsigned index) : kind(INDEX), index(index) {} + explicit arg_ref(basic_string_view<Char> name) : kind(NAME), name(name) {} + + FMT_CONSTEXPR arg_ref &operator=(unsigned idx) { + kind = INDEX; + index = idx; + return *this; + } + + Kind kind; + FMT_UNION { + unsigned index; + basic_string_view<Char> name; + }; +}; + +// Format specifiers with width and precision resolved at formatting rather +// than parsing time to allow re-using the same parsed specifiers with +// differents sets of arguments (precompilation of format strings). +template <typename Char> +struct dynamic_format_specs : basic_format_specs<Char> { + arg_ref<Char> width_ref; + arg_ref<Char> precision_ref; +}; + +// Format spec handler that saves references to arguments representing dynamic +// width and precision to be resolved at formatting time. +template <typename ParseContext> +class dynamic_specs_handler : + public specs_setter<typename ParseContext::char_type> { + public: + typedef typename ParseContext::char_type char_type; + + FMT_CONSTEXPR dynamic_specs_handler( + dynamic_format_specs<char_type> &specs, ParseContext &ctx) + : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} + + FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler &other) + : specs_setter<char_type>(other), + specs_(other.specs_), context_(other.context_) {} + + template <typename Id> + FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { + specs_.width_ref = make_arg_ref(arg_id); + } + + template <typename Id> + FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { + specs_.precision_ref = make_arg_ref(arg_id); + } + + FMT_CONSTEXPR void on_error(const char *message) { + context_.on_error(message); + } + + private: + typedef arg_ref<char_type> arg_ref_type; + + template <typename Id> + FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { + context_.check_arg_id(arg_id); + return arg_ref_type(arg_id); + } + + FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) { + return arg_ref_type(context_.next_arg_id()); + } + + dynamic_format_specs<char_type> &specs_; + ParseContext &context_; +}; + +template <typename Iterator, typename IDHandler> +FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { + typedef typename std::iterator_traits<Iterator>::value_type char_type; + char_type c = *it; + if (c == '}' || c == ':') { + handler(); + return it; + } + if (c >= '0' && c <= '9') { + unsigned index = parse_nonnegative_int(it, handler); + if (*it != '}' && *it != ':') { + handler.on_error("invalid format string"); + return it; + } + handler(index); + return it; + } + if (!is_name_start(c)) { + handler.on_error("invalid format string"); + return it; + } + auto start = it; + do { + c = *++it; + } while (is_name_start(c) || ('0' <= c && c <= '9')); + handler(basic_string_view<char_type>( + pointer_from(start), to_unsigned(it - start))); + return it; +} + +template <typename Char, typename IDHandler> +FMT_CONSTEXPR const Char *parse_arg_id( + const Char *begin, const Char *end, IDHandler &&handler) { + assert(begin != end); + Char c = *begin; + if (c == '}' || c == ':') + return handler(), begin; + if (c >= '0' && c <= '9') { + unsigned index = parse_nonnegative_int(begin, end, handler); + if (begin == end || (*begin != '}' && *begin != ':')) + return handler.on_error("invalid format string"), begin; + handler(index); + return begin; + } + if (!is_name_start(c)) + return handler.on_error("invalid format string"), begin; + auto it = begin; + do { + c = *++it; + } while (it != end && (is_name_start(c) || ('0' <= c && c <= '9'))); + handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); + return it; +} + +// Adapts SpecHandler to IDHandler API for dynamic width. +template <typename SpecHandler, typename Char> +struct width_adapter { + explicit FMT_CONSTEXPR width_adapter(SpecHandler &h) : handler(h) {} + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } + FMT_CONSTEXPR void operator()(unsigned id) { handler.on_dynamic_width(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_width(id); + } + + FMT_CONSTEXPR void on_error(const char *message) { + handler.on_error(message); + } + + SpecHandler &handler; +}; + +// Adapts SpecHandler to IDHandler API for dynamic precision. +template <typename SpecHandler, typename Char> +struct precision_adapter { + explicit FMT_CONSTEXPR precision_adapter(SpecHandler &h) : handler(h) {} + + FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } + FMT_CONSTEXPR void operator()(unsigned id) { + handler.on_dynamic_precision(id); + } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_dynamic_precision(id); + } + + FMT_CONSTEXPR void on_error(const char *message) { handler.on_error(message); } + + SpecHandler &handler; +}; + +// Parses standard format specifiers and sends notifications about parsed +// components to handler. +// it: an iterator pointing to the beginning of a null-terminated range of +// characters, possibly emulated via null_terminating_iterator, representing +// format specifiers. +template <typename Iterator, typename SpecHandler> +FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { + typedef typename std::iterator_traits<Iterator>::value_type char_type; + char_type c = *it; + if (c == '}' || !c) + return it; + + // Parse fill and alignment. + alignment align = ALIGN_DEFAULT; + int i = 1; + do { + auto p = it + i; + switch (*p) { + case '<': + align = ALIGN_LEFT; + break; + case '>': + align = ALIGN_RIGHT; + break; + case '=': + align = ALIGN_NUMERIC; + break; + case '^': + align = ALIGN_CENTER; + break; + } + if (align != ALIGN_DEFAULT) { + if (p != it) { + if (c == '{') { + handler.on_error("invalid fill character '{'"); + return it; + } + it += 2; + handler.on_fill(c); + } else ++it; + handler.on_align(align); + break; + } + } while (--i >= 0); + + // Parse sign. + switch (*it) { + case '+': + handler.on_plus(); + ++it; + break; + case '-': + handler.on_minus(); + ++it; + break; + case ' ': + handler.on_space(); + ++it; + break; + } + + if (*it == '#') { + handler.on_hash(); + ++it; + } + + // Parse zero flag. + if (*it == '0') { + handler.on_zero(); + ++it; + } + + // Parse width. + if ('0' <= *it && *it <= '9') { + handler.on_width(parse_nonnegative_int(it, handler)); + } else if (*it == '{') { + it = parse_arg_id(it + 1, width_adapter<SpecHandler, char_type>(handler)); + if (*it++ != '}') { + handler.on_error("invalid format string"); + return it; + } + } + + // Parse precision. + if (*it == '.') { + ++it; + if ('0' <= *it && *it <= '9') { + handler.on_precision(parse_nonnegative_int(it, handler)); + } else if (*it == '{') { + it = parse_arg_id( + it + 1, precision_adapter<SpecHandler, char_type>(handler)); + if (*it++ != '}') { + handler.on_error("invalid format string"); + return it; + } + } else { + handler.on_error("missing precision specifier"); + return it; + } + handler.end_precision(); + } + + // Parse type. + if (*it != '}' && *it) + handler.on_type(*it++); + return it; +} + +// Return the result via the out param to workaround gcc bug 77539. +template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*> +FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr &out) { + for (out = first; out != last; ++out) { + if (*out == value) + return true; + } + return false; +} + +template <> +inline bool find<false, char>( + const char *first, const char *last, char value, const char *&out) { + out = static_cast<const char*>(std::memchr(first, value, last - first)); + return out != FMT_NULL; +} + +template <typename Handler, typename Char> +struct id_adapter { + FMT_CONSTEXPR void operator()() { handler.on_arg_id(); } + FMT_CONSTEXPR void operator()(unsigned id) { handler.on_arg_id(id); } + FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { + handler.on_arg_id(id); + } + FMT_CONSTEXPR void on_error(const char *message) { + handler.on_error(message); + } + Handler &handler; +}; + +template <bool IS_CONSTEXPR, typename Char, typename Handler> +FMT_CONSTEXPR void parse_format_string( + basic_string_view<Char> format_str, Handler &&handler) { + struct writer { + FMT_CONSTEXPR void operator()(const Char *begin, const Char *end) { + if (begin == end) return; + for (;;) { + const Char *p = FMT_NULL; + if (!find<IS_CONSTEXPR>(begin, end, '}', p)) + return handler_.on_text(begin, end); + ++p; + if (p == end || *p != '}') + return handler_.on_error("unmatched '}' in format string"); + handler_.on_text(begin, p); + begin = p + 1; + } + } + Handler &handler_; + } write{handler}; + auto begin = format_str.data(), end = begin + format_str.size(); + while (begin != end) { + // Doing two passes with memchr (one for '{' and another for '}') is up to + // 2.5x faster than the naive one-pass implementation on big format strings. + const Char *p = begin; + if (*begin != '{' && !find<IS_CONSTEXPR>(begin, end, '{', p)) + return write(begin, end); + write(begin, p); + ++p; + if (p == end) + return handler.on_error("invalid format string"); + if (*p == '}') { + handler.on_arg_id(); + handler.on_replacement_field(p); + } else if (*p == '{') { + handler.on_text(p, p + 1); + } else { + p = parse_arg_id(p, end, id_adapter<Handler, Char>{handler}); + Char c = p != end ? *p : 0; + if (c == '}') { + handler.on_replacement_field(p); + } else if (c == ':') { + internal::null_terminating_iterator<Char> it(p + 1, end); + it = handler.on_format_specs(it); + if (*it != '}') + return handler.on_error("unknown format specifier"); + p = pointer_from(it); + } else { + return handler.on_error("missing '}' in format string"); + } + } + begin = p + 1; + } +} + +template <typename T, typename ParseContext> +FMT_CONSTEXPR const typename ParseContext::char_type * + parse_format_specs(ParseContext &ctx) { + // GCC 7.2 requires initializer. + formatter<T, typename ParseContext::char_type> f{}; + return f.parse(ctx); +} + +template <typename Char, typename ErrorHandler, typename... Args> +class format_string_checker { + public: + explicit FMT_CONSTEXPR format_string_checker( + basic_string_view<Char> format_str, ErrorHandler eh) + : arg_id_(-1), context_(format_str, eh), + parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} + + typedef internal::null_terminating_iterator<Char> iterator; + + FMT_CONSTEXPR void on_text(const Char *, const Char *) {} + + FMT_CONSTEXPR void on_arg_id() { + arg_id_ = context_.next_arg_id(); + check_arg_id(); + } + FMT_CONSTEXPR void on_arg_id(unsigned id) { + arg_id_ = id; + context_.check_arg_id(id); + check_arg_id(); + } + FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) {} + + FMT_CONSTEXPR void on_replacement_field(const Char *) {} + + FMT_CONSTEXPR const Char *on_format_specs(iterator it) { + auto p = pointer_from(it); + context_.advance_to(p); + return to_unsigned(arg_id_) < NUM_ARGS ? + parse_funcs_[arg_id_](context_) : p; + } + + FMT_CONSTEXPR void on_error(const char *message) { + context_.on_error(message); + } + + private: + typedef basic_parse_context<Char, ErrorHandler> parse_context_type; + enum { NUM_ARGS = sizeof...(Args) }; + + FMT_CONSTEXPR void check_arg_id() { + if (internal::to_unsigned(arg_id_) >= NUM_ARGS) + context_.on_error("argument index out of range"); + } + + // Format specifier parsing function. + typedef const Char *(*parse_func)(parse_context_type &); + + int arg_id_; + parse_context_type context_; + parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; +}; + +template <typename Char, typename ErrorHandler, typename... Args> +FMT_CONSTEXPR bool check_format_string( + basic_string_view<Char> s, ErrorHandler eh = ErrorHandler()) { + format_string_checker<Char, ErrorHandler, Args...> checker(s, eh); + parse_format_string<true>(s, checker); + return true; +} + +template <typename... Args, typename String> +typename std::enable_if<is_compile_string<String>::value>::type + check_format_string(String format_str) { + FMT_CONSTEXPR_DECL bool invalid_format = + internal::check_format_string<char, internal::error_handler, Args...>( + string_view(format_str.data(), format_str.size())); + (void)invalid_format; +} + +// Specifies whether to format T using the standard formatter. +// It is not possible to use get_type in formatter specialization directly +// because of a bug in MSVC. +template <typename Context, typename T> +struct format_type : + std::integral_constant<bool, get_type<Context, T>::value != custom_type> {}; + +template <template <typename> class Handler, typename Spec, typename Context> +void handle_dynamic_spec( + Spec &value, arg_ref<typename Context::char_type> ref, Context &ctx) { + typedef typename Context::char_type char_type; + switch (ref.kind) { + case arg_ref<char_type>::NONE: + break; + case arg_ref<char_type>::INDEX: + internal::set_dynamic_spec<Handler>( + value, ctx.get_arg(ref.index), ctx.error_handler()); + break; + case arg_ref<char_type>::NAME: + internal::set_dynamic_spec<Handler>( + value, ctx.get_arg(ref.name), ctx.error_handler()); + break; + } +} +} // namespace internal + +/** The default argument formatter. */ +template <typename Range> +class arg_formatter: + public internal::function< + typename internal::arg_formatter_base<Range>::iterator>, + public internal::arg_formatter_base<Range> { + private: + typedef typename Range::value_type char_type; + typedef internal::arg_formatter_base<Range> base; + typedef basic_format_context<typename base::iterator, char_type> context_type; + + context_type &ctx_; + + public: + typedef Range range; + typedef typename base::iterator iterator; + typedef typename base::format_specs format_specs; + + /** + \rst + Constructs an argument formatter object. + *ctx* is a reference to the formatting context, + *spec* contains format specifier information for standard argument types. + \endrst + */ + explicit arg_formatter(context_type &ctx, format_specs *spec = {}) + : base(Range(ctx.out()), spec), ctx_(ctx) {} + + // Deprecated. + arg_formatter(context_type &ctx, format_specs &spec) + : base(Range(ctx.out()), &spec), ctx_(ctx) {} + + using base::operator(); + + /** Formats an argument of a user-defined type. */ + iterator operator()(typename basic_format_arg<context_type>::handle handle) { + handle.format(ctx_); + return this->out(); + } +}; + +/** + An error returned by an operating system or a language runtime, + for example a file opening error. +*/ +class system_error : public std::runtime_error { + private: + FMT_API void init(int err_code, string_view format_str, format_args args); + + protected: + int error_code_; + + system_error() : std::runtime_error("") {} + + public: + /** + \rst + Constructs a :class:`fmt::system_error` object with a description + formatted with `fmt::format_system_error`. *message* and additional + arguments passed into the constructor are formatted similarly to + `fmt::format`. + + **Example**:: + + // This throws a system_error with the description + // cannot open file 'madeup': No such file or directory + // or similar (system message may vary). + const char *filename = "madeup"; + std::FILE *file = std::fopen(filename, "r"); + if (!file) + throw fmt::system_error(errno, "cannot open file '{}'", filename); + \endrst + */ + template <typename... Args> + system_error(int error_code, string_view message, const Args & ... args) + : std::runtime_error("") { + init(error_code, message, make_format_args(args...)); + } + + int error_code() const { return error_code_; } +}; + +/** + \rst + Formats an error returned by an operating system or a language runtime, + for example a file opening error, and writes it to *out* in the following + form: + + .. parsed-literal:: + *<message>*: *<system-message>* + + where *<message>* is the passed message and *<system-message>* is + the system message corresponding to the error code. + *error_code* is a system error code as given by ``errno``. + If *error_code* is not a valid error code such as -1, the system message + may look like "Unknown error -1" and is platform-dependent. + \endrst + */ +FMT_API void format_system_error(internal::buffer &out, int error_code, + fmt::string_view message) FMT_NOEXCEPT; + +/** + This template provides operations for formatting and writing data into a + character range. + */ +template <typename Range> +class basic_writer { + public: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval<Range>().begin()) iterator; + typedef basic_format_specs<char_type> format_specs; + + private: + iterator out_; // Output iterator. + std::unique_ptr<locale_provider> locale_; + + iterator out() const { return out_; } + + // Attempts to reserve space for n extra characters in the output range. + // Returns a pointer to the reserved range or a reference to out_. + auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { + return internal::reserve(out_, n); + } + + // Writes a value in the format + // <left-padding><value><right-padding> + // where <value> is written by f(it). + template <typename F> + void write_padded(std::size_t size, const align_spec &spec, F &&f); + + template <typename F> + struct padded_int_writer { + string_view prefix; + char_type fill; + std::size_t padding; + F f; + + template <typename It> + void operator()(It &&it) const { + if (prefix.size() != 0) + it = std::copy_n(prefix.data(), prefix.size(), it); + it = std::fill_n(it, padding, fill); + f(it); + } + }; + + // Writes an integer in the format + // <left-padding><prefix><numeric-padding><digits><right-padding> + // where <digits> are written by f(it). + template <typename Spec, typename F> + void write_int(unsigned num_digits, string_view prefix, + const Spec &spec, F f) { + std::size_t size = prefix.size() + num_digits; + char_type fill = static_cast<char_type>(spec.fill()); + std::size_t padding = 0; + if (spec.align() == ALIGN_NUMERIC) { + if (spec.width() > size) { + padding = spec.width() - size; + size = spec.width(); + } + } else if (spec.precision() > static_cast<int>(num_digits)) { + size = prefix.size() + static_cast<std::size_t>(spec.precision()); + padding = static_cast<std::size_t>(spec.precision()) - num_digits; + fill = '0'; + } + align_spec as = spec; + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + write_padded(size, as, padded_int_writer<F>{prefix, fill, padding, f}); + } + + // Writes a decimal integer. + template <typename Int> + void write_decimal(Int value) { + typedef typename internal::int_traits<Int>::main_type main_type; + main_type abs_value = static_cast<main_type>(value); + bool is_negative = internal::is_negative(value); + if (is_negative) + abs_value = 0 - abs_value; + unsigned num_digits = internal::count_digits(abs_value); + auto &&it = reserve((is_negative ? 1 : 0) + num_digits); + if (is_negative) + *it++ = '-'; + it = internal::format_decimal(it, abs_value, num_digits); + } + + // The handle_int_type_spec handler that writes an integer. + template <typename Int, typename Spec> + struct int_writer { + typedef typename internal::int_traits<Int>::main_type unsigned_type; + + basic_writer<Range> &writer; + const Spec &spec; + unsigned_type abs_value; + char prefix[4]; + unsigned prefix_size; + + string_view get_prefix() const { return string_view(prefix, prefix_size); } + + // Counts the number of digits in abs_value. BITS = log2(radix). + template <unsigned BITS> + unsigned count_digits() const { + unsigned_type n = abs_value; + unsigned num_digits = 0; + do { + ++num_digits; + } while ((n >>= BITS) != 0); + return num_digits; + } + + int_writer(basic_writer<Range> &w, Int value, const Spec &s) + : writer(w), spec(s), abs_value(static_cast<unsigned_type>(value)), + prefix_size(0) { + if (internal::is_negative(value)) { + prefix[0] = '-'; + ++prefix_size; + abs_value = 0 - abs_value; + } else if (spec.flag(SIGN_FLAG)) { + prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; + ++prefix_size; + } + } + + struct dec_writer { + unsigned_type abs_value; + unsigned num_digits; + + template <typename It> + void operator()(It &&it) const { + it = internal::format_decimal(it, abs_value, num_digits); + } + }; + + void on_dec() { + unsigned num_digits = internal::count_digits(abs_value); + writer.write_int(num_digits, get_prefix(), spec, + dec_writer{abs_value, num_digits}); + } + + struct hex_writer { + int_writer &self; + unsigned num_digits; + + template <typename It> + void operator()(It &&it) const { + it = internal::format_uint<4>(it, self.abs_value, num_digits, + self.spec.type() != 'x'); + } + }; + + void on_hex() { + if (spec.flag(HASH_FLAG)) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast<char>(spec.type()); + } + unsigned num_digits = count_digits<4>(); + writer.write_int(num_digits, get_prefix(), spec, + hex_writer{*this, num_digits}); + } + + template <int BITS> + struct bin_writer { + unsigned_type abs_value; + unsigned num_digits; + + template <typename It> + void operator()(It &&it) const { + it = internal::format_uint<BITS>(it, abs_value, num_digits); + } + }; + + void on_bin() { + if (spec.flag(HASH_FLAG)) { + prefix[prefix_size++] = '0'; + prefix[prefix_size++] = static_cast<char>(spec.type()); + } + unsigned num_digits = count_digits<1>(); + writer.write_int(num_digits, get_prefix(), spec, + bin_writer<1>{abs_value, num_digits}); + } + + void on_oct() { + unsigned num_digits = count_digits<3>(); + if (spec.flag(HASH_FLAG) && + spec.precision() <= static_cast<int>(num_digits)) { + // Octal prefix '0' is counted as a digit, so only add it if precision + // is not greater than the number of digits. + prefix[prefix_size++] = '0'; + } + writer.write_int(num_digits, get_prefix(), spec, + bin_writer<3>{abs_value, num_digits}); + } + + enum { SEP_SIZE = 1 }; + + struct num_writer { + unsigned_type abs_value; + unsigned size; + char_type sep; + + template <typename It> + void operator()(It &&it) const { + basic_string_view<char_type> s(&sep, SEP_SIZE); + it = format_decimal(it, abs_value, size, + internal::add_thousands_sep<char_type>(s)); + } + }; + + void on_num() { + unsigned num_digits = internal::count_digits(abs_value); + char_type sep = internal::thousands_sep<char_type>(writer.locale_.get()); + unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); + writer.write_int(size, get_prefix(), spec, + num_writer{abs_value, size, sep}); + } + + void on_error() { + FMT_THROW(format_error("invalid type specifier")); + } + }; + + // Writes a formatted integer. + template <typename T, typename Spec> + void write_int(T value, const Spec &spec) { + internal::handle_int_type_spec(spec.type(), + int_writer<T, Spec>(*this, value, spec)); + } + + enum {INF_SIZE = 3}; // This is an enum to workaround a bug in MSVC. + + struct inf_or_nan_writer { + char sign; + const char *str; + + template <typename It> + void operator()(It &&it) const { + if (sign) + *it++ = sign; + it = std::copy_n(str, static_cast<std::size_t>(INF_SIZE), it); + } + }; + + struct double_writer { + size_t n; + char sign; + basic_memory_buffer<char_type> &buffer; + + template <typename It> + void operator()(It &&it) { + if (sign) { + *it++ = sign; + --n; + } + it = std::copy_n(buffer.begin(), n, it); + } + }; + + // Formats a floating-point number (double or long double). + template <typename T> + void write_double(T value, const format_specs &spec); + template <typename T> + void write_double_sprintf(T value, const format_specs &spec, + internal::basic_buffer<char_type>& buffer); + + template <typename Char> + struct str_writer { + const Char *s; + std::size_t size; + + template <typename It> + void operator()(It &&it) const { + it = std::copy_n(s, size, it); + } + }; + + // Writes a formatted string. + template <typename Char> + void write_str(const Char *s, std::size_t size, const align_spec &spec) { + write_padded(size, spec, str_writer<Char>{s, size}); + } + + template <typename Char> + void write_str(basic_string_view<Char> str, const format_specs &spec); + + // Appends floating-point length specifier to the format string. + // The second argument is only used for overload resolution. + void append_float_length(char_type *&format_ptr, long double) { + *format_ptr++ = 'L'; + } + + template<typename T> + void append_float_length(char_type *&, T) {} + + template <typename Char> + friend class internal::arg_formatter_base; + + public: + /** Constructs a ``basic_writer`` object. */ + explicit basic_writer(Range out): out_(out.begin()) {} + + void write(int value) { write_decimal(value); } + void write(long value) { write_decimal(value); } + void write(long long value) { write_decimal(value); } + + void write(unsigned value) { write_decimal(value); } + void write(unsigned long value) { write_decimal(value); } + void write(unsigned long long value) { write_decimal(value); } + + /** + \rst + Formats *value* and writes it to the buffer. + \endrst + */ + template <typename T, typename FormatSpec, typename... FormatSpecs> + typename std::enable_if<std::is_integral<T>::value, void>::type + write(T value, FormatSpec spec, FormatSpecs... specs) { + format_specs s(spec, specs...); + s.align_ = ALIGN_RIGHT; + write_int(value, s); + } + + void write(double value) { + write_double(value, format_specs()); + } + + /** + \rst + Formats *value* using the general format for floating-point numbers + (``'g'``) and writes it to the buffer. + \endrst + */ + void write(long double value) { + write_double(value, format_specs()); + } + + /** Writes a character to the buffer. */ + void write(char value) { + *reserve(1) = value; + } + + void write(wchar_t value) { + internal::require_wchar<char_type>(); + *reserve(1) = value; + } + + /** + \rst + Writes *value* to the buffer. + \endrst + */ + void write(string_view value) { + auto &&it = reserve(value.size()); + it = std::copy(value.begin(), value.end(), it); + } + + void write(wstring_view value) { + internal::require_wchar<char_type>(); + auto &&it = reserve(value.size()); + it = std::copy(value.begin(), value.end(), it); + } + + template <typename... FormatSpecs> + void write(basic_string_view<char_type> str, FormatSpecs... specs) { + write_str(str, format_specs(specs...)); + } + + template <typename T> + typename std::enable_if<std::is_same<T, void>::value>::type + write(const T *p) { + format_specs specs; + specs.flags_ = HASH_FLAG; + specs.type_ = 'x'; + write_int(reinterpret_cast<uintptr_t>(p), specs); + } +}; + +template <typename Range> +template <typename F> +void basic_writer<Range>::write_padded( + std::size_t size, const align_spec &spec, F &&f) { + unsigned width = spec.width(); + if (width <= size) + return f(reserve(size)); + auto &&it = reserve(width); + char_type fill = static_cast<char_type>(spec.fill()); + std::size_t padding = width - size; + if (spec.align() == ALIGN_RIGHT) { + it = std::fill_n(it, padding, fill); + f(it); + } else if (spec.align() == ALIGN_CENTER) { + std::size_t left_padding = padding / 2; + it = std::fill_n(it, left_padding, fill); + f(it); + it = std::fill_n(it, padding - left_padding, fill); + } else { + f(it); + it = std::fill_n(it, padding, fill); + } +} + +template <typename Range> +template <typename Char> +void basic_writer<Range>::write_str( + basic_string_view<Char> s, const format_specs &spec) { + const Char *data = s.data(); + std::size_t size = s.size(); + std::size_t precision = static_cast<std::size_t>(spec.precision_); + if (spec.precision_ >= 0 && precision < size) + size = precision; + write_str(data, size, spec); +} + +template <typename Char> +struct float_spec_handler { + Char type; + bool upper; + + explicit float_spec_handler(Char t) : type(t), upper(false) {} + + void on_general() { + if (type == 'G') + upper = true; + else + type = 'g'; + } + + void on_exp() { + if (type == 'E') + upper = true; + } + + void on_fixed() { + if (type == 'F') { + upper = true; +#if FMT_MSC_VER + // MSVC's printf doesn't support 'F'. + type = 'f'; +#endif + } + } + + void on_hex() { + if (type == 'A') + upper = true; + } + + void on_error() { + FMT_THROW(format_error("invalid type specifier")); + } +}; + +template <typename Range> +template <typename T> +void basic_writer<Range>::write_double(T value, const format_specs &spec) { + // Check type. + float_spec_handler<char_type> handler(spec.type()); + internal::handle_float_type_spec(spec.type(), handler); + + char sign = 0; + // Use isnegative instead of value < 0 because the latter is always + // false for NaN. + if (internal::fputil::isnegative(static_cast<double>(value))) { + sign = '-'; + value = -value; + } else if (spec.flag(SIGN_FLAG)) { + sign = spec.flag(PLUS_FLAG) ? '+' : ' '; + } + + struct write_inf_or_nan_t { + basic_writer &writer; + format_specs spec; + char sign; + void operator()(const char *str) const { + writer.write_padded(INF_SIZE + (sign ? 1 : 0), spec, + inf_or_nan_writer{sign, str}); + } + } write_inf_or_nan = {*this, spec, sign}; + + // Format NaN and ininity ourselves because sprintf's output is not consistent + // across platforms. + if (internal::fputil::isnotanumber(value)) + return write_inf_or_nan(handler.upper ? "NAN" : "nan"); + if (internal::fputil::isinfinity(value)) + return write_inf_or_nan(handler.upper ? "INF" : "inf"); + + basic_memory_buffer<char_type> buffer; + char type = static_cast<char>(spec.type()); + if (internal::const_check( + internal::use_grisu() && sizeof(T) <= sizeof(double)) && + type != 'a' && type != 'A') { + char buf[100]; // TODO: correct buffer size + size_t size = 0; + internal::grisu2_format(static_cast<double>(value), buf, size, type, + spec.precision(), spec.flag(HASH_FLAG)); + FMT_ASSERT(size <= 100, "buffer overflow"); + buffer.append(buf, buf + size); // TODO: avoid extra copy + } else { + format_specs normalized_spec(spec); + normalized_spec.type_ = handler.type; + write_double_sprintf(value, normalized_spec, buffer); + } + size_t n = buffer.size(); + align_spec as = spec; + if (spec.align() == ALIGN_NUMERIC) { + if (sign) { + auto &&it = reserve(1); + *it++ = sign; + sign = 0; + if (as.width_) + --as.width_; + } + as.align_ = ALIGN_RIGHT; + } else { + if (spec.align() == ALIGN_DEFAULT) + as.align_ = ALIGN_RIGHT; + if (sign) + ++n; + } + write_padded(n, as, double_writer{n, sign, buffer}); +} + +template <typename Range> +template <typename T> +void basic_writer<Range>::write_double_sprintf( + T value, const format_specs &spec, + internal::basic_buffer<char_type>& buffer) { + // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. + FMT_ASSERT(buffer.capacity() != 0, "empty buffer"); + + // Build format string. + enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg + char_type format[MAX_FORMAT_SIZE]; + char_type *format_ptr = format; + *format_ptr++ = '%'; + if (spec.flag(HASH_FLAG)) + *format_ptr++ = '#'; + if (spec.precision() >= 0) { + *format_ptr++ = '.'; + *format_ptr++ = '*'; + } + + append_float_length(format_ptr, value); + *format_ptr++ = spec.type(); + *format_ptr = '\0'; + + // Format using snprintf. + char_type *start = FMT_NULL; + for (;;) { + std::size_t buffer_size = buffer.capacity(); + start = &buffer[0]; + int result = internal::char_traits<char_type>::format_float( + start, buffer_size, format, spec.precision(), value); + if (result >= 0) { + unsigned n = internal::to_unsigned(result); + if (n < buffer.capacity()) { + buffer.resize(n); + break; // The buffer is large enough - continue with formatting. + } + buffer.reserve(n + 1); + } else { + // If result is negative we ask to increase the capacity by at least 1, + // but as std::vector, the buffer grows exponentially. + buffer.reserve(buffer.capacity() + 1); + } + } +} + +// Reports a system error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_system_error(int error_code, + string_view message) FMT_NOEXCEPT; + +#if FMT_USE_WINDOWS_H + +/** A Windows error. */ +class windows_error : public system_error { + private: + FMT_API void init(int error_code, string_view format_str, format_args args); + + public: + /** + \rst + Constructs a :class:`fmt::windows_error` object with the description + of the form + + .. parsed-literal:: + *<message>*: *<system-message>* + + where *<message>* is the formatted message and *<system-message>* is the + system message corresponding to the error code. + *error_code* is a Windows error code as given by ``GetLastError``. + If *error_code* is not a valid error code such as -1, the system message + will look like "error -1". + + **Example**:: + + // This throws a windows_error with the description + // cannot open file 'madeup': The system cannot find the file specified. + // or similar (system message may vary). + const char *filename = "madeup"; + LPOFSTRUCT of = LPOFSTRUCT(); + HFILE file = OpenFile(filename, &of, OF_READ); + if (file == HFILE_ERROR) { + throw fmt::windows_error(GetLastError(), + "cannot open file '{}'", filename); + } + \endrst + */ + template <typename... Args> + windows_error(int error_code, string_view message, const Args & ... args) { + init(error_code, message, make_format_args(args...)); + } +}; + +// Reports a Windows error without throwing an exception. +// Can be used to report errors from destructors. +FMT_API void report_windows_error(int error_code, + string_view message) FMT_NOEXCEPT; + +#endif + +/** Fast integer formatter. */ +class format_int { + private: + // Buffer should be large enough to hold all digits (digits10 + 1), + // a sign and a null character. + enum {BUFFER_SIZE = std::numeric_limits<unsigned long long>::digits10 + 3}; + mutable char buffer_[BUFFER_SIZE]; + char *str_; + + // Formats value in reverse and returns a pointer to the beginning. + char *format_decimal(unsigned long long value) { + char *ptr = buffer_ + BUFFER_SIZE - 1; + while (value >= 100) { + // Integer division is slow so do it for a group of two digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". See speed-test for a comparison. + unsigned index = static_cast<unsigned>((value % 100) * 2); + value /= 100; + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; + } + if (value < 10) { + *--ptr = static_cast<char>('0' + value); + return ptr; + } + unsigned index = static_cast<unsigned>(value * 2); + *--ptr = internal::data::DIGITS[index + 1]; + *--ptr = internal::data::DIGITS[index]; + return ptr; + } + + void format_signed(long long value) { + unsigned long long abs_value = static_cast<unsigned long long>(value); + bool negative = value < 0; + if (negative) + abs_value = 0 - abs_value; + str_ = format_decimal(abs_value); + if (negative) + *--str_ = '-'; + } + + public: + explicit format_int(int value) { format_signed(value); } + explicit format_int(long value) { format_signed(value); } + explicit format_int(long long value) { format_signed(value); } + explicit format_int(unsigned value) : str_(format_decimal(value)) {} + explicit format_int(unsigned long value) : str_(format_decimal(value)) {} + explicit format_int(unsigned long long value) : str_(format_decimal(value)) {} + + /** Returns the number of characters written to the output buffer. */ + std::size_t size() const { + return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); + } + + /** + Returns a pointer to the output buffer content. No terminating null + character is appended. + */ + const char *data() const { return str_; } + + /** + Returns a pointer to the output buffer content with terminating null + character appended. + */ + const char *c_str() const { + buffer_[BUFFER_SIZE - 1] = '\0'; + return str_; + } + + /** + \rst + Returns the content of the output buffer as an ``std::string``. + \endrst + */ + std::string str() const { return std::string(str_, size()); } +}; + +// Formats a decimal integer value writing into buffer and returns +// a pointer to the end of the formatted string. This function doesn't +// write a terminating null character. +template <typename T> +inline void format_decimal(char *&buffer, T value) { + typedef typename internal::int_traits<T>::main_type main_type; + main_type abs_value = static_cast<main_type>(value); + if (internal::is_negative(value)) { + *buffer++ = '-'; + abs_value = 0 - abs_value; + } + if (abs_value < 100) { + if (abs_value < 10) { + *buffer++ = static_cast<char>('0' + abs_value); + return; + } + unsigned index = static_cast<unsigned>(abs_value * 2); + *buffer++ = internal::data::DIGITS[index]; + *buffer++ = internal::data::DIGITS[index + 1]; + return; + } + unsigned num_digits = internal::count_digits(abs_value); + internal::format_decimal(buffer, abs_value, num_digits); + buffer += num_digits; +} + +// Formatter of objects of type T. +template <typename T, typename Char> +struct formatter< + T, Char, + typename std::enable_if<internal::format_type< + typename buffer_context<Char>::type, T>::value>::type> { + + // Parses format specifiers stopping either at the end of the range or at the + // terminating '}'. + template <typename ParseContext> + FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) { + auto it = internal::null_terminating_iterator<Char>(ctx); + typedef internal::dynamic_specs_handler<ParseContext> handler_type; + auto type = internal::get_type< + typename buffer_context<Char>::type, T>::value; + internal::specs_checker<handler_type> + handler(handler_type(specs_, ctx), type); + it = parse_format_specs(it, handler); + auto type_spec = specs_.type(); + auto eh = ctx.error_handler(); + switch (type) { + case internal::none_type: + case internal::named_arg_type: + FMT_ASSERT(false, "invalid argument type"); + break; + case internal::int_type: + case internal::uint_type: + case internal::long_long_type: + case internal::ulong_long_type: + case internal::bool_type: + handle_int_type_spec( + type_spec, internal::int_type_checker<decltype(eh)>(eh)); + break; + case internal::char_type: + handle_char_specs( + &specs_, + internal::char_specs_checker<decltype(eh), decltype(type_spec)>( + type_spec, eh)); + break; + case internal::double_type: + case internal::long_double_type: + handle_float_type_spec( + type_spec, internal::float_type_checker<decltype(eh)>(eh)); + break; + case internal::cstring_type: + internal::handle_cstring_type_spec( + type_spec, internal::cstring_type_checker<decltype(eh)>(eh)); + break; + case internal::string_type: + internal::check_string_type_spec(type_spec, eh); + break; + case internal::pointer_type: + internal::check_pointer_type_spec(type_spec, eh); + break; + case internal::custom_type: + // Custom format specifiers should be checked in parse functions of + // formatter specializations. + break; + } + return pointer_from(it); + } + + template <typename FormatContext> + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { + internal::handle_dynamic_spec<internal::width_checker>( + specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec<internal::precision_checker>( + specs_.precision_, specs_.precision_ref, ctx); + typedef output_range<typename FormatContext::iterator, + typename FormatContext::char_type> range_type; + return fmt::visit(arg_formatter<range_type>(ctx, &specs_), + internal::make_arg<FormatContext>(val)); + } + + private: + internal::dynamic_format_specs<Char> specs_; +}; + +// A formatter for types known only at run time such as variant alternatives. +// +// Usage: +// typedef std::variant<int, std::string> variant; +// template <> +// struct formatter<variant>: dynamic_formatter<> { +// void format(buffer &buf, const variant &v, context &ctx) { +// visit([&](const auto &val) { format(buf, val, ctx); }, v); +// } +// }; +template <typename Char = char> +class dynamic_formatter { + private: + struct null_handler: internal::error_handler { + void on_align(alignment) {} + void on_plus() {} + void on_minus() {} + void on_space() {} + void on_hash() {} + }; + + public: + template <typename ParseContext> + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + auto it = internal::null_terminating_iterator<Char>(ctx); + // Checks are deferred to formatting time when the argument type is known. + internal::dynamic_specs_handler<ParseContext> handler(specs_, ctx); + it = parse_format_specs(it, handler); + return pointer_from(it); + } + + template <typename T, typename FormatContext> + auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { + handle_specs(ctx); + internal::specs_checker<null_handler> + checker(null_handler(), internal::get_type<FormatContext, T>::value); + checker.on_align(specs_.align()); + if (specs_.flags_ == 0) { + // Do nothing. + } else if (specs_.flag(SIGN_FLAG)) { + if (specs_.flag(PLUS_FLAG)) + checker.on_plus(); + else + checker.on_space(); + } else if (specs_.flag(MINUS_FLAG)) { + checker.on_minus(); + } else if (specs_.flag(HASH_FLAG)) { + checker.on_hash(); + } + if (specs_.precision_ != -1) + checker.end_precision(); + typedef output_range<typename FormatContext::iterator, + typename FormatContext::char_type> range; + fmt::visit(arg_formatter<range>(ctx, &specs_), + internal::make_arg<FormatContext>(val)); + return ctx.out(); + } + + private: + template <typename Context> + void handle_specs(Context &ctx) { + internal::handle_dynamic_spec<internal::width_checker>( + specs_.width_, specs_.width_ref, ctx); + internal::handle_dynamic_spec<internal::precision_checker>( + specs_.precision_, specs_.precision_ref, ctx); + } + + internal::dynamic_format_specs<Char> specs_; +}; + +template <typename Range, typename Char> +typename basic_format_context<Range, Char>::format_arg + basic_format_context<Range, Char>::get_arg( + basic_string_view<char_type> name) { + map_.init(this->args()); + format_arg arg = map_.find(name); + if (arg.type() == internal::none_type) + this->on_error("argument not found"); + return arg; +} + +template <typename ArgFormatter, typename Char, typename Context> +struct format_handler : internal::error_handler { + typedef internal::null_terminating_iterator<Char> iterator; + typedef typename ArgFormatter::range range; + + format_handler(range r, basic_string_view<Char> str, + basic_format_args<Context> format_args) + : context(r.begin(), str, format_args) {} + + void on_text(const Char *begin, const Char *end) { + auto size = internal::to_unsigned(end - begin); + auto out = context.out(); + auto &&it = internal::reserve(out, size); + it = std::copy_n(begin, size, it); + context.advance_to(out); + } + + void on_arg_id() { arg = context.next_arg(); } + void on_arg_id(unsigned id) { + context.parse_context().check_arg_id(id); + arg = context.get_arg(id); + } + void on_arg_id(basic_string_view<Char> id) { + arg = context.get_arg(id); + } + + void on_replacement_field(const Char *p) { + context.parse_context().advance_to(p); + if (!fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) + context.advance_to(fmt::visit(ArgFormatter(context), arg)); + } + + iterator on_format_specs(iterator it) { + auto& parse_ctx = context.parse_context(); + parse_ctx.advance_to(pointer_from(it)); + if (fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) + return iterator(parse_ctx); + basic_format_specs<Char> specs; + using internal::specs_handler; + internal::specs_checker<specs_handler<Context>> + handler(specs_handler<Context>(specs, context), arg.type()); + it = parse_format_specs(it, handler); + if (*it != '}') + on_error("missing '}' in format string"); + parse_ctx.advance_to(pointer_from(it)); + context.advance_to(fmt::visit(ArgFormatter(context, &specs), arg)); + return it; + } + + Context context; + basic_format_arg<Context> arg; +}; + +/** Formats arguments and writes the output to the range. */ +template <typename ArgFormatter, typename Char, typename Context> +typename Context::iterator vformat_to(typename ArgFormatter::range out, + basic_string_view<Char> format_str, + basic_format_args<Context> args) { + format_handler<ArgFormatter, Char, Context> h(out, format_str, args); + internal::parse_format_string<false>(format_str, h); + return h.context.out(); +} + +// Casts ``p`` to ``const void*`` for pointer formatting. +// Example: +// auto s = format("{}", ptr(p)); +template <typename T> +inline const void *ptr(const T *p) { return p; } + +template <typename It, typename Char> +struct arg_join { + It begin; + It end; + basic_string_view<Char> sep; + + arg_join(It begin, It end, basic_string_view<Char> sep) + : begin(begin), end(end), sep(sep) {} +}; + +template <typename It, typename Char> +struct formatter<arg_join<It, Char>, Char>: + formatter<typename std::iterator_traits<It>::value_type, Char> { + template <typename FormatContext> + auto format(const arg_join<It, Char> &value, FormatContext &ctx) + -> decltype(ctx.out()) { + typedef formatter<typename std::iterator_traits<It>::value_type, Char> base; + auto it = value.begin; + auto out = ctx.out(); + if (it != value.end) { + out = base::format(*it++, ctx); + while (it != value.end) { + out = std::copy(value.sep.begin(), value.sep.end(), out); + ctx.advance_to(out); + out = base::format(*it++, ctx); + } + } + return out; + } +}; + +template <typename It> +arg_join<It, char> join(It begin, It end, string_view sep) { + return arg_join<It, char>(begin, end, sep); +} + +template <typename It> +arg_join<It, wchar_t> join(It begin, It end, wstring_view sep) { + return arg_join<It, wchar_t>(begin, end, sep); +} + +// The following causes ICE in gcc 4.4. +#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) +template <typename Range> +auto join(const Range &range, string_view sep) + -> arg_join<decltype(internal::begin(range)), char> { + return join(internal::begin(range), internal::end(range), sep); +} + +template <typename Range> +auto join(const Range &range, wstring_view sep) + -> arg_join<decltype(internal::begin(range)), wchar_t> { + return join(internal::begin(range), internal::end(range), sep); +} +#endif + +/** + \rst + Converts *value* to ``std::string`` using the default format for type *T*. + It doesn't support user-defined types with custom formatters. + + **Example**:: + + #include <fmt/format.h> + + std::string answer = fmt::to_string(42); + \endrst + */ +template <typename T> +std::string to_string(const T &value) { + std::string str; + internal::container_buffer<std::string> buf(str); + writer(buf).write(value); + return str; +} + +/** + Converts *value* to ``std::wstring`` using the default format for type *T*. + */ +template <typename T> +std::wstring to_wstring(const T &value) { + std::wstring str; + internal::container_buffer<std::wstring> buf(str); + wwriter(buf).write(value); + return str; +} + +template <typename Char, std::size_t SIZE> +std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE> &buf) { + return std::basic_string<Char>(buf.data(), buf.size()); +} + +inline format_context::iterator vformat_to( + internal::buffer &buf, string_view format_str, format_args args) { + typedef back_insert_range<internal::buffer> range; + return vformat_to<arg_formatter<range>>(buf, format_str, args); +} + +inline wformat_context::iterator vformat_to( + internal::wbuffer &buf, wstring_view format_str, wformat_args args) { + typedef back_insert_range<internal::wbuffer> range; + return vformat_to<arg_formatter<range>>(buf, format_str, args); +} + +template < + typename String, typename... Args, + std::size_t SIZE = inline_buffer_size, + typename Char = typename internal::format_string_traits<String>::char_type> +inline typename buffer_context<Char>::type::iterator format_to( + basic_memory_buffer<Char, SIZE> &buf, const String &format_str, + const Args & ... args) { + internal::check_format_string<Args...>(format_str); + return vformat_to( + buf, basic_string_view<Char>(format_str), + make_format_args<typename buffer_context<Char>::type>(args...)); +} + +template <typename OutputIt, typename Char = char> +//using format_context_t = basic_format_context<OutputIt, Char>; +struct format_context_t { typedef basic_format_context<OutputIt, Char> type; }; + +template <typename OutputIt, typename Char = char> +//using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>; +struct format_args_t { + typedef basic_format_args< + typename format_context_t<OutputIt, Char>::type> type; +}; + +template <typename OutputIt, typename... Args> +inline OutputIt vformat_to(OutputIt out, string_view format_str, + typename format_args_t<OutputIt>::type args) { + typedef output_range<OutputIt, char> range; + return vformat_to<arg_formatter<range>>(range(out), format_str, args); +} +template <typename OutputIt, typename... Args> +inline OutputIt vformat_to( + OutputIt out, wstring_view format_str, + typename format_args_t<OutputIt, wchar_t>::type args) { + typedef output_range<OutputIt, wchar_t> range; + return vformat_to<arg_formatter<range>>(range(out), format_str, args); +} + +/** + \rst + Formats arguments, writes the result to the output iterator ``out`` and returns + the iterator past the end of the output range. + + **Example**:: + + std::vector<char> out; + fmt::format_to(std::back_inserter(out), "{}", 42); + \endrst + */ +template <typename OutputIt, typename... Args> +inline OutputIt format_to(OutputIt out, string_view format_str, + const Args & ... args) { + return vformat_to(out, format_str, + make_format_args<typename format_context_t<OutputIt>::type>(args...)); +} + +template <typename OutputIt> +struct format_to_n_result { + /** Iterator past the end of the output range. */ + OutputIt out; + /** Total (not truncated) output size. */ + std::size_t size; +}; + +template <typename OutputIt> +using format_to_n_context = typename fmt::format_context_t< + fmt::internal::truncating_iterator<OutputIt>>::type; + +template <typename OutputIt> +using format_to_n_args = fmt::basic_format_args<format_to_n_context<OutputIt>>; + +template <typename OutputIt, typename ...Args> +inline format_arg_store<format_to_n_context<OutputIt>, Args...> + make_format_to_n_args(const Args & ... args) { + return format_arg_store<format_to_n_context<OutputIt>, Args...>(args...); +} + +template <typename OutputIt, typename... Args> +inline format_to_n_result<OutputIt> vformat_to_n( + OutputIt out, std::size_t n, string_view format_str, + format_to_n_args<OutputIt> args) { + typedef internal::truncating_iterator<OutputIt> It; + auto it = vformat_to(It(out, n), format_str, args); + return {it.base(), it.count()}; +} + +/** + \rst + Formats arguments, writes up to ``n`` characters of the result to the output + iterator ``out`` and returns the total output size and the iterator past the + end of the output range. + \endrst + */ +template <typename OutputIt, typename... Args> +inline format_to_n_result<OutputIt> format_to_n( + OutputIt out, std::size_t n, string_view format_str, const Args &... args) { + return vformat_to_n<OutputIt>( + out, n, format_str, make_format_to_n_args<OutputIt>(args...)); +} +template <typename OutputIt, typename... Args> +inline format_to_n_result<OutputIt> format_to_n( + OutputIt out, std::size_t n, wstring_view format_str, + const Args &... args) { + typedef internal::truncating_iterator<OutputIt> It; + auto it = vformat_to(It(out, n), format_str, + make_format_args<typename format_context_t<It, wchar_t>::type>(args...)); + return {it.base(), it.count()}; +} + +template <typename Char> +inline std::basic_string<Char> internal::vformat( + basic_string_view<Char> format_str, + basic_format_args<typename buffer_context<Char>::type> args) { + basic_memory_buffer<Char> buffer; + vformat_to(buffer, format_str, args); + return fmt::to_string(buffer); +} + +template <typename String, typename... Args> +inline typename std::enable_if<internal::is_compile_string<String>::value>::type + print(String format_str, const Args & ... args) { + internal::check_format_string<Args...>(format_str); + return vprint(format_str.data(), make_format_args(args...)); +} + +/** + Returns the number of characters in the output of + ``format(format_str, args...)``. + */ +template <typename... Args> +inline std::size_t formatted_size(string_view format_str, + const Args & ... args) { + auto it = format_to(internal::counting_iterator<char>(), format_str, args...); + return it.count(); +} + +#if FMT_USE_USER_DEFINED_LITERALS +namespace internal { + +# if FMT_UDL_TEMPLATE +template <typename Char, Char... CHARS> +class udl_formatter { + public: + template <typename... Args> + std::basic_string<Char> operator()(const Args &... args) const { + FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'}; + FMT_CONSTEXPR_DECL bool invalid_format = + check_format_string<Char, error_handler, Args...>( + basic_string_view<Char>(s, sizeof...(CHARS))); + (void)invalid_format; + return format(s, args...); + } +}; +# else +template <typename Char> +struct udl_formatter { + const Char *str; + + template <typename... Args> + auto operator()(Args && ... args) const + -> decltype(format(str, std::forward<Args>(args)...)) { + return format(str, std::forward<Args>(args)...); + } +}; +# endif // FMT_UDL_TEMPLATE + +template <typename Char> +struct udl_arg { + const Char *str; + + template <typename T> + named_arg<T, Char> operator=(T &&value) const { + return {str, std::forward<T>(value)}; + } +}; + +} // namespace internal + +inline namespace literals { + +# if FMT_UDL_TEMPLATE +template <typename Char, Char... CHARS> +FMT_CONSTEXPR internal::udl_formatter<Char, CHARS...> operator""_format() { + return {}; +} +# else +/** + \rst + User-defined literal equivalent of :func:`fmt::format`. + + **Example**:: + + using namespace fmt::literals; + std::string message = "The answer is {}"_format(42); + \endrst + */ +inline internal::udl_formatter<char> +operator"" _format(const char *s, std::size_t) { return {s}; } +inline internal::udl_formatter<wchar_t> +operator"" _format(const wchar_t *s, std::size_t) { return {s}; } +# endif // FMT_UDL_TEMPLATE + +/** + \rst + User-defined literal equivalent of :func:`fmt::arg`. + + **Example**:: + + using namespace fmt::literals; + fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); + \endrst + */ +inline internal::udl_arg<char> +operator"" _a(const char *s, std::size_t) { return {s}; } +inline internal::udl_arg<wchar_t> +operator"" _a(const wchar_t *s, std::size_t) { return {s}; } +} // inline namespace literals +#endif // FMT_USE_USER_DEFINED_LITERALS +FMT_END_NAMESPACE + +#define FMT_STRING(s) [] { \ + typedef typename std::decay<decltype(s)>::type pointer; \ + struct S : fmt::compile_string { \ + static FMT_CONSTEXPR pointer data() { return s; } \ + static FMT_CONSTEXPR size_t size() { return sizeof(s); } \ + explicit operator fmt::string_view() const { return s; } \ + }; \ + return S{}; \ + }() + +#if defined(FMT_STRING_ALIAS) && FMT_STRING_ALIAS +/** + \rst + Constructs a compile-time format string. This macro is disabled by default to + prevent potential name collisions. To enable it define ``FMT_STRING_ALIAS`` to + 1 before including ``fmt/format.h``. + + **Example**:: + + #define FMT_STRING_ALIAS 1 + #include <fmt/format.h> + // A compile-time error because 'd' is an invalid specifier for strings. + std::string s = format(fmt("{:d}"), "foo"); + \endrst + */ +# define fmt(s) FMT_STRING(s) +#endif + +#ifdef FMT_HEADER_ONLY +# define FMT_FUNC inline +# include "format-inl.h" +#else +# define FMT_FUNC +#endif + +// Restore warnings. +#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION +# pragma GCC diagnostic pop +#endif + +#endif // FMT_FORMAT_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/ostream.h b/src/nmodl/ext/spdlog/fmt/bundled/ostream.h new file mode 100644 index 0000000000..e467f1ae6a --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/ostream.h @@ -0,0 +1,157 @@ +// Formatting library for C++ - std::ostream support +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_OSTREAM_H_ +#define FMT_OSTREAM_H_ + +#include "format.h" +#include <ostream> + +FMT_BEGIN_NAMESPACE +namespace internal { + +template <class Char> +class formatbuf : public std::basic_streambuf<Char> { + private: + typedef typename std::basic_streambuf<Char>::int_type int_type; + typedef typename std::basic_streambuf<Char>::traits_type traits_type; + + basic_buffer<Char> &buffer_; + + public: + formatbuf(basic_buffer<Char> &buffer) : buffer_(buffer) {} + + protected: + // The put-area is actually always empty. This makes the implementation + // simpler and has the advantage that the streambuf and the buffer are always + // in sync and sputc never writes into uninitialized memory. The obvious + // disadvantage is that each call to sputc always results in a (virtual) call + // to overflow. There is no disadvantage here for sputn since this always + // results in a call to xsputn. + + int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { + if (!traits_type::eq_int_type(ch, traits_type::eof())) + buffer_.push_back(static_cast<Char>(ch)); + return ch; + } + + std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE { + buffer_.append(s, s + count); + return count; + } +}; + +template <typename Char> +struct test_stream : std::basic_ostream<Char> { + private: + struct null; + // Hide all operator<< from std::basic_ostream<Char>. + void operator<<(null); +}; + +// Checks if T has a user-defined operator<< (e.g. not a member of std::ostream). +template <typename T, typename Char> +class is_streamable { + private: + template <typename U> + static decltype( + internal::declval<test_stream<Char>&>() + << internal::declval<U>(), std::true_type()) test(int); + + template <typename> + static std::false_type test(...); + + typedef decltype(test<T>(0)) result; + + public: + static const bool value = result::value; +}; + +// Write the content of buf to os. +template <typename Char> +void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) { + const Char *data = buf.data(); + typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize; + UnsignedStreamSize size = buf.size(); + UnsignedStreamSize max_size = + internal::to_unsigned((std::numeric_limits<std::streamsize>::max)()); + do { + UnsignedStreamSize n = size <= max_size ? size : max_size; + os.write(data, static_cast<std::streamsize>(n)); + data += n; + size -= n; + } while (size != 0); +} + +template <typename Char, typename T> +void format_value(basic_buffer<Char> &buffer, const T &value) { + internal::formatbuf<Char> format_buf(buffer); + std::basic_ostream<Char> output(&format_buf); + output.exceptions(std::ios_base::failbit | std::ios_base::badbit); + output << value; + buffer.resize(buffer.size()); +} +} // namespace internal + +// Disable conversion to int if T has an overloaded operator<< which is a free +// function (not a member of std::ostream). +template <typename T, typename Char> +struct convert_to_int<T, Char, void> { + static const bool value = + convert_to_int<T, Char, int>::value && + !internal::is_streamable<T, Char>::value; +}; + +// Formats an object of type T that has an overloaded ostream operator<<. +template <typename T, typename Char> +struct formatter<T, Char, + typename std::enable_if< + internal::is_streamable<T, Char>::value && + !internal::format_type< + typename buffer_context<Char>::type, T>::value>::type> + : formatter<basic_string_view<Char>, Char> { + + template <typename Context> + auto format(const T &value, Context &ctx) -> decltype(ctx.out()) { + basic_memory_buffer<Char> buffer; + internal::format_value(buffer, value); + basic_string_view<Char> str(buffer.data(), buffer.size()); + return formatter<basic_string_view<Char>, Char>::format(str, ctx); + } +}; + +template <typename Char> +inline void vprint(std::basic_ostream<Char> &os, + basic_string_view<Char> format_str, + basic_format_args<typename buffer_context<Char>::type> args) { + basic_memory_buffer<Char> buffer; + vformat_to(buffer, format_str, args); + internal::write(os, buffer); +} +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fmt::print(cerr, "Don't {}!", "panic"); + \endrst + */ +template <typename... Args> +inline void print(std::ostream &os, string_view format_str, + const Args & ... args) { + vprint<char>(os, format_str, make_format_args<format_context>(args...)); +} + +template <typename... Args> +inline void print(std::wostream &os, wstring_view format_str, + const Args & ... args) { + vprint<wchar_t>(os, format_str, make_format_args<wformat_context>(args...)); +} +FMT_END_NAMESPACE + +#endif // FMT_OSTREAM_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/posix.h b/src/nmodl/ext/spdlog/fmt/bundled/posix.h new file mode 100644 index 0000000000..7bf877b454 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/posix.h @@ -0,0 +1,324 @@ +// A C++ interface to POSIX functions. +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_POSIX_H_ +#define FMT_POSIX_H_ + +#if defined(__MINGW32__) || defined(__CYGWIN__) +// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. +# undef __STRICT_ANSI__ +#endif + +#include <errno.h> +#include <fcntl.h> // for O_RDONLY +#include <locale.h> // for locale_t +#include <stdio.h> +#include <stdlib.h> // for strtod_l + +#include <cstddef> + +#if defined __APPLE__ || defined(__FreeBSD__) +# include <xlocale.h> // for LC_NUMERIC_MASK on OS X +#endif + +#include "format.h" + +#ifndef FMT_POSIX +# if defined(_WIN32) && !defined(__MINGW32__) +// Fix warnings about deprecated symbols. +# define FMT_POSIX(call) _##call +# else +# define FMT_POSIX(call) call +# endif +#endif + +// Calls to system functions are wrapped in FMT_SYSTEM for testability. +#ifdef FMT_SYSTEM +# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) +#else +# define FMT_SYSTEM(call) call +# ifdef _WIN32 +// Fix warnings about deprecated symbols. +# define FMT_POSIX_CALL(call) ::_##call +# else +# define FMT_POSIX_CALL(call) ::call +# endif +#endif + +// Retries the expression while it evaluates to error_result and errno +// equals to EINTR. +#ifndef _WIN32 +# define FMT_RETRY_VAL(result, expression, error_result) \ + do { \ + result = (expression); \ + } while (result == error_result && errno == EINTR) +#else +# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) +#endif + +#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) + +FMT_BEGIN_NAMESPACE + +/** + \rst + A reference to a null-terminated string. It can be constructed from a C + string or ``std::string``. + + You can use one of the following typedefs for common character types: + + +---------------+-----------------------------+ + | Type | Definition | + +===============+=============================+ + | cstring_view | basic_cstring_view<char> | + +---------------+-----------------------------+ + | wcstring_view | basic_cstring_view<wchar_t> | + +---------------+-----------------------------+ + + This class is most useful as a parameter type to allow passing + different types of strings to a function, for example:: + + template <typename... Args> + std::string format(cstring_view format_str, const Args & ... args); + + format("{}", 42); + format(std::string("{}"), 42); + \endrst + */ +template <typename Char> +class basic_cstring_view { + private: + const Char *data_; + + public: + /** Constructs a string reference object from a C string. */ + basic_cstring_view(const Char *s) : data_(s) {} + + /** + \rst + Constructs a string reference from an ``std::string`` object. + \endrst + */ + basic_cstring_view(const std::basic_string<Char> &s) : data_(s.c_str()) {} + + /** Returns the pointer to a C string. */ + const Char *c_str() const { return data_; } +}; + +typedef basic_cstring_view<char> cstring_view; +typedef basic_cstring_view<wchar_t> wcstring_view; + +// An error code. +class error_code { + private: + int value_; + + public: + explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} + + int get() const FMT_NOEXCEPT { return value_; } +}; + +// A buffered file. +class buffered_file { + private: + FILE *file_; + + friend class file; + + explicit buffered_file(FILE *f) : file_(f) {} + + public: + // Constructs a buffered_file object which doesn't represent any file. + buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {} + + // Destroys the object closing the file it represents if any. + FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT; + + private: + buffered_file(const buffered_file &) = delete; + void operator=(const buffered_file &) = delete; + + + public: + buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) { + other.file_ = FMT_NULL; + } + + buffered_file& operator=(buffered_file &&other) { + close(); + file_ = other.file_; + other.file_ = FMT_NULL; + return *this; + } + + // Opens a file. + FMT_API buffered_file(cstring_view filename, cstring_view mode); + + // Closes the file. + FMT_API void close(); + + // Returns the pointer to a FILE object representing this file. + FILE *get() const FMT_NOEXCEPT { return file_; } + + // We place parentheses around fileno to workaround a bug in some versions + // of MinGW that define fileno as a macro. + FMT_API int (fileno)() const; + + void vprint(string_view format_str, format_args args) { + fmt::vprint(file_, format_str, args); + } + + template <typename... Args> + inline void print(string_view format_str, const Args & ... args) { + vprint(format_str, make_format_args(args...)); + } +}; + +// A file. Closed file is represented by a file object with descriptor -1. +// Methods that are not declared with FMT_NOEXCEPT may throw +// fmt::system_error in case of failure. Note that some errors such as +// closing the file multiple times will cause a crash on Windows rather +// than an exception. You can get standard behavior by overriding the +// invalid parameter handler with _set_invalid_parameter_handler. +class file { + private: + int fd_; // File descriptor. + + // Constructs a file object with a given descriptor. + explicit file(int fd) : fd_(fd) {} + + public: + // Possible values for the oflag argument to the constructor. + enum { + RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. + WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. + RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. + }; + + // Constructs a file object which doesn't represent any file. + file() FMT_NOEXCEPT : fd_(-1) {} + + // Opens a file and constructs a file object representing this file. + FMT_API file(cstring_view path, int oflag); + + private: + file(const file &) = delete; + void operator=(const file &) = delete; + + public: + file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) { + other.fd_ = -1; + } + + file& operator=(file &&other) { + close(); + fd_ = other.fd_; + other.fd_ = -1; + return *this; + } + + // Destroys the object closing the file it represents if any. + FMT_API ~file() FMT_DTOR_NOEXCEPT; + + // Returns the file descriptor. + int descriptor() const FMT_NOEXCEPT { return fd_; } + + // Closes the file. + FMT_API void close(); + + // Returns the file size. The size has signed type for consistency with + // stat::st_size. + FMT_API long long size() const; + + // Attempts to read count bytes from the file into the specified buffer. + FMT_API std::size_t read(void *buffer, std::size_t count); + + // Attempts to write count bytes from the specified buffer to the file. + FMT_API std::size_t write(const void *buffer, std::size_t count); + + // Duplicates a file descriptor with the dup function and returns + // the duplicate as a file object. + FMT_API static file dup(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd); + + // Makes fd be the copy of this file descriptor, closing fd first if + // necessary. + FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT; + + // Creates a pipe setting up read_end and write_end file objects for reading + // and writing respectively. + FMT_API static void pipe(file &read_end, file &write_end); + + // Creates a buffered_file object associated with this file and detaches + // this file object from the file. + FMT_API buffered_file fdopen(const char *mode); +}; + +// Returns the memory page size. +long getpagesize(); + +#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \ + !defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) && \ + !defined(__NEWLIB_H__) +# define FMT_LOCALE +#endif + +#ifdef FMT_LOCALE +// A "C" numeric locale. +class Locale { + private: +# ifdef _MSC_VER + typedef _locale_t locale_t; + + enum { LC_NUMERIC_MASK = LC_NUMERIC }; + + static locale_t newlocale(int category_mask, const char *locale, locale_t) { + return _create_locale(category_mask, locale); + } + + static void freelocale(locale_t locale) { + _free_locale(locale); + } + + static double strtod_l(const char *nptr, char **endptr, _locale_t locale) { + return _strtod_l(nptr, endptr, locale); + } +# endif + + locale_t locale_; + + Locale(const Locale &) = delete; + void operator=(const Locale &) = delete; + + public: + typedef locale_t Type; + + Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) { + if (!locale_) + FMT_THROW(system_error(errno, "cannot create locale")); + } + ~Locale() { freelocale(locale_); } + + Type get() const { return locale_; } + + // Converts string to floating-point number and advances str past the end + // of the parsed input. + double strtod(const char *&str) const { + char *end = FMT_NULL; + double result = strtod_l(str, &end, locale_); + str = end; + return result; + } +}; +#endif // FMT_LOCALE +FMT_END_NAMESPACE + +#endif // FMT_POSIX_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/printf.h b/src/nmodl/ext/spdlog/fmt/bundled/printf.h new file mode 100644 index 0000000000..190784df0a --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/printf.h @@ -0,0 +1,726 @@ +// Formatting library for C++ +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_PRINTF_H_ +#define FMT_PRINTF_H_ + +#include <algorithm> // std::fill_n +#include <limits> // std::numeric_limits + +#include "ostream.h" + +FMT_BEGIN_NAMESPACE +namespace internal { + +// Checks if a value fits in int - used to avoid warnings about comparing +// signed and unsigned integers. +template <bool IsSigned> +struct int_checker { + template <typename T> + static bool fits_in_int(T value) { + unsigned max = std::numeric_limits<int>::max(); + return value <= max; + } + static bool fits_in_int(bool) { return true; } +}; + +template <> +struct int_checker<true> { + template <typename T> + static bool fits_in_int(T value) { + return value >= std::numeric_limits<int>::min() && + value <= std::numeric_limits<int>::max(); + } + static bool fits_in_int(int) { return true; } +}; + +class printf_precision_handler: public function<int> { + public: + template <typename T> + typename std::enable_if<std::is_integral<T>::value, int>::type + operator()(T value) { + if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) + FMT_THROW(format_error("number is too big")); + return static_cast<int>(value); + } + + template <typename T> + typename std::enable_if<!std::is_integral<T>::value, int>::type operator()(T) { + FMT_THROW(format_error("precision is not integer")); + return 0; + } +}; + +// An argument visitor that returns true iff arg is a zero integer. +class is_zero_int: public function<bool> { + public: + template <typename T> + typename std::enable_if<std::is_integral<T>::value, bool>::type + operator()(T value) { return value == 0; } + + template <typename T> + typename std::enable_if<!std::is_integral<T>::value, bool>::type + operator()(T) { return false; } +}; + +template <typename T> +struct make_unsigned_or_bool : std::make_unsigned<T> {}; + +template <> +struct make_unsigned_or_bool<bool> { + typedef bool type; +}; + +template <typename T, typename Context> +class arg_converter: public function<void> { + private: + typedef typename Context::char_type Char; + + basic_format_arg<Context> &arg_; + typename Context::char_type type_; + + public: + arg_converter(basic_format_arg<Context> &arg, Char type) + : arg_(arg), type_(type) {} + + void operator()(bool value) { + if (type_ != 's') + operator()<bool>(value); + } + + template <typename U> + typename std::enable_if<std::is_integral<U>::value>::type + operator()(U value) { + bool is_signed = type_ == 'd' || type_ == 'i'; + typedef typename std::conditional< + std::is_same<T, void>::value, U, T>::type TargetType; + if (const_check(sizeof(TargetType) <= sizeof(int))) { + // Extra casts are used to silence warnings. + if (is_signed) { + arg_ = internal::make_arg<Context>( + static_cast<int>(static_cast<TargetType>(value))); + } else { + typedef typename make_unsigned_or_bool<TargetType>::type Unsigned; + arg_ = internal::make_arg<Context>( + static_cast<unsigned>(static_cast<Unsigned>(value))); + } + } else { + if (is_signed) { + // glibc's printf doesn't sign extend arguments of smaller types: + // std::printf("%lld", -42); // prints "4294967254" + // but we don't have to do the same because it's a UB. + arg_ = internal::make_arg<Context>(static_cast<long long>(value)); + } else { + arg_ = internal::make_arg<Context>( + static_cast<typename make_unsigned_or_bool<U>::type>(value)); + } + } + } + + template <typename U> + typename std::enable_if<!std::is_integral<U>::value>::type operator()(U) { + // No coversion needed for non-integral types. + } +}; + +// Converts an integer argument to T for printf, if T is an integral type. +// If T is void, the argument is converted to corresponding signed or unsigned +// type depending on the type specifier: 'd' and 'i' - signed, other - +// unsigned). +template <typename T, typename Context, typename Char> +void convert_arg(basic_format_arg<Context> &arg, Char type) { + fmt::visit(arg_converter<T, Context>(arg, type), arg); +} + +// Converts an integer argument to char for printf. +template <typename Context> +class char_converter: public function<void> { + private: + basic_format_arg<Context> &arg_; + + public: + explicit char_converter(basic_format_arg<Context> &arg) : arg_(arg) {} + + template <typename T> + typename std::enable_if<std::is_integral<T>::value>::type + operator()(T value) { + typedef typename Context::char_type Char; + arg_ = internal::make_arg<Context>(static_cast<Char>(value)); + } + + template <typename T> + typename std::enable_if<!std::is_integral<T>::value>::type operator()(T) { + // No coversion needed for non-integral types. + } +}; + +// Checks if an argument is a valid printf width specifier and sets +// left alignment if it is negative. +template <typename Char> +class printf_width_handler: public function<unsigned> { + private: + typedef basic_format_specs<Char> format_specs; + + format_specs &spec_; + + public: + explicit printf_width_handler(format_specs &spec) : spec_(spec) {} + + template <typename T> + typename std::enable_if<std::is_integral<T>::value, unsigned>::type + operator()(T value) { + typedef typename internal::int_traits<T>::main_type UnsignedType; + UnsignedType width = static_cast<UnsignedType>(value); + if (internal::is_negative(value)) { + spec_.align_ = ALIGN_LEFT; + width = 0 - width; + } + unsigned int_max = std::numeric_limits<int>::max(); + if (width > int_max) + FMT_THROW(format_error("number is too big")); + return static_cast<unsigned>(width); + } + + template <typename T> + typename std::enable_if<!std::is_integral<T>::value, unsigned>::type + operator()(T) { + FMT_THROW(format_error("width is not integer")); + return 0; + } +}; +} // namespace internal + +template <typename Range> +class printf_arg_formatter; + +template < + typename OutputIt, typename Char, + typename ArgFormatter = + printf_arg_formatter<back_insert_range<internal::basic_buffer<Char>>>> +class basic_printf_context; + +/** + \rst + The ``printf`` argument formatter. + \endrst + */ +template <typename Range> +class printf_arg_formatter: + public internal::function< + typename internal::arg_formatter_base<Range>::iterator>, + public internal::arg_formatter_base<Range> { + private: + typedef typename Range::value_type char_type; + typedef decltype(internal::declval<Range>().begin()) iterator; + typedef internal::arg_formatter_base<Range> base; + typedef basic_printf_context<iterator, char_type> context_type; + + context_type &context_; + + void write_null_pointer(char) { + this->spec()->type_ = 0; + this->write("(nil)"); + } + + void write_null_pointer(wchar_t) { + this->spec()->type_ = 0; + this->write(L"(nil)"); + } + + public: + typedef typename base::format_specs format_specs; + + /** + \rst + Constructs an argument formatter object. + *buffer* is a reference to the output buffer and *spec* contains format + specifier information for standard argument types. + \endrst + */ + printf_arg_formatter(internal::basic_buffer<char_type> &buffer, + format_specs &spec, context_type &ctx) + : base(back_insert_range<internal::basic_buffer<char_type>>(buffer), &spec), + context_(ctx) {} + + template <typename T> + typename std::enable_if<std::is_integral<T>::value, iterator>::type + operator()(T value) { + // MSVC2013 fails to compile separate overloads for bool and char_type so + // use std::is_same instead. + if (std::is_same<T, bool>::value) { + format_specs &fmt_spec = *this->spec(); + if (fmt_spec.type_ != 's') + return base::operator()(value ? 1 : 0); + fmt_spec.type_ = 0; + this->write(value != 0); + } else if (std::is_same<T, char_type>::value) { + format_specs &fmt_spec = *this->spec(); + if (fmt_spec.type_ && fmt_spec.type_ != 'c') + return (*this)(static_cast<int>(value)); + fmt_spec.flags_ = 0; + fmt_spec.align_ = ALIGN_RIGHT; + return base::operator()(value); + } else { + return base::operator()(value); + } + return this->out(); + } + + template <typename T> + typename std::enable_if<std::is_floating_point<T>::value, iterator>::type + operator()(T value) { + return base::operator()(value); + } + + /** Formats a null-terminated C string. */ + iterator operator()(const char *value) { + if (value) + base::operator()(value); + else if (this->spec()->type_ == 'p') + write_null_pointer(char_type()); + else + this->write("(null)"); + return this->out(); + } + + /** Formats a null-terminated wide C string. */ + iterator operator()(const wchar_t *value) { + if (value) + base::operator()(value); + else if (this->spec()->type_ == 'p') + write_null_pointer(char_type()); + else + this->write(L"(null)"); + return this->out(); + } + + iterator operator()(basic_string_view<char_type> value) { + return base::operator()(value); + } + + iterator operator()(monostate value) { + return base::operator()(value); + } + + /** Formats a pointer. */ + iterator operator()(const void *value) { + if (value) + return base::operator()(value); + this->spec()->type_ = 0; + write_null_pointer(char_type()); + return this->out(); + } + + /** Formats an argument of a custom (user-defined) type. */ + iterator operator()(typename basic_format_arg<context_type>::handle handle) { + handle.format(context_); + return this->out(); + } +}; + +template <typename T> +struct printf_formatter { + template <typename ParseContext> + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } + + template <typename FormatContext> + auto format(const T &value, FormatContext &ctx) -> decltype(ctx.out()) { + internal::format_value(internal::get_container(ctx.out()), value); + return ctx.out(); + } +}; + +/** This template formats data and writes the output to a writer. */ +template <typename OutputIt, typename Char, typename ArgFormatter> +class basic_printf_context : + // Inherit publicly as a workaround for the icc bug + // https://software.intel.com/en-us/forums/intel-c-compiler/topic/783476. + public internal::context_base< + OutputIt, basic_printf_context<OutputIt, Char, ArgFormatter>, Char> { + public: + /** The character type for the output. */ + typedef Char char_type; + + template <typename T> + struct formatter_type { typedef printf_formatter<T> type; }; + + private: + typedef internal::context_base<OutputIt, basic_printf_context, Char> base; + typedef typename base::format_arg format_arg; + typedef basic_format_specs<char_type> format_specs; + typedef internal::null_terminating_iterator<char_type> iterator; + + void parse_flags(format_specs &spec, iterator &it); + + // Returns the argument with specified index or, if arg_index is equal + // to the maximum unsigned value, the next argument. + format_arg get_arg( + iterator it, + unsigned arg_index = (std::numeric_limits<unsigned>::max)()); + + // Parses argument index, flags and width and returns the argument index. + unsigned parse_header(iterator &it, format_specs &spec); + + public: + /** + \rst + Constructs a ``printf_context`` object. References to the arguments and + the writer are stored in the context object so make sure they have + appropriate lifetimes. + \endrst + */ + basic_printf_context(OutputIt out, basic_string_view<char_type> format_str, + basic_format_args<basic_printf_context> args) + : base(out, format_str, args) {} + + using base::parse_context; + using base::out; + using base::advance_to; + + /** Formats stored arguments and writes the output to the range. */ + void format(); +}; + +template <typename OutputIt, typename Char, typename AF> +void basic_printf_context<OutputIt, Char, AF>::parse_flags( + format_specs &spec, iterator &it) { + for (;;) { + switch (*it++) { + case '-': + spec.align_ = ALIGN_LEFT; + break; + case '+': + spec.flags_ |= SIGN_FLAG | PLUS_FLAG; + break; + case '0': + spec.fill_ = '0'; + break; + case ' ': + spec.flags_ |= SIGN_FLAG; + break; + case '#': + spec.flags_ |= HASH_FLAG; + break; + default: + --it; + return; + } + } +} + +template <typename OutputIt, typename Char, typename AF> +typename basic_printf_context<OutputIt, Char, AF>::format_arg + basic_printf_context<OutputIt, Char, AF>::get_arg( + iterator it, unsigned arg_index) { + (void)it; + if (arg_index == std::numeric_limits<unsigned>::max()) + return this->do_get_arg(this->parse_context().next_arg_id()); + return base::get_arg(arg_index - 1); +} + +template <typename OutputIt, typename Char, typename AF> +unsigned basic_printf_context<OutputIt, Char, AF>::parse_header( + iterator &it, format_specs &spec) { + unsigned arg_index = std::numeric_limits<unsigned>::max(); + char_type c = *it; + if (c >= '0' && c <= '9') { + // Parse an argument index (if followed by '$') or a width possibly + // preceded with '0' flag(s). + internal::error_handler eh; + unsigned value = parse_nonnegative_int(it, eh); + if (*it == '$') { // value is an argument index + ++it; + arg_index = value; + } else { + if (c == '0') + spec.fill_ = '0'; + if (value != 0) { + // Nonzero value means that we parsed width and don't need to + // parse it or flags again, so return now. + spec.width_ = value; + return arg_index; + } + } + } + parse_flags(spec, it); + // Parse width. + if (*it >= '0' && *it <= '9') { + internal::error_handler eh; + spec.width_ = parse_nonnegative_int(it, eh); + } else if (*it == '*') { + ++it; + spec.width_ = + fmt::visit(internal::printf_width_handler<char_type>(spec), get_arg(it)); + } + return arg_index; +} + +template <typename OutputIt, typename Char, typename AF> +void basic_printf_context<OutputIt, Char, AF>::format() { + auto &buffer = internal::get_container(this->out()); + auto start = iterator(this->parse_context()); + auto it = start; + using internal::pointer_from; + while (*it) { + char_type c = *it++; + if (c != '%') continue; + if (*it == c) { + buffer.append(pointer_from(start), pointer_from(it)); + start = ++it; + continue; + } + buffer.append(pointer_from(start), pointer_from(it) - 1); + + format_specs spec; + spec.align_ = ALIGN_RIGHT; + + // Parse argument index, flags and width. + unsigned arg_index = parse_header(it, spec); + + // Parse precision. + if (*it == '.') { + ++it; + if ('0' <= *it && *it <= '9') { + internal::error_handler eh; + spec.precision_ = static_cast<int>(parse_nonnegative_int(it, eh)); + } else if (*it == '*') { + ++it; + spec.precision_ = + fmt::visit(internal::printf_precision_handler(), get_arg(it)); + } else { + spec.precision_ = 0; + } + } + + format_arg arg = get_arg(it, arg_index); + if (spec.flag(HASH_FLAG) && fmt::visit(internal::is_zero_int(), arg)) + spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG); + if (spec.fill_ == '0') { + if (arg.is_arithmetic()) + spec.align_ = ALIGN_NUMERIC; + else + spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. + } + + // Parse length and convert the argument to the required type. + using internal::convert_arg; + switch (*it++) { + case 'h': + if (*it == 'h') + convert_arg<signed char>(arg, *++it); + else + convert_arg<short>(arg, *it); + break; + case 'l': + if (*it == 'l') + convert_arg<long long>(arg, *++it); + else + convert_arg<long>(arg, *it); + break; + case 'j': + convert_arg<intmax_t>(arg, *it); + break; + case 'z': + convert_arg<std::size_t>(arg, *it); + break; + case 't': + convert_arg<std::ptrdiff_t>(arg, *it); + break; + case 'L': + // printf produces garbage when 'L' is omitted for long double, no + // need to do the same. + break; + default: + --it; + convert_arg<void>(arg, *it); + } + + // Parse type. + if (!*it) + FMT_THROW(format_error("invalid format string")); + spec.type_ = static_cast<char>(*it++); + if (arg.is_integral()) { + // Normalize type. + switch (spec.type_) { + case 'i': case 'u': + spec.type_ = 'd'; + break; + case 'c': + // TODO: handle wchar_t better? + fmt::visit(internal::char_converter<basic_printf_context>(arg), arg); + break; + } + } + + start = it; + + // Format argument. + fmt::visit(AF(buffer, spec, *this), arg); + } + buffer.append(pointer_from(start), pointer_from(it)); +} + +template <typename Char, typename Context> +void printf(internal::basic_buffer<Char> &buf, basic_string_view<Char> format, + basic_format_args<Context> args) { + Context(std::back_inserter(buf), format, args).format(); +} + +template <typename Buffer> +struct printf_context { + typedef basic_printf_context< + std::back_insert_iterator<Buffer>, typename Buffer::value_type> type; +}; + +template <typename ...Args> +inline format_arg_store<printf_context<internal::buffer>::type, Args...> + make_printf_args(const Args & ... args) { + return format_arg_store<printf_context<internal::buffer>::type, Args...>( + args...); +} +typedef basic_format_args<printf_context<internal::buffer>::type> printf_args; +typedef basic_format_args<printf_context<internal::wbuffer>::type> wprintf_args; + +inline std::string vsprintf(string_view format, printf_args args) { + memory_buffer buffer; + printf(buffer, format, args); + return to_string(buffer); +} + +/** + \rst + Formats arguments and returns the result as a string. + + **Example**:: + + std::string message = fmt::sprintf("The answer is %d", 42); + \endrst +*/ +template <typename... Args> +inline std::string sprintf(string_view format_str, const Args & ... args) { + return vsprintf(format_str, + make_format_args<typename printf_context<internal::buffer>::type>(args...)); +} + +inline std::wstring vsprintf(wstring_view format, wprintf_args args) { + wmemory_buffer buffer; + printf(buffer, format, args); + return to_string(buffer); +} + +template <typename... Args> +inline std::wstring sprintf(wstring_view format_str, const Args & ... args) { + return vsprintf(format_str, + make_format_args<typename printf_context<internal::wbuffer>::type>(args...)); +} + +template <typename Char> +inline int vfprintf(std::FILE *f, basic_string_view<Char> format, + basic_format_args<typename printf_context< + internal::basic_buffer<Char>>::type> args) { + basic_memory_buffer<Char> buffer; + printf(buffer, format, args); + std::size_t size = buffer.size(); + return std::fwrite( + buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast<int>(size); +} + +/** + \rst + Prints formatted data to the file *f*. + + **Example**:: + + fmt::fprintf(stderr, "Don't %s!", "panic"); + \endrst + */ +template <typename... Args> +inline int fprintf(std::FILE *f, string_view format_str, const Args & ... args) { + auto vargs = make_format_args< + typename printf_context<internal::buffer>::type>(args...); + return vfprintf<char>(f, format_str, vargs); +} + +template <typename... Args> +inline int fprintf(std::FILE *f, wstring_view format_str, + const Args & ... args) { + return vfprintf(f, format_str, + make_format_args<typename printf_context<internal::wbuffer>::type>(args...)); +} + +inline int vprintf(string_view format, printf_args args) { + return vfprintf(stdout, format, args); +} + +inline int vprintf(wstring_view format, wprintf_args args) { + return vfprintf(stdout, format, args); +} + +/** + \rst + Prints formatted data to ``stdout``. + + **Example**:: + + fmt::printf("Elapsed time: %.2f seconds", 1.23); + \endrst + */ +template <typename... Args> +inline int printf(string_view format_str, const Args & ... args) { + return vprintf(format_str, + make_format_args<typename printf_context<internal::buffer>::type>(args...)); +} + +template <typename... Args> +inline int printf(wstring_view format_str, const Args & ... args) { + return vprintf(format_str, + make_format_args<typename printf_context<internal::wbuffer>::type>(args...)); +} + +inline int vfprintf(std::ostream &os, string_view format_str, + printf_args args) { + memory_buffer buffer; + printf(buffer, format_str, args); + internal::write(os, buffer); + return static_cast<int>(buffer.size()); +} + +inline int vfprintf(std::wostream &os, wstring_view format_str, + wprintf_args args) { + wmemory_buffer buffer; + printf(buffer, format_str, args); + internal::write(os, buffer); + return static_cast<int>(buffer.size()); +} + +/** + \rst + Prints formatted data to the stream *os*. + + **Example**:: + + fmt::fprintf(cerr, "Don't %s!", "panic"); + \endrst + */ +template <typename... Args> +inline int fprintf(std::ostream &os, string_view format_str, + const Args & ... args) { + auto vargs = make_format_args< + typename printf_context<internal::buffer>::type>(args...); + return vfprintf(os, format_str, vargs); +} + +template <typename... Args> +inline int fprintf(std::wostream &os, wstring_view format_str, + const Args & ... args) { + auto vargs = make_format_args< + typename printf_context<internal::buffer>::type>(args...); + return vfprintf(os, format_str, vargs); +} +FMT_END_NAMESPACE + +#endif // FMT_PRINTF_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/ranges.h b/src/nmodl/ext/spdlog/fmt/bundled/ranges.h new file mode 100644 index 0000000000..3672d4ca81 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/ranges.h @@ -0,0 +1,308 @@ +// Formatting library for C++ - the core API +// +// Copyright (c) 2012 - present, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. +// +// Copyright (c) 2018 - present, Remotion (Igor Schulz) +// All Rights Reserved +// {fmt} support for ranges, containers and types tuple interface. + +#ifndef FMT_RANGES_H_ +#define FMT_RANGES_H_ + +#include "format.h" +#include <type_traits> + +// output only up to N items from the range. +#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT +# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 +#endif + +FMT_BEGIN_NAMESPACE + +template <typename Char> +struct formatting_base { + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return ctx.begin(); + } +}; + +template <typename Char, typename Enable = void> +struct formatting_range : formatting_base<Char> { + static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = + FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range. + Char prefix; + Char delimiter; + Char postfix; + formatting_range() : prefix('{'), delimiter(','), postfix('}') {} + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +template <typename Char, typename Enable = void> +struct formatting_tuple : formatting_base<Char> { + Char prefix; + Char delimiter; + Char postfix; + formatting_tuple() : prefix('('), delimiter(','), postfix(')') {} + static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; + static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; +}; + +namespace internal { + +template <typename RangeT, typename OutputIterator> +void copy(const RangeT &range, OutputIterator out) { + for (auto it = range.begin(), end = range.end(); it != end; ++it) + *out++ = *it; +} + +template <typename OutputIterator> +void copy(const char *str, OutputIterator out) { + const char *p_curr = str; + while (*p_curr) { + *out++ = *p_curr++; + } +} + +template <typename OutputIterator> +void copy(char ch, OutputIterator out) { + *out++ = ch; +} + +/// Return true value if T has std::string interface, like std::string_view. +template <typename T> +class is_like_std_string { + template <typename U> + static auto check(U *p) -> + decltype(p->find('a'), p->length(), p->data(), int()); + template <typename> + static void check(...); + + public: + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void<decltype(check<T>(FMT_NULL))>::value; +}; + +template <typename Char> +struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {}; + +template <typename... Ts> +struct conditional_helper {}; + +template <typename T, typename _ = void> +struct is_range_ : std::false_type {}; + +#if !FMT_MSC_VER || FMT_MSC_VER > 1800 +template <typename T> +struct is_range_<T, typename std::conditional< + false, + conditional_helper<decltype(internal::declval<T>().begin()), + decltype(internal::declval<T>().end())>, + void>::type> : std::true_type {}; +#endif + +/// tuple_size and tuple_element check. +template <typename T> +class is_tuple_like_ { + template <typename U> + static auto check(U *p) -> + decltype(std::tuple_size<U>::value, + internal::declval<typename std::tuple_element<0, U>::type>(), int()); + template <typename> + static void check(...); + + public: + static FMT_CONSTEXPR_DECL const bool value = + !std::is_void<decltype(check<T>(FMT_NULL))>::value; +}; + +// Check for integer_sequence +#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 +template <typename T, T... N> +using integer_sequence = std::integer_sequence<T, N...>; +template <std::size_t... N> +using index_sequence = std::index_sequence<N...>; +template <std::size_t N> +using make_index_sequence = std::make_index_sequence<N>; +#else +template <typename T, T... N> +struct integer_sequence { + typedef T value_type; + + static FMT_CONSTEXPR std::size_t size() { + return sizeof...(N); + } +}; + +template <std::size_t... N> +using index_sequence = integer_sequence<std::size_t, N...>; + +template <typename T, std::size_t N, T... Ns> +struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {}; +template <typename T, T... Ns> +struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {}; + +template <std::size_t N> +using make_index_sequence = make_integer_sequence<std::size_t, N>; +#endif + +template <class Tuple, class F, size_t... Is> +void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT { + using std::get; + // using free function get<I>(T) now. + const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; + (void)_; // blocks warnings +} + +template <class T> +FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> +get_indexes(T const &) { return {}; } + +template <class Tuple, class F> +void for_each(Tuple &&tup, F &&f) { + const auto indexes = get_indexes(tup); + for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); +} + +template<typename Arg> +FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, + typename std::enable_if< + !is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) { + return add_space ? " {}" : "{}"; +} + +template<typename Arg> +FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, + typename std::enable_if< + is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) { + return add_space ? " \"{}\"" : "\"{}\""; +} + +FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) { + return add_space ? " \"{}\"" : "\"{}\""; +} +FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) { + return add_space ? L" \"{}\"" : L"\"{}\""; +} + +FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) { + return add_space ? " '{}'" : "'{}'"; +} +FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) { + return add_space ? L" '{}'" : L"'{}'"; +} + +} // namespace internal + +template <typename T> +struct is_tuple_like { + static FMT_CONSTEXPR_DECL const bool value = + internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value; +}; + +template <typename TupleT, typename Char> +struct formatter<TupleT, Char, + typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> { +private: + // C++11 generic lambda for format() + template <typename FormatContext> + struct format_each { + template <typename T> + void operator()(const T& v) { + if (i > 0) { + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + format_to(out, + internal::format_str_quoted( + (formatting.add_delimiter_spaces && i > 0), v), + v); + ++i; + } + + formatting_tuple<Char>& formatting; + std::size_t& i; + typename std::add_lvalue_reference<decltype(std::declval<FormatContext>().out())>::type out; + }; + +public: + formatting_tuple<Char> formatting; + + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return formatting.parse(ctx); + } + + template <typename FormatContext = format_context> + auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) { + auto out = ctx.out(); + std::size_t i = 0; + internal::copy(formatting.prefix, out); + + internal::for_each(values, format_each<FormatContext>{formatting, i, out}); + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + + return ctx.out(); + } +}; + +template <typename T> +struct is_range { + static FMT_CONSTEXPR_DECL const bool value = + internal::is_range_<T>::value && !internal::is_like_std_string<T>::value; +}; + +template <typename RangeT, typename Char> +struct formatter<RangeT, Char, + typename std::enable_if<fmt::is_range<RangeT>::value>::type> { + + formatting_range<Char> formatting; + + template <typename ParseContext> + FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + return formatting.parse(ctx); + } + + template <typename FormatContext> + typename FormatContext::iterator format( + const RangeT &values, FormatContext &ctx) { + auto out = ctx.out(); + internal::copy(formatting.prefix, out); + std::size_t i = 0; + for (auto it = values.begin(), end = values.end(); it != end; ++it) { + if (i > 0) { + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.delimiter, out); + } + format_to(out, + internal::format_str_quoted( + (formatting.add_delimiter_spaces && i > 0), *it), + *it); + if (++i > formatting.range_length_limit) { + format_to(out, " ... <other elements>"); + break; + } + } + if (formatting.add_prepostfix_space) { + *out++ = ' '; + } + internal::copy(formatting.postfix, out); + return ctx.out(); + } +}; + +FMT_END_NAMESPACE + +#endif // FMT_RANGES_H_ + diff --git a/src/nmodl/ext/spdlog/fmt/bundled/time.h b/src/nmodl/ext/spdlog/fmt/bundled/time.h new file mode 100644 index 0000000000..a624058f39 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/bundled/time.h @@ -0,0 +1,156 @@ +// Formatting library for C++ - time formatting +// +// Copyright (c) 2012 - 2016, Victor Zverovich +// All rights reserved. +// +// For the license information refer to format.h. + +#ifndef FMT_TIME_H_ +#define FMT_TIME_H_ + +#include "format.h" +#include <ctime> + +FMT_BEGIN_NAMESPACE + +// Prevents expansion of a preceding token as a function-style macro. +// Usage: f FMT_NOMACRO() +#define FMT_NOMACRO + +namespace internal{ +inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } +inline null<> localtime_s(...) { return null<>(); } +inline null<> gmtime_r(...) { return null<>(); } +inline null<> gmtime_s(...) { return null<>(); } +} + +// Thread-safe replacement for std::localtime +inline std::tm localtime(std::time_t time) { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t): time_(t) {} + + bool run() { + using namespace fmt::internal; + return handle(localtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) { return tm != FMT_NULL; } + + bool handle(internal::null<>) { + using namespace fmt::internal; + return fallback(localtime_s(&tm_, &time_)); + } + + bool fallback(int res) { return res == 0; } + + bool fallback(internal::null<>) { + using namespace fmt::internal; + std::tm *tm = std::localtime(&time_); + if (tm) tm_ = *tm; + return tm != FMT_NULL; + } + }; + dispatcher lt(time); + if (lt.run()) + return lt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); +} + +// Thread-safe replacement for std::gmtime +inline std::tm gmtime(std::time_t time) { + struct dispatcher { + std::time_t time_; + std::tm tm_; + + dispatcher(std::time_t t): time_(t) {} + + bool run() { + using namespace fmt::internal; + return handle(gmtime_r(&time_, &tm_)); + } + + bool handle(std::tm *tm) { return tm != FMT_NULL; } + + bool handle(internal::null<>) { + using namespace fmt::internal; + return fallback(gmtime_s(&tm_, &time_)); + } + + bool fallback(int res) { return res == 0; } + + bool fallback(internal::null<>) { + std::tm *tm = std::gmtime(&time_); + if (tm) tm_ = *tm; + return tm != FMT_NULL; + } + }; + dispatcher gt(time); + if (gt.run()) + return gt.tm_; + // Too big time values may be unsupported. + FMT_THROW(format_error("time_t value out of range")); +} + +namespace internal { +inline std::size_t strftime(char *str, std::size_t count, const char *format, + const std::tm *time) { + return std::strftime(str, count, format, time); +} + +inline std::size_t strftime(wchar_t *str, std::size_t count, + const wchar_t *format, const std::tm *time) { + return std::wcsftime(str, count, format, time); +} +} + +template <typename Char> +struct formatter<std::tm, Char> { + template <typename ParseContext> + auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { + auto it = internal::null_terminating_iterator<Char>(ctx); + if (*it == ':') + ++it; + auto end = it; + while (*end && *end != '}') + ++end; + tm_format.reserve(end - it + 1); + using internal::pointer_from; + tm_format.append(pointer_from(it), pointer_from(end)); + tm_format.push_back('\0'); + return pointer_from(end); + } + + template <typename FormatContext> + auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) { + internal::basic_buffer<Char> &buf = internal::get_container(ctx.out()); + std::size_t start = buf.size(); + for (;;) { + std::size_t size = buf.capacity() - start; + std::size_t count = + internal::strftime(&buf[start], size, &tm_format[0], &tm); + if (count != 0) { + buf.resize(start + count); + break; + } + if (size >= tm_format.size() * 256) { + // If the buffer is 256 times larger than the format string, assume + // that `strftime` gives an empty result. There doesn't seem to be a + // better way to distinguish the two cases: + // https://github.com/fmtlib/fmt/issues/367 + break; + } + const std::size_t MIN_GROWTH = 10; + buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); + } + return ctx.out(); + } + + basic_memory_buffer<Char> tm_format; +}; +FMT_END_NAMESPACE + +#endif // FMT_TIME_H_ diff --git a/src/nmodl/ext/spdlog/fmt/fmt.h b/src/nmodl/ext/spdlog/fmt/fmt.h new file mode 100644 index 0000000000..616af0cc78 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/fmt.h @@ -0,0 +1,25 @@ +// +// Copyright(c) 2016-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// +// Include a bundled header-only copy of fmtlib or an external one. +// By default spdlog include its own copy. +// + +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#ifndef FMT_USE_WINDOWS_H +#define FMT_USE_WINDOWS_H 0 +#endif +#include "bundled/core.h" +#include "bundled/format.h" +#else // external fmtlib +#include <fmt/core.h> +#include <fmt/format.h> +#endif diff --git a/src/nmodl/ext/spdlog/fmt/ostr.h b/src/nmodl/ext/spdlog/fmt/ostr.h new file mode 100644 index 0000000000..9902898f84 --- /dev/null +++ b/src/nmodl/ext/spdlog/fmt/ostr.h @@ -0,0 +1,18 @@ +// +// Copyright(c) 2016 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +// +// include bundled or external copy of fmtlib's ostream support +// +#if !defined(SPDLOG_FMT_EXTERNAL) +#ifndef FMT_HEADER_ONLY +#define FMT_HEADER_ONLY +#endif +#include "bundled/ostream.h" +#include "fmt.h" +#else +#include <fmt/ostream.h> +#endif diff --git a/src/nmodl/ext/spdlog/formatter.h b/src/nmodl/ext/spdlog/formatter.h new file mode 100644 index 0000000000..a7ef6b8b5a --- /dev/null +++ b/src/nmodl/ext/spdlog/formatter.h @@ -0,0 +1,20 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "fmt/fmt.h" +#include "spdlog/details/log_msg.h" + +namespace spdlog { + +class formatter +{ +public: + virtual ~formatter() = default; + virtual void format(const details::log_msg &msg, fmt::memory_buffer &dest) = 0; + virtual std::unique_ptr<formatter> clone() const = 0; +}; +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/logger.h b/src/nmodl/ext/spdlog/logger.h new file mode 100644 index 0000000000..1bcf4bc295 --- /dev/null +++ b/src/nmodl/ext/spdlog/logger.h @@ -0,0 +1,167 @@ +// +// Copyright(c) 2015-2108 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +// Thread safe logger (except for set_pattern(..), set_formatter(..) and +// set_error_handler()) +// Has name, log level, vector of std::shared sink pointers and formatter +// Upon each log write the logger: +// 1. Checks if its log level is enough to log the message and if yes: +// 2. Call the underlying sinks to do the job. +// 3. Each sink use its own private copy of a formatter to format the message +// and send to its destination. +// +// The use of private formatter per sink provides the opportunity to cache some +// formatted data, +// and support customize format per each sink. + +#include "spdlog/common.h" +#include "spdlog/formatter.h" +#include "spdlog/sinks/sink.h" + +#include <locale> +#include <memory> +#include <string> +#include <vector> + +namespace spdlog { + +class logger +{ +public: + logger(std::string name, sink_ptr single_sink); + logger(std::string name, sinks_init_list sinks); + + template<typename It> + logger(std::string name, It begin, It end); + + virtual ~logger(); + + logger(const logger &) = delete; + logger &operator=(const logger &) = delete; + + template<typename... Args> + void log(level::level_enum lvl, const char *fmt, const Args &... args); + + template<typename... Args> + void log(level::level_enum lvl, const char *msg); + + template<typename... Args> + void trace(const char *fmt, const Args &... args); + + template<typename... Args> + void debug(const char *fmt, const Args &... args); + + template<typename... Args> + void info(const char *fmt, const Args &... args); + + template<typename... Args> + void warn(const char *fmt, const Args &... args); + + template<typename... Args> + void error(const char *fmt, const Args &... args); + + template<typename... Args> + void critical(const char *fmt, const Args &... args); + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +#ifndef _WIN32 +#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows +#else + template<typename... Args> + void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args); + + template<typename... Args> + void trace(const wchar_t *fmt, const Args &... args); + + template<typename... Args> + void debug(const wchar_t *fmt, const Args &... args); + + template<typename... Args> + void info(const wchar_t *fmt, const Args &... args); + + template<typename... Args> + void warn(const wchar_t *fmt, const Args &... args); + + template<typename... Args> + void error(const wchar_t *fmt, const Args &... args); + + template<typename... Args> + void critical(const wchar_t *fmt, const Args &... args); +#endif // _WIN32 +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + + template<typename T> + void log(level::level_enum lvl, const T &); + + template<typename T> + void trace(const T &msg); + + template<typename T> + void debug(const T &msg); + + template<typename T> + void info(const T &msg); + + template<typename T> + void warn(const T &msg); + + template<typename T> + void error(const T &msg); + + template<typename T> + void critical(const T &msg); + + bool should_log(level::level_enum msg_level) const; + void set_level(level::level_enum log_level); + level::level_enum level() const; + const std::string &name() const; + + // set formatting for the sinks in this logger. + // each sink will get a seperate instance of the formatter object. + void set_formatter(std::unique_ptr<formatter> formatter); + void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); + + // flush functions + void flush(); + void flush_on(level::level_enum log_level); + level::level_enum flush_level() const; + + // sinks + const std::vector<sink_ptr> &sinks() const; + std::vector<sink_ptr> &sinks(); + + // error handler + void set_error_handler(log_err_handler err_handler); + log_err_handler error_handler(); + + // create new logger with same sinks and configuration. + virtual std::shared_ptr<logger> clone(std::string logger_name); + +protected: + virtual void sink_it_(details::log_msg &msg); + virtual void flush_(); + + bool should_flush_(const details::log_msg &msg); + + // default error handler: print the error to stderr with the max rate of 1 + // message/minute + void default_err_handler_(const std::string &msg); + + // increment the message count (only if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) + void incr_msg_counter_(details::log_msg &msg); + + const std::string name_; + std::vector<sink_ptr> sinks_; + spdlog::level_t level_; + spdlog::level_t flush_level_; + log_err_handler err_handler_; + std::atomic<time_t> last_err_time_; + std::atomic<size_t> msg_counter_; +}; +} // namespace spdlog + +#include "details/logger_impl.h" diff --git a/src/nmodl/ext/spdlog/sinks/android_sink.h b/src/nmodl/ext/spdlog/sinks/android_sink.h new file mode 100644 index 0000000000..8c3383c173 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/android_sink.h @@ -0,0 +1,121 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/fmt_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/os.h" +#include "spdlog/sinks/base_sink.h" + +#include <android/log.h> +#include <chrono> +#include <mutex> +#include <string> +#include <thread> + +#if !defined(SPDLOG_ANDROID_RETRIES) +#define SPDLOG_ANDROID_RETRIES 2 +#endif + +namespace spdlog { +namespace sinks { + +/* + * Android sink (logging using __android_log_write) + */ +template<typename Mutex> +class android_sink final : public base_sink<Mutex> +{ +public: + explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) + : tag_(std::move(tag)) + , use_raw_msg_(use_raw_msg) + { + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + const android_LogPriority priority = convert_to_android_(msg.level); + fmt::memory_buffer formatted; + if (use_raw_msg_) + { + details::fmt_helper::append_buf(msg.raw, formatted); + } + else + { + sink::formatter_->format(msg, formatted); + } + formatted.push_back('\0'); + const char *msg_output = formatted.data(); + + // See system/core/liblog/logger_write.c for explanation of return value + int ret = __android_log_write(priority, tag_.c_str(), msg_output); + int retry_count = 0; + while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) + { + details::os::sleep_for_millis(5); + ret = __android_log_write(priority, tag_.c_str(), msg_output); + retry_count++; + } + + if (ret < 0) + { + throw spdlog_ex("__android_log_write() failed", ret); + } + } + + void flush_() override {} + +private: + static android_LogPriority convert_to_android_(spdlog::level::level_enum level) + { + switch (level) + { + case spdlog::level::trace: + return ANDROID_LOG_VERBOSE; + case spdlog::level::debug: + return ANDROID_LOG_DEBUG; + case spdlog::level::info: + return ANDROID_LOG_INFO; + case spdlog::level::warn: + return ANDROID_LOG_WARN; + case spdlog::level::err: + return ANDROID_LOG_ERROR; + case spdlog::level::critical: + return ANDROID_LOG_FATAL; + default: + return ANDROID_LOG_DEFAULT; + } + } + + std::string tag_; + bool use_raw_msg_; +}; + +using android_sink_mt = android_sink<std::mutex>; +using android_sink_st = android_sink<details::null_mutex>; +} // namespace sinks + +// Create and register android syslog logger + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") +{ + return Factory::template create<sinks::android_sink_mt>(logger_name, tag); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") +{ + return Factory::template create<sinks::android_sink_st>(logger_name, tag); +} + +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/ansicolor_sink.h b/src/nmodl/ext/spdlog/sinks/ansicolor_sink.h new file mode 100644 index 0000000000..bb2a2a12e6 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/ansicolor_sink.h @@ -0,0 +1,161 @@ +// +// Copyright(c) 2017 spdlog authors. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/details/os.h" +#include "spdlog/sinks/sink.h" + +#include <memory> +#include <mutex> +#include <string> +#include <unordered_map> + +namespace spdlog { +namespace sinks { + +/** + * This sink prefixes the output with an ANSI escape sequence color code + * depending on the severity + * of the message. + * If no color terminal detected, omit the escape codes. + */ +template<typename TargetStream, class ConsoleMutex> +class ansicolor_sink final : public sink +{ +public: + using mutex_t = typename ConsoleMutex::mutex_t; + ansicolor_sink() + : target_file_(TargetStream::stream()) + , mutex_(ConsoleMutex::mutex()) + + { + should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); + colors_[level::trace] = white; + colors_[level::debug] = cyan; + colors_[level::info] = green; + colors_[level::warn] = yellow + bold; + colors_[level::err] = red + bold; + colors_[level::critical] = bold + on_red; + colors_[level::off] = reset; + } + + ~ansicolor_sink() override = default; + + ansicolor_sink(const ansicolor_sink &other) = delete; + ansicolor_sink &operator=(const ansicolor_sink &other) = delete; + + void set_color(level::level_enum color_level, const std::string &color) + { + std::lock_guard<mutex_t> lock(mutex_); + colors_[color_level] = color; + } + + /// Formatting codes + const std::string reset = "\033[m"; + const std::string bold = "\033[1m"; + const std::string dark = "\033[2m"; + const std::string underline = "\033[4m"; + const std::string blink = "\033[5m"; + const std::string reverse = "\033[7m"; + const std::string concealed = "\033[8m"; + const std::string clear_line = "\033[K"; + + // Foreground colors + const std::string black = "\033[30m"; + const std::string red = "\033[31m"; + const std::string green = "\033[32m"; + const std::string yellow = "\033[33m"; + const std::string blue = "\033[34m"; + const std::string magenta = "\033[35m"; + const std::string cyan = "\033[36m"; + const std::string white = "\033[37m"; + + /// Background colors + const std::string on_black = "\033[40m"; + const std::string on_red = "\033[41m"; + const std::string on_green = "\033[42m"; + const std::string on_yellow = "\033[43m"; + const std::string on_blue = "\033[44m"; + const std::string on_magenta = "\033[45m"; + const std::string on_cyan = "\033[46m"; + const std::string on_white = "\033[47m"; + + void log(const details::log_msg &msg) override + { + // Wrap the originally formatted message in color codes. + // If color is not supported in the terminal, log as is instead. + std::lock_guard<mutex_t> lock(mutex_); + + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + if (should_do_colors_ && msg.color_range_end > msg.color_range_start) + { + // before color range + print_range_(formatted, 0, msg.color_range_start); + // in color range + print_ccode_(colors_[msg.level]); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + print_ccode_(reset); + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } + else // no color + { + print_range_(formatted, 0, formatted.size()); + } + fflush(target_file_); + } + + void flush() override + { + std::lock_guard<mutex_t> lock(mutex_); + fflush(target_file_); + } + + void set_pattern(const std::string &pattern) final + { + std::lock_guard<mutex_t> lock(mutex_); + formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override + { + std::lock_guard<mutex_t> lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + void print_ccode_(const std::string &color_code) + { + fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_); + } + void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) + { + fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); + } + + FILE *target_file_; + mutex_t &mutex_; + + bool should_do_colors_; + std::unordered_map<level::level_enum, std::string, level::level_hasher> colors_; +}; + +using ansicolor_stdout_sink_mt = ansicolor_sink<details::console_stdout, details::console_mutex>; +using ansicolor_stdout_sink_st = ansicolor_sink<details::console_stdout, details::console_nullmutex>; + +using ansicolor_stderr_sink_mt = ansicolor_sink<details::console_stderr, details::console_mutex>; +using ansicolor_stderr_sink_st = ansicolor_sink<details::console_stderr, details::console_nullmutex>; + +} // namespace sinks + +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/base_sink.h b/src/nmodl/ext/spdlog/sinks/base_sink.h new file mode 100644 index 0000000000..22595182b3 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/base_sink.h @@ -0,0 +1,69 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once +// +// base sink templated over a mutex (either dummy or real) +// concrete implementation should override the sink_it_() and flush_() methods. +// locking is taken care of in this class - no locking needed by the +// implementers.. +// + +#include "spdlog/common.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/formatter.h" +#include "spdlog/sinks/sink.h" + +namespace spdlog { +namespace sinks { +template<typename Mutex> +class base_sink : public sink +{ +public: + base_sink() = default; + base_sink(const base_sink &) = delete; + base_sink &operator=(const base_sink &) = delete; + + void log(const details::log_msg &msg) final + { + std::lock_guard<Mutex> lock(mutex_); + sink_it_(msg); + } + + void flush() final + { + std::lock_guard<Mutex> lock(mutex_); + flush_(); + } + + void set_pattern(const std::string &pattern) final + { + std::lock_guard<Mutex> lock(mutex_); + set_pattern_(pattern); + } + + void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final + { + std::lock_guard<Mutex> lock(mutex_); + set_formatter_(std::move(sink_formatter)); + } + +protected: + virtual void sink_it_(const details::log_msg &msg) = 0; + virtual void flush_() = 0; + + virtual void set_pattern_(const std::string &pattern) + { + set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); + } + + virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) + { + formatter_ = std::move(sink_formatter); + } + Mutex mutex_; +}; +} // namespace sinks +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/basic_file_sink.h b/src/nmodl/ext/spdlog/sinks/basic_file_sink.h new file mode 100644 index 0000000000..0ab884aa97 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/basic_file_sink.h @@ -0,0 +1,70 @@ +// +// Copyright(c) 2015-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include <mutex> +#include <string> + +namespace spdlog { +namespace sinks { +/* + * Trivial file sink with single file as target + */ +template<typename Mutex> +class basic_file_sink final : public base_sink<Mutex> +{ +public: + explicit basic_file_sink(const filename_t &filename, bool truncate = false) + { + file_helper_.open(filename, truncate); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + details::file_helper file_helper_; +}; + +using basic_file_sink_mt = basic_file_sink<std::mutex>; +using basic_file_sink_st = basic_file_sink<details::null_mutex>; + +} // namespace sinks + +// +// factory functions +// +template<typename Factory = default_factory> +inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) +{ + return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) +{ + return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate); +} + +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/daily_file_sink.h b/src/nmodl/ext/spdlog/sinks/daily_file_sink.h new file mode 100644 index 0000000000..376a1ab03a --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/daily_file_sink.h @@ -0,0 +1,136 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" + +#include <chrono> +#include <cstdio> +#include <ctime> +#include <mutex> +#include <string> + +namespace spdlog { +namespace sinks { + +/* + * Generator of daily log file names in format basename.YYYY-MM-DD.ext + */ +struct daily_filename_calculator +{ + // Create filename for the form basename.YYYY-MM-DD + static filename_t calc_filename(const filename_t &filename, const tm &now_tm) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + fmt::format_to( + w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); + return fmt::to_string(w); + } +}; + +/* + * Rotating file sink based on date. rotates at midnight + */ +template<typename Mutex, typename FileNameCalc = daily_filename_calculator> +class daily_file_sink final : public base_sink<Mutex> +{ +public: + // create daily file sink which rotates on given time + daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false) + : base_filename_(std::move(base_filename)) + , rotation_h_(rotation_hour) + , rotation_m_(rotation_minute) + , truncate_(truncate) + { + if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) + { + throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); + } + auto now = log_clock::now(); + file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(now)), truncate_); + rotation_tp_ = next_rotation_tp_(); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + + if (msg.time >= rotation_tp_) + { + file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(msg.time)), truncate_); + rotation_tp_ = next_rotation_tp_(); + } + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + tm now_tm(log_clock::time_point tp) + { + time_t tnow = log_clock::to_time_t(tp); + return spdlog::details::os::localtime(tnow); + } + + log_clock::time_point next_rotation_tp_() + { + auto now = log_clock::now(); + tm date = now_tm(now); + date.tm_hour = rotation_h_; + date.tm_min = rotation_m_; + date.tm_sec = 0; + auto rotation_time = log_clock::from_time_t(std::mktime(&date)); + if (rotation_time > now) + { + return rotation_time; + } + return {rotation_time + std::chrono::hours(24)}; + } + + filename_t base_filename_; + int rotation_h_; + int rotation_m_; + log_clock::time_point rotation_tp_; + details::file_helper file_helper_; + bool truncate_; +}; + +using daily_file_sink_mt = daily_file_sink<std::mutex>; +using daily_file_sink_st = daily_file_sink<details::null_mutex>; + +} // namespace sinks + +// +// factory functions +// +template<typename Factory = default_factory> +inline std::shared_ptr<logger> daily_logger_mt( + const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) +{ + return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> daily_logger_st( + const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) +{ + return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate); +} +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/dist_sink.h b/src/nmodl/ext/spdlog/sinks/dist_sink.h new file mode 100644 index 0000000000..8ed41f1a95 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/dist_sink.h @@ -0,0 +1,94 @@ +// +// Copyright (c) 2015 David Schury, Gabi Melman +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "base_sink.h" +#include "spdlog/details/log_msg.h" +#include "spdlog/details/null_mutex.h" + +#include <algorithm> +#include <memory> +#include <mutex> +#include <vector> + +// Distribution sink (mux). Stores a vector of sinks which get called when log +// is called + +namespace spdlog { +namespace sinks { + +template<typename Mutex> +class dist_sink : public base_sink<Mutex> +{ +public: + dist_sink() = default; + dist_sink(const dist_sink &) = delete; + dist_sink &operator=(const dist_sink &) = delete; + + void add_sink(std::shared_ptr<sink> sink) + { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + sinks_.push_back(sink); + } + + void remove_sink(std::shared_ptr<sink> sink) + { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); + } + + void set_sinks(std::vector<std::shared_ptr<sink>> sinks) + { + std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); + sinks_ = std::move(sinks); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + + for (auto &sink : sinks_) + { + if (sink->should_log(msg.level)) + { + sink->log(msg); + } + } + } + + void flush_() override + { + for (auto &sink : sinks_) + { + sink->flush(); + } + } + + void set_pattern_(const std::string &pattern) override + { + set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); + } + + void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override + { + base_sink<Mutex>::formatter_ = std::move(sink_formatter); + for (auto &sink : sinks_) + { + sink->set_formatter(base_sink<Mutex>::formatter_->clone()); + } + } + std::vector<std::shared_ptr<sink>> sinks_; +}; + +using dist_sink_mt = dist_sink<std::mutex>; +using dist_sink_st = dist_sink<details::null_mutex>; + +} // namespace sinks +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/msvc_sink.h b/src/nmodl/ext/spdlog/sinks/msvc_sink.h new file mode 100644 index 0000000000..c8e60be779 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/msvc_sink.h @@ -0,0 +1,54 @@ +// +// Copyright(c) 2016 Alexander Dalshov. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#if defined(_WIN32) + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include <winbase.h> + +#include <mutex> +#include <string> + +namespace spdlog { +namespace sinks { +/* + * MSVC sink (logging using OutputDebugStringA) + */ +template<typename Mutex> +class msvc_sink : public base_sink<Mutex> +{ +public: + explicit msvc_sink() {} + +protected: + void sink_it_(const details::log_msg &msg) override + { + + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + OutputDebugStringA(fmt::to_string(formatted).c_str()); + } + + void flush_() override {} +}; + +using msvc_sink_mt = msvc_sink<std::mutex>; +using msvc_sink_st = msvc_sink<details::null_mutex>; + +using windebug_sink_mt = msvc_sink_mt; +using windebug_sink_st = msvc_sink_st; + +} // namespace sinks +} // namespace spdlog + +#endif diff --git a/src/nmodl/ext/spdlog/sinks/null_sink.h b/src/nmodl/ext/spdlog/sinks/null_sink.h new file mode 100644 index 0000000000..ffab7a5299 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/null_sink.h @@ -0,0 +1,49 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include <mutex> + +namespace spdlog { +namespace sinks { + +template<typename Mutex> +class null_sink : public base_sink<Mutex> +{ +protected: + void sink_it_(const details::log_msg &) override {} + void flush_() override {} +}; + +using null_sink_mt = null_sink<std::mutex>; +using null_sink_st = null_sink<details::null_mutex>; + +} // namespace sinks + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name) +{ + auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name); + null_logger->set_level(level::off); + return null_logger; +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name) +{ + auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name); + null_logger->set_level(level::off); + return null_logger; +} + +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/ostream_sink.h b/src/nmodl/ext/spdlog/sinks/ostream_sink.h new file mode 100644 index 0000000000..b28802a050 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/ostream_sink.h @@ -0,0 +1,57 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/base_sink.h" + +#include <mutex> +#include <ostream> + +namespace spdlog { +namespace sinks { +template<typename Mutex> +class ostream_sink final : public base_sink<Mutex> +{ +public: + explicit ostream_sink(std::ostream &os, bool force_flush = false) + : ostream_(os) + , force_flush_(force_flush) + { + } + ostream_sink(const ostream_sink &) = delete; + ostream_sink &operator=(const ostream_sink &) = delete; + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); + if (force_flush_) + { + ostream_.flush(); + } + } + + void flush_() override + { + ostream_.flush(); + } + + std::ostream &ostream_; + bool force_flush_; +}; + +using ostream_sink_mt = ostream_sink<std::mutex>; +using ostream_sink_st = ostream_sink<details::null_mutex>; + +} // namespace sinks +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/rotating_file_sink.h b/src/nmodl/ext/spdlog/sinks/rotating_file_sink.h new file mode 100644 index 0000000000..adf12b6f67 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/rotating_file_sink.h @@ -0,0 +1,154 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/file_helper.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/fmt/fmt.h" +#include "spdlog/sinks/base_sink.h" + +#include <cerrno> +#include <chrono> +#include <ctime> +#include <mutex> +#include <string> +#include <tuple> + +namespace spdlog { +namespace sinks { + +// +// Rotating file sink based on size +// +template<typename Mutex> +class rotating_file_sink final : public base_sink<Mutex> +{ +public: + rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) + : base_filename_(std::move(base_filename)) + , max_size_(max_size) + , max_files_(max_files) + { + file_helper_.open(calc_filename(base_filename_, 0)); + current_size_ = file_helper_.size(); // expensive. called only once + } + + // calc filename according to index and file extension if exists. + // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". + static filename_t calc_filename(const filename_t &filename, std::size_t index) + { + typename std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; + if (index != 0u) + { + filename_t basename, ext; + std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); + fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); + } + else + { + fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); + } + return fmt::to_string(w); + } + +protected: + void sink_it_(const details::log_msg &msg) override + { + fmt::memory_buffer formatted; + sink::formatter_->format(msg, formatted); + current_size_ += formatted.size(); + if (current_size_ > max_size_) + { + rotate_(); + current_size_ = formatted.size(); + } + file_helper_.write(formatted); + } + + void flush_() override + { + file_helper_.flush(); + } + +private: + // Rotate files: + // log.txt -> log.1.txt + // log.1.txt -> log.2.txt + // log.2.txt -> log.3.txt + // log.3.txt -> delete + void rotate_() + { + using details::os::filename_to_str; + file_helper_.close(); + for (auto i = max_files_; i > 0; --i) + { + filename_t src = calc_filename(base_filename_, i - 1); + if (!details::file_helper::file_exists(src)) + { + continue; + } + filename_t target = calc_filename(base_filename_, i); + + if (!rename_file(src, target)) + { + // if failed try again after a small delay. + // this is a workaround to a windows issue, where very high rotation + // rates can cause the rename to fail with permission denied (because of antivirus?). + details::os::sleep_for_millis(100); + if (!rename_file(src, target)) + { + file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! + throw spdlog_ex( + "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); + } + } + } + file_helper_.reopen(true); + } + + // delete the target if exists, and rename the src file to target + // return true on success, false otherwise. + bool rename_file(const filename_t &src_filename, const filename_t &target_filename) + { + // try to delete the target file in case it already exists. + (void)details::os::remove(target_filename); + return details::os::rename(src_filename, target_filename) == 0; + } + + filename_t base_filename_; + std::size_t max_size_; + std::size_t max_files_; + std::size_t current_size_; + details::file_helper file_helper_; +}; + +using rotating_file_sink_mt = rotating_file_sink<std::mutex>; +using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; + +} // namespace sinks + +// +// factory functions +// + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> rotating_logger_mt( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> rotating_logger_st( + const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) +{ + return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files); +} +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/sink.h b/src/nmodl/ext/spdlog/sinks/sink.h new file mode 100644 index 0000000000..9f84c3787a --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/sink.h @@ -0,0 +1,59 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#include "spdlog/details/log_msg.h" +#include "spdlog/details/pattern_formatter.h" +#include "spdlog/formatter.h" + +namespace spdlog { +namespace sinks { +class sink +{ +public: + sink() + : level_(level::trace) + , formatter_(new pattern_formatter("%+")) + { + } + + explicit sink(std::unique_ptr<spdlog::pattern_formatter> formatter) + : level_(level::trace) + , formatter_(std::move(formatter)) + { + } + + virtual ~sink() = default; + virtual void log(const details::log_msg &msg) = 0; + virtual void flush() = 0; + virtual void set_pattern(const std::string &pattern) = 0; + virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0; + + bool should_log(level::level_enum msg_level) const + { + return msg_level >= level_.load(std::memory_order_relaxed); + } + + void set_level(level::level_enum log_level) + { + level_.store(log_level); + } + + level::level_enum level() const + { + return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); + } + +protected: + // sink log level - default is all + level_t level_; + + // sink formatter - default is full format + std::unique_ptr<spdlog::formatter> formatter_; +}; + +} // namespace sinks +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h b/src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h new file mode 100644 index 0000000000..f0475692a8 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h @@ -0,0 +1,56 @@ +// +// Copyright(c) 2018 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#ifdef _WIN32 +#include "spdlog/sinks/wincolor_sink.h" +#else +#include "spdlog/sinks/ansicolor_sink.h" +#endif + +namespace spdlog { +namespace sinks { +#ifdef _WIN32 +using stdout_color_sink_mt = wincolor_stdout_sink_mt; +using stdout_color_sink_st = wincolor_stdout_sink_st; +using stderr_color_sink_mt = wincolor_stderr_sink_mt; +using stderr_color_sink_st = wincolor_stderr_sink_st; +#else +using stdout_color_sink_mt = ansicolor_stdout_sink_mt; +using stdout_color_sink_st = ansicolor_stdout_sink_st; +using stderr_color_sink_mt = ansicolor_stderr_sink_mt; +using stderr_color_sink_st = ansicolor_stderr_sink_st; +#endif +} // namespace sinks + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name) +{ + return Factory::template create<sinks::stdout_color_sink_mt>(logger_name); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stdout_color_st(const std::string &logger_name) +{ + return Factory::template create<sinks::stdout_color_sink_st>(logger_name); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name) +{ + return Factory::template create<sinks::stderr_color_sink_mt>(logger_name); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stderr_color_st(const std::string &logger_name) +{ + return Factory::template create<sinks::stderr_color_sink_mt>(logger_name); +} +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/stdout_sinks.h b/src/nmodl/ext/spdlog/sinks/stdout_sinks.h new file mode 100644 index 0000000000..efbbaaacea --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/stdout_sinks.h @@ -0,0 +1,102 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" + +#include <cstdio> +#include <memory> +#include <mutex> + +namespace spdlog { + +namespace sinks { + +template<typename TargetStream, typename ConsoleMutex> +class stdout_sink final : public sink +{ +public: + using mutex_t = typename ConsoleMutex::mutex_t; + stdout_sink() + : mutex_(ConsoleMutex::mutex()) + , file_(TargetStream::stream()) + { + } + ~stdout_sink() override = default; + + stdout_sink(const stdout_sink &other) = delete; + stdout_sink &operator=(const stdout_sink &other) = delete; + + void log(const details::log_msg &msg) override + { + std::lock_guard<mutex_t> lock(mutex_); + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + fwrite(formatted.data(), sizeof(char), formatted.size(), file_); + fflush(TargetStream::stream()); + } + + void flush() override + { + std::lock_guard<mutex_t> lock(mutex_); + fflush(file_); + } + + void set_pattern(const std::string &pattern) override + { + std::lock_guard<mutex_t> lock(mutex_); + formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override + { + std::lock_guard<mutex_t> lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + mutex_t &mutex_; + FILE *file_; +}; + +using stdout_sink_mt = stdout_sink<details::console_stdout, details::console_mutex>; +using stdout_sink_st = stdout_sink<details::console_stdout, details::console_nullmutex>; + +using stderr_sink_mt = stdout_sink<details::console_stderr, details::console_mutex>; +using stderr_sink_st = stdout_sink<details::console_stderr, details::console_nullmutex>; + +} // namespace sinks + +// factory methods +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name) +{ + return Factory::template create<sinks::stdout_sink_mt>(logger_name); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name) +{ + return Factory::template create<sinks::stdout_sink_st>(logger_name); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name) +{ + return Factory::template create<sinks::stderr_sink_mt>(logger_name); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name) +{ + return Factory::template create<sinks::stderr_sink_st>(logger_name); +} +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/syslog_sink.h b/src/nmodl/ext/spdlog/sinks/syslog_sink.h new file mode 100644 index 0000000000..cb76613d0b --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/syslog_sink.h @@ -0,0 +1,94 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/sinks/base_sink.h" + +#include <array> +#include <string> +#include <syslog.h> + +namespace spdlog { +namespace sinks { +/** + * Sink that write to syslog using the `syscall()` library call. + * + * Locking is not needed, as `syslog()` itself is thread-safe. + */ +template<typename Mutex> +class syslog_sink : public base_sink<Mutex> +{ +public: + // + explicit syslog_sink(std::string ident = "", int syslog_option = 0, int syslog_facility = LOG_USER) + : ident_(std::move(ident)) + { + priorities_[static_cast<size_t>(level::trace)] = LOG_DEBUG; + priorities_[static_cast<size_t>(level::debug)] = LOG_DEBUG; + priorities_[static_cast<size_t>(level::info)] = LOG_INFO; + priorities_[static_cast<size_t>(level::warn)] = LOG_WARNING; + priorities_[static_cast<size_t>(level::err)] = LOG_ERR; + priorities_[static_cast<size_t>(level::critical)] = LOG_CRIT; + priorities_[static_cast<size_t>(level::off)] = LOG_INFO; + + // set ident to be program name if empty + ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); + } + + ~syslog_sink() override + { + ::closelog(); + } + + syslog_sink(const syslog_sink &) = delete; + syslog_sink &operator=(const syslog_sink &) = delete; + +protected: + void sink_it_(const details::log_msg &msg) override + { + ::syslog(syslog_prio_from_level(msg), "%s", fmt::to_string(msg.raw).c_str()); + } + + void flush_() override {} + +private: + std::array<int, 7> priorities_; + // must store the ident because the man says openlog might use the pointer as + // is and not a string copy + const std::string ident_; + + // + // Simply maps spdlog's log level to syslog priority level. + // + int syslog_prio_from_level(const details::log_msg &msg) const + { + return priorities_[static_cast<size_t>(msg.level)]; + } +}; + +using syslog_sink_mt = syslog_sink<std::mutex>; +using syslog_sink_st = syslog_sink<details::null_mutex>; +} // namespace sinks + +// Create and register a syslog logger +template<typename Factory = default_factory> +inline std::shared_ptr<logger> syslog_logger_mt( + const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) +{ + return Factory::template create<sinks::syslog_sink_mt>(logger_name, syslog_ident, syslog_option, syslog_facility); +} + +template<typename Factory = default_factory> +inline std::shared_ptr<logger> syslog_logger_st( + const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) +{ + return Factory::template create<sinks::syslog_sink_st>(logger_name, syslog_ident, syslog_option, syslog_facility); +} +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/wincolor_sink.h b/src/nmodl/ext/spdlog/sinks/wincolor_sink.h new file mode 100644 index 0000000000..6e2b012208 --- /dev/null +++ b/src/nmodl/ext/spdlog/sinks/wincolor_sink.h @@ -0,0 +1,143 @@ +// +// Copyright(c) 2016 spdlog +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#ifndef SPDLOG_H +#error "spdlog.h must be included before this file." +#endif + +#include "spdlog/common.h" +#include "spdlog/details/console_globals.h" +#include "spdlog/details/null_mutex.h" +#include "spdlog/sinks/sink.h" + +#include <memory> +#include <mutex> +#include <string> +#include <unordered_map> +#include <wincon.h> + +namespace spdlog { +namespace sinks { +/* + * Windows color console sink. Uses WriteConsoleA to write to the console with + * colors + */ +template<typename OutHandle, typename ConsoleMutex> +class wincolor_sink : public sink +{ +public: + const WORD BOLD = FOREGROUND_INTENSITY; + const WORD RED = FOREGROUND_RED; + const WORD GREEN = FOREGROUND_GREEN; + const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; + + wincolor_sink() + : out_handle_(OutHandle::handle()) + , mutex_(ConsoleMutex::mutex()) + { + colors_[level::trace] = WHITE; + colors_[level::debug] = CYAN; + colors_[level::info] = GREEN; + colors_[level::warn] = YELLOW | BOLD; + colors_[level::err] = RED | BOLD; // red bold + colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background + colors_[level::off] = 0; + } + + ~wincolor_sink() override + { + this->flush(); + } + + wincolor_sink(const wincolor_sink &other) = delete; + wincolor_sink &operator=(const wincolor_sink &other) = delete; + + // change the color for the given level + void set_color(level::level_enum level, WORD color) + { + std::lock_guard<mutex_t> lock(mutex_); + colors_[level] = color; + } + + void log(const details::log_msg &msg) final override + { + std::lock_guard<mutex_t> lock(mutex_); + fmt::memory_buffer formatted; + formatter_->format(msg, formatted); + if (msg.color_range_end > msg.color_range_start) + { + // before color range + print_range_(formatted, 0, msg.color_range_start); + + // in color range + auto orig_attribs = set_console_attribs(colors_[msg.level]); + print_range_(formatted, msg.color_range_start, msg.color_range_end); + ::SetConsoleTextAttribute(out_handle_, + orig_attribs); // reset to orig colors + // after color range + print_range_(formatted, msg.color_range_end, formatted.size()); + } + else // print without colors if color range is invalid + { + print_range_(formatted, 0, formatted.size()); + } + } + + void flush() final override + { + // windows console always flushed? + } + + void set_pattern(const std::string &pattern) override final + { + std::lock_guard<mutex_t> lock(mutex_); + formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); + } + + void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final + { + std::lock_guard<mutex_t> lock(mutex_); + formatter_ = std::move(sink_formatter); + } + +private: + using mutex_t = typename ConsoleMutex::mutex_t; + // set color and return the orig console attributes (for resetting later) + WORD set_console_attribs(WORD attribs) + { + CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; + ::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); + WORD back_color = orig_buffer_info.wAttributes; + // retrieve the current background color + back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); + // keep the background color unchanged + ::SetConsoleTextAttribute(out_handle_, attribs | back_color); + return orig_buffer_info.wAttributes; // return orig attribs + } + + // print a range of formatted message to console + void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) + { + auto size = static_cast<DWORD>(end - start); + ::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); + } + + HANDLE out_handle_; + mutex_t &mutex_; + std::unordered_map<level::level_enum, WORD, level::level_hasher> colors_; +}; + +using wincolor_stdout_sink_mt = wincolor_sink<details::console_stdout, details::console_mutex>; +using wincolor_stdout_sink_st = wincolor_sink<details::console_stdout, details::console_nullmutex>; + +using wincolor_stderr_sink_mt = wincolor_sink<details::console_stderr, details::console_mutex>; +using wincolor_stderr_sink_st = wincolor_sink<details::console_stderr, details::console_nullmutex>; + +} // namespace sinks +} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/spdlog.h b/src/nmodl/ext/spdlog/spdlog.h new file mode 100644 index 0000000000..bcca2e84f9 --- /dev/null +++ b/src/nmodl/ext/spdlog/spdlog.h @@ -0,0 +1,320 @@ +// +// Copyright(c) 2015-2018 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// +// spdlog main header file. +// see example.cpp for usage example + +#ifndef SPDLOG_H +#define SPDLOG_H +#pragma once + +#include "spdlog/common.h" +#include "spdlog/details/registry.h" +#include "spdlog/logger.h" +#include "spdlog/version.h" + +#include <chrono> +#include <functional> +#include <memory> +#include <string> + +namespace spdlog { + +// Default logger factory- creates synchronous loggers +struct synchronous_factory +{ + template<typename Sink, typename... SinkArgs> + static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args) + { + auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); + auto new_logger = std::make_shared<logger>(std::move(logger_name), std::move(sink)); + details::registry::instance().register_and_init(new_logger); + return new_logger; + } +}; + +using default_factory = synchronous_factory; + +// Create and register a logger with a templated sink type +// The logger's level, formatter and flush level will be set according the +// global settings. +// Example: +// spdlog::create<daily_file_sink_st>("logger_name", "dailylog_filename", 11, 59); +template<typename Sink, typename... SinkArgs> +inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... sink_args) +{ + return default_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); +} + +// Return an existing logger or nullptr if a logger with such name doesn't +// exist. +// example: spdlog::get("my_logger")->info("hello {}", "world"); +inline std::shared_ptr<logger> get(const std::string &name) +{ + return details::registry::instance().get(name); +} + +// Set global formatter. Each sink in each logger will get a clone of this object +inline void set_formatter(std::unique_ptr<spdlog::formatter> formatter) +{ + details::registry::instance().set_formatter(std::move(formatter)); +} + +// Set global format string. +// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); +inline void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local) +{ + set_formatter(std::unique_ptr<spdlog::formatter>(new pattern_formatter(std::move(pattern), time_type))); +} + +// Set global logging level +inline void set_level(level::level_enum log_level) +{ + details::registry::instance().set_level(log_level); +} + +// Set global flush level +inline void flush_on(level::level_enum log_level) +{ + details::registry::instance().flush_on(log_level); +} + +// Start/Restart a periodic flusher thread +// Warning: Use only if all your loggers are thread safe! +inline void flush_every(std::chrono::seconds interval) +{ + details::registry::instance().flush_every(interval); +} + +// Set global error handler +inline void set_error_handler(log_err_handler handler) +{ + details::registry::instance().set_error_handler(std::move(handler)); +} + +// Register the given logger with the given name +inline void register_logger(std::shared_ptr<logger> logger) +{ + details::registry::instance().register_logger(std::move(logger)); +} + +// Apply a user defined function on all registered loggers +// Example: +// spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); +inline void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) +{ + details::registry::instance().apply_all(fun); +} + +// Drop the reference to the given logger +inline void drop(const std::string &name) +{ + details::registry::instance().drop(name); +} + +// Drop all references from the registry +inline void drop_all() +{ + details::registry::instance().drop_all(); +} + +// stop any running threads started by spdlog and clean registry loggers +inline void shutdown() +{ + details::registry::instance().shutdown(); +} + +// API for using default logger (stdout_color_mt), +// e.g: spdlog::info("Message {}", 1); +// +// The default logger object can be accessed using the spdlog::default_logger(): +// For example, to add another sink to it: +// spdlog::default_logger()->sinks()->push_back(some_sink); +// +// The default logger can replaced using spdlog::set_default_logger(new_logger). +// For example, to replace it with a file logger. +// +// IMPORTANT: +// The default API is thread safe (for _mt loggers), but: +// set_default_logger() *should not* be used concurrently with the default API. +// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. + +inline std::shared_ptr<spdlog::logger> default_logger() +{ + return details::registry::instance().default_logger(); +} + +inline spdlog::logger *default_logger_raw() +{ + return details::registry::instance().get_default_raw(); +} + +inline void set_default_logger(std::shared_ptr<spdlog::logger> default_logger) +{ + details::registry::instance().set_default_logger(std::move(default_logger)); +} + +template<typename... Args> +inline void log(level::level_enum lvl, const char *fmt, const Args &... args) +{ + default_logger_raw()->log(lvl, fmt, args...); +} + +template<typename... Args> +inline void trace(const char *fmt, const Args &... args) +{ + default_logger_raw()->trace(fmt, args...); +} + +template<typename... Args> +inline void debug(const char *fmt, const Args &... args) +{ + default_logger_raw()->debug(fmt, args...); +} + +template<typename... Args> +inline void info(const char *fmt, const Args &... args) +{ + default_logger_raw()->info(fmt, args...); +} + +template<typename... Args> +inline void warn(const char *fmt, const Args &... args) +{ + default_logger_raw()->warn(fmt, args...); +} + +template<typename... Args> +inline void error(const char *fmt, const Args &... args) +{ + default_logger_raw()->error(fmt, args...); +} + +template<typename... Args> +inline void critical(const char *fmt, const Args &... args) +{ + default_logger_raw()->critical(fmt, args...); +} + +template<typename T> +inline void log(level::level_enum lvl, const T &msg) +{ + default_logger_raw()->log(lvl, msg); +} + +template<typename T> +inline void trace(const T &msg) +{ + default_logger_raw()->trace(msg); +} + +template<typename T> +inline void debug(const T &msg) +{ + default_logger_raw()->debug(msg); +} + +template<typename T> +inline void info(const T &msg) +{ + default_logger_raw()->info(msg); +} + +template<typename T> +inline void warn(const T &msg) +{ + default_logger_raw()->warn(msg); +} + +template<typename T> +inline void error(const T &msg) +{ + default_logger_raw()->error(msg); +} + +template<typename T> +inline void critical(const T &msg) +{ + default_logger_raw()->critical(msg); +} + +#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT +template<typename... Args> +inline void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->log(lvl, fmt, args...); +} + +template<typename... Args> +inline void trace(const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->trace(fmt, args...); +} + +template<typename... Args> +inline void debug(const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->debug(fmt, args...); +} + +template<typename... Args> +inline void info(const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->info(fmt, args...); +} + +template<typename... Args> +inline void warn(const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->warn(fmt, args...); +} + +template<typename... Args> +inline void error(const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->error(fmt, args...); +} + +template<typename... Args> +inline void critical(const wchar_t *fmt, const Args &... args) +{ + default_logger_raw()->critical(fmt, args...); +} + +#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT + +// +// Trace & Debug can be switched on/off at compile time with zero cost. +// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. +// SPDLOG_TRACE(..) will also print current file and line. +// +// Example: +// spdlog::set_level(spdlog::level::trace); +// SPDLOG_TRACE(my_logger, "another trace message {} {}", 1, 2); +// + +#ifdef SPDLOG_TRACE_ON +#define SPDLOG_STR_H(x) #x +#define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) +#ifdef _MSC_VER +#define SPDLOG_TRACE(logger, ...) \ + logger->trace("[ "__FILE__ \ + "(" SPDLOG_STR_HELPER(__LINE__) ")] " __VA_ARGS__) +#else +#define SPDLOG_TRACE(logger, ...) \ + logger->trace("[" __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) "]" \ + " " __VA_ARGS__) +#endif +#else +#define SPDLOG_TRACE(logger, ...) (void)0 +#endif + +#ifdef SPDLOG_DEBUG_ON +#define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) +#else +#define SPDLOG_DEBUG(logger, ...) (void)0 +#endif + +} // namespace spdlog +#endif // SPDLOG_H diff --git a/src/nmodl/ext/spdlog/tweakme.h b/src/nmodl/ext/spdlog/tweakme.h new file mode 100644 index 0000000000..d91159f7e6 --- /dev/null +++ b/src/nmodl/ext/spdlog/tweakme.h @@ -0,0 +1,130 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +/////////////////////////////////////////////////////////////////////////////// +// +// Edit this file to squeeze more performance, and to customize supported +// features +// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. +// This clock is less accurate - can be off by dozens of millis - depending on +// the kernel HZ. +// Uncomment to use it instead of the regular clock. +// +// #define SPDLOG_CLOCK_COARSE +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if date/time logging is not needed and never appear in the log +// pattern. +// This will prevent spdlog from querying the clock on each log call. +// +// WARNING: If the log pattern contains any date/time while this flag is on, the +// result is undefined. +// You must set new pattern(spdlog::set_pattern(..") without any +// date/time in it +// +// #define SPDLOG_NO_DATETIME +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). +// This will prevent spdlog from querying the thread id on each log call. +// +// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is +// on, the result is undefined. +// +// #define SPDLOG_NO_THREAD_ID +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent spdlog from caching thread ids in thread local storage. +// By default spdlog saves thread ids in tls to gain a few micros for each call. +// +// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined +// thread ids in the children logs. +// +// #define SPDLOG_DISABLE_TID_CACHING +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment if logger name logging is not needed. +// This will prevent spdlog from copying the logger name on each log call. +// +// #define SPDLOG_NO_NAME +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros. +// +// #define SPDLOG_DEBUG_ON +// #define SPDLOG_TRACE_ON +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to avoid spdlog's usage of atomic log levels +// Use only if your code never modifies a logger's log levels concurrently by +// different threads. +// +// #define SPDLOG_NO_ATOMIC_LEVELS +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable usage of wchar_t for file names on Windows. +// +// #define SPDLOG_WCHAR_FILENAMES +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) +// +// #define SPDLOG_EOL ";-)\n" +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to use your own copy of the fmt library instead of spdlog's copy. +// In this case spdlog will try to include <fmt/format.h> so set your -I flag +// accordingly. +// +// #define SPDLOG_FMT_EXTERNAL +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable wchar_t support (convert to utf8) +// +// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to prevent child processes from inheriting log file descriptors +// +// #define SPDLOG_PREVENT_CHILD_FD +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to enable message counting feature. +// Use the %i in the logger pattern to display log message sequence id. +// +// #define SPDLOG_ENABLE_MESSAGE_COUNTER +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to customize level names (e.g. "MT TRACE") +// +// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", +// "MY ERROR", "MY CRITICAL", "OFF" } +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Uncomment to disable default logger creation. +// This might save some (very) small initialization time if no default logger is needed. +// +// #define SPDLOG_DISABLE_DEFAULT_LOGGER +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/nmodl/ext/spdlog/version.h b/src/nmodl/ext/spdlog/version.h new file mode 100644 index 0000000000..16b9194b55 --- /dev/null +++ b/src/nmodl/ext/spdlog/version.h @@ -0,0 +1,12 @@ +// +// Copyright(c) 2015 Gabi Melman. +// Distributed under the MIT License (http://opensource.org/licenses/MIT) +// + +#pragma once + +#define SPDLOG_VER_MAJOR 1 +#define SPDLOG_VER_MINOR 3 +#define SPDLOG_VER_PATCH 0 + +#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 093c6ed2df..a68d4e37d7 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -21,4 +21,4 @@ set(FILES_FOR_CLANG_FORMAT #============================================================================= add_executable(nmodl ${NMODL_SOURCE_FILES}) -target_link_libraries(nmodl printer codegen visitor symtab util lexer fmt) +target_link_libraries(nmodl printer codegen visitor symtab util lexer) diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index d8d3b96d31..65313f291a 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -7,7 +7,7 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { // version string - auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; + auto version = nmodl::version().to_string(); try { using string_vector_type = std::vector<std::string>; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index e5c677ebc4..1dfe3caaa1 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -4,7 +4,6 @@ #include "arg_handler.hpp" #include "parser/nmodl_driver.hpp" -#include "version/version.h" #include "visitors/ast_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" @@ -21,28 +20,34 @@ #include "codegen/c-openacc/codegen_c_acc_visitor.hpp" #include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" #include "utils/common_utils.hpp" +#include "utils/logger.hpp" void ast_to_nmodl(ast::Program* ast, std::string filename) { NmodlPrintVisitor v(filename); v.visit_program(ast); + logger->info("AST to NMODL transformation written to {}", filename); } int main(int argc, const char* argv[]) { - // version string - auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; ArgumentHandler arg(argc, argv); make_path(arg.output_dir); make_path(arg.scratch_dir); + int error_count = 0; + for (auto& nmodl_file : arg.nmodl_files) { std::ifstream file(nmodl_file); if (!file.good()) { - throw std::runtime_error("Could not open file " + nmodl_file); + logger->warn("Could not open file : {}", nmodl_file); + error_count++; + continue; } + logger->info("Processing {}", nmodl_file); + std::string mod_file = remove_extension(base_name(nmodl_file)); /// driver object creates lexer and parser, just call parser method @@ -127,12 +132,11 @@ int main(int argc, const char* argv[]) { } if (arg.show_symtab) { - std::cout << "----SYMBOL TABLE ----" << std::endl; + logger->info("Printing symbol table"); std::stringstream stream; auto symtab = ast->get_model_symbol_table(); symtab->print(stream); std::cout << stream.str(); - std::cout << "---------------------" << std::endl; } { @@ -143,6 +147,8 @@ int main(int argc, const char* argv[]) { bool aos_layout = arg.aos_memory_layout(); + logger->info("Generating host code with {} backend", arg.host_backend); + if (arg.host_c_backend()) { CodegenCVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); visitor.visit_program(ast.get()); @@ -155,11 +161,17 @@ int main(int argc, const char* argv[]) { } if (arg.device_cuda_backend()) { + logger->info("Generating device code with {} backend", arg.accel_backend); CodegenCCudaVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); visitor.visit_program(ast.get()); } } } - return 0; + if (error_count) { + logger->error("Code generation encountered {} errors", error_count); + } else { + logger->info("Code generation finished successfully"); + } + return error_count; } diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 4ed7fc6594..de6ba8e6ad 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -7,6 +7,8 @@ set(UTIL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.cpp ${CMAKE_CURRENT_SOURCE_DIR}/table_data.hpp ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/logger.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp ${CMAKE_BINARY_DIR}/version.cpp ) diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp new file mode 100644 index 0000000000..7761e82d5a --- /dev/null +++ b/src/nmodl/utils/logger.cpp @@ -0,0 +1,17 @@ +#include <memory> +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +using logger_type = std::shared_ptr<spdlog::logger>; + +struct Logger { + logger_type logger; + Logger(std::string name, std::string pattern) { + logger = spdlog::stdout_color_mt(name); + logger->set_pattern(pattern); + } +}; + +Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); +logger_type logger = nmodl_logger.logger; + diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp new file mode 100644 index 0000000000..ae43fa095f --- /dev/null +++ b/src/nmodl/utils/logger.hpp @@ -0,0 +1,10 @@ +#ifndef NMODL_LOGGER_HPP +#define NMODL_LOGGER_HPP + +#include "spdlog/spdlog.h" +#include "spdlog/sinks/stdout_color_sinks.h" + +using logger_type = std::shared_ptr<spdlog::logger>; +extern logger_type logger; + +#endif diff --git a/src/nmodl/version/version.h b/src/nmodl/version/version.h index 6831047050..3abf57871c 100644 --- a/src/nmodl/version/version.h +++ b/src/nmodl/version/version.h @@ -6,5 +6,8 @@ namespace nmodl { struct version { static const std::string GIT_REVISION; static const std::string NMODL_VERSION; + std::string to_string() { + return NMODL_VERSION + " [" + GIT_REVISION + "]"; + } }; } // namespace nmodl diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 70a52a24aa..41a01953ea 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -49,7 +49,7 @@ add_library(visitor add_dependencies(visitor lexer util) add_executable(nmodl_visitor main.cpp) -target_link_libraries(nmodl_visitor printer visitor symtab util lexer fmt) +target_link_libraries(nmodl_visitor printer visitor symtab util lexer) #============================================================================= # Files for clang-format From 215536eab865154be556b751cff96fbd6c9511e4 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 20 Oct 2018 15:14:47 +0200 Subject: [PATCH 080/871] Move out external libraries from src under main project directory Change-Id: I2c4d312d74bc1eeaf1aa2951872e88165ad30155 NMODL Repo SHA: BlueBrain/nmodl@ecef16af78416dee2bf8de7d0a0629ae2d96b992 --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/ext/catch/catch.hpp | 12012 ------------- src/nmodl/ext/flags/LICENSE | 21 - src/nmodl/ext/flags/allow_flags.hpp | 24 - src/nmodl/ext/flags/flags.hpp | 309 - src/nmodl/ext/flags/flagsfwd.hpp | 8 - src/nmodl/ext/flags/iterator.hpp | 86 - src/nmodl/ext/fmt/core.h | 1502 -- src/nmodl/ext/fmt/format-inl.h | 866 - src/nmodl/ext/fmt/format.h | 3720 ---- src/nmodl/ext/json/json.hpp | 14722 ---------------- src/nmodl/ext/spdlog/async.h | 87 - src/nmodl/ext/spdlog/async_logger.h | 73 - src/nmodl/ext/spdlog/common.h | 186 - .../ext/spdlog/details/async_logger_impl.h | 110 - src/nmodl/ext/spdlog/details/circular_q.h | 72 - .../ext/spdlog/details/console_globals.h | 74 - src/nmodl/ext/spdlog/details/file_helper.h | 152 - src/nmodl/ext/spdlog/details/fmt_helper.h | 127 - src/nmodl/ext/spdlog/details/log_msg.h | 49 - src/nmodl/ext/spdlog/details/logger_impl.h | 383 - .../ext/spdlog/details/mpmc_blocking_q.h | 121 - src/nmodl/ext/spdlog/details/null_mutex.h | 45 - src/nmodl/ext/spdlog/details/os.h | 422 - .../ext/spdlog/details/pattern_formatter.h | 777 - .../ext/spdlog/details/periodic_worker.h | 71 - src/nmodl/ext/spdlog/details/registry.h | 275 - src/nmodl/ext/spdlog/details/thread_pool.h | 228 - src/nmodl/ext/spdlog/fmt/bin_to_hex.h | 172 - src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst | 23 - src/nmodl/ext/spdlog/fmt/bundled/colors.h | 257 - src/nmodl/ext/spdlog/fmt/bundled/core.h | 1502 -- src/nmodl/ext/spdlog/fmt/bundled/format-inl.h | 866 - src/nmodl/ext/spdlog/fmt/bundled/format.h | 3720 ---- src/nmodl/ext/spdlog/fmt/bundled/ostream.h | 157 - src/nmodl/ext/spdlog/fmt/bundled/posix.h | 324 - src/nmodl/ext/spdlog/fmt/bundled/printf.h | 726 - src/nmodl/ext/spdlog/fmt/bundled/ranges.h | 308 - src/nmodl/ext/spdlog/fmt/bundled/time.h | 156 - src/nmodl/ext/spdlog/fmt/fmt.h | 25 - src/nmodl/ext/spdlog/fmt/ostr.h | 18 - src/nmodl/ext/spdlog/formatter.h | 20 - src/nmodl/ext/spdlog/logger.h | 167 - src/nmodl/ext/spdlog/sinks/android_sink.h | 121 - src/nmodl/ext/spdlog/sinks/ansicolor_sink.h | 161 - src/nmodl/ext/spdlog/sinks/base_sink.h | 69 - src/nmodl/ext/spdlog/sinks/basic_file_sink.h | 70 - src/nmodl/ext/spdlog/sinks/daily_file_sink.h | 136 - src/nmodl/ext/spdlog/sinks/dist_sink.h | 94 - src/nmodl/ext/spdlog/sinks/msvc_sink.h | 54 - src/nmodl/ext/spdlog/sinks/null_sink.h | 49 - src/nmodl/ext/spdlog/sinks/ostream_sink.h | 57 - .../ext/spdlog/sinks/rotating_file_sink.h | 154 - src/nmodl/ext/spdlog/sinks/sink.h | 59 - .../ext/spdlog/sinks/stdout_color_sinks.h | 56 - src/nmodl/ext/spdlog/sinks/stdout_sinks.h | 102 - src/nmodl/ext/spdlog/sinks/syslog_sink.h | 94 - src/nmodl/ext/spdlog/sinks/wincolor_sink.h | 143 - src/nmodl/ext/spdlog/spdlog.h | 320 - src/nmodl/ext/spdlog/tweakme.h | 130 - src/nmodl/ext/spdlog/version.h | 12 - src/nmodl/ext/tclap/Arg.h | 692 - src/nmodl/ext/tclap/ArgException.h | 200 - src/nmodl/ext/tclap/ArgTraits.h | 87 - src/nmodl/ext/tclap/CmdLine.h | 651 - src/nmodl/ext/tclap/CmdLineInterface.h | 150 - src/nmodl/ext/tclap/CmdLineOutput.h | 74 - src/nmodl/ext/tclap/Constraint.h | 68 - src/nmodl/ext/tclap/DocBookOutput.h | 299 - src/nmodl/ext/tclap/HelpVisitor.h | 76 - src/nmodl/ext/tclap/IgnoreRestVisitor.h | 52 - src/nmodl/ext/tclap/MultiArg.h | 433 - src/nmodl/ext/tclap/MultiSwitchArg.h | 216 - .../ext/tclap/OptionalUnlabeledTracker.h | 62 - src/nmodl/ext/tclap/StandardTraits.h | 208 - src/nmodl/ext/tclap/StdOutput.h | 299 - src/nmodl/ext/tclap/SwitchArg.h | 266 - src/nmodl/ext/tclap/UnlabeledMultiArg.h | 301 - src/nmodl/ext/tclap/UnlabeledValueArg.h | 340 - src/nmodl/ext/tclap/ValueArg.h | 425 - src/nmodl/ext/tclap/ValuesConstraint.h | 148 - src/nmodl/ext/tclap/VersionVisitor.h | 81 - src/nmodl/ext/tclap/Visitor.h | 53 - src/nmodl/ext/tclap/XorHandler.h | 166 - src/nmodl/ext/tclap/ZshCompletionOutput.h | 323 - test/nmodl/transpiler/lexer/tokens.cpp | 2 +- test/nmodl/transpiler/printer/printer.cpp | 2 +- 87 files changed, 3 insertions(+), 52517 deletions(-) delete mode 100644 src/nmodl/ext/catch/catch.hpp delete mode 100644 src/nmodl/ext/flags/LICENSE delete mode 100644 src/nmodl/ext/flags/allow_flags.hpp delete mode 100644 src/nmodl/ext/flags/flags.hpp delete mode 100644 src/nmodl/ext/flags/flagsfwd.hpp delete mode 100644 src/nmodl/ext/flags/iterator.hpp delete mode 100644 src/nmodl/ext/fmt/core.h delete mode 100644 src/nmodl/ext/fmt/format-inl.h delete mode 100644 src/nmodl/ext/fmt/format.h delete mode 100644 src/nmodl/ext/json/json.hpp delete mode 100644 src/nmodl/ext/spdlog/async.h delete mode 100644 src/nmodl/ext/spdlog/async_logger.h delete mode 100644 src/nmodl/ext/spdlog/common.h delete mode 100644 src/nmodl/ext/spdlog/details/async_logger_impl.h delete mode 100644 src/nmodl/ext/spdlog/details/circular_q.h delete mode 100644 src/nmodl/ext/spdlog/details/console_globals.h delete mode 100644 src/nmodl/ext/spdlog/details/file_helper.h delete mode 100644 src/nmodl/ext/spdlog/details/fmt_helper.h delete mode 100644 src/nmodl/ext/spdlog/details/log_msg.h delete mode 100644 src/nmodl/ext/spdlog/details/logger_impl.h delete mode 100644 src/nmodl/ext/spdlog/details/mpmc_blocking_q.h delete mode 100644 src/nmodl/ext/spdlog/details/null_mutex.h delete mode 100644 src/nmodl/ext/spdlog/details/os.h delete mode 100644 src/nmodl/ext/spdlog/details/pattern_formatter.h delete mode 100644 src/nmodl/ext/spdlog/details/periodic_worker.h delete mode 100644 src/nmodl/ext/spdlog/details/registry.h delete mode 100644 src/nmodl/ext/spdlog/details/thread_pool.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bin_to_hex.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/colors.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/core.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/format-inl.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/format.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/ostream.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/posix.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/printf.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/ranges.h delete mode 100644 src/nmodl/ext/spdlog/fmt/bundled/time.h delete mode 100644 src/nmodl/ext/spdlog/fmt/fmt.h delete mode 100644 src/nmodl/ext/spdlog/fmt/ostr.h delete mode 100644 src/nmodl/ext/spdlog/formatter.h delete mode 100644 src/nmodl/ext/spdlog/logger.h delete mode 100644 src/nmodl/ext/spdlog/sinks/android_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/ansicolor_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/base_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/basic_file_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/daily_file_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/dist_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/msvc_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/null_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/ostream_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/rotating_file_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h delete mode 100644 src/nmodl/ext/spdlog/sinks/stdout_sinks.h delete mode 100644 src/nmodl/ext/spdlog/sinks/syslog_sink.h delete mode 100644 src/nmodl/ext/spdlog/sinks/wincolor_sink.h delete mode 100644 src/nmodl/ext/spdlog/spdlog.h delete mode 100644 src/nmodl/ext/spdlog/tweakme.h delete mode 100644 src/nmodl/ext/spdlog/version.h delete mode 100755 src/nmodl/ext/tclap/Arg.h delete mode 100755 src/nmodl/ext/tclap/ArgException.h delete mode 100755 src/nmodl/ext/tclap/ArgTraits.h delete mode 100755 src/nmodl/ext/tclap/CmdLine.h delete mode 100755 src/nmodl/ext/tclap/CmdLineInterface.h delete mode 100755 src/nmodl/ext/tclap/CmdLineOutput.h delete mode 100755 src/nmodl/ext/tclap/Constraint.h delete mode 100755 src/nmodl/ext/tclap/DocBookOutput.h delete mode 100755 src/nmodl/ext/tclap/HelpVisitor.h delete mode 100755 src/nmodl/ext/tclap/IgnoreRestVisitor.h delete mode 100755 src/nmodl/ext/tclap/MultiArg.h delete mode 100755 src/nmodl/ext/tclap/MultiSwitchArg.h delete mode 100755 src/nmodl/ext/tclap/OptionalUnlabeledTracker.h delete mode 100755 src/nmodl/ext/tclap/StandardTraits.h delete mode 100755 src/nmodl/ext/tclap/StdOutput.h delete mode 100755 src/nmodl/ext/tclap/SwitchArg.h delete mode 100755 src/nmodl/ext/tclap/UnlabeledMultiArg.h delete mode 100755 src/nmodl/ext/tclap/UnlabeledValueArg.h delete mode 100755 src/nmodl/ext/tclap/ValueArg.h delete mode 100755 src/nmodl/ext/tclap/ValuesConstraint.h delete mode 100755 src/nmodl/ext/tclap/VersionVisitor.h delete mode 100755 src/nmodl/ext/tclap/Visitor.h delete mode 100755 src/nmodl/ext/tclap/XorHandler.h delete mode 100755 src/nmodl/ext/tclap/ZshCompletionOutput.h diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4a953c13d7..1f3584e78c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -39,7 +39,7 @@ find_python_module(yaml REQUIRED) include_directories( ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src - ${PROJECT_SOURCE_DIR}/src/ext + ${PROJECT_SOURCE_DIR}/ext ) #============================================================================= diff --git a/src/nmodl/ext/catch/catch.hpp b/src/nmodl/ext/catch/catch.hpp deleted file mode 100644 index 362f8693f7..0000000000 --- a/src/nmodl/ext/catch/catch.hpp +++ /dev/null @@ -1,12012 +0,0 @@ -/* - * Catch v2.0.1 - * Generated: 2017-11-03 11:53:39.642003 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2017 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic ignored "-Wglobal-constructors" -# pragma clang diagnostic ignored "-Wvariadic-macros" -# pragma clang diagnostic ignored "-Wc99-extensions" -# pragma clang diagnostic ignored "-Wunused-variable" -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic ignored "-Wvariadic-macros" -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wparentheses" - -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -#endif - -// start catch_platform.h - -#ifdef __APPLE__ -# include <TargetConditionals.h> -# if TARGET_OS_MAC == 1 -# define CATCH_PLATFORM_MAC -# elif TARGET_OS_IPHONE == 1 -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_<feature name> form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -# if __cplusplus >= 201402L -# define CATCH_CPP14_OR_GREATER -# endif - -#endif - -#ifdef __clang__ - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic push" ) \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic pop" ) - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# endif - -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE - -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#ifdef _MSC_VER - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -# define CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include <iosfwd> -#include <string> -#include <cstdint> - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept; - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo( SourceLineInfo && ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo& operator = ( SourceLineInfo && ) = default; - - bool empty() const noexcept; - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // This is just here to avoid compiler warnings with macro constants and boolean literals - bool isTrue( bool value ); - bool alwaysTrue(); - bool alwaysFalse(); - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template<typename T> - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast<std::size_t>( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include <vector> -#include <memory> - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - using ITestCasePtr = std::shared_ptr<ITestInvoker>; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector<TestCase> const& getAllTests() const = 0; - virtual std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include <cstddef> -#include <string> -#include <iosfwd> - -namespace Catch { - - class StringData; - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. c_str() must return a null terminated - /// string, however, and so the StringRef will internally take ownership - /// (taking a copy), if necessary. In theory this ownership is not externally - /// visible - but it does mean (substring) StringRefs should not be shared between - /// threads. - class StringRef { - friend struct StringRefTestAccess; - - using size_type = std::size_t; - - char const* m_start; - size_type m_size; - - char* m_data = nullptr; - - void takeOwnership(); - - public: // construction/ assignment - StringRef() noexcept; - StringRef( StringRef const& other ) noexcept; - StringRef( StringRef&& other ) noexcept; - StringRef( char const* rawChars ) noexcept; - StringRef( char const* rawChars, size_type size ) noexcept; - StringRef( std::string const& stdString ) noexcept; - ~StringRef() noexcept; - - auto operator = ( StringRef other ) noexcept -> StringRef&; - operator std::string() const; - - void swap( StringRef& other ) noexcept; - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != ( StringRef const& other ) const noexcept -> bool; - - auto operator[] ( size_type index ) const noexcept -> char; - - public: // named queries - auto empty() const noexcept -> bool; - auto size() const noexcept -> size_type; - auto numberOfCharacters() const noexcept -> size_type; - auto c_str() const -> char const*; - - public: // substrings and searches - auto substr( size_type start, size_type size ) const noexcept -> StringRef; - - private: // ownership queries - may not be consistent between calls - auto isOwned() const noexcept -> bool; - auto isSubstring() const noexcept -> bool; - auto data() const noexcept -> char const*; - }; - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string; - auto operator + ( StringRef const& lhs, char const* rhs ) -> std::string; - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string; - - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - -} // namespace Catch - -// end catch_stringref.h -namespace Catch { - -template<typename C> -class TestInvokerAsMethod : public ITestInvoker { - void (C::*m_testAsMethod)(); -public: - TestInvokerAsMethod( void (C::*testAsMethod)() ) noexcept : m_testAsMethod( testAsMethod ) {} - - void invoke() const override { - C obj; - (obj.*m_testAsMethod)(); - } -}; - -auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker*; - -template<typename C> -auto makeTestInvoker( void (C::*testAsMethod)() ) noexcept -> ITestInvoker* { - return new(std::nothrow) TestInvokerAsMethod<C>( testAsMethod ); -} - -struct NameAndTags { - NameAndTags( StringRef name_ = "", StringRef tags_ = "" ) noexcept; - StringRef name; - StringRef tags; -}; - -struct AutoReg : NonCopyable { - AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept; - ~AutoReg(); -}; - -} // end namespace Catch - -#if defined(CATCH_CONFIG_DISABLE) - #define INTERNAL_CATCH_TESTCASE_NO_REGISTRATION( TestName, ... ) \ - static void TestName() - #define INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION( TestName, ClassName, ... ) \ - namespace{ \ - struct TestName : ClassName { \ - void test(); \ - }; \ - } \ - void TestName::test() - -#endif - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TESTCASE2( TestName, ... ) \ - static void TestName(); \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &TestName ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - static void TestName() - #define INTERNAL_CATCH_TESTCASE( ... ) \ - INTERNAL_CATCH_TESTCASE2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), __VA_ARGS__ ) - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_METHOD_AS_TEST_CASE( QualifiedMethod, ... ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( &QualifiedMethod ), CATCH_INTERNAL_LINEINFO, "&" #QualifiedMethod, Catch::NameAndTags{ __VA_ARGS__ } ); } /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_TEST_CASE_METHOD2( TestName, ClassName, ... )\ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ \ - struct TestName : ClassName{ \ - void test(); \ - }; \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar ) ( Catch::makeTestInvoker( &TestName::test ), CATCH_INTERNAL_LINEINFO, #ClassName, Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS \ - void TestName::test() - #define INTERNAL_CATCH_TEST_CASE_METHOD( ClassName, ... ) \ - INTERNAL_CATCH_TEST_CASE_METHOD2( INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), ClassName, __VA_ARGS__ ) - - /////////////////////////////////////////////////////////////////////////////// - #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - Catch::AutoReg INTERNAL_CATCH_UNIQUE_NAME( autoRegistrar )( Catch::makeTestInvoker( Function ), CATCH_INTERNAL_LINEINFO, "", Catch::NameAndTags{ __VA_ARGS__ } ); /* NOLINT */ \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -// end catch_test_registry.h -// start catch_capture.hpp - -// start catch_assertionhandler.h - -// start catch_decomposer.h - -// start catch_tostring.h - -#include <sstream> -#include <vector> -#include <cstddef> -#include <type_traits> -#include <string> - -#ifdef __OBJC__ -// start catch_objc_arc.hpp - -#import <Foundation/Foundation.h> - -#ifdef __has_feature -#define CATCH_ARC_ENABLED __has_feature(objc_arc) -#else -#define CATCH_ARC_ENABLED 0 -#endif - -void arcSafeRelease( NSObject* obj ); -id performOptionalSelector( id obj, SEL sel ); - -#if !CATCH_ARC_ENABLED -inline void arcSafeRelease( NSObject* obj ) { - [obj release]; -} -inline id performOptionalSelector( id obj, SEL sel ) { - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; - return nil; -} -#define CATCH_UNSAFE_UNRETAINED -#define CATCH_ARC_STRONG -#else -inline void arcSafeRelease( NSObject* ){} -inline id performOptionalSelector( id obj, SEL sel ) { -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Warc-performSelector-leaks" -#endif - if( [obj respondsToSelector: sel] ) - return [obj performSelector: sel]; -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - return nil; -} -#define CATCH_UNSAFE_UNRETAINED __unsafe_unretained -#define CATCH_ARC_STRONG __strong -#endif - -// end catch_objc_arc.hpp -#endif - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4180) // We attempt to stream a function (address) by const&, which MSVC complains about but is harmless -#endif - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - -namespace Catch { - // Bring in operator<< from global namespace into Catch namespace - using ::operator<<; - - namespace Detail { - - extern const std::string unprintableString; - - std::string rawMemoryToString( const void *object, std::size_t size ); - - template<typename T> - std::string rawMemoryToString( const T& object ) { - return rawMemoryToString( &object, sizeof(object) ); - } - - template<typename T> - class IsStreamInsertable { - template<typename SS, typename TT> - static auto test(int) - -> decltype(std::declval<SS&>() << std::declval<TT>(), std::true_type()); - - template<typename, typename> - static auto test(...)->std::false_type; - - public: - static const bool value = decltype(test<std::ostream, const T&>(0))::value; - }; - - } // namespace Detail - - // If we decide for C++14, change these to enable_if_ts - template <typename T> - struct StringMaker { - template <typename Fake = T> - static - typename std::enable_if<::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type - convert(const Fake& t) { - std::ostringstream sstr; - sstr << t; - return sstr.str(); - } - - template <typename Fake = T> - static - typename std::enable_if<!::Catch::Detail::IsStreamInsertable<Fake>::value, std::string>::type - convert(const Fake&) { - return Detail::unprintableString; - } - }; - - namespace Detail { - - // This function dispatches all stringification requests inside of Catch. - // Should be preferably called fully qualified, like ::Catch::Detail::stringify - template <typename T> - std::string stringify(const T& e) { - return ::Catch::StringMaker<typename std::remove_cv<typename std::remove_reference<T>::type>::type>::convert(e); - } - - } // namespace Detail - - // Some predefined specializations - - template<> - struct StringMaker<std::string> { - static std::string convert(const std::string& str); - }; - template<> - struct StringMaker<std::wstring> { - static std::string convert(const std::wstring& wstr); - }; - - template<> - struct StringMaker<char const *> { - static std::string convert(char const * str); - }; - template<> - struct StringMaker<char *> { - static std::string convert(char * str); - }; - template<> - struct StringMaker<wchar_t const *> { - static std::string convert(wchar_t const * str); - }; - template<> - struct StringMaker<wchar_t *> { - static std::string convert(wchar_t * str); - }; - - template<int SZ> - struct StringMaker<char[SZ]> { - static std::string convert(const char* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - template<int SZ> - struct StringMaker<signed char[SZ]> { - static std::string convert(const char* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - template<int SZ> - struct StringMaker<unsigned char[SZ]> { - static std::string convert(const char* str) { - return ::Catch::Detail::stringify(std::string{ str }); - } - }; - - template<> - struct StringMaker<int> { - static std::string convert(int value); - }; - template<> - struct StringMaker<long> { - static std::string convert(long value); - }; - template<> - struct StringMaker<long long> { - static std::string convert(long long value); - }; - template<> - struct StringMaker<unsigned int> { - static std::string convert(unsigned int value); - }; - template<> - struct StringMaker<unsigned long> { - static std::string convert(unsigned long value); - }; - template<> - struct StringMaker<unsigned long long> { - static std::string convert(unsigned long long value); - }; - - template<> - struct StringMaker<bool> { - static std::string convert(bool b); - }; - - template<> - struct StringMaker<char> { - static std::string convert(char c); - }; - template<> - struct StringMaker<signed char> { - static std::string convert(signed char c); - }; - template<> - struct StringMaker<unsigned char> { - static std::string convert(unsigned char c); - }; - - template<> - struct StringMaker<std::nullptr_t> { - static std::string convert(std::nullptr_t); - }; - - template<> - struct StringMaker<float> { - static std::string convert(float value); - }; - template<> - struct StringMaker<double> { - static std::string convert(double value); - }; - - template <typename T> - struct StringMaker<T*> { - template <typename U> - static std::string convert(U* p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - - template <typename R, typename C> - struct StringMaker<R C::*> { - static std::string convert(R C::* p) { - if (p) { - return ::Catch::Detail::rawMemoryToString(p); - } else { - return "nullptr"; - } - } - }; - - namespace Detail { - template<typename InputIterator> - std::string rangeToString(InputIterator first, InputIterator last) { - std::ostringstream oss; - oss << "{ "; - if (first != last) { - oss << ::Catch::Detail::stringify(*first); - for (++first; first != last; ++first) - oss << ", " << ::Catch::Detail::stringify(*first); - } - oss << " }"; - return oss.str(); - } - } - - template<typename T, typename Allocator> - struct StringMaker<std::vector<T, Allocator> > { - static std::string convert( std::vector<T,Allocator> const& v ) { - return ::Catch::Detail::rangeToString( v.begin(), v.end() ); - } - }; - - template<typename T> - struct EnumStringMaker { - static std::string convert(const T& t) { - return ::Catch::Detail::stringify(static_cast<typename std::underlying_type<T>::type>(t)); - } - }; - -#ifdef __OBJC__ - template<> - struct StringMaker<NSString*> { - static std::string convert(NSString * nsstring) { - if (!nsstring) - return "nil"; - return std::string("@") + [nsstring UTF8String]; - } - }; - template<> - struct StringMaker<NSObject*> { - static std::string convert(NSObject* nsObject) { - return ::Catch::Detail::stringify([nsObject description]); - } - - }; - namespace Detail { - inline std::string stringify( NSString* nsstring ) { - return StringMaker<NSString*>::convert( nsstring ); - } - - } // namespace Detail -#endif // __OBJC__ - -} // namespace Catch - -////////////////////////////////////////////////////// -// Separate std-lib types stringification, so it can be selectively enabled -// This means that we do not bring in - -#if defined(CATCH_CONFIG_ENABLE_ALL_STRINGMAKERS) -# define CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER -# define CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#endif - -// Separate std::pair specialization -#if defined(CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER) -#include <utility> -namespace Catch { - template<typename T1, typename T2> - struct StringMaker<std::pair<T1, T2> > { - static std::string convert(const std::pair<T1, T2>& pair) { - std::ostringstream oss; - oss << "{ " - << ::Catch::Detail::stringify(pair.first) - << ", " - << ::Catch::Detail::stringify(pair.second) - << " }"; - return oss.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_PAIR_STRINGMAKER - -// Separate std::tuple specialization -#if defined(CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER) -#include <tuple> -namespace Catch { - namespace Detail { - template< - typename Tuple, - std::size_t N = 0, - bool = (N < std::tuple_size<Tuple>::value) - > - struct TupleElementPrinter { - static void print(const Tuple& tuple, std::ostream& os) { - os << (N ? ", " : " ") - << ::Catch::Detail::stringify(std::get<N>(tuple)); - TupleElementPrinter<Tuple, N + 1>::print(tuple, os); - } - }; - - template< - typename Tuple, - std::size_t N - > - struct TupleElementPrinter<Tuple, N, false> { - static void print(const Tuple&, std::ostream&) {} - }; - - } - - template<typename ...Types> - struct StringMaker<std::tuple<Types...>> { - static std::string convert(const std::tuple<Types...>& tuple) { - std::ostringstream os; - os << '{'; - Detail::TupleElementPrinter<std::tuple<Types...>>::print(tuple, os); - os << " }"; - return os.str(); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_TUPLE_STRINGMAKER - -// Separate std::chrono::duration specialization -#if defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -#include <ctime> -#include <ratio> -#include <chrono> - -template <class Ratio> -struct ratio_string { - static std::string symbol(); -}; - -template <class Ratio> -std::string ratio_string<Ratio>::symbol() { - std::ostringstream oss; - oss << '[' << Ratio::num << '/' - << Ratio::den << ']'; - return oss.str(); -} -template <> -struct ratio_string<std::atto> { - static std::string symbol() { return "a"; } -}; -template <> -struct ratio_string<std::femto> { - static std::string symbol() { return "f"; } -}; -template <> -struct ratio_string<std::pico> { - static std::string symbol() { return "p"; } -}; -template <> -struct ratio_string<std::nano> { - static std::string symbol() { return "n"; } -}; -template <> -struct ratio_string<std::micro> { - static std::string symbol() { return "u"; } -}; -template <> -struct ratio_string<std::milli> { - static std::string symbol() { return "m"; } -}; - -namespace Catch { - //////////// - // std::chrono::duration specializations - template<typename Value, typename Ratio> - struct StringMaker<std::chrono::duration<Value, Ratio>> { - static std::string convert(std::chrono::duration<Value, Ratio> const& duration) { - std::ostringstream oss; - oss << duration.count() << ' ' << ratio_string<Ratio>::symbol() << 's'; - return oss.str(); - } - }; - template<typename Value> - struct StringMaker<std::chrono::duration<Value, std::ratio<1>>> { - static std::string convert(std::chrono::duration<Value, std::ratio<1>> const& duration) { - std::ostringstream oss; - oss << duration.count() << " s"; - return oss.str(); - } - }; - template<typename Value> - struct StringMaker<std::chrono::duration<Value, std::ratio<60>>> { - static std::string convert(std::chrono::duration<Value, std::ratio<60>> const& duration) { - std::ostringstream oss; - oss << duration.count() << " m"; - return oss.str(); - } - }; - template<typename Value> - struct StringMaker<std::chrono::duration<Value, std::ratio<3600>>> { - static std::string convert(std::chrono::duration<Value, std::ratio<3600>> const& duration) { - std::ostringstream oss; - oss << duration.count() << " h"; - return oss.str(); - } - }; - - //////////// - // std::chrono::time_point specialization - // Generic time_point cannot be specialized, only std::chrono::time_point<system_clock> - template<typename Clock, typename Duration> - struct StringMaker<std::chrono::time_point<Clock, Duration>> { - static std::string convert(std::chrono::time_point<Clock, Duration> const& time_point) { - return ::Catch::Detail::stringify(time_point.time_since_epoch()) + " since epoch"; - } - }; - // std::chrono::time_point<system_clock> specialization - template<typename Duration> - struct StringMaker<std::chrono::time_point<std::chrono::system_clock, Duration>> { - static std::string convert(std::chrono::time_point<std::chrono::system_clock, Duration> const& time_point) { - auto converted = std::chrono::system_clock::to_time_t(time_point); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &converted); -#else - std::tm* timeInfo = std::gmtime(&converted); -#endif - - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - }; -} -#endif // CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_tostring.h -#include <ostream> - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable:4389) // '==' : signed/unsigned mismatch -#pragma warning(disable:4018) // more "signed/unsigned mismatch" -#pragma warning(disable:4312) // Converting int to T* using reinterpret_cast (issue on x64 platform) -#pragma warning(disable:4180) // qualifier applied to function type has no meaning -#endif - -namespace Catch { - - struct ITransientExpression { - virtual auto isBinaryExpression() const -> bool = 0; - virtual auto getResult() const -> bool = 0; - virtual void streamReconstructedExpression( std::ostream &os ) const = 0; - - // We don't actually need a virtual destructore, but many static analysers - // complain if it's not here :-( - virtual ~ITransientExpression(); - }; - - void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ); - - template<typename LhsT, typename RhsT> - class BinaryExpr : public ITransientExpression { - bool m_result; - LhsT m_lhs; - StringRef m_op; - RhsT m_rhs; - - auto isBinaryExpression() const -> bool override { return true; } - auto getResult() const -> bool override { return m_result; } - - void streamReconstructedExpression( std::ostream &os ) const override { - formatReconstructedExpression - ( os, Catch::Detail::stringify( m_lhs ), m_op, Catch::Detail::stringify( m_rhs ) ); - } - - public: - BinaryExpr( bool comparisonResult, LhsT lhs, StringRef op, RhsT rhs ) - : m_result( comparisonResult ), - m_lhs( lhs ), - m_op( op ), - m_rhs( rhs ) - {} - }; - - template<typename LhsT> - class UnaryExpr : public ITransientExpression { - LhsT m_lhs; - - auto isBinaryExpression() const -> bool override { return false; } - auto getResult() const -> bool override { return m_lhs ? true : false; } - - void streamReconstructedExpression( std::ostream &os ) const override { - os << Catch::Detail::stringify( m_lhs ); - } - - public: - UnaryExpr( LhsT lhs ) : m_lhs( lhs ) {} - }; - - // Specialised comparison functions to handle equality comparisons between ints and pointers (NULL deduces as an int) - template<typename LhsT, typename RhsT> - auto compareEqual( LhsT const& lhs, RhsT const& rhs ) -> bool { return lhs == rhs; }; - template<typename T> - auto compareEqual( T* const& lhs, int rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } - template<typename T> - auto compareEqual( T* const& lhs, long rhs ) -> bool { return lhs == reinterpret_cast<void const*>( rhs ); } - template<typename T> - auto compareEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } - template<typename T> - auto compareEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) == rhs; } - - template<typename LhsT, typename RhsT> - auto compareNotEqual( LhsT const& lhs, RhsT&& rhs ) -> bool { return lhs != rhs; }; - template<typename T> - auto compareNotEqual( T* const& lhs, int rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } - template<typename T> - auto compareNotEqual( T* const& lhs, long rhs ) -> bool { return lhs != reinterpret_cast<void const*>( rhs ); } - template<typename T> - auto compareNotEqual( int lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } - template<typename T> - auto compareNotEqual( long lhs, T* const& rhs ) -> bool { return reinterpret_cast<void const*>( lhs ) != rhs; } - - template<typename LhsT> - class ExprLhs { - LhsT m_lhs; - public: - ExprLhs( LhsT lhs ) : m_lhs( lhs ) {} - - template<typename RhsT> - auto operator == ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return BinaryExpr<LhsT, RhsT const&>( compareEqual( m_lhs, rhs ), m_lhs, "==", rhs ); - } - auto operator == ( bool rhs ) -> BinaryExpr<LhsT, bool> const { - return BinaryExpr<LhsT, bool>( m_lhs == rhs, m_lhs, "==", rhs ); - } - - template<typename RhsT> - auto operator != ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return BinaryExpr<LhsT, RhsT const&>( compareNotEqual( m_lhs, rhs ), m_lhs, "!=", rhs ); - } - auto operator != ( bool rhs ) -> BinaryExpr<LhsT, bool> const { - return BinaryExpr<LhsT, bool>( m_lhs != rhs, m_lhs, "!=", rhs ); - } - - template<typename RhsT> - auto operator > ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return BinaryExpr<LhsT, RhsT const&>( m_lhs > rhs, m_lhs, ">", rhs ); - } - template<typename RhsT> - auto operator < ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return BinaryExpr<LhsT, RhsT const&>( m_lhs < rhs, m_lhs, "<", rhs ); - } - template<typename RhsT> - auto operator >= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return BinaryExpr<LhsT, RhsT const&>( m_lhs >= rhs, m_lhs, ">=", rhs ); - } - template<typename RhsT> - auto operator <= ( RhsT const& rhs ) -> BinaryExpr<LhsT, RhsT const&> const { - return BinaryExpr<LhsT, RhsT const&>( m_lhs <= rhs, m_lhs, "<=", rhs ); - } - - auto makeUnaryExpr() const -> UnaryExpr<LhsT> { - return UnaryExpr<LhsT>( m_lhs ); - } - }; - - void handleExpression( ITransientExpression const& expr ); - - template<typename T> - void handleExpression( ExprLhs<T> const& expr ) { - handleExpression( expr.makeUnaryExpr() ); - } - - struct Decomposer { - template<typename T> - auto operator <= ( T const& lhs ) -> ExprLhs<T const&> { - return ExprLhs<T const&>( lhs ); - } - auto operator <=( bool value ) -> ExprLhs<bool> { - return ExprLhs<bool>( value ); - } - }; - -} // end namespace Catch - -#ifdef _MSC_VER -#pragma warning(pop) -#endif - -// end catch_decomposer.h -// start catch_assertioninfo.h - -// start catch_result_type.h - -namespace Catch { - - // ResultWas::OfType enum - struct ResultWas { enum OfType { - Unknown = -1, - Ok = 0, - Info = 1, - Warning = 2, - - FailureBit = 0x10, - - ExpressionFailed = FailureBit | 1, - ExplicitFailure = FailureBit | 2, - - Exception = 0x100 | FailureBit, - - ThrewException = Exception | 1, - DidntThrowException = Exception | 2, - - FatalErrorCondition = 0x200 | FailureBit - - }; }; - - bool isOk( ResultWas::OfType resultType ); - bool isJustInfo( int flags ); - - // ResultDisposition::Flags enum - struct ResultDisposition { enum Flags { - Normal = 0x01, - - ContinueOnFailure = 0x02, // Failures fail test, but execution continues - FalseTest = 0x04, // Prefix expression with ! - SuppressFail = 0x08 // Failures are reported but do not fail the test - }; }; - - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ); - - bool shouldContinueOnFailure( int flags ); - bool isFalseTest( int flags ); - bool shouldSuppressFailure( int flags ); - -} // end namespace Catch - -// end catch_result_type.h -namespace Catch { - - struct AssertionInfo - { - StringRef macroName; - SourceLineInfo lineInfo; - StringRef capturedExpression; - ResultDisposition::Flags resultDisposition; - - // We want to delete this constructor but a compiler bug in 4.8 means - // the struct is then treated as non-aggregate - //AssertionInfo() = delete; - }; - -} // end namespace Catch - -// end catch_assertioninfo.h -namespace Catch { - - struct TestFailureException{}; - struct AssertionResultData; - - class LazyExpression { - friend class AssertionHandler; - friend struct AssertionStats; - - ITransientExpression const* m_transientExpression = nullptr; - bool m_isNegated; - public: - LazyExpression( bool isNegated ); - LazyExpression( LazyExpression const& other ); - LazyExpression& operator = ( LazyExpression const& ) = delete; - - explicit operator bool() const; - - friend auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream&; - }; - - class AssertionHandler { - AssertionInfo m_assertionInfo; - bool m_shouldDebugBreak = false; - bool m_shouldThrow = false; - bool m_inExceptionGuard = false; - - public: - AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ); - ~AssertionHandler(); - - void handle( ITransientExpression const& expr ); - - template<typename T> - void handle( ExprLhs<T> const& expr ) { - handle( expr.makeUnaryExpr() ); - } - void handle( ResultWas::OfType resultType ); - void handle( ResultWas::OfType resultType, StringRef const& message ); - void handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ); - void handle( AssertionResultData const& resultData, ITransientExpression const* expr ); - - auto shouldDebugBreak() const -> bool; - auto allowThrows() const -> bool; - void reactWithDebugBreak() const; - void reactWithoutDebugBreak() const; - void useActiveException(); - void setExceptionGuard(); - void unsetExceptionGuard(); - }; - - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ); - -} // namespace Catch - -// end catch_assertionhandler.h -// start catch_message.h - -#include <string> -#include <sstream> - -namespace Catch { - - struct MessageInfo { - MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ); - - std::string macroName; - std::string message; - SourceLineInfo lineInfo; - ResultWas::OfType type; - unsigned int sequence; - - bool operator == ( MessageInfo const& other ) const; - bool operator < ( MessageInfo const& other ) const; - private: - static unsigned int globalCount; - }; - - struct MessageStream { - - template<typename T> - MessageStream& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - // !TBD reuse a global/ thread-local stream - std::ostringstream m_stream; - }; - - struct MessageBuilder : MessageStream { - MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ); - - template<typename T> - MessageBuilder& operator << ( T const& value ) { - m_stream << value; - return *this; - } - - MessageInfo m_info; - }; - - class ScopedMessage { - public: - ScopedMessage( MessageBuilder const& builder ); - ~ScopedMessage(); - - MessageInfo m_info; - }; - -} // end namespace Catch - -// end catch_message.h -// start catch_interfaces_capture.h - -#include <string> - -namespace Catch { - - class AssertionResult; - struct AssertionInfo; - struct SectionInfo; - struct SectionEndInfo; - struct MessageInfo; - struct Counts; - struct BenchmarkInfo; - struct BenchmarkStats; - - struct IResultCapture { - - virtual ~IResultCapture(); - - virtual void assertionStarting( AssertionInfo const& info ) = 0; - virtual void assertionEnded( AssertionResult const& result ) = 0; - virtual bool sectionStarted( SectionInfo const& sectionInfo, - Counts& assertions ) = 0; - virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; - virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; - - virtual void benchmarkStarting( BenchmarkInfo const& info ) = 0; - virtual void benchmarkEnded( BenchmarkStats const& stats ) = 0; - - virtual void pushScopedMessage( MessageInfo const& message ) = 0; - virtual void popScopedMessage( MessageInfo const& message ) = 0; - - virtual std::string getCurrentTestName() const = 0; - virtual const AssertionResult* getLastResult() const = 0; - - virtual void exceptionEarlyReported() = 0; - - virtual void handleFatalErrorCondition( StringRef message ) = 0; - - virtual bool lastAssertionPassed() = 0; - virtual void assertionPassed() = 0; - virtual void assertionRun() = 0; - }; - - IResultCapture& getResultCapture(); -} - -// end catch_interfaces_capture.h -// start catch_debugger.h - -namespace Catch { - bool isDebuggerActive(); -} - -#ifdef CATCH_PLATFORM_MAC - - #define CATCH_TRAP() __asm__("int $3\n" : : ) /* NOLINT */ - -#elif defined(CATCH_PLATFORM_LINUX) - // If we can use inline assembler, do it because this allows us to break - // directly at the location of the failing check instead of breaking inside - // raise() called from it, i.e. one stack frame below. - #if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) - #define CATCH_TRAP() asm volatile ("int $3") /* NOLINT */ - #else // Fall back to the generic way. - #include <signal.h> - - #define CATCH_TRAP() raise(SIGTRAP) - #endif -#elif defined(_MSC_VER) - #define CATCH_TRAP() __debugbreak() -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) void __stdcall DebugBreak(); - #define CATCH_TRAP() DebugBreak() -#endif - -#ifdef CATCH_TRAP - #define CATCH_BREAK_INTO_DEBUGGER() if( Catch::isDebuggerActive() ) { CATCH_TRAP(); } -#else - #define CATCH_BREAK_INTO_DEBUGGER() Catch::alwaysTrue(); -#endif - -// end catch_debugger.h -#if !defined(CATCH_CONFIG_DISABLE) - -#if !defined(CATCH_CONFIG_DISABLE_STRINGIFICATION) - #define CATCH_INTERNAL_STRINGIFY(...) #__VA_ARGS__ -#else - #define CATCH_INTERNAL_STRINGIFY(...) "Disabled by CATCH_CONFIG_DISABLE_STRINGIFICATION" -#endif - -#if defined(CATCH_CONFIG_FAST_COMPILE) -/////////////////////////////////////////////////////////////////////////////// -// We can speedup compilation significantly by breaking into debugger lower in -// the callstack, because then we don't have to expand CATCH_BREAK_INTO_DEBUGGER -// macro in each assertion -#define INTERNAL_CATCH_REACT( handler ) \ - handler.reactWithDebugBreak(); - -/////////////////////////////////////////////////////////////////////////////// -// Another way to speed-up compilation is to omit local try-catch for REQUIRE* -// macros. -// This can potentially cause false negative, if the test code catches -// the exception before it propagates back up to the runner. -#define INTERNAL_CATCH_TRY( capturer ) capturer.setExceptionGuard(); -#define INTERNAL_CATCH_CATCH( capturer ) capturer.unsetExceptionGuard(); - -#else // CATCH_CONFIG_FAST_COMPILE - -/////////////////////////////////////////////////////////////////////////////// -// In the event of a failure works out if the debugger needs to be invoked -// and/or an exception thrown and takes appropriate action. -// This needs to be done as a macro so the debugger will stop in the user -// source code rather than in Catch library code -#define INTERNAL_CATCH_REACT( handler ) \ - if( handler.shouldDebugBreak() ) CATCH_BREAK_INTO_DEBUGGER(); \ - handler.reactWithoutDebugBreak(); - -#define INTERNAL_CATCH_TRY( capturer ) try -#define INTERNAL_CATCH_CATCH( capturer ) catch(...) { capturer.useActiveException(); } - -#endif - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TEST( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ - CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - catchAssertionHandler.handle( Catch::Decomposer() <= __VA_ARGS__ ); \ - CATCH_INTERNAL_UNSUPPRESS_PARENTHESES_WARNINGS \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::isTrue( false && static_cast<bool>( !!(__VA_ARGS__) ) ) ) // the expression here is never evaluated at runtime but it forces the compiler to give it a look - // The double negation silences MSVC's C4800 warning, the static_cast forces short-circuit evaluation if the type has overloaded &&. - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_IF( macroName, resultDisposition, ... ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( Catch::getResultCapture().lastAssertionPassed() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_ELSE( macroName, resultDisposition, ... ) \ - INTERNAL_CATCH_TEST( macroName, resultDisposition, __VA_ARGS__ ); \ - if( !Catch::getResultCapture().lastAssertionPassed() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_NO_THROW( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition ); \ - try { \ - static_cast<void>(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ - } \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( macroName, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__), resultDisposition); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast<void>(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_AS( macroName, exceptionType, resultDisposition, expr ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(expr) ", " CATCH_INTERNAL_STRINGIFY(exceptionType), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast<void>(expr); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( exceptionType const& ) { \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_MSG( macroName, messageType, resultDisposition, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, "", resultDisposition ); \ - catchAssertionHandler.handle( messageType, ( Catch::MessageStream() << __VA_ARGS__ + ::Catch::StreamEndStop() ).m_stream.str() ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_INFO( macroName, log ) \ - Catch::ScopedMessage INTERNAL_CATCH_UNIQUE_NAME( scopedMessage ) = Catch::MessageBuilder( macroName, CATCH_INTERNAL_LINEINFO, Catch::ResultWas::Info ) << log; - -/////////////////////////////////////////////////////////////////////////////// -// Although this is matcher-based, it can be used with just a string -#define INTERNAL_CATCH_THROWS_STR_MATCHES( macroName, resultDisposition, matcher, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast<void>(__VA_ARGS__); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( ... ) { \ - handleExceptionMatchExpr( catchAssertionHandler, matcher, #matcher ); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_capture.hpp -// start catch_section.h - -// start catch_section_info.h - -// start catch_totals.h - -#include <cstddef> - -namespace Catch { - - struct Counts { - Counts operator - ( Counts const& other ) const; - Counts& operator += ( Counts const& other ); - - std::size_t total() const; - bool allPassed() const; - bool allOk() const; - - std::size_t passed = 0; - std::size_t failed = 0; - std::size_t failedButOk = 0; - }; - - struct Totals { - - Totals operator - ( Totals const& other ) const; - Totals& operator += ( Totals const& other ); - - Totals delta( Totals const& prevTotals ) const; - - Counts assertions; - Counts testCases; - }; -} - -// end catch_totals.h -#include <string> - -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); - - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; - - struct SectionEndInfo { - SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ); - - SectionInfo sectionInfo; - Counts prevAssertions; - double durationInSeconds; - }; - -} // end namespace Catch - -// end catch_section_info.h -// start catch_timer.h - -#include <cstdint> - -namespace Catch { - - auto getCurrentNanosecondsSinceEpoch() -> uint64_t; - auto getEstimatedClockResolution() -> uint64_t; - - class Timer { - uint64_t m_nanoseconds = 0; - public: - void start(); - auto getElapsedNanoseconds() const -> unsigned int; - auto getElapsedMicroseconds() const -> unsigned int; - auto getElapsedMilliseconds() const -> unsigned int; - auto getElapsedSeconds() const -> double; - }; - -} // namespace Catch - -// end catch_timer.h -#include <string> - -namespace Catch { - - class Section : NonCopyable { - public: - Section( SectionInfo const& info ); - ~Section(); - - // This indicates whether the section should be executed or not - explicit operator bool() const; - - private: - SectionInfo m_info; - - std::string m_name; - Counts m_assertions; - bool m_sectionIncluded; - Timer m_timer; - }; - -} // end namespace Catch - - #define INTERNAL_CATCH_SECTION( ... ) \ - if( Catch::Section const& INTERNAL_CATCH_UNIQUE_NAME( catch_internal_Section ) = Catch::SectionInfo( CATCH_INTERNAL_LINEINFO, __VA_ARGS__ ) ) - -// end catch_section.h -// start catch_benchmark.h - -#include <cstdint> -#include <string> - -namespace Catch { - - class BenchmarkLooper { - - std::string m_name; - std::size_t m_count = 0; - std::size_t m_iterationsToRun = 1; - uint64_t m_resolution; - Timer m_timer; - - static auto getResolution() -> uint64_t; - public: - // Keep most of this inline as it's on the code path that is being timed - BenchmarkLooper( StringRef name ) - : m_name( name ), - m_resolution( getResolution() ) - { - reportStart(); - m_timer.start(); - } - - explicit operator bool() { - if( m_count < m_iterationsToRun ) - return true; - return needsMoreIterations(); - } - - void increment() { - ++m_count; - } - - void reportStart(); - auto needsMoreIterations() -> bool; - }; - -} // end namespace Catch - -#define BENCHMARK( name ) \ - for( Catch::BenchmarkLooper looper( name ); looper; looper.increment() ) - -// end catch_benchmark.h -// start catch_interfaces_exception.h - -// start catch_interfaces_registry_hub.h - -#include <string> -#include <memory> - -namespace Catch { - - class TestCase; - struct ITestCaseRegistry; - struct IExceptionTranslatorRegistry; - struct IExceptionTranslator; - struct IReporterRegistry; - struct IReporterFactory; - struct ITagAliasRegistry; - class StartupExceptionRegistry; - - using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; - - struct IRegistryHub { - virtual ~IRegistryHub(); - - virtual IReporterRegistry const& getReporterRegistry() const = 0; - virtual ITestCaseRegistry const& getTestCaseRegistry() const = 0; - virtual ITagAliasRegistry const& getTagAliasRegistry() const = 0; - - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() = 0; - - virtual StartupExceptionRegistry const& getStartupExceptionRegistry() const = 0; - }; - - struct IMutableRegistryHub { - virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) = 0; - virtual void registerListener( IReporterFactoryPtr const& factory ) = 0; - virtual void registerTest( TestCase const& testInfo ) = 0; - virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; - virtual void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) = 0; - virtual void registerStartupException() noexcept = 0; - }; - - IRegistryHub& getRegistryHub(); - IMutableRegistryHub& getMutableRegistryHub(); - void cleanUp(); - std::string translateActiveException(); - -} - -// end catch_interfaces_registry_hub.h -#if defined(CATCH_CONFIG_DISABLE) - #define INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( translatorName, signature) \ - static std::string translatorName( signature ) -#endif - -#include <exception> -#include <string> -#include <vector> - -namespace Catch { - using exceptionTranslateFunction = std::string(*)(); - - struct IExceptionTranslator; - using ExceptionTranslators = std::vector<std::unique_ptr<IExceptionTranslator const>>; - - struct IExceptionTranslator { - virtual ~IExceptionTranslator(); - virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; - }; - - struct IExceptionTranslatorRegistry { - virtual ~IExceptionTranslatorRegistry(); - - virtual std::string translateActiveException() const = 0; - }; - - class ExceptionTranslatorRegistrar { - template<typename T> - class ExceptionTranslator : public IExceptionTranslator { - public: - - ExceptionTranslator( std::string(*translateFunction)( T& ) ) - : m_translateFunction( translateFunction ) - {} - - std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const override { - try { - if( it == itEnd ) - std::rethrow_exception(std::current_exception()); - else - return (*it)->translate( it+1, itEnd ); - } - catch( T& ex ) { - return m_translateFunction( ex ); - } - } - - protected: - std::string(*m_translateFunction)( T& ); - }; - - public: - template<typename T> - ExceptionTranslatorRegistrar( std::string(*translateFunction)( T& ) ) { - getMutableRegistryHub().registerTranslator - ( new ExceptionTranslator<T>( translateFunction ) ); - } - }; -} - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION2( translatorName, signature ) \ - static std::string translatorName( signature ); \ - namespace{ Catch::ExceptionTranslatorRegistrar INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionRegistrar )( &translatorName ); }\ - static std::string translatorName( signature ) - -#define INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION2( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) - -// end catch_interfaces_exception.h -// start catch_approx.h - -// start catch_enforce.h - -#include <sstream> -#include <stdexcept> - -#define CATCH_PREPARE_EXCEPTION( type, msg ) \ - type( static_cast<std::ostringstream&&>( std::ostringstream() << msg ).str() ) -#define CATCH_INTERNAL_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::logic_error, CATCH_INTERNAL_LINEINFO << ": Internal Catch error: " << msg); -#define CATCH_ERROR( msg ) \ - throw CATCH_PREPARE_EXCEPTION( std::domain_error, msg ) -#define CATCH_ENFORCE( condition, msg ) \ - do{ if( !(condition) ) CATCH_ERROR( msg ); } while(false) - -// end catch_enforce.h -#include <type_traits> - -namespace Catch { -namespace Detail { - - class Approx { - private: - bool equalityComparisonImpl(double other) const; - - public: - explicit Approx ( double value ); - - static Approx custom(); - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx operator()( T const& value ) { - Approx approx( static_cast<double>(value) ); - approx.epsilon( m_epsilon ); - approx.margin( m_margin ); - approx.scale( m_scale ); - return approx; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - explicit Approx( T const& value ): Approx(static_cast<double>(value)) - {} - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator == ( const T& lhs, Approx const& rhs ) { - auto lhs_v = static_cast<double>(lhs); - return rhs.equalityComparisonImpl(lhs_v); - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator == ( Approx const& lhs, const T& rhs ) { - return operator==( rhs, lhs ); - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator != ( T const& lhs, Approx const& rhs ) { - return !operator==( lhs, rhs ); - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator != ( Approx const& lhs, T const& rhs ) { - return !operator==( rhs, lhs ); - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator <= ( T const& lhs, Approx const& rhs ) { - return static_cast<double>(lhs) < rhs.m_value || lhs == rhs; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator <= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value < static_cast<double>(rhs) || lhs == rhs; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator >= ( T const& lhs, Approx const& rhs ) { - return static_cast<double>(lhs) > rhs.m_value || lhs == rhs; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - friend bool operator >= ( Approx const& lhs, T const& rhs ) { - return lhs.m_value > static_cast<double>(rhs) || lhs == rhs; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx& epsilon( T const& newEpsilon ) { - double epsilonAsDouble = static_cast<double>(newEpsilon); - CATCH_ENFORCE(epsilonAsDouble >= 0 && epsilonAsDouble <= 1.0, - "Invalid Approx::epsilon: " << epsilonAsDouble - << ", Approx::epsilon has to be between 0 and 1"); - m_epsilon = epsilonAsDouble; - return *this; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx& margin( T const& newMargin ) { - double marginAsDouble = static_cast<double>(newMargin); - CATCH_ENFORCE(marginAsDouble >= 0, - "Invalid Approx::margin: " << marginAsDouble - << ", Approx::Margin has to be non-negative."); - m_margin = marginAsDouble; - return *this; - } - - template <typename T, typename = typename std::enable_if<std::is_constructible<double, T>::value>::type> - Approx& scale( T const& newScale ) { - m_scale = static_cast<double>(newScale); - return *this; - } - - std::string toString() const; - - private: - double m_epsilon; - double m_margin; - double m_scale; - double m_value; - }; -} - -template<> -struct StringMaker<Catch::Detail::Approx> { - static std::string convert(Catch::Detail::Approx const& value); -}; - -} // end namespace Catch - -// end catch_approx.h -// start catch_string_manip.h - -#include <string> -#include <iosfwd> - -namespace Catch { - - bool startsWith( std::string const& s, std::string const& prefix ); - bool startsWith( std::string const& s, char prefix ); - bool endsWith( std::string const& s, std::string const& suffix ); - bool endsWith( std::string const& s, char suffix ); - bool contains( std::string const& s, std::string const& infix ); - void toLowerInPlace( std::string& s ); - std::string toLower( std::string const& s ); - std::string trim( std::string const& str ); - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ); - - struct pluralise { - pluralise( std::size_t count, std::string const& label ); - - friend std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ); - - std::size_t m_count; - std::string m_label; - }; -} - -// end catch_string_manip.h -#ifndef CATCH_CONFIG_DISABLE_MATCHERS -// start catch_capture_matchers.h - -// start catch_matchers.h - -#include <string> -#include <vector> - -namespace Catch { -namespace Matchers { - namespace Impl { - - template<typename ArgT> struct MatchAllOf; - template<typename ArgT> struct MatchAnyOf; - template<typename ArgT> struct MatchNotOf; - - class MatcherUntypedBase { - public: - MatcherUntypedBase() = default; - MatcherUntypedBase ( MatcherUntypedBase const& ) = default; - MatcherUntypedBase& operator = ( MatcherUntypedBase const& ) = delete; - std::string toString() const; - - protected: - virtual ~MatcherUntypedBase(); - virtual std::string describe() const = 0; - mutable std::string m_cachedToString; - }; - - template<typename ObjectT> - struct MatcherMethod { - virtual bool match( ObjectT const& arg ) const = 0; - }; - template<typename PtrT> - struct MatcherMethod<PtrT*> { - virtual bool match( PtrT* arg ) const = 0; - }; - - template<typename ObjectT, typename ComparatorT = ObjectT> - struct MatcherBase : MatcherUntypedBase, MatcherMethod<ObjectT> { - - MatchAllOf<ComparatorT> operator && ( MatcherBase const& other ) const; - MatchAnyOf<ComparatorT> operator || ( MatcherBase const& other ) const; - MatchNotOf<ComparatorT> operator ! () const; - }; - - template<typename ArgT> - struct MatchAllOf : MatcherBase<ArgT> { - bool match( ArgT const& arg ) const override { - for( auto matcher : m_matchers ) { - if (!matcher->match(arg)) - return false; - } - return true; - } - std::string describe() const override { - std::string description; - description.reserve( 4 + m_matchers.size()*32 ); - description += "( "; - bool first = true; - for( auto matcher : m_matchers ) { - if( first ) - first = false; - else - description += " and "; - description += matcher->toString(); - } - description += " )"; - return description; - } - - MatchAllOf<ArgT>& operator && ( MatcherBase<ArgT> const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector<MatcherBase<ArgT> const*> m_matchers; - }; - template<typename ArgT> - struct MatchAnyOf : MatcherBase<ArgT> { - - bool match( ArgT const& arg ) const override { - for( auto matcher : m_matchers ) { - if (matcher->match(arg)) - return true; - } - return false; - } - std::string describe() const override { - std::string description; - description.reserve( 4 + m_matchers.size()*32 ); - description += "( "; - bool first = true; - for( auto matcher : m_matchers ) { - if( first ) - first = false; - else - description += " or "; - description += matcher->toString(); - } - description += " )"; - return description; - } - - MatchAnyOf<ArgT>& operator || ( MatcherBase<ArgT> const& other ) { - m_matchers.push_back( &other ); - return *this; - } - - std::vector<MatcherBase<ArgT> const*> m_matchers; - }; - - template<typename ArgT> - struct MatchNotOf : MatcherBase<ArgT> { - - MatchNotOf( MatcherBase<ArgT> const& underlyingMatcher ) : m_underlyingMatcher( underlyingMatcher ) {} - - bool match( ArgT const& arg ) const override { - return !m_underlyingMatcher.match( arg ); - } - - std::string describe() const override { - return "not " + m_underlyingMatcher.toString(); - } - MatcherBase<ArgT> const& m_underlyingMatcher; - }; - - template<typename ObjectT, typename ComparatorT> - MatchAllOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator && ( MatcherBase const& other ) const { - return MatchAllOf<ComparatorT>() && *this && other; - } - template<typename ObjectT, typename ComparatorT> - MatchAnyOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator || ( MatcherBase const& other ) const { - return MatchAnyOf<ComparatorT>() || *this || other; - } - template<typename ObjectT, typename ComparatorT> - MatchNotOf<ComparatorT> MatcherBase<ObjectT, ComparatorT>::operator ! () const { - return MatchNotOf<ComparatorT>( *this ); - } - - } // namespace Impl - -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; - -} // namespace Catch - -// end catch_matchers.h -// start catch_matchers_string.h - -#include <string> - -namespace Catch { -namespace Matchers { - - namespace StdString { - - struct CasedString - { - CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ); - std::string adjustString( std::string const& str ) const; - std::string caseSensitivitySuffix() const; - - CaseSensitive::Choice m_caseSensitivity; - std::string m_str; - }; - - struct StringMatcherBase : MatcherBase<std::string> { - StringMatcherBase( std::string const& operation, CasedString const& comparator ); - std::string describe() const override; - - CasedString m_comparator; - std::string m_operation; - }; - - struct EqualsMatcher : StringMatcherBase { - EqualsMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct ContainsMatcher : StringMatcherBase { - ContainsMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct StartsWithMatcher : StringMatcherBase { - StartsWithMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - struct EndsWithMatcher : StringMatcherBase { - EndsWithMatcher( CasedString const& comparator ); - bool match( std::string const& source ) const override; - }; - - } // namespace StdString - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ); - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_string.h -// start catch_matchers_vector.h - -namespace Catch { -namespace Matchers { - - namespace Vector { - - template<typename T> - struct ContainsElementMatcher : MatcherBase<std::vector<T>, T> { - - ContainsElementMatcher(T const &comparator) : m_comparator( comparator) {} - - bool match(std::vector<T> const &v) const override { - for (auto const& el : v) { - if (el == m_comparator) { - return true; - } - } - return false; - } - - std::string describe() const override { - return "Contains: " + ::Catch::Detail::stringify( m_comparator ); - } - - T const& m_comparator; - }; - - template<typename T> - struct ContainsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { - - ContainsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} - - bool match(std::vector<T> const &v) const override { - // !TBD: see note in EqualsMatcher - if (m_comparator.size() > v.size()) - return false; - for (auto const& comparator : m_comparator) { - auto present = false; - for (const auto& el : v) { - if (el == comparator) { - present = true; - break; - } - } - if (!present) { - return false; - } - } - return true; - } - std::string describe() const override { - return "Contains: " + ::Catch::Detail::stringify( m_comparator ); - } - - std::vector<T> const& m_comparator; - }; - - template<typename T> - struct EqualsMatcher : MatcherBase<std::vector<T>, std::vector<T> > { - - EqualsMatcher(std::vector<T> const &comparator) : m_comparator( comparator ) {} - - bool match(std::vector<T> const &v) const override { - // !TBD: This currently works if all elements can be compared using != - // - a more general approach would be via a compare template that defaults - // to using !=. but could be specialised for, e.g. std::vector<T> etc - // - then just call that directly - if (m_comparator.size() != v.size()) - return false; - for (std::size_t i = 0; i < v.size(); ++i) - if (m_comparator[i] != v[i]) - return false; - return true; - } - std::string describe() const override { - return "Equals: " + ::Catch::Detail::stringify( m_comparator ); - } - std::vector<T> const& m_comparator; - }; - - } // namespace Vector - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - - template<typename T> - Vector::ContainsMatcher<T> Contains( std::vector<T> const& comparator ) { - return Vector::ContainsMatcher<T>( comparator ); - } - - template<typename T> - Vector::ContainsElementMatcher<T> VectorContains( T const& comparator ) { - return Vector::ContainsElementMatcher<T>( comparator ); - } - - template<typename T> - Vector::EqualsMatcher<T> Equals( std::vector<T> const& comparator ) { - return Vector::EqualsMatcher<T>( comparator ); - } - -} // namespace Matchers -} // namespace Catch - -// end catch_matchers_vector.h -namespace Catch { - - template<typename ArgT, typename MatcherT> - class MatchExpr : public ITransientExpression { - ArgT const& m_arg; - MatcherT m_matcher; - StringRef m_matcherString; - bool m_result; - public: - MatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) - : m_arg( arg ), - m_matcher( matcher ), - m_matcherString( matcherString ), - m_result( matcher.match( arg ) ) - {} - - auto isBinaryExpression() const -> bool override { return true; } - auto getResult() const -> bool override { return m_result; } - - void streamReconstructedExpression( std::ostream &os ) const override { - auto matcherAsString = m_matcher.toString(); - os << Catch::Detail::stringify( m_arg ) << ' '; - if( matcherAsString == Detail::unprintableString ) - os << m_matcherString; - else - os << matcherAsString; - } - }; - - using StringMatcher = Matchers::Impl::MatcherBase<std::string>; - - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ); - - template<typename ArgT, typename MatcherT> - auto makeMatchExpr( ArgT const& arg, MatcherT const& matcher, StringRef matcherString ) -> MatchExpr<ArgT, MatcherT> { - return MatchExpr<ArgT, MatcherT>( arg, matcher, matcherString ); - } - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CHECK_THAT( macroName, matcher, resultDisposition, arg ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(arg) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - INTERNAL_CATCH_TRY( catchAssertionHandler ) { \ - catchAssertionHandler.handle( Catch::makeMatchExpr( arg, matcher, #matcher ) ); \ - } INTERNAL_CATCH_CATCH( catchAssertionHandler ) \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -/////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS_MATCHES( macroName, exceptionType, resultDisposition, matcher, ... ) \ - do { \ - Catch::AssertionHandler catchAssertionHandler( macroName, CATCH_INTERNAL_LINEINFO, CATCH_INTERNAL_STRINGIFY(__VA_ARGS__) ", " CATCH_INTERNAL_STRINGIFY(exceptionType) ", " CATCH_INTERNAL_STRINGIFY(matcher), resultDisposition ); \ - if( catchAssertionHandler.allowThrows() ) \ - try { \ - static_cast<void>(__VA_ARGS__ ); \ - catchAssertionHandler.handle( Catch::ResultWas::DidntThrowException ); \ - } \ - catch( exceptionType const& ex ) { \ - catchAssertionHandler.handle( Catch::makeMatchExpr( ex, matcher, #matcher ) ); \ - } \ - catch( ... ) { \ - catchAssertionHandler.useActiveException(); \ - } \ - else \ - catchAssertionHandler.handle( Catch::ResultWas::Ok ); \ - INTERNAL_CATCH_REACT( catchAssertionHandler ) \ - } while( Catch::alwaysFalse() ) - -// end catch_capture_matchers.h -#endif - -// These files are included here so the single_include script doesn't put them -// in the conditionally compiled sections -// start catch_test_case_info.h - -#include <string> -#include <vector> -#include <memory> - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -namespace Catch { - - struct ITestInvoker; - - struct TestCaseInfo { - enum SpecialProperties{ - None = 0, - IsHidden = 1 << 1, - ShouldFail = 1 << 2, - MayFail = 1 << 3, - Throws = 1 << 4, - NonPortable = 1 << 5, - Benchmark = 1 << 6 - }; - - TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::vector<std::string> const& _tags, - SourceLineInfo const& _lineInfo ); - - friend void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ); - - bool isHidden() const; - bool throws() const; - bool okToFail() const; - bool expectedToFail() const; - - std::string tagsAsString() const; - - std::string name; - std::string className; - std::string description; - std::vector<std::string> tags; - std::vector<std::string> lcaseTags; - SourceLineInfo lineInfo; - SpecialProperties properties; - }; - - class TestCase : public TestCaseInfo { - public: - - TestCase( ITestInvoker* testCase, TestCaseInfo const& info ); - - TestCase withName( std::string const& _newName ) const; - - void invoke() const; - - TestCaseInfo const& getTestCaseInfo() const; - - bool operator == ( TestCase const& other ) const; - bool operator < ( TestCase const& other ) const; - - private: - std::shared_ptr<ITestInvoker> test; - }; - - TestCase makeTestCase( ITestInvoker* testCase, - std::string const& className, - std::string const& name, - std::string const& description, - SourceLineInfo const& lineInfo ); -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_case_info.h -// start catch_interfaces_runner.h - -namespace Catch { - - struct IRunner { - virtual ~IRunner(); - virtual bool aborting() const = 0; - }; -} - -// end catch_interfaces_runner.h - -#ifdef __OBJC__ -// start catch_objc.hpp - -#import <objc/runtime.h> - -#include <string> - -// NB. Any general catch headers included here must be included -// in catch.hpp first to make sure they are included by the single -// header for non obj-usage - -/////////////////////////////////////////////////////////////////////////////// -// This protocol is really only here for (self) documenting purposes, since -// all its methods are optional. -@protocol OcFixture - -@optional - --(void) setUp; --(void) tearDown; - -@end - -namespace Catch { - - class OcMethod : public ITestInvoker { - - public: - OcMethod( Class cls, SEL sel ) : m_cls( cls ), m_sel( sel ) {} - - virtual void invoke() const { - id obj = [[m_cls alloc] init]; - - performOptionalSelector( obj, @selector(setUp) ); - performOptionalSelector( obj, m_sel ); - performOptionalSelector( obj, @selector(tearDown) ); - - arcSafeRelease( obj ); - } - private: - virtual ~OcMethod() {} - - Class m_cls; - SEL m_sel; - }; - - namespace Detail{ - - inline std::string getAnnotation( Class cls, - std::string const& annotationName, - std::string const& testCaseName ) { - NSString* selStr = [[NSString alloc] initWithFormat:@"Catch_%s_%s", annotationName.c_str(), testCaseName.c_str()]; - SEL sel = NSSelectorFromString( selStr ); - arcSafeRelease( selStr ); - id value = performOptionalSelector( cls, sel ); - if( value ) - return [(NSString*)value UTF8String]; - return ""; - } - } - - inline std::size_t registerTestMethods() { - std::size_t noTestMethods = 0; - int noClasses = objc_getClassList( nullptr, 0 ); - - Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); - objc_getClassList( classes, noClasses ); - - for( int c = 0; c < noClasses; c++ ) { - Class cls = classes[c]; - { - u_int count; - Method* methods = class_copyMethodList( cls, &count ); - for( u_int m = 0; m < count ; m++ ) { - SEL selector = method_getName(methods[m]); - std::string methodName = sel_getName(selector); - if( startsWith( methodName, "Catch_TestCase_" ) ) { - std::string testCaseName = methodName.substr( 15 ); - std::string name = Detail::getAnnotation( cls, "Name", testCaseName ); - std::string desc = Detail::getAnnotation( cls, "Description", testCaseName ); - const char* className = class_getName( cls ); - - getMutableRegistryHub().registerTest( makeTestCase( new OcMethod( cls, selector ), className, name.c_str(), desc.c_str(), SourceLineInfo("",0) ) ); - noTestMethods++; - } - } - free(methods); - } - } - return noTestMethods; - } - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) - - namespace Matchers { - namespace Impl { - namespace NSStringMatchers { - - struct StringHolder : MatcherBase<NSString*>{ - StringHolder( NSString* substr ) : m_substr( [substr copy] ){} - StringHolder( StringHolder const& other ) : m_substr( [other.m_substr copy] ){} - StringHolder() { - arcSafeRelease( m_substr ); - } - - bool match( NSString* arg ) const override { - return false; - } - - NSString* CATCH_ARC_STRONG m_substr; - }; - - struct Equals : StringHolder { - Equals( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str isEqualToString:m_substr]; - } - - std::string describe() const override { - return "equals string: " + Catch::Detail::stringify( m_substr ); - } - }; - - struct Contains : StringHolder { - Contains( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location != NSNotFound; - } - - std::string describe() const override { - return "contains string: " + Catch::Detail::stringify( m_substr ); - } - }; - - struct StartsWith : StringHolder { - StartsWith( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location == 0; - } - - std::string describe() const override { - return "starts with: " + Catch::Detail::stringify( m_substr ); - } - }; - struct EndsWith : StringHolder { - EndsWith( NSString* substr ) : StringHolder( substr ){} - - bool match( NSString* str ) const override { - return (str != nil || m_substr == nil ) && - [str rangeOfString:m_substr].location == [str length] - [m_substr length]; - } - - std::string describe() const override { - return "ends with: " + Catch::Detail::stringify( m_substr ); - } - }; - - } // namespace NSStringMatchers - } // namespace Impl - - inline Impl::NSStringMatchers::Equals - Equals( NSString* substr ){ return Impl::NSStringMatchers::Equals( substr ); } - - inline Impl::NSStringMatchers::Contains - Contains( NSString* substr ){ return Impl::NSStringMatchers::Contains( substr ); } - - inline Impl::NSStringMatchers::StartsWith - StartsWith( NSString* substr ){ return Impl::NSStringMatchers::StartsWith( substr ); } - - inline Impl::NSStringMatchers::EndsWith - EndsWith( NSString* substr ){ return Impl::NSStringMatchers::EndsWith( substr ); } - - } // namespace Matchers - - using namespace Matchers; - -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -} // namespace Catch - -/////////////////////////////////////////////////////////////////////////////// -#define OC_MAKE_UNIQUE_NAME( root, uniqueSuffix ) root##uniqueSuffix -#define OC_TEST_CASE2( name, desc, uniqueSuffix ) \ -+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Name_test_, uniqueSuffix ) \ -{ \ -return @ name; \ -} \ -+(NSString*) OC_MAKE_UNIQUE_NAME( Catch_Description_test_, uniqueSuffix ) \ -{ \ -return @ desc; \ -} \ --(void) OC_MAKE_UNIQUE_NAME( Catch_TestCase_test_, uniqueSuffix ) - -#define OC_TEST_CASE( name, desc ) OC_TEST_CASE2( name, desc, __LINE__ ) - -// end catch_objc.hpp -#endif - -#ifdef CATCH_CONFIG_EXTERNAL_INTERFACES -// start catch_external_interfaces.h - -// start catch_reporter_bases.hpp - -// start catch_interfaces_reporter.h - -// start catch_config.hpp - -// start catch_test_spec_parser.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_test_spec.h - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wpadded" -#endif - -// start catch_wildcard_pattern.h - -namespace Catch -{ - class WildcardPattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - - public: - - WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ); - virtual ~WildcardPattern() = default; - virtual bool matches( std::string const& str ) const; - - private: - std::string adjustCase( std::string const& str ) const; - CaseSensitive::Choice m_caseSensitivity; - WildcardPosition m_wildcard = NoWildcard; - std::string m_pattern; - }; -} - -// end catch_wildcard_pattern.h -#include <string> -#include <vector> -#include <memory> - -namespace Catch { - - class TestSpec { - struct Pattern { - virtual ~Pattern(); - virtual bool matches( TestCaseInfo const& testCase ) const = 0; - }; - using PatternPtr = std::shared_ptr<Pattern>; - - class NamePattern : public Pattern { - public: - NamePattern( std::string const& name ); - virtual ~NamePattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - WildcardPattern m_wildcardPattern; - }; - - class TagPattern : public Pattern { - public: - TagPattern( std::string const& tag ); - virtual ~TagPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - std::string m_tag; - }; - - class ExcludedPattern : public Pattern { - public: - ExcludedPattern( PatternPtr const& underlyingPattern ); - virtual ~ExcludedPattern(); - virtual bool matches( TestCaseInfo const& testCase ) const override; - private: - PatternPtr m_underlyingPattern; - }; - - struct Filter { - std::vector<PatternPtr> m_patterns; - - bool matches( TestCaseInfo const& testCase ) const; - }; - - public: - bool hasFilters() const; - bool matches( TestCaseInfo const& testCase ) const; - - private: - std::vector<Filter> m_filters; - - friend class TestSpecParser; - }; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec.h -// start catch_interfaces_tag_alias_registry.h - -#include <string> - -namespace Catch { - - struct TagAlias; - - struct ITagAliasRegistry { - virtual ~ITagAliasRegistry(); - // Nullptr if not present - virtual TagAlias const* find( std::string const& alias ) const = 0; - virtual std::string expandAliases( std::string const& unexpandedTestSpec ) const = 0; - - static ITagAliasRegistry const& get(); - }; - -} // end namespace Catch - -// end catch_interfaces_tag_alias_registry.h -namespace Catch { - - class TestSpecParser { - enum Mode{ None, Name, QuotedName, Tag, EscapedName }; - Mode m_mode = None; - bool m_exclusion = false; - std::size_t m_start = std::string::npos, m_pos = 0; - std::string m_arg; - std::vector<std::size_t> m_escapeChars; - TestSpec::Filter m_currentFilter; - TestSpec m_testSpec; - ITagAliasRegistry const* m_tagAliases = nullptr; - - public: - TestSpecParser( ITagAliasRegistry const& tagAliases ); - - TestSpecParser& parse( std::string const& arg ); - TestSpec testSpec(); - - private: - void visitChar( char c ); - void startNewMode( Mode mode, std::size_t start ); - void escape(); - std::string subString() const; - - template<typename T> - void addPattern() { - std::string token = subString(); - for( std::size_t i = 0; i < m_escapeChars.size(); ++i ) - token = token.substr( 0, m_escapeChars[i]-m_start-i ) + token.substr( m_escapeChars[i]-m_start-i+1 ); - m_escapeChars.clear(); - if( startsWith( token, "exclude:" ) ) { - m_exclusion = true; - token = token.substr( 8 ); - } - if( !token.empty() ) { - TestSpec::PatternPtr pattern = std::make_shared<T>( token ); - if( m_exclusion ) - pattern = std::make_shared<TestSpec::ExcludedPattern>( pattern ); - m_currentFilter.m_patterns.push_back( pattern ); - } - m_exclusion = false; - m_mode = None; - } - - void addFilter(); - }; - TestSpec parseTestSpec( std::string const& arg ); - -} // namespace Catch - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_test_spec_parser.h -// start catch_interfaces_config.h - -#include <iosfwd> -#include <string> -#include <vector> -#include <memory> - -namespace Catch { - - enum class Verbosity { - Quiet = 0, - Normal, - High - }; - - struct WarnAbout { enum What { - Nothing = 0x00, - NoAssertions = 0x01 - }; }; - - struct ShowDurations { enum OrNot { - DefaultForReporter, - Always, - Never - }; }; - struct RunTests { enum InWhatOrder { - InDeclarationOrder, - InLexicographicalOrder, - InRandomOrder - }; }; - struct UseColour { enum YesOrNo { - Auto, - Yes, - No - }; }; - struct WaitForKeypress { enum When { - Never, - BeforeStart = 1, - BeforeExit = 2, - BeforeStartAndExit = BeforeStart | BeforeExit - }; }; - - class TestSpec; - - struct IConfig : NonCopyable { - - virtual ~IConfig(); - - virtual bool allowThrows() const = 0; - virtual std::ostream& stream() const = 0; - virtual std::string name() const = 0; - virtual bool includeSuccessfulResults() const = 0; - virtual bool shouldDebugBreak() const = 0; - virtual bool warnAboutMissingAssertions() const = 0; - virtual int abortAfter() const = 0; - virtual bool showInvisibles() const = 0; - virtual ShowDurations::OrNot showDurations() const = 0; - virtual TestSpec const& testSpec() const = 0; - virtual RunTests::InWhatOrder runOrder() const = 0; - virtual unsigned int rngSeed() const = 0; - virtual int benchmarkResolutionMultiple() const = 0; - virtual UseColour::YesOrNo useColour() const = 0; - virtual std::vector<std::string> const& getSectionsToRun() const = 0; - virtual Verbosity verbosity() const = 0; - }; - - using IConfigPtr = std::shared_ptr<IConfig const>; -} - -// end catch_interfaces_config.h -// Libstdc++ doesn't like incomplete classes for unique_ptr -// start catch_stream.h - -// start catch_streambuf.h - -#include <streambuf> - -namespace Catch { - - class StreamBufBase : public std::streambuf { - public: - virtual ~StreamBufBase(); - }; -} - -// end catch_streambuf.h -#include <streambuf> -#include <ostream> -#include <fstream> -#include <memory> - -namespace Catch { - - std::ostream& cout(); - std::ostream& cerr(); - std::ostream& clog(); - - struct IStream { - virtual ~IStream(); - virtual std::ostream& stream() const = 0; - }; - - class FileStream : public IStream { - mutable std::ofstream m_ofs; - public: - FileStream( std::string const& filename ); - ~FileStream() override = default; - public: // IStream - std::ostream& stream() const override; - }; - - class CoutStream : public IStream { - mutable std::ostream m_os; - public: - CoutStream(); - ~CoutStream() override = default; - - public: // IStream - std::ostream& stream() const override; - }; - - class DebugOutStream : public IStream { - std::unique_ptr<StreamBufBase> m_streamBuf; - mutable std::ostream m_os; - public: - DebugOutStream(); - ~DebugOutStream() override = default; - - public: // IStream - std::ostream& stream() const override; - }; -} - -// end catch_stream.h - -#include <memory> -#include <vector> -#include <string> - -#ifndef CATCH_CONFIG_CONSOLE_WIDTH -#define CATCH_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { - - struct IStream; - - struct ConfigData { - bool listTests = false; - bool listTags = false; - bool listReporters = false; - bool listTestNamesOnly = false; - - bool showSuccessfulTests = false; - bool shouldDebugBreak = false; - bool noThrow = false; - bool showHelp = false; - bool showInvisibles = false; - bool filenamesAsTags = false; - bool libIdentify = false; - - int abortAfter = -1; - unsigned int rngSeed = 0; - int benchmarkResolutionMultiple = 100; - - Verbosity verbosity = Verbosity::Normal; - WarnAbout::What warnings = WarnAbout::Nothing; - ShowDurations::OrNot showDurations = ShowDurations::DefaultForReporter; - RunTests::InWhatOrder runOrder = RunTests::InDeclarationOrder; - UseColour::YesOrNo useColour = UseColour::Auto; - WaitForKeypress::When waitForKeypress = WaitForKeypress::Never; - - std::string outputFilename; - std::string name; - std::string processName; - - std::vector<std::string> reporterNames; - std::vector<std::string> testsOrTags; - std::vector<std::string> sectionsToRun; - }; - - class Config : public IConfig { - public: - - Config() = default; - Config( ConfigData const& data ); - virtual ~Config() = default; - - std::string const& getFilename() const; - - bool listTests() const; - bool listTestNamesOnly() const; - bool listTags() const; - bool listReporters() const; - - std::string getProcessName() const; - - std::vector<std::string> const& getReporterNames() const; - std::vector<std::string> const& getSectionsToRun() const override; - - virtual TestSpec const& testSpec() const override; - - bool showHelp() const; - - // IConfig interface - bool allowThrows() const override; - std::ostream& stream() const override; - std::string name() const override; - bool includeSuccessfulResults() const override; - bool warnAboutMissingAssertions() const override; - ShowDurations::OrNot showDurations() const override; - RunTests::InWhatOrder runOrder() const override; - unsigned int rngSeed() const override; - int benchmarkResolutionMultiple() const override; - UseColour::YesOrNo useColour() const override; - bool shouldDebugBreak() const override; - int abortAfter() const override; - bool showInvisibles() const override; - Verbosity verbosity() const override; - - private: - - IStream const* openStream(); - ConfigData m_data; - - std::unique_ptr<IStream const> m_stream; - TestSpec m_testSpec; - }; - -} // end namespace Catch - -// end catch_config.hpp -// start catch_assertionresult.h - -#include <string> - -namespace Catch { - - struct AssertionResultData - { - AssertionResultData() = delete; - - AssertionResultData( ResultWas::OfType _resultType, LazyExpression const& _lazyExpression ); - - std::string message; - mutable std::string reconstructedExpression; - LazyExpression lazyExpression; - ResultWas::OfType resultType; - - std::string reconstructExpression() const; - }; - - class AssertionResult { - public: - AssertionResult() = delete; - AssertionResult( AssertionInfo const& info, AssertionResultData const& data ); - - bool isOk() const; - bool succeeded() const; - ResultWas::OfType getResultType() const; - bool hasExpression() const; - bool hasMessage() const; - std::string getExpression() const; - std::string getExpressionInMacro() const; - bool hasExpandedExpression() const; - std::string getExpandedExpression() const; - std::string getMessage() const; - SourceLineInfo getSourceInfo() const; - std::string getTestMacroName() const; - - //protected: - AssertionInfo m_info; - AssertionResultData m_resultData; - }; - -} // end namespace Catch - -// end catch_assertionresult.h -// start catch_option.hpp - -namespace Catch { - - // An optional type - template<typename T> - class Option { - public: - Option() : nullableValue( nullptr ) {} - Option( T const& _value ) - : nullableValue( new( storage ) T( _value ) ) - {} - Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : nullptr ) - {} - - ~Option() { - reset(); - } - - Option& operator= ( Option const& _other ) { - if( &_other != this ) { - reset(); - if( _other ) - nullableValue = new( storage ) T( *_other ); - } - return *this; - } - Option& operator = ( T const& _value ) { - reset(); - nullableValue = new( storage ) T( _value ); - return *this; - } - - void reset() { - if( nullableValue ) - nullableValue->~T(); - nullableValue = nullptr; - } - - T& operator*() { return *nullableValue; } - T const& operator*() const { return *nullableValue; } - T* operator->() { return nullableValue; } - const T* operator->() const { return nullableValue; } - - T valueOr( T const& defaultValue ) const { - return nullableValue ? *nullableValue : defaultValue; - } - - bool some() const { return nullableValue != nullptr; } - bool none() const { return nullableValue == nullptr; } - - bool operator !() const { return nullableValue == nullptr; } - explicit operator bool() const { - return some(); - } - - private: - T *nullableValue; - alignas(alignof(T)) char storage[sizeof(T)]; - }; - -} // end namespace Catch - -// end catch_option.hpp -#include <string> -#include <iosfwd> -#include <map> -#include <set> -#include <memory> - -namespace Catch { - - struct ReporterConfig { - explicit ReporterConfig( IConfigPtr const& _fullConfig ); - - ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ); - - std::ostream& stream() const; - IConfigPtr fullConfig() const; - - private: - std::ostream* m_stream; - IConfigPtr m_fullConfig; - }; - - struct ReporterPreferences { - bool shouldRedirectStdOut = false; - }; - - template<typename T> - struct LazyStat : Option<T> { - LazyStat& operator=( T const& _value ) { - Option<T>::operator=( _value ); - used = false; - return *this; - } - void reset() { - Option<T>::reset(); - used = false; - } - bool used = false; - }; - - struct TestRunInfo { - TestRunInfo( std::string const& _name ); - std::string name; - }; - struct GroupInfo { - GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ); - - std::string name; - std::size_t groupIndex; - std::size_t groupsCounts; - }; - - struct AssertionStats { - AssertionStats( AssertionResult const& _assertionResult, - std::vector<MessageInfo> const& _infoMessages, - Totals const& _totals ); - - AssertionStats( AssertionStats const& ) = default; - AssertionStats( AssertionStats && ) = default; - AssertionStats& operator = ( AssertionStats const& ) = default; - AssertionStats& operator = ( AssertionStats && ) = default; - virtual ~AssertionStats(); - - AssertionResult assertionResult; - std::vector<MessageInfo> infoMessages; - Totals totals; - }; - - struct SectionStats { - SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ); - SectionStats( SectionStats const& ) = default; - SectionStats( SectionStats && ) = default; - SectionStats& operator = ( SectionStats const& ) = default; - SectionStats& operator = ( SectionStats && ) = default; - virtual ~SectionStats(); - - SectionInfo sectionInfo; - Counts assertions; - double durationInSeconds; - bool missingAssertions; - }; - - struct TestCaseStats { - TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ); - - TestCaseStats( TestCaseStats const& ) = default; - TestCaseStats( TestCaseStats && ) = default; - TestCaseStats& operator = ( TestCaseStats const& ) = default; - TestCaseStats& operator = ( TestCaseStats && ) = default; - virtual ~TestCaseStats(); - - TestCaseInfo testInfo; - Totals totals; - std::string stdOut; - std::string stdErr; - bool aborting; - }; - - struct TestGroupStats { - TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ); - TestGroupStats( GroupInfo const& _groupInfo ); - - TestGroupStats( TestGroupStats const& ) = default; - TestGroupStats( TestGroupStats && ) = default; - TestGroupStats& operator = ( TestGroupStats const& ) = default; - TestGroupStats& operator = ( TestGroupStats && ) = default; - virtual ~TestGroupStats(); - - GroupInfo groupInfo; - Totals totals; - bool aborting; - }; - - struct TestRunStats { - TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ); - - TestRunStats( TestRunStats const& ) = default; - TestRunStats( TestRunStats && ) = default; - TestRunStats& operator = ( TestRunStats const& ) = default; - TestRunStats& operator = ( TestRunStats && ) = default; - virtual ~TestRunStats(); - - TestRunInfo runInfo; - Totals totals; - bool aborting; - }; - - struct BenchmarkInfo { - std::string name; - }; - struct BenchmarkStats { - BenchmarkInfo info; - std::size_t iterations; - uint64_t elapsedTimeInNanoseconds; - }; - - struct IStreamingReporter { - virtual ~IStreamingReporter() = default; - - // Implementing class must also provide the following static methods: - // static std::string getDescription(); - // static std::set<Verbosity> getSupportedVerbosities() - - virtual ReporterPreferences getPreferences() const = 0; - - virtual void noMatchingTestCases( std::string const& spec ) = 0; - - virtual void testRunStarting( TestRunInfo const& testRunInfo ) = 0; - virtual void testGroupStarting( GroupInfo const& groupInfo ) = 0; - - virtual void testCaseStarting( TestCaseInfo const& testInfo ) = 0; - virtual void sectionStarting( SectionInfo const& sectionInfo ) = 0; - - // *** experimental *** - virtual void benchmarkStarting( BenchmarkInfo const& ) {} - - virtual void assertionStarting( AssertionInfo const& assertionInfo ) = 0; - - // The return value indicates if the messages buffer should be cleared: - virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; - - // *** experimental *** - virtual void benchmarkEnded( BenchmarkStats const& ) {} - - virtual void sectionEnded( SectionStats const& sectionStats ) = 0; - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; - virtual void testRunEnded( TestRunStats const& testRunStats ) = 0; - - virtual void skipTest( TestCaseInfo const& testInfo ) = 0; - - // Default empty implementation provided - virtual void fatalErrorEncountered( StringRef name ); - - virtual bool isMulti() const; - }; - using IStreamingReporterPtr = std::unique_ptr<IStreamingReporter>; - - struct IReporterFactory { - virtual ~IReporterFactory(); - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const = 0; - virtual std::string getDescription() const = 0; - }; - using IReporterFactoryPtr = std::shared_ptr<IReporterFactory>; - - struct IReporterRegistry { - using FactoryMap = std::map<std::string, IReporterFactoryPtr>; - using Listeners = std::vector<IReporterFactoryPtr>; - - virtual ~IReporterRegistry(); - virtual IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const = 0; - virtual FactoryMap const& getFactories() const = 0; - virtual Listeners const& getListeners() const = 0; - }; - - void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ); - -} // end namespace Catch - -// end catch_interfaces_reporter.h -#include <algorithm> -#include <cstring> -#include <cfloat> -#include <cstdio> -#include <assert.h> -#include <memory> - -namespace Catch { - void prepareExpandedExpression(AssertionResult& result); - - // Returns double formatted as %.3f (format expected on output) - std::string getFormattedDuration( double duration ); - - template<typename DerivedT> - struct StreamingReporterBase : IStreamingReporter { - - StreamingReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); - } - - ReporterPreferences getPreferences() const override { - return m_reporterPrefs; - } - - static std::set<Verbosity> getSupportedVerbosities() { - return { Verbosity::Normal }; - } - - ~StreamingReporterBase() override = default; - - void noMatchingTestCases(std::string const&) override {} - - void testRunStarting(TestRunInfo const& _testRunInfo) override { - currentTestRunInfo = _testRunInfo; - } - void testGroupStarting(GroupInfo const& _groupInfo) override { - currentGroupInfo = _groupInfo; - } - - void testCaseStarting(TestCaseInfo const& _testInfo) override { - currentTestCaseInfo = _testInfo; - } - void sectionStarting(SectionInfo const& _sectionInfo) override { - m_sectionStack.push_back(_sectionInfo); - } - - void sectionEnded(SectionStats const& /* _sectionStats */) override { - m_sectionStack.pop_back(); - } - void testCaseEnded(TestCaseStats const& /* _testCaseStats */) override { - currentTestCaseInfo.reset(); - } - void testGroupEnded(TestGroupStats const& /* _testGroupStats */) override { - currentGroupInfo.reset(); - } - void testRunEnded(TestRunStats const& /* _testRunStats */) override { - currentTestCaseInfo.reset(); - currentGroupInfo.reset(); - currentTestRunInfo.reset(); - } - - void skipTest(TestCaseInfo const&) override { - // Don't do anything with this by default. - // It can optionally be overridden in the derived class. - } - - IConfigPtr m_config; - std::ostream& stream; - - LazyStat<TestRunInfo> currentTestRunInfo; - LazyStat<GroupInfo> currentGroupInfo; - LazyStat<TestCaseInfo> currentTestCaseInfo; - - std::vector<SectionInfo> m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template<typename DerivedT> - struct CumulativeReporterBase : IStreamingReporter { - template<typename T, typename ChildNodeT> - struct Node { - explicit Node( T const& _value ) : value( _value ) {} - virtual ~Node() {} - - using ChildNodes = std::vector<std::shared_ptr<ChildNodeT>>; - T value; - ChildNodes children; - }; - struct SectionNode { - explicit SectionNode(SectionStats const& _stats) : stats(_stats) {} - virtual ~SectionNode() = default; - - bool operator == (SectionNode const& other) const { - return stats.sectionInfo.lineInfo == other.stats.sectionInfo.lineInfo; - } - bool operator == (std::shared_ptr<SectionNode> const& other) const { - return operator==(*other); - } - - SectionStats stats; - using ChildSections = std::vector<std::shared_ptr<SectionNode>>; - using Assertions = std::vector<AssertionStats>; - ChildSections childSections; - Assertions assertions; - std::string stdOut; - std::string stdErr; - }; - - struct BySectionInfo { - BySectionInfo( SectionInfo const& other ) : m_other( other ) {} - BySectionInfo( BySectionInfo const& other ) : m_other( other.m_other ) {} - bool operator() (std::shared_ptr<SectionNode> const& node) const { - return ((node->stats.sectionInfo.name == m_other.name) && - (node->stats.sectionInfo.lineInfo == m_other.lineInfo)); - } - void operator=(BySectionInfo const&) = delete; - - private: - SectionInfo const& m_other; - }; - - using TestCaseNode = Node<TestCaseStats, SectionNode>; - using TestGroupNode = Node<TestGroupStats, TestCaseNode>; - using TestRunNode = Node<TestRunStats, TestGroupNode>; - - CumulativeReporterBase( ReporterConfig const& _config ) - : m_config( _config.fullConfig() ), - stream( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = false; - CATCH_ENFORCE( DerivedT::getSupportedVerbosities().count( m_config->verbosity() ), "Verbosity level not supported by this reporter" ); - } - ~CumulativeReporterBase() override = default; - - ReporterPreferences getPreferences() const override { - return m_reporterPrefs; - } - - static std::set<Verbosity> getSupportedVerbosities() { - return { Verbosity::Normal }; - } - - void testRunStarting( TestRunInfo const& ) override {} - void testGroupStarting( GroupInfo const& ) override {} - - void testCaseStarting( TestCaseInfo const& ) override {} - - void sectionStarting( SectionInfo const& sectionInfo ) override { - SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); - std::shared_ptr<SectionNode> node; - if( m_sectionStack.empty() ) { - if( !m_rootSection ) - m_rootSection = std::make_shared<SectionNode>( incompleteStats ); - node = m_rootSection; - } - else { - SectionNode& parentNode = *m_sectionStack.back(); - auto it = - std::find_if( parentNode.childSections.begin(), - parentNode.childSections.end(), - BySectionInfo( sectionInfo ) ); - if( it == parentNode.childSections.end() ) { - node = std::make_shared<SectionNode>( incompleteStats ); - parentNode.childSections.push_back( node ); - } - else - node = *it; - } - m_sectionStack.push_back( node ); - m_deepestSection = std::move(node); - } - - void assertionStarting(AssertionInfo const&) override {} - - bool assertionEnded(AssertionStats const& assertionStats) override { - assert(!m_sectionStack.empty()); - // AssertionResult holds a pointer to a temporary DecomposedExpression, - // which getExpandedExpression() calls to build the expression string. - // Our section stack copy of the assertionResult will likely outlive the - // temporary, so it must be expanded or discarded now to avoid calling - // a destroyed object later. - prepareExpandedExpression(const_cast<AssertionResult&>( assertionStats.assertionResult ) ); - SectionNode& sectionNode = *m_sectionStack.back(); - sectionNode.assertions.push_back(assertionStats); - return true; - } - void sectionEnded(SectionStats const& sectionStats) override { - assert(!m_sectionStack.empty()); - SectionNode& node = *m_sectionStack.back(); - node.stats = sectionStats; - m_sectionStack.pop_back(); - } - void testCaseEnded(TestCaseStats const& testCaseStats) override { - auto node = std::make_shared<TestCaseNode>(testCaseStats); - assert(m_sectionStack.size() == 0); - node->children.push_back(m_rootSection); - m_testCases.push_back(node); - m_rootSection.reset(); - - assert(m_deepestSection); - m_deepestSection->stdOut = testCaseStats.stdOut; - m_deepestSection->stdErr = testCaseStats.stdErr; - } - void testGroupEnded(TestGroupStats const& testGroupStats) override { - auto node = std::make_shared<TestGroupNode>(testGroupStats); - node->children.swap(m_testCases); - m_testGroups.push_back(node); - } - void testRunEnded(TestRunStats const& testRunStats) override { - auto node = std::make_shared<TestRunNode>(testRunStats); - node->children.swap(m_testGroups); - m_testRuns.push_back(node); - testRunEndedCumulative(); - } - virtual void testRunEndedCumulative() = 0; - - void skipTest(TestCaseInfo const&) override {} - - IConfigPtr m_config; - std::ostream& stream; - std::vector<AssertionStats> m_assertions; - std::vector<std::vector<std::shared_ptr<SectionNode>>> m_sections; - std::vector<std::shared_ptr<TestCaseNode>> m_testCases; - std::vector<std::shared_ptr<TestGroupNode>> m_testGroups; - - std::vector<std::shared_ptr<TestRunNode>> m_testRuns; - - std::shared_ptr<SectionNode> m_rootSection; - std::shared_ptr<SectionNode> m_deepestSection; - std::vector<std::shared_ptr<SectionNode>> m_sectionStack; - ReporterPreferences m_reporterPrefs; - }; - - template<char C> - char const* getLineOfChars() { - static char line[CATCH_CONFIG_CONSOLE_WIDTH] = {0}; - if( !*line ) { - std::memset( line, C, CATCH_CONFIG_CONSOLE_WIDTH-1 ); - line[CATCH_CONFIG_CONSOLE_WIDTH-1] = 0; - } - return line; - } - - struct TestEventListenerBase : StreamingReporterBase<TestEventListenerBase> { - TestEventListenerBase( ReporterConfig const& _config ); - - void assertionStarting(AssertionInfo const&) override; - bool assertionEnded(AssertionStats const&) override; - }; - -} // end namespace Catch - -// end catch_reporter_bases.hpp -// start catch_console_colour.h - -namespace Catch { - - struct Colour { - enum Code { - None = 0, - - White, - Red, - Green, - Blue, - Cyan, - Yellow, - Grey, - - Bright = 0x10, - - BrightRed = Bright | Red, - BrightGreen = Bright | Green, - LightGrey = Bright | Grey, - BrightWhite = Bright | White, - - // By intention - FileName = LightGrey, - Warning = Yellow, - ResultError = BrightRed, - ResultSuccess = BrightGreen, - ResultExpectedFailure = Warning, - - Error = BrightRed, - Success = Green, - - OriginalExpression = Cyan, - ReconstructedExpression = Yellow, - - SecondaryText = LightGrey, - Headers = White - }; - - // Use constructed object for RAII guard - Colour( Code _colourCode ); - Colour( Colour&& other ) noexcept; - Colour& operator=( Colour&& other ) noexcept; - ~Colour(); - - // Use static method for one-shot changes - static void use( Code _colourCode ); - - private: - bool m_moved = false; - }; - - std::ostream& operator << ( std::ostream& os, Colour const& ); - -} // end namespace Catch - -// end catch_console_colour.h -// start catch_reporter_registrars.hpp - - -namespace Catch { - - template<typename T> - class ReporterRegistrar { - - class ReporterFactory : public IReporterFactory { - - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return std::unique_ptr<T>( new T( config ) ); - } - - virtual std::string getDescription() const override { - return T::getDescription(); - } - }; - - public: - - ReporterRegistrar( std::string const& name ) { - getMutableRegistryHub().registerReporter( name, std::make_shared<ReporterFactory>() ); - } - }; - - template<typename T> - class ListenerRegistrar { - - class ListenerFactory : public IReporterFactory { - - virtual IStreamingReporterPtr create( ReporterConfig const& config ) const override { - return std::unique_ptr<T>( new T( config ) ); - } - virtual std::string getDescription() const override { - return std::string(); - } - }; - - public: - - ListenerRegistrar() { - getMutableRegistryHub().registerListener( std::make_shared<ListenerFactory>() ); - } - }; -} - -#if !defined(CATCH_CONFIG_DISABLE) - -#define CATCH_REGISTER_REPORTER( name, reporterType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ReporterRegistrar<reporterType> catch_internal_RegistrarFor##reporterType( name ); } \ - CATCH_INTERNAL_UNSUPPRESS_GLOBALS_WARNINGS - -#define CATCH_REGISTER_LISTENER( listenerType ) \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::ListenerRegistrar<listenerType> catch_internal_RegistrarFor##listenerType; } \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#else // CATCH_CONFIG_DISABLE - -#define CATCH_REGISTER_REPORTER(name, reporterType) -#define CATCH_REGISTER_LISTENER(listenerType) - -#endif // CATCH_CONFIG_DISABLE - -// end catch_reporter_registrars.hpp -// end catch_external_interfaces.h -#endif - -#ifdef CATCH_IMPL -// start catch_impl.hpp - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#endif - -// Keep these here for external reporters -// start catch_test_case_tracker.h - -#include <string> -#include <vector> -#include <memory> - -namespace Catch { -namespace TestCaseTracking { - - struct NameAndLocation { - std::string name; - SourceLineInfo location; - - NameAndLocation( std::string const& _name, SourceLineInfo const& _location ); - }; - - struct ITracker; - - using ITrackerPtr = std::shared_ptr<ITracker>; - - struct ITracker { - virtual ~ITracker(); - - // static queries - virtual NameAndLocation const& nameAndLocation() const = 0; - - // dynamic queries - virtual bool isComplete() const = 0; // Successfully completed or failed - virtual bool isSuccessfullyCompleted() const = 0; - virtual bool isOpen() const = 0; // Started but not complete - virtual bool hasChildren() const = 0; - - virtual ITracker& parent() = 0; - - // actions - virtual void close() = 0; // Successfully complete - virtual void fail() = 0; - virtual void markAsNeedingAnotherRun() = 0; - - virtual void addChild( ITrackerPtr const& child ) = 0; - virtual ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) = 0; - virtual void openChild() = 0; - - // Debug/ checking - virtual bool isSectionTracker() const = 0; - virtual bool isIndexTracker() const = 0; - }; - - class TrackerContext { - - enum RunState { - NotStarted, - Executing, - CompletedCycle - }; - - ITrackerPtr m_rootTracker; - ITracker* m_currentTracker = nullptr; - RunState m_runState = NotStarted; - - public: - - static TrackerContext& instance(); - - ITracker& startRun(); - void endRun(); - - void startCycle(); - void completeCycle(); - - bool completedCycle() const; - ITracker& currentTracker(); - void setCurrentTracker( ITracker* tracker ); - }; - - class TrackerBase : public ITracker { - protected: - enum CycleState { - NotStarted, - Executing, - ExecutingChildren, - NeedsAnotherRun, - CompletedSuccessfully, - Failed - }; - - class TrackerHasName { - NameAndLocation m_nameAndLocation; - public: - TrackerHasName( NameAndLocation const& nameAndLocation ); - bool operator ()( ITrackerPtr const& tracker ) const; - }; - - using Children = std::vector<ITrackerPtr>; - NameAndLocation m_nameAndLocation; - TrackerContext& m_ctx; - ITracker* m_parent; - Children m_children; - CycleState m_runState = NotStarted; - - public: - TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - - NameAndLocation const& nameAndLocation() const override; - bool isComplete() const override; - bool isSuccessfullyCompleted() const override; - bool isOpen() const override; - bool hasChildren() const override; - - void addChild( ITrackerPtr const& child ) override; - - ITrackerPtr findChild( NameAndLocation const& nameAndLocation ) override; - ITracker& parent() override; - - void openChild() override; - - bool isSectionTracker() const override; - bool isIndexTracker() const override; - - void open(); - - void close() override; - void fail() override; - void markAsNeedingAnotherRun() override; - - private: - void moveToParent(); - void moveToThis(); - }; - - class SectionTracker : public TrackerBase { - std::vector<std::string> m_filters; - public: - SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ); - - bool isSectionTracker() const override; - - static SectionTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ); - - void tryOpen(); - - void addInitialFilters( std::vector<std::string> const& filters ); - void addNextFilters( std::vector<std::string> const& filters ); - }; - - class IndexTracker : public TrackerBase { - int m_size; - int m_index = -1; - public: - IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ); - - bool isIndexTracker() const override; - void close() override; - - static IndexTracker& acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ); - - int index() const; - - void moveNext(); - }; - -} // namespace TestCaseTracking - -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; - -} // namespace Catch - -// end catch_test_case_tracker.h - -// start catch_leak_detector.h - -namespace Catch { - - struct LeakDetector { - LeakDetector(); - }; - -} -// end catch_leak_detector.h -// Cpp files will be included in the single-header file here -// start catch_approx.cpp - -#include <cmath> -#include <limits> - -namespace { - -// Performs equivalent check of std::fabs(lhs - rhs) <= margin -// But without the subtraction to allow for INFINITY in comparison -bool marginComparison(double lhs, double rhs, double margin) { - return (lhs + margin >= rhs) && (rhs + margin >= lhs); -} - -} - -namespace Catch { -namespace Detail { - - Approx::Approx ( double value ) - : m_epsilon( std::numeric_limits<float>::epsilon()*100 ), - m_margin( 0.0 ), - m_scale( 0.0 ), - m_value( value ) - {} - - Approx Approx::custom() { - return Approx( 0 ); - } - - std::string Approx::toString() const { - std::ostringstream oss; - oss << "Approx( " << ::Catch::Detail::stringify( m_value ) << " )"; - return oss.str(); - } - - bool Approx::equalityComparisonImpl(const double other) const { - // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value - // Thanks to Richard Harris for his help refining the scaled margin value - return marginComparison(m_value, other, m_margin) || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(m_value))); - } - -} // end namespace Detail - -std::string StringMaker<Catch::Detail::Approx>::convert(Catch::Detail::Approx const& value) { - return value.toString(); -} - -} // end namespace Catch -// end catch_approx.cpp -// start catch_assertionhandler.cpp - -// start catch_context.h - -#include <memory> - -namespace Catch { - - struct IResultCapture; - struct IRunner; - struct IConfig; - - using IConfigPtr = std::shared_ptr<IConfig const>; - - struct IContext - { - virtual ~IContext(); - - virtual IResultCapture* getResultCapture() = 0; - virtual IRunner* getRunner() = 0; - virtual IConfigPtr getConfig() const = 0; - }; - - struct IMutableContext : IContext - { - virtual ~IMutableContext(); - virtual void setResultCapture( IResultCapture* resultCapture ) = 0; - virtual void setRunner( IRunner* runner ) = 0; - virtual void setConfig( IConfigPtr const& config ) = 0; - }; - - IContext& getCurrentContext(); - IMutableContext& getCurrentMutableContext(); - void cleanUpContext(); -} - -// end catch_context.h -#include <cassert> - -namespace Catch { - - auto operator <<( std::ostream& os, ITransientExpression const& expr ) -> std::ostream& { - expr.streamReconstructedExpression( os ); - return os; - } - - LazyExpression::LazyExpression( bool isNegated ) - : m_isNegated( isNegated ) - {} - - LazyExpression::LazyExpression( LazyExpression const& other ) : m_isNegated( other.m_isNegated ) {} - - LazyExpression::operator bool() const { - return m_transientExpression != nullptr; - } - - auto operator << ( std::ostream& os, LazyExpression const& lazyExpr ) -> std::ostream& { - if( lazyExpr.m_isNegated ) - os << "!"; - - if( lazyExpr ) { - if( lazyExpr.m_isNegated && lazyExpr.m_transientExpression->isBinaryExpression() ) - os << "(" << *lazyExpr.m_transientExpression << ")"; - else - os << *lazyExpr.m_transientExpression; - } - else { - os << "{** error - unchecked empty expression requested **}"; - } - return os; - } - - AssertionHandler::AssertionHandler - ( StringRef macroName, - SourceLineInfo const& lineInfo, - StringRef capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo{ macroName, lineInfo, capturedExpression, resultDisposition } - { - getCurrentContext().getResultCapture()->assertionStarting( m_assertionInfo ); - } - AssertionHandler::~AssertionHandler() { - if ( m_inExceptionGuard ) { - handle( ResultWas::ThrewException, "Exception translation was disabled by CATCH_CONFIG_FAST_COMPILE" ); - getCurrentContext().getResultCapture()->exceptionEarlyReported(); - } - } - - void AssertionHandler::handle( ITransientExpression const& expr ) { - - bool negated = isFalseTest( m_assertionInfo.resultDisposition ); - bool result = expr.getResult() != negated; - - handle( result ? ResultWas::Ok : ResultWas::ExpressionFailed, &expr, negated ); - } - void AssertionHandler::handle( ResultWas::OfType resultType ) { - handle( resultType, nullptr, false ); - } - void AssertionHandler::handle( ResultWas::OfType resultType, StringRef const& message ) { - AssertionResultData data( resultType, LazyExpression( false ) ); - data.message = message; - handle( data, nullptr ); - } - void AssertionHandler::handle( ResultWas::OfType resultType, ITransientExpression const* expr, bool negated ) { - AssertionResultData data( resultType, LazyExpression( negated ) ); - handle( data, expr ); - } - void AssertionHandler::handle( AssertionResultData const& resultData, ITransientExpression const* expr ) { - - getResultCapture().assertionRun(); - - AssertionResult assertionResult{ m_assertionInfo, resultData }; - assertionResult.m_resultData.lazyExpression.m_transientExpression = expr; - - getResultCapture().assertionEnded( assertionResult ); - - if( !assertionResult.isOk() ) { - m_shouldDebugBreak = getCurrentContext().getConfig()->shouldDebugBreak(); - m_shouldThrow = - getCurrentContext().getRunner()->aborting() || - (m_assertionInfo.resultDisposition & ResultDisposition::Normal); - } - } - - auto AssertionHandler::allowThrows() const -> bool { - return getCurrentContext().getConfig()->allowThrows(); - } - - auto AssertionHandler::shouldDebugBreak() const -> bool { - return m_shouldDebugBreak; - } - void AssertionHandler::reactWithDebugBreak() const { - if (m_shouldDebugBreak) { - /////////////////////////////////////////////////////////////////// - // To inspect the state during test, you need to go one level up the callstack - // To go back to the test and change execution, jump over the reactWithoutDebugBreak() call - /////////////////////////////////////////////////////////////////// - CATCH_BREAK_INTO_DEBUGGER(); - } - reactWithoutDebugBreak(); - } - void AssertionHandler::reactWithoutDebugBreak() const { - if( m_shouldThrow ) - throw Catch::TestFailureException(); - } - - void AssertionHandler::useActiveException() { - handle( ResultWas::ThrewException, Catch::translateActiveException() ); - } - - void AssertionHandler::setExceptionGuard() { - assert( m_inExceptionGuard == false ); - m_inExceptionGuard = true; - } - void AssertionHandler::unsetExceptionGuard() { - assert( m_inExceptionGuard == true ); - m_inExceptionGuard = false; - } - - // This is the overload that takes a string and infers the Equals matcher from it - // The more general overload, that takes any string matcher, is in catch_capture_matchers.cpp - void handleExceptionMatchExpr( AssertionHandler& handler, std::string const& str, StringRef matcherString ) { - handleExceptionMatchExpr( handler, Matchers::Equals( str ), matcherString ); - } - -} // namespace Catch -// end catch_assertionhandler.cpp -// start catch_assertionresult.cpp - -namespace Catch { - AssertionResultData::AssertionResultData(ResultWas::OfType _resultType, LazyExpression const & _lazyExpression): - lazyExpression(_lazyExpression), - resultType(_resultType) {} - - std::string AssertionResultData::reconstructExpression() const { - - if( reconstructedExpression.empty() ) { - if( lazyExpression ) { - // !TBD Use stringstream for now, but rework above to pass stream in - std::ostringstream oss; - oss << lazyExpression; - reconstructedExpression = oss.str(); - } - } - return reconstructedExpression; - } - - AssertionResult::AssertionResult( AssertionInfo const& info, AssertionResultData const& data ) - : m_info( info ), - m_resultData( data ) - {} - - // Result was a success - bool AssertionResult::succeeded() const { - return Catch::isOk( m_resultData.resultType ); - } - - // Result was a success, or failure is suppressed - bool AssertionResult::isOk() const { - return Catch::isOk( m_resultData.resultType ) || shouldSuppressFailure( m_info.resultDisposition ); - } - - ResultWas::OfType AssertionResult::getResultType() const { - return m_resultData.resultType; - } - - bool AssertionResult::hasExpression() const { - return m_info.capturedExpression[0] != 0; - } - - bool AssertionResult::hasMessage() const { - return !m_resultData.message.empty(); - } - - std::string AssertionResult::getExpression() const { - if( isFalseTest( m_info.resultDisposition ) ) - return "!(" + std::string(m_info.capturedExpression) + ")"; - else - return m_info.capturedExpression; - } - - std::string AssertionResult::getExpressionInMacro() const { - std::string expr; - if( m_info.macroName[0] == 0 ) - expr = m_info.capturedExpression; - else { - expr.reserve( m_info.macroName.size() + m_info.capturedExpression.size() + 4 ); - expr += m_info.macroName; - expr += "( "; - expr += m_info.capturedExpression; - expr += " )"; - } - return expr; - } - - bool AssertionResult::hasExpandedExpression() const { - return hasExpression() && getExpandedExpression() != getExpression(); - } - - std::string AssertionResult::getExpandedExpression() const { - std::string expr = m_resultData.reconstructExpression(); - return expr.empty() - ? getExpression() - : expr; - } - - std::string AssertionResult::getMessage() const { - return m_resultData.message; - } - SourceLineInfo AssertionResult::getSourceInfo() const { - return m_info.lineInfo; - } - - std::string AssertionResult::getTestMacroName() const { - return m_info.macroName; - } - -} // end namespace Catch -// end catch_assertionresult.cpp -// start catch_benchmark.cpp - -namespace Catch { - - auto BenchmarkLooper::getResolution() -> uint64_t { - return getEstimatedClockResolution() * getCurrentContext().getConfig()->benchmarkResolutionMultiple(); - } - - void BenchmarkLooper::reportStart() { - getResultCapture().benchmarkStarting( { m_name } ); - } - auto BenchmarkLooper::needsMoreIterations() -> bool { - auto elapsed = m_timer.getElapsedNanoseconds(); - - // Exponentially increasing iterations until we're confident in our timer resolution - if( elapsed < m_resolution ) { - m_iterationsToRun *= 10; - return true; - } - - getResultCapture().benchmarkEnded( { { m_name }, m_count, elapsed } ); - return false; - } - -} // end namespace Catch -// end catch_benchmark.cpp -// start catch_capture_matchers.cpp - -namespace Catch { - - using StringMatcher = Matchers::Impl::MatcherBase<std::string>; - - // This is the general overload that takes a any string matcher - // There is another overload, in catch_assertinhandler.h/.cpp, that only takes a string and infers - // the Equals matcher (so the header does not mention matchers) - void handleExceptionMatchExpr( AssertionHandler& handler, StringMatcher const& matcher, StringRef matcherString ) { - std::string exceptionMessage = Catch::translateActiveException(); - MatchExpr<std::string, StringMatcher const&> expr( exceptionMessage, matcher, matcherString ); - handler.handle( expr ); - } - -} // namespace Catch -// end catch_capture_matchers.cpp -// start catch_commandline.cpp - -// start catch_commandline.h - -// start catch_clara.h - -// Use Catch's value for console width (store Clara's off to the side, if present) -#ifdef CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#undef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#endif -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CONFIG_CONSOLE_WIDTH-1 - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wweak-vtables" -#pragma clang diagnostic ignored "-Wexit-time-destructors" -#pragma clang diagnostic ignored "-Wshadow" -#endif - -// start clara.hpp -// v1.0-develop.2 -// See https://github.com/philsquared/Clara - - -#ifndef CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_CONFIG_CONSOLE_WIDTH 80 -#endif - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_CLARA_CONFIG_CONSOLE_WIDTH -#endif - -// ----------- #included from clara_textflow.hpp ----------- - -// TextFlowCpp -// -// A single-header library for wrapping and laying out basic text, by Phil Nash -// -// This work is licensed under the BSD 2-Clause license. -// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause -// -// This project is hosted at https://github.com/philsquared/textflowcpp - - -#include <cassert> -#include <ostream> -#include <sstream> -#include <vector> - -#ifndef CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80 -#endif - -namespace Catch { namespace clara { namespace TextFlow { - - inline auto isWhitespace( char c ) -> bool { - static std::string chars = " \t\n\r"; - return chars.find( c ) != std::string::npos; - } - inline auto isBreakableBefore( char c ) -> bool { - static std::string chars = "[({<|"; - return chars.find( c ) != std::string::npos; - } - inline auto isBreakableAfter( char c ) -> bool { - static std::string chars = "])}>.,:;*+-=&/\\"; - return chars.find( c ) != std::string::npos; - } - - class Columns; - - class Column { - std::vector<std::string> m_strings; - size_t m_width = CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH; - size_t m_indent = 0; - size_t m_initialIndent = std::string::npos; - - public: - class iterator { - friend Column; - - Column const& m_column; - size_t m_stringIndex = 0; - size_t m_pos = 0; - - size_t m_len = 0; - size_t m_end = 0; - bool m_suffix = false; - - iterator( Column const& column, size_t stringIndex ) - : m_column( column ), - m_stringIndex( stringIndex ) - {} - - auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; } - - auto isBoundary( size_t at ) const -> bool { - assert( at > 0 ); - assert( at <= line().size() ); - - return at == line().size() || - ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) || - isBreakableBefore( line()[at] ) || - isBreakableAfter( line()[at-1] ); - } - - void calcLength() { - assert( m_stringIndex < m_column.m_strings.size() ); - - m_suffix = false; - auto width = m_column.m_width-indent(); - m_end = m_pos; - while( m_end < line().size() && line()[m_end] != '\n' ) - ++m_end; - - if( m_end < m_pos + width ) { - m_len = m_end - m_pos; - } - else { - size_t len = width; - while (len > 0 && !isBoundary(m_pos + len)) - --len; - while (len > 0 && isWhitespace( line()[m_pos + len - 1] )) - --len; - - if (len > 0) { - m_len = len; - } else { - m_suffix = true; - m_len = width - 1; - } - } - } - - auto indent() const -> size_t { - auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos; - return initial == std::string::npos ? m_column.m_indent : initial; - } - - auto addIndentAndSuffix(std::string const &plain) const -> std::string { - return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain); - } - - public: - explicit iterator( Column const& column ) : m_column( column ) { - assert( m_column.m_width > m_column.m_indent ); - assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent ); - calcLength(); - if( m_len == 0 ) - m_stringIndex++; // Empty string - } - - auto operator *() const -> std::string { - assert( m_stringIndex < m_column.m_strings.size() ); - assert( m_pos <= m_end ); - if( m_pos + m_column.m_width < m_end ) - return addIndentAndSuffix(line().substr(m_pos, m_len)); - else - return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos)); - } - - auto operator ++() -> iterator& { - m_pos += m_len; - if( m_pos < line().size() && line()[m_pos] == '\n' ) - m_pos += 1; - else - while( m_pos < line().size() && isWhitespace( line()[m_pos] ) ) - ++m_pos; - - if( m_pos == line().size() ) { - m_pos = 0; - ++m_stringIndex; - } - if( m_stringIndex < m_column.m_strings.size() ) - calcLength(); - return *this; - } - auto operator ++(int) -> iterator { - iterator prev( *this ); - operator++(); - return prev; - } - - auto operator ==( iterator const& other ) const -> bool { - return - m_pos == other.m_pos && - m_stringIndex == other.m_stringIndex && - &m_column == &other.m_column; - } - auto operator !=( iterator const& other ) const -> bool { - return !operator==( other ); - } - }; - using const_iterator = iterator; - - explicit Column( std::string const& text ) { m_strings.push_back( text ); } - - auto width( size_t newWidth ) -> Column& { - assert( newWidth > 0 ); - m_width = newWidth; - return *this; - } - auto indent( size_t newIndent ) -> Column& { - m_indent = newIndent; - return *this; - } - auto initialIndent( size_t newIndent ) -> Column& { - m_initialIndent = newIndent; - return *this; - } - - auto width() const -> size_t { return m_width; } - auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return { *this, m_strings.size() }; } - - inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) { - bool first = true; - for( auto line : col ) { - if( first ) - first = false; - else - os << "\n"; - os << line; - } - return os; - } - - auto operator + ( Column const& other ) -> Columns; - - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; - - class Spacer : public Column { - - public: - explicit Spacer( size_t spaceWidth ) : Column( "" ) { - width( spaceWidth ); - } - }; - - class Columns { - std::vector<Column> m_columns; - - public: - - class iterator { - friend Columns; - struct EndTag {}; - - std::vector<Column> const& m_columns; - std::vector<Column::iterator> m_iterators; - size_t m_activeIterators; - - iterator( Columns const& columns, EndTag ) - : m_columns( columns.m_columns ), - m_activeIterators( 0 ) - { - m_iterators.reserve( m_columns.size() ); - - for( auto const& col : m_columns ) - m_iterators.push_back( col.end() ); - } - - public: - explicit iterator( Columns const& columns ) - : m_columns( columns.m_columns ), - m_activeIterators( m_columns.size() ) - { - m_iterators.reserve( m_columns.size() ); - - for( auto const& col : m_columns ) - m_iterators.push_back( col.begin() ); - } - - auto operator ==( iterator const& other ) const -> bool { - return m_iterators == other.m_iterators; - } - auto operator !=( iterator const& other ) const -> bool { - return m_iterators != other.m_iterators; - } - auto operator *() const -> std::string { - std::string row, padding; - - for( size_t i = 0; i < m_columns.size(); ++i ) { - auto width = m_columns[i].width(); - if( m_iterators[i] != m_columns[i].end() ) { - std::string col = *m_iterators[i]; - row += padding + col; - if( col.size() < width ) - padding = std::string( width - col.size(), ' ' ); - else - padding = ""; - } - else { - padding += std::string( width, ' ' ); - } - } - return row; - } - auto operator ++() -> iterator& { - for( size_t i = 0; i < m_columns.size(); ++i ) { - if (m_iterators[i] != m_columns[i].end()) - ++m_iterators[i]; - } - return *this; - } - auto operator ++(int) -> iterator { - iterator prev( *this ); - operator++(); - return prev; - } - }; - using const_iterator = iterator; - - auto begin() const -> iterator { return iterator( *this ); } - auto end() const -> iterator { return { *this, iterator::EndTag() }; } - - auto operator += ( Column const& col ) -> Columns& { - m_columns.push_back( col ); - return *this; - } - auto operator + ( Column const& col ) -> Columns { - Columns combined = *this; - combined += col; - return combined; - } - - inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) { - - bool first = true; - for( auto line : cols ) { - if( first ) - first = false; - else - os << "\n"; - os << line; - } - return os; - } - - auto toString() const -> std::string { - std::ostringstream oss; - oss << *this; - return oss.str(); - } - }; - - inline auto Column::operator + ( Column const& other ) -> Columns { - Columns cols; - cols += *this; - cols += other; - return cols; - } -}}} // namespace Catch::clara::TextFlow - -// ----------- end of #include from clara_textflow.hpp ----------- -// ........... back in clara.hpp - -#include <memory> -#include <set> -#include <algorithm> - -#if !defined(CATCH_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) ) -#define CATCH_PLATFORM_WINDOWS -#endif - -namespace Catch { namespace clara { -namespace detail { - - // Traits for extracting arg and return type of lambdas (for single argument lambdas) - template<typename L> - struct UnaryLambdaTraits : UnaryLambdaTraits<decltype( &L::operator() )> {}; - - template<typename ClassT, typename ReturnT, typename... Args> - struct UnaryLambdaTraits<ReturnT( ClassT::* )( Args... ) const> { - static const bool isValid = false; - }; - - template<typename ClassT, typename ReturnT, typename ArgT> - struct UnaryLambdaTraits<ReturnT( ClassT::* )( ArgT ) const> { - static const bool isValid = true; - using ArgType = typename std::remove_const<typename std::remove_reference<ArgT>::type>::type;; - using ReturnType = ReturnT; - }; - - class TokenStream; - - // Transport for raw args (copied from main args, or supplied via init list for testing) - class Args { - friend TokenStream; - std::string m_exeName; - std::vector<std::string> m_args; - - public: - Args( int argc, char *argv[] ) { - m_exeName = argv[0]; - for( int i = 1; i < argc; ++i ) - m_args.push_back( argv[i] ); - } - - Args( std::initializer_list<std::string> args ) - : m_exeName( *args.begin() ), - m_args( args.begin()+1, args.end() ) - {} - - auto exeName() const -> std::string { - return m_exeName; - } - }; - - // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string - // may encode an option + its argument if the : or = form is used - enum class TokenType { - Option, Argument - }; - struct Token { - TokenType type; - std::string token; - }; - - inline auto isOptPrefix( char c ) -> bool { - return c == '-' -#ifdef CATCH_PLATFORM_WINDOWS - || c == '/' -#endif - ; - } - - // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled - class TokenStream { - using Iterator = std::vector<std::string>::const_iterator; - Iterator it; - Iterator itEnd; - std::vector<Token> m_tokenBuffer; - - void loadBuffer() { - m_tokenBuffer.resize( 0 ); - - // Skip any empty strings - while( it != itEnd && it->empty() ) - ++it; - - if( it != itEnd ) { - auto const &next = *it; - if( isOptPrefix( next[0] ) ) { - auto delimiterPos = next.find_first_of( " :=" ); - if( delimiterPos != std::string::npos ) { - m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } ); - m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } ); - } else { - if( next[1] != '-' && next.size() > 2 ) { - std::string opt = "- "; - for( size_t i = 1; i < next.size(); ++i ) { - opt[1] = next[i]; - m_tokenBuffer.push_back( { TokenType::Option, opt } ); - } - } else { - m_tokenBuffer.push_back( { TokenType::Option, next } ); - } - } - } else { - m_tokenBuffer.push_back( { TokenType::Argument, next } ); - } - } - } - - public: - explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {} - - TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) { - loadBuffer(); - } - - explicit operator bool() const { - return !m_tokenBuffer.empty() || it != itEnd; - } - - auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); } - - auto operator*() const -> Token { - assert( !m_tokenBuffer.empty() ); - return m_tokenBuffer.front(); - } - - auto operator->() const -> Token const * { - assert( !m_tokenBuffer.empty() ); - return &m_tokenBuffer.front(); - } - - auto operator++() -> TokenStream & { - if( m_tokenBuffer.size() >= 2 ) { - m_tokenBuffer.erase( m_tokenBuffer.begin() ); - } else { - if( it != itEnd ) - ++it; - loadBuffer(); - } - return *this; - } - }; - - class ResultBase { - public: - enum Type { - Ok, LogicError, RuntimeError - }; - - protected: - ResultBase( Type type ) : m_type( type ) {} - virtual ~ResultBase() = default; - - virtual void enforceOk() const = 0; - - Type m_type; - }; - - template<typename T> - class ResultValueBase : public ResultBase { - public: - auto value() const -> T const & { - enforceOk(); - return m_value; - } - - protected: - ResultValueBase( Type type ) : ResultBase( type ) {} - - ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) { - if( m_type == ResultBase::Ok ) - new( &m_value ) T( other.m_value ); - } - - ResultValueBase( Type, T const &value ) : ResultBase( Ok ) { - new( &m_value ) T( value ); - } - - auto operator=( ResultValueBase const &other ) -> ResultValueBase & { - if( m_type == ResultBase::Ok ) - m_value.~T(); - ResultBase::operator=(other); - if( m_type == ResultBase::Ok ) - new( &m_value ) T( other.m_value ); - return *this; - } - - ~ResultValueBase() { - if( m_type == Ok ) - m_value.~T(); - } - - union { - T m_value; - }; - }; - - template<> - class ResultValueBase<void> : public ResultBase { - protected: - using ResultBase::ResultBase; - }; - - template<typename T = void> - class BasicResult : public ResultValueBase<T> { - public: - template<typename U> - explicit BasicResult( BasicResult<U> const &other ) - : ResultValueBase<T>( other.type() ), - m_errorMessage( other.errorMessage() ) - { - assert( type() != ResultBase::Ok ); - } - - template<typename U> - static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; } - static auto ok() -> BasicResult { return { ResultBase::Ok }; } - static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; } - static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; } - - explicit operator bool() const { return m_type == ResultBase::Ok; } - auto type() const -> ResultBase::Type { return m_type; } - auto errorMessage() const -> std::string { return m_errorMessage; } - - protected: - virtual void enforceOk() const { - // !TBD: If no exceptions, std::terminate here or something - switch( m_type ) { - case ResultBase::LogicError: - throw std::logic_error( m_errorMessage ); - case ResultBase::RuntimeError: - throw std::runtime_error( m_errorMessage ); - case ResultBase::Ok: - break; - } - } - - std::string m_errorMessage; // Only populated if resultType is an error - - BasicResult( ResultBase::Type type, std::string const &message ) - : ResultValueBase<T>(type), - m_errorMessage(message) - { - assert( m_type != ResultBase::Ok ); - } - - using ResultValueBase<T>::ResultValueBase; - using ResultBase::m_type; - }; - - enum class ParseResultType { - Matched, NoMatch, ShortCircuitAll, ShortCircuitSame - }; - - class ParseState { - public: - - ParseState( ParseResultType type, TokenStream const &remainingTokens ) - : m_type(type), - m_remainingTokens( remainingTokens ) - {} - - auto type() const -> ParseResultType { return m_type; } - auto remainingTokens() const -> TokenStream { return m_remainingTokens; } - - private: - ParseResultType m_type; - TokenStream m_remainingTokens; - }; - - using Result = BasicResult<void>; - using ParserResult = BasicResult<ParseResultType>; - using InternalParseResult = BasicResult<ParseState>; - - struct HelpColumns { - std::string left; - std::string right; - }; - - template<typename T> - inline auto convertInto( std::string const &source, T& target ) -> ParserResult { - std::stringstream ss; - ss << source; - ss >> target; - if( ss.fail() ) - return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" ); - else - return ParserResult::ok( ParseResultType::Matched ); - } - inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult { - target = source; - return ParserResult::ok( ParseResultType::Matched ); - } - inline auto convertInto( std::string const &source, bool &target ) -> ParserResult { - std::string srcLC = source; - std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast<char>( ::tolower(c) ); } ); - if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on") - target = true; - else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off") - target = false; - else - return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - } - - struct BoundRefBase { - BoundRefBase() = default; - BoundRefBase( BoundRefBase const & ) = delete; - BoundRefBase( BoundRefBase && ) = delete; - BoundRefBase &operator=( BoundRefBase const & ) = delete; - BoundRefBase &operator=( BoundRefBase && ) = delete; - - virtual ~BoundRefBase() = default; - - virtual auto isFlag() const -> bool = 0; - virtual auto isContainer() const -> bool { return false; } - virtual auto setValue( std::string const &arg ) -> ParserResult = 0; - virtual auto setFlag( bool flag ) -> ParserResult = 0; - }; - - struct BoundValueRefBase : BoundRefBase { - auto isFlag() const -> bool override { return false; } - - auto setFlag( bool ) -> ParserResult override { - return ParserResult::logicError( "Flags can only be set on boolean fields" ); - } - }; - - struct BoundFlagRefBase : BoundRefBase { - auto isFlag() const -> bool override { return true; } - - auto setValue( std::string const &arg ) -> ParserResult override { - bool flag; - auto result = convertInto( arg, flag ); - if( result ) - setFlag( flag ); - return result; - } - }; - - template<typename T> - struct BoundRef : BoundValueRefBase { - T &m_ref; - - explicit BoundRef( T &ref ) : m_ref( ref ) {} - - auto setValue( std::string const &arg ) -> ParserResult override { - return convertInto( arg, m_ref ); - } - }; - - template<typename T> - struct BoundRef<std::vector<T>> : BoundValueRefBase { - std::vector<T> &m_ref; - - explicit BoundRef( std::vector<T> &ref ) : m_ref( ref ) {} - - auto isContainer() const -> bool override { return true; } - - auto setValue( std::string const &arg ) -> ParserResult override { - T temp; - auto result = convertInto( arg, temp ); - if( result ) - m_ref.push_back( temp ); - return result; - } - }; - - struct BoundFlagRef : BoundFlagRefBase { - bool &m_ref; - - explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {} - - auto setFlag( bool flag ) -> ParserResult override { - m_ref = flag; - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - template<typename ReturnType> - struct LambdaInvoker { - static_assert( std::is_same<ReturnType, ParserResult>::value, "Lambda must return void or clara::ParserResult" ); - - template<typename L, typename ArgType> - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - return lambda( arg ); - } - }; - - template<> - struct LambdaInvoker<void> { - template<typename L, typename ArgType> - static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult { - lambda( arg ); - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - template<typename ArgType, typename L> - inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult { - ArgType temp; - auto result = convertInto( arg, temp ); - return !result - ? result - : LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( lambda, temp ); - }; - - template<typename L> - struct BoundLambda : BoundValueRefBase { - L m_lambda; - - static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); - explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {} - - auto setValue( std::string const &arg ) -> ParserResult override { - return invokeLambda<typename UnaryLambdaTraits<L>::ArgType>( m_lambda, arg ); - } - }; - - template<typename L> - struct BoundFlagLambda : BoundFlagRefBase { - L m_lambda; - - static_assert( UnaryLambdaTraits<L>::isValid, "Supplied lambda must take exactly one argument" ); - static_assert( std::is_same<typename UnaryLambdaTraits<L>::ArgType, bool>::value, "flags must be boolean" ); - - explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {} - - auto setFlag( bool flag ) -> ParserResult override { - return LambdaInvoker<typename UnaryLambdaTraits<L>::ReturnType>::invoke( m_lambda, flag ); - } - }; - - enum class Optionality { Optional, Required }; - - struct Parser; - - class ParserBase { - public: - virtual ~ParserBase() = default; - virtual auto validate() const -> Result { return Result::ok(); } - virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0; - virtual auto cardinality() const -> size_t { return 1; } - - auto parse( Args const &args ) const -> InternalParseResult { - return parse( args.exeName(), TokenStream( args ) ); - } - }; - - template<typename DerivedT> - class ComposableParserImpl : public ParserBase { - public: - template<typename T> - auto operator|( T const &other ) const -> Parser; - }; - - // Common code and state for Args and Opts - template<typename DerivedT> - class ParserRefImpl : public ComposableParserImpl<DerivedT> { - protected: - Optionality m_optionality = Optionality::Optional; - std::shared_ptr<BoundRefBase> m_ref; - std::string m_hint; - std::string m_description; - - explicit ParserRefImpl( std::shared_ptr<BoundRefBase> const &ref ) : m_ref( ref ) {} - - public: - template<typename T> - ParserRefImpl( T &ref, std::string const &hint ) - : m_ref( std::make_shared<BoundRef<T>>( ref ) ), - m_hint( hint ) - {} - - template<typename LambdaT> - ParserRefImpl( LambdaT const &ref, std::string const &hint ) - : m_ref( std::make_shared<BoundLambda<LambdaT>>( ref ) ), - m_hint(hint) - {} - - auto operator()( std::string const &description ) -> DerivedT & { - m_description = description; - return static_cast<DerivedT &>( *this ); - } - - auto optional() -> DerivedT & { - m_optionality = Optionality::Optional; - return static_cast<DerivedT &>( *this ); - }; - - auto required() -> DerivedT & { - m_optionality = Optionality::Required; - return static_cast<DerivedT &>( *this ); - }; - - auto isOptional() const -> bool { - return m_optionality == Optionality::Optional; - } - - auto cardinality() const -> size_t override { - if( m_ref->isContainer() ) - return 0; - else - return 1; - } - - auto hint() const -> std::string { return m_hint; } - }; - - class ExeName : public ComposableParserImpl<ExeName> { - std::shared_ptr<std::string> m_name; - std::shared_ptr<BoundRefBase> m_ref; - - template<typename LambdaT> - static auto makeRef(LambdaT const &lambda) -> std::shared_ptr<BoundRefBase> { - return std::make_shared<BoundLambda<LambdaT>>( lambda) ; - } - - public: - ExeName() : m_name( std::make_shared<std::string>( "<executable>" ) ) {} - - explicit ExeName( std::string &ref ) : ExeName() { - m_ref = std::make_shared<BoundRef<std::string>>( ref ); - } - - template<typename LambdaT> - explicit ExeName( LambdaT const& lambda ) : ExeName() { - m_ref = std::make_shared<BoundLambda<LambdaT>>( lambda ); - } - - // The exe name is not parsed out of the normal tokens, but is handled specially - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); - } - - auto name() const -> std::string { return *m_name; } - auto set( std::string const& newName ) -> ParserResult { - - auto lastSlash = newName.find_last_of( "\\/" ); - auto filename = ( lastSlash == std::string::npos ) - ? newName - : newName.substr( lastSlash+1 ); - - *m_name = filename; - if( m_ref ) - return m_ref->setValue( filename ); - else - return ParserResult::ok( ParseResultType::Matched ); - } - }; - - class Arg : public ParserRefImpl<Arg> { - public: - using ParserRefImpl::ParserRefImpl; - - auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - - auto remainingTokens = tokens; - auto const &token = *remainingTokens; - if( token.type != TokenType::Argument ) - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - - auto result = m_ref->setValue( remainingTokens->token ); - if( !result ) - return InternalParseResult( result ); - else - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); - } - }; - - inline auto normaliseOpt( std::string const &optName ) -> std::string { -#ifdef CATCH_PLATFORM_WINDOWS - if( optName[0] == '/' ) - return "-" + optName.substr( 1 ); - else -#endif - return optName; - } - - class Opt : public ParserRefImpl<Opt> { - protected: - std::vector<std::string> m_optNames; - - public: - template<typename LambdaT> - explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared<BoundFlagLambda<LambdaT>>( ref ) ) {} - - explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared<BoundFlagRef>( ref ) ) {} - - template<typename LambdaT> - Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - - template<typename T> - Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {} - - auto operator[]( std::string const &optName ) -> Opt & { - m_optNames.push_back( optName ); - return *this; - } - - auto getHelpColumns() const -> std::vector<HelpColumns> { - std::ostringstream oss; - bool first = true; - for( auto const &opt : m_optNames ) { - if (first) - first = false; - else - oss << ", "; - oss << opt; - } - if( !m_hint.empty() ) - oss << " <" << m_hint << ">"; - return { { oss.str(), m_description } }; - } - - auto isMatch( std::string const &optToken ) const -> bool { - auto normalisedToken = normaliseOpt( optToken ); - for( auto const &name : m_optNames ) { - if( normaliseOpt( name ) == normalisedToken ) - return true; - } - return false; - } - - using ParserBase::parse; - - auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override { - auto validationResult = validate(); - if( !validationResult ) - return InternalParseResult( validationResult ); - - auto remainingTokens = tokens; - if( remainingTokens && remainingTokens->type == TokenType::Option ) { - auto const &token = *remainingTokens; - if( isMatch(token.token ) ) { - if( m_ref->isFlag() ) { - auto result = m_ref->setFlag( true ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } else { - ++remainingTokens; - if( !remainingTokens ) - return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto const &argToken = *remainingTokens; - if( argToken.type != TokenType::Argument ) - return InternalParseResult::runtimeError( "Expected argument following " + token.token ); - auto result = m_ref->setValue( argToken.token ); - if( !result ) - return InternalParseResult( result ); - if( result.value() == ParseResultType::ShortCircuitAll ) - return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) ); - } - return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) ); - } - } - return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) ); - } - - auto validate() const -> Result override { - if( m_optNames.empty() ) - return Result::logicError( "No options supplied to Opt" ); - for( auto const &name : m_optNames ) { - if( name.empty() ) - return Result::logicError( "Option name cannot be empty" ); -#ifdef CATCH_PLATFORM_WINDOWS - if( name[0] != '-' && name[0] != '/' ) - return Result::logicError( "Option name must begin with '-' or '/'" ); -#else - if( name[0] != '-' ) - return Result::logicError( "Option name must begin with '-'" ); -#endif - } - return ParserRefImpl::validate(); - } - }; - - struct Help : Opt { - Help( bool &showHelpFlag ) - : Opt([&]( bool flag ) { - showHelpFlag = flag; - return ParserResult::ok( ParseResultType::ShortCircuitAll ); - }) - { - static_cast<Opt &>( *this ) - ("display usage information") - ["-?"]["-h"]["--help"] - .optional(); - } - }; - - struct Parser : ParserBase { - - mutable ExeName m_exeName; - std::vector<Opt> m_options; - std::vector<Arg> m_args; - - auto operator|=( ExeName const &exeName ) -> Parser & { - m_exeName = exeName; - return *this; - } - - auto operator|=( Arg const &arg ) -> Parser & { - m_args.push_back(arg); - return *this; - } - - auto operator|=( Opt const &opt ) -> Parser & { - m_options.push_back(opt); - return *this; - } - - auto operator|=( Parser const &other ) -> Parser & { - m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end()); - m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end()); - return *this; - } - - template<typename T> - auto operator|( T const &other ) const -> Parser { - return Parser( *this ) |= other; - } - - auto getHelpColumns() const -> std::vector<HelpColumns> { - std::vector<HelpColumns> cols; - for (auto const &o : m_options) { - auto childCols = o.getHelpColumns(); - cols.insert( cols.end(), childCols.begin(), childCols.end() ); - } - return cols; - } - - void writeToStream( std::ostream &os ) const { - if (!m_exeName.name().empty()) { - os << "usage:\n" << " " << m_exeName.name() << " "; - bool required = true, first = true; - for( auto const &arg : m_args ) { - if (first) - first = false; - else - os << " "; - if( arg.isOptional() && required ) { - os << "["; - required = false; - } - os << "<" << arg.hint() << ">"; - if( arg.cardinality() == 0 ) - os << " ... "; - } - if( !required ) - os << "]"; - if( !m_options.empty() ) - os << " options"; - os << "\n\nwhere options are:" << std::endl; - } - - auto rows = getHelpColumns(); - size_t consoleWidth = CATCH_CLARA_CONFIG_CONSOLE_WIDTH; - size_t optWidth = 0; - for( auto const &cols : rows ) - optWidth = (std::max)(optWidth, cols.left.size() + 2); - - for( auto const &cols : rows ) { - auto row = - TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) + - TextFlow::Spacer(4) + - TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth ); - os << row << std::endl; - } - } - - friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& { - parser.writeToStream( os ); - return os; - } - - auto validate() const -> Result override { - for( auto const &opt : m_options ) { - auto result = opt.validate(); - if( !result ) - return result; - } - for( auto const &arg : m_args ) { - auto result = arg.validate(); - if( !result ) - return result; - } - return Result::ok(); - } - - using ParserBase::parse; - - auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override { - - struct ParserInfo { - ParserBase const* parser = nullptr; - size_t count = 0; - }; - const size_t totalParsers = m_options.size() + m_args.size(); - assert( totalParsers < 512 ); - // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do - ParserInfo parseInfos[512]; - - { - size_t i = 0; - for (auto const &opt : m_options) parseInfos[i++].parser = &opt; - for (auto const &arg : m_args) parseInfos[i++].parser = &arg; - } - - m_exeName.set( exeName ); - - auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) ); - while( result.value().remainingTokens() ) { - bool tokenParsed = false; - - for( size_t i = 0; i < totalParsers; ++i ) { - auto& parseInfo = parseInfos[i]; - if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) { - result = parseInfo.parser->parse(exeName, result.value().remainingTokens()); - if (!result) - return result; - if (result.value().type() != ParseResultType::NoMatch) { - tokenParsed = true; - ++parseInfo.count; - break; - } - } - } - - if( result.value().type() == ParseResultType::ShortCircuitAll ) - return result; - if( !tokenParsed ) - return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token ); - } - // !TBD Check missing required options - return result; - } - }; - - template<typename DerivedT> - template<typename T> - auto ComposableParserImpl<DerivedT>::operator|( T const &other ) const -> Parser { - return Parser() | static_cast<DerivedT const &>( *this ) | other; - } -} // namespace detail - -// A Combined parser -using detail::Parser; - -// A parser for options -using detail::Opt; - -// A parser for arguments -using detail::Arg; - -// Wrapper for argc, argv from main() -using detail::Args; - -// Specifies the name of the executable -using detail::ExeName; - -// Convenience wrapper for option parser that specifies the help option -using detail::Help; - -// enum of result types from a parse -using detail::ParseResultType; - -// Result type for parser operation -using detail::ParserResult; - -}} // namespace Catch::clara - -// end clara.hpp -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// Restore Clara's value for console width, if present -#ifdef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#define CATCH_CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#undef CATCH_TEMP_CLARA_CONFIG_CONSOLE_WIDTH -#endif - -// end catch_clara.h -namespace Catch { - - clara::Parser makeCommandLineParser( ConfigData& config ); - -} // end namespace Catch - -// end catch_commandline.h -#include <fstream> -#include <ctime> - -namespace Catch { - - clara::Parser makeCommandLineParser( ConfigData& config ) { - - using namespace clara; - - auto const setWarning = [&]( std::string const& warning ) { - if( warning != "NoAssertions" ) - return ParserResult::runtimeError( "Unrecognised warning: '" + warning + "'" ); - config.warnings = static_cast<WarnAbout::What>( config.warnings | WarnAbout::NoAssertions ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const loadTestNamesFromFile = [&]( std::string const& filename ) { - std::ifstream f( filename.c_str() ); - if( !f.is_open() ) - return ParserResult::runtimeError( "Unable to load input file: '" + filename + "'" ); - - std::string line; - while( std::getline( f, line ) ) { - line = trim(line); - if( !line.empty() && !startsWith( line, '#' ) ) { - if( !startsWith( line, '"' ) ) - line = '"' + line + '"'; - config.testsOrTags.push_back( line + ',' ); - } - } - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setTestOrder = [&]( std::string const& order ) { - if( startsWith( "declared", order ) ) - config.runOrder = RunTests::InDeclarationOrder; - else if( startsWith( "lexical", order ) ) - config.runOrder = RunTests::InLexicographicalOrder; - else if( startsWith( "random", order ) ) - config.runOrder = RunTests::InRandomOrder; - else - return clara::ParserResult::runtimeError( "Unrecognised ordering: '" + order + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setRngSeed = [&]( std::string const& seed ) { - if( seed != "time" ) - return clara::detail::convertInto( seed, config.rngSeed ); - config.rngSeed = static_cast<unsigned int>( std::time(nullptr) ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setColourUsage = [&]( std::string const& useColour ) { - auto mode = toLower( useColour ); - - if( mode == "yes" ) - config.useColour = UseColour::Yes; - else if( mode == "no" ) - config.useColour = UseColour::No; - else if( mode == "auto" ) - config.useColour = UseColour::Auto; - else - return ParserResult::runtimeError( "colour mode must be one of: auto, yes or no. '" + useColour + "' not recognised" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setWaitForKeypress = [&]( std::string const& keypress ) { - auto keypressLc = toLower( keypress ); - if( keypressLc == "start" ) - config.waitForKeypress = WaitForKeypress::BeforeStart; - else if( keypressLc == "exit" ) - config.waitForKeypress = WaitForKeypress::BeforeExit; - else if( keypressLc == "both" ) - config.waitForKeypress = WaitForKeypress::BeforeStartAndExit; - else - return ParserResult::runtimeError( "keypress argument must be one of: start, exit or both. '" + keypress + "' not recognised" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - auto const setVerbosity = [&]( std::string const& verbosity ) { - auto lcVerbosity = toLower( verbosity ); - if( lcVerbosity == "quiet" ) - config.verbosity = Verbosity::Quiet; - else if( lcVerbosity == "normal" ) - config.verbosity = Verbosity::Normal; - else if( lcVerbosity == "high" ) - config.verbosity = Verbosity::High; - else - return ParserResult::runtimeError( "Unrecognised verbosity, '" + verbosity + "'" ); - return ParserResult::ok( ParseResultType::Matched ); - }; - - auto cli - = ExeName( config.processName ) - | Help( config.showHelp ) - | Opt( config.listTests ) - ["-l"]["--list-tests"] - ( "list all/matching test cases" ) - | Opt( config.listTags ) - ["-t"]["--list-tags"] - ( "list all/matching tags" ) - | Opt( config.showSuccessfulTests ) - ["-s"]["--success"] - ( "include successful tests in output" ) - | Opt( config.shouldDebugBreak ) - ["-b"]["--break"] - ( "break into debugger on failure" ) - | Opt( config.noThrow ) - ["-e"]["--nothrow"] - ( "skip exception tests" ) - | Opt( config.showInvisibles ) - ["-i"]["--invisibles"] - ( "show invisibles (tabs, newlines)" ) - | Opt( config.outputFilename, "filename" ) - ["-o"]["--out"] - ( "output filename" ) - | Opt( config.reporterNames, "name" ) - ["-r"]["--reporter"] - ( "reporter to use (defaults to console)" ) - | Opt( config.name, "name" ) - ["-n"]["--name"] - ( "suite name" ) - | Opt( [&]( bool ){ config.abortAfter = 1; } ) - ["-a"]["--abort"] - ( "abort at first failure" ) - | Opt( [&]( int x ){ config.abortAfter = x; }, "no. failures" ) - ["-x"]["--abortx"] - ( "abort after x failures" ) - | Opt( setWarning, "warning name" ) - ["-w"]["--warn"] - ( "enable warnings" ) - | Opt( [&]( bool flag ) { config.showDurations = flag ? ShowDurations::Always : ShowDurations::Never; }, "yes|no" ) - ["-d"]["--durations"] - ( "show test durations" ) - | Opt( loadTestNamesFromFile, "filename" ) - ["-f"]["--input-file"] - ( "load test names to run from a file" ) - | Opt( config.filenamesAsTags ) - ["-#"]["--filenames-as-tags"] - ( "adds a tag for the filename" ) - | Opt( config.sectionsToRun, "section name" ) - ["-c"]["--section"] - ( "specify section to run" ) - | Opt( setVerbosity, "quiet|normal|high" ) - ["-v"]["--verbosity"] - ( "set output verbosity" ) - | Opt( config.listTestNamesOnly ) - ["--list-test-names-only"] - ( "list all/matching test cases names only" ) - | Opt( config.listReporters ) - ["--list-reporters"] - ( "list all reporters" ) - | Opt( setTestOrder, "decl|lex|rand" ) - ["--order"] - ( "test case order (defaults to decl)" ) - | Opt( setRngSeed, "'time'|number" ) - ["--rng-seed"] - ( "set a specific seed for random numbers" ) - | Opt( setColourUsage, "yes|no" ) - ["--use-colour"] - ( "should output be colourised" ) - | Opt( config.libIdentify ) - ["--libidentify"] - ( "report name and version according to libidentify standard" ) - | Opt( setWaitForKeypress, "start|exit|both" ) - ["--wait-for-keypress"] - ( "waits for a keypress before exiting" ) - | Opt( config.benchmarkResolutionMultiple, "multiplier" ) - ["--benchmark-resolution-multiple"] - ( "multiple of clock resolution to run benchmarks" ) - - | Arg( config.testsOrTags, "test name|pattern|tags" ) - ( "which test or tests to use" ); - - return cli; - } - -} // end namespace Catch -// end catch_commandline.cpp -// start catch_common.cpp - -#include <cstring> -#include <ostream> - -namespace Catch { - - SourceLineInfo::SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - bool SourceLineInfo::empty() const noexcept { - return file[0] == '\0'; - } - bool SourceLineInfo::operator == ( SourceLineInfo const& other ) const noexcept { - return line == other.line && (file == other.file || std::strcmp(file, other.file) == 0); - } - bool SourceLineInfo::operator < ( SourceLineInfo const& other ) const noexcept { - return line < other.line || ( line == other.line && (std::strcmp(file, other.file) < 0)); - } - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { -#ifndef __GNUG__ - os << info.file << '(' << info.line << ')'; -#else - os << info.file << ':' << info.line; -#endif - return os; - } - - bool isTrue( bool value ){ return value; } - bool alwaysTrue() { return true; } - bool alwaysFalse() { return false; } - - std::string StreamEndStop::operator+() const { - return std::string(); - } - - NonCopyable::NonCopyable() = default; - NonCopyable::~NonCopyable() = default; - -} -// end catch_common.cpp -// start catch_config.cpp - -namespace Catch { - - Config::Config( ConfigData const& data ) - : m_data( data ), - m_stream( openStream() ) - { - if( !data.testsOrTags.empty() ) { - TestSpecParser parser( ITagAliasRegistry::get() ); - for( auto const& testOrTags : data.testsOrTags ) - parser.parse( testOrTags ); - m_testSpec = parser.testSpec(); - } - } - - std::string const& Config::getFilename() const { - return m_data.outputFilename ; - } - - bool Config::listTests() const { return m_data.listTests; } - bool Config::listTestNamesOnly() const { return m_data.listTestNamesOnly; } - bool Config::listTags() const { return m_data.listTags; } - bool Config::listReporters() const { return m_data.listReporters; } - - std::string Config::getProcessName() const { return m_data.processName; } - - std::vector<std::string> const& Config::getReporterNames() const { return m_data.reporterNames; } - std::vector<std::string> const& Config::getSectionsToRun() const { return m_data.sectionsToRun; } - - TestSpec const& Config::testSpec() const { return m_testSpec; } - - bool Config::showHelp() const { return m_data.showHelp; } - - // IConfig interface - bool Config::allowThrows() const { return !m_data.noThrow; } - std::ostream& Config::stream() const { return m_stream->stream(); } - std::string Config::name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } - bool Config::includeSuccessfulResults() const { return m_data.showSuccessfulTests; } - bool Config::warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } - ShowDurations::OrNot Config::showDurations() const { return m_data.showDurations; } - RunTests::InWhatOrder Config::runOrder() const { return m_data.runOrder; } - unsigned int Config::rngSeed() const { return m_data.rngSeed; } - int Config::benchmarkResolutionMultiple() const { return m_data.benchmarkResolutionMultiple; } - UseColour::YesOrNo Config::useColour() const { return m_data.useColour; } - bool Config::shouldDebugBreak() const { return m_data.shouldDebugBreak; } - int Config::abortAfter() const { return m_data.abortAfter; } - bool Config::showInvisibles() const { return m_data.showInvisibles; } - Verbosity Config::verbosity() const { return m_data.verbosity; } - - IStream const* Config::openStream() { - if( m_data.outputFilename.empty() ) - return new CoutStream(); - else if( m_data.outputFilename[0] == '%' ) { - if( m_data.outputFilename == "%debug" ) - return new DebugOutStream(); - else - CATCH_ERROR( "Unrecognised stream: '" << m_data.outputFilename << "'" ); - } - else - return new FileStream( m_data.outputFilename ); - } - -} // end namespace Catch -// end catch_config.cpp -// start catch_console_colour.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -// start catch_errno_guard.h - -namespace Catch { - - class ErrnoGuard { - public: - ErrnoGuard(); - ~ErrnoGuard(); - private: - int m_oldErrno; - }; - -} - -// end catch_errno_guard.h -// start catch_windows_h_proxy.h - - -#if defined(CATCH_PLATFORM_WINDOWS) - -#if !defined(NOMINMAX) && !defined(CATCH_CONFIG_NO_NOMINMAX) -# define CATCH_DEFINED_NOMINMAX -# define NOMINMAX -#endif -#if !defined(WIN32_LEAN_AND_MEAN) && !defined(CATCH_CONFIG_NO_WIN32_LEAN_AND_MEAN) -# define CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -#endif - -#ifdef __AFXDLL -#include <AfxWin.h> -#else -#include <windows.h> -#endif - -#ifdef CATCH_DEFINED_NOMINMAX -# undef NOMINMAX -#endif -#ifdef CATCH_DEFINED_WIN32_LEAN_AND_MEAN -# undef WIN32_LEAN_AND_MEAN -#endif - -#endif // defined(CATCH_PLATFORM_WINDOWS) - -// end catch_windows_h_proxy.h -namespace Catch { - namespace { - - struct IColourImpl { - virtual ~IColourImpl() = default; - virtual void use( Colour::Code _colourCode ) = 0; - }; - - struct NoColourImpl : IColourImpl { - void use( Colour::Code ) {} - - static IColourImpl* instance() { - static NoColourImpl s_instance; - return &s_instance; - } - }; - - } // anon namespace -} // namespace Catch - -#if !defined( CATCH_CONFIG_COLOUR_NONE ) && !defined( CATCH_CONFIG_COLOUR_WINDOWS ) && !defined( CATCH_CONFIG_COLOUR_ANSI ) -# ifdef CATCH_PLATFORM_WINDOWS -# define CATCH_CONFIG_COLOUR_WINDOWS -# else -# define CATCH_CONFIG_COLOUR_ANSI -# endif -#endif - -#if defined ( CATCH_CONFIG_COLOUR_WINDOWS ) ///////////////////////////////////////// - -namespace Catch { -namespace { - - class Win32ColourImpl : public IColourImpl { - public: - Win32ColourImpl() : stdoutHandle( GetStdHandle(STD_OUTPUT_HANDLE) ) - { - CONSOLE_SCREEN_BUFFER_INFO csbiInfo; - GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); - originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); - } - - virtual void use( Colour::Code _colourCode ) override { - switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalForegroundAttributes ); - case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); - case Colour::Red: return setTextAttribute( FOREGROUND_RED ); - case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); - case Colour::Blue: return setTextAttribute( FOREGROUND_BLUE ); - case Colour::Cyan: return setTextAttribute( FOREGROUND_BLUE | FOREGROUND_GREEN ); - case Colour::Yellow: return setTextAttribute( FOREGROUND_RED | FOREGROUND_GREEN ); - case Colour::Grey: return setTextAttribute( 0 ); - - case Colour::LightGrey: return setTextAttribute( FOREGROUND_INTENSITY ); - case Colour::BrightRed: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_RED ); - case Colour::BrightGreen: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN ); - case Colour::BrightWhite: return setTextAttribute( FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); - - case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - } - } - - private: - void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); - } - HANDLE stdoutHandle; - WORD originalForegroundAttributes; - WORD originalBackgroundAttributes; - }; - - IColourImpl* platformColourInstance() { - static Win32ColourImpl s_instance; - - IConfigPtr config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = UseColour::Yes; - return colourMode == UseColour::Yes - ? &s_instance - : NoColourImpl::instance(); - } - -} // end anon namespace -} // end namespace Catch - -#elif defined( CATCH_CONFIG_COLOUR_ANSI ) ////////////////////////////////////// - -#include <unistd.h> - -namespace Catch { -namespace { - - // use POSIX/ ANSI console terminal codes - // Thanks to Adam Strzelecki for original contribution - // (http://github.com/nanoant) - // https://github.com/philsquared/Catch/pull/131 - class PosixColourImpl : public IColourImpl { - public: - virtual void use( Colour::Code _colourCode ) override { - switch( _colourCode ) { - case Colour::None: - case Colour::White: return setColour( "[0m" ); - case Colour::Red: return setColour( "[0;31m" ); - case Colour::Green: return setColour( "[0;32m" ); - case Colour::Blue: return setColour( "[0;34m" ); - case Colour::Cyan: return setColour( "[0;36m" ); - case Colour::Yellow: return setColour( "[0;33m" ); - case Colour::Grey: return setColour( "[1;30m" ); - - case Colour::LightGrey: return setColour( "[0;37m" ); - case Colour::BrightRed: return setColour( "[1;31m" ); - case Colour::BrightGreen: return setColour( "[1;32m" ); - case Colour::BrightWhite: return setColour( "[1;37m" ); - - case Colour::Bright: CATCH_INTERNAL_ERROR( "not a colour" ); - } - } - static IColourImpl* instance() { - static PosixColourImpl s_instance; - return &s_instance; - } - - private: - void setColour( const char* _escapeCode ) { - Catch::cout() << '\033' << _escapeCode; - } - }; - - bool useColourOnPlatform() { - return -#ifdef CATCH_PLATFORM_MAC - !isDebuggerActive() && -#endif - isatty(STDOUT_FILENO); - } - IColourImpl* platformColourInstance() { - ErrnoGuard guard; - IConfigPtr config = getCurrentContext().getConfig(); - UseColour::YesOrNo colourMode = config - ? config->useColour() - : UseColour::Auto; - if( colourMode == UseColour::Auto ) - colourMode = useColourOnPlatform() - ? UseColour::Yes - : UseColour::No; - return colourMode == UseColour::Yes - ? PosixColourImpl::instance() - : NoColourImpl::instance(); - } - -} // end anon namespace -} // end namespace Catch - -#else // not Windows or ANSI /////////////////////////////////////////////// - -namespace Catch { - - static IColourImpl* platformColourInstance() { return NoColourImpl::instance(); } - -} // end namespace Catch - -#endif // Windows/ ANSI/ None - -namespace Catch { - - Colour::Colour( Code _colourCode ) { use( _colourCode ); } - Colour::Colour( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; - } - Colour& Colour::operator=( Colour&& rhs ) noexcept { - m_moved = rhs.m_moved; - rhs.m_moved = true; - return *this; - } - - Colour::~Colour(){ if( !m_moved ) use( None ); } - - void Colour::use( Code _colourCode ) { - static IColourImpl* impl = platformColourInstance(); - impl->use( _colourCode ); - } - - std::ostream& operator << ( std::ostream& os, Colour const& ) { - return os; - } - -} // end namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -// end catch_console_colour.cpp -// start catch_context.cpp - -namespace Catch { - - class Context : public IMutableContext, NonCopyable { - - public: // IContext - virtual IResultCapture* getResultCapture() override { - return m_resultCapture; - } - virtual IRunner* getRunner() override { - return m_runner; - } - - virtual IConfigPtr getConfig() const override { - return m_config; - } - - virtual ~Context() override; - - public: // IMutableContext - virtual void setResultCapture( IResultCapture* resultCapture ) override { - m_resultCapture = resultCapture; - } - virtual void setRunner( IRunner* runner ) override { - m_runner = runner; - } - virtual void setConfig( IConfigPtr const& config ) override { - m_config = config; - } - - friend IMutableContext& getCurrentMutableContext(); - - private: - IConfigPtr m_config; - IRunner* m_runner = nullptr; - IResultCapture* m_resultCapture = nullptr; - }; - - namespace { - Context* currentContext = nullptr; - } - IMutableContext& getCurrentMutableContext() { - if( !currentContext ) - currentContext = new Context(); - return *currentContext; - } - IContext& getCurrentContext() { - return getCurrentMutableContext(); - } - - void cleanUpContext() { - delete currentContext; - currentContext = nullptr; - } - IContext::~IContext() = default; - IMutableContext::~IMutableContext() = default; - Context::~Context() = default; -} -// end catch_context.cpp -// start catch_debug_console.cpp - -// start catch_debug_console.h - -#include <string> - -namespace Catch { - void writeToDebugConsole( std::string const& text ); -} - -// end catch_debug_console.h -#ifdef CATCH_PLATFORM_WINDOWS - - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - ::OutputDebugStringA( text.c_str() ); - } - } -#else - namespace Catch { - void writeToDebugConsole( std::string const& text ) { - // !TBD: Need a version for Mac/ XCode and other IDEs - Catch::cout() << text; - } - } -#endif // Platform -// end catch_debug_console.cpp -// start catch_debugger.cpp - -#ifdef CATCH_PLATFORM_MAC - - #include <assert.h> - #include <stdbool.h> - #include <sys/types.h> - #include <unistd.h> - #include <sys/sysctl.h> - - namespace Catch { - - // The following function is taken directly from the following technical note: - // http://developer.apple.com/library/mac/#qa/qa2004/qa1361.html - - // Returns true if the current process is being debugged (either - // running under the debugger or has a debugger attached post facto). - bool isDebuggerActive(){ - - int mib[4]; - struct kinfo_proc info; - std::size_t size; - - // Initialize the flags so that, if sysctl fails for some bizarre - // reason, we get a predictable result. - - info.kp_proc.p_flag = 0; - - // Initialize mib, which tells sysctl the info we want, in this case - // we're looking for information about a specific process ID. - - mib[0] = CTL_KERN; - mib[1] = KERN_PROC; - mib[2] = KERN_PROC_PID; - mib[3] = getpid(); - - // Call sysctl. - - size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0 ) { - Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; - return false; - } - - // We're being debugged if the P_TRACED flag is set. - - return ( (info.kp_proc.p_flag & P_TRACED) != 0 ); - } - } // namespace Catch - -#elif defined(CATCH_PLATFORM_LINUX) - #include <fstream> - #include <string> - - namespace Catch{ - // The standard POSIX way of detecting a debugger is to attempt to - // ptrace() the process, but this needs to be done from a child and not - // this process itself to still allow attaching to this process later - // if wanted, so is rather heavy. Under Linux we have the PID of the - // "debugger" (which doesn't need to be gdb, of course, it could also - // be strace, for example) in /proc/$PID/status, so just get it from - // there instead. - bool isDebuggerActive(){ - // Libstdc++ has a bug, where std::ifstream sets errno to 0 - // This way our users can properly assert over errno values - ErrnoGuard guard; - std::ifstream in("/proc/self/status"); - for( std::string line; std::getline(in, line); ) { - static const int PREFIX_LEN = 11; - if( line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0 ) { - // We're traced if the PID is not 0 and no other PID starts - // with 0 digit, so it's enough to check for just a single - // character. - return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; - } - } - - return false; - } - } // namespace Catch -#elif defined(_MSC_VER) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } - } -#elif defined(__MINGW32__) - extern "C" __declspec(dllimport) int __stdcall IsDebuggerPresent(); - namespace Catch { - bool isDebuggerActive() { - return IsDebuggerPresent() != 0; - } - } -#else - namespace Catch { - bool isDebuggerActive() { return false; } - } -#endif // Platform -// end catch_debugger.cpp -// start catch_decomposer.cpp - -namespace Catch { - - ITransientExpression::~ITransientExpression() = default; - - void formatReconstructedExpression( std::ostream &os, std::string const& lhs, StringRef op, std::string const& rhs ) { - if( lhs.size() + rhs.size() < 40 && - lhs.find('\n') == std::string::npos && - rhs.find('\n') == std::string::npos ) - os << lhs << " " << op << " " << rhs; - else - os << lhs << "\n" << op << "\n" << rhs; - } -} -// end catch_decomposer.cpp -// start catch_errno_guard.cpp - -#include <cerrno> - -namespace Catch { - ErrnoGuard::ErrnoGuard():m_oldErrno(errno){} - ErrnoGuard::~ErrnoGuard() { errno = m_oldErrno; } -} -// end catch_errno_guard.cpp -// start catch_exception_translator_registry.cpp - -// start catch_exception_translator_registry.h - -#include <vector> -#include <string> -#include <memory> - -namespace Catch { - - class ExceptionTranslatorRegistry : public IExceptionTranslatorRegistry { - public: - ~ExceptionTranslatorRegistry(); - virtual void registerTranslator( const IExceptionTranslator* translator ); - virtual std::string translateActiveException() const override; - std::string tryTranslators() const; - - private: - std::vector<std::unique_ptr<IExceptionTranslator const>> m_translators; - }; -} - -// end catch_exception_translator_registry.h -#ifdef __OBJC__ -#import "Foundation/Foundation.h" -#endif - -namespace Catch { - - ExceptionTranslatorRegistry::~ExceptionTranslatorRegistry() { - } - - void ExceptionTranslatorRegistry::registerTranslator( const IExceptionTranslator* translator ) { - m_translators.push_back( std::unique_ptr<const IExceptionTranslator>( translator ) ); - } - - std::string ExceptionTranslatorRegistry::translateActiveException() const { - try { -#ifdef __OBJC__ - // In Objective-C try objective-c exceptions first - @try { - return tryTranslators(); - } - @catch (NSException *exception) { - return Catch::Detail::stringify( [exception description] ); - } -#else - return tryTranslators(); -#endif - } - catch( TestFailureException& ) { - std::rethrow_exception(std::current_exception()); - } - catch( std::exception& ex ) { - return ex.what(); - } - catch( std::string& msg ) { - return msg; - } - catch( const char* msg ) { - return msg; - } - catch(...) { - return "Unknown exception"; - } - } - - std::string ExceptionTranslatorRegistry::tryTranslators() const { - if( m_translators.empty() ) - std::rethrow_exception(std::current_exception()); - else - return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); - } -} -// end catch_exception_translator_registry.cpp -// start catch_fatal_condition.cpp - -// start catch_fatal_condition.h - -#include <string> - -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// - -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -# else // CATCH_CONFIG_WINDOWS_SEH is defined - -namespace Catch { - - struct FatalConditionHandler { - - static LONG CALLBACK handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo); - FatalConditionHandler(); - static void reset(); - ~FatalConditionHandler(); - - private: - static bool isSet; - static ULONG guaranteeSize; - static PVOID exceptionHandlerHandle; - }; - -} // namespace Catch - -# endif // CATCH_CONFIG_WINDOWS_SEH - -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace Catch { - struct FatalConditionHandler { - void reset(); - }; -} - -# else // CATCH_CONFIG_POSIX_SIGNALS is defined - -#include <signal.h> - -namespace Catch { - - struct FatalConditionHandler { - - static bool isSet; - static struct sigaction oldSigActions[];// [sizeof(signalDefs) / sizeof(SignalDefs)]; - static stack_t oldSigStack; - static char altStackMem[]; - - static void handleSignal( int sig ); - - FatalConditionHandler(); - ~FatalConditionHandler(); - static void reset(); - }; - -} // namespace Catch - -# endif // CATCH_CONFIG_POSIX_SIGNALS - -#endif // not Windows - -// end catch_fatal_condition.h -namespace { - // Report the error condition - void reportFatal( char const * const message ) { - Catch::getCurrentContext().getResultCapture()->handleFatalErrorCondition( message ); - } -} - -#if defined ( CATCH_PLATFORM_WINDOWS ) ///////////////////////////////////////// - -# if !defined ( CATCH_CONFIG_WINDOWS_SEH ) - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -# else // CATCH_CONFIG_WINDOWS_SEH is defined - -namespace Catch { - struct SignalDefs { DWORD id; const char* name; }; - - // There is no 1-1 mapping between signals and windows exceptions. - // Windows can easily distinguish between SO and SigSegV, - // but SigInt, SigTerm, etc are handled differently. - static SignalDefs signalDefs[] = { - { EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal" }, - { EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow" }, - { EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal" }, - { EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error" }, - }; - - LONG CALLBACK FatalConditionHandler::handleVectoredException(PEXCEPTION_POINTERS ExceptionInfo) { - for (auto const& def : signalDefs) { - if (ExceptionInfo->ExceptionRecord->ExceptionCode == def.id) { - reportFatal(def.name); - } - } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; - } - - FatalConditionHandler::FatalConditionHandler() { - isSet = true; - // 32k seems enough for Catch to handle stack overflow, - // but the value was found experimentally, so there is no strong guarantee - guaranteeSize = 32 * 1024; - exceptionHandlerHandle = nullptr; - // Register as first handler in current chain - exceptionHandlerHandle = AddVectoredExceptionHandler(1, handleVectoredException); - // Pass in guarantee size to be filled - SetThreadStackGuarantee(&guaranteeSize); - } - - void FatalConditionHandler::reset() { - if (isSet) { - // Unregister handler and restore the old guarantee - RemoveVectoredExceptionHandler(exceptionHandlerHandle); - SetThreadStackGuarantee(&guaranteeSize); - exceptionHandlerHandle = nullptr; - isSet = false; - } - } - - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - -bool FatalConditionHandler::isSet = false; -ULONG FatalConditionHandler::guaranteeSize = 0; -PVOID FatalConditionHandler::exceptionHandlerHandle = nullptr; - -} // namespace Catch - -# endif // CATCH_CONFIG_WINDOWS_SEH - -#else // Not Windows - assumed to be POSIX compatible ////////////////////////// - -# if !defined(CATCH_CONFIG_POSIX_SIGNALS) - -namespace Catch { - void FatalConditionHandler::reset() {} -} - -# else // CATCH_CONFIG_POSIX_SIGNALS is defined - -#include <signal.h> - -namespace Catch { - - struct SignalDefs { - int id; - const char* name; - }; - static SignalDefs signalDefs[] = { - { SIGINT, "SIGINT - Terminal interrupt signal" }, - { SIGILL, "SIGILL - Illegal instruction signal" }, - { SIGFPE, "SIGFPE - Floating point error signal" }, - { SIGSEGV, "SIGSEGV - Segmentation violation signal" }, - { SIGTERM, "SIGTERM - Termination request signal" }, - { SIGABRT, "SIGABRT - Abort (abnormal termination) signal" } - }; - - void FatalConditionHandler::handleSignal( int sig ) { - char const * name = "<unknown signal>"; - for (auto const& def : signalDefs) { - if (sig == def.id) { - name = def.name; - break; - } - } - reset(); - reportFatal(name); - raise( sig ); - } - - FatalConditionHandler::FatalConditionHandler() { - isSet = true; - stack_t sigStack; - sigStack.ss_sp = altStackMem; - sigStack.ss_size = SIGSTKSZ; - sigStack.ss_flags = 0; - sigaltstack(&sigStack, &oldSigStack); - struct sigaction sa = { }; - - sa.sa_handler = handleSignal; - sa.sa_flags = SA_ONSTACK; - for (std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i) { - sigaction(signalDefs[i].id, &sa, &oldSigActions[i]); - } - } - - FatalConditionHandler::~FatalConditionHandler() { - reset(); - } - - void FatalConditionHandler::reset() { - if( isSet ) { - // Set signals back to previous values -- hopefully nobody overwrote them in the meantime - for( std::size_t i = 0; i < sizeof(signalDefs)/sizeof(SignalDefs); ++i ) { - sigaction(signalDefs[i].id, &oldSigActions[i], nullptr); - } - // Return the old stack - sigaltstack(&oldSigStack, nullptr); - isSet = false; - } - } - - bool FatalConditionHandler::isSet = false; - struct sigaction FatalConditionHandler::oldSigActions[sizeof(signalDefs)/sizeof(SignalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[SIGSTKSZ] = {}; - -} // namespace Catch - -# endif // CATCH_CONFIG_POSIX_SIGNALS - -#endif // not Windows -// end catch_fatal_condition.cpp -// start catch_interfaces_capture.cpp - -namespace Catch { - IResultCapture::~IResultCapture() = default; -} -// end catch_interfaces_capture.cpp -// start catch_interfaces_config.cpp - -namespace Catch { - IConfig::~IConfig() = default; -} -// end catch_interfaces_config.cpp -// start catch_interfaces_exception.cpp - -namespace Catch { - IExceptionTranslator::~IExceptionTranslator() = default; - IExceptionTranslatorRegistry::~IExceptionTranslatorRegistry() = default; -} -// end catch_interfaces_exception.cpp -// start catch_interfaces_registry_hub.cpp - -namespace Catch { - IRegistryHub::~IRegistryHub() = default; - IMutableRegistryHub::~IMutableRegistryHub() = default; -} -// end catch_interfaces_registry_hub.cpp -// start catch_interfaces_reporter.cpp - -// start catch_reporter_multi.h - -namespace Catch { - - class MultipleReporters : public IStreamingReporter { - using Reporters = std::vector<IStreamingReporterPtr>; - Reporters m_reporters; - - public: - void add( IStreamingReporterPtr&& reporter ); - - public: // IStreamingReporter - - ReporterPreferences getPreferences() const override; - - void noMatchingTestCases( std::string const& spec ) override; - - static std::set<Verbosity> getSupportedVerbosities(); - - void benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) override; - void benchmarkEnded( BenchmarkStats const& benchmarkStats ) override; - - void testRunStarting( TestRunInfo const& testRunInfo ) override; - void testGroupStarting( GroupInfo const& groupInfo ) override; - void testCaseStarting( TestCaseInfo const& testInfo ) override; - void sectionStarting( SectionInfo const& sectionInfo ) override; - void assertionStarting( AssertionInfo const& assertionInfo ) override; - - // The return value indicates if the messages buffer should be cleared: - bool assertionEnded( AssertionStats const& assertionStats ) override; - void sectionEnded( SectionStats const& sectionStats ) override; - void testCaseEnded( TestCaseStats const& testCaseStats ) override; - void testGroupEnded( TestGroupStats const& testGroupStats ) override; - void testRunEnded( TestRunStats const& testRunStats ) override; - - void skipTest( TestCaseInfo const& testInfo ) override; - bool isMulti() const override; - - }; - -} // end namespace Catch - -// end catch_reporter_multi.h -namespace Catch { - - ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig ) - : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} - - ReporterConfig::ReporterConfig( IConfigPtr const& _fullConfig, std::ostream& _stream ) - : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} - - std::ostream& ReporterConfig::stream() const { return *m_stream; } - IConfigPtr ReporterConfig::fullConfig() const { return m_fullConfig; } - - TestRunInfo::TestRunInfo( std::string const& _name ) : name( _name ) {} - - GroupInfo::GroupInfo( std::string const& _name, - std::size_t _groupIndex, - std::size_t _groupsCount ) - : name( _name ), - groupIndex( _groupIndex ), - groupsCounts( _groupsCount ) - {} - - AssertionStats::AssertionStats( AssertionResult const& _assertionResult, - std::vector<MessageInfo> const& _infoMessages, - Totals const& _totals ) - : assertionResult( _assertionResult ), - infoMessages( _infoMessages ), - totals( _totals ) - { - assertionResult.m_resultData.lazyExpression.m_transientExpression = _assertionResult.m_resultData.lazyExpression.m_transientExpression; - - if( assertionResult.hasMessage() ) { - // Copy message into messages list. - // !TBD This should have been done earlier, somewhere - MessageBuilder builder( assertionResult.getTestMacroName(), assertionResult.getSourceInfo(), assertionResult.getResultType() ); - builder << assertionResult.getMessage(); - builder.m_info.message = builder.m_stream.str(); - - infoMessages.push_back( builder.m_info ); - } - } - - AssertionStats::~AssertionStats() = default; - - SectionStats::SectionStats( SectionInfo const& _sectionInfo, - Counts const& _assertions, - double _durationInSeconds, - bool _missingAssertions ) - : sectionInfo( _sectionInfo ), - assertions( _assertions ), - durationInSeconds( _durationInSeconds ), - missingAssertions( _missingAssertions ) - {} - - SectionStats::~SectionStats() = default; - - TestCaseStats::TestCaseStats( TestCaseInfo const& _testInfo, - Totals const& _totals, - std::string const& _stdOut, - std::string const& _stdErr, - bool _aborting ) - : testInfo( _testInfo ), - totals( _totals ), - stdOut( _stdOut ), - stdErr( _stdErr ), - aborting( _aborting ) - {} - - TestCaseStats::~TestCaseStats() = default; - - TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo, - Totals const& _totals, - bool _aborting ) - : groupInfo( _groupInfo ), - totals( _totals ), - aborting( _aborting ) - {} - - TestGroupStats::TestGroupStats( GroupInfo const& _groupInfo ) - : groupInfo( _groupInfo ), - aborting( false ) - {} - - TestGroupStats::~TestGroupStats() = default; - - TestRunStats::TestRunStats( TestRunInfo const& _runInfo, - Totals const& _totals, - bool _aborting ) - : runInfo( _runInfo ), - totals( _totals ), - aborting( _aborting ) - {} - - TestRunStats::~TestRunStats() = default; - - void IStreamingReporter::fatalErrorEncountered( StringRef ) {} - bool IStreamingReporter::isMulti() const { return false; } - - IReporterFactory::~IReporterFactory() = default; - IReporterRegistry::~IReporterRegistry() = default; - - void addReporter( IStreamingReporterPtr& existingReporter, IStreamingReporterPtr&& additionalReporter ) { - - if( !existingReporter ) { - existingReporter = std::move( additionalReporter ); - return; - } - - MultipleReporters* multi = nullptr; - - if( existingReporter->isMulti() ) { - multi = static_cast<MultipleReporters*>( existingReporter.get() ); - } - else { - auto newMulti = std::unique_ptr<MultipleReporters>( new MultipleReporters ); - newMulti->add( std::move( existingReporter ) ); - multi = newMulti.get(); - existingReporter = std::move( newMulti ); - } - multi->add( std::move( additionalReporter ) ); - } - -} // end namespace Catch -// end catch_interfaces_reporter.cpp -// start catch_interfaces_runner.cpp - -namespace Catch { - IRunner::~IRunner() = default; -} -// end catch_interfaces_runner.cpp -// start catch_interfaces_testcase.cpp - -namespace Catch { - ITestInvoker::~ITestInvoker() = default; - ITestCaseRegistry::~ITestCaseRegistry() = default; -} -// end catch_interfaces_testcase.cpp -// start catch_leak_detector.cpp - -namespace Catch { - -#ifdef CATCH_CONFIG_WINDOWS_CRTDBG -#include <crtdbg.h> - - LeakDetector::LeakDetector() { - int flag = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - flag |= _CRTDBG_LEAK_CHECK_DF; - flag |= _CRTDBG_ALLOC_MEM_DF; - _CrtSetDbgFlag(flag); - _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); - _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); - // Change this to leaking allocation's number to break there - _CrtSetBreakAlloc(-1); - } - -#else - - LeakDetector::LeakDetector(){} - -#endif - -} -// end catch_leak_detector.cpp -// start catch_list.cpp - -// start catch_list.h - -#include <set> - -namespace Catch { - - std::size_t listTests( Config const& config ); - - std::size_t listTestsNamesOnly( Config const& config ); - - struct TagInfo { - void add( std::string const& spelling ); - std::string all() const; - - std::set<std::string> spellings; - std::size_t count = 0; - }; - - std::size_t listTags( Config const& config ); - - std::size_t listReporters( Config const& /*config*/ ); - - Option<std::size_t> list( Config const& config ); - -} // end namespace Catch - -// end catch_list.h -// start catch_text.h - -namespace Catch { - using namespace clara::TextFlow; -} - -// end catch_text.h -#include <limits> -#include <algorithm> -#include <iomanip> - -namespace Catch { - - std::size_t listTests( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Matching test cases:\n"; - else { - Catch::cout() << "All available test cases:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - } - - auto matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( auto const& testCaseInfo : matchedTestCases ) { - Colour::Code colour = testCaseInfo.isHidden() - ? Colour::SecondaryText - : Colour::None; - Colour colourGuard( colour ); - - Catch::cout() << Column( testCaseInfo.name ).initialIndent( 2 ).indent( 4 ) << "\n"; - if( config.verbosity() >= Verbosity::High ) { - Catch::cout() << Column( Catch::Detail::stringify( testCaseInfo.lineInfo ) ).indent(4) << std::endl; - std::string description = testCaseInfo.description; - if( description.empty() ) - description = "(NO DESCRIPTION)"; - Catch::cout() << Column( description ).indent(4) << std::endl; - } - if( !testCaseInfo.tags.empty() ) - Catch::cout() << Column( testCaseInfo.tagsAsString() ).indent( 6 ) << "\n"; - } - - if( !config.testSpec().hasFilters() ) - Catch::cout() << pluralise( matchedTestCases.size(), "test case" ) << '\n' << std::endl; - else - Catch::cout() << pluralise( matchedTestCases.size(), "matching test case" ) << '\n' << std::endl; - return matchedTestCases.size(); - } - - std::size_t listTestsNamesOnly( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( !config.testSpec().hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - std::size_t matchedTests = 0; - std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( auto const& testCaseInfo : matchedTestCases ) { - matchedTests++; - if( startsWith( testCaseInfo.name, '#' ) ) - Catch::cout() << '"' << testCaseInfo.name << '"'; - else - Catch::cout() << testCaseInfo.name; - if ( config.verbosity() >= Verbosity::High ) - Catch::cout() << "\t@" << testCaseInfo.lineInfo; - Catch::cout() << std::endl; - } - return matchedTests; - } - - void TagInfo::add( std::string const& spelling ) { - ++count; - spellings.insert( spelling ); - } - - std::string TagInfo::all() const { - std::string out; - for( auto const& spelling : spellings ) - out += "[" + spelling + "]"; - return out; - } - - std::size_t listTags( Config const& config ) { - TestSpec testSpec = config.testSpec(); - if( config.testSpec().hasFilters() ) - Catch::cout() << "Tags for matching test cases:\n"; - else { - Catch::cout() << "All available tags:\n"; - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); - } - - std::map<std::string, TagInfo> tagCounts; - - std::vector<TestCase> matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); - for( auto const& testCase : matchedTestCases ) { - for( auto const& tagName : testCase.getTestCaseInfo().tags ) { - std::string lcaseTagName = toLower( tagName ); - auto countIt = tagCounts.find( lcaseTagName ); - if( countIt == tagCounts.end() ) - countIt = tagCounts.insert( std::make_pair( lcaseTagName, TagInfo() ) ).first; - countIt->second.add( tagName ); - } - } - - for( auto const& tagCount : tagCounts ) { - std::ostringstream oss; - oss << " " << std::setw(2) << tagCount.second.count << " "; - auto wrapper = Column( tagCount.second.all() ) - .initialIndent( 0 ) - .indent( oss.str().size() ) - .width( CATCH_CONFIG_CONSOLE_WIDTH-10 ); - Catch::cout() << oss.str() << wrapper << '\n'; - } - Catch::cout() << pluralise( tagCounts.size(), "tag" ) << '\n' << std::endl; - return tagCounts.size(); - } - - std::size_t listReporters( Config const& /*config*/ ) { - Catch::cout() << "Available reporters:\n"; - IReporterRegistry::FactoryMap const& factories = getRegistryHub().getReporterRegistry().getFactories(); - std::size_t maxNameLen = 0; - for( auto const& factoryKvp : factories ) - maxNameLen = (std::max)( maxNameLen, factoryKvp.first.size() ); - - for( auto const& factoryKvp : factories ) { - Catch::cout() - << Column( factoryKvp.first + ":" ) - .indent(2) - .width( 5+maxNameLen ) - + Column( factoryKvp.second->getDescription() ) - .initialIndent(0) - .indent(2) - .width( CATCH_CONFIG_CONSOLE_WIDTH - maxNameLen-8 ) - << "\n"; - } - Catch::cout() << std::endl; - return factories.size(); - } - - Option<std::size_t> list( Config const& config ) { - Option<std::size_t> listedCount; - if( config.listTests() ) - listedCount = listedCount.valueOr(0) + listTests( config ); - if( config.listTestNamesOnly() ) - listedCount = listedCount.valueOr(0) + listTestsNamesOnly( config ); - if( config.listTags() ) - listedCount = listedCount.valueOr(0) + listTags( config ); - if( config.listReporters() ) - listedCount = listedCount.valueOr(0) + listReporters( config ); - return listedCount; - } - -} // end namespace Catch -// end catch_list.cpp -// start catch_matchers.cpp - -namespace Catch { -namespace Matchers { - namespace Impl { - - std::string MatcherUntypedBase::toString() const { - if( m_cachedToString.empty() ) - m_cachedToString = describe(); - return m_cachedToString; - } - - MatcherUntypedBase::~MatcherUntypedBase() = default; - - } // namespace Impl -} // namespace Matchers - -using namespace Matchers; -using Matchers::Impl::MatcherBase; - -} // namespace Catch -// end catch_matchers.cpp -// start catch_matchers_string.cpp - -namespace Catch { -namespace Matchers { - - namespace StdString { - - CasedString::CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_str( adjustString( str ) ) - {} - std::string CasedString::adjustString( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No - ? toLower( str ) - : str; - } - std::string CasedString::caseSensitivitySuffix() const { - return m_caseSensitivity == CaseSensitive::No - ? " (case insensitive)" - : std::string(); - } - - StringMatcherBase::StringMatcherBase( std::string const& operation, CasedString const& comparator ) - : m_comparator( comparator ), - m_operation( operation ) { - } - - std::string StringMatcherBase::describe() const { - std::string description; - description.reserve(5 + m_operation.size() + m_comparator.m_str.size() + - m_comparator.caseSensitivitySuffix().size()); - description += m_operation; - description += ": \""; - description += m_comparator.m_str; - description += "\""; - description += m_comparator.caseSensitivitySuffix(); - return description; - } - - EqualsMatcher::EqualsMatcher( CasedString const& comparator ) : StringMatcherBase( "equals", comparator ) {} - - bool EqualsMatcher::match( std::string const& source ) const { - return m_comparator.adjustString( source ) == m_comparator.m_str; - } - - ContainsMatcher::ContainsMatcher( CasedString const& comparator ) : StringMatcherBase( "contains", comparator ) {} - - bool ContainsMatcher::match( std::string const& source ) const { - return contains( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - StartsWithMatcher::StartsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "starts with", comparator ) {} - - bool StartsWithMatcher::match( std::string const& source ) const { - return startsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - EndsWithMatcher::EndsWithMatcher( CasedString const& comparator ) : StringMatcherBase( "ends with", comparator ) {} - - bool EndsWithMatcher::match( std::string const& source ) const { - return endsWith( m_comparator.adjustString( source ), m_comparator.m_str ); - } - - } // namespace StdString - - StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::EqualsMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::ContainsMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::EndsWithMatcher( StdString::CasedString( str, caseSensitivity) ); - } - StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity ) { - return StdString::StartsWithMatcher( StdString::CasedString( str, caseSensitivity) ); - } - -} // namespace Matchers -} // namespace Catch -// end catch_matchers_string.cpp -// start catch_message.cpp - -namespace Catch { - - MessageInfo::MessageInfo( std::string const& _macroName, - SourceLineInfo const& _lineInfo, - ResultWas::OfType _type ) - : macroName( _macroName ), - lineInfo( _lineInfo ), - type( _type ), - sequence( ++globalCount ) - {} - - bool MessageInfo::operator==( MessageInfo const& other ) const { - return sequence == other.sequence; - } - - bool MessageInfo::operator<( MessageInfo const& other ) const { - return sequence < other.sequence; - } - - // This may need protecting if threading support is added - unsigned int MessageInfo::globalCount = 0; - - //////////////////////////////////////////////////////////////////////////// - - Catch::MessageBuilder::MessageBuilder( std::string const& macroName, - SourceLineInfo const& lineInfo, - ResultWas::OfType type ) - :m_info(macroName, lineInfo, type) {} - - //////////////////////////////////////////////////////////////////////////// - - ScopedMessage::ScopedMessage( MessageBuilder const& builder ) - : m_info( builder.m_info ) - { - m_info.message = builder.m_stream.str(); - getResultCapture().pushScopedMessage( m_info ); - } - - ScopedMessage::~ScopedMessage() { - if ( !std::uncaught_exception() ){ - getResultCapture().popScopedMessage(m_info); - } - } - -} // end namespace Catch -// end catch_message.cpp -// start catch_random_number_generator.cpp - -// start catch_random_number_generator.h - -#include <algorithm> - -namespace Catch { - - struct IConfig; - - void seedRng( IConfig const& config ); - - unsigned int rngSeed(); - - struct RandomNumberGenerator { - using result_type = unsigned int; - - static constexpr result_type (min)() { return 0; } - static constexpr result_type (max)() { return 1000000; } - - result_type operator()( result_type n ) const; - result_type operator()() const; - - template<typename V> - static void shuffle( V& vector ) { - RandomNumberGenerator rng; - std::shuffle( vector.begin(), vector.end(), rng ); - } - }; - -} - -// end catch_random_number_generator.h -#include <cstdlib> - -namespace Catch { - - void seedRng( IConfig const& config ) { - if( config.rngSeed() != 0 ) - std::srand( config.rngSeed() ); - } - unsigned int rngSeed() { - return getCurrentContext().getConfig()->rngSeed(); - } - - RandomNumberGenerator::result_type RandomNumberGenerator::operator()( result_type n ) const { - return std::rand() % n; - } - RandomNumberGenerator::result_type RandomNumberGenerator::operator()() const { - return std::rand() % (max)(); - } - -} -// end catch_random_number_generator.cpp -// start catch_registry_hub.cpp - -// start catch_test_case_registry_impl.h - -#include <vector> -#include <set> -#include <algorithm> -#include <ios> - -namespace Catch { - - class TestCase; - struct IConfig; - - std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - - void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ); - - std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ); - - class TestRegistry : public ITestCaseRegistry { - public: - virtual ~TestRegistry() = default; - - virtual void registerTest( TestCase const& testCase ); - - std::vector<TestCase> const& getAllTests() const override; - std::vector<TestCase> const& getAllTestsSorted( IConfig const& config ) const override; - - private: - std::vector<TestCase> m_functions; - mutable RunTests::InWhatOrder m_currentSortOrder = RunTests::InDeclarationOrder; - mutable std::vector<TestCase> m_sortedFunctions; - std::size_t m_unnamedCount = 0; - std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised - }; - - /////////////////////////////////////////////////////////////////////////// - - class TestInvokerAsFunction : public ITestInvoker { - void(*m_testAsFunction)(); - public: - TestInvokerAsFunction( void(*testAsFunction)() ) noexcept; - - void invoke() const override; - }; - - std::string extractClassName( std::string const& classOrQualifiedMethodName ); - - /////////////////////////////////////////////////////////////////////////// - -} // end namespace Catch - -// end catch_test_case_registry_impl.h -// start catch_reporter_registry.h - -#include <map> - -namespace Catch { - - class ReporterRegistry : public IReporterRegistry { - - public: - - ~ReporterRegistry() override; - - IStreamingReporterPtr create( std::string const& name, IConfigPtr const& config ) const override; - - void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ); - void registerListener( IReporterFactoryPtr const& factory ); - - FactoryMap const& getFactories() const override; - Listeners const& getListeners() const override; - - private: - FactoryMap m_factories; - Listeners m_listeners; - }; -} - -// end catch_reporter_registry.h -// start catch_tag_alias_registry.h - -// start catch_tag_alias.h - -#include <string> - -namespace Catch { - - struct TagAlias { - TagAlias(std::string const& _tag, SourceLineInfo _lineInfo); - - std::string tag; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - -// end catch_tag_alias.h -#include <map> - -namespace Catch { - - class TagAliasRegistry : public ITagAliasRegistry { - public: - ~TagAliasRegistry() override; - TagAlias const* find( std::string const& alias ) const override; - std::string expandAliases( std::string const& unexpandedTestSpec ) const override; - void add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ); - - private: - std::map<std::string, TagAlias> m_registry; - }; - -} // end namespace Catch - -// end catch_tag_alias_registry.h -// start catch_startup_exception_registry.h - -#include <vector> -#include <exception> - -namespace Catch { - - class StartupExceptionRegistry { - public: - void add(std::exception_ptr const& exception) noexcept; - std::vector<std::exception_ptr> const& getExceptions() const noexcept; - private: - std::vector<std::exception_ptr> m_exceptions; - }; - -} // end namespace Catch - -// end catch_startup_exception_registry.h -namespace Catch { - - namespace { - - class RegistryHub : public IRegistryHub, public IMutableRegistryHub, - private NonCopyable { - - public: // IRegistryHub - RegistryHub() = default; - IReporterRegistry const& getReporterRegistry() const override { - return m_reporterRegistry; - } - ITestCaseRegistry const& getTestCaseRegistry() const override { - return m_testCaseRegistry; - } - IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() override { - return m_exceptionTranslatorRegistry; - } - ITagAliasRegistry const& getTagAliasRegistry() const override { - return m_tagAliasRegistry; - } - StartupExceptionRegistry const& getStartupExceptionRegistry() const override { - return m_exceptionRegistry; - } - - public: // IMutableRegistryHub - void registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) override { - m_reporterRegistry.registerReporter( name, factory ); - } - void registerListener( IReporterFactoryPtr const& factory ) override { - m_reporterRegistry.registerListener( factory ); - } - void registerTest( TestCase const& testInfo ) override { - m_testCaseRegistry.registerTest( testInfo ); - } - void registerTranslator( const IExceptionTranslator* translator ) override { - m_exceptionTranslatorRegistry.registerTranslator( translator ); - } - void registerTagAlias( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) override { - m_tagAliasRegistry.add( alias, tag, lineInfo ); - } - void registerStartupException() noexcept override { - m_exceptionRegistry.add(std::current_exception()); - } - - private: - TestRegistry m_testCaseRegistry; - ReporterRegistry m_reporterRegistry; - ExceptionTranslatorRegistry m_exceptionTranslatorRegistry; - TagAliasRegistry m_tagAliasRegistry; - StartupExceptionRegistry m_exceptionRegistry; - }; - - // Single, global, instance - RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = nullptr; - if( !theRegistryHub ) - theRegistryHub = new RegistryHub(); - return theRegistryHub; - } - } - - IRegistryHub& getRegistryHub() { - return *getTheRegistryHub(); - } - IMutableRegistryHub& getMutableRegistryHub() { - return *getTheRegistryHub(); - } - void cleanUp() { - delete getTheRegistryHub(); - getTheRegistryHub() = nullptr; - cleanUpContext(); - } - std::string translateActiveException() { - return getRegistryHub().getExceptionTranslatorRegistry().translateActiveException(); - } - -} // end namespace Catch -// end catch_registry_hub.cpp -// start catch_reporter_registry.cpp - -namespace Catch { - - ReporterRegistry::~ReporterRegistry() = default; - - IStreamingReporterPtr ReporterRegistry::create( std::string const& name, IConfigPtr const& config ) const { - auto it = m_factories.find( name ); - if( it == m_factories.end() ) - return nullptr; - return it->second->create( ReporterConfig( config ) ); - } - - void ReporterRegistry::registerReporter( std::string const& name, IReporterFactoryPtr const& factory ) { - m_factories.emplace(name, factory); - } - void ReporterRegistry::registerListener( IReporterFactoryPtr const& factory ) { - m_listeners.push_back( factory ); - } - - IReporterRegistry::FactoryMap const& ReporterRegistry::getFactories() const { - return m_factories; - } - IReporterRegistry::Listeners const& ReporterRegistry::getListeners() const { - return m_listeners; - } - -} -// end catch_reporter_registry.cpp -// start catch_result_type.cpp - -namespace Catch { - - bool isOk( ResultWas::OfType resultType ) { - return ( resultType & ResultWas::FailureBit ) == 0; - } - bool isJustInfo( int flags ) { - return flags == ResultWas::Info; - } - - ResultDisposition::Flags operator | ( ResultDisposition::Flags lhs, ResultDisposition::Flags rhs ) { - return static_cast<ResultDisposition::Flags>( static_cast<int>( lhs ) | static_cast<int>( rhs ) ); - } - - bool shouldContinueOnFailure( int flags ) { return ( flags & ResultDisposition::ContinueOnFailure ) != 0; } - bool isFalseTest( int flags ) { return ( flags & ResultDisposition::FalseTest ) != 0; } - bool shouldSuppressFailure( int flags ) { return ( flags & ResultDisposition::SuppressFail ) != 0; } - -} // end namespace Catch -// end catch_result_type.cpp -// start catch_run_context.cpp -// start catch_run_context.h - -#include <string> - -namespace Catch { - - struct IMutableContext; - - class StreamRedirect { - - public: - StreamRedirect(std::ostream& stream, std::string& targetString); - - ~StreamRedirect(); - - private: - std::ostream& m_stream; - std::streambuf* m_prevBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; - - // StdErr has two constituent streams in C++, std::cerr and std::clog - // This means that we need to redirect 2 streams into 1 to keep proper - // order of writes and cannot use StreamRedirect on its own - class StdErrRedirect { - public: - StdErrRedirect(std::string& targetString); - ~StdErrRedirect(); - private: - std::streambuf* m_cerrBuf; - std::streambuf* m_clogBuf; - std::ostringstream m_oss; - std::string& m_targetString; - }; - - /////////////////////////////////////////////////////////////////////////// - - class RunContext : public IResultCapture, public IRunner { - - public: - RunContext( RunContext const& ) = delete; - RunContext& operator =( RunContext const& ) = delete; - - explicit RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter); - - virtual ~RunContext(); - - void testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount); - void testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount); - - Totals runTest(TestCase const& testCase); - - IConfigPtr config() const; - IStreamingReporter& reporter() const; - - private: // IResultCapture - - void assertionStarting(AssertionInfo const& info) override; - void assertionEnded(AssertionResult const& result) override; - - bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) override; - bool testForMissingAssertions(Counts& assertions); - - void sectionEnded(SectionEndInfo const& endInfo) override; - void sectionEndedEarly(SectionEndInfo const& endInfo) override; - - void benchmarkStarting( BenchmarkInfo const& info ) override; - void benchmarkEnded( BenchmarkStats const& stats ) override; - - void pushScopedMessage(MessageInfo const& message) override; - void popScopedMessage(MessageInfo const& message) override; - - std::string getCurrentTestName() const override; - - const AssertionResult* getLastResult() const override; - - void exceptionEarlyReported() override; - - void handleFatalErrorCondition( StringRef message ) override; - - bool lastAssertionPassed() override; - - void assertionPassed() override; - - void assertionRun() override; - - public: - // !TBD We need to do this another way! - bool aborting() const override; - - private: - - void runCurrentTest(std::string& redirectedCout, std::string& redirectedCerr); - void invokeActiveTestCase(); - - private: - - void handleUnfinishedSections(); - - TestRunInfo m_runInfo; - IMutableContext& m_context; - TestCase const* m_activeTestCase = nullptr; - ITracker* m_testCaseTracker; - Option<AssertionResult> m_lastResult; - - IConfigPtr m_config; - Totals m_totals; - IStreamingReporterPtr m_reporter; - std::vector<MessageInfo> m_messages; - AssertionInfo m_lastAssertionInfo; - std::vector<SectionEndInfo> m_unfinishedSections; - std::vector<ITracker*> m_activeSections; - TrackerContext m_trackerContext; - std::size_t m_prevPassed = 0; - bool m_shouldReportUnexpected = true; - }; - - IResultCapture& getResultCapture(); - -} // end namespace Catch - -// end catch_run_context.h - -#include <cassert> -#include <algorithm> - -namespace Catch { - - StreamRedirect::StreamRedirect(std::ostream& stream, std::string& targetString) - : m_stream(stream), - m_prevBuf(stream.rdbuf()), - m_targetString(targetString) { - stream.rdbuf(m_oss.rdbuf()); - } - - StreamRedirect::~StreamRedirect() { - m_targetString += m_oss.str(); - m_stream.rdbuf(m_prevBuf); - } - - StdErrRedirect::StdErrRedirect(std::string & targetString) - :m_cerrBuf(cerr().rdbuf()), m_clogBuf(clog().rdbuf()), - m_targetString(targetString) { - cerr().rdbuf(m_oss.rdbuf()); - clog().rdbuf(m_oss.rdbuf()); - } - - StdErrRedirect::~StdErrRedirect() { - m_targetString += m_oss.str(); - cerr().rdbuf(m_cerrBuf); - clog().rdbuf(m_clogBuf); - } - - RunContext::RunContext(IConfigPtr const& _config, IStreamingReporterPtr&& reporter) - : m_runInfo(_config->name()), - m_context(getCurrentMutableContext()), - m_config(_config), - m_reporter(std::move(reporter)), - m_lastAssertionInfo{ "", SourceLineInfo("",0), "", ResultDisposition::Normal } - { - m_context.setRunner(this); - m_context.setConfig(m_config); - m_context.setResultCapture(this); - m_reporter->testRunStarting(m_runInfo); - } - - RunContext::~RunContext() { - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, aborting())); - } - - void RunContext::testGroupStarting(std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupStarting(GroupInfo(testSpec, groupIndex, groupsCount)); - } - - void RunContext::testGroupEnded(std::string const& testSpec, Totals const& totals, std::size_t groupIndex, std::size_t groupsCount) { - m_reporter->testGroupEnded(TestGroupStats(GroupInfo(testSpec, groupIndex, groupsCount), totals, aborting())); - } - - Totals RunContext::runTest(TestCase const& testCase) { - Totals prevTotals = m_totals; - - std::string redirectedCout; - std::string redirectedCerr; - - TestCaseInfo testInfo = testCase.getTestCaseInfo(); - - m_reporter->testCaseStarting(testInfo); - - m_activeTestCase = &testCase; - - ITracker& rootTracker = m_trackerContext.startRun(); - assert(rootTracker.isSectionTracker()); - static_cast<SectionTracker&>(rootTracker).addInitialFilters(m_config->getSectionsToRun()); - do { - m_trackerContext.startCycle(); - m_testCaseTracker = &SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(testInfo.name, testInfo.lineInfo)); - runCurrentTest(redirectedCout, redirectedCerr); - } while (!m_testCaseTracker->isSuccessfullyCompleted() && !aborting()); - - Totals deltaTotals = m_totals.delta(prevTotals); - if (testInfo.expectedToFail() && deltaTotals.testCases.passed > 0) { - deltaTotals.assertions.failed++; - deltaTotals.testCases.passed--; - deltaTotals.testCases.failed++; - } - m_totals.testCases += deltaTotals.testCases; - m_reporter->testCaseEnded(TestCaseStats(testInfo, - deltaTotals, - redirectedCout, - redirectedCerr, - aborting())); - - m_activeTestCase = nullptr; - m_testCaseTracker = nullptr; - - return deltaTotals; - } - - IConfigPtr RunContext::config() const { - return m_config; - } - - IStreamingReporter& RunContext::reporter() const { - return *m_reporter; - } - - void RunContext::assertionStarting(AssertionInfo const& info) { - m_reporter->assertionStarting( info ); - } - void RunContext::assertionEnded(AssertionResult const & result) { - if (result.getResultType() == ResultWas::Ok) { - m_totals.assertions.passed++; - } else if (!result.isOk()) { - if( m_activeTestCase->getTestCaseInfo().okToFail() ) - m_totals.assertions.failedButOk++; - else - m_totals.assertions.failed++; - } - - // We have no use for the return value (whether messages should be cleared), because messages were made scoped - // and should be let to clear themselves out. - static_cast<void>(m_reporter->assertionEnded(AssertionStats(result, m_messages, m_totals))); - - // Reset working state - m_lastAssertionInfo = { "", m_lastAssertionInfo.lineInfo, "{Unknown expression after the reported line}", m_lastAssertionInfo.resultDisposition }; - m_lastResult = result; - } - - bool RunContext::sectionStarted(SectionInfo const & sectionInfo, Counts & assertions) { - ITracker& sectionTracker = SectionTracker::acquire(m_trackerContext, TestCaseTracking::NameAndLocation(sectionInfo.name, sectionInfo.lineInfo)); - if (!sectionTracker.isOpen()) - return false; - m_activeSections.push_back(§ionTracker); - - m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; - - m_reporter->sectionStarting(sectionInfo); - - assertions = m_totals.assertions; - - return true; - } - - bool RunContext::testForMissingAssertions(Counts& assertions) { - if (assertions.total() != 0) - return false; - if (!m_config->warnAboutMissingAssertions()) - return false; - if (m_trackerContext.currentTracker().hasChildren()) - return false; - m_totals.assertions.failed++; - assertions.failed++; - return true; - } - - void RunContext::sectionEnded(SectionEndInfo const & endInfo) { - Counts assertions = m_totals.assertions - endInfo.prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - - if (!m_activeSections.empty()) { - m_activeSections.back()->close(); - m_activeSections.pop_back(); - } - - m_reporter->sectionEnded(SectionStats(endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions)); - m_messages.clear(); - } - - void RunContext::sectionEndedEarly(SectionEndInfo const & endInfo) { - if (m_unfinishedSections.empty()) - m_activeSections.back()->fail(); - else - m_activeSections.back()->close(); - m_activeSections.pop_back(); - - m_unfinishedSections.push_back(endInfo); - } - void RunContext::benchmarkStarting( BenchmarkInfo const& info ) { - m_reporter->benchmarkStarting( info ); - } - void RunContext::benchmarkEnded( BenchmarkStats const& stats ) { - m_reporter->benchmarkEnded( stats ); - } - - void RunContext::pushScopedMessage(MessageInfo const & message) { - m_messages.push_back(message); - } - - void RunContext::popScopedMessage(MessageInfo const & message) { - m_messages.erase(std::remove(m_messages.begin(), m_messages.end(), message), m_messages.end()); - } - - std::string RunContext::getCurrentTestName() const { - return m_activeTestCase - ? m_activeTestCase->getTestCaseInfo().name - : std::string(); - } - - const AssertionResult * RunContext::getLastResult() const { - return &(*m_lastResult); - } - - void RunContext::exceptionEarlyReported() { - m_shouldReportUnexpected = false; - } - - void RunContext::handleFatalErrorCondition( StringRef message ) { - // First notify reporter that bad things happened - m_reporter->fatalErrorEncountered(message); - - // Don't rebuild the result -- the stringification itself can cause more fatal errors - // Instead, fake a result data. - AssertionResultData tempResult( ResultWas::FatalErrorCondition, { false } ); - tempResult.message = message; - AssertionResult result(m_lastAssertionInfo, tempResult); - - getResultCapture().assertionEnded(result); - - handleUnfinishedSections(); - - // Recreate section for test case (as we will lose the one that was in scope) - auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); - - Counts assertions; - assertions.failed = 1; - SectionStats testCaseSectionStats(testCaseSection, assertions, 0, false); - m_reporter->sectionEnded(testCaseSectionStats); - - auto const& testInfo = m_activeTestCase->getTestCaseInfo(); - - Totals deltaTotals; - deltaTotals.testCases.failed = 1; - deltaTotals.assertions.failed = 1; - m_reporter->testCaseEnded(TestCaseStats(testInfo, - deltaTotals, - std::string(), - std::string(), - false)); - m_totals.testCases.failed++; - testGroupEnded(std::string(), m_totals, 1, 1); - m_reporter->testRunEnded(TestRunStats(m_runInfo, m_totals, false)); - } - - bool RunContext::lastAssertionPassed() { - return m_totals.assertions.passed == (m_prevPassed + 1); - } - - void RunContext::assertionPassed() { - ++m_totals.assertions.passed; - m_lastAssertionInfo.capturedExpression = "{Unknown expression after the reported line}"; - m_lastAssertionInfo.macroName = ""; - } - - void RunContext::assertionRun() { - m_prevPassed = m_totals.assertions.passed; - } - - bool RunContext::aborting() const { - return m_totals.assertions.failed == static_cast<std::size_t>(m_config->abortAfter()); - } - - void RunContext::runCurrentTest(std::string & redirectedCout, std::string & redirectedCerr) { - auto const& testCaseInfo = m_activeTestCase->getTestCaseInfo(); - SectionInfo testCaseSection(testCaseInfo.lineInfo, testCaseInfo.name, testCaseInfo.description); - m_reporter->sectionStarting(testCaseSection); - Counts prevAssertions = m_totals.assertions; - double duration = 0; - m_shouldReportUnexpected = true; - try { - m_lastAssertionInfo = { "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal }; - - seedRng(*m_config); - - Timer timer; - timer.start(); - if (m_reporter->getPreferences().shouldRedirectStdOut) { - StreamRedirect coutRedir(cout(), redirectedCout); - StdErrRedirect errRedir(redirectedCerr); - invokeActiveTestCase(); - } else { - invokeActiveTestCase(); - } - duration = timer.getElapsedSeconds(); - } catch (TestFailureException&) { - // This just means the test was aborted due to failure - } catch (...) { - // Under CATCH_CONFIG_FAST_COMPILE, unexpected exceptions under REQUIRE assertions - // are reported without translation at the point of origin. - if (m_shouldReportUnexpected) { - AssertionHandler - ( m_lastAssertionInfo.macroName, - m_lastAssertionInfo.lineInfo, - m_lastAssertionInfo.capturedExpression, - m_lastAssertionInfo.resultDisposition ).useActiveException(); - } - } - m_testCaseTracker->close(); - handleUnfinishedSections(); - m_messages.clear(); - - Counts assertions = m_totals.assertions - prevAssertions; - bool missingAssertions = testForMissingAssertions(assertions); - SectionStats testCaseSectionStats(testCaseSection, assertions, duration, missingAssertions); - m_reporter->sectionEnded(testCaseSectionStats); - } - - void RunContext::invokeActiveTestCase() { - FatalConditionHandler fatalConditionHandler; // Handle signals - m_activeTestCase->invoke(); - fatalConditionHandler.reset(); - } - - void RunContext::handleUnfinishedSections() { - // If sections ended prematurely due to an exception we stored their - // infos here so we can tear them down outside the unwind process. - for (auto it = m_unfinishedSections.rbegin(), - itEnd = m_unfinishedSections.rend(); - it != itEnd; - ++it) - sectionEnded(*it); - m_unfinishedSections.clear(); - } - - IResultCapture& getResultCapture() { - if (auto* capture = getCurrentContext().getResultCapture()) - return *capture; - else - CATCH_INTERNAL_ERROR("No result capture instance"); - } -} -// end catch_run_context.cpp -// start catch_section.cpp - -namespace Catch { - - Section::Section( SectionInfo const& info ) - : m_info( info ), - m_sectionIncluded( getResultCapture().sectionStarted( m_info, m_assertions ) ) - { - m_timer.start(); - } - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4996) // std::uncaught_exception is deprecated in C++17 -#endif - Section::~Section() { - if( m_sectionIncluded ) { - SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); - if( std::uncaught_exception() ) - getResultCapture().sectionEndedEarly( endInfo ); - else - getResultCapture().sectionEnded( endInfo ); - } - } -#if defined(_MSC_VER) -#pragma warning(pop) -#endif - - // This indicates whether the section should be executed or not - Section::operator bool() const { - return m_sectionIncluded; - } - -} // end namespace Catch -// end catch_section.cpp -// start catch_section_info.cpp - -namespace Catch { - - SectionInfo::SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description ) - : name( _name ), - description( _description ), - lineInfo( _lineInfo ) - {} - - SectionEndInfo::SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) - : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - -} // end namespace Catch -// end catch_section_info.cpp -// start catch_session.cpp - -// start catch_session.h - -#include <memory> - -namespace Catch { - - class Session : NonCopyable { - public: - - Session(); - ~Session() override; - - void showHelp() const; - void libIdentify(); - - int applyCommandLine( int argc, char* argv[] ); - - void useConfigData( ConfigData const& configData ); - - int run( int argc, char* argv[] ); - #if defined(WIN32) && defined(UNICODE) - int run( int argc, wchar_t* const argv[] ); - #endif - int run(); - - clara::Parser const& cli() const; - void cli( clara::Parser const& newParser ); - ConfigData& configData(); - Config& config(); - private: - int runInternal(); - - clara::Parser m_cli; - ConfigData m_configData; - std::shared_ptr<Config> m_config; - bool m_startupExceptions = false; - }; - -} // end namespace Catch - -// end catch_session.h -// start catch_version.h - -#include <iosfwd> - -namespace Catch { - - // Versioning information - struct Version { - Version( Version const& ) = delete; - Version& operator=( Version const& ) = delete; - Version( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - char const * const _branchName, - unsigned int _buildNumber ); - - unsigned int const majorVersion; - unsigned int const minorVersion; - unsigned int const patchNumber; - - // buildNumber is only used if branchName is not null - char const * const branchName; - unsigned int const buildNumber; - - friend std::ostream& operator << ( std::ostream& os, Version const& version ); - }; - - Version const& libraryVersion(); -} - -// end catch_version.h -#include <cstdlib> -#include <iomanip> - -namespace { - const int MaxExitCode = 255; - using Catch::IStreamingReporterPtr; - using Catch::IConfigPtr; - using Catch::Config; - - IStreamingReporterPtr createReporter(std::string const& reporterName, IConfigPtr const& config) { - auto reporter = Catch::getRegistryHub().getReporterRegistry().create(reporterName, config); - CATCH_ENFORCE(reporter, "No reporter registered with name: '" << reporterName << "'"); - - return reporter; - } - -#ifndef CATCH_CONFIG_DEFAULT_REPORTER -#define CATCH_CONFIG_DEFAULT_REPORTER "console" -#endif - - IStreamingReporterPtr makeReporter(std::shared_ptr<Config> const& config) { - auto const& reporterNames = config->getReporterNames(); - if (reporterNames.empty()) - return createReporter(CATCH_CONFIG_DEFAULT_REPORTER, config); - - IStreamingReporterPtr reporter; - for (auto const& name : reporterNames) - addReporter(reporter, createReporter(name, config)); - return reporter; - } - -#undef CATCH_CONFIG_DEFAULT_REPORTER - - void addListeners(IStreamingReporterPtr& reporters, IConfigPtr const& config) { - auto const& listeners = Catch::getRegistryHub().getReporterRegistry().getListeners(); - for (auto const& listener : listeners) - addReporter(reporters, listener->create(Catch::ReporterConfig(config))); - } - - Catch::Totals runTests(std::shared_ptr<Config> const& config) { - using namespace Catch; - IStreamingReporterPtr reporter = makeReporter(config); - addListeners(reporter, config); - - RunContext context(config, std::move(reporter)); - - Totals totals; - - context.testGroupStarting(config->name(), 1, 1); - - TestSpec testSpec = config->testSpec(); - if (!testSpec.hasFilters()) - testSpec = TestSpecParser(ITagAliasRegistry::get()).parse("~[.]").testSpec(); // All not hidden tests - - auto const& allTestCases = getAllTestCasesSorted(*config); - for (auto const& testCase : allTestCases) { - if (!context.aborting() && matchTest(testCase, testSpec, *config)) - totals += context.runTest(testCase); - else - context.reporter().skipTest(testCase); - } - - context.testGroupEnded(config->name(), totals, 1, 1); - return totals; - } - - void applyFilenamesAsTags(Catch::IConfig const& config) { - using namespace Catch; - auto& tests = const_cast<std::vector<TestCase>&>(getAllTestCasesSorted(config)); - for (auto& testCase : tests) { - auto tags = testCase.tags; - - std::string filename = testCase.lineInfo.file; - auto lastSlash = filename.find_last_of("\\/"); - if (lastSlash != std::string::npos) { - filename.erase(0, lastSlash); - filename[0] = '#'; - } - - auto lastDot = filename.find_last_of('.'); - if (lastDot != std::string::npos) { - filename.erase(lastDot); - } - - tags.push_back(std::move(filename)); - setTags(testCase, tags); - } - } - -} - -namespace Catch { - - Session::Session() { - static bool alreadyInstantiated = false; - if( alreadyInstantiated ) { - try { CATCH_INTERNAL_ERROR( "Only one instance of Catch::Session can ever be used" ); } - catch(...) { getMutableRegistryHub().registerStartupException(); } - } - - const auto& exceptions = getRegistryHub().getStartupExceptionRegistry().getExceptions(); - if ( !exceptions.empty() ) { - m_startupExceptions = true; - Colour colourGuard( Colour::Red ); - Catch::cerr() << "Errors occured during startup!" << '\n'; - // iterate over all exceptions and notify user - for ( const auto& ex_ptr : exceptions ) { - try { - std::rethrow_exception(ex_ptr); - } catch ( std::exception const& ex ) { - Catch::cerr() << Column( ex.what() ).indent(2) << '\n'; - } - } - } - - alreadyInstantiated = true; - m_cli = makeCommandLineParser( m_configData ); - } - Session::~Session() { - Catch::cleanUp(); - } - - void Session::showHelp() const { - Catch::cout() - << "\nCatch v" << libraryVersion() << "\n" - << m_cli << std::endl - << "For more detailed usage please see the project docs\n" << std::endl; - } - void Session::libIdentify() { - Catch::cout() - << std::left << std::setw(16) << "description: " << "A Catch test executable\n" - << std::left << std::setw(16) << "category: " << "testframework\n" - << std::left << std::setw(16) << "framework: " << "Catch Test\n" - << std::left << std::setw(16) << "version: " << libraryVersion() << std::endl; - } - - int Session::applyCommandLine( int argc, char* argv[] ) { - if( m_startupExceptions ) - return 1; - - auto result = m_cli.parse( clara::Args( argc, argv ) ); - if( !result ) { - Catch::cerr() - << Colour( Colour::Red ) - << "\nError(s) in input:\n" - << Column( result.errorMessage() ).indent( 2 ) - << "\n\n"; - Catch::cerr() << "Run with -? for usage\n" << std::endl; - return MaxExitCode; - } - - if( m_configData.showHelp ) - showHelp(); - if( m_configData.libIdentify ) - libIdentify(); - m_config.reset(); - return 0; - } - - void Session::useConfigData( ConfigData const& configData ) { - m_configData = configData; - m_config.reset(); - } - - int Session::run( int argc, char* argv[] ) { - if( m_startupExceptions ) - return 1; - int returnCode = applyCommandLine( argc, argv ); - if( returnCode == 0 ) - returnCode = run(); - return returnCode; - } - -#if defined(WIN32) && defined(UNICODE) - int Session::run( int argc, wchar_t* const argv[] ) { - - char **utf8Argv = new char *[ argc ]; - - for ( int i = 0; i < argc; ++i ) { - int bufSize = WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, NULL, 0, NULL, NULL ); - - utf8Argv[ i ] = new char[ bufSize ]; - - WideCharToMultiByte( CP_UTF8, 0, argv[i], -1, utf8Argv[i], bufSize, NULL, NULL ); - } - - int returnCode = run( argc, utf8Argv ); - - for ( int i = 0; i < argc; ++i ) - delete [] utf8Argv[ i ]; - - delete [] utf8Argv; - - return returnCode; - } -#endif - int Session::run() { - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeStart ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before starting" << std::endl; - static_cast<void>(std::getchar()); - } - int exitCode = runInternal(); - if( ( m_configData.waitForKeypress & WaitForKeypress::BeforeExit ) != 0 ) { - Catch::cout() << "...waiting for enter/ return before exiting, with code: " << exitCode << std::endl; - static_cast<void>(std::getchar()); - } - return exitCode; - } - - clara::Parser const& Session::cli() const { - return m_cli; - } - void Session::cli( clara::Parser const& newParser ) { - m_cli = newParser; - } - ConfigData& Session::configData() { - return m_configData; - } - Config& Session::config() { - if( !m_config ) - m_config = std::make_shared<Config>( m_configData ); - return *m_config; - } - - int Session::runInternal() { - if( m_startupExceptions ) - return 1; - - if( m_configData.showHelp || m_configData.libIdentify ) - return 0; - - try - { - config(); // Force config to be constructed - - seedRng( *m_config ); - - if( m_configData.filenamesAsTags ) - applyFilenamesAsTags( *m_config ); - - // Handle list request - if( Option<std::size_t> listed = list( config() ) ) - return static_cast<int>( *listed ); - - return (std::min)( MaxExitCode, static_cast<int>( runTests( m_config ).assertions.failed ) ); - } - catch( std::exception& ex ) { - Catch::cerr() << ex.what() << std::endl; - return MaxExitCode; - } - } - -} // end namespace Catch -// end catch_session.cpp -// start catch_startup_exception_registry.cpp - -namespace Catch { - void StartupExceptionRegistry::add( std::exception_ptr const& exception ) noexcept { - try { - m_exceptions.push_back(exception); - } - catch(...) { - // If we run out of memory during start-up there's really not a lot more we can do about it - std::terminate(); - } - } - - std::vector<std::exception_ptr> const& StartupExceptionRegistry::getExceptions() const noexcept { - return m_exceptions; - } - -} // end namespace Catch -// end catch_startup_exception_registry.cpp -// start catch_stream.cpp - -#include <stdexcept> -#include <cstdio> -#include <iostream> - -namespace Catch { - - template<typename WriterF, std::size_t bufferSize=256> - class StreamBufImpl : public StreamBufBase { - char data[bufferSize]; - WriterF m_writer; - - public: - StreamBufImpl() { - setp( data, data + sizeof(data) ); - } - - ~StreamBufImpl() noexcept { - StreamBufImpl::sync(); - } - - private: - int overflow( int c ) override { - sync(); - - if( c != EOF ) { - if( pbase() == epptr() ) - m_writer( std::string( 1, static_cast<char>( c ) ) ); - else - sputc( static_cast<char>( c ) ); - } - return 0; - } - - int sync() override { - if( pbase() != pptr() ) { - m_writer( std::string( pbase(), static_cast<std::string::size_type>( pptr() - pbase() ) ) ); - setp( pbase(), epptr() ); - } - return 0; - } - }; - - /////////////////////////////////////////////////////////////////////////// - - Catch::IStream::~IStream() = default; - - FileStream::FileStream( std::string const& filename ) { - m_ofs.open( filename.c_str() ); - CATCH_ENFORCE( !m_ofs.fail(), "Unable to open file: '" << filename << "'" ); - } - - std::ostream& FileStream::stream() const { - return m_ofs; - } - - struct OutputDebugWriter { - - void operator()( std::string const&str ) { - writeToDebugConsole( str ); - } - }; - - DebugOutStream::DebugOutStream() - : m_streamBuf( new StreamBufImpl<OutputDebugWriter>() ), - m_os( m_streamBuf.get() ) - {} - - std::ostream& DebugOutStream::stream() const { - return m_os; - } - - // Store the streambuf from cout up-front because - // cout may get redirected when running tests - CoutStream::CoutStream() - : m_os( Catch::cout().rdbuf() ) - {} - - std::ostream& CoutStream::stream() const { - return m_os; - } - -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions - std::ostream& cout() { - return std::cout; - } - std::ostream& cerr() { - return std::cerr; - } - std::ostream& clog() { - return std::clog; - } -#endif -} -// end catch_stream.cpp -// start catch_streambuf.cpp - -namespace Catch { - StreamBufBase::~StreamBufBase() = default; -} -// end catch_streambuf.cpp -// start catch_string_manip.cpp - -#include <algorithm> -#include <ostream> -#include <cstring> -#include <cctype> - -namespace Catch { - - bool startsWith( std::string const& s, std::string const& prefix ) { - return s.size() >= prefix.size() && std::equal(prefix.begin(), prefix.end(), s.begin()); - } - bool startsWith( std::string const& s, char prefix ) { - return !s.empty() && s[0] == prefix; - } - bool endsWith( std::string const& s, std::string const& suffix ) { - return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin()); - } - bool endsWith( std::string const& s, char suffix ) { - return !s.empty() && s[s.size()-1] == suffix; - } - bool contains( std::string const& s, std::string const& infix ) { - return s.find( infix ) != std::string::npos; - } - char toLowerCh(char c) { - return static_cast<char>( std::tolower( c ) ); - } - void toLowerInPlace( std::string& s ) { - std::transform( s.begin(), s.end(), s.begin(), toLowerCh ); - } - std::string toLower( std::string const& s ) { - std::string lc = s; - toLowerInPlace( lc ); - return lc; - } - std::string trim( std::string const& str ) { - static char const* whitespaceChars = "\n\r\t "; - std::string::size_type start = str.find_first_not_of( whitespaceChars ); - std::string::size_type end = str.find_last_not_of( whitespaceChars ); - - return start != std::string::npos ? str.substr( start, 1+end-start ) : std::string(); - } - - bool replaceInPlace( std::string& str, std::string const& replaceThis, std::string const& withThis ) { - bool replaced = false; - std::size_t i = str.find( replaceThis ); - while( i != std::string::npos ) { - replaced = true; - str = str.substr( 0, i ) + withThis + str.substr( i+replaceThis.size() ); - if( i < str.size()-withThis.size() ) - i = str.find( replaceThis, i+withThis.size() ); - else - i = std::string::npos; - } - return replaced; - } - - pluralise::pluralise( std::size_t count, std::string const& label ) - : m_count( count ), - m_label( label ) - {} - - std::ostream& operator << ( std::ostream& os, pluralise const& pluraliser ) { - os << pluraliser.m_count << ' ' << pluraliser.m_label; - if( pluraliser.m_count != 1 ) - os << 's'; - return os; - } - -} -// end catch_string_manip.cpp -// start catch_stringref.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -#include <ostream> -#include <cassert> -#include <cstring> - -namespace Catch { - - auto getEmptyStringRef() -> StringRef { - static StringRef s_emptyStringRef(""); - return s_emptyStringRef; - } - - StringRef::StringRef() noexcept - : StringRef( getEmptyStringRef() ) - {} - - StringRef::StringRef( StringRef const& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ) - {} - - StringRef::StringRef( StringRef&& other ) noexcept - : m_start( other.m_start ), - m_size( other.m_size ), - m_data( other.m_data ) - { - other.m_data = nullptr; - } - - StringRef::StringRef( char const* rawChars ) noexcept - : m_start( rawChars ), - m_size( static_cast<size_type>( std::strlen( rawChars ) ) ) - { - assert( rawChars != nullptr ); - } - - StringRef::StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - { - size_type rawSize = rawChars == nullptr ? 0 : static_cast<size_type>( std::strlen( rawChars ) ); - if( rawSize < size ) - m_size = rawSize; - } - - StringRef::StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - StringRef::~StringRef() noexcept { - delete[] m_data; - } - - auto StringRef::operator = ( StringRef other ) noexcept -> StringRef& { - swap( other ); - return *this; - } - StringRef::operator std::string() const { - return std::string( m_start, m_size ); - } - - void StringRef::swap( StringRef& other ) noexcept { - std::swap( m_start, other.m_start ); - std::swap( m_size, other.m_size ); - std::swap( m_data, other.m_data ); - } - - auto StringRef::c_str() const -> char const* { - if( isSubstring() ) - const_cast<StringRef*>( this )->takeOwnership(); - return m_start; - } - auto StringRef::data() const noexcept -> char const* { - return m_start; - } - - auto StringRef::isOwned() const noexcept -> bool { - return m_data != nullptr; - } - auto StringRef::isSubstring() const noexcept -> bool { - return m_start[m_size] != '\0'; - } - - void StringRef::takeOwnership() { - if( !isOwned() ) { - m_data = new char[m_size+1]; - memcpy( m_data, m_start, m_size ); - m_data[m_size] = '\0'; - m_start = m_data; - } - } - auto StringRef::substr( size_type start, size_type size ) const noexcept -> StringRef { - if( start < m_size ) - return StringRef( m_start+start, size ); - else - return StringRef(); - } - auto StringRef::operator == ( StringRef const& other ) const noexcept -> bool { - return - size() == other.size() && - (std::strncmp( m_start, other.m_start, size() ) == 0); - } - auto StringRef::operator != ( StringRef const& other ) const noexcept -> bool { - return !operator==( other ); - } - - auto StringRef::operator[](size_type index) const noexcept -> char { - return m_start[index]; - } - - auto StringRef::empty() const noexcept -> bool { - return m_size == 0; - } - - auto StringRef::size() const noexcept -> size_type { - return m_size; - } - auto StringRef::numberOfCharacters() const noexcept -> size_type { - size_type noChars = m_size; - // Make adjustments for uft encodings - for( size_type i=0; i < m_size; ++i ) { - char c = m_start[i]; - if( ( c & 0b11000000 ) == 0b11000000 ) { - if( ( c & 0b11100000 ) == 0b11000000 ) - noChars--; - else if( ( c & 0b11110000 ) == 0b11100000 ) - noChars-=2; - else if( ( c & 0b11111000 ) == 0b11110000 ) - noChars-=3; - } - } - return noChars; - } - - auto operator + ( StringRef const& lhs, StringRef const& rhs ) -> std::string { - std::string str; - str.reserve( lhs.size() + rhs.size() ); - str += lhs; - str += rhs; - return str; - } - auto operator + ( StringRef const& lhs, const char* rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); - } - auto operator + ( char const* lhs, StringRef const& rhs ) -> std::string { - return std::string( lhs ) + std::string( rhs ); - } - - auto operator << ( std::ostream& os, StringRef const& str ) -> std::ostream& { - return os << str.c_str(); - } - -} // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif -// end catch_stringref.cpp -// start catch_tag_alias.cpp - -namespace Catch { - TagAlias::TagAlias(std::string const & _tag, SourceLineInfo _lineInfo): tag(_tag), lineInfo(_lineInfo) {} -} -// end catch_tag_alias.cpp -// start catch_tag_alias_autoregistrar.cpp - -namespace Catch { - - RegistrarForTagAliases::RegistrarForTagAliases(char const* alias, char const* tag, SourceLineInfo const& lineInfo) { - try { - getMutableRegistryHub().registerTagAlias(alias, tag, lineInfo); - } catch (...) { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - -} -// end catch_tag_alias_autoregistrar.cpp -// start catch_tag_alias_registry.cpp - -namespace Catch { - - TagAliasRegistry::~TagAliasRegistry() {} - - TagAlias const* TagAliasRegistry::find( std::string const& alias ) const { - auto it = m_registry.find( alias ); - if( it != m_registry.end() ) - return &(it->second); - else - return nullptr; - } - - std::string TagAliasRegistry::expandAliases( std::string const& unexpandedTestSpec ) const { - std::string expandedTestSpec = unexpandedTestSpec; - for( auto const& registryKvp : m_registry ) { - std::size_t pos = expandedTestSpec.find( registryKvp.first ); - if( pos != std::string::npos ) { - expandedTestSpec = expandedTestSpec.substr( 0, pos ) + - registryKvp.second.tag + - expandedTestSpec.substr( pos + registryKvp.first.size() ); - } - } - return expandedTestSpec; - } - - void TagAliasRegistry::add( std::string const& alias, std::string const& tag, SourceLineInfo const& lineInfo ) { - CATCH_ENFORCE( startsWith(alias, "[@") && endsWith(alias, ']'), - "error: tag alias, '" << alias << "' is not of the form [@alias name].\n" << lineInfo ); - - CATCH_ENFORCE( m_registry.insert(std::make_pair(alias, TagAlias(tag, lineInfo))).second, - "error: tag alias, '" << alias << "' already registered.\n" - << "\tFirst seen at: " << find(alias)->lineInfo << "\n" - << "\tRedefined at: " << lineInfo ); - } - - ITagAliasRegistry::~ITagAliasRegistry() {} - - ITagAliasRegistry const& ITagAliasRegistry::get() { - return getRegistryHub().getTagAliasRegistry(); - } - -} // end namespace Catch -// end catch_tag_alias_registry.cpp -// start catch_test_case_info.cpp - -#include <cctype> -#include <exception> -#include <algorithm> - -namespace Catch { - - TestCaseInfo::SpecialProperties parseSpecialTag( std::string const& tag ) { - if( startsWith( tag, '.' ) || - tag == "!hide" ) - return TestCaseInfo::IsHidden; - else if( tag == "!throws" ) - return TestCaseInfo::Throws; - else if( tag == "!shouldfail" ) - return TestCaseInfo::ShouldFail; - else if( tag == "!mayfail" ) - return TestCaseInfo::MayFail; - else if( tag == "!nonportable" ) - return TestCaseInfo::NonPortable; - else if( tag == "!benchmark" ) - return static_cast<TestCaseInfo::SpecialProperties>( TestCaseInfo::Benchmark | TestCaseInfo::IsHidden ); - else - return TestCaseInfo::None; - } - bool isReservedTag( std::string const& tag ) { - return parseSpecialTag( tag ) == TestCaseInfo::None && tag.size() > 0 && !std::isalnum( tag[0] ); - } - void enforceNotReservedTag( std::string const& tag, SourceLineInfo const& _lineInfo ) { - CATCH_ENFORCE( !isReservedTag(tag), - "Tag name: [" << tag << "] is not allowed.\n" - << "Tag names starting with non alpha-numeric characters are reserved\n" - << _lineInfo ); - } - - TestCase makeTestCase( ITestInvoker* _testCase, - std::string const& _className, - std::string const& _name, - std::string const& _descOrTags, - SourceLineInfo const& _lineInfo ) - { - bool isHidden = false; - - // Parse out tags - std::vector<std::string> tags; - std::string desc, tag; - bool inTag = false; - for (char c : _descOrTags) { - if( !inTag ) { - if( c == '[' ) - inTag = true; - else - desc += c; - } - else { - if( c == ']' ) { - TestCaseInfo::SpecialProperties prop = parseSpecialTag( tag ); - if( ( prop & TestCaseInfo::IsHidden ) != 0 ) - isHidden = true; - else if( prop == TestCaseInfo::None ) - enforceNotReservedTag( tag, _lineInfo ); - - tags.push_back( tag ); - tag.clear(); - inTag = false; - } - else - tag += c; - } - } - if( isHidden ) { - tags.push_back( "." ); - } - - TestCaseInfo info( _name, _className, desc, tags, _lineInfo ); - return TestCase( _testCase, info ); - } - - void setTags( TestCaseInfo& testCaseInfo, std::vector<std::string> tags ) { - std::sort(begin(tags), end(tags)); - tags.erase(std::unique(begin(tags), end(tags)), end(tags)); - testCaseInfo.lcaseTags.clear(); - - for( auto const& tag : tags ) { - std::string lcaseTag = toLower( tag ); - testCaseInfo.properties = static_cast<TestCaseInfo::SpecialProperties>( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); - testCaseInfo.lcaseTags.push_back( lcaseTag ); - } - testCaseInfo.tags = std::move(tags); - } - - TestCaseInfo::TestCaseInfo( std::string const& _name, - std::string const& _className, - std::string const& _description, - std::vector<std::string> const& _tags, - SourceLineInfo const& _lineInfo ) - : name( _name ), - className( _className ), - description( _description ), - lineInfo( _lineInfo ), - properties( None ) - { - setTags( *this, _tags ); - } - - bool TestCaseInfo::isHidden() const { - return ( properties & IsHidden ) != 0; - } - bool TestCaseInfo::throws() const { - return ( properties & Throws ) != 0; - } - bool TestCaseInfo::okToFail() const { - return ( properties & (ShouldFail | MayFail ) ) != 0; - } - bool TestCaseInfo::expectedToFail() const { - return ( properties & (ShouldFail ) ) != 0; - } - - std::string TestCaseInfo::tagsAsString() const { - std::string ret; - // '[' and ']' per tag - std::size_t full_size = 2 * tags.size(); - for (const auto& tag : tags) { - full_size += tag.size(); - } - ret.reserve(full_size); - for (const auto& tag : tags) { - ret.push_back('['); - ret.append(tag); - ret.push_back(']'); - } - - return ret; - } - - TestCase::TestCase( ITestInvoker* testCase, TestCaseInfo const& info ) : TestCaseInfo( info ), test( testCase ) {} - - TestCase TestCase::withName( std::string const& _newName ) const { - TestCase other( *this ); - other.name = _newName; - return other; - } - - void TestCase::invoke() const { - test->invoke(); - } - - bool TestCase::operator == ( TestCase const& other ) const { - return test.get() == other.test.get() && - name == other.name && - className == other.className; - } - - bool TestCase::operator < ( TestCase const& other ) const { - return name < other.name; - } - - TestCaseInfo const& TestCase::getTestCaseInfo() const - { - return *this; - } - -} // end namespace Catch -// end catch_test_case_info.cpp -// start catch_test_case_registry_impl.cpp - -#include <sstream> - -namespace Catch { - - std::vector<TestCase> sortTests( IConfig const& config, std::vector<TestCase> const& unsortedTestCases ) { - - std::vector<TestCase> sorted = unsortedTestCases; - - switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( sorted.begin(), sorted.end() ); - break; - case RunTests::InRandomOrder: - seedRng( config ); - RandomNumberGenerator::shuffle( sorted ); - break; - case RunTests::InDeclarationOrder: - // already in declaration order - break; - } - return sorted; - } - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { - return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); - } - - void enforceNoDuplicateTestCases( std::vector<TestCase> const& functions ) { - std::set<TestCase> seenFunctions; - for( auto const& function : functions ) { - auto prev = seenFunctions.insert( function ); - CATCH_ENFORCE( prev.second, - "error: TEST_CASE( \"" << function.name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << function.getTestCaseInfo().lineInfo ); - } - } - - std::vector<TestCase> filterTests( std::vector<TestCase> const& testCases, TestSpec const& testSpec, IConfig const& config ) { - std::vector<TestCase> filtered; - filtered.reserve( testCases.size() ); - for( auto const& testCase : testCases ) - if( matchTest( testCase, testSpec, config ) ) - filtered.push_back( testCase ); - return filtered; - } - std::vector<TestCase> const& getAllTestCasesSorted( IConfig const& config ) { - return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); - } - - void TestRegistry::registerTest( TestCase const& testCase ) { - std::string name = testCase.getTestCaseInfo().name; - if( name.empty() ) { - std::ostringstream oss; - oss << "Anonymous test case " << ++m_unnamedCount; - return registerTest( testCase.withName( oss.str() ) ); - } - m_functions.push_back( testCase ); - } - - std::vector<TestCase> const& TestRegistry::getAllTests() const { - return m_functions; - } - std::vector<TestCase> const& TestRegistry::getAllTestsSorted( IConfig const& config ) const { - if( m_sortedFunctions.empty() ) - enforceNoDuplicateTestCases( m_functions ); - - if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { - m_sortedFunctions = sortTests( config, m_functions ); - m_currentSortOrder = config.runOrder(); - } - return m_sortedFunctions; - } - - /////////////////////////////////////////////////////////////////////////// - TestInvokerAsFunction::TestInvokerAsFunction( void(*testAsFunction)() ) noexcept : m_testAsFunction( testAsFunction ) {} - - void TestInvokerAsFunction::invoke() const { - m_testAsFunction(); - } - - std::string extractClassName( std::string const& classOrQualifiedMethodName ) { - std::string className = classOrQualifiedMethodName; - if( startsWith( className, '&' ) ) - { - std::size_t lastColons = className.rfind( "::" ); - std::size_t penultimateColons = className.rfind( "::", lastColons-1 ); - if( penultimateColons == std::string::npos ) - penultimateColons = 1; - className = className.substr( penultimateColons, lastColons-penultimateColons ); - } - return className; - } - -} // end namespace Catch -// end catch_test_case_registry_impl.cpp -// start catch_test_case_tracker.cpp - -#include <algorithm> -#include <assert.h> -#include <stdexcept> -#include <memory> - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -#endif - -namespace Catch { -namespace TestCaseTracking { - - NameAndLocation::NameAndLocation( std::string const& _name, SourceLineInfo const& _location ) - : name( _name ), - location( _location ) - {} - - ITracker::~ITracker() = default; - - TrackerContext& TrackerContext::instance() { - static TrackerContext s_instance; - return s_instance; - } - - ITracker& TrackerContext::startRun() { - m_rootTracker = std::make_shared<SectionTracker>( NameAndLocation( "{root}", CATCH_INTERNAL_LINEINFO ), *this, nullptr ); - m_currentTracker = nullptr; - m_runState = Executing; - return *m_rootTracker; - } - - void TrackerContext::endRun() { - m_rootTracker.reset(); - m_currentTracker = nullptr; - m_runState = NotStarted; - } - - void TrackerContext::startCycle() { - m_currentTracker = m_rootTracker.get(); - m_runState = Executing; - } - void TrackerContext::completeCycle() { - m_runState = CompletedCycle; - } - - bool TrackerContext::completedCycle() const { - return m_runState == CompletedCycle; - } - ITracker& TrackerContext::currentTracker() { - return *m_currentTracker; - } - void TrackerContext::setCurrentTracker( ITracker* tracker ) { - m_currentTracker = tracker; - } - - TrackerBase::TrackerHasName::TrackerHasName( NameAndLocation const& nameAndLocation ) : m_nameAndLocation( nameAndLocation ) {} - bool TrackerBase::TrackerHasName::operator ()( ITrackerPtr const& tracker ) const { - return - tracker->nameAndLocation().name == m_nameAndLocation.name && - tracker->nameAndLocation().location == m_nameAndLocation.location; - } - - TrackerBase::TrackerBase( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : m_nameAndLocation( nameAndLocation ), - m_ctx( ctx ), - m_parent( parent ) - {} - - NameAndLocation const& TrackerBase::nameAndLocation() const { - return m_nameAndLocation; - } - bool TrackerBase::isComplete() const { - return m_runState == CompletedSuccessfully || m_runState == Failed; - } - bool TrackerBase::isSuccessfullyCompleted() const { - return m_runState == CompletedSuccessfully; - } - bool TrackerBase::isOpen() const { - return m_runState != NotStarted && !isComplete(); - } - bool TrackerBase::hasChildren() const { - return !m_children.empty(); - } - - void TrackerBase::addChild( ITrackerPtr const& child ) { - m_children.push_back( child ); - } - - ITrackerPtr TrackerBase::findChild( NameAndLocation const& nameAndLocation ) { - auto it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( nameAndLocation ) ); - return( it != m_children.end() ) - ? *it - : nullptr; - } - ITracker& TrackerBase::parent() { - assert( m_parent ); // Should always be non-null except for root - return *m_parent; - } - - void TrackerBase::openChild() { - if( m_runState != ExecutingChildren ) { - m_runState = ExecutingChildren; - if( m_parent ) - m_parent->openChild(); - } - } - - bool TrackerBase::isSectionTracker() const { return false; } - bool TrackerBase::isIndexTracker() const { return false; } - - void TrackerBase::open() { - m_runState = Executing; - moveToThis(); - if( m_parent ) - m_parent->openChild(); - } - - void TrackerBase::close() { - - // Close any still open children (e.g. generators) - while( &m_ctx.currentTracker() != this ) - m_ctx.currentTracker().close(); - - switch( m_runState ) { - case NeedsAnotherRun: - break; - - case Executing: - m_runState = CompletedSuccessfully; - break; - case ExecutingChildren: - if( m_children.empty() || m_children.back()->isComplete() ) - m_runState = CompletedSuccessfully; - break; - - case NotStarted: - case CompletedSuccessfully: - case Failed: - CATCH_INTERNAL_ERROR( "Illogical state: " << m_runState ); - - default: - CATCH_INTERNAL_ERROR( "Unknown state: " << m_runState ); - } - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::fail() { - m_runState = Failed; - if( m_parent ) - m_parent->markAsNeedingAnotherRun(); - moveToParent(); - m_ctx.completeCycle(); - } - void TrackerBase::markAsNeedingAnotherRun() { - m_runState = NeedsAnotherRun; - } - - void TrackerBase::moveToParent() { - assert( m_parent ); - m_ctx.setCurrentTracker( m_parent ); - } - void TrackerBase::moveToThis() { - m_ctx.setCurrentTracker( this ); - } - - SectionTracker::SectionTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent ) - : TrackerBase( nameAndLocation, ctx, parent ) - { - if( parent ) { - while( !parent->isSectionTracker() ) - parent = &parent->parent(); - - SectionTracker& parentSection = static_cast<SectionTracker&>( *parent ); - addNextFilters( parentSection.m_filters ); - } - } - - bool SectionTracker::isSectionTracker() const { return true; } - - SectionTracker& SectionTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation ) { - std::shared_ptr<SectionTracker> section; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isSectionTracker() ); - section = std::static_pointer_cast<SectionTracker>( childTracker ); - } - else { - section = std::make_shared<SectionTracker>( nameAndLocation, ctx, ¤tTracker ); - currentTracker.addChild( section ); - } - if( !ctx.completedCycle() ) - section->tryOpen(); - return *section; - } - - void SectionTracker::tryOpen() { - if( !isComplete() && (m_filters.empty() || m_filters[0].empty() || m_filters[0] == m_nameAndLocation.name ) ) - open(); - } - - void SectionTracker::addInitialFilters( std::vector<std::string> const& filters ) { - if( !filters.empty() ) { - m_filters.push_back(""); // Root - should never be consulted - m_filters.push_back(""); // Test Case - not a section filter - m_filters.insert( m_filters.end(), filters.begin(), filters.end() ); - } - } - void SectionTracker::addNextFilters( std::vector<std::string> const& filters ) { - if( filters.size() > 1 ) - m_filters.insert( m_filters.end(), ++filters.begin(), filters.end() ); - } - - IndexTracker::IndexTracker( NameAndLocation const& nameAndLocation, TrackerContext& ctx, ITracker* parent, int size ) - : TrackerBase( nameAndLocation, ctx, parent ), - m_size( size ) - {} - - bool IndexTracker::isIndexTracker() const { return true; } - - IndexTracker& IndexTracker::acquire( TrackerContext& ctx, NameAndLocation const& nameAndLocation, int size ) { - std::shared_ptr<IndexTracker> tracker; - - ITracker& currentTracker = ctx.currentTracker(); - if( ITrackerPtr childTracker = currentTracker.findChild( nameAndLocation ) ) { - assert( childTracker ); - assert( childTracker->isIndexTracker() ); - tracker = std::static_pointer_cast<IndexTracker>( childTracker ); - } - else { - tracker = std::make_shared<IndexTracker>( nameAndLocation, ctx, ¤tTracker, size ); - currentTracker.addChild( tracker ); - } - - if( !ctx.completedCycle() && !tracker->isComplete() ) { - if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) - tracker->moveNext(); - tracker->open(); - } - - return *tracker; - } - - int IndexTracker::index() const { return m_index; } - - void IndexTracker::moveNext() { - m_index++; - m_children.clear(); - } - - void IndexTracker::close() { - TrackerBase::close(); - if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) - m_runState = Executing; - } - -} // namespace TestCaseTracking - -using TestCaseTracking::ITracker; -using TestCaseTracking::TrackerContext; -using TestCaseTracking::SectionTracker; -using TestCaseTracking::IndexTracker; - -} // namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif -// end catch_test_case_tracker.cpp -// start catch_test_registry.cpp - -namespace Catch { - - auto makeTestInvoker( void(*testAsFunction)() ) noexcept -> ITestInvoker* { - return new(std::nothrow) TestInvokerAsFunction( testAsFunction ); - } - - NameAndTags::NameAndTags( StringRef name_ , StringRef tags_ ) noexcept : name( name_ ), tags( tags_ ) {} - - AutoReg::AutoReg( ITestInvoker* invoker, SourceLineInfo const& lineInfo, StringRef classOrMethod, NameAndTags const& nameAndTags ) noexcept { - try { - getMutableRegistryHub() - .registerTest( - makeTestCase( - invoker, - extractClassName( classOrMethod ), - nameAndTags.name, - nameAndTags.tags, - lineInfo)); - } catch (...) { - // Do not throw when constructing global objects, instead register the exception to be processed later - getMutableRegistryHub().registerStartupException(); - } - } - - AutoReg::~AutoReg() = default; -} -// end catch_test_registry.cpp -// start catch_test_spec.cpp - -#include <algorithm> -#include <string> -#include <vector> -#include <memory> - -namespace Catch { - - TestSpec::Pattern::~Pattern() = default; - TestSpec::NamePattern::~NamePattern() = default; - TestSpec::TagPattern::~TagPattern() = default; - TestSpec::ExcludedPattern::~ExcludedPattern() = default; - - TestSpec::NamePattern::NamePattern( std::string const& name ) - : m_wildcardPattern( toLower( name ), CaseSensitive::No ) - {} - bool TestSpec::NamePattern::matches( TestCaseInfo const& testCase ) const { - return m_wildcardPattern.matches( toLower( testCase.name ) ); - } - - TestSpec::TagPattern::TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} - bool TestSpec::TagPattern::matches( TestCaseInfo const& testCase ) const { - return std::find(begin(testCase.lcaseTags), - end(testCase.lcaseTags), - m_tag) != end(testCase.lcaseTags); - } - - TestSpec::ExcludedPattern::ExcludedPattern( PatternPtr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} - bool TestSpec::ExcludedPattern::matches( TestCaseInfo const& testCase ) const { return !m_underlyingPattern->matches( testCase ); } - - bool TestSpec::Filter::matches( TestCaseInfo const& testCase ) const { - // All patterns in a filter must match for the filter to be a match - for( auto const& pattern : m_patterns ) { - if( !pattern->matches( testCase ) ) - return false; - } - return true; - } - - bool TestSpec::hasFilters() const { - return !m_filters.empty(); - } - bool TestSpec::matches( TestCaseInfo const& testCase ) const { - // A TestSpec matches if any filter matches - for( auto const& filter : m_filters ) - if( filter.matches( testCase ) ) - return true; - return false; - } -} -// end catch_test_spec.cpp -// start catch_test_spec_parser.cpp - -namespace Catch { - - TestSpecParser::TestSpecParser( ITagAliasRegistry const& tagAliases ) : m_tagAliases( &tagAliases ) {} - - TestSpecParser& TestSpecParser::parse( std::string const& arg ) { - m_mode = None; - m_exclusion = false; - m_start = std::string::npos; - m_arg = m_tagAliases->expandAliases( arg ); - m_escapeChars.clear(); - for( m_pos = 0; m_pos < m_arg.size(); ++m_pos ) - visitChar( m_arg[m_pos] ); - if( m_mode == Name ) - addPattern<TestSpec::NamePattern>(); - return *this; - } - TestSpec TestSpecParser::testSpec() { - addFilter(); - return m_testSpec; - } - - void TestSpecParser::visitChar( char c ) { - if( m_mode == None ) { - switch( c ) { - case ' ': return; - case '~': m_exclusion = true; return; - case '[': return startNewMode( Tag, ++m_pos ); - case '"': return startNewMode( QuotedName, ++m_pos ); - case '\\': return escape(); - default: startNewMode( Name, m_pos ); break; - } - } - if( m_mode == Name ) { - if( c == ',' ) { - addPattern<TestSpec::NamePattern>(); - addFilter(); - } - else if( c == '[' ) { - if( subString() == "exclude:" ) - m_exclusion = true; - else - addPattern<TestSpec::NamePattern>(); - startNewMode( Tag, ++m_pos ); - } - else if( c == '\\' ) - escape(); - } - else if( m_mode == EscapedName ) - m_mode = Name; - else if( m_mode == QuotedName && c == '"' ) - addPattern<TestSpec::NamePattern>(); - else if( m_mode == Tag && c == ']' ) - addPattern<TestSpec::TagPattern>(); - } - void TestSpecParser::startNewMode( Mode mode, std::size_t start ) { - m_mode = mode; - m_start = start; - } - void TestSpecParser::escape() { - if( m_mode == None ) - m_start = m_pos; - m_mode = EscapedName; - m_escapeChars.push_back( m_pos ); - } - std::string TestSpecParser::subString() const { return m_arg.substr( m_start, m_pos - m_start ); } - - void TestSpecParser::addFilter() { - if( !m_currentFilter.m_patterns.empty() ) { - m_testSpec.m_filters.push_back( m_currentFilter ); - m_currentFilter = TestSpec::Filter(); - } - } - - TestSpec parseTestSpec( std::string const& arg ) { - return TestSpecParser( ITagAliasRegistry::get() ).parse( arg ).testSpec(); - } - -} // namespace Catch -// end catch_test_spec_parser.cpp -// start catch_timer.cpp - -#include <chrono> - -namespace Catch { - - auto getCurrentNanosecondsSinceEpoch() -> uint64_t { - return std::chrono::duration_cast<std::chrono::nanoseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count(); - } - - auto estimateClockResolution() -> uint64_t { - uint64_t sum = 0; - static const uint64_t iterations = 1000000; - - for( std::size_t i = 0; i < iterations; ++i ) { - - uint64_t ticks; - uint64_t baseTicks = getCurrentNanosecondsSinceEpoch(); - do { - ticks = getCurrentNanosecondsSinceEpoch(); - } - while( ticks == baseTicks ); - - auto delta = ticks - baseTicks; - sum += delta; - } - - // We're just taking the mean, here. To do better we could take the std. dev and exclude outliers - // - and potentially do more iterations if there's a high variance. - return sum/iterations; - } - auto getEstimatedClockResolution() -> uint64_t { - static auto s_resolution = estimateClockResolution(); - return s_resolution; - } - - void Timer::start() { - m_nanoseconds = getCurrentNanosecondsSinceEpoch(); - } - auto Timer::getElapsedNanoseconds() const -> unsigned int { - return static_cast<unsigned int>(getCurrentNanosecondsSinceEpoch() - m_nanoseconds); - } - auto Timer::getElapsedMicroseconds() const -> unsigned int { - return static_cast<unsigned int>(getElapsedNanoseconds()/1000); - } - auto Timer::getElapsedMilliseconds() const -> unsigned int { - return static_cast<unsigned int>(getElapsedMicroseconds()/1000); - } - auto Timer::getElapsedSeconds() const -> double { - return getElapsedMicroseconds()/1000000.0; - } - -} // namespace Catch -// end catch_timer.cpp -// start catch_tostring.cpp - -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wexit-time-destructors" -# pragma clang diagnostic ignored "-Wglobal-constructors" -#endif - -#include <iomanip> - -namespace Catch { - -namespace Detail { - - const std::string unprintableString = "{?}"; - - namespace { - const int hexThreshold = 255; - - struct Endianness { - enum Arch { Big, Little }; - - static Arch which() { - union _{ - int asInt; - char asChar[sizeof (int)]; - } u; - - u.asInt = 1; - return ( u.asChar[sizeof(int)-1] == 1 ) ? Big : Little; - } - }; - } - - std::string rawMemoryToString( const void *object, std::size_t size ) { - // Reverse order for little endian architectures - int i = 0, end = static_cast<int>( size ), inc = 1; - if( Endianness::which() == Endianness::Little ) { - i = end-1; - end = inc = -1; - } - - unsigned char const *bytes = static_cast<unsigned char const *>(object); - std::ostringstream os; - os << "0x" << std::setfill('0') << std::hex; - for( ; i != end; i += inc ) - os << std::setw(2) << static_cast<unsigned>(bytes[i]); - return os.str(); - } -} - -template<typename T> -std::string fpToString( T value, int precision ) { - std::ostringstream oss; - oss << std::setprecision( precision ) - << std::fixed - << value; - std::string d = oss.str(); - std::size_t i = d.find_last_not_of( '0' ); - if( i != std::string::npos && i != d.size()-1 ) { - if( d[i] == '.' ) - i++; - d = d.substr( 0, i+1 ); - } - return d; -} - -//// ======================================================= //// -// -// Out-of-line defs for full specialization of StringMaker -// -//// ======================================================= //// - -std::string StringMaker<std::string>::convert(const std::string& str) { - if (!getCurrentContext().getConfig()->showInvisibles()) { - return '"' + str + '"'; - } - - std::string s("\""); - for (char c : str) { - switch (c) { - case '\n': - s.append("\\n"); - break; - case '\t': - s.append("\\t"); - break; - default: - s.push_back(c); - break; - } - } - s.append("\""); - return s; -} - -std::string StringMaker<std::wstring>::convert(const std::wstring& wstr) { - std::string s; - s.reserve(wstr.size()); - for (auto c : wstr) { - s += (c <= 0xff) ? static_cast<char>(c) : '?'; - } - return ::Catch::Detail::stringify(s); -} - -std::string StringMaker<char const*>::convert(char const* str) { - if (str) { - return ::Catch::Detail::stringify(std::string{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker<char*>::convert(char* str) { - if (str) { - return ::Catch::Detail::stringify(std::string{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker<wchar_t const*>::convert(wchar_t const * str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{ str }); - } else { - return{ "{null string}" }; - } -} -std::string StringMaker<wchar_t *>::convert(wchar_t * str) { - if (str) { - return ::Catch::Detail::stringify(std::wstring{ str }); - } else { - return{ "{null string}" }; - } -} - -std::string StringMaker<int>::convert(int value) { - return ::Catch::Detail::stringify(static_cast<long long>(value)); -} -std::string StringMaker<long>::convert(long value) { - return ::Catch::Detail::stringify(static_cast<long long>(value)); -} -std::string StringMaker<long long>::convert(long long value) { - std::ostringstream oss; - oss << value; - if (value > Detail::hexThreshold) { - oss << " (0x" << std::hex << value << ')'; - } - return oss.str(); -} - -std::string StringMaker<unsigned int>::convert(unsigned int value) { - return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); -} -std::string StringMaker<unsigned long>::convert(unsigned long value) { - return ::Catch::Detail::stringify(static_cast<unsigned long long>(value)); -} -std::string StringMaker<unsigned long long>::convert(unsigned long long value) { - std::ostringstream oss; - oss << value; - if (value > Detail::hexThreshold) { - oss << " (0x" << std::hex << value << ')'; - } - return oss.str(); -} - -std::string StringMaker<bool>::convert(bool b) { - return b ? "true" : "false"; -} - -std::string StringMaker<char>::convert(char value) { - if (value == '\r') { - return "'\\r'"; - } else if (value == '\f') { - return "'\\f'"; - } else if (value == '\n') { - return "'\\n'"; - } else if (value == '\t') { - return "'\\t'"; - } else if ('\0' <= value && value < ' ') { - return ::Catch::Detail::stringify(static_cast<unsigned int>(value)); - } else { - char chstr[] = "' '"; - chstr[1] = value; - return chstr; - } -} -std::string StringMaker<signed char>::convert(signed char c) { - return ::Catch::Detail::stringify(static_cast<char>(c)); -} -std::string StringMaker<unsigned char>::convert(unsigned char c) { - return ::Catch::Detail::stringify(static_cast<char>(c)); -} - -std::string StringMaker<std::nullptr_t>::convert(std::nullptr_t) { - return "nullptr"; -} - -std::string StringMaker<float>::convert(float value) { - return fpToString(value, 5) + 'f'; -} -std::string StringMaker<double>::convert(double value) { - return fpToString(value, 10); -} - -} // end namespace Catch - -#if defined(__clang__) -# pragma clang diagnostic pop -#endif - -// end catch_tostring.cpp -// start catch_totals.cpp - -namespace Catch { - - Counts Counts::operator - ( Counts const& other ) const { - Counts diff; - diff.passed = passed - other.passed; - diff.failed = failed - other.failed; - diff.failedButOk = failedButOk - other.failedButOk; - return diff; - } - - Counts& Counts::operator += ( Counts const& other ) { - passed += other.passed; - failed += other.failed; - failedButOk += other.failedButOk; - return *this; - } - - std::size_t Counts::total() const { - return passed + failed + failedButOk; - } - bool Counts::allPassed() const { - return failed == 0 && failedButOk == 0; - } - bool Counts::allOk() const { - return failed == 0; - } - - Totals Totals::operator - ( Totals const& other ) const { - Totals diff; - diff.assertions = assertions - other.assertions; - diff.testCases = testCases - other.testCases; - return diff; - } - - Totals& Totals::operator += ( Totals const& other ) { - assertions += other.assertions; - testCases += other.testCases; - return *this; - } - - Totals Totals::delta( Totals const& prevTotals ) const { - Totals diff = *this - prevTotals; - if( diff.assertions.failed > 0 ) - ++diff.testCases.failed; - else if( diff.assertions.failedButOk > 0 ) - ++diff.testCases.failedButOk; - else - ++diff.testCases.passed; - return diff; - } - -} -// end catch_totals.cpp -// start catch_version.cpp - -#include <ostream> - -namespace Catch { - - Version::Version - ( unsigned int _majorVersion, - unsigned int _minorVersion, - unsigned int _patchNumber, - char const * const _branchName, - unsigned int _buildNumber ) - : majorVersion( _majorVersion ), - minorVersion( _minorVersion ), - patchNumber( _patchNumber ), - branchName( _branchName ), - buildNumber( _buildNumber ) - {} - - std::ostream& operator << ( std::ostream& os, Version const& version ) { - os << version.majorVersion << '.' - << version.minorVersion << '.' - << version.patchNumber; - // branchName is never null -> 0th char is \0 if it is empty - if (version.branchName[0]) { - os << '-' << version.branchName - << '.' << version.buildNumber; - } - return os; - } - - Version const& libraryVersion() { - static Version version( 2, 0, 1, "", 0 ); - return version; - } - -} -// end catch_version.cpp -// start catch_wildcard_pattern.cpp - -namespace Catch { - - WildcardPattern::WildcardPattern( std::string const& pattern, - CaseSensitive::Choice caseSensitivity ) - : m_caseSensitivity( caseSensitivity ), - m_pattern( adjustCase( pattern ) ) - { - if( startsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_pattern, '*' ) ) { - m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); - m_wildcard = static_cast<WildcardPosition>( m_wildcard | WildcardAtEnd ); - } - } - - bool WildcardPattern::matches( std::string const& str ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_pattern == adjustCase( str ); - case WildcardAtStart: - return endsWith( adjustCase( str ), m_pattern ); - case WildcardAtEnd: - return startsWith( adjustCase( str ), m_pattern ); - case WildcardAtBothEnds: - return contains( adjustCase( str ), m_pattern ); - default: - CATCH_INTERNAL_ERROR( "Unknown enum" ); - } - } - - std::string WildcardPattern::adjustCase( std::string const& str ) const { - return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; - } -} -// end catch_wildcard_pattern.cpp -// start catch_xmlwriter.cpp - -// start catch_xmlwriter.h - -#include <sstream> -#include <vector> - -namespace Catch { - - class XmlEncode { - public: - enum ForWhat { ForTextNodes, ForAttributes }; - - XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ); - - void encodeTo( std::ostream& os ) const; - - friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ); - - private: - std::string m_str; - ForWhat m_forWhat; - }; - - class XmlWriter { - public: - - class ScopedElement { - public: - ScopedElement( XmlWriter* writer ); - - ScopedElement( ScopedElement&& other ) noexcept; - ScopedElement& operator=( ScopedElement&& other ) noexcept; - - ~ScopedElement(); - - ScopedElement& writeText( std::string const& text, bool indent = true ); - - template<typename T> - ScopedElement& writeAttribute( std::string const& name, T const& attribute ) { - m_writer->writeAttribute( name, attribute ); - return *this; - } - - private: - mutable XmlWriter* m_writer = nullptr; - }; - - XmlWriter( std::ostream& os = Catch::cout() ); - ~XmlWriter(); - - XmlWriter( XmlWriter const& ) = delete; - XmlWriter& operator=( XmlWriter const& ) = delete; - - XmlWriter& startElement( std::string const& name ); - - ScopedElement scopedElement( std::string const& name ); - - XmlWriter& endElement(); - - XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ); - - XmlWriter& writeAttribute( std::string const& name, bool attribute ); - - template<typename T> - XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - m_oss.clear(); - m_oss.str(std::string()); - m_oss << attribute; - return writeAttribute( name, m_oss.str() ); - } - - XmlWriter& writeText( std::string const& text, bool indent = true ); - - XmlWriter& writeComment( std::string const& text ); - - void writeStylesheetRef( std::string const& url ); - - XmlWriter& writeBlankLine(); - - void ensureTagClosed(); - - private: - - void writeDeclaration(); - - void newlineIfNecessary(); - - bool m_tagIsOpen = false; - bool m_needsNewline = false; - std::vector<std::string> m_tags; - std::string m_indent; - std::ostream& m_os; - std::ostringstream m_oss; - }; - -} - -// end catch_xmlwriter.h -#include <iomanip> - -namespace Catch { - - XmlEncode::XmlEncode( std::string const& str, ForWhat forWhat ) - : m_str( str ), - m_forWhat( forWhat ) - {} - - void XmlEncode::encodeTo( std::ostream& os ) const { - - // Apostrophe escaping not necessary if we always use " to write attributes - // (see: http://www.w3.org/TR/xml/#syntax) - - for( std::size_t i = 0; i < m_str.size(); ++ i ) { - char c = m_str[i]; - switch( c ) { - case '<': os << "<"; break; - case '&': os << "&"; break; - - case '>': - // See: http://www.w3.org/TR/xml/#syntax - if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) - os << ">"; - else - os << c; - break; - - case '\"': - if( m_forWhat == ForAttributes ) - os << """; - else - os << c; - break; - - default: - // Escape control chars - based on contribution by @espenalb in PR #465 and - // by @mrpi PR #588 - if ( ( c >= 0 && c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) { - // see http://stackoverflow.com/questions/404107/why-are-control-characters-illegal-in-xml-1-0 - os << "\\x" << std::uppercase << std::hex << std::setfill('0') << std::setw(2) - << static_cast<int>( c ); - } - else - os << c; - } - } - } - - std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { - xmlEncode.encodeTo( os ); - return os; - } - - XmlWriter::ScopedElement::ScopedElement( XmlWriter* writer ) - : m_writer( writer ) - {} - - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept - : m_writer( other.m_writer ){ - other.m_writer = nullptr; - } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { - if ( m_writer ) { - m_writer->endElement(); - } - m_writer = other.m_writer; - other.m_writer = nullptr; - return *this; - } - - XmlWriter::ScopedElement::~ScopedElement() { - if( m_writer ) - m_writer->endElement(); - } - - XmlWriter::ScopedElement& XmlWriter::ScopedElement::writeText( std::string const& text, bool indent ) { - m_writer->writeText( text, indent ); - return *this; - } - - XmlWriter::XmlWriter( std::ostream& os ) : m_os( os ) - { - writeDeclaration(); - } - - XmlWriter::~XmlWriter() { - while( !m_tags.empty() ) - endElement(); - } - - XmlWriter& XmlWriter::startElement( std::string const& name ) { - ensureTagClosed(); - newlineIfNecessary(); - m_os << m_indent << '<' << name; - m_tags.push_back( name ); - m_indent += " "; - m_tagIsOpen = true; - return *this; - } - - XmlWriter::ScopedElement XmlWriter::scopedElement( std::string const& name ) { - ScopedElement scoped( this ); - startElement( name ); - return scoped; - } - - XmlWriter& XmlWriter::endElement() { - newlineIfNecessary(); - m_indent = m_indent.substr( 0, m_indent.size()-2 ); - if( m_tagIsOpen ) { - m_os << "/>"; - m_tagIsOpen = false; - } - else { - m_os << m_indent << "</" << m_tags.back() << ">"; - } - m_os << std::endl; - m_tags.pop_back(); - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) - m_os << ' ' << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeAttribute( std::string const& name, bool attribute ) { - m_os << ' ' << name << "=\"" << ( attribute ? "true" : "false" ) << '"'; - return *this; - } - - XmlWriter& XmlWriter::writeText( std::string const& text, bool indent ) { - if( !text.empty() ){ - bool tagWasOpen = m_tagIsOpen; - ensureTagClosed(); - if( tagWasOpen && indent ) - m_os << m_indent; - m_os << XmlEncode( text ); - m_needsNewline = true; - } - return *this; - } - - XmlWriter& XmlWriter::writeComment( std::string const& text ) { - ensureTagClosed(); - m_os << m_indent << "<!--" << text << "-->"; - m_needsNewline = true; - return *this; - } - - void XmlWriter::writeStylesheetRef( std::string const& url ) { - m_os << "<?xml-stylesheet type=\"text/xsl\" href=\"" << url << "\"?>\n"; - } - - XmlWriter& XmlWriter::writeBlankLine() { - ensureTagClosed(); - m_os << '\n'; - return *this; - } - - void XmlWriter::ensureTagClosed() { - if( m_tagIsOpen ) { - m_os << ">" << std::endl; - m_tagIsOpen = false; - } - } - - void XmlWriter::writeDeclaration() { - m_os << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"; - } - - void XmlWriter::newlineIfNecessary() { - if( m_needsNewline ) { - m_os << std::endl; - m_needsNewline = false; - } - } -} -// end catch_xmlwriter.cpp -// start catch_reporter_bases.cpp - -#include <cstring> -#include <cfloat> -#include <cstdio> -#include <assert.h> -#include <memory> - -namespace Catch { - void prepareExpandedExpression(AssertionResult& result) { - result.getExpandedExpression(); - } - - // Because formatting using c++ streams is stateful, drop down to C is required - // Alternatively we could use stringstream, but its performance is... not good. - std::string getFormattedDuration( double duration ) { - // Max exponent + 1 is required to represent the whole part - // + 1 for decimal point - // + 3 for the 3 decimal places - // + 1 for null terminator - const std::size_t maxDoubleSize = DBL_MAX_10_EXP + 1 + 1 + 3 + 1; - char buffer[maxDoubleSize]; - - // Save previous errno, to prevent sprintf from overwriting it - ErrnoGuard guard; -#ifdef _MSC_VER - sprintf_s(buffer, "%.3f", duration); -#else - sprintf(buffer, "%.3f", duration); -#endif - return std::string(buffer); - } - - TestEventListenerBase::TestEventListenerBase(ReporterConfig const & _config) - :StreamingReporterBase(_config) {} - - void TestEventListenerBase::assertionStarting(AssertionInfo const &) {} - - bool TestEventListenerBase::assertionEnded(AssertionStats const &) { - return false; - } - -} // end namespace Catch -// end catch_reporter_bases.cpp -// start catch_reporter_compact.cpp - -namespace { - -#ifdef CATCH_PLATFORM_MAC - const char* failedString() { return "FAILED"; } - const char* passedString() { return "PASSED"; } -#else - const char* failedString() { return "failed"; } - const char* passedString() { return "passed"; } -#endif - - // Colour::LightGrey - Catch::Colour::Code dimColour() { return Catch::Colour::FileName; } - - std::string bothOrAll( std::size_t count ) { - return count == 1 ? std::string() : - count == 2 ? "both " : "all " ; - } -} - -namespace Catch { - - struct CompactReporter : StreamingReporterBase<CompactReporter> { - - using StreamingReporterBase::StreamingReporterBase; - - ~CompactReporter() override; - - static std::string getDescription() { - return "Reports test results on a single line, suitable for IDEs"; - } - - ReporterPreferences getPreferences() const override { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } - - void noMatchingTestCases( std::string const& spec ) override { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void assertionStarting( AssertionInfo const& ) override {} - - bool assertionEnded( AssertionStats const& _assertionStats ) override { - AssertionResult const& result = _assertionStats.assertionResult; - - bool printInfoMessages = true; - - // Drop out if result was successful and we're not printing those - if( !m_config->includeSuccessfulResults() && result.isOk() ) { - if( result.getResultType() != ResultWas::Warning ) - return false; - printInfoMessages = false; - } - - AssertionPrinter printer( stream, _assertionStats, printInfoMessages ); - printer.print(); - - stream << std::endl; - return true; - } - - void sectionEnded(SectionStats const& _sectionStats) override { - if (m_config->showDurations() == ShowDurations::Always) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - } - - void testRunEnded( TestRunStats const& _testRunStats ) override { - printTotals( _testRunStats.totals ); - stream << '\n' << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - private: - class AssertionPrinter { - public: - AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; - AssertionPrinter( AssertionPrinter const& ) = delete; - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ) - , result( _stats.assertionResult ) - , messages( _stats.infoMessages ) - , itMessage( _stats.infoMessages.begin() ) - , printInfoMessages( _printInfoMessages ) - {} - - void print() { - printSourceInfo(); - - itMessage = messages.begin(); - - switch( result.getResultType() ) { - case ResultWas::Ok: - printResultType( Colour::ResultSuccess, passedString() ); - printOriginalExpression(); - printReconstructedExpression(); - if ( ! result.hasExpression() ) - printRemainingMessages( Colour::None ); - else - printRemainingMessages(); - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) - printResultType( Colour::ResultSuccess, failedString() + std::string( " - but was ok" ) ); - else - printResultType( Colour::Error, failedString() ); - printOriginalExpression(); - printReconstructedExpression(); - printRemainingMessages(); - break; - case ResultWas::ThrewException: - printResultType( Colour::Error, failedString() ); - printIssue( "unexpected exception with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::FatalErrorCondition: - printResultType( Colour::Error, failedString() ); - printIssue( "fatal error condition with message:" ); - printMessage(); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::DidntThrowException: - printResultType( Colour::Error, failedString() ); - printIssue( "expected exception, got none" ); - printExpressionWas(); - printRemainingMessages(); - break; - case ResultWas::Info: - printResultType( Colour::None, "info" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::Warning: - printResultType( Colour::None, "warning" ); - printMessage(); - printRemainingMessages(); - break; - case ResultWas::ExplicitFailure: - printResultType( Colour::Error, failedString() ); - printIssue( "explicitly" ); - printRemainingMessages( Colour::None ); - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - printResultType( Colour::Error, "** internal error **" ); - break; - } - } - - private: - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ':'; - } - - void printResultType( Colour::Code colour, std::string const& passOrFail ) const { - if( !passOrFail.empty() ) { - { - Colour colourGuard( colour ); - stream << ' ' << passOrFail; - } - stream << ':'; - } - } - - void printIssue( std::string const& issue ) const { - stream << ' ' << issue; - } - - void printExpressionWas() { - if( result.hasExpression() ) { - stream << ';'; - { - Colour colour( dimColour() ); - stream << " expression was:"; - } - printOriginalExpression(); - } - } - - void printOriginalExpression() const { - if( result.hasExpression() ) { - stream << ' ' << result.getExpression(); - } - } - - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - { - Colour colour( dimColour() ); - stream << " for: "; - } - stream << result.getExpandedExpression(); - } - } - - void printMessage() { - if ( itMessage != messages.end() ) { - stream << " '" << itMessage->message << '\''; - ++itMessage; - } - } - - void printRemainingMessages( Colour::Code colour = dimColour() ) { - if ( itMessage == messages.end() ) - return; - - // using messages.end() directly yields (or auto) compilation error: - std::vector<MessageInfo>::const_iterator itEnd = messages.end(); - const std::size_t N = static_cast<std::size_t>( std::distance( itMessage, itEnd ) ); - - { - Colour colourGuard( colour ); - stream << " with " << pluralise( N, "message" ) << ':'; - } - - for(; itMessage != itEnd; ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || itMessage->type != ResultWas::Info ) { - stream << " '" << itMessage->message << '\''; - if ( ++itMessage != itEnd ) { - Colour colourGuard( dimColour() ); - stream << " and"; - } - } - } - } - - private: - std::ostream& stream; - AssertionResult const& result; - std::vector<MessageInfo> messages; - std::vector<MessageInfo>::const_iterator itMessage; - bool printInfoMessages; - }; - - // Colour, message variants: - // - white: No tests ran. - // - red: Failed [both/all] N test cases, failed [both/all] M assertions. - // - white: Passed [both/all] N test cases (no assertions). - // - red: Failed N tests cases, failed M assertions. - // - green: Passed [both/all] N tests cases with M assertions. - - void printTotals( const Totals& totals ) const { - if( totals.testCases.total() == 0 ) { - stream << "No tests ran."; - } - else if( totals.testCases.failed == totals.testCases.total() ) { - Colour colour( Colour::ResultError ); - const std::string qualify_assertions_failed = - totals.assertions.failed == totals.assertions.total() ? - bothOrAll( totals.assertions.failed ) : std::string(); - stream << - "Failed " << bothOrAll( totals.testCases.failed ) - << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << qualify_assertions_failed << - pluralise( totals.assertions.failed, "assertion" ) << '.'; - } - else if( totals.assertions.total() == 0 ) { - stream << - "Passed " << bothOrAll( totals.testCases.total() ) - << pluralise( totals.testCases.total(), "test case" ) - << " (no assertions)."; - } - else if( totals.assertions.failed ) { - Colour colour( Colour::ResultError ); - stream << - "Failed " << pluralise( totals.testCases.failed, "test case" ) << ", " - "failed " << pluralise( totals.assertions.failed, "assertion" ) << '.'; - } - else { - Colour colour( Colour::ResultSuccess ); - stream << - "Passed " << bothOrAll( totals.testCases.passed ) - << pluralise( totals.testCases.passed, "test case" ) << - " with " << pluralise( totals.assertions.passed, "assertion" ) << '.'; - } - } - }; - - CompactReporter::~CompactReporter() {} - - CATCH_REGISTER_REPORTER( "compact", CompactReporter ) - -} // end namespace Catch -// end catch_reporter_compact.cpp -// start catch_reporter_console.cpp - -#include <cfloat> -#include <cstdio> - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - -namespace Catch { - - namespace { - std::size_t makeRatio( std::size_t number, std::size_t total ) { - std::size_t ratio = total > 0 ? CATCH_CONFIG_CONSOLE_WIDTH * number/ total : 0; - return ( ratio == 0 && number > 0 ) ? 1 : ratio; - } - - std::size_t& findMax( std::size_t& i, std::size_t& j, std::size_t& k ) { - if( i > j && i > k ) - return i; - else if( j > k ) - return j; - else - return k; - } - - struct ColumnInfo { - enum Justification { Left, Right }; - std::string name; - int width; - Justification justification; - }; - struct ColumnBreak {}; - struct RowBreak {}; - - class TablePrinter { - std::ostream& m_os; - std::vector<ColumnInfo> m_columnInfos; - std::ostringstream m_oss; - int m_currentColumn = -1; - bool m_isOpen = false; - - public: - TablePrinter( std::ostream& os, std::vector<ColumnInfo> const& columnInfos ) - : m_os( os ), - m_columnInfos( columnInfos ) - {} - - auto columnInfos() const -> std::vector<ColumnInfo> const& { - return m_columnInfos; - } - - void open() { - if( !m_isOpen ) { - m_isOpen = true; - *this << RowBreak(); - for( auto const& info : m_columnInfos ) - *this << info.name << ColumnBreak(); - *this << RowBreak(); - m_os << Catch::getLineOfChars<'-'>() << "\n"; - } - } - void close() { - if( m_isOpen ) { - *this << RowBreak(); - m_os << std::endl; - m_isOpen = false; - } - } - - template<typename T> - friend TablePrinter& operator << ( TablePrinter& tp, T const& value ) { - tp.m_oss << value; - return tp; - } - - friend TablePrinter& operator << ( TablePrinter& tp, ColumnBreak ) { - auto colStr = tp.m_oss.str(); - // This takes account of utf8 encodings - auto strSize = Catch::StringRef( colStr ).numberOfCharacters(); - tp.m_oss.str(""); - tp.open(); - if( tp.m_currentColumn == static_cast<int>(tp.m_columnInfos.size()-1) ) { - tp.m_currentColumn = -1; - tp.m_os << "\n"; - } - tp.m_currentColumn++; - - auto colInfo = tp.m_columnInfos[tp.m_currentColumn]; - auto padding = ( strSize+2 < static_cast<std::size_t>( colInfo.width ) ) - ? std::string( colInfo.width-(strSize+2), ' ' ) - : std::string(); - if( colInfo.justification == ColumnInfo::Left ) - tp.m_os << colStr << padding << " "; - else - tp.m_os << padding << colStr << " "; - return tp; - } - - friend TablePrinter& operator << ( TablePrinter& tp, RowBreak ) { - if( tp.m_currentColumn > 0 ) { - tp.m_os << "\n"; - tp.m_currentColumn = -1; - } - return tp; - } - }; - - class Duration { - enum class Unit { - Auto, - Nanoseconds, - Microseconds, - Milliseconds, - Seconds, - Minutes - }; - static const uint64_t s_nanosecondsInAMicrosecond = 1000; - static const uint64_t s_nanosecondsInAMillisecond = 1000*s_nanosecondsInAMicrosecond; - static const uint64_t s_nanosecondsInASecond = 1000*s_nanosecondsInAMillisecond; - static const uint64_t s_nanosecondsInAMinute = 60*s_nanosecondsInASecond; - - uint64_t m_inNanoseconds; - Unit m_units; - - public: - Duration( uint64_t inNanoseconds, Unit units = Unit::Auto ) - : m_inNanoseconds( inNanoseconds ), - m_units( units ) - { - if( m_units == Unit::Auto ) { - if( m_inNanoseconds < s_nanosecondsInAMicrosecond ) - m_units = Unit::Nanoseconds; - else if( m_inNanoseconds < s_nanosecondsInAMillisecond ) - m_units = Unit::Microseconds; - else if( m_inNanoseconds < s_nanosecondsInASecond ) - m_units = Unit::Milliseconds; - else if( m_inNanoseconds < s_nanosecondsInAMinute ) - m_units = Unit::Seconds; - else - m_units = Unit::Minutes; - } - - } - - auto value() const -> double { - switch( m_units ) { - case Unit::Microseconds: - return m_inNanoseconds / static_cast<double>( s_nanosecondsInAMicrosecond ); - case Unit::Milliseconds: - return m_inNanoseconds / static_cast<double>( s_nanosecondsInAMillisecond ); - case Unit::Seconds: - return m_inNanoseconds / static_cast<double>( s_nanosecondsInASecond ); - case Unit::Minutes: - return m_inNanoseconds / static_cast<double>( s_nanosecondsInAMinute ); - default: - return static_cast<double>( m_inNanoseconds ); - } - } - auto unitsAsString() const -> std::string { - switch( m_units ) { - case Unit::Nanoseconds: - return "ns"; - case Unit::Microseconds: - return "µs"; - case Unit::Milliseconds: - return "ms"; - case Unit::Seconds: - return "s"; - case Unit::Minutes: - return "m"; - default: - return "** internal error **"; - } - - } - friend auto operator << ( std::ostream& os, Duration const& duration ) -> std::ostream& { - return os << duration.value() << " " << duration.unitsAsString(); - } - }; - } // end anon namespace - - struct ConsoleReporter : StreamingReporterBase<ConsoleReporter> { - TablePrinter m_tablePrinter; - - ConsoleReporter( ReporterConfig const& config ) - : StreamingReporterBase( config ), - m_tablePrinter( config.stream(), - { - { "benchmark name", CATCH_CONFIG_CONSOLE_WIDTH-32, ColumnInfo::Left }, - { "iters", 8, ColumnInfo::Right }, - { "elapsed ns", 14, ColumnInfo::Right }, - { "average", 14, ColumnInfo::Right } - } ) - {} - ~ConsoleReporter() override; - static std::string getDescription() { - return "Reports test results as plain lines of text"; - } - - void noMatchingTestCases( std::string const& spec ) override { - stream << "No test cases matched '" << spec << '\'' << std::endl; - } - - void assertionStarting( AssertionInfo const& ) override { - } - - bool assertionEnded( AssertionStats const& _assertionStats ) override { - AssertionResult const& result = _assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return false; - - lazyPrint(); - - AssertionPrinter printer( stream, _assertionStats, includeResults ); - printer.print(); - stream << std::endl; - return true; - } - - void sectionStarting( SectionInfo const& _sectionInfo ) override { - m_headerPrinted = false; - StreamingReporterBase::sectionStarting( _sectionInfo ); - } - void sectionEnded( SectionStats const& _sectionStats ) override { - m_tablePrinter.close(); - if( _sectionStats.missingAssertions ) { - lazyPrint(); - Colour colour( Colour::ResultError ); - if( m_sectionStack.size() > 1 ) - stream << "\nNo assertions in section"; - else - stream << "\nNo assertions in test case"; - stream << " '" << _sectionStats.sectionInfo.name << "'\n" << std::endl; - } - if( m_config->showDurations() == ShowDurations::Always ) { - stream << getFormattedDuration(_sectionStats.durationInSeconds) << " s: " << _sectionStats.sectionInfo.name << std::endl; - } - if( m_headerPrinted ) { - m_headerPrinted = false; - } - StreamingReporterBase::sectionEnded( _sectionStats ); - } - - void benchmarkStarting( BenchmarkInfo const& info ) override { - lazyPrintWithoutClosingBenchmarkTable(); - - auto nameCol = Column( info.name ).width( m_tablePrinter.columnInfos()[0].width-2 ); - - bool firstLine = true; - for( auto line : nameCol ) { - if( !firstLine ) - m_tablePrinter << ColumnBreak() << ColumnBreak() << ColumnBreak(); - else - firstLine = false; - - m_tablePrinter << line << ColumnBreak(); - } - } - void benchmarkEnded( BenchmarkStats const& stats ) override { - Duration average( stats.elapsedTimeInNanoseconds/stats.iterations ); - m_tablePrinter - << stats.iterations << ColumnBreak() - << stats.elapsedTimeInNanoseconds << ColumnBreak() - << average << ColumnBreak(); - } - - void testCaseEnded( TestCaseStats const& _testCaseStats ) override { - m_tablePrinter.close(); - StreamingReporterBase::testCaseEnded( _testCaseStats ); - m_headerPrinted = false; - } - void testGroupEnded( TestGroupStats const& _testGroupStats ) override { - if( currentGroupInfo.used ) { - printSummaryDivider(); - stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; - printTotals( _testGroupStats.totals ); - stream << '\n' << std::endl; - } - StreamingReporterBase::testGroupEnded( _testGroupStats ); - } - void testRunEnded( TestRunStats const& _testRunStats ) override { - printTotalsDivider( _testRunStats.totals ); - printTotals( _testRunStats.totals ); - stream << std::endl; - StreamingReporterBase::testRunEnded( _testRunStats ); - } - - private: - - class AssertionPrinter { - public: - AssertionPrinter& operator= ( AssertionPrinter const& ) = delete; - AssertionPrinter( AssertionPrinter const& ) = delete; - AssertionPrinter( std::ostream& _stream, AssertionStats const& _stats, bool _printInfoMessages ) - : stream( _stream ), - stats( _stats ), - result( _stats.assertionResult ), - colour( Colour::None ), - message( result.getMessage() ), - messages( _stats.infoMessages ), - printInfoMessages( _printInfoMessages ) - { - switch( result.getResultType() ) { - case ResultWas::Ok: - colour = Colour::Success; - passOrFail = "PASSED"; - //if( result.hasMessage() ) - if( _stats.infoMessages.size() == 1 ) - messageLabel = "with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "with messages"; - break; - case ResultWas::ExpressionFailed: - if( result.isOk() ) { - colour = Colour::Success; - passOrFail = "FAILED - but was ok"; - } - else { - colour = Colour::Error; - passOrFail = "FAILED"; - } - if( _stats.infoMessages.size() == 1 ) - messageLabel = "with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "with messages"; - break; - case ResultWas::ThrewException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to unexpected exception with "; - if (_stats.infoMessages.size() == 1) - messageLabel += "message"; - if (_stats.infoMessages.size() > 1) - messageLabel += "messages"; - break; - case ResultWas::FatalErrorCondition: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "due to a fatal error condition"; - break; - case ResultWas::DidntThrowException: - colour = Colour::Error; - passOrFail = "FAILED"; - messageLabel = "because no exception was thrown where one was expected"; - break; - case ResultWas::Info: - messageLabel = "info"; - break; - case ResultWas::Warning: - messageLabel = "warning"; - break; - case ResultWas::ExplicitFailure: - passOrFail = "FAILED"; - colour = Colour::Error; - if( _stats.infoMessages.size() == 1 ) - messageLabel = "explicitly with message"; - if( _stats.infoMessages.size() > 1 ) - messageLabel = "explicitly with messages"; - break; - // These cases are here to prevent compiler warnings - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - passOrFail = "** internal error **"; - colour = Colour::Error; - break; - } - } - - void print() const { - printSourceInfo(); - if( stats.totals.assertions.total() > 0 ) { - if( result.isOk() ) - stream << '\n'; - printResultType(); - printOriginalExpression(); - printReconstructedExpression(); - } - else { - stream << '\n'; - } - printMessage(); - } - - private: - void printResultType() const { - if( !passOrFail.empty() ) { - Colour colourGuard( colour ); - stream << passOrFail << ":\n"; - } - } - void printOriginalExpression() const { - if( result.hasExpression() ) { - Colour colourGuard( Colour::OriginalExpression ); - stream << " "; - stream << result.getExpressionInMacro(); - stream << '\n'; - } - } - void printReconstructedExpression() const { - if( result.hasExpandedExpression() ) { - stream << "with expansion:\n"; - Colour colourGuard( Colour::ReconstructedExpression ); - stream << Column( result.getExpandedExpression() ).indent(2) << '\n'; - } - } - void printMessage() const { - if( !messageLabel.empty() ) - stream << messageLabel << ':' << '\n'; - for( auto const& msg : messages ) { - // If this assertion is a warning ignore any INFO messages - if( printInfoMessages || msg.type != ResultWas::Info ) - stream << Column( msg.message ).indent(2) << '\n'; - } - } - void printSourceInfo() const { - Colour colourGuard( Colour::FileName ); - stream << result.getSourceInfo() << ": "; - } - - std::ostream& stream; - AssertionStats const& stats; - AssertionResult const& result; - Colour::Code colour; - std::string passOrFail; - std::string messageLabel; - std::string message; - std::vector<MessageInfo> messages; - bool printInfoMessages; - }; - - void lazyPrint() { - - m_tablePrinter.close(); - lazyPrintWithoutClosingBenchmarkTable(); - } - - void lazyPrintWithoutClosingBenchmarkTable() { - - if( !currentTestRunInfo.used ) - lazyPrintRunInfo(); - if( !currentGroupInfo.used ) - lazyPrintGroupInfo(); - - if( !m_headerPrinted ) { - printTestCaseAndSectionHeader(); - m_headerPrinted = true; - } - } - void lazyPrintRunInfo() { - stream << '\n' << getLineOfChars<'~'>() << '\n'; - Colour colour( Colour::SecondaryText ); - stream << currentTestRunInfo->name - << " is a Catch v" << libraryVersion() << " host application.\n" - << "Run with -? for options\n\n"; - - if( m_config->rngSeed() != 0 ) - stream << "Randomness seeded to: " << m_config->rngSeed() << "\n\n"; - - currentTestRunInfo.used = true; - } - void lazyPrintGroupInfo() { - if( !currentGroupInfo->name.empty() && currentGroupInfo->groupsCounts > 1 ) { - printClosedHeader( "Group: " + currentGroupInfo->name ); - currentGroupInfo.used = true; - } - } - void printTestCaseAndSectionHeader() { - assert( !m_sectionStack.empty() ); - printOpenHeader( currentTestCaseInfo->name ); - - if( m_sectionStack.size() > 1 ) { - Colour colourGuard( Colour::Headers ); - - auto - it = m_sectionStack.begin()+1, // Skip first section (test case) - itEnd = m_sectionStack.end(); - for( ; it != itEnd; ++it ) - printHeaderString( it->name, 2 ); - } - - SourceLineInfo lineInfo = m_sectionStack.back().lineInfo; - - if( !lineInfo.empty() ){ - stream << getLineOfChars<'-'>() << '\n'; - Colour colourGuard( Colour::FileName ); - stream << lineInfo << '\n'; - } - stream << getLineOfChars<'.'>() << '\n' << std::endl; - } - - void printClosedHeader( std::string const& _name ) { - printOpenHeader( _name ); - stream << getLineOfChars<'.'>() << '\n'; - } - void printOpenHeader( std::string const& _name ) { - stream << getLineOfChars<'-'>() << '\n'; - { - Colour colourGuard( Colour::Headers ); - printHeaderString( _name ); - } - } - - // if string has a : in first line will set indent to follow it on - // subsequent lines - void printHeaderString( std::string const& _string, std::size_t indent = 0 ) { - std::size_t i = _string.find( ": " ); - if( i != std::string::npos ) - i+=2; - else - i = 0; - stream << Column( _string ).indent( indent+i ).initialIndent( indent ) << '\n'; - } - - struct SummaryColumn { - - SummaryColumn( std::string const& _label, Colour::Code _colour ) - : label( _label ), - colour( _colour ) - {} - SummaryColumn addRow( std::size_t count ) { - std::ostringstream oss; - oss << count; - std::string row = oss.str(); - for( auto& oldRow : rows ) { - while( oldRow.size() < row.size() ) - oldRow = ' ' + oldRow; - while( oldRow.size() > row.size() ) - row = ' ' + row; - } - rows.push_back( row ); - return *this; - } - - std::string label; - Colour::Code colour; - std::vector<std::string> rows; - - }; - - void printTotals( Totals const& totals ) { - if( totals.testCases.total() == 0 ) { - stream << Colour( Colour::Warning ) << "No tests ran\n"; - } - else if( totals.assertions.total() > 0 && totals.testCases.allPassed() ) { - stream << Colour( Colour::ResultSuccess ) << "All tests passed"; - stream << " (" - << pluralise( totals.assertions.passed, "assertion" ) << " in " - << pluralise( totals.testCases.passed, "test case" ) << ')' - << '\n'; - } - else { - - std::vector<SummaryColumn> columns; - columns.push_back( SummaryColumn( "", Colour::None ) - .addRow( totals.testCases.total() ) - .addRow( totals.assertions.total() ) ); - columns.push_back( SummaryColumn( "passed", Colour::Success ) - .addRow( totals.testCases.passed ) - .addRow( totals.assertions.passed ) ); - columns.push_back( SummaryColumn( "failed", Colour::ResultError ) - .addRow( totals.testCases.failed ) - .addRow( totals.assertions.failed ) ); - columns.push_back( SummaryColumn( "failed as expected", Colour::ResultExpectedFailure ) - .addRow( totals.testCases.failedButOk ) - .addRow( totals.assertions.failedButOk ) ); - - printSummaryRow( "test cases", columns, 0 ); - printSummaryRow( "assertions", columns, 1 ); - } - } - void printSummaryRow( std::string const& label, std::vector<SummaryColumn> const& cols, std::size_t row ) { - for( auto col : cols ) { - std::string value = col.rows[row]; - if( col.label.empty() ) { - stream << label << ": "; - if( value != "0" ) - stream << value; - else - stream << Colour( Colour::Warning ) << "- none -"; - } - else if( value != "0" ) { - stream << Colour( Colour::LightGrey ) << " | "; - stream << Colour( col.colour ) - << value << ' ' << col.label; - } - } - stream << '\n'; - } - - void printTotalsDivider( Totals const& totals ) { - if( totals.testCases.total() > 0 ) { - std::size_t failedRatio = makeRatio( totals.testCases.failed, totals.testCases.total() ); - std::size_t failedButOkRatio = makeRatio( totals.testCases.failedButOk, totals.testCases.total() ); - std::size_t passedRatio = makeRatio( totals.testCases.passed, totals.testCases.total() ); - while( failedRatio + failedButOkRatio + passedRatio < CATCH_CONFIG_CONSOLE_WIDTH-1 ) - findMax( failedRatio, failedButOkRatio, passedRatio )++; - while( failedRatio + failedButOkRatio + passedRatio > CATCH_CONFIG_CONSOLE_WIDTH-1 ) - findMax( failedRatio, failedButOkRatio, passedRatio )--; - - stream << Colour( Colour::Error ) << std::string( failedRatio, '=' ); - stream << Colour( Colour::ResultExpectedFailure ) << std::string( failedButOkRatio, '=' ); - if( totals.testCases.allPassed() ) - stream << Colour( Colour::ResultSuccess ) << std::string( passedRatio, '=' ); - else - stream << Colour( Colour::Success ) << std::string( passedRatio, '=' ); - } - else { - stream << Colour( Colour::Warning ) << std::string( CATCH_CONFIG_CONSOLE_WIDTH-1, '=' ); - } - stream << '\n'; - } - void printSummaryDivider() { - stream << getLineOfChars<'-'>() << '\n'; - } - - private: - bool m_headerPrinted = false; - }; - - CATCH_REGISTER_REPORTER( "console", ConsoleReporter ) - - ConsoleReporter::~ConsoleReporter() {} - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -// end catch_reporter_console.cpp -// start catch_reporter_junit.cpp - -#include <assert.h> - -#include <ctime> -#include <algorithm> - -namespace Catch { - - namespace { - std::string getCurrentTimestamp() { - // Beware, this is not reentrant because of backward compatibility issues - // Also, UTC only, again because of backward compatibility (%z is C++11) - time_t rawtime; - std::time(&rawtime); - auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); - -#ifdef _MSC_VER - std::tm timeInfo = {}; - gmtime_s(&timeInfo, &rawtime); -#else - std::tm* timeInfo; - timeInfo = std::gmtime(&rawtime); -#endif - - char timeStamp[timeStampSize]; - const char * const fmt = "%Y-%m-%dT%H:%M:%SZ"; - -#ifdef _MSC_VER - std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); -#else - std::strftime(timeStamp, timeStampSize, fmt, timeInfo); -#endif - return std::string(timeStamp); - } - - std::string fileNameTag(const std::vector<std::string> &tags) { - auto it = std::find_if(begin(tags), - end(tags), - [] (std::string const& tag) {return tag.front() == '#'; }); - if (it != tags.end()) - return it->substr(1); - return std::string(); - } - } - - class JunitReporter : public CumulativeReporterBase<JunitReporter> { - public: - JunitReporter( ReporterConfig const& _config ) - : CumulativeReporterBase( _config ), - xml( _config.stream() ) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - ~JunitReporter() override; - - static std::string getDescription() { - return "Reports test results in an XML format that looks like Ant's junitreport target"; - } - - void noMatchingTestCases( std::string const& /*spec*/ ) override {} - - void testRunStarting( TestRunInfo const& runInfo ) override { - CumulativeReporterBase::testRunStarting( runInfo ); - xml.startElement( "testsuites" ); - } - - void testGroupStarting( GroupInfo const& groupInfo ) override { - suiteTimer.start(); - stdOutForSuite.str(""); - stdErrForSuite.str(""); - unexpectedExceptions = 0; - CumulativeReporterBase::testGroupStarting( groupInfo ); - } - - void testCaseStarting( TestCaseInfo const& testCaseInfo ) override { - m_okToFail = testCaseInfo.okToFail(); - } - bool assertionEnded( AssertionStats const& assertionStats ) override { - if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException && !m_okToFail ) - unexpectedExceptions++; - return CumulativeReporterBase::assertionEnded( assertionStats ); - } - - void testCaseEnded( TestCaseStats const& testCaseStats ) override { - stdOutForSuite << testCaseStats.stdOut; - stdErrForSuite << testCaseStats.stdErr; - CumulativeReporterBase::testCaseEnded( testCaseStats ); - } - - void testGroupEnded( TestGroupStats const& testGroupStats ) override { - double suiteTime = suiteTimer.getElapsedSeconds(); - CumulativeReporterBase::testGroupEnded( testGroupStats ); - writeGroup( *m_testGroups.back(), suiteTime ); - } - - void testRunEndedCumulative() override { - xml.endElement(); - } - - void writeGroup( TestGroupNode const& groupNode, double suiteTime ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testsuite" ); - TestGroupStats const& stats = groupNode.value; - xml.writeAttribute( "name", stats.groupInfo.name ); - xml.writeAttribute( "errors", unexpectedExceptions ); - xml.writeAttribute( "failures", stats.totals.assertions.failed-unexpectedExceptions ); - xml.writeAttribute( "tests", stats.totals.assertions.total() ); - xml.writeAttribute( "hostname", "tbd" ); // !TBD - if( m_config->showDurations() == ShowDurations::Never ) - xml.writeAttribute( "time", "" ); - else - xml.writeAttribute( "time", suiteTime ); - xml.writeAttribute( "timestamp", getCurrentTimestamp() ); - - // Write test cases - for( auto const& child : groupNode.children ) - writeTestCase( *child ); - - xml.scopedElement( "system-out" ).writeText( trim( stdOutForSuite.str() ), false ); - xml.scopedElement( "system-err" ).writeText( trim( stdErrForSuite.str() ), false ); - } - - void writeTestCase( TestCaseNode const& testCaseNode ) { - TestCaseStats const& stats = testCaseNode.value; - - // All test cases have exactly one section - which represents the - // test case itself. That section may have 0-n nested sections - assert( testCaseNode.children.size() == 1 ); - SectionNode const& rootSection = *testCaseNode.children.front(); - - std::string className = stats.testInfo.className; - - if( className.empty() ) { - className = fileNameTag(stats.testInfo.tags); - if ( className.empty() ) - className = "global"; - } - - if ( !m_config->name().empty() ) - className = m_config->name() + "." + className; - - writeSection( className, "", rootSection ); - } - - void writeSection( std::string const& className, - std::string const& rootName, - SectionNode const& sectionNode ) { - std::string name = trim( sectionNode.stats.sectionInfo.name ); - if( !rootName.empty() ) - name = rootName + '/' + name; - - if( !sectionNode.assertions.empty() || - !sectionNode.stdOut.empty() || - !sectionNode.stdErr.empty() ) { - XmlWriter::ScopedElement e = xml.scopedElement( "testcase" ); - if( className.empty() ) { - xml.writeAttribute( "classname", name ); - xml.writeAttribute( "name", "root" ); - } - else { - xml.writeAttribute( "classname", className ); - xml.writeAttribute( "name", name ); - } - xml.writeAttribute( "time", ::Catch::Detail::stringify( sectionNode.stats.durationInSeconds ) ); - - writeAssertions( sectionNode ); - - if( !sectionNode.stdOut.empty() ) - xml.scopedElement( "system-out" ).writeText( trim( sectionNode.stdOut ), false ); - if( !sectionNode.stdErr.empty() ) - xml.scopedElement( "system-err" ).writeText( trim( sectionNode.stdErr ), false ); - } - for( auto const& childNode : sectionNode.childSections ) - if( className.empty() ) - writeSection( name, "", *childNode ); - else - writeSection( className, name, *childNode ); - } - - void writeAssertions( SectionNode const& sectionNode ) { - for( auto const& assertion : sectionNode.assertions ) - writeAssertion( assertion ); - } - void writeAssertion( AssertionStats const& stats ) { - AssertionResult const& result = stats.assertionResult; - if( !result.isOk() ) { - std::string elementName; - switch( result.getResultType() ) { - case ResultWas::ThrewException: - case ResultWas::FatalErrorCondition: - elementName = "error"; - break; - case ResultWas::ExplicitFailure: - elementName = "failure"; - break; - case ResultWas::ExpressionFailed: - elementName = "failure"; - break; - case ResultWas::DidntThrowException: - elementName = "failure"; - break; - - // We should never see these here: - case ResultWas::Info: - case ResultWas::Warning: - case ResultWas::Ok: - case ResultWas::Unknown: - case ResultWas::FailureBit: - case ResultWas::Exception: - elementName = "internalError"; - break; - } - - XmlWriter::ScopedElement e = xml.scopedElement( elementName ); - - xml.writeAttribute( "message", result.getExpandedExpression() ); - xml.writeAttribute( "type", result.getTestMacroName() ); - - std::ostringstream oss; - if( !result.getMessage().empty() ) - oss << result.getMessage() << '\n'; - for( auto const& msg : stats.infoMessages ) - if( msg.type == ResultWas::Info ) - oss << msg.message << '\n'; - - oss << "at " << result.getSourceInfo(); - xml.writeText( oss.str(), false ); - } - } - - XmlWriter xml; - Timer suiteTimer; - std::ostringstream stdOutForSuite; - std::ostringstream stdErrForSuite; - unsigned int unexpectedExceptions = 0; - bool m_okToFail = false; - }; - - JunitReporter::~JunitReporter() {} - CATCH_REGISTER_REPORTER( "junit", JunitReporter ) - -} // end namespace Catch -// end catch_reporter_junit.cpp -// start catch_reporter_multi.cpp - -namespace Catch { - - void MultipleReporters::add( IStreamingReporterPtr&& reporter ) { - m_reporters.push_back( std::move( reporter ) ); - } - - ReporterPreferences MultipleReporters::getPreferences() const { - return m_reporters[0]->getPreferences(); - } - - std::set<Verbosity> MultipleReporters::getSupportedVerbosities() { - return std::set<Verbosity>{ }; - } - - void MultipleReporters::noMatchingTestCases( std::string const& spec ) { - for( auto const& reporter : m_reporters ) - reporter->noMatchingTestCases( spec ); - } - - void MultipleReporters::benchmarkStarting( BenchmarkInfo const& benchmarkInfo ) { - for( auto const& reporter : m_reporters ) - reporter->benchmarkStarting( benchmarkInfo ); - } - void MultipleReporters::benchmarkEnded( BenchmarkStats const& benchmarkStats ) { - for( auto const& reporter : m_reporters ) - reporter->benchmarkEnded( benchmarkStats ); - } - - void MultipleReporters::testRunStarting( TestRunInfo const& testRunInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testRunStarting( testRunInfo ); - } - - void MultipleReporters::testGroupStarting( GroupInfo const& groupInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testGroupStarting( groupInfo ); - } - - void MultipleReporters::testCaseStarting( TestCaseInfo const& testInfo ) { - for( auto const& reporter : m_reporters ) - reporter->testCaseStarting( testInfo ); - } - - void MultipleReporters::sectionStarting( SectionInfo const& sectionInfo ) { - for( auto const& reporter : m_reporters ) - reporter->sectionStarting( sectionInfo ); - } - - void MultipleReporters::assertionStarting( AssertionInfo const& assertionInfo ) { - for( auto const& reporter : m_reporters ) - reporter->assertionStarting( assertionInfo ); - } - - // The return value indicates if the messages buffer should be cleared: - bool MultipleReporters::assertionEnded( AssertionStats const& assertionStats ) { - bool clearBuffer = false; - for( auto const& reporter : m_reporters ) - clearBuffer |= reporter->assertionEnded( assertionStats ); - return clearBuffer; - } - - void MultipleReporters::sectionEnded( SectionStats const& sectionStats ) { - for( auto const& reporter : m_reporters ) - reporter->sectionEnded( sectionStats ); - } - - void MultipleReporters::testCaseEnded( TestCaseStats const& testCaseStats ) { - for( auto const& reporter : m_reporters ) - reporter->testCaseEnded( testCaseStats ); - } - - void MultipleReporters::testGroupEnded( TestGroupStats const& testGroupStats ) { - for( auto const& reporter : m_reporters ) - reporter->testGroupEnded( testGroupStats ); - } - - void MultipleReporters::testRunEnded( TestRunStats const& testRunStats ) { - for( auto const& reporter : m_reporters ) - reporter->testRunEnded( testRunStats ); - } - - void MultipleReporters::skipTest( TestCaseInfo const& testInfo ) { - for( auto const& reporter : m_reporters ) - reporter->skipTest( testInfo ); - } - - bool MultipleReporters::isMulti() const { - return true; - } - -} // end namespace Catch -// end catch_reporter_multi.cpp -// start catch_reporter_xml.cpp - -#if defined(_MSC_VER) -#pragma warning(push) -#pragma warning(disable:4061) // Not all labels are EXPLICITLY handled in switch - // Note that 4062 (not all labels are handled - // and default is missing) is enabled -#endif - -namespace Catch { - class XmlReporter : public StreamingReporterBase<XmlReporter> { - public: - XmlReporter( ReporterConfig const& _config ) - : StreamingReporterBase( _config ), - m_xml(_config.stream()) - { - m_reporterPrefs.shouldRedirectStdOut = true; - } - - ~XmlReporter() override; - - static std::string getDescription() { - return "Reports test results as an XML document"; - } - - virtual std::string getStylesheetRef() const { - return std::string(); - } - - void writeSourceInfo( SourceLineInfo const& sourceInfo ) { - m_xml - .writeAttribute( "filename", sourceInfo.file ) - .writeAttribute( "line", sourceInfo.line ); - } - - public: // StreamingReporterBase - - void noMatchingTestCases( std::string const& s ) override { - StreamingReporterBase::noMatchingTestCases( s ); - } - - void testRunStarting( TestRunInfo const& testInfo ) override { - StreamingReporterBase::testRunStarting( testInfo ); - std::string stylesheetRef = getStylesheetRef(); - if( !stylesheetRef.empty() ) - m_xml.writeStylesheetRef( stylesheetRef ); - m_xml.startElement( "Catch" ); - if( !m_config->name().empty() ) - m_xml.writeAttribute( "name", m_config->name() ); - } - - void testGroupStarting( GroupInfo const& groupInfo ) override { - StreamingReporterBase::testGroupStarting( groupInfo ); - m_xml.startElement( "Group" ) - .writeAttribute( "name", groupInfo.name ); - } - - void testCaseStarting( TestCaseInfo const& testInfo ) override { - StreamingReporterBase::testCaseStarting(testInfo); - m_xml.startElement( "TestCase" ) - .writeAttribute( "name", trim( testInfo.name ) ) - .writeAttribute( "description", testInfo.description ) - .writeAttribute( "tags", testInfo.tagsAsString() ); - - writeSourceInfo( testInfo.lineInfo ); - - if ( m_config->showDurations() == ShowDurations::Always ) - m_testCaseTimer.start(); - m_xml.ensureTagClosed(); - } - - void sectionStarting( SectionInfo const& sectionInfo ) override { - StreamingReporterBase::sectionStarting( sectionInfo ); - if( m_sectionDepth++ > 0 ) { - m_xml.startElement( "Section" ) - .writeAttribute( "name", trim( sectionInfo.name ) ) - .writeAttribute( "description", sectionInfo.description ); - writeSourceInfo( sectionInfo.lineInfo ); - m_xml.ensureTagClosed(); - } - } - - void assertionStarting( AssertionInfo const& ) override { } - - bool assertionEnded( AssertionStats const& assertionStats ) override { - - AssertionResult const& result = assertionStats.assertionResult; - - bool includeResults = m_config->includeSuccessfulResults() || !result.isOk(); - - if( includeResults ) { - // Print any info messages in <Info> tags. - for( auto const& msg : assertionStats.infoMessages ) { - if( msg.type == ResultWas::Info ) { - m_xml.scopedElement( "Info" ) - .writeText( msg.message ); - } else if ( msg.type == ResultWas::Warning ) { - m_xml.scopedElement( "Warning" ) - .writeText( msg.message ); - } - } - } - - // Drop out if result was successful but we're not printing them. - if( !includeResults && result.getResultType() != ResultWas::Warning ) - return true; - - // Print the expression if there is one. - if( result.hasExpression() ) { - m_xml.startElement( "Expression" ) - .writeAttribute( "success", result.succeeded() ) - .writeAttribute( "type", result.getTestMacroName() ); - - writeSourceInfo( result.getSourceInfo() ); - - m_xml.scopedElement( "Original" ) - .writeText( result.getExpression() ); - m_xml.scopedElement( "Expanded" ) - .writeText( result.getExpandedExpression() ); - } - - // And... Print a result applicable to each result type. - switch( result.getResultType() ) { - case ResultWas::ThrewException: - m_xml.startElement( "Exception" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::FatalErrorCondition: - m_xml.startElement( "FatalErrorCondition" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - case ResultWas::Info: - m_xml.scopedElement( "Info" ) - .writeText( result.getMessage() ); - break; - case ResultWas::Warning: - // Warning will already have been written - break; - case ResultWas::ExplicitFailure: - m_xml.startElement( "Failure" ); - writeSourceInfo( result.getSourceInfo() ); - m_xml.writeText( result.getMessage() ); - m_xml.endElement(); - break; - default: - break; - } - - if( result.hasExpression() ) - m_xml.endElement(); - - return true; - } - - void sectionEnded( SectionStats const& sectionStats ) override { - StreamingReporterBase::sectionEnded( sectionStats ); - if( --m_sectionDepth > 0 ) { - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); - e.writeAttribute( "successes", sectionStats.assertions.passed ); - e.writeAttribute( "failures", sectionStats.assertions.failed ); - e.writeAttribute( "expectedFailures", sectionStats.assertions.failedButOk ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", sectionStats.durationInSeconds ); - - m_xml.endElement(); - } - } - - void testCaseEnded( TestCaseStats const& testCaseStats ) override { - StreamingReporterBase::testCaseEnded( testCaseStats ); - XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); - e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); - - if ( m_config->showDurations() == ShowDurations::Always ) - e.writeAttribute( "durationInSeconds", m_testCaseTimer.getElapsedSeconds() ); - - if( !testCaseStats.stdOut.empty() ) - m_xml.scopedElement( "StdOut" ).writeText( trim( testCaseStats.stdOut ), false ); - if( !testCaseStats.stdErr.empty() ) - m_xml.scopedElement( "StdErr" ).writeText( trim( testCaseStats.stdErr ), false ); - - m_xml.endElement(); - } - - void testGroupEnded( TestGroupStats const& testGroupStats ) override { - StreamingReporterBase::testGroupEnded( testGroupStats ); - // TODO: Check testGroupStats.aborting and act accordingly. - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testGroupStats.totals.assertions.passed ) - .writeAttribute( "failures", testGroupStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testGroupStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } - - void testRunEnded( TestRunStats const& testRunStats ) override { - StreamingReporterBase::testRunEnded( testRunStats ); - m_xml.scopedElement( "OverallResults" ) - .writeAttribute( "successes", testRunStats.totals.assertions.passed ) - .writeAttribute( "failures", testRunStats.totals.assertions.failed ) - .writeAttribute( "expectedFailures", testRunStats.totals.assertions.failedButOk ); - m_xml.endElement(); - } - - private: - Timer m_testCaseTimer; - XmlWriter m_xml; - int m_sectionDepth = 0; - }; - - XmlReporter::~XmlReporter() {} - CATCH_REGISTER_REPORTER( "xml", XmlReporter ) - -} // end namespace Catch - -#if defined(_MSC_VER) -#pragma warning(pop) -#endif -// end catch_reporter_xml.cpp - -namespace Catch { - LeakDetector leakDetector; -} - -#ifdef __clang__ -#pragma clang diagnostic pop -#endif - -// end catch_impl.hpp -#endif - -#ifdef CATCH_CONFIG_MAIN -// start catch_default_main.hpp - -#ifndef __OBJC__ - -#if defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN) -// Standard C/C++ Win32 Unicode wmain entry point -extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) { -#else -// Standard C/C++ main entry point -int main (int argc, char * argv[]) { -#endif - - return Catch::Session().run( argc, argv ); -} - -#else // __OBJC__ - -// Objective-C entry point -int main (int argc, char * const argv[]) { -#if !CATCH_ARC_ENABLED - NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; -#endif - - Catch::registerTestMethods(); - int result = Catch::Session().run( argc, (char**)argv ); - -#if !CATCH_ARC_ENABLED - [pool drain]; -#endif - - return result; -} - -#endif // __OBJC__ - -// end catch_default_main.hpp -#endif - -#ifdef CLARA_CONFIG_MAIN_NOT_DEFINED -# undef CLARA_CONFIG_MAIN -#endif - -#if !defined(CATCH_CONFIG_DISABLE) -////// -// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ -#ifdef CATCH_CONFIG_PREFIX_ALL - -#define CATCH_REQUIRE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define CATCH_REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) - -#define CATCH_REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_REQUIRE_THROWS", Catch::ResultDisposition::Normal, "", __VA_ARGS__ ) -#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) -#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) -#endif// CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) - -#define CATCH_CHECK( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define CATCH_CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CATCH_CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CATCH_CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CATCH_CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) - -#define CATCH_CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CATCH_CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, "", __VA_ARGS__ ) -#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CATCH_CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CATCH_CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CATCH_CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CATCH_CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) - -#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO( msg ) INTERNAL_CATCH_INFO( "CATCH_INFO", msg ) -#define CATCH_WARN( msg ) INTERNAL_CATCH_MSG( "CATCH_WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CATCH_CAPTURE( msg ) INTERNAL_CATCH_INFO( "CATCH_CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) - -#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) -#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) -#define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) -#define CATCH_REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) -#define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) -#define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define CATCH_FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "CATCH_FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( "CATCH_SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) CATCH_TEST_CASE( "Scenario: " __VA_ARGS__ ) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) -#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc ) -#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) -#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc ) - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#define REQUIRE( ... ) INTERNAL_CATCH_TEST( "REQUIRE", Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define REQUIRE_FALSE( ... ) INTERNAL_CATCH_TEST( "REQUIRE_FALSE", Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) - -#define REQUIRE_THROWS( ... ) INTERNAL_CATCH_THROWS( "REQUIRE_THROWS", Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "REQUIRE_THROWS_AS", exceptionType, Catch::ResultDisposition::Normal, expr ) -#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "REQUIRE_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::Normal, matcher, expr ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define REQUIRE_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "REQUIRE_NOTHROW", Catch::ResultDisposition::Normal, __VA_ARGS__ ) - -#define CHECK( ... ) INTERNAL_CATCH_TEST( "CHECK", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECK_FALSE( ... ) INTERNAL_CATCH_TEST( "CHECK_FALSE", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::FalseTest, __VA_ARGS__ ) -#define CHECKED_IF( ... ) INTERNAL_CATCH_IF( "CHECKED_IF", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECKED_ELSE( ... ) INTERNAL_CATCH_ELSE( "CHECKED_ELSE", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECK_NOFAIL( ... ) INTERNAL_CATCH_TEST( "CHECK_NOFAIL", Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, __VA_ARGS__ ) - -#define CHECK_THROWS( ... ) INTERNAL_CATCH_THROWS( "CHECK_THROWS", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( "CHECK_THROWS_AS", exceptionType, Catch::ResultDisposition::ContinueOnFailure, expr ) -#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "CHECK_THROWS_WITH", Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) INTERNAL_CATCH_THROWS_MATCHES( "CHECK_THROWS_MATCHES", exceptionType, Catch::ResultDisposition::ContinueOnFailure, matcher, expr ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CHECK_NOTHROW( ... ) INTERNAL_CATCH_NO_THROW( "CHECK_NOTHROW", Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg ) - -#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg ) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO( msg ) INTERNAL_CATCH_INFO( "INFO", msg ) -#define WARN( msg ) INTERNAL_CATCH_MSG( "WARN", Catch::ResultWas::Warning, Catch::ResultDisposition::ContinueOnFailure, msg ) -#define CAPTURE( msg ) INTERNAL_CATCH_INFO( "CAPTURE", #msg " := " << ::Catch::Detail::stringify(msg) ) - -#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) -#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) -#define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) -#define REGISTER_TEST_CASE( Function, ... ) INTERNAL_CATCH_REGISTER_TESTCASE( Function, __VA_ARGS__ ) -#define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) -#define FAIL( ... ) INTERNAL_CATCH_MSG( "FAIL", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, __VA_ARGS__ ) -#define FAIL_CHECK( ... ) INTERNAL_CATCH_MSG( "FAIL_CHECK", Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define SUCCEED( ... ) INTERNAL_CATCH_MSG( "SUCCEED", Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, __VA_ARGS__ ) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE() - -#endif - -#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION( signature ) - -// "BDD-style" convenience wrappers -#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ ) - -#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc ) -#define WHEN( desc ) SECTION( std::string(" When: ") + desc ) -#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc ) -#define THEN( desc ) SECTION( std::string(" Then: ") + desc ) -#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc ) - -using Catch::Detail::Approx; - -#else -////// -// If this config identifier is defined then all CATCH macros are prefixed with CATCH_ -#ifdef CATCH_CONFIG_PREFIX_ALL - -#define CATCH_REQUIRE( ... ) (void)(0) -#define CATCH_REQUIRE_FALSE( ... ) (void)(0) - -#define CATCH_REQUIRE_THROWS( ... ) (void)(0) -#define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) -#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif// CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_REQUIRE_NOTHROW( ... ) (void)(0) - -#define CATCH_CHECK( ... ) (void)(0) -#define CATCH_CHECK_FALSE( ... ) (void)(0) -#define CATCH_CHECKED_IF( ... ) if (__VA_ARGS__) -#define CATCH_CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) -#define CATCH_CHECK_NOFAIL( ... ) (void)(0) - -#define CATCH_CHECK_THROWS( ... ) (void)(0) -#define CATCH_CHECK_THROWS_AS( expr, exceptionType ) (void)(0) -#define CATCH_CHECK_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CATCH_CHECK_NOTHROW( ... ) (void)(0) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CATCH_CHECK_THAT( arg, matcher ) (void)(0) - -#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define CATCH_INFO( msg ) (void)(0) -#define CATCH_WARN( msg ) (void)(0) -#define CATCH_CAPTURE( msg ) (void)(0) - -#define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_METHOD_AS_TEST_CASE( method, ... ) -#define CATCH_REGISTER_TEST_CASE( Function, ... ) (void)(0) -#define CATCH_SECTION( ... ) -#define CATCH_FAIL( ... ) (void)(0) -#define CATCH_FAIL_CHECK( ... ) (void)(0) -#define CATCH_SUCCEED( ... ) (void)(0) - -#define CATCH_ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) - -// "BDD-style" convenience wrappers -#define CATCH_SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define CATCH_SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) -#define CATCH_GIVEN( desc ) -#define CATCH_WHEN( desc ) -#define CATCH_AND_WHEN( desc ) -#define CATCH_THEN( desc ) -#define CATCH_AND_THEN( desc ) - -// If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required -#else - -#define REQUIRE( ... ) (void)(0) -#define REQUIRE_FALSE( ... ) (void)(0) - -#define REQUIRE_THROWS( ... ) (void)(0) -#define REQUIRE_THROWS_AS( expr, exceptionType ) (void)(0) -#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define REQUIRE_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define REQUIRE_NOTHROW( ... ) (void)(0) - -#define CHECK( ... ) (void)(0) -#define CHECK_FALSE( ... ) (void)(0) -#define CHECKED_IF( ... ) if (__VA_ARGS__) -#define CHECKED_ELSE( ... ) if (!(__VA_ARGS__)) -#define CHECK_NOFAIL( ... ) (void)(0) - -#define CHECK_THROWS( ... ) (void)(0) -#define CHECK_THROWS_AS( expr, exceptionType ) (void)(0) -#define CHECK_THROWS_WITH( expr, matcher ) (void)(0) -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THROWS_MATCHES( expr, exceptionType, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS -#define CHECK_NOTHROW( ... ) (void)(0) - -#if !defined(CATCH_CONFIG_DISABLE_MATCHERS) -#define CHECK_THAT( arg, matcher ) (void)(0) - -#define REQUIRE_THAT( arg, matcher ) (void)(0) -#endif // CATCH_CONFIG_DISABLE_MATCHERS - -#define INFO( msg ) (void)(0) -#define WARN( msg ) (void)(0) -#define CAPTURE( msg ) (void)(0) - -#define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) -#define METHOD_AS_TEST_CASE( method, ... ) -#define REGISTER_TEST_CASE( Function, ... ) (void)(0) -#define SECTION( ... ) -#define FAIL( ... ) (void)(0) -#define FAIL_CHECK( ... ) (void)(0) -#define SUCCEED( ... ) (void)(0) -#define ANON_TEST_CASE() INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )) - -#endif - -#define CATCH_TRANSLATE_EXCEPTION( signature ) INTERNAL_CATCH_TRANSLATE_EXCEPTION_NO_REG( INTERNAL_CATCH_UNIQUE_NAME( catch_internal_ExceptionTranslator ), signature ) - -// "BDD-style" convenience wrappers -#define SCENARIO( ... ) INTERNAL_CATCH_TESTCASE_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ) ) -#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TESTCASE_METHOD_NO_REGISTRATION(INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ ), className ) - -#define GIVEN( desc ) -#define WHEN( desc ) -#define AND_WHEN( desc ) -#define THEN( desc ) -#define AND_THEN( desc ) - -using Catch::Detail::Approx; - -#endif - -// start catch_reenable_warnings.h - - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(pop) -# else -# pragma clang diagnostic pop -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif - -// end catch_reenable_warnings.h -// end catch.hpp -#endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED - diff --git a/src/nmodl/ext/flags/LICENSE b/src/nmodl/ext/flags/LICENSE deleted file mode 100644 index 9c0f1f8112..0000000000 --- a/src/nmodl/ext/flags/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014 Dmitry Arkhipov - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/nmodl/ext/flags/allow_flags.hpp b/src/nmodl/ext/flags/allow_flags.hpp deleted file mode 100644 index 57ec11e22e..0000000000 --- a/src/nmodl/ext/flags/allow_flags.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef ENUM_CLASS_ALLOW_FLAGS_HPP -#define ENUM_CLASS_ALLOW_FLAGS_HPP - - -#include <type_traits> - - -namespace flags { - - -template <class E, class Enabler = void> struct is_flags -: public std::false_type {}; - - -} // namespace flags - - -#define ALLOW_FLAGS_FOR_ENUM(name) \ -namespace flags { \ -template <> struct is_flags< name > : std::true_type {}; \ -} - - -#endif // ENUM_CLASS_ALLOW_FLAGS_HPP diff --git a/src/nmodl/ext/flags/flags.hpp b/src/nmodl/ext/flags/flags.hpp deleted file mode 100644 index e6811ca46b..0000000000 --- a/src/nmodl/ext/flags/flags.hpp +++ /dev/null @@ -1,309 +0,0 @@ -#ifndef ENUM_CLASS_FLAGS_HPP -#define ENUM_CLASS_FLAGS_HPP - - -#include "allow_flags.hpp" -#include "iterator.hpp" - -#include <bitset> -#include <initializer_list> -#include <numeric> -#include <utility> - - -namespace flags { - - -constexpr struct empty_t { - constexpr empty_t() noexcept {} -} empty; - - -template <class E> class flags { -public: - static_assert(is_flags<E>::value, - "flags::flags is disallowed for this type; " - "use ALLOW_FLAGS_FOR_ENUM macro."); - - using enum_type = typename std::decay<E>::type; - using underlying_type = typename std::underlying_type<enum_type>::type; - using impl_type = typename std::make_unsigned<underlying_type>::type; - - using iterator = FlagsIterator<enum_type>; - using const_iterator = iterator; - using value_type = typename iterator::value_type; - using reference = typename iterator::reference; - using const_reference = typename iterator::reference; - using pointer = enum_type *; - using const_pointer = const enum_type *; - using size_type = std::size_t; - using difference_type = typename iterator::difference_type; - - - constexpr static std::size_t bit_size() { return sizeof(impl_type) * 8; } - - -private: - template <class T, class Res = std::nullptr_t> - using convertible = std::enable_if<std::is_convertible<T, enum_type>::value, - Res>; - - -public: - flags() noexcept = default; - flags(const flags &fl) noexcept = default; - flags &operator=(const flags &fl) noexcept = default; - flags(flags &&fl) noexcept = default; - flags &operator=(flags &&fl) noexcept= default; - - - explicit constexpr flags(empty_t) noexcept : val_(0) {} - - -#ifdef ENUM_CLASS_FLAGS_FORBID_IMPLICT_CONVERSION - explicit -#endif - constexpr flags(enum_type e) noexcept - : val_(static_cast<impl_type>(e)) {} - - flags &operator=(enum_type e) noexcept { - val_ = static_cast<impl_type>(e); - return *this; - } - - - flags(std::initializer_list<enum_type> il) noexcept : val_(0) { insert(il); } - - flags &operator=(std::initializer_list<enum_type> il) noexcept { - clear(); - insert(il); - return *this; - } - - template <class ... Args> - flags(enum_type e, Args ... args) noexcept : flags{e, args...} {} - - - template <class FwIter> - flags(FwIter b, FwIter e, - typename convertible<decltype(*b)>::type = nullptr) - noexcept(noexcept(std::declval<flags>().insert(std::declval<FwIter>(), - std::declval<FwIter>()))) - : val_(0) - { insert(b, e); } - - - constexpr explicit operator bool() const noexcept { return val_; } - - constexpr bool operator!() const noexcept { return !val_; } - - friend constexpr bool operator==(flags fl1, flags fl2) { - return fl1.val_ == fl2.val_; - } - - friend constexpr bool operator!=(flags fl1, flags fl2) { - return fl1.val_ != fl2.val_; - } - - - constexpr flags operator~() const noexcept { return flags(~val_); } - - flags &operator|=(const flags &fl) noexcept { - val_ |= fl.val_; - return *this; - } - - flags &operator&=(const flags &fl) noexcept { - val_ &= fl.val_; - return *this; - } - - flags &operator^=(const flags &fl) noexcept { - val_ ^= fl.val_; - return *this; - } - - - flags &operator|=(enum_type e) noexcept { - val_ |= static_cast<impl_type>(e); - return *this; - } - - flags &operator&=(enum_type e) noexcept { - val_ &= static_cast<impl_type>(e); - return *this; - } - - flags &operator^=(enum_type e) noexcept { - val_ ^= static_cast<impl_type>(e); - return *this; - } - - friend constexpr flags operator|(flags f1, flags f2) noexcept { - return flags{static_cast<impl_type>(f1.val_ | f2.val_)}; - } - - friend constexpr flags operator&(flags f1, flags f2) noexcept { - return flags{static_cast<impl_type>(f1.val_ & f2.val_)}; - } - - friend constexpr flags operator^(flags f1, flags f2) noexcept { - return flags{static_cast<impl_type>(f1.val_ ^ f2.val_)}; - } - - - void swap(flags &fl) noexcept { std::swap(val_, fl.val_); } - - - constexpr underlying_type underlying_value() const noexcept { - return static_cast<underlying_type>(val_); - } - - void set_underlying_value(underlying_type newval) noexcept { - val_ = static_cast<impl_type>(newval); - } - - - constexpr explicit operator std::bitset<bit_size()>() const noexcept { - return to_bitset(); - } - - constexpr std::bitset<bit_size()> to_bitset() const noexcept { - return {val_}; - } - - - constexpr bool empty() const noexcept { return !val_; } - - size_type size() const noexcept { - return std::distance(this->begin(), this->end()); - } - - constexpr size_type max_size() const noexcept { return bit_size(); } - - - iterator begin() const noexcept { return cbegin(); } - iterator cbegin() const noexcept { return iterator{val_}; } - - constexpr iterator end() const noexcept { return cend(); } - constexpr iterator cend() const noexcept { return {}; } - - - constexpr iterator find(enum_type e) const noexcept { return {val_, e}; } - - constexpr size_type count(enum_type e) const noexcept { - return find(e) != end() ? 1 : 0; - } - - - std::pair<iterator, iterator> equal_range(enum_type e) const noexcept { - auto i = find(e); - auto j = i; - return {i, ++j}; - } - - - template <class... Args> - std::pair<iterator, bool> emplace(Args && ... args) noexcept { - return insert(enum_type{args...}); - } - - template <class... Args> - iterator emplace_hint(iterator, Args && ... args) noexcept { - return emplace(args...).first; - } - - - std::pair<iterator, bool> insert(enum_type e) noexcept { - auto i = find(e); - if (i == end()) { - i.mask_ = static_cast<impl_type>(e); - val_ |= i.mask_; - update_uvalue(i); - return {i, true}; - } - return {i, false}; - } - - std::pair<iterator, bool> insert(iterator, enum_type e) noexcept { - return insert(e); - } - - template <class FwIter> - auto insert(FwIter i1, FwIter i2) - noexcept(noexcept(++i1) && noexcept(*i1) && noexcept(i1 == i2)) - -> typename convertible<decltype(*i1), void>::type { - val_ |= std::accumulate(i1, i2, impl_type{0}, [](impl_type i, enum_type e) { - return i | static_cast<impl_type>(e); - }); - } - - template <class Container> - auto insert(const Container &ctn) noexcept - -> decltype(std::begin(ctn), std::end(ctn), void()) { - insert(std::begin(ctn), std::end(ctn)); - } - - - iterator erase(iterator i) noexcept { - val_ ^= i.mask_; - update_uvalue(i); - return ++i; - } - - size_type erase(enum_type e) noexcept { - auto e_count = count(e); - val_ ^= static_cast<impl_type>(e); - return e_count; - } - - iterator erase(iterator i1, iterator i2) noexcept { - val_ ^= flags(i1, i2).val_; - update_uvalue(i2); - return ++i2; - } - - - void clear() noexcept { val_ = 0; } - -private: - constexpr explicit flags(impl_type val) noexcept : val_(val) {} - - void update_uvalue(iterator &it) const noexcept { it.uvalue_ = val_; } - - impl_type val_; -}; - - -template <class E> -void swap(flags<E> &fl1, flags<E> &fl2) noexcept { fl1.swap(fl2); } - - -} // namespace flags - - -template <class E> -constexpr auto operator|(E e1, E e2) noexcept --> typename std::enable_if<flags::is_flags<E>::value, - flags::flags<E>>::type { - return flags::flags<E>(e1) | e2; -} - - -template <class E> -constexpr auto operator&(E e1, E e2) noexcept --> typename std::enable_if<flags::is_flags<E>::value, - flags::flags<E>>::type { - return flags::flags<E>(e1) & e2; -} - - -template <class E> -constexpr auto operator^(E e1, E e2) noexcept --> typename std::enable_if<flags::is_flags<E>::value, - flags::flags<E>>::type { - return flags::flags<E>(e1) ^ e2; -} - - -#endif // ENUM_CLASS_FLAGS_HPP diff --git a/src/nmodl/ext/flags/flagsfwd.hpp b/src/nmodl/ext/flags/flagsfwd.hpp deleted file mode 100644 index 0fdfbe73ee..0000000000 --- a/src/nmodl/ext/flags/flagsfwd.hpp +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef ENUM_CLASS_FLAGSFWD_HPP -#define ENUM_CLASS_FLAGSFWD_HPP - - -namespace flags { template <class E> class flags; } - - -#endif // ENUM_CLASS_FLAGSFWD_HPP diff --git a/src/nmodl/ext/flags/iterator.hpp b/src/nmodl/ext/flags/iterator.hpp deleted file mode 100644 index 732a1e19d7..0000000000 --- a/src/nmodl/ext/flags/iterator.hpp +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef ENUM_CLASS_ITERATOR_HPP -#define ENUM_CLASS_ITERATOR_HPP - - -#include "flagsfwd.hpp" - -#include <iterator> - - -namespace flags { - - -template <class E> -class FlagsIterator { -public: - using flags_type = flags<E>; - using difference_type = std::ptrdiff_t; - using value_type = E; - using pointer = value_type *; - using reference = const value_type; - using iterator_category = std::forward_iterator_tag; - - - constexpr FlagsIterator() noexcept : uvalue_(0), mask_(0) {} - - constexpr FlagsIterator(const FlagsIterator &other) noexcept - : uvalue_(other.uvalue_), mask_(other.mask_) {} - - - FlagsIterator &operator++() noexcept { - nextMask(); - return *this; - } - FlagsIterator operator++(int) noexcept { - auto copy = *this; - ++(*this); - return copy; - } - - - constexpr reference operator*() const noexcept { - return static_cast<value_type>(mask_); - } - - - friend inline constexpr bool operator==(const FlagsIterator &i, - const FlagsIterator &j) noexcept { - return i.mask_ == j.mask_; - } - - friend inline constexpr bool operator!=(const FlagsIterator &i, - const FlagsIterator &j) noexcept { - return i.mask_ != j.mask_; - } - - -private: - template <class E_> friend class flags; - - using impl_type = typename flags_type::impl_type; - - - explicit FlagsIterator(impl_type uv) noexcept : uvalue_(uv), mask_(1) { - if (!(mask_ & uvalue_)) { nextMask(); } - } - - constexpr FlagsIterator(impl_type uv, E e) noexcept - : uvalue_(uv) - , mask_(static_cast<impl_type>(static_cast<impl_type>(e) & uv)) - {} - - - void nextMask() noexcept { - do { mask_ <<= 1; } while (mask_ && !(mask_ & uvalue_)); - } - - - impl_type uvalue_; - impl_type mask_; -}; - - -} // namespace flags - - -#endif // ENUM_CLASS_ITERATOR_HPP diff --git a/src/nmodl/ext/fmt/core.h b/src/nmodl/ext/fmt/core.h deleted file mode 100644 index 5912afef82..0000000000 --- a/src/nmodl/ext/fmt/core.h +++ /dev/null @@ -1,1502 +0,0 @@ -// Formatting library for C++ - the core API -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_CORE_H_ -#define FMT_CORE_H_ - -#include <cassert> -#include <cstdio> // std::FILE -#include <cstring> -#include <iterator> -#include <string> -#include <type_traits> - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 50201 - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if defined(__has_include) && !defined(__INTELLISENSE__) && \ - (!defined(__INTEL_COMPILER) || __INTEL_COMPILER >= 1600) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#else -# define FMT_GCC_VERSION 0 -#endif - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION -#else -# define FMT_HAS_GXX_CXX11 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER -#else -# define FMT_MSC_VER 0 -#endif - -// Check if relaxed C++14 constexpr is supported. -// GCC doesn't allow throw in constexpr until version 6 (bug 67371). -#ifndef FMT_USE_CONSTEXPR -# define FMT_USE_CONSTEXPR \ - (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ - (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) -#endif -#if FMT_USE_CONSTEXPR -# define FMT_CONSTEXPR constexpr -# define FMT_CONSTEXPR_DECL constexpr -#else -# define FMT_CONSTEXPR inline -# define FMT_CONSTEXPR_DECL -#endif - -#ifndef FMT_USE_CONSTEXPR11 -# define FMT_USE_CONSTEXPR11 \ - (FMT_MSC_VER >= 1900 || FMT_GCC_VERSION >= 406 || FMT_USE_CONSTEXPR) -#endif -#if FMT_USE_CONSTEXPR11 -# define FMT_CONSTEXPR11 constexpr -#else -# define FMT_CONSTEXPR11 -#endif - -#ifndef FMT_OVERRIDE -# if FMT_HAS_FEATURE(cxx_override) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE -# endif -#endif - -#if FMT_HAS_FEATURE(cxx_explicit_conversions) || FMT_MSC_VER >= 1800 -# define FMT_EXPLICIT explicit -#else -# define FMT_EXPLICIT -#endif - -#ifndef FMT_NULL -# if FMT_HAS_FEATURE(cxx_nullptr) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600 -# define FMT_NULL nullptr -# define FMT_USE_NULLPTR 1 -# else -# define FMT_NULL NULL -# endif -#endif - -#ifndef FMT_USE_NULLPTR -# define FMT_USE_NULLPTR 0 -#endif - -#if FMT_HAS_CPP_ATTRIBUTE(noreturn) -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -// Check if exceptions are disabled. -#if defined(__GNUC__) && !defined(__EXCEPTIONS) -# define FMT_EXCEPTIONS 0 -#elif FMT_MSC_VER && !_HAS_EXCEPTIONS -# define FMT_EXCEPTIONS 0 -#endif -#ifndef FMT_EXCEPTIONS -# define FMT_EXCEPTIONS 1 -#endif - -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 -#endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept -# define FMT_HAS_CXX11_NOEXCEPT 1 -#else -# define FMT_DETECTED_NOEXCEPT throw() -# define FMT_HAS_CXX11_NOEXCEPT 0 -#endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT -# else -# define FMT_NOEXCEPT -# endif -#endif - -// This is needed because GCC still uses throw() in its headers when exceptions -// are disabled. -#if FMT_GCC_VERSION -# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT -#else -# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT -#endif - -#ifndef FMT_BEGIN_NAMESPACE -# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ - FMT_MSC_VER >= 1900 -# define FMT_INLINE_NAMESPACE inline namespace -# define FMT_END_NAMESPACE }} -# else -# define FMT_INLINE_NAMESPACE namespace -# define FMT_END_NAMESPACE } using namespace v5; } -# endif -# define FMT_BEGIN_NAMESPACE namespace fmt { FMT_INLINE_NAMESPACE v5 { -#endif - -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif - -#ifndef FMT_ASSERT -# define FMT_ASSERT(condition, message) assert((condition) && message) -#endif - -// libc++ supports string_view in pre-c++17. -#if (FMT_HAS_INCLUDE(<string_view>) && \ - (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ - (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) -# include <string_view> -# define FMT_USE_STD_STRING_VIEW -#elif (FMT_HAS_INCLUDE(<experimental/string_view>) && \ - __cplusplus >= 201402L) -# include <experimental/string_view> -# define FMT_USE_EXPERIMENTAL_STRING_VIEW -#endif - -// std::result_of is defined in <functional> in gcc 4.4. -#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 -# include <functional> -#endif - -FMT_BEGIN_NAMESPACE -namespace internal { - -// An implementation of declval for pre-C++11 compilers such as gcc 4. -template <typename T> -typename std::add_rvalue_reference<T>::type declval() FMT_NOEXCEPT; - -template <typename> -struct result_of; - -template <typename F, typename... Args> -struct result_of<F(Args...)> { - // A workaround for gcc 4.4 that doesn't allow F to be a reference. - typedef typename std::result_of< - typename std::remove_reference<F>::type(Args...)>::type type; -}; - -// Casts nonnegative integer to unsigned. -template <typename Int> -FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) { - FMT_ASSERT(value >= 0, "negative value"); - return static_cast<typename std::make_unsigned<Int>::type>(value); -} - -// A constexpr std::char_traits::length replacement for pre-C++17. -template <typename Char> -FMT_CONSTEXPR size_t length(const Char *s) { - const Char *start = s; - while (*s) ++s; - return s - start; -} -#if FMT_GCC_VERSION -FMT_CONSTEXPR size_t length(const char *s) { return std::strlen(s); } -#endif -} // namespace internal - -/** - An implementation of ``std::basic_string_view`` for pre-C++17. It provides a - subset of the API. ``fmt::basic_string_view`` is used for format strings even - if ``std::string_view`` is available to prevent issues when a library is - compiled with a different ``-std`` option than the client code (which is not - recommended). - */ -template <typename Char> -class basic_string_view { - private: - const Char *data_; - size_t size_; - - public: - typedef Char char_type; - typedef const Char *iterator; - - // Standard basic_string_view type. -#if defined(FMT_USE_STD_STRING_VIEW) - typedef std::basic_string_view<Char> type; -#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) - typedef std::experimental::basic_string_view<Char> type; -#else - struct type { - const char *data() const { return FMT_NULL; } - size_t size() const { return 0; } - }; -#endif - - FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(FMT_NULL), size_(0) {} - - /** Constructs a string reference object from a C string and a size. */ - FMT_CONSTEXPR basic_string_view(const Char *s, size_t count) FMT_NOEXCEPT - : data_(s), size_(count) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits<Char>::length``. - \endrst - */ - FMT_CONSTEXPR basic_string_view(const Char *s) - : data_(s), size_(internal::length(s)) {} - - /** Constructs a string reference from a ``std::basic_string`` object. */ - template <typename Alloc> - FMT_CONSTEXPR basic_string_view( - const std::basic_string<Char, Alloc> &s) FMT_NOEXCEPT - : data_(s.data()), size_(s.size()) {} - - FMT_CONSTEXPR basic_string_view(type s) FMT_NOEXCEPT - : data_(s.data()), size_(s.size()) {} - - /** Returns a pointer to the string data. */ - FMT_CONSTEXPR const Char *data() const { return data_; } - - /** Returns the string size. */ - FMT_CONSTEXPR size_t size() const { return size_; } - - FMT_CONSTEXPR iterator begin() const { return data_; } - FMT_CONSTEXPR iterator end() const { return data_ + size_; } - - FMT_CONSTEXPR void remove_prefix(size_t n) { - data_ += n; - size_ -= n; - } - - // Lexicographically compare this string reference to other. - int compare(basic_string_view other) const { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits<Char>::compare(data_, other.data_, str_size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } - - friend bool operator==(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) == 0; - } - friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) != 0; - } - friend bool operator<(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) < 0; - } - friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) <= 0; - } - friend bool operator>(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) > 0; - } - friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) >= 0; - } -}; - -typedef basic_string_view<char> string_view; -typedef basic_string_view<wchar_t> wstring_view; - -template <typename Context> -class basic_format_arg; - -template <typename Context> -class basic_format_args; - -template <typename T> -struct no_formatter_error : std::false_type {}; - -// A formatter for objects of type T. -template <typename T, typename Char = char, typename Enable = void> -struct formatter { - static_assert(no_formatter_error<T>::value, - "don't know how to format the type, include fmt/ostream.h if it provides " - "an operator<< that should be used"); - - // The following functions are not defined intentionally. - template <typename ParseContext> - typename ParseContext::iterator parse(ParseContext &); - template <typename FormatContext> - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()); -}; - -template <typename T, typename Char, typename Enable = void> -struct convert_to_int { - enum { - value = !std::is_arithmetic<T>::value && std::is_convertible<T, int>::value - }; -}; - -namespace internal { - -/** A contiguous memory buffer with an optional growing ability. */ -template <typename T> -class basic_buffer { - private: - basic_buffer(const basic_buffer &) = delete; - void operator=(const basic_buffer &) = delete; - - T *ptr_; - std::size_t size_; - std::size_t capacity_; - - protected: - // Don't initialize ptr_ since it is not accessed to save a few cycles. - basic_buffer(std::size_t sz) FMT_NOEXCEPT: size_(sz), capacity_(sz) {} - - basic_buffer(T *p = FMT_NULL, std::size_t sz = 0, std::size_t cap = 0) - FMT_NOEXCEPT: ptr_(p), size_(sz), capacity_(cap) {} - - /** Sets the buffer data and capacity. */ - void set(T *buf_data, std::size_t buf_capacity) FMT_NOEXCEPT { - ptr_ = buf_data; - capacity_ = buf_capacity; - } - - /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(std::size_t capacity) = 0; - - public: - typedef T value_type; - typedef const T &const_reference; - - virtual ~basic_buffer() {} - - T *begin() FMT_NOEXCEPT { return ptr_; } - T *end() FMT_NOEXCEPT { return ptr_ + size_; } - - /** Returns the size of this buffer. */ - std::size_t size() const FMT_NOEXCEPT { return size_; } - - /** Returns the capacity of this buffer. */ - std::size_t capacity() const FMT_NOEXCEPT { return capacity_; } - - /** Returns a pointer to the buffer data. */ - T *data() FMT_NOEXCEPT { return ptr_; } - - /** Returns a pointer to the buffer data. */ - const T *data() const FMT_NOEXCEPT { return ptr_; } - - /** - Resizes the buffer. If T is a POD type new elements may not be initialized. - */ - void resize(std::size_t new_size) { - reserve(new_size); - size_ = new_size; - } - - /** Clears this buffer. */ - void clear() { size_ = 0; } - - /** Reserves space to store at least *capacity* elements. */ - void reserve(std::size_t new_capacity) { - if (new_capacity > capacity_) - grow(new_capacity); - } - - void push_back(const T &value) { - reserve(size_ + 1); - ptr_[size_++] = value; - } - - /** Appends data to the end of the buffer. */ - template <typename U> - void append(const U *begin, const U *end); - - T &operator[](std::size_t index) { return ptr_[index]; } - const T &operator[](std::size_t index) const { return ptr_[index]; } -}; - -typedef basic_buffer<char> buffer; -typedef basic_buffer<wchar_t> wbuffer; - -// A container-backed buffer. -template <typename Container> -class container_buffer : public basic_buffer<typename Container::value_type> { - private: - Container &container_; - - protected: - void grow(std::size_t capacity) FMT_OVERRIDE { - container_.resize(capacity); - this->set(&container_[0], capacity); - } - - public: - explicit container_buffer(Container &c) - : basic_buffer<typename Container::value_type>(c.size()), container_(c) {} -}; - -struct error_handler { - FMT_CONSTEXPR error_handler() {} - FMT_CONSTEXPR error_handler(const error_handler &) {} - - // This function is intentionally not constexpr to give a compile-time error. - FMT_API void on_error(const char *message); -}; - -// Formatting of wide characters and strings into a narrow output is disallowed: -// fmt::format("{}", L"test"); // error -// To fix this, use a wide format string: -// fmt::format(L"{}", L"test"); -template <typename Char> -inline void require_wchar() { - static_assert( - std::is_same<wchar_t, Char>::value, - "formatting of wide characters into a narrow output is disallowed"); -} - -template <typename Char> -struct named_arg_base; - -template <typename T, typename Char> -struct named_arg; - -template <typename T> -struct is_named_arg : std::false_type {}; - -template <typename T, typename Char> -struct is_named_arg<named_arg<T, Char>> : std::true_type {}; - -enum type { - none_type, named_arg_type, - // Integer types should go first, - int_type, uint_type, long_long_type, ulong_long_type, bool_type, char_type, - last_integer_type = char_type, - // followed by floating-point types. - double_type, long_double_type, last_numeric_type = long_double_type, - cstring_type, string_type, pointer_type, custom_type -}; - -FMT_CONSTEXPR bool is_integral(type t) { - FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); - return t > internal::none_type && t <= internal::last_integer_type; -} - -FMT_CONSTEXPR bool is_arithmetic(type t) { - FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); - return t > internal::none_type && t <= internal::last_numeric_type; -} - -template <typename Char> -struct string_value { - const Char *value; - std::size_t size; -}; - -template <typename Context> -struct custom_value { - const void *value; - void (*format)(const void *arg, Context &ctx); -}; - -// A formatting argument value. -template <typename Context> -class value { - public: - typedef typename Context::char_type char_type; - - union { - int int_value; - unsigned uint_value; - long long long_long_value; - unsigned long long ulong_long_value; - double double_value; - long double long_double_value; - const void *pointer; - string_value<char_type> string; - string_value<signed char> sstring; - string_value<unsigned char> ustring; - custom_value<Context> custom; - }; - - FMT_CONSTEXPR value(int val = 0) : int_value(val) {} - value(unsigned val) { uint_value = val; } - value(long long val) { long_long_value = val; } - value(unsigned long long val) { ulong_long_value = val; } - value(double val) { double_value = val; } - value(long double val) { long_double_value = val; } - value(const char_type *val) { string.value = val; } - value(const signed char *val) { - static_assert(std::is_same<char, char_type>::value, - "incompatible string types"); - sstring.value = val; - } - value(const unsigned char *val) { - static_assert(std::is_same<char, char_type>::value, - "incompatible string types"); - ustring.value = val; - } - value(basic_string_view<char_type> val) { - string.value = val.data(); - string.size = val.size(); - } - value(const void *val) { pointer = val; } - - template <typename T> - explicit value(const T &val) { - custom.value = &val; - custom.format = &format_custom_arg<T>; - } - - const named_arg_base<char_type> &as_named_arg() { - return *static_cast<const named_arg_base<char_type>*>(pointer); - } - - private: - // Formats an argument of a custom type, such as a user-defined class. - template <typename T> - static void format_custom_arg(const void *arg, Context &ctx) { - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter<T>` for `format` and - // `printf_formatter<T>` for `printf`. - typename Context::template formatter_type<T>::type f; - auto &&parse_ctx = ctx.parse_context(); - parse_ctx.advance_to(f.parse(parse_ctx)); - ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx)); - } -}; - -// Value initializer used to delay conversion to value and reduce memory churn. -template <typename Context, typename T, type TYPE> -struct init { - T val; - static const type type_tag = TYPE; - - FMT_CONSTEXPR init(const T &v) : val(v) {} - FMT_CONSTEXPR operator value<Context>() const { return value<Context>(val); } -}; - -template <typename Context, typename T> -FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value); - -#define FMT_MAKE_VALUE(TAG, ArgType, ValueType) \ - template <typename C> \ - FMT_CONSTEXPR init<C, ValueType, TAG> make_value(ArgType val) { \ - return static_cast<ValueType>(val); \ - } - -#define FMT_MAKE_VALUE_SAME(TAG, Type) \ - template <typename C> \ - FMT_CONSTEXPR init<C, Type, TAG> make_value(Type val) { return val; } - -FMT_MAKE_VALUE(bool_type, bool, int) -FMT_MAKE_VALUE(int_type, short, int) -FMT_MAKE_VALUE(uint_type, unsigned short, unsigned) -FMT_MAKE_VALUE_SAME(int_type, int) -FMT_MAKE_VALUE_SAME(uint_type, unsigned) - -// To minimize the number of types we need to deal with, long is translated -// either to int or to long long depending on its size. -typedef std::conditional<sizeof(long) == sizeof(int), int, long long>::type - long_type; -FMT_MAKE_VALUE( - (sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) -typedef std::conditional<sizeof(unsigned long) == sizeof(unsigned), - unsigned, unsigned long long>::type ulong_type; -FMT_MAKE_VALUE( - (sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), - unsigned long, ulong_type) - -FMT_MAKE_VALUE_SAME(long_long_type, long long) -FMT_MAKE_VALUE_SAME(ulong_long_type, unsigned long long) -FMT_MAKE_VALUE(int_type, signed char, int) -FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) -FMT_MAKE_VALUE(char_type, char, int) - -#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) -template <typename C> -inline init<C, int, char_type> make_value(wchar_t val) { - require_wchar<typename C::char_type>(); - return static_cast<int>(val); -} -#endif - -FMT_MAKE_VALUE(double_type, float, double) -FMT_MAKE_VALUE_SAME(double_type, double) -FMT_MAKE_VALUE_SAME(long_double_type, long double) - -// Formatting of wide strings into a narrow buffer and multibyte strings -// into a wide buffer is disallowed (https://github.com/fmtlib/fmt/pull/606). -FMT_MAKE_VALUE(cstring_type, typename C::char_type*, - const typename C::char_type*) -FMT_MAKE_VALUE(cstring_type, const typename C::char_type*, - const typename C::char_type*) - -FMT_MAKE_VALUE(cstring_type, signed char*, const signed char*) -FMT_MAKE_VALUE_SAME(cstring_type, const signed char*) -FMT_MAKE_VALUE(cstring_type, unsigned char*, const unsigned char*) -FMT_MAKE_VALUE_SAME(cstring_type, const unsigned char*) -FMT_MAKE_VALUE_SAME(string_type, basic_string_view<typename C::char_type>) -FMT_MAKE_VALUE(string_type, - typename basic_string_view<typename C::char_type>::type, - basic_string_view<typename C::char_type>) -FMT_MAKE_VALUE(string_type, const std::basic_string<typename C::char_type>&, - basic_string_view<typename C::char_type>) -FMT_MAKE_VALUE(pointer_type, void*, const void*) -FMT_MAKE_VALUE_SAME(pointer_type, const void*) - -#if FMT_USE_NULLPTR -FMT_MAKE_VALUE(pointer_type, std::nullptr_t, const void*) -#endif - -// Formatting of arbitrary pointers is disallowed. If you want to output a -// pointer cast it to "void *" or "const void *". In particular, this forbids -// formatting of "[const] volatile char *" which is printed as bool by -// iostreams. -template <typename C, typename T> -typename std::enable_if<!std::is_same<T, typename C::char_type>::value>::type - make_value(const T *) { - static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); -} - -template <typename C, typename T> -inline typename std::enable_if< - std::is_enum<T>::value && convert_to_int<T, typename C::char_type>::value, - init<C, int, int_type>>::type - make_value(const T &val) { return static_cast<int>(val); } - -template <typename C, typename T, typename Char = typename C::char_type> -inline typename std::enable_if< - std::is_constructible<basic_string_view<Char>, T>::value, - init<C, basic_string_view<Char>, string_type>>::type - make_value(const T &val) { return basic_string_view<Char>(val); } - -template <typename C, typename T, typename Char = typename C::char_type> -inline typename std::enable_if< - !convert_to_int<T, Char>::value && - !std::is_convertible<T, basic_string_view<Char>>::value && - !std::is_constructible<basic_string_view<Char>, T>::value, - // Implicit conversion to std::string is not handled here because it's - // unsafe: https://github.com/fmtlib/fmt/issues/729 - init<C, const T &, custom_type>>::type - make_value(const T &val) { return val; } - -template <typename C, typename T> -init<C, const void*, named_arg_type> - make_value(const named_arg<T, typename C::char_type> &val) { - basic_format_arg<C> arg = make_arg<C>(val.value); - std::memcpy(val.data, &arg, sizeof(arg)); - return static_cast<const void*>(&val); -} - -// Maximum number of arguments with packed types. -enum { max_packed_args = 15 }; - -template <typename Context> -class arg_map; -} // namespace internal - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. -template <typename Context> -class basic_format_arg { - private: - internal::value<Context> value_; - internal::type type_; - - template <typename ContextType, typename T> - friend FMT_CONSTEXPR basic_format_arg<ContextType> - internal::make_arg(const T &value); - - template <typename Visitor, typename Ctx> - friend FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type - visit(Visitor &&vis, const basic_format_arg<Ctx> &arg); - - friend class basic_format_args<Context>; - friend class internal::arg_map<Context>; - - typedef typename Context::char_type char_type; - - public: - class handle { - public: - explicit handle(internal::custom_value<Context> custom): custom_(custom) {} - - void format(Context &ctx) const { custom_.format(custom_.value, ctx); } - - private: - internal::custom_value<Context> custom_; - }; - - FMT_CONSTEXPR basic_format_arg() : type_(internal::none_type) {} - - FMT_EXPLICIT operator bool() const FMT_NOEXCEPT { - return type_ != internal::none_type; - } - - internal::type type() const { return type_; } - - bool is_integral() const { return internal::is_integral(type_); } - bool is_arithmetic() const { return internal::is_arithmetic(type_); } -}; - -struct monostate {}; - -/** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - ``vis(value)`` will be called with the value of type ``double``. - \endrst - */ -template <typename Visitor, typename Context> -FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type - visit(Visitor &&vis, const basic_format_arg<Context> &arg) { - typedef typename Context::char_type char_type; - switch (arg.type_) { - case internal::none_type: - break; - case internal::named_arg_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case internal::int_type: - return vis(arg.value_.int_value); - case internal::uint_type: - return vis(arg.value_.uint_value); - case internal::long_long_type: - return vis(arg.value_.long_long_value); - case internal::ulong_long_type: - return vis(arg.value_.ulong_long_value); - case internal::bool_type: - return vis(arg.value_.int_value != 0); - case internal::char_type: - return vis(static_cast<char_type>(arg.value_.int_value)); - case internal::double_type: - return vis(arg.value_.double_value); - case internal::long_double_type: - return vis(arg.value_.long_double_value); - case internal::cstring_type: - return vis(arg.value_.string.value); - case internal::string_type: - return vis(basic_string_view<char_type>( - arg.value_.string.value, arg.value_.string.size)); - case internal::pointer_type: - return vis(arg.value_.pointer); - case internal::custom_type: - return vis(typename basic_format_arg<Context>::handle(arg.value_.custom)); - } - return vis(monostate()); -} - -// Parsing context consisting of a format string range being parsed and an -// argument counter for automatic indexing. -template <typename Char, typename ErrorHandler = internal::error_handler> -class basic_parse_context : private ErrorHandler { - private: - basic_string_view<Char> format_str_; - int next_arg_id_; - - public: - typedef Char char_type; - typedef typename basic_string_view<Char>::iterator iterator; - - explicit FMT_CONSTEXPR basic_parse_context( - basic_string_view<Char> format_str, ErrorHandler eh = ErrorHandler()) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} - - // Returns an iterator to the beginning of the format string range being - // parsed. - FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT { - return format_str_.begin(); - } - - // Returns an iterator past the end of the format string range being parsed. - FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); } - - // Advances the begin iterator to ``it``. - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(internal::to_unsigned(it - begin())); - } - - // Returns the next argument index. - FMT_CONSTEXPR unsigned next_arg_id(); - - FMT_CONSTEXPR bool check_arg_id(unsigned) { - if (next_arg_id_ > 0) { - on_error("cannot switch from automatic to manual argument indexing"); - return false; - } - next_arg_id_ = -1; - return true; - } - void check_arg_id(basic_string_view<Char>) {} - - FMT_CONSTEXPR void on_error(const char *message) { - ErrorHandler::on_error(message); - } - - FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; } -}; - -typedef basic_parse_context<char> parse_context; -typedef basic_parse_context<wchar_t> wparse_context; - -namespace internal { -// A map from argument names to their values for named arguments. -template <typename Context> -class arg_map { - private: - arg_map(const arg_map &) = delete; - void operator=(const arg_map &) = delete; - - typedef typename Context::char_type char_type; - - struct entry { - basic_string_view<char_type> name; - basic_format_arg<Context> arg; - }; - - entry *map_; - unsigned size_; - - void push_back(value<Context> val) { - const internal::named_arg_base<char_type> &named = val.as_named_arg(); - map_[size_] = entry{named.name, named.template deserialize<Context>()}; - ++size_; - } - - public: - arg_map() : map_(FMT_NULL), size_(0) {} - void init(const basic_format_args<Context> &args); - ~arg_map() { delete [] map_; } - - basic_format_arg<Context> find(basic_string_view<char_type> name) const { - // The list is unsorted, so just return the first matching name. - for (entry *it = map_, *end = map_ + size_; it != end; ++it) { - if (it->name == name) - return it->arg; - } - return basic_format_arg<Context>(); - } -}; - -template <typename OutputIt, typename Context, typename Char> -class context_base { - public: - typedef OutputIt iterator; - - private: - basic_parse_context<Char> parse_context_; - iterator out_; - basic_format_args<Context> args_; - - protected: - typedef Char char_type; - typedef basic_format_arg<Context> format_arg; - - context_base(OutputIt out, basic_string_view<char_type> format_str, - basic_format_args<Context> ctx_args) - : parse_context_(format_str), out_(out), args_(ctx_args) {} - - // Returns the argument with specified index. - format_arg do_get_arg(unsigned arg_id) { - format_arg arg = args_.get(arg_id); - if (!arg) - parse_context_.on_error("argument index out of range"); - return arg; - } - - // Checks if manual indexing is used and returns the argument with - // specified index. - format_arg get_arg(unsigned arg_id) { - return this->parse_context().check_arg_id(arg_id) ? - this->do_get_arg(arg_id) : format_arg(); - } - - public: - basic_parse_context<char_type> &parse_context() { - return parse_context_; - } - - internal::error_handler error_handler() { - return parse_context_.error_handler(); - } - - void on_error(const char *message) { parse_context_.on_error(message); } - - // Returns an iterator to the beginning of the output range. - iterator out() { return out_; } - iterator begin() { return out_; } // deprecated - - // Advances the begin iterator to ``it``. - void advance_to(iterator it) { out_ = it; } - - basic_format_args<Context> args() const { return args_; } -}; - -// Extracts a reference to the container from back_insert_iterator. -template <typename Container> -inline Container &get_container(std::back_insert_iterator<Container> it) { - typedef std::back_insert_iterator<Container> bi_iterator; - struct accessor: bi_iterator { - accessor(bi_iterator iter) : bi_iterator(iter) {} - using bi_iterator::container; - }; - return *accessor(it).container; -} -} // namespace internal - -// Formatting context. -template <typename OutputIt, typename Char> -class basic_format_context : - public internal::context_base< - OutputIt, basic_format_context<OutputIt, Char>, Char> { - public: - /** The character type for the output. */ - typedef Char char_type; - - // using formatter_type = formatter<T, char_type>; - template <typename T> - struct formatter_type { typedef formatter<T, char_type> type; }; - - private: - internal::arg_map<basic_format_context> map_; - - basic_format_context(const basic_format_context &) = delete; - void operator=(const basic_format_context &) = delete; - - typedef internal::context_base<OutputIt, basic_format_context, Char> base; - typedef typename base::format_arg format_arg; - using base::get_arg; - - public: - using typename base::iterator; - - /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. - */ - basic_format_context(OutputIt out, basic_string_view<char_type> format_str, - basic_format_args<basic_format_context> ctx_args) - : base(out, format_str, ctx_args) {} - - format_arg next_arg() { - return this->do_get_arg(this->parse_context().next_arg_id()); - } - format_arg get_arg(unsigned arg_id) { return this->do_get_arg(arg_id); } - - // Checks if manual indexing is used and returns the argument with the - // specified name. - format_arg get_arg(basic_string_view<char_type> name); -}; - -template <typename Char> -struct buffer_context { - typedef basic_format_context< - std::back_insert_iterator<internal::basic_buffer<Char>>, Char> type; -}; -typedef buffer_context<char>::type format_context; -typedef buffer_context<wchar_t>::type wformat_context; - -namespace internal { -template <typename Context, typename T> -struct get_type { - typedef decltype(make_value<Context>( - declval<typename std::decay<T>::type&>())) value_type; - static const type value = value_type::type_tag; -}; - -template <typename Context> -FMT_CONSTEXPR11 unsigned long long get_types() { return 0; } - -template <typename Context, typename Arg, typename... Args> -FMT_CONSTEXPR11 unsigned long long get_types() { - return get_type<Context, Arg>::value | (get_types<Context, Args...>() << 4); -} - -template <typename Context, typename T> -FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value) { - basic_format_arg<Context> arg; - arg.type_ = get_type<Context, T>::value; - arg.value_ = make_value<Context>(value); - return arg; -} - -template <bool IS_PACKED, typename Context, typename T> -inline typename std::enable_if<IS_PACKED, value<Context>>::type - make_arg(const T &value) { - return make_value<Context>(value); -} - -template <bool IS_PACKED, typename Context, typename T> -inline typename std::enable_if<!IS_PACKED, basic_format_arg<Context>>::type - make_arg(const T &value) { - return make_arg<Context>(value); -} -} // namespace internal - -/** - \rst - An array of references to arguments. It can be implicitly converted into - `~fmt::basic_format_args` for passing into type-erased formatting functions - such as `~fmt::vformat`. - \endrst - */ -template <typename Context, typename ...Args> -class format_arg_store { - private: - static const size_t NUM_ARGS = sizeof...(Args); - - // Packed is a macro on MinGW so use IS_PACKED instead. - static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; - - typedef typename std::conditional<IS_PACKED, - internal::value<Context>, basic_format_arg<Context>>::type value_type; - - // If the arguments are not packed, add one more element to mark the end. - static const size_t DATA_SIZE = - NUM_ARGS + (IS_PACKED && NUM_ARGS != 0 ? 0 : 1); - value_type data_[DATA_SIZE]; - - friend class basic_format_args<Context>; - - static FMT_CONSTEXPR11 long long get_types() { - return IS_PACKED ? - static_cast<long long>(internal::get_types<Context, Args...>()) : - -static_cast<long long>(NUM_ARGS); - } - - public: -#if FMT_USE_CONSTEXPR11 - static FMT_CONSTEXPR11 long long TYPES = get_types(); -#else - static const long long TYPES; -#endif - -#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 405) || \ - (FMT_MSC_VER && FMT_MSC_VER <= 1800) - // Workaround array initialization issues in gcc <= 4.5 and MSVC <= 2013. - format_arg_store(const Args &... args) { - value_type init[DATA_SIZE] = - {internal::make_arg<IS_PACKED, Context>(args)...}; - std::memcpy(data_, init, sizeof(init)); - } -#else - format_arg_store(const Args &... args) - : data_{internal::make_arg<IS_PACKED, Context>(args)...} {} -#endif -}; - -#if !FMT_USE_CONSTEXPR11 -template <typename Context, typename ...Args> -const long long format_arg_store<Context, Args...>::TYPES = get_types(); -#endif - -/** - \rst - Constructs an `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. - \endrst - */ -template <typename Context, typename ...Args> -inline format_arg_store<Context, Args...> - make_format_args(const Args & ... args) { - return format_arg_store<Context, Args...>(args...); -} - -template <typename ...Args> -inline format_arg_store<format_context, Args...> - make_format_args(const Args & ... args) { - return format_arg_store<format_context, Args...>(args...); -} - -/** Formatting arguments. */ -template <typename Context> -class basic_format_args { - public: - typedef unsigned size_type; - typedef basic_format_arg<Context> format_arg; - - private: - // To reduce compiled code size per formatting function call, types of first - // max_packed_args arguments are passed in the types_ field. - unsigned long long types_; - union { - // If the number of arguments is less than max_packed_args, the argument - // values are stored in values_, otherwise they are stored in args_. - // This is done to reduce compiled code size as storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const internal::value<Context> *values_; - const format_arg *args_; - }; - - typename internal::type type(unsigned index) const { - unsigned shift = index * 4; - unsigned long long mask = 0xf; - return static_cast<typename internal::type>( - (types_ & (mask << shift)) >> shift); - } - - friend class internal::arg_map<Context>; - - void set_data(const internal::value<Context> *values) { values_ = values; } - void set_data(const format_arg *args) { args_ = args; } - - format_arg do_get(size_type index) const { - format_arg arg; - long long signed_types = static_cast<long long>(types_); - if (signed_types < 0) { - unsigned long long num_args = - static_cast<unsigned long long>(-signed_types); - if (index < num_args) - arg = args_[index]; - return arg; - } - if (index > internal::max_packed_args) - return arg; - arg.type_ = type(index); - if (arg.type_ == internal::none_type) - return arg; - internal::value<Context> &val = arg.value_; - val = values_[index]; - return arg; - } - - public: - basic_format_args() : types_(0) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template <typename... Args> - basic_format_args(const format_arg_store<Context, Args...> &store) - : types_(static_cast<unsigned long long>(store.TYPES)) { - set_data(store.data_); - } - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - basic_format_args(const format_arg *args, size_type count) - : types_(-static_cast<int64_t>(count)) { - set_data(args); - } - - /** Returns the argument at specified index. */ - format_arg get(size_type index) const { - format_arg arg = do_get(index); - if (arg.type_ == internal::named_arg_type) - arg = arg.value_.as_named_arg().template deserialize<Context>(); - return arg; - } - - unsigned max_size() const { - long long signed_types = static_cast<long long>(types_); - return static_cast<unsigned>( - signed_types < 0 ? - -signed_types : static_cast<long long>(internal::max_packed_args)); - } -}; - -/** An alias to ``basic_format_args<context>``. */ -// It is a separate type rather than a typedef to make symbols readable. -struct format_args: basic_format_args<format_context> { - template <typename ...Args> - format_args(Args && ... arg) - : basic_format_args<format_context>(std::forward<Args>(arg)...) {} -}; -struct wformat_args : basic_format_args<wformat_context> { - template <typename ...Args> - wformat_args(Args && ... arg) - : basic_format_args<wformat_context>(std::forward<Args>(arg)...) {} -}; - -namespace internal { -template <typename Char> -struct named_arg_base { - basic_string_view<Char> name; - - // Serialized value<context>. - mutable char data[sizeof(basic_format_arg<format_context>)]; - - named_arg_base(basic_string_view<Char> nm) : name(nm) {} - - template <typename Context> - basic_format_arg<Context> deserialize() const { - basic_format_arg<Context> arg; - std::memcpy(&arg, data, sizeof(basic_format_arg<Context>)); - return arg; - } -}; - -template <typename T, typename Char> -struct named_arg : named_arg_base<Char> { - const T &value; - - named_arg(basic_string_view<Char> name, const T &val) - : named_arg_base<Char>(name), value(val) {} -}; -} - -/** - \rst - Returns a named argument to be used in a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template <typename T> -inline internal::named_arg<T, char> arg(string_view name, const T &arg) { - return internal::named_arg<T, char>(name, arg); -} - -template <typename T> -inline internal::named_arg<T, wchar_t> arg(wstring_view name, const T &arg) { - return internal::named_arg<T, wchar_t>(name, arg); -} - -// This function template is deleted intentionally to disable nested named -// arguments as in ``format("{}", arg("a", arg("b", 42)))``. -template <typename S, typename T, typename Char> -void arg(S, internal::named_arg<T, Char>) = delete; - -// A base class for compile-time strings. It is defined in the fmt namespace to -// make formatting functions visible via ADL, e.g. format(fmt("{}"), 42). -struct compile_string {}; - -namespace internal { -// If S is a format string type, format_string_traints<S>::char_type gives its -// character type. -template <typename S, typename Enable = void> -struct format_string_traits { - private: - // Use constructability as a way to detect if format_string_traits is - // specialized because other methods are broken on MSVC2013. - format_string_traits(); -}; - -template <typename Char> -struct format_string_traits_base { typedef Char char_type; }; - -template <typename Char> -struct format_string_traits<Char *> : format_string_traits_base<Char> {}; - -template <typename Char> -struct format_string_traits<const Char *> : format_string_traits_base<Char> {}; - -template <typename Char, std::size_t N> -struct format_string_traits<Char[N]> : format_string_traits_base<Char> {}; - -template <typename Char, std::size_t N> -struct format_string_traits<const Char[N]> : format_string_traits_base<Char> {}; - -template <typename Char> -struct format_string_traits<std::basic_string<Char>> : - format_string_traits_base<Char> {}; - -template <typename S> -struct format_string_traits< - S, typename std::enable_if<std::is_base_of< - basic_string_view<typename S::char_type>, S>::value>::type> : - format_string_traits_base<typename S::char_type> {}; - -template <typename S> -struct is_format_string : - std::integral_constant< - bool, std::is_constructible<format_string_traits<S>>::value> {}; - -template <typename S> -struct is_compile_string : - std::integral_constant<bool, std::is_base_of<compile_string, S>::value> {}; - -template <typename... Args, typename S> -inline typename std::enable_if<!is_compile_string<S>::value>::type - check_format_string(const S &) {} -template <typename... Args, typename S> -typename std::enable_if<is_compile_string<S>::value>::type - check_format_string(S); - -template <typename Char> -std::basic_string<Char> vformat( - basic_string_view<Char> format_str, - basic_format_args<typename buffer_context<Char>::type> args); -} // namespace internal - -format_context::iterator vformat_to( - internal::buffer &buf, string_view format_str, format_args args); -wformat_context::iterator vformat_to( - internal::wbuffer &buf, wstring_view format_str, wformat_args args); - -template <typename Container> -struct is_contiguous : std::false_type {}; - -template <typename Char> -struct is_contiguous<std::basic_string<Char>> : std::true_type {}; - -template <typename Char> -struct is_contiguous<internal::basic_buffer<Char>> : std::true_type {}; - -/** Formats a string and writes the output to ``out``. */ -template <typename Container> -typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - vformat_to(std::back_insert_iterator<Container> out, - string_view format_str, format_args args) { - internal::container_buffer<Container> buf(internal::get_container(out)); - vformat_to(buf, format_str, args); - return out; -} - -template <typename Container> -typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - vformat_to(std::back_insert_iterator<Container> out, - wstring_view format_str, wformat_args args) { - internal::container_buffer<Container> buf(internal::get_container(out)); - vformat_to(buf, format_str, args); - return out; -} - -template <typename Container, typename... Args> -inline typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - format_to(std::back_insert_iterator<Container> out, - string_view format_str, const Args & ... args) { - format_arg_store<format_context, Args...> as{args...}; - return vformat_to(out, format_str, as); -} - -template <typename Container, typename... Args> -inline typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - format_to(std::back_insert_iterator<Container> out, - wstring_view format_str, const Args & ... args) { - return vformat_to(out, format_str, - make_format_args<wformat_context>(args...)); -} - -template < - typename String, - typename Char = typename internal::format_string_traits<String>::char_type> -inline std::basic_string<Char> vformat( - const String &format_str, - basic_format_args<typename buffer_context<Char>::type> args) { - // Convert format string to string_view to reduce the number of overloads. - return internal::vformat(basic_string_view<Char>(format_str), args); -} - -/** - \rst - Formats arguments and returns the result as a string. - - **Example**:: - - #include <fmt/core.h> - std::string message = fmt::format("The answer is {}", 42); - \endrst -*/ -template <typename String, typename... Args> -inline std::basic_string< - typename internal::format_string_traits<String>::char_type> - format(const String &format_str, const Args & ... args) { - internal::check_format_string<Args...>(format_str); - // This should be just - // return vformat(format_str, make_format_args(args...)); - // but gcc has trouble optimizing the latter, so break it down. - typedef typename internal::format_string_traits<String>::char_type char_t; - typedef typename buffer_context<char_t>::type context_t; - format_arg_store<context_t, Args...> as{args...}; - return internal::vformat( - basic_string_view<char_t>(format_str), basic_format_args<context_t>(as)); -} - -FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); -FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); - -/** - \rst - Prints formatted data to the file *f*. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); - \endrst - */ -template <typename... Args> -inline void print(std::FILE *f, string_view format_str, const Args & ... args) { - format_arg_store<format_context, Args...> as(args...); - vprint(f, format_str, as); -} -/** - Prints formatted data to the file *f* which should be in wide-oriented mode - set via ``fwide(f, 1)`` or ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. - */ -template <typename... Args> -inline void print(std::FILE *f, wstring_view format_str, - const Args & ... args) { - format_arg_store<wformat_context, Args...> as(args...); - vprint(f, format_str, as); -} - -FMT_API void vprint(string_view format_str, format_args args); -FMT_API void vprint(wstring_view format_str, wformat_args args); - -/** - \rst - Prints formatted data to ``stdout``. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template <typename... Args> -inline void print(string_view format_str, const Args & ... args) { - format_arg_store<format_context, Args...> as{args...}; - vprint(format_str, as); -} - -template <typename... Args> -inline void print(wstring_view format_str, const Args & ... args) { - format_arg_store<wformat_context, Args...> as(args...); - vprint(format_str, as); -} -FMT_END_NAMESPACE - -#endif // FMT_CORE_H_ diff --git a/src/nmodl/ext/fmt/format-inl.h b/src/nmodl/ext/fmt/format-inl.h deleted file mode 100644 index 56c4d581df..0000000000 --- a/src/nmodl/ext/fmt/format-inl.h +++ /dev/null @@ -1,866 +0,0 @@ -// Formatting library for C++ -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_FORMAT_INL_H_ -#define FMT_FORMAT_INL_H_ - -#include "format.h" - -#include <string.h> - -#include <cctype> -#include <cerrno> -#include <climits> -#include <cmath> -#include <cstdarg> -#include <cstddef> // for std::ptrdiff_t -#include <cstring> // for std::memmove -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -# include <locale> -#endif - -#if FMT_USE_WINDOWS_H -# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) -# define WIN32_LEAN_AND_MEAN -# endif -# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) -# include <windows.h> -# else -# define NOMINMAX -# include <windows.h> -# undef NOMINMAX -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4702) // unreachable code -// Disable deprecation warning for strerror. The latter is not called but -// MSVC fails to detect it. -# pragma warning(disable: 4996) -#endif - -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -inline fmt::internal::null<> strerror_r(int, char *, ...) { - return fmt::internal::null<>(); -} -inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) { - return fmt::internal::null<>(); -} - -FMT_BEGIN_NAMESPACE - -namespace { - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER -inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; -} -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) -# define FMT_SWPRINTF snwprintf -#else -# define FMT_SWPRINTF swprintf -#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) - -typedef void (*FormatFunc)(internal::buffer &, int, string_view); - -// Portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -int safe_strerror( - int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); - - class dispatcher { - private: - int error_code_; - char *&buffer_; - std::size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const dispatcher &) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - int handle(char *message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - int handle(internal::null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? - ERANGE : result; - } - - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } - - public: - dispatcher(int err_code, char *&buf, std::size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { - return handle(strerror_r(error_code_, buffer_, buffer_size_)); - } - }; - return dispatcher(error_code, buffer, buffer_size).run(); -} - -void format_error_code(internal::buffer &out, int error_code, - string_view message) FMT_NOEXCEPT { - // Report error code making sure that the output fits into - // inline_buffer_size to avoid dynamic memory allocation and potential - // bad_alloc. - out.resize(0); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - typedef internal::int_traits<int>::main_type main_type; - main_type abs_value = static_cast<main_type>(error_code); - if (internal::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += internal::count_digits(abs_value); - writer w(out); - if (message.size() <= inline_buffer_size - error_code_size) { - w.write(message); - w.write(SEP); - } - w.write(ERROR_STR); - w.write(error_code); - assert(out.size() <= inline_buffer_size); -} - -void report_error(FormatFunc func, int error_code, - string_view message) FMT_NOEXCEPT { - memory_buffer full_message; - func(full_message, error_code, message); - // Use Writer::data instead of Writer::c_str to avoid potential memory - // allocation. - std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); -} -} // namespace - -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -class locale { - private: - std::locale locale_; - - public: - explicit locale(std::locale loc = std::locale()) : locale_(loc) {} - std::locale get() { return locale_; } -}; - -FMT_FUNC size_t internal::count_code_points(u8string_view s) { - const char8_t *data = s.data(); - int num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i].value & 0xc0) != 0x80) - ++num_code_points; - } - return num_code_points; -} - -template <typename Char> -FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { - std::locale loc = lp ? lp->locale().get() : std::locale(); - return std::use_facet<std::numpunct<Char>>(loc).thousands_sep(); -} -#else -template <typename Char> -FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { - return FMT_STATIC_THOUSANDS_SEPARATOR; -} -#endif - -FMT_FUNC void system_error::init( - int err_code, string_view format_str, format_args args) { - error_code_ = err_code; - memory_buffer buffer; - format_system_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(to_string(buffer)); -} - -namespace internal { -template <typename T> -int char_traits<char>::format_float( - char *buffer, std::size_t size, const char *format, int precision, T value) { - return precision < 0 ? - FMT_SNPRINTF(buffer, size, format, value) : - FMT_SNPRINTF(buffer, size, format, precision, value); -} - -template <typename T> -int char_traits<wchar_t>::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, - T value) { - return precision < 0 ? - FMT_SWPRINTF(buffer, size, format, value) : - FMT_SWPRINTF(buffer, size, format, precision, value); -} - -template <typename T> -const char basic_data<T>::DIGITS[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; - -#define FMT_POWERS_OF_10(factor) \ - factor * 10, \ - factor * 100, \ - factor * 1000, \ - factor * 10000, \ - factor * 100000, \ - factor * 1000000, \ - factor * 10000000, \ - factor * 100000000, \ - factor * 1000000000 - -template <typename T> -const uint32_t basic_data<T>::POWERS_OF_10_32[] = { - 1, FMT_POWERS_OF_10(1) -}; - -template <typename T> -const uint32_t basic_data<T>::ZERO_OR_POWERS_OF_10_32[] = { - 0, FMT_POWERS_OF_10(1) -}; - -template <typename T> -const uint64_t basic_data<T>::ZERO_OR_POWERS_OF_10_64[] = { - 0, - FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(1000000000ull), - 10000000000000000000ull -}; - -// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. -// These are generated by support/compute-powers.py. -template <typename T> -const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, -}; - -// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding -// to significands above. -template <typename T> -const int16_t basic_data<T>::POW10_EXPONENTS[] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066 -}; - -template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m"; -template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m"; - -// A handmade floating-point number f * pow(2, e). -class fp { - private: - typedef uint64_t significand_type; - - // All sizes are in bits. - static FMT_CONSTEXPR_DECL const int char_size = - std::numeric_limits<unsigned char>::digits; - // Subtract 1 to account for an implicit most significant bit in the - // normalized form. - static FMT_CONSTEXPR_DECL const int double_significand_size = - std::numeric_limits<double>::digits - 1; - static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = - 1ull << double_significand_size; - - public: - significand_type f; - int e; - - static FMT_CONSTEXPR_DECL const int significand_size = - sizeof(significand_type) * char_size; - - fp(): f(0), e(0) {} - fp(uint64_t f, int e): f(f), e(e) {} - - // Constructs fp from an IEEE754 double. It is a template to prevent compile - // errors on platforms where double is not IEEE754. - template <typename Double> - explicit fp(Double d) { - // Assume double is in the format [sign][exponent][significand]. - typedef std::numeric_limits<Double> limits; - const int double_size = static_cast<int>(sizeof(Double) * char_size); - const int exponent_size = - double_size - double_significand_size - 1; // -1 for sign - const uint64_t significand_mask = implicit_bit - 1; - const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; - const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; - auto u = bit_cast<uint64_t>(d); - auto biased_e = (u & exponent_mask) >> double_significand_size; - f = u & significand_mask; - if (biased_e != 0) - f += implicit_bit; - else - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = static_cast<int>(biased_e - exponent_bias - double_significand_size); - } - - // Normalizes the value converted from double and multiplied by (1 << SHIFT). - template <int SHIFT = 0> - void normalize() { - // Handle subnormals. - auto shifted_implicit_bit = implicit_bit << SHIFT; - while ((f & shifted_implicit_bit) == 0) { - f <<= 1; - --e; - } - // Subtract 1 to account for hidden bit. - auto offset = significand_size - double_significand_size - SHIFT - 1; - f <<= offset; - e -= offset; - } - - // Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where - // a boundary is a value half way between the number and its predecessor - // (lower) or successor (upper). The upper boundary is normalized and lower - // has the same exponent but may be not normalized. - void compute_boundaries(fp &lower, fp &upper) const { - lower = f == implicit_bit ? - fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); - upper = fp((f << 1) + 1, e - 1); - upper.normalize<1>(); // 1 is to account for the exponent shift above. - lower.f <<= lower.e - upper.e; - lower.e = upper.e; - } -}; - -// Returns an fp number representing x - y. Result may not be normalized. -inline fp operator-(fp x, fp y) { - FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands"); - return fp(x.f - y.f, x.e); -} - -// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest -// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be normalized. -FMT_API fp operator*(fp x, fp y); - -// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its -// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3. -FMT_API fp get_cached_power(int min_exponent, int &pow10_exponent); - -FMT_FUNC fp operator*(fp x, fp y) { - // Multiply 32-bit parts of significands. - uint64_t mask = (1ULL << 32) - 1; - uint64_t a = x.f >> 32, b = x.f & mask; - uint64_t c = y.f >> 32, d = y.f & mask; - uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; - // Compute mid 64-bit of result and round. - uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); - return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64); -} - -FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) { - const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10) - int index = static_cast<int>(std::ceil( - (min_exponent + fp::significand_size - 1) * one_over_log2_10)); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]); -} - -// Generates output using Grisu2 digit-gen algorithm. -FMT_FUNC void grisu2_gen_digits( - const fp &scaled_value, const fp &scaled_upper, uint64_t delta, - char *buffer, size_t &size, int &dec_exp) { - internal::fp one(1ull << -scaled_upper.e, scaled_upper.e); - // hi (p1 in Grisu) contains the most significant digits of scaled_upper. - // hi = floor(scaled_upper / one). - uint32_t hi = static_cast<uint32_t>(scaled_upper.f >> -one.e); - // lo (p2 in Grisu) contains the least significants digits of scaled_upper. - // lo = scaled_upper mod 1. - uint64_t lo = scaled_upper.f & (one.f - 1); - size = 0; - auto exp = count_digits(hi); // kappa in Grisu. - while (exp > 0) { - uint32_t digit = 0; - // This optimization by miloyip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: digit = hi / 1000000000; hi %= 1000000000; break; - case 9: digit = hi / 100000000; hi %= 100000000; break; - case 8: digit = hi / 10000000; hi %= 10000000; break; - case 7: digit = hi / 1000000; hi %= 1000000; break; - case 6: digit = hi / 100000; hi %= 100000; break; - case 5: digit = hi / 10000; hi %= 10000; break; - case 4: digit = hi / 1000; hi %= 1000; break; - case 3: digit = hi / 100; hi %= 100; break; - case 2: digit = hi / 10; hi %= 10; break; - case 1: digit = hi; hi = 0; break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - if (digit != 0 || size != 0) - buffer[size++] = static_cast<char>('0' + digit); - --exp; - uint64_t remainder = (static_cast<uint64_t>(hi) << -one.e) + lo; - if (remainder <= delta) { - dec_exp += exp; - // TODO: use scaled_value - (void)scaled_value; - return; - } - } - for (;;) { - lo *= 10; - delta *= 10; - char digit = static_cast<char>(lo >> -one.e); - if (digit != 0 || size != 0) - buffer[size++] = static_cast<char>('0' + digit); - lo &= one.f - 1; - --exp; - if (lo < delta) { - dec_exp += exp; - return; - } - } -} - -FMT_FUNC void grisu2_format_positive(double value, char *buffer, size_t &size, - int &dec_exp) { - FMT_ASSERT(value > 0, "value is nonpositive"); - fp fp_value(value); - fp lower, upper; // w^- and w^+ in the Grisu paper. - fp_value.compute_boundaries(lower, upper); - // Find a cached power of 10 close to 1 / upper. - const int min_exp = -60; // alpha in Grisu. - auto dec_pow = get_cached_power( // \tilde{c}_{-k} in Grisu. - min_exp - (upper.e + fp::significand_size), dec_exp); - dec_exp = -dec_exp; - fp_value.normalize(); - fp scaled_value = fp_value * dec_pow; - fp scaled_lower = lower * dec_pow; // \tilde{M}^- in Grisu. - fp scaled_upper = upper * dec_pow; // \tilde{M}^+ in Grisu. - ++scaled_lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}. - --scaled_upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}. - uint64_t delta = scaled_upper.f - scaled_lower.f; - grisu2_gen_digits(scaled_value, scaled_upper, delta, buffer, size, dec_exp); -} - -FMT_FUNC void round(char *buffer, size_t &size, int &exp, - int digits_to_remove) { - size -= to_unsigned(digits_to_remove); - exp += digits_to_remove; - int digit = buffer[size] - '0'; - // TODO: proper rounding and carry - if (digit > 5 || (digit == 5 && (digits_to_remove > 1 || - (buffer[size - 1] - '0') % 2) != 0)) { - ++buffer[size - 1]; - } -} - -// Writes the exponent exp in the form "[+-]d{1,3}" to buffer. -FMT_FUNC char *write_exponent(char *buffer, int exp) { - FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range"); - if (exp < 0) { - *buffer++ = '-'; - exp = -exp; - } else { - *buffer++ = '+'; - } - if (exp >= 100) { - *buffer++ = static_cast<char>('0' + exp / 100); - exp %= 100; - const char *d = data::DIGITS + exp * 2; - *buffer++ = d[0]; - *buffer++ = d[1]; - } else { - const char *d = data::DIGITS + exp * 2; - *buffer++ = d[0]; - *buffer++ = d[1]; - } - return buffer; -} - -FMT_FUNC void format_exp_notation( - char *buffer, size_t &size, int exp, int precision, bool upper) { - // Insert a decimal point after the first digit and add an exponent. - std::memmove(buffer + 2, buffer + 1, size - 1); - buffer[1] = '.'; - exp += static_cast<int>(size) - 1; - int num_digits = precision - static_cast<int>(size) + 1; - if (num_digits > 0) { - std::uninitialized_fill_n(buffer + size + 1, num_digits, '0'); - size += to_unsigned(num_digits); - } else if (num_digits < 0) { - round(buffer, size, exp, -num_digits); - } - char *p = buffer + size + 1; - *p++ = upper ? 'E' : 'e'; - size = to_unsigned(write_exponent(p, exp) - buffer); -} - -// Prettifies the output of the Grisu2 algorithm. -// The number is given as v = buffer * 10^exp. -FMT_FUNC void grisu2_prettify(char *buffer, size_t &size, int exp, - int precision, bool upper) { - // pow(10, full_exp - 1) <= v <= pow(10, full_exp). - int int_size = static_cast<int>(size); - int full_exp = int_size + exp; - const int exp_threshold = 21; - if (int_size <= full_exp && full_exp <= exp_threshold) { - // 1234e7 -> 12340000000[.0+] - std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0'); - char *p = buffer + full_exp; - if (precision > 0) { - *p++ = '.'; - std::uninitialized_fill_n(p, precision, '0'); - p += precision; - } - size = to_unsigned(p - buffer); - } else if (0 < full_exp && full_exp <= exp_threshold) { - // 1234e-2 -> 12.34[0+] - int fractional_size = -exp; - std::memmove(buffer + full_exp + 1, buffer + full_exp, - to_unsigned(fractional_size)); - buffer[full_exp] = '.'; - int num_zeros = precision - fractional_size; - if (num_zeros > 0) { - std::uninitialized_fill_n(buffer + size + 1, num_zeros, '0'); - size += to_unsigned(num_zeros); - } - ++size; - } else if (-6 < full_exp && full_exp <= 0) { - // 1234e-6 -> 0.001234 - int offset = 2 - full_exp; - std::memmove(buffer + offset, buffer, size); - buffer[0] = '0'; - buffer[1] = '.'; - std::uninitialized_fill_n(buffer + 2, -full_exp, '0'); - size = to_unsigned(int_size + offset); - } else { - format_exp_notation(buffer, size, exp, precision, upper); - } -} - -#if FMT_CLANG_VERSION -# define FMT_FALLTHROUGH [[clang::fallthrough]]; -#elif FMT_GCC_VERSION >= 700 -# define FMT_FALLTHROUGH [[gnu::fallthrough]]; -#else -# define FMT_FALLTHROUGH -#endif - -// Formats a nonnegative value using Grisu2 algorithm. Grisu2 doesn't give any -// guarantees on the shortness of the result. -FMT_FUNC void grisu2_format(double value, char *buffer, size_t &size, char type, - int precision, bool write_decimal_point) { - FMT_ASSERT(value >= 0, "value is negative"); - int dec_exp = 0; // K in Grisu. - if (value > 0) { - grisu2_format_positive(value, buffer, size, dec_exp); - } else { - *buffer = '0'; - size = 1; - } - const int default_precision = 6; - if (precision < 0) - precision = default_precision; - bool upper = false; - switch (type) { - case 'G': - upper = true; - FMT_FALLTHROUGH - case '\0': case 'g': { - int digits_to_remove = static_cast<int>(size) - precision; - if (digits_to_remove > 0) { - round(buffer, size, dec_exp, digits_to_remove); - // Remove trailing zeros. - while (size > 0 && buffer[size - 1] == '0') { - --size; - ++dec_exp; - } - } - precision = 0; - break; - } - case 'F': - upper = true; - FMT_FALLTHROUGH - case 'f': { - int digits_to_remove = -dec_exp - precision; - if (digits_to_remove > 0) { - if (digits_to_remove >= static_cast<int>(size)) - digits_to_remove = static_cast<int>(size) - 1; - round(buffer, size, dec_exp, digits_to_remove); - } - break; - } - case 'e': case 'E': - format_exp_notation(buffer, size, dec_exp, precision, type == 'E'); - return; - } - if (write_decimal_point && precision < 1) - precision = 1; - grisu2_prettify(buffer, size, dec_exp, precision, upper); -} -} // namespace internal - -#if FMT_USE_WINDOWS_H - -FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { - static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; - if (s.size() > INT_MAX) - FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG)); - int s_size = static_cast<int>(s.size()); - if (s_size == 0) { - // MultiByteToWideChar does not support zero length, handle separately. - buffer_.resize(1); - buffer_[0] = 0; - return; - } - - int length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); - if (length == 0) - FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); - buffer_.resize(length + 1); - length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); - if (length == 0) - FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); - buffer_[length] = 0; -} - -FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { - if (int error_code = convert(s)) { - FMT_THROW(windows_error(error_code, - "cannot convert string from UTF-16 to UTF-8")); - } -} - -FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) { - if (s.size() > INT_MAX) - return ERROR_INVALID_PARAMETER; - int s_size = static_cast<int>(s.size()); - if (s_size == 0) { - // WideCharToMultiByte does not support zero length, handle separately. - buffer_.resize(1); - buffer_[0] = 0; - return 0; - } - - int length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_.resize(length + 1); - length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_[length] = 0; - return 0; -} - -FMT_FUNC void windows_error::init( - int err_code, string_view format_str, format_args args) { - error_code_ = err_code; - memory_buffer buffer; - internal::format_windows_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(to_string(buffer)); -} - -FMT_FUNC void internal::format_windows_error( - internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { - FMT_TRY { - wmemory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - wchar_t *system_message = &buf[0]; - int result = FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - system_message, static_cast<uint32_t>(buf.size()), FMT_NULL); - if (result != 0) { - utf16_to_utf8 utf8_message; - if (utf8_message.convert(system_message) == ERROR_SUCCESS) { - writer w(out); - w.write(message); - w.write(": "); - w.write(utf8_message); - return; - } - break; - } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } - } FMT_CATCH(...) {} - format_error_code(out, error_code, message); -} - -#endif // FMT_USE_WINDOWS_H - -FMT_FUNC void format_system_error( - internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { - FMT_TRY { - memory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - char *system_message = &buf[0]; - int result = safe_strerror(error_code, system_message, buf.size()); - if (result == 0) { - writer w(out); - w.write(message); - w.write(": "); - w.write(system_message); - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } - } FMT_CATCH(...) {} - format_error_code(out, error_code, message); -} - -template <typename Char> -void basic_fixed_buffer<Char>::grow(std::size_t) { - FMT_THROW(std::runtime_error("buffer overflow")); -} - -FMT_FUNC void internal::error_handler::on_error(const char *message) { - FMT_THROW(format_error(message)); -} - -FMT_FUNC void report_system_error( - int error_code, fmt::string_view message) FMT_NOEXCEPT { - report_error(format_system_error, error_code, message); -} - -#if FMT_USE_WINDOWS_H -FMT_FUNC void report_windows_error( - int error_code, fmt::string_view message) FMT_NOEXCEPT { - report_error(internal::format_windows_error, error_code, message); -} -#endif - -FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) { - memory_buffer buffer; - vformat_to(buffer, format_str, args); - std::fwrite(buffer.data(), 1, buffer.size(), f); -} - -FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) { - wmemory_buffer buffer; - vformat_to(buffer, format_str, args); - std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f); -} - -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); -} - -FMT_FUNC void vprint(wstring_view format_str, wformat_args args) { - vprint(stdout, format_str, args); -} - -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -FMT_FUNC locale locale_provider::locale() { return fmt::locale(); } -#endif - -FMT_END_NAMESPACE - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -#endif // FMT_FORMAT_INL_H_ diff --git a/src/nmodl/ext/fmt/format.h b/src/nmodl/ext/fmt/format.h deleted file mode 100644 index 9f522f39b7..0000000000 --- a/src/nmodl/ext/fmt/format.h +++ /dev/null @@ -1,3720 +0,0 @@ -/* - Formatting library for C++ - - Copyright (c) 2012 - present, Victor Zverovich - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef FMT_FORMAT_H_ -#define FMT_FORMAT_H_ - -#include <algorithm> -#include <cassert> -#include <cmath> -#include <cstring> -#include <limits> -#include <memory> -#include <stdexcept> -#include <stdint.h> - -#ifdef __clang__ -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#ifdef __INTEL_COMPILER -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL -#else -# define FMT_ICC_VERSION 0 -#endif - -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) -#else -# define FMT_CUDA_VERSION 0 -#endif - -#include "core.h" - -#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION -# pragma GCC diagnostic push - -// Disable the warning about declaration shadowing because it affects too -// many valid cases. -# pragma GCC diagnostic ignored "-Wshadow" - -// Disable the warning about implicit conversions that may change the sign of -// an integer; silencing it otherwise would require many explicit casts. -# pragma GCC diagnostic ignored "-Wsign-conversion" -#endif - -# if FMT_CLANG_VERSION -# pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template" -# endif - -#ifdef _SECURE_SCL -# define FMT_SECURE_SCL _SECURE_SCL -#else -# define FMT_SECURE_SCL 0 -#endif - -#if FMT_SECURE_SCL -# include <iterator> -#endif - -#ifdef __has_builtin -# define FMT_HAS_BUILTIN(x) __has_builtin(x) -#else -# define FMT_HAS_BUILTIN(x) 0 -#endif - -#ifdef __GNUC_LIBSTD__ -# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) -#endif - -#ifndef FMT_THROW -# if FMT_EXCEPTIONS -# if FMT_MSC_VER -FMT_BEGIN_NAMESPACE -namespace internal { -template <typename Exception> -inline void do_throw(const Exception &x) { - // Silence unreachable code warnings in MSVC because these are nearly - // impossible to fix in a generic code. - volatile bool b = true; - if (b) - throw x; -} -} -FMT_END_NAMESPACE -# define FMT_THROW(x) fmt::internal::do_throw(x) -# else -# define FMT_THROW(x) throw x -# endif -# else -# define FMT_THROW(x) do { static_cast<void>(sizeof(x)); assert(false); } while(false); -# endif -#endif - -#ifndef FMT_USE_USER_DEFINED_LITERALS -// For Intel's compiler and NVIDIA's compiler both it and the system gcc/msc -// must support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || \ - FMT_GCC_VERSION >= 407 || FMT_MSC_VER >= 1900) && \ - (!(FMT_ICC_VERSION || FMT_CUDA_VERSION) || \ - FMT_ICC_VERSION >= 1500 || FMT_CUDA_VERSION >= 700) -# define FMT_USE_USER_DEFINED_LITERALS 1 -# else -# define FMT_USE_USER_DEFINED_LITERALS 0 -# endif -#endif - -// EDG C++ Front End based compilers (icc, nvcc) do not currently support UDL -// templates. -#if FMT_USE_USER_DEFINED_LITERALS && \ - FMT_ICC_VERSION == 0 && \ - FMT_CUDA_VERSION == 0 && \ - ((FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L) || \ - (defined(FMT_CLANG_VERSION) && FMT_CLANG_VERSION >= 304)) -# define FMT_UDL_TEMPLATE 1 -#else -# define FMT_UDL_TEMPLATE 0 -#endif - -#ifndef FMT_USE_EXTERN_TEMPLATES -# ifndef FMT_HEADER_ONLY -# define FMT_USE_EXTERN_TEMPLATES \ - ((FMT_CLANG_VERSION >= 209 && __cplusplus >= 201103L) || \ - (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) -# else -# define FMT_USE_EXTERN_TEMPLATES 0 -# endif -#endif - -#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_trailing_return) || \ - FMT_MSC_VER >= 1600 -# define FMT_USE_TRAILING_RETURN 1 -#else -# define FMT_USE_TRAILING_RETURN 0 -#endif - -#ifndef FMT_USE_GRISU -# define FMT_USE_GRISU 0 -#endif - -// __builtin_clz is broken in clang with Microsoft CodeGen: -// https://github.com/fmtlib/fmt/issues/519 -#ifndef _MSC_VER -# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) -# endif - -# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) -# endif -#endif - -// A workaround for gcc 4.4 that doesn't support union members with ctors. -#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 404) || \ - (FMT_MSC_VER && FMT_MSC_VER <= 1800) -# define FMT_UNION struct -#else -# define FMT_UNION union -#endif - -// Some compilers masquerade as both MSVC and GCC-likes or otherwise support -// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the -// MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) -# include <intrin.h> // _BitScanReverse, _BitScanReverse64 - -FMT_BEGIN_NAMESPACE -namespace internal { -// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# ifndef __clang__ -# pragma intrinsic(_BitScanReverse) -# endif -inline uint32_t clz(uint32_t x) { - unsigned long r = 0; - _BitScanReverse(&r, x); - - assert(x != 0); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. -# pragma warning(suppress: 6102) - return 31 - r; -} -# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) - -# if defined(_WIN64) && !defined(__clang__) -# pragma intrinsic(_BitScanReverse64) -# endif - -inline uint32_t clzll(uint64_t x) { - unsigned long r = 0; -# ifdef _WIN64 - _BitScanReverse64(&r, x); -# else - // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) - return 63 - (r + 32); - - // Scan the low 32 bits. - _BitScanReverse(&r, static_cast<uint32_t>(x)); -# endif - - assert(x != 0); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. -# pragma warning(suppress: 6102) - return 63 - r; -} -# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) -} -FMT_END_NAMESPACE -#endif - -FMT_BEGIN_NAMESPACE -namespace internal { - -// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't produce -// undefined behavior (e.g. due to type aliasing). -// Example: uint64_t d = bit_cast<uint64_t>(2.718); -template <typename Dest, typename Source> -inline Dest bit_cast(const Source& source) { - static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); - Dest dest; - std::memcpy(&dest, &source, sizeof(dest)); - return dest; -} - -// An implementation of begin and end for pre-C++11 compilers such as gcc 4. -template <typename C> -FMT_CONSTEXPR auto begin(const C &c) -> decltype(c.begin()) { - return c.begin(); -} -template <typename T, std::size_t N> -FMT_CONSTEXPR T *begin(T (&array)[N]) FMT_NOEXCEPT { return array; } -template <typename C> -FMT_CONSTEXPR auto end(const C &c) -> decltype(c.end()) { return c.end(); } -template <typename T, std::size_t N> -FMT_CONSTEXPR T *end(T (&array)[N]) FMT_NOEXCEPT { return array + N; } - -// For std::result_of in gcc 4.4. -template <typename Result> -struct function { - template <typename T> - struct result { typedef Result type; }; -}; - -struct dummy_int { - int data[2]; - operator int() const { return 0; } -}; -typedef std::numeric_limits<internal::dummy_int> fputil; - -// Dummy implementations of system functions such as signbit and ecvt called -// if the latter are not available. -inline dummy_int signbit(...) { return dummy_int(); } -inline dummy_int _ecvt_s(...) { return dummy_int(); } -inline dummy_int isinf(...) { return dummy_int(); } -inline dummy_int _finite(...) { return dummy_int(); } -inline dummy_int isnan(...) { return dummy_int(); } -inline dummy_int _isnan(...) { return dummy_int(); } - -inline bool use_grisu() { - return FMT_USE_GRISU && std::numeric_limits<double>::is_iec559; -} - -// Formats value using Grisu2 algorithm: -// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf -FMT_API void grisu2_format(double value, char *buffer, size_t &size, char type, - int precision, bool write_decimal_point); - -template <typename Allocator> -typename Allocator::value_type *allocate(Allocator& alloc, std::size_t n) { -#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 - return std::allocator_traits<Allocator>::allocate(alloc, n); -#else - return alloc.allocate(n); -#endif -} - -// A helper function to suppress bogus "conditional expression is constant" -// warnings. -template <typename T> -inline T const_check(T value) { return value; } -} // namespace internal -FMT_END_NAMESPACE - -namespace std { -// Standard permits specialization of std::numeric_limits. This specialization -// is used to resolve ambiguity between isinf and std::isinf in glibc: -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 -// and the same for isnan and signbit. -template <> -class numeric_limits<fmt::internal::dummy_int> : - public std::numeric_limits<int> { - public: - // Portable version of isinf. - template <typename T> - static bool isinfinity(T x) { - using namespace fmt::internal; - // The resolution "priority" is: - // isinf macro > std::isinf > ::isinf > fmt::internal::isinf - if (const_check(sizeof(isinf(x)) != sizeof(dummy_int))) - return isinf(x) != 0; - return !_finite(static_cast<double>(x)); - } - - // Portable version of isnan. - template <typename T> - static bool isnotanumber(T x) { - using namespace fmt::internal; - if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) - return isnan(x) != 0; - return _isnan(static_cast<double>(x)) != 0; - } - - // Portable version of signbit. - static bool isnegative(double x) { - using namespace fmt::internal; - if (const_check(sizeof(signbit(x)) != sizeof(fmt::internal::dummy_int))) - return signbit(x) != 0; - if (x < 0) return true; - if (!isnotanumber(x)) return false; - int dec = 0, sign = 0; - char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. - _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); - return sign != 0; - } -}; -} // namespace std - -FMT_BEGIN_NAMESPACE -template <typename Range> -class basic_writer; - -template <typename OutputIt, typename T = typename OutputIt::value_type> -class output_range { - private: - OutputIt it_; - - // Unused yet. - typedef void sentinel; - sentinel end() const; - - public: - typedef OutputIt iterator; - typedef T value_type; - - explicit output_range(OutputIt it): it_(it) {} - OutputIt begin() const { return it_; } -}; - -// A range where begin() returns back_insert_iterator. -template <typename Container> -class back_insert_range: - public output_range<std::back_insert_iterator<Container>> { - typedef output_range<std::back_insert_iterator<Container>> base; - public: - typedef typename Container::value_type value_type; - - back_insert_range(Container &c): base(std::back_inserter(c)) {} - back_insert_range(typename base::iterator it): base(it) {} -}; - -typedef basic_writer<back_insert_range<internal::buffer>> writer; -typedef basic_writer<back_insert_range<internal::wbuffer>> wwriter; - -/** A formatting error such as invalid format string. */ -class format_error : public std::runtime_error { - public: - explicit format_error(const char *message) - : std::runtime_error(message) {} - - explicit format_error(const std::string &message) - : std::runtime_error(message) {} -}; - -namespace internal { - -#if FMT_SECURE_SCL -template <typename T> -struct checked { typedef stdext::checked_array_iterator<T*> type; }; - -// Make a checked iterator to avoid warnings on MSVC. -template <typename T> -inline stdext::checked_array_iterator<T*> make_checked(T *p, std::size_t size) { - return {p, size}; -} -#else -template <typename T> -struct checked { typedef T *type; }; -template <typename T> -inline T *make_checked(T *p, std::size_t) { return p; } -#endif - -template <typename T> -template <typename U> -void basic_buffer<T>::append(const U *begin, const U *end) { - std::size_t new_size = size_ + internal::to_unsigned(end - begin); - reserve(new_size); - std::uninitialized_copy(begin, end, - internal::make_checked(ptr_, capacity_) + size_); - size_ = new_size; -} -} // namespace internal - -// A UTF-8 code unit type. -struct char8_t { - char value; - FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { - return value != 0; - } -}; - -// A UTF-8 string view. -class u8string_view : public basic_string_view<char8_t> { - private: - typedef basic_string_view<char8_t> base; - - public: - using basic_string_view::basic_string_view; - using basic_string_view::char_type; - - u8string_view(const char *s) - : base(reinterpret_cast<const char8_t*>(s)) {} - - u8string_view(const char *s, size_t count) FMT_NOEXCEPT - : base(reinterpret_cast<const char8_t*>(s), count) {} -}; - -#if FMT_USE_USER_DEFINED_LITERALS -inline namespace literals { -inline u8string_view operator"" _u(const char *s, std::size_t n) { - return u8string_view(s, n); -} -} -#endif - -// A wrapper around std::locale used to reduce compile times since <locale> -// is very heavy. -class locale; - -class locale_provider { - public: - virtual ~locale_provider() {} - virtual fmt::locale locale(); -}; - -// The number of characters to store in the basic_memory_buffer object itself -// to avoid dynamic memory allocation. -enum { inline_buffer_size = 500 }; - -/** - \rst - A dynamically growing memory buffer for trivially copyable/constructible types - with the first ``SIZE`` elements stored in the object itself. - - You can use one of the following typedefs for common character types: - - +----------------+------------------------------+ - | Type | Definition | - +================+==============================+ - | memory_buffer | basic_memory_buffer<char> | - +----------------+------------------------------+ - | wmemory_buffer | basic_memory_buffer<wchar_t> | - +----------------+------------------------------+ - - **Example**:: - - fmt::memory_buffer out; - format_to(out, "The answer is {}.", 42); - - This will write the following output to the ``out`` object: - - .. code-block:: none - - The answer is 42. - - The output can be converted to an ``std::string`` with ``to_string(out)``. - \endrst - */ -template <typename T, std::size_t SIZE = inline_buffer_size, - typename Allocator = std::allocator<T> > -class basic_memory_buffer: private Allocator, public internal::basic_buffer<T> { - private: - T store_[SIZE]; - - // Deallocate memory allocated by the buffer. - void deallocate() { - T* data = this->data(); - if (data != store_) Allocator::deallocate(data, this->capacity()); - } - - protected: - void grow(std::size_t size) FMT_OVERRIDE; - - public: - explicit basic_memory_buffer(const Allocator &alloc = Allocator()) - : Allocator(alloc) { - this->set(store_, SIZE); - } - ~basic_memory_buffer() { deallocate(); } - - private: - // Move data from other to this buffer. - void move(basic_memory_buffer &other) { - Allocator &this_alloc = *this, &other_alloc = other; - this_alloc = std::move(other_alloc); - T* data = other.data(); - std::size_t size = other.size(), capacity = other.capacity(); - if (data == other.store_) { - this->set(store_, capacity); - std::uninitialized_copy(other.store_, other.store_ + size, - internal::make_checked(store_, capacity)); - } else { - this->set(data, capacity); - // Set pointer to the inline array so that delete is not called - // when deallocating. - other.set(other.store_, 0); - } - this->resize(size); - } - - public: - /** - \rst - Constructs a :class:`fmt::basic_memory_buffer` object moving the content - of the other object to it. - \endrst - */ - basic_memory_buffer(basic_memory_buffer &&other) { - move(other); - } - - /** - \rst - Moves the content of the other ``basic_memory_buffer`` object to this one. - \endrst - */ - basic_memory_buffer &operator=(basic_memory_buffer &&other) { - assert(this != &other); - deallocate(); - move(other); - return *this; - } - - // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return *this; } -}; - -template <typename T, std::size_t SIZE, typename Allocator> -void basic_memory_buffer<T, SIZE, Allocator>::grow(std::size_t size) { - std::size_t old_capacity = this->capacity(); - std::size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - T *old_data = this->data(); - T *new_data = internal::allocate<Allocator>(*this, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - internal::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) - Allocator::deallocate(old_data, old_capacity); -} - -typedef basic_memory_buffer<char> memory_buffer; -typedef basic_memory_buffer<wchar_t> wmemory_buffer; - -/** - \rst - A fixed-size memory buffer. For a dynamically growing buffer use - :class:`fmt::basic_memory_buffer`. - - Trying to increase the buffer size past the initial capacity will throw - ``std::runtime_error``. - \endrst - */ -template <typename Char> -class basic_fixed_buffer : public internal::basic_buffer<Char> { - public: - /** - \rst - Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the - given size. - \endrst - */ - basic_fixed_buffer(Char *array, std::size_t size) { - this->set(array, size); - } - - /** - \rst - Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the - size known at compile time. - \endrst - */ - template <std::size_t SIZE> - explicit basic_fixed_buffer(Char (&array)[SIZE]) { - this->set(array, SIZE); - } - - protected: - FMT_API void grow(std::size_t size) FMT_OVERRIDE; -}; - -namespace internal { - -template <typename Char> -struct char_traits; - -template <> -struct char_traits<char> { - // Formats a floating-point number. - template <typename T> - FMT_API static int format_float(char *buffer, std::size_t size, - const char *format, int precision, T value); -}; - -template <> -struct char_traits<wchar_t> { - template <typename T> - FMT_API static int format_float(wchar_t *buffer, std::size_t size, - const wchar_t *format, int precision, T value); -}; - -#if FMT_USE_EXTERN_TEMPLATES -extern template int char_traits<char>::format_float<double>( - char *buffer, std::size_t size, const char* format, int precision, - double value); -extern template int char_traits<char>::format_float<long double>( - char *buffer, std::size_t size, const char* format, int precision, - long double value); - -extern template int char_traits<wchar_t>::format_float<double>( - wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, - double value); -extern template int char_traits<wchar_t>::format_float<long double>( - wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, - long double value); -#endif - -template <typename Container> -inline typename std::enable_if< - is_contiguous<Container>::value, - typename checked<typename Container::value_type>::type>::type - reserve(std::back_insert_iterator<Container> &it, std::size_t n) { - Container &c = internal::get_container(it); - std::size_t size = c.size(); - c.resize(size + n); - return make_checked(&c[size], n); -} - -template <typename Iterator> -inline Iterator &reserve(Iterator &it, std::size_t) { return it; } - -template <typename Char> -class null_terminating_iterator; - -template <typename Char> -FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator<Char> it); - -// An iterator that produces a null terminator on *end. This simplifies parsing -// and allows comparing the performance of processing a null-terminated string -// vs string_view. -template <typename Char> -class null_terminating_iterator { - public: - typedef std::ptrdiff_t difference_type; - typedef Char value_type; - typedef const Char* pointer; - typedef const Char& reference; - typedef std::random_access_iterator_tag iterator_category; - - null_terminating_iterator() : ptr_(0), end_(0) {} - - FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) - : ptr_(ptr), end_(end) {} - - template <typename Range> - FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) - : ptr_(r.begin()), end_(r.end()) {} - - FMT_CONSTEXPR null_terminating_iterator &operator=(const Char *ptr) { - assert(ptr <= end_); - ptr_ = ptr; - return *this; - } - - FMT_CONSTEXPR Char operator*() const { - return ptr_ != end_ ? *ptr_ : 0; - } - - FMT_CONSTEXPR null_terminating_iterator operator++() { - ++ptr_; - return *this; - } - - FMT_CONSTEXPR null_terminating_iterator operator++(int) { - null_terminating_iterator result(*this); - ++ptr_; - return result; - } - - FMT_CONSTEXPR null_terminating_iterator operator--() { - --ptr_; - return *this; - } - - FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) { - return null_terminating_iterator(ptr_ + n, end_); - } - - FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) { - return null_terminating_iterator(ptr_ - n, end_); - } - - FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) { - ptr_ += n; - return *this; - } - - FMT_CONSTEXPR difference_type operator-( - null_terminating_iterator other) const { - return ptr_ - other.ptr_; - } - - FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const { - return ptr_ != other.ptr_; - } - - bool operator>=(null_terminating_iterator other) const { - return ptr_ >= other.ptr_; - } - - // This should be a friend specialization pointer_from<Char> but the latter - // doesn't compile by gcc 5.1 due to a compiler bug. - template <typename CharT> - friend FMT_CONSTEXPR_DECL const CharT *pointer_from( - null_terminating_iterator<CharT> it); - - private: - const Char *ptr_; - const Char *end_; -}; - -template <typename T> -FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; } - -template <typename Char> -FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator<Char> it) { - return it.ptr_; -} - -// An output iterator that counts the number of objects written to it and -// discards them. -template <typename T> -class counting_iterator { - private: - std::size_t count_; - mutable T blackhole_; - - public: - typedef std::output_iterator_tag iterator_category; - typedef T value_type; - typedef std::ptrdiff_t difference_type; - typedef T* pointer; - typedef T& reference; - typedef counting_iterator _Unchecked_type; // Mark iterator as checked. - - counting_iterator(): count_(0) {} - - std::size_t count() const { return count_; } - - counting_iterator& operator++() { - ++count_; - return *this; - } - - counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - T &operator*() const { return blackhole_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template <typename OutputIt> -class truncating_iterator { - private: - typedef std::iterator_traits<OutputIt> traits; - - OutputIt out_; - std::size_t limit_; - std::size_t count_; - mutable typename traits::value_type blackhole_; - - public: - typedef std::output_iterator_tag iterator_category; - typedef typename traits::value_type value_type; - typedef typename traits::difference_type difference_type; - typedef typename traits::pointer pointer; - typedef typename traits::reference reference; - typedef truncating_iterator _Unchecked_type; // Mark iterator as checked. - - truncating_iterator(OutputIt out, std::size_t limit) - : out_(out), limit_(limit), count_(0) {} - - OutputIt base() const { return out_; } - std::size_t count() const { return count_; } - - truncating_iterator& operator++() { - if (count_++ < limit_) - ++out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - reference operator*() const { return count_ < limit_ ? *out_ : blackhole_; } -}; - -// Returns true if value is negative, false otherwise. -// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. -template <typename T> -FMT_CONSTEXPR typename std::enable_if< - std::numeric_limits<T>::is_signed, bool>::type is_negative(T value) { - return value < 0; -} -template <typename T> -FMT_CONSTEXPR typename std::enable_if< - !std::numeric_limits<T>::is_signed, bool>::type is_negative(T) { - return false; -} - -template <typename T> -struct int_traits { - // Smallest of uint32_t and uint64_t that is large enough to represent - // all values of T. - typedef typename std::conditional< - std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>::type main_type; -}; - -// Static data is placed in this class template to allow header-only -// configuration. -template <typename T = void> -struct FMT_API basic_data { - static const uint32_t POWERS_OF_10_32[]; - static const uint32_t ZERO_OR_POWERS_OF_10_32[]; - static const uint64_t ZERO_OR_POWERS_OF_10_64[]; - static const uint64_t POW10_SIGNIFICANDS[]; - static const int16_t POW10_EXPONENTS[]; - static const char DIGITS[]; - static const char RESET_COLOR[]; - static const wchar_t WRESET_COLOR[]; -}; - -#if FMT_USE_EXTERN_TEMPLATES -extern template struct basic_data<void>; -#endif - -typedef basic_data<> data; - -#ifdef FMT_BUILTIN_CLZLL -// Returns the number of decimal digits in n. Leading zeros are not counted -// except for n == 0 in which case count_digits returns 1. -inline unsigned count_digits(uint64_t n) { - // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 - // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. - int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_64[t]) + 1; -} -#else -// Fallback version of count_digits used when __builtin_clz is not available. -inline unsigned count_digits(uint64_t n) { - unsigned count = 1; - for (;;) { - // Integer division is slow so do it for a group of four digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; - n /= 10000u; - count += 4; - } -} -#endif - -// Counts the number of code points in a UTF-8 string. -FMT_API size_t count_code_points(u8string_view s); - -#if FMT_HAS_CPP_ATTRIBUTE(always_inline) -# define FMT_ALWAYS_INLINE __attribute__((always_inline)) -#else -# define FMT_ALWAYS_INLINE -#endif - -template <typename Handler> -inline char *lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE; - -// Computes g = floor(log10(n)) and calls h.on<g>(n); -template <typename Handler> -inline char *lg(uint32_t n, Handler h) { - return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n) - : n < 1000000 - ? n < 10000 ? n < 1000 ? h.template on<2>(n) - : h.template on<3>(n) - : n < 100000 ? h.template on<4>(n) - : h.template on<5>(n) - : n < 100000000 ? n < 10000000 ? h.template on<6>(n) - : h.template on<7>(n) - : n < 1000000000 ? h.template on<8>(n) - : h.template on<9>(n); -} - -// An lg handler that formats a decimal number. -// Usage: lg(n, decimal_formatter(buffer)); -class decimal_formatter { - private: - char *buffer_; - - void write_pair(unsigned N, uint32_t index) { - std::memcpy(buffer_ + N, data::DIGITS + index * 2, 2); - } - - public: - explicit decimal_formatter(char *buf) : buffer_(buf) {} - - template <unsigned N> char *on(uint32_t u) { - if (N == 0) { - *buffer_ = static_cast<char>(u) + '0'; - } else if (N == 1) { - write_pair(0, u); - } else { - // The idea of using 4.32 fixed-point numbers is based on - // https://github.com/jeaiii/itoa - unsigned n = N - 1; - unsigned a = n / 5 * n * 53 / 16; - uint64_t t = ((1ULL << (32 + a)) / - data::ZERO_OR_POWERS_OF_10_32[n] + 1 - n / 9); - t = ((t * u) >> a) + n / 5 * 4; - write_pair(0, t >> 32); - for (unsigned i = 2; i < N; i += 2) { - t = 100ULL * static_cast<uint32_t>(t); - write_pair(i, t >> 32); - } - if (N % 2 == 0) { - buffer_[N] = static_cast<char>( - (10ULL * static_cast<uint32_t>(t)) >> 32) + '0'; - } - } - return buffer_ += N + 1; - } -}; - -// An lg handler that formats a decimal number with a terminating null. -class decimal_formatter_null : public decimal_formatter { - public: - explicit decimal_formatter_null(char *buf) : decimal_formatter(buf) {} - - template <unsigned N> char *on(uint32_t u) { - char *buf = decimal_formatter::on<N>(u); - *buf = '\0'; - return buf; - } -}; - -#ifdef FMT_BUILTIN_CLZ -// Optional version of count_digits for better performance on 32-bit platforms. -inline unsigned count_digits(uint32_t n) { - int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_32[t]) + 1; -} -#endif - -// A functor that doesn't add a thousands separator. -struct no_thousands_sep { - typedef char char_type; - - template <typename Char> - void operator()(Char *) {} -}; - -// A functor that adds a thousands separator. -template <typename Char> -class add_thousands_sep { - private: - basic_string_view<Char> sep_; - - // Index of a decimal digit with the least significant digit having index 0. - unsigned digit_index_; - - public: - typedef Char char_type; - - explicit add_thousands_sep(basic_string_view<Char> sep) - : sep_(sep), digit_index_(0) {} - - void operator()(Char *&buffer) { - if (++digit_index_ % 3 != 0) - return; - buffer -= sep_.size(); - std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), - internal::make_checked(buffer, sep_.size())); - } -}; - -template <typename Char> -FMT_API Char thousands_sep(locale_provider *lp); - -// Formats a decimal unsigned integer value writing into buffer. -// thousands_sep is a functor that is called after writing each char to -// add a thousands separator if necessary. -template <typename UInt, typename Char, typename ThousandsSep> -inline Char *format_decimal(Char *buffer, UInt value, unsigned num_digits, - ThousandsSep thousands_sep) { - buffer += num_digits; - Char *end = buffer; - while (value >= 100) { - // Integer division is slow so do it for a group of two digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - unsigned index = static_cast<unsigned>((value % 100) * 2); - value /= 100; - *--buffer = data::DIGITS[index + 1]; - thousands_sep(buffer); - *--buffer = data::DIGITS[index]; - thousands_sep(buffer); - } - if (value < 10) { - *--buffer = static_cast<char>('0' + value); - return end; - } - unsigned index = static_cast<unsigned>(value * 2); - *--buffer = data::DIGITS[index + 1]; - thousands_sep(buffer); - *--buffer = data::DIGITS[index]; - return end; -} - -template <typename UInt, typename Iterator, typename ThousandsSep> -inline Iterator format_decimal( - Iterator out, UInt value, unsigned num_digits, ThousandsSep sep) { - typedef typename ThousandsSep::char_type char_type; - // Buffer should be large enough to hold all digits (digits10 + 1) and null. - char_type buffer[std::numeric_limits<UInt>::digits10 + 2]; - format_decimal(buffer, value, num_digits, sep); - return std::copy_n(buffer, num_digits, out); -} - -template <typename It, typename UInt> -inline It format_decimal(It out, UInt value, unsigned num_digits) { - return format_decimal(out, value, num_digits, no_thousands_sep()); -} - -template <unsigned BASE_BITS, typename Char, typename UInt> -inline Char *format_uint(Char *buffer, UInt value, unsigned num_digits, - bool upper = false) { - buffer += num_digits; - Char *end = buffer; - do { - const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; - unsigned digit = (value & ((1 << BASE_BITS) - 1)); - *--buffer = BASE_BITS < 4 ? static_cast<char>('0' + digit) : digits[digit]; - } while ((value >>= BASE_BITS) != 0); - return end; -} - -template <unsigned BASE_BITS, typename It, typename UInt> -inline It format_uint(It out, UInt value, unsigned num_digits, - bool upper = false) { - // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) - // and null. - char buffer[std::numeric_limits<UInt>::digits / BASE_BITS + 2]; - format_uint<BASE_BITS>(buffer, value, num_digits, upper); - return std::copy_n(buffer, num_digits, out); -} - -#ifndef _WIN32 -# define FMT_USE_WINDOWS_H 0 -#elif !defined(FMT_USE_WINDOWS_H) -# define FMT_USE_WINDOWS_H 1 -#endif - -// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. -// All the functionality that relies on it will be disabled too. -#if FMT_USE_WINDOWS_H -// A converter from UTF-8 to UTF-16. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf8_to_utf16 { - private: - wmemory_buffer buffer_; - - public: - FMT_API explicit utf8_to_utf16(string_view s); - operator wstring_view() const { return wstring_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const wchar_t *c_str() const { return &buffer_[0]; } - std::wstring str() const { return std::wstring(&buffer_[0], size()); } -}; - -// A converter from UTF-16 to UTF-8. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf16_to_utf8 { - private: - memory_buffer buffer_; - - public: - utf16_to_utf8() {} - FMT_API explicit utf16_to_utf8(wstring_view s); - operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char *c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } - - // Performs conversion returning a system error code instead of - // throwing exception on conversion error. This method may still throw - // in case of memory allocation error. - FMT_API int convert(wstring_view s); -}; - -FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, - fmt::string_view message) FMT_NOEXCEPT; -#endif - -template <typename T = void> -struct null {}; -} // namespace internal - -enum alignment { - ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC -}; - -// Flags. -enum {SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8}; - -enum format_spec_tag {fill_tag, align_tag, width_tag, type_tag}; - -// Format specifier. -template <typename T, format_spec_tag> -class format_spec { - private: - T value_; - - public: - typedef T value_type; - - explicit format_spec(T value) : value_(value) {} - - T value() const { return value_; } -}; - -// template <typename Char> -// typedef format_spec<Char, fill_tag> fill_spec; -template <typename Char> -class fill_spec : public format_spec<Char, fill_tag> { - public: - explicit fill_spec(Char value) : format_spec<Char, fill_tag>(value) {} -}; - -typedef format_spec<unsigned, width_tag> width_spec; -typedef format_spec<char, type_tag> type_spec; - -// An empty format specifier. -struct empty_spec {}; - -// An alignment specifier. -struct align_spec : empty_spec { - unsigned width_; - // Fill is always wchar_t and cast to char if necessary to avoid having - // two specialization of AlignSpec and its subclasses. - wchar_t fill_; - alignment align_; - - FMT_CONSTEXPR align_spec( - unsigned width, wchar_t fill, alignment align = ALIGN_DEFAULT) - : width_(width), fill_(fill), align_(align) {} - - FMT_CONSTEXPR unsigned width() const { return width_; } - FMT_CONSTEXPR wchar_t fill() const { return fill_; } - FMT_CONSTEXPR alignment align() const { return align_; } - - int precision() const { return -1; } -}; - -// Format specifiers. -template <typename Char> -class basic_format_specs : public align_spec { - public: - unsigned flags_; - int precision_; - Char type_; - - FMT_CONSTEXPR basic_format_specs( - unsigned width = 0, char type = 0, wchar_t fill = ' ') - : align_spec(width, fill), flags_(0), precision_(-1), type_(type) {} - - FMT_CONSTEXPR bool flag(unsigned f) const { return (flags_ & f) != 0; } - FMT_CONSTEXPR int precision() const { return precision_; } - FMT_CONSTEXPR Char type() const { return type_; } -}; - -typedef basic_format_specs<char> format_specs; - -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR unsigned basic_parse_context<Char, ErrorHandler>::next_arg_id() { - if (next_arg_id_ >= 0) - return internal::to_unsigned(next_arg_id_++); - on_error("cannot switch from manual to automatic argument indexing"); - return 0; -} - -namespace internal { - -template <typename S> -struct format_string_traits< - S, typename std::enable_if<std::is_base_of<compile_string, S>::value>::type>: - format_string_traits_base<char> {}; - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_int_type_spec(Char spec, Handler &&handler) { - switch (spec) { - case 0: case 'd': - handler.on_dec(); - break; - case 'x': case 'X': - handler.on_hex(); - break; - case 'b': case 'B': - handler.on_bin(); - break; - case 'o': - handler.on_oct(); - break; - case 'n': - handler.on_num(); - break; - default: - handler.on_error(); - } -} - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_float_type_spec(Char spec, Handler &&handler) { - switch (spec) { - case 0: case 'g': case 'G': - handler.on_general(); - break; - case 'e': case 'E': - handler.on_exp(); - break; - case 'f': case 'F': - handler.on_fixed(); - break; - case 'a': case 'A': - handler.on_hex(); - break; - default: - handler.on_error(); - break; - } -} - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_char_specs( - const basic_format_specs<Char> *specs, Handler &&handler) { - if (!specs) return handler.on_char(); - if (specs->type() && specs->type() != 'c') return handler.on_int(); - if (specs->align() == ALIGN_NUMERIC || specs->flag(~0u) != 0) - handler.on_error("invalid format specifier for char"); - handler.on_char(); -} - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler &&handler) { - if (spec == 0 || spec == 's') - handler.on_string(); - else if (spec == 'p') - handler.on_pointer(); - else - handler.on_error("invalid type specifier"); -} - -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler &&eh) { - if (spec != 0 && spec != 's') - eh.on_error("invalid type specifier"); -} - -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler &&eh) { - if (spec != 0 && spec != 'p') - eh.on_error("invalid type specifier"); -} - -template <typename ErrorHandler> -class int_type_checker : private ErrorHandler { - public: - FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_dec() {} - FMT_CONSTEXPR void on_hex() {} - FMT_CONSTEXPR void on_bin() {} - FMT_CONSTEXPR void on_oct() {} - FMT_CONSTEXPR void on_num() {} - - FMT_CONSTEXPR void on_error() { - ErrorHandler::on_error("invalid type specifier"); - } -}; - -template <typename ErrorHandler> -class float_type_checker : private ErrorHandler { - public: - FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh) - : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_general() {} - FMT_CONSTEXPR void on_exp() {} - FMT_CONSTEXPR void on_fixed() {} - FMT_CONSTEXPR void on_hex() {} - - FMT_CONSTEXPR void on_error() { - ErrorHandler::on_error("invalid type specifier"); - } -}; - -template <typename ErrorHandler, typename CharType> -class char_specs_checker : public ErrorHandler { - private: - CharType type_; - - public: - FMT_CONSTEXPR char_specs_checker(CharType type, ErrorHandler eh) - : ErrorHandler(eh), type_(type) {} - - FMT_CONSTEXPR void on_int() { - handle_int_type_spec(type_, int_type_checker<ErrorHandler>(*this)); - } - FMT_CONSTEXPR void on_char() {} -}; - -template <typename ErrorHandler> -class cstring_type_checker : public ErrorHandler { - public: - FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) - : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_string() {} - FMT_CONSTEXPR void on_pointer() {} -}; - -template <typename Context> -void arg_map<Context>::init(const basic_format_args<Context> &args) { - if (map_) - return; - map_ = new entry[args.max_size()]; - bool use_values = args.type(max_packed_args - 1) == internal::none_type; - if (use_values) { - for (unsigned i = 0;/*nothing*/; ++i) { - internal::type arg_type = args.type(i); - switch (arg_type) { - case internal::none_type: - return; - case internal::named_arg_type: - push_back(args.values_[i]); - break; - default: - break; // Do nothing. - } - } - } - for (unsigned i = 0; ; ++i) { - switch (args.args_[i].type_) { - case internal::none_type: - return; - case internal::named_arg_type: - push_back(args.args_[i].value_); - break; - default: - break; // Do nothing. - } - } -} - -template <typename Range> -class arg_formatter_base { - public: - typedef typename Range::value_type char_type; - typedef decltype(internal::declval<Range>().begin()) iterator; - typedef basic_format_specs<char_type> format_specs; - - private: - typedef basic_writer<Range> writer_type; - writer_type writer_; - format_specs *specs_; - - struct char_writer { - char_type value; - template <typename It> - void operator()(It &&it) const { *it++ = value; } - }; - - void write_char(char_type value) { - if (specs_) - writer_.write_padded(1, *specs_, char_writer{value}); - else - writer_.write(value); - } - - void write_pointer(const void *p) { - format_specs specs = specs_ ? *specs_ : format_specs(); - specs.flags_ = HASH_FLAG; - specs.type_ = 'x'; - writer_.write_int(reinterpret_cast<uintptr_t>(p), specs); - } - - protected: - writer_type &writer() { return writer_; } - format_specs *spec() { return specs_; } - iterator out() { return writer_.out(); } - - void write(bool value) { - string_view sv(value ? "true" : "false"); - specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); - } - - void write(const char_type *value) { - if (!value) - FMT_THROW(format_error("string pointer is null")); - auto length = std::char_traits<char_type>::length(value); - basic_string_view<char_type> sv(value, length); - specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); - } - - public: - arg_formatter_base(Range r, format_specs *s): writer_(r), specs_(s) {} - - iterator operator()(monostate) { - FMT_ASSERT(false, "invalid argument type"); - return out(); - } - - template <typename T> - typename std::enable_if<std::is_integral<T>::value, iterator>::type - operator()(T value) { - // MSVC2013 fails to compile separate overloads for bool and char_type so - // use std::is_same instead. - if (std::is_same<T, bool>::value) { - if (specs_ && specs_->type_) - return (*this)(value ? 1 : 0); - write(value != 0); - } else if (std::is_same<T, char_type>::value) { - internal::handle_char_specs( - specs_, char_spec_handler(*this, static_cast<char_type>(value))); - } else { - specs_ ? writer_.write_int(value, *specs_) : writer_.write(value); - } - return out(); - } - - template <typename T> - typename std::enable_if<std::is_floating_point<T>::value, iterator>::type - operator()(T value) { - writer_.write_double(value, specs_ ? *specs_ : format_specs()); - return out(); - } - - struct char_spec_handler : internal::error_handler { - arg_formatter_base &formatter; - char_type value; - - char_spec_handler(arg_formatter_base& f, char_type val) - : formatter(f), value(val) {} - - void on_int() { - if (formatter.specs_) - formatter.writer_.write_int(value, *formatter.specs_); - else - formatter.writer_.write(value); - } - void on_char() { formatter.write_char(value); } - }; - - struct cstring_spec_handler : internal::error_handler { - arg_formatter_base &formatter; - const char_type *value; - - cstring_spec_handler(arg_formatter_base &f, const char_type *val) - : formatter(f), value(val) {} - - void on_string() { formatter.write(value); } - void on_pointer() { formatter.write_pointer(value); } - }; - - iterator operator()(const char_type *value) { - if (!specs_) return write(value), out(); - internal::handle_cstring_type_spec( - specs_->type_, cstring_spec_handler(*this, value)); - return out(); - } - - iterator operator()(basic_string_view<char_type> value) { - if (specs_) { - internal::check_string_type_spec( - specs_->type_, internal::error_handler()); - writer_.write_str(value, *specs_); - } else { - writer_.write(value); - } - return out(); - } - - iterator operator()(const void *value) { - if (specs_) - check_pointer_type_spec(specs_->type_, internal::error_handler()); - write_pointer(value); - return out(); - } -}; - -template <typename Char> -FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; -} - -// DEPRECATED: Parses the input as an unsigned integer. This function assumes -// that the first character is a digit and presence of a non-digit character at -// the end. -// it: an iterator pointing to the beginning of the input range. -template <typename Iterator, typename ErrorHandler> -FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { - assert('0' <= *it && *it <= '9'); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits<int>::max)(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + unsigned(*it - '0'); - // Workaround for MSVC "setup_exception stack overflow" error: - auto next = it; - ++next; - it = next; - } while ('0' <= *it && *it <= '9'); - if (value > max_int) - eh.on_error("number is too big"); - return value; -} - -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR unsigned parse_nonnegative_int( - const Char *&begin, const Char *end, ErrorHandler &&eh) { - assert(begin != end && '0' <= *begin && *begin <= '9'); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits<int>::max)(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + unsigned(*begin++ - '0'); - } while (begin != end && '0' <= *begin && *begin <= '9'); - if (value > max_int) - eh.on_error("number is too big"); - return value; -} - -template <typename Char, typename Context> -class custom_formatter: public function<bool> { - private: - Context &ctx_; - - public: - explicit custom_formatter(Context &ctx): ctx_(ctx) {} - - bool operator()(typename basic_format_arg<Context>::handle h) const { - h.format(ctx_); - return true; - } - - template <typename T> - bool operator()(T) const { return false; } -}; - -template <typename T> -struct is_integer { - enum { - value = std::is_integral<T>::value && !std::is_same<T, bool>::value && - !std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value - }; -}; - -template <typename ErrorHandler> -class width_checker: public function<unsigned long long> { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler &eh) : handler_(eh) {} - - template <typename T> - FMT_CONSTEXPR - typename std::enable_if< - is_integer<T>::value, unsigned long long>::type operator()(T value) { - if (is_negative(value)) - handler_.on_error("negative width"); - return static_cast<unsigned long long>(value); - } - - template <typename T> - FMT_CONSTEXPR typename std::enable_if< - !is_integer<T>::value, unsigned long long>::type operator()(T) { - handler_.on_error("width is not integer"); - return 0; - } - - private: - ErrorHandler &handler_; -}; - -template <typename ErrorHandler> -class precision_checker: public function<unsigned long long> { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler &eh) : handler_(eh) {} - - template <typename T> - FMT_CONSTEXPR typename std::enable_if< - is_integer<T>::value, unsigned long long>::type operator()(T value) { - if (is_negative(value)) - handler_.on_error("negative precision"); - return static_cast<unsigned long long>(value); - } - - template <typename T> - FMT_CONSTEXPR typename std::enable_if< - !is_integer<T>::value, unsigned long long>::type operator()(T) { - handler_.on_error("precision is not integer"); - return 0; - } - - private: - ErrorHandler &handler_; -}; - -// A format specifier handler that sets fields in basic_format_specs. -template <typename Char> -class specs_setter { - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char> &specs): - specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter &other) : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(alignment align) { specs_.align_ = align; } - FMT_CONSTEXPR void on_fill(Char fill) { specs_.fill_ = fill; } - FMT_CONSTEXPR void on_plus() { specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; } - FMT_CONSTEXPR void on_minus() { specs_.flags_ |= MINUS_FLAG; } - FMT_CONSTEXPR void on_space() { specs_.flags_ |= SIGN_FLAG; } - FMT_CONSTEXPR void on_hash() { specs_.flags_ |= HASH_FLAG; } - - FMT_CONSTEXPR void on_zero() { - specs_.align_ = ALIGN_NUMERIC; - specs_.fill_ = '0'; - } - - FMT_CONSTEXPR void on_width(unsigned width) { specs_.width_ = width; } - FMT_CONSTEXPR void on_precision(unsigned precision) { - specs_.precision_ = static_cast<int>(precision); - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(Char type) { specs_.type_ = type; } - - protected: - basic_format_specs<Char> &specs_; -}; - -// A format specifier handler that checks if specifiers are consistent with the -// argument type. -template <typename Handler> -class specs_checker : public Handler { - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR specs_checker(const specs_checker &other) - : Handler(other), arg_type_(other.arg_type_) {} - - FMT_CONSTEXPR void on_align(alignment align) { - if (align == ALIGN_NUMERIC) - require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_plus() { - check_sign(); - Handler::on_plus(); - } - - FMT_CONSTEXPR void on_minus() { - check_sign(); - Handler::on_minus(); - } - - FMT_CONSTEXPR void on_space() { - check_sign(); - Handler::on_space(); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral(arg_type_) || arg_type_ == pointer_type) - this->on_error("precision not allowed for this argument type"); - } - - private: - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - FMT_CONSTEXPR void check_sign() { - require_numeric_argument(); - if (is_integral(arg_type_) && arg_type_ != int_type && - arg_type_ != long_long_type && arg_type_ != internal::char_type) { - this->on_error("format specifier requires signed argument"); - } - } - - internal::type arg_type_; -}; - -template <template <typename> class Handler, typename T, - typename Context, typename ErrorHandler> -FMT_CONSTEXPR void set_dynamic_spec( - T &value, basic_format_arg<Context> arg, ErrorHandler eh) { - unsigned long long big_value = fmt::visit(Handler<ErrorHandler>(eh), arg); - if (big_value > (std::numeric_limits<int>::max)()) - eh.on_error("number is too big"); - value = static_cast<T>(big_value); -} - -struct auto_id {}; - -// The standard format specifier handler with checking. -template <typename Context> -class specs_handler: public specs_setter<typename Context::char_type> { - public: - typedef typename Context::char_type char_type; - - FMT_CONSTEXPR specs_handler( - basic_format_specs<char_type> &specs, Context &ctx) - : specs_setter<char_type>(specs), context_(ctx) {} - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - set_dynamic_spec<width_checker>( - this->specs_.width_, get_arg(arg_id), context_.error_handler()); - } - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - set_dynamic_spec<precision_checker>( - this->specs_.precision_, get_arg(arg_id), context_.error_handler()); - } - - void on_error(const char *message) { - context_.on_error(message); - } - - private: - FMT_CONSTEXPR basic_format_arg<Context> get_arg(auto_id) { - return context_.next_arg(); - } - - template <typename Id> - FMT_CONSTEXPR basic_format_arg<Context> get_arg(Id arg_id) { - context_.parse_context().check_arg_id(arg_id); - return context_.get_arg(arg_id); - } - - Context &context_; -}; - -// An argument reference. -template <typename Char> -struct arg_ref { - enum Kind { NONE, INDEX, NAME }; - - FMT_CONSTEXPR arg_ref() : kind(NONE), index(0) {} - FMT_CONSTEXPR explicit arg_ref(unsigned index) : kind(INDEX), index(index) {} - explicit arg_ref(basic_string_view<Char> name) : kind(NAME), name(name) {} - - FMT_CONSTEXPR arg_ref &operator=(unsigned idx) { - kind = INDEX; - index = idx; - return *this; - } - - Kind kind; - FMT_UNION { - unsigned index; - basic_string_view<Char> name; - }; -}; - -// Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with -// differents sets of arguments (precompilation of format strings). -template <typename Char> -struct dynamic_format_specs : basic_format_specs<Char> { - arg_ref<Char> width_ref; - arg_ref<Char> precision_ref; -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template <typename ParseContext> -class dynamic_specs_handler : - public specs_setter<typename ParseContext::char_type> { - public: - typedef typename ParseContext::char_type char_type; - - FMT_CONSTEXPR dynamic_specs_handler( - dynamic_format_specs<char_type> &specs, ParseContext &ctx) - : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler &other) - : specs_setter<char_type>(other), - specs_(other.specs_), context_(other.context_) {} - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - specs_.width_ref = make_arg_ref(arg_id); - } - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - specs_.precision_ref = make_arg_ref(arg_id); - } - - FMT_CONSTEXPR void on_error(const char *message) { - context_.on_error(message); - } - - private: - typedef arg_ref<char_type> arg_ref_type; - - template <typename Id> - FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context_.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) { - return arg_ref_type(context_.next_arg_id()); - } - - dynamic_format_specs<char_type> &specs_; - ParseContext &context_; -}; - -template <typename Iterator, typename IDHandler> -FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { - typedef typename std::iterator_traits<Iterator>::value_type char_type; - char_type c = *it; - if (c == '}' || c == ':') { - handler(); - return it; - } - if (c >= '0' && c <= '9') { - unsigned index = parse_nonnegative_int(it, handler); - if (*it != '}' && *it != ':') { - handler.on_error("invalid format string"); - return it; - } - handler(index); - return it; - } - if (!is_name_start(c)) { - handler.on_error("invalid format string"); - return it; - } - auto start = it; - do { - c = *++it; - } while (is_name_start(c) || ('0' <= c && c <= '9')); - handler(basic_string_view<char_type>( - pointer_from(start), to_unsigned(it - start))); - return it; -} - -template <typename Char, typename IDHandler> -FMT_CONSTEXPR const Char *parse_arg_id( - const Char *begin, const Char *end, IDHandler &&handler) { - assert(begin != end); - Char c = *begin; - if (c == '}' || c == ':') - return handler(), begin; - if (c >= '0' && c <= '9') { - unsigned index = parse_nonnegative_int(begin, end, handler); - if (begin == end || (*begin != '}' && *begin != ':')) - return handler.on_error("invalid format string"), begin; - handler(index); - return begin; - } - if (!is_name_start(c)) - return handler.on_error("invalid format string"), begin; - auto it = begin; - do { - c = *++it; - } while (it != end && (is_name_start(c) || ('0' <= c && c <= '9'))); - handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); - return it; -} - -// Adapts SpecHandler to IDHandler API for dynamic width. -template <typename SpecHandler, typename Char> -struct width_adapter { - explicit FMT_CONSTEXPR width_adapter(SpecHandler &h) : handler(h) {} - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } - FMT_CONSTEXPR void operator()(unsigned id) { handler.on_dynamic_width(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_dynamic_width(id); - } - - FMT_CONSTEXPR void on_error(const char *message) { - handler.on_error(message); - } - - SpecHandler &handler; -}; - -// Adapts SpecHandler to IDHandler API for dynamic precision. -template <typename SpecHandler, typename Char> -struct precision_adapter { - explicit FMT_CONSTEXPR precision_adapter(SpecHandler &h) : handler(h) {} - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } - FMT_CONSTEXPR void operator()(unsigned id) { - handler.on_dynamic_precision(id); - } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_dynamic_precision(id); - } - - FMT_CONSTEXPR void on_error(const char *message) { handler.on_error(message); } - - SpecHandler &handler; -}; - -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -// it: an iterator pointing to the beginning of a null-terminated range of -// characters, possibly emulated via null_terminating_iterator, representing -// format specifiers. -template <typename Iterator, typename SpecHandler> -FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { - typedef typename std::iterator_traits<Iterator>::value_type char_type; - char_type c = *it; - if (c == '}' || !c) - return it; - - // Parse fill and alignment. - alignment align = ALIGN_DEFAULT; - int i = 1; - do { - auto p = it + i; - switch (*p) { - case '<': - align = ALIGN_LEFT; - break; - case '>': - align = ALIGN_RIGHT; - break; - case '=': - align = ALIGN_NUMERIC; - break; - case '^': - align = ALIGN_CENTER; - break; - } - if (align != ALIGN_DEFAULT) { - if (p != it) { - if (c == '{') { - handler.on_error("invalid fill character '{'"); - return it; - } - it += 2; - handler.on_fill(c); - } else ++it; - handler.on_align(align); - break; - } - } while (--i >= 0); - - // Parse sign. - switch (*it) { - case '+': - handler.on_plus(); - ++it; - break; - case '-': - handler.on_minus(); - ++it; - break; - case ' ': - handler.on_space(); - ++it; - break; - } - - if (*it == '#') { - handler.on_hash(); - ++it; - } - - // Parse zero flag. - if (*it == '0') { - handler.on_zero(); - ++it; - } - - // Parse width. - if ('0' <= *it && *it <= '9') { - handler.on_width(parse_nonnegative_int(it, handler)); - } else if (*it == '{') { - it = parse_arg_id(it + 1, width_adapter<SpecHandler, char_type>(handler)); - if (*it++ != '}') { - handler.on_error("invalid format string"); - return it; - } - } - - // Parse precision. - if (*it == '.') { - ++it; - if ('0' <= *it && *it <= '9') { - handler.on_precision(parse_nonnegative_int(it, handler)); - } else if (*it == '{') { - it = parse_arg_id( - it + 1, precision_adapter<SpecHandler, char_type>(handler)); - if (*it++ != '}') { - handler.on_error("invalid format string"); - return it; - } - } else { - handler.on_error("missing precision specifier"); - return it; - } - handler.end_precision(); - } - - // Parse type. - if (*it != '}' && *it) - handler.on_type(*it++); - return it; -} - -// Return the result via the out param to workaround gcc bug 77539. -template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*> -FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr &out) { - for (out = first; out != last; ++out) { - if (*out == value) - return true; - } - return false; -} - -template <> -inline bool find<false, char>( - const char *first, const char *last, char value, const char *&out) { - out = static_cast<const char*>(std::memchr(first, value, last - first)); - return out != FMT_NULL; -} - -template <typename Handler, typename Char> -struct id_adapter { - FMT_CONSTEXPR void operator()() { handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(unsigned id) { handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_arg_id(id); - } - FMT_CONSTEXPR void on_error(const char *message) { - handler.on_error(message); - } - Handler &handler; -}; - -template <bool IS_CONSTEXPR, typename Char, typename Handler> -FMT_CONSTEXPR void parse_format_string( - basic_string_view<Char> format_str, Handler &&handler) { - struct writer { - FMT_CONSTEXPR void operator()(const Char *begin, const Char *end) { - if (begin == end) return; - for (;;) { - const Char *p = FMT_NULL; - if (!find<IS_CONSTEXPR>(begin, end, '}', p)) - return handler_.on_text(begin, end); - ++p; - if (p == end || *p != '}') - return handler_.on_error("unmatched '}' in format string"); - handler_.on_text(begin, p); - begin = p + 1; - } - } - Handler &handler_; - } write{handler}; - auto begin = format_str.data(), end = begin + format_str.size(); - while (begin != end) { - // Doing two passes with memchr (one for '{' and another for '}') is up to - // 2.5x faster than the naive one-pass implementation on big format strings. - const Char *p = begin; - if (*begin != '{' && !find<IS_CONSTEXPR>(begin, end, '{', p)) - return write(begin, end); - write(begin, p); - ++p; - if (p == end) - return handler.on_error("invalid format string"); - if (*p == '}') { - handler.on_arg_id(); - handler.on_replacement_field(p); - } else if (*p == '{') { - handler.on_text(p, p + 1); - } else { - p = parse_arg_id(p, end, id_adapter<Handler, Char>{handler}); - Char c = p != end ? *p : 0; - if (c == '}') { - handler.on_replacement_field(p); - } else if (c == ':') { - internal::null_terminating_iterator<Char> it(p + 1, end); - it = handler.on_format_specs(it); - if (*it != '}') - return handler.on_error("unknown format specifier"); - p = pointer_from(it); - } else { - return handler.on_error("missing '}' in format string"); - } - } - begin = p + 1; - } -} - -template <typename T, typename ParseContext> -FMT_CONSTEXPR const typename ParseContext::char_type * - parse_format_specs(ParseContext &ctx) { - // GCC 7.2 requires initializer. - formatter<T, typename ParseContext::char_type> f{}; - return f.parse(ctx); -} - -template <typename Char, typename ErrorHandler, typename... Args> -class format_string_checker { - public: - explicit FMT_CONSTEXPR format_string_checker( - basic_string_view<Char> format_str, ErrorHandler eh) - : arg_id_(-1), context_(format_str, eh), - parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} - - typedef internal::null_terminating_iterator<Char> iterator; - - FMT_CONSTEXPR void on_text(const Char *, const Char *) {} - - FMT_CONSTEXPR void on_arg_id() { - arg_id_ = context_.next_arg_id(); - check_arg_id(); - } - FMT_CONSTEXPR void on_arg_id(unsigned id) { - arg_id_ = id; - context_.check_arg_id(id); - check_arg_id(); - } - FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) {} - - FMT_CONSTEXPR void on_replacement_field(const Char *) {} - - FMT_CONSTEXPR const Char *on_format_specs(iterator it) { - auto p = pointer_from(it); - context_.advance_to(p); - return to_unsigned(arg_id_) < NUM_ARGS ? - parse_funcs_[arg_id_](context_) : p; - } - - FMT_CONSTEXPR void on_error(const char *message) { - context_.on_error(message); - } - - private: - typedef basic_parse_context<Char, ErrorHandler> parse_context_type; - enum { NUM_ARGS = sizeof...(Args) }; - - FMT_CONSTEXPR void check_arg_id() { - if (internal::to_unsigned(arg_id_) >= NUM_ARGS) - context_.on_error("argument index out of range"); - } - - // Format specifier parsing function. - typedef const Char *(*parse_func)(parse_context_type &); - - int arg_id_; - parse_context_type context_; - parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; -}; - -template <typename Char, typename ErrorHandler, typename... Args> -FMT_CONSTEXPR bool check_format_string( - basic_string_view<Char> s, ErrorHandler eh = ErrorHandler()) { - format_string_checker<Char, ErrorHandler, Args...> checker(s, eh); - parse_format_string<true>(s, checker); - return true; -} - -template <typename... Args, typename String> -typename std::enable_if<is_compile_string<String>::value>::type - check_format_string(String format_str) { - FMT_CONSTEXPR_DECL bool invalid_format = - internal::check_format_string<char, internal::error_handler, Args...>( - string_view(format_str.data(), format_str.size())); - (void)invalid_format; -} - -// Specifies whether to format T using the standard formatter. -// It is not possible to use get_type in formatter specialization directly -// because of a bug in MSVC. -template <typename Context, typename T> -struct format_type : - std::integral_constant<bool, get_type<Context, T>::value != custom_type> {}; - -template <template <typename> class Handler, typename Spec, typename Context> -void handle_dynamic_spec( - Spec &value, arg_ref<typename Context::char_type> ref, Context &ctx) { - typedef typename Context::char_type char_type; - switch (ref.kind) { - case arg_ref<char_type>::NONE: - break; - case arg_ref<char_type>::INDEX: - internal::set_dynamic_spec<Handler>( - value, ctx.get_arg(ref.index), ctx.error_handler()); - break; - case arg_ref<char_type>::NAME: - internal::set_dynamic_spec<Handler>( - value, ctx.get_arg(ref.name), ctx.error_handler()); - break; - } -} -} // namespace internal - -/** The default argument formatter. */ -template <typename Range> -class arg_formatter: - public internal::function< - typename internal::arg_formatter_base<Range>::iterator>, - public internal::arg_formatter_base<Range> { - private: - typedef typename Range::value_type char_type; - typedef internal::arg_formatter_base<Range> base; - typedef basic_format_context<typename base::iterator, char_type> context_type; - - context_type &ctx_; - - public: - typedef Range range; - typedef typename base::iterator iterator; - typedef typename base::format_specs format_specs; - - /** - \rst - Constructs an argument formatter object. - *ctx* is a reference to the formatting context, - *spec* contains format specifier information for standard argument types. - \endrst - */ - explicit arg_formatter(context_type &ctx, format_specs *spec = {}) - : base(Range(ctx.out()), spec), ctx_(ctx) {} - - // Deprecated. - arg_formatter(context_type &ctx, format_specs &spec) - : base(Range(ctx.out()), &spec), ctx_(ctx) {} - - using base::operator(); - - /** Formats an argument of a user-defined type. */ - iterator operator()(typename basic_format_arg<context_type>::handle handle) { - handle.format(ctx_); - return this->out(); - } -}; - -/** - An error returned by an operating system or a language runtime, - for example a file opening error. -*/ -class system_error : public std::runtime_error { - private: - FMT_API void init(int err_code, string_view format_str, format_args args); - - protected: - int error_code_; - - system_error() : std::runtime_error("") {} - - public: - /** - \rst - Constructs a :class:`fmt::system_error` object with a description - formatted with `fmt::format_system_error`. *message* and additional - arguments passed into the constructor are formatted similarly to - `fmt::format`. - - **Example**:: - - // This throws a system_error with the description - // cannot open file 'madeup': No such file or directory - // or similar (system message may vary). - const char *filename = "madeup"; - std::FILE *file = std::fopen(filename, "r"); - if (!file) - throw fmt::system_error(errno, "cannot open file '{}'", filename); - \endrst - */ - template <typename... Args> - system_error(int error_code, string_view message, const Args & ... args) - : std::runtime_error("") { - init(error_code, message, make_format_args(args...)); - } - - int error_code() const { return error_code_; } -}; - -/** - \rst - Formats an error returned by an operating system or a language runtime, - for example a file opening error, and writes it to *out* in the following - form: - - .. parsed-literal:: - *<message>*: *<system-message>* - - where *<message>* is the passed message and *<system-message>* is - the system message corresponding to the error code. - *error_code* is a system error code as given by ``errno``. - If *error_code* is not a valid error code such as -1, the system message - may look like "Unknown error -1" and is platform-dependent. - \endrst - */ -FMT_API void format_system_error(internal::buffer &out, int error_code, - fmt::string_view message) FMT_NOEXCEPT; - -/** - This template provides operations for formatting and writing data into a - character range. - */ -template <typename Range> -class basic_writer { - public: - typedef typename Range::value_type char_type; - typedef decltype(internal::declval<Range>().begin()) iterator; - typedef basic_format_specs<char_type> format_specs; - - private: - iterator out_; // Output iterator. - std::unique_ptr<locale_provider> locale_; - - iterator out() const { return out_; } - - // Attempts to reserve space for n extra characters in the output range. - // Returns a pointer to the reserved range or a reference to out_. - auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { - return internal::reserve(out_, n); - } - - // Writes a value in the format - // <left-padding><value><right-padding> - // where <value> is written by f(it). - template <typename F> - void write_padded(std::size_t size, const align_spec &spec, F &&f); - - template <typename F> - struct padded_int_writer { - string_view prefix; - char_type fill; - std::size_t padding; - F f; - - template <typename It> - void operator()(It &&it) const { - if (prefix.size() != 0) - it = std::copy_n(prefix.data(), prefix.size(), it); - it = std::fill_n(it, padding, fill); - f(it); - } - }; - - // Writes an integer in the format - // <left-padding><prefix><numeric-padding><digits><right-padding> - // where <digits> are written by f(it). - template <typename Spec, typename F> - void write_int(unsigned num_digits, string_view prefix, - const Spec &spec, F f) { - std::size_t size = prefix.size() + num_digits; - char_type fill = static_cast<char_type>(spec.fill()); - std::size_t padding = 0; - if (spec.align() == ALIGN_NUMERIC) { - if (spec.width() > size) { - padding = spec.width() - size; - size = spec.width(); - } - } else if (spec.precision() > static_cast<int>(num_digits)) { - size = prefix.size() + static_cast<std::size_t>(spec.precision()); - padding = static_cast<std::size_t>(spec.precision()) - num_digits; - fill = '0'; - } - align_spec as = spec; - if (spec.align() == ALIGN_DEFAULT) - as.align_ = ALIGN_RIGHT; - write_padded(size, as, padded_int_writer<F>{prefix, fill, padding, f}); - } - - // Writes a decimal integer. - template <typename Int> - void write_decimal(Int value) { - typedef typename internal::int_traits<Int>::main_type main_type; - main_type abs_value = static_cast<main_type>(value); - bool is_negative = internal::is_negative(value); - if (is_negative) - abs_value = 0 - abs_value; - unsigned num_digits = internal::count_digits(abs_value); - auto &&it = reserve((is_negative ? 1 : 0) + num_digits); - if (is_negative) - *it++ = '-'; - it = internal::format_decimal(it, abs_value, num_digits); - } - - // The handle_int_type_spec handler that writes an integer. - template <typename Int, typename Spec> - struct int_writer { - typedef typename internal::int_traits<Int>::main_type unsigned_type; - - basic_writer<Range> &writer; - const Spec &spec; - unsigned_type abs_value; - char prefix[4]; - unsigned prefix_size; - - string_view get_prefix() const { return string_view(prefix, prefix_size); } - - // Counts the number of digits in abs_value. BITS = log2(radix). - template <unsigned BITS> - unsigned count_digits() const { - unsigned_type n = abs_value; - unsigned num_digits = 0; - do { - ++num_digits; - } while ((n >>= BITS) != 0); - return num_digits; - } - - int_writer(basic_writer<Range> &w, Int value, const Spec &s) - : writer(w), spec(s), abs_value(static_cast<unsigned_type>(value)), - prefix_size(0) { - if (internal::is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (spec.flag(SIGN_FLAG)) { - prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; - ++prefix_size; - } - } - - struct dec_writer { - unsigned_type abs_value; - unsigned num_digits; - - template <typename It> - void operator()(It &&it) const { - it = internal::format_decimal(it, abs_value, num_digits); - } - }; - - void on_dec() { - unsigned num_digits = internal::count_digits(abs_value); - writer.write_int(num_digits, get_prefix(), spec, - dec_writer{abs_value, num_digits}); - } - - struct hex_writer { - int_writer &self; - unsigned num_digits; - - template <typename It> - void operator()(It &&it) const { - it = internal::format_uint<4>(it, self.abs_value, num_digits, - self.spec.type() != 'x'); - } - }; - - void on_hex() { - if (spec.flag(HASH_FLAG)) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast<char>(spec.type()); - } - unsigned num_digits = count_digits<4>(); - writer.write_int(num_digits, get_prefix(), spec, - hex_writer{*this, num_digits}); - } - - template <int BITS> - struct bin_writer { - unsigned_type abs_value; - unsigned num_digits; - - template <typename It> - void operator()(It &&it) const { - it = internal::format_uint<BITS>(it, abs_value, num_digits); - } - }; - - void on_bin() { - if (spec.flag(HASH_FLAG)) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast<char>(spec.type()); - } - unsigned num_digits = count_digits<1>(); - writer.write_int(num_digits, get_prefix(), spec, - bin_writer<1>{abs_value, num_digits}); - } - - void on_oct() { - unsigned num_digits = count_digits<3>(); - if (spec.flag(HASH_FLAG) && - spec.precision() <= static_cast<int>(num_digits)) { - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - prefix[prefix_size++] = '0'; - } - writer.write_int(num_digits, get_prefix(), spec, - bin_writer<3>{abs_value, num_digits}); - } - - enum { SEP_SIZE = 1 }; - - struct num_writer { - unsigned_type abs_value; - unsigned size; - char_type sep; - - template <typename It> - void operator()(It &&it) const { - basic_string_view<char_type> s(&sep, SEP_SIZE); - it = format_decimal(it, abs_value, size, - internal::add_thousands_sep<char_type>(s)); - } - }; - - void on_num() { - unsigned num_digits = internal::count_digits(abs_value); - char_type sep = internal::thousands_sep<char_type>(writer.locale_.get()); - unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); - writer.write_int(size, get_prefix(), spec, - num_writer{abs_value, size, sep}); - } - - void on_error() { - FMT_THROW(format_error("invalid type specifier")); - } - }; - - // Writes a formatted integer. - template <typename T, typename Spec> - void write_int(T value, const Spec &spec) { - internal::handle_int_type_spec(spec.type(), - int_writer<T, Spec>(*this, value, spec)); - } - - enum {INF_SIZE = 3}; // This is an enum to workaround a bug in MSVC. - - struct inf_or_nan_writer { - char sign; - const char *str; - - template <typename It> - void operator()(It &&it) const { - if (sign) - *it++ = sign; - it = std::copy_n(str, static_cast<std::size_t>(INF_SIZE), it); - } - }; - - struct double_writer { - size_t n; - char sign; - basic_memory_buffer<char_type> &buffer; - - template <typename It> - void operator()(It &&it) { - if (sign) { - *it++ = sign; - --n; - } - it = std::copy_n(buffer.begin(), n, it); - } - }; - - // Formats a floating-point number (double or long double). - template <typename T> - void write_double(T value, const format_specs &spec); - template <typename T> - void write_double_sprintf(T value, const format_specs &spec, - internal::basic_buffer<char_type>& buffer); - - template <typename Char> - struct str_writer { - const Char *s; - std::size_t size; - - template <typename It> - void operator()(It &&it) const { - it = std::copy_n(s, size, it); - } - }; - - // Writes a formatted string. - template <typename Char> - void write_str(const Char *s, std::size_t size, const align_spec &spec) { - write_padded(size, spec, str_writer<Char>{s, size}); - } - - template <typename Char> - void write_str(basic_string_view<Char> str, const format_specs &spec); - - // Appends floating-point length specifier to the format string. - // The second argument is only used for overload resolution. - void append_float_length(char_type *&format_ptr, long double) { - *format_ptr++ = 'L'; - } - - template<typename T> - void append_float_length(char_type *&, T) {} - - template <typename Char> - friend class internal::arg_formatter_base; - - public: - /** Constructs a ``basic_writer`` object. */ - explicit basic_writer(Range out): out_(out.begin()) {} - - void write(int value) { write_decimal(value); } - void write(long value) { write_decimal(value); } - void write(long long value) { write_decimal(value); } - - void write(unsigned value) { write_decimal(value); } - void write(unsigned long value) { write_decimal(value); } - void write(unsigned long long value) { write_decimal(value); } - - /** - \rst - Formats *value* and writes it to the buffer. - \endrst - */ - template <typename T, typename FormatSpec, typename... FormatSpecs> - typename std::enable_if<std::is_integral<T>::value, void>::type - write(T value, FormatSpec spec, FormatSpecs... specs) { - format_specs s(spec, specs...); - s.align_ = ALIGN_RIGHT; - write_int(value, s); - } - - void write(double value) { - write_double(value, format_specs()); - } - - /** - \rst - Formats *value* using the general format for floating-point numbers - (``'g'``) and writes it to the buffer. - \endrst - */ - void write(long double value) { - write_double(value, format_specs()); - } - - /** Writes a character to the buffer. */ - void write(char value) { - *reserve(1) = value; - } - - void write(wchar_t value) { - internal::require_wchar<char_type>(); - *reserve(1) = value; - } - - /** - \rst - Writes *value* to the buffer. - \endrst - */ - void write(string_view value) { - auto &&it = reserve(value.size()); - it = std::copy(value.begin(), value.end(), it); - } - - void write(wstring_view value) { - internal::require_wchar<char_type>(); - auto &&it = reserve(value.size()); - it = std::copy(value.begin(), value.end(), it); - } - - template <typename... FormatSpecs> - void write(basic_string_view<char_type> str, FormatSpecs... specs) { - write_str(str, format_specs(specs...)); - } - - template <typename T> - typename std::enable_if<std::is_same<T, void>::value>::type - write(const T *p) { - format_specs specs; - specs.flags_ = HASH_FLAG; - specs.type_ = 'x'; - write_int(reinterpret_cast<uintptr_t>(p), specs); - } -}; - -template <typename Range> -template <typename F> -void basic_writer<Range>::write_padded( - std::size_t size, const align_spec &spec, F &&f) { - unsigned width = spec.width(); - if (width <= size) - return f(reserve(size)); - auto &&it = reserve(width); - char_type fill = static_cast<char_type>(spec.fill()); - std::size_t padding = width - size; - if (spec.align() == ALIGN_RIGHT) { - it = std::fill_n(it, padding, fill); - f(it); - } else if (spec.align() == ALIGN_CENTER) { - std::size_t left_padding = padding / 2; - it = std::fill_n(it, left_padding, fill); - f(it); - it = std::fill_n(it, padding - left_padding, fill); - } else { - f(it); - it = std::fill_n(it, padding, fill); - } -} - -template <typename Range> -template <typename Char> -void basic_writer<Range>::write_str( - basic_string_view<Char> s, const format_specs &spec) { - const Char *data = s.data(); - std::size_t size = s.size(); - std::size_t precision = static_cast<std::size_t>(spec.precision_); - if (spec.precision_ >= 0 && precision < size) - size = precision; - write_str(data, size, spec); -} - -template <typename Char> -struct float_spec_handler { - Char type; - bool upper; - - explicit float_spec_handler(Char t) : type(t), upper(false) {} - - void on_general() { - if (type == 'G') - upper = true; - else - type = 'g'; - } - - void on_exp() { - if (type == 'E') - upper = true; - } - - void on_fixed() { - if (type == 'F') { - upper = true; -#if FMT_MSC_VER - // MSVC's printf doesn't support 'F'. - type = 'f'; -#endif - } - } - - void on_hex() { - if (type == 'A') - upper = true; - } - - void on_error() { - FMT_THROW(format_error("invalid type specifier")); - } -}; - -template <typename Range> -template <typename T> -void basic_writer<Range>::write_double(T value, const format_specs &spec) { - // Check type. - float_spec_handler<char_type> handler(spec.type()); - internal::handle_float_type_spec(spec.type(), handler); - - char sign = 0; - // Use isnegative instead of value < 0 because the latter is always - // false for NaN. - if (internal::fputil::isnegative(static_cast<double>(value))) { - sign = '-'; - value = -value; - } else if (spec.flag(SIGN_FLAG)) { - sign = spec.flag(PLUS_FLAG) ? '+' : ' '; - } - - struct write_inf_or_nan_t { - basic_writer &writer; - format_specs spec; - char sign; - void operator()(const char *str) const { - writer.write_padded(INF_SIZE + (sign ? 1 : 0), spec, - inf_or_nan_writer{sign, str}); - } - } write_inf_or_nan = {*this, spec, sign}; - - // Format NaN and ininity ourselves because sprintf's output is not consistent - // across platforms. - if (internal::fputil::isnotanumber(value)) - return write_inf_or_nan(handler.upper ? "NAN" : "nan"); - if (internal::fputil::isinfinity(value)) - return write_inf_or_nan(handler.upper ? "INF" : "inf"); - - basic_memory_buffer<char_type> buffer; - char type = static_cast<char>(spec.type()); - if (internal::const_check( - internal::use_grisu() && sizeof(T) <= sizeof(double)) && - type != 'a' && type != 'A') { - char buf[100]; // TODO: correct buffer size - size_t size = 0; - internal::grisu2_format(static_cast<double>(value), buf, size, type, - spec.precision(), spec.flag(HASH_FLAG)); - FMT_ASSERT(size <= 100, "buffer overflow"); - buffer.append(buf, buf + size); // TODO: avoid extra copy - } else { - format_specs normalized_spec(spec); - normalized_spec.type_ = handler.type; - write_double_sprintf(value, normalized_spec, buffer); - } - size_t n = buffer.size(); - align_spec as = spec; - if (spec.align() == ALIGN_NUMERIC) { - if (sign) { - auto &&it = reserve(1); - *it++ = sign; - sign = 0; - if (as.width_) - --as.width_; - } - as.align_ = ALIGN_RIGHT; - } else { - if (spec.align() == ALIGN_DEFAULT) - as.align_ = ALIGN_RIGHT; - if (sign) - ++n; - } - write_padded(n, as, double_writer{n, sign, buffer}); -} - -template <typename Range> -template <typename T> -void basic_writer<Range>::write_double_sprintf( - T value, const format_specs &spec, - internal::basic_buffer<char_type>& buffer) { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buffer.capacity() != 0, "empty buffer"); - - // Build format string. - enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg - char_type format[MAX_FORMAT_SIZE]; - char_type *format_ptr = format; - *format_ptr++ = '%'; - if (spec.flag(HASH_FLAG)) - *format_ptr++ = '#'; - if (spec.precision() >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - - append_float_length(format_ptr, value); - *format_ptr++ = spec.type(); - *format_ptr = '\0'; - - // Format using snprintf. - char_type *start = FMT_NULL; - for (;;) { - std::size_t buffer_size = buffer.capacity(); - start = &buffer[0]; - int result = internal::char_traits<char_type>::format_float( - start, buffer_size, format, spec.precision(), value); - if (result >= 0) { - unsigned n = internal::to_unsigned(result); - if (n < buffer.capacity()) { - buffer.resize(n); - break; // The buffer is large enough - continue with formatting. - } - buffer.reserve(n + 1); - } else { - // If result is negative we ask to increase the capacity by at least 1, - // but as std::vector, the buffer grows exponentially. - buffer.reserve(buffer.capacity() + 1); - } - } -} - -// Reports a system error without throwing an exception. -// Can be used to report errors from destructors. -FMT_API void report_system_error(int error_code, - string_view message) FMT_NOEXCEPT; - -#if FMT_USE_WINDOWS_H - -/** A Windows error. */ -class windows_error : public system_error { - private: - FMT_API void init(int error_code, string_view format_str, format_args args); - - public: - /** - \rst - Constructs a :class:`fmt::windows_error` object with the description - of the form - - .. parsed-literal:: - *<message>*: *<system-message>* - - where *<message>* is the formatted message and *<system-message>* is the - system message corresponding to the error code. - *error_code* is a Windows error code as given by ``GetLastError``. - If *error_code* is not a valid error code such as -1, the system message - will look like "error -1". - - **Example**:: - - // This throws a windows_error with the description - // cannot open file 'madeup': The system cannot find the file specified. - // or similar (system message may vary). - const char *filename = "madeup"; - LPOFSTRUCT of = LPOFSTRUCT(); - HFILE file = OpenFile(filename, &of, OF_READ); - if (file == HFILE_ERROR) { - throw fmt::windows_error(GetLastError(), - "cannot open file '{}'", filename); - } - \endrst - */ - template <typename... Args> - windows_error(int error_code, string_view message, const Args & ... args) { - init(error_code, message, make_format_args(args...)); - } -}; - -// Reports a Windows error without throwing an exception. -// Can be used to report errors from destructors. -FMT_API void report_windows_error(int error_code, - string_view message) FMT_NOEXCEPT; - -#endif - -/** Fast integer formatter. */ -class format_int { - private: - // Buffer should be large enough to hold all digits (digits10 + 1), - // a sign and a null character. - enum {BUFFER_SIZE = std::numeric_limits<unsigned long long>::digits10 + 3}; - mutable char buffer_[BUFFER_SIZE]; - char *str_; - - // Formats value in reverse and returns a pointer to the beginning. - char *format_decimal(unsigned long long value) { - char *ptr = buffer_ + BUFFER_SIZE - 1; - while (value >= 100) { - // Integer division is slow so do it for a group of two digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - unsigned index = static_cast<unsigned>((value % 100) * 2); - value /= 100; - *--ptr = internal::data::DIGITS[index + 1]; - *--ptr = internal::data::DIGITS[index]; - } - if (value < 10) { - *--ptr = static_cast<char>('0' + value); - return ptr; - } - unsigned index = static_cast<unsigned>(value * 2); - *--ptr = internal::data::DIGITS[index + 1]; - *--ptr = internal::data::DIGITS[index]; - return ptr; - } - - void format_signed(long long value) { - unsigned long long abs_value = static_cast<unsigned long long>(value); - bool negative = value < 0; - if (negative) - abs_value = 0 - abs_value; - str_ = format_decimal(abs_value); - if (negative) - *--str_ = '-'; - } - - public: - explicit format_int(int value) { format_signed(value); } - explicit format_int(long value) { format_signed(value); } - explicit format_int(long long value) { format_signed(value); } - explicit format_int(unsigned value) : str_(format_decimal(value)) {} - explicit format_int(unsigned long value) : str_(format_decimal(value)) {} - explicit format_int(unsigned long long value) : str_(format_decimal(value)) {} - - /** Returns the number of characters written to the output buffer. */ - std::size_t size() const { - return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); - } - - /** - Returns a pointer to the output buffer content. No terminating null - character is appended. - */ - const char *data() const { return str_; } - - /** - Returns a pointer to the output buffer content with terminating null - character appended. - */ - const char *c_str() const { - buffer_[BUFFER_SIZE - 1] = '\0'; - return str_; - } - - /** - \rst - Returns the content of the output buffer as an ``std::string``. - \endrst - */ - std::string str() const { return std::string(str_, size()); } -}; - -// Formats a decimal integer value writing into buffer and returns -// a pointer to the end of the formatted string. This function doesn't -// write a terminating null character. -template <typename T> -inline void format_decimal(char *&buffer, T value) { - typedef typename internal::int_traits<T>::main_type main_type; - main_type abs_value = static_cast<main_type>(value); - if (internal::is_negative(value)) { - *buffer++ = '-'; - abs_value = 0 - abs_value; - } - if (abs_value < 100) { - if (abs_value < 10) { - *buffer++ = static_cast<char>('0' + abs_value); - return; - } - unsigned index = static_cast<unsigned>(abs_value * 2); - *buffer++ = internal::data::DIGITS[index]; - *buffer++ = internal::data::DIGITS[index + 1]; - return; - } - unsigned num_digits = internal::count_digits(abs_value); - internal::format_decimal(buffer, abs_value, num_digits); - buffer += num_digits; -} - -// Formatter of objects of type T. -template <typename T, typename Char> -struct formatter< - T, Char, - typename std::enable_if<internal::format_type< - typename buffer_context<Char>::type, T>::value>::type> { - - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. - template <typename ParseContext> - FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) { - auto it = internal::null_terminating_iterator<Char>(ctx); - typedef internal::dynamic_specs_handler<ParseContext> handler_type; - auto type = internal::get_type< - typename buffer_context<Char>::type, T>::value; - internal::specs_checker<handler_type> - handler(handler_type(specs_, ctx), type); - it = parse_format_specs(it, handler); - auto type_spec = specs_.type(); - auto eh = ctx.error_handler(); - switch (type) { - case internal::none_type: - case internal::named_arg_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case internal::int_type: - case internal::uint_type: - case internal::long_long_type: - case internal::ulong_long_type: - case internal::bool_type: - handle_int_type_spec( - type_spec, internal::int_type_checker<decltype(eh)>(eh)); - break; - case internal::char_type: - handle_char_specs( - &specs_, - internal::char_specs_checker<decltype(eh), decltype(type_spec)>( - type_spec, eh)); - break; - case internal::double_type: - case internal::long_double_type: - handle_float_type_spec( - type_spec, internal::float_type_checker<decltype(eh)>(eh)); - break; - case internal::cstring_type: - internal::handle_cstring_type_spec( - type_spec, internal::cstring_type_checker<decltype(eh)>(eh)); - break; - case internal::string_type: - internal::check_string_type_spec(type_spec, eh); - break; - case internal::pointer_type: - internal::check_pointer_type_spec(type_spec, eh); - break; - case internal::custom_type: - // Custom format specifiers should be checked in parse functions of - // formatter specializations. - break; - } - return pointer_from(it); - } - - template <typename FormatContext> - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { - internal::handle_dynamic_spec<internal::width_checker>( - specs_.width_, specs_.width_ref, ctx); - internal::handle_dynamic_spec<internal::precision_checker>( - specs_.precision_, specs_.precision_ref, ctx); - typedef output_range<typename FormatContext::iterator, - typename FormatContext::char_type> range_type; - return fmt::visit(arg_formatter<range_type>(ctx, &specs_), - internal::make_arg<FormatContext>(val)); - } - - private: - internal::dynamic_format_specs<Char> specs_; -}; - -// A formatter for types known only at run time such as variant alternatives. -// -// Usage: -// typedef std::variant<int, std::string> variant; -// template <> -// struct formatter<variant>: dynamic_formatter<> { -// void format(buffer &buf, const variant &v, context &ctx) { -// visit([&](const auto &val) { format(buf, val, ctx); }, v); -// } -// }; -template <typename Char = char> -class dynamic_formatter { - private: - struct null_handler: internal::error_handler { - void on_align(alignment) {} - void on_plus() {} - void on_minus() {} - void on_space() {} - void on_hash() {} - }; - - public: - template <typename ParseContext> - auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - auto it = internal::null_terminating_iterator<Char>(ctx); - // Checks are deferred to formatting time when the argument type is known. - internal::dynamic_specs_handler<ParseContext> handler(specs_, ctx); - it = parse_format_specs(it, handler); - return pointer_from(it); - } - - template <typename T, typename FormatContext> - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { - handle_specs(ctx); - internal::specs_checker<null_handler> - checker(null_handler(), internal::get_type<FormatContext, T>::value); - checker.on_align(specs_.align()); - if (specs_.flags_ == 0) { - // Do nothing. - } else if (specs_.flag(SIGN_FLAG)) { - if (specs_.flag(PLUS_FLAG)) - checker.on_plus(); - else - checker.on_space(); - } else if (specs_.flag(MINUS_FLAG)) { - checker.on_minus(); - } else if (specs_.flag(HASH_FLAG)) { - checker.on_hash(); - } - if (specs_.precision_ != -1) - checker.end_precision(); - typedef output_range<typename FormatContext::iterator, - typename FormatContext::char_type> range; - fmt::visit(arg_formatter<range>(ctx, &specs_), - internal::make_arg<FormatContext>(val)); - return ctx.out(); - } - - private: - template <typename Context> - void handle_specs(Context &ctx) { - internal::handle_dynamic_spec<internal::width_checker>( - specs_.width_, specs_.width_ref, ctx); - internal::handle_dynamic_spec<internal::precision_checker>( - specs_.precision_, specs_.precision_ref, ctx); - } - - internal::dynamic_format_specs<Char> specs_; -}; - -template <typename Range, typename Char> -typename basic_format_context<Range, Char>::format_arg - basic_format_context<Range, Char>::get_arg( - basic_string_view<char_type> name) { - map_.init(this->args()); - format_arg arg = map_.find(name); - if (arg.type() == internal::none_type) - this->on_error("argument not found"); - return arg; -} - -template <typename ArgFormatter, typename Char, typename Context> -struct format_handler : internal::error_handler { - typedef internal::null_terminating_iterator<Char> iterator; - typedef typename ArgFormatter::range range; - - format_handler(range r, basic_string_view<Char> str, - basic_format_args<Context> format_args) - : context(r.begin(), str, format_args) {} - - void on_text(const Char *begin, const Char *end) { - auto size = internal::to_unsigned(end - begin); - auto out = context.out(); - auto &&it = internal::reserve(out, size); - it = std::copy_n(begin, size, it); - context.advance_to(out); - } - - void on_arg_id() { arg = context.next_arg(); } - void on_arg_id(unsigned id) { - context.parse_context().check_arg_id(id); - arg = context.get_arg(id); - } - void on_arg_id(basic_string_view<Char> id) { - arg = context.get_arg(id); - } - - void on_replacement_field(const Char *p) { - context.parse_context().advance_to(p); - if (!fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) - context.advance_to(fmt::visit(ArgFormatter(context), arg)); - } - - iterator on_format_specs(iterator it) { - auto& parse_ctx = context.parse_context(); - parse_ctx.advance_to(pointer_from(it)); - if (fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) - return iterator(parse_ctx); - basic_format_specs<Char> specs; - using internal::specs_handler; - internal::specs_checker<specs_handler<Context>> - handler(specs_handler<Context>(specs, context), arg.type()); - it = parse_format_specs(it, handler); - if (*it != '}') - on_error("missing '}' in format string"); - parse_ctx.advance_to(pointer_from(it)); - context.advance_to(fmt::visit(ArgFormatter(context, &specs), arg)); - return it; - } - - Context context; - basic_format_arg<Context> arg; -}; - -/** Formats arguments and writes the output to the range. */ -template <typename ArgFormatter, typename Char, typename Context> -typename Context::iterator vformat_to(typename ArgFormatter::range out, - basic_string_view<Char> format_str, - basic_format_args<Context> args) { - format_handler<ArgFormatter, Char, Context> h(out, format_str, args); - internal::parse_format_string<false>(format_str, h); - return h.context.out(); -} - -// Casts ``p`` to ``const void*`` for pointer formatting. -// Example: -// auto s = format("{}", ptr(p)); -template <typename T> -inline const void *ptr(const T *p) { return p; } - -template <typename It, typename Char> -struct arg_join { - It begin; - It end; - basic_string_view<Char> sep; - - arg_join(It begin, It end, basic_string_view<Char> sep) - : begin(begin), end(end), sep(sep) {} -}; - -template <typename It, typename Char> -struct formatter<arg_join<It, Char>, Char>: - formatter<typename std::iterator_traits<It>::value_type, Char> { - template <typename FormatContext> - auto format(const arg_join<It, Char> &value, FormatContext &ctx) - -> decltype(ctx.out()) { - typedef formatter<typename std::iterator_traits<It>::value_type, Char> base; - auto it = value.begin; - auto out = ctx.out(); - if (it != value.end) { - out = base::format(*it++, ctx); - while (it != value.end) { - out = std::copy(value.sep.begin(), value.sep.end(), out); - ctx.advance_to(out); - out = base::format(*it++, ctx); - } - } - return out; - } -}; - -template <typename It> -arg_join<It, char> join(It begin, It end, string_view sep) { - return arg_join<It, char>(begin, end, sep); -} - -template <typename It> -arg_join<It, wchar_t> join(It begin, It end, wstring_view sep) { - return arg_join<It, wchar_t>(begin, end, sep); -} - -// The following causes ICE in gcc 4.4. -#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) -template <typename Range> -auto join(const Range &range, string_view sep) - -> arg_join<decltype(internal::begin(range)), char> { - return join(internal::begin(range), internal::end(range), sep); -} - -template <typename Range> -auto join(const Range &range, wstring_view sep) - -> arg_join<decltype(internal::begin(range)), wchar_t> { - return join(internal::begin(range), internal::end(range), sep); -} -#endif - -/** - \rst - Converts *value* to ``std::string`` using the default format for type *T*. - It doesn't support user-defined types with custom formatters. - - **Example**:: - - #include <fmt/format.h> - - std::string answer = fmt::to_string(42); - \endrst - */ -template <typename T> -std::string to_string(const T &value) { - std::string str; - internal::container_buffer<std::string> buf(str); - writer(buf).write(value); - return str; -} - -/** - Converts *value* to ``std::wstring`` using the default format for type *T*. - */ -template <typename T> -std::wstring to_wstring(const T &value) { - std::wstring str; - internal::container_buffer<std::wstring> buf(str); - wwriter(buf).write(value); - return str; -} - -template <typename Char, std::size_t SIZE> -std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE> &buf) { - return std::basic_string<Char>(buf.data(), buf.size()); -} - -inline format_context::iterator vformat_to( - internal::buffer &buf, string_view format_str, format_args args) { - typedef back_insert_range<internal::buffer> range; - return vformat_to<arg_formatter<range>>(buf, format_str, args); -} - -inline wformat_context::iterator vformat_to( - internal::wbuffer &buf, wstring_view format_str, wformat_args args) { - typedef back_insert_range<internal::wbuffer> range; - return vformat_to<arg_formatter<range>>(buf, format_str, args); -} - -template < - typename String, typename... Args, - std::size_t SIZE = inline_buffer_size, - typename Char = typename internal::format_string_traits<String>::char_type> -inline typename buffer_context<Char>::type::iterator format_to( - basic_memory_buffer<Char, SIZE> &buf, const String &format_str, - const Args & ... args) { - internal::check_format_string<Args...>(format_str); - return vformat_to( - buf, basic_string_view<Char>(format_str), - make_format_args<typename buffer_context<Char>::type>(args...)); -} - -template <typename OutputIt, typename Char = char> -//using format_context_t = basic_format_context<OutputIt, Char>; -struct format_context_t { typedef basic_format_context<OutputIt, Char> type; }; - -template <typename OutputIt, typename Char = char> -//using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>; -struct format_args_t { - typedef basic_format_args< - typename format_context_t<OutputIt, Char>::type> type; -}; - -template <typename OutputIt, typename... Args> -inline OutputIt vformat_to(OutputIt out, string_view format_str, - typename format_args_t<OutputIt>::type args) { - typedef output_range<OutputIt, char> range; - return vformat_to<arg_formatter<range>>(range(out), format_str, args); -} -template <typename OutputIt, typename... Args> -inline OutputIt vformat_to( - OutputIt out, wstring_view format_str, - typename format_args_t<OutputIt, wchar_t>::type args) { - typedef output_range<OutputIt, wchar_t> range; - return vformat_to<arg_formatter<range>>(range(out), format_str, args); -} - -/** - \rst - Formats arguments, writes the result to the output iterator ``out`` and returns - the iterator past the end of the output range. - - **Example**:: - - std::vector<char> out; - fmt::format_to(std::back_inserter(out), "{}", 42); - \endrst - */ -template <typename OutputIt, typename... Args> -inline OutputIt format_to(OutputIt out, string_view format_str, - const Args & ... args) { - return vformat_to(out, format_str, - make_format_args<typename format_context_t<OutputIt>::type>(args...)); -} - -template <typename OutputIt> -struct format_to_n_result { - /** Iterator past the end of the output range. */ - OutputIt out; - /** Total (not truncated) output size. */ - std::size_t size; -}; - -template <typename OutputIt> -using format_to_n_context = typename fmt::format_context_t< - fmt::internal::truncating_iterator<OutputIt>>::type; - -template <typename OutputIt> -using format_to_n_args = fmt::basic_format_args<format_to_n_context<OutputIt>>; - -template <typename OutputIt, typename ...Args> -inline format_arg_store<format_to_n_context<OutputIt>, Args...> - make_format_to_n_args(const Args & ... args) { - return format_arg_store<format_to_n_context<OutputIt>, Args...>(args...); -} - -template <typename OutputIt, typename... Args> -inline format_to_n_result<OutputIt> vformat_to_n( - OutputIt out, std::size_t n, string_view format_str, - format_to_n_args<OutputIt> args) { - typedef internal::truncating_iterator<OutputIt> It; - auto it = vformat_to(It(out, n), format_str, args); - return {it.base(), it.count()}; -} - -/** - \rst - Formats arguments, writes up to ``n`` characters of the result to the output - iterator ``out`` and returns the total output size and the iterator past the - end of the output range. - \endrst - */ -template <typename OutputIt, typename... Args> -inline format_to_n_result<OutputIt> format_to_n( - OutputIt out, std::size_t n, string_view format_str, const Args &... args) { - return vformat_to_n<OutputIt>( - out, n, format_str, make_format_to_n_args<OutputIt>(args...)); -} -template <typename OutputIt, typename... Args> -inline format_to_n_result<OutputIt> format_to_n( - OutputIt out, std::size_t n, wstring_view format_str, - const Args &... args) { - typedef internal::truncating_iterator<OutputIt> It; - auto it = vformat_to(It(out, n), format_str, - make_format_args<typename format_context_t<It, wchar_t>::type>(args...)); - return {it.base(), it.count()}; -} - -template <typename Char> -inline std::basic_string<Char> internal::vformat( - basic_string_view<Char> format_str, - basic_format_args<typename buffer_context<Char>::type> args) { - basic_memory_buffer<Char> buffer; - vformat_to(buffer, format_str, args); - return fmt::to_string(buffer); -} - -template <typename String, typename... Args> -inline typename std::enable_if<internal::is_compile_string<String>::value>::type - print(String format_str, const Args & ... args) { - internal::check_format_string<Args...>(format_str); - return vprint(format_str.data(), make_format_args(args...)); -} - -/** - Returns the number of characters in the output of - ``format(format_str, args...)``. - */ -template <typename... Args> -inline std::size_t formatted_size(string_view format_str, - const Args & ... args) { - auto it = format_to(internal::counting_iterator<char>(), format_str, args...); - return it.count(); -} - -#if FMT_USE_USER_DEFINED_LITERALS -namespace internal { - -# if FMT_UDL_TEMPLATE -template <typename Char, Char... CHARS> -class udl_formatter { - public: - template <typename... Args> - std::basic_string<Char> operator()(const Args &... args) const { - FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'}; - FMT_CONSTEXPR_DECL bool invalid_format = - check_format_string<Char, error_handler, Args...>( - basic_string_view<Char>(s, sizeof...(CHARS))); - (void)invalid_format; - return format(s, args...); - } -}; -# else -template <typename Char> -struct udl_formatter { - const Char *str; - - template <typename... Args> - auto operator()(Args && ... args) const - -> decltype(format(str, std::forward<Args>(args)...)) { - return format(str, std::forward<Args>(args)...); - } -}; -# endif // FMT_UDL_TEMPLATE - -template <typename Char> -struct udl_arg { - const Char *str; - - template <typename T> - named_arg<T, Char> operator=(T &&value) const { - return {str, std::forward<T>(value)}; - } -}; - -} // namespace internal - -inline namespace literals { - -# if FMT_UDL_TEMPLATE -template <typename Char, Char... CHARS> -FMT_CONSTEXPR internal::udl_formatter<Char, CHARS...> operator""_format() { - return {}; -} -# else -/** - \rst - User-defined literal equivalent of :func:`fmt::format`. - - **Example**:: - - using namespace fmt::literals; - std::string message = "The answer is {}"_format(42); - \endrst - */ -inline internal::udl_formatter<char> -operator"" _format(const char *s, std::size_t) { return {s}; } -inline internal::udl_formatter<wchar_t> -operator"" _format(const wchar_t *s, std::size_t) { return {s}; } -# endif // FMT_UDL_TEMPLATE - -/** - \rst - User-defined literal equivalent of :func:`fmt::arg`. - - **Example**:: - - using namespace fmt::literals; - fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); - \endrst - */ -inline internal::udl_arg<char> -operator"" _a(const char *s, std::size_t) { return {s}; } -inline internal::udl_arg<wchar_t> -operator"" _a(const wchar_t *s, std::size_t) { return {s}; } -} // inline namespace literals -#endif // FMT_USE_USER_DEFINED_LITERALS -FMT_END_NAMESPACE - -#define FMT_STRING(s) [] { \ - typedef typename std::decay<decltype(s)>::type pointer; \ - struct S : fmt::compile_string { \ - static FMT_CONSTEXPR pointer data() { return s; } \ - static FMT_CONSTEXPR size_t size() { return sizeof(s); } \ - explicit operator fmt::string_view() const { return s; } \ - }; \ - return S{}; \ - }() - -#if defined(FMT_STRING_ALIAS) && FMT_STRING_ALIAS -/** - \rst - Constructs a compile-time format string. This macro is disabled by default to - prevent potential name collisions. To enable it define ``FMT_STRING_ALIAS`` to - 1 before including ``fmt/format.h``. - - **Example**:: - - #define FMT_STRING_ALIAS 1 - #include <fmt/format.h> - // A compile-time error because 'd' is an invalid specifier for strings. - std::string s = format(fmt("{:d}"), "foo"); - \endrst - */ -# define fmt(s) FMT_STRING(s) -#endif - -#ifdef FMT_HEADER_ONLY -# define FMT_FUNC inline -# include "format-inl.h" -#else -# define FMT_FUNC -#endif - -// Restore warnings. -#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION -# pragma GCC diagnostic pop -#endif - -#endif // FMT_FORMAT_H_ diff --git a/src/nmodl/ext/json/json.hpp b/src/nmodl/ext/json/json.hpp deleted file mode 100644 index d28325a66b..0000000000 --- a/src/nmodl/ext/json/json.hpp +++ /dev/null @@ -1,14722 +0,0 @@ -/* - __ _____ _____ _____ - __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 2.1.1 -|_____|_____|_____|_|___| https://github.com/nlohmann/json - -Licensed under the MIT License <http://opensource.org/licenses/MIT>. -Copyright (c) 2013-2017 Niels Lohmann <http://nlohmann.me>. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -*/ - -#ifndef NLOHMANN_JSON_HPP -#define NLOHMANN_JSON_HPP - -#include <algorithm> // all_of, copy, fill, find, for_each, generate_n, none_of, remove, reverse, transform -#include <array> // array -#include <cassert> // assert -#include <ciso646> // and, not, or -#include <clocale> // lconv, localeconv -#include <cmath> // isfinite, labs, ldexp, signbit -#include <cstddef> // nullptr_t, ptrdiff_t, size_t -#include <cstdint> // int64_t, uint64_t -#include <cstdlib> // abort, strtod, strtof, strtold, strtoul, strtoll, strtoull -#include <cstring> // memcpy, strlen -#include <forward_list> // forward_list -#include <functional> // function, hash, less -#include <initializer_list> // initializer_list -#include <iomanip> // hex -#include <iosfwd> // istream, ostream -#include <iterator> // advance, begin, back_inserter, bidirectional_iterator_tag, distance, end, inserter, iterator, iterator_traits, next, random_access_iterator_tag, reverse_iterator -#include <limits> // numeric_limits -#include <locale> // locale -#include <map> // map -#include <memory> // addressof, allocator, allocator_traits, unique_ptr -#include <numeric> // accumulate -#include <sstream> // stringstream -#include <string> // getline, stoi, string, to_string -#include <type_traits> // add_pointer, conditional, decay, enable_if, false_type, integral_constant, is_arithmetic, is_base_of, is_const, is_constructible, is_convertible, is_default_constructible, is_enum, is_floating_point, is_integral, is_nothrow_move_assignable, is_nothrow_move_constructible, is_pointer, is_reference, is_same, is_scalar, is_signed, remove_const, remove_cv, remove_pointer, remove_reference, true_type, underlying_type -#include <utility> // declval, forward, make_pair, move, pair, swap -#include <valarray> // valarray -#include <vector> // vector - -// exclude unsupported compilers -#if defined(__clang__) - #if (__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) < 30400 - #error "unsupported Clang version - see https://github.com/nlohmann/json#supported-compilers" - #endif -#elif defined(__GNUC__) && !(defined(__ICC) || defined(__INTEL_COMPILER)) - #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 - #error "unsupported GCC version - see https://github.com/nlohmann/json#supported-compilers" - #endif -#endif - -// disable float-equal warnings on GCC/clang -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wfloat-equal" -#endif - -// disable documentation warnings on clang -#if defined(__clang__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdocumentation" -#endif - -// allow for portable deprecation warnings -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #define JSON_DEPRECATED __attribute__((deprecated)) -#elif defined(_MSC_VER) - #define JSON_DEPRECATED __declspec(deprecated) -#else - #define JSON_DEPRECATED -#endif - -// allow to disable exceptions -#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && not defined(JSON_NOEXCEPTION) - #define JSON_THROW(exception) throw exception - #define JSON_TRY try - #define JSON_CATCH(exception) catch(exception) -#else - #define JSON_THROW(exception) std::abort() - #define JSON_TRY if(true) - #define JSON_CATCH(exception) if(false) -#endif - -// manual branch prediction -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #define JSON_LIKELY(x) __builtin_expect(!!(x), 1) - #define JSON_UNLIKELY(x) __builtin_expect(!!(x), 0) -#else - #define JSON_LIKELY(x) x - #define JSON_UNLIKELY(x) x -#endif - -// cpp language standard detection -#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 -#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) - #define JSON_HAS_CPP_14 -#endif - -/*! -@brief namespace for Niels Lohmann -@see https://github.com/nlohmann -@since version 1.0.0 -*/ -namespace nlohmann -{ -template<typename = void, typename = void> -struct adl_serializer; - -// forward declaration of basic_json (required to split the class) -template<template<typename U, typename V, typename... Args> class ObjectType = - std::map, - template<typename U, typename... Args> class ArrayType = std::vector, - class StringType = std::string, class BooleanType = bool, - class NumberIntegerType = std::int64_t, - class NumberUnsignedType = std::uint64_t, - class NumberFloatType = double, - template<typename U> class AllocatorType = std::allocator, - template<typename T, typename SFINAE = void> class JSONSerializer = - adl_serializer> -class basic_json; - -// Ugly macros to avoid uglier copy-paste when specializing basic_json -// This is only temporary and will be removed in 3.0 - -#define NLOHMANN_BASIC_JSON_TPL_DECLARATION \ - template<template<typename, typename, typename...> class ObjectType, \ - template<typename, typename...> class ArrayType, \ - class StringType, class BooleanType, class NumberIntegerType, \ - class NumberUnsignedType, class NumberFloatType, \ - template<typename> class AllocatorType, \ - template<typename, typename = void> class JSONSerializer> - -#define NLOHMANN_BASIC_JSON_TPL \ - basic_json<ObjectType, ArrayType, StringType, BooleanType, \ - NumberIntegerType, NumberUnsignedType, NumberFloatType, \ - AllocatorType, JSONSerializer> - - -/*! -@brief unnamed namespace with internal helper functions - -This namespace collects some functions that could not be defined inside the -@ref basic_json class. - -@since version 2.1.0 -*/ -namespace detail -{ -//////////////// -// exceptions // -//////////////// - -/*! -@brief general exception of the @ref basic_json class - -This class is an extension of `std::exception` objects with a member @a id for -exception ids. It is used as the base class for all exceptions thrown by the -@ref basic_json class. This class can hence be used as "wildcard" to catch -exceptions. - -Subclasses: -- @ref parse_error for exceptions indicating a parse error -- @ref invalid_iterator for exceptions indicating errors with iterators -- @ref type_error for exceptions indicating executing a member function with - a wrong type -- @ref out_of_range for exceptions indicating access out of the defined range -- @ref other_error for exceptions indicating other library errors - -@internal -@note To have nothrow-copy-constructible exceptions, we internally use - `std::runtime_error` which can cope with arbitrary-length error messages. - Intermediate strings are built with static functions and then passed to - the actual constructor. -@endinternal - -@liveexample{The following code shows how arbitrary library exceptions can be -caught.,exception} - -@since version 3.0.0 -*/ -class exception : public std::exception -{ - public: - /// returns the explanatory string - const char* what() const noexcept override - { - return m.what(); - } - - /// the id of the exception - const int id; - - protected: - exception(int id_, const char* what_arg) : id(id_), m(what_arg) {} - - static std::string name(const std::string& ename, int id_) - { - return "[json.exception." + ename + "." + std::to_string(id_) + "] "; - } - - private: - /// an exception object as storage for error messages - std::runtime_error m; -}; - -/*! -@brief exception indicating a parse error - -This excpetion is thrown by the library when a parse error occurs. Parse errors -can occur during the deserialization of JSON text, CBOR, MessagePack, as well -as when using JSON Patch. - -Member @a byte holds the byte index of the last read character in the input -file. - -Exceptions have ids 1xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.parse_error.101 | parse error at 2: unexpected end of input; expected string literal | This error indicates a syntax error while deserializing a JSON text. The error message describes that an unexpected token (character) was encountered, and the member @a byte indicates the error position. -json.exception.parse_error.102 | parse error at 14: missing or wrong low surrogate | JSON uses the `\uxxxx` format to describe Unicode characters. Code points above above 0xFFFF are split into two `\uxxxx` entries ("surrogate pairs"). This error indicates that the surrogate pair is incomplete or contains an invalid code point. -json.exception.parse_error.103 | parse error: code points above 0x10FFFF are invalid | Unicode supports code points up to 0x10FFFF. Code points above 0x10FFFF are invalid. -json.exception.parse_error.104 | parse error: JSON patch must be an array of objects | [RFC 6902](https://tools.ietf.org/html/rfc6902) requires a JSON Patch document to be a JSON document that represents an array of objects. -json.exception.parse_error.105 | parse error: operation must have string member 'op' | An operation of a JSON Patch document must contain exactly one "op" member, whose value indicates the operation to perform. Its value must be one of "add", "remove", "replace", "move", "copy", or "test"; other values are errors. -json.exception.parse_error.106 | parse error: array index '01' must not begin with '0' | An array index in a JSON Pointer ([RFC 6901](https://tools.ietf.org/html/rfc6901)) may be `0` or any number wihtout a leading `0`. -json.exception.parse_error.107 | parse error: JSON pointer must be empty or begin with '/' - was: 'foo' | A JSON Pointer must be a Unicode string containing a sequence of zero or more reference tokens, each prefixed by a `/` character. -json.exception.parse_error.108 | parse error: escape character '~' must be followed with '0' or '1' | In a JSON Pointer, only `~0` and `~1` are valid escape sequences. -json.exception.parse_error.109 | parse error: array index 'one' is not a number | A JSON Pointer array index must be a number. -json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vector | When parsing CBOR or MessagePack, the byte vector ends before the complete value has been read. -json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xf8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. -json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. - -@note For an input with n bytes, 1 is the index of the first character and n+1 - is the index of the terminating null byte or the end of file. This also - holds true when reading a byte vector (CBOR or MessagePack). - -@liveexample{The following code shows how a `parse_error` exception can be -caught.,parse_error} - -@sa @ref exception for the base class of the library exceptions -@sa @ref invalid_iterator for exceptions indicating errors with iterators -@sa @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa @ref out_of_range for exceptions indicating access out of the defined range -@sa @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class parse_error : public exception -{ - public: - /*! - @brief create a parse error exception - @param[in] id_ the id of the exception - @param[in] byte_ the byte index where the error occurred (or 0 if the - position cannot be determined) - @param[in] what_arg the explanatory string - @return parse_error object - */ - static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) - { - std::string w = exception::name("parse_error", id_) + "parse error" + - (byte_ != 0 ? (" at " + std::to_string(byte_)) : "") + - ": " + what_arg; - return parse_error(id_, byte_, w.c_str()); - } - - /*! - @brief byte index of the parse error - - The byte index of the last read character in the input file. - - @note For an input with n bytes, 1 is the index of the first character and - n+1 is the index of the terminating null byte or the end of file. - This also holds true when reading a byte vector (CBOR or MessagePack). - */ - const std::size_t byte; - - private: - parse_error(int id_, std::size_t byte_, const char* what_arg) - : exception(id_, what_arg), byte(byte_) {} -}; - -/*! -@brief exception indicating errors with iterators - -This exception is thrown if iterators passed to a library function do not match -the expected semantics. - -Exceptions have ids 2xx. - -name / id | example message | description ------------------------------------ | --------------- | ------------------------- -json.exception.invalid_iterator.201 | iterators are not compatible | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.202 | iterator does not fit current value | In an erase or insert function, the passed iterator @a pos does not belong to the JSON value for which the function was called. It hence does not define a valid position for the deletion/insertion. -json.exception.invalid_iterator.203 | iterators do not fit current value | Either iterator passed to function @ref erase(IteratorType first, IteratorType last) does not belong to the JSON value from which values shall be erased. It hence does not define a valid range to delete values from. -json.exception.invalid_iterator.204 | iterators out of range | When an iterator range for a primitive type (number, boolean, or string) is passed to a constructor or an erase function, this range has to be exactly (@ref begin(), @ref end()), because this is the only way the single stored value is expressed. All other ranges are invalid. -json.exception.invalid_iterator.205 | iterator out of range | When an iterator for a primitive type (number, boolean, or string) is passed to an erase function, the iterator has to be the @ref begin() iterator, because it is the only way to address the stored value. All other iterators are invalid. -json.exception.invalid_iterator.206 | cannot construct with iterators from null | The iterators passed to constructor @ref basic_json(InputIT first, InputIT last) belong to a JSON null value and hence to not define a valid range. -json.exception.invalid_iterator.207 | cannot use key() for non-object iterators | The key() member function can only be used on iterators belonging to a JSON object, because other types do not have a concept of a key. -json.exception.invalid_iterator.208 | cannot use operator[] for object iterators | The operator[] to specify a concrete offset cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.209 | cannot use offsets with object iterators | The offset operators (+, -, +=, -=) cannot be used on iterators belonging to a JSON object, because JSON objects are unordered. -json.exception.invalid_iterator.210 | iterators do not fit | The iterator range passed to the insert function are not compatible, meaning they do not belong to the same container. Therefore, the range (@a first, @a last) is invalid. -json.exception.invalid_iterator.211 | passed iterators may not belong to container | The iterator range passed to the insert function must not be a subrange of the container to insert to. -json.exception.invalid_iterator.212 | cannot compare iterators of different containers | When two iterators are compared, they must belong to the same container. -json.exception.invalid_iterator.213 | cannot compare order of object iterators | The order of object iterators cannot be compared, because JSON objects are unordered. -json.exception.invalid_iterator.214 | cannot get value | Cannot get value for iterator: Either the iterator belongs to a null value or it is an iterator to a primitive type (number, boolean, or string), but the iterator is different to @ref begin(). - -@liveexample{The following code shows how an `invalid_iterator` exception can be -caught.,invalid_iterator} - -@sa @ref exception for the base class of the library exceptions -@sa @ref parse_error for exceptions indicating a parse error -@sa @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa @ref out_of_range for exceptions indicating access out of the defined range -@sa @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class invalid_iterator : public exception -{ - public: - static invalid_iterator create(int id_, const std::string& what_arg) - { - std::string w = exception::name("invalid_iterator", id_) + what_arg; - return invalid_iterator(id_, w.c_str()); - } - - private: - invalid_iterator(int id_, const char* what_arg) - : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating executing a member function with a wrong type - -This exception is thrown in case of a type error; that is, a library function is -executed on a JSON value whose type does not match the expected semantics. - -Exceptions have ids 3xx. - -name / id | example message | description ------------------------------ | --------------- | ------------------------- -json.exception.type_error.301 | cannot create object from initializer list | To create an object from an initializer list, the initializer list must consist only of a list of pairs whose first element is a string. When this constraint is violated, an array is created instead. -json.exception.type_error.302 | type must be object, but is array | During implicit or explicit value conversion, the JSON type must be compatible to the target type. For instance, a JSON string can only be converted into string types, but not into numbers or boolean types. -json.exception.type_error.303 | incompatible ReferenceType for get_ref, actual type is object | To retrieve a reference to a value stored in a @ref basic_json object with @ref get_ref, the type of the reference must match the value type. For instance, for a JSON array, the @a ReferenceType must be @ref array_t&. -json.exception.type_error.304 | cannot use at() with string | The @ref at() member functions can only be executed for certain JSON types. -json.exception.type_error.305 | cannot use operator[] with string | The @ref operator[] member functions can only be executed for certain JSON types. -json.exception.type_error.306 | cannot use value() with string | The @ref value() member functions can only be executed for certain JSON types. -json.exception.type_error.307 | cannot use erase() with string | The @ref erase() member functions can only be executed for certain JSON types. -json.exception.type_error.308 | cannot use push_back() with string | The @ref push_back() and @ref operator+= member functions can only be executed for certain JSON types. -json.exception.type_error.309 | cannot use insert() with | The @ref insert() member functions can only be executed for certain JSON types. -json.exception.type_error.310 | cannot use swap() with number | The @ref swap() member functions can only be executed for certain JSON types. -json.exception.type_error.311 | cannot use emplace_back() with string | The @ref emplace_back() member function can only be executed for certain JSON types. -json.exception.type_error.312 | cannot use update() with string | The @ref update() member functions can only be executed for certain JSON types. -json.exception.type_error.313 | invalid value to unflatten | The @ref unflatten function converts an object whose keys are JSON Pointers back into an arbitrary nested JSON value. The JSON Pointers must not overlap, because then the resulting value would not be well defined. -json.exception.type_error.314 | only objects can be unflattened | The @ref unflatten function only works for an object whose keys are JSON Pointers. -json.exception.type_error.315 | values in object must be primitive | The @ref unflatten function only works for an object whose keys are JSON Pointers and whose values are primitive. - -@liveexample{The following code shows how a `type_error` exception can be -caught.,type_error} - -@sa @ref exception for the base class of the library exceptions -@sa @ref parse_error for exceptions indicating a parse error -@sa @ref invalid_iterator for exceptions indicating errors with iterators -@sa @ref out_of_range for exceptions indicating access out of the defined range -@sa @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class type_error : public exception -{ - public: - static type_error create(int id_, const std::string& what_arg) - { - std::string w = exception::name("type_error", id_) + what_arg; - return type_error(id_, w.c_str()); - } - - private: - type_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating access out of the defined range - -This exception is thrown in case a library function is called on an input -parameter that exceeds the expected range, for instance in case of array -indices or nonexisting object keys. - -Exceptions have ids 4xx. - -name / id | example message | description -------------------------------- | --------------- | ------------------------- -json.exception.out_of_range.401 | array index 3 is out of range | The provided array index @a i is larger than @a size-1. -json.exception.out_of_range.402 | array index '-' (3) is out of range | The special array index `-` in a JSON Pointer never describes a valid element of the array, but the index past the end. That is, it can only be used to add elements at this position, but not to read it. -json.exception.out_of_range.403 | key 'foo' not found | The provided key was not found in the JSON object. -json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. -json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. -json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. - -@liveexample{The following code shows how an `out_of_range` exception can be -caught.,out_of_range} - -@sa @ref exception for the base class of the library exceptions -@sa @ref parse_error for exceptions indicating a parse error -@sa @ref invalid_iterator for exceptions indicating errors with iterators -@sa @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa @ref other_error for exceptions indicating other library errors - -@since version 3.0.0 -*/ -class out_of_range : public exception -{ - public: - static out_of_range create(int id_, const std::string& what_arg) - { - std::string w = exception::name("out_of_range", id_) + what_arg; - return out_of_range(id_, w.c_str()); - } - - private: - out_of_range(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - -/*! -@brief exception indicating other library errors - -This exception is thrown in case of errors that cannot be classified with the -other exception types. - -Exceptions have ids 5xx. - -name / id | example message | description ------------------------------- | --------------- | ------------------------- -json.exception.other_error.501 | unsuccessful: {"op":"test","path":"/baz", "value":"bar"} | A JSON Patch operation 'test' failed. The unsuccessful operation is also printed. -json.exception.other_error.502 | invalid object size for conversion | Some conversions to user-defined types impose constraints on the object size (e.g. std::pair) - -@sa @ref exception for the base class of the library exceptions -@sa @ref parse_error for exceptions indicating a parse error -@sa @ref invalid_iterator for exceptions indicating errors with iterators -@sa @ref type_error for exceptions indicating executing a member function with - a wrong type -@sa @ref out_of_range for exceptions indicating access out of the defined range - -@liveexample{The following code shows how an `other_error` exception can be -caught.,other_error} - -@since version 3.0.0 -*/ -class other_error : public exception -{ - public: - static other_error create(int id_, const std::string& what_arg) - { - std::string w = exception::name("other_error", id_) + what_arg; - return other_error(id_, w.c_str()); - } - - private: - other_error(int id_, const char* what_arg) : exception(id_, what_arg) {} -}; - - - -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - discarded ///< discarded by the the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string -- furthermore, each type is not smaller than itself - -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - static constexpr std::array<uint8_t, 8> order = {{ - 0, // null - 3, // object - 4, // array - 5, // string - 1, // boolean - 2, // integer - 2, // unsigned - 2, // float - } - }; - - // discarded values are not comparable - return lhs != value_t::discarded and rhs != value_t::discarded and - order[static_cast<std::size_t>(lhs)] < order[static_cast<std::size_t>(rhs)]; -} - - -///////////// -// helpers // -///////////// - -template<typename> struct is_basic_json : std::false_type {}; - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -struct is_basic_json<NLOHMANN_BASIC_JSON_TPL> : std::true_type {}; - -// alias templates to reduce boilerplate -template<bool B, typename T = void> -using enable_if_t = typename std::enable_if<B, T>::type; - -template<typename T> -using uncvref_t = typename std::remove_cv<typename std::remove_reference<T>::type>::type; - -// implementation of C++14 index_sequence and affiliates -// source: https://stackoverflow.com/a/32223343 -template<std::size_t... Ints> -struct index_sequence -{ - using type = index_sequence; - using value_type = std::size_t; - static constexpr std::size_t size() noexcept - { - return sizeof...(Ints); - } -}; - -template<class Sequence1, class Sequence2> -struct merge_and_renumber; - -template<std::size_t... I1, std::size_t... I2> -struct merge_and_renumber<index_sequence<I1...>, index_sequence<I2...>> - : index_sequence < I1..., (sizeof...(I1) + I2)... > - {}; - -template<std::size_t N> -struct make_index_sequence - : merge_and_renumber < typename make_index_sequence < N / 2 >::type, - typename make_index_sequence < N - N / 2 >::type > -{}; - -template<> struct make_index_sequence<0> : index_sequence<> { }; -template<> struct make_index_sequence<1> : index_sequence<0> { }; - -template<typename... Ts> -using index_sequence_for = make_index_sequence<sizeof...(Ts)>; - -/* -Implementation of two C++17 constructs: conjunction, negation. This is needed -to avoid evaluating all the traits in a condition - -For example: not std::is_same<void, T>::value and has_value_type<T>::value -will not compile when T = void (on MSVC at least). Whereas -conjunction<negation<std::is_same<void, T>>, has_value_type<T>>::value will -stop evaluating if negation<...>::value == false - -Please note that those constructs must be used with caution, since symbols can -become very long quickly (which can slow down compilation and cause MSVC -internal compiler errors). Only use it when you have to (see example ahead). -*/ -template<class...> struct conjunction : std::true_type {}; -template<class B1> struct conjunction<B1> : B1 {}; -template<class B1, class... Bn> -struct conjunction<B1, Bn...> : std::conditional<bool(B1::value), conjunction<Bn...>, B1>::type {}; - -template<class B> struct negation : std::integral_constant < bool, !B::value > {}; - -// dispatch utility (taken from ranges-v3) -template<unsigned N> struct priority_tag : priority_tag < N - 1 > {}; -template<> struct priority_tag<0> {}; - - -////////////////// -// constructors // -////////////////// - -template<value_t> struct external_constructor; - -template<> -struct external_constructor<value_t::boolean> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::boolean_t b) noexcept - { - j.m_type = value_t::boolean; - j.m_value = b; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor<value_t::string> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, const typename BasicJsonType::string_t& s) - { - j.m_type = value_t::string; - j.m_value = s; - j.assert_invariant(); - } - - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::string_t&& s) - { - j.m_type = value_t::string; - j.m_value = std::move(s); - j.assert_invariant(); - } -}; - -template<> -struct external_constructor<value_t::number_float> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::number_float_t val) noexcept - { - j.m_type = value_t::number_float; - j.m_value = val; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor<value_t::number_unsigned> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::number_unsigned_t val) noexcept - { - j.m_type = value_t::number_unsigned; - j.m_value = val; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor<value_t::number_integer> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::number_integer_t val) noexcept - { - j.m_type = value_t::number_integer; - j.m_value = val; - j.assert_invariant(); - } -}; - -template<> -struct external_constructor<value_t::array> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, const typename BasicJsonType::array_t& arr) - { - j.m_type = value_t::array; - j.m_value = arr; - j.assert_invariant(); - } - - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::array_t&& arr) - { - j.m_type = value_t::array; - j.m_value = std::move(arr); - j.assert_invariant(); - } - - template<typename BasicJsonType, typename CompatibleArrayType, - enable_if_t<not std::is_same<CompatibleArrayType, - typename BasicJsonType::array_t>::value, - int> = 0> - static void construct(BasicJsonType& j, const CompatibleArrayType& arr) - { - using std::begin; - using std::end; - j.m_type = value_t::array; - j.m_value.array = j.template create<typename BasicJsonType::array_t>(begin(arr), end(arr)); - j.assert_invariant(); - } - - template<typename BasicJsonType> - static void construct(BasicJsonType& j, const std::vector<bool>& arr) - { - j.m_type = value_t::array; - j.m_value = value_t::array; - j.m_value.array->reserve(arr.size()); - for (bool x : arr) - { - j.m_value.array->push_back(x); - } - j.assert_invariant(); - } - - template<typename BasicJsonType, typename T, - enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> - static void construct(BasicJsonType& j, const std::valarray<T>& arr) - { - j.m_type = value_t::array; - j.m_value = value_t::array; - j.m_value.array->resize(arr.size()); - std::copy(std::begin(arr), std::end(arr), j.m_value.array->begin()); - j.assert_invariant(); - } -}; - -template<> -struct external_constructor<value_t::object> -{ - template<typename BasicJsonType> - static void construct(BasicJsonType& j, const typename BasicJsonType::object_t& obj) - { - j.m_type = value_t::object; - j.m_value = obj; - j.assert_invariant(); - } - - template<typename BasicJsonType> - static void construct(BasicJsonType& j, typename BasicJsonType::object_t&& obj) - { - j.m_type = value_t::object; - j.m_value = std::move(obj); - j.assert_invariant(); - } - - template<typename BasicJsonType, typename CompatibleObjectType, - enable_if_t<not std::is_same<CompatibleObjectType, - typename BasicJsonType::object_t>::value, int> = 0> - static void construct(BasicJsonType& j, const CompatibleObjectType& obj) - { - using std::begin; - using std::end; - - j.m_type = value_t::object; - j.m_value.object = j.template create<typename BasicJsonType::object_t>(begin(obj), end(obj)); - j.assert_invariant(); - } -}; - - -//////////////////////// -// has_/is_ functions // -//////////////////////// - -/*! -@brief Helper to determine whether there's a key_type for T. - -This helper is used to tell associative containers apart from other containers -such as sequence containers. For instance, `std::map` passes the test as it -contains a `mapped_type`, whereas `std::vector` fails the test. - -@sa http://stackoverflow.com/a/7728728/266378 -@since version 1.0.0, overworked in version 2.0.6 -*/ -#define NLOHMANN_JSON_HAS_HELPER(type) \ - template<typename T> struct has_##type { \ - private: \ - template<typename U, typename = typename U::type> \ - static int detect(U &&); \ - static void detect(...); \ - public: \ - static constexpr bool value = \ - std::is_integral<decltype(detect(std::declval<T>()))>::value; \ - } - -NLOHMANN_JSON_HAS_HELPER(mapped_type); -NLOHMANN_JSON_HAS_HELPER(key_type); -NLOHMANN_JSON_HAS_HELPER(value_type); -NLOHMANN_JSON_HAS_HELPER(iterator); - -#undef NLOHMANN_JSON_HAS_HELPER - - -template<bool B, class RealType, class CompatibleObjectType> -struct is_compatible_object_type_impl : std::false_type {}; - -template<class RealType, class CompatibleObjectType> -struct is_compatible_object_type_impl<true, RealType, CompatibleObjectType> -{ - static constexpr auto value = - std::is_constructible<typename RealType::key_type, typename CompatibleObjectType::key_type>::value and - std::is_constructible<typename RealType::mapped_type, typename CompatibleObjectType::mapped_type>::value; -}; - -template<class BasicJsonType, class CompatibleObjectType> -struct is_compatible_object_type -{ - static auto constexpr value = is_compatible_object_type_impl < - conjunction<negation<std::is_same<void, CompatibleObjectType>>, - has_mapped_type<CompatibleObjectType>, - has_key_type<CompatibleObjectType>>::value, - typename BasicJsonType::object_t, CompatibleObjectType >::value; -}; - -template<typename BasicJsonType, typename T> -struct is_basic_json_nested_type -{ - static auto constexpr value = std::is_same<T, typename BasicJsonType::iterator>::value or - std::is_same<T, typename BasicJsonType::const_iterator>::value or - std::is_same<T, typename BasicJsonType::reverse_iterator>::value or - std::is_same<T, typename BasicJsonType::const_reverse_iterator>::value; -}; - -template<class BasicJsonType, class CompatibleArrayType> -struct is_compatible_array_type -{ - static auto constexpr value = - conjunction<negation<std::is_same<void, CompatibleArrayType>>, - negation<is_compatible_object_type< - BasicJsonType, CompatibleArrayType>>, - negation<std::is_constructible<typename BasicJsonType::string_t, - CompatibleArrayType>>, - negation<is_basic_json_nested_type<BasicJsonType, CompatibleArrayType>>, - has_value_type<CompatibleArrayType>, - has_iterator<CompatibleArrayType>>::value; -}; - -template<bool, typename, typename> -struct is_compatible_integer_type_impl : std::false_type {}; - -template<typename RealIntegerType, typename CompatibleNumberIntegerType> -struct is_compatible_integer_type_impl<true, RealIntegerType, CompatibleNumberIntegerType> -{ - // is there an assert somewhere on overflows? - using RealLimits = std::numeric_limits<RealIntegerType>; - using CompatibleLimits = std::numeric_limits<CompatibleNumberIntegerType>; - - static constexpr auto value = - std::is_constructible<RealIntegerType, CompatibleNumberIntegerType>::value and - CompatibleLimits::is_integer and - RealLimits::is_signed == CompatibleLimits::is_signed; -}; - -template<typename RealIntegerType, typename CompatibleNumberIntegerType> -struct is_compatible_integer_type -{ - static constexpr auto value = - is_compatible_integer_type_impl < - std::is_integral<CompatibleNumberIntegerType>::value and - not std::is_same<bool, CompatibleNumberIntegerType>::value, - RealIntegerType, CompatibleNumberIntegerType > ::value; -}; - - -// trait checking if JSONSerializer<T>::from_json(json const&, udt&) exists -template<typename BasicJsonType, typename T> -struct has_from_json -{ - private: - // also check the return type of from_json - template<typename U, typename = enable_if_t<std::is_same<void, decltype(uncvref_t<U>::from_json( - std::declval<BasicJsonType>(), std::declval<T&>()))>::value>> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral<decltype( - detect(std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; -}; - -// This trait checks if JSONSerializer<T>::from_json(json const&) exists -// this overload is used for non-default-constructible user-defined-types -template<typename BasicJsonType, typename T> -struct has_non_default_from_json -{ - private: - template < - typename U, - typename = enable_if_t<std::is_same< - T, decltype(uncvref_t<U>::from_json(std::declval<BasicJsonType>()))>::value >> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral<decltype(detect( - std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; -}; - -// This trait checks if BasicJsonType::json_serializer<T>::to_json exists -template<typename BasicJsonType, typename T> -struct has_to_json -{ - private: - template<typename U, typename = decltype(uncvref_t<U>::to_json( - std::declval<BasicJsonType&>(), std::declval<T>()))> - static int detect(U&&); - static void detect(...); - - public: - static constexpr bool value = std::is_integral<decltype(detect( - std::declval<typename BasicJsonType::template json_serializer<T, void>>()))>::value; -}; - - -///////////// -// to_json // -///////////// - -template<typename BasicJsonType, typename T, enable_if_t< - std::is_same<T, typename BasicJsonType::boolean_t>::value, int> = 0> -void to_json(BasicJsonType& j, T b) noexcept -{ - external_constructor<value_t::boolean>::construct(j, b); -} - -template<typename BasicJsonType, typename CompatibleString, - enable_if_t<std::is_constructible<typename BasicJsonType::string_t, - CompatibleString>::value, int> = 0> -void to_json(BasicJsonType& j, const CompatibleString& s) -{ - external_constructor<value_t::string>::construct(j, s); -} - -template <typename BasicJsonType> -void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) -{ - external_constructor<value_t::string>::construct(j, std::move(s)); -} - -template<typename BasicJsonType, typename FloatType, - enable_if_t<std::is_floating_point<FloatType>::value, int> = 0> -void to_json(BasicJsonType& j, FloatType val) noexcept -{ - external_constructor<value_t::number_float>::construct(j, static_cast<typename BasicJsonType::number_float_t>(val)); -} - -template < - typename BasicJsonType, typename CompatibleNumberUnsignedType, - enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_unsigned_t, - CompatibleNumberUnsignedType>::value, int> = 0 > -void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept -{ - external_constructor<value_t::number_unsigned>::construct(j, static_cast<typename BasicJsonType::number_unsigned_t>(val)); -} - -template < - typename BasicJsonType, typename CompatibleNumberIntegerType, - enable_if_t<is_compatible_integer_type<typename BasicJsonType::number_integer_t, - CompatibleNumberIntegerType>::value, int> = 0 > -void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept -{ - external_constructor<value_t::number_integer>::construct(j, static_cast<typename BasicJsonType::number_integer_t>(val)); -} - -template<typename BasicJsonType, typename EnumType, - enable_if_t<std::is_enum<EnumType>::value, int> = 0> -void to_json(BasicJsonType& j, EnumType e) noexcept -{ - using underlying_type = typename std::underlying_type<EnumType>::type; - external_constructor<value_t::number_integer>::construct(j, static_cast<underlying_type>(e)); -} - -template<typename BasicJsonType> -void to_json(BasicJsonType& j, const std::vector<bool>& e) -{ - external_constructor<value_t::array>::construct(j, e); -} - -template < - typename BasicJsonType, typename CompatibleArrayType, - enable_if_t < - is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value or - std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, - int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) -{ - external_constructor<value_t::array>::construct(j, arr); -} - -template <typename BasicJsonType, typename T, - enable_if_t<std::is_convertible<T, BasicJsonType>::value, int> = 0> -void to_json(BasicJsonType& j, std::valarray<T> arr) -{ - external_constructor<value_t::array>::construct(j, std::move(arr)); -} - -template <typename BasicJsonType> -void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) -{ - external_constructor<value_t::array>::construct(j, std::move(arr)); -} - -template < - typename BasicJsonType, typename CompatibleObjectType, - enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, - int> = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) -{ - external_constructor<value_t::object>::construct(j, obj); -} - -template <typename BasicJsonType> -void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) -{ - external_constructor<value_t::object>::construct(j, std::move(obj)); -} - -template<typename BasicJsonType, typename T, std::size_t N, - enable_if_t<not std::is_constructible< - typename BasicJsonType::string_t, T (&)[N]>::value, - int> = 0> -void to_json(BasicJsonType& j, T (&arr)[N]) -{ - external_constructor<value_t::array>::construct(j, arr); -} - -template<typename BasicJsonType, typename... Args> -void to_json(BasicJsonType& j, const std::pair<Args...>& p) -{ - j = {p.first, p.second}; -} - -template<typename BasicJsonType, typename Tuple, std::size_t... Idx> -void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<Idx...>) -{ - j = {std::get<Idx>(t)...}; -} - -template<typename BasicJsonType, typename... Args> -void to_json(BasicJsonType& j, const std::tuple<Args...>& t) -{ - to_json_tuple_impl(j, t, index_sequence_for<Args...> {}); -} - -/////////////// -// from_json // -/////////////// - -// overloads for basic_json template parameters -template<typename BasicJsonType, typename ArithmeticType, - enable_if_t<std::is_arithmetic<ArithmeticType>::value and - not std::is_same<ArithmeticType, - typename BasicJsonType::boolean_t>::value, - int> = 0> -void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast<value_t>(j)) - { - case value_t::number_unsigned: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); - break; - } - case value_t::number_integer: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); - break; - } - case value_t::number_float: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template<typename BasicJsonType> -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) -{ - if (JSON_UNLIKELY(not j.is_boolean())) - { - JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(j.type_name()))); - } - b = *j.template get_ptr<const typename BasicJsonType::boolean_t*>(); -} - -template<typename BasicJsonType> -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) -{ - if (JSON_UNLIKELY(not j.is_string())) - { - JSON_THROW(type_error::create(302, "type must be string, but is " + std::string(j.type_name()))); - } - s = *j.template get_ptr<const typename BasicJsonType::string_t*>(); -} - -template<typename BasicJsonType> -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) -{ - get_arithmetic_value(j, val); -} - -template<typename BasicJsonType> -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) -{ - get_arithmetic_value(j, val); -} - -template<typename BasicJsonType> -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) -{ - get_arithmetic_value(j, val); -} - -template<typename BasicJsonType, typename EnumType, - enable_if_t<std::is_enum<EnumType>::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) -{ - typename std::underlying_type<EnumType>::type val; - get_arithmetic_value(j, val); - e = static_cast<EnumType>(val); -} - -template<typename BasicJsonType> -void from_json(const BasicJsonType& j, typename BasicJsonType::array_t& arr) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - arr = *j.template get_ptr<const typename BasicJsonType::array_t*>(); -} - -// forward_list doesn't have an insert method -template<typename BasicJsonType, typename T, typename Allocator, - enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list<T, Allocator>& l) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - std::transform(j.rbegin(), j.rend(), - std::front_inserter(l), [](const BasicJsonType & i) - { - return i.template get<T>(); - }); -} - -// valarray doesn't have an insert method -template<typename BasicJsonType, typename T, - enable_if_t<std::is_convertible<BasicJsonType, T>::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray<T>& l) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - l.resize(j.size()); - std::copy(j.m_value.array->begin(), j.m_value.array->end(), std::begin(l)); -} - -template<typename BasicJsonType, typename CompatibleArrayType> -void from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<0> /*unused*/) -{ - using std::end; - - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get<BasicJsonType>() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get<typename CompatibleArrayType::value_type>(); - }); -} - -template<typename BasicJsonType, typename CompatibleArrayType> -auto from_json_array_impl(const BasicJsonType& j, CompatibleArrayType& arr, priority_tag<1> /*unused*/) --> decltype( - arr.reserve(std::declval<typename CompatibleArrayType::size_type>()), - void()) -{ - using std::end; - - arr.reserve(j.size()); - std::transform(j.begin(), j.end(), - std::inserter(arr, end(arr)), [](const BasicJsonType & i) - { - // get<BasicJsonType>() returns *this, this won't call a from_json - // method when value_type is BasicJsonType - return i.template get<typename CompatibleArrayType::value_type>(); - }); -} - -template<typename BasicJsonType, typename T, std::size_t N> -void from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr, priority_tag<2> /*unused*/) -{ - for (std::size_t i = 0; i < N; ++i) - { - arr[i] = j.at(i).template get<T>(); - } -} - -template<typename BasicJsonType, typename CompatibleArrayType, - enable_if_t<is_compatible_array_type<BasicJsonType, CompatibleArrayType>::value and - std::is_convertible<BasicJsonType, typename CompatibleArrayType::value_type>::value and - not std::is_same<typename BasicJsonType::array_t, CompatibleArrayType>::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleArrayType& arr) -{ - if (JSON_UNLIKELY(not j.is_array())) - { - JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); - } - - from_json_array_impl(j, arr, priority_tag<2> {}); -} - -template<typename BasicJsonType, typename CompatibleObjectType, - enable_if_t<is_compatible_object_type<BasicJsonType, CompatibleObjectType>::value, int> = 0> -void from_json(const BasicJsonType& j, CompatibleObjectType& obj) -{ - if (JSON_UNLIKELY(not j.is_object())) - { - JSON_THROW(type_error::create(302, "type must be object, but is " + std::string(j.type_name()))); - } - - auto inner_object = j.template get_ptr<const typename BasicJsonType::object_t*>(); - using value_type = typename CompatibleObjectType::value_type; - std::transform( - inner_object->begin(), inner_object->end(), - std::inserter(obj, obj.begin()), - [](typename BasicJsonType::object_t::value_type const & p) - { - return value_type(p.first, p.second.template get<typename CompatibleObjectType::mapped_type>()); - }); -} - -// overload for arithmetic types, not chosen for basic_json template arguments -// (BooleanType, etc..); note: Is it really necessary to provide explicit -// overloads for boolean_t etc. in case of a custom BooleanType which is not -// an arithmetic type? -template<typename BasicJsonType, typename ArithmeticType, - enable_if_t < - std::is_arithmetic<ArithmeticType>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::number_unsigned_t>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::number_integer_t>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::number_float_t>::value and - not std::is_same<ArithmeticType, typename BasicJsonType::boolean_t>::value, - int> = 0> -void from_json(const BasicJsonType& j, ArithmeticType& val) -{ - switch (static_cast<value_t>(j)) - { - case value_t::number_unsigned: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_unsigned_t*>()); - break; - } - case value_t::number_integer: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_integer_t*>()); - break; - } - case value_t::number_float: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::number_float_t*>()); - break; - } - case value_t::boolean: - { - val = static_cast<ArithmeticType>(*j.template get_ptr<const typename BasicJsonType::boolean_t*>()); - break; - } - - default: - JSON_THROW(type_error::create(302, "type must be number, but is " + std::string(j.type_name()))); - } -} - -template<typename BasicJsonType, typename A1, typename A2> -void from_json(const BasicJsonType& j, std::pair<A1, A2>& p) -{ - p = {j.at(0).template get<A1>(), j.at(1).template get<A2>()}; -} - -template<typename BasicJsonType, typename Tuple, std::size_t... Idx> -void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...>) -{ - t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...); -} - -template<typename BasicJsonType, typename... Args> -void from_json(const BasicJsonType& j, std::tuple<Args...>& t) -{ - from_json_tuple_impl(j, t, index_sequence_for<Args...> {}); -} - -struct to_json_fn -{ - private: - template<typename BasicJsonType, typename T> - auto call(BasicJsonType& j, T&& val, priority_tag<1> /*unused*/) const noexcept(noexcept(to_json(j, std::forward<T>(val)))) - -> decltype(to_json(j, std::forward<T>(val)), void()) - { - return to_json(j, std::forward<T>(val)); - } - - template<typename BasicJsonType, typename T> - void call(BasicJsonType& /*unused*/, T&& /*unused*/, priority_tag<0> /*unused*/) const noexcept - { - static_assert(sizeof(BasicJsonType) == 0, - "could not find to_json() method in T's namespace"); - } - - public: - template<typename BasicJsonType, typename T> - void operator()(BasicJsonType& j, T&& val) const - noexcept(noexcept(std::declval<to_json_fn>().call(j, std::forward<T>(val), priority_tag<1> {}))) - { - return call(j, std::forward<T>(val), priority_tag<1> {}); - } -}; - -struct from_json_fn -{ - private: - template<typename BasicJsonType, typename T> - auto call(const BasicJsonType& j, T& val, priority_tag<1> /*unused*/) const - noexcept(noexcept(from_json(j, val))) - -> decltype(from_json(j, val), void()) - { - return from_json(j, val); - } - - template<typename BasicJsonType, typename T> - void call(const BasicJsonType& /*unused*/, T& /*unused*/, priority_tag<0> /*unused*/) const noexcept - { - static_assert(sizeof(BasicJsonType) == 0, - "could not find from_json() method in T's namespace"); - } - - public: - template<typename BasicJsonType, typename T> - void operator()(const BasicJsonType& j, T& val) const - noexcept(noexcept(std::declval<from_json_fn>().call(j, val, priority_tag<1> {}))) - { - return call(j, val, priority_tag<1> {}); - } -}; - -// taken from ranges-v3 -template<typename T> -struct static_const -{ - static constexpr T value{}; -}; - -template<typename T> -constexpr T static_const<T>::value; - -//////////////////// -// input adapters // -//////////////////// - -/*! -@brief abstract input adapter interface - -Produces a stream of std::char_traits<char>::int_type characters from a -std::istream, a buffer, or some other input type. Accepts the return of exactly -one non-EOF character for future input. The int_type characters returned -consist of all valid char values as positive values (typically unsigned char), -plus an EOF value outside that range, specified by the value of the function -std::char_traits<char>::eof(). This value is typically -1, but could be any -arbitrary value which is not a valid char value. -*/ -struct input_adapter_protocol -{ - /// get a character [0,255] or std::char_traits<char>::eof(). - virtual std::char_traits<char>::int_type get_character() = 0; - /// restore the last non-eof() character to input - virtual void unget_character() = 0; - virtual ~input_adapter_protocol() = default; -}; - -/// a type to simplify interfaces -using input_adapter_t = std::shared_ptr<input_adapter_protocol>; - -/*! -Input adapter for a (caching) istream. Ignores a UFT Byte Order Mark at -beginning of input. Does not support changing the underlying std::streambuf -in mid-input. Maintains underlying std::istream and std::streambuf to support -subsequent use of standard std::istream operations to process any input -characters following those used in parsing the JSON input. Clears the -std::istream flags; any input errors (e.g., EOF) will be detected by the first -subsequent call for input from the std::istream. -*/ -class input_stream_adapter : public input_adapter_protocol -{ - public: - ~input_stream_adapter() override - { - // clear stream flags; we use underlying streambuf I/O, do not - // maintain ifstream flags - is.clear(); - } - - explicit input_stream_adapter(std::istream& i) - : is(i), sb(*i.rdbuf()) - { - // ignore Byte Order Mark at start of input - std::char_traits<char>::int_type c; - if ((c = get_character()) == 0xEF) - { - if ((c = get_character()) == 0xBB) - { - if ((c = get_character()) == 0xBF) - { - return; // Ignore BOM - } - else if (c != std::char_traits<char>::eof()) - { - is.unget(); - } - is.putback('\xBB'); - } - else if (c != std::char_traits<char>::eof()) - { - is.unget(); - } - is.putback('\xEF'); - } - else if (c != std::char_traits<char>::eof()) - { - is.unget(); // Not BOM. Process as usual. - } - } - - // delete because of pointer members - input_stream_adapter(const input_stream_adapter&) = delete; - input_stream_adapter& operator=(input_stream_adapter&) = delete; - - // std::istream/std::streambuf use std::char_traits<char>::to_int_type, to - // ensure that std::char_traits<char>::eof() and the character 0xff do not - // end up as the same value, eg. 0xffffffff. - std::char_traits<char>::int_type get_character() override - { - return sb.sbumpc(); - } - - void unget_character() override - { - sb.sungetc(); // is.unget() avoided for performance - } - - private: - /// the associated input stream - std::istream& is; - std::streambuf& sb; -}; - -/// input adapter for buffer input -class input_buffer_adapter : public input_adapter_protocol -{ - public: - input_buffer_adapter(const char* b, const std::size_t l) - : cursor(b), limit(b + l), start(b) - { - // skip byte order mark - if (l >= 3 and b[0] == '\xEF' and b[1] == '\xBB' and b[2] == '\xBF') - { - cursor += 3; - } - } - - // delete because of pointer members - input_buffer_adapter(const input_buffer_adapter&) = delete; - input_buffer_adapter& operator=(input_buffer_adapter&) = delete; - - std::char_traits<char>::int_type get_character() noexcept override - { - if (JSON_LIKELY(cursor < limit)) - { - return std::char_traits<char>::to_int_type(*(cursor++)); - } - - return std::char_traits<char>::eof(); - } - - void unget_character() noexcept override - { - if (JSON_LIKELY(cursor > start)) - { - --cursor; - } - } - - private: - /// pointer to the current character - const char* cursor; - /// pointer past the last character - const char* limit; - /// pointer to the first character - const char* start; -}; - -class input_adapter -{ - public: - // native support - - /// input adapter for input stream - input_adapter(std::istream& i) - : ia(std::make_shared<input_stream_adapter>(i)) {} - - /// input adapter for input stream - input_adapter(std::istream&& i) - : ia(std::make_shared<input_stream_adapter>(i)) {} - - /// input adapter for buffer - template<typename CharT, - typename std::enable_if< - std::is_pointer<CharT>::value and - std::is_integral< - typename std::remove_pointer<CharT>::type>::value and - sizeof(typename std::remove_pointer<CharT>::type) == 1, - int>::type = 0> - input_adapter(CharT b, std::size_t l) - : ia(std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(b), l)) {} - - // derived support - - /// input adapter for string literal - template<typename CharT, - typename std::enable_if< - std::is_pointer<CharT>::value and - std::is_integral< - typename std::remove_pointer<CharT>::type>::value and - sizeof(typename std::remove_pointer<CharT>::type) == 1, - int>::type = 0> - input_adapter(CharT b) - : input_adapter(reinterpret_cast<const char*>(b), - std::strlen(reinterpret_cast<const char*>(b))) {} - - /// input adapter for iterator range with contiguous storage - template<class IteratorType, - typename std::enable_if< - std::is_same<typename std::iterator_traits<IteratorType>::iterator_category, - std::random_access_iterator_tag>::value, - int>::type = 0> - input_adapter(IteratorType first, IteratorType last) - { - // assertion to check that the iterator range is indeed contiguous, - // see http://stackoverflow.com/a/35008842/266378 for more discussion - assert(std::accumulate( - first, last, std::pair<bool, int>(true, 0), - [&first](std::pair<bool, int> res, decltype(*first) val) - { - res.first &= (val == *(std::next(std::addressof(*first), res.second++))); - return res; - }).first); - - // assertion to check that each element is 1 byte long - static_assert( - sizeof(typename std::iterator_traits<IteratorType>::value_type) == 1, - "each element in the iterator range must have the size of 1 byte"); - - const auto len = static_cast<size_t>(std::distance(first, last)); - if (JSON_LIKELY(len > 0)) - { - // there is at least one element: use the address of first - ia = std::make_shared<input_buffer_adapter>(reinterpret_cast<const char*>(&(*first)), len); - } - else - { - // the address of first cannot be used: use nullptr - ia = std::make_shared<input_buffer_adapter>(nullptr, len); - } - } - - /// input adapter for array - template<class T, std::size_t N> - input_adapter(T (&array)[N]) - : input_adapter(std::begin(array), std::end(array)) {} - - /// input adapter for contiguous container - template < - class ContiguousContainer, - typename std::enable_if < - not std::is_pointer<ContiguousContainer>::value and - std::is_base_of<std::random_access_iterator_tag, - typename std::iterator_traits<decltype(std::begin(std::declval<ContiguousContainer const>()))>::iterator_category>::value, - int >::type = 0 > - input_adapter(const ContiguousContainer& c) - : input_adapter(std::begin(c), std::end(c)) {} - - operator input_adapter_t() - { - return ia; - } - - private: - /// the actual adapter - input_adapter_t ia = nullptr; -}; - -////////////////////// -// lexer and parser // -////////////////////// - -/*! -@brief lexical analysis - -This class organizes the lexical analysis during JSON deserialization. -*/ -template<typename BasicJsonType> -class lexer -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - - public: - /// token types for the parser - enum class token_type - { - uninitialized, ///< indicating the scanner is uninitialized - literal_true, ///< the `true` literal - literal_false, ///< the `false` literal - literal_null, ///< the `null` literal - value_string, ///< a string -- use get_string() for actual value - value_unsigned, ///< an unsigned integer -- use get_number_unsigned() for actual value - value_integer, ///< a signed integer -- use get_number_integer() for actual value - value_float, ///< an floating point number -- use get_number_float() for actual value - begin_array, ///< the character for array begin `[` - begin_object, ///< the character for object begin `{` - end_array, ///< the character for array end `]` - end_object, ///< the character for object end `}` - name_separator, ///< the name separator `:` - value_separator, ///< the value separator `,` - parse_error, ///< indicating a parse error - end_of_input, ///< indicating the end of the input buffer - literal_or_value ///< a literal or the begin of a value (only for diagnostics) - }; - - /// return name of values of type token_type (only used for errors) - static const char* token_type_name(const token_type t) noexcept - { - switch (t) - { - case token_type::uninitialized: - return "<uninitialized>"; - case token_type::literal_true: - return "true literal"; - case token_type::literal_false: - return "false literal"; - case token_type::literal_null: - return "null literal"; - case token_type::value_string: - return "string literal"; - case lexer::token_type::value_unsigned: - case lexer::token_type::value_integer: - case lexer::token_type::value_float: - return "number literal"; - case token_type::begin_array: - return "'['"; - case token_type::begin_object: - return "'{'"; - case token_type::end_array: - return "']'"; - case token_type::end_object: - return "'}'"; - case token_type::name_separator: - return "':'"; - case token_type::value_separator: - return "','"; - case token_type::parse_error: - return "<parse error>"; - case token_type::end_of_input: - return "end of input"; - case token_type::literal_or_value: - return "'[', '{', or a literal"; - default: // catch non-enum values - return "unknown token"; // LCOV_EXCL_LINE - } - } - - explicit lexer(detail::input_adapter_t adapter) - : ia(std::move(adapter)), decimal_point_char(get_decimal_point()) {} - - // delete because of pointer members - lexer(const lexer&) = delete; - lexer& operator=(lexer&) = delete; - - private: - ///////////////////// - // locales - ///////////////////// - - /// return the locale-dependent decimal point - static char get_decimal_point() noexcept - { - const auto loc = localeconv(); - assert(loc != nullptr); - return (loc->decimal_point == nullptr) ? '.' : loc->decimal_point[0]; - } - - ///////////////////// - // scan functions - ///////////////////// - - /*! - @brief get codepoint from 4 hex characters following `\u` - - For input "\u c1 c2 c3 c4" the codepoint is: - (c1 * 0x1000) + (c2 * 0x0100) + (c3 * 0x0010) + c4 - = (c1 << 12) + (c2 << 8) + (c3 << 4) + (c4 << 0) - - Furthermore, the possible characters '0'..'9', 'A'..'F', and 'a'..'f' - must be converted to the integers 0x0..0x9, 0xA..0xF, 0xA..0xF, resp. The - conversion is done by subtracting the offset (0x30, 0x37, and 0x57) - between the ASCII value of the character and the desired integer value. - - @return codepoint (0x0000..0xFFFF) or -1 in case of an error (e.g. EOF or - non-hex character) - */ - int get_codepoint() - { - // this function only makes sense after reading `\u` - assert(current == 'u'); - int codepoint = 0; - - const auto factors = { 12, 8, 4, 0 }; - for (const auto factor : factors) - { - get(); - - if (current >= '0' and current <= '9') - { - codepoint += ((current - 0x30) << factor); - } - else if (current >= 'A' and current <= 'F') - { - codepoint += ((current - 0x37) << factor); - } - else if (current >= 'a' and current <= 'f') - { - codepoint += ((current - 0x57) << factor); - } - else - { - return -1; - } - } - - assert(0x0000 <= codepoint and codepoint <= 0xFFFF); - return codepoint; - } - - /*! - @brief check if the next byte(s) are inside a given range - - Adds the current byte and, for each passed range, reads a new byte and - checks if it is inside the range. If a violation was detected, set up an - error message and return false. Otherwise, return true. - - @return true if and only if no range violation was detected - */ - bool next_byte_in_range(std::initializer_list<int> ranges) - { - assert(ranges.size() == 2 or ranges.size() == 4 or ranges.size() == 6); - add(current); - - for (auto range = ranges.begin(); range != ranges.end(); ++range) - { - get(); - if (JSON_LIKELY(*range <= current and current <= *(++range))) - { - add(current); - } - else - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return false; - } - } - - return true; - } - - /*! - @brief scan a string literal - - This function scans a string according to Sect. 7 of RFC 7159. While - scanning, bytes are escaped and copied into buffer yytext. Then the function - returns successfully, yytext is *not* null-terminated (as it may contain \0 - bytes), and yytext.size() is the number of bytes in the string. - - @return token_type::value_string if string could be successfully scanned, - token_type::parse_error otherwise - - @note In case of errors, variable error_message contains a textual - description. - */ - token_type scan_string() - { - // reset yytext (ignore opening quote) - reset(); - - // we entered the function by reading an open quote - assert(current == '\"'); - - while (true) - { - // get next character - switch (get()) - { - // end of file while parsing string - case std::char_traits<char>::eof(): - { - error_message = "invalid string: missing closing quote"; - return token_type::parse_error; - } - - // closing quote - case '\"': - { - return token_type::value_string; - } - - // escapes - case '\\': - { - switch (get()) - { - // quotation mark - case '\"': - add('\"'); - break; - // reverse solidus - case '\\': - add('\\'); - break; - // solidus - case '/': - add('/'); - break; - // backspace - case 'b': - add('\b'); - break; - // form feed - case 'f': - add('\f'); - break; - // line feed - case 'n': - add('\n'); - break; - // carriage return - case 'r': - add('\r'); - break; - // tab - case 't': - add('\t'); - break; - - // unicode escapes - case 'u': - { - int codepoint; - const int codepoint1 = get_codepoint(); - - if (JSON_UNLIKELY(codepoint1 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } - - // check if code point is a high surrogate - if (0xD800 <= codepoint1 and codepoint1 <= 0xDBFF) - { - // expect next \uxxxx entry - if (JSON_LIKELY(get() == '\\' and get() == 'u')) - { - const int codepoint2 = get_codepoint(); - - if (JSON_UNLIKELY(codepoint2 == -1)) - { - error_message = "invalid string: '\\u' must be followed by 4 hex digits"; - return token_type::parse_error; - } - - // check if codepoint2 is a low surrogate - if (JSON_LIKELY(0xDC00 <= codepoint2 and codepoint2 <= 0xDFFF)) - { - codepoint = - // high surrogate occupies the most significant 22 bits - (codepoint1 << 10) - // low surrogate occupies the least significant 15 bits - + codepoint2 - // there is still the 0xD800, 0xDC00 and 0x10000 noise - // in the result so we have to subtract with: - // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - - 0x35FDC00; - } - else - { - error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - error_message = "invalid string: surrogate U+DC00..U+DFFF must be followed by U+DC00..U+DFFF"; - return token_type::parse_error; - } - } - else - { - if (JSON_UNLIKELY(0xDC00 <= codepoint1 and codepoint1 <= 0xDFFF)) - { - error_message = "invalid string: surrogate U+DC00..U+DFFF must follow U+D800..U+DBFF"; - return token_type::parse_error; - } - - // only work with first code point - codepoint = codepoint1; - } - - // result of the above calculation yields a proper codepoint - assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - - // translate code point to bytes - if (codepoint < 0x80) - { - // 1-byte characters: 0xxxxxxx (ASCII) - add(codepoint); - } - else if (codepoint <= 0x7ff) - { - // 2-byte characters: 110xxxxx 10xxxxxx - add(0xC0 | (codepoint >> 6)); - add(0x80 | (codepoint & 0x3F)); - } - else if (codepoint <= 0xffff) - { - // 3-byte characters: 1110xxxx 10xxxxxx 10xxxxxx - add(0xE0 | (codepoint >> 12)); - add(0x80 | ((codepoint >> 6) & 0x3F)); - add(0x80 | (codepoint & 0x3F)); - } - else - { - // 4-byte characters: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - add(0xF0 | (codepoint >> 18)); - add(0x80 | ((codepoint >> 12) & 0x3F)); - add(0x80 | ((codepoint >> 6) & 0x3F)); - add(0x80 | (codepoint & 0x3F)); - } - - break; - } - - // other characters after escape - default: - error_message = "invalid string: forbidden character after backslash"; - return token_type::parse_error; - } - - break; - } - - // invalid control characters - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: - { - error_message = "invalid string: control character must be escaped"; - return token_type::parse_error; - } - - // U+0020..U+007F (except U+0022 (quote) and U+005C (backspace)) - case 0x20: - case 0x21: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5a: - case 0x5b: - case 0x5d: - case 0x5e: - case 0x5f: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7a: - case 0x7b: - case 0x7c: - case 0x7d: - case 0x7e: - case 0x7f: - { - add(current); - break; - } - - // U+0080..U+07FF: bytes C2..DF 80..BF - case 0xc2: - case 0xc3: - case 0xc4: - case 0xc5: - case 0xc6: - case 0xc7: - case 0xc8: - case 0xc9: - case 0xca: - case 0xcb: - case 0xcc: - case 0xcd: - case 0xce: - case 0xcf: - case 0xd0: - case 0xd1: - case 0xd2: - case 0xd3: - case 0xd4: - case 0xd5: - case 0xd6: - case 0xd7: - case 0xd8: - case 0xd9: - case 0xda: - case 0xdb: - case 0xdc: - case 0xdd: - case 0xde: - case 0xdf: - { - if (JSON_UNLIKELY(not next_byte_in_range({0x80, 0xBF}))) - { - return token_type::parse_error; - } - break; - } - - // U+0800..U+0FFF: bytes E0 A0..BF 80..BF - case 0xe0: - { - if (JSON_UNLIKELY(not (next_byte_in_range({0xA0, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+1000..U+CFFF: bytes E1..EC 80..BF 80..BF - // U+E000..U+FFFF: bytes EE..EF 80..BF 80..BF - case 0xe1: - case 0xe2: - case 0xe3: - case 0xe4: - case 0xe5: - case 0xe6: - case 0xe7: - case 0xe8: - case 0xe9: - case 0xea: - case 0xeb: - case 0xec: - case 0xee: - case 0xef: - { - if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+D000..U+D7FF: bytes ED 80..9F 80..BF - case 0xed: - { - if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x9F, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+10000..U+3FFFF F0 90..BF 80..BF 80..BF - case 0xf0: - { - if (JSON_UNLIKELY(not (next_byte_in_range({0x90, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+40000..U+FFFFF F1..F3 80..BF 80..BF 80..BF - case 0xf1: - case 0xf2: - case 0xf3: - { - if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0xBF, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // U+100000..U+10FFFF F4 80..8F 80..BF 80..BF - case 0xf4: - { - if (JSON_UNLIKELY(not (next_byte_in_range({0x80, 0x8F, 0x80, 0xBF, 0x80, 0xBF})))) - { - return token_type::parse_error; - } - break; - } - - // remaining bytes (80..C1 and F5..FF) are ill-formed - default: - { - error_message = "invalid string: ill-formed UTF-8 byte"; - return token_type::parse_error; - } - } - } - } - - static void strtof(float& f, const char* str, char** endptr) noexcept - { - f = std::strtof(str, endptr); - } - - static void strtof(double& f, const char* str, char** endptr) noexcept - { - f = std::strtod(str, endptr); - } - - static void strtof(long double& f, const char* str, char** endptr) noexcept - { - f = std::strtold(str, endptr); - } - - /*! - @brief scan a number literal - - This function scans a string according to Sect. 6 of RFC 7159. - - The function is realized with a deterministic finite state machine derived - from the grammar described in RFC 7159. Starting in state "init", the - input is read and used to determined the next state. Only state "done" - accepts the number. State "error" is a trap state to model errors. In the - table below, "anything" means any character but the ones listed before. - - state | 0 | 1-9 | e E | + | - | . | anything - ---------|----------|----------|----------|---------|---------|----------|----------- - init | zero | any1 | [error] | [error] | minus | [error] | [error] - minus | zero | any1 | [error] | [error] | [error] | [error] | [error] - zero | done | done | exponent | done | done | decimal1 | done - any1 | any1 | any1 | exponent | done | done | decimal1 | done - decimal1 | decimal2 | [error] | [error] | [error] | [error] | [error] | [error] - decimal2 | decimal2 | decimal2 | exponent | done | done | done | done - exponent | any2 | any2 | [error] | sign | sign | [error] | [error] - sign | any2 | any2 | [error] | [error] | [error] | [error] | [error] - any2 | any2 | any2 | done | done | done | done | done - - The state machine is realized with one label per state (prefixed with - "scan_number_") and `goto` statements between them. The state machine - contains cycles, but any cycle can be left when EOF is read. Therefore, - the function is guaranteed to terminate. - - During scanning, the read bytes are stored in yytext. This string is - then converted to a signed integer, an unsigned integer, or a - floating-point number. - - @return token_type::value_unsigned, token_type::value_integer, or - token_type::value_float if number could be successfully scanned, - token_type::parse_error otherwise - - @note The scanner is independent of the current locale. Internally, the - locale's decimal point is used instead of `.` to work with the - locale-dependent converters. - */ - token_type scan_number() - { - // reset yytext to store the number's bytes - reset(); - - // the type of the parsed number; initially set to unsigned; will be - // changed if minus sign, decimal point or exponent is read - token_type number_type = token_type::value_unsigned; - - // state (init): we just found out we need to scan a number - switch (current) - { - case '-': - { - add(current); - goto scan_number_minus; - } - - case '0': - { - add(current); - goto scan_number_zero; - } - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } - - default: - { - // all other characters are rejected outside scan_number() - assert(false); // LCOV_EXCL_LINE - } - } - -scan_number_minus: - // state: we just parsed a leading minus sign - number_type = token_type::value_integer; - switch (get()) - { - case '0': - { - add(current); - goto scan_number_zero; - } - - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } - - default: - { - error_message = "invalid number; expected digit after '-'"; - return token_type::parse_error; - } - } - -scan_number_zero: - // state: we just parse a zero (maybe with a leading minus sign) - switch (get()) - { - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } - - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } - - default: - goto scan_number_done; - } - -scan_number_any1: - // state: we just parsed a number 0-9 (maybe with a leading minus sign) - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any1; - } - - case '.': - { - add(decimal_point_char); - goto scan_number_decimal1; - } - - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } - - default: - goto scan_number_done; - } - -scan_number_decimal1: - // state: we just parsed a decimal point - number_type = token_type::value_float; - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } - - default: - { - error_message = "invalid number; expected digit after '.'"; - return token_type::parse_error; - } - } - -scan_number_decimal2: - // we just parsed at least one number after a decimal point - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_decimal2; - } - - case 'e': - case 'E': - { - add(current); - goto scan_number_exponent; - } - - default: - goto scan_number_done; - } - -scan_number_exponent: - // we just parsed an exponent - number_type = token_type::value_float; - switch (get()) - { - case '+': - case '-': - { - add(current); - goto scan_number_sign; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } - - default: - { - error_message = - "invalid number; expected '+', '-', or digit after exponent"; - return token_type::parse_error; - } - } - -scan_number_sign: - // we just parsed an exponent sign - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } - - default: - { - error_message = "invalid number; expected digit after exponent sign"; - return token_type::parse_error; - } - } - -scan_number_any2: - // we just parsed a number after the exponent or exponent sign - switch (get()) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - { - add(current); - goto scan_number_any2; - } - - default: - goto scan_number_done; - } - -scan_number_done: - // unget the character after the number (we only read it to know that - // we are done scanning a number) - unget(); - - char* endptr = nullptr; - errno = 0; - - // try to parse integers first and fall back to floats - if (number_type == token_type::value_unsigned) - { - const auto x = std::strtoull(yytext.data(), &endptr, 10); - - // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); - - if (errno == 0) - { - value_unsigned = static_cast<number_unsigned_t>(x); - if (value_unsigned == x) - { - return token_type::value_unsigned; - } - } - } - else if (number_type == token_type::value_integer) - { - const auto x = std::strtoll(yytext.data(), &endptr, 10); - - // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); - - if (errno == 0) - { - value_integer = static_cast<number_integer_t>(x); - if (value_integer == x) - { - return token_type::value_integer; - } - } - } - - // this code is reached if we parse a floating-point number or if an - // integer conversion above failed - strtof(value_float, yytext.data(), &endptr); - - // we checked the number format before - assert(endptr == yytext.data() + yytext.size()); - - return token_type::value_float; - } - - /*! - @param[in] literal_text the literal text to expect - @param[in] length the length of the passed literal text - @param[in] return_type the token type to return on success - */ - token_type scan_literal(const char* literal_text, const std::size_t length, - token_type return_type) - { - assert(current == literal_text[0]); - for (std::size_t i = 1; i < length; ++i) - { - if (JSON_UNLIKELY(get() != literal_text[i])) - { - error_message = "invalid literal"; - return token_type::parse_error; - } - } - return return_type; - } - - ///////////////////// - // input management - ///////////////////// - - /// reset yytext; current character is beginning of token - void reset() noexcept - { - yytext.clear(); - token_string.clear(); - token_string.push_back(std::char_traits<char>::to_char_type(current)); - } - - /* - @brief get next character from the input - - This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns a - `std::char_traits<char>::eof()` in that case. Stores the scanned characters - for use in error messages. - - @return character read from the input - */ - std::char_traits<char>::int_type get() - { - ++chars_read; - current = ia->get_character(); - if (JSON_LIKELY(current != std::char_traits<char>::eof())) - { - token_string.push_back(std::char_traits<char>::to_char_type(current)); - } - return current; - } - - /// unget current character (return it again on next get) - void unget() - { - --chars_read; - if (JSON_LIKELY(current != std::char_traits<char>::eof())) - { - ia->unget_character(); - assert(token_string.size() != 0); - token_string.pop_back(); - } - } - - /// add a character to yytext - void add(int c) - { - yytext.push_back(std::char_traits<char>::to_char_type(c)); - } - - public: - ///////////////////// - // value getters - ///////////////////// - - /// return integer value - constexpr number_integer_t get_number_integer() const noexcept - { - return value_integer; - } - - /// return unsigned integer value - constexpr number_unsigned_t get_number_unsigned() const noexcept - { - return value_unsigned; - } - - /// return floating-point value - constexpr number_float_t get_number_float() const noexcept - { - return value_float; - } - - /// return current string value (implicitly resets the token; useful only once) - std::string move_string() - { - return std::move(yytext); - } - - ///////////////////// - // diagnostics - ///////////////////// - - /// return position of last read token - constexpr std::size_t get_position() const noexcept - { - return chars_read; - } - - /// return the last read token (for errors only). Will never contain EOF - /// (an arbitrary value that is not a valid char value, often -1), because - /// 255 may legitimately occur. May contain NUL, which should be escaped. - std::string get_token_string() const - { - // escape control characters - std::string result; - for (auto c : token_string) - { - if ('\x00' <= c and c <= '\x1f') - { - // escape control characters - std::stringstream ss; - ss << "<U+" << std::setw(4) << std::uppercase << std::setfill('0') - << std::hex << static_cast<int>(c) << ">"; - result += ss.str(); - } - else - { - // add character as is - result.push_back(c); - } - } - - return result; - } - - /// return syntax error message - constexpr const char* get_error_message() const noexcept - { - return error_message; - } - - ///////////////////// - // actual scanner - ///////////////////// - - token_type scan() - { - // read next character and ignore whitespace - do - { - get(); - } - while (current == ' ' or current == '\t' or current == '\n' or current == '\r'); - - switch (current) - { - // structural characters - case '[': - return token_type::begin_array; - case ']': - return token_type::end_array; - case '{': - return token_type::begin_object; - case '}': - return token_type::end_object; - case ':': - return token_type::name_separator; - case ',': - return token_type::value_separator; - - // literals - case 't': - return scan_literal("true", 4, token_type::literal_true); - case 'f': - return scan_literal("false", 5, token_type::literal_false); - case 'n': - return scan_literal("null", 4, token_type::literal_null); - - // string - case '\"': - return scan_string(); - - // number - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - return scan_number(); - - // end of input (the null byte is needed when parsing from - // string literals) - case '\0': - case std::char_traits<char>::eof(): - return token_type::end_of_input; - - // error - default: - error_message = "invalid literal"; - return token_type::parse_error; - } - } - - private: - /// input adapter - detail::input_adapter_t ia = nullptr; - - /// the current character - std::char_traits<char>::int_type current = std::char_traits<char>::eof(); - - /// the number of characters read - std::size_t chars_read = 0; - - /// raw input token string (for error messages) - std::vector<char> token_string { }; - - /// buffer for variable-length tokens (numbers, strings) - std::string yytext { }; - - /// a description of occurred lexer errors - const char* error_message = ""; - - // number values - number_integer_t value_integer = 0; - number_unsigned_t value_unsigned = 0; - number_float_t value_float = 0; - - /// the decimal point - const char decimal_point_char = '.'; -}; - -/*! -@brief syntax analysis - -This class implements a recursive decent parser. -*/ -template<typename BasicJsonType> -class parser -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - using number_float_t = typename BasicJsonType::number_float_t; - using lexer_t = lexer<BasicJsonType>; - using token_type = typename lexer_t::token_type; - - public: - enum class parse_event_t : uint8_t - { - /// the parser read `{` and started to process a JSON object - object_start, - /// the parser read `}` and finished processing a JSON object - object_end, - /// the parser read `[` and started to process a JSON array - array_start, - /// the parser read `]` and finished processing a JSON array - array_end, - /// the parser read a key of a value in an object - key, - /// the parser finished reading a JSON value - value - }; - - using parser_callback_t = - std::function<bool(int depth, parse_event_t event, BasicJsonType& parsed)>; - - /// a parser reading from an input adapter - explicit parser(detail::input_adapter_t adapter, - const parser_callback_t cb = nullptr, - const bool allow_exceptions_ = true) - : callback(cb), m_lexer(adapter), allow_exceptions(allow_exceptions_) - {} - - /*! - @brief public parser interface - - @param[in] strict whether to expect the last token to be EOF - @param[in,out] result parsed JSON value - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - */ - void parse(const bool strict, BasicJsonType& result) - { - // read first token - get_token(); - - parse_internal(true, result); - result.assert_invariant(); - - // in strict mode, input must be completely read - if (strict) - { - get_token(); - expect(token_type::end_of_input); - } - - // in case of an error, return discarded value - if (errored) - { - result = value_t::discarded; - return; - } - - // set top-level value to null if it was discarded by the callback - // function - if (result.is_discarded()) - { - result = nullptr; - } - } - - /*! - @brief public accept interface - - @param[in] strict whether to expect the last token to be EOF - @return whether the input is a proper JSON text - */ - bool accept(const bool strict = true) - { - // read first token - get_token(); - - if (not accept_internal()) - { - return false; - } - - // strict => last token must be EOF - return not strict or (get_token() == token_type::end_of_input); - } - - private: - /*! - @brief the actual parser - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - */ - void parse_internal(bool keep, BasicJsonType& result) - { - // never parse after a parse error was detected - assert(not errored); - - // start with a discarded value - if (not result.is_discarded()) - { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - - switch (last_token) - { - case token_type::begin_object: - { - if (keep) - { - if (callback) - { - keep = callback(depth++, parse_event_t::object_start, result); - } - - if (not callback or keep) - { - // explicitly set result to object to cope with {} - result.m_type = value_t::object; - result.m_value = value_t::object; - } - } - - // read next token - get_token(); - - // closing } -> we are done - if (last_token == token_type::end_object) - { - if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) - { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - break; - } - - // parse values - std::string key; - BasicJsonType value; - while (true) - { - // store key - if (not expect(token_type::value_string)) - { - return; - } - key = m_lexer.move_string(); - - bool keep_tag = false; - if (keep) - { - if (callback) - { - BasicJsonType k(key); - keep_tag = callback(depth, parse_event_t::key, k); - } - else - { - keep_tag = true; - } - } - - // parse separator (:) - get_token(); - if (not expect(token_type::name_separator)) - { - return; - } - - // parse and add value - get_token(); - value.m_value.destroy(value.m_type); - value.m_type = value_t::discarded; - parse_internal(keep, value); - - if (JSON_UNLIKELY(errored)) - { - return; - } - - if (keep and keep_tag and not value.is_discarded()) - { - result.m_value.object->emplace(std::move(key), std::move(value)); - } - - // comma -> next value - get_token(); - if (last_token == token_type::value_separator) - { - get_token(); - continue; - } - - // closing } - if (not expect(token_type::end_object)) - { - return; - } - break; - } - - if (keep and callback and not callback(--depth, parse_event_t::object_end, result)) - { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - break; - } - - case token_type::begin_array: - { - if (keep) - { - if (callback) - { - keep = callback(depth++, parse_event_t::array_start, result); - } - - if (not callback or keep) - { - // explicitly set result to array to cope with [] - result.m_type = value_t::array; - result.m_value = value_t::array; - } - } - - // read next token - get_token(); - - // closing ] -> we are done - if (last_token == token_type::end_array) - { - if (callback and not callback(--depth, parse_event_t::array_end, result)) - { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - break; - } - - // parse values - BasicJsonType value; - while (true) - { - // parse value - value.m_value.destroy(value.m_type); - value.m_type = value_t::discarded; - parse_internal(keep, value); - - if (JSON_UNLIKELY(errored)) - { - return; - } - - if (keep and not value.is_discarded()) - { - result.m_value.array->push_back(std::move(value)); - } - - // comma -> next value - get_token(); - if (last_token == token_type::value_separator) - { - get_token(); - continue; - } - - // closing ] - if (not expect(token_type::end_array)) - { - return; - } - break; - } - - if (keep and callback and not callback(--depth, parse_event_t::array_end, result)) - { - result.m_value.destroy(result.m_type); - result.m_type = value_t::discarded; - } - break; - } - - case token_type::literal_null: - { - result.m_type = value_t::null; - break; - } - - case token_type::value_string: - { - result.m_type = value_t::string; - result.m_value = m_lexer.move_string(); - break; - } - - case token_type::literal_true: - { - result.m_type = value_t::boolean; - result.m_value = true; - break; - } - - case token_type::literal_false: - { - result.m_type = value_t::boolean; - result.m_value = false; - break; - } - - case token_type::value_unsigned: - { - result.m_type = value_t::number_unsigned; - result.m_value = m_lexer.get_number_unsigned(); - break; - } - - case token_type::value_integer: - { - result.m_type = value_t::number_integer; - result.m_value = m_lexer.get_number_integer(); - break; - } - - case token_type::value_float: - { - result.m_type = value_t::number_float; - result.m_value = m_lexer.get_number_float(); - - // throw in case of infinity or NAN - if (JSON_UNLIKELY(not std::isfinite(result.m_value.number_float))) - { - if (allow_exceptions) - { - JSON_THROW(out_of_range::create(406, "number overflow parsing '" + - m_lexer.get_token_string() + "'")); - } - expect(token_type::uninitialized); - } - break; - } - - case token_type::parse_error: - { - // using "uninitialized" to avoid "expected" message - if (not expect(token_type::uninitialized)) - { - return; - } - break; // LCOV_EXCL_LINE - } - - default: - { - // the last token was unexpected; we expected a value - if (not expect(token_type::literal_or_value)) - { - return; - } - break; // LCOV_EXCL_LINE - } - } - - if (keep and callback and not callback(depth, parse_event_t::value, result)) - { - result.m_type = value_t::discarded; - } - } - - /*! - @brief the acutal acceptor - - @invariant 1. The last token is not yet processed. Therefore, the caller - of this function must make sure a token has been read. - 2. When this function returns, the last token is processed. - That is, the last read character was already considered. - - This invariant makes sure that no token needs to be "unput". - */ - bool accept_internal() - { - switch (last_token) - { - case token_type::begin_object: - { - // read next token - get_token(); - - // closing } -> we are done - if (last_token == token_type::end_object) - { - return true; - } - - // parse values - while (true) - { - // parse key - if (last_token != token_type::value_string) - { - return false; - } - - // parse separator (:) - get_token(); - if (last_token != token_type::name_separator) - { - return false; - } - - // parse value - get_token(); - if (not accept_internal()) - { - return false; - } - - // comma -> next value - get_token(); - if (last_token == token_type::value_separator) - { - get_token(); - continue; - } - - // closing } - return (last_token == token_type::end_object); - } - } - - case token_type::begin_array: - { - // read next token - get_token(); - - // closing ] -> we are done - if (last_token == token_type::end_array) - { - return true; - } - - // parse values - while (true) - { - // parse value - if (not accept_internal()) - { - return false; - } - - // comma -> next value - get_token(); - if (last_token == token_type::value_separator) - { - get_token(); - continue; - } - - // closing ] - return (last_token == token_type::end_array); - } - } - - case token_type::value_float: - { - // reject infinity or NAN - return std::isfinite(m_lexer.get_number_float()); - } - - case token_type::literal_false: - case token_type::literal_null: - case token_type::literal_true: - case token_type::value_integer: - case token_type::value_string: - case token_type::value_unsigned: - return true; - - default: // the last token was unexpected - return false; - } - } - - /// get next token from lexer - token_type get_token() - { - return (last_token = m_lexer.scan()); - } - - /*! - @throw parse_error.101 if expected token did not occur - */ - bool expect(token_type t) - { - if (JSON_UNLIKELY(t != last_token)) - { - errored = true; - expected = t; - if (allow_exceptions) - { - throw_exception(); - } - else - { - return false; - } - } - - return true; - } - - [[noreturn]] void throw_exception() const - { - std::string error_msg = "syntax error - "; - if (last_token == token_type::parse_error) - { - error_msg += std::string(m_lexer.get_error_message()) + "; last read: '" + - m_lexer.get_token_string() + "'"; - } - else - { - error_msg += "unexpected " + std::string(lexer_t::token_type_name(last_token)); - } - - if (expected != token_type::uninitialized) - { - error_msg += "; expected " + std::string(lexer_t::token_type_name(expected)); - } - - JSON_THROW(parse_error::create(101, m_lexer.get_position(), error_msg)); - } - - private: - /// current level of recursion - int depth = 0; - /// callback function - const parser_callback_t callback = nullptr; - /// the type of the last read token - token_type last_token = token_type::uninitialized; - /// the lexer - lexer_t m_lexer; - /// whether a syntax error occurred - bool errored = false; - /// possible reason for the syntax error - token_type expected = token_type::uninitialized; - /// whether to throw exceptions in case of errors - const bool allow_exceptions = true; -}; - -/////////////// -// iterators // -/////////////// - -/*! -@brief an iterator for primitive JSON types - -This class models an iterator for primitive JSON types (boolean, number, -string). It's only purpose is to allow the iterator/const_iterator classes -to "iterate" over primitive values. Internally, the iterator is modeled by -a `difference_type` variable. Value begin_value (`0`) models the begin, -end_value (`1`) models past the end. -*/ -class primitive_iterator_t -{ - public: - using difference_type = std::ptrdiff_t; - - constexpr difference_type get_value() const noexcept - { - return m_it; - } - - /// set iterator to a defined beginning - void set_begin() noexcept - { - m_it = begin_value; - } - - /// set iterator to a defined past the end - void set_end() noexcept - { - m_it = end_value; - } - - /// return whether the iterator can be dereferenced - constexpr bool is_begin() const noexcept - { - return m_it == begin_value; - } - - /// return whether the iterator is at end - constexpr bool is_end() const noexcept - { - return m_it == end_value; - } - - friend constexpr bool operator==(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it == rhs.m_it; - } - - friend constexpr bool operator<(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it < rhs.m_it; - } - - primitive_iterator_t operator+(difference_type i) - { - auto result = *this; - result += i; - return result; - } - - friend constexpr difference_type operator-(primitive_iterator_t lhs, primitive_iterator_t rhs) noexcept - { - return lhs.m_it - rhs.m_it; - } - - friend std::ostream& operator<<(std::ostream& os, primitive_iterator_t it) - { - return os << it.m_it; - } - - primitive_iterator_t& operator++() - { - ++m_it; - return *this; - } - - primitive_iterator_t operator++(int) - { - auto result = *this; - m_it++; - return result; - } - - primitive_iterator_t& operator--() - { - --m_it; - return *this; - } - - primitive_iterator_t operator--(int) - { - auto result = *this; - m_it--; - return result; - } - - primitive_iterator_t& operator+=(difference_type n) - { - m_it += n; - return *this; - } - - primitive_iterator_t& operator-=(difference_type n) - { - m_it -= n; - return *this; - } - - private: - static constexpr difference_type begin_value = 0; - static constexpr difference_type end_value = begin_value + 1; - - /// iterator as signed integer type - difference_type m_it = (std::numeric_limits<std::ptrdiff_t>::min)(); -}; - -/*! -@brief an iterator value - -@note This structure could easily be a union, but MSVC currently does not allow -unions members with complex constructors, see https://github.com/nlohmann/json/pull/105. -*/ -template<typename BasicJsonType> struct internal_iterator -{ - /// iterator for JSON objects - typename BasicJsonType::object_t::iterator object_iterator {}; - /// iterator for JSON arrays - typename BasicJsonType::array_t::iterator array_iterator {}; - /// generic iterator for all other types - primitive_iterator_t primitive_iterator {}; -}; - -template<typename IteratorType> class iteration_proxy; - -/*! -@brief a template for a bidirectional iterator for the @ref basic_json class - -This class implements a both iterators (iterator and const_iterator) for the -@ref basic_json class. - -@note An iterator is called *initialized* when a pointer to a JSON value has - been set (e.g., by a constructor or a copy assignment). If the iterator is - default-constructed, it is *uninitialized* and most methods are undefined. - **The library uses assertions to detect calls on uninitialized iterators.** - -@requirement The class satisfies the following concept requirements: -- -[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): - The iterator that can be moved can be moved in both directions (i.e. - incremented and decremented). - -@since version 1.0.0, simplified in version 2.0.9, change to bidirectional - iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593) -*/ -template<typename BasicJsonType> -class iter_impl : public std::iterator<std::bidirectional_iterator_tag, BasicJsonType> -{ - /// allow basic_json to access private members - friend iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>; - friend BasicJsonType; - friend iteration_proxy<iter_impl>; - - using object_t = typename BasicJsonType::object_t; - using array_t = typename BasicJsonType::array_t; - // make sure BasicJsonType is basic_json or const basic_json - static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value, - "iter_impl only accepts (const) basic_json"); - - public: - /// the type of the values when the iterator is dereferenced - using value_type = typename BasicJsonType::value_type; - /// a type to represent differences between iterators - using difference_type = typename BasicJsonType::difference_type; - /// defines a pointer to the type iterated over (value_type) - using pointer = typename std::conditional<std::is_const<BasicJsonType>::value, - typename BasicJsonType::const_pointer, - typename BasicJsonType::pointer>::type; - /// defines a reference to the type iterated over (value_type) - using reference = - typename std::conditional<std::is_const<BasicJsonType>::value, - typename BasicJsonType::const_reference, - typename BasicJsonType::reference>::type; - - /// default constructor - iter_impl() = default; - - /*! - @brief constructor for a given JSON instance - @param[in] object pointer to a JSON object for this iterator - @pre object != nullptr - @post The iterator is initialized; i.e. `m_object != nullptr`. - */ - explicit iter_impl(pointer object) noexcept : m_object(object) - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - m_it.object_iterator = typename object_t::iterator(); - break; - } - - case value_t::array: - { - m_it.array_iterator = typename array_t::iterator(); - break; - } - - default: - { - m_it.primitive_iterator = primitive_iterator_t(); - break; - } - } - } - - /*! - @note The conventional copy constructor and copy assignment are implicitly - defined. Combined with the following converting constructor and - assignment, they support: (1) copy from iterator to iterator, (2) - copy from const iterator to const iterator, and (3) conversion from - iterator to const iterator. However conversion from const iterator - to iterator is not defined. - */ - - /*! - @brief converting constructor - @param[in] other non-const iterator to copy from - @note It is not checked whether @a other is initialized. - */ - iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept - : m_object(other.m_object), m_it(other.m_it) {} - - /*! - @brief converting assignment - @param[in,out] other non-const iterator to copy from - @return const/non-const iterator - @note It is not checked whether @a other is initialized. - */ - iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept - { - m_object = other.m_object; - m_it = other.m_it; - return *this; - } - - private: - /*! - @brief set the iterator to the first value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_begin() noexcept - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - m_it.object_iterator = m_object->m_value.object->begin(); - break; - } - - case value_t::array: - { - m_it.array_iterator = m_object->m_value.array->begin(); - break; - } - - case value_t::null: - { - // set to end so begin()==end() is true: null is empty - m_it.primitive_iterator.set_end(); - break; - } - - default: - { - m_it.primitive_iterator.set_begin(); - break; - } - } - } - - /*! - @brief set the iterator past the last value - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - void set_end() noexcept - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - m_it.object_iterator = m_object->m_value.object->end(); - break; - } - - case value_t::array: - { - m_it.array_iterator = m_object->m_value.array->end(); - break; - } - - default: - { - m_it.primitive_iterator.set_end(); - break; - } - } - } - - public: - /*! - @brief return a reference to the value pointed to by the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator*() const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return m_it.object_iterator->second; - } - - case value_t::array: - { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return *m_it.array_iterator; - } - - case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - - default: - { - if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) - { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief dereference the iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - pointer operator->() const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - assert(m_it.object_iterator != m_object->m_value.object->end()); - return &(m_it.object_iterator->second); - } - - case value_t::array: - { - assert(m_it.array_iterator != m_object->m_value.array->end()); - return &*m_it.array_iterator; - } - - default: - { - if (JSON_LIKELY(m_it.primitive_iterator.is_begin())) - { - return m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief post-increment (it++) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator++(int) - { - auto result = *this; - ++(*this); - return result; - } - - /*! - @brief pre-increment (++it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator++() - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - std::advance(m_it.object_iterator, 1); - break; - } - - case value_t::array: - { - std::advance(m_it.array_iterator, 1); - break; - } - - default: - { - ++m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief post-decrement (it--) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator--(int) - { - auto result = *this; - --(*this); - return result; - } - - /*! - @brief pre-decrement (--it) - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator--() - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - { - std::advance(m_it.object_iterator, -1); - break; - } - - case value_t::array: - { - std::advance(m_it.array_iterator, -1); - break; - } - - default: - { - --m_it.primitive_iterator; - break; - } - } - - return *this; - } - - /*! - @brief comparison: equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator==(const iter_impl& other) const - { - // if objects are not the same, the comparison is undefined - if (JSON_UNLIKELY(m_object != other.m_object)) - { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - return (m_it.object_iterator == other.m_it.object_iterator); - - case value_t::array: - return (m_it.array_iterator == other.m_it.array_iterator); - - default: - return (m_it.primitive_iterator == other.m_it.primitive_iterator); - } - } - - /*! - @brief comparison: not equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator!=(const iter_impl& other) const - { - return not operator==(other); - } - - /*! - @brief comparison: smaller - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<(const iter_impl& other) const - { - // if objects are not the same, the comparison is undefined - if (JSON_UNLIKELY(m_object != other.m_object)) - { - JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers")); - } - - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators")); - - case value_t::array: - return (m_it.array_iterator < other.m_it.array_iterator); - - default: - return (m_it.primitive_iterator < other.m_it.primitive_iterator); - } - } - - /*! - @brief comparison: less than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator<=(const iter_impl& other) const - { - return not other.operator < (*this); - } - - /*! - @brief comparison: greater than - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>(const iter_impl& other) const - { - return not operator<=(other); - } - - /*! - @brief comparison: greater than or equal - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - bool operator>=(const iter_impl& other) const - { - return not operator<(other); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator+=(difference_type i) - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - - case value_t::array: - { - std::advance(m_it.array_iterator, i); - break; - } - - default: - { - m_it.primitive_iterator += i; - break; - } - } - - return *this; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl& operator-=(difference_type i) - { - return operator+=(-i); - } - - /*! - @brief add to iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator+(difference_type i) const - { - auto result = *this; - result += i; - return result; - } - - /*! - @brief addition of distance and iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - friend iter_impl operator+(difference_type i, const iter_impl& it) - { - auto result = it; - result += i; - return result; - } - - /*! - @brief subtract from iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - iter_impl operator-(difference_type i) const - { - auto result = *this; - result -= i; - return result; - } - - /*! - @brief return difference - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - difference_type operator-(const iter_impl& other) const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators")); - - case value_t::array: - return m_it.array_iterator - other.m_it.array_iterator; - - default: - return m_it.primitive_iterator - other.m_it.primitive_iterator; - } - } - - /*! - @brief access to successor - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference operator[](difference_type n) const - { - assert(m_object != nullptr); - - switch (m_object->m_type) - { - case value_t::object: - JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators")); - - case value_t::array: - return *std::next(m_it.array_iterator, n); - - case value_t::null: - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - - default: - { - if (JSON_LIKELY(m_it.primitive_iterator.get_value() == -n)) - { - return *m_object; - } - - JSON_THROW(invalid_iterator::create(214, "cannot get value")); - } - } - } - - /*! - @brief return the key of an object iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - typename object_t::key_type key() const - { - assert(m_object != nullptr); - - if (JSON_LIKELY(m_object->is_object())) - { - return m_it.object_iterator->first; - } - - JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators")); - } - - /*! - @brief return the value of an iterator - @pre The iterator is initialized; i.e. `m_object != nullptr`. - */ - reference value() const - { - return operator*(); - } - - private: - /// associated JSON instance - pointer m_object = nullptr; - /// the actual iterator of the associated instance - internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it = {}; -}; - -/// proxy class for the iterator_wrapper functions -template<typename IteratorType> class iteration_proxy -{ - private: - /// helper class for iteration - class iteration_proxy_internal - { - private: - /// the iterator - IteratorType anchor; - /// an index for arrays (used to create key names) - std::size_t array_index = 0; - - public: - explicit iteration_proxy_internal(IteratorType it) noexcept : anchor(it) {} - - /// dereference operator (needed for range-based for) - iteration_proxy_internal& operator*() - { - return *this; - } - - /// increment operator (needed for range-based for) - iteration_proxy_internal& operator++() - { - ++anchor; - ++array_index; - - return *this; - } - - /// inequality operator (needed for range-based for) - bool operator!=(const iteration_proxy_internal& o) const noexcept - { - return anchor != o.anchor; - } - - /// return key of the iterator - std::string key() const - { - assert(anchor.m_object != nullptr); - - switch (anchor.m_object->type()) - { - // use integer array index as key - case value_t::array: - return std::to_string(array_index); - - // use key from the object - case value_t::object: - return anchor.key(); - - // use an empty key for all primitive types - default: - return ""; - } - } - - /// return value of the iterator - typename IteratorType::reference value() const - { - return anchor.value(); - } - }; - - /// the container to iterate - typename IteratorType::reference container; - - public: - /// construct iteration proxy from a container - explicit iteration_proxy(typename IteratorType::reference cont) - : container(cont) {} - - /// return iterator begin (needed for range-based for) - iteration_proxy_internal begin() noexcept - { - return iteration_proxy_internal(container.begin()); - } - - /// return iterator end (needed for range-based for) - iteration_proxy_internal end() noexcept - { - return iteration_proxy_internal(container.end()); - } -}; - -/*! -@brief a template for a reverse iterator class - -@tparam Base the base iterator type to reverse. Valid types are @ref -iterator (to create @ref reverse_iterator) and @ref const_iterator (to -create @ref const_reverse_iterator). - -@requirement The class satisfies the following concept requirements: -- -[BidirectionalIterator](http://en.cppreference.com/w/cpp/concept/BidirectionalIterator): - The iterator that can be moved can be moved in both directions (i.e. - incremented and decremented). -- [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): - It is possible to write to the pointed-to element (only if @a Base is - @ref iterator). - -@since version 1.0.0 -*/ -template<typename Base> -class json_reverse_iterator : public std::reverse_iterator<Base> -{ - public: - using difference_type = std::ptrdiff_t; - /// shortcut to the reverse iterator adaptor - using base_iterator = std::reverse_iterator<Base>; - /// the reference type for the pointed-to element - using reference = typename Base::reference; - - /// create reverse iterator from iterator - json_reverse_iterator(const typename base_iterator::iterator_type& it) noexcept - : base_iterator(it) {} - - /// create reverse iterator from base class - json_reverse_iterator(const base_iterator& it) noexcept : base_iterator(it) {} - - /// post-increment (it++) - json_reverse_iterator operator++(int) - { - return static_cast<json_reverse_iterator>(base_iterator::operator++(1)); - } - - /// pre-increment (++it) - json_reverse_iterator& operator++() - { - return static_cast<json_reverse_iterator&>(base_iterator::operator++()); - } - - /// post-decrement (it--) - json_reverse_iterator operator--(int) - { - return static_cast<json_reverse_iterator>(base_iterator::operator--(1)); - } - - /// pre-decrement (--it) - json_reverse_iterator& operator--() - { - return static_cast<json_reverse_iterator&>(base_iterator::operator--()); - } - - /// add to iterator - json_reverse_iterator& operator+=(difference_type i) - { - return static_cast<json_reverse_iterator&>(base_iterator::operator+=(i)); - } - - /// add to iterator - json_reverse_iterator operator+(difference_type i) const - { - return static_cast<json_reverse_iterator>(base_iterator::operator+(i)); - } - - /// subtract from iterator - json_reverse_iterator operator-(difference_type i) const - { - return static_cast<json_reverse_iterator>(base_iterator::operator-(i)); - } - - /// return difference - difference_type operator-(const json_reverse_iterator& other) const - { - return base_iterator(*this) - base_iterator(other); - } - - /// access to successor - reference operator[](difference_type n) const - { - return *(this->operator+(n)); - } - - /// return the key of an object iterator - auto key() const -> decltype(std::declval<Base>().key()) - { - auto it = --this->base(); - return it.key(); - } - - /// return the value of an iterator - reference value() const - { - auto it = --this->base(); - return it.operator * (); - } -}; - -///////////////////// -// output adapters // -///////////////////// - -/// abstract output adapter interface -template<typename CharType> struct output_adapter_protocol -{ - virtual void write_character(CharType c) = 0; - virtual void write_characters(const CharType* s, std::size_t length) = 0; - virtual ~output_adapter_protocol() = default; -}; - -/// a type to simplify interfaces -template<typename CharType> -using output_adapter_t = std::shared_ptr<output_adapter_protocol<CharType>>; - -/// output adapter for byte vectors -template<typename CharType> -class output_vector_adapter : public output_adapter_protocol<CharType> -{ - public: - explicit output_vector_adapter(std::vector<CharType>& vec) : v(vec) {} - - void write_character(CharType c) override - { - v.push_back(c); - } - - void write_characters(const CharType* s, std::size_t length) override - { - std::copy(s, s + length, std::back_inserter(v)); - } - - private: - std::vector<CharType>& v; -}; - -/// output adapter for output streams -template<typename CharType> -class output_stream_adapter : public output_adapter_protocol<CharType> -{ - public: - explicit output_stream_adapter(std::basic_ostream<CharType>& s) : stream(s) {} - - void write_character(CharType c) override - { - stream.put(c); - } - - void write_characters(const CharType* s, std::size_t length) override - { - stream.write(s, static_cast<std::streamsize>(length)); - } - - private: - std::basic_ostream<CharType>& stream; -}; - -/// output adapter for basic_string -template<typename CharType> -class output_string_adapter : public output_adapter_protocol<CharType> -{ - public: - explicit output_string_adapter(std::basic_string<CharType>& s) : str(s) {} - - void write_character(CharType c) override - { - str.push_back(c); - } - - void write_characters(const CharType* s, std::size_t length) override - { - str.append(s, length); - } - - private: - std::basic_string<CharType>& str; -}; - -template<typename CharType> -class output_adapter -{ - public: - output_adapter(std::vector<CharType>& vec) - : oa(std::make_shared<output_vector_adapter<CharType>>(vec)) {} - - output_adapter(std::basic_ostream<CharType>& s) - : oa(std::make_shared<output_stream_adapter<CharType>>(s)) {} - - output_adapter(std::basic_string<CharType>& s) - : oa(std::make_shared<output_string_adapter<CharType>>(s)) {} - - operator output_adapter_t<CharType>() - { - return oa; - } - - private: - output_adapter_t<CharType> oa = nullptr; -}; - -////////////////////////////// -// binary reader and writer // -////////////////////////////// - -/*! -@brief deserialization of CBOR and MessagePack values -*/ -template<typename BasicJsonType> -class binary_reader -{ - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - - public: - /*! - @brief create a binary reader - - @param[in] adapter input adapter to read from - */ - explicit binary_reader(input_adapter_t adapter) : ia(std::move(adapter)) - { - assert(ia); - } - - /*! - @brief create a JSON value from CBOR input - - @param[in] strict whether to expect the input to be consumed completed - @return JSON value created from CBOR input - - @throw parse_error.110 if input ended unexpectedly or the end of file was - not reached when @a strict was set to true - @throw parse_error.112 if unsupported byte was read - */ - BasicJsonType parse_cbor(const bool strict) - { - const auto res = parse_cbor_internal(); - if (strict) - { - get(); - check_eof(true); - } - return res; - } - - /*! - @brief create a JSON value from MessagePack input - - @param[in] strict whether to expect the input to be consumed completed - @return JSON value created from MessagePack input - - @throw parse_error.110 if input ended unexpectedly or the end of file was - not reached when @a strict was set to true - @throw parse_error.112 if unsupported byte was read - */ - BasicJsonType parse_msgpack(const bool strict) - { - const auto res = parse_msgpack_internal(); - if (strict) - { - get(); - check_eof(true); - } - return res; - } - - /*! - @brief determine system byte order - - @return true if and only if system's byte order is little endian - - @note from http://stackoverflow.com/a/1001328/266378 - */ - static constexpr bool little_endianess(int num = 1) noexcept - { - return (*reinterpret_cast<char*>(&num) == 1); - } - - private: - /*! - @param[in] get_char whether a new character should be retrieved from the - input (true, default) or whether the last read - character should be considered instead - */ - BasicJsonType parse_cbor_internal(const bool get_char = true) - { - switch (get_char ? get() : current) - { - // EOF - case std::char_traits<char>::eof(): - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); - - // Integer 0x00..0x17 (0..23) - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - return static_cast<number_unsigned_t>(current); - - case 0x18: // Unsigned integer (one-byte uint8_t follows) - return get_number<uint8_t>(); - - case 0x19: // Unsigned integer (two-byte uint16_t follows) - return get_number<uint16_t>(); - - case 0x1a: // Unsigned integer (four-byte uint32_t follows) - return get_number<uint32_t>(); - - case 0x1b: // Unsigned integer (eight-byte uint64_t follows) - return get_number<uint64_t>(); - - // Negative integer -1-0x00..-1-0x17 (-1..-24) - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - return static_cast<int8_t>(0x20 - 1 - current); - - case 0x38: // Negative integer (one-byte uint8_t follows) - { - // must be uint8_t ! - return static_cast<number_integer_t>(-1) - get_number<uint8_t>(); - } - - case 0x39: // Negative integer -1-n (two-byte uint16_t follows) - { - return static_cast<number_integer_t>(-1) - get_number<uint16_t>(); - } - - case 0x3a: // Negative integer -1-n (four-byte uint32_t follows) - { - return static_cast<number_integer_t>(-1) - get_number<uint32_t>(); - } - - case 0x3b: // Negative integer -1-n (eight-byte uint64_t follows) - { - return static_cast<number_integer_t>(-1) - - static_cast<number_integer_t>(get_number<uint64_t>()); - } - - // UTF-8 string (0x00..0x17 bytes follow) - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: // UTF-8 string (one-byte uint8_t for n follows) - case 0x79: // UTF-8 string (two-byte uint16_t for n follow) - case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) - case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) - case 0x7f: // UTF-8 string (indefinite length) - { - return get_cbor_string(); - } - - // array (0x00..0x17 data items follow) - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8a: - case 0x8b: - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - { - return get_cbor_array(current & 0x1f); - } - - case 0x98: // array (one-byte uint8_t for n follows) - { - return get_cbor_array(get_number<uint8_t>()); - } - - case 0x99: // array (two-byte uint16_t for n follow) - { - return get_cbor_array(get_number<uint16_t>()); - } - - case 0x9a: // array (four-byte uint32_t for n follow) - { - return get_cbor_array(get_number<uint32_t>()); - } - - case 0x9b: // array (eight-byte uint64_t for n follow) - { - return get_cbor_array(get_number<uint64_t>()); - } - - case 0x9f: // array (indefinite length) - { - BasicJsonType result = value_t::array; - while (get() != 0xff) - { - result.push_back(parse_cbor_internal(false)); - } - return result; - } - - // map (0x00..0x17 pairs of data items follow) - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - { - return get_cbor_object(current & 0x1f); - } - - case 0xb8: // map (one-byte uint8_t for n follows) - { - return get_cbor_object(get_number<uint8_t>()); - } - - case 0xb9: // map (two-byte uint16_t for n follow) - { - return get_cbor_object(get_number<uint16_t>()); - } - - case 0xba: // map (four-byte uint32_t for n follow) - { - return get_cbor_object(get_number<uint32_t>()); - } - - case 0xbb: // map (eight-byte uint64_t for n follow) - { - return get_cbor_object(get_number<uint64_t>()); - } - - case 0xbf: // map (indefinite length) - { - BasicJsonType result = value_t::object; - while (get() != 0xff) - { - auto key = get_cbor_string(); - result[key] = parse_cbor_internal(); - } - return result; - } - - case 0xf4: // false - { - return false; - } - - case 0xf5: // true - { - return true; - } - - case 0xf6: // null - { - return value_t::null; - } - - case 0xf9: // Half-Precision Float (two-byte IEEE 754) - { - const int byte1 = get(); - check_eof(); - const int byte2 = get(); - check_eof(); - - // code from RFC 7049, Appendix D, Figure 3: - // As half-precision floating-point numbers were only added - // to IEEE 754 in 2008, today's programming platforms often - // still only have limited support for them. It is very - // easy to include at least decoding support for them even - // without such support. An example of a small decoder for - // half-precision floating-point numbers in the C language - // is shown in Fig. 3. - const int half = (byte1 << 8) + byte2; - const int exp = (half >> 10) & 0x1f; - const int mant = half & 0x3ff; - double val; - if (exp == 0) - { - val = std::ldexp(mant, -24); - } - else if (exp != 31) - { - val = std::ldexp(mant + 1024, exp - 25); - } - else - { - val = (mant == 0) ? std::numeric_limits<double>::infinity() - : std::numeric_limits<double>::quiet_NaN(); - } - return (half & 0x8000) != 0 ? -val : val; - } - - case 0xfa: // Single-Precision Float (four-byte IEEE 754) - { - return get_number<float>(); - } - - case 0xfb: // Double-Precision Float (eight-byte IEEE 754) - { - return get_number<double>(); - } - - default: // anything else (0xFF is handled inside the other types) - { - std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(112, chars_read, "error reading CBOR; last byte: 0x" + ss.str())); - } - } - } - - BasicJsonType parse_msgpack_internal() - { - switch (get()) - { - // EOF - case std::char_traits<char>::eof(): - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); - - // positive fixint - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x08: - case 0x09: - case 0x0a: - case 0x0b: - case 0x0c: - case 0x0d: - case 0x0e: - case 0x0f: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: - case 0x20: - case 0x21: - case 0x22: - case 0x23: - case 0x24: - case 0x25: - case 0x26: - case 0x27: - case 0x28: - case 0x29: - case 0x2a: - case 0x2b: - case 0x2c: - case 0x2d: - case 0x2e: - case 0x2f: - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3a: - case 0x3b: - case 0x3c: - case 0x3d: - case 0x3e: - case 0x3f: - case 0x40: - case 0x41: - case 0x42: - case 0x43: - case 0x44: - case 0x45: - case 0x46: - case 0x47: - case 0x48: - case 0x49: - case 0x4a: - case 0x4b: - case 0x4c: - case 0x4d: - case 0x4e: - case 0x4f: - case 0x50: - case 0x51: - case 0x52: - case 0x53: - case 0x54: - case 0x55: - case 0x56: - case 0x57: - case 0x58: - case 0x59: - case 0x5a: - case 0x5b: - case 0x5c: - case 0x5d: - case 0x5e: - case 0x5f: - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - case 0x78: - case 0x79: - case 0x7a: - case 0x7b: - case 0x7c: - case 0x7d: - case 0x7e: - case 0x7f: - return static_cast<number_unsigned_t>(current); - - // fixmap - case 0x80: - case 0x81: - case 0x82: - case 0x83: - case 0x84: - case 0x85: - case 0x86: - case 0x87: - case 0x88: - case 0x89: - case 0x8a: - case 0x8b: - case 0x8c: - case 0x8d: - case 0x8e: - case 0x8f: - { - return get_msgpack_object(current & 0x0f); - } - - // fixarray - case 0x90: - case 0x91: - case 0x92: - case 0x93: - case 0x94: - case 0x95: - case 0x96: - case 0x97: - case 0x98: - case 0x99: - case 0x9a: - case 0x9b: - case 0x9c: - case 0x9d: - case 0x9e: - case 0x9f: - { - return get_msgpack_array(current & 0x0f); - } - - // fixstr - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - case 0xb8: - case 0xb9: - case 0xba: - case 0xbb: - case 0xbc: - case 0xbd: - case 0xbe: - case 0xbf: - return get_msgpack_string(); - - case 0xc0: // nil - return value_t::null; - - case 0xc2: // false - return false; - - case 0xc3: // true - return true; - - case 0xca: // float 32 - return get_number<float>(); - - case 0xcb: // float 64 - return get_number<double>(); - - case 0xcc: // uint 8 - return get_number<uint8_t>(); - - case 0xcd: // uint 16 - return get_number<uint16_t>(); - - case 0xce: // uint 32 - return get_number<uint32_t>(); - - case 0xcf: // uint 64 - return get_number<uint64_t>(); - - case 0xd0: // int 8 - return get_number<int8_t>(); - - case 0xd1: // int 16 - return get_number<int16_t>(); - - case 0xd2: // int 32 - return get_number<int32_t>(); - - case 0xd3: // int 64 - return get_number<int64_t>(); - - case 0xd9: // str 8 - case 0xda: // str 16 - case 0xdb: // str 32 - return get_msgpack_string(); - - case 0xdc: // array 16 - { - return get_msgpack_array(get_number<uint16_t>()); - } - - case 0xdd: // array 32 - { - return get_msgpack_array(get_number<uint32_t>()); - } - - case 0xde: // map 16 - { - return get_msgpack_object(get_number<uint16_t>()); - } - - case 0xdf: // map 32 - { - return get_msgpack_object(get_number<uint32_t>()); - } - - // positive fixint - case 0xe0: - case 0xe1: - case 0xe2: - case 0xe3: - case 0xe4: - case 0xe5: - case 0xe6: - case 0xe7: - case 0xe8: - case 0xe9: - case 0xea: - case 0xeb: - case 0xec: - case 0xed: - case 0xee: - case 0xef: - case 0xf0: - case 0xf1: - case 0xf2: - case 0xf3: - case 0xf4: - case 0xf5: - case 0xf6: - case 0xf7: - case 0xf8: - case 0xf9: - case 0xfa: - case 0xfb: - case 0xfc: - case 0xfd: - case 0xfe: - case 0xff: - return static_cast<int8_t>(current); - - default: // anything else - { - std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(112, chars_read, - "error reading MessagePack; last byte: 0x" + ss.str())); - } - } - } - - /*! - @brief get next character from the input - - This function provides the interface to the used input adapter. It does - not throw in case the input reached EOF, but returns a -'ve valued - `std::char_traits<char>::eof()` in that case. - - @return character read from the input - */ - int get() - { - ++chars_read; - return (current = ia->get_character()); - } - - /* - @brief read a number from the input - - @tparam NumberType the type of the number - - @return number of type @a NumberType - - @note This function needs to respect the system's endianess, because - bytes in CBOR and MessagePack are stored in network order (big - endian) and therefore need reordering on little endian systems. - - @throw parse_error.110 if input has less than `sizeof(NumberType)` bytes - */ - template<typename NumberType> NumberType get_number() - { - // step 1: read input into array with system's byte order - std::array<uint8_t, sizeof(NumberType)> vec; - for (std::size_t i = 0; i < sizeof(NumberType); ++i) - { - get(); - check_eof(); - - // reverse byte order prior to conversion if necessary - if (is_little_endian) - { - vec[sizeof(NumberType) - i - 1] = static_cast<uint8_t>(current); - } - else - { - vec[i] = static_cast<uint8_t>(current); // LCOV_EXCL_LINE - } - } - - // step 2: convert array into number of type T and return - NumberType result; - std::memcpy(&result, vec.data(), sizeof(NumberType)); - return result; - } - - /*! - @brief create a string by reading characters from the input - - @param[in] len number of bytes to read - - @note We can not reserve @a len bytes for the result, because @a len - may be too large. Usually, @ref check_eof() detects the end of - the input before we run out of string memory. - - @return string created by reading @a len bytes - - @throw parse_error.110 if input has less than @a len bytes - */ - template<typename NumberType> - std::string get_string(const NumberType len) - { - std::string result; - std::generate_n(std::back_inserter(result), len, [this]() - { - get(); - check_eof(); - return static_cast<char>(current); - }); - return result; - } - - /*! - @brief reads a CBOR string - - This function first reads starting bytes to determine the expected - string length and then copies this number of bytes into a string. - Additionally, CBOR's strings with indefinite lengths are supported. - - @return string - - @throw parse_error.110 if input ended - @throw parse_error.113 if an unexpected byte is read - */ - std::string get_cbor_string() - { - check_eof(); - - switch (current) - { - // UTF-8 string (0x00..0x17 bytes follow) - case 0x60: - case 0x61: - case 0x62: - case 0x63: - case 0x64: - case 0x65: - case 0x66: - case 0x67: - case 0x68: - case 0x69: - case 0x6a: - case 0x6b: - case 0x6c: - case 0x6d: - case 0x6e: - case 0x6f: - case 0x70: - case 0x71: - case 0x72: - case 0x73: - case 0x74: - case 0x75: - case 0x76: - case 0x77: - { - return get_string(current & 0x1f); - } - - case 0x78: // UTF-8 string (one-byte uint8_t for n follows) - { - return get_string(get_number<uint8_t>()); - } - - case 0x79: // UTF-8 string (two-byte uint16_t for n follow) - { - return get_string(get_number<uint16_t>()); - } - - case 0x7a: // UTF-8 string (four-byte uint32_t for n follow) - { - return get_string(get_number<uint32_t>()); - } - - case 0x7b: // UTF-8 string (eight-byte uint64_t for n follow) - { - return get_string(get_number<uint64_t>()); - } - - case 0x7f: // UTF-8 string (indefinite length) - { - std::string result; - while (get() != 0xff) - { - check_eof(); - result.push_back(static_cast<char>(current)); - } - return result; - } - - default: - { - std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(113, chars_read, "expected a CBOR string; last byte: 0x" + ss.str())); - } - } - } - - template<typename NumberType> - BasicJsonType get_cbor_array(const NumberType len) - { - BasicJsonType result = value_t::array; - std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() - { - return parse_cbor_internal(); - }); - return result; - } - - template<typename NumberType> - BasicJsonType get_cbor_object(const NumberType len) - { - BasicJsonType result = value_t::object; - std::generate_n(std::inserter(*result.m_value.object, - result.m_value.object->end()), - len, [this]() - { - get(); - auto key = get_cbor_string(); - auto val = parse_cbor_internal(); - return std::make_pair(std::move(key), std::move(val)); - }); - return result; - } - - /*! - @brief reads a MessagePack string - - This function first reads starting bytes to determine the expected - string length and then copies this number of bytes into a string. - - @return string - - @throw parse_error.110 if input ended - @throw parse_error.113 if an unexpected byte is read - */ - std::string get_msgpack_string() - { - check_eof(); - - switch (current) - { - // fixstr - case 0xa0: - case 0xa1: - case 0xa2: - case 0xa3: - case 0xa4: - case 0xa5: - case 0xa6: - case 0xa7: - case 0xa8: - case 0xa9: - case 0xaa: - case 0xab: - case 0xac: - case 0xad: - case 0xae: - case 0xaf: - case 0xb0: - case 0xb1: - case 0xb2: - case 0xb3: - case 0xb4: - case 0xb5: - case 0xb6: - case 0xb7: - case 0xb8: - case 0xb9: - case 0xba: - case 0xbb: - case 0xbc: - case 0xbd: - case 0xbe: - case 0xbf: - { - return get_string(current & 0x1f); - } - - case 0xd9: // str 8 - { - return get_string(get_number<uint8_t>()); - } - - case 0xda: // str 16 - { - return get_string(get_number<uint16_t>()); - } - - case 0xdb: // str 32 - { - return get_string(get_number<uint32_t>()); - } - - default: - { - std::stringstream ss; - ss << std::setw(2) << std::setfill('0') << std::hex << current; - JSON_THROW(parse_error::create(113, chars_read, - "expected a MessagePack string; last byte: 0x" + ss.str())); - } - } - } - - template<typename NumberType> - BasicJsonType get_msgpack_array(const NumberType len) - { - BasicJsonType result = value_t::array; - std::generate_n(std::back_inserter(*result.m_value.array), len, [this]() - { - return parse_msgpack_internal(); - }); - return result; - } - - template<typename NumberType> - BasicJsonType get_msgpack_object(const NumberType len) - { - BasicJsonType result = value_t::object; - std::generate_n(std::inserter(*result.m_value.object, - result.m_value.object->end()), - len, [this]() - { - get(); - auto key = get_msgpack_string(); - auto val = parse_msgpack_internal(); - return std::make_pair(std::move(key), std::move(val)); - }); - return result; - } - - /*! - @brief check if input ended - @throw parse_error.110 if input ended - */ - void check_eof(const bool expect_eof = false) const - { - if (expect_eof) - { - if (JSON_UNLIKELY(current != std::char_traits<char>::eof())) - { - JSON_THROW(parse_error::create(110, chars_read, "expected end of input")); - } - } - else - { - if (JSON_UNLIKELY(current == std::char_traits<char>::eof())) - { - JSON_THROW(parse_error::create(110, chars_read, "unexpected end of input")); - } - } - } - - private: - /// input adapter - input_adapter_t ia = nullptr; - - /// the current character - int current = std::char_traits<char>::eof(); - - /// the number of characters read - std::size_t chars_read = 0; - - /// whether we can assume little endianess - const bool is_little_endian = little_endianess(); -}; - -/*! -@brief serialization to CBOR and MessagePack values -*/ -template<typename BasicJsonType, typename CharType> -class binary_writer -{ - public: - /*! - @brief create a binary writer - - @param[in] adapter output adapter to write to - */ - explicit binary_writer(output_adapter_t<CharType> adapter) : oa(adapter) - { - assert(oa); - } - - /*! - @brief[in] j JSON value to serialize - */ - void write_cbor(const BasicJsonType& j) - { - switch (j.type()) - { - case value_t::null: - { - oa->write_character(static_cast<CharType>(0xf6)); - break; - } - - case value_t::boolean: - { - oa->write_character(j.m_value.boolean - ? static_cast<CharType>(0xf5) - : static_cast<CharType>(0xf4)); - break; - } - - case value_t::number_integer: - { - if (j.m_value.number_integer >= 0) - { - // CBOR does not differentiate between positive signed - // integers and unsigned integers. Therefore, we used the - // code from the value_t::number_unsigned case here. - if (j.m_value.number_integer <= 0x17) - { - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer <= (std::numeric_limits<uint8_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x18)); - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer <= (std::numeric_limits<uint16_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x19)); - write_number(static_cast<uint16_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer <= (std::numeric_limits<uint32_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x1a)); - write_number(static_cast<uint32_t>(j.m_value.number_integer)); - } - else - { - oa->write_character(static_cast<CharType>(0x1b)); - write_number(static_cast<uint64_t>(j.m_value.number_integer)); - } - } - else - { - // The conversions below encode the sign in the first - // byte, and the value is converted to a positive number. - const auto positive_number = -1 - j.m_value.number_integer; - if (j.m_value.number_integer >= -24) - { - write_number(static_cast<uint8_t>(0x20 + positive_number)); - } - else if (positive_number <= (std::numeric_limits<uint8_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x38)); - write_number(static_cast<uint8_t>(positive_number)); - } - else if (positive_number <= (std::numeric_limits<uint16_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x39)); - write_number(static_cast<uint16_t>(positive_number)); - } - else if (positive_number <= (std::numeric_limits<uint32_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x3a)); - write_number(static_cast<uint32_t>(positive_number)); - } - else - { - oa->write_character(static_cast<CharType>(0x3b)); - write_number(static_cast<uint64_t>(positive_number)); - } - } - break; - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned <= 0x17) - { - write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x18)); - write_number(static_cast<uint8_t>(j.m_value.number_unsigned)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x19)); - write_number(static_cast<uint16_t>(j.m_value.number_unsigned)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)()) - { - oa->write_character(static_cast<CharType>(0x1a)); - write_number(static_cast<uint32_t>(j.m_value.number_unsigned)); - } - else - { - oa->write_character(static_cast<CharType>(0x1b)); - write_number(static_cast<uint64_t>(j.m_value.number_unsigned)); - } - break; - } - - case value_t::number_float: // Double-Precision Float - { - oa->write_character(static_cast<CharType>(0xfb)); - write_number(j.m_value.number_float); - break; - } - - case value_t::string: - { - // step 1: write control byte and the string length - const auto N = j.m_value.string->size(); - if (N <= 0x17) - { - write_number(static_cast<uint8_t>(0x60 + N)); - } - else if (N <= 0xff) - { - oa->write_character(static_cast<CharType>(0x78)); - write_number(static_cast<uint8_t>(N)); - } - else if (N <= 0xffff) - { - oa->write_character(static_cast<CharType>(0x79)); - write_number(static_cast<uint16_t>(N)); - } - else if (N <= 0xffffffff) - { - oa->write_character(static_cast<CharType>(0x7a)); - write_number(static_cast<uint32_t>(N)); - } - // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) - { - oa->write_character(static_cast<CharType>(0x7b)); - write_number(static_cast<uint64_t>(N)); - } - // LCOV_EXCL_STOP - - // step 2: write the string - oa->write_characters( - reinterpret_cast<const CharType*>(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - // step 1: write control byte and the array size - const auto N = j.m_value.array->size(); - if (N <= 0x17) - { - write_number(static_cast<uint8_t>(0x80 + N)); - } - else if (N <= 0xff) - { - oa->write_character(static_cast<CharType>(0x98)); - write_number(static_cast<uint8_t>(N)); - } - else if (N <= 0xffff) - { - oa->write_character(static_cast<CharType>(0x99)); - write_number(static_cast<uint16_t>(N)); - } - else if (N <= 0xffffffff) - { - oa->write_character(static_cast<CharType>(0x9a)); - write_number(static_cast<uint32_t>(N)); - } - // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) - { - oa->write_character(static_cast<CharType>(0x9b)); - write_number(static_cast<uint64_t>(N)); - } - // LCOV_EXCL_STOP - - // step 2: write each element - for (const auto& el : *j.m_value.array) - { - write_cbor(el); - } - break; - } - - case value_t::object: - { - // step 1: write control byte and the object size - const auto N = j.m_value.object->size(); - if (N <= 0x17) - { - write_number(static_cast<uint8_t>(0xa0 + N)); - } - else if (N <= 0xff) - { - oa->write_character(static_cast<CharType>(0xb8)); - write_number(static_cast<uint8_t>(N)); - } - else if (N <= 0xffff) - { - oa->write_character(static_cast<CharType>(0xb9)); - write_number(static_cast<uint16_t>(N)); - } - else if (N <= 0xffffffff) - { - oa->write_character(static_cast<CharType>(0xba)); - write_number(static_cast<uint32_t>(N)); - } - // LCOV_EXCL_START - else if (N <= 0xffffffffffffffff) - { - oa->write_character(static_cast<CharType>(0xbb)); - write_number(static_cast<uint64_t>(N)); - } - // LCOV_EXCL_STOP - - // step 2: write each element - for (const auto& el : *j.m_value.object) - { - write_cbor(el.first); - write_cbor(el.second); - } - break; - } - - default: - break; - } - } - - /*! - @brief[in] j JSON value to serialize - */ - void write_msgpack(const BasicJsonType& j) - { - switch (j.type()) - { - case value_t::null: // nil - { - oa->write_character(static_cast<CharType>(0xc0)); - break; - } - - case value_t::boolean: // true and false - { - oa->write_character(j.m_value.boolean - ? static_cast<CharType>(0xc3) - : static_cast<CharType>(0xc2)); - break; - } - - case value_t::number_integer: - { - if (j.m_value.number_integer >= 0) - { - // MessagePack does not differentiate between positive - // signed integers and unsigned integers. Therefore, we used - // the code from the value_t::number_unsigned case here. - if (j.m_value.number_unsigned < 128) - { - // positive fixnum - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)()) - { - // uint 8 - oa->write_character(static_cast<CharType>(0xcc)); - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)()) - { - // uint 16 - oa->write_character(static_cast<CharType>(0xcd)); - write_number(static_cast<uint16_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)()) - { - // uint 32 - oa->write_character(static_cast<CharType>(0xce)); - write_number(static_cast<uint32_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)()) - { - // uint 64 - oa->write_character(static_cast<CharType>(0xcf)); - write_number(static_cast<uint64_t>(j.m_value.number_integer)); - } - } - else - { - if (j.m_value.number_integer >= -32) - { - // negative fixnum - write_number(static_cast<int8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits<int8_t>::min)() and - j.m_value.number_integer <= (std::numeric_limits<int8_t>::max)()) - { - // int 8 - oa->write_character(static_cast<CharType>(0xd0)); - write_number(static_cast<int8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits<int16_t>::min)() and - j.m_value.number_integer <= (std::numeric_limits<int16_t>::max)()) - { - // int 16 - oa->write_character(static_cast<CharType>(0xd1)); - write_number(static_cast<int16_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits<int32_t>::min)() and - j.m_value.number_integer <= (std::numeric_limits<int32_t>::max)()) - { - // int 32 - oa->write_character(static_cast<CharType>(0xd2)); - write_number(static_cast<int32_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_integer >= (std::numeric_limits<int64_t>::min)() and - j.m_value.number_integer <= (std::numeric_limits<int64_t>::max)()) - { - // int 64 - oa->write_character(static_cast<CharType>(0xd3)); - write_number(static_cast<int64_t>(j.m_value.number_integer)); - } - } - break; - } - - case value_t::number_unsigned: - { - if (j.m_value.number_unsigned < 128) - { - // positive fixnum - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint8_t>::max)()) - { - // uint 8 - oa->write_character(static_cast<CharType>(0xcc)); - write_number(static_cast<uint8_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint16_t>::max)()) - { - // uint 16 - oa->write_character(static_cast<CharType>(0xcd)); - write_number(static_cast<uint16_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint32_t>::max)()) - { - // uint 32 - oa->write_character(static_cast<CharType>(0xce)); - write_number(static_cast<uint32_t>(j.m_value.number_integer)); - } - else if (j.m_value.number_unsigned <= (std::numeric_limits<uint64_t>::max)()) - { - // uint 64 - oa->write_character(static_cast<CharType>(0xcf)); - write_number(static_cast<uint64_t>(j.m_value.number_integer)); - } - break; - } - - case value_t::number_float: // float 64 - { - oa->write_character(static_cast<CharType>(0xcb)); - write_number(j.m_value.number_float); - break; - } - - case value_t::string: - { - // step 1: write control byte and the string length - const auto N = j.m_value.string->size(); - if (N <= 31) - { - // fixstr - write_number(static_cast<uint8_t>(0xa0 | N)); - } - else if (N <= 255) - { - // str 8 - oa->write_character(static_cast<CharType>(0xd9)); - write_number(static_cast<uint8_t>(N)); - } - else if (N <= 65535) - { - // str 16 - oa->write_character(static_cast<CharType>(0xda)); - write_number(static_cast<uint16_t>(N)); - } - else if (N <= 4294967295) - { - // str 32 - oa->write_character(static_cast<CharType>(0xdb)); - write_number(static_cast<uint32_t>(N)); - } - - // step 2: write the string - oa->write_characters( - reinterpret_cast<const CharType*>(j.m_value.string->c_str()), - j.m_value.string->size()); - break; - } - - case value_t::array: - { - // step 1: write control byte and the array size - const auto N = j.m_value.array->size(); - if (N <= 15) - { - // fixarray - write_number(static_cast<uint8_t>(0x90 | N)); - } - else if (N <= 0xffff) - { - // array 16 - oa->write_character(static_cast<CharType>(0xdc)); - write_number(static_cast<uint16_t>(N)); - } - else if (N <= 0xffffffff) - { - // array 32 - oa->write_character(static_cast<CharType>(0xdd)); - write_number(static_cast<uint32_t>(N)); - } - - // step 2: write each element - for (const auto& el : *j.m_value.array) - { - write_msgpack(el); - } - break; - } - - case value_t::object: - { - // step 1: write control byte and the object size - const auto N = j.m_value.object->size(); - if (N <= 15) - { - // fixmap - write_number(static_cast<uint8_t>(0x80 | (N & 0xf))); - } - else if (N <= 65535) - { - // map 16 - oa->write_character(static_cast<CharType>(0xde)); - write_number(static_cast<uint16_t>(N)); - } - else if (N <= 4294967295) - { - // map 32 - oa->write_character(static_cast<CharType>(0xdf)); - write_number(static_cast<uint32_t>(N)); - } - - // step 2: write each element - for (const auto& el : *j.m_value.object) - { - write_msgpack(el.first); - write_msgpack(el.second); - } - break; - } - - default: - break; - } - } - - private: - /* - @brief write a number to output input - - @param[in] n number of type @a NumberType - @tparam NumberType the type of the number - - @note This function needs to respect the system's endianess, because bytes - in CBOR and MessagePack are stored in network order (big endian) and - therefore need reordering on little endian systems. - */ - template<typename NumberType> void write_number(NumberType n) - { - // step 1: write number to array of length NumberType - std::array<CharType, sizeof(NumberType)> vec; - std::memcpy(vec.data(), &n, sizeof(NumberType)); - - // step 2: write array to output (with possible reordering) - if (is_little_endian) - { - // reverse byte order prior to conversion if necessary - std::reverse(vec.begin(), vec.end()); - } - - oa->write_characters(vec.data(), sizeof(NumberType)); - } - - private: - /// whether we can assume little endianess - const bool is_little_endian = binary_reader<BasicJsonType>::little_endianess(); - - /// the output - output_adapter_t<CharType> oa = nullptr; -}; - -/////////////////// -// serialization // -/////////////////// - -template<typename BasicJsonType> -class serializer -{ - using string_t = typename BasicJsonType::string_t; - using number_float_t = typename BasicJsonType::number_float_t; - using number_integer_t = typename BasicJsonType::number_integer_t; - using number_unsigned_t = typename BasicJsonType::number_unsigned_t; - public: - /*! - @param[in] s output stream to serialize to - @param[in] ichar indentation character to use - */ - serializer(output_adapter_t<char> s, const char ichar) - : o(std::move(s)), loc(std::localeconv()), - thousands_sep(loc->thousands_sep == nullptr ? '\0' : loc->thousands_sep[0]), - decimal_point(loc->decimal_point == nullptr ? '\0' : loc->decimal_point[0]), - indent_char(ichar), indent_string(512, indent_char) {} - - // delete because of pointer members - serializer(const serializer&) = delete; - serializer& operator=(const serializer&) = delete; - - /*! - @brief internal implementation of the serialization function - - This function is called by the public member function dump and organizes - the serialization internally. The indentation level is propagated as - additional parameter. In case of arrays and objects, the function is - called recursively. - - - strings and object keys are escaped using `escape_string()` - - integer numbers are converted implicitly via `operator<<` - - floating-point numbers are converted to a string using `"%g"` format - - @param[in] val value to serialize - @param[in] pretty_print whether the output shall be pretty-printed - @param[in] indent_step the indent level - @param[in] current_indent the current indent level (only used internally) - */ - void dump(const BasicJsonType& val, const bool pretty_print, - const bool ensure_ascii, - const unsigned int indent_step, - const unsigned int current_indent = 0) - { - switch (val.m_type) - { - case value_t::object: - { - if (val.m_value.object->empty()) - { - o->write_characters("{}", 2); - return; - } - - if (pretty_print) - { - o->write_characters("{\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_characters(indent_string.c_str(), new_indent); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\": ", 3); - dump(i->second, true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character('}'); - } - else - { - o->write_character('{'); - - // first n-1 elements - auto i = val.m_value.object->cbegin(); - for (std::size_t cnt = 0; cnt < val.m_value.object->size() - 1; ++cnt, ++i) - { - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); - } - - // last element - assert(i != val.m_value.object->cend()); - assert(std::next(i) == val.m_value.object->cend()); - o->write_character('\"'); - dump_escaped(i->first, ensure_ascii); - o->write_characters("\":", 2); - dump(i->second, false, ensure_ascii, indent_step, current_indent); - - o->write_character('}'); - } - - return; - } - - case value_t::array: - { - if (val.m_value.array->empty()) - { - o->write_characters("[]", 2); - return; - } - - if (pretty_print) - { - o->write_characters("[\n", 2); - - // variable to hold indentation for recursive calls - const auto new_indent = current_indent + indent_step; - if (JSON_UNLIKELY(indent_string.size() < new_indent)) - { - indent_string.resize(indent_string.size() * 2, ' '); - } - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - o->write_characters(indent_string.c_str(), new_indent); - dump(*i, true, ensure_ascii, indent_step, new_indent); - o->write_characters(",\n", 2); - } - - // last element - assert(not val.m_value.array->empty()); - o->write_characters(indent_string.c_str(), new_indent); - dump(val.m_value.array->back(), true, ensure_ascii, indent_step, new_indent); - - o->write_character('\n'); - o->write_characters(indent_string.c_str(), current_indent); - o->write_character(']'); - } - else - { - o->write_character('['); - - // first n-1 elements - for (auto i = val.m_value.array->cbegin(); - i != val.m_value.array->cend() - 1; ++i) - { - dump(*i, false, ensure_ascii, indent_step, current_indent); - o->write_character(','); - } - - // last element - assert(not val.m_value.array->empty()); - dump(val.m_value.array->back(), false, ensure_ascii, indent_step, current_indent); - - o->write_character(']'); - } - - return; - } - - case value_t::string: - { - o->write_character('\"'); - dump_escaped(*val.m_value.string, ensure_ascii); - o->write_character('\"'); - return; - } - - case value_t::boolean: - { - if (val.m_value.boolean) - { - o->write_characters("true", 4); - } - else - { - o->write_characters("false", 5); - } - return; - } - - case value_t::number_integer: - { - dump_integer(val.m_value.number_integer); - return; - } - - case value_t::number_unsigned: - { - dump_integer(val.m_value.number_unsigned); - return; - } - - case value_t::number_float: - { - dump_float(val.m_value.number_float); - return; - } - - case value_t::discarded: - { - o->write_characters("<discarded>", 11); - return; - } - - case value_t::null: - { - o->write_characters("null", 4); - return; - } - } - } - - private: - /*! - @brief returns the number of expected bytes following in UTF-8 string - - @param[in] u the first byte of a UTF-8 string - @return the number of expected bytes following - */ - static constexpr std::size_t bytes_following(const uint8_t u) - { - return ((u <= 127) ? 0 - : ((192 <= u and u <= 223) ? 1 - : ((224 <= u and u <= 239) ? 2 - : ((240 <= u and u <= 247) ? 3 : std::string::npos)))); - } - - /*! - @brief calculates the extra space to escape a JSON string - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - @return the number of characters required to escape string @a s - - @complexity Linear in the length of string @a s. - */ - static std::size_t extra_space(const string_t& s, - const bool ensure_ascii) noexcept - { - std::size_t res = 0; - - for (std::size_t i = 0; i < s.size(); ++i) - { - switch (s[i]) - { - // control characters that can be escaped with a backslash - case '"': - case '\\': - case '\b': - case '\f': - case '\n': - case '\r': - case '\t': - { - // from c (1 byte) to \x (2 bytes) - res += 1; - break; - } - - // control characters that need \uxxxx escaping - case 0x00: - case 0x01: - case 0x02: - case 0x03: - case 0x04: - case 0x05: - case 0x06: - case 0x07: - case 0x0b: - case 0x0e: - case 0x0f: - case 0x10: - case 0x11: - case 0x12: - case 0x13: - case 0x14: - case 0x15: - case 0x16: - case 0x17: - case 0x18: - case 0x19: - case 0x1a: - case 0x1b: - case 0x1c: - case 0x1d: - case 0x1e: - case 0x1f: - { - // from c (1 byte) to \uxxxx (6 bytes) - res += 5; - break; - } - - default: - { - if (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F)) - { - const auto bytes = bytes_following(static_cast<uint8_t>(s[i])); - if (bytes == std::string::npos) - { - // invalid characters are treated as is, so no - // additional space will be used - break; - } - - if (bytes == 3) - { - // codepoints that need 4 bytes (i.e., 3 additional - // bytes) in UTF-8 need a surrogate pair when \u - // escaping is used: from 4 bytes to \uxxxx\uxxxx - // (12 bytes) - res += (12 - bytes - 1); - } - else - { - // from x bytes to \uxxxx (6 bytes) - res += (6 - bytes - 1); - } - - // skip the additional bytes - i += bytes; - } - break; - } - } - } - - return res; - } - - static void escape_codepoint(int codepoint, string_t& result, std::size_t& pos) - { - // expecting a proper codepoint - assert(0x00 <= codepoint and codepoint <= 0x10FFFF); - - // the last written character was the backslash before the 'u' - assert(result[pos] == '\\'); - - // write the 'u' - result[++pos] = 'u'; - - // convert a number 0..15 to its hex representation (0..f) - static const std::array<char, 16> hexify = - { - { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' - } - }; - - if (codepoint < 0x10000) - { - // codepoints U+0000..U+FFFF can be represented as \uxxxx. - result[++pos] = hexify[(codepoint >> 12) & 0x0F]; - result[++pos] = hexify[(codepoint >> 8) & 0x0F]; - result[++pos] = hexify[(codepoint >> 4) & 0x0F]; - result[++pos] = hexify[codepoint & 0x0F]; - } - else - { - // codepoints U+10000..U+10FFFF need a surrogate pair to be - // represented as \uxxxx\uxxxx. - // http://www.unicode.org/faq/utf_bom.html#utf16-4 - codepoint -= 0x10000; - const int high_surrogate = 0xD800 | ((codepoint >> 10) & 0x3FF); - const int low_surrogate = 0xDC00 | (codepoint & 0x3FF); - result[++pos] = hexify[(high_surrogate >> 12) & 0x0F]; - result[++pos] = hexify[(high_surrogate >> 8) & 0x0F]; - result[++pos] = hexify[(high_surrogate >> 4) & 0x0F]; - result[++pos] = hexify[high_surrogate & 0x0F]; - ++pos; // backslash is already in output - result[++pos] = 'u'; - result[++pos] = hexify[(low_surrogate >> 12) & 0x0F]; - result[++pos] = hexify[(low_surrogate >> 8) & 0x0F]; - result[++pos] = hexify[(low_surrogate >> 4) & 0x0F]; - result[++pos] = hexify[low_surrogate & 0x0F]; - } - - ++pos; - } - - /*! - @brief dump escaped string - - Escape a string by replacing certain special characters by a sequence of an - escape character (backslash) and another character and other control - characters by a sequence of "\u" followed by a four-digit hex - representation. The escaped string is written to output stream @a o. - - @param[in] s the string to escape - @param[in] ensure_ascii whether to escape non-ASCII characters with - \uXXXX sequences - - @complexity Linear in the length of string @a s. - */ - void dump_escaped(const string_t& s, const bool ensure_ascii) const - { - const auto space = extra_space(s, ensure_ascii); - if (space == 0) - { - o->write_characters(s.c_str(), s.size()); - return; - } - - // create a result string of necessary size - string_t result(s.size() + space, '\\'); - std::size_t pos = 0; - - for (std::size_t i = 0; i < s.size(); ++i) - { - switch (s[i]) - { - case '"': // quotation mark (0x22) - { - result[pos + 1] = '"'; - pos += 2; - break; - } - - case '\\': // reverse solidus (0x5c) - { - // nothing to change - pos += 2; - break; - } - - case '\b': // backspace (0x08) - { - result[pos + 1] = 'b'; - pos += 2; - break; - } - - case '\f': // formfeed (0x0c) - { - result[pos + 1] = 'f'; - pos += 2; - break; - } - - case '\n': // newline (0x0a) - { - result[pos + 1] = 'n'; - pos += 2; - break; - } - - case '\r': // carriage return (0x0d) - { - result[pos + 1] = 'r'; - pos += 2; - break; - } - - case '\t': // horizontal tab (0x09) - { - result[pos + 1] = 't'; - pos += 2; - break; - } - - default: - { - // escape control characters (0x00..0x1F) or, if - // ensure_ascii parameter is used, non-ASCII characters - if ((0x00 <= s[i] and s[i] <= 0x1F) or - (ensure_ascii and (s[i] & 0x80 or s[i] == 0x7F))) - { - const auto bytes = bytes_following(static_cast<uint8_t>(s[i])); - if (bytes == std::string::npos) - { - // copy invalid character as is - result[pos++] = s[i]; - break; - } - - // check that the additional bytes are present - assert(i + bytes < s.size()); - - // to use \uxxxx escaping, we first need to caluclate - // the codepoint from the UTF-8 bytes - int codepoint = 0; - - assert(0 <= bytes and bytes <= 3); - switch (bytes) - { - case 0: - { - codepoint = s[i] & 0xFF; - break; - } - - case 1: - { - codepoint = ((s[i] & 0x3F) << 6) - + (s[i + 1] & 0x7F); - break; - } - - case 2: - { - codepoint = ((s[i] & 0x1F) << 12) - + ((s[i + 1] & 0x7F) << 6) - + (s[i + 2] & 0x7F); - break; - } - - case 3: - { - codepoint = ((s[i] & 0xF) << 18) - + ((s[i + 1] & 0x7F) << 12) - + ((s[i + 2] & 0x7F) << 6) - + (s[i + 3] & 0x7F); - break; - } - - default: - break; // LCOV_EXCL_LINE - } - - escape_codepoint(codepoint, result, pos); - i += bytes; - } - else - { - // all other characters are added as-is - result[pos++] = s[i]; - } - break; - } - } - } - - assert(pos == result.size()); - o->write_characters(result.c_str(), result.size()); - } - - /*! - @brief dump an integer - - Dump a given integer to output stream @a o. Works internally with - @a number_buffer. - - @param[in] x integer number (signed or unsigned) to dump - @tparam NumberType either @a number_integer_t or @a number_unsigned_t - */ - template < - typename NumberType, - detail::enable_if_t<std::is_same<NumberType, number_unsigned_t>::value or - std::is_same<NumberType, number_integer_t>::value, - int> = 0 > - void dump_integer(NumberType x) - { - // special case for "0" - if (x == 0) - { - o->write_character('0'); - return; - } - - const bool is_negative = (x <= 0) and (x != 0); // see issue #755 - std::size_t i = 0; - - while (x != 0) - { - // spare 1 byte for '\0' - assert(i < number_buffer.size() - 1); - - const auto digit = std::labs(static_cast<long>(x % 10)); - number_buffer[i++] = static_cast<char>('0' + digit); - x /= 10; - } - - if (is_negative) - { - // make sure there is capacity for the '-' - assert(i < number_buffer.size() - 2); - number_buffer[i++] = '-'; - } - - std::reverse(number_buffer.begin(), number_buffer.begin() + i); - o->write_characters(number_buffer.data(), i); - } - - /*! - @brief dump a floating-point number - - Dump a given floating-point number to output stream @a o. Works internally - with @a number_buffer. - - @param[in] x floating-point number to dump - */ - void dump_float(number_float_t x) - { - // NaN / inf - if (not std::isfinite(x) or std::isnan(x)) - { - o->write_characters("null", 4); - return; - } - - // get number of digits for a text -> float -> text round-trip - static constexpr auto d = std::numeric_limits<number_float_t>::digits10; - - // the actual conversion - std::ptrdiff_t len = snprintf(number_buffer.data(), number_buffer.size(), "%.*g", d, x); - - // negative value indicates an error - assert(len > 0); - // check if buffer was large enough - assert(static_cast<std::size_t>(len) < number_buffer.size()); - - // erase thousands separator - if (thousands_sep != '\0') - { - const auto end = std::remove(number_buffer.begin(), - number_buffer.begin() + len, thousands_sep); - std::fill(end, number_buffer.end(), '\0'); - assert((end - number_buffer.begin()) <= len); - len = (end - number_buffer.begin()); - } - - // convert decimal point to '.' - if (decimal_point != '\0' and decimal_point != '.') - { - const auto dec_pos = std::find(number_buffer.begin(), number_buffer.end(), decimal_point); - if (dec_pos != number_buffer.end()) - { - *dec_pos = '.'; - } - } - - o->write_characters(number_buffer.data(), static_cast<std::size_t>(len)); - - // determine if need to append ".0" - const bool value_is_int_like = - std::none_of(number_buffer.begin(), number_buffer.begin() + len + 1, - [](char c) - { - return (c == '.' or c == 'e'); - }); - - if (value_is_int_like) - { - o->write_characters(".0", 2); - } - } - - private: - /// the output of the serializer - output_adapter_t<char> o = nullptr; - - /// a (hopefully) large enough character buffer - std::array<char, 64> number_buffer{{}}; - - /// the locale - const std::lconv* loc = nullptr; - /// the locale's thousand separator character - const char thousands_sep = '\0'; - /// the locale's decimal point character - const char decimal_point = '\0'; - - /// the indentation character - const char indent_char; - - /// the indentation string - string_t indent_string; -}; - -template<typename BasicJsonType> -class json_ref -{ - public: - using value_type = BasicJsonType; - - json_ref(value_type&& value) - : owned_value(std::move(value)), - value_ref(&owned_value), - is_rvalue(true) - {} - - json_ref(const value_type& value) - : value_ref(const_cast<value_type*>(&value)), - is_rvalue(false) - {} - - json_ref(std::initializer_list<json_ref> init) - : owned_value(init), - value_ref(&owned_value), - is_rvalue(true) - {} - - template <class... Args> - json_ref(Args&& ... args) - : owned_value(std::forward<Args>(args)...), - value_ref(&owned_value), - is_rvalue(true) - {} - - // class should be movable only - json_ref(json_ref&&) = default; - json_ref(const json_ref&) = delete; - json_ref& operator=(const json_ref&) = delete; - - value_type moved_or_copied() const - { - if (is_rvalue) - { - return std::move(*value_ref); - } - return *value_ref; - } - - value_type const& operator*() const - { - return *static_cast<value_type const*>(value_ref); - } - - value_type const* operator->() const - { - return static_cast<value_type const*>(value_ref); - } - - private: - mutable value_type owned_value = nullptr; - value_type* value_ref = nullptr; - const bool is_rvalue; -}; - -} // namespace detail - -/// namespace to hold default `to_json` / `from_json` functions -namespace -{ -constexpr const auto& to_json = detail::static_const<detail::to_json_fn>::value; -constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::value; -} - - -/*! -@brief default JSONSerializer template argument - -This serializer ignores the template arguments and uses ADL -([argument-dependent lookup](http://en.cppreference.com/w/cpp/language/adl)) -for serialization. -*/ -template<typename, typename> -struct adl_serializer -{ - /*! - @brief convert a JSON value to any value type - - This function is usually called by the `get()` function of the - @ref basic_json class (either explicit or via conversion operators). - - @param[in] j JSON value to read from - @param[in,out] val value to write to - */ - template<typename BasicJsonType, typename ValueType> - static void from_json(BasicJsonType&& j, ValueType& val) noexcept( - noexcept(::nlohmann::from_json(std::forward<BasicJsonType>(j), val))) - { - ::nlohmann::from_json(std::forward<BasicJsonType>(j), val); - } - - /*! - @brief convert any value type to a JSON value - - This function is usually called by the constructors of the @ref basic_json - class. - - @param[in,out] j JSON value to write to - @param[in] val value to read from - */ - template<typename BasicJsonType, typename ValueType> - static void to_json(BasicJsonType& j, ValueType&& val) noexcept( - noexcept(::nlohmann::to_json(j, std::forward<ValueType>(val)))) - { - ::nlohmann::to_json(j, std::forward<ValueType>(val)); - } -}; - -/*! -@brief JSON Pointer - -A JSON pointer defines a string syntax for identifying a specific value -within a JSON document. It can be used with functions `at` and -`operator[]`. Furthermore, JSON pointers are the base for JSON patches. - -@sa [RFC 6901](https://tools.ietf.org/html/rfc6901) - -@since version 2.0.0 -*/ -class json_pointer -{ - /// allow basic_json to access private members - NLOHMANN_BASIC_JSON_TPL_DECLARATION - friend class basic_json; - - public: - /*! - @brief create JSON pointer - - Create a JSON pointer according to the syntax described in - [Section 3 of RFC6901](https://tools.ietf.org/html/rfc6901#section-3). - - @param[in] s string representing the JSON pointer; if omitted, the empty - string is assumed which references the whole JSON value - - @throw parse_error.107 if the given JSON pointer @a s is nonempty and - does not begin with a slash (`/`); see example below - - @throw parse_error.108 if a tilde (`~`) in the given JSON pointer @a s - is not followed by `0` (representing `~`) or `1` (representing `/`); - see example below - - @liveexample{The example shows the construction several valid JSON - pointers as well as the exceptional behavior.,json_pointer} - - @since version 2.0.0 - */ - explicit json_pointer(const std::string& s = "") : reference_tokens(split(s)) {} - - /*! - @brief return a string representation of the JSON pointer - - @invariant For each JSON pointer `ptr`, it holds: - @code {.cpp} - ptr == json_pointer(ptr.to_string()); - @endcode - - @return a string representation of the JSON pointer - - @liveexample{The example shows the result of `to_string`., - json_pointer__to_string} - - @since version 2.0.0 - */ - std::string to_string() const noexcept - { - return std::accumulate(reference_tokens.begin(), reference_tokens.end(), - std::string{}, - [](const std::string & a, const std::string & b) - { - return a + "/" + escape(b); - }); - } - - /// @copydoc to_string() - operator std::string() const - { - return to_string(); - } - - private: - /*! - @brief remove and return last reference pointer - @throw out_of_range.405 if JSON pointer has no parent - */ - std::string pop_back() - { - if (JSON_UNLIKELY(is_root())) - { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); - } - - auto last = reference_tokens.back(); - reference_tokens.pop_back(); - return last; - } - - /// return whether pointer points to the root document - bool is_root() const - { - return reference_tokens.empty(); - } - - json_pointer top() const - { - if (JSON_UNLIKELY(is_root())) - { - JSON_THROW(detail::out_of_range::create(405, "JSON pointer has no parent")); - } - - json_pointer result = *this; - result.reference_tokens = {reference_tokens[0]}; - return result; - } - - - /*! - @brief create and return a reference to the pointed to value - - @complexity Linear in the number of reference tokens. - - @throw parse_error.109 if array index is not a number - @throw type_error.313 if value cannot be unflattened - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const; - - /*! - @brief return a reference to the pointed to value - - @note This version does not throw if a value is not present, but tries to - create nested values instead. For instance, calling this function - with pointer `"/this/that"` on a null value is equivalent to calling - `operator[]("this").operator[]("that")` on that value, effectively - changing the null value to an object. - - @param[in] ptr a JSON value - - @return reference to the JSON value pointed to by the JSON pointer - - @complexity Linear in the length of the JSON pointer. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const; - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - NLOHMANN_BASIC_JSON_TPL& get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const; - - /*! - @brief return a const reference to the pointed to value - - @param[in] ptr a JSON value - - @return const reference to the JSON value pointed to by the JSON - pointer - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - const NLOHMANN_BASIC_JSON_TPL& get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; - - /*! - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - const NLOHMANN_BASIC_JSON_TPL& get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const; - - /*! - @brief split the string input to reference tokens - - @note This function is only called by the json_pointer constructor. - All exceptions below are documented there. - - @throw parse_error.107 if the pointer is not empty or begins with '/' - @throw parse_error.108 if character '~' is not followed by '0' or '1' - */ - static std::vector<std::string> split(const std::string& reference_string) - { - std::vector<std::string> result; - - // special case: empty reference string -> no reference tokens - if (reference_string.empty()) - { - return result; - } - - // check if nonempty reference string begins with slash - if (JSON_UNLIKELY(reference_string[0] != '/')) - { - JSON_THROW(detail::parse_error::create(107, 1, - "JSON pointer must be empty or begin with '/' - was: '" + - reference_string + "'")); - } - - // extract the reference tokens: - // - slash: position of the last read slash (or end of string) - // - start: position after the previous slash - for ( - // search for the first slash after the first character - std::size_t slash = reference_string.find_first_of('/', 1), - // set the beginning of the first reference token - start = 1; - // we can stop if start == string::npos+1 = 0 - start != 0; - // set the beginning of the next reference token - // (will eventually be 0 if slash == std::string::npos) - start = slash + 1, - // find next slash - slash = reference_string.find_first_of('/', start)) - { - // use the text between the beginning of the reference token - // (start) and the last slash (slash). - auto reference_token = reference_string.substr(start, slash - start); - - // check reference tokens are properly escaped - for (std::size_t pos = reference_token.find_first_of('~'); - pos != std::string::npos; - pos = reference_token.find_first_of('~', pos + 1)) - { - assert(reference_token[pos] == '~'); - - // ~ must be followed by 0 or 1 - if (JSON_UNLIKELY(pos == reference_token.size() - 1 or - (reference_token[pos + 1] != '0' and - reference_token[pos + 1] != '1'))) - { - JSON_THROW(detail::parse_error::create(108, 0, "escape character '~' must be followed with '0' or '1'")); - } - } - - // finally, store the reference token - unescape(reference_token); - result.push_back(reference_token); - } - - return result; - } - - /*! - @brief replace all occurrences of a substring by another string - - @param[in,out] s the string to manipulate; changed so that all - occurrences of @a f are replaced with @a t - @param[in] f the substring to replace with @a t - @param[in] t the string to replace @a f - - @pre The search string @a f must not be empty. **This precondition is - enforced with an assertion.** - - @since version 2.0.0 - */ - static void replace_substring(std::string& s, const std::string& f, - const std::string& t) - { - assert(not f.empty()); - for (auto pos = s.find(f); // find first occurrence of f - pos != std::string::npos; // make sure f was found - s.replace(pos, f.size(), t), // replace with t, and - pos = s.find(f, pos + t.size())) // find next occurrence of f - {} - } - - /// escape "~"" to "~0" and "/" to "~1" - static std::string escape(std::string s) - { - replace_substring(s, "~", "~0"); - replace_substring(s, "/", "~1"); - return s; - } - - /// unescape "~1" to tilde and "~0" to slash (order is important!) - static void unescape(std::string& s) - { - replace_substring(s, "~1", "/"); - replace_substring(s, "~0", "~"); - } - - /*! - @param[in] reference_string the reference string to the current value - @param[in] value the value to consider - @param[in,out] result the result object to insert values to - - @note Empty objects or arrays are flattened to `null`. - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - static void flatten(const std::string& reference_string, - const NLOHMANN_BASIC_JSON_TPL& value, - NLOHMANN_BASIC_JSON_TPL& result); - - /*! - @param[in] value flattened JSON - - @return unflattened JSON - - @throw parse_error.109 if array index is not a number - @throw type_error.314 if value is not an object - @throw type_error.315 if object values are not primitive - @throw type_error.313 if value cannot be unflattened - */ - NLOHMANN_BASIC_JSON_TPL_DECLARATION - static NLOHMANN_BASIC_JSON_TPL - unflatten(const NLOHMANN_BASIC_JSON_TPL& value); - - friend bool operator==(json_pointer const& lhs, - json_pointer const& rhs) noexcept; - - friend bool operator!=(json_pointer const& lhs, - json_pointer const& rhs) noexcept; - - /// the reference tokens - std::vector<std::string> reference_tokens; -}; - -/*! -@brief a class to store JSON values - -@tparam ObjectType type for JSON objects (`std::map` by default; will be used -in @ref object_t) -@tparam ArrayType type for JSON arrays (`std::vector` by default; will be used -in @ref array_t) -@tparam StringType type for JSON strings and object keys (`std::string` by -default; will be used in @ref string_t) -@tparam BooleanType type for JSON booleans (`bool` by default; will be used -in @ref boolean_t) -@tparam NumberIntegerType type for JSON integer numbers (`int64_t` by -default; will be used in @ref number_integer_t) -@tparam NumberUnsignedType type for JSON unsigned integer numbers (@c -`uint64_t` by default; will be used in @ref number_unsigned_t) -@tparam NumberFloatType type for JSON floating-point numbers (`double` by -default; will be used in @ref number_float_t) -@tparam AllocatorType type of the allocator to use (`std::allocator` by -default) -@tparam JSONSerializer the serializer to resolve internal calls to `to_json()` -and `from_json()` (@ref adl_serializer by default) - -@requirement The class satisfies the following concept requirements: -- Basic - - [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible): - JSON values can be default constructed. The result will be a JSON null - value. - - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): - A JSON value can be constructed from an rvalue argument. - - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): - A JSON value can be copy-constructed from an lvalue expression. - - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): - A JSON value van be assigned from an rvalue argument. - - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): - A JSON value can be copy-assigned from an lvalue expression. - - [Destructible](http://en.cppreference.com/w/cpp/concept/Destructible): - JSON values can be destructed. -- Layout - - [StandardLayoutType](http://en.cppreference.com/w/cpp/concept/StandardLayoutType): - JSON values have - [standard layout](http://en.cppreference.com/w/cpp/language/data_members#Standard_layout): - All non-static data members are private and standard layout types, the - class has no virtual functions or (virtual) base classes. -- Library-wide - - [EqualityComparable](http://en.cppreference.com/w/cpp/concept/EqualityComparable): - JSON values can be compared with `==`, see @ref - operator==(const_reference,const_reference). - - [LessThanComparable](http://en.cppreference.com/w/cpp/concept/LessThanComparable): - JSON values can be compared with `<`, see @ref - operator<(const_reference,const_reference). - - [Swappable](http://en.cppreference.com/w/cpp/concept/Swappable): - Any JSON lvalue or rvalue of can be swapped with any lvalue or rvalue of - other compatible types, using unqualified function call @ref swap(). - - [NullablePointer](http://en.cppreference.com/w/cpp/concept/NullablePointer): - JSON values can be compared against `std::nullptr_t` objects which are used - to model the `null` value. -- Container - - [Container](http://en.cppreference.com/w/cpp/concept/Container): - JSON values can be used like STL containers and provide iterator access. - - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer); - JSON values can be used like STL containers and provide reverse iterator - access. - -@invariant The member variables @a m_value and @a m_type have the following -relationship: -- If `m_type == value_t::object`, then `m_value.object != nullptr`. -- If `m_type == value_t::array`, then `m_value.array != nullptr`. -- If `m_type == value_t::string`, then `m_value.string != nullptr`. -The invariants are checked by member function assert_invariant(). - -@internal -@note ObjectType trick from http://stackoverflow.com/a/9860911 -@endinternal - -@see [RFC 7159: The JavaScript Object Notation (JSON) Data Interchange -Format](http://rfc7159.net/rfc7159) - -@since version 1.0.0 - -@nosubgrouping -*/ -NLOHMANN_BASIC_JSON_TPL_DECLARATION -class basic_json -{ - private: - template<detail::value_t> friend struct detail::external_constructor; - friend ::nlohmann::json_pointer; - friend ::nlohmann::detail::parser<basic_json>; - friend ::nlohmann::detail::serializer<basic_json>; - template<typename BasicJsonType> - friend class ::nlohmann::detail::iter_impl; - template<typename BasicJsonType, typename CharType> - friend class ::nlohmann::detail::binary_writer; - template<typename BasicJsonType> - friend class ::nlohmann::detail::binary_reader; - - /// workaround type for MSVC - using basic_json_t = NLOHMANN_BASIC_JSON_TPL; - - // convenience aliases for types residing in namespace detail; - using lexer = ::nlohmann::detail::lexer<basic_json>; - using parser = ::nlohmann::detail::parser<basic_json>; - - using primitive_iterator_t = ::nlohmann::detail::primitive_iterator_t; - template<typename BasicJsonType> - using internal_iterator = ::nlohmann::detail::internal_iterator<BasicJsonType>; - template<typename BasicJsonType> - using iter_impl = ::nlohmann::detail::iter_impl<BasicJsonType>; - template<typename Iterator> - using iteration_proxy = ::nlohmann::detail::iteration_proxy<Iterator>; - template<typename Base> using json_reverse_iterator = ::nlohmann::detail::json_reverse_iterator<Base>; - - template<typename CharType> - using output_adapter_t = ::nlohmann::detail::output_adapter_t<CharType>; - - using binary_reader = ::nlohmann::detail::binary_reader<basic_json>; - template<typename CharType> using binary_writer = ::nlohmann::detail::binary_writer<basic_json, CharType>; - - using serializer = ::nlohmann::detail::serializer<basic_json>; - - public: - using value_t = detail::value_t; - // forward declarations - using json_pointer = ::nlohmann::json_pointer; - template<typename T, typename SFINAE> - using json_serializer = JSONSerializer<T, SFINAE>; - - using initializer_list_t = std::initializer_list<detail::json_ref<basic_json>>; - - //////////////// - // exceptions // - //////////////// - - /// @name exceptions - /// Classes to implement user-defined exceptions. - /// @{ - - /// @copydoc detail::exception - using exception = detail::exception; - /// @copydoc detail::parse_error - using parse_error = detail::parse_error; - /// @copydoc detail::invalid_iterator - using invalid_iterator = detail::invalid_iterator; - /// @copydoc detail::type_error - using type_error = detail::type_error; - /// @copydoc detail::out_of_range - using out_of_range = detail::out_of_range; - /// @copydoc detail::other_error - using other_error = detail::other_error; - - /// @} - - - ///////////////////// - // container types // - ///////////////////// - - /// @name container types - /// The canonic container types to use @ref basic_json like any other STL - /// container. - /// @{ - - /// the type of elements in a basic_json container - using value_type = basic_json; - - /// the type of an element reference - using reference = value_type&; - /// the type of an element const reference - using const_reference = const value_type&; - - /// a type to represent differences between iterators - using difference_type = std::ptrdiff_t; - /// a type to represent container sizes - using size_type = std::size_t; - - /// the allocator type - using allocator_type = AllocatorType<basic_json>; - - /// the type of an element pointer - using pointer = typename std::allocator_traits<allocator_type>::pointer; - /// the type of an element const pointer - using const_pointer = typename std::allocator_traits<allocator_type>::const_pointer; - - /// an iterator for a basic_json container - using iterator = iter_impl<basic_json>; - /// a const iterator for a basic_json container - using const_iterator = iter_impl<const basic_json>; - /// a reverse iterator for a basic_json container - using reverse_iterator = json_reverse_iterator<typename basic_json::iterator>; - /// a const reverse iterator for a basic_json container - using const_reverse_iterator = json_reverse_iterator<typename basic_json::const_iterator>; - - /// @} - - - /*! - @brief returns the allocator associated with the container - */ - static allocator_type get_allocator() - { - return allocator_type(); - } - - /*! - @brief returns version information on the library - - This function returns a JSON object with information about the library, - including the version number and information on the platform and compiler. - - @return JSON object holding version information - key | description - ----------- | --------------- - `compiler` | Information on the used compiler. It is an object with the following keys: `c++` (the used C++ standard), `family` (the compiler family; possible values are `clang`, `icc`, `gcc`, `ilecpp`, `msvc`, `pgcpp`, `sunpro`, and `unknown`), and `version` (the compiler version). - `copyright` | The copyright line for the library as string. - `name` | The name of the library as string. - `platform` | The used platform as string. Possible values are `win32`, `linux`, `apple`, `unix`, and `unknown`. - `url` | The URL of the project as string. - `version` | The version of the library. It is an object with the following keys: `major`, `minor`, and `patch` as defined by [Semantic Versioning](http://semver.org), and `string` (the version string). - - @liveexample{The following code shows an example output of the `meta()` - function.,meta} - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @complexity Constant. - - @since 2.1.0 - */ - static basic_json meta() - { - basic_json result; - - result["copyright"] = "(C) 2013-2017 Niels Lohmann"; - result["name"] = "JSON for Modern C++"; - result["url"] = "https://github.com/nlohmann/json"; - result["version"] = - { - {"string", "2.1.1"}, {"major", 2}, {"minor", 1}, {"patch", 1} - }; - -#ifdef _WIN32 - result["platform"] = "win32"; -#elif defined __linux__ - result["platform"] = "linux"; -#elif defined __APPLE__ - result["platform"] = "apple"; -#elif defined __unix__ - result["platform"] = "unix"; -#else - result["platform"] = "unknown"; -#endif - -#if defined(__ICC) || defined(__INTEL_COMPILER) - result["compiler"] = {{"family", "icc"}, {"version", __INTEL_COMPILER}}; -#elif defined(__clang__) - result["compiler"] = {{"family", "clang"}, {"version", __clang_version__}}; -#elif defined(__GNUC__) || defined(__GNUG__) - result["compiler"] = {{"family", "gcc"}, {"version", std::to_string(__GNUC__) + "." + std::to_string(__GNUC_MINOR__) + "." + std::to_string(__GNUC_PATCHLEVEL__)}}; -#elif defined(__HP_cc) || defined(__HP_aCC) - result["compiler"] = "hp" -#elif defined(__IBMCPP__) - result["compiler"] = {{"family", "ilecpp"}, {"version", __IBMCPP__}}; -#elif defined(_MSC_VER) - result["compiler"] = {{"family", "msvc"}, {"version", _MSC_VER}}; -#elif defined(__PGI) - result["compiler"] = {{"family", "pgcpp"}, {"version", __PGI}}; -#elif defined(__SUNPRO_CC) - result["compiler"] = {{"family", "sunpro"}, {"version", __SUNPRO_CC}}; -#else - result["compiler"] = {{"family", "unknown"}, {"version", "unknown"}}; -#endif - -#ifdef __cplusplus - result["compiler"]["c++"] = std::to_string(__cplusplus); -#else - result["compiler"]["c++"] = "unknown"; -#endif - return result; - } - - - /////////////////////////// - // JSON value data types // - /////////////////////////// - - /// @name JSON value data types - /// The data types to store a JSON value. These types are derived from - /// the template arguments passed to class @ref basic_json. - /// @{ - - /*! - @brief a type for an object - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON objects as follows: - > An object is an unordered collection of zero or more name/value pairs, - > where a name is a string and a value is a string, number, boolean, null, - > object, or array. - - To store objects in C++, a type is defined by the template parameters - described below. - - @tparam ObjectType the container to store objects (e.g., `std::map` or - `std::unordered_map`) - @tparam StringType the type of the keys or names (e.g., `std::string`). - The comparison function `std::less<StringType>` is used to order elements - inside the container. - @tparam AllocatorType the allocator to use for objects (e.g., - `std::allocator`) - - #### Default type - - With the default values for @a ObjectType (`std::map`), @a StringType - (`std::string`), and @a AllocatorType (`std::allocator`), the default - value for @a object_t is: - - @code {.cpp} - std::map< - std::string, // key_type - basic_json, // value_type - std::less<std::string>, // key_compare - std::allocator<std::pair<const std::string, basic_json>> // allocator_type - > - @endcode - - #### Behavior - - The choice of @a object_t influences the behavior of the JSON class. With - the default type, objects have the following behavior: - - - When all names are unique, objects will be interoperable in the sense - that all software implementations receiving that object will agree on - the name-value mappings. - - When the names within an object are not unique, later stored name/value - pairs overwrite previously stored name/value pairs, leaving the used - names unique. For instance, `{"key": 1}` and `{"key": 2, "key": 1}` will - be treated as equal and both stored as `{"key": 1}`. - - Internally, name/value pairs are stored in lexicographical order of the - names. Objects will also be serialized (see @ref dump) in this order. - For instance, `{"b": 1, "a": 2}` and `{"a": 2, "b": 1}` will be stored - and serialized as `{"a": 2, "b": 1}`. - - When comparing objects, the order of the name/value pairs is irrelevant. - This makes objects interoperable in the sense that they will not be - affected by these differences. For instance, `{"b": 1, "a": 2}` and - `{"a": 2, "b": 1}` will be treated as equal. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. - - In this class, the object's limit of nesting is not explicitly constrained. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON object. - - #### Storage - - Objects are stored as pointers in a @ref basic_json type. That is, for any - access to object values, a pointer of type `object_t*` must be - dereferenced. - - @sa @ref array_t -- type for an array value - - @since version 1.0.0 - - @note The order name/value pairs are added to the object is *not* - preserved by the library. Therefore, iterating an object may return - name/value pairs in a different order than they were originally stored. In - fact, keys will be traversed in alphabetical order as `std::map` with - `std::less` is used by default. Please note this behavior conforms to [RFC - 7159](http://rfc7159.net/rfc7159), because any order implements the - specified "unordered" nature of JSON objects. - */ - -#if defined(JSON_HAS_CPP_14) - // Use transparent comparator if possible, combined with perfect forwarding - // on find() and count() calls prevents unnecessary string construction. - using object_comparator_t = std::less<>; -#else - using object_comparator_t = std::less<StringType>; -#endif - using object_t = ObjectType<StringType, - basic_json, - object_comparator_t, - AllocatorType<std::pair<const StringType, - basic_json>>>; - - /*! - @brief a type for an array - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON arrays as follows: - > An array is an ordered sequence of zero or more values. - - To store objects in C++, a type is defined by the template parameters - explained below. - - @tparam ArrayType container type to store arrays (e.g., `std::vector` or - `std::list`) - @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) - - #### Default type - - With the default values for @a ArrayType (`std::vector`) and @a - AllocatorType (`std::allocator`), the default value for @a array_t is: - - @code {.cpp} - std::vector< - basic_json, // value_type - std::allocator<basic_json> // allocator_type - > - @endcode - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the maximum depth of nesting. - - In this class, the array's limit of nesting is not explicitly constrained. - However, a maximum depth of nesting may be introduced by the compiler or - runtime environment. A theoretical limit can be queried by calling the - @ref max_size function of a JSON array. - - #### Storage - - Arrays are stored as pointers in a @ref basic_json type. That is, for any - access to array values, a pointer of type `array_t*` must be dereferenced. - - @sa @ref object_t -- type for an object value - - @since version 1.0.0 - */ - using array_t = ArrayType<basic_json, AllocatorType<basic_json>>; - - /*! - @brief a type for a string - - [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: - > A string is a sequence of zero or more Unicode characters. - - To store objects in C++, a type is defined by the template parameter - described below. Unicode values are split by the JSON class into - byte-sized characters during deserialization. - - @tparam StringType the container to store strings (e.g., `std::string`). - Note this container is used for keys/names in objects, see @ref object_t. - - #### Default type - - With the default values for @a StringType (`std::string`), the default - value for @a string_t is: - - @code {.cpp} - std::string - @endcode - - #### Encoding - - Strings are stored in UTF-8 encoding. Therefore, functions like - `std::string::size()` or `std::string::length()` return the number of - bytes in the string rather than the number of characters or glyphs. - - #### String comparison - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > Software implementations are typically required to test names of object - > members for equality. Implementations that transform the textual - > representation into sequences of Unicode code units and then perform the - > comparison numerically, code unit by code unit, are interoperable in the - > sense that implementations will agree in all cases on equality or - > inequality of two strings. For example, implementations that compare - > strings with escaped characters unconverted may incorrectly find that - > `"a\\b"` and `"a\u005Cb"` are not equal. - - This implementation is interoperable as it does compare strings code unit - by code unit. - - #### Storage - - String values are stored as pointers in a @ref basic_json type. That is, - for any access to string values, a pointer of type `string_t*` must be - dereferenced. - - @since version 1.0.0 - */ - using string_t = StringType; - - /*! - @brief a type for a boolean - - [RFC 7159](http://rfc7159.net/rfc7159) implicitly describes a boolean as a - type which differentiates the two literals `true` and `false`. - - To store objects in C++, a type is defined by the template parameter @a - BooleanType which chooses the type to use. - - #### Default type - - With the default values for @a BooleanType (`bool`), the default value for - @a boolean_t is: - - @code {.cpp} - bool - @endcode - - #### Storage - - Boolean values are stored directly inside a @ref basic_json type. - - @since version 1.0.0 - */ - using boolean_t = BooleanType; - - /*! - @brief a type for a number (integer) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store integer numbers in C++, a type is defined by the template - parameter @a NumberIntegerType which chooses the type to use. - - #### Default type - - With the default values for @a NumberIntegerType (`int64_t`), the default - value for @a number_integer_t is: - - @code {.cpp} - int64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `9223372036854775807` (INT64_MAX) and the minimal integer number - that can be stored is `-9223372036854775808` (INT64_MIN). Integer numbers - that are out of range will yield over/underflow when used in a - constructor. During deserialization, too large or small integer numbers - will be automatically be stored as @ref number_unsigned_t or @ref - number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange of the exactly supported range [INT64_MIN, - INT64_MAX], this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_integer_t = NumberIntegerType; - - /*! - @brief a type for a number (unsigned) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store unsigned integer numbers in C++, a type is defined by the - template parameter @a NumberUnsignedType which chooses the type to use. - - #### Default type - - With the default values for @a NumberUnsignedType (`uint64_t`), the - default value for @a number_unsigned_t is: - - @code {.cpp} - uint64_t - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in integer literals lead to an interpretation as octal - number. Internally, the value will be stored as decimal number. For - instance, the C++ integer literal `010` will be serialized to `8`. - During deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) specifies: - > An implementation may set limits on the range and precision of numbers. - - When the default type is used, the maximal integer number that can be - stored is `18446744073709551615` (UINT64_MAX) and the minimal integer - number that can be stored is `0`. Integer numbers that are out of range - will yield over/underflow when used in a constructor. During - deserialization, too large or small integer numbers will be automatically - be stored as @ref number_integer_t or @ref number_float_t. - - [RFC 7159](http://rfc7159.net/rfc7159) further states: - > Note that when such software is used, numbers that are integers and are - > in the range \f$[-2^{53}+1, 2^{53}-1]\f$ are interoperable in the sense - > that implementations will agree exactly on their numeric values. - - As this range is a subrange (when considered in conjunction with the - number_integer_t type) of the exactly supported range [0, UINT64_MAX], - this class's integer type is interoperable. - - #### Storage - - Integer number values are stored directly inside a @ref basic_json type. - - @sa @ref number_float_t -- type for number values (floating-point) - @sa @ref number_integer_t -- type for number values (integer) - - @since version 2.0.0 - */ - using number_unsigned_t = NumberUnsignedType; - - /*! - @brief a type for a number (floating-point) - - [RFC 7159](http://rfc7159.net/rfc7159) describes numbers as follows: - > The representation of numbers is similar to that used in most - > programming languages. A number is represented in base 10 using decimal - > digits. It contains an integer component that may be prefixed with an - > optional minus sign, which may be followed by a fraction part and/or an - > exponent part. Leading zeros are not allowed. (...) Numeric values that - > cannot be represented in the grammar below (such as Infinity and NaN) - > are not permitted. - - This description includes both integer and floating-point numbers. - However, C++ allows more precise storage if it is known whether the number - is a signed integer, an unsigned integer or a floating-point number. - Therefore, three different types, @ref number_integer_t, @ref - number_unsigned_t and @ref number_float_t are used. - - To store floating-point numbers in C++, a type is defined by the template - parameter @a NumberFloatType which chooses the type to use. - - #### Default type - - With the default values for @a NumberFloatType (`double`), the default - value for @a number_float_t is: - - @code {.cpp} - double - @endcode - - #### Default behavior - - - The restrictions about leading zeros is not enforced in C++. Instead, - leading zeros in floating-point literals will be ignored. Internally, - the value will be stored as decimal number. For instance, the C++ - floating-point literal `01.2` will be serialized to `1.2`. During - deserialization, leading zeros yield an error. - - Not-a-number (NaN) values will be serialized to `null`. - - #### Limits - - [RFC 7159](http://rfc7159.net/rfc7159) states: - > This specification allows implementations to set limits on the range and - > precision of numbers accepted. Since software that implements IEEE - > 754-2008 binary64 (double precision) numbers is generally available and - > widely used, good interoperability can be achieved by implementations - > that expect no more precision or range than these provide, in the sense - > that implementations will approximate JSON numbers within the expected - > precision. - - This implementation does exactly follow this approach, as it uses double - precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` - will be stored as NaN internally and be serialized to `null`. - - #### Storage - - Floating-point number values are stored directly inside a @ref basic_json - type. - - @sa @ref number_integer_t -- type for number values (integer) - - @sa @ref number_unsigned_t -- type for number values (unsigned integer) - - @since version 1.0.0 - */ - using number_float_t = NumberFloatType; - - /// @} - - private: - - /// helper for exception-safe object creation - template<typename T, typename... Args> - static T* create(Args&& ... args) - { - AllocatorType<T> alloc; - auto deleter = [&](T * object) - { - alloc.deallocate(object, 1); - }; - std::unique_ptr<T, decltype(deleter)> object(alloc.allocate(1), deleter); - alloc.construct(object.get(), std::forward<Args>(args)...); - assert(object != nullptr); - return object.release(); - } - - //////////////////////// - // JSON value storage // - //////////////////////// - - /*! - @brief a JSON value - - The actual storage for a JSON value of the @ref basic_json class. This - union combines the different storage types for the JSON value types - defined in @ref value_t. - - JSON type | value_t type | used type - --------- | --------------- | ------------------------ - object | object | pointer to @ref object_t - array | array | pointer to @ref array_t - string | string | pointer to @ref string_t - boolean | boolean | @ref boolean_t - number | number_integer | @ref number_integer_t - number | number_unsigned | @ref number_unsigned_t - number | number_float | @ref number_float_t - null | null | *no value is stored* - - @note Variable-length types (objects, arrays, and strings) are stored as - pointers. The size of the union should not exceed 64 bits if the default - value types are used. - - @since version 1.0.0 - */ - union json_value - { - /// object (stored with pointer to save storage) - object_t* object; - /// array (stored with pointer to save storage) - array_t* array; - /// string (stored with pointer to save storage) - string_t* string; - /// boolean - boolean_t boolean; - /// number (integer) - number_integer_t number_integer; - /// number (unsigned integer) - number_unsigned_t number_unsigned; - /// number (floating-point) - number_float_t number_float; - - /// default constructor (for null values) - json_value() = default; - /// constructor for booleans - json_value(boolean_t v) noexcept : boolean(v) {} - /// constructor for numbers (integer) - json_value(number_integer_t v) noexcept : number_integer(v) {} - /// constructor for numbers (unsigned) - json_value(number_unsigned_t v) noexcept : number_unsigned(v) {} - /// constructor for numbers (floating-point) - json_value(number_float_t v) noexcept : number_float(v) {} - /// constructor for empty values of a given type - json_value(value_t t) - { - switch (t) - { - case value_t::object: - { - object = create<object_t>(); - break; - } - - case value_t::array: - { - array = create<array_t>(); - break; - } - - case value_t::string: - { - string = create<string_t>(""); - break; - } - - case value_t::boolean: - { - boolean = boolean_t(false); - break; - } - - case value_t::number_integer: - { - number_integer = number_integer_t(0); - break; - } - - case value_t::number_unsigned: - { - number_unsigned = number_unsigned_t(0); - break; - } - - case value_t::number_float: - { - number_float = number_float_t(0.0); - break; - } - - case value_t::null: - { - break; - } - - default: - { - if (JSON_UNLIKELY(t == value_t::null)) - { - JSON_THROW(other_error::create(500, "961c151d2e87f2686a955a9be24d316f1362bf21 2.1.1")); // LCOV_EXCL_LINE - } - break; - } - } - } - - /// constructor for strings - json_value(const string_t& value) - { - string = create<string_t>(value); - } - - /// constructor for rvalue strings - json_value(string_t&& value) - { - string = create<string_t>(std::move(value)); - } - - /// constructor for objects - json_value(const object_t& value) - { - object = create<object_t>(value); - } - - /// constructor for rvalue objects - json_value(object_t&& value) - { - object = create<object_t>(std::move(value)); - } - - /// constructor for arrays - json_value(const array_t& value) - { - array = create<array_t>(value); - } - - /// constructor for rvalue arrays - json_value(array_t&& value) - { - array = create<array_t>(std::move(value)); - } - - void destroy(value_t t) - { - switch (t) - { - case value_t::object: - { - AllocatorType<object_t> alloc; - alloc.destroy(object); - alloc.deallocate(object, 1); - break; - } - - case value_t::array: - { - AllocatorType<array_t> alloc; - alloc.destroy(array); - alloc.deallocate(array, 1); - break; - } - - case value_t::string: - { - AllocatorType<string_t> alloc; - alloc.destroy(string); - alloc.deallocate(string, 1); - break; - } - - default: - { - break; - } - } - } - }; - - /*! - @brief checks the class invariants - - This function asserts the class invariants. It needs to be called at the - end of every constructor to make sure that created objects respect the - invariant. Furthermore, it has to be called each time the type of a JSON - value is changed, because the invariant expresses a relationship between - @a m_type and @a m_value. - */ - void assert_invariant() const - { - assert(m_type != value_t::object or m_value.object != nullptr); - assert(m_type != value_t::array or m_value.array != nullptr); - assert(m_type != value_t::string or m_value.string != nullptr); - } - - public: - ////////////////////////// - // JSON parser callback // - ////////////////////////// - - using parse_event_t = typename parser::parse_event_t; - - /*! - @brief per-element parser callback type - - With a parser callback function, the result of parsing a JSON text can be - influenced. When passed to @ref parse, it is called on certain events - (passed as @ref parse_event_t via parameter @a event) with a set recursion - depth @a depth and context JSON value @a parsed. The return value of the - callback function is a boolean indicating whether the element that emitted - the callback shall be kept or not. - - We distinguish six scenarios (determined by the event type) in which the - callback function can be called. The following table describes the values - of the parameters @a depth, @a event, and @a parsed. - - parameter @a event | description | parameter @a depth | parameter @a parsed - ------------------ | ----------- | ------------------ | ------------------- - parse_event_t::object_start | the parser read `{` and started to process a JSON object | depth of the parent of the JSON object | a JSON value with type discarded - parse_event_t::key | the parser read a key of a value in an object | depth of the currently parsed JSON object | a JSON string containing the key - parse_event_t::object_end | the parser read `}` and finished processing a JSON object | depth of the parent of the JSON object | the parsed JSON object - parse_event_t::array_start | the parser read `[` and started to process a JSON array | depth of the parent of the JSON array | a JSON value with type discarded - parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array - parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value - - @image html callback_events.png "Example when certain parse events are triggered" - - Discarding a value (i.e., returning `false`) has different effects - depending on the context in which function was called: - - - Discarded values in structured types are skipped. That is, the parser - will behave as if the discarded value was never read. - - In case a value outside a structured type is skipped, it is replaced - with `null`. This case happens if the top-level element is skipped. - - @param[in] depth the depth of the recursion during parsing - - @param[in] event an event of type parse_event_t indicating the context in - the callback function has been called - - @param[in,out] parsed the current intermediate parse result; note that - writing to this value has no effect for parse_event_t::key events - - @return Whether the JSON value which called the function during parsing - should be kept (`true`) or not (`false`). In the latter case, it is either - skipped completely or replaced by an empty discarded object. - - @sa @ref parse for examples - - @since version 1.0.0 - */ - using parser_callback_t = typename parser::parser_callback_t; - - - ////////////////// - // constructors // - ////////////////// - - /// @name constructors and destructors - /// Constructors of class @ref basic_json, copy/move constructor, copy - /// assignment, static functions creating objects, and the destructor. - /// @{ - - /*! - @brief create an empty value with a given type - - Create an empty JSON value with a given type. The value will be default - initialized with an empty value which depends on the type: - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - - @param[in] v the type of the value to create - - @complexity Constant. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows the constructor for different @ref - value_t values,basic_json__value_t} - - @sa @ref clear() -- restores the postcondition of this constructor - - @since version 1.0.0 - */ - basic_json(const value_t v) - : m_type(v), m_value(v) - { - assert_invariant(); - } - - /*! - @brief create a null object - - Create a `null` JSON value. It either takes a null pointer as parameter - (explicitly creating `null`) or no parameter (implicitly creating `null`). - The passed null pointer itself is not read -- it is only used to choose - the right constructor. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @liveexample{The following code shows the constructor with and without a - null pointer parameter.,basic_json__nullptr_t} - - @since version 1.0.0 - */ - basic_json(std::nullptr_t = nullptr) noexcept - : basic_json(value_t::null) - { - assert_invariant(); - } - - /*! - @brief create a JSON value - - This is a "catch all" constructor for all compatible JSON types; that is, - types for which a `to_json()` method exsits. The constructor forwards the - parameter @a val to that method (to `json_serializer<U>::to_json` method - with `U = uncvref_t<CompatibleType>`, to be exact). - - Template type @a CompatibleType includes, but is not limited to, the - following types: - - **arrays**: @ref array_t and all kinds of compatible containers such as - `std::vector`, `std::deque`, `std::list`, `std::forward_list`, - `std::array`, `std::valarray`, `std::set`, `std::unordered_set`, - `std::multiset`, and `std::unordered_multiset` with a `value_type` from - which a @ref basic_json value can be constructed. - - **objects**: @ref object_t and all kinds of compatible associative - containers such as `std::map`, `std::unordered_map`, `std::multimap`, - and `std::unordered_multimap` with a `key_type` compatible to - @ref string_t and a `value_type` from which a @ref basic_json value can - be constructed. - - **strings**: @ref string_t, string literals, and all compatible string - containers can be used. - - **numbers**: @ref number_integer_t, @ref number_unsigned_t, - @ref number_float_t, and all convertible number types such as `int`, - `size_t`, `int64_t`, `float` or `double` can be used. - - **boolean**: @ref boolean_t / `bool` can be used. - - See the examples below. - - @tparam CompatibleType a type such that: - - @a CompatibleType is not derived from `std::istream`, - - @a CompatibleType is not @ref basic_json (to avoid hijacking copy/move - constructors), - - @a CompatibleType is not a @ref basic_json nested type (e.g., - @ref json_pointer, @ref iterator, etc ...) - - @ref @ref json_serializer<U> has a - `to_json(basic_json_t&, CompatibleType&&)` method - - @tparam U = `uncvref_t<CompatibleType>` - - @param[in] val the value to be forwarded to the respective constructor - - @complexity Usually linear in the size of the passed @a val, also - depending on the implementation of the called `to_json()` - method. - - @exceptionsafety Depends on the called constructor. For types directly - supported by the library (i.e., all types for which no `to_json()` function - was provided), strong guarantee holds: if an exception is thrown, there are - no changes to any JSON value. - - @liveexample{The following code shows the constructor with several - compatible types.,basic_json__CompatibleType} - - @since version 2.1.0 - */ - template<typename CompatibleType, typename U = detail::uncvref_t<CompatibleType>, - detail::enable_if_t<not std::is_base_of<std::istream, U>::value and - not std::is_same<U, basic_json_t>::value and - not detail::is_basic_json_nested_type< - basic_json_t, U>::value and - detail::has_to_json<basic_json, U>::value, - int> = 0> - basic_json(CompatibleType && val) noexcept(noexcept(JSONSerializer<U>::to_json( - std::declval<basic_json_t&>(), std::forward<CompatibleType>(val)))) - { - JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val)); - assert_invariant(); - } - - /*! - @brief create a container (array or object) from an initializer list - - Creates a JSON value of type array or object from the passed initializer - list @a init. In case @a type_deduction is `true` (default), the type of - the JSON value to be created is deducted from the initializer list @a init - according to the following rules: - - 1. If the list is empty, an empty JSON object value `{}` is created. - 2. If the list consists of pairs whose first element is a string, a JSON - object value is created where the first elements of the pairs are - treated as keys and the second elements are as values. - 3. In all other cases, an array is created. - - The rules aim to create the best fit between a C++ initializer list and - JSON values. The rationale is as follows: - - 1. The empty initializer list is written as `{}` which is exactly an empty - JSON object. - 2. C++ has no way of describing mapped types other than to list a list of - pairs. As JSON requires that keys must be of type string, rule 2 is the - weakest constraint one can pose on initializer lists to interpret them - as an object. - 3. In all other cases, the initializer list could not be interpreted as - JSON object type, so interpreting it as JSON array type is safe. - - With the rules described above, the following JSON values cannot be - expressed by an initializer list: - - - the empty array (`[]`): use @ref array(initializer_list_t) - with an empty initializer list in this case - - arrays whose elements satisfy rule 2: use @ref - array(initializer_list_t) with the same initializer list - in this case - - @note When used without parentheses around an empty initializer list, @ref - basic_json() is called instead of this function, yielding the JSON null - value. - - @param[in] init initializer list with JSON values - - @param[in] type_deduction internal parameter; when set to `true`, the type - of the JSON value is deducted from the initializer list @a init; when set - to `false`, the type provided via @a manual_type is forced. This mode is - used by the functions @ref array(initializer_list_t) and - @ref object(initializer_list_t). - - @param[in] manual_type internal parameter; when @a type_deduction is set - to `false`, the created JSON value will use the provided type (only @ref - value_t::array and @ref value_t::object are valid); when @a type_deduction - is set to `true`, this parameter has no effect - - @throw type_error.301 if @a type_deduction is `false`, @a manual_type is - `value_t::object`, but @a init contains an element which is not a pair - whose first element is a string. In this case, the constructor could not - create an object. If @a type_deduction would have be `true`, an array - would have been created. See @ref object(initializer_list_t) - for an example. - - @complexity Linear in the size of the initializer list @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The example below shows how JSON values are created from - initializer lists.,basic_json__list_init_t} - - @sa @ref array(initializer_list_t) -- create a JSON array - value from an initializer list - @sa @ref object(initializer_list_t) -- create a JSON object - value from an initializer list - - @since version 1.0.0 - */ - basic_json(initializer_list_t init, - bool type_deduction = true, - value_t manual_type = value_t::array) - { - // check if each element is an array with two elements whose first - // element is a string - bool is_an_object = std::all_of(init.begin(), init.end(), - [](const detail::json_ref<basic_json>& element_ref) - { - return (element_ref->is_array() and element_ref->size() == 2 and (*element_ref)[0].is_string()); - }); - - // adjust type if type deduction is not wanted - if (not type_deduction) - { - // if array is wanted, do not create an object though possible - if (manual_type == value_t::array) - { - is_an_object = false; - } - - // if object is wanted but impossible, throw an exception - if (JSON_UNLIKELY(manual_type == value_t::object and not is_an_object)) - { - JSON_THROW(type_error::create(301, "cannot create object from initializer list")); - } - } - - if (is_an_object) - { - // the initializer list is a list of pairs -> create object - m_type = value_t::object; - m_value = value_t::object; - - std::for_each(init.begin(), init.end(), [this](const detail::json_ref<basic_json>& element_ref) - { - auto element = element_ref.moved_or_copied(); - m_value.object->emplace( - std::move(*((*element.m_value.array)[0].m_value.string)), - std::move((*element.m_value.array)[1])); - }); - } - else - { - // the initializer list describes an array -> create array - m_type = value_t::array; - m_value.array = create<array_t>(init.begin(), init.end()); - } - - assert_invariant(); - } - - /*! - @brief explicitly create an array from an initializer list - - Creates a JSON array value from a given initializer list. That is, given a - list of values `a, b, c`, creates the JSON value `[a, b, c]`. If the - initializer list is empty, the empty array `[]` is created. - - @note This function is only needed to express two edge cases that cannot - be realized with the initializer list constructor (@ref - basic_json(initializer_list_t, bool, value_t)). These cases - are: - 1. creating an array whose elements are all pairs whose first element is a - string -- in this case, the initializer list constructor would create an - object, taking the first elements as keys - 2. creating an empty array -- passing the empty initializer list to the - initializer list constructor yields an empty object - - @param[in] init initializer list with JSON values to create an array from - (optional) - - @return JSON array value - - @complexity Linear in the size of @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows an example for the `array` - function.,array} - - @sa @ref basic_json(initializer_list_t, bool, value_t) -- - create a JSON value from an initializer list - @sa @ref object(initializer_list_t) -- create a JSON object - value from an initializer list - - @since version 1.0.0 - */ - static basic_json array(initializer_list_t init = {}) - { - return basic_json(init, false, value_t::array); - } - - /*! - @brief explicitly create an object from an initializer list - - Creates a JSON object value from a given initializer list. The initializer - lists elements must be pairs, and their first elements must be strings. If - the initializer list is empty, the empty object `{}` is created. - - @note This function is only added for symmetry reasons. In contrast to the - related function @ref array(initializer_list_t), there are - no cases which can only be expressed by this function. That is, any - initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(initializer_list_t, bool, value_t). - - @param[in] init initializer list to create an object from (optional) - - @return JSON object value - - @throw type_error.301 if @a init is not a list of pairs whose first - elements are strings. In this case, no object can be created. When such a - value is passed to @ref basic_json(initializer_list_t, bool, value_t), - an array would have been created from the passed initializer list @a init. - See example below. - - @complexity Linear in the size of @a init. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows an example for the `object` - function.,object} - - @sa @ref basic_json(initializer_list_t, bool, value_t) -- - create a JSON value from an initializer list - @sa @ref array(initializer_list_t) -- create a JSON array - value from an initializer list - - @since version 1.0.0 - */ - static basic_json object(initializer_list_t init = {}) - { - return basic_json(init, false, value_t::object); - } - - /*! - @brief construct an array with count copies of given value - - Constructs a JSON array value by creating @a cnt copies of a passed value. - In case @a cnt is `0`, an empty array is created. - - @param[in] cnt the number of JSON copies of @a val to create - @param[in] val the JSON value to copy - - @post `std::distance(begin(),end()) == cnt` holds. - - @complexity Linear in @a cnt. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The following code shows examples for the @ref - basic_json(size_type\, const basic_json&) - constructor.,basic_json__size_type_basic_json} - - @since version 1.0.0 - */ - basic_json(size_type cnt, const basic_json& val) - : m_type(value_t::array) - { - m_value.array = create<array_t>(cnt, val); - assert_invariant(); - } - - /*! - @brief construct a JSON container given an iterator range - - Constructs the JSON value with the contents of the range `[first, last)`. - The semantics depends on the different types a JSON value can have: - - In case of a null type, invalid_iterator.206 is thrown. - - In case of other primitive types (number, boolean, or string), @a first - must be `begin()` and @a last must be `end()`. In this case, the value is - copied. Otherwise, invalid_iterator.204 is thrown. - - In case of structured types (array, object), the constructor behaves as - similar versions for `std::vector` or `std::map`; that is, a JSON array - or object is constructed from the values in the range. - - @tparam InputIT an input iterator type (@ref iterator or @ref - const_iterator) - - @param[in] first begin of the range to copy from (included) - @param[in] last end of the range to copy from (excluded) - - @pre Iterators @a first and @a last must be initialized. **This - precondition is enforced with an assertion (see warning).** If - assertions are switched off, a violation of this precondition yields - undefined behavior. - - @pre Range `[first, last)` is valid. Usually, this precondition cannot be - checked efficiently. Only certain edge cases are detected; see the - description of the exceptions below. A violation of this precondition - yields undefined behavior. - - @warning A precondition is enforced with a runtime assertion that will - result in calling `std::abort` if this precondition is not met. - Assertions can be disabled by defining `NDEBUG` at compile time. - See http://en.cppreference.com/w/cpp/error/assert for more - information. - - @throw invalid_iterator.201 if iterators @a first and @a last are not - compatible (i.e., do not belong to the same JSON value). In this case, - the range `[first, last)` is undefined. - @throw invalid_iterator.204 if iterators @a first and @a last belong to a - primitive type (number, boolean, or string), but @a first does not point - to the first element any more. In this case, the range `[first, last)` is - undefined. See example code below. - @throw invalid_iterator.206 if iterators @a first and @a last belong to a - null value. In this case, the range `[first, last)` is undefined. - - @complexity Linear in distance between @a first and @a last. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @liveexample{The example below shows several ways to create JSON values by - specifying a subrange with iterators.,basic_json__InputIt_InputIt} - - @since version 1.0.0 - */ - template<class InputIT, typename std::enable_if< - std::is_same<InputIT, typename basic_json_t::iterator>::value or - std::is_same<InputIT, typename basic_json_t::const_iterator>::value, int>::type = 0> - basic_json(InputIT first, InputIT last) - { - assert(first.m_object != nullptr); - assert(last.m_object != nullptr); - - // make sure iterator fits the current value - if (JSON_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(201, "iterators are not compatible")); - } - - // copy type from first iterator - m_type = first.m_object->m_type; - - // check if iterator range is complete for primitive values - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (JSON_UNLIKELY(not first.m_it.primitive_iterator.is_begin() - or not last.m_it.primitive_iterator.is_end())) - { - JSON_THROW(invalid_iterator::create(204, "iterators out of range")); - } - break; - } - - default: - break; - } - - switch (m_type) - { - case value_t::number_integer: - { - m_value.number_integer = first.m_object->m_value.number_integer; - break; - } - - case value_t::number_unsigned: - { - m_value.number_unsigned = first.m_object->m_value.number_unsigned; - break; - } - - case value_t::number_float: - { - m_value.number_float = first.m_object->m_value.number_float; - break; - } - - case value_t::boolean: - { - m_value.boolean = first.m_object->m_value.boolean; - break; - } - - case value_t::string: - { - m_value = *first.m_object->m_value.string; - break; - } - - case value_t::object: - { - m_value.object = create<object_t>(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } - - case value_t::array: - { - m_value.array = create<array_t>(first.m_it.array_iterator, - last.m_it.array_iterator); - break; - } - - default: - JSON_THROW(invalid_iterator::create(206, "cannot construct with iterators from " + - std::string(first.m_object->type_name()))); - } - - assert_invariant(); - } - - - /////////////////////////////////////// - // other constructors and destructor // - /////////////////////////////////////// - - /// @private - basic_json(const detail::json_ref<basic_json>& ref) - : basic_json(ref.moved_or_copied()) - {} - - /*! - @brief copy constructor - - Creates a copy of a given JSON value. - - @param[in] other the JSON value to copy - - @post `*this == other` - - @complexity Linear in the size of @a other. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes to any JSON value. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is linear. - - As postcondition, it holds: `other == basic_json(other)`. - - @liveexample{The following code shows an example for the copy - constructor.,basic_json__basic_json} - - @since version 1.0.0 - */ - basic_json(const basic_json& other) - : m_type(other.m_type) - { - // check of passed value is valid - other.assert_invariant(); - - switch (m_type) - { - case value_t::object: - { - m_value = *other.m_value.object; - break; - } - - case value_t::array: - { - m_value = *other.m_value.array; - break; - } - - case value_t::string: - { - m_value = *other.m_value.string; - break; - } - - case value_t::boolean: - { - m_value = other.m_value.boolean; - break; - } - - case value_t::number_integer: - { - m_value = other.m_value.number_integer; - break; - } - - case value_t::number_unsigned: - { - m_value = other.m_value.number_unsigned; - break; - } - - case value_t::number_float: - { - m_value = other.m_value.number_float; - break; - } - - default: - break; - } - - assert_invariant(); - } - - /*! - @brief move constructor - - Move constructor. Constructs a JSON value with the contents of the given - value @a other using move semantics. It "steals" the resources from @a - other and leaves it as JSON null value. - - @param[in,out] other value to move to this object - - @post `*this` has the same value as @a other before the call. - @post @a other is a JSON null value. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this constructor never throws - exceptions. - - @requirement This function helps `basic_json` satisfying the - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible) - requirements. - - @liveexample{The code below shows the move constructor explicitly called - via std::move.,basic_json__moveconstructor} - - @since version 1.0.0 - */ - basic_json(basic_json&& other) noexcept - : m_type(std::move(other.m_type)), - m_value(std::move(other.m_value)) - { - // check that passed value is valid - other.assert_invariant(); - - // invalidate payload - other.m_type = value_t::null; - other.m_value = {}; - - assert_invariant(); - } - - /*! - @brief copy assignment - - Copy assignment operator. Copies a JSON value via the "copy and swap" - strategy: It is expressed in terms of the copy constructor, destructor, - and the `swap()` member function. - - @param[in] other value to copy from - - @complexity Linear. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is linear. - - @liveexample{The code below shows and example for the copy assignment. It - creates a copy of value `a` which is then swapped with `b`. Finally\, the - copy of `a` (which is the null value after the swap) is - destroyed.,basic_json__copyassignment} - - @since version 1.0.0 - */ - reference& operator=(basic_json other) noexcept ( - std::is_nothrow_move_constructible<value_t>::value and - std::is_nothrow_move_assignable<value_t>::value and - std::is_nothrow_move_constructible<json_value>::value and - std::is_nothrow_move_assignable<json_value>::value - ) - { - // check that passed value is valid - other.assert_invariant(); - - using std::swap; - swap(m_type, other.m_type); - swap(m_value, other.m_value); - - assert_invariant(); - return *this; - } - - /*! - @brief destructor - - Destroys the JSON value and frees all allocated memory. - - @complexity Linear. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is linear. - - All stored elements are destroyed and all memory is freed. - - @since version 1.0.0 - */ - ~basic_json() - { - assert_invariant(); - m_value.destroy(m_type); - } - - /// @} - - public: - /////////////////////// - // object inspection // - /////////////////////// - - /// @name object inspection - /// Functions to inspect the type of a JSON value. - /// @{ - - /*! - @brief serialization - - Serialization function for JSON values. The function tries to mimic - Python's `json.dumps()` function, and currently supports its @a indent - and @a ensure_ascii parameters. - - @param[in] indent If indent is nonnegative, then array elements and object - members will be pretty-printed with that indent level. An indent level of - `0` will only insert newlines. `-1` (the default) selects the most compact - representation. - @param[in] indent_char The character to use for indentation if @a indent is - greater than `0`. The default is ` ` (space). - @param[in] ensure_ascii If @a ensure_ascii is true, all non-ASCII characters - in the output are escaped with \uXXXX sequences, and the result consists - of ASCII characters only. - - @return string containing the serialization of the JSON value - - @complexity Linear. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @liveexample{The following example shows the effect of different @a indent\, - @a indent_char\, and @a ensure_ascii parameters to the result of the - serialization.,dump} - - @see https://docs.python.org/2/library/json.html#json.dump - - @since version 1.0.0; indentation character @a indent_char and option - @a ensure_ascii added in version 3.0.0 - */ - string_t dump(const int indent = -1, const char indent_char = ' ', - const bool ensure_ascii = false) const - { - string_t result; - serializer s(detail::output_adapter<char>(result), indent_char); - - if (indent >= 0) - { - s.dump(*this, true, ensure_ascii, static_cast<unsigned int>(indent)); - } - else - { - s.dump(*this, false, ensure_ascii, 0); - } - - return result; - } - - /*! - @brief return the type of the JSON value (explicit) - - Return the type of the JSON value as a value from the @ref value_t - enumeration. - - @return the type of the JSON value - Value type | return value - ------------------------- | ------------------------- - null | value_t::null - boolean | value_t::boolean - string | value_t::string - number (integer) | value_t::number_integer - number (unsigned integer) | value_t::number_unsigned - number (foating-point) | value_t::number_float - object | value_t::object - array | value_t::array - discarded | value_t::discarded - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `type()` for all JSON - types.,type} - - @sa @ref operator value_t() -- return the type of the JSON value (implicit) - @sa @ref type_name() -- return the type as string - - @since version 1.0.0 - */ - constexpr value_t type() const noexcept - { - return m_type; - } - - /*! - @brief return whether type is primitive - - This function returns true if and only if the JSON type is primitive - (string, number, boolean, or null). - - @return `true` if type is primitive (string, number, boolean, or null), - `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_primitive()` for all JSON - types.,is_primitive} - - @sa @ref is_structured() -- returns whether JSON value is structured - @sa @ref is_null() -- returns whether JSON value is `null` - @sa @ref is_string() -- returns whether JSON value is a string - @sa @ref is_boolean() -- returns whether JSON value is a boolean - @sa @ref is_number() -- returns whether JSON value is a number - - @since version 1.0.0 - */ - constexpr bool is_primitive() const noexcept - { - return is_null() or is_string() or is_boolean() or is_number(); - } - - /*! - @brief return whether type is structured - - This function returns true if and only if the JSON type is structured - (array or object). - - @return `true` if type is structured (array or object), `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_structured()` for all JSON - types.,is_structured} - - @sa @ref is_primitive() -- returns whether value is primitive - @sa @ref is_array() -- returns whether value is an array - @sa @ref is_object() -- returns whether value is an object - - @since version 1.0.0 - */ - constexpr bool is_structured() const noexcept - { - return is_array() or is_object(); - } - - /*! - @brief return whether value is null - - This function returns true if and only if the JSON value is null. - - @return `true` if type is null, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_null()` for all JSON - types.,is_null} - - @since version 1.0.0 - */ - constexpr bool is_null() const noexcept - { - return (m_type == value_t::null); - } - - /*! - @brief return whether value is a boolean - - This function returns true if and only if the JSON value is a boolean. - - @return `true` if type is boolean, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_boolean()` for all JSON - types.,is_boolean} - - @since version 1.0.0 - */ - constexpr bool is_boolean() const noexcept - { - return (m_type == value_t::boolean); - } - - /*! - @brief return whether value is a number - - This function returns true if and only if the JSON value is a number. This - includes both integer (signed and unsigned) and floating-point values. - - @return `true` if type is number (regardless whether integer, unsigned - integer or floating-type), `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number()` for all JSON - types.,is_number} - - @sa @ref is_number_integer() -- check if value is an integer or unsigned - integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 1.0.0 - */ - constexpr bool is_number() const noexcept - { - return is_number_integer() or is_number_float(); - } - - /*! - @brief return whether value is an integer number - - This function returns true if and only if the JSON value is a signed or - unsigned integer number. This excludes floating-point values. - - @return `true` if type is an integer or unsigned integer number, `false` - otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_integer()` for all - JSON types.,is_number_integer} - - @sa @ref is_number() -- check if value is a number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 1.0.0 - */ - constexpr bool is_number_integer() const noexcept - { - return (m_type == value_t::number_integer or m_type == value_t::number_unsigned); - } - - /*! - @brief return whether value is an unsigned integer number - - This function returns true if and only if the JSON value is an unsigned - integer number. This excludes floating-point and signed integer values. - - @return `true` if type is an unsigned integer number, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_unsigned()` for all - JSON types.,is_number_unsigned} - - @sa @ref is_number() -- check if value is a number - @sa @ref is_number_integer() -- check if value is an integer or unsigned - integer number - @sa @ref is_number_float() -- check if value is a floating-point number - - @since version 2.0.0 - */ - constexpr bool is_number_unsigned() const noexcept - { - return (m_type == value_t::number_unsigned); - } - - /*! - @brief return whether value is a floating-point number - - This function returns true if and only if the JSON value is a - floating-point number. This excludes signed and unsigned integer values. - - @return `true` if type is a floating-point number, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_number_float()` for all - JSON types.,is_number_float} - - @sa @ref is_number() -- check if value is number - @sa @ref is_number_integer() -- check if value is an integer number - @sa @ref is_number_unsigned() -- check if value is an unsigned integer - number - - @since version 1.0.0 - */ - constexpr bool is_number_float() const noexcept - { - return (m_type == value_t::number_float); - } - - /*! - @brief return whether value is an object - - This function returns true if and only if the JSON value is an object. - - @return `true` if type is object, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_object()` for all JSON - types.,is_object} - - @since version 1.0.0 - */ - constexpr bool is_object() const noexcept - { - return (m_type == value_t::object); - } - - /*! - @brief return whether value is an array - - This function returns true if and only if the JSON value is an array. - - @return `true` if type is array, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_array()` for all JSON - types.,is_array} - - @since version 1.0.0 - */ - constexpr bool is_array() const noexcept - { - return (m_type == value_t::array); - } - - /*! - @brief return whether value is a string - - This function returns true if and only if the JSON value is a string. - - @return `true` if type is string, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_string()` for all JSON - types.,is_string} - - @since version 1.0.0 - */ - constexpr bool is_string() const noexcept - { - return (m_type == value_t::string); - } - - /*! - @brief return whether value is discarded - - This function returns true if and only if the JSON value was discarded - during parsing with a callback function (see @ref parser_callback_t). - - @note This function will always be `false` for JSON values after parsing. - That is, discarded values can only occur during parsing, but will be - removed when inside a structured value or replaced by null in other cases. - - @return `true` if type is discarded, `false` otherwise. - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies `is_discarded()` for all JSON - types.,is_discarded} - - @since version 1.0.0 - */ - constexpr bool is_discarded() const noexcept - { - return (m_type == value_t::discarded); - } - - /*! - @brief return the type of the JSON value (implicit) - - Implicitly return the type of the JSON value as a value from the @ref - value_t enumeration. - - @return the type of the JSON value - - @complexity Constant. - - @exceptionsafety No-throw guarantee: this member function never throws - exceptions. - - @liveexample{The following code exemplifies the @ref value_t operator for - all JSON types.,operator__value_t} - - @sa @ref type() -- return the type of the JSON value (explicit) - @sa @ref type_name() -- return the type as string - - @since version 1.0.0 - */ - constexpr operator value_t() const noexcept - { - return m_type; - } - - /// @} - - private: - ////////////////// - // value access // - ////////////////// - - /// get a boolean (explicit) - boolean_t get_impl(boolean_t* /*unused*/) const - { - if (JSON_LIKELY(is_boolean())) - { - return m_value.boolean; - } - - JSON_THROW(type_error::create(302, "type must be boolean, but is " + std::string(type_name()))); - } - - /// get a pointer to the value (object) - object_t* get_impl_ptr(object_t* /*unused*/) noexcept - { - return is_object() ? m_value.object : nullptr; - } - - /// get a pointer to the value (object) - constexpr const object_t* get_impl_ptr(const object_t* /*unused*/) const noexcept - { - return is_object() ? m_value.object : nullptr; - } - - /// get a pointer to the value (array) - array_t* get_impl_ptr(array_t* /*unused*/) noexcept - { - return is_array() ? m_value.array : nullptr; - } - - /// get a pointer to the value (array) - constexpr const array_t* get_impl_ptr(const array_t* /*unused*/) const noexcept - { - return is_array() ? m_value.array : nullptr; - } - - /// get a pointer to the value (string) - string_t* get_impl_ptr(string_t* /*unused*/) noexcept - { - return is_string() ? m_value.string : nullptr; - } - - /// get a pointer to the value (string) - constexpr const string_t* get_impl_ptr(const string_t* /*unused*/) const noexcept - { - return is_string() ? m_value.string : nullptr; - } - - /// get a pointer to the value (boolean) - boolean_t* get_impl_ptr(boolean_t* /*unused*/) noexcept - { - return is_boolean() ? &m_value.boolean : nullptr; - } - - /// get a pointer to the value (boolean) - constexpr const boolean_t* get_impl_ptr(const boolean_t* /*unused*/) const noexcept - { - return is_boolean() ? &m_value.boolean : nullptr; - } - - /// get a pointer to the value (integer number) - number_integer_t* get_impl_ptr(number_integer_t* /*unused*/) noexcept - { - return is_number_integer() ? &m_value.number_integer : nullptr; - } - - /// get a pointer to the value (integer number) - constexpr const number_integer_t* get_impl_ptr(const number_integer_t* /*unused*/) const noexcept - { - return is_number_integer() ? &m_value.number_integer : nullptr; - } - - /// get a pointer to the value (unsigned number) - number_unsigned_t* get_impl_ptr(number_unsigned_t* /*unused*/) noexcept - { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } - - /// get a pointer to the value (unsigned number) - constexpr const number_unsigned_t* get_impl_ptr(const number_unsigned_t* /*unused*/) const noexcept - { - return is_number_unsigned() ? &m_value.number_unsigned : nullptr; - } - - /// get a pointer to the value (floating-point number) - number_float_t* get_impl_ptr(number_float_t* /*unused*/) noexcept - { - return is_number_float() ? &m_value.number_float : nullptr; - } - - /// get a pointer to the value (floating-point number) - constexpr const number_float_t* get_impl_ptr(const number_float_t* /*unused*/) const noexcept - { - return is_number_float() ? &m_value.number_float : nullptr; - } - - /*! - @brief helper function to implement get_ref() - - This function helps to implement get_ref() without code duplication for - const and non-const overloads - - @tparam ThisType will be deduced as `basic_json` or `const basic_json` - - @throw type_error.303 if ReferenceType does not match underlying value - type of the current JSON - */ - template<typename ReferenceType, typename ThisType> - static ReferenceType get_ref_impl(ThisType& obj) - { - // delegate the call to get_ptr<>() - auto ptr = obj.template get_ptr<typename std::add_pointer<ReferenceType>::type>(); - - if (JSON_LIKELY(ptr != nullptr)) - { - return *ptr; - } - - JSON_THROW(type_error::create(303, "incompatible ReferenceType for get_ref, actual type is " + std::string(obj.type_name()))); - } - - public: - /// @name value access - /// Direct access to the stored value of a JSON value. - /// @{ - - /*! - @brief get special-case overload - - This overloads avoids a lot of template boilerplate, it can be seen as the - identity method - - @tparam BasicJsonType == @ref basic_json - - @return a copy of *this - - @complexity Constant. - - @since version 2.1.0 - */ - template < - typename BasicJsonType, - detail::enable_if_t<std::is_same<typename std::remove_const<BasicJsonType>::type, - basic_json_t>::value, - int> = 0 > - basic_json get() const - { - return *this; - } - - /*! - @brief get a value (explicit) - - Explicit type conversion between the JSON value and a compatible value - which is [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) - and [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). - The value is converted by calling the @ref json_serializer<ValueType> - `from_json()` method. - - The function is equivalent to executing - @code {.cpp} - ValueType ret; - JSONSerializer<ValueType>::from_json(*this, ret); - return ret; - @endcode - - This overloads is chosen if: - - @a ValueType is not @ref basic_json, - - @ref json_serializer<ValueType> has a `from_json()` method of the form - `void from_json(const basic_json&, ValueType&)`, and - - @ref json_serializer<ValueType> does not have a `from_json()` method of - the form `ValueType from_json(const basic_json&)` - - @tparam ValueTypeCV the provided value type - @tparam ValueType the returned value type - - @return copy of the JSON value, converted to @a ValueType - - @throw what @ref json_serializer<ValueType> `from_json()` method throws - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector<short>`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map<std::string\, - json>`.,get__ValueType_const} - - @since version 2.1.0 - */ - template < - typename ValueTypeCV, - typename ValueType = detail::uncvref_t<ValueTypeCV>, - detail::enable_if_t < - not std::is_same<basic_json_t, ValueType>::value and - detail::has_from_json<basic_json_t, ValueType>::value and - not detail::has_non_default_from_json<basic_json_t, ValueType>::value, - int > = 0 > - ValueType get() const noexcept(noexcept( - JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>()))) - { - // we cannot static_assert on ValueTypeCV being non-const, because - // there is support for get<const basic_json_t>(), which is why we - // still need the uncvref - static_assert(not std::is_reference<ValueTypeCV>::value, - "get() cannot be used with reference types, you might want to use get_ref()"); - static_assert(std::is_default_constructible<ValueType>::value, - "types must be DefaultConstructible when used with get()"); - - ValueType ret; - JSONSerializer<ValueType>::from_json(*this, ret); - return ret; - } - - /*! - @brief get a value (explicit); special case - - Explicit type conversion between the JSON value and a compatible value - which is **not** [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible) - and **not** [DefaultConstructible](http://en.cppreference.com/w/cpp/concept/DefaultConstructible). - The value is converted by calling the @ref json_serializer<ValueType> - `from_json()` method. - - The function is equivalent to executing - @code {.cpp} - return JSONSerializer<ValueTypeCV>::from_json(*this); - @endcode - - This overloads is chosen if: - - @a ValueType is not @ref basic_json and - - @ref json_serializer<ValueType> has a `from_json()` method of the form - `ValueType from_json(const basic_json&)` - - @note If @ref json_serializer<ValueType> has both overloads of - `from_json()`, this one is chosen. - - @tparam ValueTypeCV the provided value type - @tparam ValueType the returned value type - - @return copy of the JSON value, converted to @a ValueType - - @throw what @ref json_serializer<ValueType> `from_json()` method throws - - @since version 2.1.0 - */ - template < - typename ValueTypeCV, - typename ValueType = detail::uncvref_t<ValueTypeCV>, - detail::enable_if_t<not std::is_same<basic_json_t, ValueType>::value and - detail::has_non_default_from_json<basic_json_t, - ValueType>::value, int> = 0 > - ValueType get() const noexcept(noexcept( - JSONSerializer<ValueTypeCV>::from_json(std::declval<const basic_json_t&>()))) - { - static_assert(not std::is_reference<ValueTypeCV>::value, - "get() cannot be used with reference types, you might want to use get_ref()"); - return JSONSerializer<ValueTypeCV>::from_json(*this); - } - - /*! - @brief get a pointer value (explicit) - - Explicit pointer access to the internally stored JSON value. No copies are - made. - - @warning The pointer becomes invalid if the underlying JSON object - changes. - - @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. - - @return pointer to the internally stored JSON value if the requested - pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - - @complexity Constant. - - @liveexample{The example below shows how pointers to internal values of a - JSON value can be requested. Note that no type conversions are made and a - `nullptr` is returned if the value and the requested pointer type does not - match.,get__PointerType} - - @sa @ref get_ptr() for explicit pointer-member access - - @since version 1.0.0 - */ - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value, int>::type = 0> - PointerType get() noexcept - { - // delegate the call to get_ptr - return get_ptr<PointerType>(); - } - - /*! - @brief get a pointer value (explicit) - @copydoc get() - */ - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value, int>::type = 0> - constexpr const PointerType get() const noexcept - { - // delegate the call to get_ptr - return get_ptr<PointerType>(); - } - - /*! - @brief get a pointer value (implicit) - - Implicit pointer access to the internally stored JSON value. No copies are - made. - - @warning Writing data to the pointee of the result yields an undefined - state. - - @tparam PointerType pointer type; must be a pointer to @ref array_t, @ref - object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, - @ref number_unsigned_t, or @ref number_float_t. Enforced by a static - assertion. - - @return pointer to the internally stored JSON value if the requested - pointer type @a PointerType fits to the JSON value; `nullptr` otherwise - - @complexity Constant. - - @liveexample{The example below shows how pointers to internal values of a - JSON value can be requested. Note that no type conversions are made and a - `nullptr` is returned if the value and the requested pointer type does not - match.,get_ptr} - - @since version 1.0.0 - */ - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value, int>::type = 0> - PointerType get_ptr() noexcept - { - // get the type of the PointerType (remove pointer and const) - using pointee_t = typename std::remove_const<typename - std::remove_pointer<typename - std::remove_const<PointerType>::type>::type>::type; - // make sure the type matches the allowed types - static_assert( - std::is_same<object_t, pointee_t>::value - or std::is_same<array_t, pointee_t>::value - or std::is_same<string_t, pointee_t>::value - or std::is_same<boolean_t, pointee_t>::value - or std::is_same<number_integer_t, pointee_t>::value - or std::is_same<number_unsigned_t, pointee_t>::value - or std::is_same<number_float_t, pointee_t>::value - , "incompatible pointer type"); - - // delegate the call to get_impl_ptr<>() - return get_impl_ptr(static_cast<PointerType>(nullptr)); - } - - /*! - @brief get a pointer value (implicit) - @copydoc get_ptr() - */ - template<typename PointerType, typename std::enable_if< - std::is_pointer<PointerType>::value and - std::is_const<typename std::remove_pointer<PointerType>::type>::value, int>::type = 0> - constexpr const PointerType get_ptr() const noexcept - { - // get the type of the PointerType (remove pointer and const) - using pointee_t = typename std::remove_const<typename - std::remove_pointer<typename - std::remove_const<PointerType>::type>::type>::type; - // make sure the type matches the allowed types - static_assert( - std::is_same<object_t, pointee_t>::value - or std::is_same<array_t, pointee_t>::value - or std::is_same<string_t, pointee_t>::value - or std::is_same<boolean_t, pointee_t>::value - or std::is_same<number_integer_t, pointee_t>::value - or std::is_same<number_unsigned_t, pointee_t>::value - or std::is_same<number_float_t, pointee_t>::value - , "incompatible pointer type"); - - // delegate the call to get_impl_ptr<>() const - return get_impl_ptr(static_cast<PointerType>(nullptr)); - } - - /*! - @brief get a reference value (implicit) - - Implicit reference access to the internally stored JSON value. No copies - are made. - - @warning Writing data to the referee of the result yields an undefined - state. - - @tparam ReferenceType reference type; must be a reference to @ref array_t, - @ref object_t, @ref string_t, @ref boolean_t, @ref number_integer_t, or - @ref number_float_t. Enforced by static assertion. - - @return reference to the internally stored JSON value if the requested - reference type @a ReferenceType fits to the JSON value; throws - type_error.303 otherwise - - @throw type_error.303 in case passed type @a ReferenceType is incompatible - with the stored JSON value; see example below - - @complexity Constant. - - @liveexample{The example shows several calls to `get_ref()`.,get_ref} - - @since version 1.1.0 - */ - template<typename ReferenceType, typename std::enable_if< - std::is_reference<ReferenceType>::value, int>::type = 0> - ReferenceType get_ref() - { - // delegate call to get_ref_impl - return get_ref_impl<ReferenceType>(*this); - } - - /*! - @brief get a reference value (implicit) - @copydoc get_ref() - */ - template<typename ReferenceType, typename std::enable_if< - std::is_reference<ReferenceType>::value and - std::is_const<typename std::remove_reference<ReferenceType>::type>::value, int>::type = 0> - ReferenceType get_ref() const - { - // delegate call to get_ref_impl - return get_ref_impl<ReferenceType>(*this); - } - - /*! - @brief get a value (implicit) - - Implicit type conversion between the JSON value and a compatible value. - The call is realized by calling @ref get() const. - - @tparam ValueType non-pointer type compatible to the JSON value, for - instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays. The character type of @ref string_t - as well as an initializer list of this type is excluded to avoid - ambiguities as these types implicitly convert to `std::string`. - - @return copy of the JSON value, converted to type @a ValueType - - @throw type_error.302 in case passed type @a ValueType is incompatible - to the JSON value type (e.g., the JSON value is of type boolean, but a - string is requested); see example below - - @complexity Linear in the size of the JSON value. - - @liveexample{The example below shows several conversions from JSON values - to other types. There a few things to note: (1) Floating-point numbers can - be converted to integers\, (2) A JSON array can be converted to a standard - `std::vector<short>`\, (3) A JSON object can be converted to C++ - associative containers such as `std::unordered_map<std::string\, - json>`.,operator__ValueType} - - @since version 1.0.0 - */ - template < typename ValueType, typename std::enable_if < - not std::is_pointer<ValueType>::value and - not std::is_same<ValueType, detail::json_ref<basic_json>>::value and - not std::is_same<ValueType, typename string_t::value_type>::value -#ifndef _MSC_VER // fix for issue #167 operator<< ambiguity under VS2015 - and not std::is_same<ValueType, std::initializer_list<typename string_t::value_type>>::value -#endif -#if defined(JSON_HAS_CPP_17) - and not std::is_same<ValueType, typename std::string_view>::value -#endif - , int >::type = 0 > - operator ValueType() const - { - // delegate the call to get<>() const - return get<ValueType>(); - } - - /// @} - - - //////////////////// - // element access // - //////////////////// - - /// @name element access - /// Access to the JSON value. - /// @{ - - /*! - @brief access specified array element with bounds checking - - Returns a reference to the element at specified location @a idx, with - bounds checking. - - @param[in] idx index of the element to access - - @return reference to the element at index @a idx - - @throw type_error.304 if the JSON value is not an array; in this case, - calling `at` with an index makes no sense. See example below. - @throw out_of_range.401 if the index @a idx is out of range of the array; - that is, `idx >= size()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 1.0.0 - - @liveexample{The example below shows how array elements can be read and - written using `at()`. It also demonstrates the different exceptions that - can be thrown.,at__size_type} - */ - reference at(size_type idx) - { - // at only works for arrays - if (JSON_LIKELY(is_array())) - { - JSON_TRY - { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified array element with bounds checking - - Returns a const reference to the element at specified location @a idx, - with bounds checking. - - @param[in] idx index of the element to access - - @return const reference to the element at index @a idx - - @throw type_error.304 if the JSON value is not an array; in this case, - calling `at` with an index makes no sense. See example below. - @throw out_of_range.401 if the index @a idx is out of range of the array; - that is, `idx >= size()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 1.0.0 - - @liveexample{The example below shows how array elements can be read using - `at()`. It also demonstrates the different exceptions that can be thrown., - at__size_type_const} - */ - const_reference at(size_type idx) const - { - // at only works for arrays - if (JSON_LIKELY(is_array())) - { - JSON_TRY - { - return m_value.array->at(idx); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified object element with bounds checking - - Returns a reference to the element at with specified key @a key, with - bounds checking. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.304 if the JSON value is not an object; in this case, - calling `at` with a key makes no sense. See example below. - @throw out_of_range.403 if the key @a key is is not stored in the object; - that is, `find(key) == end()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Logarithmic in the size of the container. - - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - - @liveexample{The example below shows how object elements can be read and - written using `at()`. It also demonstrates the different exceptions that - can be thrown.,at__object_t_key_type} - */ - reference at(const typename object_t::key_type& key) - { - // at only works for objects - if (JSON_LIKELY(is_object())) - { - JSON_TRY - { - return m_value.object->at(key); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified object element with bounds checking - - Returns a const reference to the element at with specified key @a key, - with bounds checking. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @throw type_error.304 if the JSON value is not an object; in this case, - calling `at` with a key makes no sense. See example below. - @throw out_of_range.403 if the key @a key is is not stored in the object; - that is, `find(key) == end()`. See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Logarithmic in the size of the container. - - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - - @liveexample{The example below shows how object elements can be read using - `at()`. It also demonstrates the different exceptions that can be thrown., - at__object_t_key_type_const} - */ - const_reference at(const typename object_t::key_type& key) const - { - // at only works for objects - if (JSON_LIKELY(is_object())) - { - JSON_TRY - { - return m_value.object->at(key); - } - JSON_CATCH (std::out_of_range&) - { - // create better exception explanation - JSON_THROW(out_of_range::create(403, "key '" + key + "' not found")); - } - } - else - { - JSON_THROW(type_error::create(304, "cannot use at() with " + std::string(type_name()))); - } - } - - /*! - @brief access specified array element - - Returns a reference to the element at specified location @a idx. - - @note If @a idx is beyond the range of the array (i.e., `idx >= size()`), - then the array is silently filled up with `null` values to make `idx` a - valid reference to the last stored element. - - @param[in] idx index of the element to access - - @return reference to the element at index @a idx - - @throw type_error.305 if the JSON value is not an array or null; in that - cases, using the [] operator with an index makes no sense. - - @complexity Constant if @a idx is in the range of the array. Otherwise - linear in `idx - size()`. - - @liveexample{The example below shows how array elements can be read and - written using `[]` operator. Note the addition of `null` - values.,operatorarray__size_type} - - @since version 1.0.0 - */ - reference operator[](size_type idx) - { - // implicitly convert null value to an empty array - if (is_null()) - { - m_type = value_t::array; - m_value.array = create<array_t>(); - assert_invariant(); - } - - // operator[] only works for arrays - if (JSON_LIKELY(is_array())) - { - // fill up array with null values if given idx is outside range - if (idx >= m_value.array->size()) - { - m_value.array->insert(m_value.array->end(), - idx - m_value.array->size() + 1, - basic_json()); - } - - return m_value.array->operator[](idx); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); - } - - /*! - @brief access specified array element - - Returns a const reference to the element at specified location @a idx. - - @param[in] idx index of the element to access - - @return const reference to the element at index @a idx - - @throw type_error.305 if the JSON value is not an array; in that cases, - using the [] operator with an index makes no sense. - - @complexity Constant. - - @liveexample{The example below shows how array elements can be read using - the `[]` operator.,operatorarray__size_type_const} - - @since version 1.0.0 - */ - const_reference operator[](size_type idx) const - { - // const operator[] only works for arrays - if (JSON_LIKELY(is_array())) - { - return m_value.array->operator[](idx); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.305 if the JSON value is not an object or null; in that - cases, using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - reference operator[](const typename object_t::key_type& key) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create<object_t>(); - assert_invariant(); - } - - // operator[] only works for objects - if (JSON_LIKELY(is_object())) - { - return m_value.object->operator[](key); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @pre The element with key @a key must exist. **This precondition is - enforced with an assertion.** - - @throw type_error.305 if the JSON value is not an object; in that cases, - using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.0.0 - */ - const_reference operator[](const typename object_t::key_type& key) const - { - // const operator[] only works for objects - if (JSON_LIKELY(is_object())) - { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); - } - - /*! - @brief access specified object element - - Returns a reference to the element at with specified key @a key. - - @note If @a key is not found in the object, then it is silently added to - the object and filled with a `null` value to make `key` a valid reference. - In case the value was `null` before, it is converted to an object. - - @param[in] key key of the element to access - - @return reference to the element at key @a key - - @throw type_error.305 if the JSON value is not an object or null; in that - cases, using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read and - written using the `[]` operator.,operatorarray__key_type} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.1.0 - */ - template<typename T> - reference operator[](T* key) - { - // implicitly convert null to object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // at only works for objects - if (JSON_LIKELY(is_object())) - { - return m_value.object->operator[](key); - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); - } - - /*! - @brief read-only access specified object element - - Returns a const reference to the element at with specified key @a key. No - bounds checking is performed. - - @warning If the element with key @a key does not exist, the behavior is - undefined. - - @param[in] key key of the element to access - - @return const reference to the element at key @a key - - @pre The element with key @a key must exist. **This precondition is - enforced with an assertion.** - - @throw type_error.305 if the JSON value is not an object; in that cases, - using the [] operator with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be read using - the `[]` operator.,operatorarray__key_type_const} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref value() for access by value with a default value - - @since version 1.1.0 - */ - template<typename T> - const_reference operator[](T* key) const - { - // at only works for objects - if (JSON_LIKELY(is_object())) - { - assert(m_value.object->find(key) != m_value.object->end()); - return m_value.object->find(key)->second; - } - - JSON_THROW(type_error::create(305, "cannot use operator[] with " + std::string(type_name()))); - } - - /*! - @brief access specified object element with default value - - Returns either a copy of an object's element at the specified key @a key - or a given default value if no element with key @a key exists. - - The function is basically equivalent to executing - @code {.cpp} - try { - return at(key); - } catch(out_of_range) { - return default_value; - } - @endcode - - @note Unlike @ref at(const typename object_t::key_type&), this function - does not throw if the given key @a key was not found. - - @note Unlike @ref operator[](const typename object_t::key_type& key), this - function does not implicitly add an element to the position defined by @a - key. This function is furthermore also applicable to const objects. - - @param[in] key key of the element to access - @param[in] default_value the value to return if @a key is not found - - @tparam ValueType type compatible to JSON values, for instance `int` for - JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for - JSON arrays. Note the type of the expected value at @a key and the default - value @a default_value must be compatible. - - @return copy of the element at key @a key or @a default_value if @a key - is not found - - @throw type_error.306 if the JSON value is not an objec; in that cases, - using `value()` with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be queried - with a default value.,basic_json__value} - - @sa @ref at(const typename object_t::key_type&) for access by reference - with range checking - @sa @ref operator[](const typename object_t::key_type&) for unchecked - access by reference - - @since version 1.0.0 - */ - template<class ValueType, typename std::enable_if< - std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> - ValueType value(const typename object_t::key_type& key, const ValueType& default_value) const - { - // at only works for objects - if (JSON_LIKELY(is_object())) - { - // if key is found, return value and given default value otherwise - const auto it = find(key); - if (it != end()) - { - return *it; - } - - return default_value; - } - - JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); - } - - /*! - @brief overload for a default value of type const char* - @copydoc basic_json::value(const typename object_t::key_type&, ValueType) const - */ - string_t value(const typename object_t::key_type& key, const char* default_value) const - { - return value(key, string_t(default_value)); - } - - /*! - @brief access specified object element via JSON Pointer with default value - - Returns either a copy of an object's element at the specified key @a key - or a given default value if no element with key @a key exists. - - The function is basically equivalent to executing - @code {.cpp} - try { - return at(ptr); - } catch(out_of_range) { - return default_value; - } - @endcode - - @note Unlike @ref at(const json_pointer&), this function does not throw - if the given key @a key was not found. - - @param[in] ptr a JSON pointer to the element to access - @param[in] default_value the value to return if @a ptr found no value - - @tparam ValueType type compatible to JSON values, for instance `int` for - JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for - JSON arrays. Note the type of the expected value at @a key and the default - value @a default_value must be compatible. - - @return copy of the element at key @a key or @a default_value if @a key - is not found - - @throw type_error.306 if the JSON value is not an objec; in that cases, - using `value()` with a key makes no sense. - - @complexity Logarithmic in the size of the container. - - @liveexample{The example below shows how object elements can be queried - with a default value.,basic_json__value_ptr} - - @sa @ref operator[](const json_pointer&) for unchecked access by reference - - @since version 2.0.2 - */ - template<class ValueType, typename std::enable_if< - std::is_convertible<basic_json_t, ValueType>::value, int>::type = 0> - ValueType value(const json_pointer& ptr, const ValueType& default_value) const - { - // at only works for objects - if (JSON_LIKELY(is_object())) - { - // if pointer resolves a value, return it or use default value - JSON_TRY - { - return ptr.get_checked(this); - } - JSON_CATCH (out_of_range&) - { - return default_value; - } - } - - JSON_THROW(type_error::create(306, "cannot use value() with " + std::string(type_name()))); - } - - /*! - @brief overload for a default value of type const char* - @copydoc basic_json::value(const json_pointer&, ValueType) const - */ - string_t value(const json_pointer& ptr, const char* default_value) const - { - return value(ptr, string_t(default_value)); - } - - /*! - @brief access the first element - - Returns a reference to the first element in the container. For a JSON - container `c`, the expression `c.front()` is equivalent to `*c.begin()`. - - @return In case of a structured type (array or object), a reference to the - first element is returned. In case of number, string, or boolean values, a - reference to the value is returned. - - @complexity Constant. - - @pre The JSON value must not be `null` (would throw `std::out_of_range`) - or an empty array or object (undefined behavior, **guarded by - assertions**). - @post The JSON value remains unchanged. - - @throw invalid_iterator.214 when called on `null` value - - @liveexample{The following code shows an example for `front()`.,front} - - @sa @ref back() -- access the last element - - @since version 1.0.0 - */ - reference front() - { - return *begin(); - } - - /*! - @copydoc basic_json::front() - */ - const_reference front() const - { - return *cbegin(); - } - - /*! - @brief access the last element - - Returns a reference to the last element in the container. For a JSON - container `c`, the expression `c.back()` is equivalent to - @code {.cpp} - auto tmp = c.end(); - --tmp; - return *tmp; - @endcode - - @return In case of a structured type (array or object), a reference to the - last element is returned. In case of number, string, or boolean values, a - reference to the value is returned. - - @complexity Constant. - - @pre The JSON value must not be `null` (would throw `std::out_of_range`) - or an empty array or object (undefined behavior, **guarded by - assertions**). - @post The JSON value remains unchanged. - - @throw invalid_iterator.214 when called on a `null` value. See example - below. - - @liveexample{The following code shows an example for `back()`.,back} - - @sa @ref front() -- access the first element - - @since version 1.0.0 - */ - reference back() - { - auto tmp = end(); - --tmp; - return *tmp; - } - - /*! - @copydoc basic_json::back() - */ - const_reference back() const - { - auto tmp = cend(); - --tmp; - return *tmp; - } - - /*! - @brief remove element given an iterator - - Removes the element specified by iterator @a pos. The iterator @a pos must - be valid and dereferenceable. Thus the `end()` iterator (which is valid, - but is not dereferenceable) cannot be used as a value for @a pos. - - If called on a primitive type other than `null`, the resulting JSON value - will be `null`. - - @param[in] pos iterator to the element to remove - @return Iterator following the last removed element. If the iterator @a - pos refers to the last element, the `end()` iterator is returned. - - @tparam IteratorType an @ref iterator or @ref const_iterator - - @post Invalidates iterators and references at or after the point of the - erase, including the `end()` iterator. - - @throw type_error.307 if called on a `null` value; example: `"cannot use - erase() with null"` - @throw invalid_iterator.202 if called on an iterator which does not belong - to the current JSON value; example: `"iterator does not fit current - value"` - @throw invalid_iterator.205 if called on a primitive type with invalid - iterator (i.e., any iterator which is not `begin()`); example: `"iterator - out of range"` - - @complexity The complexity depends on the type: - - objects: amortized constant - - arrays: linear in distance between @a pos and the end of the container - - strings: linear in the length of the string - - other types: constant - - @liveexample{The example shows the result of `erase()` for different JSON - types.,erase__IteratorType} - - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - template<class IteratorType, typename std::enable_if< - std::is_same<IteratorType, typename basic_json_t::iterator>::value or - std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type - = 0> - IteratorType erase(IteratorType pos) - { - // make sure iterator fits the current value - if (JSON_UNLIKELY(this != pos.m_object)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - IteratorType result = end(); - - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (JSON_UNLIKELY(not pos.m_it.primitive_iterator.is_begin())) - { - JSON_THROW(invalid_iterator::create(205, "iterator out of range")); - } - - if (is_string()) - { - AllocatorType<string_t> alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); - m_value.string = nullptr; - } - - m_type = value_t::null; - assert_invariant(); - break; - } - - case value_t::object: - { - result.m_it.object_iterator = m_value.object->erase(pos.m_it.object_iterator); - break; - } - - case value_t::array: - { - result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); - break; - } - - default: - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - - return result; - } - - /*! - @brief remove elements given an iterator range - - Removes the element specified by the range `[first; last)`. The iterator - @a first does not need to be dereferenceable if `first == last`: erasing - an empty range is a no-op. - - If called on a primitive type other than `null`, the resulting JSON value - will be `null`. - - @param[in] first iterator to the beginning of the range to remove - @param[in] last iterator past the end of the range to remove - @return Iterator following the last removed element. If the iterator @a - second refers to the last element, the `end()` iterator is returned. - - @tparam IteratorType an @ref iterator or @ref const_iterator - - @post Invalidates iterators and references at or after the point of the - erase, including the `end()` iterator. - - @throw type_error.307 if called on a `null` value; example: `"cannot use - erase() with null"` - @throw invalid_iterator.203 if called on iterators which does not belong - to the current JSON value; example: `"iterators do not fit current value"` - @throw invalid_iterator.204 if called on a primitive type with invalid - iterators (i.e., if `first != begin()` and `last != end()`); example: - `"iterators out of range"` - - @complexity The complexity depends on the type: - - objects: `log(size()) + std::distance(first, last)` - - arrays: linear in the distance between @a first and @a last, plus linear - in the distance between @a last and end of the container - - strings: linear in the length of the string - - other types: constant - - @liveexample{The example shows the result of `erase()` for different JSON - types.,erase__IteratorType_IteratorType} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - template<class IteratorType, typename std::enable_if< - std::is_same<IteratorType, typename basic_json_t::iterator>::value or - std::is_same<IteratorType, typename basic_json_t::const_iterator>::value, int>::type - = 0> - IteratorType erase(IteratorType first, IteratorType last) - { - // make sure iterator fits the current value - if (JSON_UNLIKELY(this != first.m_object or this != last.m_object)) - { - JSON_THROW(invalid_iterator::create(203, "iterators do not fit current value")); - } - - IteratorType result = end(); - - switch (m_type) - { - case value_t::boolean: - case value_t::number_float: - case value_t::number_integer: - case value_t::number_unsigned: - case value_t::string: - { - if (JSON_LIKELY(not first.m_it.primitive_iterator.is_begin() - or not last.m_it.primitive_iterator.is_end())) - { - JSON_THROW(invalid_iterator::create(204, "iterators out of range")); - } - - if (is_string()) - { - AllocatorType<string_t> alloc; - alloc.destroy(m_value.string); - alloc.deallocate(m_value.string, 1); - m_value.string = nullptr; - } - - m_type = value_t::null; - assert_invariant(); - break; - } - - case value_t::object: - { - result.m_it.object_iterator = m_value.object->erase(first.m_it.object_iterator, - last.m_it.object_iterator); - break; - } - - case value_t::array: - { - result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, - last.m_it.array_iterator); - break; - } - - default: - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - - return result; - } - - /*! - @brief remove element from a JSON object given a key - - Removes elements from a JSON object with the key value @a key. - - @param[in] key value of the elements to remove - - @return Number of elements removed. If @a ObjectType is the default - `std::map` type, the return value will always be `0` (@a key was not - found) or `1` (@a key was found). - - @post References and iterators to the erased elements are invalidated. - Other references and iterators are not affected. - - @throw type_error.307 when called on a type other than JSON object; - example: `"cannot use erase() with null"` - - @complexity `log(size()) + count(key)` - - @liveexample{The example shows the effect of `erase()`.,erase__key_type} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const size_type) -- removes the element from an array at - the given index - - @since version 1.0.0 - */ - size_type erase(const typename object_t::key_type& key) - { - // this erase only works for objects - if (JSON_LIKELY(is_object())) - { - return m_value.object->erase(key); - } - - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - - /*! - @brief remove element from a JSON array given an index - - Removes element from a JSON array at the index @a idx. - - @param[in] idx index of the element to remove - - @throw type_error.307 when called on a type other than JSON object; - example: `"cannot use erase() with null"` - @throw out_of_range.401 when `idx >= size()`; example: `"array index 17 - is out of range"` - - @complexity Linear in distance between @a idx and the end of the container. - - @liveexample{The example shows the effect of `erase()`.,erase__size_type} - - @sa @ref erase(IteratorType) -- removes the element at a given position - @sa @ref erase(IteratorType, IteratorType) -- removes the elements in - the given range - @sa @ref erase(const typename object_t::key_type&) -- removes the element - from an object at the given key - - @since version 1.0.0 - */ - void erase(const size_type idx) - { - // this erase only works for arrays - if (JSON_LIKELY(is_array())) - { - if (JSON_UNLIKELY(idx >= size())) - { - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - - m_value.array->erase(m_value.array->begin() + static_cast<difference_type>(idx)); - } - else - { - JSON_THROW(type_error::create(307, "cannot use erase() with " + std::string(type_name()))); - } - } - - /// @} - - - //////////// - // lookup // - //////////// - - /// @name lookup - /// @{ - - /*! - @brief find an element in a JSON object - - Finds an element in a JSON object with key equivalent to @a key. If the - element is not found or the JSON value is not an object, end() is - returned. - - @note This method always returns @ref end() when executed on a JSON type - that is not an object. - - @param[in] key key value of the element to search for. - - @return Iterator to an element with key equivalent to @a key. If no such - element is found or the JSON value is not an object, past-the-end (see - @ref end()) iterator is returned. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The example shows how `find()` is used.,find__key_type} - - @since version 1.0.0 - */ - template<typename KeyT> - iterator find(KeyT&& key) - { - auto result = end(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); - } - - return result; - } - - /*! - @brief find an element in a JSON object - @copydoc find(KeyT&&) - */ - template<typename KeyT> - const_iterator find(KeyT&& key) const - { - auto result = cend(); - - if (is_object()) - { - result.m_it.object_iterator = m_value.object->find(std::forward<KeyT>(key)); - } - - return result; - } - - /*! - @brief returns the number of occurrences of a key in a JSON object - - Returns the number of elements with key @a key. If ObjectType is the - default `std::map` type, the return value will always be `0` (@a key was - not found) or `1` (@a key was found). - - @note This method always returns `0` when executed on a JSON type that is - not an object. - - @param[in] key key value of the element to count - - @return Number of elements with key @a key. If the JSON value is not an - object, the return value will be `0`. - - @complexity Logarithmic in the size of the JSON object. - - @liveexample{The example shows how `count()` is used.,count} - - @since version 1.0.0 - */ - template<typename KeyT> - size_type count(KeyT&& key) const - { - // return 0 for all nonobject types - return is_object() ? m_value.object->count(std::forward<KeyT>(key)) : 0; - } - - /// @} - - - /////////////// - // iterators // - /////////////// - - /// @name iterators - /// @{ - - /*! - @brief returns an iterator to the first element - - Returns an iterator to the first element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return iterator to the first element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - @liveexample{The following code shows an example for `begin()`.,begin} - - @sa @ref cbegin() -- returns a const iterator to the beginning - @sa @ref end() -- returns an iterator to the end - @sa @ref cend() -- returns a const iterator to the end - - @since version 1.0.0 - */ - iterator begin() noexcept - { - iterator result(this); - result.set_begin(); - return result; - } - - /*! - @copydoc basic_json::cbegin() - */ - const_iterator begin() const noexcept - { - return cbegin(); - } - - /*! - @brief returns a const iterator to the first element - - Returns a const iterator to the first element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return const iterator to the first element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast<const basic_json&>(*this).begin()`. - - @liveexample{The following code shows an example for `cbegin()`.,cbegin} - - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref end() -- returns an iterator to the end - @sa @ref cend() -- returns a const iterator to the end - - @since version 1.0.0 - */ - const_iterator cbegin() const noexcept - { - const_iterator result(this); - result.set_begin(); - return result; - } - - /*! - @brief returns an iterator to one past the last element - - Returns an iterator to one past the last element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return iterator one past the last element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - @liveexample{The following code shows an example for `end()`.,end} - - @sa @ref cend() -- returns a const iterator to the end - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref cbegin() -- returns a const iterator to the beginning - - @since version 1.0.0 - */ - iterator end() noexcept - { - iterator result(this); - result.set_end(); - return result; - } - - /*! - @copydoc basic_json::cend() - */ - const_iterator end() const noexcept - { - return cend(); - } - - /*! - @brief returns a const iterator to one past the last element - - Returns a const iterator to one past the last element. - - @image html range-begin-end.svg "Illustration from cppreference.com" - - @return const iterator one past the last element - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast<const basic_json&>(*this).end()`. - - @liveexample{The following code shows an example for `cend()`.,cend} - - @sa @ref end() -- returns an iterator to the end - @sa @ref begin() -- returns an iterator to the beginning - @sa @ref cbegin() -- returns a const iterator to the beginning - - @since version 1.0.0 - */ - const_iterator cend() const noexcept - { - const_iterator result(this); - result.set_end(); - return result; - } - - /*! - @brief returns an iterator to the reverse-beginning - - Returns an iterator to the reverse-beginning; that is, the last element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `reverse_iterator(end())`. - - @liveexample{The following code shows an example for `rbegin()`.,rbegin} - - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref crend() -- returns a const reverse iterator to the end - - @since version 1.0.0 - */ - reverse_iterator rbegin() noexcept - { - return reverse_iterator(end()); - } - - /*! - @copydoc basic_json::crbegin() - */ - const_reverse_iterator rbegin() const noexcept - { - return crbegin(); - } - - /*! - @brief returns an iterator to the reverse-end - - Returns an iterator to the reverse-end; that is, one before the first - element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `reverse_iterator(begin())`. - - @liveexample{The following code shows an example for `rend()`.,rend} - - @sa @ref crend() -- returns a const reverse iterator to the end - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - - @since version 1.0.0 - */ - reverse_iterator rend() noexcept - { - return reverse_iterator(begin()); - } - - /*! - @copydoc basic_json::crend() - */ - const_reverse_iterator rend() const noexcept - { - return crend(); - } - - /*! - @brief returns a const reverse iterator to the last element - - Returns a const iterator to the reverse-beginning; that is, the last - element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast<const basic_json&>(*this).rbegin()`. - - @liveexample{The following code shows an example for `crbegin()`.,crbegin} - - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref crend() -- returns a const reverse iterator to the end - - @since version 1.0.0 - */ - const_reverse_iterator crbegin() const noexcept - { - return const_reverse_iterator(cend()); - } - - /*! - @brief returns a const reverse iterator to one before the first - - Returns a const reverse iterator to the reverse-end; that is, one before - the first element. - - @image html range-rbegin-rend.svg "Illustration from cppreference.com" - - @complexity Constant. - - @requirement This function helps `basic_json` satisfying the - [ReversibleContainer](http://en.cppreference.com/w/cpp/concept/ReversibleContainer) - requirements: - - The complexity is constant. - - Has the semantics of `const_cast<const basic_json&>(*this).rend()`. - - @liveexample{The following code shows an example for `crend()`.,crend} - - @sa @ref rend() -- returns a reverse iterator to the end - @sa @ref rbegin() -- returns a reverse iterator to the beginning - @sa @ref crbegin() -- returns a const reverse iterator to the beginning - - @since version 1.0.0 - */ - const_reverse_iterator crend() const noexcept - { - return const_reverse_iterator(cbegin()); - } - - public: - /*! - @brief wrapper to access iterator member functions in range-based for - - This function allows to access @ref iterator::key() and @ref - iterator::value() during range-based for loops. In these loops, a - reference to the JSON values is returned, so there is no access to the - underlying iterator. - - @liveexample{The following code shows how the wrapper is used,iterator_wrapper} - - @note The name of this function is not yet final and may change in the - future. - */ - static iteration_proxy<iterator> iterator_wrapper(reference cont) - { - return iteration_proxy<iterator>(cont); - } - - /*! - @copydoc iterator_wrapper(reference) - */ - static iteration_proxy<const_iterator> iterator_wrapper(const_reference cont) - { - return iteration_proxy<const_iterator>(cont); - } - - /// @} - - - ////////////// - // capacity // - ////////////// - - /// @name capacity - /// @{ - - /*! - @brief checks whether the container is empty. - - Checks if a JSON value has no elements (i.e. whether its @ref size is `0`). - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `true` - boolean | `false` - string | `false` - number | `false` - object | result of function `object_t::empty()` - array | result of function `array_t::empty()` - - @liveexample{The following code uses `empty()` to check if a JSON - object contains any elements.,empty} - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their `empty()` functions have constant - complexity. - - @iterators No changes. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @note This function does not return whether a string stored as JSON value - is empty - it returns whether the JSON container itself is empty which is - false in the case of a string. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `begin() == end()`. - - @sa @ref size() -- returns the number of elements - - @since version 1.0.0 - */ - bool empty() const noexcept - { - switch (m_type) - { - case value_t::null: - { - // null values are empty - return true; - } - - case value_t::array: - { - // delegate call to array_t::empty() - return m_value.array->empty(); - } - - case value_t::object: - { - // delegate call to object_t::empty() - return m_value.object->empty(); - } - - default: - { - // all other types are nonempty - return false; - } - } - } - - /*! - @brief returns the number of elements - - Returns the number of elements in a JSON value. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `0` - boolean | `1` - string | `1` - number | `1` - object | result of function object_t::size() - array | result of function array_t::size() - - @liveexample{The following code calls `size()` on the different value - types.,size} - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their size() functions have constant - complexity. - - @iterators No changes. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @note This function does not return the length of a string stored as JSON - value - it returns the number of elements in the JSON value which is 1 in - the case of a string. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of `std::distance(begin(), end())`. - - @sa @ref empty() -- checks whether the container is empty - @sa @ref max_size() -- returns the maximal number of elements - - @since version 1.0.0 - */ - size_type size() const noexcept - { - switch (m_type) - { - case value_t::null: - { - // null values are empty - return 0; - } - - case value_t::array: - { - // delegate call to array_t::size() - return m_value.array->size(); - } - - case value_t::object: - { - // delegate call to object_t::size() - return m_value.object->size(); - } - - default: - { - // all other types have size 1 - return 1; - } - } - } - - /*! - @brief returns the maximum possible number of elements - - Returns the maximum number of elements a JSON value is able to hold due to - system or library implementation limitations, i.e. `std::distance(begin(), - end())` for the JSON value. - - @return The return value depends on the different types and is - defined as follows: - Value type | return value - ----------- | ------------- - null | `0` (same as `size()`) - boolean | `1` (same as `size()`) - string | `1` (same as `size()`) - number | `1` (same as `size()`) - object | result of function `object_t::max_size()` - array | result of function `array_t::max_size()` - - @liveexample{The following code calls `max_size()` on the different value - types. Note the output is implementation specific.,max_size} - - @complexity Constant, as long as @ref array_t and @ref object_t satisfy - the Container concept; that is, their `max_size()` functions have constant - complexity. - - @iterators No changes. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @requirement This function helps `basic_json` satisfying the - [Container](http://en.cppreference.com/w/cpp/concept/Container) - requirements: - - The complexity is constant. - - Has the semantics of returning `b.size()` where `b` is the largest - possible JSON value. - - @sa @ref size() -- returns the number of elements - - @since version 1.0.0 - */ - size_type max_size() const noexcept - { - switch (m_type) - { - case value_t::array: - { - // delegate call to array_t::max_size() - return m_value.array->max_size(); - } - - case value_t::object: - { - // delegate call to object_t::max_size() - return m_value.object->max_size(); - } - - default: - { - // all other types have max_size() == size() - return size(); - } - } - } - - /// @} - - - /////////////// - // modifiers // - /////////////// - - /// @name modifiers - /// @{ - - /*! - @brief clears the contents - - Clears the content of a JSON value and resets it to the default value as - if @ref basic_json(value_t) would have been called with the current value - type from @ref type(): - - Value type | initial value - ----------- | ------------- - null | `null` - boolean | `false` - string | `""` - number | `0` - object | `{}` - array | `[]` - - @post Has the same effect as calling - @code {.cpp} - *this = basic_json(type()); - @endcode - - @liveexample{The example below shows the effect of `clear()` to different - JSON types.,clear} - - @complexity Linear in the size of the JSON value. - - @iterators All iterators, pointers and references related to this container - are invalidated. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @sa @ref basic_json(value_t) -- constructor that creates an object with the - same value than calling `clear()` - - @since version 1.0.0 - */ - void clear() noexcept - { - switch (m_type) - { - case value_t::number_integer: - { - m_value.number_integer = 0; - break; - } - - case value_t::number_unsigned: - { - m_value.number_unsigned = 0; - break; - } - - case value_t::number_float: - { - m_value.number_float = 0.0; - break; - } - - case value_t::boolean: - { - m_value.boolean = false; - break; - } - - case value_t::string: - { - m_value.string->clear(); - break; - } - - case value_t::array: - { - m_value.array->clear(); - break; - } - - case value_t::object: - { - m_value.object->clear(); - break; - } - - default: - break; - } - } - - /*! - @brief add an object to an array - - Appends the given element @a val to the end of the JSON value. If the - function is called on a JSON null value, an empty array is created before - appending @a val. - - @param[in] val the value to add to the JSON array - - @throw type_error.308 when called on a type other than JSON array or - null; example: `"cannot use push_back() with number"` - - @complexity Amortized constant. - - @liveexample{The example shows how `push_back()` and `+=` can be used to - add elements to a JSON array. Note how the `null` value was silently - converted to a JSON array.,push_back} - - @since version 1.0.0 - */ - void push_back(basic_json&& val) - { - // push_back only works for null objects or arrays - if (JSON_UNLIKELY(not(is_null() or is_array()))) - { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array (move semantics) - m_value.array->push_back(std::move(val)); - // invalidate object - val.m_type = value_t::null; - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - reference operator+=(basic_json&& val) - { - push_back(std::move(val)); - return *this; - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - void push_back(const basic_json& val) - { - // push_back only works for null objects or arrays - if (JSON_UNLIKELY(not(is_null() or is_array()))) - { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array - m_value.array->push_back(val); - } - - /*! - @brief add an object to an array - @copydoc push_back(basic_json&&) - */ - reference operator+=(const basic_json& val) - { - push_back(val); - return *this; - } - - /*! - @brief add an object to an object - - Inserts the given element @a val to the JSON object. If the function is - called on a JSON null value, an empty object is created before inserting - @a val. - - @param[in] val the value to add to the JSON object - - @throw type_error.308 when called on a type other than JSON object or - null; example: `"cannot use push_back() with number"` - - @complexity Logarithmic in the size of the container, O(log(`size()`)). - - @liveexample{The example shows how `push_back()` and `+=` can be used to - add elements to a JSON object. Note how the `null` value was silently - converted to a JSON object.,push_back__object_t__value} - - @since version 1.0.0 - */ - void push_back(const typename object_t::value_type& val) - { - // push_back only works for null objects or objects - if (JSON_UNLIKELY(not(is_null() or is_object()))) - { - JSON_THROW(type_error::create(308, "cannot use push_back() with " + std::string(type_name()))); - } - - // transform null object into an object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // add element to array - m_value.object->insert(val); - } - - /*! - @brief add an object to an object - @copydoc push_back(const typename object_t::value_type&) - */ - reference operator+=(const typename object_t::value_type& val) - { - push_back(val); - return *this; - } - - /*! - @brief add an object to an object - - This function allows to use `push_back` with an initializer list. In case - - 1. the current value is an object, - 2. the initializer list @a init contains only two elements, and - 3. the first element of @a init is a string, - - @a init is converted into an object element and added using - @ref push_back(const typename object_t::value_type&). Otherwise, @a init - is converted to a JSON value and added using @ref push_back(basic_json&&). - - @param[in] init an initializer list - - @complexity Linear in the size of the initializer list @a init. - - @note This function is required to resolve an ambiguous overload error, - because pairs like `{"key", "value"}` can be both interpreted as - `object_t::value_type` or `std::initializer_list<basic_json>`, see - https://github.com/nlohmann/json/issues/235 for more information. - - @liveexample{The example shows how initializer lists are treated as - objects when possible.,push_back__initializer_list} - */ - void push_back(initializer_list_t init) - { - if (is_object() and init.size() == 2 and (*init.begin())->is_string()) - { - basic_json&& key = init.begin()->moved_or_copied(); - push_back(typename object_t::value_type( - std::move(key.get_ref<string_t&>()), (init.begin() + 1)->moved_or_copied())); - } - else - { - push_back(basic_json(init)); - } - } - - /*! - @brief add an object to an object - @copydoc push_back(initializer_list_t) - */ - reference operator+=(initializer_list_t init) - { - push_back(init); - return *this; - } - - /*! - @brief add an object to an array - - Creates a JSON value from the passed parameters @a args to the end of the - JSON value. If the function is called on a JSON null value, an empty array - is created before appending the value created from @a args. - - @param[in] args arguments to forward to a constructor of @ref basic_json - @tparam Args compatible types to create a @ref basic_json object - - @throw type_error.311 when called on a type other than JSON array or - null; example: `"cannot use emplace_back() with number"` - - @complexity Amortized constant. - - @liveexample{The example shows how `push_back()` can be used to add - elements to a JSON array. Note how the `null` value was silently converted - to a JSON array.,emplace_back} - - @since version 2.0.8 - */ - template<class... Args> - void emplace_back(Args&& ... args) - { - // emplace_back only works for null objects or arrays - if (JSON_UNLIKELY(not(is_null() or is_array()))) - { - JSON_THROW(type_error::create(311, "cannot use emplace_back() with " + std::string(type_name()))); - } - - // transform null object into an array - if (is_null()) - { - m_type = value_t::array; - m_value = value_t::array; - assert_invariant(); - } - - // add element to array (perfect forwarding) - m_value.array->emplace_back(std::forward<Args>(args)...); - } - - /*! - @brief add an object to an object if key does not exist - - Inserts a new element into a JSON object constructed in-place with the - given @a args if there is no element with the key in the container. If the - function is called on a JSON null value, an empty object is created before - appending the value created from @a args. - - @param[in] args arguments to forward to a constructor of @ref basic_json - @tparam Args compatible types to create a @ref basic_json object - - @return a pair consisting of an iterator to the inserted element, or the - already-existing element if no insertion happened, and a bool - denoting whether the insertion took place. - - @throw type_error.311 when called on a type other than JSON object or - null; example: `"cannot use emplace() with number"` - - @complexity Logarithmic in the size of the container, O(log(`size()`)). - - @liveexample{The example shows how `emplace()` can be used to add elements - to a JSON object. Note how the `null` value was silently converted to a - JSON object. Further note how no value is added if there was already one - value stored with the same key.,emplace} - - @since version 2.0.8 - */ - template<class... Args> - std::pair<iterator, bool> emplace(Args&& ... args) - { - // emplace only works for null objects or arrays - if (JSON_UNLIKELY(not(is_null() or is_object()))) - { - JSON_THROW(type_error::create(311, "cannot use emplace() with " + std::string(type_name()))); - } - - // transform null object into an object - if (is_null()) - { - m_type = value_t::object; - m_value = value_t::object; - assert_invariant(); - } - - // add element to array (perfect forwarding) - auto res = m_value.object->emplace(std::forward<Args>(args)...); - // create result iterator and set iterator to the result of emplace - auto it = begin(); - it.m_it.object_iterator = res.first; - - // return pair of iterator and boolean - return {it, res.second}; - } - - /*! - @brief inserts element - - Inserts element @a val before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] val element to insert - @return iterator pointing to the inserted @a val. - - @throw type_error.309 if called on JSON values other than arrays; - example: `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - - @complexity Constant plus linear in the distance between @a pos and end of - the container. - - @liveexample{The example shows how `insert()` is used.,insert} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, const basic_json& val) - { - // insert only works for arrays - if (JSON_LIKELY(is_array())) - { - // check if iterator pos fits to this JSON value - if (JSON_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); - return result; - } - - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - /*! - @brief inserts element - @copydoc insert(const_iterator, const basic_json&) - */ - iterator insert(const_iterator pos, basic_json&& val) - { - return insert(pos, val); - } - - /*! - @brief inserts elements - - Inserts @a cnt copies of @a val before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] cnt number of copies of @a val to insert - @param[in] val element to insert - @return iterator pointing to the first element inserted, or @a pos if - `cnt==0` - - @throw type_error.309 if called on JSON values other than arrays; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - - @complexity Linear in @a cnt plus linear in the distance between @a pos - and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__count} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, size_type cnt, const basic_json& val) - { - // insert only works for arrays - if (JSON_LIKELY(is_array())) - { - // check if iterator pos fits to this JSON value - if (JSON_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); - return result; - } - - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - /*! - @brief inserts elements - - Inserts elements from range `[first, last)` before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw type_error.309 if called on JSON values other than arrays; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - @throw invalid_iterator.210 if @a first and @a last do not belong to the - same JSON value; example: `"iterators do not fit"` - @throw invalid_iterator.211 if @a first or @a last are iterators into - container for which insert is called; example: `"passed iterators may not - belong to container"` - - @return iterator pointing to the first element inserted, or @a pos if - `first==last` - - @complexity Linear in `std::distance(first, last)` plus linear in the - distance between @a pos and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__range} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, const_iterator first, const_iterator last) - { - // insert only works for arrays - if (JSON_UNLIKELY(not is_array())) - { - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - // check if iterator pos fits to this JSON value - if (JSON_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // check if range iterators belong to the same JSON object - if (JSON_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - if (JSON_UNLIKELY(first.m_object == this)) - { - JSON_THROW(invalid_iterator::create(211, "passed iterators may not belong to container")); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert( - pos.m_it.array_iterator, - first.m_it.array_iterator, - last.m_it.array_iterator); - return result; - } - - /*! - @brief inserts elements - - Inserts elements from initializer list @a ilist before iterator @a pos. - - @param[in] pos iterator before which the content will be inserted; may be - the end() iterator - @param[in] ilist initializer list to insert the values from - - @throw type_error.309 if called on JSON values other than arrays; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if @a pos is not an iterator of *this; - example: `"iterator does not fit current value"` - - @return iterator pointing to the first element inserted, or @a pos if - `ilist` is empty - - @complexity Linear in `ilist.size()` plus linear in the distance between - @a pos and end of the container. - - @liveexample{The example shows how `insert()` is used.,insert__ilist} - - @since version 1.0.0 - */ - iterator insert(const_iterator pos, initializer_list_t ilist) - { - // insert only works for arrays - if (JSON_UNLIKELY(not is_array())) - { - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - // check if iterator pos fits to this JSON value - if (JSON_UNLIKELY(pos.m_object != this)) - { - JSON_THROW(invalid_iterator::create(202, "iterator does not fit current value")); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist.begin(), ilist.end()); - return result; - } - - /*! - @brief inserts elements - - Inserts elements from range `[first, last)`. - - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw type_error.309 if called on JSON values other than objects; example: - `"cannot use insert() with string"` - @throw invalid_iterator.202 if iterator @a first or @a last does does not - point to an object; example: `"iterators first and last must point to - objects"` - @throw invalid_iterator.210 if @a first and @a last do not belong to the - same JSON value; example: `"iterators do not fit"` - - @complexity Logarithmic: `O(N*log(size() + N))`, where `N` is the number - of elements to insert. - - @liveexample{The example shows how `insert()` is used.,insert__range_object} - - @since version 3.0.0 - */ - void insert(const_iterator first, const_iterator last) - { - // insert only works for objects - if (JSON_UNLIKELY(not is_object())) - { - JSON_THROW(type_error::create(309, "cannot use insert() with " + std::string(type_name()))); - } - - // check if range iterators belong to the same JSON object - if (JSON_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - // passed iterators must belong to objects - if (JSON_UNLIKELY(not first.m_object->is_object())) - { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); - } - - m_value.object->insert(first.m_it.object_iterator, last.m_it.object_iterator); - } - - /*! - @brief updates a JSON object from another object, overwriting existing keys - - Inserts all values from JSON object @a j and overwrites existing keys. - - @param[in] j JSON object to read values from - - @throw type_error.312 if called on JSON values other than objects; example: - `"cannot use update() with string"` - - @complexity O(N*log(size() + N)), where N is the number of elements to - insert. - - @liveexample{The example shows how `update()` is used.,update} - - @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - - @since version 3.0.0 - */ - void update(const_reference j) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create<object_t>(); - assert_invariant(); - } - - if (JSON_UNLIKELY(not is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); - } - if (JSON_UNLIKELY(not j.is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(j.type_name()))); - } - - for (auto it = j.begin(); it != j.end(); ++it) - { - m_value.object->operator[](it.key()) = it.value(); - } - } - - /*! - @brief updates a JSON object from another object, overwriting existing keys - - Inserts all values from from range `[first, last)` and overwrites existing - keys. - - @param[in] first begin of the range of elements to insert - @param[in] last end of the range of elements to insert - - @throw type_error.312 if called on JSON values other than objects; example: - `"cannot use update() with string"` - @throw invalid_iterator.202 if iterator @a first or @a last does does not - point to an object; example: `"iterators first and last must point to - objects"` - @throw invalid_iterator.210 if @a first and @a last do not belong to the - same JSON value; example: `"iterators do not fit"` - - @complexity O(N*log(size() + N)), where N is the number of elements to - insert. - - @liveexample{The example shows how `update()` is used__range.,update} - - @sa https://docs.python.org/3.6/library/stdtypes.html#dict.update - - @since version 3.0.0 - */ - void update(const_iterator first, const_iterator last) - { - // implicitly convert null value to an empty object - if (is_null()) - { - m_type = value_t::object; - m_value.object = create<object_t>(); - assert_invariant(); - } - - if (JSON_UNLIKELY(not is_object())) - { - JSON_THROW(type_error::create(312, "cannot use update() with " + std::string(type_name()))); - } - - // check if range iterators belong to the same JSON object - if (JSON_UNLIKELY(first.m_object != last.m_object)) - { - JSON_THROW(invalid_iterator::create(210, "iterators do not fit")); - } - - // passed iterators must belong to objects - if (JSON_UNLIKELY(not first.m_object->is_object() - or not first.m_object->is_object())) - { - JSON_THROW(invalid_iterator::create(202, "iterators first and last must point to objects")); - } - - for (auto it = first; it != last; ++it) - { - m_value.object->operator[](it.key()) = it.value(); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of the JSON value with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other JSON value to exchange the contents with - - @complexity Constant. - - @liveexample{The example below shows how JSON values can be swapped with - `swap()`.,swap__reference} - - @since version 1.0.0 - */ - void swap(reference other) noexcept ( - std::is_nothrow_move_constructible<value_t>::value and - std::is_nothrow_move_assignable<value_t>::value and - std::is_nothrow_move_constructible<json_value>::value and - std::is_nothrow_move_assignable<json_value>::value - ) - { - std::swap(m_type, other.m_type); - std::swap(m_value, other.m_value); - assert_invariant(); - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON array with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other array to exchange the contents with - - @throw type_error.310 when JSON value is not an array; example: `"cannot - use swap() with string"` - - @complexity Constant. - - @liveexample{The example below shows how arrays can be swapped with - `swap()`.,swap__array_t} - - @since version 1.0.0 - */ - void swap(array_t& other) - { - // swap only works for arrays - if (JSON_LIKELY(is_array())) - { - std::swap(*(m_value.array), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON object with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other object to exchange the contents with - - @throw type_error.310 when JSON value is not an object; example: - `"cannot use swap() with string"` - - @complexity Constant. - - @liveexample{The example below shows how objects can be swapped with - `swap()`.,swap__object_t} - - @since version 1.0.0 - */ - void swap(object_t& other) - { - // swap only works for objects - if (JSON_LIKELY(is_object())) - { - std::swap(*(m_value.object), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /*! - @brief exchanges the values - - Exchanges the contents of a JSON string with those of @a other. Does not - invoke any move, copy, or swap operations on individual elements. All - iterators and references remain valid. The past-the-end iterator is - invalidated. - - @param[in,out] other string to exchange the contents with - - @throw type_error.310 when JSON value is not a string; example: `"cannot - use swap() with boolean"` - - @complexity Constant. - - @liveexample{The example below shows how strings can be swapped with - `swap()`.,swap__string_t} - - @since version 1.0.0 - */ - void swap(string_t& other) - { - // swap only works for strings - if (JSON_LIKELY(is_string())) - { - std::swap(*(m_value.string), other); - } - else - { - JSON_THROW(type_error::create(310, "cannot use swap() with " + std::string(type_name()))); - } - } - - /// @} - - public: - ////////////////////////////////////////// - // lexicographical comparison operators // - ////////////////////////////////////////// - - /// @name lexicographical comparison operators - /// @{ - - /*! - @brief comparison: equal - - Compares two JSON values for equality according to the following rules: - - Two JSON values are equal if (1) they are from the same type and (2) - their stored values are the same according to their respective - `operator==`. - - Integer and floating-point numbers are automatically converted before - comparison. Note than two NaN values are always treated as unequal. - - Two JSON null values are equal. - - @note Floating-point inside JSON values numbers are compared with - `json::number_float_t::operator==` which is `double::operator==` by - default. To compare floating-point while respecting an epsilon, an alternative - [comparison function](https://github.com/mariokonrad/marnav/blob/master/src/marnav/math/floatingpoint.hpp#L34-#L39) - could be used, for instance - @code {.cpp} - template <typename T, typename = typename std::enable_if<std::is_floating_point<T>::value, T>::type> - inline bool is_same(T a, T b, T epsilon = std::numeric_limits<T>::epsilon()) noexcept - { - return std::abs(a - b) <= epsilon; - } - @endcode - - @note NaN values never compare equal to themselves or to other NaN values. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are equal - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @complexity Linear. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__equal} - - @since version 1.0.0 - */ - friend bool operator==(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return (*lhs.m_value.array == *rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object == *rhs.m_value.object); - - case value_t::null: - return true; - - case value_t::string: - return (*lhs.m_value.string == *rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean == rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer == rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned == rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float == rhs.m_value.number_float); - - default: - return false; - } - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) - { - return (static_cast<number_float_t>(lhs.m_value.number_integer) == rhs.m_value.number_float); - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) - { - return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_integer)); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) - { - return (static_cast<number_float_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_float); - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) - { - return (lhs.m_value.number_float == static_cast<number_float_t>(rhs.m_value.number_unsigned)); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) - { - return (static_cast<number_integer_t>(lhs.m_value.number_unsigned) == rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) - { - return (lhs.m_value.number_integer == static_cast<number_integer_t>(rhs.m_value.number_unsigned)); - } - - return false; - } - - /*! - @brief comparison: equal - @copydoc operator==(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator==(const_reference lhs, const ScalarType rhs) noexcept - { - return (lhs == basic_json(rhs)); - } - - /*! - @brief comparison: equal - @copydoc operator==(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator==(const ScalarType lhs, const_reference rhs) noexcept - { - return (basic_json(lhs) == rhs); - } - - /*! - @brief comparison: not equal - - Compares two JSON values for inequality by calculating `not (lhs == rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether the values @a lhs and @a rhs are not equal - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__notequal} - - @since version 1.0.0 - */ - friend bool operator!=(const_reference lhs, const_reference rhs) noexcept - { - return not (lhs == rhs); - } - - /*! - @brief comparison: not equal - @copydoc operator!=(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator!=(const_reference lhs, const ScalarType rhs) noexcept - { - return (lhs != basic_json(rhs)); - } - - /*! - @brief comparison: not equal - @copydoc operator!=(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator!=(const ScalarType lhs, const_reference rhs) noexcept - { - return (basic_json(lhs) != rhs); - } - - /*! - @brief comparison: less than - - Compares whether one JSON value @a lhs is less than another JSON value @a - rhs according to the following rules: - - If @a lhs and @a rhs have the same type, the values are compared using - the default `<` operator. - - Integer and floating-point numbers are automatically converted before - comparison - - In case @a lhs and @a rhs have different types, the values are ignored - and the order of the types is considered, see - @ref operator<(const value_t, const value_t). - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__less} - - @since version 1.0.0 - */ - friend bool operator<(const_reference lhs, const_reference rhs) noexcept - { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return *lhs.m_value.object < *rhs.m_value.object; - - case value_t::null: - return false; - - case value_t::string: - return *lhs.m_value.string < *rhs.m_value.string; - - case value_t::boolean: - return lhs.m_value.boolean < rhs.m_value.boolean; - - case value_t::number_integer: - return lhs.m_value.number_integer < rhs.m_value.number_integer; - - case value_t::number_unsigned: - return lhs.m_value.number_unsigned < rhs.m_value.number_unsigned; - - case value_t::number_float: - return lhs.m_value.number_float < rhs.m_value.number_float; - - default: - return false; - } - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) - { - return static_cast<number_float_t>(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_float) - { - return static_cast<number_float_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float and rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast<number_float_t>(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast<number_integer_t>(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned and rhs_type == value_t::number_integer) - { - return static_cast<number_integer_t>(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, - // we compare types. Note we have to call the operator explicitly, - // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); - } - - /*! - @brief comparison: less than - @copydoc operator<(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<(const_reference lhs, const ScalarType rhs) noexcept - { - return (lhs < basic_json(rhs)); - } - - /*! - @brief comparison: less than - @copydoc operator<(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<(const ScalarType lhs, const_reference rhs) noexcept - { - return (basic_json(lhs) < rhs); - } - - /*! - @brief comparison: less than or equal - - Compares whether one JSON value @a lhs is less than or equal to another - JSON value by calculating `not (rhs < lhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is less than or equal to @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__greater} - - @since version 1.0.0 - */ - friend bool operator<=(const_reference lhs, const_reference rhs) noexcept - { - return not (rhs < lhs); - } - - /*! - @brief comparison: less than or equal - @copydoc operator<=(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<=(const_reference lhs, const ScalarType rhs) noexcept - { - return (lhs <= basic_json(rhs)); - } - - /*! - @brief comparison: less than or equal - @copydoc operator<=(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator<=(const ScalarType lhs, const_reference rhs) noexcept - { - return (basic_json(lhs) <= rhs); - } - - /*! - @brief comparison: greater than - - Compares whether one JSON value @a lhs is greater than another - JSON value by calculating `not (lhs <= rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than to @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__lessequal} - - @since version 1.0.0 - */ - friend bool operator>(const_reference lhs, const_reference rhs) noexcept - { - return not (lhs <= rhs); - } - - /*! - @brief comparison: greater than - @copydoc operator>(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>(const_reference lhs, const ScalarType rhs) noexcept - { - return (lhs > basic_json(rhs)); - } - - /*! - @brief comparison: greater than - @copydoc operator>(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>(const ScalarType lhs, const_reference rhs) noexcept - { - return (basic_json(lhs) > rhs); - } - - /*! - @brief comparison: greater than or equal - - Compares whether one JSON value @a lhs is greater than or equal to another - JSON value by calculating `not (lhs < rhs)`. - - @param[in] lhs first JSON value to consider - @param[in] rhs second JSON value to consider - @return whether @a lhs is greater than or equal to @a rhs - - @complexity Linear. - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @liveexample{The example demonstrates comparing several JSON - types.,operator__greaterequal} - - @since version 1.0.0 - */ - friend bool operator>=(const_reference lhs, const_reference rhs) noexcept - { - return not (lhs < rhs); - } - - /*! - @brief comparison: greater than or equal - @copydoc operator>=(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>=(const_reference lhs, const ScalarType rhs) noexcept - { - return (lhs >= basic_json(rhs)); - } - - /*! - @brief comparison: greater than or equal - @copydoc operator>=(const_reference, const_reference) - */ - template<typename ScalarType, typename std::enable_if< - std::is_scalar<ScalarType>::value, int>::type = 0> - friend bool operator>=(const ScalarType lhs, const_reference rhs) noexcept - { - return (basic_json(lhs) >= rhs); - } - - /// @} - - /////////////////// - // serialization // - /////////////////// - - /// @name serialization - /// @{ - - /*! - @brief serialize to stream - - Serialize the given JSON value @a j to the output stream @a o. The JSON - value will be serialized using the @ref dump member function. - - - The indentation of the output can be controlled with the member variable - `width` of the output stream @a o. For instance, using the manipulator - `std::setw(4)` on @a o sets the indentation level to `4` and the - serialization result is the same as calling `dump(4)`. - - - The indentation characrer can be controlled with the member variable - `fill` of the output stream @a o. For instance, the manipulator - `std::setfill('\\t')` sets indentation to use a tab character rather than - the default space character. - - @param[in,out] o stream to serialize to - @param[in] j JSON value to serialize - - @return the stream @a o - - @complexity Linear. - - @liveexample{The example below shows the serialization with different - parameters to `width` to adjust the indentation level.,operator_serialize} - - @since version 1.0.0; indentaction character added in version 3.0.0 - */ - friend std::ostream& operator<<(std::ostream& o, const basic_json& j) - { - // read width member and use it as indentation parameter if nonzero - const bool pretty_print = (o.width() > 0); - const auto indentation = (pretty_print ? o.width() : 0); - - // reset width to 0 for subsequent calls to this stream - o.width(0); - - // do the actual serialization - serializer s(detail::output_adapter<char>(o), o.fill()); - s.dump(j, pretty_print, false, static_cast<unsigned int>(indentation)); - return o; - } - - /*! - @brief serialize to stream - @deprecated This stream operator is deprecated and will be removed in a - future version of the library. Please use - @ref operator<<(std::ostream&, const basic_json&) - instead; that is, replace calls like `j >> o;` with `o << j;`. - @since version 1.0.0; deprecated since version 3.0.0 - */ - JSON_DEPRECATED - friend std::ostream& operator>>(const basic_json& j, std::ostream& o) - { - return o << j; - } - - /// @} - - - ///////////////////// - // deserialization // - ///////////////////// - - /// @name deserialization - /// @{ - - /*! - @brief deserialize from a compatible input - - This function reads from a compatible input. Examples are: - - an array of 1-byte values - - strings with character/literal type with size of 1 byte - - input streams - - container with contiguous storage of 1-byte values. Compatible container - types include `std::vector`, `std::string`, `std::array`, - `std::valarray`, and `std::initializer_list`. Furthermore, C-style - arrays can be used with `std::begin()`/`std::end()`. User-defined - containers can be used as long as they implement random-access iterators - and a contiguous storage. - - @pre Each element of the container has a size of 1 byte. Violating this - precondition yields undefined behavior. **This precondition is enforced - with a static assertion.** - - @pre The container storage is contiguous. Violating this precondition - yields undefined behavior. **This precondition is enforced with an - assertion.** - @pre Each element of the container has a size of 1 byte. Violating this - precondition yields undefined behavior. **This precondition is enforced - with a static assertion.** - - @warning There is no way to enforce all preconditions at compile-time. If - the function is called with a noncompliant container and with - assertions switched off, the behavior is undefined and will most - likely yield segmentation violation. - - @param[in] i input to read from - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - - @return result of the deserialization - - @throw parse_error.101 if a parse error occurs; example: `""unexpected end - of input; expected string literal""` - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function reading - from an array.,parse__array__parser_callback_t} - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__string__parser_callback_t} - - @liveexample{The example below demonstrates the `parse()` function with - and without callback function.,parse__istream__parser_callback_t} - - @liveexample{The example below demonstrates the `parse()` function reading - from a contiguous container.,parse__contiguouscontainer__parser_callback_t} - - @since version 2.0.3 (contiguous containers) - */ - static basic_json parse(detail::input_adapter i, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) - { - basic_json result; - parser(i, cb, allow_exceptions).parse(true, result); - return result; - } - - /*! - @copydoc basic_json parse(detail::input_adapter, const parser_callback_t) - */ - static basic_json parse(detail::input_adapter& i, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) - { - basic_json result; - parser(i, cb, allow_exceptions).parse(true, result); - return result; - } - - static bool accept(detail::input_adapter i) - { - return parser(i).accept(true); - } - - static bool accept(detail::input_adapter& i) - { - return parser(i).accept(true); - } - - /*! - @brief deserialize from an iterator range with contiguous storage - - This function reads from an iterator range of a container with contiguous - storage of 1-byte values. Compatible container types include - `std::vector`, `std::string`, `std::array`, `std::valarray`, and - `std::initializer_list`. Furthermore, C-style arrays can be used with - `std::begin()`/`std::end()`. User-defined containers can be used as long - as they implement random-access iterators and a contiguous storage. - - @pre The iterator range is contiguous. Violating this precondition yields - undefined behavior. **This precondition is enforced with an assertion.** - @pre Each element in the range has a size of 1 byte. Violating this - precondition yields undefined behavior. **This precondition is enforced - with a static assertion.** - - @warning There is no way to enforce all preconditions at compile-time. If - the function is called with noncompliant iterators and with - assertions switched off, the behavior is undefined and will most - likely yield segmentation violation. - - @tparam IteratorType iterator of container with contiguous storage - @param[in] first begin of the range to parse (included) - @param[in] last end of the range to parse (excluded) - @param[in] cb a parser callback function of type @ref parser_callback_t - which is used to control the deserialization by filtering unwanted values - (optional) - @param[in] allow_exceptions whether to throw exceptions in case of a - parse error (optional, true by default) - - @return result of the deserialization - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. The complexity can be higher if the parser callback function - @a cb has a super-linear complexity. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below demonstrates the `parse()` function reading - from an iterator range.,parse__iteratortype__parser_callback_t} - - @since version 2.0.3 - */ - template<class IteratorType, typename std::enable_if< - std::is_base_of< - std::random_access_iterator_tag, - typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> - static basic_json parse(IteratorType first, IteratorType last, - const parser_callback_t cb = nullptr, - const bool allow_exceptions = true) - { - basic_json result; - parser(detail::input_adapter(first, last), cb, allow_exceptions).parse(true, result); - return result; - } - - template<class IteratorType, typename std::enable_if< - std::is_base_of< - std::random_access_iterator_tag, - typename std::iterator_traits<IteratorType>::iterator_category>::value, int>::type = 0> - static bool accept(IteratorType first, IteratorType last) - { - return parser(detail::input_adapter(first, last)).accept(true); - } - - /*! - @brief deserialize from stream - @deprecated This stream operator is deprecated and will be removed in a - future version of the library. Please use - @ref operator>>(std::istream&, basic_json&) - instead; that is, replace calls like `j << i;` with `i >> j;`. - @since version 1.0.0; deprecated since version 3.0.0 - */ - JSON_DEPRECATED - friend std::istream& operator<<(basic_json& j, std::istream& i) - { - return operator>>(i, j); - } - - /*! - @brief deserialize from stream - - Deserializes an input stream to a JSON value. - - @param[in,out] i input stream to read a serialized JSON value from - @param[in,out] j JSON value to write the deserialized input to - - @throw parse_error.101 in case of an unexpected token - @throw parse_error.102 if to_unicode fails or surrogate error - @throw parse_error.103 if to_unicode fails - - @complexity Linear in the length of the input. The parser is a predictive - LL(1) parser. - - @note A UTF-8 byte order mark is silently ignored. - - @liveexample{The example below shows how a JSON value is constructed by - reading a serialization from a stream.,operator_deserialize} - - @sa parse(std::istream&, const parser_callback_t) for a variant with a - parser callback function to filter values while parsing - - @since version 1.0.0 - */ - friend std::istream& operator>>(std::istream& i, basic_json& j) - { - parser(detail::input_adapter(i)).parse(false, j); - return i; - } - - /// @} - - /////////////////////////// - // convenience functions // - /////////////////////////// - - /*! - @brief return the type as string - - Returns the type name as string to be used in error messages - usually to - indicate that a function was called on a wrong JSON type. - - @return a string representation of a the @a m_type member: - Value type | return value - ----------- | ------------- - null | `"null"` - boolean | `"boolean"` - string | `"string"` - number | `"number"` (for all number types) - object | `"object"` - array | `"array"` - discarded | `"discarded"` - - @exceptionsafety No-throw guarantee: this function never throws exceptions. - - @complexity Constant. - - @liveexample{The following code exemplifies `type_name()` for all JSON - types.,type_name} - - @sa @ref type() -- return the type of the JSON value - @sa @ref operator value_t() -- return the type of the JSON value (implicit) - - @since version 1.0.0, public since 2.1.0, `const char*` and `noexcept` - since 3.0.0 - */ - const char* type_name() const noexcept - { - { - switch (m_type) - { - case value_t::null: - return "null"; - case value_t::object: - return "object"; - case value_t::array: - return "array"; - case value_t::string: - return "string"; - case value_t::boolean: - return "boolean"; - case value_t::discarded: - return "discarded"; - default: - return "number"; - } - } - } - - - private: - ////////////////////// - // member variables // - ////////////////////// - - /// the type of the current element - value_t m_type = value_t::null; - - /// the value of the current element - json_value m_value = {}; - - ////////////////////////////////////////// - // binary serialization/deserialization // - ////////////////////////////////////////// - - /// @name binary serialization/deserialization support - /// @{ - - public: - /*! - @brief create a CBOR serialization of a given JSON value - - Serializes a given JSON value @a j to a byte vector using the CBOR (Concise - Binary Object Representation) serialization format. CBOR is a binary - serialization format which aims to be more compact than JSON itself, yet - more efficient to parse. - - The library uses the following mapping from JSON values types to - CBOR types according to the CBOR specification (RFC 7049): - - JSON value type | value/range | CBOR type | first byte - --------------- | ------------------------------------------ | ---------------------------------- | --------------- - null | `null` | Null | 0xf6 - boolean | `true` | True | 0xf5 - boolean | `false` | False | 0xf4 - number_integer | -9223372036854775808..-2147483649 | Negative integer (8 bytes follow) | 0x3b - number_integer | -2147483648..-32769 | Negative integer (4 bytes follow) | 0x3a - number_integer | -32768..-129 | Negative integer (2 bytes follow) | 0x39 - number_integer | -128..-25 | Negative integer (1 byte follow) | 0x38 - number_integer | -24..-1 | Negative integer | 0x20..0x37 - number_integer | 0..23 | Integer | 0x00..0x17 - number_integer | 24..255 | Unsigned integer (1 byte follow) | 0x18 - number_integer | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_integer | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a - number_integer | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b - number_unsigned | 0..23 | Integer | 0x00..0x17 - number_unsigned | 24..255 | Unsigned integer (1 byte follow) | 0x18 - number_unsigned | 256..65535 | Unsigned integer (2 bytes follow) | 0x19 - number_unsigned | 65536..4294967295 | Unsigned integer (4 bytes follow) | 0x1a - number_unsigned | 4294967296..18446744073709551615 | Unsigned integer (8 bytes follow) | 0x1b - number_float | *any value* | Double-Precision Float | 0xfb - string | *length*: 0..23 | UTF-8 string | 0x60..0x77 - string | *length*: 23..255 | UTF-8 string (1 byte follow) | 0x78 - string | *length*: 256..65535 | UTF-8 string (2 bytes follow) | 0x79 - string | *length*: 65536..4294967295 | UTF-8 string (4 bytes follow) | 0x7a - string | *length*: 4294967296..18446744073709551615 | UTF-8 string (8 bytes follow) | 0x7b - array | *size*: 0..23 | array | 0x80..0x97 - array | *size*: 23..255 | array (1 byte follow) | 0x98 - array | *size*: 256..65535 | array (2 bytes follow) | 0x99 - array | *size*: 65536..4294967295 | array (4 bytes follow) | 0x9a - array | *size*: 4294967296..18446744073709551615 | array (8 bytes follow) | 0x9b - object | *size*: 0..23 | map | 0xa0..0xb7 - object | *size*: 23..255 | map (1 byte follow) | 0xb8 - object | *size*: 256..65535 | map (2 bytes follow) | 0xb9 - object | *size*: 65536..4294967295 | map (4 bytes follow) | 0xba - object | *size*: 4294967296..18446744073709551615 | map (8 bytes follow) | 0xbb - - @note The mapping is **complete** in the sense that any JSON value type - can be converted to a CBOR value. - - @note If NaN or Infinity are stored inside a JSON number, they are - serialized properly. This behavior differs from the @ref dump() - function which serializes NaN or Infinity to `null`. - - @note The following CBOR types are not used in the conversion: - - byte strings (0x40..0x5f) - - UTF-8 strings terminated by "break" (0x7f) - - arrays terminated by "break" (0x9f) - - maps terminated by "break" (0xbf) - - date/time (0xc0..0xc1) - - bignum (0xc2..0xc3) - - decimal fraction (0xc4) - - bigfloat (0xc5) - - tagged items (0xc6..0xd4, 0xd8..0xdb) - - expected conversions (0xd5..0xd7) - - simple values (0xe0..0xf3, 0xf8) - - undefined (0xf7) - - half and single-precision floats (0xf9-0xfa) - - break (0xff) - - @param[in] j JSON value to serialize - @return MessagePack serialization as byte vector - - @complexity Linear in the size of the JSON value @a j. - - @liveexample{The example shows the serialization of a JSON value to a byte - vector in CBOR format.,to_cbor} - - @sa http://cbor.io - @sa @ref from_cbor(const std::vector<uint8_t>&, const size_t) for the - analogous deserialization - @sa @ref to_msgpack(const basic_json&) for the related MessagePack format - - @since version 2.0.9 - */ - static std::vector<uint8_t> to_cbor(const basic_json& j) - { - std::vector<uint8_t> result; - to_cbor(j, result); - return result; - } - - static void to_cbor(const basic_json& j, detail::output_adapter<uint8_t> o) - { - binary_writer<uint8_t>(o).write_cbor(j); - } - - static void to_cbor(const basic_json& j, detail::output_adapter<char> o) - { - binary_writer<char>(o).write_cbor(j); - } - - /*! - @brief create a MessagePack serialization of a given JSON value - - Serializes a given JSON value @a j to a byte vector using the MessagePack - serialization format. MessagePack is a binary serialization format which - aims to be more compact than JSON itself, yet more efficient to parse. - - The library uses the following mapping from JSON values types to - MessagePack types according to the MessagePack specification: - - JSON value type | value/range | MessagePack type | first byte - --------------- | --------------------------------- | ---------------- | ---------- - null | `null` | nil | 0xc0 - boolean | `true` | true | 0xc3 - boolean | `false` | false | 0xc2 - number_integer | -9223372036854775808..-2147483649 | int64 | 0xd3 - number_integer | -2147483648..-32769 | int32 | 0xd2 - number_integer | -32768..-129 | int16 | 0xd1 - number_integer | -128..-33 | int8 | 0xd0 - number_integer | -32..-1 | negative fixint | 0xe0..0xff - number_integer | 0..127 | positive fixint | 0x00..0x7f - number_integer | 128..255 | uint 8 | 0xcc - number_integer | 256..65535 | uint 16 | 0xcd - number_integer | 65536..4294967295 | uint 32 | 0xce - number_integer | 4294967296..18446744073709551615 | uint 64 | 0xcf - number_unsigned | 0..127 | positive fixint | 0x00..0x7f - number_unsigned | 128..255 | uint 8 | 0xcc - number_unsigned | 256..65535 | uint 16 | 0xcd - number_unsigned | 65536..4294967295 | uint 32 | 0xce - number_unsigned | 4294967296..18446744073709551615 | uint 64 | 0xcf - number_float | *any value* | float 64 | 0xcb - string | *length*: 0..31 | fixstr | 0xa0..0xbf - string | *length*: 32..255 | str 8 | 0xd9 - string | *length*: 256..65535 | str 16 | 0xda - string | *length*: 65536..4294967295 | str 32 | 0xdb - array | *size*: 0..15 | fixarray | 0x90..0x9f - array | *size*: 16..65535 | array 16 | 0xdc - array | *size*: 65536..4294967295 | array 32 | 0xdd - object | *size*: 0..15 | fix map | 0x80..0x8f - object | *size*: 16..65535 | map 16 | 0xde - object | *size*: 65536..4294967295 | map 32 | 0xdf - - @note The mapping is **complete** in the sense that any JSON value type - can be converted to a MessagePack value. - - @note The following values can **not** be converted to a MessagePack value: - - strings with more than 4294967295 bytes - - arrays with more than 4294967295 elements - - objects with more than 4294967295 elements - - @note The following MessagePack types are not used in the conversion: - - bin 8 - bin 32 (0xc4..0xc6) - - ext 8 - ext 32 (0xc7..0xc9) - - float 32 (0xca) - - fixext 1 - fixext 16 (0xd4..0xd8) - - @note Any MessagePack output created @ref to_msgpack can be successfully - parsed by @ref from_msgpack. - - @note If NaN or Infinity are stored inside a JSON number, they are - serialized properly. This behavior differs from the @ref dump() - function which serializes NaN or Infinity to `null`. - - @param[in] j JSON value to serialize - @return MessagePack serialization as byte vector - - @complexity Linear in the size of the JSON value @a j. - - @liveexample{The example shows the serialization of a JSON value to a byte - vector in MessagePack format.,to_msgpack} - - @sa http://msgpack.org - @sa @ref from_msgpack(const std::vector<uint8_t>&, const size_t) for the - analogous deserialization - @sa @ref to_cbor(const basic_json& for the related CBOR format - - @since version 2.0.9 - */ - static std::vector<uint8_t> to_msgpack(const basic_json& j) - { - std::vector<uint8_t> result; - to_msgpack(j, result); - return result; - } - - static void to_msgpack(const basic_json& j, detail::output_adapter<uint8_t> o) - { - binary_writer<uint8_t>(o).write_msgpack(j); - } - - static void to_msgpack(const basic_json& j, detail::output_adapter<char> o) - { - binary_writer<char>(o).write_msgpack(j); - } - - /*! - @brief create a JSON value from an input in CBOR format - - Deserializes a given input @a i to a JSON value using the CBOR (Concise - Binary Object Representation) serialization format. - - The library maps CBOR types to JSON value types as follows: - - CBOR type | JSON value type | first byte - ---------------------- | --------------- | ---------- - Integer | number_unsigned | 0x00..0x17 - Unsigned integer | number_unsigned | 0x18 - Unsigned integer | number_unsigned | 0x19 - Unsigned integer | number_unsigned | 0x1a - Unsigned integer | number_unsigned | 0x1b - Negative integer | number_integer | 0x20..0x37 - Negative integer | number_integer | 0x38 - Negative integer | number_integer | 0x39 - Negative integer | number_integer | 0x3a - Negative integer | number_integer | 0x3b - Negative integer | number_integer | 0x40..0x57 - UTF-8 string | string | 0x60..0x77 - UTF-8 string | string | 0x78 - UTF-8 string | string | 0x79 - UTF-8 string | string | 0x7a - UTF-8 string | string | 0x7b - UTF-8 string | string | 0x7f - array | array | 0x80..0x97 - array | array | 0x98 - array | array | 0x99 - array | array | 0x9a - array | array | 0x9b - array | array | 0x9f - map | object | 0xa0..0xb7 - map | object | 0xb8 - map | object | 0xb9 - map | object | 0xba - map | object | 0xbb - map | object | 0xbf - False | `false` | 0xf4 - True | `true` | 0xf5 - Nill | `null` | 0xf6 - Half-Precision Float | number_float | 0xf9 - Single-Precision Float | number_float | 0xfa - Double-Precision Float | number_float | 0xfb - - @warning The mapping is **incomplete** in the sense that not all CBOR - types can be converted to a JSON value. The following CBOR types - are not supported and will yield parse errors (parse_error.112): - - byte strings (0x40..0x5f) - - date/time (0xc0..0xc1) - - bignum (0xc2..0xc3) - - decimal fraction (0xc4) - - bigfloat (0xc5) - - tagged items (0xc6..0xd4, 0xd8..0xdb) - - expected conversions (0xd5..0xd7) - - simple values (0xe0..0xf3, 0xf8) - - undefined (0xf7) - - @warning CBOR allows map keys of any type, whereas JSON only allows - strings as keys in object values. Therefore, CBOR maps with keys - other than UTF-8 strings are rejected (parse_error.113). - - @note Any CBOR output created @ref to_cbor can be successfully parsed by - @ref from_cbor. - - @param[in] i an input in CBOR format convertible to an input adapter - @param[in] strict whether to expect the input to be consumed until EOF - (true by default) - @return deserialized JSON value - - @throw parse_error.110 if the given input ends prematurely or the end of - file was not reached when @a strict was set to true - @throw parse_error.112 if unsupported features from CBOR were - used in the given input @a v or if the input is not valid CBOR - @throw parse_error.113 if a string was expected as map key, but not found - - @complexity Linear in the size of the input @a i. - - @liveexample{The example shows the deserialization of a byte vector in CBOR - format to a JSON value.,from_cbor} - - @sa http://cbor.io - @sa @ref to_cbor(const basic_json&) for the analogous serialization - @sa @ref from_msgpack(detail::input_adapter, const bool) for the - related MessagePack format - - @since version 2.0.9; parameter @a start_index since 2.1.1; changed to - consume input adapters, removed start_index parameter, and added - @a strict parameter since 3.0.0 - */ - static basic_json from_cbor(detail::input_adapter i, - const bool strict = true) - { - return binary_reader(i).parse_cbor(strict); - } - - /*! - @copydoc from_cbor(detail::input_adapter, const bool) - */ - template<typename A1, typename A2, - detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> - static basic_json from_cbor(A1 && a1, A2 && a2, const bool strict = true) - { - return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_cbor(strict); - } - - /*! - @brief create a JSON value from an input in MessagePack format - - Deserializes a given input @a i to a JSON value using the MessagePack - serialization format. - - The library maps MessagePack types to JSON value types as follows: - - MessagePack type | JSON value type | first byte - ---------------- | --------------- | ---------- - positive fixint | number_unsigned | 0x00..0x7f - fixmap | object | 0x80..0x8f - fixarray | array | 0x90..0x9f - fixstr | string | 0xa0..0xbf - nil | `null` | 0xc0 - false | `false` | 0xc2 - true | `true` | 0xc3 - float 32 | number_float | 0xca - float 64 | number_float | 0xcb - uint 8 | number_unsigned | 0xcc - uint 16 | number_unsigned | 0xcd - uint 32 | number_unsigned | 0xce - uint 64 | number_unsigned | 0xcf - int 8 | number_integer | 0xd0 - int 16 | number_integer | 0xd1 - int 32 | number_integer | 0xd2 - int 64 | number_integer | 0xd3 - str 8 | string | 0xd9 - str 16 | string | 0xda - str 32 | string | 0xdb - array 16 | array | 0xdc - array 32 | array | 0xdd - map 16 | object | 0xde - map 32 | object | 0xdf - negative fixint | number_integer | 0xe0-0xff - - @warning The mapping is **incomplete** in the sense that not all - MessagePack types can be converted to a JSON value. The following - MessagePack types are not supported and will yield parse errors: - - bin 8 - bin 32 (0xc4..0xc6) - - ext 8 - ext 32 (0xc7..0xc9) - - fixext 1 - fixext 16 (0xd4..0xd8) - - @note Any MessagePack output created @ref to_msgpack can be successfully - parsed by @ref from_msgpack. - - @param[in] i an input in MessagePack format convertible to an input - adapter - @param[in] strict whether to expect the input to be consumed until EOF - (true by default) - - @throw parse_error.110 if the given input ends prematurely or the end of - file was not reached when @a strict was set to true - @throw parse_error.112 if unsupported features from MessagePack were - used in the given input @a i or if the input is not valid MessagePack - @throw parse_error.113 if a string was expected as map key, but not found - - @complexity Linear in the size of the input @a i. - - @liveexample{The example shows the deserialization of a byte vector in - MessagePack format to a JSON value.,from_msgpack} - - @sa http://msgpack.org - @sa @ref to_msgpack(const basic_json&) for the analogous serialization - @sa @ref from_cbor(detail::input_adapter, const bool) for the related CBOR - format - - @since version 2.0.9; parameter @a start_index since 2.1.1; changed to - consume input adapters, removed start_index parameter, and added - @a strict parameter since 3.0.0 - */ - static basic_json from_msgpack(detail::input_adapter i, - const bool strict = true) - { - return binary_reader(i).parse_msgpack(strict); - } - - /*! - @copydoc from_msgpack(detail::input_adapter, const bool) - */ - template<typename A1, typename A2, - detail::enable_if_t<std::is_constructible<detail::input_adapter, A1, A2>::value, int> = 0> - static basic_json from_msgpack(A1 && a1, A2 && a2, const bool strict = true) - { - return binary_reader(detail::input_adapter(std::forward<A1>(a1), std::forward<A2>(a2))).parse_msgpack(strict); - } - - /// @} - - ////////////////////////// - // JSON Pointer support // - ////////////////////////// - - /// @name JSON Pointer functions - /// @{ - - /*! - @brief access specified element via JSON Pointer - - Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. Similar to @ref operator[](const typename - object_t::key_type&), `null` values are created in arrays and objects if - necessary. - - In particular: - - If the JSON pointer points to an object key that does not exist, it - is created an filled with a `null` value before a reference to it - is returned. - - If the JSON pointer points to an array index that does not exist, it - is created an filled with a `null` value before a reference to it - is returned. All indices between the current maximum and the given - index are also filled with `null`. - - The special value `-` is treated as a synonym for the index past the - end. - - @param[in] ptr a JSON pointer - - @return reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.404 if the JSON pointer can not be resolved - - @liveexample{The behavior is shown in the example.,operatorjson_pointer} - - @since version 2.0.0 - */ - reference operator[](const json_pointer& ptr) - { - return ptr.get_unchecked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Uses a JSON pointer to retrieve a reference to the respective JSON value. - No bound checking is performed. The function does not change the JSON - value; no `null` values are created. In particular, the the special value - `-` yields an exception. - - @param[in] ptr JSON pointer to the desired element - - @return const reference to the element pointed to by @a ptr - - @complexity Constant. - - @throw parse_error.106 if an array index begins with '0' - @throw parse_error.109 if an array index was not a number - @throw out_of_range.402 if the array index '-' is used - @throw out_of_range.404 if the JSON pointer can not be resolved - - @liveexample{The behavior is shown in the example.,operatorjson_pointer_const} - - @since version 2.0.0 - */ - const_reference operator[](const json_pointer& ptr) const - { - return ptr.get_unchecked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Returns a reference to the element at with specified JSON pointer @a ptr, - with bounds checking. - - @param[in] ptr JSON pointer to the desired element - - @return reference to the element pointed to by @a ptr - - @throw parse_error.106 if an array index in the passed JSON pointer @a ptr - begins with '0'. See example below. - - @throw parse_error.109 if an array index in the passed JSON pointer @a ptr - is not a number. See example below. - - @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr - is out of range. See example below. - - @throw out_of_range.402 if the array index '-' is used in the passed JSON - pointer @a ptr. As `at` provides checked access (and no elements are - implicitly inserted), the index '-' is always invalid. See example below. - - @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. - See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 2.0.0 - - @liveexample{The behavior is shown in the example.,at_json_pointer} - */ - reference at(const json_pointer& ptr) - { - return ptr.get_checked(this); - } - - /*! - @brief access specified element via JSON Pointer - - Returns a const reference to the element at with specified JSON pointer @a - ptr, with bounds checking. - - @param[in] ptr JSON pointer to the desired element - - @return reference to the element pointed to by @a ptr - - @throw parse_error.106 if an array index in the passed JSON pointer @a ptr - begins with '0'. See example below. - - @throw parse_error.109 if an array index in the passed JSON pointer @a ptr - is not a number. See example below. - - @throw out_of_range.401 if an array index in the passed JSON pointer @a ptr - is out of range. See example below. - - @throw out_of_range.402 if the array index '-' is used in the passed JSON - pointer @a ptr. As `at` provides checked access (and no elements are - implicitly inserted), the index '-' is always invalid. See example below. - - @throw out_of_range.404 if the JSON pointer @a ptr can not be resolved. - See example below. - - @exceptionsafety Strong guarantee: if an exception is thrown, there are no - changes in the JSON value. - - @complexity Constant. - - @since version 2.0.0 - - @liveexample{The behavior is shown in the example.,at_json_pointer_const} - */ - const_reference at(const json_pointer& ptr) const - { - return ptr.get_checked(this); - } - - /*! - @brief return flattened JSON value - - The function creates a JSON object whose keys are JSON pointers (see [RFC - 6901](https://tools.ietf.org/html/rfc6901)) and whose values are all - primitive. The original JSON value can be restored using the @ref - unflatten() function. - - @return an object that maps JSON pointers to primitive values - - @note Empty objects and arrays are flattened to `null` and will not be - reconstructed correctly by the @ref unflatten() function. - - @complexity Linear in the size the JSON value. - - @liveexample{The following code shows how a JSON object is flattened to an - object whose keys consist of JSON pointers.,flatten} - - @sa @ref unflatten() for the reverse function - - @since version 2.0.0 - */ - basic_json flatten() const - { - basic_json result(value_t::object); - json_pointer::flatten("", *this, result); - return result; - } - - /*! - @brief unflatten a previously flattened JSON value - - The function restores the arbitrary nesting of a JSON value that has been - flattened before using the @ref flatten() function. The JSON value must - meet certain constraints: - 1. The value must be an object. - 2. The keys must be JSON pointers (see - [RFC 6901](https://tools.ietf.org/html/rfc6901)) - 3. The mapped values must be primitive JSON types. - - @return the original JSON from a flattened version - - @note Empty objects and arrays are flattened by @ref flatten() to `null` - values and can not unflattened to their original type. Apart from - this example, for a JSON value `j`, the following is always true: - `j == j.flatten().unflatten()`. - - @complexity Linear in the size the JSON value. - - @throw type_error.314 if value is not an object - @throw type_error.315 if object values are not primitive - - @liveexample{The following code shows how a flattened JSON object is - unflattened into the original nested JSON object.,unflatten} - - @sa @ref flatten() for the reverse function - - @since version 2.0.0 - */ - basic_json unflatten() const - { - return json_pointer::unflatten(*this); - } - - /// @} - - ////////////////////////// - // JSON Patch functions // - ////////////////////////// - - /// @name JSON Patch functions - /// @{ - - /*! - @brief applies a JSON patch - - [JSON Patch](http://jsonpatch.com) defines a JSON document structure for - expressing a sequence of operations to apply to a JSON) document. With - this function, a JSON Patch is applied to the current JSON value by - executing all operations from the patch. - - @param[in] json_patch JSON patch document - @return patched document - - @note The application of a patch is atomic: Either all operations succeed - and the patched document is returned or an exception is thrown. In - any case, the original value is not changed: the patch is applied - to a copy of the value. - - @throw parse_error.104 if the JSON patch does not consist of an array of - objects - - @throw parse_error.105 if the JSON patch is malformed (e.g., mandatory - attributes are missing); example: `"operation add must have member path"` - - @throw out_of_range.401 if an array index is out of range. - - @throw out_of_range.403 if a JSON pointer inside the patch could not be - resolved successfully in the current JSON value; example: `"key baz not - found"` - - @throw out_of_range.405 if JSON pointer has no parent ("add", "remove", - "move") - - @throw other_error.501 if "test" operation was unsuccessful - - @complexity Linear in the size of the JSON value and the length of the - JSON patch. As usually only a fraction of the JSON value is affected by - the patch, the complexity can usually be neglected. - - @liveexample{The following code shows how a JSON patch is applied to a - value.,patch} - - @sa @ref diff -- create a JSON patch by comparing two JSON values - - @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - @sa [RFC 6901 (JSON Pointer)](https://tools.ietf.org/html/rfc6901) - - @since version 2.0.0 - */ - basic_json patch(const basic_json& json_patch) const - { - // make a working copy to apply the patch to - basic_json result = *this; - - // the valid JSON Patch operations - enum class patch_operations {add, remove, replace, move, copy, test, invalid}; - - const auto get_op = [](const std::string & op) - { - if (op == "add") - { - return patch_operations::add; - } - if (op == "remove") - { - return patch_operations::remove; - } - if (op == "replace") - { - return patch_operations::replace; - } - if (op == "move") - { - return patch_operations::move; - } - if (op == "copy") - { - return patch_operations::copy; - } - if (op == "test") - { - return patch_operations::test; - } - - return patch_operations::invalid; - }; - - // wrapper for "add" operation; add value at ptr - const auto operation_add = [&result](json_pointer & ptr, basic_json val) - { - // adding to the root of the target document means replacing it - if (ptr.is_root()) - { - result = val; - } - else - { - // make sure the top element of the pointer exists - json_pointer top_pointer = ptr.top(); - if (top_pointer != ptr) - { - result.at(top_pointer); - } - - // get reference to parent of JSON pointer ptr - const auto last_path = ptr.pop_back(); - basic_json& parent = result[ptr]; - - switch (parent.m_type) - { - case value_t::null: - case value_t::object: - { - // use operator[] to add value - parent[last_path] = val; - break; - } - - case value_t::array: - { - if (last_path == "-") - { - // special case: append to back - parent.push_back(val); - } - else - { - const auto idx = std::stoi(last_path); - if (JSON_UNLIKELY(static_cast<size_type>(idx) > parent.size())) - { - // avoid undefined behavior - JSON_THROW(out_of_range::create(401, "array index " + std::to_string(idx) + " is out of range")); - } - else - { - // default case: insert add offset - parent.insert(parent.begin() + static_cast<difference_type>(idx), val); - } - } - break; - } - - default: - { - // if there exists a parent it cannot be primitive - assert(false); // LCOV_EXCL_LINE - } - } - } - }; - - // wrapper for "remove" operation; remove value at ptr - const auto operation_remove = [&result](json_pointer & ptr) - { - // get reference to parent of JSON pointer ptr - const auto last_path = ptr.pop_back(); - basic_json& parent = result.at(ptr); - - // remove child - if (parent.is_object()) - { - // perform range check - auto it = parent.find(last_path); - if (JSON_LIKELY(it != parent.end())) - { - parent.erase(it); - } - else - { - JSON_THROW(out_of_range::create(403, "key '" + last_path + "' not found")); - } - } - else if (parent.is_array()) - { - // note erase performs range check - parent.erase(static_cast<size_type>(std::stoi(last_path))); - } - }; - - // type check: top level value must be an array - if (JSON_UNLIKELY(not json_patch.is_array())) - { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); - } - - // iterate and apply the operations - for (const auto& val : json_patch) - { - // wrapper to get a value for an operation - const auto get_value = [&val](const std::string & op, - const std::string & member, - bool string_type) -> basic_json& - { - // find value - auto it = val.m_value.object->find(member); - - // context-sensitive error message - const auto error_msg = (op == "op") ? "operation" : "operation '" + op + "'"; - - // check if desired value is present - if (JSON_UNLIKELY(it == val.m_value.object->end())) - { - JSON_THROW(parse_error::create(105, 0, error_msg + " must have member '" + member + "'")); - } - - // check if result is of type string - if (JSON_UNLIKELY(string_type and not it->second.is_string())) - { - JSON_THROW(parse_error::create(105, 0, error_msg + " must have string member '" + member + "'")); - } - - // no error: return value - return it->second; - }; - - // type check: every element of the array must be an object - if (JSON_UNLIKELY(not val.is_object())) - { - JSON_THROW(parse_error::create(104, 0, "JSON patch must be an array of objects")); - } - - // collect mandatory members - const std::string op = get_value("op", "op", true); - const std::string path = get_value(op, "path", true); - json_pointer ptr(path); - - switch (get_op(op)) - { - case patch_operations::add: - { - operation_add(ptr, get_value("add", "value", false)); - break; - } - - case patch_operations::remove: - { - operation_remove(ptr); - break; - } - - case patch_operations::replace: - { - // the "path" location must exist - use at() - result.at(ptr) = get_value("replace", "value", false); - break; - } - - case patch_operations::move: - { - const std::string from_path = get_value("move", "from", true); - json_pointer from_ptr(from_path); - - // the "from" location must exist - use at() - basic_json v = result.at(from_ptr); - - // The move operation is functionally identical to a - // "remove" operation on the "from" location, followed - // immediately by an "add" operation at the target - // location with the value that was just removed. - operation_remove(from_ptr); - operation_add(ptr, v); - break; - } - - case patch_operations::copy: - { - const std::string from_path = get_value("copy", "from", true); - const json_pointer from_ptr(from_path); - - // the "from" location must exist - use at() - result[ptr] = result.at(from_ptr); - break; - } - - case patch_operations::test: - { - bool success = false; - JSON_TRY - { - // check if "value" matches the one at "path" - // the "path" location must exist - use at() - success = (result.at(ptr) == get_value("test", "value", false)); - } - JSON_CATCH (out_of_range&) - { - // ignore out of range errors: success remains false - } - - // throw an exception if test fails - if (JSON_UNLIKELY(not success)) - { - JSON_THROW(other_error::create(501, "unsuccessful: " + val.dump())); - } - - break; - } - - case patch_operations::invalid: - { - // op must be "add", "remove", "replace", "move", "copy", or - // "test" - JSON_THROW(parse_error::create(105, 0, "operation value '" + op + "' is invalid")); - } - } - } - - return result; - } - - /*! - @brief creates a diff as a JSON patch - - Creates a [JSON Patch](http://jsonpatch.com) so that value @a source can - be changed into the value @a target by calling @ref patch function. - - @invariant For two JSON values @a source and @a target, the following code - yields always `true`: - @code {.cpp} - source.patch(diff(source, target)) == target; - @endcode - - @note Currently, only `remove`, `add`, and `replace` operations are - generated. - - @param[in] source JSON value to compare from - @param[in] target JSON value to compare against - @param[in] path helper value to create JSON pointers - - @return a JSON patch to convert the @a source to @a target - - @complexity Linear in the lengths of @a source and @a target. - - @liveexample{The following code shows how a JSON patch is created as a - diff for two JSON values.,diff} - - @sa @ref patch -- apply a JSON patch - - @sa [RFC 6902 (JSON Patch)](https://tools.ietf.org/html/rfc6902) - - @since version 2.0.0 - */ - static basic_json diff(const basic_json& source, const basic_json& target, - const std::string& path = "") - { - // the patch - basic_json result(value_t::array); - - // if the values are the same, return empty patch - if (source == target) - { - return result; - } - - if (source.type() != target.type()) - { - // different types: replace value - result.push_back( - { - {"op", "replace"}, {"path", path}, {"value", target} - }); - } - else - { - switch (source.type()) - { - case value_t::array: - { - // first pass: traverse common elements - std::size_t i = 0; - while (i < source.size() and i < target.size()) - { - // recursive call to compare array values at index i - auto temp_diff = diff(source[i], target[i], path + "/" + std::to_string(i)); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - ++i; - } - - // i now reached the end of at least one array - // in a second pass, traverse the remaining elements - - // remove my remaining elements - const auto end_index = static_cast<difference_type>(result.size()); - while (i < source.size()) - { - // add operations in reverse order to avoid invalid - // indices - result.insert(result.begin() + end_index, object( - { - {"op", "remove"}, - {"path", path + "/" + std::to_string(i)} - })); - ++i; - } - - // add other remaining elements - while (i < target.size()) - { - result.push_back( - { - {"op", "add"}, - {"path", path + "/" + std::to_string(i)}, - {"value", target[i]} - }); - ++i; - } - - break; - } - - case value_t::object: - { - // first pass: traverse this object's elements - for (auto it = source.begin(); it != source.end(); ++it) - { - // escape the key name to be used in a JSON patch - const auto key = json_pointer::escape(it.key()); - - if (target.find(it.key()) != target.end()) - { - // recursive call to compare object values at key it - auto temp_diff = diff(it.value(), target[it.key()], path + "/" + key); - result.insert(result.end(), temp_diff.begin(), temp_diff.end()); - } - else - { - // found a key that is not in o -> remove it - result.push_back(object( - { - {"op", "remove"}, {"path", path + "/" + key} - })); - } - } - - // second pass: traverse other object's elements - for (auto it = target.begin(); it != target.end(); ++it) - { - if (source.find(it.key()) == source.end()) - { - // found a key that is not in this -> add it - const auto key = json_pointer::escape(it.key()); - result.push_back( - { - {"op", "add"}, {"path", path + "/" + key}, - {"value", it.value()} - }); - } - } - - break; - } - - default: - { - // both primitive type: replace value - result.push_back( - { - {"op", "replace"}, {"path", path}, {"value", target} - }); - break; - } - } - } - - return result; - } - - /// @} -}; - -///////////// -// presets // -///////////// - -/*! -@brief default JSON class - -This type is the default specialization of the @ref basic_json class which -uses the standard template types. - -@since version 1.0.0 -*/ -using json = basic_json<>; - -////////////////// -// json_pointer // -////////////////// - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_and_create(NLOHMANN_BASIC_JSON_TPL& j) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - auto result = &j; - - // in case no reference tokens exist, return a reference to the JSON value - // j which will be overwritten by a primitive value - for (const auto& reference_token : reference_tokens) - { - switch (result->m_type) - { - case detail::value_t::null: - { - if (reference_token == "0") - { - // start a new array if reference token is 0 - result = &result->operator[](0); - } - else - { - // start a new object otherwise - result = &result->operator[](reference_token); - } - break; - } - - case detail::value_t::object: - { - // create an entry in the object - result = &result->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // create an entry in the array - JSON_TRY - { - result = &result->operator[](static_cast<size_type>(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - /* - The following code is only reached if there exists a reference - token _and_ the current value is primitive. In this case, we have - an error situation, because primitive values may only occur as - single value; that is, with an empty list of reference tokens. - */ - default: - JSON_THROW(detail::type_error::create(313, "invalid value to unflatten")); - } - } - - return *result; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_unchecked(NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - // convert null values to arrays or objects before continuing - if (ptr->m_type == detail::value_t::null) - { - // check if reference token is a number - const bool nums = - std::all_of(reference_token.begin(), reference_token.end(), - [](const char x) - { - return (x >= '0' and x <= '9'); - }); - - // change value to array for numbers or "-" or to object otherwise - *ptr = (nums or reference_token == "-") - ? detail::value_t::array - : detail::value_t::object; - } - - switch (ptr->m_type) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - if (reference_token == "-") - { - // explicitly treat "-" as index beyond the end - ptr = &ptr->operator[](ptr->m_value.array->size()); - } - else - { - // convert array index to number; unchecked access - JSON_TRY - { - ptr = &ptr->operator[]( - static_cast<size_type>(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_checked(NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // note: at performs range check - JSON_TRY - { - ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -const NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_unchecked(const NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // use unchecked object access - ptr = &ptr->operator[](reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" cannot be used for const access - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // use unchecked array access - JSON_TRY - { - ptr = &ptr->operator[]( - static_cast<size_type>(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -const NLOHMANN_BASIC_JSON_TPL& -json_pointer::get_checked(const NLOHMANN_BASIC_JSON_TPL* ptr) const -{ - using size_type = typename NLOHMANN_BASIC_JSON_TPL::size_type; - for (const auto& reference_token : reference_tokens) - { - switch (ptr->m_type) - { - case detail::value_t::object: - { - // note: at performs range check - ptr = &ptr->at(reference_token); - break; - } - - case detail::value_t::array: - { - if (JSON_UNLIKELY(reference_token == "-")) - { - // "-" always fails the range check - JSON_THROW(detail::out_of_range::create(402, - "array index '-' (" + std::to_string(ptr->m_value.array->size()) + - ") is out of range")); - } - - // error condition (cf. RFC 6901, Sect. 4) - if (JSON_UNLIKELY(reference_token.size() > 1 and reference_token[0] == '0')) - { - JSON_THROW(detail::parse_error::create(106, 0, - "array index '" + reference_token + - "' must not begin with '0'")); - } - - // note: at performs range check - JSON_TRY - { - ptr = &ptr->at(static_cast<size_type>(std::stoi(reference_token))); - } - JSON_CATCH(std::invalid_argument&) - { - JSON_THROW(detail::parse_error::create(109, 0, "array index '" + reference_token + "' is not a number")); - } - break; - } - - default: - JSON_THROW(detail::out_of_range::create(404, "unresolved reference token '" + reference_token + "'")); - } - } - - return *ptr; -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -void json_pointer::flatten(const std::string& reference_string, - const NLOHMANN_BASIC_JSON_TPL& value, - NLOHMANN_BASIC_JSON_TPL& result) -{ - switch (value.m_type) - { - case detail::value_t::array: - { - if (value.m_value.array->empty()) - { - // flatten empty array as null - result[reference_string] = nullptr; - } - else - { - // iterate array and use index as reference string - for (std::size_t i = 0; i < value.m_value.array->size(); ++i) - { - flatten(reference_string + "/" + std::to_string(i), - value.m_value.array->operator[](i), result); - } - } - break; - } - - case detail::value_t::object: - { - if (value.m_value.object->empty()) - { - // flatten empty object as null - result[reference_string] = nullptr; - } - else - { - // iterate object and use keys as reference string - for (const auto& element : *value.m_value.object) - { - flatten(reference_string + "/" + escape(element.first), element.second, result); - } - } - break; - } - - default: - { - // add primitive value with its reference string - result[reference_string] = value; - break; - } - } -} - -NLOHMANN_BASIC_JSON_TPL_DECLARATION -NLOHMANN_BASIC_JSON_TPL -json_pointer::unflatten(const NLOHMANN_BASIC_JSON_TPL& value) -{ - if (JSON_UNLIKELY(not value.is_object())) - { - JSON_THROW(detail::type_error::create(314, "only objects can be unflattened")); - } - - NLOHMANN_BASIC_JSON_TPL result; - - // iterate the JSON object values - for (const auto& element : *value.m_value.object) - { - if (JSON_UNLIKELY(not element.second.is_primitive())) - { - JSON_THROW(detail::type_error::create(315, "values in object must be primitive")); - } - - // assign value to reference pointed to by JSON pointer; Note that if - // the JSON pointer is "" (i.e., points to the whole value), function - // get_and_create returns a reference to result itself. An assignment - // will then create a primitive value. - json_pointer(element.first).get_and_create(result) = element.second; - } - - return result; -} - -inline bool operator==(json_pointer const& lhs, json_pointer const& rhs) noexcept -{ - return (lhs.reference_tokens == rhs.reference_tokens); -} - -inline bool operator!=(json_pointer const& lhs, json_pointer const& rhs) noexcept -{ - return not (lhs == rhs); -} -} // namespace nlohmann - - -/////////////////////// -// nonmember support // -/////////////////////// - -// specialization of std::swap, and std::hash -namespace std -{ -/*! -@brief exchanges the values of two JSON objects - -@since version 1.0.0 -*/ -template<> -inline void swap(nlohmann::json& j1, - nlohmann::json& j2) noexcept( - is_nothrow_move_constructible<nlohmann::json>::value and - is_nothrow_move_assignable<nlohmann::json>::value - ) -{ - j1.swap(j2); -} - -/// hash value for JSON objects -template<> -struct hash<nlohmann::json> -{ - /*! - @brief return a hash value for a JSON object - - @since version 1.0.0 - */ - std::size_t operator()(const nlohmann::json& j) const - { - // a naive hashing via the string representation - const auto& h = hash<nlohmann::json::string_t>(); - return h(j.dump()); - } -}; - -/// specialization for std::less<value_t> -/// @note: do not remove the space after '<', -/// see https://github.com/nlohmann/json/pull/679 -template<> -struct less< ::nlohmann::detail::value_t> -{ - /*! - @brief compare two value_t enum values - @since version 3.0.0 - */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept - { - return nlohmann::detail::operator<(lhs, rhs); - } -}; - -} // namespace std - -/*! -@brief user-defined string literal for JSON values - -This operator implements a user-defined string literal for JSON objects. It -can be used by adding `"_json"` to a string literal and returns a JSON object -if no parse error occurred. - -@param[in] s a string representation of a JSON object -@param[in] n the length of string @a s -@return a JSON object - -@since version 1.0.0 -*/ -inline nlohmann::json operator "" _json(const char* s, std::size_t n) -{ - return nlohmann::json::parse(s, s + n); -} - -/*! -@brief user-defined string literal for JSON pointer - -This operator implements a user-defined string literal for JSON Pointers. It -can be used by adding `"_json_pointer"` to a string literal and returns a JSON pointer -object if no parse error occurred. - -@param[in] s a string representation of a JSON Pointer -@param[in] n the length of string @a s -@return a JSON pointer object - -@since version 2.0.0 -*/ -inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std::size_t n) -{ - return nlohmann::json::json_pointer(std::string(s, n)); -} - -// restore GCC/clang diagnostic settings -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic pop -#endif -#if defined(__clang__) - #pragma GCC diagnostic pop -#endif - -// clean up -#undef JSON_CATCH -#undef JSON_THROW -#undef JSON_TRY -#undef JSON_LIKELY -#undef JSON_UNLIKELY -#undef JSON_DEPRECATED -#undef NLOHMANN_BASIC_JSON_TPL_DECLARATION -#undef NLOHMANN_BASIC_JSON_TPL - -#endif diff --git a/src/nmodl/ext/spdlog/async.h b/src/nmodl/ext/spdlog/async.h deleted file mode 100644 index 9264a4e3f4..0000000000 --- a/src/nmodl/ext/spdlog/async.h +++ /dev/null @@ -1,87 +0,0 @@ - -// -// Copyright(c) 2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// -// Async logging using global thread pool -// All loggers created here share same global thread pool. -// Each log message is pushed to a queue along withe a shared pointer to the -// logger. -// If a logger deleted while having pending messages in the queue, it's actual -// destruction will defer -// until all its messages are processed by the thread pool. -// This is because each message in the queue holds a shared_ptr to the -// originating logger. - -#include "spdlog/async_logger.h" -#include "spdlog/details/registry.h" -#include "spdlog/details/thread_pool.h" - -#include <memory> -#include <mutex> - -namespace spdlog { - -namespace details { -static const size_t default_async_q_size = 8192; -} - -// async logger factory - creates async loggers backed with thread pool. -// if a global thread pool doesn't already exist, create it with default queue -// size of 8192 items and single thread. -template<async_overflow_policy OverflowPolicy = async_overflow_policy::block> -struct async_factory_impl -{ - template<typename Sink, typename... SinkArgs> - static std::shared_ptr<async_logger> create(std::string logger_name, SinkArgs &&... args) - { - auto ®istry_inst = details::registry::instance(); - - // create global thread pool if not already exists.. - std::lock_guard<std::recursive_mutex> tp_lock(registry_inst.tp_mutex()); - auto tp = registry_inst.get_tp(); - if (tp == nullptr) - { - tp = std::make_shared<details::thread_pool>(details::default_async_q_size, 1); - registry_inst.set_tp(tp); - } - - auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); - auto new_logger = std::make_shared<async_logger>(std::move(logger_name), std::move(sink), std::move(tp), OverflowPolicy); - registry_inst.register_and_init(new_logger); - return new_logger; - } -}; - -using async_factory = async_factory_impl<async_overflow_policy::block>; -using async_factory_nonblock = async_factory_impl<async_overflow_policy::overrun_oldest>; - -template<typename Sink, typename... SinkArgs> -inline std::shared_ptr<spdlog::logger> create_async(std::string logger_name, SinkArgs &&... sink_args) -{ - return async_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); -} - -template<typename Sink, typename... SinkArgs> -inline std::shared_ptr<spdlog::logger> create_async_nb(std::string logger_name, SinkArgs &&... sink_args) -{ - return async_factory_nonblock::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); -} - -// set global thread pool. -inline void init_thread_pool(size_t q_size, size_t thread_count) -{ - auto tp = std::make_shared<details::thread_pool>(q_size, thread_count); - details::registry::instance().set_tp(std::move(tp)); -} - -// get the global thread pool. -inline std::shared_ptr<spdlog::details::thread_pool> thread_pool() -{ - return details::registry::instance().get_tp(); -} -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/async_logger.h b/src/nmodl/ext/spdlog/async_logger.h deleted file mode 100644 index a7ecb78737..0000000000 --- a/src/nmodl/ext/spdlog/async_logger.h +++ /dev/null @@ -1,73 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// Very fast asynchronous logger (millions of logs per second on an average -// desktop) -// Uses pre allocated lockfree queue for maximum throughput even under large -// number of threads. -// Creates a single back thread to pop messages from the queue and log them. -// -// Upon each log write the logger: -// 1. Checks if its log level is enough to log the message -// 2. Push a new copy of the message to a queue (or block the caller until -// space is available in the queue) -// 3. will throw spdlog_ex upon log exceptions -// Upon destruction, logs all remaining messages in the queue before -// destructing.. - -#include "spdlog/common.h" -#include "spdlog/logger.h" - -#include <chrono> -#include <memory> -#include <string> - -namespace spdlog { - -// Async overflow policy - block by default. -enum class async_overflow_policy -{ - block, // Block until message can be enqueued - overrun_oldest // Discard oldest message in the queue if full when trying to - // add new item. -}; - -namespace details { -class thread_pool; -} - -class async_logger final : public std::enable_shared_from_this<async_logger>, public logger -{ - friend class details::thread_pool; - -public: - template<typename It> - async_logger(std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, - async_overflow_policy overflow_policy = async_overflow_policy::block); - - async_logger(std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, - async_overflow_policy overflow_policy = async_overflow_policy::block); - - async_logger(std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, - async_overflow_policy overflow_policy = async_overflow_policy::block); - - std::shared_ptr<logger> clone(std::string new_name) override; - -protected: - void sink_it_(details::log_msg &msg) override; - void flush_() override; - - void backend_log_(const details::log_msg &incoming_log_msg); - void backend_flush_(); - -private: - std::weak_ptr<details::thread_pool> thread_pool_; - async_overflow_policy overflow_policy_; -}; -} // namespace spdlog - -#include "details/async_logger_impl.h" diff --git a/src/nmodl/ext/spdlog/common.h b/src/nmodl/ext/spdlog/common.h deleted file mode 100644 index b614734624..0000000000 --- a/src/nmodl/ext/spdlog/common.h +++ /dev/null @@ -1,186 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include "spdlog/tweakme.h" - -#include <atomic> -#include <chrono> -#include <functional> -#include <initializer_list> -#include <memory> -#include <stdexcept> -#include <string> -#include <type_traits> -#include <unordered_map> - -#if defined(SPDLOG_WCHAR_FILENAMES) || defined(SPDLOG_WCHAR_TO_UTF8_SUPPORT) -#include <codecvt> -#include <locale> -#endif - -#include "spdlog/details/null_mutex.h" - -// visual studio upto 2013 does not support noexcept nor constexpr -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define SPDLOG_NOEXCEPT throw() -#define SPDLOG_CONSTEXPR -#else -#define SPDLOG_NOEXCEPT noexcept -#define SPDLOG_CONSTEXPR constexpr -#endif - -#if defined(__GNUC__) || defined(__clang__) -#define SPDLOG_DEPRECATED __attribute__((deprecated)) -#elif defined(_MSC_VER) -#define SPDLOG_DEPRECATED __declspec(deprecated) -#else -#define SPDLOG_DEPRECATED -#endif - -#include "spdlog/fmt/fmt.h" - -namespace spdlog { - -class formatter; - -namespace sinks { -class sink; -} - -using log_clock = std::chrono::system_clock; -using sink_ptr = std::shared_ptr<sinks::sink>; -using sinks_init_list = std::initializer_list<sink_ptr>; -using log_err_handler = std::function<void(const std::string &err_msg)>; - -#if defined(SPDLOG_NO_ATOMIC_LEVELS) -using level_t = details::null_atomic_int; -#else -using level_t = std::atomic<int>; -#endif - -// Log level enum -namespace level { -enum level_enum -{ - trace = 0, - debug = 1, - info = 2, - warn = 3, - err = 4, - critical = 5, - off = 6 -}; - -#if !defined(SPDLOG_LEVEL_NAMES) -#define SPDLOG_LEVEL_NAMES \ - { \ - "trace", "debug", "info", "warning", "error", "critical", "off" \ - } -#endif -static const char *level_names[] SPDLOG_LEVEL_NAMES; - -static const char *short_level_names[]{"T", "D", "I", "W", "E", "C", "O"}; - -inline const char *to_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT -{ - return level_names[l]; -} - -inline const char *to_short_c_str(spdlog::level::level_enum l) SPDLOG_NOEXCEPT -{ - return short_level_names[l]; -} - -inline spdlog::level::level_enum from_str(const std::string &name) SPDLOG_NOEXCEPT -{ - static std::unordered_map<std::string, level_enum> name_to_level = // map string->level - {{level_names[0], level::trace}, // trace - {level_names[1], level::debug}, // debug - {level_names[2], level::info}, // info - {level_names[3], level::warn}, // warn - {level_names[4], level::err}, // err - {level_names[5], level::critical}, // critical - {level_names[6], level::off}}; // off - - auto lvl_it = name_to_level.find(name); - return lvl_it != name_to_level.end() ? lvl_it->second : level::off; -} - -using level_hasher = std::hash<int>; -} // namespace level - -// -// Pattern time - specific time getting to use for pattern_formatter. -// local time by default -// -enum class pattern_time_type -{ - local, // log localtime - utc // log utc -}; - -// -// Log exception -// -class spdlog_ex : public std::exception -{ -public: - explicit spdlog_ex(std::string msg) - : msg_(std::move(msg)) - { - } - - spdlog_ex(const std::string &msg, int last_errno) - { - fmt::memory_buffer outbuf; - fmt::format_system_error(outbuf, last_errno, msg); - msg_ = fmt::to_string(outbuf); - } - - const char *what() const SPDLOG_NOEXCEPT override - { - return msg_.c_str(); - } - -private: - std::string msg_; -}; - -// -// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) -// -#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) -using filename_t = std::wstring; -#else -using filename_t = std::string; -#endif - -#define SPDLOG_CATCH_AND_HANDLE \ - catch (const std::exception &ex) \ - { \ - err_handler_(ex.what()); \ - } \ - catch (...) \ - { \ - err_handler_("Unknown exeption in logger"); \ - } - -namespace details { -// make_unique support for pre c++14 - -#if __cplusplus >= 201402L // C++14 and beyond -using std::make_unique; -#else -template<typename T, typename... Args> -std::unique_ptr<T> make_unique(Args &&... args) -{ - static_assert(!std::is_array<T>::value, "arrays not supported"); - return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} -#endif -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/async_logger_impl.h b/src/nmodl/ext/spdlog/details/async_logger_impl.h deleted file mode 100644 index 2841ab2b27..0000000000 --- a/src/nmodl/ext/spdlog/details/async_logger_impl.h +++ /dev/null @@ -1,110 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// async logger implementation -// uses a thread pool to perform the actual logging - -#include "spdlog/details/thread_pool.h" - -#include <chrono> -#include <memory> -#include <string> - -template<typename It> -inline spdlog::async_logger::async_logger( - std::string logger_name, It begin, It end, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) - : logger(std::move(logger_name), begin, end) - , thread_pool_(std::move(tp)) - , overflow_policy_(overflow_policy) -{ -} - -inline spdlog::async_logger::async_logger( - std::string logger_name, sinks_init_list sinks_list, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) - : async_logger(std::move(logger_name), sinks_list.begin(), sinks_list.end(), std::move(tp), overflow_policy) -{ -} - -inline spdlog::async_logger::async_logger( - std::string logger_name, sink_ptr single_sink, std::weak_ptr<details::thread_pool> tp, async_overflow_policy overflow_policy) - : async_logger(std::move(logger_name), {std::move(single_sink)}, std::move(tp), overflow_policy) -{ -} - -// send the log message to the thread pool -inline void spdlog::async_logger::sink_it_(details::log_msg &msg) -{ -#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) - incr_msg_counter_(msg); -#endif - if (auto pool_ptr = thread_pool_.lock()) - { - pool_ptr->post_log(shared_from_this(), std::move(msg), overflow_policy_); - } - else - { - throw spdlog_ex("async log: thread pool doesn't exist anymore"); - } -} - -// send flush request to the thread pool -inline void spdlog::async_logger::flush_() -{ - if (auto pool_ptr = thread_pool_.lock()) - { - pool_ptr->post_flush(shared_from_this(), overflow_policy_); - } - else - { - throw spdlog_ex("async flush: thread pool doesn't exist anymore"); - } -} - -// -// backend functions - called from the thread pool to do the actual job -// -inline void spdlog::async_logger::backend_log_(const details::log_msg &incoming_log_msg) -{ - try - { - for (auto &s : sinks_) - { - if (s->should_log(incoming_log_msg.level)) - { - s->log(incoming_log_msg); - } - } - } - SPDLOG_CATCH_AND_HANDLE - - if (should_flush_(incoming_log_msg)) - { - backend_flush_(); - } -} - -inline void spdlog::async_logger::backend_flush_() -{ - try - { - for (auto &sink : sinks_) - { - sink->flush(); - } - } - SPDLOG_CATCH_AND_HANDLE -} - -inline std::shared_ptr<spdlog::logger> spdlog::async_logger::clone(std::string new_name) -{ - auto cloned = std::make_shared<spdlog::async_logger>(std::move(new_name), sinks_.begin(), sinks_.end(), thread_pool_, overflow_policy_); - - cloned->set_level(this->level()); - cloned->flush_on(this->flush_level()); - cloned->set_error_handler(this->error_handler()); - return std::move(cloned); -} diff --git a/src/nmodl/ext/spdlog/details/circular_q.h b/src/nmodl/ext/spdlog/details/circular_q.h deleted file mode 100644 index b01325bb75..0000000000 --- a/src/nmodl/ext/spdlog/details/circular_q.h +++ /dev/null @@ -1,72 +0,0 @@ -// -// Copyright(c) 2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -// cirucal q view of std::vector. -#pragma once - -#include <vector> - -namespace spdlog { -namespace details { -template<typename T> -class circular_q -{ -public: - using item_type = T; - - explicit circular_q(size_t max_items) - : max_items_(max_items + 1) // one item is reserved as marker for full q - , v_(max_items_) - { - } - - // push back, overrun (oldest) item if no room left - void push_back(T &&item) - { - v_[tail_] = std::move(item); - tail_ = (tail_ + 1) % max_items_; - - if (tail_ == head_) // overrun last item if full - { - head_ = (head_ + 1) % max_items_; - ++overrun_counter_; - } - } - - // Pop item from front. - // If there are no elements in the container, the behavior is undefined. - void pop_front(T &popped_item) - { - popped_item = std::move(v_[head_]); - head_ = (head_ + 1) % max_items_; - } - - bool empty() - { - return tail_ == head_; - } - - bool full() - { - // head is ahead of the tail by 1 - return ((tail_ + 1) % max_items_) == head_; - } - - size_t overrun_counter() const - { - return overrun_counter_; - } - -private: - size_t max_items_; - typename std::vector<T>::size_type head_ = 0; - typename std::vector<T>::size_type tail_ = 0; - - std::vector<T> v_; - - size_t overrun_counter_ = 0; -}; -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/console_globals.h b/src/nmodl/ext/spdlog/details/console_globals.h deleted file mode 100644 index e2afb6bf01..0000000000 --- a/src/nmodl/ext/spdlog/details/console_globals.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once -// -// Copyright(c) 2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#include "spdlog/details/null_mutex.h" -#include <cstdio> -#include <mutex> - -#ifdef _WIN32 - -#ifndef NOMINMAX -#define NOMINMAX // prevent windows redefining min/max -#endif - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#include <windows.h> -#endif - -namespace spdlog { -namespace details { -struct console_stdout -{ - static std::FILE *stream() - { - return stdout; - } -#ifdef _WIN32 - static HANDLE handle() - { - return ::GetStdHandle(STD_OUTPUT_HANDLE); - } -#endif -}; - -struct console_stderr -{ - static std::FILE *stream() - { - return stderr; - } -#ifdef _WIN32 - static HANDLE handle() - { - return ::GetStdHandle(STD_ERROR_HANDLE); - } -#endif -}; - -struct console_mutex -{ - using mutex_t = std::mutex; - static mutex_t &mutex() - { - static mutex_t s_mutex; - return s_mutex; - } -}; - -struct console_nullmutex -{ - using mutex_t = null_mutex; - static mutex_t &mutex() - { - static mutex_t s_mutex; - return s_mutex; - } -}; -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/file_helper.h b/src/nmodl/ext/spdlog/details/file_helper.h deleted file mode 100644 index f72820004d..0000000000 --- a/src/nmodl/ext/spdlog/details/file_helper.h +++ /dev/null @@ -1,152 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// Helper class for file sinks. -// When failing to open a file, retry several times(5) with a delay interval(10 ms). -// Throw spdlog_ex exception on errors. - -#include "spdlog/details/log_msg.h" -#include "spdlog/details/os.h" - -#include <cerrno> -#include <chrono> -#include <cstdio> -#include <string> -#include <thread> -#include <tuple> - -namespace spdlog { -namespace details { - -class file_helper -{ - -public: - const int open_tries = 5; - const int open_interval = 10; - - explicit file_helper() = default; - - file_helper(const file_helper &) = delete; - file_helper &operator=(const file_helper &) = delete; - - ~file_helper() - { - close(); - } - - void open(const filename_t &fname, bool truncate = false) - { - close(); - auto *mode = truncate ? SPDLOG_FILENAME_T("wb") : SPDLOG_FILENAME_T("ab"); - _filename = fname; - for (int tries = 0; tries < open_tries; ++tries) - { - if (!os::fopen_s(&fd_, fname, mode)) - { - return; - } - - details::os::sleep_for_millis(open_interval); - } - - throw spdlog_ex("Failed opening file " + os::filename_to_str(_filename) + " for writing", errno); - } - - void reopen(bool truncate) - { - if (_filename.empty()) - { - throw spdlog_ex("Failed re opening file - was not opened before"); - } - open(_filename, truncate); - } - - void flush() - { - std::fflush(fd_); - } - - void close() - { - if (fd_ != nullptr) - { - std::fclose(fd_); - fd_ = nullptr; - } - } - - void write(const fmt::memory_buffer &buf) - { - size_t msg_size = buf.size(); - auto data = buf.data(); - if (std::fwrite(data, 1, msg_size, fd_) != msg_size) - { - throw spdlog_ex("Failed writing to file " + os::filename_to_str(_filename), errno); - } - } - - size_t size() const - { - if (fd_ == nullptr) - { - throw spdlog_ex("Cannot use size() on closed file " + os::filename_to_str(_filename)); - } - return os::filesize(fd_); - } - - const filename_t &filename() const - { - return _filename; - } - - static bool file_exists(const filename_t &fname) - { - return os::file_exists(fname); - } - - // - // return file path and its extension: - // - // "mylog.txt" => ("mylog", ".txt") - // "mylog" => ("mylog", "") - // "mylog." => ("mylog.", "") - // "/dir1/dir2/mylog.txt" => ("/dir1/dir2/mylog", ".txt") - // - // the starting dot in filenames is ignored (hidden files): - // - // ".mylog" => (".mylog". "") - // "my_folder/.mylog" => ("my_folder/.mylog", "") - // "my_folder/.mylog.txt" => ("my_folder/.mylog", ".txt") - static std::tuple<filename_t, filename_t> split_by_extenstion(const spdlog::filename_t &fname) - { - auto ext_index = fname.rfind('.'); - - // no valid extension found - return whole path and empty string as - // extension - if (ext_index == filename_t::npos || ext_index == 0 || ext_index == fname.size() - 1) - { - return std::make_tuple(fname, spdlog::filename_t()); - } - - // treat casese like "/etc/rc.d/somelogfile or "/abc/.hiddenfile" - auto folder_index = fname.rfind(details::os::folder_sep); - if (folder_index != filename_t::npos && folder_index >= ext_index - 1) - { - return std::make_tuple(fname, spdlog::filename_t()); - } - - // finally - return a valid base and extension tuple - return std::make_tuple(fname.substr(0, ext_index), fname.substr(ext_index)); - } - -private: - std::FILE *fd_{nullptr}; - filename_t _filename; -}; -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/fmt_helper.h b/src/nmodl/ext/spdlog/details/fmt_helper.h deleted file mode 100644 index 1518b2c0ff..0000000000 --- a/src/nmodl/ext/spdlog/details/fmt_helper.h +++ /dev/null @@ -1,127 +0,0 @@ -// -// Created by gabi on 6/15/18. -// - -#pragma once - -#include "chrono" -#include "spdlog/fmt/fmt.h" - -// Some fmt helpers to efficiently format and pad ints and strings -namespace spdlog { -namespace details { -namespace fmt_helper { - -template<size_t Buffer_Size> -inline void append_str(const std::string &str, fmt::basic_memory_buffer<char, Buffer_Size> &dest) -{ - auto *str_ptr = str.data(); - dest.append(str_ptr, str_ptr + str.size()); -} - -template<size_t Buffer_Size> -inline void append_c_str(const char *c_str, fmt::basic_memory_buffer<char, Buffer_Size> &dest) -{ - auto len = std::char_traits<char>::length(c_str); - dest.append(c_str, c_str + len); -} - -template<size_t Buffer_Size1, size_t Buffer_Size2> -inline void append_buf(const fmt::basic_memory_buffer<char, Buffer_Size1> &buf, fmt::basic_memory_buffer<char, Buffer_Size2> &dest) -{ - auto *buf_ptr = buf.data(); - dest.append(buf_ptr, buf_ptr + buf.size()); -} - -template<typename T, size_t Buffer_Size> -inline void append_int(T n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) -{ - fmt::format_int i(n); - dest.append(i.data(), i.data() + i.size()); -} - -template<size_t Buffer_Size> -inline void pad2(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) -{ - if (n > 99) - { - append_int(n, dest); - return; - } - if (n > 9) // 10-99 - { - dest.push_back(static_cast<char>('0' + n / 10)); - dest.push_back(static_cast<char>('0' + n % 10)); - return; - } - if (n >= 0) // 0-9 - { - dest.push_back('0'); - dest.push_back(static_cast<char>('0' + n)); - return; - } - // negatives (unlikely, but just in case, let fmt deal with it) - fmt::format_to(dest, "{:02}", n); -} - -template<size_t Buffer_Size> -inline void pad3(int n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) -{ - if (n > 999) - { - append_int(n, dest); - return; - } - - if (n > 99) // 100-999 - { - dest.push_back(static_cast<char>('0' + n / 100)); - pad2(n % 100, dest); - return; - } - if (n > 9) // 10-99 - { - dest.push_back('0'); - dest.push_back(static_cast<char>('0' + n / 10)); - dest.push_back(static_cast<char>('0' + n % 10)); - return; - } - if (n >= 0) - { - dest.push_back('0'); - dest.push_back('0'); - dest.push_back(static_cast<char>('0' + n)); - return; - } - // negatives (unlikely, but just in case let fmt deal with it) - fmt::format_to(dest, "{:03}", n); -} - -template<size_t Buffer_Size> -inline void pad6(size_t n, fmt::basic_memory_buffer<char, Buffer_Size> &dest) -{ - if (n > 99999) - { - append_int(n, dest); - return; - } - pad3(static_cast<int>(n / 1000), dest); - pad3(static_cast<int>(n % 1000), dest); -} - -// return fraction of a second of the given time_point. -// e.g. -// fraction<std::milliseconds>(tp) -> will return the millis part of the second -template<typename ToDuration> -inline ToDuration time_fraction(const log_clock::time_point &tp) -{ - using std::chrono::duration_cast; - using std::chrono::seconds; - auto duration = tp.time_since_epoch(); - auto secs = duration_cast<seconds>(duration); - return duration_cast<ToDuration>(duration) - duration_cast<ToDuration>(secs); -} - -} // namespace fmt_helper -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/log_msg.h b/src/nmodl/ext/spdlog/details/log_msg.h deleted file mode 100644 index 49987515b7..0000000000 --- a/src/nmodl/ext/spdlog/details/log_msg.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include "spdlog/common.h" -#include "spdlog/details/os.h" - -#include <string> -#include <utility> - -namespace spdlog { -namespace details { -struct log_msg -{ - log_msg() = default; - - log_msg(const std::string *loggers_name, level::level_enum lvl) - : logger_name(loggers_name) - , level(lvl) -#ifndef SPDLOG_NO_DATETIME - , time(os::now()) -#endif - -#ifndef SPDLOG_NO_THREAD_ID - , thread_id(os::thread_id()) -#endif - { - } - - log_msg(const log_msg &other) = delete; - log_msg(log_msg &&other) = delete; - log_msg &operator=(log_msg &&other) = delete; - - const std::string *logger_name{nullptr}; - level::level_enum level; - log_clock::time_point time; - size_t thread_id; - fmt::memory_buffer raw; - size_t msg_id; - - // info about wrapping the formatted text with color (updated by pattern_formatter). - mutable size_t color_range_start{0}; - mutable size_t color_range_end{0}; -}; -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/logger_impl.h b/src/nmodl/ext/spdlog/details/logger_impl.h deleted file mode 100644 index 46301ea1e1..0000000000 --- a/src/nmodl/ext/spdlog/details/logger_impl.h +++ /dev/null @@ -1,383 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include "spdlog/details/fmt_helper.h" - -#include <memory> -#include <string> - -// create logger with given name, sinks and the default pattern formatter -// all other ctors will call this one -template<typename It> -inline spdlog::logger::logger(std::string logger_name, It begin, It end) - : name_(std::move(logger_name)) - , sinks_(begin, end) - , level_(level::info) - , flush_level_(level::off) - , last_err_time_(0) - , msg_counter_(1) // message counter will start from 1. 0-message id will be - // reserved for controll messages -{ - err_handler_ = [this](const std::string &msg) { this->default_err_handler_(msg); }; -} - -// ctor with sinks as init list -inline spdlog::logger::logger(std::string logger_name, sinks_init_list sinks_list) - : logger(std::move(logger_name), sinks_list.begin(), sinks_list.end()) -{ -} - -// ctor with single sink -inline spdlog::logger::logger(std::string logger_name, spdlog::sink_ptr single_sink) - : logger(std::move(logger_name), {std::move(single_sink)}) -{ -} - -inline spdlog::logger::~logger() = default; - -inline void spdlog::logger::set_formatter(std::unique_ptr<spdlog::formatter> f) -{ - for (auto &sink : sinks_) - { - sink->set_formatter(f->clone()); - } -} - -inline void spdlog::logger::set_pattern(std::string pattern, pattern_time_type time_type) -{ - auto new_formatter = details::make_unique<spdlog::pattern_formatter>(std::move(pattern), time_type); - set_formatter(std::move(new_formatter)); -} - -template<typename... Args> -inline void spdlog::logger::log(level::level_enum lvl, const char *fmt, const Args &... args) -{ - if (!should_log(lvl)) - { - return; - } - - try - { - details::log_msg log_msg(&name_, lvl); - fmt::format_to(log_msg.raw, fmt, args...); - sink_it_(log_msg); - } - SPDLOG_CATCH_AND_HANDLE -} - -template<typename... Args> -inline void spdlog::logger::log(level::level_enum lvl, const char *msg) -{ - if (!should_log(lvl)) - { - return; - } - try - { - details::log_msg log_msg(&name_, lvl); - details::fmt_helper::append_c_str(msg, log_msg.raw); - sink_it_(log_msg); - } - SPDLOG_CATCH_AND_HANDLE -} - -template<typename T> -inline void spdlog::logger::log(level::level_enum lvl, const T &msg) -{ - if (!should_log(lvl)) - { - return; - } - try - { - details::log_msg log_msg(&name_, lvl); - fmt::format_to(log_msg.raw, "{}", msg); - sink_it_(log_msg); - } - SPDLOG_CATCH_AND_HANDLE -} - -template<typename... Args> -inline void spdlog::logger::trace(const char *fmt, const Args &... args) -{ - log(level::trace, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::debug(const char *fmt, const Args &... args) -{ - log(level::debug, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::info(const char *fmt, const Args &... args) -{ - log(level::info, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::warn(const char *fmt, const Args &... args) -{ - log(level::warn, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::error(const char *fmt, const Args &... args) -{ - log(level::err, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::critical(const char *fmt, const Args &... args) -{ - log(level::critical, fmt, args...); -} - -template<typename T> -inline void spdlog::logger::trace(const T &msg) -{ - log(level::trace, msg); -} - -template<typename T> -inline void spdlog::logger::debug(const T &msg) -{ - log(level::debug, msg); -} - -template<typename T> -inline void spdlog::logger::info(const T &msg) -{ - log(level::info, msg); -} - -template<typename T> -inline void spdlog::logger::warn(const T &msg) -{ - log(level::warn, msg); -} - -template<typename T> -inline void spdlog::logger::error(const T &msg) -{ - log(level::err, msg); -} - -template<typename T> -inline void spdlog::logger::critical(const T &msg) -{ - log(level::critical, msg); -} - -#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT - -inline void wbuf_to_utf8buf(const fmt::wmemory_buffer &wbuf, fmt::memory_buffer &target) -{ - int wbuf_size = static_cast<int>(wbuf.size()); - if (wbuf_size == 0) - { - return; - } - - auto result_size = ::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, NULL, 0, NULL, NULL); - - if (result_size > 0) - { - target.resize(result_size); - ::WideCharToMultiByte(CP_UTF8, 0, wbuf.data(), wbuf_size, &target.data()[0], result_size, NULL, NULL); - } - else - { - throw spdlog::spdlog_ex(fmt::format("WideCharToMultiByte failed. Last error: {}", ::GetLastError())); - } -} - -template<typename... Args> -inline void spdlog::logger::log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) -{ - if (!should_log(lvl)) - { - return; - } - - try - { - // format to wmemory_buffer and convert to utf8 - details::log_msg log_msg(&name_, lvl); - fmt::wmemory_buffer wbuf; - fmt::format_to(wbuf, fmt, args...); - wbuf_to_utf8buf(wbuf, log_msg.raw); - sink_it_(log_msg); - } - SPDLOG_CATCH_AND_HANDLE -} - -template<typename... Args> -inline void spdlog::logger::trace(const wchar_t *fmt, const Args &... args) -{ - log(level::trace, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::debug(const wchar_t *fmt, const Args &... args) -{ - log(level::debug, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::info(const wchar_t *fmt, const Args &... args) -{ - log(level::info, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::warn(const wchar_t *fmt, const Args &... args) -{ - log(level::warn, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::error(const wchar_t *fmt, const Args &... args) -{ - log(level::err, fmt, args...); -} - -template<typename... Args> -inline void spdlog::logger::critical(const wchar_t *fmt, const Args &... args) -{ - log(level::critical, fmt, args...); -} - -#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT - -// -// name and level -// -inline const std::string &spdlog::logger::name() const -{ - return name_; -} - -inline void spdlog::logger::set_level(spdlog::level::level_enum log_level) -{ - level_.store(log_level); -} - -inline void spdlog::logger::set_error_handler(spdlog::log_err_handler err_handler) -{ - err_handler_ = std::move(err_handler); -} - -inline spdlog::log_err_handler spdlog::logger::error_handler() -{ - return err_handler_; -} - -inline void spdlog::logger::flush() -{ - try - { - flush_(); - } - SPDLOG_CATCH_AND_HANDLE -} - -inline void spdlog::logger::flush_on(level::level_enum log_level) -{ - flush_level_.store(log_level); -} - -inline spdlog::level::level_enum spdlog::logger::flush_level() const -{ - return static_cast<spdlog::level::level_enum>(flush_level_.load(std::memory_order_relaxed)); -} - -inline bool spdlog::logger::should_flush_(const details::log_msg &msg) -{ - auto flush_level = flush_level_.load(std::memory_order_relaxed); - return (msg.level >= flush_level) && (msg.level != level::off); -} - -inline spdlog::level::level_enum spdlog::logger::level() const -{ - return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); -} - -inline bool spdlog::logger::should_log(spdlog::level::level_enum msg_level) const -{ - return msg_level >= level_.load(std::memory_order_relaxed); -} - -// -// protected virtual called at end of each user log call (if enabled) by the -// line_logger -// -inline void spdlog::logger::sink_it_(details::log_msg &msg) -{ -#if defined(SPDLOG_ENABLE_MESSAGE_COUNTER) - incr_msg_counter_(msg); -#endif - for (auto &sink : sinks_) - { - if (sink->should_log(msg.level)) - { - sink->log(msg); - } - } - - if (should_flush_(msg)) - { - flush(); - } -} - -inline void spdlog::logger::flush_() -{ - for (auto &sink : sinks_) - { - sink->flush(); - } -} - -inline void spdlog::logger::default_err_handler_(const std::string &msg) -{ - auto now = time(nullptr); - if (now - last_err_time_ < 60) - { - return; - } - last_err_time_ = now; - auto tm_time = details::os::localtime(now); - char date_buf[100]; - std::strftime(date_buf, sizeof(date_buf), "%Y-%m-%d %H:%M:%S", &tm_time); - fmt::print(stderr, "[*** LOG ERROR ***] [{}] [{}] {}\n", date_buf, name(), msg); -} - -inline void spdlog::logger::incr_msg_counter_(details::log_msg &msg) -{ - msg.msg_id = msg_counter_.fetch_add(1, std::memory_order_relaxed); -} - -inline const std::vector<spdlog::sink_ptr> &spdlog::logger::sinks() const -{ - return sinks_; -} - -inline std::vector<spdlog::sink_ptr> &spdlog::logger::sinks() -{ - return sinks_; -} - -inline std::shared_ptr<spdlog::logger> spdlog::logger::clone(std::string logger_name) -{ - auto cloned = std::make_shared<spdlog::logger>(std::move(logger_name), sinks_.begin(), sinks_.end()); - cloned->set_level(this->level()); - cloned->flush_on(this->flush_level()); - cloned->set_error_handler(this->error_handler()); - return cloned; -} diff --git a/src/nmodl/ext/spdlog/details/mpmc_blocking_q.h b/src/nmodl/ext/spdlog/details/mpmc_blocking_q.h deleted file mode 100644 index ca789fc660..0000000000 --- a/src/nmodl/ext/spdlog/details/mpmc_blocking_q.h +++ /dev/null @@ -1,121 +0,0 @@ -#pragma once - -// -// Copyright(c) 2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -// multi producer-multi consumer blocking queue. -// enqueue(..) - will block until room found to put the new message. -// enqueue_nowait(..) - will return immediately with false if no room left in -// the queue. -// dequeue_for(..) - will block until the queue is not empty or timeout have -// passed. - -#include "spdlog/details/circular_q.h" - -#include <condition_variable> -#include <mutex> - -namespace spdlog { -namespace details { - -template<typename T> -class mpmc_blocking_queue -{ -public: - using item_type = T; - explicit mpmc_blocking_queue(size_t max_items) - : q_(max_items) - { - } - -#ifndef __MINGW32__ - // try to enqueue and block if no room left - void enqueue(T &&item) - { - { - std::unique_lock<std::mutex> lock(queue_mutex_); - pop_cv_.wait(lock, [this] { return !this->q_.full(); }); - q_.push_back(std::move(item)); - } - push_cv_.notify_one(); - } - - // enqueue immediately. overrun oldest message in the queue if no room left. - void enqueue_nowait(T &&item) - { - { - std::unique_lock<std::mutex> lock(queue_mutex_); - q_.push_back(std::move(item)); - } - push_cv_.notify_one(); - } - - // try to dequeue item. if no item found. wait upto timeout and try again - // Return true, if succeeded dequeue item, false otherwise - bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) - { - { - std::unique_lock<std::mutex> lock(queue_mutex_); - if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) - { - return false; - } - q_.pop_front(popped_item); - } - pop_cv_.notify_one(); - return true; - } - -#else - // apparently mingw deadlocks if the mutex is released before cv.notify_one(), - // so release the mutex at the very end each function. - - // try to enqueue and block if no room left - void enqueue(T &&item) - { - std::unique_lock<std::mutex> lock(queue_mutex_); - pop_cv_.wait(lock, [this] { return !this->q_.full(); }); - q_.push_back(std::move(item)); - push_cv_.notify_one(); - } - - // enqueue immediately. overrun oldest message in the queue if no room left. - void enqueue_nowait(T &&item) - { - std::unique_lock<std::mutex> lock(queue_mutex_); - q_.push_back(std::move(item)); - push_cv_.notify_one(); - } - - // try to dequeue item. if no item found. wait upto timeout and try again - // Return true, if succeeded dequeue item, false otherwise - bool dequeue_for(T &popped_item, std::chrono::milliseconds wait_duration) - { - std::unique_lock<std::mutex> lock(queue_mutex_); - if (!push_cv_.wait_for(lock, wait_duration, [this] { return !this->q_.empty(); })) - { - return false; - } - q_.pop_front(popped_item); - pop_cv_.notify_one(); - return true; - } - -#endif - - size_t overrun_counter() - { - std::unique_lock<std::mutex> lock(queue_mutex_); - return q_.overrun_counter(); - } - -private: - std::mutex queue_mutex_; - std::condition_variable push_cv_; - std::condition_variable pop_cv_; - spdlog::details::circular_q<T> q_; -}; -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/null_mutex.h b/src/nmodl/ext/spdlog/details/null_mutex.h deleted file mode 100644 index 3f495bd98a..0000000000 --- a/src/nmodl/ext/spdlog/details/null_mutex.h +++ /dev/null @@ -1,45 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include <atomic> -// null, no cost dummy "mutex" and dummy "atomic" int - -namespace spdlog { -namespace details { -struct null_mutex -{ - void lock() {} - void unlock() {} - bool try_lock() - { - return true; - } -}; - -struct null_atomic_int -{ - int value; - null_atomic_int() = default; - - explicit null_atomic_int(int val) - : value(val) - { - } - - int load(std::memory_order) const - { - return value; - } - - void store(int val) - { - value = val; - } -}; - -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/os.h b/src/nmodl/ext/spdlog/details/os.h deleted file mode 100644 index 23e011be92..0000000000 --- a/src/nmodl/ext/spdlog/details/os.h +++ /dev/null @@ -1,422 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// -#pragma once - -#include "../common.h" - -#include <algorithm> -#include <chrono> -#include <cstdio> -#include <cstdlib> -#include <cstring> -#include <ctime> -#include <functional> -#include <string> -#include <sys/stat.h> -#include <sys/types.h> -#include <thread> - -#ifdef _WIN32 - -#ifndef NOMINMAX -#define NOMINMAX // prevent windows redefining min/max -#endif - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif -#include <io.h> // _get_osfhandle and _isatty support -#include <process.h> // _get_pid support -#include <windows.h> - -#ifdef __MINGW32__ -#include <share.h> -#endif - -#else // unix - -#include <fcntl.h> -#include <unistd.h> - -#ifdef __linux__ -#include <sys/syscall.h> //Use gettid() syscall under linux to get thread id - -#elif __FreeBSD__ -#include <sys/thr.h> //Use thr_self() syscall under FreeBSD to get thread id -#endif - -#endif // unix - -#ifndef __has_feature // Clang - feature checking macros. -#define __has_feature(x) 0 // Compatibility with non-clang compilers. -#endif - -namespace spdlog { -namespace details { -namespace os { - -inline spdlog::log_clock::time_point now() SPDLOG_NOEXCEPT -{ - -#if defined __linux__ && defined SPDLOG_CLOCK_COARSE - timespec ts; - ::clock_gettime(CLOCK_REALTIME_COARSE, &ts); - return std::chrono::time_point<log_clock, typename log_clock::duration>( - std::chrono::duration_cast<typename log_clock::duration>(std::chrono::seconds(ts.tv_sec) + std::chrono::nanoseconds(ts.tv_nsec))); - -#else - return log_clock::now(); -#endif -} -inline std::tm localtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT -{ - -#ifdef _WIN32 - std::tm tm; - localtime_s(&tm, &time_tt); -#else - std::tm tm; - localtime_r(&time_tt, &tm); -#endif - return tm; -} - -inline std::tm localtime() SPDLOG_NOEXCEPT -{ - std::time_t now_t = time(nullptr); - return localtime(now_t); -} - -inline std::tm gmtime(const std::time_t &time_tt) SPDLOG_NOEXCEPT -{ - -#ifdef _WIN32 - std::tm tm; - gmtime_s(&tm, &time_tt); -#else - std::tm tm; - gmtime_r(&time_tt, &tm); -#endif - return tm; -} - -inline std::tm gmtime() SPDLOG_NOEXCEPT -{ - std::time_t now_t = time(nullptr); - return gmtime(now_t); -} - -// eol definition -#if !defined(SPDLOG_EOL) -#ifdef _WIN32 -#define SPDLOG_EOL "\r\n" -#else -#define SPDLOG_EOL "\n" -#endif -#endif - -SPDLOG_CONSTEXPR static const char *default_eol = SPDLOG_EOL; - -// folder separator -#ifdef _WIN32 -SPDLOG_CONSTEXPR static const char folder_sep = '\\'; -#else -SPDLOG_CONSTEXPR static const char folder_sep = '/'; -#endif - -inline void prevent_child_fd(FILE *f) -{ - -#ifdef _WIN32 -#if !defined(__cplusplus_winrt) - auto file_handle = (HANDLE)_get_osfhandle(_fileno(f)); - if (!::SetHandleInformation(file_handle, HANDLE_FLAG_INHERIT, 0)) - throw spdlog_ex("SetHandleInformation failed", errno); -#endif -#else - auto fd = fileno(f); - if (fcntl(fd, F_SETFD, FD_CLOEXEC) == -1) - { - throw spdlog_ex("fcntl with FD_CLOEXEC failed", errno); - } -#endif -} - -// fopen_s on non windows for writing -inline bool fopen_s(FILE **fp, const filename_t &filename, const filename_t &mode) -{ -#ifdef _WIN32 -#ifdef SPDLOG_WCHAR_FILENAMES - *fp = _wfsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); -#else - *fp = _fsopen((filename.c_str()), mode.c_str(), _SH_DENYNO); -#endif -#else // unix - *fp = fopen((filename.c_str()), mode.c_str()); -#endif - -#ifdef SPDLOG_PREVENT_CHILD_FD - if (*fp != nullptr) - { - prevent_child_fd(*fp); - } -#endif - return *fp == nullptr; -} - -inline int remove(const filename_t &filename) SPDLOG_NOEXCEPT -{ -#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) - return _wremove(filename.c_str()); -#else - return std::remove(filename.c_str()); -#endif -} - -inline int rename(const filename_t &filename1, const filename_t &filename2) SPDLOG_NOEXCEPT -{ -#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) - return _wrename(filename1.c_str(), filename2.c_str()); -#else - return std::rename(filename1.c_str(), filename2.c_str()); -#endif -} - -// Return if file exists -inline bool file_exists(const filename_t &filename) SPDLOG_NOEXCEPT -{ -#ifdef _WIN32 -#ifdef SPDLOG_WCHAR_FILENAMES - auto attribs = GetFileAttributesW(filename.c_str()); -#else - auto attribs = GetFileAttributesA(filename.c_str()); -#endif - return (attribs != INVALID_FILE_ATTRIBUTES && !(attribs & FILE_ATTRIBUTE_DIRECTORY)); -#else // common linux/unix all have the stat system call - struct stat buffer; - return (stat(filename.c_str(), &buffer) == 0); -#endif -} - -// Return file size according to open FILE* object -inline size_t filesize(FILE *f) -{ - if (f == nullptr) - { - throw spdlog_ex("Failed getting file size. fd is null"); - } -#if defined(_WIN32) && !defined(__CYGWIN__) - int fd = _fileno(f); -#if _WIN64 // 64 bits - struct _stat64 st; - if (_fstat64(fd, &st) == 0) - { - return st.st_size; - } - -#else // windows 32 bits - long ret = _filelength(fd); - if (ret >= 0) - { - return static_cast<size_t>(ret); - } -#endif - -#else // unix - int fd = fileno(f); -// 64 bits(but not in osx or cygwin, where fstat64 is deprecated) -#if !defined(__FreeBSD__) && !defined(__APPLE__) && (defined(__x86_64__) || defined(__ppc64__)) && !defined(__CYGWIN__) - struct stat64 st; - if (fstat64(fd, &st) == 0) - { - return static_cast<size_t>(st.st_size); - } -#else // unix 32 bits or cygwin - struct stat st; - - if (fstat(fd, &st) == 0) - { - return static_cast<size_t>(st.st_size); - } -#endif -#endif - throw spdlog_ex("Failed getting file size from fd", errno); -} - -// Return utc offset in minutes or throw spdlog_ex on failure -inline int utc_minutes_offset(const std::tm &tm = details::os::localtime()) -{ - -#ifdef _WIN32 -#if _WIN32_WINNT < _WIN32_WINNT_WS08 - TIME_ZONE_INFORMATION tzinfo; - auto rv = GetTimeZoneInformation(&tzinfo); -#else - DYNAMIC_TIME_ZONE_INFORMATION tzinfo; - auto rv = GetDynamicTimeZoneInformation(&tzinfo); -#endif - if (rv == TIME_ZONE_ID_INVALID) - throw spdlog::spdlog_ex("Failed getting timezone info. ", errno); - - int offset = -tzinfo.Bias; - if (tm.tm_isdst) - { - offset -= tzinfo.DaylightBias; - } - else - { - offset -= tzinfo.StandardBias; - } - return offset; -#else - -#if defined(sun) || defined(__sun) || defined(_AIX) - // 'tm_gmtoff' field is BSD extension and it's missing on SunOS/Solaris - struct helper - { - static long int calculate_gmt_offset(const std::tm &localtm = details::os::localtime(), const std::tm &gmtm = details::os::gmtime()) - { - int local_year = localtm.tm_year + (1900 - 1); - int gmt_year = gmtm.tm_year + (1900 - 1); - - long int days = ( - // difference in day of year - localtm.tm_yday - - gmtm.tm_yday - - // + intervening leap days - + ((local_year >> 2) - (gmt_year >> 2)) - (local_year / 100 - gmt_year / 100) + - ((local_year / 100 >> 2) - (gmt_year / 100 >> 2)) - - // + difference in years * 365 */ - + (long int)(local_year - gmt_year) * 365); - - long int hours = (24 * days) + (localtm.tm_hour - gmtm.tm_hour); - long int mins = (60 * hours) + (localtm.tm_min - gmtm.tm_min); - long int secs = (60 * mins) + (localtm.tm_sec - gmtm.tm_sec); - - return secs; - } - }; - - auto offset_seconds = helper::calculate_gmt_offset(tm); -#else - auto offset_seconds = tm.tm_gmtoff; -#endif - - return static_cast<int>(offset_seconds / 60); -#endif -} - -// Return current thread id as size_t -// It exists because the std::this_thread::get_id() is much slower(especially -// under VS 2013) -inline size_t _thread_id() SPDLOG_NOEXCEPT -{ -#ifdef _WIN32 - return static_cast<size_t>(::GetCurrentThreadId()); -#elif __linux__ -#if defined(__ANDROID__) && defined(__ANDROID_API__) && (__ANDROID_API__ < 21) -#define SYS_gettid __NR_gettid -#endif - return static_cast<size_t>(syscall(SYS_gettid)); -#elif __FreeBSD__ - long tid; - thr_self(&tid); - return static_cast<size_t>(tid); -#elif __APPLE__ - uint64_t tid; - pthread_threadid_np(nullptr, &tid); - return static_cast<size_t>(tid); -#else // Default to standard C++11 (other Unix) - return static_cast<size_t>(std::hash<std::thread::id>()(std::this_thread::get_id())); -#endif -} - -// Return current thread id as size_t (from thread local storage) -inline size_t thread_id() SPDLOG_NOEXCEPT -{ -#if defined(SPDLOG_DISABLE_TID_CACHING) || (defined(_MSC_VER) && (_MSC_VER < 1900)) || defined(__cplusplus_winrt) || \ - (defined(__clang__) && !__has_feature(cxx_thread_local)) - return _thread_id(); -#else // cache thread id in tls - static thread_local const size_t tid = _thread_id(); - return tid; -#endif -} - -// This is avoid msvc issue in sleep_for that happens if the clock changes. -// See https://github.com/gabime/spdlog/issues/609 -inline void sleep_for_millis(int milliseconds) SPDLOG_NOEXCEPT -{ -#if defined(_WIN32) - ::Sleep(milliseconds); -#else - std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); -#endif -} - -// wchar support for windows file names (SPDLOG_WCHAR_FILENAMES must be defined) -#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES) -#define SPDLOG_FILENAME_T(s) L##s -inline std::string filename_to_str(const filename_t &filename) -{ - std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> c; - return c.to_bytes(filename); -} -#else -#define SPDLOG_FILENAME_T(s) s -inline std::string filename_to_str(const filename_t &filename) -{ - return filename; -} -#endif - -inline int pid() -{ - -#ifdef _WIN32 - return static_cast<int>(::GetCurrentProcessId()); -#else - return static_cast<int>(::getpid()); -#endif -} - -// Determine if the terminal supports colors -// Source: https://github.com/agauniyal/rang/ -inline bool is_color_terminal() SPDLOG_NOEXCEPT -{ -#ifdef _WIN32 - return true; -#else - static constexpr const char *Terms[] = { - "ansi", "color", "console", "cygwin", "gnome", "konsole", "kterm", "linux", "msys", "putty", "rxvt", "screen", "vt100", "xterm"}; - - const char *env_p = std::getenv("TERM"); - if (env_p == nullptr) - { - return false; - } - - static const bool result = - std::any_of(std::begin(Terms), std::end(Terms), [&](const char *term) { return std::strstr(env_p, term) != nullptr; }); - return result; -#endif -} - -// Detrmine if the terminal attached -// Source: https://github.com/agauniyal/rang/ -inline bool in_terminal(FILE *file) SPDLOG_NOEXCEPT -{ - -#ifdef _WIN32 - return _isatty(_fileno(file)) != 0; -#else - return isatty(fileno(file)) != 0; -#endif -} -} // namespace os -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/pattern_formatter.h b/src/nmodl/ext/spdlog/details/pattern_formatter.h deleted file mode 100644 index f5a8406e99..0000000000 --- a/src/nmodl/ext/spdlog/details/pattern_formatter.h +++ /dev/null @@ -1,777 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include "spdlog/details/fmt_helper.h" -#include "spdlog/details/log_msg.h" -#include "spdlog/details/os.h" -#include "spdlog/fmt/fmt.h" -#include "spdlog/formatter.h" - -#include <array> -#include <chrono> -#include <ctime> -#include <memory> -#include <mutex> -#include <string> -#include <thread> -#include <utility> -#include <vector> - -namespace spdlog { -namespace details { - -class flag_formatter -{ -public: - virtual ~flag_formatter() = default; - virtual void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) = 0; -}; - -/////////////////////////////////////////////////////////////////////// -// name & level pattern appenders -/////////////////////////////////////////////////////////////////////// -class name_formatter : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::append_str(*msg.logger_name, dest); - } -}; - -// log level appender -class level_formatter : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(level::to_c_str(msg.level), dest); - } -}; - -// short log level appender -class short_level_formatter : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(level::to_short_c_str(msg.level), dest); - } -}; - -/////////////////////////////////////////////////////////////////////// -// Date time pattern appenders -/////////////////////////////////////////////////////////////////////// - -static const char *ampm(const tm &t) -{ - return t.tm_hour >= 12 ? "PM" : "AM"; -} - -static int to12h(const tm &t) -{ - return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour; -} - -// Abbreviated weekday name -static const char *days[]{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; -class a_formatter : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(days[tm_time.tm_wday], dest); - } -}; - -// Full weekday name -static const char *full_days[]{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}; -class A_formatter : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(full_days[tm_time.tm_wday], dest); - } -}; - -// Abbreviated month -static const char *months[]{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}; -class b_formatter : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(months[tm_time.tm_mon], dest); - } -}; - -// Full month name -static const char *full_months[]{ - "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}; -class B_formatter : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(full_months[tm_time.tm_mon], dest); - } -}; - -// Date and time representation (Thu Aug 23 15:35:46 2014) -class c_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - // fmt::format_to(dest, "{} {} {} ", days[tm_time.tm_wday], - // months[tm_time.tm_mon], tm_time.tm_mday); - // date - fmt_helper::append_c_str(days[tm_time.tm_wday], dest); - dest.push_back(' '); - fmt_helper::append_c_str(months[tm_time.tm_mon], dest); - dest.push_back(' '); - fmt_helper::append_int(tm_time.tm_mday, dest); - dest.push_back(' '); - // time - - fmt_helper::pad2(tm_time.tm_hour, dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_min, dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_sec, dest); - dest.push_back(' '); - fmt_helper::append_int(tm_time.tm_year + 1900, dest); - } -}; - -// year - 2 digit -class C_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_year % 100, dest); - } -}; - -// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01 -class D_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_mon + 1, dest); - dest.push_back('/'); - fmt_helper::pad2(tm_time.tm_mday, dest); - dest.push_back('/'); - fmt_helper::pad2(tm_time.tm_year % 100, dest); - } -}; - -// year - 4 digit -class Y_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::append_int(tm_time.tm_year + 1900, dest); - } -}; - -// month 1-12 -class m_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_mon + 1, dest); - } -}; - -// day of month 1-31 -class d_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_mday, dest); - } -}; - -// hours in 24 format 0-23 -class H_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_hour, dest); - } -}; - -// hours in 12 format 1-12 -class I_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(to12h(tm_time), dest); - } -}; - -// minutes 0-59 -class M_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_min, dest); - } -}; - -// seconds 0-59 -class S_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_sec, dest); - } -}; - -// milliseconds -class e_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - auto millis = fmt_helper::time_fraction<std::chrono::milliseconds>(msg.time); - fmt_helper::pad3(static_cast<int>(millis.count()), dest); - } -}; - -// microseconds -class f_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - auto micros = fmt_helper::time_fraction<std::chrono::microseconds>(msg.time); - fmt_helper::pad6(static_cast<size_t>(micros.count()), dest); - } -}; - -// nanoseconds -class F_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - auto ns = fmt_helper::time_fraction<std::chrono::nanoseconds>(msg.time); - fmt::format_to(dest, "{:09}", ns.count()); - } -}; - -// seconds since epoch -class E_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - auto duration = msg.time.time_since_epoch(); - auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration).count(); - fmt_helper::append_int(seconds, dest); - } -}; - -// AM/PM -class p_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::append_c_str(ampm(tm_time), dest); - } -}; - -// 12 hour clock 02:55:02 pm -class r_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(to12h(tm_time), dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_min, dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_sec, dest); - dest.push_back(' '); - fmt_helper::append_c_str(ampm(tm_time), dest); - } -}; - -// 24-hour HH:MM time, equivalent to %H:%M -class R_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - fmt_helper::pad2(tm_time.tm_hour, dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_min, dest); - } -}; - -// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S -class T_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - // fmt::format_to(dest, "{:02}:{:02}:{:02}", tm_time.tm_hour, - // tm_time.tm_min, tm_time.tm_sec); - fmt_helper::pad2(tm_time.tm_hour, dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_min, dest); - dest.push_back(':'); - fmt_helper::pad2(tm_time.tm_sec, dest); - } -}; - -// ISO 8601 offset from UTC in timezone (+-HH:MM) -class z_formatter final : public flag_formatter -{ -public: - const std::chrono::seconds cache_refresh = std::chrono::seconds(5); - - z_formatter() = default; - z_formatter(const z_formatter &) = delete; - z_formatter &operator=(const z_formatter &) = delete; - - void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override - { -#ifdef _WIN32 - int total_minutes = get_cached_offset(msg, tm_time); -#else - // No need to chache under gcc, - // it is very fast (already stored in tm.tm_gmtoff) - (void)(msg); - int total_minutes = os::utc_minutes_offset(tm_time); -#endif - bool is_negative = total_minutes < 0; - if (is_negative) - { - total_minutes = -total_minutes; - dest.push_back('-'); - } - else - { - dest.push_back('+'); - } - - fmt_helper::pad2(total_minutes / 60, dest); // hours - dest.push_back(':'); - fmt_helper::pad2(total_minutes % 60, dest); // minutes - } - -private: - log_clock::time_point last_update_{std::chrono::seconds(0)}; -#ifdef _WIN32 - int offset_minutes_{0}; - - int get_cached_offset(const log_msg &msg, const std::tm &tm_time) - { - if (msg.time - last_update_ >= cache_refresh) - { - offset_minutes_ = os::utc_minutes_offset(tm_time); - last_update_ = msg.time; - } - return offset_minutes_; - } -#endif -}; - -// Thread id -class t_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::pad6(msg.thread_id, dest); - } -}; - -// Current pid -class pid_formatter final : public flag_formatter -{ - void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::append_int(details::os::pid(), dest); - } -}; - -// message counter formatter -class i_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::pad6(msg.msg_id, dest); - } -}; - -class v_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::append_buf(msg.raw, dest); - } -}; - -class ch_formatter final : public flag_formatter -{ -public: - explicit ch_formatter(char ch) - : ch_(ch) - { - } - void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override - { - dest.push_back(ch_); - } - -private: - char ch_; -}; - -// aggregate user chars to display as is -class aggregate_formatter final : public flag_formatter -{ -public: - aggregate_formatter() = default; - - void add_ch(char ch) - { - str_ += ch; - } - void format(const details::log_msg &, const std::tm &, fmt::memory_buffer &dest) override - { - fmt_helper::append_str(str_, dest); - } - -private: - std::string str_; -}; - -// mark the color range. expect it to be in the form of "%^colored text%$" -class color_start_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - msg.color_range_start = dest.size(); - } -}; -class color_stop_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &, fmt::memory_buffer &dest) override - { - msg.color_range_end = dest.size(); - } -}; - -// Full info formatter -// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v -class full_formatter final : public flag_formatter -{ - void format(const details::log_msg &msg, const std::tm &tm_time, fmt::memory_buffer &dest) override - { - using std::chrono::duration_cast; - using std::chrono::milliseconds; - using std::chrono::seconds; - -#ifndef SPDLOG_NO_DATETIME - - // cache the date/time part for the next second. - auto duration = msg.time.time_since_epoch(); - auto secs = duration_cast<seconds>(duration); - - if (cache_timestamp_ != secs || cached_datetime_.size() == 0) - { - cached_datetime_.clear(); - cached_datetime_.push_back('['); - fmt_helper::append_int(tm_time.tm_year + 1900, cached_datetime_); - cached_datetime_.push_back('-'); - - fmt_helper::pad2(tm_time.tm_mon + 1, cached_datetime_); - cached_datetime_.push_back('-'); - - fmt_helper::pad2(tm_time.tm_mday, cached_datetime_); - cached_datetime_.push_back(' '); - - fmt_helper::pad2(tm_time.tm_hour, cached_datetime_); - cached_datetime_.push_back(':'); - - fmt_helper::pad2(tm_time.tm_min, cached_datetime_); - cached_datetime_.push_back(':'); - - fmt_helper::pad2(tm_time.tm_sec, cached_datetime_); - cached_datetime_.push_back('.'); - - cache_timestamp_ = secs; - } - fmt_helper::append_buf(cached_datetime_, dest); - - auto millis = fmt_helper::time_fraction<milliseconds>(msg.time); - fmt_helper::pad3(static_cast<int>(millis.count()), dest); - dest.push_back(']'); - dest.push_back(' '); - -#else // no datetime needed - (void)tm_time; -#endif - -#ifndef SPDLOG_NO_NAME - if (!msg.logger_name->empty()) - { - dest.push_back('['); - fmt_helper::append_str(*msg.logger_name, dest); - dest.push_back(']'); - dest.push_back(' '); - } -#endif - - dest.push_back('['); - // wrap the level name with color - msg.color_range_start = dest.size(); - fmt_helper::append_c_str(level::to_c_str(msg.level), dest); - msg.color_range_end = dest.size(); - dest.push_back(']'); - dest.push_back(' '); - fmt_helper::append_buf(msg.raw, dest); - } - -private: - std::chrono::seconds cache_timestamp_{0}; - fmt::basic_memory_buffer<char, 128> cached_datetime_; -}; - -} // namespace details - -class pattern_formatter final : public formatter -{ -public: - explicit pattern_formatter( - std::string pattern, pattern_time_type time_type = pattern_time_type::local, std::string eol = spdlog::details::os::default_eol) - : pattern_(std::move(pattern)) - , eol_(std::move(eol)) - , pattern_time_type_(time_type) - , last_log_secs_(0) - { - std::memset(&cached_tm_, 0, sizeof(cached_tm_)); - compile_pattern_(pattern_); - } - - pattern_formatter(const pattern_formatter &other) = delete; - pattern_formatter &operator=(const pattern_formatter &other) = delete; - - std::unique_ptr<formatter> clone() const override - { - return details::make_unique<pattern_formatter>(pattern_, pattern_time_type_, eol_); - } - - void format(const details::log_msg &msg, fmt::memory_buffer &dest) override - { -#ifndef SPDLOG_NO_DATETIME - auto secs = std::chrono::duration_cast<std::chrono::seconds>(msg.time.time_since_epoch()); - if (secs != last_log_secs_) - { - cached_tm_ = get_time_(msg); - last_log_secs_ = secs; - } -#endif - for (auto &f : formatters_) - { - f->format(msg, cached_tm_, dest); - } - // write eol - details::fmt_helper::append_c_str(eol_.c_str(), dest); - } - -private: - std::string pattern_; - std::string eol_; - pattern_time_type pattern_time_type_; - std::tm cached_tm_; - std::chrono::seconds last_log_secs_; - - std::vector<std::unique_ptr<details::flag_formatter>> formatters_; - - std::tm get_time_(const details::log_msg &msg) - { - if (pattern_time_type_ == pattern_time_type::local) - { - return details::os::localtime(log_clock::to_time_t(msg.time)); - } - return details::os::gmtime(log_clock::to_time_t(msg.time)); - } - - void handle_flag_(char flag) - { - switch (flag) - { - // logger name - case 'n': - formatters_.push_back(details::make_unique<details::name_formatter>()); - break; - - case 'l': - formatters_.push_back(details::make_unique<details::level_formatter>()); - break; - - case 'L': - formatters_.push_back(details::make_unique<details::short_level_formatter>()); - break; - - case ('t'): - formatters_.push_back(details::make_unique<details::t_formatter>()); - break; - - case ('v'): - formatters_.push_back(details::make_unique<details::v_formatter>()); - break; - - case ('a'): - formatters_.push_back(details::make_unique<details::a_formatter>()); - break; - - case ('A'): - formatters_.push_back(details::make_unique<details::A_formatter>()); - break; - - case ('b'): - case ('h'): - formatters_.push_back(details::make_unique<details::b_formatter>()); - break; - - case ('B'): - formatters_.push_back(details::make_unique<details::B_formatter>()); - break; - case ('c'): - formatters_.push_back(details::make_unique<details::c_formatter>()); - break; - - case ('C'): - formatters_.push_back(details::make_unique<details::C_formatter>()); - break; - - case ('Y'): - formatters_.push_back(details::make_unique<details::Y_formatter>()); - break; - - case ('D'): - case ('x'): - formatters_.push_back(details::make_unique<details::D_formatter>()); - break; - - case ('m'): - formatters_.push_back(details::make_unique<details::m_formatter>()); - break; - - case ('d'): - formatters_.push_back(details::make_unique<details::d_formatter>()); - break; - - case ('H'): - formatters_.push_back(details::make_unique<details::H_formatter>()); - break; - - case ('I'): - formatters_.push_back(details::make_unique<details::I_formatter>()); - break; - - case ('M'): - formatters_.push_back(details::make_unique<details::M_formatter>()); - break; - - case ('S'): - formatters_.push_back(details::make_unique<details::S_formatter>()); - break; - - case ('e'): - formatters_.push_back(details::make_unique<details::e_formatter>()); - break; - - case ('f'): - formatters_.push_back(details::make_unique<details::f_formatter>()); - break; - case ('F'): - formatters_.push_back(details::make_unique<details::F_formatter>()); - break; - - case ('E'): - formatters_.push_back(details::make_unique<details::E_formatter>()); - break; - - case ('p'): - formatters_.push_back(details::make_unique<details::p_formatter>()); - break; - - case ('r'): - formatters_.push_back(details::make_unique<details::r_formatter>()); - break; - - case ('R'): - formatters_.push_back(details::make_unique<details::R_formatter>()); - break; - - case ('T'): - case ('X'): - formatters_.push_back(details::make_unique<details::T_formatter>()); - break; - - case ('z'): - formatters_.push_back(details::make_unique<details::z_formatter>()); - break; - - case ('+'): - formatters_.push_back(details::make_unique<details::full_formatter>()); - break; - - case ('P'): - formatters_.push_back(details::make_unique<details::pid_formatter>()); - break; -#ifdef SPDLOG_ENABLE_MESSAGE_COUNTER - case ('i'): - formatters_.push_back(details::make_unique<details::i_formatter>()); - break; -#endif - case ('^'): - formatters_.push_back(details::make_unique<details::color_start_formatter>()); - break; - - case ('$'): - formatters_.push_back(details::make_unique<details::color_stop_formatter>()); - break; - - default: // Unknown flag appears as is - formatters_.push_back(details::make_unique<details::ch_formatter>('%')); - formatters_.push_back(details::make_unique<details::ch_formatter>(flag)); - break; - } - } - - void compile_pattern_(const std::string &pattern) - { - auto end = pattern.end(); - std::unique_ptr<details::aggregate_formatter> user_chars; - formatters_.clear(); - for (auto it = pattern.begin(); it != end; ++it) - { - if (*it == '%') - { - if (user_chars) // append user chars found so far - { - formatters_.push_back(std::move(user_chars)); - } - if (++it != end) - { - handle_flag_(*it); - } - else - { - break; - } - } - else // chars not following the % sign should be displayed as is - { - if (!user_chars) - { - user_chars = details::make_unique<details::aggregate_formatter>(); - } - user_chars->add_ch(*it); - } - } - if (user_chars) // append raw chars found so far - { - formatters_.push_back(std::move(user_chars)); - } - } -}; -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/periodic_worker.h b/src/nmodl/ext/spdlog/details/periodic_worker.h deleted file mode 100644 index 57e5fa7717..0000000000 --- a/src/nmodl/ext/spdlog/details/periodic_worker.h +++ /dev/null @@ -1,71 +0,0 @@ - -// -// Copyright(c) 2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// periodic worker thread - periodically executes the given callback function. -// -// RAII over the owned thread: -// creates the thread on construction. -// stops and joins the thread on destruction. - -#include <chrono> -#include <condition_variable> -#include <functional> -#include <mutex> -#include <thread> -namespace spdlog { -namespace details { - -class periodic_worker -{ -public: - periodic_worker(const std::function<void()> &callback_fun, std::chrono::seconds interval) - { - active_ = (interval > std::chrono::seconds::zero()); - if (!active_) - { - return; - } - - worker_thread_ = std::thread([this, callback_fun, interval]() { - for (;;) - { - std::unique_lock<std::mutex> lock(this->mutex_); - if (this->cv_.wait_for(lock, interval, [this] { return !this->active_; })) - { - return; // active_ == false, so exit this thread - } - callback_fun(); - } - }); - } - - periodic_worker(const periodic_worker &) = delete; - periodic_worker &operator=(const periodic_worker &) = delete; - - // stop the worker thread and join it - ~periodic_worker() - { - if (worker_thread_.joinable()) - { - { - std::lock_guard<std::mutex> lock(mutex_); - active_ = false; - } - cv_.notify_one(); - worker_thread_.join(); - } - } - -private: - bool active_; - std::thread worker_thread_; - std::mutex mutex_; - std::condition_variable cv_; -}; -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/registry.h b/src/nmodl/ext/spdlog/details/registry.h deleted file mode 100644 index 439395f704..0000000000 --- a/src/nmodl/ext/spdlog/details/registry.h +++ /dev/null @@ -1,275 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// Loggers registy of unique name->logger pointer -// An attempt to create a logger with an already existing name will be ignored -// If user requests a non existing logger, nullptr will be returned -// This class is thread safe - -#include "spdlog/common.h" -#include "spdlog/details/periodic_worker.h" -#include "spdlog/logger.h" - -#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER -// support for the default stdout color logger -#ifdef _WIN32 -#include "spdlog/sinks/wincolor_sink.h" -#else -#include "spdlog/sinks/ansicolor_sink.h" -#endif -#endif // SPDLOG_DISABLE_DEFAULT_LOGGER - -#include <chrono> -#include <functional> -#include <memory> -#include <string> -#include <unordered_map> - -namespace spdlog { -namespace details { -class thread_pool; - -class registry -{ -public: - registry(const registry &) = delete; - registry &operator=(const registry &) = delete; - - void register_logger(std::shared_ptr<logger> new_logger) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - auto logger_name = new_logger->name(); - throw_if_exists_(logger_name); - loggers_[logger_name] = std::move(new_logger); - } - - void register_and_init(std::shared_ptr<logger> new_logger) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - auto logger_name = new_logger->name(); - throw_if_exists_(logger_name); - - // set the global formatter pattern - new_logger->set_formatter(formatter_->clone()); - - if (err_handler_) - { - new_logger->set_error_handler(err_handler_); - } - - new_logger->set_level(level_); - new_logger->flush_on(flush_level_); - - // add to registry - loggers_[logger_name] = std::move(new_logger); - } - - std::shared_ptr<logger> get(const std::string &logger_name) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - auto found = loggers_.find(logger_name); - return found == loggers_.end() ? nullptr : found->second; - } - - std::shared_ptr<logger> default_logger() - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - return default_logger_; - } - - // Return raw ptr to the default logger. - // To be used directly by the spdlog default api (e.g. spdlog::info) - // This make the default API faster, but cannot be used concurrently with set_default_logger(). - // e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. - logger *get_default_raw() - { - return default_logger_.get(); - } - - // set default logger. - // default logger is stored in default_logger_ (for faster retrieval) and in the loggers_ map. - void set_default_logger(std::shared_ptr<logger> new_default_logger) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - // remove previous default logger from the map - if (default_logger_ != nullptr) - { - loggers_.erase(default_logger_->name()); - } - if (new_default_logger != nullptr) - { - loggers_[new_default_logger->name()] = new_default_logger; - } - default_logger_ = std::move(new_default_logger); - } - - void set_tp(std::shared_ptr<thread_pool> tp) - { - std::lock_guard<std::recursive_mutex> lock(tp_mutex_); - tp_ = std::move(tp); - } - - std::shared_ptr<thread_pool> get_tp() - { - std::lock_guard<std::recursive_mutex> lock(tp_mutex_); - return tp_; - } - - // Set global formatter. Each sink in each logger will get a clone of this object - void set_formatter(std::unique_ptr<formatter> formatter) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - formatter_ = std::move(formatter); - for (auto &l : loggers_) - { - l.second->set_formatter(formatter_->clone()); - } - } - - void set_level(level::level_enum log_level) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - for (auto &l : loggers_) - { - l.second->set_level(log_level); - } - level_ = log_level; - } - - void flush_on(level::level_enum log_level) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - for (auto &l : loggers_) - { - l.second->flush_on(log_level); - } - flush_level_ = log_level; - } - - void flush_every(std::chrono::seconds interval) - { - std::lock_guard<std::mutex> lock(flusher_mutex_); - std::function<void()> clbk = std::bind(®istry::flush_all, this); - periodic_flusher_ = details::make_unique<periodic_worker>(clbk, interval); - } - - void set_error_handler(log_err_handler handler) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - for (auto &l : loggers_) - { - l.second->set_error_handler(handler); - } - err_handler_ = handler; - } - - void apply_all(const std::function<void(const std::shared_ptr<logger>)> &fun) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - for (auto &l : loggers_) - { - fun(l.second); - } - } - - void flush_all() - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - for (auto &l : loggers_) - { - l.second->flush(); - } - } - - void drop(const std::string &logger_name) - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - loggers_.erase(logger_name); - if (default_logger_ && default_logger_->name() == logger_name) - { - default_logger_.reset(); - } - } - - void drop_all() - { - std::lock_guard<std::mutex> lock(logger_map_mutex_); - loggers_.clear(); - default_logger_.reset(); - } - - // clean all resources and threads started by the registry - void shutdown() - { - { - std::lock_guard<std::mutex> lock(flusher_mutex_); - periodic_flusher_.reset(); - } - - drop_all(); - - { - std::lock_guard<std::recursive_mutex> lock(tp_mutex_); - tp_.reset(); - } - } - - std::recursive_mutex &tp_mutex() - { - return tp_mutex_; - } - - static registry &instance() - { - static registry s_instance; - return s_instance; - } - -private: - registry() - : formatter_(new pattern_formatter("%+")) - { - -#ifndef SPDLOG_DISABLE_DEFAULT_LOGGER - // create default logger (ansicolor_stdout_sink_mt or wincolor_stdout_sink_mt in windows). -#ifdef _WIN32 - auto color_sink = std::make_shared<sinks::wincolor_stdout_sink_mt>(); -#else - auto color_sink = std::make_shared<sinks::ansicolor_stdout_sink_mt>(); -#endif - - const char *default_logger_name = ""; - default_logger_ = std::make_shared<spdlog::logger>(default_logger_name, std::move(color_sink)); - loggers_[default_logger_name] = default_logger_; - -#endif // SPDLOG_DISABLE_DEFAULT_LOGGER - } - - ~registry() = default; - - void throw_if_exists_(const std::string &logger_name) - { - if (loggers_.find(logger_name) != loggers_.end()) - { - throw spdlog_ex("logger with name '" + logger_name + "' already exists"); - } - } - - std::mutex logger_map_mutex_, flusher_mutex_; - std::recursive_mutex tp_mutex_; - std::unordered_map<std::string, std::shared_ptr<logger>> loggers_; - std::unique_ptr<formatter> formatter_; - level::level_enum level_ = level::info; - level::level_enum flush_level_ = level::off; - log_err_handler err_handler_; - std::shared_ptr<thread_pool> tp_; - std::unique_ptr<periodic_worker> periodic_flusher_; - std::shared_ptr<logger> default_logger_; -}; - -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/details/thread_pool.h b/src/nmodl/ext/spdlog/details/thread_pool.h deleted file mode 100644 index 0c716c3b22..0000000000 --- a/src/nmodl/ext/spdlog/details/thread_pool.h +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once - -#include "spdlog/details/log_msg.h" -#include "spdlog/details/mpmc_blocking_q.h" -#include "spdlog/details/os.h" - -#include <chrono> -#include <memory> -#include <thread> -#include <vector> - -namespace spdlog { -namespace details { - -using async_logger_ptr = std::shared_ptr<spdlog::async_logger>; - -enum class async_msg_type -{ - log, - flush, - terminate -}; - -// Async msg to move to/from the queue -// Movable only. should never be copied -struct async_msg -{ - async_msg_type msg_type; - level::level_enum level; - log_clock::time_point time; - size_t thread_id; - fmt::basic_memory_buffer<char, 176> raw; - - size_t msg_id; - async_logger_ptr worker_ptr; - - async_msg() = default; - ~async_msg() = default; - - // should only be moved in or out of the queue.. - async_msg(const async_msg &) = delete; - -// support for vs2013 move -#if defined(_MSC_VER) && _MSC_VER <= 1800 - async_msg(async_msg &&other) SPDLOG_NOEXCEPT : msg_type(other.msg_type), - level(other.level), - time(other.time), - thread_id(other.thread_id), - raw(move(other.raw)), - msg_id(other.msg_id), - worker_ptr(std::move(other.worker_ptr)) - { - } - - async_msg &operator=(async_msg &&other) SPDLOG_NOEXCEPT - { - msg_type = other.msg_type; - level = other.level; - time = other.time; - thread_id = other.thread_id; - raw = std::move(other.raw); - msg_id = other.msg_id; - worker_ptr = std::move(other.worker_ptr); - return *this; - } -#else // (_MSC_VER) && _MSC_VER <= 1800 - async_msg(async_msg &&) = default; - async_msg &operator=(async_msg &&) = default; -#endif - - // construct from log_msg with given type - async_msg(async_logger_ptr &&worker, async_msg_type the_type, details::log_msg &&m) - : msg_type(the_type) - , level(m.level) - , time(m.time) - , thread_id(m.thread_id) - , msg_id(m.msg_id) - , worker_ptr(std::move(worker)) - { - fmt_helper::append_buf(m.raw, raw); - } - - async_msg(async_logger_ptr &&worker, async_msg_type the_type) - : async_msg(std::move(worker), the_type, details::log_msg()) - { - } - - explicit async_msg(async_msg_type the_type) - : async_msg(nullptr, the_type, details::log_msg()) - { - } - - // copy into log_msg - void to_log_msg(log_msg &msg) - { - msg.logger_name = &worker_ptr->name(); - msg.level = level; - msg.time = time; - msg.thread_id = thread_id; - fmt_helper::append_buf(raw, msg.raw); - msg.msg_id = msg_id; - msg.color_range_start = 0; - msg.color_range_end = 0; - } -}; - -class thread_pool -{ -public: - using item_type = async_msg; - using q_type = details::mpmc_blocking_queue<item_type>; - - thread_pool(size_t q_max_items, size_t threads_n) - : q_(q_max_items) - { - // std::cout << "thread_pool() q_size_bytes: " << q_size_bytes << - // "\tthreads_n: " << threads_n << std::endl; - if (threads_n == 0 || threads_n > 1000) - { - throw spdlog_ex("spdlog::thread_pool(): invalid threads_n param (valid " - "range is 1-1000)"); - } - for (size_t i = 0; i < threads_n; i++) - { - threads_.emplace_back(&thread_pool::worker_loop_, this); - } - } - - // message all threads to terminate gracefully join them - ~thread_pool() - { - try - { - for (size_t i = 0; i < threads_.size(); i++) - { - post_async_msg_(async_msg(async_msg_type::terminate), async_overflow_policy::block); - } - - for (auto &t : threads_) - { - t.join(); - } - } - catch (...) - { - } - } - - thread_pool(const thread_pool &) = delete; - thread_pool &operator=(thread_pool &&) = delete; - - void post_log(async_logger_ptr &&worker_ptr, details::log_msg &&msg, async_overflow_policy overflow_policy) - { - async_msg async_m(std::move(worker_ptr), async_msg_type::log, std::move(msg)); - post_async_msg_(std::move(async_m), overflow_policy); - } - - void post_flush(async_logger_ptr &&worker_ptr, async_overflow_policy overflow_policy) - { - post_async_msg_(async_msg(std::move(worker_ptr), async_msg_type::flush), overflow_policy); - } - - size_t overrun_counter() - { - return q_.overrun_counter(); - } - -private: - q_type q_; - - std::vector<std::thread> threads_; - - void post_async_msg_(async_msg &&new_msg, async_overflow_policy overflow_policy) - { - if (overflow_policy == async_overflow_policy::block) - { - q_.enqueue(std::move(new_msg)); - } - else - { - q_.enqueue_nowait(std::move(new_msg)); - } - } - - void worker_loop_() - { - while (process_next_msg_()) {}; - } - - // process next message in the queue - // return true if this thread should still be active (while no terminate msg - // was received) - bool process_next_msg_() - { - async_msg incoming_async_msg; - bool dequeued = q_.dequeue_for(incoming_async_msg, std::chrono::seconds(10)); - if (!dequeued) - { - return true; - } - - switch (incoming_async_msg.msg_type) - { - case async_msg_type::log: - { - log_msg msg; - incoming_async_msg.to_log_msg(msg); - incoming_async_msg.worker_ptr->backend_log_(msg); - return true; - } - case async_msg_type::flush: - { - incoming_async_msg.worker_ptr->backend_flush_(); - return true; - } - - case async_msg_type::terminate: - { - return false; - } - } - assert(false && "Unexpected async_msg_type"); - return true; - } -}; - -} // namespace details -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/fmt/bin_to_hex.h b/src/nmodl/ext/spdlog/fmt/bin_to_hex.h deleted file mode 100644 index 3523380204..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bin_to_hex.h +++ /dev/null @@ -1,172 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// -// Support for logging binary data as hex -// format flags: -// {:X} - print in uppercase. -// {:s} - don't separate each byte with space. -// {:p} - don't print the position on each line start. -// {:n} - don't split the output to lines. - -// -// Examples: -// -// std::vector<char> v(200, 0x0b); -// logger->info("Some buffer {}", spdlog::to_hex(v)); -// char buf[128]; -// logger->info("Some buffer {:X}", spdlog::to_hex(std::begin(buf), std::end(buf))); - -namespace spdlog { -namespace details { - -template<typename It> -class bytes_range -{ -public: - bytes_range(It range_begin, It range_end) - : begin_(range_begin) - , end_(range_end) - { - } - - It begin() const - { - return begin_; - } - It end() const - { - return end_; - } - -private: - It begin_, end_; -}; -} // namespace details - -// create a bytes_range that wraps the given container -template<typename Container> -inline details::bytes_range<typename Container::const_iterator> to_hex(const Container &container) -{ - static_assert(sizeof(typename Container::value_type) == 1, "sizeof(Container::value_type) != 1"); - using Iter = typename Container::const_iterator; - return details::bytes_range<Iter>(std::begin(container), std::end(container)); -} - -// create bytes_range from ranges -template<typename It> -inline details::bytes_range<It> to_hex(const It range_begin, const It range_end) -{ - return details::bytes_range<It>(range_begin, range_end); -} - -} // namespace spdlog - -namespace fmt { - -template<typename T> -struct formatter<spdlog::details::bytes_range<T>> -{ - const std::size_t line_size = 100; - const char delimiter = ' '; - - bool put_newlines = true; - bool put_delimiters = true; - bool use_uppercase = false; - bool put_positions = true; // position on start of each line - - // parse the format string flags - template<typename ParseContext> - auto parse(ParseContext &ctx) -> decltype(ctx.begin()) - { - auto it = ctx.begin(); - while (*it && *it != '}') - { - switch (*it) - { - case 'X': - use_uppercase = true; - break; - case 's': - put_delimiters = false; - break; - case 'p': - put_positions = false; - break; - case 'n': - put_newlines = false; - break; - } - - ++it; - } - return it; - } - - // format the given bytes range as hex - template<typename FormatContext, typename Container> - auto format(const spdlog::details::bytes_range<Container> &the_range, FormatContext &ctx) -> decltype(ctx.out()) - { - SPDLOG_CONSTEXPR const char *hex_upper = "0123456789ABCDEF"; - SPDLOG_CONSTEXPR const char *hex_lower = "0123456789abcdef"; - const char *hex_chars = use_uppercase ? hex_upper : hex_lower; - - std::size_t pos = 0; - std::size_t column = line_size; - auto inserter = ctx.begin(); - - for (auto &item : the_range) - { - auto ch = static_cast<unsigned char>(item); - pos++; - - if (put_newlines && column >= line_size) - { - column = put_newline(inserter, pos); - - // put first byte without delimiter in front of it - *inserter++ = hex_chars[(ch >> 4) & 0x0f]; - *inserter++ = hex_chars[ch & 0x0f]; - column += 2; - continue; - } - - if (put_delimiters) - { - *inserter++ = delimiter; - ++column; - } - - *inserter++ = hex_chars[(ch >> 4) & 0x0f]; - *inserter++ = hex_chars[ch & 0x0f]; - column += 2; - } - return inserter; - } - - // put newline(and position header) - // return the next column - template<typename It> - std::size_t put_newline(It inserter, std::size_t pos) - { -#ifdef _WIN32 - *inserter++ = '\r'; -#endif - *inserter++ = '\n'; - - if (put_positions) - { - fmt::format_to(inserter, "{:<04X}: ", pos - 1); - return 7; - } - else - { - return 1; - } - } -}; -} // namespace fmt diff --git a/src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst b/src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst deleted file mode 100644 index eb6be6503e..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/LICENSE.rst +++ /dev/null @@ -1,23 +0,0 @@ -Copyright (c) 2012 - 2016, Victor Zverovich - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/nmodl/ext/spdlog/fmt/bundled/colors.h b/src/nmodl/ext/spdlog/fmt/bundled/colors.h deleted file mode 100644 index 003a6679ee..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/colors.h +++ /dev/null @@ -1,257 +0,0 @@ -// Formatting library for C++ - the core API -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. -// -// Copyright (c) 2018 - present, Remotion (Igor Schulz) -// All Rights Reserved -// {fmt} support for rgb color output. - -#ifndef FMT_COLORS_H_ -#define FMT_COLORS_H_ - -#include "format.h" - -FMT_BEGIN_NAMESPACE - -// rgb is a struct for red, green and blue colors. -// We use rgb as name because some editors will show it as color direct in the -// editor. -struct rgb -{ - FMT_CONSTEXPR_DECL rgb() - : r(0) - , g(0) - , b(0) - { - } - FMT_CONSTEXPR_DECL rgb(uint8_t r_, uint8_t g_, uint8_t b_) - : r(r_) - , g(g_) - , b(b_) - { - } - FMT_CONSTEXPR_DECL rgb(uint32_t hex) - : r((hex >> 16) & 0xFF) - , g((hex >> 8) & 0xFF) - , b((hex)&0xFF) - { - } - uint8_t r; - uint8_t g; - uint8_t b; -}; - -namespace internal { - -FMT_CONSTEXPR inline void to_esc(uint8_t c, char out[], int offset) -{ - out[offset + 0] = static_cast<char>('0' + c / 100); - out[offset + 1] = static_cast<char>('0' + c / 10 % 10); - out[offset + 2] = static_cast<char>('0' + c % 10); -} - -} // namespace internal - -FMT_FUNC void vprint_rgb(rgb fd, string_view format, format_args args) -{ - char escape_fd[] = "\x1b[38;2;000;000;000m"; - static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; - internal::to_esc(fd.r, escape_fd, 7); - internal::to_esc(fd.g, escape_fd, 11); - internal::to_esc(fd.b, escape_fd, 15); - - std::fputs(escape_fd, stdout); - vprint(format, args); - std::fputs(RESET_COLOR, stdout); -} - -FMT_FUNC void vprint_rgb(rgb fd, rgb bg, string_view format, format_args args) -{ - char escape_fd[] = "\x1b[38;2;000;000;000m"; // foreground color - char escape_bg[] = "\x1b[48;2;000;000;000m"; // background color - static FMT_CONSTEXPR_DECL const char RESET_COLOR[] = "\x1b[0m"; - internal::to_esc(fd.r, escape_fd, 7); - internal::to_esc(fd.g, escape_fd, 11); - internal::to_esc(fd.b, escape_fd, 15); - - internal::to_esc(bg.r, escape_bg, 7); - internal::to_esc(bg.g, escape_bg, 11); - internal::to_esc(bg.b, escape_bg, 15); - - std::fputs(escape_fd, stdout); - std::fputs(escape_bg, stdout); - vprint(format, args); - std::fputs(RESET_COLOR, stdout); -} - -template<typename... Args> -inline void print_rgb(rgb fd, string_view format_str, const Args &... args) -{ - vprint_rgb(fd, format_str, make_format_args(args...)); -} - -// rgb foreground color -template<typename... Args> -inline void print(rgb fd, string_view format_str, const Args &... args) -{ - vprint_rgb(fd, format_str, make_format_args(args...)); -} - -// rgb foreground color and background color -template<typename... Args> -inline void print(rgb fd, rgb bg, string_view format_str, const Args &... args) -{ - vprint_rgb(fd, bg, format_str, make_format_args(args...)); -} - -enum class color : uint32_t -{ - alice_blue = 0xF0F8FF, // rgb(240,248,255) - antique_white = 0xFAEBD7, // rgb(250,235,215) - aqua = 0x00FFFF, // rgb(0,255,255) - aquamarine = 0x7FFFD4, // rgb(127,255,212) - azure = 0xF0FFFF, // rgb(240,255,255) - beige = 0xF5F5DC, // rgb(245,245,220) - bisque = 0xFFE4C4, // rgb(255,228,196) - black = 0x000000, // rgb(0,0,0) - blanched_almond = 0xFFEBCD, // rgb(255,235,205) - blue = 0x0000FF, // rgb(0,0,255) - blue_violet = 0x8A2BE2, // rgb(138,43,226) - brown = 0xA52A2A, // rgb(165,42,42) - burly_wood = 0xDEB887, // rgb(222,184,135) - cadet_blue = 0x5F9EA0, // rgb(95,158,160) - chartreuse = 0x7FFF00, // rgb(127,255,0) - chocolate = 0xD2691E, // rgb(210,105,30) - coral = 0xFF7F50, // rgb(255,127,80) - cornflower_blue = 0x6495ED, // rgb(100,149,237) - cornsilk = 0xFFF8DC, // rgb(255,248,220) - crimson = 0xDC143C, // rgb(220,20,60) - cyan = 0x00FFFF, // rgb(0,255,255) - dark_blue = 0x00008B, // rgb(0,0,139) - dark_cyan = 0x008B8B, // rgb(0,139,139) - dark_golden_rod = 0xB8860B, // rgb(184,134,11) - dark_gray = 0xA9A9A9, // rgb(169,169,169) - dark_green = 0x006400, // rgb(0,100,0) - dark_khaki = 0xBDB76B, // rgb(189,183,107) - dark_magenta = 0x8B008B, // rgb(139,0,139) - dark_olive_green = 0x556B2F, // rgb(85,107,47) - dark_orange = 0xFF8C00, // rgb(255,140,0) - dark_orchid = 0x9932CC, // rgb(153,50,204) - dark_red = 0x8B0000, // rgb(139,0,0) - dark_salmon = 0xE9967A, // rgb(233,150,122) - dark_sea_green = 0x8FBC8F, // rgb(143,188,143) - dark_slate_blue = 0x483D8B, // rgb(72,61,139) - dark_slate_gray = 0x2F4F4F, // rgb(47,79,79) - dark_turquoise = 0x00CED1, // rgb(0,206,209) - dark_violet = 0x9400D3, // rgb(148,0,211) - deep_pink = 0xFF1493, // rgb(255,20,147) - deep_sky_blue = 0x00BFFF, // rgb(0,191,255) - dim_gray = 0x696969, // rgb(105,105,105) - dodger_blue = 0x1E90FF, // rgb(30,144,255) - fire_brick = 0xB22222, // rgb(178,34,34) - floral_white = 0xFFFAF0, // rgb(255,250,240) - forest_green = 0x228B22, // rgb(34,139,34) - fuchsia = 0xFF00FF, // rgb(255,0,255) - gainsboro = 0xDCDCDC, // rgb(220,220,220) - ghost_white = 0xF8F8FF, // rgb(248,248,255) - gold = 0xFFD700, // rgb(255,215,0) - golden_rod = 0xDAA520, // rgb(218,165,32) - gray = 0x808080, // rgb(128,128,128) - green = 0x008000, // rgb(0,128,0) - green_yellow = 0xADFF2F, // rgb(173,255,47) - honey_dew = 0xF0FFF0, // rgb(240,255,240) - hot_pink = 0xFF69B4, // rgb(255,105,180) - indian_red = 0xCD5C5C, // rgb(205,92,92) - indigo = 0x4B0082, // rgb(75,0,130) - ivory = 0xFFFFF0, // rgb(255,255,240) - khaki = 0xF0E68C, // rgb(240,230,140) - lavender = 0xE6E6FA, // rgb(230,230,250) - lavender_blush = 0xFFF0F5, // rgb(255,240,245) - lawn_green = 0x7CFC00, // rgb(124,252,0) - lemon_chiffon = 0xFFFACD, // rgb(255,250,205) - light_blue = 0xADD8E6, // rgb(173,216,230) - light_coral = 0xF08080, // rgb(240,128,128) - light_cyan = 0xE0FFFF, // rgb(224,255,255) - light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210) - light_gray = 0xD3D3D3, // rgb(211,211,211) - light_green = 0x90EE90, // rgb(144,238,144) - light_pink = 0xFFB6C1, // rgb(255,182,193) - light_salmon = 0xFFA07A, // rgb(255,160,122) - light_sea_green = 0x20B2AA, // rgb(32,178,170) - light_sky_blue = 0x87CEFA, // rgb(135,206,250) - light_slate_gray = 0x778899, // rgb(119,136,153) - light_steel_blue = 0xB0C4DE, // rgb(176,196,222) - light_yellow = 0xFFFFE0, // rgb(255,255,224) - lime = 0x00FF00, // rgb(0,255,0) - lime_green = 0x32CD32, // rgb(50,205,50) - linen = 0xFAF0E6, // rgb(250,240,230) - magenta = 0xFF00FF, // rgb(255,0,255) - maroon = 0x800000, // rgb(128,0,0) - medium_aqua_marine = 0x66CDAA, // rgb(102,205,170) - medium_blue = 0x0000CD, // rgb(0,0,205) - medium_orchid = 0xBA55D3, // rgb(186,85,211) - medium_purple = 0x9370DB, // rgb(147,112,219) - medium_sea_green = 0x3CB371, // rgb(60,179,113) - medium_slate_blue = 0x7B68EE, // rgb(123,104,238) - medium_spring_green = 0x00FA9A, // rgb(0,250,154) - medium_turquoise = 0x48D1CC, // rgb(72,209,204) - medium_violet_red = 0xC71585, // rgb(199,21,133) - midnight_blue = 0x191970, // rgb(25,25,112) - mint_cream = 0xF5FFFA, // rgb(245,255,250) - misty_rose = 0xFFE4E1, // rgb(255,228,225) - moccasin = 0xFFE4B5, // rgb(255,228,181) - navajo_white = 0xFFDEAD, // rgb(255,222,173) - navy = 0x000080, // rgb(0,0,128) - old_lace = 0xFDF5E6, // rgb(253,245,230) - olive = 0x808000, // rgb(128,128,0) - olive_drab = 0x6B8E23, // rgb(107,142,35) - orange = 0xFFA500, // rgb(255,165,0) - orange_red = 0xFF4500, // rgb(255,69,0) - orchid = 0xDA70D6, // rgb(218,112,214) - pale_golden_rod = 0xEEE8AA, // rgb(238,232,170) - pale_green = 0x98FB98, // rgb(152,251,152) - pale_turquoise = 0xAFEEEE, // rgb(175,238,238) - pale_violet_red = 0xDB7093, // rgb(219,112,147) - papaya_whip = 0xFFEFD5, // rgb(255,239,213) - peach_puff = 0xFFDAB9, // rgb(255,218,185) - peru = 0xCD853F, // rgb(205,133,63) - pink = 0xFFC0CB, // rgb(255,192,203) - plum = 0xDDA0DD, // rgb(221,160,221) - powder_blue = 0xB0E0E6, // rgb(176,224,230) - purple = 0x800080, // rgb(128,0,128) - rebecca_purple = 0x663399, // rgb(102,51,153) - red = 0xFF0000, // rgb(255,0,0) - rosy_brown = 0xBC8F8F, // rgb(188,143,143) - royal_blue = 0x4169E1, // rgb(65,105,225) - saddle_brown = 0x8B4513, // rgb(139,69,19) - salmon = 0xFA8072, // rgb(250,128,114) - sandy_brown = 0xF4A460, // rgb(244,164,96) - sea_green = 0x2E8B57, // rgb(46,139,87) - sea_shell = 0xFFF5EE, // rgb(255,245,238) - sienna = 0xA0522D, // rgb(160,82,45) - silver = 0xC0C0C0, // rgb(192,192,192) - sky_blue = 0x87CEEB, // rgb(135,206,235) - slate_blue = 0x6A5ACD, // rgb(106,90,205) - slate_gray = 0x708090, // rgb(112,128,144) - snow = 0xFFFAFA, // rgb(255,250,250) - spring_green = 0x00FF7F, // rgb(0,255,127) - steel_blue = 0x4682B4, // rgb(70,130,180) - tan = 0xD2B48C, // rgb(210,180,140) - teal = 0x008080, // rgb(0,128,128) - thistle = 0xD8BFD8, // rgb(216,191,216) - tomato = 0xFF6347, // rgb(255,99,71) - turquoise = 0x40E0D0, // rgb(64,224,208) - violet = 0xEE82EE, // rgb(238,130,238) - wheat = 0xF5DEB3, // rgb(245,222,179) - white = 0xFFFFFF, // rgb(255,255,255) - white_smoke = 0xF5F5F5, // rgb(245,245,245) - yellow = 0xFFFF00, // rgb(255,255,0) - yellow_green = 0x9ACD32, // rgb(154,205,50) -}; // enum class colors - -FMT_END_NAMESPACE - -#endif // FMT_COLORS_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/core.h b/src/nmodl/ext/spdlog/fmt/bundled/core.h deleted file mode 100644 index 5912afef82..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/core.h +++ /dev/null @@ -1,1502 +0,0 @@ -// Formatting library for C++ - the core API -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_CORE_H_ -#define FMT_CORE_H_ - -#include <cassert> -#include <cstdio> // std::FILE -#include <cstring> -#include <iterator> -#include <string> -#include <type_traits> - -// The fmt library version in the form major * 10000 + minor * 100 + patch. -#define FMT_VERSION 50201 - -#ifdef __has_feature -# define FMT_HAS_FEATURE(x) __has_feature(x) -#else -# define FMT_HAS_FEATURE(x) 0 -#endif - -#if defined(__has_include) && !defined(__INTELLISENSE__) && \ - (!defined(__INTEL_COMPILER) || __INTEL_COMPILER >= 1600) -# define FMT_HAS_INCLUDE(x) __has_include(x) -#else -# define FMT_HAS_INCLUDE(x) 0 -#endif - -#ifdef __has_cpp_attribute -# define FMT_HAS_CPP_ATTRIBUTE(x) __has_cpp_attribute(x) -#else -# define FMT_HAS_CPP_ATTRIBUTE(x) 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -# define FMT_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__) -#else -# define FMT_GCC_VERSION 0 -#endif - -#if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) -# define FMT_HAS_GXX_CXX11 FMT_GCC_VERSION -#else -# define FMT_HAS_GXX_CXX11 0 -#endif - -#ifdef _MSC_VER -# define FMT_MSC_VER _MSC_VER -#else -# define FMT_MSC_VER 0 -#endif - -// Check if relaxed C++14 constexpr is supported. -// GCC doesn't allow throw in constexpr until version 6 (bug 67371). -#ifndef FMT_USE_CONSTEXPR -# define FMT_USE_CONSTEXPR \ - (FMT_HAS_FEATURE(cxx_relaxed_constexpr) || FMT_MSC_VER >= 1910 || \ - (FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L)) -#endif -#if FMT_USE_CONSTEXPR -# define FMT_CONSTEXPR constexpr -# define FMT_CONSTEXPR_DECL constexpr -#else -# define FMT_CONSTEXPR inline -# define FMT_CONSTEXPR_DECL -#endif - -#ifndef FMT_USE_CONSTEXPR11 -# define FMT_USE_CONSTEXPR11 \ - (FMT_MSC_VER >= 1900 || FMT_GCC_VERSION >= 406 || FMT_USE_CONSTEXPR) -#endif -#if FMT_USE_CONSTEXPR11 -# define FMT_CONSTEXPR11 constexpr -#else -# define FMT_CONSTEXPR11 -#endif - -#ifndef FMT_OVERRIDE -# if FMT_HAS_FEATURE(cxx_override) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_OVERRIDE override -# else -# define FMT_OVERRIDE -# endif -#endif - -#if FMT_HAS_FEATURE(cxx_explicit_conversions) || FMT_MSC_VER >= 1800 -# define FMT_EXPLICIT explicit -#else -# define FMT_EXPLICIT -#endif - -#ifndef FMT_NULL -# if FMT_HAS_FEATURE(cxx_nullptr) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1600 -# define FMT_NULL nullptr -# define FMT_USE_NULLPTR 1 -# else -# define FMT_NULL NULL -# endif -#endif - -#ifndef FMT_USE_NULLPTR -# define FMT_USE_NULLPTR 0 -#endif - -#if FMT_HAS_CPP_ATTRIBUTE(noreturn) -# define FMT_NORETURN [[noreturn]] -#else -# define FMT_NORETURN -#endif - -// Check if exceptions are disabled. -#if defined(__GNUC__) && !defined(__EXCEPTIONS) -# define FMT_EXCEPTIONS 0 -#elif FMT_MSC_VER && !_HAS_EXCEPTIONS -# define FMT_EXCEPTIONS 0 -#endif -#ifndef FMT_EXCEPTIONS -# define FMT_EXCEPTIONS 1 -#endif - -// Define FMT_USE_NOEXCEPT to make fmt use noexcept (C++11 feature). -#ifndef FMT_USE_NOEXCEPT -# define FMT_USE_NOEXCEPT 0 -#endif - -#if FMT_USE_NOEXCEPT || FMT_HAS_FEATURE(cxx_noexcept) || \ - (FMT_GCC_VERSION >= 408 && FMT_HAS_GXX_CXX11) || FMT_MSC_VER >= 1900 -# define FMT_DETECTED_NOEXCEPT noexcept -# define FMT_HAS_CXX11_NOEXCEPT 1 -#else -# define FMT_DETECTED_NOEXCEPT throw() -# define FMT_HAS_CXX11_NOEXCEPT 0 -#endif - -#ifndef FMT_NOEXCEPT -# if FMT_EXCEPTIONS || FMT_HAS_CXX11_NOEXCEPT -# define FMT_NOEXCEPT FMT_DETECTED_NOEXCEPT -# else -# define FMT_NOEXCEPT -# endif -#endif - -// This is needed because GCC still uses throw() in its headers when exceptions -// are disabled. -#if FMT_GCC_VERSION -# define FMT_DTOR_NOEXCEPT FMT_DETECTED_NOEXCEPT -#else -# define FMT_DTOR_NOEXCEPT FMT_NOEXCEPT -#endif - -#ifndef FMT_BEGIN_NAMESPACE -# if FMT_HAS_FEATURE(cxx_inline_namespaces) || FMT_GCC_VERSION >= 404 || \ - FMT_MSC_VER >= 1900 -# define FMT_INLINE_NAMESPACE inline namespace -# define FMT_END_NAMESPACE }} -# else -# define FMT_INLINE_NAMESPACE namespace -# define FMT_END_NAMESPACE } using namespace v5; } -# endif -# define FMT_BEGIN_NAMESPACE namespace fmt { FMT_INLINE_NAMESPACE v5 { -#endif - -#if !defined(FMT_HEADER_ONLY) && defined(_WIN32) -# ifdef FMT_EXPORT -# define FMT_API __declspec(dllexport) -# elif defined(FMT_SHARED) -# define FMT_API __declspec(dllimport) -# endif -#endif -#ifndef FMT_API -# define FMT_API -#endif - -#ifndef FMT_ASSERT -# define FMT_ASSERT(condition, message) assert((condition) && message) -#endif - -// libc++ supports string_view in pre-c++17. -#if (FMT_HAS_INCLUDE(<string_view>) && \ - (__cplusplus > 201402L || defined(_LIBCPP_VERSION))) || \ - (defined(_MSVC_LANG) && _MSVC_LANG > 201402L && _MSC_VER >= 1910) -# include <string_view> -# define FMT_USE_STD_STRING_VIEW -#elif (FMT_HAS_INCLUDE(<experimental/string_view>) && \ - __cplusplus >= 201402L) -# include <experimental/string_view> -# define FMT_USE_EXPERIMENTAL_STRING_VIEW -#endif - -// std::result_of is defined in <functional> in gcc 4.4. -#if FMT_GCC_VERSION && FMT_GCC_VERSION <= 404 -# include <functional> -#endif - -FMT_BEGIN_NAMESPACE -namespace internal { - -// An implementation of declval for pre-C++11 compilers such as gcc 4. -template <typename T> -typename std::add_rvalue_reference<T>::type declval() FMT_NOEXCEPT; - -template <typename> -struct result_of; - -template <typename F, typename... Args> -struct result_of<F(Args...)> { - // A workaround for gcc 4.4 that doesn't allow F to be a reference. - typedef typename std::result_of< - typename std::remove_reference<F>::type(Args...)>::type type; -}; - -// Casts nonnegative integer to unsigned. -template <typename Int> -FMT_CONSTEXPR typename std::make_unsigned<Int>::type to_unsigned(Int value) { - FMT_ASSERT(value >= 0, "negative value"); - return static_cast<typename std::make_unsigned<Int>::type>(value); -} - -// A constexpr std::char_traits::length replacement for pre-C++17. -template <typename Char> -FMT_CONSTEXPR size_t length(const Char *s) { - const Char *start = s; - while (*s) ++s; - return s - start; -} -#if FMT_GCC_VERSION -FMT_CONSTEXPR size_t length(const char *s) { return std::strlen(s); } -#endif -} // namespace internal - -/** - An implementation of ``std::basic_string_view`` for pre-C++17. It provides a - subset of the API. ``fmt::basic_string_view`` is used for format strings even - if ``std::string_view`` is available to prevent issues when a library is - compiled with a different ``-std`` option than the client code (which is not - recommended). - */ -template <typename Char> -class basic_string_view { - private: - const Char *data_; - size_t size_; - - public: - typedef Char char_type; - typedef const Char *iterator; - - // Standard basic_string_view type. -#if defined(FMT_USE_STD_STRING_VIEW) - typedef std::basic_string_view<Char> type; -#elif defined(FMT_USE_EXPERIMENTAL_STRING_VIEW) - typedef std::experimental::basic_string_view<Char> type; -#else - struct type { - const char *data() const { return FMT_NULL; } - size_t size() const { return 0; } - }; -#endif - - FMT_CONSTEXPR basic_string_view() FMT_NOEXCEPT : data_(FMT_NULL), size_(0) {} - - /** Constructs a string reference object from a C string and a size. */ - FMT_CONSTEXPR basic_string_view(const Char *s, size_t count) FMT_NOEXCEPT - : data_(s), size_(count) {} - - /** - \rst - Constructs a string reference object from a C string computing - the size with ``std::char_traits<Char>::length``. - \endrst - */ - FMT_CONSTEXPR basic_string_view(const Char *s) - : data_(s), size_(internal::length(s)) {} - - /** Constructs a string reference from a ``std::basic_string`` object. */ - template <typename Alloc> - FMT_CONSTEXPR basic_string_view( - const std::basic_string<Char, Alloc> &s) FMT_NOEXCEPT - : data_(s.data()), size_(s.size()) {} - - FMT_CONSTEXPR basic_string_view(type s) FMT_NOEXCEPT - : data_(s.data()), size_(s.size()) {} - - /** Returns a pointer to the string data. */ - FMT_CONSTEXPR const Char *data() const { return data_; } - - /** Returns the string size. */ - FMT_CONSTEXPR size_t size() const { return size_; } - - FMT_CONSTEXPR iterator begin() const { return data_; } - FMT_CONSTEXPR iterator end() const { return data_ + size_; } - - FMT_CONSTEXPR void remove_prefix(size_t n) { - data_ += n; - size_ -= n; - } - - // Lexicographically compare this string reference to other. - int compare(basic_string_view other) const { - size_t str_size = size_ < other.size_ ? size_ : other.size_; - int result = std::char_traits<Char>::compare(data_, other.data_, str_size); - if (result == 0) - result = size_ == other.size_ ? 0 : (size_ < other.size_ ? -1 : 1); - return result; - } - - friend bool operator==(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) == 0; - } - friend bool operator!=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) != 0; - } - friend bool operator<(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) < 0; - } - friend bool operator<=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) <= 0; - } - friend bool operator>(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) > 0; - } - friend bool operator>=(basic_string_view lhs, basic_string_view rhs) { - return lhs.compare(rhs) >= 0; - } -}; - -typedef basic_string_view<char> string_view; -typedef basic_string_view<wchar_t> wstring_view; - -template <typename Context> -class basic_format_arg; - -template <typename Context> -class basic_format_args; - -template <typename T> -struct no_formatter_error : std::false_type {}; - -// A formatter for objects of type T. -template <typename T, typename Char = char, typename Enable = void> -struct formatter { - static_assert(no_formatter_error<T>::value, - "don't know how to format the type, include fmt/ostream.h if it provides " - "an operator<< that should be used"); - - // The following functions are not defined intentionally. - template <typename ParseContext> - typename ParseContext::iterator parse(ParseContext &); - template <typename FormatContext> - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()); -}; - -template <typename T, typename Char, typename Enable = void> -struct convert_to_int { - enum { - value = !std::is_arithmetic<T>::value && std::is_convertible<T, int>::value - }; -}; - -namespace internal { - -/** A contiguous memory buffer with an optional growing ability. */ -template <typename T> -class basic_buffer { - private: - basic_buffer(const basic_buffer &) = delete; - void operator=(const basic_buffer &) = delete; - - T *ptr_; - std::size_t size_; - std::size_t capacity_; - - protected: - // Don't initialize ptr_ since it is not accessed to save a few cycles. - basic_buffer(std::size_t sz) FMT_NOEXCEPT: size_(sz), capacity_(sz) {} - - basic_buffer(T *p = FMT_NULL, std::size_t sz = 0, std::size_t cap = 0) - FMT_NOEXCEPT: ptr_(p), size_(sz), capacity_(cap) {} - - /** Sets the buffer data and capacity. */ - void set(T *buf_data, std::size_t buf_capacity) FMT_NOEXCEPT { - ptr_ = buf_data; - capacity_ = buf_capacity; - } - - /** Increases the buffer capacity to hold at least *capacity* elements. */ - virtual void grow(std::size_t capacity) = 0; - - public: - typedef T value_type; - typedef const T &const_reference; - - virtual ~basic_buffer() {} - - T *begin() FMT_NOEXCEPT { return ptr_; } - T *end() FMT_NOEXCEPT { return ptr_ + size_; } - - /** Returns the size of this buffer. */ - std::size_t size() const FMT_NOEXCEPT { return size_; } - - /** Returns the capacity of this buffer. */ - std::size_t capacity() const FMT_NOEXCEPT { return capacity_; } - - /** Returns a pointer to the buffer data. */ - T *data() FMT_NOEXCEPT { return ptr_; } - - /** Returns a pointer to the buffer data. */ - const T *data() const FMT_NOEXCEPT { return ptr_; } - - /** - Resizes the buffer. If T is a POD type new elements may not be initialized. - */ - void resize(std::size_t new_size) { - reserve(new_size); - size_ = new_size; - } - - /** Clears this buffer. */ - void clear() { size_ = 0; } - - /** Reserves space to store at least *capacity* elements. */ - void reserve(std::size_t new_capacity) { - if (new_capacity > capacity_) - grow(new_capacity); - } - - void push_back(const T &value) { - reserve(size_ + 1); - ptr_[size_++] = value; - } - - /** Appends data to the end of the buffer. */ - template <typename U> - void append(const U *begin, const U *end); - - T &operator[](std::size_t index) { return ptr_[index]; } - const T &operator[](std::size_t index) const { return ptr_[index]; } -}; - -typedef basic_buffer<char> buffer; -typedef basic_buffer<wchar_t> wbuffer; - -// A container-backed buffer. -template <typename Container> -class container_buffer : public basic_buffer<typename Container::value_type> { - private: - Container &container_; - - protected: - void grow(std::size_t capacity) FMT_OVERRIDE { - container_.resize(capacity); - this->set(&container_[0], capacity); - } - - public: - explicit container_buffer(Container &c) - : basic_buffer<typename Container::value_type>(c.size()), container_(c) {} -}; - -struct error_handler { - FMT_CONSTEXPR error_handler() {} - FMT_CONSTEXPR error_handler(const error_handler &) {} - - // This function is intentionally not constexpr to give a compile-time error. - FMT_API void on_error(const char *message); -}; - -// Formatting of wide characters and strings into a narrow output is disallowed: -// fmt::format("{}", L"test"); // error -// To fix this, use a wide format string: -// fmt::format(L"{}", L"test"); -template <typename Char> -inline void require_wchar() { - static_assert( - std::is_same<wchar_t, Char>::value, - "formatting of wide characters into a narrow output is disallowed"); -} - -template <typename Char> -struct named_arg_base; - -template <typename T, typename Char> -struct named_arg; - -template <typename T> -struct is_named_arg : std::false_type {}; - -template <typename T, typename Char> -struct is_named_arg<named_arg<T, Char>> : std::true_type {}; - -enum type { - none_type, named_arg_type, - // Integer types should go first, - int_type, uint_type, long_long_type, ulong_long_type, bool_type, char_type, - last_integer_type = char_type, - // followed by floating-point types. - double_type, long_double_type, last_numeric_type = long_double_type, - cstring_type, string_type, pointer_type, custom_type -}; - -FMT_CONSTEXPR bool is_integral(type t) { - FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); - return t > internal::none_type && t <= internal::last_integer_type; -} - -FMT_CONSTEXPR bool is_arithmetic(type t) { - FMT_ASSERT(t != internal::named_arg_type, "invalid argument type"); - return t > internal::none_type && t <= internal::last_numeric_type; -} - -template <typename Char> -struct string_value { - const Char *value; - std::size_t size; -}; - -template <typename Context> -struct custom_value { - const void *value; - void (*format)(const void *arg, Context &ctx); -}; - -// A formatting argument value. -template <typename Context> -class value { - public: - typedef typename Context::char_type char_type; - - union { - int int_value; - unsigned uint_value; - long long long_long_value; - unsigned long long ulong_long_value; - double double_value; - long double long_double_value; - const void *pointer; - string_value<char_type> string; - string_value<signed char> sstring; - string_value<unsigned char> ustring; - custom_value<Context> custom; - }; - - FMT_CONSTEXPR value(int val = 0) : int_value(val) {} - value(unsigned val) { uint_value = val; } - value(long long val) { long_long_value = val; } - value(unsigned long long val) { ulong_long_value = val; } - value(double val) { double_value = val; } - value(long double val) { long_double_value = val; } - value(const char_type *val) { string.value = val; } - value(const signed char *val) { - static_assert(std::is_same<char, char_type>::value, - "incompatible string types"); - sstring.value = val; - } - value(const unsigned char *val) { - static_assert(std::is_same<char, char_type>::value, - "incompatible string types"); - ustring.value = val; - } - value(basic_string_view<char_type> val) { - string.value = val.data(); - string.size = val.size(); - } - value(const void *val) { pointer = val; } - - template <typename T> - explicit value(const T &val) { - custom.value = &val; - custom.format = &format_custom_arg<T>; - } - - const named_arg_base<char_type> &as_named_arg() { - return *static_cast<const named_arg_base<char_type>*>(pointer); - } - - private: - // Formats an argument of a custom type, such as a user-defined class. - template <typename T> - static void format_custom_arg(const void *arg, Context &ctx) { - // Get the formatter type through the context to allow different contexts - // have different extension points, e.g. `formatter<T>` for `format` and - // `printf_formatter<T>` for `printf`. - typename Context::template formatter_type<T>::type f; - auto &&parse_ctx = ctx.parse_context(); - parse_ctx.advance_to(f.parse(parse_ctx)); - ctx.advance_to(f.format(*static_cast<const T*>(arg), ctx)); - } -}; - -// Value initializer used to delay conversion to value and reduce memory churn. -template <typename Context, typename T, type TYPE> -struct init { - T val; - static const type type_tag = TYPE; - - FMT_CONSTEXPR init(const T &v) : val(v) {} - FMT_CONSTEXPR operator value<Context>() const { return value<Context>(val); } -}; - -template <typename Context, typename T> -FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value); - -#define FMT_MAKE_VALUE(TAG, ArgType, ValueType) \ - template <typename C> \ - FMT_CONSTEXPR init<C, ValueType, TAG> make_value(ArgType val) { \ - return static_cast<ValueType>(val); \ - } - -#define FMT_MAKE_VALUE_SAME(TAG, Type) \ - template <typename C> \ - FMT_CONSTEXPR init<C, Type, TAG> make_value(Type val) { return val; } - -FMT_MAKE_VALUE(bool_type, bool, int) -FMT_MAKE_VALUE(int_type, short, int) -FMT_MAKE_VALUE(uint_type, unsigned short, unsigned) -FMT_MAKE_VALUE_SAME(int_type, int) -FMT_MAKE_VALUE_SAME(uint_type, unsigned) - -// To minimize the number of types we need to deal with, long is translated -// either to int or to long long depending on its size. -typedef std::conditional<sizeof(long) == sizeof(int), int, long long>::type - long_type; -FMT_MAKE_VALUE( - (sizeof(long) == sizeof(int) ? int_type : long_long_type), long, long_type) -typedef std::conditional<sizeof(unsigned long) == sizeof(unsigned), - unsigned, unsigned long long>::type ulong_type; -FMT_MAKE_VALUE( - (sizeof(unsigned long) == sizeof(unsigned) ? uint_type : ulong_long_type), - unsigned long, ulong_type) - -FMT_MAKE_VALUE_SAME(long_long_type, long long) -FMT_MAKE_VALUE_SAME(ulong_long_type, unsigned long long) -FMT_MAKE_VALUE(int_type, signed char, int) -FMT_MAKE_VALUE(uint_type, unsigned char, unsigned) -FMT_MAKE_VALUE(char_type, char, int) - -#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) -template <typename C> -inline init<C, int, char_type> make_value(wchar_t val) { - require_wchar<typename C::char_type>(); - return static_cast<int>(val); -} -#endif - -FMT_MAKE_VALUE(double_type, float, double) -FMT_MAKE_VALUE_SAME(double_type, double) -FMT_MAKE_VALUE_SAME(long_double_type, long double) - -// Formatting of wide strings into a narrow buffer and multibyte strings -// into a wide buffer is disallowed (https://github.com/fmtlib/fmt/pull/606). -FMT_MAKE_VALUE(cstring_type, typename C::char_type*, - const typename C::char_type*) -FMT_MAKE_VALUE(cstring_type, const typename C::char_type*, - const typename C::char_type*) - -FMT_MAKE_VALUE(cstring_type, signed char*, const signed char*) -FMT_MAKE_VALUE_SAME(cstring_type, const signed char*) -FMT_MAKE_VALUE(cstring_type, unsigned char*, const unsigned char*) -FMT_MAKE_VALUE_SAME(cstring_type, const unsigned char*) -FMT_MAKE_VALUE_SAME(string_type, basic_string_view<typename C::char_type>) -FMT_MAKE_VALUE(string_type, - typename basic_string_view<typename C::char_type>::type, - basic_string_view<typename C::char_type>) -FMT_MAKE_VALUE(string_type, const std::basic_string<typename C::char_type>&, - basic_string_view<typename C::char_type>) -FMT_MAKE_VALUE(pointer_type, void*, const void*) -FMT_MAKE_VALUE_SAME(pointer_type, const void*) - -#if FMT_USE_NULLPTR -FMT_MAKE_VALUE(pointer_type, std::nullptr_t, const void*) -#endif - -// Formatting of arbitrary pointers is disallowed. If you want to output a -// pointer cast it to "void *" or "const void *". In particular, this forbids -// formatting of "[const] volatile char *" which is printed as bool by -// iostreams. -template <typename C, typename T> -typename std::enable_if<!std::is_same<T, typename C::char_type>::value>::type - make_value(const T *) { - static_assert(!sizeof(T), "formatting of non-void pointers is disallowed"); -} - -template <typename C, typename T> -inline typename std::enable_if< - std::is_enum<T>::value && convert_to_int<T, typename C::char_type>::value, - init<C, int, int_type>>::type - make_value(const T &val) { return static_cast<int>(val); } - -template <typename C, typename T, typename Char = typename C::char_type> -inline typename std::enable_if< - std::is_constructible<basic_string_view<Char>, T>::value, - init<C, basic_string_view<Char>, string_type>>::type - make_value(const T &val) { return basic_string_view<Char>(val); } - -template <typename C, typename T, typename Char = typename C::char_type> -inline typename std::enable_if< - !convert_to_int<T, Char>::value && - !std::is_convertible<T, basic_string_view<Char>>::value && - !std::is_constructible<basic_string_view<Char>, T>::value, - // Implicit conversion to std::string is not handled here because it's - // unsafe: https://github.com/fmtlib/fmt/issues/729 - init<C, const T &, custom_type>>::type - make_value(const T &val) { return val; } - -template <typename C, typename T> -init<C, const void*, named_arg_type> - make_value(const named_arg<T, typename C::char_type> &val) { - basic_format_arg<C> arg = make_arg<C>(val.value); - std::memcpy(val.data, &arg, sizeof(arg)); - return static_cast<const void*>(&val); -} - -// Maximum number of arguments with packed types. -enum { max_packed_args = 15 }; - -template <typename Context> -class arg_map; -} // namespace internal - -// A formatting argument. It is a trivially copyable/constructible type to -// allow storage in basic_memory_buffer. -template <typename Context> -class basic_format_arg { - private: - internal::value<Context> value_; - internal::type type_; - - template <typename ContextType, typename T> - friend FMT_CONSTEXPR basic_format_arg<ContextType> - internal::make_arg(const T &value); - - template <typename Visitor, typename Ctx> - friend FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type - visit(Visitor &&vis, const basic_format_arg<Ctx> &arg); - - friend class basic_format_args<Context>; - friend class internal::arg_map<Context>; - - typedef typename Context::char_type char_type; - - public: - class handle { - public: - explicit handle(internal::custom_value<Context> custom): custom_(custom) {} - - void format(Context &ctx) const { custom_.format(custom_.value, ctx); } - - private: - internal::custom_value<Context> custom_; - }; - - FMT_CONSTEXPR basic_format_arg() : type_(internal::none_type) {} - - FMT_EXPLICIT operator bool() const FMT_NOEXCEPT { - return type_ != internal::none_type; - } - - internal::type type() const { return type_; } - - bool is_integral() const { return internal::is_integral(type_); } - bool is_arithmetic() const { return internal::is_arithmetic(type_); } -}; - -struct monostate {}; - -/** - \rst - Visits an argument dispatching to the appropriate visit method based on - the argument type. For example, if the argument type is ``double`` then - ``vis(value)`` will be called with the value of type ``double``. - \endrst - */ -template <typename Visitor, typename Context> -FMT_CONSTEXPR typename internal::result_of<Visitor(int)>::type - visit(Visitor &&vis, const basic_format_arg<Context> &arg) { - typedef typename Context::char_type char_type; - switch (arg.type_) { - case internal::none_type: - break; - case internal::named_arg_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case internal::int_type: - return vis(arg.value_.int_value); - case internal::uint_type: - return vis(arg.value_.uint_value); - case internal::long_long_type: - return vis(arg.value_.long_long_value); - case internal::ulong_long_type: - return vis(arg.value_.ulong_long_value); - case internal::bool_type: - return vis(arg.value_.int_value != 0); - case internal::char_type: - return vis(static_cast<char_type>(arg.value_.int_value)); - case internal::double_type: - return vis(arg.value_.double_value); - case internal::long_double_type: - return vis(arg.value_.long_double_value); - case internal::cstring_type: - return vis(arg.value_.string.value); - case internal::string_type: - return vis(basic_string_view<char_type>( - arg.value_.string.value, arg.value_.string.size)); - case internal::pointer_type: - return vis(arg.value_.pointer); - case internal::custom_type: - return vis(typename basic_format_arg<Context>::handle(arg.value_.custom)); - } - return vis(monostate()); -} - -// Parsing context consisting of a format string range being parsed and an -// argument counter for automatic indexing. -template <typename Char, typename ErrorHandler = internal::error_handler> -class basic_parse_context : private ErrorHandler { - private: - basic_string_view<Char> format_str_; - int next_arg_id_; - - public: - typedef Char char_type; - typedef typename basic_string_view<Char>::iterator iterator; - - explicit FMT_CONSTEXPR basic_parse_context( - basic_string_view<Char> format_str, ErrorHandler eh = ErrorHandler()) - : ErrorHandler(eh), format_str_(format_str), next_arg_id_(0) {} - - // Returns an iterator to the beginning of the format string range being - // parsed. - FMT_CONSTEXPR iterator begin() const FMT_NOEXCEPT { - return format_str_.begin(); - } - - // Returns an iterator past the end of the format string range being parsed. - FMT_CONSTEXPR iterator end() const FMT_NOEXCEPT { return format_str_.end(); } - - // Advances the begin iterator to ``it``. - FMT_CONSTEXPR void advance_to(iterator it) { - format_str_.remove_prefix(internal::to_unsigned(it - begin())); - } - - // Returns the next argument index. - FMT_CONSTEXPR unsigned next_arg_id(); - - FMT_CONSTEXPR bool check_arg_id(unsigned) { - if (next_arg_id_ > 0) { - on_error("cannot switch from automatic to manual argument indexing"); - return false; - } - next_arg_id_ = -1; - return true; - } - void check_arg_id(basic_string_view<Char>) {} - - FMT_CONSTEXPR void on_error(const char *message) { - ErrorHandler::on_error(message); - } - - FMT_CONSTEXPR ErrorHandler error_handler() const { return *this; } -}; - -typedef basic_parse_context<char> parse_context; -typedef basic_parse_context<wchar_t> wparse_context; - -namespace internal { -// A map from argument names to their values for named arguments. -template <typename Context> -class arg_map { - private: - arg_map(const arg_map &) = delete; - void operator=(const arg_map &) = delete; - - typedef typename Context::char_type char_type; - - struct entry { - basic_string_view<char_type> name; - basic_format_arg<Context> arg; - }; - - entry *map_; - unsigned size_; - - void push_back(value<Context> val) { - const internal::named_arg_base<char_type> &named = val.as_named_arg(); - map_[size_] = entry{named.name, named.template deserialize<Context>()}; - ++size_; - } - - public: - arg_map() : map_(FMT_NULL), size_(0) {} - void init(const basic_format_args<Context> &args); - ~arg_map() { delete [] map_; } - - basic_format_arg<Context> find(basic_string_view<char_type> name) const { - // The list is unsorted, so just return the first matching name. - for (entry *it = map_, *end = map_ + size_; it != end; ++it) { - if (it->name == name) - return it->arg; - } - return basic_format_arg<Context>(); - } -}; - -template <typename OutputIt, typename Context, typename Char> -class context_base { - public: - typedef OutputIt iterator; - - private: - basic_parse_context<Char> parse_context_; - iterator out_; - basic_format_args<Context> args_; - - protected: - typedef Char char_type; - typedef basic_format_arg<Context> format_arg; - - context_base(OutputIt out, basic_string_view<char_type> format_str, - basic_format_args<Context> ctx_args) - : parse_context_(format_str), out_(out), args_(ctx_args) {} - - // Returns the argument with specified index. - format_arg do_get_arg(unsigned arg_id) { - format_arg arg = args_.get(arg_id); - if (!arg) - parse_context_.on_error("argument index out of range"); - return arg; - } - - // Checks if manual indexing is used and returns the argument with - // specified index. - format_arg get_arg(unsigned arg_id) { - return this->parse_context().check_arg_id(arg_id) ? - this->do_get_arg(arg_id) : format_arg(); - } - - public: - basic_parse_context<char_type> &parse_context() { - return parse_context_; - } - - internal::error_handler error_handler() { - return parse_context_.error_handler(); - } - - void on_error(const char *message) { parse_context_.on_error(message); } - - // Returns an iterator to the beginning of the output range. - iterator out() { return out_; } - iterator begin() { return out_; } // deprecated - - // Advances the begin iterator to ``it``. - void advance_to(iterator it) { out_ = it; } - - basic_format_args<Context> args() const { return args_; } -}; - -// Extracts a reference to the container from back_insert_iterator. -template <typename Container> -inline Container &get_container(std::back_insert_iterator<Container> it) { - typedef std::back_insert_iterator<Container> bi_iterator; - struct accessor: bi_iterator { - accessor(bi_iterator iter) : bi_iterator(iter) {} - using bi_iterator::container; - }; - return *accessor(it).container; -} -} // namespace internal - -// Formatting context. -template <typename OutputIt, typename Char> -class basic_format_context : - public internal::context_base< - OutputIt, basic_format_context<OutputIt, Char>, Char> { - public: - /** The character type for the output. */ - typedef Char char_type; - - // using formatter_type = formatter<T, char_type>; - template <typename T> - struct formatter_type { typedef formatter<T, char_type> type; }; - - private: - internal::arg_map<basic_format_context> map_; - - basic_format_context(const basic_format_context &) = delete; - void operator=(const basic_format_context &) = delete; - - typedef internal::context_base<OutputIt, basic_format_context, Char> base; - typedef typename base::format_arg format_arg; - using base::get_arg; - - public: - using typename base::iterator; - - /** - Constructs a ``basic_format_context`` object. References to the arguments are - stored in the object so make sure they have appropriate lifetimes. - */ - basic_format_context(OutputIt out, basic_string_view<char_type> format_str, - basic_format_args<basic_format_context> ctx_args) - : base(out, format_str, ctx_args) {} - - format_arg next_arg() { - return this->do_get_arg(this->parse_context().next_arg_id()); - } - format_arg get_arg(unsigned arg_id) { return this->do_get_arg(arg_id); } - - // Checks if manual indexing is used and returns the argument with the - // specified name. - format_arg get_arg(basic_string_view<char_type> name); -}; - -template <typename Char> -struct buffer_context { - typedef basic_format_context< - std::back_insert_iterator<internal::basic_buffer<Char>>, Char> type; -}; -typedef buffer_context<char>::type format_context; -typedef buffer_context<wchar_t>::type wformat_context; - -namespace internal { -template <typename Context, typename T> -struct get_type { - typedef decltype(make_value<Context>( - declval<typename std::decay<T>::type&>())) value_type; - static const type value = value_type::type_tag; -}; - -template <typename Context> -FMT_CONSTEXPR11 unsigned long long get_types() { return 0; } - -template <typename Context, typename Arg, typename... Args> -FMT_CONSTEXPR11 unsigned long long get_types() { - return get_type<Context, Arg>::value | (get_types<Context, Args...>() << 4); -} - -template <typename Context, typename T> -FMT_CONSTEXPR basic_format_arg<Context> make_arg(const T &value) { - basic_format_arg<Context> arg; - arg.type_ = get_type<Context, T>::value; - arg.value_ = make_value<Context>(value); - return arg; -} - -template <bool IS_PACKED, typename Context, typename T> -inline typename std::enable_if<IS_PACKED, value<Context>>::type - make_arg(const T &value) { - return make_value<Context>(value); -} - -template <bool IS_PACKED, typename Context, typename T> -inline typename std::enable_if<!IS_PACKED, basic_format_arg<Context>>::type - make_arg(const T &value) { - return make_arg<Context>(value); -} -} // namespace internal - -/** - \rst - An array of references to arguments. It can be implicitly converted into - `~fmt::basic_format_args` for passing into type-erased formatting functions - such as `~fmt::vformat`. - \endrst - */ -template <typename Context, typename ...Args> -class format_arg_store { - private: - static const size_t NUM_ARGS = sizeof...(Args); - - // Packed is a macro on MinGW so use IS_PACKED instead. - static const bool IS_PACKED = NUM_ARGS < internal::max_packed_args; - - typedef typename std::conditional<IS_PACKED, - internal::value<Context>, basic_format_arg<Context>>::type value_type; - - // If the arguments are not packed, add one more element to mark the end. - static const size_t DATA_SIZE = - NUM_ARGS + (IS_PACKED && NUM_ARGS != 0 ? 0 : 1); - value_type data_[DATA_SIZE]; - - friend class basic_format_args<Context>; - - static FMT_CONSTEXPR11 long long get_types() { - return IS_PACKED ? - static_cast<long long>(internal::get_types<Context, Args...>()) : - -static_cast<long long>(NUM_ARGS); - } - - public: -#if FMT_USE_CONSTEXPR11 - static FMT_CONSTEXPR11 long long TYPES = get_types(); -#else - static const long long TYPES; -#endif - -#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 405) || \ - (FMT_MSC_VER && FMT_MSC_VER <= 1800) - // Workaround array initialization issues in gcc <= 4.5 and MSVC <= 2013. - format_arg_store(const Args &... args) { - value_type init[DATA_SIZE] = - {internal::make_arg<IS_PACKED, Context>(args)...}; - std::memcpy(data_, init, sizeof(init)); - } -#else - format_arg_store(const Args &... args) - : data_{internal::make_arg<IS_PACKED, Context>(args)...} {} -#endif -}; - -#if !FMT_USE_CONSTEXPR11 -template <typename Context, typename ...Args> -const long long format_arg_store<Context, Args...>::TYPES = get_types(); -#endif - -/** - \rst - Constructs an `~fmt::format_arg_store` object that contains references to - arguments and can be implicitly converted to `~fmt::format_args`. `Context` - can be omitted in which case it defaults to `~fmt::context`. - \endrst - */ -template <typename Context, typename ...Args> -inline format_arg_store<Context, Args...> - make_format_args(const Args & ... args) { - return format_arg_store<Context, Args...>(args...); -} - -template <typename ...Args> -inline format_arg_store<format_context, Args...> - make_format_args(const Args & ... args) { - return format_arg_store<format_context, Args...>(args...); -} - -/** Formatting arguments. */ -template <typename Context> -class basic_format_args { - public: - typedef unsigned size_type; - typedef basic_format_arg<Context> format_arg; - - private: - // To reduce compiled code size per formatting function call, types of first - // max_packed_args arguments are passed in the types_ field. - unsigned long long types_; - union { - // If the number of arguments is less than max_packed_args, the argument - // values are stored in values_, otherwise they are stored in args_. - // This is done to reduce compiled code size as storing larger objects - // may require more code (at least on x86-64) even if the same amount of - // data is actually copied to stack. It saves ~10% on the bloat test. - const internal::value<Context> *values_; - const format_arg *args_; - }; - - typename internal::type type(unsigned index) const { - unsigned shift = index * 4; - unsigned long long mask = 0xf; - return static_cast<typename internal::type>( - (types_ & (mask << shift)) >> shift); - } - - friend class internal::arg_map<Context>; - - void set_data(const internal::value<Context> *values) { values_ = values; } - void set_data(const format_arg *args) { args_ = args; } - - format_arg do_get(size_type index) const { - format_arg arg; - long long signed_types = static_cast<long long>(types_); - if (signed_types < 0) { - unsigned long long num_args = - static_cast<unsigned long long>(-signed_types); - if (index < num_args) - arg = args_[index]; - return arg; - } - if (index > internal::max_packed_args) - return arg; - arg.type_ = type(index); - if (arg.type_ == internal::none_type) - return arg; - internal::value<Context> &val = arg.value_; - val = values_[index]; - return arg; - } - - public: - basic_format_args() : types_(0) {} - - /** - \rst - Constructs a `basic_format_args` object from `~fmt::format_arg_store`. - \endrst - */ - template <typename... Args> - basic_format_args(const format_arg_store<Context, Args...> &store) - : types_(static_cast<unsigned long long>(store.TYPES)) { - set_data(store.data_); - } - - /** - \rst - Constructs a `basic_format_args` object from a dynamic set of arguments. - \endrst - */ - basic_format_args(const format_arg *args, size_type count) - : types_(-static_cast<int64_t>(count)) { - set_data(args); - } - - /** Returns the argument at specified index. */ - format_arg get(size_type index) const { - format_arg arg = do_get(index); - if (arg.type_ == internal::named_arg_type) - arg = arg.value_.as_named_arg().template deserialize<Context>(); - return arg; - } - - unsigned max_size() const { - long long signed_types = static_cast<long long>(types_); - return static_cast<unsigned>( - signed_types < 0 ? - -signed_types : static_cast<long long>(internal::max_packed_args)); - } -}; - -/** An alias to ``basic_format_args<context>``. */ -// It is a separate type rather than a typedef to make symbols readable. -struct format_args: basic_format_args<format_context> { - template <typename ...Args> - format_args(Args && ... arg) - : basic_format_args<format_context>(std::forward<Args>(arg)...) {} -}; -struct wformat_args : basic_format_args<wformat_context> { - template <typename ...Args> - wformat_args(Args && ... arg) - : basic_format_args<wformat_context>(std::forward<Args>(arg)...) {} -}; - -namespace internal { -template <typename Char> -struct named_arg_base { - basic_string_view<Char> name; - - // Serialized value<context>. - mutable char data[sizeof(basic_format_arg<format_context>)]; - - named_arg_base(basic_string_view<Char> nm) : name(nm) {} - - template <typename Context> - basic_format_arg<Context> deserialize() const { - basic_format_arg<Context> arg; - std::memcpy(&arg, data, sizeof(basic_format_arg<Context>)); - return arg; - } -}; - -template <typename T, typename Char> -struct named_arg : named_arg_base<Char> { - const T &value; - - named_arg(basic_string_view<Char> name, const T &val) - : named_arg_base<Char>(name), value(val) {} -}; -} - -/** - \rst - Returns a named argument to be used in a formatting function. - - **Example**:: - - fmt::print("Elapsed time: {s:.2f} seconds", fmt::arg("s", 1.23)); - \endrst - */ -template <typename T> -inline internal::named_arg<T, char> arg(string_view name, const T &arg) { - return internal::named_arg<T, char>(name, arg); -} - -template <typename T> -inline internal::named_arg<T, wchar_t> arg(wstring_view name, const T &arg) { - return internal::named_arg<T, wchar_t>(name, arg); -} - -// This function template is deleted intentionally to disable nested named -// arguments as in ``format("{}", arg("a", arg("b", 42)))``. -template <typename S, typename T, typename Char> -void arg(S, internal::named_arg<T, Char>) = delete; - -// A base class for compile-time strings. It is defined in the fmt namespace to -// make formatting functions visible via ADL, e.g. format(fmt("{}"), 42). -struct compile_string {}; - -namespace internal { -// If S is a format string type, format_string_traints<S>::char_type gives its -// character type. -template <typename S, typename Enable = void> -struct format_string_traits { - private: - // Use constructability as a way to detect if format_string_traits is - // specialized because other methods are broken on MSVC2013. - format_string_traits(); -}; - -template <typename Char> -struct format_string_traits_base { typedef Char char_type; }; - -template <typename Char> -struct format_string_traits<Char *> : format_string_traits_base<Char> {}; - -template <typename Char> -struct format_string_traits<const Char *> : format_string_traits_base<Char> {}; - -template <typename Char, std::size_t N> -struct format_string_traits<Char[N]> : format_string_traits_base<Char> {}; - -template <typename Char, std::size_t N> -struct format_string_traits<const Char[N]> : format_string_traits_base<Char> {}; - -template <typename Char> -struct format_string_traits<std::basic_string<Char>> : - format_string_traits_base<Char> {}; - -template <typename S> -struct format_string_traits< - S, typename std::enable_if<std::is_base_of< - basic_string_view<typename S::char_type>, S>::value>::type> : - format_string_traits_base<typename S::char_type> {}; - -template <typename S> -struct is_format_string : - std::integral_constant< - bool, std::is_constructible<format_string_traits<S>>::value> {}; - -template <typename S> -struct is_compile_string : - std::integral_constant<bool, std::is_base_of<compile_string, S>::value> {}; - -template <typename... Args, typename S> -inline typename std::enable_if<!is_compile_string<S>::value>::type - check_format_string(const S &) {} -template <typename... Args, typename S> -typename std::enable_if<is_compile_string<S>::value>::type - check_format_string(S); - -template <typename Char> -std::basic_string<Char> vformat( - basic_string_view<Char> format_str, - basic_format_args<typename buffer_context<Char>::type> args); -} // namespace internal - -format_context::iterator vformat_to( - internal::buffer &buf, string_view format_str, format_args args); -wformat_context::iterator vformat_to( - internal::wbuffer &buf, wstring_view format_str, wformat_args args); - -template <typename Container> -struct is_contiguous : std::false_type {}; - -template <typename Char> -struct is_contiguous<std::basic_string<Char>> : std::true_type {}; - -template <typename Char> -struct is_contiguous<internal::basic_buffer<Char>> : std::true_type {}; - -/** Formats a string and writes the output to ``out``. */ -template <typename Container> -typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - vformat_to(std::back_insert_iterator<Container> out, - string_view format_str, format_args args) { - internal::container_buffer<Container> buf(internal::get_container(out)); - vformat_to(buf, format_str, args); - return out; -} - -template <typename Container> -typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - vformat_to(std::back_insert_iterator<Container> out, - wstring_view format_str, wformat_args args) { - internal::container_buffer<Container> buf(internal::get_container(out)); - vformat_to(buf, format_str, args); - return out; -} - -template <typename Container, typename... Args> -inline typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - format_to(std::back_insert_iterator<Container> out, - string_view format_str, const Args & ... args) { - format_arg_store<format_context, Args...> as{args...}; - return vformat_to(out, format_str, as); -} - -template <typename Container, typename... Args> -inline typename std::enable_if< - is_contiguous<Container>::value, std::back_insert_iterator<Container>>::type - format_to(std::back_insert_iterator<Container> out, - wstring_view format_str, const Args & ... args) { - return vformat_to(out, format_str, - make_format_args<wformat_context>(args...)); -} - -template < - typename String, - typename Char = typename internal::format_string_traits<String>::char_type> -inline std::basic_string<Char> vformat( - const String &format_str, - basic_format_args<typename buffer_context<Char>::type> args) { - // Convert format string to string_view to reduce the number of overloads. - return internal::vformat(basic_string_view<Char>(format_str), args); -} - -/** - \rst - Formats arguments and returns the result as a string. - - **Example**:: - - #include <fmt/core.h> - std::string message = fmt::format("The answer is {}", 42); - \endrst -*/ -template <typename String, typename... Args> -inline std::basic_string< - typename internal::format_string_traits<String>::char_type> - format(const String &format_str, const Args & ... args) { - internal::check_format_string<Args...>(format_str); - // This should be just - // return vformat(format_str, make_format_args(args...)); - // but gcc has trouble optimizing the latter, so break it down. - typedef typename internal::format_string_traits<String>::char_type char_t; - typedef typename buffer_context<char_t>::type context_t; - format_arg_store<context_t, Args...> as{args...}; - return internal::vformat( - basic_string_view<char_t>(format_str), basic_format_args<context_t>(as)); -} - -FMT_API void vprint(std::FILE *f, string_view format_str, format_args args); -FMT_API void vprint(std::FILE *f, wstring_view format_str, wformat_args args); - -/** - \rst - Prints formatted data to the file *f*. - - **Example**:: - - fmt::print(stderr, "Don't {}!", "panic"); - \endrst - */ -template <typename... Args> -inline void print(std::FILE *f, string_view format_str, const Args & ... args) { - format_arg_store<format_context, Args...> as(args...); - vprint(f, format_str, as); -} -/** - Prints formatted data to the file *f* which should be in wide-oriented mode - set via ``fwide(f, 1)`` or ``_setmode(_fileno(f), _O_U8TEXT)`` on Windows. - */ -template <typename... Args> -inline void print(std::FILE *f, wstring_view format_str, - const Args & ... args) { - format_arg_store<wformat_context, Args...> as(args...); - vprint(f, format_str, as); -} - -FMT_API void vprint(string_view format_str, format_args args); -FMT_API void vprint(wstring_view format_str, wformat_args args); - -/** - \rst - Prints formatted data to ``stdout``. - - **Example**:: - - fmt::print("Elapsed time: {0:.2f} seconds", 1.23); - \endrst - */ -template <typename... Args> -inline void print(string_view format_str, const Args & ... args) { - format_arg_store<format_context, Args...> as{args...}; - vprint(format_str, as); -} - -template <typename... Args> -inline void print(wstring_view format_str, const Args & ... args) { - format_arg_store<wformat_context, Args...> as(args...); - vprint(format_str, as); -} -FMT_END_NAMESPACE - -#endif // FMT_CORE_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/format-inl.h b/src/nmodl/ext/spdlog/fmt/bundled/format-inl.h deleted file mode 100644 index 56c4d581df..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/format-inl.h +++ /dev/null @@ -1,866 +0,0 @@ -// Formatting library for C++ -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_FORMAT_INL_H_ -#define FMT_FORMAT_INL_H_ - -#include "format.h" - -#include <string.h> - -#include <cctype> -#include <cerrno> -#include <climits> -#include <cmath> -#include <cstdarg> -#include <cstddef> // for std::ptrdiff_t -#include <cstring> // for std::memmove -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -# include <locale> -#endif - -#if FMT_USE_WINDOWS_H -# if !defined(FMT_HEADER_ONLY) && !defined(WIN32_LEAN_AND_MEAN) -# define WIN32_LEAN_AND_MEAN -# endif -# if defined(NOMINMAX) || defined(FMT_WIN_MINMAX) -# include <windows.h> -# else -# define NOMINMAX -# include <windows.h> -# undef NOMINMAX -# endif -#endif - -#if FMT_EXCEPTIONS -# define FMT_TRY try -# define FMT_CATCH(x) catch (x) -#else -# define FMT_TRY if (true) -# define FMT_CATCH(x) if (false) -#endif - -#ifdef _MSC_VER -# pragma warning(push) -# pragma warning(disable: 4127) // conditional expression is constant -# pragma warning(disable: 4702) // unreachable code -// Disable deprecation warning for strerror. The latter is not called but -// MSVC fails to detect it. -# pragma warning(disable: 4996) -#endif - -// Dummy implementations of strerror_r and strerror_s called if corresponding -// system functions are not available. -inline fmt::internal::null<> strerror_r(int, char *, ...) { - return fmt::internal::null<>(); -} -inline fmt::internal::null<> strerror_s(char *, std::size_t, ...) { - return fmt::internal::null<>(); -} - -FMT_BEGIN_NAMESPACE - -namespace { - -#ifndef _MSC_VER -# define FMT_SNPRINTF snprintf -#else // _MSC_VER -inline int fmt_snprintf(char *buffer, size_t size, const char *format, ...) { - va_list args; - va_start(args, format); - int result = vsnprintf_s(buffer, size, _TRUNCATE, format, args); - va_end(args); - return result; -} -# define FMT_SNPRINTF fmt_snprintf -#endif // _MSC_VER - -#if defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) -# define FMT_SWPRINTF snwprintf -#else -# define FMT_SWPRINTF swprintf -#endif // defined(_WIN32) && defined(__MINGW32__) && !defined(__NO_ISOCEXT) - -typedef void (*FormatFunc)(internal::buffer &, int, string_view); - -// Portable thread-safe version of strerror. -// Sets buffer to point to a string describing the error code. -// This can be either a pointer to a string stored in buffer, -// or a pointer to some static immutable string. -// Returns one of the following values: -// 0 - success -// ERANGE - buffer is not large enough to store the error message -// other - failure -// Buffer should be at least of size 1. -int safe_strerror( - int error_code, char *&buffer, std::size_t buffer_size) FMT_NOEXCEPT { - FMT_ASSERT(buffer != FMT_NULL && buffer_size != 0, "invalid buffer"); - - class dispatcher { - private: - int error_code_; - char *&buffer_; - std::size_t buffer_size_; - - // A noop assignment operator to avoid bogus warnings. - void operator=(const dispatcher &) {} - - // Handle the result of XSI-compliant version of strerror_r. - int handle(int result) { - // glibc versions before 2.13 return result in errno. - return result == -1 ? errno : result; - } - - // Handle the result of GNU-specific version of strerror_r. - int handle(char *message) { - // If the buffer is full then the message is probably truncated. - if (message == buffer_ && strlen(buffer_) == buffer_size_ - 1) - return ERANGE; - buffer_ = message; - return 0; - } - - // Handle the case when strerror_r is not available. - int handle(internal::null<>) { - return fallback(strerror_s(buffer_, buffer_size_, error_code_)); - } - - // Fallback to strerror_s when strerror_r is not available. - int fallback(int result) { - // If the buffer is full then the message is probably truncated. - return result == 0 && strlen(buffer_) == buffer_size_ - 1 ? - ERANGE : result; - } - - // Fallback to strerror if strerror_r and strerror_s are not available. - int fallback(internal::null<>) { - errno = 0; - buffer_ = strerror(error_code_); - return errno; - } - - public: - dispatcher(int err_code, char *&buf, std::size_t buf_size) - : error_code_(err_code), buffer_(buf), buffer_size_(buf_size) {} - - int run() { - return handle(strerror_r(error_code_, buffer_, buffer_size_)); - } - }; - return dispatcher(error_code, buffer, buffer_size).run(); -} - -void format_error_code(internal::buffer &out, int error_code, - string_view message) FMT_NOEXCEPT { - // Report error code making sure that the output fits into - // inline_buffer_size to avoid dynamic memory allocation and potential - // bad_alloc. - out.resize(0); - static const char SEP[] = ": "; - static const char ERROR_STR[] = "error "; - // Subtract 2 to account for terminating null characters in SEP and ERROR_STR. - std::size_t error_code_size = sizeof(SEP) + sizeof(ERROR_STR) - 2; - typedef internal::int_traits<int>::main_type main_type; - main_type abs_value = static_cast<main_type>(error_code); - if (internal::is_negative(error_code)) { - abs_value = 0 - abs_value; - ++error_code_size; - } - error_code_size += internal::count_digits(abs_value); - writer w(out); - if (message.size() <= inline_buffer_size - error_code_size) { - w.write(message); - w.write(SEP); - } - w.write(ERROR_STR); - w.write(error_code); - assert(out.size() <= inline_buffer_size); -} - -void report_error(FormatFunc func, int error_code, - string_view message) FMT_NOEXCEPT { - memory_buffer full_message; - func(full_message, error_code, message); - // Use Writer::data instead of Writer::c_str to avoid potential memory - // allocation. - std::fwrite(full_message.data(), full_message.size(), 1, stderr); - std::fputc('\n', stderr); -} -} // namespace - -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -class locale { - private: - std::locale locale_; - - public: - explicit locale(std::locale loc = std::locale()) : locale_(loc) {} - std::locale get() { return locale_; } -}; - -FMT_FUNC size_t internal::count_code_points(u8string_view s) { - const char8_t *data = s.data(); - int num_code_points = 0; - for (size_t i = 0, size = s.size(); i != size; ++i) { - if ((data[i].value & 0xc0) != 0x80) - ++num_code_points; - } - return num_code_points; -} - -template <typename Char> -FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { - std::locale loc = lp ? lp->locale().get() : std::locale(); - return std::use_facet<std::numpunct<Char>>(loc).thousands_sep(); -} -#else -template <typename Char> -FMT_FUNC Char internal::thousands_sep(locale_provider *lp) { - return FMT_STATIC_THOUSANDS_SEPARATOR; -} -#endif - -FMT_FUNC void system_error::init( - int err_code, string_view format_str, format_args args) { - error_code_ = err_code; - memory_buffer buffer; - format_system_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(to_string(buffer)); -} - -namespace internal { -template <typename T> -int char_traits<char>::format_float( - char *buffer, std::size_t size, const char *format, int precision, T value) { - return precision < 0 ? - FMT_SNPRINTF(buffer, size, format, value) : - FMT_SNPRINTF(buffer, size, format, precision, value); -} - -template <typename T> -int char_traits<wchar_t>::format_float( - wchar_t *buffer, std::size_t size, const wchar_t *format, int precision, - T value) { - return precision < 0 ? - FMT_SWPRINTF(buffer, size, format, value) : - FMT_SWPRINTF(buffer, size, format, precision, value); -} - -template <typename T> -const char basic_data<T>::DIGITS[] = - "0001020304050607080910111213141516171819" - "2021222324252627282930313233343536373839" - "4041424344454647484950515253545556575859" - "6061626364656667686970717273747576777879" - "8081828384858687888990919293949596979899"; - -#define FMT_POWERS_OF_10(factor) \ - factor * 10, \ - factor * 100, \ - factor * 1000, \ - factor * 10000, \ - factor * 100000, \ - factor * 1000000, \ - factor * 10000000, \ - factor * 100000000, \ - factor * 1000000000 - -template <typename T> -const uint32_t basic_data<T>::POWERS_OF_10_32[] = { - 1, FMT_POWERS_OF_10(1) -}; - -template <typename T> -const uint32_t basic_data<T>::ZERO_OR_POWERS_OF_10_32[] = { - 0, FMT_POWERS_OF_10(1) -}; - -template <typename T> -const uint64_t basic_data<T>::ZERO_OR_POWERS_OF_10_64[] = { - 0, - FMT_POWERS_OF_10(1), - FMT_POWERS_OF_10(1000000000ull), - 10000000000000000000ull -}; - -// Normalized 64-bit significands of pow(10, k), for k = -348, -340, ..., 340. -// These are generated by support/compute-powers.py. -template <typename T> -const uint64_t basic_data<T>::POW10_SIGNIFICANDS[] = { - 0xfa8fd5a0081c0288, 0xbaaee17fa23ebf76, 0x8b16fb203055ac76, - 0xcf42894a5dce35ea, 0x9a6bb0aa55653b2d, 0xe61acf033d1a45df, - 0xab70fe17c79ac6ca, 0xff77b1fcbebcdc4f, 0xbe5691ef416bd60c, - 0x8dd01fad907ffc3c, 0xd3515c2831559a83, 0x9d71ac8fada6c9b5, - 0xea9c227723ee8bcb, 0xaecc49914078536d, 0x823c12795db6ce57, - 0xc21094364dfb5637, 0x9096ea6f3848984f, 0xd77485cb25823ac7, - 0xa086cfcd97bf97f4, 0xef340a98172aace5, 0xb23867fb2a35b28e, - 0x84c8d4dfd2c63f3b, 0xc5dd44271ad3cdba, 0x936b9fcebb25c996, - 0xdbac6c247d62a584, 0xa3ab66580d5fdaf6, 0xf3e2f893dec3f126, - 0xb5b5ada8aaff80b8, 0x87625f056c7c4a8b, 0xc9bcff6034c13053, - 0x964e858c91ba2655, 0xdff9772470297ebd, 0xa6dfbd9fb8e5b88f, - 0xf8a95fcf88747d94, 0xb94470938fa89bcf, 0x8a08f0f8bf0f156b, - 0xcdb02555653131b6, 0x993fe2c6d07b7fac, 0xe45c10c42a2b3b06, - 0xaa242499697392d3, 0xfd87b5f28300ca0e, 0xbce5086492111aeb, - 0x8cbccc096f5088cc, 0xd1b71758e219652c, 0x9c40000000000000, - 0xe8d4a51000000000, 0xad78ebc5ac620000, 0x813f3978f8940984, - 0xc097ce7bc90715b3, 0x8f7e32ce7bea5c70, 0xd5d238a4abe98068, - 0x9f4f2726179a2245, 0xed63a231d4c4fb27, 0xb0de65388cc8ada8, - 0x83c7088e1aab65db, 0xc45d1df942711d9a, 0x924d692ca61be758, - 0xda01ee641a708dea, 0xa26da3999aef774a, 0xf209787bb47d6b85, - 0xb454e4a179dd1877, 0x865b86925b9bc5c2, 0xc83553c5c8965d3d, - 0x952ab45cfa97a0b3, 0xde469fbd99a05fe3, 0xa59bc234db398c25, - 0xf6c69a72a3989f5c, 0xb7dcbf5354e9bece, 0x88fcf317f22241e2, - 0xcc20ce9bd35c78a5, 0x98165af37b2153df, 0xe2a0b5dc971f303a, - 0xa8d9d1535ce3b396, 0xfb9b7cd9a4a7443c, 0xbb764c4ca7a44410, - 0x8bab8eefb6409c1a, 0xd01fef10a657842c, 0x9b10a4e5e9913129, - 0xe7109bfba19c0c9d, 0xac2820d9623bf429, 0x80444b5e7aa7cf85, - 0xbf21e44003acdd2d, 0x8e679c2f5e44ff8f, 0xd433179d9c8cb841, - 0x9e19db92b4e31ba9, 0xeb96bf6ebadf77d9, 0xaf87023b9bf0ee6b, -}; - -// Binary exponents of pow(10, k), for k = -348, -340, ..., 340, corresponding -// to significands above. -template <typename T> -const int16_t basic_data<T>::POW10_EXPONENTS[] = { - -1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980, -954, - -927, -901, -874, -847, -821, -794, -768, -741, -715, -688, -661, - -635, -608, -582, -555, -529, -502, -475, -449, -422, -396, -369, - -343, -316, -289, -263, -236, -210, -183, -157, -130, -103, -77, - -50, -24, 3, 30, 56, 83, 109, 136, 162, 189, 216, - 242, 269, 295, 322, 348, 375, 402, 428, 455, 481, 508, - 534, 561, 588, 614, 641, 667, 694, 720, 747, 774, 800, - 827, 853, 880, 907, 933, 960, 986, 1013, 1039, 1066 -}; - -template <typename T> const char basic_data<T>::RESET_COLOR[] = "\x1b[0m"; -template <typename T> const wchar_t basic_data<T>::WRESET_COLOR[] = L"\x1b[0m"; - -// A handmade floating-point number f * pow(2, e). -class fp { - private: - typedef uint64_t significand_type; - - // All sizes are in bits. - static FMT_CONSTEXPR_DECL const int char_size = - std::numeric_limits<unsigned char>::digits; - // Subtract 1 to account for an implicit most significant bit in the - // normalized form. - static FMT_CONSTEXPR_DECL const int double_significand_size = - std::numeric_limits<double>::digits - 1; - static FMT_CONSTEXPR_DECL const uint64_t implicit_bit = - 1ull << double_significand_size; - - public: - significand_type f; - int e; - - static FMT_CONSTEXPR_DECL const int significand_size = - sizeof(significand_type) * char_size; - - fp(): f(0), e(0) {} - fp(uint64_t f, int e): f(f), e(e) {} - - // Constructs fp from an IEEE754 double. It is a template to prevent compile - // errors on platforms where double is not IEEE754. - template <typename Double> - explicit fp(Double d) { - // Assume double is in the format [sign][exponent][significand]. - typedef std::numeric_limits<Double> limits; - const int double_size = static_cast<int>(sizeof(Double) * char_size); - const int exponent_size = - double_size - double_significand_size - 1; // -1 for sign - const uint64_t significand_mask = implicit_bit - 1; - const uint64_t exponent_mask = (~0ull >> 1) & ~significand_mask; - const int exponent_bias = (1 << exponent_size) - limits::max_exponent - 1; - auto u = bit_cast<uint64_t>(d); - auto biased_e = (u & exponent_mask) >> double_significand_size; - f = u & significand_mask; - if (biased_e != 0) - f += implicit_bit; - else - biased_e = 1; // Subnormals use biased exponent 1 (min exponent). - e = static_cast<int>(biased_e - exponent_bias - double_significand_size); - } - - // Normalizes the value converted from double and multiplied by (1 << SHIFT). - template <int SHIFT = 0> - void normalize() { - // Handle subnormals. - auto shifted_implicit_bit = implicit_bit << SHIFT; - while ((f & shifted_implicit_bit) == 0) { - f <<= 1; - --e; - } - // Subtract 1 to account for hidden bit. - auto offset = significand_size - double_significand_size - SHIFT - 1; - f <<= offset; - e -= offset; - } - - // Compute lower and upper boundaries (m^- and m^+ in the Grisu paper), where - // a boundary is a value half way between the number and its predecessor - // (lower) or successor (upper). The upper boundary is normalized and lower - // has the same exponent but may be not normalized. - void compute_boundaries(fp &lower, fp &upper) const { - lower = f == implicit_bit ? - fp((f << 2) - 1, e - 2) : fp((f << 1) - 1, e - 1); - upper = fp((f << 1) + 1, e - 1); - upper.normalize<1>(); // 1 is to account for the exponent shift above. - lower.f <<= lower.e - upper.e; - lower.e = upper.e; - } -}; - -// Returns an fp number representing x - y. Result may not be normalized. -inline fp operator-(fp x, fp y) { - FMT_ASSERT(x.f >= y.f && x.e == y.e, "invalid operands"); - return fp(x.f - y.f, x.e); -} - -// Computes an fp number r with r.f = x.f * y.f / pow(2, 64) rounded to nearest -// with half-up tie breaking, r.e = x.e + y.e + 64. Result may not be normalized. -FMT_API fp operator*(fp x, fp y); - -// Returns cached power (of 10) c_k = c_k.f * pow(2, c_k.e) such that its -// (binary) exponent satisfies min_exponent <= c_k.e <= min_exponent + 3. -FMT_API fp get_cached_power(int min_exponent, int &pow10_exponent); - -FMT_FUNC fp operator*(fp x, fp y) { - // Multiply 32-bit parts of significands. - uint64_t mask = (1ULL << 32) - 1; - uint64_t a = x.f >> 32, b = x.f & mask; - uint64_t c = y.f >> 32, d = y.f & mask; - uint64_t ac = a * c, bc = b * c, ad = a * d, bd = b * d; - // Compute mid 64-bit of result and round. - uint64_t mid = (bd >> 32) + (ad & mask) + (bc & mask) + (1U << 31); - return fp(ac + (ad >> 32) + (bc >> 32) + (mid >> 32), x.e + y.e + 64); -} - -FMT_FUNC fp get_cached_power(int min_exponent, int &pow10_exponent) { - const double one_over_log2_10 = 0.30102999566398114; // 1 / log2(10) - int index = static_cast<int>(std::ceil( - (min_exponent + fp::significand_size - 1) * one_over_log2_10)); - // Decimal exponent of the first (smallest) cached power of 10. - const int first_dec_exp = -348; - // Difference between 2 consecutive decimal exponents in cached powers of 10. - const int dec_exp_step = 8; - index = (index - first_dec_exp - 1) / dec_exp_step + 1; - pow10_exponent = first_dec_exp + index * dec_exp_step; - return fp(data::POW10_SIGNIFICANDS[index], data::POW10_EXPONENTS[index]); -} - -// Generates output using Grisu2 digit-gen algorithm. -FMT_FUNC void grisu2_gen_digits( - const fp &scaled_value, const fp &scaled_upper, uint64_t delta, - char *buffer, size_t &size, int &dec_exp) { - internal::fp one(1ull << -scaled_upper.e, scaled_upper.e); - // hi (p1 in Grisu) contains the most significant digits of scaled_upper. - // hi = floor(scaled_upper / one). - uint32_t hi = static_cast<uint32_t>(scaled_upper.f >> -one.e); - // lo (p2 in Grisu) contains the least significants digits of scaled_upper. - // lo = scaled_upper mod 1. - uint64_t lo = scaled_upper.f & (one.f - 1); - size = 0; - auto exp = count_digits(hi); // kappa in Grisu. - while (exp > 0) { - uint32_t digit = 0; - // This optimization by miloyip reduces the number of integer divisions by - // one per iteration. - switch (exp) { - case 10: digit = hi / 1000000000; hi %= 1000000000; break; - case 9: digit = hi / 100000000; hi %= 100000000; break; - case 8: digit = hi / 10000000; hi %= 10000000; break; - case 7: digit = hi / 1000000; hi %= 1000000; break; - case 6: digit = hi / 100000; hi %= 100000; break; - case 5: digit = hi / 10000; hi %= 10000; break; - case 4: digit = hi / 1000; hi %= 1000; break; - case 3: digit = hi / 100; hi %= 100; break; - case 2: digit = hi / 10; hi %= 10; break; - case 1: digit = hi; hi = 0; break; - default: - FMT_ASSERT(false, "invalid number of digits"); - } - if (digit != 0 || size != 0) - buffer[size++] = static_cast<char>('0' + digit); - --exp; - uint64_t remainder = (static_cast<uint64_t>(hi) << -one.e) + lo; - if (remainder <= delta) { - dec_exp += exp; - // TODO: use scaled_value - (void)scaled_value; - return; - } - } - for (;;) { - lo *= 10; - delta *= 10; - char digit = static_cast<char>(lo >> -one.e); - if (digit != 0 || size != 0) - buffer[size++] = static_cast<char>('0' + digit); - lo &= one.f - 1; - --exp; - if (lo < delta) { - dec_exp += exp; - return; - } - } -} - -FMT_FUNC void grisu2_format_positive(double value, char *buffer, size_t &size, - int &dec_exp) { - FMT_ASSERT(value > 0, "value is nonpositive"); - fp fp_value(value); - fp lower, upper; // w^- and w^+ in the Grisu paper. - fp_value.compute_boundaries(lower, upper); - // Find a cached power of 10 close to 1 / upper. - const int min_exp = -60; // alpha in Grisu. - auto dec_pow = get_cached_power( // \tilde{c}_{-k} in Grisu. - min_exp - (upper.e + fp::significand_size), dec_exp); - dec_exp = -dec_exp; - fp_value.normalize(); - fp scaled_value = fp_value * dec_pow; - fp scaled_lower = lower * dec_pow; // \tilde{M}^- in Grisu. - fp scaled_upper = upper * dec_pow; // \tilde{M}^+ in Grisu. - ++scaled_lower.f; // \tilde{M}^- + 1 ulp -> M^-_{\uparrow}. - --scaled_upper.f; // \tilde{M}^+ - 1 ulp -> M^+_{\downarrow}. - uint64_t delta = scaled_upper.f - scaled_lower.f; - grisu2_gen_digits(scaled_value, scaled_upper, delta, buffer, size, dec_exp); -} - -FMT_FUNC void round(char *buffer, size_t &size, int &exp, - int digits_to_remove) { - size -= to_unsigned(digits_to_remove); - exp += digits_to_remove; - int digit = buffer[size] - '0'; - // TODO: proper rounding and carry - if (digit > 5 || (digit == 5 && (digits_to_remove > 1 || - (buffer[size - 1] - '0') % 2) != 0)) { - ++buffer[size - 1]; - } -} - -// Writes the exponent exp in the form "[+-]d{1,3}" to buffer. -FMT_FUNC char *write_exponent(char *buffer, int exp) { - FMT_ASSERT(-1000 < exp && exp < 1000, "exponent out of range"); - if (exp < 0) { - *buffer++ = '-'; - exp = -exp; - } else { - *buffer++ = '+'; - } - if (exp >= 100) { - *buffer++ = static_cast<char>('0' + exp / 100); - exp %= 100; - const char *d = data::DIGITS + exp * 2; - *buffer++ = d[0]; - *buffer++ = d[1]; - } else { - const char *d = data::DIGITS + exp * 2; - *buffer++ = d[0]; - *buffer++ = d[1]; - } - return buffer; -} - -FMT_FUNC void format_exp_notation( - char *buffer, size_t &size, int exp, int precision, bool upper) { - // Insert a decimal point after the first digit and add an exponent. - std::memmove(buffer + 2, buffer + 1, size - 1); - buffer[1] = '.'; - exp += static_cast<int>(size) - 1; - int num_digits = precision - static_cast<int>(size) + 1; - if (num_digits > 0) { - std::uninitialized_fill_n(buffer + size + 1, num_digits, '0'); - size += to_unsigned(num_digits); - } else if (num_digits < 0) { - round(buffer, size, exp, -num_digits); - } - char *p = buffer + size + 1; - *p++ = upper ? 'E' : 'e'; - size = to_unsigned(write_exponent(p, exp) - buffer); -} - -// Prettifies the output of the Grisu2 algorithm. -// The number is given as v = buffer * 10^exp. -FMT_FUNC void grisu2_prettify(char *buffer, size_t &size, int exp, - int precision, bool upper) { - // pow(10, full_exp - 1) <= v <= pow(10, full_exp). - int int_size = static_cast<int>(size); - int full_exp = int_size + exp; - const int exp_threshold = 21; - if (int_size <= full_exp && full_exp <= exp_threshold) { - // 1234e7 -> 12340000000[.0+] - std::uninitialized_fill_n(buffer + int_size, full_exp - int_size, '0'); - char *p = buffer + full_exp; - if (precision > 0) { - *p++ = '.'; - std::uninitialized_fill_n(p, precision, '0'); - p += precision; - } - size = to_unsigned(p - buffer); - } else if (0 < full_exp && full_exp <= exp_threshold) { - // 1234e-2 -> 12.34[0+] - int fractional_size = -exp; - std::memmove(buffer + full_exp + 1, buffer + full_exp, - to_unsigned(fractional_size)); - buffer[full_exp] = '.'; - int num_zeros = precision - fractional_size; - if (num_zeros > 0) { - std::uninitialized_fill_n(buffer + size + 1, num_zeros, '0'); - size += to_unsigned(num_zeros); - } - ++size; - } else if (-6 < full_exp && full_exp <= 0) { - // 1234e-6 -> 0.001234 - int offset = 2 - full_exp; - std::memmove(buffer + offset, buffer, size); - buffer[0] = '0'; - buffer[1] = '.'; - std::uninitialized_fill_n(buffer + 2, -full_exp, '0'); - size = to_unsigned(int_size + offset); - } else { - format_exp_notation(buffer, size, exp, precision, upper); - } -} - -#if FMT_CLANG_VERSION -# define FMT_FALLTHROUGH [[clang::fallthrough]]; -#elif FMT_GCC_VERSION >= 700 -# define FMT_FALLTHROUGH [[gnu::fallthrough]]; -#else -# define FMT_FALLTHROUGH -#endif - -// Formats a nonnegative value using Grisu2 algorithm. Grisu2 doesn't give any -// guarantees on the shortness of the result. -FMT_FUNC void grisu2_format(double value, char *buffer, size_t &size, char type, - int precision, bool write_decimal_point) { - FMT_ASSERT(value >= 0, "value is negative"); - int dec_exp = 0; // K in Grisu. - if (value > 0) { - grisu2_format_positive(value, buffer, size, dec_exp); - } else { - *buffer = '0'; - size = 1; - } - const int default_precision = 6; - if (precision < 0) - precision = default_precision; - bool upper = false; - switch (type) { - case 'G': - upper = true; - FMT_FALLTHROUGH - case '\0': case 'g': { - int digits_to_remove = static_cast<int>(size) - precision; - if (digits_to_remove > 0) { - round(buffer, size, dec_exp, digits_to_remove); - // Remove trailing zeros. - while (size > 0 && buffer[size - 1] == '0') { - --size; - ++dec_exp; - } - } - precision = 0; - break; - } - case 'F': - upper = true; - FMT_FALLTHROUGH - case 'f': { - int digits_to_remove = -dec_exp - precision; - if (digits_to_remove > 0) { - if (digits_to_remove >= static_cast<int>(size)) - digits_to_remove = static_cast<int>(size) - 1; - round(buffer, size, dec_exp, digits_to_remove); - } - break; - } - case 'e': case 'E': - format_exp_notation(buffer, size, dec_exp, precision, type == 'E'); - return; - } - if (write_decimal_point && precision < 1) - precision = 1; - grisu2_prettify(buffer, size, dec_exp, precision, upper); -} -} // namespace internal - -#if FMT_USE_WINDOWS_H - -FMT_FUNC internal::utf8_to_utf16::utf8_to_utf16(string_view s) { - static const char ERROR_MSG[] = "cannot convert string from UTF-8 to UTF-16"; - if (s.size() > INT_MAX) - FMT_THROW(windows_error(ERROR_INVALID_PARAMETER, ERROR_MSG)); - int s_size = static_cast<int>(s.size()); - if (s_size == 0) { - // MultiByteToWideChar does not support zero length, handle separately. - buffer_.resize(1); - buffer_[0] = 0; - return; - } - - int length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, FMT_NULL, 0); - if (length == 0) - FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); - buffer_.resize(length + 1); - length = MultiByteToWideChar( - CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), s_size, &buffer_[0], length); - if (length == 0) - FMT_THROW(windows_error(GetLastError(), ERROR_MSG)); - buffer_[length] = 0; -} - -FMT_FUNC internal::utf16_to_utf8::utf16_to_utf8(wstring_view s) { - if (int error_code = convert(s)) { - FMT_THROW(windows_error(error_code, - "cannot convert string from UTF-16 to UTF-8")); - } -} - -FMT_FUNC int internal::utf16_to_utf8::convert(wstring_view s) { - if (s.size() > INT_MAX) - return ERROR_INVALID_PARAMETER; - int s_size = static_cast<int>(s.size()); - if (s_size == 0) { - // WideCharToMultiByte does not support zero length, handle separately. - buffer_.resize(1); - buffer_[0] = 0; - return 0; - } - - int length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, FMT_NULL, 0, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_.resize(length + 1); - length = WideCharToMultiByte( - CP_UTF8, 0, s.data(), s_size, &buffer_[0], length, FMT_NULL, FMT_NULL); - if (length == 0) - return GetLastError(); - buffer_[length] = 0; - return 0; -} - -FMT_FUNC void windows_error::init( - int err_code, string_view format_str, format_args args) { - error_code_ = err_code; - memory_buffer buffer; - internal::format_windows_error(buffer, err_code, vformat(format_str, args)); - std::runtime_error &base = *this; - base = std::runtime_error(to_string(buffer)); -} - -FMT_FUNC void internal::format_windows_error( - internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { - FMT_TRY { - wmemory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - wchar_t *system_message = &buf[0]; - int result = FormatMessageW( - FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - FMT_NULL, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - system_message, static_cast<uint32_t>(buf.size()), FMT_NULL); - if (result != 0) { - utf16_to_utf8 utf8_message; - if (utf8_message.convert(system_message) == ERROR_SUCCESS) { - writer w(out); - w.write(message); - w.write(": "); - w.write(utf8_message); - return; - } - break; - } - if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } - } FMT_CATCH(...) {} - format_error_code(out, error_code, message); -} - -#endif // FMT_USE_WINDOWS_H - -FMT_FUNC void format_system_error( - internal::buffer &out, int error_code, string_view message) FMT_NOEXCEPT { - FMT_TRY { - memory_buffer buf; - buf.resize(inline_buffer_size); - for (;;) { - char *system_message = &buf[0]; - int result = safe_strerror(error_code, system_message, buf.size()); - if (result == 0) { - writer w(out); - w.write(message); - w.write(": "); - w.write(system_message); - return; - } - if (result != ERANGE) - break; // Can't get error message, report error code instead. - buf.resize(buf.size() * 2); - } - } FMT_CATCH(...) {} - format_error_code(out, error_code, message); -} - -template <typename Char> -void basic_fixed_buffer<Char>::grow(std::size_t) { - FMT_THROW(std::runtime_error("buffer overflow")); -} - -FMT_FUNC void internal::error_handler::on_error(const char *message) { - FMT_THROW(format_error(message)); -} - -FMT_FUNC void report_system_error( - int error_code, fmt::string_view message) FMT_NOEXCEPT { - report_error(format_system_error, error_code, message); -} - -#if FMT_USE_WINDOWS_H -FMT_FUNC void report_windows_error( - int error_code, fmt::string_view message) FMT_NOEXCEPT { - report_error(internal::format_windows_error, error_code, message); -} -#endif - -FMT_FUNC void vprint(std::FILE *f, string_view format_str, format_args args) { - memory_buffer buffer; - vformat_to(buffer, format_str, args); - std::fwrite(buffer.data(), 1, buffer.size(), f); -} - -FMT_FUNC void vprint(std::FILE *f, wstring_view format_str, wformat_args args) { - wmemory_buffer buffer; - vformat_to(buffer, format_str, args); - std::fwrite(buffer.data(), sizeof(wchar_t), buffer.size(), f); -} - -FMT_FUNC void vprint(string_view format_str, format_args args) { - vprint(stdout, format_str, args); -} - -FMT_FUNC void vprint(wstring_view format_str, wformat_args args) { - vprint(stdout, format_str, args); -} - -#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR) -FMT_FUNC locale locale_provider::locale() { return fmt::locale(); } -#endif - -FMT_END_NAMESPACE - -#ifdef _MSC_VER -# pragma warning(pop) -#endif - -#endif // FMT_FORMAT_INL_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/format.h b/src/nmodl/ext/spdlog/fmt/bundled/format.h deleted file mode 100644 index 9f522f39b7..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/format.h +++ /dev/null @@ -1,3720 +0,0 @@ -/* - Formatting library for C++ - - Copyright (c) 2012 - present, Victor Zverovich - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef FMT_FORMAT_H_ -#define FMT_FORMAT_H_ - -#include <algorithm> -#include <cassert> -#include <cmath> -#include <cstring> -#include <limits> -#include <memory> -#include <stdexcept> -#include <stdint.h> - -#ifdef __clang__ -# define FMT_CLANG_VERSION (__clang_major__ * 100 + __clang_minor__) -#else -# define FMT_CLANG_VERSION 0 -#endif - -#ifdef __INTEL_COMPILER -# define FMT_ICC_VERSION __INTEL_COMPILER -#elif defined(__ICL) -# define FMT_ICC_VERSION __ICL -#else -# define FMT_ICC_VERSION 0 -#endif - -#ifdef __NVCC__ -# define FMT_CUDA_VERSION (__CUDACC_VER_MAJOR__ * 100 + __CUDACC_VER_MINOR__) -#else -# define FMT_CUDA_VERSION 0 -#endif - -#include "core.h" - -#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION -# pragma GCC diagnostic push - -// Disable the warning about declaration shadowing because it affects too -// many valid cases. -# pragma GCC diagnostic ignored "-Wshadow" - -// Disable the warning about implicit conversions that may change the sign of -// an integer; silencing it otherwise would require many explicit casts. -# pragma GCC diagnostic ignored "-Wsign-conversion" -#endif - -# if FMT_CLANG_VERSION -# pragma GCC diagnostic ignored "-Wgnu-string-literal-operator-template" -# endif - -#ifdef _SECURE_SCL -# define FMT_SECURE_SCL _SECURE_SCL -#else -# define FMT_SECURE_SCL 0 -#endif - -#if FMT_SECURE_SCL -# include <iterator> -#endif - -#ifdef __has_builtin -# define FMT_HAS_BUILTIN(x) __has_builtin(x) -#else -# define FMT_HAS_BUILTIN(x) 0 -#endif - -#ifdef __GNUC_LIBSTD__ -# define FMT_GNUC_LIBSTD_VERSION (__GNUC_LIBSTD__ * 100 + __GNUC_LIBSTD_MINOR__) -#endif - -#ifndef FMT_THROW -# if FMT_EXCEPTIONS -# if FMT_MSC_VER -FMT_BEGIN_NAMESPACE -namespace internal { -template <typename Exception> -inline void do_throw(const Exception &x) { - // Silence unreachable code warnings in MSVC because these are nearly - // impossible to fix in a generic code. - volatile bool b = true; - if (b) - throw x; -} -} -FMT_END_NAMESPACE -# define FMT_THROW(x) fmt::internal::do_throw(x) -# else -# define FMT_THROW(x) throw x -# endif -# else -# define FMT_THROW(x) do { static_cast<void>(sizeof(x)); assert(false); } while(false); -# endif -#endif - -#ifndef FMT_USE_USER_DEFINED_LITERALS -// For Intel's compiler and NVIDIA's compiler both it and the system gcc/msc -// must support UDLs. -# if (FMT_HAS_FEATURE(cxx_user_literals) || \ - FMT_GCC_VERSION >= 407 || FMT_MSC_VER >= 1900) && \ - (!(FMT_ICC_VERSION || FMT_CUDA_VERSION) || \ - FMT_ICC_VERSION >= 1500 || FMT_CUDA_VERSION >= 700) -# define FMT_USE_USER_DEFINED_LITERALS 1 -# else -# define FMT_USE_USER_DEFINED_LITERALS 0 -# endif -#endif - -// EDG C++ Front End based compilers (icc, nvcc) do not currently support UDL -// templates. -#if FMT_USE_USER_DEFINED_LITERALS && \ - FMT_ICC_VERSION == 0 && \ - FMT_CUDA_VERSION == 0 && \ - ((FMT_GCC_VERSION >= 600 && __cplusplus >= 201402L) || \ - (defined(FMT_CLANG_VERSION) && FMT_CLANG_VERSION >= 304)) -# define FMT_UDL_TEMPLATE 1 -#else -# define FMT_UDL_TEMPLATE 0 -#endif - -#ifndef FMT_USE_EXTERN_TEMPLATES -# ifndef FMT_HEADER_ONLY -# define FMT_USE_EXTERN_TEMPLATES \ - ((FMT_CLANG_VERSION >= 209 && __cplusplus >= 201103L) || \ - (FMT_GCC_VERSION >= 303 && FMT_HAS_GXX_CXX11)) -# else -# define FMT_USE_EXTERN_TEMPLATES 0 -# endif -#endif - -#if FMT_HAS_GXX_CXX11 || FMT_HAS_FEATURE(cxx_trailing_return) || \ - FMT_MSC_VER >= 1600 -# define FMT_USE_TRAILING_RETURN 1 -#else -# define FMT_USE_TRAILING_RETURN 0 -#endif - -#ifndef FMT_USE_GRISU -# define FMT_USE_GRISU 0 -#endif - -// __builtin_clz is broken in clang with Microsoft CodeGen: -// https://github.com/fmtlib/fmt/issues/519 -#ifndef _MSC_VER -# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clz) -# define FMT_BUILTIN_CLZ(n) __builtin_clz(n) -# endif - -# if FMT_GCC_VERSION >= 400 || FMT_HAS_BUILTIN(__builtin_clzll) -# define FMT_BUILTIN_CLZLL(n) __builtin_clzll(n) -# endif -#endif - -// A workaround for gcc 4.4 that doesn't support union members with ctors. -#if (FMT_GCC_VERSION && FMT_GCC_VERSION <= 404) || \ - (FMT_MSC_VER && FMT_MSC_VER <= 1800) -# define FMT_UNION struct -#else -# define FMT_UNION union -#endif - -// Some compilers masquerade as both MSVC and GCC-likes or otherwise support -// __builtin_clz and __builtin_clzll, so only define FMT_BUILTIN_CLZ using the -// MSVC intrinsics if the clz and clzll builtins are not available. -#if FMT_MSC_VER && !defined(FMT_BUILTIN_CLZLL) && !defined(_MANAGED) -# include <intrin.h> // _BitScanReverse, _BitScanReverse64 - -FMT_BEGIN_NAMESPACE -namespace internal { -// Avoid Clang with Microsoft CodeGen's -Wunknown-pragmas warning. -# ifndef __clang__ -# pragma intrinsic(_BitScanReverse) -# endif -inline uint32_t clz(uint32_t x) { - unsigned long r = 0; - _BitScanReverse(&r, x); - - assert(x != 0); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. -# pragma warning(suppress: 6102) - return 31 - r; -} -# define FMT_BUILTIN_CLZ(n) fmt::internal::clz(n) - -# if defined(_WIN64) && !defined(__clang__) -# pragma intrinsic(_BitScanReverse64) -# endif - -inline uint32_t clzll(uint64_t x) { - unsigned long r = 0; -# ifdef _WIN64 - _BitScanReverse64(&r, x); -# else - // Scan the high 32 bits. - if (_BitScanReverse(&r, static_cast<uint32_t>(x >> 32))) - return 63 - (r + 32); - - // Scan the low 32 bits. - _BitScanReverse(&r, static_cast<uint32_t>(x)); -# endif - - assert(x != 0); - // Static analysis complains about using uninitialized data - // "r", but the only way that can happen is if "x" is 0, - // which the callers guarantee to not happen. -# pragma warning(suppress: 6102) - return 63 - r; -} -# define FMT_BUILTIN_CLZLL(n) fmt::internal::clzll(n) -} -FMT_END_NAMESPACE -#endif - -FMT_BEGIN_NAMESPACE -namespace internal { - -// An equivalent of `*reinterpret_cast<Dest*>(&source)` that doesn't produce -// undefined behavior (e.g. due to type aliasing). -// Example: uint64_t d = bit_cast<uint64_t>(2.718); -template <typename Dest, typename Source> -inline Dest bit_cast(const Source& source) { - static_assert(sizeof(Dest) == sizeof(Source), "size mismatch"); - Dest dest; - std::memcpy(&dest, &source, sizeof(dest)); - return dest; -} - -// An implementation of begin and end for pre-C++11 compilers such as gcc 4. -template <typename C> -FMT_CONSTEXPR auto begin(const C &c) -> decltype(c.begin()) { - return c.begin(); -} -template <typename T, std::size_t N> -FMT_CONSTEXPR T *begin(T (&array)[N]) FMT_NOEXCEPT { return array; } -template <typename C> -FMT_CONSTEXPR auto end(const C &c) -> decltype(c.end()) { return c.end(); } -template <typename T, std::size_t N> -FMT_CONSTEXPR T *end(T (&array)[N]) FMT_NOEXCEPT { return array + N; } - -// For std::result_of in gcc 4.4. -template <typename Result> -struct function { - template <typename T> - struct result { typedef Result type; }; -}; - -struct dummy_int { - int data[2]; - operator int() const { return 0; } -}; -typedef std::numeric_limits<internal::dummy_int> fputil; - -// Dummy implementations of system functions such as signbit and ecvt called -// if the latter are not available. -inline dummy_int signbit(...) { return dummy_int(); } -inline dummy_int _ecvt_s(...) { return dummy_int(); } -inline dummy_int isinf(...) { return dummy_int(); } -inline dummy_int _finite(...) { return dummy_int(); } -inline dummy_int isnan(...) { return dummy_int(); } -inline dummy_int _isnan(...) { return dummy_int(); } - -inline bool use_grisu() { - return FMT_USE_GRISU && std::numeric_limits<double>::is_iec559; -} - -// Formats value using Grisu2 algorithm: -// https://www.cs.tufts.edu/~nr/cs257/archive/florian-loitsch/printf.pdf -FMT_API void grisu2_format(double value, char *buffer, size_t &size, char type, - int precision, bool write_decimal_point); - -template <typename Allocator> -typename Allocator::value_type *allocate(Allocator& alloc, std::size_t n) { -#if __cplusplus >= 201103L || FMT_MSC_VER >= 1700 - return std::allocator_traits<Allocator>::allocate(alloc, n); -#else - return alloc.allocate(n); -#endif -} - -// A helper function to suppress bogus "conditional expression is constant" -// warnings. -template <typename T> -inline T const_check(T value) { return value; } -} // namespace internal -FMT_END_NAMESPACE - -namespace std { -// Standard permits specialization of std::numeric_limits. This specialization -// is used to resolve ambiguity between isinf and std::isinf in glibc: -// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=48891 -// and the same for isnan and signbit. -template <> -class numeric_limits<fmt::internal::dummy_int> : - public std::numeric_limits<int> { - public: - // Portable version of isinf. - template <typename T> - static bool isinfinity(T x) { - using namespace fmt::internal; - // The resolution "priority" is: - // isinf macro > std::isinf > ::isinf > fmt::internal::isinf - if (const_check(sizeof(isinf(x)) != sizeof(dummy_int))) - return isinf(x) != 0; - return !_finite(static_cast<double>(x)); - } - - // Portable version of isnan. - template <typename T> - static bool isnotanumber(T x) { - using namespace fmt::internal; - if (const_check(sizeof(isnan(x)) != sizeof(fmt::internal::dummy_int))) - return isnan(x) != 0; - return _isnan(static_cast<double>(x)) != 0; - } - - // Portable version of signbit. - static bool isnegative(double x) { - using namespace fmt::internal; - if (const_check(sizeof(signbit(x)) != sizeof(fmt::internal::dummy_int))) - return signbit(x) != 0; - if (x < 0) return true; - if (!isnotanumber(x)) return false; - int dec = 0, sign = 0; - char buffer[2]; // The buffer size must be >= 2 or _ecvt_s will fail. - _ecvt_s(buffer, sizeof(buffer), x, 0, &dec, &sign); - return sign != 0; - } -}; -} // namespace std - -FMT_BEGIN_NAMESPACE -template <typename Range> -class basic_writer; - -template <typename OutputIt, typename T = typename OutputIt::value_type> -class output_range { - private: - OutputIt it_; - - // Unused yet. - typedef void sentinel; - sentinel end() const; - - public: - typedef OutputIt iterator; - typedef T value_type; - - explicit output_range(OutputIt it): it_(it) {} - OutputIt begin() const { return it_; } -}; - -// A range where begin() returns back_insert_iterator. -template <typename Container> -class back_insert_range: - public output_range<std::back_insert_iterator<Container>> { - typedef output_range<std::back_insert_iterator<Container>> base; - public: - typedef typename Container::value_type value_type; - - back_insert_range(Container &c): base(std::back_inserter(c)) {} - back_insert_range(typename base::iterator it): base(it) {} -}; - -typedef basic_writer<back_insert_range<internal::buffer>> writer; -typedef basic_writer<back_insert_range<internal::wbuffer>> wwriter; - -/** A formatting error such as invalid format string. */ -class format_error : public std::runtime_error { - public: - explicit format_error(const char *message) - : std::runtime_error(message) {} - - explicit format_error(const std::string &message) - : std::runtime_error(message) {} -}; - -namespace internal { - -#if FMT_SECURE_SCL -template <typename T> -struct checked { typedef stdext::checked_array_iterator<T*> type; }; - -// Make a checked iterator to avoid warnings on MSVC. -template <typename T> -inline stdext::checked_array_iterator<T*> make_checked(T *p, std::size_t size) { - return {p, size}; -} -#else -template <typename T> -struct checked { typedef T *type; }; -template <typename T> -inline T *make_checked(T *p, std::size_t) { return p; } -#endif - -template <typename T> -template <typename U> -void basic_buffer<T>::append(const U *begin, const U *end) { - std::size_t new_size = size_ + internal::to_unsigned(end - begin); - reserve(new_size); - std::uninitialized_copy(begin, end, - internal::make_checked(ptr_, capacity_) + size_); - size_ = new_size; -} -} // namespace internal - -// A UTF-8 code unit type. -struct char8_t { - char value; - FMT_CONSTEXPR explicit operator bool() const FMT_NOEXCEPT { - return value != 0; - } -}; - -// A UTF-8 string view. -class u8string_view : public basic_string_view<char8_t> { - private: - typedef basic_string_view<char8_t> base; - - public: - using basic_string_view::basic_string_view; - using basic_string_view::char_type; - - u8string_view(const char *s) - : base(reinterpret_cast<const char8_t*>(s)) {} - - u8string_view(const char *s, size_t count) FMT_NOEXCEPT - : base(reinterpret_cast<const char8_t*>(s), count) {} -}; - -#if FMT_USE_USER_DEFINED_LITERALS -inline namespace literals { -inline u8string_view operator"" _u(const char *s, std::size_t n) { - return u8string_view(s, n); -} -} -#endif - -// A wrapper around std::locale used to reduce compile times since <locale> -// is very heavy. -class locale; - -class locale_provider { - public: - virtual ~locale_provider() {} - virtual fmt::locale locale(); -}; - -// The number of characters to store in the basic_memory_buffer object itself -// to avoid dynamic memory allocation. -enum { inline_buffer_size = 500 }; - -/** - \rst - A dynamically growing memory buffer for trivially copyable/constructible types - with the first ``SIZE`` elements stored in the object itself. - - You can use one of the following typedefs for common character types: - - +----------------+------------------------------+ - | Type | Definition | - +================+==============================+ - | memory_buffer | basic_memory_buffer<char> | - +----------------+------------------------------+ - | wmemory_buffer | basic_memory_buffer<wchar_t> | - +----------------+------------------------------+ - - **Example**:: - - fmt::memory_buffer out; - format_to(out, "The answer is {}.", 42); - - This will write the following output to the ``out`` object: - - .. code-block:: none - - The answer is 42. - - The output can be converted to an ``std::string`` with ``to_string(out)``. - \endrst - */ -template <typename T, std::size_t SIZE = inline_buffer_size, - typename Allocator = std::allocator<T> > -class basic_memory_buffer: private Allocator, public internal::basic_buffer<T> { - private: - T store_[SIZE]; - - // Deallocate memory allocated by the buffer. - void deallocate() { - T* data = this->data(); - if (data != store_) Allocator::deallocate(data, this->capacity()); - } - - protected: - void grow(std::size_t size) FMT_OVERRIDE; - - public: - explicit basic_memory_buffer(const Allocator &alloc = Allocator()) - : Allocator(alloc) { - this->set(store_, SIZE); - } - ~basic_memory_buffer() { deallocate(); } - - private: - // Move data from other to this buffer. - void move(basic_memory_buffer &other) { - Allocator &this_alloc = *this, &other_alloc = other; - this_alloc = std::move(other_alloc); - T* data = other.data(); - std::size_t size = other.size(), capacity = other.capacity(); - if (data == other.store_) { - this->set(store_, capacity); - std::uninitialized_copy(other.store_, other.store_ + size, - internal::make_checked(store_, capacity)); - } else { - this->set(data, capacity); - // Set pointer to the inline array so that delete is not called - // when deallocating. - other.set(other.store_, 0); - } - this->resize(size); - } - - public: - /** - \rst - Constructs a :class:`fmt::basic_memory_buffer` object moving the content - of the other object to it. - \endrst - */ - basic_memory_buffer(basic_memory_buffer &&other) { - move(other); - } - - /** - \rst - Moves the content of the other ``basic_memory_buffer`` object to this one. - \endrst - */ - basic_memory_buffer &operator=(basic_memory_buffer &&other) { - assert(this != &other); - deallocate(); - move(other); - return *this; - } - - // Returns a copy of the allocator associated with this buffer. - Allocator get_allocator() const { return *this; } -}; - -template <typename T, std::size_t SIZE, typename Allocator> -void basic_memory_buffer<T, SIZE, Allocator>::grow(std::size_t size) { - std::size_t old_capacity = this->capacity(); - std::size_t new_capacity = old_capacity + old_capacity / 2; - if (size > new_capacity) - new_capacity = size; - T *old_data = this->data(); - T *new_data = internal::allocate<Allocator>(*this, new_capacity); - // The following code doesn't throw, so the raw pointer above doesn't leak. - std::uninitialized_copy(old_data, old_data + this->size(), - internal::make_checked(new_data, new_capacity)); - this->set(new_data, new_capacity); - // deallocate must not throw according to the standard, but even if it does, - // the buffer already uses the new storage and will deallocate it in - // destructor. - if (old_data != store_) - Allocator::deallocate(old_data, old_capacity); -} - -typedef basic_memory_buffer<char> memory_buffer; -typedef basic_memory_buffer<wchar_t> wmemory_buffer; - -/** - \rst - A fixed-size memory buffer. For a dynamically growing buffer use - :class:`fmt::basic_memory_buffer`. - - Trying to increase the buffer size past the initial capacity will throw - ``std::runtime_error``. - \endrst - */ -template <typename Char> -class basic_fixed_buffer : public internal::basic_buffer<Char> { - public: - /** - \rst - Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the - given size. - \endrst - */ - basic_fixed_buffer(Char *array, std::size_t size) { - this->set(array, size); - } - - /** - \rst - Constructs a :class:`fmt::basic_fixed_buffer` object for *array* of the - size known at compile time. - \endrst - */ - template <std::size_t SIZE> - explicit basic_fixed_buffer(Char (&array)[SIZE]) { - this->set(array, SIZE); - } - - protected: - FMT_API void grow(std::size_t size) FMT_OVERRIDE; -}; - -namespace internal { - -template <typename Char> -struct char_traits; - -template <> -struct char_traits<char> { - // Formats a floating-point number. - template <typename T> - FMT_API static int format_float(char *buffer, std::size_t size, - const char *format, int precision, T value); -}; - -template <> -struct char_traits<wchar_t> { - template <typename T> - FMT_API static int format_float(wchar_t *buffer, std::size_t size, - const wchar_t *format, int precision, T value); -}; - -#if FMT_USE_EXTERN_TEMPLATES -extern template int char_traits<char>::format_float<double>( - char *buffer, std::size_t size, const char* format, int precision, - double value); -extern template int char_traits<char>::format_float<long double>( - char *buffer, std::size_t size, const char* format, int precision, - long double value); - -extern template int char_traits<wchar_t>::format_float<double>( - wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, - double value); -extern template int char_traits<wchar_t>::format_float<long double>( - wchar_t *buffer, std::size_t size, const wchar_t* format, int precision, - long double value); -#endif - -template <typename Container> -inline typename std::enable_if< - is_contiguous<Container>::value, - typename checked<typename Container::value_type>::type>::type - reserve(std::back_insert_iterator<Container> &it, std::size_t n) { - Container &c = internal::get_container(it); - std::size_t size = c.size(); - c.resize(size + n); - return make_checked(&c[size], n); -} - -template <typename Iterator> -inline Iterator &reserve(Iterator &it, std::size_t) { return it; } - -template <typename Char> -class null_terminating_iterator; - -template <typename Char> -FMT_CONSTEXPR_DECL const Char *pointer_from(null_terminating_iterator<Char> it); - -// An iterator that produces a null terminator on *end. This simplifies parsing -// and allows comparing the performance of processing a null-terminated string -// vs string_view. -template <typename Char> -class null_terminating_iterator { - public: - typedef std::ptrdiff_t difference_type; - typedef Char value_type; - typedef const Char* pointer; - typedef const Char& reference; - typedef std::random_access_iterator_tag iterator_category; - - null_terminating_iterator() : ptr_(0), end_(0) {} - - FMT_CONSTEXPR null_terminating_iterator(const Char *ptr, const Char *end) - : ptr_(ptr), end_(end) {} - - template <typename Range> - FMT_CONSTEXPR explicit null_terminating_iterator(const Range &r) - : ptr_(r.begin()), end_(r.end()) {} - - FMT_CONSTEXPR null_terminating_iterator &operator=(const Char *ptr) { - assert(ptr <= end_); - ptr_ = ptr; - return *this; - } - - FMT_CONSTEXPR Char operator*() const { - return ptr_ != end_ ? *ptr_ : 0; - } - - FMT_CONSTEXPR null_terminating_iterator operator++() { - ++ptr_; - return *this; - } - - FMT_CONSTEXPR null_terminating_iterator operator++(int) { - null_terminating_iterator result(*this); - ++ptr_; - return result; - } - - FMT_CONSTEXPR null_terminating_iterator operator--() { - --ptr_; - return *this; - } - - FMT_CONSTEXPR null_terminating_iterator operator+(difference_type n) { - return null_terminating_iterator(ptr_ + n, end_); - } - - FMT_CONSTEXPR null_terminating_iterator operator-(difference_type n) { - return null_terminating_iterator(ptr_ - n, end_); - } - - FMT_CONSTEXPR null_terminating_iterator operator+=(difference_type n) { - ptr_ += n; - return *this; - } - - FMT_CONSTEXPR difference_type operator-( - null_terminating_iterator other) const { - return ptr_ - other.ptr_; - } - - FMT_CONSTEXPR bool operator!=(null_terminating_iterator other) const { - return ptr_ != other.ptr_; - } - - bool operator>=(null_terminating_iterator other) const { - return ptr_ >= other.ptr_; - } - - // This should be a friend specialization pointer_from<Char> but the latter - // doesn't compile by gcc 5.1 due to a compiler bug. - template <typename CharT> - friend FMT_CONSTEXPR_DECL const CharT *pointer_from( - null_terminating_iterator<CharT> it); - - private: - const Char *ptr_; - const Char *end_; -}; - -template <typename T> -FMT_CONSTEXPR const T *pointer_from(const T *p) { return p; } - -template <typename Char> -FMT_CONSTEXPR const Char *pointer_from(null_terminating_iterator<Char> it) { - return it.ptr_; -} - -// An output iterator that counts the number of objects written to it and -// discards them. -template <typename T> -class counting_iterator { - private: - std::size_t count_; - mutable T blackhole_; - - public: - typedef std::output_iterator_tag iterator_category; - typedef T value_type; - typedef std::ptrdiff_t difference_type; - typedef T* pointer; - typedef T& reference; - typedef counting_iterator _Unchecked_type; // Mark iterator as checked. - - counting_iterator(): count_(0) {} - - std::size_t count() const { return count_; } - - counting_iterator& operator++() { - ++count_; - return *this; - } - - counting_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - T &operator*() const { return blackhole_; } -}; - -// An output iterator that truncates the output and counts the number of objects -// written to it. -template <typename OutputIt> -class truncating_iterator { - private: - typedef std::iterator_traits<OutputIt> traits; - - OutputIt out_; - std::size_t limit_; - std::size_t count_; - mutable typename traits::value_type blackhole_; - - public: - typedef std::output_iterator_tag iterator_category; - typedef typename traits::value_type value_type; - typedef typename traits::difference_type difference_type; - typedef typename traits::pointer pointer; - typedef typename traits::reference reference; - typedef truncating_iterator _Unchecked_type; // Mark iterator as checked. - - truncating_iterator(OutputIt out, std::size_t limit) - : out_(out), limit_(limit), count_(0) {} - - OutputIt base() const { return out_; } - std::size_t count() const { return count_; } - - truncating_iterator& operator++() { - if (count_++ < limit_) - ++out_; - return *this; - } - - truncating_iterator operator++(int) { - auto it = *this; - ++*this; - return it; - } - - reference operator*() const { return count_ < limit_ ? *out_ : blackhole_; } -}; - -// Returns true if value is negative, false otherwise. -// Same as (value < 0) but doesn't produce warnings if T is an unsigned type. -template <typename T> -FMT_CONSTEXPR typename std::enable_if< - std::numeric_limits<T>::is_signed, bool>::type is_negative(T value) { - return value < 0; -} -template <typename T> -FMT_CONSTEXPR typename std::enable_if< - !std::numeric_limits<T>::is_signed, bool>::type is_negative(T) { - return false; -} - -template <typename T> -struct int_traits { - // Smallest of uint32_t and uint64_t that is large enough to represent - // all values of T. - typedef typename std::conditional< - std::numeric_limits<T>::digits <= 32, uint32_t, uint64_t>::type main_type; -}; - -// Static data is placed in this class template to allow header-only -// configuration. -template <typename T = void> -struct FMT_API basic_data { - static const uint32_t POWERS_OF_10_32[]; - static const uint32_t ZERO_OR_POWERS_OF_10_32[]; - static const uint64_t ZERO_OR_POWERS_OF_10_64[]; - static const uint64_t POW10_SIGNIFICANDS[]; - static const int16_t POW10_EXPONENTS[]; - static const char DIGITS[]; - static const char RESET_COLOR[]; - static const wchar_t WRESET_COLOR[]; -}; - -#if FMT_USE_EXTERN_TEMPLATES -extern template struct basic_data<void>; -#endif - -typedef basic_data<> data; - -#ifdef FMT_BUILTIN_CLZLL -// Returns the number of decimal digits in n. Leading zeros are not counted -// except for n == 0 in which case count_digits returns 1. -inline unsigned count_digits(uint64_t n) { - // Based on http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog10 - // and the benchmark https://github.com/localvoid/cxx-benchmark-count-digits. - int t = (64 - FMT_BUILTIN_CLZLL(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_64[t]) + 1; -} -#else -// Fallback version of count_digits used when __builtin_clz is not available. -inline unsigned count_digits(uint64_t n) { - unsigned count = 1; - for (;;) { - // Integer division is slow so do it for a group of four digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - if (n < 10) return count; - if (n < 100) return count + 1; - if (n < 1000) return count + 2; - if (n < 10000) return count + 3; - n /= 10000u; - count += 4; - } -} -#endif - -// Counts the number of code points in a UTF-8 string. -FMT_API size_t count_code_points(u8string_view s); - -#if FMT_HAS_CPP_ATTRIBUTE(always_inline) -# define FMT_ALWAYS_INLINE __attribute__((always_inline)) -#else -# define FMT_ALWAYS_INLINE -#endif - -template <typename Handler> -inline char *lg(uint32_t n, Handler h) FMT_ALWAYS_INLINE; - -// Computes g = floor(log10(n)) and calls h.on<g>(n); -template <typename Handler> -inline char *lg(uint32_t n, Handler h) { - return n < 100 ? n < 10 ? h.template on<0>(n) : h.template on<1>(n) - : n < 1000000 - ? n < 10000 ? n < 1000 ? h.template on<2>(n) - : h.template on<3>(n) - : n < 100000 ? h.template on<4>(n) - : h.template on<5>(n) - : n < 100000000 ? n < 10000000 ? h.template on<6>(n) - : h.template on<7>(n) - : n < 1000000000 ? h.template on<8>(n) - : h.template on<9>(n); -} - -// An lg handler that formats a decimal number. -// Usage: lg(n, decimal_formatter(buffer)); -class decimal_formatter { - private: - char *buffer_; - - void write_pair(unsigned N, uint32_t index) { - std::memcpy(buffer_ + N, data::DIGITS + index * 2, 2); - } - - public: - explicit decimal_formatter(char *buf) : buffer_(buf) {} - - template <unsigned N> char *on(uint32_t u) { - if (N == 0) { - *buffer_ = static_cast<char>(u) + '0'; - } else if (N == 1) { - write_pair(0, u); - } else { - // The idea of using 4.32 fixed-point numbers is based on - // https://github.com/jeaiii/itoa - unsigned n = N - 1; - unsigned a = n / 5 * n * 53 / 16; - uint64_t t = ((1ULL << (32 + a)) / - data::ZERO_OR_POWERS_OF_10_32[n] + 1 - n / 9); - t = ((t * u) >> a) + n / 5 * 4; - write_pair(0, t >> 32); - for (unsigned i = 2; i < N; i += 2) { - t = 100ULL * static_cast<uint32_t>(t); - write_pair(i, t >> 32); - } - if (N % 2 == 0) { - buffer_[N] = static_cast<char>( - (10ULL * static_cast<uint32_t>(t)) >> 32) + '0'; - } - } - return buffer_ += N + 1; - } -}; - -// An lg handler that formats a decimal number with a terminating null. -class decimal_formatter_null : public decimal_formatter { - public: - explicit decimal_formatter_null(char *buf) : decimal_formatter(buf) {} - - template <unsigned N> char *on(uint32_t u) { - char *buf = decimal_formatter::on<N>(u); - *buf = '\0'; - return buf; - } -}; - -#ifdef FMT_BUILTIN_CLZ -// Optional version of count_digits for better performance on 32-bit platforms. -inline unsigned count_digits(uint32_t n) { - int t = (32 - FMT_BUILTIN_CLZ(n | 1)) * 1233 >> 12; - return to_unsigned(t) - (n < data::ZERO_OR_POWERS_OF_10_32[t]) + 1; -} -#endif - -// A functor that doesn't add a thousands separator. -struct no_thousands_sep { - typedef char char_type; - - template <typename Char> - void operator()(Char *) {} -}; - -// A functor that adds a thousands separator. -template <typename Char> -class add_thousands_sep { - private: - basic_string_view<Char> sep_; - - // Index of a decimal digit with the least significant digit having index 0. - unsigned digit_index_; - - public: - typedef Char char_type; - - explicit add_thousands_sep(basic_string_view<Char> sep) - : sep_(sep), digit_index_(0) {} - - void operator()(Char *&buffer) { - if (++digit_index_ % 3 != 0) - return; - buffer -= sep_.size(); - std::uninitialized_copy(sep_.data(), sep_.data() + sep_.size(), - internal::make_checked(buffer, sep_.size())); - } -}; - -template <typename Char> -FMT_API Char thousands_sep(locale_provider *lp); - -// Formats a decimal unsigned integer value writing into buffer. -// thousands_sep is a functor that is called after writing each char to -// add a thousands separator if necessary. -template <typename UInt, typename Char, typename ThousandsSep> -inline Char *format_decimal(Char *buffer, UInt value, unsigned num_digits, - ThousandsSep thousands_sep) { - buffer += num_digits; - Char *end = buffer; - while (value >= 100) { - // Integer division is slow so do it for a group of two digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - unsigned index = static_cast<unsigned>((value % 100) * 2); - value /= 100; - *--buffer = data::DIGITS[index + 1]; - thousands_sep(buffer); - *--buffer = data::DIGITS[index]; - thousands_sep(buffer); - } - if (value < 10) { - *--buffer = static_cast<char>('0' + value); - return end; - } - unsigned index = static_cast<unsigned>(value * 2); - *--buffer = data::DIGITS[index + 1]; - thousands_sep(buffer); - *--buffer = data::DIGITS[index]; - return end; -} - -template <typename UInt, typename Iterator, typename ThousandsSep> -inline Iterator format_decimal( - Iterator out, UInt value, unsigned num_digits, ThousandsSep sep) { - typedef typename ThousandsSep::char_type char_type; - // Buffer should be large enough to hold all digits (digits10 + 1) and null. - char_type buffer[std::numeric_limits<UInt>::digits10 + 2]; - format_decimal(buffer, value, num_digits, sep); - return std::copy_n(buffer, num_digits, out); -} - -template <typename It, typename UInt> -inline It format_decimal(It out, UInt value, unsigned num_digits) { - return format_decimal(out, value, num_digits, no_thousands_sep()); -} - -template <unsigned BASE_BITS, typename Char, typename UInt> -inline Char *format_uint(Char *buffer, UInt value, unsigned num_digits, - bool upper = false) { - buffer += num_digits; - Char *end = buffer; - do { - const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; - unsigned digit = (value & ((1 << BASE_BITS) - 1)); - *--buffer = BASE_BITS < 4 ? static_cast<char>('0' + digit) : digits[digit]; - } while ((value >>= BASE_BITS) != 0); - return end; -} - -template <unsigned BASE_BITS, typename It, typename UInt> -inline It format_uint(It out, UInt value, unsigned num_digits, - bool upper = false) { - // Buffer should be large enough to hold all digits (digits / BASE_BITS + 1) - // and null. - char buffer[std::numeric_limits<UInt>::digits / BASE_BITS + 2]; - format_uint<BASE_BITS>(buffer, value, num_digits, upper); - return std::copy_n(buffer, num_digits, out); -} - -#ifndef _WIN32 -# define FMT_USE_WINDOWS_H 0 -#elif !defined(FMT_USE_WINDOWS_H) -# define FMT_USE_WINDOWS_H 1 -#endif - -// Define FMT_USE_WINDOWS_H to 0 to disable use of windows.h. -// All the functionality that relies on it will be disabled too. -#if FMT_USE_WINDOWS_H -// A converter from UTF-8 to UTF-16. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf8_to_utf16 { - private: - wmemory_buffer buffer_; - - public: - FMT_API explicit utf8_to_utf16(string_view s); - operator wstring_view() const { return wstring_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const wchar_t *c_str() const { return &buffer_[0]; } - std::wstring str() const { return std::wstring(&buffer_[0], size()); } -}; - -// A converter from UTF-16 to UTF-8. -// It is only provided for Windows since other systems support UTF-8 natively. -class utf16_to_utf8 { - private: - memory_buffer buffer_; - - public: - utf16_to_utf8() {} - FMT_API explicit utf16_to_utf8(wstring_view s); - operator string_view() const { return string_view(&buffer_[0], size()); } - size_t size() const { return buffer_.size() - 1; } - const char *c_str() const { return &buffer_[0]; } - std::string str() const { return std::string(&buffer_[0], size()); } - - // Performs conversion returning a system error code instead of - // throwing exception on conversion error. This method may still throw - // in case of memory allocation error. - FMT_API int convert(wstring_view s); -}; - -FMT_API void format_windows_error(fmt::internal::buffer &out, int error_code, - fmt::string_view message) FMT_NOEXCEPT; -#endif - -template <typename T = void> -struct null {}; -} // namespace internal - -enum alignment { - ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC -}; - -// Flags. -enum {SIGN_FLAG = 1, PLUS_FLAG = 2, MINUS_FLAG = 4, HASH_FLAG = 8}; - -enum format_spec_tag {fill_tag, align_tag, width_tag, type_tag}; - -// Format specifier. -template <typename T, format_spec_tag> -class format_spec { - private: - T value_; - - public: - typedef T value_type; - - explicit format_spec(T value) : value_(value) {} - - T value() const { return value_; } -}; - -// template <typename Char> -// typedef format_spec<Char, fill_tag> fill_spec; -template <typename Char> -class fill_spec : public format_spec<Char, fill_tag> { - public: - explicit fill_spec(Char value) : format_spec<Char, fill_tag>(value) {} -}; - -typedef format_spec<unsigned, width_tag> width_spec; -typedef format_spec<char, type_tag> type_spec; - -// An empty format specifier. -struct empty_spec {}; - -// An alignment specifier. -struct align_spec : empty_spec { - unsigned width_; - // Fill is always wchar_t and cast to char if necessary to avoid having - // two specialization of AlignSpec and its subclasses. - wchar_t fill_; - alignment align_; - - FMT_CONSTEXPR align_spec( - unsigned width, wchar_t fill, alignment align = ALIGN_DEFAULT) - : width_(width), fill_(fill), align_(align) {} - - FMT_CONSTEXPR unsigned width() const { return width_; } - FMT_CONSTEXPR wchar_t fill() const { return fill_; } - FMT_CONSTEXPR alignment align() const { return align_; } - - int precision() const { return -1; } -}; - -// Format specifiers. -template <typename Char> -class basic_format_specs : public align_spec { - public: - unsigned flags_; - int precision_; - Char type_; - - FMT_CONSTEXPR basic_format_specs( - unsigned width = 0, char type = 0, wchar_t fill = ' ') - : align_spec(width, fill), flags_(0), precision_(-1), type_(type) {} - - FMT_CONSTEXPR bool flag(unsigned f) const { return (flags_ & f) != 0; } - FMT_CONSTEXPR int precision() const { return precision_; } - FMT_CONSTEXPR Char type() const { return type_; } -}; - -typedef basic_format_specs<char> format_specs; - -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR unsigned basic_parse_context<Char, ErrorHandler>::next_arg_id() { - if (next_arg_id_ >= 0) - return internal::to_unsigned(next_arg_id_++); - on_error("cannot switch from manual to automatic argument indexing"); - return 0; -} - -namespace internal { - -template <typename S> -struct format_string_traits< - S, typename std::enable_if<std::is_base_of<compile_string, S>::value>::type>: - format_string_traits_base<char> {}; - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_int_type_spec(Char spec, Handler &&handler) { - switch (spec) { - case 0: case 'd': - handler.on_dec(); - break; - case 'x': case 'X': - handler.on_hex(); - break; - case 'b': case 'B': - handler.on_bin(); - break; - case 'o': - handler.on_oct(); - break; - case 'n': - handler.on_num(); - break; - default: - handler.on_error(); - } -} - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_float_type_spec(Char spec, Handler &&handler) { - switch (spec) { - case 0: case 'g': case 'G': - handler.on_general(); - break; - case 'e': case 'E': - handler.on_exp(); - break; - case 'f': case 'F': - handler.on_fixed(); - break; - case 'a': case 'A': - handler.on_hex(); - break; - default: - handler.on_error(); - break; - } -} - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_char_specs( - const basic_format_specs<Char> *specs, Handler &&handler) { - if (!specs) return handler.on_char(); - if (specs->type() && specs->type() != 'c') return handler.on_int(); - if (specs->align() == ALIGN_NUMERIC || specs->flag(~0u) != 0) - handler.on_error("invalid format specifier for char"); - handler.on_char(); -} - -template <typename Char, typename Handler> -FMT_CONSTEXPR void handle_cstring_type_spec(Char spec, Handler &&handler) { - if (spec == 0 || spec == 's') - handler.on_string(); - else if (spec == 'p') - handler.on_pointer(); - else - handler.on_error("invalid type specifier"); -} - -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR void check_string_type_spec(Char spec, ErrorHandler &&eh) { - if (spec != 0 && spec != 's') - eh.on_error("invalid type specifier"); -} - -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR void check_pointer_type_spec(Char spec, ErrorHandler &&eh) { - if (spec != 0 && spec != 'p') - eh.on_error("invalid type specifier"); -} - -template <typename ErrorHandler> -class int_type_checker : private ErrorHandler { - public: - FMT_CONSTEXPR explicit int_type_checker(ErrorHandler eh) : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_dec() {} - FMT_CONSTEXPR void on_hex() {} - FMT_CONSTEXPR void on_bin() {} - FMT_CONSTEXPR void on_oct() {} - FMT_CONSTEXPR void on_num() {} - - FMT_CONSTEXPR void on_error() { - ErrorHandler::on_error("invalid type specifier"); - } -}; - -template <typename ErrorHandler> -class float_type_checker : private ErrorHandler { - public: - FMT_CONSTEXPR explicit float_type_checker(ErrorHandler eh) - : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_general() {} - FMT_CONSTEXPR void on_exp() {} - FMT_CONSTEXPR void on_fixed() {} - FMT_CONSTEXPR void on_hex() {} - - FMT_CONSTEXPR void on_error() { - ErrorHandler::on_error("invalid type specifier"); - } -}; - -template <typename ErrorHandler, typename CharType> -class char_specs_checker : public ErrorHandler { - private: - CharType type_; - - public: - FMT_CONSTEXPR char_specs_checker(CharType type, ErrorHandler eh) - : ErrorHandler(eh), type_(type) {} - - FMT_CONSTEXPR void on_int() { - handle_int_type_spec(type_, int_type_checker<ErrorHandler>(*this)); - } - FMT_CONSTEXPR void on_char() {} -}; - -template <typename ErrorHandler> -class cstring_type_checker : public ErrorHandler { - public: - FMT_CONSTEXPR explicit cstring_type_checker(ErrorHandler eh) - : ErrorHandler(eh) {} - - FMT_CONSTEXPR void on_string() {} - FMT_CONSTEXPR void on_pointer() {} -}; - -template <typename Context> -void arg_map<Context>::init(const basic_format_args<Context> &args) { - if (map_) - return; - map_ = new entry[args.max_size()]; - bool use_values = args.type(max_packed_args - 1) == internal::none_type; - if (use_values) { - for (unsigned i = 0;/*nothing*/; ++i) { - internal::type arg_type = args.type(i); - switch (arg_type) { - case internal::none_type: - return; - case internal::named_arg_type: - push_back(args.values_[i]); - break; - default: - break; // Do nothing. - } - } - } - for (unsigned i = 0; ; ++i) { - switch (args.args_[i].type_) { - case internal::none_type: - return; - case internal::named_arg_type: - push_back(args.args_[i].value_); - break; - default: - break; // Do nothing. - } - } -} - -template <typename Range> -class arg_formatter_base { - public: - typedef typename Range::value_type char_type; - typedef decltype(internal::declval<Range>().begin()) iterator; - typedef basic_format_specs<char_type> format_specs; - - private: - typedef basic_writer<Range> writer_type; - writer_type writer_; - format_specs *specs_; - - struct char_writer { - char_type value; - template <typename It> - void operator()(It &&it) const { *it++ = value; } - }; - - void write_char(char_type value) { - if (specs_) - writer_.write_padded(1, *specs_, char_writer{value}); - else - writer_.write(value); - } - - void write_pointer(const void *p) { - format_specs specs = specs_ ? *specs_ : format_specs(); - specs.flags_ = HASH_FLAG; - specs.type_ = 'x'; - writer_.write_int(reinterpret_cast<uintptr_t>(p), specs); - } - - protected: - writer_type &writer() { return writer_; } - format_specs *spec() { return specs_; } - iterator out() { return writer_.out(); } - - void write(bool value) { - string_view sv(value ? "true" : "false"); - specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); - } - - void write(const char_type *value) { - if (!value) - FMT_THROW(format_error("string pointer is null")); - auto length = std::char_traits<char_type>::length(value); - basic_string_view<char_type> sv(value, length); - specs_ ? writer_.write_str(sv, *specs_) : writer_.write(sv); - } - - public: - arg_formatter_base(Range r, format_specs *s): writer_(r), specs_(s) {} - - iterator operator()(monostate) { - FMT_ASSERT(false, "invalid argument type"); - return out(); - } - - template <typename T> - typename std::enable_if<std::is_integral<T>::value, iterator>::type - operator()(T value) { - // MSVC2013 fails to compile separate overloads for bool and char_type so - // use std::is_same instead. - if (std::is_same<T, bool>::value) { - if (specs_ && specs_->type_) - return (*this)(value ? 1 : 0); - write(value != 0); - } else if (std::is_same<T, char_type>::value) { - internal::handle_char_specs( - specs_, char_spec_handler(*this, static_cast<char_type>(value))); - } else { - specs_ ? writer_.write_int(value, *specs_) : writer_.write(value); - } - return out(); - } - - template <typename T> - typename std::enable_if<std::is_floating_point<T>::value, iterator>::type - operator()(T value) { - writer_.write_double(value, specs_ ? *specs_ : format_specs()); - return out(); - } - - struct char_spec_handler : internal::error_handler { - arg_formatter_base &formatter; - char_type value; - - char_spec_handler(arg_formatter_base& f, char_type val) - : formatter(f), value(val) {} - - void on_int() { - if (formatter.specs_) - formatter.writer_.write_int(value, *formatter.specs_); - else - formatter.writer_.write(value); - } - void on_char() { formatter.write_char(value); } - }; - - struct cstring_spec_handler : internal::error_handler { - arg_formatter_base &formatter; - const char_type *value; - - cstring_spec_handler(arg_formatter_base &f, const char_type *val) - : formatter(f), value(val) {} - - void on_string() { formatter.write(value); } - void on_pointer() { formatter.write_pointer(value); } - }; - - iterator operator()(const char_type *value) { - if (!specs_) return write(value), out(); - internal::handle_cstring_type_spec( - specs_->type_, cstring_spec_handler(*this, value)); - return out(); - } - - iterator operator()(basic_string_view<char_type> value) { - if (specs_) { - internal::check_string_type_spec( - specs_->type_, internal::error_handler()); - writer_.write_str(value, *specs_); - } else { - writer_.write(value); - } - return out(); - } - - iterator operator()(const void *value) { - if (specs_) - check_pointer_type_spec(specs_->type_, internal::error_handler()); - write_pointer(value); - return out(); - } -}; - -template <typename Char> -FMT_CONSTEXPR bool is_name_start(Char c) { - return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || '_' == c; -} - -// DEPRECATED: Parses the input as an unsigned integer. This function assumes -// that the first character is a digit and presence of a non-digit character at -// the end. -// it: an iterator pointing to the beginning of the input range. -template <typename Iterator, typename ErrorHandler> -FMT_CONSTEXPR unsigned parse_nonnegative_int(Iterator &it, ErrorHandler &&eh) { - assert('0' <= *it && *it <= '9'); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits<int>::max)(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + unsigned(*it - '0'); - // Workaround for MSVC "setup_exception stack overflow" error: - auto next = it; - ++next; - it = next; - } while ('0' <= *it && *it <= '9'); - if (value > max_int) - eh.on_error("number is too big"); - return value; -} - -// Parses the range [begin, end) as an unsigned integer. This function assumes -// that the range is non-empty and the first character is a digit. -template <typename Char, typename ErrorHandler> -FMT_CONSTEXPR unsigned parse_nonnegative_int( - const Char *&begin, const Char *end, ErrorHandler &&eh) { - assert(begin != end && '0' <= *begin && *begin <= '9'); - unsigned value = 0; - // Convert to unsigned to prevent a warning. - unsigned max_int = (std::numeric_limits<int>::max)(); - unsigned big = max_int / 10; - do { - // Check for overflow. - if (value > big) { - value = max_int + 1; - break; - } - value = value * 10 + unsigned(*begin++ - '0'); - } while (begin != end && '0' <= *begin && *begin <= '9'); - if (value > max_int) - eh.on_error("number is too big"); - return value; -} - -template <typename Char, typename Context> -class custom_formatter: public function<bool> { - private: - Context &ctx_; - - public: - explicit custom_formatter(Context &ctx): ctx_(ctx) {} - - bool operator()(typename basic_format_arg<Context>::handle h) const { - h.format(ctx_); - return true; - } - - template <typename T> - bool operator()(T) const { return false; } -}; - -template <typename T> -struct is_integer { - enum { - value = std::is_integral<T>::value && !std::is_same<T, bool>::value && - !std::is_same<T, char>::value && !std::is_same<T, wchar_t>::value - }; -}; - -template <typename ErrorHandler> -class width_checker: public function<unsigned long long> { - public: - explicit FMT_CONSTEXPR width_checker(ErrorHandler &eh) : handler_(eh) {} - - template <typename T> - FMT_CONSTEXPR - typename std::enable_if< - is_integer<T>::value, unsigned long long>::type operator()(T value) { - if (is_negative(value)) - handler_.on_error("negative width"); - return static_cast<unsigned long long>(value); - } - - template <typename T> - FMT_CONSTEXPR typename std::enable_if< - !is_integer<T>::value, unsigned long long>::type operator()(T) { - handler_.on_error("width is not integer"); - return 0; - } - - private: - ErrorHandler &handler_; -}; - -template <typename ErrorHandler> -class precision_checker: public function<unsigned long long> { - public: - explicit FMT_CONSTEXPR precision_checker(ErrorHandler &eh) : handler_(eh) {} - - template <typename T> - FMT_CONSTEXPR typename std::enable_if< - is_integer<T>::value, unsigned long long>::type operator()(T value) { - if (is_negative(value)) - handler_.on_error("negative precision"); - return static_cast<unsigned long long>(value); - } - - template <typename T> - FMT_CONSTEXPR typename std::enable_if< - !is_integer<T>::value, unsigned long long>::type operator()(T) { - handler_.on_error("precision is not integer"); - return 0; - } - - private: - ErrorHandler &handler_; -}; - -// A format specifier handler that sets fields in basic_format_specs. -template <typename Char> -class specs_setter { - public: - explicit FMT_CONSTEXPR specs_setter(basic_format_specs<Char> &specs): - specs_(specs) {} - - FMT_CONSTEXPR specs_setter(const specs_setter &other) : specs_(other.specs_) {} - - FMT_CONSTEXPR void on_align(alignment align) { specs_.align_ = align; } - FMT_CONSTEXPR void on_fill(Char fill) { specs_.fill_ = fill; } - FMT_CONSTEXPR void on_plus() { specs_.flags_ |= SIGN_FLAG | PLUS_FLAG; } - FMT_CONSTEXPR void on_minus() { specs_.flags_ |= MINUS_FLAG; } - FMT_CONSTEXPR void on_space() { specs_.flags_ |= SIGN_FLAG; } - FMT_CONSTEXPR void on_hash() { specs_.flags_ |= HASH_FLAG; } - - FMT_CONSTEXPR void on_zero() { - specs_.align_ = ALIGN_NUMERIC; - specs_.fill_ = '0'; - } - - FMT_CONSTEXPR void on_width(unsigned width) { specs_.width_ = width; } - FMT_CONSTEXPR void on_precision(unsigned precision) { - specs_.precision_ = static_cast<int>(precision); - } - FMT_CONSTEXPR void end_precision() {} - - FMT_CONSTEXPR void on_type(Char type) { specs_.type_ = type; } - - protected: - basic_format_specs<Char> &specs_; -}; - -// A format specifier handler that checks if specifiers are consistent with the -// argument type. -template <typename Handler> -class specs_checker : public Handler { - public: - FMT_CONSTEXPR specs_checker(const Handler& handler, internal::type arg_type) - : Handler(handler), arg_type_(arg_type) {} - - FMT_CONSTEXPR specs_checker(const specs_checker &other) - : Handler(other), arg_type_(other.arg_type_) {} - - FMT_CONSTEXPR void on_align(alignment align) { - if (align == ALIGN_NUMERIC) - require_numeric_argument(); - Handler::on_align(align); - } - - FMT_CONSTEXPR void on_plus() { - check_sign(); - Handler::on_plus(); - } - - FMT_CONSTEXPR void on_minus() { - check_sign(); - Handler::on_minus(); - } - - FMT_CONSTEXPR void on_space() { - check_sign(); - Handler::on_space(); - } - - FMT_CONSTEXPR void on_hash() { - require_numeric_argument(); - Handler::on_hash(); - } - - FMT_CONSTEXPR void on_zero() { - require_numeric_argument(); - Handler::on_zero(); - } - - FMT_CONSTEXPR void end_precision() { - if (is_integral(arg_type_) || arg_type_ == pointer_type) - this->on_error("precision not allowed for this argument type"); - } - - private: - FMT_CONSTEXPR void require_numeric_argument() { - if (!is_arithmetic(arg_type_)) - this->on_error("format specifier requires numeric argument"); - } - - FMT_CONSTEXPR void check_sign() { - require_numeric_argument(); - if (is_integral(arg_type_) && arg_type_ != int_type && - arg_type_ != long_long_type && arg_type_ != internal::char_type) { - this->on_error("format specifier requires signed argument"); - } - } - - internal::type arg_type_; -}; - -template <template <typename> class Handler, typename T, - typename Context, typename ErrorHandler> -FMT_CONSTEXPR void set_dynamic_spec( - T &value, basic_format_arg<Context> arg, ErrorHandler eh) { - unsigned long long big_value = fmt::visit(Handler<ErrorHandler>(eh), arg); - if (big_value > (std::numeric_limits<int>::max)()) - eh.on_error("number is too big"); - value = static_cast<T>(big_value); -} - -struct auto_id {}; - -// The standard format specifier handler with checking. -template <typename Context> -class specs_handler: public specs_setter<typename Context::char_type> { - public: - typedef typename Context::char_type char_type; - - FMT_CONSTEXPR specs_handler( - basic_format_specs<char_type> &specs, Context &ctx) - : specs_setter<char_type>(specs), context_(ctx) {} - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - set_dynamic_spec<width_checker>( - this->specs_.width_, get_arg(arg_id), context_.error_handler()); - } - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - set_dynamic_spec<precision_checker>( - this->specs_.precision_, get_arg(arg_id), context_.error_handler()); - } - - void on_error(const char *message) { - context_.on_error(message); - } - - private: - FMT_CONSTEXPR basic_format_arg<Context> get_arg(auto_id) { - return context_.next_arg(); - } - - template <typename Id> - FMT_CONSTEXPR basic_format_arg<Context> get_arg(Id arg_id) { - context_.parse_context().check_arg_id(arg_id); - return context_.get_arg(arg_id); - } - - Context &context_; -}; - -// An argument reference. -template <typename Char> -struct arg_ref { - enum Kind { NONE, INDEX, NAME }; - - FMT_CONSTEXPR arg_ref() : kind(NONE), index(0) {} - FMT_CONSTEXPR explicit arg_ref(unsigned index) : kind(INDEX), index(index) {} - explicit arg_ref(basic_string_view<Char> name) : kind(NAME), name(name) {} - - FMT_CONSTEXPR arg_ref &operator=(unsigned idx) { - kind = INDEX; - index = idx; - return *this; - } - - Kind kind; - FMT_UNION { - unsigned index; - basic_string_view<Char> name; - }; -}; - -// Format specifiers with width and precision resolved at formatting rather -// than parsing time to allow re-using the same parsed specifiers with -// differents sets of arguments (precompilation of format strings). -template <typename Char> -struct dynamic_format_specs : basic_format_specs<Char> { - arg_ref<Char> width_ref; - arg_ref<Char> precision_ref; -}; - -// Format spec handler that saves references to arguments representing dynamic -// width and precision to be resolved at formatting time. -template <typename ParseContext> -class dynamic_specs_handler : - public specs_setter<typename ParseContext::char_type> { - public: - typedef typename ParseContext::char_type char_type; - - FMT_CONSTEXPR dynamic_specs_handler( - dynamic_format_specs<char_type> &specs, ParseContext &ctx) - : specs_setter<char_type>(specs), specs_(specs), context_(ctx) {} - - FMT_CONSTEXPR dynamic_specs_handler(const dynamic_specs_handler &other) - : specs_setter<char_type>(other), - specs_(other.specs_), context_(other.context_) {} - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_width(Id arg_id) { - specs_.width_ref = make_arg_ref(arg_id); - } - - template <typename Id> - FMT_CONSTEXPR void on_dynamic_precision(Id arg_id) { - specs_.precision_ref = make_arg_ref(arg_id); - } - - FMT_CONSTEXPR void on_error(const char *message) { - context_.on_error(message); - } - - private: - typedef arg_ref<char_type> arg_ref_type; - - template <typename Id> - FMT_CONSTEXPR arg_ref_type make_arg_ref(Id arg_id) { - context_.check_arg_id(arg_id); - return arg_ref_type(arg_id); - } - - FMT_CONSTEXPR arg_ref_type make_arg_ref(auto_id) { - return arg_ref_type(context_.next_arg_id()); - } - - dynamic_format_specs<char_type> &specs_; - ParseContext &context_; -}; - -template <typename Iterator, typename IDHandler> -FMT_CONSTEXPR Iterator parse_arg_id(Iterator it, IDHandler &&handler) { - typedef typename std::iterator_traits<Iterator>::value_type char_type; - char_type c = *it; - if (c == '}' || c == ':') { - handler(); - return it; - } - if (c >= '0' && c <= '9') { - unsigned index = parse_nonnegative_int(it, handler); - if (*it != '}' && *it != ':') { - handler.on_error("invalid format string"); - return it; - } - handler(index); - return it; - } - if (!is_name_start(c)) { - handler.on_error("invalid format string"); - return it; - } - auto start = it; - do { - c = *++it; - } while (is_name_start(c) || ('0' <= c && c <= '9')); - handler(basic_string_view<char_type>( - pointer_from(start), to_unsigned(it - start))); - return it; -} - -template <typename Char, typename IDHandler> -FMT_CONSTEXPR const Char *parse_arg_id( - const Char *begin, const Char *end, IDHandler &&handler) { - assert(begin != end); - Char c = *begin; - if (c == '}' || c == ':') - return handler(), begin; - if (c >= '0' && c <= '9') { - unsigned index = parse_nonnegative_int(begin, end, handler); - if (begin == end || (*begin != '}' && *begin != ':')) - return handler.on_error("invalid format string"), begin; - handler(index); - return begin; - } - if (!is_name_start(c)) - return handler.on_error("invalid format string"), begin; - auto it = begin; - do { - c = *++it; - } while (it != end && (is_name_start(c) || ('0' <= c && c <= '9'))); - handler(basic_string_view<Char>(begin, to_unsigned(it - begin))); - return it; -} - -// Adapts SpecHandler to IDHandler API for dynamic width. -template <typename SpecHandler, typename Char> -struct width_adapter { - explicit FMT_CONSTEXPR width_adapter(SpecHandler &h) : handler(h) {} - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_width(auto_id()); } - FMT_CONSTEXPR void operator()(unsigned id) { handler.on_dynamic_width(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_dynamic_width(id); - } - - FMT_CONSTEXPR void on_error(const char *message) { - handler.on_error(message); - } - - SpecHandler &handler; -}; - -// Adapts SpecHandler to IDHandler API for dynamic precision. -template <typename SpecHandler, typename Char> -struct precision_adapter { - explicit FMT_CONSTEXPR precision_adapter(SpecHandler &h) : handler(h) {} - - FMT_CONSTEXPR void operator()() { handler.on_dynamic_precision(auto_id()); } - FMT_CONSTEXPR void operator()(unsigned id) { - handler.on_dynamic_precision(id); - } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_dynamic_precision(id); - } - - FMT_CONSTEXPR void on_error(const char *message) { handler.on_error(message); } - - SpecHandler &handler; -}; - -// Parses standard format specifiers and sends notifications about parsed -// components to handler. -// it: an iterator pointing to the beginning of a null-terminated range of -// characters, possibly emulated via null_terminating_iterator, representing -// format specifiers. -template <typename Iterator, typename SpecHandler> -FMT_CONSTEXPR Iterator parse_format_specs(Iterator it, SpecHandler &&handler) { - typedef typename std::iterator_traits<Iterator>::value_type char_type; - char_type c = *it; - if (c == '}' || !c) - return it; - - // Parse fill and alignment. - alignment align = ALIGN_DEFAULT; - int i = 1; - do { - auto p = it + i; - switch (*p) { - case '<': - align = ALIGN_LEFT; - break; - case '>': - align = ALIGN_RIGHT; - break; - case '=': - align = ALIGN_NUMERIC; - break; - case '^': - align = ALIGN_CENTER; - break; - } - if (align != ALIGN_DEFAULT) { - if (p != it) { - if (c == '{') { - handler.on_error("invalid fill character '{'"); - return it; - } - it += 2; - handler.on_fill(c); - } else ++it; - handler.on_align(align); - break; - } - } while (--i >= 0); - - // Parse sign. - switch (*it) { - case '+': - handler.on_plus(); - ++it; - break; - case '-': - handler.on_minus(); - ++it; - break; - case ' ': - handler.on_space(); - ++it; - break; - } - - if (*it == '#') { - handler.on_hash(); - ++it; - } - - // Parse zero flag. - if (*it == '0') { - handler.on_zero(); - ++it; - } - - // Parse width. - if ('0' <= *it && *it <= '9') { - handler.on_width(parse_nonnegative_int(it, handler)); - } else if (*it == '{') { - it = parse_arg_id(it + 1, width_adapter<SpecHandler, char_type>(handler)); - if (*it++ != '}') { - handler.on_error("invalid format string"); - return it; - } - } - - // Parse precision. - if (*it == '.') { - ++it; - if ('0' <= *it && *it <= '9') { - handler.on_precision(parse_nonnegative_int(it, handler)); - } else if (*it == '{') { - it = parse_arg_id( - it + 1, precision_adapter<SpecHandler, char_type>(handler)); - if (*it++ != '}') { - handler.on_error("invalid format string"); - return it; - } - } else { - handler.on_error("missing precision specifier"); - return it; - } - handler.end_precision(); - } - - // Parse type. - if (*it != '}' && *it) - handler.on_type(*it++); - return it; -} - -// Return the result via the out param to workaround gcc bug 77539. -template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*> -FMT_CONSTEXPR bool find(Ptr first, Ptr last, T value, Ptr &out) { - for (out = first; out != last; ++out) { - if (*out == value) - return true; - } - return false; -} - -template <> -inline bool find<false, char>( - const char *first, const char *last, char value, const char *&out) { - out = static_cast<const char*>(std::memchr(first, value, last - first)); - return out != FMT_NULL; -} - -template <typename Handler, typename Char> -struct id_adapter { - FMT_CONSTEXPR void operator()() { handler.on_arg_id(); } - FMT_CONSTEXPR void operator()(unsigned id) { handler.on_arg_id(id); } - FMT_CONSTEXPR void operator()(basic_string_view<Char> id) { - handler.on_arg_id(id); - } - FMT_CONSTEXPR void on_error(const char *message) { - handler.on_error(message); - } - Handler &handler; -}; - -template <bool IS_CONSTEXPR, typename Char, typename Handler> -FMT_CONSTEXPR void parse_format_string( - basic_string_view<Char> format_str, Handler &&handler) { - struct writer { - FMT_CONSTEXPR void operator()(const Char *begin, const Char *end) { - if (begin == end) return; - for (;;) { - const Char *p = FMT_NULL; - if (!find<IS_CONSTEXPR>(begin, end, '}', p)) - return handler_.on_text(begin, end); - ++p; - if (p == end || *p != '}') - return handler_.on_error("unmatched '}' in format string"); - handler_.on_text(begin, p); - begin = p + 1; - } - } - Handler &handler_; - } write{handler}; - auto begin = format_str.data(), end = begin + format_str.size(); - while (begin != end) { - // Doing two passes with memchr (one for '{' and another for '}') is up to - // 2.5x faster than the naive one-pass implementation on big format strings. - const Char *p = begin; - if (*begin != '{' && !find<IS_CONSTEXPR>(begin, end, '{', p)) - return write(begin, end); - write(begin, p); - ++p; - if (p == end) - return handler.on_error("invalid format string"); - if (*p == '}') { - handler.on_arg_id(); - handler.on_replacement_field(p); - } else if (*p == '{') { - handler.on_text(p, p + 1); - } else { - p = parse_arg_id(p, end, id_adapter<Handler, Char>{handler}); - Char c = p != end ? *p : 0; - if (c == '}') { - handler.on_replacement_field(p); - } else if (c == ':') { - internal::null_terminating_iterator<Char> it(p + 1, end); - it = handler.on_format_specs(it); - if (*it != '}') - return handler.on_error("unknown format specifier"); - p = pointer_from(it); - } else { - return handler.on_error("missing '}' in format string"); - } - } - begin = p + 1; - } -} - -template <typename T, typename ParseContext> -FMT_CONSTEXPR const typename ParseContext::char_type * - parse_format_specs(ParseContext &ctx) { - // GCC 7.2 requires initializer. - formatter<T, typename ParseContext::char_type> f{}; - return f.parse(ctx); -} - -template <typename Char, typename ErrorHandler, typename... Args> -class format_string_checker { - public: - explicit FMT_CONSTEXPR format_string_checker( - basic_string_view<Char> format_str, ErrorHandler eh) - : arg_id_(-1), context_(format_str, eh), - parse_funcs_{&parse_format_specs<Args, parse_context_type>...} {} - - typedef internal::null_terminating_iterator<Char> iterator; - - FMT_CONSTEXPR void on_text(const Char *, const Char *) {} - - FMT_CONSTEXPR void on_arg_id() { - arg_id_ = context_.next_arg_id(); - check_arg_id(); - } - FMT_CONSTEXPR void on_arg_id(unsigned id) { - arg_id_ = id; - context_.check_arg_id(id); - check_arg_id(); - } - FMT_CONSTEXPR void on_arg_id(basic_string_view<Char>) {} - - FMT_CONSTEXPR void on_replacement_field(const Char *) {} - - FMT_CONSTEXPR const Char *on_format_specs(iterator it) { - auto p = pointer_from(it); - context_.advance_to(p); - return to_unsigned(arg_id_) < NUM_ARGS ? - parse_funcs_[arg_id_](context_) : p; - } - - FMT_CONSTEXPR void on_error(const char *message) { - context_.on_error(message); - } - - private: - typedef basic_parse_context<Char, ErrorHandler> parse_context_type; - enum { NUM_ARGS = sizeof...(Args) }; - - FMT_CONSTEXPR void check_arg_id() { - if (internal::to_unsigned(arg_id_) >= NUM_ARGS) - context_.on_error("argument index out of range"); - } - - // Format specifier parsing function. - typedef const Char *(*parse_func)(parse_context_type &); - - int arg_id_; - parse_context_type context_; - parse_func parse_funcs_[NUM_ARGS > 0 ? NUM_ARGS : 1]; -}; - -template <typename Char, typename ErrorHandler, typename... Args> -FMT_CONSTEXPR bool check_format_string( - basic_string_view<Char> s, ErrorHandler eh = ErrorHandler()) { - format_string_checker<Char, ErrorHandler, Args...> checker(s, eh); - parse_format_string<true>(s, checker); - return true; -} - -template <typename... Args, typename String> -typename std::enable_if<is_compile_string<String>::value>::type - check_format_string(String format_str) { - FMT_CONSTEXPR_DECL bool invalid_format = - internal::check_format_string<char, internal::error_handler, Args...>( - string_view(format_str.data(), format_str.size())); - (void)invalid_format; -} - -// Specifies whether to format T using the standard formatter. -// It is not possible to use get_type in formatter specialization directly -// because of a bug in MSVC. -template <typename Context, typename T> -struct format_type : - std::integral_constant<bool, get_type<Context, T>::value != custom_type> {}; - -template <template <typename> class Handler, typename Spec, typename Context> -void handle_dynamic_spec( - Spec &value, arg_ref<typename Context::char_type> ref, Context &ctx) { - typedef typename Context::char_type char_type; - switch (ref.kind) { - case arg_ref<char_type>::NONE: - break; - case arg_ref<char_type>::INDEX: - internal::set_dynamic_spec<Handler>( - value, ctx.get_arg(ref.index), ctx.error_handler()); - break; - case arg_ref<char_type>::NAME: - internal::set_dynamic_spec<Handler>( - value, ctx.get_arg(ref.name), ctx.error_handler()); - break; - } -} -} // namespace internal - -/** The default argument formatter. */ -template <typename Range> -class arg_formatter: - public internal::function< - typename internal::arg_formatter_base<Range>::iterator>, - public internal::arg_formatter_base<Range> { - private: - typedef typename Range::value_type char_type; - typedef internal::arg_formatter_base<Range> base; - typedef basic_format_context<typename base::iterator, char_type> context_type; - - context_type &ctx_; - - public: - typedef Range range; - typedef typename base::iterator iterator; - typedef typename base::format_specs format_specs; - - /** - \rst - Constructs an argument formatter object. - *ctx* is a reference to the formatting context, - *spec* contains format specifier information for standard argument types. - \endrst - */ - explicit arg_formatter(context_type &ctx, format_specs *spec = {}) - : base(Range(ctx.out()), spec), ctx_(ctx) {} - - // Deprecated. - arg_formatter(context_type &ctx, format_specs &spec) - : base(Range(ctx.out()), &spec), ctx_(ctx) {} - - using base::operator(); - - /** Formats an argument of a user-defined type. */ - iterator operator()(typename basic_format_arg<context_type>::handle handle) { - handle.format(ctx_); - return this->out(); - } -}; - -/** - An error returned by an operating system or a language runtime, - for example a file opening error. -*/ -class system_error : public std::runtime_error { - private: - FMT_API void init(int err_code, string_view format_str, format_args args); - - protected: - int error_code_; - - system_error() : std::runtime_error("") {} - - public: - /** - \rst - Constructs a :class:`fmt::system_error` object with a description - formatted with `fmt::format_system_error`. *message* and additional - arguments passed into the constructor are formatted similarly to - `fmt::format`. - - **Example**:: - - // This throws a system_error with the description - // cannot open file 'madeup': No such file or directory - // or similar (system message may vary). - const char *filename = "madeup"; - std::FILE *file = std::fopen(filename, "r"); - if (!file) - throw fmt::system_error(errno, "cannot open file '{}'", filename); - \endrst - */ - template <typename... Args> - system_error(int error_code, string_view message, const Args & ... args) - : std::runtime_error("") { - init(error_code, message, make_format_args(args...)); - } - - int error_code() const { return error_code_; } -}; - -/** - \rst - Formats an error returned by an operating system or a language runtime, - for example a file opening error, and writes it to *out* in the following - form: - - .. parsed-literal:: - *<message>*: *<system-message>* - - where *<message>* is the passed message and *<system-message>* is - the system message corresponding to the error code. - *error_code* is a system error code as given by ``errno``. - If *error_code* is not a valid error code such as -1, the system message - may look like "Unknown error -1" and is platform-dependent. - \endrst - */ -FMT_API void format_system_error(internal::buffer &out, int error_code, - fmt::string_view message) FMT_NOEXCEPT; - -/** - This template provides operations for formatting and writing data into a - character range. - */ -template <typename Range> -class basic_writer { - public: - typedef typename Range::value_type char_type; - typedef decltype(internal::declval<Range>().begin()) iterator; - typedef basic_format_specs<char_type> format_specs; - - private: - iterator out_; // Output iterator. - std::unique_ptr<locale_provider> locale_; - - iterator out() const { return out_; } - - // Attempts to reserve space for n extra characters in the output range. - // Returns a pointer to the reserved range or a reference to out_. - auto reserve(std::size_t n) -> decltype(internal::reserve(out_, n)) { - return internal::reserve(out_, n); - } - - // Writes a value in the format - // <left-padding><value><right-padding> - // where <value> is written by f(it). - template <typename F> - void write_padded(std::size_t size, const align_spec &spec, F &&f); - - template <typename F> - struct padded_int_writer { - string_view prefix; - char_type fill; - std::size_t padding; - F f; - - template <typename It> - void operator()(It &&it) const { - if (prefix.size() != 0) - it = std::copy_n(prefix.data(), prefix.size(), it); - it = std::fill_n(it, padding, fill); - f(it); - } - }; - - // Writes an integer in the format - // <left-padding><prefix><numeric-padding><digits><right-padding> - // where <digits> are written by f(it). - template <typename Spec, typename F> - void write_int(unsigned num_digits, string_view prefix, - const Spec &spec, F f) { - std::size_t size = prefix.size() + num_digits; - char_type fill = static_cast<char_type>(spec.fill()); - std::size_t padding = 0; - if (spec.align() == ALIGN_NUMERIC) { - if (spec.width() > size) { - padding = spec.width() - size; - size = spec.width(); - } - } else if (spec.precision() > static_cast<int>(num_digits)) { - size = prefix.size() + static_cast<std::size_t>(spec.precision()); - padding = static_cast<std::size_t>(spec.precision()) - num_digits; - fill = '0'; - } - align_spec as = spec; - if (spec.align() == ALIGN_DEFAULT) - as.align_ = ALIGN_RIGHT; - write_padded(size, as, padded_int_writer<F>{prefix, fill, padding, f}); - } - - // Writes a decimal integer. - template <typename Int> - void write_decimal(Int value) { - typedef typename internal::int_traits<Int>::main_type main_type; - main_type abs_value = static_cast<main_type>(value); - bool is_negative = internal::is_negative(value); - if (is_negative) - abs_value = 0 - abs_value; - unsigned num_digits = internal::count_digits(abs_value); - auto &&it = reserve((is_negative ? 1 : 0) + num_digits); - if (is_negative) - *it++ = '-'; - it = internal::format_decimal(it, abs_value, num_digits); - } - - // The handle_int_type_spec handler that writes an integer. - template <typename Int, typename Spec> - struct int_writer { - typedef typename internal::int_traits<Int>::main_type unsigned_type; - - basic_writer<Range> &writer; - const Spec &spec; - unsigned_type abs_value; - char prefix[4]; - unsigned prefix_size; - - string_view get_prefix() const { return string_view(prefix, prefix_size); } - - // Counts the number of digits in abs_value. BITS = log2(radix). - template <unsigned BITS> - unsigned count_digits() const { - unsigned_type n = abs_value; - unsigned num_digits = 0; - do { - ++num_digits; - } while ((n >>= BITS) != 0); - return num_digits; - } - - int_writer(basic_writer<Range> &w, Int value, const Spec &s) - : writer(w), spec(s), abs_value(static_cast<unsigned_type>(value)), - prefix_size(0) { - if (internal::is_negative(value)) { - prefix[0] = '-'; - ++prefix_size; - abs_value = 0 - abs_value; - } else if (spec.flag(SIGN_FLAG)) { - prefix[0] = spec.flag(PLUS_FLAG) ? '+' : ' '; - ++prefix_size; - } - } - - struct dec_writer { - unsigned_type abs_value; - unsigned num_digits; - - template <typename It> - void operator()(It &&it) const { - it = internal::format_decimal(it, abs_value, num_digits); - } - }; - - void on_dec() { - unsigned num_digits = internal::count_digits(abs_value); - writer.write_int(num_digits, get_prefix(), spec, - dec_writer{abs_value, num_digits}); - } - - struct hex_writer { - int_writer &self; - unsigned num_digits; - - template <typename It> - void operator()(It &&it) const { - it = internal::format_uint<4>(it, self.abs_value, num_digits, - self.spec.type() != 'x'); - } - }; - - void on_hex() { - if (spec.flag(HASH_FLAG)) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast<char>(spec.type()); - } - unsigned num_digits = count_digits<4>(); - writer.write_int(num_digits, get_prefix(), spec, - hex_writer{*this, num_digits}); - } - - template <int BITS> - struct bin_writer { - unsigned_type abs_value; - unsigned num_digits; - - template <typename It> - void operator()(It &&it) const { - it = internal::format_uint<BITS>(it, abs_value, num_digits); - } - }; - - void on_bin() { - if (spec.flag(HASH_FLAG)) { - prefix[prefix_size++] = '0'; - prefix[prefix_size++] = static_cast<char>(spec.type()); - } - unsigned num_digits = count_digits<1>(); - writer.write_int(num_digits, get_prefix(), spec, - bin_writer<1>{abs_value, num_digits}); - } - - void on_oct() { - unsigned num_digits = count_digits<3>(); - if (spec.flag(HASH_FLAG) && - spec.precision() <= static_cast<int>(num_digits)) { - // Octal prefix '0' is counted as a digit, so only add it if precision - // is not greater than the number of digits. - prefix[prefix_size++] = '0'; - } - writer.write_int(num_digits, get_prefix(), spec, - bin_writer<3>{abs_value, num_digits}); - } - - enum { SEP_SIZE = 1 }; - - struct num_writer { - unsigned_type abs_value; - unsigned size; - char_type sep; - - template <typename It> - void operator()(It &&it) const { - basic_string_view<char_type> s(&sep, SEP_SIZE); - it = format_decimal(it, abs_value, size, - internal::add_thousands_sep<char_type>(s)); - } - }; - - void on_num() { - unsigned num_digits = internal::count_digits(abs_value); - char_type sep = internal::thousands_sep<char_type>(writer.locale_.get()); - unsigned size = num_digits + SEP_SIZE * ((num_digits - 1) / 3); - writer.write_int(size, get_prefix(), spec, - num_writer{abs_value, size, sep}); - } - - void on_error() { - FMT_THROW(format_error("invalid type specifier")); - } - }; - - // Writes a formatted integer. - template <typename T, typename Spec> - void write_int(T value, const Spec &spec) { - internal::handle_int_type_spec(spec.type(), - int_writer<T, Spec>(*this, value, spec)); - } - - enum {INF_SIZE = 3}; // This is an enum to workaround a bug in MSVC. - - struct inf_or_nan_writer { - char sign; - const char *str; - - template <typename It> - void operator()(It &&it) const { - if (sign) - *it++ = sign; - it = std::copy_n(str, static_cast<std::size_t>(INF_SIZE), it); - } - }; - - struct double_writer { - size_t n; - char sign; - basic_memory_buffer<char_type> &buffer; - - template <typename It> - void operator()(It &&it) { - if (sign) { - *it++ = sign; - --n; - } - it = std::copy_n(buffer.begin(), n, it); - } - }; - - // Formats a floating-point number (double or long double). - template <typename T> - void write_double(T value, const format_specs &spec); - template <typename T> - void write_double_sprintf(T value, const format_specs &spec, - internal::basic_buffer<char_type>& buffer); - - template <typename Char> - struct str_writer { - const Char *s; - std::size_t size; - - template <typename It> - void operator()(It &&it) const { - it = std::copy_n(s, size, it); - } - }; - - // Writes a formatted string. - template <typename Char> - void write_str(const Char *s, std::size_t size, const align_spec &spec) { - write_padded(size, spec, str_writer<Char>{s, size}); - } - - template <typename Char> - void write_str(basic_string_view<Char> str, const format_specs &spec); - - // Appends floating-point length specifier to the format string. - // The second argument is only used for overload resolution. - void append_float_length(char_type *&format_ptr, long double) { - *format_ptr++ = 'L'; - } - - template<typename T> - void append_float_length(char_type *&, T) {} - - template <typename Char> - friend class internal::arg_formatter_base; - - public: - /** Constructs a ``basic_writer`` object. */ - explicit basic_writer(Range out): out_(out.begin()) {} - - void write(int value) { write_decimal(value); } - void write(long value) { write_decimal(value); } - void write(long long value) { write_decimal(value); } - - void write(unsigned value) { write_decimal(value); } - void write(unsigned long value) { write_decimal(value); } - void write(unsigned long long value) { write_decimal(value); } - - /** - \rst - Formats *value* and writes it to the buffer. - \endrst - */ - template <typename T, typename FormatSpec, typename... FormatSpecs> - typename std::enable_if<std::is_integral<T>::value, void>::type - write(T value, FormatSpec spec, FormatSpecs... specs) { - format_specs s(spec, specs...); - s.align_ = ALIGN_RIGHT; - write_int(value, s); - } - - void write(double value) { - write_double(value, format_specs()); - } - - /** - \rst - Formats *value* using the general format for floating-point numbers - (``'g'``) and writes it to the buffer. - \endrst - */ - void write(long double value) { - write_double(value, format_specs()); - } - - /** Writes a character to the buffer. */ - void write(char value) { - *reserve(1) = value; - } - - void write(wchar_t value) { - internal::require_wchar<char_type>(); - *reserve(1) = value; - } - - /** - \rst - Writes *value* to the buffer. - \endrst - */ - void write(string_view value) { - auto &&it = reserve(value.size()); - it = std::copy(value.begin(), value.end(), it); - } - - void write(wstring_view value) { - internal::require_wchar<char_type>(); - auto &&it = reserve(value.size()); - it = std::copy(value.begin(), value.end(), it); - } - - template <typename... FormatSpecs> - void write(basic_string_view<char_type> str, FormatSpecs... specs) { - write_str(str, format_specs(specs...)); - } - - template <typename T> - typename std::enable_if<std::is_same<T, void>::value>::type - write(const T *p) { - format_specs specs; - specs.flags_ = HASH_FLAG; - specs.type_ = 'x'; - write_int(reinterpret_cast<uintptr_t>(p), specs); - } -}; - -template <typename Range> -template <typename F> -void basic_writer<Range>::write_padded( - std::size_t size, const align_spec &spec, F &&f) { - unsigned width = spec.width(); - if (width <= size) - return f(reserve(size)); - auto &&it = reserve(width); - char_type fill = static_cast<char_type>(spec.fill()); - std::size_t padding = width - size; - if (spec.align() == ALIGN_RIGHT) { - it = std::fill_n(it, padding, fill); - f(it); - } else if (spec.align() == ALIGN_CENTER) { - std::size_t left_padding = padding / 2; - it = std::fill_n(it, left_padding, fill); - f(it); - it = std::fill_n(it, padding - left_padding, fill); - } else { - f(it); - it = std::fill_n(it, padding, fill); - } -} - -template <typename Range> -template <typename Char> -void basic_writer<Range>::write_str( - basic_string_view<Char> s, const format_specs &spec) { - const Char *data = s.data(); - std::size_t size = s.size(); - std::size_t precision = static_cast<std::size_t>(spec.precision_); - if (spec.precision_ >= 0 && precision < size) - size = precision; - write_str(data, size, spec); -} - -template <typename Char> -struct float_spec_handler { - Char type; - bool upper; - - explicit float_spec_handler(Char t) : type(t), upper(false) {} - - void on_general() { - if (type == 'G') - upper = true; - else - type = 'g'; - } - - void on_exp() { - if (type == 'E') - upper = true; - } - - void on_fixed() { - if (type == 'F') { - upper = true; -#if FMT_MSC_VER - // MSVC's printf doesn't support 'F'. - type = 'f'; -#endif - } - } - - void on_hex() { - if (type == 'A') - upper = true; - } - - void on_error() { - FMT_THROW(format_error("invalid type specifier")); - } -}; - -template <typename Range> -template <typename T> -void basic_writer<Range>::write_double(T value, const format_specs &spec) { - // Check type. - float_spec_handler<char_type> handler(spec.type()); - internal::handle_float_type_spec(spec.type(), handler); - - char sign = 0; - // Use isnegative instead of value < 0 because the latter is always - // false for NaN. - if (internal::fputil::isnegative(static_cast<double>(value))) { - sign = '-'; - value = -value; - } else if (spec.flag(SIGN_FLAG)) { - sign = spec.flag(PLUS_FLAG) ? '+' : ' '; - } - - struct write_inf_or_nan_t { - basic_writer &writer; - format_specs spec; - char sign; - void operator()(const char *str) const { - writer.write_padded(INF_SIZE + (sign ? 1 : 0), spec, - inf_or_nan_writer{sign, str}); - } - } write_inf_or_nan = {*this, spec, sign}; - - // Format NaN and ininity ourselves because sprintf's output is not consistent - // across platforms. - if (internal::fputil::isnotanumber(value)) - return write_inf_or_nan(handler.upper ? "NAN" : "nan"); - if (internal::fputil::isinfinity(value)) - return write_inf_or_nan(handler.upper ? "INF" : "inf"); - - basic_memory_buffer<char_type> buffer; - char type = static_cast<char>(spec.type()); - if (internal::const_check( - internal::use_grisu() && sizeof(T) <= sizeof(double)) && - type != 'a' && type != 'A') { - char buf[100]; // TODO: correct buffer size - size_t size = 0; - internal::grisu2_format(static_cast<double>(value), buf, size, type, - spec.precision(), spec.flag(HASH_FLAG)); - FMT_ASSERT(size <= 100, "buffer overflow"); - buffer.append(buf, buf + size); // TODO: avoid extra copy - } else { - format_specs normalized_spec(spec); - normalized_spec.type_ = handler.type; - write_double_sprintf(value, normalized_spec, buffer); - } - size_t n = buffer.size(); - align_spec as = spec; - if (spec.align() == ALIGN_NUMERIC) { - if (sign) { - auto &&it = reserve(1); - *it++ = sign; - sign = 0; - if (as.width_) - --as.width_; - } - as.align_ = ALIGN_RIGHT; - } else { - if (spec.align() == ALIGN_DEFAULT) - as.align_ = ALIGN_RIGHT; - if (sign) - ++n; - } - write_padded(n, as, double_writer{n, sign, buffer}); -} - -template <typename Range> -template <typename T> -void basic_writer<Range>::write_double_sprintf( - T value, const format_specs &spec, - internal::basic_buffer<char_type>& buffer) { - // Buffer capacity must be non-zero, otherwise MSVC's vsnprintf_s will fail. - FMT_ASSERT(buffer.capacity() != 0, "empty buffer"); - - // Build format string. - enum { MAX_FORMAT_SIZE = 10}; // longest format: %#-*.*Lg - char_type format[MAX_FORMAT_SIZE]; - char_type *format_ptr = format; - *format_ptr++ = '%'; - if (spec.flag(HASH_FLAG)) - *format_ptr++ = '#'; - if (spec.precision() >= 0) { - *format_ptr++ = '.'; - *format_ptr++ = '*'; - } - - append_float_length(format_ptr, value); - *format_ptr++ = spec.type(); - *format_ptr = '\0'; - - // Format using snprintf. - char_type *start = FMT_NULL; - for (;;) { - std::size_t buffer_size = buffer.capacity(); - start = &buffer[0]; - int result = internal::char_traits<char_type>::format_float( - start, buffer_size, format, spec.precision(), value); - if (result >= 0) { - unsigned n = internal::to_unsigned(result); - if (n < buffer.capacity()) { - buffer.resize(n); - break; // The buffer is large enough - continue with formatting. - } - buffer.reserve(n + 1); - } else { - // If result is negative we ask to increase the capacity by at least 1, - // but as std::vector, the buffer grows exponentially. - buffer.reserve(buffer.capacity() + 1); - } - } -} - -// Reports a system error without throwing an exception. -// Can be used to report errors from destructors. -FMT_API void report_system_error(int error_code, - string_view message) FMT_NOEXCEPT; - -#if FMT_USE_WINDOWS_H - -/** A Windows error. */ -class windows_error : public system_error { - private: - FMT_API void init(int error_code, string_view format_str, format_args args); - - public: - /** - \rst - Constructs a :class:`fmt::windows_error` object with the description - of the form - - .. parsed-literal:: - *<message>*: *<system-message>* - - where *<message>* is the formatted message and *<system-message>* is the - system message corresponding to the error code. - *error_code* is a Windows error code as given by ``GetLastError``. - If *error_code* is not a valid error code such as -1, the system message - will look like "error -1". - - **Example**:: - - // This throws a windows_error with the description - // cannot open file 'madeup': The system cannot find the file specified. - // or similar (system message may vary). - const char *filename = "madeup"; - LPOFSTRUCT of = LPOFSTRUCT(); - HFILE file = OpenFile(filename, &of, OF_READ); - if (file == HFILE_ERROR) { - throw fmt::windows_error(GetLastError(), - "cannot open file '{}'", filename); - } - \endrst - */ - template <typename... Args> - windows_error(int error_code, string_view message, const Args & ... args) { - init(error_code, message, make_format_args(args...)); - } -}; - -// Reports a Windows error without throwing an exception. -// Can be used to report errors from destructors. -FMT_API void report_windows_error(int error_code, - string_view message) FMT_NOEXCEPT; - -#endif - -/** Fast integer formatter. */ -class format_int { - private: - // Buffer should be large enough to hold all digits (digits10 + 1), - // a sign and a null character. - enum {BUFFER_SIZE = std::numeric_limits<unsigned long long>::digits10 + 3}; - mutable char buffer_[BUFFER_SIZE]; - char *str_; - - // Formats value in reverse and returns a pointer to the beginning. - char *format_decimal(unsigned long long value) { - char *ptr = buffer_ + BUFFER_SIZE - 1; - while (value >= 100) { - // Integer division is slow so do it for a group of two digits instead - // of for every digit. The idea comes from the talk by Alexandrescu - // "Three Optimization Tips for C++". See speed-test for a comparison. - unsigned index = static_cast<unsigned>((value % 100) * 2); - value /= 100; - *--ptr = internal::data::DIGITS[index + 1]; - *--ptr = internal::data::DIGITS[index]; - } - if (value < 10) { - *--ptr = static_cast<char>('0' + value); - return ptr; - } - unsigned index = static_cast<unsigned>(value * 2); - *--ptr = internal::data::DIGITS[index + 1]; - *--ptr = internal::data::DIGITS[index]; - return ptr; - } - - void format_signed(long long value) { - unsigned long long abs_value = static_cast<unsigned long long>(value); - bool negative = value < 0; - if (negative) - abs_value = 0 - abs_value; - str_ = format_decimal(abs_value); - if (negative) - *--str_ = '-'; - } - - public: - explicit format_int(int value) { format_signed(value); } - explicit format_int(long value) { format_signed(value); } - explicit format_int(long long value) { format_signed(value); } - explicit format_int(unsigned value) : str_(format_decimal(value)) {} - explicit format_int(unsigned long value) : str_(format_decimal(value)) {} - explicit format_int(unsigned long long value) : str_(format_decimal(value)) {} - - /** Returns the number of characters written to the output buffer. */ - std::size_t size() const { - return internal::to_unsigned(buffer_ - str_ + BUFFER_SIZE - 1); - } - - /** - Returns a pointer to the output buffer content. No terminating null - character is appended. - */ - const char *data() const { return str_; } - - /** - Returns a pointer to the output buffer content with terminating null - character appended. - */ - const char *c_str() const { - buffer_[BUFFER_SIZE - 1] = '\0'; - return str_; - } - - /** - \rst - Returns the content of the output buffer as an ``std::string``. - \endrst - */ - std::string str() const { return std::string(str_, size()); } -}; - -// Formats a decimal integer value writing into buffer and returns -// a pointer to the end of the formatted string. This function doesn't -// write a terminating null character. -template <typename T> -inline void format_decimal(char *&buffer, T value) { - typedef typename internal::int_traits<T>::main_type main_type; - main_type abs_value = static_cast<main_type>(value); - if (internal::is_negative(value)) { - *buffer++ = '-'; - abs_value = 0 - abs_value; - } - if (abs_value < 100) { - if (abs_value < 10) { - *buffer++ = static_cast<char>('0' + abs_value); - return; - } - unsigned index = static_cast<unsigned>(abs_value * 2); - *buffer++ = internal::data::DIGITS[index]; - *buffer++ = internal::data::DIGITS[index + 1]; - return; - } - unsigned num_digits = internal::count_digits(abs_value); - internal::format_decimal(buffer, abs_value, num_digits); - buffer += num_digits; -} - -// Formatter of objects of type T. -template <typename T, typename Char> -struct formatter< - T, Char, - typename std::enable_if<internal::format_type< - typename buffer_context<Char>::type, T>::value>::type> { - - // Parses format specifiers stopping either at the end of the range or at the - // terminating '}'. - template <typename ParseContext> - FMT_CONSTEXPR typename ParseContext::iterator parse(ParseContext &ctx) { - auto it = internal::null_terminating_iterator<Char>(ctx); - typedef internal::dynamic_specs_handler<ParseContext> handler_type; - auto type = internal::get_type< - typename buffer_context<Char>::type, T>::value; - internal::specs_checker<handler_type> - handler(handler_type(specs_, ctx), type); - it = parse_format_specs(it, handler); - auto type_spec = specs_.type(); - auto eh = ctx.error_handler(); - switch (type) { - case internal::none_type: - case internal::named_arg_type: - FMT_ASSERT(false, "invalid argument type"); - break; - case internal::int_type: - case internal::uint_type: - case internal::long_long_type: - case internal::ulong_long_type: - case internal::bool_type: - handle_int_type_spec( - type_spec, internal::int_type_checker<decltype(eh)>(eh)); - break; - case internal::char_type: - handle_char_specs( - &specs_, - internal::char_specs_checker<decltype(eh), decltype(type_spec)>( - type_spec, eh)); - break; - case internal::double_type: - case internal::long_double_type: - handle_float_type_spec( - type_spec, internal::float_type_checker<decltype(eh)>(eh)); - break; - case internal::cstring_type: - internal::handle_cstring_type_spec( - type_spec, internal::cstring_type_checker<decltype(eh)>(eh)); - break; - case internal::string_type: - internal::check_string_type_spec(type_spec, eh); - break; - case internal::pointer_type: - internal::check_pointer_type_spec(type_spec, eh); - break; - case internal::custom_type: - // Custom format specifiers should be checked in parse functions of - // formatter specializations. - break; - } - return pointer_from(it); - } - - template <typename FormatContext> - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { - internal::handle_dynamic_spec<internal::width_checker>( - specs_.width_, specs_.width_ref, ctx); - internal::handle_dynamic_spec<internal::precision_checker>( - specs_.precision_, specs_.precision_ref, ctx); - typedef output_range<typename FormatContext::iterator, - typename FormatContext::char_type> range_type; - return fmt::visit(arg_formatter<range_type>(ctx, &specs_), - internal::make_arg<FormatContext>(val)); - } - - private: - internal::dynamic_format_specs<Char> specs_; -}; - -// A formatter for types known only at run time such as variant alternatives. -// -// Usage: -// typedef std::variant<int, std::string> variant; -// template <> -// struct formatter<variant>: dynamic_formatter<> { -// void format(buffer &buf, const variant &v, context &ctx) { -// visit([&](const auto &val) { format(buf, val, ctx); }, v); -// } -// }; -template <typename Char = char> -class dynamic_formatter { - private: - struct null_handler: internal::error_handler { - void on_align(alignment) {} - void on_plus() {} - void on_minus() {} - void on_space() {} - void on_hash() {} - }; - - public: - template <typename ParseContext> - auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - auto it = internal::null_terminating_iterator<Char>(ctx); - // Checks are deferred to formatting time when the argument type is known. - internal::dynamic_specs_handler<ParseContext> handler(specs_, ctx); - it = parse_format_specs(it, handler); - return pointer_from(it); - } - - template <typename T, typename FormatContext> - auto format(const T &val, FormatContext &ctx) -> decltype(ctx.out()) { - handle_specs(ctx); - internal::specs_checker<null_handler> - checker(null_handler(), internal::get_type<FormatContext, T>::value); - checker.on_align(specs_.align()); - if (specs_.flags_ == 0) { - // Do nothing. - } else if (specs_.flag(SIGN_FLAG)) { - if (specs_.flag(PLUS_FLAG)) - checker.on_plus(); - else - checker.on_space(); - } else if (specs_.flag(MINUS_FLAG)) { - checker.on_minus(); - } else if (specs_.flag(HASH_FLAG)) { - checker.on_hash(); - } - if (specs_.precision_ != -1) - checker.end_precision(); - typedef output_range<typename FormatContext::iterator, - typename FormatContext::char_type> range; - fmt::visit(arg_formatter<range>(ctx, &specs_), - internal::make_arg<FormatContext>(val)); - return ctx.out(); - } - - private: - template <typename Context> - void handle_specs(Context &ctx) { - internal::handle_dynamic_spec<internal::width_checker>( - specs_.width_, specs_.width_ref, ctx); - internal::handle_dynamic_spec<internal::precision_checker>( - specs_.precision_, specs_.precision_ref, ctx); - } - - internal::dynamic_format_specs<Char> specs_; -}; - -template <typename Range, typename Char> -typename basic_format_context<Range, Char>::format_arg - basic_format_context<Range, Char>::get_arg( - basic_string_view<char_type> name) { - map_.init(this->args()); - format_arg arg = map_.find(name); - if (arg.type() == internal::none_type) - this->on_error("argument not found"); - return arg; -} - -template <typename ArgFormatter, typename Char, typename Context> -struct format_handler : internal::error_handler { - typedef internal::null_terminating_iterator<Char> iterator; - typedef typename ArgFormatter::range range; - - format_handler(range r, basic_string_view<Char> str, - basic_format_args<Context> format_args) - : context(r.begin(), str, format_args) {} - - void on_text(const Char *begin, const Char *end) { - auto size = internal::to_unsigned(end - begin); - auto out = context.out(); - auto &&it = internal::reserve(out, size); - it = std::copy_n(begin, size, it); - context.advance_to(out); - } - - void on_arg_id() { arg = context.next_arg(); } - void on_arg_id(unsigned id) { - context.parse_context().check_arg_id(id); - arg = context.get_arg(id); - } - void on_arg_id(basic_string_view<Char> id) { - arg = context.get_arg(id); - } - - void on_replacement_field(const Char *p) { - context.parse_context().advance_to(p); - if (!fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) - context.advance_to(fmt::visit(ArgFormatter(context), arg)); - } - - iterator on_format_specs(iterator it) { - auto& parse_ctx = context.parse_context(); - parse_ctx.advance_to(pointer_from(it)); - if (fmt::visit(internal::custom_formatter<Char, Context>(context), arg)) - return iterator(parse_ctx); - basic_format_specs<Char> specs; - using internal::specs_handler; - internal::specs_checker<specs_handler<Context>> - handler(specs_handler<Context>(specs, context), arg.type()); - it = parse_format_specs(it, handler); - if (*it != '}') - on_error("missing '}' in format string"); - parse_ctx.advance_to(pointer_from(it)); - context.advance_to(fmt::visit(ArgFormatter(context, &specs), arg)); - return it; - } - - Context context; - basic_format_arg<Context> arg; -}; - -/** Formats arguments and writes the output to the range. */ -template <typename ArgFormatter, typename Char, typename Context> -typename Context::iterator vformat_to(typename ArgFormatter::range out, - basic_string_view<Char> format_str, - basic_format_args<Context> args) { - format_handler<ArgFormatter, Char, Context> h(out, format_str, args); - internal::parse_format_string<false>(format_str, h); - return h.context.out(); -} - -// Casts ``p`` to ``const void*`` for pointer formatting. -// Example: -// auto s = format("{}", ptr(p)); -template <typename T> -inline const void *ptr(const T *p) { return p; } - -template <typename It, typename Char> -struct arg_join { - It begin; - It end; - basic_string_view<Char> sep; - - arg_join(It begin, It end, basic_string_view<Char> sep) - : begin(begin), end(end), sep(sep) {} -}; - -template <typename It, typename Char> -struct formatter<arg_join<It, Char>, Char>: - formatter<typename std::iterator_traits<It>::value_type, Char> { - template <typename FormatContext> - auto format(const arg_join<It, Char> &value, FormatContext &ctx) - -> decltype(ctx.out()) { - typedef formatter<typename std::iterator_traits<It>::value_type, Char> base; - auto it = value.begin; - auto out = ctx.out(); - if (it != value.end) { - out = base::format(*it++, ctx); - while (it != value.end) { - out = std::copy(value.sep.begin(), value.sep.end(), out); - ctx.advance_to(out); - out = base::format(*it++, ctx); - } - } - return out; - } -}; - -template <typename It> -arg_join<It, char> join(It begin, It end, string_view sep) { - return arg_join<It, char>(begin, end, sep); -} - -template <typename It> -arg_join<It, wchar_t> join(It begin, It end, wstring_view sep) { - return arg_join<It, wchar_t>(begin, end, sep); -} - -// The following causes ICE in gcc 4.4. -#if FMT_USE_TRAILING_RETURN && (!FMT_GCC_VERSION || FMT_GCC_VERSION >= 405) -template <typename Range> -auto join(const Range &range, string_view sep) - -> arg_join<decltype(internal::begin(range)), char> { - return join(internal::begin(range), internal::end(range), sep); -} - -template <typename Range> -auto join(const Range &range, wstring_view sep) - -> arg_join<decltype(internal::begin(range)), wchar_t> { - return join(internal::begin(range), internal::end(range), sep); -} -#endif - -/** - \rst - Converts *value* to ``std::string`` using the default format for type *T*. - It doesn't support user-defined types with custom formatters. - - **Example**:: - - #include <fmt/format.h> - - std::string answer = fmt::to_string(42); - \endrst - */ -template <typename T> -std::string to_string(const T &value) { - std::string str; - internal::container_buffer<std::string> buf(str); - writer(buf).write(value); - return str; -} - -/** - Converts *value* to ``std::wstring`` using the default format for type *T*. - */ -template <typename T> -std::wstring to_wstring(const T &value) { - std::wstring str; - internal::container_buffer<std::wstring> buf(str); - wwriter(buf).write(value); - return str; -} - -template <typename Char, std::size_t SIZE> -std::basic_string<Char> to_string(const basic_memory_buffer<Char, SIZE> &buf) { - return std::basic_string<Char>(buf.data(), buf.size()); -} - -inline format_context::iterator vformat_to( - internal::buffer &buf, string_view format_str, format_args args) { - typedef back_insert_range<internal::buffer> range; - return vformat_to<arg_formatter<range>>(buf, format_str, args); -} - -inline wformat_context::iterator vformat_to( - internal::wbuffer &buf, wstring_view format_str, wformat_args args) { - typedef back_insert_range<internal::wbuffer> range; - return vformat_to<arg_formatter<range>>(buf, format_str, args); -} - -template < - typename String, typename... Args, - std::size_t SIZE = inline_buffer_size, - typename Char = typename internal::format_string_traits<String>::char_type> -inline typename buffer_context<Char>::type::iterator format_to( - basic_memory_buffer<Char, SIZE> &buf, const String &format_str, - const Args & ... args) { - internal::check_format_string<Args...>(format_str); - return vformat_to( - buf, basic_string_view<Char>(format_str), - make_format_args<typename buffer_context<Char>::type>(args...)); -} - -template <typename OutputIt, typename Char = char> -//using format_context_t = basic_format_context<OutputIt, Char>; -struct format_context_t { typedef basic_format_context<OutputIt, Char> type; }; - -template <typename OutputIt, typename Char = char> -//using format_args_t = basic_format_args<format_context_t<OutputIt, Char>>; -struct format_args_t { - typedef basic_format_args< - typename format_context_t<OutputIt, Char>::type> type; -}; - -template <typename OutputIt, typename... Args> -inline OutputIt vformat_to(OutputIt out, string_view format_str, - typename format_args_t<OutputIt>::type args) { - typedef output_range<OutputIt, char> range; - return vformat_to<arg_formatter<range>>(range(out), format_str, args); -} -template <typename OutputIt, typename... Args> -inline OutputIt vformat_to( - OutputIt out, wstring_view format_str, - typename format_args_t<OutputIt, wchar_t>::type args) { - typedef output_range<OutputIt, wchar_t> range; - return vformat_to<arg_formatter<range>>(range(out), format_str, args); -} - -/** - \rst - Formats arguments, writes the result to the output iterator ``out`` and returns - the iterator past the end of the output range. - - **Example**:: - - std::vector<char> out; - fmt::format_to(std::back_inserter(out), "{}", 42); - \endrst - */ -template <typename OutputIt, typename... Args> -inline OutputIt format_to(OutputIt out, string_view format_str, - const Args & ... args) { - return vformat_to(out, format_str, - make_format_args<typename format_context_t<OutputIt>::type>(args...)); -} - -template <typename OutputIt> -struct format_to_n_result { - /** Iterator past the end of the output range. */ - OutputIt out; - /** Total (not truncated) output size. */ - std::size_t size; -}; - -template <typename OutputIt> -using format_to_n_context = typename fmt::format_context_t< - fmt::internal::truncating_iterator<OutputIt>>::type; - -template <typename OutputIt> -using format_to_n_args = fmt::basic_format_args<format_to_n_context<OutputIt>>; - -template <typename OutputIt, typename ...Args> -inline format_arg_store<format_to_n_context<OutputIt>, Args...> - make_format_to_n_args(const Args & ... args) { - return format_arg_store<format_to_n_context<OutputIt>, Args...>(args...); -} - -template <typename OutputIt, typename... Args> -inline format_to_n_result<OutputIt> vformat_to_n( - OutputIt out, std::size_t n, string_view format_str, - format_to_n_args<OutputIt> args) { - typedef internal::truncating_iterator<OutputIt> It; - auto it = vformat_to(It(out, n), format_str, args); - return {it.base(), it.count()}; -} - -/** - \rst - Formats arguments, writes up to ``n`` characters of the result to the output - iterator ``out`` and returns the total output size and the iterator past the - end of the output range. - \endrst - */ -template <typename OutputIt, typename... Args> -inline format_to_n_result<OutputIt> format_to_n( - OutputIt out, std::size_t n, string_view format_str, const Args &... args) { - return vformat_to_n<OutputIt>( - out, n, format_str, make_format_to_n_args<OutputIt>(args...)); -} -template <typename OutputIt, typename... Args> -inline format_to_n_result<OutputIt> format_to_n( - OutputIt out, std::size_t n, wstring_view format_str, - const Args &... args) { - typedef internal::truncating_iterator<OutputIt> It; - auto it = vformat_to(It(out, n), format_str, - make_format_args<typename format_context_t<It, wchar_t>::type>(args...)); - return {it.base(), it.count()}; -} - -template <typename Char> -inline std::basic_string<Char> internal::vformat( - basic_string_view<Char> format_str, - basic_format_args<typename buffer_context<Char>::type> args) { - basic_memory_buffer<Char> buffer; - vformat_to(buffer, format_str, args); - return fmt::to_string(buffer); -} - -template <typename String, typename... Args> -inline typename std::enable_if<internal::is_compile_string<String>::value>::type - print(String format_str, const Args & ... args) { - internal::check_format_string<Args...>(format_str); - return vprint(format_str.data(), make_format_args(args...)); -} - -/** - Returns the number of characters in the output of - ``format(format_str, args...)``. - */ -template <typename... Args> -inline std::size_t formatted_size(string_view format_str, - const Args & ... args) { - auto it = format_to(internal::counting_iterator<char>(), format_str, args...); - return it.count(); -} - -#if FMT_USE_USER_DEFINED_LITERALS -namespace internal { - -# if FMT_UDL_TEMPLATE -template <typename Char, Char... CHARS> -class udl_formatter { - public: - template <typename... Args> - std::basic_string<Char> operator()(const Args &... args) const { - FMT_CONSTEXPR_DECL Char s[] = {CHARS..., '\0'}; - FMT_CONSTEXPR_DECL bool invalid_format = - check_format_string<Char, error_handler, Args...>( - basic_string_view<Char>(s, sizeof...(CHARS))); - (void)invalid_format; - return format(s, args...); - } -}; -# else -template <typename Char> -struct udl_formatter { - const Char *str; - - template <typename... Args> - auto operator()(Args && ... args) const - -> decltype(format(str, std::forward<Args>(args)...)) { - return format(str, std::forward<Args>(args)...); - } -}; -# endif // FMT_UDL_TEMPLATE - -template <typename Char> -struct udl_arg { - const Char *str; - - template <typename T> - named_arg<T, Char> operator=(T &&value) const { - return {str, std::forward<T>(value)}; - } -}; - -} // namespace internal - -inline namespace literals { - -# if FMT_UDL_TEMPLATE -template <typename Char, Char... CHARS> -FMT_CONSTEXPR internal::udl_formatter<Char, CHARS...> operator""_format() { - return {}; -} -# else -/** - \rst - User-defined literal equivalent of :func:`fmt::format`. - - **Example**:: - - using namespace fmt::literals; - std::string message = "The answer is {}"_format(42); - \endrst - */ -inline internal::udl_formatter<char> -operator"" _format(const char *s, std::size_t) { return {s}; } -inline internal::udl_formatter<wchar_t> -operator"" _format(const wchar_t *s, std::size_t) { return {s}; } -# endif // FMT_UDL_TEMPLATE - -/** - \rst - User-defined literal equivalent of :func:`fmt::arg`. - - **Example**:: - - using namespace fmt::literals; - fmt::print("Elapsed time: {s:.2f} seconds", "s"_a=1.23); - \endrst - */ -inline internal::udl_arg<char> -operator"" _a(const char *s, std::size_t) { return {s}; } -inline internal::udl_arg<wchar_t> -operator"" _a(const wchar_t *s, std::size_t) { return {s}; } -} // inline namespace literals -#endif // FMT_USE_USER_DEFINED_LITERALS -FMT_END_NAMESPACE - -#define FMT_STRING(s) [] { \ - typedef typename std::decay<decltype(s)>::type pointer; \ - struct S : fmt::compile_string { \ - static FMT_CONSTEXPR pointer data() { return s; } \ - static FMT_CONSTEXPR size_t size() { return sizeof(s); } \ - explicit operator fmt::string_view() const { return s; } \ - }; \ - return S{}; \ - }() - -#if defined(FMT_STRING_ALIAS) && FMT_STRING_ALIAS -/** - \rst - Constructs a compile-time format string. This macro is disabled by default to - prevent potential name collisions. To enable it define ``FMT_STRING_ALIAS`` to - 1 before including ``fmt/format.h``. - - **Example**:: - - #define FMT_STRING_ALIAS 1 - #include <fmt/format.h> - // A compile-time error because 'd' is an invalid specifier for strings. - std::string s = format(fmt("{:d}"), "foo"); - \endrst - */ -# define fmt(s) FMT_STRING(s) -#endif - -#ifdef FMT_HEADER_ONLY -# define FMT_FUNC inline -# include "format-inl.h" -#else -# define FMT_FUNC -#endif - -// Restore warnings. -#if FMT_GCC_VERSION >= 406 || FMT_CLANG_VERSION -# pragma GCC diagnostic pop -#endif - -#endif // FMT_FORMAT_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/ostream.h b/src/nmodl/ext/spdlog/fmt/bundled/ostream.h deleted file mode 100644 index e467f1ae6a..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/ostream.h +++ /dev/null @@ -1,157 +0,0 @@ -// Formatting library for C++ - std::ostream support -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_OSTREAM_H_ -#define FMT_OSTREAM_H_ - -#include "format.h" -#include <ostream> - -FMT_BEGIN_NAMESPACE -namespace internal { - -template <class Char> -class formatbuf : public std::basic_streambuf<Char> { - private: - typedef typename std::basic_streambuf<Char>::int_type int_type; - typedef typename std::basic_streambuf<Char>::traits_type traits_type; - - basic_buffer<Char> &buffer_; - - public: - formatbuf(basic_buffer<Char> &buffer) : buffer_(buffer) {} - - protected: - // The put-area is actually always empty. This makes the implementation - // simpler and has the advantage that the streambuf and the buffer are always - // in sync and sputc never writes into uninitialized memory. The obvious - // disadvantage is that each call to sputc always results in a (virtual) call - // to overflow. There is no disadvantage here for sputn since this always - // results in a call to xsputn. - - int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE { - if (!traits_type::eq_int_type(ch, traits_type::eof())) - buffer_.push_back(static_cast<Char>(ch)); - return ch; - } - - std::streamsize xsputn(const Char *s, std::streamsize count) FMT_OVERRIDE { - buffer_.append(s, s + count); - return count; - } -}; - -template <typename Char> -struct test_stream : std::basic_ostream<Char> { - private: - struct null; - // Hide all operator<< from std::basic_ostream<Char>. - void operator<<(null); -}; - -// Checks if T has a user-defined operator<< (e.g. not a member of std::ostream). -template <typename T, typename Char> -class is_streamable { - private: - template <typename U> - static decltype( - internal::declval<test_stream<Char>&>() - << internal::declval<U>(), std::true_type()) test(int); - - template <typename> - static std::false_type test(...); - - typedef decltype(test<T>(0)) result; - - public: - static const bool value = result::value; -}; - -// Write the content of buf to os. -template <typename Char> -void write(std::basic_ostream<Char> &os, basic_buffer<Char> &buf) { - const Char *data = buf.data(); - typedef std::make_unsigned<std::streamsize>::type UnsignedStreamSize; - UnsignedStreamSize size = buf.size(); - UnsignedStreamSize max_size = - internal::to_unsigned((std::numeric_limits<std::streamsize>::max)()); - do { - UnsignedStreamSize n = size <= max_size ? size : max_size; - os.write(data, static_cast<std::streamsize>(n)); - data += n; - size -= n; - } while (size != 0); -} - -template <typename Char, typename T> -void format_value(basic_buffer<Char> &buffer, const T &value) { - internal::formatbuf<Char> format_buf(buffer); - std::basic_ostream<Char> output(&format_buf); - output.exceptions(std::ios_base::failbit | std::ios_base::badbit); - output << value; - buffer.resize(buffer.size()); -} -} // namespace internal - -// Disable conversion to int if T has an overloaded operator<< which is a free -// function (not a member of std::ostream). -template <typename T, typename Char> -struct convert_to_int<T, Char, void> { - static const bool value = - convert_to_int<T, Char, int>::value && - !internal::is_streamable<T, Char>::value; -}; - -// Formats an object of type T that has an overloaded ostream operator<<. -template <typename T, typename Char> -struct formatter<T, Char, - typename std::enable_if< - internal::is_streamable<T, Char>::value && - !internal::format_type< - typename buffer_context<Char>::type, T>::value>::type> - : formatter<basic_string_view<Char>, Char> { - - template <typename Context> - auto format(const T &value, Context &ctx) -> decltype(ctx.out()) { - basic_memory_buffer<Char> buffer; - internal::format_value(buffer, value); - basic_string_view<Char> str(buffer.data(), buffer.size()); - return formatter<basic_string_view<Char>, Char>::format(str, ctx); - } -}; - -template <typename Char> -inline void vprint(std::basic_ostream<Char> &os, - basic_string_view<Char> format_str, - basic_format_args<typename buffer_context<Char>::type> args) { - basic_memory_buffer<Char> buffer; - vformat_to(buffer, format_str, args); - internal::write(os, buffer); -} -/** - \rst - Prints formatted data to the stream *os*. - - **Example**:: - - fmt::print(cerr, "Don't {}!", "panic"); - \endrst - */ -template <typename... Args> -inline void print(std::ostream &os, string_view format_str, - const Args & ... args) { - vprint<char>(os, format_str, make_format_args<format_context>(args...)); -} - -template <typename... Args> -inline void print(std::wostream &os, wstring_view format_str, - const Args & ... args) { - vprint<wchar_t>(os, format_str, make_format_args<wformat_context>(args...)); -} -FMT_END_NAMESPACE - -#endif // FMT_OSTREAM_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/posix.h b/src/nmodl/ext/spdlog/fmt/bundled/posix.h deleted file mode 100644 index 7bf877b454..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/posix.h +++ /dev/null @@ -1,324 +0,0 @@ -// A C++ interface to POSIX functions. -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_POSIX_H_ -#define FMT_POSIX_H_ - -#if defined(__MINGW32__) || defined(__CYGWIN__) -// Workaround MinGW bug https://sourceforge.net/p/mingw/bugs/2024/. -# undef __STRICT_ANSI__ -#endif - -#include <errno.h> -#include <fcntl.h> // for O_RDONLY -#include <locale.h> // for locale_t -#include <stdio.h> -#include <stdlib.h> // for strtod_l - -#include <cstddef> - -#if defined __APPLE__ || defined(__FreeBSD__) -# include <xlocale.h> // for LC_NUMERIC_MASK on OS X -#endif - -#include "format.h" - -#ifndef FMT_POSIX -# if defined(_WIN32) && !defined(__MINGW32__) -// Fix warnings about deprecated symbols. -# define FMT_POSIX(call) _##call -# else -# define FMT_POSIX(call) call -# endif -#endif - -// Calls to system functions are wrapped in FMT_SYSTEM for testability. -#ifdef FMT_SYSTEM -# define FMT_POSIX_CALL(call) FMT_SYSTEM(call) -#else -# define FMT_SYSTEM(call) call -# ifdef _WIN32 -// Fix warnings about deprecated symbols. -# define FMT_POSIX_CALL(call) ::_##call -# else -# define FMT_POSIX_CALL(call) ::call -# endif -#endif - -// Retries the expression while it evaluates to error_result and errno -// equals to EINTR. -#ifndef _WIN32 -# define FMT_RETRY_VAL(result, expression, error_result) \ - do { \ - result = (expression); \ - } while (result == error_result && errno == EINTR) -#else -# define FMT_RETRY_VAL(result, expression, error_result) result = (expression) -#endif - -#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1) - -FMT_BEGIN_NAMESPACE - -/** - \rst - A reference to a null-terminated string. It can be constructed from a C - string or ``std::string``. - - You can use one of the following typedefs for common character types: - - +---------------+-----------------------------+ - | Type | Definition | - +===============+=============================+ - | cstring_view | basic_cstring_view<char> | - +---------------+-----------------------------+ - | wcstring_view | basic_cstring_view<wchar_t> | - +---------------+-----------------------------+ - - This class is most useful as a parameter type to allow passing - different types of strings to a function, for example:: - - template <typename... Args> - std::string format(cstring_view format_str, const Args & ... args); - - format("{}", 42); - format(std::string("{}"), 42); - \endrst - */ -template <typename Char> -class basic_cstring_view { - private: - const Char *data_; - - public: - /** Constructs a string reference object from a C string. */ - basic_cstring_view(const Char *s) : data_(s) {} - - /** - \rst - Constructs a string reference from an ``std::string`` object. - \endrst - */ - basic_cstring_view(const std::basic_string<Char> &s) : data_(s.c_str()) {} - - /** Returns the pointer to a C string. */ - const Char *c_str() const { return data_; } -}; - -typedef basic_cstring_view<char> cstring_view; -typedef basic_cstring_view<wchar_t> wcstring_view; - -// An error code. -class error_code { - private: - int value_; - - public: - explicit error_code(int value = 0) FMT_NOEXCEPT : value_(value) {} - - int get() const FMT_NOEXCEPT { return value_; } -}; - -// A buffered file. -class buffered_file { - private: - FILE *file_; - - friend class file; - - explicit buffered_file(FILE *f) : file_(f) {} - - public: - // Constructs a buffered_file object which doesn't represent any file. - buffered_file() FMT_NOEXCEPT : file_(FMT_NULL) {} - - // Destroys the object closing the file it represents if any. - FMT_API ~buffered_file() FMT_DTOR_NOEXCEPT; - - private: - buffered_file(const buffered_file &) = delete; - void operator=(const buffered_file &) = delete; - - - public: - buffered_file(buffered_file &&other) FMT_NOEXCEPT : file_(other.file_) { - other.file_ = FMT_NULL; - } - - buffered_file& operator=(buffered_file &&other) { - close(); - file_ = other.file_; - other.file_ = FMT_NULL; - return *this; - } - - // Opens a file. - FMT_API buffered_file(cstring_view filename, cstring_view mode); - - // Closes the file. - FMT_API void close(); - - // Returns the pointer to a FILE object representing this file. - FILE *get() const FMT_NOEXCEPT { return file_; } - - // We place parentheses around fileno to workaround a bug in some versions - // of MinGW that define fileno as a macro. - FMT_API int (fileno)() const; - - void vprint(string_view format_str, format_args args) { - fmt::vprint(file_, format_str, args); - } - - template <typename... Args> - inline void print(string_view format_str, const Args & ... args) { - vprint(format_str, make_format_args(args...)); - } -}; - -// A file. Closed file is represented by a file object with descriptor -1. -// Methods that are not declared with FMT_NOEXCEPT may throw -// fmt::system_error in case of failure. Note that some errors such as -// closing the file multiple times will cause a crash on Windows rather -// than an exception. You can get standard behavior by overriding the -// invalid parameter handler with _set_invalid_parameter_handler. -class file { - private: - int fd_; // File descriptor. - - // Constructs a file object with a given descriptor. - explicit file(int fd) : fd_(fd) {} - - public: - // Possible values for the oflag argument to the constructor. - enum { - RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only. - WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only. - RDWR = FMT_POSIX(O_RDWR) // Open for reading and writing. - }; - - // Constructs a file object which doesn't represent any file. - file() FMT_NOEXCEPT : fd_(-1) {} - - // Opens a file and constructs a file object representing this file. - FMT_API file(cstring_view path, int oflag); - - private: - file(const file &) = delete; - void operator=(const file &) = delete; - - public: - file(file &&other) FMT_NOEXCEPT : fd_(other.fd_) { - other.fd_ = -1; - } - - file& operator=(file &&other) { - close(); - fd_ = other.fd_; - other.fd_ = -1; - return *this; - } - - // Destroys the object closing the file it represents if any. - FMT_API ~file() FMT_DTOR_NOEXCEPT; - - // Returns the file descriptor. - int descriptor() const FMT_NOEXCEPT { return fd_; } - - // Closes the file. - FMT_API void close(); - - // Returns the file size. The size has signed type for consistency with - // stat::st_size. - FMT_API long long size() const; - - // Attempts to read count bytes from the file into the specified buffer. - FMT_API std::size_t read(void *buffer, std::size_t count); - - // Attempts to write count bytes from the specified buffer to the file. - FMT_API std::size_t write(const void *buffer, std::size_t count); - - // Duplicates a file descriptor with the dup function and returns - // the duplicate as a file object. - FMT_API static file dup(int fd); - - // Makes fd be the copy of this file descriptor, closing fd first if - // necessary. - FMT_API void dup2(int fd); - - // Makes fd be the copy of this file descriptor, closing fd first if - // necessary. - FMT_API void dup2(int fd, error_code &ec) FMT_NOEXCEPT; - - // Creates a pipe setting up read_end and write_end file objects for reading - // and writing respectively. - FMT_API static void pipe(file &read_end, file &write_end); - - // Creates a buffered_file object associated with this file and detaches - // this file object from the file. - FMT_API buffered_file fdopen(const char *mode); -}; - -// Returns the memory page size. -long getpagesize(); - -#if (defined(LC_NUMERIC_MASK) || defined(_MSC_VER)) && \ - !defined(__ANDROID__) && !defined(__CYGWIN__) && !defined(__OpenBSD__) && \ - !defined(__NEWLIB_H__) -# define FMT_LOCALE -#endif - -#ifdef FMT_LOCALE -// A "C" numeric locale. -class Locale { - private: -# ifdef _MSC_VER - typedef _locale_t locale_t; - - enum { LC_NUMERIC_MASK = LC_NUMERIC }; - - static locale_t newlocale(int category_mask, const char *locale, locale_t) { - return _create_locale(category_mask, locale); - } - - static void freelocale(locale_t locale) { - _free_locale(locale); - } - - static double strtod_l(const char *nptr, char **endptr, _locale_t locale) { - return _strtod_l(nptr, endptr, locale); - } -# endif - - locale_t locale_; - - Locale(const Locale &) = delete; - void operator=(const Locale &) = delete; - - public: - typedef locale_t Type; - - Locale() : locale_(newlocale(LC_NUMERIC_MASK, "C", FMT_NULL)) { - if (!locale_) - FMT_THROW(system_error(errno, "cannot create locale")); - } - ~Locale() { freelocale(locale_); } - - Type get() const { return locale_; } - - // Converts string to floating-point number and advances str past the end - // of the parsed input. - double strtod(const char *&str) const { - char *end = FMT_NULL; - double result = strtod_l(str, &end, locale_); - str = end; - return result; - } -}; -#endif // FMT_LOCALE -FMT_END_NAMESPACE - -#endif // FMT_POSIX_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/printf.h b/src/nmodl/ext/spdlog/fmt/bundled/printf.h deleted file mode 100644 index 190784df0a..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/printf.h +++ /dev/null @@ -1,726 +0,0 @@ -// Formatting library for C++ -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_PRINTF_H_ -#define FMT_PRINTF_H_ - -#include <algorithm> // std::fill_n -#include <limits> // std::numeric_limits - -#include "ostream.h" - -FMT_BEGIN_NAMESPACE -namespace internal { - -// Checks if a value fits in int - used to avoid warnings about comparing -// signed and unsigned integers. -template <bool IsSigned> -struct int_checker { - template <typename T> - static bool fits_in_int(T value) { - unsigned max = std::numeric_limits<int>::max(); - return value <= max; - } - static bool fits_in_int(bool) { return true; } -}; - -template <> -struct int_checker<true> { - template <typename T> - static bool fits_in_int(T value) { - return value >= std::numeric_limits<int>::min() && - value <= std::numeric_limits<int>::max(); - } - static bool fits_in_int(int) { return true; } -}; - -class printf_precision_handler: public function<int> { - public: - template <typename T> - typename std::enable_if<std::is_integral<T>::value, int>::type - operator()(T value) { - if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value)) - FMT_THROW(format_error("number is too big")); - return static_cast<int>(value); - } - - template <typename T> - typename std::enable_if<!std::is_integral<T>::value, int>::type operator()(T) { - FMT_THROW(format_error("precision is not integer")); - return 0; - } -}; - -// An argument visitor that returns true iff arg is a zero integer. -class is_zero_int: public function<bool> { - public: - template <typename T> - typename std::enable_if<std::is_integral<T>::value, bool>::type - operator()(T value) { return value == 0; } - - template <typename T> - typename std::enable_if<!std::is_integral<T>::value, bool>::type - operator()(T) { return false; } -}; - -template <typename T> -struct make_unsigned_or_bool : std::make_unsigned<T> {}; - -template <> -struct make_unsigned_or_bool<bool> { - typedef bool type; -}; - -template <typename T, typename Context> -class arg_converter: public function<void> { - private: - typedef typename Context::char_type Char; - - basic_format_arg<Context> &arg_; - typename Context::char_type type_; - - public: - arg_converter(basic_format_arg<Context> &arg, Char type) - : arg_(arg), type_(type) {} - - void operator()(bool value) { - if (type_ != 's') - operator()<bool>(value); - } - - template <typename U> - typename std::enable_if<std::is_integral<U>::value>::type - operator()(U value) { - bool is_signed = type_ == 'd' || type_ == 'i'; - typedef typename std::conditional< - std::is_same<T, void>::value, U, T>::type TargetType; - if (const_check(sizeof(TargetType) <= sizeof(int))) { - // Extra casts are used to silence warnings. - if (is_signed) { - arg_ = internal::make_arg<Context>( - static_cast<int>(static_cast<TargetType>(value))); - } else { - typedef typename make_unsigned_or_bool<TargetType>::type Unsigned; - arg_ = internal::make_arg<Context>( - static_cast<unsigned>(static_cast<Unsigned>(value))); - } - } else { - if (is_signed) { - // glibc's printf doesn't sign extend arguments of smaller types: - // std::printf("%lld", -42); // prints "4294967254" - // but we don't have to do the same because it's a UB. - arg_ = internal::make_arg<Context>(static_cast<long long>(value)); - } else { - arg_ = internal::make_arg<Context>( - static_cast<typename make_unsigned_or_bool<U>::type>(value)); - } - } - } - - template <typename U> - typename std::enable_if<!std::is_integral<U>::value>::type operator()(U) { - // No coversion needed for non-integral types. - } -}; - -// Converts an integer argument to T for printf, if T is an integral type. -// If T is void, the argument is converted to corresponding signed or unsigned -// type depending on the type specifier: 'd' and 'i' - signed, other - -// unsigned). -template <typename T, typename Context, typename Char> -void convert_arg(basic_format_arg<Context> &arg, Char type) { - fmt::visit(arg_converter<T, Context>(arg, type), arg); -} - -// Converts an integer argument to char for printf. -template <typename Context> -class char_converter: public function<void> { - private: - basic_format_arg<Context> &arg_; - - public: - explicit char_converter(basic_format_arg<Context> &arg) : arg_(arg) {} - - template <typename T> - typename std::enable_if<std::is_integral<T>::value>::type - operator()(T value) { - typedef typename Context::char_type Char; - arg_ = internal::make_arg<Context>(static_cast<Char>(value)); - } - - template <typename T> - typename std::enable_if<!std::is_integral<T>::value>::type operator()(T) { - // No coversion needed for non-integral types. - } -}; - -// Checks if an argument is a valid printf width specifier and sets -// left alignment if it is negative. -template <typename Char> -class printf_width_handler: public function<unsigned> { - private: - typedef basic_format_specs<Char> format_specs; - - format_specs &spec_; - - public: - explicit printf_width_handler(format_specs &spec) : spec_(spec) {} - - template <typename T> - typename std::enable_if<std::is_integral<T>::value, unsigned>::type - operator()(T value) { - typedef typename internal::int_traits<T>::main_type UnsignedType; - UnsignedType width = static_cast<UnsignedType>(value); - if (internal::is_negative(value)) { - spec_.align_ = ALIGN_LEFT; - width = 0 - width; - } - unsigned int_max = std::numeric_limits<int>::max(); - if (width > int_max) - FMT_THROW(format_error("number is too big")); - return static_cast<unsigned>(width); - } - - template <typename T> - typename std::enable_if<!std::is_integral<T>::value, unsigned>::type - operator()(T) { - FMT_THROW(format_error("width is not integer")); - return 0; - } -}; -} // namespace internal - -template <typename Range> -class printf_arg_formatter; - -template < - typename OutputIt, typename Char, - typename ArgFormatter = - printf_arg_formatter<back_insert_range<internal::basic_buffer<Char>>>> -class basic_printf_context; - -/** - \rst - The ``printf`` argument formatter. - \endrst - */ -template <typename Range> -class printf_arg_formatter: - public internal::function< - typename internal::arg_formatter_base<Range>::iterator>, - public internal::arg_formatter_base<Range> { - private: - typedef typename Range::value_type char_type; - typedef decltype(internal::declval<Range>().begin()) iterator; - typedef internal::arg_formatter_base<Range> base; - typedef basic_printf_context<iterator, char_type> context_type; - - context_type &context_; - - void write_null_pointer(char) { - this->spec()->type_ = 0; - this->write("(nil)"); - } - - void write_null_pointer(wchar_t) { - this->spec()->type_ = 0; - this->write(L"(nil)"); - } - - public: - typedef typename base::format_specs format_specs; - - /** - \rst - Constructs an argument formatter object. - *buffer* is a reference to the output buffer and *spec* contains format - specifier information for standard argument types. - \endrst - */ - printf_arg_formatter(internal::basic_buffer<char_type> &buffer, - format_specs &spec, context_type &ctx) - : base(back_insert_range<internal::basic_buffer<char_type>>(buffer), &spec), - context_(ctx) {} - - template <typename T> - typename std::enable_if<std::is_integral<T>::value, iterator>::type - operator()(T value) { - // MSVC2013 fails to compile separate overloads for bool and char_type so - // use std::is_same instead. - if (std::is_same<T, bool>::value) { - format_specs &fmt_spec = *this->spec(); - if (fmt_spec.type_ != 's') - return base::operator()(value ? 1 : 0); - fmt_spec.type_ = 0; - this->write(value != 0); - } else if (std::is_same<T, char_type>::value) { - format_specs &fmt_spec = *this->spec(); - if (fmt_spec.type_ && fmt_spec.type_ != 'c') - return (*this)(static_cast<int>(value)); - fmt_spec.flags_ = 0; - fmt_spec.align_ = ALIGN_RIGHT; - return base::operator()(value); - } else { - return base::operator()(value); - } - return this->out(); - } - - template <typename T> - typename std::enable_if<std::is_floating_point<T>::value, iterator>::type - operator()(T value) { - return base::operator()(value); - } - - /** Formats a null-terminated C string. */ - iterator operator()(const char *value) { - if (value) - base::operator()(value); - else if (this->spec()->type_ == 'p') - write_null_pointer(char_type()); - else - this->write("(null)"); - return this->out(); - } - - /** Formats a null-terminated wide C string. */ - iterator operator()(const wchar_t *value) { - if (value) - base::operator()(value); - else if (this->spec()->type_ == 'p') - write_null_pointer(char_type()); - else - this->write(L"(null)"); - return this->out(); - } - - iterator operator()(basic_string_view<char_type> value) { - return base::operator()(value); - } - - iterator operator()(monostate value) { - return base::operator()(value); - } - - /** Formats a pointer. */ - iterator operator()(const void *value) { - if (value) - return base::operator()(value); - this->spec()->type_ = 0; - write_null_pointer(char_type()); - return this->out(); - } - - /** Formats an argument of a custom (user-defined) type. */ - iterator operator()(typename basic_format_arg<context_type>::handle handle) { - handle.format(context_); - return this->out(); - } -}; - -template <typename T> -struct printf_formatter { - template <typename ParseContext> - auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { return ctx.begin(); } - - template <typename FormatContext> - auto format(const T &value, FormatContext &ctx) -> decltype(ctx.out()) { - internal::format_value(internal::get_container(ctx.out()), value); - return ctx.out(); - } -}; - -/** This template formats data and writes the output to a writer. */ -template <typename OutputIt, typename Char, typename ArgFormatter> -class basic_printf_context : - // Inherit publicly as a workaround for the icc bug - // https://software.intel.com/en-us/forums/intel-c-compiler/topic/783476. - public internal::context_base< - OutputIt, basic_printf_context<OutputIt, Char, ArgFormatter>, Char> { - public: - /** The character type for the output. */ - typedef Char char_type; - - template <typename T> - struct formatter_type { typedef printf_formatter<T> type; }; - - private: - typedef internal::context_base<OutputIt, basic_printf_context, Char> base; - typedef typename base::format_arg format_arg; - typedef basic_format_specs<char_type> format_specs; - typedef internal::null_terminating_iterator<char_type> iterator; - - void parse_flags(format_specs &spec, iterator &it); - - // Returns the argument with specified index or, if arg_index is equal - // to the maximum unsigned value, the next argument. - format_arg get_arg( - iterator it, - unsigned arg_index = (std::numeric_limits<unsigned>::max)()); - - // Parses argument index, flags and width and returns the argument index. - unsigned parse_header(iterator &it, format_specs &spec); - - public: - /** - \rst - Constructs a ``printf_context`` object. References to the arguments and - the writer are stored in the context object so make sure they have - appropriate lifetimes. - \endrst - */ - basic_printf_context(OutputIt out, basic_string_view<char_type> format_str, - basic_format_args<basic_printf_context> args) - : base(out, format_str, args) {} - - using base::parse_context; - using base::out; - using base::advance_to; - - /** Formats stored arguments and writes the output to the range. */ - void format(); -}; - -template <typename OutputIt, typename Char, typename AF> -void basic_printf_context<OutputIt, Char, AF>::parse_flags( - format_specs &spec, iterator &it) { - for (;;) { - switch (*it++) { - case '-': - spec.align_ = ALIGN_LEFT; - break; - case '+': - spec.flags_ |= SIGN_FLAG | PLUS_FLAG; - break; - case '0': - spec.fill_ = '0'; - break; - case ' ': - spec.flags_ |= SIGN_FLAG; - break; - case '#': - spec.flags_ |= HASH_FLAG; - break; - default: - --it; - return; - } - } -} - -template <typename OutputIt, typename Char, typename AF> -typename basic_printf_context<OutputIt, Char, AF>::format_arg - basic_printf_context<OutputIt, Char, AF>::get_arg( - iterator it, unsigned arg_index) { - (void)it; - if (arg_index == std::numeric_limits<unsigned>::max()) - return this->do_get_arg(this->parse_context().next_arg_id()); - return base::get_arg(arg_index - 1); -} - -template <typename OutputIt, typename Char, typename AF> -unsigned basic_printf_context<OutputIt, Char, AF>::parse_header( - iterator &it, format_specs &spec) { - unsigned arg_index = std::numeric_limits<unsigned>::max(); - char_type c = *it; - if (c >= '0' && c <= '9') { - // Parse an argument index (if followed by '$') or a width possibly - // preceded with '0' flag(s). - internal::error_handler eh; - unsigned value = parse_nonnegative_int(it, eh); - if (*it == '$') { // value is an argument index - ++it; - arg_index = value; - } else { - if (c == '0') - spec.fill_ = '0'; - if (value != 0) { - // Nonzero value means that we parsed width and don't need to - // parse it or flags again, so return now. - spec.width_ = value; - return arg_index; - } - } - } - parse_flags(spec, it); - // Parse width. - if (*it >= '0' && *it <= '9') { - internal::error_handler eh; - spec.width_ = parse_nonnegative_int(it, eh); - } else if (*it == '*') { - ++it; - spec.width_ = - fmt::visit(internal::printf_width_handler<char_type>(spec), get_arg(it)); - } - return arg_index; -} - -template <typename OutputIt, typename Char, typename AF> -void basic_printf_context<OutputIt, Char, AF>::format() { - auto &buffer = internal::get_container(this->out()); - auto start = iterator(this->parse_context()); - auto it = start; - using internal::pointer_from; - while (*it) { - char_type c = *it++; - if (c != '%') continue; - if (*it == c) { - buffer.append(pointer_from(start), pointer_from(it)); - start = ++it; - continue; - } - buffer.append(pointer_from(start), pointer_from(it) - 1); - - format_specs spec; - spec.align_ = ALIGN_RIGHT; - - // Parse argument index, flags and width. - unsigned arg_index = parse_header(it, spec); - - // Parse precision. - if (*it == '.') { - ++it; - if ('0' <= *it && *it <= '9') { - internal::error_handler eh; - spec.precision_ = static_cast<int>(parse_nonnegative_int(it, eh)); - } else if (*it == '*') { - ++it; - spec.precision_ = - fmt::visit(internal::printf_precision_handler(), get_arg(it)); - } else { - spec.precision_ = 0; - } - } - - format_arg arg = get_arg(it, arg_index); - if (spec.flag(HASH_FLAG) && fmt::visit(internal::is_zero_int(), arg)) - spec.flags_ &= ~internal::to_unsigned<int>(HASH_FLAG); - if (spec.fill_ == '0') { - if (arg.is_arithmetic()) - spec.align_ = ALIGN_NUMERIC; - else - spec.fill_ = ' '; // Ignore '0' flag for non-numeric types. - } - - // Parse length and convert the argument to the required type. - using internal::convert_arg; - switch (*it++) { - case 'h': - if (*it == 'h') - convert_arg<signed char>(arg, *++it); - else - convert_arg<short>(arg, *it); - break; - case 'l': - if (*it == 'l') - convert_arg<long long>(arg, *++it); - else - convert_arg<long>(arg, *it); - break; - case 'j': - convert_arg<intmax_t>(arg, *it); - break; - case 'z': - convert_arg<std::size_t>(arg, *it); - break; - case 't': - convert_arg<std::ptrdiff_t>(arg, *it); - break; - case 'L': - // printf produces garbage when 'L' is omitted for long double, no - // need to do the same. - break; - default: - --it; - convert_arg<void>(arg, *it); - } - - // Parse type. - if (!*it) - FMT_THROW(format_error("invalid format string")); - spec.type_ = static_cast<char>(*it++); - if (arg.is_integral()) { - // Normalize type. - switch (spec.type_) { - case 'i': case 'u': - spec.type_ = 'd'; - break; - case 'c': - // TODO: handle wchar_t better? - fmt::visit(internal::char_converter<basic_printf_context>(arg), arg); - break; - } - } - - start = it; - - // Format argument. - fmt::visit(AF(buffer, spec, *this), arg); - } - buffer.append(pointer_from(start), pointer_from(it)); -} - -template <typename Char, typename Context> -void printf(internal::basic_buffer<Char> &buf, basic_string_view<Char> format, - basic_format_args<Context> args) { - Context(std::back_inserter(buf), format, args).format(); -} - -template <typename Buffer> -struct printf_context { - typedef basic_printf_context< - std::back_insert_iterator<Buffer>, typename Buffer::value_type> type; -}; - -template <typename ...Args> -inline format_arg_store<printf_context<internal::buffer>::type, Args...> - make_printf_args(const Args & ... args) { - return format_arg_store<printf_context<internal::buffer>::type, Args...>( - args...); -} -typedef basic_format_args<printf_context<internal::buffer>::type> printf_args; -typedef basic_format_args<printf_context<internal::wbuffer>::type> wprintf_args; - -inline std::string vsprintf(string_view format, printf_args args) { - memory_buffer buffer; - printf(buffer, format, args); - return to_string(buffer); -} - -/** - \rst - Formats arguments and returns the result as a string. - - **Example**:: - - std::string message = fmt::sprintf("The answer is %d", 42); - \endrst -*/ -template <typename... Args> -inline std::string sprintf(string_view format_str, const Args & ... args) { - return vsprintf(format_str, - make_format_args<typename printf_context<internal::buffer>::type>(args...)); -} - -inline std::wstring vsprintf(wstring_view format, wprintf_args args) { - wmemory_buffer buffer; - printf(buffer, format, args); - return to_string(buffer); -} - -template <typename... Args> -inline std::wstring sprintf(wstring_view format_str, const Args & ... args) { - return vsprintf(format_str, - make_format_args<typename printf_context<internal::wbuffer>::type>(args...)); -} - -template <typename Char> -inline int vfprintf(std::FILE *f, basic_string_view<Char> format, - basic_format_args<typename printf_context< - internal::basic_buffer<Char>>::type> args) { - basic_memory_buffer<Char> buffer; - printf(buffer, format, args); - std::size_t size = buffer.size(); - return std::fwrite( - buffer.data(), sizeof(Char), size, f) < size ? -1 : static_cast<int>(size); -} - -/** - \rst - Prints formatted data to the file *f*. - - **Example**:: - - fmt::fprintf(stderr, "Don't %s!", "panic"); - \endrst - */ -template <typename... Args> -inline int fprintf(std::FILE *f, string_view format_str, const Args & ... args) { - auto vargs = make_format_args< - typename printf_context<internal::buffer>::type>(args...); - return vfprintf<char>(f, format_str, vargs); -} - -template <typename... Args> -inline int fprintf(std::FILE *f, wstring_view format_str, - const Args & ... args) { - return vfprintf(f, format_str, - make_format_args<typename printf_context<internal::wbuffer>::type>(args...)); -} - -inline int vprintf(string_view format, printf_args args) { - return vfprintf(stdout, format, args); -} - -inline int vprintf(wstring_view format, wprintf_args args) { - return vfprintf(stdout, format, args); -} - -/** - \rst - Prints formatted data to ``stdout``. - - **Example**:: - - fmt::printf("Elapsed time: %.2f seconds", 1.23); - \endrst - */ -template <typename... Args> -inline int printf(string_view format_str, const Args & ... args) { - return vprintf(format_str, - make_format_args<typename printf_context<internal::buffer>::type>(args...)); -} - -template <typename... Args> -inline int printf(wstring_view format_str, const Args & ... args) { - return vprintf(format_str, - make_format_args<typename printf_context<internal::wbuffer>::type>(args...)); -} - -inline int vfprintf(std::ostream &os, string_view format_str, - printf_args args) { - memory_buffer buffer; - printf(buffer, format_str, args); - internal::write(os, buffer); - return static_cast<int>(buffer.size()); -} - -inline int vfprintf(std::wostream &os, wstring_view format_str, - wprintf_args args) { - wmemory_buffer buffer; - printf(buffer, format_str, args); - internal::write(os, buffer); - return static_cast<int>(buffer.size()); -} - -/** - \rst - Prints formatted data to the stream *os*. - - **Example**:: - - fmt::fprintf(cerr, "Don't %s!", "panic"); - \endrst - */ -template <typename... Args> -inline int fprintf(std::ostream &os, string_view format_str, - const Args & ... args) { - auto vargs = make_format_args< - typename printf_context<internal::buffer>::type>(args...); - return vfprintf(os, format_str, vargs); -} - -template <typename... Args> -inline int fprintf(std::wostream &os, wstring_view format_str, - const Args & ... args) { - auto vargs = make_format_args< - typename printf_context<internal::buffer>::type>(args...); - return vfprintf(os, format_str, vargs); -} -FMT_END_NAMESPACE - -#endif // FMT_PRINTF_H_ diff --git a/src/nmodl/ext/spdlog/fmt/bundled/ranges.h b/src/nmodl/ext/spdlog/fmt/bundled/ranges.h deleted file mode 100644 index 3672d4ca81..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/ranges.h +++ /dev/null @@ -1,308 +0,0 @@ -// Formatting library for C++ - the core API -// -// Copyright (c) 2012 - present, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. -// -// Copyright (c) 2018 - present, Remotion (Igor Schulz) -// All Rights Reserved -// {fmt} support for ranges, containers and types tuple interface. - -#ifndef FMT_RANGES_H_ -#define FMT_RANGES_H_ - -#include "format.h" -#include <type_traits> - -// output only up to N items from the range. -#ifndef FMT_RANGE_OUTPUT_LENGTH_LIMIT -# define FMT_RANGE_OUTPUT_LENGTH_LIMIT 256 -#endif - -FMT_BEGIN_NAMESPACE - -template <typename Char> -struct formatting_base { - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - return ctx.begin(); - } -}; - -template <typename Char, typename Enable = void> -struct formatting_range : formatting_base<Char> { - static FMT_CONSTEXPR_DECL const std::size_t range_length_limit = - FMT_RANGE_OUTPUT_LENGTH_LIMIT; // output only up to N items from the range. - Char prefix; - Char delimiter; - Char postfix; - formatting_range() : prefix('{'), delimiter(','), postfix('}') {} - static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; - static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; -}; - -template <typename Char, typename Enable = void> -struct formatting_tuple : formatting_base<Char> { - Char prefix; - Char delimiter; - Char postfix; - formatting_tuple() : prefix('('), delimiter(','), postfix(')') {} - static FMT_CONSTEXPR_DECL const bool add_delimiter_spaces = true; - static FMT_CONSTEXPR_DECL const bool add_prepostfix_space = false; -}; - -namespace internal { - -template <typename RangeT, typename OutputIterator> -void copy(const RangeT &range, OutputIterator out) { - for (auto it = range.begin(), end = range.end(); it != end; ++it) - *out++ = *it; -} - -template <typename OutputIterator> -void copy(const char *str, OutputIterator out) { - const char *p_curr = str; - while (*p_curr) { - *out++ = *p_curr++; - } -} - -template <typename OutputIterator> -void copy(char ch, OutputIterator out) { - *out++ = ch; -} - -/// Return true value if T has std::string interface, like std::string_view. -template <typename T> -class is_like_std_string { - template <typename U> - static auto check(U *p) -> - decltype(p->find('a'), p->length(), p->data(), int()); - template <typename> - static void check(...); - - public: - static FMT_CONSTEXPR_DECL const bool value = - !std::is_void<decltype(check<T>(FMT_NULL))>::value; -}; - -template <typename Char> -struct is_like_std_string<fmt::basic_string_view<Char>> : std::true_type {}; - -template <typename... Ts> -struct conditional_helper {}; - -template <typename T, typename _ = void> -struct is_range_ : std::false_type {}; - -#if !FMT_MSC_VER || FMT_MSC_VER > 1800 -template <typename T> -struct is_range_<T, typename std::conditional< - false, - conditional_helper<decltype(internal::declval<T>().begin()), - decltype(internal::declval<T>().end())>, - void>::type> : std::true_type {}; -#endif - -/// tuple_size and tuple_element check. -template <typename T> -class is_tuple_like_ { - template <typename U> - static auto check(U *p) -> - decltype(std::tuple_size<U>::value, - internal::declval<typename std::tuple_element<0, U>::type>(), int()); - template <typename> - static void check(...); - - public: - static FMT_CONSTEXPR_DECL const bool value = - !std::is_void<decltype(check<T>(FMT_NULL))>::value; -}; - -// Check for integer_sequence -#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900 -template <typename T, T... N> -using integer_sequence = std::integer_sequence<T, N...>; -template <std::size_t... N> -using index_sequence = std::index_sequence<N...>; -template <std::size_t N> -using make_index_sequence = std::make_index_sequence<N>; -#else -template <typename T, T... N> -struct integer_sequence { - typedef T value_type; - - static FMT_CONSTEXPR std::size_t size() { - return sizeof...(N); - } -}; - -template <std::size_t... N> -using index_sequence = integer_sequence<std::size_t, N...>; - -template <typename T, std::size_t N, T... Ns> -struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {}; -template <typename T, T... Ns> -struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {}; - -template <std::size_t N> -using make_index_sequence = make_integer_sequence<std::size_t, N>; -#endif - -template <class Tuple, class F, size_t... Is> -void for_each(index_sequence<Is...>, Tuple &&tup, F &&f) FMT_NOEXCEPT { - using std::get; - // using free function get<I>(T) now. - const int _[] = {0, ((void)f(get<Is>(tup)), 0)...}; - (void)_; // blocks warnings -} - -template <class T> -FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> -get_indexes(T const &) { return {}; } - -template <class Tuple, class F> -void for_each(Tuple &&tup, F &&f) { - const auto indexes = get_indexes(tup); - for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f)); -} - -template<typename Arg> -FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, - typename std::enable_if< - !is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) { - return add_space ? " {}" : "{}"; -} - -template<typename Arg> -FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const Arg&, - typename std::enable_if< - is_like_std_string<typename std::decay<Arg>::type>::value>::type* = nullptr) { - return add_space ? " \"{}\"" : "\"{}\""; -} - -FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char*) { - return add_space ? " \"{}\"" : "\"{}\""; -} -FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t*) { - return add_space ? L" \"{}\"" : L"\"{}\""; -} - -FMT_CONSTEXPR const char* format_str_quoted(bool add_space, const char) { - return add_space ? " '{}'" : "'{}'"; -} -FMT_CONSTEXPR const wchar_t* format_str_quoted(bool add_space, const wchar_t) { - return add_space ? L" '{}'" : L"'{}'"; -} - -} // namespace internal - -template <typename T> -struct is_tuple_like { - static FMT_CONSTEXPR_DECL const bool value = - internal::is_tuple_like_<T>::value && !internal::is_range_<T>::value; -}; - -template <typename TupleT, typename Char> -struct formatter<TupleT, Char, - typename std::enable_if<fmt::is_tuple_like<TupleT>::value>::type> { -private: - // C++11 generic lambda for format() - template <typename FormatContext> - struct format_each { - template <typename T> - void operator()(const T& v) { - if (i > 0) { - if (formatting.add_prepostfix_space) { - *out++ = ' '; - } - internal::copy(formatting.delimiter, out); - } - format_to(out, - internal::format_str_quoted( - (formatting.add_delimiter_spaces && i > 0), v), - v); - ++i; - } - - formatting_tuple<Char>& formatting; - std::size_t& i; - typename std::add_lvalue_reference<decltype(std::declval<FormatContext>().out())>::type out; - }; - -public: - formatting_tuple<Char> formatting; - - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - return formatting.parse(ctx); - } - - template <typename FormatContext = format_context> - auto format(const TupleT &values, FormatContext &ctx) -> decltype(ctx.out()) { - auto out = ctx.out(); - std::size_t i = 0; - internal::copy(formatting.prefix, out); - - internal::for_each(values, format_each<FormatContext>{formatting, i, out}); - if (formatting.add_prepostfix_space) { - *out++ = ' '; - } - internal::copy(formatting.postfix, out); - - return ctx.out(); - } -}; - -template <typename T> -struct is_range { - static FMT_CONSTEXPR_DECL const bool value = - internal::is_range_<T>::value && !internal::is_like_std_string<T>::value; -}; - -template <typename RangeT, typename Char> -struct formatter<RangeT, Char, - typename std::enable_if<fmt::is_range<RangeT>::value>::type> { - - formatting_range<Char> formatting; - - template <typename ParseContext> - FMT_CONSTEXPR auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - return formatting.parse(ctx); - } - - template <typename FormatContext> - typename FormatContext::iterator format( - const RangeT &values, FormatContext &ctx) { - auto out = ctx.out(); - internal::copy(formatting.prefix, out); - std::size_t i = 0; - for (auto it = values.begin(), end = values.end(); it != end; ++it) { - if (i > 0) { - if (formatting.add_prepostfix_space) { - *out++ = ' '; - } - internal::copy(formatting.delimiter, out); - } - format_to(out, - internal::format_str_quoted( - (formatting.add_delimiter_spaces && i > 0), *it), - *it); - if (++i > formatting.range_length_limit) { - format_to(out, " ... <other elements>"); - break; - } - } - if (formatting.add_prepostfix_space) { - *out++ = ' '; - } - internal::copy(formatting.postfix, out); - return ctx.out(); - } -}; - -FMT_END_NAMESPACE - -#endif // FMT_RANGES_H_ - diff --git a/src/nmodl/ext/spdlog/fmt/bundled/time.h b/src/nmodl/ext/spdlog/fmt/bundled/time.h deleted file mode 100644 index a624058f39..0000000000 --- a/src/nmodl/ext/spdlog/fmt/bundled/time.h +++ /dev/null @@ -1,156 +0,0 @@ -// Formatting library for C++ - time formatting -// -// Copyright (c) 2012 - 2016, Victor Zverovich -// All rights reserved. -// -// For the license information refer to format.h. - -#ifndef FMT_TIME_H_ -#define FMT_TIME_H_ - -#include "format.h" -#include <ctime> - -FMT_BEGIN_NAMESPACE - -// Prevents expansion of a preceding token as a function-style macro. -// Usage: f FMT_NOMACRO() -#define FMT_NOMACRO - -namespace internal{ -inline null<> localtime_r FMT_NOMACRO(...) { return null<>(); } -inline null<> localtime_s(...) { return null<>(); } -inline null<> gmtime_r(...) { return null<>(); } -inline null<> gmtime_s(...) { return null<>(); } -} - -// Thread-safe replacement for std::localtime -inline std::tm localtime(std::time_t time) { - struct dispatcher { - std::time_t time_; - std::tm tm_; - - dispatcher(std::time_t t): time_(t) {} - - bool run() { - using namespace fmt::internal; - return handle(localtime_r(&time_, &tm_)); - } - - bool handle(std::tm *tm) { return tm != FMT_NULL; } - - bool handle(internal::null<>) { - using namespace fmt::internal; - return fallback(localtime_s(&tm_, &time_)); - } - - bool fallback(int res) { return res == 0; } - - bool fallback(internal::null<>) { - using namespace fmt::internal; - std::tm *tm = std::localtime(&time_); - if (tm) tm_ = *tm; - return tm != FMT_NULL; - } - }; - dispatcher lt(time); - if (lt.run()) - return lt.tm_; - // Too big time values may be unsupported. - FMT_THROW(format_error("time_t value out of range")); -} - -// Thread-safe replacement for std::gmtime -inline std::tm gmtime(std::time_t time) { - struct dispatcher { - std::time_t time_; - std::tm tm_; - - dispatcher(std::time_t t): time_(t) {} - - bool run() { - using namespace fmt::internal; - return handle(gmtime_r(&time_, &tm_)); - } - - bool handle(std::tm *tm) { return tm != FMT_NULL; } - - bool handle(internal::null<>) { - using namespace fmt::internal; - return fallback(gmtime_s(&tm_, &time_)); - } - - bool fallback(int res) { return res == 0; } - - bool fallback(internal::null<>) { - std::tm *tm = std::gmtime(&time_); - if (tm) tm_ = *tm; - return tm != FMT_NULL; - } - }; - dispatcher gt(time); - if (gt.run()) - return gt.tm_; - // Too big time values may be unsupported. - FMT_THROW(format_error("time_t value out of range")); -} - -namespace internal { -inline std::size_t strftime(char *str, std::size_t count, const char *format, - const std::tm *time) { - return std::strftime(str, count, format, time); -} - -inline std::size_t strftime(wchar_t *str, std::size_t count, - const wchar_t *format, const std::tm *time) { - return std::wcsftime(str, count, format, time); -} -} - -template <typename Char> -struct formatter<std::tm, Char> { - template <typename ParseContext> - auto parse(ParseContext &ctx) -> decltype(ctx.begin()) { - auto it = internal::null_terminating_iterator<Char>(ctx); - if (*it == ':') - ++it; - auto end = it; - while (*end && *end != '}') - ++end; - tm_format.reserve(end - it + 1); - using internal::pointer_from; - tm_format.append(pointer_from(it), pointer_from(end)); - tm_format.push_back('\0'); - return pointer_from(end); - } - - template <typename FormatContext> - auto format(const std::tm &tm, FormatContext &ctx) -> decltype(ctx.out()) { - internal::basic_buffer<Char> &buf = internal::get_container(ctx.out()); - std::size_t start = buf.size(); - for (;;) { - std::size_t size = buf.capacity() - start; - std::size_t count = - internal::strftime(&buf[start], size, &tm_format[0], &tm); - if (count != 0) { - buf.resize(start + count); - break; - } - if (size >= tm_format.size() * 256) { - // If the buffer is 256 times larger than the format string, assume - // that `strftime` gives an empty result. There doesn't seem to be a - // better way to distinguish the two cases: - // https://github.com/fmtlib/fmt/issues/367 - break; - } - const std::size_t MIN_GROWTH = 10; - buf.reserve(buf.capacity() + (size > MIN_GROWTH ? size : MIN_GROWTH)); - } - return ctx.out(); - } - - basic_memory_buffer<Char> tm_format; -}; -FMT_END_NAMESPACE - -#endif // FMT_TIME_H_ diff --git a/src/nmodl/ext/spdlog/fmt/fmt.h b/src/nmodl/ext/spdlog/fmt/fmt.h deleted file mode 100644 index 616af0cc78..0000000000 --- a/src/nmodl/ext/spdlog/fmt/fmt.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// Copyright(c) 2016-2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// -// Include a bundled header-only copy of fmtlib or an external one. -// By default spdlog include its own copy. -// - -#if !defined(SPDLOG_FMT_EXTERNAL) -#ifndef FMT_HEADER_ONLY -#define FMT_HEADER_ONLY -#endif -#ifndef FMT_USE_WINDOWS_H -#define FMT_USE_WINDOWS_H 0 -#endif -#include "bundled/core.h" -#include "bundled/format.h" -#else // external fmtlib -#include <fmt/core.h> -#include <fmt/format.h> -#endif diff --git a/src/nmodl/ext/spdlog/fmt/ostr.h b/src/nmodl/ext/spdlog/fmt/ostr.h deleted file mode 100644 index 9902898f84..0000000000 --- a/src/nmodl/ext/spdlog/fmt/ostr.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright(c) 2016 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once -// -// include bundled or external copy of fmtlib's ostream support -// -#if !defined(SPDLOG_FMT_EXTERNAL) -#ifndef FMT_HEADER_ONLY -#define FMT_HEADER_ONLY -#endif -#include "bundled/ostream.h" -#include "fmt.h" -#else -#include <fmt/ostream.h> -#endif diff --git a/src/nmodl/ext/spdlog/formatter.h b/src/nmodl/ext/spdlog/formatter.h deleted file mode 100644 index a7ef6b8b5a..0000000000 --- a/src/nmodl/ext/spdlog/formatter.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include "fmt/fmt.h" -#include "spdlog/details/log_msg.h" - -namespace spdlog { - -class formatter -{ -public: - virtual ~formatter() = default; - virtual void format(const details::log_msg &msg, fmt::memory_buffer &dest) = 0; - virtual std::unique_ptr<formatter> clone() const = 0; -}; -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/logger.h b/src/nmodl/ext/spdlog/logger.h deleted file mode 100644 index 1bcf4bc295..0000000000 --- a/src/nmodl/ext/spdlog/logger.h +++ /dev/null @@ -1,167 +0,0 @@ -// -// Copyright(c) 2015-2108 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -// Thread safe logger (except for set_pattern(..), set_formatter(..) and -// set_error_handler()) -// Has name, log level, vector of std::shared sink pointers and formatter -// Upon each log write the logger: -// 1. Checks if its log level is enough to log the message and if yes: -// 2. Call the underlying sinks to do the job. -// 3. Each sink use its own private copy of a formatter to format the message -// and send to its destination. -// -// The use of private formatter per sink provides the opportunity to cache some -// formatted data, -// and support customize format per each sink. - -#include "spdlog/common.h" -#include "spdlog/formatter.h" -#include "spdlog/sinks/sink.h" - -#include <locale> -#include <memory> -#include <string> -#include <vector> - -namespace spdlog { - -class logger -{ -public: - logger(std::string name, sink_ptr single_sink); - logger(std::string name, sinks_init_list sinks); - - template<typename It> - logger(std::string name, It begin, It end); - - virtual ~logger(); - - logger(const logger &) = delete; - logger &operator=(const logger &) = delete; - - template<typename... Args> - void log(level::level_enum lvl, const char *fmt, const Args &... args); - - template<typename... Args> - void log(level::level_enum lvl, const char *msg); - - template<typename... Args> - void trace(const char *fmt, const Args &... args); - - template<typename... Args> - void debug(const char *fmt, const Args &... args); - - template<typename... Args> - void info(const char *fmt, const Args &... args); - - template<typename... Args> - void warn(const char *fmt, const Args &... args); - - template<typename... Args> - void error(const char *fmt, const Args &... args); - - template<typename... Args> - void critical(const char *fmt, const Args &... args); - -#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT -#ifndef _WIN32 -#error SPDLOG_WCHAR_TO_UTF8_SUPPORT only supported on windows -#else - template<typename... Args> - void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args); - - template<typename... Args> - void trace(const wchar_t *fmt, const Args &... args); - - template<typename... Args> - void debug(const wchar_t *fmt, const Args &... args); - - template<typename... Args> - void info(const wchar_t *fmt, const Args &... args); - - template<typename... Args> - void warn(const wchar_t *fmt, const Args &... args); - - template<typename... Args> - void error(const wchar_t *fmt, const Args &... args); - - template<typename... Args> - void critical(const wchar_t *fmt, const Args &... args); -#endif // _WIN32 -#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT - - template<typename T> - void log(level::level_enum lvl, const T &); - - template<typename T> - void trace(const T &msg); - - template<typename T> - void debug(const T &msg); - - template<typename T> - void info(const T &msg); - - template<typename T> - void warn(const T &msg); - - template<typename T> - void error(const T &msg); - - template<typename T> - void critical(const T &msg); - - bool should_log(level::level_enum msg_level) const; - void set_level(level::level_enum log_level); - level::level_enum level() const; - const std::string &name() const; - - // set formatting for the sinks in this logger. - // each sink will get a seperate instance of the formatter object. - void set_formatter(std::unique_ptr<formatter> formatter); - void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local); - - // flush functions - void flush(); - void flush_on(level::level_enum log_level); - level::level_enum flush_level() const; - - // sinks - const std::vector<sink_ptr> &sinks() const; - std::vector<sink_ptr> &sinks(); - - // error handler - void set_error_handler(log_err_handler err_handler); - log_err_handler error_handler(); - - // create new logger with same sinks and configuration. - virtual std::shared_ptr<logger> clone(std::string logger_name); - -protected: - virtual void sink_it_(details::log_msg &msg); - virtual void flush_(); - - bool should_flush_(const details::log_msg &msg); - - // default error handler: print the error to stderr with the max rate of 1 - // message/minute - void default_err_handler_(const std::string &msg); - - // increment the message count (only if defined(SPDLOG_ENABLE_MESSAGE_COUNTER)) - void incr_msg_counter_(details::log_msg &msg); - - const std::string name_; - std::vector<sink_ptr> sinks_; - spdlog::level_t level_; - spdlog::level_t flush_level_; - log_err_handler err_handler_; - std::atomic<time_t> last_err_time_; - std::atomic<size_t> msg_counter_; -}; -} // namespace spdlog - -#include "details/logger_impl.h" diff --git a/src/nmodl/ext/spdlog/sinks/android_sink.h b/src/nmodl/ext/spdlog/sinks/android_sink.h deleted file mode 100644 index 8c3383c173..0000000000 --- a/src/nmodl/ext/spdlog/sinks/android_sink.h +++ /dev/null @@ -1,121 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/fmt_helper.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/details/os.h" -#include "spdlog/sinks/base_sink.h" - -#include <android/log.h> -#include <chrono> -#include <mutex> -#include <string> -#include <thread> - -#if !defined(SPDLOG_ANDROID_RETRIES) -#define SPDLOG_ANDROID_RETRIES 2 -#endif - -namespace spdlog { -namespace sinks { - -/* - * Android sink (logging using __android_log_write) - */ -template<typename Mutex> -class android_sink final : public base_sink<Mutex> -{ -public: - explicit android_sink(std::string tag = "spdlog", bool use_raw_msg = false) - : tag_(std::move(tag)) - , use_raw_msg_(use_raw_msg) - { - } - -protected: - void sink_it_(const details::log_msg &msg) override - { - const android_LogPriority priority = convert_to_android_(msg.level); - fmt::memory_buffer formatted; - if (use_raw_msg_) - { - details::fmt_helper::append_buf(msg.raw, formatted); - } - else - { - sink::formatter_->format(msg, formatted); - } - formatted.push_back('\0'); - const char *msg_output = formatted.data(); - - // See system/core/liblog/logger_write.c for explanation of return value - int ret = __android_log_write(priority, tag_.c_str(), msg_output); - int retry_count = 0; - while ((ret == -11 /*EAGAIN*/) && (retry_count < SPDLOG_ANDROID_RETRIES)) - { - details::os::sleep_for_millis(5); - ret = __android_log_write(priority, tag_.c_str(), msg_output); - retry_count++; - } - - if (ret < 0) - { - throw spdlog_ex("__android_log_write() failed", ret); - } - } - - void flush_() override {} - -private: - static android_LogPriority convert_to_android_(spdlog::level::level_enum level) - { - switch (level) - { - case spdlog::level::trace: - return ANDROID_LOG_VERBOSE; - case spdlog::level::debug: - return ANDROID_LOG_DEBUG; - case spdlog::level::info: - return ANDROID_LOG_INFO; - case spdlog::level::warn: - return ANDROID_LOG_WARN; - case spdlog::level::err: - return ANDROID_LOG_ERROR; - case spdlog::level::critical: - return ANDROID_LOG_FATAL; - default: - return ANDROID_LOG_DEFAULT; - } - } - - std::string tag_; - bool use_raw_msg_; -}; - -using android_sink_mt = android_sink<std::mutex>; -using android_sink_st = android_sink<details::null_mutex>; -} // namespace sinks - -// Create and register android syslog logger - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> android_logger_mt(const std::string &logger_name, const std::string &tag = "spdlog") -{ - return Factory::template create<sinks::android_sink_mt>(logger_name, tag); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> android_logger_st(const std::string &logger_name, const std::string &tag = "spdlog") -{ - return Factory::template create<sinks::android_sink_st>(logger_name, tag); -} - -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/ansicolor_sink.h b/src/nmodl/ext/spdlog/sinks/ansicolor_sink.h deleted file mode 100644 index bb2a2a12e6..0000000000 --- a/src/nmodl/ext/spdlog/sinks/ansicolor_sink.h +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright(c) 2017 spdlog authors. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/console_globals.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/details/os.h" -#include "spdlog/sinks/sink.h" - -#include <memory> -#include <mutex> -#include <string> -#include <unordered_map> - -namespace spdlog { -namespace sinks { - -/** - * This sink prefixes the output with an ANSI escape sequence color code - * depending on the severity - * of the message. - * If no color terminal detected, omit the escape codes. - */ -template<typename TargetStream, class ConsoleMutex> -class ansicolor_sink final : public sink -{ -public: - using mutex_t = typename ConsoleMutex::mutex_t; - ansicolor_sink() - : target_file_(TargetStream::stream()) - , mutex_(ConsoleMutex::mutex()) - - { - should_do_colors_ = details::os::in_terminal(target_file_) && details::os::is_color_terminal(); - colors_[level::trace] = white; - colors_[level::debug] = cyan; - colors_[level::info] = green; - colors_[level::warn] = yellow + bold; - colors_[level::err] = red + bold; - colors_[level::critical] = bold + on_red; - colors_[level::off] = reset; - } - - ~ansicolor_sink() override = default; - - ansicolor_sink(const ansicolor_sink &other) = delete; - ansicolor_sink &operator=(const ansicolor_sink &other) = delete; - - void set_color(level::level_enum color_level, const std::string &color) - { - std::lock_guard<mutex_t> lock(mutex_); - colors_[color_level] = color; - } - - /// Formatting codes - const std::string reset = "\033[m"; - const std::string bold = "\033[1m"; - const std::string dark = "\033[2m"; - const std::string underline = "\033[4m"; - const std::string blink = "\033[5m"; - const std::string reverse = "\033[7m"; - const std::string concealed = "\033[8m"; - const std::string clear_line = "\033[K"; - - // Foreground colors - const std::string black = "\033[30m"; - const std::string red = "\033[31m"; - const std::string green = "\033[32m"; - const std::string yellow = "\033[33m"; - const std::string blue = "\033[34m"; - const std::string magenta = "\033[35m"; - const std::string cyan = "\033[36m"; - const std::string white = "\033[37m"; - - /// Background colors - const std::string on_black = "\033[40m"; - const std::string on_red = "\033[41m"; - const std::string on_green = "\033[42m"; - const std::string on_yellow = "\033[43m"; - const std::string on_blue = "\033[44m"; - const std::string on_magenta = "\033[45m"; - const std::string on_cyan = "\033[46m"; - const std::string on_white = "\033[47m"; - - void log(const details::log_msg &msg) override - { - // Wrap the originally formatted message in color codes. - // If color is not supported in the terminal, log as is instead. - std::lock_guard<mutex_t> lock(mutex_); - - fmt::memory_buffer formatted; - formatter_->format(msg, formatted); - if (should_do_colors_ && msg.color_range_end > msg.color_range_start) - { - // before color range - print_range_(formatted, 0, msg.color_range_start); - // in color range - print_ccode_(colors_[msg.level]); - print_range_(formatted, msg.color_range_start, msg.color_range_end); - print_ccode_(reset); - // after color range - print_range_(formatted, msg.color_range_end, formatted.size()); - } - else // no color - { - print_range_(formatted, 0, formatted.size()); - } - fflush(target_file_); - } - - void flush() override - { - std::lock_guard<mutex_t> lock(mutex_); - fflush(target_file_); - } - - void set_pattern(const std::string &pattern) final - { - std::lock_guard<mutex_t> lock(mutex_); - formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); - } - - void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override - { - std::lock_guard<mutex_t> lock(mutex_); - formatter_ = std::move(sink_formatter); - } - -private: - void print_ccode_(const std::string &color_code) - { - fwrite(color_code.data(), sizeof(char), color_code.size(), target_file_); - } - void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) - { - fwrite(formatted.data() + start, sizeof(char), end - start, target_file_); - } - - FILE *target_file_; - mutex_t &mutex_; - - bool should_do_colors_; - std::unordered_map<level::level_enum, std::string, level::level_hasher> colors_; -}; - -using ansicolor_stdout_sink_mt = ansicolor_sink<details::console_stdout, details::console_mutex>; -using ansicolor_stdout_sink_st = ansicolor_sink<details::console_stdout, details::console_nullmutex>; - -using ansicolor_stderr_sink_mt = ansicolor_sink<details::console_stderr, details::console_mutex>; -using ansicolor_stderr_sink_st = ansicolor_sink<details::console_stderr, details::console_nullmutex>; - -} // namespace sinks - -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/base_sink.h b/src/nmodl/ext/spdlog/sinks/base_sink.h deleted file mode 100644 index 22595182b3..0000000000 --- a/src/nmodl/ext/spdlog/sinks/base_sink.h +++ /dev/null @@ -1,69 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once -// -// base sink templated over a mutex (either dummy or real) -// concrete implementation should override the sink_it_() and flush_() methods. -// locking is taken care of in this class - no locking needed by the -// implementers.. -// - -#include "spdlog/common.h" -#include "spdlog/details/log_msg.h" -#include "spdlog/formatter.h" -#include "spdlog/sinks/sink.h" - -namespace spdlog { -namespace sinks { -template<typename Mutex> -class base_sink : public sink -{ -public: - base_sink() = default; - base_sink(const base_sink &) = delete; - base_sink &operator=(const base_sink &) = delete; - - void log(const details::log_msg &msg) final - { - std::lock_guard<Mutex> lock(mutex_); - sink_it_(msg); - } - - void flush() final - { - std::lock_guard<Mutex> lock(mutex_); - flush_(); - } - - void set_pattern(const std::string &pattern) final - { - std::lock_guard<Mutex> lock(mutex_); - set_pattern_(pattern); - } - - void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) final - { - std::lock_guard<Mutex> lock(mutex_); - set_formatter_(std::move(sink_formatter)); - } - -protected: - virtual void sink_it_(const details::log_msg &msg) = 0; - virtual void flush_() = 0; - - virtual void set_pattern_(const std::string &pattern) - { - set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); - } - - virtual void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) - { - formatter_ = std::move(sink_formatter); - } - Mutex mutex_; -}; -} // namespace sinks -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/basic_file_sink.h b/src/nmodl/ext/spdlog/sinks/basic_file_sink.h deleted file mode 100644 index 0ab884aa97..0000000000 --- a/src/nmodl/ext/spdlog/sinks/basic_file_sink.h +++ /dev/null @@ -1,70 +0,0 @@ -// -// Copyright(c) 2015-2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/file_helper.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/sinks/base_sink.h" - -#include <mutex> -#include <string> - -namespace spdlog { -namespace sinks { -/* - * Trivial file sink with single file as target - */ -template<typename Mutex> -class basic_file_sink final : public base_sink<Mutex> -{ -public: - explicit basic_file_sink(const filename_t &filename, bool truncate = false) - { - file_helper_.open(filename, truncate); - } - -protected: - void sink_it_(const details::log_msg &msg) override - { - fmt::memory_buffer formatted; - sink::formatter_->format(msg, formatted); - file_helper_.write(formatted); - } - - void flush_() override - { - file_helper_.flush(); - } - -private: - details::file_helper file_helper_; -}; - -using basic_file_sink_mt = basic_file_sink<std::mutex>; -using basic_file_sink_st = basic_file_sink<details::null_mutex>; - -} // namespace sinks - -// -// factory functions -// -template<typename Factory = default_factory> -inline std::shared_ptr<logger> basic_logger_mt(const std::string &logger_name, const filename_t &filename, bool truncate = false) -{ - return Factory::template create<sinks::basic_file_sink_mt>(logger_name, filename, truncate); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> basic_logger_st(const std::string &logger_name, const filename_t &filename, bool truncate = false) -{ - return Factory::template create<sinks::basic_file_sink_st>(logger_name, filename, truncate); -} - -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/daily_file_sink.h b/src/nmodl/ext/spdlog/sinks/daily_file_sink.h deleted file mode 100644 index 376a1ab03a..0000000000 --- a/src/nmodl/ext/spdlog/sinks/daily_file_sink.h +++ /dev/null @@ -1,136 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/file_helper.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/fmt/fmt.h" -#include "spdlog/sinks/base_sink.h" - -#include <chrono> -#include <cstdio> -#include <ctime> -#include <mutex> -#include <string> - -namespace spdlog { -namespace sinks { - -/* - * Generator of daily log file names in format basename.YYYY-MM-DD.ext - */ -struct daily_filename_calculator -{ - // Create filename for the form basename.YYYY-MM-DD - static filename_t calc_filename(const filename_t &filename, const tm &now_tm) - { - filename_t basename, ext; - std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); - std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; - fmt::format_to( - w, SPDLOG_FILENAME_T("{}_{:04d}-{:02d}-{:02d}{}"), basename, now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday, ext); - return fmt::to_string(w); - } -}; - -/* - * Rotating file sink based on date. rotates at midnight - */ -template<typename Mutex, typename FileNameCalc = daily_filename_calculator> -class daily_file_sink final : public base_sink<Mutex> -{ -public: - // create daily file sink which rotates on given time - daily_file_sink(filename_t base_filename, int rotation_hour, int rotation_minute, bool truncate = false) - : base_filename_(std::move(base_filename)) - , rotation_h_(rotation_hour) - , rotation_m_(rotation_minute) - , truncate_(truncate) - { - if (rotation_hour < 0 || rotation_hour > 23 || rotation_minute < 0 || rotation_minute > 59) - { - throw spdlog_ex("daily_file_sink: Invalid rotation time in ctor"); - } - auto now = log_clock::now(); - file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(now)), truncate_); - rotation_tp_ = next_rotation_tp_(); - } - -protected: - void sink_it_(const details::log_msg &msg) override - { - - if (msg.time >= rotation_tp_) - { - file_helper_.open(FileNameCalc::calc_filename(base_filename_, now_tm(msg.time)), truncate_); - rotation_tp_ = next_rotation_tp_(); - } - fmt::memory_buffer formatted; - sink::formatter_->format(msg, formatted); - file_helper_.write(formatted); - } - - void flush_() override - { - file_helper_.flush(); - } - -private: - tm now_tm(log_clock::time_point tp) - { - time_t tnow = log_clock::to_time_t(tp); - return spdlog::details::os::localtime(tnow); - } - - log_clock::time_point next_rotation_tp_() - { - auto now = log_clock::now(); - tm date = now_tm(now); - date.tm_hour = rotation_h_; - date.tm_min = rotation_m_; - date.tm_sec = 0; - auto rotation_time = log_clock::from_time_t(std::mktime(&date)); - if (rotation_time > now) - { - return rotation_time; - } - return {rotation_time + std::chrono::hours(24)}; - } - - filename_t base_filename_; - int rotation_h_; - int rotation_m_; - log_clock::time_point rotation_tp_; - details::file_helper file_helper_; - bool truncate_; -}; - -using daily_file_sink_mt = daily_file_sink<std::mutex>; -using daily_file_sink_st = daily_file_sink<details::null_mutex>; - -} // namespace sinks - -// -// factory functions -// -template<typename Factory = default_factory> -inline std::shared_ptr<logger> daily_logger_mt( - const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) -{ - return Factory::template create<sinks::daily_file_sink_mt>(logger_name, filename, hour, minute, truncate); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> daily_logger_st( - const std::string &logger_name, const filename_t &filename, int hour = 0, int minute = 0, bool truncate = false) -{ - return Factory::template create<sinks::daily_file_sink_st>(logger_name, filename, hour, minute, truncate); -} -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/dist_sink.h b/src/nmodl/ext/spdlog/sinks/dist_sink.h deleted file mode 100644 index 8ed41f1a95..0000000000 --- a/src/nmodl/ext/spdlog/sinks/dist_sink.h +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright (c) 2015 David Schury, Gabi Melman -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "base_sink.h" -#include "spdlog/details/log_msg.h" -#include "spdlog/details/null_mutex.h" - -#include <algorithm> -#include <memory> -#include <mutex> -#include <vector> - -// Distribution sink (mux). Stores a vector of sinks which get called when log -// is called - -namespace spdlog { -namespace sinks { - -template<typename Mutex> -class dist_sink : public base_sink<Mutex> -{ -public: - dist_sink() = default; - dist_sink(const dist_sink &) = delete; - dist_sink &operator=(const dist_sink &) = delete; - - void add_sink(std::shared_ptr<sink> sink) - { - std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); - sinks_.push_back(sink); - } - - void remove_sink(std::shared_ptr<sink> sink) - { - std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); - sinks_.erase(std::remove(sinks_.begin(), sinks_.end(), sink), sinks_.end()); - } - - void set_sinks(std::vector<std::shared_ptr<sink>> sinks) - { - std::lock_guard<Mutex> lock(base_sink<Mutex>::mutex_); - sinks_ = std::move(sinks); - } - -protected: - void sink_it_(const details::log_msg &msg) override - { - - for (auto &sink : sinks_) - { - if (sink->should_log(msg.level)) - { - sink->log(msg); - } - } - } - - void flush_() override - { - for (auto &sink : sinks_) - { - sink->flush(); - } - } - - void set_pattern_(const std::string &pattern) override - { - set_formatter_(details::make_unique<spdlog::pattern_formatter>(pattern)); - } - - void set_formatter_(std::unique_ptr<spdlog::formatter> sink_formatter) override - { - base_sink<Mutex>::formatter_ = std::move(sink_formatter); - for (auto &sink : sinks_) - { - sink->set_formatter(base_sink<Mutex>::formatter_->clone()); - } - } - std::vector<std::shared_ptr<sink>> sinks_; -}; - -using dist_sink_mt = dist_sink<std::mutex>; -using dist_sink_st = dist_sink<details::null_mutex>; - -} // namespace sinks -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/msvc_sink.h b/src/nmodl/ext/spdlog/sinks/msvc_sink.h deleted file mode 100644 index c8e60be779..0000000000 --- a/src/nmodl/ext/spdlog/sinks/msvc_sink.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright(c) 2016 Alexander Dalshov. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#if defined(_WIN32) - -#include "spdlog/details/null_mutex.h" -#include "spdlog/sinks/base_sink.h" - -#include <winbase.h> - -#include <mutex> -#include <string> - -namespace spdlog { -namespace sinks { -/* - * MSVC sink (logging using OutputDebugStringA) - */ -template<typename Mutex> -class msvc_sink : public base_sink<Mutex> -{ -public: - explicit msvc_sink() {} - -protected: - void sink_it_(const details::log_msg &msg) override - { - - fmt::memory_buffer formatted; - sink::formatter_->format(msg, formatted); - OutputDebugStringA(fmt::to_string(formatted).c_str()); - } - - void flush_() override {} -}; - -using msvc_sink_mt = msvc_sink<std::mutex>; -using msvc_sink_st = msvc_sink<details::null_mutex>; - -using windebug_sink_mt = msvc_sink_mt; -using windebug_sink_st = msvc_sink_st; - -} // namespace sinks -} // namespace spdlog - -#endif diff --git a/src/nmodl/ext/spdlog/sinks/null_sink.h b/src/nmodl/ext/spdlog/sinks/null_sink.h deleted file mode 100644 index ffab7a5299..0000000000 --- a/src/nmodl/ext/spdlog/sinks/null_sink.h +++ /dev/null @@ -1,49 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/null_mutex.h" -#include "spdlog/sinks/base_sink.h" - -#include <mutex> - -namespace spdlog { -namespace sinks { - -template<typename Mutex> -class null_sink : public base_sink<Mutex> -{ -protected: - void sink_it_(const details::log_msg &) override {} - void flush_() override {} -}; - -using null_sink_mt = null_sink<std::mutex>; -using null_sink_st = null_sink<details::null_mutex>; - -} // namespace sinks - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> null_logger_mt(const std::string &logger_name) -{ - auto null_logger = Factory::template create<sinks::null_sink_mt>(logger_name); - null_logger->set_level(level::off); - return null_logger; -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> null_logger_st(const std::string &logger_name) -{ - auto null_logger = Factory::template create<sinks::null_sink_st>(logger_name); - null_logger->set_level(level::off); - return null_logger; -} - -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/ostream_sink.h b/src/nmodl/ext/spdlog/sinks/ostream_sink.h deleted file mode 100644 index b28802a050..0000000000 --- a/src/nmodl/ext/spdlog/sinks/ostream_sink.h +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/null_mutex.h" -#include "spdlog/sinks/base_sink.h" - -#include <mutex> -#include <ostream> - -namespace spdlog { -namespace sinks { -template<typename Mutex> -class ostream_sink final : public base_sink<Mutex> -{ -public: - explicit ostream_sink(std::ostream &os, bool force_flush = false) - : ostream_(os) - , force_flush_(force_flush) - { - } - ostream_sink(const ostream_sink &) = delete; - ostream_sink &operator=(const ostream_sink &) = delete; - -protected: - void sink_it_(const details::log_msg &msg) override - { - fmt::memory_buffer formatted; - sink::formatter_->format(msg, formatted); - ostream_.write(formatted.data(), static_cast<std::streamsize>(formatted.size())); - if (force_flush_) - { - ostream_.flush(); - } - } - - void flush_() override - { - ostream_.flush(); - } - - std::ostream &ostream_; - bool force_flush_; -}; - -using ostream_sink_mt = ostream_sink<std::mutex>; -using ostream_sink_st = ostream_sink<details::null_mutex>; - -} // namespace sinks -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/rotating_file_sink.h b/src/nmodl/ext/spdlog/sinks/rotating_file_sink.h deleted file mode 100644 index adf12b6f67..0000000000 --- a/src/nmodl/ext/spdlog/sinks/rotating_file_sink.h +++ /dev/null @@ -1,154 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/file_helper.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/fmt/fmt.h" -#include "spdlog/sinks/base_sink.h" - -#include <cerrno> -#include <chrono> -#include <ctime> -#include <mutex> -#include <string> -#include <tuple> - -namespace spdlog { -namespace sinks { - -// -// Rotating file sink based on size -// -template<typename Mutex> -class rotating_file_sink final : public base_sink<Mutex> -{ -public: - rotating_file_sink(filename_t base_filename, std::size_t max_size, std::size_t max_files) - : base_filename_(std::move(base_filename)) - , max_size_(max_size) - , max_files_(max_files) - { - file_helper_.open(calc_filename(base_filename_, 0)); - current_size_ = file_helper_.size(); // expensive. called only once - } - - // calc filename according to index and file extension if exists. - // e.g. calc_filename("logs/mylog.txt, 3) => "logs/mylog.3.txt". - static filename_t calc_filename(const filename_t &filename, std::size_t index) - { - typename std::conditional<std::is_same<filename_t::value_type, char>::value, fmt::memory_buffer, fmt::wmemory_buffer>::type w; - if (index != 0u) - { - filename_t basename, ext; - std::tie(basename, ext) = details::file_helper::split_by_extenstion(filename); - fmt::format_to(w, SPDLOG_FILENAME_T("{}.{}{}"), basename, index, ext); - } - else - { - fmt::format_to(w, SPDLOG_FILENAME_T("{}"), filename); - } - return fmt::to_string(w); - } - -protected: - void sink_it_(const details::log_msg &msg) override - { - fmt::memory_buffer formatted; - sink::formatter_->format(msg, formatted); - current_size_ += formatted.size(); - if (current_size_ > max_size_) - { - rotate_(); - current_size_ = formatted.size(); - } - file_helper_.write(formatted); - } - - void flush_() override - { - file_helper_.flush(); - } - -private: - // Rotate files: - // log.txt -> log.1.txt - // log.1.txt -> log.2.txt - // log.2.txt -> log.3.txt - // log.3.txt -> delete - void rotate_() - { - using details::os::filename_to_str; - file_helper_.close(); - for (auto i = max_files_; i > 0; --i) - { - filename_t src = calc_filename(base_filename_, i - 1); - if (!details::file_helper::file_exists(src)) - { - continue; - } - filename_t target = calc_filename(base_filename_, i); - - if (!rename_file(src, target)) - { - // if failed try again after a small delay. - // this is a workaround to a windows issue, where very high rotation - // rates can cause the rename to fail with permission denied (because of antivirus?). - details::os::sleep_for_millis(100); - if (!rename_file(src, target)) - { - file_helper_.reopen(true); // truncate the log file anyway to prevent it to grow beyond its limit! - throw spdlog_ex( - "rotating_file_sink: failed renaming " + filename_to_str(src) + " to " + filename_to_str(target), errno); - } - } - } - file_helper_.reopen(true); - } - - // delete the target if exists, and rename the src file to target - // return true on success, false otherwise. - bool rename_file(const filename_t &src_filename, const filename_t &target_filename) - { - // try to delete the target file in case it already exists. - (void)details::os::remove(target_filename); - return details::os::rename(src_filename, target_filename) == 0; - } - - filename_t base_filename_; - std::size_t max_size_; - std::size_t max_files_; - std::size_t current_size_; - details::file_helper file_helper_; -}; - -using rotating_file_sink_mt = rotating_file_sink<std::mutex>; -using rotating_file_sink_st = rotating_file_sink<details::null_mutex>; - -} // namespace sinks - -// -// factory functions -// - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> rotating_logger_mt( - const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) -{ - return Factory::template create<sinks::rotating_file_sink_mt>(logger_name, filename, max_file_size, max_files); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> rotating_logger_st( - const std::string &logger_name, const filename_t &filename, size_t max_file_size, size_t max_files) -{ - return Factory::template create<sinks::rotating_file_sink_st>(logger_name, filename, max_file_size, max_files); -} -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/sink.h b/src/nmodl/ext/spdlog/sinks/sink.h deleted file mode 100644 index 9f84c3787a..0000000000 --- a/src/nmodl/ext/spdlog/sinks/sink.h +++ /dev/null @@ -1,59 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#include "spdlog/details/log_msg.h" -#include "spdlog/details/pattern_formatter.h" -#include "spdlog/formatter.h" - -namespace spdlog { -namespace sinks { -class sink -{ -public: - sink() - : level_(level::trace) - , formatter_(new pattern_formatter("%+")) - { - } - - explicit sink(std::unique_ptr<spdlog::pattern_formatter> formatter) - : level_(level::trace) - , formatter_(std::move(formatter)) - { - } - - virtual ~sink() = default; - virtual void log(const details::log_msg &msg) = 0; - virtual void flush() = 0; - virtual void set_pattern(const std::string &pattern) = 0; - virtual void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) = 0; - - bool should_log(level::level_enum msg_level) const - { - return msg_level >= level_.load(std::memory_order_relaxed); - } - - void set_level(level::level_enum log_level) - { - level_.store(log_level); - } - - level::level_enum level() const - { - return static_cast<spdlog::level::level_enum>(level_.load(std::memory_order_relaxed)); - } - -protected: - // sink log level - default is all - level_t level_; - - // sink formatter - default is full format - std::unique_ptr<spdlog::formatter> formatter_; -}; - -} // namespace sinks -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h b/src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h deleted file mode 100644 index f0475692a8..0000000000 --- a/src/nmodl/ext/spdlog/sinks/stdout_color_sinks.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// Copyright(c) 2018 spdlog -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#ifdef _WIN32 -#include "spdlog/sinks/wincolor_sink.h" -#else -#include "spdlog/sinks/ansicolor_sink.h" -#endif - -namespace spdlog { -namespace sinks { -#ifdef _WIN32 -using stdout_color_sink_mt = wincolor_stdout_sink_mt; -using stdout_color_sink_st = wincolor_stdout_sink_st; -using stderr_color_sink_mt = wincolor_stderr_sink_mt; -using stderr_color_sink_st = wincolor_stderr_sink_st; -#else -using stdout_color_sink_mt = ansicolor_stdout_sink_mt; -using stdout_color_sink_st = ansicolor_stdout_sink_st; -using stderr_color_sink_mt = ansicolor_stderr_sink_mt; -using stderr_color_sink_st = ansicolor_stderr_sink_st; -#endif -} // namespace sinks - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stdout_color_mt(const std::string &logger_name) -{ - return Factory::template create<sinks::stdout_color_sink_mt>(logger_name); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stdout_color_st(const std::string &logger_name) -{ - return Factory::template create<sinks::stdout_color_sink_st>(logger_name); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stderr_color_mt(const std::string &logger_name) -{ - return Factory::template create<sinks::stderr_color_sink_mt>(logger_name); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stderr_color_st(const std::string &logger_name) -{ - return Factory::template create<sinks::stderr_color_sink_mt>(logger_name); -} -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/stdout_sinks.h b/src/nmodl/ext/spdlog/sinks/stdout_sinks.h deleted file mode 100644 index efbbaaacea..0000000000 --- a/src/nmodl/ext/spdlog/sinks/stdout_sinks.h +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/details/console_globals.h" -#include "spdlog/details/null_mutex.h" - -#include <cstdio> -#include <memory> -#include <mutex> - -namespace spdlog { - -namespace sinks { - -template<typename TargetStream, typename ConsoleMutex> -class stdout_sink final : public sink -{ -public: - using mutex_t = typename ConsoleMutex::mutex_t; - stdout_sink() - : mutex_(ConsoleMutex::mutex()) - , file_(TargetStream::stream()) - { - } - ~stdout_sink() override = default; - - stdout_sink(const stdout_sink &other) = delete; - stdout_sink &operator=(const stdout_sink &other) = delete; - - void log(const details::log_msg &msg) override - { - std::lock_guard<mutex_t> lock(mutex_); - fmt::memory_buffer formatted; - formatter_->format(msg, formatted); - fwrite(formatted.data(), sizeof(char), formatted.size(), file_); - fflush(TargetStream::stream()); - } - - void flush() override - { - std::lock_guard<mutex_t> lock(mutex_); - fflush(file_); - } - - void set_pattern(const std::string &pattern) override - { - std::lock_guard<mutex_t> lock(mutex_); - formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); - } - - void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override - { - std::lock_guard<mutex_t> lock(mutex_); - formatter_ = std::move(sink_formatter); - } - -private: - mutex_t &mutex_; - FILE *file_; -}; - -using stdout_sink_mt = stdout_sink<details::console_stdout, details::console_mutex>; -using stdout_sink_st = stdout_sink<details::console_stdout, details::console_nullmutex>; - -using stderr_sink_mt = stdout_sink<details::console_stderr, details::console_mutex>; -using stderr_sink_st = stdout_sink<details::console_stderr, details::console_nullmutex>; - -} // namespace sinks - -// factory methods -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stdout_logger_mt(const std::string &logger_name) -{ - return Factory::template create<sinks::stdout_sink_mt>(logger_name); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stdout_logger_st(const std::string &logger_name) -{ - return Factory::template create<sinks::stdout_sink_st>(logger_name); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stderr_logger_mt(const std::string &logger_name) -{ - return Factory::template create<sinks::stderr_sink_mt>(logger_name); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> stderr_logger_st(const std::string &logger_name) -{ - return Factory::template create<sinks::stderr_sink_st>(logger_name); -} -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/syslog_sink.h b/src/nmodl/ext/spdlog/sinks/syslog_sink.h deleted file mode 100644 index cb76613d0b..0000000000 --- a/src/nmodl/ext/spdlog/sinks/syslog_sink.h +++ /dev/null @@ -1,94 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/sinks/base_sink.h" - -#include <array> -#include <string> -#include <syslog.h> - -namespace spdlog { -namespace sinks { -/** - * Sink that write to syslog using the `syscall()` library call. - * - * Locking is not needed, as `syslog()` itself is thread-safe. - */ -template<typename Mutex> -class syslog_sink : public base_sink<Mutex> -{ -public: - // - explicit syslog_sink(std::string ident = "", int syslog_option = 0, int syslog_facility = LOG_USER) - : ident_(std::move(ident)) - { - priorities_[static_cast<size_t>(level::trace)] = LOG_DEBUG; - priorities_[static_cast<size_t>(level::debug)] = LOG_DEBUG; - priorities_[static_cast<size_t>(level::info)] = LOG_INFO; - priorities_[static_cast<size_t>(level::warn)] = LOG_WARNING; - priorities_[static_cast<size_t>(level::err)] = LOG_ERR; - priorities_[static_cast<size_t>(level::critical)] = LOG_CRIT; - priorities_[static_cast<size_t>(level::off)] = LOG_INFO; - - // set ident to be program name if empty - ::openlog(ident_.empty() ? nullptr : ident_.c_str(), syslog_option, syslog_facility); - } - - ~syslog_sink() override - { - ::closelog(); - } - - syslog_sink(const syslog_sink &) = delete; - syslog_sink &operator=(const syslog_sink &) = delete; - -protected: - void sink_it_(const details::log_msg &msg) override - { - ::syslog(syslog_prio_from_level(msg), "%s", fmt::to_string(msg.raw).c_str()); - } - - void flush_() override {} - -private: - std::array<int, 7> priorities_; - // must store the ident because the man says openlog might use the pointer as - // is and not a string copy - const std::string ident_; - - // - // Simply maps spdlog's log level to syslog priority level. - // - int syslog_prio_from_level(const details::log_msg &msg) const - { - return priorities_[static_cast<size_t>(msg.level)]; - } -}; - -using syslog_sink_mt = syslog_sink<std::mutex>; -using syslog_sink_st = syslog_sink<details::null_mutex>; -} // namespace sinks - -// Create and register a syslog logger -template<typename Factory = default_factory> -inline std::shared_ptr<logger> syslog_logger_mt( - const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) -{ - return Factory::template create<sinks::syslog_sink_mt>(logger_name, syslog_ident, syslog_option, syslog_facility); -} - -template<typename Factory = default_factory> -inline std::shared_ptr<logger> syslog_logger_st( - const std::string &logger_name, const std::string &syslog_ident = "", int syslog_option = 0, int syslog_facility = (1 << 3)) -{ - return Factory::template create<sinks::syslog_sink_st>(logger_name, syslog_ident, syslog_option, syslog_facility); -} -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/sinks/wincolor_sink.h b/src/nmodl/ext/spdlog/sinks/wincolor_sink.h deleted file mode 100644 index 6e2b012208..0000000000 --- a/src/nmodl/ext/spdlog/sinks/wincolor_sink.h +++ /dev/null @@ -1,143 +0,0 @@ -// -// Copyright(c) 2016 spdlog -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#ifndef SPDLOG_H -#error "spdlog.h must be included before this file." -#endif - -#include "spdlog/common.h" -#include "spdlog/details/console_globals.h" -#include "spdlog/details/null_mutex.h" -#include "spdlog/sinks/sink.h" - -#include <memory> -#include <mutex> -#include <string> -#include <unordered_map> -#include <wincon.h> - -namespace spdlog { -namespace sinks { -/* - * Windows color console sink. Uses WriteConsoleA to write to the console with - * colors - */ -template<typename OutHandle, typename ConsoleMutex> -class wincolor_sink : public sink -{ -public: - const WORD BOLD = FOREGROUND_INTENSITY; - const WORD RED = FOREGROUND_RED; - const WORD GREEN = FOREGROUND_GREEN; - const WORD CYAN = FOREGROUND_GREEN | FOREGROUND_BLUE; - const WORD WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - const WORD YELLOW = FOREGROUND_RED | FOREGROUND_GREEN; - - wincolor_sink() - : out_handle_(OutHandle::handle()) - , mutex_(ConsoleMutex::mutex()) - { - colors_[level::trace] = WHITE; - colors_[level::debug] = CYAN; - colors_[level::info] = GREEN; - colors_[level::warn] = YELLOW | BOLD; - colors_[level::err] = RED | BOLD; // red bold - colors_[level::critical] = BACKGROUND_RED | WHITE | BOLD; // white bold on red background - colors_[level::off] = 0; - } - - ~wincolor_sink() override - { - this->flush(); - } - - wincolor_sink(const wincolor_sink &other) = delete; - wincolor_sink &operator=(const wincolor_sink &other) = delete; - - // change the color for the given level - void set_color(level::level_enum level, WORD color) - { - std::lock_guard<mutex_t> lock(mutex_); - colors_[level] = color; - } - - void log(const details::log_msg &msg) final override - { - std::lock_guard<mutex_t> lock(mutex_); - fmt::memory_buffer formatted; - formatter_->format(msg, formatted); - if (msg.color_range_end > msg.color_range_start) - { - // before color range - print_range_(formatted, 0, msg.color_range_start); - - // in color range - auto orig_attribs = set_console_attribs(colors_[msg.level]); - print_range_(formatted, msg.color_range_start, msg.color_range_end); - ::SetConsoleTextAttribute(out_handle_, - orig_attribs); // reset to orig colors - // after color range - print_range_(formatted, msg.color_range_end, formatted.size()); - } - else // print without colors if color range is invalid - { - print_range_(formatted, 0, formatted.size()); - } - } - - void flush() final override - { - // windows console always flushed? - } - - void set_pattern(const std::string &pattern) override final - { - std::lock_guard<mutex_t> lock(mutex_); - formatter_ = std::unique_ptr<spdlog::formatter>(new pattern_formatter(pattern)); - } - - void set_formatter(std::unique_ptr<spdlog::formatter> sink_formatter) override final - { - std::lock_guard<mutex_t> lock(mutex_); - formatter_ = std::move(sink_formatter); - } - -private: - using mutex_t = typename ConsoleMutex::mutex_t; - // set color and return the orig console attributes (for resetting later) - WORD set_console_attribs(WORD attribs) - { - CONSOLE_SCREEN_BUFFER_INFO orig_buffer_info; - ::GetConsoleScreenBufferInfo(out_handle_, &orig_buffer_info); - WORD back_color = orig_buffer_info.wAttributes; - // retrieve the current background color - back_color &= static_cast<WORD>(~(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY)); - // keep the background color unchanged - ::SetConsoleTextAttribute(out_handle_, attribs | back_color); - return orig_buffer_info.wAttributes; // return orig attribs - } - - // print a range of formatted message to console - void print_range_(const fmt::memory_buffer &formatted, size_t start, size_t end) - { - auto size = static_cast<DWORD>(end - start); - ::WriteConsoleA(out_handle_, formatted.data() + start, size, nullptr, nullptr); - } - - HANDLE out_handle_; - mutex_t &mutex_; - std::unordered_map<level::level_enum, WORD, level::level_hasher> colors_; -}; - -using wincolor_stdout_sink_mt = wincolor_sink<details::console_stdout, details::console_mutex>; -using wincolor_stdout_sink_st = wincolor_sink<details::console_stdout, details::console_nullmutex>; - -using wincolor_stderr_sink_mt = wincolor_sink<details::console_stderr, details::console_mutex>; -using wincolor_stderr_sink_st = wincolor_sink<details::console_stderr, details::console_nullmutex>; - -} // namespace sinks -} // namespace spdlog diff --git a/src/nmodl/ext/spdlog/spdlog.h b/src/nmodl/ext/spdlog/spdlog.h deleted file mode 100644 index bcca2e84f9..0000000000 --- a/src/nmodl/ext/spdlog/spdlog.h +++ /dev/null @@ -1,320 +0,0 @@ -// -// Copyright(c) 2015-2018 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// -// spdlog main header file. -// see example.cpp for usage example - -#ifndef SPDLOG_H -#define SPDLOG_H -#pragma once - -#include "spdlog/common.h" -#include "spdlog/details/registry.h" -#include "spdlog/logger.h" -#include "spdlog/version.h" - -#include <chrono> -#include <functional> -#include <memory> -#include <string> - -namespace spdlog { - -// Default logger factory- creates synchronous loggers -struct synchronous_factory -{ - template<typename Sink, typename... SinkArgs> - static std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... args) - { - auto sink = std::make_shared<Sink>(std::forward<SinkArgs>(args)...); - auto new_logger = std::make_shared<logger>(std::move(logger_name), std::move(sink)); - details::registry::instance().register_and_init(new_logger); - return new_logger; - } -}; - -using default_factory = synchronous_factory; - -// Create and register a logger with a templated sink type -// The logger's level, formatter and flush level will be set according the -// global settings. -// Example: -// spdlog::create<daily_file_sink_st>("logger_name", "dailylog_filename", 11, 59); -template<typename Sink, typename... SinkArgs> -inline std::shared_ptr<spdlog::logger> create(std::string logger_name, SinkArgs &&... sink_args) -{ - return default_factory::create<Sink>(std::move(logger_name), std::forward<SinkArgs>(sink_args)...); -} - -// Return an existing logger or nullptr if a logger with such name doesn't -// exist. -// example: spdlog::get("my_logger")->info("hello {}", "world"); -inline std::shared_ptr<logger> get(const std::string &name) -{ - return details::registry::instance().get(name); -} - -// Set global formatter. Each sink in each logger will get a clone of this object -inline void set_formatter(std::unique_ptr<spdlog::formatter> formatter) -{ - details::registry::instance().set_formatter(std::move(formatter)); -} - -// Set global format string. -// example: spdlog::set_pattern("%Y-%m-%d %H:%M:%S.%e %l : %v"); -inline void set_pattern(std::string pattern, pattern_time_type time_type = pattern_time_type::local) -{ - set_formatter(std::unique_ptr<spdlog::formatter>(new pattern_formatter(std::move(pattern), time_type))); -} - -// Set global logging level -inline void set_level(level::level_enum log_level) -{ - details::registry::instance().set_level(log_level); -} - -// Set global flush level -inline void flush_on(level::level_enum log_level) -{ - details::registry::instance().flush_on(log_level); -} - -// Start/Restart a periodic flusher thread -// Warning: Use only if all your loggers are thread safe! -inline void flush_every(std::chrono::seconds interval) -{ - details::registry::instance().flush_every(interval); -} - -// Set global error handler -inline void set_error_handler(log_err_handler handler) -{ - details::registry::instance().set_error_handler(std::move(handler)); -} - -// Register the given logger with the given name -inline void register_logger(std::shared_ptr<logger> logger) -{ - details::registry::instance().register_logger(std::move(logger)); -} - -// Apply a user defined function on all registered loggers -// Example: -// spdlog::apply_all([&](std::shared_ptr<spdlog::logger> l) {l->flush();}); -inline void apply_all(const std::function<void(std::shared_ptr<logger>)> &fun) -{ - details::registry::instance().apply_all(fun); -} - -// Drop the reference to the given logger -inline void drop(const std::string &name) -{ - details::registry::instance().drop(name); -} - -// Drop all references from the registry -inline void drop_all() -{ - details::registry::instance().drop_all(); -} - -// stop any running threads started by spdlog and clean registry loggers -inline void shutdown() -{ - details::registry::instance().shutdown(); -} - -// API for using default logger (stdout_color_mt), -// e.g: spdlog::info("Message {}", 1); -// -// The default logger object can be accessed using the spdlog::default_logger(): -// For example, to add another sink to it: -// spdlog::default_logger()->sinks()->push_back(some_sink); -// -// The default logger can replaced using spdlog::set_default_logger(new_logger). -// For example, to replace it with a file logger. -// -// IMPORTANT: -// The default API is thread safe (for _mt loggers), but: -// set_default_logger() *should not* be used concurrently with the default API. -// e.g do not call set_default_logger() from one thread while calling spdlog::info() from another. - -inline std::shared_ptr<spdlog::logger> default_logger() -{ - return details::registry::instance().default_logger(); -} - -inline spdlog::logger *default_logger_raw() -{ - return details::registry::instance().get_default_raw(); -} - -inline void set_default_logger(std::shared_ptr<spdlog::logger> default_logger) -{ - details::registry::instance().set_default_logger(std::move(default_logger)); -} - -template<typename... Args> -inline void log(level::level_enum lvl, const char *fmt, const Args &... args) -{ - default_logger_raw()->log(lvl, fmt, args...); -} - -template<typename... Args> -inline void trace(const char *fmt, const Args &... args) -{ - default_logger_raw()->trace(fmt, args...); -} - -template<typename... Args> -inline void debug(const char *fmt, const Args &... args) -{ - default_logger_raw()->debug(fmt, args...); -} - -template<typename... Args> -inline void info(const char *fmt, const Args &... args) -{ - default_logger_raw()->info(fmt, args...); -} - -template<typename... Args> -inline void warn(const char *fmt, const Args &... args) -{ - default_logger_raw()->warn(fmt, args...); -} - -template<typename... Args> -inline void error(const char *fmt, const Args &... args) -{ - default_logger_raw()->error(fmt, args...); -} - -template<typename... Args> -inline void critical(const char *fmt, const Args &... args) -{ - default_logger_raw()->critical(fmt, args...); -} - -template<typename T> -inline void log(level::level_enum lvl, const T &msg) -{ - default_logger_raw()->log(lvl, msg); -} - -template<typename T> -inline void trace(const T &msg) -{ - default_logger_raw()->trace(msg); -} - -template<typename T> -inline void debug(const T &msg) -{ - default_logger_raw()->debug(msg); -} - -template<typename T> -inline void info(const T &msg) -{ - default_logger_raw()->info(msg); -} - -template<typename T> -inline void warn(const T &msg) -{ - default_logger_raw()->warn(msg); -} - -template<typename T> -inline void error(const T &msg) -{ - default_logger_raw()->error(msg); -} - -template<typename T> -inline void critical(const T &msg) -{ - default_logger_raw()->critical(msg); -} - -#ifdef SPDLOG_WCHAR_TO_UTF8_SUPPORT -template<typename... Args> -inline void log(level::level_enum lvl, const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->log(lvl, fmt, args...); -} - -template<typename... Args> -inline void trace(const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->trace(fmt, args...); -} - -template<typename... Args> -inline void debug(const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->debug(fmt, args...); -} - -template<typename... Args> -inline void info(const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->info(fmt, args...); -} - -template<typename... Args> -inline void warn(const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->warn(fmt, args...); -} - -template<typename... Args> -inline void error(const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->error(fmt, args...); -} - -template<typename... Args> -inline void critical(const wchar_t *fmt, const Args &... args) -{ - default_logger_raw()->critical(fmt, args...); -} - -#endif // SPDLOG_WCHAR_TO_UTF8_SUPPORT - -// -// Trace & Debug can be switched on/off at compile time with zero cost. -// Uncomment SPDLOG_DEBUG_ON/SPDLOG_TRACE_ON in tweakme.h to enable. -// SPDLOG_TRACE(..) will also print current file and line. -// -// Example: -// spdlog::set_level(spdlog::level::trace); -// SPDLOG_TRACE(my_logger, "another trace message {} {}", 1, 2); -// - -#ifdef SPDLOG_TRACE_ON -#define SPDLOG_STR_H(x) #x -#define SPDLOG_STR_HELPER(x) SPDLOG_STR_H(x) -#ifdef _MSC_VER -#define SPDLOG_TRACE(logger, ...) \ - logger->trace("[ "__FILE__ \ - "(" SPDLOG_STR_HELPER(__LINE__) ")] " __VA_ARGS__) -#else -#define SPDLOG_TRACE(logger, ...) \ - logger->trace("[" __FILE__ ":" SPDLOG_STR_HELPER(__LINE__) "]" \ - " " __VA_ARGS__) -#endif -#else -#define SPDLOG_TRACE(logger, ...) (void)0 -#endif - -#ifdef SPDLOG_DEBUG_ON -#define SPDLOG_DEBUG(logger, ...) logger->debug(__VA_ARGS__) -#else -#define SPDLOG_DEBUG(logger, ...) (void)0 -#endif - -} // namespace spdlog -#endif // SPDLOG_H diff --git a/src/nmodl/ext/spdlog/tweakme.h b/src/nmodl/ext/spdlog/tweakme.h deleted file mode 100644 index d91159f7e6..0000000000 --- a/src/nmodl/ext/spdlog/tweakme.h +++ /dev/null @@ -1,130 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -/////////////////////////////////////////////////////////////////////////////// -// -// Edit this file to squeeze more performance, and to customize supported -// features -// -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Under Linux, the much faster CLOCK_REALTIME_COARSE clock can be used. -// This clock is less accurate - can be off by dozens of millis - depending on -// the kernel HZ. -// Uncomment to use it instead of the regular clock. -// -// #define SPDLOG_CLOCK_COARSE -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment if date/time logging is not needed and never appear in the log -// pattern. -// This will prevent spdlog from querying the clock on each log call. -// -// WARNING: If the log pattern contains any date/time while this flag is on, the -// result is undefined. -// You must set new pattern(spdlog::set_pattern(..") without any -// date/time in it -// -// #define SPDLOG_NO_DATETIME -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment if thread id logging is not needed (i.e. no %t in the log pattern). -// This will prevent spdlog from querying the thread id on each log call. -// -// WARNING: If the log pattern contains thread id (i.e, %t) while this flag is -// on, the result is undefined. -// -// #define SPDLOG_NO_THREAD_ID -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to prevent spdlog from caching thread ids in thread local storage. -// By default spdlog saves thread ids in tls to gain a few micros for each call. -// -// WARNING: if your program forks, UNCOMMENT this flag to prevent undefined -// thread ids in the children logs. -// -// #define SPDLOG_DISABLE_TID_CACHING -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment if logger name logging is not needed. -// This will prevent spdlog from copying the logger name on each log call. -// -// #define SPDLOG_NO_NAME -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to enable the SPDLOG_DEBUG/SPDLOG_TRACE macros. -// -// #define SPDLOG_DEBUG_ON -// #define SPDLOG_TRACE_ON -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to avoid spdlog's usage of atomic log levels -// Use only if your code never modifies a logger's log levels concurrently by -// different threads. -// -// #define SPDLOG_NO_ATOMIC_LEVELS -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to enable usage of wchar_t for file names on Windows. -// -// #define SPDLOG_WCHAR_FILENAMES -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to override default eol ("\n" or "\r\n" under Linux/Windows) -// -// #define SPDLOG_EOL ";-)\n" -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to use your own copy of the fmt library instead of spdlog's copy. -// In this case spdlog will try to include <fmt/format.h> so set your -I flag -// accordingly. -// -// #define SPDLOG_FMT_EXTERNAL -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to enable wchar_t support (convert to utf8) -// -// #define SPDLOG_WCHAR_TO_UTF8_SUPPORT -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to prevent child processes from inheriting log file descriptors -// -// #define SPDLOG_PREVENT_CHILD_FD -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to enable message counting feature. -// Use the %i in the logger pattern to display log message sequence id. -// -// #define SPDLOG_ENABLE_MESSAGE_COUNTER -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to customize level names (e.g. "MT TRACE") -// -// #define SPDLOG_LEVEL_NAMES { "MY TRACE", "MY DEBUG", "MY INFO", "MY WARNING", -// "MY ERROR", "MY CRITICAL", "OFF" } -/////////////////////////////////////////////////////////////////////////////// - -/////////////////////////////////////////////////////////////////////////////// -// Uncomment to disable default logger creation. -// This might save some (very) small initialization time if no default logger is needed. -// -// #define SPDLOG_DISABLE_DEFAULT_LOGGER -/////////////////////////////////////////////////////////////////////////////// diff --git a/src/nmodl/ext/spdlog/version.h b/src/nmodl/ext/spdlog/version.h deleted file mode 100644 index 16b9194b55..0000000000 --- a/src/nmodl/ext/spdlog/version.h +++ /dev/null @@ -1,12 +0,0 @@ -// -// Copyright(c) 2015 Gabi Melman. -// Distributed under the MIT License (http://opensource.org/licenses/MIT) -// - -#pragma once - -#define SPDLOG_VER_MAJOR 1 -#define SPDLOG_VER_MINOR 3 -#define SPDLOG_VER_PATCH 0 - -#define SPDLOG_VERSION (SPDLOG_VER_MAJOR * 10000 + SPDLOG_VER_MINOR * 100 + SPDLOG_VER_PATCH) diff --git a/src/nmodl/ext/tclap/Arg.h b/src/nmodl/ext/tclap/Arg.h deleted file mode 100755 index b28eef117c..0000000000 --- a/src/nmodl/ext/tclap/Arg.h +++ /dev/null @@ -1,692 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: Arg.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_ARGUMENT_H -#define TCLAP_ARGUMENT_H - -#ifdef HAVE_CONFIG_H -#include <config.h> -#else -#define HAVE_SSTREAM -#endif - -#include <string> -#include <vector> -#include <list> -#include <iostream> -#include <iomanip> -#include <cstdio> - -#if defined(HAVE_SSTREAM) -#include <sstream> -typedef std::istringstream istringstream; -#elif defined(HAVE_STRSTREAM) -#include <strstream> -typedef std::istrstream istringstream; -#else -#error "Need a stringstream (sstream or strstream) to compile!" -#endif - -#include <tclap/ArgException.h> -#include <tclap/Visitor.h> -#include <tclap/CmdLineInterface.h> -#include <tclap/ArgTraits.h> -#include <tclap/StandardTraits.h> - -namespace TCLAP { - -/** - * A virtual base class that defines the essential data for all arguments. - * This class, or one of its existing children, must be subclassed to do - * anything. - */ -class Arg -{ - private: - /** - * Prevent accidental copying. - */ - Arg(const Arg& rhs); - - /** - * Prevent accidental copying. - */ - Arg& operator=(const Arg& rhs); - - /** - * Indicates whether the rest of the arguments should be ignored. - */ - static bool& ignoreRestRef() { static bool ign = false; return ign; } - - /** - * The delimiter that separates an argument flag/name from the - * value. - */ - static char& delimiterRef() { static char delim = ' '; return delim; } - - protected: - - /** - * The single char flag used to identify the argument. - * This value (preceded by a dash {-}), can be used to identify - * an argument on the command line. The _flag can be blank, - * in fact this is how unlabeled args work. Unlabeled args must - * override appropriate functions to get correct handling. Note - * that the _flag does NOT include the dash as part of the flag. - */ - std::string _flag; - - /** - * A single work namd indentifying the argument. - * This value (preceded by two dashed {--}) can also be used - * to identify an argument on the command line. Note that the - * _name does NOT include the two dashes as part of the _name. The - * _name cannot be blank. - */ - std::string _name; - - /** - * Description of the argument. - */ - std::string _description; - - /** - * Indicating whether the argument is required. - */ - bool _required; - - /** - * Label to be used in usage description. Normally set to - * "required", but can be changed when necessary. - */ - std::string _requireLabel; - - /** - * Indicates whether a value is required for the argument. - * Note that the value may be required but the argument/value - * combination may not be, as specified by _required. - */ - bool _valueRequired; - - /** - * Indicates whether the argument has been set. - * Indicates that a value on the command line has matched the - * name/flag of this argument and the values have been set accordingly. - */ - bool _alreadySet; - - /** - * A pointer to a vistitor object. - * The visitor allows special handling to occur as soon as the - * argument is matched. This defaults to NULL and should not - * be used unless absolutely necessary. - */ - Visitor* _visitor; - - /** - * Whether this argument can be ignored, if desired. - */ - bool _ignoreable; - - /** - * Indicates that the arg was set as part of an XOR and not on the - * command line. - */ - bool _xorSet; - - bool _acceptsMultipleValues; - - /** - * Performs the special handling described by the Vistitor. - */ - void _checkWithVisitor() const; - - /** - * Primary constructor. YOU (yes you) should NEVER construct an Arg - * directly, this is a base class that is extended by various children - * that are meant to be used. Use SwitchArg, ValueArg, MultiArg, - * UnlabeledValueArg, or UnlabeledMultiArg instead. - * - * \param flag - The flag identifying the argument. - * \param name - The name identifying the argument. - * \param desc - The description of the argument, used in the usage. - * \param req - Whether the argument is required. - * \param valreq - Whether the a value is required for the argument. - * \param v - The visitor checked by the argument. Defaults to NULL. - */ - Arg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - bool valreq, - Visitor* v = NULL ); - - public: - /** - * Destructor. - */ - virtual ~Arg(); - - /** - * Adds this to the specified list of Args. - * \param argList - The list to add this to. - */ - virtual void addToList( std::list<Arg*>& argList ) const; - - /** - * Begin ignoring arguments since the "--" argument was specified. - */ - static void beginIgnoring() { ignoreRestRef() = true; } - - /** - * Whether to ignore the rest. - */ - static bool ignoreRest() { return ignoreRestRef(); } - - /** - * The delimiter that separates an argument flag/name from the - * value. - */ - static char delimiter() { return delimiterRef(); } - - /** - * The char used as a place holder when SwitchArgs are combined. - * Currently set to the bell char (ASCII 7). - */ - static char blankChar() { return (char)7; } - - /** - * The char that indicates the beginning of a flag. Defaults to '-', but - * clients can define TCLAP_FLAGSTARTCHAR to override. - */ -#ifndef TCLAP_FLAGSTARTCHAR -#define TCLAP_FLAGSTARTCHAR '-' -#endif - static char flagStartChar() { return TCLAP_FLAGSTARTCHAR; } - - /** - * The sting that indicates the beginning of a flag. Defaults to "-", but - * clients can define TCLAP_FLAGSTARTSTRING to override. Should be the same - * as TCLAP_FLAGSTARTCHAR. - */ -#ifndef TCLAP_FLAGSTARTSTRING -#define TCLAP_FLAGSTARTSTRING "-" -#endif - static const std::string flagStartString() { return TCLAP_FLAGSTARTSTRING; } - - /** - * The sting that indicates the beginning of a name. Defaults to "--", but - * clients can define TCLAP_NAMESTARTSTRING to override. - */ -#ifndef TCLAP_NAMESTARTSTRING -#define TCLAP_NAMESTARTSTRING "--" -#endif - static const std::string nameStartString() { return TCLAP_NAMESTARTSTRING; } - - /** - * The name used to identify the ignore rest argument. - */ - static const std::string ignoreNameString() { return "ignore_rest"; } - - /** - * Sets the delimiter for all arguments. - * \param c - The character that delimits flags/names from values. - */ - static void setDelimiter( char c ) { delimiterRef() = c; } - - /** - * Pure virtual method meant to handle the parsing and value assignment - * of the string on the command line. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. What is - * passed in from main. - */ - virtual bool processArg(int *i, std::vector<std::string>& args) = 0; - - /** - * Operator ==. - * Equality operator. Must be virtual to handle unlabeled args. - * \param a - The Arg to be compared to this. - */ - virtual bool operator==(const Arg& a) const; - - /** - * Returns the argument flag. - */ - const std::string& getFlag() const; - - /** - * Returns the argument name. - */ - const std::string& getName() const; - - /** - * Returns the argument description. - */ - std::string getDescription() const; - - /** - * Indicates whether the argument is required. - */ - virtual bool isRequired() const; - - /** - * Sets _required to true. This is used by the XorHandler. - * You really have no reason to ever use it. - */ - void forceRequired(); - - /** - * Sets the _alreadySet value to true. This is used by the XorHandler. - * You really have no reason to ever use it. - */ - void xorSet(); - - /** - * Indicates whether a value must be specified for argument. - */ - bool isValueRequired() const; - - /** - * Indicates whether the argument has already been set. Only true - * if the arg has been matched on the command line. - */ - bool isSet() const; - - /** - * Indicates whether the argument can be ignored, if desired. - */ - bool isIgnoreable() const; - - /** - * A method that tests whether a string matches this argument. - * This is generally called by the processArg() method. This - * method could be re-implemented by a child to change how - * arguments are specified on the command line. - * \param s - The string to be compared to the flag/name to determine - * whether the arg matches. - */ - virtual bool argMatches( const std::string& s ) const; - - /** - * Returns a simple string representation of the argument. - * Primarily for debugging. - */ - virtual std::string toString() const; - - /** - * Returns a short ID for the usage. - * \param valueId - The value used in the id. - */ - virtual std::string shortID( const std::string& valueId = "val" ) const; - - /** - * Returns a long ID for the usage. - * \param valueId - The value used in the id. - */ - virtual std::string longID( const std::string& valueId = "val" ) const; - - /** - * Trims a value off of the flag. - * \param flag - The string from which the flag and value will be - * trimmed. Contains the flag once the value has been trimmed. - * \param value - Where the value trimmed from the string will - * be stored. - */ - virtual void trimFlag( std::string& flag, std::string& value ) const; - - /** - * Checks whether a given string has blank chars, indicating that - * it is a combined SwitchArg. If so, return true, otherwise return - * false. - * \param s - string to be checked. - */ - bool _hasBlanks( const std::string& s ) const; - - /** - * Sets the requireLabel. Used by XorHandler. You shouldn't ever - * use this. - * \param s - Set the requireLabel to this value. - */ - void setRequireLabel( const std::string& s ); - - /** - * Used for MultiArgs and XorHandler to determine whether args - * can still be set. - */ - virtual bool allowMore(); - - /** - * Use by output classes to determine whether an Arg accepts - * multiple values. - */ - virtual bool acceptsMultipleValues(); - - /** - * Clears the Arg object and allows it to be reused by new - * command lines. - */ - virtual void reset(); -}; - -/** - * Typedef of an Arg list iterator. - */ -typedef std::list<Arg*>::iterator ArgListIterator; - -/** - * Typedef of an Arg vector iterator. - */ -typedef std::vector<Arg*>::iterator ArgVectorIterator; - -/** - * Typedef of a Visitor list iterator. - */ -typedef std::list<Visitor*>::iterator VisitorListIterator; - -/* - * Extract a value of type T from it's string representation contained - * in strVal. The ValueLike parameter used to select the correct - * specialization of ExtractValue depending on the value traits of T. - * ValueLike traits use operator>> to assign the value from strVal. - */ -template<typename T> void -ExtractValue(T &destVal, const std::string& strVal, ValueLike vl) -{ - static_cast<void>(vl); // Avoid warning about unused vl - std::istringstream is(strVal); - - int valuesRead = 0; - while ( is.good() ) { - if ( is.peek() != EOF ) -#ifdef TCLAP_SETBASE_ZERO - is >> std::setbase(0) >> destVal; -#else - is >> destVal; -#endif - else - break; - - valuesRead++; - } - - if ( is.fail() ) - throw( ArgParseException("Couldn't read argument value " - "from string '" + strVal + "'")); - - - if ( valuesRead > 1 ) - throw( ArgParseException("More than one valid value parsed from " - "string '" + strVal + "'")); - -} - -/* - * Extract a value of type T from it's string representation contained - * in strVal. The ValueLike parameter used to select the correct - * specialization of ExtractValue depending on the value traits of T. - * StringLike uses assignment (operator=) to assign from strVal. - */ -template<typename T> void -ExtractValue(T &destVal, const std::string& strVal, StringLike sl) -{ - static_cast<void>(sl); // Avoid warning about unused sl - SetString(destVal, strVal); -} - -////////////////////////////////////////////////////////////////////// -//BEGIN Arg.cpp -////////////////////////////////////////////////////////////////////// - -inline Arg::Arg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - bool valreq, - Visitor* v) : - _flag(flag), - _name(name), - _description(desc), - _required(req), - _requireLabel("required"), - _valueRequired(valreq), - _alreadySet(false), - _visitor( v ), - _ignoreable(true), - _xorSet(false), - _acceptsMultipleValues(false) -{ - if ( _flag.length() > 1 ) - throw(SpecificationException( - "Argument flag can only be one character long", toString() ) ); - - if ( _name != ignoreNameString() && - ( _flag == Arg::flagStartString() || - _flag == Arg::nameStartString() || - _flag == " " ) ) - throw(SpecificationException("Argument flag cannot be either '" + - Arg::flagStartString() + "' or '" + - Arg::nameStartString() + "' or a space.", - toString() ) ); - - if ( ( _name.substr( 0, Arg::flagStartString().length() ) == Arg::flagStartString() ) || - ( _name.substr( 0, Arg::nameStartString().length() ) == Arg::nameStartString() ) || - ( _name.find( " ", 0 ) != std::string::npos ) ) - throw(SpecificationException("Argument name begin with either '" + - Arg::flagStartString() + "' or '" + - Arg::nameStartString() + "' or space.", - toString() ) ); - -} - -inline Arg::~Arg() { } - -inline std::string Arg::shortID( const std::string& valueId ) const -{ - std::string id = ""; - - if ( _flag != "" ) - id = Arg::flagStartString() + _flag; - else - id = Arg::nameStartString() + _name; - - if ( _valueRequired ) - id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; - - if ( !_required ) - id = "[" + id + "]"; - - return id; -} - -inline std::string Arg::longID( const std::string& valueId ) const -{ - std::string id = ""; - - if ( _flag != "" ) - { - id += Arg::flagStartString() + _flag; - - if ( _valueRequired ) - id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; - - id += ", "; - } - - id += Arg::nameStartString() + _name; - - if ( _valueRequired ) - id += std::string( 1, Arg::delimiter() ) + "<" + valueId + ">"; - - return id; - -} - -inline bool Arg::operator==(const Arg& a) const -{ - if ( ( _flag != "" && _flag == a._flag ) || _name == a._name) - return true; - else - return false; -} - -inline std::string Arg::getDescription() const -{ - std::string desc = ""; - if ( _required ) - desc = "(" + _requireLabel + ") "; - -// if ( _valueRequired ) -// desc += "(value required) "; - - desc += _description; - return desc; -} - -inline const std::string& Arg::getFlag() const { return _flag; } - -inline const std::string& Arg::getName() const { return _name; } - -inline bool Arg::isRequired() const { return _required; } - -inline bool Arg::isValueRequired() const { return _valueRequired; } - -inline bool Arg::isSet() const -{ - if ( _alreadySet && !_xorSet ) - return true; - else - return false; -} - -inline bool Arg::isIgnoreable() const { return _ignoreable; } - -inline void Arg::setRequireLabel( const std::string& s) -{ - _requireLabel = s; -} - -inline bool Arg::argMatches( const std::string& argFlag ) const -{ - if ( ( argFlag == Arg::flagStartString() + _flag && _flag != "" ) || - argFlag == Arg::nameStartString() + _name ) - return true; - else - return false; -} - -inline std::string Arg::toString() const -{ - std::string s = ""; - - if ( _flag != "" ) - s += Arg::flagStartString() + _flag + " "; - - s += "(" + Arg::nameStartString() + _name + ")"; - - return s; -} - -inline void Arg::_checkWithVisitor() const -{ - if ( _visitor != NULL ) - _visitor->visit(); -} - -/** - * Implementation of trimFlag. - */ -inline void Arg::trimFlag(std::string& flag, std::string& value) const -{ - int stop = 0; - for ( int i = 0; static_cast<unsigned int>(i) < flag.length(); i++ ) - if ( flag[i] == Arg::delimiter() ) - { - stop = i; - break; - } - - if ( stop > 1 ) - { - value = flag.substr(stop+1); - flag = flag.substr(0,stop); - } - -} - -/** - * Implementation of _hasBlanks. - */ -inline bool Arg::_hasBlanks( const std::string& s ) const -{ - for ( int i = 1; static_cast<unsigned int>(i) < s.length(); i++ ) - if ( s[i] == Arg::blankChar() ) - return true; - - return false; -} - -inline void Arg::forceRequired() -{ - _required = true; -} - -inline void Arg::xorSet() -{ - _alreadySet = true; - _xorSet = true; -} - -/** - * Overridden by Args that need to added to the end of the list. - */ -inline void Arg::addToList( std::list<Arg*>& argList ) const -{ - argList.push_front( const_cast<Arg*>(this) ); -} - -inline bool Arg::allowMore() -{ - return false; -} - -inline bool Arg::acceptsMultipleValues() -{ - return _acceptsMultipleValues; -} - -inline void Arg::reset() -{ - _xorSet = false; - _alreadySet = false; -} - -////////////////////////////////////////////////////////////////////// -//END Arg.cpp -////////////////////////////////////////////////////////////////////// - -} //namespace TCLAP - -#endif - diff --git a/src/nmodl/ext/tclap/ArgException.h b/src/nmodl/ext/tclap/ArgException.h deleted file mode 100755 index 3411aa9543..0000000000 --- a/src/nmodl/ext/tclap/ArgException.h +++ /dev/null @@ -1,200 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: ArgException.h - * - * Copyright (c) 2003, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_ARG_EXCEPTION_H -#define TCLAP_ARG_EXCEPTION_H - -#include <string> -#include <exception> - -namespace TCLAP { - -/** - * A simple class that defines and argument exception. Should be caught - * whenever a CmdLine is created and parsed. - */ -class ArgException : public std::exception -{ - public: - - /** - * Constructor. - * \param text - The text of the exception. - * \param id - The text identifying the argument source. - * \param td - Text describing the type of ArgException it is. - * of the exception. - */ - ArgException( const std::string& text = "undefined exception", - const std::string& id = "undefined", - const std::string& td = "Generic ArgException") - : std::exception(), - _errorText(text), - _argId( id ), - _typeDescription(td) - { } - - /** - * Destructor. - */ - virtual ~ArgException() throw() { } - - /** - * Returns the error text. - */ - std::string error() const { return ( _errorText ); } - - /** - * Returns the argument id. - */ - std::string argId() const - { - if ( _argId == "undefined" ) - return " "; - else - return ( "Argument: " + _argId ); - } - - /** - * Returns the arg id and error text. - */ - const char* what() const throw() - { - static std::string ex; - ex = _argId + " -- " + _errorText; - return ex.c_str(); - } - - /** - * Returns the type of the exception. Used to explain and distinguish - * between different child exceptions. - */ - std::string typeDescription() const - { - return _typeDescription; - } - - - private: - - /** - * The text of the exception message. - */ - std::string _errorText; - - /** - * The argument related to this exception. - */ - std::string _argId; - - /** - * Describes the type of the exception. Used to distinguish - * between different child exceptions. - */ - std::string _typeDescription; - -}; - -/** - * Thrown from within the child Arg classes when it fails to properly - * parse the argument it has been passed. - */ -class ArgParseException : public ArgException -{ - public: - /** - * Constructor. - * \param text - The text of the exception. - * \param id - The text identifying the argument source - * of the exception. - */ - ArgParseException( const std::string& text = "undefined exception", - const std::string& id = "undefined" ) - : ArgException( text, - id, - std::string( "Exception found while parsing " ) + - std::string( "the value the Arg has been passed." )) - { } -}; - -/** - * Thrown from CmdLine when the arguments on the command line are not - * properly specified, e.g. too many arguments, required argument missing, etc. - */ -class CmdLineParseException : public ArgException -{ - public: - /** - * Constructor. - * \param text - The text of the exception. - * \param id - The text identifying the argument source - * of the exception. - */ - CmdLineParseException( const std::string& text = "undefined exception", - const std::string& id = "undefined" ) - : ArgException( text, - id, - std::string( "Exception found when the values ") + - std::string( "on the command line do not meet ") + - std::string( "the requirements of the defined ") + - std::string( "Args." )) - { } -}; - -/** - * Thrown from Arg and CmdLine when an Arg is improperly specified, e.g. - * same flag as another Arg, same name, etc. - */ -class SpecificationException : public ArgException -{ - public: - /** - * Constructor. - * \param text - The text of the exception. - * \param id - The text identifying the argument source - * of the exception. - */ - SpecificationException( const std::string& text = "undefined exception", - const std::string& id = "undefined" ) - : ArgException( text, - id, - std::string("Exception found when an Arg object ")+ - std::string("is improperly defined by the ") + - std::string("developer." )) - { } - -}; - -class ExitException { -public: - ExitException(int estat) : _estat(estat) {} - - int getExitStatus() const { return _estat; } - -private: - int _estat; -}; - -} // namespace TCLAP - -#endif - diff --git a/src/nmodl/ext/tclap/ArgTraits.h b/src/nmodl/ext/tclap/ArgTraits.h deleted file mode 100755 index 0b2c18f70c..0000000000 --- a/src/nmodl/ext/tclap/ArgTraits.h +++ /dev/null @@ -1,87 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: ArgTraits.h - * - * Copyright (c) 2007, Daniel Aarno, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -// This is an internal tclap file, you should probably not have to -// include this directly - -#ifndef TCLAP_ARGTRAITS_H -#define TCLAP_ARGTRAITS_H - -namespace TCLAP { - -// We use two empty structs to get compile type specialization -// function to work - -/** - * A value like argument value type is a value that can be set using - * operator>>. This is the default value type. - */ -struct ValueLike { - typedef ValueLike ValueCategory; - virtual ~ValueLike() {} -}; - -/** - * A string like argument value type is a value that can be set using - * operator=(string). Usefull if the value type contains spaces which - * will be broken up into individual tokens by operator>>. - */ -struct StringLike { - virtual ~StringLike() {} -}; - -/** - * A class can inherit from this object to make it have string like - * traits. This is a compile time thing and does not add any overhead - * to the inherenting class. - */ -struct StringLikeTrait { - typedef StringLike ValueCategory; - virtual ~StringLikeTrait() {} -}; - -/** - * A class can inherit from this object to make it have value like - * traits. This is a compile time thing and does not add any overhead - * to the inherenting class. - */ -struct ValueLikeTrait { - typedef ValueLike ValueCategory; - virtual ~ValueLikeTrait() {} -}; - -/** - * Arg traits are used to get compile type specialization when parsing - * argument values. Using an ArgTraits you can specify the way that - * values gets assigned to any particular type during parsing. The two - * supported types are StringLike and ValueLike. - */ -template<typename T> -struct ArgTraits { - typedef typename T::ValueCategory ValueCategory; - virtual ~ArgTraits() {} - //typedef ValueLike ValueCategory; -}; - -#endif - -} // namespace diff --git a/src/nmodl/ext/tclap/CmdLine.h b/src/nmodl/ext/tclap/CmdLine.h deleted file mode 100755 index aabe3a28e4..0000000000 --- a/src/nmodl/ext/tclap/CmdLine.h +++ /dev/null @@ -1,651 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: CmdLine.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_CMDLINE_H -#define TCLAP_CMDLINE_H - -#include <tclap/SwitchArg.h> -#include <tclap/MultiSwitchArg.h> -#include <tclap/UnlabeledValueArg.h> -#include <tclap/UnlabeledMultiArg.h> - -#include <tclap/XorHandler.h> -#include <tclap/HelpVisitor.h> -#include <tclap/VersionVisitor.h> -#include <tclap/IgnoreRestVisitor.h> - -#include <tclap/CmdLineOutput.h> -#include <tclap/StdOutput.h> - -#include <tclap/Constraint.h> -#include <tclap/ValuesConstraint.h> - -#include <string> -#include <vector> -#include <list> -#include <iostream> -#include <iomanip> -#include <algorithm> -#include <stdlib.h> // Needed for exit(), which isn't defined in some envs. - -namespace TCLAP { - -template<typename T> void DelPtr(T ptr) -{ - delete ptr; -} - -template<typename C> void ClearContainer(C &c) -{ - typedef typename C::value_type value_type; - std::for_each(c.begin(), c.end(), DelPtr<value_type>); - c.clear(); -} - - -/** - * The base class that manages the command line definition and passes - * along the parsing to the appropriate Arg classes. - */ -class CmdLine : public CmdLineInterface -{ - protected: - - /** - * The list of arguments that will be tested against the - * command line. - */ - std::list<Arg*> _argList; - - /** - * The name of the program. Set to argv[0]. - */ - std::string _progName; - - /** - * A message used to describe the program. Used in the usage output. - */ - std::string _message; - - /** - * The version to be displayed with the --version switch. - */ - std::string _version; - - /** - * The number of arguments that are required to be present on - * the command line. This is set dynamically, based on the - * Args added to the CmdLine object. - */ - int _numRequired; - - /** - * The character that is used to separate the argument flag/name - * from the value. Defaults to ' ' (space). - */ - char _delimiter; - - /** - * The handler that manages xoring lists of args. - */ - XorHandler _xorHandler; - - /** - * A list of Args to be explicitly deleted when the destructor - * is called. At the moment, this only includes the three default - * Args. - */ - std::list<Arg*> _argDeleteOnExitList; - - /** - * A list of Visitors to be explicitly deleted when the destructor - * is called. At the moment, these are the Vistors created for the - * default Args. - */ - std::list<Visitor*> _visitorDeleteOnExitList; - - /** - * Object that handles all output for the CmdLine. - */ - CmdLineOutput* _output; - - /** - * Should CmdLine handle parsing exceptions internally? - */ - bool _handleExceptions; - - /** - * Throws an exception listing the missing args. - */ - void missingArgsException(); - - /** - * Checks whether a name/flag string matches entirely matches - * the Arg::blankChar. Used when multiple switches are combined - * into a single argument. - * \param s - The message to be used in the usage. - */ - bool _emptyCombined(const std::string& s); - - /** - * Perform a delete ptr; operation on ptr when this object is deleted. - */ - void deleteOnExit(Arg* ptr); - - /** - * Perform a delete ptr; operation on ptr when this object is deleted. - */ - void deleteOnExit(Visitor* ptr); - -private: - - /** - * Prevent accidental copying. - */ - CmdLine(const CmdLine& rhs); - CmdLine& operator=(const CmdLine& rhs); - - /** - * Encapsulates the code common to the constructors - * (which is all of it). - */ - void _constructor(); - - - /** - * Is set to true when a user sets the output object. We use this so - * that we don't delete objects that are created outside of this lib. - */ - bool _userSetOutput; - - /** - * Whether or not to automatically create help and version switches. - */ - bool _helpAndVersion; - - /** - * Whether or not to ignore unmatched args. - */ - bool _ignoreUnmatched; - - public: - - /** - * Command line constructor. Defines how the arguments will be - * parsed. - * \param message - The message to be used in the usage - * output. - * \param delimiter - The character that is used to separate - * the argument flag/name from the value. Defaults to ' ' (space). - * \param version - The version number to be used in the - * --version switch. - * \param helpAndVersion - Whether or not to create the Help and - * Version switches. Defaults to true. - */ - CmdLine(const std::string& message, - const char delimiter = ' ', - const std::string& version = "none", - bool helpAndVersion = true); - - /** - * Deletes any resources allocated by a CmdLine object. - */ - virtual ~CmdLine(); - - /** - * Adds an argument to the list of arguments to be parsed. - * \param a - Argument to be added. - */ - void add( Arg& a ); - - /** - * An alternative add. Functionally identical. - * \param a - Argument to be added. - */ - void add( Arg* a ); - - /** - * Add two Args that will be xor'd. If this method is used, add does - * not need to be called. - * \param a - Argument to be added and xor'd. - * \param b - Argument to be added and xor'd. - */ - void xorAdd( Arg& a, Arg& b ); - - /** - * Add a list of Args that will be xor'd. If this method is used, - * add does not need to be called. - * \param xors - List of Args to be added and xor'd. - */ - void xorAdd( std::vector<Arg*>& xors ); - - /** - * Parses the command line. - * \param argc - Number of arguments. - * \param argv - Array of arguments. - */ - void parse(int argc, const char * const * argv); - - /** - * Parses the command line. - * \param args - A vector of strings representing the args. - * args[0] is still the program name. - */ - void parse(std::vector<std::string>& args); - - /** - * - */ - CmdLineOutput* getOutput(); - - /** - * - */ - void setOutput(CmdLineOutput* co); - - /** - * - */ - std::string& getVersion(); - - /** - * - */ - std::string& getProgramName(); - - /** - * - */ - std::list<Arg*>& getArgList(); - - /** - * - */ - XorHandler& getXorHandler(); - - /** - * - */ - char getDelimiter(); - - /** - * - */ - std::string& getMessage(); - - /** - * - */ - bool hasHelpAndVersion(); - - /** - * Disables or enables CmdLine's internal parsing exception handling. - * - * @param state Should CmdLine handle parsing exceptions internally? - */ - void setExceptionHandling(const bool state); - - /** - * Returns the current state of the internal exception handling. - * - * @retval true Parsing exceptions are handled internally. - * @retval false Parsing exceptions are propagated to the caller. - */ - bool getExceptionHandling() const; - - /** - * Allows the CmdLine object to be reused. - */ - void reset(); - - /** - * Allows unmatched args to be ignored. By default false. - * - * @param ignore If true the cmdline will ignore any unmatched args - * and if false it will behave as normal. - */ - void ignoreUnmatched(const bool ignore); -}; - - -/////////////////////////////////////////////////////////////////////////////// -//Begin CmdLine.cpp -/////////////////////////////////////////////////////////////////////////////// - -inline CmdLine::CmdLine(const std::string& m, - char delim, - const std::string& v, - bool help ) - : - _argList(std::list<Arg*>()), - _progName("not_set_yet"), - _message(m), - _version(v), - _numRequired(0), - _delimiter(delim), - _xorHandler(XorHandler()), - _argDeleteOnExitList(std::list<Arg*>()), - _visitorDeleteOnExitList(std::list<Visitor*>()), - _output(0), - _handleExceptions(true), - _userSetOutput(false), - _helpAndVersion(help), - _ignoreUnmatched(false) -{ - _constructor(); -} - -inline CmdLine::~CmdLine() -{ - ClearContainer(_argDeleteOnExitList); - ClearContainer(_visitorDeleteOnExitList); - - if ( !_userSetOutput ) { - delete _output; - _output = 0; - } -} - -inline void CmdLine::_constructor() -{ - _output = new StdOutput; - - Arg::setDelimiter( _delimiter ); - - Visitor* v; - - if ( _helpAndVersion ) - { - v = new HelpVisitor( this, &_output ); - SwitchArg* help = new SwitchArg("h","help", - "Displays usage information and exits.", - false, v); - add( help ); - deleteOnExit(help); - deleteOnExit(v); - - v = new VersionVisitor( this, &_output ); - SwitchArg* vers = new SwitchArg("","version", - "Displays version information and exits.", - false, v); - add( vers ); - deleteOnExit(vers); - deleteOnExit(v); - } - - v = new IgnoreRestVisitor(); - SwitchArg* ignore = new SwitchArg(Arg::flagStartString(), - Arg::ignoreNameString(), - "Ignores the rest of the labeled arguments following this flag.", - false, v); - add( ignore ); - deleteOnExit(ignore); - deleteOnExit(v); -} - -inline void CmdLine::xorAdd( std::vector<Arg*>& ors ) -{ - _xorHandler.add( ors ); - - for (ArgVectorIterator it = ors.begin(); it != ors.end(); it++) - { - (*it)->forceRequired(); - (*it)->setRequireLabel( "OR required" ); - add( *it ); - } -} - -inline void CmdLine::xorAdd( Arg& a, Arg& b ) -{ - std::vector<Arg*> ors; - ors.push_back( &a ); - ors.push_back( &b ); - xorAdd( ors ); -} - -inline void CmdLine::add( Arg& a ) -{ - add( &a ); -} - -inline void CmdLine::add( Arg* a ) -{ - for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ ) - if ( *a == *(*it) ) - throw( SpecificationException( - "Argument with same flag/name already exists!", - a->longID() ) ); - - a->addToList( _argList ); - - if ( a->isRequired() ) - _numRequired++; -} - - -inline void CmdLine::parse(int argc, const char * const * argv) -{ - // this step is necessary so that we have easy access to - // mutable strings. - std::vector<std::string> args; - for (int i = 0; i < argc; i++) - args.push_back(argv[i]); - - parse(args); -} - -inline void CmdLine::parse(std::vector<std::string>& args) -{ - bool shouldExit = false; - int estat = 0; - - try { - _progName = args.front(); - args.erase(args.begin()); - - int requiredCount = 0; - - for (int i = 0; static_cast<unsigned int>(i) < args.size(); i++) - { - bool matched = false; - for (ArgListIterator it = _argList.begin(); - it != _argList.end(); it++) { - if ( (*it)->processArg( &i, args ) ) - { - requiredCount += _xorHandler.check( *it ); - matched = true; - break; - } - } - - // checks to see if the argument is an empty combined - // switch and if so, then we've actually matched it - if ( !matched && _emptyCombined( args[i] ) ) - matched = true; - - if ( !matched && !Arg::ignoreRest() && !_ignoreUnmatched) - throw(CmdLineParseException("Couldn't find match " - "for argument", - args[i])); - } - - if ( requiredCount < _numRequired ) - missingArgsException(); - - if ( requiredCount > _numRequired ) - throw(CmdLineParseException("Too many arguments!")); - - } catch ( ArgException& e ) { - // If we're not handling the exceptions, rethrow. - if ( !_handleExceptions) { - throw; - } - - try { - _output->failure(*this,e); - } catch ( ExitException &ee ) { - estat = ee.getExitStatus(); - shouldExit = true; - } - } catch (ExitException &ee) { - // If we're not handling the exceptions, rethrow. - if ( !_handleExceptions) { - throw; - } - - estat = ee.getExitStatus(); - shouldExit = true; - } - - if (shouldExit) - exit(estat); -} - -inline bool CmdLine::_emptyCombined(const std::string& s) -{ - if ( s.length() > 0 && s[0] != Arg::flagStartChar() ) - return false; - - for ( int i = 1; static_cast<unsigned int>(i) < s.length(); i++ ) - if ( s[i] != Arg::blankChar() ) - return false; - - return true; -} - -inline void CmdLine::missingArgsException() -{ - int count = 0; - - std::string missingArgList; - for (ArgListIterator it = _argList.begin(); it != _argList.end(); it++) - { - if ( (*it)->isRequired() && !(*it)->isSet() ) - { - missingArgList += (*it)->getName(); - missingArgList += ", "; - count++; - } - } - missingArgList = missingArgList.substr(0,missingArgList.length()-2); - - std::string msg; - if ( count > 1 ) - msg = "Required arguments missing: "; - else - msg = "Required argument missing: "; - - msg += missingArgList; - - throw(CmdLineParseException(msg)); -} - -inline void CmdLine::deleteOnExit(Arg* ptr) -{ - _argDeleteOnExitList.push_back(ptr); -} - -inline void CmdLine::deleteOnExit(Visitor* ptr) -{ - _visitorDeleteOnExitList.push_back(ptr); -} - -inline CmdLineOutput* CmdLine::getOutput() -{ - return _output; -} - -inline void CmdLine::setOutput(CmdLineOutput* co) -{ - if ( !_userSetOutput ) - delete _output; - _userSetOutput = true; - _output = co; -} - -inline std::string& CmdLine::getVersion() -{ - return _version; -} - -inline std::string& CmdLine::getProgramName() -{ - return _progName; -} - -inline std::list<Arg*>& CmdLine::getArgList() -{ - return _argList; -} - -inline XorHandler& CmdLine::getXorHandler() -{ - return _xorHandler; -} - -inline char CmdLine::getDelimiter() -{ - return _delimiter; -} - -inline std::string& CmdLine::getMessage() -{ - return _message; -} - -inline bool CmdLine::hasHelpAndVersion() -{ - return _helpAndVersion; -} - -inline void CmdLine::setExceptionHandling(const bool state) -{ - _handleExceptions = state; -} - -inline bool CmdLine::getExceptionHandling() const -{ - return _handleExceptions; -} - -inline void CmdLine::reset() -{ - for( ArgListIterator it = _argList.begin(); it != _argList.end(); it++ ) - (*it)->reset(); - - _progName.clear(); -} - -inline void CmdLine::ignoreUnmatched(const bool ignore) -{ - _ignoreUnmatched = ignore; -} - -/////////////////////////////////////////////////////////////////////////////// -//End CmdLine.cpp -/////////////////////////////////////////////////////////////////////////////// - - - -} //namespace TCLAP -#endif diff --git a/src/nmodl/ext/tclap/CmdLineInterface.h b/src/nmodl/ext/tclap/CmdLineInterface.h deleted file mode 100755 index 1b25e9b8c9..0000000000 --- a/src/nmodl/ext/tclap/CmdLineInterface.h +++ /dev/null @@ -1,150 +0,0 @@ - -/****************************************************************************** - * - * file: CmdLineInterface.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_COMMANDLINE_INTERFACE_H -#define TCLAP_COMMANDLINE_INTERFACE_H - -#include <string> -#include <vector> -#include <list> -#include <iostream> -#include <algorithm> - - -namespace TCLAP { - -class Arg; -class CmdLineOutput; -class XorHandler; - -/** - * The base class that manages the command line definition and passes - * along the parsing to the appropriate Arg classes. - */ -class CmdLineInterface -{ - public: - - /** - * Destructor - */ - virtual ~CmdLineInterface() {} - - /** - * Adds an argument to the list of arguments to be parsed. - * \param a - Argument to be added. - */ - virtual void add( Arg& a )=0; - - /** - * An alternative add. Functionally identical. - * \param a - Argument to be added. - */ - virtual void add( Arg* a )=0; - - /** - * Add two Args that will be xor'd. - * If this method is used, add does - * not need to be called. - * \param a - Argument to be added and xor'd. - * \param b - Argument to be added and xor'd. - */ - virtual void xorAdd( Arg& a, Arg& b )=0; - - /** - * Add a list of Args that will be xor'd. If this method is used, - * add does not need to be called. - * \param xors - List of Args to be added and xor'd. - */ - virtual void xorAdd( std::vector<Arg*>& xors )=0; - - /** - * Parses the command line. - * \param argc - Number of arguments. - * \param argv - Array of arguments. - */ - virtual void parse(int argc, const char * const * argv)=0; - - /** - * Parses the command line. - * \param args - A vector of strings representing the args. - * args[0] is still the program name. - */ - void parse(std::vector<std::string>& args); - - /** - * Returns the CmdLineOutput object. - */ - virtual CmdLineOutput* getOutput()=0; - - /** - * \param co - CmdLineOutput object that we want to use instead. - */ - virtual void setOutput(CmdLineOutput* co)=0; - - /** - * Returns the version string. - */ - virtual std::string& getVersion()=0; - - /** - * Returns the program name string. - */ - virtual std::string& getProgramName()=0; - - /** - * Returns the argList. - */ - virtual std::list<Arg*>& getArgList()=0; - - /** - * Returns the XorHandler. - */ - virtual XorHandler& getXorHandler()=0; - - /** - * Returns the delimiter string. - */ - virtual char getDelimiter()=0; - - /** - * Returns the message string. - */ - virtual std::string& getMessage()=0; - - /** - * Indicates whether or not the help and version switches were created - * automatically. - */ - virtual bool hasHelpAndVersion()=0; - - /** - * Resets the instance as if it had just been constructed so that the - * instance can be reused. - */ - virtual void reset()=0; -}; - -} //namespace - - -#endif diff --git a/src/nmodl/ext/tclap/CmdLineOutput.h b/src/nmodl/ext/tclap/CmdLineOutput.h deleted file mode 100755 index 71ee5a3b41..0000000000 --- a/src/nmodl/ext/tclap/CmdLineOutput.h +++ /dev/null @@ -1,74 +0,0 @@ - - -/****************************************************************************** - * - * file: CmdLineOutput.h - * - * Copyright (c) 2004, Michael E. Smoot - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_CMDLINEOUTPUT_H -#define TCLAP_CMDLINEOUTPUT_H - -#include <string> -#include <vector> -#include <list> -#include <iostream> -#include <iomanip> -#include <algorithm> - -namespace TCLAP { - -class CmdLineInterface; -class ArgException; - -/** - * The interface that any output object must implement. - */ -class CmdLineOutput -{ - - public: - - /** - * Virtual destructor. - */ - virtual ~CmdLineOutput() {} - - /** - * Generates some sort of output for the USAGE. - * \param c - The CmdLine object the output is generated for. - */ - virtual void usage(CmdLineInterface& c)=0; - - /** - * Generates some sort of output for the version. - * \param c - The CmdLine object the output is generated for. - */ - virtual void version(CmdLineInterface& c)=0; - - /** - * Generates some sort of output for a failure. - * \param c - The CmdLine object the output is generated for. - * \param e - The ArgException that caused the failure. - */ - virtual void failure( CmdLineInterface& c, - ArgException& e )=0; - -}; - -} //namespace TCLAP -#endif diff --git a/src/nmodl/ext/tclap/Constraint.h b/src/nmodl/ext/tclap/Constraint.h deleted file mode 100755 index a92acf9a9a..0000000000 --- a/src/nmodl/ext/tclap/Constraint.h +++ /dev/null @@ -1,68 +0,0 @@ - -/****************************************************************************** - * - * file: Constraint.h - * - * Copyright (c) 2005, Michael E. Smoot - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_CONSTRAINT_H -#define TCLAP_CONSTRAINT_H - -#include <string> -#include <vector> -#include <list> -#include <iostream> -#include <iomanip> -#include <algorithm> - -namespace TCLAP { - -/** - * The interface that defines the interaction between the Arg and Constraint. - */ -template<class T> -class Constraint -{ - - public: - /** - * Returns a description of the Constraint. - */ - virtual std::string description() const =0; - - /** - * Returns the short ID for the Constraint. - */ - virtual std::string shortID() const =0; - - /** - * The method used to verify that the value parsed from the command - * line meets the constraint. - * \param value - The value that will be checked. - */ - virtual bool check(const T& value) const =0; - - /** - * Destructor. - * Silences warnings about Constraint being a base class with virtual - * functions but without a virtual destructor. - */ - virtual ~Constraint() { ; } -}; - -} //namespace TCLAP -#endif diff --git a/src/nmodl/ext/tclap/DocBookOutput.h b/src/nmodl/ext/tclap/DocBookOutput.h deleted file mode 100755 index a42ca274df..0000000000 --- a/src/nmodl/ext/tclap/DocBookOutput.h +++ /dev/null @@ -1,299 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: DocBookOutput.h - * - * Copyright (c) 2004, Michael E. Smoot - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_DOCBOOKOUTPUT_H -#define TCLAP_DOCBOOKOUTPUT_H - -#include <string> -#include <vector> -#include <list> -#include <iostream> -#include <algorithm> - -#include <tclap/CmdLineInterface.h> -#include <tclap/CmdLineOutput.h> -#include <tclap/XorHandler.h> -#include <tclap/Arg.h> - -namespace TCLAP { - -/** - * A class that generates DocBook output for usage() method for the - * given CmdLine and its Args. - */ -class DocBookOutput : public CmdLineOutput -{ - - public: - - /** - * Prints the usage to stdout. Can be overridden to - * produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - */ - virtual void usage(CmdLineInterface& c); - - /** - * Prints the version to stdout. Can be overridden - * to produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - */ - virtual void version(CmdLineInterface& c); - - /** - * Prints (to stderr) an error message, short usage - * Can be overridden to produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - * \param e - The ArgException that caused the failure. - */ - virtual void failure(CmdLineInterface& c, - ArgException& e ); - - protected: - - /** - * Substitutes the char r for string x in string s. - * \param s - The string to operate on. - * \param r - The char to replace. - * \param x - What to replace r with. - */ - void substituteSpecialChars( std::string& s, char r, std::string& x ); - void removeChar( std::string& s, char r); - void basename( std::string& s ); - - void printShortArg(Arg* it); - void printLongArg(Arg* it); - - char theDelimiter; -}; - - -inline void DocBookOutput::version(CmdLineInterface& _cmd) -{ - std::cout << _cmd.getVersion() << std::endl; -} - -inline void DocBookOutput::usage(CmdLineInterface& _cmd ) -{ - std::list<Arg*> argList = _cmd.getArgList(); - std::string progName = _cmd.getProgramName(); - std::string xversion = _cmd.getVersion(); - theDelimiter = _cmd.getDelimiter(); - XorHandler xorHandler = _cmd.getXorHandler(); - std::vector< std::vector<Arg*> > xorList = xorHandler.getXorList(); - basename(progName); - - std::cout << "<?xml version='1.0'?>" << std::endl; - std::cout << "<!DOCTYPE refentry PUBLIC \"-//OASIS//DTD DocBook XML V4.2//EN\"" << std::endl; - std::cout << "\t\"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd\">" << std::endl << std::endl; - - std::cout << "<refentry>" << std::endl; - - std::cout << "<refmeta>" << std::endl; - std::cout << "<refentrytitle>" << progName << "</refentrytitle>" << std::endl; - std::cout << "<manvolnum>1</manvolnum>" << std::endl; - std::cout << "</refmeta>" << std::endl; - - std::cout << "<refnamediv>" << std::endl; - std::cout << "<refname>" << progName << "</refname>" << std::endl; - std::cout << "<refpurpose>" << _cmd.getMessage() << "</refpurpose>" << std::endl; - std::cout << "</refnamediv>" << std::endl; - - std::cout << "<refsynopsisdiv>" << std::endl; - std::cout << "<cmdsynopsis>" << std::endl; - - std::cout << "<command>" << progName << "</command>" << std::endl; - - // xor - for ( int i = 0; (unsigned int)i < xorList.size(); i++ ) - { - std::cout << "<group choice='req'>" << std::endl; - for ( ArgVectorIterator it = xorList[i].begin(); - it != xorList[i].end(); it++ ) - printShortArg((*it)); - - std::cout << "</group>" << std::endl; - } - - // rest of args - for (ArgListIterator it = argList.begin(); it != argList.end(); it++) - if ( !xorHandler.contains( (*it) ) ) - printShortArg((*it)); - - std::cout << "</cmdsynopsis>" << std::endl; - std::cout << "</refsynopsisdiv>" << std::endl; - - std::cout << "<refsect1>" << std::endl; - std::cout << "<title>Description" << std::endl; - std::cout << "" << std::endl; - std::cout << _cmd.getMessage() << std::endl; - std::cout << "" << std::endl; - std::cout << "" << std::endl; - - std::cout << "" << std::endl; - std::cout << "Options" << std::endl; - - std::cout << "" << std::endl; - - for (ArgListIterator it = argList.begin(); it != argList.end(); it++) - printLongArg((*it)); - - std::cout << "" << std::endl; - std::cout << "" << std::endl; - - std::cout << "" << std::endl; - std::cout << "Version" << std::endl; - std::cout << "" << std::endl; - std::cout << xversion << std::endl; - std::cout << "" << std::endl; - std::cout << "" << std::endl; - - std::cout << "" << std::endl; - -} - -inline void DocBookOutput::failure( CmdLineInterface& _cmd, - ArgException& e ) -{ - static_cast(_cmd); // unused - std::cout << e.what() << std::endl; - throw ExitException(1); -} - -inline void DocBookOutput::substituteSpecialChars( std::string& s, - char r, - std::string& x ) -{ - size_t p; - while ( (p = s.find_first_of(r)) != std::string::npos ) - { - s.erase(p,1); - s.insert(p,x); - } -} - -inline void DocBookOutput::removeChar( std::string& s, char r) -{ - size_t p; - while ( (p = s.find_first_of(r)) != std::string::npos ) - { - s.erase(p,1); - } -} - -inline void DocBookOutput::basename( std::string& s ) -{ - size_t p = s.find_last_of('/'); - if ( p != std::string::npos ) - { - s.erase(0, p + 1); - } -} - -inline void DocBookOutput::printShortArg(Arg* a) -{ - std::string lt = "<"; - std::string gt = ">"; - - std::string id = a->shortID(); - substituteSpecialChars(id,'<',lt); - substituteSpecialChars(id,'>',gt); - removeChar(id,'['); - removeChar(id,']'); - - std::string choice = "opt"; - if ( a->isRequired() ) - choice = "plain"; - - std::cout << "acceptsMultipleValues() ) - std::cout << " rep='repeat'"; - - - std::cout << '>'; - if ( !a->getFlag().empty() ) - std::cout << a->flagStartChar() << a->getFlag(); - else - std::cout << a->nameStartString() << a->getName(); - if ( a->isValueRequired() ) - { - std::string arg = a->shortID(); - removeChar(arg,'['); - removeChar(arg,']'); - removeChar(arg,'<'); - removeChar(arg,'>'); - arg.erase(0, arg.find_last_of(theDelimiter) + 1); - std::cout << theDelimiter; - std::cout << "" << arg << ""; - } - std::cout << "" << std::endl; - -} - -inline void DocBookOutput::printLongArg(Arg* a) -{ - std::string lt = "<"; - std::string gt = ">"; - - std::string desc = a->getDescription(); - substituteSpecialChars(desc,'<',lt); - substituteSpecialChars(desc,'>',gt); - - std::cout << "" << std::endl; - - if ( !a->getFlag().empty() ) - { - std::cout << "" << std::endl; - std::cout << "" << std::endl; - std::cout << "" << std::endl; - } - - std::cout << "" << std::endl; - std::cout << "" << std::endl; - std::cout << "" << std::endl; - - std::cout << "" << std::endl; - std::cout << "" << std::endl; - std::cout << desc << std::endl; - std::cout << "" << std::endl; - std::cout << "" << std::endl; - - std::cout << "" << std::endl; -} - -} //namespace TCLAP -#endif diff --git a/src/nmodl/ext/tclap/HelpVisitor.h b/src/nmodl/ext/tclap/HelpVisitor.h deleted file mode 100755 index cc3bd070ca..0000000000 --- a/src/nmodl/ext/tclap/HelpVisitor.h +++ /dev/null @@ -1,76 +0,0 @@ - -/****************************************************************************** - * - * file: HelpVisitor.h - * - * Copyright (c) 2003, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_HELP_VISITOR_H -#define TCLAP_HELP_VISITOR_H - -#include -#include -#include - -namespace TCLAP { - -/** - * A Visitor object that calls the usage method of the given CmdLineOutput - * object for the specified CmdLine object. - */ -class HelpVisitor: public Visitor -{ - private: - /** - * Prevent accidental copying. - */ - HelpVisitor(const HelpVisitor& rhs); - HelpVisitor& operator=(const HelpVisitor& rhs); - - protected: - - /** - * The CmdLine the output will be generated for. - */ - CmdLineInterface* _cmd; - - /** - * The output object. - */ - CmdLineOutput** _out; - - public: - - /** - * Constructor. - * \param cmd - The CmdLine the output will be generated for. - * \param out - The type of output. - */ - HelpVisitor(CmdLineInterface* cmd, CmdLineOutput** out) - : Visitor(), _cmd( cmd ), _out( out ) { } - - /** - * Calls the usage method of the CmdLineOutput for the - * specified CmdLine. - */ - void visit() { (*_out)->usage(*_cmd); throw ExitException(0); } - -}; - -} - -#endif diff --git a/src/nmodl/ext/tclap/IgnoreRestVisitor.h b/src/nmodl/ext/tclap/IgnoreRestVisitor.h deleted file mode 100755 index e328649e51..0000000000 --- a/src/nmodl/ext/tclap/IgnoreRestVisitor.h +++ /dev/null @@ -1,52 +0,0 @@ - -/****************************************************************************** - * - * file: IgnoreRestVisitor.h - * - * Copyright (c) 2003, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_IGNORE_REST_VISITOR_H -#define TCLAP_IGNORE_REST_VISITOR_H - -#include -#include - -namespace TCLAP { - -/** - * A Vistor that tells the CmdLine to begin ignoring arguments after - * this one is parsed. - */ -class IgnoreRestVisitor: public Visitor -{ - public: - - /** - * Constructor. - */ - IgnoreRestVisitor() : Visitor() {} - - /** - * Sets Arg::_ignoreRest. - */ - void visit() { Arg::beginIgnoring(); } -}; - -} - -#endif diff --git a/src/nmodl/ext/tclap/MultiArg.h b/src/nmodl/ext/tclap/MultiArg.h deleted file mode 100755 index 34bb2d7895..0000000000 --- a/src/nmodl/ext/tclap/MultiArg.h +++ /dev/null @@ -1,433 +0,0 @@ -/****************************************************************************** - * - * file: MultiArg.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_MULTIPLE_ARGUMENT_H -#define TCLAP_MULTIPLE_ARGUMENT_H - -#include -#include - -#include -#include - -namespace TCLAP { -/** - * An argument that allows multiple values of type T to be specified. Very - * similar to a ValueArg, except a vector of values will be returned - * instead of just one. - */ -template -class MultiArg : public Arg -{ -public: - typedef std::vector container_type; - typedef typename container_type::iterator iterator; - typedef typename container_type::const_iterator const_iterator; - -protected: - - /** - * The list of values parsed from the CmdLine. - */ - std::vector _values; - - /** - * The description of type T to be used in the usage. - */ - std::string _typeDesc; - - /** - * A list of constraint on this Arg. - */ - Constraint* _constraint; - - /** - * Extracts the value from the string. - * Attempts to parse string as type T, if this fails an exception - * is thrown. - * \param val - The string to be read. - */ - void _extractValue( const std::string& val ); - - /** - * Used by XorHandler to decide whether to keep parsing for this arg. - */ - bool _allowMore; - -public: - - /** - * Constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - MultiArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - Visitor* v = NULL); - - /** - * Constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param parser - A CmdLine parser object to add this Arg to - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - MultiArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - CmdLineInterface& parser, - Visitor* v = NULL ); - - /** - * Constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - MultiArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - Visitor* v = NULL ); - - /** - * Constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param parser - A CmdLine parser object to add this Arg to - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - MultiArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - CmdLineInterface& parser, - Visitor* v = NULL ); - - /** - * Handles the processing of the argument. - * This re-implements the Arg version of this method to set the - * _value of the argument appropriately. It knows the difference - * between labeled and unlabeled. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. Passed from main(). - */ - virtual bool processArg(int* i, std::vector& args); - - /** - * Returns a vector of type T containing the values parsed from - * the command line. - */ - const std::vector& getValue(); - - /** - * Returns an iterator over the values parsed from the command - * line. - */ - const_iterator begin() const { return _values.begin(); } - - /** - * Returns the end of the values parsed from the command - * line. - */ - const_iterator end() const { return _values.end(); } - - /** - * Returns the a short id string. Used in the usage. - * \param val - value to be used. - */ - virtual std::string shortID(const std::string& val="val") const; - - /** - * Returns the a long id string. Used in the usage. - * \param val - value to be used. - */ - virtual std::string longID(const std::string& val="val") const; - - /** - * Once we've matched the first value, then the arg is no longer - * required. - */ - virtual bool isRequired() const; - - virtual bool allowMore(); - - virtual void reset(); - -private: - /** - * Prevent accidental copying - */ - MultiArg(const MultiArg& rhs); - MultiArg& operator=(const MultiArg& rhs); - -}; - -template -MultiArg::MultiArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - Visitor* v) : - Arg( flag, name, desc, req, true, v ), - _values(std::vector()), - _typeDesc( typeDesc ), - _constraint( NULL ), - _allowMore(false) -{ - _acceptsMultipleValues = true; -} - -template -MultiArg::MultiArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - CmdLineInterface& parser, - Visitor* v) -: Arg( flag, name, desc, req, true, v ), - _values(std::vector()), - _typeDesc( typeDesc ), - _constraint( NULL ), - _allowMore(false) -{ - parser.add( this ); - _acceptsMultipleValues = true; -} - -/** - * - */ -template -MultiArg::MultiArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - Visitor* v) -: Arg( flag, name, desc, req, true, v ), - _values(std::vector()), - _typeDesc( constraint->shortID() ), - _constraint( constraint ), - _allowMore(false) -{ - _acceptsMultipleValues = true; -} - -template -MultiArg::MultiArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - CmdLineInterface& parser, - Visitor* v) -: Arg( flag, name, desc, req, true, v ), - _values(std::vector()), - _typeDesc( constraint->shortID() ), - _constraint( constraint ), - _allowMore(false) -{ - parser.add( this ); - _acceptsMultipleValues = true; -} - -template -const std::vector& MultiArg::getValue() { return _values; } - -template -bool MultiArg::processArg(int *i, std::vector& args) -{ - if ( _ignoreable && Arg::ignoreRest() ) - return false; - - if ( _hasBlanks( args[*i] ) ) - return false; - - std::string flag = args[*i]; - std::string value = ""; - - trimFlag( flag, value ); - - if ( argMatches( flag ) ) - { - if ( Arg::delimiter() != ' ' && value == "" ) - throw( ArgParseException( - "Couldn't find delimiter for this argument!", - toString() ) ); - - // always take the first one, regardless of start string - if ( value == "" ) - { - (*i)++; - if ( static_cast(*i) < args.size() ) - _extractValue( args[*i] ); - else - throw( ArgParseException("Missing a value for this argument!", - toString() ) ); - } - else - _extractValue( value ); - - /* - // continuing taking the args until we hit one with a start string - while ( (unsigned int)(*i)+1 < args.size() && - args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 && - args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 ) - _extractValue( args[++(*i)] ); - */ - - _alreadySet = true; - _checkWithVisitor(); - - return true; - } - else - return false; -} - -/** - * - */ -template -std::string MultiArg::shortID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return Arg::shortID(_typeDesc) + " ... "; -} - -/** - * - */ -template -std::string MultiArg::longID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return Arg::longID(_typeDesc) + " (accepted multiple times)"; -} - -/** - * Once we've matched the first value, then the arg is no longer - * required. - */ -template -bool MultiArg::isRequired() const -{ - if ( _required ) - { - if ( _values.size() > 1 ) - return false; - else - return true; - } - else - return false; - -} - -template -void MultiArg::_extractValue( const std::string& val ) -{ - try { - T tmp; - ExtractValue(tmp, val, typename ArgTraits::ValueCategory()); - _values.push_back(tmp); - } catch( ArgParseException &e) { - throw ArgParseException(e.error(), toString()); - } - - if ( _constraint != NULL ) - if ( ! _constraint->check( _values.back() ) ) - throw( CmdLineParseException( "Value '" + val + - "' does not meet constraint: " + - _constraint->description(), - toString() ) ); -} - -template -bool MultiArg::allowMore() -{ - bool am = _allowMore; - _allowMore = true; - return am; -} - -template -void MultiArg::reset() -{ - Arg::reset(); - _values.clear(); -} - -} // namespace TCLAP - -#endif diff --git a/src/nmodl/ext/tclap/MultiSwitchArg.h b/src/nmodl/ext/tclap/MultiSwitchArg.h deleted file mode 100755 index 8820b64162..0000000000 --- a/src/nmodl/ext/tclap/MultiSwitchArg.h +++ /dev/null @@ -1,216 +0,0 @@ - -/****************************************************************************** -* -* file: MultiSwitchArg.h -* -* Copyright (c) 2003, Michael E. Smoot . -* Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. -* Copyright (c) 2005, Michael E. Smoot, Daniel Aarno, Erik Zeek. -* All rights reverved. -* -* See the file COPYING in the top directory of this distribution for -* more information. -* -* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS -* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -* DEALINGS IN THE SOFTWARE. -* -*****************************************************************************/ - - -#ifndef TCLAP_MULTI_SWITCH_ARG_H -#define TCLAP_MULTI_SWITCH_ARG_H - -#include -#include - -#include - -namespace TCLAP { - -/** -* A multiple switch argument. If the switch is set on the command line, then -* the getValue method will return the number of times the switch appears. -*/ -class MultiSwitchArg : public SwitchArg -{ - protected: - - /** - * The value of the switch. - */ - int _value; - - /** - * Used to support the reset() method so that ValueArg can be - * reset to their constructed value. - */ - int _default; - - public: - - /** - * MultiSwitchArg constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param init - Optional. The initial/default value of this Arg. - * Defaults to 0. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - MultiSwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - int init = 0, - Visitor* v = NULL); - - - /** - * MultiSwitchArg constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param parser - A CmdLine parser object to add this Arg to - * \param init - Optional. The initial/default value of this Arg. - * Defaults to 0. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - MultiSwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - CmdLineInterface& parser, - int init = 0, - Visitor* v = NULL); - - - /** - * Handles the processing of the argument. - * This re-implements the SwitchArg version of this method to set the - * _value of the argument appropriately. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. Passed - * in from main(). - */ - virtual bool processArg(int* i, std::vector& args); - - /** - * Returns int, the number of times the switch has been set. - */ - int getValue(); - - /** - * Returns the shortID for this Arg. - */ - std::string shortID(const std::string& val) const; - - /** - * Returns the longID for this Arg. - */ - std::string longID(const std::string& val) const; - - void reset(); - -}; - -////////////////////////////////////////////////////////////////////// -//BEGIN MultiSwitchArg.cpp -////////////////////////////////////////////////////////////////////// -inline MultiSwitchArg::MultiSwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - int init, - Visitor* v ) -: SwitchArg(flag, name, desc, false, v), -_value( init ), -_default( init ) -{ } - -inline MultiSwitchArg::MultiSwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - CmdLineInterface& parser, - int init, - Visitor* v ) -: SwitchArg(flag, name, desc, false, v), -_value( init ), -_default( init ) -{ - parser.add( this ); -} - -inline int MultiSwitchArg::getValue() { return _value; } - -inline bool MultiSwitchArg::processArg(int *i, std::vector& args) -{ - if ( _ignoreable && Arg::ignoreRest() ) - return false; - - if ( argMatches( args[*i] )) - { - // so the isSet() method will work - _alreadySet = true; - - // Matched argument: increment value. - ++_value; - - _checkWithVisitor(); - - return true; - } - else if ( combinedSwitchesMatch( args[*i] ) ) - { - // so the isSet() method will work - _alreadySet = true; - - // Matched argument: increment value. - ++_value; - - // Check for more in argument and increment value. - while ( combinedSwitchesMatch( args[*i] ) ) - ++_value; - - _checkWithVisitor(); - - return false; - } - else - return false; -} - -inline std::string -MultiSwitchArg::shortID(const std::string& val) const -{ - return Arg::shortID(val) + " ... "; -} - -inline std::string -MultiSwitchArg::longID(const std::string& val) const -{ - return Arg::longID(val) + " (accepted multiple times)"; -} - -inline void -MultiSwitchArg::reset() -{ - MultiSwitchArg::_value = MultiSwitchArg::_default; -} - -////////////////////////////////////////////////////////////////////// -//END MultiSwitchArg.cpp -////////////////////////////////////////////////////////////////////// - -} //namespace TCLAP - -#endif diff --git a/src/nmodl/ext/tclap/OptionalUnlabeledTracker.h b/src/nmodl/ext/tclap/OptionalUnlabeledTracker.h deleted file mode 100755 index 8174c5f624..0000000000 --- a/src/nmodl/ext/tclap/OptionalUnlabeledTracker.h +++ /dev/null @@ -1,62 +0,0 @@ - - -/****************************************************************************** - * - * file: OptionalUnlabeledTracker.h - * - * Copyright (c) 2005, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_OPTIONAL_UNLABELED_TRACKER_H -#define TCLAP_OPTIONAL_UNLABELED_TRACKER_H - -#include - -namespace TCLAP { - -class OptionalUnlabeledTracker -{ - - public: - - static void check( bool req, const std::string& argName ); - - static void gotOptional() { alreadyOptionalRef() = true; } - - static bool& alreadyOptional() { return alreadyOptionalRef(); } - - private: - - static bool& alreadyOptionalRef() { static bool ct = false; return ct; } -}; - - -inline void OptionalUnlabeledTracker::check( bool req, const std::string& argName ) -{ - if ( OptionalUnlabeledTracker::alreadyOptional() ) - throw( SpecificationException( - "You can't specify ANY Unlabeled Arg following an optional Unlabeled Arg", - argName ) ); - - if ( !req ) - OptionalUnlabeledTracker::gotOptional(); -} - - -} // namespace TCLAP - -#endif diff --git a/src/nmodl/ext/tclap/StandardTraits.h b/src/nmodl/ext/tclap/StandardTraits.h deleted file mode 100755 index 46d7f6fafd..0000000000 --- a/src/nmodl/ext/tclap/StandardTraits.h +++ /dev/null @@ -1,208 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: StandardTraits.h - * - * Copyright (c) 2007, Daniel Aarno, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -// This is an internal tclap file, you should probably not have to -// include this directly - -#ifndef TCLAP_STANDARD_TRAITS_H -#define TCLAP_STANDARD_TRAITS_H - -#ifdef HAVE_CONFIG_H -#include // To check for long long -#endif - -// If Microsoft has already typedef'd wchar_t as an unsigned -// short, then compiles will break because it's as if we're -// creating ArgTraits twice for unsigned short. Thus... -#ifdef _MSC_VER -#ifndef _NATIVE_WCHAR_T_DEFINED -#define TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS -#endif -#endif - -namespace TCLAP { - -// ====================================================================== -// Integer types -// ====================================================================== - -/** - * longs have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * ints have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * shorts have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * chars have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -#ifdef HAVE_LONG_LONG -/** - * long longs have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; -#endif - -// ====================================================================== -// Unsigned integer types -// ====================================================================== - -/** - * unsigned longs have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * unsigned ints have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * unsigned shorts have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * unsigned chars have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -// Microsoft implements size_t awkwardly. -#if defined(_MSC_VER) && defined(_M_X64) -/** - * size_ts have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; -#endif - - -#ifdef HAVE_LONG_LONG -/** - * unsigned long longs have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; -#endif - -// ====================================================================== -// Float types -// ====================================================================== - -/** - * floats have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -/** - * doubles have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - -// ====================================================================== -// Other types -// ====================================================================== - -/** - * bools have value-like semantics. - */ -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; - - -/** - * wchar_ts have value-like semantics. - */ -#ifndef TCLAP_DONT_DECLARE_WCHAR_T_ARGTRAITS -template<> -struct ArgTraits { - typedef ValueLike ValueCategory; -}; -#endif - -/** - * Strings have string like argument traits. - */ -template<> -struct ArgTraits { - typedef StringLike ValueCategory; -}; - -template -void SetString(T &dst, const std::string &src) -{ - dst = src; -} - -} // namespace - -#endif - diff --git a/src/nmodl/ext/tclap/StdOutput.h b/src/nmodl/ext/tclap/StdOutput.h deleted file mode 100755 index 944cff4dd1..0000000000 --- a/src/nmodl/ext/tclap/StdOutput.h +++ /dev/null @@ -1,299 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: StdOutput.h - * - * Copyright (c) 2004, Michael E. Smoot - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_STDCMDLINEOUTPUT_H -#define TCLAP_STDCMDLINEOUTPUT_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace TCLAP { - -/** - * A class that isolates any output from the CmdLine object so that it - * may be easily modified. - */ -class StdOutput : public CmdLineOutput -{ - - public: - - /** - * Prints the usage to stdout. Can be overridden to - * produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - */ - virtual void usage(CmdLineInterface& c); - - /** - * Prints the version to stdout. Can be overridden - * to produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - */ - virtual void version(CmdLineInterface& c); - - /** - * Prints (to stderr) an error message, short usage - * Can be overridden to produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - * \param e - The ArgException that caused the failure. - */ - virtual void failure(CmdLineInterface& c, - ArgException& e ); - - protected: - - /** - * Writes a brief usage message with short args. - * \param c - The CmdLine object the output is generated for. - * \param os - The stream to write the message to. - */ - void _shortUsage( CmdLineInterface& c, std::ostream& os ) const; - - /** - * Writes a longer usage message with long and short args, - * provides descriptions and prints message. - * \param c - The CmdLine object the output is generated for. - * \param os - The stream to write the message to. - */ - void _longUsage( CmdLineInterface& c, std::ostream& os ) const; - - /** - * This function inserts line breaks and indents long strings - * according the params input. It will only break lines at spaces, - * commas and pipes. - * \param os - The stream to be printed to. - * \param s - The string to be printed. - * \param maxWidth - The maxWidth allowed for the output line. - * \param indentSpaces - The number of spaces to indent the first line. - * \param secondLineOffset - The number of spaces to indent the second - * and all subsequent lines in addition to indentSpaces. - */ - void spacePrint( std::ostream& os, - const std::string& s, - int maxWidth, - int indentSpaces, - int secondLineOffset ) const; - -}; - - -inline void StdOutput::version(CmdLineInterface& _cmd) -{ - std::string progName = _cmd.getProgramName(); - std::string xversion = _cmd.getVersion(); - - std::cout << std::endl << progName << " version: " - << xversion << std::endl << std::endl; -} - -inline void StdOutput::usage(CmdLineInterface& _cmd ) -{ - std::cout << std::endl << "USAGE: " << std::endl << std::endl; - - _shortUsage( _cmd, std::cout ); - - std::cout << std::endl << std::endl << "Where: " << std::endl << std::endl; - - _longUsage( _cmd, std::cout ); - - std::cout << std::endl; - -} - -inline void StdOutput::failure( CmdLineInterface& _cmd, - ArgException& e ) -{ - std::string progName = _cmd.getProgramName(); - - std::cerr << "PARSE ERROR: " << e.argId() << std::endl - << " " << e.error() << std::endl << std::endl; - - if ( _cmd.hasHelpAndVersion() ) - { - std::cerr << "Brief USAGE: " << std::endl; - - _shortUsage( _cmd, std::cerr ); - - std::cerr << std::endl << "For complete USAGE and HELP type: " - << std::endl << " " << progName << " " - << Arg::nameStartString() << "help" - << std::endl << std::endl; - } - else - usage(_cmd); - - throw ExitException(1); -} - -inline void -StdOutput::_shortUsage( CmdLineInterface& _cmd, - std::ostream& os ) const -{ - std::list argList = _cmd.getArgList(); - std::string progName = _cmd.getProgramName(); - XorHandler xorHandler = _cmd.getXorHandler(); - std::vector< std::vector > xorList = xorHandler.getXorList(); - - std::string s = progName + " "; - - // first the xor - for ( int i = 0; static_cast(i) < xorList.size(); i++ ) - { - s += " {"; - for ( ArgVectorIterator it = xorList[i].begin(); - it != xorList[i].end(); it++ ) - s += (*it)->shortID() + "|"; - - s[s.length()-1] = '}'; - } - - // then the rest - for (ArgListIterator it = argList.begin(); it != argList.end(); it++) - if ( !xorHandler.contains( (*it) ) ) - s += " " + (*it)->shortID(); - - // if the program name is too long, then adjust the second line offset - int secondLineOffset = static_cast(progName.length()) + 2; - if ( secondLineOffset > 75/2 ) - secondLineOffset = static_cast(75/2); - - spacePrint( os, s, 75, 3, secondLineOffset ); -} - -inline void -StdOutput::_longUsage( CmdLineInterface& _cmd, - std::ostream& os ) const -{ - std::list argList = _cmd.getArgList(); - std::string message = _cmd.getMessage(); - XorHandler xorHandler = _cmd.getXorHandler(); - std::vector< std::vector > xorList = xorHandler.getXorList(); - - // first the xor - for ( int i = 0; static_cast(i) < xorList.size(); i++ ) - { - for ( ArgVectorIterator it = xorList[i].begin(); - it != xorList[i].end(); - it++ ) - { - spacePrint( os, (*it)->longID(), 75, 3, 3 ); - spacePrint( os, (*it)->getDescription(), 75, 5, 0 ); - - if ( it+1 != xorList[i].end() ) - spacePrint(os, "-- OR --", 75, 9, 0); - } - os << std::endl << std::endl; - } - - // then the rest - for (ArgListIterator it = argList.begin(); it != argList.end(); it++) - if ( !xorHandler.contains( (*it) ) ) - { - spacePrint( os, (*it)->longID(), 75, 3, 3 ); - spacePrint( os, (*it)->getDescription(), 75, 5, 0 ); - os << std::endl; - } - - os << std::endl; - - spacePrint( os, message, 75, 3, 0 ); -} - -inline void StdOutput::spacePrint( std::ostream& os, - const std::string& s, - int maxWidth, - int indentSpaces, - int secondLineOffset ) const -{ - int len = static_cast(s.length()); - - if ( (len + indentSpaces > maxWidth) && maxWidth > 0 ) - { - int allowedLen = maxWidth - indentSpaces; - int start = 0; - while ( start < len ) - { - // find the substring length - // int stringLen = std::min( len - start, allowedLen ); - // doing it this way to support a VisualC++ 2005 bug - using namespace std; - int stringLen = min( len - start, allowedLen ); - - // trim the length so it doesn't end in middle of a word - if ( stringLen == allowedLen ) - while ( stringLen >= 0 && - s[stringLen+start] != ' ' && - s[stringLen+start] != ',' && - s[stringLen+start] != '|' ) - stringLen--; - - // ok, the word is longer than the line, so just split - // wherever the line ends - if ( stringLen <= 0 ) - stringLen = allowedLen; - - // check for newlines - for ( int i = 0; i < stringLen; i++ ) - if ( s[start+i] == '\n' ) - stringLen = i+1; - - // print the indent - for ( int i = 0; i < indentSpaces; i++ ) - os << " "; - - if ( start == 0 ) - { - // handle second line offsets - indentSpaces += secondLineOffset; - - // adjust allowed len - allowedLen -= secondLineOffset; - } - - os << s.substr(start,stringLen) << std::endl; - - // so we don't start a line with a space - while ( s[stringLen+start] == ' ' && start < len ) - start++; - - start += stringLen; - } - } - else - { - for ( int i = 0; i < indentSpaces; i++ ) - os << " "; - os << s << std::endl; - } -} - -} //namespace TCLAP -#endif diff --git a/src/nmodl/ext/tclap/SwitchArg.h b/src/nmodl/ext/tclap/SwitchArg.h deleted file mode 100755 index 3916109069..0000000000 --- a/src/nmodl/ext/tclap/SwitchArg.h +++ /dev/null @@ -1,266 +0,0 @@ - -/****************************************************************************** - * - * file: SwitchArg.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_SWITCH_ARG_H -#define TCLAP_SWITCH_ARG_H - -#include -#include - -#include - -namespace TCLAP { - -/** - * A simple switch argument. If the switch is set on the command line, then - * the getValue method will return the opposite of the default value for the - * switch. - */ -class SwitchArg : public Arg -{ - protected: - - /** - * The value of the switch. - */ - bool _value; - - /** - * Used to support the reset() method so that ValueArg can be - * reset to their constructed value. - */ - bool _default; - - public: - - /** - * SwitchArg constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param def - The default value for this Switch. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - SwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool def = false, - Visitor* v = NULL); - - - /** - * SwitchArg constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param parser - A CmdLine parser object to add this Arg to - * \param def - The default value for this Switch. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - SwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - CmdLineInterface& parser, - bool def = false, - Visitor* v = NULL); - - - /** - * Handles the processing of the argument. - * This re-implements the Arg version of this method to set the - * _value of the argument appropriately. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. Passed - * in from main(). - */ - virtual bool processArg(int* i, std::vector& args); - - /** - * Checks a string to see if any of the chars in the string - * match the flag for this Switch. - */ - bool combinedSwitchesMatch(std::string& combined); - - /** - * Returns bool, whether or not the switch has been set. - */ - bool getValue(); - - virtual void reset(); - - private: - /** - * Checks to see if we've found the last match in - * a combined string. - */ - bool lastCombined(std::string& combined); - - /** - * Does the common processing of processArg. - */ - void commonProcessing(); -}; - -////////////////////////////////////////////////////////////////////// -//BEGIN SwitchArg.cpp -////////////////////////////////////////////////////////////////////// -inline SwitchArg::SwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool default_val, - Visitor* v ) -: Arg(flag, name, desc, false, false, v), - _value( default_val ), - _default( default_val ) -{ } - -inline SwitchArg::SwitchArg(const std::string& flag, - const std::string& name, - const std::string& desc, - CmdLineInterface& parser, - bool default_val, - Visitor* v ) -: Arg(flag, name, desc, false, false, v), - _value( default_val ), - _default(default_val) -{ - parser.add( this ); -} - -inline bool SwitchArg::getValue() { return _value; } - -inline bool SwitchArg::lastCombined(std::string& combinedSwitches ) -{ - for ( unsigned int i = 1; i < combinedSwitches.length(); i++ ) - if ( combinedSwitches[i] != Arg::blankChar() ) - return false; - - return true; -} - -inline bool SwitchArg::combinedSwitchesMatch(std::string& combinedSwitches ) -{ - // make sure this is actually a combined switch - if ( combinedSwitches.length() > 0 && - combinedSwitches[0] != Arg::flagStartString()[0] ) - return false; - - // make sure it isn't a long name - if ( combinedSwitches.substr( 0, Arg::nameStartString().length() ) == - Arg::nameStartString() ) - return false; - - // make sure the delimiter isn't in the string - if ( combinedSwitches.find_first_of( Arg::delimiter() ) != std::string::npos ) - return false; - - // ok, we're not specifying a ValueArg, so we know that we have - // a combined switch list. - for ( unsigned int i = 1; i < combinedSwitches.length(); i++ ) - if ( _flag.length() > 0 && - combinedSwitches[i] == _flag[0] && - _flag[0] != Arg::flagStartString()[0] ) - { - // update the combined switches so this one is no longer present - // this is necessary so that no unlabeled args are matched - // later in the processing. - //combinedSwitches.erase(i,1); - combinedSwitches[i] = Arg::blankChar(); - return true; - } - - // none of the switches passed in the list match. - return false; -} - -inline void SwitchArg::commonProcessing() -{ - if ( _xorSet ) - throw(CmdLineParseException( - "Mutually exclusive argument already set!", toString())); - - if ( _alreadySet ) - throw(CmdLineParseException("Argument already set!", toString())); - - _alreadySet = true; - - if ( _value == true ) - _value = false; - else - _value = true; - - _checkWithVisitor(); -} - -inline bool SwitchArg::processArg(int *i, std::vector& args) -{ - if ( _ignoreable && Arg::ignoreRest() ) - return false; - - // if the whole string matches the flag or name string - if ( argMatches( args[*i] ) ) - { - commonProcessing(); - - return true; - } - // if a substring matches the flag as part of a combination - else if ( combinedSwitchesMatch( args[*i] ) ) - { - // check again to ensure we don't misinterpret - // this as a MultiSwitchArg - if ( combinedSwitchesMatch( args[*i] ) ) - throw(CmdLineParseException("Argument already set!", - toString())); - - commonProcessing(); - - // We only want to return true if we've found the last combined - // match in the string, otherwise we return true so that other - // switches in the combination will have a chance to match. - return lastCombined( args[*i] ); - } - else - return false; -} - -inline void SwitchArg::reset() -{ - Arg::reset(); - _value = _default; -} -////////////////////////////////////////////////////////////////////// -//End SwitchArg.cpp -////////////////////////////////////////////////////////////////////// - -} //namespace TCLAP - -#endif diff --git a/src/nmodl/ext/tclap/UnlabeledMultiArg.h b/src/nmodl/ext/tclap/UnlabeledMultiArg.h deleted file mode 100755 index d5e1781060..0000000000 --- a/src/nmodl/ext/tclap/UnlabeledMultiArg.h +++ /dev/null @@ -1,301 +0,0 @@ - -/****************************************************************************** - * - * file: UnlabeledMultiArg.h - * - * Copyright (c) 2003, Michael E. Smoot. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_MULTIPLE_UNLABELED_ARGUMENT_H -#define TCLAP_MULTIPLE_UNLABELED_ARGUMENT_H - -#include -#include - -#include -#include - -namespace TCLAP { - -/** - * Just like a MultiArg, except that the arguments are unlabeled. Basically, - * this Arg will slurp up everything that hasn't been matched to another - * Arg. - */ -template -class UnlabeledMultiArg : public MultiArg -{ - - // If compiler has two stage name lookup (as gcc >= 3.4 does) - // this is requried to prevent undef. symbols - using MultiArg::_ignoreable; - using MultiArg::_hasBlanks; - using MultiArg::_extractValue; - using MultiArg::_typeDesc; - using MultiArg::_name; - using MultiArg::_description; - using MultiArg::_alreadySet; - using MultiArg::toString; - - public: - - /** - * Constructor. - * \param name - The name of the Arg. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param ignoreable - Whether or not this argument can be ignored - * using the "--" flag. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - UnlabeledMultiArg( const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - bool ignoreable = false, - Visitor* v = NULL ); - /** - * Constructor. - * \param name - The name of the Arg. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param parser - A CmdLine parser object to add this Arg to - * \param ignoreable - Whether or not this argument can be ignored - * using the "--" flag. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - UnlabeledMultiArg( const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - CmdLineInterface& parser, - bool ignoreable = false, - Visitor* v = NULL ); - - /** - * Constructor. - * \param name - The name of the Arg. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param ignoreable - Whether or not this argument can be ignored - * using the "--" flag. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - UnlabeledMultiArg( const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - bool ignoreable = false, - Visitor* v = NULL ); - - /** - * Constructor. - * \param name - The name of the Arg. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param parser - A CmdLine parser object to add this Arg to - * \param ignoreable - Whether or not this argument can be ignored - * using the "--" flag. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - UnlabeledMultiArg( const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - CmdLineInterface& parser, - bool ignoreable = false, - Visitor* v = NULL ); - - /** - * Handles the processing of the argument. - * This re-implements the Arg version of this method to set the - * _value of the argument appropriately. It knows the difference - * between labeled and unlabeled. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. Passed from main(). - */ - virtual bool processArg(int* i, std::vector& args); - - /** - * Returns the a short id string. Used in the usage. - * \param val - value to be used. - */ - virtual std::string shortID(const std::string& val="val") const; - - /** - * Returns the a long id string. Used in the usage. - * \param val - value to be used. - */ - virtual std::string longID(const std::string& val="val") const; - - /** - * Opertor ==. - * \param a - The Arg to be compared to this. - */ - virtual bool operator==(const Arg& a) const; - - /** - * Pushes this to back of list rather than front. - * \param argList - The list this should be added to. - */ - virtual void addToList( std::list& argList ) const; -}; - -template -UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - bool ignoreable, - Visitor* v) -: MultiArg("", name, desc, req, typeDesc, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(true, toString()); -} - -template -UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, - const std::string& desc, - bool req, - const std::string& typeDesc, - CmdLineInterface& parser, - bool ignoreable, - Visitor* v) -: MultiArg("", name, desc, req, typeDesc, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(true, toString()); - parser.add( this ); -} - - -template -UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - bool ignoreable, - Visitor* v) -: MultiArg("", name, desc, req, constraint, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(true, toString()); -} - -template -UnlabeledMultiArg::UnlabeledMultiArg(const std::string& name, - const std::string& desc, - bool req, - Constraint* constraint, - CmdLineInterface& parser, - bool ignoreable, - Visitor* v) -: MultiArg("", name, desc, req, constraint, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(true, toString()); - parser.add( this ); -} - - -template -bool UnlabeledMultiArg::processArg(int *i, std::vector& args) -{ - - if ( _hasBlanks( args[*i] ) ) - return false; - - // never ignore an unlabeled multi arg - - - // always take the first value, regardless of the start string - _extractValue( args[(*i)] ); - - /* - // continue taking args until we hit the end or a start string - while ( (unsigned int)(*i)+1 < args.size() && - args[(*i)+1].find_first_of( Arg::flagStartString() ) != 0 && - args[(*i)+1].find_first_of( Arg::nameStartString() ) != 0 ) - _extractValue( args[++(*i)] ); - */ - - _alreadySet = true; - - return true; -} - -template -std::string UnlabeledMultiArg::shortID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return std::string("<") + _typeDesc + "> ..."; -} - -template -std::string UnlabeledMultiArg::longID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return std::string("<") + _typeDesc + "> (accepted multiple times)"; -} - -template -bool UnlabeledMultiArg::operator==(const Arg& a) const -{ - if ( _name == a.getName() || _description == a.getDescription() ) - return true; - else - return false; -} - -template -void UnlabeledMultiArg::addToList( std::list& argList ) const -{ - argList.push_back( const_cast(static_cast(this)) ); -} - -} - -#endif diff --git a/src/nmodl/ext/tclap/UnlabeledValueArg.h b/src/nmodl/ext/tclap/UnlabeledValueArg.h deleted file mode 100755 index 5721d61252..0000000000 --- a/src/nmodl/ext/tclap/UnlabeledValueArg.h +++ /dev/null @@ -1,340 +0,0 @@ - -/****************************************************************************** - * - * file: UnlabeledValueArg.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_UNLABELED_VALUE_ARGUMENT_H -#define TCLAP_UNLABELED_VALUE_ARGUMENT_H - -#include -#include - -#include -#include - - -namespace TCLAP { - -/** - * The basic unlabeled argument that parses a value. - * This is a template class, which means the type T defines the type - * that a given object will attempt to parse when an UnlabeledValueArg - * is reached in the list of args that the CmdLine iterates over. - */ -template -class UnlabeledValueArg : public ValueArg -{ - - // If compiler has two stage name lookup (as gcc >= 3.4 does) - // this is requried to prevent undef. symbols - using ValueArg::_ignoreable; - using ValueArg::_hasBlanks; - using ValueArg::_extractValue; - using ValueArg::_typeDesc; - using ValueArg::_name; - using ValueArg::_description; - using ValueArg::_alreadySet; - using ValueArg::toString; - - public: - - /** - * UnlabeledValueArg constructor. - * \param name - A one word name for the argument. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param ignoreable - Allows you to specify that this argument can be - * ignored if the '--' flag is set. This defaults to false (cannot - * be ignored) and should generally stay that way unless you have - * some special need for certain arguments to be ignored. - * \param v - Optional Vistor. You should leave this blank unless - * you have a very good reason. - */ - UnlabeledValueArg( const std::string& name, - const std::string& desc, - bool req, - T value, - const std::string& typeDesc, - bool ignoreable = false, - Visitor* v = NULL); - - /** - * UnlabeledValueArg constructor. - * \param name - A one word name for the argument. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param parser - A CmdLine parser object to add this Arg to - * \param ignoreable - Allows you to specify that this argument can be - * ignored if the '--' flag is set. This defaults to false (cannot - * be ignored) and should generally stay that way unless you have - * some special need for certain arguments to be ignored. - * \param v - Optional Vistor. You should leave this blank unless - * you have a very good reason. - */ - UnlabeledValueArg( const std::string& name, - const std::string& desc, - bool req, - T value, - const std::string& typeDesc, - CmdLineInterface& parser, - bool ignoreable = false, - Visitor* v = NULL ); - - /** - * UnlabeledValueArg constructor. - * \param name - A one word name for the argument. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param ignoreable - Allows you to specify that this argument can be - * ignored if the '--' flag is set. This defaults to false (cannot - * be ignored) and should generally stay that way unless you have - * some special need for certain arguments to be ignored. - * \param v - Optional Vistor. You should leave this blank unless - * you have a very good reason. - */ - UnlabeledValueArg( const std::string& name, - const std::string& desc, - bool req, - T value, - Constraint* constraint, - bool ignoreable = false, - Visitor* v = NULL ); - - - /** - * UnlabeledValueArg constructor. - * \param name - A one word name for the argument. Note that this is used for - * identification, not as a long flag. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param parser - A CmdLine parser object to add this Arg to - * \param ignoreable - Allows you to specify that this argument can be - * ignored if the '--' flag is set. This defaults to false (cannot - * be ignored) and should generally stay that way unless you have - * some special need for certain arguments to be ignored. - * \param v - Optional Vistor. You should leave this blank unless - * you have a very good reason. - */ - UnlabeledValueArg( const std::string& name, - const std::string& desc, - bool req, - T value, - Constraint* constraint, - CmdLineInterface& parser, - bool ignoreable = false, - Visitor* v = NULL); - - /** - * Handles the processing of the argument. - * This re-implements the Arg version of this method to set the - * _value of the argument appropriately. Handling specific to - * unlabled arguments. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. - */ - virtual bool processArg(int* i, std::vector& args); - - /** - * Overrides shortID for specific behavior. - */ - virtual std::string shortID(const std::string& val="val") const; - - /** - * Overrides longID for specific behavior. - */ - virtual std::string longID(const std::string& val="val") const; - - /** - * Overrides operator== for specific behavior. - */ - virtual bool operator==(const Arg& a ) const; - - /** - * Instead of pushing to the front of list, push to the back. - * \param argList - The list to add this to. - */ - virtual void addToList( std::list& argList ) const; - -}; - -/** - * Constructor implemenation. - */ -template -UnlabeledValueArg::UnlabeledValueArg(const std::string& name, - const std::string& desc, - bool req, - T val, - const std::string& typeDesc, - bool ignoreable, - Visitor* v) -: ValueArg("", name, desc, req, val, typeDesc, v) -{ - _ignoreable = ignoreable; - - OptionalUnlabeledTracker::check(req, toString()); - -} - -template -UnlabeledValueArg::UnlabeledValueArg(const std::string& name, - const std::string& desc, - bool req, - T val, - const std::string& typeDesc, - CmdLineInterface& parser, - bool ignoreable, - Visitor* v) -: ValueArg("", name, desc, req, val, typeDesc, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(req, toString()); - parser.add( this ); -} - -/** - * Constructor implemenation. - */ -template -UnlabeledValueArg::UnlabeledValueArg(const std::string& name, - const std::string& desc, - bool req, - T val, - Constraint* constraint, - bool ignoreable, - Visitor* v) -: ValueArg("", name, desc, req, val, constraint, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(req, toString()); -} - -template -UnlabeledValueArg::UnlabeledValueArg(const std::string& name, - const std::string& desc, - bool req, - T val, - Constraint* constraint, - CmdLineInterface& parser, - bool ignoreable, - Visitor* v) -: ValueArg("", name, desc, req, val, constraint, v) -{ - _ignoreable = ignoreable; - OptionalUnlabeledTracker::check(req, toString()); - parser.add( this ); -} - -/** - * Implementation of processArg(). - */ -template -bool UnlabeledValueArg::processArg(int *i, std::vector& args) -{ - - if ( _alreadySet ) - return false; - - if ( _hasBlanks( args[*i] ) ) - return false; - - // never ignore an unlabeled arg - - _extractValue( args[*i] ); - _alreadySet = true; - return true; -} - -/** - * Overriding shortID for specific output. - */ -template -std::string UnlabeledValueArg::shortID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return std::string("<") + _typeDesc + ">"; -} - -/** - * Overriding longID for specific output. - */ -template -std::string UnlabeledValueArg::longID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - - // Ideally we would like to be able to use RTTI to return the name - // of the type required for this argument. However, g++ at least, - // doesn't appear to return terribly useful "names" of the types. - return std::string("<") + _typeDesc + ">"; -} - -/** - * Overriding operator== for specific behavior. - */ -template -bool UnlabeledValueArg::operator==(const Arg& a ) const -{ - if ( _name == a.getName() || _description == a.getDescription() ) - return true; - else - return false; -} - -template -void UnlabeledValueArg::addToList( std::list& argList ) const -{ - argList.push_back( const_cast(static_cast(this)) ); -} - -} -#endif diff --git a/src/nmodl/ext/tclap/ValueArg.h b/src/nmodl/ext/tclap/ValueArg.h deleted file mode 100755 index 7ac29526b9..0000000000 --- a/src/nmodl/ext/tclap/ValueArg.h +++ /dev/null @@ -1,425 +0,0 @@ -/****************************************************************************** - * - * file: ValueArg.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_VALUE_ARGUMENT_H -#define TCLAP_VALUE_ARGUMENT_H - -#include -#include - -#include -#include - -namespace TCLAP { - -/** - * The basic labeled argument that parses a value. - * This is a template class, which means the type T defines the type - * that a given object will attempt to parse when the flag/name is matched - * on the command line. While there is nothing stopping you from creating - * an unflagged ValueArg, it is unwise and would cause significant problems. - * Instead use an UnlabeledValueArg. - */ -template -class ValueArg : public Arg -{ - protected: - - /** - * The value parsed from the command line. - * Can be of any type, as long as the >> operator for the type - * is defined. - */ - T _value; - - /** - * Used to support the reset() method so that ValueArg can be - * reset to their constructed value. - */ - T _default; - - /** - * A human readable description of the type to be parsed. - * This is a hack, plain and simple. Ideally we would use RTTI to - * return the name of type T, but until there is some sort of - * consistent support for human readable names, we are left to our - * own devices. - */ - std::string _typeDesc; - - /** - * A Constraint this Arg must conform to. - */ - Constraint* _constraint; - - /** - * Extracts the value from the string. - * Attempts to parse string as type T, if this fails an exception - * is thrown. - * \param val - value to be parsed. - */ - void _extractValue( const std::string& val ); - - public: - - /** - * Labeled ValueArg constructor. - * You could conceivably call this constructor with a blank flag, - * but that would make you a bad person. It would also cause - * an exception to be thrown. If you want an unlabeled argument, - * use the other constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - ValueArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T value, - const std::string& typeDesc, - Visitor* v = NULL); - - - /** - * Labeled ValueArg constructor. - * You could conceivably call this constructor with a blank flag, - * but that would make you a bad person. It would also cause - * an exception to be thrown. If you want an unlabeled argument, - * use the other constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param typeDesc - A short, human readable description of the - * type that this object expects. This is used in the generation - * of the USAGE statement. The goal is to be helpful to the end user - * of the program. - * \param parser - A CmdLine parser object to add this Arg to - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - ValueArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T value, - const std::string& typeDesc, - CmdLineInterface& parser, - Visitor* v = NULL ); - - /** - * Labeled ValueArg constructor. - * You could conceivably call this constructor with a blank flag, - * but that would make you a bad person. It would also cause - * an exception to be thrown. If you want an unlabeled argument, - * use the other constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param parser - A CmdLine parser object to add this Arg to. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - ValueArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T value, - Constraint* constraint, - CmdLineInterface& parser, - Visitor* v = NULL ); - - /** - * Labeled ValueArg constructor. - * You could conceivably call this constructor with a blank flag, - * but that would make you a bad person. It would also cause - * an exception to be thrown. If you want an unlabeled argument, - * use the other constructor. - * \param flag - The one character flag that identifies this - * argument on the command line. - * \param name - A one word name for the argument. Can be - * used as a long flag on the command line. - * \param desc - A description of what the argument is for or - * does. - * \param req - Whether the argument is required on the command - * line. - * \param value - The default value assigned to this argument if it - * is not present on the command line. - * \param constraint - A pointer to a Constraint object used - * to constrain this Arg. - * \param v - An optional visitor. You probably should not - * use this unless you have a very good reason. - */ - ValueArg( const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T value, - Constraint* constraint, - Visitor* v = NULL ); - - /** - * Handles the processing of the argument. - * This re-implements the Arg version of this method to set the - * _value of the argument appropriately. It knows the difference - * between labeled and unlabeled. - * \param i - Pointer the the current argument in the list. - * \param args - Mutable list of strings. Passed - * in from main(). - */ - virtual bool processArg(int* i, std::vector& args); - - /** - * Returns the value of the argument. - */ - T& getValue() ; - - /** - * Specialization of shortID. - * \param val - value to be used. - */ - virtual std::string shortID(const std::string& val = "val") const; - - /** - * Specialization of longID. - * \param val - value to be used. - */ - virtual std::string longID(const std::string& val = "val") const; - - virtual void reset() ; - -private: - /** - * Prevent accidental copying - */ - ValueArg(const ValueArg& rhs); - ValueArg& operator=(const ValueArg& rhs); -}; - - -/** - * Constructor implementation. - */ -template -ValueArg::ValueArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T val, - const std::string& typeDesc, - Visitor* v) -: Arg(flag, name, desc, req, true, v), - _value( val ), - _default( val ), - _typeDesc( typeDesc ), - _constraint( NULL ) -{ } - -template -ValueArg::ValueArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T val, - const std::string& typeDesc, - CmdLineInterface& parser, - Visitor* v) -: Arg(flag, name, desc, req, true, v), - _value( val ), - _default( val ), - _typeDesc( typeDesc ), - _constraint( NULL ) -{ - parser.add( this ); -} - -template -ValueArg::ValueArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T val, - Constraint* constraint, - Visitor* v) -: Arg(flag, name, desc, req, true, v), - _value( val ), - _default( val ), - _typeDesc( constraint->shortID() ), - _constraint( constraint ) -{ } - -template -ValueArg::ValueArg(const std::string& flag, - const std::string& name, - const std::string& desc, - bool req, - T val, - Constraint* constraint, - CmdLineInterface& parser, - Visitor* v) -: Arg(flag, name, desc, req, true, v), - _value( val ), - _default( val ), - _typeDesc( constraint->shortID() ), - _constraint( constraint ) -{ - parser.add( this ); -} - - -/** - * Implementation of getValue(). - */ -template -T& ValueArg::getValue() { return _value; } - -/** - * Implementation of processArg(). - */ -template -bool ValueArg::processArg(int *i, std::vector& args) -{ - if ( _ignoreable && Arg::ignoreRest() ) - return false; - - if ( _hasBlanks( args[*i] ) ) - return false; - - std::string flag = args[*i]; - - std::string value = ""; - trimFlag( flag, value ); - - if ( argMatches( flag ) ) - { - if ( _alreadySet ) - { - if ( _xorSet ) - throw( CmdLineParseException( - "Mutually exclusive argument already set!", - toString()) ); - else - throw( CmdLineParseException("Argument already set!", - toString()) ); - } - - if ( Arg::delimiter() != ' ' && value == "" ) - throw( ArgParseException( - "Couldn't find delimiter for this argument!", - toString() ) ); - - if ( value == "" ) - { - (*i)++; - if ( static_cast(*i) < args.size() ) - _extractValue( args[*i] ); - else - throw( ArgParseException("Missing a value for this argument!", - toString() ) ); - } - else - _extractValue( value ); - - _alreadySet = true; - _checkWithVisitor(); - return true; - } - else - return false; -} - -/** - * Implementation of shortID. - */ -template -std::string ValueArg::shortID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return Arg::shortID( _typeDesc ); -} - -/** - * Implementation of longID. - */ -template -std::string ValueArg::longID(const std::string& val) const -{ - static_cast(val); // Ignore input, don't warn - return Arg::longID( _typeDesc ); -} - -template -void ValueArg::_extractValue( const std::string& val ) -{ - try { - ExtractValue(_value, val, typename ArgTraits::ValueCategory()); - } catch( ArgParseException &e) { - throw ArgParseException(e.error(), toString()); - } - - if ( _constraint != NULL ) - if ( ! _constraint->check( _value ) ) - throw( CmdLineParseException( "Value '" + val + - + "' does not meet constraint: " - + _constraint->description(), - toString() ) ); -} - -template -void ValueArg::reset() -{ - Arg::reset(); - _value = _default; -} - -} // namespace TCLAP - -#endif diff --git a/src/nmodl/ext/tclap/ValuesConstraint.h b/src/nmodl/ext/tclap/ValuesConstraint.h deleted file mode 100755 index cb41f645e5..0000000000 --- a/src/nmodl/ext/tclap/ValuesConstraint.h +++ /dev/null @@ -1,148 +0,0 @@ - - -/****************************************************************************** - * - * file: ValuesConstraint.h - * - * Copyright (c) 2005, Michael E. Smoot - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_VALUESCONSTRAINT_H -#define TCLAP_VALUESCONSTRAINT_H - -#include -#include -#include - -#ifdef HAVE_CONFIG_H -#include -#else -#define HAVE_SSTREAM -#endif - -#if defined(HAVE_SSTREAM) -#include -#elif defined(HAVE_STRSTREAM) -#include -#else -#error "Need a stringstream (sstream or strstream) to compile!" -#endif - -namespace TCLAP { - -/** - * A Constraint that constrains the Arg to only those values specified - * in the constraint. - */ -template -class ValuesConstraint : public Constraint -{ - - public: - - /** - * Constructor. - * \param allowed - vector of allowed values. - */ - ValuesConstraint(std::vector& allowed); - - /** - * Virtual destructor. - */ - virtual ~ValuesConstraint() {} - - /** - * Returns a description of the Constraint. - */ - virtual std::string description() const; - - /** - * Returns the short ID for the Constraint. - */ - virtual std::string shortID() const; - - /** - * The method used to verify that the value parsed from the command - * line meets the constraint. - * \param value - The value that will be checked. - */ - virtual bool check(const T& value) const; - - protected: - - /** - * The list of valid values. - */ - std::vector _allowed; - - /** - * The string used to describe the allowed values of this constraint. - */ - std::string _typeDesc; - -}; - -template -ValuesConstraint::ValuesConstraint(std::vector& allowed) -: _allowed(allowed), - _typeDesc("") -{ - for ( unsigned int i = 0; i < _allowed.size(); i++ ) - { - -#if defined(HAVE_SSTREAM) - std::ostringstream os; -#elif defined(HAVE_STRSTREAM) - std::ostrstream os; -#else -#error "Need a stringstream (sstream or strstream) to compile!" -#endif - - os << _allowed[i]; - - std::string temp( os.str() ); - - if ( i > 0 ) - _typeDesc += "|"; - _typeDesc += temp; - } -} - -template -bool ValuesConstraint::check( const T& val ) const -{ - if ( std::find(_allowed.begin(),_allowed.end(),val) == _allowed.end() ) - return false; - else - return true; -} - -template -std::string ValuesConstraint::shortID() const -{ - return _typeDesc; -} - -template -std::string ValuesConstraint::description() const -{ - return _typeDesc; -} - - -} //namespace TCLAP -#endif - diff --git a/src/nmodl/ext/tclap/VersionVisitor.h b/src/nmodl/ext/tclap/VersionVisitor.h deleted file mode 100755 index c110d4fa00..0000000000 --- a/src/nmodl/ext/tclap/VersionVisitor.h +++ /dev/null @@ -1,81 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: VersionVisitor.h - * - * Copyright (c) 2003, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_VERSION_VISITOR_H -#define TCLAP_VERSION_VISITOR_H - -#include -#include -#include - -namespace TCLAP { - -/** - * A Vistor that will call the version method of the given CmdLineOutput - * for the specified CmdLine object and then exit. - */ -class VersionVisitor: public Visitor -{ - private: - /** - * Prevent accidental copying - */ - VersionVisitor(const VersionVisitor& rhs); - VersionVisitor& operator=(const VersionVisitor& rhs); - - protected: - - /** - * The CmdLine of interest. - */ - CmdLineInterface* _cmd; - - /** - * The output object. - */ - CmdLineOutput** _out; - - public: - - /** - * Constructor. - * \param cmd - The CmdLine the output is generated for. - * \param out - The type of output. - */ - VersionVisitor( CmdLineInterface* cmd, CmdLineOutput** out ) - : Visitor(), _cmd( cmd ), _out( out ) { } - - /** - * Calls the version method of the output object using the - * specified CmdLine. - */ - void visit() { - (*_out)->version(*_cmd); - throw ExitException(0); - } - -}; - -} - -#endif diff --git a/src/nmodl/ext/tclap/Visitor.h b/src/nmodl/ext/tclap/Visitor.h deleted file mode 100755 index 38ddcbdb86..0000000000 --- a/src/nmodl/ext/tclap/Visitor.h +++ /dev/null @@ -1,53 +0,0 @@ - -/****************************************************************************** - * - * file: Visitor.h - * - * Copyright (c) 2003, Michael E. Smoot . - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - - -#ifndef TCLAP_VISITOR_H -#define TCLAP_VISITOR_H - -namespace TCLAP { - -/** - * A base class that defines the interface for visitors. - */ -class Visitor -{ - public: - - /** - * Constructor. Does nothing. - */ - Visitor() { } - - /** - * Destructor. Does nothing. - */ - virtual ~Visitor() { } - - /** - * Does nothing. Should be overridden by child. - */ - virtual void visit() { } -}; - -} - -#endif diff --git a/src/nmodl/ext/tclap/XorHandler.h b/src/nmodl/ext/tclap/XorHandler.h deleted file mode 100755 index d9dfad31f6..0000000000 --- a/src/nmodl/ext/tclap/XorHandler.h +++ /dev/null @@ -1,166 +0,0 @@ - -/****************************************************************************** - * - * file: XorHandler.h - * - * Copyright (c) 2003, Michael E. Smoot . - * Copyright (c) 2004, Michael E. Smoot, Daniel Aarno. - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_XORHANDLER_H -#define TCLAP_XORHANDLER_H - -#include -#include -#include -#include -#include - -namespace TCLAP { - -/** - * This class handles lists of Arg's that are to be XOR'd on the command - * line. This is used by CmdLine and you shouldn't ever use it. - */ -class XorHandler -{ - protected: - - /** - * The list of of lists of Arg's to be or'd together. - */ - std::vector< std::vector > _orList; - - public: - - /** - * Constructor. Does nothing. - */ - XorHandler( ) : _orList(std::vector< std::vector >()) {} - - /** - * Add a list of Arg*'s that will be orred together. - * \param ors - list of Arg* that will be xor'd. - */ - void add( std::vector& ors ); - - /** - * Checks whether the specified Arg is in one of the xor lists and - * if it does match one, returns the size of the xor list that the - * Arg matched. If the Arg matches, then it also sets the rest of - * the Arg's in the list. You shouldn't use this. - * \param a - The Arg to be checked. - */ - int check( const Arg* a ); - - /** - * Returns the XOR specific short usage. - */ - std::string shortUsage(); - - /** - * Prints the XOR specific long usage. - * \param os - Stream to print to. - */ - void printLongUsage(std::ostream& os); - - /** - * Simply checks whether the Arg is contained in one of the arg - * lists. - * \param a - The Arg to be checked. - */ - bool contains( const Arg* a ); - - std::vector< std::vector >& getXorList(); - -}; - - -////////////////////////////////////////////////////////////////////// -//BEGIN XOR.cpp -////////////////////////////////////////////////////////////////////// -inline void XorHandler::add( std::vector& ors ) -{ - _orList.push_back( ors ); -} - -inline int XorHandler::check( const Arg* a ) -{ - // iterate over each XOR list - for ( int i = 0; static_cast(i) < _orList.size(); i++ ) - { - // if the XOR list contains the arg.. - ArgVectorIterator ait = std::find( _orList[i].begin(), - _orList[i].end(), a ); - if ( ait != _orList[i].end() ) - { - // first check to see if a mutually exclusive switch - // has not already been set - for ( ArgVectorIterator it = _orList[i].begin(); - it != _orList[i].end(); - it++ ) - if ( a != (*it) && (*it)->isSet() ) - throw(CmdLineParseException( - "Mutually exclusive argument already set!", - (*it)->toString())); - - // go through and set each arg that is not a - for ( ArgVectorIterator it = _orList[i].begin(); - it != _orList[i].end(); - it++ ) - if ( a != (*it) ) - (*it)->xorSet(); - - // return the number of required args that have now been set - if ( (*ait)->allowMore() ) - return 0; - else - return static_cast(_orList[i].size()); - } - } - - if ( a->isRequired() ) - return 1; - else - return 0; -} - -inline bool XorHandler::contains( const Arg* a ) -{ - for ( int i = 0; static_cast(i) < _orList.size(); i++ ) - for ( ArgVectorIterator it = _orList[i].begin(); - it != _orList[i].end(); - it++ ) - if ( a == (*it) ) - return true; - - return false; -} - -inline std::vector< std::vector >& XorHandler::getXorList() -{ - return _orList; -} - - - -////////////////////////////////////////////////////////////////////// -//END XOR.cpp -////////////////////////////////////////////////////////////////////// - -} //namespace TCLAP - -#endif diff --git a/src/nmodl/ext/tclap/ZshCompletionOutput.h b/src/nmodl/ext/tclap/ZshCompletionOutput.h deleted file mode 100755 index 0b37fc7296..0000000000 --- a/src/nmodl/ext/tclap/ZshCompletionOutput.h +++ /dev/null @@ -1,323 +0,0 @@ -// -*- Mode: c++; c-basic-offset: 4; tab-width: 4; -*- - -/****************************************************************************** - * - * file: ZshCompletionOutput.h - * - * Copyright (c) 2006, Oliver Kiddle - * All rights reverved. - * - * See the file COPYING in the top directory of this distribution for - * more information. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *****************************************************************************/ - -#ifndef TCLAP_ZSHCOMPLETIONOUTPUT_H -#define TCLAP_ZSHCOMPLETIONOUTPUT_H - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace TCLAP { - -/** - * A class that generates a Zsh completion function as output from the usage() - * method for the given CmdLine and its Args. - */ -class ZshCompletionOutput : public CmdLineOutput -{ - - public: - - ZshCompletionOutput(); - - /** - * Prints the usage to stdout. Can be overridden to - * produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - */ - virtual void usage(CmdLineInterface& c); - - /** - * Prints the version to stdout. Can be overridden - * to produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - */ - virtual void version(CmdLineInterface& c); - - /** - * Prints (to stderr) an error message, short usage - * Can be overridden to produce alternative behavior. - * \param c - The CmdLine object the output is generated for. - * \param e - The ArgException that caused the failure. - */ - virtual void failure(CmdLineInterface& c, - ArgException& e ); - - protected: - - void basename( std::string& s ); - void quoteSpecialChars( std::string& s ); - - std::string getMutexList( CmdLineInterface& _cmd, Arg* a ); - void printOption( Arg* it, std::string mutex ); - void printArg( Arg* it ); - - std::map common; - char theDelimiter; -}; - -ZshCompletionOutput::ZshCompletionOutput() -: common(std::map()), - theDelimiter('=') -{ - common["host"] = "_hosts"; - common["hostname"] = "_hosts"; - common["file"] = "_files"; - common["filename"] = "_files"; - common["user"] = "_users"; - common["username"] = "_users"; - common["directory"] = "_directories"; - common["path"] = "_directories"; - common["url"] = "_urls"; -} - -inline void ZshCompletionOutput::version(CmdLineInterface& _cmd) -{ - std::cout << _cmd.getVersion() << std::endl; -} - -inline void ZshCompletionOutput::usage(CmdLineInterface& _cmd ) -{ - std::list argList = _cmd.getArgList(); - std::string progName = _cmd.getProgramName(); - std::string xversion = _cmd.getVersion(); - theDelimiter = _cmd.getDelimiter(); - basename(progName); - - std::cout << "#compdef " << progName << std::endl << std::endl << - "# " << progName << " version " << _cmd.getVersion() << std::endl << std::endl << - "_arguments -s -S"; - - for (ArgListIterator it = argList.begin(); it != argList.end(); it++) - { - if ( (*it)->shortID().at(0) == '<' ) - printArg((*it)); - else if ( (*it)->getFlag() != "-" ) - printOption((*it), getMutexList(_cmd, *it)); - } - - std::cout << std::endl; -} - -inline void ZshCompletionOutput::failure( CmdLineInterface& _cmd, - ArgException& e ) -{ - static_cast(_cmd); // unused - std::cout << e.what() << std::endl; -} - -inline void ZshCompletionOutput::quoteSpecialChars( std::string& s ) -{ - size_t idx = s.find_last_of(':'); - while ( idx != std::string::npos ) - { - s.insert(idx, 1, '\\'); - idx = s.find_last_of(':', idx); - } - idx = s.find_last_of('\''); - while ( idx != std::string::npos ) - { - s.insert(idx, "'\\'"); - if (idx == 0) - idx = std::string::npos; - else - idx = s.find_last_of('\'', --idx); - } -} - -inline void ZshCompletionOutput::basename( std::string& s ) -{ - size_t p = s.find_last_of('/'); - if ( p != std::string::npos ) - { - s.erase(0, p + 1); - } -} - -inline void ZshCompletionOutput::printArg(Arg* a) -{ - static int count = 1; - - std::cout << " \\" << std::endl << " '"; - if ( a->acceptsMultipleValues() ) - std::cout << '*'; - else - std::cout << count++; - std::cout << ':'; - if ( !a->isRequired() ) - std::cout << ':'; - - std::cout << a->getName() << ':'; - std::map::iterator compArg = common.find(a->getName()); - if ( compArg != common.end() ) - { - std::cout << compArg->second; - } - else - { - std::cout << "_guard \"^-*\" " << a->getName(); - } - std::cout << '\''; -} - -inline void ZshCompletionOutput::printOption(Arg* a, std::string mutex) -{ - std::string flag = a->flagStartChar() + a->getFlag(); - std::string name = a->nameStartString() + a->getName(); - std::string desc = a->getDescription(); - - // remove full stop and capitalisation from description as - // this is the convention for zsh function - if (!desc.compare(0, 12, "(required) ")) - { - desc.erase(0, 12); - } - if (!desc.compare(0, 15, "(OR required) ")) - { - desc.erase(0, 15); - } - size_t len = desc.length(); - if (len && desc.at(--len) == '.') - { - desc.erase(len); - } - if (len) - { - desc.replace(0, 1, 1, tolower(desc.at(0))); - } - - std::cout << " \\" << std::endl << " '" << mutex; - - if ( a->getFlag().empty() ) - { - std::cout << name; - } - else - { - std::cout << "'{" << flag << ',' << name << "}'"; - } - if ( theDelimiter == '=' && a->isValueRequired() ) - std::cout << "=-"; - quoteSpecialChars(desc); - std::cout << '[' << desc << ']'; - - if ( a->isValueRequired() ) - { - std::string arg = a->shortID(); - arg.erase(0, arg.find_last_of(theDelimiter) + 1); - if ( arg.at(arg.length()-1) == ']' ) - arg.erase(arg.length()-1); - if ( arg.at(arg.length()-1) == ']' ) - { - arg.erase(arg.length()-1); - } - if ( arg.at(0) == '<' ) - { - arg.erase(arg.length()-1); - arg.erase(0, 1); - } - size_t p = arg.find('|'); - if ( p != std::string::npos ) - { - do - { - arg.replace(p, 1, 1, ' '); - } - while ( (p = arg.find_first_of('|', p)) != std::string::npos ); - quoteSpecialChars(arg); - std::cout << ": :(" << arg << ')'; - } - else - { - std::cout << ':' << arg; - std::map::iterator compArg = common.find(arg); - if ( compArg != common.end() ) - { - std::cout << ':' << compArg->second; - } - } - } - - std::cout << '\''; -} - -inline std::string ZshCompletionOutput::getMutexList( CmdLineInterface& _cmd, Arg* a) -{ - XorHandler xorHandler = _cmd.getXorHandler(); - std::vector< std::vector > xorList = xorHandler.getXorList(); - - if (a->getName() == "help" || a->getName() == "version") - { - return "(-)"; - } - - std::ostringstream list; - if ( a->acceptsMultipleValues() ) - { - list << '*'; - } - - for ( int i = 0; static_cast(i) < xorList.size(); i++ ) - { - for ( ArgVectorIterator it = xorList[i].begin(); - it != xorList[i].end(); - it++) - if ( a == (*it) ) - { - list << '('; - for ( ArgVectorIterator iu = xorList[i].begin(); - iu != xorList[i].end(); - iu++ ) - { - bool notCur = (*iu) != a; - bool hasFlag = !(*iu)->getFlag().empty(); - if ( iu != xorList[i].begin() && (notCur || hasFlag) ) - list << ' '; - if (hasFlag) - list << (*iu)->flagStartChar() << (*iu)->getFlag() << ' '; - if ( notCur || hasFlag ) - list << (*iu)->nameStartString() << (*iu)->getName(); - } - list << ')'; - return list.str(); - } - } - - // wasn't found in xor list - if (!a->getFlag().empty()) { - list << "(" << a->flagStartChar() << a->getFlag() << ' ' << - a->nameStartString() << a->getName() << ')'; - } - - return list.str(); -} - -} //namespace TCLAP -#endif diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 43428b953c..c4ad8919e1 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -2,7 +2,7 @@ #include -#include "catch.hpp" +#include "catch/catch.hpp" #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index efa4e13977..406eb52cd8 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -2,7 +2,7 @@ #include -#include "catch.hpp" +#include "catch/catch.hpp" #include "printer/json_printer.hpp" TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { From 9c5521b9f660a019faf2e17ccb37320b056794d9 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 20 Oct 2018 23:05:58 +0200 Subject: [PATCH 081/871] Bug fixes : - while visitor in code generation was missing - global hoc variables were not declared properly - _threadargs_ wasnt handled properly - handle diam and area variables Change-Id: Ie6217dd51e014012d6959c7d217a792663f14273 NMODL Repo SHA: BlueBrain/nmodl@49e53460bf14d1dc614db98b6cb1ea4e7032f155 --- .../codegen/base/codegen_base_visitor.cpp | 24 +++ .../codegen/base/codegen_base_visitor.hpp | 6 + .../codegen/base/codegen_helper_visitor.cpp | 15 ++ .../codegen/base/codegen_helper_visitor.hpp | 2 + src/nmodl/codegen/c/codegen_c_visitor.cpp | 145 ++++++++++++++---- src/nmodl/codegen/c/codegen_c_visitor.hpp | 24 +-- src/nmodl/codegen/codegen_info.hpp | 6 + src/nmodl/nmodl/main.cpp | 1 - src/nmodl/symtab/symbol_table.cpp | 16 +- src/nmodl/symtab/symbol_table.hpp | 6 +- src/nmodl/utils/common_utils.cpp | 2 +- src/nmodl/utils/logger.cpp | 1 - src/nmodl/utils/string_utils.hpp | 2 +- test/nmodl/transpiler/visitor/visitor.cpp | 4 +- 14 files changed, 201 insertions(+), 53 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 42ead9ef2b..d7b0ed116a 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -164,6 +164,12 @@ void CodegenBaseVisitor::visit_else_statement(ElseStatement* node) { node->visit_children(this); } +void CodegenBaseVisitor::visit_while_statement(WhileStatement* node) { + printer->add_text("while ("); + node->condition->accept(this); + printer->add_text(") "); + node->statementblock->accept(this); +} void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { if (!codegen) { @@ -562,6 +568,7 @@ void CodegenBaseVisitor::print_statement_block(ast::StatementBlock* node, void CodegenBaseVisitor::update_index_semantics() { int index = 0; info.semantics.clear(); + if (info.point_process) { info.semantics.emplace_back(index++, "area", 1); info.semantics.emplace_back(index++, "pntproc", 1); @@ -592,6 +599,15 @@ void CodegenBaseVisitor::update_index_semantics() { } index += size; } + + if (info.diam_used) { + info.semantics.emplace_back(index++, diam_variable, 1); + } + + if(info.area_used) { + info.semantics.emplace_back(index++, area_variable, 1); + } + if (info.net_send_used) { info.semantics.emplace_back(index++, "netsend", 1); } @@ -698,6 +714,14 @@ std::vector CodegenBaseVisitor::get_int_variables() { } } + if (info.diam_used) { + variables.emplace_back(make_symbol("diam")); + } + + if (info.area_used) { + variables.emplace_back(make_symbol("area")); + } + // for non-artificial cell, when net_receive buffering is enabled // then tqitem is an offset if (info.net_send_used) { diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 38ed31315a..a9382f4845 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -172,6 +172,11 @@ class CodegenBaseVisitor : public AstVisitor { /// node area variable const std::string node_area = "node_area"; + /// node diameter variable + const std::string diam_variable = "diam"; + + /// similar to node area but user can explicitly declare it + const std::string area_variable = "area"; /// nmodl language version std::string nmodl_version() { @@ -449,6 +454,7 @@ class CodegenBaseVisitor : public AstVisitor { virtual void visit_unary_operator(ast::UnaryOperator* node) override; virtual void visit_statement_block(ast::StatementBlock* node) override; virtual void visit_program(ast::Program* node) override; + virtual void visit_while_statement(ast::WhileStatement* node) override; }; diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 0301261ba8..d0d57e1bce 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -219,6 +219,21 @@ void CodegenHelperVisitor::find_non_range_variables() { | NmodlInfo::bbcore_pointer_var; // clang-format on info.pointer_variables = psymtab->get_variables_with_properties(properties); + + // find special variables like diam, area + // clang-format off + properties = NmodlInfo::dependent_def + | NmodlInfo::param_assign; + vars = psymtab->get_variables_with_properties(properties); + for (auto& var : vars) { + if (var->get_name() == area_variable) { + info.area_used = true; + } + if (var->get_name() == diam_variable) { + info.diam_used = true; + } + } + // clang-format on } /** diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp index c75e99cb3a..36176b983e 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -61,6 +61,8 @@ class CodegenHelperVisitor : public AstVisitor { const std::string net_event_method = "net_event"; const std::string artificial_cell = "ARTIFICIAL_CELL"; const std::string point_process = "POINT_PROCESS"; + const std::string diam_variable = "diam"; + const std::string area_variable = "area"; void find_solve_node(); void find_ion_variables(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index f37f03c29b..beb3c9834b 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -204,14 +204,33 @@ std::vector CodegenCVisitor::ion_write_statements(BlockType * Here we process if we are processing verbatim block at global scope. */ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { - if (printing_top_verbatim_blocks) { - std::string name = token; - if (verbatim_variables_mapping.find(token) != verbatim_variables_mapping.end()) { - name = verbatim_variables_mapping[token]; - } - return get_variable_name(name, false); + std::string name = token; + + /** + * If given token is procedure name and if it's defined + * in the current mod file then it must be replaced + */ + if (program_symtab->is_method_defined(token)) { + return method_name(token); } - return get_variable_name(token); + + /** + * Check if token is commongly used variable name in + * verbatim block like nt, _threadargs etc. If so, replace + * it and return. + */ + auto new_name = replace_if_verbatim_variable(name); + if (new_name != name) { + return get_variable_name(new_name, false); + } + + /** + * For top level verbatim blocks we shouldn't replace variable + * names with Instance because arguments are provided from coreneuron + * and they are missing inst. + */ + auto use_instance = printing_top_verbatim_blocks ? false : true; + return get_variable_name(token, use_instance); } @@ -696,26 +715,6 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { /****************************************************************************************/ - -std::string CodegenCVisitor::process_verbatim_text(std::string text) { - c11::Driver driver; - driver.scan_string(text); - auto tokens = driver.all_tokens(); - std::string result; - for (auto& token : tokens) { - auto name = process_verbatim_token(token); - if (token == "_tqitem") { - name = "&" + name; - } - if (token == "_STRIDE") { - name = (layout == LayoutType::soa) ? "pnodecount+id" : "1"; - } - result += name; - } - return result; -} - - std::string CodegenCVisitor::internal_method_arguments() { if (ion_variable_struct_required()) { return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; @@ -746,10 +745,97 @@ std::string CodegenCVisitor::external_method_parameters() { } +/** + * Function call arguments when function or procedure is external (i.e. + * not visible at nmodl level) + */ std::string CodegenCVisitor::nrn_thread_arguments() { + if (ion_variable_struct_required()) { + return "id, pnodecount, ionvar, data, indexes, thread, nt, v"; + } return "id, pnodecount, data, indexes, thread, nt, v"; } +/** + * Function call arguments when function or procedure is defined in the + * same mod file itself + */ +std::string CodegenCVisitor::nrn_thread_internal_arguments() { + if (ion_variable_struct_required()) { + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; + } + return "id, pnodecount, inst, data, indexes, thread, nt, v"; +} + + +/** + * Commonly used variables in the verbatim blocks and their corresponding + * variable name in the new code generation backend. + */ +std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { + // clang-format off + std::map verbatim_variables_map{ + {"_nt", "nt"}, + {"_p", "data"}, + {"_ppvar", "indexes"}, + {"_thread", "thread"}, + {"_iml", "id"}, + {"_cntml_padded", "pnodecount"}, + {"_cntml", "nodecount"}, + {"_tqitem", "tqitem"}}; + // clang-format on + + if (verbatim_variables_map.find(name) != verbatim_variables_map.end()) { + name = verbatim_variables_map[name]; + } + + /// if function is defined the same mod file then the arguments must + /// contain mechanism instance as well. + if (name == "_threadargs_") { + if (internal_method_call_encountered) { + name = nrn_thread_internal_arguments(); + internal_method_call_encountered = false; + } else { + name = nrn_thread_arguments(); + } + } + if (name == "_threadargsproto_") { + name = external_method_parameters(); + } + return name; +} + +/** + * Processing commonly used constructs in the verbatim blocks. + * @todo : this is still ad-hoc and requires re-implementation to + * handle it more elegantly. + */ +std::string CodegenCVisitor::process_verbatim_text(std::string text) { + c11::Driver driver; + driver.scan_string(text); + auto tokens = driver.all_tokens(); + std::string result; + for (size_t i = 0; i < tokens.size(); i++) { + auto token = tokens[i]; + + /// check if we have function call in the verbatim block where + /// function is defined in the same mod file + if (program_symtab->is_method_defined(token) && tokens[i + 1] == "(") { + internal_method_call_encountered = true; + } + auto name = process_verbatim_token(token); + + if (token == "_tqitem") { + name = "&" + name; + } + if (token == "_STRIDE") { + name = (layout == LayoutType::soa) ? "pnodecount+id" : "1"; + } + result += name; + } + return result; +} + std::string CodegenCVisitor::register_mechanism_arguments() { auto nrn_cur = nrn_cur_required() ? method_name("nrn_cur") : "NULL"; @@ -1151,6 +1237,7 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); + printer->add_line("#include "); } @@ -2664,10 +2751,12 @@ void CodegenCVisitor::codegen_all() { codegen_namespace_begin(); print_mechanism_info_array(); - print_global_variables_for_hoc(); codegen_data_structures(); + // must be after codegen_data_structures + print_global_variables_for_hoc(); + codegen_common_getters(); print_thread_memory_callbacks(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index ae7eb93133..46f302bd10 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -33,22 +33,8 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// currently printing verbatim blocks in top level block bool printing_top_verbatim_blocks = false; - /** - * Commonly used variables in the verbatim blocks and their corresponding - * variable name in the new code generation backend. - */ - std::map verbatim_variables_mapping{ - {"_nt", "nt"}, - {"_p", "data"}, - {"_ppvar", "indexes"}, - {"_thread", "thread"}, - {"_iml", "id"}, - {"_cntml_padded", "pnodecount"}, - {"_cntml", "nodecount"}, - {"_tqitem", "tqitem"}, - {"_threadargs_", nrn_thread_arguments()}, - {"_threadargsproto_", external_method_parameters()}}; - + /// internal method call was encountered while processing vebatim block + bool internal_method_call_encountered = false; /// number of threads to allocate int num_thread_objects() { @@ -160,11 +146,17 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// arguments for "_threadargs_" macro in neuron implementation std::string nrn_thread_arguments(); + /// arguments for "_threadargs_" macro in neuron implementation + std::string nrn_thread_internal_arguments(); /// list of shadow statements in the current block std::vector shadow_statements; + /// replace commonly used verbatim variables + std::string replace_if_verbatim_variable(std::string name); + + /// return name of main compute kernels virtual std::string compute_method_name(BlockType type); diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 908fd858c0..7dd0efc061 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -159,6 +159,12 @@ namespace codegen { /// if net_even function is used bool net_event_used = false; + /// if diam is used + bool diam_used = false; + + /// if area is used + bool area_used = false; + /** * thread_data_index indicates number of threads being allocated. * For example, if there is derivimplicit method used, then two thread diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 1dfe3caaa1..a212c6c21e 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -29,7 +29,6 @@ void ast_to_nmodl(ast::Program* ast, std::string filename) { } int main(int argc, const char* argv[]) { - ArgumentHandler arg(argc, argv); make_path(arg.output_dir); diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 41dcc40b12..76db542aab 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -22,7 +22,7 @@ namespace symtab { } - std::shared_ptr Table::lookup(const std::string& name) { + std::shared_ptr Table::lookup(const std::string& name) const { for (const auto& symbol : symbols) { if (symbol->get_name() == name) { return symbol; @@ -44,6 +44,18 @@ namespace symtab { return node->get_type_name(); } + bool SymbolTable::is_method_defined(const std::string& name) const { + auto symbol = lookup_in_scope(name); + if (symbol != nullptr) { + auto node = symbol->get_node(); + if (node) { + if (node->is_procedure_block() || node->is_function_block()) { + return true; + } + } + } + return false; + } std::string SymbolTable::position() const { auto token = node->get_token(); @@ -143,7 +155,7 @@ namespace symtab { /// lookup for symbol in current scope as well as all parents - std::shared_ptr SymbolTable::lookup_in_scope(const std::string& name) { + std::shared_ptr SymbolTable::lookup_in_scope(const std::string& name) const { auto symbol = table.lookup(name); if (!symbol && (parent != nullptr)) { symbol = parent->lookup_in_scope(name); diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 01572cc453..eab3b32830 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -33,7 +33,7 @@ namespace symtab { void insert(const std::shared_ptr& symbol); /// check if symbol with given name exist - std::shared_ptr lookup(const std::string& name); + std::shared_ptr lookup(const std::string& name) const; /// pretty print void print(std::stringstream& stream, std::string title, int indent); @@ -101,6 +101,8 @@ namespace symtab { std::string title(); + bool is_method_defined(const std::string& name) const; + /// todo: set token for every block from parser std::string position() const; @@ -137,7 +139,7 @@ namespace symtab { return new SymbolTable(*this); } - std::shared_ptr lookup_in_scope(const std::string& name); + std::shared_ptr lookup_in_scope(const std::string& name) const; std::vector> get_variables(SymbolInfo with, SymbolInfo without); diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index dbc38cca1e..05afc28175 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -4,7 +4,7 @@ #include bool is_dir_exist(const std::string& path) { - struct stat info{}; + struct stat info {}; if (stat(path.c_str(), &info) != 0) { return false; } diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 7761e82d5a..9747cfd92c 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -14,4 +14,3 @@ struct Logger { Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); logger_type logger = nmodl_logger.logger; - diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 5fede5f91a..47fa2b267e 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -53,7 +53,7 @@ namespace stringutils { case '"': case '\\': after += '\\'; - /// don't break here as we want to append actual character + /// don't break here as we want to append actual character default: after += c; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 7109d1ea70..e492a8fcd0 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -368,7 +368,9 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { THEN("existing variables could be renamed") { std::vector> variables = { - {"m", "mm"}, {"gNaTs2_tbar", "new_gNaTs2_tbar"}, {"mAlpha", "mBeta"}, + {"m", "mm"}, + {"gNaTs2_tbar", "new_gNaTs2_tbar"}, + {"mAlpha", "mBeta"}, }; auto result = run_var_rename_visitor(input, variables); REQUIRE(result == expected_output); From a54253f9ebc25a768c2383e3168a50ee8501a53e Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Tue, 23 Oct 2018 22:22:23 +0200 Subject: [PATCH 082/871] Bug fix : - exclude read/write ion variables being treated as global - handle code generation for net_move Change-Id: I05702041eedcc9fecbb0d8dd43b170abf694040e NMODL Repo SHA: BlueBrain/nmodl@9e66597206eba0d17b66b9becc09da0f43aefe96 --- .../codegen/base/codegen_base_visitor.hpp | 4 +++ .../codegen/base/codegen_helper_visitor.cpp | 4 ++- src/nmodl/codegen/c/codegen_c_visitor.cpp | 36 +++++++++++++++++++ src/nmodl/codegen/c/codegen_c_visitor.hpp | 4 +++ 4 files changed, 47 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index a9382f4845..7b4534dc8e 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -230,6 +230,10 @@ class CodegenBaseVisitor : public AstVisitor { return name == "net_send"; } + /// function name for net move + bool is_net_move(const std::string& name) { + return name == "net_move"; + } /// function name for net event bool is_net_event(const std::string& name) { diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index d0d57e1bce..7382a3b950 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -150,7 +150,9 @@ void CodegenHelperVisitor::find_non_range_variables() { | NmodlInfo::dependent_def | NmodlInfo::global_var | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var; + | NmodlInfo::bbcore_pointer_var + | NmodlInfo::read_ion_var + | NmodlInfo::write_ion_var; // clang-format on vars = psymtab->get_variables(with, without); for (auto& var : vars) { diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index beb3c9834b..069664cc47 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -580,18 +580,27 @@ void CodegenCVisitor::print_function_call(FunctionCall* node) { print_net_send_call(node); return; } + + if (is_net_move(name)) { + print_net_move_call(node); + return; + } + if (is_net_event(name)) { print_net_event_call(node); return; } + auto arguments = node->get_arguments(); printer->add_text("{}("_format(function_name)); + if (defined_method(name)) { printer->add_text(internal_method_arguments()); if (!arguments.empty()) { printer->add_text(", "); } } + print_vector_elements(arguments, ", "); printer->add_text(")"); } @@ -2153,6 +2162,33 @@ void CodegenCVisitor::print_net_send_call(FunctionCall* node) { } +void CodegenCVisitor::print_net_move_call(FunctionCall* node) { + if (!printing_net_receive) { + std::cout << "Error : net_move only allowed in NET_RECEIVE block" << std::endl; + abort(); + } + + auto arguments = node->get_arguments(); + auto tqitem = get_variable_name("tqitem"); + std::string weight_index = "-1"; + std::string pnt = "pnt"; + + /// artificial cells don't use spike buffering + // clang-format off + if (info.artificial_cell) { + printer->add_text("artcell_net_move(&{}, {}, {}, t+"_format(tqitem, weight_index, pnt)); + } else { + auto point_process = get_variable_name("point_process"); + printer->add_text("net_send_buffering("); + printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, t+"_format(tqitem, weight_index, point_process)); + } + // clang-format off + print_vector_elements(arguments, ", "); + printer->add_text(", 0.0"); + printer->add_text(")"); +} + + void CodegenCVisitor::print_net_event_call(FunctionCall* node) { auto arguments = node->get_arguments(); if (info.artificial_cell) { diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 46f302bd10..2c7cf5a411 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -310,6 +310,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { void print_net_send_call(ast::FunctionCall* node); + /// net_move call + void print_net_move_call(ast::FunctionCall* node); + + /// net_event call void print_net_event_call(ast::FunctionCall* node); From 485316d12b4d4434ef6d1dd6ae5ce3e8e2906d64 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 2 Nov 2018 17:06:30 +0100 Subject: [PATCH 083/871] Supoort for WATCH statements in C code generation backend. GluSynapse compile and links fine Change-Id: I4b313b676dc4a0ff6e53927843a83461117ff1ba NMODL Repo SHA: BlueBrain/nmodl@5dbf06e3aa0e49877f06669b7abf4bf4d0f974a2 --- .../codegen/base/codegen_base_visitor.cpp | 31 ++- .../codegen/base/codegen_base_visitor.hpp | 7 +- .../codegen/base/codegen_helper_visitor.cpp | 16 ++ .../codegen/base/codegen_helper_visitor.hpp | 3 + src/nmodl/codegen/c/codegen_c_visitor.cpp | 180 ++++++++++++++++-- src/nmodl/codegen/c/codegen_c_visitor.hpp | 23 +++ src/nmodl/codegen/codegen_info.hpp | 14 ++ 7 files changed, 249 insertions(+), 25 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index d7b0ed116a..46993c103c 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -311,7 +311,7 @@ bool CodegenBaseVisitor::skip_statement(Statement* node) { bool CodegenBaseVisitor::net_send_buffer_required() { if (net_receive_required() && !info.artificial_cell) { - if (info.net_event_used || info.net_send_used) { + if (info.net_event_used || info.net_send_used || info.is_watch_used()) { return true; } } @@ -604,13 +604,27 @@ void CodegenBaseVisitor::update_index_semantics() { info.semantics.emplace_back(index++, diam_variable, 1); } - if(info.area_used) { + if (info.area_used) { info.semantics.emplace_back(index++, area_variable, 1); } if (info.net_send_used) { info.semantics.emplace_back(index++, "netsend", 1); } + + /** + * Number of semantics for watch is one greater than number of + * actual watch statements in the mod file + */ + if (info.watch_statements.size() > 0) { + for (int i = 0; i < info.watch_statements.size() + 1; i++) { + info.semantics.emplace_back(index++, "watch", 1); + } + } + + if (info.for_netcon_used) { + info.semantics.emplace_back(index++, "fornetcon", 1); + } } @@ -681,6 +695,7 @@ std::vector CodegenBaseVisitor::get_int_variables() { variables.emplace_back(make_symbol("point_process"), true); } else { variables.emplace_back(make_symbol("point_process"), false, false, true); + variables.back().is_constant = true; } } @@ -729,9 +744,21 @@ std::vector CodegenBaseVisitor::get_int_variables() { variables.emplace_back(make_symbol("tqitem"), true); } else { variables.emplace_back(make_symbol("tqitem"), false, false, true); + variables.back().is_constant = true; } info.tqitem_index = variables.size() - 1; } + + /** + * Variables for watch statements : note that there is one extra variable + * used in coreneuron compared to actual watch statements for compatibility + * with neuron (which uses one extra Datum variable) + */ + if (info.watch_statements.size() > 0) { + for (int i = 0; i < info.watch_statements.size() + 1; i++) { + variables.emplace_back(make_symbol("watch{}"_format(i)), false, false, true); + } + } return variables; } diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 7b4534dc8e..0389bb2d99 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -31,7 +31,10 @@ enum class BlockType { Ode, /// derivative block - State + State, + + /// watch block + Watch }; @@ -76,7 +79,7 @@ struct IndexVariableInfo { /// is printed as array accesses bool is_integer = false; - /// if the variable is qualified as constant (this is propery of IndexVariable) + /// if the variable is qualified as constant (this is property of IndexVariable) bool is_constant = false; IndexVariableInfo(std::shared_ptr symbol, diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 7382a3b950..9c35cba5b5 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -525,6 +525,22 @@ void CodegenHelperVisitor::visit_bbcore_ptr(BbcorePtr* node) { } +void CodegenHelperVisitor::visit_watch(ast::Watch* node) { + info.watch_count++; +} + + +void CodegenHelperVisitor::visit_watch_statement(ast::WatchStatement* node) { + info.watch_statements.push_back(node); + node->visit_children(this); +} + + +void CodegenHelperVisitor::visit_for_netcon(ast::ForNetcon* node) { + info.for_netcon_used = true; +} + + void CodegenHelperVisitor::find_solve_node() { if (info.solve_node != nullptr) { return; diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp index 36176b983e..d1a8a6f1ed 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -88,6 +88,9 @@ class CodegenHelperVisitor : public AstVisitor { virtual void visit_derivative_block(ast::DerivativeBlock* node) override; virtual void visit_net_receive_block(ast::NetReceiveBlock* node) override; virtual void visit_bbcore_ptr(ast::BbcorePtr* node) override; + virtual void visit_watch(ast::Watch* node) override; + virtual void visit_watch_statement(ast::WatchStatement* node) override; + virtual void visit_for_netcon(ast::ForNetcon* node) override; virtual void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 069664cc47..06fa3fd61a 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -519,6 +519,9 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) { if (type == BlockType::Equation) { return method_name("nrn_cur"); } + if (type == BlockType::Watch) { + return method_name("nrn_watch_check"); + } throw std::logic_error("compute_method_name not implemented"); } @@ -569,6 +572,12 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, } +void CodegenCVisitor::visit_watch_statement(ast::WatchStatement* node) { + printer->add_text( + "nrn_watch_activate(inst, id, pnodecount, {})"_format(current_watch_statement++)); +} + + void CodegenCVisitor::print_function_call(FunctionCall* node) { auto name = node->get_function_name()->get_name(); auto function_name = name; @@ -1247,6 +1256,8 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include <_kinderiv.h>"); } @@ -1666,7 +1677,8 @@ void CodegenCVisitor::print_mechanism_var_structure() { for (auto& var : int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { - printer->add_line("{}{}* {}{};"_format(k_const(), int_type, k_restrict(), name)); + auto qualifier = var.is_constant ? k_const() : ""; + printer->add_line("{}{}* {}{};"_format(qualifier, int_type, k_restrict(), name)); } else { auto qualifier = var.is_constant ? k_const() : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); @@ -2034,7 +2046,7 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { printer->add_line("double* {} vec_d = nt->_actual_d;"_format(k_restrict())); print_rhs_d_shadow_variables(); } - printer->add_line("{}Datum* {}indexes = ml->pdata;"_format(k_const(), k_restrict())); + printer->add_line("Datum* {}indexes = ml->pdata;"_format(k_restrict())); printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(k_restrict())); if (type == BlockType::Initial) { @@ -2096,6 +2108,126 @@ void CodegenCVisitor::print_nrn_alloc() { printer->add_newline(); } +/** + * Print nrn_watch_activate function used as a callback for every + * WATCH statement in the mod file. + * Todo : number of watch could be more than number of statements + * according to grammar. Check if this is correctly handled in neuron + * and coreneuron. + */ +void CodegenCVisitor::print_watch_activate() { + if (info.watch_statements.empty()) { + return; + } + codegen = true; + printer->add_newline(2); + auto inst = "{}* inst"_format(instance_struct()); + + printer->start_block( + "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id) "_format(inst)); + + /// initialize all variables only during first watch statement + printer->add_line("if (watch_id == 0) {"); + for (int i = 0; i < info.watch_count; i++) { + auto name = get_variable_name("watch{}"_format(i + 1)); + printer->add_line(" {} = 0;"_format(name)); + } + printer->add_line("}"); + + // todo : similar to neuron/coreneuron we are using + // first watch and ignoring rest. + for (int i = 0; i < info.watch_statements.size(); i++) { + auto& statement = info.watch_statements[i]; + printer->start_block("if (watch_id == {})"_format(i)); + + auto varname = get_variable_name("watch{}"_format(i + 1)); + printer->add_indent(); + printer->add_text("{} = 2 + "_format(varname)); + auto& watch = statement->statements.front(); + watch->expression->visit_children(this); + printer->add_text(";"); + printer->add_newline(); + + printer->end_block(); + printer->add_newline(); + } + printer->end_block(); + printer->add_newline(); + codegen = false; +} + +/** + * Print kernel for watch activation + * todo : similar to print_watch_activate, we are using only + * first watch. need to verify with neuron/coreneuron about rest. + */ + +void CodegenCVisitor::print_watch_check() { + codegen = true; + printer->add_newline(2); + printer->add_line("/** routine to check watch activation */"); + print_global_function_common_code(BlockType::Watch); + print_channel_iteration_tiling_block_begin(BlockType::Watch); + print_channel_iteration_block_begin(); + print_post_channel_iteration_common_code(); + + for (int i = 0; i < info.watch_statements.size(); i++) { + auto& statement = info.watch_statements[i]; + auto& watch = statement->statements.front(); + auto varname = get_variable_name("watch{}"_format(i + 1)); + + // start block 1 + printer->start_block("if ({}&2)"_format(varname)); + + // start block 2 + printer->add_indent(); + printer->add_text("if ("); + watch->expression->accept(this); + printer->add_text(") {"); + printer->add_newline(); + printer->increase_indent(); + + // start block 3 + printer->start_block("if (({}&1) == 0)"_format(varname)); + + auto tqitem = get_variable_name("tqitem"); + auto point_process = get_variable_name("point_process"); + printer->add_indent(); + printer->add_text("net_send_buffering("); + printer->add_text( + "ml->_net_send_buffer, 0, {}, 0, {}, t+0.0, "_format(tqitem, point_process)); + watch->value->accept(this); + printer->add_text(");"); + printer->add_newline(); + + printer->add_line("{} = 3;"_format(varname)); + printer->end_block(); + printer->add_newline(); + // end block 3 + + // start block 3 + printer->start_block("else"_format()); + printer->add_line("{} = 2;"_format(varname)); + printer->end_block(); + printer->add_newline(); + // end block 3 + + printer->decrease_indent(); + printer->add_line("}"); + // end block 2 + + printer->end_block(); + printer->add_newline(); + // end block 1 + } + + print_channel_iteration_block_end(); + print_send_event_move(); + printer->end_block(); + printer->add_newline(); + codegen = false; +} + void CodegenCVisitor::print_net_receive_common_code(Block* node) { printer->add_line("int tid = pnt->_tid;"); @@ -2227,6 +2359,25 @@ void CodegenCVisitor::print_net_init_kernel() { codegen = false; } +void CodegenCVisitor::print_send_event_move() { + printer->add_newline(); + printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); + // todo : update net send buffer on host + printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); + printer->add_line(" int type = nsb->_sendtype[i];"); + printer->add_line(" int tid = nt->id;"); + printer->add_line(" double t = nsb->_nsb_t[i];"); + printer->add_line(" double flag = nsb->_nsb_flag[i];"); + printer->add_line(" int vdata_index = nsb->_vdata_index[i];"); + printer->add_line(" int weight_index = nsb->_weight_index[i];"); + printer->add_line(" int point_index = nsb->_pnt_index[i];"); + // clang-format off + printer->add_line(" net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag);"); + // clang-format on + printer->add_line("}"); + printer->add_line("nsb->_cnt = 0;"); + // todo : update net send buffer count on device +} void CodegenCVisitor::print_net_receive_buffer_kernel() { if (!net_receive_required() || info.artificial_cell) { @@ -2258,26 +2409,10 @@ void CodegenCVisitor::print_net_receive_buffer_kernel() { printer->add_line("}"); if (info.net_send_used || info.net_event_used) { - printer->add_newline(); - printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); - printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); - printer->add_line(" int type = nsb->_sendtype[i];"); - printer->add_line(" int tid = nt->id;"); - printer->add_line(" double t = nsb->_nsb_t[i];"); - printer->add_line(" double flag = nsb->_nsb_flag[i];"); - printer->add_line(" int vdata_index = nsb->_vdata_index[i];"); - printer->add_line(" int weight_index = nsb->_weight_index[i];"); - printer->add_line(" int point_index = nsb->_pnt_index[i];"); - // clang-format off - printer->add_line(" net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag);"); - // clang-format on - printer->add_line("}"); + print_send_event_move(); } printer->add_newline(); - if (info.net_send_used || info.net_event_used) { - printer->add_line("nsb->_cnt = 0;"); - } printer->add_line("nrb->_displ_cnt = 0;"); printer->add_line("nrb->_cnt = 0;"); @@ -2390,7 +2525,8 @@ void CodegenCVisitor::print_derivative_kernel_for_euler() { auto arguments = external_method_parameters(); printer->add_newline(2); - printer->start_block("int {}({})"_format(node->get_name(), arguments)); + printer->add_line("/* _euler_ state _{} */"_format(info.mod_suffix)); + printer->start_block("int {}_{}({})"_format(node->get_name(), info.mod_suffix, arguments)); printer->add_line(instance); print_statement_block(node->get_statement_block().get(), false, false); printer->add_line("return 0;"); @@ -2522,7 +2658,7 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line(statement); } else if (info.euler_used) { auto args = - "{}, {}, {}, euler_{}_{}, {}" + "{}, {}, {}, _euler_{}_{}, {}" ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); auto statement = "euler_thread({});"_format(args); printer->add_line(statement); @@ -2772,6 +2908,8 @@ void CodegenCVisitor::codegen_compute_functions() { print_net_init_kernel(); print_net_send_buffer_kernel(); + print_watch_activate(); + print_watch_check(); print_net_receive(); print_net_receive_buffer_kernel(); print_nrn_init(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 2c7cf5a411..088a45c3d0 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -36,6 +36,9 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// internal method call was encountered while processing vebatim block bool internal_method_call_encountered = false; + /// index of watch statement being printed + int current_watch_statement = 0; + /// number of threads to allocate int num_thread_objects() { return info.vectorize ? (info.thread_data_index + 1) : 0; @@ -405,6 +408,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { void print_net_send_buffer_kernel(); + /// send event move block used in net receive as well as watch + void print_send_event_move(); + + /// kernel for buffering net_receive events void print_net_receive_buffer_kernel(); @@ -488,24 +495,39 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// mechanism registration function void print_mechanism_register(); + + /// print watch activate function + void print_watch_activate(); + + + /// print watch activate function + void print_watch_check(); + + /// all includes void codegen_includes(); + /// start of namespaces void codegen_namespace_begin(); + /// end of namespaces void codegen_namespace_end(); + /// common getter void codegen_common_getters(); + /// all classes void codegen_data_structures(); + /// all compute functions for every backend virtual void codegen_compute_functions(); + /// entry point to code generation virtual void codegen_all(); @@ -525,6 +547,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { : CodegenBaseVisitor(mod_file, stream, aos, float_type) { } + virtual void visit_watch_statement(ast::WatchStatement* node) override; virtual void visit_function_call(ast::FunctionCall* node) override; virtual void visit_verbatim(ast::Verbatim* node) override; virtual void visit_program(ast::Program* node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 7dd0efc061..32dbd05891 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -165,6 +165,12 @@ namespace codegen { /// if area is used bool area_used = false; + /// if for_netcon is used + bool for_netcon_used = false; + + /// number of watch expressions + int watch_count = 0; + /** * thread_data_index indicates number of threads being allocated. * For example, if there is derivimplicit method used, then two thread @@ -316,6 +322,9 @@ namespace codegen { /// all top level verbatim blocks std::vector top_verbatim_blocks; + /// all watch statements + std::vector watch_statements; + /// tqitem index in int variables /// note that if tqitem doesn't exist then default value should be 0 int tqitem_index = 0; @@ -334,6 +343,11 @@ namespace codegen { /// if a current bool is_current(const std::string& name); + + /// if watch statements are used + bool is_watch_used() const { + return watch_count > 0; + } }; }; // namespace codegen From e74054f19f68addf59c8f871ed40ab02337e386e Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 24 Nov 2018 21:01:35 +0100 Subject: [PATCH 084/871] Add global variables with write count > 0 as thread safe Change-Id: I6bdbdc808a36d7dcde46089c7def7a38cae7fe08 NMODL Repo SHA: BlueBrain/nmodl@000b7a363716ff130d2838fe77ab2a7a7df14303 --- src/nmodl/codegen/base/codegen_helper_visitor.cpp | 14 +++++++++++++- src/nmodl/symtab/symbol_properties.cpp | 5 +++++ src/nmodl/symtab/symbol_properties.hpp | 6 +++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 9c35cba5b5..39352097de 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -130,6 +130,7 @@ void CodegenHelperVisitor::find_non_range_variables() { auto vars = psymtab->get_variables_with_properties(NmodlInfo::global_var); for (auto& var : vars) { if (info.thread_safe && var->get_write_count() > 0) { + var->add_property(NmodlInfo::thread_safe); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); } else { @@ -156,7 +157,18 @@ void CodegenHelperVisitor::find_non_range_variables() { // clang-format on vars = psymtab->get_variables(with, without); for (auto& var : vars) { + // some variables like area and diam are declared in parameter + // block but they are not global + if (var->get_name() == diam_variable + || var->get_name() == area_variable + || var->has_properties(NmodlInfo::extern_neuron_variable)) { + continue; + } + + // if model is thread safe and if parameter is being written then + // those variables should be promoted to thread safe variable if (info.thread_safe && var->get_write_count() > 0) { + var->add_property(NmodlInfo::thread_safe); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); } else { @@ -575,4 +587,4 @@ void CodegenHelperVisitor::visit_program(Program* node) { codegen::CodegenInfo CodegenHelperVisitor::get_code_info(ast::Program* node) { node->accept(this); return info; -} \ No newline at end of file +} diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 336f9d955f..af7201e171 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -148,6 +148,11 @@ std::vector to_string_vector(const SymbolInfo& obj) { if (has_property(obj, NmodlInfo::useion)) { properties.emplace_back("ion"); } + + if (has_property(obj, NmodlInfo::thread_safe)) { + properties.emplace_back("thread_safe"); + } + return properties; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index f67eac37fc..b369618bf1 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -90,6 +90,7 @@ namespace symtab { * * \todo Rename param_assign to parameter_var * \todo Reaching max limit (31), need to refactor all block types + * into separate type */ enum class NmodlInfo : long long { /** Local Variable */ @@ -183,7 +184,10 @@ namespace symtab { to_solve = 1 << 29, /** ion type */ - useion = 1 << 30 + useion = 1 << 30, + + /** variable is converted to thread safe */ + thread_safe = 1 << 31 /** Discrete Block */ // discrete_block = 1 << 31, From 1771a20400d40c9db5083c71b86bb6f3a5a6b4ee Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 28 Dec 2018 14:56:35 +0100 Subject: [PATCH 085/871] throw exception if global variable is written Change-Id: Ib03d830bf8485ec803a7da683937128306194e7e NMODL Repo SHA: BlueBrain/nmodl@82431ad257f78f46e4c5f989ff8c88242bf37933 --- src/nmodl/codegen/base/codegen_helper_visitor.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 39352097de..fdde610efd 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -127,17 +127,29 @@ void CodegenHelperVisitor::find_non_range_variables() { * All global variables remain global if mod file is not marked thread safe. * Otherwise, global variables written at least once gets promoted to thread variables. */ + + std::string variables; + auto vars = psymtab->get_variables_with_properties(NmodlInfo::global_var); for (auto& var : vars) { if (info.thread_safe && var->get_write_count() > 0) { var->add_property(NmodlInfo::thread_safe); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); + variables += " " + var->get_name(); } else { info.global_variables.push_back(var); } } + /** + * todo : move this to separate pass + */ + if (!variables.empty()) { + std::string message = "Global variables are updated in compute blocks, convert them to range? : "; + throw std::runtime_error(message + variables); + } + /** * If parameter is not a range and used only as read variable then it becomes global * variable. To qualify it as thread variable it must be be written at least once and From 7a3ac1ffb5555634c5b56940b8f778a453c91727 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 28 Dec 2018 16:49:04 +0100 Subject: [PATCH 086/871] bug fix : global variables need static allocation Change-Id: I51c6bc914c6a17ae271dea0b2e674a7443cf15f1 NMODL Repo SHA: BlueBrain/nmodl@5932e4bb03b912cbf284b2f3b9edd8a3a661a35c --- src/nmodl/codegen/c/codegen_c_visitor.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 06fa3fd61a..a4ddf801a7 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1140,7 +1140,7 @@ std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, std::string CodegenCVisitor::global_variable_name(SymbolType& symbol) { - return "{}_global->{}"_format(info.mod_suffix, symbol->get_name()); + return "{}_global.{}"_format(info.mod_suffix, symbol->get_name()); } @@ -1389,7 +1389,7 @@ void CodegenCVisitor::print_mechanism_global_structure() { printer->add_newline(1); printer->add_line("/** holds object of global variable */"); - printer->add_line("{}* {}{}_global;"_format(global_struct(), k_restrict(), info.mod_suffix)); + printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); } @@ -1733,10 +1733,6 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line(" return;"); printer->add_line("}"); - printer->add_newline(1); - auto allocation = "({0}*) mem_alloc(1, sizeof({0}))"_format(global_struct()); - printer->add_line("{0}_global = {1};"_format(info.mod_suffix, allocation)); - /// offsets for state variables if (info.primes_size != 0) { auto slist1 = get_variable_name("slist1"); From 4dcaba33448bf0a59af272cef2fe860d6f35765b Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 29 Dec 2018 10:45:13 +0100 Subject: [PATCH 087/871] Use %g for constants and global variable initialization Change-Id: Id2418169dba97f6b15bc3cc5440338dc611e0cdd NMODL Repo SHA: BlueBrain/nmodl@c3320d6fdc456db94f0c360b32741f52d6025e66 --- src/nmodl/codegen/c/codegen_c_visitor.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index a4ddf801a7..11032ab27d 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1801,7 +1801,8 @@ void CodegenCVisitor::print_global_variable_setup() { if (value_ptr != nullptr) { value = *value_ptr; } - printer->add_line("{} = {};"_format(name, double_to_string(value))); + /// use %g to be same as nocmodl in neuron + printer->add_line("{} = {};"_format(name, "{:g}"_format(value))); } } @@ -1813,7 +1814,8 @@ void CodegenCVisitor::print_global_variable_setup() { if (value_ptr != nullptr) { value = *value_ptr; } - printer->add_line("{} = {};"_format(name, double_to_string(value))); + /// use %g to be same as nocmodl in neuron + printer->add_line("{} = {};"_format(name, "{:g}"_format(value))); } printer->add_newline(); From dc37064821bef4735cac090f2c82eca056961ba1 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sun, 25 Nov 2018 18:48:25 +0100 Subject: [PATCH 088/871] Added usetable variable in global structure Change-Id: I02e781ba91e9ba5279c174cb9852bfee1c3ac1f7 NMODL Repo SHA: BlueBrain/nmodl@d22a55a8dd6f48bc37b9da14af0ff592683d6c22 --- CodeGen.md | 2 +- .../codegen/base/codegen_base_visitor.hpp | 1 + .../codegen/base/codegen_helper_visitor.cpp | 6 +++++ .../codegen/base/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/c/codegen_c_visitor.cpp | 22 ++++++++++++++++--- src/nmodl/codegen/codegen_info.hpp | 10 ++++++--- src/nmodl/symtab/symbol_properties.cpp | 2 +- src/nmodl/symtab/symbol_properties.hpp | 1 + 8 files changed, 37 insertions(+), 8 deletions(-) diff --git a/CodeGen.md b/CodeGen.md index 1d46658f26..9a438f992a 100644 --- a/CodeGen.md +++ b/CodeGen.md @@ -1,4 +1,4 @@ -#### Nodes for Code Generation +#### Notes for Code Generation ###### when vectorize is true and what is relation with threadsafe? diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 0389bb2d99..ec51d33cbb 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -158,6 +158,7 @@ class CodegenBaseVisitor : public AstVisitor { std::vector int_variables; /// all global variables for the model + /// todo : this has become different than CodegenInfo std::vector global_variables; /// all ion variables that could be possibly written diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index fdde610efd..e165a6e80a 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -447,6 +447,7 @@ void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { info.procedures.push_back(node); + node->visit_children(this); } @@ -565,6 +566,11 @@ void CodegenHelperVisitor::visit_for_netcon(ast::ForNetcon* node) { } +void CodegenHelperVisitor::visit_table_statement(ast::TableStatement* node) { + info.table_count++; +} + + void CodegenHelperVisitor::find_solve_node() { if (info.solve_node != nullptr) { return; diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp index d1a8a6f1ed..4c025d3d35 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -91,6 +91,7 @@ class CodegenHelperVisitor : public AstVisitor { virtual void visit_watch(ast::Watch* node) override; virtual void visit_watch_statement(ast::WatchStatement* node) override; virtual void visit_for_netcon(ast::ForNetcon* node) override; + virtual void visit_table_statement(ast::TableStatement* node) override; virtual void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 11032ab27d..246ef11220 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1379,6 +1379,11 @@ void CodegenCVisitor::print_mechanism_global_structure() { } } + if (info.table_count > 0) { + printer->add_line("double usetable;"); + global_variables.push_back(make_symbol("usetable")); + } + if (info.vectorize) { printer->add_line("ThreadDatum* {}ext_call_thread;"_format(k_restrict())); global_variables.push_back(make_symbol("ext_call_thread")); @@ -1436,7 +1441,7 @@ void CodegenCVisitor::print_global_variables_for_hoc() { for (auto& variable : variables) { if (variable->is_array() == if_array) { auto name = get_variable_name(variable->get_name()); - auto ename = add_escape_quote(variable->get_name()); + auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); auto length = variable->get_length(); if (if_vector) { printer->add_line("{}, {}, {},"_format(ename, name, length)); @@ -1450,6 +1455,10 @@ void CodegenCVisitor::print_global_variables_for_hoc() { auto globals = info.global_variables; auto thread_vars = info.thread_variables; + if (info.table_count > 0) { + globals.push_back(make_symbol("usetable")); + } + printer->add_newline(2); printer->add_line("/** connect global (scalar) variables to hoc -- */"); printer->add_line("static DoubScal hoc_scalar_double[] = {"); @@ -1542,12 +1551,14 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("thread_mem_init({});"_format(name)); printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); } + if (info.thread_callback_register) { printer->add_line("_nrn_thread_reg0(mech_type, thread_mem_cleanup);"); printer->add_line("_nrn_thread_reg1(mech_type, thread_mem_init);"); } - if (info.emit_table_thread) { - printer->add_line("nrn_thread_table_reg(mech_type, check_table_thread);"); + + if (info.emit_table_thread()) { + printer->add_line("_nrn_thread_table_reg(mech_type, check_table_thread);"); } /// register read/write callbacks for pointers @@ -1818,6 +1829,11 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line("{} = {};"_format(name, "{:g}"_format(value))); } + if (info.table_count > 0) { + auto name = get_variable_name("usetable"); + printer->add_line("{} = 1;"_format(name)); + } + printer->add_newline(); printer->add_line("setup_done = 1;"); diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 32dbd05891..ac3a05d0b4 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -144,9 +144,6 @@ namespace codegen { /// if thread thread call back routines need to register bool thread_callback_register = false; - /// \todo : if table routines need to be printed - bool emit_table_thread = false; - /// if bbcore pointer is used bool bbcore_pointer_used = false; @@ -171,6 +168,9 @@ namespace codegen { /// number of watch expressions int watch_count = 0; + // number of table statements + int table_count = 0; + /** * thread_data_index indicates number of threads being allocated. * For example, if there is derivimplicit method used, then two thread @@ -348,6 +348,10 @@ namespace codegen { bool is_watch_used() const { return watch_count > 0; } + + bool emit_table_thread() const { + return (table_count > 0 && vectorize == true); + } }; }; // namespace codegen diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index af7201e171..8f2cf31a4a 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -103,7 +103,7 @@ std::vector to_string_vector(const SymbolInfo& obj) { properties.emplace_back("non_linear_block"); } - /** todo: temporarily commented out + /** todo: temporarily disabled due to property limit in enum if (has_property(obj, NmodlInfo::discrete_block)) { properties.emplace_back("discrete_block"); } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index b369618bf1..eca516da28 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -73,6 +73,7 @@ namespace symtab { write = 1 << 1 }; + /** @brief nmodl variable properties * * Certain variables/symbols specified in various places in the From d79a0f9a66b8afbb76bec07d382b93cf15a4bdac Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Mon, 17 Dec 2018 14:32:31 +0100 Subject: [PATCH 089/871] Use long long for enums instead of int Change-Id: Ib7b97f0cf65b695345640d1ab8cef01ef9244778 NMODL Repo SHA: BlueBrain/nmodl@eb7f9f08851b299e49bd8620107420e4503adc83 --- .../codegen/base/codegen_helper_visitor.cpp | 4 +- src/nmodl/language/nmodl.yaml | 4 +- src/nmodl/symtab/symbol.hpp | 4 + src/nmodl/symtab/symbol_properties.cpp | 16 +-- src/nmodl/symtab/symbol_properties.hpp | 97 ++++++++++--------- test/nmodl/transpiler/symtab/symbol_table.cpp | 2 +- 6 files changed, 69 insertions(+), 58 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index e165a6e80a..d0773c9d18 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -133,7 +133,7 @@ void CodegenHelperVisitor::find_non_range_variables() { auto vars = psymtab->get_variables_with_properties(NmodlInfo::global_var); for (auto& var : vars) { if (info.thread_safe && var->get_write_count() > 0) { - var->add_property(NmodlInfo::thread_safe); + var->mark_thread_safe(); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); variables += " " + var->get_name(); @@ -180,7 +180,7 @@ void CodegenHelperVisitor::find_non_range_variables() { // if model is thread safe and if parameter is being written then // those variables should be promoted to thread safe variable if (info.thread_safe && var->get_write_count() > 0) { - var->add_property(NmodlInfo::thread_safe); + var->mark_thread_safe(); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); } else { diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 1356674e57..2f16b7038b 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1577,11 +1577,11 @@ - TableStatement: nmodl: "TABLE " members: - - tablst: + - table_vars: type: Name vector: true separator: "," - - dependlst: + - depend_vars: type: Name vector: true prefix: {value: " DEPEND "} diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index d5f7702daf..e69631ba10 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -189,6 +189,10 @@ namespace symtab { status |= Status::localized; } + void mark_thread_safe() { + status |= Status::thread_safe; + } + void created_from_state() { created(); status |= Status::from_state; diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 8f2cf31a4a..4c96be87f4 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -103,11 +103,9 @@ std::vector to_string_vector(const SymbolInfo& obj) { properties.emplace_back("non_linear_block"); } - /** todo: temporarily disabled due to property limit in enum - if (has_property(obj, NmodlInfo::discrete_block)) { - properties.emplace_back("discrete_block"); + if (has_property(obj, NmodlInfo::table_dependent)) { + properties.emplace_back("table_dependent"); } - */ if (has_property(obj, NmodlInfo::constant_var)) { properties.emplace_back("constant"); @@ -149,8 +147,8 @@ std::vector to_string_vector(const SymbolInfo& obj) { properties.emplace_back("ion"); } - if (has_property(obj, NmodlInfo::thread_safe)) { - properties.emplace_back("thread_safe"); + if (has_property(obj, NmodlInfo::discrete_block)) { + properties.emplace_back("discrete_block"); } return properties; @@ -178,9 +176,15 @@ std::vector to_string_vector(const SymbolStatus& obj) { if (has_status(obj, Status::created)) { status.emplace_back("created"); } + if (has_status(obj, Status::from_state)) { status.emplace_back("from_state"); } + + if (has_status(obj, Status::thread_safe)) { + status.emplace_back("thread_safe"); + } + return status; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index eca516da28..b4b53ee448 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -11,7 +11,7 @@ namespace symtab { namespace details { /** kind of symbol */ - enum class DeclarationType { + enum class DeclarationType : long long { /** variable */ variable, @@ -20,7 +20,7 @@ namespace symtab { }; /** scope within a mod file */ - enum class Scope { + enum class Scope : long long { /** local variable */ local, @@ -35,28 +35,31 @@ namespace symtab { }; /** state during various compiler passes */ - enum class Status { + enum class Status : long long { /** converted to local */ - localized = 1 << 0, + localized = 1L << 0, /** converted to global */ - globalized = 1 << 1, + globalized = 1L << 1, /** inlined */ - inlined = 1 << 2, + inlined = 1L << 2, /** renamed */ - renamed = 1 << 3, + renamed = 1L << 3, /** created */ - created = 1 << 4, + created = 1L << 4, /** derived from state */ - from_state = 1 << 5 + from_state = 1L << 5, + + /** variable marked as thread safe */ + thread_safe = 1L << 6 }; /** usage of mod file as array or scalar */ - enum class VariableType { + enum class VariableType : long long { /** scalar / single value */ scalar, @@ -65,12 +68,12 @@ namespace symtab { }; /** variable usage within a mod file */ - enum class Access { + enum class Access : long long { /** variable is ready only */ - read = 1 << 0, + read = 1L << 0, /** variable is written only */ - write = 1 << 1 + write = 1L << 1 }; @@ -95,103 +98,103 @@ namespace symtab { */ enum class NmodlInfo : long long { /** Local Variable */ - local_var = 1 << 0, + local_var = 1L << 0, /** Global Variable */ - global_var = 1 << 1, + global_var = 1L << 1, /** Range Variable */ - range_var = 1 << 2, + range_var = 1L << 2, /** Parameter Variable */ - param_assign = 1 << 3, + param_assign = 1L << 3, /** Pointer Type */ - pointer_var = 1 << 4, + pointer_var = 1L << 4, /** Bbcorepointer Type */ - bbcore_pointer_var = 1 << 5, + bbcore_pointer_var = 1L << 5, /** Extern Type */ - extern_var = 1 << 6, + extern_var = 1L << 6, /** Prime Type */ - prime_name = 1 << 7, + prime_name = 1L << 7, /** Dependent Def */ - dependent_def = 1 << 8, + dependent_def = 1L << 8, /** Unit Def */ - unit_def = 1 << 9, + unit_def = 1L << 9, /** Read Ion */ - read_ion_var = 1 << 10, + read_ion_var = 1L << 10, /** Write Ion */ - write_ion_var = 1 << 11, + write_ion_var = 1L << 11, /** Non Specific Current */ - nonspe_cur_var = 1 << 12, + nonspe_cur_var = 1L << 12, /** Electrode Current */ - electrode_cur_var = 1 << 13, + electrode_cur_var = 1L << 13, /** Section Type */ - section_var = 1 << 14, + section_var = 1L << 14, /** Argument Type */ - argument = 1 << 15, + argument = 1L << 15, /** Function Type */ - function_block = 1 << 16, + function_block = 1L << 16, /** Procedure Type */ - procedure_block = 1 << 17, + procedure_block = 1L << 17, /** Derivative Block */ - derivative_block = 1 << 18, + derivative_block = 1L << 18, /** Linear Block */ - linear_block = 1 << 19, + linear_block = 1L << 19, /** NonLinear Block */ - non_linear_block = 1 << 20, + non_linear_block = 1L << 20, /** constant variable */ - constant_var = 1 << 21, + constant_var = 1L << 21, /** Partial Block */ - partial_block = 1 << 22, + partial_block = 1L << 22, /** Kinetic Block */ - kinetic_block = 1 << 23, + kinetic_block = 1L << 23, /** FunctionTable Block */ - function_table_block = 1 << 24, + function_table_block = 1L << 24, /** factor in unit block */ - factor_def = 1 << 25, + factor_def = 1L << 25, /** neuron variable accessible in mod file */ - extern_neuron_variable = 1 << 26, + extern_neuron_variable = 1L << 26, /** neuron solver methods and math functions */ - extern_method = 1 << 27, + extern_method = 1L << 27, /** state variable */ - state_var = 1 << 28, + state_var = 1L << 28, /** need to solve : used in solve statement */ - to_solve = 1 << 29, + to_solve = 1L << 29, /** ion type */ - useion = 1 << 30, + useion = 1L << 30, - /** variable is converted to thread safe */ - thread_safe = 1 << 31 + /** variable is used in table as dependent */ + table_dependent = 1L << 31, /** Discrete Block */ - // discrete_block = 1 << 31, + discrete_block = 1L << 32 }; } // namespace details diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 8d6833ac1b..43ab8d3cd6 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -76,7 +76,7 @@ SCENARIO("Symbol properties can be added and converted to string") { SCENARIO("Symbol operations") { SymbolInfo property1 = NmodlInfo::argument; SymbolInfo property2 = NmodlInfo::range_var; - SymbolInfo property3 = NmodlInfo::param_assign; + SymbolInfo property3 = NmodlInfo::discrete_block; GIVEN("A symbol") { ModToken token(true); Symbol symbol("alpha", token); From f76b6dece59e85dbb1cad88a4804391bd5c1564b Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 28 Dec 2018 14:14:26 +0100 Subject: [PATCH 090/871] Print nmodl constants for time being Change-Id: I37ba40b96ce9f1e52f585d6a0b8976bd5867b508 NMODL Repo SHA: BlueBrain/nmodl@58746258df9e215b3eda2c6a499666a6b0377ff5 --- src/nmodl/codegen/c/codegen_c_visitor.cpp | 15 +++++++++++++++ src/nmodl/codegen/c/codegen_c_visitor.hpp | 4 ++++ 2 files changed, 19 insertions(+) diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 246ef11220..8e2a5b2e5e 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -260,6 +260,19 @@ bool CodegenCVisitor::is_constant_variable(std::string name) { return is_constant; } +/** + * NMODL constants from unit database + * + * todo : this should be replaced with constant handling from unit database + */ + +void CodegenCVisitor::print_nmodl_constant() { + printer->add_newline(2); + printer->add_line("/** constants used in nmodl */"); + printer->add_line("static double FARADAY = 96485.3;"); + printer->add_line("static double PI = 3.14159;"); + printer->add_line("static double R = 8.3145;"); +} /****************************************************************************************/ @@ -2938,6 +2951,8 @@ void CodegenCVisitor::codegen_all() { codegen_includes(); codegen_namespace_begin(); + print_nmodl_constant(); + print_mechanism_info_array(); codegen_data_structures(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 088a45c3d0..f5a45fc262 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -187,6 +187,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual void print_backend_namespace_end(); + /// nmodl constants + void print_nmodl_constant(); + + /// top header printed in generated code void print_backend_info(); From 0760213e217948965f771ae487042cf3a636e8e9 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Thu, 27 Dec 2018 13:45:27 +0100 Subject: [PATCH 091/871] Refactor symtab visitor by removing all template functions Change-Id: Idf7b419a084838e69cf5c9f981b15f6e5fd959ee NMODL Repo SHA: BlueBrain/nmodl@8d9a9e871e92c4e941f8eda9470e6f69b2acb7ca --- src/nmodl/ast/ast_utils.hpp | 4 + src/nmodl/language/ast_printer.py | 2 +- src/nmodl/language/node_info.py | 5 +- src/nmodl/language/visitors_printer.py | 56 ++++--- src/nmodl/symtab/symbol_table.cpp | 8 +- src/nmodl/visitors/CMakeLists.txt | 2 +- ...r_helper.hpp => symtab_visitor_helper.cpp} | 140 ++++++++++-------- 7 files changed, 117 insertions(+), 100 deletions(-) rename src/nmodl/visitors/{symtab_visitor_helper.hpp => symtab_visitor_helper.cpp} (66%) diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index ccc7c478b6..3abc2842eb 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -88,6 +88,10 @@ namespace ast { return nullptr; } + virtual void set_symbol_table(symtab::SymbolTable* newsymtab) { + throw std::runtime_error("set_symbol_table() not implemented"); + } + virtual symtab::SymbolTable* get_symbol_table() { throw std::runtime_error("get_symbol_table() not implemented"); } diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index b0c3bc23a7..51e93f28e3 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -213,7 +213,7 @@ def ast_classes_declaration(self): self.write_line("void set_token(ModToken& tok) " + " { token = std::shared_ptr(new ModToken(tok)); }") if node.is_symtab_needed(): - self.write_line("void set_symbol_table(symtab::SymbolTable* newsymtab) " + " { symtab = newsymtab; }") + self.write_line("void set_symbol_table(symtab::SymbolTable* newsymtab) override " + " { symtab = newsymtab; }") self.write_line("symtab::SymbolTable* get_symbol_table() override " + " { return symtab; }") if node.is_program_node(): diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index af09a076c4..33cb0550c7 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -69,13 +69,14 @@ "ConstantVar"] # block nodes which will go into symbol table -# todo : skipping discrete block due to limits in +# these blocks doesn't define global variables but they +# use variables from other global variables SYMBOL_BLOCK_TYPES = ["FunctionBlock", "ProcedureBlock", "DerivativeBlock", "LinearBlock", "NonLinearBlock", - #"DiscreteBlock", + "DiscreteBlock", "PartialBlock", "KineticBlock", "FunctionTableBlock"] diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index bdc43ce15c..82236b7545 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -172,7 +172,7 @@ def private_declaration(self): self.write_line("std::unique_ptr printer;") self.write_line("std::string block_to_solve;") self.write_line("bool update = false;") - self.write_line("bool state_block = false;", newline=2) + self.write_line("bool under_state_block = false;", newline=2) def public_declaration(self): self.write_line("public:", post_gutter=1) @@ -186,25 +186,27 @@ def public_declaration(self): line = self.classname + "(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {}" self.write_line(line, newline=2) - # helper function for creating symbol for variables - self.write_line("template") - self.write_line("void setup_symbol(T* node, SymbolInfo property, int order = 0);", newline=2) + # helper function for setting up symbol for variable + self.write_line("void setup_symbol(ast::Node* node, SymbolInfo property);", newline=2) - # helper function for creating symbol table for blocks - # without name (e.g. parameter, unit, breakpoint) - self.write_line("template") - line = "void setup_symbol_table(T *node, std::string name, bool is_global);" + # add symbol with given property to model symbol table + line = "void add_model_symbol_with_property(ast::Node* node, SymbolInfo property);" self.write_line(line, newline=2) # helper function for creating symbol table for blocks - # with name (e.g. procedure, function, derivative) - self.write_line("template") - line = "void setup_symbol_table(T *node, std::string name, SymbolInfo property, bool is_global);" + line = "void setup_symbol_table(ast::AST *node, std::string name, bool is_global);" + self.write_line(line, newline=2) + + # helper function for creating symbol table for global blocks of mod file + line = "void setup_symbol_table_for_global_block(ast::Node *node);" + self.write_line(line, newline=2) + + # helper function for creating symbol table for non-global blocks (e.g. function, procedures) + line = "void setup_symbol_table_for_scoped_block(ast::Node *node, std::string name);" self.write_line(line, newline=2) # helper function to setup program symbol table - self.write_line("template") - line = "void setup_program_symbol_table(T *node, std::string name, bool is_global);" + line = "void setup_symbol_table_for_program_block(ast::Program *node);" self.write_line(line, newline=2) # we have to override visitor methods for the nodes @@ -216,11 +218,6 @@ def public_declaration(self): self.writer.decrease_gutter() - def post_declaration(self): - # helper function definitions - self.write_line() - self.write_line('#include "visitors/symtab_visitor_helper.hpp"') - class SymtabVisitorDefinitionPrinter(DefinitionPrinter): """Prints visitor class definition for printing Symbol table in JSON format""" @@ -239,26 +236,25 @@ def definitions(self): self.write_line(line, post_gutter=1) type_name = to_snake_case(node.class_name) - property_name = "symtab::details::NmodlInfo::" + type_name + property_name = "NmodlInfo::" + type_name - if node.is_symbol_var_node() or node.is_prime_node(): - is_prime = ", node->get_order()" if node.is_prime_node() else ""; - self.write_line("setup_symbol(node, " + property_name + is_prime + ");") + if node.is_symbol_var_node(): + self.write_line("setup_symbol(node, " + property_name + ");") else: """ setupBlock has node*, properties, global_block""" - if node.is_symbol_block_node(): - fun_call = "setup_symbol_table(node, node->get_name(), " + property_name + ", false);" - - elif node.is_program_node(): - fun_call = "setup_program_symbol_table(node, node->get_type_name(), true);" + if node.is_program_node(): + self.write_line("setup_symbol_table_for_program_block(node);") elif node.is_global_block_node(): - fun_call = "setup_symbol_table(node, node->get_type_name(), true);" + self.write_line("setup_symbol_table_for_global_block(node);") else: """this is for nodes which has parent class as Block node""" - fun_call = "setup_symbol_table(node, node->get_type_name(), false);" + if node.is_symbol_block_node(): + self.write_line("add_model_symbol_with_property(node, %s);" % property_name) + self.write_line("setup_symbol_table_for_scoped_block(node, node->get_name());") + else: + self.write_line("setup_symbol_table_for_scoped_block(node, node->get_type_name());") - self.write_line(fun_call) self.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 76db542aab..0afe6316ee 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -349,7 +349,8 @@ namespace symtab { } - /** Function callback at the entry of every block in nmodl file + /** + * Function callback at the entry of every block in nmodl file * Every block starts a new scope and hence new symbol table is created. * The same symbol table is returned so that visitor can store pointer to * symbol table within a node. @@ -366,7 +367,10 @@ namespace symtab { breakpoint_exist = true; } - /// all global blocks in mod file have same global symbol table + /** + * All global blocks in mod file have same global symbol table. If there + * is already symbol table setup in global scope, return the same. + */ if (symtab && global) { return symtab.get(); } diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 41a01953ea..87e41e6ea4 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -8,7 +8,7 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.cpp similarity index 66% rename from src/nmodl/visitors/symtab_visitor_helper.hpp rename to src/nmodl/visitors/symtab_visitor_helper.cpp index 61cdec3491..3b242cf43e 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -1,26 +1,9 @@ -#ifndef _SYMTAB_VISITOR_HELPER_HPP_ -#define _SYMTAB_VISITOR_HELPER_HPP_ - #include "lexer/token_mapping.hpp" -#include "symtab/symbol_table.hpp" - -/// helper function to setup/insert symbol into symbol table -/// for the ast nodes which are of variable types -template -void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { - std::shared_ptr symbol; +#include "visitors/symtab_visitor.hpp" - /// if prime variable is already exist in symbol table - /// then just update the order - if (order) { - symbol = modsymtab->lookup(node->get_name()); - if (symbol) { - symbol->set_order(order); - symbol->add_property(property); - return; - } - } +// create symbol for given node +static std::shared_ptr create_symbol_for_node(Node* node, SymbolInfo property, bool under_state_block) { ModToken token; auto token_ptr = node->get_token(); if (token_ptr != nullptr) { @@ -28,16 +11,40 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { } /// add new symbol - auto name = node->get_name(); - symbol = std::make_shared(name, node, token); - + auto symbol = std::make_shared(node->get_name(), node, token); symbol->add_property(property); - + // non specific variable is range if (property == NmodlInfo::nonspe_cur_var) { symbol->add_property(NmodlInfo::range_var); } + /// extra property for state variables + if (under_state_block) { + symbol->add_property(NmodlInfo::state_var); + } + return symbol; +} + + +/// helper function to setup/insert symbol into symbol table +/// for the ast nodes which are of variable types +void SymtabVisitor::setup_symbol(Node* node, SymbolInfo property) { + std::shared_ptr symbol; + auto name = node->get_name(); + + /// if prime variable is already exist in symbol table + /// then just update the order + if (node->is_prime_name()) { + auto prime = dynamic_cast(node); + symbol = modsymtab->lookup(name); + if (symbol) { + symbol->set_order(prime->get_order()); + symbol->add_property(property); + return; + } + } + /// range and non_spec_cur can appear in any order in neuron block. /// for both properties, we have to check if symbol is already exist. /// if so we have to return to avoid duplicate definition error. @@ -49,15 +56,11 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { } } + symbol = create_symbol_for_node(node, property, under_state_block); /// insert might return different symbol if already exist in the same scope symbol = modsymtab->insert(symbol); - /// extra property for state variables - if (state_block) { - symbol->add_property(NmodlInfo::state_var); - } - if (node->is_param_assign()) { auto parameter = dynamic_cast(node); if (parameter->value) { @@ -94,17 +97,15 @@ void SymtabVisitor::setup_symbol(T* node, SymbolInfo property, int order) { } } - /// visit childrens, most likely variables are already + /// visit children, most likely variables are already /// leaf nodes, not necessary to visit node->visit_children(this); } -template -void SymtabVisitor::setup_symbol_table(T* node, - std::string name, - SymbolInfo property, - bool is_global) { + +void SymtabVisitor::add_model_symbol_with_property(Node* node, SymbolInfo property) { auto token = node->get_token(); + auto name = node->get_name(); auto symbol = std::make_shared(name, node, *token); symbol->add_property(property); @@ -113,28 +114,32 @@ void SymtabVisitor::setup_symbol_table(T* node, } modsymtab->insert(symbol); - setup_symbol_table(node, name, is_global); } -/** - * Symtab visitor could be called multiple times, after optimization passes, - * in which case we have to throw awayold symbol tables and setup new ones. - */ -template -void SymtabVisitor::setup_program_symbol_table(T* node, std::string name, bool is_global) { - modsymtab = node->get_model_symbol_table(); - modsymtab->set_mode(update); - setup_symbol_table(node, name, is_global); +static void add_external_symbols(symtab::ModelSymbolTable* symtab) { + ModToken tok(true); + auto variables = nmodl::get_external_variables(); + for (auto variable : variables) { + auto symbol = std::make_shared(variable, nullptr, tok); + symbol->add_property(NmodlInfo::extern_neuron_variable); + symtab->insert(symbol); + } + auto methods = nmodl::get_external_functions(); + for (auto method : methods) { + auto symbol = std::make_shared(method, nullptr, tok); + symbol->add_property(NmodlInfo::extern_method); + symtab->insert(symbol); + } } -template -void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global) { + +void SymtabVisitor::setup_symbol_table(AST* node, std::string name, bool is_global) { /// entering into new nmodl block auto symtab = modsymtab->enter_scope(name, node, is_global, node->get_symbol_table()); if (node->is_state_block()) { - state_block = true; + under_state_block = true; } /// there is only one solve statement allowed in mod file @@ -150,30 +155,37 @@ void SymtabVisitor::setup_symbol_table(T* node, std::string name, bool is_global /// when visiting highest level node i.e. Program, we insert /// all global variables to the global symbol table if (node->is_program()) { - ModToken tok(true); - auto variables = nmodl::get_external_variables(); - for (auto variable : variables) { - auto symbol = std::make_shared(variable, nullptr, tok); - symbol->add_property(symtab::details::NmodlInfo::extern_neuron_variable); - modsymtab->insert(symbol); - } - auto methods = nmodl::get_external_functions(); - for (auto method : methods) { - auto symbol = std::make_shared(method, nullptr, tok); - symbol->add_property(symtab::details::NmodlInfo::extern_method); - modsymtab->insert(symbol); - } + add_external_symbols(modsymtab); } /// look for all children blocks recursively node->visit_children(this); - /// exisiting nmodl block + /// existing nmodl block modsymtab->leave_scope(); if (node->is_state_block()) { - state_block = false; + under_state_block = false; } } -#endif \ No newline at end of file + +/** + * Symtab visitor could be called multiple times, after optimization passes, + * in which case we have to throw awayold symbol tables and setup new ones. + */ +void SymtabVisitor::setup_symbol_table_for_program_block(Program* node) { + modsymtab = node->get_model_symbol_table(); + modsymtab->set_mode(update); + setup_symbol_table(node, node->get_type_name(), true); +} + + +void SymtabVisitor::setup_symbol_table_for_global_block(Node* node) { + setup_symbol_table(node, node->get_type_name(), true); +} + + +void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, std::string name) { + setup_symbol_table(node, name, false); +} \ No newline at end of file From f22bcd0e3b2782d4f9037dd2e46f56329f15f5c6 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Thu, 27 Dec 2018 15:13:40 +0100 Subject: [PATCH 092/871] Update table and table dependent variables to symtab Change-Id: Ib597b202368a619ff77d608dc2692d4ded6dea84 NMODL Repo SHA: BlueBrain/nmodl@9547e0a774994e8d3699d97fb7ad117e757d9122 --- src/nmodl/language/node_info.py | 3 +++ src/nmodl/language/nodes.py | 6 ++++++ src/nmodl/language/visitors_printer.py | 7 ++++++- src/nmodl/symtab/symbol_properties.cpp | 8 ++++++-- src/nmodl/symtab/symbol_properties.hpp | 7 +++++-- src/nmodl/visitors/symtab_visitor_helper.cpp | 20 ++++++++++++++++++++ 6 files changed, 46 insertions(+), 5 deletions(-) diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 33cb0550c7..5c7550d826 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -81,6 +81,9 @@ "KineticBlock", "FunctionTableBlock"] +# nodes which need extra handling to augument symbol table +SYMBOL_TABLE_HELPER_NODES = ["TableStatement"] + # blocks defining global variables GLOBAL_BLOCKS = ["NeuronBlock", "ParamBlock", diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 1badc0b1fb..d258d31f4a 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -106,6 +106,9 @@ def is_data_type_node(self): def is_symbol_var_node(self): return True if self.class_name in SYMBOL_VAR_TYPES else False + def is_symbol_helper_node(self): + return True if self.class_name in SYMBOL_TABLE_HELPER_NODES else False + def is_symbol_block_node(self): return True if self.class_name in SYMBOL_BLOCK_TYPES else False @@ -265,6 +268,9 @@ def is_symtab_method_required(self): if self.is_program_node() or self.has_parent_block_node(): method_required = True + if self.class_name in SYMBOL_TABLE_HELPER_NODES: + method_required = True + return method_required def is_base_class_number_node(self): diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 82236b7545..a7de1cffbe 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -230,6 +230,11 @@ def headers(self): def definitions(self): for node in self.nodes: + + # for helper nodes definition needs to be diectly implemented in symtab_visitor_helper.cpp + if node.is_symbol_helper_node(): + continue + if node.is_symtab_method_required(): line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" @@ -242,7 +247,7 @@ def definitions(self): self.write_line("setup_symbol(node, " + property_name + ");") else: - """ setupBlock has node*, properties, global_block""" + if node.is_program_node(): self.write_line("setup_symbol_table_for_program_block(node);") diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 4c96be87f4..9efe1549ad 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -103,8 +103,12 @@ std::vector to_string_vector(const SymbolInfo& obj) { properties.emplace_back("non_linear_block"); } - if (has_property(obj, NmodlInfo::table_dependent)) { - properties.emplace_back("table_dependent"); + if (has_property(obj, NmodlInfo::table_statement_var)) { + properties.emplace_back("table_statement_var"); + } + + if (has_property(obj, NmodlInfo::table_dependent_var)) { + properties.emplace_back("table_dependent_var"); } if (has_property(obj, NmodlInfo::constant_var)) { diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index b4b53ee448..8e3bcc36c9 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -190,11 +190,14 @@ namespace symtab { /** ion type */ useion = 1L << 30, + /** variable is used in table statement */ + table_statement_var = 1L << 31, + /** variable is used in table as dependent */ - table_dependent = 1L << 31, + table_dependent_var = 1L << 32, /** Discrete Block */ - discrete_block = 1L << 32 + discrete_block = 1L << 33 }; } // namespace details diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index 3b242cf43e..e6858032dc 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -188,4 +188,24 @@ void SymtabVisitor::setup_symbol_table_for_global_block(Node* node) { void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, std::string name) { setup_symbol_table(node, name, false); +} + + +/** + * Visit table statement and update symbol in symbol table + * + * @todo : we assume table statement follows variable declaration + */ +void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { + auto update_symbol = [this](NameVector& variables, NmodlInfo property) { + for(auto &var : variables) { + auto name = var->get_name(); + auto symbol = modsymtab->lookup(name); + if (symbol) { + symbol->add_property(property); + } + } + }; + update_symbol(node->table_vars, NmodlInfo::table_statement_var); + update_symbol(node->depend_vars, NmodlInfo::table_dependent_var); } \ No newline at end of file From 8dac0b0f1d4017e49919db1f40db4347e69f527c Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Thu, 27 Dec 2018 20:44:01 +0100 Subject: [PATCH 093/871] Implementation of table functions Change-Id: I8746ddaddef41334db8e2cda8f36bec685e3a8cf NMODL Repo SHA: BlueBrain/nmodl@3ca77f3cf3ba540af41b7b939aaea34e5986a842 --- .../codegen/base/codegen_base_visitor.cpp | 3 +- .../codegen/base/codegen_helper_visitor.cpp | 21 ++ .../codegen/base/codegen_helper_visitor.hpp | 4 + src/nmodl/codegen/c/codegen_c_visitor.cpp | 295 +++++++++++++++--- src/nmodl/codegen/c/codegen_c_visitor.hpp | 52 ++- src/nmodl/codegen/codegen_info.hpp | 7 + src/nmodl/language/ast_printer.py | 12 +- src/nmodl/language/nmodl.yaml | 6 +- src/nmodl/language/node_info.py | 4 + src/nmodl/language/nodes.py | 3 + src/nmodl/symtab/symbol.hpp | 11 + src/nmodl/visitors/symtab_visitor_helper.cpp | 10 +- 12 files changed, 377 insertions(+), 51 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 46993c103c..29fbbee57c 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -292,7 +292,8 @@ bool CodegenBaseVisitor::skip_statement(Statement* node) { if (node->is_unit_state() || node->is_comment() || node->is_solve_block() - || node->is_conductance_hint()) { + || node->is_conductance_hint() + || node->is_table_statement()) { return true; } // clang-format on diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index d0773c9d18..f179643365 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -392,6 +392,14 @@ void CodegenHelperVisitor::find_range_variables() { } +void CodegenHelperVisitor::find_table_variables() { + auto property = NmodlInfo::table_statement_var; + info.table_statement_variables = psymtab->get_variables_with_properties(property); + property = NmodlInfo::table_dependent_var; + info.table_dependent_variables = psymtab->get_variables_with_properties(property); +} + + void CodegenHelperVisitor::visit_suffix(Suffix* node) { auto type = node->get_suffix_type()->get_name(); if (type == point_process) { @@ -448,11 +456,22 @@ void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { info.procedures.push_back(node); node->visit_children(this); + if(table_statement_used) { + table_statement_used = false; + node->use_table(true); + info.functions_with_table.push_back(node); + } } void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { info.functions.push_back(node); + node->visit_children(this); + if(table_statement_used) { + table_statement_used = false; + node->use_table(true); + info.functions_with_table.push_back(node); + } } @@ -568,6 +587,7 @@ void CodegenHelperVisitor::visit_for_netcon(ast::ForNetcon* node) { void CodegenHelperVisitor::visit_table_statement(ast::TableStatement* node) { info.table_count++; + table_statement_used = true; } @@ -599,6 +619,7 @@ void CodegenHelperVisitor::visit_program(Program* node) { find_non_range_variables(); find_ion_variables(); find_solve_node(); + find_table_variables(); } diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp index 4c025d3d35..96ca95b36c 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -39,6 +39,9 @@ class CodegenHelperVisitor : public AstVisitor { /// if visiting breakpoint block bool under_breakpoint_block = false; + /// table statement found + bool table_statement_used = false; + /// symbol table for the program symtab::SymbolTable* psymtab = nullptr; @@ -66,6 +69,7 @@ class CodegenHelperVisitor : public AstVisitor { void find_solve_node(); void find_ion_variables(); + void find_table_variables(); void find_range_variables(); void find_non_range_variables(); void sort_with_mod2c_symbol_order(std::vector& symbols); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 8e2a5b2e5e..ed6808513b 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -686,13 +686,13 @@ void CodegenCVisitor::print_function_prototypes() { } codegen = true; printer->add_newline(2); - for (const auto& function : info.functions) { - print_function_declaration(function); + for (const auto& node : info.functions) { + print_function_declaration(node, node->get_name()); printer->add_text(";"); printer->add_newline(); } - for (const auto& function : info.procedures) { - print_function_declaration(function); + for (const auto& node : info.procedures) { + print_function_declaration(node, node->get_name()); printer->add_text(";"); printer->add_newline(); } @@ -700,18 +700,227 @@ void CodegenCVisitor::print_function_prototypes() { } -void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { - codegen = true; - auto parameters = node->get_arguments(); +static TableStatement* get_table_statement(ast::Block* node) { + TableStatementVisitor v; + node->accept(&v); + auto table_statements = v.get_statements(); + if (table_statements.empty()) { + throw std::runtime_error("Could not find table statement in " + node->get_name()); + } else if (table_statements.size() > 1) { + throw std::runtime_error("More than one table statement found in " + node->get_name()); + } + return table_statements.front(); +} + +void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_newline(2); - print_function_declaration(node); + auto name = node->get_name(); + auto internal_params = internal_method_parameters(); + + print_device_method_annotation(); + printer->add_indent(); + printer->add_text("void check_{}({})"_format(method_name(name), internal_params)); + printer->start_block(); + + auto statement = get_table_statement(node); + auto table_variables = statement->table_vars; + auto depend_variables = statement->depend_vars; + auto from = statement->from; + auto to = statement->to; + + auto with = statement->with->eval(); + auto use_table_var = get_variable_name("usetable"); + auto float_type = default_float_data_type(); + auto tmin_name = get_variable_name("tmin_"+name); + auto mfac_name = get_variable_name("mfac_"+name); + + printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); + printer->add_line(" return;"); + printer->add_line("}"); + + printer->add_line("static bool make_table = true;"); + for(auto& variable : depend_variables) { + printer->add_line("static {} save_{};"_format(float_type, variable->get_name())); + } + + for(auto& variable : depend_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); + printer->add_line(" make_table = true;"); + printer->add_line("}"); + } + + printer->add_line("if (make_table) {}"_format("{")); + printer->increase_indent(); + + printer->add_line("make_table = false;"); + + printer->add_indent(); + printer->add_text("{} = "_format(tmin_name)); + from->accept(this); + printer->add_text(";"); + printer->add_newline(); + + printer->add_indent(); + printer->add_text("double tmax = "); + to->accept(this); + printer->add_text(";"); + printer->add_newline(); + + + printer->add_line("double dx = (tmax-{})/{}.0;"_format(tmin_name, with)); + printer->add_line("{} = 1.0/dx;"_format(mfac_name)); + + printer->add_line("int i = 0;"); + printer->add_line("double x = 0;"); + printer->add_line("for(i = 0, x = {}; i < {}; x += dx, i++) {}"_format(tmin_name, with+1, "{")); + auto function_name = method_name("f_"+name); + printer->add_line(" {}({}, x);"_format(function_name, internal_method_arguments())); + for(auto& variable : table_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_"+name); + printer->add_line(" {}[i] = {};"_format(table_name, instance_name)); + } + printer->add_line("}"); + + for(auto& variable : depend_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + printer->add_line("save_{} = {};"_format(name, instance_name)); + } + + printer->decrease_indent(); + printer->add_line("}"); + + printer->end_block(); + printer->add_newline(); +} + +void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { + printer->add_newline(2); + auto name = node->get_name(); + print_function_declaration(node, name); printer->add_text(" "); printer->start_block(); - print_statement_block(node->get_statement_block().get(), false, false); + + auto statement = get_table_statement(node); + auto table_variables = statement->table_vars; + auto with = statement->with->eval(); + auto use_table_var = get_variable_name("usetable"); + auto float_type = default_float_data_type(); + auto tmin_name = get_variable_name("tmin_"+name); + auto mfac_name = get_variable_name("mfac_"+name); + auto function_name = method_name("f_"+name); + + printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); + printer->add_line(" {}({}, arg_v);"_format(function_name, internal_method_arguments())); + printer->add_line(" return 0;"); + printer->add_line("}"); + + printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); + printer->add_line("if (isnan(xi)) {"); + for(auto& variable : table_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + printer->add_line(" {} = xi;"_format(instance_name)); + } + printer->add_line(" return 0;"); + printer->add_line("}"); + + printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); + printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); + for(auto& variable : table_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_"+name); + printer->add_line(" {} = {}[index];"_format(instance_name, table_name)); + } + printer->add_line(" return 0;"); + printer->add_line("}"); + printer->add_newline(); + + printer->add_line("int i = int(xi);"); + printer->add_line("double theta = xi - double(i);"); + for(auto& variable : table_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_"+name); + printer->add_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);"_format(instance_name, table_name)); + } + printer->add_line("return 0;"); printer->end_block(); printer->add_newline(); +} + + +void CodegenCVisitor::print_check_table_thread_function() { + + if (info.table_count == 0) { + return; + } + + printer->add_newline(2); + auto name = method_name("check_table_thread"); + auto parameters = external_method_parameters(true); + + printer->add_line("static void {} ({}) {}"_format(name, parameters, "{")); + printer->add_line(" Memb_list* ml = nt->_ml_list[tml_id];"); + printer->add_line(" setup_instance(nt, ml);"); + printer->add_line(" {0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + printer->add_line(" double v = 0;"); + printer->add_line(" IonCurVar ionvar = {0};"); + + for(auto& function: info.functions_with_table) { + auto name = method_name("check_" + function->get_name()); + auto arguments = internal_method_arguments(); + printer->add_line(" {}({});"_format(name, arguments)); + } + + /// todo : check_table_thread is called multiple times from coreneuron including + /// after finitialize. If we cleaup the instance then it will result in segfault + /// but if we don't then there is memory leak + printer->add_line(" // cleanup_instance(ml);"); + printer->add_line("}"); +} + +void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& name) { + auto block = node->get_statement_block().get(); + printer->add_newline(2); + print_function_declaration(node, name); + printer->add_text(" "); + printer->start_block(); + + // function requires return variable declaration + if (node->is_function_block()) { + auto type = default_float_data_type(); + printer->add_line("{} ret_{} = 0.0;"_format(type, name)); + } else { + printer->add_line("int ret_{} = 0;"_format(name)); + } + + print_statement_block(block, false, false); + printer->add_line("return ret_{};"_format(name)); + printer->end_block(); + printer->add_newline(); +} + +void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { + codegen = true; + auto name = node->get_name(); + + if (node->use_table()) { + auto new_name = "f_" + name; + print_function_or_procedure(node, new_name); + print_table_check_function(node); + print_table_replacement_function(node); + } else { + print_function_or_procedure(node, name); + } + codegen = false; } @@ -720,22 +929,13 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = true; auto name = node->get_name(); auto return_var = "ret_" + name; - auto type = default_float_data_type() + " "; /// first rename return variable name auto block = node->get_statement_block().get(); RenameVisitor v(name, return_var); block->accept(&v); - printer->add_newline(2); - print_function_declaration(node); - printer->add_text(" "); - printer->start_block(); - printer->add_line("{}{} = 0.0;"_format(type, return_var)); - print_statement_block(block, false, false); - printer->add_line("return {};"_format(return_var)); - printer->end_block(); - printer->add_newline(); + print_function_or_procedure(node, name); codegen = false; } @@ -770,9 +970,14 @@ std::string CodegenCVisitor::external_method_arguments() { } -std::string CodegenCVisitor::external_method_parameters() { - return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, double v"; +std::string CodegenCVisitor::external_method_parameters(bool table) { + if (table) { + return "int id, int pnodecount, double* data, Datum* indexes, " + "ThreadDatum* thread, NrnThread* nt, int tml_id"; + } else { + return "int id, int pnodecount, double* data, Datum* indexes, " + "ThreadDatum* thread, NrnThread* nt, double v"; + } } @@ -1395,6 +1600,20 @@ void CodegenCVisitor::print_mechanism_global_structure() { if (info.table_count > 0) { printer->add_line("double usetable;"); global_variables.push_back(make_symbol("usetable")); + + for(auto& block: info.functions_with_table) { + auto name = block->get_name(); + printer->add_line("{} tmin_{};"_format(float_type, name)); + printer->add_line("{} mfac_{};"_format(float_type, name)); + global_variables.push_back(make_symbol("tmin_"+name)); + global_variables.push_back(make_symbol("mfac_"+name)); + } + + for(auto &variable : info.table_statement_variables) { + auto name = "t_" + variable->get_name(); + printer->add_line("{}* {};"_format(float_type, name)); + global_variables.push_back(make_symbol(name)); + } } if (info.vectorize) { @@ -1571,7 +1790,8 @@ void CodegenCVisitor::print_mechanism_register() { } if (info.emit_table_thread()) { - printer->add_line("_nrn_thread_table_reg(mech_type, check_table_thread);"); + auto name = method_name("check_table_thread"); + printer->add_line("_nrn_thread_table_reg(mech_type, {});"_format(name)); } /// register read/write callbacks for pointers @@ -1845,6 +2065,12 @@ void CodegenCVisitor::print_global_variable_setup() { if (info.table_count > 0) { auto name = get_variable_name("usetable"); printer->add_line("{} = 1;"_format(name)); + + for(auto &variable : info.table_statement_variables) { + auto name = get_variable_name("t_" + variable->get_name()); + int num_values = variable->get_num_values(); + printer->add_line("{} = (double*) mem_alloc({}, sizeof(double));"_format(name, num_values)); + } } printer->add_newline(); @@ -1994,23 +2220,19 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("ml->instance = (void*) inst;"); printer->end_block(); - printer->add_newline(); + printer->add_newline(2); + printer->add_line("/** cleanup mechanism instance variables */"); + printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); + printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); if (range_variable_setup_required()) { - printer->add_newline(2); - printer->add_line("/** cleanup mechanism instance variables */"); - printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); - if (variables_to_free.empty()) { - printer->add_line("// do nothing"); - } else { - printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); - for (auto& var : variables_to_free) { - printer->add_line("mem_free((void*)inst->{});"_format(var)); - } + for (auto &var : variables_to_free) { + printer->add_line("mem_free((void*)inst->{});"_format(var)); } - printer->end_block(); - printer->add_newline(); } + printer->add_line("mem_free((void*)inst);"); + printer->end_block(); + printer->add_newline(); } @@ -2970,6 +3192,7 @@ void CodegenCVisitor::codegen_all() { print_nrn_alloc(); codegen_compute_functions(); + print_check_table_thread_function(); print_mechanism_register(); codegen_namespace_end(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index f5a45fc262..822e7d90bf 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -15,6 +15,29 @@ using namespace fmt::literals; +/** + * \class TableStatementVisitor + * \brief Helper visitor to return table statement in given node + */ +class TableStatementVisitor : public AstVisitor { +private: + + /// vector containing all table statements + std::vector statements; + +public: + TableStatementVisitor() = default; + + void visit_table_statement(TableStatement* node) override { + statements.push_back(node); + } + + std::vector get_statements() { + return statements; + } +}; + + /** * \class CodegenCVisitor * \brief Visitor for printing c code compatible with legacy api @@ -139,7 +162,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// parameters for external functions - std::string external_method_parameters(); + std::string external_method_parameters(bool table = false); /// arguments for register_mech or point_register_mech function @@ -369,6 +392,18 @@ class CodegenCVisitor : public CodegenBaseVisitor { void print_function_prototypes(); + /// print check_function() for function/procedure using table + void print_table_check_function(ast::Block* node); + + + /// print replacement function for function/procedure using table + void print_table_replacement_function(ast::Block* node); + + + /// print check_table functions + void print_check_table_thread_function(); + + /// nmodl function definition void print_function(ast::FunctionBlock* node); @@ -377,6 +412,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { void print_procedure(ast::ProcedureBlock* node); + /// print nmodl function or procedure (common code) + void print_function_or_procedure(ast::Block* node, std::string& name); + + /// thread related memory allocation and deallocation callbacks void print_thread_memory_callbacks(); @@ -393,7 +432,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// prototype declarations of functions and procedures template - void print_function_declaration(T& node); + void print_function_declaration(T& node, std::string name); /// initial block @@ -484,6 +523,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// common code for global functions like nrn_init, nrn_cur and nrn_state void print_global_function_common_code(BlockType type); + /// nrn_init function definition void print_nrn_init(); @@ -564,14 +604,14 @@ class CodegenCVisitor : public CodegenBaseVisitor { * * If there is an argument with name (say alpha) same as range variable (say alpha), * we want avoid it being printed as instance->alpha. And hence we disable variable - * name lookup during prototype declaration. + * name lookup during prototype declaration. Note that the name of procedure can be + * different in case of table statement. */ template -void CodegenCVisitor::print_function_declaration(T& node) { +void CodegenCVisitor::print_function_declaration(T& node, std::string name) { enable_variable_name_lookup = false; - /// name, internal and user provided arguments - auto name = node->get_name(); + /// internal and user provided arguments auto internal_params = internal_method_parameters(); auto params = node->get_arguments(); diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index ac3a05d0b4..099febb275 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -298,6 +298,13 @@ namespace codegen { /// this is required while printing them in initlist function std::vector prime_variables_by_order; + /// table variables + std::vector table_statement_variables; + std::vector table_dependent_variables; + + /// function or procedures with table statement + std::vector functions_with_table; + /// represent conductance statements used in mod file std::vector conductances; diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 51e93f28e3..757ee0f023 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -134,6 +134,12 @@ def ast_classes_declaration(self): if node.is_program_node(): self.write_line("symtab::ModelSymbolTable model_symtab;") + if node.can_use_table(): + self.write_line("") + self.write_line("bool has_table = false;") + self.write_line("bool use_table() { return has_table; }") + self.write_line("void use_table(bool use) { has_table = use; }") + self.write_line("") if members: @@ -242,7 +248,11 @@ def ast_classes_declaration(self): if node.is_base_block_node(): self.write_line("virtual std::shared_ptr get_statement_block() {") self.write_line(' throw std::runtime_error("get_statement_node not implemented");') - self.write_line("}") + self.write_line("}", newline=2) + + self.write_line("virtual ArgumentVector& get_arguments() {") + self.write_line(' throw std::runtime_error("get_arguments not implemented");') + self.write_line("}", newline=2) # if node is of enum type then return enum value # TODO: hardcoded Names diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 2f16b7038b..42f8edd605 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -879,7 +879,7 @@ prefix: {value: "(", force: true} suffix: {value: ")", force: true} separator: ", " - getter: {name: get_arguments} + getter: {name: get_arguments, override: true} - unit: type: Unit optional: true @@ -900,7 +900,7 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - getter: {name: get_arguments} + getter: {name: get_arguments, override: true} - unit: type: Unit optional: true @@ -916,7 +916,7 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - getter: {name: get_arguments} + getter: {name: get_arguments, override: true} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 5c7550d826..bbfa7af969 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -84,6 +84,10 @@ # nodes which need extra handling to augument symbol table SYMBOL_TABLE_HELPER_NODES = ["TableStatement"] +# nodes where table can be used +NODE_WITH_TABLE = ["FunctionBlock", + "ProcedureBlock"] + # blocks defining global variables GLOBAL_BLOCKS = ["NeuronBlock", "ParamBlock", diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index d258d31f4a..20992cbf03 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -273,5 +273,8 @@ def is_symtab_method_required(self): return method_required + def can_use_table(self): + return True if self.class_name in NODE_WITH_TABLE else False + def is_base_class_number_node(self): return True if self.base_class == NUMBER_NODE else False diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index e69631ba10..e170aa7ba1 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -81,6 +81,9 @@ namespace symtab { /// number of elements int length = 1; + // number of values in case of table variable + int num_values = 0; + public: Symbol() = delete; @@ -225,6 +228,14 @@ namespace symtab { return length; } + int get_num_values() { + return num_values; + } + + void set_num_values(int n) { + num_values = n; + } + std::string get_original_name() { return renamed_from; } diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index e6858032dc..cfa906e619 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -197,15 +197,17 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, std::string * @todo : we assume table statement follows variable declaration */ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { - auto update_symbol = [this](NameVector& variables, NmodlInfo property) { + auto update_symbol = [this](NameVector& variables, NmodlInfo property, int num_values) { for(auto &var : variables) { auto name = var->get_name(); auto symbol = modsymtab->lookup(name); if (symbol) { symbol->add_property(property); + symbol->set_num_values(num_values); } } }; - update_symbol(node->table_vars, NmodlInfo::table_statement_var); - update_symbol(node->depend_vars, NmodlInfo::table_dependent_var); -} \ No newline at end of file + int num_values = node->with->eval() + 1; + update_symbol(node->table_vars, NmodlInfo::table_statement_var, num_values); + update_symbol(node->depend_vars, NmodlInfo::table_dependent_var, num_values); +} From eac47dec4639d3771f55ddad296a8e5d4969b98e Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 28 Dec 2018 23:17:55 +0100 Subject: [PATCH 094/871] Various bug fixes in master: - initial block needs to be skipped if _nrn_skip_initmodel is true - fix use of t vs nt->_t - fix weight index in print_net_receive_buffer_kernel - artificial cell net_receieve doesnt have t argument, use nt->_t Change-Id: Id18bb16a4f2b3e6dde5ccd87ee474add4e75ae73 NMODL Repo SHA: BlueBrain/nmodl@423cfc22378a8d640e0b912a4fdbe3d864a64a15 --- src/nmodl/codegen/c/codegen_c_visitor.cpp | 49 +++++++++++++++++++---- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index ed6808513b..d5f08f33a9 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -1426,6 +1426,16 @@ std::string CodegenCVisitor::get_variable_name(std::string name, bool use_instan return ion_shadow_variable_name(*s); } + if (name == "dt") { + return "nt->_dt"; + } + + /// t in net_receive method is an argument to function and hence it should + /// ne used instead of nt->_t which is current time of thread + if (name == "t" && !printing_net_receive) { + return "nt->_t"; + } + /// otherwise return original name return name; } @@ -2313,6 +2323,7 @@ void CodegenCVisitor::print_nrn_init() { codegen = true; printer->add_newline(2); printer->add_line("/** initialize channel */"); + print_global_function_common_code(BlockType::Initial); if (info.derivimplicit_used) { printer->add_newline(); @@ -2327,16 +2338,24 @@ void CodegenCVisitor::print_nrn_init() { // clang-format on } + printer->start_block("if (_nrn_skip_initmodel == 0)"); + print_channel_iteration_tiling_block_begin(BlockType::Initial); print_channel_iteration_block_begin(); + print_post_channel_iteration_common_code(); + if (info.net_receive_node != nullptr) { printer->add_line("{} = -1e20;"_format(get_variable_name("tsave"))); } + print_initial_block(info.initial_node); print_channel_iteration_block_end(); + print_shadow_reduction_statements(); print_channel_iteration_tiling_block_end(); + printer->end_block(); + printer->add_newline(); if (info.derivimplicit_used) { printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); @@ -2412,6 +2431,9 @@ void CodegenCVisitor::print_watch_activate() { */ void CodegenCVisitor::print_watch_check() { + if (info.watch_statements.empty()) { + return; + } codegen = true; printer->add_newline(2); printer->add_line("/** routine to check watch activation */"); @@ -2443,8 +2465,9 @@ void CodegenCVisitor::print_watch_check() { auto point_process = get_variable_name("point_process"); printer->add_indent(); printer->add_text("net_send_buffering("); + auto t = get_variable_name("t"); printer->add_text( - "ml->_net_send_buffer, 0, {}, 0, {}, t+0.0, "_format(tqitem, point_process)); + "ml->_net_send_buffer, 0, {}, 0, {}, {}+0.0, "_format(tqitem, point_process, t)); watch->value->accept(this); printer->add_text(");"); printer->add_newline(); @@ -2531,11 +2554,12 @@ void CodegenCVisitor::print_net_send_call(FunctionCall* node) { /// artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text("artcell_net_send(&{}, {}, {}, t+"_format(tqitem, weight_index, pnt)); + printer->add_text("artcell_net_send(&{}, {}, {}, nt->_t+"_format(tqitem, weight_index, pnt)); } else { auto point_process = get_variable_name("point_process"); + std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text("ml->_net_send_buffer, 0, {}, {}, {}, t+"_format(tqitem, weight_index, point_process)); + printer->add_text("ml->_net_send_buffer, 0, {}, {}, {}, {}+"_format(tqitem, weight_index, point_process, t)); } // clang-format off print_vector_elements(arguments, ", "); @@ -2557,11 +2581,12 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { /// artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text("artcell_net_move(&{}, {}, {}, t+"_format(tqitem, weight_index, pnt)); + printer->add_text("artcell_net_move(&{}, {}, {}, nt->_t+"_format(tqitem, weight_index, pnt)); } else { auto point_process = get_variable_name("point_process"); + std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, t+"_format(tqitem, weight_index, point_process)); + printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, {}+"_format(tqitem, weight_index, point_process, t)); } // clang-format off print_vector_elements(arguments, ", "); @@ -2649,9 +2674,9 @@ void CodegenCVisitor::print_net_receive_buffer_kernel() { printer->add_line(" for (int j = start; j < end; j++) {"); printer->add_line(" int index = nrb->_nrb_index[j];"); printer->add_line(" int offset = nrb->_pnt_index[index];"); - printer->add_line(" double t = nrb->_nrb_t[i];"); - printer->add_line(" int weight_index = nrb->_weight_index[i];"); - printer->add_line(" double flag = nrb->_nrb_flag[i];"); + printer->add_line(" double t = nrb->_nrb_t[index];"); + printer->add_line(" int weight_index = nrb->_weight_index[index];"); + printer->add_line(" double flag = nrb->_nrb_flag[index];"); printer->add_line(" Point_process* point_process = nt->pntprocs + offset;"); printer->add_line(" {}(t, point_process, weight_index, flag);"_format(net_receive)); printer->add_line(" }"); @@ -2720,6 +2745,7 @@ void CodegenCVisitor::print_net_receive() { printing_net_receive = true; std::string function_name = method_name("net_receive_kernel"); std::string function_arguments = "double t, Point_process* pnt, int weight_index, double flag"; + if (info.artificial_cell) { function_name = method_name("net_receive"); function_arguments = "Point_process* pnt, int weight_index, double flag"; @@ -2729,6 +2755,13 @@ void CodegenCVisitor::print_net_receive() { printer->add_newline(2); printer->start_block("static void {}({}) "_format(function_name, function_arguments)); print_net_receive_common_code(node); + + if (info.artificial_cell) { + printer->add_line("double t = nt->_t;"); + } + + printer->add_line("{} = t;"_format(get_variable_name("tsave"))); + printer->add_indent(); node->get_statement_block()->accept(this); printer->add_newline(); From d06e7203a692c7d11034597a801f566f43aaa0d0 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 29 Dec 2018 12:05:30 +0100 Subject: [PATCH 095/871] Remove use_table from AST nodes Change-Id: Iba99a235d4b52191fb149d7e212907ce38c71d82 NMODL Repo SHA: BlueBrain/nmodl@9d567979e57d5581b48380fbe01a922844c75269 --- src/nmodl/codegen/base/codegen_helper_visitor.cpp | 2 -- src/nmodl/codegen/c/codegen_c_visitor.cpp | 2 +- src/nmodl/codegen/codegen_info.cpp | 10 ++++++++++ src/nmodl/codegen/codegen_info.hpp | 2 ++ src/nmodl/language/ast_printer.py | 6 ------ src/nmodl/language/node_info.py | 4 ---- src/nmodl/language/nodes.py | 3 --- 7 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index f179643365..13e61ed1cb 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -458,7 +458,6 @@ void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { node->visit_children(this); if(table_statement_used) { table_statement_used = false; - node->use_table(true); info.functions_with_table.push_back(node); } } @@ -469,7 +468,6 @@ void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { node->visit_children(this); if(table_statement_used) { table_statement_used = false; - node->use_table(true); info.functions_with_table.push_back(node); } } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index d5f08f33a9..cf499e8e1a 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -912,7 +912,7 @@ void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { codegen = true; auto name = node->get_name(); - if (node->use_table()) { + if (info.function_uses_table(name)) { auto new_name = "f_" + name; print_function_or_procedure(node, new_name); print_table_check_function(node); diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index e2b08f3a3f..75bc52af10 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -55,3 +55,13 @@ bool CodegenInfo::is_current(const std::string& name) { } return false; } + + +bool CodegenInfo::function_uses_table(std::string& name) const { + for(auto& function: functions_with_table) { + if(name == function->get_name()) { + return true; + } + } + return false; +} \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 099febb275..f3cadeb7d6 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -359,6 +359,8 @@ namespace codegen { bool emit_table_thread() const { return (table_count > 0 && vectorize == true); } + + bool function_uses_table(std::string& name) const; }; }; // namespace codegen diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 757ee0f023..aa7fb4b6d5 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -134,12 +134,6 @@ def ast_classes_declaration(self): if node.is_program_node(): self.write_line("symtab::ModelSymbolTable model_symtab;") - if node.can_use_table(): - self.write_line("") - self.write_line("bool has_table = false;") - self.write_line("bool use_table() { return has_table; }") - self.write_line("void use_table(bool use) { has_table = use; }") - self.write_line("") if members: diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index bbfa7af969..5c7550d826 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -84,10 +84,6 @@ # nodes which need extra handling to augument symbol table SYMBOL_TABLE_HELPER_NODES = ["TableStatement"] -# nodes where table can be used -NODE_WITH_TABLE = ["FunctionBlock", - "ProcedureBlock"] - # blocks defining global variables GLOBAL_BLOCKS = ["NeuronBlock", "ParamBlock", diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 20992cbf03..d258d31f4a 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -273,8 +273,5 @@ def is_symtab_method_required(self): return method_required - def can_use_table(self): - return True if self.class_name in NODE_WITH_TABLE else False - def is_base_class_number_node(self): return True if self.base_class == NUMBER_NODE else False From e9df2fac3d238caa152890e9cbc99b002303a5ca Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 29 Dec 2018 12:55:08 +0100 Subject: [PATCH 096/871] clang-tidy and clang-format changes Change-Id: Ia2aff8ec261fb4634ada20dae9d55c11a51a7dc6 NMODL Repo SHA: BlueBrain/nmodl@5771ba9b96c66feb97962ef264d7a54cea04e08b --- .../codegen/base/codegen_base_visitor.cpp | 4 +- .../codegen/base/codegen_helper_visitor.cpp | 12 +- .../codegen/c-cuda/codegen_c_cuda_visitor.cpp | 4 +- .../codegen/c-cuda/codegen_c_cuda_visitor.hpp | 2 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 243 +++++++++--------- src/nmodl/codegen/c/codegen_c_visitor.hpp | 11 +- src/nmodl/codegen/codegen_info.cpp | 4 +- src/nmodl/language/visitors_printer.py | 4 +- src/nmodl/nmodl/main.cpp | 4 +- src/nmodl/symtab/symbol_table.cpp | 22 +- src/nmodl/symtab/symbol_table.hpp | 4 +- src/nmodl/utils/logger.cpp | 6 +- src/nmodl/visitors/symtab_visitor_helper.cpp | 12 +- test/nmodl/transpiler/visitor/visitor.cpp | 6 +- 14 files changed, 171 insertions(+), 167 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 29fbbee57c..72fe1f2350 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -617,7 +617,7 @@ void CodegenBaseVisitor::update_index_semantics() { * Number of semantics for watch is one greater than number of * actual watch statements in the mod file */ - if (info.watch_statements.size() > 0) { + if (!info.watch_statements.empty()) { for (int i = 0; i < info.watch_statements.size() + 1; i++) { info.semantics.emplace_back(index++, "watch", 1); } @@ -755,7 +755,7 @@ std::vector CodegenBaseVisitor::get_int_variables() { * used in coreneuron compared to actual watch statements for compatibility * with neuron (which uses one extra Datum variable) */ - if (info.watch_statements.size() > 0) { + if (!info.watch_statements.empty()) { for (int i = 0; i < info.watch_statements.size() + 1; i++) { variables.emplace_back(make_symbol("watch{}"_format(i)), false, false, true); } diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 13e61ed1cb..958f3de6fd 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -146,7 +146,8 @@ void CodegenHelperVisitor::find_non_range_variables() { * todo : move this to separate pass */ if (!variables.empty()) { - std::string message = "Global variables are updated in compute blocks, convert them to range? : "; + std::string message = + "Global variables are updated in compute blocks, convert them to range? : "; throw std::runtime_error(message + variables); } @@ -171,9 +172,8 @@ void CodegenHelperVisitor::find_non_range_variables() { for (auto& var : vars) { // some variables like area and diam are declared in parameter // block but they are not global - if (var->get_name() == diam_variable - || var->get_name() == area_variable - || var->has_properties(NmodlInfo::extern_neuron_variable)) { + if (var->get_name() == diam_variable || var->get_name() == area_variable || + var->has_properties(NmodlInfo::extern_neuron_variable)) { continue; } @@ -456,7 +456,7 @@ void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { info.procedures.push_back(node); node->visit_children(this); - if(table_statement_used) { + if (table_statement_used) { table_statement_used = false; info.functions_with_table.push_back(node); } @@ -466,7 +466,7 @@ void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { info.functions.push_back(node); node->visit_children(this); - if(table_statement_used) { + if (table_statement_used) { table_statement_used = false; info.functions_with_table.push_back(node); } diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp index dc4db8e0ea..65b7e94fde 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp @@ -47,7 +47,9 @@ std::string CodegenCCudaVisitor::compute_method_name(BlockType type) { } -void CodegenCCudaVisitor::print_atomic_op(std::string lhs, std::string op, std::string rhs) { +void CodegenCCudaVisitor::print_atomic_op(const std::string& lhs, + const std::string& op, + const std::string& rhs) { std::string function; if (op == "+") { function = "atomicAdd"; diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp index 5a51c46f07..a5e8e39276 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp @@ -13,7 +13,7 @@ * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) */ class CodegenCCudaVisitor : public CodegenCVisitor { - void print_atomic_op(std::string lhs, std::string op, std::string rhs); + void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); protected: /// name of the code generation backend diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index cf499e8e1a..0b8f23c232 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -204,7 +204,7 @@ std::vector CodegenCVisitor::ion_write_statements(BlockType * Here we process if we are processing verbatim block at global scope. */ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { - std::string name = token; + const std::string& name = token; /** * If given token is procedure name and if it's defined @@ -229,7 +229,7 @@ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { * names with Instance because arguments are provided from coreneuron * and they are missing inst. */ - auto use_instance = printing_top_verbatim_blocks ? false : true; + auto use_instance = !printing_top_verbatim_blocks; return get_variable_name(token, use_instance); } @@ -704,161 +704,154 @@ static TableStatement* get_table_statement(ast::Block* node) { TableStatementVisitor v; node->accept(&v); auto table_statements = v.get_statements(); - if (table_statements.empty()) { - throw std::runtime_error("Could not find table statement in " + node->get_name()); - } else if (table_statements.size() > 1) { - throw std::runtime_error("More than one table statement found in " + node->get_name()); + auto nstatements = table_statements.size(); + if (nstatements != 1) { + auto message = + "One table statement expected in {} found {}"_format(node->get_name(), nstatements); + throw std::runtime_error(message); } return table_statements.front(); } void CodegenCVisitor::print_table_check_function(ast::Block* node) { - printer->add_newline(2); auto name = node->get_name(); auto internal_params = internal_method_parameters(); - - print_device_method_annotation(); - printer->add_indent(); - printer->add_text("void check_{}({})"_format(method_name(name), internal_params)); - printer->start_block(); - auto statement = get_table_statement(node); auto table_variables = statement->table_vars; auto depend_variables = statement->depend_vars; auto from = statement->from; auto to = statement->to; - auto with = statement->with->eval(); auto use_table_var = get_variable_name("usetable"); auto float_type = default_float_data_type(); - auto tmin_name = get_variable_name("tmin_"+name); - auto mfac_name = get_variable_name("mfac_"+name); - - printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); - printer->add_line(" return;"); - printer->add_line("}"); - - printer->add_line("static bool make_table = true;"); - for(auto& variable : depend_variables) { - printer->add_line("static {} save_{};"_format(float_type, variable->get_name())); - } + auto tmin_name = get_variable_name("tmin_" + name); + auto mfac_name = get_variable_name("mfac_" + name); - for(auto& variable : depend_variables) { - auto name = variable->get_name(); - auto instance_name = get_variable_name(name); - printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); - printer->add_line(" make_table = true;"); + printer->add_newline(2); + print_device_method_annotation(); + printer->start_block("void check_{}({})"_format(method_name(name), internal_params)); + { + printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); + printer->add_line(" return;"); printer->add_line("}"); - } - - printer->add_line("if (make_table) {}"_format("{")); - printer->increase_indent(); - - printer->add_line("make_table = false;"); - printer->add_indent(); - printer->add_text("{} = "_format(tmin_name)); - from->accept(this); - printer->add_text(";"); - printer->add_newline(); + printer->add_line("static bool make_table = true;"); + for (auto& variable : depend_variables) { + printer->add_line("static {} save_{};"_format(float_type, variable->get_name())); + } - printer->add_indent(); - printer->add_text("double tmax = "); - to->accept(this); - printer->add_text(";"); - printer->add_newline(); + for (auto& variable : depend_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); + printer->add_line(" make_table = true;"); + printer->add_line("}"); + } + printer->start_block("if (make_table)"); + { + printer->add_line("make_table = false;"); - printer->add_line("double dx = (tmax-{})/{}.0;"_format(tmin_name, with)); - printer->add_line("{} = 1.0/dx;"_format(mfac_name)); + printer->add_indent(); + printer->add_text("{} = "_format(tmin_name)); + from->accept(this); + printer->add_text(";"); + printer->add_newline(); - printer->add_line("int i = 0;"); - printer->add_line("double x = 0;"); - printer->add_line("for(i = 0, x = {}; i < {}; x += dx, i++) {}"_format(tmin_name, with+1, "{")); - auto function_name = method_name("f_"+name); - printer->add_line(" {}({}, x);"_format(function_name, internal_method_arguments())); - for(auto& variable : table_variables) { - auto name = variable->get_name(); - auto instance_name = get_variable_name(name); - auto table_name = get_variable_name("t_"+name); - printer->add_line(" {}[i] = {};"_format(table_name, instance_name)); - } - printer->add_line("}"); + printer->add_indent(); + printer->add_text("double tmax = "); + to->accept(this); + printer->add_text(";"); + printer->add_newline(); + + + printer->add_line("double dx = (tmax-{})/{}.0;"_format(tmin_name, with)); + printer->add_line("{} = 1.0/dx;"_format(mfac_name)); + + printer->add_line("int i = 0;"); + printer->add_line("double x = 0;"); + printer->add_line( + "for(i = 0, x = {}; i < {}; x += dx, i++) {}"_format(tmin_name, with + 1, "{")); + auto function = method_name("f_" + name); + printer->add_line(" {}({}, x);"_format(function, internal_method_arguments())); + for (auto& variable : table_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_" + name); + printer->add_line(" {}[i] = {};"_format(table_name, instance_name)); + } + printer->add_line("}"); - for(auto& variable : depend_variables) { - auto name = variable->get_name(); - auto instance_name = get_variable_name(name); - printer->add_line("save_{} = {};"_format(name, instance_name)); + for (auto& variable : depend_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + printer->add_line("save_{} = {};"_format(name, instance_name)); + } + } + printer->end_block(); } - - printer->decrease_indent(); - printer->add_line("}"); - printer->end_block(); printer->add_newline(); } void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { - printer->add_newline(2); auto name = node->get_name(); - print_function_declaration(node, name); - printer->add_text(" "); - printer->start_block(); - auto statement = get_table_statement(node); auto table_variables = statement->table_vars; auto with = statement->with->eval(); auto use_table_var = get_variable_name("usetable"); auto float_type = default_float_data_type(); - auto tmin_name = get_variable_name("tmin_"+name); - auto mfac_name = get_variable_name("mfac_"+name); - auto function_name = method_name("f_"+name); + auto tmin_name = get_variable_name("tmin_" + name); + auto mfac_name = get_variable_name("mfac_" + name); + auto function_name = method_name("f_" + name); - printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); - printer->add_line(" {}({}, arg_v);"_format(function_name, internal_method_arguments())); - printer->add_line(" return 0;"); - printer->add_line("}"); + printer->add_newline(2); + print_function_declaration(node, name); + printer->start_block(); + { + printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); + printer->add_line(" {}({}, arg_v);"_format(function_name, internal_method_arguments())); + printer->add_line(" return 0;"); + printer->add_line("}"); - printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); - printer->add_line("if (isnan(xi)) {"); - for(auto& variable : table_variables) { - auto name = variable->get_name(); - auto instance_name = get_variable_name(name); - printer->add_line(" {} = xi;"_format(instance_name)); - } - printer->add_line(" return 0;"); - printer->add_line("}"); + printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); + printer->add_line("if (isnan(xi)) {"); + for (auto& var : table_variables) { + auto name = get_variable_name(var->get_name()); + printer->add_line(" {} = xi;"_format(name)); + } + printer->add_line(" return 0;"); + printer->add_line("}"); - printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); - printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); - for(auto& variable : table_variables) { - auto name = variable->get_name(); - auto instance_name = get_variable_name(name); - auto table_name = get_variable_name("t_"+name); - printer->add_line(" {} = {}[index];"_format(instance_name, table_name)); - } - printer->add_line(" return 0;"); - printer->add_line("}"); - printer->add_newline(); + printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); + printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); + for (auto& variable : table_variables) { + auto name = variable->get_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_" + name); + printer->add_line(" {} = {}[index];"_format(instance_name, table_name)); + } + printer->add_line(" return 0;"); + printer->add_line("}"); - printer->add_line("int i = int(xi);"); - printer->add_line("double theta = xi - double(i);"); - for(auto& variable : table_variables) { - auto name = variable->get_name(); - auto instance_name = get_variable_name(name); - auto table_name = get_variable_name("t_"+name); - printer->add_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);"_format(instance_name, table_name)); - } + printer->add_line("int i = int(xi);"); + printer->add_line("double theta = xi - double(i);"); + for (auto& var : table_variables) { + auto instance_name = get_variable_name(var->get_name()); + auto table_name = get_variable_name("t_" + var->get_name()); + printer->add_line( + "{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);"_format(instance_name, table_name)); + } - printer->add_line("return 0;"); + printer->add_line("return 0;"); + } printer->end_block(); printer->add_newline(); } void CodegenCVisitor::print_check_table_thread_function() { - if (info.table_count == 0) { return; } @@ -874,7 +867,7 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" double v = 0;"); printer->add_line(" IonCurVar ionvar = {0};"); - for(auto& function: info.functions_with_table) { + for (auto& function : info.functions_with_table) { auto name = method_name("check_" + function->get_name()); auto arguments = internal_method_arguments(); printer->add_line(" {}({});"_format(name, arguments)); @@ -973,11 +966,10 @@ std::string CodegenCVisitor::external_method_arguments() { std::string CodegenCVisitor::external_method_parameters(bool table) { if (table) { return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, int tml_id"; - } else { - return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, double v"; + "ThreadDatum* thread, NrnThread* nt, int tml_id"; } + return "int id, int pnodecount, double* data, Datum* indexes, " + "ThreadDatum* thread, NrnThread* nt, double v"; } @@ -1093,7 +1085,7 @@ std::pair CodegenCVisitor::write_ion_variable_name(std } -std::string CodegenCVisitor::conc_write_statement(std::string ion_name, +std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, const std::string& concentration, int index) { auto conc_var_name = get_variable_name("ion_" + concentration); @@ -1326,7 +1318,7 @@ std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_in std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, - std::string name, + const std::string& name, bool use_instance) { auto position = position_of_int_var(name); auto num_int = num_int_variable(); @@ -1427,7 +1419,7 @@ std::string CodegenCVisitor::get_variable_name(std::string name, bool use_instan } if (name == "dt") { - return "nt->_dt"; + return "nt->_dt"; } /// t in net_receive method is an argument to function and hence it should @@ -1611,15 +1603,15 @@ void CodegenCVisitor::print_mechanism_global_structure() { printer->add_line("double usetable;"); global_variables.push_back(make_symbol("usetable")); - for(auto& block: info.functions_with_table) { + for (auto& block : info.functions_with_table) { auto name = block->get_name(); printer->add_line("{} tmin_{};"_format(float_type, name)); printer->add_line("{} mfac_{};"_format(float_type, name)); - global_variables.push_back(make_symbol("tmin_"+name)); - global_variables.push_back(make_symbol("mfac_"+name)); + global_variables.push_back(make_symbol("tmin_" + name)); + global_variables.push_back(make_symbol("mfac_" + name)); } - for(auto &variable : info.table_statement_variables) { + for (auto& variable : info.table_statement_variables) { auto name = "t_" + variable->get_name(); printer->add_line("{}* {};"_format(float_type, name)); global_variables.push_back(make_symbol(name)); @@ -2076,10 +2068,11 @@ void CodegenCVisitor::print_global_variable_setup() { auto name = get_variable_name("usetable"); printer->add_line("{} = 1;"_format(name)); - for(auto &variable : info.table_statement_variables) { + for (auto& variable : info.table_statement_variables) { auto name = get_variable_name("t_" + variable->get_name()); int num_values = variable->get_num_values(); - printer->add_line("{} = (double*) mem_alloc({}, sizeof(double));"_format(name, num_values)); + printer->add_line( + "{} = (double*) mem_alloc({}, sizeof(double));"_format(name, num_values)); } } @@ -2236,7 +2229,7 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); if (range_variable_setup_required()) { - for (auto &var : variables_to_free) { + for (auto& var : variables_to_free) { printer->add_line("mem_free((void*)inst->{});"_format(var)); } } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 822e7d90bf..7efdf02a07 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -20,12 +20,11 @@ using namespace fmt::literals; * \brief Helper visitor to return table statement in given node */ class TableStatementVisitor : public AstVisitor { -private: - + private: /// vector containing all table statements std::vector statements; -public: + public: TableStatementVisitor() = default; void visit_table_statement(TableStatement* node) override { @@ -91,7 +90,9 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// get variable name for int variable - std::string int_variable_name(IndexVariableInfo& symbol, std::string name, bool use_instance); + std::string int_variable_name(IndexVariableInfo& symbol, + const std::string& name, + bool use_instance); /// get variable name for global variable @@ -144,7 +145,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// function call / statement for nrn_wrote_conc - std::string conc_write_statement(std::string ion_name, + std::string conc_write_statement(const std::string& ion_name, const std::string& concentration, int index); diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 75bc52af10..807f9f9103 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -58,8 +58,8 @@ bool CodegenInfo::is_current(const std::string& name) { bool CodegenInfo::function_uses_table(std::string& name) const { - for(auto& function: functions_with_table) { - if(name == function->get_name()) { + for (auto& function : functions_with_table) { + if (name == function->get_name()) { return true; } } diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index a7de1cffbe..d2f046a619 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -194,7 +194,7 @@ def public_declaration(self): self.write_line(line, newline=2) # helper function for creating symbol table for blocks - line = "void setup_symbol_table(ast::AST *node, std::string name, bool is_global);" + line = "void setup_symbol_table(ast::AST *node, const std::string& name, bool is_global);" self.write_line(line, newline=2) # helper function for creating symbol table for global blocks of mod file @@ -202,7 +202,7 @@ def public_declaration(self): self.write_line(line, newline=2) # helper function for creating symbol table for non-global blocks (e.g. function, procedures) - line = "void setup_symbol_table_for_scoped_block(ast::Node *node, std::string name);" + line = "void setup_symbol_table_for_scoped_block(ast::Node *node, const std::string& name);" self.write_line(line, newline=2) # helper function to setup program symbol table diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index a212c6c21e..b19899e56e 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -22,7 +22,7 @@ #include "utils/common_utils.hpp" #include "utils/logger.hpp" -void ast_to_nmodl(ast::Program* ast, std::string filename) { +void ast_to_nmodl(ast::Program* ast, const std::string& filename) { NmodlPrintVisitor v(filename); v.visit_program(ast); logger->info("AST to NMODL transformation written to {}", filename); @@ -167,7 +167,7 @@ int main(int argc, const char* argv[]) { } } - if (error_count) { + if (error_count != 0) { logger->error("Code generation encountered {} errors", error_count); } else { logger->info("Code generation finished successfully"); diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 0afe6316ee..755109fa74 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -48,7 +48,7 @@ namespace symtab { auto symbol = lookup_in_scope(name); if (symbol != nullptr) { auto node = symbol->get_node(); - if (node) { + if (node != nullptr) { if (node->is_procedure_block() || node->is_function_block()) { return true; } @@ -336,16 +336,18 @@ namespace symtab { * them we simply append counter. * \todo We should add position information to make name unique */ - std::string ModelSymbolTable::get_unique_name(std::string name, AST* node, bool is_global) { + std::string ModelSymbolTable::get_unique_name(const std::string& name, + AST* node, + bool is_global) { static int block_counter = 0; - + std::string new_name(name); if (is_global) { - name = GLOBAL_SYMTAB_NAME; + new_name = GLOBAL_SYMTAB_NAME; } else if (node->is_statement_block() || node->is_solve_block() || node->is_before_block() || node->is_after_block()) { - name += std::to_string(block_counter++); + new_name += std::to_string(block_counter++); } - return name; + return new_name; } @@ -355,7 +357,7 @@ namespace symtab { * The same symbol table is returned so that visitor can store pointer to * symbol table within a node. */ - SymbolTable* ModelSymbolTable::enter_scope(std::string name, + SymbolTable* ModelSymbolTable::enter_scope(const std::string& name, AST* node, bool global, SymbolTable* node_symtab) { @@ -381,14 +383,14 @@ namespace symtab { } if (node_symtab == nullptr || !update_table) { - name = get_unique_name(name, node, global); - auto new_symtab = std::make_shared(name, node, global); + auto new_name = get_unique_name(name, node, global); + auto new_symtab = std::make_shared(new_name, node, global); new_symtab->set_parent_table(current_symtab); if (symtab == nullptr) { symtab = new_symtab; } if (current_symtab != nullptr) { - current_symtab->insert_table(name, new_symtab); + current_symtab->insert_table(new_name, new_symtab); } node_symtab = new_symtab.get(); } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index eab3b32830..7de11623c4 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -182,7 +182,7 @@ namespace symtab { SymbolTable* current_symtab = nullptr; /// return unique name by appending some counter value - std::string get_unique_name(std::string name, AST* node, bool is_global); + std::string get_unique_name(const std::string& name, AST* node, bool is_global); /// name of top level global symbol table const std::string GLOBAL_SYMTAB_NAME = "NMODL_GLOBAL"; @@ -211,7 +211,7 @@ namespace symtab { public: /// entering into new nmodl block - SymbolTable* enter_scope(std::string name, + SymbolTable* enter_scope(const std::string& name, AST* node, bool global, SymbolTable* node_symtab); diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 9747cfd92c..6030c7e501 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -1,4 +1,6 @@ #include +#include + #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" @@ -6,9 +8,9 @@ using logger_type = std::shared_ptr; struct Logger { logger_type logger; - Logger(std::string name, std::string pattern) { + Logger(const std::string& name, std::string pattern) { logger = spdlog::stdout_color_mt(name); - logger->set_pattern(pattern); + logger->set_pattern(std::move(pattern)); } }; diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index cfa906e619..2b70800662 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -1,9 +1,13 @@ +#include + #include "lexer/token_mapping.hpp" #include "visitors/symtab_visitor.hpp" // create symbol for given node -static std::shared_ptr create_symbol_for_node(Node* node, SymbolInfo property, bool under_state_block) { +static std::shared_ptr create_symbol_for_node(Node* node, + SymbolInfo property, + bool under_state_block) { ModToken token; auto token_ptr = node->get_token(); if (token_ptr != nullptr) { @@ -134,7 +138,7 @@ static void add_external_symbols(symtab::ModelSymbolTable* symtab) { } -void SymtabVisitor::setup_symbol_table(AST* node, std::string name, bool is_global) { +void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool is_global) { /// entering into new nmodl block auto symtab = modsymtab->enter_scope(name, node, is_global, node->get_symbol_table()); @@ -186,7 +190,7 @@ void SymtabVisitor::setup_symbol_table_for_global_block(Node* node) { } -void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, std::string name) { +void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, const std::string& name) { setup_symbol_table(node, name, false); } @@ -198,7 +202,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, std::string */ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { auto update_symbol = [this](NameVector& variables, NmodlInfo property, int num_values) { - for(auto &var : variables) { + for (auto& var : variables) { auto name = var->get_name(); auto symbol = modsymtab->lookup(name); if (symbol) { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index e492a8fcd0..e0c5a16ca3 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -41,7 +41,7 @@ TEST_CASE("Verbatim Visitor") { auto blocks = run_verbatim_visitor(text); REQUIRE(blocks.size() == 1); - REQUIRE(blocks.front().compare(" int a; ") == 0); + REQUIRE(blocks.front() == " int a; "); } SECTION("Multiple Blocks") { @@ -49,8 +49,8 @@ TEST_CASE("Verbatim Visitor") { auto blocks = run_verbatim_visitor(text); REQUIRE(blocks.size() == 2); - REQUIRE(blocks[0].compare(" int a; ") == 0); - REQUIRE(blocks[1].compare(" float b; ") == 0); + REQUIRE(blocks[0] == " int a; "); + REQUIRE(blocks[1] == " float b; "); } } From cefa08c4bc222371c081cdc04c5e3d0411f3d8fe Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sat, 29 Dec 2018 20:58:54 +0100 Subject: [PATCH 097/871] Namespace and ast generator cleanup : - removed using namespace from all source files - ast forward declarations are separated into ast_decl.hpp - Type renamed to AstNodeType - symtab::details moved to syminfo - NmodlInfo renamed to NmodlType - removed use of _list and _ptr in nmodl.yy and removed it from ast.hpp - SymbolInfo -> NmodlTypeFlag and SymbolStatus -> StatusFlag Change-Id: I894bee36ab3b43dd086efaf0d6aedb2aa89ed34d NMODL Repo SHA: BlueBrain/nmodl@8129b50a9bf633eb4a2ed2641541fdfad79fa363 --- src/nmodl/ast/ast_utils.hpp | 11 +- .../codegen/base/codegen_base_visitor.cpp | 10 +- .../codegen/base/codegen_helper_visitor.cpp | 140 ++++---- .../codegen/base/codegen_helper_visitor.hpp | 2 +- .../codegen/c-cuda/codegen_c_cuda_visitor.cpp | 5 +- .../c-openacc/codegen_c_acc_visitor.cpp | 2 +- .../c-openmp/codegen_c_omp_visitor.cpp | 2 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 20 +- src/nmodl/codegen/c/codegen_c_visitor.hpp | 6 +- src/nmodl/codegen/codegen_info.cpp | 2 +- src/nmodl/language/argument.py | 2 +- src/nmodl/language/ast_printer.py | 81 ++--- src/nmodl/language/code_generator.py | 7 +- src/nmodl/language/nmodl_printer.py | 2 +- src/nmodl/language/utils.py | 2 +- src/nmodl/language/visitors_printer.py | 42 +-- src/nmodl/lexer/main_c.cpp | 2 +- src/nmodl/lexer/main_nmodl.cpp | 12 +- src/nmodl/lexer/nmodl.ll | 3 +- src/nmodl/lexer/nmodl_utils.cpp | 2 +- src/nmodl/parser/nmodl.yy | 293 +++++++++-------- src/nmodl/symtab/symbol.cpp | 50 +-- src/nmodl/symtab/symbol.hpp | 48 ++- src/nmodl/symtab/symbol_properties.cpp | 82 ++--- src/nmodl/symtab/symbol_properties.hpp | 310 +++++++++--------- src/nmodl/symtab/symbol_table.cpp | 15 +- src/nmodl/symtab/symbol_table.hpp | 17 +- src/nmodl/utils/table_data.cpp | 2 +- src/nmodl/utils/table_data.hpp | 2 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 4 +- src/nmodl/visitors/cnexp_solve_visitor.hpp | 8 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 5 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 24 +- src/nmodl/visitors/inline_visitor.hpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 67 ++-- src/nmodl/visitors/perf_visitor.cpp | 37 ++- src/nmodl/visitors/perf_visitor.hpp | 74 ++--- src/nmodl/visitors/rename_visitor.cpp | 6 +- src/nmodl/visitors/symtab_visitor_helper.cpp | 31 +- src/nmodl/visitors/var_usage_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_visitor.cpp | 4 +- src/nmodl/visitors/verbatim_visitor.hpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- src/nmodl/visitors/visitor_utils.hpp | 8 +- test/nmodl/transpiler/lexer/tokens.cpp | 10 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 4 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 84 ++--- test/nmodl/transpiler/utils/test_utils.cpp | 2 +- test/nmodl/transpiler/utils/test_utils.hpp | 2 +- test/nmodl/transpiler/visitor/visitor.cpp | 16 +- 50 files changed, 788 insertions(+), 780 deletions(-) diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_utils.hpp index 3abc2842eb..9406c13454 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_utils.hpp @@ -3,6 +3,9 @@ #include +#include "ast/ast_decl.hpp" +#include "visitors/visitor.hpp" + #include "lexer/modtoken.hpp" #include "symtab/symbol_table.hpp" @@ -52,17 +55,13 @@ namespace ast { typedef enum { LTMINUSGT, LTLT, MINUSGT } ReactionOp; static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; - /* base class for all visitors implementation */ - class Visitor; - /* enum class for ast types */ enum class Type; /* define abstract base class for all AST nodes * this also serves to define the visitable objects. */ - class AST { - public: + struct AST { /* all AST nodes have a member which stores their * basetype (int, bool, none, object). Further type * information will come from the symbol table. @@ -72,7 +71,7 @@ namespace ast { /* all AST nodes provide visit children and accept methods */ virtual void visit_children(Visitor* v) = 0; virtual void accept(Visitor* v) = 0; - virtual Type get_type() = 0; + virtual AstNodeType get_type() = 0; virtual std::string get_type_name() = 0; virtual std::string get_name() { diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 72fe1f2350..4074be3d21 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -12,7 +12,9 @@ #include "parser/c11_driver.hpp" +using namespace ast; using namespace symtab; +using namespace syminfo; using namespace fmt::literals; using SymbolType = std::shared_ptr; @@ -451,7 +453,7 @@ bool CodegenBaseVisitor::need_semicolon(Statement* node) { // check if there is a function or procedure defined with given name bool CodegenBaseVisitor::defined_method(std::string name) { auto function = program_symtab->lookup(std::move(name)); - auto properties = NmodlInfo::function_block | NmodlInfo::procedure_block; + auto properties = NmodlType::function_block | NmodlType::procedure_block; return function && function->has_properties(properties); } @@ -472,7 +474,7 @@ std::string CodegenBaseVisitor::breakpoint_current(std::string current) { return current; } auto symtab = breakpoint->get_statement_block()->get_symbol_table(); - auto variables = symtab->get_variables_with_properties(NmodlInfo::local_var); + auto variables = symtab->get_variables_with_properties(NmodlType::local_var); for (auto& var : variables) { auto renamed_name = var->get_name(); auto original_name = var->get_original_name(); @@ -593,7 +595,7 @@ void CodegenBaseVisitor::update_index_semantics() { info.first_pointer_var_index = index; } int size = var->get_length(); - if (var->has_properties(NmodlInfo::pointer_var)) { + if (var->has_properties(NmodlType::pointer_var)) { info.semantics.emplace_back(index, "pointer", size); } else { info.semantics.emplace_back(index, "bbcorepointer", size); @@ -723,7 +725,7 @@ std::vector CodegenBaseVisitor::get_int_variables() { for (auto& var : info.pointer_variables) { auto name = var->get_name(); - if (var->has_properties(NmodlInfo::pointer_var)) { + if (var->has_properties(NmodlType::pointer_var)) { variables.emplace_back(make_symbol(name)); } else { variables.emplace_back(make_symbol(name), true); diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 958f3de6fd..c8c4e6aa53 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -6,8 +6,10 @@ #include +using namespace ast; using namespace codegen; using namespace symtab; +using namespace syminfo; using namespace fmt::literals; /** @@ -50,11 +52,11 @@ void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& */ void CodegenHelperVisitor::find_ion_variables() { /// name of the ions used - auto ion_vars = psymtab->get_variables_with_properties(NmodlInfo::useion); + auto ion_vars = psymtab->get_variables_with_properties(NmodlType::useion); /// read variables from all ions - auto read_ion_vars = psymtab->get_variables_with_properties(NmodlInfo::read_ion_var); + auto read_ion_vars = psymtab->get_variables_with_properties(NmodlType::read_ion_var); /// write variables from all ions - auto write_ion_vars = psymtab->get_variables_with_properties(NmodlInfo::write_ion_var); + auto write_ion_vars = psymtab->get_variables_with_properties(NmodlType::write_ion_var); /** * Check if given variable belongs to given ion. @@ -90,11 +92,11 @@ void CodegenHelperVisitor::find_ion_variables() { } /// once ions are populated, we can find all currents - auto vars = psymtab->get_variables_with_properties(NmodlInfo::nonspe_cur_var); + auto vars = psymtab->get_variables_with_properties(NmodlType::nonspe_cur_var); for (auto& var : vars) { info.currents.push_back(var->get_name()); } - vars = psymtab->get_variables_with_properties(NmodlInfo::electrode_cur_var); + vars = psymtab->get_variables_with_properties(NmodlType::electrode_cur_var); for (auto& var : vars) { info.currents.push_back(var->get_name()); } @@ -120,8 +122,8 @@ void CodegenHelperVisitor::find_non_range_variables() { * Top local variables are local variables appear in global scope. All local * variables in program symbol table are in global scope. */ - info.constant_variables = psymtab->get_variables_with_properties(NmodlInfo::constant_var); - info.top_local_variables = psymtab->get_variables_with_properties(NmodlInfo::local_var); + info.constant_variables = psymtab->get_variables_with_properties(NmodlType::constant_var); + info.top_local_variables = psymtab->get_variables_with_properties(NmodlType::local_var); /** * All global variables remain global if mod file is not marked thread safe. @@ -130,7 +132,7 @@ void CodegenHelperVisitor::find_non_range_variables() { std::string variables; - auto vars = psymtab->get_variables_with_properties(NmodlInfo::global_var); + auto vars = psymtab->get_variables_with_properties(NmodlType::global_var); for (auto& var : vars) { if (info.thread_safe && var->get_write_count() > 0) { var->mark_thread_safe(); @@ -159,21 +161,21 @@ void CodegenHelperVisitor::find_non_range_variables() { * and then sort them with neuron/mod2c order. */ // clang-format off - auto with = NmodlInfo::param_assign; - auto without = NmodlInfo::range_var - | NmodlInfo::dependent_def - | NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::read_ion_var - | NmodlInfo::write_ion_var; + auto with = NmodlType::param_assign; + auto without = NmodlType::range_var + | NmodlType::dependent_def + | NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::read_ion_var + | NmodlType::write_ion_var; // clang-format on vars = psymtab->get_variables(with, without); for (auto& var : vars) { // some variables like area and diam are declared in parameter // block but they are not global if (var->get_name() == diam_variable || var->get_name() == area_variable || - var->has_properties(NmodlInfo::extern_neuron_variable)) { + var->has_properties(NmodlType::extern_neuron_variable)) { continue; } @@ -233,7 +235,7 @@ void CodegenHelperVisitor::find_non_range_variables() { } /// find number of prime variables and total size - auto primes = psymtab->get_variables_with_properties(NmodlInfo::prime_name); + auto primes = psymtab->get_variables_with_properties(NmodlType::prime_name); info.num_primes = primes.size(); for (auto& variable : primes) { info.primes_size += variable->get_length(); @@ -241,15 +243,15 @@ void CodegenHelperVisitor::find_non_range_variables() { /// find pointer or bbcore pointer variables // clang-format off - auto properties = NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var; + auto properties = NmodlType::pointer_var + | NmodlType::bbcore_pointer_var; // clang-format on info.pointer_variables = psymtab->get_variables_with_properties(properties); // find special variables like diam, area // clang-format off - properties = NmodlInfo::dependent_def - | NmodlInfo::param_assign; + properties = NmodlType::dependent_def + | NmodlType::param_assign; vars = psymtab->get_variables_with_properties(properties); for (auto& var : vars) { if (var->get_name() == area_variable) { @@ -282,12 +284,12 @@ void CodegenHelperVisitor::find_range_variables() { * First come parameters which are range variables. */ // clang-format off - auto with = NmodlInfo::range_var - | NmodlInfo::param_assign; - auto without = NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::state_var; + auto with = NmodlType::range_var + | NmodlType::param_assign; + auto without = NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::state_var; // clang-format on info.range_parameter_vars = psymtab->get_variables(with, without); std::sort(info.range_parameter_vars.begin(), info.range_parameter_vars.end(), comparator); @@ -296,13 +298,13 @@ void CodegenHelperVisitor::find_range_variables() { * Second come dependent variables which are range variables. */ // clang-format off - with = NmodlInfo::range_var - | NmodlInfo::dependent_def; - without = NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::state_var - | NmodlInfo::param_assign; + with = NmodlType::range_var + | NmodlType::dependent_def; + without = NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::state_var + | NmodlType::param_assign; // clang-format on info.range_dependent_vars = psymtab->get_variables(with, without); std::sort(info.range_dependent_vars.begin(), info.range_dependent_vars.end(), comparator); @@ -316,12 +318,12 @@ void CodegenHelperVisitor::find_range_variables() { * \todo: need to validate with more models and mod2c details. */ // clang-format off - with = NmodlInfo::state_var; - without = NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::read_ion_var - | NmodlInfo::write_ion_var; + with = NmodlType::state_var; + without = NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::read_ion_var + | NmodlType::write_ion_var; // clang-format on info.state_vars = psymtab->get_variables(with, without); std::sort(info.state_vars.begin(), info.state_vars.end(), comparator); @@ -337,14 +339,14 @@ void CodegenHelperVisitor::find_range_variables() { * first get dependent definition without read ion variables */ // clang-format off - with = NmodlInfo::dependent_def; - without = NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::state_var - | NmodlInfo::range_var - | NmodlInfo::extern_neuron_variable - | NmodlInfo::read_ion_var; + with = NmodlType::dependent_def; + without = NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::state_var + | NmodlType::range_var + | NmodlType::extern_neuron_variable + | NmodlType::read_ion_var; // clang-format on info.dependent_vars = psymtab->get_variables(with, without); @@ -354,13 +356,13 @@ void CodegenHelperVisitor::find_range_variables() { * compiled anyway. */ // clang-format off - with = NmodlInfo::read_ion_var; - without = NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::state_var - | NmodlInfo::range_var - | NmodlInfo::extern_neuron_variable; + with = NmodlType::read_ion_var; + without = NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::state_var + | NmodlType::range_var + | NmodlType::extern_neuron_variable; // clang-format on auto variables = psymtab->get_variables(with, without); info.dependent_vars.insert(info.dependent_vars.end(), variables.begin(), variables.end()); @@ -371,18 +373,18 @@ void CodegenHelperVisitor::find_range_variables() { * treat them separately for ordering. */ // clang-format off - with = NmodlInfo::state_var; - without = NmodlInfo::global_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::range_var - | NmodlInfo::extern_neuron_variable; + with = NmodlType::state_var; + without = NmodlType::global_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::range_var + | NmodlType::extern_neuron_variable; // clang-format on variables = psymtab->get_variables(with, without); for (auto& variable : variables) { // clang-format off - auto properties = NmodlInfo::read_ion_var - | NmodlInfo::write_ion_var; + auto properties = NmodlType::read_ion_var + | NmodlType::write_ion_var; // clang-format on if (variable->has_properties(properties)) { info.ion_state_vars.push_back(variable); @@ -393,9 +395,9 @@ void CodegenHelperVisitor::find_range_variables() { void CodegenHelperVisitor::find_table_variables() { - auto property = NmodlInfo::table_statement_var; + auto property = NmodlType::table_statement_var; info.table_statement_variables = psymtab->get_variables_with_properties(property); - property = NmodlInfo::table_dependent_var; + property = NmodlType::table_dependent_var; info.table_dependent_variables = psymtab->get_variables_with_properties(property); } @@ -537,8 +539,8 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { auto name = assign_lhs->get_name(); auto symbol = psymtab->lookup(name); if (symbol != nullptr) { - auto is_prime = symbol->has_properties(NmodlInfo::prime_name); - auto from_state = symbol->has_any_status(Status::from_state); + auto is_prime = symbol->has_properties(NmodlType::prime_name); + auto from_state = symbol->has_any_status(syminfo::Status::from_state); if (is_prime || from_state) { if (from_state) { symbol = psymtab->lookup(name.substr(1, name.size())); @@ -593,7 +595,7 @@ void CodegenHelperVisitor::find_solve_node() { if (info.solve_node != nullptr) { return; } - auto symbols = psymtab->get_variables_with_properties(NmodlInfo::to_solve); + auto symbols = psymtab->get_variables_with_properties(NmodlType::to_solve); if (!symbols.empty()) { assert(symbols.size() == 1); info.solve_node = dynamic_cast(symbols.at(0)->get_node()); diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp index 96ca95b36c..ae737143ff 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -99,4 +99,4 @@ class CodegenHelperVisitor : public AstVisitor { virtual void visit_program(ast::Program* node) override; }; -#endif \ No newline at end of file +#endif diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp index 65b7e94fde..a5267a2a0a 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp @@ -6,6 +6,7 @@ using namespace fmt::literals; using namespace symtab; +using namespace syminfo; /****************************************************************************************/ @@ -22,10 +23,10 @@ bool CodegenCCudaVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { - if (symbol->has_properties(NmodlInfo::read_ion_var)) { + if (symbol->has_properties(NmodlType::read_ion_var)) { is_constant = true; } - if (symbol->has_properties(NmodlInfo::param_assign)) { + if (symbol->has_properties(NmodlType::param_assign)) { is_constant = true; } } diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp index cb492b55a2..4316fe6c47 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp @@ -110,4 +110,4 @@ void CodegenCAccVisitor::print_rhs_d_shadow_variables() { bool CodegenCAccVisitor::nrn_cur_reduction_loop_required() { return false; -} \ No newline at end of file +} diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp index aa93ea8a24..0f7a074cdc 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp @@ -92,4 +92,4 @@ bool CodegenCOmpVisitor::channel_task_dependency_enabled() { bool CodegenCOmpVisitor::block_require_shadow_update(BlockType type) { return !(!channel_task_dependency_enabled() || type == BlockType::Initial); -} \ No newline at end of file +} diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 0b8f23c232..577ad26326 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -11,7 +11,9 @@ #include "parser/c11_driver.hpp" +using namespace ast; using namespace symtab; +using namespace syminfo; using namespace fmt::literals; using SymbolType = std::shared_ptr; @@ -70,7 +72,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { return current; } auto symtab = breakpoint->get_statement_block()->get_symbol_table(); - auto variables = symtab->get_variables_with_properties(NmodlInfo::local_var); + auto variables = symtab->get_variables_with_properties(NmodlType::local_var); for (auto& var : variables) { auto renamed_name = var->get_name(); auto original_name = var->get_original_name(); @@ -250,10 +252,10 @@ bool CodegenCVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { - if (symbol->has_properties(NmodlInfo::read_ion_var)) { + if (symbol->has_properties(NmodlType::read_ion_var)) { is_constant = true; } - if (symbol->has_properties(NmodlInfo::param_assign) && symbol->get_write_count() == 0) { + if (symbol->has_properties(NmodlType::param_assign) && symbol->get_write_count() == 0) { is_constant = true; } } @@ -2000,7 +2002,7 @@ void CodegenCVisitor::print_global_variable_setup() { /// additional list for derivimplicit method if (info.derivimplicit_used) { - auto primes = program_symtab->get_variables_with_properties(NmodlInfo::prime_name); + auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); auto slist2 = get_variable_name("slist2"); auto nprimes = info.primes_size; printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(slist2, nprimes)); @@ -2158,11 +2160,11 @@ void CodegenCVisitor::print_setup_range_variable() { */ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) { // clang-format off - auto with = NmodlInfo::read_ion_var - | NmodlInfo::write_ion_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::extern_neuron_variable; + auto with = NmodlType::read_ion_var + | NmodlType::write_ion_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::extern_neuron_variable; // clang-format on bool need_default_type = symbol->has_properties(with); if (need_default_type) { diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 7efdf02a07..656e3948fb 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -22,16 +22,16 @@ using namespace fmt::literals; class TableStatementVisitor : public AstVisitor { private: /// vector containing all table statements - std::vector statements; + std::vector statements; public: TableStatementVisitor() = default; - void visit_table_statement(TableStatement* node) override { + void visit_table_statement(ast::TableStatement* node) override { statements.push_back(node); } - std::vector get_statements() { + std::vector get_statements() { return statements; } }; diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 807f9f9103..784cacb5cb 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -64,4 +64,4 @@ bool CodegenInfo::function_uses_table(std::string& name) const { } } return false; -} \ No newline at end of file +} diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 7be8c75e04..65f6ee47db 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -18,4 +18,4 @@ def __init__(self): self.getname_method = False self.has_token = False self.getter_method = False - self.getter_override = False \ No newline at end of file + self.getter_override = False diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index aa7fb4b6d5..8d1a945b5a 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -2,78 +2,61 @@ from utils import * from node_info import ORDER_VAR_NAME - -class AstDeclarationPrinter(DeclarationPrinter): - """Prints all AST nodes class declarations""" - - def ast_types(self): - """ print ast type for every ast node """ - self.write_line("enum class Type {", post_gutter=1) - for node in self.nodes[:-1]: - name = to_snake_case(node.class_name).upper() + "," - self.write_line(name) - name = to_snake_case(self.nodes[-1].class_name).upper() - self.write_line(name) - self.write_line("};", pre_gutter=-1) +class AstForwardDeclarationPrinter(DeclarationPrinter): + """Prints all AST nodes forward declarations""" def headers(self): - self.write_line("#include ") self.write_line("#include ") self.write_line("#include ") self.write_line("#include ", newline=2) - self.write_line('#include "ast/ast_utils.hpp"') - self.write_line('#include "lexer/modtoken.hpp"') - self.write_line('#include "utils/common_utils.hpp"', newline=2) - - def class_comment(self): - self.write_line("/* all classes representing Abstract Syntax Tree (AST) nodes */") - - def forward_declarations(self): + def class_name_declaration(self): + self.write_line("namespace ast {", newline=2, post_gutter=1) self.write_line("/* forward declarations of AST classes */") for node in self.nodes: self.write_line("class " + node.class_name + ";") self.write_line() self.write_line("/* Type for every ast node */") - self.ast_types() + self.write_line("enum class AstNodeType {", post_gutter=1) + for node in self.nodes: + self.write_line(to_snake_case(node.class_name).upper() + ",") + self.write_line("};", pre_gutter=-1) self.write_line() self.write_line("/* std::vector for convenience */") - for node in self.nodes: typename = "std::vector>" self.write_line("using " + node.class_name + "Vector = " + typename + ";") - created_types = [] + def declaration_end(self): + self.write_line(pre_gutter=-1) - # iterate over all node types - for node in self.nodes: - type = (node.get_typename(), node.class_name.lower() + "_ptr") - if type not in created_types: - created_types.append(type) + def private_declaration(self): + pass - for child in node.children: - ctype = child.get_typename() - cname = child.class_name.lower() + def public_declaration(self): + pass - # base types like int, string are not defined in YYSTYPE - if child.is_base_type_node(): - continue + def post_declaration(self): + self.write_line("} // namespace ast", pre_gutter=-1) - if child.is_vector: - cname = cname + "_list" - else: - cname += "_ptr"; - type = (ctype, cname) +class AstDeclarationPrinter(DeclarationPrinter): + """Prints all AST nodes class declarations""" - if type not in created_types: - created_types.append(type) - for tuple in created_types: - self.write_line("using " + tuple[1] + " = " + tuple[0] + ";") + def headers(self): + self.write_line("#include ") + self.write_line("#include ") + self.write_line("#include ") + self.write_line("#include ", newline=2) + self.write_line('#include "ast/ast_decl.hpp"') + self.write_line('#include "ast/ast_utils.hpp"') + self.write_line('#include "lexer/modtoken.hpp"') + self.write_line('#include "utils/common_utils.hpp"') + self.write_line('#include "visitors/visitor.hpp"', newline=2) def ast_classes_declaration(self): @@ -82,11 +65,6 @@ def ast_classes_declaration(self): # when ModToken in NULL. This was introduced when added SymtabVisitor # pass to get Token information. - # TODO: remove visitor related method definition from declaration - # to avoid this include - self.write_line() - self.write_line("#include ", newline=2) - self.write_line("/* Define all AST nodes */", newline=2) for node in self.nodes: @@ -204,7 +182,7 @@ def ast_classes_declaration(self): # TODO: type should declared as enum class typename = to_snake_case(node.class_name).upper() - self.write_line(virtual + "Type get_type() override { return Type::" + typename + "; }") + self.write_line(virtual + "AstNodeType get_type() override { return AstNodeType::" + typename + "; }") self.write_line("bool is_" + to_snake_case(node.class_name) + " () override { return true; }") self.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") @@ -264,7 +242,6 @@ def ast_classes_declaration(self): def class_name_declaration(self): self.write_line("namespace ast {", newline=2, post_gutter=1) - self.forward_declarations() self.ast_classes_declaration() def declaration_end(self): diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 58f27e78eb..244ab6d2b2 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -9,6 +9,11 @@ # print various header and code files +AstForwardDeclarationPrinter( + "../ast/ast_decl.hpp", + "", + nodes).write() + AstDeclarationPrinter( "../ast/ast.hpp", "", @@ -62,4 +67,4 @@ NmodlVisitorDefinitionPrinter( "../visitors/nmodl_visitor.cpp", "NmodlPrintVisitor", - nodes).write() \ No newline at end of file + nodes).write() diff --git a/src/nmodl/language/nmodl_printer.py b/src/nmodl/language/nmodl_printer.py index 8b26fcc49a..0bedff5c0b 100644 --- a/src/nmodl/language/nmodl_printer.py +++ b/src/nmodl/language/nmodl_printer.py @@ -13,7 +13,7 @@ def class_comment(self): self.write_line("/* Visitor for printing AST back to NMODL */") def class_name_declaration(self): - self.write_line("class " + self.classname + " : public ast::Visitor {") + self.write_line("class " + self.classname + " : public Visitor {") def private_declaration(self): self.write_line("private:") diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index a31b97f930..43ba0e5e84 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -8,4 +8,4 @@ def camel_case_to_underscore(name): # convert string of the form "AabcDef" to "abc_def" def to_snake_case(name): - return camel_case_to_underscore(name).lower() \ No newline at end of file + return camel_case_to_underscore(name).lower() diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index d2f046a619..dc5d8c78ea 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -6,7 +6,8 @@ class AbstractVisitorPrinter(DeclarationPrinter): """Prints abstract base class for all visitor implementations""" def headers(self): - pass + line = '#include "ast/ast_decl.hpp"' + self.write_line(line) def class_comment(self): self.write_line("/* Abstract base class for all visitor implementations */") @@ -15,7 +16,7 @@ def public_declaration(self): self.write_line("public:", post_gutter=1) for node in self.nodes: - line = "virtual void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) = 0;" + line = "virtual void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) = 0;" self.write_line(line) self.writer.decrease_gutter() @@ -28,8 +29,6 @@ def headers(self): line = '#include "ast/ast.hpp"' self.write_line(line) line = '#include "visitors/visitor.hpp"' - self.write_line(line) - line = "using namespace ast;" self.write_line(line, newline=2) def class_comment(self): @@ -42,7 +41,7 @@ def public_declaration(self): self.write_line("public:", post_gutter=1) for node in self.nodes: - line = "virtual void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" + line = "virtual void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" self.write_line(line) self.writer.decrease_gutter() @@ -52,7 +51,8 @@ class AstVisitorDefinitionPrinter(DefinitionPrinter): """Prints base visitor class method definitions""" def headers(self): - self.write_line('#include "visitors/ast_visitor.hpp"', newline=2) + self.write_line('#include "visitors/ast_visitor.hpp"') + self.write_line("using namespace ast;", newline=2) def definitions(self): for node in self.nodes: @@ -100,7 +100,7 @@ def public_declaration(self): self.write_line(line, newline=2) for node in self.nodes: - line = "void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" + line = "void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" self.write_line(line) self.writer.decrease_gutter() @@ -110,8 +110,8 @@ class JSONVisitorDefinitionPrinter(DefinitionPrinter): """Prints visitor class definition for printing AST in JSON format""" def headers(self): - line = '#include "visitors/json_visitor.hpp"' - self.write_line(line, newline=2) + self.write_line('#include "visitors/json_visitor.hpp"') + self.write_line('using namespace ast;', newline=2) def definitions(self): for node in self.nodes: @@ -162,13 +162,11 @@ def class_comment(self): self.write_line("/* Concrete visitor for constructing symbol table from AST */") def class_name_declaration(self): - - self.write_line("using namespace symtab;") self.write_line("class " + self.classname + " : public AstVisitor {") def private_declaration(self): self.write_line("private:", post_gutter=1) - self.write_line("ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) + self.write_line("symtab::ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) self.write_line("std::unique_ptr printer;") self.write_line("std::string block_to_solve;") self.write_line("bool update = false;") @@ -187,10 +185,10 @@ def public_declaration(self): self.write_line(line, newline=2) # helper function for setting up symbol for variable - self.write_line("void setup_symbol(ast::Node* node, SymbolInfo property);", newline=2) + self.write_line("void setup_symbol(ast::Node* node, NmodlTypeFlag property);", newline=2) # add symbol with given property to model symbol table - line = "void add_model_symbol_with_property(ast::Node* node, SymbolInfo property);" + line = "void add_model_symbol_with_property(ast::Node* node, NmodlTypeFlag property);" self.write_line(line, newline=2) # helper function for creating symbol table for blocks @@ -213,7 +211,7 @@ def public_declaration(self): # which goes into symbol table for node in self.nodes: if node.is_symtab_method_required(): - line = "void visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) override;" + line = "void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" self.write_line(line) self.writer.decrease_gutter() @@ -223,10 +221,12 @@ class SymtabVisitorDefinitionPrinter(DefinitionPrinter): """Prints visitor class definition for printing Symbol table in JSON format""" def headers(self): - line = '#include "symtab/symbol_table.hpp"' - self.write_line(line) - line = '#include "visitors/symtab_visitor.hpp"' - self.write_line(line, newline=2) + self.write_line('#include "symtab/symbol_table.hpp"') + self.write_line('#include "visitors/symtab_visitor.hpp"', newline=2) + + self.write_line('using namespace symtab;') + self.write_line('using namespace syminfo;') + self.write_line('using namespace ast;', newline=2) def definitions(self): for node in self.nodes: @@ -241,7 +241,7 @@ def definitions(self): self.write_line(line, post_gutter=1) type_name = to_snake_case(node.class_name) - property_name = "NmodlInfo::" + type_name + property_name = "syminfo::NmodlType::" + type_name if node.is_symbol_var_node(): self.write_line("setup_symbol(node, " + property_name + ");") @@ -262,4 +262,4 @@ def definitions(self): else: self.write_line("setup_symbol_table_for_scoped_block(node, node->get_type_name());") - self.write_line("}", pre_gutter=-1, newline=2) \ No newline at end of file + self.write_line("}", pre_gutter=-1, newline=2) diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index e1b4e0ef60..f68478a075 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -59,4 +59,4 @@ int main(int argc, const char* argv[]) { } return 0; -} \ No newline at end of file +} diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index a790cd602a..df6b41f217 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -55,7 +55,7 @@ int main(int argc, const char* argv[]) { /** Lexer returns different ast types base on token type. We * retrieve token object from each instance and print it. - * Note that value is of ast type i.e. ast::name_ptr etc. */ + * Note that value is of ast type i.e. ast::Name* etc. */ switch (token) { /// token with name ast class case Token::NAME: @@ -64,7 +64,7 @@ int main(int argc, const char* argv[]) { case Token::VALENCE: case Token::DEL: case Token::DEL2: { - auto value = sym.value.as(); + auto value = sym.value.as(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -72,7 +72,7 @@ int main(int argc, const char* argv[]) { /// token with prime ast class case Token::PRIME: { - auto value = sym.value.as(); + auto value = sym.value.as(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -80,7 +80,7 @@ int main(int argc, const char* argv[]) { /// token with integer ast class case Token::INTEGER: { - auto value = sym.value.as(); + auto value = sym.value.as(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -88,7 +88,7 @@ int main(int argc, const char* argv[]) { /// token with double/float ast class case Token::REAL: { - auto value = sym.value.as(); + auto value = sym.value.as(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -96,7 +96,7 @@ int main(int argc, const char* argv[]) { /// token with string ast class case Token::STRING: { - auto value = sym.value.as(); + auto value = sym.value.as(); std::cout << *(value->get_token()) << std::endl; delete value; break; diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 049714aed1..90a06b07f8 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -1,4 +1,5 @@ %{ + #include #include "ast/ast.hpp" @@ -514,7 +515,7 @@ void nmodl::Lexer::scan_unit() { } /** return last scanned unit, it shouln't be null pointer */ -ast::string_ptr nmodl::Lexer::get_unit() { +ast::String* nmodl::Lexer::get_unit() { if (last_unit == nullptr) { throw std::runtime_error("Trying to get unscanned empty unit"); } diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index d6dbd1777c..45193339c8 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -19,7 +19,7 @@ namespace nmodl { /** Create symbol for integer ast class. Integer class also represent * macro definition and hence could have associated text. */ SymbolType integer_symbol(int value, PositionType& pos, const char* text) { - ast::name_ptr macro = nullptr; + ast::Name* macro = nullptr; ModToken token(std::to_string(value), Token::INTEGER, pos); if (text != nullptr) { diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 60fdf73d03..6106a9825e 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -21,6 +21,17 @@ %code requires { #include "ast/ast.hpp" + + /** + * todo : not clear why %token requires below types and doesn't allow + * specifying direct ast node types. Declare these type until + * the issue is identified / fixed. + */ + using double_ptr = ast::Double*; + using integer_ptr = ast::Integer*; + using name_ptr = ast::Name*; + using primename_ptr = ast::PrimeName*; + using string_ptr = ast::String*; } /** use C++ parser interface of bison */ @@ -167,22 +178,22 @@ %token NETRECEIVE %token FOR_NETCONS %token CONDUCTANCE -%token REAL -%token INTEGER -%token DEFINEDVAR -%token NAME -%token METHOD -%token SUFFIX -%token VALENCE -%token DEL -%token DEL2 -%token PRIME +%token REAL +%token INTEGER +%token DEFINEDVAR +%token NAME +%token METHOD +%token SUFFIX +%token VALENCE +%token DEL +%token DEL2 +%token PRIME %token VERBATIM %token COMMENT %token INLINE_COMMENT %token LINE_PART -%token STRING -%token UNIT_STR +%token STRING +%token UNIT_STR %token OPEN_BRACE "{" %token CLOSE_BRACE "}" %token OPEN_PARENTHESIS "(" @@ -212,135 +223,135 @@ * as NEURON. This helps during development and debugging. This could be * once all implementation details get ported. */ -%type all -%type Name -%type NUMBER -%type real -%type intexpr -%type integer -%type model -%type units -%type optindex -%type unit -%type proc -%type limits -%type abstol -%type name -%type number -%type primary -%type term -%type leftlinexpr -%type linexpr -%type numlist -%type expr -%type aexpr -%type ostmt -%type astmt -%type stmtlist -%type locallist -%type locallist1 -%type varname -%type exprlist -%type define1 -%type queuestmt -%type asgn -%type fromstmt -%type whilestmt -%type ifstmt -%type optelseif -%type optelse -%type solveblk -%type funccall -%type ifsolerr -%type opinc -%type opstart -%type senslist -%type sens -%type lagstmt -%type forallstmt -%type parmasgn -%type stepped -%type indepdef -%type depdef -%type declare -%type parmblk -%type parmbody -%type indepblk -%type indepbody -%type depblk -%type depbody -%type stateblk -%type stepblk -%type stepbdy -%type watchstmt -%type watchdir -%type watch1 -%type fornetcon -%type plotdecl -%type pvlist -%type constblk -%type conststmt -%type matchblk -%type matchlist -%type match -%type matchname -%type pareqn -%type firstlast -%type reaction -%type conserve -%type react -%type compart -%type ldifus -%type namelist -%type unitblk -%type unitbody -%type unitdef -%type factordef -%type solvefor -%type solvefor1 -%type uniton -%type tablst -%type tablst1 -%type tablestmt -%type dependlst -%type arglist -%type arglist1 -%type locoptarray -%type neuronblk -%type nrnuse -%type nrnstmt -%type nrnionrlist -%type nrnionwlist -%type nrnonspeclist -%type nrneclist -%type nrnseclist -%type nrnrangelist -%type nrnglobalist -%type nrnptrlist -%type nrnbbptrlist -%type nrnextlist -%type opthsafelist -%type threadsafelist -%type valence -%type initstmt -%type bablk -%type conducthint -%type stmtlist1 -%type initblk -%type constructblk -%type destructblk -%type funcblk -%type kineticblk -%type brkptblk -%type derivblk -%type linblk -%type nonlinblk -%type procedblk -%type netrecblk -%type terminalblk -%type discretblk -%type partialblk -%type functableblk +%type all +%type Name +%type NUMBER +%type real +%type intexpr +%type integer +%type model +%type units +%type optindex +%type unit +%type proc +%type limits +%type abstol +%type name +%type number +%type primary +%type term +%type leftlinexpr +%type linexpr +%type numlist +%type expr +%type aexpr +%type ostmt +%type astmt +%type stmtlist +%type locallist +%type locallist1 +%type varname +%type exprlist +%type define1 +%type queuestmt +%type asgn +%type fromstmt +%type whilestmt +%type ifstmt +%type optelseif +%type optelse +%type solveblk +%type funccall +%type ifsolerr +%type opinc +%type opstart +%type senslist +%type sens +%type lagstmt +%type forallstmt +%type parmasgn +%type stepped +%type indepdef +%type depdef +%type declare +%type parmblk +%type parmbody +%type indepblk +%type indepbody +%type depblk +%type depbody +%type stateblk +%type stepblk +%type stepbdy +%type watchstmt +%type watchdir +%type watch1 +%type fornetcon +%type plotdecl +%type pvlist +%type constblk +%type conststmt +%type matchblk +%type matchlist +%type match +%type matchname +%type pareqn +%type firstlast +%type reaction +%type conserve +%type react +%type compart +%type ldifus +%type namelist +%type unitblk +%type unitbody +%type unitdef +%type factordef +%type solvefor +%type solvefor1 +%type uniton +%type tablst +%type tablst1 +%type tablestmt +%type dependlst +%type arglist +%type arglist1 +%type locoptarray +%type neuronblk +%type nrnuse +%type nrnstmt +%type nrnionrlist +%type nrnionwlist +%type nrnonspeclist +%type nrneclist +%type nrnseclist +%type nrnrangelist +%type nrnglobalist +%type nrnptrlist +%type nrnbbptrlist +%type nrnextlist +%type opthsafelist +%type threadsafelist +%type valence +%type initstmt +%type bablk +%type conducthint +%type stmtlist1 +%type initblk +%type constructblk +%type destructblk +%type funcblk +%type kineticblk +%type brkptblk +%type derivblk +%type linblk +%type nonlinblk +%type procedblk +%type netrecblk +%type terminalblk +%type discretblk +%type partialblk +%type functableblk /** Precedence and Associativity : specify operator precedency and * associativity (from lower to higher. Note that '^' represent diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index f1071638d2..d3e1ba8d71 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -1,5 +1,7 @@ #include "symtab/symbol.hpp" +using namespace syminfo; + namespace symtab { /** if symbol has only extern_token property : this is true @@ -9,42 +11,42 @@ namespace symtab { * instead of exact comparisons */ bool Symbol::is_external_symbol_only() { - return (properties == details::NmodlInfo::extern_neuron_variable || - properties == details::NmodlInfo::extern_method); + return (properties == NmodlType::extern_neuron_variable || + properties == NmodlType::extern_method); } /// check if symbol has any of the common properties - bool Symbol::has_properties(SymbolInfo new_properties) { + bool Symbol::has_properties(NmodlTypeFlag new_properties) { return static_cast(properties & new_properties); } /// check if symbol has all of the given properties - bool Symbol::has_all_properties(SymbolInfo new_properties) { + bool Symbol::has_all_properties(NmodlTypeFlag new_properties) { return ((properties & new_properties) == new_properties); } /// check if symbol has any of the status - bool Symbol::has_any_status(SymbolStatus new_status) { + bool Symbol::has_any_status(StatusFlag new_status) { return static_cast(status & new_status); } /// check if symbol has all of the status - bool Symbol::has_all_status(SymbolStatus new_status) { + bool Symbol::has_all_status(StatusFlag new_status) { return ((status & new_status) == new_status); } /// add new properties to symbol with bitwise or - void Symbol::combine_properties(SymbolInfo new_properties) { + void Symbol::combine_properties(NmodlTypeFlag new_properties) { properties |= new_properties; } - void Symbol::add_property(NmodlInfo property) { + void Symbol::add_property(NmodlType property) { properties |= property; } - void Symbol::add_property(SymbolInfo property) { + void Symbol::add_property(NmodlTypeFlag property) { properties |= property; } @@ -62,21 +64,21 @@ namespace symtab { /// check if symbol is of variable type in nmodl bool Symbol::is_variable() { // clang-format off - SymbolInfo var_properties = NmodlInfo::local_var - | NmodlInfo::global_var - | NmodlInfo::range_var - | NmodlInfo::param_assign - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::extern_var - | NmodlInfo::dependent_def - | NmodlInfo::read_ion_var - | NmodlInfo::write_ion_var - | NmodlInfo::nonspe_cur_var - | NmodlInfo::electrode_cur_var - | NmodlInfo::section_var - | NmodlInfo::argument - | NmodlInfo::extern_neuron_variable; + NmodlTypeFlag var_properties = NmodlType::local_var + | NmodlType::global_var + | NmodlType::range_var + | NmodlType::param_assign + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::extern_var + | NmodlType::dependent_def + | NmodlType::read_ion_var + | NmodlType::write_ion_var + | NmodlType::nonspe_cur_var + | NmodlType::electrode_cur_var + | NmodlType::section_var + | NmodlType::argument + | NmodlType::extern_neuron_variable; // clang-format on return has_properties(var_properties); } diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index e170aa7ba1..f397fda798 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -13,16 +13,13 @@ namespace ast { namespace symtab { - using namespace ast; - using namespace details; - /** * \class Symbol * \brief Represent symbol in symbol table * * Symbol table generator pass visit the AST and insert symbol for * each node into symbol table. Symbol could appear multiple times - * in a block or different global blocks. SymbolInfo object has all + * in a block or different global blocks. NmodlTypeFlag object has all * nmodl properties information. * * \todo Multiple tokens (i.e. location information) for symbol should @@ -46,16 +43,16 @@ namespace symtab { /// ast node where symbol encountered first time /// node is passed from visitor and hence we have /// raw pointer instead of shared_ptr - AST* node = nullptr; + ast::AST* node = nullptr; /// token for position information ModToken token; /// properties of symbol from whole mod file - SymbolInfo properties{flags::empty}; + NmodlTypeFlag properties{flags::empty}; /// status of symbol during various passes - SymbolStatus status{flags::empty}; + StatusFlag status{flags::empty}; /// scope of the symbol (block name) std::string scope; @@ -87,13 +84,14 @@ namespace symtab { public: Symbol() = delete; - Symbol(std::string name, AST* node) : name(name), node(node) { + Symbol(std::string name, ast::AST* node) : name(name), node(node) { } Symbol(std::string name, ModToken token) : name(name), token(token) { } - Symbol(std::string name, AST* node, ModToken token) : name(name), node(node), token(token) { + Symbol(std::string name, ast::AST* node, ModToken token) + : name(name), node(node), token(token) { } void set_scope(std::string s) { @@ -123,11 +121,11 @@ namespace symtab { return scope; } - SymbolInfo get_properties() { + NmodlTypeFlag get_properties() { return properties; } - SymbolStatus get_status() { + StatusFlag get_status() { return status; } @@ -139,7 +137,7 @@ namespace symtab { write_count++; } - AST* get_node() { + ast::AST* get_node() { return node; } @@ -162,43 +160,43 @@ namespace symtab { bool is_external_symbol_only(); /// \todo : rename to has_any_property - bool has_properties(SymbolInfo new_properties); + bool has_properties(NmodlTypeFlag new_properties); - bool has_all_properties(SymbolInfo new_properties); + bool has_all_properties(NmodlTypeFlag new_properties); - bool has_any_status(SymbolStatus new_status); + bool has_any_status(StatusFlag new_status); - bool has_all_status(SymbolStatus new_status); + bool has_all_status(StatusFlag new_status); - void combine_properties(SymbolInfo new_properties); + void combine_properties(NmodlTypeFlag new_properties); - void add_property(NmodlInfo property); + void add_property(syminfo::NmodlType property); - void add_property(SymbolInfo property); + void add_property(NmodlTypeFlag property); void inlined() { - status |= Status::inlined; + status |= syminfo::Status::inlined; } void created() { - status |= Status::created; + status |= syminfo::Status::created; } void renamed() { - status |= Status::renamed; + status |= syminfo::Status::renamed; } void localized() { - status |= Status::localized; + status |= syminfo::Status::localized; } void mark_thread_safe() { - status |= Status::thread_safe; + status |= syminfo::Status::thread_safe; } void created_from_state() { created(); - status |= Status::from_state; + status |= syminfo::Status::from_state; } void set_order(int new_order); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 9efe1549ad..5b8eb12287 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -4,161 +4,161 @@ #include "utils/string_utils.hpp" #include "symtab/symbol_properties.hpp" -using namespace symtab::details; +using namespace syminfo; /// check if any property is set -bool has_property(const SymbolInfo& obj, NmodlInfo property) { +bool has_property(const NmodlTypeFlag& obj, NmodlType property) { return static_cast(obj & property); } -bool has_status(const SymbolStatus& obj, Status state) { +bool has_status(const StatusFlag& obj, Status state) { return static_cast(obj & state); } /// helper function to convert properties to string -std::vector to_string_vector(const SymbolInfo& obj) { +std::vector to_string_vector(const NmodlTypeFlag& obj) { std::vector properties; - if (has_property(obj, NmodlInfo::local_var)) { + if (has_property(obj, NmodlType::local_var)) { properties.emplace_back("local"); } - if (has_property(obj, NmodlInfo::global_var)) { + if (has_property(obj, NmodlType::global_var)) { properties.emplace_back("global"); } - if (has_property(obj, NmodlInfo::range_var)) { + if (has_property(obj, NmodlType::range_var)) { properties.emplace_back("range"); } - if (has_property(obj, NmodlInfo::param_assign)) { + if (has_property(obj, NmodlType::param_assign)) { properties.emplace_back("parameter"); } - if (has_property(obj, NmodlInfo::pointer_var)) { + if (has_property(obj, NmodlType::pointer_var)) { properties.emplace_back("pointer"); } - if (has_property(obj, NmodlInfo::bbcore_pointer_var)) { + if (has_property(obj, NmodlType::bbcore_pointer_var)) { properties.emplace_back("bbcore_pointer"); } - if (has_property(obj, NmodlInfo::extern_var)) { + if (has_property(obj, NmodlType::extern_var)) { properties.emplace_back("extern"); } - if (has_property(obj, NmodlInfo::prime_name)) { + if (has_property(obj, NmodlType::prime_name)) { properties.emplace_back("prime_name"); } - if (has_property(obj, NmodlInfo::dependent_def)) { + if (has_property(obj, NmodlType::dependent_def)) { properties.emplace_back("dependent_def"); } - if (has_property(obj, NmodlInfo::unit_def)) { + if (has_property(obj, NmodlType::unit_def)) { properties.emplace_back("unit_def"); } - if (has_property(obj, NmodlInfo::read_ion_var)) { + if (has_property(obj, NmodlType::read_ion_var)) { properties.emplace_back("read_ion"); } - if (has_property(obj, NmodlInfo::write_ion_var)) { + if (has_property(obj, NmodlType::write_ion_var)) { properties.emplace_back("write_ion"); } - if (has_property(obj, NmodlInfo::nonspe_cur_var)) { + if (has_property(obj, NmodlType::nonspe_cur_var)) { properties.emplace_back("nonspe_cur"); } - if (has_property(obj, NmodlInfo::electrode_cur_var)) { + if (has_property(obj, NmodlType::electrode_cur_var)) { properties.emplace_back("electrode_cur"); } - if (has_property(obj, NmodlInfo::section_var)) { + if (has_property(obj, NmodlType::section_var)) { properties.emplace_back("section"); } - if (has_property(obj, NmodlInfo::argument)) { + if (has_property(obj, NmodlType::argument)) { properties.emplace_back("argument"); } - if (has_property(obj, NmodlInfo::function_block)) { + if (has_property(obj, NmodlType::function_block)) { properties.emplace_back("function_block"); } - if (has_property(obj, NmodlInfo::procedure_block)) { + if (has_property(obj, NmodlType::procedure_block)) { properties.emplace_back("procedure_block"); } - if (has_property(obj, NmodlInfo::derivative_block)) { + if (has_property(obj, NmodlType::derivative_block)) { properties.emplace_back("derivative_block"); } - if (has_property(obj, NmodlInfo::linear_block)) { + if (has_property(obj, NmodlType::linear_block)) { properties.emplace_back("linear_block"); } - if (has_property(obj, NmodlInfo::non_linear_block)) { + if (has_property(obj, NmodlType::non_linear_block)) { properties.emplace_back("non_linear_block"); } - if (has_property(obj, NmodlInfo::table_statement_var)) { + if (has_property(obj, NmodlType::table_statement_var)) { properties.emplace_back("table_statement_var"); } - if (has_property(obj, NmodlInfo::table_dependent_var)) { + if (has_property(obj, NmodlType::table_dependent_var)) { properties.emplace_back("table_dependent_var"); } - if (has_property(obj, NmodlInfo::constant_var)) { + if (has_property(obj, NmodlType::constant_var)) { properties.emplace_back("constant"); } - if (has_property(obj, NmodlInfo::partial_block)) { + if (has_property(obj, NmodlType::partial_block)) { properties.emplace_back("partial_block"); } - if (has_property(obj, NmodlInfo::kinetic_block)) { + if (has_property(obj, NmodlType::kinetic_block)) { properties.emplace_back("kinetic_block"); } - if (has_property(obj, NmodlInfo::function_table_block)) { + if (has_property(obj, NmodlType::function_table_block)) { properties.emplace_back("function_table_block"); } - if (has_property(obj, NmodlInfo::factor_def)) { + if (has_property(obj, NmodlType::factor_def)) { properties.emplace_back("factor_def"); } - if (has_property(obj, NmodlInfo::extern_neuron_variable)) { + if (has_property(obj, NmodlType::extern_neuron_variable)) { properties.emplace_back("extern_neuron_var"); } - if (has_property(obj, NmodlInfo::extern_method)) { + if (has_property(obj, NmodlType::extern_method)) { properties.emplace_back("extern_method"); } - if (has_property(obj, NmodlInfo::state_var)) { + if (has_property(obj, NmodlType::state_var)) { properties.emplace_back("state_var"); } - if (has_property(obj, NmodlInfo::to_solve)) { + if (has_property(obj, NmodlType::to_solve)) { properties.emplace_back("to_solve"); } - if (has_property(obj, NmodlInfo::useion)) { + if (has_property(obj, NmodlType::useion)) { properties.emplace_back("ion"); } - if (has_property(obj, NmodlInfo::discrete_block)) { + if (has_property(obj, NmodlType::discrete_block)) { properties.emplace_back("discrete_block"); } return properties; } -std::vector to_string_vector(const SymbolStatus& obj) { +std::vector to_string_vector(const StatusFlag& obj) { std::vector status; if (has_status(obj, Status::localized)) { @@ -192,13 +192,13 @@ std::vector to_string_vector(const SymbolStatus& obj) { return status; } -std::ostream& operator<<(std::ostream& os, const SymbolInfo& obj) { +std::ostream& operator<<(std::ostream& os, const NmodlTypeFlag& obj) { os << to_string(obj); return os; } -std::ostream& operator<<(std::ostream& os, const SymbolStatus& obj) { +std::ostream& operator<<(std::ostream& os, const StatusFlag& obj) { os << to_string(obj); return os; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 8e3bcc36c9..5367dd20d1 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -6,216 +6,212 @@ #include "flags/flags.hpp" #include "utils/string_utils.hpp" -namespace symtab { - - namespace details { - - /** kind of symbol */ - enum class DeclarationType : long long { - /** variable */ - variable, - - /** function */ - function - }; - - /** scope within a mod file */ - enum class Scope : long long { - /** local variable */ - local, +namespace syminfo { + + /** kind of symbol */ + enum class DeclarationType : long long { + /** variable */ + variable, + + /** function */ + function + }; + + /** scope within a mod file */ + enum class Scope : long long { + /** local variable */ + local, + + /** global variable */ + global, - /** global variable */ - global, + /** neuron variable */ + neuron, - /** neuron variable */ - neuron, + /** extern variable */ + external + }; - /** extern variable */ - external - }; + /** state during various compiler passes */ + enum class Status : long long { + /** converted to local */ + localized = 1L << 0, - /** state during various compiler passes */ - enum class Status : long long { - /** converted to local */ - localized = 1L << 0, + /** converted to global */ + globalized = 1L << 1, - /** converted to global */ - globalized = 1L << 1, + /** inlined */ + inlined = 1L << 2, - /** inlined */ - inlined = 1L << 2, + /** renamed */ + renamed = 1L << 3, - /** renamed */ - renamed = 1L << 3, + /** created */ + created = 1L << 4, - /** created */ - created = 1L << 4, + /** derived from state */ + from_state = 1L << 5, - /** derived from state */ - from_state = 1L << 5, + /** variable marked as thread safe */ + thread_safe = 1L << 6 + }; - /** variable marked as thread safe */ - thread_safe = 1L << 6 - }; + /** usage of mod file as array or scalar */ + enum class VariableType : long long { + /** scalar / single value */ + scalar, - /** usage of mod file as array or scalar */ - enum class VariableType : long long { - /** scalar / single value */ - scalar, + /** vector type */ + array + }; - /** vector type */ - array - }; + /** variable usage within a mod file */ + enum class Access : long long { + /** variable is ready only */ + read = 1L << 0, - /** variable usage within a mod file */ - enum class Access : long long { - /** variable is ready only */ - read = 1L << 0, + /** variable is written only */ + write = 1L << 1 + }; - /** variable is written only */ - write = 1L << 1 - }; + /** @brief nmodl variable properties + * + * Certain variables/symbols specified in various places in the + * same mod file. For example, RANGE variable could be in PARAMETER + * bloc, ASSIGNED block etc. In this case, the symbol in global + * scope need to have multiple properties. This is also important + * while code generation. Hence, in addition to AST node pointer, + * we define NmodlType so that we can set multiple properties. + * Note that AST pointer is no longer pointing to all pointers. + * Same applies for ModToken. + * + * These is some redundancy between NmodlType and other enums like + * DeclarationType and Scope. But as AST will become more abstract + * from NMODL (towards C/C++) then other types will be useful. + * + * \todo Rename param_assign to parameter_var + * \todo Reaching max limit (31), need to refactor all block types + * into separate type + */ + enum class NmodlType : long long { + /** Local Variable */ + local_var = 1L << 0, - /** @brief nmodl variable properties - * - * Certain variables/symbols specified in various places in the - * same mod file. For example, RANGE variable could be in PARAMETER - * bloc, ASSIGNED block etc. In this case, the symbol in global - * scope need to have multiple properties. This is also important - * while code generation. Hence, in addition to AST node pointer, - * we define NmodlInfo so that we can set multiple properties. - * Note that AST pointer is no longer pointing to all pointers. - * Same applies for ModToken. - * - * These is some redundancy between NmodlInfo and other enums like - * DeclarationType and Scope. But as AST will become more abstract - * from NMODL (towards C/C++) then other types will be useful. - * - * \todo Rename param_assign to parameter_var - * \todo Reaching max limit (31), need to refactor all block types - * into separate type - */ - enum class NmodlInfo : long long { - /** Local Variable */ - local_var = 1L << 0, + /** Global Variable */ + global_var = 1L << 1, - /** Global Variable */ - global_var = 1L << 1, + /** Range Variable */ + range_var = 1L << 2, - /** Range Variable */ - range_var = 1L << 2, + /** Parameter Variable */ + param_assign = 1L << 3, - /** Parameter Variable */ - param_assign = 1L << 3, + /** Pointer Type */ + pointer_var = 1L << 4, - /** Pointer Type */ - pointer_var = 1L << 4, + /** Bbcorepointer Type */ + bbcore_pointer_var = 1L << 5, - /** Bbcorepointer Type */ - bbcore_pointer_var = 1L << 5, + /** Extern Type */ + extern_var = 1L << 6, - /** Extern Type */ - extern_var = 1L << 6, + /** Prime Type */ + prime_name = 1L << 7, - /** Prime Type */ - prime_name = 1L << 7, + /** Dependent Def */ + dependent_def = 1L << 8, - /** Dependent Def */ - dependent_def = 1L << 8, + /** Unit Def */ + unit_def = 1L << 9, - /** Unit Def */ - unit_def = 1L << 9, + /** Read Ion */ + read_ion_var = 1L << 10, - /** Read Ion */ - read_ion_var = 1L << 10, + /** Write Ion */ + write_ion_var = 1L << 11, - /** Write Ion */ - write_ion_var = 1L << 11, + /** Non Specific Current */ + nonspe_cur_var = 1L << 12, - /** Non Specific Current */ - nonspe_cur_var = 1L << 12, + /** Electrode Current */ + electrode_cur_var = 1L << 13, - /** Electrode Current */ - electrode_cur_var = 1L << 13, + /** Section Type */ + section_var = 1L << 14, - /** Section Type */ - section_var = 1L << 14, + /** Argument Type */ + argument = 1L << 15, - /** Argument Type */ - argument = 1L << 15, + /** Function Type */ + function_block = 1L << 16, - /** Function Type */ - function_block = 1L << 16, + /** Procedure Type */ + procedure_block = 1L << 17, - /** Procedure Type */ - procedure_block = 1L << 17, + /** Derivative Block */ + derivative_block = 1L << 18, - /** Derivative Block */ - derivative_block = 1L << 18, + /** Linear Block */ + linear_block = 1L << 19, - /** Linear Block */ - linear_block = 1L << 19, + /** NonLinear Block */ + non_linear_block = 1L << 20, - /** NonLinear Block */ - non_linear_block = 1L << 20, + /** constant variable */ + constant_var = 1L << 21, - /** constant variable */ - constant_var = 1L << 21, + /** Partial Block */ + partial_block = 1L << 22, - /** Partial Block */ - partial_block = 1L << 22, + /** Kinetic Block */ + kinetic_block = 1L << 23, - /** Kinetic Block */ - kinetic_block = 1L << 23, + /** FunctionTable Block */ + function_table_block = 1L << 24, - /** FunctionTable Block */ - function_table_block = 1L << 24, + /** factor in unit block */ + factor_def = 1L << 25, - /** factor in unit block */ - factor_def = 1L << 25, + /** neuron variable accessible in mod file */ + extern_neuron_variable = 1L << 26, - /** neuron variable accessible in mod file */ - extern_neuron_variable = 1L << 26, + /** neuron solver methods and math functions */ + extern_method = 1L << 27, - /** neuron solver methods and math functions */ - extern_method = 1L << 27, + /** state variable */ + state_var = 1L << 28, - /** state variable */ - state_var = 1L << 28, + /** need to solve : used in solve statement */ + to_solve = 1L << 29, - /** need to solve : used in solve statement */ - to_solve = 1L << 29, + /** ion type */ + useion = 1L << 30, - /** ion type */ - useion = 1L << 30, + /** variable is used in table statement */ + table_statement_var = 1L << 31, - /** variable is used in table statement */ - table_statement_var = 1L << 31, + /** variable is used in table as dependent */ + table_dependent_var = 1L << 32, - /** variable is used in table as dependent */ - table_dependent_var = 1L << 32, + /** Discrete Block */ + discrete_block = 1L << 33 + }; - /** Discrete Block */ - discrete_block = 1L << 33 - }; +} // namespace syminfo - } // namespace details +ALLOW_FLAGS_FOR_ENUM(syminfo::NmodlType) +ALLOW_FLAGS_FOR_ENUM(syminfo::Access) +ALLOW_FLAGS_FOR_ENUM(syminfo::Status) -} // namespace symtab +using NmodlTypeFlag = flags::flags; +using StatusFlag = flags::flags; -ALLOW_FLAGS_FOR_ENUM(symtab::details::NmodlInfo) -ALLOW_FLAGS_FOR_ENUM(symtab::details::Access) -ALLOW_FLAGS_FOR_ENUM(symtab::details::Status) +std::ostream& operator<<(std::ostream& os, const NmodlTypeFlag& obj); +std::ostream& operator<<(std::ostream& os, const StatusFlag& obj); -using SymbolInfo = flags::flags; -using SymbolStatus = flags::flags; - -std::ostream& operator<<(std::ostream& os, const SymbolInfo& obj); -std::ostream& operator<<(std::ostream& os, const SymbolStatus& obj); - -std::vector to_string_vector(const SymbolInfo& obj); -std::vector to_string_vector(const SymbolStatus& obj); +std::vector to_string_vector(const NmodlTypeFlag& obj); +std::vector to_string_vector(const StatusFlag& obj); template std::string to_string(const T& obj) { diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 755109fa74..5a7257e387 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -4,6 +4,9 @@ #include "symtab/symbol_table.hpp" #include "utils/table_data.hpp" +using namespace ast; +using namespace syminfo; + namespace symtab { int Table::counter = 0; @@ -80,7 +83,7 @@ namespace symtab { /// return all symbol having any of the provided properties std::vector> SymbolTable::get_variables_with_properties( - SymbolInfo properties, + NmodlTypeFlag properties, bool all) { std::vector> variables; for (auto& symbol : table.symbols) { @@ -98,8 +101,8 @@ namespace symtab { } /// return all symbol which has all "with" properties and none of the "without" properties - std::vector> SymbolTable::get_variables(SymbolInfo with, - SymbolInfo without) { + std::vector> SymbolTable::get_variables(NmodlTypeFlag with, + NmodlTypeFlag without) { auto variables = get_variables_with_properties(with, true); decltype(variables) result; for (auto& variable : variables) { @@ -111,7 +114,7 @@ namespace symtab { } - std::vector> SymbolTable::get_variables_with_status(SymbolStatus status, + std::vector> SymbolTable::get_variables_with_status(StatusFlag status, bool all) { std::vector> variables; for (auto& symbol : table.symbols) { @@ -266,8 +269,8 @@ namespace symtab { const std::shared_ptr& new_symbol) { auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; - bool is_parameter = new_symbol->has_properties(NmodlInfo::param_assign); - bool is_dependent_def = new_symbol->has_properties(NmodlInfo::dependent_def); + bool is_parameter = new_symbol->has_properties(NmodlType::param_assign); + bool is_dependent_def = new_symbol->has_properties(NmodlType::dependent_def); if (symbol->get_definition_order() == -1) { if (is_parameter || is_dependent_def) { diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 7de11623c4..f40b4cedbb 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -9,9 +9,6 @@ namespace symtab { - using namespace details; - using namespace ast; - /** * \class Table * \brief Helper class for implementing symbol table @@ -70,7 +67,7 @@ namespace symtab { Table table; /// pointer to ast node for which current block symbol created - AST* node = nullptr; + ast::AST* node = nullptr; /// true if current symbol table is global. blocks like neuron, /// parameter defines global variables and hence they go into @@ -87,7 +84,7 @@ namespace symtab { std::map> children; public: - SymbolTable(std::string name, AST* node, bool global = false) + SymbolTable(std::string name, ast::AST* node, bool global = false) : symtab_name(name), node(node), global(global) { } @@ -141,12 +138,12 @@ namespace symtab { std::shared_ptr lookup_in_scope(const std::string& name) const; - std::vector> get_variables(SymbolInfo with, SymbolInfo without); + std::vector> get_variables(NmodlTypeFlag with, NmodlTypeFlag without); - std::vector> get_variables_with_properties(SymbolInfo properties, + std::vector> get_variables_with_properties(NmodlTypeFlag properties, bool all = false); - std::vector> get_variables_with_status(SymbolStatus status, + std::vector> get_variables_with_status(StatusFlag status, bool all = false); bool under_global_scope(); @@ -182,7 +179,7 @@ namespace symtab { SymbolTable* current_symtab = nullptr; /// return unique name by appending some counter value - std::string get_unique_name(const std::string& name, AST* node, bool is_global); + std::string get_unique_name(const std::string& name, ast::AST* node, bool is_global); /// name of top level global symbol table const std::string GLOBAL_SYMTAB_NAME = "NMODL_GLOBAL"; @@ -212,7 +209,7 @@ namespace symtab { public: /// entering into new nmodl block SymbolTable* enter_scope(const std::string& name, - AST* node, + ast::AST* node, bool global, SymbolTable* node_symtab); diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index b237b854ae..b5b1cb4b23 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -106,4 +106,4 @@ void TableData::print(int indent) { std::stringstream ss; print(ss, indent); std::cout << ss.str(); -} \ No newline at end of file +} diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index 5523b1cc6a..66406f3230 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -33,4 +33,4 @@ struct TableData { void print(std::stringstream& stream, int indent = 0); }; -#endif \ No newline at end of file +#endif diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index fd79156136..01fb9f1304 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -6,6 +6,8 @@ #include "visitors/visitor_utils.hpp" #include "symtab/symbol.hpp" +using namespace ast; + void CnexpSolveVisitor::visit_solve_block(SolveBlock* node) { if (node->method) { solve_method = node->method->value->eval(); @@ -72,4 +74,4 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { void CnexpSolveVisitor::visit_program(Program* node) { program_symtab = node->get_symbol_table(); node->visit_children(this); -} \ No newline at end of file +} diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp index 256bb6e8ce..ab3b7b7e58 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -39,10 +39,10 @@ class CnexpSolveVisitor : public AstVisitor { public: CnexpSolveVisitor() = default; - void visit_solve_block(SolveBlock* node) override; - void visit_derivative_block(DerivativeBlock* node) override; - void visit_binary_expression(BinaryExpression* node) override; - void visit_program(Program* node) override; + void visit_solve_block(ast::SolveBlock* node) override; + void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_binary_expression(ast::BinaryExpression* node) override; + void visit_program(ast::Program* node) override; }; #endif diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index ec05da7c73..3d1d8893c3 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -4,6 +4,7 @@ #include "visitors/defuse_analyze_visitor.hpp" using namespace ast; +using namespace syminfo; /// DUState to string conversion for pretty-printing std::string to_string(DUState state) { @@ -257,7 +258,7 @@ void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { if (name == variable_name) { auto symbol = current_symtab->lookup_in_scope(name); // variable properties that make it local - auto properties = symtab::NmodlInfo::local_var | symtab::NmodlInfo::argument; + auto properties = NmodlType::local_var | NmodlType::argument; auto is_local = symbol->has_properties(properties); if (unsupported_node) { @@ -307,4 +308,4 @@ DUChain DefUseAnalyzeVisitor::analyze(ast::Node* node, const std::string& name) symtab_stack.pop(); return usage; -} \ No newline at end of file +} diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 680b897e70..69b53dce82 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -182,8 +182,8 @@ class DefUseAnalyzeVisitor : public AstVisitor { bool visiting_lhs = false; void update_defuse_chain(const std::string& name); - void visit_unsupported_node(Node* node); - void visit_with_new_chain(Node* node, DUState state); + void visit_unsupported_node(ast::Node* node); + void visit_with_new_chain(ast::Node* node, DUState state); void start_new_chain(DUState state); public: @@ -196,8 +196,8 @@ class DefUseAnalyzeVisitor : public AstVisitor { : global_symtab(symtab), ignore_verbatim(ignore_verbatim) { } - virtual void visit_binary_expression(BinaryExpression* node) override; - virtual void visit_if_statement(IfStatement* node) override; + virtual void visit_binary_expression(ast::BinaryExpression* node) override; + virtual void visit_if_statement(ast::IfStatement* node) override; virtual void visit_function_call(ast::FunctionCall* node) override; virtual void visit_statement_block(ast::StatementBlock* node) override; virtual void visit_verbatim(ast::Verbatim* node) override; @@ -205,27 +205,27 @@ class DefUseAnalyzeVisitor : public AstVisitor { /// unsupported statements : we aren't sure how to handle this "yet" and /// hence variables used in any of the below statements are handled separately - virtual void visit_reaction_statement(ReactionStatement* node) override { + virtual void visit_reaction_statement(ast::ReactionStatement* node) override { visit_unsupported_node(node); } - virtual void visit_non_lin_equation(NonLinEquation* node) override { + virtual void visit_non_lin_equation(ast::NonLinEquation* node) override { visit_unsupported_node(node); } - virtual void visit_lin_equation(LinEquation* node) override { + virtual void visit_lin_equation(ast::LinEquation* node) override { visit_unsupported_node(node); } - virtual void visit_partial_boundary(PartialBoundary* node) override { + virtual void visit_partial_boundary(ast::PartialBoundary* node) override { visit_unsupported_node(node); } - virtual void visit_from_statement(FromStatement* node) override { + virtual void visit_from_statement(ast::FromStatement* node) override { visit_unsupported_node(node); } - virtual void visit_conserve(Conserve* node) override { + virtual void visit_conserve(ast::Conserve* node) override { visit_unsupported_node(node); } @@ -240,10 +240,10 @@ class DefUseAnalyzeVisitor : public AstVisitor { /// statements / nodes that should not be used for def-use chain analysis - virtual void visit_conductance_hint(ConductanceHint* /*node*/) override { + virtual void visit_conductance_hint(ast::ConductanceHint* /*node*/) override { } - virtual void visit_local_list_statement(LocalListStatement* /*node*/) override { + virtual void visit_local_list_statement(ast::LocalListStatement* /*node*/) override { } virtual void visit_argument(ast::Argument* /*node*/) override { diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 8203c18589..217458b728 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -167,7 +167,7 @@ class InlineVisitor : public AstVisitor { virtual void visit_wrapped_expression(ast::WrappedExpression* node) override; - virtual void visit_program(Program* node) override; + virtual void visit_program(ast::Program* node) override; }; /** diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 6b843474b7..174d62006b 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -5,6 +5,7 @@ using namespace ast; using namespace symtab; +using namespace syminfo; bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { auto type = node->get_type(); @@ -16,22 +17,22 @@ bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { * result as "Use". So it's safe. */ // clang-format off - const std::vector blocks_to_analyze = { - ast::Type::INITIAL_BLOCK, - ast::Type::BREAKPOINT_BLOCK, - ast::Type::CONSTRUCTOR_BLOCK, - ast::Type::DESTRUCTOR_BLOCK, - ast::Type::DERIVATIVE_BLOCK, - ast::Type::LINEAR_BLOCK, - ast::Type::NON_LINEAR_BLOCK, - ast::Type::DISCRETE_BLOCK, - ast::Type::PARTIAL_BLOCK, - ast::Type::NET_RECEIVE_BLOCK, - ast::Type::TERMINAL_BLOCK, - ast::Type::BA_BLOCK, - ast::Type::FOR_NETCON, - ast::Type::BEFORE_BLOCK, - ast::Type::AFTER_BLOCK, + const std::vector blocks_to_analyze = { + ast::AstNodeType::INITIAL_BLOCK, + ast::AstNodeType::BREAKPOINT_BLOCK, + ast::AstNodeType::CONSTRUCTOR_BLOCK, + ast::AstNodeType::DESTRUCTOR_BLOCK, + ast::AstNodeType::DERIVATIVE_BLOCK, + ast::AstNodeType::LINEAR_BLOCK, + ast::AstNodeType::NON_LINEAR_BLOCK, + ast::AstNodeType::DISCRETE_BLOCK, + ast::AstNodeType::PARTIAL_BLOCK, + ast::AstNodeType::NET_RECEIVE_BLOCK, + ast::AstNodeType::TERMINAL_BLOCK, + ast::AstNodeType::BA_BLOCK, + ast::AstNodeType::FOR_NETCON, + ast::AstNodeType::BEFORE_BLOCK, + ast::AstNodeType::AFTER_BLOCK, }; // clang-format on auto it = std::find(blocks_to_analyze.begin(), blocks_to_analyze.end(), type); @@ -47,7 +48,7 @@ bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { if (node->is_procedure_block()) { auto symbol = program_symtab->lookup(node->get_name()); - if (symbol && symbol->has_properties(NmodlInfo::to_solve)) { + if (symbol && symbol->has_properties(NmodlType::to_solve)) { return true; } } @@ -56,21 +57,21 @@ bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { std::vector LocalizeVisitor::variables_to_optimize() { // clang-format off - using NmodlInfo = symtab::details::NmodlInfo; - const SymbolInfo excluded_var_properties = NmodlInfo::extern_var - | NmodlInfo::extern_neuron_variable - | NmodlInfo::read_ion_var - | NmodlInfo::write_ion_var - | NmodlInfo::prime_name - | NmodlInfo::nonspe_cur_var - | NmodlInfo::pointer_var - | NmodlInfo::bbcore_pointer_var - | NmodlInfo::electrode_cur_var - | NmodlInfo::section_var; - - SymbolInfo global_var_properties = NmodlInfo::range_var - | NmodlInfo::dependent_def - | NmodlInfo::param_assign; + using NmodlType = NmodlType; + const NmodlTypeFlag excluded_var_properties = NmodlType::extern_var + | NmodlType::extern_neuron_variable + | NmodlType::read_ion_var + | NmodlType::write_ion_var + | NmodlType::prime_name + | NmodlType::nonspe_cur_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::electrode_cur_var + | NmodlType::section_var; + + NmodlTypeFlag global_var_properties = NmodlType::range_var + | NmodlType::dependent_def + | NmodlType::param_assign; // clang-format on @@ -135,7 +136,7 @@ void LocalizeVisitor::visit_program(Program* node) { /// insert new symbol into symbol table auto symtab = statement_block->get_symbol_table(); auto new_symbol = std::make_shared(varname, variable); - new_symbol->add_property(NmodlInfo::local_var); + new_symbol->add_property(NmodlType::local_var); new_symbol->created(); symtab->insert(new_symbol); } diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 2a9b202ef0..73e77d4ac3 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -2,8 +2,9 @@ #include "visitors/perf_visitor.hpp" -using namespace symtab; using namespace ast; +using namespace syminfo; +using namespace symtab; PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) { } @@ -164,7 +165,7 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { node->visit_children(this); auto symbol = current_symtab->lookup_in_scope(name); - auto method_property = NmodlInfo::procedure_block | NmodlInfo::function_block; + auto method_property = NmodlType::procedure_block | NmodlType::function_block; if (symbol != nullptr && symbol->has_properties(method_property)) { current_block_perf.n_int_func_call++; } else { @@ -207,28 +208,28 @@ void PerfVisitor::count_variables() { /// assigned block are not treated as range num_instance_variables = 0; - SymbolInfo property = NmodlInfo::range_var | NmodlInfo::dependent_def | NmodlInfo::state_var; + NmodlTypeFlag property = NmodlType::range_var | NmodlType::dependent_def | NmodlType::state_var; auto variables = current_symtab->get_variables_with_properties(property); for (auto& variable : variables) { - if (!variable->has_properties(NmodlInfo::global_var)) { + if (!variable->has_properties(NmodlType::global_var)) { num_instance_variables++; - if (variable->has_properties(NmodlInfo::param_assign)) { + if (variable->has_properties(NmodlType::param_assign)) { num_constant_instance_variables++; } - if (variable->has_any_status(Status::localized)) { + if (variable->has_any_status(syminfo::Status::localized)) { num_localized_instance_variables++; } } } /// state variables have state_var property - property = NmodlInfo::state_var; + property = NmodlType::state_var; variables = current_symtab->get_variables_with_properties(property); num_state_variables = variables.size(); /// pointer variables have pointer/bbcorepointer - property = NmodlInfo::pointer_var | NmodlInfo::bbcore_pointer_var; + property = NmodlType::pointer_var | NmodlType::bbcore_pointer_var; variables = current_symtab->get_variables_with_properties(property); num_pointer_variables = variables.size(); @@ -236,19 +237,19 @@ void PerfVisitor::count_variables() { /// number of global variables : parameters and pointers could appear also /// as range variables and hence need to filter out. But if anything declared /// as global is always global. - property = NmodlInfo::global_var | NmodlInfo::param_assign | NmodlInfo::bbcore_pointer_var | - NmodlInfo::pointer_var; + property = NmodlType::global_var | NmodlType::param_assign | NmodlType::bbcore_pointer_var | + NmodlType::pointer_var; variables = current_symtab->get_variables_with_properties(property); num_global_variables = 0; for (auto& variable : variables) { - auto is_global = variable->has_properties(NmodlInfo::global_var); - property = NmodlInfo::range_var | NmodlInfo::dependent_def; + auto is_global = variable->has_properties(NmodlType::global_var); + property = NmodlType::range_var | NmodlType::dependent_def; if (!variable->has_properties(property) || is_global) { num_global_variables++; - if (variable->has_properties(NmodlInfo::param_assign)) { + if (variable->has_properties(NmodlType::param_assign)) { num_constant_global_variables++; } - if (variable->has_any_status(Status::localized)) { + if (variable->has_any_status(syminfo::Status::localized)) { num_localized_global_variables++; } } @@ -387,12 +388,12 @@ void PerfVisitor::visit_unary_expression(UnaryExpression* node) { bool PerfVisitor::symbol_to_skip(const std::shared_ptr& symbol) { bool skip = false; - auto is_method = symbol->has_properties(NmodlInfo::extern_method | NmodlInfo::function_block); + auto is_method = symbol->has_properties(NmodlType::extern_method | NmodlType::function_block); if (is_method && under_function_call) { skip = true; } - is_method = symbol->has_properties(NmodlInfo::derivative_block | NmodlInfo::extern_method); + is_method = symbol->has_properties(NmodlType::derivative_block | NmodlType::extern_method); if (is_method && under_solve_block) { skip = true; } @@ -403,7 +404,7 @@ bool PerfVisitor::symbol_to_skip(const std::shared_ptr& symbol) { bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) { bool is_local = false; /// in the function when we write to function variable then consider it as local variable - auto properties = NmodlInfo::local_var | NmodlInfo::argument | NmodlInfo::function_block; + auto properties = NmodlType::local_var | NmodlType::argument | NmodlType::function_block; if (symbol->has_properties(properties)) { is_local = true; } @@ -412,7 +413,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr& symbo bool PerfVisitor::is_constant_variable(const std::shared_ptr& symbol) { bool is_constant = false; - auto properties = NmodlInfo::param_assign; + auto properties = NmodlType::param_assign; if (symbol->has_properties(properties)) { is_constant = true; } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index b61c7627d1..86dc9bbbf0 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -120,7 +120,7 @@ class PerfVisitor : public AstVisitor { void count_variables(); - void measure_performance(AST* node); + void measure_performance(ast::AST* node); void print_memory_usage(); @@ -159,131 +159,131 @@ class PerfVisitor : public AstVisitor { return num_state_variables; } - void visit_binary_expression(BinaryExpression* node) override; + void visit_binary_expression(ast::BinaryExpression* node) override; - void visit_function_call(FunctionCall* node) override; + void visit_function_call(ast::FunctionCall* node) override; - void visit_name(Name* node) override; + void visit_name(ast::Name* node) override; - void visit_prime_name(PrimeName* node) override; + void visit_prime_name(ast::PrimeName* node) override; - void visit_solve_block(SolveBlock* node) override; + void visit_solve_block(ast::SolveBlock* node) override; - void visit_statement_block(StatementBlock* node) override; + void visit_statement_block(ast::StatementBlock* node) override; - void visit_unary_expression(UnaryExpression* node) override; + void visit_unary_expression(ast::UnaryExpression* node) override; - void visit_if_statement(IfStatement* node) override; + void visit_if_statement(ast::IfStatement* node) override; - void visit_else_if_statement(ElseIfStatement* node) override; + void visit_else_if_statement(ast::ElseIfStatement* node) override; - void visit_program(Program* node) override; + void visit_program(ast::Program* node) override; - void visit_plot_block(PlotBlock* node) override { + void visit_plot_block(ast::PlotBlock* node) override { measure_performance(node); } /// skip initial block under net_receive block - void visit_initial_block(InitialBlock* node) override { + void visit_initial_block(ast::InitialBlock* node) override { if (!under_net_receive_block) { measure_performance(node); } } - void visit_constructor_block(ConstructorBlock* node) override { + void visit_constructor_block(ast::ConstructorBlock* node) override { measure_performance(node); } - void visit_destructor_block(DestructorBlock* node) override { + void visit_destructor_block(ast::DestructorBlock* node) override { measure_performance(node); } - void visit_derivative_block(DerivativeBlock* node) override { + void visit_derivative_block(ast::DerivativeBlock* node) override { measure_performance(node); } - void visit_linear_block(LinearBlock* node) override { + void visit_linear_block(ast::LinearBlock* node) override { measure_performance(node); } - void visit_non_linear_block(NonLinearBlock* node) override { + void visit_non_linear_block(ast::NonLinearBlock* node) override { measure_performance(node); } - void visit_discrete_block(DiscreteBlock* node) override { + void visit_discrete_block(ast::DiscreteBlock* node) override { measure_performance(node); } - void visit_partial_block(PartialBlock* node) override { + void visit_partial_block(ast::PartialBlock* node) override { measure_performance(node); } - void visit_function_table_block(FunctionTableBlock* node) override { + void visit_function_table_block(ast::FunctionTableBlock* node) override { measure_performance(node); } - void visit_function_block(FunctionBlock* node) override { + void visit_function_block(ast::FunctionBlock* node) override { measure_performance(node); } - void visit_procedure_block(ProcedureBlock* node) override { + void visit_procedure_block(ast::ProcedureBlock* node) override { measure_performance(node); } - void visit_net_receive_block(NetReceiveBlock* node) override { + void visit_net_receive_block(ast::NetReceiveBlock* node) override { under_net_receive_block = true; measure_performance(node); under_net_receive_block = false; } - void visit_breakpoint_block(BreakpointBlock* node) override { + void visit_breakpoint_block(ast::BreakpointBlock* node) override { measure_performance(node); } - void visit_terminal_block(TerminalBlock* node) override { + void visit_terminal_block(ast::TerminalBlock* node) override { measure_performance(node); } - void visit_before_block(BeforeBlock* node) override { + void visit_before_block(ast::BeforeBlock* node) override { measure_performance(node); } - void visit_after_block(AfterBlock* node) override { + void visit_after_block(ast::AfterBlock* node) override { measure_performance(node); } - void visit_ba_block(BABlock* node) override { + void visit_ba_block(ast::BABlock* node) override { measure_performance(node); } - void visit_for_netcon(ForNetcon* node) override { + void visit_for_netcon(ast::ForNetcon* node) override { measure_performance(node); } - void visit_kinetic_block(KineticBlock* node) override { + void visit_kinetic_block(ast::KineticBlock* node) override { measure_performance(node); } - void visit_match_block(MatchBlock* node) override { + void visit_match_block(ast::MatchBlock* node) override { measure_performance(node); } /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations - void visit_conductance_hint(ConductanceHint* /*node*/) override { + void visit_conductance_hint(ast::ConductanceHint* /*node*/) override { } - void visit_local_list_statement(LocalListStatement* /*node*/) override { + void visit_local_list_statement(ast::LocalListStatement* /*node*/) override { } - void visit_suffix(Suffix* /*node*/) override { + void visit_suffix(ast::Suffix* /*node*/) override { } - void visit_useion(Useion* /*node*/) override { + void visit_useion(ast::Useion* /*node*/) override { } - void visit_valence(Valence* /*node*/) override { + void visit_valence(ast::Valence* /*node*/) override { } void print(std::stringstream& ss) { diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 70353cbd16..7247ede44d 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -1,8 +1,10 @@ #include "visitors/rename_visitor.hpp" #include "parser/c11_driver.hpp" +using namespace ast; + /// rename matching variable -void RenameVisitor::visit_name(ast::Name* node) { +void RenameVisitor::visit_name(Name* node) { std::string name = node->get_name(); if (name == var_name) { node->value->set(new_var_name); @@ -42,4 +44,4 @@ void RenameVisitor::visit_verbatim(Verbatim* node) { } } statement->set(result); -} \ No newline at end of file +} diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index 2b70800662..48c5ed2fc0 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -3,10 +3,13 @@ #include "lexer/token_mapping.hpp" #include "visitors/symtab_visitor.hpp" +using namespace ast; +using namespace symtab; +using namespace syminfo; // create symbol for given node static std::shared_ptr create_symbol_for_node(Node* node, - SymbolInfo property, + NmodlTypeFlag property, bool under_state_block) { ModToken token; auto token_ptr = node->get_token(); @@ -19,13 +22,13 @@ static std::shared_ptr create_symbol_for_node(Node* node, symbol->add_property(property); // non specific variable is range - if (property == NmodlInfo::nonspe_cur_var) { - symbol->add_property(NmodlInfo::range_var); + if (property == NmodlType::nonspe_cur_var) { + symbol->add_property(NmodlType::range_var); } /// extra property for state variables if (under_state_block) { - symbol->add_property(NmodlInfo::state_var); + symbol->add_property(NmodlType::state_var); } return symbol; } @@ -33,7 +36,7 @@ static std::shared_ptr create_symbol_for_node(Node* node, /// helper function to setup/insert symbol into symbol table /// for the ast nodes which are of variable types -void SymtabVisitor::setup_symbol(Node* node, SymbolInfo property) { +void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { std::shared_ptr symbol; auto name = node->get_name(); @@ -52,9 +55,9 @@ void SymtabVisitor::setup_symbol(Node* node, SymbolInfo property) { /// range and non_spec_cur can appear in any order in neuron block. /// for both properties, we have to check if symbol is already exist. /// if so we have to return to avoid duplicate definition error. - if (property == NmodlInfo::range_var || property == NmodlInfo::nonspe_cur_var) { + if (property == NmodlType::range_var || property == NmodlType::nonspe_cur_var) { auto s = modsymtab->lookup(name); - if (s && s->has_properties(NmodlInfo::nonspe_cur_var | NmodlInfo::range_var)) { + if (s && s->has_properties(NmodlType::nonspe_cur_var | NmodlType::range_var)) { s->add_property(property); return; } @@ -107,14 +110,14 @@ void SymtabVisitor::setup_symbol(Node* node, SymbolInfo property) { } -void SymtabVisitor::add_model_symbol_with_property(Node* node, SymbolInfo property) { +void SymtabVisitor::add_model_symbol_with_property(Node* node, NmodlTypeFlag property) { auto token = node->get_token(); auto name = node->get_name(); auto symbol = std::make_shared(name, node, *token); symbol->add_property(property); if (name == block_to_solve) { - symbol->add_property(NmodlInfo::to_solve); + symbol->add_property(NmodlType::to_solve); } modsymtab->insert(symbol); @@ -126,13 +129,13 @@ static void add_external_symbols(symtab::ModelSymbolTable* symtab) { auto variables = nmodl::get_external_variables(); for (auto variable : variables) { auto symbol = std::make_shared(variable, nullptr, tok); - symbol->add_property(NmodlInfo::extern_neuron_variable); + symbol->add_property(NmodlType::extern_neuron_variable); symtab->insert(symbol); } auto methods = nmodl::get_external_functions(); for (auto method : methods) { auto symbol = std::make_shared(method, nullptr, tok); - symbol->add_property(NmodlInfo::extern_method); + symbol->add_property(NmodlType::extern_method); symtab->insert(symbol); } } @@ -201,7 +204,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, const std::s * @todo : we assume table statement follows variable declaration */ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { - auto update_symbol = [this](NameVector& variables, NmodlInfo property, int num_values) { + auto update_symbol = [this](NameVector& variables, NmodlType property, int num_values) { for (auto& var : variables) { auto name = var->get_name(); auto symbol = modsymtab->lookup(name); @@ -212,6 +215,6 @@ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { } }; int num_values = node->with->eval() + 1; - update_symbol(node->table_vars, NmodlInfo::table_statement_var, num_values); - update_symbol(node->depend_vars, NmodlInfo::table_dependent_var, num_values); + update_symbol(node->table_vars, NmodlType::table_statement_var, num_values); + update_symbol(node->depend_vars, NmodlType::table_dependent_var, num_values); } diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 87df9eaa6d..aa138275e4 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -15,4 +15,4 @@ bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { var_name = std::move(name); node->visit_children(this); return used; -} \ No newline at end of file +} diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 718d16e635..7d55a535ad 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,6 +1,8 @@ #include #include "visitors/verbatim_visitor.hpp" +using namespace ast; + void VerbatimVisitor::visit_verbatim(Verbatim* node) { std::string block; auto statement = node->get_statement(); @@ -14,4 +16,4 @@ void VerbatimVisitor::visit_verbatim(Verbatim* node) { } blocks.push_back(block); -} \ No newline at end of file +} diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 8be5063a93..9320290e57 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -32,7 +32,7 @@ class VerbatimVisitor : public AstVisitor { verbose = flag; } - void visit_verbatim(Verbatim* node) override; + void visit_verbatim(ast::Verbatim* node) override; std::vector verbatim_blocks() { return blocks; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 4619c306c4..1b26099ab6 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -20,7 +20,7 @@ std::string get_new_name(const std::string& name, LocalVarVector* get_local_variables(const StatementBlock* node) { for (const auto& statement : node->statements) { - if (statement->get_type() == Type::LOCAL_LIST_STATEMENT) { + if (statement->get_type() == AstNodeType::LOCAL_LIST_STATEMENT) { auto local_statement = std::static_pointer_cast(statement); return &(local_statement->variables); } diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index f971308154..455816c8f2 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -20,10 +20,10 @@ ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); void add_local_statement(ast::StatementBlock* node); /** Add new local variable to the block */ -LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); -LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); -LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname, int dim); +ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); +ast::LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); +ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname, int dim); /** Create ast statement node from given code in string format */ std::shared_ptr create_statement(const std::string& code_statement); -#endif \ No newline at end of file +#endif diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index c4ad8919e1..da3ebecc97 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -33,31 +33,31 @@ nmodl::Parser::token_type token_type(const std::string& name) { case Token::VALENCE: case Token::DEL: case Token::DEL2: { - auto value = sym.value.as(); + auto value = sym.value.as(); delete value; break; } case Token::PRIME: { - auto value = sym.value.as(); + auto value = sym.value.as(); delete value; break; } case Token::INTEGER: { - auto value = sym.value.as(); + auto value = sym.value.as(); delete value; break; } case Token::REAL: { - auto value = sym.value.as(); + auto value = sym.value.as(); delete value; break; } case Token::STRING: { - auto value = sym.value.as(); + auto value = sym.value.as(); delete value; break; } diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 6a483dc0b0..14ebc95801 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -23,7 +23,7 @@ void symbol_type(const std::string& name, T& value) { /// test symbol type returned by lexer TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { SECTION("Symbol type : name ast class test") { - ast::name_ptr value = nullptr; + ast::Name* value = nullptr; { std::stringstream ss; @@ -43,7 +43,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { } SECTION("Symbol type : prime ast class test") { - ast::primename_ptr value = nullptr; + ast::PrimeName* value = nullptr; { std::stringstream ss; diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 43ab8d3cd6..c7d1471ffb 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -8,18 +8,18 @@ #include "symtab/symbol_table.hpp" using namespace symtab; -using namespace symtab::details; +using namespace syminfo; -extern bool has_property(const SymbolInfo& obj, NmodlInfo property); +extern bool has_property(const NmodlTypeFlag& obj, NmodlType property); //============================================================================= // Symbol properties test //============================================================================= SCENARIO("Symbol properties can be added and converted to string") { - SymbolInfo prop1{flags::empty}; - SymbolInfo prop2 = NmodlInfo::local_var; - SymbolInfo prop3 = NmodlInfo::global_var; + NmodlTypeFlag prop1{flags::empty}; + NmodlTypeFlag prop2 = NmodlType::local_var; + NmodlTypeFlag prop3 = NmodlType::global_var; GIVEN("A empty property") { WHEN("converted to string") { @@ -29,29 +29,29 @@ SCENARIO("Symbol properties can be added and converted to string") { } WHEN("checked for property") { THEN("doesn't have any property") { - REQUIRE_FALSE(has_property(prop1, NmodlInfo::local_var)); + REQUIRE_FALSE(has_property(prop1, NmodlType::local_var)); } } WHEN("adding another empty property") { - SymbolInfo result = prop1 | prop1; + NmodlTypeFlag result = prop1 | prop1; THEN("to_string still returns empty string") { REQUIRE(to_string(result).empty()); } } WHEN("added some other property") { - SymbolInfo result = prop1 | prop2; + NmodlTypeFlag result = prop1 | prop2; THEN("to_string returns added property") { REQUIRE(to_string(result) == "local"); } WHEN("checked for property") { THEN("has required property") { - REQUIRE(has_property(result, NmodlInfo::local_var) == true); + REQUIRE(has_property(result, NmodlType::local_var) == true); } } } WHEN("added multiple properties") { - SymbolInfo result = prop1 | prop2 | prop3; - result |= NmodlInfo::write_ion_var; + NmodlTypeFlag result = prop1 | prop2 | prop3; + result |= NmodlType::write_ion_var; THEN("to_string returns all added properties") { REQUIRE_THAT(to_string(result), Catch::Contains("local")); REQUIRE_THAT(to_string(result), Catch::Contains("global")); @@ -59,10 +59,10 @@ SCENARIO("Symbol properties can be added and converted to string") { } WHEN("checked for property") { THEN("has all added properties") { - REQUIRE(has_property(result, NmodlInfo::local_var) == true); - REQUIRE(has_property(result, NmodlInfo::global_var) == true); - REQUIRE(has_property(result, NmodlInfo::write_ion_var) == true); - REQUIRE_FALSE(has_property(result, NmodlInfo::read_ion_var)); + REQUIRE(has_property(result, NmodlType::local_var) == true); + REQUIRE(has_property(result, NmodlType::global_var) == true); + REQUIRE(has_property(result, NmodlType::write_ion_var) == true); + REQUIRE_FALSE(has_property(result, NmodlType::read_ion_var)); } } } @@ -74,14 +74,14 @@ SCENARIO("Symbol properties can be added and converted to string") { //============================================================================= SCENARIO("Symbol operations") { - SymbolInfo property1 = NmodlInfo::argument; - SymbolInfo property2 = NmodlInfo::range_var; - SymbolInfo property3 = NmodlInfo::discrete_block; + NmodlTypeFlag property1 = NmodlType::argument; + NmodlTypeFlag property2 = NmodlType::range_var; + NmodlTypeFlag property3 = NmodlType::discrete_block; GIVEN("A symbol") { ModToken token(true); Symbol symbol("alpha", token); WHEN("added external property") { - symbol.add_property(NmodlInfo::extern_neuron_variable); + symbol.add_property(NmodlType::extern_neuron_variable); THEN("symbol becomes external") { REQUIRE(symbol.is_external_symbol_only() == true); } @@ -106,12 +106,12 @@ SCENARIO("Symbol operations") { property = property2 | property3; REQUIRE(symbol.has_all_properties(property) == true); - property |= NmodlInfo::to_solve; + property |= NmodlType::to_solve; REQUIRE(symbol.has_all_properties(property) == false); } } WHEN("combined properties") { - SymbolInfo property = NmodlInfo::factor_def | NmodlInfo::global_var; + NmodlTypeFlag property = NmodlType::factor_def | NmodlType::global_var; THEN("symbol has union of all properties") { REQUIRE(symbol.has_properties(property) == false); symbol.combine_properties(property); @@ -167,14 +167,14 @@ SCENARIO("Symbol table operations") { } WHEN("checked for global variables") { table->insert(symbol); - auto variables = table->get_variables_with_properties(NmodlInfo::range_var); + auto variables = table->get_variables_with_properties(NmodlType::range_var); THEN("table doesn't have any global variables") { REQUIRE(variables.empty()); WHEN("added global symbol") { auto next_symbol = std::make_shared("gamma", ModToken()); - next_symbol->add_property(NmodlInfo::dependent_def); + next_symbol->add_property(NmodlType::dependent_def); table->insert(next_symbol); - auto variables = table->get_variables_with_properties(NmodlInfo::dependent_def); + auto variables = table->get_variables_with_properties(NmodlType::dependent_def); THEN("table has global variable") { REQUIRE(variables.size() == 1); } @@ -197,40 +197,40 @@ SCENARIO("Symbol table operations") { auto symbol3 = std::make_shared("gamma", ModToken()); auto symbol4 = std::make_shared("delta", ModToken()); - symbol1->add_property(NmodlInfo::range_var | NmodlInfo::param_assign); - symbol2->add_property(NmodlInfo::range_var | NmodlInfo::param_assign | - NmodlInfo::state_var); - symbol3->add_property(NmodlInfo::range_var | NmodlInfo::dependent_def | - NmodlInfo::pointer_var); - symbol4->add_property(NmodlInfo::range_var); + symbol1->add_property(NmodlType::range_var | NmodlType::param_assign); + symbol2->add_property(NmodlType::range_var | NmodlType::param_assign | + NmodlType::state_var); + symbol3->add_property(NmodlType::range_var | NmodlType::dependent_def | + NmodlType::pointer_var); + symbol4->add_property(NmodlType::range_var); table->insert(symbol1); table->insert(symbol2); table->insert(symbol3); table->insert(symbol4); - auto result = table->get_variables_with_properties(NmodlInfo::range_var); + auto result = table->get_variables_with_properties(NmodlType::range_var); REQUIRE(result.size() == 4); result = - table->get_variables_with_properties(NmodlInfo::range_var | NmodlInfo::pointer_var); + table->get_variables_with_properties(NmodlType::range_var | NmodlType::pointer_var); REQUIRE(result.size() == 4); - auto with = NmodlInfo::range_var | NmodlInfo::param_assign; - auto without = NmodlInfo::state_var | NmodlInfo::pointer_var; + auto with = NmodlType::range_var | NmodlType::param_assign; + auto without = NmodlType::state_var | NmodlType::pointer_var; result = table->get_variables(with, without); REQUIRE(result.size() == 1); REQUIRE(result[0]->get_name() == "alpha"); - with = NmodlInfo::range_var; - without = NmodlInfo::param_assign | NmodlInfo::dependent_def; + with = NmodlType::range_var; + without = NmodlType::param_assign | NmodlType::dependent_def; result = table->get_variables(with, without); REQUIRE(result.size() == 1); REQUIRE(result[0]->get_name() == "delta"); - with = NmodlInfo::range_var; - without = NmodlInfo::range_var; + with = NmodlType::range_var; + without = NmodlType::range_var; result = table->get_variables(with, without); REQUIRE(result.empty()); } @@ -250,9 +250,9 @@ SCENARIO("Model symbol table operations") { auto symbol2 = std::make_shared("alpha", ModToken()); auto symbol3 = std::make_shared("alpha", ModToken()); - symbol1->add_property(NmodlInfo::param_assign); - symbol2->add_property(NmodlInfo::range_var); - symbol3->add_property(NmodlInfo::range_var); + symbol1->add_property(NmodlType::param_assign); + symbol2->add_property(NmodlType::range_var); + symbol3->add_property(NmodlType::range_var); SymbolTable* old_symtab = nullptr; @@ -289,7 +289,7 @@ SCENARIO("Model symbol table operations") { mod_symtab.insert(symbol2); THEN("only one symbol gets added with combined properties") { auto symbol = mod_symtab.lookup("alpha"); - auto properties = NmodlInfo::param_assign | NmodlInfo::range_var; + auto properties = NmodlType::param_assign | NmodlType::range_var; REQUIRE(symbol->get_properties() == properties); } } diff --git a/test/nmodl/transpiler/utils/test_utils.cpp b/test/nmodl/transpiler/utils/test_utils.cpp index 4d57f1290e..1dd127a931 100644 --- a/test/nmodl/transpiler/utils/test_utils.cpp +++ b/test/nmodl/transpiler/utils/test_utils.cpp @@ -65,4 +65,4 @@ std::string reindent_text(const std::string& text) { } } return indented_text; -} \ No newline at end of file +} diff --git a/test/nmodl/transpiler/utils/test_utils.hpp b/test/nmodl/transpiler/utils/test_utils.hpp index f0070534bd..6612d28139 100644 --- a/test/nmodl/transpiler/utils/test_utils.hpp +++ b/test/nmodl/transpiler/utils/test_utils.hpp @@ -3,4 +3,4 @@ std::string reindent_text(const std::string& text); -#endif \ No newline at end of file +#endif diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index e0c5a16ca3..fb6fe17a70 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -177,23 +177,23 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { v.visit_program(ast.get()); auto symtab = ast->get_model_symbol_table(); - using namespace symtab::details; + using namespace syminfo; THEN("Can lookup for defined variables") { auto symbol = symtab->lookup("m"); - REQUIRE(symbol->has_properties(NmodlInfo::dependent_def)); - REQUIRE_FALSE(symbol->has_properties(NmodlInfo::local_var)); + REQUIRE(symbol->has_properties(NmodlType::dependent_def)); + REQUIRE_FALSE(symbol->has_properties(NmodlType::local_var)); symbol = symtab->lookup("gNaTs2_tbar"); - REQUIRE(symbol->has_properties(NmodlInfo::param_assign)); - REQUIRE(symbol->has_properties(NmodlInfo::range_var)); + REQUIRE(symbol->has_properties(NmodlType::param_assign)); + REQUIRE(symbol->has_properties(NmodlType::range_var)); symbol = symtab->lookup("ena"); - REQUIRE(symbol->has_properties(NmodlInfo::read_ion_var)); + REQUIRE(symbol->has_properties(NmodlType::read_ion_var)); } THEN("Can lookup for defined functions") { auto symbol = symtab->lookup("hBetaf"); - REQUIRE(symbol->has_properties(NmodlInfo::function_block)); + REQUIRE(symbol->has_properties(NmodlType::function_block)); } THEN("Non existent variable lookup returns nullptr") { REQUIRE(symtab->lookup("xyz") == nullptr); @@ -1255,7 +1255,7 @@ std::vector run_defuse_visitor(const std::string& text, const std::stri DefUseAnalyzeVisitor v(ast->get_symbol_table()); for (auto& block : ast->blocks) { - if (block->get_type() != ast::Type::NEURON_BLOCK) { + if (block->get_type() != ast::AstNodeType::NEURON_BLOCK) { chains.push_back(v.analyze(block.get(), variable)); } } From e6d58bfe22ef7430674e45acac0516e8423a3cf2 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Sun, 30 Dec 2018 21:43:04 +0100 Subject: [PATCH 098/871] Python ast generator refactoring: - use string format instead of append - remove add_line and buffered flush from printer - rename ast_utils.hpp to ast_common.hpp - constructors updated to use raw pointers directly - use emplace_back instead of push_back Change-Id: Ie4bcc783cc4a7bd671b5f6f5b4b5f9c2a1498880 NMODL Repo SHA: BlueBrain/nmodl@6336774a68a1b92b24e6a4226cc780f2c348d9f7 --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/ast/CMakeLists.txt | 4 +- .../ast/{ast_utils.hpp => ast_common.hpp} | 19 ++ src/nmodl/language/ast_printer.py | 264 ++++++------------ src/nmodl/language/nmodl.yaml | 1 + src/nmodl/language/nodes.py | 24 +- src/nmodl/language/parser.py | 2 +- src/nmodl/language/printer.py | 21 -- 8 files changed, 134 insertions(+), 203 deletions(-) rename src/nmodl/ast/{ast_utils.hpp => ast_common.hpp} (95%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 1f3584e78c..0f40501727 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -33,7 +33,7 @@ include(FindPythonModule) # Find required python packages #============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp REQUIRED) +find_package(PythonInterp 2.7 REQUIRED) find_python_module(yaml REQUIRED) include_directories( diff --git a/src/nmodl/ast/CMakeLists.txt b/src/nmodl/ast/CMakeLists.txt index 815d0e961c..07e879079f 100644 --- a/src/nmodl/ast/CMakeLists.txt +++ b/src/nmodl/ast/CMakeLists.txt @@ -2,7 +2,7 @@ # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT - ${PROJECT_SOURCE_DIR}/src/ast/ast_utils.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE -) \ No newline at end of file +) diff --git a/src/nmodl/ast/ast_utils.hpp b/src/nmodl/ast/ast_common.hpp similarity index 95% rename from src/nmodl/ast/ast_utils.hpp rename to src/nmodl/ast/ast_common.hpp index 9406c13454..23c4c8b339 100644 --- a/src/nmodl/ast/ast_utils.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -95,6 +95,25 @@ namespace ast { throw std::runtime_error("get_symbol_table() not implemented"); } + virtual std::shared_ptr get_statement_block() { + throw std::runtime_error("get_statement_node not implemented"); + } + + // implemented in Number sub classes + virtual void negate() { + throw std::runtime_error("negate() not implemented"); + } + + // implemented in Number sub classes + virtual double number_value() { + throw std::runtime_error("number_value() not implemented"); + } + + // implemented in Identifier sub classes + virtual void set_name(std::string /*name*/) { + throw std::runtime_error("set_name() not implemented"); + } + virtual ~AST() { } diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 8d1a945b5a..82e4ae4ec0 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -13,21 +13,20 @@ def headers(self): def class_name_declaration(self): self.write_line("namespace ast {", newline=2, post_gutter=1) self.write_line("/* forward declarations of AST classes */") - for node in self.nodes: - self.write_line("class " + node.class_name + ";") + for node in sorted(self.nodes): + self.write_line("class {};".format(node.class_name)) self.write_line() self.write_line("/* Type for every ast node */") self.write_line("enum class AstNodeType {", post_gutter=1) - for node in self.nodes: - self.write_line(to_snake_case(node.class_name).upper() + ",") - self.write_line("};", pre_gutter=-1) + for node in sorted(self.nodes): + self.write_line("{},".format(to_snake_case(node.class_name).upper())) + self.write_line("};", pre_gutter=-1, newline=2) - self.write_line() self.write_line("/* std::vector for convenience */") - for node in self.nodes: - typename = "std::vector>" - self.write_line("using " + node.class_name + "Vector = " + typename + ";") + for node in sorted(self.nodes): + typename = "std::vector>".format(node.class_name) + self.write_line("using {}Vector = {};".format(node.class_name, typename)) def declaration_end(self): self.write_line(pre_gutter=-1) @@ -45,7 +44,6 @@ def post_declaration(self): class AstDeclarationPrinter(DeclarationPrinter): """Prints all AST nodes class declarations""" - def headers(self): self.write_line("#include ") self.write_line("#include ") @@ -53,7 +51,7 @@ def headers(self): self.write_line("#include ", newline=2) self.write_line('#include "ast/ast_decl.hpp"') - self.write_line('#include "ast/ast_utils.hpp"') + self.write_line('#include "ast/ast_common.hpp"') self.write_line('#include "lexer/modtoken.hpp"') self.write_line('#include "utils/common_utils.hpp"') self.write_line('#include "visitors/visitor.hpp"', newline=2) @@ -62,46 +60,21 @@ def ast_classes_declaration(self): # TODO: for demo, removing error messages in get_token method. # need to look in detail whether we should abort in this case - # when ModToken in NULL. This was introduced when added SymtabVisitor + # when ModToken is NULL. This was introduced when added SymtabVisitor # pass to get Token information. + # todo : need a way to setup location + # self.write_line("void setLocation(nmodl::location loc) {}") + self.write_line("/* Define all AST nodes */", newline=2) for node in self.nodes: - class_decl = "class " + node.class_name + " : public " - - if node.base_class: - class_decl += node.base_class - else: - class_decl += " AST" - # depending on if node is abstract or not, we have to - # add virtual or override keyword to methods - if node.is_abstract: - virtual = "virtual " - override = "" - else: - virtual = "" - override = " override" - - self.write_line(class_decl + " {", post_gutter=1) - self.write_line("public:", post_gutter=1) - - members = [] - members_with_types = [] + self.write_line("class {} : public {} {{".format(node.class_name, node.base_class), post_gutter=1) + self.write_line("public:", post_gutter=1, newline=1) for child in node.children: - members.append(child.get_typename() + " " + child.varname) - members_with_types.append(child.get_typename_as_member() + " " + child.varname) - - if members: - self.write_line("/* member variables */") - - # todo : need a way to setup location - # self.write_line("void setLocation(nmodl::location loc) {}") - - for member in members_with_types: - self.write_line(member + ";") + self.write_line("{} {};".format(child.member_typename, child.varname)) if node.has_token: self.write_line("std::shared_ptr token;") @@ -112,93 +85,77 @@ def ast_classes_declaration(self): if node.is_program_node(): self.write_line("symtab::ModelSymbolTable model_symtab;") - self.write_line("") - - if members: + if node.children: + members = [ child.get_typename() + " " + child.varname for child in node.children] + self.write_line("") self.write_line("/* constructors */") - self.write_line(node.class_name + "(" + (", ".join(members)) + ");") - self.write_line(node.class_name + "(const " + node.class_name + "& obj);") + self.write_line("{}({});".format(node.class_name, (", ".join(members)))) + self.write_line("{0}(const {0}& obj);".format(node.class_name)) + + self.write_line("") # program node holds statements and blocks and we instantiate it in driver # also other nodes which we use as value type, parsor needs to return them # as a value. And instantiation of those causes error if default constructor not there. if node.is_program_node() or node.is_ptr_excluded_node(): - self.write_line( node.class_name + "() {}", newline=2) + self.write_line( node.class_name + "() = default;", newline=2) + + # depending on if node is abstract or not, we have to + # add virtual or override keyword to methods + virtual, override = ("virtual ", "") if node.is_abstract else ("", " override") # Todo : We need virtual destructor otherwise there will be memory leaks. # But we need define which are virtual base classes that needs virtual function. - self.write_line("virtual ~" + node.class_name + "() {}") - self.write_line() - - get_method_added = False + self.write_line("virtual ~{}() {{}}".format(node.class_name), newline=2) for child in node.children: class_name = child.class_name varname = child.varname if child.add_method: - self.write_line("void add" + class_name + "(" + class_name + " *s) {") - self.write_line(" " + varname + ".push_back(std::shared_ptr<" + class_name + ">(s));") + self.write_line("void add{0}({0} *s) {{".format(class_name)) + self.write_line(" {}.emplace_back(s);".format(varname)) self.write_line("}") if child.getname_method: # string node should be evaluated and hence eval() method - if child.is_string_node(): - method = "eval" - else: - method = "get_name" - + method = "eval" if child.is_string_node() else "get_name" self.write_line("virtual std::string get_name() override {") - self.write_line(" return " + varname + "->" + method + "();") + self.write_line(" return {}->{}();".format(varname, method)) self.write_line("}") - if not node.has_token: - self.write_line(virtual + "ModToken* get_token() override {") - self.write_line(" return " + varname + "->get_token();") - self.write_line("}") - - get_method_added = True - + # todo : return type should go into node : refactor when changing return types for all ast nodes if child.getter_method: getter_method = child.getter_method - getter_override = " override" if child.getter_override else "" - if child.is_vector: - return_type = class_name + "Vector& " - else: - if child.is_ptr_excluded_node() or child.is_base_type_node(): - return_type = class_name + " " - else: - return_type = "std::shared_ptr<" + class_name + "> " - self.write_line(return_type + getter_method + "()" + getter_override + " { return " + varname + "; }") + getter_override = "override" if child.getter_override else "" + return_type = child.return_typename + self.write_line("{} {}() {}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) - if node.is_prime_node() and child.varname == ORDER_VAR_NAME: - self.write_line("int get_order() " + " { return " + ORDER_VAR_NAME + "->eval(); }") + if node.is_prime_node(): + self.write_line("int get_order() {{ return {}->eval(); }}".format(ORDER_VAR_NAME)) # add method to return typename - self.write_line("virtual std::string get_type_name() override { return \"" + node.class_name + "\"; }") + self.write_line('virtual std::string get_type_name() override {{ return "{}"; }}'.format(node.class_name)) # all member functions - self.write_line(virtual + "void visit_children (Visitor* v) override;") - self.write_line(virtual + "void accept(Visitor* v) override { v->visit_" + to_snake_case(node.class_name) + "(this); }") + self.write_line("{}void visit_children (Visitor* v) override;".format(virtual)) + self.write_line("{}void accept(Visitor* v) override {{ v->visit_{}(this); }}".format(virtual, to_snake_case(node.class_name))) # TODO: type should declared as enum class typename = to_snake_case(node.class_name).upper() - self.write_line(virtual + "AstNodeType get_type() override { return AstNodeType::" + typename + "; }") - self.write_line("bool is_" + to_snake_case(node.class_name) + " () override { return true; }") - self.write_line(virtual + node.class_name + "* clone() override { return new " + node.class_name + "(*this); }") + self.write_line("{}AstNodeType get_type() override {{ return AstNodeType::{}; }}".format(virtual, typename)) + self.write_line("bool is_{} () override {{ return true; }}".format(to_snake_case(node.class_name))) + self.write_line("{0}{1}* clone() override {{ return new {1}(*this); }}".format(virtual, node.class_name)) if node.has_token: - self.write_line(virtual + "ModToken* get_token() " + override + " { return token.get(); }") - self.write_line("void set_token(ModToken& tok) " + " { token = std::shared_ptr(new ModToken(tok)); }") + self.write_line("{}ModToken* get_token(){} {{ return token.get(); }}".format(virtual, override)) + self.write_line("void set_token(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); }") if node.is_symtab_needed(): - self.write_line("void set_symbol_table(symtab::SymbolTable* newsymtab) override " + " { symtab = newsymtab; }") - self.write_line("symtab::SymbolTable* get_symbol_table() override " + " { return symtab; }") + self.write_line("void set_symbol_table(symtab::SymbolTable* newsymtab) override { symtab = newsymtab; }") + self.write_line("symtab::SymbolTable* get_symbol_table() override { return symtab; }") if node.is_program_node(): - self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() " + " { return &model_symtab; }") - - if node.is_number_node(): - self.write_line(virtual + "void negate()" + override + " { std::cout << \"ERROR : negate() not implemented! \"; abort(); } ") + self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() { return &model_symtab; }") if node.is_base_class_number_node(): if node.is_boolean_node(): @@ -207,36 +164,23 @@ def ast_classes_declaration(self): self.write_line("void negate() override { value = -value; }") self.write_line("double number_value() override { return value; }") - - if node.is_identifier_node(): - self.write_line(virtual + "void set_name(std::string /*name*/)" + override + " { std::cout << \"ERROR : set_name() not implemented! \"; abort(); }") - - if node.is_number_node(): - self.write_line(virtual + "double number_value()" + override + " { std::cout << \"ERROR : number_value() not implemented! \"; abort(); }") - if node.is_name_node(): - self.write_line(virtual + "void set_name(std::string name)" + override + " { value->set(name); }") + self.write_line("{}void set_name(std::string name){} {{ value->set(name); }}".format(virtual, override)) if node.is_base_block_node(): - self.write_line("virtual std::shared_ptr get_statement_block() {") - self.write_line(' throw std::runtime_error("get_statement_node not implemented");') - self.write_line("}", newline=2) - self.write_line("virtual ArgumentVector& get_arguments() {") self.write_line(' throw std::runtime_error("get_arguments not implemented");') self.write_line("}", newline=2) # if node is of enum type then return enum value - # TODO: hardcoded Names if node.is_data_type_node(): data_type = node.get_data_type_name() if node.is_enum_node(): - self.write_line("std::string " + " eval() { return " + data_type + "Names[value]; }") + self.write_line("std::string eval() {{ return {}Names[value]; }}".format(data_type)) # But if basic data type then eval return their value - # TODO: value member is hardcoded else: - self.write_line(data_type + " eval() { return value; }") - self.write_line("void set(" + data_type + " _value) " + " { value = _value; }") + self.write_line("{} eval() {{ return value; }}".format(data_type)) + self.write_line("void set({} _value) {{ value = _value; }}".format(data_type)) self.write_line("};", pre_gutter=-2, newline=2) @@ -269,89 +213,59 @@ def definitions(self): for node in self.nodes: # first get types of all childrens into members vector - members = [] - - # in order to print initializer list, we need to know which - # members are no pointer types - ptr_members = [] - non_ptr_members = [] - - for child in node.children: - members.append((child.get_typename(), child.varname)) + members = [(child.get_typename(), child.varname) for child in node.children] + non_base_members = [child for child in node.children if not child.is_base_type_node()] - if child.is_pointer_node() and not child.is_vector: - ptr_members.append((child.get_typename(), child.varname)) - else: - non_ptr_members.append((child.get_typename(), child.varname)) - - if child.is_base_type_node(): - continue + self.write_line("/* visit method for {} ast node */".format(node.class_name)) + self.write_line("void {}::visit_children(Visitor* v) {{".format(node.class_name), post_gutter=1) - self.writer.increase_gutter() + for child in non_base_members: if child.is_vector: - # TODO : remove this with C++11 style - self.add_line("for (auto& item : this->" + child.varname + ") {") - self.add_line(" item->accept(v);") - self.add_line("}") - elif child.optional or child.is_statement_block_node(): - self.add_line("if (this->" + child.varname + ") {") - self.add_line(" this->" + child.varname + "->accept(v);") - self.add_line("}") - elif not child.is_pointer_node(): - self.add_line(child.varname + ".accept(v);") + self.write_line("for (auto& item : this->{}) {{".format(child.varname)) + self.write_line(" item->accept(v);") + self.write_line("}") + elif child.optional: + self.write_line("if (this->{}) {{".format(child.varname)) + self.write_line(" this->{}->accept(v);".format(child.varname)) + self.write_line("}") + elif child.is_pointer_node(): + self.write_line("{}->accept(v);".format(child.varname)) else: - self.add_line(child.varname + "->accept(v);") - self.writer.decrease_gutter() + self.write_line("{}.accept(v);".format(child.varname)) - if self.writer.num_buffered_lines(): - self.write_line("/* visit method for " + node.class_name + " ast node */") - self.write_line("void " + node.class_name + "::visit_children(Visitor* v) {", post_gutter=1) - self.writer.flush_buffered_lines() - self.write_line("}", pre_gutter=-1, newline=2) - else: - self.write_line("void " + node.class_name + "::visit_children(Visitor* /*v*/) {}") + if not non_base_members: + self.write_line("(void)v;") + + self.write_line("}", pre_gutter=-1, newline=2) if members: - # TODO : constructor definition : remove this with C++11 style - self.write_line("/* constructor for " + node.class_name + " ast node */") arguments = (", ".join(map(lambda x: x[0] + " " + x[1], members))) - self.write_line(node.class_name + "::" + node.class_name + "(" + arguments + ")") - - if non_ptr_members: - self.write_line(":", newline=0) - self.write_line(",\n".join(map(lambda x: x[1] + "(" + x[1] + ")", non_ptr_members))) + initializer = ", ".join(map(lambda x: x[1] + "(" + x[1] + ")", members)) - self.write_line("{") + self.write_line("/* constructor for {} ast node */".format(node.class_name)) + self.write_line("{0}::{0}({1})".format(node.class_name, arguments)) + self.write_line(" : {}".format(initializer)) + self.write_line("{}", newline=2) - for member in ptr_members: - # todo : bit hack here, need to remove pointer because we are creating smart pointer - typename = member[0].replace("*", "") - self.write_line(" this->" + member[1] + " = std::shared_ptr<" + typename + ">(" + member[1] + ");") + self.write_line("/* copy constructor for {} ast node */".format(node.class_name)) + self.write_line("{0}::{0}(const {0}& obj) {{".format(node.class_name), post_gutter=1) - self.write_line("}", newline=2) - - # copy construcotr definition : remove this with C++11 style - self.write_line("/* copy constructor for " + node.class_name + " ast node */") - self.write_line(node.class_name + "::" + node.class_name + "(const " + node.class_name + "& obj) ") - - self.write_line("{", post_gutter=1) - - # TODO : more cleanup for child in node.children: if child.is_vector: - self.write_line("for (auto& item : obj." + child.varname + ") {") - self.write_line(" this->" + child.varname + ".push_back(std::shared_ptr< " + child.class_name + ">(item->clone()));") + self.write_line("for (auto& item : obj.{}) {{".format(child.varname)) + self.write_line(" this->{}.emplace_back(item->clone());".format(child.varname)) + self.write_line("}") + elif child.is_pointer_node() or child.optional: + self.write_line("if (obj.{}) {{".format(child.varname)) + self.write_line(" this->{0}.reset(obj.{0}->clone());".format(child.varname)) self.write_line("}") else: - if child.is_pointer_node(): - self.write_line("if (obj." + child.varname + ")") - self.write_line(" this->" + child.varname + " = std::shared_ptr<" + child.class_name + ">(obj." + child.varname + "->clone());") - else: - self.write_line("this->" + child.varname + " = obj." + child.varname + ";") + self.write_line("this->{0} = obj.{0};".format(child.varname)) if node.has_token: - self.write_line("if (obj.token)") + self.write_line("if (obj.token) {") self.write_line(" this->token = std::shared_ptr(obj.token->clone());") + self.write_line("}") self.write_line("}", pre_gutter=-1, newline=2) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 42f8edd605..d41167fa49 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -934,6 +934,7 @@ getter: {name: get_method} - ifsolerr: type: StatementBlock + optional: true prefix: {value: " IFERROR "} - BreakpointBlock: nmodl: "BREAKPOINT " diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index d258d31f4a..bc74287ffe 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -22,6 +22,9 @@ def __init__(self, args): self.force_suffix = args.force_suffix self.is_abstract = False + def __cmp__(self, other): + return cmp(self.class_name, other.class_name) + def get_data_type_name(self): """ return type name for the node """ return DATA_TYPES[self.class_name] @@ -181,10 +184,11 @@ def get_typename(self): return type_name - def get_typename_as_member(self): - """returns type of the node to be a member of the class + @property + def member_typename(self): + """returns type when used as a member of the class - Check how to refactor this + todo: Check how to refactor this """ type_name = self.class_name @@ -196,6 +200,20 @@ def get_typename_as_member(self): return type_name + @property + def return_typename(self): + """returns type when returned from getter method + + todo: Check how to refactor this + """ + type_name = "std::shared_ptr<" + self.class_name + ">" + if self.is_vector: + type_name = self.class_name + "Vector&" + elif self.is_ptr_excluded_node() or self.is_base_type_node(): + type_name = self.class_name + + return type_name + class Node(BaseNode): """represent a class for every rule in language specification""" diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index b0e181aacd..0f72d3ef0c 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -148,7 +148,7 @@ def parse_yaml_rules(self, nodelist, base_class=None): has_token = LanguageParser.is_token(class_name) args = Argument() - args.base_class = base_class + args.base_class = base_class if base_class else 'AST' args.class_name = class_name args.nmodl_name = nmodl_name args.has_token = has_token diff --git a/src/nmodl/language/printer.py b/src/nmodl/language/printer.py index d120bda441..bfe1c632e8 100644 --- a/src/nmodl/language/printer.py +++ b/src/nmodl/language/printer.py @@ -45,24 +45,6 @@ def write_line(self, string, newline, pre_gutter, post_gutter): self.fh.write("\n") self.num_tabs += post_gutter - def add_line(self, string, newline, pre_gutter, post_gutter): - self.num_tabs += pre_gutter - if string: - for i in range(self.num_tabs): - string = self.TAB + string - for i in range(newline): - string = string + "\n" - self.lines.append(string) - self.num_tabs += post_gutter - - def num_buffered_lines(self): - return len(self.lines) - - def flush_buffered_lines(self): - for line in self.lines: - self.fh.write(line) - self.lines = [] - def __del__(self): if not self.fh.closed: self.fh.close() @@ -80,9 +62,6 @@ def __init__(self, filepath, classname, nodes): def write_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): self.writer.write_line(string, newline, pre_gutter, post_gutter) - def add_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): - self.writer.add_line(string, newline, pre_gutter, post_gutter) - @abstractmethod def write(self): pass From 305b6609bde1600ce9b9c836f9e200e79f4016b1 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Mon, 31 Dec 2018 00:35:34 +0100 Subject: [PATCH 099/871] Remove use push_back withemplace_back in nmodl parser Change-Id: I83760b4b976964d1cb18577eda2bcecde9e4144e NMODL Repo SHA: BlueBrain/nmodl@6a625ea7f0b3efd02a53d3e1430537d4fef4c7d8 --- src/nmodl/parser/nmodl.yy | 166 +++++++++++++------------------ src/nmodl/utils/common_utils.cpp | 3 +- 2 files changed, 72 insertions(+), 97 deletions(-) diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 6106a9825e..b92d17214c 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -537,7 +537,7 @@ parmbody : { } | parmbody parmasgn { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } ; @@ -608,7 +608,7 @@ stepblk : STEPPED "{" stepbdy "}" stepbdy : { $$ = ast::SteppedVector(); } | stepbdy stepped { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } ; @@ -624,12 +624,12 @@ stepped : NAME "=" numlist units numlist : number "," number { $$ = ast::NumberVector(); - $$.push_back(std::shared_ptr($1)); - $$.push_back(std::shared_ptr($3)); + $$.emplace_back($1); + $$.emplace_back($3); } | numlist "," number { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $$ = $1; } ; @@ -680,12 +680,12 @@ indepbody : { } | indepbody indepdef { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } | indepbody SWEEP indepdef { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $3->sweep = std::shared_ptr(new ast::Boolean(1)); $$ = $1; } @@ -719,7 +719,7 @@ depbody : { } | depbody depdef { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } ; @@ -780,13 +780,13 @@ pvlist : name optindex { $$ = ast::PlotVarVector(); auto variable = new ast::PlotVar($1, $2); - $$.push_back(std::shared_ptr(variable)); + $$.emplace_back(variable); } | pvlist "," name optindex { $$ = $1; auto variable = new ast::PlotVar($3, $4); - $$.push_back(std::shared_ptr(variable)); + $$.emplace_back(variable); } ; @@ -884,20 +884,20 @@ locallist1 : NAME locoptarray $$ = ast::LocalVarVector(); if($2) { auto variable = new ast::LocalVar(new ast::IndexedName($1, $2)); - $$.push_back(std::shared_ptr(variable)); + $$.emplace_back(variable); } else { auto variable = new ast::LocalVar($1); - $$.push_back(std::shared_ptr(variable)); + $$.emplace_back(variable); } } | locallist1 "," NAME locoptarray { if($4) { auto variable = new ast::LocalVar(new ast::IndexedName($3, $4)); - $1.push_back(std::shared_ptr(variable)); + $1.emplace_back(variable); } else { auto variable = new ast::LocalVar($3); - $1.push_back(std::shared_ptr(variable)); + $1.emplace_back(variable); } $$ = $1; } @@ -914,18 +914,18 @@ stmtlist1 : { } | stmtlist1 ostmt { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } | stmtlist1 astmt { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } | stmtlist1 INLINE_COMMENT { auto statement = new ast::Comment(nullptr, new ast::String($2)); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(statement); $$ = $1; } ; @@ -1214,21 +1214,21 @@ exprlist : { | expr { $$ = ast::ExpressionVector(); - $$.push_back(std::shared_ptr($1)); + $$.emplace_back($1); } | STRING { $$ = ast::ExpressionVector(); - $$.push_back(std::shared_ptr($1)); + $$.emplace_back($1); } | exprlist "," expr { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $$ = $1; } | exprlist "," STRING { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $$ = $1; } ; @@ -1280,8 +1280,7 @@ optelseif : { } | optelseif ELSE IF "(" expr ")" stmtlist "}" { - auto statement = new ast::ElseIfStatement($5, $7); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::ElseIfStatement($5, $7)); $$ = $1; } ; @@ -1392,11 +1391,11 @@ arglist : { arglist1 : name units { $$ = ast::ArgumentVector(); - $$.push_back(std::shared_ptr(new ast::Argument($1, $2))); + $$.emplace_back(new ast::Argument($1, $2)); } | arglist1 "," name units { - $1.push_back(std::shared_ptr(new ast::Argument($3, $4))); + $1.emplace_back(new ast::Argument($3, $4)); $$ = $1; } ; @@ -1455,11 +1454,11 @@ solvefor : { $$ = ast::NameVector(); } solvefor1 : SOLVEFOR NAME { $$ = ast::NameVector(); - $$.push_back(std::shared_ptr($2)); + $$.emplace_back($2); } | solvefor1 "," NAME { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $$ = $1; } | SOLVEFOR error @@ -1603,11 +1602,11 @@ sens : SENS senslist senslist : varname { $$ = ast::VarNameVector(); - $$.push_back(std::shared_ptr($1)); + $$.emplace_back($1); } | senslist "," varname { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $$ = $1; } ; @@ -1649,11 +1648,11 @@ ldifus : LONGDIFUS NAME "," expr "{" namelist "}" namelist : NAME { $$ = ast::NameVector(); - $$.push_back(std::shared_ptr($1)); + $$.emplace_back($1); } | namelist NAME { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } ; @@ -1740,11 +1739,11 @@ matchblk : MATCH "{" matchlist "}" matchlist : match { $$ = ast::MatchVector(); - $$.push_back(std::shared_ptr($1)); + $$.emplace_back($1); } | matchlist match { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } ; @@ -1789,12 +1788,12 @@ unitbody : { } | unitbody unitdef { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } | unitbody factordef { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } ; @@ -1846,8 +1845,7 @@ conststmt : { | conststmt NAME "=" number units { auto constant = new ast::ConstantVar($2, $4, $5); - auto statement = new ast::ConstantStatement(constant); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::ConstantStatement(constant)); $$ = $1; } ; @@ -1872,11 +1870,11 @@ tablst : { $$ = ast::NameVector(); } tablst1 : Name { $$ = ast::NameVector(); - $$.push_back(std::shared_ptr($1)); + $$.emplace_back($1); } | tablst1 "," Name { - $1.push_back(std::shared_ptr($3)); + $1.emplace_back($3); $$ = $1; } ; @@ -1899,67 +1897,57 @@ neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE nrnstmt : { $$ = ast::StatementVector(); } | nrnstmt SUFFIX NAME { - auto statement = new ast::Suffix($2, $3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::Suffix($2, $3)); $$ = $1; } | nrnstmt nrnuse { - $1.push_back(std::shared_ptr($2)); + $1.emplace_back($2); $$ = $1; } | nrnstmt NONSPECIFIC nrnonspeclist { - auto statement = new ast::Nonspecific($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::Nonspecific($3)); $$ = $1; } | nrnstmt ELECTRODE_CURRENT nrneclist { - auto statement = new ast::ElctrodeCurrent($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::ElctrodeCurrent($3)); $$ = $1; } | nrnstmt SECTION nrnseclist { - auto statement = new ast::Section($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::Section($3)); $$ = $1; } | nrnstmt RANGE nrnrangelist { - auto statement = new ast::Range($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::Range($3)); $$ = $1; } | nrnstmt GLOBAL nrnglobalist { - auto statement = new ast::Global($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::Global($3)); $$ = $1; } | nrnstmt POINTER nrnptrlist { - auto statement = new ast::Pointer($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::Pointer($3)); $$ = $1; } | nrnstmt BBCOREPOINTER nrnbbptrlist { - auto statement = new ast::BbcorePtr($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::BbcorePtr($3)); $$ = $1; } | nrnstmt EXTERNAL nrnextlist { - auto statement = new ast::External($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::External($3)); $$ = $1; } | nrnstmt THREADSAFE opthsafelist { - auto statement = new ast::ThreadSafe($3); - $1.push_back(std::shared_ptr(statement)); + $1.emplace_back(new ast::ThreadSafe($3)); $$ = $1; } ; @@ -1987,11 +1975,11 @@ nrnuse : USEION NAME READ nrnionrlist valence nrnionrlist : NAME { $$ = ast::ReadIonVarVector(); - $$.push_back(std::shared_ptr(new ast::ReadIonVar($1))); + $$.emplace_back(new ast::ReadIonVar($1)); } | nrnionrlist "," NAME { - $1.push_back(std::shared_ptr(new ast::ReadIonVar($3))); + $1.emplace_back(new ast::ReadIonVar($3)); $$ = $1; } | error @@ -2004,11 +1992,11 @@ nrnionrlist : NAME nrnionwlist : NAME { $$ = ast::WriteIonVarVector(); - $$.push_back(std::shared_ptr(new ast::WriteIonVar($1))); + $$.emplace_back(new ast::WriteIonVar($1)); } | nrnionwlist "," NAME { - $1.push_back(std::shared_ptr(new ast::WriteIonVar($3))); + $1.emplace_back(new ast::WriteIonVar($3)); $$ = $1; } | error @@ -2021,13 +2009,11 @@ nrnionwlist : NAME nrnonspeclist : NAME { $$ = ast::NonspeCurVarVector(); - auto var = new ast::NonspeCurVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::NonspeCurVar($1)); } | nrnonspeclist "," NAME { - auto var = new ast::NonspeCurVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::NonspeCurVar($3)); $$ = $1; } | error @@ -2040,13 +2026,11 @@ nrnonspeclist : NAME nrneclist : NAME { $$ = ast::ElectrodeCurVarVector(); - auto var = new ast::ElectrodeCurVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::ElectrodeCurVar($1)); } | nrneclist "," NAME { - auto var = new ast::ElectrodeCurVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::ElectrodeCurVar($3)); $$ = $1; } | error @@ -2059,13 +2043,11 @@ nrneclist : NAME nrnseclist : NAME { $$ = ast::SectionVarVector(); - auto var = new ast::SectionVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::SectionVar($1)); } | nrnseclist "," NAME { - auto var = new ast::SectionVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::SectionVar($3)); $$ = $1; } | error @@ -2078,11 +2060,11 @@ nrnseclist : NAME nrnrangelist : NAME { $$ = ast::RangeVarVector(); - $$.push_back(std::shared_ptr(new ast::RangeVar($1))); + $$.emplace_back(new ast::RangeVar($1)); } | nrnrangelist "," NAME { - $1.push_back(std::shared_ptr(new ast::RangeVar($3))); + $1.emplace_back(new ast::RangeVar($3)); $$ = $1; } | error @@ -2095,11 +2077,11 @@ nrnrangelist : NAME nrnglobalist : NAME { $$ = ast::GlobalVarVector(); - $$.push_back(std::shared_ptr(new ast::GlobalVar($1))); + $$.emplace_back(new ast::GlobalVar($1)); } | nrnglobalist "," NAME { - $1.push_back(std::shared_ptr(new ast::GlobalVar($3))); + $1.emplace_back(new ast::GlobalVar($3)); $$ = $1; } | error @@ -2112,13 +2094,11 @@ nrnglobalist : NAME nrnptrlist : NAME { $$ = ast::PointerVarVector(); - auto var = new ast::PointerVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::PointerVar($1)); } | nrnptrlist "," NAME { - auto var = new ast::PointerVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::PointerVar($3)); $$ = $1; } | error @@ -2131,13 +2111,11 @@ nrnptrlist : NAME nrnbbptrlist : NAME { $$ = ast::BbcorePointerVarVector(); - auto var = new ast::BbcorePointerVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::BbcorePointerVar($1)); } | nrnbbptrlist "," NAME { - auto var = new ast::BbcorePointerVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::BbcorePointerVar($3)); $$ = $1; } | error @@ -2150,13 +2128,11 @@ nrnbbptrlist : NAME nrnextlist : NAME { $$ = ast::ExternVarVector(); - auto var = new ast::ExternVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::ExternVar($1)); } | nrnextlist "," NAME { - auto var = new ast::ExternVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::ExternVar($3)); $$ = $1; } | error @@ -2174,13 +2150,11 @@ opthsafelist : { $$ = ast::ThreadsafeVarVector(); } threadsafelist : NAME { $$ = ast::ThreadsafeVarVector(); - auto var = new ast::ThreadsafeVar($1); - $$.push_back(std::shared_ptr(var)); + $$.emplace_back(new ast::ThreadsafeVar($1)); } | threadsafelist "," NAME { - auto var = new ast::ThreadsafeVar($3); - $1.push_back(std::shared_ptr(var)); + $1.emplace_back(new ast::ThreadsafeVar($3)); $$ = $1; } ; diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 05afc28175..5896d5546e 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -1,7 +1,8 @@ +#include #include +#include #include #include -#include bool is_dir_exist(const std::string& path) { struct stat info {}; From 7867c96f1cee0532c9320991ca67595f26342954 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Mon, 31 Dec 2018 12:16:01 +0100 Subject: [PATCH 100/871] Parameters vs Argumenets consistency fixes: - function call uses argument whereas functions use parameter as variable name - get_type and get_type_name are renamed to get_node_type and get_node_type_name - return ArgumentVector instead of ArgumentVector& Change-Id: I0ae584408b6ece2b77a50d8621bac74d22d99712 NMODL Repo SHA: BlueBrain/nmodl@121b19d904384219201654d66ae1f5d109418195 --- src/nmodl/ast/ast_common.hpp | 8 +++++-- .../codegen/base/codegen_base_visitor.hpp | 12 +++++----- .../codegen/base/codegen_helper_visitor.cpp | 2 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 24 +++++++++---------- src/nmodl/codegen/c/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_info.hpp | 2 +- src/nmodl/language/ast_printer.py | 22 +++++++---------- src/nmodl/language/nmodl.yaml | 16 ++++++------- src/nmodl/language/nodes.py | 7 +++--- src/nmodl/language/visitors_printer.py | 12 +++++----- src/nmodl/symtab/symbol_table.cpp | 4 ++-- src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.cpp | 4 ++-- src/nmodl/visitors/inline_visitor.hpp | 6 ++--- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 6 ++--- src/nmodl/visitors/symtab_visitor_helper.cpp | 4 ++-- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/visitor/visitor.cpp | 2 +- 19 files changed, 69 insertions(+), 70 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 23c4c8b339..e61c3be636 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -71,8 +71,8 @@ namespace ast { /* all AST nodes provide visit children and accept methods */ virtual void visit_children(Visitor* v) = 0; virtual void accept(Visitor* v) = 0; - virtual AstNodeType get_type() = 0; - virtual std::string get_type_name() = 0; + virtual AstNodeType get_node_type() = 0; + virtual std::string get_node_type_name() = 0; virtual std::string get_name() { throw std::logic_error("get_name() not implemented"); @@ -99,6 +99,10 @@ namespace ast { throw std::runtime_error("get_statement_node not implemented"); } + virtual ArgumentVector get_parameters() { + throw std::runtime_error("get_parameters not implemented"); + } + // implemented in Number sub classes virtual void negate() { throw std::runtime_error("negate() not implemented"); diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index ec51d33cbb..7b7ae3ccf4 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -484,18 +484,18 @@ void CodegenBaseVisitor::print_vector_elements(const std::vector& elements, /** - * Check if function or procedure node has argument with given name + * Check if function or procedure node has parameter with given name * * @tparam T Node type (either procedure or function) * @param node AST node (either procedure or function) - * @param name Name of argument + * @param name Name of parameter * @return True if argument with name exist */ template -bool has_argument_of_name(const T& node, std::string name) { - auto arguments = node->get_arguments(); - for (const auto& argument : arguments) { - if (argument->get_name() == name) { +bool has_parameter_of_name(const T &node, std::string name) { + auto parameters = node->get_parameters(); + for (const auto& parameter : parameters) { + if (parameter->get_name() == name) { return true; } } diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index c8c4e6aa53..256a5f3ceb 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -433,7 +433,7 @@ void CodegenHelperVisitor::visit_initial_block(InitialBlock* node) { void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock* node) { under_net_receive_block = true; info.net_receive_node = node; - info.num_net_receive_arguments = node->get_arguments().size(); + info.num_net_receive_parameters = node->get_parameters().size(); node->visit_children(this); under_net_receive_block = false; } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 577ad26326..7faf5fc3dc 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -615,17 +615,17 @@ void CodegenCVisitor::print_function_call(FunctionCall* node) { return; } - auto arguments = node->get_arguments(); + auto parameters = node->get_parameters(); printer->add_text("{}("_format(function_name)); if (defined_method(name)) { printer->add_text(internal_method_arguments()); - if (!arguments.empty()) { + if (!parameters.empty()) { printer->add_text(", "); } } - print_vector_elements(arguments, ", "); + print_vector_elements(parameters, ", "); printer->add_text(")"); } @@ -669,12 +669,12 @@ void CodegenCVisitor::rename_function_arguments() { stringutils::trim(arg); RenameVisitor v(arg, "arg_" + arg); for (const auto& function : info.functions) { - if (has_argument_of_name(function, arg)) { + if (has_parameter_of_name(function, arg)) { function->accept(&v); } } for (const auto& function : info.procedures) { - if (has_argument_of_name(function, arg)) { + if (has_parameter_of_name(function, arg)) { function->accept(&v); } } @@ -1186,7 +1186,7 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { printer->add_newline(2); print_device_method_annotation(); printer->add_line("static inline int num_net_receive_args() {"); - printer->add_line(" return {};"_format(info.num_net_receive_arguments)); + printer->add_line(" return {};"_format(info.num_net_receive_parameters)); printer->add_line("}"); } @@ -1829,7 +1829,7 @@ void CodegenCVisitor::print_mechanism_register() { if (net_receive_buffering_required()) { printer->add_line("hoc_register_net_receive_buffering(net_buf_receive, mech_type);"); } - if (info.num_net_receive_arguments != 0) { + if (info.num_net_receive_parameters != 0) { printer->add_line("pnt_receive[mech_type] = {};"_format(method_name("net_receive"))); printer->add_line("pnt_receive_size[mech_type] = num_net_receive_args();"); if (info.net_receive_initial_node != nullptr) { @@ -2511,7 +2511,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node) { printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); /// rename variables but need to see if they are actually used - auto arguments = info.net_receive_node->get_arguments(); + auto arguments = info.net_receive_node->get_parameters(); if (!arguments.empty()) { int i = 0; printer->add_newline(); @@ -2531,7 +2531,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node) { void CodegenCVisitor::print_net_send_call(FunctionCall* node) { - auto arguments = node->get_arguments(); + auto arguments = node->get_parameters(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; std::string pnt = "pnt"; @@ -2568,7 +2568,7 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { abort(); } - auto arguments = node->get_arguments(); + auto arguments = node->get_parameters(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "-1"; std::string pnt = "pnt"; @@ -2591,7 +2591,7 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { void CodegenCVisitor::print_net_event_call(FunctionCall* node) { - auto arguments = node->get_arguments(); + auto arguments = node->get_parameters(); if (info.artificial_cell) { printer->add_text("net_event(pnt, "); print_vector_elements(arguments, ", "); @@ -2726,7 +2726,7 @@ void CodegenCVisitor::print_net_receive() { auto node = info.net_receive_node; /// rename arguments but need to see if they are actually used - auto arguments = node->get_arguments(); + auto arguments = node->get_parameters(); for (auto& argument : arguments) { auto name = argument->get_name(); auto var_used = VarUsageVisitor().variable_used(node, name); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 656e3948fb..f500286daa 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -614,7 +614,7 @@ void CodegenCVisitor::print_function_declaration(T& node, std::string name) { /// internal and user provided arguments auto internal_params = internal_method_parameters(); - auto params = node->get_arguments(); + auto params = node->get_parameters(); /// procedures have "int" return type by default std::string return_type = "int"; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index f3cadeb7d6..7ba66340f7 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -239,7 +239,7 @@ namespace codegen { ast::NetReceiveBlock* net_receive_node = nullptr; /// number of arguments to net_receive block - int num_net_receive_arguments = 0; + int num_net_receive_parameters = 0; /// initial block within net receive block ast::InitialBlock* net_receive_initial_node = nullptr; diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 82e4ae4ec0..70dd2871b2 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -88,12 +88,9 @@ def ast_classes_declaration(self): if node.children: members = [ child.get_typename() + " " + child.varname for child in node.children] self.write_line("") - self.write_line("/* constructors */") self.write_line("{}({});".format(node.class_name, (", ".join(members)))) self.write_line("{0}(const {0}& obj);".format(node.class_name)) - self.write_line("") - # program node holds statements and blocks and we instantiate it in driver # also other nodes which we use as value type, parsor needs to return them # as a value. And instantiation of those causes error if default constructor not there. @@ -106,7 +103,9 @@ def ast_classes_declaration(self): # Todo : We need virtual destructor otherwise there will be memory leaks. # But we need define which are virtual base classes that needs virtual function. - self.write_line("virtual ~{}() {{}}".format(node.class_name), newline=2) + self.write_line("virtual ~{}() {{}}".format(node.class_name)) + + self.write_line("") for child in node.children: class_name = child.class_name @@ -133,18 +132,18 @@ def ast_classes_declaration(self): if node.is_prime_node(): self.write_line("int get_order() {{ return {}->eval(); }}".format(ORDER_VAR_NAME)) - # add method to return typename - self.write_line('virtual std::string get_type_name() override {{ return "{}"; }}'.format(node.class_name)) - # all member functions self.write_line("{}void visit_children (Visitor* v) override;".format(virtual)) self.write_line("{}void accept(Visitor* v) override {{ v->visit_{}(this); }}".format(virtual, to_snake_case(node.class_name))) + self.write_line("{0}{1}* clone() override {{ return new {1}(*this); }}".format(virtual, node.class_name)) + + self.write_line() # TODO: type should declared as enum class typename = to_snake_case(node.class_name).upper() - self.write_line("{}AstNodeType get_type() override {{ return AstNodeType::{}; }}".format(virtual, typename)) + self.write_line("{}AstNodeType get_node_type() override {{ return AstNodeType::{}; }}".format(virtual, typename)) + self.write_line('{}std::string get_node_type_name() override {{ return "{}"; }}'.format(virtual, node.class_name)) self.write_line("bool is_{} () override {{ return true; }}".format(to_snake_case(node.class_name))) - self.write_line("{0}{1}* clone() override {{ return new {1}(*this); }}".format(virtual, node.class_name)) if node.has_token: self.write_line("{}ModToken* get_token(){} {{ return token.get(); }}".format(virtual, override)) @@ -167,11 +166,6 @@ def ast_classes_declaration(self): if node.is_name_node(): self.write_line("{}void set_name(std::string name){} {{ value->set(name); }}".format(virtual, override)) - if node.is_base_block_node(): - self.write_line("virtual ArgumentVector& get_arguments() {") - self.write_line(' throw std::runtime_error("get_arguments not implemented");') - self.write_line("}", newline=2) - # if node is of enum type then return enum value if node.is_data_type_node(): data_type = node.get_data_type_name() diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index d41167fa49..0d079566bb 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -857,7 +857,7 @@ - name: type: Name getname: true - - arguments: + - parameters: type: Argument vector: true prefix: {value: "(", force: true} @@ -873,13 +873,13 @@ - name: type: Name getname: true - - arguments: + - parameters: type: Argument vector: true prefix: {value: "(", force: true} suffix: {value: ")", force: true} separator: ", " - getter: {name: get_arguments, override: true} + getter: {name: get_parameters, override: true} - unit: type: Unit optional: true @@ -894,13 +894,13 @@ - name: type: Name getname: true - - arguments: + - parameters: type: Argument vector: true prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - getter: {name: get_arguments, override: true} + getter: {name: get_parameters, override: true} - unit: type: Unit optional: true @@ -910,13 +910,13 @@ - NetReceiveBlock: nmodl: "NET_RECEIVE " members: - - arguments: + - parameters: type: Argument vector: true prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - getter: {name: get_arguments, override: true} + getter: {name: get_parameters, override: true} - statementblock: type: StatementBlock getter: {name: get_statement_block, override: true} @@ -969,7 +969,7 @@ - ForNetcon: nmodl: "FOR_NETCONS " members: - - arguments: + - parameters: type: Argument vector: true prefix: {value: "(", force: true} diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index bc74287ffe..19cb24dcf1 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -179,7 +179,7 @@ def get_typename(self): if self.is_vector: type_name += "Vector" - elif self.class_name not in BASE_TYPES and self.class_name not in PTR_EXCLUDE_TYPES: + elif not self.is_base_type_node() and not self.is_ptr_excluded_node(): type_name += "*" return type_name @@ -195,7 +195,7 @@ def member_typename(self): if self.is_vector: type_name += "Vector" - elif self.class_name not in BASE_TYPES and self.class_name not in PTR_EXCLUDE_TYPES: + elif not self.is_base_type_node() and not self.is_ptr_excluded_node(): type_name = "std::shared_ptr<" + type_name + ">" return type_name @@ -207,8 +207,9 @@ def return_typename(self): todo: Check how to refactor this """ type_name = "std::shared_ptr<" + self.class_name + ">" + if self.is_vector: - type_name = self.class_name + "Vector&" + type_name = self.class_name + "Vector" elif self.is_ptr_excluded_node() or self.is_base_type_node(): type_name = self.class_name diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index dc5d8c78ea..610d9640fc 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -119,15 +119,15 @@ def definitions(self): self.write_line(line, post_gutter=1) if node.has_children(): - self.write_line("printer->push_block(node->get_type_name());") + self.write_line("printer->push_block(node->get_node_type_name());") self.write_line("node->visit_children(this);") if node.is_data_type_node(): if node.class_name == "Integer": - self.write_line("if(!node->macroname) {", post_gutter=1) - self.write_line("std::stringstream ss;") - self.write_line("ss << node->eval();") - self.write_line("printer->add_node(ss.str());", post_gutter=-1) + self.write_line("if(!node->macroname) {") + self.write_line(" std::stringstream ss;") + self.write_line(" ss << node->eval();") + self.write_line(" printer->add_node(ss.str());") self.write_line("}") else: self.write_line("std::stringstream ss;") @@ -260,6 +260,6 @@ def definitions(self): self.write_line("add_model_symbol_with_property(node, %s);" % property_name) self.write_line("setup_symbol_table_for_scoped_block(node, node->get_name());") else: - self.write_line("setup_symbol_table_for_scoped_block(node, node->get_type_name());") + self.write_line("setup_symbol_table_for_scoped_block(node, node->get_node_type_name());") self.write_line("}", pre_gutter=-1, newline=2) diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 5a7257e387..26b0d20c50 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -44,7 +44,7 @@ namespace symtab { std::string SymbolTable::type() const { - return node->get_type_name(); + return node->get_node_type_name(); } bool SymbolTable::is_method_defined(const std::string& name) const { @@ -202,7 +202,7 @@ namespace symtab { auto properties = to_string(second->get_properties()); std::string type = "UNKNOWN"; if (node != nullptr) { - type = node->get_type_name(); + type = node->get_node_type_name(); } if (redefinition) { diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 3d1d8893c3..4a9e7a5899 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -299,7 +299,7 @@ DUChain DefUseAnalyzeVisitor::analyze(ast::Node* node, const std::string& name) unsupported_node = false; /// new chain - DUChain usage(node->get_type_name()); + DUChain usage(node->get_node_type_name()); current_chain = &usage.chain; /// analyze given node diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index f12f56c6eb..75e3223c4b 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -61,7 +61,7 @@ bool InlineVisitor::can_replace_statement(const std::shared_ptr& stat } void InlineVisitor::inline_arguments(StatementBlock* inlined_block, - const ArgumentVector& callee_arguments, + const ArgumentVector& callee_parameters, const ExpressionVector& caller_expressions) { /// nothing to inline if no arguments for function call if (caller_expressions.empty()) { @@ -72,7 +72,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, size_t counter = 0; - for (const auto& argument : callee_arguments) { + for (const auto& argument : callee_parameters) { auto name = argument->name->clone(); auto old_name = name->get_name(); auto new_name = get_new_name(old_name, "in", inlined_variables); diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 217458b728..2894856a72 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -151,7 +151,7 @@ class InlineVisitor : public AstVisitor { /// add assignement statements into given statement block to inline arguments void inline_arguments(ast::StatementBlock* inlined_block, - const ast::ArgumentVector& callee_arguments, + const ast::ArgumentVector& callee_parameters, const ast::ExpressionVector& caller_expressions); /// add assignment statement at end of block (to use as a return statement @@ -195,7 +195,7 @@ bool InlineVisitor::inline_function_call(T* callee, LocalVarRenameVisitor v; v.visit_statement_block(caller); - auto& caller_arguments = node->arguments; + auto caller_arguments = node->get_arguments(); std::string new_varname = get_new_name(function_name, "in", inlined_variables); /// check if caller statement could be replaced @@ -229,7 +229,7 @@ bool InlineVisitor::inline_function_call(T* callee, inlined_block->set_symbol_table(nullptr); /// each argument is added as new assignment statement - inline_arguments(inlined_block, callee->arguments, caller_arguments); + inline_arguments(inlined_block, callee->get_parameters(), caller_arguments); /// to return value from procedure we have to add new variable if (callee->is_procedure_block() && to_replace == false) { diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 174d62006b..988cf54ee4 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -8,7 +8,7 @@ using namespace symtab; using namespace syminfo; bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { - auto type = node->get_type(); + auto type = node->get_node_type(); /** * Blocks where we should compute def-use chains. We are excluding diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 73e77d4ac3..a0c9b98dff 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -121,12 +121,12 @@ void PerfVisitor::measure_performance(AST* node) { auto symtab = node->get_symbol_table(); if (symtab == nullptr) { throw std::runtime_error("Perfvisitor : symbol table not setup for " + - node->get_type_name()); + node->get_node_type_name()); } auto name = symtab->name(); if (node->is_derivative_block()) { - name = node->get_type_name(); + name = node->get_node_type_name(); } if (printer) { @@ -333,7 +333,7 @@ void PerfVisitor::visit_statement_block(StatementBlock* node) { if (current_symtab == nullptr) { throw std::runtime_error("Perfvisitor : symbol table not setup for " + - node->get_type_name()); + node->get_node_type_name()); } /// new block perf starts from zero diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index 48c5ed2fc0..822554b0be 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -184,12 +184,12 @@ void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool void SymtabVisitor::setup_symbol_table_for_program_block(Program* node) { modsymtab = node->get_model_symbol_table(); modsymtab->set_mode(update); - setup_symbol_table(node, node->get_type_name(), true); + setup_symbol_table(node, node->get_node_type_name(), true); } void SymtabVisitor::setup_symbol_table_for_global_block(Node* node) { - setup_symbol_table(node, node->get_type_name(), true); + setup_symbol_table(node, node->get_node_type_name(), true); } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 1b26099ab6..b1ae5c97e2 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -20,7 +20,7 @@ std::string get_new_name(const std::string& name, LocalVarVector* get_local_variables(const StatementBlock* node) { for (const auto& statement : node->statements) { - if (statement->get_type() == AstNodeType::LOCAL_LIST_STATEMENT) { + if (statement->is_local_list_statement()) { auto local_statement = std::static_pointer_cast(statement); return &(local_statement->variables); } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index fb6fe17a70..4086fe08ce 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1255,7 +1255,7 @@ std::vector run_defuse_visitor(const std::string& text, const std::stri DefUseAnalyzeVisitor v(ast->get_symbol_table()); for (auto& block : ast->blocks) { - if (block->get_type() != ast::AstNodeType::NEURON_BLOCK) { + if (block->get_node_type() != ast::AstNodeType::NEURON_BLOCK) { chains.push_back(v.analyze(block.get(), variable)); } } From bc36229669a21a26e2000940f7b01db8220aae32 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Mon, 31 Dec 2018 16:47:00 +0100 Subject: [PATCH 101/871] Print getter method for each member except one with 'name' - removed explicit getter method names from yaml - removed double_value with to_double - macroname to macro_name - rename statementblock to statement_block and remove explicit getters Change-Id: I275e25ffd9816d46e66856e2421c8aff43dc136e NMODL Repo SHA: BlueBrain/nmodl@a0a20e277ccf26752af4f648c245b4e06a44af20 --- src/nmodl/ast/ast_common.hpp | 13 +- .../codegen/base/codegen_base_visitor.cpp | 16 +-- .../codegen/base/codegen_helper_visitor.cpp | 10 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 28 ++-- src/nmodl/language/ast_printer.py | 25 ++-- src/nmodl/language/nmodl.yaml | 127 ++++++++---------- src/nmodl/language/nmodl_printer.py | 2 +- src/nmodl/language/parser.py | 3 +- src/nmodl/language/visitors_printer.py | 2 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 5 +- src/nmodl/visitors/inline_visitor.cpp | 80 +++++++++++ src/nmodl/visitors/inline_visitor.hpp | 83 +----------- src/nmodl/visitors/symtab_visitor_helper.cpp | 8 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 2 +- 15 files changed, 195 insertions(+), 211 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index e61c3be636..2a4f18685b 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -82,7 +82,7 @@ namespace ast { throw std::logic_error("clone() not implemented"); } - /* @todo: please revisit this. adding quickly for symtab */ + /* @todo: revisit, adding quickly for symtab */ virtual ModToken* get_token() { /*std::cout << "\n ERROR: get_token not implemented!";*/ return nullptr; } @@ -96,11 +96,7 @@ namespace ast { } virtual std::shared_ptr get_statement_block() { - throw std::runtime_error("get_statement_node not implemented"); - } - - virtual ArgumentVector get_parameters() { - throw std::runtime_error("get_parameters not implemented"); + throw std::runtime_error("get_statement_block not implemented"); } // implemented in Number sub classes @@ -108,11 +104,6 @@ namespace ast { throw std::runtime_error("negate() not implemented"); } - // implemented in Number sub classes - virtual double number_value() { - throw std::runtime_error("number_value() not implemented"); - } - // implemented in Identifier sub classes virtual void set_name(std::string /*name*/) { throw std::runtime_error("set_name() not implemented"); diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 4074be3d21..a2c8b92b43 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -139,7 +139,7 @@ void CodegenBaseVisitor::visit_if_statement(IfStatement* node) { printer->add_text("if ("); node->condition->accept(this); printer->add_text(") "); - node->statementblock->accept(this); + node->get_statement_block()->accept(this); print_vector_elements(node->elseifs, ""); if (node->elses) { node->elses->accept(this); @@ -154,7 +154,7 @@ void CodegenBaseVisitor::visit_else_if_statement(ElseIfStatement* node) { printer->add_text(" else if ("); node->condition->accept(this); printer->add_text(") "); - node->statementblock->accept(this); + node->get_statement_block()->accept(this); } @@ -170,18 +170,18 @@ void CodegenBaseVisitor::visit_while_statement(WhileStatement* node) { printer->add_text("while ("); node->condition->accept(this); printer->add_text(") "); - node->statementblock->accept(this); + node->get_statement_block()->accept(this); } void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { if (!codegen) { return; } - auto name = node->get_from_name()->get_name(); - auto from = node->get_from_expression(); - auto to = node->get_to_expression(); - auto inc = node->get_inc_expression(); - auto block = node->get_block(); + auto name = node->get_name(); + auto from = node->get_from(); + auto to = node->get_to(); + auto inc = node->get_opinc(); + auto block = node->get_statement_block(); printer->add_text("for(int {}="_format(name)); from->accept(this); printer->add_text("; {}<="_format(name)); diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 256a5f3ceb..780086561e 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -403,7 +403,7 @@ void CodegenHelperVisitor::find_table_variables() { void CodegenHelperVisitor::visit_suffix(Suffix* node) { - auto type = node->get_suffix_type()->get_name(); + auto type = node->get_type()->get_name(); if (type == point_process) { info.point_process = true; } @@ -411,7 +411,7 @@ void CodegenHelperVisitor::visit_suffix(Suffix* node) { info.artificial_cell = true; info.point_process = true; } - info.mod_suffix = node->get_suffix_name()->get_name(); + info.mod_suffix = node->get_name(); } @@ -476,7 +476,7 @@ void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { - auto name = node->get_function_name()->get_name(); + auto name = node->get_name(); if (name == net_send_method) { info.net_send_used = true; } @@ -488,7 +488,7 @@ void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { auto ion = node->get_ion(); - auto variable = node->get_variable(); + auto variable = node->get_conductance(); std::string ion_name; if (ion) { ion_name = ion->get_name(); @@ -500,7 +500,7 @@ void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { void CodegenHelperVisitor::visit_solve_block(SolveBlock* node) { info.num_solve_blocks++; if (under_breakpoint_block) { - info.solve_block_name = node->get_solve_block()->get_name(); + info.solve_block_name = node->get_block_name()->get_name(); if (node->get_method()) { info.solve_method = node->get_method()->get_name(); if (info.solve_method == derivimplicit_method) { diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 7faf5fc3dc..36ccbc7ac2 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -594,7 +594,7 @@ void CodegenCVisitor::visit_watch_statement(ast::WatchStatement* node) { void CodegenCVisitor::print_function_call(FunctionCall* node) { - auto name = node->get_function_name()->get_name(); + auto name = node->get_name(); auto function_name = name; if (defined_method(name)) { function_name = method_name(name); @@ -615,17 +615,17 @@ void CodegenCVisitor::print_function_call(FunctionCall* node) { return; } - auto parameters = node->get_parameters(); + auto arguments = node->get_arguments(); printer->add_text("{}("_format(function_name)); if (defined_method(name)) { printer->add_text(internal_method_arguments()); - if (!parameters.empty()) { + if (!arguments.empty()) { printer->add_text(", "); } } - print_vector_elements(parameters, ", "); + print_vector_elements(arguments, ", "); printer->add_text(")"); } @@ -2511,12 +2511,12 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node) { printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); /// rename variables but need to see if they are actually used - auto arguments = info.net_receive_node->get_parameters(); - if (!arguments.empty()) { + auto parameters = info.net_receive_node->get_parameters(); + if (!parameters.empty()) { int i = 0; printer->add_newline(); - for (auto& argument : arguments) { - auto name = argument->get_name(); + for (auto& parameter : parameters) { + auto name = parameter->get_name(); VarUsageVisitor vu; auto var_used = vu.variable_used(node, "(*" + name + ")"); if (var_used) { @@ -2531,7 +2531,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node) { void CodegenCVisitor::print_net_send_call(FunctionCall* node) { - auto arguments = node->get_parameters(); + auto arguments = node->get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; std::string pnt = "pnt"; @@ -2568,7 +2568,7 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { abort(); } - auto arguments = node->get_parameters(); + auto arguments = node->get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "-1"; std::string pnt = "pnt"; @@ -2591,7 +2591,7 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { void CodegenCVisitor::print_net_event_call(FunctionCall* node) { - auto arguments = node->get_parameters(); + auto arguments = node->get_arguments(); if (info.artificial_cell) { printer->add_text("net_event(pnt, "); print_vector_elements(arguments, ", "); @@ -2726,9 +2726,9 @@ void CodegenCVisitor::print_net_receive() { auto node = info.net_receive_node; /// rename arguments but need to see if they are actually used - auto arguments = node->get_parameters(); - for (auto& argument : arguments) { - auto name = argument->get_name(); + auto parameters = node->get_parameters(); + for (auto& parameter : parameters) { + auto name = parameter->get_name(); auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index 70dd2871b2..f1d888bdc2 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -1,6 +1,5 @@ from printer import * from utils import * -from node_info import ORDER_VAR_NAME class AstForwardDeclarationPrinter(DeclarationPrinter): """Prints all AST nodes forward declarations""" @@ -121,16 +120,16 @@ def ast_classes_declaration(self): self.write_line("virtual std::string get_name() override {") self.write_line(" return {}->{}();".format(varname, method)) self.write_line("}") - - # todo : return type should go into node : refactor when changing return types for all ast nodes - if child.getter_method: - getter_method = child.getter_method - getter_override = "override" if child.getter_override else "" + else: + getter_method = child.getter_method if child.getter_method else "get_" + to_snake_case(varname) + getter_override = " override" if child.getter_override else "" return_type = child.return_typename - self.write_line("{} {}() {}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) + if getter_method == "get_name": + print "WARNING : skipping get_name method for node {} and member name of type {}".format(node.class_name, return_type) + else : + self.write_line("{} {}(){}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) - if node.is_prime_node(): - self.write_line("int get_order() {{ return {}->eval(); }}".format(ORDER_VAR_NAME)) + self.write_line() # all member functions self.write_line("{}void visit_children (Visitor* v) override;".format(virtual)) @@ -161,11 +160,17 @@ def ast_classes_declaration(self): self.write_line("void negate() override { value = !value; }") else: self.write_line("void negate() override { value = -value; }") - self.write_line("double number_value() override { return value; }") + self.write_line("double to_double() override { return value; }") if node.is_name_node(): self.write_line("{}void set_name(std::string name){} {{ value->set(name); }}".format(virtual, override)) + if node.is_number_node(): + self.write_line('{}double to_double() {{ throw std::runtime_error("to_double not implemented"); }}'.format(virtual)) + + if node.is_base_block_node(): + self.write_line('virtual ArgumentVector get_parameters() { throw std::runtime_error("get_parameters not implemented"); }') + # if node is of enum type then return enum value if node.is_data_type_node(): data_type = node.get_data_type_name() diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 0d079566bb..32434c0cea 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -605,11 +605,9 @@ members: - value: type: int - getter: {name: get_value} - - macroname: + - macro_name: type: Name optional: true - getter: {name: get_macro_name} - Float: members: - value: @@ -628,7 +626,6 @@ - value: type: String getname: true - getter: {name: get_value} - PrimeName: members: - value: @@ -659,7 +656,6 @@ type: Expression prefix: {value: "["} suffix: {value: "]"} - getter: {name: get_length} - Argument: members: - name: @@ -770,27 +766,26 @@ - InitialBlock: nmodl: "INITIAL " members: - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - ConstructorBlock: nmodl: "CONSTRUCTOR " members: - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - DestructorBlock: nmodl: "DESTRUCTOR " members: - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - StatementBlock: members: - statements: type: Statement vector: true - getter: {name: get_statements} - DerivativeBlock: nmodl: "DERIVATIVE " members: @@ -798,9 +793,9 @@ type: Name getname: true suffix: {value: " "} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - LinearBlock: nmodl: "LINEAR " members: @@ -813,9 +808,9 @@ vector: true separator: "," prefix: {value: " SOLVEFOR "} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - NonLinearBlock: nmodl: "NONLINEAR " members: @@ -828,9 +823,9 @@ separator: "," prefix: {value: " SOLVEFOR "} suffix: {value: " ", force: true} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - DiscreteBlock: nmodl: "DISCRETE " members: @@ -838,9 +833,9 @@ type: Name getname: true suffix: {value: " "} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - PartialBlock: nmodl: "PARTIAL " members: @@ -848,9 +843,9 @@ type: Name getname: true suffix: {value: " "} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - FunctionTableBlock: nmodl: "FUNCTION_TABLE " members: @@ -863,6 +858,7 @@ prefix: {value: "(", force: true} suffix: {value: ")", force: true} separator: ", " + getter: {override: true} - unit: type: Unit optional: true @@ -879,15 +875,15 @@ prefix: {value: "(", force: true} suffix: {value: ")", force: true} separator: ", " - getter: {name: get_parameters, override: true} + getter: {override: true} - unit: type: Unit optional: true prefix: {value: " "} suffix: {value: " ", force: true} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - ProcedureBlock: nmodl: "PROCEDURE " members: @@ -900,13 +896,13 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - getter: {name: get_parameters, override: true} + getter: {override: true} - unit: type: Unit optional: true - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - NetReceiveBlock: nmodl: "NET_RECEIVE " members: @@ -916,22 +912,20 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - getter: {name: get_parameters, override: true} - - statementblock: + getter: {override: true} + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - SolveBlock: nmodl: SOLVE members: - - name: + - block_name: type: Name prefix: {value: " "} - getter: {name: get_solve_block} - method: type: Name optional: true prefix: {value: " METHOD "} - getter: {name: get_method} - ifsolerr: type: StatementBlock optional: true @@ -939,15 +933,15 @@ - BreakpointBlock: nmodl: "BREAKPOINT " members: - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - TerminalBlock: nmodl: "TERMINAL " members: - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - BeforeBlock: nmodl: "BEFORE " members: @@ -963,9 +957,9 @@ - type: type: BABlockType suffix: {value: " "} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - ForNetcon: nmodl: "FOR_NETCONS " members: @@ -975,9 +969,10 @@ prefix: {value: "(", force: true} suffix: {value: ") ", force: true} separator: ", " - - statementblock: + getter: {override: true} + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - KineticBlock: nmodl: "KINETIC " members: @@ -989,9 +984,9 @@ type: Name vector: true separator: "," - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_statement_block, override: true} + getter: {override: true} - MatchBlock: nmodl: MATCH members: @@ -1016,8 +1011,9 @@ - NeuronBlock: nmodl: "NEURON " members: - - statementblock: + - statement_block: type: StatementBlock + getter: {override: true} - Unit: members: - name: @@ -1103,13 +1099,10 @@ members: - lhs: type: Expression - getter: {name: get_lhs} - op: type: BinaryOperator - getter: {name: get_op} - rhs: type: Expression - getter: {name: get_rhs} - UnaryExpression: members: - op: @@ -1136,14 +1129,13 @@ members: - name: type: Name - getter: {name: get_function_name} + getname: true - arguments: type: Expression vector: true separator: ", " prefix: {value: "(", force: true} suffix: {value: ")", force: true} - getter: {name: get_arguments} - FirstLastTypeIndex: members: - value: @@ -1305,7 +1297,6 @@ optional: true prefix: {value: "["} suffix: {value: "]"} - getter: {name: get_length} - from: type: Number prefix: {value: " FROM "} @@ -1342,17 +1333,14 @@ members: - conductance: type: Name - getter: {name: get_variable} - ion: type: Name optional: true prefix: {value: " USEION "} - getter: {name: get_ion} - ExpressionStatement: members: - expression: type: Expression - getter: {name: get_expression} - ProtectStatement: nmodl: "PROTECT " members: @@ -1363,32 +1351,30 @@ members: - name: type: Name - getter: {name: get_from_name} + getname: True - from: type: Expression prefix: {value: " = "} - getter: {name: get_from_expression} - to: type: Expression prefix: {value: " TO "} - getter: {name: get_to_expression} - opinc: type: Expression prefix: {value: " BY "} suffix: {value: " ", force: true} optional: true - getter: {name: get_inc_expression} - - statementblock: + - statement_block: type: StatementBlock - getter: {name: get_block} + getter: {override: true} - ForAllStatement: nmodl: "FORALL " members: - name: type: Name suffix: {value: " "} - - statementblock: + - statement_block: type: StatementBlock + getter: {override: true} - WhileStatement: nmodl: "WHILE " members: @@ -1396,8 +1382,9 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - statementblock: + - statement_block: type: StatementBlock + getter: {override: true} - IfStatement: nmodl: "IF " members: @@ -1405,8 +1392,9 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - statementblock: + - statement_block: type: StatementBlock + getter: {override: true} - elseifs: type: ElseIfStatement vector: true @@ -1420,13 +1408,15 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - statementblock: + - statement_block: type: StatementBlock + getter: {override: true} - ElseStatement: nmodl: " ELSE " members: - - statementblock: + - statement_block: type: StatementBlock + getter: {override: true} - PartialEquation: members: - prime: @@ -1601,10 +1591,9 @@ - type: type: Name suffix: {value: " "} - getter: {name: get_suffix_type} - name: type: Name - getter: {name: get_suffix_name} + getname: True - Useion: nmodl: "USEION " members: @@ -1691,9 +1680,8 @@ - Verbatim: nmodl: VERBATIM members: - - statements: + - statement: type: String - getter: {name: get_statement} suffix: {value: "ENDVERBATIM"} - Comment: members: @@ -1712,4 +1700,3 @@ type: Node vector: true add: true - getter: {name: get_blocks} diff --git a/src/nmodl/language/nmodl_printer.py b/src/nmodl/language/nmodl_printer.py index 0bedff5c0b..d6a6eab013 100644 --- a/src/nmodl/language/nmodl_printer.py +++ b/src/nmodl/language/nmodl_printer.py @@ -72,7 +72,7 @@ def definitions(self): # but for integer node we have to check if it is represented as macro if node.is_data_type_node(): if node.is_integer_node(): - self.write_line("if(node->macroname == nullptr) {") + self.write_line("if(node->get_macro_name() == nullptr) {") self.write_line(" printer->add_element(std::to_string(node->eval()));") self.write_line("}") else: diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 0f72d3ef0c..44f78401bf 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -79,7 +79,8 @@ def parse_child_rule(self, child): # if getter method required if 'getter' in properties: - args.getter_method = properties['getter']['name'] + if 'name' in properties['getter']: + args.getter_method = properties['getter']['name'] if 'override' in properties['getter']: args.getter_override = properties['getter']['override'] diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 610d9640fc..2b7655bbc6 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -124,7 +124,7 @@ def definitions(self): if node.is_data_type_node(): if node.class_name == "Integer": - self.write_line("if(!node->macroname) {") + self.write_line("if(!node->get_macro_name()) {") self.write_line(" std::stringstream ss;") self.write_line(" ss << node->eval();") self.write_line(" printer->add_node(ss.str());") diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 4a9e7a5899..af29f1e757 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -216,8 +216,9 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { auto last_chain = current_chain; start_new_chain(DUState::IF); node->condition->accept(this); - if (node->statementblock) { - node->statementblock->accept(this); + auto block = node->get_statement_block(); + if (block) { + block->accept(this); } current_chain = last_chain; diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 75e3223c4b..000f47e3bb 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -96,6 +96,86 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, } } + +/** + * Inline function/procedure call + * @param callee : ast node representing function/procedure definition being called + * @param node : function/procedure call node + * @param caller : statement block containing function call + */ +bool InlineVisitor::inline_function_call(ast::Block* callee, + ast::FunctionCall* node, + ast::StatementBlock* caller) { + std::string function_name = callee->get_name(); + + /// do nothing if we can't inline given procedure/function + if (!can_inline_block(callee->get_statement_block().get())) { + std::cerr << "Can not inline function call to " + function_name << std::endl; + return false; + } + + /// make sure to rename conflicting local variable in caller block + /// because in case of procedure inlining they can conflict with + /// global variables used in procedure being inlined + LocalVarRenameVisitor v; + v.visit_statement_block(caller); + + auto caller_arguments = node->get_arguments(); + std::string new_varname = get_new_name(function_name, "in", inlined_variables); + + /// check if caller statement could be replaced + bool to_replace = can_replace_statement(caller_statement); + + /// need to add local variable for function calls or for procedure call if it is part of + /// expression (standalone procedure calls don't need return statement) + if (callee->is_function_block() || to_replace == false) { + /// create new variable which will be used for returning value from inlined block + auto name = new ast::Name(new ast::String(new_varname)); + ModToken tok; + name->set_token(tok); + + auto local_variables = get_local_variables(caller); + /// each block should already have local statement + if (local_variables == nullptr) { + throw std::logic_error("got local statement as nullptr"); + } + local_variables->push_back(std::make_shared(name)); + } + + /// get a copy of function/procedure body + auto inlined_block = callee->get_statement_block()->clone(); + + /// function definition has function name as return value. we have to rename + /// it with new variable name + RenameVisitor visitor(function_name, new_varname); + inlined_block->visit_children(&visitor); + + /// \todo: have to re-run symtab visitor pass to update symbol table + inlined_block->set_symbol_table(nullptr); + + /// each argument is added as new assignment statement + inline_arguments(inlined_block, callee->get_parameters(), caller_arguments); + + /// to return value from procedure we have to add new variable + if (callee->is_procedure_block() && to_replace == false) { + add_return_variable(inlined_block, new_varname); + } + + auto statement = new ast::ExpressionStatement(inlined_block); + + if (to_replace) { + replaced_statements[caller_statement] = statement; + } else { + inlined_statements[caller_statement].push_back( + std::shared_ptr(statement)); + } + + /// variable name which will replace the function call that we just inlined + replaced_fun_calls[node] = new_varname; + return true; +} + + void InlineVisitor::visit_function_call(FunctionCall* node) { /// argument can be function call itself node->visit_children(this); diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 2894856a72..40d8c7204f 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -146,8 +146,7 @@ class InlineVisitor : public AstVisitor { bool can_replace_statement(const std::shared_ptr& statement); /// inline function/procedure into caller block - template - bool inline_function_call(T* callee, ast::FunctionCall* node, ast::StatementBlock* caller); + bool inline_function_call(ast::Block* callee, ast::FunctionCall* node, ast::StatementBlock* caller); /// add assignement statements into given statement block to inline arguments void inline_arguments(ast::StatementBlock* inlined_block, @@ -170,84 +169,4 @@ class InlineVisitor : public AstVisitor { virtual void visit_program(ast::Program* node) override; }; -/** - * Inline function/procedure call - * @tparam T - * @param callee : ast node representing definition of function/procedure being called - * @param node : function/procedure call node - * @param caller : statement block containing function call - */ -template -bool InlineVisitor::inline_function_call(T* callee, - ast::FunctionCall* node, - ast::StatementBlock* caller) { - std::string function_name = callee->name->get_name(); - - /// do nothing if we can't inline given procedure/function - if (!can_inline_block(callee->statementblock.get())) { - std::cerr << "Can not inline function call to " + function_name << std::endl; - return false; - } - - /// make sure to rename conflicting local variable in caller block - /// because in case of procedure inlining they can conflict with - /// global variables used in procedure being inlined - LocalVarRenameVisitor v; - v.visit_statement_block(caller); - - auto caller_arguments = node->get_arguments(); - std::string new_varname = get_new_name(function_name, "in", inlined_variables); - - /// check if caller statement could be replaced - bool to_replace = can_replace_statement(caller_statement); - - /// need to add local variable for function calls or for procedure call if it is part of - /// expression (standalone procedure calls don't need return statement) - if (callee->is_function_block() || to_replace == false) { - /// create new variable which will be used for returning value from inlined block - auto name = new ast::Name(new ast::String(new_varname)); - ModToken tok; - name->set_token(tok); - - auto local_variables = get_local_variables(caller); - /// each block should already have local statement - if (local_variables == nullptr) { - throw std::logic_error("got local statement as nullptr"); - } - local_variables->push_back(std::make_shared(name)); - } - - /// get a copy of function/procedure body - auto inlined_block = callee->statementblock->clone(); - - /// function definition has function name as return value. we have to rename - /// it with new variable name - RenameVisitor visitor(function_name, new_varname); - inlined_block->visit_children(&visitor); - - /// \todo: have to re-run symtab visitor pass to update symbol table - inlined_block->set_symbol_table(nullptr); - - /// each argument is added as new assignment statement - inline_arguments(inlined_block, callee->get_parameters(), caller_arguments); - - /// to return value from procedure we have to add new variable - if (callee->is_procedure_block() && to_replace == false) { - add_return_variable(inlined_block, new_varname); - } - - auto statement = new ast::ExpressionStatement(inlined_block); - - if (to_replace) { - replaced_statements[caller_statement] = statement; - } else { - inlined_statements[caller_statement].push_back( - std::shared_ptr(statement)); - } - - /// variable name which will replace the function call that we just inlined - replaced_fun_calls[node] = new_varname; - return true; -} - #endif // diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index 822554b0be..5363439e7c 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -46,7 +46,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { auto prime = dynamic_cast(node); symbol = modsymtab->lookup(name); if (symbol) { - symbol->set_order(prime->get_order()); + symbol->set_order(prime->get_order()->eval()); symbol->add_property(property); return; } @@ -71,7 +71,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { if (node->is_param_assign()) { auto parameter = dynamic_cast(node); if (parameter->value) { - symbol->set_value(parameter->value->number_value()); + symbol->set_value(parameter->get_value()->to_double()); } if (parameter->name->is_indexed_name()) { auto name = dynamic_cast(parameter->name.get()); @@ -91,7 +91,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { if (node->is_constant_var()) { auto constant = dynamic_cast(node); if (constant->value) { - symbol->set_value(constant->value->number_value()); + symbol->set_value(constant->get_value()->to_double()); } } @@ -152,7 +152,7 @@ void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool /// there is only one solve statement allowed in mod file if (node->is_solve_block()) { auto solve_block = dynamic_cast(node); - block_to_solve = solve_block->name->get_name(); + block_to_solve = solve_block->get_block_name()->get_name(); } /// not required at the moment but every node diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index b1ae5c97e2..e2315a68d9 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -70,6 +70,6 @@ std::shared_ptr create_statement(const std::string& code_statement) { driver.parse_string(nmodl_text); auto ast = driver.ast(); auto procedure = std::dynamic_pointer_cast(ast->blocks[0]); - auto statement = std::shared_ptr(procedure->statementblock->statements[0]->clone()); + auto statement = std::shared_ptr(procedure->get_statement_block()->get_statements()[0]->clone()); return statement; } diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 14ebc95801..cebcfc4d55 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -50,7 +50,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { symbol_type("h'' = ", value); ss << *(value->get_token()); REQUIRE(ss.str() == " h'' at [1.1-3] type 362"); - REQUIRE(value->get_order() == 2); + REQUIRE(value->get_order()->eval() == 2); delete value; } } From eaa1a8ab9ae38857d6f83a2520e0582884a3529d Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Tue, 1 Jan 2019 12:30:51 +0100 Subject: [PATCH 102/871] Remove old text based spec from nmodl.yaml Remove unused code from nodes_info.py Change-Id: I96d6b15928d913c106836fa75738976cf7de1719 NMODL Repo SHA: BlueBrain/nmodl@0f01fa7686f08618b10280baf08a47954fe2e0dd --- src/nmodl/language/nmodl.yaml | 543 +------------------------------- src/nmodl/language/node_info.py | 10 +- src/nmodl/language/nodes.py | 42 --- 3 files changed, 11 insertions(+), 584 deletions(-) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 32434c0cea..11dfa5282b 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -6,7 +6,7 @@ # Lot of information about language constructs is necessary at various stages # like AST definitions, visitors implementation, source-to-source transformations # etc. In case of small changes in grammar or AST definition, especially -# during early development stage, we have to re-write considerable part of the code. +# during development stage, we have to re-write considerable part of the code. # The idea of "abstract" language definition is to define framework that can automatically # generate part of this "mechanical" code. This approach has been used in some open source # projects (e.g. compiler design courses, TableGen format in LLVM). @@ -27,10 +27,10 @@ # MemberType member_name; # }; # -# This specification format was sufficient initial AST design but as we started implementing -# more features and compiler passes, we need to add more information to language definition. -# For example, we need to make certain members vector, some are optional, some need -# specific method (get/set) etc. In the future we may need to make certain members private. +# This specification format was sufficient for first AST design but as we started +# implementing more features and compiler passes, we need to add more information to language +# definition. For example, we need to make certain members vector, some are optional, some need +# specific method (getter/setter) etc. In the future we may need to make certain members private. # Above text definition is limiting if we have to accomodate future enhancements. # # To overcome above limitation, we are moving to YAML definition format. This allows easy @@ -38,535 +38,12 @@ # exisiting specification. # # -# IMPORTANT NOTE -# ============== -# -# Below documentation is from old text specification (i.e. nmod.def). We converted -# old text specification to YAML format which we will describe at the end. We should -# convert all below documentation/notes for YAML format. For now, keeping old documentation -# as it has valuable information about how/why specific types were choose for AST. -# -# -# TEXT BASED RULE SPECIFICATION (OLD, SKIP UP TO YAML SECTION) -# ============================================================ -# -# FORMT : -# ===== -# -# Here is brief information about the format of rules specification itself: -# -# To define an abstract class "ChildType", we use below specification: -# -# BaseType:ChildType => -# -# The member variable could be an optional or vector type. This is captured by symbol ? and * -# -# BaseType:ChildType => Member1Type ?Member2Type *Member3Type -# -# In above specification the ChildType class has three member variables: -# -# mname1 : is a member variable of type Member1Type -# mname2 : is an optional variable i.e. pointer of type Member2Type -# mname3 : is a vector variable of type Member3Type -# -# Above specification will generate class definition lik : -# -# class ChildType: public BaseType { -# private: -# Member1Type mname1; -# Member2Type* mname2; -# std::vector mname3; -# }; -# -# NOTE : Many member variables need to hold subclasses (due to various optimization). -# Hence all member variables are now of type pointer. -# -# Some parser actions add new node while parsing the input. This requires "addMemberType" -# method. This is specified using '+' decorator for that member as: -# -# BaseType:ChildType => *MemberType<+name> -# -# In order to print AST back to NMODL format, we need a way to map every AST node -# back to NMODL. In order to achieve this we introduced node "taging". Every node -# can have prefix, printing name, separator and suffix. For example, if we have -# following specification: -# -# BaseType:ChildType("MYNAME") => -# -# When we will print ChildType class, it will be written as "MYNAME" to NMODL file. -# This will be more clear when we will look the examples. -# -# The member variable can have prefix, suffix and separator specification. The -# simple syntax is ("prefix:suffix"). If the member is of type vector, then it -# could have separator specification as ("prefix:suffix:separator). The separator -# is used usually for vector data types. Note that the member could have value as -# well as prefix, suffix and separator. -# -# These rules are best illustrated by following example: -# -# BaseType:ChildType => Member1Type(":=") Member2Type(" USEION :") *Member3Type("::,") -# -# Above specification will be translated as: -# -# (":=") => prefix is empty, followed by value of name1, followed by suffix "=" -# (" USEION :") => prefix is " USEION " followed by value of name2 -# ("::,") => prefix is empty, every variable (except last) of name3 will be -# separated by comma, no suffix -# -# Consider function definition which has arguments as vector. Typically we will define '(' as prfix -# and ')' as suffix for the argument node. But if there are no arguments to function then we have to -# still print '()' i.e. prefix and suffix must be printed. This is not desirable in all cases but few. -# And not only for function call arguments but for reaction statements etc. -# To enforce this rule we prepend '!' to the prefix or suffix. For example: -# -# Expression:FunctionCall => Name *Expression("!(:!):,") -# -# This was so naive! But we have removed with YAML specification! -# -# REFERENCES: -# -# 1] Writing Your Own Toy Compiler Using Flex, Bison and LLVM -# http://gnuu.org/2009/09/18/writing-your-own-toy-compiler/ -# -# 2] Use of abstract language: https://github.com/jazariethach/project6_CS160.git -# Github repository is not available anymore. You cab look at -# /Users/kumbhar/workarena/repos/personal/git_compiler_projects/project6_CS160 -# or bitbucket fork) -# -# Some projects [1] use AstNode as a base class for all node types. The main base class AstNode -# is defined by in python code itself and rest are derived from it. -# -# Abstract base classes for AST specification: -# -# AST:Expression => -# AST:Statement => -# Expression:Identifier => -# Expression:Block => -# Expression:Number => -# -# Every node which can have "name" is of type String: -# -# Expression:String => std::string -# -# Basic data types like int, float and double. Note that NMODL supprts macro where users -# usually define flags or integer. Hence Integer can have associated macro name: -# -# Number:Integer => int ?Name -# Number:Float => float -# Number:Double => double -# Number:Boolean => int -# -# 'Name' and 'name' is used differently in the NMODL grammar specification. 'Name' could be -# just string whereas 'name' could be PRIME. 'Name' and 'NAME' are same in AST context: -# -# Identifier:Name => String -# -# Prime has name and order. Note that the variable name "order" is checked -# in the ast generator. It's hardcoded in node_types.py as PRIME_ORDER_VAR: -# -# Identifier:PrimeName => String Integer -# -# VarName could have name and Index as an Expression. -# Pair of Name, Expression could be represented by Indexname and hence Identifier -# is added as a member variable: -# -# Identifier:VarName => Identifier ?Integer("@:") -# -# Todo : Verify this is correctly working with IndexedName and being handled correctly -# -# IndexedName has Name and integer index but for 'matchname' grammar we need index as -# 'Name'. Hence 'index' is now Expression to take generic form: -# -# Identifier:IndexedName => Identifier Expression("[:]") -# -# Units is none or single Unit: -# -# Expression:Unit => String("(:)") -# -# UNITON and UNITOFF statements are represented by TRUE or FALSE. UnitStateType -# is enum to represent this state: -# -# Statement:UnitState => UnitStateType -# -# Unit definition has double value and unit name: -# -# Expression:DoubleUnit => Double ?Unit -# -# Argument coule be name with units or 'name' that could be prime. Hence -# adding Identifier as a member variable: -# -# Identifier:Argument => Identifier ?Unit -# -# Local variable: -# -# Expression:LocalVar => Identifier -# -# Local statement in NMODL to represent the list of local variables: -# -# Statement:LocalListStatement("LOCAL") => *LocalVar("::,") -# -# -# Limits is the range of double value: -# -# Expression:Limits => Double("<:,") Double(":>") -# -# Number range variable could int or double range: -# -# Expression:NumberRange => Number("<:,") Number(":>") -# -# Program represents entire NMODL file or module. This could have one or more -# blocks (like INITIAL, STATE), methods and statements like include, define: -# -# Program => *Statement<+statements> *Block<+blocks> -# -# Title, Macro definition and include statement: -# -# Statement:Model("TITLE") => String -# Statement:Define("DEFINE") => Name<name> Integer<value> -# Statement:Include("INCLUDE") => String<filename> -# -# Parameter block and statement within this block: -# -# Block:ParamBlock("PARAMETER") => *ParamAssign<statements> -# Statement:ParamAssign => Identifier<name> ?Number<value>("=:") ?Unit<unit> ?Limits<limit> -# -# Step block and statement within this block: -# -# Block:StepBlock("STEPPED") => *Stepped<statements> -# Statement:Stepped => Name<name> *Number<values>("=::,") Unit<unit> -# -# -# Independent block and statement within this block. Note that SWEEP is not part -# of IndependentDef but will be set from bison action: -# -# Block:IndependentBlock("INDEPENDENT") => *IndependentDef<definitions> -# Statement:IndependentDef => ?Boolean<sweep> -# Name<name> -# Number<from>(" FROM :") -# Number<to>(" TO :") -# Integer<with>(" WITH :") -# ?Number<opstart>(" START :") -# Unit<unit> -# -# Dependent block and statement within this block. The 'name' could be Name or -# PrimeName and hence using Identifier. DependentDef is also used in State -# block and hence except name everything needs to be optional: -# -# Statement:DependentDef => Identifier<name>(": ") -# ?Integer<index>("[:]") -# ?Number<from>(" FROM :") -# ?Number<to>(" TO :") -# ?Number<opstart>(" START : ") -# ?Unit<unit> -# ?Double<abstol>(" <:> ") -# Block:DependentBlock("ASSIGNED") => *DependentDef<definitions> -# Block:StateBlock("STATE") => *DependentDef<definitions> -# -# Plot declaration. PlotVar is 'name' and hence could just IndexedName or Name or prime: -# Todo : check if existing variable could be used -# -# Block:PlotBlock => PlotDeclaration<plot> -# Statement:PlotDeclaration("PLOT ") => *PlotVar<pvlist>("::,") PlotVar<name>(" VS :") -# Expression:PlotVar => Identifier<name>(" :") ?Integer<index>("[:]") -# -# Initial block: -# -# Block:InitialBlock("INITIAL") => StatementBlock<statementblock> -# -# Constructor block: -# -# Block:ConstructorBlock("CONSTRUCTOR") => StatementBlock<statementblock> -# -# Destructor block: -# -# Block:DestructorBlock("DESTRUCTOR") => StatementBlock<statementblock> -# -# Condustance staement added for CoreNeuronmoptimizations: -# -# Statement:ConductanceHint("CONDUCTANCE") => Name<conductance> ?Name<ion>(" USEION :") -# -# 1] is using ExpressionStatement class which holds Expression and used for may blocks in bison grammar : -# -# Statement:ExpressionStatement => Expression<expression> -# -# Protect statement: -# -# Statement:ProtectStatement("PROTECT") => Expression<expression> -# -# List of statements enclosed by opening and closing curly braces. -# Todo: One of the production of ostmt has stmtlist and check how to handle this: -# -# Block:StatementBlock => *Statement<statements> -# -# To translate AST back to NMODL, we need method to print operators and hence adding classes -# for binary operators: -# -# Expression:BinaryOperator => BinaryOp<value> -# Expression:UnaryOperator => UnaryOp<value> -# Expression:ReactionOperator => ReactionOp<value> -# -# Binary or Uniary expressions: -# -# Expression:BinaryExpression => Expression<lhs> BinaryOperator<op> Expression<rhs> -# Expression:UnaryExpression => UnaryOperator<op> Expression<expression> -# -# After reading all bison productions it is clear that two basic classes derived from Expression -# are needed : LinEquation and NonLinEquation: -# Todo : add more NMODL tests for validation -# -# Expression:NonLinEquation("~") => Expression<lhs>(":=") Expression<rhs> -# Expression:LinEquation("~") => Expression<leftlinexpr>(":=") Expression<linexpr> -# -# Function call (must be expression because it could be called in expression definition): -# -# Expression:FunctionCall => Name<name> *Expression<arguments>("!(:!):,") -# -# From statement: -# -# Statement:FromStatement("FROM") => Name<name>(" : ") -# Expression<from>("=:") -# Expression<to>(" TO :") -# ?Expression<opinc>(" BY :") -# StatementBlock<statementblock> -# -# Forall statement: -# -# Statement:ForAllStatement("FORALL") => Name<name>(" : ") StatementBlock<statementblock> -# -# While statement: -# -# Statement:WhileStatement("WHILE") => Expression<condition>("(:)") StatementBlock<statementblock> -# -# If statement: -# -# Statement:IfStatement("IF") => Expression<condition>("(:)") -# StatementBlock<statementblock> -# *ElseIfStatement<elseifs> -# ?ElseStatement<elses> -# -# Else if statement: -# -# Statement:ElseIfStatement("ELSE IF") => Expression<condition>("(:)") StatementBlock<statementblock> -# -# Else statement: -# -# Statement:ElseStatement("ELSE") => StatementBlock<statementblock> -# -# Derivative block: -# -# Block:DerivativeBlock("DERIVATIVE") => Name<name> StatementBlock<statementblock> -# -# Linear block: -# -# Block:LinearBlock("LINEAR") => Name<name> *Name<solvefor>(" SOLVEFOR :") StatementBlock<statementblock> -# -# Non-linear block: -# -# Block:NonLinearBlock("NONLINEAR") => Name<name> *Name<solvefor>(" SOLVEFOR :") StatementBlock<statementblock> -# -# Discrete block: -# -# Block:DiscreteBlock("DISCRETE") => Name<name> StatementBlock<statementblock> -# -# Partial block: -# -# Block:PartialBlock("PARTIAL") => Name<name> StatementBlock<statementblock> -# -# Partial equation: -# -# Statement:PartialEquation => PrimeName<prime> Name<name1> Name<name2> Name<name3> -# -# FirstLastType could be just an enum but added for convenience for printing NMODL: -# -# Expression:FirstLastTypeIndex => FirstLastType<value> -# Statement:PartialBoundary("~") => ?Name<del>(": ") -# Identifier<name> -# ?FirstLastTypeIndex<index>("[:]") -# ?Expression<expression>("=:") -# ?Name<name1>("=:*") -# ?Name<del2>(":(") -# ?Name<name2>(":)") -# ?Name<name3>("+:") -# -# Function and Function table. Note that argument is pair of <name, unit> for function table: -# -# Block:FunctionTableBlock("FUNCTION_TABLE") => Name<name> *Argument<arguments>("!(:!):,") ?Unit<unit> -# Block:FunctionBlock("FUNCTION") => Name<name> *Argument<arguments>("!(:!):,") -# ?Unit<unit> StatementBlock<statementblock> -# -# Procedure and NetReceive functions: -# -# Block:ProcedureBlock("PROCEDURE") => Name<name> -# *Argument<arguments>("!(:!):,") -# ?Unit<unit> StatementBlock<statementblock> -# Block:NetReceiveBlock("NET_RECEIVE") => *Argument<arguments>("!(:!):,") -# StatementBlock<statementblock> -# -# Usually solve block is a single statement but 'iferr' could be also a block -# and hence it is appropriate to define it as a Block like original bison -# specification: -# -# Block:SolveBlock("SOLVE") => Name<name> -# ?Name<method>(" METHOD :") -# StatementBlock<ifsolerr>("IFERROR:") -# -# Breakpoint block: -# -# Block:BreakpointBlock("BREAKPOINT") => StatementBlock<statementblock> -# -# Terminal block: -# -# Block:TerminalBlock("TERMINAL") => StatementBlock<statementblock> -# -# Before/After block could be of any type and hence added enum for BAType: -# -# Block:BeforeBlock("BEFORE") => BABlock<block> -# Block:AfterBlock("AFTER") => BABlock<block> -# Expression:BABlockType => BAType<value> -# Block:BABlock => BABlockType<type> StatementBlock<statementblock> -# -# WatchStatement has the form : WATCH (v < vh) down, (v > vth) up -# which is actually an expression. From current tests this seems sufficient. -# Watch statement usually use macro as an integer value (which is now handled by integer) : -# -# Statement:WatchStatement("WATCH") => *Watch<+statements>("::,") -# Expression:Watch => Expression<expression> Expression<value>(" :") -# -# ForNetcon is also a function but without name or units: -# -# Block:ForNetcon("FOR_NETCONS") => *Argument<arguments>("!(:!):,") StatementBlock<statementblock> -# -# Mutex lock / unlock and and Reset statement: -# -# Statement:MutexLock("MUTEXLOCK") => -# Statement:MutexUnlock("MUTEXUNLOCK") => -# Statement:Reset("RESET") => -# -# Sens statement: -# Todo: No test case added yet -# -# Statement:Sens("SENS") => *VarName<senslist>("::,") -# -# Conserve statement which is assignment expression with lhs and rhs: -# -# Statement:Conserve("CONSERVE") => Expression<react> Expression<expr>("=:") -# -# Compartment statement: -# -# Statement:Compartment("COMPARTMENT") => ?Name<name>(":,") -# Expression<expression> -# *Name<names>("{:}: ") -# -# Diffusion statement: -# -# Statement:LonDifuse("LONGITUDINAL_DIFFUSION") => ?Name<name>(":,") -# Expression<expression> -# *Name<names>("{:}: ") -# Kinetic block: -# -# Block:KineticBlock("KINETIC") => Name<name> *Name<solvefor> StatementBlock<statementblock> -# -# Reaction statement: -# Todo: check test cases and discuss with Michael -# -# Statement:ReactionStatement("~") => Expression<react1>(": ") -# ReactionOperator<op>(" : ") -# ?Expression<react2>(" : ") -# Expression<expr1>("(:") -# ?Expression<expr2>(",:!)") -# -# Variable used in react production: -# -# Identifier:ReactVarName => ?Integer<value>(" :") VarName<name> -# -# Lag statement: -# -# Statement:LagStatement("LAG") => Identifier<name> Name<byname>(" BY :") -# -# Put or Get queue statement: -# -# Statement:QueueStatement => QueueExpressionType<qype> Identifier<name> -# Expression:QueueExpressionType => QueueType<value> -# -# Match block and match statements: -# Todo: Expression lhs has parenthesis and its better to represent lhs and rhs -# separately rather than binary expression (for printing) -# -# Block:MatchBlock("MATCH") => *Match<matchs> -# Expression:Match => Identifier<name> ?Expression<expression> -# -# Unit block and statements within this block -# -# Block:UnitBlock("UNITS") => *Expression<definitions> -# Expression:UnitDef => Unit<unit1> Unit<unit2>("=:") -# -# Factordef statement (boolean variable gt to decide if '->' symbol exist in expression) -# This is the only case where "gt" member needs a "nmodl name" as "->" -# -# Expression:FactorDef => Name<name>(":=") ?Double<value> Unit<unit1> ?Boolean<gt>("->") ?Unit<unit2> -# -# Constant blocks and statements within this block: -# -# Statement:ConstantStatement => Name<name> Number<value>("=:") ?Unit<unit> -# Block:ConstantBlock("CONSTANT") => *ConstantStatement<statements> -# -# Table statement: -# -# Statement:TableStatement("TABLE") => *Name<tablst>("::,") -# *Name<dependlst>(" DEPEND ::,") -# Expression<from>(" FROM :") -# Expression<to>(" TO :") -# Integer<with>(" WITH :") -# -# Neuron block and Various statement within this block: -# -# Initial design confusion was whether we need separate class for every -# statement type. This is required because when we construct AST, every node -# need to be interpreted differently. This could be though separate class or -# some enum property. It might be straightforward to generate code with different -# classes. -# -# Block:NeuronBlock("NEURON") => StatementBlock<statementblock> -# -# Identifier:ReadIonVar => Name<name> -# Identifier:WriteIonVar => Name<name> -# Identifier:NonspeCurVar => Name<name> -# Identifier:ElectrodeCurVar => Name<name> -# Identifier:SectionVar => Name<name> -# Identifier:RangeVar => Name<name> -# Identifier:GlobalVar => Name<name> -# Identifier:PointerVar => Name<name> -# Identifier:BbcorePointerVar => Name<name> -# Identifier:ExternVar => Name<name> -# Identifier:ThreadsafeVar => Name<name> -# -# Statement types -# Statement:Suffix => Name<type>(": ") Name<name> -# Statement:Useion("USEION") => Name<name> -# *ReadIonVar<readlist>(" READ ::,") -# *WriteIonVar<writelist>(" WRITE ::,") -# ?Valence<valence> -# Statement:Nonspecific("NONSPECIFIC_CURRENT") => *NonspeCurVar<currents>("::,") -# Statement:ElctrodeCurrent("ELECTRODE_CURRENT") => *ElectrodeCurVar<ecurrents>("::,") -# Statement:Section("SECTION") => *SectionVar<sections>("::,") -# Statement:Range("RANGE") => *RangeVar<range_vars>("::,") -# Statement:Global("GLOBAL") => *GlobalVar<global_vars>("::,") -# Statement:Pointer("POINTER") => *PointerVar<pointers>("::,") -# Statement:BbcorePtr("BBCOREPOINTER") => *BbcorePointerVar<bbcore_pointers>("::,") -# Statement:External("EXTERNAL") => *ExternVar<externals>("::,") -# Statement:ThreadSafe("THREADSAFE") => *ThreadsafeVar<threadsafe>("::,") -# -# Verbatim and Comment statements -# Todo : Need a way to distinguish inline comment and block comment -# -# Statement:Verbatim("VERBATIM") => String<statement>(":ENDVERBATIM") -# Statement:Comment("COMMENT") => String<comment>(":ENDCOMMENT") -# -# Valence expression for use ion statement -# -# Expression:Valence => Name<type>(" : ") Double<value> -# +# NOTE +# ==== # +# Old text specification has been converted to YAML format. The old documentation has information +# about how/why specific types were choosen for AST. You can find that documentation in the older +# commits of this file (e.g. a0a20e277ccf26752af4f648c245b4e06a44af20). # # YAML BASED RULE SPECIFICATION # ============================= diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 5c7550d826..d6c1995685 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -136,18 +136,10 @@ PRIME_NAME_NODE = "PrimeName" STRING_NODE = "String" NUMBER_NODE = "Number" -IDENTIFIER_NODE = "Identifier" +BINARY_EXPRESSION_NODE = "BinaryExpression" NAME_NODE = "Name" BOOLEAN_NODE = "Boolean" INTEGER_NODE = "Integer" -REACTION_STATEMENT_NODE = "ReactionStatement" -CONSERVE_NODE = "Conserve" -EXPRESSION_NODE = "Expression" -BINARY_EXPRESSION_NODE = "BinaryExpression" -UNARY_EXPRESSION_NODE = "UnaryExpression" -VERBATIM_NODE = "Verbatim" -COMMENT_NODE = "Comment" -INDEPENDENTDEF_NODE = "IndependentDef" STATEMENT_BLOCK_NODE = "StatementBlock" UNIT_BLOCK = "UnitBlock" diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 19cb24dcf1..c479200280 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -29,27 +29,6 @@ def get_data_type_name(self): """ return type name for the node """ return DATA_TYPES[self.class_name] - def get_typename(self): - """returns type of the node for declaration - - When node is of base type then it is returned as it is. Depending - on pointer or list, appropriate suffix is added. Some of the examples - of typename are Expression, ExpressionList, Expression* etc. - - This is different than child node because childs are typically - class members and need to be - """ - - type_name = self.class_name - - if self.class_name not in BASE_TYPES and self.class_name not in PTR_EXCLUDE_TYPES: - type_name += "*" - - return type_name - - def is_expression_node(self): - return True if self.class_name == EXPRESSION_NODE else False - def is_statement_block_node(self): return True if self.class_name == STATEMENT_BLOCK_NODE else False @@ -79,27 +58,6 @@ def is_block_node(self): """ return True if self.class_name in BLOCK_TYPES else False - def is_reaction_statement_node(self): - return True if self.class_name == REACTION_STATEMENT_NODE else False - - def is_conserve_node(self): - return True if self.class_name == CONSERVE_NODE else False - - def is_binary_expression_node(self): - return True if self.class_name == BINARY_EXPRESSION_NODE else False - - def is_unary_expression_node(self): - return True if self.class_name == UNARY_EXPRESSION_NODE else False - - def is_verbatim_node(self): - return True if self.class_name == VERBATIM_NODE else False - - def is_comment_node(self): - return True if self.class_name == COMMENT_NODE else False - - def is_independentdef_node(self): - return True if self.class_name == INDEPENDENTDEF_NODE else False - def is_unit_block(self): return True if self.class_name == UNIT_BLOCK else False From f0d01c2919e137f20ded4e51ef577e845fc0de94 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 1 Jan 2019 15:36:58 +0100 Subject: [PATCH 103/871] Refactor: - getname changed to node_name property - added setter methods with && Change-Id: If9d3a944f273b6aa2bee08024ca8b808dfaec80b NMODL Repo SHA: BlueBrain/nmodl@9df310d45040c52185590fdb7af93db0d02b439c --- src/nmodl/ast/ast_common.hpp | 4 +- .../codegen/base/codegen_base_visitor.cpp | 2 +- .../codegen/base/codegen_base_visitor.hpp | 2 +- .../codegen/base/codegen_helper_visitor.cpp | 16 ++-- src/nmodl/codegen/c/codegen_c_visitor.cpp | 46 +++++------ src/nmodl/codegen/codegen_info.cpp | 2 +- src/nmodl/language/argument.py | 2 +- src/nmodl/language/ast_printer.py | 29 ++++--- src/nmodl/language/nmodl.yaml | 76 +++++++++---------- src/nmodl/language/nodes.py | 2 +- src/nmodl/language/parser.py | 6 +- src/nmodl/language/visitors_printer.py | 2 +- src/nmodl/parser/nmodl.yy | 6 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 4 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 4 +- src/nmodl/visitors/inline_visitor.cpp | 6 +- .../visitors/local_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 6 +- src/nmodl/visitors/rename_visitor.cpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.cpp | 10 +-- src/nmodl/visitors/var_usage_visitor.cpp | 2 +- 23 files changed, 120 insertions(+), 115 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 2a4f18685b..61ec61224e 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -74,8 +74,8 @@ namespace ast { virtual AstNodeType get_node_type() = 0; virtual std::string get_node_type_name() = 0; - virtual std::string get_name() { - throw std::logic_error("get_name() not implemented"); + virtual std::string get_node_name() { + throw std::logic_error("get_node_name() not implemented"); } virtual AST* clone() { diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index a2c8b92b43..3e223b7485 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -177,7 +177,7 @@ void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { if (!codegen) { return; } - auto name = node->get_name(); + auto name = node->get_node_name(); auto from = node->get_from(); auto to = node->get_to(); auto inc = node->get_opinc(); diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 7b7ae3ccf4..947d6c7da8 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -495,7 +495,7 @@ template <typename T> bool has_parameter_of_name(const T &node, std::string name) { auto parameters = node->get_parameters(); for (const auto& parameter : parameters) { - if (parameter->get_name() == name) { + if (parameter->get_node_name() == name) { return true; } } diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index 780086561e..ca2d0b3d45 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -403,7 +403,7 @@ void CodegenHelperVisitor::find_table_variables() { void CodegenHelperVisitor::visit_suffix(Suffix* node) { - auto type = node->get_type()->get_name(); + auto type = node->get_type()->get_node_name(); if (type == point_process) { info.point_process = true; } @@ -411,7 +411,7 @@ void CodegenHelperVisitor::visit_suffix(Suffix* node) { info.artificial_cell = true; info.point_process = true; } - info.mod_suffix = node->get_name(); + info.mod_suffix = node->get_node_name(); } @@ -476,7 +476,7 @@ void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { - auto name = node->get_name(); + auto name = node->get_node_name(); if (name == net_send_method) { info.net_send_used = true; } @@ -491,18 +491,18 @@ void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { auto variable = node->get_conductance(); std::string ion_name; if (ion) { - ion_name = ion->get_name(); + ion_name = ion->get_node_name(); } - info.conductances.push_back({ion_name, variable->get_name()}); + info.conductances.push_back({ion_name, variable->get_node_name()}); } void CodegenHelperVisitor::visit_solve_block(SolveBlock* node) { info.num_solve_blocks++; if (under_breakpoint_block) { - info.solve_block_name = node->get_block_name()->get_name(); + info.solve_block_name = node->get_block_name()->get_node_name(); if (node->get_method()) { - info.solve_method = node->get_method()->get_name(); + info.solve_method = node->get_method()->get_node_name(); if (info.solve_method == derivimplicit_method) { info.derivimplicit_used = true; } else if (info.solve_method == euler_method) { @@ -536,7 +536,7 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { statement->accept(this); if (under_derivative_block && assign_lhs && (assign_lhs->is_name() || assign_lhs->is_var_name())) { - auto name = assign_lhs->get_name(); + auto name = assign_lhs->get_node_name(); auto symbol = psymtab->lookup(name); if (symbol != nullptr) { auto is_prime = symbol->has_properties(NmodlType::prime_name); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 36ccbc7ac2..8798fff8ae 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -594,7 +594,7 @@ void CodegenCVisitor::visit_watch_statement(ast::WatchStatement* node) { void CodegenCVisitor::print_function_call(FunctionCall* node) { - auto name = node->get_name(); + auto name = node->get_node_name(); auto function_name = name; if (defined_method(name)) { function_name = method_name(name); @@ -689,12 +689,12 @@ void CodegenCVisitor::print_function_prototypes() { codegen = true; printer->add_newline(2); for (const auto& node : info.functions) { - print_function_declaration(node, node->get_name()); + print_function_declaration(node, node->get_node_name()); printer->add_text(";"); printer->add_newline(); } for (const auto& node : info.procedures) { - print_function_declaration(node, node->get_name()); + print_function_declaration(node, node->get_node_name()); printer->add_text(";"); printer->add_newline(); } @@ -709,7 +709,7 @@ static TableStatement* get_table_statement(ast::Block* node) { auto nstatements = table_statements.size(); if (nstatements != 1) { auto message = - "One table statement expected in {} found {}"_format(node->get_name(), nstatements); + "One table statement expected in {} found {}"_format(node->get_node_name(), nstatements); throw std::runtime_error(message); } return table_statements.front(); @@ -717,7 +717,7 @@ static TableStatement* get_table_statement(ast::Block* node) { void CodegenCVisitor::print_table_check_function(ast::Block* node) { - auto name = node->get_name(); + auto name = node->get_node_name(); auto internal_params = internal_method_parameters(); auto statement = get_table_statement(node); auto table_variables = statement->table_vars; @@ -740,11 +740,11 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_line("static bool make_table = true;"); for (auto& variable : depend_variables) { - printer->add_line("static {} save_{};"_format(float_type, variable->get_name())); + printer->add_line("static {} save_{};"_format(float_type, variable->get_node_name())); } for (auto& variable : depend_variables) { - auto name = variable->get_name(); + auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); printer->add_line(" make_table = true;"); @@ -778,7 +778,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { auto function = method_name("f_" + name); printer->add_line(" {}({}, x);"_format(function, internal_method_arguments())); for (auto& variable : table_variables) { - auto name = variable->get_name(); + auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); printer->add_line(" {}[i] = {};"_format(table_name, instance_name)); @@ -786,7 +786,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_line("}"); for (auto& variable : depend_variables) { - auto name = variable->get_name(); + auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); printer->add_line("save_{} = {};"_format(name, instance_name)); } @@ -798,7 +798,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { } void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { - auto name = node->get_name(); + auto name = node->get_node_name(); auto statement = get_table_statement(node); auto table_variables = statement->table_vars; auto with = statement->with->eval(); @@ -820,7 +820,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); printer->add_line("if (isnan(xi)) {"); for (auto& var : table_variables) { - auto name = get_variable_name(var->get_name()); + auto name = get_variable_name(var->get_node_name()); printer->add_line(" {} = xi;"_format(name)); } printer->add_line(" return 0;"); @@ -829,7 +829,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); for (auto& variable : table_variables) { - auto name = variable->get_name(); + auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); printer->add_line(" {} = {}[index];"_format(instance_name, table_name)); @@ -840,8 +840,8 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("int i = int(xi);"); printer->add_line("double theta = xi - double(i);"); for (auto& var : table_variables) { - auto instance_name = get_variable_name(var->get_name()); - auto table_name = get_variable_name("t_" + var->get_name()); + auto instance_name = get_variable_name(var->get_node_name()); + auto table_name = get_variable_name("t_" + var->get_node_name()); printer->add_line( "{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);"_format(instance_name, table_name)); } @@ -870,7 +870,7 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" IonCurVar ionvar = {0};"); for (auto& function : info.functions_with_table) { - auto name = method_name("check_" + function->get_name()); + auto name = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); printer->add_line(" {}({});"_format(name, arguments)); } @@ -905,7 +905,7 @@ void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { codegen = true; - auto name = node->get_name(); + auto name = node->get_node_name(); if (info.function_uses_table(name)) { auto new_name = "f_" + name; @@ -922,7 +922,7 @@ void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = true; - auto name = node->get_name(); + auto name = node->get_node_name(); auto return_var = "ret_" + name; /// first rename return variable name @@ -1606,7 +1606,7 @@ void CodegenCVisitor::print_mechanism_global_structure() { global_variables.push_back(make_symbol("usetable")); for (auto& block : info.functions_with_table) { - auto name = block->get_name(); + auto name = block->get_node_name(); printer->add_line("{} tmin_{};"_format(float_type, name)); printer->add_line("{} mfac_{};"_format(float_type, name)); global_variables.push_back(make_symbol("tmin_" + name)); @@ -2516,7 +2516,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node) { int i = 0; printer->add_newline(); for (auto& parameter : parameters) { - auto name = parameter->get_name(); + auto name = parameter->get_node_name(); VarUsageVisitor vu; auto var_used = vu.variable_used(node, "(*" + name + ")"); if (var_used) { @@ -2728,7 +2728,7 @@ void CodegenCVisitor::print_net_receive() { /// rename arguments but need to see if they are actually used auto parameters = node->get_parameters(); for (auto& parameter : parameters) { - auto name = parameter->get_name(); + auto name = parameter->get_node_name(); auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); @@ -2803,7 +2803,7 @@ void CodegenCVisitor::print_derivative_kernel_for_euler() { printer->add_newline(2); printer->add_line("/* _euler_ state _{} */"_format(info.mod_suffix)); - printer->start_block("int {}_{}({})"_format(node->get_name(), info.mod_suffix, arguments)); + printer->start_block("int {}_{}({})"_format(node->get_node_name(), info.mod_suffix, arguments)); printer->add_line(instance); print_statement_block(node->get_statement_block().get(), false, false); printer->add_line("return 0;"); @@ -2836,7 +2836,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_newline(2); // clang-format off - printer->start_block("int {}_{}({})"_format(node->get_name(), suffix, ext_params)); + printer->start_block("int {}_{}({})"_format(node->get_node_name(), suffix, ext_params)); auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); auto slist1 = "int* slist{} = {};"_format(list_num, get_variable_name("slist{}"_format(list_num))); auto slist2 = "int* slist{} = {};"_format(list_num+1, get_variable_name("slist{}"_format(list_num+1))); @@ -2859,7 +2859,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_newline(); printer->add_newline(2); - printer->start_block("int newton_{}_{}({}) "_format(node->get_name(), info.mod_suffix, external_method_parameters())); + printer->start_block("int newton_{}_{}({}) "_format(node->get_node_name(), info.mod_suffix, external_method_parameters())); printer->add_line(instance); printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); printer->add_line(slist1); diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 784cacb5cb..09a1bdc940 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -59,7 +59,7 @@ bool CodegenInfo::is_current(const std::string& name) { bool CodegenInfo::function_uses_table(std::string& name) const { for (auto& function : functions_with_table) { - if (name == function->get_name()) { + if (name == function->get_node_name()) { return true; } } diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 65f6ee47db..d9c5839b11 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -15,7 +15,7 @@ def __init__(self): self.is_vector = False self.is_optional = False self.add_method = False - self.getname_method = False + self.get_node_name = False self.has_token = False self.getter_method = False self.getter_override = False diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index f1d888bdc2..fefcb5c06a 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -18,8 +18,10 @@ def class_name_declaration(self): self.write_line() self.write_line("/* Type for every ast node */") self.write_line("enum class AstNodeType {", post_gutter=1) + for node in sorted(self.nodes): self.write_line("{},".format(to_snake_case(node.class_name).upper())) + self.write_line("};", pre_gutter=-1, newline=2) self.write_line("/* std::vector for convenience */") @@ -70,7 +72,8 @@ def ast_classes_declaration(self): for node in self.nodes: self.write_line("class {} : public {} {{".format(node.class_name, node.base_class), post_gutter=1) - self.write_line("public:", post_gutter=1, newline=1) + + self.write_line("public:", post_gutter=1) for child in node.children: self.write_line("{} {};".format(child.member_typename, child.varname)) @@ -114,20 +117,22 @@ def ast_classes_declaration(self): self.write_line(" {}.emplace_back(s);".format(varname)) self.write_line("}") - if child.getname_method: + if child.get_node_name: # string node should be evaluated and hence eval() method - method = "eval" if child.is_string_node() else "get_name" - self.write_line("virtual std::string get_name() override {") + method = "eval" if child.is_string_node() else "get_node_name" + self.write_line("virtual std::string get_node_name() override {") self.write_line(" return {}->{}();".format(varname, method)) self.write_line("}") - else: - getter_method = child.getter_method if child.getter_method else "get_" + to_snake_case(varname) - getter_override = " override" if child.getter_override else "" - return_type = child.return_typename - if getter_method == "get_name": - print "WARNING : skipping get_name method for node {} and member name of type {}".format(node.class_name, return_type) - else : - self.write_line("{} {}(){}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) + + getter_method = child.getter_method if child.getter_method else "get_" + to_snake_case(varname) + getter_override = " override" if child.getter_override else "" + return_type = child.return_typename + self.write_line("{} {}(){}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) + + setter_method = "set_" + to_snake_case(varname) + setter_type = child.member_typename + reference = "" if child.is_base_type_node() else "&&" + self.write_line("void {0}({1}{2} {3}) {{ this->{3} = {3}; }}".format(setter_method, setter_type, reference, varname)) self.write_line() diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 11dfa5282b..a706aa44c9 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -62,7 +62,7 @@ # value : indicates the value (i.e. string here) of prefix/suffix itself # force : print value always even if member if null pointer itself (e.g. arguments) # 8. separator : for the vector data type, the separator for prinitng elements -# 9. getname : add getName() method to class and return corresponding value as name +# 9. node_name : add get_node_name() method to class and return corresponding value as name # 10. add : need to have addMemberType method for corresponding variable (todo : remove this) # 11. order of the members is important and determines how constructors/nmodl visitor get # defined from python code generators. @@ -102,19 +102,19 @@ members: - value: type: String - getname: true + node_name: true - PrimeName: members: - value: type: String - getname: true + node_name: true - order: type: Integer - VarName: members: - name: type: Identifier - getname: true + node_name: true - at_index: type: Integer optional: true @@ -128,7 +128,7 @@ members: - name: type: Identifier - getname: true + node_name: true - length: type: Expression prefix: {value: "["} @@ -137,7 +137,7 @@ members: - name: type: Identifier - getname: true + node_name: true - unit: type: Unit optional: true @@ -149,62 +149,62 @@ prefix: {value: " "} - name: type: VarName - getname: true + node_name: true - ReadIonVar: members: - name: type: Name - getname: true + node_name: true - WriteIonVar: members: - name: type: Name - getname: true + node_name: true - NonspeCurVar: members: - name: type: Name - getname: true + node_name: true - ElectrodeCurVar: members: - name: type: Name - getname: true + node_name: true - SectionVar: members: - name: type: Name - getname: true + node_name: true - RangeVar: members: - name: type: Name - getname: true + node_name: true - GlobalVar: members: - name: type: Name - getname: true + node_name: true - PointerVar: members: - name: type: Name - getname: true + node_name: true - BbcorePointerVar: members: - name: type: Name - getname: true + node_name: true - ExternVar: members: - name: type: Name - getname: true + node_name: true - ThreadsafeVar: members: - name: type: Name - getname: true + node_name: true - Block: - ParamBlock: nmodl: "PARAMETER " @@ -268,7 +268,7 @@ members: - name: type: Name - getname: true + node_name: true suffix: {value: " "} - statement_block: type: StatementBlock @@ -278,7 +278,7 @@ members: - name: type: Name - getname: true + node_name: true suffix: {value: " "} - solvefor: type: Name @@ -293,7 +293,7 @@ members: - name: type: Name - getname: true + node_name: true - solvefor: type: Name vector: true @@ -308,7 +308,7 @@ members: - name: type: Name - getname: true + node_name: true suffix: {value: " "} - statement_block: type: StatementBlock @@ -318,7 +318,7 @@ members: - name: type: Name - getname: true + node_name: true suffix: {value: " "} - statement_block: type: StatementBlock @@ -328,7 +328,7 @@ members: - name: type: Name - getname: true + node_name: true - parameters: type: Argument vector: true @@ -345,7 +345,7 @@ members: - name: type: Name - getname: true + node_name: true - parameters: type: Argument vector: true @@ -366,7 +366,7 @@ members: - name: type: Name - getname: true + node_name: true - parameters: type: Argument vector: true @@ -455,7 +455,7 @@ members: - name: type: Name - getname: true + node_name: true suffix: {value: " "} - solvefor: type: Name @@ -495,7 +495,7 @@ members: - name: type: String - getname: true + node_name: true prefix: {value: "("} suffix: {value: ")"} - DoubleUnit: @@ -509,7 +509,7 @@ members: - name: type: Identifier - getname: true + node_name: true - Limits: members: - min: @@ -542,7 +542,7 @@ members: - name: type: Name - getname: true + node_name: true - value: type: Number prefix: {value: " = "} @@ -606,7 +606,7 @@ members: - name: type: Name - getname: true + node_name: true - arguments: type: Expression vector: true @@ -645,7 +645,7 @@ members: - unit1: type: Unit - getname: true + node_name: true - unit2: type: Unit prefix: {value: " = "} @@ -653,7 +653,7 @@ members: - name: type: Name - getname: true + node_name: true suffix: {value: " ="} - value: type: Double @@ -713,7 +713,7 @@ members: - name: type: Identifier - getname: true + node_name: true - value: type: Number optional: true @@ -768,7 +768,7 @@ members: - name: type: Identifier - getname: true + node_name: true - length: type: Integer optional: true @@ -828,7 +828,7 @@ members: - name: type: Name - getname: True + node_name: true - from: type: Expression prefix: {value: " = "} @@ -1070,13 +1070,13 @@ suffix: {value: " "} - name: type: Name - getname: True + node_name: true - Useion: nmodl: "USEION " members: - name: type: Name - getname: true + node_name: true - readlist: type: ReadIonVar vector: true diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index c479200280..6ffa3fb75e 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -121,7 +121,7 @@ def __init__(self, args): self.is_vector = args.is_vector self.optional = args.is_optional self.add_method = args.add_method - self.getname_method = args.getname_method + self.get_node_name = args.get_node_name self.getter_method = args.getter_method self.getter_override = args.getter_override diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 44f78401bf..3d342e6850 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -73,9 +73,9 @@ def parse_child_rule(self, child): if 'vector' in properties: args.is_vector = properties['vector'] - # if getNmae method required - if 'getname' in properties: - args.getname_method = properties['getname'] + # if get_node_name method required + if 'node_name' in properties: + args.get_node_name = properties['node_name'] # if getter method required if 'getter' in properties: diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py index 2b7655bbc6..a91b95a141 100644 --- a/src/nmodl/language/visitors_printer.py +++ b/src/nmodl/language/visitors_printer.py @@ -258,7 +258,7 @@ def definitions(self): """this is for nodes which has parent class as Block node""" if node.is_symbol_block_node(): self.write_line("add_model_symbol_with_property(node, %s);" % property_name) - self.write_line("setup_symbol_table_for_scoped_block(node, node->get_name());") + self.write_line("setup_symbol_table_for_scoped_block(node, node->get_node_name());") else: self.write_line("setup_symbol_table_for_scoped_block(node, node->get_node_type_name());") diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index b92d17214c..244eaa617f 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -409,7 +409,7 @@ top : all { - driver.astRoot = std::shared_ptr<ast::Program>($1); + driver.astRoot.reset($1); } | error { @@ -497,7 +497,7 @@ model : MODEL LINE_PART define1 : DEFINE1 NAME INTEGER { $$ = new ast::Define($2, $3); - driver.add_defined_var($2->get_name(), $3->eval()); + driver.add_defined_var($2->get_node_name(), $3->eval()); } | DEFINE1 error { @@ -686,7 +686,7 @@ indepbody : { | indepbody SWEEP indepdef { $1.emplace_back($3); - $3->sweep = std::shared_ptr<ast::Boolean>(new ast::Boolean(1)); + $3->set_sweep(std::make_shared<ast::Boolean>(1)); $$ = $1; } ; diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 01fb9f1304..857be06926 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -58,11 +58,11 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { std::cerr << "cnexp solver not possible"; } } else if (solve_method == derivimplicit_method || solve_method == euler_method) { - auto varname = "D" + name->get_name(); + auto varname = "D" + name->get_node_name(); auto variable = new ast::Name(new ast::String(varname)); lhs.reset(variable); auto symbol = std::make_shared<symtab::Symbol>(varname, ModToken()); - symbol->set_original_name(name->get_name()); + symbol->set_original_name(name->get_node_name()); symbol->created_from_state(); program_symtab->insert(symbol); } else { diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index af29f1e757..b3218ba6bb 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -171,7 +171,7 @@ void DefUseAnalyzeVisitor::visit_unsupported_node(Node* node) { * unsupported. */ void DefUseAnalyzeVisitor::visit_function_call(FunctionCall* node) { - std::string function_name = node->name->get_name(); + std::string function_name = node->name->get_node_name(); auto symbol = global_symtab->lookup_in_scope(function_name); if (symbol == nullptr || symbol->is_external_symbol_only()) { node->visit_children(this); diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 69b53dce82..1dde4da073 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -230,11 +230,11 @@ class DefUseAnalyzeVisitor : public AstVisitor { } virtual void visit_var_name(ast::VarName* node) override { - update_defuse_chain(node->get_name()); + update_defuse_chain(node->get_node_name()); }; virtual void visit_name(ast::Name* node) override { - update_defuse_chain(node->get_name()); + update_defuse_chain(node->get_node_name()); }; diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 000f47e3bb..af4fea678e 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -74,7 +74,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, for (const auto& argument : callee_parameters) { auto name = argument->name->clone(); - auto old_name = name->get_name(); + auto old_name = name->get_node_name(); auto new_name = get_new_name(old_name, "in", inlined_variables); name->set_name(new_name); @@ -106,7 +106,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, bool InlineVisitor::inline_function_call(ast::Block* callee, ast::FunctionCall* node, ast::StatementBlock* caller) { - std::string function_name = callee->get_name(); + std::string function_name = callee->get_node_name(); /// do nothing if we can't inline given procedure/function if (!can_inline_block(callee->get_statement_block().get())) { @@ -180,7 +180,7 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { /// argument can be function call itself node->visit_children(this); - std::string function_name = node->name->get_name(); + std::string function_name = node->name->get_node_name(); auto symbol = program_symtab->lookup_in_scope(function_name); /// nothing to do if called function is not defined or it's external diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 1d7080e40d..c67e5b358d 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -47,7 +47,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { RenameVisitor rename_visitor; for (auto& var : *variables) { - std::string name = var->get_name(); + std::string name = var->get_node_name(); auto s = parent_symtab->lookup_in_scope(name); /// if symbol is a variable name (avoid renaming use of units like mV) if (s && s->is_variable()) { diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 988cf54ee4..aa67d55e0a 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -47,7 +47,7 @@ bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { */ bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { if (node->is_procedure_block()) { - auto symbol = program_symtab->lookup(node->get_name()); + auto symbol = program_symtab->lookup(node->get_node_name()); if (symbol && symbol->has_properties(NmodlType::to_solve)) { return true; } diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index a0c9b98dff..1f05faf1b9 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -154,7 +154,7 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { under_function_call = true; if (start_measurement) { - auto name = node->name->get_name(); + auto name = node->name->get_node_name(); if (name == "exp") { current_block_perf.n_exp++; } else if (name == "log") { @@ -178,13 +178,13 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { /// every variable used is of type name, update counters void PerfVisitor::visit_name(Name* node) { - update_memory_ops(node->get_name()); + update_memory_ops(node->get_node_name()); node->visit_children(this); } /// prime name derived from identifier and hence need to be handled here void PerfVisitor::visit_prime_name(PrimeName* node) { - update_memory_ops(node->get_name()); + update_memory_ops(node->get_node_name()); node->visit_children(this); } diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 7247ede44d..4b68d47872 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -5,7 +5,7 @@ using namespace ast; /// rename matching variable void RenameVisitor::visit_name(Name* node) { - std::string name = node->get_name(); + std::string name = node->get_node_name(); if (name == var_name) { node->value->set(new_var_name); } diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index 5363439e7c..23a7d65f41 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -18,7 +18,7 @@ static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, } /// add new symbol - auto symbol = std::make_shared<Symbol>(node->get_name(), node, token); + auto symbol = std::make_shared<Symbol>(node->get_node_name(), node, token); symbol->add_property(property); // non specific variable is range @@ -38,7 +38,7 @@ static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, /// for the ast nodes which are of variable types void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { std::shared_ptr<Symbol> symbol; - auto name = node->get_name(); + auto name = node->get_node_name(); /// if prime variable is already exist in symbol table /// then just update the order @@ -112,7 +112,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { void SymtabVisitor::add_model_symbol_with_property(Node* node, NmodlTypeFlag property) { auto token = node->get_token(); - auto name = node->get_name(); + auto name = node->get_node_name(); auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); @@ -152,7 +152,7 @@ void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool /// there is only one solve statement allowed in mod file if (node->is_solve_block()) { auto solve_block = dynamic_cast<SolveBlock*>(node); - block_to_solve = solve_block->get_block_name()->get_name(); + block_to_solve = solve_block->get_block_name()->get_node_name(); } /// not required at the moment but every node @@ -206,7 +206,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, const std::s void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { auto update_symbol = [this](NameVector& variables, NmodlType property, int num_values) { for (auto& var : variables) { - auto name = var->get_name(); + auto name = var->get_node_name(); auto symbol = modsymtab->lookup(name); if (symbol) { symbol->add_property(property); diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index aa138275e4..9feee3bfc5 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -4,7 +4,7 @@ /// rename matching variable void VarUsageVisitor::visit_name(ast::Name* node) { - std::string name = node->get_name(); + std::string name = node->get_node_name(); if (name == var_name) { used = true; } From 91574c701732222c6446c7f2795bcd42709bf8a0 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 2 Jan 2019 15:16:37 +0100 Subject: [PATCH 104/871] Make AST members private by defualt: - ast_printer updated to print public and private members - all visitors updated to use getter and setter - nmodl_printer updated to use string.format Change-Id: Ia34841bfe98739fc815333041ff7166a92a16ad4 NMODL Repo SHA: BlueBrain/nmodl@f3b9ef2290dae873023b73f585cc6e9145461253 --- src/nmodl/ast/ast_common.hpp | 2 +- .../codegen/base/codegen_base_visitor.cpp | 44 +++++++------ .../codegen/base/codegen_base_visitor.hpp | 5 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 36 +++++----- src/nmodl/language/argument.py | 1 + src/nmodl/language/ast_printer.py | 34 +++++----- src/nmodl/language/nmodl.yaml | 6 ++ src/nmodl/language/nmodl_printer.py | 61 ++++++----------- src/nmodl/language/nodes.py | 65 ++++++++++++------- src/nmodl/language/parser.py | 4 ++ src/nmodl/symtab/symbol_table.hpp | 3 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 12 ++-- src/nmodl/visitors/defuse_analyze_visitor.cpp | 18 ++--- src/nmodl/visitors/inline_visitor.cpp | 23 +++---- src/nmodl/visitors/inline_visitor.hpp | 4 +- .../visitors/local_var_rename_visitor.cpp | 6 +- src/nmodl/visitors/localize_visitor.cpp | 4 +- src/nmodl/visitors/perf_visitor.cpp | 12 ++-- src/nmodl/visitors/rename_visitor.cpp | 3 +- src/nmodl/visitors/symtab_visitor_helper.cpp | 34 +++++----- .../visitors/verbatim_var_rename_visitor.cpp | 6 +- src/nmodl/visitors/visitor_utils.cpp | 3 +- test/nmodl/transpiler/visitor/visitor.cpp | 6 +- 23 files changed, 212 insertions(+), 180 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 61ec61224e..2efa393a01 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -101,7 +101,7 @@ namespace ast { // implemented in Number sub classes virtual void negate() { - throw std::runtime_error("negate() not implemented"); + throw std::runtime_error("negate() not implemented"); } // implemented in Identifier sub classes diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 3e223b7485..6085da919b 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -98,14 +98,17 @@ void CodegenBaseVisitor::visit_var_name(VarName* node) { if (!codegen) { return; } - node->name->accept(this); - if (node->at_index) { + auto name = node->get_name(); + auto at_index = node->get_at_index(); + auto index = node->get_index(); + name->accept(this); + if (at_index) { printer->add_text("@"); - node->at_index->accept(this); + at_index->accept(this); } - if (node->index) { + if (index) { printer->add_text("["); - node->index->accept(this); + index->accept(this); printer->add_text("]"); } } @@ -115,9 +118,9 @@ void CodegenBaseVisitor::visit_indexed_name(IndexedName* node) { if (!codegen) { return; } - node->name->accept(this); + node->get_name()->accept(this); printer->add_text("["); - node->length->accept(this); + node->get_length()->accept(this); printer->add_text("]"); } @@ -128,7 +131,7 @@ void CodegenBaseVisitor::visit_local_list_statement(LocalListStatement* node) { } auto type = local_var_type() + " "; printer->add_text(type); - print_vector_elements(node->variables, ", "); + print_vector_elements(node->get_variables(), ", "); } @@ -137,12 +140,13 @@ void CodegenBaseVisitor::visit_if_statement(IfStatement* node) { return; } printer->add_text("if ("); - node->condition->accept(this); + node->get_condition()->accept(this); printer->add_text(") "); node->get_statement_block()->accept(this); - print_vector_elements(node->elseifs, ""); - if (node->elses) { - node->elses->accept(this); + print_vector_elements(node->get_elseifs(), ""); + auto elses = node->get_elses(); + if (elses) { + elses->accept(this); } } @@ -152,7 +156,7 @@ void CodegenBaseVisitor::visit_else_if_statement(ElseIfStatement* node) { return; } printer->add_text(" else if ("); - node->condition->accept(this); + node->get_condition()->accept(this); printer->add_text(") "); node->get_statement_block()->accept(this); } @@ -168,7 +172,7 @@ void CodegenBaseVisitor::visit_else_statement(ElseStatement* node) { void CodegenBaseVisitor::visit_while_statement(WhileStatement* node) { printer->add_text("while ("); - node->condition->accept(this); + node->get_condition()->accept(this); printer->add_text(") "); node->get_statement_block()->accept(this); } @@ -202,7 +206,7 @@ void CodegenBaseVisitor::visit_paren_expression(ParenExpression* node) { return; } printer->add_text("("); - node->expr->accept(this); + node->get_expr()->accept(this); printer->add_text(")"); } @@ -211,7 +215,7 @@ void CodegenBaseVisitor::visit_binary_expression(BinaryExpression* node) { if (!codegen) { return; } - auto op = node->op.eval(); + auto op = node->get_op().eval(); auto lhs = node->get_lhs(); auto rhs = node->get_rhs(); if (op == "^") { @@ -695,9 +699,9 @@ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { variables.back().is_constant = true; /// note that this variable is not printed in neuron implementation if (info.artificial_cell) { - variables.emplace_back(make_symbol("point_process"), true); + variables.emplace_back(make_symbol(point_process), true); } else { - variables.emplace_back(make_symbol("point_process"), false, false, true); + variables.emplace_back(make_symbol(point_process), false, false, true); variables.back().is_constant = true; } } @@ -733,11 +737,11 @@ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { } if (info.diam_used) { - variables.emplace_back(make_symbol("diam")); + variables.emplace_back(make_symbol(diam_variable)); } if (info.area_used) { - variables.emplace_back(make_symbol("area")); + variables.emplace_back(make_symbol(area_variable)); } // for non-artificial cell, when net_receive buffering is enabled diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index 947d6c7da8..bb39d3e1d5 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -182,6 +182,9 @@ class CodegenBaseVisitor : public AstVisitor { /// similar to node area but user can explicitly declare it const std::string area_variable = "area"; + /// point process variable + const std::string point_process = "point_process"; + /// nmodl language version std::string nmodl_version() { return "6.2.0"; @@ -492,7 +495,7 @@ void CodegenBaseVisitor::print_vector_elements(const std::vector<T>& elements, * @return True if argument with name exist */ template <typename T> -bool has_parameter_of_name(const T &node, std::string name) { +bool has_parameter_of_name(const T& node, std::string name) { auto parameters = node->get_parameters(); for (const auto& parameter : parameters) { if (parameter->get_node_name() == name) { diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 8798fff8ae..37a8018b11 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -708,8 +708,8 @@ static TableStatement* get_table_statement(ast::Block* node) { auto table_statements = v.get_statements(); auto nstatements = table_statements.size(); if (nstatements != 1) { - auto message = - "One table statement expected in {} found {}"_format(node->get_node_name(), nstatements); + auto message = "One table statement expected in {} found {}"_format(node->get_node_name(), + nstatements); throw std::runtime_error(message); } return table_statements.front(); @@ -717,18 +717,18 @@ static TableStatement* get_table_statement(ast::Block* node) { void CodegenCVisitor::print_table_check_function(ast::Block* node) { + auto statement = get_table_statement(node); + auto table_variables = statement->get_table_vars(); + auto depend_variables = statement->get_depend_vars(); + auto from = statement->get_from(); + auto to = statement->get_to(); auto name = node->get_node_name(); auto internal_params = internal_method_parameters(); - auto statement = get_table_statement(node); - auto table_variables = statement->table_vars; - auto depend_variables = statement->depend_vars; - auto from = statement->from; - auto to = statement->to; - auto with = statement->with->eval(); + auto with = statement->get_with()->eval(); auto use_table_var = get_variable_name("usetable"); - auto float_type = default_float_data_type(); auto tmin_name = get_variable_name("tmin_" + name); auto mfac_name = get_variable_name("mfac_" + name); + auto float_type = default_float_data_type(); printer->add_newline(2); print_device_method_annotation(); @@ -800,8 +800,8 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { auto name = node->get_node_name(); auto statement = get_table_statement(node); - auto table_variables = statement->table_vars; - auto with = statement->with->eval(); + auto table_variables = statement->get_table_vars(); + auto with = statement->get_with()->eval(); auto use_table_var = get_variable_name("usetable"); auto float_type = default_float_data_type(); auto tmin_name = get_variable_name("tmin_" + name); @@ -2400,14 +2400,14 @@ void CodegenCVisitor::print_watch_activate() { // todo : similar to neuron/coreneuron we are using // first watch and ignoring rest. for (int i = 0; i < info.watch_statements.size(); i++) { - auto& statement = info.watch_statements[i]; + auto statement = info.watch_statements[i]; printer->start_block("if (watch_id == {})"_format(i)); auto varname = get_variable_name("watch{}"_format(i + 1)); printer->add_indent(); printer->add_text("{} = 2 + "_format(varname)); - auto& watch = statement->statements.front(); - watch->expression->visit_children(this); + auto watch = statement->get_statements().front(); + watch->get_expression()->visit_children(this); printer->add_text(";"); printer->add_newline(); @@ -2438,8 +2438,8 @@ void CodegenCVisitor::print_watch_check() { print_post_channel_iteration_common_code(); for (int i = 0; i < info.watch_statements.size(); i++) { - auto& statement = info.watch_statements[i]; - auto& watch = statement->statements.front(); + auto statement = info.watch_statements[i]; + auto watch = statement->get_statements().front(); auto varname = get_variable_name("watch{}"_format(i + 1)); // start block 1 @@ -2448,7 +2448,7 @@ void CodegenCVisitor::print_watch_check() { // start block 2 printer->add_indent(); printer->add_text("if ("); - watch->expression->accept(this); + watch->get_expression()->accept(this); printer->add_text(") {"); printer->add_newline(); printer->increase_indent(); @@ -2463,7 +2463,7 @@ void CodegenCVisitor::print_watch_check() { auto t = get_variable_name("t"); printer->add_text( "ml->_net_send_buffer, 0, {}, 0, {}, {}+0.0, "_format(tqitem, point_process, t)); - watch->value->accept(this); + watch->get_value()->accept(this); printer->add_text(");"); printer->add_newline(); diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index d9c5839b11..d0ca7624ae 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -12,6 +12,7 @@ def __init__(self): self.separator = "" self.typename = "" self.varname = "" + self.is_public = False self.is_vector = False self.is_optional = False self.add_method = False diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py index fefcb5c06a..c6ab6ac199 100644 --- a/src/nmodl/language/ast_printer.py +++ b/src/nmodl/language/ast_printer.py @@ -73,23 +73,22 @@ def ast_classes_declaration(self): self.write_line("class {} : public {} {{".format(node.class_name, node.base_class), post_gutter=1) - self.write_line("public:", post_gutter=1) - - for child in node.children: - self.write_line("{} {};".format(child.member_typename, child.varname)) + private_members = node.private_members() + public_members = node.public_members() - if node.has_token: - self.write_line("std::shared_ptr<ModToken> token;") - - if node.is_symtab_needed(): - self.write_line("symtab::SymbolTable* symtab = nullptr;") + if private_members: + self.write_line("private:", post_gutter=1) + for member in private_members: + self.write_line("{} {};".format(member[0], member[1])) + self.write_line(post_gutter=-1) - if node.is_program_node(): - self.write_line("symtab::ModelSymbolTable model_symtab;") + self.write_line("public:", post_gutter=1) + for member in public_members: + self.write_line("{} {};".format(member[0], member[1])) if node.children: members = [ child.get_typename() + " " + child.varname for child in node.children] - self.write_line("") + self.write_line() self.write_line("{}({});".format(node.class_name, (", ".join(members)))) self.write_line("{0}(const {0}& obj);".format(node.class_name)) @@ -106,8 +105,7 @@ def ast_classes_declaration(self): # Todo : We need virtual destructor otherwise there will be memory leaks. # But we need define which are virtual base classes that needs virtual function. self.write_line("virtual ~{}() {{}}".format(node.class_name)) - - self.write_line("") + self.write_line() for child in node.children: class_name = child.class_name @@ -126,7 +124,7 @@ def ast_classes_declaration(self): getter_method = child.getter_method if child.getter_method else "get_" + to_snake_case(varname) getter_override = " override" if child.getter_override else "" - return_type = child.return_typename + return_type = child.member_typename self.write_line("{} {}(){}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) setter_method = "set_" + to_snake_case(varname) @@ -161,10 +159,8 @@ def ast_classes_declaration(self): self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() { return &model_symtab; }") if node.is_base_class_number_node(): - if node.is_boolean_node(): - self.write_line("void negate() override { value = !value; }") - else: - self.write_line("void negate() override { value = -value; }") + negation = "!" if node.is_boolean_node() else "-"; + self.write_line("void negate() override {{ value = {}value; }}".format(negation)) self.write_line("double to_double() override { return value; }") if node.is_name_node(): diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index a706aa44c9..b71ba556a0 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -263,6 +263,7 @@ - statements: type: Statement vector: true + public: true - DerivativeBlock: nmodl: "DERIVATIVE " members: @@ -576,10 +577,13 @@ members: - lhs: type: Expression + public: true - op: type: BinaryOperator + public: true - rhs: type: Expression + public: true - UnaryExpression: members: - op: @@ -691,6 +695,7 @@ type: LocalVar vector: true separator: ", " + public: true - Model: nmodl: TITLE members: @@ -1177,3 +1182,4 @@ type: Node vector: true add: true + public: true diff --git a/src/nmodl/language/nmodl_printer.py b/src/nmodl/language/nmodl_printer.py index d6a6eab013..0bbe4b023e 100644 --- a/src/nmodl/language/nmodl_printer.py +++ b/src/nmodl/language/nmodl_printer.py @@ -13,7 +13,7 @@ def class_comment(self): self.write_line("/* Visitor for printing AST back to NMODL */") def class_name_declaration(self): - self.write_line("class " + self.classname + " : public Visitor {") + self.write_line("class {} : public Visitor {{".format(self.classname)) def private_declaration(self): self.write_line("private:") @@ -21,23 +21,16 @@ def private_declaration(self): def public_declaration(self): self.write_line("public:", post_gutter=1) - line = self.classname + "() : printer(new NMODLPrinter()) {}" - self.write_line(line) + self.write_line("{}() : printer(new NMODLPrinter()) {{}}".format(self.classname)) + self.write_line("{}(std::string filename) : printer(new NMODLPrinter(filename)) {{}}".format(self.classname)) + self.write_line("{}(std::stringstream& stream) : printer(new NMODLPrinter(stream)) {{}}".format(self.classname)) + self.write_line() - line = self.classname + "(std::string filename) : printer(new NMODLPrinter(filename)) {}" - self.write_line(line) - - line = self.classname + "(std::stringstream& stream) : printer(new NMODLPrinter(stream)) {}" - self.write_line(line, newline=2) + self.write_line("template<typename T>") + self.write_line("void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement);") for node in self.nodes: - line = "virtual void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" - self.write_line(line) - - line = "template<typename T>" - self.write_line(line) - line = "void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement);" - self.write_line(line) + self.write_line("virtual void visit_{}(ast::{}* node) override;".format(to_snake_case(node.class_name), node.class_name)) class NmodlVisitorDefinitionPrinter(DefinitionPrinter): @@ -50,18 +43,15 @@ def headers(self): def add_element(self, name): if name: - name = '"' + name + '"' - self.write_line("printer->add_element(" + name + ");") + self.write_line('printer->add_element("{}");'.format(name)) def definitions(self): for node in self.nodes: - self.write_line("/* concrete visit function for " + node.class_name + " nodes */") - self.write_line("void NmodlPrintVisitor::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {", post_gutter=1) + self.write_line("void NmodlPrintVisitor::visit_{}({}* node) {{".format(to_snake_case(node.class_name), node.class_name), post_gutter=1) if node.nmodl_name: - name = '"' + node.nmodl_name + '"' - self.write_line("printer->add_element(" + name + ");") + self.write_line('printer->add_element("{}");'.format(node.nmodl_name)) self.add_element(node.prefix) @@ -83,16 +73,13 @@ def definitions(self): for child in node.children: self.add_element(child.force_prefix) - is_program = "false" + is_program = "true" if node.is_program_node() else "false" is_statement = "false" # unit block has expressions as children and hence need to be considered as statements if child.is_statement_node() or node.is_unit_block(): is_statement = "true" - if node.is_program_node(): - is_program = "true" - # In the current implementation all childrens are non-base-data-type nodes # i.e. int, double are wrapped into Integer, Double classes. Hence it is # not tested in non data types nodes. This restriction could be easily avoided @@ -101,52 +88,46 @@ def definitions(self): if not node.is_data_type_node(): raise "Base data type members not in non data type nodes handled/tested!" else: - self.write_line("// Processing " + child.class_name + " " + child.varname) # optional members or statement blocks are in brace (could be nullptr) # start of a brace if child.optional or child.is_statement_block_node(): - line = 'if(node->' + child.varname + ') {' - self.write_line(line, post_gutter=1) + self.write_line('if(node->get_{}()) {{'.format(child.varname), post_gutter=1) # todo : assugming member with nmodl of type Boolean. currently there # are only two use cases: SWEEP and "->"" if child.nmodl_name: - self.write_line('if(' + 'node->' + child.varname + '->eval()) {') - name = '"' + child.nmodl_name + '"' - self.write_line(" printer->add_element(" + name + ");") + self.write_line('if(node->get_{}()->eval()) {{'.format(child.varname)) + self.write_line(' printer->add_element("{}");'.format(child.nmodl_name)) self.write_line('}') # vector members could be empty and pre-defined method to iterate/visit elif child.is_vector: if child.prefix or child.suffix: - self.write_line("if (!node->" + child.varname + ".empty()) {", post_gutter=1) + self.write_line("if (!node->get_{}().empty()) {{".format(child.varname), post_gutter=1) self.add_element(child.prefix) - separator = '"' + child.separator + '"' - self.write_line("visit_element(node->" + child.varname + "," + separator + "," + is_program + "," + is_statement + ");"); + self.write_line('visit_element(node->get_{}(),"{}",{},{});'.format(child.varname, child.separator, is_program, is_statement)); self.add_element(child.suffix) if child.prefix or child.suffix: self.write_line("}", pre_gutter=-1) # prime need to be printed with "'" as suffix elif node.is_prime_node() and child.varname == ORDER_VAR_NAME: - self.write_line("auto order = node->" + child.varname + "->eval();") + self.write_line("auto order = node->get_{}()->eval();".format(child.varname)) self.write_line("auto symbol = std::string(order, '\\'');") self.write_line("printer->add_element(symbol);"); # add space surrounding certain binary operators for readability elif node.class_name == BINARY_EXPRESSION_NODE and child.varname == BINARY_OPERATOR_NAME: - self.write_line('std::string op = node->op.eval();') + self.write_line('std::string op = node->get_op().eval();') self.write_line('if(op == "=" || op == "&&" || op == "||" || op == "==")') self.write_line(' op = " " + op + " ";') self.write_line('printer->add_element(op);') else: self.add_element(child.prefix) - if child.is_pointer_node(): - self.write_line("node->" + child.varname + "->accept(this);") - else: - self.write_line("node->" + child.varname + ".accept(this);") + op = "->" if child.is_pointer_node() else "." + self.write_line("node->get_{}(){}accept(this);".format(child.varname, op)) self.add_element(child.suffix) # end of a brace diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 6ffa3fb75e..49b061b0a5 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -118,6 +118,7 @@ def __init__(self, args): BaseNode.__init__(self, args) self.typename = args.typename self.varname = args.varname + self.is_public = args.is_public self.is_vector = args.is_vector self.optional = args.is_optional self.add_method = args.add_method @@ -144,32 +145,14 @@ def get_typename(self): @property def member_typename(self): - """returns type when used as a member of the class - - todo: Check how to refactor this - """ - - type_name = self.class_name - - if self.is_vector: - type_name += "Vector" - elif not self.is_base_type_node() and not self.is_ptr_excluded_node(): - type_name = "std::shared_ptr<" + type_name + ">" - - return type_name - - @property - def return_typename(self): - """returns type when returned from getter method - - todo: Check how to refactor this - """ - type_name = "std::shared_ptr<" + self.class_name + ">" + """returns type when used as a member of the class""" if self.is_vector: type_name = self.class_name + "Vector" - elif self.is_ptr_excluded_node() or self.is_base_type_node(): - type_name = self.class_name + elif self.is_base_type_node() or self.is_ptr_excluded_node(): + type_name = self.class_name + else: + type_name = "std::shared_ptr<" + self.class_name + ">" return type_name @@ -251,4 +234,40 @@ def is_symtab_method_required(self): return method_required def is_base_class_number_node(self): + """ + Check if node is of type Number + """ return True if self.base_class == NUMBER_NODE else False + + def public_members(self): + """ + Return public members of the node + """ + members = [] + + for child in self.children: + if child.is_public: + members.append([child.member_typename, child.varname]) + + return members + + def private_members(self): + """ + Return private members of the node + """ + members = [] + + for child in self.children: + if not child.is_public: + members.append([child.member_typename, child.varname]) + + if self.has_token: + members.append(["std::shared_ptr<ModToken>", "token"]) + + if self.is_symtab_needed(): + members.append(["symtab::SymbolTable*", "symtab = nullptr"]) + + if self.is_program_node(): + members.append(["symtab::ModelSymbolTable", "model_symtab;"]) + + return members diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 3d342e6850..308bde77e1 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -69,6 +69,10 @@ def parse_child_rule(self, child): if 'separator' in properties: args.separator = properties['separator'] + # if variable is public member + if 'public' in properties: + args.is_public = properties['public'] + # if variable if of vector type if 'vector' in properties: args.is_vector = properties['vector'] diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index f40b4cedbb..ad4ca7c6ec 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -138,7 +138,8 @@ namespace symtab { std::shared_ptr<Symbol> lookup_in_scope(const std::string& name) const; - std::vector<std::shared_ptr<Symbol>> get_variables(NmodlTypeFlag with, NmodlTypeFlag without); + std::vector<std::shared_ptr<Symbol>> get_variables(NmodlTypeFlag with, + NmodlTypeFlag without); std::vector<std::shared_ptr<Symbol>> get_variables_with_properties(NmodlTypeFlag properties, bool all = false); diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 857be06926..0df65354d6 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -9,8 +9,9 @@ using namespace ast; void CnexpSolveVisitor::visit_solve_block(SolveBlock* node) { - if (node->method) { - solve_method = node->method->value->eval(); + auto method = node->get_method(); + if (method) { + solve_method = method->get_value()->eval(); } } @@ -23,9 +24,10 @@ void CnexpSolveVisitor::visit_derivative_block(DerivativeBlock* node) { void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { auto& lhs = node->lhs; auto& rhs = node->rhs; + auto& op = node->op; /// we have to only solve binary expressions in derivative block - if (!derivative_block || (node->op.value != BOP_ASSIGN)) { + if (!derivative_block || (op.get_value() != BOP_ASSIGN)) { return; } @@ -34,7 +36,7 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { return; } - auto name = std::dynamic_pointer_cast<VarName>(lhs)->name; + auto name = std::dynamic_pointer_cast<VarName>(lhs)->get_name(); if (name->is_prime_name()) { /// convert ode into string format @@ -51,7 +53,7 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); auto bin_expr = - std::dynamic_pointer_cast<BinaryExpression>(expr_statement->expression); + std::dynamic_pointer_cast<BinaryExpression>(expr_statement->get_expression()); lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index b3218ba6bb..e79b7de378 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -171,7 +171,7 @@ void DefUseAnalyzeVisitor::visit_unsupported_node(Node* node) { * unsupported. */ void DefUseAnalyzeVisitor::visit_function_call(FunctionCall* node) { - std::string function_name = node->name->get_node_name(); + std::string function_name = node->get_node_name(); auto symbol = global_symtab->lookup_in_scope(function_name); if (symbol == nullptr || symbol->is_external_symbol_only()) { node->visit_children(this); @@ -196,11 +196,11 @@ void DefUseAnalyzeVisitor::visit_statement_block(StatementBlock* node) { * and hence not necessary to keep track of assignment operator using stack. */ void DefUseAnalyzeVisitor::visit_binary_expression(BinaryExpression* node) { - node->rhs->visit_children(this); - if (node->op.value == BOP_ASSIGN) { + node->get_rhs()->visit_children(this); + if (node->get_op().get_value() == BOP_ASSIGN) { visiting_lhs = true; } - node->lhs->visit_children(this); + node->get_lhs()->visit_children(this); visiting_lhs = false; } @@ -210,12 +210,12 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { /// starting new if block previous_chain->push_back(DUInstance(DUState::CONDITIONAL_BLOCK)); - current_chain = &previous_chain->back().children; + current_chain = &(previous_chain->back().children); /// visiting if sub-block auto last_chain = current_chain; start_new_chain(DUState::IF); - node->condition->accept(this); + node->get_condition()->accept(this); auto block = node->get_statement_block(); if (block) { block->accept(this); @@ -223,13 +223,13 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { current_chain = last_chain; /// visiting else if sub-blocks - for (auto& item : node->elseifs) { + for (const auto& item : node->get_elseifs()) { visit_with_new_chain(item.get(), DUState::ELSEIF); } /// visiting else sub-block - if (node->elses) { - visit_with_new_chain(node->elses.get(), DUState::ELSE); + if (node->get_elses()) { + visit_with_new_chain(node->get_elses().get(), DUState::ELSE); } /// restore to previous chain diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index af4fea678e..fa2fc5145a 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -5,7 +5,8 @@ using namespace ast; bool InlineVisitor::can_inline_block(StatementBlock* block) { bool to_inline = true; - for (const auto& statement : block->statements) { + const auto& statements = block->get_statements(); + for (auto statement : statements) { /// inlining is disabled if function/procedure contains table or lag statement if (statement->is_table_statement() || statement->is_lag_statement()) { to_inline = false; @@ -50,10 +51,10 @@ bool InlineVisitor::can_replace_statement(const std::shared_ptr<Statement>& stat bool to_replace = false; auto es = static_cast<ExpressionStatement*>(statement.get()); - auto e = es->expression; + auto e = es->get_expression(); if (e->is_wrapped_expression()) { auto wrapped_expression = static_cast<WrappedExpression*>(e.get()); - if (wrapped_expression->expression->is_function_call()) { + if (wrapped_expression->get_expression()->is_function_call()) { to_replace = true; } } @@ -68,12 +69,11 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, return; } - auto& statements = inlined_block->statements; - size_t counter = 0; + auto& statements = inlined_block->statements; for (const auto& argument : callee_parameters) { - auto name = argument->name->clone(); + auto name = argument->get_name()->clone(); auto old_name = name->get_node_name(); auto new_name = get_new_name(old_name, "in", inlined_variables); name->set_name(new_name); @@ -167,7 +167,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, replaced_statements[caller_statement] = statement; } else { inlined_statements[caller_statement].push_back( - std::shared_ptr<ast::ExpressionStatement>(statement)); + std::shared_ptr<ast::ExpressionStatement>(statement)); } /// variable name which will replace the function call that we just inlined @@ -180,7 +180,7 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { /// argument can be function call itself node->visit_children(this); - std::string function_name = node->name->get_node_name(); + std::string function_name = node->get_name()->get_node_name(); auto symbol = program_symtab->lookup_in_scope(function_name); /// nothing to do if called function is not defined or it's external @@ -283,11 +283,12 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { */ void InlineVisitor::visit_wrapped_expression(WrappedExpression* node) { node->visit_children(this); - if (node->expression->is_function_call()) { - auto expression = static_cast<FunctionCall*>(node->expression.get()); + auto e = node->get_expression(); + if (e->is_function_call()) { + auto expression = static_cast<FunctionCall*>(e.get()); if (replaced_fun_calls.find(expression) != replaced_fun_calls.end()) { auto var = replaced_fun_calls[expression]; - node->expression = std::make_shared<Name>(new String(var)); + node->set_expression(std::make_shared<Name>(new String(var))); } } } diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 40d8c7204f..9d0e09d836 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -146,7 +146,9 @@ class InlineVisitor : public AstVisitor { bool can_replace_statement(const std::shared_ptr<ast::Statement>& statement); /// inline function/procedure into caller block - bool inline_function_call(ast::Block* callee, ast::FunctionCall* node, ast::StatementBlock* caller); + bool inline_function_call(ast::Block* callee, + ast::FunctionCall* node, + ast::StatementBlock* caller); /// add assignement statements into given statement block to inline arguments void inline_arguments(ast::StatementBlock* inlined_block, diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index c67e5b358d..e5fe89d21c 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -8,7 +8,7 @@ using namespace symtab; /// rename name conflicting variables in the statement block and it's all children void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { /// nothing to do - if (node->statements.empty()) { + if (node->get_statements().empty()) { return; } @@ -23,7 +23,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { symtab_stack.push(symtab); // first need to process all children : perform recursively from innermost block - for (auto& item : node->statements) { + for (const auto& item : node->get_statements()) { item->visit_children(this); } @@ -46,7 +46,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { RenameVisitor rename_visitor; - for (auto& var : *variables) { + for (const auto& var : *variables) { std::string name = var->get_node_name(); auto s = parent_symtab->lookup_in_scope(name); /// if symbol is a variable name (avoid renaming use of units like mV) diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index aa67d55e0a..31a1ac5c39 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -100,11 +100,11 @@ void LocalizeVisitor::visit_program(Program* node) { auto variables = variables_to_optimize(); for (const auto& varname : variables) { - auto blocks = node->blocks; + const auto& blocks = node->get_blocks(); std::map<DUState, std::vector<std::shared_ptr<ast::Node>>> block_usage; /// compute def use chains - for (auto& block : blocks) { + for (auto block : blocks) { if (node_for_def_use_analysis(block.get())) { DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); auto usages = v.analyze(block.get(), varname); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 1f05faf1b9..3e050b96db 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -14,7 +14,8 @@ void PerfVisitor::visit_binary_expression(BinaryExpression* node) { bool assign_op = false; if (start_measurement) { - switch (node->op.value) { + auto value = node->get_op().get_value(); + switch (value) { case BOP_ADDITION: current_block_perf.n_add++; break; @@ -83,12 +84,12 @@ void PerfVisitor::visit_binary_expression(BinaryExpression* node) { visiting_lhs_expression = true; } - node->lhs->accept(this); + node->get_lhs()->accept(this); /// lhs is done (rhs is read only) visiting_lhs_expression = false; - node->rhs->accept(this); + node->get_rhs()->accept(this); } /// add performance stats to json printer @@ -154,7 +155,7 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { under_function_call = true; if (start_measurement) { - auto name = node->name->get_node_name(); + auto name = node->get_node_name(); if (name == "exp") { current_block_perf.n_exp++; } else if (name == "log") { @@ -363,7 +364,8 @@ void PerfVisitor::visit_solve_block(SolveBlock* node) { void PerfVisitor::visit_unary_expression(UnaryExpression* node) { if (start_measurement) { - switch (node->op.value) { + auto value = node->get_op().get_value(); + switch (value) { case UOP_NEGATION: current_block_perf.n_neg++; break; diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 4b68d47872..80a9c80288 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -7,7 +7,8 @@ using namespace ast; void RenameVisitor::visit_name(Name* node) { std::string name = node->get_node_name(); if (name == var_name) { - node->value->set(new_var_name); + auto value = node->get_value(); + value->set(new_var_name); } } diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.cpp index 23a7d65f41..406183a3cc 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.cpp @@ -70,12 +70,14 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { if (node->is_param_assign()) { auto parameter = dynamic_cast<ParamAssign*>(node); - if (parameter->value) { - symbol->set_value(parameter->get_value()->to_double()); + auto value = parameter->get_value(); + auto name = parameter->get_name(); + if (value) { + symbol->set_value(value->to_double()); } - if (parameter->name->is_indexed_name()) { - auto name = dynamic_cast<IndexedName*>(parameter->name.get()); - auto length = dynamic_cast<Integer*>(name->get_length().get()); + if (name->is_indexed_name()) { + auto index_name = dynamic_cast<IndexedName*>(name.get()); + auto length = dynamic_cast<Integer*>(index_name->get_length().get()); symbol->set_as_array(length->eval()); } } @@ -90,16 +92,18 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { if (node->is_constant_var()) { auto constant = dynamic_cast<ConstantVar*>(node); - if (constant->value) { - symbol->set_value(constant->get_value()->to_double()); + auto value = constant->get_value(); + if (value) { + symbol->set_value(value->to_double()); } } if (node->is_local_var()) { - auto local_var = dynamic_cast<LocalVar*>(node); - if (local_var->name->is_indexed_name()) { - auto name = dynamic_cast<IndexedName*>(local_var->name.get()); - auto length = dynamic_cast<Integer*>(name->get_length().get()); + auto variable = dynamic_cast<LocalVar*>(node); + auto name = variable->get_name(); + if (name->is_indexed_name()) { + auto index_name = dynamic_cast<IndexedName*>(name.get()); + auto length = dynamic_cast<Integer*>(index_name->get_length().get()); symbol->set_as_array(length->eval()); } } @@ -204,7 +208,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, const std::s * @todo : we assume table statement follows variable declaration */ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { - auto update_symbol = [this](NameVector& variables, NmodlType property, int num_values) { + auto update_symbol = [this](const NameVector& variables, NmodlType property, int num_values) { for (auto& var : variables) { auto name = var->get_node_name(); auto symbol = modsymtab->lookup(name); @@ -214,7 +218,7 @@ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { } } }; - int num_values = node->with->eval() + 1; - update_symbol(node->table_vars, NmodlType::table_statement_var, num_values); - update_symbol(node->depend_vars, NmodlType::table_dependent_var, num_values); + int num_values = node->get_with()->eval() + 1; + update_symbol(node->get_table_vars(), NmodlType::table_statement_var, num_values); + update_symbol(node->get_depend_vars(), NmodlType::table_dependent_var, num_values); } diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 55479afd25..7af8eaf27b 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -5,7 +5,7 @@ using namespace ast; using namespace symtab; void VerbatimVarRenameVisitor::visit_statement_block(StatementBlock* node) { - if (node->statements.empty()) { + if (node->get_statements().empty()) { return; } @@ -20,7 +20,7 @@ void VerbatimVarRenameVisitor::visit_statement_block(StatementBlock* node) { symtab_stack.push(symtab); // first need to process all children : perform recursively from innermost block - for (auto& item : node->statements) { + for (const auto& item : node->get_statements()) { item->accept(this); } @@ -70,7 +70,7 @@ void VerbatimVarRenameVisitor::visit_verbatim(Verbatim* node) { auto tokens = driver.all_tokens(); std::string result; - for (auto& token : tokens) { + for (const auto& token : tokens) { result += rename_variable(token); } statement->set(result); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index e2315a68d9..0a65957cb1 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -70,6 +70,7 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { driver.parse_string(nmodl_text); auto ast = driver.ast(); auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); - auto statement = std::shared_ptr<Statement>(procedure->get_statement_block()->get_statements()[0]->clone()); + auto statement = + std::shared_ptr<Statement>(procedure->get_statement_block()->get_statements()[0]->clone()); return statement; } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 4086fe08ce..69e387454d 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -596,7 +596,9 @@ SCENARIO("Presence of local variable in verbatim block") { LOCAL gNaTs2_tbar, x VERBATIM _lx = _lgNaTs2_tbar + #define my_macro_var _lgNaTs2_tbar*2 ENDVERBATIM + gNaTs2_tbar = my_macro_var + 1 } PROCEDURE alpha() { @@ -615,7 +617,9 @@ SCENARIO("Presence of local variable in verbatim block") { LOCAL gNaTs2_tbar_r_0, x VERBATIM x = gNaTs2_tbar_r_0 + #define my_macro_var gNaTs2_tbar_r_0*2 ENDVERBATIM + gNaTs2_tbar_r_0 = my_macro_var+1 } PROCEDURE alpha() { @@ -1254,7 +1258,7 @@ std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::stri std::vector<DUChain> chains; DefUseAnalyzeVisitor v(ast->get_symbol_table()); - for (auto& block : ast->blocks) { + for (const auto& block : ast->get_blocks()) { if (block->get_node_type() != ast::AstNodeType::NEURON_BLOCK) { chains.push_back(v.analyze(block.get(), variable)); } From f812402348e26dbd8a37715a154e8afa684512be Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Fri, 4 Jan 2019 13:27:45 +0100 Subject: [PATCH 105/871] Update README with BB5 Change-Id: I8b39f26ad8c179552f2ead5321d324a53fe582d9 NMODL Repo SHA: BlueBrain/nmodl@66a5234a838fa56a0a36d06f0d7390c7eac0cba7 --- README.md | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1611fe3e75..f219177910 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,10 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - Python2/3 (>=2.7) - Python yaml (pyyaml) -Make sure to have latest version of flex (>=2.6) and bison (>=3.0). For example, on OS X we typically install packages via brew or macport as: +Make sure to have latest version of flex (>=2.6) and bison (>=3.0). + + +On OS X we typically install packages via brew or macport as: ``` brew install flex bison @@ -71,12 +74,16 @@ If flex / bison is installed in non-standard location then set `PATH` env variab cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" ``` - On Lugano BBP IV system we have to use newer versions installed in below path: +On BB5, you can do: - ``` - export PATH=/gpfs/bbp.cscs.ch/project/proj16/software/viz/hpc/bison-3.0.4-/bin:$PATH - export PATH=/gpfs/bbp.cscs.ch/project/proj16/software/viz/hpc/flex-2.6.4/bin:$PATH - ``` +``` +module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 + +mkdir build && cd build +cmake .. +make VERBOSE=1 -j +make test +``` If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: @@ -95,6 +102,12 @@ You can independently run lexer, parser or visitors as: ./bin/nmodl_visitor --file ../test/input/channel.mod ``` +To run code generator, you can do: + +``` +./bin/nmodl ../test/input/channel.mod +``` + #### Running Test From 16362b6e5c0117b30d6a5c8f2e5bcaed79509cec Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 6 Jan 2019 23:58:35 +0100 Subject: [PATCH 106/871] Change __cmp__ with __lt__ for Python 3 Change-Id: Ic4a65a5d7530e5fcb8cc60ba73266f1beb780d84 NMODL Repo SHA: BlueBrain/nmodl@47e9dac7030bf403d529db052f81f146c0b16a23 --- src/nmodl/language/nodes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 49b061b0a5..42e2808e2e 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -22,8 +22,8 @@ def __init__(self, args): self.force_suffix = args.force_suffix self.is_abstract = False - def __cmp__(self, other): - return cmp(self.class_name, other.class_name) + def __lt__(self, other): + return (self.class_name < other.class_name) def get_data_type_name(self): """ return type name for the node """ From 371677fad58d3b10eba89f10a13ca899b757de89 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 8 Jan 2019 08:56:11 +0100 Subject: [PATCH 107/871] Update README Change-Id: I1759d0da37f4af03f2ea8fcff36153d12ccae0e7 NMODL Repo SHA: BlueBrain/nmodl@3bfeb4eb0c286bd4cc165d5db1933d7418e72cc6 --- README.md | 81 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index f219177910..78d0eb367c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ ## NMODL -This is prototype implementation of code generation framework for NMODL. +This is a source-to-source code generation framework for NMODL. + #### Cloning Source @@ -17,20 +18,26 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - Python2/3 (>=2.7) - Python yaml (pyyaml) -Make sure to have latest version of flex (>=2.6) and bison (>=3.0). +#### Getting Dependencies + +Many systems have older version of Flex and Bison. Make sure to have latest version of Flex (>=2.6) and Bison (>=3.0). -On OS X we typically install packages via brew or macport as: +On OS X we typically install packages via brew/macport and pip : ``` brew install flex bison +pip install pyyaml + +# Or +pip3 install pyyaml ``` -This will install flex and bison in: +Make sure to have latest flex/bison in $PATH : + ``` -/usr/local/opt/flex -/usr/local/opt/bison +export PATH=/usr/local/opt/flex:/usr/local/opt/bison:$PATH ``` On Ubuntu (>=16.04) you should already have recent version of flex/bison: @@ -43,35 +50,32 @@ $ bison --version bison (GNU Bison) 3.0.4 ``` -Python yaml can be installed on Ubuntu using: - -``` -sudo apt-get install python-yaml -``` - -On OS X: +Python yaml can be installed as : ``` -pip install pyyaml -pip3 install pyyaml - +apt-get install python-yaml ``` #### Build -Build/Compile NMODL as: + +Once all dependencies are in place, you can build cmake project as : ``` -mkdir build -cd build +mkdir nocmodl/build +cd nocmodl/build cmake .. -make -j +make ``` -If flex / bison is installed in non-standard location then set `PATH` env variable to have latest flex/bison in `PATH` or use `CMAKE_PREFIX_PATH`: +If flex / bison is installed in non-standard location then you can do : ``` - cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" +cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" + + # Or, + +cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison ``` On BB5, you can do: @@ -81,18 +85,29 @@ module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 mkdir build && cd build cmake .. -make VERBOSE=1 -j +make VERBOSE=1 make test ``` -If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: +#### Running CoreNEURON + +You can use NMODL code generator instead of MOD2 for CoreNEURON. You have to simply use extra CMake argument `-DMOD2C` pointing to `nmodl` binary: ``` -cmake .. -DENABLE_CLANG_TIDY=ON +git clone --recursive https://github.com/BlueBrain/CoreNeuron.git coreneuron +mkdir coreneuron/build && cd coreneuron/build +cmake .. -DMOD2C=/path/nocmodl/build/bin/nmodl +make +make test ``` +#### Using NMODL -#### Running NMODL +To run code generator, you can do: + +``` +./bin/nmodl ../test/input/channel.mod +``` You can independently run lexer, parser or visitors as: @@ -102,12 +117,6 @@ You can independently run lexer, parser or visitors as: ./bin/nmodl_visitor --file ../test/input/channel.mod ``` -To run code generator, you can do: - -``` -./bin/nmodl ../test/input/channel.mod -``` - #### Running Test @@ -128,7 +137,7 @@ To run code generator, you can do: ``` -#### Memory Leaks +#### Memory Leaks and Clang Tidy Test memory leaks using : @@ -141,3 +150,9 @@ Or using CTest as: ``` ctest -T memcheck ``` + +If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: + +``` +cmake .. -DENABLE_CLANG_TIDY=ON +``` \ No newline at end of file From c8385f25a5a7481e959fffea4f422bddfa30d928 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Wed, 9 Jan 2019 14:01:27 +0100 Subject: [PATCH 108/871] Cherry-pick from sandbox/matwolf/jinja (e5c45d0) Replace visitor code generation by templates. Differences currently amount to whitespace in the nmodl visitor, due to simplified logic that introduces some newlines in the template resolution. Note: keeping src/language/nmodl_printer.py and src/language/visitors_printer.py to have both old and new implementations intact for now. Change-Id: I11d8899756a2309fe48a8acd6a8ffb84200ca6dd Merged jinja template generator with updates in AST classes A bunch of small fixes and updates to conform the PoC code to the latest changes elsewhere in the code and get the templates to produce correct output: * Fixed a bit of namespacing * Fixed access to use getters/setters now * Took into account method and property name changes * Removed templating where it was dropped Took Pramod's review comments into account. Change-Id: I083bfa20ca4561aa0e65cafa025b6f0259300ea0 NMODL Repo SHA: BlueBrain/nmodl@c574c86cc78a0cf8fe1e01d581116debf8735bad --- README.md | 15 ++- cmake/nmodl/CMakeLists.txt | 3 +- src/nmodl/language/code_generator.py | 59 +++------- src/nmodl/language/templates/ast_visitor.cpp | 8 ++ src/nmodl/language/templates/ast_visitor.hpp | 15 +++ src/nmodl/language/templates/json_visitor.cpp | 31 +++++ src/nmodl/language/templates/json_visitor.hpp | 24 ++++ .../language/templates/nmodl_visitor.cpp | 107 ++++++++++++++++++ .../language/templates/nmodl_visitor.hpp | 22 ++++ .../language/templates/symtab_visitor.cpp | 25 ++++ .../language/templates/symtab_visitor.hpp | 46 ++++++++ src/nmodl/language/templates/visitor.hpp | 11 ++ src/nmodl/visitors/CMakeLists.txt | 2 +- ...r_helper.cpp => symtab_visitor_helper.hpp} | 1 - 14 files changed, 319 insertions(+), 50 deletions(-) create mode 100644 src/nmodl/language/templates/ast_visitor.cpp create mode 100644 src/nmodl/language/templates/ast_visitor.hpp create mode 100644 src/nmodl/language/templates/json_visitor.cpp create mode 100644 src/nmodl/language/templates/json_visitor.hpp create mode 100644 src/nmodl/language/templates/nmodl_visitor.cpp create mode 100644 src/nmodl/language/templates/nmodl_visitor.hpp create mode 100644 src/nmodl/language/templates/symtab_visitor.cpp create mode 100644 src/nmodl/language/templates/symtab_visitor.hpp create mode 100644 src/nmodl/language/templates/visitor.hpp rename src/nmodl/visitors/{symtab_visitor_helper.cpp => symtab_visitor_helper.hpp} (99%) diff --git a/README.md b/README.md index 78d0eb367c..7491064600 100644 --- a/README.md +++ b/README.md @@ -15,15 +15,16 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - bison (>=3.0) - CMake (>=3.1) - C++ compiler (with c++11 support) -- Python2/3 (>=2.7) +- Python3 (>=3.6) - Python yaml (pyyaml) +- Jinja2 (>=2.10) #### Getting Dependencies Many systems have older version of Flex and Bison. Make sure to have latest version of Flex (>=2.6) and Bison (>=3.0). -On OS X we typically install packages via brew/macport and pip : +On macos X we typically install packages via brew/macport and pip : ``` brew install flex bison @@ -50,6 +51,14 @@ $ bison --version bison (GNU Bison) 3.0.4 ``` +NMODL depends on Python 3, so make sure you have an up-to-date Python installation. On macos X Python 3 can be installed +through e.g. homebrew. On Ubuntu, depending on your version, Python 3 is either already available by default or can be easily +obtained through + +``` +$ apt-get install python3 +``` + Python yaml can be installed as : ``` @@ -155,4 +164,4 @@ If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake > ``` cmake .. -DENABLE_CLANG_TIDY=ON -``` \ No newline at end of file +``` diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 0f40501727..43142ed231 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -33,8 +33,9 @@ include(FindPythonModule) # Find required python packages #============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp 2.7 REQUIRED) +find_package(PythonInterp 3.6 REQUIRED) find_python_module(yaml REQUIRED) +find_python_module(jinja2 REQUIRED) include_directories( ${PROJECT_SOURCE_DIR} diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 244ab6d2b2..e03146a343 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,7 +1,9 @@ +import jinja2 +from pathlib import Path + from parser import LanguageParser from ast_printer import * -from visitors_printer import * -from nmodl_printer import * +import node_info # parse nmodl definition file and get list of abstract nodes @@ -24,47 +26,16 @@ "", nodes).write() -AbstractVisitorPrinter( - "../visitors/visitor.hpp", - "Visitor", - nodes).write() - -AstVisitorDeclarationPrinter( - "../visitors/ast_visitor.hpp", - "AstVisitor", - nodes).write() - -AstVisitorDefinitionPrinter( - "../visitors/ast_visitor.cpp", - "AstVisitor", - nodes).write() - -JSONVisitorDeclarationPrinter( - "../visitors/json_visitor.hpp", - "JSONVisitor", - nodes).write() - -JSONVisitorDefinitionPrinter( - "../visitors/json_visitor.cpp", - "JSONVisitor", - nodes).write() - -SymtabVisitorDeclarationPrinter( - "../visitors/symtab_visitor.hpp", - "SymtabVisitor", - nodes).write() - -SymtabVisitorDefinitionPrinter( - "../visitors/symtab_visitor.cpp", - "SymtabVisitor", - nodes).write() +templates = Path(__file__).parent / 'templates' +basedir = Path(__file__).resolve().parent.parent -NmodlVisitorDeclarationPrinter( - "../visitors/nmodl_visitor.hpp", - "NmodlPrintVisitor", - nodes).write() +env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates)), + trim_blocks=True, + lstrip_blocks=True) +env.filters['snake_case'] = to_snake_case -NmodlVisitorDefinitionPrinter( - "../visitors/nmodl_visitor.cpp", - "NmodlPrintVisitor", - nodes).write() +for fn in templates.glob('*.[ch]pp'): + filename = basedir / ('visitors' if 'visitor' in fn.name else 'ast') / fn.name + template = env.get_template(fn.name) + with filename.open('w') as fd: + fd.write(template.render(nodes=nodes, node_info=node_info)) diff --git a/src/nmodl/language/templates/ast_visitor.cpp b/src/nmodl/language/templates/ast_visitor.cpp new file mode 100644 index 0000000000..c01ab0841c --- /dev/null +++ b/src/nmodl/language/templates/ast_visitor.cpp @@ -0,0 +1,8 @@ +#include "visitors/ast_visitor.hpp" + +{% for node in nodes %} +void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { + node->visit_children(this); +} + +{% endfor %} diff --git a/src/nmodl/language/templates/ast_visitor.hpp b/src/nmodl/language/templates/ast_visitor.hpp new file mode 100644 index 0000000000..ca3f0689c0 --- /dev/null +++ b/src/nmodl/language/templates/ast_visitor.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include "ast/ast.hpp" +#include "visitors/visitor.hpp" +using namespace ast; + + +/* Basic visitor implementation */ +class AstVisitor : public Visitor { + + public: + {% for node in nodes %} + virtual void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; + {% endfor %} +}; diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/json_visitor.cpp new file mode 100644 index 0000000000..c0bafa9fd8 --- /dev/null +++ b/src/nmodl/language/templates/json_visitor.cpp @@ -0,0 +1,31 @@ +#include "visitors/json_visitor.hpp" + +{% for node in nodes %} +void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { + {% if node.has_children() %} + printer->push_block(node->get_node_type_name()); + node->visit_children(this); + {% if node.is_data_type_node() %} + {% if node.is_integer_node() %} + if(!node->get_macro_name()) { + std::stringstream ss; + ss << node->eval(); + printer->add_node(ss.str()); + } + {% else %} + std::stringstream ss; + ss << node->eval(); + printer->add_node(ss.str()); + {% endif %} + {% endif %} + printer->pop_block(); + {% if node.is_program_node() %} + flush(); + {% endif %} + {% else %} + (void)node; + printer->add_node("{{ node.class_name }}"); + {% endif %} +} + +{% endfor %} diff --git a/src/nmodl/language/templates/json_visitor.hpp b/src/nmodl/language/templates/json_visitor.hpp new file mode 100644 index 0000000000..d03a7ba389 --- /dev/null +++ b/src/nmodl/language/templates/json_visitor.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "visitors/ast_visitor.hpp" +#include "printer/json_printer.hpp" + + +/* Concrete visitor for printing AST in JSON format */ +class JSONVisitor : public AstVisitor { + + private: + std::unique_ptr<JSONPrinter> printer; + + public: + JSONVisitor() : printer(new JSONPrinter()) {} + JSONVisitor(std::string filename) : printer(new JSONPrinter(filename)) {} + JSONVisitor(std::stringstream &ss) : printer(new JSONPrinter(ss)) {} + + void flush() { printer->flush(); } + void compact_json(bool flag) { printer->compact_json(flag); } + + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; + {% endfor %} +}; diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp new file mode 100644 index 0000000000..998ead5577 --- /dev/null +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -0,0 +1,107 @@ +#include "visitors/nmodl_visitor.hpp" +#include "visitors/nmodl_visitor_helper.hpp" + +using namespace ast; + +{% macro add_element(name) -%} +{% if name %} + printer->add_element("{{ name }}"); +{% endif -%} +{%- endmacro -%} + + +{%- macro guard(pre, post) -%} +{{ add_element(pre)|trim }} +{{ caller() -}} +{{ add_element(post)|trim }} +{% endmacro -%} + + +{%- macro is_program(n) -%} +{{ "true" if n.is_program_node() else "false" }} +{%- endmacro %} + + +{%- macro is_statement(n, c) -%} +{{ "true" if c.is_statement_node() or n.is_unit_block() else "false" }} +{%- endmacro -%} + + +{%- macro add_vector_child(node, child) -%} +{% call guard(child.prefix, child.suffix) %} +visit_element(node->get_{{ child.varname }}(),"{{ child.separator }}",{{ is_program(node) }},{{ is_statement(node, child) }}); +{% endcall %} +{%- endmacro -%} + + +{%- macro add_child(node, child) -%} + {% if child.nmodl_name %} +if(node->get_{{ child.varname }}()->eval()) { + printer->add_element("{{ child.nmodl_name }}"); +} + {% elif child.is_vector %} + {%- if child.prefix or child.suffix %} +if (!node->get_{{ child.varname }}().empty()) { + {{ add_vector_child(node, child)|trim|indent }} +} + {%- else %} +{{- add_vector_child(node, child) }} + {%- endif %} + {% elif node.is_prime_node() and child.varname == node_info.ORDER_VAR_NAME %} +auto order = node->get_{{ child.varname }}()->eval(); +auto symbol = std::string(order, '\''); +printer->add_element(symbol); + {% elif node.class_name == node_info.BINARY_EXPRESSION_NODE and child.varname == node_info.BINARY_OPERATOR_NAME %} +std::string op = node->op.eval(); +if(op == "=" || op == "&&" || op == "||" || op == "==") + op = " " + op + " "; +printer->add_element(op); + {% else %} + {% call guard(child.prefix, child.suffix) %} +node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node() else "." }}accept(this); + {% endcall %} + {%- endif %} +{%- endmacro -%} + + +{%- for node in nodes %} +void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}* node) { +{{ add_element(node.nmodl_name) -}} + {% filter indent -%} + {% call guard(node.prefix, node.suffix) -%} + {% if node.is_block_node() %} +printer->push_level(); + {% endif %} + {% if node.is_data_type_node() %} + {% if node.is_integer_node() %} +if(node->get_macro_name() == nullptr) { + printer->add_element(std::to_string(node->eval())); +} + {% else %} +std::stringstream ss; +ss << node->eval(); +printer->add_element(ss.str()); + {% endif %} + {% endif %} + {% for child in node.children %} + {% call guard(child.force_prefix, child.force_suffix) -%} + {% if child.is_base_type_node() %} + {% else %} + {% if child.optional or child.is_statement_block_node() %} +if(node->get_{{ child.varname }}()) { + {{ add_child(node, child)|trim|indent() }} +} + {% else %} +{{ add_child(node, child)|trim }} + {% endif %} + {% endif %} + {% endcall -%} + {% endfor %} + {% endcall -%} + {% if node.is_block_node() -%} +printer->pop_level(); + {% endif -%} + {% endfilter -%} +} + +{% endfor %} diff --git a/src/nmodl/language/templates/nmodl_visitor.hpp b/src/nmodl/language/templates/nmodl_visitor.hpp new file mode 100644 index 0000000000..a34595a6f5 --- /dev/null +++ b/src/nmodl/language/templates/nmodl_visitor.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "ast/ast.hpp" +#include "printer/nmodl_printer.hpp" + + +/* Visitor for printing AST back to NMODL */ +class NmodlPrintVisitor : public Visitor { + + private: + std::unique_ptr<NMODLPrinter> printer; + public: + NmodlPrintVisitor() : printer(new NMODLPrinter()) {} + NmodlPrintVisitor(std::string filename) : printer(new NMODLPrinter(filename)) {} + NmodlPrintVisitor(std::stringstream& stream) : printer(new NMODLPrinter(stream)) {} + + {% for node in nodes %} + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} + template<typename T> + void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement); +}; diff --git a/src/nmodl/language/templates/symtab_visitor.cpp b/src/nmodl/language/templates/symtab_visitor.cpp new file mode 100644 index 0000000000..ec135c754d --- /dev/null +++ b/src/nmodl/language/templates/symtab_visitor.cpp @@ -0,0 +1,25 @@ +#include "symtab/symbol_table.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/symtab_visitor_helper.hpp" + +{% for node in nodes %} +{% if node.is_symtab_method_required() and not node.is_symbol_helper_node() %} +{% set typename = node.class_name|snake_case %} +{% set propname = "syminfo::NmodlType::" + typename %} +void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { + {% if node.is_symbol_var_node() %} + setup_symbol(node, {{ propname }}); + {% elif node.is_program_node() %} + setup_symbol_table_for_program_block(node); + {% elif node.is_global_block_node() %} + setup_symbol_table_for_global_block(node); + {% elif node.is_symbol_block_node() %} + add_model_symbol_with_property(node, {{ propname }}); + setup_symbol_table_for_scoped_block(node, node->get_node_name()); + {% else %} + setup_symbol_table_for_scoped_block(node, node->get_node_type_name()); + {% endif %} +} + +{% endif %} +{% endfor %} diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp new file mode 100644 index 0000000000..1ae422bf64 --- /dev/null +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "visitors/json_visitor.hpp" +#include "visitors/ast_visitor.hpp" +#include "symtab/symbol_table.hpp" + + +/* Concrete visitor for constructing symbol table from AST */ +class SymtabVisitor : public AstVisitor { + +private: + symtab::ModelSymbolTable* modsymtab; + + std::unique_ptr<JSONPrinter> printer; + std::string block_to_solve; + bool update = false; + bool under_state_block = false; + +public: + explicit SymtabVisitor(bool update = false) : printer(new JSONPrinter()), update(update) {} + SymtabVisitor(std::stringstream &ss, bool update = false) : printer(new JSONPrinter(ss)), update(update) {} + SymtabVisitor(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {} + + void add_model_symbol_with_property(Node* node, NmodlTypeFlag property); + + void setup_symbol(Node* node, NmodlTypeFlag property); + + void setup_symbol_table(AST* node, const std::string& name, bool is_global); + + void setup_symbol_table(Node* node, const std::string& name, NmodlTypeFlag property, bool is_global); + + void setup_program_symbol_table(Node* node, const std::string& name, bool is_global); + + void setup_symbol_table_for_program_block(Program* node); + + void setup_symbol_table_for_global_block(Node* node); + + void setup_symbol_table_for_scoped_block(Node* node, const std::string& name); + + {% for node in nodes %} + {% if node.is_symtab_method_required() %} + void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; + {% endif %} + {% endfor %} +}; + diff --git a/src/nmodl/language/templates/visitor.hpp b/src/nmodl/language/templates/visitor.hpp new file mode 100644 index 0000000000..1410ba0a60 --- /dev/null +++ b/src/nmodl/language/templates/visitor.hpp @@ -0,0 +1,11 @@ +#pragma once + + +/* Abstract base class for all visitor implementations */ +class Visitor { + + public: + {% for node in nodes %} + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) = 0; + {% endfor %} +}; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 87e41e6ea4..41a01953ea 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -8,7 +8,7 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp diff --git a/src/nmodl/visitors/symtab_visitor_helper.cpp b/src/nmodl/visitors/symtab_visitor_helper.hpp similarity index 99% rename from src/nmodl/visitors/symtab_visitor_helper.cpp rename to src/nmodl/visitors/symtab_visitor_helper.hpp index 406183a3cc..ccc80a863e 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.cpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -1,7 +1,6 @@ #include <utility> #include "lexer/token_mapping.hpp" -#include "visitors/symtab_visitor.hpp" using namespace ast; using namespace symtab; From 04cc0ccc77e92f6dbd222858b02c37ebf0e9b3d7 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Mon, 14 Jan 2019 17:47:01 +0100 Subject: [PATCH 109/871] Added Jinja2 templated AST class generator - replaces the previous `writeln` style version with a jinja2 template for all AST class headers and implementations - helper classes in nodes.py were slightly extended to include a bit more logic and string templates to more cleanly and easily construct the templates - nmodl template was also cleaned up and clang-format was added into the build pipeline for all the template files - old printer modules are deleted - updated .clang-format which caused minor changes in unrelated files (extra empty lines were suppressed). - improved CMakeLists for clang-format Change-Id: I00a9c88fae535edec7612cf7d8c30c96690574fa NMODL Repo SHA: BlueBrain/nmodl@f4683ca389e91d89dcb45d8186d8ee56e7fadf67 --- .clang-format | 2 +- cmake/nmodl/CMakeLists.txt | 20 +- cmake/nmodl/FindClangFormat.cmake | 84 ++++++ .../codegen/base/codegen_base_visitor.cpp | 2 - .../codegen/c-cuda/codegen_c_cuda_visitor.cpp | 1 - .../c-openmp/codegen_c_omp_visitor.cpp | 1 - src/nmodl/codegen/c/codegen_c_visitor.cpp | 9 - src/nmodl/codegen/c/codegen_c_visitor.hpp | 1 - src/nmodl/language/CMakeLists.txt | 70 +++-- src/nmodl/language/ast_printer.py | 272 ------------------ src/nmodl/language/code_generator.py | 22 +- src/nmodl/language/nmodl_printer.py | 144 ---------- src/nmodl/language/nodes.py | 53 +++- src/nmodl/language/printer.py | 148 ---------- src/nmodl/language/templates/ast.cpp | 68 +++++ src/nmodl/language/templates/ast.hpp | 124 ++++++++ src/nmodl/language/templates/ast_decl.hpp | 25 ++ .../language/templates/nmodl_visitor.cpp | 102 ++++--- src/nmodl/language/visitors_printer.py | 265 ----------------- src/nmodl/lexer/CMakeLists.txt | 4 +- src/nmodl/parser/{verbatim.y => verbatim.yy} | 0 21 files changed, 464 insertions(+), 953 deletions(-) create mode 100644 cmake/nmodl/FindClangFormat.cmake delete mode 100644 src/nmodl/language/ast_printer.py delete mode 100644 src/nmodl/language/nmodl_printer.py delete mode 100644 src/nmodl/language/printer.py create mode 100644 src/nmodl/language/templates/ast.cpp create mode 100644 src/nmodl/language/templates/ast.hpp create mode 100644 src/nmodl/language/templates/ast_decl.hpp delete mode 100644 src/nmodl/language/visitors_printer.py rename src/nmodl/parser/{verbatim.y => verbatim.yy} (100%) diff --git a/.clang-format b/.clang-format index 66c88799e0..2bd8848b20 100644 --- a/.clang-format +++ b/.clang-format @@ -61,7 +61,7 @@ IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' -MaxEmptyLinesToKeep: 3 +MaxEmptyLinesToKeep: 2 NamespaceIndentation: All ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 43142ed231..3159b20d54 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -27,15 +27,23 @@ include(GitRevision) include(FlexHelper) include(CompilerHelper) include(ClangTidyHelper) +include(FindClangFormat) include(FindPythonModule) +#============================================================================= +# Find clang-format +#============================================================================= + +find_package(ClangFormat) + #============================================================================= # Find required python packages #============================================================================= message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) -find_python_module(yaml REQUIRED) find_python_module(jinja2 REQUIRED) +find_python_module(yaml REQUIRED) +find_python_module(textwrap REQUIRED) include_directories( ${PROJECT_SOURCE_DIR} @@ -77,7 +85,9 @@ add_subdirectory(test) #============================================================================= # Clang format target #============================================================================= -add_custom_target(clang-format - COMMAND - clang-format -i - --style=file ${FILES_FOR_CLANG_FORMAT}) +if(CLANG_FORMAT_FOUND) + add_custom_target(clang-format + COMMAND + ${CLANG_FORMAT_EXECUTABLE} -i + --style=file ${FILES_FOR_CLANG_FORMAT}) +endif() diff --git a/cmake/nmodl/FindClangFormat.cmake b/cmake/nmodl/FindClangFormat.cmake new file mode 100644 index 0000000000..6d4c93b8bc --- /dev/null +++ b/cmake/nmodl/FindClangFormat.cmake @@ -0,0 +1,84 @@ +# +#.rst: +# FindClangFormat +# --------------- +# +# The module defines the following variables +# +# ``CLANG_FORMAT_EXECUTABLE`` +# Path to clang-format executable +# ``CLANG_FORMAT_FOUND`` +# True if the clang-format executable was found. +# ``CLANG_FORMAT_VERSION`` +# The version of clang-format found +# ``CLANG_FORMAT_VERSION_MAJOR`` +# The clang-format major version if specified, 0 otherwise +# ``CLANG_FORMAT_VERSION_MINOR`` +# The clang-format minor version if specified, 0 otherwise +# ``CLANG_FORMAT_VERSION_PATCH`` +# The clang-format patch version if specified, 0 otherwise +# ``CLANG_FORMAT_VERSION_COUNT`` +# Number of version components reported by clang-format +# +# Example usage: +# +# .. code-block:: cmake +# +# find_package(ClangFormat) +# if(CLANG_FORMAT_FOUND) +# message("clang-format executable found: ${CLANG_FORMAT_EXECUTABLE}\n" +# "version: ${CLANG_FORMAT_VERSION}") +# endif() + +find_program(CLANG_FORMAT_EXECUTABLE + NAMES clang-format clang-format-7 + clang-format-6.0 clang-format-5.0 + clang-format-4.0 clang-format-3.9 + clang-format-3.8 clang-format-3.7 + clang-format-3.6 clang-format-3.5 + clang-format-3.4 clang-format-3.3 + DOC "clang-format executable" + ) +mark_as_advanced(CLANG_FORMAT_EXECUTABLE) + +# Extract version from command "clang-format -version" +if(CLANG_FORMAT_EXECUTABLE) + execute_process(COMMAND ${CLANG_FORMAT_EXECUTABLE} -version + OUTPUT_VARIABLE clang_format_version + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if (clang_format_version MATCHES "^clang-format version .*") + # clang_format_version sample: "clang-format version 3.9.1-4ubuntu3~16.04.1 (tags/RELEASE_391/rc2)" + string(REGEX REPLACE + "clang-format version ([.0-9]+).*" "\\1" + CLANG_FORMAT_VERSION "${clang_format_version}") + # CLANG_FORMAT_VERSION sample: "3.9.1" + + # Extract version components + string(REPLACE "." ";" clang_format_version "${CLANG_FORMAT_VERSION}") + list(LENGTH clang_format_version CLANG_FORMAT_VERSION_COUNT) + if(CLANG_FORMAT_VERSION_COUNT GREATER 0) + list(GET clang_format_version 0 CLANG_FORMAT_VERSION_MAJOR) + else() + set(CLANG_FORMAT_VERSION_MAJOR 0) + endif() + if(CLANG_FORMAT_VERSION_COUNT GREATER 1) + list(GET clang_format_version 1 CLANG_FORMAT_VERSION_MINOR) + else() + set(CLANG_FORMAT_VERSION_MINOR 0) + endif() + if(CLANG_FORMAT_VERSION_COUNT GREATER 2) + list(GET clang_format_version 2 CLANG_FORMAT_VERSION_PATCH) + else() + set(CLANG_FORMAT_VERSION_PATCH 0) + endif() + endif() + unset(clang_format_version) +endif() + +if(CLANG_FORMAT_EXECUTABLE) + set(CLANG_FORMAT_FOUND TRUE) +else() + set(CLANG_FORMAT_FOUND FALSE) +endif() \ No newline at end of file diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 6085da919b..ffbdb02e4a 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -568,7 +568,6 @@ void CodegenBaseVisitor::print_statement_block(ast::StatementBlock* node, } - /** * Once variables are populated, update index semantics to register with coreneuron */ @@ -770,7 +769,6 @@ std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { } - /** * Return all ion write variables that require shadow vectors during code generation * diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp index a5267a2a0a..b95ae0af59 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp @@ -111,7 +111,6 @@ void CodegenCCudaVisitor::print_channel_iteration_block_end() { } - void CodegenCCudaVisitor::print_nrn_cur_matrix_shadow_reduction() { // do nothing } diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp index 0f7a074cdc..c18e9f5302 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp +++ b/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp @@ -11,7 +11,6 @@ using SymbolType = std::shared_ptr<symtab::Symbol>; /****************************************************************************************/ - void CodegenCOmpVisitor::print_channel_iteration_task_begin(BlockType type) { std::string vars; if (type == BlockType::Equation) { diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index 37a8018b11..e8cedbbb51 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -18,13 +18,11 @@ using namespace fmt::literals; using SymbolType = std::shared_ptr<symtab::Symbol>; - /****************************************************************************************/ /* Overloaded visitor routines */ /****************************************************************************************/ - void CodegenCVisitor::visit_function_call(FunctionCall* node) { if (!codegen) { return; @@ -55,7 +53,6 @@ void CodegenCVisitor::visit_verbatim(Verbatim* node) { /****************************************************************************************/ - /** * Return "current" for variable name used in breakpoint block * @@ -351,7 +348,6 @@ void CodegenCVisitor::print_channel_iteration_block_parallel_hint() { } - /// if reduction block in nrn_cur required bool CodegenCVisitor::nrn_cur_reduction_loop_required() { return channel_task_dependency_enabled() || info.point_process; @@ -557,7 +553,6 @@ std::string CodegenCVisitor::k_const() { /****************************************************************************************/ - void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, bool open_brace, bool close_brace) { @@ -935,7 +930,6 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { } - /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ @@ -1103,7 +1097,6 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, } - /** * If mechanisms dependency level execution is enabled then certain updates * like ionic current contributions needs to be atomically updated. In this @@ -1968,7 +1961,6 @@ void CodegenCVisitor::print_ion_variables_structure() { } - void CodegenCVisitor::print_global_variable_setup() { std::vector<std::string> allocated_variables; @@ -2813,7 +2805,6 @@ void CodegenCVisitor::print_derivative_kernel_for_euler() { } - /** * Todo: data is not derived. Need to add instance into instance struct? * data used here is wrong in AoS because as in original implementation, diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index f500286daa..1adacd8d62 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -599,7 +599,6 @@ class CodegenCVisitor : public CodegenBaseVisitor { }; - /** * Print prototype declaration for function and procedures * diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 24903bb453..f7858f4b11 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -1,34 +1,50 @@ #============================================================================= # Command to generate AST/Visitor classes from language definition #============================================================================= -add_custom_command ( - COMMAND ${PYTHON_EXECUTABLE} - ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.cpp - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/argument.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nodes.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/parser.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/node_info.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/printer.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/ast_printer.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/visitors_printer.py - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl_printer.py - COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" -) +set ( AUTOGEN_FILES ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.cpp + ) +file ( GLOB TEMPLATE_FILES + "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" + "${PROJECT_SOURCE_DIR}/src/language/templates/*.hpp" + ) + +file ( GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py" ) + + +if(CLANG_FORMAT_FOUND) + add_custom_command ( + OUTPUT ${AUTOGEN_FILES} + COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + COMMAND ${CLANG_FORMAT_EXECUTABLE} ARGS -i --style=file ${AUTOGEN_FILES} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" + ) +else() + add_custom_command ( + OUTPUT ${AUTOGEN_FILES} + COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" + ) +endif() #============================================================================= # Target to propogate dependencies properly to lexer #============================================================================= diff --git a/src/nmodl/language/ast_printer.py b/src/nmodl/language/ast_printer.py deleted file mode 100644 index c6ab6ac199..0000000000 --- a/src/nmodl/language/ast_printer.py +++ /dev/null @@ -1,272 +0,0 @@ -from printer import * -from utils import * - -class AstForwardDeclarationPrinter(DeclarationPrinter): - """Prints all AST nodes forward declarations""" - - def headers(self): - self.write_line("#include <memory>") - self.write_line("#include <string>") - self.write_line("#include <vector>", newline=2) - - def class_name_declaration(self): - self.write_line("namespace ast {", newline=2, post_gutter=1) - self.write_line("/* forward declarations of AST classes */") - for node in sorted(self.nodes): - self.write_line("class {};".format(node.class_name)) - - self.write_line() - self.write_line("/* Type for every ast node */") - self.write_line("enum class AstNodeType {", post_gutter=1) - - for node in sorted(self.nodes): - self.write_line("{},".format(to_snake_case(node.class_name).upper())) - - self.write_line("};", pre_gutter=-1, newline=2) - - self.write_line("/* std::vector for convenience */") - for node in sorted(self.nodes): - typename = "std::vector<std::shared_ptr<{}>>".format(node.class_name) - self.write_line("using {}Vector = {};".format(node.class_name, typename)) - - def declaration_end(self): - self.write_line(pre_gutter=-1) - - def private_declaration(self): - pass - - def public_declaration(self): - pass - - def post_declaration(self): - self.write_line("} // namespace ast", pre_gutter=-1) - - -class AstDeclarationPrinter(DeclarationPrinter): - """Prints all AST nodes class declarations""" - - def headers(self): - self.write_line("#include <iostream>") - self.write_line("#include <memory>") - self.write_line("#include <string>") - self.write_line("#include <vector>", newline=2) - - self.write_line('#include "ast/ast_decl.hpp"') - self.write_line('#include "ast/ast_common.hpp"') - self.write_line('#include "lexer/modtoken.hpp"') - self.write_line('#include "utils/common_utils.hpp"') - self.write_line('#include "visitors/visitor.hpp"', newline=2) - - def ast_classes_declaration(self): - - # TODO: for demo, removing error messages in get_token method. - # need to look in detail whether we should abort in this case - # when ModToken is NULL. This was introduced when added SymtabVisitor - # pass to get Token information. - - # todo : need a way to setup location - # self.write_line("void setLocation(nmodl::location loc) {}") - - self.write_line("/* Define all AST nodes */", newline=2) - - for node in self.nodes: - - self.write_line("class {} : public {} {{".format(node.class_name, node.base_class), post_gutter=1) - - private_members = node.private_members() - public_members = node.public_members() - - if private_members: - self.write_line("private:", post_gutter=1) - for member in private_members: - self.write_line("{} {};".format(member[0], member[1])) - self.write_line(post_gutter=-1) - - self.write_line("public:", post_gutter=1) - for member in public_members: - self.write_line("{} {};".format(member[0], member[1])) - - if node.children: - members = [ child.get_typename() + " " + child.varname for child in node.children] - self.write_line() - self.write_line("{}({});".format(node.class_name, (", ".join(members)))) - self.write_line("{0}(const {0}& obj);".format(node.class_name)) - - # program node holds statements and blocks and we instantiate it in driver - # also other nodes which we use as value type, parsor needs to return them - # as a value. And instantiation of those causes error if default constructor not there. - if node.is_program_node() or node.is_ptr_excluded_node(): - self.write_line( node.class_name + "() = default;", newline=2) - - # depending on if node is abstract or not, we have to - # add virtual or override keyword to methods - virtual, override = ("virtual ", "") if node.is_abstract else ("", " override") - - # Todo : We need virtual destructor otherwise there will be memory leaks. - # But we need define which are virtual base classes that needs virtual function. - self.write_line("virtual ~{}() {{}}".format(node.class_name)) - self.write_line() - - for child in node.children: - class_name = child.class_name - varname = child.varname - if child.add_method: - self.write_line("void add{0}({0} *s) {{".format(class_name)) - self.write_line(" {}.emplace_back(s);".format(varname)) - self.write_line("}") - - if child.get_node_name: - # string node should be evaluated and hence eval() method - method = "eval" if child.is_string_node() else "get_node_name" - self.write_line("virtual std::string get_node_name() override {") - self.write_line(" return {}->{}();".format(varname, method)) - self.write_line("}") - - getter_method = child.getter_method if child.getter_method else "get_" + to_snake_case(varname) - getter_override = " override" if child.getter_override else "" - return_type = child.member_typename - self.write_line("{} {}(){}{{ return {}; }}".format(return_type, getter_method, getter_override, varname)) - - setter_method = "set_" + to_snake_case(varname) - setter_type = child.member_typename - reference = "" if child.is_base_type_node() else "&&" - self.write_line("void {0}({1}{2} {3}) {{ this->{3} = {3}; }}".format(setter_method, setter_type, reference, varname)) - - self.write_line() - - # all member functions - self.write_line("{}void visit_children (Visitor* v) override;".format(virtual)) - self.write_line("{}void accept(Visitor* v) override {{ v->visit_{}(this); }}".format(virtual, to_snake_case(node.class_name))) - self.write_line("{0}{1}* clone() override {{ return new {1}(*this); }}".format(virtual, node.class_name)) - - self.write_line() - - # TODO: type should declared as enum class - typename = to_snake_case(node.class_name).upper() - self.write_line("{}AstNodeType get_node_type() override {{ return AstNodeType::{}; }}".format(virtual, typename)) - self.write_line('{}std::string get_node_type_name() override {{ return "{}"; }}'.format(virtual, node.class_name)) - self.write_line("bool is_{} () override {{ return true; }}".format(to_snake_case(node.class_name))) - - if node.has_token: - self.write_line("{}ModToken* get_token(){} {{ return token.get(); }}".format(virtual, override)) - self.write_line("void set_token(ModToken& tok) { token = std::shared_ptr<ModToken>(new ModToken(tok)); }") - - if node.is_symtab_needed(): - self.write_line("void set_symbol_table(symtab::SymbolTable* newsymtab) override { symtab = newsymtab; }") - self.write_line("symtab::SymbolTable* get_symbol_table() override { return symtab; }") - - if node.is_program_node(): - self.write_line("symtab::ModelSymbolTable* get_model_symbol_table() { return &model_symtab; }") - - if node.is_base_class_number_node(): - negation = "!" if node.is_boolean_node() else "-"; - self.write_line("void negate() override {{ value = {}value; }}".format(negation)) - self.write_line("double to_double() override { return value; }") - - if node.is_name_node(): - self.write_line("{}void set_name(std::string name){} {{ value->set(name); }}".format(virtual, override)) - - if node.is_number_node(): - self.write_line('{}double to_double() {{ throw std::runtime_error("to_double not implemented"); }}'.format(virtual)) - - if node.is_base_block_node(): - self.write_line('virtual ArgumentVector get_parameters() { throw std::runtime_error("get_parameters not implemented"); }') - - # if node is of enum type then return enum value - if node.is_data_type_node(): - data_type = node.get_data_type_name() - if node.is_enum_node(): - self.write_line("std::string eval() {{ return {}Names[value]; }}".format(data_type)) - # But if basic data type then eval return their value - else: - self.write_line("{} eval() {{ return value; }}".format(data_type)) - self.write_line("void set({} _value) {{ value = _value; }}".format(data_type)) - - self.write_line("};", pre_gutter=-2, newline=2) - - def class_name_declaration(self): - self.write_line("namespace ast {", newline=2, post_gutter=1) - self.ast_classes_declaration() - - def declaration_end(self): - self.write_line(pre_gutter=-1) - - def private_declaration(self): - pass - - def public_declaration(self): - pass - - def post_declaration(self): - self.write_line("} // namespace ast", pre_gutter=-1) - - -class AstDefinitionPrinter(DefinitionPrinter): - """Prints AST class definitions""" - - def headers(self): - self.write_line('#include "ast/ast.hpp"') - self.write_line('#include "symtab/symbol_table.hpp"', newline=2) - - def definitions(self): - self.write_line("namespace ast {", post_gutter=1) - - for node in self.nodes: - # first get types of all childrens into members vector - members = [(child.get_typename(), child.varname) for child in node.children] - non_base_members = [child for child in node.children if not child.is_base_type_node()] - - self.write_line("/* visit method for {} ast node */".format(node.class_name)) - self.write_line("void {}::visit_children(Visitor* v) {{".format(node.class_name), post_gutter=1) - - for child in non_base_members: - if child.is_vector: - self.write_line("for (auto& item : this->{}) {{".format(child.varname)) - self.write_line(" item->accept(v);") - self.write_line("}") - elif child.optional: - self.write_line("if (this->{}) {{".format(child.varname)) - self.write_line(" this->{}->accept(v);".format(child.varname)) - self.write_line("}") - elif child.is_pointer_node(): - self.write_line("{}->accept(v);".format(child.varname)) - else: - self.write_line("{}.accept(v);".format(child.varname)) - - if not non_base_members: - self.write_line("(void)v;") - - self.write_line("}", pre_gutter=-1, newline=2) - - if members: - arguments = (", ".join(map(lambda x: x[0] + " " + x[1], members))) - initializer = ", ".join(map(lambda x: x[1] + "(" + x[1] + ")", members)) - - self.write_line("/* constructor for {} ast node */".format(node.class_name)) - self.write_line("{0}::{0}({1})".format(node.class_name, arguments)) - self.write_line(" : {}".format(initializer)) - self.write_line("{}", newline=2) - - self.write_line("/* copy constructor for {} ast node */".format(node.class_name)) - self.write_line("{0}::{0}(const {0}& obj) {{".format(node.class_name), post_gutter=1) - - for child in node.children: - if child.is_vector: - self.write_line("for (auto& item : obj.{}) {{".format(child.varname)) - self.write_line(" this->{}.emplace_back(item->clone());".format(child.varname)) - self.write_line("}") - elif child.is_pointer_node() or child.optional: - self.write_line("if (obj.{}) {{".format(child.varname)) - self.write_line(" this->{0}.reset(obj.{0}->clone());".format(child.varname)) - self.write_line("}") - else: - self.write_line("this->{0} = obj.{0};".format(child.varname)) - - if node.has_token: - self.write_line("if (obj.token) {") - self.write_line(" this->token = std::shared_ptr<ModToken>(obj.token->clone());") - self.write_line("}") - - self.write_line("}", pre_gutter=-1, newline=2) - - self.write_line("} // namespace ast", pre_gutter=-1) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index e03146a343..db0e041474 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -2,37 +2,19 @@ from pathlib import Path from parser import LanguageParser -from ast_printer import * import node_info - +import utils # parse nmodl definition file and get list of abstract nodes nodes = LanguageParser("nmodl.yaml").parse_file() -# print various header and code files - -AstForwardDeclarationPrinter( - "../ast/ast_decl.hpp", - "", - nodes).write() - -AstDeclarationPrinter( - "../ast/ast.hpp", - "", - nodes).write() - -AstDefinitionPrinter( - "../ast/ast.cpp", - "", - nodes).write() - templates = Path(__file__).parent / 'templates' basedir = Path(__file__).resolve().parent.parent env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates)), trim_blocks=True, lstrip_blocks=True) -env.filters['snake_case'] = to_snake_case +env.filters['snake_case'] = utils.to_snake_case for fn in templates.glob('*.[ch]pp'): filename = basedir / ('visitors' if 'visitor' in fn.name else 'ast') / fn.name diff --git a/src/nmodl/language/nmodl_printer.py b/src/nmodl/language/nmodl_printer.py deleted file mode 100644 index 0bbe4b023e..0000000000 --- a/src/nmodl/language/nmodl_printer.py +++ /dev/null @@ -1,144 +0,0 @@ -from printer import * -from utils import * -from node_info import ORDER_VAR_NAME, BINARY_OPERATOR_NAME, BINARY_EXPRESSION_NODE - -class NmodlVisitorDeclarationPrinter(DeclarationPrinter): - """Visitor class declaration for printing AST back to NMODL""" - - def headers(self): - self.write_line('#include "ast/ast.hpp"') - self.write_line('#include "printer/nmodl_printer.hpp"', newline=2) - - def class_comment(self): - self.write_line("/* Visitor for printing AST back to NMODL */") - - def class_name_declaration(self): - self.write_line("class {} : public Visitor {{".format(self.classname)) - - def private_declaration(self): - self.write_line("private:") - self.write_line(" std::unique_ptr<NMODLPrinter> printer;") - - def public_declaration(self): - self.write_line("public:", post_gutter=1) - self.write_line("{}() : printer(new NMODLPrinter()) {{}}".format(self.classname)) - self.write_line("{}(std::string filename) : printer(new NMODLPrinter(filename)) {{}}".format(self.classname)) - self.write_line("{}(std::stringstream& stream) : printer(new NMODLPrinter(stream)) {{}}".format(self.classname)) - self.write_line() - - self.write_line("template<typename T>") - self.write_line("void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement);") - - for node in self.nodes: - self.write_line("virtual void visit_{}(ast::{}* node) override;".format(to_snake_case(node.class_name), node.class_name)) - - -class NmodlVisitorDefinitionPrinter(DefinitionPrinter): - """Print visitor class definitions for printing AST back to NMODL""" - - def headers(self): - self.write_line('#include "visitors/nmodl_visitor.hpp"') - self.write_line('#include "visitors/nmodl_visitor_helper.hpp"', newline=2) - self.write_line("using namespace ast;", newline=2) - - def add_element(self, name): - if name: - self.write_line('printer->add_element("{}");'.format(name)) - - def definitions(self): - for node in self.nodes: - - self.write_line("void NmodlPrintVisitor::visit_{}({}* node) {{".format(to_snake_case(node.class_name), node.class_name), post_gutter=1) - - if node.nmodl_name: - self.write_line('printer->add_element("{}");'.format(node.nmodl_name)) - - self.add_element(node.prefix) - - if node.is_block_node(): - self.write_line("printer->push_level();") - - # for basic data types we just have to eval and they will return their value - # but for integer node we have to check if it is represented as macro - if node.is_data_type_node(): - if node.is_integer_node(): - self.write_line("if(node->get_macro_name() == nullptr) {") - self.write_line(" printer->add_element(std::to_string(node->eval()));") - self.write_line("}") - else: - self.write_line("std::stringstream ss;") - self.write_line("ss << node->eval();") - self.write_line("printer->add_element(ss.str());") - - for child in node.children: - self.add_element(child.force_prefix) - - is_program = "true" if node.is_program_node() else "false" - is_statement = "false" - - # unit block has expressions as children and hence need to be considered as statements - if child.is_statement_node() or node.is_unit_block(): - is_statement = "true" - - # In the current implementation all childrens are non-base-data-type nodes - # i.e. int, double are wrapped into Integer, Double classes. Hence it is - # not tested in non data types nodes. This restriction could be easily avoided - # if required. - if child.is_base_type_node(): - if not node.is_data_type_node(): - raise "Base data type members not in non data type nodes handled/tested!" - else: - - # optional members or statement blocks are in brace (could be nullptr) - # start of a brace - if child.optional or child.is_statement_block_node(): - self.write_line('if(node->get_{}()) {{'.format(child.varname), post_gutter=1) - - # todo : assugming member with nmodl of type Boolean. currently there - # are only two use cases: SWEEP and "->"" - if child.nmodl_name: - self.write_line('if(node->get_{}()->eval()) {{'.format(child.varname)) - self.write_line(' printer->add_element("{}");'.format(child.nmodl_name)) - self.write_line('}') - - # vector members could be empty and pre-defined method to iterate/visit - elif child.is_vector: - if child.prefix or child.suffix: - self.write_line("if (!node->get_{}().empty()) {{".format(child.varname), post_gutter=1) - self.add_element(child.prefix) - self.write_line('visit_element(node->get_{}(),"{}",{},{});'.format(child.varname, child.separator, is_program, is_statement)); - self.add_element(child.suffix) - if child.prefix or child.suffix: - self.write_line("}", pre_gutter=-1) - - # prime need to be printed with "'" as suffix - elif node.is_prime_node() and child.varname == ORDER_VAR_NAME: - self.write_line("auto order = node->get_{}()->eval();".format(child.varname)) - self.write_line("auto symbol = std::string(order, '\\'');") - self.write_line("printer->add_element(symbol);"); - - # add space surrounding certain binary operators for readability - elif node.class_name == BINARY_EXPRESSION_NODE and child.varname == BINARY_OPERATOR_NAME: - self.write_line('std::string op = node->get_op().eval();') - self.write_line('if(op == "=" || op == "&&" || op == "||" || op == "==")') - self.write_line(' op = " " + op + " ";') - self.write_line('printer->add_element(op);') - - else: - self.add_element(child.prefix) - op = "->" if child.is_pointer_node() else "." - self.write_line("node->get_{}(){}accept(this);".format(child.varname, op)) - self.add_element(child.suffix) - - # end of a brace - if child.optional or child.is_statement_block_node(): - self.write_line("}", pre_gutter=-1) - - self.add_element(child.force_suffix) - - self.add_element(node.suffix) - - if node.is_block_node(): - self.write_line("printer->pop_level();") - - self.write_line("}", pre_gutter=-1, newline=2) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 42e2808e2e..ae3b2b18e7 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -5,9 +5,9 @@ is represented by ChildNode. """ -from argument import Argument from node_info import * - +import textwrap +from utils import * class BaseNode: """base class for all node types (parent + child) """ @@ -152,10 +152,41 @@ def member_typename(self): elif self.is_base_type_node() or self.is_ptr_excluded_node(): type_name = self.class_name else: - type_name = "std::shared_ptr<" + self.class_name + ">" + type_name = f"std::shared_ptr<{self.class_name}>" return type_name + def get_add_method(self): + s = '' + if self.add_method: + method = f"""void add{self.class_name}({self.class_name} *s) {{ + {self.varname}.emplace_back(s); + }}""" + s = textwrap.dedent(method) + return s + + def get_node_name_method(self): + s = '' + if self.get_node_name: + # string node should be evaluated and hence eval() method + method_name = "eval" if self.is_string_node() else "get_node_name" + method = f"""virtual std::string get_node_name() override {{ + return {self.varname}->{method_name}(); + }}""" + s = textwrap.dedent(method) + return s + + def get_getter_method(self): + getter_method = self.getter_method if self.getter_method else "get_" + to_snake_case(self.varname) + getter_override = " override" if self.getter_override else "" + return_type = self.member_typename + return f"{return_type} {getter_method}(){getter_override}{{ return {self.varname}; }}" + + def get_setter_method(self): + setter_method = "set_" + to_snake_case(self.varname) + setter_type = self.member_typename + reference = "" if self.is_base_type_node() else "&&" + return f"void {setter_method}({setter_type}{reference} {self.varname}) {{ this->{self.varname} = {self.varname}; }}" class Node(BaseNode): """represent a class for every rule in language specification""" @@ -166,6 +197,14 @@ def __init__(self, args): self.has_token = args.has_token self.children = [] + @property + def ast_enum_name(self): + return to_snake_case(self.class_name).upper() + + @property + def negation(self): + return "!" if self.is_boolean_node() else "-" + def add_child(self, args): """add new child i.e. member to the class""" node = ChildNode(args) @@ -271,3 +310,11 @@ def private_members(self): members.append(["symtab::ModelSymbolTable", "model_symtab;"]) return members + + @property + def members(self): + return [(child.get_typename(), child.varname) for child in self.children] + + @property + def non_base_members(self): + return [child for child in self.children if not child.is_base_type_node()] diff --git a/src/nmodl/language/printer.py b/src/nmodl/language/printer.py deleted file mode 100644 index bfe1c632e8..0000000000 --- a/src/nmodl/language/printer.py +++ /dev/null @@ -1,148 +0,0 @@ -from abc import ABCMeta, abstractmethod -import sys - - -class Writer(object): - """ Utility class for writing to file - - Common methods for writing code are provided by this - class. This handles opening/closing of file, indentation, - newlines, buffering etc. - """ - - # tab is four whitespaces - TAB = " " - - def __init__(self, filepath): - self.filepath = filepath - self.num_tabs = 0 - self.lines = [] - try: - self.fh = open(self.filepath, "w+") - except IOError as e: - print("Error :: I/O error while writing {0} : {1}".format(self.filepath, e.strerror)) - sys.exit(1) - - def print_gutter(self): - for i in range(0, self.num_tabs): - self.fh.write(self.TAB) - - def increase_gutter(self): - self.num_tabs += 1 - - def decrease_gutter(self): - self.num_tabs -= 1 - - def write(self, string): - if string: - self.print_gutter() - self.fh.write(string) - - def write_line(self, string, newline, pre_gutter, post_gutter): - self.num_tabs += pre_gutter - self.write(string) - for i in range(newline): - self.fh.write("\n") - self.num_tabs += post_gutter - - def __del__(self): - if not self.fh.closed: - self.fh.close() - - -class Printer(object): - """ Base class for code printer classes""" - __metaclass__ = ABCMeta - - def __init__(self, filepath, classname, nodes): - self.writer = Writer(filepath) - self.classname = classname - self.nodes = nodes - - def write_line(self, string=None, newline=1, pre_gutter=0, post_gutter=0): - self.writer.write_line(string, newline, pre_gutter, post_gutter) - - @abstractmethod - def write(self): - pass - - -class DeclarationPrinter(Printer): - """ Utility class for writing declaration / header file (.hpp) - - Header file of the class typically consists of - - headers include section - - comment about the class - - class name / class declaration - - private member declarations - - public members declaration - - end of class declaration - - post declaration, e.g. end of namespace - """ - - def guard(self): - self.write_line("#pragma once", newline=2) - - @abstractmethod - def headers(self): - pass - - def class_comment(self): - pass - - def class_name_declaration(self): - self.write_line("class " + self.classname + " {") - - def declaration_start(self): - self.guard() - self.headers() - self.write_line() - self.class_comment() - self.class_name_declaration() - - def private_declaration(self): - pass - - def public_declaration(self): - pass - - def body(self): - self.writer.increase_gutter() - self.write_line() - self.private_declaration() - self.public_declaration() - self.writer.decrease_gutter() - - def declaration_end(self): - self.write_line("};", pre_gutter=-1) - - def post_declaration(self): - pass - - def write(self): - self.declaration_start() - self.body() - self.declaration_end() - self.post_declaration() - - -class DefinitionPrinter(Printer): - """ Utility class for writing definition / implementation file (.cpp) - - Implementation file of the class typically consists of - - headers include section (.hpp file) - - class methods definition - """ - __metaclass__ = ABCMeta - - @abstractmethod - def headers(self): - pass - - @abstractmethod - def definitions(self): - pass - - def write(self): - self.headers() - self.definitions() diff --git a/src/nmodl/language/templates/ast.cpp b/src/nmodl/language/templates/ast.cpp new file mode 100644 index 0000000000..5d62cce61d --- /dev/null +++ b/src/nmodl/language/templates/ast.cpp @@ -0,0 +1,68 @@ +#include "ast/ast.hpp" +#include "symtab/symbol_table.hpp" + +{% macro arglist(members) -%} + {%- for type, var in members %} {{ type }} {{ var }} {%- if not loop.last %}, {% endif %} {% endfor -%} +{%- endmacro %} + +{% macro initlist(members) -%} +{%- for type, var in members %} {{ var }}({{ var }}) {%- if not loop.last %}, {% endif %} {% endfor -%} +{%- endmacro %} + +namespace ast { + + {% for node in nodes %} + + /* visit method for {{ node.class_name }} ast node */ + void {{ node.class_name }}::visit_children(Visitor* v) { + {% for child in node.non_base_members %} + {% if child.is_vector %} + for (auto& item : this->{{ child.varname }}) { + item->accept(v); + } + {% elif child.optional %} + if (this->{{ child.varname }}) { + this->{{ child.varname }}->accept(v); + } + {% elif child.is_pointer_node() %} + {{ child.varname }}->accept(v); + {% else %} + {{ child.varname }}.accept(v); + {% endif %} + (void)v; + {% endfor %} + } + + {% if node.members %} + /* constructor for {{ node.class_name }} ast node */ + {{ node.class_name }}::{{ node.class_name }}({{ arglist(node.members) }}) + : {{ initlist(node.members) }} {} + + /* copy constructor for {{ node.class_name }} ast node */ + {{ node.class_name }}::{{ node.class_name }}(const {{ node.class_name }}& obj) { + + {% for child in node.children %} + {% if child.is_vector %} + for (auto& item : obj.{{ child.varname }}) { + this->{{ child.varname }}.emplace_back(item->clone()); + } + {% elif child.is_pointer_node() or child.optional %} + if (obj.{{ child.varname }}) { + this->{{ child.varname }}.reset(obj.{{ child.varname }}->clone()); + } + {% else %} + this->{{ child.varname }} = obj.{{ child.varname }}; + {% endif %} + {% endfor %} + {% if node.has_token %} + if (obj.token) { + this-> token = std::shared_ptr<ModToken>(obj.token->clone()); + } + {% endif %} + } + + {% endif %} + + {% endfor %} + +} diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp new file mode 100644 index 0000000000..fb9f940d7f --- /dev/null +++ b/src/nmodl/language/templates/ast.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include <iostream> +#include <memory> +#include <string> +#include <vector> + +#include "ast/ast_decl.hpp" +#include "ast/ast_common.hpp" +#include "lexer/modtoken.hpp" +#include "utils/common_utils.hpp" +#include "visitors/visitor.hpp" + +{% macro ctor_arglist(members) -%} + {%- for type, var in members %} {{ type }} {{ var }} {%- if not loop.last %}, {% endif %} {% endfor -%} +{%- endmacro %} + +{% macro virtual(node) %} + {% if node.is_abstract %} virtual {% endif %} +{% endmacro %} + +{% macro override(node) %} + {% if not node.is_abstract %} override {% endif %} +{% endmacro %} + +namespace ast { + + /* Define all AST nodes */ + {% for node in nodes %} + class {{ node.class_name }} : public {{ node.base_class }} { + {% if node.private_members() %} + private: + {% for member in node.private_members() %} + {{ member[0] }} {{ member[1] }}; + {% endfor %} + {% endif %} + public: + {% for member in node.public_members() %} + {{ member[0] }} {{ member[1] }}; + {% endfor %} + + {% if node.children %} + {{ node.class_name }}({{ ctor_arglist(node.members) }}); + {{ node.class_name }}(const {{ node.class_name }}& obj); + {% endif %} + + {% if node.is_program_node() or node.is_ptr_excluded_node() %} + {{ node.class_name}}() = default; + {% endif %} + + virtual ~{{ node.class_name }}() {} + + {% for child in node.children %} + {{ child.get_add_method() }} + + {{ child.get_node_name_method() }} + + {{ child.get_getter_method() }} + + {{ child.get_setter_method() }} + + {% endfor %} + + {{ virtual(node) }} void visit_children (Visitor* v) override; + + {{ virtual(node) }} void accept(Visitor* v) override { v->visit_{{ node.class_name | snake_case }}(this); } + + {{ virtual(node) }} {{ node.class_name }}* clone() override { return new {{ node.class_name }}(*this); } + + {{ virtual(node) }} AstNodeType get_node_type() override { return AstNodeType::{{ node.ast_enum_name }}; } + + {{ virtual(node) }} std::string get_node_type_name() override { return "{{ node.class_name }}"; } + + bool is_{{ node.class_name | snake_case }} () override { return true; } + + {% if node.has_token %} + {{ virtual(node) }}ModToken* get_token(){{ override(node) }} { return token.get(); } + + void set_token(ModToken& tok) { token = std::shared_ptr<ModToken>(new ModToken(tok)); } + {% endif %} + + {% if node.is_symtab_needed() %} + void set_symbol_table(symtab::SymbolTable* newsymtab) override { symtab = newsymtab; } + + symtab::SymbolTable* get_symbol_table() override { return symtab; } + {% endif %} + + {% if node.is_program_node() %} + symtab::ModelSymbolTable* get_model_symbol_table() { return &model_symtab; } + {% endif %} + + {% if node.is_base_class_number_node() %} + void negate() override { value = {{ node.negation }}value; } + + double to_double() override { return value; } + {% endif %} + + {% if node.is_name_node() %} + {{ virtual(node) }}void set_name(std::string name){{ override(node) }} { value->set(name); } + {% endif %} + + {% if node.is_number_node() %} + {{ virtual(node) }}double to_double() { throw std::runtime_error("to_double not implemented"); } + {% endif %} + + {% if node.is_base_block_node() %} + virtual ArgumentVector get_parameters() { throw std::runtime_error("get_parameters not implemented"); } + {% endif %} + + {% if node.is_data_type_node() %} + {# if node is of enum type then return enum value #} + {% if node.is_enum_node() %} + std::string eval() { return {{ node.get_data_type_name() }}Names[value]; } + {# But if basic data type then eval return their value #} + {% else %} + {{ node.get_data_type_name() }} eval() { return value; } + + void set({{ node.get_data_type_name() }} _value) { value = _value; } + {% endif %} + {% endif %} + }; + + {% endfor %} +} diff --git a/src/nmodl/language/templates/ast_decl.hpp b/src/nmodl/language/templates/ast_decl.hpp new file mode 100644 index 0000000000..116a7a872b --- /dev/null +++ b/src/nmodl/language/templates/ast_decl.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include <memory> +#include <utility> +#include <vector> + +namespace ast { + + {% for node in nodes %} + class {{ node.class_name }}; + {% endfor %} + + /* Type for every ast node */ + enum class AstNodeType { + {% for node in nodes %} + {{ node.class_name|snake_case|upper }}, + {% endfor %} + }; + + /* std::vector for convenience */ + {% for node in nodes %} + using {{ node.class_name }}Vector = std::vector<std::shared_ptr<{{ node.class_name }}>>; + {% endfor %} + +} diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index 998ead5577..281b3374dc 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -29,79 +29,77 @@ using namespace ast; {%- macro add_vector_child(node, child) -%} {% call guard(child.prefix, child.suffix) %} -visit_element(node->get_{{ child.varname }}(),"{{ child.separator }}",{{ is_program(node) }},{{ is_statement(node, child) }}); + visit_element(node->get_{{ child.varname }}(),"{{ child.separator }}",{{ is_program(node) }},{{ is_statement(node, child) }}); {% endcall %} {%- endmacro -%} {%- macro add_child(node, child) -%} {% if child.nmodl_name %} -if(node->get_{{ child.varname }}()->eval()) { - printer->add_element("{{ child.nmodl_name }}"); -} + if(node->get_{{ child.varname }}()->eval()) { + printer->add_element("{{ child.nmodl_name }}"); + } {% elif child.is_vector %} {%- if child.prefix or child.suffix %} -if (!node->get_{{ child.varname }}().empty()) { - {{ add_vector_child(node, child)|trim|indent }} -} + if (!node->get_{{ child.varname }}().empty()) { + {{ add_vector_child(node, child)|trim }} + } {%- else %} -{{- add_vector_child(node, child) }} + {{- add_vector_child(node, child) }} {%- endif %} {% elif node.is_prime_node() and child.varname == node_info.ORDER_VAR_NAME %} -auto order = node->get_{{ child.varname }}()->eval(); -auto symbol = std::string(order, '\''); -printer->add_element(symbol); + auto order = node->get_{{ child.varname }}()->eval(); + auto symbol = std::string(order, '\''); + printer->add_element(symbol); {% elif node.class_name == node_info.BINARY_EXPRESSION_NODE and child.varname == node_info.BINARY_OPERATOR_NAME %} -std::string op = node->op.eval(); -if(op == "=" || op == "&&" || op == "||" || op == "==") - op = " " + op + " "; -printer->add_element(op); + std::string op = node->op.eval(); + if(op == "=" || op == "&&" || op == "||" || op == "==") + op = " " + op + " "; + printer->add_element(op); {% else %} - {% call guard(child.prefix, child.suffix) %} -node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node() else "." }}accept(this); - {% endcall %} + {% call guard(child.prefix, child.suffix) %} + node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node() else "." }}accept(this); + {% endcall %} {%- endif %} {%- endmacro -%} {%- for node in nodes %} void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}* node) { -{{ add_element(node.nmodl_name) -}} - {% filter indent -%} - {% call guard(node.prefix, node.suffix) -%} - {% if node.is_block_node() %} -printer->push_level(); + {{ add_element(node.nmodl_name) -}} + {% call guard(node.prefix, node.suffix) -%} + {% if node.is_block_node() %} + printer->push_level(); + {% endif %} + {% if node.is_data_type_node() %} + {% if node.is_integer_node() %} + if(node->get_macro_name() == nullptr) { + printer->add_element(std::to_string(node->eval())); + } + {% else %} + std::stringstream ss; + ss << node->eval(); + printer->add_element(ss.str()); + {% endif %} + {% endif %} + {% for child in node.children %} + {% call guard(child.force_prefix, child.force_suffix) -%} + {% if child.is_base_type_node() %} + {% else %} + {% if child.optional or child.is_statement_block_node() %} + if(node->get_{{ child.varname }}()) { + {{ add_child(node, child)|trim }} + } + {% else %} + {{ add_child(node, child)|trim }} {% endif %} - {% if node.is_data_type_node() %} - {% if node.is_integer_node() %} -if(node->get_macro_name() == nullptr) { - printer->add_element(std::to_string(node->eval())); -} - {% else %} -std::stringstream ss; -ss << node->eval(); -printer->add_element(ss.str()); - {% endif %} - {% endif %} - {% for child in node.children %} - {% call guard(child.force_prefix, child.force_suffix) -%} - {% if child.is_base_type_node() %} - {% else %} - {% if child.optional or child.is_statement_block_node() %} -if(node->get_{{ child.varname }}()) { - {{ add_child(node, child)|trim|indent() }} -} - {% else %} -{{ add_child(node, child)|trim }} - {% endif %} - {% endif %} - {% endcall -%} - {% endfor %} + {% endif %} {% endcall -%} - {% if node.is_block_node() -%} -printer->pop_level(); - {% endif -%} - {% endfilter -%} + {% endfor %} + {% endcall -%} + {% if node.is_block_node() -%} + printer->pop_level(); + {% endif -%} } {% endfor %} diff --git a/src/nmodl/language/visitors_printer.py b/src/nmodl/language/visitors_printer.py deleted file mode 100644 index a91b95a141..0000000000 --- a/src/nmodl/language/visitors_printer.py +++ /dev/null @@ -1,265 +0,0 @@ -from printer import * -from utils import * - - -class AbstractVisitorPrinter(DeclarationPrinter): - """Prints abstract base class for all visitor implementations""" - - def headers(self): - line = '#include "ast/ast_decl.hpp"' - self.write_line(line) - - def class_comment(self): - self.write_line("/* Abstract base class for all visitor implementations */") - - def public_declaration(self): - self.write_line("public:", post_gutter=1) - - for node in self.nodes: - line = "virtual void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) = 0;" - self.write_line(line) - - self.writer.decrease_gutter() - - -class AstVisitorDeclarationPrinter(DeclarationPrinter): - """Prints base visitor class declaration""" - - def headers(self): - line = '#include "ast/ast.hpp"' - self.write_line(line) - line = '#include "visitors/visitor.hpp"' - self.write_line(line, newline=2) - - def class_comment(self): - self.write_line("/* Basic visitor implementation */") - - def class_name_declaration(self): - self.write_line("class " + self.classname + " : public Visitor {") - - def public_declaration(self): - self.write_line("public:", post_gutter=1) - - for node in self.nodes: - line = "virtual void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" - self.write_line(line) - - self.writer.decrease_gutter() - - -class AstVisitorDefinitionPrinter(DefinitionPrinter): - """Prints base visitor class method definitions""" - - def headers(self): - self.write_line('#include "visitors/ast_visitor.hpp"') - self.write_line("using namespace ast;", newline=2) - - def definitions(self): - for node in self.nodes: - line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" - self.write_line(line, post_gutter=1) - self.write_line("node->visit_children(this);", post_gutter=-1) - self.write_line("}", newline=2) - - -class JSONVisitorDeclarationPrinter(DeclarationPrinter): - """Prints visitor class declaration for printing AST in JSON format""" - - def headers(self): - line = '#include "visitors/ast_visitor.hpp"' - self.write_line(line) - line = '#include "printer/json_printer.hpp"' - self.write_line(line, newline=2) - - def class_comment(self): - self.write_line("/* Concrete visitor for printing AST in JSON format */") - - def class_name_declaration(self): - self.write_line("class " + self.classname + " : public AstVisitor {") - - def private_declaration(self): - self.write_line("private:", post_gutter=1) - line = "std::unique_ptr<JSONPrinter> printer;" - self.write_line(line, newline=2, post_gutter=-1) - - def public_declaration(self): - self.write_line("public:", post_gutter=1) - - line = self.classname + "() : printer(new JSONPrinter()) {} " - self.write_line(line) - - line = self.classname + "(std::string filename) : printer(new JSONPrinter(filename)) {}" - self.write_line(line) - - line = self.classname + "(std::stringstream &ss) : printer(new JSONPrinter(ss)) {}" - self.write_line(line, newline=2) - - line = "void flush() { printer->flush(); }" - self.write_line(line) - line = "void compact_json(bool flag) { printer->compact_json(flag); } " - self.write_line(line, newline=2) - - for node in self.nodes: - line = "void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" - self.write_line(line) - - self.writer.decrease_gutter() - - -class JSONVisitorDefinitionPrinter(DefinitionPrinter): - """Prints visitor class definition for printing AST in JSON format""" - - def headers(self): - self.write_line('#include "visitors/json_visitor.hpp"') - self.write_line('using namespace ast;', newline=2) - - def definitions(self): - for node in self.nodes: - line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" - self.write_line(line, post_gutter=1) - - if node.has_children(): - self.write_line("printer->push_block(node->get_node_type_name());") - self.write_line("node->visit_children(this);") - - if node.is_data_type_node(): - if node.class_name == "Integer": - self.write_line("if(!node->get_macro_name()) {") - self.write_line(" std::stringstream ss;") - self.write_line(" ss << node->eval();") - self.write_line(" printer->add_node(ss.str());") - self.write_line("}") - else: - self.write_line("std::stringstream ss;") - self.write_line("ss << node->eval();") - self.write_line("printer->add_node(ss.str());") - - self.write_line("printer->pop_block();") - - if node.class_name == "Program": - self.write_line("flush();") - else: - self.write_line("(void)node;") - self.write_line("printer->add_node(\"" + node.class_name + "\");") - - self.write_line("}", pre_gutter=-1, newline=2) - - self.writer.decrease_gutter() - - -class SymtabVisitorDeclarationPrinter(DeclarationPrinter): - """Prints visitor class declaration for printing Symbol table in JSON format""" - - def headers(self): - line = '#include "visitors/json_visitor.hpp"' - self.write_line(line) - line = '#include "visitors/ast_visitor.hpp"' - self.write_line(line) - line = '#include "symtab/symbol_table.hpp"' - self.write_line(line, newline=2) - - def class_comment(self): - self.write_line("/* Concrete visitor for constructing symbol table from AST */") - - def class_name_declaration(self): - self.write_line("class " + self.classname + " : public AstVisitor {") - - def private_declaration(self): - self.write_line("private:", post_gutter=1) - self.write_line("symtab::ModelSymbolTable* modsymtab;", newline=2, post_gutter=-1) - self.write_line("std::unique_ptr<JSONPrinter> printer;") - self.write_line("std::string block_to_solve;") - self.write_line("bool update = false;") - self.write_line("bool under_state_block = false;", newline=2) - - def public_declaration(self): - self.write_line("public:", post_gutter=1) - - line = "explicit " + self.classname + "(bool update = false) : printer(new JSONPrinter()), update(update) {} " - self.write_line(line) - - line = self.classname + "(std::stringstream &ss, bool update = false) : printer(new JSONPrinter(ss)), update(update) {}" - self.write_line(line) - - line = self.classname + "(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {}" - self.write_line(line, newline=2) - - # helper function for setting up symbol for variable - self.write_line("void setup_symbol(ast::Node* node, NmodlTypeFlag property);", newline=2) - - # add symbol with given property to model symbol table - line = "void add_model_symbol_with_property(ast::Node* node, NmodlTypeFlag property);" - self.write_line(line, newline=2) - - # helper function for creating symbol table for blocks - line = "void setup_symbol_table(ast::AST *node, const std::string& name, bool is_global);" - self.write_line(line, newline=2) - - # helper function for creating symbol table for global blocks of mod file - line = "void setup_symbol_table_for_global_block(ast::Node *node);" - self.write_line(line, newline=2) - - # helper function for creating symbol table for non-global blocks (e.g. function, procedures) - line = "void setup_symbol_table_for_scoped_block(ast::Node *node, const std::string& name);" - self.write_line(line, newline=2) - - # helper function to setup program symbol table - line = "void setup_symbol_table_for_program_block(ast::Program *node);" - self.write_line(line, newline=2) - - # we have to override visitor methods for the nodes - # which goes into symbol table - for node in self.nodes: - if node.is_symtab_method_required(): - line = "void visit_" + to_snake_case(node.class_name) + "(ast::" + node.class_name + "* node) override;" - self.write_line(line) - - self.writer.decrease_gutter() - - -class SymtabVisitorDefinitionPrinter(DefinitionPrinter): - """Prints visitor class definition for printing Symbol table in JSON format""" - - def headers(self): - self.write_line('#include "symtab/symbol_table.hpp"') - self.write_line('#include "visitors/symtab_visitor.hpp"', newline=2) - - self.write_line('using namespace symtab;') - self.write_line('using namespace syminfo;') - self.write_line('using namespace ast;', newline=2) - - def definitions(self): - for node in self.nodes: - - # for helper nodes definition needs to be diectly implemented in symtab_visitor_helper.cpp - if node.is_symbol_helper_node(): - continue - - if node.is_symtab_method_required(): - - line = "void " + self.classname + "::visit_" + to_snake_case(node.class_name) + "(" + node.class_name + "* node) {" - self.write_line(line, post_gutter=1) - - type_name = to_snake_case(node.class_name) - property_name = "syminfo::NmodlType::" + type_name - - if node.is_symbol_var_node(): - self.write_line("setup_symbol(node, " + property_name + ");") - - else: - - if node.is_program_node(): - self.write_line("setup_symbol_table_for_program_block(node);") - - elif node.is_global_block_node(): - self.write_line("setup_symbol_table_for_global_block(node);") - - else: - """this is for nodes which has parent class as Block node""" - if node.is_symbol_block_node(): - self.write_line("add_model_symbol_with_property(node, %s);" % property_name) - self.write_line("setup_symbol_table_for_scoped_block(node, node->get_node_name());") - else: - self.write_line("setup_symbol_table_for_scoped_block(node, node->get_node_type_name());") - - self.write_line("}", pre_gutter=-1, newline=2) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 2a26f76009..67dbb3ceb4 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -82,10 +82,10 @@ add_custom_command ( add_custom_command ( COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/verbatim.y + ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.hpp - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.y + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --" ) diff --git a/src/nmodl/parser/verbatim.y b/src/nmodl/parser/verbatim.yy similarity index 100% rename from src/nmodl/parser/verbatim.y rename to src/nmodl/parser/verbatim.yy From 10e6a8cb3f03c8725533a9d032b780c5b9cf2547 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Mon, 14 Jan 2019 17:47:01 +0100 Subject: [PATCH 110/871] Initial work for python bindings - added pybind11 submodule - moved AST class constructors from template into nodes.py - added more shared_ptr constructors in AST calsses - added decorators into python code Change-Id: I29f032a67be67b367426bb53f8d16f53d6ef85fe NMODL Repo SHA: BlueBrain/nmodl@dd1df37a2f1a82f810de3686727210b3d8668f97 --- README.md | 2 + cmake/nmodl/CMakeLists.txt | 7 ++ src/nmodl/language/CMakeLists.txt | 20 ++-- src/nmodl/language/nodes.py | 94 ++++++++++++++++--- src/nmodl/language/templates/ast.cpp | 20 ++-- src/nmodl/language/templates/ast.hpp | 29 +++--- src/nmodl/language/templates/json_visitor.cpp | 6 +- .../language/templates/nmodl_visitor.cpp | 20 ++-- .../language/templates/symtab_visitor.cpp | 10 +- .../language/templates/symtab_visitor.hpp | 2 +- 10 files changed, 140 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index 7491064600..e6cbe46ed6 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - Python3 (>=3.6) - Python yaml (pyyaml) - Jinja2 (>=2.10) +- Python textwrap +- pybind11 (which should be fetched in its submodule in ext/pybind11) #### Getting Dependencies diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 3159b20d54..1ff2677b4d 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -51,6 +51,13 @@ include_directories( ${PROJECT_SOURCE_DIR}/ext ) +#============================================================================= +# Include pybind11 +#============================================================================= +message(STATUS "INCLUDING PYBIND11") +add_subdirectory(ext/pybind11) + + #============================================================================= # Project version from git and project directories #============================================================================= diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index f7858f4b11..363ba98d16 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -33,17 +33,17 @@ if(CLANG_FORMAT_FOUND) DEPENDS ${PYCODE} DEPENDS ${TEMPLATE_FILES} COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" - ) + ) else() - add_custom_command ( - OUTPUT ${AUTOGEN_FILES} - COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" - ) + add_custom_command ( + OUTPUT ${AUTOGEN_FILES} + COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" + ) endif() #============================================================================= # Target to propogate dependencies properly to lexer diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index ae3b2b18e7..569583d78e 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -29,18 +29,23 @@ def get_data_type_name(self): """ return type name for the node """ return DATA_TYPES[self.class_name] + @property def is_statement_block_node(self): return True if self.class_name == STATEMENT_BLOCK_NODE else False + @property def is_statement_node(self): return True if self.class_name in STATEMENT_TYPES else False + @property def is_global_block_node(self): return True if self.class_name in GLOBAL_BLOCKS else False + @property def is_prime_node(self): return True if self.class_name == PRIME_NAME_NODE else False + @property def is_program_node(self): """ check if current node is main program container node @@ -48,6 +53,7 @@ def is_program_node(self): """ return True if self.class_name == PROGRAM_BLOCK else False + @property def is_block_node(self): """ Check if current node is of type Block @@ -58,53 +64,68 @@ def is_block_node(self): """ return True if self.class_name in BLOCK_TYPES else False + @property def is_unit_block(self): return True if self.class_name == UNIT_BLOCK else False + @property def is_data_type_node(self): return True if self.class_name in DATA_TYPES.keys() else False + @property def is_symbol_var_node(self): return True if self.class_name in SYMBOL_VAR_TYPES else False + @property def is_symbol_helper_node(self): return True if self.class_name in SYMBOL_TABLE_HELPER_NODES else False + @property def is_symbol_block_node(self): return True if self.class_name in SYMBOL_BLOCK_TYPES else False + @property def is_base_type_node(self): return True if self.class_name in BASE_TYPES else False + @property def is_string_node(self): return True if self.class_name == STRING_NODE else False + @property def is_integer_node(self): return True if self.class_name == INTEGER_NODE else False + @property def is_number_node(self): return True if self.class_name == NUMBER_NODE else False + @property def is_boolean_node(self): return True if self.class_name == BOOLEAN_NODE else False + @property def is_identifier_node(self): return True if self.class_name == IDENTIFIER_NODE else False + @property def is_name_node(self): return True if self.class_name == NAME_NODE else False + @property def is_enum_node(self): data_type = DATA_TYPES[self.class_name] return True if data_type in ENUM_BASE_TYPES else False + @property def is_pointer_node(self): if self.class_name in PTR_EXCLUDE_TYPES: return False - if self.is_base_type_node(): + if self.is_base_type_node: return False return True + @property def is_ptr_excluded_node(self): if self.class_name in PTR_EXCLUDE_TYPES: return True @@ -138,7 +159,7 @@ def get_typename(self): if self.is_vector: type_name += "Vector" - elif not self.is_base_type_node() and not self.is_ptr_excluded_node(): + elif not self.is_base_type_node and not self.is_ptr_excluded_node: type_name += "*" return type_name @@ -149,7 +170,7 @@ def member_typename(self): if self.is_vector: type_name = self.class_name + "Vector" - elif self.is_base_type_node() or self.is_ptr_excluded_node(): + elif self.is_base_type_node or self.is_ptr_excluded_node: type_name = self.class_name else: type_name = f"std::shared_ptr<{self.class_name}>" @@ -169,7 +190,7 @@ def get_node_name_method(self): s = '' if self.get_node_name: # string node should be evaluated and hence eval() method - method_name = "eval" if self.is_string_node() else "get_node_name" + method_name = "eval" if self.is_string_node else "get_node_name" method = f"""virtual std::string get_node_name() override {{ return {self.varname}->{method_name}(); }}""" @@ -185,7 +206,7 @@ def get_getter_method(self): def get_setter_method(self): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename - reference = "" if self.is_base_type_node() else "&&" + reference = "" if self.is_base_type_node else "&&" return f"void {setter_method}({setter_type}{reference} {self.varname}) {{ this->{self.varname} = {self.varname}; }}" class Node(BaseNode): @@ -203,7 +224,7 @@ def ast_enum_name(self): @property def negation(self): - return "!" if self.is_boolean_node() else "-" + return "!" if self.is_boolean_node else "-" def add_child(self, args): """add new child i.e. member to the class""" @@ -213,6 +234,7 @@ def add_child(self, args): def has_children(self): return True if self.children else False + @property def is_block_scoped_node(self): """ Check if node is derived from BASE_BLOCK @@ -227,6 +249,7 @@ def has_parent_block_node(self): """ return True if self.base_class == BASE_BLOCK else False + @property def is_base_block_node(self): """ check if node is Block @@ -234,6 +257,7 @@ def is_base_block_node(self): """ return True if self.class_name == BASE_BLOCK else False + @property def is_symtab_needed(self): """ Check if symbol tabel needed for current node @@ -242,10 +266,11 @@ def is_symtab_needed(self): :return: True or False """ # block scope nodes have symtab pointer - if self.is_program_node() or self.is_block_scoped_node(): + if self.is_program_node or self.is_block_scoped_node: return True return False + @property def is_symtab_method_required(self): """ Symbols are not present in all block nodes e.g. Integer node @@ -264,7 +289,7 @@ def is_symtab_method_required(self): if self.class_name in SYMBOL_VAR_TYPES or self.class_name in SYMBOL_BLOCK_TYPES: method_required = True - if self.is_program_node() or self.has_parent_block_node(): + if self.is_program_node or self.has_parent_block_node(): method_required = True if self.class_name in SYMBOL_TABLE_HELPER_NODES: @@ -272,12 +297,56 @@ def is_symtab_method_required(self): return method_required + @property def is_base_class_number_node(self): """ Check if node is of type Number """ return True if self.base_class == NUMBER_NODE else False + def ctor_declaration(self): + args = [] + for c in self.children: + args.append(f'{c.get_typename()} {c.varname}') + return f"{self.class_name}({', '.join(args)});" + + def ctor_definition(self): + args = [] + for c in self.children: + args.append(f'{c.get_typename()} {c.varname}') + initlist = [] + for c in self.children: + initlist.append(f'{c.varname}({c.varname})') + s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) + : {', '.join(initlist)} {{}} + """ + return textwrap.dedent(s) + + def ctor_shrptr_declaration(self): + args = [] + for c in self.children: + args.append(f'{c.member_typename} {c.varname}') + return f"{self.class_name}({', '.join(args)});" + + def ctor_shrptr_definition(self): + args = [] + for c in self.children: + args.append(f'{c.member_typename} {c.varname}') + initlist = [] + for c in self.children: + initlist.append(f'{c.varname}({c.varname})') + s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) + : {', '.join(initlist)} {{}} + """ + return textwrap.dedent(s) + + def has_ptr_children(self): + for c in self.children: + if not (c.is_vector or c.is_base_type_node or c.is_ptr_excluded_node): + return True + return False + + def public_members(self): """ Return public members of the node @@ -303,18 +372,15 @@ def private_members(self): if self.has_token: members.append(["std::shared_ptr<ModToken>", "token"]) - if self.is_symtab_needed(): + if self.is_symtab_needed: members.append(["symtab::SymbolTable*", "symtab = nullptr"]) - if self.is_program_node(): + if self.is_program_node: members.append(["symtab::ModelSymbolTable", "model_symtab;"]) return members - @property - def members(self): - return [(child.get_typename(), child.varname) for child in self.children] @property def non_base_members(self): - return [child for child in self.children if not child.is_base_type_node()] + return [child for child in self.children if not child.is_base_type_node] diff --git a/src/nmodl/language/templates/ast.cpp b/src/nmodl/language/templates/ast.cpp index 5d62cce61d..5fd454c83e 100644 --- a/src/nmodl/language/templates/ast.cpp +++ b/src/nmodl/language/templates/ast.cpp @@ -1,13 +1,6 @@ #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" -{% macro arglist(members) -%} - {%- for type, var in members %} {{ type }} {{ var }} {%- if not loop.last %}, {% endif %} {% endfor -%} -{%- endmacro %} - -{% macro initlist(members) -%} -{%- for type, var in members %} {{ var }}({{ var }}) {%- if not loop.last %}, {% endif %} {% endfor -%} -{%- endmacro %} namespace ast { @@ -24,7 +17,7 @@ namespace ast { if (this->{{ child.varname }}) { this->{{ child.varname }}->accept(v); } - {% elif child.is_pointer_node() %} + {% elif child.is_pointer_node %} {{ child.varname }}->accept(v); {% else %} {{ child.varname }}.accept(v); @@ -33,10 +26,13 @@ namespace ast { {% endfor %} } - {% if node.members %} + {% if node.children %} /* constructor for {{ node.class_name }} ast node */ - {{ node.class_name }}::{{ node.class_name }}({{ arglist(node.members) }}) - : {{ initlist(node.members) }} {} + {{ node.ctor_definition() }} + + {% if node.has_ptr_children() %} + {{ node.ctor_shrptr_definition() }} + {% endif %} /* copy constructor for {{ node.class_name }} ast node */ {{ node.class_name }}::{{ node.class_name }}(const {{ node.class_name }}& obj) { @@ -46,7 +42,7 @@ namespace ast { for (auto& item : obj.{{ child.varname }}) { this->{{ child.varname }}.emplace_back(item->clone()); } - {% elif child.is_pointer_node() or child.optional %} + {% elif child.is_pointer_node or child.optional %} if (obj.{{ child.varname }}) { this->{{ child.varname }}.reset(obj.{{ child.varname }}->clone()); } diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index fb9f940d7f..d102f30305 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -11,11 +11,7 @@ #include "utils/common_utils.hpp" #include "visitors/visitor.hpp" -{% macro ctor_arglist(members) -%} - {%- for type, var in members %} {{ type }} {{ var }} {%- if not loop.last %}, {% endif %} {% endfor -%} -{%- endmacro %} - -{% macro virtual(node) %} +{% macro virtual(node) -%} {% if node.is_abstract %} virtual {% endif %} {% endmacro %} @@ -40,11 +36,14 @@ namespace ast { {% endfor %} {% if node.children %} - {{ node.class_name }}({{ ctor_arglist(node.members) }}); + {{ node.ctor_declaration() }} + {% if node.has_ptr_children() %} + {{ node.ctor_shrptr_declaration() }} + {% endif %} {{ node.class_name }}(const {{ node.class_name }}& obj); {% endif %} - {% if node.is_program_node() or node.is_ptr_excluded_node() %} + {% if node.is_program_node or node.is_ptr_excluded_node %} {{ node.class_name}}() = default; {% endif %} @@ -79,37 +78,37 @@ namespace ast { void set_token(ModToken& tok) { token = std::shared_ptr<ModToken>(new ModToken(tok)); } {% endif %} - {% if node.is_symtab_needed() %} + {% if node.is_symtab_needed %} void set_symbol_table(symtab::SymbolTable* newsymtab) override { symtab = newsymtab; } symtab::SymbolTable* get_symbol_table() override { return symtab; } {% endif %} - {% if node.is_program_node() %} + {% if node.is_program_node %} symtab::ModelSymbolTable* get_model_symbol_table() { return &model_symtab; } {% endif %} - {% if node.is_base_class_number_node() %} + {% if node.is_base_class_number_node %} void negate() override { value = {{ node.negation }}value; } double to_double() override { return value; } {% endif %} - {% if node.is_name_node() %} + {% if node.is_name_node %} {{ virtual(node) }}void set_name(std::string name){{ override(node) }} { value->set(name); } {% endif %} - {% if node.is_number_node() %} + {% if node.is_number_node %} {{ virtual(node) }}double to_double() { throw std::runtime_error("to_double not implemented"); } {% endif %} - {% if node.is_base_block_node() %} + {% if node.is_base_block_node %} virtual ArgumentVector get_parameters() { throw std::runtime_error("get_parameters not implemented"); } {% endif %} - {% if node.is_data_type_node() %} + {% if node.is_data_type_node %} {# if node is of enum type then return enum value #} - {% if node.is_enum_node() %} + {% if node.is_enum_node %} std::string eval() { return {{ node.get_data_type_name() }}Names[value]; } {# But if basic data type then eval return their value #} {% else %} diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/json_visitor.cpp index c0bafa9fd8..9de96c330d 100644 --- a/src/nmodl/language/templates/json_visitor.cpp +++ b/src/nmodl/language/templates/json_visitor.cpp @@ -5,8 +5,8 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* {% if node.has_children() %} printer->push_block(node->get_node_type_name()); node->visit_children(this); - {% if node.is_data_type_node() %} - {% if node.is_integer_node() %} + {% if node.is_data_type_node %} + {% if node.is_integer_node %} if(!node->get_macro_name()) { std::stringstream ss; ss << node->eval(); @@ -19,7 +19,7 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* {% endif %} {% endif %} printer->pop_block(); - {% if node.is_program_node() %} + {% if node.is_program_node %} flush(); {% endif %} {% else %} diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index 281b3374dc..0cdedc4094 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -18,12 +18,12 @@ using namespace ast; {%- macro is_program(n) -%} -{{ "true" if n.is_program_node() else "false" }} +{{ "true" if n.is_program_node else "false" }} {%- endmacro %} {%- macro is_statement(n, c) -%} -{{ "true" if c.is_statement_node() or n.is_unit_block() else "false" }} +{{ "true" if c.is_statement_node or n.is_unit_block else "false" }} {%- endmacro -%} @@ -47,7 +47,7 @@ using namespace ast; {%- else %} {{- add_vector_child(node, child) }} {%- endif %} - {% elif node.is_prime_node() and child.varname == node_info.ORDER_VAR_NAME %} + {% elif node.is_prime_node and child.varname == node_info.ORDER_VAR_NAME %} auto order = node->get_{{ child.varname }}()->eval(); auto symbol = std::string(order, '\''); printer->add_element(symbol); @@ -58,7 +58,7 @@ using namespace ast; printer->add_element(op); {% else %} {% call guard(child.prefix, child.suffix) %} - node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node() else "." }}accept(this); + node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node else "." }}accept(this); {% endcall %} {%- endif %} {%- endmacro -%} @@ -68,11 +68,11 @@ using namespace ast; void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}* node) { {{ add_element(node.nmodl_name) -}} {% call guard(node.prefix, node.suffix) -%} - {% if node.is_block_node() %} + {% if node.is_block_node %} printer->push_level(); {% endif %} - {% if node.is_data_type_node() %} - {% if node.is_integer_node() %} + {% if node.is_data_type_node %} + {% if node.is_integer_node %} if(node->get_macro_name() == nullptr) { printer->add_element(std::to_string(node->eval())); } @@ -84,9 +84,9 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% endif %} {% for child in node.children %} {% call guard(child.force_prefix, child.force_suffix) -%} - {% if child.is_base_type_node() %} + {% if child.is_base_type_node %} {% else %} - {% if child.optional or child.is_statement_block_node() %} + {% if child.optional or child.is_statement_block_node %} if(node->get_{{ child.varname }}()) { {{ add_child(node, child)|trim }} } @@ -97,7 +97,7 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% endcall -%} {% endfor %} {% endcall -%} - {% if node.is_block_node() -%} + {% if node.is_block_node -%} printer->pop_level(); {% endif -%} } diff --git a/src/nmodl/language/templates/symtab_visitor.cpp b/src/nmodl/language/templates/symtab_visitor.cpp index ec135c754d..0b60b732d4 100644 --- a/src/nmodl/language/templates/symtab_visitor.cpp +++ b/src/nmodl/language/templates/symtab_visitor.cpp @@ -3,17 +3,17 @@ #include "visitors/symtab_visitor_helper.hpp" {% for node in nodes %} -{% if node.is_symtab_method_required() and not node.is_symbol_helper_node() %} +{% if node.is_symtab_method_required and not node.is_symbol_helper_node %} {% set typename = node.class_name|snake_case %} {% set propname = "syminfo::NmodlType::" + typename %} void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { - {% if node.is_symbol_var_node() %} + {% if node.is_symbol_var_node %} setup_symbol(node, {{ propname }}); - {% elif node.is_program_node() %} + {% elif node.is_program_node %} setup_symbol_table_for_program_block(node); - {% elif node.is_global_block_node() %} + {% elif node.is_global_block_node %} setup_symbol_table_for_global_block(node); - {% elif node.is_symbol_block_node() %} + {% elif node.is_symbol_block_node %} add_model_symbol_with_property(node, {{ propname }}); setup_symbol_table_for_scoped_block(node, node->get_node_name()); {% else %} diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp index 1ae422bf64..355810e245 100644 --- a/src/nmodl/language/templates/symtab_visitor.hpp +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -38,7 +38,7 @@ class SymtabVisitor : public AstVisitor { void setup_symbol_table_for_scoped_block(Node* node, const std::string& name); {% for node in nodes %} - {% if node.is_symtab_method_required() %} + {% if node.is_symtab_method_required %} void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; {% endif %} {% endfor %} From 55f281627f9eb24038379273c7bbba966b39f2d5 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 21 Jan 2019 17:21:46 +0100 Subject: [PATCH 111/871] Provision for meta information in YAML specification [NOCMODL-70]: - update nmodl yaml specification with url and description attribute - childrens (child classes) are now moved inside children key which allow adding extra keys if needed - parser and ast template updated to show use of description attribute Change-Id: I7ee62cc31bbd5b72e811bcfd278d920b8f2ccc95 NMODL Repo SHA: BlueBrain/nmodl@3d3862e7c93ba11bb6a0d1144d992fa057368d75 --- src/nmodl/language/argument.py | 2 + src/nmodl/language/nmodl.yaml | 2588 +++++++++++++++----------- src/nmodl/language/nodes.py | 12 +- src/nmodl/language/parser.py | 16 +- src/nmodl/language/templates/ast.hpp | 4 +- 5 files changed, 1505 insertions(+), 1117 deletions(-) diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index d0ca7624ae..c34d539d68 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -4,6 +4,7 @@ class Argument: def __init__(self): self.base_class = "" self.class_name = "" + self.description = "" self.nmodl_name = "" self.prefix = "" self.suffix = "" @@ -20,3 +21,4 @@ def __init__(self): self.has_token = False self.getter_method = False self.getter_override = False + self.url = None diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index b71ba556a0..55b1dfe76f 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -71,1115 +71,1487 @@ # specification - AST: - - Node: - - Expression: - - String: - members: - - value: - type: std::string - - Number: - - Integer: - members: - - value: - type: int - - macro_name: - type: Name - optional: true - - Float: - members: - - value: - type: float - - Double: - members: - - value: - type: double - - Boolean: - members: - - value: - type: int - - Identifier: - - Name: - members: - - value: - type: String - node_name: true - - PrimeName: - members: - - value: - type: String - node_name: true - - order: - type: Integer - - VarName: - members: - - name: - type: Identifier - node_name: true - - at_index: - type: Integer - optional: true - prefix: {value: "@"} - - index: - type: Expression - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - IndexedName: - members: - - name: - type: Identifier - node_name: true - - length: - type: Expression - prefix: {value: "["} - suffix: {value: "]"} - - Argument: - members: - - name: - type: Identifier - node_name: true - - unit: - type: Unit - optional: true - - ReactVarName: - members: - - value: - type: Integer - optional: true - prefix: {value: " "} - - name: - type: VarName - node_name: true - - ReadIonVar: - members: - - name: - type: Name - node_name: true - - WriteIonVar: - members: - - name: - type: Name - node_name: true - - NonspeCurVar: - members: - - name: - type: Name - node_name: true - - ElectrodeCurVar: - members: - - name: - type: Name - node_name: true - - SectionVar: - members: - - name: - type: Name - node_name: true - - RangeVar: - members: - - name: - type: Name - node_name: true - - GlobalVar: - members: - - name: - type: Name - node_name: true - - PointerVar: - members: - - name: - type: Name - node_name: true - - BbcorePointerVar: - members: - - name: - type: Name - node_name: true - - ExternVar: - members: - - name: - type: Name - node_name: true - - ThreadsafeVar: - members: - - name: - type: Name - node_name: true - - Block: - - ParamBlock: - nmodl: "PARAMETER " - members: - - statements: - type: ParamAssign - vector: true - - StepBlock: - nmodl: "STEPPED " - members: - - statements: - type: Stepped - vector: true - - IndependentBlock: - nmodl: "INDEPENDENT " - members: - - definitions: - type: IndependentDef - vector: true - - DependentBlock: - nmodl: "ASSIGNED " - members: - - definitions: - type: DependentDef - vector: true - - StateBlock: - nmodl: "STATE " - members: - - definitions: - type: DependentDef - vector: true - - PlotBlock: - members: - - plot: - type: PlotDeclaration - - InitialBlock: - nmodl: "INITIAL " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - ConstructorBlock: - nmodl: "CONSTRUCTOR " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - DestructorBlock: - nmodl: "DESTRUCTOR " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - StatementBlock: - members: - - statements: - type: Statement - vector: true - public: true - - DerivativeBlock: - nmodl: "DERIVATIVE " - members: - - name: - type: Name - node_name: true - suffix: {value: " "} - - statement_block: - type: StatementBlock - getter: {override: true} - - LinearBlock: - nmodl: "LINEAR " - members: - - name: - type: Name - node_name: true - suffix: {value: " "} - - solvefor: - type: Name - vector: true - separator: "," - prefix: {value: " SOLVEFOR "} - - statement_block: - type: StatementBlock - getter: {override: true} - - NonLinearBlock: - nmodl: "NONLINEAR " - members: - - name: - type: Name - node_name: true - - solvefor: - type: Name - vector: true - separator: "," - prefix: {value: " SOLVEFOR "} - suffix: {value: " ", force: true} - - statement_block: - type: StatementBlock - getter: {override: true} - - DiscreteBlock: - nmodl: "DISCRETE " - members: - - name: - type: Name - node_name: true - suffix: {value: " "} - - statement_block: - type: StatementBlock - getter: {override: true} - - PartialBlock: - nmodl: "PARTIAL " - members: - - name: - type: Name - node_name: true - suffix: {value: " "} - - statement_block: - type: StatementBlock - getter: {override: true} - - FunctionTableBlock: - nmodl: "FUNCTION_TABLE " - members: - - name: - type: Name - node_name: true - - parameters: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - separator: ", " - getter: {override: true} - - unit: - type: Unit - optional: true - prefix: {value: " "} - - FunctionBlock: - nmodl: "FUNCTION " - members: - - name: - type: Name - node_name: true - - parameters: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - separator: ", " - getter: {override: true} - - unit: - type: Unit - optional: true - prefix: {value: " "} - suffix: {value: " ", force: true} - - statement_block: - type: StatementBlock - getter: {override: true} - - ProcedureBlock: - nmodl: "PROCEDURE " - members: - - name: - type: Name - node_name: true - - parameters: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ") ", force: true} - separator: ", " - getter: {override: true} - - unit: - type: Unit - optional: true - - statement_block: - type: StatementBlock - getter: {override: true} - - NetReceiveBlock: - nmodl: "NET_RECEIVE " - members: - - parameters: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ") ", force: true} - separator: ", " - getter: {override: true} - - statement_block: - type: StatementBlock - getter: {override: true} - - SolveBlock: - nmodl: SOLVE - members: - - block_name: - type: Name - prefix: {value: " "} - - method: - type: Name - optional: true - prefix: {value: " METHOD "} - - ifsolerr: - type: StatementBlock - optional: true - prefix: {value: " IFERROR "} - - BreakpointBlock: - nmodl: "BREAKPOINT " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - TerminalBlock: - nmodl: "TERMINAL " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - BeforeBlock: - nmodl: "BEFORE " - members: - - block: - type: BABlock - - AfterBlock: - nmodl: "AFTER " - members: - - block: - type: BABlock - - BABlock: - members: - - type: - type: BABlockType - suffix: {value: " "} - - statement_block: - type: StatementBlock - getter: {override: true} - - ForNetcon: - nmodl: "FOR_NETCONS " - members: - - parameters: - type: Argument - vector: true - prefix: {value: "(", force: true} - suffix: {value: ") ", force: true} - separator: ", " - getter: {override: true} - - statement_block: - type: StatementBlock - getter: {override: true} - - KineticBlock: - nmodl: "KINETIC " - members: - - name: - type: Name - node_name: true - suffix: {value: " "} - - solvefor: - type: Name - vector: true - separator: "," - - statement_block: - type: StatementBlock - getter: {override: true} - - MatchBlock: - nmodl: MATCH - members: - - matchs: - type: Match - vector: true - separator: " " - prefix: {value: " { "} - suffix: {value: " }"} - - UnitBlock: - nmodl: "UNITS " - members: - - definitions: - type: Expression - vector: true - - ConstantBlock: - nmodl: "CONSTANT " - members: - - statements: - type: ConstantStatement - vector: true - - NeuronBlock: - nmodl: "NEURON " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - Unit: - members: - - name: - type: String - node_name: true - prefix: {value: "("} - suffix: {value: ")"} - - DoubleUnit: - members: - - values: - type: Double - - unit: - type: Unit - optional: true - - LocalVar: - members: - - name: - type: Identifier - node_name: true - - Limits: - members: - - min: - type: Double - prefix: {value: "<"} - suffix: {value: ","} - - max: - type: Double - suffix: {value: ">"} - - NumberRange: - members: - - min: - type: Number - prefix: {value: "<"} - suffix: {value: ","} - - max: - type: Number - suffix: {value: ">"} + description: "Base class for AST" + children: + - Node: + description: "Base class for node in the AST" + children: + - Expression: + description: "Represents a(n) expression" + children: + - String: + description: "Represents a(n) double quoted string" + members: + - value: + description: "string value" + type: std::string + - Number: + description: "Represents a(n) number" + children: + - Integer: + description: "Represents a(n) integer number" + members: + - value: + description: "integer value" + type: int + - macro_name: + description: "macro name for integer" + type: Name + optional: true + - Float: + description: "Represents a(n) float number" + members: + - value: + description: "float value" + type: float + - Double: + description: "Represents a(n) double number" + members: + - value: + description: "double value" + type: double + - Boolean: + description: "Represents a(n) boolean variable" + members: + - value: + description: "bool value" + type: int + - Identifier: + description: "Represents a(n) identifier" + children: + - Name: + description: "Represents a(n) name" + members: + - value: + description: "string value" + type: String + node_name: true + - PrimeName: + description: "Represents a(n) prime variable" + members: + - value: + description: "variable name" + type: String + node_name: true + - order: + description: "order of prime" + type: Integer + - VarName: + description: "Represents a(n) NMODL variable" + members: + - name: + description: "variable name" + type: Identifier + node_name: true + - at_index: + description: "todo" + type: Integer + optional: true + prefix: {value: "@"} + - index: + description: "index value in case of array" + type: Expression + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - IndexedName: + description: ".." + members: + - name: + description: "..." + type: Identifier + node_name: true + - length: + description: "..." + type: Expression + prefix: {value: "["} + suffix: {value: "]"} + - Argument: + description: ".." + members: + - name: + description: "..." + type: Identifier + node_name: true + - unit: + description: "..." + type: Unit + optional: true + - ReactVarName: + description: ".." + members: + - value: + description: "..." + type: Integer + optional: true + prefix: {value: " "} + - name: + description: "..." + type: VarName + node_name: true + - ReadIonVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - WriteIonVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - NonspeCurVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - ElectrodeCurVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - SectionVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - RangeVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - GlobalVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - PointerVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - BbcorePointerVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - ExternVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - ThreadsafeVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - Block: + description: ".." + children: + - ParamBlock: + description: ".." + nmodl: "PARAMETER " + members: + - statements: + description: "..." + type: ParamAssign + vector: true + - StepBlock: + description: ".." + nmodl: "STEPPED " + members: + - statements: + description: "..." + type: Stepped + vector: true + - IndependentBlock: + description: ".." + nmodl: "INDEPENDENT " + members: + - definitions: + description: "..." + type: IndependentDef + vector: true + - DependentBlock: + description: ".." + nmodl: "ASSIGNED " + members: + - definitions: + description: "..." + type: DependentDef + vector: true + - StateBlock: + description: ".." + nmodl: "STATE " + members: + - definitions: + description: "..." + type: DependentDef + vector: true + - PlotBlock: + description: ".." + members: + - plot: + description: "..." + type: PlotDeclaration + - InitialBlock: + description: ".." + nmodl: "INITIAL " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - ConstructorBlock: + description: ".." + nmodl: "CONSTRUCTOR " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - DestructorBlock: + description: ".." + nmodl: "DESTRUCTOR " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - StatementBlock: + description: ".." + members: + - statements: + description: "..." + type: Statement + vector: true + public: true + - DerivativeBlock: + description: ".." + nmodl: "DERIVATIVE " + members: + - name: + description: "..." + type: Name + node_name: true + suffix: {value: " "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - LinearBlock: + description: ".." + nmodl: "LINEAR " + members: + - name: + description: "..." + type: Name + node_name: true + suffix: {value: " "} + - solvefor: + description: "..." + type: Name + vector: true + separator: "," + prefix: {value: " SOLVEFOR "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - NonLinearBlock: + description: ".." + nmodl: "NONLINEAR " + members: + - name: + description: "..." + type: Name + node_name: true + - solvefor: + description: "..." + type: Name + vector: true + separator: "," + prefix: {value: " SOLVEFOR "} + suffix: {value: " ", force: true} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - DiscreteBlock: + description: ".." + nmodl: "DISCRETE " + members: + - name: + description: "..." + type: Name + node_name: true + suffix: {value: " "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - PartialBlock: + description: ".." + nmodl: "PARTIAL " + members: + - name: + description: "..." + type: Name + node_name: true + suffix: {value: " "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - FunctionTableBlock: + description: ".." + nmodl: "FUNCTION_TABLE " + members: + - name: + description: "..." + type: Name + node_name: true + - parameters: + description: "..." + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + getter: {override: true} + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - FunctionBlock: + description: ".." + nmodl: "FUNCTION " + members: + - name: + description: "..." + type: Name + node_name: true + - parameters: + description: "..." + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + separator: ", " + getter: {override: true} + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + suffix: {value: " ", force: true} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - ProcedureBlock: + description: ".." + nmodl: "PROCEDURE " + members: + - name: + description: "..." + type: Name + node_name: true + - parameters: + description: "..." + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ") ", force: true} + separator: ", " + getter: {override: true} + - unit: + description: "..." + type: Unit + optional: true + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - NetReceiveBlock: + description: ".." + nmodl: "NET_RECEIVE " + members: + - parameters: + description: "..." + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ") ", force: true} + separator: ", " + getter: {override: true} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - SolveBlock: + description: ".." + nmodl: SOLVE + members: + - block_name: + description: "..." + type: Name + prefix: {value: " "} + - method: + description: "..." + type: Name + optional: true + prefix: {value: " METHOD "} + - ifsolerr: + description: "..." + type: StatementBlock + optional: true + prefix: {value: " IFERROR "} + - BreakpointBlock: + description: ".." + nmodl: "BREAKPOINT " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - TerminalBlock: + description: ".." + nmodl: "TERMINAL " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - BeforeBlock: + description: ".." + nmodl: "BEFORE " + members: + - block: + description: "..." + type: BABlock + - AfterBlock: + description: ".." + nmodl: "AFTER " + members: + - block: + description: "..." + type: BABlock + - BABlock: + description: ".." + members: + - type: + description: "..." + type: BABlockType + suffix: {value: " "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - ForNetcon: + description: ".." + nmodl: "FOR_NETCONS " + members: + - parameters: + description: "..." + type: Argument + vector: true + prefix: {value: "(", force: true} + suffix: {value: ") ", force: true} + separator: ", " + getter: {override: true} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - KineticBlock: + description: ".." + nmodl: "KINETIC " + members: + - name: + description: "..." + type: Name + node_name: true + suffix: {value: " "} + - solvefor: + description: "..." + type: Name + vector: true + separator: "," + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - MatchBlock: + description: ".." + nmodl: MATCH + members: + - matchs: + description: "..." + type: Match + vector: true + separator: " " + prefix: {value: " { "} + suffix: {value: " }"} + - UnitBlock: + description: ".." + nmodl: "UNITS " + members: + - definitions: + description: "..." + type: Expression + vector: true + - ConstantBlock: + description: ".." + nmodl: "CONSTANT " + members: + - statements: + description: "..." + type: ConstantStatement + vector: true + - NeuronBlock: + description: ".." + url: "https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl2.html#neuron" + nmodl: "NEURON " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - Unit: + description: ".." + members: + - name: + description: "..." + type: String + node_name: true + prefix: {value: "("} + suffix: {value: ")"} + - DoubleUnit: + description: ".." + members: + - values: + description: "..." + type: Double + - unit: + description: "..." + type: Unit + optional: true + - LocalVar: + description: ".." + members: + - name: + description: "..." + type: Identifier + node_name: true + - Limits: + description: ".." + members: + - min: + description: "..." + type: Double + prefix: {value: "<"} + suffix: {value: ","} + - max: + description: "..." + type: Double + suffix: {value: ">"} + - NumberRange: + description: ".." + members: + - min: + description: "..." + type: Number + prefix: {value: "<"} + suffix: {value: ","} + - max: + description: "..." + type: Number + suffix: {value: ">"} + - PlotVar: + description: ".." + members: + - name: + description: "..." + type: Identifier + - index: + description: "..." + type: Integer + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - ConstantVar: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - value: + description: "..." + type: Number + prefix: {value: " = "} + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - BinaryOperator: + description: ".." + members: + - value: + description: "..." + type: BinaryOp + - UnaryOperator: + description: ".." + members: + - value: + description: "..." + type: UnaryOp + - ReactionOperator: + description: ".." + members: + - value: + description: "..." + type: ReactionOp + - WrappedExpression: + description: ".." + members: + - expression: + description: "..." + type: Expression + - ParenExpression: + description: ".." + members: + - expr: + description: "..." + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - BinaryExpression: + description: ".." + members: + - lhs: + description: "..." + type: Expression + public: true + - op: + description: "..." + type: BinaryOperator + public: true + - rhs: + description: "..." + type: Expression + public: true + - UnaryExpression: + description: ".." + members: + - op: + description: "..." + type: UnaryOperator + - expression: + description: "..." + type: Expression + - NonLinEquation: + description: ".." + nmodl: "~ " + members: + - lhs: + description: "..." + type: Expression + suffix: {value: " = "} + - rhs: + description: "..." + type: Expression + - LinEquation: + description: ".." + nmodl: "~ " + members: + - leftlinexpr: + description: "..." + type: Expression + suffix: {value: " = "} + - linexpr: + description: "..." + type: Expression + - FunctionCall: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + - arguments: + description: "..." + type: Expression + vector: true + separator: ", " + prefix: {value: "(", force: true} + suffix: {value: ")", force: true} + - FirstLastTypeIndex: + description: ".." + members: + - value: + description: "..." + type: FirstLastType + - Watch: + description: ".." + members: + - expression: + description: "..." + type: Expression + prefix: {value: "("} + suffix: {value: ")"} + - value: + description: "..." + type: Expression + prefix: {value: " "} + - QueueExpressionType: + description: ".." + members: + - value: + description: "..." + type: QueueType + - Match: + description: ".." + members: + - name: + description: "..." + type: Identifier + - expression: + description: "..." + type: Expression + optional: true + - BABlockType: + description: ".." + members: + - value: + description: "..." + type: BAType + - UnitDef: + description: ".." + members: + - unit1: + description: "..." + type: Unit + node_name: true + - unit2: + description: "..." + type: Unit + prefix: {value: " = "} + - FactorDef: + description: ".." + members: + - name: + description: "..." + type: Name + node_name: true + suffix: {value: " ="} + - value: + description: "..." + type: Double + optional: true + prefix: {value: " "} + - unit1: + description: "..." + type: Unit + prefix: {value: " "} + - gt: + description: "..." + type: Boolean + nmodl: " ->" + optional: true + - unit2: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - Valence: + description: ".." + members: + - type: + type: Name + description: "..." + prefix: {value: " "} + suffix: {value: " "} + - value: + description: "..." + type: Double - - PlotVar: - members: - - name: - type: Identifier - - index: - type: Integer - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - ConstantVar: - members: - - name: - type: Name - node_name: true - - value: - type: Number - prefix: {value: " = "} - - unit: - type: Unit - optional: true - prefix: {value: " "} - - BinaryOperator: - members: - - value: - type: BinaryOp - - UnaryOperator: - members: - - value: - type: UnaryOp - - ReactionOperator: - members: - - value: - type: ReactionOp - - WrappedExpression: - members: - - expression: - type: Expression - - ParenExpression: - members: - - expr: - type: Expression - prefix: {value: "("} - suffix: {value: ")"} - - BinaryExpression: - members: - - lhs: - type: Expression - public: true - - op: - type: BinaryOperator - public: true - - rhs: - type: Expression - public: true - - UnaryExpression: - members: - - op: - type: UnaryOperator - - expression: - type: Expression - - NonLinEquation: - nmodl: "~ " - members: - - lhs: - type: Expression - suffix: {value: " = "} - - rhs: - type: Expression - - LinEquation: - nmodl: "~ " - members: - - leftlinexpr: - type: Expression - suffix: {value: " = "} - - linexpr: - type: Expression - - FunctionCall: - members: - - name: - type: Name - node_name: true - - arguments: - type: Expression - vector: true - separator: ", " - prefix: {value: "(", force: true} - suffix: {value: ")", force: true} - - FirstLastTypeIndex: - members: - - value: - type: FirstLastType - - Watch: - members: - - expression: - type: Expression - prefix: {value: "("} - suffix: {value: ")"} - - value: - type: Expression - prefix: {value: " "} - - QueueExpressionType: - members: - - value: - type: QueueType - - Match: - members: - - name: - type: Identifier - - expression: - type: Expression - optional: true - - BABlockType: - members: - - value: - type: BAType - - UnitDef: - members: - - unit1: - type: Unit - node_name: true - - unit2: - type: Unit - prefix: {value: " = "} - - FactorDef: - members: - - name: - type: Name - node_name: true - suffix: {value: " ="} - - value: - type: Double - optional: true - prefix: {value: " "} - - unit1: - type: Unit - prefix: {value: " "} - - gt: - type: Boolean - nmodl: " ->" - optional: true - - unit2: - type: Unit - optional: true - prefix: {value: " "} - - Valence: - members: - - type: - type: Name - prefix: {value: " "} - suffix: {value: " "} - - value: - type: Double - - - Statement: - - UnitState: - members: - - value: - type: UnitStateType - - LocalListStatement: - nmodl: "LOCAL " - members: - - variables: - type: LocalVar - vector: true - separator: ", " - public: true - - Model: - nmodl: TITLE - members: - - title: - type: String - - Define: - nmodl: "DEFINE " - members: - - name: - type: Name - - value: - type: Integer - prefix: {value: " "} - - Include: - nmodl: "INCLUDE " - members: - - filename: - type: String - - ParamAssign: - members: - - name: - type: Identifier - node_name: true - - value: - type: Number - optional: true - prefix: {value: " = "} - - unit: - type: Unit - optional: true - prefix: {value: " "} - - limit: - type: Limits - optional: true - prefix: {value: " "} - - Stepped: - members: - - name: - type: Name - - values: - type: Number - vector: true - prefix: {value: " = "} - separator: ", " - - unit: - type: Unit - optional: true - prefix: {value: " "} - - IndependentDef: - members: - - sweep: - type: Boolean - optional: true - nmodl: "SWEEP " - - name: - type: Name - - from: - type: Number - prefix: {value: " FROM "} - - to: - type: Number - prefix: {value: " TO "} - - with: - type: Integer - prefix: {value: " WITH "} - - opstart: - type: Number - prefix: {value: " START "} - optional: true - - unit: - type: Unit - optional: true - prefix: {value: " "} - - DependentDef: - members: - - name: - type: Identifier - node_name: true - - length: - type: Integer - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - from: - type: Number - prefix: {value: " FROM "} - optional: true - - to: - type: Number - prefix: {value: " TO "} - optional: true - - opstart: - type: Number - prefix: {value: " START "} - optional: true - - unit: - type: Unit - optional: true - prefix: {value: " "} - - abstol: - type: Double - prefix: {value: " <"} - suffix: {value: ">"} - optional: true - - PlotDeclaration: - nmodl: "PLOT " - members: - - pvlist: - type: PlotVar - vector: true - separator: ", " - - name: - type: PlotVar - prefix: {value: " VS "} - - ConductanceHint: - nmodl: "CONDUCTANCE " - members: - - conductance: - type: Name - - ion: - type: Name - optional: true - prefix: {value: " USEION "} - - ExpressionStatement: - members: - - expression: - type: Expression - - ProtectStatement: - nmodl: "PROTECT " - members: - - expression: - type: Expression - - FromStatement: - nmodl: "FROM " - members: - - name: - type: Name - node_name: true - - from: - type: Expression - prefix: {value: " = "} - - to: - type: Expression - prefix: {value: " TO "} - - opinc: - type: Expression - prefix: {value: " BY "} - suffix: {value: " ", force: true} - optional: true - - statement_block: - type: StatementBlock - getter: {override: true} - - ForAllStatement: - nmodl: "FORALL " - members: - - name: - type: Name - suffix: {value: " "} - - statement_block: - type: StatementBlock - getter: {override: true} - - WhileStatement: - nmodl: "WHILE " - members: - - condition: - type: Expression - prefix: {value: "("} - suffix: {value: ") "} - - statement_block: - type: StatementBlock - getter: {override: true} - - IfStatement: - nmodl: "IF " - members: - - condition: - type: Expression - prefix: {value: "("} - suffix: {value: ") "} - - statement_block: - type: StatementBlock - getter: {override: true} - - elseifs: - type: ElseIfStatement - vector: true - - elses: - type: ElseStatement - optional: true - - ElseIfStatement: - nmodl: " ELSE IF " - members: - - condition: - type: Expression - prefix: {value: "("} - suffix: {value: ") "} - - statement_block: - type: StatementBlock - getter: {override: true} - - ElseStatement: - nmodl: " ELSE " - members: - - statement_block: - type: StatementBlock - getter: {override: true} - - PartialEquation: - members: - - prime: - type: PrimeName - - name1: - type: Name - - name2: - type: Name - - name3: - type: Name - - PartialBoundary: - nmodl: "~ " - members: - - del: - type: Name - optional: true - suffix: {value: " "} - - name: - type: Identifier - - index: - type: FirstLastTypeIndex - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - expression: - type: Expression - optional: true - prefix: {value: " = "} - - name1: - type: Name - optional: true - prefix: {value: " = "} - suffix: {value: "*"} - - del2: - type: Name - optional: true - suffix: {value: "("} - - name2: - type: Name - optional: true - suffix: {value: ")"} - - name3: - type: Name - optional: true - prefix: {value: "+"} - - WatchStatement: - nmodl: "WATCH " - members: - - statements: - type: Watch - vector: true - separator: "," - add: true - - MutexLock: - nmodl: MUTEXLOCK - - MutexUnlock: - nmodl: MUTEXUNLOCK - - Reset: - nmodl: RESET - - Sens: - nmodl: "SENS " - members: - - senslist: - type: VarName - vector: true - separator: ", " - - Conserve: - nmodl: CONSERVE - members: - - react: - type: Expression - prefix: {value: " "} - - expr: - type: Expression - prefix: {value: " = "} - - Compartment: - nmodl: COMPARTMENT - members: - - name: - type: Name - optional: true - prefix: {value: " "} - suffix: {value: ","} - - expression: - type: Expression - prefix: {value: " "} - - names: - type: Name - vector: true - prefix: {value: " {"} - suffix: {value: "}"} - separator: " " - - LonDifuse: - nmodl: LONGITUDINAL_DIFFUSION - members: - - name: - type: Name - optional: true - prefix: {value: " "} - suffix: {value: ","} - - expression: - type: Expression - prefix: {value: " "} - - names: - type: Name - vector: true - prefix: {value: " {"} - suffix: {value: "}"} - separator: " " - - ReactionStatement: - nmodl: "~ " - members: - - react1: - type: Expression - - op: - type: ReactionOperator - prefix: {value: " "} - - react2: - type: Expression - prefix: {value: " "} - optional: true - - expr1: - type: Expression - prefix: {value: " ("} - - expr2: - type: Expression - prefix: {value: ", "} - suffix: {value: ")", force: true} - optional: true - - LagStatement: - nmodl: "LAG " - members: - - name: - type: Identifier - - byname: - type: Name - prefix: {value: " BY "} - - QueueStatement: - members: - - qype: - type: QueueExpressionType - - name: - type: Identifier - prefix: {value: " "} - - ConstantStatement: - members: - - constant: - type: ConstantVar - - TableStatement: - nmodl: "TABLE " - members: - - table_vars: - type: Name - vector: true - separator: "," - - depend_vars: - type: Name - vector: true - prefix: {value: " DEPEND "} - separator: "," - - from: - type: Expression - prefix: {value: " FROM "} - - to: - type: Expression - prefix: {value: " TO "} - - with: - type: Integer - prefix: {value: " WITH "} - - Suffix: - members: - - type: - type: Name - suffix: {value: " "} - - name: - type: Name - node_name: true - - Useion: - nmodl: "USEION " - members: - - name: - type: Name - node_name: true - - readlist: - type: ReadIonVar - vector: true - prefix: {value: " READ "} - separator: ", " - - writelist: - type: WriteIonVar - vector: true - prefix: {value: " WRITE "} - separator: ", " - - valence: - type: Valence - optional: true - - Nonspecific: - nmodl: "NONSPECIFIC_CURRENT " - members: - - currents: - type: NonspeCurVar - vector: true - separator: ", " - - ElctrodeCurrent: - nmodl: "ELECTRODE_CURRENT " - members: - - ecurrents: - type: ElectrodeCurVar - vector: true - separator: ", " - - Section: - nmodl: "SECTION " - members: - - sections: - type: SectionVar - vector: true - separator: ", " - - Range: - nmodl: "RANGE " - members: - - range_vars: - type: RangeVar - vector: true - separator: ", " - - Global: - nmodl: "GLOBAL " - members: - - global_vars: - type: GlobalVar - vector: true - separator: ", " - - Pointer: - nmodl: "POINTER " - members: - - pointers: - type: PointerVar - vector: true - separator: ", " - - BbcorePtr: - nmodl: "BBCOREPOINTER " - members: - - bbcore_pointers: - type: BbcorePointerVar - vector: true - separator: ", " - - External: - nmodl: "EXTERNAL " - members: - - externals: - type: ExternVar - vector: true - separator: ", " - - ThreadSafe: - nmodl: THREADSAFE - members: - - threadsafe: - type: ThreadsafeVar - vector: true - separator: ", " - prefix: {value: " "} - - Verbatim: - nmodl: VERBATIM - members: - - statement: - type: String - suffix: {value: "ENDVERBATIM"} - - Comment: - members: - - block_comment: - type: String - prefix: {value: "COMMENT"} - suffix: {value: "ENDCOMMENT"} - optional: true - - line_comment: - type: String - optional: true + - Statement: + description: ".." + children: + - UnitState: + description: ".." + members: + - value: + description: "..." + type: UnitStateType + - LocalListStatement: + description: ".." + nmodl: "LOCAL " + members: + - variables: + description: "..." + type: LocalVar + vector: true + separator: ", " + public: true + - Model: + description: ".." + nmodl: TITLE + members: + - title: + description: "..." + type: String + - Define: + description: ".." + nmodl: "DEFINE " + members: + - name: + description: "..." + type: Name + - value: + description: "..." + type: Integer + prefix: {value: " "} + - Include: + description: ".." + nmodl: "INCLUDE " + members: + - filename: + description: "..." + type: String + - ParamAssign: + description: ".." + members: + - name: + description: "..." + type: Identifier + node_name: true + - value: + description: "..." + type: Number + optional: true + prefix: {value: " = "} + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - limit: + description: "..." + type: Limits + optional: true + prefix: {value: " "} + - Stepped: + description: ".." + members: + - name: + description: "..." + type: Name + - values: + description: "..." + type: Number + vector: true + prefix: {value: " = "} + separator: ", " + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - IndependentDef: + description: ".." + members: + - sweep: + description: "..." + type: Boolean + optional: true + nmodl: "SWEEP " + - name: + description: "..." + type: Name + - from: + description: "..." + type: Number + prefix: {value: " FROM "} + - to: + description: "..." + type: Number + prefix: {value: " TO "} + - with: + description: "..." + type: Integer + prefix: {value: " WITH "} + - opstart: + description: "..." + type: Number + prefix: {value: " START "} + optional: true + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - DependentDef: + description: ".." + members: + - name: + description: "..." + type: Identifier + node_name: true + - length: + description: "..." + type: Integer + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - from: + description: "..." + type: Number + prefix: {value: " FROM "} + optional: true + - to: + description: "..." + type: Number + prefix: {value: " TO "} + optional: true + - opstart: + description: "..." + type: Number + prefix: {value: " START "} + optional: true + - unit: + description: "..." + type: Unit + optional: true + prefix: {value: " "} + - abstol: + description: "..." + type: Double + prefix: {value: " <"} + suffix: {value: ">"} + optional: true + - PlotDeclaration: + description: ".." + nmodl: "PLOT " + members: + - pvlist: + description: "..." + type: PlotVar + vector: true + separator: ", " + - name: + description: "..." + type: PlotVar + prefix: {value: " VS "} + - ConductanceHint: + description: ".." + nmodl: "CONDUCTANCE " + members: + - conductance: + description: "..." + type: Name + - ion: + description: "..." + type: Name + optional: true + prefix: {value: " USEION "} + - ExpressionStatement: + description: ".." + members: + - expression: + description: "..." + type: Expression + - ProtectStatement: + description: ".." + nmodl: "PROTECT " + members: + - expression: + description: "..." + type: Expression + - FromStatement: + description: ".." + nmodl: "FROM " + members: + - name: + description: "..." + type: Name + node_name: true + - from: + description: "..." + type: Expression + prefix: {value: " = "} + - to: + description: "..." + type: Expression + prefix: {value: " TO "} + - opinc: + description: "..." + type: Expression + prefix: {value: " BY "} + suffix: {value: " ", force: true} + optional: true + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - ForAllStatement: + description: ".." + nmodl: "FORALL " + members: + - name: + description: "..." + type: Name + suffix: {value: " "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - WhileStatement: + description: ".." + nmodl: "WHILE " + members: + - condition: + description: "..." + type: Expression + prefix: {value: "("} + suffix: {value: ") "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - IfStatement: + description: ".." + nmodl: "IF " + members: + - condition: + description: "..." + type: Expression + prefix: {value: "("} + suffix: {value: ") "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - elseifs: + description: "..." + type: ElseIfStatement + vector: true + - elses: + description: "..." + type: ElseStatement + optional: true + - ElseIfStatement: + description: ".." + nmodl: " ELSE IF " + members: + - condition: + description: "..." + type: Expression + prefix: {value: "("} + suffix: {value: ") "} + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - ElseStatement: + description: ".." + nmodl: " ELSE " + members: + - statement_block: + description: "..." + type: StatementBlock + getter: {override: true} + - PartialEquation: + description: ".." + members: + - prime: + description: "..." + type: PrimeName + - name1: + description: "..." + type: Name + - name2: + description: "..." + type: Name + - name3: + description: "..." + type: Name + - PartialBoundary: + description: ".." + nmodl: "~ " + members: + - del: + description: "..." + type: Name + optional: true + suffix: {value: " "} + - name: + description: "..." + type: Identifier + - index: + description: "..." + type: FirstLastTypeIndex + optional: true + prefix: {value: "["} + suffix: {value: "]"} + - expression: + description: "..." + type: Expression + optional: true + prefix: {value: " = "} + - name1: + description: "..." + type: Name + optional: true + prefix: {value: " = "} + suffix: {value: "*"} + - del2: + description: "..." + type: Name + optional: true + suffix: {value: "("} + - name2: + description: "..." + type: Name + optional: true + suffix: {value: ")"} + - name3: + description: "..." + type: Name + optional: true + prefix: {value: "+"} + - WatchStatement: + description: ".." + nmodl: "WATCH " + members: + - statements: + description: "..." + type: Watch + vector: true + separator: "," + add: true + - MutexLock: + description: ".." + nmodl: MUTEXLOCK + - MutexUnlock: + description: ".." + nmodl: MUTEXUNLOCK + - Reset: + description: ".." + nmodl: RESET + - Sens: + description: ".." + nmodl: "SENS " + members: + - senslist: + description: "..." + type: VarName + vector: true + separator: ", " + - Conserve: + description: ".." + nmodl: CONSERVE + members: + - react: + description: "..." + type: Expression + prefix: {value: " "} + - expr: + description: "..." + type: Expression + prefix: {value: " = "} + - Compartment: + description: ".." + nmodl: COMPARTMENT + members: + - name: + description: "..." + type: Name + optional: true + prefix: {value: " "} + suffix: {value: ","} + - expression: + description: "..." + type: Expression + prefix: {value: " "} + - names: + description: "..." + type: Name + vector: true + prefix: {value: " {"} + suffix: {value: "}"} + separator: " " + - LonDifuse: + description: ".." + nmodl: LONGITUDINAL_DIFFUSION + members: + - name: + description: "..." + type: Name + optional: true + prefix: {value: " "} + suffix: {value: ","} + - expression: + description: "..." + type: Expression + prefix: {value: " "} + - names: + description: "..." + type: Name + vector: true + prefix: {value: " {"} + suffix: {value: "}"} + separator: " " + - ReactionStatement: + description: ".." + nmodl: "~ " + members: + - react1: + description: "..." + type: Expression + - op: + description: "..." + type: ReactionOperator + prefix: {value: " "} + - react2: + description: "..." + type: Expression + prefix: {value: " "} + optional: true + - expr1: + description: "..." + type: Expression + prefix: {value: " ("} + - expr2: + description: "..." + type: Expression + prefix: {value: ", "} + suffix: {value: ")", force: true} + optional: true + - LagStatement: + description: ".." + nmodl: "LAG " + members: + - name: + description: "..." + type: Identifier + - byname: + description: "..." + type: Name + prefix: {value: " BY "} + - QueueStatement: + description: ".." + members: + - qype: + description: "..." + type: QueueExpressionType + - name: + description: "..." + type: Identifier + prefix: {value: " "} + - ConstantStatement: + description: ".." + members: + - constant: + description: "..." + type: ConstantVar + - TableStatement: + description: ".." + nmodl: "TABLE " + members: + - table_vars: + description: "..." + type: Name + vector: true + separator: "," + - depend_vars: + description: "..." + type: Name + vector: true + prefix: {value: " DEPEND "} + separator: "," + - from: + description: "..." + type: Expression + prefix: {value: " FROM "} + - to: + description: "..." + type: Expression + prefix: {value: " TO "} + - with: + description: "..." + type: Integer + prefix: {value: " WITH "} + - Suffix: + description: ".." + members: + - type: + description: "..." + type: Name + suffix: {value: " "} + - name: + description: "..." + type: Name + node_name: true + - Useion: + description: ".." + nmodl: "USEION " + members: + - name: + description: "..." + type: Name + node_name: true + - readlist: + description: "..." + type: ReadIonVar + vector: true + prefix: {value: " READ "} + separator: ", " + - writelist: + description: "..." + type: WriteIonVar + vector: true + prefix: {value: " WRITE "} + separator: ", " + - valence: + description: "..." + type: Valence + optional: true + - Nonspecific: + description: ".." + nmodl: "NONSPECIFIC_CURRENT " + members: + - currents: + description: "..." + type: NonspeCurVar + vector: true + separator: ", " + - ElctrodeCurrent: + description: ".." + nmodl: "ELECTRODE_CURRENT " + members: + - ecurrents: + description: "..." + type: ElectrodeCurVar + vector: true + separator: ", " + - Section: + description: ".." + nmodl: "SECTION " + members: + - sections: + description: "..." + type: SectionVar + vector: true + separator: ", " + - Range: + description: ".." + nmodl: "RANGE " + members: + - range_vars: + description: "..." + type: RangeVar + vector: true + separator: ", " + - Global: + description: ".." + nmodl: "GLOBAL " + members: + - global_vars: + description: "..." + type: GlobalVar + vector: true + separator: ", " + - Pointer: + description: ".." + nmodl: "POINTER " + members: + - pointers: + description: "..." + type: PointerVar + vector: true + separator: ", " + - BbcorePtr: + description: ".." + nmodl: "BBCOREPOINTER " + members: + - bbcore_pointers: + description: "..." + type: BbcorePointerVar + vector: true + separator: ", " + - External: + description: ".." + nmodl: "EXTERNAL " + members: + - externals: + description: "..." + type: ExternVar + vector: true + separator: ", " + - ThreadSafe: + description: ".." + nmodl: THREADSAFE + members: + - threadsafe: + description: "..." + type: ThreadsafeVar + vector: true + separator: ", " + prefix: {value: " "} + - Verbatim: + description: "Represents a(n) C code block" + nmodl: VERBATIM + members: + - statement: + description: "C code as a string" + type: String + suffix: {value: "ENDVERBATIM"} + - Comment: + description: "Represents a(n) comment in the NMODL" + members: + - block_comment: + description: "Multi-line block comment" + type: String + prefix: {value: "COMMENT"} + suffix: {value: "ENDCOMMENT"} + optional: true + - line_comment: + description: "Single line comment" + type: String + optional: true - Program: + description: "Represents a(n) whole AST node for input NMODL file" members: - - blocks: - type: Node - vector: true - add: true - public: true + - blocks: + description: "..." + type: Node + vector: true + add: true + public: true diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 569583d78e..cf0d6ce353 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -20,6 +20,7 @@ def __init__(self, args): self.separator = args.separator self.force_prefix = args.force_prefix self.force_suffix = args.force_suffix + self.description = args.description self.is_abstract = False def __lt__(self, other): @@ -216,6 +217,7 @@ def __init__(self, args): BaseNode.__init__(self, args) self.base_class = args.base_class self.has_token = args.has_token + self.url = args.url self.children = [] @property @@ -355,7 +357,7 @@ def public_members(self): for child in self.children: if child.is_public: - members.append([child.member_typename, child.varname]) + members.append([child.member_typename, child.varname, child.description]) return members @@ -367,16 +369,16 @@ def private_members(self): for child in self.children: if not child.is_public: - members.append([child.member_typename, child.varname]) + members.append([child.member_typename, child.varname, child.description]) if self.has_token: - members.append(["std::shared_ptr<ModToken>", "token"]) + members.append(["std::shared_ptr<ModToken>", "token", "token with location information"]) if self.is_symtab_needed: - members.append(["symtab::SymbolTable*", "symtab = nullptr"]) + members.append(["symtab::SymbolTable*", "symtab = nullptr", "symbol table for a block"]) if self.is_program_node: - members.append(["symtab::ModelSymbolTable", "model_symtab;"]) + members.append(["symtab::ModelSymbolTable", "model_symtab", "global symbol table for model"]) return members diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 308bde77e1..528b50db5a 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -81,6 +81,10 @@ def parse_child_rule(self, child): if 'node_name' in properties: args.get_node_name = properties['node_name'] + # description of member variable + if 'description' in properties: + args.description = properties['description'] + # if getter method required if 'getter' in properties: if 'name' in properties['getter']: @@ -123,11 +127,12 @@ def parse_yaml_rules(self, nodelist, base_class=None): # name of the ast class and it's properties as dictionary class_name = next(iter(list(node.keys()))) properties = next(iter(list(node.values()))) + url = properties['url'] if 'url' in properties else None - # yaml file has abstract classes and their subclasses (i.e. children) as a list - if isinstance(properties, list): + # yaml file has abstract classes and their subclasses with children as a property + if 'children' in properties: # recursively parse all sub-classes of current abstract class - child_abstract_nodes, child_nodes = self.parse_yaml_rules(properties, class_name) + child_abstract_nodes, child_nodes = self.parse_yaml_rules(properties['children'], class_name) # append all parsed subclasses abstract_nodes.extend(child_abstract_nodes) @@ -136,9 +141,12 @@ def parse_yaml_rules(self, nodelist, base_class=None): # classes like AST which don't have base class # are not added (we print AST class separately) if base_class: + args = Argument() args.base_class = base_class args.class_name = class_name + args.description = properties['description'] + args.url = url node = Node(args) abstract_nodes.append(node) nodes.insert(0, node) @@ -155,8 +163,10 @@ def parse_yaml_rules(self, nodelist, base_class=None): args = Argument() args.base_class = base_class if base_class else 'AST' args.class_name = class_name + args.description = properties['description'] args.nmodl_name = nmodl_name args.has_token = has_token + args.url = url # create tree node and add to the list node = Node(args) diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index d102f30305..c86421542f 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -21,17 +21,19 @@ namespace ast { - /* Define all AST nodes */ {% for node in nodes %} + {{ '/* ' + node.description + ' */'}} class {{ node.class_name }} : public {{ node.base_class }} { {% if node.private_members() %} private: {% for member in node.private_members() %} + {{ '// ' + member[2] }} {{ member[0] }} {{ member[1] }}; {% endfor %} {% endif %} public: {% for member in node.public_members() %} + {{ '// ' + member[2] }} {{ member[0] }} {{ member[1] }}; {% endfor %} From e76fcc35ede61c571a0dfc68bfc61bdb52aa2927 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 22 Jan 2019 14:51:44 +0100 Subject: [PATCH 112/871] Fix issues with Bison 3.2 : - remove unnecessary type aliases that causes bison to generate different method signatures resulting in compilation error - uniform lexer to return POD (ast node object) instead of pointers - add default constructors to ast nodes returned from lexer - update nmodl bison specification for POD - fix lexer and modtoken tests for the same Change-Id: Ibb29ad1b6d79122a6b778763738f84d8e14d6e23 NMODL Repo SHA: BlueBrain/nmodl@bb64950dd6d46f447b06add8acffb1f802e5cefe --- src/nmodl/language/nodes.py | 6 + src/nmodl/language/templates/ast.hpp | 2 +- src/nmodl/lexer/nmodl.ll | 5 +- src/nmodl/lexer/nmodl_utils.cpp | 20 +- src/nmodl/parser/nmodl.yy | 226 +++++++++++--------- test/nmodl/transpiler/lexer/tokens.cpp | 15 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 15 +- 7 files changed, 152 insertions(+), 137 deletions(-) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 569583d78e..0a7f1156cb 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -131,6 +131,12 @@ def is_ptr_excluded_node(self): return True return False + @property + def requires_default_constructor(self): + if self.class_name in LEXER_DATA_TYPES or self.is_program_node or self.is_ptr_excluded_node: + return True + else: + return False class ChildNode(BaseNode): """represent member variable for a Node""" diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index d102f30305..45aef981d1 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -43,7 +43,7 @@ namespace ast { {{ node.class_name }}(const {{ node.class_name }}& obj); {% endif %} - {% if node.is_program_node or node.is_ptr_excluded_node %} + {% if node.requires_default_constructor %} {{ node.class_name}}() = default; {% endif %} diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 90a06b07f8..5793f8c38d 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -163,8 +163,8 @@ ELSE { /** Token for certain keywords need name_ptr value. */ auto type = token_type(yytext); ModToken tok(yytext, type, loc); - auto value = new ast::Name( new ast::String(yytext) ); - value->set_token(tok); + ast::Name value( new ast::String(yytext) ); + value.set_token(tok); switch (static_cast<int>(type)) { /** Tokens requiring name_ptr as value */ @@ -189,7 +189,6 @@ ELSE { } /** value is not used */ - delete value; return token_symbol(yytext, loc, type); } else { diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 45193339c8..26d8130864 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -11,8 +11,8 @@ namespace nmodl { /// create symbol for double/real ast class SymbolType double_symbol(double value, PositionType& pos) { ModToken token(std::to_string(value), Token::REAL, pos); - auto floatvalue = new ast::Double(value); - floatvalue->set_token(token); + ast::Double floatvalue(value); + floatvalue.set_token(token); return Parser::make_REAL(floatvalue, pos); } @@ -27,8 +27,8 @@ namespace nmodl { macro->set_token(token); } - auto intvalue = new ast::Integer(value, macro); - intvalue->set_token(token); + ast::Integer intvalue(value, macro); + intvalue.set_token(token); return Parser::make_INTEGER(intvalue, pos); } @@ -39,8 +39,8 @@ namespace nmodl { * token we should mark those as external. */ SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type) { ModToken token(text, type, pos); - auto value = new ast::Name(new ast::String(text)); - value->set_token(token); + ast::Name value(new ast::String(text)); + value.set_token(token); return Parser::make_NAME(value, pos); } @@ -54,16 +54,16 @@ namespace nmodl { auto prime_name = new ast::String(text); auto prime_order = new ast::Integer(order, nullptr); - auto value = new ast::PrimeName(prime_name, prime_order); - value->set_token(token); + ast::PrimeName value(prime_name, prime_order); + value.set_token(token); return Parser::make_PRIME(value, pos); } /// create symbol for string ast class SymbolType string_symbol(const std::string& text, PositionType& pos) { ModToken token(text, Token::STRING, pos); - auto value = new ast::String(text); - value->set_token(token); + ast::String value(text); + value.set_token(token); return Parser::make_STRING(value, pos); } diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 244eaa617f..3861c5d721 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -27,11 +27,6 @@ * specifying direct ast node types. Declare these type until * the issue is identified / fixed. */ - using double_ptr = ast::Double*; - using integer_ptr = ast::Integer*; - using name_ptr = ast::Name*; - using primename_ptr = ast::PrimeName*; - using string_ptr = ast::String*; } /** use C++ parser interface of bison */ @@ -178,22 +173,21 @@ %token <ModToken> NETRECEIVE %token <ModToken> FOR_NETCONS %token <ModToken> CONDUCTANCE -%token <double_ptr> REAL -%token <integer_ptr> INTEGER -%token <integer_ptr> DEFINEDVAR -%token <name_ptr> NAME -%token <name_ptr> METHOD -%token <name_ptr> SUFFIX -%token <name_ptr> VALENCE -%token <name_ptr> DEL -%token <name_ptr> DEL2 -%token <primename_ptr> PRIME +%token <ast::Double> REAL +%token <ast::Integer> INTEGER +%token <ast::Integer> DEFINEDVAR +%token <ast::Name> NAME +%token <ast::Name> METHOD +%token <ast::Name> SUFFIX +%token <ast::Name> VALENCE +%token <ast::Name> DEL +%token <ast::Name> DEL2 +%token <ast::PrimeName> PRIME %token <std::string> VERBATIM %token <std::string> COMMENT %token <std::string> INLINE_COMMENT %token <std::string> LINE_PART -%token <string_ptr> STRING -%token <string_ptr> UNIT_STR +%token <ast::String> STRING %token <ModToken> OPEN_BRACE "{" %token <ModToken> CLOSE_BRACE "}" %token <ModToken> OPEN_PARENTHESIS "(" @@ -352,6 +346,9 @@ %type <ast::DiscreteBlock*> discretblk %type <ast::PartialBlock*> partialblk %type <ast::FunctionTableBlock*> functableblk +%type <ast::Integer*> INTEGER_PTR +%type <ast::Name*> NAME_PTR +%type <ast::String*> STRING_PTR /** Precedence and Associativity : specify operator precedency and * associativity (from lower to higher. Note that '^' represent @@ -441,7 +438,7 @@ all : { $1->addNode($2); $$ = $1; } - | all MODEL_LEVEL INTEGER declare + | all MODEL_LEVEL INTEGER_PTR declare { /** todo : This is discussed with Michael Hines. Model level was inserted * by merge program which is no longer exist. This was to avoid the name @@ -478,7 +475,7 @@ all : { $1->addNode($2); $$ = $1; } - | all INCLUDE1 STRING + | all INCLUDE1 STRING_PTR { auto statement = new ast::Include($3); $1->addNode(statement); @@ -494,7 +491,7 @@ model : MODEL LINE_PART ; -define1 : DEFINE1 NAME INTEGER +define1 : DEFINE1 NAME_PTR INTEGER_PTR { $$ = new ast::Define($2, $3); driver.add_defined_var($2->get_node_name(), $3->eval()); @@ -506,7 +503,7 @@ define1 : DEFINE1 NAME INTEGER ; -Name : NAME { $$ = $1; } +Name : NAME_PTR { $$ = $1; } ; @@ -543,15 +540,15 @@ parmbody : { ; -parmasgn : NAME "=" number units limits +parmasgn : NAME_PTR "=" number units limits { $$ = new ast::ParamAssign($1, $3, $4, $5); } - | NAME units limits + | NAME_PTR units limits { $$ = new ast::ParamAssign($1, NULL, $2, $3); } - | NAME "[" integer "]" units limits + | NAME_PTR "[" integer "]" units limits { $$ = new ast::ParamAssign(new ast::IndexedName($1, $3), NULL, $5, $6); } @@ -614,7 +611,7 @@ stepbdy : { $$ = ast::SteppedVector(); } ; -stepped : NAME "=" numlist units +stepped : NAME_PTR "=" numlist units { $$ = new ast::Stepped($1, $3, $4); } @@ -635,8 +632,8 @@ numlist : number "," number ; -name : Name { $$ = $1; } - | PRIME { $$ = $1; } +name : Name { $$ = $1; } + | PRIME { $$ = $1.clone(); } ; @@ -649,17 +646,17 @@ number : NUMBER { $$ = $1; } ; -NUMBER : integer { $$ = $1; } - | REAL { $$ = $1; } +NUMBER : integer { $$ = $1; } + | REAL { $$ = $1.clone(); } ; -integer : INTEGER { $$ = $1; } - | DEFINEDVAR { $$ = $1; } +integer : INTEGER_PTR { $$ = $1; } + | DEFINEDVAR { $$ = $1.clone(); } ; -real : REAL { $$ = $1; } +real : REAL { $$ = $1.clone(); } | integer { $$ = new ast::Double(double($1->eval())); @@ -692,7 +689,7 @@ indepbody : { ; -indepdef : NAME FROM number TO number withby integer opstart units +indepdef : NAME_PTR FROM number TO number withby integer opstart units { $$ = new ast::IndependentDef(NULL, $1, $3, $5, $7, $8, $9); } @@ -791,8 +788,8 @@ pvlist : name optindex ; -optindex : { $$ = nullptr; } - | "[" INTEGER "]" { $$ = $2; } +optindex : { $$ = nullptr; } + | "[" INTEGER_PTR "]" { $$ = $2; } ; @@ -861,7 +858,7 @@ conducthint : CONDUCTANCE Name { $$ = new ast::ConductanceHint($2, NULL); } - | CONDUCTANCE Name USEION NAME + | CONDUCTANCE Name USEION NAME_PTR { $$ = new ast::ConductanceHint($2, $4); } @@ -879,7 +876,7 @@ locallist : LOCAL locallist1 ; -locallist1 : NAME locoptarray +locallist1 : NAME_PTR locoptarray { $$ = ast::LocalVarVector(); if($2) { @@ -890,7 +887,7 @@ locallist1 : NAME locoptarray $$.emplace_back(variable); } } - | locallist1 "," NAME locoptarray + | locallist1 "," NAME_PTR locoptarray { if($4) { auto variable = new ast::LocalVar(new ast::IndexedName($3, $4)); @@ -1029,11 +1026,11 @@ varname : name { $$ = new ast::VarName(new ast::IndexedName($1, $3), nullptr, nullptr); } - | NAME "@" integer + | NAME_PTR "@" integer { $$ = new ast::VarName($1, $3, nullptr); } - | NAME "@" integer "[" intexpr "]" + | NAME_PTR "@" integer "[" intexpr "]" { $$ = new ast::VarName($1, $3, $5); } @@ -1200,7 +1197,7 @@ term : varname { $$ = $1; } ; -funccall : NAME "(" exprlist ")" +funccall : NAME_PTR "(" exprlist ")" { auto expression = new ast::FunctionCall($1, $3); $$ = new ast::WrappedExpression(expression); @@ -1216,7 +1213,7 @@ exprlist : { $$ = ast::ExpressionVector(); $$.emplace_back($1); } - | STRING + | STRING_PTR { $$ = ast::ExpressionVector(); $$.emplace_back($1); @@ -1226,7 +1223,7 @@ exprlist : { $1.emplace_back($3); $$ = $1; } - | exprlist "," STRING + | exprlist "," STRING_PTR { $1.emplace_back($3); $$ = $1; @@ -1234,7 +1231,7 @@ exprlist : { ; -fromstmt : FROM NAME "=" intexpr TO intexpr opinc stmtlist "}" +fromstmt : FROM NAME_PTR "=" intexpr TO intexpr opinc stmtlist "}" { $$ = new ast::FromStatement($2, $4, $6, $7, $8); } @@ -1250,7 +1247,7 @@ opinc : { $$ = nullptr; } ; -forallstmt : FORALL1 NAME stmtlist "}" +forallstmt : FORALL1 NAME_PTR stmtlist "}" { $$ = new ast::ForAllStatement($2, $3); } @@ -1294,7 +1291,7 @@ optelse : { $$ = nullptr; } ; -derivblk : DERIVATIVE NAME stmtlist "}" +derivblk : DERIVATIVE NAME_PTR stmtlist "}" { $$ = new ast::DerivativeBlock($2, $3); $$->set_token($1); @@ -1302,7 +1299,7 @@ derivblk : DERIVATIVE NAME stmtlist "}" ; -linblk : LINEAR NAME solvefor stmtlist "}" +linblk : LINEAR NAME_PTR solvefor stmtlist "}" { $$ = new ast::LinearBlock($2, $3, $4); $$->set_token($1); @@ -1310,7 +1307,7 @@ linblk : LINEAR NAME solvefor stmtlist "}" ; -nonlinblk : NONLINEAR NAME solvefor stmtlist "}" +nonlinblk : NONLINEAR NAME_PTR solvefor stmtlist "}" { $$ = new ast::NonLinearBlock($2, $3, $4); $$->set_token($1); @@ -1318,7 +1315,7 @@ nonlinblk : NONLINEAR NAME solvefor stmtlist "}" ; -discretblk : DISCRETE NAME stmtlist "}" +discretblk : DISCRETE NAME_PTR stmtlist "}" { $$ = new ast::DiscreteBlock($2, $3); // todo : disabled symbol table, remove this @@ -1327,7 +1324,7 @@ discretblk : DISCRETE NAME stmtlist "}" ; -partialblk : PARTIAL NAME stmtlist "}" +partialblk : PARTIAL NAME_PTR stmtlist "}" { $$ = new ast::PartialBlock($2, $3); $$->set_token($1); @@ -1339,15 +1336,15 @@ partialblk : PARTIAL NAME stmtlist "}" ; -pareqn : "~" PRIME "=" NAME "*" DEL2 "(" NAME ")" "+" NAME +pareqn : "~" PRIME "=" NAME_PTR "*" DEL2 "(" NAME_PTR ")" "+" NAME_PTR { - $$ = new ast::PartialBoundary(NULL, $2, NULL, NULL, $4, $6, $8, $11); + $$ = new ast::PartialBoundary(NULL, $2.clone(), NULL, NULL, $4, $6.clone(), $8, $11); } - | "~" DEL NAME "[" firstlast "]" "=" expr + | "~" DEL NAME_PTR "[" firstlast "]" "=" expr { - $$ = new ast::PartialBoundary($2, $3, $5, $8, NULL, NULL, NULL, NULL); + $$ = new ast::PartialBoundary($2.clone(), $3, $5, $8, NULL, NULL, NULL, NULL); } - | "~" NAME "[" firstlast "]" "=" expr + | "~" NAME_PTR "[" firstlast "]" "=" expr { $$ = new ast::PartialBoundary(NULL, $2, $4, $7, NULL, NULL, NULL, NULL); } @@ -1365,7 +1362,7 @@ firstlast : FIRST ; -functableblk : FUNCTION_TABLE NAME "(" arglist ")" units +functableblk : FUNCTION_TABLE NAME_PTR "(" arglist ")" units { $$ = new ast::FunctionTableBlock($2, $4, $6); $$->set_token($1); @@ -1373,7 +1370,7 @@ functableblk : FUNCTION_TABLE NAME "(" arglist ")" units ; -funcblk : FUNCTION1 NAME "(" arglist ")" units stmtlist "}" +funcblk : FUNCTION1 NAME_PTR "(" arglist ")" units stmtlist "}" { $$ = new ast::FunctionBlock($2, $4, $6, $7); $$->set_token($1); @@ -1401,7 +1398,7 @@ arglist1 : name units ; -procedblk : PROCEDURE NAME "(" arglist ")" units stmtlist "}" +procedblk : PROCEDURE NAME_PTR "(" arglist ")" units stmtlist "}" { $$ = new ast::ProcedureBlock($2, $4, $6, $7); $$->set_token($1); } @@ -1426,13 +1423,13 @@ initstmt : INITIAL1 stmtlist "}" ; -solveblk : SOLVE NAME ifsolerr +solveblk : SOLVE NAME_PTR ifsolerr { $$ = new ast::SolveBlock($2, NULL, $3); } - | SOLVE NAME USING METHOD ifsolerr + | SOLVE NAME_PTR USING METHOD ifsolerr { - $$ = new ast::SolveBlock($2, $4, $5); + $$ = new ast::SolveBlock($2, $4.clone(), $5); } | SOLVE error { @@ -1451,12 +1448,12 @@ solvefor : { $$ = ast::NameVector(); } ; -solvefor1 : SOLVEFOR NAME +solvefor1 : SOLVEFOR NAME_PTR { $$ = ast::NameVector(); $$.emplace_back($2); } - | solvefor1 "," NAME + | solvefor1 "," NAME_PTR { $1.emplace_back($3); $$ = $1; @@ -1623,7 +1620,7 @@ conserve : CONSERVE react "=" expr ; -compart : COMPARTMENT NAME "," expr "{" namelist "}" +compart : COMPARTMENT NAME_PTR "," expr "{" namelist "}" { $$ = new ast::Compartment($2, $4, $6); } @@ -1634,7 +1631,7 @@ compart : COMPARTMENT NAME "," expr "{" namelist "}" ; -ldifus : LONGDIFUS NAME "," expr "{" namelist "}" +ldifus : LONGDIFUS NAME_PTR "," expr "{" namelist "}" { $$ = new ast::LonDifuse($2, $4, $6); } @@ -1645,12 +1642,12 @@ ldifus : LONGDIFUS NAME "," expr "{" namelist "}" ; -namelist : NAME +namelist : NAME_PTR { $$ = ast::NameVector(); $$.emplace_back($1); } - | namelist NAME + | namelist NAME_PTR { $1.emplace_back($2); $$ = $1; @@ -1658,7 +1655,7 @@ namelist : NAME ; -kineticblk : KINETIC NAME solvefor stmtlist "}" +kineticblk : KINETIC NAME_PTR solvefor stmtlist "}" { $$ = new ast::KineticBlock($2, $3, $4); $$->set_token($1); @@ -1707,7 +1704,7 @@ react : varname { $$ = $1; } ; -lagstmt : LAG name BY NAME +lagstmt : LAG name BY NAME_PTR { $$ = new ast::LagStatement($2, $4); } @@ -1769,7 +1766,7 @@ match : name matchname : name { $$ = $1; } - | name "[" NAME "]" + | name "[" NAME_PTR "]" { $$ = new ast::IndexedName($1, $3); } @@ -1810,17 +1807,17 @@ unitdef : unit "=" unit ; -factordef : NAME "=" real unit +factordef : NAME_PTR "=" real unit { $$ = new ast::FactorDef($1, $3, $4, NULL, NULL); $$->set_token(*($1->get_token())); } - | NAME "=" unit unit + | NAME_PTR "=" unit unit { $$ = new ast::FactorDef($1, NULL, $3, NULL, $4); $$->set_token(*($1->get_token())); } - | NAME "=" unit "-" GT unit + | NAME_PTR "=" unit "-" GT unit { $$ = new ast::FactorDef($1, NULL, $3, new ast::Boolean(1), $6); $$->set_token(*($1->get_token())); @@ -1842,7 +1839,7 @@ constblk : CONSTANT "{" conststmt "}" conststmt : { $$ = ast::ConstantStatementVector(); } - | conststmt NAME "=" number units + | conststmt NAME_PTR "=" number units { auto constant = new ast::ConstantVar($2, $4, $5); $1.emplace_back(new ast::ConstantStatement(constant)); @@ -1851,7 +1848,7 @@ conststmt : { ; -tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER +tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER_PTR { $$ = new ast::TableStatement($2, $3, $5, $7, $9); } @@ -1895,9 +1892,9 @@ neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE nrnstmt : { $$ = ast::StatementVector(); } - | nrnstmt SUFFIX NAME + | nrnstmt SUFFIX NAME_PTR { - $1.emplace_back(new ast::Suffix($2, $3)); + $1.emplace_back(new ast::Suffix($2.clone(), $3)); $$ = $1; } | nrnstmt nrnuse @@ -1953,15 +1950,15 @@ nrnstmt : { $$ = ast::StatementVector(); } ; -nrnuse : USEION NAME READ nrnionrlist valence +nrnuse : USEION NAME_PTR READ nrnionrlist valence { $$ = new ast::Useion($2, $4, ast::WriteIonVarVector(), $5); } - | USEION NAME WRITE nrnionwlist valence + | USEION NAME_PTR WRITE nrnionwlist valence { $$ = new ast::Useion($2, ast::ReadIonVarVector(), $4, $5); } - | USEION NAME READ nrnionrlist WRITE nrnionwlist valence + | USEION NAME_PTR READ nrnionrlist WRITE nrnionwlist valence { $$ = new ast::Useion($2, $4, $6, $7); } @@ -1972,12 +1969,12 @@ nrnuse : USEION NAME READ nrnionrlist valence ; -nrnionrlist : NAME +nrnionrlist : NAME_PTR { $$ = ast::ReadIonVarVector(); $$.emplace_back(new ast::ReadIonVar($1)); } - | nrnionrlist "," NAME + | nrnionrlist "," NAME_PTR { $1.emplace_back(new ast::ReadIonVar($3)); $$ = $1; @@ -1989,12 +1986,12 @@ nrnionrlist : NAME ; -nrnionwlist : NAME +nrnionwlist : NAME_PTR { $$ = ast::WriteIonVarVector(); $$.emplace_back(new ast::WriteIonVar($1)); } - | nrnionwlist "," NAME + | nrnionwlist "," NAME_PTR { $1.emplace_back(new ast::WriteIonVar($3)); $$ = $1; @@ -2006,12 +2003,12 @@ nrnionwlist : NAME ; -nrnonspeclist : NAME +nrnonspeclist : NAME_PTR { $$ = ast::NonspeCurVarVector(); $$.emplace_back(new ast::NonspeCurVar($1)); } - | nrnonspeclist "," NAME + | nrnonspeclist "," NAME_PTR { $1.emplace_back(new ast::NonspeCurVar($3)); $$ = $1; @@ -2023,12 +2020,12 @@ nrnonspeclist : NAME ; -nrneclist : NAME +nrneclist : NAME_PTR { $$ = ast::ElectrodeCurVarVector(); $$.emplace_back(new ast::ElectrodeCurVar($1)); } - | nrneclist "," NAME + | nrneclist "," NAME_PTR { $1.emplace_back(new ast::ElectrodeCurVar($3)); $$ = $1; @@ -2040,12 +2037,12 @@ nrneclist : NAME ; -nrnseclist : NAME +nrnseclist : NAME_PTR { $$ = ast::SectionVarVector(); $$.emplace_back(new ast::SectionVar($1)); } - | nrnseclist "," NAME + | nrnseclist "," NAME_PTR { $1.emplace_back(new ast::SectionVar($3)); $$ = $1; @@ -2057,12 +2054,12 @@ nrnseclist : NAME ; -nrnrangelist : NAME +nrnrangelist : NAME_PTR { $$ = ast::RangeVarVector(); $$.emplace_back(new ast::RangeVar($1)); } - | nrnrangelist "," NAME + | nrnrangelist "," NAME_PTR { $1.emplace_back(new ast::RangeVar($3)); $$ = $1; @@ -2074,12 +2071,12 @@ nrnrangelist : NAME ; -nrnglobalist : NAME +nrnglobalist : NAME_PTR { $$ = ast::GlobalVarVector(); $$.emplace_back(new ast::GlobalVar($1)); } - | nrnglobalist "," NAME + | nrnglobalist "," NAME_PTR { $1.emplace_back(new ast::GlobalVar($3)); $$ = $1; @@ -2091,12 +2088,12 @@ nrnglobalist : NAME ; -nrnptrlist : NAME +nrnptrlist : NAME_PTR { $$ = ast::PointerVarVector(); $$.emplace_back(new ast::PointerVar($1)); } - | nrnptrlist "," NAME + | nrnptrlist "," NAME_PTR { $1.emplace_back(new ast::PointerVar($3)); $$ = $1; @@ -2108,12 +2105,12 @@ nrnptrlist : NAME ; -nrnbbptrlist : NAME +nrnbbptrlist : NAME_PTR { $$ = ast::BbcorePointerVarVector(); $$.emplace_back(new ast::BbcorePointerVar($1)); } - | nrnbbptrlist "," NAME + | nrnbbptrlist "," NAME_PTR { $1.emplace_back(new ast::BbcorePointerVar($3)); $$ = $1; @@ -2125,12 +2122,12 @@ nrnbbptrlist : NAME ; -nrnextlist : NAME +nrnextlist : NAME_PTR { $$ = ast::ExternVarVector(); $$.emplace_back(new ast::ExternVar($1)); } - | nrnextlist "," NAME + | nrnextlist "," NAME_PTR { $1.emplace_back(new ast::ExternVar($3)); $$ = $1; @@ -2147,12 +2144,12 @@ opthsafelist : { $$ = ast::ThreadsafeVarVector(); } ; -threadsafelist : NAME +threadsafelist : NAME_PTR { $$ = ast::ThreadsafeVarVector(); $$.emplace_back(new ast::ThreadsafeVar($1)); } - | threadsafelist "," NAME + | threadsafelist "," NAME_PTR { $1.emplace_back(new ast::ThreadsafeVar($3)); $$ = $1; @@ -2163,15 +2160,36 @@ threadsafelist : NAME valence : { $$ = nullptr; } | VALENCE real { - $$ = new ast::Valence($1, $2); + $$ = new ast::Valence($1.clone(), $2); } | VALENCE "-" real { $3->negate(); - $$ = new ast::Valence($1, $3); + $$ = new ast::Valence($1.clone(), $3); } ; + + INTEGER_PTR : INTEGER + { + $$ = $1.clone(); + } + ; + + + NAME_PTR : NAME + { + $$ = $1.clone(); + } + ; + + + STRING_PTR : STRING + { + $$ = $1.clone(); + } + ; + %% diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index da3ebecc97..97b9c07be7 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -33,32 +33,27 @@ nmodl::Parser::token_type token_type(const std::string& name) { case Token::VALENCE: case Token::DEL: case Token::DEL2: { - auto value = sym.value.as<ast::Name*>(); - delete value; + auto value = sym.value.as<ast::Name>(); break; } case Token::PRIME: { - auto value = sym.value.as<ast::PrimeName*>(); - delete value; + auto value = sym.value.as<ast::PrimeName>(); break; } case Token::INTEGER: { - auto value = sym.value.as<ast::Integer*>(); - delete value; + auto value = sym.value.as<ast::Integer>(); break; } case Token::REAL: { - auto value = sym.value.as<ast::Double*>(); - delete value; + auto value = sym.value.as<ast::Double>(); break; } case Token::STRING: { - auto value = sym.value.as<ast::String*>(); - delete value; + auto value = sym.value.as<ast::String>(); break; } diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index cebcfc4d55..c5c2523523 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -23,35 +23,32 @@ void symbol_type(const std::string& name, T& value) { /// test symbol type returned by lexer TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { SECTION("Symbol type : name ast class test") { - ast::Name* value = nullptr; + ast::Name value; { std::stringstream ss; symbol_type("text", value); - ss << *(value->get_token()); + ss << *(value.get_token()); REQUIRE(ss.str() == " text at [1.1-4] type 356"); - delete value; } { std::stringstream ss; symbol_type(" some_text", value); - ss << *(value->get_token()); + ss << *(value.get_token()); REQUIRE(ss.str() == " some_text at [1.3-11] type 356"); - delete value; } } SECTION("Symbol type : prime ast class test") { - ast::PrimeName* value = nullptr; + ast::PrimeName value; { std::stringstream ss; symbol_type("h'' = ", value); - ss << *(value->get_token()); + ss << *(value.get_token()); REQUIRE(ss.str() == " h'' at [1.1-3] type 362"); - REQUIRE(value->get_order()->eval() == 2); - delete value; + REQUIRE(value.get_order()->eval() == 2); } } } From 5b4b62904bf99e83e46c15d02c2806cfcebcd895 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 27 Jan 2019 18:32:26 +0100 Subject: [PATCH 113/871] Review yaml specification and rename variable names: - yaml naming changes for consistency - added separate ast node for block comment and single line comment - renamed COMMENT to BLOCK_COMMENT and INLINE_COMMENT to LINE_COMMENT Change-Id: I72840932d9192ac8734d0ccdfad8a62be6e81760 NMODL Repo SHA: BlueBrain/nmodl@e0cc2ac47c67e7d75def256087ce02e6b23dad2c --- src/nmodl/ast/ast_common.hpp | 16 ++- .../codegen/base/codegen_base_visitor.cpp | 21 +-- src/nmodl/codegen/c/codegen_c_visitor.cpp | 26 ++-- src/nmodl/language/nmodl.yaml | 129 +++++++++--------- src/nmodl/language/templates/json_visitor.cpp | 2 +- .../language/templates/nmodl_visitor.cpp | 2 +- src/nmodl/lexer/main_nmodl.cpp | 2 +- src/nmodl/lexer/nmodl.ll | 10 +- src/nmodl/lexer/token_mapping.cpp | 2 +- src/nmodl/parser/nmodl.yy | 30 ++-- src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.cpp | 4 +- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/nmodl_visitor_helper.hpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/lexer/tokens.cpp | 2 +- 16 files changed, 130 insertions(+), 124 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 2efa393a01..80faee8102 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -95,8 +95,8 @@ namespace ast { throw std::runtime_error("get_symbol_table() not implemented"); } - virtual std::shared_ptr<StatementBlock> get_statement_block() { - throw std::runtime_error("get_statement_block not implemented"); + virtual std::shared_ptr<StatementBlock> get_block() { + throw std::runtime_error("get_block not implemented"); } // implemented in Number sub classes @@ -188,7 +188,7 @@ namespace ast { return false; } - virtual bool is_nonspe_cur_var() { + virtual bool is_nonspecific_cur_var() { return false; } @@ -588,7 +588,7 @@ namespace ast { return false; } - /// \todo : how is this different from is_nonspe_cur_var ? + /// \todo : how is this different from is_nonspecific_cur_var ? virtual bool is_nonspecific() { return false; } @@ -609,7 +609,7 @@ namespace ast { return false; } - /// \todo : how these are different from is_pointer_var ? + /// \todo : how is this different from is_pointer_var ? virtual bool is_pointer() { return false; } @@ -630,7 +630,11 @@ namespace ast { return false; } - virtual bool is_comment() { + virtual bool is_line_comment() { + return false; + } + + virtual bool is_block_comment() { return false; } diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index ffbdb02e4a..8cf7e681a6 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -40,7 +40,7 @@ void CodegenBaseVisitor::visit_integer(Integer* node) { if (!codegen) { return; } - auto macro = node->get_macro_name(); + auto macro = node->get_macro(); auto value = node->get_value(); if (macro) { macro->accept(this); @@ -99,7 +99,7 @@ void CodegenBaseVisitor::visit_var_name(VarName* node) { return; } auto name = node->get_name(); - auto at_index = node->get_at_index(); + auto at_index = node->get_at(); auto index = node->get_index(); name->accept(this); if (at_index) { @@ -142,7 +142,7 @@ void CodegenBaseVisitor::visit_if_statement(IfStatement* node) { printer->add_text("if ("); node->get_condition()->accept(this); printer->add_text(") "); - node->get_statement_block()->accept(this); + node->get_block()->accept(this); print_vector_elements(node->get_elseifs(), ""); auto elses = node->get_elses(); if (elses) { @@ -158,7 +158,7 @@ void CodegenBaseVisitor::visit_else_if_statement(ElseIfStatement* node) { printer->add_text(" else if ("); node->get_condition()->accept(this); printer->add_text(") "); - node->get_statement_block()->accept(this); + node->get_block()->accept(this); } @@ -174,7 +174,7 @@ void CodegenBaseVisitor::visit_while_statement(WhileStatement* node) { printer->add_text("while ("); node->get_condition()->accept(this); printer->add_text(") "); - node->get_statement_block()->accept(this); + node->get_block()->accept(this); } void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { @@ -184,8 +184,8 @@ void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { auto name = node->get_node_name(); auto from = node->get_from(); auto to = node->get_to(); - auto inc = node->get_opinc(); - auto block = node->get_statement_block(); + auto inc = node->get_increment(); + auto block = node->get_block(); printer->add_text("for(int {}="_format(name)); from->accept(this); printer->add_text("; {}<="_format(name)); @@ -206,7 +206,7 @@ void CodegenBaseVisitor::visit_paren_expression(ParenExpression* node) { return; } printer->add_text("("); - node->get_expr()->accept(this); + node->get_expression()->accept(this); printer->add_text(")"); } @@ -296,7 +296,8 @@ void CodegenBaseVisitor::visit_program(Program* node) { bool CodegenBaseVisitor::skip_statement(Statement* node) { // clang-format off if (node->is_unit_state() - || node->is_comment() + || node->is_line_comment() + || node->is_block_comment() || node->is_solve_block() || node->is_conductance_hint() || node->is_table_statement()) { @@ -477,7 +478,7 @@ std::string CodegenBaseVisitor::breakpoint_current(std::string current) { if (breakpoint == nullptr) { return current; } - auto symtab = breakpoint->get_statement_block()->get_symbol_table(); + auto symtab = breakpoint->get_block()->get_symbol_table(); auto variables = symtab->get_variables_with_properties(NmodlType::local_var); for (auto& var : variables) { auto renamed_name = var->get_name(); diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index e8cedbbb51..b3cbf756b7 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -68,7 +68,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { if (breakpoint == nullptr) { return current; } - auto symtab = breakpoint->get_statement_block()->get_symbol_table(); + auto symtab = breakpoint->get_block()->get_symbol_table(); auto variables = symtab->get_variables_with_properties(NmodlType::local_var); for (auto& var : variables) { auto renamed_name = var->get_name(); @@ -878,7 +878,7 @@ void CodegenCVisitor::print_check_table_thread_function() { } void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& name) { - auto block = node->get_statement_block().get(); + auto block = node->get_block().get(); printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); @@ -921,7 +921,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { auto return_var = "ret_" + name; /// first rename return variable name - auto block = node->get_statement_block().get(); + auto block = node->get_block().get(); RenameVisitor v(name, return_var); block->accept(&v); @@ -2261,7 +2261,7 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { /// initial block if (node != nullptr) { - auto block = node->get_statement_block(); + auto block = node->get_block(); print_statement_block(block.get(), false, false); } @@ -2608,7 +2608,7 @@ void CodegenCVisitor::print_net_init_kernel() { printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); printer->start_block("static void net_init({}) "_format(args)); - auto block = node->get_statement_block().get(); + auto block = node->get_block().get(); if (block->get_statements().empty()) { printer->add_line("// do nothing"); } else { @@ -2724,7 +2724,7 @@ void CodegenCVisitor::print_net_receive() { auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); - node->get_statement_block()->visit_children(&vr); + node->get_block()->visit_children(&vr); } } @@ -2750,7 +2750,7 @@ void CodegenCVisitor::print_net_receive() { printer->add_line("{} = t;"_format(get_variable_name("tsave"))); printer->add_indent(); - node->get_statement_block()->accept(this); + node->get_block()->accept(this); printer->add_newline(); printer->end_block(); printer->add_newline(); @@ -2797,7 +2797,7 @@ void CodegenCVisitor::print_derivative_kernel_for_euler() { printer->add_line("/* _euler_ state _{} */"_format(info.mod_suffix)); printer->start_block("int {}_{}({})"_format(node->get_node_name(), info.mod_suffix, arguments)); printer->add_line(instance); - print_statement_block(node->get_statement_block().get(), false, false); + print_statement_block(node->get_block().get(), false, false); printer->add_line("return 0;"); printer->end_block(); printer->add_newline(); @@ -2856,7 +2856,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); - print_statement_block(node->get_statement_block().get(), false, false); + print_statement_block(node->get_block().get(), false, false); printer->add_line("int counter = -1;"); printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); @@ -2932,13 +2932,13 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line(statement); } else { if (info.solve_node != nullptr) { - auto block = info.solve_node->get_statement_block(); + auto block = info.solve_node->get_block(); print_statement_block(block.get(), false, false); } } if (info.currents.empty() && info.breakpoint_node != nullptr) { - auto block = info.breakpoint_node->get_statement_block(); + auto block = info.breakpoint_node->get_block(); print_statement_block(block.get(), false, false); } @@ -2969,7 +2969,7 @@ void CodegenCVisitor::print_nrn_state() { void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { auto args = internal_method_parameters(); - auto block = node->get_statement_block().get(); + auto block = node->get_block().get(); printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline double nrn_current({})"_format(args)); @@ -2986,7 +2986,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { - auto block = node->get_statement_block(); + auto block = node->get_block(); print_statement_block(block.get(), false, false); if (!info.currents.empty()) { std::string sum; diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 55b1dfe76f..bcfce0a42f 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -94,7 +94,7 @@ - value: description: "integer value" type: int - - macro_name: + - macro: description: "macro name for integer" type: Name optional: true @@ -143,8 +143,8 @@ description: "variable name" type: Identifier node_name: true - - at_index: - description: "todo" + - at: + description: "todo : Michael to define the semantics of this" type: Integer optional: true prefix: {value: "@"} @@ -203,7 +203,7 @@ description: "..." type: Name node_name: true - - NonspeCurVar: + - NonspecificCurVar: description: ".." members: - name: @@ -319,7 +319,7 @@ description: ".." nmodl: "INITIAL " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -327,7 +327,7 @@ description: ".." nmodl: "CONSTRUCTOR " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -335,7 +335,7 @@ description: ".." nmodl: "DESTRUCTOR " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -356,7 +356,7 @@ type: Name node_name: true suffix: {value: " "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -375,7 +375,7 @@ vector: true separator: "," prefix: {value: " SOLVEFOR "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -394,7 +394,7 @@ separator: "," prefix: {value: " SOLVEFOR "} suffix: {value: " ", force: true} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -407,7 +407,7 @@ type: Name node_name: true suffix: {value: " "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -420,7 +420,7 @@ type: Name node_name: true suffix: {value: " "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -467,7 +467,7 @@ optional: true prefix: {value: " "} suffix: {value: " ", force: true} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -491,7 +491,7 @@ description: "..." type: Unit optional: true - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -507,7 +507,7 @@ suffix: {value: ") ", force: true} separator: ", " getter: {override: true} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -533,7 +533,7 @@ description: ".." nmodl: "BREAKPOINT " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -541,7 +541,7 @@ description: ".." nmodl: "TERMINAL " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -549,14 +549,14 @@ description: ".." nmodl: "BEFORE " members: - - block: + - bablock: description: "..." type: BABlock - AfterBlock: description: ".." nmodl: "AFTER " members: - - block: + - bablock: description: "..." type: BABlock - BABlock: @@ -566,7 +566,7 @@ description: "..." type: BABlockType suffix: {value: " "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -582,7 +582,7 @@ suffix: {value: ") ", force: true} separator: ", " getter: {override: true} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -600,7 +600,7 @@ type: Name vector: true separator: "," - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -636,7 +636,7 @@ url: "https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl2.html#neuron" nmodl: "NEURON " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -652,7 +652,7 @@ - DoubleUnit: description: ".." members: - - values: + - value: description: "..." type: Double - unit: @@ -745,7 +745,7 @@ - ParenExpression: description: ".." members: - - expr: + - expression: description: "..." type: Expression prefix: {value: "("} @@ -789,11 +789,11 @@ description: ".." nmodl: "~ " members: - - leftlinexpr: + - left_linxpression: description: "..." type: Expression suffix: {value: " = "} - - linexpr: + - linxpression: description: "..." type: Expression - FunctionCall: @@ -879,7 +879,7 @@ type: Unit prefix: {value: " "} - gt: - description: "..." + description: "Todo: @Michael : rename variable gt as well" type: Boolean nmodl: " ->" optional: true @@ -1006,7 +1006,7 @@ description: "..." type: Integer prefix: {value: " WITH "} - - opstart: + - start: description: "..." type: Number prefix: {value: " START "} @@ -1039,7 +1039,7 @@ type: Number prefix: {value: " TO "} optional: true - - opstart: + - start: description: "..." type: Number prefix: {value: " START "} @@ -1059,7 +1059,7 @@ description: ".." nmodl: "PLOT " members: - - pvlist: + - variables: description: "..." type: PlotVar vector: true @@ -1109,13 +1109,13 @@ description: "..." type: Expression prefix: {value: " TO "} - - opinc: + - increment: description: "..." type: Expression prefix: {value: " BY "} suffix: {value: " ", force: true} optional: true - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -1127,7 +1127,7 @@ description: "..." type: Name suffix: {value: " "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -1140,7 +1140,7 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -1153,7 +1153,7 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -1174,7 +1174,7 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -1182,7 +1182,7 @@ description: ".." nmodl: " ELSE " members: - - statement_block: + - block: description: "..." type: StatementBlock getter: {override: true} @@ -1268,7 +1268,7 @@ description: ".." nmodl: "SENS " members: - - senslist: + - variables: description: "..." type: VarName vector: true @@ -1331,23 +1331,23 @@ description: ".." nmodl: "~ " members: - - react1: + - reaction1: description: "..." type: Expression - op: description: "..." type: ReactionOperator prefix: {value: " "} - - react2: + - reaction2: description: "..." type: Expression prefix: {value: " "} optional: true - - expr1: + - expression1: description: "..." type: Expression prefix: {value: " ("} - - expr2: + - expression2: description: "..." type: Expression prefix: {value: ", "} @@ -1367,7 +1367,7 @@ - QueueStatement: description: ".." members: - - qype: + - qtype: description: "..." type: QueueExpressionType - name: @@ -1448,14 +1448,14 @@ members: - currents: description: "..." - type: NonspeCurVar + type: NonspecificCurVar vector: true separator: ", " - ElctrodeCurrent: description: ".." nmodl: "ELECTRODE_CURRENT " members: - - ecurrents: + - currents: description: "..." type: ElectrodeCurVar vector: true @@ -1473,7 +1473,7 @@ description: ".." nmodl: "RANGE " members: - - range_vars: + - variables: description: "..." type: RangeVar vector: true @@ -1482,7 +1482,7 @@ description: ".." nmodl: "GLOBAL " members: - - global_vars: + - variables: description: "..." type: GlobalVar vector: true @@ -1491,7 +1491,7 @@ description: ".." nmodl: "POINTER " members: - - pointers: + - variables: description: "..." type: PointerVar vector: true @@ -1500,7 +1500,7 @@ description: ".." nmodl: "BBCOREPOINTER " members: - - bbcore_pointers: + - variables: description: "..." type: BbcorePointerVar vector: true @@ -1509,7 +1509,7 @@ description: ".." nmodl: "EXTERNAL " members: - - externals: + - variables: description: "..." type: ExternVar vector: true @@ -1518,36 +1518,37 @@ description: ".." nmodl: THREADSAFE members: - - threadsafe: + - variables: description: "..." type: ThreadsafeVar vector: true separator: ", " prefix: {value: " "} - Verbatim: - description: "Represents a(n) C code block" + description: "Represents a C code block" nmodl: VERBATIM members: - statement: description: "C code as a string" type: String suffix: {value: "ENDVERBATIM"} - - Comment: - description: "Represents a(n) comment in the NMODL" + - LineComment: + description: "Represents a one line comment in NMODL" members: - - block_comment: - description: "Multi-line block comment" + - statement: + description: "comment text" type: String - prefix: {value: "COMMENT"} - suffix: {value: "ENDCOMMENT"} - optional: true - - line_comment: - description: "Single line comment" + - BlockComment: + description: "Represents a multi-line comment in NMODL" + nmodl: COMMENT + members: + - statement: + description: "comment text" type: String - optional: true + suffix: {value: "ENDCOMMENT"} - Program: - description: "Represents a(n) whole AST node for input NMODL file" + description: "Represents top level AST node for whole NMODL input" members: - blocks: description: "..." diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/json_visitor.cpp index 9de96c330d..e2530d1a99 100644 --- a/src/nmodl/language/templates/json_visitor.cpp +++ b/src/nmodl/language/templates/json_visitor.cpp @@ -7,7 +7,7 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node->visit_children(this); {% if node.is_data_type_node %} {% if node.is_integer_node %} - if(!node->get_macro_name()) { + if(!node->get_macro()) { std::stringstream ss; ss << node->eval(); printer->add_node(ss.str()); diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index 0cdedc4094..73edcc5fbb 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -73,7 +73,7 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% endif %} {% if node.is_data_type_node %} {% if node.is_integer_node %} - if(node->get_macro_name() == nullptr) { + if(node->get_macro() == nullptr) { printer->add_element(std::to_string(node->eval())); } {% else %} diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index df6b41f217..50cac5c0e4 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -104,7 +104,7 @@ int main(int argc, const char* argv[]) { /// token with string data type case Token::VERBATIM: - case Token::COMMENT: + case Token::BLOCK_COMMENT: case Token::LINE_PART: { auto str = sym.value.as<std::string>(); std::cout << str << std::endl; diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 5793f8c38d..5d0ab6f140 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -110,8 +110,8 @@ E [Ee][-+]?{D}+ } } -WHILE | -IF | +WHILE | +IF | ELSE { /** Lower or upper case if,else,while keywords are allowded. * To avoid extra keywords, make token lower case */ @@ -385,7 +385,7 @@ ELSE { \?.* { /** Todo : add grammar support for inline vs single-line comments auto str = std::string(yytext); - return nmodl::Parser::make_INLINE_COMMENT(str, loc); + return nmodl::Parser::make_LINE_COMMENT(str, loc); */ } @@ -422,10 +422,10 @@ ELSE { } <COPY_MODE>"ENDCOMMENT" { - auto str = "COMMENT" + std::string(yytext); + auto str = "BLOCK_COMMENT" + std::string(yytext); BEGIN(INITIAL); reset_end_position(); - return nmodl::Parser::make_COMMENT(str, loc); + return nmodl::Parser::make_BLOCK_COMMENT(str, loc); } <COPY_MODE><<EOF>> { diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 72d45e6656..199a869090 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -20,7 +20,7 @@ namespace nmodl { * Once we finish code generation part then we change this. */ static std::map<std::string, TokenType> keywords = { {"VERBATIM", Token::VERBATIM}, - {"COMMENT", Token::COMMENT}, + {"COMMENT", Token::BLOCK_COMMENT}, {"TITLE", Token::MODEL}, {"CONSTANT", Token::CONSTANT}, {"PARAMETER", Token::PARAMETER}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 3861c5d721..5eaef273c7 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -184,8 +184,8 @@ %token <ast::Name> DEL2 %token <ast::PrimeName> PRIME %token <std::string> VERBATIM -%token <std::string> COMMENT -%token <std::string> INLINE_COMMENT +%token <std::string> BLOCK_COMMENT +%token <std::string> LINE_COMMENT %token <std::string> LINE_PART %token <ast::String> STRING %token <ModToken> OPEN_BRACE "{" @@ -316,7 +316,7 @@ %type <ast::StatementVector> nrnstmt %type <ast::ReadIonVarVector> nrnionrlist %type <ast::WriteIonVarVector> nrnionwlist -%type <ast::NonspeCurVarVector> nrnonspeclist +%type <ast::NonspecificCurVarVector> nrnonspeclist %type <ast::ElectrodeCurVarVector> nrneclist %type <ast::SectionVarVector> nrnseclist %type <ast::RangeVarVector> nrnrangelist @@ -399,7 +399,7 @@ * set accurate location for each production. We need to add method in AST * classes to handle this. * - * \todo INLINE_COMMENT adds comment as separate statement and hence they + * \todo LINE_COMMENT adds comment as separate statement and hence they * go into separate line in nmodl printer. Need to update grammar to distinguish * standalone single line comment vs. inline comment. */ @@ -457,16 +457,16 @@ all : { $1->addNode(statement); $$ = $1; } - | all COMMENT + | all BLOCK_COMMENT { auto text = parse_with_verbatim_parser($2); - auto statement = new ast::Comment(new ast::String(text), nullptr); + auto statement = new ast::BlockComment(new ast::String(text)); $1->addNode(statement); $$ = $1; } - | all INLINE_COMMENT + | all LINE_COMMENT { - auto statement = new ast::Comment(nullptr, new ast::String($2)); + auto statement = new ast::LineComment(new ast::String($2)); $1->addNode(statement); $$ = $1; } @@ -919,9 +919,9 @@ stmtlist1 : { $1.emplace_back($2); $$ = $1; } - | stmtlist1 INLINE_COMMENT + | stmtlist1 LINE_COMMENT { - auto statement = new ast::Comment(nullptr, new ast::String($2)); + auto statement = new ast::LineComment(new ast::String($2)); $1.emplace_back(statement); $$ = $1; } @@ -945,9 +945,9 @@ ostmt : fromstmt { $$ = $1; } { auto text = parse_with_verbatim_parser($1); $$ = new ast::Verbatim(new ast::String(text)); } - | COMMENT + | BLOCK_COMMENT { auto text = parse_with_verbatim_parser($1); - $$ = new ast::Comment(new ast::String(text), nullptr); + $$ = new ast::BlockComment(new ast::String(text)); } | sens { $$ = $1; } | compart { $$ = $1; } @@ -2005,12 +2005,12 @@ nrnionwlist : NAME_PTR nrnonspeclist : NAME_PTR { - $$ = ast::NonspeCurVarVector(); - $$.emplace_back(new ast::NonspeCurVar($1)); + $$ = ast::NonspecificCurVarVector(); + $$.emplace_back(new ast::NonspecificCurVar($1)); } | nrnonspeclist "," NAME_PTR { - $1.emplace_back(new ast::NonspeCurVar($3)); + $1.emplace_back(new ast::NonspecificCurVar($3)); $$ = $1; } | error diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index e79b7de378..eb256939aa 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -216,7 +216,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { auto last_chain = current_chain; start_new_chain(DUState::IF); node->get_condition()->accept(this); - auto block = node->get_statement_block(); + auto block = node->get_block(); if (block) { block->accept(this); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index fa2fc5145a..afd8c40bcd 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -109,7 +109,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, std::string function_name = callee->get_node_name(); /// do nothing if we can't inline given procedure/function - if (!can_inline_block(callee->get_statement_block().get())) { + if (!can_inline_block(callee->get_block().get())) { std::cerr << "Can not inline function call to " + function_name << std::endl; return false; } @@ -143,7 +143,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, } /// get a copy of function/procedure body - auto inlined_block = callee->get_statement_block()->clone(); + auto inlined_block = callee->get_block()->clone(); /// function definition has function name as return value. we have to rename /// it with new variable name diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 31a1ac5c39..dce218c91b 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -120,7 +120,7 @@ void LocalizeVisitor::visit_program(Program* node) { /// all blocks that are using variable should get local variable for (auto& block : block_usage[DUState::D]) { auto block_ptr = dynamic_cast<Block*>(block.get()); - auto statement_block = block_ptr->get_statement_block(); + auto statement_block = block_ptr->get_block(); LocalVar* variable; auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.hpp index 67111a6d16..cf1785dc83 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.hpp +++ b/src/nmodl/visitors/nmodl_visitor_helper.hpp @@ -40,7 +40,7 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, bool extra_newline = false; if (!is_last(iter, elements)) { extra_newline = true; - if ((*iter)->is_comment() && (*(iter + 1))->is_comment()) { + if ((*iter)->is_line_comment() && (*(iter + 1))->is_line_comment()) { extra_newline = false; } } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 0a65957cb1..1b8aa98fd6 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -71,6 +71,6 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { auto ast = driver.ast(); auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); auto statement = - std::shared_ptr<Statement>(procedure->get_statement_block()->get_statements()[0]->clone()); + std::shared_ptr<Statement>(procedure->get_block()->get_statements()[0]->clone()); return statement; } diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 97b9c07be7..40ccf3f46b 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -58,7 +58,7 @@ nmodl::Parser::token_type token_type(const std::string& name) { } case Token::VERBATIM: - case Token::COMMENT: + case Token::BLOCK_COMMENT: case Token::LINE_PART: { auto value = sym.value.as<std::string>(); break; From f4c40112118caa006d2ca8f70a9cbe3530d71649 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 27 Jan 2019 23:00:12 +0100 Subject: [PATCH 114/871] Add ast lookup visitor to search nodes into ast: - add new template for lookup visitor - update tests - remove using namespace from header files Change-Id: I665ad415fbb782ff86951783d053f8b85d0e563d NMODL Repo SHA: BlueBrain/nmodl@de0f1e2fdf6c573c118198bb8b5663d8416ed935 --- src/nmodl/language/CMakeLists.txt | 4 +- src/nmodl/language/templates/ast_visitor.cpp | 2 + src/nmodl/language/templates/ast_visitor.hpp | 3 +- src/nmodl/language/templates/json_visitor.cpp | 2 + src/nmodl/language/templates/json_visitor.hpp | 2 +- .../language/templates/lookup_visitor.cpp | 32 +++++++ .../language/templates/lookup_visitor.hpp | 28 ++++++ .../language/templates/symtab_visitor.cpp | 2 + .../language/templates/symtab_visitor.hpp | 18 ++-- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/perf_visitor.cpp | 1 - test/nmodl/transpiler/visitor/visitor.cpp | 87 ++++++++++++++++++- 12 files changed, 168 insertions(+), 15 deletions(-) create mode 100644 src/nmodl/language/templates/lookup_visitor.cpp create mode 100644 src/nmodl/language/templates/lookup_visitor.hpp diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 363ba98d16..ef7c9ab271 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -9,6 +9,8 @@ set ( AUTOGEN_FILES ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp @@ -48,4 +50,4 @@ endif() #============================================================================= # Target to propogate dependencies properly to lexer #============================================================================= -add_custom_target (pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) \ No newline at end of file +add_custom_target (pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) diff --git a/src/nmodl/language/templates/ast_visitor.cpp b/src/nmodl/language/templates/ast_visitor.cpp index c01ab0841c..2ce3046175 100644 --- a/src/nmodl/language/templates/ast_visitor.cpp +++ b/src/nmodl/language/templates/ast_visitor.cpp @@ -1,5 +1,7 @@ #include "visitors/ast_visitor.hpp" +using namespace ast; + {% for node in nodes %} void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { node->visit_children(this); diff --git a/src/nmodl/language/templates/ast_visitor.hpp b/src/nmodl/language/templates/ast_visitor.hpp index ca3f0689c0..e4ae7822c1 100644 --- a/src/nmodl/language/templates/ast_visitor.hpp +++ b/src/nmodl/language/templates/ast_visitor.hpp @@ -2,7 +2,6 @@ #include "ast/ast.hpp" #include "visitors/visitor.hpp" -using namespace ast; /* Basic visitor implementation */ @@ -10,6 +9,6 @@ class AstVisitor : public Visitor { public: {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/json_visitor.cpp index e2530d1a99..ab2cd6c40a 100644 --- a/src/nmodl/language/templates/json_visitor.cpp +++ b/src/nmodl/language/templates/json_visitor.cpp @@ -1,5 +1,7 @@ #include "visitors/json_visitor.hpp" +using namespace ast; + {% for node in nodes %} void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { {% if node.has_children() %} diff --git a/src/nmodl/language/templates/json_visitor.hpp b/src/nmodl/language/templates/json_visitor.hpp index d03a7ba389..04169d57b8 100644 --- a/src/nmodl/language/templates/json_visitor.hpp +++ b/src/nmodl/language/templates/json_visitor.hpp @@ -19,6 +19,6 @@ class JSONVisitor : public AstVisitor { void compact_json(bool flag) { printer->compact_json(flag); } {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/lookup_visitor.cpp new file mode 100644 index 0000000000..8c9092a7d8 --- /dev/null +++ b/src/nmodl/language/templates/lookup_visitor.cpp @@ -0,0 +1,32 @@ +#include <algorithm> +#include "visitors/lookup_visitor.hpp" + +using namespace ast; + +{% for node in nodes %} +void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { + auto type = node->get_node_type(); + if(std::find(types.begin(), types.end(), type) != types.end()) { + nodes.push_back(node); + } + node->visit_children(this); +} + +{% endfor %} + + +std::vector<AST*> AstLookupVisitor::lookup(Program* node, std::vector<AstNodeType>& _types) { + nodes.clear(); + types = _types; + node->accept(this); + return nodes; +} + + +std::vector<AST*> AstLookupVisitor::lookup(Program* node, AstNodeType type) { + nodes.clear(); + types.clear(); + types.push_back(type); + node->accept(this); + return nodes; +} diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp new file mode 100644 index 0000000000..4f61ae8150 --- /dev/null +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "ast/ast.hpp" +#include "visitors/visitor.hpp" + + +/** + * \class AstLookupVisitor + * \brief Visitor to find ast nodes based on on the ast types + */ + +class AstLookupVisitor : public Visitor { + private: + /// node types to search in the ast + std::vector<ast::AstNodeType> types; + + /// matching nodes found in the ast + std::vector<ast::AST*> nodes; + + public: + std::vector<ast::AST*> lookup(ast::Program* node, ast::AstNodeType type); + + std::vector<ast::AST*> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); + + {% for node in nodes %} + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} +}; diff --git a/src/nmodl/language/templates/symtab_visitor.cpp b/src/nmodl/language/templates/symtab_visitor.cpp index 0b60b732d4..24befc9f47 100644 --- a/src/nmodl/language/templates/symtab_visitor.cpp +++ b/src/nmodl/language/templates/symtab_visitor.cpp @@ -2,6 +2,8 @@ #include "visitors/symtab_visitor.hpp" #include "visitors/symtab_visitor_helper.hpp" +using namespace ast; + {% for node in nodes %} {% if node.is_symtab_method_required and not node.is_symbol_helper_node %} {% set typename = node.class_name|snake_case %} diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp index 355810e245..87262d957e 100644 --- a/src/nmodl/language/templates/symtab_visitor.hpp +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -21,25 +21,25 @@ class SymtabVisitor : public AstVisitor { SymtabVisitor(std::stringstream &ss, bool update = false) : printer(new JSONPrinter(ss)), update(update) {} SymtabVisitor(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {} - void add_model_symbol_with_property(Node* node, NmodlTypeFlag property); + void add_model_symbol_with_property(ast::Node* node, NmodlTypeFlag property); - void setup_symbol(Node* node, NmodlTypeFlag property); + void setup_symbol(ast::Node* node, NmodlTypeFlag property); - void setup_symbol_table(AST* node, const std::string& name, bool is_global); + void setup_symbol_table(ast::AST* node, const std::string& name, bool is_global); - void setup_symbol_table(Node* node, const std::string& name, NmodlTypeFlag property, bool is_global); + void setup_symbol_table(ast::Node* node, const std::string& name, NmodlTypeFlag property, bool is_global); - void setup_program_symbol_table(Node* node, const std::string& name, bool is_global); + void setup_program_symbol_table(ast::Node* node, const std::string& name, bool is_global); - void setup_symbol_table_for_program_block(Program* node); + void setup_symbol_table_for_program_block(ast::Program* node); - void setup_symbol_table_for_global_block(Node* node); + void setup_symbol_table_for_global_block(ast::Node* node); - void setup_symbol_table_for_scoped_block(Node* node, const std::string& name); + void setup_symbol_table_for_scoped_block(ast::Node* node, const std::string& name); {% for node in nodes %} {% if node.is_symtab_method_required %} - void visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endif %} {% endfor %} }; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 41a01953ea..de27ab1b8e 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -32,6 +32,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.cpp ) set_source_files_properties( diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 3e050b96db..96628068e1 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -260,7 +260,6 @@ void PerfVisitor::count_variables() { void PerfVisitor::print_memory_usage() { stream << std::endl; - stream << "#VARIABLES :: "; stream << " INSTANCE : " << num_instance_variables << " "; stream << "[ CONSTANT " << num_constant_instance_variables << ", "; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 69e387454d..485f1bc862 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -3,6 +3,7 @@ #include <string> #include "catch/catch.hpp" + #include "parser/nmodl_driver.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" @@ -16,10 +17,12 @@ #include "visitors/localize_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/lookup_visitor.hpp" #include "test/utils/nmodl_constructs.h" #include "test/utils/test_utils.hpp" using json = nlohmann::json; +using namespace ast; //============================================================================= // Verbatim visitor tests @@ -1259,7 +1262,7 @@ std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::stri DefUseAnalyzeVisitor v(ast->get_symbol_table()); for (const auto& block : ast->get_blocks()) { - if (block->get_node_type() != ast::AstNodeType::NEURON_BLOCK) { + if (block->get_node_type() != AstNodeType::NEURON_BLOCK) { chains.push_back(v.analyze(block.get(), variable)); } } @@ -1869,3 +1872,85 @@ SCENARIO("Running visitor passes multiple time") { } } } + + +//============================================================================= +// Ast lookup visitor tests +//============================================================================= + +std::vector<AST*> run_lookup_visitor(Program* node, std::vector<AstNodeType>& types) { + AstLookupVisitor v; + return v.lookup(node, types); +} + +SCENARIO("Searching for ast nodes using AstLookupVisitor") { + auto to_ast = [](const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + return driver.ast(); + }; + + auto to_nmodl = [](AST* node) { + std::stringstream stream; + NmodlPrintVisitor v(stream); + node->accept(&v); + return stream.str(); + }; + + GIVEN("A mod file with nodes of type NEURON, RANGE, BinaryExpression") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, h + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + h' = h + 2 + } + + : My comment here + )"; + + auto ast = to_ast(nmodl_text); + + WHEN("Looking for existing nodes") { + THEN("Can find RANGE variables") { + std::vector<AstNodeType> types{AstNodeType::RANGE_VAR}; + auto result = run_lookup_visitor(ast.get(), types); + REQUIRE(result.size() == 2); + REQUIRE(to_nmodl(result[0]) == "tau"); + REQUIRE(to_nmodl(result[1]) == "h"); + } + + THEN("Can find NEURON block") { + std::vector<AstNodeType> types{AstNodeType::NEURON_BLOCK}; + auto nodes = run_lookup_visitor(ast.get(), types); + REQUIRE(nodes.size() == 1); + + std::string neuron_block = R"( + NEURON { + RANGE tau, h + })"; + auto result = reindent_text(to_nmodl(nodes[0])); + auto expected = reindent_text(neuron_block); + REQUIRE(result == expected); + } + + THEN("Can find Binary Expressions and function call") { + std::vector<AstNodeType> types{AstNodeType::BINARY_EXPRESSION, + AstNodeType::FUNCTION_CALL}; + auto result = run_lookup_visitor(ast.get(), types); + REQUIRE(result.size() == 4); + } + } + + WHEN("Looking for missing nodes") { + THEN("Can not find BREAKPOINT block") { + std::vector<AstNodeType> types{AstNodeType::BREAKPOINT_BLOCK}; + auto result = run_lookup_visitor(ast.get(), types); + REQUIRE(result.size() == 0); + } + } + } +} From 64844b1b65593cc02653dd0b8bbc98152a972124 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Mon, 21 Jan 2019 15:32:18 +0100 Subject: [PATCH 115/871] Python bindings for NMODL The bindings are implemented using pybind11 and all boilerplate code has been generated using jinja2 templates. - Created bindings for all AST nodes and Visitor base classes - Created bindings for JSON and NMODL printer classes - Added `__repr__` properties for AST nodes printing a human-readable JSON representation using the description strings in `nmodl.yaml` - Added bindings for NMODL parser - Added C++ glue code for directly writing to and reading from python `io.IOBase`. This is based on the pybind11 `stream_buffer` wrapper and using an additiona buffer class for input streams. - updated CMake files to properly build pybind interface - Created a setuptools script for building the package and creating pypi package complete with .so module and python modules. - Added pytest stub for unit tests - Hooked pytest into CMake ctest - disabled clang-format for src/utils/logger.hpp header to avoid breaking spdlog Also, but not directly related: - reverted renaming statement_block to block. After having discussed with Pramod `statement_block` seems clearer Change-Id: Ibea53f7d14d1699defdc9ed4bf25a898c113668a NMODL Repo SHA: BlueBrain/nmodl@4b59b9dffcb86aa3299a9e4d713f361f8d5feebe --- README.md | 6 +- cmake/nmodl/CMakeLists.txt | 24 ++++ nmodl/__init__.py | 2 + setup.py | 73 +++++++++++ src/nmodl/ast/ast_common.hpp | 4 +- .../codegen/base/codegen_base_visitor.cpp | 18 +-- .../codegen/base/codegen_base_visitor.hpp | 4 +- .../codegen/base/codegen_helper_visitor.cpp | 2 +- .../codegen/base/codegen_helper_visitor.hpp | 2 +- .../codegen/c-cuda/codegen_c_cuda_visitor.cpp | 2 +- .../c-openacc/codegen_c_acc_visitor.cpp | 2 +- src/nmodl/codegen/c/codegen_c_visitor.cpp | 32 ++--- src/nmodl/codegen/c/codegen_c_visitor.hpp | 6 +- src/nmodl/language/CMakeLists.txt | 24 +--- src/nmodl/language/code_generator.py | 7 +- src/nmodl/language/nmodl.yaml | 46 +++---- src/nmodl/language/nodes.py | 13 +- src/nmodl/language/templates/ast.hpp | 2 +- .../language/templates/nmodl_visitor.hpp | 2 +- src/nmodl/language/templates/pyast.cpp | 100 +++++++++++++++ src/nmodl/language/templates/pyast.hpp | 71 ++++++++++ src/nmodl/language/templates/pyvisitor.cpp | 83 ++++++++++++ src/nmodl/language/templates/pyvisitor.hpp | 27 ++++ src/nmodl/language/templates/visitor.hpp | 2 + src/nmodl/lexer/CMakeLists.txt | 1 + src/nmodl/lexer/token_mapping.hpp | 2 +- src/nmodl/nmodl/arg_handler.cpp | 2 +- src/nmodl/nmodl/arg_handler.hpp | 2 +- src/nmodl/nmodl/main.cpp | 20 +-- src/nmodl/printer/CMakeLists.txt | 1 + src/nmodl/printer/code_printer.hpp | 2 +- src/nmodl/printer/json_printer.hpp | 2 +- src/nmodl/printer/nmodl_printer.hpp | 4 +- src/nmodl/pybind/CMakeLists.txt | 43 +++++++ src/nmodl/pybind/pynmodl.cpp | 121 ++++++++++++++++++ src/nmodl/symtab/symbol_properties.cpp | 4 +- src/nmodl/utils/logger.hpp | 2 + src/nmodl/utils/table_data.cpp | 4 +- src/nmodl/visitors/CMakeLists.txt | 1 + src/nmodl/visitors/cnexp_solve_visitor.cpp | 4 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.cpp | 4 +- src/nmodl/visitors/inline_visitor.hpp | 4 +- .../visitors/local_var_rename_visitor.hpp | 4 +- src/nmodl/visitors/localize_visitor.cpp | 4 +- src/nmodl/visitors/localize_visitor.hpp | 4 +- src/nmodl/visitors/main.cpp | 8 +- src/nmodl/visitors/perf_visitor.hpp | 2 +- src/nmodl/visitors/rename_visitor.hpp | 2 +- .../visitors/verbatim_var_rename_visitor.hpp | 4 +- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 4 +- src/nmodl/visitors/visitor_utils.hpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 9 ++ test/nmodl/transpiler/parser/parser.cpp | 2 +- test/nmodl/transpiler/pybind/__init__.py | 0 test/nmodl/transpiler/pybind/test_ast.py | 9 ++ test/nmodl/transpiler/symtab/symbol_table.cpp | 2 +- .../nmodl/transpiler/utils/nmodl_constructs.h | 2 +- test/nmodl/transpiler/visitor/visitor.cpp | 16 +-- 60 files changed, 712 insertions(+), 143 deletions(-) create mode 100644 nmodl/__init__.py create mode 100644 setup.py create mode 100644 src/nmodl/language/templates/pyast.cpp create mode 100644 src/nmodl/language/templates/pyast.hpp create mode 100644 src/nmodl/language/templates/pyvisitor.cpp create mode 100644 src/nmodl/language/templates/pyvisitor.hpp create mode 100644 src/nmodl/pybind/CMakeLists.txt create mode 100644 src/nmodl/pybind/pynmodl.cpp create mode 100644 test/nmodl/transpiler/pybind/__init__.py create mode 100644 test/nmodl/transpiler/pybind/test_ast.py diff --git a/README.md b/README.md index e6cbe46ed6..17b641c8f1 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,12 @@ This is a source-to-source code generation framework for NMODL. #### Cloning Source ``` -git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl +git clone --recurse-submodules ssh://bbpcode.epfl.ch:22/incubator/nocmodl ``` +Note: This project uses git submodules which must be cloned along with the repository +itself. + #### Dependencies - flex (>=2.6) @@ -20,6 +23,7 @@ git clone ssh://bbpcode.epfl.ch:22/incubator/nocmodl - Jinja2 (>=2.10) - Python textwrap - pybind11 (which should be fetched in its submodule in ext/pybind11) +- pytest (>=4.0.0) (only for tests) #### Getting Dependencies diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 1ff2677b4d..49264d1213 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -67,6 +67,28 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) +#============================================================================= +# list of autogenerated files +#============================================================================= +set ( AUTO_GENERATED_FILES + ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyast.hpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyast.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.hpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.cpp + ) + add_subdirectory(src/ast) add_subdirectory(src/codegen) add_subdirectory(src/language) @@ -78,6 +100,8 @@ add_subdirectory(src/symtab) add_subdirectory(src/utils) add_subdirectory(src/visitors) +add_subdirectory(src/pybind) + #============================================================================= # Memory checker options and add tests #============================================================================= diff --git a/nmodl/__init__.py b/nmodl/__init__.py new file mode 100644 index 0000000000..eb5e3d31a5 --- /dev/null +++ b/nmodl/__init__.py @@ -0,0 +1,2 @@ + +from _nmodl import * \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000000..d3d6595e9c --- /dev/null +++ b/setup.py @@ -0,0 +1,73 @@ +import os +import re +import sys +import platform +import subprocess + +from setuptools import setup, Extension +from setuptools.command.build_ext import build_ext +from distutils.version import LooseVersion + + +class CMakeExtension(Extension): + def __init__(self, name, sourcedir=''): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) + + +class CMakeBuild(build_ext): + def run(self): + try: + out = subprocess.check_output(['cmake', '--version']) + except OSError: + raise RuntimeError("CMake must be installed to build the following extensions: " + + ", ".join(e.name for e in self.extensions)) + + cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) + if cmake_version < '3.1.0': + raise RuntimeError("CMake >= 3.1.0 is required") + + for ext in self.extensions: + self.build_extension(ext) + + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, + '-DPYTHON_EXECUTABLE=' + sys.executable] + + cfg = 'Debug' if self.debug else 'Release' + build_args = ['--config', cfg] + + if platform.system() == "Windows": + cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] + if sys.maxsize > 2**32: + cmake_args += ['-A', 'x64'] + build_args += ['--', '/m'] + else: + cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] + build_args += ['--', '-j2'] + + env = os.environ.copy() + env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), + self.distribution.get_version()) + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) + subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) + subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + +setup( + name='NMODL', + version='0.0.1', + author='The Blue Brain Project', + author_email='omar.awile@epfl.ch', + description='A NEURON modelling language source-to-source compiler framework', + long_description='', + packages=['nmodl'], + ext_modules=[CMakeExtension('nmodl')], + cmdclass=dict(build_ext=CMakeBuild), + zip_safe=False, + setup_requires=['jinja2>=2.10', + 'PyYAML>=3.13', + ], + tests_require=['pytest>=4.0.0'] +) \ No newline at end of file diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 80faee8102..efc17429bd 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -95,8 +95,8 @@ namespace ast { throw std::runtime_error("get_symbol_table() not implemented"); } - virtual std::shared_ptr<StatementBlock> get_block() { - throw std::runtime_error("get_block not implemented"); + virtual std::shared_ptr<StatementBlock> get_statement_block() { + throw std::runtime_error("get_statement_block not implemented"); } // implemented in Number sub classes diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp index 8cf7e681a6..a7a9a24469 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.cpp @@ -1,15 +1,15 @@ #include <algorithm> -#include <utility> #include <cmath> #include <ctime> +#include <utility> -#include "visitors/rename_visitor.hpp" #include "codegen/base/codegen_helper_visitor.hpp" #include "codegen/c/codegen_c_visitor.hpp" -#include "visitors/var_usage_visitor.hpp" +#include "parser/c11_driver.hpp" #include "utils/string_utils.hpp" #include "version/version.h" -#include "parser/c11_driver.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/var_usage_visitor.hpp" using namespace ast; @@ -142,7 +142,7 @@ void CodegenBaseVisitor::visit_if_statement(IfStatement* node) { printer->add_text("if ("); node->get_condition()->accept(this); printer->add_text(") "); - node->get_block()->accept(this); + node->get_statement_block()->accept(this); print_vector_elements(node->get_elseifs(), ""); auto elses = node->get_elses(); if (elses) { @@ -158,7 +158,7 @@ void CodegenBaseVisitor::visit_else_if_statement(ElseIfStatement* node) { printer->add_text(" else if ("); node->get_condition()->accept(this); printer->add_text(") "); - node->get_block()->accept(this); + node->get_statement_block()->accept(this); } @@ -174,7 +174,7 @@ void CodegenBaseVisitor::visit_while_statement(WhileStatement* node) { printer->add_text("while ("); node->get_condition()->accept(this); printer->add_text(") "); - node->get_block()->accept(this); + node->get_statement_block()->accept(this); } void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { @@ -185,7 +185,7 @@ void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { auto from = node->get_from(); auto to = node->get_to(); auto inc = node->get_increment(); - auto block = node->get_block(); + auto block = node->get_statement_block(); printer->add_text("for(int {}="_format(name)); from->accept(this); printer->add_text("; {}<="_format(name)); @@ -478,7 +478,7 @@ std::string CodegenBaseVisitor::breakpoint_current(std::string current) { if (breakpoint == nullptr) { return current; } - auto symtab = breakpoint->get_block()->get_symbol_table(); + auto symtab = breakpoint->get_statement_block()->get_symbol_table(); auto variables = symtab->get_variables_with_properties(NmodlType::local_var); for (auto& var : variables) { auto renamed_name = var->get_name(); diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp index bb39d3e1d5..ad8746d7b9 100644 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_base_visitor.hpp @@ -2,11 +2,11 @@ #define NMODL_CODEGEN_BASE_VISITOR_HPP -#include <string> #include <algorithm> +#include <string> -#include "fmt/format.h" #include "codegen/codegen_info.hpp" +#include "fmt/format.h" #include "printer/code_printer.hpp" #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index ca2d0b3d45..c1c47ea61d 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -1,8 +1,8 @@ #include <algorithm> #include <cmath> -#include "visitors/rename_visitor.hpp" #include "codegen/base/codegen_helper_visitor.hpp" +#include "visitors/rename_visitor.hpp" #include <fmt/format.h> diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/base/codegen_helper_visitor.hpp index ae737143ff..5224daa0b8 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.hpp @@ -3,9 +3,9 @@ #include <string> +#include "codegen/codegen_info.hpp" #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" -#include "codegen/codegen_info.hpp" /** diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp index b95ae0af59..14cda8f4bb 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp +++ b/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp @@ -1,7 +1,7 @@ #include <fmt/format.h> -#include "symtab/symbol_table.hpp" #include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" +#include "symtab/symbol_table.hpp" #include "utils/string_utils.hpp" using namespace fmt::literals; diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp index 4316fe6c47..54aa67d4f8 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp +++ b/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp @@ -1,5 +1,5 @@ -#include <fmt/format.h> #include "codegen/c-openacc/codegen_c_acc_visitor.hpp" +#include <fmt/format.h> using namespace fmt::literals; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/c/codegen_c_visitor.cpp index b3cbf756b7..521df5e720 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.cpp @@ -2,13 +2,13 @@ #include <cmath> #include <ctime> -#include "visitors/rename_visitor.hpp" #include "codegen/base/codegen_helper_visitor.hpp" #include "codegen/c/codegen_c_visitor.hpp" -#include "visitors/var_usage_visitor.hpp" +#include "parser/c11_driver.hpp" #include "utils/string_utils.hpp" #include "version/version.h" -#include "parser/c11_driver.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/var_usage_visitor.hpp" using namespace ast; @@ -68,7 +68,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { if (breakpoint == nullptr) { return current; } - auto symtab = breakpoint->get_block()->get_symbol_table(); + auto symtab = breakpoint->get_statement_block()->get_symbol_table(); auto variables = symtab->get_variables_with_properties(NmodlType::local_var); for (auto& var : variables) { auto renamed_name = var->get_name(); @@ -878,7 +878,7 @@ void CodegenCVisitor::print_check_table_thread_function() { } void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& name) { - auto block = node->get_block().get(); + auto block = node->get_statement_block().get(); printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); @@ -921,7 +921,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { auto return_var = "ret_" + name; /// first rename return variable name - auto block = node->get_block().get(); + auto block = node->get_statement_block().get(); RenameVisitor v(name, return_var); block->accept(&v); @@ -2261,7 +2261,7 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { /// initial block if (node != nullptr) { - auto block = node->get_block(); + auto block = node->get_statement_block(); print_statement_block(block.get(), false, false); } @@ -2608,7 +2608,7 @@ void CodegenCVisitor::print_net_init_kernel() { printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); printer->start_block("static void net_init({}) "_format(args)); - auto block = node->get_block().get(); + auto block = node->get_statement_block().get(); if (block->get_statements().empty()) { printer->add_line("// do nothing"); } else { @@ -2724,7 +2724,7 @@ void CodegenCVisitor::print_net_receive() { auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); - node->get_block()->visit_children(&vr); + node->get_statement_block()->visit_children(&vr); } } @@ -2750,7 +2750,7 @@ void CodegenCVisitor::print_net_receive() { printer->add_line("{} = t;"_format(get_variable_name("tsave"))); printer->add_indent(); - node->get_block()->accept(this); + node->get_statement_block()->accept(this); printer->add_newline(); printer->end_block(); printer->add_newline(); @@ -2797,7 +2797,7 @@ void CodegenCVisitor::print_derivative_kernel_for_euler() { printer->add_line("/* _euler_ state _{} */"_format(info.mod_suffix)); printer->start_block("int {}_{}({})"_format(node->get_node_name(), info.mod_suffix, arguments)); printer->add_line(instance); - print_statement_block(node->get_block().get(), false, false); + print_statement_block(node->get_statement_block().get(), false, false); printer->add_line("return 0;"); printer->end_block(); printer->add_newline(); @@ -2856,7 +2856,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); - print_statement_block(node->get_block().get(), false, false); + print_statement_block(node->get_statement_block().get(), false, false); printer->add_line("int counter = -1;"); printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); @@ -2932,13 +2932,13 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line(statement); } else { if (info.solve_node != nullptr) { - auto block = info.solve_node->get_block(); + auto block = info.solve_node->get_statement_block(); print_statement_block(block.get(), false, false); } } if (info.currents.empty() && info.breakpoint_node != nullptr) { - auto block = info.breakpoint_node->get_block(); + auto block = info.breakpoint_node->get_statement_block(); print_statement_block(block.get(), false, false); } @@ -2969,7 +2969,7 @@ void CodegenCVisitor::print_nrn_state() { void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { auto args = internal_method_parameters(); - auto block = node->get_block().get(); + auto block = node->get_statement_block().get(); printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline double nrn_current({})"_format(args)); @@ -2986,7 +2986,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { - auto block = node->get_block(); + auto block = node->get_statement_block(); print_statement_block(block.get(), false, false); if (!info.currents.empty()) { std::string sum; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/c/codegen_c_visitor.hpp index 1adacd8d62..2631d45957 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/c/codegen_c_visitor.hpp @@ -1,15 +1,15 @@ #ifndef NMODL_CODEGEN_C_VISITOR_HPP #define NMODL_CODEGEN_C_VISITOR_HPP -#include <string> #include <algorithm> +#include <string> -#include "fmt/format.h" +#include "codegen/base/codegen_base_visitor.hpp" #include "codegen/codegen_info.hpp" +#include "fmt/format.h" #include "printer/code_printer.hpp" #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" -#include "codegen/base/codegen_base_visitor.hpp" using namespace fmt::literals; diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index ef7c9ab271..cee415770d 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -1,21 +1,8 @@ #============================================================================= # Command to generate AST/Visitor classes from language definition #============================================================================= -set ( AUTOGEN_FILES ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.cpp - ) +set_source_files_properties( ${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) + file ( GLOB TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" @@ -24,12 +11,11 @@ file ( GLOB TEMPLATE_FILES file ( GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py" ) - if(CLANG_FORMAT_FOUND) add_custom_command ( - OUTPUT ${AUTOGEN_FILES} + OUTPUT ${AUTO_GENERATED_FILES} COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - COMMAND ${CLANG_FORMAT_EXECUTABLE} ARGS -i --style=file ${AUTOGEN_FILES} + COMMAND ${CLANG_FORMAT_EXECUTABLE} ARGS -i --style=file ${AUTO_GENERATED_FILES} WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PYCODE} @@ -38,7 +24,7 @@ if(CLANG_FORMAT_FOUND) ) else() add_custom_command ( - OUTPUT ${AUTOGEN_FILES} + OUTPUT ${AUTO_GENERATED_FILES} COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index db0e041474..62e4bd9118 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -17,7 +17,12 @@ env.filters['snake_case'] = utils.to_snake_case for fn in templates.glob('*.[ch]pp'): - filename = basedir / ('visitors' if 'visitor' in fn.name else 'ast') / fn.name + if fn.name.startswith('py'): + filename = basedir / 'pybind' / fn.name + elif 'visitor' in fn.name: + filename = basedir / 'visitors' / fn.name + else: + filename = basedir / 'ast' / fn.name template = env.get_template(fn.name) with filename.open('w') as fd: fd.write(template.render(nodes=nodes, node_info=node_info)) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index bcfce0a42f..48da45e41f 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -319,7 +319,7 @@ description: ".." nmodl: "INITIAL " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -327,7 +327,7 @@ description: ".." nmodl: "CONSTRUCTOR " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -335,7 +335,7 @@ description: ".." nmodl: "DESTRUCTOR " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -356,7 +356,7 @@ type: Name node_name: true suffix: {value: " "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -375,7 +375,7 @@ vector: true separator: "," prefix: {value: " SOLVEFOR "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -394,7 +394,7 @@ separator: "," prefix: {value: " SOLVEFOR "} suffix: {value: " ", force: true} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -407,7 +407,7 @@ type: Name node_name: true suffix: {value: " "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -420,7 +420,7 @@ type: Name node_name: true suffix: {value: " "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -467,7 +467,7 @@ optional: true prefix: {value: " "} suffix: {value: " ", force: true} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -491,7 +491,7 @@ description: "..." type: Unit optional: true - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -507,7 +507,7 @@ suffix: {value: ") ", force: true} separator: ", " getter: {override: true} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -533,7 +533,7 @@ description: ".." nmodl: "BREAKPOINT " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -541,7 +541,7 @@ description: ".." nmodl: "TERMINAL " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -566,7 +566,7 @@ description: "..." type: BABlockType suffix: {value: " "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -582,7 +582,7 @@ suffix: {value: ") ", force: true} separator: ", " getter: {override: true} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -600,7 +600,7 @@ type: Name vector: true separator: "," - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -636,7 +636,7 @@ url: "https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl2.html#neuron" nmodl: "NEURON " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -1115,7 +1115,7 @@ prefix: {value: " BY "} suffix: {value: " ", force: true} optional: true - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -1127,7 +1127,7 @@ description: "..." type: Name suffix: {value: " "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -1140,7 +1140,7 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -1153,7 +1153,7 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -1174,7 +1174,7 @@ type: Expression prefix: {value: "("} suffix: {value: ") "} - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} @@ -1182,7 +1182,7 @@ description: ".." nmodl: " ELSE " members: - - block: + - statement_block: description: "..." type: StatementBlock getter: {override: true} diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index f2a5ee4f51..016e81e026 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -184,12 +184,17 @@ def member_typename(self): return type_name - def get_add_method(self): + def get_add_methods(self): s = '' if self.add_method: - method = f"""void add{self.class_name}({self.class_name} *s) {{ - {self.varname}.emplace_back(s); - }}""" + method = f"""void add{self.class_name}({self.class_name} *n) {{ + {self.varname}.emplace_back(n); + }} + + void add{self.class_name}(std::shared_ptr<{self.class_name}> n) {{ + {self.varname}.push_back(n); + }} + """ s = textwrap.dedent(method) return s diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index 9af81011b2..bf046e0ca4 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -52,7 +52,7 @@ namespace ast { virtual ~{{ node.class_name }}() {} {% for child in node.children %} - {{ child.get_add_method() }} + {{ child.get_add_methods() }} {{ child.get_node_name_method() }} diff --git a/src/nmodl/language/templates/nmodl_visitor.hpp b/src/nmodl/language/templates/nmodl_visitor.hpp index a34595a6f5..6dd32b454f 100644 --- a/src/nmodl/language/templates/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/nmodl_visitor.hpp @@ -12,7 +12,7 @@ class NmodlPrintVisitor : public Visitor { public: NmodlPrintVisitor() : printer(new NMODLPrinter()) {} NmodlPrintVisitor(std::string filename) : printer(new NMODLPrinter(filename)) {} - NmodlPrintVisitor(std::stringstream& stream) : printer(new NMODLPrinter(stream)) {} + NmodlPrintVisitor(std::ostream& stream) : printer(new NMODLPrinter(stream)) {} {% for node in nodes %} virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pyast.cpp new file mode 100644 index 0000000000..9758d0e239 --- /dev/null +++ b/src/nmodl/language/templates/pyast.cpp @@ -0,0 +1,100 @@ +#include "pybind/pyast.hpp" +#include "visitors/json_visitor.hpp" +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "OCDFAInspection" +{% macro var(node) -%} +{{ node.class_name | snake_case }}_ +{%- endmacro -%} + +{% macro args(children) %} +{% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} +{%- endmacro -%} + +namespace py = pybind11; +using namespace ast; + +void init_ast_module(py::module& m) { + + py::module m_ast = m.def_submodule("ast"); + + py::enum_<BinaryOp>(m_ast, "BinaryOp") + .value("BOP_ADDITION", BinaryOp::BOP_ADDITION) + .value("BOP_SUBTRACTION", BinaryOp::BOP_SUBTRACTION) + .value("BOP_MULTIPLICATION", BinaryOp::BOP_MULTIPLICATION) + .value("BOP_DIVISION", BinaryOp::BOP_DIVISION) + .value("BOP_POWER", BinaryOp::BOP_POWER) + .value("BOP_AND", BinaryOp::BOP_AND) + .value("BOP_OR", BinaryOp::BOP_OR) + .value("BOP_GREATER", BinaryOp::BOP_GREATER) + .value("BOP_LESS", BinaryOp::BOP_LESS) + .value("BOP_GREATER_EQUAL", BinaryOp::BOP_GREATER_EQUAL) + .value("BOP_LESS_EQUAL", BinaryOp::BOP_LESS_EQUAL) + .value("BOP_ASSIGN", BinaryOp::BOP_ASSIGN) + .value("BOP_NOT_EQUAL", BinaryOp::BOP_NOT_EQUAL) + .value("BOP_EXACT_EQUAL", BinaryOp::BOP_EXACT_EQUAL) + .export_values(); + + py::enum_<AstNodeType>(m_ast, "AstNodeType") + {% for node in nodes %} + .value("{{ node.class_name|snake_case|upper }}", AstNodeType::{{ node.class_name|snake_case|upper }}) + {% endfor %} + .export_values(); + + + + py::class_<AST, PyAST> ast_(m_ast, "AST"); + ast_.def(py::init<>()) + .def("visit_children", &AST::visit_children) + .def("accept", &AST::accept) + .def("get_node_type", &AST::get_node_type) + .def("get_node_type_name", &AST::get_node_type_name) + .def("get_node_name", &AST::get_node_name) + .def("clone", &AST::clone) + .def("get_token", &AST::get_token) + .def("set_symbol_table", &AST::set_symbol_table) + .def("get_symbol_table", &AST::get_symbol_table) + .def("get_statement_block", &AST::get_statement_block) + .def("negate", &AST::negate) + .def("set_name", &AST::set_name) + .def("is_ast", &AST::is_ast) + {% for node in nodes %} + .def("is_{{ node.class_name | snake_case }}", &AST::is_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} + + {% for node in nodes %} + py::class_<{{ node.class_name }}, std::shared_ptr<{{ node.class_name }}>> {{ var(node) }}(m_ast, "{{ node.class_name }}", {{ node.base_class | snake_case }}_); + {{ var(node) }}.doc() = "{{ node.description }}"; + {% if node.children %} + {{ var(node) }}.def(py::init<{{ args(node.children) }}>()); + {% endif %} + {% if node.is_program_node or node.is_ptr_excluded_node %} + {{ var(node) }}.def(py::init<>()); + {% endif %} + + {{ var(node) }}.def("__repr__", []({{ node.class_name }}& n) { + std::stringstream ss; + JSONVisitor v(ss); + v.compact_json(true); + n.accept(&v); + return ss.str(); + }); + + {% for member in node.public_members() %} + {{ var(node) }}.def_readwrite("{{ member[1] }}", &{{ node.class_name }}::{{ member[1] }}); + {% endfor %} + + {{ var(node) }}.def("visit_children", &{{ node.class_name }}::visit_children) + .def("accept", &{{ node.class_name }}::accept) + .def("clone", &{{ node.class_name }}::clone) + .def("get_node_type", &{{ node.class_name }}::get_node_type) + .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name) + .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}); + + {% endfor %} +} + +#pragma clang diagnostic pop \ No newline at end of file diff --git a/src/nmodl/language/templates/pyast.hpp b/src/nmodl/language/templates/pyast.hpp new file mode 100644 index 0000000000..261b9fc8a0 --- /dev/null +++ b/src/nmodl/language/templates/pyast.hpp @@ -0,0 +1,71 @@ +#pragma once + + +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include "ast/ast.hpp" +#include "lexer/modtoken.hpp" +#include "symtab/symbol_table.hpp" + + +namespace ast { + +struct PyAST: public AST { + public: + using AST::AST; + + void visit_children(Visitor* v) override { + PYBIND11_OVERLOAD_PURE(void, // Return type + AST, // Parent class + visit_children, // Name of function in C++ (must match Python name) + v // Argument(s) + ); + } + + void accept(Visitor* v) override { PYBIND11_OVERLOAD_PURE(void, AST, accept, v); } + + AstNodeType get_node_type() override { + PYBIND11_OVERLOAD_PURE(AstNodeType, // Return type + AST, // Parent class + get_node_type, // Name of function in C++ (must match Python name) + // No argument (trailing ,) + ); + } + + std::string get_node_type_name() override { + PYBIND11_OVERLOAD_PURE(std::string, AST, get_node_type_name, ); + } + + std::string get_node_name() override { PYBIND11_OVERLOAD(std::string, AST, get_node_name, ); } + + AST* clone() override { PYBIND11_OVERLOAD(AST*, AST, clone, ); } + + ModToken* get_token() override { PYBIND11_OVERLOAD(ModToken*, AST, get_token, ); } + + void set_symbol_table(symtab::SymbolTable* newsymtab) override { + PYBIND11_OVERLOAD(void, AST, set_symbol_table, newsymtab); + } + + symtab::SymbolTable* get_symbol_table() override { + PYBIND11_OVERLOAD(symtab::SymbolTable*, AST, get_symbol_table, ); + } + std::shared_ptr<StatementBlock> get_statement_block() override { + PYBIND11_OVERLOAD(std::shared_ptr<StatementBlock>, AST, get_statement_block, ); + } + + void negate() override { PYBIND11_OVERLOAD(void, AST, negate, ); } + + void set_name(std::string name) override { PYBIND11_OVERLOAD(void, AST, set_name, name); } + + bool is_ast() override { PYBIND11_OVERLOAD(bool, AST, is_ast, ); } + + {% for node in nodes %} + + bool is_{{node.class_name | snake_case}}() override { + PYBIND11_OVERLOAD(bool, AST, is_{{node.class_name | snake_case}}, ); + } + + {% endfor %} +}; + +} // namespace ast \ No newline at end of file diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp new file mode 100644 index 0000000000..b3f28e5af3 --- /dev/null +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -0,0 +1,83 @@ +#include <memory> +#include <pybind11/iostream.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> +#include "pybind/pyvisitor.hpp" +#include "visitors/nmodl_visitor.hpp" + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "OCDFAInspection" +{% macro var(node) -%} + {{ node.class_name | snake_case }}_ +{%- endmacro -%} + +{% macro args(children) %} + {% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} +{%- endmacro -%} + +namespace py = pybind11; + +{% for node in nodes %} +void PyVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) { + PYBIND11_OVERLOAD_PURE(void, Visitor, visit_{{ node.class_name|snake_case }}, node); +} +{% endfor %} + +{% for node in nodes %} +void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) { + PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, node); +} +{% endfor %} + + +class PyNModlResources { +protected: + std::unique_ptr<py::detail::pythonbuf> buf; + std::unique_ptr<std::ostream> ostream; +public: + PyNModlResources() = default; + PyNModlResources(py::object object) : buf(new py::detail::pythonbuf(object)), + ostream(new std::ostream(buf.get())) {} +}; + + +class PyNmodlPrintVisitor : private PyNModlResources, public NmodlPrintVisitor { +public: + using NmodlPrintVisitor::NmodlPrintVisitor; + + PyNmodlPrintVisitor() = default; + PyNmodlPrintVisitor(std::string filename) : NmodlPrintVisitor(filename) {}; + PyNmodlPrintVisitor(py::object object) : PyNModlResources(object), + NmodlPrintVisitor(*ostream) { }; +}; + + +void init_visitor_module(py::module& m) { + py::module m_visitor = m.def_submodule("visitor"); + + py::class_<Visitor, PyVisitor> visitor(m_visitor, "Visitor"); + visitor.def(py::init<>()) + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &Visitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} + + py::class_<AstVisitor, PyAstVisitor> ast_visitor(m_visitor, "AstVisitor"); + ast_visitor.def(py::init<>()) + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &AstVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} + + py::class_<PyNmodlPrintVisitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor"); + nmodl_visitor.def(py::init<std::string>()); + nmodl_visitor.def(py::init<py::object>()); + nmodl_visitor.def(py::init<>()) + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &PyNmodlPrintVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} + + +} +#pragma clang diagnostic pop \ No newline at end of file diff --git a/src/nmodl/language/templates/pyvisitor.hpp b/src/nmodl/language/templates/pyvisitor.hpp new file mode 100644 index 0000000000..158beca9a8 --- /dev/null +++ b/src/nmodl/language/templates/pyvisitor.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "ast/ast.hpp" +#include "visitors/visitor.hpp" +#include "visitors/ast_visitor.hpp" +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + + +class PyVisitor : public Visitor { +public: + using Visitor::Visitor; + + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} +}; + +/* Python interface of basic visitor implementation */ +class PyAstVisitor : public AstVisitor { +public: + using AstVisitor::AstVisitor; + + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} +}; diff --git a/src/nmodl/language/templates/visitor.hpp b/src/nmodl/language/templates/visitor.hpp index 1410ba0a60..e45e7ac8fb 100644 --- a/src/nmodl/language/templates/visitor.hpp +++ b/src/nmodl/language/templates/visitor.hpp @@ -5,6 +5,8 @@ class Visitor { public: + virtual ~Visitor() = default; + {% for node in nodes %} virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) = 0; {% endfor %} diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 67dbb3ceb4..27b667c39b 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -173,6 +173,7 @@ add_library(lexer ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} ${AST_SOURCE_FILES}) +set_property(TARGET lexer PROPERTY POSITION_INDEPENDENT_CODE ON) add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 7316fea7a7..580f96de43 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -1,7 +1,7 @@ #pragma once -#include <string> #include "parser/nmodl/nmodl_parser.hpp" +#include <string> namespace nmodl { bool is_keyword(const std::string& name); diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index 65313f291a..18a67a80e7 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -1,7 +1,7 @@ #include "arg_handler.hpp" -#include "utils/string_utils.hpp" #include "tclap/CmdLine.h" +#include "utils/string_utils.hpp" #include "version/version.h" diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp index 3376cb9443..3f8c7f73d4 100644 --- a/src/nmodl/nmodl/arg_handler.hpp +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -1,8 +1,8 @@ #ifndef NMODL_ARG_HANDLER_HPP #define NMODL_ARG_HANDLER_HPP -#include <vector> #include <string> +#include <vector> /** * \class ArgumentHandler diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index b19899e56e..9bf062ae25 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -3,24 +3,24 @@ #include <sstream> #include "arg_handler.hpp" +#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" +#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" +#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" +#include "codegen/c/codegen_c_visitor.hpp" #include "parser/nmodl_driver.hpp" +#include "utils/common_utils.hpp" +#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" -#include "visitors/localize_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" -#include "codegen/c/codegen_c_visitor.hpp" -#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" -#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" -#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" -#include "utils/common_utils.hpp" -#include "utils/logger.hpp" +#include "visitors/verbatim_visitor.hpp" void ast_to_nmodl(ast::Program* ast, const std::string& filename) { NmodlPrintVisitor v(filename); diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index a6b6646341..72537c55ae 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -16,6 +16,7 @@ set(PRINTER_SOURCE_FILES add_library(printer STATIC ${PRINTER_SOURCE_FILES}) +set_property(TARGET printer PROPERTY POSITION_INDEPENDENT_CODE ON) #============================================================================= # Files for clang-format diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 94387e5d17..cbb5e5bd19 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -3,8 +3,8 @@ #include <fstream> #include <iostream> -#include <sstream> #include <memory> +#include <sstream> /** * \class CodePrinter diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 0150507077..e59a851ffd 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -1,9 +1,9 @@ #ifndef _JSON_PRINTER_HPP_ #define _JSON_PRINTER_HPP_ -#include <stack> #include <fstream> #include <iostream> +#include <stack> #include "json/json.hpp" diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp index 197177306e..d782986426 100644 --- a/src/nmodl/printer/nmodl_printer.hpp +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -3,8 +3,8 @@ #include <fstream> #include <iostream> -#include <sstream> #include <memory> +#include <sstream> /** * \class NMODLPrinter @@ -27,7 +27,7 @@ class NMODLPrinter { public: NMODLPrinter() : result(new std::ostream(std::cout.rdbuf())) { } - NMODLPrinter(std::stringstream& stream) : result(new std::ostream(stream.rdbuf())) { + NMODLPrinter(std::ostream& stream) : result(new std::ostream(stream.rdbuf())) { } NMODLPrinter(const std::string& filename); diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt new file mode 100644 index 0000000000..21b5c60749 --- /dev/null +++ b/src/nmodl/pybind/CMakeLists.txt @@ -0,0 +1,43 @@ +#============================================================================= +# pybind targets +#============================================================================= +set_source_files_properties( ${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) + + +set(PYNMODL_SOURCES + ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + ${PROJECT_SOURCE_DIR}/src/lexer/modtoken.cpp + ${PROJECT_SOURCE_DIR}/src/symtab/symbol.cpp + ${PROJECT_SOURCE_DIR}/src/symtab/symbol_properties.cpp + ${PROJECT_SOURCE_DIR}/src/symtab/symbol_table.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/utils/common_utils.cpp + ${PROJECT_SOURCE_DIR}/src/utils/table_data.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyast.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) + +set(PYNMODL_HEADERS + ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp + ${PROJECT_SOURCE_DIR}/src/lexer/modtoken.hpp + ${PROJECT_SOURCE_DIR}/src/symtab/symbol.hpp + ${PROJECT_SOURCE_DIR}/src/symtab/symbol_properties.hpp + ${PROJECT_SOURCE_DIR}/src/symtab/symbol_table.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp + ${PROJECT_SOURCE_DIR}/src/utils/common_utils.hpp + ${PROJECT_SOURCE_DIR}/src/utils/string_utils.hpp + ${PROJECT_SOURCE_DIR}/src/utils/table_data.hpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyast.hpp + ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.hpp) + + +pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES}) +target_link_libraries(_nmodl PRIVATE lexer + PRIVATE visitor + PRIVATE printer) + +add_dependencies(_nmodl pyastgen) +add_dependencies(_nmodl nmodl_parser) diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp new file mode 100644 index 0000000000..c69dd6f063 --- /dev/null +++ b/src/nmodl/pybind/pynmodl.cpp @@ -0,0 +1,121 @@ +#include "parser/nmodl_driver.hpp" +#include <memory> +#include <pybind11/iostream.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + + +namespace pybind11 { +namespace detail { + +template <typename StringType> +struct CopyFromPython { + void operator()(char* start, size_t n, StringType data) { + char* buffer; + ssize_t length; + if (PYBIND11_BYTES_AS_STRING_AND_SIZE(data.ptr(), &buffer, &length)) + pybind11_fail("Unable to extract string contents! (invalid type)"); + std::memcpy(start, buffer, n); + } +}; + +template <> +struct CopyFromPython<str> { + void operator()(char* start, size_t n, str data) { + if (PyUnicode_Check(data.ptr())) { + data = reinterpret_steal<object>(PyUnicode_AsUTF8String(data.ptr())); + if (!data) + pybind11_fail("Unable to extract string contents! (encoding issue)"); + } + CopyFromPython<bytes>()(start, n, data); + } +}; + + +template <typename StringType> +class pythonibuf: public std::streambuf { + private: + using traits_type = std::streambuf::traits_type; + + const static std::size_t put_back_ = 1; + const static std::size_t buf_sz = 1024 + put_back_; + char d_buffer[buf_sz]; + + object pyistream; + object pyread; + + // copy ctor and assignment not implemented; + // copying not allowed + pythonibuf(const pythonibuf&); + pythonibuf& operator=(const pythonibuf&); + + int_type underflow() { + if (gptr() < egptr()) { // buffer not exhausted + return traits_type::to_int_type(*gptr()); + } + + char* base = d_buffer; + char* start = base; + if (eback() == base) { + std::memmove(base, egptr() - put_back_, put_back_); + start += put_back_; + } + StringType data = pyread(buf_sz - (start - base)); + size_t n = len(data); + if (n == 0) { + return traits_type::eof(); + } + CopyFromPython<StringType>()(start, n, data); + setg(base, start, start + n); + return traits_type::to_int_type(*gptr()); + } + + + public: + pythonibuf(object pyistream) + : pyistream(pyistream) + , pyread(pyistream.attr("read")) { + char* end = d_buffer + buf_sz; + setg(end, end, end); + } +}; +} // namespace detail +} // namespace pybind11 + +namespace py = pybind11; + + +class PyDriver: public nmodl::Driver { + public: + using nmodl::Driver::Driver; + + bool parse_stream(py::object object) { + py::object tiob = py::module::import("io").attr("TextIOBase"); + if (py::isinstance(object, tiob)) { + py::detail::pythonibuf<py::str> buf(object); + std::istream istr(&buf); + return nmodl::Driver::parse_stream(istr); + } else { + py::detail::pythonibuf<py::bytes> buf(object); + std::istream istr(&buf); + return nmodl::Driver::parse_stream(istr); + } + } +}; + +// forward declaration of submodule init functions +void init_visitor_module(py::module& m); +void init_ast_module(py::module& m); + +PYBIND11_MODULE(_nmodl, m_nmodl) { + py::class_<PyDriver> nmodl_driver(m_nmodl, "Driver"); + nmodl_driver.def(py::init<bool, bool>()); + nmodl_driver.def(py::init<>()) + .def("parse_string", &PyDriver::parse_string) + .def("parse_file", &PyDriver::parse_file) + .def("parse_stream", &PyDriver::parse_stream) + .def("ast", &PyDriver::ast); + + init_visitor_module(m_nmodl); + init_ast_module(m_nmodl); +} diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 5b8eb12287..829a2e6001 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -1,8 +1,8 @@ -#include <vector> #include <string> +#include <vector> -#include "utils/string_utils.hpp" #include "symtab/symbol_properties.hpp" +#include "utils/string_utils.hpp" using namespace syminfo; diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index ae43fa095f..06f6d7e593 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -1,8 +1,10 @@ #ifndef NMODL_LOGGER_HPP #define NMODL_LOGGER_HPP +// clang-format off #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" +// clang-format on using logger_type = std::shared_ptr<spdlog::logger>; extern logger_type logger; diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index b5b1cb4b23..51eb28c201 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -1,8 +1,8 @@ -#include <numeric> #include <iostream> +#include <numeric> -#include "utils/table_data.hpp" #include "utils/string_utils.hpp" +#include "utils/table_data.hpp" /** * Print table data in below shown format: title as first row (centrally aligned), diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index de27ab1b8e..bd2e732dd0 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -47,6 +47,7 @@ set_source_files_properties( add_library(visitor STATIC ${VISITOR_SOURCE_FILES}) +set_property(TARGET visitor PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(visitor lexer util) diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 0df65354d6..4a2124760e 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -1,10 +1,10 @@ #include <sstream> +#include "parser/diffeq_driver.hpp" +#include "symtab/symbol.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" -#include "parser/diffeq_driver.hpp" #include "visitors/visitor_utils.hpp" -#include "symtab/symbol.hpp" using namespace ast; diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index eb256939aa..e79b7de378 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -216,7 +216,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { auto last_chain = current_chain; start_new_chain(DUState::IF); node->get_condition()->accept(this); - auto block = node->get_block(); + auto block = node->get_statement_block(); if (block) { block->accept(this); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index afd8c40bcd..fa2fc5145a 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -109,7 +109,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, std::string function_name = callee->get_node_name(); /// do nothing if we can't inline given procedure/function - if (!can_inline_block(callee->get_block().get())) { + if (!can_inline_block(callee->get_statement_block().get())) { std::cerr << "Can not inline function call to " + function_name << std::endl; return false; } @@ -143,7 +143,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, } /// get a copy of function/procedure body - auto inlined_block = callee->get_block()->clone(); + auto inlined_block = callee->get_statement_block()->clone(); /// function definition has function name as return value. we have to rename /// it with new variable name diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 9d0e09d836..f02d645d19 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -5,11 +5,11 @@ #include <stack> #include "ast/ast.hpp" +#include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "symtab/symbol_table.hpp" /** * \class InlineVisitor diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index 1037babd3f..de7630b03b 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -1,12 +1,12 @@ #ifndef LOCAL_VAR_RENAME_VISITOR_HPP #define LOCAL_VAR_RENAME_VISITOR_HPP -#include <stack> #include <map> +#include <stack> #include "ast/ast.hpp" -#include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" /** * \class LocalVarRenameVisitor diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index dce218c91b..b5d199a73e 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -1,7 +1,7 @@ #include <algorithm> -#include "visitors/localize_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" +#include "visitors/localize_visitor.hpp" using namespace ast; using namespace symtab; @@ -120,7 +120,7 @@ void LocalizeVisitor::visit_program(Program* node) { /// all blocks that are using variable should get local variable for (auto& block : block_usage[DUState::D]) { auto block_ptr = dynamic_cast<Block*>(block.get()); - auto statement_block = block_ptr->get_block(); + auto statement_block = block_ptr->get_statement_block(); LocalVar* variable; auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 5d1718e69e..889f90d648 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -6,11 +6,11 @@ #include "ast/ast.hpp" #include "printer/json_printer.hpp" +#include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "symtab/symbol_table.hpp" /** * \class LocalizeVisitor diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index d21ece3f86..87f589e1b9 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -4,16 +4,16 @@ #include "parser/nmodl_driver.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" -#include "visitors/localize_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" #include "tclap/CmdLine.h" diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 86dc9bbbf0..0d9f2de4de 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -1,8 +1,8 @@ #ifndef _NMODL_PERF_VISITOR_HPP_ #define _NMODL_PERF_VISITOR_HPP_ -#include <stack> #include <set> +#include <stack> #include "printer/json_printer.hpp" #include "symtab/symbol_table.hpp" diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index ba026dda53..cb2d09eb3e 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -4,8 +4,8 @@ #include <string> #include "ast/ast.hpp" -#include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" /** * \class VarRenameVisitor diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index 9f81809d03..c8fcd96051 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -1,11 +1,11 @@ #ifndef VERBATIM_VAR_RENAME_VISITOR_HPP #define VERBATIM_VAR_RENAME_VISITOR_HPP -#include <string> #include <stack> +#include <string> -#include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" /** * \class VerbatimVarRenameVisitor diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 7d55a535ad..318d0a27e6 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,5 +1,5 @@ -#include <iostream> #include "visitors/verbatim_visitor.hpp" +#include <iostream> using namespace ast; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 1b8aa98fd6..af9e414e39 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -1,6 +1,6 @@ -#include <string> #include <map> #include <memory> +#include <string> #include "ast/ast.hpp" #include "parser/nmodl_driver.hpp" @@ -71,6 +71,6 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { auto ast = driver.ast(); auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); auto statement = - std::shared_ptr<Statement>(procedure->get_block()->get_statements()[0]->clone()); + std::shared_ptr<Statement>(procedure->get_statement_block()->get_statements()[0]->clone()); return statement; } diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 455816c8f2..4e3364cbae 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -1,8 +1,8 @@ #ifndef NMODL_VISITOR_UTILS #define NMODL_VISITOR_UTILS -#include <string> #include <map> +#include <string> #include "ast/ast.hpp" diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index c0896d13cc..990ba12429 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -38,6 +38,15 @@ add_test (NAME Visitor COMMAND testvisitor) add_test (NAME Printer COMMAND testprinter) add_test (NAME Symtab COMMAND testsymtab) +#============================================================================= +# pybind11 tests +#============================================================================= + +add_test (NAME Pybind COMMAND python3 -m pytest + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +set_tests_properties(Pybind PROPERTIES + ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}/src/pybind:$ENV{PYTHONPATH}) + #============================================================================= # Files for clang-format #============================================================================= diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 629db261d7..723319d251 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -4,8 +4,8 @@ #include <utility> #include "catch/catch.hpp" -#include "parser/nmodl_driver.hpp" #include "parser/diffeq_driver.hpp" +#include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.h" //============================================================================= diff --git a/test/nmodl/transpiler/pybind/__init__.py b/test/nmodl/transpiler/pybind/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py new file mode 100644 index 0000000000..8acc48ee16 --- /dev/null +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -0,0 +1,9 @@ +from nmodl import ast, visitor + +import pytest + +class TestAST(object): + + def test_empty_program(self): + pnode = ast.Program() + assert str(pnode) == '{"Program":[]}' diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index c7d1471ffb..b6d14fddda 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -2,8 +2,8 @@ #include <string> -#include "catch/catch.hpp" #include "ast/ast.hpp" +#include "catch/catch.hpp" #include "symtab/symbol.hpp" #include "symtab/symbol_table.hpp" diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.h b/test/nmodl/transpiler/utils/nmodl_constructs.h index 389c4932f2..2d292e9561 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.h +++ b/test/nmodl/transpiler/utils/nmodl_constructs.h @@ -1,9 +1,9 @@ #ifndef NMODL_TEST_CONSTRUCTS #define NMODL_TEST_CONSTRUCTS +#include <map> #include <string> #include <vector> -#include <map> /// represent nmodl construct test struct NmodlTestCase { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 485f1bc862..b4f66fc2cb 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -5,21 +5,21 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" +#include "test/utils/nmodl_constructs.h" +#include "test/utils/test_utils.hpp" +#include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" -#include "visitors/symtab_visitor.hpp" #include "visitors/rename_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" -#include "visitors/defuse_analyze_visitor.hpp" -#include "visitors/localize_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" -#include "visitors/lookup_visitor.hpp" -#include "test/utils/nmodl_constructs.h" -#include "test/utils/test_utils.hpp" +#include "visitors/verbatim_visitor.hpp" using json = nlohmann::json; using namespace ast; From a44e3e1eb23f8b0c64445c3c6b9d27941603cd5f Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 3 Feb 2019 01:47:10 +0100 Subject: [PATCH 116/871] Bug fix from e0cc2ac47c67e7d75def256087ce02e6b23dad2c: - NonspeCurVar was renamed to NonspecificCurVar but nodes_info.py was still refering to NonspeCurVar - Because of this non specific current variable was getting inserted into symbol table resulting in wrong code generation (ring test failure) Change-Id: Ica9eb457e3ee6f3a18324906cb29e57c4df971cb NMODL Repo SHA: BlueBrain/nmodl@cb6f2442da5495ad8cac228785ea17894ba19f9e --- src/nmodl/codegen/base/codegen_helper_visitor.cpp | 2 +- src/nmodl/language/node_info.py | 2 +- src/nmodl/symtab/symbol.cpp | 2 +- src/nmodl/symtab/symbol_properties.cpp | 2 +- src/nmodl/symtab/symbol_properties.hpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 6 +++--- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/base/codegen_helper_visitor.cpp index c1c47ea61d..307812791a 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/base/codegen_helper_visitor.cpp @@ -92,7 +92,7 @@ void CodegenHelperVisitor::find_ion_variables() { } /// once ions are populated, we can find all currents - auto vars = psymtab->get_variables_with_properties(NmodlType::nonspe_cur_var); + auto vars = psymtab->get_variables_with_properties(NmodlType::nonspecific_cur_var); for (auto& var : vars) { info.currents.push_back(var->get_name()); } diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index d6c1995685..bbf450f2f8 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -58,7 +58,7 @@ "Useion", "ReadIonVar", "WriteIonVar", - "NonspeCurVar", + "NonspecificCurVar", "ElectrodeCurVar", "SectionVar", "GlobalVar", diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index d3e1ba8d71..9264aac1c9 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -74,7 +74,7 @@ namespace symtab { | NmodlType::dependent_def | NmodlType::read_ion_var | NmodlType::write_ion_var - | NmodlType::nonspe_cur_var + | NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | NmodlType::section_var | NmodlType::argument diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 829a2e6001..7e94f65855 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -67,7 +67,7 @@ std::vector<std::string> to_string_vector(const NmodlTypeFlag& obj) { properties.emplace_back("write_ion"); } - if (has_property(obj, NmodlType::nonspe_cur_var)) { + if (has_property(obj, NmodlType::nonspecific_cur_var)) { properties.emplace_back("nonspe_cur"); } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 5367dd20d1..992b532b5b 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -132,7 +132,7 @@ namespace syminfo { write_ion_var = 1L << 11, /** Non Specific Current */ - nonspe_cur_var = 1L << 12, + nonspecific_cur_var = 1L << 12, /** Electrode Current */ electrode_cur_var = 1L << 13, diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index b5d199a73e..6ce8b9d918 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -63,7 +63,7 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { | NmodlType::read_ion_var | NmodlType::write_ion_var | NmodlType::prime_name - | NmodlType::nonspe_cur_var + | NmodlType::nonspecific_cur_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var | NmodlType::electrode_cur_var diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index ccc80a863e..f364d386a6 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -21,7 +21,7 @@ static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, symbol->add_property(property); // non specific variable is range - if (property == NmodlType::nonspe_cur_var) { + if (property == NmodlType::nonspecific_cur_var) { symbol->add_property(NmodlType::range_var); } @@ -54,9 +54,9 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { /// range and non_spec_cur can appear in any order in neuron block. /// for both properties, we have to check if symbol is already exist. /// if so we have to return to avoid duplicate definition error. - if (property == NmodlType::range_var || property == NmodlType::nonspe_cur_var) { + if (property == NmodlType::range_var || property == NmodlType::nonspecific_cur_var) { auto s = modsymtab->lookup(name); - if (s && s->has_properties(NmodlType::nonspe_cur_var | NmodlType::range_var)) { + if (s && s->has_properties(NmodlType::nonspecific_cur_var | NmodlType::range_var)) { s->add_property(property); return; } From 3c22e28cacaf10487d3bc97b24094815498fd51f Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sat, 2 Feb 2019 12:11:22 +0100 Subject: [PATCH 117/871] Re-structuring and clean up of code generation visitors : - print routines renamed for consistency - end_block now accepts num of lines to print - emoved TableStatementVisitor and used generic AstLookupVisitor - remove sub directories from codegen folder - removed codegen_base_visitor* and combined it with codegen_c_visitor - remove C prefix from files as well as visitors classes - LayoutType is used instread of bool for memory layout - init() removed from constructors - global variables used in code generation is centralized in codegen_naming.hpp Change-Id: Ib27167e78d3e849b199eabd9995a060110f61268 NMODL Repo SHA: BlueBrain/nmodl@5d31a9008e41d7ad48e04da51a6dd1d6dcf94a01 --- cmake/nmodl/CMakeLists.txt | 1 - src/nmodl/codegen/CMakeLists.txt | 25 +- .../codegen/base/codegen_base_visitor.cpp | 796 ----------- .../codegen/base/codegen_base_visitor.hpp | 508 ------- ...cc_visitor.cpp => codegen_acc_visitor.cpp} | 24 +- ...cc_visitor.hpp => codegen_acc_visitor.hpp} | 31 +- .../codegen/{c => }/codegen_c_visitor.cpp | 1202 +++++++++++++---- .../codegen/{c => }/codegen_c_visitor.hpp | 517 ++++++- ...a_visitor.cpp => codegen_cuda_visitor.cpp} | 60 +- ...a_visitor.hpp => codegen_cuda_visitor.hpp} | 36 +- .../{base => }/codegen_helper_visitor.cpp | 26 +- .../{base => }/codegen_helper_visitor.hpp | 27 +- src/nmodl/codegen/codegen_info.hpp | 12 +- src/nmodl/codegen/codegen_naming.hpp | 149 ++ ...mp_visitor.cpp => codegen_omp_visitor.cpp} | 22 +- ...mp_visitor.hpp => codegen_omp_visitor.hpp} | 30 +- .../language/templates/lookup_visitor.hpp | 18 +- src/nmodl/lexer/nmodl.ll | 2 +- src/nmodl/nmodl/main.cpp | 18 +- src/nmodl/printer/code_printer.cpp | 10 +- src/nmodl/printer/code_printer.hpp | 4 +- src/nmodl/pybind/CMakeLists.txt | 3 +- src/nmodl/symtab/symbol_table.cpp | 5 +- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/utils/CMakeLists.txt | 1 + src/nmodl/utils/logger.cpp | 2 +- src/nmodl/visitors/CMakeLists.txt | 52 +- test/nmodl/transpiler/visitor/visitor.cpp | 5 +- 28 files changed, 1734 insertions(+), 1854 deletions(-) delete mode 100644 src/nmodl/codegen/base/codegen_base_visitor.cpp delete mode 100644 src/nmodl/codegen/base/codegen_base_visitor.hpp rename src/nmodl/codegen/{c-openacc/codegen_c_acc_visitor.cpp => codegen_acc_visitor.cpp} (76%) rename src/nmodl/codegen/{c-openacc/codegen_c_acc_visitor.hpp => codegen_acc_visitor.hpp} (65%) rename src/nmodl/codegen/{c => }/codegen_c_visitor.cpp (77%) rename src/nmodl/codegen/{c => }/codegen_c_visitor.hpp (54%) rename src/nmodl/codegen/{c-cuda/codegen_c_cuda_visitor.cpp => codegen_cuda_visitor.cpp} (70%) rename src/nmodl/codegen/{c-cuda/codegen_c_cuda_visitor.hpp => codegen_cuda_visitor.hpp} (69%) rename src/nmodl/codegen/{base => }/codegen_helper_visitor.cpp (96%) rename src/nmodl/codegen/{base => }/codegen_helper_visitor.hpp (79%) create mode 100644 src/nmodl/codegen/codegen_naming.hpp rename src/nmodl/codegen/{c-openmp/codegen_c_omp_visitor.cpp => codegen_omp_visitor.cpp} (73%) rename src/nmodl/codegen/{c-openmp/codegen_c_omp_visitor.hpp => codegen_omp_visitor.hpp} (64%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 49264d1213..734ed6d2cd 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -33,7 +33,6 @@ include(FindPythonModule) #============================================================================= # Find clang-format #============================================================================= - find_package(ClangFormat) #============================================================================= diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index ffdf4ede7f..26a405830c 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -2,20 +2,19 @@ # Codegen sources #============================================================================= set(CODEGEN_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_cuda_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_cuda_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_omp_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_omp_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_c_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_c_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_helper_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_helper_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_base_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_base_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_helper_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/base/codegen_helper_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/c/codegen_c_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/c/codegen_c_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/c-openmp/codegen_c_omp_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/c-openmp/codegen_c_omp_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/c-openacc/codegen_c_acc_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/c-openacc/codegen_c_acc_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/c-cuda/codegen_c_cuda_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/c-cuda/codegen_c_cuda_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp ) #============================================================================= diff --git a/src/nmodl/codegen/base/codegen_base_visitor.cpp b/src/nmodl/codegen/base/codegen_base_visitor.cpp deleted file mode 100644 index a7a9a24469..0000000000 --- a/src/nmodl/codegen/base/codegen_base_visitor.cpp +++ /dev/null @@ -1,796 +0,0 @@ -#include <algorithm> -#include <cmath> -#include <ctime> -#include <utility> - -#include "codegen/base/codegen_helper_visitor.hpp" -#include "codegen/c/codegen_c_visitor.hpp" -#include "parser/c11_driver.hpp" -#include "utils/string_utils.hpp" -#include "version/version.h" -#include "visitors/rename_visitor.hpp" -#include "visitors/var_usage_visitor.hpp" - - -using namespace ast; -using namespace symtab; -using namespace syminfo; -using namespace fmt::literals; -using SymbolType = std::shared_ptr<symtab::Symbol>; - - -/****************************************************************************************/ -/* All visitor routines */ -/****************************************************************************************/ - - -void CodegenBaseVisitor::visit_string(String* node) { - if (!codegen) { - return; - } - std::string name = node->eval(); - if (enable_variable_name_lookup) { - name = get_variable_name(name); - } - printer->add_text(name); -} - - -void CodegenBaseVisitor::visit_integer(Integer* node) { - if (!codegen) { - return; - } - auto macro = node->get_macro(); - auto value = node->get_value(); - if (macro) { - macro->accept(this); - } else { - printer->add_text(std::to_string(value)); - } -} - - -void CodegenBaseVisitor::visit_float(Float* node) { - if (!codegen) { - return; - } - printer->add_text(std::to_string(node->eval())); -} - - -void CodegenBaseVisitor::visit_double(Double* node) { - if (!codegen) { - return; - } - auto value = node->eval(); - printer->add_text(double_to_string(value)); -} - - -void CodegenBaseVisitor::visit_boolean(Boolean* node) { - if (!codegen) { - return; - } - printer->add_text(std::to_string(static_cast<int>(node->eval()))); -} - - -void CodegenBaseVisitor::visit_name(Name* node) { - if (!codegen) { - return; - } - node->visit_children(this); -} - - -void CodegenBaseVisitor::visit_unit(ast::Unit* node) { - // do not print units -} - - -void CodegenBaseVisitor::visit_prime_name(PrimeName* node) { - throw std::runtime_error("Prime encountered, ODEs not solved?"); -} - - -/// \todo : validate how @ is being handled in neuron implementation -void CodegenBaseVisitor::visit_var_name(VarName* node) { - if (!codegen) { - return; - } - auto name = node->get_name(); - auto at_index = node->get_at(); - auto index = node->get_index(); - name->accept(this); - if (at_index) { - printer->add_text("@"); - at_index->accept(this); - } - if (index) { - printer->add_text("["); - index->accept(this); - printer->add_text("]"); - } -} - - -void CodegenBaseVisitor::visit_indexed_name(IndexedName* node) { - if (!codegen) { - return; - } - node->get_name()->accept(this); - printer->add_text("["); - node->get_length()->accept(this); - printer->add_text("]"); -} - - -void CodegenBaseVisitor::visit_local_list_statement(LocalListStatement* node) { - if (!codegen) { - return; - } - auto type = local_var_type() + " "; - printer->add_text(type); - print_vector_elements(node->get_variables(), ", "); -} - - -void CodegenBaseVisitor::visit_if_statement(IfStatement* node) { - if (!codegen) { - return; - } - printer->add_text("if ("); - node->get_condition()->accept(this); - printer->add_text(") "); - node->get_statement_block()->accept(this); - print_vector_elements(node->get_elseifs(), ""); - auto elses = node->get_elses(); - if (elses) { - elses->accept(this); - } -} - - -void CodegenBaseVisitor::visit_else_if_statement(ElseIfStatement* node) { - if (!codegen) { - return; - } - printer->add_text(" else if ("); - node->get_condition()->accept(this); - printer->add_text(") "); - node->get_statement_block()->accept(this); -} - - -void CodegenBaseVisitor::visit_else_statement(ElseStatement* node) { - if (!codegen) { - return; - } - printer->add_text(" else "); - node->visit_children(this); -} - -void CodegenBaseVisitor::visit_while_statement(WhileStatement* node) { - printer->add_text("while ("); - node->get_condition()->accept(this); - printer->add_text(") "); - node->get_statement_block()->accept(this); -} - -void CodegenBaseVisitor::visit_from_statement(ast::FromStatement* node) { - if (!codegen) { - return; - } - auto name = node->get_node_name(); - auto from = node->get_from(); - auto to = node->get_to(); - auto inc = node->get_increment(); - auto block = node->get_statement_block(); - printer->add_text("for(int {}="_format(name)); - from->accept(this); - printer->add_text("; {}<="_format(name)); - to->accept(this); - if (inc) { - printer->add_text("; {}+="_format(name)); - inc->accept(this); - } else { - printer->add_text("; {}++"_format(name)); - } - printer->add_text(")"); - block->accept(this); -} - - -void CodegenBaseVisitor::visit_paren_expression(ParenExpression* node) { - if (!codegen) { - return; - } - printer->add_text("("); - node->get_expression()->accept(this); - printer->add_text(")"); -} - - -void CodegenBaseVisitor::visit_binary_expression(BinaryExpression* node) { - if (!codegen) { - return; - } - auto op = node->get_op().eval(); - auto lhs = node->get_lhs(); - auto rhs = node->get_rhs(); - if (op == "^") { - printer->add_text("pow("); - lhs->accept(this); - printer->add_text(","); - rhs->accept(this); - printer->add_text(")"); - } else { - if (op == "=" || op == "&&" || op == "||" || op == "==") { - op = " " + op + " "; - } - lhs->accept(this); - printer->add_text(op); - rhs->accept(this); - } -} - - -void CodegenBaseVisitor::visit_binary_operator(BinaryOperator* node) { - if (!codegen) { - return; - } - printer->add_text(node->eval()); -} - - -void CodegenBaseVisitor::visit_unary_operator(UnaryOperator* node) { - if (!codegen) { - return; - } - printer->add_text(" " + node->eval()); -} - - -/** - * Statement block is top level construct (for every nmodl block). - * Sometime we want to analyse ast nodes even if code generation is - * false. Hence we visit children even if code generation is false. - */ -void CodegenBaseVisitor::visit_statement_block(StatementBlock* node) { - if (!codegen) { - node->visit_children(this); - return; - } - print_statement_block(node); -} - - -void CodegenBaseVisitor::visit_program(Program* node) { - program_symtab = node->get_symbol_table(); - - CodegenHelperVisitor v; - info = v.get_code_info(node); - info.mod_file = mod_file_suffix; - - float_variables = get_float_variables(); - int_variables = get_int_variables(); - shadow_variables = get_shadow_variables(); - - update_index_semantics(); -} - - -/****************************************************************************************/ -/* Common helper routines */ -/****************************************************************************************/ - - -/** - * Check if given statement needs to be skipped during code generation - * - * Certain statements like unit, comment, solve can/need to be skipped - * during code generation. Note that solve block is wrapped in expression - * statement and hence we have to check inner expression. It's also true - * for the initial block defined inside net receive block. - */ -bool CodegenBaseVisitor::skip_statement(Statement* node) { - // clang-format off - if (node->is_unit_state() - || node->is_line_comment() - || node->is_block_comment() - || node->is_solve_block() - || node->is_conductance_hint() - || node->is_table_statement()) { - return true; - } - // clang-format on - if (node->is_expression_statement()) { - auto expression = dynamic_cast<ExpressionStatement*>(node)->get_expression(); - if (expression->is_solve_block()) { - return true; - } - if (expression->is_initial_block()) { - return true; - } - } - return false; -} - - -bool CodegenBaseVisitor::net_send_buffer_required() { - if (net_receive_required() && !info.artificial_cell) { - if (info.net_event_used || info.net_send_used || info.is_watch_used()) { - return true; - } - } - return false; -} - - -bool CodegenBaseVisitor::net_receive_buffering_required() { - return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; -} - - -bool CodegenBaseVisitor::nrn_state_required() { - if (info.artificial_cell) { - return false; - } - return info.solve_node != nullptr || info.currents.empty(); -} - - -bool CodegenBaseVisitor::nrn_cur_required() { - return info.breakpoint_node != nullptr && !info.currents.empty(); -} - - -bool CodegenBaseVisitor::net_receive_exist() { - return info.net_receive_node != nullptr; -} - - -bool CodegenBaseVisitor::breakpoint_exist() { - return info.breakpoint_node != nullptr; -} - - -bool CodegenBaseVisitor::net_receive_required() { - return net_receive_exist(); -} - - -/** - * When floating point data type is not default (i.e. double) then we - * have to copy old array to new type (for range variables). - */ -bool CodegenBaseVisitor::range_variable_setup_required() { - return default_float_data_type() != float_data_type(); -} - - -bool CodegenBaseVisitor::state_variable(std::string name) { - // clang-format off - auto result = std::find_if(info.state_vars.begin(), - info.state_vars.end(), - [&name](const SymbolType& sym) { - return name == sym->get_name(); - } - ); - // clang-format on - return result != info.state_vars.end(); -} - - -int CodegenBaseVisitor::position_of_float_var(const std::string& name) { - int index = 0; - for (auto& var : float_variables) { - if (var->get_name() == name) { - return index; - } - index += var->get_length(); - } - throw std::logic_error(name + " variable not found"); -} - - -int CodegenBaseVisitor::position_of_int_var(const std::string& name) { - int index = 0; - for (auto& var : int_variables) { - if (var.symbol->get_name() == name) { - return index; - } - index += var.symbol->get_length(); - } - throw std::logic_error(name + " variable not found"); -} - - -/** - * Convert double value to string - * - * We can directly use to_string method but if user specify 7.0 then it gets - * printed as 7 (as integer). To avoid this, we use below wrapper. But note - * that there are still issues. For example, if 1.1 is not exactly represented - * in floating point, then it gets printed as 1.0999999999999. May be better - * to use std::to_string in else part? - */ -std::string CodegenBaseVisitor::double_to_string(double value) { - if (ceilf(value) == value) { - return "{:.1f}"_format(value); - } - return "{:.16g}"_format(value); -} - - -/** - * Check if given statement needs semicolon at the end of statement - * - * Statements like if, else etc. don't need semicolon at the end. - * (Note that it's valid to have "extraneous" semicolon). Also, statement - * block can appear as statement using expression statement which need to - * be inspected. - */ -bool CodegenBaseVisitor::need_semicolon(Statement* node) { - // clang-format off - if (node->is_if_statement() - || node->is_else_if_statement() - || node->is_else_statement() - || node->is_from_statement() - || node->is_verbatim() - || node->is_for_all_statement() - || node->is_from_statement() - || node->is_conductance_hint() - || node->is_while_statement()) { - return false; - } - // clang-format on - if (node->is_expression_statement()) { - auto statement = dynamic_cast<ExpressionStatement*>(node); - if (statement->get_expression()->is_statement_block()) { - return false; - } - } - return true; -} - - -// check if there is a function or procedure defined with given name -bool CodegenBaseVisitor::defined_method(std::string name) { - auto function = program_symtab->lookup(std::move(name)); - auto properties = NmodlType::function_block | NmodlType::procedure_block; - return function && function->has_properties(properties); -} - - -/** - * Return "current" for variable name used in breakpoint block - * - * Current variable used in breakpoint block could be local variable. - * In this case, neuron has already renamed the variable name by prepending - * "_l". In our implementation, the variable could have been renamed by - * one of the pass. And hence, we search all local variables and check if - * the variable is renamed. Note that we have to look into the symbol table - * of statement block and not breakpoint. - */ -std::string CodegenBaseVisitor::breakpoint_current(std::string current) { - auto breakpoint = info.breakpoint_node; - if (breakpoint == nullptr) { - return current; - } - auto symtab = breakpoint->get_statement_block()->get_symbol_table(); - auto variables = symtab->get_variables_with_properties(NmodlType::local_var); - for (auto& var : variables) { - auto renamed_name = var->get_name(); - auto original_name = var->get_original_name(); - if (current == original_name) { - current = renamed_name; - break; - } - } - return current; -} - - -int CodegenBaseVisitor::num_float_variable() { - auto count_length = [](std::vector<SymbolType>& variables) -> int { - int length = 0; - for (const auto& variable : variables) { - length += variable->get_length(); - } - return length; - }; - - int num_variables = count_length(info.range_parameter_vars); - num_variables += count_length(info.range_dependent_vars); - num_variables += count_length(info.state_vars); - num_variables += count_length(info.dependent_vars); - - /// for state variables we add Dstate variables - num_variables += info.state_vars.size(); - num_variables += info.ion_state_vars.size(); - - /// for v_unused variable - if (info.vectorize) { - num_variables++; - } - /// for g_unused variable - if (breakpoint_exist()) { - num_variables++; - } - /// for tsave variable - if (net_receive_exist()) { - num_variables++; - } - return num_variables; -} - - -int CodegenBaseVisitor::num_int_variable() { - int num_variables = 0; - for (auto& semantic : info.semantics) { - num_variables += semantic.size; - } - return num_variables; -} - - -/****************************************************************************************/ -/* Non-code-specific printing routines for code generation */ -/****************************************************************************************/ - - -void CodegenBaseVisitor::print_statement_block(ast::StatementBlock* node, - bool open_brace, - bool close_brace) { - if (open_brace) { - printer->start_block(); - } - - auto statements = node->get_statements(); - for (auto& statement : statements) { - if (skip_statement(statement.get())) { - continue; - } - /// not necessary to add indent for vetbatim block (pretty-printing) - if (!statement->is_verbatim()) { - printer->add_indent(); - } - statement->accept(this); - if (need_semicolon(statement.get())) { - printer->add_text(";"); - } - printer->add_newline(); - } - - if (close_brace) { - printer->end_block(); - } -} - - -/** - * Once variables are populated, update index semantics to register with coreneuron - */ -void CodegenBaseVisitor::update_index_semantics() { - int index = 0; - info.semantics.clear(); - - if (info.point_process) { - info.semantics.emplace_back(index++, "area", 1); - info.semantics.emplace_back(index++, "pntproc", 1); - } - for (const auto& ion : info.ions) { - for (auto& var : ion.reads) { - info.semantics.emplace_back(index++, ion.name + "_ion", 1); - } - for (auto& var : ion.writes) { - info.semantics.emplace_back(index++, ion.name + "_ion", 1); - if (ion.is_ionic_current(var)) { - info.semantics.emplace_back(index++, ion.name + "_ion", 1); - } - } - if (ion.need_style) { - info.semantics.emplace_back(index++, "#{}_ion"_format(ion.name), 1); - } - } - for (auto& var : info.pointer_variables) { - if (info.first_pointer_var_index == -1) { - info.first_pointer_var_index = index; - } - int size = var->get_length(); - if (var->has_properties(NmodlType::pointer_var)) { - info.semantics.emplace_back(index, "pointer", size); - } else { - info.semantics.emplace_back(index, "bbcorepointer", size); - } - index += size; - } - - if (info.diam_used) { - info.semantics.emplace_back(index++, diam_variable, 1); - } - - if (info.area_used) { - info.semantics.emplace_back(index++, area_variable, 1); - } - - if (info.net_send_used) { - info.semantics.emplace_back(index++, "netsend", 1); - } - - /** - * Number of semantics for watch is one greater than number of - * actual watch statements in the mod file - */ - if (!info.watch_statements.empty()) { - for (int i = 0; i < info.watch_statements.size() + 1; i++) { - info.semantics.emplace_back(index++, "watch", 1); - } - } - - if (info.for_netcon_used) { - info.semantics.emplace_back(index++, "fornetcon", 1); - } -} - - -/** - * Return all floating point variables required for code generation - */ -std::vector<SymbolType> CodegenBaseVisitor::get_float_variables() { - /// sort with definition order - auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { - return first->get_definition_order() < second->get_definition_order(); - }; - - auto dependents = info.dependent_vars; - auto states = info.state_vars; - states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); - - /// each state variable has corresponding Dstate variable - for (auto& variable : states) { - auto name = "D" + variable->get_name(); - auto symbol = make_symbol(name); - symbol->set_definition_order(variable->get_definition_order()); - dependents.push_back(symbol); - } - std::sort(dependents.begin(), dependents.end(), comparator); - - auto variables = info.range_parameter_vars; - variables.insert(variables.end(), info.range_dependent_vars.begin(), - info.range_dependent_vars.end()); - variables.insert(variables.end(), info.state_vars.begin(), info.state_vars.end()); - variables.insert(variables.end(), dependents.begin(), dependents.end()); - - if (info.vectorize) { - variables.push_back(make_symbol("v_unused")); - } - if (breakpoint_exist()) { - std::string name = info.vectorize ? "g_unused" : "_g"; - variables.push_back(make_symbol(name)); - } - if (net_receive_exist()) { - variables.push_back(make_symbol("tsave")); - } - return variables; -} - - -/** - * Return all integer variables required for code generation - * - * IndexVariableInfo has following constructor arguments: - * - symbol - * - is_vdata (false) - * - is_index (false - * - is_integer (false) - * - * Which variables are constant qualified? - * - * - node area is read only - * - read ion variables are read only - * - style_ionname is index / offset - */ -std::vector<IndexVariableInfo> CodegenBaseVisitor::get_int_variables() { - std::vector<IndexVariableInfo> variables; - if (info.point_process) { - variables.emplace_back(make_symbol(node_area)); - variables.back().is_constant = true; - /// note that this variable is not printed in neuron implementation - if (info.artificial_cell) { - variables.emplace_back(make_symbol(point_process), true); - } else { - variables.emplace_back(make_symbol(point_process), false, false, true); - variables.back().is_constant = true; - } - } - - for (auto& ion : info.ions) { - bool need_style = false; - for (auto& var : ion.reads) { - variables.emplace_back(make_symbol("ion_" + var)); - variables.back().is_constant = true; - } - for (auto& var : ion.writes) { - variables.emplace_back(make_symbol("ion_" + var)); - if (ion.is_ionic_current(var)) { - variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); - } - if (ion.is_intra_cell_conc(var) || ion.is_extra_cell_conc(var)) { - need_style = true; - } - } - if (need_style) { - variables.emplace_back(make_symbol("style_" + ion.name), false, true); - variables.back().is_constant = true; - } - } - - for (auto& var : info.pointer_variables) { - auto name = var->get_name(); - if (var->has_properties(NmodlType::pointer_var)) { - variables.emplace_back(make_symbol(name)); - } else { - variables.emplace_back(make_symbol(name), true); - } - } - - if (info.diam_used) { - variables.emplace_back(make_symbol(diam_variable)); - } - - if (info.area_used) { - variables.emplace_back(make_symbol(area_variable)); - } - - // for non-artificial cell, when net_receive buffering is enabled - // then tqitem is an offset - if (info.net_send_used) { - if (info.artificial_cell) { - variables.emplace_back(make_symbol("tqitem"), true); - } else { - variables.emplace_back(make_symbol("tqitem"), false, false, true); - variables.back().is_constant = true; - } - info.tqitem_index = variables.size() - 1; - } - - /** - * Variables for watch statements : note that there is one extra variable - * used in coreneuron compared to actual watch statements for compatibility - * with neuron (which uses one extra Datum variable) - */ - if (!info.watch_statements.empty()) { - for (int i = 0; i < info.watch_statements.size() + 1; i++) { - variables.emplace_back(make_symbol("watch{}"_format(i)), false, false, true); - } - } - return variables; -} - - -/** - * Return all ion write variables that require shadow vectors during code generation - * - * When we enable fine level parallelism at channel level, we have do updates - * to ion variables in atomic way. As cpus don't have atomic instructions in - * simd loop, we have to use shadow vectors for every ion variables. Here - * we return list of all such variables. - * - * \todo: if conductances are specified, we don't need all below variables - */ -std::vector<SymbolType> CodegenBaseVisitor::get_shadow_variables() { - std::vector<SymbolType> variables; - for (auto& ion : info.ions) { - for (auto& var : ion.writes) { - variables.push_back({make_symbol(shadow_varname("ion_" + var))}); - if (ion.is_ionic_current(var)) { - variables.push_back({make_symbol(shadow_varname("ion_di" + ion.name + "dv"))}); - } - } - } - variables.push_back({make_symbol("ml_rhs")}); - variables.push_back({make_symbol("ml_d")}); - return variables; -} diff --git a/src/nmodl/codegen/base/codegen_base_visitor.hpp b/src/nmodl/codegen/base/codegen_base_visitor.hpp deleted file mode 100644 index ad8746d7b9..0000000000 --- a/src/nmodl/codegen/base/codegen_base_visitor.hpp +++ /dev/null @@ -1,508 +0,0 @@ -#ifndef NMODL_CODEGEN_BASE_VISITOR_HPP -#define NMODL_CODEGEN_BASE_VISITOR_HPP - - -#include <algorithm> -#include <string> - -#include "codegen/codegen_info.hpp" -#include "fmt/format.h" -#include "printer/code_printer.hpp" -#include "symtab/symbol_table.hpp" -#include "visitors/ast_visitor.hpp" - - -using namespace fmt::literals; - - -/** - * \enum BlockType - * \brief Helper to represent various block types (similar to NEURON) - * - */ -enum class BlockType { - /// initial block - Initial, - - /// breakpoint block - Equation, - - /// ode_* routines block (not used) - Ode, - - /// derivative block - State, - - /// watch block - Watch -}; - - -/** - * \enum MemberType - * \brief Helper to represent various variables types - * - */ -enum class MemberType { - /// index / int variables - index, - - /// range / double variables - range, - - /// global variables - global, - - /// thread variables - thread -}; - - -/** - * \class IndexVariableInfo - * \brief Helper to represent information about index/int variables - * - */ -struct IndexVariableInfo { - /// symbol for the variable - std::shared_ptr<symtab::Symbol> symbol; - - /// if variable reside in vdata field of NrnThread - /// typically true for bbcore pointer - bool is_vdata = false; - - /// if this is pure index (e.g. style_ion) variables is directly - /// index and shouldn't be printed with data/vdata - bool is_index = false; - - /// if this is an integer (e.g. tqitem, point_process) variable which - /// is printed as array accesses - bool is_integer = false; - - /// if the variable is qualified as constant (this is property of IndexVariable) - bool is_constant = false; - - IndexVariableInfo(std::shared_ptr<symtab::Symbol> symbol, - bool is_vdata = false, - bool is_index = false, - bool is_integer = false) - : symbol(symbol), is_vdata(is_vdata), is_index(is_index), is_integer(is_integer) { - } -}; - - -/** - * \enum LayoutType - * \brief Represent memory layout to use for code generation - * - */ -enum class LayoutType { - /// array of structure - aos, - - /// structure of array - soa -}; - - -/** - * \class ShadowUseStatement - * \brief Represent ion write statement during code generation - * - * Ion update statement need use of shadow vectors for certain backends - * as atomics can be done in vector loops on cpu. - * - * \todo : if shadow_lhs is empty then we assume shadow statement not required. - */ -struct ShadowUseStatement { - std::string lhs; - std::string op; - std::string rhs; -}; - - -/** - * \class CodegenBaseVisitor - * \brief Visitor for printing c code compatible with legacy api - * - * \todo : - * - handle define i.e. macro statement printing - * - return statement in the verbatim block of inlined function not handled (e.g. netstim.mod) - */ -class CodegenBaseVisitor : public AstVisitor { - protected: - using SymbolType = std::shared_ptr<symtab::Symbol>; - - /// data type of floating point variables - std::string float_type = "double"; - - /// memory layout for code generation - LayoutType layout; - - /// name of mod file (without .mod suffix) - std::string mod_file_suffix; - - /// flag to indicate is visitor should print the visited nodes - bool codegen = false; - - /// variable name should be converted to instance name (but not for function arguments) - bool enable_variable_name_lookup = true; - - /// symbol table for the program - symtab::SymbolTable* program_symtab = nullptr; - - /// all float variables for the model - std::vector<SymbolType> float_variables; - - /// all int variables for the model - std::vector<IndexVariableInfo> int_variables; - - /// all global variables for the model - /// todo : this has become different than CodegenInfo - std::vector<SymbolType> global_variables; - - /// all ion variables that could be possibly written - std::vector<SymbolType> shadow_variables; - - /// all ast information for code generation - codegen::CodegenInfo info; - - /// code printer object - std::unique_ptr<CodePrinter> printer; - - /// list of shadow statements in the current block - std::vector<ShadowUseStatement> shadow_statements; - - /// node area variable - const std::string node_area = "node_area"; - - /// node diameter variable - const std::string diam_variable = "diam"; - - /// similar to node area but user can explicitly declare it - const std::string area_variable = "area"; - - /// point process variable - const std::string point_process = "point_process"; - - /// nmodl language version - std::string nmodl_version() { - return "6.2.0"; - } - - - std::string add_escape_quote(const std::string& text) { - return "\"" + text + "\""; - } - - - /// operator for rhs vector update (matrix update) - std::string operator_for_rhs() { - return info.electorde_current ? "+=" : "-="; - } - - - /// operator for diagonal vector update (matrix update) - std::string operator_for_d() { - return info.electorde_current ? "-=" : "+="; - } - - - /// data type for the local variables - std::string local_var_type() { - return "double"; - } - - - /// default data type for floating point elements - std::string default_float_data_type() { - return "double"; - } - - - /// data type for floating point elements specified on command line - std::string float_data_type() { - return float_type; - } - - - /// default data type for integer (offset) elements - std::string default_int_data_type() { - return "int"; - } - - - /// function name for net send - bool is_net_send(const std::string& name) { - return name == "net_send"; - } - - /// function name for net move - bool is_net_move(const std::string& name) { - return name == "net_move"; - } - - /// function name for net event - bool is_net_event(const std::string& name) { - return name == "net_event"; - } - - - /// name of structure that wraps range variables - std::string instance_struct() { - return "{}_Instance"_format(info.mod_suffix); - } - - - /// name of structure that wraps range variables - std::string global_struct() { - return "{}_Store"_format(info.mod_suffix); - } - - - /// name of function or procedure - std::string method_name(const std::string& name) { - auto suffix = info.mod_suffix; - return name + "_" + suffix; - } - - - /// name for shadow variable - std::string shadow_varname(const std::string& name) { - return "shadow_" + name; - } - - - /// create temporary symbol - SymbolType make_symbol(std::string name) { - return std::make_shared<symtab::Symbol>(name, ModToken()); - } - - - /// check if given variable is state variable - bool state_variable(std::string name); - - - /// check if net receive/send buffering kernels required - bool net_receive_buffering_required(); - - - /// check if nrn_state function is required - bool nrn_state_required(); - - - /// check if nrn_cur function is required - bool nrn_cur_required(); - - - /// check if net_receive function is required - bool net_receive_required(); - - - /// check if net_send_buffer is required - bool net_send_buffer_required(); - - - /// check if setup_range_variable function is required - bool range_variable_setup_required(); - - - /// check if net_receive node exist - bool net_receive_exist(); - - - /// check if breakpoint node exist - bool breakpoint_exist(); - - - /// if method is defined the mod file - bool defined_method(std::string name); - - - /// check if give statement should be skipped during code generation - bool skip_statement(ast::Statement* node); - - - /// check if semicolon required at the end of given statement - bool need_semicolon(ast::Statement* node); - - - /// number of threads to allocate - int num_thread_objects() { - return info.vectorize ? (info.thread_data_index + 1) : 0; - } - - - /// num of float variables in the model - int num_float_variable(); - - - /// num of integer variables in the model - int num_int_variable(); - - - /// for given float variable name, index position in the data array - int position_of_float_var(const std::string& name); - - - /// for given int variable name, index position in the data array - int position_of_int_var(const std::string& name); - - - /// when ion variable copies optimized then change name (e.g. ena to ion_ena) - std::string update_if_ion_variable_name(std::string name); - - - /// name of the code generation backend - std::string backend_name(); - - - /// convert given double value to string (for printing) - std::string double_to_string(double value); - - - /// get variable name for float variable - std::string float_variable_name(SymbolType& symbol, bool use_instance); - - - /// get variable name for int variable - std::string int_variable_name(IndexVariableInfo& symbol, std::string name, bool use_instance); - - - /// get variable name for global variable - std::string global_variable_name(SymbolType& symbol); - - - /// get ion shadow variable name - std::string ion_shadow_variable_name(SymbolType& symbol); - - - /// get variable name for given name. if use_instance is true then "Instance" - /// structure is used while returning name (implemented by derived classes) - virtual std::string get_variable_name(std::string name, bool use_instance = true) = 0; - - - /// name of the current variable used in the breakpoint bock - std::string breakpoint_current(std::string current); - - - /// populate all index semantics needed for registration with coreneuron - void update_index_semantics(); - - - /// return all float variables required during code generation - std::vector<SymbolType> get_float_variables(); - - - /// return all int variables required during code generation - std::vector<IndexVariableInfo> get_int_variables(); - - - /// return all ion write variables that require shadow vectors during code generation - std::vector<SymbolType> get_shadow_variables(); - - - /// print vector elements (all types) - template <typename T> - void print_vector_elements(const std::vector<T>& elements, - std::string separator, - std::string prefix = ""); - - - /// check if function or procedure has argument with same name - template <typename T> - bool has_argument_with_name(const T& node, std::string name); - - /// any statement block in nmodl with option to (not) print braces - void print_statement_block(ast::StatementBlock* node, - bool open_brace = true, - bool close_brace = true); - - - /// common init for constructors - void init(std::string filename, bool aos, std::string type) { - mod_file_suffix = filename; - layout = aos ? LayoutType::aos : LayoutType::soa; - float_type = type; - } - - public: - CodegenBaseVisitor(std::string mod_file, - std::string output_dir, - bool aos, - std::string float_type, - std::string extension = ".cpp") - : printer(new CodePrinter(output_dir + "/" + mod_file + extension)) { - init(mod_file, aos, float_type); - } - - CodegenBaseVisitor(std::string mod_file, - std::stringstream& stream, - bool aos, - std::string float_type) - : printer(new CodePrinter(stream)) { - init(mod_file, aos, float_type); - } - - virtual void visit_unit(ast::Unit* node) override; - virtual void visit_string(ast::String* node) override; - virtual void visit_integer(ast::Integer* node) override; - virtual void visit_float(ast::Float* node) override; - virtual void visit_double(ast::Double* node) override; - virtual void visit_boolean(ast::Boolean* node) override; - virtual void visit_name(ast::Name* node) override; - virtual void visit_prime_name(ast::PrimeName* node) override; - virtual void visit_var_name(ast::VarName* node) override; - virtual void visit_indexed_name(ast::IndexedName* node) override; - virtual void visit_local_list_statement(ast::LocalListStatement* node) override; - virtual void visit_if_statement(ast::IfStatement* node) override; - virtual void visit_else_if_statement(ast::ElseIfStatement* node) override; - virtual void visit_else_statement(ast::ElseStatement* node) override; - virtual void visit_from_statement(ast::FromStatement* node) override; - virtual void visit_paren_expression(ast::ParenExpression* node) override; - virtual void visit_binary_expression(ast::BinaryExpression* node) override; - virtual void visit_binary_operator(ast::BinaryOperator* node) override; - virtual void visit_unary_operator(ast::UnaryOperator* node) override; - virtual void visit_statement_block(ast::StatementBlock* node) override; - virtual void visit_program(ast::Program* node) override; - virtual void visit_while_statement(ast::WhileStatement* node) override; -}; - - -/** - * Print elements of vector with given separator and prefix string - */ -template <typename T> -void CodegenBaseVisitor::print_vector_elements(const std::vector<T>& elements, - std::string separator, - std::string prefix) { - for (auto iter = elements.begin(); iter != elements.end(); iter++) { - printer->add_text(prefix); - (*iter)->accept(this); - if (!separator.empty() && !is_last(iter, elements)) { - printer->add_text(separator); - } - } -} - - -/** - * Check if function or procedure node has parameter with given name - * - * @tparam T Node type (either procedure or function) - * @param node AST node (either procedure or function) - * @param name Name of parameter - * @return True if argument with name exist - */ -template <typename T> -bool has_parameter_of_name(const T& node, std::string name) { - auto parameters = node->get_parameters(); - for (const auto& parameter : parameters) { - if (parameter->get_node_name() == name) { - return true; - } - } - return false; -} - -#endif diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp similarity index 76% rename from src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp rename to src/nmodl/codegen/codegen_acc_visitor.cpp index 54aa67d4f8..cc0869d0c8 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -1,4 +1,4 @@ -#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" +#include "codegen/codegen_acc_visitor.hpp" #include <fmt/format.h> @@ -21,28 +21,28 @@ using namespace fmt::literals; * for(int id=0; id<nodecount; id++) { * */ -void CodegenCAccVisitor::print_channel_iteration_block_parallel_hint() { +void CodegenAccVisitor::print_channel_iteration_block_parallel_hint() { printer->add_line("#pragma acc parallel loop"); } -void CodegenCAccVisitor::print_atomic_reduction_pragma() { +void CodegenAccVisitor::print_atomic_reduction_pragma() { printer->add_line("#pragma acc atomic update"); } -void CodegenCAccVisitor::print_backend_includes() { +void CodegenAccVisitor::print_backend_includes() { printer->add_line("#include <cuda.h>"); printer->add_line("#include <openacc.h>"); } -std::string CodegenCAccVisitor::backend_name() { +std::string CodegenAccVisitor::backend_name() { return "C-OpenAcc (api-compatibility)"; } -void CodegenCAccVisitor::print_memory_allocation_routine() { +void CodegenAccVisitor::print_memory_allocation_routine() { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); @@ -71,7 +71,7 @@ void CodegenCAccVisitor::print_memory_allocation_routine() { * } * } */ -void CodegenCAccVisitor::print_kernel_data_present_annotation_block_begin() { +void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { auto global_variable = "{}_global"_format(info.mod_suffix); printer->add_line("#pragma acc data present(nt, ml, {})"_format(global_variable)); printer->add_line("{"); @@ -79,7 +79,7 @@ void CodegenCAccVisitor::print_kernel_data_present_annotation_block_begin() { } -void CodegenCAccVisitor::print_nrn_cur_matrix_shadow_update() { +void CodegenAccVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); print_atomic_reduction_pragma(); @@ -89,7 +89,7 @@ void CodegenCAccVisitor::print_nrn_cur_matrix_shadow_update() { } -void CodegenCAccVisitor::print_nrn_cur_matrix_shadow_reduction() { +void CodegenAccVisitor::print_nrn_cur_matrix_shadow_reduction() { // do nothing } @@ -97,17 +97,17 @@ void CodegenCAccVisitor::print_nrn_cur_matrix_shadow_reduction() { /** * End of print_kernel_enter_data_begin */ -void CodegenCAccVisitor::print_kernel_data_present_annotation_block_end() { +void CodegenAccVisitor::print_kernel_data_present_annotation_block_end() { printer->decrease_indent(); printer->add_line("}"); } -void CodegenCAccVisitor::print_rhs_d_shadow_variables() { +void CodegenAccVisitor::print_rhs_d_shadow_variables() { // do nothing } -bool CodegenCAccVisitor::nrn_cur_reduction_loop_required() { +bool CodegenAccVisitor::nrn_cur_reduction_loop_required() { return false; } diff --git a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp similarity index 65% rename from src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp rename to src/nmodl/codegen/codegen_acc_visitor.hpp index 8d56ef0e12..b84d09d5cc 100644 --- a/src/nmodl/codegen/c-openacc/codegen_c_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -1,18 +1,14 @@ #ifndef NMODL_CODEGEN_C_ACC_VISITOR_HPP #define NMODL_CODEGEN_C_ACC_VISITOR_HPP -#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/codegen_c_visitor.hpp" /** - * \class CodegenCAccVisitor + * \class CodegenAccVisitor * \brief Visitor for printing c code with OpenMP backend - * - * \todo : - * - handle define i.e. macro statement printing - * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) */ -class CodegenCAccVisitor : public CodegenCVisitor { +class CodegenAccVisitor : public CodegenCVisitor { protected: /// name of the code generation backend std::string backend_name() override; @@ -53,23 +49,24 @@ class CodegenCAccVisitor : public CodegenCVisitor { /// setup method for setting matrix shadow vectors void print_rhs_d_shadow_variables() override; + /// if reduction block in nrn_cur required bool nrn_cur_reduction_loop_required() override; public: - CodegenCAccVisitor(std::string mod_file, - std::string output_dir, - bool aos, - std::string float_type) - : CodegenCVisitor(mod_file, output_dir, aos, float_type) { + CodegenAccVisitor(std::string mod_file, + std::string output_dir, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, output_dir, layout, float_type) { } - CodegenCAccVisitor(std::string mod_file, - std::stringstream& stream, - bool aos, - std::string float_type) - : CodegenCVisitor(mod_file, stream, aos, float_type) { + CodegenAccVisitor(std::string mod_file, + std::stringstream& stream, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, stream, layout, float_type) { } }; diff --git a/src/nmodl/codegen/c/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp similarity index 77% rename from src/nmodl/codegen/c/codegen_c_visitor.cpp rename to src/nmodl/codegen/codegen_c_visitor.cpp index 521df5e720..236e02cd5e 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2,16 +2,20 @@ #include <cmath> #include <ctime> -#include "codegen/base/codegen_helper_visitor.hpp" -#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/codegen_helper_visitor.hpp" +#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_naming.hpp" #include "parser/c11_driver.hpp" #include "utils/string_utils.hpp" +#include "utils/logger.hpp" #include "version/version.h" #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" +#include "visitors/lookup_visitor.hpp" using namespace ast; +using namespace codegen; using namespace symtab; using namespace syminfo; using namespace fmt::literals; @@ -23,6 +27,247 @@ using SymbolType = std::shared_ptr<symtab::Symbol>; /****************************************************************************************/ +void CodegenCVisitor::visit_string(String* node) { + if (!codegen) { + return; + } + std::string name = node->eval(); + if (enable_variable_name_lookup) { + name = get_variable_name(name); + } + printer->add_text(name); +} + + +void CodegenCVisitor::visit_integer(Integer* node) { + if (!codegen) { + return; + } + auto macro = node->get_macro(); + auto value = node->get_value(); + if (macro) { + macro->accept(this); + } else { + printer->add_text(std::to_string(value)); + } +} + + +void CodegenCVisitor::visit_float(Float* node) { + if (!codegen) { + return; + } + printer->add_text(std::to_string(node->eval())); +} + + +void CodegenCVisitor::visit_double(Double* node) { + if (!codegen) { + return; + } + auto value = node->eval(); + printer->add_text(double_to_string(value)); +} + + +void CodegenCVisitor::visit_boolean(Boolean* node) { + if (!codegen) { + return; + } + printer->add_text(std::to_string(static_cast<int>(node->eval()))); +} + + +void CodegenCVisitor::visit_name(Name* node) { + if (!codegen) { + return; + } + node->visit_children(this); +} + + +void CodegenCVisitor::visit_unit(ast::Unit* node) { + // do not print units +} + + +void CodegenCVisitor::visit_prime_name(PrimeName* node) { + throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); +} + + +/// \todo : validate how @ is being handled in neuron implementation +void CodegenCVisitor::visit_var_name(VarName* node) { + if (!codegen) { + return; + } + auto name = node->get_name(); + auto at_index = node->get_at(); + auto index = node->get_index(); + name->accept(this); + if (at_index) { + printer->add_text("@"); + at_index->accept(this); + } + if (index) { + printer->add_text("["); + index->accept(this); + printer->add_text("]"); + } +} + + +void CodegenCVisitor::visit_indexed_name(IndexedName* node) { + if (!codegen) { + return; + } + node->get_name()->accept(this); + printer->add_text("["); + node->get_length()->accept(this); + printer->add_text("]"); +} + + +void CodegenCVisitor::visit_local_list_statement(LocalListStatement* node) { + if (!codegen) { + return; + } + auto type = local_var_type() + " "; + printer->add_text(type); + print_vector_elements(node->get_variables(), ", "); +} + + +void CodegenCVisitor::visit_if_statement(IfStatement* node) { + if (!codegen) { + return; + } + printer->add_text("if ("); + node->get_condition()->accept(this); + printer->add_text(") "); + node->get_statement_block()->accept(this); + print_vector_elements(node->get_elseifs(), ""); + auto elses = node->get_elses(); + if (elses) { + elses->accept(this); + } +} + + +void CodegenCVisitor::visit_else_if_statement(ElseIfStatement* node) { + if (!codegen) { + return; + } + printer->add_text(" else if ("); + node->get_condition()->accept(this); + printer->add_text(") "); + node->get_statement_block()->accept(this); +} + + +void CodegenCVisitor::visit_else_statement(ElseStatement* node) { + if (!codegen) { + return; + } + printer->add_text(" else "); + node->visit_children(this); +} + +void CodegenCVisitor::visit_while_statement(WhileStatement* node) { + printer->add_text("while ("); + node->get_condition()->accept(this); + printer->add_text(") "); + node->get_statement_block()->accept(this); +} + +void CodegenCVisitor::visit_from_statement(ast::FromStatement* node) { + if (!codegen) { + return; + } + auto name = node->get_node_name(); + auto from = node->get_from(); + auto to = node->get_to(); + auto inc = node->get_increment(); + auto block = node->get_statement_block(); + printer->add_text("for(int {}="_format(name)); + from->accept(this); + printer->add_text("; {}<="_format(name)); + to->accept(this); + if (inc) { + printer->add_text("; {}+="_format(name)); + inc->accept(this); + } else { + printer->add_text("; {}++"_format(name)); + } + printer->add_text(")"); + block->accept(this); +} + + +void CodegenCVisitor::visit_paren_expression(ParenExpression* node) { + if (!codegen) { + return; + } + printer->add_text("("); + node->get_expression()->accept(this); + printer->add_text(")"); +} + + +void CodegenCVisitor::visit_binary_expression(BinaryExpression* node) { + if (!codegen) { + return; + } + auto op = node->get_op().eval(); + auto lhs = node->get_lhs(); + auto rhs = node->get_rhs(); + if (op == "^") { + printer->add_text("pow("); + lhs->accept(this); + printer->add_text(","); + rhs->accept(this); + printer->add_text(")"); + } else { + if (op == "=" || op == "&&" || op == "||" || op == "==") { + op = " " + op + " "; + } + lhs->accept(this); + printer->add_text(op); + rhs->accept(this); + } +} + + +void CodegenCVisitor::visit_binary_operator(BinaryOperator* node) { + if (!codegen) { + return; + } + printer->add_text(node->eval()); +} + + +void CodegenCVisitor::visit_unary_operator(UnaryOperator* node) { + if (!codegen) { + return; + } + printer->add_text(" " + node->eval()); +} + + +/** + * Statement block is top level construct (for every nmodl block). + * Sometime we want to analyse ast nodes even if code generation is + * false. Hence we visit children even if code generation is false. + */ +void CodegenCVisitor::visit_statement_block(StatementBlock* node) { + if (!codegen) { + node->visit_children(this); + return; + } + print_statement_block(node); +} + + void CodegenCVisitor::visit_function_call(FunctionCall* node) { if (!codegen) { return; @@ -53,6 +298,184 @@ void CodegenCVisitor::visit_verbatim(Verbatim* node) { /****************************************************************************************/ +/** + * Check if given statement needs to be skipped during code generation + * + * Certain statements like unit, comment, solve can/need to be skipped + * during code generation. Note that solve block is wrapped in expression + * statement and hence we have to check inner expression. It's also true + * for the initial block defined inside net receive block. + */ +bool CodegenCVisitor::statement_to_skip(Statement* node) { + // clang-format off + if (node->is_unit_state() + || node->is_line_comment() + || node->is_block_comment() + || node->is_solve_block() + || node->is_conductance_hint() + || node->is_table_statement()) { + return true; + } + // clang-format on + if (node->is_expression_statement()) { + auto expression = dynamic_cast<ExpressionStatement*>(node)->get_expression(); + if (expression->is_solve_block()) { + return true; + } + if (expression->is_initial_block()) { + return true; + } + } + return false; +} + + +bool CodegenCVisitor::net_send_buffer_required() { + if (net_receive_required() && !info.artificial_cell) { + if (info.net_event_used || info.net_send_used || info.is_watch_used()) { + return true; + } + } + return false; +} + + +bool CodegenCVisitor::net_receive_buffering_required() { + return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; +} + + +bool CodegenCVisitor::nrn_state_required() { + if (info.artificial_cell) { + return false; + } + return info.solve_node != nullptr || info.currents.empty(); +} + + +bool CodegenCVisitor::nrn_cur_required() { + return info.breakpoint_node != nullptr && !info.currents.empty(); +} + + +bool CodegenCVisitor::net_receive_exist() { + return info.net_receive_node != nullptr; +} + + +bool CodegenCVisitor::breakpoint_exist() { + return info.breakpoint_node != nullptr; +} + + +bool CodegenCVisitor::net_receive_required() { + return net_receive_exist(); +} + + +/** + * When floating point data type is not default (i.e. double) then we + * have to copy old array to new type (for range variables). + */ +bool CodegenCVisitor::range_variable_setup_required() { + return codegen::naming::DEFAULT_FLOAT_TYPE != float_data_type(); +} + + +bool CodegenCVisitor::state_variable(std::string name) { + // clang-format off + auto result = std::find_if(info.state_vars.begin(), + info.state_vars.end(), + [&name](const SymbolType& sym) { + return name == sym->get_name(); + } + ); + // clang-format on + return result != info.state_vars.end(); +} + + +int CodegenCVisitor::position_of_float_var(const std::string& name) { + int index = 0; + for (const auto& var : codegen_float_variables) { + if (var->get_name() == name) { + return index; + } + index += var->get_length(); + } + throw std::logic_error(name + " variable not found"); +} + + +int CodegenCVisitor::position_of_int_var(const std::string& name) { + int index = 0; + for (const auto& var : codegen_int_variables) { + if (var.symbol->get_name() == name) { + return index; + } + index += var.symbol->get_length(); + } + throw std::logic_error(name + " variable not found"); +} + + +/** + * Convert double value to string + * + * We can directly use to_string method but if user specify 7.0 then it gets + * printed as 7 (as integer). To avoid this, we use below wrapper. But note + * that there are still issues. For example, if 1.1 is not exactly represented + * in floating point, then it gets printed as 1.0999999999999. May be better + * to use std::to_string in else part? + */ +std::string CodegenCVisitor::double_to_string(double value) { + if (ceilf(value) == value) { + return "{:.1f}"_format(value); + } + return "{:.16g}"_format(value); +} + + +/** + * Check if given statement needs semicolon at the end of statement + * + * Statements like if, else etc. don't need semicolon at the end. + * (Note that it's valid to have "extraneous" semicolon). Also, statement + * block can appear as statement using expression statement which need to + * be inspected. + */ +bool CodegenCVisitor::need_semicolon(Statement* node) { + // clang-format off + if (node->is_if_statement() + || node->is_else_if_statement() + || node->is_else_statement() + || node->is_from_statement() + || node->is_verbatim() + || node->is_for_all_statement() + || node->is_from_statement() + || node->is_conductance_hint() + || node->is_while_statement()) { + return false; + } + // clang-format on + if (node->is_expression_statement()) { + auto statement = dynamic_cast<ExpressionStatement*>(node); + if (statement->get_expression()->is_statement_block()) { + return false; + } + } + return true; +} + + +// check if there is a function or procedure defined with given name +bool CodegenCVisitor::defined_method(const std::string& name) { + auto function = program_symtab->lookup(name); + auto properties = NmodlType::function_block | NmodlType::procedure_block; + return function && function->has_properties(properties); +} + + /** * Return "current" for variable name used in breakpoint block * @@ -70,7 +493,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { } auto symtab = breakpoint->get_statement_block()->get_symbol_table(); auto variables = symtab->get_variables_with_properties(NmodlType::local_var); - for (auto& var : variables) { + for (const auto& var : variables) { auto renamed_name = var->get_name(); auto original_name = var->get_original_name(); if (current == original_name) { @@ -82,6 +505,49 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { } +int CodegenCVisitor::float_variables_size() { + auto count_length = [](std::vector<SymbolType>& variables) -> int { + int length = 0; + for (const auto& variable : variables) { + length += variable->get_length(); + } + return length; + }; + + int float_size = count_length(info.range_parameter_vars); + float_size += count_length(info.range_dependent_vars); + float_size += count_length(info.state_vars); + float_size += count_length(info.dependent_vars); + + /// for state variables we add Dstate variables + float_size += info.state_vars.size(); + float_size += info.ion_state_vars.size(); + + /// for v_unused variable + if (info.vectorize) { + float_size++; + } + /// for g_unused variable + if (breakpoint_exist()) { + float_size++; + } + /// for tsave variable + if (net_receive_exist()) { + float_size++; + } + return float_size; +} + + +int CodegenCVisitor::int_variables_size() { + int num_variables = 0; + for (const auto& semantic : info.semantics) { + num_variables += semantic.size; + } + return num_variables; +} + + /** * For given block type, return statements for all read ion variables * @@ -96,12 +562,12 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { */ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { if (optimize_ion_variable_copies()) { - return ion_read_statements_optimal(type); + return ion_read_statements_optimized(type); } std::vector<std::string> statements; - for (auto& ion : info.ions) { + for (const auto& ion : info.ions) { auto name = ion.name; - for (auto& var : ion.reads) { + for (const auto& var : ion.reads) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } @@ -110,7 +576,7 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { auto second = get_variable_name(variable_names.second); statements.push_back("{} = {};"_format(first, second)); } - for (auto& var : ion.writes) { + for (const auto& var : ion.writes) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } @@ -126,10 +592,10 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { } -std::vector<std::string> CodegenCVisitor::ion_read_statements_optimal(BlockType type) { +std::vector<std::string> CodegenCVisitor::ion_read_statements_optimized(BlockType type) { std::vector<std::string> statements; - for (auto& ion : info.ions) { - for (auto& var : ion.writes) { + for (const auto& ion : info.ions) { + for (const auto& var : ion.writes) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } @@ -147,10 +613,10 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements_optimal(BlockType std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType type) { std::vector<ShadowUseStatement> statements; - for (auto& ion : info.ions) { + for (const auto& ion : info.ions) { std::string concentration; auto name = ion.name; - for (auto& var : ion.writes) { + for (const auto& var : ion.writes) { auto variable_names = write_ion_variable_name(var); if (ion.is_ionic_current(var)) { if (type == BlockType::Equation) { @@ -159,7 +625,7 @@ std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType auto op = "+="; auto rhs = get_variable_name(current); if (info.point_process) { - auto area = get_variable_name(node_area); + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); rhs += "*(1.e2/{})"_format(area); } statements.push_back(ShadowUseStatement{lhs, op, rhs}); @@ -259,18 +725,232 @@ bool CodegenCVisitor::is_constant_variable(std::string name) { return is_constant; } + /** - * NMODL constants from unit database + * Once variables are populated, update index semantics to register with coreneuron + */ +void CodegenCVisitor::update_index_semantics() { + int index = 0; + info.semantics.clear(); + + if (info.point_process) { + info.semantics.emplace_back(index++, naming::AREA_SEMANTIC, 1); + info.semantics.emplace_back(index++, naming::POINT_PROCESS_SEMANTIC, 1); + } + for (const auto& ion : info.ions) { + for (auto& var : ion.reads) { + info.semantics.emplace_back(index++, ion.name + "_ion", 1); + } + for (auto& var : ion.writes) { + info.semantics.emplace_back(index++, ion.name + "_ion", 1); + if (ion.is_ionic_current(var)) { + info.semantics.emplace_back(index++, ion.name + "_ion", 1); + } + } + if (ion.need_style) { + info.semantics.emplace_back(index++, "#{}_ion"_format(ion.name), 1); + } + } + for (auto& var : info.pointer_variables) { + if (info.first_pointer_var_index == -1) { + info.first_pointer_var_index = index; + } + int size = var->get_length(); + if (var->has_properties(NmodlType::pointer_var)) { + info.semantics.emplace_back(index, naming::POINTER_SEMANTIC, size); + } else { + info.semantics.emplace_back(index, naming::CORE_POINTER_SEMANTIC, size); + } + index += size; + } + + if (info.diam_used) { + info.semantics.emplace_back(index++, naming::DIAM_VARIABLE, 1); + } + + if (info.area_used) { + info.semantics.emplace_back(index++, naming::AREA_VARIABLE, 1); + } + + if (info.net_send_used) { + info.semantics.emplace_back(index++, naming::NET_SEND_SEMANTIC, 1); + } + + /** + * Number of semantics for watch is one greater than number of + * actual watch statements in the mod file + */ + if (!info.watch_statements.empty()) { + for (int i = 0; i < info.watch_statements.size() + 1; i++) { + info.semantics.emplace_back(index++, naming::WATCH_SEMANTIC, 1); + } + } + + if (info.for_netcon_used) { + info.semantics.emplace_back(index++, naming::FOR_NETCON_SEMANTIC, 1); + } +} + + +/** + * Return all floating point variables required for code generation + */ +std::vector<SymbolType> CodegenCVisitor::get_float_variables() { + /// sort with definition order + auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { + return first->get_definition_order() < second->get_definition_order(); + }; + + auto dependents = info.dependent_vars; + auto states = info.state_vars; + states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); + + /// each state variable has corresponding Dstate variable + for (auto& variable : states) { + auto name = "D" + variable->get_name(); + auto symbol = make_symbol(name); + symbol->set_definition_order(variable->get_definition_order()); + dependents.push_back(symbol); + } + std::sort(dependents.begin(), dependents.end(), comparator); + + auto variables = info.range_parameter_vars; + variables.insert(variables.end(), info.range_dependent_vars.begin(), + info.range_dependent_vars.end()); + variables.insert(variables.end(), info.state_vars.begin(), info.state_vars.end()); + variables.insert(variables.end(), dependents.begin(), dependents.end()); + + if (info.vectorize) { + variables.push_back(make_symbol(naming::VOLTAGE_UNUSED_VARIABLE)); + } + if (breakpoint_exist()) { + std::string name = + info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE : naming::CONDUCTANCE_VARIABLE; + variables.push_back(make_symbol(name)); + } + if (net_receive_exist()) { + variables.push_back(make_symbol(naming::T_SAVE_VARIABLE)); + } + return variables; +} + + +/** + * Return all integer variables required for code generation * - * todo : this should be replaced with constant handling from unit database + * IndexVariableInfo has following constructor arguments: + * - symbol + * - is_vdata (false) + * - is_index (false + * - is_integer (false) + * + * Which variables are constant qualified? + * + * - node area is read only + * - read ion variables are read only + * - style_ionname is index / offset */ +std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { + std::vector<IndexVariableInfo> variables; + if (info.point_process) { + variables.emplace_back(make_symbol(naming::NODE_AREA_VARIABLE)); + variables.back().is_constant = true; + /// note that this variable is not printed in neuron implementation + if (info.artificial_cell) { + variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), true); + } else { + variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), false, false, true); + variables.back().is_constant = true; + } + } -void CodegenCVisitor::print_nmodl_constant() { - printer->add_newline(2); - printer->add_line("/** constants used in nmodl */"); - printer->add_line("static double FARADAY = 96485.3;"); - printer->add_line("static double PI = 3.14159;"); - printer->add_line("static double R = 8.3145;"); + for (const auto& ion : info.ions) { + bool need_style = false; + for (const auto& var : ion.reads) { + variables.emplace_back(make_symbol("ion_" + var)); + variables.back().is_constant = true; + } + for (const auto& var : ion.writes) { + variables.emplace_back(make_symbol("ion_" + var)); + if (ion.is_ionic_current(var)) { + variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); + } + if (ion.is_intra_cell_conc(var) || ion.is_extra_cell_conc(var)) { + need_style = true; + } + } + if (need_style) { + variables.emplace_back(make_symbol("style_" + ion.name), false, true); + variables.back().is_constant = true; + } + } + + for (const auto& var : info.pointer_variables) { + auto name = var->get_name(); + if (var->has_properties(NmodlType::pointer_var)) { + variables.emplace_back(make_symbol(name)); + } else { + variables.emplace_back(make_symbol(name), true); + } + } + + if (info.diam_used) { + variables.emplace_back(make_symbol(naming::DIAM_VARIABLE)); + } + + if (info.area_used) { + variables.emplace_back(make_symbol(naming::AREA_VARIABLE)); + } + + // for non-artificial cell, when net_receive buffering is enabled + // then tqitem is an offset + if (info.net_send_used) { + if (info.artificial_cell) { + variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), true); + } else { + variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), false, false, true); + variables.back().is_constant = true; + } + info.tqitem_index = variables.size() - 1; + } + + /** + * Variables for watch statements : note that there is one extra variable + * used in coreneuron compared to actual watch statements for compatibility + * with neuron (which uses one extra Datum variable) + */ + if (!info.watch_statements.empty()) { + for (int i = 0; i < info.watch_statements.size() + 1; i++) { + variables.emplace_back(make_symbol("watch{}"_format(i)), false, false, true); + } + } + return variables; +} + + +/** + * Return all ion write variables that require shadow vectors during code generation + * + * When we enable fine level parallelism at channel level, we have do updates + * to ion variables in atomic way. As cpus don't have atomic instructions in + * simd loop, we have to use shadow vectors for every ion variables. Here + * we return list of all such variables. + * + * \todo: if conductances are specified, we don't need all below variables + */ +std::vector<SymbolType> CodegenCVisitor::get_shadow_variables() { + std::vector<SymbolType> variables; + for (const auto& ion : info.ions) { + for (const auto& var : ion.writes) { + variables.push_back({make_symbol(shadow_varname("ion_" + var))}); + if (ion.is_ionic_current(var)) { + variables.push_back({make_symbol(shadow_varname("ion_di" + ion.name + "dv"))}); + } + } + } + variables.push_back({make_symbol("ml_rhs")}); + variables.push_back({make_symbol("ml_d")}); + return variables; } @@ -337,10 +1017,10 @@ void CodegenCVisitor::print_kernel_data_present_annotation_block_end() { * for parallelization. For example: * * #pragma ivdep - * for(int id=0; id<nodecount; id++) { + * for(int id = 0; id < nodecount; id++) { * * #pragma acc parallel loop - * for(int id=0; id<nodecount; id++) { + * for(int id = 0; id < nodecount; id++) { * */ void CodegenCVisitor::print_channel_iteration_block_parallel_hint() { @@ -355,7 +1035,7 @@ bool CodegenCVisitor::nrn_cur_reduction_loop_required() { /// if shadow vectors required bool CodegenCVisitor::shadow_vector_setup_required() { - return (channel_task_dependency_enabled() && !shadow_variables.empty()); + return (channel_task_dependency_enabled() && !codegen_shadow_variables.empty()); } @@ -372,15 +1052,14 @@ void CodegenCVisitor::print_channel_iteration_block_begin() { void CodegenCVisitor::print_channel_iteration_block_end() { - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } void CodegenCVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { - printer->add_line("double* shadow_rhs = nt->_shadow_rhs;"); - printer->add_line("double* shadow_d = nt->_shadow_d;"); + printer->add_line("double* shadow_rhs = nt->{};"_format(naming::NTHREAD_RHS_SHADOW)); + printer->add_line("double* shadow_d = nt->{};"_format(naming::NTHREAD_D_SHADOW)); } } @@ -441,7 +1120,7 @@ void CodegenCVisitor::print_shadow_reduction_block_begin() { void CodegenCVisitor::print_shadow_reduction_statements() { - for (auto& statement : shadow_statements) { + for (const auto& statement : shadow_statements) { print_atomic_reduction_pragma(); auto lhs = get_variable_name(statement.lhs); auto rhs = get_variable_name(shadow_varname(statement.lhs)); @@ -453,8 +1132,7 @@ void CodegenCVisitor::print_shadow_reduction_statements() { void CodegenCVisitor::print_shadow_reduction_block_end() { - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -473,13 +1151,13 @@ void CodegenCVisitor::print_backend_namespace_start() { } -void CodegenCVisitor::print_backend_namespace_end() { +void CodegenCVisitor::print_backend_namespace_stop() { // no separate namespace for C (cpu) backend } void CodegenCVisitor::print_backend_includes() { - // backend specific, do nothing + // backend specific, nothing for cpu } @@ -522,16 +1200,16 @@ void CodegenCVisitor::print_memory_allocation_routine() { std::string CodegenCVisitor::compute_method_name(BlockType type) { if (type == BlockType::Initial) { - return method_name("nrn_init"); + return method_name(naming::NRN_INIT_METHOD); } if (type == BlockType::State) { - return method_name("nrn_state"); + return method_name(naming::NRN_STATE_METHOD); } if (type == BlockType::Equation) { - return method_name("nrn_cur"); + return method_name(naming::NRN_CUR_METHOD); } if (type == BlockType::Watch) { - return method_name("nrn_watch_check"); + return method_name(naming::NRN_WATCH_CHECK_METHOD); } throw std::logic_error("compute_method_name not implemented"); } @@ -549,10 +1227,16 @@ std::string CodegenCVisitor::k_const() { /****************************************************************************************/ -/* Non-code-specific printing routines for code generation */ +/* printing routines for code generation */ /****************************************************************************************/ +void CodegenCVisitor::visit_watch_statement(ast::WatchStatement* node) { + printer->add_text( + "nrn_watch_activate(inst, id, pnodecount, {})"_format(current_watch_statement++)); +} + + void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, bool open_brace, bool close_brace) { @@ -561,11 +1245,11 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, } auto statements = node->get_statements(); - for (auto& statement : statements) { - if (skip_statement(statement.get())) { + for (const auto& statement : statements) { + if (statement_to_skip(statement.get())) { continue; } - /// not necessary to add indent for vebatim block (pretty-printing) + /// not necessary to add indent for verbatim block (pretty-printing) if (!statement->is_verbatim()) { printer->add_indent(); } @@ -582,12 +1266,6 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, } -void CodegenCVisitor::visit_watch_statement(ast::WatchStatement* node) { - printer->add_text( - "nrn_watch_activate(inst, id, pnodecount, {})"_format(current_watch_statement++)); -} - - void CodegenCVisitor::print_function_call(FunctionCall* node) { auto name = node->get_node_name(); auto function_name = name; @@ -629,14 +1307,14 @@ void CodegenCVisitor::print_top_verbatim_blocks() { if (info.top_verbatim_blocks.empty()) { return; } - print_namespace_end(); + print_namespace_stop(); printer->add_newline(2); printer->add_line("using namespace coreneuron;"); codegen = true; printing_top_verbatim_blocks = true; - for (auto& block : info.top_blocks) { + for (const auto& block : info.top_blocks) { if (block->is_verbatim()) { printer->add_newline(2); block->accept(this); @@ -698,16 +1376,19 @@ void CodegenCVisitor::print_function_prototypes() { static TableStatement* get_table_statement(ast::Block* node) { - TableStatementVisitor v; + // TableStatementVisitor v; + + AstLookupVisitor v(AstNodeType::TABLE_STATEMENT); node->accept(&v); - auto table_statements = v.get_statements(); - auto nstatements = table_statements.size(); - if (nstatements != 1) { - auto message = "One table statement expected in {} found {}"_format(node->get_node_name(), - nstatements); + + auto table_statements = v.get_nodes(); + + if (table_statements.size() != 1) { + auto message = "One table statement expected in {} found {}"_format( + node->get_node_name(), table_statements.size()); throw std::runtime_error(message); } - return table_statements.front(); + return dynamic_cast<TableStatement*>(table_statements.front()); } @@ -720,7 +1401,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { auto name = node->get_node_name(); auto internal_params = internal_method_parameters(); auto with = statement->get_with()->eval(); - auto use_table_var = get_variable_name("usetable"); + auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); auto tmin_name = get_variable_name("tmin_" + name); auto mfac_name = get_variable_name("mfac_" + name); auto float_type = default_float_data_type(); @@ -734,11 +1415,11 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_line("}"); printer->add_line("static bool make_table = true;"); - for (auto& variable : depend_variables) { + for (const auto& variable : depend_variables) { printer->add_line("static {} save_{};"_format(float_type, variable->get_node_name())); } - for (auto& variable : depend_variables) { + for (const auto& variable : depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); @@ -772,7 +1453,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { "for(i = 0, x = {}; i < {}; x += dx, i++) {}"_format(tmin_name, with + 1, "{")); auto function = method_name("f_" + name); printer->add_line(" {}({}, x);"_format(function, internal_method_arguments())); - for (auto& variable : table_variables) { + for (const auto& variable : table_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); @@ -780,7 +1461,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { } printer->add_line("}"); - for (auto& variable : depend_variables) { + for (const auto& variable : depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); printer->add_line("save_{} = {};"_format(name, instance_name)); @@ -788,8 +1469,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { } printer->end_block(); } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { @@ -797,7 +1477,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); auto with = statement->get_with()->eval(); - auto use_table_var = get_variable_name("usetable"); + auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); auto float_type = default_float_data_type(); auto tmin_name = get_variable_name("tmin_" + name); auto mfac_name = get_variable_name("mfac_" + name); @@ -814,7 +1494,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); printer->add_line("if (isnan(xi)) {"); - for (auto& var : table_variables) { + for (const auto& var : table_variables) { auto name = get_variable_name(var->get_node_name()); printer->add_line(" {} = xi;"_format(name)); } @@ -823,7 +1503,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); - for (auto& variable : table_variables) { + for (const auto& variable : table_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); @@ -834,7 +1514,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("int i = int(xi);"); printer->add_line("double theta = xi - double(i);"); - for (auto& var : table_variables) { + for (const auto& var : table_variables) { auto instance_name = get_variable_name(var->get_node_name()); auto table_name = get_variable_name("t_" + var->get_node_name()); printer->add_line( @@ -843,8 +1523,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("return 0;"); } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -864,7 +1543,7 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" double v = 0;"); printer->add_line(" IonCurVar ionvar = {0};"); - for (auto& function : info.functions_with_table) { + for (const auto& function : info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); printer->add_line(" {}({});"_format(name, arguments)); @@ -878,7 +1557,6 @@ void CodegenCVisitor::print_check_table_thread_function() { } void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& name) { - auto block = node->get_statement_block().get(); printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); @@ -892,10 +1570,9 @@ void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& printer->add_line("int ret_{} = 0;"_format(name)); } - print_statement_block(block, false, false); + print_statement_block(node->get_statement_block().get(), false, false); printer->add_line("return ret_{};"_format(name)); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { @@ -997,25 +1674,13 @@ std::string CodegenCVisitor::nrn_thread_internal_arguments() { * variable name in the new code generation backend. */ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { - // clang-format off - std::map<std::string, std::string> verbatim_variables_map{ - {"_nt", "nt"}, - {"_p", "data"}, - {"_ppvar", "indexes"}, - {"_thread", "thread"}, - {"_iml", "id"}, - {"_cntml_padded", "pnodecount"}, - {"_cntml", "nodecount"}, - {"_tqitem", "tqitem"}}; - // clang-format on - - if (verbatim_variables_map.find(name) != verbatim_variables_map.end()) { - name = verbatim_variables_map[name]; + if (naming::VERBATIM_VARIABLES_MAPPING.find(name) != naming::VERBATIM_VARIABLES_MAPPING.end()) { + name = naming::VERBATIM_VARIABLES_MAPPING.at(name); } /// if function is defined the same mod file then the arguments must /// contain mechanism instance as well. - if (name == "_threadargs_") { + if (name == naming::THREAD_ARGS) { if (internal_method_call_encountered) { name = nrn_thread_internal_arguments(); internal_method_call_encountered = false; @@ -1023,7 +1688,7 @@ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { name = nrn_thread_arguments(); } } - if (name == "_threadargsproto_") { + if (name == naming::THREAD_ARGS_PROTO) { name = external_method_parameters(); } return name; @@ -1049,7 +1714,7 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { } auto name = process_verbatim_token(token); - if (token == "_tqitem") { + if (token == ("_" + naming::TQITEM_VARIABLE)) { name = "&" + name; } if (token == "_STRIDE") { @@ -1062,10 +1727,10 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { std::string CodegenCVisitor::register_mechanism_arguments() { - auto nrn_cur = nrn_cur_required() ? method_name("nrn_cur") : "NULL"; - auto nrn_state = nrn_state_required() ? method_name("nrn_state") : "NULL"; - auto nrn_alloc = method_name("nrn_alloc"); - auto nrn_init = method_name("nrn_init"); + auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "NULL"; + auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "NULL"; + auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); + auto nrn_init = method_name(naming::NRN_INIT_METHOD); return "mechanism, {}, {}, NULL, {}, {}, first_pointer_var_index()" ""_format(nrn_alloc, nrn_cur, nrn_state, nrn_init); } @@ -1136,6 +1801,21 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& /****************************************************************************************/ +/** + * NMODL constants from unit database + * + * todo : this should be replaced with constant handling from unit database + */ + +void CodegenCVisitor::print_nmodl_constant() { + printer->add_newline(2); + printer->add_line("/** constants used in nmodl */"); + printer->add_line("static double FARADAY = 96485.3;"); + printer->add_line("static double PI = 3.14159;"); + printer->add_line("static double R = 8.3145;"); +} + + void CodegenCVisitor::print_memory_layout_getter() { printer->add_newline(2); printer->add_line("static inline int get_memory_layout() {"); @@ -1160,14 +1840,14 @@ void CodegenCVisitor::print_first_pointer_var_index_getter() { void CodegenCVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int num_float_variable() {"); - printer->add_line(" return {};"_format(num_float_variable())); + printer->add_line("static inline int float_variables_size() {"); + printer->add_line(" return {};"_format(float_variables_size())); printer->add_line("}"); printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int num_int_variable() {"); - printer->add_line(" return {};"_format(num_int_variable())); + printer->add_line("static inline int int_variables_size() {"); + printer->add_line(" return {};"_format(int_variables_size())); printer->add_line("}"); } @@ -1207,8 +1887,8 @@ void CodegenCVisitor::print_memb_list_getter() { void CodegenCVisitor::print_post_channel_iteration_common_code() { if (layout == LayoutType::aos) { - printer->add_line("data = ml->data + id*{};"_format(num_float_variable())); - printer->add_line("indexes = ml->pdata + id*{};"_format(num_int_variable())); + printer->add_line("data = ml->data + id*{};"_format(float_variables_size())); + printer->add_line("indexes = ml->pdata + id*{};"_format(int_variables_size())); } } @@ -1219,9 +1899,8 @@ void CodegenCVisitor::print_namespace_start() { } -void CodegenCVisitor::print_namespace_end() { - printer->end_block(); - printer->add_newline(); +void CodegenCVisitor::print_namespace_stop() { + printer->end_block(1); } @@ -1291,7 +1970,7 @@ void CodegenCVisitor::print_thread_getters() { std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_instance) { auto name = symbol->get_name(); auto dimension = symbol->get_length(); - auto num_float = num_float_variable(); + auto num_float = float_variables_size(); auto position = position_of_float_var(name); // clang-format off if (symbol->is_array()) { @@ -1316,7 +1995,7 @@ std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, const std::string& name, bool use_instance) { auto position = position_of_int_var(name); - auto num_int = num_int_variable(); + auto num_int = int_variables_size(); std::string offset; // clang-format off if (symbol.is_index) { @@ -1354,19 +2033,20 @@ std::string CodegenCVisitor::ion_shadow_variable_name(SymbolType& symbol) { } -std::string CodegenCVisitor::update_if_ion_variable_name(std::string name) { +std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name) { + std::string result(name); if (ion_variable_struct_required()) { if (info.is_ion_read_variable(name)) { - return "ion_" + name; + result = "ion_" + name; } if (info.is_ion_write_variable(name)) { - return "ionvar." + name; + result = "ionvar." + name; } if (info.is_current(name)) { - return "ionvar." + name; + result = "ionvar." + name; } } - return name; + return result; } @@ -1376,55 +2056,59 @@ std::string CodegenCVisitor::update_if_ion_variable_name(std::string name) { * @param name variable name that is being printed * @return use_instance whether print name using Instance structure (or data array if false) */ -std::string CodegenCVisitor::get_variable_name(std::string name, bool use_instance) { - name = update_if_ion_variable_name(name); +std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use_instance) { + std::string varname = update_if_ion_variable_name(name); // clang-format off - auto l_symbol = [&name](const SymbolType& sym) { - return name == sym->get_name(); + auto symbol_comparator = [&varname](const SymbolType& sym) { + return varname == sym->get_name(); }; - auto i_symbol = [&name](const IndexVariableInfo& var) { - return name == var.symbol->get_name(); + auto index_comparator = [&varname](const IndexVariableInfo& var) { + return varname == var.symbol->get_name(); }; // clang-format on /// float variable - auto f = std::find_if(float_variables.begin(), float_variables.end(), l_symbol); - if (f != float_variables.end()) { + auto f = std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), + symbol_comparator); + if (f != codegen_float_variables.end()) { return float_variable_name(*f, use_instance); } /// integer variable - auto i = std::find_if(int_variables.begin(), int_variables.end(), i_symbol); - if (i != int_variables.end()) { - return int_variable_name(*i, name, use_instance); + auto i = + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); + if (i != codegen_int_variables.end()) { + return int_variable_name(*i, varname, use_instance); } /// global variable - auto g = std::find_if(global_variables.begin(), global_variables.end(), l_symbol); - if (g != global_variables.end()) { + auto g = std::find_if(codegen_global_variables.begin(), codegen_global_variables.end(), + symbol_comparator); + if (g != codegen_global_variables.end()) { return global_variable_name(*g); } /// shadow variable - auto s = std::find_if(shadow_variables.begin(), shadow_variables.end(), l_symbol); - if (s != shadow_variables.end()) { + auto s = std::find_if(codegen_shadow_variables.begin(), codegen_shadow_variables.end(), + symbol_comparator); + if (s != codegen_shadow_variables.end()) { return ion_shadow_variable_name(*s); } - if (name == "dt") { - return "nt->_dt"; + if (varname == naming::NTHREAD_DT_VARIABLE) { + return "nt->_" + naming::NTHREAD_DT_VARIABLE; } /// t in net_receive method is an argument to function and hence it should /// ne used instead of nt->_t which is current time of thread - if (name == "t" && !printing_net_receive) { - return "nt->_t"; + if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { + return "nt->_" + naming::NTHREAD_T_VARIABLE; } /// otherwise return original name - return name; + return varname; } @@ -1493,7 +2177,7 @@ void CodegenCVisitor::print_coreneuron_includes() { * Note that static variables are already initialized to 0. We do the * same for some variables to keep same code as neuron. */ -void CodegenCVisitor::print_mechanism_global_structure() { +void CodegenCVisitor::print_mechanism_global_var_structure() { auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); @@ -1504,34 +2188,34 @@ void CodegenCVisitor::print_mechanism_global_structure() { for (const auto& ion : info.ions) { auto name = "{}_type"_format(ion.name); printer->add_line("int {};"_format(name)); - global_variables.push_back(make_symbol(name)); + codegen_global_variables.push_back(make_symbol(name)); } } if (info.point_process) { printer->add_line("int point_type;"); - global_variables.push_back(make_symbol("point_type")); + codegen_global_variables.push_back(make_symbol("point_type")); } if (!info.state_vars.empty()) { - for (auto& var : info.state_vars) { + for (const auto& var : info.state_vars) { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { printer->add_line("{} {};"_format(float_type, name)); - global_variables.push_back(make_symbol(name)); + codegen_global_variables.push_back(make_symbol(name)); } } } if (!info.vectorize) { printer->add_line("{} v;"_format(float_type)); - global_variables.push_back(make_symbol("v")); + codegen_global_variables.push_back(make_symbol("v")); } auto& top_locals = info.top_local_variables; if (!info.vectorize && !top_locals.empty()) { - for (auto& var : top_locals) { + for (const auto& var : top_locals) { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { @@ -1539,30 +2223,30 @@ void CodegenCVisitor::print_mechanism_global_structure() { } else { printer->add_line("{} {};"_format(float_type, name)); } - global_variables.push_back(var); + codegen_global_variables.push_back(var); } } if (!info.thread_variables.empty()) { printer->add_line("int thread_data_in_use;"); printer->add_line("{} thread_data[{}];"_format(float_type, info.thread_var_data_size)); - global_variables.push_back(make_symbol("thread_data_in_use")); + codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); symbol->set_as_array(info.thread_var_data_size); - global_variables.push_back(symbol); + codegen_global_variables.push_back(symbol); } printer->add_line("int reset;"); - global_variables.push_back(make_symbol("reset")); + codegen_global_variables.push_back(make_symbol("reset")); printer->add_line("int mech_type;"); - global_variables.push_back(make_symbol("mech_type")); + codegen_global_variables.push_back(make_symbol("mech_type")); auto& globals = info.global_variables; auto& constants = info.constant_variables; if (!globals.empty()) { - for (auto& var : globals) { + for (const auto& var : globals) { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { @@ -1570,52 +2254,52 @@ void CodegenCVisitor::print_mechanism_global_structure() { } else { printer->add_line("{} {};"_format(float_type, name)); } - global_variables.push_back(var); + codegen_global_variables.push_back(var); } } if (!constants.empty()) { - for (auto& var : constants) { + for (const auto& var : constants) { auto name = var->get_name(); auto value_ptr = var->get_value(); printer->add_line("{} {};"_format(float_type, name)); - global_variables.push_back(var); + codegen_global_variables.push_back(var); } } if (info.primes_size != 0) { printer->add_line("int* slist1;"); printer->add_line("int* dlist1;"); - global_variables.push_back(make_symbol("slist1")); - global_variables.push_back(make_symbol("dlist1")); + codegen_global_variables.push_back(make_symbol("slist1")); + codegen_global_variables.push_back(make_symbol("dlist1")); if (info.derivimplicit_used) { printer->add_line("int* slist2;"); - global_variables.push_back(make_symbol("slist2")); + codegen_global_variables.push_back(make_symbol("slist2")); } } if (info.table_count > 0) { printer->add_line("double usetable;"); - global_variables.push_back(make_symbol("usetable")); + codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); - for (auto& block : info.functions_with_table) { + for (const auto& block : info.functions_with_table) { auto name = block->get_node_name(); printer->add_line("{} tmin_{};"_format(float_type, name)); printer->add_line("{} mfac_{};"_format(float_type, name)); - global_variables.push_back(make_symbol("tmin_" + name)); - global_variables.push_back(make_symbol("mfac_" + name)); + codegen_global_variables.push_back(make_symbol("tmin_" + name)); + codegen_global_variables.push_back(make_symbol("mfac_" + name)); } - for (auto& variable : info.table_statement_variables) { + for (const auto& variable : info.table_statement_variables) { auto name = "t_" + variable->get_name(); printer->add_line("{}* {};"_format(float_type, name)); - global_variables.push_back(make_symbol(name)); + codegen_global_variables.push_back(make_symbol(name)); } } if (info.vectorize) { printer->add_line("ThreadDatum* {}ext_call_thread;"_format(k_restrict())); - global_variables.push_back(make_symbol("ext_call_thread")); + codegen_global_variables.push_back(make_symbol("ext_call_thread")); } printer->decrease_indent(); @@ -1627,9 +2311,9 @@ void CodegenCVisitor::print_mechanism_global_structure() { } -void CodegenCVisitor::print_mechanism_info_array() { +void CodegenCVisitor::print_mechanism_info() { auto variable_printer = [this](std::vector<SymbolType>& variables) { - for (auto& v : variables) { + for (const auto& v : variables) { auto name = v->get_name(); if (!info.point_process) { name += "_" + info.mod_suffix; @@ -1665,9 +2349,9 @@ void CodegenCVisitor::print_mechanism_info_array() { * vector elements of type global and thread variables. */ void CodegenCVisitor::print_global_variables_for_hoc() { - auto variable_printer = [this](std::vector<SymbolType>& variables, bool if_array, + auto variable_printer = [this](const std::vector<SymbolType>& variables, bool if_array, bool if_vector) { - for (auto& variable : variables) { + for (const auto& variable : variables) { if (variable->is_array() == if_array) { auto name = get_variable_name(variable->get_name()); auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); @@ -1685,7 +2369,7 @@ void CodegenCVisitor::print_global_variables_for_hoc() { auto thread_vars = info.thread_variables; if (info.table_count > 0) { - globals.push_back(make_symbol("usetable")); + globals.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); } printer->add_newline(2); @@ -1799,7 +2483,7 @@ void CodegenCVisitor::print_mechanism_register() { /// register size of double and int elements // clang-format off - printer->add_line("hoc_register_prop_size(mech_type, num_float_variable(), num_int_variable());"); + printer->add_line("hoc_register_prop_size(mech_type, float_variables_size(), int_variables_size());"); // clang-format on /// register semantics for index variables @@ -1835,8 +2519,7 @@ void CodegenCVisitor::print_mechanism_register() { /// register variables for hoc printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -1871,12 +2554,10 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->add_line(" {} = 1;"_format(thread_data_in_use)); printer->add_line("}"); } - printer->end_block(); - printer->add_newline(); + printer->end_block(3); /// thread_mem_cleanup callback - printer->add_newline(2); printer->add_line("/** thread memory cleanup callback */"); printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); @@ -1898,24 +2579,23 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->add_line(" free(thread[thread_var_tid()].pval);"); printer->add_line("}"); } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } -void CodegenCVisitor::print_mechanism_var_structure() { +void CodegenCVisitor::print_mechanism_range_var_structure() { auto float_type = default_float_data_type(); auto int_type = default_int_data_type(); printer->add_newline(2); printer->add_line("/** all mechanism instance variables */"); printer->start_block("struct {} "_format(instance_struct())); - for (auto& var : float_variables) { + for (auto& var : codegen_float_variables) { auto name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? k_const() : ""; printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); } - for (auto& var : int_variables) { + for (auto& var : codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? k_const() : ""; @@ -1927,7 +2607,7 @@ void CodegenCVisitor::print_mechanism_var_structure() { } } if (channel_task_dependency_enabled()) { - for (auto& var : shadow_variables) { + for (auto& var : codegen_shadow_variables) { auto name = var->get_name(); printer->add_line("{}* {}{};"_format(float_type, k_restrict(), name)); } @@ -1938,7 +2618,7 @@ void CodegenCVisitor::print_mechanism_var_structure() { } -void CodegenCVisitor::print_ion_variables_structure() { +void CodegenCVisitor::print_ion_var_structure() { if (!ion_variable_struct_required()) { return; } @@ -2059,7 +2739,7 @@ void CodegenCVisitor::print_global_variable_setup() { } if (info.table_count > 0) { - auto name = get_variable_name("usetable"); + auto name = get_variable_name(naming::USE_TABLE_VARIABLE); printer->add_line("{} = 1;"_format(name)); for (auto& variable : info.table_statement_variables) { @@ -2072,11 +2752,8 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_newline(); printer->add_line("setup_done = 1;"); + printer->end_block(3); - printer->end_block(); - printer->add_newline(); - - printer->add_newline(2); printer->add_line("/** free global variables */"); printer->start_block("static inline void free_global_variables() "); if (allocated_variables.empty()) { @@ -2086,8 +2763,7 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line("mem_free({});"_format(var)); } } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -2098,28 +2774,25 @@ void CodegenCVisitor::print_shadow_vector_setup() { printer->start_block("static inline void setup_shadow_vectors({}) "_format(args)); if (channel_task_dependency_enabled()) { printer->add_line("int nodecount = ml->nodecount;"); - for (auto& var : shadow_variables) { + for (auto& var : codegen_shadow_variables) { auto name = var->get_name(); auto type = default_float_data_type(); auto allocation = "({0}*) mem_alloc(nodecount, sizeof({0}))"_format(type); printer->add_line("inst->{0} = {1};"_format(name, allocation)); } } - printer->end_block(); - printer->add_newline(); + printer->end_block(3); - printer->add_newline(2); printer->add_line("/** free shadow vector */"); args = "{}* inst"_format(instance_struct()); printer->start_block("static inline void free_shadow_vectors({}) "_format(args)); if (channel_task_dependency_enabled()) { - for (auto& var : shadow_variables) { + for (auto& var : codegen_shadow_variables) { auto name = var->get_name(); printer->add_line("mem_free(inst->{});"_format(name)); } } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -2134,8 +2807,7 @@ void CodegenCVisitor::print_setup_range_variable() { printer->add_line(" data[i] = variable[i];"); printer->add_line("}"); printer->add_line("return data;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -2178,7 +2850,7 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("/** initialize mechanism instance variables */"); printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml) "); printer->add_line("{0}* inst = ({0}*) mem_alloc(1, sizeof({0}));"_format(instance_struct())); - if (channel_task_dependency_enabled() && !shadow_variables.empty()) { + if (channel_task_dependency_enabled() && !codegen_shadow_variables.empty()) { printer->add_line("setup_shadow_vectors(inst, ml);"); } @@ -2191,7 +2863,7 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("Datum* indexes = ml->pdata;"); int id = 0; std::vector<std::string> variables_to_free; - for (auto& var : float_variables) { + for (auto& var : codegen_float_variables) { auto name = var->get_name(); auto default_type = default_float_data_type(); auto range_var_type = get_range_var_float_type(var); @@ -2205,7 +2877,7 @@ void CodegenCVisitor::print_instance_variable_setup() { id += var->get_length(); } - for (auto& var : int_variables) { + for (auto& var : codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { printer->add_line("inst->{} = ml->pdata;"_format(name)); @@ -2216,9 +2888,8 @@ void CodegenCVisitor::print_instance_variable_setup() { } printer->add_line("ml->instance = (void*) inst;"); - printer->end_block(); + printer->end_block(3); - printer->add_newline(2); printer->add_line("/** cleanup mechanism instance variables */"); printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); @@ -2228,8 +2899,7 @@ void CodegenCVisitor::print_instance_variable_setup() { } } printer->add_line("mem_free((void*)inst);"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -2341,15 +3011,13 @@ void CodegenCVisitor::print_nrn_init() { print_shadow_reduction_statements(); print_channel_iteration_tiling_block_end(); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); if (info.derivimplicit_used) { printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); } print_kernel_data_present_annotation_block_end(); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -2359,8 +3027,7 @@ void CodegenCVisitor::print_nrn_alloc() { auto method = method_name("nrn_alloc"); printer->start_block("static void {}(double* data, Datum* indexes, int type) "_format(method)); printer->add_line("// do nothing"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } /** @@ -2403,11 +3070,9 @@ void CodegenCVisitor::print_watch_activate() { printer->add_text(";"); printer->add_newline(); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -2460,30 +3125,26 @@ void CodegenCVisitor::print_watch_check() { printer->add_newline(); printer->add_line("{} = 3;"_format(varname)); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); // end block 3 // start block 3 printer->start_block("else"_format()); printer->add_line("{} = 2;"_format(varname)); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); // end block 3 printer->decrease_indent(); printer->add_line("}"); // end block 2 - printer->end_block(); - printer->add_newline(); + printer->end_block(1); // end block 1 } print_channel_iteration_block_end(); print_send_event_move(); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -2598,7 +3259,7 @@ void CodegenCVisitor::print_net_event_call(FunctionCall* node) { } -void CodegenCVisitor::print_net_init_kernel() { +void CodegenCVisitor::print_net_init() { auto node = info.net_receive_initial_node; if (node == nullptr) { return; @@ -2615,8 +3276,7 @@ void CodegenCVisitor::print_net_init_kernel() { print_net_receive_common_code(node); print_statement_block(block, false, false); } - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -2640,7 +3300,7 @@ void CodegenCVisitor::print_send_event_move() { // todo : update net send buffer count on device } -void CodegenCVisitor::print_net_receive_buffer_kernel() { +void CodegenCVisitor::print_net_receive_buffering() { if (!net_receive_required() || info.artificial_cell) { return; } @@ -2677,12 +3337,11 @@ void CodegenCVisitor::print_net_receive_buffer_kernel() { printer->add_line("nrb->_displ_cnt = 0;"); printer->add_line("nrb->_cnt = 0;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } -void CodegenCVisitor::print_net_send_buffer_kernel() { +void CodegenCVisitor::print_net_send_buffering() { if (!net_send_buffer_required()) { return; } @@ -2706,8 +3365,7 @@ void CodegenCVisitor::print_net_send_buffer_kernel() { printer->add_line("nsb->_pnt_index[i] = point_index;"); printer->add_line("nsb->_nsb_t[i] = t;"); printer->add_line("nsb->_nsb_flag[i] = flag;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -2773,8 +3431,7 @@ void CodegenCVisitor::print_net_receive() { printer->add_line("nrb->_nrb_t[id] = nt->_t;"); printer->add_line("nrb->_nrb_flag[id] = flag;"); printer->add_line("nrb->_cnt++;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } printing_net_receive = false; codegen = false; @@ -2799,8 +3456,7 @@ void CodegenCVisitor::print_derivative_kernel_for_euler() { printer->add_line(instance); print_statement_block(node->get_statement_block().get(), false, false); printer->add_line("return 0;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -2846,10 +3502,8 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { auto argument = "{}, slist{}, derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, solve_block_name, suffix, list_num + 1, ext_args); printer->add_line("int reset = nrn_newton_thread(*newtonspace{}(thread), {});"_format(list_num, argument)); printer->add_line("return reset;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(3); - printer->add_newline(2); printer->start_block("int newton_{}_{}({}) "_format(node->get_node_name(), info.mod_suffix, external_method_parameters())); printer->add_line(instance); printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); @@ -2956,8 +3610,7 @@ void CodegenCVisitor::print_nrn_state() { print_channel_iteration_tiling_block_end(); print_kernel_data_present_annotation_block_end(); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -2980,8 +3633,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { printer->add_line("current += {};"_format(name)); } printer->add_line("return current;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } @@ -3040,7 +3692,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { auto lhs = "ion_di" + ion.name + "dv"; auto rhs = "(di{}-{})/0.001"_format(ion.name, get_variable_name(var)); if (info.point_process) { - auto area = get_variable_name(node_area); + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); rhs += "*1.e2/{}"_format(area); } auto statement = ShadowUseStatement{lhs, "+=", rhs}; @@ -3077,7 +3729,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { } if (info.point_process) { - auto area = get_variable_name(node_area); + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); printer->add_line("double mfactor = 1.e2/{};"_format(area)); printer->add_line("g = g*mfactor;"); printer->add_line("rhs = rhs*mfactor;"); @@ -3114,8 +3766,7 @@ void CodegenCVisitor::print_nrn_cur() { print_channel_iteration_tiling_block_end(); print_kernel_data_present_annotation_block_end(); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); codegen = false; } @@ -3125,26 +3776,26 @@ void CodegenCVisitor::print_nrn_cur() { /****************************************************************************************/ -void CodegenCVisitor::codegen_includes() { +void CodegenCVisitor::print_headers_include() { print_standard_includes(); print_backend_includes(); print_coreneuron_includes(); } -void CodegenCVisitor::codegen_namespace_begin() { +void CodegenCVisitor::print_namespace_begin() { print_namespace_start(); print_backend_namespace_start(); } -void CodegenCVisitor::codegen_namespace_end() { - print_backend_namespace_end(); - print_namespace_end(); +void CodegenCVisitor::print_namespace_end() { + print_backend_namespace_stop(); + print_namespace_stop(); } -void CodegenCVisitor::codegen_common_getters() { +void CodegenCVisitor::print_common_getters() { print_first_pointer_var_index_getter(); print_memory_layout_getter(); print_net_receive_arg_size_getter(); @@ -3155,71 +3806,68 @@ void CodegenCVisitor::codegen_common_getters() { } -void CodegenCVisitor::codegen_data_structures() { - print_mechanism_global_structure(); - print_mechanism_var_structure(); - print_ion_variables_structure(); +void CodegenCVisitor::print_data_structures() { + print_mechanism_global_var_structure(); + print_mechanism_range_var_structure(); + print_ion_var_structure(); } -void CodegenCVisitor::codegen_compute_functions() { +void CodegenCVisitor::print_compute_functions() { print_top_verbatim_blocks(); print_function_prototypes(); - for (const auto& procedure : info.procedures) { print_procedure(procedure); } - for (const auto& function : info.functions) { print_function(function); } - - print_net_init_kernel(); - print_net_send_buffer_kernel(); + print_net_init(); + print_net_send_buffering(); print_watch_activate(); print_watch_check(); print_net_receive(); - print_net_receive_buffer_kernel(); + print_net_receive_buffering(); print_nrn_init(); print_nrn_cur(); print_nrn_state(); } -void CodegenCVisitor::codegen_all() { +void CodegenCVisitor::print_codegen_routines() { codegen = true; print_backend_info(); - codegen_includes(); - codegen_namespace_begin(); - + print_headers_include(); + print_namespace_begin(); print_nmodl_constant(); - - print_mechanism_info_array(); - - codegen_data_structures(); - - // must be after codegen_data_structures + print_mechanism_info(); + print_data_structures(); print_global_variables_for_hoc(); - - codegen_common_getters(); - + print_common_getters(); print_thread_memory_callbacks(); print_memory_allocation_routine(); - print_global_variable_setup(); print_instance_variable_setup(); print_nrn_alloc(); - - codegen_compute_functions(); + print_compute_functions(); print_check_table_thread_function(); print_mechanism_register(); - - codegen_namespace_end(); + print_namespace_end(); } void CodegenCVisitor::visit_program(Program* node) { - CodegenBaseVisitor::visit_program(node); + program_symtab = node->get_symbol_table(); + + CodegenHelperVisitor v; + info = v.analyze(node); + info.mod_file = mod_filename; + + codegen_float_variables = get_float_variables(); + codegen_int_variables = get_int_variables(); + codegen_shadow_variables = get_shadow_variables(); + + update_index_semantics(); rename_function_arguments(); - codegen_all(); + print_codegen_routines(); } diff --git a/src/nmodl/codegen/c/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp similarity index 54% rename from src/nmodl/codegen/c/codegen_c_visitor.hpp rename to src/nmodl/codegen/codegen_c_visitor.hpp index 2631d45957..54cd58943d 100644 --- a/src/nmodl/codegen/c/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -2,11 +2,15 @@ #define NMODL_CODEGEN_C_VISITOR_HPP #include <algorithm> +#include <cmath> +#include <ctime> #include <string> +#include <utility> -#include "codegen/base/codegen_base_visitor.hpp" -#include "codegen/codegen_info.hpp" #include "fmt/format.h" + +#include "codegen/codegen_info.hpp" +#include "codegen/codegen_naming.hpp" #include "printer/code_printer.hpp" #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" @@ -16,54 +20,262 @@ using namespace fmt::literals; /** - * \class TableStatementVisitor - * \brief Helper visitor to return table statement in given node + * \enum BlockType + * \brief Helper to represent various block types + * */ -class TableStatementVisitor : public AstVisitor { - private: - /// vector containing all table statements - std::vector<ast::TableStatement*> statements; +enum class BlockType { + /// initial block + Initial, - public: - TableStatementVisitor() = default; + /// breakpoint block + Equation, - void visit_table_statement(ast::TableStatement* node) override { - statements.push_back(node); - } + /// ode_* routines block (not used) + Ode, - std::vector<ast::TableStatement*> get_statements() { - return statements; + /// derivative block + State, + + /// watch block + Watch +}; + + +/** + * \enum MemberType + * \brief Helper to represent various variables types + * + */ +enum class MemberType { + /// index / int variables + index, + + /// range / double variables + range, + + /// global variables + global, + + /// thread variables + thread +}; + + +/** + * \class IndexVariableInfo + * \brief Helper to represent information about index/int variables + * + */ +struct IndexVariableInfo { + /// symbol for the variable + std::shared_ptr<symtab::Symbol> symbol; + + /// if variable reside in vdata field of NrnThread + /// typically true for bbcore pointer + bool is_vdata = false; + + /// if this is pure index (e.g. style_ion) variables is directly + /// index and shouldn't be printed with data/vdata + bool is_index = false; + + /// if this is an integer (e.g. tqitem, point_process) variable which + /// is printed as array accesses + bool is_integer = false; + + /// if the variable is qualified as constant (this is property of IndexVariable) + bool is_constant = false; + + IndexVariableInfo(std::shared_ptr<symtab::Symbol> symbol, + bool is_vdata = false, + bool is_index = false, + bool is_integer = false) + : symbol(symbol), is_vdata(is_vdata), is_index(is_index), is_integer(is_integer) { } }; +/** + * \enum LayoutType + * \brief Represent memory layout to use for code generation + * + */ +enum class LayoutType { + /// array of structure + aos, + + /// structure of array + soa +}; + + +/** + * \class ShadowUseStatement + * \brief Represent ion write statement during code generation + * + * Ion update statement need use of shadow vectors for certain backends + * as atomics operations are not supported on cpu backend. + * + * \todo : if shadow_lhs is empty then we assume shadow statement not required + */ +struct ShadowUseStatement { + std::string lhs; + std::string op; + std::string rhs; +}; + + /** * \class CodegenCVisitor * \brief Visitor for printing c code compatible with legacy api * * \todo : - * - handle define i.e. macro statement printing - * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) + * - handle define statement (i.e. macro statement printing) + * - return statement in the verbatim block of inlined function not handled + * for example, see netstim.mod where we removed return from verbatim block */ -class CodegenCVisitor : public CodegenBaseVisitor { +class CodegenCVisitor : public AstVisitor { protected: using SymbolType = std::shared_ptr<symtab::Symbol>; - /// currently net_receive block being printed + /// name of mod file (without .mod suffix) + std::string mod_filename; + + /// flag to indicate if visitor should print the visited nodes + bool codegen = false; + + /// variable name should be converted to instance name (but not for function arguments) + bool enable_variable_name_lookup = true; + + /// symbol table for the program + symtab::SymbolTable* program_symtab = nullptr; + + /// all float variables for the model + std::vector<SymbolType> codegen_float_variables; + + /// all int variables for the model + std::vector<IndexVariableInfo> codegen_int_variables; + + /// all global variables for the model + /// todo : this has become different than CodegenInfo + std::vector<SymbolType> codegen_global_variables; + + /// all ion variables that could be possibly written + std::vector<SymbolType> codegen_shadow_variables; + + /// true if currently net_receive block being printed bool printing_net_receive = false; - /// currently printing verbatim blocks in top level block + /// true if currently printing top level verbatim blocks bool printing_top_verbatim_blocks = false; - /// internal method call was encountered while processing vebatim block + /// true if internal method call was encountered while processing verbatim block bool internal_method_call_encountered = false; /// index of watch statement being printed int current_watch_statement = 0; - /// number of threads to allocate - int num_thread_objects() { - return info.vectorize ? (info.thread_data_index + 1) : 0; + /// data type of floating point variables + std::string float_type = codegen::naming::DEFAULT_FLOAT_TYPE; + + /// memory layout for code generation + LayoutType layout; + + /// all ast information for code generation + codegen::CodegenInfo info; + + /// code printer object + std::unique_ptr<CodePrinter> printer; + + /// list of shadow statements in the current block + std::vector<ShadowUseStatement> shadow_statements; + + + /// nmodl language version + std::string nmodl_version() { + return codegen::naming::NMODL_VERSION; + } + + + std::string add_escape_quote(const std::string& text) { + return "\"" + text + "\""; + } + + + /// operator for rhs vector update (matrix update) + std::string operator_for_rhs() { + return info.electorde_current ? "+=" : "-="; + } + + + /// operator for diagonal vector update (matrix update) + std::string operator_for_d() { + return info.electorde_current ? "-=" : "+="; + } + + + /// data type for the local variables + std::string local_var_type() { + return codegen::naming::DEFAULT_LOCAL_VAR_TYPE; + } + + + /// default data type for floating point elements + std::string default_float_data_type() { + return codegen::naming::DEFAULT_FLOAT_TYPE; + } + + + /// data type for floating point elements specified on command line + std::string float_data_type() { + return float_type; + } + + + /// default data type for integer (offset) elements + std::string default_int_data_type() { + return codegen::naming::DEFAULT_INTEGER_TYPE; + } + + + /// function name for net send + bool is_net_send(const std::string& name) { + return name == codegen::naming::NET_SEND_METHOD; + } + + /// function name for net move + bool is_net_move(const std::string& name) { + return name == codegen::naming::NET_MOVE_METHOD; + } + + /// function name for net event + bool is_net_event(const std::string& name) { + return name == codegen::naming::NET_EVENT_METHOD; + } + + + /// name of structure that wraps range variables + std::string instance_struct() { + return "{}_Instance"_format(info.mod_suffix); + } + + + /// name of structure that wraps range variables + std::string global_struct() { + return "{}_Store"_format(info.mod_suffix); + } + + + /// name of function or procedure + std::string method_name(const std::string& name) { + auto suffix = info.mod_suffix; + return name + "_" + suffix; + } + + + /// name for shadow variable + std::string shadow_varname(const std::string& name) { + return "shadow_" + name; } @@ -73,18 +285,88 @@ class CodegenCVisitor : public CodegenBaseVisitor { } - /// check if structure for ion variables required - bool ion_variable_struct_required(); + /// check if given variable is state variable + bool state_variable(std::string name); + + + /// check if net receive/send buffering kernels required + bool net_receive_buffering_required(); + + + /// check if nrn_state function is required + bool nrn_state_required(); + + + /// check if nrn_cur function is required + bool nrn_cur_required(); + + + /// check if net_receive function is required + bool net_receive_required(); + + + /// check if net_send_buffer is required + bool net_send_buffer_required(); + + + /// check if setup_range_variable function is required + bool range_variable_setup_required(); + + + /// check if net_receive node exist + bool net_receive_exist(); + + + /// check if breakpoint node exist + bool breakpoint_exist(); + + + /// if method is defined the mod file + bool defined_method(const std::string& name); + + + /// check if give statement should be skipped during code generation + bool statement_to_skip(ast::Statement* node); + + + /// check if semicolon required at the end of given statement + bool need_semicolon(ast::Statement* node); + + + /// number of threads to allocate + int num_thread_objects() { + return info.vectorize ? (info.thread_data_index + 1) : 0; + } + + + /// num of float variables in the model + int float_variables_size(); + + + /// num of integer variables in the model + int int_variables_size(); + + + /// for given float variable name, index position in the data array + int position_of_float_var(const std::string& name); + + + /// for given int variable name, index position in the data array + int position_of_int_var(const std::string& name); /// when ion variable copies optimized then change name (e.g. ena to ion_ena) - std::string update_if_ion_variable_name(std::string name); + std::string update_if_ion_variable_name(const std::string& name); /// name of the code generation backend virtual std::string backend_name(); + /// convert given double value to string (for printing) + std::string double_to_string(double value); + + /// get variable name for float variable std::string float_variable_name(SymbolType& symbol, bool use_instance); @@ -104,14 +386,52 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// get variable name for given name. if use_instance is true then "Instance" - /// structure is used while returning name - std::string get_variable_name(std::string name, bool use_instance = true) override; + /// structure is used while returning name (implemented by derived classes) + virtual std::string get_variable_name(const std::string& name, bool use_instance = true); /// name of the current variable used in the breakpoint bock std::string breakpoint_current(std::string current); + /// populate all index semantics needed for registration with coreneuron + void update_index_semantics(); + + + /// return all float variables required during code generation + std::vector<SymbolType> get_float_variables(); + + + /// return all int variables required during code generation + std::vector<IndexVariableInfo> get_int_variables(); + + + /// return all ion write variables that require shadow vectors during code generation + std::vector<SymbolType> get_shadow_variables(); + + + /// print vector elements (all types) + template <typename T> + void print_vector_elements(const std::vector<T>& elements, + const std::string& separator, + const std::string& prefix = ""); + + + /// check if function or procedure has argument with same name + template <typename T> + bool has_argument_with_name(const T& node, std::string name); + + + /// any statement block in nmodl with option to (not) print braces + void print_statement_block(ast::StatementBlock* node, + bool open_brace = true, + bool close_brace = true); + + + /// check if structure for ion variables required + bool ion_variable_struct_required(); + + /// process verbatim block for possible variable renaming std::string process_verbatim_text(std::string text); @@ -129,7 +449,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { //// minimal statements for reading ion values - std::vector<std::string> ion_read_statements_optimal(BlockType type); + std::vector<std::string> ion_read_statements_optimized(BlockType type); //// statements for writing ion values @@ -173,12 +493,10 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// arguments for "_threadargs_" macro in neuron implementation std::string nrn_thread_arguments(); + /// arguments for "_threadargs_" macro in neuron implementation std::string nrn_thread_internal_arguments(); - /// list of shadow statements in the current block - std::vector<ShadowUseStatement> shadow_statements; - /// replace commonly used verbatim variables std::string replace_if_verbatim_variable(std::string name); @@ -191,6 +509,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// restrict keyword virtual std::string k_restrict(); + /// const keyword virtual std::string k_const(); @@ -200,7 +519,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// end of coreneuron namespace - void print_namespace_end(); + void print_namespace_stop(); /// start of backend namespace @@ -208,7 +527,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// end of backend namespace - virtual void print_backend_namespace_end(); + virtual void print_backend_namespace_stop(); /// nmodl constants @@ -223,9 +542,15 @@ class CodegenCVisitor : public CodegenBaseVisitor { virtual void print_memory_allocation_routine(); - /// common includes : standard c/c++, coreneuron and backend specific + /// standard c/c++ includes void print_standard_includes(); + + + /// includes from coreneuron void print_coreneuron_includes(); + + + /// backend specific includes virtual void print_backend_includes(); @@ -254,19 +579,19 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// char array that has mechanism information (to be registered with coreneuron) - void print_mechanism_info_array(); + void print_mechanism_info(); /// structure that wraps all global variables in the mod file - void print_mechanism_global_structure(); + void print_mechanism_global_var_structure(); /// structure that wraps all range and int variables required for mod file - void print_mechanism_var_structure(); + void print_mechanism_range_var_structure(); /// structure of ion variables used for local copies - void print_ion_variables_structure(); + void print_ion_var_structure(); /// return floating point type for given range variable symbol @@ -425,15 +750,9 @@ class CodegenCVisitor : public CodegenBaseVisitor { void print_top_verbatim_blocks(); - /// any statement block in nmodl with option to (not) print braces - void print_statement_block(ast::StatementBlock* node, - bool open_brace = true, - bool close_brace = true); - - /// prototype declarations of functions and procedures template <typename T> - void print_function_declaration(T& node, std::string name); + void print_function_declaration(const T& node, const std::string& name); /// initial block @@ -441,7 +760,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// initial block in the net receive block - void print_net_init_kernel(); + void print_net_init(); /// common code section for net receive related methods @@ -449,7 +768,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// kernel for buffering net_send events - void print_net_send_buffer_kernel(); + void print_net_send_buffering(); /// send event move block used in net receive as well as watch @@ -457,7 +776,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// kernel for buffering net_receive events - void print_net_receive_buffer_kernel(); + void print_net_receive_buffering(); /// net_receive function definition @@ -550,55 +869,121 @@ class CodegenCVisitor : public CodegenBaseVisitor { /// all includes - void codegen_includes(); + void print_headers_include(); /// start of namespaces - void codegen_namespace_begin(); + void print_namespace_begin(); /// end of namespaces - void codegen_namespace_end(); + void print_namespace_end(); /// common getter - void codegen_common_getters(); + void print_common_getters(); /// all classes - void codegen_data_structures(); + void print_data_structures(); /// all compute functions for every backend - virtual void codegen_compute_functions(); + virtual void print_compute_functions(); /// entry point to code generation - virtual void codegen_all(); + virtual void print_codegen_routines(); + public: - CodegenCVisitor(std::string mod_file, + CodegenCVisitor(std::string mod_filename, std::string output_dir, - bool aos, + LayoutType layout, std::string float_type, std::string extension = ".cpp") - : CodegenBaseVisitor(mod_file, output_dir, aos, float_type, extension) { + : printer(new CodePrinter(output_dir + "/" + mod_filename + extension)), + mod_filename(mod_filename), + layout(layout), + float_type(float_type) { } - CodegenCVisitor(std::string mod_file, + + CodegenCVisitor(std::string mod_filename, std::stringstream& stream, - bool aos, + LayoutType layout, std::string float_type) - : CodegenBaseVisitor(mod_file, stream, aos, float_type) { + : printer(new CodePrinter(stream)), + mod_filename(mod_filename), + layout(layout), + float_type(float_type) { } - virtual void visit_watch_statement(ast::WatchStatement* node) override; + virtual void visit_binary_expression(ast::BinaryExpression* node) override; + virtual void visit_binary_operator(ast::BinaryOperator* node) override; + virtual void visit_boolean(ast::Boolean* node) override; + virtual void visit_double(ast::Double* node) override; + virtual void visit_else_if_statement(ast::ElseIfStatement* node) override; + virtual void visit_else_statement(ast::ElseStatement* node) override; + virtual void visit_float(ast::Float* node) override; + virtual void visit_from_statement(ast::FromStatement* node) override; virtual void visit_function_call(ast::FunctionCall* node) override; - virtual void visit_verbatim(ast::Verbatim* node) override; + virtual void visit_if_statement(ast::IfStatement* node) override; + virtual void visit_indexed_name(ast::IndexedName* node) override; + virtual void visit_integer(ast::Integer* node) override; + virtual void visit_local_list_statement(ast::LocalListStatement* node) override; + virtual void visit_name(ast::Name* node) override; + virtual void visit_paren_expression(ast::ParenExpression* node) override; + virtual void visit_prime_name(ast::PrimeName* node) override; virtual void visit_program(ast::Program* node) override; + virtual void visit_statement_block(ast::StatementBlock* node) override; + virtual void visit_string(ast::String* node) override; + virtual void visit_unary_operator(ast::UnaryOperator* node) override; + virtual void visit_unit(ast::Unit* node) override; + virtual void visit_var_name(ast::VarName* node) override; + virtual void visit_verbatim(ast::Verbatim* node) override; + virtual void visit_watch_statement(ast::WatchStatement* node) override; + virtual void visit_while_statement(ast::WhileStatement* node) override; }; +/** + * Print elements of vector with given separator and prefix string + */ +template <typename T> +void CodegenCVisitor::print_vector_elements(const std::vector<T>& elements, + const std::string& separator, + const std::string& prefix) { + for (auto iter = elements.begin(); iter != elements.end(); iter++) { + printer->add_text(prefix); + (*iter)->accept(this); + if (!separator.empty() && !is_last(iter, elements)) { + printer->add_text(separator); + } + } +} + + +/** + * Check if function or procedure node has parameter with given name + * + * @tparam T Node type (either procedure or function) + * @param node AST node (either procedure or function) + * @param name Name of parameter + * @return True if argument with name exist + */ +template <typename T> +bool has_parameter_of_name(const T& node, const std::string& name) { + auto parameters = node->get_parameters(); + for (const auto& parameter : parameters) { + if (parameter->get_node_name() == name) { + return true; + } + } + return false; +} + + /** * Print prototype declaration for function and procedures * @@ -608,7 +993,7 @@ class CodegenCVisitor : public CodegenBaseVisitor { * different in case of table statement. */ template <typename T> -void CodegenCVisitor::print_function_declaration(T& node, std::string name) { +void CodegenCVisitor::print_function_declaration(const T& node, const std::string& name) { enable_variable_name_lookup = false; /// internal and user provided arguments diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp similarity index 70% rename from src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp rename to src/nmodl/codegen/codegen_cuda_visitor.cpp index 14cda8f4bb..dbf0870e48 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -1,6 +1,6 @@ #include <fmt/format.h> -#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" +#include "codegen/codegen_cuda_visitor.hpp" #include "symtab/symbol_table.hpp" #include "utils/string_utils.hpp" @@ -19,7 +19,7 @@ using namespace syminfo; * backend can mark the parameter as constant even if they have * write count > 0 (typically due to initial block). */ -bool CodegenCCudaVisitor::is_constant_variable(std::string name) { +bool CodegenCudaVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { @@ -34,7 +34,7 @@ bool CodegenCCudaVisitor::is_constant_variable(std::string name) { } -std::string CodegenCCudaVisitor::compute_method_name(BlockType type) { +std::string CodegenCudaVisitor::compute_method_name(BlockType type) { if (type == BlockType::Initial) { return method_name("nrn_init"); } @@ -48,9 +48,9 @@ std::string CodegenCCudaVisitor::compute_method_name(BlockType type) { } -void CodegenCCudaVisitor::print_atomic_op(const std::string& lhs, - const std::string& op, - const std::string& rhs) { +void CodegenCudaVisitor::print_atomic_op(const std::string& lhs, + const std::string& op, + const std::string& rhs) { std::string function; if (op == "+") { function = "atomicAdd"; @@ -63,27 +63,27 @@ void CodegenCCudaVisitor::print_atomic_op(const std::string& lhs, } -void CodegenCCudaVisitor::print_backend_includes() { +void CodegenCudaVisitor::print_backend_includes() { printer->add_line("#include <cuda.h>"); } -std::string CodegenCCudaVisitor::backend_name() { +std::string CodegenCudaVisitor::backend_name() { return "C-CUDA (api-compatibility)"; } -void CodegenCCudaVisitor::print_global_method_annotation() { +void CodegenCudaVisitor::print_global_method_annotation() { printer->add_line("__global__"); } -void CodegenCCudaVisitor::print_device_method_annotation() { +void CodegenCudaVisitor::print_device_method_annotation() { printer->add_line("__device__"); } -void CodegenCCudaVisitor::print_nrn_cur_matrix_shadow_update() { +void CodegenCudaVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); stringutils::remove_character(rhs_op, '='); @@ -99,46 +99,46 @@ void CodegenCCudaVisitor::print_nrn_cur_matrix_shadow_update() { * For GPU backend its thread id less than total channel instances. Below we * assume we launch 1-d grid. */ -void CodegenCCudaVisitor::print_channel_iteration_block_begin() { +void CodegenCudaVisitor::print_channel_iteration_block_begin() { printer->add_line("int id = blockIdx.x * blockDim.x + threadIdx.x;"); printer->start_block("if (id < end) "); } -void CodegenCCudaVisitor::print_channel_iteration_block_end() { +void CodegenCudaVisitor::print_channel_iteration_block_end() { printer->end_block(); printer->add_newline(); } -void CodegenCCudaVisitor::print_nrn_cur_matrix_shadow_reduction() { +void CodegenCudaVisitor::print_nrn_cur_matrix_shadow_reduction() { // do nothing } -void CodegenCCudaVisitor::print_rhs_d_shadow_variables() { +void CodegenCudaVisitor::print_rhs_d_shadow_variables() { // do nothing } -bool CodegenCCudaVisitor::nrn_cur_reduction_loop_required() { +bool CodegenCudaVisitor::nrn_cur_reduction_loop_required() { return false; } -void CodegenCCudaVisitor::print_backend_namespace_start() { +void CodegenCudaVisitor::print_backend_namespace_start() { printer->add_newline(1); printer->start_block("namespace cuda"); } -void CodegenCCudaVisitor::print_backend_namespace_end() { +void CodegenCudaVisitor::print_backend_namespace_stop() { printer->end_block(); printer->add_newline(); } -void CodegenCCudaVisitor::codegen_compute_functions() { +void CodegenCudaVisitor::print_compute_functions() { print_top_verbatim_blocks(); print_function_prototypes(); @@ -150,15 +150,15 @@ void CodegenCCudaVisitor::codegen_compute_functions() { print_function(function); } - print_net_send_buffer_kernel(); + print_net_send_buffering(); print_net_receive(); - print_net_receive_buffer_kernel(); + print_net_receive_buffering(); print_nrn_cur(); print_nrn_state(); } -void CodegenCCudaVisitor::print_wrapper_routine(std::string wraper_function, BlockType type) { +void CodegenCudaVisitor::print_wrapper_routine(std::string wraper_function, BlockType type) { auto args = "NrnThread* nt, Memb_list* ml, int type"; wraper_function = method_name(wraper_function); auto compute_function = compute_method_name(type); @@ -175,24 +175,24 @@ void CodegenCCudaVisitor::print_wrapper_routine(std::string wraper_function, Blo } -void CodegenCCudaVisitor::codegen_wrapper_routines() { +void CodegenCudaVisitor::codegen_wrapper_routines() { print_wrapper_routine("nrn_cur", BlockType::Equation); print_wrapper_routine("nrn_sate", BlockType::State); } -void CodegenCCudaVisitor::codegen_all() { +void CodegenCudaVisitor::print_codegen_routines() { codegen = true; print_backend_info(); - codegen_includes(); - codegen_namespace_begin(); + print_headers_include(); + print_namespace_begin(); - codegen_data_structures(); - codegen_common_getters(); + print_data_structures(); + print_common_getters(); - codegen_compute_functions(); + print_compute_functions(); codegen_wrapper_routines(); - codegen_namespace_end(); + print_namespace_end(); } diff --git a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp similarity index 69% rename from src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp rename to src/nmodl/codegen/codegen_cuda_visitor.hpp index a5e8e39276..15e30ab076 100644 --- a/src/nmodl/codegen/c-cuda/codegen_c_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -1,18 +1,14 @@ #ifndef NMODL_CODEGEN_C_CUDA_VISITOR_HPP #define NMODL_CODEGEN_C_CUDA_VISITOR_HPP -#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/codegen_c_visitor.hpp" /** - * \class CodegenCCudaVisitor + * \class CodegenCudaVisitor * \brief Visitor for printing CUDA backend - * - * \todo : - * - handle define i.e. macro statement printing - * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) */ -class CodegenCCudaVisitor : public CodegenCVisitor { +class CodegenCudaVisitor : public CodegenCVisitor { void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); protected: @@ -58,7 +54,7 @@ class CodegenCCudaVisitor : public CodegenCVisitor { /// end of backend namespace - void print_backend_namespace_end() override; + void print_backend_namespace_stop() override; /// backend specific global method annotation @@ -70,7 +66,7 @@ class CodegenCCudaVisitor : public CodegenCVisitor { /// all compute functions for every backend - void codegen_compute_functions() override; + void print_compute_functions() override; /// print wrapper function that calls cuda kernel @@ -82,21 +78,21 @@ class CodegenCCudaVisitor : public CodegenCVisitor { /// entry point to code generation - void codegen_all() override; + void print_codegen_routines() override; public: - CodegenCCudaVisitor(std::string mod_file, - std::string output_dir, - bool aos, - std::string float_type) - : CodegenCVisitor(mod_file, output_dir, aos, float_type, ".cu") { + CodegenCudaVisitor(std::string mod_file, + std::string output_dir, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".cu") { } - CodegenCCudaVisitor(std::string mod_file, - std::stringstream& stream, - bool aos, - std::string float_type) - : CodegenCVisitor(mod_file, stream, aos, float_type) { + CodegenCudaVisitor(std::string mod_file, + std::stringstream& stream, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, stream, layout, float_type) { } }; diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp similarity index 96% rename from src/nmodl/codegen/base/codegen_helper_visitor.cpp rename to src/nmodl/codegen/codegen_helper_visitor.cpp index 307812791a..9e2739c596 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -1,7 +1,8 @@ #include <algorithm> #include <cmath> -#include "codegen/base/codegen_helper_visitor.hpp" +#include "codegen/codegen_helper_visitor.hpp" +#include "codegen/codegen_naming.hpp" #include "visitors/rename_visitor.hpp" #include <fmt/format.h> @@ -174,7 +175,7 @@ void CodegenHelperVisitor::find_non_range_variables() { for (auto& var : vars) { // some variables like area and diam are declared in parameter // block but they are not global - if (var->get_name() == diam_variable || var->get_name() == area_variable || + if (var->get_name() == naming::DIAM_VARIABLE || var->get_name() == naming::AREA_VARIABLE || var->has_properties(NmodlType::extern_neuron_variable)) { continue; } @@ -254,10 +255,10 @@ void CodegenHelperVisitor::find_non_range_variables() { | NmodlType::param_assign; vars = psymtab->get_variables_with_properties(properties); for (auto& var : vars) { - if (var->get_name() == area_variable) { + if (var->get_name() == naming::AREA_VARIABLE) { info.area_used = true; } - if (var->get_name() == diam_variable) { + if (var->get_name() == naming::DIAM_VARIABLE) { info.diam_used = true; } } @@ -404,10 +405,10 @@ void CodegenHelperVisitor::find_table_variables() { void CodegenHelperVisitor::visit_suffix(Suffix* node) { auto type = node->get_type()->get_node_name(); - if (type == point_process) { + if (type == naming::POINT_PROCESS) { info.point_process = true; } - if (type == artificial_cell) { + if (type == naming::ARTIFICIAL_CELL) { info.artificial_cell = true; info.point_process = true; } @@ -477,10 +478,10 @@ void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { auto name = node->get_node_name(); - if (name == net_send_method) { + if (name == naming::NET_SEND_METHOD) { info.net_send_used = true; } - if (name == net_event_method) { + if (name == naming::NET_EVENT_METHOD) { info.net_event_used = true; } } @@ -503,11 +504,11 @@ void CodegenHelperVisitor::visit_solve_block(SolveBlock* node) { info.solve_block_name = node->get_block_name()->get_node_name(); if (node->get_method()) { info.solve_method = node->get_method()->get_node_name(); - if (info.solve_method == derivimplicit_method) { + if (info.solve_method == naming::DERIVIMPLICIT_METHOD) { info.derivimplicit_used = true; - } else if (info.solve_method == euler_method) { + } else if (info.solve_method == naming::EULER_METHOD) { info.euler_used = true; - } else if (info.solve_method == cnexp_method) { + } else if (info.solve_method == naming::CNEXP_METHOD) { info.cnexp_used = true; } } @@ -606,7 +607,6 @@ void CodegenHelperVisitor::find_solve_node() { void CodegenHelperVisitor::visit_program(Program* node) { psymtab = node->get_symbol_table(); - model_symtab = node->get_model_symbol_table(); auto blocks = node->get_blocks(); for (auto& block : blocks) { info.top_blocks.push_back(block.get()); @@ -623,7 +623,7 @@ void CodegenHelperVisitor::visit_program(Program* node) { } -codegen::CodegenInfo CodegenHelperVisitor::get_code_info(ast::Program* node) { +codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program* node) { node->accept(this); return info; } diff --git a/src/nmodl/codegen/base/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp similarity index 79% rename from src/nmodl/codegen/base/codegen_helper_visitor.hpp rename to src/nmodl/codegen/codegen_helper_visitor.hpp index 5224daa0b8..da94181c25 100644 --- a/src/nmodl/codegen/base/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -27,6 +27,8 @@ * Need to check what is correct value. */ class CodegenHelperVisitor : public AstVisitor { + using SymbolType = std::shared_ptr<symtab::Symbol>; + /// holds all codegen related information codegen::CodegenInfo info; @@ -45,27 +47,8 @@ class CodegenHelperVisitor : public AstVisitor { /// symbol table for the program symtab::SymbolTable* psymtab = nullptr; - /// symbol table for the entire model - symtab::ModelSymbolTable* model_symtab = nullptr; - - /// if we are visiting lhs of assignment statement - bool visiting_lhs_assignment = false; - - using SymbolType = std::shared_ptr<symtab::Symbol>; - /// lhs of assignment in derivative block - std::shared_ptr<ast::Expression> assign_lhs = nullptr; - - /// name of the derivimplicit, euler and cnexp methods - const std::string derivimplicit_method = "derivimplicit"; - const std::string euler_method = "euler"; - const std::string cnexp_method = "cnexp"; - const std::string net_send_method = "net_send"; - const std::string net_event_method = "net_event"; - const std::string artificial_cell = "ARTIFICIAL_CELL"; - const std::string point_process = "POINT_PROCESS"; - const std::string diam_variable = "diam"; - const std::string area_variable = "area"; + std::shared_ptr<ast::Expression> assign_lhs; void find_solve_node(); void find_ion_variables(); @@ -76,7 +59,9 @@ class CodegenHelperVisitor : public AstVisitor { public: CodegenHelperVisitor() = default; - codegen::CodegenInfo get_code_info(ast::Program* node); + + /// run visitor and return information for code generation + codegen::CodegenInfo analyze(ast::Program* node); virtual void visit_elctrode_current(ast::ElctrodeCurrent* node) override; virtual void visit_suffix(ast::Suffix* node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 7ba66340f7..5608a23fa2 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -30,10 +30,10 @@ namespace codegen { /// name of the ion std::string name; - /// variables that are being read + /// ion variables that are being read std::vector<std::string> reads; - /// variables that are being written + /// ion variables that are being written std::vector<std::string> writes; /// if style semantic needed @@ -282,6 +282,10 @@ namespace codegen { /// index/offset for first pointer variable if exist int first_pointer_var_index = -1; + /// tqitem index in integer variables + /// note that if tqitem doesn't exist then the default value should be 0 + int tqitem_index = 0; + /// global variables std::vector<SymbolType> global_variables; @@ -332,10 +336,6 @@ namespace codegen { /// all watch statements std::vector<ast::WatchStatement*> watch_statements; - /// tqitem index in int variables - /// note that if tqitem doesn't exist then default value should be 0 - int tqitem_index = 0; - /// if any ion has write variable bool ion_has_write_variable(); diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp new file mode 100644 index 0000000000..ab54071833 --- /dev/null +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -0,0 +1,149 @@ +#ifndef CODEGEN_NAMING_HPP +#define CODEGEN_NAMING_HPP + +#include <string> + +namespace codegen { + + namespace naming { + + /// nmodl language version + /// @todo : should be moved from codegen to global scope + const std::string NMODL_VERSION("6.2.0"); + + /// derivimplicit method in nmodl + const std::string DERIVIMPLICIT_METHOD("derivimplicit"); + + /// euler method in nmodl + const std::string EULER_METHOD("euler"); + + /// cnexp method in nmodl + const std::string CNEXP_METHOD("cnexp"); + + /// net_event function call in nmodl + const std::string NET_EVENT_METHOD("net_event"); + + /// net_move function call in nmodl + const std::string NET_MOVE_METHOD("net_move"); + + /// net_send function call in nmodl + const std::string NET_SEND_METHOD("net_send"); + + /// artificial cell keyword in nmodl + const std::string ARTIFICIAL_CELL("ARTIFICIAL_CELL"); + + /// point process keyword in nmodl + const std::string POINT_PROCESS("POINT_PROCESS"); + + /// inbuilt neuron variable for diameter of the compartment + const std::string DIAM_VARIABLE("diam"); + + /// inbuilt neuron variable for area of the compartment + const std::string NODE_AREA_VARIABLE("node_area"); + + /// similar to node_area but user can explicitly declare it as area + const std::string AREA_VARIABLE("area"); + + /// inbuilt neuron variable for point process + const std::string POINT_PROCESS_VARIABLE("point_process"); + + /// inbuilt neuron variable for tqitem process + const std::string TQITEM_VARIABLE("tqitem"); + + /// range variable for conductance + const std::string CONDUCTANCE_VARIABLE("_g"); + + /// global variable to indicate if table is used + const std::string USE_TABLE_VARIABLE("usetable"); + + /// range variable when conductance is not used (for vectorized model) + const std::string CONDUCTANCE_UNUSED_VARIABLE("g_unused"); + + /// range variable for voltage when unused (for vectorized model) + const std::string VOLTAGE_UNUSED_VARIABLE("v_unused"); + + /// variable t indicating last execution time of net receive block + const std::string T_SAVE_VARIABLE("tsave"); + + /// shadow rhs variable in neuron thread structure + const std::string NTHREAD_RHS_SHADOW("_shadow_rhs"); + + /// shadow d variable in neuron thread structure + const std::string NTHREAD_D_SHADOW("_shadow_d"); + + /// t variable in neuron thread structure + const std::string NTHREAD_T_VARIABLE("t"); + + /// dt variable in neuron thread structure + const std::string NTHREAD_DT_VARIABLE("dt"); + + /// default float variable type + const std::string DEFAULT_FLOAT_TYPE("double"); + + /// default local variable type + const std::string DEFAULT_LOCAL_VAR_TYPE("double"); + + /// default integer variable type + const std::string DEFAULT_INTEGER_TYPE("int"); + + /// semantic type for area variable + const std::string AREA_SEMANTIC("area"); + + /// semantic type for point process variable + const std::string POINT_PROCESS_SEMANTIC("pntproc"); + + /// semantic type for pointer variable + const std::string POINTER_SEMANTIC("pointer"); + + /// semantic type for core pointer variable + const std::string CORE_POINTER_SEMANTIC("bbcorepointer"); + + /// semantic type for net send call + const std::string NET_SEND_SEMANTIC("netsend"); + + /// semantic type for watch statement + const std::string WATCH_SEMANTIC("watch"); + + /// semantic type for for_netcon statement + const std::string FOR_NETCON_SEMANTIC("fornetcon"); + + /// nrn_init method in generated code + const std::string NRN_INIT_METHOD("nrn_init"); + + /// nrn_alloc method in generated code + const std::string NRN_ALLOC_METHOD("nrn_alloc"); + + /// nrn_state method in generated code + const std::string NRN_STATE_METHOD("nrn_state"); + + /// nrn_cur method in generated code + const std::string NRN_CUR_METHOD("nrn_cur"); + + /// nrn_watch_check method in generated c file + const std::string NRN_WATCH_CHECK_METHOD("nrn_watch_check"); + + /// verbatim name of the variable for nrn thread arguments + const std::string THREAD_ARGS("_threadargs_"); + + /// verbatim name of the variable for nrn thread arguments in prototype + const std::string THREAD_ARGS_PROTO("_threadargsproto_"); + + /// commonly used variables in verbatim block and how they + /// should be mapped to new code generation backends + // clang-format off + const std::map<std::string, std::string> VERBATIM_VARIABLES_MAPPING{ + {"_nt", "nt"}, + {"_p", "data"}, + {"_ppvar", "indexes"}, + {"_thread", "thread"}, + {"_iml", "id"}, + {"_cntml_padded", "pnodecount"}, + {"_cntml", "nodecount"}, + {"_tqitem", "tqitem"}}; + // clang-format on + + } // namespace naming + +}; // namespace codegen + +#endif diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp similarity index 73% rename from src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp rename to src/nmodl/codegen/codegen_omp_visitor.cpp index c18e9f5302..fce6d15bab 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -1,4 +1,4 @@ -#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" +#include "codegen/codegen_omp_visitor.hpp" using namespace symtab; @@ -11,7 +11,7 @@ using SymbolType = std::shared_ptr<symtab::Symbol>; /****************************************************************************************/ -void CodegenCOmpVisitor::print_channel_iteration_task_begin(BlockType type) { +void CodegenOmpVisitor::print_channel_iteration_task_begin(BlockType type) { std::string vars; if (type == BlockType::Equation) { vars = "start, end, node_index, indexes, voltage, vec_rhs, vec_d, inst, thread, nt"; @@ -24,7 +24,7 @@ void CodegenCOmpVisitor::print_channel_iteration_task_begin(BlockType type) { } -void CodegenCOmpVisitor::print_channel_iteration_task_end() { +void CodegenOmpVisitor::print_channel_iteration_task_end() { printer->decrease_indent(); printer->add_line("}"); } @@ -33,7 +33,7 @@ void CodegenCOmpVisitor::print_channel_iteration_task_end() { /* * Depending on the backend, print loop for tiling channel iterations */ -void CodegenCOmpVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { +void CodegenOmpVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { printer->add_line("const int TILE = 3;"); printer->start_block("for (int block = 0; block < nodecount;) "); printer->add_line("int start = block;"); @@ -46,7 +46,7 @@ void CodegenCOmpVisitor::print_channel_iteration_tiling_block_begin(BlockType ty /** * End of tiled channel iteration block */ -void CodegenCOmpVisitor::print_channel_iteration_tiling_block_end() { +void CodegenOmpVisitor::print_channel_iteration_tiling_block_end() { print_channel_iteration_task_end(); printer->end_block(); printer->add_newline(); @@ -64,31 +64,31 @@ void CodegenCOmpVisitor::print_channel_iteration_tiling_block_end() { * for(int id=0; id<nodecount; id++) { * */ -void CodegenCOmpVisitor::print_channel_iteration_block_parallel_hint() { +void CodegenOmpVisitor::print_channel_iteration_block_parallel_hint() { printer->add_line("#pragma omp simd"); } -void CodegenCOmpVisitor::print_atomic_reduction_pragma() { +void CodegenOmpVisitor::print_atomic_reduction_pragma() { printer->add_line("#pragma omp atomic update"); } -void CodegenCOmpVisitor::print_backend_includes() { +void CodegenOmpVisitor::print_backend_includes() { printer->add_line("#include <omp.h>"); } -std::string CodegenCOmpVisitor::backend_name() { +std::string CodegenOmpVisitor::backend_name() { return "C-OpenMP (api-compatibility)"; } -bool CodegenCOmpVisitor::channel_task_dependency_enabled() { +bool CodegenOmpVisitor::channel_task_dependency_enabled() { return true; } -bool CodegenCOmpVisitor::block_require_shadow_update(BlockType type) { +bool CodegenOmpVisitor::block_require_shadow_update(BlockType type) { return !(!channel_task_dependency_enabled() || type == BlockType::Initial); } diff --git a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp similarity index 64% rename from src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp rename to src/nmodl/codegen/codegen_omp_visitor.hpp index b21eb3cf2c..b18de34a13 100644 --- a/src/nmodl/codegen/c-openmp/codegen_c_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -1,18 +1,14 @@ #ifndef NMODL_CODEGEN_C_OMP_VISITOR_HPP #define NMODL_CODEGEN_C_OMP_VISITOR_HPP -#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/codegen_c_visitor.hpp" /** - * \class CodegenCOmpVisitor + * \class CodegenOmpVisitor * \brief Visitor for printing c code with OpenMP backend - * - * \todo : - * - handle define i.e. macro statement printing - * - return statement in the verbatim block of inline function not handled (e.g. netstim.mod) */ -class CodegenCOmpVisitor : public CodegenCVisitor { +class CodegenOmpVisitor : public CodegenCVisitor { protected: /// name of the code generation backend std::string backend_name() override; @@ -55,18 +51,18 @@ class CodegenCOmpVisitor : public CodegenCVisitor { public: - CodegenCOmpVisitor(std::string mod_file, - std::string output_dir, - bool aos, - std::string float_type) - : CodegenCVisitor(mod_file, output_dir, aos, float_type) { + CodegenOmpVisitor(std::string mod_file, + std::string output_dir, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, output_dir, layout, float_type) { } - CodegenCOmpVisitor(std::string mod_file, - std::stringstream& stream, - bool aos, - std::string float_type) - : CodegenCVisitor(mod_file, stream, aos, float_type) { + CodegenOmpVisitor(std::string mod_file, + std::stringstream& stream, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, stream, layout, float_type) { } }; diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index 4f61ae8150..d8f8f3ecc2 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -18,11 +18,27 @@ class AstLookupVisitor : public Visitor { std::vector<ast::AST*> nodes; public: + + AstLookupVisitor() = default; + + AstLookupVisitor(ast::AstNodeType type) { + types.push_back(type); + } + std::vector<ast::AST*> lookup(ast::Program* node, ast::AstNodeType type); std::vector<ast::AST*> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); - {% for node in nodes %} + std::vector<ast::AST*> get_nodes() { + return nodes; + } + + void clear() { + types.clear(); + nodes.clear(); + } + + {% for node in nodes %} virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 5d0ab6f140..9b8726faac 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -422,7 +422,7 @@ ELSE { } <COPY_MODE>"ENDCOMMENT" { - auto str = "BLOCK_COMMENT" + std::string(yytext); + auto str = "COMMENT" + std::string(yytext); BEGIN(INITIAL); reset_end_position(); return nmodl::Parser::make_BLOCK_COMMENT(str, loc); diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 9bf062ae25..cc5337878c 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -3,10 +3,10 @@ #include <sstream> #include "arg_handler.hpp" -#include "codegen/c-cuda/codegen_c_cuda_visitor.hpp" -#include "codegen/c-openacc/codegen_c_acc_visitor.hpp" -#include "codegen/c-openmp/codegen_c_omp_visitor.hpp" -#include "codegen/c/codegen_c_visitor.hpp" +#include "codegen/codegen_cuda_visitor.hpp" +#include "codegen/codegen_acc_visitor.hpp" +#include "codegen/codegen_omp_visitor.hpp" +#include "codegen/codegen_c_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" @@ -144,24 +144,24 @@ int main(int argc, const char* argv[]) { PerfVisitor v; v.visit_program(ast.get()); - bool aos_layout = arg.aos_memory_layout(); + auto layout = arg.aos_memory_layout() ? LayoutType::aos : LayoutType::soa; logger->info("Generating host code with {} backend", arg.host_backend); if (arg.host_c_backend()) { - CodegenCVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + CodegenCVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); visitor.visit_program(ast.get()); } else if (arg.host_omp_backend()) { - CodegenCOmpVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + CodegenOmpVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); visitor.visit_program(ast.get()); } else if (arg.host_acc_backend()) { - CodegenCAccVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + CodegenAccVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); visitor.visit_program(ast.get()); } if (arg.device_cuda_backend()) { logger->info("Generating device code with {} backend", arg.accel_backend); - CodegenCCudaVisitor visitor(mod_file, arg.output_dir, aos_layout, arg.dtype); + CodegenCudaVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); visitor.visit_program(ast.get()); } } diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 60c37faa1d..4e0b32a9c8 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -44,14 +44,22 @@ void CodePrinter::add_line(const std::string& text) { add_newline(); } +void CodePrinter::add_multi_line(const std::string& text) { + auto lines = stringutils::split_string(text, '\n'); + for (const auto& line : lines) { + add_line(line); + } +} + void CodePrinter::add_newline(int n) { for (int i = 0; i < n; i++) { *result << std::endl; } } -void CodePrinter::end_block() { +void CodePrinter::end_block(int num_newlines) { indent_level--; add_indent(); *result << "}"; + add_newline(num_newlines); } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index cbb5e5bd19..d7cc8b8cac 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -46,6 +46,8 @@ class CodePrinter { void add_line(const std::string&); + void add_multi_line(const std::string&); + void add_newline(int n = 1); void increase_indent() { @@ -58,7 +60,7 @@ class CodePrinter { /// end of current block scope (i.e. end with "}") /// and decreases indentation level - void end_block(); + void end_block(int num_newlines = 0); int indent_spaces() { return NUM_SPACES * indent_level; diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 21b5c60749..a889f7553d 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -37,7 +37,8 @@ set(PYNMODL_HEADERS pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES}) target_link_libraries(_nmodl PRIVATE lexer PRIVATE visitor - PRIVATE printer) + PRIVATE printer + PRIVATE util) add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl nmodl_parser) diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 26b0d20c50..72ec11dd10 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -3,6 +3,7 @@ #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" #include "utils/table_data.hpp" +#include "utils/logger.hpp" using namespace ast; using namespace syminfo; @@ -211,10 +212,10 @@ namespace symtab { msg += " with one in " + second->get_scope(); throw std::runtime_error(msg); } - std::string msg = "SYMTAB WARNING: " + name + " [" + type + "] in "; + std::string msg = "SYMTAB :: " + name + " [" + type + "] in "; msg += current_symtab->name() + " shadows <" + properties; msg += "> definition in " + second->get_scope(); - std::cout << msg << std::endl; + logger->warn(msg); } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index ad4ca7c6ec..64b0e0ebc2 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -115,7 +115,7 @@ namespace symtab { return parent ? parent->name() : "None"; } - std::shared_ptr<Symbol> lookup(std::string name) { + std::shared_ptr<Symbol> lookup(const std::string& name) { return table.lookup(name); } diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index de6ba8e6ad..7099841031 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -19,6 +19,7 @@ add_library(util STATIC ${UTIL_SOURCE_FILES} ) +set_property(TARGET util PROPERTY POSITION_INDEPENDENT_CODE ON) #============================================================================= # Files for clang-format diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 6030c7e501..8db54d94b9 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -1,5 +1,4 @@ #include <memory> -#include <utility> #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" @@ -14,5 +13,6 @@ struct Logger { } }; + Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); logger_type logger = nmodl_logger.logger; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index bd2e732dd0..190213faae 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -3,37 +3,37 @@ #============================================================================= set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/ast_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp ) set_source_files_properties( diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index b4f66fc2cb..e57f49efcb 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1924,8 +1924,9 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { } THEN("Can find NEURON block") { - std::vector<AstNodeType> types{AstNodeType::NEURON_BLOCK}; - auto nodes = run_lookup_visitor(ast.get(), types); + AstLookupVisitor v(AstNodeType::NEURON_BLOCK); + ast->accept(&v); + auto nodes = v.get_nodes(); REQUIRE(nodes.size() == 1); std::string neuron_block = R"( From 8a9733fd45ce851c5e582503faa0ddc836c71c4e Mon Sep 17 00:00:00 2001 From: Antonio Bellotta <antonio.bellotta@epfl.ch> Date: Mon, 4 Feb 2019 14:52:59 +0100 Subject: [PATCH 118/871] EQUATION token (deprecated) now redirects to BREAKPOINT token. EQUATION tests implemented. TOKENS_TEST.md file included listing all the tokens that are covered by unit tests. Minor typos correction. Change-Id: I4049cd023504c081d6c99306af1e76664b24c08c NMODL Repo SHA: BlueBrain/nmodl@7598ece1e9c35ccd86685bcc57046122a1ac4558 --- TOKENS_TEST.md | 143 ++++++++++++++++++ src/nmodl/lexer/token_mapping.cpp | 2 +- .../transpiler/utils/nmodl_constructs.cpp | 59 +++++++- 3 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 TOKENS_TEST.md diff --git a/TOKENS_TEST.md b/TOKENS_TEST.md new file mode 100644 index 0000000000..b00daea64d --- /dev/null +++ b/TOKENS_TEST.md @@ -0,0 +1,143 @@ +# Complete list of tokens tests +## Legend + +The following symbols will be used in the document: + +✅ = Token is already present in an indipendent and simple unit test\ +❌ = Token not present in any form in any of the tests\ +❓ = Unclear if token is present or not\ +✓ = Token present in tests but not in a standalone statement, e.g. in another, possibly more complex token test + +| Token | Test Situation | +| ----- | -------------- | +|END | ❓ | +|MODEL | Deprecated? | +|CONSTANT | ✅ | +|INDEPENDENT | ✅ | +|DEPENDENT (ASSIGNED) | ✅ | +|STATE | ✅ | +|INITIAL1 | ✅ | +|DERIVATIVE | ✅ | +|SOLVE | ✅ | +|USING | ✅ | +|WITH | ✓ | +|STEPPED | ✅ | +|DISCRETE | ✅ | +|FROM | ✅ | +|FORALL1 | ✅ | +|TO | ✓ | +|BY | ✓ | +|WHILE | ✅ | +|IF | ✅ | +|ELSE | ✅ | +|START1 | ✓ | +|STEP | ✓ | +|SENS | ✅ | +|SOLVEFOR | ✓ | +|PROCEDURE | ✅ | +|PARTIAL | ✅ | +|DEFINE1 | ✅ | +|IFERROR | ✅ | +|PARAMETER | ✅ | +|DERFUNC | ❓ | +|EQUATION | ✅ | +|TERMINAL | ✅ | +|LINEAR | ✅ | +|NONLINEAR | ✅ | +|FUNCTION1 | ✅ | +|LOCAL | ✅ | +|LIN1 (~) | ✅ | +|NONLIN1 (~+) | ❌ | +|PUTQ | ✅ | +|GETQ | ✅ | +|TABLE | ✅ | +|DEPEND | ✓ | +|BREAKPOINT | ✅ | +|INCLUDE1 | ✅ | +|FUNCTION_TABLE | ✅ | +|PROTECT | ✅ | +|NRNMUTEXLOCK | ✅ | +|NRNMUTEXUNLOCK | ✅ | +|OR | ❌ | +|AND | ✓ | +|GT | ✅ | +|LT | ✅ | +|LE | ❌ | +|EQ | ✓ | +|NE | ❌ | +|NOT | ❌ | +|GE | ❌ | +|PLOT | ✅ | +|VS | ✅ | +|LAG | ✅ | +|RESET | ✅ | +|MATCH | ✅ | +|MODEL_LEVEL | Deprecated? | +|SWEEP | ✓ | +|FIRST | ✓ | +|LAST | ✓ | +|KINETIC | ✅ | +|CONSERVE | ✅ | +|REACTION | ✅ | +|REACT1 | ✅ | +|COMPARTMENT | ✅ | +|UNITS | ✅ | +|UNITSON | ✅ | +|UNITSOFF | ✅ | +|LONGDIFUS | ✅ | +|NEURON | ✅ | +|NONSPECIFIC | ✓ | +|READ | ✓ | +|WRITE | ✓ | +|USEION | ✓ | +|THREADSAFE | ✅ | +|GLOBAL | ✓ | +|SECTION | ❌ | +|RANGE | ✓ | +|POINTER | ✓ | +|BBCOREPOINTER | ✓ | +|EXTERNAL | ✓ | +|BEFORE | ✅ | +|AFTER | ✅ | +|WATCH | ✅ | +|ELECTRODE_CURRENT | ✓ | +|CONSTRUCTOR | ✓ | +|DESTRUCTOR | ✓ | +|NETRECEIVE | ✅ | +|FOR_NETCONS | ✅ | +|CONDUCTANCE | ✅ | +|REAL | ❓ | +|INTEGER | ❓ | +|DEFINEDVAR | ❓ | +|NAME | ❓ | +|METHOD | ✅ | +|SUFFIX | ✓ | +|VALENCE | ✓ | +|DEL | ✓ | +|DEL2 | ❌ | +|PRIME | ❓ | +|VERBATIM | ✅ | +|BLOCK_COMMENT | ✅ | +|LINE_COMMENT | ❓ | +|LINE_PART | ❓ | +|STRING | ❓ | +|OPEN_BRACE | ✅ | +|CLOSE_BRACE | ✅ | +|OPEN_PARENTHESIS | ✅ | +|CLOSE_PARENTHESIS | ✅ | +|OPEN_BRACKET | ✅ | +|CLOSE_BRACKET | ✅ | +|AT | ❓ | +|ADD | ✅ | +|MULTIPLY | ✅ | +|MINUS | ✅ | +|DIVIDE | ✅ | +|EQUAL | ✅ | +|CARET | ✓ | +|COLON | ❓ | +|COMMA | ✅ | +|TILDE | ✅ | +|PERIOD | ✅ | +|UNKNOWN | ❓ | +|INVALID_TOKEN | ❓ | +|UNARYMINUS | ❓ | \ No newline at end of file diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 199a869090..25993f08fd 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -29,7 +29,7 @@ namespace nmodl { {"INITIAL", Token::INITIAL1}, {"TERMINAL", Token::TERMINAL}, {"DERIVATIVE", Token::DERIVATIVE}, - {"EQUATION", Token::EQUATION}, + {"EQUATION", Token::BREAKPOINT}, {"BREAKPOINT", Token::BREAKPOINT}, {"CONDUCTANCE", Token::CONDUCTANCE}, {"SOLVE", Token::SOLVE}, diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 9f54b6e23e..b5a354a47c 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -740,7 +740,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ { "solve_block_3", { - "Solve statement with iferor block", + "Solve statement with iferror block", R"( BREAKPOINT { SOLVE states METHOD cnexp IFERROR { @@ -751,6 +751,63 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + { + "solve_block_equation_1", + { + "Solve statement without method using EQUATION, generating BREAKPOINT", + R"( + EQUATION { + SOLVE states + } + )", + + R"( + BREAKPOINT { + SOLVE states + } + )" + } + }, + + { + "solve_block_equation_2", + { + "Solve statement with method using EQUATION, generating BREAKPOINT", + R"( + EQUATION { + SOLVE states METHOD cnexp + } + )", + R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + )" + + } + }, + + { + "solve_block_equation_3", + { + "Solve statement with iferror block using EQUATION, generating BREAKPOINT", + R"( + EQUATION { + SOLVE states METHOD cnexp IFERROR { + a = 1 + } + } + )", + R"( + BREAKPOINT { + SOLVE states METHOD cnexp IFERROR { + a = 1 + } + } + )" + } + }, + { "conduct_hint_1", { From 961e8cc1692910d82290460aee589a8c944d671f Mon Sep 17 00:00:00 2001 From: Antonio Bellotta <antonio.bellotta@epfl.ch> Date: Mon, 4 Feb 2019 17:37:26 +0100 Subject: [PATCH 119/871] cosmetic: fixed formatting of nmodl_constructs Change-Id: Ifa6ab487ba7ae1cdcbd20c862906b79623978256 NMODL Repo SHA: BlueBrain/nmodl@47562c6244dbf7e8a1d51950abe3c10991c50234 --- .../transpiler/utils/nmodl_constructs.cpp | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index b5a354a47c..87878a4634 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -752,27 +752,27 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ }, { - "solve_block_equation_1", + "solve_block_equation_1", { - "Solve statement without method using EQUATION, generating BREAKPOINT", - R"( - EQUATION { - SOLVE states - } + "Solve statement without method using EQUATION, generating BREAKPOINT", + R"( + EQUATION { + SOLVE states + } )", - R"( - BREAKPOINT { - SOLVE states - } + R"( + BREAKPOINT { + SOLVE states + } )" } }, { - "solve_block_equation_2", - { - "Solve statement with method using EQUATION, generating BREAKPOINT", + "solve_block_equation_2", + { + "Solve statement with method using EQUATION, generating BREAKPOINT", R"( EQUATION { SOLVE states METHOD cnexp @@ -784,13 +784,13 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } )" - } + } }, { - "solve_block_equation_3", - { - "Solve statement with iferror block using EQUATION, generating BREAKPOINT", + "solve_block_equation_3", + { + "Solve statement with iferror block using EQUATION, generating BREAKPOINT", R"( EQUATION { SOLVE states METHOD cnexp IFERROR { @@ -805,7 +805,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } } )" - } + } }, { From 3ece8d4d5f6c6d878bfda031d14094e4a1efcdd6 Mon Sep 17 00:00:00 2001 From: Antonio Bellotta <antonio.bellotta@epfl.ch> Date: Tue, 5 Feb 2019 17:44:21 +0100 Subject: [PATCH 120/871] Updated test suite document : - Added test for SECTION, NONLIN1 and conditional operators - TOKENS_TEST.md file updated and moved to doc sub-directory Change-Id: I573d80707c10cf8d457a04bc0cfea9ff470ab952 NMODL Repo SHA: BlueBrain/nmodl@f38912af2c8044fa8c23fd706917fee541e5850c --- TOKENS_TEST.md | 143 ------------------ doc/test_status.md | 143 ++++++++++++++++++ src/nmodl/parser/nmodl.yy | 2 +- .../transpiler/utils/nmodl_constructs.cpp | 65 +++++++- 4 files changed, 208 insertions(+), 145 deletions(-) delete mode 100644 TOKENS_TEST.md create mode 100644 doc/test_status.md diff --git a/TOKENS_TEST.md b/TOKENS_TEST.md deleted file mode 100644 index b00daea64d..0000000000 --- a/TOKENS_TEST.md +++ /dev/null @@ -1,143 +0,0 @@ -# Complete list of tokens tests -## Legend - -The following symbols will be used in the document: - -✅ = Token is already present in an indipendent and simple unit test\ -❌ = Token not present in any form in any of the tests\ -❓ = Unclear if token is present or not\ -✓ = Token present in tests but not in a standalone statement, e.g. in another, possibly more complex token test - -| Token | Test Situation | -| ----- | -------------- | -|END | ❓ | -|MODEL | Deprecated? | -|CONSTANT | ✅ | -|INDEPENDENT | ✅ | -|DEPENDENT (ASSIGNED) | ✅ | -|STATE | ✅ | -|INITIAL1 | ✅ | -|DERIVATIVE | ✅ | -|SOLVE | ✅ | -|USING | ✅ | -|WITH | ✓ | -|STEPPED | ✅ | -|DISCRETE | ✅ | -|FROM | ✅ | -|FORALL1 | ✅ | -|TO | ✓ | -|BY | ✓ | -|WHILE | ✅ | -|IF | ✅ | -|ELSE | ✅ | -|START1 | ✓ | -|STEP | ✓ | -|SENS | ✅ | -|SOLVEFOR | ✓ | -|PROCEDURE | ✅ | -|PARTIAL | ✅ | -|DEFINE1 | ✅ | -|IFERROR | ✅ | -|PARAMETER | ✅ | -|DERFUNC | ❓ | -|EQUATION | ✅ | -|TERMINAL | ✅ | -|LINEAR | ✅ | -|NONLINEAR | ✅ | -|FUNCTION1 | ✅ | -|LOCAL | ✅ | -|LIN1 (~) | ✅ | -|NONLIN1 (~+) | ❌ | -|PUTQ | ✅ | -|GETQ | ✅ | -|TABLE | ✅ | -|DEPEND | ✓ | -|BREAKPOINT | ✅ | -|INCLUDE1 | ✅ | -|FUNCTION_TABLE | ✅ | -|PROTECT | ✅ | -|NRNMUTEXLOCK | ✅ | -|NRNMUTEXUNLOCK | ✅ | -|OR | ❌ | -|AND | ✓ | -|GT | ✅ | -|LT | ✅ | -|LE | ❌ | -|EQ | ✓ | -|NE | ❌ | -|NOT | ❌ | -|GE | ❌ | -|PLOT | ✅ | -|VS | ✅ | -|LAG | ✅ | -|RESET | ✅ | -|MATCH | ✅ | -|MODEL_LEVEL | Deprecated? | -|SWEEP | ✓ | -|FIRST | ✓ | -|LAST | ✓ | -|KINETIC | ✅ | -|CONSERVE | ✅ | -|REACTION | ✅ | -|REACT1 | ✅ | -|COMPARTMENT | ✅ | -|UNITS | ✅ | -|UNITSON | ✅ | -|UNITSOFF | ✅ | -|LONGDIFUS | ✅ | -|NEURON | ✅ | -|NONSPECIFIC | ✓ | -|READ | ✓ | -|WRITE | ✓ | -|USEION | ✓ | -|THREADSAFE | ✅ | -|GLOBAL | ✓ | -|SECTION | ❌ | -|RANGE | ✓ | -|POINTER | ✓ | -|BBCOREPOINTER | ✓ | -|EXTERNAL | ✓ | -|BEFORE | ✅ | -|AFTER | ✅ | -|WATCH | ✅ | -|ELECTRODE_CURRENT | ✓ | -|CONSTRUCTOR | ✓ | -|DESTRUCTOR | ✓ | -|NETRECEIVE | ✅ | -|FOR_NETCONS | ✅ | -|CONDUCTANCE | ✅ | -|REAL | ❓ | -|INTEGER | ❓ | -|DEFINEDVAR | ❓ | -|NAME | ❓ | -|METHOD | ✅ | -|SUFFIX | ✓ | -|VALENCE | ✓ | -|DEL | ✓ | -|DEL2 | ❌ | -|PRIME | ❓ | -|VERBATIM | ✅ | -|BLOCK_COMMENT | ✅ | -|LINE_COMMENT | ❓ | -|LINE_PART | ❓ | -|STRING | ❓ | -|OPEN_BRACE | ✅ | -|CLOSE_BRACE | ✅ | -|OPEN_PARENTHESIS | ✅ | -|CLOSE_PARENTHESIS | ✅ | -|OPEN_BRACKET | ✅ | -|CLOSE_BRACKET | ✅ | -|AT | ❓ | -|ADD | ✅ | -|MULTIPLY | ✅ | -|MINUS | ✅ | -|DIVIDE | ✅ | -|EQUAL | ✅ | -|CARET | ✓ | -|COLON | ❓ | -|COMMA | ✅ | -|TILDE | ✅ | -|PERIOD | ✅ | -|UNKNOWN | ❓ | -|INVALID_TOKEN | ❓ | -|UNARYMINUS | ❓ | \ No newline at end of file diff --git a/doc/test_status.md b/doc/test_status.md new file mode 100644 index 0000000000..e0edb8db18 --- /dev/null +++ b/doc/test_status.md @@ -0,0 +1,143 @@ +# Test Suite + +This page describe the status of NMODL test suite. The goal of test suite is to cover all NMODL language constructs and test implementation of lexer, parser, ast and visitors. + +## Legend + +The following symbols are used in the document to descrive the status : + +* ✅ = Test implemented +* ❌ = Test doesn't exist +* ❓ = Unclear + +## Status + +| Token | Lexer | +| ----- | -------------- | +|TITLE | ✅ | +|CONSTANT | ✅ | +|INDEPENDENT | ✅ | +|ASSIGNED | ✅ | +|STATE | ✅ | +|INITIAL | ✅ | +|DERIVATIVE | ✅ | +|SOLVE | ✅ | +|USING | ✅ | +|WITH | ✅ | +|STEPPED | ✅ | +|DISCRETE | ✅ | +|FROM | ✅ | +|FORALL | ✅ | +|TO | ✅ | +|BY | ✅ | +|WHILE | ✅ | +|IF | ✅ | +|ELSE | ✅ | +|START | ✅ | +|STEP | ✅ | +|SENS | ✅ | +|SOLVEFOR | ✅ | +|PROCEDURE | ✅ | +|PARTIAL | ✅ | +|DEFINE | ✅ | +|IFERROR | ✅ | +|PARAMETER | ✅ | +|EQUATION | ✅ | +|TERMINAL | ✅ | +|LINEAR | ✅ | +|NONLINEAR | ✅ | +|FUNCTION | ✅ | +|LOCAL | ✅ | +| ~ | ✅ | +|~+ | ❓ | +|PUTQ | ✅ | +|GETQ | ✅ | +|TABLE | ✅ | +|DEPEND | ✅ | +|BREAKPOINT | ✅ | +|INCLUDE | ✅ | +|FUNCTION_TABLE | ✅ | +|PROTECT | ✅ | +|MUTEXLOCK | ✅ | +|MUTEXUNLOCK | ✅ | +| \|\| | ✅ | +| && | ✅ | +| \> | ✅ | +| \< | ✅ | +| \<= | ✅ | +| == | ✅ | +| != | ✅ | +| ! | ✅ | +| \>= | ✅ | +|PLOT | ✅ | +|VS | ✅ | +|LAG | ✅ | +|RESET | ✅ | +|MATCH | ✅ | +|MODEL_LEVEL | ✅ | +|SWEEP | ✅ | +|FIRST | ✅ | +|LAST | ✅ | +|KINETIC | ✅ | +|CONSERVE | ✅ | +|REACTION | ✅ | +|<-> | ✅ | +|COMPARTMENT | ✅ | +|UNITS | ✅ | +|UNITSON | ✅ | +|UNITSOFF | ✅ | +|LONGDIFUS | ✅ | +|NEURON | ✅ | +|NONSPECIFIC | ✅ | +|READ | ✅ | +|WRITE | ✅ | +|USEION | ✅ | +|THREADSAFE | ✅ | +|GLOBAL | ✅ | +|SECTION | ✅ | +|RANGE | ✅ | +|POINTER | ✅ | +|BBCOREPOINTER | ✅ | +|EXTERNAL | ✅ | +|BEFORE | ✅ | +|AFTER | ✅ | +|WATCH | ✅ | +|ELECTRODE_CURRENT | ✅ | +|CONSTRUCTOR | ✅ | +|DESTRUCTOR | ✅ | +|NETRECEIVE | ✅ | +|FOR_NETCONS | ✅ | +|CONDUCTANCE | ✅ | +|REAL | ✅ | +|INTEGER | ✅ | +|DEFINEDVAR | ✅ | +|NAME | ✅ | +|METHOD | ✅ | +|SUFFIX | ✅ | +|VALENCE | ✅ | +|DEL | ✅ | +|DEL2 | ✅ | +|PRIME | ✅ | +|VERBATIM | ✅ | +|COMMENT ENDCOMMENT | ✅ | +|: | ✅ | +|? | ✅ | +|STRING | ✅ | +|{ | ✅ | +|} | ✅ | +|( | ✅ | +|) | ✅ | +|[ | ✅ | +|] | ✅ | +|@ | ✅ | +|+ | ✅ | +|x | ✅ | +|- | ✅ | +|/ | ✅ | +|= | ✅ | +|^ | ✅ | +|: | ✅ | +|, | ✅ | +|~ | ✅ | +|. | ✅ | +|- | ✅ | diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 5eaef273c7..cd080a94e9 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -77,7 +77,7 @@ * we want to access original string and location. With C++ interface and other * location related information, this is now less useful in parser. But when we * lexer executable or tests, it's useful to return ModToken. Note that UNKNOWN - * token is added for convenience (with default argumebts). */ + * token is added for convenience (with default arguments). */ %token <ModToken> MODEL %token <ModToken> CONSTANT diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 87878a4634..108369e39a 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -630,7 +630,7 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ { "while_statement_2", { - "While statement with expression as condition", + "While statement with AND expression as condition", R"( CONSTRUCTOR { WHILE ((a+1)<2 && b>100) { @@ -642,6 +642,35 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + { + "while_statement_3", + { + "While statement with OR expression as condition", + R"( + CONSTRUCTOR { + WHILE ((a+1)<=2 || b>=100) { + x = 10 + y = 20 + } + } + )" + } + }, + + { + "while_statement_4", + { + "While statement with NE and NOT condition", + R"( + CONSTRUCTOR { + WHILE (a!=2 && !b) { + x = 10 + y = 20 + } + } + )" + } + }, { "if_statement_1", @@ -994,6 +1023,29 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + /// \todo : NONLIN1 (i.e. ~+) gets replaced with ~. This is not + /// a problem as ~ in NONLINEAR block gets replaced with + /// ~ anyway. But it would be good to keep same variable + /// for nmodl-format utility + { + "nonlinear_block_2", + { + "NONLINEAR block using symbol", + R"( + NONLINEAR some_name SOLVEFOR a,b { + ~+ I1*bi1+C2*b01-C1*(fi1+f01) = 0 + ~+ C1+C2+C3+C4+C5+O+I1+I2+I3+I4+I5+I6 = 1 + } + )", + R"( + NONLINEAR some_name SOLVEFOR a,b { + ~ I1*bi1+C2*b01-C1*(fi1+f01) = 0 + ~ C1+C2+C3+C4+C5+O+I1+I2+I3+I4+I5+I6 = 1 + } + )" + } + }, + { "table_statement_1", { @@ -1335,6 +1387,17 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } )" } + }, + { + "section_test", + { + "Section token test", + R"( + NEURON { + SECTION a, b + } + )" + } } // clang-format on }; From ca0a30d7da8e94ca2cdcc0b3cfd9e7aa7dc969d6 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 6 Feb 2019 06:06:13 +0100 Subject: [PATCH 121/871] New AST node and simple visitor for differential equations : - new ast node DifferentialEquation added to represent ODE - cnexp visitor updated to use this new node type - simple utility visitor added that can help to find all ODEs in a given MOD file - updated visitors test - added command line option to dump ast to json file Change-Id: I32f4dc6605074d8c21fd0b94db4eea6ba225f959 NMODL Repo SHA: BlueBrain/nmodl@1477d77937b640cafb72066a2f2c936e5075fd27 --- src/nmodl/ast/ast_common.hpp | 4 ++ src/nmodl/language/CMakeLists.txt | 1 + src/nmodl/language/nmodl.yaml | 7 +++ src/nmodl/nmodl/arg_handler.cpp | 8 +++ src/nmodl/nmodl/arg_handler.hpp | 3 + src/nmodl/nmodl/main.cpp | 7 +++ src/nmodl/parser/nmodl.yy | 8 ++- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/cnexp_solve_visitor.cpp | 10 ++-- src/nmodl/visitors/cnexp_solve_visitor.hpp | 6 +- .../differential_equation_visitor.cpp | 8 +++ .../differential_equation_visitor.hpp | 32 +++++++++++ test/nmodl/transpiler/visitor/visitor.cpp | 57 +++++++++++++++++++ 13 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 src/nmodl/visitors/differential_equation_visitor.cpp create mode 100644 src/nmodl/visitors/differential_equation_visitor.hpp diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index efc17429bd..6a7f583f0f 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -649,6 +649,10 @@ namespace ast { virtual bool is_constant_var() { return false; } + + virtual bool is_diff_eq_expression() { + return false; + } }; } // namespace ast diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index cee415770d..6ff3eaa832 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -33,6 +33,7 @@ else() COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" ) endif() + #============================================================================= # Target to propogate dependencies properly to lexer #============================================================================= diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 48da45e41f..20361e5bf9 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -765,6 +765,13 @@ description: "..." type: Expression public: true + - DiffEqExpression: + description: "Represents differential equation in DERIVATIVE block" + members: + - expression: + description: "Differential Expression" + type: BinaryExpression + public: true - UnaryExpression: description: ".." members: diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index 18a67a80e7..d2fd8046d9 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -130,6 +130,13 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { cmd, false); + switch_arg_type ast_to_json_arg( + "", + "dump-ast-as-json", + "Dump intermediate AST states into JSON state", + cmd, + false); + switch_arg_type no_verbatim_rename_arg( "", "no-verbatim-rename", @@ -170,6 +177,7 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { output_dir = output_dir_arg.getValue(); scratch_dir = scratch_dir_arg.getValue(); ast_to_nmodl = nmodl_state_arg.getValue(); + ast_to_json = ast_to_json_arg.getValue(); } catch (TCLAP::ArgException& e) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; } diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp index 3f8c7f73d4..613d3175ee 100644 --- a/src/nmodl/nmodl/arg_handler.hpp +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -46,6 +46,9 @@ struct ArgumentHandler { /// generate nmodl from ast bool ast_to_nmodl; + /// generate json from ast + bool ast_to_json; + /// enable verbose (todo: replace by log) bool verbose; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index cc5337878c..47a436098f 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -127,6 +127,13 @@ int main(int argc, const char* argv[]) { if (arg.perf_stats) { PerfVisitor v(arg.scratch_dir + "/" + mod_file + ".perf.json"); + logger->info("Dumping performance statistics into JSON format"); + v.visit_program(ast.get()); + } + + if (arg.ast_to_json) { + JSONVisitor v(arg.scratch_dir + "/" + mod_file + ".ast.json"); + logger->info("Dumping AST state into JSON format"); v.visit_program(ast.get()); } diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index cd080a94e9..fa75f22778 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1005,7 +1005,13 @@ astmt : asgn asgn : varname "=" expr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ASSIGN), $3); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ASSIGN), $3); + auto name = $1->get_name(); + if (name->is_prime_name()) { + $$ = new ast::DiffEqExpression(expression); + } else { + $$ = expression; + } } | nonlineqn expr "=" expr { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 190213faae..8d006af6b6 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -7,6 +7,8 @@ set(VISITOR_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 4a2124760e..9e0bb1db6b 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -2,6 +2,7 @@ #include "parser/diffeq_driver.hpp" #include "symtab/symbol.hpp" +#include "utils/logger.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -15,10 +16,10 @@ void CnexpSolveVisitor::visit_solve_block(SolveBlock* node) { } } -void CnexpSolveVisitor::visit_derivative_block(DerivativeBlock* node) { - derivative_block = true; +void CnexpSolveVisitor::visit_diff_eq_expression(DiffEqExpression* node) { + differential_equation = true; node->visit_children(this); - derivative_block = false; + differential_equation = false; } void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { @@ -27,12 +28,13 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { auto& op = node->op; /// we have to only solve binary expressions in derivative block - if (!derivative_block || (op.get_value() != BOP_ASSIGN)) { + if (!differential_equation) { return; } /// lhs of the expression should be variable if (!lhs->is_var_name()) { + logger->warn("LHS of differential equation is not a VariableName"); return; } diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp index ab3b7b7e58..2658fb078e 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -22,8 +22,8 @@ class CnexpSolveVisitor : public AstVisitor { /// method specified in solve block std::string solve_method; - /// true while visiting derivative block - bool derivative_block = false; + /// true while visiting differential equation + bool differential_equation = false; /// name of the cnexp method const std::string cnexp_method = "cnexp"; @@ -40,7 +40,7 @@ class CnexpSolveVisitor : public AstVisitor { CnexpSolveVisitor() = default; void visit_solve_block(ast::SolveBlock* node) override; - void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_diff_eq_expression(ast::DiffEqExpression* node) override; void visit_binary_expression(ast::BinaryExpression* node) override; void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/visitors/differential_equation_visitor.cpp b/src/nmodl/visitors/differential_equation_visitor.cpp new file mode 100644 index 0000000000..268512bc67 --- /dev/null +++ b/src/nmodl/visitors/differential_equation_visitor.cpp @@ -0,0 +1,8 @@ +#include "visitors/differential_equation_visitor.hpp" +#include <iostream> + +using namespace ast; + +void DifferentialEquationVisitor::visit_diff_eq_expression(DiffEqExpression* node) { + equations.push_back(node); +} diff --git a/src/nmodl/visitors/differential_equation_visitor.hpp b/src/nmodl/visitors/differential_equation_visitor.hpp new file mode 100644 index 0000000000..45745972fa --- /dev/null +++ b/src/nmodl/visitors/differential_equation_visitor.hpp @@ -0,0 +1,32 @@ +#ifndef DIFF_EQ_VISITOR_HPP +#define DIFF_EQ_VISITOR_HPP + +#include <vector> + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" + +/** + * \class DifferentialEquationVisitor + * \brief Visitor for differential equations in derivative block + * + * Simple example of visitor to find out all differential + * equations in a MOD file. + */ + +class DifferentialEquationVisitor : public AstVisitor { + private: + + std::vector<ast::DiffEqExpression*> equations; + + public: + DifferentialEquationVisitor() = default; + + void visit_diff_eq_expression(ast::DiffEqExpression* node) override; + + std::vector<ast::DiffEqExpression*> get_equations() { + return equations; + } +}; + +#endif diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index e57f49efcb..ba9991abc0 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -9,6 +9,7 @@ #include "test/utils/test_utils.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" +#include "visitors/differential_equation_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" @@ -1955,3 +1956,59 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { } } } + + +//============================================================================= +// DifferentialEquation visitor tests +//============================================================================= + +std::vector<std::string> run_differential_equation_visitor(const std::string& text) { + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + DifferentialEquationVisitor v; + v.visit_program(ast.get()); + auto equations = v.get_equations(); + + std::vector<std::string> result; + for(const auto& equation : equations) { + std::stringstream stream; + NmodlPrintVisitor v(stream); + equation->accept(&v); + result.push_back(stream.str()); + } + return result; +} + + +SCENARIO("DifferentialEquation visitor finding ODEs") { + GIVEN("Derivative block with multiple differential equations") { + std::string nmodl_text = R"( + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + m = m + h + } + )"; + + THEN("ODEs can be searched in AST") { + auto result = run_differential_equation_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "m' = (mInf-m)/mTau"); + REQUIRE(result[1] == "h' = (hInf-h)/hTau"); + } + } + GIVEN("Derivative mod files without ODE") { + std::string nmodl_text = R"( + DERIVATIVE states { + m = m + h + } + )"; + + THEN("No ODEs are found") { + auto result = run_differential_equation_visitor(nmodl_text); + REQUIRE(result.size() == 0); + } + } +} From be39ce94dc4cbdf2aec25a83046e808f08b98ba2 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 6 Feb 2019 22:15:30 +0100 Subject: [PATCH 122/871] Enable shared_from_this for AST nodes: - this allows returning shared pointers from visitors - will be used for transfering ownership from c++ to python - added c++ glue code to enable python bindings explicitly returning shared pointers. Change-Id: Iac96f6fd727b2c06595f26b3e5e6818416e66cb7 NMODL Repo SHA: BlueBrain/nmodl@c839f6366d923b89dcc8de6ab86f01323b27eed1 --- src/nmodl/ast/ast_common.hpp | 8 +++++++- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- src/nmodl/language/templates/ast.hpp | 4 ++++ src/nmodl/language/templates/lookup_visitor.cpp | 6 +++--- src/nmodl/language/templates/lookup_visitor.hpp | 8 ++++---- src/nmodl/language/templates/pyast.cpp | 4 +++- src/nmodl/language/templates/pyast.hpp | 2 ++ test/nmodl/transpiler/visitor/visitor.cpp | 4 ++-- 8 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 6a7f583f0f..fd0668a885 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -1,6 +1,7 @@ #ifndef ASTUTILS_HPP #define ASTUTILS_HPP +#include <memory> #include <string> #include "ast/ast_decl.hpp" @@ -61,7 +62,7 @@ namespace ast { /* define abstract base class for all AST nodes * this also serves to define the visitable objects. */ - struct AST { + struct AST : public std::enable_shared_from_this<AST> { /* all AST nodes have a member which stores their * basetype (int, bool, none, object). Further type * information will come from the symbol table. @@ -112,6 +113,10 @@ namespace ast { virtual ~AST() { } + virtual std::shared_ptr<AST> get_shared_ptr() { + return std::static_pointer_cast<AST>(shared_from_this()); + } + virtual bool is_ast() { return true; } @@ -653,6 +658,7 @@ namespace ast { virtual bool is_diff_eq_expression() { return false; } + }; } // namespace ast diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 236e02cd5e..486ce4bd90 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1388,7 +1388,7 @@ static TableStatement* get_table_statement(ast::Block* node) { node->get_node_name(), table_statements.size()); throw std::runtime_error(message); } - return dynamic_cast<TableStatement*>(table_statements.front()); + return dynamic_cast<TableStatement*>(table_statements.front().get()); } diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index bf046e0ca4..7e8ff9bbd0 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -68,6 +68,10 @@ namespace ast { {{ virtual(node) }} {{ node.class_name }}* clone() override { return new {{ node.class_name }}(*this); } + {{ virtual(node) }} std::shared_ptr<AST> get_shared_ptr() override { + return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); + } + {{ virtual(node) }} AstNodeType get_node_type() override { return AstNodeType::{{ node.ast_enum_name }}; } {{ virtual(node) }} std::string get_node_type_name() override { return "{{ node.class_name }}"; } diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/lookup_visitor.cpp index 8c9092a7d8..34c743fa0a 100644 --- a/src/nmodl/language/templates/lookup_visitor.cpp +++ b/src/nmodl/language/templates/lookup_visitor.cpp @@ -7,7 +7,7 @@ using namespace ast; void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { auto type = node->get_node_type(); if(std::find(types.begin(), types.end(), type) != types.end()) { - nodes.push_back(node); + nodes.push_back(node->get_shared_ptr()); } node->visit_children(this); } @@ -15,7 +15,7 @@ void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name {% endfor %} -std::vector<AST*> AstLookupVisitor::lookup(Program* node, std::vector<AstNodeType>& _types) { +std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(Program* node, std::vector<AstNodeType>& _types) { nodes.clear(); types = _types; node->accept(this); @@ -23,7 +23,7 @@ std::vector<AST*> AstLookupVisitor::lookup(Program* node, std::vector<AstNodeTyp } -std::vector<AST*> AstLookupVisitor::lookup(Program* node, AstNodeType type) { +std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(Program* node, AstNodeType type) { nodes.clear(); types.clear(); types.push_back(type); diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index d8f8f3ecc2..44ce788a4d 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -15,7 +15,7 @@ class AstLookupVisitor : public Visitor { std::vector<ast::AstNodeType> types; /// matching nodes found in the ast - std::vector<ast::AST*> nodes; + std::vector<std::shared_ptr<ast::AST>> nodes; public: @@ -25,11 +25,11 @@ class AstLookupVisitor : public Visitor { types.push_back(type); } - std::vector<ast::AST*> lookup(ast::Program* node, ast::AstNodeType type); + std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, ast::AstNodeType type); - std::vector<ast::AST*> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); + std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); - std::vector<ast::AST*> get_nodes() { + std::vector<std::shared_ptr<ast::AST>> get_nodes() { return nodes; } diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pyast.cpp index 9758d0e239..ea4b72b604 100644 --- a/src/nmodl/language/templates/pyast.cpp +++ b/src/nmodl/language/templates/pyast.cpp @@ -45,10 +45,11 @@ void init_ast_module(py::module& m) { - py::class_<AST, PyAST> ast_(m_ast, "AST"); + py::class_<AST, PyAST, std::shared_ptr<AST>> ast_(m_ast, "AST"); ast_.def(py::init<>()) .def("visit_children", &AST::visit_children) .def("accept", &AST::accept) + .def("get_shared_ptr", &AST::get_shared_ptr) .def("get_node_type", &AST::get_node_type) .def("get_node_type_name", &AST::get_node_type_name) .def("get_node_name", &AST::get_node_name) @@ -90,6 +91,7 @@ void init_ast_module(py::module& m) { {{ var(node) }}.def("visit_children", &{{ node.class_name }}::visit_children) .def("accept", &{{ node.class_name }}::accept) .def("clone", &{{ node.class_name }}::clone) + .def("get_shared_ptr", &{{ node.class_name }}::get_shared_ptr) .def("get_node_type", &{{ node.class_name }}::get_node_type) .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name) .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}); diff --git a/src/nmodl/language/templates/pyast.hpp b/src/nmodl/language/templates/pyast.hpp index 261b9fc8a0..298df1179e 100644 --- a/src/nmodl/language/templates/pyast.hpp +++ b/src/nmodl/language/templates/pyast.hpp @@ -40,6 +40,8 @@ struct PyAST: public AST { AST* clone() override { PYBIND11_OVERLOAD(AST*, AST, clone, ); } + std::shared_ptr<AST> get_shared_ptr() override { PYBIND11_OVERLOAD(std::shared_ptr<AST>, AST, get_shared_ptr, ); } + ModToken* get_token() override { PYBIND11_OVERLOAD(ModToken*, AST, get_token, ); } void set_symbol_table(symtab::SymbolTable* newsymtab) override { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index ba9991abc0..ffec63ec00 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1879,7 +1879,7 @@ SCENARIO("Running visitor passes multiple time") { // Ast lookup visitor tests //============================================================================= -std::vector<AST*> run_lookup_visitor(Program* node, std::vector<AstNodeType>& types) { +std::vector<std::shared_ptr<AST>> run_lookup_visitor(Program* node, std::vector<AstNodeType>& types) { AstLookupVisitor v; return v.lookup(node, types); } @@ -1891,7 +1891,7 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { return driver.ast(); }; - auto to_nmodl = [](AST* node) { + auto to_nmodl = [](std::shared_ptr<AST> node) { std::stringstream stream; NmodlPrintVisitor v(stream); node->accept(&v); From 2e9a89eb133f81f8830de5df54ae8fc4ac6caf22 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 7 Feb 2019 23:43:01 +0100 Subject: [PATCH 123/871] Provide to_nmodl and to_json helper function - update tests to use these new helpers Change-Id: Iebe2385170e9dafbd51925e402267b69b1c7979f NMODL Repo SHA: BlueBrain/nmodl@2183fbbba22310394a25645513fd0d33f84adb52 --- src/nmodl/visitors/visitor_utils.cpp | 22 +++++++++++++++++ src/nmodl/visitors/visitor_utils.hpp | 9 +++++++ test/nmodl/transpiler/visitor/visitor.cpp | 29 +++++------------------ 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index af9e414e39..6cdbe857d8 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -4,6 +4,8 @@ #include "ast/ast.hpp" #include "parser/nmodl_driver.hpp" +#include "visitors/json_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" using namespace ast; @@ -74,3 +76,23 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { std::shared_ptr<Statement>(procedure->get_statement_block()->get_statements()[0]->clone()); return statement; } + +namespace nmodl { + + std::string to_nmodl(ast::AST *node) { + std::stringstream stream; + NmodlPrintVisitor v(stream); + node->accept(&v); + return stream.str(); + } + + + std::string to_json(ast::AST *node, bool compact) { + std::stringstream stream; + JSONVisitor v(stream); + v.compact_json(compact); + node->accept(&v); + return stream.str(); + } + +} \ No newline at end of file diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 4e3364cbae..d98b01bb38 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -26,4 +26,13 @@ ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& /** Create ast statement node from given code in string format */ std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); + +namespace nmodl { + /** Given AST node, return the NMODL string representation */ + std::string to_nmodl(ast::AST *node); + + /** Given AST node, return the JSON string representation */ + std::string to_json(ast::AST *node, bool compact = false); +} + #endif diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index ffec63ec00..b91b20eb45 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -24,6 +24,7 @@ using json = nlohmann::json; using namespace ast; +using namespace nmodl; //============================================================================= // Verbatim visitor tests @@ -66,15 +67,7 @@ std::string run_json_visitor(const std::string& text, bool compact = false) { nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); - - std::stringstream ss; - JSONVisitor v(ss); - - /// if compact is true then we get compact json output - v.compact_json(compact); - - v.visit_program(ast.get()); - return ss.str(); + return to_json(ast.get(), compact); } TEST_CASE("JSON Visitor") { @@ -1891,13 +1884,6 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { return driver.ast(); }; - auto to_nmodl = [](std::shared_ptr<AST> node) { - std::stringstream stream; - NmodlPrintVisitor v(stream); - node->accept(&v); - return stream.str(); - }; - GIVEN("A mod file with nodes of type NEURON, RANGE, BinaryExpression") { std::string nmodl_text = R"( NEURON { @@ -1920,8 +1906,8 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { std::vector<AstNodeType> types{AstNodeType::RANGE_VAR}; auto result = run_lookup_visitor(ast.get(), types); REQUIRE(result.size() == 2); - REQUIRE(to_nmodl(result[0]) == "tau"); - REQUIRE(to_nmodl(result[1]) == "h"); + REQUIRE(to_nmodl(result[0].get()) == "tau"); + REQUIRE(to_nmodl(result[1].get()) == "h"); } THEN("Can find NEURON block") { @@ -1934,7 +1920,7 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { NEURON { RANGE tau, h })"; - auto result = reindent_text(to_nmodl(nodes[0])); + auto result = reindent_text(to_nmodl(nodes[0].get())); auto expected = reindent_text(neuron_block); REQUIRE(result == expected); } @@ -1973,10 +1959,7 @@ std::vector<std::string> run_differential_equation_visitor(const std::string& te std::vector<std::string> result; for(const auto& equation : equations) { - std::stringstream stream; - NmodlPrintVisitor v(stream); - equation->accept(&v); - result.push_back(stream.str()); + result.push_back(to_nmodl(equation)); } return result; } From 0ef187f566b81e6e52b76a955e44e168e5b5cd2b Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Fri, 8 Feb 2019 16:41:42 +0100 Subject: [PATCH 124/871] CMake and visitor update : - add find module for pytest - use to_nmodl in helper in cnexp visitor Change-Id: I4615d1490ad56268f87f8bb4f1b817df390efa88 NMODL Repo SHA: BlueBrain/nmodl@e332993914fce31afb19e8623cfbcd7eadd774aa --- README.md | 2 +- cmake/nmodl/CMakeLists.txt | 3 ++- src/nmodl/visitors/cnexp_solve_visitor.cpp | 7 ++----- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 17b641c8f1..d4db5e3793 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ itself. - Python yaml (pyyaml) - Jinja2 (>=2.10) - Python textwrap -- pybind11 (which should be fetched in its submodule in ext/pybind11) +- pybind11 (which should be fetched as submodule in ext/pybind11) - pytest (>=4.0.0) (only for tests) #### Getting Dependencies diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 734ed6d2cd..895296c3da 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -41,8 +41,9 @@ find_package(ClangFormat) message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) find_python_module(jinja2 REQUIRED) -find_python_module(yaml REQUIRED) +find_python_module(test REQUIRED) find_python_module(textwrap REQUIRED) +find_python_module(yaml REQUIRED) include_directories( ${PROJECT_SOURCE_DIR} diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 9e0bb1db6b..0f30e82ddd 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -41,11 +41,8 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { auto name = std::dynamic_pointer_cast<VarName>(lhs)->get_name(); if (name->is_prime_name()) { - /// convert ode into string format - std::stringstream stream; - NmodlPrintVisitor v(stream); - node->visit_children(&v); - auto equation = stream.str(); + + auto equation = nmodl::to_nmodl(node); diffeq::Driver diffeq_driver; if (solve_method == cnexp_method) { From ee69b40f664e9a3c711e0508816b95b48b93a8e5 Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Fri, 8 Feb 2019 16:30:13 +0100 Subject: [PATCH 125/871] code_generator.py now only update outdated files in ./src/ast/ - `code_generator.py` do not update up-to-date files, so that they are not considered as changed by CMake. - clang-format is now called by `code_generator.py` - clang-format on template files only for version >= 4 Change-Id: I7e36fb82b45fa62b4b27d5c8aeb5ec66aae67211 NMODL Repo SHA: BlueBrain/nmodl@44421bc4502eaae434907253e2decf249feffcb9 --- src/nmodl/language/CMakeLists.txt | 58 ++++++++++++---------------- src/nmodl/language/code_generator.py | 43 +++++++++++++++++++-- src/nmodl/language/templates/ast.hpp | 2 +- 3 files changed, 65 insertions(+), 38 deletions(-) diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 6ff3eaa832..3860ae50da 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -1,40 +1,30 @@ -#============================================================================= +# ============================================================================= # Command to generate AST/Visitor classes from language definition -#============================================================================= -set_source_files_properties( ${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) +# ============================================================================= +set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) +file(GLOB TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" + "${PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") -file ( GLOB TEMPLATE_FILES - "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${PROJECT_SOURCE_DIR}/src/language/templates/*.hpp" - ) +file(GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py") -file ( GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py" ) - -if(CLANG_FORMAT_FOUND) - add_custom_command ( - OUTPUT ${AUTO_GENERATED_FILES} - COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - COMMAND ${CLANG_FORMAT_EXECUTABLE} ARGS -i --style=file ${AUTO_GENERATED_FILES} - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" - ) -else() - add_custom_command ( - OUTPUT ${AUTO_GENERATED_FILES} - COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODLX : GENERATING AST CLASSES WITH PYTHON GENERATOR! --" - ) +if(CLANG_FORMAT_FOUND AND CLANG_FORMAT_VERSION_MAJOR GREATER_EQUAL 4) + set(CODE_GENERATOR_OPTS --clang-format=${CLANG_FORMAT_EXECUTABLE}) endif() -#============================================================================= -# Target to propogate dependencies properly to lexer -#============================================================================= -add_custom_target (pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) +add_custom_command( + OUTPUT ${AUTO_GENERATED_FILES} + COMMAND ${PYTHON_EXECUTABLE} + ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +unset(CODE_GENERATOR_OPTS) + +# ============================================================================= +# Target to propagate dependencies properly to lexer +# ============================================================================= +add_custom_target(pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 62e4bd9118..3bfd87b48a 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,10 +1,25 @@ -import jinja2 +import argparse +import filecmp +import logging +import os from pathlib import Path +import shutil +import subprocess +import tempfile + +import jinja2 from parser import LanguageParser import node_info import utils + +logging.basicConfig(level=logging.INFO, format='%(message)s') +parser = argparse.ArgumentParser() +parser.add_argument('--clang-format') +args = parser.parse_args() +clang_format = args.clang_format + # parse nmodl definition file and get list of abstract nodes nodes = LanguageParser("nmodl.yaml").parse_file() @@ -16,6 +31,8 @@ lstrip_blocks=True) env.filters['snake_case'] = utils.to_snake_case +updated_files = [] + for fn in templates.glob('*.[ch]pp'): if fn.name.startswith('py'): filename = basedir / 'pybind' / fn.name @@ -24,5 +41,25 @@ else: filename = basedir / 'ast' / fn.name template = env.get_template(fn.name) - with filename.open('w') as fd: - fd.write(template.render(nodes=nodes, node_info=node_info)) + content = template.render(nodes=nodes, node_info=node_info) + if filename.exists(): + # render template in temporary file + # and update target file ONLY if different + # to save a lot of build time + fd, tmp_path = tempfile.mkstemp() + os.write(fd, content.encode('utf-8')) + os.close(fd) + if clang_format: + subprocess.check_call([clang_format, '-i', tmp_path]) + if not filecmp.cmp(str(filename), tmp_path): + shutil.move(tmp_path, filename) + updated_files.append(str(fn.name)) + else: + with filename.open('w') as fd: + fd.write(content) + updated_files.append(str(fn.name)) + if clang_format: + subprocess.check_call([clang_format, '-i', filename]) + +if updated_files: + logging.info(' Updating out of date template files : %s', ' '.join(updated_files)) diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index 7e8ff9bbd0..b4e1546428 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -126,4 +126,4 @@ namespace ast { }; {% endfor %} -} +} // namespace ast From 98d0c900928690956b0426f5c61e343b36df3fec Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Tue, 5 Feb 2019 18:52:40 +0100 Subject: [PATCH 126/871] Improved python binding support : - introduced most bindings for symbols, symbol properties and symbol table - added AstLookupVisitor bindings - changed CMakeLists.txt to use OBJECT targets, this helps with target dependency resolution and should fix NOCMODL-79 - replaced stringstream in several visitors with ostream (we should check that this is done consistently now to avoid surprises in the future) - Fixed a bug in the bindings for derived classes of an abstract base class (the Visitor class hierarchy), where the base class was not declared in - created interfaces for the to_nmodl and to_json helpers - removed enum-flags and used plain scoped enums to facilitate python bindings - symtab : removed NmodlTypeFlag, StatusFlag and fixed method naming - added symbol table and ast visitor functional tests into pytest Change-Id: I83087fb916c11b9b78c9db9a795f20f1ad3b65dd NMODL Repo SHA: BlueBrain/nmodl@d93d0eeec914346748d1b2e71b29d148c2a15b62 --- cmake/nmodl/CMakeLists.txt | 5 +- src/nmodl/ast/ast_common.hpp | 1 - src/nmodl/language/templates/ast_visitor.hpp | 2 +- .../language/templates/lookup_visitor.hpp | 4 +- src/nmodl/language/templates/pyast.cpp | 8 +- src/nmodl/language/templates/pysymtab.cpp | 182 ++++++++++++++++++ src/nmodl/language/templates/pyvisitor.cpp | 35 ++-- .../language/templates/symtab_visitor.hpp | 10 +- src/nmodl/lexer/CMakeLists.txt | 17 +- src/nmodl/printer/CMakeLists.txt | 11 +- src/nmodl/printer/json_printer.hpp | 2 +- src/nmodl/pybind/CMakeLists.txt | 34 ++-- src/nmodl/pybind/pybind_utils.hpp | 94 +++++++++ src/nmodl/pybind/pynmodl.cpp | 87 +-------- src/nmodl/symtab/CMakeLists.txt | 25 ++- src/nmodl/symtab/symbol.cpp | 30 ++- src/nmodl/symtab/symbol.hpp | 32 +-- src/nmodl/symtab/symbol_properties.cpp | 18 +- src/nmodl/symtab/symbol_properties.hpp | 53 +++-- src/nmodl/symtab/symbol_table.cpp | 26 +-- src/nmodl/symtab/symbol_table.hpp | 69 ++++--- src/nmodl/utils/CMakeLists.txt | 12 +- src/nmodl/visitors/CMakeLists.txt | 87 +++++---- src/nmodl/visitors/cnexp_solve_visitor.cpp | 1 - .../differential_equation_visitor.hpp | 1 - .../visitors/local_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 9 +- src/nmodl/visitors/perf_visitor.cpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 7 +- src/nmodl/visitors/visitor_utils.cpp | 9 +- src/nmodl/visitors/visitor_utils.hpp | 7 +- test/nmodl/transpiler/pybind/conftest.py | 30 +++ test/nmodl/transpiler/pybind/test_ast.py | 47 ++++- test/nmodl/transpiler/pybind/test_symtab.py | 22 +++ test/nmodl/transpiler/symtab/symbol_table.cpp | 35 ++-- test/nmodl/transpiler/visitor/visitor.cpp | 8 +- 36 files changed, 701 insertions(+), 323 deletions(-) create mode 100644 src/nmodl/language/templates/pysymtab.cpp create mode 100644 src/nmodl/pybind/pybind_utils.hpp create mode 100644 test/nmodl/transpiler/pybind/conftest.py create mode 100644 test/nmodl/transpiler/pybind/test_symtab.py diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 895296c3da..997b00cac2 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -41,7 +41,7 @@ find_package(ClangFormat) message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) find_python_module(jinja2 REQUIRED) -find_python_module(test REQUIRED) +find_python_module(pytest REQUIRED) find_python_module(textwrap REQUIRED) find_python_module(yaml REQUIRED) @@ -76,6 +76,7 @@ set ( AUTO_GENERATED_FILES ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp ${PROJECT_SOURCE_DIR}/src/pybind/pyast.hpp ${PROJECT_SOURCE_DIR}/src/pybind/pyast.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pysymtab.cpp ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.hpp ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp @@ -83,6 +84,8 @@ set ( AUTO_GENERATED_FILES ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index fd0668a885..53172aae55 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -658,7 +658,6 @@ namespace ast { virtual bool is_diff_eq_expression() { return false; } - }; } // namespace ast diff --git a/src/nmodl/language/templates/ast_visitor.hpp b/src/nmodl/language/templates/ast_visitor.hpp index e4ae7822c1..020c51e040 100644 --- a/src/nmodl/language/templates/ast_visitor.hpp +++ b/src/nmodl/language/templates/ast_visitor.hpp @@ -9,6 +9,6 @@ class AstVisitor : public Visitor { public: {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index 44ce788a4d..8b27b66ef3 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -38,7 +38,7 @@ class AstLookupVisitor : public Visitor { nodes.clear(); } - {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pyast.cpp index ea4b72b604..ebb99a728f 100644 --- a/src/nmodl/language/templates/pyast.cpp +++ b/src/nmodl/language/templates/pyast.cpp @@ -55,8 +55,7 @@ void init_ast_module(py::module& m) { .def("get_node_name", &AST::get_node_name) .def("clone", &AST::clone) .def("get_token", &AST::get_token) - .def("set_symbol_table", &AST::set_symbol_table) - .def("get_symbol_table", &AST::get_symbol_table) + .def("get_symbol_table", &AST::get_symbol_table, py::return_value_policy::reference) .def("get_statement_block", &AST::get_statement_block) .def("negate", &AST::negate) .def("set_name", &AST::set_name) @@ -94,9 +93,12 @@ void init_ast_module(py::module& m) { .def("get_shared_ptr", &{{ node.class_name }}::get_shared_ptr) .def("get_node_type", &{{ node.class_name }}::get_node_type) .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name) + {% if node.is_data_type_node %} + .def("eval", &{{ node.class_name }}::eval) + {% endif %} .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}); {% endfor %} } -#pragma clang diagnostic pop \ No newline at end of file +#pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pysymtab.cpp b/src/nmodl/language/templates/pysymtab.cpp new file mode 100644 index 0000000000..07cd0a6779 --- /dev/null +++ b/src/nmodl/language/templates/pysymtab.cpp @@ -0,0 +1,182 @@ +#include <pybind11/pybind11.h> +#include <pybind11/iostream.h> +#include <pybind11/stl.h> + +#include "ast/ast.hpp" +#include "pybind/pybind_utils.hpp" +#include "symtab/symbol_properties.hpp" +#include "symtab/symbol.hpp" +#include "symtab/symbol_table.hpp" +#include "visitors/symtab_visitor.hpp" + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "OCDFAInspection" +{% macro var(node) -%} + {{ node.class_name | snake_case }}_ +{%- endmacro -%} + +{% macro args(children) %} + {% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} +{%- endmacro -%} + +namespace py = pybind11; + + +class PySymtabVisitor : private VisitorOStreamResources, public SymtabVisitor { +public: + using SymtabVisitor::SymtabVisitor; + + explicit PySymtabVisitor(bool update = false) : SymtabVisitor(update) { }; + PySymtabVisitor(std::string filename, bool update = false) : SymtabVisitor(filename, update) {}; + PySymtabVisitor(py::object object, bool update = false) : VisitorOStreamResources(object), + SymtabVisitor(*ostream, update) { }; +}; + +using namespace symtab; + +void init_symtab_module(py::module& m) { + py::module m_symtab = m.def_submodule("symtab"); + + py::enum_<syminfo::DeclarationType>(m_symtab, "DeclarationType") + .value("function", syminfo::DeclarationType::function) + .value("variable", syminfo::DeclarationType::variable) + .export_values(); + + py::enum_<syminfo::Scope>(m_symtab, "Scope") + .value("external", syminfo::Scope::external) + .value("global", syminfo::Scope::global) + .value("local", syminfo::Scope::local) + .value("neuron", syminfo::Scope::neuron) + .export_values(); + + py::enum_<syminfo::Status> e_status(m_symtab, "Status", py::arithmetic()); + e_status.value("created", syminfo::Status::created) + .value("from_state", syminfo::Status::from_state) + .value("globalized", syminfo::Status::globalized) + .value("inlined", syminfo::Status::inlined) + .value("localized", syminfo::Status::localized) + .value("renamed", syminfo::Status::renamed) + .value("thread_safe", syminfo::Status::thread_safe) + .export_values(); + e_status.def("__or__",[](const syminfo::Status& x, syminfo::Status y) { + return x | y; + }) + .def("__and__",[](const syminfo::Status& x, syminfo::Status y) { + return x & y; + }) + .def("__str__", &to_string<syminfo::Status>); + + py::enum_<syminfo::VariableType>(m_symtab, "VariableType") + .value("array", syminfo::VariableType::array) + .value("scalar", syminfo::VariableType::scalar) + .export_values(); + + py::enum_<syminfo::Access>(m_symtab, "Access") + .value("read", syminfo::Access::read) + .value("write", syminfo::Access::write) + .export_values(); + + py::enum_<syminfo::NmodlType> e_nmodltype(m_symtab, "NmodlType", py::arithmetic()); + e_nmodltype.value("argument", syminfo::NmodlType::argument) + .value("bbcore_pointer_var", syminfo::NmodlType::bbcore_pointer_var) + .value("constant_var", syminfo::NmodlType::constant_var) + .value("dependent_def", syminfo::NmodlType::dependent_def) + .value("derivative_block", syminfo::NmodlType::derivative_block) + .value("discrete_block", syminfo::NmodlType::discrete_block) + .value("electrode_cur_var", syminfo::NmodlType::electrode_cur_var) + .value("extern_method", syminfo::NmodlType::extern_method) + .value("extern_neuron_variable", syminfo::NmodlType::extern_neuron_variable) + .value("extern_var", syminfo::NmodlType::extern_var) + .value("vactor_def", syminfo::NmodlType::factor_def) + .value("function_block", syminfo::NmodlType::function_block) + .value("function_table_block", syminfo::NmodlType::function_table_block) + .value("global_var", syminfo::NmodlType::global_var) + .value("kinetic_block", syminfo::NmodlType::kinetic_block) + .value("linear_block", syminfo::NmodlType::linear_block) + .value("local_var", syminfo::NmodlType::local_var) + .value("man_linear_block", syminfo::NmodlType::non_linear_block) + .value("nonspecifig_cur_var", syminfo::NmodlType::nonspecific_cur_var) + .value("param_assign", syminfo::NmodlType::param_assign) + .value("partial_block", syminfo::NmodlType::partial_block) + .value("pointer_var", syminfo::NmodlType::pointer_var) + .value("prime_name", syminfo::NmodlType::prime_name) + .value("procedure_block", syminfo::NmodlType::procedure_block) + .value("range_var", syminfo::NmodlType::range_var) + .value("read_ion_var", syminfo::NmodlType::read_ion_var) + .value("section_var", syminfo::NmodlType::section_var) + .value("state_var", syminfo::NmodlType::state_var) + .value("table_dependent_var", syminfo::NmodlType::table_dependent_var) + .value("table_statement_var", syminfo::NmodlType::table_statement_var) + .value("to_solve", syminfo::NmodlType::to_solve) + .value("unit_def", syminfo::NmodlType::unit_def) + .value("useion", syminfo::NmodlType::useion) + .value("write_ion_var", syminfo::NmodlType::write_ion_var) + .export_values(); + e_nmodltype.def("__or__",[](const syminfo::NmodlType& x, syminfo::NmodlType y) { + return x | y; + }) + .def("__and__",[](const syminfo::NmodlType& x, syminfo::NmodlType y) { + return x & y; + }) + .def("__str__", &to_string<syminfo::NmodlType>); + + + py::class_<Symbol, std::shared_ptr<Symbol>> symbol(m_symtab, "Symbol"); + symbol.def(py::init<std::string, ast::AST*>()); + symbol.def("get_token", &Symbol::get_token) + .def("is_variable", &Symbol::is_variable) + .def("is_external_symbol_only", &Symbol::is_external_symbol_only) + .def("get_id", &Symbol::get_id) + .def("get_status", &Symbol::get_status) + .def("get_properties", &Symbol::get_properties) + .def("get_node", &Symbol::get_node) + .def("get_original_name", &Symbol::get_original_name) + .def("get_name", &Symbol::get_name) + .def("has_properties", &Symbol::has_properties) + .def("has_all_properties", &Symbol::has_all_properties) + .def("has_any_status", &Symbol::has_any_status) + .def("has_all_status", &Symbol::has_all_status) + .def("__str__", &Symbol::to_string); + + py::class_<SymbolTable> symbol_table(m_symtab, "SymbolTable"); + symbol_table.def(py::init<std::string, ast::AST*, bool>()); + symbol_table.def("name", &SymbolTable::name) + .def("type", &SymbolTable::type) + .def("title", &SymbolTable::title) + .def("is_method_defined", &SymbolTable::is_method_defined) + .def("get_variables", &SymbolTable::get_variables) + .def("get_variables_with_properties", &SymbolTable::get_variables_with_properties, + py::arg("properties"), py::arg("all") = false) + .def("get_variables_with_status", &SymbolTable::get_variables_with_status, + py::arg("status"), py::arg("all") = false) + .def("get_parent_table", &SymbolTable::get_parent_table) + .def("get_parent_table_name", &SymbolTable::get_parent_table_name) + .def("lookup", &SymbolTable::lookup) + .def("lookup_in_scope", &SymbolTable::lookup_in_scope) + .def("insert", &SymbolTable::insert) + .def("insert_table", &SymbolTable::insert_table) + .def("__str__", &SymbolTable::to_string); + + py::class_<SymtabVisitor, AstVisitor, PySymtabVisitor> symtab_visitor(m_symtab, + "SymtabVisitor"); + symtab_visitor.def(py::init<std::string, bool>(), py::arg("filename"), + py::arg("update") = false); + symtab_visitor.def(py::init<py::object, bool>(), py::arg("ostream"), py::arg("update") = false); + symtab_visitor.def(py::init<bool>(), py::arg("update") = false) + .def("add_model_symbol_with_property", &PySymtabVisitor::add_model_symbol_with_property) + .def("setup_symbol", &PySymtabVisitor::setup_symbol) + .def("setup_symbol_table", &PySymtabVisitor::setup_symbol_table) + .def("setup_symbol_table_for_program_block", + &PySymtabVisitor::setup_symbol_table_for_program_block) + .def("setup_symbol_table_for_global_block", + &PySymtabVisitor::setup_symbol_table_for_global_block) + .def("setup_symbol_table_for_scoped_block", + &PySymtabVisitor::setup_symbol_table_for_scoped_block) + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &PySymtabVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} + + +} +#pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index b3f28e5af3..756e879f56 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -2,8 +2,12 @@ #include <pybind11/iostream.h> #include <pybind11/pybind11.h> #include <pybind11/stl.h> + +#include "pybind/pybind_utils.hpp" #include "pybind/pyvisitor.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" @@ -30,24 +34,13 @@ void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_nam {% endfor %} -class PyNModlResources { -protected: - std::unique_ptr<py::detail::pythonbuf> buf; - std::unique_ptr<std::ostream> ostream; -public: - PyNModlResources() = default; - PyNModlResources(py::object object) : buf(new py::detail::pythonbuf(object)), - ostream(new std::ostream(buf.get())) {} -}; - - -class PyNmodlPrintVisitor : private PyNModlResources, public NmodlPrintVisitor { +class PyNmodlPrintVisitor : private VisitorOStreamResources, public NmodlPrintVisitor { public: using NmodlPrintVisitor::NmodlPrintVisitor; PyNmodlPrintVisitor() = default; PyNmodlPrintVisitor(std::string filename) : NmodlPrintVisitor(filename) {}; - PyNmodlPrintVisitor(py::object object) : PyNModlResources(object), + PyNmodlPrintVisitor(py::object object) : VisitorOStreamResources(object), NmodlPrintVisitor(*ostream) { }; }; @@ -62,14 +55,14 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - py::class_<AstVisitor, PyAstVisitor> ast_visitor(m_visitor, "AstVisitor"); + py::class_<AstVisitor, Visitor, PyAstVisitor> ast_visitor(m_visitor, "AstVisitor"); ast_visitor.def(py::init<>()) {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &AstVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} - py::class_<PyNmodlPrintVisitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor"); + py::class_<NmodlPrintVisitor, Visitor, PyNmodlPrintVisitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor"); nmodl_visitor.def(py::init<std::string>()); nmodl_visitor.def(py::init<py::object>()); nmodl_visitor.def(py::init<>()) @@ -78,6 +71,16 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - + py::class_<AstLookupVisitor, Visitor> lookup_visitor(m_visitor, "AstLookupVisitor"); + lookup_visitor.def(py::init<>()) + .def(py::init<ast::AstNodeType>()) + .def("get_nodes", &AstLookupVisitor::get_nodes) + .def("clear", &AstLookupVisitor::clear) + .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::Program*, ast::AstNodeType)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::Program*, std::vector<ast::AstNodeType>&)) &AstLookupVisitor::lookup) + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &AstLookupVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} } #pragma clang diagnostic pop \ No newline at end of file diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp index 87262d957e..2331a5833a 100644 --- a/src/nmodl/language/templates/symtab_visitor.hpp +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -18,19 +18,15 @@ class SymtabVisitor : public AstVisitor { public: explicit SymtabVisitor(bool update = false) : printer(new JSONPrinter()), update(update) {} - SymtabVisitor(std::stringstream &ss, bool update = false) : printer(new JSONPrinter(ss)), update(update) {} + SymtabVisitor(std::ostream &os, bool update = false) : printer(new JSONPrinter(os)), update(update) {} SymtabVisitor(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {} - void add_model_symbol_with_property(ast::Node* node, NmodlTypeFlag property); + void add_model_symbol_with_property(ast::Node* node, syminfo::NmodlType property); - void setup_symbol(ast::Node* node, NmodlTypeFlag property); + void setup_symbol(ast::Node* node, syminfo::NmodlType property); void setup_symbol_table(ast::AST* node, const std::string& name, bool is_global); - void setup_symbol_table(ast::Node* node, const std::string& name, NmodlTypeFlag property, bool is_global); - - void setup_program_symbol_table(ast::Node* node, const std::string& name, bool is_global); - void setup_symbol_table_for_program_block(ast::Program* node); void setup_symbol_table_for_global_block(ast::Node* node); diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 27b667c39b..34ec6edc29 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -168,12 +168,19 @@ add_custom_command( #============================================================================= # Libraries & executables #============================================================================= + +add_library(lexer_obj + OBJECT + ${LEXER_SOURCE_FILES} + ${BISON_GENERATED_SOURCE_FILES} + ${AST_SOURCE_FILES}) +set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) + add_library(lexer - STATIC - ${LEXER_SOURCE_FILES} - ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES}) -set_property(TARGET lexer PROPERTY POSITION_INDEPENDENT_CODE ON) + STATIC + $<TARGET_OBJECTS:lexer_obj>) + + add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 72537c55ae..82ae10f74d 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -13,10 +13,15 @@ set(PRINTER_SOURCE_FILES #============================================================================= # Printer library #============================================================================= + +add_library(printer_obj + OBJECT + ${PRINTER_SOURCE_FILES}) +set_property(TARGET printer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) + add_library(printer - STATIC - ${PRINTER_SOURCE_FILES}) -set_property(TARGET printer PROPERTY POSITION_INDEPENDENT_CODE ON) + STATIC + $<TARGET_OBJECTS:printer_obj>) #============================================================================= # Files for clang-format diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index e59a851ffd..8ac519d577 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -53,7 +53,7 @@ class JSONPrinter { } // Dump output to stringstream - JSONPrinter(std::stringstream& ss) : result(new std::ostream(ss.rdbuf())) { + JSONPrinter(std::ostream& os) : result(new std::ostream(os.rdbuf())) { } ~JSONPrinter() { diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index a889f7553d..ca83b645ae 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -5,40 +5,28 @@ set_source_files_properties( ${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) set(PYNMODL_SOURCES - ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp - ${PROJECT_SOURCE_DIR}/src/lexer/modtoken.cpp - ${PROJECT_SOURCE_DIR}/src/symtab/symbol.cpp - ${PROJECT_SOURCE_DIR}/src/symtab/symbol_properties.cpp - ${PROJECT_SOURCE_DIR}/src/symtab/symbol_table.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/utils/common_utils.cpp - ${PROJECT_SOURCE_DIR}/src/utils/table_data.cpp ${PROJECT_SOURCE_DIR}/src/pybind/pyast.cpp ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pysymtab.cpp ${PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) set(PYNMODL_HEADERS - ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${PROJECT_SOURCE_DIR}/src/lexer/modtoken.hpp - ${PROJECT_SOURCE_DIR}/src/symtab/symbol.hpp - ${PROJECT_SOURCE_DIR}/src/symtab/symbol_properties.hpp - ${PROJECT_SOURCE_DIR}/src/symtab/symbol_table.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp - ${PROJECT_SOURCE_DIR}/src/utils/common_utils.hpp - ${PROJECT_SOURCE_DIR}/src/utils/string_utils.hpp - ${PROJECT_SOURCE_DIR}/src/utils/table_data.hpp + ${PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp ${PROJECT_SOURCE_DIR}/src/pybind/pyast.hpp ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.hpp) -pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES}) -target_link_libraries(_nmodl PRIVATE lexer - PRIVATE visitor - PRIVATE printer - PRIVATE util) +pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES} + $<TARGET_OBJECTS:symtab_obj> + $<TARGET_OBJECTS:visitor_obj> + $<TARGET_OBJECTS:lexer_obj> + $<TARGET_OBJECTS:util_obj> + $<TARGET_OBJECTS:printer_obj> + ) add_dependencies(_nmodl pyastgen) -add_dependencies(_nmodl nmodl_parser) +add_dependencies(_nmodl lexer_obj) +add_dependencies(_nmodl util_obj) diff --git a/src/nmodl/pybind/pybind_utils.hpp b/src/nmodl/pybind/pybind_utils.hpp new file mode 100644 index 0000000000..fc4a6233de --- /dev/null +++ b/src/nmodl/pybind/pybind_utils.hpp @@ -0,0 +1,94 @@ +#pragma once + +#include <memory> +#include <pybind11/iostream.h> +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + + +namespace pybind11 { +namespace detail { + +template <typename StringType> +struct CopyFromPython { + void operator()(char* start, size_t n, StringType data) { + char* buffer; + ssize_t length; + if (PYBIND11_BYTES_AS_STRING_AND_SIZE(data.ptr(), &buffer, &length)) + pybind11_fail("Unable to extract string contents! (invalid type)"); + std::memcpy(start, buffer, n); + } +}; + +template <> +struct CopyFromPython<str> { + void operator()(char* start, size_t n, str data) { + if (PyUnicode_Check(data.ptr())) { + data = reinterpret_steal<object>(PyUnicode_AsUTF8String(data.ptr())); + if (!data) + pybind11_fail("Unable to extract string contents! (encoding issue)"); + } + CopyFromPython<bytes>()(start, n, data); + } +}; + + +template <typename StringType> +class pythonibuf: public std::streambuf { + private: + using traits_type = std::streambuf::traits_type; + + const static std::size_t put_back_ = 1; + const static std::size_t buf_sz = 1024 + put_back_; + char d_buffer[buf_sz]; + + object pyistream; + object pyread; + + // copy ctor and assignment not implemented; + // copying not allowed + pythonibuf(const pythonibuf&); + pythonibuf& operator=(const pythonibuf&); + + int_type underflow() { + if (gptr() < egptr()) { // buffer not exhausted + return traits_type::to_int_type(*gptr()); + } + + char* base = d_buffer; + char* start = base; + if (eback() == base) { + std::memmove(base, egptr() - put_back_, put_back_); + start += put_back_; + } + StringType data = pyread(buf_sz - (start - base)); + size_t n = len(data); + if (n == 0) { + return traits_type::eof(); + } + CopyFromPython<StringType>()(start, n, data); + setg(base, start, start + n); + return traits_type::to_int_type(*gptr()); + } + + + public: + pythonibuf(object pyistream) + : pyistream(pyistream) + , pyread(pyistream.attr("read")) { + char* end = d_buffer + buf_sz; + setg(end, end, end); + } +}; +} // namespace detail +} // namespace pybind11 + +class VisitorOStreamResources { +protected: + std::unique_ptr<pybind11::detail::pythonbuf> buf; + std::unique_ptr<std::ostream> ostream; +public: + VisitorOStreamResources() = default; + VisitorOStreamResources(pybind11::object object) : buf(new pybind11::detail::pythonbuf(object)), + ostream(new std::ostream(buf.get())) {} +}; diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index c69dd6f063..4ba6aac685 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -1,86 +1,11 @@ -#include "parser/nmodl_driver.hpp" #include <memory> #include <pybind11/iostream.h> #include <pybind11/pybind11.h> #include <pybind11/stl.h> - -namespace pybind11 { -namespace detail { - -template <typename StringType> -struct CopyFromPython { - void operator()(char* start, size_t n, StringType data) { - char* buffer; - ssize_t length; - if (PYBIND11_BYTES_AS_STRING_AND_SIZE(data.ptr(), &buffer, &length)) - pybind11_fail("Unable to extract string contents! (invalid type)"); - std::memcpy(start, buffer, n); - } -}; - -template <> -struct CopyFromPython<str> { - void operator()(char* start, size_t n, str data) { - if (PyUnicode_Check(data.ptr())) { - data = reinterpret_steal<object>(PyUnicode_AsUTF8String(data.ptr())); - if (!data) - pybind11_fail("Unable to extract string contents! (encoding issue)"); - } - CopyFromPython<bytes>()(start, n, data); - } -}; - - -template <typename StringType> -class pythonibuf: public std::streambuf { - private: - using traits_type = std::streambuf::traits_type; - - const static std::size_t put_back_ = 1; - const static std::size_t buf_sz = 1024 + put_back_; - char d_buffer[buf_sz]; - - object pyistream; - object pyread; - - // copy ctor and assignment not implemented; - // copying not allowed - pythonibuf(const pythonibuf&); - pythonibuf& operator=(const pythonibuf&); - - int_type underflow() { - if (gptr() < egptr()) { // buffer not exhausted - return traits_type::to_int_type(*gptr()); - } - - char* base = d_buffer; - char* start = base; - if (eback() == base) { - std::memmove(base, egptr() - put_back_, put_back_); - start += put_back_; - } - StringType data = pyread(buf_sz - (start - base)); - size_t n = len(data); - if (n == 0) { - return traits_type::eof(); - } - CopyFromPython<StringType>()(start, n, data); - setg(base, start, start + n); - return traits_type::to_int_type(*gptr()); - } - - - public: - pythonibuf(object pyistream) - : pyistream(pyistream) - , pyread(pyistream.attr("read")) { - char* end = d_buffer + buf_sz; - setg(end, end, end); - } -}; -} // namespace detail -} // namespace pybind11 +#include "parser/nmodl_driver.hpp" +#include "pybind/pybind_utils.hpp" +#include "visitors/visitor_utils.hpp" namespace py = pybind11; @@ -106,6 +31,7 @@ class PyDriver: public nmodl::Driver { // forward declaration of submodule init functions void init_visitor_module(py::module& m); void init_ast_module(py::module& m); +void init_symtab_module(py::module& m); PYBIND11_MODULE(_nmodl, m_nmodl) { py::class_<PyDriver> nmodl_driver(m_nmodl, "Driver"); @@ -116,6 +42,11 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { .def("parse_stream", &PyDriver::parse_stream) .def("ast", &PyDriver::ast); + m_nmodl.def("to_nmodl", nmodl::to_nmodl); + m_nmodl.def("to_json", nmodl::to_json, + py::arg("node"), py::arg("compact") = false); + init_visitor_module(m_nmodl); init_ast_module(m_nmodl); + init_symtab_module(m_nmodl); } diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index 870582ad6d..254ecd3a3b 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -1,22 +1,31 @@ #============================================================================= # Visitor sources #============================================================================= -set(SYMTAB_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp +set(SYMTAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp ) +set(SYMTAB_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp + ) + #============================================================================= # Symbol table library #============================================================================= +add_library(symtab_obj + OBJECT + ${SYMTAB_SOURCES} ${SYMTAB_HEADERS}) +set_property(TARGET symtab_obj PROPERTY POSITION_INDEPENDENT_CODE ON) + +add_dependencies(symtab_obj lexer_obj) + add_library(symtab - STATIC - ${SYMTAB_SOURCE_FILES} -) + STATIC + $<TARGET_OBJECTS:symtab_obj>) add_dependencies(symtab lexer) @@ -24,7 +33,7 @@ add_dependencies(symtab lexer) # Files for clang-format #============================================================================= set(FILES_FOR_CLANG_FORMAT - ${SYMTAB_SOURCE_FILES} + ${SYMTAB_SOURCES} ${SYMTAB_HEADERS} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) \ No newline at end of file diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 9264aac1c9..479bf9b88e 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -1,6 +1,9 @@ +#include "fmt/format.h" + #include "symtab/symbol.hpp" using namespace syminfo; +using namespace fmt::literals; namespace symtab { @@ -17,28 +20,28 @@ namespace symtab { /// check if symbol has any of the common properties - bool Symbol::has_properties(NmodlTypeFlag new_properties) { + bool Symbol::has_properties(NmodlType new_properties) { return static_cast<bool>(properties & new_properties); } /// check if symbol has all of the given properties - bool Symbol::has_all_properties(NmodlTypeFlag new_properties) { + bool Symbol::has_all_properties(NmodlType new_properties) { return ((properties & new_properties) == new_properties); } /// check if symbol has any of the status - bool Symbol::has_any_status(StatusFlag new_status) { + bool Symbol::has_any_status(Status new_status) { return static_cast<bool>(status & new_status); } /// check if symbol has all of the status - bool Symbol::has_all_status(StatusFlag new_status) { + bool Symbol::has_all_status(Status new_status) { return ((status & new_status) == new_status); } /// add new properties to symbol with bitwise or - void Symbol::combine_properties(NmodlTypeFlag new_properties) { + void Symbol::add_properties(NmodlType new_properties) { properties |= new_properties; } @@ -46,10 +49,6 @@ namespace symtab { properties |= property; } - void Symbol::add_property(NmodlTypeFlag property) { - properties |= property; - } - /** Prime variable will appear in different block and could have * multiple derivative orders. We have to store highest order. * @@ -64,7 +63,7 @@ namespace symtab { /// check if symbol is of variable type in nmodl bool Symbol::is_variable() { // clang-format off - NmodlTypeFlag var_properties = NmodlType::local_var + NmodlType var_properties = NmodlType::local_var | NmodlType::global_var | NmodlType::range_var | NmodlType::param_assign @@ -83,4 +82,15 @@ namespace symtab { return has_properties(var_properties); } + std::string Symbol::to_string() { + std::string s(name); + if (properties != NmodlType::empty) { + s += " [Properties : {}]"_format(::to_string(properties)); + } + if (status != Status::empty) { + s += " [Status : {}]"_format(::to_string(status)); + } + return s; + } + } // namespace symtab diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index f397fda798..676d4b9e6e 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -19,7 +19,7 @@ namespace symtab { * * Symbol table generator pass visit the AST and insert symbol for * each node into symbol table. Symbol could appear multiple times - * in a block or different global blocks. NmodlTypeFlag object has all + * in a block or different global blocks. NmodlType object has all * nmodl properties information. * * \todo Multiple tokens (i.e. location information) for symbol should @@ -49,10 +49,10 @@ namespace symtab { ModToken token; /// properties of symbol from whole mod file - NmodlTypeFlag properties{flags::empty}; + syminfo::NmodlType properties{syminfo::NmodlType::empty}; /// status of symbol during various passes - StatusFlag status{flags::empty}; + syminfo::Status status{syminfo::Status::empty}; /// scope of the symbol (block name) std::string scope; @@ -121,11 +121,11 @@ namespace symtab { return scope; } - NmodlTypeFlag get_properties() { + syminfo::NmodlType get_properties() { return properties; } - StatusFlag get_status() { + syminfo::Status get_status() { return status; } @@ -160,33 +160,31 @@ namespace symtab { bool is_external_symbol_only(); /// \todo : rename to has_any_property - bool has_properties(NmodlTypeFlag new_properties); + bool has_properties(syminfo::NmodlType new_properties); - bool has_all_properties(NmodlTypeFlag new_properties); + bool has_all_properties(syminfo::NmodlType new_properties); - bool has_any_status(StatusFlag new_status); + bool has_any_status(syminfo::Status new_status); - bool has_all_status(StatusFlag new_status); + bool has_all_status(syminfo::Status new_status); - void combine_properties(NmodlTypeFlag new_properties); + void add_properties(syminfo::NmodlType new_properties); void add_property(syminfo::NmodlType property); - void add_property(NmodlTypeFlag property); - void inlined() { status |= syminfo::Status::inlined; } - void created() { + void mark_created() { status |= syminfo::Status::created; } - void renamed() { + void mark_renamed() { status |= syminfo::Status::renamed; } - void localized() { + void mark_localized() { status |= syminfo::Status::localized; } @@ -195,7 +193,7 @@ namespace symtab { } void created_from_state() { - created(); + mark_created(); status |= syminfo::Status::from_state; } @@ -246,6 +244,8 @@ namespace symtab { renamed_from = new_name; } + std::string to_string(); + bool is_variable(); }; diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 7e94f65855..9a9e1bbbda 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -7,18 +7,20 @@ using namespace syminfo; /// check if any property is set -bool has_property(const NmodlTypeFlag& obj, NmodlType property) { +bool has_property(const NmodlType& obj, NmodlType property) { return static_cast<bool>(obj & property); } -bool has_status(const StatusFlag& obj, Status state) { +bool has_status(const Status& obj, Status state) { return static_cast<bool>(obj & state); } /// helper function to convert properties to string -std::vector<std::string> to_string_vector(const NmodlTypeFlag& obj) { +std::vector<std::string> to_string_vector(const NmodlType& obj) { std::vector<std::string> properties; + // do nothing for NmodlType::empty + if (has_property(obj, NmodlType::local_var)) { properties.emplace_back("local"); } @@ -68,7 +70,7 @@ std::vector<std::string> to_string_vector(const NmodlTypeFlag& obj) { } if (has_property(obj, NmodlType::nonspecific_cur_var)) { - properties.emplace_back("nonspe_cur"); + properties.emplace_back("nonspecific_cur_var"); } if (has_property(obj, NmodlType::electrode_cur_var)) { @@ -158,9 +160,11 @@ std::vector<std::string> to_string_vector(const NmodlTypeFlag& obj) { return properties; } -std::vector<std::string> to_string_vector(const StatusFlag& obj) { +std::vector<std::string> to_string_vector(const Status& obj) { std::vector<std::string> status; + // do nothing for Status::empty + if (has_status(obj, Status::localized)) { status.emplace_back("localized"); } @@ -192,13 +196,13 @@ std::vector<std::string> to_string_vector(const StatusFlag& obj) { return status; } -std::ostream& operator<<(std::ostream& os, const NmodlTypeFlag& obj) { +std::ostream& operator<<(std::ostream& os, const NmodlType& obj) { os << to_string(obj); return os; } -std::ostream& operator<<(std::ostream& os, const StatusFlag& obj) { +std::ostream& operator<<(std::ostream& os, const Status& obj) { os << to_string(obj); return os; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 992b532b5b..c33a0254e5 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -3,13 +3,15 @@ #include <sstream> -#include "flags/flags.hpp" #include "utils/string_utils.hpp" +//@todo : error from pybind if std::underlying_typ is used +using enum_type = long long; + namespace syminfo { /** kind of symbol */ - enum class DeclarationType : long long { + enum class DeclarationType : enum_type { /** variable */ variable, @@ -18,7 +20,7 @@ namespace syminfo { }; /** scope within a mod file */ - enum class Scope : long long { + enum class Scope : enum_type { /** local variable */ local, @@ -33,7 +35,10 @@ namespace syminfo { }; /** state during various compiler passes */ - enum class Status : long long { + enum class Status : enum_type { + + empty = 0, + /** converted to local */ localized = 1L << 0, @@ -57,7 +62,7 @@ namespace syminfo { }; /** usage of mod file as array or scalar */ - enum class VariableType : long long { + enum class VariableType : enum_type { /** scalar / single value */ scalar, @@ -66,7 +71,7 @@ namespace syminfo { }; /** variable usage within a mod file */ - enum class Access : long long { + enum class Access : enum_type { /** variable is ready only */ read = 1L << 0, @@ -94,7 +99,10 @@ namespace syminfo { * \todo Reaching max limit (31), need to refactor all block types * into separate type */ - enum class NmodlType : long long { + enum class NmodlType : enum_type { + + empty = 0, + /** Local Variable */ local_var = 1L << 0, @@ -200,18 +208,31 @@ namespace syminfo { } // namespace syminfo -ALLOW_FLAGS_FOR_ENUM(syminfo::NmodlType) -ALLOW_FLAGS_FOR_ENUM(syminfo::Access) -ALLOW_FLAGS_FOR_ENUM(syminfo::Status) -using NmodlTypeFlag = flags::flags<syminfo::NmodlType>; -using StatusFlag = flags::flags<syminfo::Status>; +template <typename T> +inline T operator|(T lhs, T rhs) { + using utype = enum_type; + return static_cast<T>(static_cast<utype>(lhs) | static_cast<utype>(rhs)); +} + +template <typename T> +inline T operator&(T lhs, T rhs) { + using utype = enum_type; + return static_cast<T>(static_cast<utype>(lhs) & static_cast<utype>(rhs)); +} + +template <typename T> +inline T& operator|=(T& lhs, T rhs) { + lhs = lhs | rhs; + return lhs; +} + -std::ostream& operator<<(std::ostream& os, const NmodlTypeFlag& obj); -std::ostream& operator<<(std::ostream& os, const StatusFlag& obj); +std::ostream& operator<<(std::ostream& os, const syminfo::NmodlType& obj); +std::ostream& operator<<(std::ostream& os, const syminfo::Status& obj); -std::vector<std::string> to_string_vector(const NmodlTypeFlag& obj); -std::vector<std::string> to_string_vector(const StatusFlag& obj); +std::vector<std::string> to_string_vector(const syminfo::NmodlType& obj); +std::vector<std::string> to_string_vector(const syminfo::Status& obj); template <typename T> std::string to_string(const T& obj) { diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 72ec11dd10..a6b61507ce 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -10,13 +10,13 @@ using namespace syminfo; namespace symtab { - int Table::counter = 0; + int SymbolTable::Table::counter = 0; /** * Insert symbol into current symbol table. There are certain * cases where we were getting re-insertion errors. */ - void Table::insert(const std::shared_ptr<Symbol>& symbol) { + void SymbolTable::Table::insert(const std::shared_ptr<Symbol>& symbol) { std::string name = symbol->get_name(); if (lookup(name) != nullptr) { throw std::runtime_error("Trying to re-insert symbol " + name); @@ -26,7 +26,7 @@ namespace symtab { } - std::shared_ptr<Symbol> Table::lookup(const std::string& name) const { + std::shared_ptr<Symbol> SymbolTable::Table::lookup(const std::string& name) const { for (const auto& symbol : symbols) { if (symbol->get_name() == name) { return symbol; @@ -84,7 +84,7 @@ namespace symtab { /// return all symbol having any of the provided properties std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( - NmodlTypeFlag properties, + NmodlType properties, bool all) { std::vector<std::shared_ptr<Symbol>> variables; for (auto& symbol : table.symbols) { @@ -102,8 +102,8 @@ namespace symtab { } /// return all symbol which has all "with" properties and none of the "without" properties - std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables(NmodlTypeFlag with, - NmodlTypeFlag without) { + std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables(NmodlType with, + NmodlType without) { auto variables = get_variables_with_properties(with, true); decltype(variables) result; for (auto& variable : variables) { @@ -115,7 +115,7 @@ namespace symtab { } - std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_status(StatusFlag status, + std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_status(Status status, bool all) { std::vector<std::shared_ptr<Symbol>> variables; for (auto& symbol : table.symbols) { @@ -242,7 +242,7 @@ namespace symtab { std::shared_ptr<Symbol> ModelSymbolTable::update_mode_insert( const std::shared_ptr<Symbol>& symbol) { symbol->set_scope(current_symtab->name()); - symbol->created(); + symbol->mark_created(); std::string name = symbol->get_name(); auto search_symbol = lookup(name); @@ -255,7 +255,7 @@ namespace symtab { /// for global scope just combine properties if (current_symtab->global_scope()) { - search_symbol->combine_properties(symbol->get_properties()); + search_symbol->add_properties(symbol->get_properties()); return search_symbol; } @@ -313,7 +313,7 @@ namespace symtab { if (search_symbol->has_properties(symbol->get_properties())) { emit_message(symbol, search_symbol, true); } else { - search_symbol->combine_properties(symbol->get_properties()); + search_symbol->add_properties(symbol->get_properties()); } return search_symbol; } @@ -443,7 +443,7 @@ namespace symtab { //============================================================================= - void Table::print(std::stringstream& stream, std::string title, int indent) { + void SymbolTable::Table::print(std::stringstream& stream, std::string title, int indent) { if (!symbols.empty()) { TableData table; table.title = std::move(title); @@ -467,8 +467,8 @@ namespace symtab { name += "[" + std::to_string(symbol->get_length()) + "]"; } auto position = symbol->get_token().position(); - auto properties = to_string(symbol->get_properties()); - auto status = to_string(symbol->get_status()); + auto properties = ::to_string(symbol->get_properties()); + auto status = ::to_string(symbol->get_status()); auto reads = std::to_string(symbol->get_read_count()); std::string value; auto sym_value = symbol->get_value(); diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 64b0e0ebc2..a5d73f164c 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -9,32 +9,6 @@ namespace symtab { - /** - * \class Table - * \brief Helper class for implementing symbol table - * - * Table is used to store information about every block construct - * encountered in the nmodl file. Each symbol has name but for fast lookup, - * we create map with the associated name. - * - * \todo Re-implement pretty printing - */ - class Table { - static int counter; - - public: - /// map of symbol name and associated symbol for faster lookup - std::vector<std::shared_ptr<Symbol>> symbols; - - /// insert new symbol into table - void insert(const std::shared_ptr<Symbol>& symbol); - - /// check if symbol with given name exist - std::shared_ptr<Symbol> lookup(const std::string& name) const; - - /// pretty print - void print(std::stringstream& stream, std::string title, int indent); - }; /** * \class SymbolTable @@ -60,6 +34,32 @@ namespace symtab { */ class SymbolTable { private: + /** + * \class Table + * \brief Helper class for implementing symbol table + * + * Table is used to store information about every block construct + * encountered in the nmodl file. Each symbol has name but for fast lookup, + * we create map with the associated name. + * + * \todo Re-implement pretty printing + */ + class Table { + static int counter; + + public: + /// map of symbol name and associated symbol for faster lookup + std::vector<std::shared_ptr<Symbol>> symbols; + + /// insert new symbol into table + void insert(const std::shared_ptr<Symbol>& symbol); + + /// check if symbol with given name exist + std::shared_ptr<Symbol> lookup(const std::string& name) const; + + /// pretty print + void print(std::stringstream& stream, std::string title, int indent); + }; /// name of the block std::string symtab_name; @@ -138,13 +138,14 @@ namespace symtab { std::shared_ptr<Symbol> lookup_in_scope(const std::string& name) const; - std::vector<std::shared_ptr<Symbol>> get_variables(NmodlTypeFlag with, - NmodlTypeFlag without); + std::vector<std::shared_ptr<Symbol>> get_variables(syminfo::NmodlType with, + syminfo::NmodlType without); - std::vector<std::shared_ptr<Symbol>> get_variables_with_properties(NmodlTypeFlag properties, - bool all = false); + std::vector<std::shared_ptr<Symbol>> get_variables_with_properties( + syminfo::NmodlType properties, + bool all = false); - std::vector<std::shared_ptr<Symbol>> get_variables_with_status(StatusFlag status, + std::vector<std::shared_ptr<Symbol>> get_variables_with_status(syminfo::Status status, bool all = false); bool under_global_scope(); @@ -152,6 +153,12 @@ namespace symtab { void insert_table(const std::string& name, std::shared_ptr<SymbolTable> table); void print(std::stringstream& ss, int level); + + std::string to_string() { + std::stringstream s; + print(s, 0); + return s.str(); + } }; /** diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 7099841031..3e10d3fcff 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -15,11 +15,15 @@ set(UTIL_SOURCE_FILES #============================================================================= # Symbol table library #============================================================================= + +add_library(util_obj + OBJECT + ${UTIL_SOURCE_FILES}) +set_property(TARGET util_obj PROPERTY POSITION_INDEPENDENT_CODE ON) + add_library(util - STATIC - ${UTIL_SOURCE_FILES} -) -set_property(TARGET util PROPERTY POSITION_INDEPENDENT_CODE ON) + STATIC + $<TARGET_OBJECTS:util_obj>) #============================================================================= # Files for clang-format diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 8d006af6b6..a192fe6ab4 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -1,55 +1,64 @@ #============================================================================= # Visitor sources #============================================================================= -set(VISITOR_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/ast_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp +set(VISITOR_SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/ast_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ) +set(VISITOR_HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp + ) + set_source_files_properties( - ${VISITOR_SOURCE_FILES} + ${VISITOR_SOURCES} ${VISITOR_HEADERS} PROPERTIES GENERATED TRUE ) #============================================================================= # Visitor library and executable #============================================================================= +add_library(visitor_obj + OBJECT + ${VISITOR_SOURCES} ${VISITOR_HEADERS}) +set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) + +add_dependencies(visitor_obj lexer_obj) + add_library(visitor STATIC - ${VISITOR_SOURCE_FILES}) -set_property(TARGET visitor PROPERTY POSITION_INDEPENDENT_CODE ON) + $<TARGET_OBJECTS:visitor_obj>) add_dependencies(visitor lexer util) @@ -61,7 +70,7 @@ target_link_libraries(nmodl_visitor printer visitor symtab util lexer) #============================================================================= set(FILES_FOR_CLANG_FORMAT ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp - ${VISITOR_SOURCE_FILES} + ${VISITOR_SOURCES} ${VISITOR_HEADERS} ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 0f30e82ddd..08eadce4f8 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -41,7 +41,6 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { auto name = std::dynamic_pointer_cast<VarName>(lhs)->get_name(); if (name->is_prime_name()) { - auto equation = nmodl::to_nmodl(node); diffeq::Driver diffeq_driver; diff --git a/src/nmodl/visitors/differential_equation_visitor.hpp b/src/nmodl/visitors/differential_equation_visitor.hpp index 45745972fa..c5115a6b59 100644 --- a/src/nmodl/visitors/differential_equation_visitor.hpp +++ b/src/nmodl/visitors/differential_equation_visitor.hpp @@ -16,7 +16,6 @@ class DifferentialEquationVisitor : public AstVisitor { private: - std::vector<ast::DiffEqExpression*> equations; public: diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index e5fe89d21c..b5a6a130a5 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -56,7 +56,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { rename_visitor.visit_statement_block(node); auto symbol = symtab->lookup_in_scope(name); symbol->set_name(new_name); - symbol->renamed(); + symbol->mark_renamed(); } } } diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 6ce8b9d918..d9b4d5c59a 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -57,8 +57,7 @@ bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { std::vector<std::string> LocalizeVisitor::variables_to_optimize() { // clang-format off - using NmodlType = NmodlType; - const NmodlTypeFlag excluded_var_properties = NmodlType::extern_var + const NmodlType excluded_var_properties = NmodlType::extern_var | NmodlType::extern_neuron_variable | NmodlType::read_ion_var | NmodlType::write_ion_var @@ -69,7 +68,7 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { | NmodlType::electrode_cur_var | NmodlType::section_var; - NmodlTypeFlag global_var_properties = NmodlType::range_var + NmodlType global_var_properties = NmodlType::range_var | NmodlType::dependent_def | NmodlType::param_assign; // clang-format on @@ -131,13 +130,13 @@ void LocalizeVisitor::visit_program(Program* node) { } /// mark variable as localized in global symbol table - symbol->localized(); + symbol->mark_localized(); /// insert new symbol into symbol table auto symtab = statement_block->get_symbol_table(); auto new_symbol = std::make_shared<Symbol>(varname, variable); new_symbol->add_property(NmodlType::local_var); - new_symbol->created(); + new_symbol->mark_created(); symtab->insert(new_symbol); } } diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 96628068e1..af2e998e2e 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -209,7 +209,7 @@ void PerfVisitor::count_variables() { /// assigned block are not treated as range num_instance_variables = 0; - NmodlTypeFlag property = NmodlType::range_var | NmodlType::dependent_def | NmodlType::state_var; + NmodlType property = NmodlType::range_var | NmodlType::dependent_def | NmodlType::state_var; auto variables = current_symtab->get_variables_with_properties(property); for (auto& variable : variables) { diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index f364d386a6..e4d2c4c660 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -1,6 +1,7 @@ #include <utility> #include "lexer/token_mapping.hpp" +#include "visitors/symtab_visitor.hpp" using namespace ast; using namespace symtab; @@ -8,7 +9,7 @@ using namespace syminfo; // create symbol for given node static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, - NmodlTypeFlag property, + NmodlType property, bool under_state_block) { ModToken token; auto token_ptr = node->get_token(); @@ -35,7 +36,7 @@ static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, /// helper function to setup/insert symbol into symbol table /// for the ast nodes which are of variable types -void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { +void SymtabVisitor::setup_symbol(Node* node, NmodlType property) { std::shared_ptr<Symbol> symbol; auto name = node->get_node_name(); @@ -113,7 +114,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlTypeFlag property) { } -void SymtabVisitor::add_model_symbol_with_property(Node* node, NmodlTypeFlag property) { +void SymtabVisitor::add_model_symbol_with_property(Node* node, NmodlType property) { auto token = node->get_token(); auto name = node->get_node_name(); auto symbol = std::make_shared<Symbol>(name, node, *token); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 6cdbe857d8..92e055c6fe 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -77,9 +77,9 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { return statement; } -namespace nmodl { +namespace nmodl { - std::string to_nmodl(ast::AST *node) { + std::string to_nmodl(ast::AST* node) { std::stringstream stream; NmodlPrintVisitor v(stream); node->accept(&v); @@ -87,12 +87,13 @@ namespace nmodl { } - std::string to_json(ast::AST *node, bool compact) { + std::string to_json(ast::AST* node, bool compact) { std::stringstream stream; JSONVisitor v(stream); v.compact_json(compact); node->accept(&v); + v.flush(); return stream.str(); } -} \ No newline at end of file +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index d98b01bb38..fd7161064e 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -29,10 +29,11 @@ std::shared_ptr<ast::Statement> create_statement(const std::string& code_stateme namespace nmodl { /** Given AST node, return the NMODL string representation */ - std::string to_nmodl(ast::AST *node); + std::string to_nmodl(ast::AST* node); /** Given AST node, return the JSON string representation */ - std::string to_json(ast::AST *node, bool compact = false); -} + std::string to_json(ast::AST* node, bool compact = false); + +} // namespace nmodl #endif diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/pybind/conftest.py new file mode 100644 index 0000000000..cc52c35626 --- /dev/null +++ b/test/nmodl/transpiler/pybind/conftest.py @@ -0,0 +1,30 @@ +import pytest +import nmodl + +CHANNEL = """NEURON { + SUFFIX NaTs2_t + RANGE mInf, hInf +} + +ASSIGNED { + mInf + hInf +} + +STATE { + m + h +} + +DERIVATIVE states { + m' = mInf-m + h' = hInf-h +} +""" + + +@pytest.fixture +def ch_ast(): + d = nmodl.Driver() + d.parse_string(CHANNEL) + return d.ast() \ No newline at end of file diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py index 8acc48ee16..c501593d9b 100644 --- a/test/nmodl/transpiler/pybind/test_ast.py +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -1,9 +1,50 @@ -from nmodl import ast, visitor - +import nmodl import pytest +from nmodl import ast, visitor class TestAST(object): - def test_empty_program(self): pnode = ast.Program() assert str(pnode) == '{"Program":[]}' + + +def test_lookup_visitor(ch_ast): + lookup_visitor = visitor.AstLookupVisitor() + eqs = lookup_visitor.lookup(ch_ast, ast.AstNodeType.DIFF_EQ_EXPRESSION) + eq_str = nmodl.to_nmodl(eqs[0]) + assert eq_str == "m' = mInf-m" + + +def test_lookup_visitor_constructor(ch_ast): + lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.DIFF_EQ_EXPRESSION) + ch_ast.accept(lookup_visitor) + eqs = lookup_visitor.get_nodes() + eq_str = nmodl.to_nmodl(eqs[0]) + eq_json = nmodl.to_json(eqs[0], True) + assert eq_str == "m' = mInf-m" + assert '"DiffEqExpression":[{"BinaryExpression"' in eq_json + + +def test_custom_visitor(ch_ast): + + class StateVisitor(visitor.AstVisitor): + def __init__(self): + visitor.AstVisitor.__init__(self) + self.in_state = False + self.states = [] + + def visit_state_block(self, node): + self.in_state = True + node.visit_children(self) + self.in_state = False + + def visit_name(self, node): + if self.in_state: + self.states.append(nmodl.to_nmodl(node)) + + myvisitor = StateVisitor() + ch_ast.accept(myvisitor) + + assert len(myvisitor.states) is 2 + assert myvisitor.states[0] == "m" + assert myvisitor.states[1] == "h" diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py new file mode 100644 index 0000000000..24af87db60 --- /dev/null +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -0,0 +1,22 @@ +from nmodl import ast, visitor, symtab +import nmodl +import pytest + +def test_symtab(ch_ast): + v = symtab.SymtabVisitor() + v.visit_program(ch_ast) + s = ch_ast.get_symbol_table() + m = s.lookup('m') + assert m is not None + assert m.get_name() == "m" + assert m.has_all_properties(symtab.NmodlType.state_var | symtab.NmodlType.prime_name) is True + + mInf = s.lookup('mInf') + assert mInf is not None + assert mInf.get_name() == "mInf" + assert mInf.has_properties(symtab.NmodlType.range_var) is True + assert mInf.has_properties(symtab.NmodlType.local_var) is False + + variables = s.get_variables_with_properties(symtab.NmodlType.range_var, True) + assert len(variables) == 2 + assert str(variables[0]) == "mInf [Properties : range dependent_def]" diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index b6d14fddda..bb06e42c1f 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -10,16 +10,16 @@ using namespace symtab; using namespace syminfo; -extern bool has_property(const NmodlTypeFlag& obj, NmodlType property); +extern bool has_property(const NmodlType& obj, NmodlType property); //============================================================================= // Symbol properties test //============================================================================= SCENARIO("Symbol properties can be added and converted to string") { - NmodlTypeFlag prop1{flags::empty}; - NmodlTypeFlag prop2 = NmodlType::local_var; - NmodlTypeFlag prop3 = NmodlType::global_var; + NmodlType prop1{NmodlType::empty}; + NmodlType prop2 = NmodlType::local_var; + NmodlType prop3 = NmodlType::global_var; GIVEN("A empty property") { WHEN("converted to string") { @@ -33,13 +33,13 @@ SCENARIO("Symbol properties can be added and converted to string") { } } WHEN("adding another empty property") { - NmodlTypeFlag result = prop1 | prop1; + NmodlType result = prop1 | prop1; THEN("to_string still returns empty string") { REQUIRE(to_string(result).empty()); } } WHEN("added some other property") { - NmodlTypeFlag result = prop1 | prop2; + NmodlType result = prop1 | prop2; THEN("to_string returns added property") { REQUIRE(to_string(result) == "local"); } @@ -50,7 +50,7 @@ SCENARIO("Symbol properties can be added and converted to string") { } } WHEN("added multiple properties") { - NmodlTypeFlag result = prop1 | prop2 | prop3; + NmodlType result = prop1 | prop2 | prop3; result |= NmodlType::write_ion_var; THEN("to_string returns all added properties") { REQUIRE_THAT(to_string(result), Catch::Contains("local")); @@ -66,6 +66,17 @@ SCENARIO("Symbol properties can be added and converted to string") { } } } + WHEN("properties manipulated with bitwise operators") { + THEN("& applied correctly") { + NmodlType result1 = prop2 & prop2; + NmodlType result2 = prop1 & prop2; + NmodlType result3 = prop1 & prop2 & prop3; + REQUIRE(to_string(result1) == "local"); + REQUIRE(to_string_vector(result2).empty()); + REQUIRE(result2 == NmodlType::empty); + REQUIRE(result3 == NmodlType::empty); + } + } } } @@ -74,9 +85,9 @@ SCENARIO("Symbol properties can be added and converted to string") { //============================================================================= SCENARIO("Symbol operations") { - NmodlTypeFlag property1 = NmodlType::argument; - NmodlTypeFlag property2 = NmodlType::range_var; - NmodlTypeFlag property3 = NmodlType::discrete_block; + NmodlType property1 = NmodlType::argument; + NmodlType property2 = NmodlType::range_var; + NmodlType property3 = NmodlType::discrete_block; GIVEN("A symbol") { ModToken token(true); Symbol symbol("alpha", token); @@ -111,10 +122,10 @@ SCENARIO("Symbol operations") { } } WHEN("combined properties") { - NmodlTypeFlag property = NmodlType::factor_def | NmodlType::global_var; + NmodlType property = NmodlType::factor_def | NmodlType::global_var; THEN("symbol has union of all properties") { REQUIRE(symbol.has_properties(property) == false); - symbol.combine_properties(property); + symbol.add_properties(property); REQUIRE(symbol.has_properties(property) == true); property |= symbol.get_properties(); REQUIRE(symbol.get_properties() == property); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index b91b20eb45..c5216c61b6 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -25,6 +25,7 @@ using json = nlohmann::json; using namespace ast; using namespace nmodl; +using namespace syminfo; //============================================================================= // Verbatim visitor tests @@ -174,8 +175,6 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { v.visit_program(ast.get()); auto symtab = ast->get_model_symbol_table(); - using namespace syminfo; - THEN("Can lookup for defined variables") { auto symbol = symtab->lookup("m"); REQUIRE(symbol->has_properties(NmodlType::dependent_def)); @@ -1872,7 +1871,8 @@ SCENARIO("Running visitor passes multiple time") { // Ast lookup visitor tests //============================================================================= -std::vector<std::shared_ptr<AST>> run_lookup_visitor(Program* node, std::vector<AstNodeType>& types) { +std::vector<std::shared_ptr<AST>> run_lookup_visitor(Program* node, + std::vector<AstNodeType>& types) { AstLookupVisitor v; return v.lookup(node, types); } @@ -1958,7 +1958,7 @@ std::vector<std::string> run_differential_equation_visitor(const std::string& te auto equations = v.get_equations(); std::vector<std::string> result; - for(const auto& equation : equations) { + for (const auto& equation : equations) { result.push_back(to_nmodl(equation)); } return result; From 9547980f88f6f0b7c95044b17fa0cc107cf31565 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 10 Feb 2019 22:28:17 +0100 Subject: [PATCH 127/871] Preparation for open source : - add LGPL v3 license file - update LICENSE file - add make install targets - remove include of FLEX_INCLUDE_DIRS - add contributing guidelines based on nexus project - update setup.py with version and project name - fixed setup.py and CMakeLists to place python targets into correct destinations Change-Id: Iaeee7e48d12ceec63003496b4898146db3bd9cc2 NMODL Repo SHA: BlueBrain/nmodl@3570043d85079b29ef09757b1e8b455070358877 --- CONTRIBUTING.md | 99 ++++++++++++++++++ LGPL.txt | 165 ++++++++++++++++++++++++++++++ cmake/nmodl/FlexHelper.cmake | 2 - nmodl/__init__.py | 2 +- setup.py | 23 +++-- src/nmodl/lexer/CMakeLists.txt | 6 ++ src/nmodl/nmodl/CMakeLists.txt | 6 ++ src/nmodl/nmodl/LICENSE | 37 ++++--- src/nmodl/parser/CMakeLists.txt | 6 ++ src/nmodl/pybind/CMakeLists.txt | 6 ++ src/nmodl/visitors/CMakeLists.txt | 6 ++ 11 files changed, 330 insertions(+), 28 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 LGPL.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..19b6574087 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,99 @@ +# Contributing to the NMODL + +We would love for you to contribute to the NMODL and help make it better than it is today. As a +contributor, here are the guidelines we would like you to follow: + - [Question or Problem?](#question) + - [Issues and Bugs](#issue) + - [Feature Requests](#feature) + - [Submission Guidelines](#submit) + +## <a name="question"></a> Got a Question? + +Please do not hesitate to raise an issue on [github project page][github]. + +## <a name="issue"></a> Found a Bug? + +If you find a bug in the source code, you can help us by [submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can [submit a Pull Request](#submit-pr) with a fix. + +## <a name="feature"></a> Missing a Feature? + +You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub Repository. If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it. + +Please consider what kind of change it is: + +* For a **Major Feature**, first open an issue and outline your proposal so that it can be +discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, +and help you to craft the change so that it is successfully accepted into the project. +* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). + +## <a name="submit"></a> Submission Guidelines + +### <a name="submit-issue"></a> Submitting an Issue + +Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the +discussion might inform you of workarounds readily available. + +We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will need as much information as possible, and preferably a sample MOD file or Python example. + +### <a name="submit-pr"></a> Submitting a Pull Request (PR) + +When you wish to contribute to the code base, please consider the following guidelines: + +* Make a [fork](https://guides.github.com/activities/forking/) of this repository. +* Make your changes in your fork, in a new git branch: + + ```shell + git checkout -b my-fix-branch master + ``` +* Create your patch, **including appropriate test cases**. +* Run the full test suite, and ensure that all tests pass. +* Commit your changes using a descriptive commit message. + + ```shell + git commit -a + ``` +* Push your branch to GitHub: + + ```shell + git push origin my-fix-branch + ``` +* In GitHub, send a Pull Request to the `master` branch of the upstream repository of the relevant component. +* If we suggest changes then: + * Make the required updates. + * Re-run the test suites to ensure tests are still passing. + * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): + + ```shell + git rebase master -i + git push -f + ``` + +That’s it! Thank you for your contribution! + +#### After your pull request is merged + +After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) +repository: + +* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: + + ```shell + git push origin --delete my-fix-branch + ``` +* Check out the master branch: + + ```shell + git checkout master -f + ``` +* Delete the local branch: + + ```shell + git branch -D my-fix-branch + ``` +* Update your master with the latest upstream version: + + ```shell + git pull --ff upstream master + ``` + +[github]: https://github.com/BlueBrain/nmodl diff --git a/LGPL.txt b/LGPL.txt new file mode 100644 index 0000000000..65c5ca88a6 --- /dev/null +++ b/LGPL.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/cmake/nmodl/FlexHelper.cmake b/cmake/nmodl/FlexHelper.cmake index 4240ae4cbe..61227714d7 100644 --- a/cmake/nmodl/FlexHelper.cmake +++ b/cmake/nmodl/FlexHelper.cmake @@ -16,5 +16,3 @@ if(NOT FLEX_BIN_DIR MATCHES "/usr/bin") include_directories(${FLEX_INCLUDE_PATH}) endif() endif() - -include_directories(${FLEX_INCLUDE_DIRS}) diff --git a/nmodl/__init__.py b/nmodl/__init__.py index eb5e3d31a5..dec6ecf29f 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -1,2 +1,2 @@ -from _nmodl import * \ No newline at end of file +from ._nmodl import * diff --git a/setup.py b/setup.py index d3d6595e9c..0843407506 100644 --- a/setup.py +++ b/setup.py @@ -1,18 +1,22 @@ import os +import os.path as osp import re +import shutil + import sys import platform import subprocess from setuptools import setup, Extension from setuptools.command.build_ext import build_ext +from setuptools.command.install_lib import install_lib from distutils.version import LooseVersion class CMakeExtension(Extension): def __init__(self, name, sourcedir=''): Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) + self.sourcedir = osp.abspath(sourcedir) class CMakeBuild(build_ext): @@ -31,7 +35,7 @@ def run(self): self.build_extension(ext) def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + extdir = osp.abspath(osp.join(osp.dirname(self.get_ext_fullpath(ext.name)), 'nmodl')) cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, '-DPYTHON_EXECUTABLE=' + sys.executable] @@ -50,17 +54,18 @@ def build_extension(self, ext): env = os.environ.copy() env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), self.distribution.get_version()) - if not os.path.exists(self.build_temp): + if not osp.exists(self.build_temp): os.makedirs(self.build_temp) subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + subprocess.check_call(['cmake', '--build', '.', '--target', '_nmodl'] + build_args, cwd=self.build_temp) + setup( name='NMODL', - version='0.0.1', - author='The Blue Brain Project', - author_email='omar.awile@epfl.ch', - description='A NEURON modelling language source-to-source compiler framework', + version='0.1', + author='Blue Brain Project', + author_email='bbp-ou-hpc@groupes.epfl.ch', + description='NEURON Modelling Language Source-to-Source Compiler Framework', long_description='', packages=['nmodl'], ext_modules=[CMakeExtension('nmodl')], @@ -70,4 +75,4 @@ def build_extension(self, ext): 'PyYAML>=3.13', ], tests_require=['pytest>=4.0.0'] -) \ No newline at end of file +) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 34ec6edc29..b16de11b25 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -200,3 +200,9 @@ set(FILES_FOR_CLANG_FORMAT ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) + +#============================================================================= +# Install executable +#============================================================================= +install(TARGETS nmodl_lexer DESTINATION bin/lexer) +install(TARGETS c_lexer DESTINATION bin/lexer) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index a68d4e37d7..49a19f9786 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -22,3 +22,9 @@ set(FILES_FOR_CLANG_FORMAT add_executable(nmodl ${NMODL_SOURCE_FILES}) target_link_libraries(nmodl printer codegen visitor symtab util lexer) + + +#============================================================================= +# Install executable +#============================================================================= +install(TARGETS nmodl DESTINATION bin) \ No newline at end of file diff --git a/src/nmodl/nmodl/LICENSE b/src/nmodl/nmodl/LICENSE index fb7f8d875c..a79775abab 100644 --- a/src/nmodl/nmodl/LICENSE +++ b/src/nmodl/nmodl/LICENSE @@ -1,16 +1,21 @@ -/********************************************************************************* - * - * Copyright (c) 2017, Blue Brain Project, EPFL. - * All rights reverved. - * - * Do not redistribute, copy or create a derivative work without prior permission. - * - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - *********************************************************************************/ +********************************************************************************* +* NMODL - NEURON Modeling Language Code Generation Framework +* +* Copyright (c) 2019, Blue Brain Project, EPFL. +* +* NMODL is licensed under the LGPL, unless noted otherwise, e.g., for external +* dependencies. See file LGPL.txt for the full license. Examples and external +* dependencies are either LGPL or BSD-licensed. +* +* This version is not open sourced yet and provided for collaborators. This +* does not allow redistribution of the source without prior consent. + +* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS +* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +* DEALINGS IN THE SOFTWARE. +* +*********************************************************************************/ diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index e4c6f7f64d..2f502dfe97 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -21,3 +21,9 @@ set(FILES_FOR_CLANG_FORMAT ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) + +#============================================================================= +# Install executable +#============================================================================= +install(TARGETS nmodl_parser DESTINATION bin/parser) +install(TARGETS c_parser DESTINATION bin/parser) \ No newline at end of file diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index ca83b645ae..27f59237d3 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -30,3 +30,9 @@ pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES} add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) + +#============================================================================= +# Install python binding components +#============================================================================= +install(TARGETS _nmodl DESTINATION lib/python/nmodl) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/nmodl DESTINATION lib/python/) diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index a192fe6ab4..e185f83ad9 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -74,3 +74,9 @@ set(FILES_FOR_CLANG_FORMAT ${FILES_FOR_CLANG_FORMAT} PARENT_SCOPE ) + + +#============================================================================= +# Install executable +#============================================================================= +install(TARGETS nmodl_visitor DESTINATION bin/visitor) From c616af44efbcd5cf3ea7511a2b089e2b50add313 Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Mon, 11 Feb 2019 11:18:28 +0100 Subject: [PATCH 128/871] NmodlPrintVisitor flushes ostream after visiting a node - Python code does not have to call NmodlPrintVisitor destructor explicitily to force ostream to be flushed. - Fixed issue in build system that prevented pytest from working correctly. This was a regression introduced by the recent build- system changes. Change-Id: I54c7a5b2f3d756bfb71d60539bee96a692d515cb NMODL Repo SHA: BlueBrain/nmodl@b2c7443dc25dcfb69304ab1561dec534a1c1d4c6 --- setup.py | 3 ++- src/nmodl/language/templates/pyvisitor.cpp | 10 +++++++++- src/nmodl/pybind/CMakeLists.txt | 9 +++++++++ src/nmodl/pybind/pybind_utils.hpp | 3 +++ test/nmodl/transpiler/CMakeLists.txt | 5 ++--- test/nmodl/transpiler/pybind/test_symtab.py | 15 +++++++++++++-- 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 0843407506..66fa7db9df 100644 --- a/setup.py +++ b/setup.py @@ -35,7 +35,8 @@ def run(self): self.build_extension(ext) def build_extension(self, ext): - extdir = osp.abspath(osp.join(osp.dirname(self.get_ext_fullpath(ext.name)), 'nmodl')) + extdir = osp.abspath(osp.dirname(self.get_ext_fullpath(ext.name))) + extdir = osp.join(extdir, ext.name) cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, '-DPYTHON_EXECUTABLE=' + sys.executable] diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 756e879f56..8a0ed39483 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -37,11 +37,19 @@ void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_nam class PyNmodlPrintVisitor : private VisitorOStreamResources, public NmodlPrintVisitor { public: using NmodlPrintVisitor::NmodlPrintVisitor; + using VisitorOStreamResources::flush; PyNmodlPrintVisitor() = default; PyNmodlPrintVisitor(std::string filename) : NmodlPrintVisitor(filename) {}; PyNmodlPrintVisitor(py::object object) : VisitorOStreamResources(object), NmodlPrintVisitor(*ostream) { }; + + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override { + NmodlPrintVisitor::visit_{{ node.class_name|snake_case }}(node); + flush(); + } + {% endfor %} }; @@ -62,7 +70,7 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - py::class_<NmodlPrintVisitor, Visitor, PyNmodlPrintVisitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor"); + py::class_<PyNmodlPrintVisitor, Visitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor"); nmodl_visitor.def(py::init<std::string>()); nmodl_visitor.def(py::init<py::object>()); nmodl_visitor.def(py::init<>()) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 27f59237d3..bfb063593c 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -31,6 +31,15 @@ add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) +add_custom_command(TARGET _nmodl POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${PROJECT_SOURCE_DIR}/nmodl + ${CMAKE_BINARY_DIR}/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_if_different + $<TARGET_FILE:_nmodl> + ${CMAKE_BINARY_DIR}/nmodl + ) + #============================================================================= # Install python binding components #============================================================================= diff --git a/src/nmodl/pybind/pybind_utils.hpp b/src/nmodl/pybind/pybind_utils.hpp index fc4a6233de..fdc5ecaedd 100644 --- a/src/nmodl/pybind/pybind_utils.hpp +++ b/src/nmodl/pybind/pybind_utils.hpp @@ -91,4 +91,7 @@ class VisitorOStreamResources { VisitorOStreamResources() = default; VisitorOStreamResources(pybind11::object object) : buf(new pybind11::detail::pythonbuf(object)), ostream(new std::ostream(buf.get())) {} + void flush() { + ostream->flush(); + } }; diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 990ba12429..7d7e17a681 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -42,10 +42,9 @@ add_test (NAME Symtab COMMAND testsymtab) # pybind11 tests #============================================================================= -add_test (NAME Pybind COMMAND python3 -m pytest - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) +add_test (NAME Pybind COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) set_tests_properties(Pybind PROPERTIES - ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}/src/pybind:$ENV{PYTHONPATH}) + ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) #============================================================================= # Files for clang-format diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py index 24af87db60..90f10fa6b3 100644 --- a/test/nmodl/transpiler/pybind/test_symtab.py +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -1,6 +1,7 @@ +import io + from nmodl import ast, visitor, symtab -import nmodl -import pytest + def test_symtab(ch_ast): v = symtab.SymtabVisitor() @@ -20,3 +21,13 @@ def test_symtab(ch_ast): variables = s.get_variables_with_properties(symtab.NmodlType.range_var, True) assert len(variables) == 2 assert str(variables[0]) == "mInf [Properties : range dependent_def]" + + +def test_visitor_python_io(ch_ast): + lookup_visitor = visitor.AstLookupVisitor() + eqs = lookup_visitor.lookup(ch_ast, ast.AstNodeType.DIFF_EQ_EXPRESSION) + stream = io.StringIO() + pv = visitor.NmodlPrintVisitor(stream) + eqs[0].accept(pv) + # `del pv` is not required to have the ostream flushed + assert stream.getvalue() == "m' = mInf-m" From 5905e2a89947cd23f59765e7a0c0ecdb0d7aa5a7 Mon Sep 17 00:00:00 2001 From: lkeegan <liam@keegan.ch> Date: Sun, 10 Feb 2019 11:09:15 +0100 Subject: [PATCH 129/871] add SympySolverVisitor -solves differential eqs analytically -disabled by default, to enable use --enable-sympy -only applies to case that solver method is cnexp -depends on SymPy, added dependency to CMake -uses pybind11 embedded python, added to visitor library in CMake -changed nmodl folder structure, added ode.py -added unit tests to test/visitor.cpp Change-Id: I06f9b3822f59de682dbcb9e3b87e176f8713e5ef NMODL Repo SHA: BlueBrain/nmodl@eced22d4a7bcfa2007c8b41f6dac05759769ec79 --- cmake/nmodl/CMakeLists.txt | 1 + nmodl/__init__.py | 2 - nmodl/dsl.py | 2 + nmodl/ode.py | 73 +++++++ setup.py | 1 + src/nmodl/nmodl/CMakeLists.txt | 1 + src/nmodl/nmodl/arg_handler.cpp | 10 +- src/nmodl/nmodl/arg_handler.hpp | 3 + src/nmodl/nmodl/main.cpp | 15 ++ src/nmodl/visitors/CMakeLists.txt | 6 +- .../differential_equation_visitor.cpp | 8 - .../differential_equation_visitor.hpp | 31 --- src/nmodl/visitors/sympy_solver_visitor.cpp | 99 ++++++++++ src/nmodl/visitors/sympy_solver_visitor.hpp | 39 ++++ test/nmodl/transpiler/CMakeLists.txt | 4 + test/nmodl/transpiler/pybind/conftest.py | 3 +- test/nmodl/transpiler/pybind/test_ast.py | 10 +- test/nmodl/transpiler/pybind/test_symtab.py | 5 +- test/nmodl/transpiler/visitor/visitor.cpp | 181 ++++++++++++++++-- 19 files changed, 420 insertions(+), 74 deletions(-) create mode 100644 nmodl/dsl.py create mode 100644 nmodl/ode.py delete mode 100644 src/nmodl/visitors/differential_equation_visitor.cpp delete mode 100644 src/nmodl/visitors/differential_equation_visitor.hpp create mode 100644 src/nmodl/visitors/sympy_solver_visitor.cpp create mode 100644 src/nmodl/visitors/sympy_solver_visitor.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 997b00cac2..e8feb2cbd6 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -42,6 +42,7 @@ message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) find_python_module(jinja2 REQUIRED) find_python_module(pytest REQUIRED) +find_python_module(sympy REQUIRED) find_python_module(textwrap REQUIRED) find_python_module(yaml REQUIRED) diff --git a/nmodl/__init__.py b/nmodl/__init__.py index dec6ecf29f..e69de29bb2 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -1,2 +0,0 @@ - -from ._nmodl import * diff --git a/nmodl/dsl.py b/nmodl/dsl.py new file mode 100644 index 0000000000..dec6ecf29f --- /dev/null +++ b/nmodl/dsl.py @@ -0,0 +1,2 @@ + +from ._nmodl import * diff --git a/nmodl/ode.py b/nmodl/ode.py new file mode 100644 index 0000000000..fcefdaebbb --- /dev/null +++ b/nmodl/ode.py @@ -0,0 +1,73 @@ +import sympy as sp + +def integrate2c(diff_string, vars): + """Analytically integrate supplied derivative, return solution as C code. + + Derivative should be of the form "x' = f(x)", + and vars should contain the set of all the variables + referenced by f(x), for example: + -integrate2c("x' = a*x", "a") + -integrate2c("x' = a + b*x - sin(3.2)", {"a","b"}) + + Args: + diff_string: Derivative to be integrated e.g. "x' = a*x" + vars: set of variables used in expression, e.g. {"x", "a"} + + Returns: + String containing analytic integral of derivative as C code + + Raises: + NotImplementedError: if ODE is too hard, or if fails to solve it. + """ + + # only try to solve ODEs that are not too hard + ode_properties_require_all = {'separable'} + ode_properties_require_one_of = { + '1st_exact', + '1st_linear', + 'almost_linear', + 'nth_linear_constant_coeff_homogeneous', + '1st_exact_Integral', + '1st_linear_Integral', + } + + # every symbol (a.k.a variable) that SymPy + # is going to manipulate needs to be declared + # explicitly + sympy_vars = {} + # time is always t + t = sp.symbols('t', real=True, positive=True) + vars = set(vars) + vars.discard("t") + # the dependent variable is a function of t + # we use the python variable name x for this + dependent_var = diff_string.split('=')[0].split("'")[0].strip() + x = sp.Function(dependent_var, real=True)(t) + vars.discard(dependent_var) + # declare all other supplied variables + sympy_vars = {var: sp.symbols(var) for var in vars} + sympy_vars[dependent_var] = x + sympy_vars["t"] = t + + # parse string into SymPy equation + diffeq = sp.Eq( + x.diff(t), + sp.sympify( + diff_string.split('=')[1], + locals=sympy_vars)) + + # classify ODE, if it is too hard then exit + ode_properties = set(sp.classify_ode(diffeq)) + if not ode_properties_require_all <= ode_properties: + raise NotImplementedError("ODE too hard") + if len(ode_properties_require_one_of & ode_properties) == 0: + raise NotImplementedError("ODE too hard") + + # try to find analytic solution + dt = sp.symbols('dt', real=True, positive=True) + x_0 = sp.symbols(dependent_var, real=True) + solution = sp.dsolve(diffeq, x, ics={x.subs({t: 0}): x_0}).subs({t: dt}) + # note dsolve can return a list of solutions, in which case the above fails + + # return result as C code in NEURON format + return f"{sp.ccode(x_0)} = {sp.ccode(solution.rhs)}" diff --git a/setup.py b/setup.py index 66fa7db9df..d20aa8e5d4 100644 --- a/setup.py +++ b/setup.py @@ -74,6 +74,7 @@ def build_extension(self, ext): zip_safe=False, setup_requires=['jinja2>=2.10', 'PyYAML>=3.13', + 'sympy>=1.2' ], tests_require=['pytest>=4.0.0'] ) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 49a19f9786..e64f871d60 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -1,6 +1,7 @@ #============================================================================= # NMODL sources #============================================================================= +include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.hpp diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index d2fd8046d9..f71a1a6f1f 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -81,13 +81,20 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { "string", cmd); - switch_arg_type verbose_arg( + switch_arg_type verbose_arg( "", "verbose", "Enable verbose output", cmd, false); + switch_arg_type sympy_arg( + "", + "enable-sympy", + "Enable SymPy analytic integration", + cmd, + false); + switch_arg_type inline_arg( "", "nmodl-inline", @@ -167,6 +174,7 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { dtype = stringutils::tolower(dtype_arg.getValue()); mlayout = layout_arg.getValue(); verbose = verbose_arg.getValue(); + sympy = sympy_arg.getValue(); inlining = inline_arg.getValue(); localize_with_verbatim = localize_verbatim_arg.getValue(); local_rename = local_rename_arg.getValue(); diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp index 613d3175ee..71924882ee 100644 --- a/src/nmodl/nmodl/arg_handler.hpp +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -25,6 +25,9 @@ struct ArgumentHandler { /// memory layout to use std::string mlayout; + /// enable SymPy analytic integration + bool sympy; + /// enable nmodl level inlining bool inlining; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 47a436098f..98158b46d3 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -1,6 +1,7 @@ #include <fstream> #include <iostream> #include <sstream> +#include <pybind11/embed.h> #include "arg_handler.hpp" #include "codegen/codegen_cuda_visitor.hpp" @@ -19,6 +20,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/sympy_solver_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -36,6 +38,10 @@ int main(int argc, const char* argv[]) { int error_count = 0; + if (arg.sympy) { + pybind11::initialize_interpreter(); + } + for (auto& nmodl_file : arg.nmodl_files) { std::ifstream file(nmodl_file); @@ -78,6 +84,11 @@ int main(int argc, const char* argv[]) { } } + if (arg.sympy) { + SympySolverVisitor v; + v.visit_program(ast.get()); + } + { CnexpSolveVisitor v; v.visit_program(ast.get()); @@ -174,6 +185,10 @@ int main(int argc, const char* argv[]) { } } + if (arg.sympy) { + pybind11::finalize_interpreter(); + } + if (error_count != 0) { logger->error("Code generation encountered {} errors", error_count); } else { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index e185f83ad9..3a2e36fe65 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -5,7 +5,6 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/ast_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp @@ -15,6 +14,7 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp @@ -24,7 +24,6 @@ set(VISITOR_SOURCES set(VISITOR_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/differential_equation_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp @@ -35,6 +34,7 @@ set(VISITOR_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp @@ -49,6 +49,7 @@ set_source_files_properties( #============================================================================= # Visitor library and executable #============================================================================= +include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) add_library(visitor_obj OBJECT ${VISITOR_SOURCES} ${VISITOR_HEADERS}) @@ -59,6 +60,7 @@ add_dependencies(visitor_obj lexer_obj) add_library(visitor STATIC $<TARGET_OBJECTS:visitor_obj>) +target_link_libraries(visitor PRIVATE pybind11::embed) add_dependencies(visitor lexer util) diff --git a/src/nmodl/visitors/differential_equation_visitor.cpp b/src/nmodl/visitors/differential_equation_visitor.cpp deleted file mode 100644 index 268512bc67..0000000000 --- a/src/nmodl/visitors/differential_equation_visitor.cpp +++ /dev/null @@ -1,8 +0,0 @@ -#include "visitors/differential_equation_visitor.hpp" -#include <iostream> - -using namespace ast; - -void DifferentialEquationVisitor::visit_diff_eq_expression(DiffEqExpression* node) { - equations.push_back(node); -} diff --git a/src/nmodl/visitors/differential_equation_visitor.hpp b/src/nmodl/visitors/differential_equation_visitor.hpp deleted file mode 100644 index c5115a6b59..0000000000 --- a/src/nmodl/visitors/differential_equation_visitor.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef DIFF_EQ_VISITOR_HPP -#define DIFF_EQ_VISITOR_HPP - -#include <vector> - -#include "ast/ast.hpp" -#include "visitors/ast_visitor.hpp" - -/** - * \class DifferentialEquationVisitor - * \brief Visitor for differential equations in derivative block - * - * Simple example of visitor to find out all differential - * equations in a MOD file. - */ - -class DifferentialEquationVisitor : public AstVisitor { - private: - std::vector<ast::DiffEqExpression*> equations; - - public: - DifferentialEquationVisitor() = default; - - void visit_diff_eq_expression(ast::DiffEqExpression* node) override; - - std::vector<ast::DiffEqExpression*> get_equations() { - return equations; - } -}; - -#endif diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp new file mode 100644 index 0000000000..26bbc62df9 --- /dev/null +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -0,0 +1,99 @@ +#include "symtab/symbol.hpp" +#include "utils/logger.hpp" +#include "visitors/sympy_solver_visitor.hpp" +#include "visitor_utils.hpp" +#include <iostream> +using namespace ast; +namespace py = pybind11; +using namespace py::literals; +using namespace syminfo; + +void SympySolverVisitor::visit_solve_block(SolveBlock* node) { + auto method = node->get_method(); + if (method) { + solve_method = method->get_value()->eval(); + } +} + +void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { + if (solve_method != cnexp_method) { + logger->warn( + "SympySolverVisitor: solve method not cnexp, so not integrating " + "expression analytically"); + return; + } + + auto& lhs = node->get_expression()->lhs; + auto& rhs = node->get_expression()->rhs; + + if (!lhs->is_var_name()) { + logger->warn("SympySolverVisitor: LHS of differential equation is not a VariableName"); + return; + } + auto lhs_name = std::dynamic_pointer_cast<VarName>(lhs)->get_name(); + if (!lhs_name->is_prime_name()) { + logger->warn("SympySolverVisitor: LHS of differential equation is not a PrimeName"); + return; + } + + std::unordered_set<std::string> var_strings{}; + for (auto v : vars) { + var_strings.insert(v->get_name()); + } + + auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), "vars"_a = var_strings); + py::exec(R"( + from nmodl.ode import integrate2c + exception_message = "" + try: + solution = integrate2c(equation_string, vars) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), locals); + + auto solution = locals["solution"].cast<std::string>(); + auto exception_message = locals["exception_message"].cast<std::string>(); + if (!exception_message.empty()) { + logger->warn("SympySolverVisitor: python exception: " + exception_message); + } + if (!solution.empty()) { + auto statement = create_statement(solution); + auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); + auto bin_expr = + std::dynamic_pointer_cast<BinaryExpression>(expr_statement->get_expression()); + lhs.reset(bin_expr->lhs->clone()); + rhs.reset(bin_expr->rhs->clone()); + } else { + logger->warn("SympySolverVisitor: analytic solution to differential equation not possible"); + } +} + +void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { + // get any local vars + auto symtab = node->get_statement_block()->get_symbol_table(); + if (symtab) { + auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); + for (auto v : localvars) { + vars.push_back(v); + } + } + node->visit_children(this); +} + +void SympySolverVisitor::visit_program(ast::Program* node) { + // get global vars + auto symtab = node->get_symbol_table(); + NmodlType property = NmodlType::global_var | NmodlType::range_var | NmodlType::param_assign | + NmodlType::extern_var | NmodlType::prime_name | NmodlType::dependent_def | + NmodlType::read_ion_var | NmodlType::write_ion_var | + NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | + NmodlType::section_var | NmodlType::constant_var | + NmodlType::extern_neuron_variable | NmodlType::state_var; + if (symtab) { + vars = symtab->get_variables_with_properties(property); + } + node->visit_children(this); +} \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp new file mode 100644 index 0000000000..68e02530c4 --- /dev/null +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include <unordered_set> +#include <vector> +#include <pybind11/embed.h> +#include <pybind11/stl.h> + +#include "ast/ast.hpp" +#include "symtab/symbol.hpp" +#include "visitors/ast_visitor.hpp" + +/** + * \class SympySolverVisitor + * \brief Visitor for differential equations in derivative block + * + * When solver method is "cnexp", this class replaces the + * differential equations with their analytic solution using SymPy, + * i.e. it duplicates the functionality of CnexpSolveVisitor + * + * It will soon also deal with other solver methods. + */ + +class SympySolverVisitor : public AstVisitor { + private: + std::vector<std::shared_ptr<symtab::Symbol>> vars; + /// method specified in solve block + std::string solve_method; + + /// solver method names + const std::string cnexp_method = "cnexp"; + + public: + SympySolverVisitor() = default; + + void visit_solve_block(ast::SolveBlock* node) override; + void visit_diff_eq_expression(ast::DiffEqExpression* node) override; + void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_program(ast::Program* node) override; +}; \ No newline at end of file diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 7d7e17a681..166864afff 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -1,5 +1,6 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/test) +include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) include_directories( ${PROJECT_SOURCE_DIR}/src/ext/catch ${PROJECT_SOURCE_DIR}/test @@ -38,6 +39,9 @@ add_test (NAME Visitor COMMAND testvisitor) add_test (NAME Printer COMMAND testprinter) add_test (NAME Symtab COMMAND testsymtab) +set_tests_properties(Visitor PROPERTIES + ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + #============================================================================= # pybind11 tests #============================================================================= diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/pybind/conftest.py index cc52c35626..4b68d3c2e3 100644 --- a/test/nmodl/transpiler/pybind/conftest.py +++ b/test/nmodl/transpiler/pybind/conftest.py @@ -1,5 +1,6 @@ import pytest import nmodl +from nmodl.dsl import Driver CHANNEL = """NEURON { SUFFIX NaTs2_t @@ -25,6 +26,6 @@ @pytest.fixture def ch_ast(): - d = nmodl.Driver() + d = Driver() d.parse_string(CHANNEL) return d.ast() \ No newline at end of file diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py index c501593d9b..5352846459 100644 --- a/test/nmodl/transpiler/pybind/test_ast.py +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -1,6 +1,6 @@ import nmodl +from nmodl.dsl import ast, visitor import pytest -from nmodl import ast, visitor class TestAST(object): def test_empty_program(self): @@ -11,7 +11,7 @@ def test_empty_program(self): def test_lookup_visitor(ch_ast): lookup_visitor = visitor.AstLookupVisitor() eqs = lookup_visitor.lookup(ch_ast, ast.AstNodeType.DIFF_EQ_EXPRESSION) - eq_str = nmodl.to_nmodl(eqs[0]) + eq_str = nmodl.dsl.to_nmodl(eqs[0]) assert eq_str == "m' = mInf-m" @@ -19,8 +19,8 @@ def test_lookup_visitor_constructor(ch_ast): lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.DIFF_EQ_EXPRESSION) ch_ast.accept(lookup_visitor) eqs = lookup_visitor.get_nodes() - eq_str = nmodl.to_nmodl(eqs[0]) - eq_json = nmodl.to_json(eqs[0], True) + eq_str = nmodl.dsl.to_nmodl(eqs[0]) + eq_json = nmodl.dsl.to_json(eqs[0], True) assert eq_str == "m' = mInf-m" assert '"DiffEqExpression":[{"BinaryExpression"' in eq_json @@ -40,7 +40,7 @@ def visit_state_block(self, node): def visit_name(self, node): if self.in_state: - self.states.append(nmodl.to_nmodl(node)) + self.states.append(nmodl.dsl.to_nmodl(node)) myvisitor = StateVisitor() ch_ast.accept(myvisitor) diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py index 90f10fa6b3..1f1d1b27a8 100644 --- a/test/nmodl/transpiler/pybind/test_symtab.py +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -1,7 +1,6 @@ import io - -from nmodl import ast, visitor, symtab - +import nmodl +from nmodl.dsl import ast, visitor, symtab def test_symtab(ch_ast): v = symtab.SymtabVisitor() diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index c5216c61b6..b27afdc1ac 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1,15 +1,15 @@ -#define CATCH_CONFIG_MAIN +#define CATCH_CONFIG_RUNNER #include <string> #include "catch/catch.hpp" +#include <pybind11/embed.h> #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.h" #include "test/utils/test_utils.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" -#include "visitors/differential_equation_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" @@ -19,6 +19,7 @@ #include "visitors/perf_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/sympy_solver_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -26,6 +27,15 @@ using json = nlohmann::json; using namespace ast; using namespace nmodl; using namespace syminfo; +namespace py = pybind11; + +int main(int argc, char* argv[]) { + // initialize python interpreter once for + // entire catch executable + pybind11::scoped_interpreter guard{}; + int result = Catch::Session().run(argc, argv); + return result; +} //============================================================================= // Verbatim visitor tests @@ -1945,53 +1955,182 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { //============================================================================= -// DifferentialEquation visitor tests +// SympySolver visitor tests //============================================================================= -std::vector<std::string> run_differential_equation_visitor(const std::string& text) { +std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { + std::vector<std::string> results; + + // construct AST from text nmodl::Driver driver; driver.parse_string(text); auto ast = driver.ast(); - DifferentialEquationVisitor v; - v.visit_program(ast.get()); - auto equations = v.get_equations(); + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(ast.get()); - std::vector<std::string> result; - for (const auto& equation : equations) { - result.push_back(to_nmodl(equation)); + // run SympySolver on AST + SympySolverVisitor v_sympy; + v_sympy.visit_program(ast.get()); + + // run lookup visitor to extract results from AST + AstLookupVisitor v_lookup; + auto res = v_lookup.lookup(ast.get(), AstNodeType::DIFF_EQ_EXPRESSION); + for (auto r : res) { + results.push_back(to_nmodl(r.get())); } - return result; + + return results; +} + +void run_sympy_visitor_passes(Program* node) { + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(node); + + // run SympySolver on AST several times + SympySolverVisitor v_sympy1; + v_sympy1.visit_program(node); + v_sympy1.visit_program(node); + + // also use a second instance of SympySolver + SympySolverVisitor v_sympy2; + v_sympy2.visit_program(node); + v_sympy1.visit_program(node); + v_sympy2.visit_program(node); } +std::string ast_to_string(Program* node) { + std::stringstream stream; + { + NmodlPrintVisitor v(stream); + v.visit_program(node); + } + return stream.str(); +} -SCENARIO("DifferentialEquation visitor finding ODEs") { - GIVEN("Derivative block with multiple differential equations") { +SCENARIO("SympySolver visitor", "[sympy]") { + GIVEN("Derivative block without ODE, solver method cnexp") { std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m = m + h + } + )"; + + THEN("No ODEs found - do nothing") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 0); + } + } + GIVEN("Derivative block with ODES, solver method is not cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } DERIVATIVE states { m' = (mInf-m)/mTau h' = (hInf-h)/hTau - m = m + h + z = a*b + c } )"; - THEN("ODEs can be searched in AST") { - auto result = run_differential_equation_visitor(nmodl_text); + THEN("Solver method is not cnexp - do nothing") { + auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); REQUIRE(result[0] == "m' = (mInf-m)/mTau"); REQUIRE(result[1] == "h' = (hInf-h)/hTau"); } } - GIVEN("Derivative mod files without ODE") { + GIVEN("Derivative block with linear ODES, solver method cnexp") { std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } DERIVATIVE states { - m = m + h + m' = (mInf-m)/mTau + z = a*b + c + h' = hInf/hTau - h/hTau } )"; - THEN("No ODEs are found") { - auto result = run_differential_equation_visitor(nmodl_text); - REQUIRE(result.size() == 0); + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "m = mInf+(m-mInf)*exp(-dt/mTau)"); + REQUIRE(result[1] == "h = hInf+(h-hInf)*exp(-dt/hTau)"); + } + } + GIVEN("Derivative block including non-linear but solvable ODES, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = c2 * h*h + } + )"; + + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "m = mInf+(m-mInf)*exp(-dt/mTau)"); + REQUIRE(result[1] == "h = -1/(c2*dt-1/h)"); + } + } + GIVEN("Derivative block including ODES that can't currently be solved, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + z' = a/z + b/z/z + h' = c2 * h*h + x' = a + y' = c3 * y*y*y + } + )"; + + THEN("Integrate equations analytically where possible, otherwise leave untouched") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 4); + REQUIRE(result[0] == "z' = a/z+b/z/z"); + REQUIRE(result[1] == "h = -1/(c2*dt-1/h)"); + REQUIRE(result[2] == "x = a*dt+x"); + REQUIRE(result[3] == "y' = c3*y*y*y"); + } + } + GIVEN("Derivative block with cnexp solver method, AST after SympySolver pass") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m' = (mInf-m)/mTau + } + )"; + // construct AST from text + nmodl::Driver driver; + driver.parse_string(nmodl_text); + auto ast = driver.ast(); + + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(ast.get()); + + // run SympySolver on AST + SympySolverVisitor v_sympy; + v_sympy.visit_program(ast.get()); + + std::string AST_string = ast_to_string(ast.get()); + + THEN("More SympySolver passes do nothing to the AST and don't throw") { + REQUIRE_NOTHROW(run_sympy_visitor_passes(ast.get())); + REQUIRE(AST_string == ast_to_string(ast.get())); } } } From 782a635c7f87326f536d25e256dbd70ba870660e Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Tue, 12 Feb 2019 14:55:47 +0100 Subject: [PATCH 130/871] Generate files in build directory, it has 2 advantages: - it keeps the working directory clean - it paves the way to integration with https://github.com/BlueBrain/hpc-coding-conventions so that it will be easier to know which files have to be checked (with clang-format, clang-tidy, ...) Change-Id: I81c7895ed8669a97844599cb4e548ff0dfad0931 NMODL Repo SHA: BlueBrain/nmodl@ae75220e1c39e162b21ca4f78b68081e9470d3f3 --- cmake/nmodl/CMakeLists.txt | 40 ++++----- src/nmodl/language/CMakeLists.txt | 4 +- src/nmodl/language/code_generator.py | 6 +- src/nmodl/lexer/CMakeLists.txt | 126 +++++++++++++++------------ src/nmodl/pybind/CMakeLists.txt | 14 +-- src/nmodl/visitors/CMakeLists.txt | 53 +++++------ 6 files changed, 133 insertions(+), 110 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e8feb2cbd6..cb75e708d6 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -49,6 +49,7 @@ find_python_module(yaml REQUIRED) include_directories( ${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src ${PROJECT_SOURCE_DIR}/ext ) @@ -72,28 +73,27 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in # list of autogenerated files #============================================================================= set ( AUTO_GENERATED_FILES - ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyast.hpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyast.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.hpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/lookup_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/nmodl_visitor.cpp + ${PROJECT_BINARY_DIR}/src/ast/ast.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp ) -add_subdirectory(src/ast) add_subdirectory(src/codegen) add_subdirectory(src/language) add_subdirectory(src/lexer) diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 3860ae50da..5daef3badb 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -16,7 +16,7 @@ add_custom_command( OUTPUT ${AUTO_GENERATED_FILES} COMMAND ${PYTHON_EXECUTABLE} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${CODE_GENERATOR_OPTS} + ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PYCODE} @@ -27,4 +27,4 @@ unset(CODE_GENERATOR_OPTS) # ============================================================================= # Target to propagate dependencies properly to lexer # ============================================================================= -add_custom_target(pyastgen DEPENDS ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp) +add_custom_target(pyastgen DEPENDS ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 3bfd87b48a..1f18e0a6d4 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -17,6 +17,7 @@ logging.basicConfig(level=logging.INFO, format='%(message)s') parser = argparse.ArgumentParser() parser.add_argument('--clang-format') +parser.add_argument('--base-dir') args = parser.parse_args() clang_format = args.clang_format @@ -24,7 +25,10 @@ nodes = LanguageParser("nmodl.yaml").parse_file() templates = Path(__file__).parent / 'templates' -basedir = Path(__file__).resolve().parent.parent + +basedir = Path(args.base_dir) or Path(__file__).resolve().parent.parent +for subdir in ['pybind', 'visitors', 'ast']: + (basedir / subdir).mkdir(parents=True, exist_ok=True) env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates)), trim_blocks=True, diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index b16de11b25..bd374634bf 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -2,15 +2,18 @@ # Various project components and their source files #============================================================================= set (BISON_GENERATED_SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp -) + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp) set(AST_SOURCE_FILES - ${PROJECT_SOURCE_DIR}/src/ast/ast.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast.cpp + ${PROJECT_BINARY_DIR}/src/ast/ast.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast.cpp ) set(NMODL_DRIVER_FILES @@ -44,10 +47,14 @@ set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.hpp ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp ${NMODL_DRIVER_FILES} ${DIFFEQ_DRIVER_FILES} ${C_DRIVER_FILES} @@ -56,111 +63,122 @@ set(LEXER_SOURCE_FILES #============================================================================= # Directories for parsers (as they need to be in separate directories) #============================================================================= -file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/nmodl) -file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/diffeq) -file(MAKE_DIRECTORY ${PROJECT_SOURCE_DIR}/src/parser/c) +file(MAKE_DIRECTORY + ${PROJECT_BINARY_DIR}/src/parser/nmodl + ${PROJECT_BINARY_DIR}/src/parser/diffeq + ${PROJECT_BINARY_DIR}/src/parser/c) #============================================================================= # Lexer & Parser commands #============================================================================= # command to generate nmodl parser add_custom_command ( + OUTPUT + ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp + ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/nmodl_parser.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/location.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/position.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/nmodl/stack.hh - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS pyastgen + pyastgen COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" ) # command to generate verbatim parser add_custom_command ( + OUTPUT + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp + ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/verbatim_parser.hpp DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --" ) # command to generate differential equation parser add_custom_command ( + OUTPUT + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp + ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/diffeq_parser.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/location.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/position.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/diffeq/stack.hh - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + DEPENDS + ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --" ) # command to generate C (11) parser add_custom_command ( + OUTPUT + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/c/location.hh + ${PROJECT_BINARY_DIR}/src/parser/c/position.hh + ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp + ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp ${PROJECT_SOURCE_DIR}/src/parser/c11.yy - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.cpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/c11_parser.hpp - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/location.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/position.hh - OUTPUT ${PROJECT_SOURCE_DIR}/src/parser/c/stack.hh DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --" ) # command to generate nmodl lexer add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.cpp - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_base_lexer.hpp - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --" ) # command to generate verbatim lexer add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.cpp - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_lexer.hpp - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --" ) # command to generate differential equation lexer add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.cpp - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_base_lexer.hpp - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --" ) # command to generate C (11) lexer add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.cpp - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/c11_base_lexer.hpp - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --" ) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index bfb063593c..5f6f65e072 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -5,18 +5,18 @@ set_source_files_properties( ${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) set(PYNMODL_SOURCES - ${PROJECT_SOURCE_DIR}/src/pybind/pyast.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp ${PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) set(PYNMODL_HEADERS - ${PROJECT_SOURCE_DIR}/src/ast/ast_decl.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${PROJECT_SOURCE_DIR}/src/visitors/visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp ${PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyast.hpp - ${PROJECT_SOURCE_DIR}/src/pybind/pyvisitor.hpp) + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES} diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 3a2e36fe65..640967d82d 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -2,47 +2,48 @@ # Visitor sources #============================================================================= set(VISITOR_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/ast_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/json_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp -) - -set(VISITOR_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/lookup_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp - ) +) + +set(VISITOR_GENERATED_SOURCES + ${CMAKE_CURRENT_BINARY_DIR}/ast_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.hpp + ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.hpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.hpp + ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.hpp +) set_source_files_properties( - ${VISITOR_SOURCES} ${VISITOR_HEADERS} + ${VISITOR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE ) @@ -52,7 +53,7 @@ set_source_files_properties( include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) add_library(visitor_obj OBJECT - ${VISITOR_SOURCES} ${VISITOR_HEADERS}) + ${VISITOR_SOURCES} ${VISITOR_GENERATED_SOURCES}) set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(visitor_obj lexer_obj) From 51f6c5fd64f07b1540f8b5d838dbcc7c891515b4 Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Wed, 13 Feb 2019 07:56:11 +0100 Subject: [PATCH 131/871] integrate hpc-coding-conventions - new submodule in cmake/ subdirectory. - remove FindClangFormat and use the one provided by submodule. - remove `clang-format` CMake target and `FILES_FOR_CLANG_FORMAT` CMake variable in favor of what the submodule provides. - add developer documentation Change-Id: I26026aca06a0a7d75b9b9a496500ffe493892ee6 NMODL Repo SHA: BlueBrain/nmodl@e96dd54070f43853bbd22c2637f6e65c49754c3d --- .clang-format | 93 ---------------------------- CONTRIBUTING.md | 3 + README.md | 38 +++++++++++- cmake/nmodl/CMakeLists.txt | 29 +++++---- cmake/nmodl/FindClangFormat.cmake | 84 ------------------------- cmake/nmodl/hpc-coding-conventions | 1 + src/nmodl/ast/CMakeLists.txt | 8 --- src/nmodl/codegen/CMakeLists.txt | 9 --- src/nmodl/language/CMakeLists.txt | 10 ++- src/nmodl/language/code_generator.py | 12 +++- src/nmodl/lexer/CMakeLists.txt | 14 +---- src/nmodl/nmodl/CMakeLists.txt | 9 --- src/nmodl/parser/CMakeLists.txt | 13 +--- src/nmodl/printer/CMakeLists.txt | 9 --- src/nmodl/symtab/CMakeLists.txt | 9 --- src/nmodl/utils/CMakeLists.txt | 13 +--- src/nmodl/utils/logger.cpp | 2 + src/nmodl/visitors/CMakeLists.txt | 11 ---- test/nmodl/transpiler/CMakeLists.txt | 17 ----- 19 files changed, 81 insertions(+), 303 deletions(-) delete mode 100644 .clang-format delete mode 100644 cmake/nmodl/FindClangFormat.cmake create mode 160000 cmake/nmodl/hpc-coding-conventions delete mode 100644 src/nmodl/ast/CMakeLists.txt diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 2bd8848b20..0000000000 --- a/.clang-format +++ /dev/null @@ -1,93 +0,0 @@ ---- -Language: Cpp -AccessModifierOffset: -2 -AlignAfterOpenBracket: Align -AlignConsecutiveAssignments: false -AlignConsecutiveDeclarations: false -AlignEscapedNewlinesLeft: true -AlignOperands: true -AlignTrailingComments: true -AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false -AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: false -AllowShortIfStatementsOnASingleLine: false -AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: None -AlwaysBreakBeforeMultilineStrings: true -AlwaysBreakTemplateDeclarations: true -BinPackArguments: true -BinPackParameters: false -BraceWrapping: - AfterClass: false - AfterControlStatement: false - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false -BreakBeforeBinaryOperators: None -BreakBeforeBraces: Attach -BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -BreakAfterJavaFieldAnnotations: false -BreakStringLiterals: true -ColumnLimit: 100 -CommentPragmas: '^ IWYU pragma:' -ConstructorInitializerAllOnOneLineOrOnePerLine: true -ConstructorInitializerIndentWidth: 4 -ContinuationIndentWidth: 4 -Cpp11BracedListStyle: true -DerivePointerAlignment: false -DisableFormat: false -ExperimentalAutoDetectBinPacking: false -ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] -IncludeCategories: - - Regex: '^<.*\.h>' - Priority: 1 - - Regex: '^<.*' - Priority: 2 - - Regex: '.*' - Priority: 3 -IncludeIsMainRegex: '([-_](test|unittest))?$' -IndentCaseLabels: true -IndentWidth: 4 -IndentWrappedFunctionNames: false -KeepEmptyLinesAtTheStartOfBlocks: false -MacroBlockBegin: '' -MacroBlockEnd: '' -MaxEmptyLinesToKeep: 2 -NamespaceIndentation: All -ObjCBlockIndentWidth: 2 -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: false -PenaltyBreakBeforeFirstCallParameter: 1 -PenaltyBreakComment: 300 -PenaltyBreakFirstLessLess: 120 -PenaltyBreakString: 1000 -PenaltyExcessCharacter: 1000000 -PenaltyReturnTypeOnItsOwnLine: 200 -PointerAlignment: Left -ReflowComments: true -SortIncludes: false -SpaceAfterCStyleCast: false -SpaceBeforeAssignmentOperators: true -SpaceBeforeParens: ControlStatements -SpaceInEmptyParentheses: false -SpacesBeforeTrailingComments: 2 -SpacesInAngles: false -SpacesInContainerLiterals: true -SpacesInCStyleCastParentheses: false -SpacesInParentheses: false -SpacesInSquareBrackets: false -Standard: Cpp11 -TabWidth: 8 -UseTab: Never -JavaScriptQuotes: Leave -... - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 19b6574087..ca3d6a08ec 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,6 +46,9 @@ When you wish to contribute to the code base, please consider the following guid git checkout -b my-fix-branch master ``` * Create your patch, **including appropriate test cases**. +* Enable `NMODL_FORMATTING` and `NMODL_PRECOMMIT` CMake variables + to ensure that your change follows coding conventions of this project. + Please see [README.md](./README.md) for more information. * Run the full test suite, and ensure that all tests pass. * Commit your changes using a descriptive commit message. diff --git a/README.md b/README.md index d4db5e3793..3ccc3c512a 100644 --- a/README.md +++ b/README.md @@ -132,8 +132,44 @@ You can independently run lexer, parser or visitors as: ./bin/nmodl_visitor --file ../test/input/channel.mod ``` +#### Development Conventions -#### Running Test +Enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` +CMake variables to ensure that your contributions follow +the coding conventions of this project. + +##### Usage +```cmake +cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON <path> +``` + +The first variable provides the following additional targets to format +C, C++, and CMake files: + +``` +make clang-format cmake-format +``` + +The second option activates Git hooks that will discard commits that +do not comply with coding conventions of this project. + + +##### Requirements + +These 2 CMake variables require additional utilities: + +* [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) +* [cmake-format](https://github.com/cheshirekow/cmake_format) Python package +* [pre-commit](https://pre-commit.com/) Python package + +_ClangFormat_ can be installed on Linux thanks +to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a +[brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) +to install ClangFormat 7. + +_cmake-format_ and _pre-commit_ utilities can be installed with *pip*. + +#### Running Tests You can run unit tests as: diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index cb75e708d6..a11ae28ecd 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) -project(nmodl CXX) +project(NMODL CXX) #============================================================================= # CMake common project settings @@ -19,6 +19,22 @@ message(STATUS "CHECKING FOR FLEX/BISON") find_package(FLEX 2.6 REQUIRED) find_package(BISON 3.0 REQUIRED) +#============================================================================= +# HPC Coding Conventions +#============================================================================= +set(NMODL_ClangFormat_EXCLUDES_RE ".*/ext/.*$$" + CACHE STRING + "list of regular expressions to exclude C/C++ files from formatting" + FORCE) +set(NMODL_CMakeFormat_EXCLUDES_RE ".*/ext/.*$$" + CACHE STRING + "list of regular expressions to exclude CMake files from formatting" + FORCE) +set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen + CACHE STRING "list of CMake targets to build before formatting C++ code" + FORCE) +add_subdirectory(cmake/hpc-coding-conventions/cpp) + #============================================================================= # Include cmake modules #============================================================================= @@ -27,7 +43,6 @@ include(GitRevision) include(FlexHelper) include(CompilerHelper) include(ClangTidyHelper) -include(FindClangFormat) include(FindPythonModule) #============================================================================= @@ -116,13 +131,3 @@ set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ --show-possibly-lost=no") include (CTest) add_subdirectory(test) - -#============================================================================= -# Clang format target -#============================================================================= -if(CLANG_FORMAT_FOUND) - add_custom_target(clang-format - COMMAND - ${CLANG_FORMAT_EXECUTABLE} -i - --style=file ${FILES_FOR_CLANG_FORMAT}) -endif() diff --git a/cmake/nmodl/FindClangFormat.cmake b/cmake/nmodl/FindClangFormat.cmake deleted file mode 100644 index 6d4c93b8bc..0000000000 --- a/cmake/nmodl/FindClangFormat.cmake +++ /dev/null @@ -1,84 +0,0 @@ -# -#.rst: -# FindClangFormat -# --------------- -# -# The module defines the following variables -# -# ``CLANG_FORMAT_EXECUTABLE`` -# Path to clang-format executable -# ``CLANG_FORMAT_FOUND`` -# True if the clang-format executable was found. -# ``CLANG_FORMAT_VERSION`` -# The version of clang-format found -# ``CLANG_FORMAT_VERSION_MAJOR`` -# The clang-format major version if specified, 0 otherwise -# ``CLANG_FORMAT_VERSION_MINOR`` -# The clang-format minor version if specified, 0 otherwise -# ``CLANG_FORMAT_VERSION_PATCH`` -# The clang-format patch version if specified, 0 otherwise -# ``CLANG_FORMAT_VERSION_COUNT`` -# Number of version components reported by clang-format -# -# Example usage: -# -# .. code-block:: cmake -# -# find_package(ClangFormat) -# if(CLANG_FORMAT_FOUND) -# message("clang-format executable found: ${CLANG_FORMAT_EXECUTABLE}\n" -# "version: ${CLANG_FORMAT_VERSION}") -# endif() - -find_program(CLANG_FORMAT_EXECUTABLE - NAMES clang-format clang-format-7 - clang-format-6.0 clang-format-5.0 - clang-format-4.0 clang-format-3.9 - clang-format-3.8 clang-format-3.7 - clang-format-3.6 clang-format-3.5 - clang-format-3.4 clang-format-3.3 - DOC "clang-format executable" - ) -mark_as_advanced(CLANG_FORMAT_EXECUTABLE) - -# Extract version from command "clang-format -version" -if(CLANG_FORMAT_EXECUTABLE) - execute_process(COMMAND ${CLANG_FORMAT_EXECUTABLE} -version - OUTPUT_VARIABLE clang_format_version - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if (clang_format_version MATCHES "^clang-format version .*") - # clang_format_version sample: "clang-format version 3.9.1-4ubuntu3~16.04.1 (tags/RELEASE_391/rc2)" - string(REGEX REPLACE - "clang-format version ([.0-9]+).*" "\\1" - CLANG_FORMAT_VERSION "${clang_format_version}") - # CLANG_FORMAT_VERSION sample: "3.9.1" - - # Extract version components - string(REPLACE "." ";" clang_format_version "${CLANG_FORMAT_VERSION}") - list(LENGTH clang_format_version CLANG_FORMAT_VERSION_COUNT) - if(CLANG_FORMAT_VERSION_COUNT GREATER 0) - list(GET clang_format_version 0 CLANG_FORMAT_VERSION_MAJOR) - else() - set(CLANG_FORMAT_VERSION_MAJOR 0) - endif() - if(CLANG_FORMAT_VERSION_COUNT GREATER 1) - list(GET clang_format_version 1 CLANG_FORMAT_VERSION_MINOR) - else() - set(CLANG_FORMAT_VERSION_MINOR 0) - endif() - if(CLANG_FORMAT_VERSION_COUNT GREATER 2) - list(GET clang_format_version 2 CLANG_FORMAT_VERSION_PATCH) - else() - set(CLANG_FORMAT_VERSION_PATCH 0) - endif() - endif() - unset(clang_format_version) -endif() - -if(CLANG_FORMAT_EXECUTABLE) - set(CLANG_FORMAT_FOUND TRUE) -else() - set(CLANG_FORMAT_FOUND FALSE) -endif() \ No newline at end of file diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions new file mode 160000 index 0000000000..858c6c8742 --- /dev/null +++ b/cmake/nmodl/hpc-coding-conventions @@ -0,0 +1 @@ +Subproject commit 858c6c874281d3da281151510eaa13e0f70f44cd diff --git a/src/nmodl/ast/CMakeLists.txt b/src/nmodl/ast/CMakeLists.txt deleted file mode 100644 index 07e879079f..0000000000 --- a/src/nmodl/ast/CMakeLists.txt +++ /dev/null @@ -1,8 +0,0 @@ -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 26a405830c..ab1709ddd8 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -25,12 +25,3 @@ add_library(codegen ${CODEGEN_SOURCE_FILES}) add_dependencies(codegen lexer util) - -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${CODEGEN_SOURCE_FILES} - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 5daef3badb..9934ae5cf5 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -8,8 +8,14 @@ file(GLOB TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" file(GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py") -if(CLANG_FORMAT_FOUND AND CLANG_FORMAT_VERSION_MAJOR GREATER_EQUAL 4) - set(CODE_GENERATOR_OPTS --clang-format=${CLANG_FORMAT_EXECUTABLE}) +if(ClangFormat_FOUND AND ClangFormat_VERSION_MAJOR GREATER_EQUAL 4) + set(CODE_GENERATOR_OPTS --clang-format=${ClangFormat_EXECUTABLE}) + if(nmodl_ClangFormat_OPTIONS) + set(CODE_GENERATOR_OPTS + ${CODE_GENERATOR_OPTS} + --clang-format-opts + ${nmodl_ClangFormat_OPTIONS}) + endif(nmodl_ClangFormat_OPTIONS) endif() add_custom_command( diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 1f18e0a6d4..ad94612279 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -3,6 +3,7 @@ import logging import os from pathlib import Path +import shlex import shutil import subprocess import tempfile @@ -16,10 +17,15 @@ logging.basicConfig(level=logging.INFO, format='%(message)s') parser = argparse.ArgumentParser() -parser.add_argument('--clang-format') +parser.add_argument('--clang-format', help="Path to clang-format executable") +parser.add_argument('--clang-format-opts', help="Path to clang-format executable", nargs='+') parser.add_argument('--base-dir') args = parser.parse_args() clang_format = args.clang_format +if clang_format: + clang_format = [clang_format] + if args.clang_format_opts: + clang_format += args.clang_format_opts # parse nmodl definition file and get list of abstract nodes nodes = LanguageParser("nmodl.yaml").parse_file() @@ -54,7 +60,7 @@ os.write(fd, content.encode('utf-8')) os.close(fd) if clang_format: - subprocess.check_call([clang_format, '-i', tmp_path]) + subprocess.check_call(clang_format + ['-i', tmp_path]) if not filecmp.cmp(str(filename), tmp_path): shutil.move(tmp_path, filename) updated_files.append(str(fn.name)) @@ -63,7 +69,7 @@ fd.write(content) updated_files.append(str(fn.name)) if clang_format: - subprocess.check_call([clang_format, '-i', filename]) + subprocess.check_call(clang_format + ['-i', filename]) if updated_files: logging.info(' Updating out of date template files : %s', ' '.join(updated_files)) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index bd374634bf..1e692a15e1 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -206,21 +206,9 @@ add_executable(c_lexer main_c.cpp) target_link_libraries(nmodl_lexer lexer) target_link_libraries(c_lexer lexer) -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${LEXER_SOURCE_FILES} - ${CMAKE_CURRENT_SOURCE_DIR}/main_nmodl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/main_c.cpp - ${DIFFEQ_DRIVER_FILES} - ${C_DRIVER_FILES} - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) - #============================================================================= # Install executable #============================================================================= install(TARGETS nmodl_lexer DESTINATION bin/lexer) install(TARGETS c_lexer DESTINATION bin/lexer) +add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index e64f871d60..0e341a61ab 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -8,15 +8,6 @@ set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.cpp ) -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${NMODL_SOURCE_FILES} - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) - #============================================================================= # Add executables #============================================================================= diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 2f502dfe97..22d709739d 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -3,7 +3,7 @@ #============================================================================= # lexer library links with all parser related files -# so no eed to have parser as a separate library +# so no need to have parser as a separate library add_executable(nmodl_parser main_nmodl.cpp) add_executable(c_parser main_c.cpp) @@ -11,17 +11,6 @@ add_executable(c_parser main_c.cpp) target_link_libraries(nmodl_parser lexer) target_link_libraries(c_parser lexer) -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/main_nmodl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_context.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/diffeq_context.hpp - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) - #============================================================================= # Install executable #============================================================================= diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 82ae10f74d..d1ca3a001b 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -22,12 +22,3 @@ set_property(TARGET printer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_library(printer STATIC $<TARGET_OBJECTS:printer_obj>) - -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${PRINTER_SOURCE_FILES} - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) \ No newline at end of file diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index 254ecd3a3b..b9da83dbba 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -28,12 +28,3 @@ add_library(symtab $<TARGET_OBJECTS:symtab_obj>) add_dependencies(symtab lexer) - -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${SYMTAB_SOURCES} ${SYMTAB_HEADERS} - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) \ No newline at end of file diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 3e10d3fcff..f139fea9ac 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -2,6 +2,8 @@ # Utility sources #============================================================================= set(UTIL_SOURCE_FILES + ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.cpp @@ -24,14 +26,3 @@ set_property(TARGET util_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_library(util STATIC $<TARGET_OBJECTS:util_obj>) - -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${UTIL_SOURCE_FILES} - ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 8db54d94b9..8761292c10 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -1,7 +1,9 @@ #include <memory> +// clang-format off #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" +// clang-format on using logger_type = std::shared_ptr<spdlog::logger>; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 640967d82d..7737505141 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -68,17 +68,6 @@ add_dependencies(visitor lexer util) add_executable(nmodl_visitor main.cpp) target_link_libraries(nmodl_visitor printer visitor symtab util lexer) -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp - ${VISITOR_SOURCES} ${VISITOR_HEADERS} - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) - - #============================================================================= # Install executable #============================================================================= diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 166864afff..a981603406 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -49,20 +49,3 @@ set_tests_properties(Visitor PROPERTIES add_test (NAME Pybind COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) set_tests_properties(Pybind PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) - -#============================================================================= -# Files for clang-format -#============================================================================= -set(FILES_FOR_CLANG_FORMAT - ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.h - ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modtoken/modtoken.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/lexer/tokens.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/parser/parser.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor/visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/printer/printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab/symbol_table.cpp - ${FILES_FOR_CLANG_FORMAT} - PARENT_SCOPE -) From b6922cd755c35031c92a3ec3fcc8dc0d5848dd9b Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Wed, 13 Feb 2019 19:44:47 +0100 Subject: [PATCH 132/871] JSON schema update and added python notebook example : - to_json method support extra parameter to expand key-value into separate nodes (for d3 visualization) - json printer and visitor updated with expand_keys method - separated ast and visitor tests for pybind - "value" key changed to "name" in json schema - added jupyter notebook in docs/notebook directory - fixed setup.py to allow pip install . Change-Id: Ifc91d92cbde3e00c3577c415c097ab6587999ff6 NMODL Repo SHA: BlueBrain/nmodl@ed6074cbc3a61776fea22959f1f48c5bcfb7ce22 --- doc/notebooks/nmodl-python-tutorial.ipynb | 587 ++++++++++++++++++ doc/notebooks/tree.js | 190 ++++++ setup.py | 8 +- src/nmodl/language/templates/json_visitor.hpp | 1 + src/nmodl/printer/json_printer.cpp | 13 +- src/nmodl/printer/json_printer.hpp | 15 +- src/nmodl/pybind/pynmodl.cpp | 4 +- src/nmodl/visitors/visitor_utils.cpp | 5 +- src/nmodl/visitors/visitor_utils.hpp | 2 +- test/nmodl/transpiler/printer/printer.cpp | 21 +- test/nmodl/transpiler/pybind/conftest.py | 3 +- test/nmodl/transpiler/pybind/test_ast.py | 41 -- test/nmodl/transpiler/pybind/test_symtab.py | 1 - test/nmodl/transpiler/pybind/test_visitor.py | 60 ++ test/nmodl/transpiler/visitor/visitor.cpp | 14 +- 15 files changed, 901 insertions(+), 64 deletions(-) create mode 100644 doc/notebooks/nmodl-python-tutorial.ipynb create mode 100644 doc/notebooks/tree.js create mode 100644 test/nmodl/transpiler/pybind/test_visitor.py diff --git a/doc/notebooks/nmodl-python-tutorial.ipynb b/doc/notebooks/nmodl-python-tutorial.ipynb new file mode 100644 index 0000000000..ba05090872 --- /dev/null +++ b/doc/notebooks/nmodl-python-tutorial.ipynb @@ -0,0 +1,587 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Visualization Library Setup" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from IPython.display import display, Javascript, HTML\n", + "import json" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%javascript\n", + "require.config({\n", + " paths: {\n", + " d3: \"https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min\"\n", + " }\n", + "});" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Javascript(filename='tree.js')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "HTML(\"\"\"\n", + "<style>\n", + "\n", + ".node {\n", + " cursor: pointer;\n", + "}\n", + "\n", + ".node circle {\n", + " fill: #d49c9c;\n", + " stroke: #8c6666;\n", + " stroke-width: 1.5px;\n", + "}\n", + "\n", + ".node text {\n", + " font-size: 11px !important;\n", + " font-family: sans-serif;\n", + " fill: #4545b5;\n", + "}\n", + "\n", + ".link {\n", + " fill: none;\n", + " stroke: #efcece;\n", + " stroke: #efceed;\n", + " stroke-width: 2px;\n", + "}\n", + "\n", + ".templink {\n", + " fill: none;\n", + " stroke: red;\n", + " stroke-width: 2px;\n", + "}\n", + "</style>\n", + "\"\"\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "## NMODL Python Interface Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Introduction\n", + "\n", + "NMODL is a code generation framework for NEURON Modeling Language. It is primarily designed to support optimised code generation backends for morphologically detailed neuron simulators. It provides high level Python interface that can be used for model introspection as well as performing various analysis on underlying model.\n", + "\n", + "The main goals of the NMODL framework are :\n", + "\n", + "* Support for full NMODL specification\n", + "* Providing modular tools for lexing, parsing and analysis\n", + "* Optimised code generation for modern architectures\n", + "* Compatibility with exisiting simulators\n", + "* Ability to implement new simulator backends with minimal efforts\n", + "\n", + "This tutorial provides introduction to python API with examples." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Installation \n", + "<a id='installation'></a> NMODL can be installed as CMake project or using python setuptools. See README.md for detailed installation instructions. For example :\n", + "\n", + "```bash\n", + "python3 -m venv myenv\n", + ". myenv/bin/activate\n", + "pip3 install nmodl_source_directory/\n", + "```\n", + "\n", + "With this you should have nmodl installed." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Parsing Model And Constructing AST\n", + "\n", + "Once the NMODL is setup properly we should be able to import `nmodl` module :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you see any issues, check the [installation section](#installation). Lets take an example of a channelCaDynamics :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "channel = \"\"\"\n", + "NEURON {\n", + " SUFFIX CaDynamics\n", + " USEION ca READ ica WRITE cai\n", + " RANGE decay, gamma, minCai, depth\n", + "}\n", + "\n", + "UNITS {\n", + " (mV) = (millivolt)\n", + " (mA) = (milliamp)\n", + " FARADAY = (faraday) (coulombs)\n", + " (molar) = (1/liter)\n", + " (mM) = (millimolar)\n", + " (um) = (micron)\n", + "}\n", + "\n", + "PARAMETER {\n", + " gamma = 0.05 : percent of free calcium (not buffered)\n", + " decay = 80 (ms) : rate of removal of calcium\n", + " depth = 0.1 (um) : depth of shell\n", + " minCai = 1e-4 (mM)\n", + "}\n", + "\n", + "ASSIGNED {ica (mA/cm2)}\n", + "\n", + "INITIAL {\n", + " cai = minCai\n", + "}\n", + "\n", + "STATE {\n", + " cai (mM)\n", + "}\n", + "\n", + "BREAKPOINT { SOLVE states METHOD cnexp }\n", + "\n", + "DERIVATIVE states {\n", + " cai' = -(10000)*(ica*gamma/(2*FARADAY*depth)) - (cai - minCai)/decay\n", + "}\n", + "\n", + "FUNCTION foo() {\n", + " LOCAL temp\n", + " foo = 1.0 + gamma\n", + "}\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can parse any valid NMODL constructs using parsing interface. First, we have to create nmodl parser object using `nmodl::Driver` and then we can use `parse_string` method <a id='create-ast'></a>:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "driver = nmodl.Driver()\n", + "driver.parse_string(channel)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `parse_string` method will throw an exception with parsing error if input is invalid. Otherwise it returns `True` and internally creates [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) object. We can access the AST using `ast()` method :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "modast = driver.ast()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we simply print AST object, we can see JSON representation :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print ('%.100s' % modast) # only first 100 characters\n", + "import json\n", + "json_data = json.loads(nmodl.to_json(modast, True))\n", + "json_data_expand = json.loads(nmodl.to_json(modast, True, True))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Javascript(\"\"\"(function(element){\n", + " require(['draw_tree'], function(draw) { draw(element.get(0), %s) });\n", + " })(element);\"\"\" % json.dumps(json_data_expand) )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Querying AST object with Visitors\n", + "\n", + "One of the strength of NMODL python interface is access to inbuilt [Visitors](https://en.wikipedia.org/wiki/Visitor_pattern). One can perform different queries and analysis on AST using different visitors. Lets start with the examples of inbuilt visitors." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Lookup visitor\n", + "\n", + "As name suggest, lookup visitor allows to search different NMODL constructs in the AST. The `visitor` module provides access to inbuilt visitors. In order to use this visitor, we create an object of AstLookupVisitor :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from nmodl.dsl import visitor\n", + "from nmodl.dsl import ast\n", + "lookup_visitor = visitor.AstLookupVisitor()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Assuming we have created AST object (as shown [here](#create-ast)), we can search for any NMODL construct in the AST using AstLookupVisitor. For example, to find out `STATE` block in the mod file, we can simply do:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "states = lookup_visitor.lookup(modast, ast.AstNodeType.STATE_BLOCK)\n", + "for state in states:\n", + " print (nmodl.to_nmodl(state))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We have used `to_nmodl` helper function to print AST object in NMODL format. Note that the output of `to_nmodl` should be same as input except for comments (?, :, COMMENT). There are very few edge cases where the NMODL output could slightly differ and this is considered as bug. This is being addressed by testing entire [ModelDB](https://senselab.med.yale.edu/modeldb/) database.\n", + "\n", + "Using AstLookupVisitor we can introspect NMODL constructs at any level of details. Below are some examples to find out different constructs in the example mod file:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "odes = lookup_visitor.lookup(modast, ast.AstNodeType.DIFF_EQ_EXPRESSION)\n", + "primes = lookup_visitor.lookup(modast, ast.AstNodeType.PRIME_NAME)\n", + "range_vars = lookup_visitor.lookup(modast, ast.AstNodeType.RANGE_VAR)\n", + "parameters = lookup_visitor.lookup(modast, ast.AstNodeType.PARAM_ASSIGN)\n", + "units = lookup_visitor.lookup(modast, ast.AstNodeType.UNIT)\n", + "\n", + "if odes:\n", + " print(\"%d differential equation(s) exist : \" % len(odes))\n", + " for ode in odes:\n", + " print (\"\\t %s \" % nmodl.to_nmodl(ode))\n", + " \n", + "if primes:\n", + " print(\"%d prime variables exist :\" % len(primes), end='')\n", + " for prime in primes:\n", + " print (\" %s\" % nmodl.to_nmodl(prime), end='')\n", + " print()\n", + "\n", + "if range_vars:\n", + " print(\"%d range variables exist :\" % len(range_vars), end='')\n", + " for range_var in range_vars:\n", + " print (\" %s\" % nmodl.to_nmodl(range_var), end='')\n", + " print()\n", + "\n", + "if parameters:\n", + " print(\"%d parameter variables exist :\" % len(parameters), end='')\n", + " for range_var in range_vars:\n", + " print (\" %s\" % nmodl.to_nmodl(range_var), end='')\n", + " print()\n", + " \n", + "if units:\n", + " print(\"%d units uses :\" % len(units), end='')\n", + " for unit in units:\n", + " print (\" %s\" % nmodl.to_nmodl(unit), end='')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Apart from performing lookup on whole AST object (i.e. entire NMODL file), we can perform analysis on the specific construct. Lets take a synthetic example : say we want to find out all assignment statements in function `foo`. If we use lookup visitor on AST, it will returnn all statements in the mod file (e.g. including DERIVATIVE block). To avoid this, we can first find FUNCTION blocks and then search for statements within that block :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "functions = lookup_visitor.lookup(modast, ast.AstNodeType.FUNCTION_BLOCK)\n", + "function = functions[0] # first function\n", + "\n", + "# expression statements include assignments\n", + "new_lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.EXPRESSION_STATEMENT)\n", + "\n", + "# using accept method of node\n", + "function.accept(new_lookup_visitor)\n", + "statements = new_lookup_visitor.get_nodes()\n", + "\n", + "for statement in statements:\n", + " print (nmodl.to_nmodl(statement))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Every AST node provides `accept` method that takes visitor object as parameter. In above example, we used `accept` method of `FunctionBock` node. This allows to run a given visitor on a specific node. `AstLookupVisitor` provides `get_nodes()` method that can be used to retrive the result of visitor. List of all `AstNodeType` can be found [here (todo)](####)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Symbol Table Visitor\n", + "\n", + "Symbol table visitor is used to find out all variables and their usage in mod file. To use this, first create a visitor object as: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from nmodl.dsl import symtab\n", + "\n", + "symv = symtab.SymtabVisitor()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Once the visitor object is created, we can run visitor on AST object to populate symbol table. Symbol table provides print method that can be used to print whole symbol table : " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "symv.visit_program(modast)\n", + "table = modast.get_symbol_table()\n", + "table_s = str(table)\n", + "\n", + "print (table_s)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can query for variables in the symbol table based on name of variable: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "cai = table.lookup('cai')\n", + "print (cai)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When we print the symbol, all it's properties get printed. For example, in above case the `cai` variable is used in :\n", + "* differential equation (prime)\n", + "* state block\n", + "* assigned block\n", + "* use ion statement\n", + "\n", + "We can also query based on the kind of variables. For example, to find out all range variables we can use `get_variables_with_properties` method with symbol property as an argument:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "range_vars = table.get_variables_with_properties(symtab.NmodlType.range_var)\n", + "for var in range_vars:\n", + " print (var)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can also query with multiple properties. For example, to find out read or write ion variables we can use (second argument `False` indicates any one property):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ions_var = table.get_variables_with_properties(symtab.NmodlType.read_ion_var | symtab.NmodlType.write_ion_var, False)\n", + "for var in ions_var:\n", + " print (var)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Custom AST visitor\n", + "\n", + "If predefined visitors are limited, we can implement new visitor using AstVisitor interface. Lets say we want to implement a visitor that will print every floating point number in MOD file. Here is how we can do this: " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from nmodl.dsl import ast, visitor\n", + "\n", + "class DoubleVisitor(visitor.AstVisitor):\n", + "\n", + " def visit_double(self, node):\n", + " print (node.eval()) # or, can use nmodl.to_nmodl(node) \n", + " \n", + "d_visitor = DoubleVisitor()\n", + "modast.accept(d_visitor)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `AstVisitor` base class provides all necessary methods to traverse different ast nodes. New visitors will inherit from `AstVisitor` and implement only those method where we want different behaviour. For example, in the above case we want to visit ast nodes of type `Double` and print their value. To achieve this we implemented associated method of `Double` node i.e. `visit_double`. When we call `accept` method on the ast object, the entire AST tree will be visited (by `AstVisitor`). But whenever double node type will encounter in AST, the control will be handed back to `DoubleVisitor.visit_double` method. \n", + "\n", + "Lets implement the example of lookup visitor to print parameters with values :" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class ParameterVisitor(visitor.AstVisitor):\n", + " \n", + " def __init__(self):\n", + " visitor.AstVisitor.__init__(self)\n", + " self.in_parameter = False\n", + " \n", + " def visit_param_block(self, node):\n", + " self.in_parameter = True\n", + " node.visit_children(self)\n", + " self.in_parameter = False\n", + "\n", + " def visit_name(self, node):\n", + " if self.in_parameter:\n", + " print (nmodl.to_nmodl(node))\n", + " \n", + " def visit_double(self, node):\n", + " if self.in_parameter:\n", + " print (node.eval())\n", + "\n", + " def visit_integer(self, node):\n", + " if self.in_parameter:\n", + " print (node.eval())\n", + "\n", + "param_visitor = ParameterVisitor()\n", + "modast.accept(param_visitor)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/notebooks/tree.js b/doc/notebooks/tree.js new file mode 100644 index 0000000000..5b3fd335b8 --- /dev/null +++ b/doc/notebooks/tree.js @@ -0,0 +1,190 @@ +define('draw_tree', ['d3'], function(d3) { + + /// draw d3 tree to given div container + function draw_ast(container, jsontree) { + + // container margin + var margin = { + top: 20, + right: 20, + bottom: 20, + left: 20 + }; + + /// diameter equal to container width (e.g. notebook cell) + var diameter = d3.select(container).node().getBoundingClientRect().width; + + /// width / ehight for the box + var width = diameter; + var height = diameter; + + /// animation duration + var duration = 200; + + /// unique ids for nodes + var id = 0; + + /// define tree layout + // see: https://d3-wiki.readthedocs.io/zh_CN/master/Tree-Layout/ + var tree = d3.layout.tree() + .size([360, diameter / 2 - 80]) + .separation(function(a, b) { + return (a.parent == b.parent ? 1 : 10) / a.depth; + }); + var diagonal = d3.svg.diagonal.radial() + .projection(function(d) { + return [d.y, d.x / 180 * Math.PI]; + }); + + /// create svg element inside container + var tree_svg = d3.select(container).append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")"); + + /// start drawing tree + jsontree.x0 = width / 2; + jsontree.y0 = height / 2; + update(jsontree, tree_svg, jsontree); + + /// toggle children state on click + function click(d, destnode, jsonroot) { + if (d.children) { + d._children = d.children; + d.children = null; + } else { + d.children = d._children; + d._children = null; + } + update(d, destnode, jsonroot); + } + + /// collapse nodes on click + function collapse(d) { + if (d.children) { + d._children = d.children; + d._children.forEach(collapse); + d.children = null; + } + } + + function update(source, destnode, jsonroot) { + /// compute the new tree layout + var nodes = tree.nodes(jsonroot); + var links = tree.links(nodes); + + /// normalize for fixed-depth + nodes.forEach(function(d) { + d.y = d.depth * 80; + }); + + /// update the nodes + var node = destnode.selectAll("g.node") + .data(nodes, function(d) { + return d.id || (d.id = ++id); + }); + + /// enter any new nodes at the parent's previous position + var nodeEnter = node.enter().append("g") + .attr("class", "node") + .on("click", function(d) { + click(d, destnode, jsonroot); + }); + + nodeEnter.append("circle") + .attr("r", 1e-6) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }); + + nodeEnter.append("text") + .attr("x", 10) + .attr("dy", ".55em") + .attr("text-anchor", "start") + .text(function(d) { + return d.name; + }) + .style("fill-opacity", 1e-6); + + /// transition nodes to their new position + var nodeUpdate = node.transition() + .duration(duration) + .attr("transform", function(d) { + return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; + }) + + nodeUpdate.select("circle") + .attr("r", 4.5) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }); + + nodeUpdate.select("text") + .style("fill-opacity", 1) + .attr("transform", function(d) { + return d.x < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length + 50) + ")"; + }); + + /// appropriate transform + var nodeExit = node.exit().transition() + .duration(duration) + .remove(); + + nodeExit.select("circle") + .attr("r", 1e-6); + + nodeExit.select("text") + .style("fill-opacity", 1e-6); + + /// update the links + var link = destnode.selectAll("path.link") + .data(links, function(d) { + return d.target.id; + }); + + /// enter any new links at the parent's previous position + link.enter().insert("path", "g") + .attr("class", "link") + .attr("d", function(d) { + var o = { + x: source.x0, + y: source.y0 + }; + return diagonal({ + source: o, + target: o + }); + }); + + /// transition links to their new position + link.transition() + .duration(duration) + .attr("d", diagonal); + + /// transition exiting nodes to the parent's new position + link.exit().transition() + .duration(duration) + .attr("d", function(d) { + var o = { + x: source.x, + y: source.y + }; + return diagonal({ + source: o, + target: o + }); + }) + .remove(); + + /// stash the old positions for transition + nodes.forEach(function(d) { + d.x0 = d.x; + d.y0 = d.y; + }); + } + + } + + return draw_ast; +}); \ No newline at end of file diff --git a/setup.py b/setup.py index d20aa8e5d4..c5f34dcd5e 100644 --- a/setup.py +++ b/setup.py @@ -74,7 +74,13 @@ def build_extension(self, ext): zip_safe=False, setup_requires=['jinja2>=2.10', 'PyYAML>=3.13', - 'sympy>=1.2' + 'sympy>=1.2', + 'pytest>=4.0.0' + ], + install_requires=['jinja2>=2.10', + 'PyYAML>=3.13', + 'sympy>=1.2', + 'pytest>=4.0.0' ], tests_require=['pytest>=4.0.0'] ) diff --git a/src/nmodl/language/templates/json_visitor.hpp b/src/nmodl/language/templates/json_visitor.hpp index 04169d57b8..4df1b73f2e 100644 --- a/src/nmodl/language/templates/json_visitor.hpp +++ b/src/nmodl/language/templates/json_visitor.hpp @@ -17,6 +17,7 @@ class JSONVisitor : public AstVisitor { void flush() { printer->flush(); } void compact_json(bool flag) { printer->compact_json(flag); } + void expand_keys(bool flag) { printer->expand_keys(flag); } {% for node in nodes %} void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index e06a958cf3..a7c1bb1e5a 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -18,26 +18,31 @@ JSONPrinter::JSONPrinter(const std::string& filename) { } /// Add node to json (typically basic type) -void JSONPrinter::add_node(std::string value, const std::string& name) { +void JSONPrinter::add_node(std::string value, const std::string& key) { if (!block) { auto text = "Block not initialized (push_block missing?)"; throw std::logic_error(text); } json j; - j[name] = value; + j[key] = value; block->front().push_back(j); } /// Add new json object (typically start of new block) /// name here is type of new block encountered -void JSONPrinter::push_block(const std::string& name) { +void JSONPrinter::push_block(const std::string& value, const std::string& key) { if (block) { stack.push(block); } json j; - j[name] = json::array(); + if (expand) { + j[key] = value; + j[child_key] = json::array(); + } else { + j[value] = json::array(); + } block = std::shared_ptr<json>(new json(j)); } diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 8ac519d577..5a4494c586 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -45,6 +45,13 @@ class JSONPrinter { /// true if need to print json in compact format bool compact = false; + /// true if we need to expand keys i.e. add separate key/value + /// pair for node name and it's children + bool expand = false; + + /// json key for children + const std::string child_key = "children"; + public: JSONPrinter(const std::string& filename); @@ -60,8 +67,8 @@ class JSONPrinter { flush(); } - void push_block(const std::string& name); - void add_node(std::string value, const std::string& name = "value"); + void push_block(const std::string& value, const std::string& key = "name"); + void add_node(std::string value, const std::string& key = "name"); void pop_block(); void flush(); @@ -69,6 +76,10 @@ class JSONPrinter { void compact_json(bool flag) { compact = flag; } + + void expand_keys(bool flag) { + expand = flag; + } }; #endif diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 4ba6aac685..865178b5c9 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -44,7 +44,9 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.def("to_nmodl", nmodl::to_nmodl); m_nmodl.def("to_json", nmodl::to_json, - py::arg("node"), py::arg("compact") = false); + py::arg("node"), + py::arg("compact") = false, + py::arg("expand") = false); init_visitor_module(m_nmodl); init_ast_module(m_nmodl); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 92e055c6fe..7a6986b48f 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -87,13 +87,14 @@ namespace nmodl { } - std::string to_json(ast::AST* node, bool compact) { + std::string to_json(ast::AST* node, bool compact, bool expand) { std::stringstream stream; JSONVisitor v(stream); v.compact_json(compact); + v.expand_keys(expand); node->accept(&v); v.flush(); return stream.str(); } -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index fd7161064e..492fc43603 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -32,7 +32,7 @@ namespace nmodl { std::string to_nmodl(ast::AST* node); /** Given AST node, return the JSON string representation */ - std::string to_json(ast::AST* node, bool compact = false); + std::string to_json(ast::AST* node, bool compact = false, bool expand = false); } // namespace nmodl diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 406eb52cd8..0678c027af 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -16,7 +16,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { p.pop_block(); p.flush(); - auto result = R"({"A":[{"value":"B"}]})"; + auto result = R"({"A":[{"name":"B"}]})"; REQUIRE(ss.str() == result); } @@ -34,7 +34,24 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { p.pop_block(); p.flush(); - auto result = R"({"A":[{"value":"B"},{"value":"C"},{"D":[{"value":"E"}]}]})"; + auto result = R"({"A":[{"name":"B"},{"name":"C"},{"D":[{"name":"E"}]}]})"; + REQUIRE(ss.str() == result); + } + + SECTION("Test with nodes as separate tags") { + std::stringstream ss; + JSONPrinter p(ss); + p.compact_json(true); + p.expand_keys(true); + + p.push_block("A"); + p.add_node("B"); + p.push_block("D"); + p.add_node("E"); + p.pop_block(); + p.flush(); + + auto result = R"({"children":[{"name":"B"},{"children":[{"name":"E"}],"name":"D"}],"name":"A"})"; REQUIRE(ss.str() == result); } } diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/pybind/conftest.py index 4b68d3c2e3..e30e487673 100644 --- a/test/nmodl/transpiler/pybind/conftest.py +++ b/test/nmodl/transpiler/pybind/conftest.py @@ -1,5 +1,4 @@ import pytest -import nmodl from nmodl.dsl import Driver CHANNEL = """NEURON { @@ -28,4 +27,4 @@ def ch_ast(): d = Driver() d.parse_string(CHANNEL) - return d.ast() \ No newline at end of file + return d.ast() diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py index 5352846459..3d55ff7426 100644 --- a/test/nmodl/transpiler/pybind/test_ast.py +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -7,44 +7,3 @@ def test_empty_program(self): pnode = ast.Program() assert str(pnode) == '{"Program":[]}' - -def test_lookup_visitor(ch_ast): - lookup_visitor = visitor.AstLookupVisitor() - eqs = lookup_visitor.lookup(ch_ast, ast.AstNodeType.DIFF_EQ_EXPRESSION) - eq_str = nmodl.dsl.to_nmodl(eqs[0]) - assert eq_str == "m' = mInf-m" - - -def test_lookup_visitor_constructor(ch_ast): - lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.DIFF_EQ_EXPRESSION) - ch_ast.accept(lookup_visitor) - eqs = lookup_visitor.get_nodes() - eq_str = nmodl.dsl.to_nmodl(eqs[0]) - eq_json = nmodl.dsl.to_json(eqs[0], True) - assert eq_str == "m' = mInf-m" - assert '"DiffEqExpression":[{"BinaryExpression"' in eq_json - - -def test_custom_visitor(ch_ast): - - class StateVisitor(visitor.AstVisitor): - def __init__(self): - visitor.AstVisitor.__init__(self) - self.in_state = False - self.states = [] - - def visit_state_block(self, node): - self.in_state = True - node.visit_children(self) - self.in_state = False - - def visit_name(self, node): - if self.in_state: - self.states.append(nmodl.dsl.to_nmodl(node)) - - myvisitor = StateVisitor() - ch_ast.accept(myvisitor) - - assert len(myvisitor.states) is 2 - assert myvisitor.states[0] == "m" - assert myvisitor.states[1] == "h" diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py index 1f1d1b27a8..5ac3a05a2f 100644 --- a/test/nmodl/transpiler/pybind/test_symtab.py +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -1,5 +1,4 @@ import io -import nmodl from nmodl.dsl import ast, visitor, symtab def test_symtab(ch_ast): diff --git a/test/nmodl/transpiler/pybind/test_visitor.py b/test/nmodl/transpiler/pybind/test_visitor.py new file mode 100644 index 0000000000..c337d75f09 --- /dev/null +++ b/test/nmodl/transpiler/pybind/test_visitor.py @@ -0,0 +1,60 @@ +import nmodl +from nmodl.dsl import ast, visitor +import pytest + + +def test_lookup_visitor(ch_ast): + lookup_visitor = visitor.AstLookupVisitor() + eqs = lookup_visitor.lookup(ch_ast, ast.AstNodeType.DIFF_EQ_EXPRESSION) + eq_str = nmodl.dsl.to_nmodl(eqs[0]) + assert eq_str == "m' = mInf-m" + + +def test_lookup_visitor_constructor(ch_ast): + lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.DIFF_EQ_EXPRESSION) + ch_ast.accept(lookup_visitor) + eqs = lookup_visitor.get_nodes() + eq_str = nmodl.dsl.to_nmodl(eqs[0]) + + +def test_json_visitor(ch_ast): + lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.PRIME_NAME) + ch_ast.accept(lookup_visitor) + primes = lookup_visitor.get_nodes() + + # test compact json + prime_str = nmodl.dsl.to_nmodl(primes[0]) + prime_json = nmodl.dsl.to_json(primes[0], True) + assert prime_json == '{"PrimeName":[{"String":[{"name":"m"}]},{"Integer":[{"name":"1"}]}]}' + + # test json with expanded keys + result_json = nmodl.dsl.to_json(primes[0], True, True) + expected_json = ('{"children":[{"children":[{"name":"m"}],' + '"name":"String"},{"children":[{"name":"1"}],' + '"name":"Integer"}],"name":"PrimeName"}') + assert result_json == expected_json + + +def test_custom_visitor(ch_ast): + + class StateVisitor(visitor.AstVisitor): + def __init__(self): + visitor.AstVisitor.__init__(self) + self.in_state = False + self.states = [] + + def visit_state_block(self, node): + self.in_state = True + node.visit_children(self) + self.in_state = False + + def visit_name(self, node): + if self.in_state: + self.states.append(nmodl.dsl.to_nmodl(node)) + + myvisitor = StateVisitor() + ch_ast.accept(myvisitor) + + assert len(myvisitor.states) is 2 + assert myvisitor.states[0] == "m" + assert myvisitor.states[1] == "h" diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index b27afdc1ac..bfddbe4096 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1287,7 +1287,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"value":"D"},{"value":"U"},{"value":"D"}]})"; + R"({"DerivativeBlock":[{"name":"D"},{"name":"U"},{"name":"D"}]})"; THEN("Def-Use chains for individual usage is printed") { std::string input = reindent_text(nmodl_text); @@ -1311,7 +1311,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"value":"U"},{"value":"D"},{"value":"U"}]})"; + R"({"DerivativeBlock":[{"name":"U"},{"name":"D"},{"name":"U"}]})"; THEN("Verbatim block is considered as use of the variable") { std::string input = reindent_text(nmodl_text); @@ -1338,7 +1338,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"LD"}]},{"ELSE":[{"value":"D"}]}]}]})"; + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]},{"ELSE":[{"name":"D"}]}]}]})"; THEN("Def-Use chains should return NONE") { std::string input = reindent_text(nmodl_text); @@ -1363,7 +1363,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSE":[{"value":"U"},{"value":"D"}]}]}]})"; + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"U"},{"name":"D"}]}]}]})"; THEN("Def-Use chains should return USE") { std::string input = reindent_text(nmodl_text); @@ -1390,7 +1390,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"D"},{"value":"U"}]},{"ELSE":[{"value":"D"}]}]}]})"; + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"},{"name":"U"}]},{"ELSE":[{"name":"D"}]}]}]})"; THEN("Def-Use chains should return DEF") { std::string input = reindent_text(nmodl_text); @@ -1422,7 +1422,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"D"}]}]},{"value":"U"},{"value":"D"},{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSEIF":[{"value":"D"}]}]}]})"; + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"}]}]},{"name":"U"},{"name":"D"},{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSEIF":[{"name":"D"}]}]}]})"; THEN("Def-Use chains for individual usage is printed") { std::string input = reindent_text(nmodl_text); @@ -1463,7 +1463,7 @@ SCENARIO("Running defuse analyzer") { )"; std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"value":"LD"}]}]},{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSE":[{"value":"D"}]}]}]},{"ELSEIF":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"value":"IF"},{"ELSE":[{"value":"U"}]}]}]}]},{"value":"D"}]}]}]})"; + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]}]},{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"D"}]}]}]},{"ELSEIF":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"U"}]}]}]}]},{"name":"D"}]}]}]})"; THEN("Def-Use chains for nested statements calculated") { std::string input = reindent_text(nmodl_text); From a419ca4b78e195c76914097f6cf7767ae3bdb5fc Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Wed, 13 Feb 2019 15:25:16 +0100 Subject: [PATCH 133/871] add SympyConductanceVisitor - differentiates currents to generate CONDUCTANCE hints - disabled by default, to enable use flag --enable-sympy - generates statements for each write ion from USEION statements - and also for currents in NONSPECIFIC_CURRENT statements - if a current already has a CONDUCTANCE hint, does not modify it - after running in main there is an nmodl dump and a symtab update - (also made --verbose flag enable logger debug output) - added get_global_vars method to visitor_utils - updated SympySolver to also use this get_global_vars method - added unit tests to visitor tests - TODO: take into account functions that run during BREAKPOINT? - TODO: check AST parser bug with floats: (1.0/2.0) != (1/2) Change-Id: Ifc1a7db3b3cf888a0565c93a6f9a305b4938f56c NMODL Repo SHA: BlueBrain/nmodl@c50190a5fa6e7f3981234c50a30a04e91ca53d28 --- cmake/nmodl/CMakeLists.txt | 5 - cmake/nmodl/hpc-coding-conventions | 2 +- nmodl/ode.py | 80 ++- src/nmodl/codegen/codegen_naming.hpp | 1 + src/nmodl/nmodl/main.cpp | 18 + src/nmodl/visitors/CMakeLists.txt | 2 + .../visitors/sympy_conductance_visitor.cpp | 183 +++++ .../visitors/sympy_conductance_visitor.hpp | 60 ++ src/nmodl/visitors/sympy_solver_visitor.cpp | 34 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 8 +- src/nmodl/visitors/visitor_utils.cpp | 19 + src/nmodl/visitors/visitor_utils.hpp | 4 + test/nmodl/transpiler/visitor/visitor.cpp | 655 +++++++++++++++++- 13 files changed, 1014 insertions(+), 57 deletions(-) create mode 100644 src/nmodl/visitors/sympy_conductance_visitor.cpp create mode 100644 src/nmodl/visitors/sympy_conductance_visitor.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index a11ae28ecd..e386df2e75 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -45,11 +45,6 @@ include(CompilerHelper) include(ClangTidyHelper) include(FindPythonModule) -#============================================================================= -# Find clang-format -#============================================================================= -find_package(ClangFormat) - #============================================================================= # Find required python packages #============================================================================= diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 858c6c8742..2866ea5319 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 858c6c874281d3da281151510eaa13e0f70f44cd +Subproject commit 2866ea53196ef7f82d9535c1f55963c988631c3e diff --git a/nmodl/ode.py b/nmodl/ode.py index fcefdaebbb..2a78e77a6a 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -1,6 +1,11 @@ import sympy as sp -def integrate2c(diff_string, vars): +major, minor = (int(v) for v in sp.__version__.split(".")[:2]) +if not ((major >= 1) and (minor >= 2)): + raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") + + +def integrate2c(diff_string, t_var, dt_var, vars): """Analytically integrate supplied derivative, return solution as C code. Derivative should be of the form "x' = f(x)", @@ -11,6 +16,8 @@ def integrate2c(diff_string, vars): Args: diff_string: Derivative to be integrated e.g. "x' = a*x" + t_var: name of time variable in NEURON + dt_var: name of dt variable in NEURON vars: set of variables used in expression, e.g. {"x", "a"} Returns: @@ -18,43 +25,39 @@ def integrate2c(diff_string, vars): Raises: NotImplementedError: if ODE is too hard, or if fails to solve it. + ImportError: if SymPy version is too old (<1.2) """ # only try to solve ODEs that are not too hard - ode_properties_require_all = {'separable'} + ode_properties_require_all = {"separable"} ode_properties_require_one_of = { - '1st_exact', - '1st_linear', - 'almost_linear', - 'nth_linear_constant_coeff_homogeneous', - '1st_exact_Integral', - '1st_linear_Integral', + "1st_exact", + "1st_linear", + "almost_linear", + "nth_linear_constant_coeff_homogeneous", + "1st_exact_Integral", + "1st_linear_Integral", } # every symbol (a.k.a variable) that SymPy # is going to manipulate needs to be declared # explicitly sympy_vars = {} - # time is always t - t = sp.symbols('t', real=True, positive=True) + t = sp.symbols(t_var, real=True, positive=True) vars = set(vars) - vars.discard("t") + vars.discard(t_var) # the dependent variable is a function of t # we use the python variable name x for this - dependent_var = diff_string.split('=')[0].split("'")[0].strip() + dependent_var = diff_string.split("=")[0].split("'")[0].strip() x = sp.Function(dependent_var, real=True)(t) vars.discard(dependent_var) # declare all other supplied variables sympy_vars = {var: sp.symbols(var) for var in vars} sympy_vars[dependent_var] = x - sympy_vars["t"] = t + sympy_vars[t_var] = t # parse string into SymPy equation - diffeq = sp.Eq( - x.diff(t), - sp.sympify( - diff_string.split('=')[1], - locals=sympy_vars)) + diffeq = sp.Eq(x.diff(t), sp.sympify(diff_string.split("=")[1], locals=sympy_vars)) # classify ODE, if it is too hard then exit ode_properties = set(sp.classify_ode(diffeq)) @@ -64,10 +67,49 @@ def integrate2c(diff_string, vars): raise NotImplementedError("ODE too hard") # try to find analytic solution - dt = sp.symbols('dt', real=True, positive=True) + dt = sp.symbols(dt_var, real=True, positive=True) x_0 = sp.symbols(dependent_var, real=True) solution = sp.dsolve(diffeq, x, ics={x.subs({t: 0}): x_0}).subs({t: dt}) # note dsolve can return a list of solutions, in which case the above fails # return result as C code in NEURON format return f"{sp.ccode(x_0)} = {sp.ccode(solution.rhs)}" + + +def differentiate2c(expression, dependent_var, vars): + """Analytically differentiate supplied expression, return solution as C code. + + Expression should be of the form "f(x)", where "x" is + the dependent variable, and the function returns df(x)/dx + vars should contain the set of all the variables + referenced by f(x), for example: + -differentiate2c("a*x", "x", {"a"}) == "a" + -differentiate2c("cos(y) + b*y**2", "y", {"a","b"}) == "Dy = 2*b*y - sin(y)" + + Args: + expression: expression to be differentiated e.g. "a*x + b" + dependent_var: dependent variable, e.g. "x" + vars: set of all other variables used in expression, e.g. {"a", "b"} + + Returns: + String containing analytic derivative as C code + """ + + # every symbol (a.k.a variable) that SymPy + # is going to manipulate needs to be declared + # explicitly + x = sp.symbols(dependent_var, real=True) + vars = set(vars) + vars.discard(dependent_var) + # declare all other supplied variables + sympy_vars = {var: sp.symbols(var, real=True) for var in vars} + sympy_vars[dependent_var] = x + + # parse string into SymPy equation + expr = sp.sympify(expression, locals=sympy_vars) + + # differentiate w.r.t. x + diff = expr.diff(x) + + # return result as C code in NEURON format + return sp.ccode(diff) diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index ab54071833..7ccee46afd 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -1,6 +1,7 @@ #ifndef CODEGEN_NAMING_HPP #define CODEGEN_NAMING_HPP +#include <map> #include <string> namespace codegen { diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 98158b46d3..6f7c008672 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -20,6 +20,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -38,6 +39,10 @@ int main(int argc, const char* argv[]) { int error_count = 0; + if (arg.verbose) { + logger->set_level(spdlog::level::debug); + } + if (arg.sympy) { pybind11::initialize_interpreter(); } @@ -84,6 +89,19 @@ int main(int argc, const char* argv[]) { } } + if (arg.sympy) { + SympyConductanceVisitor v; + v.visit_program(ast.get()); + { + SymtabVisitor v(false); + v.visit_program(ast.get()); + } + if (arg.ast_to_nmodl) { + ast_to_nmodl(ast.get(), + arg.scratch_dir + "/" + mod_file + ".nmodl.conductance.mod"); + } + } + if (arg.sympy) { SympySolverVisitor v; v.visit_program(ast.get()); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 7737505141..18af7aa9e4 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -17,6 +17,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp new file mode 100644 index 0000000000..5fdb61e9e9 --- /dev/null +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -0,0 +1,183 @@ +#include <algorithm> +#include <iostream> + +#include "symtab/symbol.hpp" +#include "utils/logger.hpp" +#include "visitor_utils.hpp" +#include "visitors/sympy_conductance_visitor.hpp" + +using namespace ast; +namespace py = pybind11; +using namespace py::literals; +using namespace syminfo; + +// Generate statement strings to be added to BREAKPOINT section +std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( + BreakpointBlock* node) { + std::vector<std::string> statements; + // iterate over binary expressions from breakpoint + for (const auto& expr: binary_exprs) { + auto lhs_str = expr.first; + auto equation_string = expr.second; + // look for a current name that matches lhs of expr (current write name) + auto it = i_name.find(lhs_str); + if (it != i_name.end()) { + std::string i_name_str = it->second; + // differentiate dI/dV + auto locals = py::dict("equation_string"_a = equation_string, "vars"_a = vars); + py::exec(R"( + from nmodl.ode import differentiate2c + exception_message = "" + try: + rhs = equation_string.split("=")[1] + solution = differentiate2c(rhs, "v", vars) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), locals); + auto dIdV = locals["solution"].cast<std::string>(); + auto exception_message = locals["exception_message"].cast<std::string>(); + if (!exception_message.empty()) { + logger->warn("SympyConductance :: python exception: " + exception_message); + } + if (dIdV.empty()) { + logger->warn( + "SympyConductance :: analytic differentiation of ionic current " + "not possible"); + } else { + std::string g_var = dIdV; + // if conductance g_var is not an existing variable, need to generate + // a new variable name, declare it, and asign it + if (vars.find(g_var) == vars.end()) { + // generate new variable name + std::map<std::string, int> var_map; + for (auto const& v: vars) { + var_map[v] = 0; + } + g_var = get_new_name("g", i_name_str, var_map); + // declare it + add_local_variable(node->get_statement_block().get(), g_var); + // asign dIdV to it + std::string statement_str = g_var + " = " + dIdV; + statements.insert(statements.begin(), statement_str); + logger->debug("SympyConductance :: Adding BREAKPOINT statement: " + + statement_str); + } + std::string statement_str = "CONDUCTANCE " + g_var; + if (i_name_str != "") { + statement_str += " USEION " + i_name_str; + } + statements.push_back(statement_str); + logger->debug("SympyConductance :: Adding BREAKPOINT statement: " + statement_str); + } + } + } + return statements; +} + +void SympyConductanceVisitor::visit_binary_expression(BinaryExpression* node) { + // only want binary expressions of form x = ... + if (node->lhs->is_var_name() && (node->op.get_value() == BinaryOp::BOP_ASSIGN)) { + auto lhs_str = std::dynamic_pointer_cast<VarName>(node->lhs)->get_name()->get_node_name(); + binary_exprs[lhs_str] = nmodl::to_nmodl(node); + } +} + +void SympyConductanceVisitor::lookup_nonspecific_statements() { + // add NONSPECIFIC_CURRENT statements to i_name map between write vars and names + // note that they don't have an ion name, so we set it to "" + if (!NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS) { + for (auto ns_curr_ast: nonspecific_nodes) { + logger->debug("SympyConductance :: Found NONSPECIFIC_CURRENT statement"); + for (auto write_name: + std::dynamic_pointer_cast<Nonspecific>(ns_curr_ast).get()->get_currents()) { + std::string curr_write = write_name->get_node_name(); + logger->debug("SympyConductance :: -> Adding non-specific current write name: " + + curr_write); + i_name[curr_write] = ""; + } + } + } +} + +void SympyConductanceVisitor::lookup_useion_statements() { + // add USEION statements to i_name map between write vars and names + for (auto useion_ast: use_ion_nodes) { + auto ion = std::dynamic_pointer_cast<Useion>(useion_ast).get(); + std::string ion_name = ion->get_node_name(); + logger->debug("SympyConductance :: Found USEION statement " + nmodl::to_nmodl(ion)); + if (i_ignore.find(ion_name) != i_ignore.end()) { + logger->debug("SympyConductance :: -> Ignoring ion current name: " + ion_name); + } else { + auto wl = ion->get_writelist(); + for (auto w: wl) { + std::string ion_write = w->get_node_name(); + logger->debug("SympyConductance :: -> Adding ion write name: " + ion_write + + " for ion current name: " + ion_name); + i_name[ion_write] = ion_name; + } + } + } +} + +void SympyConductanceVisitor::visit_conductance_hint(ConductanceHint* node) { + // find existing CONDUCTANCE statements - do not want to alter them + // so keep a set of ion names i_ignore that we should ignore later + logger->debug("SympyConductance :: Found existing CONDUCTANCE statement: " + + nmodl::to_nmodl(node)); + auto ion = node->get_ion(); + if (ion) { + logger->debug("SympyConductance :: -> Ignoring ion current name: " + ion->get_node_name()); + i_ignore.insert(ion->get_node_name()); + } else { + logger->debug("SympyConductance :: -> Ignoring all non-specific currents"); + NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS = true; + } +}; + +void SympyConductanceVisitor::visit_breakpoint_block(BreakpointBlock* node) { + // add any breakpoint local variables to vars + if (auto symtab = node->get_statement_block()->get_symbol_table()) { + for (auto localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { + vars.insert(localvar->get_name()); + } + } + + // visit BREAKPOINT block statements + node->visit_children(this); + + // lookup USEION and NONSPECIFIC statements from NEURON block + lookup_useion_statements(); + lookup_nonspecific_statements(); + + // add new CONDUCTANCE statements to BREAKPOINT + auto new_statements = generate_statement_strings(node); + if (!new_statements.empty()) { + // get a copy of existing BREAKPOINT statements + auto brkpnt_statements = node->get_statement_block()->get_statements(); + // insert new CONDUCTANCE statements at top of BREAKPOINT + // or just below LOCAL statement if it exists + auto insertion_point = brkpnt_statements.begin(); + while ((*insertion_point)->is_local_list_statement()) { + ++insertion_point; + } + for (const auto& statement_str: new_statements) { + insertion_point = brkpnt_statements.insert(insertion_point, + create_statement(statement_str)); + } + // replace old set of BREAKPOINT statements in AST with new one + node->get_statement_block()->set_statements(std::move(brkpnt_statements)); + } +} + +void SympyConductanceVisitor::visit_program(Program* node) { + vars = get_global_vars(node); + + AstLookupVisitor ast_lookup_visitor; + use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); + nonspecific_nodes = ast_lookup_visitor.lookup(node, AstNodeType::NONSPECIFIC); + + node->visit_children(this); +} diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp new file mode 100644 index 0000000000..bfed0b997d --- /dev/null +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include <map> +#include <set> +#include <vector> + +#include <pybind11/embed.h> +#include <pybind11/stl.h> + +#include "ast/ast.hpp" +#include "symtab/symbol.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/lookup_visitor.hpp" + +/** + * \class SympyConductanceVisitor + * \brief Visitor for generating CONDUCTANCE statements for ions + * + * This class visits each ion expresion I = ... in the breakpoint + * and symbolically differentiates it to get dI/dV, + * i.e. the conductance. + * + * If this coincides with an existing global variable g + * (e.g. when I is linear in V) then it will add + * CONDUCTANCE g USEION I + * + * If dI/dV is a more complicated expression, it generates + * a new unique variable name g_unique, and adds two lines + * CONDUCTANCE g_unique USEION I + * g_unique = [dI/dV expression] + * + * If an ion channel already has a CONDUCTANCE statement + * then it does not modify it. + * + * TODO: take into account any functions called in breakpoint + */ + +class SympyConductanceVisitor: public AstVisitor { + private: + typedef std::map<std::string, std::string> string_map; + typedef std::set<std::string> string_set; + string_set vars; + string_set i_ignore; + string_map i_name; + bool NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS = false; + string_map binary_exprs; + std::vector<std::shared_ptr<ast::AST>> use_ion_nodes; + std::vector<std::shared_ptr<ast::AST>> nonspecific_nodes; + + std::vector<std::string> generate_statement_strings(ast::BreakpointBlock* node); + void lookup_useion_statements(); + void lookup_nonspecific_statements(); + + public: + SympyConductanceVisitor() = default; + void visit_binary_expression(ast::BinaryExpression* node) override; + void visit_breakpoint_block(ast::BreakpointBlock* node) override; + void visit_conductance_hint(ast::ConductanceHint* node) override; + void visit_program(ast::Program* node) override; +}; \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 26bbc62df9..3cf67582b6 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -1,6 +1,7 @@ +#include "visitors/sympy_solver_visitor.hpp" +#include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitors/sympy_solver_visitor.hpp" #include "visitor_utils.hpp" #include <iostream> using namespace ast; @@ -36,17 +37,14 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { return; } - std::unordered_set<std::string> var_strings{}; - for (auto v : vars) { - var_strings.insert(v->get_name()); - } - - auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), "vars"_a = var_strings); + auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), + "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, + "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars); py::exec(R"( from nmodl.ode import integrate2c exception_message = "" try: - solution = integrate2c(equation_string, vars) + solution = integrate2c(equation_string, t_var, dt_var, vars) except Exception as e: # if we fail, fail silently and return empty string solution = "" @@ -62,8 +60,8 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { if (!solution.empty()) { auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); - auto bin_expr = - std::dynamic_pointer_cast<BinaryExpression>(expr_statement->get_expression()); + auto bin_expr = std::dynamic_pointer_cast<BinaryExpression>( + expr_statement->get_expression()); lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { @@ -76,24 +74,14 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { auto symtab = node->get_statement_block()->get_symbol_table(); if (symtab) { auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); - for (auto v : localvars) { - vars.push_back(v); + for (auto v: localvars) { + vars.insert(v->get_name()); } } node->visit_children(this); } void SympySolverVisitor::visit_program(ast::Program* node) { - // get global vars - auto symtab = node->get_symbol_table(); - NmodlType property = NmodlType::global_var | NmodlType::range_var | NmodlType::param_assign | - NmodlType::extern_var | NmodlType::prime_name | NmodlType::dependent_def | - NmodlType::read_ion_var | NmodlType::write_ion_var | - NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | - NmodlType::section_var | NmodlType::constant_var | - NmodlType::extern_neuron_variable | NmodlType::state_var; - if (symtab) { - vars = symtab->get_variables_with_properties(property); - } + vars = get_global_vars(node); node->visit_children(this); } \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 68e02530c4..846b038f73 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -1,9 +1,9 @@ #pragma once -#include <unordered_set> -#include <vector> #include <pybind11/embed.h> #include <pybind11/stl.h> +#include <set> +#include <vector> #include "ast/ast.hpp" #include "symtab/symbol.hpp" @@ -20,9 +20,9 @@ * It will soon also deal with other solver methods. */ -class SympySolverVisitor : public AstVisitor { +class SympySolverVisitor: public AstVisitor { private: - std::vector<std::shared_ptr<symtab::Symbol>> vars; + std::set<std::string> vars; /// method specified in solve block std::string solve_method; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 92e055c6fe..6eb64dfc9e 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -6,6 +6,7 @@ #include "parser/nmodl_driver.hpp" #include "visitors/json_visitor.hpp" #include "visitors/nmodl_visitor.hpp" +#include "visitor_utils.hpp" using namespace ast; @@ -77,6 +78,24 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { return statement; } +std::set<std::string> get_global_vars(Program* node) { + std::set<std::string> vars; + if (auto symtab = node->get_symbol_table()) { + syminfo::NmodlType property = + syminfo::NmodlType::global_var | syminfo::NmodlType::range_var | + syminfo::NmodlType::param_assign | syminfo::NmodlType::extern_var | + syminfo::NmodlType::prime_name | syminfo::NmodlType::dependent_def | + syminfo::NmodlType::read_ion_var | syminfo::NmodlType::write_ion_var | + syminfo::NmodlType::nonspecific_cur_var | syminfo::NmodlType::electrode_cur_var | + syminfo::NmodlType::section_var | syminfo::NmodlType::constant_var | + syminfo::NmodlType::extern_neuron_variable | syminfo::NmodlType::state_var; + for (auto globalvar : symtab->get_variables_with_properties(property)) { + vars.insert(globalvar->get_name()); + } + } + return vars; +} + namespace nmodl { std::string to_nmodl(ast::AST* node) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index fd7161064e..1492b5dc90 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -2,6 +2,7 @@ #define NMODL_VISITOR_UTILS #include <map> +#include <set> #include <string> #include "ast/ast.hpp" @@ -27,6 +28,9 @@ ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& /** Create ast statement node from given code in string format */ std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); +/** Return set of strings with the names of all global variables */ +std::set<std::string> get_global_vars(ast::Program* node); + namespace nmodl { /** Given AST node, return the NMODL string representation */ std::string to_nmodl(ast::AST* node); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index b27afdc1ac..31cf9de546 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -18,8 +18,9 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/rename_visitor.hpp" -#include "visitors/symtab_visitor.hpp" +#include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -270,7 +271,7 @@ std::string run_nmodl_visitor(const std::string& text) { } SCENARIO("Test for AST back to NMODL transformation") { - for (const auto& construct : nmodl_valid_constructs) { + for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; std::string input_nmodl_text = reindent_text(test_case.input); std::string output_nmodl_text = reindent_text(test_case.output); @@ -292,7 +293,7 @@ std::string run_var_rename_visitor(const std::string& text, driver.parse_string(text); auto ast = driver.ast(); { - for (const auto& variable : variables) { + for (const auto& variable: variables) { RenameVisitor v(variable.first, variable.second); v.visit_program(ast.get()); } @@ -1264,7 +1265,7 @@ std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::stri std::vector<DUChain> chains; DefUseAnalyzeVisitor v(ast->get_symbol_table()); - for (const auto& block : ast->get_blocks()) { + for (const auto& block: ast->get_blocks()) { if (block->get_node_type() != AstNodeType::NEURON_BLOCK) { chains.push_back(v.analyze(block.get(), variable)); } @@ -1977,7 +1978,7 @@ std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; auto res = v_lookup.lookup(ast.get(), AstNodeType::DIFF_EQ_EXPRESSION); - for (auto r : res) { + for (auto r: res) { results.push_back(to_nmodl(r.get())); } @@ -2134,3 +2135,647 @@ SCENARIO("SympySolver visitor", "[sympy]") { } } } + + +//============================================================================= +// SympyConductance visitor tests +//============================================================================= + +std::string run_sympy_conductance_visitor(const std::string& text) { + // construct AST from text + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(ast.get()); + + // run SympyConductance on AST + SympyConductanceVisitor v_sympy; + v_sympy.visit_program(ast.get()); + + // run lookup visitor to extract results from AST + AstLookupVisitor v_lookup; + // return BREAKPOINT block as JSON string + return reindent_text( + nmodl::to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); +} + +std::string breakpoint_to_nmodl(const std::string& text) { + // construct AST from text + nmodl::Driver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(ast.get()); + + // run lookup visitor to extract results from AST + AstLookupVisitor v_lookup; + // return BREAKPOINT block as JSON string + return reindent_text( + nmodl::to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); +} + +void run_sympy_conductance_passes(Program* node) { + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(node); + + // run SympySolver on AST several times + SympyConductanceVisitor v_sympy1; + v_sympy1.visit_program(node); + v_symtab.visit_program(node); + v_sympy1.visit_program(node); + v_symtab.visit_program(node); + + // also use a second instance of SympySolver + SympyConductanceVisitor v_sympy2; + v_sympy2.visit_program(node); + v_symtab.visit_program(node); + v_sympy1.visit_program(node); + v_symtab.visit_program(node); + v_sympy2.visit_program(node); + v_symtab.visit_program(node); +} + +SCENARIO("SympyConductance visitor", "[sympy]") { + // Test mod files below all based on: + // nmodldb/models/db/bluebrain/CortexSimplified/mod/Ca.mod + GIVEN("ion current, existing CONDUCTANCE hint & var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + THEN("Do nothing") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("ion current, no CONDUCTANCE hint, existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + THEN("Add CONDUCTANCE hint using existing var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("ion current, no CONDUCTANCE hint, no existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0 + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + } + )"; + THEN("Add CONDUCTANCE hint with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("2 ion currents, 1 CONDUCTANCE hint, 1 existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + USEION na READ ena WRITE ina + RANGE gCabar, gNabar, ica, ina + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + gNabar = 0.00005 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ina (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_na_0 + CONDUCTANCE g_na_0 USEION na + g_na_0 = gNabar*h*m + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + )"; + THEN("Add 1 CONDUCTANCE hint with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("2 ion currents, no CONDUCTANCE hints, 1 existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + USEION na READ ena WRITE ina + RANGE gCabar, gNabar, ica, ina + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + gNabar = 0.00005 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ina (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_na_0 + CONDUCTANCE g_na_0 USEION na + CONDUCTANCE gCa USEION ca + g_na_0 = gNabar*h*m + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints, 1 with existing var, 1 with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("2 ion currents, no CONDUCTANCE hints, no existing vars") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + USEION na READ ena WRITE ina + RANGE gCabar, gNabar, ica, ina + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + gNabar = 0.00005 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ina (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0, g_na_0 + CONDUCTANCE g_na_0 USEION na + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + g_na_0 = gNabar*h*m + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints with 2 new local vars") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("1 ion current, 1 nonspecific current, no CONDUCTANCE hints, no existing vars") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + NONSPECIFIC_CURRENT ihcn + RANGE gCabar, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ihcn (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ihcn = (0.1235*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0, g__0 + CONDUCTANCE g__0 + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + g__0 = 0.1235*h*m + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ihcn = (0.1235*m*h)*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints with 2 new local vars") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("1 ion current, 1 nonspecific current, no CONDUCTANCE hints, 1 existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + NONSPECIFIC_CURRENT ihcn + RANGE gCabar, ica, gihcn + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ihcn (mA/cm2) + gCa (S/cm2) + gihcn (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + gihcn = 0.1235*m*h + ica = (gCabar*m*m*h)*(v-eca) + ihcn = gihcn*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0 + CONDUCTANCE gihcn + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + SOLVE states METHOD cnexp + gihcn = 0.1235*m*h + ica = (gCabar*m*m*h)*(v-eca) + ihcn = gihcn*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints, 1 using existing var, 1 with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } +} From 8970da7ef97ceadd9652cafd227d31f1fca6033f Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 18 Feb 2019 21:55:43 +0100 Subject: [PATCH 134/871] Preparation for open source with hpc-coding-convention : - perform clang-format and cmake-format on entire code base - added license to all source files - include-guards to #pragma once for maintainability - remove extra virtual keywork in CodegenHelperVisitor Change-Id: I766ee3ba8f6ac6761e8f8da7c4e328e0b4d89819 NMODL Repo SHA: BlueBrain/nmodl@338f0ead81df7539f61b30039d67c9d36ae1cd93 --- cmake/nmodl/CMakeLists.txt | 96 +- cmake/nmodl/ClangTidyHelper.cmake | 31 +- cmake/nmodl/CompilerHelper.cmake | 12 +- cmake/nmodl/FindPythonModule.cmake | 48 +- cmake/nmodl/FlexHelper.cmake | 23 +- cmake/nmodl/GitRevision.cmake | 42 +- cmake/nmodl/hpc-coding-conventions | 2 +- nmodl/ode.py | 7 + setup.py | 7 + src/nmodl/ast/ast_common.hpp | 1061 +++++++++-------- src/nmodl/codegen/CMakeLists.txt | 15 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 7 + src/nmodl/codegen/codegen_acc_visitor.hpp | 21 +- src/nmodl/codegen/codegen_c_visitor.cpp | 207 ++-- src/nmodl/codegen/codegen_c_visitor.hpp | 40 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 11 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 22 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 35 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 54 +- src/nmodl/codegen/codegen_info.cpp | 21 +- src/nmodl/codegen/codegen_info.hpp | 544 ++++----- src/nmodl/codegen/codegen_naming.hpp | 189 +-- src/nmodl/codegen/codegen_omp_visitor.cpp | 7 + src/nmodl/codegen/codegen_omp_visitor.hpp | 21 +- src/nmodl/language/CMakeLists.txt | 31 +- src/nmodl/language/argument.py | 7 + src/nmodl/language/code_generator.py | 7 + src/nmodl/language/nmodl.yaml | 7 + src/nmodl/language/node_info.py | 7 + src/nmodl/language/nodes.py | 7 + src/nmodl/language/parser.py | 7 + src/nmodl/language/templates/ast.cpp | 7 + src/nmodl/language/templates/ast.hpp | 7 + src/nmodl/language/templates/ast_decl.hpp | 7 + src/nmodl/language/templates/ast_visitor.cpp | 7 + src/nmodl/language/templates/ast_visitor.hpp | 7 + src/nmodl/language/templates/json_visitor.cpp | 7 + src/nmodl/language/templates/json_visitor.hpp | 7 + .../language/templates/lookup_visitor.cpp | 7 + .../language/templates/lookup_visitor.hpp | 7 + .../language/templates/nmodl_visitor.cpp | 7 + .../language/templates/nmodl_visitor.hpp | 7 + src/nmodl/language/templates/pyast.cpp | 7 + src/nmodl/language/templates/pyast.hpp | 7 + src/nmodl/language/templates/pysymtab.cpp | 7 + src/nmodl/language/templates/pyvisitor.cpp | 7 + src/nmodl/language/templates/pyvisitor.hpp | 7 + .../language/templates/symtab_visitor.cpp | 7 + .../language/templates/symtab_visitor.hpp | 7 + src/nmodl/language/templates/visitor.hpp | 7 + src/nmodl/language/utils.py | 7 + src/nmodl/lexer/CMakeLists.txt | 241 ++-- src/nmodl/lexer/c11.ll | 11 + src/nmodl/lexer/c11_lexer.hpp | 80 +- src/nmodl/lexer/diffeq.ll | 8 + src/nmodl/lexer/diffeq_lexer.hpp | 70 +- src/nmodl/lexer/main_c.cpp | 7 + src/nmodl/lexer/main_nmodl.cpp | 125 +- src/nmodl/lexer/modl.h | 9 + src/nmodl/lexer/modtoken.cpp | 7 + src/nmodl/lexer/modtoken.hpp | 26 +- src/nmodl/lexer/nmodl.ll | 8 + src/nmodl/lexer/nmodl_lexer.hpp | 138 ++- src/nmodl/lexer/nmodl_utils.cpp | 597 +++++----- src/nmodl/lexer/nmodl_utils.hpp | 31 +- src/nmodl/lexer/token_mapping.cpp | 196 +-- src/nmodl/lexer/token_mapping.hpp | 17 +- src/nmodl/lexer/verbatim.l | 7 + src/nmodl/nmodl/CMakeLists.txt | 21 +- src/nmodl/nmodl/arg_handler.cpp | 6 + src/nmodl/nmodl/arg_handler.hpp | 12 +- src/nmodl/nmodl/main.cpp | 17 +- src/nmodl/parser/CMakeLists.txt | 13 +- src/nmodl/parser/c11.yy | 5 +- src/nmodl/parser/c11_driver.cpp | 102 +- src/nmodl/parser/c11_driver.hpp | 132 +- src/nmodl/parser/diffeq.yy | 10 +- src/nmodl/parser/diffeq_context.cpp | 11 +- src/nmodl/parser/diffeq_context.hpp | 178 +-- src/nmodl/parser/diffeq_driver.cpp | 99 +- src/nmodl/parser/diffeq_driver.hpp | 88 +- src/nmodl/parser/diffeq_helper.hpp | 284 ++--- src/nmodl/parser/main_c.cpp | 10 +- src/nmodl/parser/main_nmodl.cpp | 7 + src/nmodl/parser/nmodl.yy | 10 +- src/nmodl/parser/nmodl_driver.cpp | 98 +- src/nmodl/parser/nmodl_driver.hpp | 175 +-- src/nmodl/parser/verbatim.yy | 12 +- src/nmodl/parser/verbatim_context.hpp | 12 +- src/nmodl/printer/CMakeLists.txt | 19 +- src/nmodl/printer/code_printer.cpp | 9 +- src/nmodl/printer/code_printer.hpp | 20 +- src/nmodl/printer/json_printer.cpp | 7 + src/nmodl/printer/json_printer.hpp | 20 +- src/nmodl/printer/nmodl_printer.cpp | 7 + src/nmodl/printer/nmodl_printer.hpp | 20 +- src/nmodl/pybind/CMakeLists.txt | 60 +- src/nmodl/pybind/pybind_utils.hpp | 17 +- src/nmodl/pybind/pynmodl.cpp | 13 +- src/nmodl/symtab/CMakeLists.txt | 26 +- src/nmodl/symtab/symbol.cpp | 139 ++- src/nmodl/symtab/symbol.hpp | 361 +++--- src/nmodl/symtab/symbol_properties.cpp | 7 + src/nmodl/symtab/symbol_properties.hpp | 296 ++--- src/nmodl/symtab/symbol_table.cpp | 840 ++++++------- src/nmodl/symtab/symbol_table.hpp | 389 +++--- src/nmodl/utils/CMakeLists.txt | 19 +- src/nmodl/utils/common_utils.cpp | 43 +- src/nmodl/utils/common_utils.hpp | 12 +- src/nmodl/utils/logger.cpp | 12 +- src/nmodl/utils/logger.hpp | 12 +- src/nmodl/utils/perf_stat.cpp | 7 + src/nmodl/utils/perf_stat.hpp | 12 +- src/nmodl/utils/string_utils.hpp | 171 +-- src/nmodl/utils/table_data.cpp | 13 +- src/nmodl/utils/table_data.hpp | 12 +- src/nmodl/version/version.cpp.in | 7 + src/nmodl/version/version.h | 21 +- src/nmodl/visitors/CMakeLists.txt | 101 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 11 +- src/nmodl/visitors/cnexp_solve_visitor.hpp | 14 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 63 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 39 +- src/nmodl/visitors/inline_visitor.cpp | 21 +- src/nmodl/visitors/inline_visitor.hpp | 14 +- .../visitors/local_var_rename_visitor.cpp | 11 +- .../visitors/local_var_rename_visitor.hpp | 14 +- src/nmodl/visitors/localize_visitor.cpp | 19 +- src/nmodl/visitors/localize_visitor.hpp | 18 +- src/nmodl/visitors/main.cpp | 7 + src/nmodl/visitors/nmodl_visitor_helper.hpp | 12 +- src/nmodl/visitors/perf_visitor.cpp | 123 +- src/nmodl/visitors/perf_visitor.hpp | 29 +- src/nmodl/visitors/rename_visitor.cpp | 9 +- src/nmodl/visitors/rename_visitor.hpp | 18 +- .../visitors/sympy_conductance_visitor.cpp | 7 + .../visitors/sympy_conductance_visitor.hpp | 7 + src/nmodl/visitors/sympy_solver_visitor.cpp | 7 + src/nmodl/visitors/sympy_solver_visitor.hpp | 7 + src/nmodl/visitors/symtab_visitor_helper.hpp | 9 + src/nmodl/visitors/var_usage_visitor.cpp | 7 + src/nmodl/visitors/var_usage_visitor.hpp | 14 +- .../visitors/verbatim_var_rename_visitor.cpp | 11 +- .../visitors/verbatim_var_rename_visitor.hpp | 14 +- src/nmodl/visitors/verbatim_visitor.cpp | 7 + src/nmodl/visitors/verbatim_visitor.hpp | 14 +- src/nmodl/visitors/visitor_utils.cpp | 47 +- src/nmodl/visitors/visitor_utils.hpp | 20 +- test/nmodl/transpiler/CMakeLists.txt | 53 +- test/nmodl/transpiler/lexer/tokens.cpp | 83 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 7 + test/nmodl/transpiler/parser/parser.cpp | 15 +- test/nmodl/transpiler/printer/printer.cpp | 10 +- test/nmodl/transpiler/pybind/conftest.py | 7 + test/nmodl/transpiler/pybind/test_ast.py | 7 + test/nmodl/transpiler/pybind/test_symtab.py | 7 + test/nmodl/transpiler/pybind/test_visitor.py | 7 + test/nmodl/transpiler/symtab/symbol_table.cpp | 11 +- .../transpiler/utils/nmodl_constructs.cpp | 9 +- ...modl_constructs.h => nmodl_constructs.hpp} | 25 +- test/nmodl/transpiler/utils/test_utils.cpp | 7 + test/nmodl/transpiler/utils/test_utils.hpp | 12 +- test/nmodl/transpiler/visitor/visitor.cpp | 9 +- 163 files changed, 4989 insertions(+), 4181 deletions(-) rename test/nmodl/transpiler/utils/{nmodl_constructs.h => nmodl_constructs.hpp} (58%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e386df2e75..0a1df71823 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,9 +1,9 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) project(NMODL CXX) -#============================================================================= +# ============================================================================= # CMake common project settings -#============================================================================= +# ============================================================================= set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 1) set(CMAKE_BUILD_TYPE Debug) @@ -12,32 +12,30 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) -#============================================================================= +# ============================================================================= # Find required packages -#============================================================================= +# ============================================================================= message(STATUS "CHECKING FOR FLEX/BISON") find_package(FLEX 2.6 REQUIRED) find_package(BISON 3.0 REQUIRED) -#============================================================================= +# ============================================================================= # HPC Coding Conventions -#============================================================================= +# ============================================================================= set(NMODL_ClangFormat_EXCLUDES_RE ".*/ext/.*$$" - CACHE STRING - "list of regular expressions to exclude C/C++ files from formatting" + CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) set(NMODL_CMakeFormat_EXCLUDES_RE ".*/ext/.*$$" - CACHE STRING - "list of regular expressions to exclude CMake files from formatting" + CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen CACHE STRING "list of CMake targets to build before formatting C++ code" FORCE) add_subdirectory(cmake/hpc-coding-conventions/cpp) -#============================================================================= +# ============================================================================= # Include cmake modules -#============================================================================= +# ============================================================================= list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) include(GitRevision) include(FlexHelper) @@ -45,9 +43,9 @@ include(CompilerHelper) include(ClangTidyHelper) include(FindPythonModule) -#============================================================================= +# ============================================================================= # Find required python packages -#============================================================================= +# ============================================================================= message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) find_python_module(jinja2 REQUIRED) @@ -56,53 +54,49 @@ find_python_module(sympy REQUIRED) find_python_module(textwrap REQUIRED) find_python_module(yaml REQUIRED) -include_directories( - ${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/src - ${PROJECT_BINARY_DIR}/src - ${PROJECT_SOURCE_DIR}/ext -) +include_directories(${PROJECT_SOURCE_DIR} + ${PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src + ${PROJECT_SOURCE_DIR}/ext) -#============================================================================= +# ============================================================================= # Include pybind11 -#============================================================================= +# ============================================================================= message(STATUS "INCLUDING PYBIND11") add_subdirectory(ext/pybind11) - -#============================================================================= +# ============================================================================= # Project version from git and project directories -#============================================================================= +# ============================================================================= set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) # generate file with version number from git configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) -#============================================================================= +# ============================================================================= # list of autogenerated files -#============================================================================= -set ( AUTO_GENERATED_FILES - ${PROJECT_BINARY_DIR}/src/ast/ast.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp - ) +# ============================================================================= +set(AUTO_GENERATED_FILES + ${PROJECT_BINARY_DIR}/src/ast/ast.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp) add_subdirectory(src/codegen) add_subdirectory(src/language) @@ -116,13 +110,13 @@ add_subdirectory(src/visitors) add_subdirectory(src/pybind) -#============================================================================= +# ============================================================================= # Memory checker options and add tests -#============================================================================= +# ============================================================================= find_program(MEMORYCHECK_COMMAND valgrind) set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ --leak-check=full \ --track-origins=yes \ --show-possibly-lost=no") -include (CTest) +include(CTest) add_subdirectory(test) diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake index d6985a4b86..3c4b892825 100644 --- a/cmake/nmodl/ClangTidyHelper.cmake +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -1,14 +1,21 @@ -if ( CMAKE_VERSION VERSION_GREATER "3.5" ) - set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") - if (ENABLE_CLANG_TIDY) - find_program (CLANG_TIDY_EXE NAMES "clang-tidy") - if (CLANG_TIDY_EXE) - message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(CLANG_TIDY_CHECKS "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*") - set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" CACHE STRING "" FORCE) - else() - message(AUTHOR_WARNING "clang-tidy not found!") - set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it - endif() +if(CMAKE_VERSION VERSION_GREATER "3.5") + set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") + if(ENABLE_CLANG_TIDY) + find_program(CLANG_TIDY_EXE NAMES "clang-tidy") + if(CLANG_TIDY_EXE) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + set( + CLANG_TIDY_CHECKS + "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" + ) + set( + CMAKE_CXX_CLANG_TIDY + "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" + CACHE STRING "" + FORCE) + else() + message(AUTHOR_WARNING "clang-tidy not found!") + set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it endif() + endif() endif() diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 1e7e866ea1..ca40baf01b 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -1,7 +1,7 @@ # minimal check for c++11 compliant gnu compiler -if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if (NOT (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9)) - message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.9 (for c++11 support)") - endif () -endif () +if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) + if(NOT (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9)) + message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.9 (for c++11 support)") + endif() +endif() diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index d8525918b2..e3736af63e 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -1,28 +1,26 @@ -# Find if a Python module is installed -# Found at http://www.cmake.org/pipermail/cmake/2011-January/041666.html -# To use do: find_python_module(PyQt4 REQUIRED) +# Find if a Python module is installed Found at +# http://www.cmake.org/pipermail/cmake/2011-January/041666.html To use do: find_python_module(PyQt4 +# REQUIRED) function(find_python_module module) - string(TOUPPER ${module} module_upper) - if(NOT PY_${module_upper}) - if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") - set(${module}_FIND_REQUIRED TRUE) - endif() - # A module's location is usually a directory, but for binary modules - # it's a .so file. - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_location - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT _${module}_status) - set(PY_${module_upper} ${_${module}_location} CACHE STRING - "Location of Python module ${module}") - endif(NOT _${module}_status) - endif(NOT PY_${module_upper}) - find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) - if(NOT PY_${module_upper} AND ${module}_FIND_REQUIRED) - message(FATAL_ERROR "Could not find ${module}") + string(TOUPPER ${module} module_upper) + if(NOT PY_${module_upper}) + if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") + set(${module}_FIND_REQUIRED TRUE) endif() + # A module's location is usually a directory, but for binary modules it's a .so file. + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" "-c" + "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_location + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT _${module}_status) + set(PY_${module_upper} ${_${module}_location} + CACHE STRING "Location of Python module ${module}") + endif(NOT _${module}_status) + endif(NOT PY_${module_upper}) + find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) + if(NOT PY_${module_upper} AND ${module}_FIND_REQUIRED) + message(FATAL_ERROR "Could not find ${module}") + endif() endfunction(find_python_module) - diff --git a/cmake/nmodl/FlexHelper.cmake b/cmake/nmodl/FlexHelper.cmake index 61227714d7..6b7c2bc755 100644 --- a/cmake/nmodl/FlexHelper.cmake +++ b/cmake/nmodl/FlexHelper.cmake @@ -1,18 +1,15 @@ -# Often older version of flex is available in /usr. -# Even we set PATH for newer flex, CMake will set -# FLEX_INCLUDE_DIRS to /usr/include. This will result -# in compilation errors. Hence we check for flex include -# directory for the corresponding FLEX_EXECUTABLE. -# If found, we add that first and then we include -# include path from CMake. +# Often older version of flex is available in /usr. Even we set PATH for newer flex, CMake will set +# FLEX_INCLUDE_DIRS to /usr/include. This will result in compilation errors. Hence we check for flex +# include directory for the corresponding FLEX_EXECUTABLE. If found, we add that first and then we +# include include path from CMake. get_filename_component(FLEX_BIN_DIR ${FLEX_EXECUTABLE} DIRECTORY) if(NOT FLEX_BIN_DIR MATCHES "/usr/bin") - get_filename_component(FLEX_INCLUDE_PATH ${FLEX_BIN_DIR} PATH) - set(FLEX_INCLUDE_PATH ${FLEX_INCLUDE_PATH}/include/) - if(EXISTS "${FLEX_INCLUDE_PATH}/FlexLexer.h") - message(STATUS " Adding Flex include path as : ${FLEX_INCLUDE_PATH}") - include_directories(${FLEX_INCLUDE_PATH}) - endif() + get_filename_component(FLEX_INCLUDE_PATH ${FLEX_BIN_DIR} PATH) + set(FLEX_INCLUDE_PATH ${FLEX_INCLUDE_PATH}/include/) + if(EXISTS "${FLEX_INCLUDE_PATH}/FlexLexer.h") + message(STATUS " Adding Flex include path as : ${FLEX_INCLUDE_PATH}") + include_directories(${FLEX_INCLUDE_PATH}) + endif() endif() diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index 2ff0980b3f..2bbf2ce135 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -1,30 +1,32 @@ -# For now use simple approach to get version information as -# git is always avaialble on the machine where we are building +# For now use simple approach to get version information as git is always avaialble on the machine +# where we are building find_package(Git) if(GIT_FOUND) - # get last commit sha1 - execute_process(COMMAND - ${GIT_EXECUTABLE} log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_SHA1 - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - - # get last commit date - execute_process(COMMAND - ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_DATE - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - - # remove extra double quotes - string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") - set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") + # get last commit sha1 + execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + + # get last commit date + execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_DATE + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + + # remove extra double quotes + string(REGEX + REPLACE "\"" + "" + GIT_REVISION_DATE + "${GIT_REVISION_DATE}") + set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") else() - set(GIT_REVISION "unknown") + set(GIT_REVISION "unknown") endif() diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 2866ea5319..6b2f7346db 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 2866ea53196ef7f82d9535c1f55963c988631c3e +Subproject commit 6b2f7346db5cf5d438aa35ee16d4cb7fe5a71e46 diff --git a/nmodl/ode.py b/nmodl/ode.py index 2a78e77a6a..505207a00f 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import sympy as sp major, minor = (int(v) for v in sp.__version__.split(".")[:2]) diff --git a/setup.py b/setup.py index c5f34dcd5e..e92ba885a6 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import os import os.path as osp import re diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 53172aae55..524b9b5b3c 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -1,5 +1,11 @@ -#ifndef ASTUTILS_HPP -#define ASTUTILS_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <memory> #include <string> @@ -11,655 +17,652 @@ #include "symtab/symbol_table.hpp" namespace ast { - /* enumaration of all binary operators in the language */ - typedef enum { - BOP_ADDITION, - BOP_SUBTRACTION, - BOP_MULTIPLICATION, - BOP_DIVISION, - BOP_POWER, - BOP_AND, - BOP_OR, - BOP_GREATER, - BOP_LESS, - BOP_GREATER_EQUAL, - BOP_LESS_EQUAL, - BOP_ASSIGN, - BOP_NOT_EQUAL, - BOP_EXACT_EQUAL - } BinaryOp; - - static const std::string BinaryOpNames[] = {"+", "-", "*", "/", "^", "&&", "||", - ">", "<", ">=", "<=", "=", "!=", "=="}; - - /* enumaration of all unary operators in the language */ - typedef enum { UOP_NOT, UOP_NEGATION } UnaryOp; - static const std::string UnaryOpNames[] = {"!", "-"}; - - /* enumaration of types used in partial equation */ - typedef enum { PEQ_FIRST, PEQ_LAST } FirstLastType; - static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; - - /* enumaration of queue types */ - typedef enum { PUT_QUEUE, GET_QUEUE } QueueType; - static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; - - /* enumaration of type used for BEFORE or AFTER block */ - typedef enum { BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP } BAType; - static const std::string BATypeNames[] = {"BREAKPOINT", "SOLVE", "INITIAL", "STEP"}; - - /* enumaration of type used for UNIT_ON or UNIT_OFF state*/ - typedef enum { UNIT_ON, UNIT_OFF } UnitStateType; - static const std::string UnitStateTypeNames[] = {"UNITSON", "UNITSOFF"}; - - /* enumaration of type used for Reaction statement */ - typedef enum { LTMINUSGT, LTLT, MINUSGT } ReactionOp; - static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; - - /* enum class for ast types */ - enum class Type; - - /* define abstract base class for all AST nodes - * this also serves to define the visitable objects. +/* enumaration of all binary operators in the language */ +typedef enum { + BOP_ADDITION, + BOP_SUBTRACTION, + BOP_MULTIPLICATION, + BOP_DIVISION, + BOP_POWER, + BOP_AND, + BOP_OR, + BOP_GREATER, + BOP_LESS, + BOP_GREATER_EQUAL, + BOP_LESS_EQUAL, + BOP_ASSIGN, + BOP_NOT_EQUAL, + BOP_EXACT_EQUAL +} BinaryOp; + +static const std::string BinaryOpNames[] = {"+", "-", "*", "/", "^", "&&", "||", + ">", "<", ">=", "<=", "=", "!=", "=="}; + +/* enumaration of all unary operators in the language */ +typedef enum { UOP_NOT, UOP_NEGATION } UnaryOp; +static const std::string UnaryOpNames[] = {"!", "-"}; + +/* enumaration of types used in partial equation */ +typedef enum { PEQ_FIRST, PEQ_LAST } FirstLastType; +static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; + +/* enumaration of queue types */ +typedef enum { PUT_QUEUE, GET_QUEUE } QueueType; +static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; + +/* enumaration of type used for BEFORE or AFTER block */ +typedef enum { BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP } BAType; +static const std::string BATypeNames[] = {"BREAKPOINT", "SOLVE", "INITIAL", "STEP"}; + +/* enumaration of type used for UNIT_ON or UNIT_OFF state*/ +typedef enum { UNIT_ON, UNIT_OFF } UnitStateType; +static const std::string UnitStateTypeNames[] = {"UNITSON", "UNITSOFF"}; + +/* enumaration of type used for Reaction statement */ +typedef enum { LTMINUSGT, LTLT, MINUSGT } ReactionOp; +static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; + +/* enum class for ast types */ +enum class Type; + +/* define abstract base class for all AST nodes + * this also serves to define the visitable objects. + */ +struct AST: public std::enable_shared_from_this<AST> { + /* all AST nodes have a member which stores their + * basetype (int, bool, none, object). Further type + * information will come from the symbol table. + * BaseType basetype; */ - struct AST : public std::enable_shared_from_this<AST> { - /* all AST nodes have a member which stores their - * basetype (int, bool, none, object). Further type - * information will come from the symbol table. - * BaseType basetype; - */ - - /* all AST nodes provide visit children and accept methods */ - virtual void visit_children(Visitor* v) = 0; - virtual void accept(Visitor* v) = 0; - virtual AstNodeType get_node_type() = 0; - virtual std::string get_node_type_name() = 0; - - virtual std::string get_node_name() { - throw std::logic_error("get_node_name() not implemented"); - } - - virtual AST* clone() { - throw std::logic_error("clone() not implemented"); - } - - /* @todo: revisit, adding quickly for symtab */ - virtual ModToken* get_token() { /*std::cout << "\n ERROR: get_token not implemented!";*/ - return nullptr; - } - - virtual void set_symbol_table(symtab::SymbolTable* newsymtab) { - throw std::runtime_error("set_symbol_table() not implemented"); - } - - virtual symtab::SymbolTable* get_symbol_table() { - throw std::runtime_error("get_symbol_table() not implemented"); - } - - virtual std::shared_ptr<StatementBlock> get_statement_block() { - throw std::runtime_error("get_statement_block not implemented"); - } - - // implemented in Number sub classes - virtual void negate() { - throw std::runtime_error("negate() not implemented"); - } - - // implemented in Identifier sub classes - virtual void set_name(std::string /*name*/) { - throw std::runtime_error("set_name() not implemented"); - } - - virtual ~AST() { - } - - virtual std::shared_ptr<AST> get_shared_ptr() { - return std::static_pointer_cast<AST>(shared_from_this()); - } - - virtual bool is_ast() { - return true; - } - - virtual bool is_statement() { - return false; - } - - virtual bool is_expression() { - return false; - } - - virtual bool is_block() { - return false; - } - - virtual bool is_identifier() { - return false; - } - - virtual bool is_number() { - return false; - } - - virtual bool is_string() { - return false; - } - - virtual bool is_integer() { - return false; - } - - virtual bool is_float() { - return false; - } - - virtual bool is_double() { - return false; - } - - virtual bool is_boolean() { - return false; - } - - virtual bool is_name() { - return false; - } - - virtual bool is_prime_name() { - return false; - } - virtual bool is_var_name() { - return false; - } + /* all AST nodes provide visit children and accept methods */ + virtual void visit_children(Visitor* v) = 0; + virtual void accept(Visitor* v) = 0; + virtual AstNodeType get_node_type() = 0; + virtual std::string get_node_type_name() = 0; + + virtual std::string get_node_name() { + throw std::logic_error("get_node_name() not implemented"); + } + + virtual AST* clone() { + throw std::logic_error("clone() not implemented"); + } + + /* @todo: revisit, adding quickly for symtab */ + virtual ModToken* get_token() { /*std::cout << "\n ERROR: get_token not implemented!";*/ + return nullptr; + } + + virtual void set_symbol_table(symtab::SymbolTable* newsymtab) { + throw std::runtime_error("set_symbol_table() not implemented"); + } + + virtual symtab::SymbolTable* get_symbol_table() { + throw std::runtime_error("get_symbol_table() not implemented"); + } + + virtual std::shared_ptr<StatementBlock> get_statement_block() { + throw std::runtime_error("get_statement_block not implemented"); + } + + // implemented in Number sub classes + virtual void negate() { + throw std::runtime_error("negate() not implemented"); + } + + // implemented in Identifier sub classes + virtual void set_name(std::string /*name*/) { + throw std::runtime_error("set_name() not implemented"); + } + + virtual ~AST() {} + + virtual std::shared_ptr<AST> get_shared_ptr() { + return std::static_pointer_cast<AST>(shared_from_this()); + } + + virtual bool is_ast() { + return true; + } + + virtual bool is_statement() { + return false; + } + + virtual bool is_expression() { + return false; + } + + virtual bool is_block() { + return false; + } + + virtual bool is_identifier() { + return false; + } + + virtual bool is_number() { + return false; + } + + virtual bool is_string() { + return false; + } + + virtual bool is_integer() { + return false; + } - virtual bool is_indexed_name() { - return false; - } + virtual bool is_float() { + return false; + } - virtual bool is_argument() { - return false; - } + virtual bool is_double() { + return false; + } - virtual bool is_react_var_name() { - return false; - } + virtual bool is_boolean() { + return false; + } - virtual bool is_read_ion_var() { - return false; - } + virtual bool is_name() { + return false; + } - virtual bool is_write_ion_var() { - return false; - } + virtual bool is_prime_name() { + return false; + } - virtual bool is_nonspecific_cur_var() { - return false; - } + virtual bool is_var_name() { + return false; + } - virtual bool is_electrode_cur_var() { - return false; - } + virtual bool is_indexed_name() { + return false; + } - virtual bool is_section_var() { - return false; - } + virtual bool is_argument() { + return false; + } - virtual bool is_range_var() { - return false; - } + virtual bool is_react_var_name() { + return false; + } - virtual bool is_global_var() { - return false; - } + virtual bool is_read_ion_var() { + return false; + } - virtual bool is_pointer_var() { - return false; - } + virtual bool is_write_ion_var() { + return false; + } - virtual bool is_bbcore_pointer_var() { - return false; - } + virtual bool is_nonspecific_cur_var() { + return false; + } - virtual bool is_extern_var() { - return false; - } + virtual bool is_electrode_cur_var() { + return false; + } - virtual bool is_threadsafe_var() { - return false; - } + virtual bool is_section_var() { + return false; + } - virtual bool is_param_block() { - return false; - } + virtual bool is_range_var() { + return false; + } - virtual bool is_step_block() { - return false; - } + virtual bool is_global_var() { + return false; + } - virtual bool is_independent_block() { - return false; - } + virtual bool is_pointer_var() { + return false; + } - virtual bool is_dependent_block() { - return false; - } + virtual bool is_bbcore_pointer_var() { + return false; + } - virtual bool is_state_block() { - return false; - } + virtual bool is_extern_var() { + return false; + } - virtual bool is_plot_block() { - return false; - } + virtual bool is_threadsafe_var() { + return false; + } - virtual bool is_initial_block() { - return false; - } + virtual bool is_param_block() { + return false; + } - virtual bool is_constructor_block() { - return false; - } + virtual bool is_step_block() { + return false; + } - virtual bool is_destructor_block() { - return false; - } + virtual bool is_independent_block() { + return false; + } - virtual bool is_statement_block() { - return false; - } + virtual bool is_dependent_block() { + return false; + } - virtual bool is_derivative_block() { - return false; - } + virtual bool is_state_block() { + return false; + } - virtual bool is_linear_block() { - return false; - } + virtual bool is_plot_block() { + return false; + } - virtual bool is_non_linear_block() { - return false; - } + virtual bool is_initial_block() { + return false; + } - virtual bool is_discrete_block() { - return false; - } + virtual bool is_constructor_block() { + return false; + } - virtual bool is_partial_block() { - return false; - } + virtual bool is_destructor_block() { + return false; + } - virtual bool is_function_table_block() { - return false; - } + virtual bool is_statement_block() { + return false; + } - virtual bool is_function_block() { - return false; - } + virtual bool is_derivative_block() { + return false; + } - virtual bool is_procedure_block() { - return false; - } + virtual bool is_linear_block() { + return false; + } - virtual bool is_net_receive_block() { - return false; - } + virtual bool is_non_linear_block() { + return false; + } - virtual bool is_solve_block() { - return false; - } + virtual bool is_discrete_block() { + return false; + } - virtual bool is_breakpoint_block() { - return false; - } + virtual bool is_partial_block() { + return false; + } - virtual bool is_terminal_block() { - return false; - } + virtual bool is_function_table_block() { + return false; + } - virtual bool is_before_block() { - return false; - } + virtual bool is_function_block() { + return false; + } - virtual bool is_after_block() { - return false; - } + virtual bool is_procedure_block() { + return false; + } - virtual bool is_ba_block() { - return false; - } + virtual bool is_net_receive_block() { + return false; + } - virtual bool is_for_netcon() { - return false; - } + virtual bool is_solve_block() { + return false; + } - virtual bool is_kinetic_block() { - return false; - } + virtual bool is_breakpoint_block() { + return false; + } - virtual bool is_match_block() { - return false; - } + virtual bool is_terminal_block() { + return false; + } - virtual bool is_unit_block() { - return false; - } + virtual bool is_before_block() { + return false; + } - virtual bool is_constant_block() { - return false; - } + virtual bool is_after_block() { + return false; + } - virtual bool is_neuron_block() { - return false; - } + virtual bool is_ba_block() { + return false; + } - virtual bool is_unit() { - return false; - } + virtual bool is_for_netcon() { + return false; + } - virtual bool is_double_unit() { - return false; - } + virtual bool is_kinetic_block() { + return false; + } - virtual bool is_local_var() { - return false; - } + virtual bool is_match_block() { + return false; + } - virtual bool is_limits() { - return false; - } + virtual bool is_unit_block() { + return false; + } - virtual bool is_number_range() { - return false; - } + virtual bool is_constant_block() { + return false; + } - virtual bool is_plot_var() { - return false; - } + virtual bool is_neuron_block() { + return false; + } - virtual bool is_binary_operator() { - return false; - } + virtual bool is_unit() { + return false; + } - virtual bool is_wrapped_expression() { - return true; - } + virtual bool is_double_unit() { + return false; + } - virtual bool is_paren_expression() { - return false; - } + virtual bool is_local_var() { + return false; + } - virtual bool is_unary_operator() { - return false; - } + virtual bool is_limits() { + return false; + } - virtual bool is_reaction_operator() { - return false; - } + virtual bool is_number_range() { + return false; + } - virtual bool is_binary_expression() { - return false; - } + virtual bool is_plot_var() { + return false; + } - virtual bool is_unary_expression() { - return false; - } + virtual bool is_binary_operator() { + return false; + } - virtual bool is_non_lin_equation() { - return false; - } + virtual bool is_wrapped_expression() { + return true; + } - virtual bool is_lin_equation() { - return false; - } + virtual bool is_paren_expression() { + return false; + } - virtual bool is_function_call() { - return false; - } + virtual bool is_unary_operator() { + return false; + } - virtual bool is_first_last_type_index() { - return false; - } + virtual bool is_reaction_operator() { + return false; + } - virtual bool is_watch() { - return false; - } + virtual bool is_binary_expression() { + return false; + } - virtual bool is_queue_expression_type() { - return false; - } + virtual bool is_unary_expression() { + return false; + } - virtual bool is_match() { - return false; - } + virtual bool is_non_lin_equation() { + return false; + } - virtual bool is_ba_block_type() { - return false; - } + virtual bool is_lin_equation() { + return false; + } - virtual bool is_unit_def() { - return false; - } + virtual bool is_function_call() { + return false; + } - virtual bool is_factor_def() { - return false; - } + virtual bool is_first_last_type_index() { + return false; + } - virtual bool is_valence() { - return false; - } + virtual bool is_watch() { + return false; + } - virtual bool is_unit_state() { - return false; - } + virtual bool is_queue_expression_type() { + return false; + } - virtual bool is_local_list_statement() { - return false; - } + virtual bool is_match() { + return false; + } - virtual bool is_model() { - return false; - } + virtual bool is_ba_block_type() { + return false; + } - virtual bool is_define() { - return false; - } + virtual bool is_unit_def() { + return false; + } - virtual bool is_include() { - return false; - } + virtual bool is_factor_def() { + return false; + } - virtual bool is_param_assign() { - return false; - } + virtual bool is_valence() { + return false; + } - virtual bool is_stepped() { - return false; - } + virtual bool is_unit_state() { + return false; + } - virtual bool is_independent_def() { - return false; - } + virtual bool is_local_list_statement() { + return false; + } - virtual bool is_dependent_def() { - return false; - } + virtual bool is_model() { + return false; + } - virtual bool is_plot_declaration() { - return false; - } + virtual bool is_define() { + return false; + } - virtual bool is_conductance_hint() { - return false; - } + virtual bool is_include() { + return false; + } - virtual bool is_expression_statement() { - return false; - } + virtual bool is_param_assign() { + return false; + } - virtual bool is_protect_statement() { - return false; - } + virtual bool is_stepped() { + return false; + } - virtual bool is_from_statement() { - return false; - } + virtual bool is_independent_def() { + return false; + } - virtual bool is_for_all_statement() { - return false; - } + virtual bool is_dependent_def() { + return false; + } - virtual bool is_while_statement() { - return false; - } + virtual bool is_plot_declaration() { + return false; + } - virtual bool is_if_statement() { - return false; - } + virtual bool is_conductance_hint() { + return false; + } - virtual bool is_else_if_statement() { - return false; - } + virtual bool is_expression_statement() { + return false; + } - virtual bool is_else_statement() { - return false; - } + virtual bool is_protect_statement() { + return false; + } - virtual bool is_partial_equation() { - return false; - } + virtual bool is_from_statement() { + return false; + } - virtual bool is_partial_boundary() { - return false; - } + virtual bool is_for_all_statement() { + return false; + } - virtual bool is_watch_statement() { - return false; - } + virtual bool is_while_statement() { + return false; + } - virtual bool is_mutex_lock() { - return false; - } + virtual bool is_if_statement() { + return false; + } - virtual bool is_mutex_unlock() { - return false; - } + virtual bool is_else_if_statement() { + return false; + } - virtual bool is_reset() { - return false; - } + virtual bool is_else_statement() { + return false; + } - virtual bool is_sens() { - return false; - } + virtual bool is_partial_equation() { + return false; + } - virtual bool is_conserve() { - return false; - } + virtual bool is_partial_boundary() { + return false; + } - virtual bool is_compartment() { - return false; - } + virtual bool is_watch_statement() { + return false; + } - virtual bool is_lon_difuse() { - return false; - } + virtual bool is_mutex_lock() { + return false; + } - virtual bool is_reaction_statement() { - return false; - } + virtual bool is_mutex_unlock() { + return false; + } - virtual bool is_lag_statement() { - return false; - } + virtual bool is_reset() { + return false; + } - virtual bool is_queue_statement() { - return false; - } + virtual bool is_sens() { + return false; + } - virtual bool is_constant_statement() { - return false; - } + virtual bool is_conserve() { + return false; + } - virtual bool is_table_statement() { - return false; - } + virtual bool is_compartment() { + return false; + } - virtual bool is_suffix() { - return false; - } + virtual bool is_lon_difuse() { + return false; + } - virtual bool is_useion() { - return false; - } + virtual bool is_reaction_statement() { + return false; + } - /// \todo : how is this different from is_nonspecific_cur_var ? - virtual bool is_nonspecific() { - return false; - } + virtual bool is_lag_statement() { + return false; + } - virtual bool is_elctrode_current() { - return false; - } + virtual bool is_queue_statement() { + return false; + } - virtual bool is_section() { - return false; - } + virtual bool is_constant_statement() { + return false; + } - virtual bool is_range() { - return false; - } + virtual bool is_table_statement() { + return false; + } - virtual bool is_global() { - return false; - } + virtual bool is_suffix() { + return false; + } - /// \todo : how is this different from is_pointer_var ? - virtual bool is_pointer() { - return false; - } + virtual bool is_useion() { + return false; + } - virtual bool is_bbcore_ptr() { - return false; - } + /// \todo : how is this different from is_nonspecific_cur_var ? + virtual bool is_nonspecific() { + return false; + } + + virtual bool is_elctrode_current() { + return false; + } + + virtual bool is_section() { + return false; + } + + virtual bool is_range() { + return false; + } + + virtual bool is_global() { + return false; + } + + /// \todo : how is this different from is_pointer_var ? + virtual bool is_pointer() { + return false; + } - virtual bool is_external() { - return false; - } + virtual bool is_bbcore_ptr() { + return false; + } - virtual bool is_thread_safe() { - return false; - } + virtual bool is_external() { + return false; + } - virtual bool is_verbatim() { - return false; - } + virtual bool is_thread_safe() { + return false; + } - virtual bool is_line_comment() { - return false; - } + virtual bool is_verbatim() { + return false; + } - virtual bool is_block_comment() { - return false; - } + virtual bool is_line_comment() { + return false; + } - virtual bool is_node() { - return false; - } + virtual bool is_block_comment() { + return false; + } - virtual bool is_program() { - return false; - } + virtual bool is_node() { + return false; + } - virtual bool is_constant_var() { - return false; - } + virtual bool is_program() { + return false; + } - virtual bool is_diff_eq_expression() { - return false; - } - }; + virtual bool is_constant_var() { + return false; + } -} // namespace ast + virtual bool is_diff_eq_expression() { + return false; + } +}; -#endif +} // namespace ast \ No newline at end of file diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index ab1709ddd8..337f183565 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -1,6 +1,6 @@ -#============================================================================= +# ============================================================================= # Codegen sources -#============================================================================= +# ============================================================================= set(CODEGEN_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.hpp @@ -14,14 +14,11 @@ set(CODEGEN_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/codegen_helper_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp) -#============================================================================= +# ============================================================================= # Codegen library and executable -#============================================================================= -add_library(codegen - STATIC - ${CODEGEN_SOURCE_FILES}) +# ============================================================================= +add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) add_dependencies(codegen lexer util) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index cc0869d0c8..b05c70672d 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "codegen/codegen_acc_visitor.hpp" #include <fmt/format.h> diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index b84d09d5cc..3cf9cbc6d2 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_CODEGEN_C_ACC_VISITOR_HPP -#define NMODL_CODEGEN_C_ACC_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "codegen/codegen_c_visitor.hpp" @@ -8,7 +14,7 @@ * \class CodegenAccVisitor * \brief Visitor for printing c code with OpenMP backend */ -class CodegenAccVisitor : public CodegenCVisitor { +class CodegenAccVisitor: public CodegenCVisitor { protected: /// name of the code generation backend std::string backend_name() override; @@ -59,16 +65,11 @@ class CodegenAccVisitor : public CodegenCVisitor { std::string output_dir, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type) { - } + : CodegenCVisitor(mod_file, output_dir, layout, float_type) {} CodegenAccVisitor(std::string mod_file, std::stringstream& stream, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) { - } + : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; - - -#endif diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 486ce4bd90..4b581f10fc 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1,17 +1,24 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <algorithm> #include <cmath> #include <ctime> -#include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "parser/c11_driver.hpp" -#include "utils/string_utils.hpp" #include "utils/logger.hpp" +#include "utils/string_utils.hpp" #include "version/version.h" +#include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" -#include "visitors/lookup_visitor.hpp" using namespace ast; @@ -284,7 +291,7 @@ void CodegenCVisitor::visit_verbatim(Verbatim* node) { auto result = process_verbatim_text(text); auto statements = stringutils::split_string(result, '\n'); - for (auto& statement : statements) { + for (auto& statement: statements) { stringutils::trim_newline(statement); if (statement.find_first_not_of(' ') != std::string::npos) { printer->add_line(statement); @@ -397,7 +404,7 @@ bool CodegenCVisitor::state_variable(std::string name) { int CodegenCVisitor::position_of_float_var(const std::string& name) { int index = 0; - for (const auto& var : codegen_float_variables) { + for (const auto& var: codegen_float_variables) { if (var->get_name() == name) { return index; } @@ -409,7 +416,7 @@ int CodegenCVisitor::position_of_float_var(const std::string& name) { int CodegenCVisitor::position_of_int_var(const std::string& name) { int index = 0; - for (const auto& var : codegen_int_variables) { + for (const auto& var: codegen_int_variables) { if (var.symbol->get_name() == name) { return index; } @@ -493,7 +500,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { } auto symtab = breakpoint->get_statement_block()->get_symbol_table(); auto variables = symtab->get_variables_with_properties(NmodlType::local_var); - for (const auto& var : variables) { + for (const auto& var: variables) { auto renamed_name = var->get_name(); auto original_name = var->get_original_name(); if (current == original_name) { @@ -508,7 +515,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { int CodegenCVisitor::float_variables_size() { auto count_length = [](std::vector<SymbolType>& variables) -> int { int length = 0; - for (const auto& variable : variables) { + for (const auto& variable: variables) { length += variable->get_length(); } return length; @@ -541,7 +548,7 @@ int CodegenCVisitor::float_variables_size() { int CodegenCVisitor::int_variables_size() { int num_variables = 0; - for (const auto& semantic : info.semantics) { + for (const auto& semantic: info.semantics) { num_variables += semantic.size; } return num_variables; @@ -565,9 +572,9 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { return ion_read_statements_optimized(type); } std::vector<std::string> statements; - for (const auto& ion : info.ions) { + for (const auto& ion: info.ions) { auto name = ion.name; - for (const auto& var : ion.reads) { + for (const auto& var: ion.reads) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } @@ -576,7 +583,7 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { auto second = get_variable_name(variable_names.second); statements.push_back("{} = {};"_format(first, second)); } - for (const auto& var : ion.writes) { + for (const auto& var: ion.writes) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } @@ -594,8 +601,8 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { std::vector<std::string> CodegenCVisitor::ion_read_statements_optimized(BlockType type) { std::vector<std::string> statements; - for (const auto& ion : info.ions) { - for (const auto& var : ion.writes) { + for (const auto& ion: info.ions) { + for (const auto& var: ion.writes) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } @@ -613,10 +620,10 @@ std::vector<std::string> CodegenCVisitor::ion_read_statements_optimized(BlockTyp std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType type) { std::vector<ShadowUseStatement> statements; - for (const auto& ion : info.ions) { + for (const auto& ion: info.ions) { std::string concentration; auto name = ion.name; - for (const auto& var : ion.writes) { + for (const auto& var: ion.writes) { auto variable_names = write_ion_variable_name(var); if (ion.is_ionic_current(var)) { if (type == BlockType::Equation) { @@ -737,11 +744,11 @@ void CodegenCVisitor::update_index_semantics() { info.semantics.emplace_back(index++, naming::AREA_SEMANTIC, 1); info.semantics.emplace_back(index++, naming::POINT_PROCESS_SEMANTIC, 1); } - for (const auto& ion : info.ions) { - for (auto& var : ion.reads) { + for (const auto& ion: info.ions) { + for (auto& var: ion.reads) { info.semantics.emplace_back(index++, ion.name + "_ion", 1); } - for (auto& var : ion.writes) { + for (auto& var: ion.writes) { info.semantics.emplace_back(index++, ion.name + "_ion", 1); if (ion.is_ionic_current(var)) { info.semantics.emplace_back(index++, ion.name + "_ion", 1); @@ -751,7 +758,7 @@ void CodegenCVisitor::update_index_semantics() { info.semantics.emplace_back(index++, "#{}_ion"_format(ion.name), 1); } } - for (auto& var : info.pointer_variables) { + for (auto& var: info.pointer_variables) { if (info.first_pointer_var_index == -1) { info.first_pointer_var_index = index; } @@ -806,7 +813,7 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); /// each state variable has corresponding Dstate variable - for (auto& variable : states) { + for (auto& variable: states) { auto name = "D" + variable->get_name(); auto symbol = make_symbol(name); symbol->set_definition_order(variable->get_definition_order()); @@ -824,8 +831,8 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { variables.push_back(make_symbol(naming::VOLTAGE_UNUSED_VARIABLE)); } if (breakpoint_exist()) { - std::string name = - info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE : naming::CONDUCTANCE_VARIABLE; + std::string name = info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE + : naming::CONDUCTANCE_VARIABLE; variables.push_back(make_symbol(name)); } if (net_receive_exist()) { @@ -864,13 +871,13 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { } } - for (const auto& ion : info.ions) { + for (const auto& ion: info.ions) { bool need_style = false; - for (const auto& var : ion.reads) { + for (const auto& var: ion.reads) { variables.emplace_back(make_symbol("ion_" + var)); variables.back().is_constant = true; } - for (const auto& var : ion.writes) { + for (const auto& var: ion.writes) { variables.emplace_back(make_symbol("ion_" + var)); if (ion.is_ionic_current(var)) { variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); @@ -885,7 +892,7 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { } } - for (const auto& var : info.pointer_variables) { + for (const auto& var: info.pointer_variables) { auto name = var->get_name(); if (var->has_properties(NmodlType::pointer_var)) { variables.emplace_back(make_symbol(name)); @@ -940,8 +947,8 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { */ std::vector<SymbolType> CodegenCVisitor::get_shadow_variables() { std::vector<SymbolType> variables; - for (const auto& ion : info.ions) { - for (const auto& var : ion.writes) { + for (const auto& ion: info.ions) { + for (const auto& var: ion.writes) { variables.push_back({make_symbol(shadow_varname("ion_" + var))}); if (ion.is_ionic_current(var)) { variables.push_back({make_symbol(shadow_varname("ion_di" + ion.name + "dv"))}); @@ -1120,7 +1127,7 @@ void CodegenCVisitor::print_shadow_reduction_block_begin() { void CodegenCVisitor::print_shadow_reduction_statements() { - for (const auto& statement : shadow_statements) { + for (const auto& statement: shadow_statements) { print_atomic_reduction_pragma(); auto lhs = get_variable_name(statement.lhs); auto rhs = get_variable_name(shadow_varname(statement.lhs)); @@ -1245,7 +1252,7 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, } auto statements = node->get_statements(); - for (const auto& statement : statements) { + for (const auto& statement: statements) { if (statement_to_skip(statement.get())) { continue; } @@ -1314,7 +1321,7 @@ void CodegenCVisitor::print_top_verbatim_blocks() { codegen = true; printing_top_verbatim_blocks = true; - for (const auto& block : info.top_blocks) { + for (const auto& block: info.top_blocks) { if (block->is_verbatim()) { printer->add_newline(2); block->accept(this); @@ -1338,15 +1345,15 @@ void CodegenCVisitor::print_top_verbatim_blocks() { */ void CodegenCVisitor::rename_function_arguments() { auto default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); - for (auto& arg : default_arguments) { + for (auto& arg: default_arguments) { stringutils::trim(arg); RenameVisitor v(arg, "arg_" + arg); - for (const auto& function : info.functions) { + for (const auto& function: info.functions) { if (has_parameter_of_name(function, arg)) { function->accept(&v); } } - for (const auto& function : info.procedures) { + for (const auto& function: info.procedures) { if (has_parameter_of_name(function, arg)) { function->accept(&v); } @@ -1361,12 +1368,12 @@ void CodegenCVisitor::print_function_prototypes() { } codegen = true; printer->add_newline(2); - for (const auto& node : info.functions) { + for (const auto& node: info.functions) { print_function_declaration(node, node->get_node_name()); printer->add_text(";"); printer->add_newline(); } - for (const auto& node : info.procedures) { + for (const auto& node: info.procedures) { print_function_declaration(node, node->get_node_name()); printer->add_text(";"); printer->add_newline(); @@ -1384,8 +1391,9 @@ static TableStatement* get_table_statement(ast::Block* node) { auto table_statements = v.get_nodes(); if (table_statements.size() != 1) { - auto message = "One table statement expected in {} found {}"_format( - node->get_node_name(), table_statements.size()); + auto message = + "One table statement expected in {} found {}"_format(node->get_node_name(), + table_statements.size()); throw std::runtime_error(message); } return dynamic_cast<TableStatement*>(table_statements.front().get()); @@ -1415,11 +1423,11 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_line("}"); printer->add_line("static bool make_table = true;"); - for (const auto& variable : depend_variables) { + for (const auto& variable: depend_variables) { printer->add_line("static {} save_{};"_format(float_type, variable->get_node_name())); } - for (const auto& variable : depend_variables) { + for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); @@ -1453,7 +1461,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { "for(i = 0, x = {}; i < {}; x += dx, i++) {}"_format(tmin_name, with + 1, "{")); auto function = method_name("f_" + name); printer->add_line(" {}({}, x);"_format(function, internal_method_arguments())); - for (const auto& variable : table_variables) { + for (const auto& variable: table_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); @@ -1461,7 +1469,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { } printer->add_line("}"); - for (const auto& variable : depend_variables) { + for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); printer->add_line("save_{} = {};"_format(name, instance_name)); @@ -1494,7 +1502,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); printer->add_line("if (isnan(xi)) {"); - for (const auto& var : table_variables) { + for (const auto& var: table_variables) { auto name = get_variable_name(var->get_node_name()); printer->add_line(" {} = xi;"_format(name)); } @@ -1503,7 +1511,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); - for (const auto& variable : table_variables) { + for (const auto& variable: table_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); @@ -1514,7 +1522,7 @@ void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { printer->add_line("int i = int(xi);"); printer->add_line("double theta = xi - double(i);"); - for (const auto& var : table_variables) { + for (const auto& var: table_variables) { auto instance_name = get_variable_name(var->get_node_name()); auto table_name = get_variable_name("t_" + var->get_node_name()); printer->add_line( @@ -1543,7 +1551,7 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" double v = 0;"); printer->add_line(" IonCurVar ionvar = {0};"); - for (const auto& function : info.functions_with_table) { + for (const auto& function: info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); printer->add_line(" {}({});"_format(name, arguments)); @@ -2077,8 +2085,8 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use } /// integer variable - auto i = - std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); + auto i = std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), + index_comparator); if (i != codegen_int_variables.end()) { return int_variable_name(*i, varname, use_instance); } @@ -2185,7 +2193,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { printer->increase_indent(); if (!info.ions.empty()) { - for (const auto& ion : info.ions) { + for (const auto& ion: info.ions) { auto name = "{}_type"_format(ion.name); printer->add_line("int {};"_format(name)); codegen_global_variables.push_back(make_symbol(name)); @@ -2198,7 +2206,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { } if (!info.state_vars.empty()) { - for (const auto& var : info.state_vars) { + for (const auto& var: info.state_vars) { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { @@ -2215,7 +2223,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto& top_locals = info.top_local_variables; if (!info.vectorize && !top_locals.empty()) { - for (const auto& var : top_locals) { + for (const auto& var: top_locals) { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { @@ -2246,7 +2254,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto& constants = info.constant_variables; if (!globals.empty()) { - for (const auto& var : globals) { + for (const auto& var: globals) { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { @@ -2259,7 +2267,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { } if (!constants.empty()) { - for (const auto& var : constants) { + for (const auto& var: constants) { auto name = var->get_name(); auto value_ptr = var->get_value(); printer->add_line("{} {};"_format(float_type, name)); @@ -2282,7 +2290,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { printer->add_line("double usetable;"); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); - for (const auto& block : info.functions_with_table) { + for (const auto& block: info.functions_with_table) { auto name = block->get_node_name(); printer->add_line("{} tmin_{};"_format(float_type, name)); printer->add_line("{} mfac_{};"_format(float_type, name)); @@ -2290,7 +2298,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { codegen_global_variables.push_back(make_symbol("mfac_" + name)); } - for (const auto& variable : info.table_statement_variables) { + for (const auto& variable: info.table_statement_variables) { auto name = "t_" + variable->get_name(); printer->add_line("{}* {};"_format(float_type, name)); codegen_global_variables.push_back(make_symbol(name)); @@ -2313,7 +2321,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { void CodegenCVisitor::print_mechanism_info() { auto variable_printer = [this](std::vector<SymbolType>& variables) { - for (const auto& v : variables) { + for (const auto& v: variables) { auto name = v->get_name(); if (!info.point_process) { name += "_" + info.mod_suffix; @@ -2351,7 +2359,7 @@ void CodegenCVisitor::print_mechanism_info() { void CodegenCVisitor::print_global_variables_for_hoc() { auto variable_printer = [this](const std::vector<SymbolType>& variables, bool if_array, bool if_vector) { - for (const auto& variable : variables) { + for (const auto& variable: variables) { if (variable->is_array() == if_array) { auto name = get_variable_name(variable->get_name()); auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); @@ -2447,7 +2455,7 @@ void CodegenCVisitor::print_mechanism_register() { } /// types for ion - for (const auto& ion : info.ions) { + for (const auto& ion: info.ions) { auto type = get_variable_name(ion.name + "_type"); auto name = add_escape_quote(ion.name + "_ion"); printer->add_line(type + " = nrn_get_mechtype(" + name + ");"); @@ -2487,7 +2495,7 @@ void CodegenCVisitor::print_mechanism_register() { // clang-format on /// register semantics for index variables - for (auto& semantic : info.semantics) { + for (auto& semantic: info.semantics) { auto args = "mech_type, {}, {}"_format(semantic.index, add_escape_quote(semantic.name)); printer->add_line("hoc_register_dparam_semantics({});"_format(args)); } @@ -2589,13 +2597,13 @@ void CodegenCVisitor::print_mechanism_range_var_structure() { printer->add_newline(2); printer->add_line("/** all mechanism instance variables */"); printer->start_block("struct {} "_format(instance_struct())); - for (auto& var : codegen_float_variables) { + for (auto& var: codegen_float_variables) { auto name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? k_const() : ""; printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); } - for (auto& var : codegen_int_variables) { + for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? k_const() : ""; @@ -2607,7 +2615,7 @@ void CodegenCVisitor::print_mechanism_range_var_structure() { } } if (channel_task_dependency_enabled()) { - for (auto& var : codegen_shadow_variables) { + for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); printer->add_line("{}* {}{};"_format(float_type, k_restrict(), name)); } @@ -2625,12 +2633,12 @@ void CodegenCVisitor::print_ion_var_structure() { printer->add_newline(2); printer->add_line("/** ion write variables */"); printer->start_block("struct IonCurVar"); - for (auto& ion : info.ions) { - for (auto& var : ion.writes) { + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { printer->add_line("{} {};"_format(default_float_data_type(), var)); } } - for (auto& var : info.currents) { + for (auto& var: info.currents) { if (!info.is_ion_variable(var)) { printer->add_line("{} {};"_format(default_float_data_type(), var)); } @@ -2664,7 +2672,7 @@ void CodegenCVisitor::print_global_variable_setup() { allocated_variables.push_back(dlist1); int id = 0; - for (auto& prime : info.prime_variables_by_order) { + for (auto& prime: info.prime_variables_by_order) { auto name = prime->get_name(); printer->add_line("{}[{}] = {};"_format(slist1, id, position_of_float_var(name))); printer->add_line("{}[{}] = {};"_format(dlist1, id, position_of_float_var("D" + name))); @@ -2679,7 +2687,7 @@ void CodegenCVisitor::print_global_variable_setup() { auto nprimes = info.primes_size; printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(slist2, nprimes)); int id = 0; - for (auto& variable : primes) { + for (auto& variable: primes) { auto name = variable->get_name(); printer->add_line("{}[{}] = {};"_format(slist2, id, position_of_float_var(name))); id++; @@ -2697,7 +2705,7 @@ void CodegenCVisitor::print_global_variable_setup() { } /// initialize global variables - for (auto& var : info.state_vars) { + for (auto& var: info.state_vars) { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { @@ -2713,7 +2721,7 @@ void CodegenCVisitor::print_global_variable_setup() { } /// initialize global variables - for (auto& var : info.global_variables) { + for (auto& var: info.global_variables) { if (!var->is_array()) { auto name = get_variable_name(var->get_name()); double value = 0; @@ -2727,7 +2735,7 @@ void CodegenCVisitor::print_global_variable_setup() { } /// initialize constant variables - for (auto& var : info.constant_variables) { + for (auto& var: info.constant_variables) { auto name = get_variable_name(var->get_name()); auto value_ptr = var->get_value(); double value = 0; @@ -2742,7 +2750,7 @@ void CodegenCVisitor::print_global_variable_setup() { auto name = get_variable_name(naming::USE_TABLE_VARIABLE); printer->add_line("{} = 1;"_format(name)); - for (auto& variable : info.table_statement_variables) { + for (auto& variable: info.table_statement_variables) { auto name = get_variable_name("t_" + variable->get_name()); int num_values = variable->get_num_values(); printer->add_line( @@ -2759,7 +2767,7 @@ void CodegenCVisitor::print_global_variable_setup() { if (allocated_variables.empty()) { printer->add_line("// do nothing"); } else { - for (auto& var : allocated_variables) { + for (auto& var: allocated_variables) { printer->add_line("mem_free({});"_format(var)); } } @@ -2774,7 +2782,7 @@ void CodegenCVisitor::print_shadow_vector_setup() { printer->start_block("static inline void setup_shadow_vectors({}) "_format(args)); if (channel_task_dependency_enabled()) { printer->add_line("int nodecount = ml->nodecount;"); - for (auto& var : codegen_shadow_variables) { + for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); auto type = default_float_data_type(); auto allocation = "({0}*) mem_alloc(nodecount, sizeof({0}))"_format(type); @@ -2787,7 +2795,7 @@ void CodegenCVisitor::print_shadow_vector_setup() { args = "{}* inst"_format(instance_struct()); printer->start_block("static inline void free_shadow_vectors({}) "_format(args)); if (channel_task_dependency_enabled()) { - for (auto& var : codegen_shadow_variables) { + for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); printer->add_line("mem_free(inst->{});"_format(name)); } @@ -2863,21 +2871,22 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("Datum* indexes = ml->pdata;"); int id = 0; std::vector<std::string> variables_to_free; - for (auto& var : codegen_float_variables) { + for (auto& var: codegen_float_variables) { auto name = var->get_name(); auto default_type = default_float_data_type(); auto range_var_type = get_range_var_float_type(var); if (default_type == range_var_type) { printer->add_line("inst->{} = ml->data+{}{};"_format(name, id, stride)); } else { - printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format( - name, id, stride)); + printer->add_line( + "inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format(name, id, + stride)); variables_to_free.push_back(name); } id += var->get_length(); } - for (auto& var : codegen_int_variables) { + for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { printer->add_line("inst->{} = ml->pdata;"_format(name)); @@ -2894,7 +2903,7 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); if (range_variable_setup_required()) { - for (auto& var : variables_to_free) { + for (auto& var: variables_to_free) { printer->add_line("mem_free((void*)inst->{});"_format(var)); } } @@ -2917,12 +2926,12 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { /// read ion statements auto read_statements = ion_read_statements(BlockType::Initial); - for (auto& statement : read_statements) { + for (auto& statement: read_statements) { printer->add_line(statement); } /// initialize state variables (excluding ion state) - for (auto& var : info.state_vars) { + for (auto& var: info.state_vars) { auto name = var->get_name(); auto lhs = get_variable_name(name); auto rhs = get_variable_name(name + "0"); @@ -2937,7 +2946,7 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { /// write ion statements auto write_statements = ion_write_statements(BlockType::Initial); - for (auto& statement : write_statements) { + for (auto& statement: write_statements) { auto text = process_shadow_update_statement(statement, BlockType::Initial); printer->add_line(text); } @@ -3168,7 +3177,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node) { if (!parameters.empty()) { int i = 0; printer->add_newline(); - for (auto& parameter : parameters) { + for (auto& parameter: parameters) { auto name = parameter->get_node_name(); VarUsageVisitor vu; auto var_used = vu.variable_used(node, "(*" + name + ")"); @@ -3377,7 +3386,7 @@ void CodegenCVisitor::print_net_receive() { /// rename arguments but need to see if they are actually used auto parameters = node->get_parameters(); - for (auto& parameter : parameters) { + for (auto& parameter: parameters) { auto name = parameter->get_node_name(); auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { @@ -3560,7 +3569,7 @@ void CodegenCVisitor::print_nrn_state() { } auto read_statements = ion_read_statements(BlockType::State); - for (auto& statement : read_statements) { + for (auto& statement: read_statements) { printer->add_line(statement); } @@ -3597,7 +3606,7 @@ void CodegenCVisitor::print_nrn_state() { } auto write_statements = ion_write_statements(BlockType::State); - for (auto& statement : write_statements) { + for (auto& statement: write_statements) { auto text = process_shadow_update_statement(statement, BlockType::State); printer->add_line(text); } @@ -3628,7 +3637,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { printer->start_block("static inline double nrn_current({})"_format(args)); printer->add_line("double current = 0.0;"); print_statement_block(block, false, false); - for (auto& current : info.currents) { + for (auto& current: info.currents) { auto name = get_variable_name(current); printer->add_line("current += {};"_format(name)); } @@ -3642,7 +3651,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { print_statement_block(block.get(), false, false); if (!info.currents.empty()) { std::string sum; - for (const auto& current : info.currents) { + for (const auto& current: info.currents) { auto var = breakpoint_current(current); sum += get_variable_name(var); if (¤t != &info.currents.back()) { @@ -3653,7 +3662,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { } if (!info.conductances.empty()) { std::string sum; - for (const auto& conductance : info.conductances) { + for (const auto& conductance: info.conductances) { auto var = breakpoint_current(conductance.variable); sum += get_variable_name(var); if (&conductance != &info.conductances.back()) { @@ -3662,7 +3671,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { } printer->add_line("double g = {};"_format(sum)); } - for (const auto& conductance : info.conductances) { + for (const auto& conductance: info.conductances) { if (!conductance.ion.empty()) { auto lhs = "ion_di" + conductance.ion + "dv"; auto rhs = get_variable_name(conductance.variable); @@ -3676,8 +3685,8 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { printer->add_line("double g = nrn_current({}+0.001);"_format(internal_method_arguments())); - for (auto& ion : info.ions) { - for (auto& var : ion.writes) { + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { auto name = get_variable_name(var); printer->add_line("double di{} = {};"_format(ion.name, name)); @@ -3686,8 +3695,8 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { } printer->add_line("double rhs = nrn_current({});"_format(internal_method_arguments())); printer->add_line("g = (g-rhs)/0.001;"); - for (auto& ion : info.ions) { - for (auto& var : ion.writes) { + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { auto lhs = "ion_di" + ion.name + "dv"; auto rhs = "(di{}-{})/0.001"_format(ion.name, get_variable_name(var)); @@ -3712,7 +3721,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { } auto read_statements = ion_read_statements(BlockType::Equation); - for (auto& statement : read_statements) { + for (auto& statement: read_statements) { printer->add_line(statement); } @@ -3723,7 +3732,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { } auto write_statements = ion_write_statements(BlockType::Equation); - for (auto& statement : write_statements) { + for (auto& statement: write_statements) { auto text = process_shadow_update_statement(statement, BlockType::Equation); printer->add_line(text); } @@ -3816,10 +3825,10 @@ void CodegenCVisitor::print_data_structures() { void CodegenCVisitor::print_compute_functions() { print_top_verbatim_blocks(); print_function_prototypes(); - for (const auto& procedure : info.procedures) { + for (const auto& procedure: info.procedures) { print_procedure(procedure); } - for (const auto& function : info.functions) { + for (const auto& function: info.functions) { print_function(function); } print_net_init(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 54cd58943d..2820338b92 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_CODEGEN_C_VISITOR_HPP -#define NMODL_CODEGEN_C_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <algorithm> #include <cmath> @@ -90,8 +96,10 @@ struct IndexVariableInfo { bool is_vdata = false, bool is_index = false, bool is_integer = false) - : symbol(symbol), is_vdata(is_vdata), is_index(is_index), is_integer(is_integer) { - } + : symbol(symbol) + , is_vdata(is_vdata) + , is_index(is_index) + , is_integer(is_integer) {} }; @@ -134,7 +142,7 @@ struct ShadowUseStatement { * - return statement in the verbatim block of inlined function not handled * for example, see netstim.mod where we removed return from verbatim block */ -class CodegenCVisitor : public AstVisitor { +class CodegenCVisitor: public AstVisitor { protected: using SymbolType = std::shared_ptr<symtab::Symbol>; @@ -902,22 +910,20 @@ class CodegenCVisitor : public AstVisitor { LayoutType layout, std::string float_type, std::string extension = ".cpp") - : printer(new CodePrinter(output_dir + "/" + mod_filename + extension)), - mod_filename(mod_filename), - layout(layout), - float_type(float_type) { - } + : printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) + , mod_filename(mod_filename) + , layout(layout) + , float_type(float_type) {} CodegenCVisitor(std::string mod_filename, std::stringstream& stream, LayoutType layout, std::string float_type) - : printer(new CodePrinter(stream)), - mod_filename(mod_filename), - layout(layout), - float_type(float_type) { - } + : printer(new CodePrinter(stream)) + , mod_filename(mod_filename) + , layout(layout) + , float_type(float_type) {} virtual void visit_binary_expression(ast::BinaryExpression* node) override; virtual void visit_binary_operator(ast::BinaryOperator* node) override; @@ -975,7 +981,7 @@ void CodegenCVisitor::print_vector_elements(const std::vector<T>& elements, template <typename T> bool has_parameter_of_name(const T& node, const std::string& name) { auto parameters = node->get_parameters(); - for (const auto& parameter : parameters) { + for (const auto& parameter: parameters) { if (parameter->get_node_name() == name) { return true; } @@ -1020,5 +1026,3 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin enable_variable_name_lookup = true; } - -#endif diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index dbf0870e48..4cd1a7e296 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fmt/format.h> #include "codegen/codegen_cuda_visitor.hpp" @@ -142,11 +149,11 @@ void CodegenCudaVisitor::print_compute_functions() { print_top_verbatim_blocks(); print_function_prototypes(); - for (const auto& procedure : info.procedures) { + for (const auto& procedure: info.procedures) { print_procedure(procedure); } - for (const auto& function : info.functions) { + for (const auto& function: info.functions) { print_function(function); } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 15e30ab076..bcef9fa410 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_CODEGEN_C_CUDA_VISITOR_HPP -#define NMODL_CODEGEN_C_CUDA_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "codegen/codegen_c_visitor.hpp" @@ -8,7 +14,7 @@ * \class CodegenCudaVisitor * \brief Visitor for printing CUDA backend */ -class CodegenCudaVisitor : public CodegenCVisitor { +class CodegenCudaVisitor: public CodegenCVisitor { void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); protected: @@ -85,15 +91,11 @@ class CodegenCudaVisitor : public CodegenCVisitor { std::string output_dir, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".cu") { - } + : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".cu") {} CodegenCudaVisitor(std::string mod_file, std::stringstream& stream, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) { - } -}; - -#endif + : CodegenCVisitor(mod_file, stream, layout, float_type) {} +}; \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 9e2739c596..f59b0bcbbd 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <algorithm> #include <cmath> @@ -70,16 +77,16 @@ void CodegenHelperVisitor::find_ion_variables() { }; /// iterate over all ion types and construct the Ion objects - for (auto& ion_var : ion_vars) { + for (auto& ion_var: ion_vars) { auto ion_name = ion_var->get_name(); Ion ion(ion_name); - for (auto& read_var : read_ion_vars) { + for (auto& read_var: read_ion_vars) { auto var = read_var->get_name(); if (ion_variable(var, ion_name)) { ion.reads.push_back(var); } } - for (auto& write_var : write_ion_vars) { + for (auto& write_var: write_ion_vars) { auto varname = write_var->get_name(); if (ion_variable(varname, ion_name)) { ion.writes.push_back(varname); @@ -94,15 +101,15 @@ void CodegenHelperVisitor::find_ion_variables() { /// once ions are populated, we can find all currents auto vars = psymtab->get_variables_with_properties(NmodlType::nonspecific_cur_var); - for (auto& var : vars) { + for (auto& var: vars) { info.currents.push_back(var->get_name()); } vars = psymtab->get_variables_with_properties(NmodlType::electrode_cur_var); - for (auto& var : vars) { + for (auto& var: vars) { info.currents.push_back(var->get_name()); } - for (auto& ion : info.ions) { - for (auto& var : ion.writes) { + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { info.currents.push_back(var); } @@ -134,7 +141,7 @@ void CodegenHelperVisitor::find_non_range_variables() { std::string variables; auto vars = psymtab->get_variables_with_properties(NmodlType::global_var); - for (auto& var : vars) { + for (auto& var: vars) { if (info.thread_safe && var->get_write_count() > 0) { var->mark_thread_safe(); info.thread_variables.push_back(var); @@ -172,7 +179,7 @@ void CodegenHelperVisitor::find_non_range_variables() { | NmodlType::write_ion_var; // clang-format on vars = psymtab->get_variables(with, without); - for (auto& var : vars) { + for (auto& var: vars) { // some variables like area and diam are declared in parameter // block but they are not global if (var->get_name() == naming::DIAM_VARIABLE || var->get_name() == naming::AREA_VARIABLE || @@ -231,14 +238,14 @@ void CodegenHelperVisitor::find_non_range_variables() { } /// find total size of local variables in global scope - for (auto& var : info.top_local_variables) { + for (auto& var: info.top_local_variables) { info.top_local_thread_size += var->get_length(); } /// find number of prime variables and total size auto primes = psymtab->get_variables_with_properties(NmodlType::prime_name); info.num_primes = primes.size(); - for (auto& variable : primes) { + for (auto& variable: primes) { info.primes_size += variable->get_length(); } @@ -382,7 +389,7 @@ void CodegenHelperVisitor::find_range_variables() { | NmodlType::extern_neuron_variable; // clang-format on variables = psymtab->get_variables(with, without); - for (auto& variable : variables) { + for (auto& variable: variables) { // clang-format off auto properties = NmodlType::read_ion_var | NmodlType::write_ion_var; @@ -533,7 +540,7 @@ void CodegenHelperVisitor::visit_solve_block(SolveBlock* node) { */ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { auto statements = node->get_statements(); - for (auto& statement : statements) { + for (auto& statement: statements) { statement->accept(this); if (under_derivative_block && assign_lhs && (assign_lhs->is_name() || assign_lhs->is_var_name())) { @@ -608,7 +615,7 @@ void CodegenHelperVisitor::find_solve_node() { void CodegenHelperVisitor::visit_program(Program* node) { psymtab = node->get_symbol_table(); auto blocks = node->get_blocks(); - for (auto& block : blocks) { + for (auto& block: blocks) { info.top_blocks.push_back(block.get()); if (block->is_verbatim()) { info.top_verbatim_blocks.push_back(block.get()); diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index da94181c25..80fe641243 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef CODEGEN_HELPER_VISITOR_HPP -#define CODEGEN_HELPER_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> @@ -26,7 +32,7 @@ * - POINTER rng and if it's also assigned rng[4] then it is printed as one value. * Need to check what is correct value. */ -class CodegenHelperVisitor : public AstVisitor { +class CodegenHelperVisitor: public AstVisitor { using SymbolType = std::shared_ptr<symtab::Symbol>; /// holds all codegen related information @@ -63,25 +69,23 @@ class CodegenHelperVisitor : public AstVisitor { /// run visitor and return information for code generation codegen::CodegenInfo analyze(ast::Program* node); - virtual void visit_elctrode_current(ast::ElctrodeCurrent* node) override; - virtual void visit_suffix(ast::Suffix* node) override; - virtual void visit_function_call(ast::FunctionCall* node) override; - virtual void visit_binary_expression(ast::BinaryExpression* node) override; - virtual void visit_conductance_hint(ast::ConductanceHint* node) override; - virtual void visit_procedure_block(ast::ProcedureBlock* node) override; - virtual void visit_function_block(ast::FunctionBlock* node) override; - virtual void visit_solve_block(ast::SolveBlock* node) override; - virtual void visit_statement_block(ast::StatementBlock* node) override; - virtual void visit_initial_block(ast::InitialBlock* node) override; - virtual void visit_breakpoint_block(ast::BreakpointBlock* node) override; - virtual void visit_derivative_block(ast::DerivativeBlock* node) override; - virtual void visit_net_receive_block(ast::NetReceiveBlock* node) override; - virtual void visit_bbcore_ptr(ast::BbcorePtr* node) override; - virtual void visit_watch(ast::Watch* node) override; - virtual void visit_watch_statement(ast::WatchStatement* node) override; - virtual void visit_for_netcon(ast::ForNetcon* node) override; - virtual void visit_table_statement(ast::TableStatement* node) override; - virtual void visit_program(ast::Program* node) override; -}; - -#endif + void visit_elctrode_current(ast::ElctrodeCurrent* node) override; + void visit_suffix(ast::Suffix* node) override; + void visit_function_call(ast::FunctionCall* node) override; + void visit_binary_expression(ast::BinaryExpression* node) override; + void visit_conductance_hint(ast::ConductanceHint* node) override; + void visit_procedure_block(ast::ProcedureBlock* node) override; + void visit_function_block(ast::FunctionBlock* node) override; + void visit_solve_block(ast::SolveBlock* node) override; + void visit_statement_block(ast::StatementBlock* node) override; + void visit_initial_block(ast::InitialBlock* node) override; + void visit_breakpoint_block(ast::BreakpointBlock* node) override; + void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_net_receive_block(ast::NetReceiveBlock* node) override; + void visit_bbcore_ptr(ast::BbcorePtr* node) override; + void visit_watch(ast::Watch* node) override; + void visit_watch_statement(ast::WatchStatement* node) override; + void visit_for_netcon(ast::ForNetcon* node) override; + void visit_table_statement(ast::TableStatement* node) override; + void visit_program(ast::Program* node) override; +}; \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 09a1bdc940..4fe9e5ef64 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "codegen/codegen_info.hpp" using namespace codegen; @@ -5,7 +12,7 @@ using namespace codegen; /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() { - for (const auto& ion : ions) { + for (const auto& ion: ions) { if (!ion.writes.empty()) { return true; } @@ -16,8 +23,8 @@ bool CodegenInfo::ion_has_write_variable() { /// if given variable is ion write variable bool CodegenInfo::is_ion_write_variable(const std::string& name) { - for (const auto& ion : ions) { - for (auto& var : ion.writes) { + for (const auto& ion: ions) { + for (auto& var: ion.writes) { if (var == name) { return true; } @@ -29,8 +36,8 @@ bool CodegenInfo::is_ion_write_variable(const std::string& name) { /// if given variable is ion read variable bool CodegenInfo::is_ion_read_variable(const std::string& name) { - for (const auto& ion : ions) { - for (auto& var : ion.reads) { + for (const auto& ion: ions) { + for (auto& var: ion.reads) { if (var == name) { return true; } @@ -48,7 +55,7 @@ bool CodegenInfo::is_ion_variable(const std::string& name) { /// if a current bool CodegenInfo::is_current(const std::string& name) { - for (auto& var : currents) { + for (auto& var: currents) { if (var == name) { return true; } @@ -58,7 +65,7 @@ bool CodegenInfo::is_current(const std::string& name) { bool CodegenInfo::function_uses_table(std::string& name) const { - for (auto& function : functions_with_table) { + for (auto& function: functions_with_table) { if (name == function->get_node_name()) { return true; } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 5608a23fa2..235409d626 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -1,5 +1,11 @@ -#ifndef CODEGEN_INFO_HPP -#define CODEGEN_INFO_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <string> @@ -9,359 +15,359 @@ namespace codegen { - /** - * \class Conductance - * \brief Represent conductance statements used in mod file - */ - struct Conductance { - /// name of the ion - std::string ion; +/** + * \class Conductance + * \brief Represent conductance statements used in mod file + */ +struct Conductance { + /// name of the ion + std::string ion; - /// ion variable like intra/extra concentration - std::string variable; - }; + /// ion variable like intra/extra concentration + std::string variable; +}; - /** - * \class Ion - * \brief Represent ions used in mod file - */ - struct Ion { - /// name of the ion - std::string name; - - /// ion variables that are being read - std::vector<std::string> reads; - - /// ion variables that are being written - std::vector<std::string> writes; - - /// if style semantic needed - bool need_style = false; - - Ion() = delete; - - Ion(std::string name) : name(name) { - } - - /** - * Check if variable name is a ionic current - * - * This is equivalent of IONCUR flag in mod2c. - * If it is read variable then also get NRNCURIN flag. - * If it is write variables then also get NRNCUROUT flag. - */ - bool is_ionic_current(std::string text) const { - return text == ("i" + name); - } - - /** - * Check if variable name is internal cell concentration - * - * This is equivalent of IONIN flag in mod2c. - */ - bool is_intra_cell_conc(std::string text) const { - return text == (name + "i"); - } - - /** - * Check if variable name is external cell concentration - * - * This is equivalent of IONOUT flag in mod2c. - */ - bool is_extra_cell_conc(std::string text) const { - return text == (name + "o"); - } - - /** - * Check if variable name is reveral potential - * - * This is equivalent of IONEREV flag in mod2c. - */ - bool is_rev_potential(std::string text) const { - return text == ("e" + name); - } - - /// check if it is either internal or external concentration - bool is_ionic_conc(std::string text) const { - return is_intra_cell_conc(text) || is_extra_cell_conc(text); - } - }; +/** + * \class Ion + * \brief Represent ions used in mod file + */ +struct Ion { + /// name of the ion + std::string name; + /// ion variables that are being read + std::vector<std::string> reads; - /** - * \class IndexSemantics - * \brief Represent semantic information for index variable - */ - struct IndexSemantics { - /// start position in the int array - int index; + /// ion variables that are being written + std::vector<std::string> writes; - /// name/type of the variable (i.e. semantics) - std::string name; + /// if style semantic needed + bool need_style = false; - /// number of elements (typically one) - int size; + Ion() = delete; - IndexSemantics() = delete; - IndexSemantics(int index, std::string name, int size) - : index(index), name(name), size(size) { - } - }; + Ion(std::string name) + : name(name) {} + /** + * Check if variable name is a ionic current + * + * This is equivalent of IONCUR flag in mod2c. + * If it is read variable then also get NRNCURIN flag. + * If it is write variables then also get NRNCUROUT flag. + */ + bool is_ionic_current(std::string text) const { + return text == ("i" + name); + } /** - * \class CodegenInfo - * \brief Represent information collected from AST for code generation + * Check if variable name is internal cell concentration * - * Code generation passes require different information from AST. This - * information is gathered in this single class. + * This is equivalent of IONIN flag in mod2c. + */ + bool is_intra_cell_conc(std::string text) const { + return text == (name + "i"); + } + + /** + * Check if variable name is external cell concentration * - * \todo : need to store all Define i.e. macro definitions? + * This is equivalent of IONOUT flag in mod2c. */ - struct CodegenInfo { - /// name of mod file - std::string mod_file; + bool is_extra_cell_conc(std::string text) const { + return text == (name + "o"); + } - /// name of the suffix - std::string mod_suffix; + /** + * Check if variable name is reveral potential + * + * This is equivalent of IONEREV flag in mod2c. + */ + bool is_rev_potential(std::string text) const { + return text == ("e" + name); + } + + /// check if it is either internal or external concentration + bool is_ionic_conc(std::string text) const { + return is_intra_cell_conc(text) || is_extra_cell_conc(text); + } +}; + + +/** + * \class IndexSemantics + * \brief Represent semantic information for index variable + */ +struct IndexSemantics { + /// start position in the int array + int index; + + /// name/type of the variable (i.e. semantics) + std::string name; + + /// number of elements (typically one) + int size; + + IndexSemantics() = delete; + IndexSemantics(int index, std::string name, int size) + : index(index) + , name(name) + , size(size) {} +}; - /// if mod file is vectorizable (always true for coreneuron) - bool vectorize = true; - /// if mod file is thread safe (always true for coreneuron) - bool thread_safe = true; +/** + * \class CodegenInfo + * \brief Represent information collected from AST for code generation + * + * Code generation passes require different information from AST. This + * information is gathered in this single class. + * + * \todo : need to store all Define i.e. macro definitions? + */ +struct CodegenInfo { + /// name of mod file + std::string mod_file; - /// if mod file is point process - bool point_process = false; + /// name of the suffix + std::string mod_suffix; - /// if mod file is artificial cell - bool artificial_cell = false; + /// if mod file is vectorizable (always true for coreneuron) + bool vectorize = true; - /// if electrode current specified - bool electorde_current = false; + /// if mod file is thread safe (always true for coreneuron) + bool thread_safe = true; - /// if thread thread call back routines need to register - bool thread_callback_register = false; + /// if mod file is point process + bool point_process = false; - /// if bbcore pointer is used - bool bbcore_pointer_used = false; + /// if mod file is artificial cell + bool artificial_cell = false; - /// if write concentration call required in initial block - bool write_concentration = false; + /// if electrode current specified + bool electorde_current = false; - /// if net_send function is used - bool net_send_used = false; + /// if thread thread call back routines need to register + bool thread_callback_register = false; - /// if net_even function is used - bool net_event_used = false; + /// if bbcore pointer is used + bool bbcore_pointer_used = false; - /// if diam is used - bool diam_used = false; + /// if write concentration call required in initial block + bool write_concentration = false; - /// if area is used - bool area_used = false; + /// if net_send function is used + bool net_send_used = false; - /// if for_netcon is used - bool for_netcon_used = false; + /// if net_even function is used + bool net_event_used = false; - /// number of watch expressions - int watch_count = 0; + /// if diam is used + bool diam_used = false; - // number of table statements - int table_count = 0; + /// if area is used + bool area_used = false; - /** - * thread_data_index indicates number of threads being allocated. - * For example, if there is derivimplicit method used, then two thread - * structures are created. When we print global variables then - * thread_data_index is used to indicate whats next thread id to use. - */ - int thread_data_index = 0; + /// if for_netcon is used + bool for_netcon_used = false; - /** - * Top local variables are those local variables that appear in global scope. - * Thread structure is created for top local variables and doesn't thread id - * 0. For example, if derivimplicit method is used then thread id 0 is assigned - * to those structures first. And then next thread id is assigned for top local - * variables. The idea of thread is assignement is to have same order for variables - * between neuron and coreneuron. - */ + /// number of watch expressions + int watch_count = 0; + + // number of table statements + int table_count = 0; + + /** + * thread_data_index indicates number of threads being allocated. + * For example, if there is derivimplicit method used, then two thread + * structures are created. When we print global variables then + * thread_data_index is used to indicate whats next thread id to use. + */ + int thread_data_index = 0; + + /** + * Top local variables are those local variables that appear in global scope. + * Thread structure is created for top local variables and doesn't thread id + * 0. For example, if derivimplicit method is used then thread id 0 is assigned + * to those structures first. And then next thread id is assigned for top local + * variables. The idea of thread is assignement is to have same order for variables + * between neuron and coreneuron. + */ - /// thread id for top local variables - int top_local_thread_id = 0; + /// thread id for top local variables + int top_local_thread_id = 0; - /// total length of all top local variables - int top_local_thread_size = 0; + /// total length of all top local variables + int top_local_thread_size = 0; - /// thread id for thread promoted variables - int thread_var_thread_id = 0; + /// thread id for thread promoted variables + int thread_var_thread_id = 0; - /// sum of length of thread promoted variables - int thread_var_data_size = 0; + /// sum of length of thread promoted variables + int thread_var_data_size = 0; - /// thread id for derivimplicit variables - int derivimplicit_var_thread_id = -1; + /// thread id for derivimplicit variables + int derivimplicit_var_thread_id = -1; - /// slist/dlist id for derivimplicit block - int derivimplicit_list_num = -1; + /// slist/dlist id for derivimplicit block + int derivimplicit_list_num = -1; - /// slist/dlist id for for euler block - int euler_list_num = -1; + /// slist/dlist id for for euler block + int euler_list_num = -1; - /// number of solve blocks in mod file - int num_solve_blocks = 0; + /// number of solve blocks in mod file + int num_solve_blocks = 0; - /// number of primes (all state variables not necessary to be prime) - int num_primes = 0; + /// number of primes (all state variables not necessary to be prime) + int num_primes = 0; - /// sum of length of all prime variables - int primes_size = 0; + /// sum of length of all prime variables + int primes_size = 0; - /// number of equations (i.e. statements) in derivative block - /// typically equal to number of primes - int num_equations = 0; + /// number of equations (i.e. statements) in derivative block + /// typically equal to number of primes + int num_equations = 0; - /// block used in solve statement - /// typically derivative block or procedure - ast::Block* solve_node = nullptr; + /// block used in solve statement + /// typically derivative block or procedure + ast::Block* solve_node = nullptr; - /// \todo: name of the solve block - std::string solve_block_name; + /// \todo: name of the solve block + std::string solve_block_name; - /// solve method used - std::string solve_method; + /// solve method used + std::string solve_method; - /// derivative block - ast::BreakpointBlock* breakpoint_node = nullptr; + /// derivative block + ast::BreakpointBlock* breakpoint_node = nullptr; - /// net receive block for point process - ast::NetReceiveBlock* net_receive_node = nullptr; + /// net receive block for point process + ast::NetReceiveBlock* net_receive_node = nullptr; - /// number of arguments to net_receive block - int num_net_receive_parameters = 0; + /// number of arguments to net_receive block + int num_net_receive_parameters = 0; - /// initial block within net receive block - ast::InitialBlock* net_receive_initial_node = nullptr; + /// initial block within net receive block + ast::InitialBlock* net_receive_initial_node = nullptr; - /// initial block - ast::InitialBlock* initial_node = nullptr; + /// initial block + ast::InitialBlock* initial_node = nullptr; - /// all procedures defined in the mod file - std::vector<ast::ProcedureBlock*> procedures; + /// all procedures defined in the mod file + std::vector<ast::ProcedureBlock*> procedures; - /// all functions defined in the mod file - std::vector<ast::FunctionBlock*> functions; + /// all functions defined in the mod file + std::vector<ast::FunctionBlock*> functions; - /// ions used in the mod file - std::vector<Ion> ions; + /// ions used in the mod file + std::vector<Ion> ions; - using SymbolType = std::shared_ptr<symtab::Symbol>; + using SymbolType = std::shared_ptr<symtab::Symbol>; - /// range variables which are parameter as well - std::vector<SymbolType> range_parameter_vars; + /// range variables which are parameter as well + std::vector<SymbolType> range_parameter_vars; - /// range variables which are dependent variables as well - std::vector<SymbolType> range_dependent_vars; + /// range variables which are dependent variables as well + std::vector<SymbolType> range_dependent_vars; - /// reamining dependent variables - std::vector<SymbolType> dependent_vars; + /// reamining dependent variables + std::vector<SymbolType> dependent_vars; - /// state variables - std::vector<SymbolType> state_vars; + /// state variables + std::vector<SymbolType> state_vars; - /// ion variables which are also state variables - std::vector<SymbolType> ion_state_vars; + /// ion variables which are also state variables + std::vector<SymbolType> ion_state_vars; - /// local variables in the global scope - std::vector<SymbolType> top_local_variables; + /// local variables in the global scope + std::vector<SymbolType> top_local_variables; - /// pointer or bbcore pointer variables - std::vector<SymbolType> pointer_variables; + /// pointer or bbcore pointer variables + std::vector<SymbolType> pointer_variables; - /// index/offset for first pointer variable if exist - int first_pointer_var_index = -1; + /// index/offset for first pointer variable if exist + int first_pointer_var_index = -1; - /// tqitem index in integer variables - /// note that if tqitem doesn't exist then the default value should be 0 - int tqitem_index = 0; + /// tqitem index in integer variables + /// note that if tqitem doesn't exist then the default value should be 0 + int tqitem_index = 0; - /// global variables - std::vector<SymbolType> global_variables; + /// global variables + std::vector<SymbolType> global_variables; - /// constant variables - std::vector<SymbolType> constant_variables; + /// constant variables + std::vector<SymbolType> constant_variables; - /// thread variables (e.g. global variables promoted to thread) - std::vector<SymbolType> thread_variables; + /// thread variables (e.g. global variables promoted to thread) + std::vector<SymbolType> thread_variables; - /// new one used in print_ion_types - std::vector<SymbolType> use_ion_variables; + /// new one used in print_ion_types + std::vector<SymbolType> use_ion_variables; - /// this is the order in which they appear in derivative block - /// this is required while printing them in initlist function - std::vector<SymbolType> prime_variables_by_order; + /// this is the order in which they appear in derivative block + /// this is required while printing them in initlist function + std::vector<SymbolType> prime_variables_by_order; - /// table variables - std::vector<SymbolType> table_statement_variables; - std::vector<SymbolType> table_dependent_variables; + /// table variables + std::vector<SymbolType> table_statement_variables; + std::vector<SymbolType> table_dependent_variables; - /// function or procedures with table statement - std::vector<ast::Block*> functions_with_table; + /// function or procedures with table statement + std::vector<ast::Block*> functions_with_table; - /// represent conductance statements used in mod file - std::vector<Conductance> conductances; + /// represent conductance statements used in mod file + std::vector<Conductance> conductances; - /// index variable semantic information - std::vector<IndexSemantics> semantics; + /// index variable semantic information + std::vector<IndexSemantics> semantics; - /// non specific and ionic currents - std::vector<std::string> currents; + /// non specific and ionic currents + std::vector<std::string> currents; - /// if mod file used dervimplicit method - bool derivimplicit_used = false; + /// if mod file used dervimplicit method + bool derivimplicit_used = false; - /// if mod file used euler method - bool euler_used = false; + /// if mod file used euler method + bool euler_used = false; - /// if mod file used cnexp method - bool cnexp_used = false; + /// if mod file used cnexp method + bool cnexp_used = false; - /// all top level global blocks - std::vector<ast::Node*> top_blocks; + /// all top level global blocks + std::vector<ast::Node*> top_blocks; - /// all top level verbatim blocks - std::vector<ast::Node*> top_verbatim_blocks; + /// all top level verbatim blocks + std::vector<ast::Node*> top_verbatim_blocks; - /// all watch statements - std::vector<ast::WatchStatement*> watch_statements; + /// all watch statements + std::vector<ast::WatchStatement*> watch_statements; - /// if any ion has write variable - bool ion_has_write_variable(); + /// if any ion has write variable + bool ion_has_write_variable(); - /// if given variable is ion write variable - bool is_ion_write_variable(const std::string& name); + /// if given variable is ion write variable + bool is_ion_write_variable(const std::string& name); - /// if given variable is ion read variable - bool is_ion_read_variable(const std::string& name); + /// if given variable is ion read variable + bool is_ion_read_variable(const std::string& name); - /// if either read or write variable - bool is_ion_variable(const std::string& name); + /// if either read or write variable + bool is_ion_variable(const std::string& name); - /// if a current - bool is_current(const std::string& name); + /// if a current + bool is_current(const std::string& name); - /// if watch statements are used - bool is_watch_used() const { - return watch_count > 0; - } + /// if watch statements are used + bool is_watch_used() const { + return watch_count > 0; + } - bool emit_table_thread() const { - return (table_count > 0 && vectorize == true); - } + bool emit_table_thread() const { + return (table_count > 0 && vectorize == true); + } - bool function_uses_table(std::string& name) const; - }; -}; // namespace codegen + bool function_uses_table(std::string& name) const; +}; -#endif +} // namespace codegen diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 7ccee46afd..2b64e65815 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -1,137 +1,143 @@ -#ifndef CODEGEN_NAMING_HPP -#define CODEGEN_NAMING_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <string> namespace codegen { - namespace naming { +namespace naming { - /// nmodl language version - /// @todo : should be moved from codegen to global scope - const std::string NMODL_VERSION("6.2.0"); +/// nmodl language version +/// @todo : should be moved from codegen to global scope +const std::string NMODL_VERSION("6.2.0"); - /// derivimplicit method in nmodl - const std::string DERIVIMPLICIT_METHOD("derivimplicit"); +/// derivimplicit method in nmodl +const std::string DERIVIMPLICIT_METHOD("derivimplicit"); - /// euler method in nmodl - const std::string EULER_METHOD("euler"); +/// euler method in nmodl +const std::string EULER_METHOD("euler"); - /// cnexp method in nmodl - const std::string CNEXP_METHOD("cnexp"); +/// cnexp method in nmodl +const std::string CNEXP_METHOD("cnexp"); - /// net_event function call in nmodl - const std::string NET_EVENT_METHOD("net_event"); +/// net_event function call in nmodl +const std::string NET_EVENT_METHOD("net_event"); - /// net_move function call in nmodl - const std::string NET_MOVE_METHOD("net_move"); +/// net_move function call in nmodl +const std::string NET_MOVE_METHOD("net_move"); - /// net_send function call in nmodl - const std::string NET_SEND_METHOD("net_send"); +/// net_send function call in nmodl +const std::string NET_SEND_METHOD("net_send"); - /// artificial cell keyword in nmodl - const std::string ARTIFICIAL_CELL("ARTIFICIAL_CELL"); +/// artificial cell keyword in nmodl +const std::string ARTIFICIAL_CELL("ARTIFICIAL_CELL"); - /// point process keyword in nmodl - const std::string POINT_PROCESS("POINT_PROCESS"); +/// point process keyword in nmodl +const std::string POINT_PROCESS("POINT_PROCESS"); - /// inbuilt neuron variable for diameter of the compartment - const std::string DIAM_VARIABLE("diam"); +/// inbuilt neuron variable for diameter of the compartment +const std::string DIAM_VARIABLE("diam"); - /// inbuilt neuron variable for area of the compartment - const std::string NODE_AREA_VARIABLE("node_area"); +/// inbuilt neuron variable for area of the compartment +const std::string NODE_AREA_VARIABLE("node_area"); - /// similar to node_area but user can explicitly declare it as area - const std::string AREA_VARIABLE("area"); +/// similar to node_area but user can explicitly declare it as area +const std::string AREA_VARIABLE("area"); - /// inbuilt neuron variable for point process - const std::string POINT_PROCESS_VARIABLE("point_process"); +/// inbuilt neuron variable for point process +const std::string POINT_PROCESS_VARIABLE("point_process"); - /// inbuilt neuron variable for tqitem process - const std::string TQITEM_VARIABLE("tqitem"); +/// inbuilt neuron variable for tqitem process +const std::string TQITEM_VARIABLE("tqitem"); - /// range variable for conductance - const std::string CONDUCTANCE_VARIABLE("_g"); +/// range variable for conductance +const std::string CONDUCTANCE_VARIABLE("_g"); - /// global variable to indicate if table is used - const std::string USE_TABLE_VARIABLE("usetable"); +/// global variable to indicate if table is used +const std::string USE_TABLE_VARIABLE("usetable"); - /// range variable when conductance is not used (for vectorized model) - const std::string CONDUCTANCE_UNUSED_VARIABLE("g_unused"); +/// range variable when conductance is not used (for vectorized model) +const std::string CONDUCTANCE_UNUSED_VARIABLE("g_unused"); - /// range variable for voltage when unused (for vectorized model) - const std::string VOLTAGE_UNUSED_VARIABLE("v_unused"); +/// range variable for voltage when unused (for vectorized model) +const std::string VOLTAGE_UNUSED_VARIABLE("v_unused"); - /// variable t indicating last execution time of net receive block - const std::string T_SAVE_VARIABLE("tsave"); +/// variable t indicating last execution time of net receive block +const std::string T_SAVE_VARIABLE("tsave"); - /// shadow rhs variable in neuron thread structure - const std::string NTHREAD_RHS_SHADOW("_shadow_rhs"); +/// shadow rhs variable in neuron thread structure +const std::string NTHREAD_RHS_SHADOW("_shadow_rhs"); - /// shadow d variable in neuron thread structure - const std::string NTHREAD_D_SHADOW("_shadow_d"); +/// shadow d variable in neuron thread structure +const std::string NTHREAD_D_SHADOW("_shadow_d"); - /// t variable in neuron thread structure - const std::string NTHREAD_T_VARIABLE("t"); +/// t variable in neuron thread structure +const std::string NTHREAD_T_VARIABLE("t"); - /// dt variable in neuron thread structure - const std::string NTHREAD_DT_VARIABLE("dt"); +/// dt variable in neuron thread structure +const std::string NTHREAD_DT_VARIABLE("dt"); - /// default float variable type - const std::string DEFAULT_FLOAT_TYPE("double"); +/// default float variable type +const std::string DEFAULT_FLOAT_TYPE("double"); - /// default local variable type - const std::string DEFAULT_LOCAL_VAR_TYPE("double"); +/// default local variable type +const std::string DEFAULT_LOCAL_VAR_TYPE("double"); - /// default integer variable type - const std::string DEFAULT_INTEGER_TYPE("int"); +/// default integer variable type +const std::string DEFAULT_INTEGER_TYPE("int"); - /// semantic type for area variable - const std::string AREA_SEMANTIC("area"); +/// semantic type for area variable +const std::string AREA_SEMANTIC("area"); - /// semantic type for point process variable - const std::string POINT_PROCESS_SEMANTIC("pntproc"); +/// semantic type for point process variable +const std::string POINT_PROCESS_SEMANTIC("pntproc"); - /// semantic type for pointer variable - const std::string POINTER_SEMANTIC("pointer"); +/// semantic type for pointer variable +const std::string POINTER_SEMANTIC("pointer"); - /// semantic type for core pointer variable - const std::string CORE_POINTER_SEMANTIC("bbcorepointer"); +/// semantic type for core pointer variable +const std::string CORE_POINTER_SEMANTIC("bbcorepointer"); - /// semantic type for net send call - const std::string NET_SEND_SEMANTIC("netsend"); +/// semantic type for net send call +const std::string NET_SEND_SEMANTIC("netsend"); - /// semantic type for watch statement - const std::string WATCH_SEMANTIC("watch"); +/// semantic type for watch statement +const std::string WATCH_SEMANTIC("watch"); - /// semantic type for for_netcon statement - const std::string FOR_NETCON_SEMANTIC("fornetcon"); +/// semantic type for for_netcon statement +const std::string FOR_NETCON_SEMANTIC("fornetcon"); - /// nrn_init method in generated code - const std::string NRN_INIT_METHOD("nrn_init"); +/// nrn_init method in generated code +const std::string NRN_INIT_METHOD("nrn_init"); - /// nrn_alloc method in generated code - const std::string NRN_ALLOC_METHOD("nrn_alloc"); +/// nrn_alloc method in generated code +const std::string NRN_ALLOC_METHOD("nrn_alloc"); - /// nrn_state method in generated code - const std::string NRN_STATE_METHOD("nrn_state"); +/// nrn_state method in generated code +const std::string NRN_STATE_METHOD("nrn_state"); - /// nrn_cur method in generated code - const std::string NRN_CUR_METHOD("nrn_cur"); +/// nrn_cur method in generated code +const std::string NRN_CUR_METHOD("nrn_cur"); - /// nrn_watch_check method in generated c file - const std::string NRN_WATCH_CHECK_METHOD("nrn_watch_check"); +/// nrn_watch_check method in generated c file +const std::string NRN_WATCH_CHECK_METHOD("nrn_watch_check"); - /// verbatim name of the variable for nrn thread arguments - const std::string THREAD_ARGS("_threadargs_"); +/// verbatim name of the variable for nrn thread arguments +const std::string THREAD_ARGS("_threadargs_"); - /// verbatim name of the variable for nrn thread arguments in prototype - const std::string THREAD_ARGS_PROTO("_threadargsproto_"); +/// verbatim name of the variable for nrn thread arguments in prototype +const std::string THREAD_ARGS_PROTO("_threadargsproto_"); - /// commonly used variables in verbatim block and how they - /// should be mapped to new code generation backends - // clang-format off +/// commonly used variables in verbatim block and how they +/// should be mapped to new code generation backends +// clang-format off const std::map<std::string, std::string> VERBATIM_VARIABLES_MAPPING{ {"_nt", "nt"}, {"_p", "data"}, @@ -141,10 +147,7 @@ namespace codegen { {"_cntml_padded", "pnodecount"}, {"_cntml", "nodecount"}, {"_tqitem", "tqitem"}}; - // clang-format on - - } // namespace naming - -}; // namespace codegen +// clang-format on -#endif +} // namespace naming +} // namespace codegen diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp index fce6d15bab..9032cd4db6 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "codegen/codegen_omp_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index b18de34a13..2dfd5c6226 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_CODEGEN_C_OMP_VISITOR_HPP -#define NMODL_CODEGEN_C_OMP_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "codegen/codegen_c_visitor.hpp" @@ -8,7 +14,7 @@ * \class CodegenOmpVisitor * \brief Visitor for printing c code with OpenMP backend */ -class CodegenOmpVisitor : public CodegenCVisitor { +class CodegenOmpVisitor: public CodegenCVisitor { protected: /// name of the code generation backend std::string backend_name() override; @@ -55,16 +61,11 @@ class CodegenOmpVisitor : public CodegenCVisitor { std::string output_dir, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type) { - } + : CodegenCVisitor(mod_file, output_dir, layout, float_type) {} CodegenOmpVisitor(std::string mod_file, std::stringstream& stream, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) { - } + : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; - - -#endif diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 9934ae5cf5..dd292e7441 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -9,25 +9,22 @@ file(GLOB TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" file(GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py") if(ClangFormat_FOUND AND ClangFormat_VERSION_MAJOR GREATER_EQUAL 4) - set(CODE_GENERATOR_OPTS --clang-format=${ClangFormat_EXECUTABLE}) - if(nmodl_ClangFormat_OPTIONS) - set(CODE_GENERATOR_OPTS - ${CODE_GENERATOR_OPTS} - --clang-format-opts - ${nmodl_ClangFormat_OPTIONS}) - endif(nmodl_ClangFormat_OPTIONS) + set(CODE_GENERATOR_OPTS --clang-format=${ClangFormat_EXECUTABLE}) + if(nmodl_ClangFormat_OPTIONS) + set(CODE_GENERATOR_OPTS ${CODE_GENERATOR_OPTS} --clang-format-opts ${nmodl_ClangFormat_OPTIONS}) + endif(nmodl_ClangFormat_OPTIONS) endif() -add_custom_command( - OUTPUT ${AUTO_GENERATED_FILES} - COMMAND ${PYTHON_EXECUTABLE} - ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} + COMMAND ${PYTHON_EXECUTABLE} + ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} + --base-dir ${PROJECT_BINARY_DIR}/src + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") unset(CODE_GENERATOR_OPTS) # ============================================================================= diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index c34d539d68..957940626a 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + class Argument: """Utility class for holding all arguments for node classes""" diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index ad94612279..61c6340394 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import argparse import filecmp import logging diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 20361e5bf9..c200d15dfe 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1,3 +1,10 @@ +# ********************************************************************* +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# ********************************************************************* + ######################### NMODL Abstract Language Definition ############################## # # PURPOSE diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index bbf450f2f8..a089a7c0de 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + """Define node and data types used in parser and ast generator While generating ast and visitors we need to lookup for base data types, diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 016e81e026..b42d437b01 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + """Define basic classes used from parser Node is used to represent a node parsed from every rule diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 528b50db5a..12d51b726f 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + """Parser for parsing abstract NMODL language definition file Abstract definition file of NMODL has custom syntax for defining language diff --git a/src/nmodl/language/templates/ast.cpp b/src/nmodl/language/templates/ast.cpp index 5fd454c83e..6c69a3da6b 100644 --- a/src/nmodl/language/templates/ast.cpp +++ b/src/nmodl/language/templates/ast.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index b4e1546428..b60d2ec6e2 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <iostream> diff --git a/src/nmodl/language/templates/ast_decl.hpp b/src/nmodl/language/templates/ast_decl.hpp index 116a7a872b..ce770e4164 100644 --- a/src/nmodl/language/templates/ast_decl.hpp +++ b/src/nmodl/language/templates/ast_decl.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <memory> diff --git a/src/nmodl/language/templates/ast_visitor.cpp b/src/nmodl/language/templates/ast_visitor.cpp index 2ce3046175..94252e7d59 100644 --- a/src/nmodl/language/templates/ast_visitor.cpp +++ b/src/nmodl/language/templates/ast_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/ast_visitor.hpp" using namespace ast; diff --git a/src/nmodl/language/templates/ast_visitor.hpp b/src/nmodl/language/templates/ast_visitor.hpp index 020c51e040..e8bc460471 100644 --- a/src/nmodl/language/templates/ast_visitor.hpp +++ b/src/nmodl/language/templates/ast_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "ast/ast.hpp" diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/json_visitor.cpp index ab2cd6c40a..f084f9db81 100644 --- a/src/nmodl/language/templates/json_visitor.cpp +++ b/src/nmodl/language/templates/json_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/json_visitor.hpp" using namespace ast; diff --git a/src/nmodl/language/templates/json_visitor.hpp b/src/nmodl/language/templates/json_visitor.hpp index 4df1b73f2e..5eb5509d2c 100644 --- a/src/nmodl/language/templates/json_visitor.hpp +++ b/src/nmodl/language/templates/json_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/lookup_visitor.cpp index 34c743fa0a..064955af2f 100644 --- a/src/nmodl/language/templates/lookup_visitor.cpp +++ b/src/nmodl/language/templates/lookup_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <algorithm> #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index 8b27b66ef3..b43ebb6b34 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "ast/ast.hpp" diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index 73edcc5fbb..c9498569f3 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/nmodl_visitor.hpp" #include "visitors/nmodl_visitor_helper.hpp" diff --git a/src/nmodl/language/templates/nmodl_visitor.hpp b/src/nmodl/language/templates/nmodl_visitor.hpp index 6dd32b454f..06f96d7bd0 100644 --- a/src/nmodl/language/templates/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/nmodl_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "ast/ast.hpp" diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pyast.cpp index ebb99a728f..7ee86ce385 100644 --- a/src/nmodl/language/templates/pyast.cpp +++ b/src/nmodl/language/templates/pyast.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "pybind/pyast.hpp" #include "visitors/json_visitor.hpp" #include <pybind11/pybind11.h> diff --git a/src/nmodl/language/templates/pyast.hpp b/src/nmodl/language/templates/pyast.hpp index 298df1179e..abd17206e4 100644 --- a/src/nmodl/language/templates/pyast.hpp +++ b/src/nmodl/language/templates/pyast.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once diff --git a/src/nmodl/language/templates/pysymtab.cpp b/src/nmodl/language/templates/pysymtab.cpp index 07cd0a6779..bb8462195f 100644 --- a/src/nmodl/language/templates/pysymtab.cpp +++ b/src/nmodl/language/templates/pysymtab.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <pybind11/pybind11.h> #include <pybind11/iostream.h> #include <pybind11/stl.h> diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 8a0ed39483..8920d25c1a 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <memory> #include <pybind11/iostream.h> #include <pybind11/pybind11.h> diff --git a/src/nmodl/language/templates/pyvisitor.hpp b/src/nmodl/language/templates/pyvisitor.hpp index 158beca9a8..eaa9048ce6 100644 --- a/src/nmodl/language/templates/pyvisitor.hpp +++ b/src/nmodl/language/templates/pyvisitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "ast/ast.hpp" diff --git a/src/nmodl/language/templates/symtab_visitor.cpp b/src/nmodl/language/templates/symtab_visitor.cpp index 24befc9f47..6c247ca06f 100644 --- a/src/nmodl/language/templates/symtab_visitor.cpp +++ b/src/nmodl/language/templates/symtab_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "symtab/symbol_table.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/symtab_visitor_helper.hpp" diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp index 2331a5833a..e6aa122257 100644 --- a/src/nmodl/language/templates/symtab_visitor.hpp +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "visitors/json_visitor.hpp" diff --git a/src/nmodl/language/templates/visitor.hpp b/src/nmodl/language/templates/visitor.hpp index e45e7ac8fb..4cbc75c5c2 100644 --- a/src/nmodl/language/templates/visitor.hpp +++ b/src/nmodl/language/templates/visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index 43ba0e5e84..b6b7a55694 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import re # convert string of the form "AabcDef" to "Abc_Def" diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 1e692a15e1..c3b5cfa5c2 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -1,7 +1,7 @@ -#============================================================================= +# ============================================================================= # Various project components and their source files -#============================================================================= -set (BISON_GENERATED_SOURCE_FILES +# ============================================================================= +set(BISON_GENERATED_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp @@ -11,33 +11,22 @@ set (BISON_GENERATED_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp) -set(AST_SOURCE_FILES - ${PROJECT_BINARY_DIR}/src/ast/ast.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast.cpp -) +set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) -set(NMODL_DRIVER_FILES - ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp - ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp -) +set(NMODL_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp + ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) set(DIFFEQ_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp ${PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp -) + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) -set(C_DRIVER_FILES - ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp - ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp -) +set(C_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp + ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) -set_source_files_properties( - ${AST_SOURCE_FILES} - PROPERTIES GENERATED TRUE -) +set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modl.h @@ -57,148 +46,118 @@ set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp ${NMODL_DRIVER_FILES} ${DIFFEQ_DRIVER_FILES} - ${C_DRIVER_FILES} -) + ${C_DRIVER_FILES}) -#============================================================================= +# ============================================================================= # Directories for parsers (as they need to be in separate directories) -#============================================================================= -file(MAKE_DIRECTORY - ${PROJECT_BINARY_DIR}/src/parser/nmodl - ${PROJECT_BINARY_DIR}/src/parser/diffeq - ${PROJECT_BINARY_DIR}/src/parser/c) +# ============================================================================= +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR}/src/parser/diffeq + ${PROJECT_BINARY_DIR}/src/parser/c) -#============================================================================= +# ============================================================================= # Lexer & Parser commands -#============================================================================= +# ============================================================================= # command to generate nmodl parser -add_custom_command ( - OUTPUT - ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS - ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - pyastgen - COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --" -) +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen + COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") # command to generate verbatim parser -add_custom_command ( - OUTPUT - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --" -) +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") # command to generate differential equation parser -add_custom_command ( - OUTPUT - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS - ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --" -) +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") # command to generate C (11) parser -add_custom_command ( - OUTPUT - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/location.hh - ${PROJECT_BINARY_DIR}/src/parser/c/position.hh - ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/c11.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy - COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --" -) +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/c/location.hh + ${PROJECT_BINARY_DIR}/src/parser/c/position.hh + ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/c11.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy + COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") # command to generate nmodl lexer -add_custom_command( - OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} - ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp - COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --" -) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") # command to generate verbatim lexer -add_custom_command( - OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} - ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --" -) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") # command to generate differential equation lexer -add_custom_command( - OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} - ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --" -) +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") # command to generate C (11) lexer -add_custom_command( - OUTPUT - ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} - ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --" -) - -#============================================================================= +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") + +# ============================================================================= # Libraries & executables -#============================================================================= +# ============================================================================= add_library(lexer_obj - OBJECT - ${LEXER_SOURCE_FILES} - ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES}) + OBJECT + ${LEXER_SOURCE_FILES} + ${BISON_GENERATED_SOURCE_FILES} + ${AST_SOURCE_FILES}) set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) -add_library(lexer - STATIC - $<TARGET_OBJECTS:lexer_obj>) - - +add_library(lexer STATIC $<TARGET_OBJECTS:lexer_obj>) add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) @@ -206,9 +165,9 @@ add_executable(c_lexer main_c.cpp) target_link_libraries(nmodl_lexer lexer) target_link_libraries(c_lexer lexer) -#============================================================================= +# ============================================================================= # Install executable -#============================================================================= +# ============================================================================= install(TARGETS nmodl_lexer DESTINATION bin/lexer) install(TARGETS c_lexer DESTINATION bin/lexer) add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index 8cf6b8057d..c717e8d65e 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -1,3 +1,14 @@ +/********************************************************************************** + * + * @brief Flex lexer implementation for C (11) + * + * NMODL has verbatim constructs that allow to specify C code sections within + * nmodl implementation. + * + * CREDIT : This is based on flex specification available at + * http://www.quut.com/c/ANSI-C-grammar-l-2011.html + *****************************************************************************/ + %{ #include <iostream> diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 479cdf724e..74dd60ab3a 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -1,5 +1,11 @@ -#ifndef C11_LEXER_HPP -#define C11_LEXER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "parser/c/c11_parser.hpp" @@ -20,45 +26,45 @@ namespace c11 { - /** - * \class Lexer - * \brief Represent Lexer/Scanner class for C (11) language parsing - * - * Lexer defined to add some extra function to the scanner class from flex. - * Flex itself creates yyFlexLexer class, which we renamed using macros to - * C11FlexLexer. But we change the context of the generated yylex() function - * because the yylex() defined in C11FlexLexer has no parameters. Also, note - * that implementation of the member functions are in c11.ll file due to use - * of macros. */ - class Lexer : public C11FlexLexer { - public: - /** Reference to driver object which contains this lexer instance. This is - * used for error reporting and checking macro definitions. */ - Driver& driver; - - /// For tracking location of the tokens - location loc; +/** + * \class Lexer + * \brief Represent Lexer/Scanner class for C (11) language parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * Flex itself creates yyFlexLexer class, which we renamed using macros to + * C11FlexLexer. But we change the context of the generated yylex() function + * because the yylex() defined in C11FlexLexer has no parameters. Also, note + * that implementation of the member functions are in c11.ll file due to use + * of macros. */ +class Lexer: public C11FlexLexer { + public: + /** Reference to driver object which contains this lexer instance. This is + * used for error reporting and checking macro definitions. */ + Driver& driver; - /** The streams in and out default to cin and cout, but that assignment - * is only made when initializing in yylex(). */ - explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) - : C11FlexLexer(in, out), driver(drv) {} + /// For tracking location of the tokens + location loc; - ~Lexer() override= default;; + /** The streams in and out default to cin and cout, but that assignment + * is only made when initializing in yylex(). */ + explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + : C11FlexLexer(in, out) + , driver(drv) {} - /** Main lexing function generated by flex according to the macro declaration - * YY_DECL above. The generated bison parser then calls this virtual function - * to fetch new tokens. Note that yylex() has different declaration and hence - * it can't be used for new lexer. */ - virtual Parser::symbol_type next_token(); + ~Lexer() override = default; + ; - /** Return type of the word : could be typedef, identifier or enum constant */ - Parser::symbol_type check_type(); + /** Main lexing function generated by flex according to the macro declaration + * YY_DECL above. The generated bison parser then calls this virtual function + * to fetch new tokens. Note that yylex() has different declaration and hence + * it can't be used for new lexer. */ + virtual Parser::symbol_type next_token(); - /// consume comment - std::string input_comment(); - }; + /** Return type of the word : could be typedef, identifier or enum constant */ + Parser::symbol_type check_type(); -} // namespace c11 + /// consume comment + std::string input_comment(); +}; -#endif +} // namespace c11 diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index 4cfda9ed37..a46d87fb1b 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -1,3 +1,11 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2019 Michael Hines + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + %{ #include <cstdio> #include <cstdlib> diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp index c1e7209a3b..c32022c8b0 100644 --- a/src/nmodl/lexer/diffeq_lexer.hpp +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -1,5 +1,11 @@ -#ifndef DIFFEQ_LEXER_HPP -#define DIFFEQ_LEXER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "parser/diffeq/diffeq_parser.hpp" @@ -20,34 +26,32 @@ namespace diffeq { - /** - * \class Lexer - * \brief Represent Lexer/Scanner class for differential equation parsing - * - * Lexer defined to add some extra function to the scanner class from flex. - * At the moment we are using basic functionality but it could be easily - * extended for further development. - */ - class Lexer : public DiffEqFlexLexer { - public: - /// for tracking location of the tokens - location loc; - - /** The streams in and out default to cin and cout, but that assignment - * is only made when initializing in yylex(). */ - Lexer(std::istream* in = nullptr, std::ostream* out = nullptr) - : DiffEqFlexLexer(in, out) {} - - ~Lexer() override = default;; - - /** Main lexing function generated by flex according to the macro declaration - * YY_DECL above. The generated bison parser then calls this virtual function - * to fetch new tokens. Note that yylex() has different declaration and hence - * it can't be used for new lexer. */ - virtual Parser::symbol_type next_token(); - - }; - -} // namespace diffeq - -#endif +/** + * \class Lexer + * \brief Represent Lexer/Scanner class for differential equation parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * At the moment we are using basic functionality but it could be easily + * extended for further development. + */ +class Lexer: public DiffEqFlexLexer { + public: + /// for tracking location of the tokens + location loc; + + /** The streams in and out default to cin and cout, but that assignment + * is only made when initializing in yylex(). */ + Lexer(std::istream* in = nullptr, std::ostream* out = nullptr) + : DiffEqFlexLexer(in, out) {} + + ~Lexer() override = default; + ; + + /** Main lexing function generated by flex according to the macro declaration + * YY_DECL above. The generated bison parser then calls this virtual function + * to fetch new tokens. Note that yylex() has different declaration and hence + * it can't be used for new lexer. */ + virtual Parser::symbol_type next_token(); +}; + +} // namespace diffeq diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index f68478a075..1945700aa9 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <iostream> diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 50cac5c0e4..62c9e854fc 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <iostream> @@ -57,65 +64,65 @@ int main(int argc, const char* argv[]) { * retrieve token object from each instance and print it. * Note that value is of ast type i.e. ast::Name* etc. */ switch (token) { - /// token with name ast class - case Token::NAME: - case Token::METHOD: - case Token::SUFFIX: - case Token::VALENCE: - case Token::DEL: - case Token::DEL2: { - auto value = sym.value.as<ast::Name*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } - - /// token with prime ast class - case Token::PRIME: { - auto value = sym.value.as<ast::PrimeName*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } - - /// token with integer ast class - case Token::INTEGER: { - auto value = sym.value.as<ast::Integer*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } - - /// token with double/float ast class - case Token::REAL: { - auto value = sym.value.as<ast::Double*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } - - /// token with string ast class - case Token::STRING: { - auto value = sym.value.as<ast::String*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } - - /// token with string data type - case Token::VERBATIM: - case Token::BLOCK_COMMENT: - case Token::LINE_PART: { - auto str = sym.value.as<std::string>(); - std::cout << str << std::endl; - break; - } - - /// all remaining tokens has ModToken* as a vaue - default: { - auto token = sym.value.as<ModToken>(); - std::cout << token << std::endl; - } + /// token with name ast class + case Token::NAME: + case Token::METHOD: + case Token::SUFFIX: + case Token::VALENCE: + case Token::DEL: + case Token::DEL2: { + auto value = sym.value.as<ast::Name*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } + + /// token with prime ast class + case Token::PRIME: { + auto value = sym.value.as<ast::PrimeName*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } + + /// token with integer ast class + case Token::INTEGER: { + auto value = sym.value.as<ast::Integer*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } + + /// token with double/float ast class + case Token::REAL: { + auto value = sym.value.as<ast::Double*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } + + /// token with string ast class + case Token::STRING: { + auto value = sym.value.as<ast::String*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } + + /// token with string data type + case Token::VERBATIM: + case Token::BLOCK_COMMENT: + case Token::LINE_PART: { + auto str = sym.value.as<std::string>(); + std::cout << str << std::endl; + break; + } + + /// all remaining tokens has ModToken* as a vaue + default: { + auto token = sym.value.as<ModToken>(); + std::cout << token << std::endl; + } } } } catch (TCLAP::ArgException& e) { diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index 138da57e62..cecaf8b73e 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -1,3 +1,12 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +// @todo : This file has been added for legacy purpose and can be removed. + #pragma once /** diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 08848eda6e..e2a2615b37 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "lexer/modtoken.hpp" /** Return position of the token as string. This is used used by other diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 0ded98cbd4..107a89b606 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -1,5 +1,11 @@ -#ifndef _LEXER_MODTOKEN_HPP_ -#define _LEXER_MODTOKEN_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <iomanip> #include <iostream> @@ -41,13 +47,17 @@ class ModToken { bool external = false; public: - ModToken() : pos(nullptr, 0){}; + ModToken() + : pos(nullptr, 0){}; - explicit ModToken(bool ext) : pos(nullptr, 0), external(ext) { - } + explicit ModToken(bool ext) + : pos(nullptr, 0) + , external(ext) {} - ModToken(std::string str, int tok, nmodl::location& loc) : name(str), token(tok), pos(loc) { - } + ModToken(std::string str, int tok, nmodl::location& loc) + : name(str) + , token(tok) + , pos(loc) {} ModToken* clone() const { return new ModToken(*this); @@ -73,5 +83,3 @@ class ModToken { friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt); }; - -#endif diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 9b8726faac..f668d5b894 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -1,3 +1,11 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2019 Michael Hines + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + %{ #include <iostream> diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 7b1365a10f..99b8b57949 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_LEXER_HPP -#define NMODL_LEXER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "ast/ast.hpp" #include "parser/nmodl/nmodl_parser.hpp" @@ -21,67 +27,67 @@ namespace nmodl { - /** - * \class Lexer - * \brief Represent Lexer/Scanner class for NMODL language parsing - * - * Lexer defined to add some extra function to the scanner class from flex. - * Flex itself creates yyFlexLexer class, which we renamed using macros to - * NmodlFlexLexer. But we change the context of the generated yylex() function - * because the yylex() defined in NmodlFlexLexer has no parameters. Also, note - * that implementation of the member functions are in nmodl.l file due to use - * of macros. */ - class Lexer : public NmodlFlexLexer { - public: - /** Reference to driver object which contains this lexer instance. This is - * used for error reporting and checking macro definitions. */ - Driver& driver; - - /// For tracking location of the tokens - location loc; - - /// Units are stored in the scanner (could be stored in driver) - ast::String* last_unit = nullptr; - - /** For reaction ('~') we return different token based on the lexical - * context, store the current context. Note that this was returned from - * parser in original implementation. */ - int lexcontext = 0; - - /** The streams in and out default to cin and cout, but that assignment - * is only made when initializing in yylex(). */ - explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) - : NmodlFlexLexer(in, out), driver(drv) {} - - ~Lexer() override= default;; - - /** Main lexing function generated by flex according to the macro declaration - * YY_DECL above. The generated bison parser then calls this virtual function - * to fetch new tokens. Note that yylex() has different declaration and hence - * it can't be used for new lexer. */ - virtual Parser::symbol_type next_token(); - - /// Enable debug output (via yyout) if compiled into the scanner. - void set_debug(bool b); - - /** For units we have to consume string until end of closing parenthesis - * and store it in the scanner object. */ - void scan_unit(); - - /** Return unit object created by scan_unit(). Initialize to nullptr to avoid - * using empty units or units from last parsing. */ - ast::String* get_unit(); - - /// For TITLE we have to input string until end of line. - std::string inputline(); - - /** Due to copy more the end position is not accurate. Set column 0 to avoid - * confusion (see NOCMODL-25). */ - void reset_end_position() { - loc.end.column = 0; - } - }; - -} // namespace nmodl - -#endif +/** + * \class Lexer + * \brief Represent Lexer/Scanner class for NMODL language parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * Flex itself creates yyFlexLexer class, which we renamed using macros to + * NmodlFlexLexer. But we change the context of the generated yylex() function + * because the yylex() defined in NmodlFlexLexer has no parameters. Also, note + * that implementation of the member functions are in nmodl.l file due to use + * of macros. */ +class Lexer: public NmodlFlexLexer { + public: + /** Reference to driver object which contains this lexer instance. This is + * used for error reporting and checking macro definitions. */ + Driver& driver; + + /// For tracking location of the tokens + location loc; + + /// Units are stored in the scanner (could be stored in driver) + ast::String* last_unit = nullptr; + + /** For reaction ('~') we return different token based on the lexical + * context, store the current context. Note that this was returned from + * parser in original implementation. */ + int lexcontext = 0; + + /** The streams in and out default to cin and cout, but that assignment + * is only made when initializing in yylex(). */ + explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + : NmodlFlexLexer(in, out) + , driver(drv) {} + + ~Lexer() override = default; + ; + + /** Main lexing function generated by flex according to the macro declaration + * YY_DECL above. The generated bison parser then calls this virtual function + * to fetch new tokens. Note that yylex() has different declaration and hence + * it can't be used for new lexer. */ + virtual Parser::symbol_type next_token(); + + /// Enable debug output (via yyout) if compiled into the scanner. + void set_debug(bool b); + + /** For units we have to consume string until end of closing parenthesis + * and store it in the scanner object. */ + void scan_unit(); + + /** Return unit object created by scan_unit(). Initialize to nullptr to avoid + * using empty units or units from last parsing. */ + ast::String* get_unit(); + + /// For TITLE we have to input string until end of line. + std::string inputline(); + + /** Due to copy more the end position is not accurate. Set column 0 to avoid + * confusion (see NOCMODL-25). */ + void reset_end_position() { + loc.end.column = 0; + } +}; + +} // namespace nmodl diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 26d8130864..a99e607281 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <cstring> #include <iostream> @@ -8,314 +15,314 @@ #include "utils/string_utils.hpp" namespace nmodl { - /// create symbol for double/real ast class - SymbolType double_symbol(double value, PositionType& pos) { - ModToken token(std::to_string(value), Token::REAL, pos); - ast::Double floatvalue(value); - floatvalue.set_token(token); - return Parser::make_REAL(floatvalue, pos); - } +/// create symbol for double/real ast class +SymbolType double_symbol(double value, PositionType& pos) { + ModToken token(std::to_string(value), Token::REAL, pos); + ast::Double floatvalue(value); + floatvalue.set_token(token); + return Parser::make_REAL(floatvalue, pos); +} - /** Create symbol for integer ast class. Integer class also represent - * macro definition and hence could have associated text. */ - SymbolType integer_symbol(int value, PositionType& pos, const char* text) { - ast::Name* macro = nullptr; - ModToken token(std::to_string(value), Token::INTEGER, pos); +/** Create symbol for integer ast class. Integer class also represent + * macro definition and hence could have associated text. */ +SymbolType integer_symbol(int value, PositionType& pos, const char* text) { + ast::Name* macro = nullptr; + ModToken token(std::to_string(value), Token::INTEGER, pos); - if (text != nullptr) { - macro = new ast::Name(new ast::String(text)); - macro->set_token(token); - } - - ast::Integer intvalue(value, macro); - intvalue.set_token(token); - return Parser::make_INTEGER(intvalue, pos); + if (text != nullptr) { + macro = new ast::Name(new ast::String(text)); + macro->set_token(token); } - /** Create symbol for name ast class. - * - * \todo In addition to keywords and methods, there are also external - * definitions for math and neuron specific functions/variables. In the - * token we should mark those as external. */ - SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type) { - ModToken token(text, type, pos); - ast::Name value(new ast::String(text)); - value.set_token(token); - return Parser::make_NAME(value, pos); - } + ast::Integer intvalue(value, macro); + intvalue.set_token(token); + return Parser::make_INTEGER(intvalue, pos); +} - /** Create symbol for prime ast class. Prime has name as well as - * order. Text returned by lexer has single quote (') as an order. - * We count order and remove quote from text */ - SymbolType prime_symbol(std::string text, PositionType& pos) { - ModToken token(text, Token::PRIME, pos); - auto order = std::count(text.begin(), text.end(), '\''); - stringutils::remove_character(text, '\''); +/** Create symbol for name ast class. + * + * \todo In addition to keywords and methods, there are also external + * definitions for math and neuron specific functions/variables. In the + * token we should mark those as external. */ +SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type) { + ModToken token(text, type, pos); + ast::Name value(new ast::String(text)); + value.set_token(token); + return Parser::make_NAME(value, pos); +} - auto prime_name = new ast::String(text); - auto prime_order = new ast::Integer(order, nullptr); - ast::PrimeName value(prime_name, prime_order); - value.set_token(token); - return Parser::make_PRIME(value, pos); - } +/** Create symbol for prime ast class. Prime has name as well as + * order. Text returned by lexer has single quote (') as an order. + * We count order and remove quote from text */ +SymbolType prime_symbol(std::string text, PositionType& pos) { + ModToken token(text, Token::PRIME, pos); + auto order = std::count(text.begin(), text.end(), '\''); + stringutils::remove_character(text, '\''); - /// create symbol for string ast class - SymbolType string_symbol(const std::string& text, PositionType& pos) { - ModToken token(text, Token::STRING, pos); - ast::String value(text); - value.set_token(token); - return Parser::make_STRING(value, pos); - } + auto prime_name = new ast::String(text); + auto prime_order = new ast::Integer(order, nullptr); + ast::PrimeName value(prime_name, prime_order); + value.set_token(token); + return Parser::make_PRIME(value, pos); +} + +/// create symbol for string ast class +SymbolType string_symbol(const std::string& text, PositionType& pos) { + ModToken token(text, Token::STRING, pos); + ast::String value(text); + value.set_token(token); + return Parser::make_STRING(value, pos); +} - /** Create symbol for generic / non-ast token. These tokens doesn't have - * specific value to pass to the parser. They are more part of grammar. - * Depending on the type of toke, we create appropriate symbol. From - * lexer we pass token type (which is optional). This is required for - * reaction parsing where we have "lexical context". Hence, if token - * type is passed then we don't check/search for the token type. */ +/** Create symbol for generic / non-ast token. These tokens doesn't have + * specific value to pass to the parser. They are more part of grammar. + * Depending on the type of toke, we create appropriate symbol. From + * lexer we pass token type (which is optional). This is required for + * reaction parsing where we have "lexical context". Hence, if token + * type is passed then we don't check/search for the token type. */ - SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType type) { - /// if token type is not passed, check if it is keyword or method - if (type == Token::UNKNOWN) { - if (is_keyword(key) || is_method(key)) { - type = token_type(key); - } +SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType type) { + /// if token type is not passed, check if it is keyword or method + if (type == Token::UNKNOWN) { + if (is_keyword(key) || is_method(key)) { + type = token_type(key); } + } - ModToken token(key, type, pos); + ModToken token(key, type, pos); - /// lookup for token type and create approrpiate symbol type - switch (static_cast<int>(type)) { - /// Most of the nmodl keywords - case Token::AFTER: - return Parser::make_AFTER(token, pos); - case Token::BBCOREPOINTER: - return Parser::make_BBCOREPOINTER(token, pos); - case Token::BEFORE: - return Parser::make_BEFORE(token, pos); - case Token::BREAKPOINT: - return Parser::make_BREAKPOINT(token, pos); - case Token::BY: - return Parser::make_BY(token, pos); - case Token::COMPARTMENT: - return Parser::make_COMPARTMENT(token, pos); - case Token::CONDUCTANCE: - return Parser::make_CONDUCTANCE(token, pos); - case Token::CONSERVE: - return Parser::make_CONSERVE(token, pos); - case Token::CONSTANT: - return Parser::make_CONSTANT(token, pos); - case Token::CONSTRUCTOR: - return Parser::make_CONSTRUCTOR(token, pos); - case Token::DEFINE1: - return Parser::make_DEFINE1(token, pos); - case Token::DEPEND: - return Parser::make_DEPEND(token, pos); - case Token::DEPENDENT: - return Parser::make_DEPENDENT(token, pos); - case Token::DERIVATIVE: - return Parser::make_DERIVATIVE(token, pos); - case Token::DESTRUCTOR: - return Parser::make_DESTRUCTOR(token, pos); - case Token::DISCRETE: - return Parser::make_DISCRETE(token, pos); - case Token::ELECTRODE_CURRENT: - return Parser::make_ELECTRODE_CURRENT(token, pos); - case Token::ELSE: - return Parser::make_ELSE(token, pos); - case Token::EQUATION: - return Parser::make_EQUATION(token, pos); - case Token::EXTERNAL: - return Parser::make_EXTERNAL(token, pos); - case Token::FIRST: - return Parser::make_FIRST(token, pos); - case Token::FOR_NETCONS: - return Parser::make_FOR_NETCONS(token, pos); - case Token::FORALL1: - return Parser::make_FORALL1(token, pos); - case Token::FROM: - return Parser::make_FROM(token, pos); - case Token::FUNCTION1: - return Parser::make_FUNCTION1(token, pos); - case Token::FUNCTION_TABLE: - return Parser::make_FUNCTION_TABLE(token, pos); - case Token::GETQ: - return Parser::make_GETQ(token, pos); - case Token::GLOBAL: - return Parser::make_GLOBAL(token, pos); - case Token::IF: - return Parser::make_IF(token, pos); - case Token::IFERROR: - return Parser::make_IFERROR(token, pos); - case Token::INCLUDE1: - return Parser::make_INCLUDE1(token, pos); - case Token::INDEPENDENT: - return Parser::make_INDEPENDENT(token, pos); - case Token::INITIAL1: - return Parser::make_INITIAL1(token, pos); - case Token::KINETIC: - return Parser::make_KINETIC(token, pos); - case Token::LAG: - return Parser::make_LAG(token, pos); - case Token::LAST: - return Parser::make_LAST(token, pos); - case Token::LINEAR: - return Parser::make_LINEAR(token, pos); - case Token::LOCAL: - return Parser::make_LOCAL(token, pos); - case Token::LONGDIFUS: - return Parser::make_LONGDIFUS(token, pos); - case Token::MATCH: - return Parser::make_MATCH(token, pos); - case Token::MODEL: - return Parser::make_MODEL(token, pos); - case Token::MODEL_LEVEL: - return Parser::make_MODEL_LEVEL(token, pos); - case Token::NETRECEIVE: - return Parser::make_NETRECEIVE(token, pos); - case Token::NEURON: - return Parser::make_NEURON(token, pos); - case Token::NONLINEAR: - return Parser::make_NONLINEAR(token, pos); - case Token::NONSPECIFIC: - return Parser::make_NONSPECIFIC(token, pos); - case Token::NRNMUTEXLOCK: - return Parser::make_NRNMUTEXLOCK(token, pos); - case Token::NRNMUTEXUNLOCK: - return Parser::make_NRNMUTEXUNLOCK(token, pos); - case Token::PARAMETER: - return Parser::make_PARAMETER(token, pos); - case Token::PARTIAL: - return Parser::make_PARTIAL(token, pos); - case Token::PLOT: - return Parser::make_PLOT(token, pos); - case Token::POINTER: - return Parser::make_POINTER(token, pos); - case Token::PROCEDURE: - return Parser::make_PROCEDURE(token, pos); - case Token::PROTECT: - return Parser::make_PROTECT(token, pos); - case Token::PUTQ: - return Parser::make_PUTQ(token, pos); - case Token::RANGE: - return Parser::make_RANGE(token, pos); - case Token::READ: - return Parser::make_READ(token, pos); - case Token::RESET: - return Parser::make_RESET(token, pos); - case Token::SECTION: - return Parser::make_SECTION(token, pos); - case Token::SENS: - return Parser::make_SENS(token, pos); - case Token::SOLVE: - return Parser::make_SOLVE(token, pos); - case Token::SOLVEFOR: - return Parser::make_SOLVEFOR(token, pos); - case Token::START1: - return Parser::make_START1(token, pos); - case Token::STATE: - return Parser::make_STATE(token, pos); - case Token::STEP: - return Parser::make_STEP(token, pos); - case Token::STEPPED: - return Parser::make_STEPPED(token, pos); - case Token::SWEEP: - return Parser::make_SWEEP(token, pos); - case Token::TABLE: - return Parser::make_TABLE(token, pos); - case Token::TERMINAL: - return Parser::make_TERMINAL(token, pos); - case Token::THREADSAFE: - return Parser::make_THREADSAFE(token, pos); - case Token::TO: - return Parser::make_TO(token, pos); - case Token::UNITS: - return Parser::make_UNITS(token, pos); - case Token::UNITSOFF: - return Parser::make_UNITSOFF(token, pos); - case Token::UNITSON: - return Parser::make_UNITSON(token, pos); - case Token::USEION: - return Parser::make_USEION(token, pos); - case Token::USING: - return Parser::make_USING(token, pos); - case Token::VS: - return Parser::make_VS(token, pos); - case Token::WATCH: - return Parser::make_WATCH(token, pos); - case Token::WHILE: - return Parser::make_WHILE(token, pos); - case Token::WITH: - return Parser::make_WITH(token, pos); - case Token::WRITE: - return Parser::make_WRITE(token, pos); - case Token::REACT1: - return Parser::make_REACT1(token, pos); - case Token::NONLIN1: - return Parser::make_NONLIN1(token, pos); - case Token::LIN1: - return Parser::make_LIN1(token, pos); - case Token::TILDE: - return Parser::make_TILDE(token, pos); - case Token::REACTION: - return Parser::make_REACTION(token, pos); - case Token::GT: - return Parser::make_GT(token, pos); - case Token::GE: - return Parser::make_GE(token, pos); - case Token::LT: - return Parser::make_LT(token, pos); - case Token::LE: - return Parser::make_LE(token, pos); - case Token::EQ: - return Parser::make_EQ(token, pos); - case Token::NE: - return Parser::make_NE(token, pos); - case Token::NOT: - return Parser::make_NOT(token, pos); - case Token::AND: - return Parser::make_AND(token, pos); - case Token::OR: - return Parser::make_OR(token, pos); - case Token::OPEN_BRACE: - return Parser::make_OPEN_BRACE(token, pos); - case Token::CLOSE_BRACE: - return Parser::make_CLOSE_BRACE(token, pos); - case Token::OPEN_PARENTHESIS: - return Parser::make_OPEN_PARENTHESIS(token, pos); - case Token::CLOSE_PARENTHESIS: - return Parser::make_CLOSE_PARENTHESIS(token, pos); - case Token::OPEN_BRACKET: - return Parser::make_OPEN_BRACKET(token, pos); - case Token::CLOSE_BRACKET: - return Parser::make_CLOSE_BRACKET(token, pos); - case Token::AT: - return Parser::make_AT(token, pos); - case Token::ADD: - return Parser::make_ADD(token, pos); - case Token::MINUS: - return Parser::make_MINUS(token, pos); - case Token::MULTIPLY: - return Parser::make_MULTIPLY(token, pos); - case Token::DIVIDE: - return Parser::make_DIVIDE(token, pos); - case Token::EQUAL: - return Parser::make_EQUAL(token, pos); - case Token::CARET: - return Parser::make_CARET(token, pos); - case Token::COLON: - return Parser::make_COLON(token, pos); - case Token::COMMA: - return Parser::make_COMMA(token, pos); - case Token::PERIOD: - return Parser::make_PERIOD(token, pos); + /// lookup for token type and create approrpiate symbol type + switch (static_cast<int>(type)) { + /// Most of the nmodl keywords + case Token::AFTER: + return Parser::make_AFTER(token, pos); + case Token::BBCOREPOINTER: + return Parser::make_BBCOREPOINTER(token, pos); + case Token::BEFORE: + return Parser::make_BEFORE(token, pos); + case Token::BREAKPOINT: + return Parser::make_BREAKPOINT(token, pos); + case Token::BY: + return Parser::make_BY(token, pos); + case Token::COMPARTMENT: + return Parser::make_COMPARTMENT(token, pos); + case Token::CONDUCTANCE: + return Parser::make_CONDUCTANCE(token, pos); + case Token::CONSERVE: + return Parser::make_CONSERVE(token, pos); + case Token::CONSTANT: + return Parser::make_CONSTANT(token, pos); + case Token::CONSTRUCTOR: + return Parser::make_CONSTRUCTOR(token, pos); + case Token::DEFINE1: + return Parser::make_DEFINE1(token, pos); + case Token::DEPEND: + return Parser::make_DEPEND(token, pos); + case Token::DEPENDENT: + return Parser::make_DEPENDENT(token, pos); + case Token::DERIVATIVE: + return Parser::make_DERIVATIVE(token, pos); + case Token::DESTRUCTOR: + return Parser::make_DESTRUCTOR(token, pos); + case Token::DISCRETE: + return Parser::make_DISCRETE(token, pos); + case Token::ELECTRODE_CURRENT: + return Parser::make_ELECTRODE_CURRENT(token, pos); + case Token::ELSE: + return Parser::make_ELSE(token, pos); + case Token::EQUATION: + return Parser::make_EQUATION(token, pos); + case Token::EXTERNAL: + return Parser::make_EXTERNAL(token, pos); + case Token::FIRST: + return Parser::make_FIRST(token, pos); + case Token::FOR_NETCONS: + return Parser::make_FOR_NETCONS(token, pos); + case Token::FORALL1: + return Parser::make_FORALL1(token, pos); + case Token::FROM: + return Parser::make_FROM(token, pos); + case Token::FUNCTION1: + return Parser::make_FUNCTION1(token, pos); + case Token::FUNCTION_TABLE: + return Parser::make_FUNCTION_TABLE(token, pos); + case Token::GETQ: + return Parser::make_GETQ(token, pos); + case Token::GLOBAL: + return Parser::make_GLOBAL(token, pos); + case Token::IF: + return Parser::make_IF(token, pos); + case Token::IFERROR: + return Parser::make_IFERROR(token, pos); + case Token::INCLUDE1: + return Parser::make_INCLUDE1(token, pos); + case Token::INDEPENDENT: + return Parser::make_INDEPENDENT(token, pos); + case Token::INITIAL1: + return Parser::make_INITIAL1(token, pos); + case Token::KINETIC: + return Parser::make_KINETIC(token, pos); + case Token::LAG: + return Parser::make_LAG(token, pos); + case Token::LAST: + return Parser::make_LAST(token, pos); + case Token::LINEAR: + return Parser::make_LINEAR(token, pos); + case Token::LOCAL: + return Parser::make_LOCAL(token, pos); + case Token::LONGDIFUS: + return Parser::make_LONGDIFUS(token, pos); + case Token::MATCH: + return Parser::make_MATCH(token, pos); + case Token::MODEL: + return Parser::make_MODEL(token, pos); + case Token::MODEL_LEVEL: + return Parser::make_MODEL_LEVEL(token, pos); + case Token::NETRECEIVE: + return Parser::make_NETRECEIVE(token, pos); + case Token::NEURON: + return Parser::make_NEURON(token, pos); + case Token::NONLINEAR: + return Parser::make_NONLINEAR(token, pos); + case Token::NONSPECIFIC: + return Parser::make_NONSPECIFIC(token, pos); + case Token::NRNMUTEXLOCK: + return Parser::make_NRNMUTEXLOCK(token, pos); + case Token::NRNMUTEXUNLOCK: + return Parser::make_NRNMUTEXUNLOCK(token, pos); + case Token::PARAMETER: + return Parser::make_PARAMETER(token, pos); + case Token::PARTIAL: + return Parser::make_PARTIAL(token, pos); + case Token::PLOT: + return Parser::make_PLOT(token, pos); + case Token::POINTER: + return Parser::make_POINTER(token, pos); + case Token::PROCEDURE: + return Parser::make_PROCEDURE(token, pos); + case Token::PROTECT: + return Parser::make_PROTECT(token, pos); + case Token::PUTQ: + return Parser::make_PUTQ(token, pos); + case Token::RANGE: + return Parser::make_RANGE(token, pos); + case Token::READ: + return Parser::make_READ(token, pos); + case Token::RESET: + return Parser::make_RESET(token, pos); + case Token::SECTION: + return Parser::make_SECTION(token, pos); + case Token::SENS: + return Parser::make_SENS(token, pos); + case Token::SOLVE: + return Parser::make_SOLVE(token, pos); + case Token::SOLVEFOR: + return Parser::make_SOLVEFOR(token, pos); + case Token::START1: + return Parser::make_START1(token, pos); + case Token::STATE: + return Parser::make_STATE(token, pos); + case Token::STEP: + return Parser::make_STEP(token, pos); + case Token::STEPPED: + return Parser::make_STEPPED(token, pos); + case Token::SWEEP: + return Parser::make_SWEEP(token, pos); + case Token::TABLE: + return Parser::make_TABLE(token, pos); + case Token::TERMINAL: + return Parser::make_TERMINAL(token, pos); + case Token::THREADSAFE: + return Parser::make_THREADSAFE(token, pos); + case Token::TO: + return Parser::make_TO(token, pos); + case Token::UNITS: + return Parser::make_UNITS(token, pos); + case Token::UNITSOFF: + return Parser::make_UNITSOFF(token, pos); + case Token::UNITSON: + return Parser::make_UNITSON(token, pos); + case Token::USEION: + return Parser::make_USEION(token, pos); + case Token::USING: + return Parser::make_USING(token, pos); + case Token::VS: + return Parser::make_VS(token, pos); + case Token::WATCH: + return Parser::make_WATCH(token, pos); + case Token::WHILE: + return Parser::make_WHILE(token, pos); + case Token::WITH: + return Parser::make_WITH(token, pos); + case Token::WRITE: + return Parser::make_WRITE(token, pos); + case Token::REACT1: + return Parser::make_REACT1(token, pos); + case Token::NONLIN1: + return Parser::make_NONLIN1(token, pos); + case Token::LIN1: + return Parser::make_LIN1(token, pos); + case Token::TILDE: + return Parser::make_TILDE(token, pos); + case Token::REACTION: + return Parser::make_REACTION(token, pos); + case Token::GT: + return Parser::make_GT(token, pos); + case Token::GE: + return Parser::make_GE(token, pos); + case Token::LT: + return Parser::make_LT(token, pos); + case Token::LE: + return Parser::make_LE(token, pos); + case Token::EQ: + return Parser::make_EQ(token, pos); + case Token::NE: + return Parser::make_NE(token, pos); + case Token::NOT: + return Parser::make_NOT(token, pos); + case Token::AND: + return Parser::make_AND(token, pos); + case Token::OR: + return Parser::make_OR(token, pos); + case Token::OPEN_BRACE: + return Parser::make_OPEN_BRACE(token, pos); + case Token::CLOSE_BRACE: + return Parser::make_CLOSE_BRACE(token, pos); + case Token::OPEN_PARENTHESIS: + return Parser::make_OPEN_PARENTHESIS(token, pos); + case Token::CLOSE_PARENTHESIS: + return Parser::make_CLOSE_PARENTHESIS(token, pos); + case Token::OPEN_BRACKET: + return Parser::make_OPEN_BRACKET(token, pos); + case Token::CLOSE_BRACKET: + return Parser::make_CLOSE_BRACKET(token, pos); + case Token::AT: + return Parser::make_AT(token, pos); + case Token::ADD: + return Parser::make_ADD(token, pos); + case Token::MINUS: + return Parser::make_MINUS(token, pos); + case Token::MULTIPLY: + return Parser::make_MULTIPLY(token, pos); + case Token::DIVIDE: + return Parser::make_DIVIDE(token, pos); + case Token::EQUAL: + return Parser::make_EQUAL(token, pos); + case Token::CARET: + return Parser::make_CARET(token, pos); + case Token::COLON: + return Parser::make_COLON(token, pos); + case Token::COMMA: + return Parser::make_COMMA(token, pos); + case Token::PERIOD: + return Parser::make_PERIOD(token, pos); - /** We hit default case for missing token type. This is more likely - * a bug in the implementation where we forgot to handle token type. */ - default: - auto msg = "Token type not found while creating a symbol!"; - throw std::runtime_error(msg); - } + /** We hit default case for missing token type. This is more likely + * a bug in the implementation where we forgot to handle token type. */ + default: + auto msg = "Token type not found while creating a symbol!"; + throw std::runtime_error(msg); } +} } // namespace nmodl diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index f7fa6c7dc5..cf5f6757c3 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "parser/nmodl/location.hh" @@ -14,20 +21,16 @@ */ namespace nmodl { - using PositionType = nmodl::location; - using SymbolType = nmodl::Parser::symbol_type; - using Token = nmodl::Parser::token; - using TokenType = nmodl::Parser::token_type; +using PositionType = nmodl::location; +using SymbolType = nmodl::Parser::symbol_type; +using Token = nmodl::Parser::token; +using TokenType = nmodl::Parser::token_type; - SymbolType double_symbol(double value, PositionType& pos); - SymbolType integer_symbol(int value, PositionType& pos, const char* text = nullptr); - SymbolType name_symbol(const std::string& text, - PositionType& pos, - TokenType type = Token::NAME); - SymbolType prime_symbol(std::string text, PositionType& pos); - SymbolType string_symbol(const std::string& text, PositionType& pos); - SymbolType token_symbol(const std::string& key, - PositionType& pos, - TokenType type = Token::UNKNOWN); +SymbolType double_symbol(double value, PositionType& pos); +SymbolType integer_symbol(int value, PositionType& pos, const char* text = nullptr); +SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type = Token::NAME); +SymbolType prime_symbol(std::string text, PositionType& pos); +SymbolType string_symbol(const std::string& text, PositionType& pos); +SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType type = Token::UNKNOWN); } // namespace nmodl diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 25993f08fd..f4cdc5a2a8 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <cstring> #include <map> #include <vector> @@ -7,11 +14,11 @@ #include "parser/nmodl/nmodl_parser.hpp" namespace nmodl { - using Token = nmodl::Parser::token; - using TokenType = nmodl::Parser::token_type; +using Token = nmodl::Parser::token; +using TokenType = nmodl::Parser::token_type; - namespace internal { - // clang-format off +namespace internal { +// clang-format off /** Keywords from NMODL language : name and token pair * * \todo Some keywords have different token names, e.g. TITLE @@ -110,22 +117,23 @@ namespace nmodl { {"PROTECT", Token::PROTECT}, {"MUTEXLOCK", Token::NRNMUTEXLOCK}, {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; - // clang-format on +// clang-format on - /// numerical methods supported in nmodl - struct MethodInfo { - /// method types that will work with this method - int64_t subtype = 0; +/// numerical methods supported in nmodl +struct MethodInfo { + /// method types that will work with this method + int64_t subtype = 0; - /// if it's a variable timestep method - int varstep = 0; + /// if it's a variable timestep method + int varstep = 0; - MethodInfo() = default; - MethodInfo(int64_t s, int v) : subtype(s), varstep(v) { - } - }; + MethodInfo() = default; + MethodInfo(int64_t s, int v) + : subtype(s) + , varstep(v) {} +}; - // clang-format off +// clang-format off static std::map<std::string, MethodInfo> methods = {{"adams", MethodInfo(DERF | KINF, 0)}, {"runge", MethodInfo(DERF | KINF, 0)}, {"euler", MethodInfo(DERF | KINF, 0)}, @@ -145,30 +153,30 @@ namespace nmodl { {"after_cvode", MethodInfo(0, 0)}, {"cvode_t", MethodInfo(0, 0)}, {"cvode_t_v", MethodInfo(0, 0)}}; - // clang-format on +// clang-format on - /** In the original implementation different vectors were created for - * extdef, extdef2, extdef3, extdef4 etc. Instead of that we are changing - * those vectors with <name, type> map. This will help us to search - * in single map and find it's type. The types are defined as follows: - * - * DefinitionType::EXT_DOUBLE : external names that can be used as doubles - * without giving an error message - * - * DefinitionType::EXT_2 : external function names that can be used with - * array and function name arguments - * - * DefinitionType::EXT_3 : function names that get two reset arguments - * - * DefinitionType::EXT_4 : functions that need a first arg of NrnThread* - * - * DefinitionType::EXT_5 : the extdef names that are not threadsafe - * - * These types were used so that it's easy to it to old implementation. */ +/** In the original implementation different vectors were created for + * extdef, extdef2, extdef3, extdef4 etc. Instead of that we are changing + * those vectors with <name, type> map. This will help us to search + * in single map and find it's type. The types are defined as follows: + * + * DefinitionType::EXT_DOUBLE : external names that can be used as doubles + * without giving an error message + * + * DefinitionType::EXT_2 : external function names that can be used with + * array and function name arguments + * + * DefinitionType::EXT_3 : function names that get two reset arguments + * + * DefinitionType::EXT_4 : functions that need a first arg of NrnThread* + * + * DefinitionType::EXT_5 : the extdef names that are not threadsafe + * + * These types were used so that it's easy to it to old implementation. */ - enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; +enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; - // clang-format off +// clang-format off static std::map<std::string, DefinitionType> extern_definitions = { {"first_time", DefinitionType::EXT_DOUBLE}, {"error", DefinitionType::EXT_DOUBLE}, @@ -272,77 +280,77 @@ namespace nmodl { {"schedule", DefinitionType::EXT_5}, {"set_seed", DefinitionType::EXT_5}, {"nrn_random_play", DefinitionType::EXT_5}}; - // clang-format on +// clang-format on - /** Internal NEURON variables that can be used in nmod files. The compiler - * passes like scope checker need to know if certain variable is undefined. - * Note that these are not used by lexer/parser. */ +/** Internal NEURON variables that can be used in nmod files. The compiler + * passes like scope checker need to know if certain variable is undefined. + * Note that these are not used by lexer/parser. */ - static std::vector<std::string> neuron_vars = {"t", "dt", "celsius", "v", "diam", "area"}; +static std::vector<std::string> neuron_vars = {"t", "dt", "celsius", "v", "diam", "area"}; - TokenType keyword_type(const std::string& name) { - return keywords[name]; - } +TokenType keyword_type(const std::string& name) { + return keywords[name]; +} - /** \todo: revisit implementation, this is no longer - * necessary as token_type is sufficient - */ - TokenType method_type(const std::string& /*name*/) { - return Token::METHOD; - } +/** \todo: revisit implementation, this is no longer + * necessary as token_type is sufficient + */ +TokenType method_type(const std::string& /*name*/) { + return Token::METHOD; +} - bool is_externdef(const std::string& name) { - return (extern_definitions.find(name) != extern_definitions.end()); - } +bool is_externdef(const std::string& name) { + return (extern_definitions.find(name) != extern_definitions.end()); +} - DefinitionType extdef_type(const std::string& name) { - if (!is_externdef(name)) { - throw std::runtime_error("Can't find " + name + " in external definitions!"); - } - return extern_definitions[name]; - } +DefinitionType extdef_type(const std::string& name) { + if (!is_externdef(name)) { + throw std::runtime_error("Can't find " + name + " in external definitions!"); + } + return extern_definitions[name]; +} - } // namespace internal +} // namespace internal - /// methods exposed to lexer, parser and compilers passes +/// methods exposed to lexer, parser and compilers passes - bool is_keyword(const std::string& name) { - return (internal::keywords.find(name) != internal::keywords.end()); - } +bool is_keyword(const std::string& name) { + return (internal::keywords.find(name) != internal::keywords.end()); +} - bool is_method(const std::string& name) { - return (internal::methods.find(name) != internal::methods.end()); - } +bool is_method(const std::string& name) { + return (internal::methods.find(name) != internal::methods.end()); +} - /// return token type for associated name (used by nmodl scanner) - TokenType token_type(const std::string& name) { - if (is_keyword(name)) { - return internal::keyword_type(name); - } - if (is_method(name)) { - return internal::method_type(name); - } - throw std::runtime_error("get_token_type called for non-existent token " + name); +/// return token type for associated name (used by nmodl scanner) +TokenType token_type(const std::string& name) { + if (is_keyword(name)) { + return internal::keyword_type(name); } - - /// return all external variables - std::vector<std::string> get_external_variables() { - std::vector<std::string> result; - result.insert(result.end(), internal::neuron_vars.begin(), internal::neuron_vars.end()); - return result; + if (is_method(name)) { + return internal::method_type(name); } + throw std::runtime_error("get_token_type called for non-existent token " + name); +} + +/// return all external variables +std::vector<std::string> get_external_variables() { + std::vector<std::string> result; + result.insert(result.end(), internal::neuron_vars.begin(), internal::neuron_vars.end()); + return result; +} - /// return all solver methods as well as commonly used math functions - std::vector<std::string> get_external_functions() { - std::vector<std::string> result; - result.reserve(internal::methods.size()); - for (auto& method : internal::methods) { - result.push_back(method.first); - } - for (auto& definition : internal::extern_definitions) { - result.push_back(definition.first); - } - return result; +/// return all solver methods as well as commonly used math functions +std::vector<std::string> get_external_functions() { + std::vector<std::string> result; + result.reserve(internal::methods.size()); + for (auto& method: internal::methods) { + result.push_back(method.first); + } + for (auto& definition: internal::extern_definitions) { + result.push_back(definition.first); } + return result; +} } // namespace nmodl diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 580f96de43..991f42e143 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -1,12 +1,19 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include "parser/nmodl/nmodl_parser.hpp" #include <string> namespace nmodl { - bool is_keyword(const std::string& name); - bool is_method(const std::string& name); - nmodl::Parser::token_type token_type(const std::string& name); - std::vector<std::string> get_external_variables(); - std::vector<std::string> get_external_functions(); +bool is_keyword(const std::string& name); +bool is_method(const std::string& name); +nmodl::Parser::token_type token_type(const std::string& name); +std::vector<std::string> get_external_variables(); +std::vector<std::string> get_external_functions(); } // namespace nmodl diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 6183b2d70c..0318d3a883 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + %{ #include <cstdio> #include <cstdlib> diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 0e341a61ab..2e7d835a99 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -1,22 +1,19 @@ -#============================================================================= +# ============================================================================= # NMODL sources -#============================================================================= +# ============================================================================= include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.cpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.cpp) -#============================================================================= +# ============================================================================= # Add executables -#============================================================================= -add_executable(nmodl - ${NMODL_SOURCE_FILES}) +# ============================================================================= +add_executable(nmodl ${NMODL_SOURCE_FILES}) target_link_libraries(nmodl printer codegen visitor symtab util lexer) - -#============================================================================= +# ============================================================================= # Install executable -#============================================================================= -install(TARGETS nmodl DESTINATION bin) \ No newline at end of file +# ============================================================================= +install(TARGETS nmodl DESTINATION bin) diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index f71a1a6f1f..feccde7f59 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -1,3 +1,9 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ #include "arg_handler.hpp" #include "tclap/CmdLine.h" diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp index 71924882ee..8f5d9c8de6 100644 --- a/src/nmodl/nmodl/arg_handler.hpp +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_ARG_HANDLER_HPP -#define NMODL_ARG_HANDLER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> #include <vector> @@ -86,5 +92,3 @@ struct ArgumentHandler { return accel_backend == "CUDA"; } }; - -#endif diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 6f7c008672..1b3fc75c55 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -1,13 +1,20 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <iostream> -#include <sstream> #include <pybind11/embed.h> +#include <sstream> #include "arg_handler.hpp" -#include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_acc_visitor.hpp" -#include "codegen/codegen_omp_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_cuda_visitor.hpp" +#include "codegen/codegen_omp_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" @@ -19,9 +26,9 @@ #include "visitors/localize_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" -#include "visitors/symtab_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -47,7 +54,7 @@ int main(int argc, const char* argv[]) { pybind11::initialize_interpreter(); } - for (auto& nmodl_file : arg.nmodl_files) { + for (auto& nmodl_file: arg.nmodl_files) { std::ifstream file(nmodl_file); if (!file.good()) { diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 22d709739d..7634d1a947 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -1,9 +1,8 @@ -#============================================================================= +# ============================================================================= # Parser executable -#============================================================================= +# ============================================================================= -# lexer library links with all parser related files -# so no need to have parser as a separate library +# lexer library links with all parser related files so no need to have parser as a separate library add_executable(nmodl_parser main_nmodl.cpp) add_executable(c_parser main_c.cpp) @@ -11,8 +10,8 @@ add_executable(c_parser main_c.cpp) target_link_libraries(nmodl_parser lexer) target_link_libraries(c_parser lexer) -#============================================================================= +# ============================================================================= # Install executable -#============================================================================= +# ============================================================================= install(TARGETS nmodl_parser DESTINATION bin/parser) -install(TARGETS c_parser DESTINATION bin/parser) \ No newline at end of file +install(TARGETS c_parser DESTINATION bin/parser) diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index ad604fa936..069e2ad643 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -1,9 +1,12 @@ -/****************************************************************************** +/********************************************************************************** * * @brief Bison grammar and parser implementation for C (11) * * NMODL has verbatim constructs that allow to specify C code sections within * nmodl implementation. + * + * CREDIT : This is based on bison specification available at + * http://www.quut.com/c/ANSI-C-grammar-y-2011.html *****************************************************************************/ %code requires diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index c41b39bcda..44f3ea6673 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <sstream> @@ -6,65 +13,66 @@ namespace c11 { - Driver::Driver(bool strace, bool ptrace) : trace_scanner(strace), trace_parser(ptrace) { - } +Driver::Driver(bool strace, bool ptrace) + : trace_scanner(strace) + , trace_parser(ptrace) {} - /// parse c file provided as istream - bool Driver::parse_stream(std::istream& in) { - Lexer scanner(*this, &in); - Parser parser(scanner, *this); +/// parse c file provided as istream +bool Driver::parse_stream(std::istream& in) { + Lexer scanner(*this, &in); + Parser parser(scanner, *this); - this->lexer = &scanner; - this->parser = &parser; + this->lexer = &scanner; + this->parser = &parser; - scanner.set_debug(trace_scanner); - parser.set_debug_level(trace_parser); - return (parser.parse() == 0); - } + scanner.set_debug(trace_scanner); + parser.set_debug_level(trace_parser); + return (parser.parse() == 0); +} - //// parse c file - bool Driver::parse_file(const std::string& filename) { - std::ifstream in(filename.c_str()); - streamname = filename; +//// parse c file +bool Driver::parse_file(const std::string& filename) { + std::ifstream in(filename.c_str()); + streamname = filename; - if (!in.good()) { - return false; - } - return parse_stream(in); + if (!in.good()) { + return false; } + return parse_stream(in); +} - /// parser c provided as string (used for testing) - bool Driver::parse_string(const std::string& input) { - std::istringstream iss(input); - return parse_stream(iss); - } +/// parser c provided as string (used for testing) +bool Driver::parse_string(const std::string& input) { + std::istringstream iss(input); + return parse_stream(iss); +} - void Driver::error(const std::string& m, const class location& l) { - std::cerr << l << " : " << m << std::endl; - } +void Driver::error(const std::string& m, const class location& l) { + std::cerr << l << " : " << m << std::endl; +} - void Driver::error(const std::string& m) { - std::cerr << m << std::endl; - } +void Driver::error(const std::string& m) { + std::cerr << m << std::endl; +} - void Driver::add_token(const std::string& text) { - tokens.push_back(text); - // here we will query and look into symbol table or register callback - } +void Driver::add_token(const std::string& text) { + tokens.push_back(text); + // here we will query and look into symbol table or register callback +} - void Driver::scan_string(std::string& text) { - std::istringstream in(text); - Lexer scanner(*this, &in); - Parser parser(scanner, *this); - this->lexer = &scanner; - this->parser = &parser; - while (true) { - auto sym = lexer->next_token(); - auto token = sym.token(); - if (token == Parser::token::END) { - break; - } +void Driver::scan_string(std::string& text) { + std::istringstream in(text); + Lexer scanner(*this, &in); + Parser parser(scanner, *this); + this->lexer = &scanner; + this->parser = &parser; + while (true) { + auto sym = lexer->next_token(); + auto token = sym.token(); + if (token == Parser::token::END) { + break; } } +} } // namespace c11 diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index dd47030be2..622e42d25d 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -1,6 +1,11 @@ -#ifndef C11_DRIVER_HPP -#define C11_DRIVER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#pragma once #include <algorithm> #include <map> @@ -10,89 +15,86 @@ /** The c11 namespace encapsulates everything related to C (11) parsing */ namespace c11 { - /** - * \class Driver - * \brief Class that binds all pieces together for parsing C verbatim blocks - */ +/** + * \class Driver + * \brief Class that binds all pieces together for parsing C verbatim blocks + */ - /// flex generated scanner class (extends base lexer class of flex) - class Lexer; +/// flex generated scanner class (extends base lexer class of flex) +class Lexer; - /// parser class generated by bison - class Parser; +/// parser class generated by bison +class Parser; - class Driver { - private: - /// all typedefs - std::map<std::string, std::string> typedefs; +class Driver { + private: + /// all typedefs + std::map<std::string, std::string> typedefs; - /// constants defined in enum - std::vector<std::string> enum_constants; + /// constants defined in enum + std::vector<std::string> enum_constants; - /// all tokens encountered - std::vector<std::string> tokens; + /// all tokens encountered + std::vector<std::string> tokens; - /// enable debug output in the flex scanner - bool trace_scanner = false; + /// enable debug output in the flex scanner + bool trace_scanner = false; - /// enable debug output in the bison parser - bool trace_parser = false; + /// enable debug output in the bison parser + bool trace_parser = false; - /// pointer to the lexer instance being used - Lexer* lexer = nullptr; + /// pointer to the lexer instance being used + Lexer* lexer = nullptr; - /// pointer to the parser instance being used - Parser* parser = nullptr; + /// pointer to the parser instance being used + Parser* parser = nullptr; - /// print messages from lexer/parser - bool verbose = false; + /// print messages from lexer/parser + bool verbose = false; - public: - /// file or input stream name (used by scanner for position), see todo - std::string streamname; + public: + /// file or input stream name (used by scanner for position), see todo + std::string streamname; - Driver() = default; - Driver(bool strace, bool ptrace); + Driver() = default; + Driver(bool strace, bool ptrace); - void error(const std::string& m, const class location& l); - void error(const std::string& m); + void error(const std::string& m, const class location& l); + void error(const std::string& m); - bool parse_stream(std::istream& in); - bool parse_string(const std::string& input); - bool parse_file(const std::string& filename); - void scan_string(std::string& text); - void add_token(const std::string&); + bool parse_stream(std::istream& in); + bool parse_string(const std::string& input); + bool parse_file(const std::string& filename); + void scan_string(std::string& text); + void add_token(const std::string&); - void set_verbose(bool b) { - verbose = b; - } + void set_verbose(bool b) { + verbose = b; + } - bool is_verbose() const { - return verbose; - } + bool is_verbose() const { + return verbose; + } - bool is_typedef(std::string type) const { - return typedefs.find(type) != typedefs.end(); - } + bool is_typedef(std::string type) const { + return typedefs.find(type) != typedefs.end(); + } - bool is_enum_constant(std::string constant) const { - return std::find(enum_constants.begin(), enum_constants.end(), constant) != - enum_constants.end(); - } + bool is_enum_constant(std::string constant) const { + return std::find(enum_constants.begin(), enum_constants.end(), constant) != + enum_constants.end(); + } - std::vector<std::string> all_tokens() const { - return tokens; - } + std::vector<std::string> all_tokens() const { + return tokens; + } - bool has_token(std::string token) { - if (std::find(tokens.begin(), tokens.end(), token) != tokens.end()) { - return true; - } - return false; + bool has_token(std::string token) { + if (std::find(tokens.begin(), tokens.end(), token) != tokens.end()) { + return true; } - }; + return false; + } +}; } // namespace c11 - - -#endif diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 8594f37df0..a91feb7433 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -1,4 +1,12 @@ -/****************************************************************************** +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2019 Michael Hines + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +/********************************************************************************** * * @brief Bison grammar and parser implementation for ODEs * diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index fe31b93b67..ce55e4c1d8 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <iostream> #include "lexer/diffeq_lexer.hpp" @@ -6,7 +13,9 @@ using namespace diffeq; -Term::Term(const std::string& expr, const std::string& state) : expr(expr), b(expr) { +Term::Term(const std::string& expr, const std::string& state) + : expr(expr) + , b(expr) { if (expr == state) { deriv = "1.0"; a = "1.0"; diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 4ed82f98b9..0122bf8bb3 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -1,123 +1,131 @@ -#ifndef NMODL_DIFFEQ_CONTEXT -#define NMODL_DIFFEQ_CONTEXT +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> namespace diffeq { - /** - * \class Term - * \brief Represent a term in differential equation and it's "current" solution - * - * When differential equation is parsed, each variable/term is represented - * by this class. As expressions are formed, like a+b, the solution gets - * updated - */ +/** + * \class Term + * \brief Represent a term in differential equation and it's "current" solution + * + * When differential equation is parsed, each variable/term is represented + * by this class. As expressions are formed, like a+b, the solution gets + * updated + */ - struct Term { - /// expression being solved - std::string expr; +struct Term { + /// expression being solved + std::string expr; - /// derivative of the expression - std::string deriv = "0.0"; + /// derivative of the expression + std::string deriv = "0.0"; - /// \todo : need to check in neuron implementation? - std::string a = "0.0"; - std::string b = "0.0"; + /// \todo : need to check in neuron implementation? + std::string a = "0.0"; + std::string b = "0.0"; - Term() = default; + Term() = default; - Term(const std::string& expr, const std::string& state); + Term(const std::string& expr, const std::string& state); - Term(std::string expr, std::string deriv, std::string a, std::string b) - : expr(expr), deriv(deriv), a(a), b(b) { - } + Term(std::string expr, std::string deriv, std::string a, std::string b) + : expr(expr) + , deriv(deriv) + , a(a) + , b(b) {} - /// helper routines used in parser + /// helper routines used in parser - bool deriv_nonzero() { - return deriv != "0.0"; - } + bool deriv_nonzero() { + return deriv != "0.0"; + } - bool a_nonzero() { - return a != "0.0"; - } + bool a_nonzero() { + return a != "0.0"; + } - bool b_nonzero() { - return b != "0.0"; - } + bool b_nonzero() { + return b != "0.0"; + } - void print(); - }; + void print(); +}; - /** - * \class DiffEqContext - * \brief Helper class used by driver and parser while solving diff equation - * - */ +/** + * \class DiffEqContext + * \brief Helper class used by driver and parser while solving diff equation + * + */ - class DiffEqContext { - /// name of the solve method - std::string method; +class DiffEqContext { + /// name of the solve method + std::string method; - /// name of the state variable - std::string state; + /// name of the state variable + std::string state; - /// rhs of equation - std::string rhs; + /// rhs of equation + std::string rhs; - /// order of the diff equation - int order = 0; + /// order of the diff equation + int order = 0; - /// if equation is non-linear then expression to use during code generation - std::string expr_for_nonlinear; + /// if equation is non-linear then expression to use during code generation + std::string expr_for_nonlinear; - /// return solution for cnexp method - std::string get_cnexp_solution(); + /// return solution for cnexp method + std::string get_cnexp_solution(); - /// return solution for non-cnexp method - std::string get_non_cnexp_solution(); + /// return solution for non-cnexp method + std::string get_non_cnexp_solution(); - /// for non-cnexp methods : return the solution based on if equation is linear or not - std::string get_cvode_linear_diffeq(); - std::string get_cvode_nonlinear_diffeq(); + /// for non-cnexp methods : return the solution based on if equation is linear or not + std::string get_cvode_linear_diffeq(); + std::string get_cvode_nonlinear_diffeq(); - /// \todo: methods inherited neuron implementation - std::string cvode_deriv(); - std::string cvode_eqnrhs(); + /// \todo: methods inherited neuron implementation + std::string cvode_deriv(); + std::string cvode_eqnrhs(); - public: - /// "final" solution of the equation - Term solution; + public: + /// "final" solution of the equation + Term solution; - /// if derivative of the equation is invalid - bool deriv_invalid = false; + /// if derivative of the equation is invalid + bool deriv_invalid = false; - /// if equation itself is invalid - bool eqn_invalid = false; + /// if equation itself is invalid + bool eqn_invalid = false; - DiffEqContext() = default; + DiffEqContext() = default; - DiffEqContext(std::string state, int order, std::string rhs, std::string method) - : state(state), order(order), rhs(rhs), method(method) { - } + DiffEqContext(std::string state, int order, std::string rhs, std::string method) + : state(state) + , order(order) + , rhs(rhs) + , method(method) {} - /// return the state variable - std::string state_variable() const { - return state; - } + /// return the state variable + std::string state_variable() const { + return state; + } - /// return solution of the differential equation - std::string get_solution(bool& cnexp_possible); + /// return solution of the differential equation + std::string get_solution(bool& cnexp_possible); - /// return expression with Dstate added - std::string get_expr_for_nonlinear(); + /// return expression with Dstate added + std::string get_expr_for_nonlinear(); - /// print the context (for debugging) - void print(); - }; + /// print the context (for debugging) + void print(); +}; } // namespace diffeq - -#endif // NMODL_DIFFEQ_CONTEXT diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index a722fa5573..1e31135c06 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <sstream> #include <utility> @@ -7,58 +14,58 @@ namespace diffeq { - void Driver::parse_equation(const std::string& equation, - std::string& state, - std::string& rhs, - int& order) { - auto parts = stringutils::split_string(equation, '='); - state = stringutils::trim(parts[0]); - rhs = stringutils::trim(parts[1]); +void Driver::parse_equation(const std::string& equation, + std::string& state, + std::string& rhs, + int& order) { + auto parts = stringutils::split_string(equation, '='); + state = stringutils::trim(parts[0]); + rhs = stringutils::trim(parts[1]); - /// expect prime on lhs, find order and remove quote - order = std::count(state.begin(), state.end(), '\''); - stringutils::remove_character(state, '\''); + /// expect prime on lhs, find order and remove quote + order = std::count(state.begin(), state.end(), '\''); + stringutils::remove_character(state, '\''); - /// error if no prime in equation or not an assignment statement - if (order == 0 || state.empty()) { - throw std::runtime_error("Invalid equation, no prime on rhs? " + equation); - } + /// error if no prime in equation or not an assignment statement + if (order == 0 || state.empty()) { + throw std::runtime_error("Invalid equation, no prime on rhs? " + equation); } +} - std::string Driver::solve(const std::string& equation, std::string method, bool debug) { - std::string state, rhs; - int order = 0; - bool cnexp_possible; - parse_equation(equation, state, rhs, order); - return solve_equation(state, order, rhs, method, cnexp_possible, debug); - } +std::string Driver::solve(const std::string& equation, std::string method, bool debug) { + std::string state, rhs; + int order = 0; + bool cnexp_possible; + parse_equation(equation, state, rhs, order); + return solve_equation(state, order, rhs, method, cnexp_possible, debug); +} - std::string Driver::solve_equation(std::string& state, - int order, - std::string& rhs, - std::string& method, - bool& cnexp_possible, - bool debug) { - std::istringstream in(rhs); - DiffEqContext context(state, order, rhs, method); - Lexer scanner(&in); - Parser parser(scanner, context); - parser.parse(); - if (debug) { - context.print(); - } - return context.get_solution(cnexp_possible); +std::string Driver::solve_equation(std::string& state, + int order, + std::string& rhs, + std::string& method, + bool& cnexp_possible, + bool debug) { + std::istringstream in(rhs); + DiffEqContext context(state, order, rhs, method); + Lexer scanner(&in); + Parser parser(scanner, context); + parser.parse(); + if (debug) { + context.print(); } + return context.get_solution(cnexp_possible); +} - /// \todo : instead of using neuron like api, we need to refactor - bool Driver::cnexp_possible(const std::string& equation, std::string& solution) { - std::string state, rhs; - int order = 0; - bool cnexp_possible; - std::string method = "cnexp"; - parse_equation(equation, state, rhs, order); - solution = solve_equation(state, order, rhs, method, cnexp_possible); - return cnexp_possible; - } +/// \todo : instead of using neuron like api, we need to refactor +bool Driver::cnexp_possible(const std::string& equation, std::string& solution) { + std::string state, rhs; + int order = 0; + bool cnexp_possible; + std::string method = "cnexp"; + parse_equation(equation, state, rhs, order); + solution = solve_equation(state, order, rhs, method, cnexp_possible); + return cnexp_possible; +} } // namespace diffeq diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index ae7e5ec339..28558332b8 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -1,5 +1,11 @@ -#ifndef DIFFEQ_DRIVER_HPP -#define DIFFEQ_DRIVER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> @@ -7,45 +13,43 @@ namespace diffeq { - /** - * \class Driver - * \brief Class that binds all pieces together for parsing differential equations - * - * Driver class bind components required for lexing, parsing and ast - * generation for differential equations. - */ - - /// flex generated scanner class (extends base lexer class of flex) - class Lexer; - - /// parser class generated by bison - class Parser; - - class Driver { - private: - std::string solve_equation(std::string& state, - int order, - std::string& rhs, - std::string& method, - bool& cnexp_possible, - bool debug = false); - - /// parse given equation into lhs, rhs and find it's order and state variable - void parse_equation(const std::string& equation, - std::string& state, - std::string& rhs, - int& order); - - public: - Driver() = default; - - /// solve equation using provided method - std::string solve(const std::string& equation, std::string method, bool debug = false); - - /// check if given equation can be solved using cnexp method - bool cnexp_possible(const std::string& equation, std::string& solution); - }; +/** + * \class Driver + * \brief Class that binds all pieces together for parsing differential equations + * + * Driver class bind components required for lexing, parsing and ast + * generation for differential equations. + */ + +/// flex generated scanner class (extends base lexer class of flex) +class Lexer; + +/// parser class generated by bison +class Parser; + +class Driver { + private: + std::string solve_equation(std::string& state, + int order, + std::string& rhs, + std::string& method, + bool& cnexp_possible, + bool debug = false); + + /// parse given equation into lhs, rhs and find it's order and state variable + void parse_equation(const std::string& equation, + std::string& state, + std::string& rhs, + int& order); + + public: + Driver() = default; + + /// solve equation using provided method + std::string solve(const std::string& equation, std::string method, bool debug = false); + + /// check if given equation can be solved using cnexp method + bool cnexp_possible(const std::string& equation, std::string& solution); +}; } // namespace diffeq - -#endif diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index d24281ef05..a759a9804f 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_DIFFEQ_HELPER -#define NMODL_DIFFEQ_HELPER +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "parser/diffeq_context.hpp" @@ -17,153 +23,151 @@ namespace diffeq { - /// operators beign supported as part of binary expressions - enum class MathOp { add = 1, sub, mul, div }; - - template <MathOp Op> - inline Term eval_derivative(Term& first, Term& second, bool& deriv_invalid, bool& eqn_invalid); - - - /// implement (f(x) + g(x))' = f'(x) + g'(x) - template <> - inline Term eval_derivative<MathOp::add>(Term& first, - Term& second, - bool& deriv_invalid, - bool& eqn_invalid) { - Term solution; - solution.expr = first.expr + "+" + second.expr; - - if (first.deriv_nonzero() && second.deriv_nonzero()) { - solution.deriv = first.deriv + "+" + second.deriv; - } else if (first.deriv_nonzero()) { - solution.deriv = first.deriv; - } else if (second.deriv_nonzero()) { - solution.deriv = second.deriv; - } - - if (first.a_nonzero() && second.a_nonzero()) { - solution.a = first.a + "+" + second.a; - } else if (first.a_nonzero()) { - solution.a = first.a; - } else if (second.a_nonzero()) { - solution.a = second.a; - } - - if (first.b_nonzero() && second.b_nonzero()) { - solution.b = first.b + "+" + second.b; - } else if (first.b_nonzero()) { - solution.b = first.b; - } else if (second.b_nonzero()) { - solution.b = second.b; - } - - return solution; +/// operators beign supported as part of binary expressions +enum class MathOp { add = 1, sub, mul, div }; + +template <MathOp Op> +inline Term eval_derivative(Term& first, Term& second, bool& deriv_invalid, bool& eqn_invalid); + + +/// implement (f(x) + g(x))' = f'(x) + g'(x) +template <> +inline Term eval_derivative<MathOp::add>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "+" + second.expr; + + if (first.deriv_nonzero() && second.deriv_nonzero()) { + solution.deriv = first.deriv + "+" + second.deriv; + } else if (first.deriv_nonzero()) { + solution.deriv = first.deriv; + } else if (second.deriv_nonzero()) { + solution.deriv = second.deriv; } + if (first.a_nonzero() && second.a_nonzero()) { + solution.a = first.a + "+" + second.a; + } else if (first.a_nonzero()) { + solution.a = first.a; + } else if (second.a_nonzero()) { + solution.a = second.a; + } - /// implement (f(x) - g(x))' = f'(x) - g'(x) - template <> - inline Term eval_derivative<MathOp::sub>(Term& first, - Term& second, - bool& deriv_invalid, - bool& eqn_invalid) { - Term solution; - solution.expr = first.expr + "-" + second.expr; - - if (first.deriv_nonzero() && second.deriv_nonzero()) { - solution.deriv = first.deriv + "-" + second.deriv; - } else if (first.deriv_nonzero()) { - solution.deriv = first.deriv; - } else if (second.deriv_nonzero()) { - solution.deriv = "(-" + second.deriv + ")"; - } - - if (first.a_nonzero() && second.a_nonzero()) { - solution.a = first.a + "-" + second.a; - } else if (first.a_nonzero()) { - solution.a = first.a; - } else if (second.a_nonzero()) { - solution.a = "(-" + second.a + ")"; - } - - if (first.b_nonzero() && second.b_nonzero()) { - solution.b = first.b + "-" + second.b; - } else if (first.b_nonzero()) { - solution.b = first.b; - } else if (second.b_nonzero()) { - solution.b = "(-" + second.b + ")"; - } - - return solution; + if (first.b_nonzero() && second.b_nonzero()) { + solution.b = first.b + "+" + second.b; + } else if (first.b_nonzero()) { + solution.b = first.b; + } else if (second.b_nonzero()) { + solution.b = second.b; } + return solution; +} + + +/// implement (f(x) - g(x))' = f'(x) - g'(x) +template <> +inline Term eval_derivative<MathOp::sub>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "-" + second.expr; + + if (first.deriv_nonzero() && second.deriv_nonzero()) { + solution.deriv = first.deriv + "-" + second.deriv; + } else if (first.deriv_nonzero()) { + solution.deriv = first.deriv; + } else if (second.deriv_nonzero()) { + solution.deriv = "(-" + second.deriv + ")"; + } - /// implement (f(x) * g(x))' = f'(x)g(x) + f(x)g'(x) - template <> - inline Term eval_derivative<MathOp::mul>(Term& first, - Term& second, - bool& deriv_invalid, - bool& eqn_invalid) { - Term solution; - solution.expr = first.expr + "*" + second.expr; - - if (first.deriv_nonzero() && second.deriv_nonzero()) { - solution.deriv = "((" + first.deriv + ")*(" + second.expr + ")"; - solution.deriv += "+(" + first.expr + ")*(" + second.deriv + "))"; - } else if (first.deriv_nonzero()) { - solution.deriv = "(" + first.deriv + ")*(" + second.expr + ")"; - } else if (second.deriv_nonzero()) { - solution.deriv = "(" + first.expr + ")*(" + second.deriv + ")"; - } - - if (first.a_nonzero() && second.a_nonzero()) { - eqn_invalid = true; - } else if (first.a_nonzero() && second.b_nonzero()) { - solution.a = "(" + first.a + ")*(" + second.b + ")"; - } else if (second.a_nonzero() && first.b_nonzero()) { - solution.a = "(" + first.b + ")*(" + second.a + ")"; - } - - if (first.b_nonzero() && second.b_nonzero()) { - solution.b = "(" + first.b + ")*(" + second.b + ")"; - } - - return solution; + if (first.a_nonzero() && second.a_nonzero()) { + solution.a = first.a + "-" + second.a; + } else if (first.a_nonzero()) { + solution.a = first.a; + } else if (second.a_nonzero()) { + solution.a = "(-" + second.a + ")"; } + if (first.b_nonzero() && second.b_nonzero()) { + solution.b = first.b + "-" + second.b; + } else if (first.b_nonzero()) { + solution.b = first.b; + } else if (second.b_nonzero()) { + solution.b = "(-" + second.b + ")"; + } - /** - * Implement (f(x) / g(x))' = (f'(x)g(x) - f(x)g'(x))/g^2(x) - * Note that the implementation is very limited for the g(x) - * and this needs to be discussed with Michael. - */ - template <> - inline Term eval_derivative<MathOp::div>(Term& first, - Term& second, - bool& deriv_invalid, - bool& eqn_invalid) { - Term solution; - solution.expr = first.expr + "/" + second.expr; - - if (second.deriv_nonzero()) { - deriv_invalid = true; - } else if (first.deriv_nonzero()) { - solution.deriv = "(" + first.deriv + ")/" + second.expr; - } - - if (second.a_nonzero()) { - eqn_invalid = true; - } else if (first.a_nonzero()) { - solution.a = "(" + first.a + ")/" + second.expr; - } - - if (first.b_nonzero()) { - solution.b = "(" + first.b + ")/" + second.expr; - } - - return solution; + return solution; +} + + +/// implement (f(x) * g(x))' = f'(x)g(x) + f(x)g'(x) +template <> +inline Term eval_derivative<MathOp::mul>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "*" + second.expr; + + if (first.deriv_nonzero() && second.deriv_nonzero()) { + solution.deriv = "((" + first.deriv + ")*(" + second.expr + ")"; + solution.deriv += "+(" + first.expr + ")*(" + second.deriv + "))"; + } else if (first.deriv_nonzero()) { + solution.deriv = "(" + first.deriv + ")*(" + second.expr + ")"; + } else if (second.deriv_nonzero()) { + solution.deriv = "(" + first.expr + ")*(" + second.deriv + ")"; } -} // namespace diffeq + if (first.a_nonzero() && second.a_nonzero()) { + eqn_invalid = true; + } else if (first.a_nonzero() && second.b_nonzero()) { + solution.a = "(" + first.a + ")*(" + second.b + ")"; + } else if (second.a_nonzero() && first.b_nonzero()) { + solution.a = "(" + first.b + ")*(" + second.a + ")"; + } + + if (first.b_nonzero() && second.b_nonzero()) { + solution.b = "(" + first.b + ")*(" + second.b + ")"; + } -#endif // NMODL_DIFFEQ_HELPER + return solution; +} + + +/** + * Implement (f(x) / g(x))' = (f'(x)g(x) - f(x)g'(x))/g^2(x) + * Note that the implementation is very limited for the g(x) + * and this needs to be discussed with Michael. + */ +template <> +inline Term eval_derivative<MathOp::div>(Term& first, + Term& second, + bool& deriv_invalid, + bool& eqn_invalid) { + Term solution; + solution.expr = first.expr + "/" + second.expr; + + if (second.deriv_nonzero()) { + deriv_invalid = true; + } else if (first.deriv_nonzero()) { + solution.deriv = "(" + first.deriv + ")/" + second.expr; + } + + if (second.a_nonzero()) { + eqn_invalid = true; + } else if (first.a_nonzero()) { + solution.a = "(" + first.a + ")/" + second.expr; + } + + if (first.b_nonzero()) { + solution.b = "(" + first.b + ")/" + second.expr; + } + + return solution; +} + +} // namespace diffeq diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index 1a01f1fe06..64c1832658 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <iostream> @@ -12,8 +19,7 @@ int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("C Parser: Standalone parser program for C"); - TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, - "", "string"); + TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, "", "string"); cmd.add(filearg); cmd.parse(argc, argv); diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 2ada6d1075..fbf12b331b 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <iostream> diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index fa75f22778..2db9656fa6 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1,4 +1,12 @@ -/****************************************************************************** +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2019 Michael Hines + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +/********************************************************************************** * * @brief Bison grammar and parser implementation for NMODL * diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 671a9659da..c91e588ce9 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <sstream> @@ -5,63 +12,64 @@ #include "parser/nmodl_driver.hpp" namespace nmodl { - Driver::Driver(bool strace, bool ptrace) : trace_scanner(strace), trace_parser(ptrace) { - } +Driver::Driver(bool strace, bool ptrace) + : trace_scanner(strace) + , trace_parser(ptrace) {} - /// parse nmodl file provided as istream - bool Driver::parse_stream(std::istream& in) { - Lexer scanner(*this, &in); - Parser parser(scanner, *this); +/// parse nmodl file provided as istream +bool Driver::parse_stream(std::istream& in) { + Lexer scanner(*this, &in); + Parser parser(scanner, *this); - this->lexer = &scanner; - this->parser = &parser; + this->lexer = &scanner; + this->parser = &parser; - scanner.set_debug(trace_scanner); - parser.set_debug_level(trace_parser); - return (parser.parse() == 0); - } + scanner.set_debug(trace_scanner); + parser.set_debug_level(trace_parser); + return (parser.parse() == 0); +} - //// parse nmodl file - bool Driver::parse_file(const std::string& filename) { - std::ifstream in(filename.c_str()); - streamname = filename; +//// parse nmodl file +bool Driver::parse_file(const std::string& filename) { + std::ifstream in(filename.c_str()); + streamname = filename; - if (!in.good()) { - return false; - } - return parse_stream(in); + if (!in.good()) { + return false; } + return parse_stream(in); +} - /// parser nmodl provided as string (used for testing) - bool Driver::parse_string(const std::string& input) { - std::istringstream iss(input); - return parse_stream(iss); - } +/// parser nmodl provided as string (used for testing) +bool Driver::parse_string(const std::string& input) { + std::istringstream iss(input); + return parse_stream(iss); +} - void Driver::error(const std::string& m, const class location& l) { - std::cerr << l << " : " << m << std::endl; - } +void Driver::error(const std::string& m, const class location& l) { + std::cerr << l << " : " << m << std::endl; +} - void Driver::error(const std::string& m) { - std::cerr << m << std::endl; - } +void Driver::error(const std::string& m) { + std::cerr << m << std::endl; +} - /// add macro definition and it's value (DEFINE keyword of nmodl) - void Driver::add_defined_var(const std::string& name, int value) { - defined_var[name] = value; - } +/// add macro definition and it's value (DEFINE keyword of nmodl) +void Driver::add_defined_var(const std::string& name, int value) { + defined_var[name] = value; +} - /// check if particular text is defined as macro - bool Driver::is_defined_var(const std::string& name) { - return !(defined_var.find(name) == defined_var.end()); - } +/// check if particular text is defined as macro +bool Driver::is_defined_var(const std::string& name) { + return !(defined_var.find(name) == defined_var.end()); +} - /// return variable's value defined as macro (always an integer) - int Driver::get_defined_var_value(const std::string& name) { - if (is_defined_var(name)) { - return defined_var[name]; - } - throw std::runtime_error("Trying to get undefined macro / define :" + name); +/// return variable's value defined as macro (always an integer) +int Driver::get_defined_var_value(const std::string& name) { + if (is_defined_var(name)) { + return defined_var[name]; } + throw std::runtime_error("Trying to get undefined macro / define :" + name); +} } // namespace nmodl diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index f69daeb93c..fbdea04642 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <map> @@ -8,89 +15,89 @@ /** The nmodl namespace encapsulates everything related to nmodl parsing * which includes lexer, parser, driver, keywords, token mapping etc. */ namespace nmodl { - /** - * \class Driver - * \brief Class that binds all pieces together for parsing nmodl file - * - * Driver class bind components required for lexing, parsing and ast - * generation from nmodl file. We create an instance of lexer, parser - * and provides different methods to parse from file, stream or string. - * The scanner also gets reference to driver object for two purposes : - * scanner store/query the macro definitions into/from driver class - * and erros can be propogated back to driver (not implemented yet). - * Parser class also gets a reference to driver class as a parameter. - * Parsing actions generate ast and it's pointer is stored in driver - * class. - * - * \todo lexer, parser and ast member variables are used inside lexer/ - * parser instances. The local instaces are created inside parse_stream - * and hence the pointers are no longer valid except ast. Need better - * way to handle this. - * - * \todo stream name is not used as it will need better support as - * location object used in scanner takes string pointer which could - * be invalid when we copy location object. - */ - - /// flex generated scanner class (extends base lexer class of flex) - class Lexer; - - /// parser class generated by bison - class Parser; - - class Driver { - private: - /// all macro defined in the mod file - std::map<std::string, int> defined_var; - - /// enable debug output in the flex scanner - bool trace_scanner = false; - - /// enable debug output in the bison parser - bool trace_parser = false; - - /// pointer to the lexer instance being used - Lexer* lexer = nullptr; - - /// pointer to the parser instance being used - Parser* parser = nullptr; - - /// print messages from lexer/parser - bool verbose = false; - - public: - /// file or input stream name (used by scanner for position), see todo - std::string streamname; - - /// root of the ast - std::shared_ptr<ast::Program> astRoot = nullptr; - - Driver() = default; - Driver(bool strace, bool ptrace); - - void error(const std::string& m, const class location& l); - void error(const std::string& m); - - void add_defined_var(const std::string& name, int value); - bool is_defined_var(const std::string& name); - int get_defined_var_value(const std::string& name); - - bool parse_stream(std::istream& in); - - bool parse_string(const std::string& input); - bool parse_file(const std::string& filename); - - void set_verbose(bool b) { - verbose = b; - } - - bool is_verbose() const { - return verbose; - } - - std::shared_ptr<ast::Program> ast() const { - return astRoot; - } - }; +/** + * \class Driver + * \brief Class that binds all pieces together for parsing nmodl file + * + * Driver class bind components required for lexing, parsing and ast + * generation from nmodl file. We create an instance of lexer, parser + * and provides different methods to parse from file, stream or string. + * The scanner also gets reference to driver object for two purposes : + * scanner store/query the macro definitions into/from driver class + * and erros can be propogated back to driver (not implemented yet). + * Parser class also gets a reference to driver class as a parameter. + * Parsing actions generate ast and it's pointer is stored in driver + * class. + * + * \todo lexer, parser and ast member variables are used inside lexer/ + * parser instances. The local instaces are created inside parse_stream + * and hence the pointers are no longer valid except ast. Need better + * way to handle this. + * + * \todo stream name is not used as it will need better support as + * location object used in scanner takes string pointer which could + * be invalid when we copy location object. + */ + +/// flex generated scanner class (extends base lexer class of flex) +class Lexer; + +/// parser class generated by bison +class Parser; + +class Driver { + private: + /// all macro defined in the mod file + std::map<std::string, int> defined_var; + + /// enable debug output in the flex scanner + bool trace_scanner = false; + + /// enable debug output in the bison parser + bool trace_parser = false; + + /// pointer to the lexer instance being used + Lexer* lexer = nullptr; + + /// pointer to the parser instance being used + Parser* parser = nullptr; + + /// print messages from lexer/parser + bool verbose = false; + + public: + /// file or input stream name (used by scanner for position), see todo + std::string streamname; + + /// root of the ast + std::shared_ptr<ast::Program> astRoot = nullptr; + + Driver() = default; + Driver(bool strace, bool ptrace); + + void error(const std::string& m, const class location& l); + void error(const std::string& m); + + void add_defined_var(const std::string& name, int value); + bool is_defined_var(const std::string& name); + int get_defined_var_value(const std::string& name); + + bool parse_stream(std::istream& in); + + bool parse_string(const std::string& input); + bool parse_file(const std::string& filename); + + void set_verbose(bool b) { + verbose = b; + } + + bool is_verbose() const { + return verbose; + } + + std::shared_ptr<ast::Program> ast() const { + return astRoot; + } +}; } // namespace nmodl diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index 609a421b57..61fa61e63f 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -1,5 +1,13 @@ -/* Bison specification for NMODL Extensions which includes - * VERBATIM and COMMENT blocks +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +/* + * Bison specification for NMODL Extensions which includes + * VERBATIM and COMMENT blocks */ %{ diff --git a/src/nmodl/parser/verbatim_context.hpp b/src/nmodl/parser/verbatim_context.hpp index 529d306b5c..227bb72aae 100644 --- a/src/nmodl/parser/verbatim_context.hpp +++ b/src/nmodl/parser/verbatim_context.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_VERBATIM_CONTEXT -#define NMODL_VERBATIM_CONTEXT +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <iostream> @@ -28,5 +34,3 @@ class VerbatimContext { }; int Verbatim_parse(VerbatimContext*); - -#endif // NMODL_VERBATIM_CONTEXT diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index d1ca3a001b..18aad38487 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -1,24 +1,19 @@ -#============================================================================= +# ============================================================================= # Printer sources -#============================================================================= +# ============================================================================= set(PRINTER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) -#============================================================================= +# ============================================================================= # Printer library -#============================================================================= +# ============================================================================= -add_library(printer_obj - OBJECT - ${PRINTER_SOURCE_FILES}) +add_library(printer_obj OBJECT ${PRINTER_SOURCE_FILES}) set_property(TARGET printer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) -add_library(printer - STATIC - $<TARGET_OBJECTS:printer_obj>) +add_library(printer STATIC $<TARGET_OBJECTS:printer_obj>) diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 4e0b32a9c8..6fcf7fbb29 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "printer/code_printer.hpp" #include "utils/string_utils.hpp" @@ -46,7 +53,7 @@ void CodePrinter::add_line(const std::string& text) { void CodePrinter::add_multi_line(const std::string& text) { auto lines = stringutils::split_string(text, '\n'); - for (const auto& line : lines) { + for (const auto& line: lines) { add_line(line); } } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index d7cc8b8cac..c17066571c 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_CODE_PRINTER_HPP -#define NMODL_CODE_PRINTER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <fstream> #include <iostream> @@ -23,10 +29,10 @@ class CodePrinter { const int NUM_SPACES = 4; public: - CodePrinter() : result(new std::ostream(std::cout.rdbuf())) { - } - CodePrinter(std::stringstream& stream) : result(new std::ostream(stream.rdbuf())) { - } + CodePrinter() + : result(new std::ostream(std::cout.rdbuf())) {} + CodePrinter(std::stringstream& stream) + : result(new std::ostream(stream.rdbuf())) {} CodePrinter(const std::string& filename); ~CodePrinter() { @@ -66,5 +72,3 @@ class CodePrinter { return NUM_SPACES * indent_level; } }; - -#endif diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index a7c1bb1e5a..830c5f0d3c 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "printer/json_printer.hpp" /// Dump output to provided file diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 5a4494c586..3c69d9e348 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -1,5 +1,11 @@ -#ifndef _JSON_PRINTER_HPP_ -#define _JSON_PRINTER_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <fstream> #include <iostream> @@ -56,12 +62,12 @@ class JSONPrinter { JSONPrinter(const std::string& filename); /// By default dump output to std::cout - JSONPrinter() : result(new std::ostream(std::cout.rdbuf())) { - } + JSONPrinter() + : result(new std::ostream(std::cout.rdbuf())) {} // Dump output to stringstream - JSONPrinter(std::ostream& os) : result(new std::ostream(os.rdbuf())) { - } + JSONPrinter(std::ostream& os) + : result(new std::ostream(os.rdbuf())) {} ~JSONPrinter() { flush(); @@ -81,5 +87,3 @@ class JSONPrinter { expand = flag; } }; - -#endif diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index 8c0c746eea..5832bb4fa0 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "printer/nmodl_printer.hpp" #include "utils/string_utils.hpp" diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp index d782986426..a72add00f3 100644 --- a/src/nmodl/printer/nmodl_printer.hpp +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_PRINTER_HPP_ -#define _NMODL_PRINTER_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <fstream> #include <iostream> @@ -25,10 +31,10 @@ class NMODLPrinter { size_t indent_level = 0; public: - NMODLPrinter() : result(new std::ostream(std::cout.rdbuf())) { - } - NMODLPrinter(std::ostream& stream) : result(new std::ostream(stream.rdbuf())) { - } + NMODLPrinter() + : result(new std::ostream(std::cout.rdbuf())) {} + NMODLPrinter(std::ostream& stream) + : result(new std::ostream(stream.rdbuf())) {} NMODLPrinter(const std::string& filename); ~NMODLPrinter() { @@ -49,5 +55,3 @@ class NMODLPrinter { /// and decreases indentation level void pop_level(); }; - -#endif diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 5f6f65e072..fcb6bc87f9 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -1,47 +1,43 @@ -#============================================================================= +# ============================================================================= # pybind targets -#============================================================================= -set_source_files_properties( ${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) - +# ============================================================================= +set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) set(PYNMODL_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) set(PYNMODL_HEADERS - ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp - ${PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) - - -pybind11_add_module(_nmodl ${PYNMODL_HEADERS} ${PYNMODL_SOURCES} - $<TARGET_OBJECTS:symtab_obj> - $<TARGET_OBJECTS:visitor_obj> - $<TARGET_OBJECTS:lexer_obj> - $<TARGET_OBJECTS:util_obj> - $<TARGET_OBJECTS:printer_obj> - ) + ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp + ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp + ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp + ${PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) + +pybind11_add_module(_nmodl + ${PYNMODL_HEADERS} + ${PYNMODL_SOURCES} + $<TARGET_OBJECTS:symtab_obj> + $<TARGET_OBJECTS:visitor_obj> + $<TARGET_OBJECTS:lexer_obj> + $<TARGET_OBJECTS:util_obj> + $<TARGET_OBJECTS:printer_obj>) add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) add_custom_command(TARGET _nmodl POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${PROJECT_SOURCE_DIR}/nmodl - ${CMAKE_BINARY_DIR}/nmodl - COMMAND ${CMAKE_COMMAND} -E copy_if_different - $<TARGET_FILE:_nmodl> - ${CMAKE_BINARY_DIR}/nmodl - ) + COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/nmodl + ${CMAKE_BINARY_DIR}/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:_nmodl> + ${CMAKE_BINARY_DIR}/nmodl) -#============================================================================= +# ============================================================================= # Install python binding components -#============================================================================= +# ============================================================================= install(TARGETS _nmodl DESTINATION lib/python/nmodl) install(DIRECTORY ${PROJECT_SOURCE_DIR}/nmodl DESTINATION lib/python/) diff --git a/src/nmodl/pybind/pybind_utils.hpp b/src/nmodl/pybind/pybind_utils.hpp index fdc5ecaedd..bdac60644c 100644 --- a/src/nmodl/pybind/pybind_utils.hpp +++ b/src/nmodl/pybind/pybind_utils.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <memory> @@ -84,13 +91,15 @@ class pythonibuf: public std::streambuf { } // namespace pybind11 class VisitorOStreamResources { -protected: + protected: std::unique_ptr<pybind11::detail::pythonbuf> buf; std::unique_ptr<std::ostream> ostream; -public: + + public: VisitorOStreamResources() = default; - VisitorOStreamResources(pybind11::object object) : buf(new pybind11::detail::pythonbuf(object)), - ostream(new std::ostream(buf.get())) {} + VisitorOStreamResources(pybind11::object object) + : buf(new pybind11::detail::pythonbuf(object)) + , ostream(new std::ostream(buf.get())) {} void flush() { ostream->flush(); } diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 865178b5c9..da5cd2759a 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <memory> #include <pybind11/iostream.h> #include <pybind11/pybind11.h> @@ -43,10 +50,8 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { .def("ast", &PyDriver::ast); m_nmodl.def("to_nmodl", nmodl::to_nmodl); - m_nmodl.def("to_json", nmodl::to_json, - py::arg("node"), - py::arg("compact") = false, - py::arg("expand") = false); + m_nmodl.def("to_json", nmodl::to_json, py::arg("node"), py::arg("compact") = false, + py::arg("expand") = false); init_visitor_module(m_nmodl); init_ast_module(m_nmodl); diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index b9da83dbba..dc9323953e 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -1,30 +1,24 @@ -#============================================================================= +# ============================================================================= # Visitor sources -#============================================================================= +# ============================================================================= set(SYMTAB_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp) set(SYMTAB_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp - ) + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp) -#============================================================================= +# ============================================================================= # Symbol table library -#============================================================================= -add_library(symtab_obj - OBJECT - ${SYMTAB_SOURCES} ${SYMTAB_HEADERS}) +# ============================================================================= +add_library(symtab_obj OBJECT ${SYMTAB_SOURCES} ${SYMTAB_HEADERS}) set_property(TARGET symtab_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(symtab_obj lexer_obj) -add_library(symtab - STATIC - $<TARGET_OBJECTS:symtab_obj>) +add_library(symtab STATIC $<TARGET_OBJECTS:symtab_obj>) add_dependencies(symtab lexer) diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 479bf9b88e..41ed98b046 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "fmt/format.h" #include "symtab/symbol.hpp" @@ -7,62 +14,62 @@ using namespace fmt::literals; namespace symtab { - /** if symbol has only extern_token property : this is true - * for symbols which are external variables from neuron - * like v, t, dt etc. - * \todo: check if we should check two properties using has_property - * instead of exact comparisons - */ - bool Symbol::is_external_symbol_only() { - return (properties == NmodlType::extern_neuron_variable || - properties == NmodlType::extern_method); - } - - /// check if symbol has any of the common properties - - bool Symbol::has_properties(NmodlType new_properties) { - return static_cast<bool>(properties & new_properties); - } - - /// check if symbol has all of the given properties - bool Symbol::has_all_properties(NmodlType new_properties) { - return ((properties & new_properties) == new_properties); - } - - - /// check if symbol has any of the status - bool Symbol::has_any_status(Status new_status) { - return static_cast<bool>(status & new_status); +/** if symbol has only extern_token property : this is true + * for symbols which are external variables from neuron + * like v, t, dt etc. + * \todo: check if we should check two properties using has_property + * instead of exact comparisons + */ +bool Symbol::is_external_symbol_only() { + return (properties == NmodlType::extern_neuron_variable || + properties == NmodlType::extern_method); +} + +/// check if symbol has any of the common properties + +bool Symbol::has_properties(NmodlType new_properties) { + return static_cast<bool>(properties & new_properties); +} + +/// check if symbol has all of the given properties +bool Symbol::has_all_properties(NmodlType new_properties) { + return ((properties & new_properties) == new_properties); +} + + +/// check if symbol has any of the status +bool Symbol::has_any_status(Status new_status) { + return static_cast<bool>(status & new_status); +} + +/// check if symbol has all of the status +bool Symbol::has_all_status(Status new_status) { + return ((status & new_status) == new_status); +} + +/// add new properties to symbol with bitwise or +void Symbol::add_properties(NmodlType new_properties) { + properties |= new_properties; +} + +void Symbol::add_property(NmodlType property) { + properties |= property; +} + +/** Prime variable will appear in different block and could have + * multiple derivative orders. We have to store highest order. + * + * \todo Check if analysis passes need more information. + */ +void Symbol::set_order(int new_order) { + if (new_order > order) { + order = new_order; } +} - /// check if symbol has all of the status - bool Symbol::has_all_status(Status new_status) { - return ((status & new_status) == new_status); - } - - /// add new properties to symbol with bitwise or - void Symbol::add_properties(NmodlType new_properties) { - properties |= new_properties; - } - - void Symbol::add_property(NmodlType property) { - properties |= property; - } - - /** Prime variable will appear in different block and could have - * multiple derivative orders. We have to store highest order. - * - * \todo Check if analysis passes need more information. - */ - void Symbol::set_order(int new_order) { - if (new_order > order) { - order = new_order; - } - } - - /// check if symbol is of variable type in nmodl - bool Symbol::is_variable() { - // clang-format off +/// check if symbol is of variable type in nmodl +bool Symbol::is_variable() { + // clang-format off NmodlType var_properties = NmodlType::local_var | NmodlType::global_var | NmodlType::range_var @@ -78,19 +85,19 @@ namespace symtab { | NmodlType::section_var | NmodlType::argument | NmodlType::extern_neuron_variable; - // clang-format on - return has_properties(var_properties); + // clang-format on + return has_properties(var_properties); +} + +std::string Symbol::to_string() { + std::string s(name); + if (properties != NmodlType::empty) { + s += " [Properties : {}]"_format(::to_string(properties)); } - - std::string Symbol::to_string() { - std::string s(name); - if (properties != NmodlType::empty) { - s += " [Properties : {}]"_format(::to_string(properties)); - } - if (status != Status::empty) { - s += " [Status : {}]"_format(::to_string(status)); - } - return s; + if (status != Status::empty) { + s += " [Status : {}]"_format(::to_string(status)); } + return s; +} } // namespace symtab diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 676d4b9e6e..0aa9d2ca21 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_SYMBOL_HPP_ -#define _NMODL_SYMBOL_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <memory> @@ -8,247 +14,248 @@ #include "symtab/symbol_properties.hpp" namespace ast { - class AST; +class AST; } namespace symtab { - /** - * \class Symbol - * \brief Represent symbol in symbol table - * - * Symbol table generator pass visit the AST and insert symbol for - * each node into symbol table. Symbol could appear multiple times - * in a block or different global blocks. NmodlType object has all - * nmodl properties information. - * - * \todo Multiple tokens (i.e. location information) for symbol should - * be tracked - * \todo Scope information should be more than just string - * \todo Perf block should track information about all usage of the symbol - * (would be helpful for perf modeling) - */ +/** + * \class Symbol + * \brief Represent symbol in symbol table + * + * Symbol table generator pass visit the AST and insert symbol for + * each node into symbol table. Symbol could appear multiple times + * in a block or different global blocks. NmodlType object has all + * nmodl properties information. + * + * \todo Multiple tokens (i.e. location information) for symbol should + * be tracked + * \todo Scope information should be more than just string + * \todo Perf block should track information about all usage of the symbol + * (would be helpful for perf modeling) + */ - class Symbol { - private: - /// name of the symbol - std::string name; +class Symbol { + private: + /// name of the symbol + std::string name; - /// original name in case of renaming - std::string renamed_from; + /// original name in case of renaming + std::string renamed_from; - /// unique id or index position when symbol is inserted into specific table - int id = 0; + /// unique id or index position when symbol is inserted into specific table + int id = 0; - /// ast node where symbol encountered first time - /// node is passed from visitor and hence we have - /// raw pointer instead of shared_ptr - ast::AST* node = nullptr; + /// ast node where symbol encountered first time + /// node is passed from visitor and hence we have + /// raw pointer instead of shared_ptr + ast::AST* node = nullptr; - /// token for position information - ModToken token; + /// token for position information + ModToken token; - /// properties of symbol from whole mod file - syminfo::NmodlType properties{syminfo::NmodlType::empty}; + /// properties of symbol from whole mod file + syminfo::NmodlType properties{syminfo::NmodlType::empty}; - /// status of symbol during various passes - syminfo::Status status{syminfo::Status::empty}; + /// status of symbol during various passes + syminfo::Status status{syminfo::Status::empty}; - /// scope of the symbol (block name) - std::string scope; + /// scope of the symbol (block name) + std::string scope; - /// number of times symbol is read - int read_count = 0; + /// number of times symbol is read + int read_count = 0; - /// number of times symbol is written - int write_count = 0; + /// number of times symbol is written + int write_count = 0; - /// order of derivative (for prime node) - int order = 0; + /// order of derivative (for prime node) + int order = 0; - /// order in which symbol arrives - int definition_order = -1; + /// order in which symbol arrives + int definition_order = -1; - /// value (for parameters, constant etc) - std::shared_ptr<double> value; + /// value (for parameters, constant etc) + std::shared_ptr<double> value; - /// true if array variable - bool array = false; + /// true if array variable + bool array = false; - /// number of elements - int length = 1; + /// number of elements + int length = 1; - // number of values in case of table variable - int num_values = 0; + // number of values in case of table variable + int num_values = 0; - public: - Symbol() = delete; + public: + Symbol() = delete; - Symbol(std::string name, ast::AST* node) : name(name), node(node) { - } + Symbol(std::string name, ast::AST* node) + : name(name) + , node(node) {} - Symbol(std::string name, ModToken token) : name(name), token(token) { - } + Symbol(std::string name, ModToken token) + : name(name) + , token(token) {} - Symbol(std::string name, ast::AST* node, ModToken token) - : name(name), node(node), token(token) { - } + Symbol(std::string name, ast::AST* node, ModToken token) + : name(name) + , node(node) + , token(token) {} - void set_scope(std::string s) { - scope = s; - } + void set_scope(std::string s) { + scope = s; + } - std::string get_name() { - return name; - } + std::string get_name() { + return name; + } - int get_id() { - return id; - } + int get_id() { + return id; + } - void set_id(int i) { - id = i; - } + void set_id(int i) { + id = i; + } - void set_name(std::string new_name) { - if (renamed_from.empty()) { - renamed_from = name; - } - name = new_name; + void set_name(std::string new_name) { + if (renamed_from.empty()) { + renamed_from = name; } + name = new_name; + } - std::string get_scope() { - return scope; - } + std::string get_scope() { + return scope; + } - syminfo::NmodlType get_properties() { - return properties; - } + syminfo::NmodlType get_properties() { + return properties; + } - syminfo::Status get_status() { - return status; - } + syminfo::Status get_status() { + return status; + } - void read() { - read_count++; - } + void read() { + read_count++; + } - void write() { - write_count++; - } + void write() { + write_count++; + } - ast::AST* get_node() { - return node; - } + ast::AST* get_node() { + return node; + } - ModToken get_token() { - return token; - } + ModToken get_token() { + return token; + } - int get_read_count() const { - return read_count; - } + int get_read_count() const { + return read_count; + } - int get_write_count() const { - return write_count; - } + int get_write_count() const { + return write_count; + } - int get_definition_order() const { - return definition_order; - } + int get_definition_order() const { + return definition_order; + } - bool is_external_symbol_only(); + bool is_external_symbol_only(); - /// \todo : rename to has_any_property - bool has_properties(syminfo::NmodlType new_properties); + /// \todo : rename to has_any_property + bool has_properties(syminfo::NmodlType new_properties); - bool has_all_properties(syminfo::NmodlType new_properties); + bool has_all_properties(syminfo::NmodlType new_properties); - bool has_any_status(syminfo::Status new_status); + bool has_any_status(syminfo::Status new_status); - bool has_all_status(syminfo::Status new_status); + bool has_all_status(syminfo::Status new_status); - void add_properties(syminfo::NmodlType new_properties); + void add_properties(syminfo::NmodlType new_properties); - void add_property(syminfo::NmodlType property); + void add_property(syminfo::NmodlType property); - void inlined() { - status |= syminfo::Status::inlined; - } + void inlined() { + status |= syminfo::Status::inlined; + } - void mark_created() { - status |= syminfo::Status::created; - } + void mark_created() { + status |= syminfo::Status::created; + } - void mark_renamed() { - status |= syminfo::Status::renamed; - } + void mark_renamed() { + status |= syminfo::Status::renamed; + } - void mark_localized() { - status |= syminfo::Status::localized; - } + void mark_localized() { + status |= syminfo::Status::localized; + } - void mark_thread_safe() { - status |= syminfo::Status::thread_safe; - } + void mark_thread_safe() { + status |= syminfo::Status::thread_safe; + } - void created_from_state() { - mark_created(); - status |= syminfo::Status::from_state; - } + void created_from_state() { + mark_created(); + status |= syminfo::Status::from_state; + } - void set_order(int new_order); + void set_order(int new_order); - void set_definition_order(int order) { - definition_order = order; - } + void set_definition_order(int order) { + definition_order = order; + } - void set_value(double val) { - value = std::make_shared<double>(val); - } + void set_value(double val) { + value = std::make_shared<double>(val); + } - void set_as_array(int len) { - array = true; - length = len; - } + void set_as_array(int len) { + array = true; + length = len; + } - bool is_array() { - return array; - } + bool is_array() { + return array; + } - void set_length(int len) { - length = len; - } + void set_length(int len) { + length = len; + } - int get_length() { - return length; - } + int get_length() { + return length; + } - int get_num_values() { - return num_values; - } + int get_num_values() { + return num_values; + } - void set_num_values(int n) { - num_values = n; - } + void set_num_values(int n) { + num_values = n; + } - std::string get_original_name() { - return renamed_from; - } + std::string get_original_name() { + return renamed_from; + } - std::shared_ptr<double> get_value() { - return value; - } + std::shared_ptr<double> get_value() { + return value; + } - void set_original_name(std::string new_name) { - renamed_from = new_name; - } + void set_original_name(std::string new_name) { + renamed_from = new_name; + } - std::string to_string(); + std::string to_string(); - bool is_variable(); - }; + bool is_variable(); +}; } // namespace symtab - -#endif diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 9a9e1bbbda..88a2ca4529 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <string> #include <vector> diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index c33a0254e5..c0bb7ab094 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -1,5 +1,11 @@ -#ifndef SYMBOL_PROPERTIES_HPP -#define SYMBOL_PROPERTIES_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <sstream> @@ -10,201 +16,201 @@ using enum_type = long long; namespace syminfo { - /** kind of symbol */ - enum class DeclarationType : enum_type { - /** variable */ - variable, +/** kind of symbol */ +enum class DeclarationType : enum_type { + /** variable */ + variable, - /** function */ - function - }; + /** function */ + function +}; - /** scope within a mod file */ - enum class Scope : enum_type { - /** local variable */ - local, +/** scope within a mod file */ +enum class Scope : enum_type { + /** local variable */ + local, - /** global variable */ - global, + /** global variable */ + global, - /** neuron variable */ - neuron, + /** neuron variable */ + neuron, - /** extern variable */ - external - }; + /** extern variable */ + external +}; - /** state during various compiler passes */ - enum class Status : enum_type { +/** state during various compiler passes */ +enum class Status : enum_type { - empty = 0, + empty = 0, - /** converted to local */ - localized = 1L << 0, + /** converted to local */ + localized = 1L << 0, - /** converted to global */ - globalized = 1L << 1, + /** converted to global */ + globalized = 1L << 1, - /** inlined */ - inlined = 1L << 2, + /** inlined */ + inlined = 1L << 2, - /** renamed */ - renamed = 1L << 3, + /** renamed */ + renamed = 1L << 3, - /** created */ - created = 1L << 4, + /** created */ + created = 1L << 4, - /** derived from state */ - from_state = 1L << 5, + /** derived from state */ + from_state = 1L << 5, - /** variable marked as thread safe */ - thread_safe = 1L << 6 - }; + /** variable marked as thread safe */ + thread_safe = 1L << 6 +}; - /** usage of mod file as array or scalar */ - enum class VariableType : enum_type { - /** scalar / single value */ - scalar, +/** usage of mod file as array or scalar */ +enum class VariableType : enum_type { + /** scalar / single value */ + scalar, - /** vector type */ - array - }; + /** vector type */ + array +}; - /** variable usage within a mod file */ - enum class Access : enum_type { - /** variable is ready only */ - read = 1L << 0, +/** variable usage within a mod file */ +enum class Access : enum_type { + /** variable is ready only */ + read = 1L << 0, - /** variable is written only */ - write = 1L << 1 - }; + /** variable is written only */ + write = 1L << 1 +}; - /** @brief nmodl variable properties - * - * Certain variables/symbols specified in various places in the - * same mod file. For example, RANGE variable could be in PARAMETER - * bloc, ASSIGNED block etc. In this case, the symbol in global - * scope need to have multiple properties. This is also important - * while code generation. Hence, in addition to AST node pointer, - * we define NmodlType so that we can set multiple properties. - * Note that AST pointer is no longer pointing to all pointers. - * Same applies for ModToken. - * - * These is some redundancy between NmodlType and other enums like - * DeclarationType and Scope. But as AST will become more abstract - * from NMODL (towards C/C++) then other types will be useful. - * - * \todo Rename param_assign to parameter_var - * \todo Reaching max limit (31), need to refactor all block types - * into separate type - */ - enum class NmodlType : enum_type { +/** @brief nmodl variable properties + * + * Certain variables/symbols specified in various places in the + * same mod file. For example, RANGE variable could be in PARAMETER + * bloc, ASSIGNED block etc. In this case, the symbol in global + * scope need to have multiple properties. This is also important + * while code generation. Hence, in addition to AST node pointer, + * we define NmodlType so that we can set multiple properties. + * Note that AST pointer is no longer pointing to all pointers. + * Same applies for ModToken. + * + * These is some redundancy between NmodlType and other enums like + * DeclarationType and Scope. But as AST will become more abstract + * from NMODL (towards C/C++) then other types will be useful. + * + * \todo Rename param_assign to parameter_var + * \todo Reaching max limit (31), need to refactor all block types + * into separate type + */ +enum class NmodlType : enum_type { - empty = 0, + empty = 0, - /** Local Variable */ - local_var = 1L << 0, + /** Local Variable */ + local_var = 1L << 0, - /** Global Variable */ - global_var = 1L << 1, + /** Global Variable */ + global_var = 1L << 1, - /** Range Variable */ - range_var = 1L << 2, + /** Range Variable */ + range_var = 1L << 2, - /** Parameter Variable */ - param_assign = 1L << 3, + /** Parameter Variable */ + param_assign = 1L << 3, - /** Pointer Type */ - pointer_var = 1L << 4, + /** Pointer Type */ + pointer_var = 1L << 4, - /** Bbcorepointer Type */ - bbcore_pointer_var = 1L << 5, + /** Bbcorepointer Type */ + bbcore_pointer_var = 1L << 5, - /** Extern Type */ - extern_var = 1L << 6, + /** Extern Type */ + extern_var = 1L << 6, - /** Prime Type */ - prime_name = 1L << 7, + /** Prime Type */ + prime_name = 1L << 7, - /** Dependent Def */ - dependent_def = 1L << 8, + /** Dependent Def */ + dependent_def = 1L << 8, - /** Unit Def */ - unit_def = 1L << 9, + /** Unit Def */ + unit_def = 1L << 9, - /** Read Ion */ - read_ion_var = 1L << 10, + /** Read Ion */ + read_ion_var = 1L << 10, - /** Write Ion */ - write_ion_var = 1L << 11, + /** Write Ion */ + write_ion_var = 1L << 11, - /** Non Specific Current */ - nonspecific_cur_var = 1L << 12, + /** Non Specific Current */ + nonspecific_cur_var = 1L << 12, - /** Electrode Current */ - electrode_cur_var = 1L << 13, + /** Electrode Current */ + electrode_cur_var = 1L << 13, - /** Section Type */ - section_var = 1L << 14, + /** Section Type */ + section_var = 1L << 14, - /** Argument Type */ - argument = 1L << 15, + /** Argument Type */ + argument = 1L << 15, - /** Function Type */ - function_block = 1L << 16, + /** Function Type */ + function_block = 1L << 16, - /** Procedure Type */ - procedure_block = 1L << 17, + /** Procedure Type */ + procedure_block = 1L << 17, - /** Derivative Block */ - derivative_block = 1L << 18, + /** Derivative Block */ + derivative_block = 1L << 18, - /** Linear Block */ - linear_block = 1L << 19, + /** Linear Block */ + linear_block = 1L << 19, - /** NonLinear Block */ - non_linear_block = 1L << 20, + /** NonLinear Block */ + non_linear_block = 1L << 20, - /** constant variable */ - constant_var = 1L << 21, + /** constant variable */ + constant_var = 1L << 21, - /** Partial Block */ - partial_block = 1L << 22, + /** Partial Block */ + partial_block = 1L << 22, - /** Kinetic Block */ - kinetic_block = 1L << 23, + /** Kinetic Block */ + kinetic_block = 1L << 23, - /** FunctionTable Block */ - function_table_block = 1L << 24, + /** FunctionTable Block */ + function_table_block = 1L << 24, - /** factor in unit block */ - factor_def = 1L << 25, + /** factor in unit block */ + factor_def = 1L << 25, - /** neuron variable accessible in mod file */ - extern_neuron_variable = 1L << 26, + /** neuron variable accessible in mod file */ + extern_neuron_variable = 1L << 26, - /** neuron solver methods and math functions */ - extern_method = 1L << 27, + /** neuron solver methods and math functions */ + extern_method = 1L << 27, - /** state variable */ - state_var = 1L << 28, + /** state variable */ + state_var = 1L << 28, - /** need to solve : used in solve statement */ - to_solve = 1L << 29, + /** need to solve : used in solve statement */ + to_solve = 1L << 29, - /** ion type */ - useion = 1L << 30, + /** ion type */ + useion = 1L << 30, - /** variable is used in table statement */ - table_statement_var = 1L << 31, + /** variable is used in table statement */ + table_statement_var = 1L << 31, - /** variable is used in table as dependent */ - table_dependent_var = 1L << 32, + /** variable is used in table as dependent */ + table_dependent_var = 1L << 32, - /** Discrete Block */ - discrete_block = 1L << 33 - }; + /** Discrete Block */ + discrete_block = 1L << 33 +}; } // namespace syminfo @@ -238,12 +244,10 @@ template <typename T> std::string to_string(const T& obj) { auto elements = to_string_vector(obj); std::string text; - for (const auto& element : elements) { + for (const auto& element: elements) { text += element + " "; } // remove extra whitespace at the end stringutils::trim(text); return text; -} - -#endif +} \ No newline at end of file diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index a6b61507ce..5d05eee744 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -1,520 +1,524 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <utility> #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" -#include "utils/table_data.hpp" #include "utils/logger.hpp" +#include "utils/table_data.hpp" using namespace ast; using namespace syminfo; namespace symtab { - int SymbolTable::Table::counter = 0; +int SymbolTable::Table::counter = 0; - /** - * Insert symbol into current symbol table. There are certain - * cases where we were getting re-insertion errors. - */ - void SymbolTable::Table::insert(const std::shared_ptr<Symbol>& symbol) { - std::string name = symbol->get_name(); - if (lookup(name) != nullptr) { - throw std::runtime_error("Trying to re-insert symbol " + name); - } - symbol->set_id(counter++); - symbols.push_back(symbol); +/** + * Insert symbol into current symbol table. There are certain + * cases where we were getting re-insertion errors. + */ +void SymbolTable::Table::insert(const std::shared_ptr<Symbol>& symbol) { + std::string name = symbol->get_name(); + if (lookup(name) != nullptr) { + throw std::runtime_error("Trying to re-insert symbol " + name); } + symbol->set_id(counter++); + symbols.push_back(symbol); +} - std::shared_ptr<Symbol> SymbolTable::Table::lookup(const std::string& name) const { - for (const auto& symbol : symbols) { - if (symbol->get_name() == name) { - return symbol; - } +std::shared_ptr<Symbol> SymbolTable::Table::lookup(const std::string& name) const { + for (const auto& symbol: symbols) { + if (symbol->get_name() == name) { + return symbol; } - return nullptr; } + return nullptr; +} - SymbolTable::SymbolTable(const SymbolTable& table) { - symtab_name = table.name(); - global = table.global_scope(); - node = nullptr; - parent = nullptr; - } +SymbolTable::SymbolTable(const SymbolTable& table) { + symtab_name = table.name(); + global = table.global_scope(); + node = nullptr; + parent = nullptr; +} - std::string SymbolTable::type() const { - return node->get_node_type_name(); - } +std::string SymbolTable::type() const { + return node->get_node_type_name(); +} - bool SymbolTable::is_method_defined(const std::string& name) const { - auto symbol = lookup_in_scope(name); - if (symbol != nullptr) { - auto node = symbol->get_node(); - if (node != nullptr) { - if (node->is_procedure_block() || node->is_function_block()) { - return true; - } +bool SymbolTable::is_method_defined(const std::string& name) const { + auto symbol = lookup_in_scope(name); + if (symbol != nullptr) { + auto node = symbol->get_node(); + if (node != nullptr) { + if (node->is_procedure_block() || node->is_function_block()) { + return true; } } - return false; } + return false; +} + +std::string SymbolTable::position() const { + auto token = node->get_token(); + std::string position; + if (token != nullptr) { + position = token->position(); + } else { + position = ModToken().position(); + } + return position; +} - std::string SymbolTable::position() const { - auto token = node->get_token(); - std::string position; - if (token != nullptr) { - position = token->position(); + +/// insert new symbol table of one of the children block +void SymbolTable::insert_table(const std::string& name, std::shared_ptr<SymbolTable> table) { + if (children.find(name) != children.end()) { + throw std::runtime_error("Trying to re-insert SymbolTable " + name); + } + children[name] = std::move(table); +} + + +/// return all symbol having any of the provided properties +std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( + NmodlType properties, + bool all) { + std::vector<std::shared_ptr<Symbol>> variables; + for (auto& symbol: table.symbols) { + if (all) { + if (symbol->has_all_properties(properties)) { + variables.push_back(symbol); + } } else { - position = ModToken().position(); + if (symbol->has_properties(properties)) { + variables.push_back(symbol); + } } - return position; } + return variables; +} - - /// insert new symbol table of one of the children block - void SymbolTable::insert_table(const std::string& name, std::shared_ptr<SymbolTable> table) { - if (children.find(name) != children.end()) { - throw std::runtime_error("Trying to re-insert SymbolTable " + name); - } - children[name] = std::move(table); - } - - - /// return all symbol having any of the provided properties - std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( - NmodlType properties, - bool all) { - std::vector<std::shared_ptr<Symbol>> variables; - for (auto& symbol : table.symbols) { - if (all) { - if (symbol->has_all_properties(properties)) { - variables.push_back(symbol); - } - } else { - if (symbol->has_properties(properties)) { - variables.push_back(symbol); - } - } +/// return all symbol which has all "with" properties and none of the "without" properties +std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables(NmodlType with, NmodlType without) { + auto variables = get_variables_with_properties(with, true); + decltype(variables) result; + for (auto& variable: variables) { + if (!variable->has_properties(without)) { + result.push_back(variable); } - return variables; } + return result; +} - /// return all symbol which has all "with" properties and none of the "without" properties - std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables(NmodlType with, - NmodlType without) { - auto variables = get_variables_with_properties(with, true); - decltype(variables) result; - for (auto& variable : variables) { - if (!variable->has_properties(without)) { - result.push_back(variable); + +std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_status(Status status, + bool all) { + std::vector<std::shared_ptr<Symbol>> variables; + for (auto& symbol: table.symbols) { + if (all) { + if (symbol->has_all_status(status)) { + variables.push_back(symbol); } - } - return result; - } - - - std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_status(Status status, - bool all) { - std::vector<std::shared_ptr<Symbol>> variables; - for (auto& symbol : table.symbols) { - if (all) { - if (symbol->has_all_status(status)) { - variables.push_back(symbol); - } - } else { - if (symbol->has_any_status(status)) { - variables.push_back(symbol); - } + } else { + if (symbol->has_any_status(status)) { + variables.push_back(symbol); } } - return variables; } + return variables; +} + + +/** + * Check if current symbol table is in global scope + * + * We create program scope at the top level and it has global scope. + * It contains neuron variables like t, dt, celsius etc. Then each + * nmodl block defining variables are added under this program's symbol + * table. Hence there are multiple levels of global scopes. In this + * helper function we make sure current block as well as it's parent are + * under global scopes. + */ +bool SymbolTable::under_global_scope() { + bool global_scope = global; + auto parent_table = parent; + + // traverse all parent blocks to make sure everyone is global + while (global_scope && (parent_table != nullptr)) { + parent_table = parent_table->parent; + if (parent_table != nullptr) { + global_scope = parent_table->global_scope(); + } + } + return global_scope; +} - /** - * Check if current symbol table is in global scope - * - * We create program scope at the top level and it has global scope. - * It contains neuron variables like t, dt, celsius etc. Then each - * nmodl block defining variables are added under this program's symbol - * table. Hence there are multiple levels of global scopes. In this - * helper function we make sure current block as well as it's parent are - * under global scopes. - */ - bool SymbolTable::under_global_scope() { - bool global_scope = global; - auto parent_table = parent; - - // traverse all parent blocks to make sure everyone is global - while (global_scope && (parent_table != nullptr)) { - parent_table = parent_table->parent; - if (parent_table != nullptr) { - global_scope = parent_table->global_scope(); - } - } - return global_scope; +/// lookup for symbol in current scope as well as all parents +std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(const std::string& name) const { + auto symbol = table.lookup(name); + if (!symbol && (parent != nullptr)) { + symbol = parent->lookup_in_scope(name); } + return symbol; +} +/// lookup in current sytab as well as all parent symbol tables +std::shared_ptr<Symbol> ModelSymbolTable::lookup(const std::string& name) { + if (current_symtab == nullptr) { + throw std::logic_error("Lookup with previous symtab = nullptr "); + } - /// lookup for symbol in current scope as well as all parents - std::shared_ptr<Symbol> SymbolTable::lookup_in_scope(const std::string& name) const { - auto symbol = table.lookup(name); - if (!symbol && (parent != nullptr)) { - symbol = parent->lookup_in_scope(name); - } + auto symbol = current_symtab->lookup(name); + if (symbol) { return symbol; } - /// lookup in current sytab as well as all parent symbol tables - std::shared_ptr<Symbol> ModelSymbolTable::lookup(const std::string& name) { - if (current_symtab == nullptr) { - throw std::logic_error("Lookup with previous symtab = nullptr "); - } - - auto symbol = current_symtab->lookup(name); + // check into all parent symbol tables + auto parent = current_symtab->get_parent_table(); + while (parent != nullptr) { + symbol = parent->lookup(name); if (symbol) { - return symbol; + break; } + parent = parent->get_parent_table(); + } + return symbol; +} + + +/** + * Emit warning message for shadowing definition or throw an exception + * if variable is being redefined in the same block. + */ +void ModelSymbolTable::emit_message(const std::shared_ptr<Symbol>& first, + const std::shared_ptr<Symbol>& second, + bool redefinition) { + auto node = first->get_node(); + std::string name = first->get_name(); + auto properties = to_string(second->get_properties()); + std::string type = "UNKNOWN"; + if (node != nullptr) { + type = node->get_node_type_name(); + } - // check into all parent symbol tables - auto parent = current_symtab->get_parent_table(); - while (parent != nullptr) { - symbol = parent->lookup(name); - if (symbol) { - break; - } - parent = parent->get_parent_table(); - } + if (redefinition) { + std::string msg = "Re-declaration of " + name + " [" + type + "]"; + msg += "<" + properties + "> in " + current_symtab->name(); + msg += " with one in " + second->get_scope(); + throw std::runtime_error(msg); + } + std::string msg = "SYMTAB :: " + name + " [" + type + "] in "; + msg += current_symtab->name() + " shadows <" + properties; + msg += "> definition in " + second->get_scope(); + logger->warn(msg); +} + + +/** + * Insert symbol in the update mode i.e. symbol table is previously created + * and we are adding new symbol table. + * + * We set status as "created" because missing symbol means the variable is + * added by some intermediate passes. + * + * Consider inlining pass which creates a block like: + * + * DERIVATIVE states() { + * LOCAL xx + * { + * LOCAL xx + * } + * } + * + * Second xx is not added into symbol table (and hence not visible + * to other passes like local renamer). In this case we have to do + * local lookup in the current symtab and insert if doesn't exist. + */ +std::shared_ptr<Symbol> ModelSymbolTable::update_mode_insert( + const std::shared_ptr<Symbol>& symbol) { + symbol->set_scope(current_symtab->name()); + symbol->mark_created(); + + std::string name = symbol->get_name(); + auto search_symbol = lookup(name); + + /// if no symbol found then safe to insert + if (search_symbol == nullptr) { + current_symtab->insert(symbol); return symbol; } - - /** - * Emit warning message for shadowing definition or throw an exception - * if variable is being redefined in the same block. - */ - void ModelSymbolTable::emit_message(const std::shared_ptr<Symbol>& first, - const std::shared_ptr<Symbol>& second, - bool redefinition) { - auto node = first->get_node(); - std::string name = first->get_name(); - auto properties = to_string(second->get_properties()); - std::string type = "UNKNOWN"; - if (node != nullptr) { - type = node->get_node_type_name(); - } - - if (redefinition) { - std::string msg = "Re-declaration of " + name + " [" + type + "]"; - msg += "<" + properties + "> in " + current_symtab->name(); - msg += " with one in " + second->get_scope(); - throw std::runtime_error(msg); - } - std::string msg = "SYMTAB :: " + name + " [" + type + "] in "; - msg += current_symtab->name() + " shadows <" + properties; - msg += "> definition in " + second->get_scope(); - logger->warn(msg); + /// for global scope just combine properties + if (current_symtab->global_scope()) { + search_symbol->add_properties(symbol->get_properties()); + return search_symbol; } - - /** - * Insert symbol in the update mode i.e. symbol table is previously created - * and we are adding new symbol table. - * - * We set status as "created" because missing symbol means the variable is - * added by some intermediate passes. - * - * Consider inlining pass which creates a block like: - * - * DERIVATIVE states() { - * LOCAL xx - * { - * LOCAL xx - * } - * } - * - * Second xx is not added into symbol table (and hence not visible - * to other passes like local renamer). In this case we have to do - * local lookup in the current symtab and insert if doesn't exist. - */ - std::shared_ptr<Symbol> ModelSymbolTable::update_mode_insert( - const std::shared_ptr<Symbol>& symbol) { - symbol->set_scope(current_symtab->name()); - symbol->mark_created(); - - std::string name = symbol->get_name(); - auto search_symbol = lookup(name); - - /// if no symbol found then safe to insert - if (search_symbol == nullptr) { - current_symtab->insert(symbol); - return symbol; - } - - /// for global scope just combine properties - if (current_symtab->global_scope()) { - search_symbol->add_properties(symbol->get_properties()); - return search_symbol; - } - - /// insert into current block's symbol table - if (current_symtab->lookup(name) == nullptr) { - current_symtab->insert(symbol); - } - return symbol; + /// insert into current block's symbol table + if (current_symtab->lookup(name) == nullptr) { + current_symtab->insert(symbol); } + return symbol; +} - void ModelSymbolTable::update_order(const std::shared_ptr<Symbol>& present_symbol, - const std::shared_ptr<Symbol>& new_symbol) { - auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; +void ModelSymbolTable::update_order(const std::shared_ptr<Symbol>& present_symbol, + const std::shared_ptr<Symbol>& new_symbol) { + auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; - bool is_parameter = new_symbol->has_properties(NmodlType::param_assign); - bool is_dependent_def = new_symbol->has_properties(NmodlType::dependent_def); + bool is_parameter = new_symbol->has_properties(NmodlType::param_assign); + bool is_dependent_def = new_symbol->has_properties(NmodlType::dependent_def); - if (symbol->get_definition_order() == -1) { - if (is_parameter || is_dependent_def) { - symbol->set_definition_order(definition_order++); - } + if (symbol->get_definition_order() == -1) { + if (is_parameter || is_dependent_def) { + symbol->set_definition_order(definition_order++); } } +} - std::shared_ptr<Symbol> ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { - if (current_symtab == nullptr) { - throw std::logic_error("Can not insert symbol without entering scope"); - } - - auto search_symbol = lookup(symbol->get_name()); - update_order(search_symbol, symbol); +std::shared_ptr<Symbol> ModelSymbolTable::insert(const std::shared_ptr<Symbol>& symbol) { + if (current_symtab == nullptr) { + throw std::logic_error("Can not insert symbol without entering scope"); + } - /// handle update mode insertion - if (update_table) { - return update_mode_insert(symbol); - } + auto search_symbol = lookup(symbol->get_name()); + update_order(search_symbol, symbol); - symbol->set_scope(current_symtab->name()); + /// handle update mode insertion + if (update_table) { + return update_mode_insert(symbol); + } - // if no symbol found then safe to insert - if (search_symbol == nullptr) { - current_symtab->insert(symbol); - return symbol; - } + symbol->set_scope(current_symtab->name()); - /** - * For global symbol tables, same variable can appear in multiple - * nmodl "global" blocks. It's an error if it appears multiple times - * in the same nmodl block. To check this we compare symbol properties - * which are bit flags. If they are not same that means, we have to - * add new properties to existing symbol. If the properties are same - * that means symbol are duplicate. - */ - if (current_symtab->global_scope()) { - if (search_symbol->has_properties(symbol->get_properties())) { - emit_message(symbol, search_symbol, true); - } else { - search_symbol->add_properties(symbol->get_properties()); - } - return search_symbol; - } + // if no symbol found then safe to insert + if (search_symbol == nullptr) { + current_symtab->insert(symbol); + return symbol; + } - /** - * For non-global scopes, check if symbol that we found has same - * scope name as symbol table. This means variable is being re-declared - * within the same scope. Otherwise, there is variable with same name - * in parent scopes and it will shadow the definition. In this case just - * emit the warning and insert the symbol. - */ - if (search_symbol->get_scope() == current_symtab->name()) { + /** + * For global symbol tables, same variable can appear in multiple + * nmodl "global" blocks. It's an error if it appears multiple times + * in the same nmodl block. To check this we compare symbol properties + * which are bit flags. If they are not same that means, we have to + * add new properties to existing symbol. If the properties are same + * that means symbol are duplicate. + */ + if (current_symtab->global_scope()) { + if (search_symbol->has_properties(symbol->get_properties())) { emit_message(symbol, search_symbol, true); } else { - emit_message(symbol, search_symbol, false); - current_symtab->insert(symbol); + search_symbol->add_properties(symbol->get_properties()); } - return symbol; + return search_symbol; } - /** - * Some blocks can appear multiple times in the nmodl file. In order to distinguish - * them we simply append counter. - * \todo We should add position information to make name unique + * For non-global scopes, check if symbol that we found has same + * scope name as symbol table. This means variable is being re-declared + * within the same scope. Otherwise, there is variable with same name + * in parent scopes and it will shadow the definition. In this case just + * emit the warning and insert the symbol. */ - std::string ModelSymbolTable::get_unique_name(const std::string& name, - AST* node, - bool is_global) { - static int block_counter = 0; - std::string new_name(name); - if (is_global) { - new_name = GLOBAL_SYMTAB_NAME; - } else if (node->is_statement_block() || node->is_solve_block() || - node->is_before_block() || node->is_after_block()) { - new_name += std::to_string(block_counter++); - } - return new_name; + if (search_symbol->get_scope() == current_symtab->name()) { + emit_message(symbol, search_symbol, true); + } else { + emit_message(symbol, search_symbol, false); + current_symtab->insert(symbol); + } + return symbol; +} + + +/** + * Some blocks can appear multiple times in the nmodl file. In order to distinguish + * them we simply append counter. + * \todo We should add position information to make name unique + */ +std::string ModelSymbolTable::get_unique_name(const std::string& name, AST* node, bool is_global) { + static int block_counter = 0; + std::string new_name(name); + if (is_global) { + new_name = GLOBAL_SYMTAB_NAME; + } else if (node->is_statement_block() || node->is_solve_block() || node->is_before_block() || + node->is_after_block()) { + new_name += std::to_string(block_counter++); + } + return new_name; +} + + +/** + * Function callback at the entry of every block in nmodl file + * Every block starts a new scope and hence new symbol table is created. + * The same symbol table is returned so that visitor can store pointer to + * symbol table within a node. + */ +SymbolTable* ModelSymbolTable::enter_scope(const std::string& name, + AST* node, + bool global, + SymbolTable* node_symtab) { + if (node == nullptr) { + throw std::runtime_error("Can't enter with empty node"); } + if (node->is_breakpoint_block()) { + breakpoint_exist = true; + } /** - * Function callback at the entry of every block in nmodl file - * Every block starts a new scope and hence new symbol table is created. - * The same symbol table is returned so that visitor can store pointer to - * symbol table within a node. + * All global blocks in mod file have same global symbol table. If there + * is already symbol table setup in global scope, return the same. */ - SymbolTable* ModelSymbolTable::enter_scope(const std::string& name, - AST* node, - bool global, - SymbolTable* node_symtab) { - if (node == nullptr) { - throw std::runtime_error("Can't enter with empty node"); - } - - if (node->is_breakpoint_block()) { - breakpoint_exist = true; - } + if (symtab && global) { + return symtab.get(); + } - /** - * All global blocks in mod file have same global symbol table. If there - * is already symbol table setup in global scope, return the same. - */ - if (symtab && global) { - return symtab.get(); - } + /// statement block within global scope is part of global block itself + if (symtab && node->is_statement_block() && current_symtab->under_global_scope()) { + return symtab.get(); + } - /// statement block within global scope is part of global block itself - if (symtab && node->is_statement_block() && current_symtab->under_global_scope()) { - return symtab.get(); + if (node_symtab == nullptr || !update_table) { + auto new_name = get_unique_name(name, node, global); + auto new_symtab = std::make_shared<SymbolTable>(new_name, node, global); + new_symtab->set_parent_table(current_symtab); + if (symtab == nullptr) { + symtab = new_symtab; } - - if (node_symtab == nullptr || !update_table) { - auto new_name = get_unique_name(name, node, global); - auto new_symtab = std::make_shared<SymbolTable>(new_name, node, global); - new_symtab->set_parent_table(current_symtab); - if (symtab == nullptr) { - symtab = new_symtab; - } - if (current_symtab != nullptr) { - current_symtab->insert_table(new_name, new_symtab); - } - node_symtab = new_symtab.get(); + if (current_symtab != nullptr) { + current_symtab->insert_table(new_name, new_symtab); } - current_symtab = node_symtab; - return current_symtab; + node_symtab = new_symtab.get(); + } + current_symtab = node_symtab; + return current_symtab; +} + + +/** + * Callback at the exit of every block in nmodl file. When we reach + * program node (top most level), there is no parent block and hence + * use top level symbol table. Otherwise traverse back to parent. + */ +void ModelSymbolTable::leave_scope() { + if (current_symtab == nullptr) { + throw std::logic_error("Trying leave scope without entering"); + } + if (current_symtab != nullptr) { + current_symtab = current_symtab->get_parent_table(); + } + if (current_symtab == nullptr) { + current_symtab = symtab.get(); } +} - /** - * Callback at the exit of every block in nmodl file. When we reach - * program node (top most level), there is no parent block and hence - * use top level symbol table. Otherwise traverse back to parent. - */ - void ModelSymbolTable::leave_scope() { - if (current_symtab == nullptr) { - throw std::logic_error("Trying leave scope without entering"); - } - if (current_symtab != nullptr) { - current_symtab = current_symtab->get_parent_table(); - } - if (current_symtab == nullptr) { - current_symtab = symtab.get(); - } +/** + * Update mode is true if we want to re-use exisiting symbol table. + * If there is no symbol table constructed then we toggle mode. + */ +void ModelSymbolTable::set_mode(bool update_mode) { + if (update_mode && symtab == nullptr) { + update_mode = false; } + update_table = update_mode; + if (!update_table) { + symtab = nullptr; + current_symtab = nullptr; + } + definition_order = 0; +} - /** - * Update mode is true if we want to re-use exisiting symbol table. - * If there is no symbol table constructed then we toggle mode. - */ - void ModelSymbolTable::set_mode(bool update_mode) { - if (update_mode && symtab == nullptr) { - update_mode = false; - } - update_table = update_mode; - if (!update_table) { - symtab = nullptr; - current_symtab = nullptr; - } - definition_order = 0; - } - - - //============================================================================= - // Symbol table pretty-printing functions - //============================================================================= - - - void SymbolTable::Table::print(std::stringstream& stream, std::string title, int indent) { - if (!symbols.empty()) { - TableData table; - table.title = std::move(title); - table.headers = {"NAME", "PROPERTIES", "STATUS", "LOCATION", - "VALUE", "# READS", "# WRITES"}; - table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, - text_alignment::right, text_alignment::right}; - - for (const auto& symbol : symbols) { - auto is_external = symbol->is_external_symbol_only(); - auto read_count = symbol->get_read_count(); - auto write_count = symbol->get_write_count(); - - // do not print external symbols which are not used in the current model - if (is_external && read_count == 0 && write_count == 0) { - continue; - } - - auto name = symbol->get_name(); - if (symbol->is_array()) { - name += "[" + std::to_string(symbol->get_length()) + "]"; - } - auto position = symbol->get_token().position(); - auto properties = ::to_string(symbol->get_properties()); - auto status = ::to_string(symbol->get_status()); - auto reads = std::to_string(symbol->get_read_count()); - std::string value; - auto sym_value = symbol->get_value(); - if (sym_value) { - value = std::to_string(*sym_value); - } - auto writes = std::to_string(symbol->get_write_count()); - table.rows.push_back({name, properties, status, position, value, reads, writes}); +//============================================================================= +// Symbol table pretty-printing functions +//============================================================================= + + +void SymbolTable::Table::print(std::stringstream& stream, std::string title, int indent) { + if (!symbols.empty()) { + TableData table; + table.title = std::move(title); + table.headers = {"NAME", "PROPERTIES", "STATUS", "LOCATION", + "VALUE", "# READS", "# WRITES"}; + table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, + text_alignment::right, text_alignment::right}; + + for (const auto& symbol: symbols) { + auto is_external = symbol->is_external_symbol_only(); + auto read_count = symbol->get_read_count(); + auto write_count = symbol->get_write_count(); + + // do not print external symbols which are not used in the current model + if (is_external && read_count == 0 && write_count == 0) { + continue; + } + + auto name = symbol->get_name(); + if (symbol->is_array()) { + name += "[" + std::to_string(symbol->get_length()) + "]"; + } + auto position = symbol->get_token().position(); + auto properties = ::to_string(symbol->get_properties()); + auto status = ::to_string(symbol->get_status()); + auto reads = std::to_string(symbol->get_read_count()); + std::string value; + auto sym_value = symbol->get_value(); + if (sym_value) { + value = std::to_string(*sym_value); } - table.print(stream, indent); + auto writes = std::to_string(symbol->get_write_count()); + table.rows.push_back({name, properties, status, position, value, reads, writes}); } + table.print(stream, indent); } +} - /// construct title for symbol table - std::string SymbolTable::title() { - auto name = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; - auto location = "POSITION : " + position(); - auto scope = global ? "GLOBAL" : "LOCAL"; - return name + location + " SCOPE : " + scope; - } +/// construct title for symbol table +std::string SymbolTable::title() { + auto name = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; + auto location = "POSITION : " + position(); + auto scope = global ? "GLOBAL" : "LOCAL"; + return name + location + " SCOPE : " + scope; +} - void SymbolTable::print(std::stringstream& ss, int level) { - table.print(ss, title(), level); +void SymbolTable::print(std::stringstream& ss, int level) { + table.print(ss, title(), level); - /// when current symbol table is empty, the children - /// can be printed from the same indentation level - /// (this is to avoid unnecessary empty indentations) - auto next_level = level; - if (table.symbols.empty()) { - next_level--; - } + /// when current symbol table is empty, the children + /// can be printed from the same indentation level + /// (this is to avoid unnecessary empty indentations) + auto next_level = level; + if (table.symbols.empty()) { + next_level--; + } - /// recursively print all children tables - for (const auto& item : children) { - if (item.second->symbol_count() >= 0) { - (item.second)->print(ss, ++next_level); - next_level--; - } + /// recursively print all children tables + for (const auto& item: children) { + if (item.second->symbol_count() >= 0) { + (item.second)->print(ss, ++next_level); + next_level--; } } +} - void ModelSymbolTable::print(std::stringstream& ss) { - symtab->print(ss, 0); - } +void ModelSymbolTable::print(std::stringstream& ss) { + symtab->print(ss, 0); +} } // namespace symtab diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index a5d73f164c..f66bbece34 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_SYMTAB_HPP_ -#define _NMODL_SYMTAB_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <memory> @@ -10,242 +16,241 @@ namespace symtab { +/** + * \class SymbolTable + * \brief Represent symbol table for nmodl block + * + * Symbol Table is used to track information about every block construct + * encountered in the nmodl. In NMODL, block constructs are NEURON, + * PARAMETER NET_RECEIVE etc. Each block is considered a new scope. + * + * NMODL supports nested block definitions (i.e. nested blocks). One + * specific example of this is INITIAL block in NET_RECEIVE block. In this + * case we need multiple scopes for single top level block of NMODL. In + * the future if we enable block level scopes, we will need symbol tables + * per block. Hence we are implementing BlockSymbolTable which stores + * all symbol table information for specific NMODL block. Note that each + * BlockSymbolTable implementation is recursive because while symbol + * lookup we have to search first into local/current block. If lookup is + * unsuccessfull then we have to traverse parent blocks until the end. + * + * \todo Revisit when clone method is used and implementation of copy + * constructor + * \todo Name may not require as we have added AST node + */ +class SymbolTable { + private: /** - * \class SymbolTable - * \brief Represent symbol table for nmodl block - * - * Symbol Table is used to track information about every block construct - * encountered in the nmodl. In NMODL, block constructs are NEURON, - * PARAMETER NET_RECEIVE etc. Each block is considered a new scope. + * \class Table + * \brief Helper class for implementing symbol table * - * NMODL supports nested block definitions (i.e. nested blocks). One - * specific example of this is INITIAL block in NET_RECEIVE block. In this - * case we need multiple scopes for single top level block of NMODL. In - * the future if we enable block level scopes, we will need symbol tables - * per block. Hence we are implementing BlockSymbolTable which stores - * all symbol table information for specific NMODL block. Note that each - * BlockSymbolTable implementation is recursive because while symbol - * lookup we have to search first into local/current block. If lookup is - * unsuccessfull then we have to traverse parent blocks until the end. + * Table is used to store information about every block construct + * encountered in the nmodl file. Each symbol has name but for fast lookup, + * we create map with the associated name. * - * \todo Revisit when clone method is used and implementation of copy - * constructor - * \todo Name may not require as we have added AST node + * \todo Re-implement pretty printing */ - class SymbolTable { - private: - /** - * \class Table - * \brief Helper class for implementing symbol table - * - * Table is used to store information about every block construct - * encountered in the nmodl file. Each symbol has name but for fast lookup, - * we create map with the associated name. - * - * \todo Re-implement pretty printing - */ - class Table { - static int counter; - - public: - /// map of symbol name and associated symbol for faster lookup - std::vector<std::shared_ptr<Symbol>> symbols; - - /// insert new symbol into table - void insert(const std::shared_ptr<Symbol>& symbol); - - /// check if symbol with given name exist - std::shared_ptr<Symbol> lookup(const std::string& name) const; - - /// pretty print - void print(std::stringstream& stream, std::string title, int indent); - }; - /// name of the block - std::string symtab_name; - - /// table holding all symbols in the current block - Table table; - - /// pointer to ast node for which current block symbol created - ast::AST* node = nullptr; - - /// true if current symbol table is global. blocks like neuron, - /// parameter defines global variables and hence they go into - /// single global symbol table - bool global = false; - - /// pointer to the symbol table of parent block in the mod file - SymbolTable* parent = nullptr; - - /// symbol table for each enclosing block in the current nmodl block - /// construct. for example, for every block statement (like if, while, - /// for) within function we append new symbol table. note that this is - /// also required for nested blocks like INITIAL in NET_RECEIVE. - std::map<std::string, std::shared_ptr<SymbolTable>> children; + class Table { + static int counter; public: - SymbolTable(std::string name, ast::AST* node, bool global = false) - : symtab_name(name), node(node), global(global) { - } + /// map of symbol name and associated symbol for faster lookup + std::vector<std::shared_ptr<Symbol>> symbols; - SymbolTable(const SymbolTable& table); + /// insert new symbol into table + void insert(const std::shared_ptr<Symbol>& symbol); - std::string name() const { - return symtab_name; - } + /// check if symbol with given name exist + std::shared_ptr<Symbol> lookup(const std::string& name) const; - std::string type() const; + /// pretty print + void print(std::stringstream& stream, std::string title, int indent); + }; + /// name of the block + std::string symtab_name; - std::string title(); + /// table holding all symbols in the current block + Table table; - bool is_method_defined(const std::string& name) const; + /// pointer to ast node for which current block symbol created + ast::AST* node = nullptr; - /// todo: set token for every block from parser - std::string position() const; + /// true if current symbol table is global. blocks like neuron, + /// parameter defines global variables and hence they go into + /// single global symbol table + bool global = false; - bool global_scope() const { - return global; - } + /// pointer to the symbol table of parent block in the mod file + SymbolTable* parent = nullptr; - SymbolTable* get_parent_table() const { - return parent; - } + /// symbol table for each enclosing block in the current nmodl block + /// construct. for example, for every block statement (like if, while, + /// for) within function we append new symbol table. note that this is + /// also required for nested blocks like INITIAL in NET_RECEIVE. + std::map<std::string, std::shared_ptr<SymbolTable>> children; + + public: + SymbolTable(std::string name, ast::AST* node, bool global = false) + : symtab_name(name) + , node(node) + , global(global) {} + + SymbolTable(const SymbolTable& table); - std::string get_parent_table_name() { - return parent ? parent->name() : "None"; - } + std::string name() const { + return symtab_name; + } - std::shared_ptr<Symbol> lookup(const std::string& name) { - return table.lookup(name); - } + std::string type() const; - void insert(std::shared_ptr<Symbol> symbol) { - table.insert(symbol); - } + std::string title(); - void set_parent_table(SymbolTable* block) { - parent = block; - } + bool is_method_defined(const std::string& name) const; - int symbol_count() const { - return table.symbols.size(); - } + /// todo: set token for every block from parser + std::string position() const; - /// \todo: revisit the usage as tokens will be pointing to old nodes - SymbolTable* clone() { - return new SymbolTable(*this); - } + bool global_scope() const { + return global; + } - std::shared_ptr<Symbol> lookup_in_scope(const std::string& name) const; + SymbolTable* get_parent_table() const { + return parent; + } - std::vector<std::shared_ptr<Symbol>> get_variables(syminfo::NmodlType with, - syminfo::NmodlType without); + std::string get_parent_table_name() { + return parent ? parent->name() : "None"; + } - std::vector<std::shared_ptr<Symbol>> get_variables_with_properties( - syminfo::NmodlType properties, - bool all = false); + std::shared_ptr<Symbol> lookup(const std::string& name) { + return table.lookup(name); + } - std::vector<std::shared_ptr<Symbol>> get_variables_with_status(syminfo::Status status, - bool all = false); + void insert(std::shared_ptr<Symbol> symbol) { + table.insert(symbol); + } - bool under_global_scope(); + void set_parent_table(SymbolTable* block) { + parent = block; + } - void insert_table(const std::string& name, std::shared_ptr<SymbolTable> table); + int symbol_count() const { + return table.symbols.size(); + } - void print(std::stringstream& ss, int level); + /// \todo: revisit the usage as tokens will be pointing to old nodes + SymbolTable* clone() { + return new SymbolTable(*this); + } - std::string to_string() { - std::stringstream s; - print(s, 0); - return s.str(); - } - }; + std::shared_ptr<Symbol> lookup_in_scope(const std::string& name) const; - /** - * \class ModelSymbolTable - * \brief Represent symbol table for nmodl block - * - * SymbolTable is sufficient to hold information about all symbols in the - * mod file. It might be sufficient to keep track of global symbol tables - * and local symbol tables. But we construct symbol table using visitor - * pass. In this case we visit ast and recursively create symbol table for - * each block scope. In this case, ModelSymbolTable is provide interface - * to build symbol table with visitor. - * - * \note For included mod file it's not clear yet whether we need to maintain - * separate ModelSymbolTable. - * \note See command project in compiler teaching course for details - * - * \todo Unique name should be based on location. Use ModToken to get position. - */ - class ModelSymbolTable { - private: - /// symbol table for mod file (always top level symbol table) - std::shared_ptr<SymbolTable> symtab = nullptr; + std::vector<std::shared_ptr<Symbol>> get_variables(syminfo::NmodlType with, + syminfo::NmodlType without); - /// current symbol table being constructed - SymbolTable* current_symtab = nullptr; + std::vector<std::shared_ptr<Symbol>> get_variables_with_properties( + syminfo::NmodlType properties, + bool all = false); - /// return unique name by appending some counter value - std::string get_unique_name(const std::string& name, ast::AST* node, bool is_global); + std::vector<std::shared_ptr<Symbol>> get_variables_with_status(syminfo::Status status, + bool all = false); - /// name of top level global symbol table - const std::string GLOBAL_SYMTAB_NAME = "NMODL_GLOBAL"; + bool under_global_scope(); - /// default mode of symbol table: if update is true then we update exisiting - /// symbols otherwise we throw away old table and construct new one - bool update_table = false; + void insert_table(const std::string& name, std::shared_ptr<SymbolTable> table); - /// current order of variable being defined - int definition_order = 0; + void print(std::stringstream& ss, int level); - /// if breakpoint block exist in the model - bool breakpoint_exist = false; + std::string to_string() { + std::stringstream s; + print(s, 0); + return s.str(); + } +}; - std::string model_suffix = ""; +/** + * \class ModelSymbolTable + * \brief Represent symbol table for nmodl block + * + * SymbolTable is sufficient to hold information about all symbols in the + * mod file. It might be sufficient to keep track of global symbol tables + * and local symbol tables. But we construct symbol table using visitor + * pass. In this case we visit ast and recursively create symbol table for + * each block scope. In this case, ModelSymbolTable is provide interface + * to build symbol table with visitor. + * + * \note For included mod file it's not clear yet whether we need to maintain + * separate ModelSymbolTable. + * \note See command project in compiler teaching course for details + * + * \todo Unique name should be based on location. Use ModToken to get position. + */ +class ModelSymbolTable { + private: + /// symbol table for mod file (always top level symbol table) + std::shared_ptr<SymbolTable> symtab = nullptr; - /// insert symbol table in update mode - std::shared_ptr<Symbol> update_mode_insert(const std::shared_ptr<Symbol>& symbol); + /// current symbol table being constructed + SymbolTable* current_symtab = nullptr; - void emit_message(const std::shared_ptr<Symbol>& first, - const std::shared_ptr<Symbol>& second, - bool redefinition); + /// return unique name by appending some counter value + std::string get_unique_name(const std::string& name, ast::AST* node, bool is_global); - void update_order(const std::shared_ptr<Symbol>& present_symbol, - const std::shared_ptr<Symbol>& new_symbol); + /// name of top level global symbol table + const std::string GLOBAL_SYMTAB_NAME = "NMODL_GLOBAL"; - public: - /// entering into new nmodl block - SymbolTable* enter_scope(const std::string& name, - ast::AST* node, - bool global, - SymbolTable* node_symtab); + /// default mode of symbol table: if update is true then we update exisiting + /// symbols otherwise we throw away old table and construct new one + bool update_table = false; - /// leaving current nmodl block - void leave_scope(); + /// current order of variable being defined + int definition_order = 0; - /// insert new symbol into current table - std::shared_ptr<Symbol> insert(const std::shared_ptr<Symbol>& symbol); + /// if breakpoint block exist in the model + bool breakpoint_exist = false; - /// lookup for symbol into current as well as all parent tables - std::shared_ptr<Symbol> lookup(const std::string& name); + std::string model_suffix = ""; - /// pretty print - void print(std::stringstream& ss); + /// insert symbol table in update mode + std::shared_ptr<Symbol> update_mode_insert(const std::shared_ptr<Symbol>& symbol); - /// re-initialize members to throw away old symbol tables - /// this is required as symtab visitor pass runs multiple time - void set_mode(bool update_mode); + void emit_message(const std::shared_ptr<Symbol>& first, + const std::shared_ptr<Symbol>& second, + bool redefinition); - bool has_breakpoint() const { - return breakpoint_exist; - } + void update_order(const std::shared_ptr<Symbol>& present_symbol, + const std::shared_ptr<Symbol>& new_symbol); - std::string model_name() const { - return model_suffix; - } - }; + public: + /// entering into new nmodl block + SymbolTable* enter_scope(const std::string& name, + ast::AST* node, + bool global, + SymbolTable* node_symtab); -} // namespace symtab + /// leaving current nmodl block + void leave_scope(); + + /// insert new symbol into current table + std::shared_ptr<Symbol> insert(const std::shared_ptr<Symbol>& symbol); + + /// lookup for symbol into current as well as all parent tables + std::shared_ptr<Symbol> lookup(const std::string& name); -#endif + /// pretty print + void print(std::stringstream& ss); + + /// re-initialize members to throw away old symbol tables + /// this is required as symtab visitor pass runs multiple time + void set_mode(bool update_mode); + + bool has_breakpoint() const { + return breakpoint_exist; + } + + std::string model_name() const { + return model_suffix; + } +}; + +} // namespace symtab diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index f139fea9ac..4073ad572c 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -1,6 +1,6 @@ -#============================================================================= +# ============================================================================= # Utility sources -#============================================================================= +# ============================================================================= set(UTIL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp @@ -11,18 +11,13 @@ set(UTIL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.hpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp - ${CMAKE_BINARY_DIR}/version.cpp -) + ${CMAKE_BINARY_DIR}/version.cpp) -#============================================================================= +# ============================================================================= # Symbol table library -#============================================================================= +# ============================================================================= -add_library(util_obj - OBJECT - ${UTIL_SOURCE_FILES}) +add_library(util_obj OBJECT ${UTIL_SOURCE_FILES}) set_property(TARGET util_obj PROPERTY POSITION_INDEPENDENT_CODE ON) -add_library(util - STATIC - $<TARGET_OBJECTS:util_obj>) +add_library(util STATIC $<TARGET_OBJECTS:util_obj>) diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 5896d5546e..b985757164 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <cerrno> #include <iostream> #include <stdexcept> @@ -20,26 +27,26 @@ bool make_path(const std::string& path) { } switch (errno) { - case ENOENT: - // parent didn't exist, try to create it - { - int pos = path.find_last_of('/'); - if (pos == std::string::npos) { - return false; - } - if (!make_path(path.substr(0, pos))) { - return false; - } + case ENOENT: + // parent didn't exist, try to create it + { + int pos = path.find_last_of('/'); + if (pos == std::string::npos) { + return false; + } + if (!make_path(path.substr(0, pos))) { + return false; } - // now, try to create again - return 0 == mkdir(path.c_str(), mode); + } + // now, try to create again + return 0 == mkdir(path.c_str(), mode); - case EEXIST: - // done! - return is_dir_exist(path); + case EEXIST: + // done! + return is_dir_exist(path); - default: - auto msg = "Can not create directory " + path; - throw std::runtime_error(msg); + default: + auto msg = "Can not create directory " + path; + throw std::runtime_error(msg); } } diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index e2846e69c3..5a86526554 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -1,5 +1,11 @@ -#ifndef _COMMON_UTILS_H_ -#define _COMMON_UTILS_H_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once /** Check if the iterator is pointing to last element in the container */ template <typename Iter, typename Cont> @@ -22,5 +28,3 @@ T remove_extension(T const& filename) { /** Given directory path, create sub-directories */ bool make_path(const std::string& path); - -#endif diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 8761292c10..95c148e39f 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -1,9 +1,13 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <memory> -// clang-format off -#include "spdlog/spdlog.h" -#include "spdlog/sinks/stdout_color_sinks.h" -// clang-format on +#include "utils/logger.hpp" using logger_type = std::shared_ptr<spdlog::logger>; diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index 06f6d7e593..bb7100e156 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_LOGGER_HPP -#define NMODL_LOGGER_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once // clang-format off #include "spdlog/spdlog.h" @@ -8,5 +14,3 @@ using logger_type = std::shared_ptr<spdlog::logger>; extern logger_type logger; - -#endif diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index e43f5e9648..31360ecdf5 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <iostream> #include <vector> diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index ff6cb15b94..6eded62dd0 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_PERF_STAT_HPP_ -#define _NMODL_PERF_STAT_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <sstream> @@ -84,5 +90,3 @@ class PerfStat { std::vector<std::string> keys(); std::vector<std::string> values(); }; - -#endif diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 47fa2b267e..6490840e02 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <algorithm> @@ -14,97 +21,97 @@ enum class text_alignment { left, right, center }; namespace stringutils { - /// Trim from start - static inline std::string& ltrim(std::string& s) { - s.erase(s.begin(), - std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); - return s; - } - - /// Trim from end - static inline std::string& rtrim(std::string& s) { - s.erase(std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))) - .base(), - s.end()); - return s; - } - - /// Trim from both ends - static inline std::string& trim(std::string& s) { - return ltrim(rtrim(s)); - } - - static inline void remove_character(std::string& str, const char c) { - str.erase(std::remove(str.begin(), str.end(), c), str.end()); - } - - /// Remove leading newline for the string read by grammar - static inline std::string& trim_newline(std::string& s) { - remove_character(s, '\n'); - return s; - } - - /// for printing json, we have to escape double quotes - static inline std::string escape_quotes(const std::string& before) { - std::string after; - - for (auto c : before) { - switch (c) { - case '"': - case '\\': - after += '\\'; - /// don't break here as we want to append actual character - - default: - after += c; - } +/// Trim from start +static inline std::string& ltrim(std::string& s) { + s.erase(s.begin(), + std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun<int, int>(std::isspace)))); + return s; +} + +/// Trim from end +static inline std::string& rtrim(std::string& s) { + s.erase( + std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun<int, int>(std::isspace))).base(), + s.end()); + return s; +} + +/// Trim from both ends +static inline std::string& trim(std::string& s) { + return ltrim(rtrim(s)); +} + +static inline void remove_character(std::string& str, const char c) { + str.erase(std::remove(str.begin(), str.end(), c), str.end()); +} + +/// Remove leading newline for the string read by grammar +static inline std::string& trim_newline(std::string& s) { + remove_character(s, '\n'); + return s; +} + +/// for printing json, we have to escape double quotes +static inline std::string escape_quotes(const std::string& before) { + std::string after; + + for (auto c: before) { + switch (c) { + case '"': + case '\\': + after += '\\'; + /// don't break here as we want to append actual character + + default: + after += c; } - - return after; } - /// Spilt string with given delimiter and returns vector - static inline std::vector<std::string> split_string(const std::string& text, char delimiter) { - std::vector<std::string> elements; - std::stringstream ss(text); - std::string item; + return after; +} - while (std::getline(ss, item, delimiter)) { - elements.push_back(item); - } +/// Spilt string with given delimiter and returns vector +static inline std::vector<std::string> split_string(const std::string& text, char delimiter) { + std::vector<std::string> elements; + std::stringstream ss(text); + std::string item; - return elements; + while (std::getline(ss, item, delimiter)) { + elements.push_back(item); } - /// Left/Right/Center-aligns string within a field of width "width" - static inline std::string align_text(std::string text, int width, text_alignment type) { - /// left and right spacing - std::string left, right; - - /// count excess room to pad - int padding = width - text.size(); - - if (padding > 0) { - if (type == text_alignment::left) { - right = std::string(padding, ' '); - } else if (type == text_alignment::right) { - left = std::string(padding, ' '); - } else { - left = std::string(padding / 2, ' '); - right = std::string(padding / 2, ' '); - /// if odd #, add one more space - if (padding > 0 && padding % 2 != 0) { - right += " "; - } + return elements; +} + +/// Left/Right/Center-aligns string within a field of width "width" +static inline std::string align_text(std::string text, int width, text_alignment type) { + /// left and right spacing + std::string left, right; + + /// count excess room to pad + int padding = width - text.size(); + + if (padding > 0) { + if (type == text_alignment::left) { + right = std::string(padding, ' '); + } else if (type == text_alignment::right) { + left = std::string(padding, ' '); + } else { + left = std::string(padding / 2, ' '); + right = std::string(padding / 2, ' '); + /// if odd #, add one more space + if (padding > 0 && padding % 2 != 0) { + right += " "; } } - return left + text + right; - } - - /// To lower case - static inline std::string tolower(std::string text) { - std::transform(text.begin(), text.end(), text.begin(), ::tolower); - return text; } + return left + text + right; +} + +/// To lower case +static inline std::string tolower(std::string text) { + std::transform(text.begin(), text.end(), text.begin(), ::tolower); + return text; +} } // namespace stringutils diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 51eb28c201..6239e51dca 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <iostream> #include <numeric> @@ -54,13 +61,13 @@ void TableData::print(std::stringstream& stream, int indent) { if ((extra_size % ncolumns) != 0) { column_pad++; } - for (auto& column : col_width) { + for (auto& column: col_width) { column += column_pad; } } /// check length of columns in each row to find max length required - for (const auto& row : rows) { + for (const auto& row: rows) { for (unsigned i = 0; i < row.size(); i++) { if (col_width[i] < (row[i].length()) + PADDING) { col_width[i] = row[i].length() + PADDING; @@ -91,7 +98,7 @@ void TableData::print(std::stringstream& stream, int indent) { stream << "\n" << gutter << separator_line; /// data rows - for (const auto& row : rows) { + for (const auto& row: rows) { stream << "\n" << gutter << "| "; for (unsigned i = 0; i < row.size(); i++) { stream << stringutils::align_text(row[i], col_width[i], alignments[i]) << " | "; diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index 66406f3230..1c48b639c6 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_TABLE_DATA_HPP_ -#define _NMODL_TABLE_DATA_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <sstream> #include <vector> @@ -32,5 +38,3 @@ struct TableData { void print(int indent = 0); void print(std::stringstream& stream, int indent = 0); }; - -#endif diff --git a/src/nmodl/version/version.cpp.in b/src/nmodl/version/version.cpp.in index f9677c8d83..284044e995 100644 --- a/src/nmodl/version/version.cpp.in +++ b/src/nmodl/version/version.cpp.in @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "version/version.h" const std::string nmodl::version::GIT_REVISION = "@GIT_REVISION@"; diff --git a/src/nmodl/version/version.h b/src/nmodl/version/version.h index 3abf57871c..2d687f5c70 100644 --- a/src/nmodl/version/version.h +++ b/src/nmodl/version/version.h @@ -1,13 +1,20 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <string> namespace nmodl { - struct version { - static const std::string GIT_REVISION; - static const std::string NMODL_VERSION; - std::string to_string() { - return NMODL_VERSION + " [" + GIT_REVISION + "]"; - } - }; +struct version { + static const std::string GIT_REVISION; + static const std::string NMODL_VERSION; + std::string to_string() { + return NMODL_VERSION + " [" + GIT_REVISION + "]"; + } +}; } // namespace nmodl diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 18af7aa9e4..5a9a299149 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -1,68 +1,59 @@ -#============================================================================= +# ============================================================================= # Visitor sources -#============================================================================= +# ============================================================================= set(VISITOR_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp) set(VISITOR_GENERATED_SOURCES - ${CMAKE_CURRENT_BINARY_DIR}/ast_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.hpp - ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.hpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.hpp - ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.hpp -) + ${CMAKE_CURRENT_BINARY_DIR}/ast_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.hpp + ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.hpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.hpp + ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.hpp) -set_source_files_properties( - ${VISITOR_GENERATED_SOURCES} - PROPERTIES GENERATED TRUE -) +set_source_files_properties(${VISITOR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) -#============================================================================= +# ============================================================================= # Visitor library and executable -#============================================================================= +# ============================================================================= include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -add_library(visitor_obj - OBJECT - ${VISITOR_SOURCES} ${VISITOR_GENERATED_SOURCES}) +add_library(visitor_obj OBJECT ${VISITOR_SOURCES} ${VISITOR_GENERATED_SOURCES}) set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(visitor_obj lexer_obj) -add_library(visitor - STATIC - $<TARGET_OBJECTS:visitor_obj>) +add_library(visitor STATIC $<TARGET_OBJECTS:visitor_obj>) target_link_libraries(visitor PRIVATE pybind11::embed) add_dependencies(visitor lexer util) @@ -70,7 +61,7 @@ add_dependencies(visitor lexer util) add_executable(nmodl_visitor main.cpp) target_link_libraries(nmodl_visitor printer visitor symtab util lexer) -#============================================================================= +# ============================================================================= # Install executable -#============================================================================= +# ============================================================================= install(TARGETS nmodl_visitor DESTINATION bin/visitor) diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 08eadce4f8..6be74f8b4a 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <sstream> #include "parser/diffeq_driver.hpp" @@ -50,8 +57,8 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { if (diffeq_driver.cnexp_possible(equation, solution)) { auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); - auto bin_expr = - std::dynamic_pointer_cast<BinaryExpression>(expr_statement->get_expression()); + auto bin_expr = std::dynamic_pointer_cast<BinaryExpression>( + expr_statement->get_expression()); lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp index 2658fb078e..5a689ddb74 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef CNEXP_SOLVE_VISITOR_HPP -#define CNEXP_SOLVE_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> @@ -17,7 +23,7 @@ * pass. */ -class CnexpSolveVisitor : public AstVisitor { +class CnexpSolveVisitor: public AstVisitor { private: /// method specified in solve block std::string solve_method; @@ -44,5 +50,3 @@ class CnexpSolveVisitor : public AstVisitor { void visit_binary_expression(ast::BinaryExpression* node) override; void visit_program(ast::Program* node) override; }; - -#endif diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index e79b7de378..3d5db2d7fb 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <algorithm> #include <utility> @@ -9,28 +16,28 @@ using namespace syminfo; /// DUState to string conversion for pretty-printing std::string to_string(DUState state) { switch (state) { - case DUState::U: - return "U"; - case DUState::D: - return "D"; - case DUState::LU: - return "LU"; - case DUState::LD: - return "LD"; - case DUState::CONDITIONAL_BLOCK: - return "CONDITIONAL_BLOCK"; - case DUState::IF: - return "IF"; - case DUState::ELSEIF: - return "ELSEIF"; - case DUState::ELSE: - return "ELSE"; - case DUState::UNKNOWN: - return "UNKNOWN"; - case DUState::NONE: - return "NONE"; - default: - throw std::runtime_error("Unhandled DUState?"); + case DUState::U: + return "U"; + case DUState::D: + return "D"; + case DUState::LU: + return "LU"; + case DUState::LD: + return "LD"; + case DUState::CONDITIONAL_BLOCK: + return "CONDITIONAL_BLOCK"; + case DUState::IF: + return "IF"; + case DUState::ELSEIF: + return "ELSEIF"; + case DUState::ELSE: + return "ELSE"; + case DUState::UNKNOWN: + return "UNKNOWN"; + case DUState::NONE: + return "NONE"; + default: + throw std::runtime_error("Unhandled DUState?"); } } @@ -45,7 +52,7 @@ void DUInstance::print(JSONPrinter& printer) { printer.add_node(to_string(state)); } else { printer.push_block(to_string(state)); - for (auto& inst : children) { + for (auto& inst: children) { inst.print(printer); } printer.pop_block(); @@ -59,7 +66,7 @@ std::string DUChain::to_string(bool compact) { printer.compact_json(compact); printer.push_block(name); - for (auto& instance : chain) { + for (auto& instance: chain) { instance.print(printer); } printer.pop_block(); @@ -74,7 +81,7 @@ std::string DUChain::to_string(bool compact) { */ DUState DUInstance::sub_block_eval() { DUState result = DUState::NONE; - for (auto& chain : children) { + for (auto& chain: children) { auto child_state = chain.eval(); if (child_state == DUState::U || child_state == DUState::D) { result = child_state; @@ -110,7 +117,7 @@ DUState DUInstance::conditional_block_eval() { DUState result = DUState::NONE; bool block_with_none = false; - for (auto& chain : children) { + for (auto& chain: children) { auto child_state = chain.eval(); if (child_state == DUState::U) { result = child_state; @@ -149,7 +156,7 @@ DUState DUInstance::eval() { /// or usage. Note that if-else blocks already evaluated. DUState DUChain::eval() { auto result = DUState::NONE; - for (auto& inst : chain) { + for (auto& inst: chain) { auto re = inst.eval(); if (re == DUState::U || re == DUState::D) { result = re; @@ -223,7 +230,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { current_chain = last_chain; /// visiting else if sub-blocks - for (const auto& item : node->get_elseifs()) { + for (const auto& item: node->get_elseifs()) { visit_with_new_chain(item.get(), DUState::ELSEIF); } diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 1dde4da073..6f29586b7d 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_DEFUSE_ANALYZE_VISITOR_HPP -#define NMODL_DEFUSE_ANALYZE_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <stack> @@ -63,8 +69,8 @@ class DUInstance { /// usage of variable in case of if like statements std::vector<DUInstance> children; - DUInstance(DUState state) : state(state) { - } + DUInstance(DUState state) + : state(state) {} /// analyze all children and return "effective" usage DUState eval(); @@ -92,8 +98,8 @@ class DUChain { std::vector<DUInstance> chain; DUChain() = default; - DUChain(std::string name) : name(name) { - } + DUChain(std::string name) + : name(name) {} /// return "effective" usage of a variable DUState eval(); @@ -154,7 +160,7 @@ class DUChain { * in any of the if-elseif-else part then it is considered as "used". And * this is done recursively from innermost level to the top. */ -class DefUseAnalyzeVisitor : public AstVisitor { +class DefUseAnalyzeVisitor: public AstVisitor { private: /// symbol table containing global variables symtab::SymbolTable* global_symtab = nullptr; @@ -189,12 +195,12 @@ class DefUseAnalyzeVisitor : public AstVisitor { public: DefUseAnalyzeVisitor() = delete; - explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) : global_symtab(symtab) { - } + explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) + : global_symtab(symtab) {} DefUseAnalyzeVisitor(symtab::SymbolTable* symtab, bool ignore_verbatim) - : global_symtab(symtab), ignore_verbatim(ignore_verbatim) { - } + : global_symtab(symtab) + , ignore_verbatim(ignore_verbatim) {} virtual void visit_binary_expression(ast::BinaryExpression* node) override; virtual void visit_if_statement(ast::IfStatement* node) override; @@ -240,17 +246,12 @@ class DefUseAnalyzeVisitor : public AstVisitor { /// statements / nodes that should not be used for def-use chain analysis - virtual void visit_conductance_hint(ast::ConductanceHint* /*node*/) override { - } + virtual void visit_conductance_hint(ast::ConductanceHint* /*node*/) override {} - virtual void visit_local_list_statement(ast::LocalListStatement* /*node*/) override { - } + virtual void visit_local_list_statement(ast::LocalListStatement* /*node*/) override {} - virtual void visit_argument(ast::Argument* /*node*/) override { - } + virtual void visit_argument(ast::Argument* /*node*/) override {} /// compute def-use chain for a variable within the node DUChain analyze(ast::Node* node, const std::string& name); }; - -#endif diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index fa2fc5145a..e8df83a028 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/inline_visitor.hpp" #include "parser/c11_driver.hpp" @@ -6,7 +13,7 @@ using namespace ast; bool InlineVisitor::can_inline_block(StatementBlock* block) { bool to_inline = true; const auto& statements = block->get_statements(); - for (auto statement : statements) { + for (auto statement: statements) { /// inlining is disabled if function/procedure contains table or lag statement if (statement->is_table_statement() || statement->is_lag_statement()) { to_inline = false; @@ -72,7 +79,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, size_t counter = 0; auto& statements = inlined_block->statements; - for (const auto& argument : callee_parameters) { + for (const auto& argument: callee_parameters) { auto name = argument->get_name()->clone(); auto old_name = name->get_node_name(); auto new_name = get_new_name(old_name, "in", inlined_variables); @@ -199,10 +206,10 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { bool inlined = false; if (function_definition->is_procedure_block()) { - auto proc = (ProcedureBlock*)function_definition; + auto proc = (ProcedureBlock*) function_definition; inlined = inline_function_call(proc, node, caller_block); } else if (function_definition->is_function_block()) { - auto func = (FunctionBlock*)function_definition; + auto func = (FunctionBlock*) function_definition; inlined = inline_function_call(func, node, caller_block); } @@ -230,7 +237,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { auto& statements = node->statements; - for (auto& statement : statements) { + for (auto& statement: statements) { caller_statement = statement; statement_stack.push(statement); caller_statement->visit_children(this); @@ -245,14 +252,14 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { /// check if any statement is candidate for replacement due to inlining /// this is typicall case of procedure calls - for (auto& statement : statements) { + for (auto& statement: statements) { if (replaced_statements.find(statement) != replaced_statements.end()) { statement.reset(replaced_statements[statement]); } } /// all statements from inlining needs to be added before the caller statement - for (auto& element : inlined_statements) { + for (auto& element: inlined_statements) { auto it = std::find(statements.begin(), statements.end(), element.first); if (it != statements.end()) { statements.insert(it, element.second.begin(), element.second.end()); diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index f02d645d19..8cc220c519 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_INLINE_VISITOR_HPP -#define NMODL_INLINE_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <stack> @@ -107,7 +113,7 @@ * - Location of symbol/nodes after inlining still points to old nodes */ -class InlineVisitor : public AstVisitor { +class InlineVisitor: public AstVisitor { private: /// statement block containing current function call ast::StatementBlock* caller_block = nullptr; @@ -170,5 +176,3 @@ class InlineVisitor : public AstVisitor { virtual void visit_program(ast::Program* node) override; }; - -#endif // diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index b5a6a130a5..daf871e07a 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/local_var_rename_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -23,7 +30,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { symtab_stack.push(symtab); // first need to process all children : perform recursively from innermost block - for (const auto& item : node->get_statements()) { + for (const auto& item: node->get_statements()) { item->visit_children(this); } @@ -46,7 +53,7 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { RenameVisitor rename_visitor; - for (const auto& var : *variables) { + for (const auto& var: *variables) { std::string name = var->get_node_name(); auto s = parent_symtab->lookup_in_scope(name); /// if symbol is a variable name (avoid renaming use of units like mV) diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index de7630b03b..10829b4256 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef LOCAL_VAR_RENAME_VISITOR_HPP -#define LOCAL_VAR_RENAME_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <stack> @@ -39,7 +45,7 @@ * In this case ideally we should not rename. */ -class LocalVarRenameVisitor : public AstVisitor { +class LocalVarRenameVisitor: public AstVisitor { private: /// non-null symbol table in the scope hierarchy symtab::SymbolTable* symtab = nullptr; @@ -54,5 +60,3 @@ class LocalVarRenameVisitor : public AstVisitor { LocalVarRenameVisitor() = default; virtual void visit_statement_block(ast::StatementBlock* node) override; }; - -#endif diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index d9b4d5c59a..144bfa980f 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <algorithm> #include "visitors/defuse_analyze_visitor.hpp" @@ -82,7 +89,7 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { auto variables = program_symtab->get_variables_with_properties(global_var_properties); std::vector<std::string> result; - for (auto& variable : variables) { + for (auto& variable: variables) { if (!variable->has_properties(excluded_var_properties)) { result.push_back(variable->get_name()); } @@ -98,12 +105,12 @@ void LocalizeVisitor::visit_program(Program* node) { } auto variables = variables_to_optimize(); - for (const auto& varname : variables) { + for (const auto& varname: variables) { const auto& blocks = node->get_blocks(); std::map<DUState, std::vector<std::shared_ptr<ast::Node>>> block_usage; /// compute def use chains - for (auto block : blocks) { + for (auto block: blocks) { if (node_for_def_use_analysis(block.get())) { DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); auto usages = v.analyze(block.get(), varname); @@ -117,14 +124,14 @@ void LocalizeVisitor::visit_program(Program* node) { auto it = block_usage.find(DUState::U); if (it == block_usage.end()) { /// all blocks that are using variable should get local variable - for (auto& block : block_usage[DUState::D]) { + for (auto& block: block_usage[DUState::D]) { auto block_ptr = dynamic_cast<Block*>(block.get()); auto statement_block = block_ptr->get_statement_block(); LocalVar* variable; auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { - variable = - add_local_variable(statement_block.get(), varname, symbol->get_length()); + variable = add_local_variable(statement_block.get(), varname, + symbol->get_length()); } else { variable = add_local_variable(statement_block.get(), varname); } diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 889f90d648..98ee42b09d 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_LOCALIZE_VISITOR_HPP -#define NMODL_LOCALIZE_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <stack> @@ -71,7 +77,7 @@ * } */ -class LocalizeVisitor : public AstVisitor { +class LocalizeVisitor: public AstVisitor { private: /// ignore verbatim blocks while localizing bool ignore_verbatim = false; @@ -87,10 +93,8 @@ class LocalizeVisitor : public AstVisitor { public: LocalizeVisitor() = default; - explicit LocalizeVisitor(bool ignore_verbatim) : ignore_verbatim(ignore_verbatim) { - } + explicit LocalizeVisitor(bool ignore_verbatim) + : ignore_verbatim(ignore_verbatim) {} virtual void visit_program(ast::Program* node) override; }; - -#endif diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 87f589e1b9..09ac3c224a 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <fstream> #include <iostream> #include <sstream> diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.hpp index cf1785dc83..4a6fff6715 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.hpp +++ b/src/nmodl/visitors/nmodl_visitor_helper.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_VISITOR_HELPER_HPP_ -#define _NMODL_VISITOR_HELPER_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include "visitors/nmodl_visitor.hpp" @@ -54,5 +60,3 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, } } } - -#endif diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index af2e998e2e..b109f1f554 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <utility> #include "visitors/perf_visitor.hpp" @@ -6,8 +13,8 @@ using namespace ast; using namespace syminfo; using namespace symtab; -PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) { -} +PerfVisitor::PerfVisitor(const std::string& filename) + : printer(new JSONPrinter(filename)) {} /// count math operations from all binary expressions void PerfVisitor::visit_binary_expression(BinaryExpression* node) { @@ -16,65 +23,65 @@ void PerfVisitor::visit_binary_expression(BinaryExpression* node) { if (start_measurement) { auto value = node->get_op().get_value(); switch (value) { - case BOP_ADDITION: - current_block_perf.n_add++; - break; + case BOP_ADDITION: + current_block_perf.n_add++; + break; - case BOP_SUBTRACTION: - current_block_perf.n_sub++; - break; + case BOP_SUBTRACTION: + current_block_perf.n_sub++; + break; - case BOP_MULTIPLICATION: - current_block_perf.n_mul++; - break; + case BOP_MULTIPLICATION: + current_block_perf.n_mul++; + break; - case BOP_DIVISION: - current_block_perf.n_div++; - break; + case BOP_DIVISION: + current_block_perf.n_div++; + break; - case BOP_POWER: - current_block_perf.n_pow++; - break; + case BOP_POWER: + current_block_perf.n_pow++; + break; - case BOP_AND: - current_block_perf.n_and++; - break; + case BOP_AND: + current_block_perf.n_and++; + break; - case BOP_OR: - current_block_perf.n_or++; - break; + case BOP_OR: + current_block_perf.n_or++; + break; - case BOP_GREATER: - current_block_perf.n_gt++; - break; + case BOP_GREATER: + current_block_perf.n_gt++; + break; - case BOP_GREATER_EQUAL: - current_block_perf.n_ge++; - break; + case BOP_GREATER_EQUAL: + current_block_perf.n_ge++; + break; - case BOP_LESS: - current_block_perf.n_lt++; - break; + case BOP_LESS: + current_block_perf.n_lt++; + break; - case BOP_LESS_EQUAL: - current_block_perf.n_le++; - break; + case BOP_LESS_EQUAL: + current_block_perf.n_le++; + break; - case BOP_ASSIGN: - current_block_perf.n_assign++; - assign_op = true; - break; + case BOP_ASSIGN: + current_block_perf.n_assign++; + assign_op = true; + break; - case BOP_NOT_EQUAL: - current_block_perf.n_ne++; - break; + case BOP_NOT_EQUAL: + current_block_perf.n_ne++; + break; - case BOP_EXACT_EQUAL: - current_block_perf.n_ee++; - break; + case BOP_EXACT_EQUAL: + current_block_perf.n_ee++; + break; - default: - throw std::logic_error("Binary operator not handled in perf visitor"); + default: + throw std::logic_error("Binary operator not handled in perf visitor"); } } @@ -145,7 +152,7 @@ void PerfVisitor::measure_performance(AST* node) { start_measurement = false; /// clear var usage map - for (auto& var_set : var_usage) { + for (auto& var_set: var_usage) { var_set.second.clear(); } } @@ -212,7 +219,7 @@ void PerfVisitor::count_variables() { NmodlType property = NmodlType::range_var | NmodlType::dependent_def | NmodlType::state_var; auto variables = current_symtab->get_variables_with_properties(property); - for (auto& variable : variables) { + for (auto& variable: variables) { if (!variable->has_properties(NmodlType::global_var)) { num_instance_variables++; if (variable->has_properties(NmodlType::param_assign)) { @@ -242,7 +249,7 @@ void PerfVisitor::count_variables() { NmodlType::pointer_var; variables = current_symtab->get_variables_with_properties(property); num_global_variables = 0; - for (auto& variable : variables) { + for (auto& variable: variables) { auto is_global = variable->has_properties(NmodlType::global_var); property = NmodlType::range_var | NmodlType::dependent_def; if (!variable->has_properties(property) || is_global) { @@ -365,16 +372,16 @@ void PerfVisitor::visit_unary_expression(UnaryExpression* node) { if (start_measurement) { auto value = node->get_op().get_value(); switch (value) { - case UOP_NEGATION: - current_block_perf.n_neg++; - break; + case UOP_NEGATION: + current_block_perf.n_neg++; + break; - case UOP_NOT: - current_block_perf.n_not++; - break; + case UOP_NOT: + current_block_perf.n_not++; + break; - default: - throw std::logic_error("Unary operator not handled in perf visitor"); + default: + throw std::logic_error("Unary operator not handled in perf visitor"); } } node->visit_children(this); diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 0d9f2de4de..dd74a3ce43 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef _NMODL_PERF_VISITOR_HPP_ -#define _NMODL_PERF_VISITOR_HPP_ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <set> #include <stack> @@ -34,7 +40,7 @@ * which PerfVisitor should be inherited. */ -class PerfVisitor : public AstVisitor { +class PerfVisitor: public AstVisitor { private: /// symbol table of current block being visited symtab::SymbolTable* current_symtab = nullptr; @@ -271,24 +277,17 @@ class PerfVisitor : public AstVisitor { /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations - void visit_conductance_hint(ast::ConductanceHint* /*node*/) override { - } + void visit_conductance_hint(ast::ConductanceHint* /*node*/) override {} - void visit_local_list_statement(ast::LocalListStatement* /*node*/) override { - } + void visit_local_list_statement(ast::LocalListStatement* /*node*/) override {} - void visit_suffix(ast::Suffix* /*node*/) override { - } + void visit_suffix(ast::Suffix* /*node*/) override {} - void visit_useion(ast::Useion* /*node*/) override { - } + void visit_useion(ast::Useion* /*node*/) override {} - void visit_valence(ast::Valence* /*node*/) override { - } + void visit_valence(ast::Valence* /*node*/) override {} void print(std::stringstream& ss) { ss << stream.str(); } }; - -#endif diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 80a9c80288..1a38f78583 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/rename_visitor.hpp" #include "parser/c11_driver.hpp" @@ -37,7 +44,7 @@ void RenameVisitor::visit_verbatim(Verbatim* node) { auto tokens = driver.all_tokens(); std::string result; - for (auto& token : tokens) { + for (auto& token: tokens) { if (token == var_name) { result += new_var_name; } else { diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index cb2d09eb3e..9453bd991f 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef VAR_RENAME_VISITOR_HPP -#define VAR_RENAME_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> @@ -21,7 +27,7 @@ * \todo : Add log/warning messages. */ -class RenameVisitor : public AstVisitor { +class RenameVisitor: public AstVisitor { private: /// variable to rename std::string var_name; @@ -36,8 +42,8 @@ class RenameVisitor : public AstVisitor { RenameVisitor() = default; RenameVisitor(std::string old_name, std::string new_name) - : var_name(old_name), new_var_name(new_name) { - } + : var_name(old_name) + , new_var_name(new_name) {} void set(std::string old_name, std::string new_name) { var_name = old_name; @@ -52,5 +58,3 @@ class RenameVisitor : public AstVisitor { virtual void visit_prime_name(ast::PrimeName* node) override; virtual void visit_verbatim(ast::Verbatim* node) override; }; - -#endif diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 5fdb61e9e9..1e8954bc5f 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <algorithm> #include <iostream> diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index bfed0b997d..cdf806384e 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <map> diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 3cf67582b6..37deac19cb 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/sympy_solver_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 846b038f73..e3b0d0ce24 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #pragma once #include <pybind11/embed.h> diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index e4d2c4c660..6841605008 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -1,3 +1,12 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + #include <utility> #include "lexer/token_mapping.hpp" diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 9feee3bfc5..5254a1325d 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/var_usage_visitor.hpp" #include <utility> diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index 079462d748..ca431d9280 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef VAR_USAGE_VISITOR_HPP -#define VAR_USAGE_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <string> @@ -13,7 +19,7 @@ * \todo : check if macro is considered as variable */ -class VarUsageVisitor : public AstVisitor { +class VarUsageVisitor: public AstVisitor { private: /// variable to check usage std::string var_name; @@ -26,5 +32,3 @@ class VarUsageVisitor : public AstVisitor { virtual void visit_name(ast::Name* node) override; }; - -#endif diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 7af8eaf27b..a54357d5ec 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/verbatim_var_rename_visitor.hpp" #include "parser/c11_driver.hpp" @@ -20,7 +27,7 @@ void VerbatimVarRenameVisitor::visit_statement_block(StatementBlock* node) { symtab_stack.push(symtab); // first need to process all children : perform recursively from innermost block - for (const auto& item : node->get_statements()) { + for (const auto& item: node->get_statements()) { item->accept(this); } @@ -70,7 +77,7 @@ void VerbatimVarRenameVisitor::visit_verbatim(Verbatim* node) { auto tokens = driver.all_tokens(); std::string result; - for (const auto& token : tokens) { + for (const auto& token: tokens) { result += rename_variable(token); } statement->set(result); diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index c8fcd96051..85b61f755f 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef VERBATIM_VAR_RENAME_VISITOR_HPP -#define VERBATIM_VAR_RENAME_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <stack> #include <string> @@ -27,7 +33,7 @@ * could be error prone. */ -class VerbatimVarRenameVisitor : public AstVisitor { +class VerbatimVarRenameVisitor: public AstVisitor { private: /// non-null symbol table in the scope hierarchy symtab::SymbolTable* symtab = nullptr; @@ -49,5 +55,3 @@ class VerbatimVarRenameVisitor : public AstVisitor { virtual void visit_verbatim(ast::Verbatim* node) override; virtual void visit_statement_block(ast::StatementBlock* node) override; }; - -#endif diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 318d0a27e6..1b1f93f255 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "visitors/verbatim_visitor.hpp" #include <iostream> diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 9320290e57..dbcb16022e 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -1,5 +1,11 @@ -#ifndef VERBATIM_VISITOR_HPP -#define VERBATIM_VISITOR_HPP +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <vector> @@ -17,7 +23,7 @@ * in ModelDB. */ -class VerbatimVisitor : public AstVisitor { +class VerbatimVisitor: public AstVisitor { private: /// flag to enable/disable printing blocks as we visit them bool verbose = false; @@ -38,5 +44,3 @@ class VerbatimVisitor : public AstVisitor { return blocks; } }; - -#endif diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 98f7db8d38..ca704db59c 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -1,12 +1,19 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include <map> #include <memory> #include <string> #include "ast/ast.hpp" #include "parser/nmodl_driver.hpp" +#include "visitor_utils.hpp" #include "visitors/json_visitor.hpp" #include "visitors/nmodl_visitor.hpp" -#include "visitor_utils.hpp" using namespace ast; @@ -22,7 +29,7 @@ std::string get_new_name(const std::string& name, } LocalVarVector* get_local_variables(const StatementBlock* node) { - for (const auto& statement : node->statements) { + for (const auto& statement: node->statements) { if (statement->is_local_list_statement()) { auto local_statement = std::static_pointer_cast<LocalListStatement>(statement); return &(local_statement->variables); @@ -73,8 +80,8 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { driver.parse_string(nmodl_text); auto ast = driver.ast(); auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); - auto statement = - std::shared_ptr<Statement>(procedure->get_statement_block()->get_statements()[0]->clone()); + auto statement = std::shared_ptr<Statement>( + procedure->get_statement_block()->get_statements()[0]->clone()); return statement; } @@ -89,7 +96,7 @@ std::set<std::string> get_global_vars(Program* node) { syminfo::NmodlType::nonspecific_cur_var | syminfo::NmodlType::electrode_cur_var | syminfo::NmodlType::section_var | syminfo::NmodlType::constant_var | syminfo::NmodlType::extern_neuron_variable | syminfo::NmodlType::state_var; - for (auto globalvar : symtab->get_variables_with_properties(property)) { + for (auto globalvar: symtab->get_variables_with_properties(property)) { vars.insert(globalvar->get_name()); } } @@ -98,22 +105,22 @@ std::set<std::string> get_global_vars(Program* node) { namespace nmodl { - std::string to_nmodl(ast::AST* node) { - std::stringstream stream; - NmodlPrintVisitor v(stream); - node->accept(&v); - return stream.str(); - } +std::string to_nmodl(ast::AST* node) { + std::stringstream stream; + NmodlPrintVisitor v(stream); + node->accept(&v); + return stream.str(); +} - std::string to_json(ast::AST* node, bool compact, bool expand) { - std::stringstream stream; - JSONVisitor v(stream); - v.compact_json(compact); - v.expand_keys(expand); - node->accept(&v); - v.flush(); - return stream.str(); - } +std::string to_json(ast::AST* node, bool compact, bool expand) { + std::stringstream stream; + JSONVisitor v(stream); + v.compact_json(compact); + v.expand_keys(expand); + node->accept(&v); + v.flush(); + return stream.str(); +} } // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index a66cb998c4..a085870f2d 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_VISITOR_UTILS -#define NMODL_VISITOR_UTILS +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <set> @@ -32,12 +38,10 @@ std::shared_ptr<ast::Statement> create_statement(const std::string& code_stateme std::set<std::string> get_global_vars(ast::Program* node); namespace nmodl { - /** Given AST node, return the NMODL string representation */ - std::string to_nmodl(ast::AST* node); +/** Given AST node, return the NMODL string representation */ +std::string to_nmodl(ast::AST* node); - /** Given AST node, return the JSON string representation */ - std::string to_json(ast::AST* node, bool compact = false, bool expand = false); +/** Given AST node, return the JSON string representation */ +std::string to_json(ast::AST* node, bool compact = false, bool expand = false); } // namespace nmodl - -#endif diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index a981603406..da41ebbd1f 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -1,29 +1,25 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/test) include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -include_directories( - ${PROJECT_SOURCE_DIR}/src/ext/catch - ${PROJECT_SOURCE_DIR}/test -) +include_directories(${PROJECT_SOURCE_DIR}/src/ext/catch ${PROJECT_SOURCE_DIR}/test) -#============================================================================= +# ============================================================================= # Common input data library -#============================================================================= +# ============================================================================= add_library(test_util STATIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp -) + ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) -#============================================================================= +# ============================================================================= # Test executables -#============================================================================= -add_executable (testmodtoken modtoken/modtoken.cpp) -add_executable (testlexer lexer/tokens.cpp) -add_executable (testparser parser/parser.cpp) -add_executable (testvisitor visitor/visitor.cpp) -add_executable (testprinter printer/printer.cpp) -add_executable (testsymtab symtab/symbol_table.cpp) +# ============================================================================= +add_executable(testmodtoken modtoken/modtoken.cpp) +add_executable(testlexer lexer/tokens.cpp) +add_executable(testparser parser/parser.cpp) +add_executable(testvisitor visitor/visitor.cpp) +add_executable(testprinter printer/printer.cpp) +add_executable(testsymtab symtab/symbol_table.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) @@ -32,20 +28,19 @@ target_link_libraries(testvisitor symtab visitor lexer util test_util printer) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) -add_test (NAME ModToken COMMAND testmodtoken) -add_test (NAME Lexer COMMAND testlexer) -add_test (NAME Parser COMMAND testparser) -add_test (NAME Visitor COMMAND testvisitor) -add_test (NAME Printer COMMAND testprinter) -add_test (NAME Symtab COMMAND testsymtab) +add_test(NAME ModToken COMMAND testmodtoken) +add_test(NAME Lexer COMMAND testlexer) +add_test(NAME Parser COMMAND testparser) +add_test(NAME Visitor COMMAND testvisitor) +add_test(NAME Printer COMMAND testprinter) +add_test(NAME Symtab COMMAND testsymtab) -set_tests_properties(Visitor PROPERTIES - ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) +set_tests_properties(Visitor PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) -#============================================================================= +# ============================================================================= # pybind11 tests -#============================================================================= +# ============================================================================= -add_test (NAME Pybind COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) -set_tests_properties(Pybind PROPERTIES - ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) +add_test(NAME Pybind + COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) +set_tests_properties(Pybind PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 40ccf3f46b..f502d52b6d 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #define CATCH_CONFIG_MAIN #include <string> @@ -27,44 +34,44 @@ nmodl::Parser::token_type token_type(const std::string& name) { * and we need to clean-up memory for those. * Todo: add tests later for checking values */ switch (token) { - case Token::NAME: - case Token::METHOD: - case Token::SUFFIX: - case Token::VALENCE: - case Token::DEL: - case Token::DEL2: { - auto value = sym.value.as<ast::Name>(); - break; - } - - case Token::PRIME: { - auto value = sym.value.as<ast::PrimeName>(); - break; - } - - case Token::INTEGER: { - auto value = sym.value.as<ast::Integer>(); - break; - } - - case Token::REAL: { - auto value = sym.value.as<ast::Double>(); - break; - } - - case Token::STRING: { - auto value = sym.value.as<ast::String>(); - break; - } - - case Token::VERBATIM: - case Token::BLOCK_COMMENT: - case Token::LINE_PART: { - auto value = sym.value.as<std::string>(); - break; - } - - default: { auto value = sym.value.as<ModToken>(); } + case Token::NAME: + case Token::METHOD: + case Token::SUFFIX: + case Token::VALENCE: + case Token::DEL: + case Token::DEL2: { + auto value = sym.value.as<ast::Name>(); + break; + } + + case Token::PRIME: { + auto value = sym.value.as<ast::PrimeName>(); + break; + } + + case Token::INTEGER: { + auto value = sym.value.as<ast::Integer>(); + break; + } + + case Token::REAL: { + auto value = sym.value.as<ast::Double>(); + break; + } + + case Token::STRING: { + auto value = sym.value.as<ast::String>(); + break; + } + + case Token::VERBATIM: + case Token::BLOCK_COMMENT: + case Token::LINE_PART: { + auto value = sym.value.as<std::string>(); + break; + } + + default: { auto value = sym.value.as<ModToken>(); } } return token; diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index c5c2523523..afaabd54ec 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #define CATCH_CONFIG_MAIN #include <string> diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 723319d251..854f88b3f4 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #define CATCH_CONFIG_MAIN #include <string> @@ -6,7 +13,7 @@ #include "catch/catch.hpp" #include "parser/diffeq_driver.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/nmodl_constructs.h" +#include "test/utils/nmodl_constructs.hpp" //============================================================================= // Parser tests @@ -93,7 +100,7 @@ SCENARIO("Macros can be used anywhere in NMODL program") { } SCENARIO("Parser test for valid NMODL grammar constructs") { - for (const auto& construct : nmodl_valid_constructs) { + for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { THEN("Parser successfully parses : " + test_case.input) { @@ -104,7 +111,7 @@ SCENARIO("Parser test for valid NMODL grammar constructs") { } SCENARIO("Parser test for invalid NMODL grammar constructs") { - for (const auto& construct : nmdol_invalid_constructs) { + for (const auto& construct: nmdol_invalid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { THEN("Parser throws an exception while parsing : " + test_case.input) { @@ -128,7 +135,7 @@ std::string solve_construct(const std::string& equation, std::string method) { SCENARIO("Solving differential equations using NEURON's implementation") { GIVEN("A differential equation") { int counter = 0; - for (const auto& test_case : diff_eq_constructs) { + for (const auto& test_case: diff_eq_constructs) { auto prefix = "." + std::to_string(counter); WHEN(prefix + " EQUATION = " + test_case.equation + " METHOD = " + test_case.method) { THEN(prefix + " SOLUTION = " + test_case.solution) { diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 0678c027af..9fc9103e38 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #define CATCH_CONFIG_MAIN #include <string> @@ -51,7 +58,8 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { p.pop_block(); p.flush(); - auto result = R"({"children":[{"name":"B"},{"children":[{"name":"E"}],"name":"D"}],"name":"A"})"; + auto result = + R"({"children":[{"name":"B"},{"children":[{"name":"E"}],"name":"D"}],"name":"A"})"; REQUIRE(ss.str() == result); } } diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/pybind/conftest.py index e30e487673..0b47aa598d 100644 --- a/test/nmodl/transpiler/pybind/conftest.py +++ b/test/nmodl/transpiler/pybind/conftest.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import pytest from nmodl.dsl import Driver diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py index 3d55ff7426..274d715ebb 100644 --- a/test/nmodl/transpiler/pybind/test_ast.py +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import nmodl from nmodl.dsl import ast, visitor import pytest diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py index 5ac3a05a2f..0785ad173f 100644 --- a/test/nmodl/transpiler/pybind/test_symtab.py +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import io from nmodl.dsl import ast, visitor, symtab diff --git a/test/nmodl/transpiler/pybind/test_visitor.py b/test/nmodl/transpiler/pybind/test_visitor.py index c337d75f09..cfa5b4ee98 100644 --- a/test/nmodl/transpiler/pybind/test_visitor.py +++ b/test/nmodl/transpiler/pybind/test_visitor.py @@ -1,3 +1,10 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + import nmodl from nmodl.dsl import ast, visitor import pytest diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index bb06e42c1f..281cd5d597 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #define CATCH_CONFIG_MAIN #include <string> @@ -223,8 +230,8 @@ SCENARIO("Symbol table operations") { auto result = table->get_variables_with_properties(NmodlType::range_var); REQUIRE(result.size() == 4); - result = - table->get_variables_with_properties(NmodlType::range_var | NmodlType::pointer_var); + result = table->get_variables_with_properties(NmodlType::range_var | + NmodlType::pointer_var); REQUIRE(result.size() == 4); auto with = NmodlType::range_var | NmodlType::param_assign; diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 108369e39a..d8be03f608 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1,4 +1,11 @@ -#include "test/utils/nmodl_constructs.h" +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "test/utils/nmodl_constructs.hpp" /** Guidelines for adding nmodl text constructs * diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.h b/test/nmodl/transpiler/utils/nmodl_constructs.hpp similarity index 58% rename from test/nmodl/transpiler/utils/nmodl_constructs.h rename to test/nmodl/transpiler/utils/nmodl_constructs.hpp index 2d292e9561..7b74ccace1 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.h +++ b/test/nmodl/transpiler/utils/nmodl_constructs.hpp @@ -1,5 +1,11 @@ -#ifndef NMODL_TEST_CONSTRUCTS -#define NMODL_TEST_CONSTRUCTS +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once #include <map> #include <string> @@ -20,12 +26,15 @@ struct NmodlTestCase { NmodlTestCase() = delete; - NmodlTestCase(std::string name, std::string input) : name(name), input(input), output(input) { - } + NmodlTestCase(std::string name, std::string input) + : name(name) + , input(input) + , output(input) {} NmodlTestCase(std::string name, std::string input, std::string output) - : name(name), input(input), output(output) { - } + : name(name) + , input(input) + , output(output) {} }; /// represent differential equation test @@ -45,6 +54,4 @@ struct DiffEqTestCase { extern std::map<std::string, NmodlTestCase> nmdol_invalid_constructs; extern std::map<std::string, NmodlTestCase> nmodl_valid_constructs; -extern std::vector<DiffEqTestCase> diff_eq_constructs; - -#endif \ No newline at end of file +extern std::vector<DiffEqTestCase> diff_eq_constructs; \ No newline at end of file diff --git a/test/nmodl/transpiler/utils/test_utils.cpp b/test/nmodl/transpiler/utils/test_utils.cpp index 1dd127a931..88a040ff7f 100644 --- a/test/nmodl/transpiler/utils/test_utils.cpp +++ b/test/nmodl/transpiler/utils/test_utils.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #include "utils/string_utils.hpp" int count_leading_spaces(std::string text) { diff --git a/test/nmodl/transpiler/utils/test_utils.hpp b/test/nmodl/transpiler/utils/test_utils.hpp index 6612d28139..8025e4d5d7 100644 --- a/test/nmodl/transpiler/utils/test_utils.hpp +++ b/test/nmodl/transpiler/utils/test_utils.hpp @@ -1,6 +1,10 @@ -#ifndef NMODL_TEST_UTILS -#define NMODL_TEST_UTILS +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ -std::string reindent_text(const std::string& text); +#pragma once -#endif +std::string reindent_text(const std::string& text); \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index fab3bc5228..1ebfecdb10 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1,3 +1,10 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + #define CATCH_CONFIG_RUNNER #include <string> @@ -6,7 +13,7 @@ #include <pybind11/embed.h> #include "parser/nmodl_driver.hpp" -#include "test/utils/nmodl_constructs.h" +#include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" From d2fa8ae77124c56c53831359c1a22c2d97e0094d Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Tue, 19 Feb 2019 12:51:04 +0100 Subject: [PATCH 135/871] add python bindings for sympy visitors - added SympySolverVisitor() to python visitors - added SympyConductanceVisitor() to python visitors - added new ipynb notebook with example of use Change-Id: Ia6c2e25ea9532699e1cd74d0239aa7a5f9988de7 NMODL Repo SHA: BlueBrain/nmodl@01c8edd4e71c82d31d2a4535601295611b198b07 --- .../nmodl-python-sympy-examples.ipynb | 351 ++++++++++++++++++ src/nmodl/language/templates/pyvisitor.cpp | 10 + 2 files changed, 361 insertions(+) create mode 100644 doc/notebooks/nmodl-python-sympy-examples.ipynb diff --git a/doc/notebooks/nmodl-python-sympy-examples.ipynb b/doc/notebooks/nmodl-python-sympy-examples.ipynb new file mode 100644 index 0000000000..f0e932aac1 --- /dev/null +++ b/doc/notebooks/nmodl-python-sympy-examples.ipynb @@ -0,0 +1,351 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL SymPy Visitor Examples\n", + "\n", + "Some examples of the use of SymPy within NMODL to\n", + "- solve differential equations to generate solutions for the CNEXP solver (`SympySolverVisitor`)\n", + "- differentiate current expressions to generate CONDUCTANCE statements (`SympyConductanceVisitor`)\n", + "\n", + "Please see the notebook `nmodl-python-tutorial.ipynb` for a more general tutorial on using the NMODL python interface, including installation instructions." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl\n", + "from nmodl.dsl import visitor\n", + "from nmodl.dsl import ast\n", + "from nmodl.dsl import symtab" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "hh.mod input file:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "channel = \"\"\"\n", + "NEURON {\n", + " SUFFIX hh\n", + " USEION na READ ena WRITE ina\n", + " USEION k READ ek WRITE ik\n", + " NONSPECIFIC_CURRENT il\n", + " RANGE gnabar, gkbar, gl, el, gna, gk\n", + " RANGE minf, hinf, ninf, mtau, htau, ntau\n", + "}\n", + " \n", + "UNITS {\n", + " (mV) = (millivolt)\n", + " (S) = (siemens)\n", + "}\n", + " \n", + "PARAMETER {\n", + " gnabar = .12 (S/cm2)\n", + " gkbar = .036 (S/cm2)\n", + " gl = .0003 (S/cm2)\n", + " el = -54.3 (mV)\n", + " celsius\n", + "}\n", + " \n", + "STATE {\n", + " m h n\n", + "}\n", + " \n", + "ASSIGNED {\n", + " v (mV)\n", + " \n", + " gna (S/cm2)\n", + " gk (S/cm2)\n", + " minf\n", + " hinf\n", + " ninf\n", + " mtau (ms)\n", + " htau (ms)\n", + " ntau (ms)\n", + "}\n", + " \n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + " gna = gnabar*m*m*m*h\n", + " ina = gna*(v - ena)\n", + " gk = gkbar*n*n*n*n\n", + " ik = gk*(v - ek)\n", + " il = gl*(v - el)\n", + "}\n", + " \n", + "INITIAL {\n", + " rates(v, celsius)\n", + " m = minf\n", + " h = hinf\n", + " n = ninf\n", + "}\n", + " \n", + "DERIVATIVE states {\n", + " rates(v, celsius)\n", + " m' = (minf-m)/mtau\n", + " h' = (hinf-h)/htau\n", + " n' = (ninf-n)/ntau\n", + "}\n", + " \n", + "PROCEDURE rates(v, celsius)\n", + "{\n", + " LOCAL alpha, beta, sum, q10\n", + " \n", + " q10 = 3^((celsius - 6.3)/10)\n", + " \n", + " :\"m\" sodium activation system\n", + " alpha = .1 * vtrap(-(v+40),10)\n", + " beta = 4 * exp(-(v+65)/18)\n", + " sum = alpha + beta\n", + " mtau = 1/(q10*sum)\n", + " minf = alpha/sum\n", + " \n", + " :\"h\" sodium inactivation system\n", + " alpha = .07 * exp(-(v+65)/20)\n", + " beta = 1 / (exp(-(v+35)/10) + 1)\n", + " sum = alpha + beta\n", + " htau = 1/(q10*sum)\n", + " hinf = alpha/sum\n", + " \n", + " :\"n\" potassium activation system\n", + " alpha = .01*vtrap(-(v+55),10)\n", + " beta = .125*exp(-(v+65)/80)\n", + " sum = alpha + beta\n", + " ntau = 1/(q10*sum)\n", + " ninf = alpha/sum\n", + "}\n", + " \n", + "FUNCTION vtrap(x,y) {\n", + " : use built in exprelr(z) = z/(exp(z)-1), which handles the z=0 case correctly\n", + " vtrap = y*exprelr(x/y)\n", + "}\n", + "\"\"\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ODE Solver example" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# parse NMDOL file\n", + "driver = nmodl.Driver()\n", + "driver.parse_string(channel)\n", + "modast = driver.ast()\n", + "lookup_visitor = visitor.AstLookupVisitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SOLVE states METHOD cnexp\n" + ] + } + ], + "source": [ + "# print solve method\n", + "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.SOLVE_BLOCK)[0]))" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE states {\n", + " rates(v, celsius)\n", + " m' = (minf-m)/mtau\n", + " h' = (hinf-h)/htau\n", + " n' = (ninf-n)/ntau\n", + "}\n" + ] + } + ], + "source": [ + "# print DERIVATIVE block\n", + "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Run `SympySolverVisitor`, then print DERIVATIVE block again:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE states {\n", + " rates(v, celsius)\n", + " m = minf+(m-minf)*exp(-dt/mtau)\n", + " h = hinf+(h-hinf)*exp(-dt/htau)\n", + " n = ninf+(n-ninf)*exp(-dt/ntau)\n", + "}\n" + ] + } + ], + "source": [ + "# first need to run SymtabVisitor to generate Symbol Table\n", + "symv = symtab.SymtabVisitor()\n", + "symv.visit_program(modast)\n", + "# then we can run SympySolverVisitor\n", + "sympy_solver_visitor = visitor.SympySolverVisitor()\n", + "sympy_solver_visitor.visit_program(modast)\n", + "# print DERIVATIVE block\n", + "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CONDUCTANCE example" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "USEION na READ ena WRITE ina\n", + "USEION k READ ek WRITE ik\n" + ] + } + ], + "source": [ + "# print USEION and NONSPECIFIC current statements\n", + "for node in lookup_visitor.lookup(modast, ast.AstNodeType.USEION or ast.AstNodeType.NONSPECIFIC):\n", + " print(nmodl.to_nmodl(node))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + " gna = gnabar*m*m*m*h\n", + " ina = gna*(v-ena)\n", + " gk = gkbar*n*n*n*n\n", + " ik = gk*(v-ek)\n", + " il = gl*(v-el)\n", + "}\n" + ] + } + ], + "source": [ + "# print BREAKPOINT\n", + "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "BREAKPOINT block after running `SympyConductanceVisitor`:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " CONDUCTANCE gna USEION na\n", + " CONDUCTANCE gl\n", + " CONDUCTANCE gk USEION k\n", + " SOLVE states METHOD cnexp\n", + " gna = gnabar*m*m*m*h\n", + " ina = gna*(v-ena)\n", + " gk = gkbar*n*n*n*n\n", + " ik = gk*(v-ek)\n", + " il = gl*(v-el)\n", + "}\n" + ] + } + ], + "source": [ + "# first need to run SymtabVisitor to generate Symbol Table\n", + "symv = symtab.SymtabVisitor()\n", + "symv.visit_program(modast)\n", + "# then we can run SympyConductanceVisitor\n", + "sympy_conductance_visitor = visitor.SympyConductanceVisitor()\n", + "sympy_conductance_visitor.visit_program(modast)\n", + "# print BREAKPOINT block\n", + "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0]))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 8920d25c1a..180246d051 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -14,6 +14,8 @@ #include "pybind/pyvisitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" +#include "visitors/sympy_conductance_visitor.hpp" +#include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" #pragma clang diagnostic push @@ -97,5 +99,13 @@ void init_visitor_module(py::module& m) { .def("visit_{{ node.class_name | snake_case }}", &AstLookupVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} + + py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor"); + sympy_solver_visitor.def(py::init<>()) + .def("visit_program", &SympySolverVisitor::visit_program); + + py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor"); + sympy_conductance_visitor.def(py::init<>()) + .def("visit_program", &SympyConductanceVisitor::visit_program); } #pragma clang diagnostic pop \ No newline at end of file From c7a45aa9e323caccdbf63528a8873a82ac80b781 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Tue, 19 Feb 2019 16:16:49 +0100 Subject: [PATCH 136/871] add Pade approx to SympySolverVisitor - add option to approximate solution to ODE with Pade approximant - disabled by default, enable with flag --enable-pade-approx - uses (1,1) Pade approx, which is correct to second order in dt - avoids having to call exp function for CNEXP integrator at each step - added example of use to nmodl-python-sympy-examples.ipynb notebook Change-Id: I151b55b01bde6a926e93c766f4ecdc510cd5597e NMODL Repo SHA: BlueBrain/nmodl@165dd8330ce6bdf909a82b1bcdcfeace1e1bc220 --- .../nmodl-python-sympy-examples.ipynb | 66 +++++++++++++++++-- nmodl/ode.py | 20 ++++-- src/nmodl/language/templates/pyvisitor.cpp | 2 +- src/nmodl/nmodl/arg_handler.cpp | 8 +++ src/nmodl/nmodl/arg_handler.hpp | 3 + src/nmodl/nmodl/main.cpp | 2 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 7 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 5 +- 8 files changed, 98 insertions(+), 15 deletions(-) diff --git a/doc/notebooks/nmodl-python-sympy-examples.ipynb b/doc/notebooks/nmodl-python-sympy-examples.ipynb index f0e932aac1..f2deaf4243 100644 --- a/doc/notebooks/nmodl-python-sympy-examples.ipynb +++ b/doc/notebooks/nmodl-python-sympy-examples.ipynb @@ -207,7 +207,9 @@ { "cell_type": "code", "execution_count": 6, - "metadata": {}, + "metadata": { + "scrolled": true + }, "outputs": [ { "name": "stdout", @@ -237,13 +239,69 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### CONDUCTANCE example" + "Repeat the process, but this time with the option `use_pade_approx` set to `True`.\n", + "\n", + "This returns the (1,1) Pade approximant to the analytic solution, which is correct to second order in `dt`" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE states {\n", + " rates(v, celsius)\n", + " m = (-dt*m+2*dt*minf+2*m*mtau)/(dt+2*mtau)\n", + " h = (-dt*h+2*dt*hinf+2*h*htau)/(dt+2*htau)\n", + " n = (-dt*n+2*dt*ninf+2*n*ntau)/(dt+2*ntau)\n", + "}\n" + ] + } + ], + "source": [ + "# parse NMDOL file\n", + "driver = nmodl.Driver()\n", + "driver.parse_string(channel)\n", + "modast = driver.ast()\n", + "lookup_visitor = visitor.AstLookupVisitor()\n", + "# first need to run SymtabVisitor to generate Symbol Table\n", + "symv = symtab.SymtabVisitor()\n", + "symv.visit_program(modast)\n", + "# then we can run SympySolverVisitor\n", + "sympy_solver_visitor = visitor.SympySolverVisitor(use_pade_approx=True)\n", + "sympy_solver_visitor.visit_program(modast)\n", + "# print DERIVATIVE block\n", + "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### CONDUCTANCE example" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "# parse NMDOL file\n", + "driver = nmodl.Driver()\n", + "driver.parse_string(channel)\n", + "modast = driver.ast()\n", + "lookup_visitor = visitor.AstLookupVisitor()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -262,7 +320,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -294,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "metadata": {}, "outputs": [ { diff --git a/nmodl/ode.py b/nmodl/ode.py index 505207a00f..77327b5fd9 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -12,7 +12,7 @@ raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") -def integrate2c(diff_string, t_var, dt_var, vars): +def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): """Analytically integrate supplied derivative, return solution as C code. Derivative should be of the form "x' = f(x)", @@ -26,6 +26,9 @@ def integrate2c(diff_string, t_var, dt_var, vars): t_var: name of time variable in NEURON dt_var: name of dt variable in NEURON vars: set of variables used in expression, e.g. {"x", "a"} + ues_pade_approx][]: if False: return exact solution + if True: return (1,1) Pade approx to solution + correct to second order in dt_var Returns: String containing analytic integral of derivative as C code @@ -76,11 +79,20 @@ def integrate2c(diff_string, t_var, dt_var, vars): # try to find analytic solution dt = sp.symbols(dt_var, real=True, positive=True) x_0 = sp.symbols(dependent_var, real=True) - solution = sp.dsolve(diffeq, x, ics={x.subs({t: 0}): x_0}).subs({t: dt}) - # note dsolve can return a list of solutions, in which case the above fails + # note dsolve can return a list of solutions, in which case this fails: + solution = sp.dsolve(diffeq, x, ics={x.subs({t: 0}): x_0}).subs({t: dt}).rhs + + if use_pade_approx: + # (1,1) order pade approximant, correct to 2nd order in dt, + # constructed from coefficients of 2nd order taylor expansion + taylor_series = sp.Poly(sp.series(solution, dt, 0, 3).removeO(), dt) + _a0 = taylor_series.nth(0) + _a1 = taylor_series.nth(1) + _a2 = taylor_series.nth(2) + solution = ((_a0*_a1 + (_a1*_a1-_a0*_a2)*dt)/(_a1-_a2*dt)).simplify() # return result as C code in NEURON format - return f"{sp.ccode(x_0)} = {sp.ccode(solution.rhs)}" + return f"{sp.ccode(x_0)} = {sp.ccode(solution)}" def differentiate2c(expression, dependent_var, vars): diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 180246d051..cae65a8cb9 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -101,7 +101,7 @@ void init_visitor_module(py::module& m) { {% endfor %} py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor"); - sympy_solver_visitor.def(py::init<>()) + sympy_solver_visitor.def(py::init<bool>(), py::arg("use_pade_approx")=false) .def("visit_program", &SympySolverVisitor::visit_program); py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor"); diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index feccde7f59..4d5fa42314 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -101,6 +101,13 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { cmd, false); + switch_arg_type pade_approx_arg( + "", + "enable-pade-approx", + "Enable Pade Approx in SymPy analytic integration", + cmd, + false); + switch_arg_type inline_arg( "", "nmodl-inline", @@ -181,6 +188,7 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { mlayout = layout_arg.getValue(); verbose = verbose_arg.getValue(); sympy = sympy_arg.getValue(); + pade_approx = pade_approx_arg.getValue(); inlining = inline_arg.getValue(); localize_with_verbatim = localize_verbatim_arg.getValue(); local_rename = local_rename_arg.getValue(); diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp index 8f5d9c8de6..8a22edf7ab 100644 --- a/src/nmodl/nmodl/arg_handler.hpp +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -34,6 +34,9 @@ struct ArgumentHandler { /// enable SymPy analytic integration bool sympy; + /// enable Pade approx in SymPy analytic integration + bool pade_approx; + /// enable nmodl level inlining bool inlining; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 1b3fc75c55..9518db216a 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -110,7 +110,7 @@ int main(int argc, const char* argv[]) { } if (arg.sympy) { - SympySolverVisitor v; + SympySolverVisitor v(arg.pade_approx); v.visit_program(ast.get()); } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 37deac19cb..fb6de81f9f 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -43,15 +43,16 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { logger->warn("SympySolverVisitor: LHS of differential equation is not a PrimeName"); return; } - auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, - "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars); + "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, + "vars"_a = vars, + "use_pade_approx"_a = use_pade_approx); py::exec(R"( from nmodl.ode import integrate2c exception_message = "" try: - solution = integrate2c(equation_string, t_var, dt_var, vars) + solution = integrate2c(equation_string, t_var, dt_var, vars, use_pade_approx) except Exception as e: # if we fail, fail silently and return empty string solution = "" diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index e3b0d0ce24..fc44945589 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -35,9 +35,10 @@ class SympySolverVisitor: public AstVisitor { /// solver method names const std::string cnexp_method = "cnexp"; - + /// optionally replace solution with (1,1) pade approx + bool use_pade_approx; public: - SympySolverVisitor() = default; + SympySolverVisitor(bool use_pade_approx=false) : use_pade_approx(use_pade_approx) {}; void visit_solve_block(ast::SolveBlock* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; From dde361e29491a7a2ee4d9aac958121572f3ddb54 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Wed, 20 Feb 2019 13:13:10 +0100 Subject: [PATCH 137/871] Expand documentation in notebooks : - update sympy-examples notebook - execute nmodl python tutorial notebook Change-Id: I4695f2f36af6bb67547c01a6be33f604e677a819 NMODL Repo SHA: BlueBrain/nmodl@2ddf4e228dbf75a24c9b2b16bb5a208e113455bb --- .../nmodl-python-sympy-examples.ipynb | 157 ++++-- doc/notebooks/nmodl-python-tutorial.ipynb | 527 ++++++++++++++++-- src/nmodl/visitors/sympy_solver_visitor.cpp | 3 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 4 +- 4 files changed, 585 insertions(+), 106 deletions(-) diff --git a/doc/notebooks/nmodl-python-sympy-examples.ipynb b/doc/notebooks/nmodl-python-sympy-examples.ipynb index f2deaf4243..5210f4e1e7 100644 --- a/doc/notebooks/nmodl-python-sympy-examples.ipynb +++ b/doc/notebooks/nmodl-python-sympy-examples.ipynb @@ -10,7 +10,7 @@ "- solve differential equations to generate solutions for the CNEXP solver (`SympySolverVisitor`)\n", "- differentiate current expressions to generate CONDUCTANCE statements (`SympyConductanceVisitor`)\n", "\n", - "Please see the notebook `nmodl-python-tutorial.ipynb` for a more general tutorial on using the NMODL python interface, including installation instructions." + "Please see the [tutorial notebook](nmodl-python-tutorial.ipynb) for a more general tutorial on using the NMODL python interface, including installation instructions." ] }, { @@ -20,16 +20,14 @@ "outputs": [], "source": [ "import nmodl.dsl as nmodl\n", - "from nmodl.dsl import visitor\n", - "from nmodl.dsl import ast\n", - "from nmodl.dsl import symtab" + "from nmodl.dsl import ast, symtab, visitor" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "hh.mod input file:" + "Starting with the `hh.mod` input file:" ] }, { @@ -149,10 +147,17 @@ "metadata": {}, "outputs": [], "source": [ - "# parse NMDOL file\n", - "driver = nmodl.Driver()\n", - "driver.parse_string(channel)\n", - "modast = driver.ast()\n", + "def parse_mod_to_ast(mod_string):\n", + " # parse NMDOL file (supplied as a string)\n", + " driver = nmodl.Driver()\n", + " driver.parse_string(mod_string)\n", + " modast = driver.ast()\n", + " # run SymtabVisitor to generate Symbol Table\n", + " symv = symtab.SymtabVisitor()\n", + " symv.visit_program(modast)\n", + " # return AST\n", + " return modast\n", + "\n", "lookup_visitor = visitor.AstLookupVisitor()" ] }, @@ -171,6 +176,7 @@ ], "source": [ "# print solve method\n", + "modast = parse_mod_to_ast(channel)\n", "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.SOLVE_BLOCK)[0]))" ] }, @@ -194,22 +200,52 @@ ], "source": [ "# print DERIVATIVE block\n", - "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0]))" + "print(\n", + " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Run `SympySolverVisitor`, then print DERIVATIVE block again:" + "If we run `SympySolverVisitor`, it does the following:\n", + "\n", + "* If the solver method is \"cnexp\":\n", + " * Get list of all global scope variables from the Symbol Table, as well as any local variables in DERIVATIVE block\n", + " * For each differential equation in DERIVATIVE block:\n", + " * Parse equation into SymPy, giving it the list of variables\n", + " * This gives us a differential equation of the form:\n", + " * $\\frac{dm}{dt} = f(m, \\dots)$\n", + " * where the function $f$ depends on $m$, as well as possibly other variables reprensented by $\\dots$ which we assume do not depend on $m$ or $t$\n", + " * Solve equation analytically using [sympy.dsolve](https://docs.sympy.org/latest/modules/solvers/ode.html) to give a solution of the form:\n", + " * $m(t+dt) = g(m(t), dt, \\dots)$\n", + " * where $g$ is some function that depends on the value of $m$ at time t, the timestep $dt$, and the other variables ($\\dots$).\n", + " * Return solution from SymPy as C code using [sympy.printing.ccode](https://docs.sympy.org/latest/_modules/sympy/printing/ccode.html)\n", + " * If we failed to find a solution then revert to existing CNEXP solver routine (same as mod2c or nocmodl)" ] }, { "cell_type": "code", "execution_count": 6, - "metadata": { - "scrolled": true - }, + "metadata": {}, + "outputs": [], + "source": [ + "sympy_solver_visitor = visitor.SympySolverVisitor()\n", + "sympy_solver_visitor.visit_program(modast)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If we print the DERIVATIVE block again we see the results:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, "outputs": [ { "name": "stdout", @@ -225,28 +261,32 @@ } ], "source": [ - "# first need to run SymtabVisitor to generate Symbol Table\n", - "symv = symtab.SymtabVisitor()\n", - "symv.visit_program(modast)\n", - "# then we can run SympySolverVisitor\n", - "sympy_solver_visitor = visitor.SympySolverVisitor()\n", - "sympy_solver_visitor.visit_program(modast)\n", - "# print DERIVATIVE block\n", - "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0]))" + "print(\n", + " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Repeat the process, but this time with the option `use_pade_approx` set to `True`.\n", + "There is an option `use_pade_approx` which if enabled does the following extra step:\n", + "\n", + "* Given the analytic solution $f(t)$:\n", + " * Expand the solution in a Taylor series in `dt`, extract the coefficients $a_i$\n", + " * $f(t + dt) = f(t) + dt f'(t) + dt^2 f''(t) / 2 + \\dots = a_0 + a_1 dt + a_2 dt^2 + \\dots$\n", + " * Construct the (1,1) Pade approximant to the solution using these Taylor coefficients\n", + " * $f_{PADE}(t+dt) = (a_0 a_1 + (a_1^2 - a_0 a_2) dt)/(a_1 - a_2 dt)$\n", + " * Return this approximate solution (correct to second order in $dt$) as C code\n", "\n", - "This returns the (1,1) Pade approximant to the analytic solution, which is correct to second order in `dt`" + "(Replacing the exponential with a Pade aproximant here was suggested in sec 5.2 of (https://www.eccomas2016.org/proceedings/pdf/7366.pdf) - since the overall numerical integration scheme in NEURON is only correct to first or second order in $dt$, it is valid to expand the analytic solution here to the same order and so avoid evaluating the exponential function)\n", + "\n", + "If we now run `SympySolverVisitor` with `use_pade_approx=True`, and print the DERIVATIVE block again, we see the results:" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -263,19 +303,13 @@ } ], "source": [ - "# parse NMDOL file\n", - "driver = nmodl.Driver()\n", - "driver.parse_string(channel)\n", - "modast = driver.ast()\n", - "lookup_visitor = visitor.AstLookupVisitor()\n", - "# first need to run SymtabVisitor to generate Symbol Table\n", - "symv = symtab.SymtabVisitor()\n", - "symv.visit_program(modast)\n", - "# then we can run SympySolverVisitor\n", + "modast = parse_mod_to_ast(channel)\n", "sympy_solver_visitor = visitor.SympySolverVisitor(use_pade_approx=True)\n", "sympy_solver_visitor.visit_program(modast)\n", "# print DERIVATIVE block\n", - "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0]))" + "print(\n", + " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", + ")" ] }, { @@ -286,16 +320,10 @@ ] }, { - "cell_type": "code", - "execution_count": 8, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "# parse NMDOL file\n", - "driver = nmodl.Driver()\n", - "driver.parse_string(channel)\n", - "modast = driver.ast()\n", - "lookup_visitor = visitor.AstLookupVisitor()" + "The CONDUCTANCE keyword has been introduced to NEURON as well as CoreNEURON. If the i/v relation is ohmic in BREAKPOINT block then one can use CONDUCTANCE keyword for efficiency." ] }, { @@ -308,13 +336,17 @@ "output_type": "stream", "text": [ "USEION na READ ena WRITE ina\n", - "USEION k READ ek WRITE ik\n" + "USEION k READ ek WRITE ik\n", + "NONSPECIFIC_CURRENT il\n" ] } ], "source": [ + "modast = parse_mod_to_ast(channel)\n", "# print USEION and NONSPECIFIC current statements\n", - "for node in lookup_visitor.lookup(modast, ast.AstNodeType.USEION or ast.AstNodeType.NONSPECIFIC):\n", + "for node in lookup_visitor.lookup(modast, ast.AstNodeType.USEION):\n", + " print(nmodl.to_nmodl(node))\n", + "for node in lookup_visitor.lookup(modast, ast.AstNodeType.NONSPECIFIC):\n", " print(nmodl.to_nmodl(node))" ] }, @@ -340,14 +372,37 @@ ], "source": [ "# print BREAKPOINT\n", - "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0]))" + "print(\n", + " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0])\n", + ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "BREAKPOINT block after running `SympyConductanceVisitor`:" + "If we run `SympyConductanceVisitor`, it does the following:\n", + "\n", + "* For each ion write statement $i = \\dots$ in the BREAKPOINT block\n", + " * Differentiate to find the conductance $g_i=di/dv$\n", + " * If this $g_i$ coincides with an existing variable, e.g. $g$, add to BREAKPOINT the statement:\n", + " * CONDUCTANCE g USEION ion_name\n", + " * If not, also need to declare and asign a variable for the calculated conductance:\n", + " * LOCAL g_i_0\n", + " * CONDUCTANCE g_i_0 USEION ion_name\n", + " * g_i_0 = ...\n", + " * But if there is an existing CONDUCTANCE statement, then do not modify it\n", + "\n", + "\n", + "* NOTE: currently we just differentiate the equation, assuming that the variables do not depend on v\n", + " * Ideally we should do something like:\n", + " * check after inlining which variables are written to in BREAKPOINT block\n", + " * get rhs of each such expression, and substitute it (recursively) for the lhs in SymPy\n", + " * this should give a (complicated) expression $i = ...$ where all v dependence is explicit\n", + " * then differentiate this w.r.t v\n", + " * then simplify and try to write the result in terms of an existing variable\n", + "\n", + "Here is the BREAKPOINT block after running `SympyConductanceVisitor`:" ] }, { @@ -374,14 +429,12 @@ } ], "source": [ - "# first need to run SymtabVisitor to generate Symbol Table\n", - "symv = symtab.SymtabVisitor()\n", - "symv.visit_program(modast)\n", - "# then we can run SympyConductanceVisitor\n", "sympy_conductance_visitor = visitor.SympyConductanceVisitor()\n", "sympy_conductance_visitor.visit_program(modast)\n", "# print BREAKPOINT block\n", - "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0]))" + "print(\n", + " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0])\n", + ")" ] } ], diff --git a/doc/notebooks/nmodl-python-tutorial.ipynb b/doc/notebooks/nmodl-python-tutorial.ipynb index ba05090872..faa7a87ab2 100644 --- a/doc/notebooks/nmodl-python-tutorial.ipynb +++ b/doc/notebooks/nmodl-python-tutorial.ipynb @@ -9,7 +9,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -19,9 +19,26 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "require.config({\n", + " paths: {\n", + " d3: \"https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min\"\n", + " }\n", + "});\n" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "%%javascript\n", "require.config({\n", @@ -33,18 +50,266 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "define('draw_tree', ['d3'], function(d3) {\n", + "\n", + " /// draw d3 tree to given div container\n", + " function draw_ast(container, jsontree) {\n", + "\n", + " // container margin\n", + " var margin = {\n", + " top: 20,\n", + " right: 20,\n", + " bottom: 20,\n", + " left: 20\n", + " };\n", + "\n", + " /// diameter equal to container width (e.g. notebook cell)\n", + " var diameter = d3.select(container).node().getBoundingClientRect().width;\n", + "\n", + " /// width / ehight for the box\n", + " var width = diameter;\n", + " var height = diameter;\n", + "\n", + " /// animation duration\n", + " var duration = 200;\n", + "\n", + " /// unique ids for nodes\n", + " var id = 0;\n", + "\n", + " /// define tree layout\n", + " // see: https://d3-wiki.readthedocs.io/zh_CN/master/Tree-Layout/\n", + " var tree = d3.layout.tree()\n", + " .size([360, diameter / 2 - 80])\n", + " .separation(function(a, b) {\n", + " return (a.parent == b.parent ? 1 : 10) / a.depth;\n", + " });\n", + " var diagonal = d3.svg.diagonal.radial()\n", + " .projection(function(d) {\n", + " return [d.y, d.x / 180 * Math.PI];\n", + " });\n", + "\n", + " /// create svg element inside container\n", + " var tree_svg = d3.select(container).append(\"svg\")\n", + " .attr(\"width\", width)\n", + " .attr(\"height\", height)\n", + " .append(\"g\")\n", + " .attr(\"transform\", \"translate(\" + diameter / 2 + \",\" + diameter / 2 + \")\");\n", + "\n", + " /// start drawing tree\n", + " jsontree.x0 = width / 2;\n", + " jsontree.y0 = height / 2;\n", + " update(jsontree, tree_svg, jsontree);\n", + "\n", + " /// toggle children state on click\n", + " function click(d, destnode, jsonroot) {\n", + " if (d.children) {\n", + " d._children = d.children;\n", + " d.children = null;\n", + " } else {\n", + " d.children = d._children;\n", + " d._children = null;\n", + " }\n", + " update(d, destnode, jsonroot);\n", + " }\n", + "\n", + " /// collapse nodes on click\n", + " function collapse(d) {\n", + " if (d.children) {\n", + " d._children = d.children;\n", + " d._children.forEach(collapse);\n", + " d.children = null;\n", + " }\n", + " }\n", + "\n", + " function update(source, destnode, jsonroot) {\n", + " /// compute the new tree layout\n", + " var nodes = tree.nodes(jsonroot);\n", + " var links = tree.links(nodes);\n", + "\n", + " /// normalize for fixed-depth\n", + " nodes.forEach(function(d) {\n", + " d.y = d.depth * 80;\n", + " });\n", + "\n", + " /// update the nodes\n", + " var node = destnode.selectAll(\"g.node\")\n", + " .data(nodes, function(d) {\n", + " return d.id || (d.id = ++id);\n", + " });\n", + "\n", + " /// enter any new nodes at the parent's previous position\n", + " var nodeEnter = node.enter().append(\"g\")\n", + " .attr(\"class\", \"node\")\n", + " .on(\"click\", function(d) {\n", + " click(d, destnode, jsonroot);\n", + " });\n", + "\n", + " nodeEnter.append(\"circle\")\n", + " .attr(\"r\", 1e-6)\n", + " .style(\"fill\", function(d) {\n", + " return d._children ? \"lightsteelblue\" : \"#fff\";\n", + " });\n", + "\n", + " nodeEnter.append(\"text\")\n", + " .attr(\"x\", 10)\n", + " .attr(\"dy\", \".55em\")\n", + " .attr(\"text-anchor\", \"start\")\n", + " .text(function(d) {\n", + " return d.name;\n", + " })\n", + " .style(\"fill-opacity\", 1e-6);\n", + "\n", + " /// transition nodes to their new position\n", + " var nodeUpdate = node.transition()\n", + " .duration(duration)\n", + " .attr(\"transform\", function(d) {\n", + " return \"rotate(\" + (d.x - 90) + \")translate(\" + d.y + \")\";\n", + " })\n", + "\n", + " nodeUpdate.select(\"circle\")\n", + " .attr(\"r\", 4.5)\n", + " .style(\"fill\", function(d) {\n", + " return d._children ? \"lightsteelblue\" : \"#fff\";\n", + " });\n", + "\n", + " nodeUpdate.select(\"text\")\n", + " .style(\"fill-opacity\", 1)\n", + " .attr(\"transform\", function(d) {\n", + " return d.x < 180 ? \"translate(0)\" : \"rotate(180)translate(-\" + (d.name.length + 50) + \")\";\n", + " });\n", + "\n", + " /// appropriate transform\n", + " var nodeExit = node.exit().transition()\n", + " .duration(duration)\n", + " .remove();\n", + "\n", + " nodeExit.select(\"circle\")\n", + " .attr(\"r\", 1e-6);\n", + "\n", + " nodeExit.select(\"text\")\n", + " .style(\"fill-opacity\", 1e-6);\n", + "\n", + " /// update the links\n", + " var link = destnode.selectAll(\"path.link\")\n", + " .data(links, function(d) {\n", + " return d.target.id;\n", + " });\n", + "\n", + " /// enter any new links at the parent's previous position\n", + " link.enter().insert(\"path\", \"g\")\n", + " .attr(\"class\", \"link\")\n", + " .attr(\"d\", function(d) {\n", + " var o = {\n", + " x: source.x0,\n", + " y: source.y0\n", + " };\n", + " return diagonal({\n", + " source: o,\n", + " target: o\n", + " });\n", + " });\n", + "\n", + " /// transition links to their new position\n", + " link.transition()\n", + " .duration(duration)\n", + " .attr(\"d\", diagonal);\n", + "\n", + " /// transition exiting nodes to the parent's new position\n", + " link.exit().transition()\n", + " .duration(duration)\n", + " .attr(\"d\", function(d) {\n", + " var o = {\n", + " x: source.x,\n", + " y: source.y\n", + " };\n", + " return diagonal({\n", + " source: o,\n", + " target: o\n", + " });\n", + " })\n", + " .remove();\n", + "\n", + " /// stash the old positions for transition\n", + " nodes.forEach(function(d) {\n", + " d.x0 = d.x;\n", + " d.y0 = d.y;\n", + " });\n", + " }\n", + "\n", + " }\n", + "\n", + " return draw_ast;\n", + "});" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "Javascript(filename='tree.js')" ] }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "<style>\n", + "\n", + ".node {\n", + " cursor: pointer;\n", + "}\n", + "\n", + ".node circle {\n", + " fill: #d49c9c;\n", + " stroke: #8c6666;\n", + " stroke-width: 1.5px;\n", + "}\n", + "\n", + ".node text {\n", + " font-size: 11px !important;\n", + " font-family: sans-serif;\n", + " fill: #4545b5;\n", + "}\n", + "\n", + ".link {\n", + " fill: none;\n", + " stroke: #efcece;\n", + " stroke: #efceed;\n", + " stroke-width: 2px;\n", + "}\n", + "\n", + ".templink {\n", + " fill: none;\n", + " stroke: red;\n", + " stroke-width: 2px;\n", + "}\n", + "</style>\n" + ], + "text/plain": [ + "<IPython.core.display.HTML object>" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "HTML(\"\"\"\n", "<style>\n", @@ -135,7 +400,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -151,7 +416,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -210,9 +475,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "driver = nmodl.Driver()\n", "driver.parse_string(channel)" @@ -227,7 +503,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -243,9 +519,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "{\"Program\":[{\"NeuronBlock\":[{\"StatementBlock\":[{\"Suffix\":[{\"Name\":[{\"String\":[{\"name\":\"SUFFIX\"}]}]},\n" + ] + } + ], "source": [ "print ('%.100s' % modast) # only first 100 characters\n", "import json\n", @@ -255,9 +539,25 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "(function(element){\n", + " require(['draw_tree'], function(draw) { draw(element.get(0), {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"SUFFIX\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"CaDynamics\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"Suffix\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ca\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"ReadIonVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"WriteIonVar\"}], \"name\": \"Useion\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}], \"name\": \"Range\"}], \"name\": \"StatementBlock\"}], \"name\": \"NeuronBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"mV\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millivolt\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mA\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"milliamp\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"faraday\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"coulombs\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"FactorDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"molar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"1/liter\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millimolar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"micron\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}], \"name\": \"UnitBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.05\"}], \"name\": \"Double\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"80\"}], \"name\": \"Integer\"}, {\"children\": [{\"children\": [{\"name\": \"ms\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.1\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.0001\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}], \"name\": \"ParamBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mA/cm2\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"DependentBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"InitialBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"StateBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"cnexp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"SolveBlock\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"BreakpointBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}, {\"children\": [{\"name\": \"1\"}], \"name\": \"Integer\"}], \"name\": \"PrimeName\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"-\"}], \"name\": \"UnaryOperator\"}, {\"children\": [{\"children\": [{\"name\": \"10000\"}], \"name\": \"Double\"}], \"name\": \"ParenExpression\"}], \"name\": \"UnaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"2\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"BinaryExpression\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ParenExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"DiffEqExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"DerivativeBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"temp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"LocalVar\"}], \"name\": \"LocalListStatement\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"name\": \"1\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"+\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"FunctionBlock\"}], \"name\": \"Program\"}) });\n", + " })(element);" + ], + "text/plain": [ + "<IPython.core.display.Javascript object>" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "Javascript(\"\"\"(function(element){\n", " require(['draw_tree'], function(draw) { draw(element.get(0), %s) });\n", @@ -284,7 +584,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -302,9 +602,19 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "STATE {\n", + " cai (mM)\n", + "}\n" + ] + } + ], "source": [ "states = lookup_visitor.lookup(modast, ast.AstNodeType.STATE_BLOCK)\n", "for state in states:\n", @@ -322,9 +632,22 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1 differential equation(s) exist : \n", + "\t cai' = -(10000)*(ica*gamma/(2*FARADAY*depth))-(cai-minCai)/decay \n", + "1 prime variables exist : cai'\n", + "4 range variables exist : decay gamma minCai depth\n", + "4 parameter variables exist : decay gamma minCai depth\n", + "17 units uses : (mV) (millivolt) (mA) (milliamp) (faraday) (coulombs) (molar) (1/liter) (mM) (millimolar) (um) (micron) (ms) (um) (mM) (mA/cm2) (mM)" + ] + } + ], "source": [ "odes = lookup_visitor.lookup(modast, ast.AstNodeType.DIFF_EQ_EXPRESSION)\n", "primes = lookup_visitor.lookup(modast, ast.AstNodeType.PRIME_NAME)\n", @@ -370,9 +693,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "foo = 1+gamma\n" + ] + } + ], "source": [ "functions = lookup_visitor.lookup(modast, ast.AstNodeType.FUNCTION_BLOCK)\n", "function = functions[0] # first function\n", @@ -406,7 +737,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -424,9 +755,47 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "------------------------------------------------------------------------------------------------------------------------------\n", + "| NMODL_GLOBAL [Program IN None] POSITION : UNKNOWN SCOPE : GLOBAL |\n", + "------------------------------------------------------------------------------------------------------------------------------\n", + "| NAME | PROPERTIES | STATUS | LOCATION | VALUE | # READS | # WRITES | \n", + "------------------------------------------------------------------------------------------------------------------------------\n", + "| ca | ion | | UNKNOWN | | 0 | 0 | \n", + "| ica | dependent_def read_ion | | UNKNOWN | | 0 | 0 | \n", + "| cai | prime_name dependent_def write_ion state_var | | UNKNOWN | | 0 | 0 | \n", + "| decay | range parameter | | UNKNOWN | 80.000000 | 0 | 0 | \n", + "| gamma | range parameter | | UNKNOWN | 0.050000 | 0 | 0 | \n", + "| minCai | range parameter | | UNKNOWN | 0.000100 | 0 | 0 | \n", + "| depth | range parameter | | UNKNOWN | 0.100000 | 0 | 0 | \n", + "| mV | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| mA | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| FARADAY | factor_def | | 11.5-11 | | 0 | 0 | \n", + "| molar | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| mM | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| um | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| states | derivative_block to_solve | | 36.1-10 | | 0 | 0 | \n", + "| foo | function_block | | 40.1-8 | | 0 | 0 | \n", + "------------------------------------------------------------------------------------------------------------------------------\n", + "\n", + " --------------------------------------------------------------------------------------------------\n", + " | StatementBlock4 [StatementBlock IN foo] POSITION : 40.16 SCOPE : LOCAL |\n", + " --------------------------------------------------------------------------------------------------\n", + " | NAME | PROPERTIES | STATUS | LOCATION | VALUE | # READS | # WRITES | \n", + " --------------------------------------------------------------------------------------------------\n", + " | temp | local | | UNKNOWN | | 0 | 0 | \n", + " --------------------------------------------------------------------------------------------------\n", + "\n" + ] + } + ], "source": [ "symv.visit_program(modast)\n", "table = modast.get_symbol_table()\n", @@ -444,9 +813,17 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cai [Properties : prime_name dependent_def write_ion state_var]\n" + ] + } + ], "source": [ "cai = table.lookup('cai')\n", "print (cai)" @@ -467,9 +844,20 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "decay [Properties : range parameter]\n", + "gamma [Properties : range parameter]\n", + "minCai [Properties : range parameter]\n", + "depth [Properties : range parameter]\n" + ] + } + ], "source": [ "range_vars = table.get_variables_with_properties(symtab.NmodlType.range_var)\n", "for var in range_vars:\n", @@ -485,9 +873,18 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ica [Properties : dependent_def read_ion]\n", + "cai [Properties : prime_name dependent_def write_ion state_var]\n" + ] + } + ], "source": [ "ions_var = table.get_variables_with_properties(symtab.NmodlType.read_ion_var | symtab.NmodlType.write_ion_var, False)\n", "for var in ions_var:\n", @@ -505,9 +902,22 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.05\n", + "0.1\n", + "0.0001\n", + "10000.0\n", + "2.0\n", + "1.0\n" + ] + } + ], "source": [ "from nmodl.dsl import ast, visitor\n", "\n", @@ -531,9 +941,24 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "gamma\n", + "0.05\n", + "decay\n", + "80\n", + "depth\n", + "0.1\n", + "minCai\n", + "0.0001\n" + ] + } + ], "source": [ "class ParameterVisitor(visitor.AstVisitor):\n", " \n", diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index fb6de81f9f..f4e4353e6f 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -45,8 +45,7 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { } auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, - "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, - "vars"_a = vars, + "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, "use_pade_approx"_a = use_pade_approx); py::exec(R"( from nmodl.ode import integrate2c diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index fc44945589..05e89f4782 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -37,8 +37,10 @@ class SympySolverVisitor: public AstVisitor { const std::string cnexp_method = "cnexp"; /// optionally replace solution with (1,1) pade approx bool use_pade_approx; + public: - SympySolverVisitor(bool use_pade_approx=false) : use_pade_approx(use_pade_approx) {}; + SympySolverVisitor(bool use_pade_approx = false) + : use_pade_approx(use_pade_approx){}; void visit_solve_block(ast::SolveBlock* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; From f17a39215a9dd882c1f97042489a5a0f56ef2b5f Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Thu, 21 Feb 2019 11:23:56 +0100 Subject: [PATCH 138/871] Minor updates in README (BlueBrain/nmodl#2) NMODL Repo SHA: BlueBrain/nmodl@7ef3c04e57922113e6843f2c83a72b111489c58c --- README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3ccc3c512a..5926ce76c1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ This is a source-to-source code generation framework for NMODL. #### Cloning Source ``` -git clone --recurse-submodules ssh://bbpcode.epfl.ch:22/incubator/nocmodl +git clone --recurse-submodules git@github.com:BlueBrain/nmodl.git ``` Note: This project uses git submodules which must be cloned along with the repository @@ -20,7 +20,7 @@ itself. - C++ compiler (with c++11 support) - Python3 (>=3.6) - Python yaml (pyyaml) -- Jinja2 (>=2.10) +- Python Jinja2 (>=2.10) - Python textwrap - pybind11 (which should be fetched as submodule in ext/pybind11) - pytest (>=4.0.0) (only for tests) @@ -30,7 +30,7 @@ itself. Many systems have older version of Flex and Bison. Make sure to have latest version of Flex (>=2.6) and Bison (>=3.0). -On macos X we typically install packages via brew/macport and pip : +On macos X packages are typically installed via brew/macport and pip: ``` brew install flex bison @@ -47,7 +47,7 @@ Make sure to have latest flex/bison in $PATH : export PATH=/usr/local/opt/flex:/usr/local/opt/bison:$PATH ``` -On Ubuntu (>=16.04) you should already have recent version of flex/bison: +On Ubuntu (>=16.04) flex/bison versions are recent enough: ``` $ flex --version @@ -57,9 +57,9 @@ $ bison --version bison (GNU Bison) 3.0.4 ``` -NMODL depends on Python 3, so make sure you have an up-to-date Python installation. On macos X Python 3 can be installed -through e.g. homebrew. On Ubuntu, depending on your version, Python 3 is either already available by default or can be easily -obtained through +NMODL depends on Python 3, so make sure to have an up-to-date Python installation. +On macos X Python 3 can be installed through e.g. homebrew. On Ubuntu, depending on your version, +Python 3 is either already available by default or can be easily obtained through ``` $ apt-get install python3 @@ -71,7 +71,6 @@ Python yaml can be installed as : apt-get install python-yaml ``` - #### Build Once all dependencies are in place, you can build cmake project as : From ec0ce154bfa757d295f0d33db5c212c8aa04a158 Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Wed, 20 Feb 2019 17:35:00 +0100 Subject: [PATCH 139/871] Remove Gerrit configuration NMODL Repo SHA: BlueBrain/nmodl@e2f2c1c4eb7b16913ffa29b0fdd9d602d4f485c1 --- .gitreview | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .gitreview diff --git a/.gitreview b/.gitreview deleted file mode 100644 index 054b6aa692..0000000000 --- a/.gitreview +++ /dev/null @@ -1,6 +0,0 @@ -[gerrit] -host=bbpcode.epfl.ch -port=22 -project=incubator/nocmodl -defaultbranch=master -defaultremote=origin From 8abbbed032da6bf5663cafb57d63037001ccdf8f Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Wed, 20 Feb 2019 17:35:12 +0100 Subject: [PATCH 140/871] Fix 2 shift/reduce conflicts in c11.yy * the first conflict was between the 2 expressions below when cursor is at position "."" The parser was already shifting, which is the expected behavior. ``` : IF "(" expression ")" statement . ELSE statement | IF "(" expression ")" statement ``` * However the second conflict shows that the parser was reducing instead of shifting. Before: ``` State 27 157 atomic_type_specifier: ATOMIC . "(" type_name ")" 161 type_qualifier: ATOMIC . "(" shift, and go to state 49 "(" [reduce using rule 161 (type_qualifier)] $default reduce using rule 161 (type_qualifier) ``` After: ``` State 27 157 atomic_type_specifier: ATOMIC . "(" type_name ")" 161 type_qualifier: ATOMIC . "(" shift, and go to state 49 $default reduce using rule 161 (type_qualifier) ``` NMODL Repo SHA: BlueBrain/nmodl@d135483422c002a1c78ed4653d4988650083a08e --- src/nmodl/parser/c11.yy | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index 069e2ad643..e493523a7b 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -27,7 +27,7 @@ /** enable tracing parser for debugging */ %define parse.trace -/** add extra arguments to yyparse() and yylexe() methods */ +/** add extra arguments to yyparse() and yylex() methods */ %parse-param {class Lexer& scanner} %parse-param {class Driver& driver} %lex-param {c11::Scanner &scanner} @@ -110,6 +110,7 @@ %token <std::string> CASE %token <std::string> DEFAULT %token <std::string> IF +%token <std::string> THEN %token <std::string> ELSE %token <std::string> SWITCH %token <std::string> WHILE @@ -154,6 +155,11 @@ %token <std::string> QUESTION "?" %token END 0 "End of file" +// Priorities/associativities. +%nonassoc ATOMIC +%right THEN ELSE // Same precedence, but "shift" wins. +%left OPEN_PARENTHESIS + %{ #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" @@ -635,7 +641,7 @@ expression_statement selection_statement : IF "(" expression ")" statement ELSE statement - | IF "(" expression ")" statement + | IF "(" expression ")" statement %prec THEN | SWITCH "(" expression ")" statement ; From 64d5401d701c8ef435f912ee4270cd66927b3bf8 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Fri, 22 Feb 2019 14:28:42 +0100 Subject: [PATCH 141/871] Update README (BlueBrain/nmodl#4) - added brief introduction - instructions for os x and ubuntu installation - code generation & coreneuron usage - added reference to jupyter notebooks NMODL Repo SHA: BlueBrain/nmodl@66ce33a6604565c3b6624cca44b7d3720554a9b5 --- README.md | 237 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 140 insertions(+), 97 deletions(-) diff --git a/README.md b/README.md index 5926ce76c1..4ec0337ea0 100644 --- a/README.md +++ b/README.md @@ -1,143 +1,201 @@ ## NMODL +> NEURON MOdeling Language Code Generation Framework -This is a source-to-source code generation framework for NMODL. +NMODL is a code generation framework for [NEURON Modeling Language](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html). The main goals of this framework are : +* Support for full NMODL specification +* Providing modular tools for parsing, analysis and optimization +* High level python interface +* Optimised code generation for modern CPU/GPU architectures +* Code generation backends compatible with existing simulators +* Flexibility to implement new simulator backend with minimal efforts + +It is primarily designed to support optimised code generation backends but the underlying infrastructure can be used with high level python interface for model introspection and analysis. + +### Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. #### Cloning Source +This project uses git submodules which must be cloned along with the repository itself: + ``` -git clone --recurse-submodules git@github.com:BlueBrain/nmodl.git +git clone --recursive git@github.com:BlueBrain/nmodl.git ``` -Note: This project uses git submodules which must be cloned along with the repository -itself. +#### Prerequisites -#### Dependencies +To build the project from source, modern C++ compiler with c++11 support is necessary. Make sure you have following packages available: - flex (>=2.6) - bison (>=3.0) - CMake (>=3.1) -- C++ compiler (with c++11 support) -- Python3 (>=3.6) -- Python yaml (pyyaml) -- Python Jinja2 (>=2.10) -- Python textwrap -- pybind11 (which should be fetched as submodule in ext/pybind11) -- pytest (>=4.0.0) (only for tests) +- Python (>=3.6) +- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.2), textwrap -#### Getting Dependencies +##### On OS X -Many systems have older version of Flex and Bison. Make sure to have latest version of Flex (>=2.6) and Bison (>=3.0). +Often we have older version of flex and bison. We can get recent version of dependencies via brew/macport and pip: +``` +brew install flex bison cmake python3 +pip3 install Jinja2 PyYAML pytest sympy +``` -On macos X packages are typically installed via brew/macport and pip: +Make sure to have latest flex/bison in $PATH : ``` -brew install flex bison -pip install pyyaml +export PATH=/usr/local/opt/flex:/usr/local/opt/bison:/usr/local/bin/:$PATH +``` + +##### On Ubuntu + +On Ubuntu (>=16.04) flex/bison versions are recent enough. We can install Python3 and dependencies using: -# Or -pip3 install pyyaml +``` +apt-get install python3 python3-pip +pip3 install Jinja2 PyYAML pytest sympy ``` -Make sure to have latest flex/bison in $PATH : +##### On BB5 +On Blue Brain 5 system, we can load following modules : ``` -export PATH=/usr/local/opt/flex:/usr/local/opt/bison:$PATH +module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 python3-dev ``` -On Ubuntu (>=16.04) flex/bison versions are recent enough: +#### Build Project + +##### Using CMake + +Once all dependencies are in place, build project as: ``` -$ flex --version -flex 2.6.4 +mkdir -p nmodl/build +cd nocmodl/build +cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl +make -j && make install +``` + +And set PYTHONPATH as: -$ bison --version -bison (GNU Bison) 3.0.4 ``` +export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH +``` + +##### Using pip -NMODL depends on Python 3, so make sure to have an up-to-date Python installation. -On macos X Python 3 can be installed through e.g. homebrew. On Ubuntu, depending on your version, -Python 3 is either already available by default or can be easily obtained through +Alternatively, we can build the project using pip as: ``` -$ apt-get install python3 +pip3 install nmodl/. --target=$HOME/nmodl # remove --target option if want to install ``` -Python yaml can be installed as : +And if --target option was used, set PYTHONPATH as: ``` -apt-get install python-yaml +export PYTHONPATH=$HOME/nmodl:$PYTHONPATH ``` -#### Build +#### Testing Installed Module -Once all dependencies are in place, you can build cmake project as : +If you install NMODL using CMake, you can run tests from build directory as: ``` -mkdir nocmodl/build -cd nocmodl/build -cmake .. -make +$ make test +Running tests... +Test project /home/kumbhar/nmodl/build + Start 1: ModToken +1/7 Test #1: ModToken ......................... Passed 0.01 sec +... ``` -If flex / bison is installed in non-standard location then you can do : +We can use nmodl module from python as: ``` -cmake .. -DCMAKE_PREFIX_PATH="/usr/local/opt/bison/;/usr/local/opt/flex" +$ python3 +>>> import nmodl.dsl as nmodl +>>> driver = nmodl.Driver() +>>> driver.parse_string("NEURON { SUFFIX hh }") +True +>>> modast = driver.ast() +>>> print ('%.100s' % modast) +{"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]}, +``` - # Or, +NMODL is now setup correctly! -cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison -``` +#### Understand Python API -On BB5, you can do: +The user documentation for NMODL is incomplete and not available on GitHub yet. The best way to understand the API and usage is using Jupyter notebooks provided in docs directory : ``` -module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 +cd nmodl/doc/notebooks +jupyter notebook +``` + +You can look at [nmodl-python-tutorial.ipynb](doc/notebooks/nmodl-python-tutorial.ipynb) notebook for python interface tutorial. There is also [nmodl-python-sympy-examples.ipynb](doc/notebooks/nmodl-python-sympy-examples.ipynb)showing how [SymPy](https://www.sympy.org/en/index.html) is used in NMODL. + + +#### Using NMODL For Code Generation + +Once you install project using CMake, you will have following binaries in the installation directory: -mkdir build && cd build -cmake .. -make VERBOSE=1 -make test ``` +$ tree $HOME/nmodl/bin -#### Running CoreNEURON +|-- lexer +| |-- c_lexer +| `-- nmodl_lexer +|-- nmodl +|-- parser +| |-- c_parser +| `-- nmodl_parser +`-- visitor + `-- nmodl_visitor +``` -You can use NMODL code generator instead of MOD2 for CoreNEURON. You have to simply use extra CMake argument `-DMOD2C` pointing to `nmodl` binary: +The `nmodl_lexer` and `nmodl_parser` are standalone tools for testing mod files. If you want to test if given mod file can be successfully parsed by NMODL then you can do: ``` -git clone --recursive https://github.com/BlueBrain/CoreNeuron.git coreneuron -mkdir coreneuron/build && cd coreneuron/build -cmake .. -DMOD2C=/path/nocmodl/build/bin/nmodl -make -make test +nmodl_parser --file <path>/hh.mod ``` -#### Using NMODL +To see how NMODL will generate the code for given mod file, you can do: + +``` +nmodl <path>/hh.mod +``` -To run code generator, you can do: +This will generate hh.cpp in the current directory. There are different optimisation options for code generation that you can see using: ``` -./bin/nmodl ../test/input/channel.mod +nmodl --help ``` -You can independently run lexer, parser or visitors as: + +#### Using NMODL With CoreNEURON + +We can use NMODL instead of MOD2 for CoreNEURON. You have to simply use extra CMake argument `-DMOD2C` pointing to `nmodl` binary: ``` -./bin/nmodl_lexer --file ../test/input/channel.mod -./bin/nmodl_parser --file ../test/input/channel.mod -./bin/nmodl_visitor --file ../test/input/channel.mod +git clone --recursive https://github.com/BlueBrain/CoreNeuron.git coreneuron +mkdir coreneuron/build && cd coreneuron/build +cmake .. -DMOD2C=<path>/bin/nmodl +make -j +make test ``` -#### Development Conventions +> Note that the code generation backend is not complete yet. + -Enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` -CMake variables to ensure that your contributions follow -the coding conventions of this project. +### Development Conventions + +If you are developing NMODL, make sure to enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` +CMake variables to ensure that your contributions follow the coding conventions of this project: -##### Usage ```cmake cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON <path> ``` @@ -150,12 +208,7 @@ make clang-format cmake-format ``` The second option activates Git hooks that will discard commits that -do not comply with coding conventions of this project. - - -##### Requirements - -These 2 CMake variables require additional utilities: +do not comply with coding conventions of this project. These 2 CMake variables require additional utilities: * [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) * [cmake-format](https://github.com/cheshirekow/cmake_format) Python package @@ -164,32 +217,12 @@ These 2 CMake variables require additional utilities: _ClangFormat_ can be installed on Linux thanks to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a [brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) -to install ClangFormat 7. - -_cmake-format_ and _pre-commit_ utilities can be installed with *pip*. - -#### Running Tests - - You can run unit tests as: - -``` - make test -``` +to install clang-format 7. _cmake-format_ and _pre-commit_ utilities can be installed with *pip*. - Or individual test binaries with verbode output: - ``` - ./bin/test/testlexer -s - ./bin/test/testmodtoken -s - ./bin/test/testprinter -s - ./bin/test/testsymtab -s - ./bin/test/testvisitor -s - ``` +##### Memory Leaks and Clang Tidy - -#### Memory Leaks and Clang Tidy - -Test memory leaks using : +If you want to test for memory leaks, do : ``` valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer @@ -206,3 +239,13 @@ If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake > ``` cmake .. -DENABLE_CLANG_TIDY=ON ``` + +##### Flex / Bison Paths + +If flex / bison is not in default $PATH, you can provide path to cmake as: + +``` +cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ + -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ + -DCMAKE_INSTALL_PREFIX=$HOME/nmodl +``` From 8fdceefe7cdfac3f812c19e022afd5ba69b7c826 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Fri, 22 Feb 2019 14:39:05 +0100 Subject: [PATCH 142/871] Update README typo (BlueBrain/nmodl#5) - nodmodl to nmodl NMODL Repo SHA: BlueBrain/nmodl@1a7f18b35c76fc99dc2e3f8b1650cf33ace7ca83 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4ec0337ea0..67de5476c7 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Once all dependencies are in place, build project as: ``` mkdir -p nmodl/build -cd nocmodl/build +cd nmodl/build cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl make -j && make install ``` From 660e59a713391fc43d8d530b4498650c1cd6a4cd Mon Sep 17 00:00:00 2001 From: Antonio B <antonio.bellotta@epfl.ch> Date: Sat, 23 Feb 2019 20:03:14 +0100 Subject: [PATCH 143/871] Fix for empty unit declaration: (BlueBrain/nmodl#3) - allow empty units in nmodl parser - added unit test for parser - move invalid unit tests to valid construct NMODL Repo SHA: BlueBrain/nmodl@e53a7a3f7fcc14205d2358764f8bf9afd6356507 --- doc/test_status.md | 2 +- src/nmodl/parser/nmodl.yy | 4 +-- test/nmodl/transpiler/parser/parser.cpp | 13 +++++++ .../transpiler/utils/nmodl_constructs.cpp | 36 ++++++++++++------- 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/doc/test_status.md b/doc/test_status.md index e0edb8db18..78ff56381c 100644 --- a/doc/test_status.md +++ b/doc/test_status.md @@ -60,7 +60,7 @@ The following symbols are used in the document to descrive the status : |PROTECT | ✅ | |MUTEXLOCK | ✅ | |MUTEXUNLOCK | ✅ | -| \|\| | ✅ | +| | | | ✅ | | && | ✅ | | \> | ✅ | | \< | ✅ | diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 2db9656fa6..c5942e6af4 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -574,11 +574,9 @@ units : { $$ = nullptr; } unit : "(" { scanner.scan_unit(); } ")" { + // @todo : empty units should be handled in semantic analysis auto unit = scanner.get_unit(); auto text = unit->eval(); - if (text.size() == 0) { - error(scanner.loc, "empty unit"); - } $$ = new ast::Unit(unit); } ; diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 854f88b3f4..9cd20a10ec 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -99,6 +99,19 @@ SCENARIO("Macros can be used anywhere in NMODL program") { } } +SCENARIO("Parser for empty unit") { + std::string nmodl_text = R"( + FUNCTION ssCB(kdf(), kds()) (mM) { + + } + )"; + WHEN("FUNCTION is defined with empty unit") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct(nmodl_text)); + } + } +} + SCENARIO("Parser test for valid NMODL grammar constructs") { for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index d8be03f608..39f6f73e6b 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -156,19 +156,7 @@ std::map<std::string, NmodlTestCase> nmdol_invalid_constructs{ } )" } - }, - - { - "unit_block_1", - { - "UNITS block with empty unit", - R"( - UNITS { - () = (millivolt) - } - )" - } - }, + } // clang-format on }; @@ -508,6 +496,18 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + { + "unit_block_3", + { + "UNITS block with empty unit (called default unit)", + R"( + UNITS { + () = (millivolt) + } + )" + } + }, + { "constant_block_1", { @@ -1405,6 +1405,16 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } )" } + }, + { + "empty_unit_declaration", + { + "Declaration with empty units", + R"( + FUNCTION ssCB(kdf(), kds()) (mM) { + } + )" + } } // clang-format on }; From 2534d84746fca151c36664c52492f625849f49bc Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 25 Feb 2019 18:13:14 +0100 Subject: [PATCH 144/871] CMake updates for pybind link time warning and clang-format issues (fixes BlueBrain/nmodl#9) (BlueBrain/nmodl#10) - use hidden visibility for static libraries as they may be later linked into shared libraries or executables - update cmake module path and find_package for clang-format NMODL Repo SHA: BlueBrain/nmodl@0d28f302e66ccf6e8b1335aa6258b4dbdd6b69f2 --- cmake/nmodl/CMakeLists.txt | 15 ++++++++++----- src/nmodl/language/CMakeLists.txt | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 0a1df71823..5cb33294d2 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR) +cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR) project(NMODL CXX) # ============================================================================= @@ -12,6 +12,11 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +# ============================================================================= +# Compile static libraries with hidden visibility +# ============================================================================= +set(CMAKE_CXX_VISIBILITY_PRESET hidden) + # ============================================================================= # Find required packages # ============================================================================= @@ -37,11 +42,12 @@ add_subdirectory(cmake/hpc-coding-conventions/cpp) # Include cmake modules # ============================================================================= list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -include(GitRevision) -include(FlexHelper) -include(CompilerHelper) include(ClangTidyHelper) +include(CompilerHelper) +include(FindClangFormat) include(FindPythonModule) +include(FlexHelper) +include(GitRevision) # ============================================================================= # Find required python packages @@ -107,7 +113,6 @@ add_subdirectory(src/printer) add_subdirectory(src/symtab) add_subdirectory(src/utils) add_subdirectory(src/visitors) - add_subdirectory(src/pybind) # ============================================================================= diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index dd292e7441..eef2827ebf 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -8,7 +8,7 @@ file(GLOB TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" file(GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py") -if(ClangFormat_FOUND AND ClangFormat_VERSION_MAJOR GREATER_EQUAL 4) +if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) set(CODE_GENERATOR_OPTS --clang-format=${ClangFormat_EXECUTABLE}) if(nmodl_ClangFormat_OPTIONS) set(CODE_GENERATOR_OPTS ${CODE_GENERATOR_OPTS} --clang-format-opts ${nmodl_ClangFormat_OPTIONS}) From f4bbee4e498778662b49728806ecfdaafe4b39d9 Mon Sep 17 00:00:00 2001 From: MikeG <michael.gevaert@epfl.ch> Date: Mon, 25 Feb 2019 18:14:51 +0100 Subject: [PATCH 145/871] Simplify testing of attributes for python nodes (BlueBrain/nmodl#15) NMODL Repo SHA: BlueBrain/nmodl@a0589ae586a375dd5c155180a13ac191a8cd94da --- src/nmodl/language/nodes.py | 123 +++++++++++++++--------------------- 1 file changed, 50 insertions(+), 73 deletions(-) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index b42d437b01..1a57a3baef 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -16,6 +16,7 @@ import textwrap from utils import * + class BaseNode: """base class for all node types (parent + child) """ @@ -39,19 +40,19 @@ def get_data_type_name(self): @property def is_statement_block_node(self): - return True if self.class_name == STATEMENT_BLOCK_NODE else False + return self.class_name == STATEMENT_BLOCK_NODE @property def is_statement_node(self): - return True if self.class_name in STATEMENT_TYPES else False + return self.class_name in STATEMENT_TYPES @property def is_global_block_node(self): - return True if self.class_name in GLOBAL_BLOCKS else False + return self.class_name in GLOBAL_BLOCKS @property def is_prime_node(self): - return True if self.class_name == PRIME_NAME_NODE else False + return self.class_name == PRIME_NAME_NODE @property def is_program_node(self): @@ -59,7 +60,7 @@ def is_program_node(self): check if current node is main program container node :return: True if main program block otherwise False """ - return True if self.class_name == PROGRAM_BLOCK else False + return self.class_name == PROGRAM_BLOCK @property def is_block_node(self): @@ -70,81 +71,76 @@ def is_block_node(self): :return: True or False """ - return True if self.class_name in BLOCK_TYPES else False + return self.class_name in BLOCK_TYPES @property def is_unit_block(self): - return True if self.class_name == UNIT_BLOCK else False + return self.class_name == UNIT_BLOCK @property def is_data_type_node(self): - return True if self.class_name in DATA_TYPES.keys() else False + return self.class_name in DATA_TYPES @property def is_symbol_var_node(self): - return True if self.class_name in SYMBOL_VAR_TYPES else False + return self.class_name in SYMBOL_VAR_TYPES @property def is_symbol_helper_node(self): - return True if self.class_name in SYMBOL_TABLE_HELPER_NODES else False + return self.class_name in SYMBOL_TABLE_HELPER_NODES @property def is_symbol_block_node(self): - return True if self.class_name in SYMBOL_BLOCK_TYPES else False + return self.class_name in SYMBOL_BLOCK_TYPES @property def is_base_type_node(self): - return True if self.class_name in BASE_TYPES else False + return self.class_name in BASE_TYPES @property def is_string_node(self): - return True if self.class_name == STRING_NODE else False + return self.class_name == STRING_NODE @property def is_integer_node(self): - return True if self.class_name == INTEGER_NODE else False + return self.class_name == INTEGER_NODE @property def is_number_node(self): - return True if self.class_name == NUMBER_NODE else False + return self.class_name == NUMBER_NODE @property def is_boolean_node(self): - return True if self.class_name == BOOLEAN_NODE else False + return self.class_name == BOOLEAN_NODE @property def is_identifier_node(self): - return True if self.class_name == IDENTIFIER_NODE else False + return self.class_name == IDENTIFIER_NODE @property def is_name_node(self): - return True if self.class_name == NAME_NODE else False + return self.class_name == NAME_NODE @property def is_enum_node(self): data_type = DATA_TYPES[self.class_name] - return True if data_type in ENUM_BASE_TYPES else False + return data_type in ENUM_BASE_TYPES @property def is_pointer_node(self): - if self.class_name in PTR_EXCLUDE_TYPES: - return False - if self.is_base_type_node: - return False - return True + return not (self.class_name in PTR_EXCLUDE_TYPES or + self.is_base_type_node) @property def is_ptr_excluded_node(self): - if self.class_name in PTR_EXCLUDE_TYPES: - return True - return False + return self.class_name in PTR_EXCLUDE_TYPES @property def requires_default_constructor(self): - if self.class_name in LEXER_DATA_TYPES or self.is_program_node or self.is_ptr_excluded_node: - return True - else: - return False + return (self.class_name in LEXER_DATA_TYPES or + self.is_program_node or + self.is_ptr_excluded_node) + class ChildNode(BaseNode): """represent member variable for a Node""" @@ -228,6 +224,7 @@ def get_setter_method(self): reference = "" if self.is_base_type_node else "&&" return f"void {setter_method}({setter_type}{reference} {self.varname}) {{ this->{self.varname} = {self.varname}; }}" + class Node(BaseNode): """represent a class for every rule in language specification""" @@ -252,7 +249,7 @@ def add_child(self, args): self.children.append(node) def has_children(self): - return True if self.children else False + return bool(self.children) @property def is_block_scoped_node(self): @@ -260,14 +257,14 @@ def is_block_scoped_node(self): Check if node is derived from BASE_BLOCK :return: True / False """ - return True if self.base_class == BASE_BLOCK else False + return self.base_class == BASE_BLOCK def has_parent_block_node(self): """ check is base or parent is structured base block :return: True if parent is BASE_BLOCK otherwise False """ - return True if self.base_class == BASE_BLOCK else False + return self.base_class == BASE_BLOCK @property def is_base_block_node(self): @@ -275,7 +272,7 @@ def is_base_block_node(self): check if node is Block :return: True if node type/name is BASE_BLOCK """ - return True if self.class_name == BASE_BLOCK else False + return self.class_name == BASE_BLOCK @property def is_symtab_needed(self): @@ -286,9 +283,7 @@ def is_symtab_needed(self): :return: True or False """ # block scope nodes have symtab pointer - if self.is_program_node or self.is_block_scoped_node: - return True - return False + return self.is_program_node or self.is_block_scoped_node @property def is_symtab_method_required(self): @@ -322,60 +317,45 @@ def is_base_class_number_node(self): """ Check if node is of type Number """ - return True if self.base_class == NUMBER_NODE else False + return self.base_class == NUMBER_NODE def ctor_declaration(self): - args = [] - for c in self.children: - args.append(f'{c.get_typename()} {c.varname}') + args = [f'{c.get_typename()} {c.varname}' for c in self.children] return f"{self.class_name}({', '.join(args)});" def ctor_definition(self): - args = [] - for c in self.children: - args.append(f'{c.get_typename()} {c.varname}') - initlist = [] - for c in self.children: - initlist.append(f'{c.varname}({c.varname})') + args = [f'{c.get_typename()} {c.varname}' for c in self.children] + initlist = [f'{c.varname}({c.varname})' for c in self.children] + s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) : {', '.join(initlist)} {{}} """ return textwrap.dedent(s) def ctor_shrptr_declaration(self): - args = [] - for c in self.children: - args.append(f'{c.member_typename} {c.varname}') + args = [f'{c.member_typename} {c.varname}' for c in self.children] return f"{self.class_name}({', '.join(args)});" def ctor_shrptr_definition(self): - args = [] - for c in self.children: - args.append(f'{c.member_typename} {c.varname}') - initlist = [] - for c in self.children: - initlist.append(f'{c.varname}({c.varname})') + args = [f'{c.member_typename} {c.varname}' for c in self.children] + initlist = [f'{c.varname}({c.varname})' for c in self.children] + s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) : {', '.join(initlist)} {{}} """ return textwrap.dedent(s) def has_ptr_children(self): - for c in self.children: - if not (c.is_vector or c.is_base_type_node or c.is_ptr_excluded_node): - return True - return False - + return any(not (c.is_vector or c.is_base_type_node or c.is_ptr_excluded_node) + for c in self.children) def public_members(self): """ Return public members of the node """ - members = [] - - for child in self.children: - if child.is_public: - members.append([child.member_typename, child.varname, child.description]) + members = [[child.member_typename, child.varname, child.description] + for child in self.children + if child.is_public] return members @@ -383,11 +363,9 @@ def private_members(self): """ Return private members of the node """ - members = [] - - for child in self.children: - if not child.is_public: - members.append([child.member_typename, child.varname, child.description]) + members = [[child.member_typename, child.varname, child.description] + for child in self.children + if not child.is_public] if self.has_token: members.append(["std::shared_ptr<ModToken>", "token", "token with location information"]) @@ -400,7 +378,6 @@ def private_members(self): return members - @property def non_base_members(self): return [child for child in self.children if not child.is_base_type_node] From 6eafe3ed2fbcfb8c52f5358e87ea646308e3ee21 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Mon, 25 Feb 2019 19:55:53 +0100 Subject: [PATCH 146/871] Enhance SympyConductanceVisitor - fixes BlueBrain/nmodl#7 (BlueBrain/nmodl#14) - now takes into account all previous statements in BREAKPOINT - substitutes them (recursively) before differentiating - extended unit tests - also added unit tests for differentiate2c function in ode.py NMODL Repo SHA: BlueBrain/nmodl@fd3c6c6fbbe889054caeb11ec2b75efbbbbf6c57 --- nmodl/ode.py | 63 +++++++++++-- .../visitors/sympy_conductance_visitor.cpp | 75 +++++++++------ .../visitors/sympy_conductance_visitor.hpp | 12 ++- src/nmodl/visitors/sympy_solver_visitor.cpp | 5 +- src/nmodl/visitors/visitor_utils.cpp | 4 +- test/nmodl/transpiler/CMakeLists.txt | 3 + test/nmodl/transpiler/ode/__init__.py | 0 test/nmodl/transpiler/ode/test_ode.py | 59 ++++++++++++ test/nmodl/transpiler/visitor/visitor.cpp | 94 ++++++++++++++++++- 9 files changed, 266 insertions(+), 49 deletions(-) create mode 100644 test/nmodl/transpiler/ode/__init__.py create mode 100644 test/nmodl/transpiler/ode/test_ode.py diff --git a/nmodl/ode.py b/nmodl/ode.py index 77327b5fd9..ed023e25b0 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -67,7 +67,9 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): sympy_vars[t_var] = t # parse string into SymPy equation - diffeq = sp.Eq(x.diff(t), sp.sympify(diff_string.split("=")[1], locals=sympy_vars)) + diffeq = sp.Eq( + x.diff(t), sp.sympify(diff_string.split("=", 1)[1], locals=sympy_vars) + ) # classify ODE, if it is too hard then exit ode_properties = set(sp.classify_ode(diffeq)) @@ -89,31 +91,46 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): _a0 = taylor_series.nth(0) _a1 = taylor_series.nth(1) _a2 = taylor_series.nth(2) - solution = ((_a0*_a1 + (_a1*_a1-_a0*_a2)*dt)/(_a1-_a2*dt)).simplify() + solution = ( + (_a0 * _a1 + (_a1 * _a1 - _a0 * _a2) * dt) / (_a1 - _a2 * dt) + ).simplify() # return result as C code in NEURON format return f"{sp.ccode(x_0)} = {sp.ccode(solution)}" -def differentiate2c(expression, dependent_var, vars): +def differentiate2c(expression, dependent_var, vars, prev_expressions=None): """Analytically differentiate supplied expression, return solution as C code. Expression should be of the form "f(x)", where "x" is the dependent variable, and the function returns df(x)/dx - vars should contain the set of all the variables - referenced by f(x), for example: + + The set vars must contain all variables used in the expression. + + Furthermore, if any of these variables are themselves functions that should + be substituted before differentiating, they can be supplied in the prev_expressions list. + Before differentiating each of these expressions will be substituted into expressions, + where possible, in reverse order - i.e. starting from the end of the list. + + If the result coincides with one of the vars, or the LHS of one of + the prev_expressions, then it is simplified to this expression. + + Some simple examples of use: -differentiate2c("a*x", "x", {"a"}) == "a" -differentiate2c("cos(y) + b*y**2", "y", {"a","b"}) == "Dy = 2*b*y - sin(y)" Args: expression: expression to be differentiated e.g. "a*x + b" dependent_var: dependent variable, e.g. "x" - vars: set of all other variables used in expression, e.g. {"a", "b"} + vars: set of all other variables used in expression, e.g. {"a", "b", "c"} + prev_expressions: time-ordered list of preceeding expressions + to evaluate & substitute, e.g. ["b = x + c", "a = 12*b"] Returns: - String containing analytic derivative as C code + String containing analytic derivative of expression (including any substitutions + of variables from supplied prev_expressions) w.r.t dependent_var as C code. """ - + prev_expressions = prev_expressions or [] # every symbol (a.k.a variable) that SymPy # is going to manipulate needs to be declared # explicitly @@ -127,8 +144,36 @@ def differentiate2c(expression, dependent_var, vars): # parse string into SymPy equation expr = sp.sympify(expression, locals=sympy_vars) + # parse previous equations into (lhs, rhs) pairs & reverse order + prev_eqs = [ + ( + sp.sympify(e.split("=", 1)[0], locals=sympy_vars), + sp.sympify(e.split("=", 1)[1], locals=sympy_vars), + ) + for e in prev_expressions + ] + prev_eqs.reverse() + + # substitute each prev equation in reverse order: latest first + for eq in prev_eqs: + expr = expr.subs(eq[0], eq[1]) + # differentiate w.r.t. x - diff = expr.diff(x) + diff = expr.diff(x).simplify() + + # if expression is equal to one of the supplied vars, replace with this var + for v in sympy_vars: + if (diff - sympy_vars[v]).simplify() == 0: + diff = sympy_vars[v] + # or if equal to rhs of one of supplied equations, replace with lhs + for i_eq, eq in enumerate(prev_eqs): + # each supplied eq also needs recursive substitution of preceeding statements + # here, before comparison with diff expression + expr = eq[1] + for sub_eq in prev_eqs[i_eq:]: + expr = expr.subs(sub_eq[0], sub_eq[1]) + if (diff - expr).simplify() == 0: + diff = eq[0] # return result as C code in NEURON format return sp.ccode(diff) diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 1e8954bc5f..8aff87cba9 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -22,22 +22,29 @@ using namespace syminfo; std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( BreakpointBlock* node) { std::vector<std::string> statements; - // iterate over binary expressions from breakpoint - for (const auto& expr: binary_exprs) { - auto lhs_str = expr.first; - auto equation_string = expr.second; + // iterate over binary expression lhs's from breakpoint + for (const auto& lhs_str: ordered_binary_exprs_lhs) { // look for a current name that matches lhs of expr (current write name) auto it = i_name.find(lhs_str); if (it != i_name.end()) { + const auto& equation_string = ordered_binary_exprs[binary_expr_index[lhs_str]]; std::string i_name_str = it->second; + // SymPy needs the current expression & all previous expressions + std::vector<std::string> expressions(ordered_binary_exprs.begin(), + ordered_binary_exprs.begin() + + binary_expr_index[lhs_str] + 1); // differentiate dI/dV - auto locals = py::dict("equation_string"_a = equation_string, "vars"_a = vars); + auto locals = py::dict("expressions"_a = expressions, "vars"_a = vars); py::exec(R"( from nmodl.ode import differentiate2c exception_message = "" try: - rhs = equation_string.split("=")[1] - solution = differentiate2c(rhs, "v", vars) + rhs = expressions[-1].split("=", 1)[1] + solution = differentiate2c(rhs, + "v", + vars, + expressions[:-1] + ) except Exception as e: # if we fail, fail silently and return empty string solution = "" @@ -47,7 +54,7 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( auto dIdV = locals["solution"].cast<std::string>(); auto exception_message = locals["exception_message"].cast<std::string>(); if (!exception_message.empty()) { - logger->warn("SympyConductance :: python exception: " + exception_message); + logger->warn("SympyConductance :: python exception: {}", exception_message); } if (dIdV.empty()) { logger->warn( @@ -67,17 +74,18 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( // declare it add_local_variable(node->get_statement_block().get(), g_var); // asign dIdV to it - std::string statement_str = g_var + " = " + dIdV; + std::string statement_str = g_var; + statement_str.append(" = ").append(dIdV); statements.insert(statements.begin(), statement_str); - logger->debug("SympyConductance :: Adding BREAKPOINT statement: " + + logger->debug("SympyConductance :: Adding BREAKPOINT statement: {}", statement_str); } std::string statement_str = "CONDUCTANCE " + g_var; - if (i_name_str != "") { + if (!i_name_str.empty()) { statement_str += " USEION " + i_name_str; } statements.push_back(statement_str); - logger->debug("SympyConductance :: Adding BREAKPOINT statement: " + statement_str); + logger->debug("SympyConductance :: Adding BREAKPOINT statement: {}", statement_str); } } } @@ -85,10 +93,16 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( } void SympyConductanceVisitor::visit_binary_expression(BinaryExpression* node) { + // only want binary expressions from breakpoint block + if (!breakpoint_block) { + return; + } // only want binary expressions of form x = ... if (node->lhs->is_var_name() && (node->op.get_value() == BinaryOp::BOP_ASSIGN)) { auto lhs_str = std::dynamic_pointer_cast<VarName>(node->lhs)->get_name()->get_node_name(); - binary_exprs[lhs_str] = nmodl::to_nmodl(node); + binary_expr_index[lhs_str] = ordered_binary_exprs.size(); + ordered_binary_exprs.push_back(nmodl::to_nmodl(node)); + ordered_binary_exprs_lhs.push_back(lhs_str); } } @@ -96,12 +110,12 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { // add NONSPECIFIC_CURRENT statements to i_name map between write vars and names // note that they don't have an ion name, so we set it to "" if (!NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS) { - for (auto ns_curr_ast: nonspecific_nodes) { + for (const auto& ns_curr_ast: nonspecific_nodes) { logger->debug("SympyConductance :: Found NONSPECIFIC_CURRENT statement"); - for (auto write_name: + for (const auto& write_name: std::dynamic_pointer_cast<Nonspecific>(ns_curr_ast).get()->get_currents()) { std::string curr_write = write_name->get_node_name(); - logger->debug("SympyConductance :: -> Adding non-specific current write name: " + + logger->debug("SympyConductance :: -> Adding non-specific current write name: {}", curr_write); i_name[curr_write] = ""; } @@ -111,18 +125,18 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { void SympyConductanceVisitor::lookup_useion_statements() { // add USEION statements to i_name map between write vars and names - for (auto useion_ast: use_ion_nodes) { + for (const auto& useion_ast: use_ion_nodes) { auto ion = std::dynamic_pointer_cast<Useion>(useion_ast).get(); std::string ion_name = ion->get_node_name(); - logger->debug("SympyConductance :: Found USEION statement " + nmodl::to_nmodl(ion)); + logger->debug("SympyConductance :: Found USEION statement {}", nmodl::to_nmodl(ion)); if (i_ignore.find(ion_name) != i_ignore.end()) { - logger->debug("SympyConductance :: -> Ignoring ion current name: " + ion_name); + logger->debug("SympyConductance :: -> Ignoring ion current name: {}", ion_name); } else { - auto wl = ion->get_writelist(); - for (auto w: wl) { + for (const auto& w: ion->get_writelist()) { std::string ion_write = w->get_node_name(); - logger->debug("SympyConductance :: -> Adding ion write name: " + ion_write + - " for ion current name: " + ion_name); + logger->debug( + "SympyConductance :: -> Adding ion write name: {} for ion current name: {}", + ion_write, ion_name); i_name[ion_write] = ion_name; } } @@ -132,11 +146,10 @@ void SympyConductanceVisitor::lookup_useion_statements() { void SympyConductanceVisitor::visit_conductance_hint(ConductanceHint* node) { // find existing CONDUCTANCE statements - do not want to alter them // so keep a set of ion names i_ignore that we should ignore later - logger->debug("SympyConductance :: Found existing CONDUCTANCE statement: " + + logger->debug("SympyConductance :: Found existing CONDUCTANCE statement: {}", nmodl::to_nmodl(node)); - auto ion = node->get_ion(); - if (ion) { - logger->debug("SympyConductance :: -> Ignoring ion current name: " + ion->get_node_name()); + if (auto ion = node->get_ion()) { + logger->debug("SympyConductance :: -> Ignoring ion current name: {}", ion->get_node_name()); i_ignore.insert(ion->get_node_name()); } else { logger->debug("SympyConductance :: -> Ignoring all non-specific currents"); @@ -146,14 +159,15 @@ void SympyConductanceVisitor::visit_conductance_hint(ConductanceHint* node) { void SympyConductanceVisitor::visit_breakpoint_block(BreakpointBlock* node) { // add any breakpoint local variables to vars - if (auto symtab = node->get_statement_block()->get_symbol_table()) { - for (auto localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { + if (auto* symtab = node->get_statement_block()->get_symbol_table()) { + for (const auto& localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { vars.insert(localvar->get_name()); } } - // visit BREAKPOINT block statements + breakpoint_block = true; node->visit_children(this); + breakpoint_block = false; // lookup USEION and NONSPECIFIC statements from NEURON block lookup_useion_statements(); @@ -181,7 +195,6 @@ void SympyConductanceVisitor::visit_breakpoint_block(BreakpointBlock* node) { void SympyConductanceVisitor::visit_program(Program* node) { vars = get_global_vars(node); - AstLookupVisitor ast_lookup_visitor; use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); nonspecific_nodes = ast_lookup_visitor.lookup(node, AstNodeType::NONSPECIFIC); diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index cdf806384e..fcb3aac2aa 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -44,13 +44,23 @@ class SympyConductanceVisitor: public AstVisitor { private: + /// true while visiting breakpoint block + bool breakpoint_block = false; typedef std::map<std::string, std::string> string_map; typedef std::set<std::string> string_set; + // set of all variables for SymPy string_set vars; + // set of currents to ignore string_set i_ignore; + // map between current write names and ion names string_map i_name; bool NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS = false; - string_map binary_exprs; + // list in order of binary expressions in breakpoint + std::vector<std::string> ordered_binary_exprs; + // ditto but for LHS of expression only + std::vector<std::string> ordered_binary_exprs_lhs; + // map from lhs of binary expression to index of expression in above vector + std::map<std::string, std::size_t> binary_expr_index; std::vector<std::shared_ptr<ast::AST>> use_ion_nodes; std::vector<std::shared_ptr<ast::AST>> nonspecific_nodes; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index f4e4353e6f..b48a300842 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -78,10 +78,9 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { // get any local vars - auto symtab = node->get_statement_block()->get_symbol_table(); - if (symtab) { + if (auto symtab = node->get_statement_block()->get_symbol_table()) { auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); - for (auto v: localvars) { + for (const auto& v: localvars) { vars.insert(v->get_name()); } } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index ca704db59c..e72a3369a7 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -87,7 +87,7 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { std::set<std::string> get_global_vars(Program* node) { std::set<std::string> vars; - if (auto symtab = node->get_symbol_table()) { + if (auto* symtab = node->get_symbol_table()) { syminfo::NmodlType property = syminfo::NmodlType::global_var | syminfo::NmodlType::range_var | syminfo::NmodlType::param_assign | syminfo::NmodlType::extern_var | @@ -96,7 +96,7 @@ std::set<std::string> get_global_vars(Program* node) { syminfo::NmodlType::nonspecific_cur_var | syminfo::NmodlType::electrode_cur_var | syminfo::NmodlType::section_var | syminfo::NmodlType::constant_var | syminfo::NmodlType::extern_neuron_variable | syminfo::NmodlType::state_var; - for (auto globalvar: symtab->get_variables_with_properties(property)) { + for (const auto& globalvar: symtab->get_variables_with_properties(property)) { vars.insert(globalvar->get_name()); } } diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index da41ebbd1f..74a8b9dfee 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -41,6 +41,9 @@ set_tests_properties(Visitor PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DI # pybind11 tests # ============================================================================= +add_test(NAME Ode + COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/ode) +set_tests_properties(Ode PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) add_test(NAME Pybind COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) set_tests_properties(Pybind PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) diff --git a/test/nmodl/transpiler/ode/__init__.py b/test/nmodl/transpiler/ode/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/nmodl/transpiler/ode/test_ode.py b/test/nmodl/transpiler/ode/test_ode.py new file mode 100644 index 0000000000..f966af7d2d --- /dev/null +++ b/test/nmodl/transpiler/ode/test_ode.py @@ -0,0 +1,59 @@ +# *********************************************************************** +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# *********************************************************************** + +from nmodl.ode import differentiate2c + + +def test_differentiation(): + + # simple examples, no prev_expressions + assert differentiate2c("0", "x", "") == "0" + assert differentiate2c("x", "x", "") == "1" + assert differentiate2c("a", "x", "a") == "0" + assert differentiate2c("a*x", "x", "a") == "a" + assert differentiate2c("a*x", "a", "x") == "x" + assert differentiate2c("a*x", "y", {"x", "y"}) == "0" + assert differentiate2c("a*x + b*x*x", "x", {"a", "b"}) == "a + 2*b*x" + assert differentiate2c("a*cos(x+b)", "x", {"a", "b"}) == "-a*sin(b + x)" + assert ( + differentiate2c("a*cos(x+b) + c*x*x", "x", {"a", "b", "c"}) + == "-a*sin(b + x) + 2*c*x" + ) + + # single prev_expression to substitute + assert differentiate2c("a*x + b", "x", {"a", "b", "c", "d"}, ["c = sqrt(d)"]) == "a" + assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x"]) == "a + 2" + + # multiple prev_eqs to substitute + # (these statements should be in the same order as in the mod file) + assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2"]) == "0" + assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2"]) == "0" + + # multiple prev_eqs to recursively substitute + # note prev_eqs always substituted in reverse order + assert differentiate2c("a*x + b", "x", {"a", "b"}, ["a=3", "b = 2*a*x"]) == "9" + # if we can return result in terms of supplied var, do so + # even in this case where the supplied var a is equal to 3: + assert ( + differentiate2c( + "a*x + b*c", "x", {"a", "b", "c"}, ["a=3", "b = 2*a*x", "c = a/x"] + ) + == "a" + ) + assert ( + differentiate2c("-a*x + b*c", "x", {"a", "b", "c"}, ["b = 2*x*x", "c = a/x"]) + == "a" + ) + assert ( + differentiate2c( + "(g1 + g2)*(v-e)", + "v", + {"g", "e", "g1", "g2", "c", "d"}, + ["g2 = sqrt(d) + 3", "g1 = 2*c", "g = g1 + g2"], + ) + == "g" + ) diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 1ebfecdb10..a655cdafd6 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -10,6 +10,7 @@ #include <string> #include "catch/catch.hpp" +#include "utils/logger.hpp" #include <pybind11/embed.h> #include "parser/nmodl_driver.hpp" @@ -35,12 +36,12 @@ using json = nlohmann::json; using namespace ast; using namespace nmodl; using namespace syminfo; -namespace py = pybind11; int main(int argc, char* argv[]) { // initialize python interpreter once for // entire catch executable pybind11::scoped_interpreter guard{}; + logger->set_level(spdlog::level::debug); int result = Catch::Session().run(argc, argv); return result; } @@ -1985,7 +1986,7 @@ std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; auto res = v_lookup.lookup(ast.get(), AstNodeType::DIFF_EQ_EXPRESSION); - for (auto r: res) { + for (const auto& r: res) { results.push_back(to_nmodl(r.get())); } @@ -2031,7 +2032,7 @@ SCENARIO("SympySolver visitor", "[sympy]") { THEN("No ODEs found - do nothing") { auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 0); + REQUIRE(result.empty()); } } GIVEN("Derivative block with ODES, solver method is not cnexp") { @@ -2785,4 +2786,91 @@ SCENARIO("SympyConductance visitor", "[sympy]") { REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); } } + // based on bluebrain/CortextPlastic/mod/ProbAMPANMDA.mod + GIVEN( + "2 ion currents, 1 nonspecific current, no CONDUCTANCE hints, indirect relation between " + "eqns") { + std::string nmodl_text = R"( + NEURON { + THREADSAFE + POINT_PROCESS ProbAMPANMDA + RANGE tau_r_AMPA, tau_d_AMPA, tau_r_NMDA, tau_d_NMDA + RANGE Use, u, Dep, Fac, u0, mg, NMDA_ratio + RANGE i, i_AMPA, i_NMDA, g_AMPA, g_NMDA, g, e + NONSPECIFIC_CURRENT i, i_AMPA,i_NMDA + POINTER rng + RANGE synapseID, verboseLevel + } + PARAMETER { + tau_r_AMPA = 0.2 (ms) : dual-exponential conductance profile + tau_d_AMPA = 1.7 (ms) : IMPORTANT: tau_r < tau_d + tau_r_NMDA = 0.29 (ms) : dual-exponential conductance profile + tau_d_NMDA = 43 (ms) : IMPORTANT: tau_r < tau_d + Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) + Dep = 100 (ms) : relaxation time constant from depression + Fac = 10 (ms) : relaxation time constant from facilitation + e = 0 (mV) : AMPA and NMDA reversal potential + mg = 1 (mM) : initial concentration of mg2+ + mggate + gmax = .001 (uS) : weight conversion factor (from nS to uS) + u0 = 0 :initial value of u, which is the running value of Use + NMDA_ratio = 0.71 (1) : The ratio of NMDA to AMPA + synapseID = 0 + verboseLevel = 0 + } + ASSIGNED { + v (mV) + i (nA) + i_AMPA (nA) + i_NMDA (nA) + g_AMPA (uS) + g_NMDA (uS) + g (uS) + factor_AMPA + factor_NMDA + rng + } + STATE { + A_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_r_AMPA + B_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_d_AMPA + A_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_r_NMDA + B_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_d_NMDA + } + BREAKPOINT { + SOLVE state METHOD cnexp + mggate = 1.2 + g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA + g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics + g = g_AMPA + g_NMDA + i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal + i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal + i = i_AMPA + i_NMDA + } + DERIVATIVE state{ + A_AMPA' = -A_AMPA/tau_r_AMPA + B_AMPA' = -B_AMPA/tau_d_AMPA + A_NMDA' = -A_NMDA/tau_r_NMDA + B_NMDA' = -B_NMDA/tau_d_NMDA + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE g + CONDUCTANCE g_NMDA + CONDUCTANCE g_AMPA + SOLVE state METHOD cnexp + mggate = 1.2 + g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA + g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics + g = g_AMPA + g_NMDA + i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal + i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal + i = i_AMPA + i_NMDA + } + )"; + THEN("Add 3 CONDUCTANCE hints, using existing vars") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } } From b2c9fcc0c6d5f4e4bdc657f560fe4530233e57ae Mon Sep 17 00:00:00 2001 From: MikeG <michael.gevaert@epfl.ch> Date: Wed, 27 Feb 2019 00:01:33 +0100 Subject: [PATCH 147/871] Remove * imports from yaml language parsing code (BlueBrain/nmodl#16) NMODL Repo SHA: BlueBrain/nmodl@a41d6692ca3219b4de819263de6c722874707acd --- src/nmodl/language/code_generator.py | 1 - src/nmodl/language/node_info.py | 18 ++++---- src/nmodl/language/nodes.py | 68 ++++++++++++++-------------- src/nmodl/language/parser.py | 20 ++++---- src/nmodl/language/utils.py | 8 ++-- 5 files changed, 57 insertions(+), 58 deletions(-) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 61c6340394..a1e9f3d6d6 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -10,7 +10,6 @@ import logging import os from pathlib import Path -import shlex import shutil import subprocess import tempfile diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index a089a7c0de..0baa1912d5 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -11,9 +11,9 @@ nmodl blocks defining variables, nmodl constructs that define variables etc. These all data types are defined in this file. -\todo : Other way is to add extra attributes to YAML language definitions. +TODO: Other way is to add extra attributes to YAML language definitions. YAML will become more verbose but advantage would be that the YAML will be -self sufficien, single definition file instead of hard-coded names here. +self sufficient, single definition file instead of hard-coded names here. We should properties like is_enum, data_type, is_symbol, is_global etc. """ @@ -103,12 +103,12 @@ # when translating back to nmodl, we need print each statement # to new line. Those nodes are are used from this list. -STATEMENT_TYPES=["Statement", - "IndependentDef", - "DependentDef", - "ParamAssign", - "ConstantStatement", - "Stepped"] +STATEMENT_TYPES = ["Statement", + "IndependentDef", + "DependentDef", + "ParamAssign", + "ConstantStatement", + "Stepped"] # data types which have token as an argument to the constructor LEXER_DATA_TYPES = ["Name", @@ -136,7 +136,7 @@ PTR_EXCLUDE_TYPES = ["BinaryOperator", "UnaryOperator", "ReactionOperator"] # these node names are explicitly added because they are used in ast/visitor -# printer classes. In otder to avoid hardcoding in the printer functions, they +# printer classes. In order to avoid hardcoding in the printer functions, they # are defined here. PROGRAM_BLOCK = "Program" BASE_BLOCK = "Block" diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 1a57a3baef..30a76eee8f 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -12,9 +12,10 @@ is represented by ChildNode. """ -from node_info import * import textwrap -from utils import * + +import node_info +from utils import to_snake_case class BaseNode: @@ -32,27 +33,27 @@ def __init__(self, args): self.is_abstract = False def __lt__(self, other): - return (self.class_name < other.class_name) + return self.class_name < other.class_name def get_data_type_name(self): """ return type name for the node """ - return DATA_TYPES[self.class_name] + return node_info.DATA_TYPES[self.class_name] @property def is_statement_block_node(self): - return self.class_name == STATEMENT_BLOCK_NODE + return self.class_name == node_info.STATEMENT_BLOCK_NODE @property def is_statement_node(self): - return self.class_name in STATEMENT_TYPES + return self.class_name in node_info.STATEMENT_TYPES @property def is_global_block_node(self): - return self.class_name in GLOBAL_BLOCKS + return self.class_name in node_info.GLOBAL_BLOCKS @property def is_prime_node(self): - return self.class_name == PRIME_NAME_NODE + return self.class_name == node_info.PRIME_NAME_NODE @property def is_program_node(self): @@ -60,7 +61,7 @@ def is_program_node(self): check if current node is main program container node :return: True if main program block otherwise False """ - return self.class_name == PROGRAM_BLOCK + return self.class_name == node_info.PROGRAM_BLOCK @property def is_block_node(self): @@ -71,73 +72,73 @@ def is_block_node(self): :return: True or False """ - return self.class_name in BLOCK_TYPES + return self.class_name in node_info.BLOCK_TYPES @property def is_unit_block(self): - return self.class_name == UNIT_BLOCK + return self.class_name == node_info.UNIT_BLOCK @property def is_data_type_node(self): - return self.class_name in DATA_TYPES + return self.class_name in node_info.DATA_TYPES @property def is_symbol_var_node(self): - return self.class_name in SYMBOL_VAR_TYPES + return self.class_name in node_info.SYMBOL_VAR_TYPES @property def is_symbol_helper_node(self): - return self.class_name in SYMBOL_TABLE_HELPER_NODES + return self.class_name in node_info.SYMBOL_TABLE_HELPER_NODES @property def is_symbol_block_node(self): - return self.class_name in SYMBOL_BLOCK_TYPES + return self.class_name in node_info.SYMBOL_BLOCK_TYPES @property def is_base_type_node(self): - return self.class_name in BASE_TYPES + return self.class_name in node_info.BASE_TYPES @property def is_string_node(self): - return self.class_name == STRING_NODE + return self.class_name == node_info.STRING_NODE @property def is_integer_node(self): - return self.class_name == INTEGER_NODE + return self.class_name == node_info.INTEGER_NODE @property def is_number_node(self): - return self.class_name == NUMBER_NODE + return self.class_name == node_info.NUMBER_NODE @property def is_boolean_node(self): - return self.class_name == BOOLEAN_NODE + return self.class_name == node_info.BOOLEAN_NODE @property def is_identifier_node(self): - return self.class_name == IDENTIFIER_NODE + return self.class_name == node_info.IDENTIFIER_NODE @property def is_name_node(self): - return self.class_name == NAME_NODE + return self.class_name == node_info.NAME_NODE @property def is_enum_node(self): - data_type = DATA_TYPES[self.class_name] - return data_type in ENUM_BASE_TYPES + data_type = node_info.DATA_TYPES[self.class_name] + return data_type in node_info.ENUM_BASE_TYPES @property def is_pointer_node(self): - return not (self.class_name in PTR_EXCLUDE_TYPES or + return not (self.class_name in node_info.PTR_EXCLUDE_TYPES or self.is_base_type_node) @property def is_ptr_excluded_node(self): - return self.class_name in PTR_EXCLUDE_TYPES + return self.class_name in node_info.PTR_EXCLUDE_TYPES @property def requires_default_constructor(self): - return (self.class_name in LEXER_DATA_TYPES or + return (self.class_name in node_info.LEXER_DATA_TYPES or self.is_program_node or self.is_ptr_excluded_node) @@ -257,14 +258,14 @@ def is_block_scoped_node(self): Check if node is derived from BASE_BLOCK :return: True / False """ - return self.base_class == BASE_BLOCK + return self.base_class == node_info.BASE_BLOCK def has_parent_block_node(self): """ check is base or parent is structured base block :return: True if parent is BASE_BLOCK otherwise False """ - return self.base_class == BASE_BLOCK + return self.base_class == node_info.BASE_BLOCK @property def is_base_block_node(self): @@ -272,7 +273,7 @@ def is_base_block_node(self): check if node is Block :return: True if node type/name is BASE_BLOCK """ - return self.class_name == BASE_BLOCK + return self.class_name == node_info.BASE_BLOCK @property def is_symtab_needed(self): @@ -301,13 +302,14 @@ def is_symtab_method_required(self): if self.has_children(): - if self.class_name in SYMBOL_VAR_TYPES or self.class_name in SYMBOL_BLOCK_TYPES: + if(self.class_name in node_info.SYMBOL_VAR_TYPES or + self.class_name in node_info.SYMBOL_BLOCK_TYPES): method_required = True if self.is_program_node or self.has_parent_block_node(): method_required = True - if self.class_name in SYMBOL_TABLE_HELPER_NODES: + if self.class_name in node_info.SYMBOL_TABLE_HELPER_NODES: method_required = True return method_required @@ -317,7 +319,7 @@ def is_base_class_number_node(self): """ Check if node is of type Number """ - return self.base_class == NUMBER_NODE + return self.base_class == node_info.NUMBER_NODE def ctor_declaration(self): args = [f'{c.get_typename()} {c.varname}' for c in self.children] diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 12d51b726f..85f458ab5d 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -15,9 +15,10 @@ import sys import yaml + from argument import Argument from nodes import Node -from node_info import * +import node_info class LanguageParser: @@ -27,17 +28,16 @@ def __init__(self, filename, debug=False): self.filename = filename self.debug = debug - @classmethod - def is_token(self, name): + @staticmethod + def is_token(name): """check if the name (i.e. class) is a token type in lexer Lexims returned from Lexer have position and hence token object. Return True if this node is returned by lexer otherwise False """ - if name in LEXER_DATA_TYPES or name in SYMBOL_BLOCK_TYPES or name in ADDITIONAL_TOKEN_BLOCKS: - return True - else: - return False + return (name in node_info.LEXER_DATA_TYPES or + name in node_info.SYMBOL_BLOCK_TYPES or + name in node_info.ADDITIONAL_TOKEN_BLOCKS) def parse_child_rule(self, child): """parse child specification and return argument as properties @@ -59,7 +59,6 @@ def parse_child_rule(self, child): # type i.e. class of the variable args.class_name = properties['type'] - if self.debug: print(('Child {}, {}'.format(args.varname, args.class_name))) @@ -125,7 +124,6 @@ def parse_child_rule(self, child): return args - def parse_yaml_rules(self, nodelist, base_class=None): abstract_nodes = [] nodes = [] @@ -148,7 +146,6 @@ def parse_yaml_rules(self, nodelist, base_class=None): # classes like AST which don't have base class # are not added (we print AST class separately) if base_class: - args = Argument() args.base_class = base_class args.class_name = class_name @@ -203,8 +200,7 @@ def parse_file(self): with open(self.filename, 'r') as stream: try: rules = yaml.load(stream) - # abstract nodes are not used though - abstract_nodes, nodes = self.parse_yaml_rules(rules) + _, nodes = self.parse_yaml_rules(rules) except yaml.YAMLError as e: print(("Error while parsing YAML definition file {0} : {1}".format(self.filename, e.strerror))) sys.exit(1) diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index b6b7a55694..87b90d72ab 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -7,12 +7,14 @@ import re -# convert string of the form "AabcDef" to "Abc_Def" + def camel_case_to_underscore(name): + """convert string from 'AaaBbbbCccDdd' -> 'Aaa_Bbbb_Ccc_Ddd'""" s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) return typename -# convert string of the form "AabcDef" to "abc_def" + def to_snake_case(name): - return camel_case_to_underscore(name).lower() + """convert string from 'AaaBbbbCccDdd' -> 'aaa_bbbb_ccc_ddd'""" + return camel_case_to_underscore(name).lower() From 49a12aab3bf62e3fb62903ed510e614f4d5d72a7 Mon Sep 17 00:00:00 2001 From: Mike Gevaert <michael.gevaert@epfl.ch> Date: Tue, 26 Feb 2019 13:24:33 +0100 Subject: [PATCH 148/871] use node.py helpers when possible * remove reference to unused IDENTIFIER_NODE * changed to sets for membership tests in node_info * added Node __repr__/__str__'s for easier debugging/printing NMODL Repo SHA: BlueBrain/nmodl@fbd12010b2605f94d0ce00f3a68f34c44b7db282 --- src/nmodl/language/argument.py | 11 +++++-- src/nmodl/language/node_info.py | 51 +++++++++++++++++++------------ src/nmodl/language/nodes.py | 38 +++++++++++------------ src/nmodl/language/parser.py | 53 +++++++++++++-------------------- 4 files changed, 80 insertions(+), 73 deletions(-) diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 957940626a..1cd4fdd81a 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -9,15 +9,17 @@ class Argument: """Utility class for holding all arguments for node classes""" def __init__(self): - self.base_class = "" + # BaseNode self.class_name = "" - self.description = "" self.nmodl_name = "" self.prefix = "" self.suffix = "" self.force_prefix = "" self.force_suffix = "" self.separator = "" + self.description = "" + + # ChildNode self.typename = "" self.varname = "" self.is_public = False @@ -25,7 +27,10 @@ def __init__(self): self.is_optional = False self.add_method = False self.get_node_name = False - self.has_token = False self.getter_method = False self.getter_override = False + + # Node + self.base_class = "" + self.has_token = False self.url = None diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 0baa1912d5..9bf661349c 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -18,7 +18,7 @@ """ # yapf: disable -BASE_TYPES = ["short", +BASE_TYPES = {"short", "int", "float", "double", @@ -29,16 +29,18 @@ "FirstLastType", "QueueType", "BAType", - "UnitStateType"] + "UnitStateType", + } # base types which are enums -ENUM_BASE_TYPES = ["BinaryOp", +ENUM_BASE_TYPES = {"BinaryOp", "UnaryOp", "ReactionOp", "FirstLastType", "QueueType", "BAType", - "UnitStateType"] + "UnitStateType", + } # data types and then their return types DATA_TYPES = {"Boolean": "bool", @@ -52,10 +54,11 @@ "UnitState": "UnitStateType", "BABlockType": "BAType", "QueueExpressionType": "QueueType", - "FirstLastTypeIndex": "FirstLastType"} + "FirstLastTypeIndex": "FirstLastType", + } # nodes which will go into symbol table -SYMBOL_VAR_TYPES = ["LocalVar", +SYMBOL_VAR_TYPES = {"LocalVar", "ParamAssign", "Argument", "DependentDef", @@ -73,12 +76,13 @@ "BbcorePointerVar", "ExternVar", "PrimeName", - "ConstantVar"] + "ConstantVar", + } # block nodes which will go into symbol table # these blocks doesn't define global variables but they # use variables from other global variables -SYMBOL_BLOCK_TYPES = ["FunctionBlock", +SYMBOL_BLOCK_TYPES = {"FunctionBlock", "ProcedureBlock", "DerivativeBlock", "LinearBlock", @@ -86,54 +90,63 @@ "DiscreteBlock", "PartialBlock", "KineticBlock", - "FunctionTableBlock"] + "FunctionTableBlock" + } # nodes which need extra handling to augument symbol table -SYMBOL_TABLE_HELPER_NODES = ["TableStatement"] +SYMBOL_TABLE_HELPER_NODES = {"TableStatement", + } # blocks defining global variables -GLOBAL_BLOCKS = ["NeuronBlock", +GLOBAL_BLOCKS = {"NeuronBlock", "ParamBlock", "UnitBlock", "StepBlock", "IndependentBlock", "DependentBlock", "StateBlock", - "ConstantBlock"] + "ConstantBlock", + } # when translating back to nmodl, we need print each statement # to new line. Those nodes are are used from this list. -STATEMENT_TYPES = ["Statement", +STATEMENT_TYPES = {"Statement", "IndependentDef", "DependentDef", "ParamAssign", "ConstantStatement", - "Stepped"] + "Stepped", + } # data types which have token as an argument to the constructor -LEXER_DATA_TYPES = ["Name", +LEXER_DATA_TYPES = {"Name", "PrimeName", "Integer", "Double", "String", - "FactorDef"] + "FactorDef", + } # while printing symbol table we needed setToken() method for StatementBlock and # hence need to add this -ADDITIONAL_TOKEN_BLOCKS = ["StatementBlock"] +ADDITIONAL_TOKEN_BLOCKS = {"StatementBlock", + } # for printing NMODL, we need to know which nodes are block types. # TODO: NEURON block is removed because it has internal statement block # and we don't want to print extra brace block for NMODL # We are removing NeuronBlock because it has statement block which # prints braces already. -BLOCK_TYPES = (GLOBAL_BLOCKS + ADDITIONAL_TOKEN_BLOCKS) +BLOCK_TYPES = GLOBAL_BLOCKS | ADDITIONAL_TOKEN_BLOCKS BLOCK_TYPES.remove("NeuronBlock") # Note that these are nodes which are not of type pointer in AST. # This also means that they can't be optional because they will appear # as value type in AST. -PTR_EXCLUDE_TYPES = ["BinaryOperator", "UnaryOperator", "ReactionOperator"] +PTR_EXCLUDE_TYPES = {"BinaryOperator", + "UnaryOperator", + "ReactionOperator", + } # these node names are explicitly added because they are used in ast/visitor # printer classes. In order to avoid hardcoding in the printer functions, they diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 30a76eee8f..747d3988f5 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -114,10 +114,6 @@ def is_number_node(self): def is_boolean_node(self): return self.class_name == node_info.BOOLEAN_NODE - @property - def is_identifier_node(self): - return self.class_name == node_info.IDENTIFIER_NODE - @property def is_name_node(self): return self.class_name == node_info.NAME_NODE @@ -225,6 +221,12 @@ def get_setter_method(self): reference = "" if self.is_base_type_node else "&&" return f"void {setter_method}({setter_type}{reference} {self.varname}) {{ this->{self.varname} = {self.varname}; }}" + def __repr__(self): + return "ChildNode(class_name='{}', nmodl_name='{}')".format( + self.class_name, self.nmodl_name) + + __str__ = __repr__ + class Node(BaseNode): """represent a class for every rule in language specification""" @@ -298,21 +300,13 @@ def is_symtab_method_required(self): :return: True if need to print visit method for node in symtabjsonvisitor otherwise False """ - method_required = False - - if self.has_children(): - - if(self.class_name in node_info.SYMBOL_VAR_TYPES or - self.class_name in node_info.SYMBOL_BLOCK_TYPES): - method_required = True - - if self.is_program_node or self.has_parent_block_node(): - method_required = True - - if self.class_name in node_info.SYMBOL_TABLE_HELPER_NODES: - method_required = True - - return method_required + return (self.has_children() and + (self.is_symbol_var_node or + self.is_symbol_block_node or + self.is_symbol_helper_node or + self.is_program_node or + self.has_parent_block_node() + )) @property def is_base_class_number_node(self): @@ -383,3 +377,9 @@ def private_members(self): @property def non_base_members(self): return [child for child in self.children if not child.is_base_type_node] + + def __repr__(self): + return "Node(class_name='{}', base_class='{}', nmodl_name='{}')".format( + self.class_name, self.base_class, self.nmodl_name) + + __str__ = __repr__ diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 85f458ab5d..772a076f17 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -45,22 +45,17 @@ def parse_child_rule(self, child): Child specification has additional option like list, optional, getName method etc. """ - # there is only one key and it has one value - varname = next(iter(list(child.keys()))) - properties = next(iter(list(child.values()))) + varname, properties = next(iter(child.items())) - # arguments holder for creating tree node args = Argument() - - # node of the variable args.varname = varname # type i.e. class of the variable args.class_name = properties['type'] if self.debug: - print(('Child {}, {}'.format(args.varname, args.class_name))) + print('Child {}, {}'.format(args.varname, args.class_name)) # if there is add method for member in the class if 'add' in properties: @@ -130,9 +125,12 @@ def parse_yaml_rules(self, nodelist, base_class=None): for node in nodelist: # name of the ast class and it's properties as dictionary - class_name = next(iter(list(node.keys()))) - properties = next(iter(list(node.values()))) - url = properties['url'] if 'url' in properties else None + class_name, properties = next(iter(node.items())) + + args = Argument() + args.url = properties['url'] if 'url' in properties else None + args.class_name = class_name + args.description = properties['description'] # yaml file has abstract classes and their subclasses with children as a property if 'children' in properties: @@ -145,45 +143,35 @@ def parse_yaml_rules(self, nodelist, base_class=None): # classes like AST which don't have base class # are not added (we print AST class separately) - if base_class: - args = Argument() + if base_class is not None: args.base_class = base_class - args.class_name = class_name - args.description = properties['description'] - args.url = url node = Node(args) abstract_nodes.append(node) nodes.insert(0, node) if self.debug: - print(('Abstract {}, {}'.format(base_class, class_name))) + print('Abstract {}'.format(node)) else: - # name of the node while printing back to NMODL - nmodl_name = properties['nmodl'] if 'nmodl' in properties else None + args.base_class = base_class if base_class else 'AST' # check if we need token for the node # todo : we will have token for every node - has_token = LanguageParser.is_token(class_name) + args.has_token = LanguageParser.is_token(class_name) - args = Argument() - args.base_class = base_class if base_class else 'AST' - args.class_name = class_name - args.description = properties['description'] - args.nmodl_name = nmodl_name - args.has_token = has_token - args.url = url + # name of the node while printing back to NMODL + args.nmodl_name = properties['nmodl'] if 'nmodl' in properties else None # create tree node and add to the list node = Node(args) nodes.append(node) if self.debug: - print(('Class {}, {}, {}'.format(base_class, class_name, nmodl_name))) + print('Class {}'.format(node)) # now process all children specification - childs = properties['members'] if 'members' in properties else [] - for child in childs: - args = self.parse_child_rule(child) - node.add_child(args) + if 'members' in properties: + for child in properties['members']: + args = self.parse_child_rule(child) + node.add_child(args) # update the abstract nodes for absnode in abstract_nodes: @@ -202,7 +190,8 @@ def parse_file(self): rules = yaml.load(stream) _, nodes = self.parse_yaml_rules(rules) except yaml.YAMLError as e: - print(("Error while parsing YAML definition file {0} : {1}".format(self.filename, e.strerror))) + print("Error while parsing YAML definition file {0} : {1}".format( + self.filename, e.strerror)) sys.exit(1) return nodes From ee7bb0792a66e88687f458191d738aa812f3a175 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Tue, 5 Mar 2019 07:51:43 +0100 Subject: [PATCH 149/871] Add namespaces consistently (BlueBrain/nmodl#18) - add nmodl global namespace for all codebase - add parser namespace and remove c11, diffeq namespaces - distinct class names for lexer, parser and drivers - move syminfo inside symtab namespace - remove most of `using namespace` statements NMODL Repo SHA: BlueBrain/nmodl@335ea14691e88acc9b67e5d75d61b8984038c223 --- src/nmodl/ast/ast_common.hpp | 11 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 6 + src/nmodl/codegen/codegen_acc_visitor.hpp | 6 + src/nmodl/codegen/codegen_c_visitor.cpp | 16 +- src/nmodl/codegen/codegen_c_visitor.hpp | 6 + src/nmodl/codegen/codegen_cuda_visitor.cpp | 10 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 7 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 18 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 7 +- src/nmodl/codegen/codegen_info.cpp | 6 +- src/nmodl/codegen/codegen_info.hpp | 2 + src/nmodl/codegen/codegen_naming.hpp | 4 +- src/nmodl/codegen/codegen_omp_visitor.cpp | 8 +- src/nmodl/codegen/codegen_omp_visitor.hpp | 6 + src/nmodl/language/templates/ast.cpp | 4 +- src/nmodl/language/templates/ast.hpp | 4 + src/nmodl/language/templates/ast_decl.hpp | 5 +- src/nmodl/language/templates/ast_visitor.cpp | 5 + src/nmodl/language/templates/ast_visitor.hpp | 4 + src/nmodl/language/templates/json_visitor.cpp | 5 + src/nmodl/language/templates/json_visitor.hpp | 3 + .../language/templates/lookup_visitor.cpp | 5 + .../language/templates/lookup_visitor.hpp | 4 + .../language/templates/nmodl_visitor.cpp | 7 +- .../language/templates/nmodl_visitor.hpp | 3 + src/nmodl/language/templates/pyast.cpp | 5 +- src/nmodl/language/templates/pyast.hpp | 5 +- src/nmodl/language/templates/pysymtab.cpp | 9 +- src/nmodl/language/templates/pyvisitor.cpp | 3 +- src/nmodl/language/templates/pyvisitor.hpp | 1 + .../language/templates/symtab_visitor.cpp | 8 +- .../language/templates/symtab_visitor.hpp | 6 +- src/nmodl/language/templates/visitor.hpp | 4 + src/nmodl/lexer/c11.ll | 220 +++++++++--------- src/nmodl/lexer/c11_lexer.hpp | 24 +- src/nmodl/lexer/diffeq.ll | 20 +- src/nmodl/lexer/diffeq_lexer.hpp | 16 +- src/nmodl/lexer/main_c.cpp | 13 +- src/nmodl/lexer/main_nmodl.cpp | 24 +- src/nmodl/lexer/modtoken.hpp | 6 +- src/nmodl/lexer/nmodl.ll | 34 +-- src/nmodl/lexer/nmodl_lexer.hpp | 17 +- src/nmodl/lexer/nmodl_utils.cpp | 3 + src/nmodl/lexer/nmodl_utils.hpp | 9 +- src/nmodl/lexer/token_mapping.cpp | 6 +- src/nmodl/lexer/token_mapping.hpp | 4 +- src/nmodl/lexer/verbatim.l | 6 +- src/nmodl/nmodl/arg_handler.cpp | 4 + src/nmodl/nmodl/arg_handler.hpp | 5 + src/nmodl/nmodl/main.cpp | 11 +- src/nmodl/parser/c11.yy | 24 +- src/nmodl/parser/c11_driver.cpp | 38 +-- src/nmodl/parser/c11_driver.hpp | 36 +-- src/nmodl/parser/diffeq.yy | 26 ++- src/nmodl/parser/diffeq_context.cpp | 12 +- src/nmodl/parser/diffeq_context.hpp | 4 + src/nmodl/parser/diffeq_driver.cpp | 36 +-- src/nmodl/parser/diffeq_driver.hpp | 17 +- src/nmodl/parser/diffeq_helper.hpp | 4 + src/nmodl/parser/main_c.cpp | 2 +- src/nmodl/parser/main_nmodl.cpp | 2 +- src/nmodl/parser/nmodl.yy | 31 ++- src/nmodl/parser/nmodl_driver.cpp | 25 +- src/nmodl/parser/nmodl_driver.hpp | 17 +- src/nmodl/parser/verbatim.yy | 5 +- src/nmodl/parser/verbatim_context.hpp | 11 +- src/nmodl/printer/code_printer.cpp | 4 + src/nmodl/printer/code_printer.hpp | 4 + src/nmodl/printer/json_printer.cpp | 5 + src/nmodl/printer/json_printer.hpp | 4 + src/nmodl/printer/nmodl_printer.cpp | 4 + src/nmodl/printer/nmodl_printer.hpp | 4 + src/nmodl/pybind/pynmodl.cpp | 14 +- src/nmodl/symtab/symbol.cpp | 12 +- src/nmodl/symtab/symbol.hpp | 4 + src/nmodl/symtab/symbol_properties.cpp | 9 +- src/nmodl/symtab/symbol_properties.hpp | 14 +- src/nmodl/symtab/symbol_table.cpp | 12 +- src/nmodl/symtab/symbol_table.hpp | 4 +- src/nmodl/utils/common_utils.cpp | 5 + src/nmodl/utils/common_utils.hpp | 5 + src/nmodl/utils/logger.cpp | 5 + src/nmodl/utils/logger.hpp | 4 + src/nmodl/utils/perf_stat.cpp | 4 + src/nmodl/utils/perf_stat.hpp | 5 + src/nmodl/utils/string_utils.hpp | 5 + src/nmodl/utils/table_data.cpp | 9 +- src/nmodl/utils/table_data.hpp | 5 + src/nmodl/visitors/CMakeLists.txt | 2 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 22 +- src/nmodl/visitors/cnexp_solve_visitor.hpp | 4 + src/nmodl/visitors/defuse_analyze_visitor.cpp | 23 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 4 + src/nmodl/visitors/inline_visitor.cpp | 6 +- src/nmodl/visitors/inline_visitor.hpp | 5 + .../visitors/local_var_rename_visitor.cpp | 10 +- .../visitors/local_var_rename_visitor.hpp | 4 + src/nmodl/visitors/localize_visitor.cpp | 15 +- src/nmodl/visitors/localize_visitor.hpp | 5 + src/nmodl/visitors/main.cpp | 3 +- ...or_helper.hpp => nmodl_visitor_helper.ipp} | 4 + src/nmodl/visitors/perf_visitor.cpp | 73 +++--- src/nmodl/visitors/perf_visitor.hpp | 5 + src/nmodl/visitors/rename_visitor.cpp | 11 +- src/nmodl/visitors/rename_visitor.hpp | 5 + .../visitors/sympy_conductance_visitor.cpp | 28 ++- .../visitors/sympy_conductance_visitor.hpp | 22 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 27 ++- src/nmodl/visitors/sympy_solver_visitor.hpp | 6 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 47 ++-- src/nmodl/visitors/var_usage_visitor.cpp | 5 + src/nmodl/visitors/var_usage_visitor.hpp | 5 + .../visitors/verbatim_var_rename_visitor.cpp | 13 +- .../visitors/verbatim_var_rename_visitor.hpp | 4 + src/nmodl/visitors/verbatim_visitor.cpp | 7 +- src/nmodl/visitors/verbatim_visitor.hpp | 4 + src/nmodl/visitors/visitor_utils.cpp | 22 +- src/nmodl/visitors/visitor_utils.hpp | 3 +- test/nmodl/transpiler/lexer/tokens.cpp | 18 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 10 +- test/nmodl/transpiler/parser/parser.cpp | 4 +- test/nmodl/transpiler/printer/printer.cpp | 6 +- test/nmodl/transpiler/pybind/conftest.py | 4 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 3 +- test/nmodl/transpiler/utils/test_utils.cpp | 4 +- test/nmodl/transpiler/visitor/visitor.cpp | 48 ++-- 126 files changed, 998 insertions(+), 549 deletions(-) rename src/nmodl/visitors/{nmodl_visitor_helper.hpp => nmodl_visitor_helper.ipp} (98%) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 524b9b5b3c..84e08ef27b 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -11,12 +11,13 @@ #include <string> #include "ast/ast_decl.hpp" -#include "visitors/visitor.hpp" - #include "lexer/modtoken.hpp" #include "symtab/symbol_table.hpp" +#include "visitors/visitor.hpp" +namespace nmodl { namespace ast { + /* enumaration of all binary operators in the language */ typedef enum { BOP_ADDITION, @@ -77,8 +78,11 @@ struct AST: public std::enable_shared_from_this<AST> { /* all AST nodes provide visit children and accept methods */ virtual void visit_children(Visitor* v) = 0; + virtual void accept(Visitor* v) = 0; + virtual AstNodeType get_node_type() = 0; + virtual std::string get_node_type_name() = 0; virtual std::string get_node_name() { @@ -665,4 +669,5 @@ struct AST: public std::enable_shared_from_this<AST> { } }; -} // namespace ast \ No newline at end of file +} // namespace ast +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index b05c70672d..5ab088c8fb 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -12,6 +12,9 @@ using namespace fmt::literals; +namespace nmodl { +namespace codegen { + /****************************************************************************************/ /* Routines must be overloaded in backend */ /****************************************************************************************/ @@ -118,3 +121,6 @@ void CodegenAccVisitor::print_rhs_d_shadow_variables() { bool CodegenAccVisitor::nrn_cur_reduction_loop_required() { return false; } + +} // namespace codegen +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 3cf9cbc6d2..b1b8b79e9d 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -10,6 +10,9 @@ #include "codegen/codegen_c_visitor.hpp" +namespace nmodl { +namespace codegen { + /** * \class CodegenAccVisitor * \brief Visitor for printing c code with OpenMP backend @@ -73,3 +76,6 @@ class CodegenAccVisitor: public CodegenCVisitor { std::string float_type) : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 4b581f10fc..3f80912b09 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -20,14 +20,15 @@ #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" +using namespace fmt::literals; + +namespace nmodl { +namespace codegen { using namespace ast; -using namespace codegen; -using namespace symtab; -using namespace syminfo; -using namespace fmt::literals; -using SymbolType = std::shared_ptr<symtab::Symbol>; +using symtab::syminfo::NmodlType; +using SymbolType = std::shared_ptr<symtab::Symbol>; /****************************************************************************************/ /* Overloaded visitor routines */ @@ -1708,7 +1709,7 @@ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { * handle it more elegantly. */ std::string CodegenCVisitor::process_verbatim_text(std::string text) { - c11::Driver driver; + parser::CDriver driver; driver.scan_string(text); auto tokens = driver.all_tokens(); std::string result; @@ -3880,3 +3881,6 @@ void CodegenCVisitor::visit_program(Program* node) { rename_function_arguments(); print_codegen_routines(); } + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 2820338b92..5fb0fda374 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -25,6 +25,9 @@ using namespace fmt::literals; +namespace nmodl { +namespace codegen { + /** * \enum BlockType * \brief Helper to represent various block types @@ -1026,3 +1029,6 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin enable_variable_name_lookup = true; } + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 4cd1a7e296..015db5de53 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -12,15 +12,16 @@ #include "utils/string_utils.hpp" using namespace fmt::literals; -using namespace symtab; -using namespace syminfo; +namespace nmodl { +namespace codegen { + +using symtab::syminfo::NmodlType; /****************************************************************************************/ /* Routines must be overloaded in backend */ /****************************************************************************************/ - /** * As initial block is/can be executed on c/cpu backend, gpu/cuda * backend can mark the parameter as constant even if they have @@ -203,3 +204,6 @@ void CodegenCudaVisitor::print_codegen_routines() { print_namespace_end(); } + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index bcef9fa410..8db1586850 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -9,6 +9,8 @@ #include "codegen/codegen_c_visitor.hpp" +namespace nmodl { +namespace codegen { /** * \class CodegenCudaVisitor @@ -98,4 +100,7 @@ class CodegenCudaVisitor: public CodegenCVisitor { LayoutType layout, std::string float_type) : CodegenCVisitor(mod_file, stream, layout, float_type) {} -}; \ No newline at end of file +}; + +} // namespace codegen +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index f59b0bcbbd..65bf847a7a 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -14,12 +14,17 @@ #include <fmt/format.h> -using namespace ast; -using namespace codegen; -using namespace symtab; -using namespace syminfo; using namespace fmt::literals; + +namespace nmodl { +namespace codegen { + +using namespace ast; + +using symtab::syminfo::NmodlType; +using symtab::syminfo::Status; + /** * How symbols are stored in NEURON? See notes written in markdown file. * @@ -548,7 +553,7 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { auto symbol = psymtab->lookup(name); if (symbol != nullptr) { auto is_prime = symbol->has_properties(NmodlType::prime_name); - auto from_state = symbol->has_any_status(syminfo::Status::from_state); + auto from_state = symbol->has_any_status(Status::from_state); if (is_prime || from_state) { if (from_state) { symbol = psymtab->lookup(name.substr(1, name.size())); @@ -634,3 +639,6 @@ codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program* node) { node->accept(this); return info; } + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 80fe641243..b562f972e0 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -13,6 +13,8 @@ #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" +namespace nmodl { +namespace codegen { /** * \class CodegenHelperVisitor @@ -88,4 +90,7 @@ class CodegenHelperVisitor: public AstVisitor { void visit_for_netcon(ast::ForNetcon* node) override; void visit_table_statement(ast::TableStatement* node) override; void visit_program(ast::Program* node) override; -}; \ No newline at end of file +}; + +} // namespace codegen +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 4fe9e5ef64..ddd2c37585 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -7,8 +7,9 @@ #include "codegen/codegen_info.hpp" -using namespace codegen; +namespace nmodl { +namespace codegen { /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() { @@ -72,3 +73,6 @@ bool CodegenInfo::function_uses_table(std::string& name) const { } return false; } + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 235409d626..f69cb49bff 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -13,6 +13,7 @@ #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" +namespace nmodl { namespace codegen { /** @@ -371,3 +372,4 @@ struct CodegenInfo { }; } // namespace codegen +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 2b64e65815..6742008029 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -10,8 +10,9 @@ #include <map> #include <string> -namespace codegen { +namespace nmodl { +namespace codegen { namespace naming { /// nmodl language version @@ -151,3 +152,4 @@ const std::string THREAD_ARGS_PROTO("_threadargsproto_"); } // namespace naming } // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp index 9032cd4db6..01ae4f97d4 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -8,9 +8,10 @@ #include "codegen/codegen_omp_visitor.hpp" -using namespace symtab; using namespace fmt::literals; -using SymbolType = std::shared_ptr<symtab::Symbol>; + +namespace nmodl { +namespace codegen { /****************************************************************************************/ @@ -99,3 +100,6 @@ bool CodegenOmpVisitor::channel_task_dependency_enabled() { bool CodegenOmpVisitor::block_require_shadow_update(BlockType type) { return !(!channel_task_dependency_enabled() || type == BlockType::Initial); } + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index 2dfd5c6226..3517f79f4f 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -10,6 +10,9 @@ #include "codegen/codegen_c_visitor.hpp" +namespace nmodl { +namespace codegen { + /** * \class CodegenOmpVisitor * \brief Visitor for printing c code with OpenMP backend @@ -69,3 +72,6 @@ class CodegenOmpVisitor: public CodegenCVisitor { std::string float_type) : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; + +} // namespace codegen +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/ast.cpp b/src/nmodl/language/templates/ast.cpp index 6c69a3da6b..a74f979b6b 100644 --- a/src/nmodl/language/templates/ast.cpp +++ b/src/nmodl/language/templates/ast.cpp @@ -9,6 +9,7 @@ #include "symtab/symbol_table.hpp" +namespace nmodl { namespace ast { {% for node in nodes %} @@ -68,4 +69,5 @@ namespace ast { {% endfor %} -} +} // namespace ast +} // namespace nmodl diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast.hpp index b60d2ec6e2..8cdaafb21e 100644 --- a/src/nmodl/language/templates/ast.hpp +++ b/src/nmodl/language/templates/ast.hpp @@ -26,6 +26,8 @@ {% if not node.is_abstract %} override {% endif %} {% endmacro %} + +namespace nmodl { namespace ast { {% for node in nodes %} @@ -133,4 +135,6 @@ namespace ast { }; {% endfor %} + } // namespace ast +} // namespace nmodl diff --git a/src/nmodl/language/templates/ast_decl.hpp b/src/nmodl/language/templates/ast_decl.hpp index ce770e4164..e53f81d46e 100644 --- a/src/nmodl/language/templates/ast_decl.hpp +++ b/src/nmodl/language/templates/ast_decl.hpp @@ -11,6 +11,8 @@ #include <utility> #include <vector> + +namespace nmodl { namespace ast { {% for node in nodes %} @@ -29,4 +31,5 @@ namespace ast { using {{ node.class_name }}Vector = std::vector<std::shared_ptr<{{ node.class_name }}>>; {% endfor %} -} +} // namespace ast +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/ast_visitor.cpp b/src/nmodl/language/templates/ast_visitor.cpp index 94252e7d59..d970bcb7e2 100644 --- a/src/nmodl/language/templates/ast_visitor.cpp +++ b/src/nmodl/language/templates/ast_visitor.cpp @@ -7,6 +7,9 @@ #include "visitors/ast_visitor.hpp" + +namespace nmodl { + using namespace ast; {% for node in nodes %} @@ -15,3 +18,5 @@ void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* n } {% endfor %} + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/ast_visitor.hpp b/src/nmodl/language/templates/ast_visitor.hpp index e8bc460471..9d3adbb8cc 100644 --- a/src/nmodl/language/templates/ast_visitor.hpp +++ b/src/nmodl/language/templates/ast_visitor.hpp @@ -11,6 +11,8 @@ #include "visitors/visitor.hpp" +namespace nmodl { + /* Basic visitor implementation */ class AstVisitor : public Visitor { @@ -19,3 +21,5 @@ class AstVisitor : public Visitor { void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/json_visitor.cpp index f084f9db81..e68c4c50f9 100644 --- a/src/nmodl/language/templates/json_visitor.cpp +++ b/src/nmodl/language/templates/json_visitor.cpp @@ -7,6 +7,9 @@ #include "visitors/json_visitor.hpp" + +namespace nmodl { + using namespace ast; {% for node in nodes %} @@ -38,3 +41,5 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* } {% endfor %} + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/json_visitor.hpp b/src/nmodl/language/templates/json_visitor.hpp index 5eb5509d2c..dc56c222a5 100644 --- a/src/nmodl/language/templates/json_visitor.hpp +++ b/src/nmodl/language/templates/json_visitor.hpp @@ -10,6 +10,7 @@ #include "visitors/ast_visitor.hpp" #include "printer/json_printer.hpp" +namespace nmodl { /* Concrete visitor for printing AST in JSON format */ class JSONVisitor : public AstVisitor { @@ -30,3 +31,5 @@ class JSONVisitor : public AstVisitor { void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/lookup_visitor.cpp index 064955af2f..68f3e6e1db 100644 --- a/src/nmodl/language/templates/lookup_visitor.cpp +++ b/src/nmodl/language/templates/lookup_visitor.cpp @@ -8,6 +8,9 @@ #include <algorithm> #include "visitors/lookup_visitor.hpp" + +namespace nmodl { + using namespace ast; {% for node in nodes %} @@ -37,3 +40,5 @@ std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(Program* node, A node->accept(this); return nodes; } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index b43ebb6b34..0b510562de 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -11,6 +11,8 @@ #include "visitors/visitor.hpp" +namespace nmodl { + /** * \class AstLookupVisitor * \brief Visitor to find ast nodes based on on the ast types @@ -49,3 +51,5 @@ class AstLookupVisitor : public Visitor { void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endfor %} }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index c9498569f3..74a5e938ca 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -6,7 +6,10 @@ *************************************************************************/ #include "visitors/nmodl_visitor.hpp" -#include "visitors/nmodl_visitor_helper.hpp" +#include "visitors/nmodl_visitor_helper.ipp" + + +namespace nmodl { using namespace ast; @@ -110,3 +113,5 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name } {% endfor %} + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/nmodl_visitor.hpp b/src/nmodl/language/templates/nmodl_visitor.hpp index 06f96d7bd0..05d7718e4f 100644 --- a/src/nmodl/language/templates/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/nmodl_visitor.hpp @@ -10,6 +10,7 @@ #include "ast/ast.hpp" #include "printer/nmodl_printer.hpp" +namespace nmodl { /* Visitor for printing AST back to NMODL */ class NmodlPrintVisitor : public Visitor { @@ -27,3 +28,5 @@ class NmodlPrintVisitor : public Visitor { template<typename T> void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement); }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pyast.cpp index 7ee86ce385..e9bde27cb4 100644 --- a/src/nmodl/language/templates/pyast.cpp +++ b/src/nmodl/language/templates/pyast.cpp @@ -21,7 +21,10 @@ {%- endmacro -%} namespace py = pybind11; -using namespace ast; + + +using namespace nmodl::ast; +using nmodl::JSONVisitor; void init_ast_module(py::module& m) { diff --git a/src/nmodl/language/templates/pyast.hpp b/src/nmodl/language/templates/pyast.hpp index abd17206e4..3fcde3abd0 100644 --- a/src/nmodl/language/templates/pyast.hpp +++ b/src/nmodl/language/templates/pyast.hpp @@ -15,7 +15,8 @@ #include "symtab/symbol_table.hpp" -namespace ast { +using namespace nmodl; +using namespace ast; struct PyAST: public AST { public: @@ -76,5 +77,3 @@ struct PyAST: public AST { {% endfor %} }; - -} // namespace ast \ No newline at end of file diff --git a/src/nmodl/language/templates/pysymtab.cpp b/src/nmodl/language/templates/pysymtab.cpp index bb8462195f..30cc8747cb 100644 --- a/src/nmodl/language/templates/pysymtab.cpp +++ b/src/nmodl/language/templates/pysymtab.cpp @@ -27,6 +27,8 @@ {%- endmacro -%} namespace py = pybind11; +using namespace nmodl; +using namespace symtab; class PySymtabVisitor : private VisitorOStreamResources, public SymtabVisitor { @@ -39,7 +41,6 @@ class PySymtabVisitor : private VisitorOStreamResources, public SymtabVisitor { SymtabVisitor(*ostream, update) { }; }; -using namespace symtab; void init_symtab_module(py::module& m) { py::module m_symtab = m.def_submodule("symtab"); @@ -71,7 +72,7 @@ void init_symtab_module(py::module& m) { .def("__and__",[](const syminfo::Status& x, syminfo::Status y) { return x & y; }) - .def("__str__", &to_string<syminfo::Status>); + .def("__str__", &syminfo::to_string<syminfo::Status>); py::enum_<syminfo::VariableType>(m_symtab, "VariableType") .value("array", syminfo::VariableType::array) @@ -125,7 +126,7 @@ void init_symtab_module(py::module& m) { .def("__and__",[](const syminfo::NmodlType& x, syminfo::NmodlType y) { return x & y; }) - .def("__str__", &to_string<syminfo::NmodlType>); + .def("__str__", &syminfo::to_string<syminfo::NmodlType>); py::class_<Symbol, std::shared_ptr<Symbol>> symbol(m_symtab, "Symbol"); @@ -184,6 +185,6 @@ void init_symtab_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - } + #pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index cae65a8cb9..0af5b487f0 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -108,4 +108,5 @@ void init_visitor_module(py::module& m) { sympy_conductance_visitor.def(py::init<>()) .def("visit_program", &SympyConductanceVisitor::visit_program); } -#pragma clang diagnostic pop \ No newline at end of file + +#pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pyvisitor.hpp b/src/nmodl/language/templates/pyvisitor.hpp index eaa9048ce6..989c178740 100644 --- a/src/nmodl/language/templates/pyvisitor.hpp +++ b/src/nmodl/language/templates/pyvisitor.hpp @@ -13,6 +13,7 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +using namespace nmodl; class PyVisitor : public Visitor { public: diff --git a/src/nmodl/language/templates/symtab_visitor.cpp b/src/nmodl/language/templates/symtab_visitor.cpp index 6c247ca06f..182631dd59 100644 --- a/src/nmodl/language/templates/symtab_visitor.cpp +++ b/src/nmodl/language/templates/symtab_visitor.cpp @@ -9,12 +9,16 @@ #include "visitors/symtab_visitor.hpp" #include "visitors/symtab_visitor_helper.hpp" + +namespace nmodl { + using namespace ast; +using symtab::syminfo::NmodlType; {% for node in nodes %} {% if node.is_symtab_method_required and not node.is_symbol_helper_node %} {% set typename = node.class_name|snake_case %} -{% set propname = "syminfo::NmodlType::" + typename %} +{% set propname = "NmodlType::" + typename %} void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { {% if node.is_symbol_var_node %} setup_symbol(node, {{ propname }}); @@ -32,3 +36,5 @@ void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { {% endif %} {% endfor %} + +} // namespace nmodl diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp index e6aa122257..fb3dcb5bc6 100644 --- a/src/nmodl/language/templates/symtab_visitor.hpp +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -11,6 +11,7 @@ #include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" +namespace nmodl { /* Concrete visitor for constructing symbol table from AST */ class SymtabVisitor : public AstVisitor { @@ -28,9 +29,9 @@ class SymtabVisitor : public AstVisitor { SymtabVisitor(std::ostream &os, bool update = false) : printer(new JSONPrinter(os)), update(update) {} SymtabVisitor(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {} - void add_model_symbol_with_property(ast::Node* node, syminfo::NmodlType property); + void add_model_symbol_with_property(ast::Node* node, symtab::syminfo::NmodlType property); - void setup_symbol(ast::Node* node, syminfo::NmodlType property); + void setup_symbol(ast::Node* node, symtab::syminfo::NmodlType property); void setup_symbol_table(ast::AST* node, const std::string& name, bool is_global); @@ -47,3 +48,4 @@ class SymtabVisitor : public AstVisitor { {% endfor %} }; +} // namespace nmodl diff --git a/src/nmodl/language/templates/visitor.hpp b/src/nmodl/language/templates/visitor.hpp index 4cbc75c5c2..0d7a90a8bb 100644 --- a/src/nmodl/language/templates/visitor.hpp +++ b/src/nmodl/language/templates/visitor.hpp @@ -8,6 +8,8 @@ #pragma once +namespace nmodl { + /* Abstract base class for all visitor implementations */ class Visitor { @@ -18,3 +20,5 @@ class Visitor { virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) = 0; {% endfor %} }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index c717e8d65e..afd586b139 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -26,7 +26,7 @@ /** By default yylex returns int, we use token_type. Unfortunately * yyterminate by default returns 0, which is not of token_type. */ - #define yyterminate() return c11::Parser::make_END(loc); + #define yyterminate() return CParser::make_END(loc); /** Disables inclusion of unistd.h, which is not available under Visual * C++ on Win32. The C++ scanner uses STL streams instead. */ @@ -66,8 +66,8 @@ WS [ \t\v\n\f] /** name of the lexer implementation file */ %option outfile="c11_base_lexer.cpp" -/** change the name of the scanner class (to "C11FlexLexer") */ -%option prefix="C11" +/** change the name of the scanner class (to "CFlexLexer") */ +%option prefix="C" /** enable C++ scanner which is also reentrant */ %option c++ @@ -156,227 +156,227 @@ WS [ \t\v\n\f] "auto" { driver.add_token(yytext); - return c11::Parser::make_AUTO(yytext, loc); + return CParser::make_AUTO(yytext, loc); } "break" { driver.add_token(yytext); - return c11::Parser::make_BREAK(yytext, loc); + return CParser::make_BREAK(yytext, loc); } "case" { driver.add_token(yytext); - return c11::Parser::make_CASE(yytext, loc); + return CParser::make_CASE(yytext, loc); } "char" { driver.add_token(yytext); - return c11::Parser::make_CHAR(yytext, loc); + return CParser::make_CHAR(yytext, loc); } "const" { driver.add_token(yytext); - return c11::Parser::make_CONST(yytext, loc); + return CParser::make_CONST(yytext, loc); } "continue" { driver.add_token(yytext); - return c11::Parser::make_CONTINUE(yytext, loc); + return CParser::make_CONTINUE(yytext, loc); } "default" { driver.add_token(yytext); - return c11::Parser::make_DEFAULT(yytext, loc); + return CParser::make_DEFAULT(yytext, loc); } "do" { driver.add_token(yytext); - return c11::Parser::make_DO(yytext, loc); + return CParser::make_DO(yytext, loc); } "double" { driver.add_token(yytext); - return c11::Parser::make_DOUBLE(yytext, loc); + return CParser::make_DOUBLE(yytext, loc); } "else" { driver.add_token(yytext); - return c11::Parser::make_ELSE(yytext, loc); + return CParser::make_ELSE(yytext, loc); } "enum" { driver.add_token(yytext); - return c11::Parser::make_ENUM(yytext, loc); + return CParser::make_ENUM(yytext, loc); } "extern" { driver.add_token(yytext); - return c11::Parser::make_EXTERN(yytext, loc); + return CParser::make_EXTERN(yytext, loc); } "float" { driver.add_token(yytext); - return c11::Parser::make_FLOAT(yytext, loc); + return CParser::make_FLOAT(yytext, loc); } "for" { driver.add_token(yytext); - return c11::Parser::make_FOR(yytext, loc); + return CParser::make_FOR(yytext, loc); } "goto" { driver.add_token(yytext); - return c11::Parser::make_GOTO(yytext, loc); + return CParser::make_GOTO(yytext, loc); } "if" { driver.add_token(yytext); - return c11::Parser::make_IF(yytext, loc); + return CParser::make_IF(yytext, loc); } "inline" { driver.add_token(yytext); - return c11::Parser::make_INLINE(yytext, loc); + return CParser::make_INLINE(yytext, loc); } "int" { driver.add_token(yytext); - return c11::Parser::make_INT(yytext, loc); + return CParser::make_INT(yytext, loc); } "long" { driver.add_token(yytext); - return c11::Parser::make_LONG(yytext, loc); + return CParser::make_LONG(yytext, loc); } "register" { driver.add_token(yytext); - return c11::Parser::make_REGISTER(yytext, loc); + return CParser::make_REGISTER(yytext, loc); } "restrict" { driver.add_token(yytext); - return c11::Parser::make_RESTRICT(yytext, loc); + return CParser::make_RESTRICT(yytext, loc); } "return" { driver.add_token(yytext); - return c11::Parser::make_RETURN(yytext, loc); + return CParser::make_RETURN(yytext, loc); } "short" { driver.add_token(yytext); - return c11::Parser::make_SHORT(yytext, loc); + return CParser::make_SHORT(yytext, loc); } "signed" { driver.add_token(yytext); - return c11::Parser::make_SIGNED(yytext, loc); + return CParser::make_SIGNED(yytext, loc); } "sizeof" { driver.add_token(yytext); - return c11::Parser::make_SIZEOF(yytext, loc); + return CParser::make_SIZEOF(yytext, loc); } "static" { driver.add_token(yytext); - return c11::Parser::make_STATIC(yytext, loc); + return CParser::make_STATIC(yytext, loc); } "struct" { driver.add_token(yytext); - return c11::Parser::make_STRUCT(yytext, loc); + return CParser::make_STRUCT(yytext, loc); } "switch" { driver.add_token(yytext); - return c11::Parser::make_SWITCH(yytext, loc); + return CParser::make_SWITCH(yytext, loc); } "typedef" { driver.add_token(yytext); - return c11::Parser::make_TYPEDEF(yytext, loc); + return CParser::make_TYPEDEF(yytext, loc); } "union" { driver.add_token(yytext); - return c11::Parser::make_UNION(yytext, loc); + return CParser::make_UNION(yytext, loc); } "unsigned" { driver.add_token(yytext); - return c11::Parser::make_UNSIGNED(yytext, loc); + return CParser::make_UNSIGNED(yytext, loc); } "void" { driver.add_token(yytext); - return c11::Parser::make_VOID(yytext, loc); + return CParser::make_VOID(yytext, loc); } "volatile" { driver.add_token(yytext); - return c11::Parser::make_VOLATILE(yytext, loc); + return CParser::make_VOLATILE(yytext, loc); } "while" { driver.add_token(yytext); - return c11::Parser::make_WHILE(yytext, loc); + return CParser::make_WHILE(yytext, loc); } "_Alignas" { driver.add_token(yytext); - return c11::Parser::make_ALIGNAS(yytext, loc); + return CParser::make_ALIGNAS(yytext, loc); } "_Alignof" { driver.add_token(yytext); - return c11::Parser::make_ALIGNOF(yytext, loc); + return CParser::make_ALIGNOF(yytext, loc); } "_Atomic" { driver.add_token(yytext); - return c11::Parser::make_ATOMIC(yytext, loc); + return CParser::make_ATOMIC(yytext, loc); } "_Bool" { driver.add_token(yytext); - return c11::Parser::make_BOOL(yytext, loc); + return CParser::make_BOOL(yytext, loc); } "_Complex" { driver.add_token(yytext); - return c11::Parser::make_COMPLEX(yytext, loc); + return CParser::make_COMPLEX(yytext, loc); } "_Generic" { driver.add_token(yytext); - return c11::Parser::make_GENERIC(yytext, loc); + return CParser::make_GENERIC(yytext, loc); } "_Imaginary" { driver.add_token(yytext); - return c11::Parser::make_IMAGINARY(yytext, loc); + return CParser::make_IMAGINARY(yytext, loc); } "_Noreturn" { driver.add_token(yytext); - return c11::Parser::make_NORETURN(yytext, loc); + return CParser::make_NORETURN(yytext, loc); } "_Static_assert" { driver.add_token(yytext); - return c11::Parser::make_STATIC_ASSERT(yytext, loc); + return CParser::make_STATIC_ASSERT(yytext, loc); } "_Thread_local" { driver.add_token(yytext); - return c11::Parser::make_THREAD_LOCAL(yytext, loc); + return CParser::make_THREAD_LOCAL(yytext, loc); } "__func__" { driver.add_token(yytext); - return c11::Parser::make_FUNC_NAME(yytext, loc); + return CParser::make_FUNC_NAME(yytext, loc); } {L}{A}* { @@ -386,287 +386,287 @@ WS [ \t\v\n\f] {HP}{H}+{IS}? { driver.add_token(yytext); - return c11::Parser::make_I_CONSTANT(yytext, loc); + return CParser::make_I_CONSTANT(yytext, loc); } {NZ}{D}*{IS}? { driver.add_token(yytext); - return c11::Parser::make_I_CONSTANT(yytext, loc); + return CParser::make_I_CONSTANT(yytext, loc); } "0"{O}*{IS}? { driver.add_token(yytext); - return c11::Parser::make_I_CONSTANT(yytext, loc); + return CParser::make_I_CONSTANT(yytext, loc); } {CP}?"'"([^'\\\n]|{ES})+"'" { driver.add_token(yytext); - return c11::Parser::make_I_CONSTANT(yytext, loc); + return CParser::make_I_CONSTANT(yytext, loc); } {D}+{E}{FS}? { driver.add_token(yytext); - return c11::Parser::make_F_CONSTANT(yytext, loc); + return CParser::make_F_CONSTANT(yytext, loc); } {D}*"."{D}+{E}?{FS}? { driver.add_token(yytext); - return c11::Parser::make_F_CONSTANT(yytext, loc); + return CParser::make_F_CONSTANT(yytext, loc); } {D}+"."{E}?{FS}? { driver.add_token(yytext); - return c11::Parser::make_F_CONSTANT(yytext, loc); + return CParser::make_F_CONSTANT(yytext, loc); } {HP}{H}+{P}{FS}? { driver.add_token(yytext); - return c11::Parser::make_F_CONSTANT(yytext, loc); + return CParser::make_F_CONSTANT(yytext, loc); } {HP}{H}*"."{H}+{P}{FS}? { driver.add_token(yytext); - return c11::Parser::make_F_CONSTANT(yytext, loc); + return CParser::make_F_CONSTANT(yytext, loc); } {HP}{H}+"."{P}{FS}? { driver.add_token(yytext); - return c11::Parser::make_F_CONSTANT(yytext, loc); + return CParser::make_F_CONSTANT(yytext, loc); } ({SP}?\"([^"\\\n]|{ES})*\"{WS}*)+ { driver.add_token(yytext); - return c11::Parser::make_STRING_LITERAL(yytext, loc); + return CParser::make_STRING_LITERAL(yytext, loc); } "..." { driver.add_token(yytext); - return c11::Parser::make_ELLIPSIS(yytext, loc); + return CParser::make_ELLIPSIS(yytext, loc); } ">>=" { driver.add_token(yytext); - return c11::Parser::make_RIGHT_ASSIGN(yytext, loc); + return CParser::make_RIGHT_ASSIGN(yytext, loc); } "<<=" { driver.add_token(yytext); - return c11::Parser::make_LEFT_ASSIGN(yytext, loc); + return CParser::make_LEFT_ASSIGN(yytext, loc); } "+=" { driver.add_token(yytext); - return c11::Parser::make_ADD_ASSIGN(yytext, loc); + return CParser::make_ADD_ASSIGN(yytext, loc); } "-=" { driver.add_token(yytext); - return c11::Parser::make_SUB_ASSIGN(yytext, loc); + return CParser::make_SUB_ASSIGN(yytext, loc); } "*=" { driver.add_token(yytext); - return c11::Parser::make_MUL_ASSIGN(yytext, loc); + return CParser::make_MUL_ASSIGN(yytext, loc); } "/=" { driver.add_token(yytext); - return c11::Parser::make_DIV_ASSIGN(yytext, loc); + return CParser::make_DIV_ASSIGN(yytext, loc); } "%=" { driver.add_token(yytext); - return c11::Parser::make_MOD_ASSIGN(yytext, loc); + return CParser::make_MOD_ASSIGN(yytext, loc); } "&=" { driver.add_token(yytext); - return c11::Parser::make_AND_ASSIGN(yytext, loc); + return CParser::make_AND_ASSIGN(yytext, loc); } "^=" { driver.add_token(yytext); - return c11::Parser::make_XOR_ASSIGN(yytext, loc); + return CParser::make_XOR_ASSIGN(yytext, loc); } "|=" { driver.add_token(yytext); - return c11::Parser::make_OR_ASSIGN(yytext, loc); + return CParser::make_OR_ASSIGN(yytext, loc); } ">>" { driver.add_token(yytext); - return c11::Parser::make_RIGHT_OP(yytext, loc); + return CParser::make_RIGHT_OP(yytext, loc); } "<<" { driver.add_token(yytext); - return c11::Parser::make_LEFT_OP(yytext, loc); + return CParser::make_LEFT_OP(yytext, loc); } "++" { driver.add_token(yytext); - return c11::Parser::make_INC_OP(yytext, loc); + return CParser::make_INC_OP(yytext, loc); } "--" { driver.add_token(yytext); - return c11::Parser::make_DEC_OP(yytext, loc); + return CParser::make_DEC_OP(yytext, loc); } "->" { driver.add_token(yytext); - return c11::Parser::make_PTR_OP(yytext, loc); + return CParser::make_PTR_OP(yytext, loc); } "&&" { driver.add_token(yytext); - return c11::Parser::make_AND_OP(yytext, loc); + return CParser::make_AND_OP(yytext, loc); } "||" { driver.add_token(yytext); - return c11::Parser::make_OR_OP(yytext, loc); + return CParser::make_OR_OP(yytext, loc); } "<=" { driver.add_token(yytext); - return c11::Parser::make_LE_OP(yytext, loc); + return CParser::make_LE_OP(yytext, loc); } ">=" { driver.add_token(yytext); - return c11::Parser::make_GE_OP(yytext, loc); + return CParser::make_GE_OP(yytext, loc); } "==" { driver.add_token(yytext); - return c11::Parser::make_EQ_OP(yytext, loc); + return CParser::make_EQ_OP(yytext, loc); } "!=" { driver.add_token(yytext); - return c11::Parser::make_NE_OP(yytext, loc); + return CParser::make_NE_OP(yytext, loc); } ";" { driver.add_token(yytext); - return c11::Parser::make_SEMICOLON(yytext, loc); + return CParser::make_SEMICOLON(yytext, loc); } ("{"|"<%") { driver.add_token(yytext); - return c11::Parser::make_OPEN_BRACE(yytext, loc); + return CParser::make_OPEN_BRACE(yytext, loc); } ("}"|"%>") { driver.add_token(yytext); - return c11::Parser::make_CLOSE_BRACE(yytext, loc); + return CParser::make_CLOSE_BRACE(yytext, loc); } "," { driver.add_token(yytext); - return c11::Parser::make_COMMA(yytext, loc); + return CParser::make_COMMA(yytext, loc); } ":" { driver.add_token(yytext); - return c11::Parser::make_COLON(yytext, loc); + return CParser::make_COLON(yytext, loc); } "=" { driver.add_token(yytext); - return c11::Parser::make_ASSIGN(yytext, loc); + return CParser::make_ASSIGN(yytext, loc); } "(" { driver.add_token(yytext); - return c11::Parser::make_OPEN_PARENTHESIS(yytext, loc); + return CParser::make_OPEN_PARENTHESIS(yytext, loc); } ")" { driver.add_token(yytext); - return c11::Parser::make_CLOSE_PARENTHESIS(yytext, loc); + return CParser::make_CLOSE_PARENTHESIS(yytext, loc); } ("["|"<:") { driver.add_token(yytext); - return c11::Parser::make_OPEN_BRACKET(yytext, loc); + return CParser::make_OPEN_BRACKET(yytext, loc); } ("]"|":>") { driver.add_token(yytext); - return c11::Parser::make_CLOSE_BRACKET(yytext, loc); + return CParser::make_CLOSE_BRACKET(yytext, loc); } "." { driver.add_token(yytext); - return c11::Parser::make_PERIOD(yytext, loc); + return CParser::make_PERIOD(yytext, loc); } "&" { driver.add_token(yytext); - return c11::Parser::make_AMPERSTAND(yytext, loc); + return CParser::make_AMPERSTAND(yytext, loc); } "!" { driver.add_token(yytext); - return c11::Parser::make_NEGATION(yytext, loc); + return CParser::make_NEGATION(yytext, loc); } "~" { driver.add_token(yytext); - return c11::Parser::make_NEGATION(yytext, loc); + return CParser::make_NEGATION(yytext, loc); } "-" { driver.add_token(yytext); - return c11::Parser::make_MINUS(yytext, loc); + return CParser::make_MINUS(yytext, loc); } "+" { driver.add_token(yytext); - return c11::Parser::make_ADD(yytext, loc); + return CParser::make_ADD(yytext, loc); } "*" { driver.add_token(yytext); - return c11::Parser::make_MULTIPLY(yytext, loc); + return CParser::make_MULTIPLY(yytext, loc); } "/" { driver.add_token(yytext); - return c11::Parser::make_DIVIDE(yytext, loc); + return CParser::make_DIVIDE(yytext, loc); } "%" { driver.add_token(yytext); - return c11::Parser::make_PERCENT(yytext, loc); + return CParser::make_PERCENT(yytext, loc); } "<" { driver.add_token(yytext); - return c11::Parser::make_LT(yytext, loc); + return CParser::make_LT(yytext, loc); } ">" { driver.add_token(yytext); - return c11::Parser::make_GT(yytext, loc); + return CParser::make_GT(yytext, loc); } "^" { driver.add_token(yytext); - return c11::Parser::make_CARET(yytext, loc); + return CParser::make_CARET(yytext, loc); } "|" { driver.add_token(yytext); - return c11::Parser::make_OR(yytext, loc); + return CParser::make_OR(yytext, loc); } "?" { driver.add_token(yytext); - return c11::Parser::make_QUESTION(yytext, loc); + return CParser::make_QUESTION(yytext, loc); } {WS}+ { @@ -688,19 +688,19 @@ WS [ \t\v\n\f] %% -int C11FlexLexer::yylex() { +int CFlexLexer::yylex() { throw std::runtime_error("next_token() should be used instead of yylex()"); } -c11::Parser::symbol_type c11::Lexer::check_type() { +nmodl::parser::CParser::symbol_type nmodl::parser::CLexer::check_type() { if (driver.is_typedef(yytext)) { - return c11::Parser::make_TYPEDEF_NAME(yytext, loc); + return CParser::make_TYPEDEF_NAME(yytext, loc); } if (driver.is_enum_constant(yytext)) { - return c11::Parser::make_ENUMERATION_CONSTANT(yytext, loc); + return CParser::make_ENUMERATION_CONSTANT(yytext, loc); } - return c11::Parser::make_IDENTIFIER(yytext, loc); + return CParser::make_IDENTIFIER(yytext, loc); } diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 74dd60ab3a..5b916bf366 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -12,7 +12,7 @@ /** Flex expects the declaration of yylex to be defined in the macro YY_DECL * and C++ parser class expects it to be declared. */ #ifndef YY_DECL -#define YY_DECL c11::Parser::symbol_type c11::Lexer::next_token() +#define YY_DECL nmodl::parser::CParser::symbol_type nmodl::parser::CLexer::next_token() #endif /** For creating multiple (different) lexer classes, we can use '-P' flag @@ -20,11 +20,12 @@ * ‘xxFlexLexer’. And then include <FlexLexer.h> in other sources once per * lexer class, first renaming yyFlexLexer as shown below. */ #ifndef __FLEX_LEXER_H -#define yyFlexLexer C11FlexLexer +#define yyFlexLexer CFlexLexer #include "FlexLexer.h" #endif -namespace c11 { +namespace nmodl { +namespace parser { /** * \class Lexer @@ -36,35 +37,36 @@ namespace c11 { * because the yylex() defined in C11FlexLexer has no parameters. Also, note * that implementation of the member functions are in c11.ll file due to use * of macros. */ -class Lexer: public C11FlexLexer { +class CLexer: public CFlexLexer { public: /** Reference to driver object which contains this lexer instance. This is * used for error reporting and checking macro definitions. */ - Driver& driver; + CDriver& driver; /// For tracking location of the tokens location loc; /** The streams in and out default to cin and cout, but that assignment * is only made when initializing in yylex(). */ - explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) - : C11FlexLexer(in, out) + explicit CLexer(CDriver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + : CFlexLexer(in, out) , driver(drv) {} - ~Lexer() override = default; + ~CLexer() override = default; ; /** Main lexing function generated by flex according to the macro declaration * YY_DECL above. The generated bison parser then calls this virtual function * to fetch new tokens. Note that yylex() has different declaration and hence * it can't be used for new lexer. */ - virtual Parser::symbol_type next_token(); + virtual CParser::symbol_type next_token(); /** Return type of the word : could be typedef, identifier or enum constant */ - Parser::symbol_type check_type(); + CParser::symbol_type check_type(); /// consume comment std::string input_comment(); }; -} // namespace c11 +} // namespace parser +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index a46d87fb1b..e48b0fdb93 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -14,7 +14,7 @@ #include "lexer/diffeq_lexer.hpp" #include "parser/diffeq_driver.hpp" - #define yyterminate() return Parser::make_END(loc); + #define yyterminate() return DiffeqParser::make_END(loc); #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } @@ -68,19 +68,19 @@ E [Ee][-+]?{D}+ %% -"+" { return Parser::make_ADD(yytext, loc); } +"+" { return DiffeqParser::make_ADD(yytext, loc); } -"-" { return Parser::make_SUB(yytext, loc); } +"-" { return DiffeqParser::make_SUB(yytext, loc); } -"*" { return Parser::make_MUL(yytext, loc); } +"*" { return DiffeqParser::make_MUL(yytext, loc); } -"/" { return Parser::make_DIV(yytext, loc); } +"/" { return DiffeqParser::make_DIV(yytext, loc); } -"(" { return Parser::make_OPEN_PARENTHESIS(yytext, loc); } +"(" { return DiffeqParser::make_OPEN_PARENTHESIS(yytext, loc); } -")" { return Parser::make_CLOSE_PARENTHESIS(yytext, loc); } +")" { return DiffeqParser::make_CLOSE_PARENTHESIS(yytext, loc); } -"," { return Parser::make_COMMA(yytext, loc); } +"," { return DiffeqParser::make_COMMA(yytext, loc); } :.* { /* ignore inline comments */ } @@ -90,9 +90,9 @@ E [Ee][-+]?{D}+ {D}+"."{D}*({E})? | {D}*"."{D}+({E})? | {D}+{E} | -[a-zA-Z][a-zA-Z0-9_]* { return Parser::make_ATOM(yytext, loc); } +[a-zA-Z][a-zA-Z0-9_]* { return DiffeqParser::make_ATOM(yytext, loc); } -"\n" { return Parser::make_NEWLINE(yytext, loc); } +"\n" { return DiffeqParser::make_NEWLINE(yytext, loc); } %% diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp index c32022c8b0..d6232b544a 100644 --- a/src/nmodl/lexer/diffeq_lexer.hpp +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -12,7 +12,7 @@ /** Flex expects the declaration of yylex to be defined in the macro YY_DECL * and C++ parser class expects it to be declared. */ #ifndef YY_DECL -#define YY_DECL diffeq::Parser::symbol_type diffeq::Lexer::next_token() +#define YY_DECL nmodl::parser::DiffeqParser::symbol_type nmodl::parser::DiffeqLexer::next_token() #endif /** For creating multiple (different) lexer classes, we can use '-P' flag @@ -24,7 +24,8 @@ #include "FlexLexer.h" #endif -namespace diffeq { +namespace nmodl { +namespace parser { /** * \class Lexer @@ -34,24 +35,25 @@ namespace diffeq { * At the moment we are using basic functionality but it could be easily * extended for further development. */ -class Lexer: public DiffEqFlexLexer { +class DiffeqLexer: public DiffEqFlexLexer { public: /// for tracking location of the tokens location loc; /** The streams in and out default to cin and cout, but that assignment * is only made when initializing in yylex(). */ - Lexer(std::istream* in = nullptr, std::ostream* out = nullptr) + DiffeqLexer(std::istream* in = nullptr, std::ostream* out = nullptr) : DiffEqFlexLexer(in, out) {} - ~Lexer() override = default; + ~DiffeqLexer() override = default; ; /** Main lexing function generated by flex according to the macro declaration * YY_DECL above. The generated bison parser then calls this virtual function * to fetch new tokens. Note that yylex() has different declaration and hence * it can't be used for new lexer. */ - virtual Parser::symbol_type next_token(); + virtual DiffeqParser::symbol_type next_token(); }; -} // namespace diffeq +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 1945700aa9..17450661e8 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -17,6 +17,7 @@ * usage of scanner and driver class. */ + int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("C Lexer: Standalone lexer program for C"); @@ -42,17 +43,15 @@ int main(int argc, const char* argv[]) { std::cout << "\n C Lexer : Processing file : " << filename << std::endl; std::istream& in(file); - c11::Driver driver; - c11::Lexer scanner(driver, &in); + nmodl::parser::CDriver driver; + nmodl::parser::CLexer scanner(driver, &in); - using Token = c11::Parser::token; - using TokenType = c11::Parser::token_type; - using SymbolType = c11::Parser::symbol_type; + using Token = nmodl::parser::CParser::token; /// parse C file untile EOF, print each token while (true) { - SymbolType sym = scanner.next_token(); - TokenType token = sym.token(); + auto sym = scanner.next_token(); + auto token = sym.token(); /// end of file if (token == Token::END) { diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 62c9e854fc..f431a3a150 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -20,6 +20,11 @@ * location. */ + +using SymbolType = nmodl::parser::NmodlParser::symbol_type; +using Token = nmodl::parser::NmodlParser::token; +using TokenType = nmodl::parser::NmodlParser::token_type; + int main(int argc, const char* argv[]) { try { TCLAP::CmdLine cmd("NMODL Lexer: Standalone lexer program for NMODL"); @@ -41,14 +46,11 @@ int main(int argc, const char* argv[]) { std::istream& in(file); /// lexer instace use driver object for error reporting - nmodl::Driver driver; + nmodl::parser::NmodlDriver driver; /// lexer instace with stream to read-in tokens - nmodl::Lexer scanner(driver, &in); + nmodl::parser::NmodlLexer scanner(driver, &in); - using Token = nmodl::Parser::token; - using TokenType = nmodl::Parser::token_type; - using SymbolType = nmodl::Parser::symbol_type; /// parse nmodl file untile EOF, print each token while (true) { @@ -62,7 +64,7 @@ int main(int argc, const char* argv[]) { /** Lexer returns different ast types base on token type. We * retrieve token object from each instance and print it. - * Note that value is of ast type i.e. ast::Name* etc. */ + * Note that value is of ast type i.e. nmodl::ast::Name* etc. */ switch (token) { /// token with name ast class case Token::NAME: @@ -71,7 +73,7 @@ int main(int argc, const char* argv[]) { case Token::VALENCE: case Token::DEL: case Token::DEL2: { - auto value = sym.value.as<ast::Name*>(); + auto value = sym.value.as<nmodl::ast::Name*>(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -79,7 +81,7 @@ int main(int argc, const char* argv[]) { /// token with prime ast class case Token::PRIME: { - auto value = sym.value.as<ast::PrimeName*>(); + auto value = sym.value.as<nmodl::ast::PrimeName*>(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -87,7 +89,7 @@ int main(int argc, const char* argv[]) { /// token with integer ast class case Token::INTEGER: { - auto value = sym.value.as<ast::Integer*>(); + auto value = sym.value.as<nmodl::ast::Integer*>(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -95,7 +97,7 @@ int main(int argc, const char* argv[]) { /// token with double/float ast class case Token::REAL: { - auto value = sym.value.as<ast::Double*>(); + auto value = sym.value.as<nmodl::ast::Double*>(); std::cout << *(value->get_token()) << std::endl; delete value; break; @@ -103,7 +105,7 @@ int main(int argc, const char* argv[]) { /// token with string ast class case Token::STRING: { - auto value = sym.value.as<ast::String*>(); + auto value = sym.value.as<nmodl::ast::String*>(); std::cout << *(value->get_token()) << std::endl; delete value; break; diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 107a89b606..56cd33ac52 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -33,6 +33,8 @@ */ class ModToken { + using LocationType = nmodl::parser::location; + private: /// name of the token std::string name; @@ -41,7 +43,7 @@ class ModToken { int token = -1; /// position of the token in mod file - nmodl::location pos; + LocationType pos; /// if token is externally defined symbol bool external = false; @@ -54,7 +56,7 @@ class ModToken { : pos(nullptr, 0) , external(ext) {} - ModToken(std::string str, int tok, nmodl::location& loc) + ModToken(std::string str, int tok, LocationType& loc) : name(str) , token(tok) , pos(loc) {} diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index f668d5b894..63533bbf13 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -26,7 +26,7 @@ /** By default yylex returns int, we use token_type. Unfortunately * yyterminate by default returns 0, which is not of token_type. */ - #define yyterminate() return nmodl::Parser::make_END(loc); + #define yyterminate() return NmodlParser::make_END(loc); /** Disables inclusion of unistd.h, which is not available under Visual * C++ on Win32. The C++ scanner uses STL streams instead. */ @@ -171,21 +171,21 @@ ELSE { /** Token for certain keywords need name_ptr value. */ auto type = token_type(yytext); ModToken tok(yytext, type, loc); - ast::Name value( new ast::String(yytext) ); + nmodl::ast::Name value( new nmodl::ast::String(yytext) ); value.set_token(tok); switch (static_cast<int>(type)) { /** Tokens requiring name_ptr as value */ case Token::METHOD: - return nmodl::Parser::make_METHOD(value, loc); + return NmodlParser::make_METHOD(value, loc); case Token::SUFFIX: - return nmodl::Parser::make_SUFFIX(value, loc); + return NmodlParser::make_SUFFIX(value, loc); case Token::VALENCE: - return nmodl::Parser::make_VALENCE(value, loc); + return NmodlParser::make_VALENCE(value, loc); case Token::DEL: - return nmodl::Parser::make_DEL(value, loc); + return NmodlParser::make_DEL(value, loc); case Token::DEL2: - return nmodl::Parser::make_DEL2(value, loc); + return NmodlParser::make_DEL2(value, loc); /** We have to store context for the reaction type */ case Token::NONLINEAR: @@ -393,7 +393,7 @@ ELSE { \?.* { /** Todo : add grammar support for inline vs single-line comments auto str = std::string(yytext); - return nmodl::Parser::make_LINE_COMMENT(str, loc); + return NmodlParser::make_LINE_COMMENT(str, loc); */ } @@ -426,19 +426,19 @@ ELSE { auto str = "VERBATIM" + std::string(yytext); BEGIN(INITIAL); reset_end_position(); - return nmodl::Parser::make_VERBATIM(str, loc); + return NmodlParser::make_VERBATIM(str, loc); } <COPY_MODE>"ENDCOMMENT" { auto str = "COMMENT" + std::string(yytext); BEGIN(INITIAL); reset_end_position(); - return nmodl::Parser::make_BLOCK_COMMENT(str, loc); + return NmodlParser::make_BLOCK_COMMENT(str, loc); } <COPY_MODE><<EOF>> { std::cout << "\n ERROR: Unexpected end of file in COPY_MODE! \n"; - return nmodl::Parser::make_END(loc); + return NmodlParser::make_END(loc); } <COPY_MODE>. { @@ -452,7 +452,7 @@ ELSE { std::string str(yytext); stringutils::trim_newline(str); BEGIN(INITIAL); - return nmodl::Parser::make_LINE_PART(str, loc); + return NmodlParser::make_LINE_PART(str, loc); } <LINE_MODE>. { @@ -464,7 +464,7 @@ ELSE { /** If macro name doesn't start with character or value * is not an integer then it's invalid macro definition */ - return nmodl::Parser::make_INVALID_TOKEN(loc); + return NmodlParser::make_INVALID_TOKEN(loc); } \"[^\"\n]*$ { @@ -486,13 +486,13 @@ int NmodlFlexLexer::yylex() { } /** yy_flex_debug is member of parent scanner class */ -void nmodl::Lexer::set_debug(bool b) { +void nmodl::parser::NmodlLexer::set_debug(bool b) { yy_flex_debug = b; } /** Scan unit which is a string between opening and closing parenthesis. * We store this in lexer itself and then consumed later from the parser. */ -void nmodl::Lexer::scan_unit() { +void nmodl::parser::NmodlLexer::scan_unit() { /** We are interested in unit after first parenthesis. * So to get correct position update the location. */ loc.step(); @@ -517,12 +517,12 @@ void nmodl::Lexer::scan_unit() { loc.columns(str.size()); ModToken tok(str, Token::UNITS, loc); - last_unit = new ast::String(str); + last_unit = new nmodl::ast::String(str); last_unit->set_token(tok); } /** return last scanned unit, it shouln't be null pointer */ -ast::String* nmodl::Lexer::get_unit() { +nmodl::ast::String* nmodl::parser::NmodlLexer::get_unit() { if (last_unit == nullptr) { throw std::runtime_error("Trying to get unscanned empty unit"); } diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 99b8b57949..173c9e7108 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -10,10 +10,11 @@ #include "ast/ast.hpp" #include "parser/nmodl/nmodl_parser.hpp" + /** Flex expects the declaration of yylex to be defined in the macro YY_DECL * and C++ parser class expects it to be declared. */ #ifndef YY_DECL -#define YY_DECL nmodl::Parser::symbol_type nmodl::Lexer::next_token() +#define YY_DECL nmodl::parser::NmodlParser::symbol_type nmodl::parser::NmodlLexer::next_token() #endif /** For creating multiple (different) lexer classes, we can use '-P' flag @@ -26,9 +27,10 @@ #endif namespace nmodl { +namespace parser { /** - * \class Lexer + * \class NmodlLexer * \brief Represent Lexer/Scanner class for NMODL language parsing * * Lexer defined to add some extra function to the scanner class from flex. @@ -37,11 +39,11 @@ namespace nmodl { * because the yylex() defined in NmodlFlexLexer has no parameters. Also, note * that implementation of the member functions are in nmodl.l file due to use * of macros. */ -class Lexer: public NmodlFlexLexer { +class NmodlLexer: public NmodlFlexLexer { public: /** Reference to driver object which contains this lexer instance. This is * used for error reporting and checking macro definitions. */ - Driver& driver; + NmodlDriver& driver; /// For tracking location of the tokens location loc; @@ -56,18 +58,18 @@ class Lexer: public NmodlFlexLexer { /** The streams in and out default to cin and cout, but that assignment * is only made when initializing in yylex(). */ - explicit Lexer(Driver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + explicit NmodlLexer(NmodlDriver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) : NmodlFlexLexer(in, out) , driver(drv) {} - ~Lexer() override = default; + ~NmodlLexer() override = default; ; /** Main lexing function generated by flex according to the macro declaration * YY_DECL above. The generated bison parser then calls this virtual function * to fetch new tokens. Note that yylex() has different declaration and hence * it can't be used for new lexer. */ - virtual Parser::symbol_type next_token(); + virtual NmodlParser::symbol_type next_token(); /// Enable debug output (via yyout) if compiled into the scanner. void set_debug(bool b); @@ -90,4 +92,5 @@ class Lexer: public NmodlFlexLexer { } }; +} // namespace parser } // namespace nmodl diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index a99e607281..d8fcc0fef8 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -15,6 +15,9 @@ #include "utils/string_utils.hpp" namespace nmodl { + +using Parser = parser::NmodlParser; + /// create symbol for double/real ast class SymbolType double_symbol(double value, PositionType& pos) { ModToken token(std::to_string(value), Token::REAL, pos); diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index cf5f6757c3..74944bbcef 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -21,10 +21,11 @@ */ namespace nmodl { -using PositionType = nmodl::location; -using SymbolType = nmodl::Parser::symbol_type; -using Token = nmodl::Parser::token; -using TokenType = nmodl::Parser::token_type; + +using PositionType = parser::location; +using SymbolType = parser::NmodlParser::symbol_type; +using Token = parser::NmodlParser::token; +using TokenType = parser::NmodlParser::token_type; SymbolType double_symbol(double value, PositionType& pos); SymbolType integer_symbol(int value, PositionType& pos, const char* text = nullptr); diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index f4cdc5a2a8..86f73b6e27 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -14,8 +14,10 @@ #include "parser/nmodl/nmodl_parser.hpp" namespace nmodl { -using Token = nmodl::Parser::token; -using TokenType = nmodl::Parser::token_type; + +using Token = parser::NmodlParser::token; +using TokenType = parser::NmodlParser::token_type; +using Parser = parser::NmodlParser; namespace internal { // clang-format off diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 991f42e143..55efc3b010 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -11,9 +11,11 @@ #include <string> namespace nmodl { + bool is_keyword(const std::string& name); bool is_method(const std::string& name); -nmodl::Parser::token_type token_type(const std::string& name); +nmodl::parser::NmodlParser::token_type token_type(const std::string& name); std::vector<std::string> get_external_variables(); std::vector<std::string> get_external_functions(); + } // namespace nmodl diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 0318d3a883..10092e9d40 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -16,7 +16,7 @@ /* the scanner state will include a field called yyextra * that can be used for user-defined state */ - #define YY_EXTRA_TYPE VerbatimContext* + #define YY_EXTRA_TYPE nmodl::parser::VerbatimContext* /* we need to parse strings as well as files, we redefine YY_INPUT @@ -135,14 +135,14 @@ /* initialize nmodlext lexer context */ -void VerbatimContext::init_scanner() { +void nmodl::parser::VerbatimContext::init_scanner() { yylex_init(&scanner); yyset_extra(this, scanner); } /* delete nmodlext lexer context */ -void VerbatimContext::destroy_scanner() { +void nmodl::parser::VerbatimContext::destroy_scanner() { yylex_destroy(scanner); } diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp index 4d5fa42314..2fac546620 100644 --- a/src/nmodl/nmodl/arg_handler.cpp +++ b/src/nmodl/nmodl/arg_handler.cpp @@ -11,6 +11,8 @@ #include "version/version.h" +namespace nmodl { + ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { // version string auto version = nmodl::version().to_string(); @@ -204,3 +206,5 @@ ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; } } + +} // namespace nmodl diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp index 8a22edf7ab..70a9574934 100644 --- a/src/nmodl/nmodl/arg_handler.hpp +++ b/src/nmodl/nmodl/arg_handler.hpp @@ -10,6 +10,9 @@ #include <string> #include <vector> + +namespace nmodl { + /** * \class ArgumentHandler * \brief Parser comamnd line arguments @@ -95,3 +98,5 @@ struct ArgumentHandler { return accel_backend == "CUDA"; } }; + +} // namespace nmodl diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 9518db216a..c7a6bb870e 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -32,6 +32,12 @@ #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +using namespace nmodl; +using namespace codegen; + +using nmodl::codegen::LayoutType; + + void ast_to_nmodl(ast::Program* ast, const std::string& filename) { NmodlPrintVisitor v(filename); v.visit_program(ast); @@ -68,7 +74,7 @@ int main(int argc, const char* argv[]) { std::string mod_file = remove_extension(base_name(nmodl_file)); /// driver object creates lexer and parser, just call parser method - nmodl::Driver driver; + nmodl::parser::NmodlDriver driver; driver.parse_file(nmodl_file); /// shared_ptr to ast constructed from parsing nmodl file @@ -195,7 +201,8 @@ int main(int argc, const char* argv[]) { CodegenCVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); visitor.visit_program(ast.get()); } else if (arg.host_omp_backend()) { - CodegenOmpVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); + nmodl::codegen::CodegenOmpVisitor visitor(mod_file, arg.output_dir, layout, + arg.dtype); visitor.visit_program(ast.get()); } else if (arg.host_acc_backend()) { CodegenAccVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index e493523a7b..6520f86402 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -12,6 +12,8 @@ %code requires { #include <string> + #include <sstream> + #include "parser/c11_driver.hpp" } @@ -28,10 +30,10 @@ %define parse.trace /** add extra arguments to yyparse() and yylex() methods */ -%parse-param {class Lexer& scanner} -%parse-param {class Driver& driver} -%lex-param {c11::Scanner &scanner} -%lex-param {c11::Driver &driver} +%parse-param {class CLexer& scanner} +%parse-param {class CDriver& driver} +%lex-param {nmodl::CScanner &scanner} +%lex-param {nmodl::CDriver &driver} /** use variant based implementation of semantic values */ %define api.value.type variant @@ -42,11 +44,11 @@ /** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ %define api.token.constructor -/** namespace to enclose parser */ -%name-prefix "c11" +/** specify the namespace for the parser class */ +%define api.namespace {nmodl::parser} /** set the parser's class identifier */ -%define parser_class_name {Parser} +%define parser_class_name {CParser} /** keep track of the current position within the input */ %locations @@ -164,9 +166,13 @@ #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" + using nmodl::parser::CParser; + using nmodl::parser::CLexer; + using nmodl::parser::CDriver; + /// yylex takes scanner as well as driver reference /// \todo: check if driver argument is required - static c11::Parser::symbol_type yylex(c11::Lexer &scanner, c11::Driver &/*driver*/) { + static CParser::symbol_type yylex(CLexer &scanner, CDriver &/*driver*/) { return scanner.next_token(); } %} @@ -686,7 +692,7 @@ declaration_list /** Bison expects error handler for parser */ -void c11::Parser::error(const location &loc , const std::string &message) { +void CParser::error(const location &loc , const std::string &message) { std::stringstream ss; ss << "C Parser Error : " << message << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index 44f3ea6673..654d560d7b 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -11,16 +11,17 @@ #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" -namespace c11 { +namespace nmodl { +namespace parser { -Driver::Driver(bool strace, bool ptrace) +CDriver::CDriver(bool strace, bool ptrace) : trace_scanner(strace) , trace_parser(ptrace) {} /// parse c file provided as istream -bool Driver::parse_stream(std::istream& in) { - Lexer scanner(*this, &in); - Parser parser(scanner, *this); +bool CDriver::parse_stream(std::istream& in) { + CLexer scanner(*this, &in); + CParser parser(scanner, *this); this->lexer = &scanner; this->parser = &parser; @@ -31,7 +32,7 @@ bool Driver::parse_stream(std::istream& in) { } //// parse c file -bool Driver::parse_file(const std::string& filename) { +bool CDriver::parse_file(const std::string& filename) { std::ifstream in(filename.c_str()); streamname = filename; @@ -42,37 +43,38 @@ bool Driver::parse_file(const std::string& filename) { } /// parser c provided as string (used for testing) -bool Driver::parse_string(const std::string& input) { +bool CDriver::parse_string(const std::string& input) { std::istringstream iss(input); return parse_stream(iss); } -void Driver::error(const std::string& m, const class location& l) { - std::cerr << l << " : " << m << std::endl; -} - -void Driver::error(const std::string& m) { +void CDriver::error(const std::string& m) { std::cerr << m << std::endl; } -void Driver::add_token(const std::string& text) { +void CDriver::add_token(const std::string& text) { tokens.push_back(text); // here we will query and look into symbol table or register callback } -void Driver::scan_string(std::string& text) { +void CDriver::error(const std::string& m, const location& l) { + std::cerr << l << " : " << m << std::endl; +} + +void CDriver::scan_string(std::string& text) { std::istringstream in(text); - Lexer scanner(*this, &in); - Parser parser(scanner, *this); + CLexer scanner(*this, &in); + CParser parser(scanner, *this); this->lexer = &scanner; this->parser = &parser; while (true) { auto sym = lexer->next_token(); auto token = sym.token(); - if (token == Parser::token::END) { + if (token == CParser::token::END) { break; } } } -} // namespace c11 +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 622e42d25d..ce16be7f2b 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -10,23 +10,25 @@ #include <algorithm> #include <map> #include <string> +#include <vector> -#include "ast/ast.hpp" -/** The c11 namespace encapsulates everything related to C (11) parsing */ -namespace c11 { -/** - * \class Driver - * \brief Class that binds all pieces together for parsing C verbatim blocks - */ +namespace nmodl { +namespace parser { /// flex generated scanner class (extends base lexer class of flex) -class Lexer; +class CLexer; /// parser class generated by bison -class Parser; +class CParser; + +class location; -class Driver { +/** + * \class Driver + * \brief Class that binds all pieces together for parsing C verbatim blocks + */ +class CDriver { private: /// all typedefs std::map<std::string, std::string> typedefs; @@ -44,10 +46,10 @@ class Driver { bool trace_parser = false; /// pointer to the lexer instance being used - Lexer* lexer = nullptr; + CLexer* lexer = nullptr; /// pointer to the parser instance being used - Parser* parser = nullptr; + CParser* parser = nullptr; /// print messages from lexer/parser bool verbose = false; @@ -56,10 +58,9 @@ class Driver { /// file or input stream name (used by scanner for position), see todo std::string streamname; - Driver() = default; - Driver(bool strace, bool ptrace); + CDriver() = default; + CDriver(bool strace, bool ptrace); - void error(const std::string& m, const class location& l); void error(const std::string& m); bool parse_stream(std::istream& in); @@ -68,6 +69,8 @@ class Driver { void scan_string(std::string& text); void add_token(const std::string&); + void error(const std::string& m, const location& l); + void set_verbose(bool b) { verbose = b; } @@ -97,4 +100,5 @@ class Driver { } }; -} // namespace c11 +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index a91feb7433..4a42799d2f 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -19,6 +19,7 @@ { #include <string> #include "parser/diffeq_context.hpp" + #include "parser/diffeq_driver.hpp" #include "parser/diffeq_helper.hpp" } @@ -38,9 +39,9 @@ %locations /** add extra arguments to yyparse() and yylexe() methods */ -%parse-param {class Lexer& scanner} +%parse-param {class DiffeqLexer& scanner} %parse-param {diffeq::DiffEqContext& context} -%lex-param { diffeq::Scanner &scanner } +%lex-param {diffeq::DiffEqScanner &scanner } /** use variant based implementation of semantic values */ %define api.value.type variant @@ -51,11 +52,11 @@ /** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ %define api.token.constructor -/** namespace to enclose parser */ -%name-prefix "diffeq" +/** specify the namespace for the parser class */ +%define api.namespace {nmodl::parser} /** set the parser's class identifier */ -%define parser_class_name {Parser} +%define parser_class_name {DiffeqParser} /** keep track of the current position within the input */ %locations @@ -74,7 +75,7 @@ %type <std::string> top -%type <Term> expression e arglist arg +%type <diffeq::Term> expression e arglist arg /* operator associativity */ @@ -87,7 +88,14 @@ #include <sstream> #include "lexer/diffeq_lexer.hpp" - static diffeq::Parser::symbol_type yylex(diffeq::Lexer &scanner) { + using nmodl::parser::DiffeqParser; + using nmodl::parser::DiffeqLexer; + using nmodl::parser::DiffeqDriver; + using nmodl::parser::diffeq::eval_derivative; + using nmodl::parser::diffeq::MathOp; + using nmodl::parser::diffeq::Term; + + static DiffeqParser::symbol_type yylex(DiffeqLexer &scanner) { return scanner.next_token(); } %} @@ -102,7 +110,7 @@ * * \todo We pass the differential equation context to the parser to keep track of current * solution and and also some global states like equation/derivative valid or not. We could - * add extra properties in Term class and avoid passing context to parser class. Need to + * add extra properties in diffeq::Term class and avoid passing context to parser class. Need to * check the implementation of other solvers in neuron before making changes. */ @@ -220,7 +228,7 @@ arg : e { %% -void diffeq::Parser::error(const location &loc , const std::string &message) { +void DiffeqParser::error(const location &loc , const std::string &message) { std::stringstream ss; ss << "DiffEq Parser Error : " << message << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index ce55e4c1d8..aa0029f7eb 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -10,8 +10,10 @@ #include "lexer/diffeq_lexer.hpp" #include "utils/string_utils.hpp" -using namespace diffeq; +namespace nmodl { +namespace parser { +namespace diffeq { Term::Term(const std::string& expr, const std::string& state) : expr(expr) @@ -70,13 +72,13 @@ std::string DiffEqContext::get_expr_for_nonlinear() { /// build lexer instance std::istringstream in(rhs); - Lexer scanner(&in); + DiffeqLexer scanner(&in); /// scan entire expression while (true) { auto sym = scanner.next_token(); auto token = sym.token(); - if (token == Parser::token::END) { + if (token == DiffeqParser::token::END) { break; } /// extract value of the token and check if it is a token @@ -168,3 +170,7 @@ std::string DiffEqContext::get_solution(bool& cnexp_possible) { } return solution; } + +} // namespace diffeq +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 0122bf8bb3..bb97147c79 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -9,6 +9,8 @@ #include <string> +namespace nmodl { +namespace parser { namespace diffeq { /** @@ -129,3 +131,5 @@ class DiffEqContext { }; } // namespace diffeq +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 1e31135c06..d55b14c1a6 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -12,12 +12,13 @@ #include "parser/diffeq_driver.hpp" #include "utils/string_utils.hpp" -namespace diffeq { +namespace nmodl { +namespace parser { -void Driver::parse_equation(const std::string& equation, - std::string& state, - std::string& rhs, - int& order) { +void DiffeqDriver::parse_equation(const std::string& equation, + std::string& state, + std::string& rhs, + int& order) { auto parts = stringutils::split_string(equation, '='); state = stringutils::trim(parts[0]); rhs = stringutils::trim(parts[1]); @@ -32,7 +33,7 @@ void Driver::parse_equation(const std::string& equation, } } -std::string Driver::solve(const std::string& equation, std::string method, bool debug) { +std::string DiffeqDriver::solve(const std::string& equation, std::string method, bool debug) { std::string state, rhs; int order = 0; bool cnexp_possible; @@ -40,16 +41,16 @@ std::string Driver::solve(const std::string& equation, std::string method, bool return solve_equation(state, order, rhs, method, cnexp_possible, debug); } -std::string Driver::solve_equation(std::string& state, - int order, - std::string& rhs, - std::string& method, - bool& cnexp_possible, - bool debug) { +std::string DiffeqDriver::solve_equation(std::string& state, + int order, + std::string& rhs, + std::string& method, + bool& cnexp_possible, + bool debug) { std::istringstream in(rhs); - DiffEqContext context(state, order, rhs, method); - Lexer scanner(&in); - Parser parser(scanner, context); + diffeq::DiffEqContext context(state, order, rhs, method); + DiffeqLexer scanner(&in); + DiffeqParser parser(scanner, context); parser.parse(); if (debug) { context.print(); @@ -58,7 +59,7 @@ std::string Driver::solve_equation(std::string& state, } /// \todo : instead of using neuron like api, we need to refactor -bool Driver::cnexp_possible(const std::string& equation, std::string& solution) { +bool DiffeqDriver::cnexp_possible(const std::string& equation, std::string& solution) { std::string state, rhs; int order = 0; bool cnexp_possible; @@ -68,4 +69,5 @@ bool Driver::cnexp_possible(const std::string& equation, std::string& solution) return cnexp_possible; } -} // namespace diffeq +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index 28558332b8..bb27bec205 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -12,9 +12,11 @@ #include "parser/diffeq_context.hpp" -namespace diffeq { +namespace nmodl { +namespace parser { + /** - * \class Driver + * \class NmodlDriver * \brief Class that binds all pieces together for parsing differential equations * * Driver class bind components required for lexing, parsing and ast @@ -22,12 +24,12 @@ namespace diffeq { */ /// flex generated scanner class (extends base lexer class of flex) -class Lexer; +class DiffeqlLexer; /// parser class generated by bison -class Parser; +class DiffeqParser; -class Driver { +class DiffeqDriver { private: std::string solve_equation(std::string& state, int order, @@ -43,7 +45,7 @@ class Driver { int& order); public: - Driver() = default; + DiffeqDriver() = default; /// solve equation using provided method std::string solve(const std::string& equation, std::string method, bool debug = false); @@ -52,4 +54,5 @@ class Driver { bool cnexp_possible(const std::string& equation, std::string& solution); }; -} // namespace diffeq +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index a759a9804f..8c6744689e 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -21,6 +21,8 @@ * altogether. */ +namespace nmodl { +namespace parser { namespace diffeq { /// operators beign supported as part of binary expressions @@ -171,3 +173,5 @@ inline Term eval_derivative<MathOp::div>(Term& first, } } // namespace diffeq +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index 64c1832658..fb3b72d2e6 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -42,7 +42,7 @@ int main(int argc, const char* argv[]) { std::istream& in(file); /// driver object creates lexer and parser, just call parser method - c11::Driver driver; + nmodl::parser::CDriver driver; driver.set_verbose(true); driver.parse_stream(in); diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index fbf12b331b..1205bef734 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -40,7 +40,7 @@ int main(int argc, const char* argv[]) { std::istream& in(file); /// driver object creates lexer and parser, just call parser method - nmodl::Driver driver; + nmodl::parser::NmodlDriver driver; driver.set_verbose(true); driver.parse_stream(in); diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index c5942e6af4..1d0f7e4f15 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -29,12 +29,6 @@ %code requires { #include "ast/ast.hpp" - - /** - * todo : not clear why %token requires below types and doesn't allow - * specifying direct ast node types. Declare these type until - * the issue is identified / fixed. - */ } /** use C++ parser interface of bison */ @@ -50,10 +44,10 @@ %define parse.trace /** add extra arguments to yyparse() and yylexe() methods */ -%parse-param {class Lexer& scanner} -%parse-param {class Driver& driver} -%lex-param { nmodl::Scanner &scanner } -%lex-param { nmodl::Driver &driver } +%parse-param {class NmodlLexer& scanner} +%parse-param {class NmodlDriver& driver} +%lex-param { nmodl::NmodlScanner &scanner } +%lex-param { nmodl::NmodlDriver &driver } /** use variant based implementation of semantic values */ %define api.value.type variant @@ -64,13 +58,11 @@ /** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ %define api.token.constructor -/** namespace to enclose parser */ -%name-prefix "nmodl" - -%define api.namespace {nmodl} +/** specify the namespace for the parser class */ +%define api.namespace {nmodl::parser} /** set the parser's class identifier */ -%define parser_class_name {Parser} +%define parser_class_name {NmodlParser} /** keep track of the current position within the input */ %locations @@ -375,9 +367,14 @@ #include "parser/nmodl_driver.hpp" #include "parser/verbatim_context.hpp" + using nmodl::parser::NmodlParser; + using nmodl::parser::NmodlLexer; + using nmodl::parser::NmodlDriver; + using nmodl::parser::VerbatimContext; + /// yylex takes scanner as well as driver reference /// \todo: check if driver argument is required - static nmodl::Parser::symbol_type yylex(nmodl::Lexer &scanner, nmodl::Driver &/*driver*/) { + static NmodlParser::symbol_type yylex(NmodlLexer &scanner, NmodlDriver &/*driver*/) { return scanner.next_token(); } @@ -2229,7 +2226,7 @@ std::string parse_with_verbatim_parser(std::string str) { * and report all errors. For now simply abort. */ -void nmodl::Parser::error(const location &loc , const std::string &message) { +void NmodlParser::error(const location &loc , const std::string &message) { std::stringstream ss; ss << "NMODL Parser Error : " << message << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index c91e588ce9..5dad57af03 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -12,14 +12,16 @@ #include "parser/nmodl_driver.hpp" namespace nmodl { -Driver::Driver(bool strace, bool ptrace) +namespace parser { + +NmodlDriver::NmodlDriver(bool strace, bool ptrace) : trace_scanner(strace) , trace_parser(ptrace) {} /// parse nmodl file provided as istream -bool Driver::parse_stream(std::istream& in) { - Lexer scanner(*this, &in); - Parser parser(scanner, *this); +bool NmodlDriver::parse_stream(std::istream& in) { + NmodlLexer scanner(*this, &in); + NmodlParser parser(scanner, *this); this->lexer = &scanner; this->parser = &parser; @@ -30,7 +32,7 @@ bool Driver::parse_stream(std::istream& in) { } //// parse nmodl file -bool Driver::parse_file(const std::string& filename) { +bool NmodlDriver::parse_file(const std::string& filename) { std::ifstream in(filename.c_str()); streamname = filename; @@ -41,35 +43,36 @@ bool Driver::parse_file(const std::string& filename) { } /// parser nmodl provided as string (used for testing) -bool Driver::parse_string(const std::string& input) { +bool NmodlDriver::parse_string(const std::string& input) { std::istringstream iss(input); return parse_stream(iss); } -void Driver::error(const std::string& m, const class location& l) { +void NmodlDriver::error(const std::string& m, const class location& l) { std::cerr << l << " : " << m << std::endl; } -void Driver::error(const std::string& m) { +void NmodlDriver::error(const std::string& m) { std::cerr << m << std::endl; } /// add macro definition and it's value (DEFINE keyword of nmodl) -void Driver::add_defined_var(const std::string& name, int value) { +void NmodlDriver::add_defined_var(const std::string& name, int value) { defined_var[name] = value; } /// check if particular text is defined as macro -bool Driver::is_defined_var(const std::string& name) { +bool NmodlDriver::is_defined_var(const std::string& name) { return !(defined_var.find(name) == defined_var.end()); } /// return variable's value defined as macro (always an integer) -int Driver::get_defined_var_value(const std::string& name) { +int NmodlDriver::get_defined_var_value(const std::string& name) { if (is_defined_var(name)) { return defined_var[name]; } throw std::runtime_error("Trying to get undefined macro / define :" + name); } +} // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index fbdea04642..cef1ea78ef 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -15,6 +15,8 @@ /** The nmodl namespace encapsulates everything related to nmodl parsing * which includes lexer, parser, driver, keywords, token mapping etc. */ namespace nmodl { +namespace parser { + /** * \class Driver * \brief Class that binds all pieces together for parsing nmodl file @@ -40,12 +42,12 @@ namespace nmodl { */ /// flex generated scanner class (extends base lexer class of flex) -class Lexer; +class NmodlLexer; /// parser class generated by bison -class Parser; +class NmodlParser; -class Driver { +class NmodlDriver { private: /// all macro defined in the mod file std::map<std::string, int> defined_var; @@ -57,10 +59,10 @@ class Driver { bool trace_parser = false; /// pointer to the lexer instance being used - Lexer* lexer = nullptr; + NmodlLexer* lexer = nullptr; /// pointer to the parser instance being used - Parser* parser = nullptr; + NmodlParser* parser = nullptr; /// print messages from lexer/parser bool verbose = false; @@ -72,8 +74,8 @@ class Driver { /// root of the ast std::shared_ptr<ast::Program> astRoot = nullptr; - Driver() = default; - Driver(bool strace, bool ptrace); + NmodlDriver() = default; + NmodlDriver(bool strace, bool ptrace); void error(const std::string& m, const class location& l); void error(const std::string& m); @@ -100,4 +102,5 @@ class Driver { } }; +} // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index 61fa61e63f..36634eb287 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -34,7 +34,7 @@ %defines /* yyparse() takes an extra argument context */ -%parse-param {VerbatimContext* context} +%parse-param {nmodl::parser::VerbatimContext* context} /* reentrant lexer needs an extra argument for yylex() */ %lex-param {void * scanner} @@ -61,6 +61,9 @@ %type <string_ptr> commentblock %{ + + using nmodl::parser::VerbatimContext; + /* a macro that extracts the scanner state from the parser state for yylex */ #define scanner context->scanner diff --git a/src/nmodl/parser/verbatim_context.hpp b/src/nmodl/parser/verbatim_context.hpp index 227bb72aae..e577cf2d5b 100644 --- a/src/nmodl/parser/verbatim_context.hpp +++ b/src/nmodl/parser/verbatim_context.hpp @@ -9,6 +9,10 @@ #include <iostream> + +namespace nmodl { +namespace parser { + class VerbatimContext { public: void* scanner = nullptr; @@ -28,9 +32,12 @@ class VerbatimContext { } protected: - /* defined in nmodlext.l */ void init_scanner(); void destroy_scanner(); }; -int Verbatim_parse(VerbatimContext*); +} // namespace parser +} // namespace nmodl + + +int Verbatim_parse(nmodl::parser::VerbatimContext*); \ No newline at end of file diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 6fcf7fbb29..fd7f47cbb5 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -8,6 +8,8 @@ #include "printer/code_printer.hpp" #include "utils/string_utils.hpp" +namespace nmodl { + CodePrinter::CodePrinter(const std::string& filename) { if (filename.empty()) { throw std::runtime_error("Empty filename for CodePrinter"); @@ -70,3 +72,5 @@ void CodePrinter::end_block(int num_newlines) { *result << "}"; add_newline(num_newlines); } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index c17066571c..bca2670a5f 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -12,6 +12,8 @@ #include <memory> #include <sstream> +namespace nmodl { + /** * \class CodePrinter * \brief Helper class for printing C/C++ code @@ -72,3 +74,5 @@ class CodePrinter { return NUM_SPACES * indent_level; } }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 830c5f0d3c..ab26cef021 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -7,6 +7,9 @@ #include "printer/json_printer.hpp" + +namespace nmodl { + /// Dump output to provided file JSONPrinter::JSONPrinter(const std::string& filename) { if (filename.empty()) { @@ -76,3 +79,5 @@ void JSONPrinter::flush() { block = nullptr; } } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 3c69d9e348..465ec989df 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -15,6 +15,8 @@ using json = nlohmann::json; +namespace nmodl { + /** * \class JSONPrinter * \brief Helper class for printing AST in JSON format @@ -87,3 +89,5 @@ class JSONPrinter { expand = flag; } }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index 5832bb4fa0..894ca9c306 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -8,6 +8,8 @@ #include "printer/nmodl_printer.hpp" #include "utils/string_utils.hpp" +namespace nmodl { + NMODLPrinter::NMODLPrinter(const std::string& filename) { if (filename.empty()) { throw std::runtime_error("Empty filename for NMODLPrinter"); @@ -47,3 +49,5 @@ void NMODLPrinter::pop_level() { add_indent(); *result << "}"; } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp index a72add00f3..a48ea3f7d3 100644 --- a/src/nmodl/printer/nmodl_printer.hpp +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -12,6 +12,8 @@ #include <memory> #include <sstream> +namespace nmodl { + /** * \class NMODLPrinter * \brief Helper class for printing AST back to NMDOL test @@ -55,3 +57,5 @@ class NMODLPrinter { /// and decreases indentation level void pop_level(); }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index da5cd2759a..3f9c4821a5 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -17,20 +17,18 @@ namespace py = pybind11; -class PyDriver: public nmodl::Driver { +class PyDriver: public nmodl::parser::NmodlDriver { public: - using nmodl::Driver::Driver; - bool parse_stream(py::object object) { py::object tiob = py::module::import("io").attr("TextIOBase"); if (py::isinstance(object, tiob)) { py::detail::pythonibuf<py::str> buf(object); std::istream istr(&buf); - return nmodl::Driver::parse_stream(istr); + return NmodlDriver::parse_stream(istr); } else { py::detail::pythonibuf<py::bytes> buf(object); std::istream istr(&buf); - return nmodl::Driver::parse_stream(istr); + return NmodlDriver::parse_stream(istr); } } }; @@ -41,8 +39,10 @@ void init_ast_module(py::module& m); void init_symtab_module(py::module& m); PYBIND11_MODULE(_nmodl, m_nmodl) { - py::class_<PyDriver> nmodl_driver(m_nmodl, "Driver"); - nmodl_driver.def(py::init<bool, bool>()); + py::class_<PyDriver> nmodl_driver(m_nmodl, "NmodlDriver"); + + // todo : what has changed ? fix this! + // nmodl_driver.def(py::init<bool, bool>()); nmodl_driver.def(py::init<>()) .def("parse_string", &PyDriver::parse_string) .def("parse_file", &PyDriver::parse_file) diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 41ed98b046..a1d5a6331f 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -5,15 +5,18 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "symtab/symbol.hpp" #include "fmt/format.h" -#include "symtab/symbol.hpp" -using namespace syminfo; using namespace fmt::literals; +namespace nmodl { namespace symtab { +using syminfo::NmodlType; +using syminfo::Status; + /** if symbol has only extern_token property : this is true * for symbols which are external variables from neuron * like v, t, dt etc. @@ -92,12 +95,13 @@ bool Symbol::is_variable() { std::string Symbol::to_string() { std::string s(name); if (properties != NmodlType::empty) { - s += " [Properties : {}]"_format(::to_string(properties)); + s += " [Properties : {}]"_format(syminfo::to_string(properties)); } if (status != Status::empty) { - s += " [Status : {}]"_format(::to_string(status)); + s += " [Status : {}]"_format(syminfo::to_string(status)); } return s; } } // namespace symtab +} // namespace nmodl diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 0aa9d2ca21..887310889b 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -13,6 +13,9 @@ #include "lexer/modtoken.hpp" #include "symtab/symbol_properties.hpp" + +namespace nmodl { + namespace ast { class AST; } @@ -259,3 +262,4 @@ class Symbol { }; } // namespace symtab +} // namespace nmodl diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 88a2ca4529..e93e8a5e8f 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -11,7 +11,10 @@ #include "symtab/symbol_properties.hpp" #include "utils/string_utils.hpp" -using namespace syminfo; + +namespace nmodl { +namespace symtab { +namespace syminfo { /// check if any property is set bool has_property(const NmodlType& obj, NmodlType property) { @@ -213,3 +216,7 @@ std::ostream& operator<<(std::ostream& os, const Status& obj) { os << to_string(obj); return os; } + +} // namespace syminfo +} // namespace symtab +} // namespace nmodl diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index c0bb7ab094..763cd4fad4 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -14,6 +14,8 @@ //@todo : error from pybind if std::underlying_typ is used using enum_type = long long; +namespace nmodl { +namespace symtab { namespace syminfo { /** kind of symbol */ @@ -212,8 +214,12 @@ enum class NmodlType : enum_type { discrete_block = 1L << 33 }; -} // namespace syminfo +/// check if any property is set +bool has_property(const NmodlType& obj, NmodlType property); + +/// check if any status is set +bool has_status(const Status& obj, Status state); template <typename T> inline T operator|(T lhs, T rhs) { @@ -250,4 +256,8 @@ std::string to_string(const T& obj) { // remove extra whitespace at the end stringutils::trim(text); return text; -} \ No newline at end of file +} + +} // namespace syminfo +} // namespace symtab +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 5d05eee744..d99ad8ef4c 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -12,11 +12,14 @@ #include "utils/logger.hpp" #include "utils/table_data.hpp" -using namespace ast; -using namespace syminfo; +namespace nmodl { namespace symtab { +using namespace ast; +using syminfo::NmodlType; +using syminfo::Status; + int SymbolTable::Table::counter = 0; /** @@ -471,8 +474,8 @@ void SymbolTable::Table::print(std::stringstream& stream, std::string title, int name += "[" + std::to_string(symbol->get_length()) + "]"; } auto position = symbol->get_token().position(); - auto properties = ::to_string(symbol->get_properties()); - auto status = ::to_string(symbol->get_status()); + auto properties = syminfo::to_string(symbol->get_properties()); + auto status = syminfo::to_string(symbol->get_status()); auto reads = std::to_string(symbol->get_read_count()); std::string value; auto sym_value = symbol->get_value(); @@ -522,3 +525,4 @@ void ModelSymbolTable::print(std::stringstream& ss) { } } // namespace symtab +} // namespace nmodl diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index f66bbece34..00c18c6eb4 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -13,8 +13,9 @@ #include "symtab/symbol.hpp" -namespace symtab { +namespace nmodl { +namespace symtab { /** * \class SymbolTable @@ -254,3 +255,4 @@ class ModelSymbolTable { }; } // namespace symtab +} // namespace nmodl diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index b985757164..6884baff0b 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -11,6 +11,9 @@ #include <string> #include <sys/stat.h> + +namespace nmodl { + bool is_dir_exist(const std::string& path) { struct stat info {}; if (stat(path.c_str(), &info) != 0) { @@ -50,3 +53,5 @@ bool make_path(const std::string& path) { throw std::runtime_error(msg); } } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 5a86526554..bb3486ceeb 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -7,6 +7,9 @@ #pragma once + +namespace nmodl { + /** Check if the iterator is pointing to last element in the container */ template <typename Iter, typename Cont> bool is_last(Iter iter, const Cont& cont) { @@ -28,3 +31,5 @@ T remove_extension(T const& filename) { /** Given directory path, create sub-directories */ bool make_path(const std::string& path); + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 95c148e39f..8404ee6edd 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -9,6 +9,9 @@ #include "utils/logger.hpp" + +namespace nmodl { + using logger_type = std::shared_ptr<spdlog::logger>; struct Logger { @@ -22,3 +25,5 @@ struct Logger { Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); logger_type logger = nmodl_logger.logger; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index bb7100e156..244d75da6e 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -12,5 +12,9 @@ #include "spdlog/sinks/stdout_color_sinks.h" // clang-format on +namespace nmodl { + using logger_type = std::shared_ptr<spdlog::logger>; extern logger_type logger; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index 31360ecdf5..e33d4a5677 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -12,6 +12,8 @@ #include "utils/perf_stat.hpp" #include "utils/table_data.hpp" +namespace nmodl { + PerfStat operator+(const PerfStat& first, const PerfStat& second) { PerfStat result; @@ -111,3 +113,5 @@ std::vector<std::string> PerfStat::values() { return row; } + +} // namespace nmodl diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index 6eded62dd0..cdf9dad077 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -9,6 +9,9 @@ #include <sstream> + +namespace nmodl { + /** * \class PerfStat * \brief Helper class to collect performance statistics @@ -90,3 +93,5 @@ class PerfStat { std::vector<std::string> keys(); std::vector<std::string> values(); }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 6490840e02..853894c76b 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -11,6 +11,9 @@ #include <sstream> #include <vector> + +namespace nmodl { + /** * \brief String manipulation functions * @@ -115,3 +118,5 @@ static inline std::string tolower(std::string text) { } } // namespace stringutils + +} // namespace nmodl diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 6239e51dca..815d169a92 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -11,6 +11,9 @@ #include "utils/string_utils.hpp" #include "utils/table_data.hpp" + +namespace nmodl { + /** * Print table data in below shown format: title as first row (centrally aligned), * second row is header for individual column (centrally aligned) and then all data @@ -25,8 +28,6 @@ * ---------------------------------------------------------------------------------------- */ -using namespace stringutils; - void TableData::print(std::stringstream& stream, int indent) { const int PADDING = 1; @@ -78,7 +79,7 @@ void TableData::print(std::stringstream& stream, int indent) { std::stringstream header; header << "| "; for (size_t i = 0; i < headers.size(); i++) { - auto text = align_text(headers[i], col_width[i], text_alignment::center); + auto text = stringutils::align_text(headers[i], col_width[i], text_alignment::center); header << text << " | "; } @@ -114,3 +115,5 @@ void TableData::print(int indent) { print(ss, indent); std::cout << ss.str(); } + +} // namespace nmodl diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index 1c48b639c6..9d3ccac087 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -12,6 +12,9 @@ #include "utils/string_utils.hpp" + +namespace nmodl { + /** * \class TableData * \brief Class to construct and pretty-print tabular data @@ -38,3 +41,5 @@ struct TableData { void print(int indent = 0); void print(std::stringstream& stream, int indent = 0); }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 5a9a299149..55cf25ffa3 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -12,7 +12,7 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.ipp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 6be74f8b4a..a5c62ad27d 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -14,22 +14,23 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/visitor_utils.hpp" -using namespace ast; -void CnexpSolveVisitor::visit_solve_block(SolveBlock* node) { +namespace nmodl { + +void CnexpSolveVisitor::visit_solve_block(ast::SolveBlock* node) { auto method = node->get_method(); if (method) { solve_method = method->get_value()->eval(); } } -void CnexpSolveVisitor::visit_diff_eq_expression(DiffEqExpression* node) { +void CnexpSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { differential_equation = true; node->visit_children(this); differential_equation = false; } -void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { +void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto& lhs = node->lhs; auto& rhs = node->rhs; auto& op = node->op; @@ -45,19 +46,20 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { return; } - auto name = std::dynamic_pointer_cast<VarName>(lhs)->get_name(); + auto name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); if (name->is_prime_name()) { auto equation = nmodl::to_nmodl(node); - diffeq::Driver diffeq_driver; + parser::DiffeqDriver diffeq_driver; if (solve_method == cnexp_method) { std::string solution; /// check if ode can be solved with cnexp method if (diffeq_driver.cnexp_possible(equation, solution)) { auto statement = create_statement(solution); - auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); - auto bin_expr = std::dynamic_pointer_cast<BinaryExpression>( + auto expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>( + statement); + auto bin_expr = std::dynamic_pointer_cast<ast::BinaryExpression>( expr_statement->get_expression()); lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); @@ -78,7 +80,9 @@ void CnexpSolveVisitor::visit_binary_expression(BinaryExpression* node) { } } -void CnexpSolveVisitor::visit_program(Program* node) { +void CnexpSolveVisitor::visit_program(ast::Program* node) { program_symtab = node->get_symbol_table(); node->visit_children(this); } + +} // namespace nmodl diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp index 5a689ddb74..42ade98e14 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -12,6 +12,8 @@ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" +namespace nmodl { + /** * \class CnexpSolveVisitor * \brief Visitor that solves and replaces ODEs using cnexp method @@ -50,3 +52,5 @@ class CnexpSolveVisitor: public AstVisitor { void visit_binary_expression(ast::BinaryExpression* node) override; void visit_program(ast::Program* node) override; }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 3d5db2d7fb..e3c0265e22 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -10,8 +10,9 @@ #include "visitors/defuse_analyze_visitor.hpp" -using namespace ast; -using namespace syminfo; +namespace nmodl { + +using symtab::syminfo::NmodlType; /// DUState to string conversion for pretty-printing std::string to_string(DUState state) { @@ -166,7 +167,7 @@ DUState DUChain::eval() { return result; } -void DefUseAnalyzeVisitor::visit_unsupported_node(Node* node) { +void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node* node) { unsupported_node = true; node->visit_children(this); unsupported_node = false; @@ -177,7 +178,7 @@ void DefUseAnalyzeVisitor::visit_unsupported_node(Node* node) { * there is no inlining happened. In this case we mark the call as * unsupported. */ -void DefUseAnalyzeVisitor::visit_function_call(FunctionCall* node) { +void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall* node) { std::string function_name = node->get_node_name(); auto symbol = global_symtab->lookup_in_scope(function_name); if (symbol == nullptr || symbol->is_external_symbol_only()) { @@ -187,7 +188,7 @@ void DefUseAnalyzeVisitor::visit_function_call(FunctionCall* node) { } } -void DefUseAnalyzeVisitor::visit_statement_block(StatementBlock* node) { +void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock* node) { auto symtab = node->get_symbol_table(); if (symtab != nullptr) { current_symtab = symtab; @@ -202,16 +203,16 @@ void DefUseAnalyzeVisitor::visit_statement_block(StatementBlock* node) { /** Nmodl grammar doesn't allow assignment operator on rhs (e.g. a = b + (b=c) * and hence not necessary to keep track of assignment operator using stack. */ -void DefUseAnalyzeVisitor::visit_binary_expression(BinaryExpression* node) { +void DefUseAnalyzeVisitor::visit_binary_expression(ast::BinaryExpression* node) { node->get_rhs()->visit_children(this); - if (node->get_op().get_value() == BOP_ASSIGN) { + if (node->get_op().get_value() == ast::BOP_ASSIGN) { visiting_lhs = true; } node->get_lhs()->visit_children(this); visiting_lhs = false; } -void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { +void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement* node) { /// store previous chain auto previous_chain = current_chain; @@ -249,7 +250,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(IfStatement* node) { * \todo: one simple way would be to look for p_name in the string * of verbatim block to find the variable usage. */ -void DefUseAnalyzeVisitor::visit_verbatim(Verbatim* node) { +void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim* node) { if (!ignore_verbatim) { current_chain->push_back(DUInstance(DUState::U)); } @@ -287,7 +288,7 @@ void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { } } -void DefUseAnalyzeVisitor::visit_with_new_chain(Node* node, DUState state) { +void DefUseAnalyzeVisitor::visit_with_new_chain(ast::Node* node, DUState state) { auto last_chain = current_chain; start_new_chain(state); node->visit_children(this); @@ -317,3 +318,5 @@ DUChain DefUseAnalyzeVisitor::analyze(ast::Node* node, const std::string& name) return usage; } + +} // namespace nmodl diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 6f29586b7d..7299f1365e 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -16,6 +16,8 @@ #include "visitors/ast_visitor.hpp" +namespace nmodl { + /// state in def-use chain enum class DUState { /// global variable is used @@ -255,3 +257,5 @@ class DefUseAnalyzeVisitor: public AstVisitor { /// compute def-use chain for a variable within the node DUChain analyze(ast::Node* node, const std::string& name); }; + +} // namespace nmodl diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index e8df83a028..37ffdcc04a 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -8,6 +8,8 @@ #include "visitors/inline_visitor.hpp" #include "parser/c11_driver.hpp" + +namespace nmodl { using namespace ast; bool InlineVisitor::can_inline_block(StatementBlock* block) { @@ -24,7 +26,7 @@ bool InlineVisitor::can_inline_block(StatementBlock* block) { if (statement->is_verbatim()) { auto node = static_cast<Verbatim*>(statement.get()); auto text = node->get_statement()->eval(); - c11::Driver driver; + parser::CDriver driver; driver.scan_string(text); if (driver.has_token("return")) { to_inline = false; @@ -307,3 +309,5 @@ void InlineVisitor::visit_program(Program* node) { } node->visit_children(this); } + +} // namespace nmodl diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 8cc220c519..661000bf98 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -17,6 +17,9 @@ #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" + +namespace nmodl { + /** * \class InlineVisitor * \brief Visitor to inline local procedure and function calls @@ -176,3 +179,5 @@ class InlineVisitor: public AstVisitor { virtual void visit_program(ast::Program* node) override; }; + +} // namespace nmodl diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index daf871e07a..35e0573045 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -9,11 +9,13 @@ #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" -using namespace ast; -using namespace symtab; + +namespace nmodl { + +using symtab::SymbolTable; /// rename name conflicting variables in the statement block and it's all children -void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { +void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { /// nothing to do if (node->get_statements().empty()) { return; @@ -67,3 +69,5 @@ void LocalVarRenameVisitor::visit_statement_block(StatementBlock* node) { } } } + +} // namespace nmodl diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index 10829b4256..7de23e6a05 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -14,6 +14,8 @@ #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" +namespace nmodl { + /** * \class LocalVarRenameVisitor * \brief Visitor to rename local variables conflicting with global scope @@ -60,3 +62,5 @@ class LocalVarRenameVisitor: public AstVisitor { LocalVarRenameVisitor() = default; virtual void visit_statement_block(ast::StatementBlock* node) override; }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 144bfa980f..19c08b83a1 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -10,9 +10,10 @@ #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/localize_visitor.hpp" -using namespace ast; -using namespace symtab; -using namespace syminfo; +namespace nmodl { + +using symtab::Symbol; +using symtab::syminfo::NmodlType; bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { auto type = node->get_node_type(); @@ -97,7 +98,7 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { return result; } -void LocalizeVisitor::visit_program(Program* node) { +void LocalizeVisitor::visit_program(ast::Program* node) { /// symtab visitor pass must be run before program_symtab = node->get_symbol_table(); if (program_symtab == nullptr) { @@ -125,9 +126,9 @@ void LocalizeVisitor::visit_program(Program* node) { if (it == block_usage.end()) { /// all blocks that are using variable should get local variable for (auto& block: block_usage[DUState::D]) { - auto block_ptr = dynamic_cast<Block*>(block.get()); + auto block_ptr = dynamic_cast<ast::Block*>(block.get()); auto statement_block = block_ptr->get_statement_block(); - LocalVar* variable; + ast::LocalVar* variable; auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { variable = add_local_variable(statement_block.get(), varname, @@ -149,3 +150,5 @@ void LocalizeVisitor::visit_program(Program* node) { } } } + +} // namespace nmodl diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 98ee42b09d..536c99021d 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -18,6 +18,9 @@ #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" + +namespace nmodl { + /** * \class LocalizeVisitor * \brief Visitor to transform global variable usage to local @@ -98,3 +101,5 @@ class LocalizeVisitor: public AstVisitor { virtual void visit_program(ast::Program* node) override; }; + +} // namespace nmodl diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 09ac3c224a..c0ce11565d 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -24,6 +24,7 @@ #include "tclap/CmdLine.h" +using namespace nmodl; using namespace symtab; /** @@ -55,7 +56,7 @@ int main(int argc, const char* argv[]) { std::string mod_filename = remove_extension(base_name(filename)); /// driver object creates lexer and parser, just call parser method - nmodl::Driver driver; + nmodl::parser::NmodlDriver driver; driver.parse_file(filename); /// shared_ptr to ast constructed from parsing nmodl file diff --git a/src/nmodl/visitors/nmodl_visitor_helper.hpp b/src/nmodl/visitors/nmodl_visitor_helper.ipp similarity index 98% rename from src/nmodl/visitors/nmodl_visitor_helper.hpp rename to src/nmodl/visitors/nmodl_visitor_helper.ipp index 4a6fff6715..4766d3ec0e 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.hpp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -10,6 +10,8 @@ #include "visitors/nmodl_visitor.hpp" +namespace nmodl { + /** Helper function to visit vector elements * * @tparam T @@ -60,3 +62,5 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, } } } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index b109f1f554..b1ca4b7704 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -9,74 +9,77 @@ #include "visitors/perf_visitor.hpp" -using namespace ast; -using namespace syminfo; -using namespace symtab; + +namespace nmodl { + +using symtab::Symbol; +using symtab::syminfo::NmodlType; +using symtab::syminfo::Status; PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) {} /// count math operations from all binary expressions -void PerfVisitor::visit_binary_expression(BinaryExpression* node) { +void PerfVisitor::visit_binary_expression(ast::BinaryExpression* node) { bool assign_op = false; if (start_measurement) { auto value = node->get_op().get_value(); switch (value) { - case BOP_ADDITION: + case ast::BOP_ADDITION: current_block_perf.n_add++; break; - case BOP_SUBTRACTION: + case ast::BOP_SUBTRACTION: current_block_perf.n_sub++; break; - case BOP_MULTIPLICATION: + case ast::BOP_MULTIPLICATION: current_block_perf.n_mul++; break; - case BOP_DIVISION: + case ast::BOP_DIVISION: current_block_perf.n_div++; break; - case BOP_POWER: + case ast::BOP_POWER: current_block_perf.n_pow++; break; - case BOP_AND: + case ast::BOP_AND: current_block_perf.n_and++; break; - case BOP_OR: + case ast::BOP_OR: current_block_perf.n_or++; break; - case BOP_GREATER: + case ast::BOP_GREATER: current_block_perf.n_gt++; break; - case BOP_GREATER_EQUAL: + case ast::BOP_GREATER_EQUAL: current_block_perf.n_ge++; break; - case BOP_LESS: + case ast::BOP_LESS: current_block_perf.n_lt++; break; - case BOP_LESS_EQUAL: + case ast::BOP_LESS_EQUAL: current_block_perf.n_le++; break; - case BOP_ASSIGN: + case ast::BOP_ASSIGN: current_block_perf.n_assign++; assign_op = true; break; - case BOP_NOT_EQUAL: + case ast::BOP_NOT_EQUAL: current_block_perf.n_ne++; break; - case BOP_EXACT_EQUAL: + case ast::BOP_EXACT_EQUAL: current_block_perf.n_ee++; break; @@ -115,7 +118,7 @@ void PerfVisitor::add_perf_to_printer(PerfStat& perf) { * all children visited, we get total performance by summing * perfstat of all children. */ -void PerfVisitor::measure_performance(AST* node) { +void PerfVisitor::measure_performance(ast::AST* node) { start_measurement = true; node->visit_children(this); @@ -158,7 +161,7 @@ void PerfVisitor::measure_performance(AST* node) { } /// count function calls and "most useful" or "commonly used" math functions -void PerfVisitor::visit_function_call(FunctionCall* node) { +void PerfVisitor::visit_function_call(ast::FunctionCall* node) { under_function_call = true; if (start_measurement) { @@ -185,25 +188,25 @@ void PerfVisitor::visit_function_call(FunctionCall* node) { } /// every variable used is of type name, update counters -void PerfVisitor::visit_name(Name* node) { +void PerfVisitor::visit_name(ast::Name* node) { update_memory_ops(node->get_node_name()); node->visit_children(this); } /// prime name derived from identifier and hence need to be handled here -void PerfVisitor::visit_prime_name(PrimeName* node) { +void PerfVisitor::visit_prime_name(ast::PrimeName* node) { update_memory_ops(node->get_node_name()); node->visit_children(this); } -void PerfVisitor::visit_if_statement(IfStatement* node) { +void PerfVisitor::visit_if_statement(ast::IfStatement* node) { if (start_measurement) { current_block_perf.n_if++; node->visit_children(this); } } -void PerfVisitor::visit_else_if_statement(ElseIfStatement* node) { +void PerfVisitor::visit_else_if_statement(ast::ElseIfStatement* node) { if (start_measurement) { current_block_perf.n_elif++; node->visit_children(this); @@ -225,7 +228,7 @@ void PerfVisitor::count_variables() { if (variable->has_properties(NmodlType::param_assign)) { num_constant_instance_variables++; } - if (variable->has_any_status(syminfo::Status::localized)) { + if (variable->has_any_status(Status::localized)) { num_localized_instance_variables++; } } @@ -257,7 +260,7 @@ void PerfVisitor::count_variables() { if (variable->has_properties(NmodlType::param_assign)) { num_constant_global_variables++; } - if (variable->has_any_status(syminfo::Status::localized)) { + if (variable->has_any_status(Status::localized)) { num_localized_global_variables++; } } @@ -306,7 +309,7 @@ void PerfVisitor::print_memory_usage() { } } -void PerfVisitor::visit_program(Program* node) { +void PerfVisitor::visit_program(ast::Program* node) { if (printer) { printer->push_block("BlockPerf"); } @@ -332,7 +335,7 @@ void PerfVisitor::visit_program(Program* node) { * blocks like net receive has nested initial blocks. Hence need * to maintain separate stack. */ -void PerfVisitor::visit_statement_block(StatementBlock* node) { +void PerfVisitor::visit_statement_block(ast::StatementBlock* node) { /// starting new block, store current state blocks_perf.push(current_block_perf); @@ -362,21 +365,21 @@ void PerfVisitor::visit_statement_block(StatementBlock* node) { /// and hence could/should not be skipped completely /// we can't ignore the block because it could have associated /// statement block (in theory) -void PerfVisitor::visit_solve_block(SolveBlock* node) { +void PerfVisitor::visit_solve_block(ast::SolveBlock* node) { under_solve_block = true; node->visit_children(this); under_solve_block = false; } -void PerfVisitor::visit_unary_expression(UnaryExpression* node) { +void PerfVisitor::visit_unary_expression(ast::UnaryExpression* node) { if (start_measurement) { auto value = node->get_op().get_value(); switch (value) { - case UOP_NEGATION: + case ast::UOP_NEGATION: current_block_perf.n_neg++; break; - case UOP_NOT: + case ast::UOP_NOT: current_block_perf.n_not++; break; @@ -409,7 +412,7 @@ bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { return skip; } -bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbol) { +bool PerfVisitor::is_local_variable(const std::shared_ptr<Symbol>& symbol) { bool is_local = false; /// in the function when we write to function variable then consider it as local variable auto properties = NmodlType::local_var | NmodlType::argument | NmodlType::function_block; @@ -419,7 +422,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr<symtab::Symbol>& symbo return is_local; } -bool PerfVisitor::is_constant_variable(const std::shared_ptr<symtab::Symbol>& symbol) { +bool PerfVisitor::is_constant_variable(const std::shared_ptr<Symbol>& symbol) { bool is_constant = false; auto properties = NmodlType::param_assign; if (symbol->has_properties(properties)) { @@ -488,3 +491,5 @@ void PerfVisitor::update_memory_ops(const std::string& name) { } } } + +} // namespace nmodl diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index dd74a3ce43..ee0717f157 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -15,6 +15,9 @@ #include "utils/perf_stat.hpp" #include "visitors/ast_visitor.hpp" + +namespace nmodl { + /** * \class PerfVisitor * \brief Visitor for measuring performance related information @@ -291,3 +294,5 @@ class PerfVisitor: public AstVisitor { ss << stream.str(); } }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 1a38f78583..7d4e2bac08 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -8,10 +8,11 @@ #include "visitors/rename_visitor.hpp" #include "parser/c11_driver.hpp" -using namespace ast; + +namespace nmodl { /// rename matching variable -void RenameVisitor::visit_name(Name* node) { +void RenameVisitor::visit_name(ast::Name* node) { std::string name = node->get_node_name(); if (name == var_name) { auto value = node->get_value(); @@ -31,14 +32,14 @@ void RenameVisitor::visit_prime_name(ast::PrimeName* node) { /** * Parse verbatim blocks and rename variable if it is used. */ -void RenameVisitor::visit_verbatim(Verbatim* node) { +void RenameVisitor::visit_verbatim(ast::Verbatim* node) { if (!rename_verbatim) { return; } auto statement = node->get_statement(); auto text = statement->eval(); - c11::Driver driver; + parser::CDriver driver; driver.scan_string(text); auto tokens = driver.all_tokens(); @@ -53,3 +54,5 @@ void RenameVisitor::visit_verbatim(Verbatim* node) { } statement->set(result); } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index 9453bd991f..c21bb5ba0f 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -13,6 +13,9 @@ #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" + +namespace nmodl { + /** * \class VarRenameVisitor * \brief "Blindly" rename given variable to new name @@ -58,3 +61,5 @@ class RenameVisitor: public AstVisitor { virtual void visit_prime_name(ast::PrimeName* node) override; virtual void visit_verbatim(ast::Verbatim* node) override; }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 8aff87cba9..34524d8097 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -13,14 +13,19 @@ #include "visitor_utils.hpp" #include "visitors/sympy_conductance_visitor.hpp" -using namespace ast; + namespace py = pybind11; using namespace py::literals; -using namespace syminfo; + +namespace nmodl { + +using ast::AstNodeType; +using ast::BinaryOp; +using symtab::syminfo::NmodlType; // Generate statement strings to be added to BREAKPOINT section std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( - BreakpointBlock* node) { + ast::BreakpointBlock* node) { std::vector<std::string> statements; // iterate over binary expression lhs's from breakpoint for (const auto& lhs_str: ordered_binary_exprs_lhs) { @@ -92,14 +97,15 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( return statements; } -void SympyConductanceVisitor::visit_binary_expression(BinaryExpression* node) { +void SympyConductanceVisitor::visit_binary_expression(ast::BinaryExpression* node) { // only want binary expressions from breakpoint block if (!breakpoint_block) { return; } // only want binary expressions of form x = ... if (node->lhs->is_var_name() && (node->op.get_value() == BinaryOp::BOP_ASSIGN)) { - auto lhs_str = std::dynamic_pointer_cast<VarName>(node->lhs)->get_name()->get_node_name(); + auto lhs_str = + std::dynamic_pointer_cast<ast::VarName>(node->lhs)->get_name()->get_node_name(); binary_expr_index[lhs_str] = ordered_binary_exprs.size(); ordered_binary_exprs.push_back(nmodl::to_nmodl(node)); ordered_binary_exprs_lhs.push_back(lhs_str); @@ -113,7 +119,7 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { for (const auto& ns_curr_ast: nonspecific_nodes) { logger->debug("SympyConductance :: Found NONSPECIFIC_CURRENT statement"); for (const auto& write_name: - std::dynamic_pointer_cast<Nonspecific>(ns_curr_ast).get()->get_currents()) { + std::dynamic_pointer_cast<ast::Nonspecific>(ns_curr_ast).get()->get_currents()) { std::string curr_write = write_name->get_node_name(); logger->debug("SympyConductance :: -> Adding non-specific current write name: {}", curr_write); @@ -126,7 +132,7 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { void SympyConductanceVisitor::lookup_useion_statements() { // add USEION statements to i_name map between write vars and names for (const auto& useion_ast: use_ion_nodes) { - auto ion = std::dynamic_pointer_cast<Useion>(useion_ast).get(); + auto ion = std::dynamic_pointer_cast<ast::Useion>(useion_ast).get(); std::string ion_name = ion->get_node_name(); logger->debug("SympyConductance :: Found USEION statement {}", nmodl::to_nmodl(ion)); if (i_ignore.find(ion_name) != i_ignore.end()) { @@ -143,7 +149,7 @@ void SympyConductanceVisitor::lookup_useion_statements() { } } -void SympyConductanceVisitor::visit_conductance_hint(ConductanceHint* node) { +void SympyConductanceVisitor::visit_conductance_hint(ast::ConductanceHint* node) { // find existing CONDUCTANCE statements - do not want to alter them // so keep a set of ion names i_ignore that we should ignore later logger->debug("SympyConductance :: Found existing CONDUCTANCE statement: {}", @@ -157,7 +163,7 @@ void SympyConductanceVisitor::visit_conductance_hint(ConductanceHint* node) { } }; -void SympyConductanceVisitor::visit_breakpoint_block(BreakpointBlock* node) { +void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { // add any breakpoint local variables to vars if (auto* symtab = node->get_statement_block()->get_symbol_table()) { for (const auto& localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { @@ -193,7 +199,7 @@ void SympyConductanceVisitor::visit_breakpoint_block(BreakpointBlock* node) { } } -void SympyConductanceVisitor::visit_program(Program* node) { +void SympyConductanceVisitor::visit_program(ast::Program* node) { vars = get_global_vars(node); AstLookupVisitor ast_lookup_visitor; use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); @@ -201,3 +207,5 @@ void SympyConductanceVisitor::visit_program(Program* node) { node->visit_children(this); } + +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index fcb3aac2aa..fa092d21bd 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -19,6 +19,8 @@ #include "visitors/ast_visitor.hpp" #include "visitors/lookup_visitor.hpp" +namespace nmodl { + /** * \class SympyConductanceVisitor * \brief Visitor for generating CONDUCTANCE statements for ions @@ -43,25 +45,37 @@ */ class SympyConductanceVisitor: public AstVisitor { + typedef std::map<std::string, std::string> string_map; + typedef std::set<std::string> string_set; + private: /// true while visiting breakpoint block bool breakpoint_block = false; - typedef std::map<std::string, std::string> string_map; - typedef std::set<std::string> string_set; + // set of all variables for SymPy string_set vars; + // set of currents to ignore string_set i_ignore; + // map between current write names and ion names string_map i_name; + bool NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS = false; + // list in order of binary expressions in breakpoint std::vector<std::string> ordered_binary_exprs; + // ditto but for LHS of expression only std::vector<std::string> ordered_binary_exprs_lhs; + // map from lhs of binary expression to index of expression in above vector std::map<std::string, std::size_t> binary_expr_index; + + // use ion ast nodes std::vector<std::shared_ptr<ast::AST>> use_ion_nodes; + + // non specific currents std::vector<std::shared_ptr<ast::AST>> nonspecific_nodes; std::vector<std::string> generate_statement_strings(ast::BreakpointBlock* node); @@ -74,4 +88,6 @@ class SympyConductanceVisitor: public AstVisitor { void visit_breakpoint_block(ast::BreakpointBlock* node) override; void visit_conductance_hint(ast::ConductanceHint* node) override; void visit_program(ast::Program* node) override; -}; \ No newline at end of file +}; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index b48a300842..b30e636e80 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -5,25 +5,30 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "visitors/sympy_solver_visitor.hpp" +#include <iostream> + #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "visitor_utils.hpp" -#include <iostream> -using namespace ast; +#include "visitors/sympy_solver_visitor.hpp" + + namespace py = pybind11; using namespace py::literals; -using namespace syminfo; -void SympySolverVisitor::visit_solve_block(SolveBlock* node) { +namespace nmodl { + +using symtab::syminfo::NmodlType; + +void SympySolverVisitor::visit_solve_block(ast::SolveBlock* node) { auto method = node->get_method(); if (method) { solve_method = method->get_value()->eval(); } } -void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { +void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { if (solve_method != cnexp_method) { logger->warn( "SympySolverVisitor: solve method not cnexp, so not integrating " @@ -38,7 +43,7 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { logger->warn("SympySolverVisitor: LHS of differential equation is not a VariableName"); return; } - auto lhs_name = std::dynamic_pointer_cast<VarName>(lhs)->get_name(); + auto lhs_name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); if (!lhs_name->is_prime_name()) { logger->warn("SympySolverVisitor: LHS of differential equation is not a PrimeName"); return; @@ -66,8 +71,8 @@ void SympySolverVisitor::visit_diff_eq_expression(DiffEqExpression* node) { } if (!solution.empty()) { auto statement = create_statement(solution); - auto expr_statement = std::dynamic_pointer_cast<ExpressionStatement>(statement); - auto bin_expr = std::dynamic_pointer_cast<BinaryExpression>( + auto expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement); + auto bin_expr = std::dynamic_pointer_cast<ast::BinaryExpression>( expr_statement->get_expression()); lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); @@ -90,4 +95,6 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { void SympySolverVisitor::visit_program(ast::Program* node) { vars = get_global_vars(node); node->visit_children(this); -} \ No newline at end of file +} + +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 05e89f4782..82f9eab0bc 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -16,6 +16,8 @@ #include "symtab/symbol.hpp" #include "visitors/ast_visitor.hpp" +namespace nmodl { + /** * \class SympySolverVisitor * \brief Visitor for differential equations in derivative block @@ -46,4 +48,6 @@ class SympySolverVisitor: public AstVisitor { void visit_diff_eq_expression(ast::DiffEqExpression* node) override; void visit_derivative_block(ast::DerivativeBlock* node) override; void visit_program(ast::Program* node) override; -}; \ No newline at end of file +}; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 6841605008..9ad9eb7a2a 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -12,12 +12,14 @@ #include "lexer/token_mapping.hpp" #include "visitors/symtab_visitor.hpp" -using namespace ast; -using namespace symtab; -using namespace syminfo; + +namespace nmodl { + +using symtab::Symbol; +using symtab::syminfo::NmodlType; // create symbol for given node -static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, +static std::shared_ptr<Symbol> create_symbol_for_node(ast::Node* node, NmodlType property, bool under_state_block) { ModToken token; @@ -45,14 +47,14 @@ static std::shared_ptr<Symbol> create_symbol_for_node(Node* node, /// helper function to setup/insert symbol into symbol table /// for the ast nodes which are of variable types -void SymtabVisitor::setup_symbol(Node* node, NmodlType property) { +void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { std::shared_ptr<Symbol> symbol; auto name = node->get_node_name(); /// if prime variable is already exist in symbol table /// then just update the order if (node->is_prime_name()) { - auto prime = dynamic_cast<PrimeName*>(node); + auto prime = dynamic_cast<ast::PrimeName*>(node); symbol = modsymtab->lookup(name); if (symbol) { symbol->set_order(prime->get_order()->eval()); @@ -78,21 +80,21 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlType property) { symbol = modsymtab->insert(symbol); if (node->is_param_assign()) { - auto parameter = dynamic_cast<ParamAssign*>(node); + auto parameter = dynamic_cast<ast::ParamAssign*>(node); auto value = parameter->get_value(); auto name = parameter->get_name(); if (value) { symbol->set_value(value->to_double()); } if (name->is_indexed_name()) { - auto index_name = dynamic_cast<IndexedName*>(name.get()); - auto length = dynamic_cast<Integer*>(index_name->get_length().get()); + auto index_name = dynamic_cast<ast::IndexedName*>(name.get()); + auto length = dynamic_cast<ast::Integer*>(index_name->get_length().get()); symbol->set_as_array(length->eval()); } } if (node->is_dependent_def()) { - auto variable = dynamic_cast<DependentDef*>(node); + auto variable = dynamic_cast<ast::DependentDef*>(node); auto length = variable->get_length(); if (length) { symbol->set_as_array(length->eval()); @@ -100,7 +102,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlType property) { } if (node->is_constant_var()) { - auto constant = dynamic_cast<ConstantVar*>(node); + auto constant = dynamic_cast<ast::ConstantVar*>(node); auto value = constant->get_value(); if (value) { symbol->set_value(value->to_double()); @@ -108,11 +110,11 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlType property) { } if (node->is_local_var()) { - auto variable = dynamic_cast<LocalVar*>(node); + auto variable = dynamic_cast<ast::LocalVar*>(node); auto name = variable->get_name(); if (name->is_indexed_name()) { - auto index_name = dynamic_cast<IndexedName*>(name.get()); - auto length = dynamic_cast<Integer*>(index_name->get_length().get()); + auto index_name = dynamic_cast<ast::IndexedName*>(name.get()); + auto length = dynamic_cast<ast::Integer*>(index_name->get_length().get()); symbol->set_as_array(length->eval()); } } @@ -123,7 +125,7 @@ void SymtabVisitor::setup_symbol(Node* node, NmodlType property) { } -void SymtabVisitor::add_model_symbol_with_property(Node* node, NmodlType property) { +void SymtabVisitor::add_model_symbol_with_property(ast::Node* node, NmodlType property) { auto token = node->get_token(); auto name = node->get_node_name(); auto symbol = std::make_shared<Symbol>(name, node, *token); @@ -154,7 +156,7 @@ static void add_external_symbols(symtab::ModelSymbolTable* symtab) { } -void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool is_global) { +void SymtabVisitor::setup_symbol_table(ast::AST* node, const std::string& name, bool is_global) { /// entering into new nmodl block auto symtab = modsymtab->enter_scope(name, node, is_global, node->get_symbol_table()); @@ -164,7 +166,7 @@ void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool /// there is only one solve statement allowed in mod file if (node->is_solve_block()) { - auto solve_block = dynamic_cast<SolveBlock*>(node); + auto solve_block = dynamic_cast<ast::SolveBlock*>(node); block_to_solve = solve_block->get_block_name()->get_node_name(); } @@ -194,19 +196,19 @@ void SymtabVisitor::setup_symbol_table(AST* node, const std::string& name, bool * Symtab visitor could be called multiple times, after optimization passes, * in which case we have to throw awayold symbol tables and setup new ones. */ -void SymtabVisitor::setup_symbol_table_for_program_block(Program* node) { +void SymtabVisitor::setup_symbol_table_for_program_block(ast::Program* node) { modsymtab = node->get_model_symbol_table(); modsymtab->set_mode(update); setup_symbol_table(node, node->get_node_type_name(), true); } -void SymtabVisitor::setup_symbol_table_for_global_block(Node* node) { +void SymtabVisitor::setup_symbol_table_for_global_block(ast::Node* node) { setup_symbol_table(node, node->get_node_type_name(), true); } -void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, const std::string& name) { +void SymtabVisitor::setup_symbol_table_for_scoped_block(ast::Node* node, const std::string& name) { setup_symbol_table(node, name, false); } @@ -217,7 +219,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(Node* node, const std::s * @todo : we assume table statement follows variable declaration */ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { - auto update_symbol = [this](const NameVector& variables, NmodlType property, int num_values) { + auto update_symbol = [this](const ast::NameVector& variables, NmodlType property, int num_values) { for (auto& var : variables) { auto name = var->get_node_name(); auto symbol = modsymtab->lookup(name); @@ -231,3 +233,6 @@ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { update_symbol(node->get_table_vars(), NmodlType::table_statement_var, num_values); update_symbol(node->get_depend_vars(), NmodlType::table_dependent_var, num_values); } + + +} // namespace nmodl diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 5254a1325d..519d016fdd 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -9,6 +9,9 @@ #include <utility> + +namespace nmodl { + /// rename matching variable void VarUsageVisitor::visit_name(ast::Name* node) { std::string name = node->get_node_name(); @@ -23,3 +26,5 @@ bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { node->visit_children(this); return used; } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index ca431d9280..2607efa35b 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -12,6 +12,9 @@ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" + +namespace nmodl { + /** * \class VarUsageVisitor * \brief Check if variable is used in given block @@ -32,3 +35,5 @@ class VarUsageVisitor: public AstVisitor { virtual void visit_name(ast::Name* node) override; }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index a54357d5ec..3592bce0a8 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -8,10 +8,11 @@ #include "visitors/verbatim_var_rename_visitor.hpp" #include "parser/c11_driver.hpp" -using namespace ast; -using namespace symtab; -void VerbatimVarRenameVisitor::visit_statement_block(StatementBlock* node) { +namespace nmodl { + + +void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { if (node->get_statements().empty()) { return; } @@ -68,10 +69,10 @@ std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { /** * Parse verbatim blocks and rename variables used */ -void VerbatimVarRenameVisitor::visit_verbatim(Verbatim* node) { +void VerbatimVarRenameVisitor::visit_verbatim(ast::Verbatim* node) { auto statement = node->get_statement(); auto text = statement->eval(); - c11::Driver driver; + parser::CDriver driver; driver.scan_string(text); auto tokens = driver.all_tokens(); @@ -82,3 +83,5 @@ void VerbatimVarRenameVisitor::visit_verbatim(Verbatim* node) { } statement->set(result); } + +} // namespace nmodl diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index 85b61f755f..23a60a1c34 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -13,6 +13,8 @@ #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" +namespace nmodl { + /** * \class VerbatimVarRenameVisitor * \brief Rename variable in verbatim block @@ -55,3 +57,5 @@ class VerbatimVarRenameVisitor: public AstVisitor { virtual void visit_verbatim(ast::Verbatim* node) override; virtual void visit_statement_block(ast::StatementBlock* node) override; }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 1b1f93f255..82b0e30a49 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -8,9 +8,10 @@ #include "visitors/verbatim_visitor.hpp" #include <iostream> -using namespace ast; -void VerbatimVisitor::visit_verbatim(Verbatim* node) { +namespace nmodl { + +void VerbatimVisitor::visit_verbatim(ast::Verbatim* node) { std::string block; auto statement = node->get_statement(); if (statement) { @@ -24,3 +25,5 @@ void VerbatimVisitor::visit_verbatim(Verbatim* node) { blocks.push_back(block); } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index dbcb16022e..030cda4115 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -12,6 +12,8 @@ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" +namespace nmodl { + /** * \class VerbatimVisitor * \brief Visitor for verbatim blocks of AST @@ -44,3 +46,5 @@ class VerbatimVisitor: public AstVisitor { return blocks; } }; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index e72a3369a7..16a0d268f6 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -15,7 +15,11 @@ #include "visitors/json_visitor.hpp" #include "visitors/nmodl_visitor.hpp" + +namespace nmodl { + using namespace ast; +using symtab::syminfo::NmodlType; std::string get_new_name(const std::string& name, const std::string& suffix, @@ -75,7 +79,7 @@ LocalVar* add_local_variable(StatementBlock* node, const std::string& varname, i * if all statements can be part of procedure block. */ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { - nmodl::Driver driver; + nmodl::parser::NmodlDriver driver; auto nmodl_text = "PROCEDURE dummy() { " + code_statement + " }"; driver.parse_string(nmodl_text); auto ast = driver.ast(); @@ -88,14 +92,13 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { std::set<std::string> get_global_vars(Program* node) { std::set<std::string> vars; if (auto* symtab = node->get_symbol_table()) { - syminfo::NmodlType property = - syminfo::NmodlType::global_var | syminfo::NmodlType::range_var | - syminfo::NmodlType::param_assign | syminfo::NmodlType::extern_var | - syminfo::NmodlType::prime_name | syminfo::NmodlType::dependent_def | - syminfo::NmodlType::read_ion_var | syminfo::NmodlType::write_ion_var | - syminfo::NmodlType::nonspecific_cur_var | syminfo::NmodlType::electrode_cur_var | - syminfo::NmodlType::section_var | syminfo::NmodlType::constant_var | - syminfo::NmodlType::extern_neuron_variable | syminfo::NmodlType::state_var; + NmodlType property = NmodlType::global_var | NmodlType::range_var | + NmodlType::param_assign | NmodlType::extern_var | + NmodlType::prime_name | NmodlType::dependent_def | + NmodlType::read_ion_var | NmodlType::write_ion_var | + NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | + NmodlType::section_var | NmodlType::constant_var | + NmodlType::extern_neuron_variable | NmodlType::state_var; for (const auto& globalvar: symtab->get_variables_with_properties(property)) { vars.insert(globalvar->get_name()); } @@ -103,7 +106,6 @@ std::set<std::string> get_global_vars(Program* node) { return vars; } -namespace nmodl { std::string to_nmodl(ast::AST* node) { std::stringstream stream; diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index a085870f2d..8f1e8004f0 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -13,6 +13,8 @@ #include "ast/ast.hpp" +namespace nmodl { + /** Return new name variable by appending "_suffix_COUNT" where COUNT is number * of times the given variable is already used. */ @@ -37,7 +39,6 @@ std::shared_ptr<ast::Statement> create_statement(const std::string& code_stateme /** Return set of strings with the names of all global variables */ std::set<std::string> get_global_vars(ast::Program* node); -namespace nmodl { /** Given AST node, return the NMODL string representation */ std::string to_nmodl(ast::AST* node); diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index f502d52b6d..fd960f7f78 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -14,18 +14,22 @@ #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" -using Token = nmodl::Parser::token; +using namespace nmodl; + +using nmodl::parser::NmodlDriver; +using nmodl::parser::NmodlLexer; +using parser::NmodlParser; +using Token = NmodlParser::token; +using TokenType = NmodlParser::token_type; +using SymbolType = NmodlParser::symbol_type; /// just retrieve token type from lexer -nmodl::Parser::token_type token_type(const std::string& name) { +TokenType token_type(const std::string& name) { std::istringstream ss(name); std::istream& in = ss; - nmodl::Driver driver; - nmodl::Lexer scanner(driver, &in); - - using TokenType = nmodl::Parser::token_type; - using SymbolType = nmodl::Parser::symbol_type; + NmodlDriver driver; + NmodlLexer scanner(driver, &in); SymbolType sym = scanner.next_token(); TokenType token = sym.token(); diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index afaabd54ec..c869862bca 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -14,16 +14,20 @@ #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" +using namespace nmodl; +using nmodl::parser::NmodlDriver; +using nmodl::parser::NmodlLexer; + /// retrieve token from lexer template <typename T> void symbol_type(const std::string& name, T& value) { std::istringstream ss(name); std::istream& in = ss; - nmodl::Driver driver; - nmodl::Lexer scanner(driver, &in); + NmodlDriver driver; + NmodlLexer scanner(driver, &in); - nmodl::Parser::symbol_type sym = scanner.next_token(); + auto sym = scanner.next_token(); value = sym.value.as<T>(); } diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 9cd20a10ec..6a4196a742 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -20,7 +20,7 @@ //============================================================================= bool is_valid_construct(const std::string& construct) { - nmodl::Driver driver; + nmodl::parser::NmodlDriver driver; return driver.parse_string(construct); } @@ -140,7 +140,7 @@ SCENARIO("Parser test for invalid NMODL grammar constructs") { //============================================================================= std::string solve_construct(const std::string& equation, std::string method) { - diffeq::Driver driver; + nmodl::parser::DiffeqDriver driver; auto solution = driver.solve(equation, std::move(method)); return solution; } diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 9fc9103e38..8ea7b2e3c9 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -15,7 +15,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Stringstream test 1") { std::stringstream ss; - JSONPrinter p(ss); + nmodl::JSONPrinter p(ss); p.compact_json(true); p.push_block("A"); @@ -29,7 +29,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Stringstream test 2") { std::stringstream ss; - JSONPrinter p(ss); + nmodl::JSONPrinter p(ss); p.compact_json(true); p.push_block("A"); @@ -47,7 +47,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Test with nodes as separate tags") { std::stringstream ss; - JSONPrinter p(ss); + nmodl::JSONPrinter p(ss); p.compact_json(true); p.expand_keys(true); diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/pybind/conftest.py index 0b47aa598d..bb0d27e83f 100644 --- a/test/nmodl/transpiler/pybind/conftest.py +++ b/test/nmodl/transpiler/pybind/conftest.py @@ -6,7 +6,7 @@ # *********************************************************************** import pytest -from nmodl.dsl import Driver +from nmodl.dsl import NmodlDriver CHANNEL = """NEURON { SUFFIX NaTs2_t @@ -32,6 +32,6 @@ @pytest.fixture def ch_ast(): - d = Driver() + d = NmodlDriver() d.parse_string(CHANNEL) return d.ast() diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 281cd5d597..7f763fbcd9 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -14,11 +14,10 @@ #include "symtab/symbol.hpp" #include "symtab/symbol_table.hpp" +using namespace nmodl; using namespace symtab; using namespace syminfo; -extern bool has_property(const NmodlType& obj, NmodlType property); - //============================================================================= // Symbol properties test //============================================================================= diff --git a/test/nmodl/transpiler/utils/test_utils.cpp b/test/nmodl/transpiler/utils/test_utils.cpp index 88a040ff7f..e7669516b8 100644 --- a/test/nmodl/transpiler/utils/test_utils.cpp +++ b/test/nmodl/transpiler/utils/test_utils.cpp @@ -9,14 +9,14 @@ int count_leading_spaces(std::string text) { int length = text.size(); - stringutils::ltrim(text); + nmodl::stringutils::ltrim(text); int num_whitespaces = length - text.size(); return num_whitespaces; } /// check if string has only whitespaces bool is_empty(std::string text) { - stringutils::trim(text); + nmodl::stringutils::trim(text); return text.empty(); } diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index a655cdafd6..b7d1fbc449 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -33,9 +33,11 @@ #include "visitors/verbatim_visitor.hpp" using json = nlohmann::json; -using namespace ast; + using namespace nmodl; -using namespace syminfo; +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; +using symtab::syminfo::NmodlType; int main(int argc, char* argv[]) { // initialize python interpreter once for @@ -51,7 +53,7 @@ int main(int argc, char* argv[]) { //============================================================================= std::vector<std::string> run_verbatim_visitor(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -84,7 +86,7 @@ TEST_CASE("Verbatim Visitor") { //============================================================================= std::string run_json_visitor(const std::string& text, bool compact = false) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); return to_json(ast.get(), compact); @@ -185,7 +187,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { } )"; - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(nmodl_text); auto ast = driver.ast(); @@ -267,7 +269,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { //============================================================================= std::string run_nmodl_visitor(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -297,7 +299,7 @@ SCENARIO("Test for AST back to NMODL transformation") { //============================================================================= std::string run_var_rename_visitor(const std::string& text, std::vector<std::pair<std::string, std::string>> variables) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); { @@ -405,7 +407,7 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { //============================================================================= std::string run_local_var_rename_visitor(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -658,7 +660,7 @@ SCENARIO("Presence of local variable in verbatim block") { //============================================================================= std::string run_inline_visitor(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -1255,7 +1257,7 @@ SCENARIO("Procedure inlining handles local-global name conflict") { //============================================================================= std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string& variable) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -1489,7 +1491,7 @@ SCENARIO("Running defuse analyzer") { //============================================================================= std::string run_localize_visitor(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -1702,7 +1704,7 @@ SCENARIO("Localizer test with multiple global blocks") { //============================================================================= std::string run_cnexp_solve_visitor(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -1846,7 +1848,7 @@ SCENARIO("CnexpSolver visitor solving ODEs") { //============================================================================= void run_visitor_passes(const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); { @@ -1890,15 +1892,15 @@ SCENARIO("Running visitor passes multiple time") { // Ast lookup visitor tests //============================================================================= -std::vector<std::shared_ptr<AST>> run_lookup_visitor(Program* node, - std::vector<AstNodeType>& types) { +std::vector<std::shared_ptr<ast::AST>> run_lookup_visitor(ast::Program* node, + std::vector<AstNodeType>& types) { AstLookupVisitor v; return v.lookup(node, types); } SCENARIO("Searching for ast nodes using AstLookupVisitor") { auto to_ast = [](const std::string& text) { - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); return driver.ast(); }; @@ -1971,7 +1973,7 @@ std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { std::vector<std::string> results; // construct AST from text - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -1993,7 +1995,7 @@ std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { return results; } -void run_sympy_visitor_passes(Program* node) { +void run_sympy_visitor_passes(ast::Program* node) { // construct symbol table from AST SymtabVisitor v_symtab; v_symtab.visit_program(node); @@ -2010,7 +2012,7 @@ void run_sympy_visitor_passes(Program* node) { v_sympy2.visit_program(node); } -std::string ast_to_string(Program* node) { +std::string ast_to_string(ast::Program* node) { std::stringstream stream; { NmodlPrintVisitor v(stream); @@ -2123,7 +2125,7 @@ SCENARIO("SympySolver visitor", "[sympy]") { } )"; // construct AST from text - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(nmodl_text); auto ast = driver.ast(); @@ -2151,7 +2153,7 @@ SCENARIO("SympySolver visitor", "[sympy]") { std::string run_sympy_conductance_visitor(const std::string& text) { // construct AST from text - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -2172,7 +2174,7 @@ std::string run_sympy_conductance_visitor(const std::string& text) { std::string breakpoint_to_nmodl(const std::string& text) { // construct AST from text - nmodl::Driver driver; + NmodlDriver driver; driver.parse_string(text); auto ast = driver.ast(); @@ -2187,7 +2189,7 @@ std::string breakpoint_to_nmodl(const std::string& text) { nmodl::to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); } -void run_sympy_conductance_passes(Program* node) { +void run_sympy_conductance_passes(ast::Program* node) { // construct symbol table from AST SymtabVisitor v_symtab; v_symtab.visit_program(node); From 1fb23533c18ce5bc603f47fc30f3065e5921a4c4 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Tue, 5 Mar 2019 20:52:32 +0100 Subject: [PATCH 150/871] Move tclap to CLI11 for better UI (BlueBrain/nmodl#24) * update all programs * remove tclap code * update README update NMODL Repo SHA: BlueBrain/nmodl@0b78d2482b56af6980682377d8e40a09957b0ee3 --- README.md | 97 +++++++++- cmake/nmodl/CMakeLists.txt | 5 + src/nmodl/lexer/CMakeLists.txt | 4 +- src/nmodl/lexer/main_c.cpp | 55 ++---- src/nmodl/lexer/main_nmodl.cpp | 185 +++++++++--------- src/nmodl/nmodl/CMakeLists.txt | 5 +- src/nmodl/nmodl/arg_handler.cpp | 210 -------------------- src/nmodl/nmodl/arg_handler.hpp | 102 ---------- src/nmodl/nmodl/main.cpp | 331 ++++++++++++++++++++------------ src/nmodl/parser/CMakeLists.txt | 4 +- src/nmodl/parser/main_c.cpp | 50 ++--- src/nmodl/parser/main_nmodl.cpp | 50 ++--- src/nmodl/visitors/main.cpp | 42 ++-- 13 files changed, 463 insertions(+), 677 deletions(-) delete mode 100644 src/nmodl/nmodl/arg_handler.cpp delete mode 100644 src/nmodl/nmodl/arg_handler.hpp diff --git a/README.md b/README.md index 67de5476c7..81711c71e6 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ True NMODL is now setup correctly! -#### Understand Python API +#### Using Python API The user documentation for NMODL is incomplete and not available on GitHub yet. The best way to understand the API and usage is using Jupyter notebooks provided in docs directory : @@ -160,20 +160,103 @@ $ tree $HOME/nmodl/bin The `nmodl_lexer` and `nmodl_parser` are standalone tools for testing mod files. If you want to test if given mod file can be successfully parsed by NMODL then you can do: ``` -nmodl_parser --file <path>/hh.mod +$ nmodl_parser <path>/hh.mod ``` -To see how NMODL will generate the code for given mod file, you can do: +Main code generation program is `nmodl`. You can see all sub-commands supported using: ``` -nmodl <path>/hh.mod +$ ./bin/nmodl -h + +NMODL : Source-to-Source Code Generation Framework +Usage: ./bin/nmodl [OPTIONS] file... [SUBCOMMAND] + +Positionals: + file TEXT:FILE ... REQUIRED One or more MOD files to process + +Options: + -h,--help Print this help message and exit + -H,--help-all Print this help message including all sub-commands + -o,--output TEXT=. Directory for backend code output + --scratch TEXT=tmp Directory for intermediate code output + +Subcommands: + host HOST/CPU code backends + acc Accelerator code backends + sympy SymPy based analysis and optimizations + passes Analyse/Optimization passes + codegen Code generation options ``` -This will generate hh.cpp in the current directory. There are different optimisation options for code generation that you can see using: +To see all command line options from every sub-command, you can do: ``` -nmodl --help +$ ./bin/nmodl -H + +NMODL : Source-to-Source Code Generation Framework +Usage: ./bin/nmodl [OPTIONS] file... [SUBCOMMAND] + +Positionals: + file TEXT:FILE ... REQUIRED One or more MOD files to process + +Options: + -h,--help Print this help message and exit + -H,--help-all Print this help message including all sub-commands + -o,--output TEXT=. Directory for backend code output + --scratch TEXT=tmp Directory for intermediate code output + +Subcommands: +host + HOST/CPU code backends + Options: + --c C/C++ backend + --omp C/C++ backend with OpenMP + +acc + Accelerator code backends + Options: + --oacc C/C++ backend with OpenACC + --cuda C/C++ backend with CUDA + +sympy + SymPy based analysis and optimizations + Options: + --analytic Solve ODEs using SymPy analytic integration + --pade Pade approximation in SymPy analytic integration + --conductance Add CONDUCTANCE keyword in BREAKPOINT + +passes + Analyse/Optimization passes + Options: + --inline Perform inlining at NMODL level + --localize Convert RANGE variables to LOCAL + --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist + --local-rename Rename LOCAL variable if variable of same name exist in global scope + --verbatim-inline Inline even if verbatim block exist + --verbatim-rename Rename variables in verbatim block + --json-ast Write AST to JSON file + --nmodl-ast Write AST to NMODL file + --json-perf Write performance statistics to JSON file + --show-symtab Write symbol table to stdout + +codegen + Code generation options + Options: + --layout TEXT:{aos,soa}=soa Memory layout for code generation + --datatype TEXT:{float,double}=soa Data type for floating point variables +``` + +To use code generation capability you can do: + ``` +$ nmodl <path>/hh.mod \ + host --c \ + sympy --analytic --pade --conductance \ + passes --inline --localize --localize-verbatim \ + --local-rename --verbatim-inline --verbatim-rename +``` + +This will generate hh.cpp in the current directory. #### Using NMODL With CoreNEURON @@ -188,7 +271,7 @@ make -j make test ``` -> Note that the code generation backend is not complete yet. +Note that the latest master has changed command line option. Use released version **`0.1`** for now. ### Development Conventions diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 5cb33294d2..9a87c3e534 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -71,6 +71,11 @@ include_directories(${PROJECT_SOURCE_DIR} message(STATUS "INCLUDING PYBIND11") add_subdirectory(ext/pybind11) +# ============================================================================= +# Include path from external libraries +# ============================================================================= +include_directories(${PROJECT_SOURCE_DIR}/ext/cli11/include) + # ============================================================================= # Project version from git and project directories # ============================================================================= diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index c3b5cfa5c2..61c580d940 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -162,8 +162,8 @@ add_library(lexer STATIC $<TARGET_OBJECTS:lexer_obj>) add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) -target_link_libraries(nmodl_lexer lexer) -target_link_libraries(c_lexer lexer) +target_link_libraries(nmodl_lexer lexer util) +target_link_libraries(c_lexer lexer util) # ============================================================================= # Install executable diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 17450661e8..437df95c77 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -6,63 +6,42 @@ *************************************************************************/ #include <fstream> -#include <iostream> +#include "CLI/CLI.hpp" #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" -#include "tclap/CmdLine.h" +#include "utils/logger.hpp" /** - * Standlone lexer program for C. This demonstrate basic - * usage of scanner and driver class. + * Example of standalone lexer program for C codes that + * demonstrate use of CLexer and CDriver classes. */ - int main(int argc, const char* argv[]) { - try { - TCLAP::CmdLine cmd("C Lexer: Standalone lexer program for C"); - TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, "", "string"); - - cmd.add(filearg); - cmd.parse(argc, argv); - - std::string filename = filearg.getValue(); - - if (filename.empty()) { - std::cerr << "Error : Pass input C file, see --help" << std::endl; - return 1; - } + CLI::App app{"C-Lexer : Standalone Lexer for C Code"}; + std::vector<std::string> files; + app.add_option("file", files, "One or more C files to process") + ->required() + ->check(CLI::ExistingFile); - std::ifstream file(filename); + CLI11_PARSE(app, argc, argv); - if (!file) { - throw std::runtime_error("Could not open file " + filename); - } - - std::cout << "\n C Lexer : Processing file : " << filename << std::endl; - - std::istream& in(file); + for (const auto& f: files) { + nmodl::logger->info("Processing {}", f); + std::ifstream file(f); nmodl::parser::CDriver driver; - nmodl::parser::CLexer scanner(driver, &in); - - using Token = nmodl::parser::CParser::token; + nmodl::parser::CLexer scanner(driver, &file); - /// parse C file untile EOF, print each token + /// parse C file and print token until EOF while (true) { auto sym = scanner.next_token(); auto token = sym.token(); - - /// end of file - if (token == Token::END) { + if (token == nmodl::parser::CParser::token::END) { break; } - std::cout << sym.value.as<std::string>(); + std::cout << sym.value.as<std::string>() << std::endl; } - } catch (TCLAP::ArgException& e) { - std::cout << std::endl << "Argument Error: " << e.error() << " for arg " << e.argId(); - return 1; } - return 0; } diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index f431a3a150..bc3c1e3d75 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -6,131 +6,128 @@ *************************************************************************/ #include <fstream> -#include <iostream> +#include <streambuf> +#include "CLI/CLI.hpp" #include "ast/ast.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" -#include "tclap/CmdLine.h" +#include "utils/logger.hpp" /** - * Standlone lexer program for NMODL. This demonstrate basic - * usage of scanner and driver class. We parse user provided + * Stand alone lexer program for NMODL. This demonstrate basic + * usage of scanner and driver classes. We parse user provided * nmodl file and print individual token with it's value and * location. */ +using namespace nmodl; -using SymbolType = nmodl::parser::NmodlParser::symbol_type; -using Token = nmodl::parser::NmodlParser::token; -using TokenType = nmodl::parser::NmodlParser::token_type; +using parser::NmodlDriver; +using parser::NmodlLexer; +using SymbolType = parser::NmodlParser::symbol_type; +using Token = parser::NmodlParser::token; +using TokenType = parser::NmodlParser::token_type; -int main(int argc, const char* argv[]) { - try { - TCLAP::CmdLine cmd("NMODL Lexer: Standalone lexer program for NMODL"); - TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, - "../test/input/channel.mod", "string"); - - cmd.add(filearg); - cmd.parse(argc, argv); - - std::string filename = filearg.getValue(); - std::ifstream file(filename); - - if (!file) { - throw std::runtime_error("Could not open file " + filename); - } - - std::cout << "\n NMODL Lexer : Processing file : " << filename << std::endl; +void tokenize(const std::string& mod_text) { + std::istringstream in(mod_text); - std::istream& in(file); + /// lexer instance use driver object for error reporting + NmodlDriver driver; - /// lexer instace use driver object for error reporting - nmodl::parser::NmodlDriver driver; + /// lexer instance with stream to read-in tokens + NmodlLexer scanner(driver, &in); - /// lexer instace with stream to read-in tokens - nmodl::parser::NmodlLexer scanner(driver, &in); + /// parse nmodl text and print token until EOF + while (true) { + SymbolType sym = scanner.next_token(); + TokenType token = sym.token(); + if (token == Token::END) { + break; + } - /// parse nmodl file untile EOF, print each token - while (true) { - SymbolType sym = scanner.next_token(); - TokenType token = sym.token(); - - /// end of file - if (token == Token::END) { - break; - } - - /** Lexer returns different ast types base on token type. We - * retrieve token object from each instance and print it. - * Note that value is of ast type i.e. nmodl::ast::Name* etc. */ - switch (token) { - /// token with name ast class - case Token::NAME: - case Token::METHOD: - case Token::SUFFIX: - case Token::VALENCE: - case Token::DEL: - case Token::DEL2: { - auto value = sym.value.as<nmodl::ast::Name*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } + /** Lexer returns different ast types base on token type. We + * retrieve token object from each instance and print it. + * Note that value is of ast type i.e. ast::Name* etc. */ + switch (token) { + /// token with name ast class + case Token::NAME: + case Token::METHOD: + case Token::SUFFIX: + case Token::VALENCE: + case Token::DEL: + case Token::DEL2: { + auto value = sym.value.as<ast::Name*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } /// token with prime ast class - case Token::PRIME: { - auto value = sym.value.as<nmodl::ast::PrimeName*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } + case Token::PRIME: { + auto value = sym.value.as<ast::PrimeName*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } /// token with integer ast class - case Token::INTEGER: { - auto value = sym.value.as<nmodl::ast::Integer*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } + case Token::INTEGER: { + auto value = sym.value.as<ast::Integer*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } /// token with double/float ast class - case Token::REAL: { - auto value = sym.value.as<nmodl::ast::Double*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } + case Token::REAL: { + auto value = sym.value.as<ast::Double*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } /// token with string ast class - case Token::STRING: { - auto value = sym.value.as<nmodl::ast::String*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; - break; - } + case Token::STRING: { + auto value = sym.value.as<ast::String*>(); + std::cout << *(value->get_token()) << std::endl; + delete value; + break; + } /// token with string data type - case Token::VERBATIM: - case Token::BLOCK_COMMENT: - case Token::LINE_PART: { - auto str = sym.value.as<std::string>(); - std::cout << str << std::endl; - break; - } + case Token::VERBATIM: + case Token::BLOCK_COMMENT: + case Token::LINE_PART: { + auto str = sym.value.as<std::string>(); + std::cout << str << std::endl; + break; + } /// all remaining tokens has ModToken* as a vaue - default: { - auto token = sym.value.as<ModToken>(); - std::cout << token << std::endl; - } - } + default: { + auto token = sym.value.as<ModToken>(); + std::cout << token << std::endl; + } } - } catch (TCLAP::ArgException& e) { - std::cout << std::endl << "Argument Error: " << e.error() << " for arg " << e.argId(); - return 1; } +} + +int main(int argc, const char* argv[]) { + CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code"}; + + std::vector<std::string> files; + app.add_option("file", files, "One or more NMODL files")->required()->check(CLI::ExistingFile); + + CLI11_PARSE(app, argc, argv); + + for (const auto& file: files) { + logger->info("Processing file : {}", file); + std::ifstream f(file); + std::string mod((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); + tokenize(mod); + } return 0; } diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 2e7d835a99..8d6d0ed0c1 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -2,10 +2,7 @@ # NMODL sources # ============================================================================= include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -set(NMODL_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/arg_handler.cpp) +set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) # ============================================================================= # Add executables diff --git a/src/nmodl/nmodl/arg_handler.cpp b/src/nmodl/nmodl/arg_handler.cpp deleted file mode 100644 index 2fac546620..0000000000 --- a/src/nmodl/nmodl/arg_handler.cpp +++ /dev/null @@ -1,210 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#include "arg_handler.hpp" -#include "tclap/CmdLine.h" -#include "utils/string_utils.hpp" -#include "version/version.h" - - -namespace nmodl { - -ArgumentHandler::ArgumentHandler(const int& argc, const char** argv) { - // version string - auto version = nmodl::version().to_string(); - - try { - using string_vector_type = std::vector<std::string>; - using value_constraint_type = TCLAP::ValuesConstraint<std::string>; - using value_arg_type = TCLAP::ValueArg<std::string>; - using switch_arg_type = TCLAP::SwitchArg; - using unlabel_arg_type = TCLAP::UnlabeledMultiArg<std::string>; - - TCLAP::CmdLine cmd("NMODL :: Code Generator Toolkit for NMODL", ' ', version); - - // clang-format off - string_vector_type host_val = {"SERIAL", "OPENMP", "OPENACC"}; - value_constraint_type host_constr(host_val); - value_arg_type host_arg( - "", - "host", - "Host / CPU backend [" + host_val[0] +"]", - false, - host_val[0], - &host_constr, - cmd); - - string_vector_type accel_val = {"CUDA"}; - value_constraint_type accel_constr(accel_val); - value_arg_type accel_arg( - "", - "accelerator", - "Accelerator / Device backend [" + accel_val[0] + "]", - false, - "", - &accel_constr, - cmd); - - string_vector_type dtype_val = {"DOUBLE", "FLOAT"}; - value_constraint_type dtype_constr(dtype_val); - value_arg_type dtype_arg( - "", - "datatype", - "Floating point data type [" + dtype_val[0] + "]", - false, - dtype_val[0], - &dtype_constr, - cmd); - - string_vector_type layout_val = {"SOA", "AOS"}; - value_constraint_type layout_constr(layout_val); - value_arg_type layout_arg( - "", - "layout", - "Memory layout for channel/synapse [" + layout_val[0] +"]", - false, - layout_val[0], - &layout_constr, - cmd); - - value_arg_type output_dir_arg( - "", - "output-dir", - "Output directory for code generator [.]", - false, - ".", - "string", - cmd); - - value_arg_type scratch_dir_arg( - "", - "scratch-dir", - "Output directory for intermediate results [tmp]", - false, - "tmp", - "string", - cmd); - - switch_arg_type verbose_arg( - "", - "verbose", - "Enable verbose output", - cmd, - false); - - switch_arg_type sympy_arg( - "", - "enable-sympy", - "Enable SymPy analytic integration", - cmd, - false); - - switch_arg_type pade_approx_arg( - "", - "enable-pade-approx", - "Enable Pade Approx in SymPy analytic integration", - cmd, - false); - - switch_arg_type inline_arg( - "", - "nmodl-inline", - "Enable NMODL inlining", - cmd, - false); - - switch_arg_type localize_arg( - "", - "localize", - "Localize RANGE, GLOBAL, ASSIGNED variables", - cmd, - false); - - switch_arg_type localize_verbatim_arg( - "", - "localize-verbatim", - "Localize even with VERBATIM blocks (unsafe optimizations)", - cmd, - false); - - switch_arg_type local_rename_arg( - "", - "local-rename", - "Rename LOCAL variables if necessary", - cmd, - false); - - switch_arg_type perf_stats_arg( - "", - "dump-perf-stats", - "Run performance visitor and dump stats into JSON file", - cmd, - false); - - switch_arg_type nmodl_state_arg( - "", - "dump-nmodl-state", - "Dump intermediate AST states into NMODL", - cmd, - false); - - switch_arg_type ast_to_json_arg( - "", - "dump-ast-as-json", - "Dump intermediate AST states into JSON state", - cmd, - false); - - switch_arg_type no_verbatim_rename_arg( - "", - "no-verbatim-rename", - "Disable renaming variables in VERBATIM block", - cmd, - false); - - switch_arg_type show_symtab_arg( - "", - "show-symtab", - "Show symbol table to stdout", - cmd, - false); - - unlabel_arg_type nmodl_arg( - "files", - "NMODL input models path", - true, - "string", - cmd); - // clang-format on - - cmd.parse(argc, argv); - - nmodl_files = nmodl_arg.getValue(); - host_backend = host_arg.getValue(); - accel_backend = accel_arg.getValue(); - dtype = stringutils::tolower(dtype_arg.getValue()); - mlayout = layout_arg.getValue(); - verbose = verbose_arg.getValue(); - sympy = sympy_arg.getValue(); - pade_approx = pade_approx_arg.getValue(); - inlining = inline_arg.getValue(); - localize_with_verbatim = localize_verbatim_arg.getValue(); - local_rename = local_rename_arg.getValue(); - verbatim_rename = !no_verbatim_rename_arg.getValue(); - localize = localize_arg.getValue(); - perf_stats = perf_stats_arg.getValue(); - show_symtab = show_symtab_arg.getValue(); - output_dir = output_dir_arg.getValue(); - scratch_dir = scratch_dir_arg.getValue(); - ast_to_nmodl = nmodl_state_arg.getValue(); - ast_to_json = ast_to_json_arg.getValue(); - } catch (TCLAP::ArgException& e) { - std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; - } -} - -} // namespace nmodl diff --git a/src/nmodl/nmodl/arg_handler.hpp b/src/nmodl/nmodl/arg_handler.hpp deleted file mode 100644 index 70a9574934..0000000000 --- a/src/nmodl/nmodl/arg_handler.hpp +++ /dev/null @@ -1,102 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -#include <string> -#include <vector> - - -namespace nmodl { - -/** - * \class ArgumentHandler - * \brief Parser comamnd line arguments - */ - -struct ArgumentHandler { - /// input files for code generation - std::vector<std::string> nmodl_files; - - /// host code generation backend - std::string host_backend; - - /// device code generation backend - std::string accel_backend; - - /// floating data type to use - std::string dtype; - - /// memory layout to use - std::string mlayout; - - /// enable SymPy analytic integration - bool sympy; - - /// enable Pade approx in SymPy analytic integration - bool pade_approx; - - /// enable nmodl level inlining - bool inlining; - - /// enable inlining even if verbatim block exisits - bool localize_with_verbatim; - - /// enable renaming local variables - bool local_rename; - - /// enable conversion of RANGE variables to LOCAL - bool localize; - - /// dump performance statistics - bool perf_stats; - - /// show symbol table information - bool show_symtab; - - /// generate nmodl from ast - bool ast_to_nmodl; - - /// generate json from ast - bool ast_to_json; - - /// enable verbose (todo: replace by log) - bool verbose; - - /// enable renaming inside verbatim block - bool verbatim_rename; - - /// directory for code generation - std::string output_dir; - - /// directory for intermediate files from code generation - std::string scratch_dir; - - ArgumentHandler(const int& argc, const char* argv[]); - - bool aos_memory_layout() { - return mlayout == "AOS"; - } - - bool host_c_backend() { - return host_backend == "SERIAL"; - } - - bool host_omp_backend() { - return host_backend == "OPENMP"; - } - - bool host_acc_backend() { - return host_backend == "OPENACC"; - } - - bool device_cuda_backend() { - return accel_backend == "CUDA"; - } -}; - -} // namespace nmodl diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index c7a6bb870e..cb0f154eb7 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -5,17 +5,17 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include <fstream> -#include <iostream> -#include <pybind11/embed.h> #include <sstream> +#include <string> +#include <vector> -#include "arg_handler.hpp" +#include "CLI/CLI.hpp" #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_omp_visitor.hpp" #include "parser/nmodl_driver.hpp" +#include "pybind11/embed.h" #include "utils/common_utils.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" @@ -34,197 +34,288 @@ using namespace nmodl; using namespace codegen; +using nmodl::parser::NmodlDriver; -using nmodl::codegen::LayoutType; +int main(int argc, const char* argv[]) { + CLI::App app{"NMODL : Source-to-Source Code Generation Framework"}; + /// list of mod files to process + std::vector<std::string> mod_files; -void ast_to_nmodl(ast::Program* ast, const std::string& filename) { - NmodlPrintVisitor v(filename); - v.visit_program(ast); - logger->info("AST to NMODL transformation written to {}", filename); -} + /// true if serial c code to be generated + bool c_backend(true); -int main(int argc, const char* argv[]) { - ArgumentHandler arg(argc, argv); + /// true if c code with openmp to be generated + bool omp_backend(false); - make_path(arg.output_dir); - make_path(arg.scratch_dir); + /// true if c code with openacc to be generated + bool oacc_backend(false); - int error_count = 0; + /// true if cuda code to be generated + bool cuda_backend(false); - if (arg.verbose) { - logger->set_level(spdlog::level::debug); - } + /// true if sympy should be used for solving ODEs analytically + bool sympy_analytic(false); + + /// true if Pade approximation to be used + bool sympy_pade(false); + + /// true if conductance keyword can be added to breakpoint + bool sympy_conductance(false); + + /// true if inlining at nmodl level to be done + bool nmodl_inline(false); + + /// true if range variables to be converted to local + bool localize(false); + + /// true if localize variables even if verbatim block is used + bool localize_verbatim(false); + + /// true if local variables to be renamed + bool local_rename(false); + + /// true if inline even if verbatim block exist + bool verbatim_inline(false); + + /// true if verbatim blocks + bool verbatim_rename(false); + + /// directory where code will be generated + std::string output_dir("."); + + /// directory where intermediate file will be generated + std::string scratch_dir("tmp"); + + /// true if ast should be converted to json + bool json_ast(false); + + /// true if ast should be converted to nmodl + bool nmodl_ast(false); + + /// true if performance stats should be converted to json + bool json_perfstat(false); + + /// true if symbol table should be printed + bool show_symtab(false); + + /// memory layout for code generation + std::string layout("soa"); + + /// floating point data type + std::string data_type("double"); + + app.get_formatter()->column_width(40); + app.set_help_all_flag("-H,--help-all", "Print this help message including all sub-commands"); - if (arg.sympy) { + app.add_option("file", mod_files, "One or more MOD files to process") + ->ignore_case() + ->required() + ->check(CLI::ExistingFile); + + app.add_option("-o,--output", output_dir, "Directory for backend code output", true) + ->ignore_case(); + app.add_option("--scratch", scratch_dir, "Directory for intermediate code output", true) + ->ignore_case(); + + auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); + host_opt->add_flag("--c", c_backend, "C/C++ backend")->ignore_case(); + host_opt->add_flag("--omp", omp_backend, "C/C++ backend with OpenMP")->ignore_case(); + + auto acc_opt = app.add_subcommand("acc", "Accelerator code backends")->ignore_case(); + acc_opt->add_flag("--oacc", oacc_backend, "C/C++ backend with OpenACC")->ignore_case(); + acc_opt->add_flag("--cuda", cuda_backend, "C/C++ backend with CUDA")->ignore_case(); + + // clang-format off + auto sympy_opt = app.add_subcommand("sympy", "SymPy based analysis and optimizations")->ignore_case(); + sympy_opt->add_flag("--analytic", sympy_analytic, "Solve ODEs using SymPy analytic integration")->ignore_case(); + sympy_opt->add_flag("--pade", sympy_pade, "Pade approximation in SymPy analytic integration")->ignore_case(); + sympy_opt->add_flag("--conductance", sympy_conductance, "Add CONDUCTANCE keyword in BREAKPOINT")->ignore_case(); + + auto passes_opt = app.add_subcommand("passes", "Analyse/Optimization passes")->ignore_case(); + passes_opt->add_flag("--inline", nmodl_inline, "Perform inlining at NMODL level")->ignore_case(); + passes_opt->add_flag("--localize", localize, "Convert RANGE variables to LOCAL")->ignore_case(); + passes_opt->add_flag("--localize-verbatim", localize_verbatim, "Convert RANGE variables to LOCAL even if verbatim block exist")->ignore_case(); + passes_opt->add_flag("--local-rename", local_rename, "Rename LOCAL variable if variable of same name exist in global scope")->ignore_case(); + passes_opt->add_flag("--verbatim-inline", verbatim_inline, "Inline even if verbatim block exist")->ignore_case(); + passes_opt->add_flag("--verbatim-rename", verbatim_rename, "Rename variables in verbatim block")->ignore_case(); + passes_opt->add_flag("--json-ast", json_ast, "Write AST to JSON file")->ignore_case(); + passes_opt->add_flag("--nmodl-ast", nmodl_ast, "Write AST to NMODL file")->ignore_case(); + passes_opt->add_flag("--json-perf", json_perfstat, "Write performance statistics to JSON file")->ignore_case(); + passes_opt->add_flag("--show-symtab", show_symtab, "Write symbol table to stdout")->ignore_case(); + + auto codegen_opt = app.add_subcommand("codegen", "Code generation options")->ignore_case(); + codegen_opt->add_option("--layout", layout, "Memory layout for code generation", true)->ignore_case()->check(CLI::IsMember({"aos", "soa"})); + codegen_opt->add_option("--datatype", layout, "Data type for floating point variables", true)->ignore_case()->check(CLI::IsMember({"float", "double"})); + // clang-format on + + CLI11_PARSE(app, argc, argv); + + make_path(output_dir); + make_path(scratch_dir); + + if (sympy_opt) { pybind11::initialize_interpreter(); } - for (auto& nmodl_file: arg.nmodl_files) { - std::ifstream file(nmodl_file); - - if (!file.good()) { - logger->warn("Could not open file : {}", nmodl_file); - error_count++; - continue; + /// write ast to nmodl + auto ast_to_nmodl = [&](ast::Program* ast, const std::string& filepath) { + if (nmodl_ast) { + NmodlPrintVisitor v(filepath); + v.visit_program(ast); + logger->info("AST to NMODL transformation written to {}", filepath); } + }; - logger->info("Processing {}", nmodl_file); + for (const auto& file: mod_files) { + logger->info("Processing {}", file); - std::string mod_file = remove_extension(base_name(nmodl_file)); + auto modfile = remove_extension(base_name(file)); + + /// create file path for nmodl file + auto filepath = [&](std::string suffix) { + static int count = 0; + return scratch_dir + "/" + modfile + "." + std::to_string(count++) + "." + suffix + + ".mod"; + }; /// driver object creates lexer and parser, just call parser method - nmodl::parser::NmodlDriver driver; - driver.parse_file(nmodl_file); + NmodlDriver driver; + driver.parse_file(file); - /// shared_ptr to ast constructed from parsing nmodl file + /// parse mod file and construct ast auto ast = driver.ast(); + /// just visit the astt { AstVisitor v; v.visit_program(ast.get()); } + /// construct symbol table { + logger->info("Running symtab visitor"); SymtabVisitor v(false); v.visit_program(ast.get()); } - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.mod"); - } - - if (arg.verbatim_rename) { - VerbatimVarRenameVisitor v; - v.visit_program(ast.get()); - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.verbrename.mod"); - } + if (show_symtab) { + logger->info("Printing symbol table"); + std::stringstream stream; + auto symtab = ast->get_model_symbol_table(); + symtab->print(stream); + std::cout << stream.str(); } - if (arg.sympy) { - SympyConductanceVisitor v; - v.visit_program(ast.get()); - { - SymtabVisitor v(false); - v.visit_program(ast.get()); - } - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), - arg.scratch_dir + "/" + mod_file + ".nmodl.conductance.mod"); - } - } + ast_to_nmodl(ast.get(), filepath("ast")); - if (arg.sympy) { - SympySolverVisitor v(arg.pade_approx); + if (json_ast) { + logger->info("Writing AST into {}", file); + auto file = scratch_dir + "/" + modfile + ".ast.json"; + JSONVisitor v(file); v.visit_program(ast.get()); } - { - CnexpSolveVisitor v; + if (verbatim_rename) { + logger->info("Running verbatim rename visitor"); + VerbatimVarRenameVisitor v; v.visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("verbatim_rename")); } - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.cnexp.mod"); + if (sympy_conductance) { + logger->info("Running sympy conductance visitor"); + SympyConductanceVisitor v1; + SymtabVisitor v2(false); + v1.visit_program(ast.get()); + v2.visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("sympy_conductance")); } - if (arg.inlining) { - InlineVisitor v; + if (sympy_analytic) { + logger->info("Running sympy solve visitor"); + SympySolverVisitor v(sympy_pade); v.visit_program(ast.get()); - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.in.mod"); - } + ast_to_nmodl(ast.get(), filepath("sympy_solve")); } - if (arg.local_rename) { - LocalVarRenameVisitor v; + { + logger->info("Running cnexp visitor"); + CnexpSolveVisitor v; v.visit_program(ast.get()); - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.locrename.mod"); - } + ast_to_nmodl(ast.get(), filepath("cnexp")); } - { - SymtabVisitor v(true); + if (nmodl_inline) { + logger->info("Running nmodl inline visitor"); + InlineVisitor v; v.visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("inline")); } - if (arg.localize) { - // localize pass must be followed by renaming in order to avoid conflict - // with global scope variables - LocalizeVisitor v1(arg.localize_with_verbatim); + if (local_rename) { + logger->info("Running local variable rename visitor"); + LocalVarRenameVisitor v1; + SymtabVisitor v2(true); v1.visit_program(ast.get()); - LocalVarRenameVisitor v2; v2.visit_program(ast.get()); - if (arg.ast_to_nmodl) { - ast_to_nmodl(ast.get(), arg.scratch_dir + "/" + mod_file + ".nmodl.localize.mod"); - } + ast_to_nmodl(ast.get(), filepath("local_rename")); } - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - if (arg.perf_stats) { - PerfVisitor v(arg.scratch_dir + "/" + mod_file + ".perf.json"); - logger->info("Dumping performance statistics into JSON format"); - v.visit_program(ast.get()); + if (localize) { + // localize pass must follow rename pass to avoid conflict + logger->info("Running localize visitor"); + LocalizeVisitor v1(localize_verbatim); + LocalVarRenameVisitor v2; + SymtabVisitor v3(true); + v1.visit_program(ast.get()); + v2.visit_program(ast.get()); + v3.visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("localize")); } - if (arg.ast_to_json) { - JSONVisitor v(arg.scratch_dir + "/" + mod_file + ".ast.json"); - logger->info("Dumping AST state into JSON format"); + if (json_perfstat) { + auto file = scratch_dir + "/" + modfile + ".perf.json"; + logger->info("Writing performance statistics to {}", file); + PerfVisitor v(file); v.visit_program(ast.get()); } - if (arg.show_symtab) { - logger->info("Printing symbol table"); - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - } - { // make sure to run perf visitor because code generator // looks for read/write counts const/non-const declaration PerfVisitor v; v.visit_program(ast.get()); + } - auto layout = arg.aos_memory_layout() ? LayoutType::aos : LayoutType::soa; - - logger->info("Generating host code with {} backend", arg.host_backend); + { + auto mem_layout = layout == "aos" ? codegen::LayoutType::aos : codegen::LayoutType::soa; - if (arg.host_c_backend()) { - CodegenCVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); + if (c_backend) { + logger->info("Running C backend code generator"); + CodegenCVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); - } else if (arg.host_omp_backend()) { - nmodl::codegen::CodegenOmpVisitor visitor(mod_file, arg.output_dir, layout, - arg.dtype); + } + if (omp_backend) { + logger->info("Running OpenMP backend code generator"); + CodegenOmpVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); - } else if (arg.host_acc_backend()) { - CodegenAccVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); + } + if (oacc_backend) { + logger->info("Running OpenACC backend code generator"); + CodegenAccVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); } - if (arg.device_cuda_backend()) { - logger->info("Generating device code with {} backend", arg.accel_backend); - CodegenCudaVisitor visitor(mod_file, arg.output_dir, layout, arg.dtype); + if (cuda_backend) { + logger->info("Running CUDA backend code generator"); + CodegenCudaVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); } } } - if (arg.sympy) { + if (sympy_opt) { pybind11::finalize_interpreter(); } - - if (error_count != 0) { - logger->error("Code generation encountered {} errors", error_count); - } else { - logger->info("Code generation finished successfully"); - } - return error_count; } diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 7634d1a947..c5d0dacf75 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -7,8 +7,8 @@ add_executable(nmodl_parser main_nmodl.cpp) add_executable(c_parser main_c.cpp) -target_link_libraries(nmodl_parser lexer) -target_link_libraries(c_parser lexer) +target_link_libraries(nmodl_parser lexer util) +target_link_libraries(c_parser lexer util) # ============================================================================= # Install executable diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index fb3b72d2e6..5e2fe29ccb 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -6,54 +6,36 @@ *************************************************************************/ #include <fstream> -#include <iostream> +#include "CLI/CLI.hpp" #include "parser/c11_driver.hpp" -#include "tclap/CmdLine.h" +#include "utils/logger.hpp" /** - * Standlone parser program for C. This demonstrate basic - * usage of psrser and driver class. + * Standalone parser program for C. This demonstrate basic + * usage of parser and driver class. */ int main(int argc, const char* argv[]) { - try { - TCLAP::CmdLine cmd("C Parser: Standalone parser program for C"); - TCLAP::ValueArg<std::string> filearg("", "file", "C input file path", false, "", "string"); + CLI::App app{"C-Parser : Standalone Parser for C Code"}; - cmd.add(filearg); - cmd.parse(argc, argv); + std::vector<std::string> files; + app.add_option("file", files, "One or more C files to process") + ->required() + ->check(CLI::ExistingFile); - std::string filename = filearg.getValue(); + CLI11_PARSE(app, argc, argv); - if (filename.empty()) { - std::cerr << "Error : Pass input C file, see --help" << std::endl; - return 1; - } + for (const auto& f: files) { + nmodl::logger->info("Processing {}", f); + std::ifstream file(f); - std::ifstream file(filename); - - if (!file) { - throw std::runtime_error("Could not open file " + filename); - } - - std::cout << "\n C Parser : Processing file : " << filename << std::endl; - - std::istream& in(file); - - /// driver object creates lexer and parser, just call parser method + /// driver object creates lexer and parser nmodl::parser::CDriver driver; - driver.set_verbose(true); - driver.parse_stream(in); - - // driver.parse_file(filename); - std::cout << "----PARSING FINISHED----" << std::endl; - } catch (TCLAP::ArgException& e) { - std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; - return 1; + /// just call parser method + driver.parse_stream(file); } - return 0; } diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 1205bef734..1b876e60cc 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -5,53 +5,31 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include <fstream> -#include <iostream> +#include "CLI/CLI.hpp" #include "parser/nmodl_driver.hpp" -#include "tclap/CmdLine.h" +#include "utils/logger.hpp" /** - * Standlone parser program for NMODL. This demonstrate basic - * usage of psrser and driver class. - * - * \todo Parser create ast during parse actions. We need to - * hook up json/yaml writer in this example to dump ast. + * Standalone parser program for NMODL. This demonstrate + * basic usage of parser and driver classes. */ int main(int argc, const char* argv[]) { - try { - TCLAP::CmdLine cmd("NMODL Parser: Standalone parser program for NMODL"); - TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, - "../test/input/channel.mod", "string"); - - cmd.add(filearg); - cmd.parse(argc, argv); - - std::string filename = filearg.getValue(); - std::ifstream file(filename); - - if (!file) { - throw std::runtime_error("Could not open file " + filename); - } + CLI::App app{"NMODL-Parser : Standalone Parser for NMODL"}; - std::cout << "\n NMODL Parser : Processing file : " << filename << std::endl; + std::vector<std::string> files; + app.add_option("file", files, "One or more MOD files to process") + ->required() + ->check(CLI::ExistingFile); - std::istream& in(file); + CLI11_PARSE(app, argc, argv); - /// driver object creates lexer and parser, just call parser method + for (const auto& f: files) { + nmodl::logger->info("Processing {}", f); nmodl::parser::NmodlDriver driver; - driver.set_verbose(true); - driver.parse_stream(in); - - // driver.parse_file(filename); - - std::cout << "----PARSING FINISHED----" << std::endl; - } catch (TCLAP::ArgException& e) { - std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; - return 1; + driver.parse_file(f); } - return 0; -} +} \ No newline at end of file diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index c0ce11565d..4bed8b82d7 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -5,11 +5,11 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include <fstream> -#include <iostream> #include <sstream> +#include "CLI/CLI.hpp" #include "parser/nmodl_driver.hpp" +#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/inline_visitor.hpp" @@ -22,40 +22,32 @@ #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" -#include "tclap/CmdLine.h" - using namespace nmodl; -using namespace symtab; /** * Standalone visitor program for NMODL. This demonstrate basic - * usage of different visitors classes and driver class. + * usage of different visitor and driver classes. **/ int main(int argc, const char* argv[]) { - try { - TCLAP::CmdLine cmd("NMODL Visitor: Standalone visitor program for NMODL"); - TCLAP::ValueArg<std::string> filearg("", "file", "NMODL input file path", false, - "../test/input/channel.mod", "string"); - TCLAP::SwitchArg verbosearg("", "verbose", "Enable verbose output", false); - - cmd.add(filearg); - cmd.add(verbosearg); + CLI::App app{"NMODL Visitor : Runs standalone visitor classes"}; - cmd.parse(argc, argv); + bool verbose = false; + std::vector<std::string> files; - std::string filename = filearg.getValue(); - bool verbose = verbosearg.getValue(); + app.add_flag("-v,--verbose", verbose, "Show intermediate output"); + app.add_option("-f,--file,file", files, "One or more MOD files to process") + ->required() + ->check(CLI::ExistingFile); - std::ifstream file(filename); + CLI11_PARSE(app, argc, argv); - if (!file.good()) { - throw std::runtime_error("Could not open file " + filename); - } + for (const auto& filename: files) { + nmodl::logger->info("Processing {}", filename); std::string mod_filename = remove_extension(base_name(filename)); - /// driver object creates lexer and parser, just call parser method + /// driver object that creates lexer and parser nmodl::parser::NmodlDriver driver; driver.parse_file(filename); @@ -201,12 +193,6 @@ int main(int argc, const char* argv[]) { std::cout << ss.str() << std::endl; std::cout << "----PERF VISITOR FINISHED----" << std::endl; } - - - } catch (TCLAP::ArgException& e) { - std::cout << "Argument Error: " << e.error() << " for arg " << e.argId() << std::endl; - return 1; } - return 0; } From e96b52ad42a85b2fde6363ab5534dca0bce6d1c0 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Tue, 5 Mar 2019 22:22:44 +0100 Subject: [PATCH 151/871] Add inline euler solver method (BlueBrain/nmodl#25) - add inline euler solution to SympySolver - also add to CnexpSolve for when sympy not enabled - remove euler derivative kernel from codegen - remove euler_thread callback from codegen - remove extra Dx statements which are not used in coreneuron - update unit tests to match new behaviour NMODL Repo SHA: BlueBrain/nmodl@d6c6c4285b4d3f96d1a685909392124ffaa7a112 --- nmodl/ode.py | 48 +++++++++++++- src/nmodl/codegen/codegen_c_visitor.cpp | 33 ---------- src/nmodl/codegen/codegen_c_visitor.hpp | 4 -- src/nmodl/parser/diffeq_context.cpp | 13 +++- src/nmodl/parser/diffeq_context.hpp | 3 + src/nmodl/visitors/cnexp_solve_visitor.cpp | 11 +++- src/nmodl/visitors/sympy_solver_visitor.cpp | 63 +++++++++++++------ src/nmodl/visitors/sympy_solver_visitor.hpp | 15 +++-- .../transpiler/utils/nmodl_constructs.cpp | 18 +++--- test/nmodl/transpiler/visitor/visitor.cpp | 10 +-- 10 files changed, 138 insertions(+), 80 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index ed023e25b0..d2c0cac4c3 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -26,8 +26,8 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): t_var: name of time variable in NEURON dt_var: name of dt variable in NEURON vars: set of variables used in expression, e.g. {"x", "a"} - ues_pade_approx][]: if False: return exact solution - if True: return (1,1) Pade approx to solution + uses_pade_approx: if False: return exact solution + if True: return (1,1) Pade approx to solution correct to second order in dt_var Returns: @@ -177,3 +177,47 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): # return result as C code in NEURON format return sp.ccode(diff) + + +def forwards_euler2c(diff_string, dt_var, vars): + """Return forwards euler solution of diff_string as C code. + + Derivative should be of the form "x' = f(x)", + and vars should contain the set of all the variables + referenced by f(x), for example: + -forwards_euler2c("x' = a*x", "a") + -forwards_euler2c("x' = a + b*x - sin(3.2)", {"a","b"}) + + Args: + diff_string: Derivative to be integrated e.g. "x' = a*x" + dt_var: name of dt variable in NEURON + vars: set of variables used in expression, e.g. {"x", "a"} + + Returns: + String containing forwards Euler timestep as C code + + Raises: + ImportError: if SymPy version is too old (<1.2) + """ + + # every symbol (a.k.a variable) that SymPy + # is going to manipulate needs to be declared + # explicitly + sympy_vars = {} + vars = set(vars) + dependent_var = diff_string.split("=")[0].split("'")[0].strip() + x = sp.symbols(dependent_var, real=True) + vars.discard(dependent_var) + # declare all other supplied variables + sympy_vars = {var: sp.symbols(var, real=True) for var in vars} + sympy_vars[dependent_var] = x + + # parse string into SymPy equation + diffeq_rhs = sp.sympify(diff_string.split("=", 1)[1], locals=sympy_vars) + + # forwards Euler solution is x + dx/dt * dt + dt = sp.symbols(dt_var, real=True, positive=True) + solution = (x + diffeq_rhs * dt).simplify().evalf() + + # return result as C code in NEURON format + return f"{sp.ccode(x)} = {sp.ccode(solution)}" diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 3f80912b09..357a2d4862 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3448,29 +3448,6 @@ void CodegenCVisitor::print_net_receive() { } -/** - * When euler method is used then derivative block is printed as separate function - * which will be used for the callback from euler_thread function. Otherwise, derivative - * block is printed as part of nrn_state itself. - */ -void CodegenCVisitor::print_derivative_kernel_for_euler() { - assert(info.solve_node->is_derivative_block()); - auto node = info.solve_node; - codegen = true; - auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); - auto arguments = external_method_parameters(); - - printer->add_newline(2); - printer->add_line("/* _euler_ state _{} */"_format(info.mod_suffix)); - printer->start_block("int {}_{}({})"_format(node->get_node_name(), info.mod_suffix, arguments)); - printer->add_line(instance); - print_statement_block(node->get_statement_block().get(), false, false); - printer->add_line("return 0;"); - printer->end_block(1); - codegen = false; -} - - /** * Todo: data is not derived. Need to add instance into instance struct? * data used here is wrong in AoS because as in original implementation, @@ -3548,10 +3525,6 @@ void CodegenCVisitor::print_nrn_state() { } codegen = true; - if (info.euler_used) { - print_derivative_kernel_for_euler(); - } - if (info.derivimplicit_used) { print_derivative_kernel_for_derivimplicit(); } @@ -3588,12 +3561,6 @@ void CodegenCVisitor::print_nrn_state() { ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); auto statement = "derivimplicit_thread({});"_format(args); printer->add_line(statement); - } else if (info.euler_used) { - auto args = - "{}, {}, {}, _euler_{}_{}, {}" - ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); - auto statement = "euler_thread({});"_format(args); - printer->add_line(statement); } else { if (info.solve_node != nullptr) { auto block = info.solve_node->get_statement_block(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 5fb0fda374..4b62dda306 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -794,10 +794,6 @@ class CodegenCVisitor: public AstVisitor { void print_net_receive(); - /// derivative kernel when euler method is used - void print_derivative_kernel_for_euler(); - - /// derivative kernel when derivimplicit method is used void print_derivative_kernel_for_derivimplicit(); diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index aa0029f7eb..7361b781f0 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -136,6 +136,14 @@ std::string DiffEqContext::get_cnexp_solution() { } +/** + * Return solution for euler method + */ +std::string DiffEqContext::get_euler_solution() { + return state + " = " + state + "+dt*(" + rhs + ")"; +} + + /** * Return solution for non-cnexp method */ @@ -161,7 +169,10 @@ std::string DiffEqContext::get_non_cnexp_solution() { */ std::string DiffEqContext::get_solution(bool& cnexp_possible) { std::string solution; - if (method == "cnexp" && !(deriv_invalid && eqn_invalid)) { + if (method == "euler") { + cnexp_possible = false; + solution = get_euler_solution(); + } else if (method == "cnexp" && !(deriv_invalid && eqn_invalid)) { cnexp_possible = true; solution = get_cnexp_solution(); } else { diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index bb97147c79..2ec179bacb 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -86,6 +86,9 @@ class DiffEqContext { /// return solution for cnexp method std::string get_cnexp_solution(); + /// return solution for euler method + std::string get_euler_solution(); + /// return solution for non-cnexp method std::string get_non_cnexp_solution(); diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index a5c62ad27d..8a3b62f10a 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -7,6 +7,7 @@ #include <sstream> +#include "codegen/codegen_naming.hpp" #include "parser/diffeq_driver.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" @@ -66,7 +67,15 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { } else { std::cerr << "cnexp solver not possible"; } - } else if (solve_method == derivimplicit_method || solve_method == euler_method) { + } else if (solve_method == euler_method) { + std::string solution = diffeq_driver.solve(equation, solve_method); + auto statement = create_statement(solution); + auto expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement); + auto bin_expr = std::dynamic_pointer_cast<ast::BinaryExpression>( + expr_statement->get_expression()); + lhs.reset(bin_expr->lhs->clone()); + rhs.reset(bin_expr->rhs->clone()); + } else if (solve_method == derivimplicit_method) { auto varname = "D" + name->get_node_name(); auto variable = new ast::Name(new ast::String(varname)); lhs.reset(variable); diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index b30e636e80..e02b2c7bb9 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -29,10 +29,11 @@ void SympySolverVisitor::visit_solve_block(ast::SolveBlock* node) { } void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { - if (solve_method != cnexp_method) { - logger->warn( - "SympySolverVisitor: solve method not cnexp, so not integrating " - "expression analytically"); + if ((solve_method != cnexp_method) && (solve_method != euler_method)) { + logger->debug( + "SympySolverVisitor :: solve method \"{}\" not {} or {}, so not integrating " + "expression analytically", + solve_method, cnexp_method, euler_method); return; } @@ -40,34 +41,56 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { auto& rhs = node->get_expression()->rhs; if (!lhs->is_var_name()) { - logger->warn("SympySolverVisitor: LHS of differential equation is not a VariableName"); + logger->warn("SympySolverVisitor :: LHS of differential equation is not a VariableName"); return; } auto lhs_name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); if (!lhs_name->is_prime_name()) { - logger->warn("SympySolverVisitor: LHS of differential equation is not a PrimeName"); + logger->warn("SympySolverVisitor :: LHS of differential equation is not a PrimeName"); return; } auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, "use_pade_approx"_a = use_pade_approx); - py::exec(R"( - from nmodl.ode import integrate2c - exception_message = "" - try: - solution = integrate2c(equation_string, t_var, dt_var, vars, use_pade_approx) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), locals); - + if (solve_method == euler_method) { + logger->debug("SympySolverVisitor :: EULER - solving: {}", nmodl::to_nmodl(node)); + // replace x' = f(x) differential equation + // with forwards Euler timestep: + // x = x + f(x) * dt + py::exec(R"( + from nmodl.ode import forwards_euler2c + exception_message = "" + try: + solution = forwards_euler2c(equation_string, dt_var, vars) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), locals); + } else if (solve_method == cnexp_method) { + // replace x' = f(x) differential equation + // with analytic solution for x(t+dt) in terms of x(t) + // x = ... + logger->debug("SympySolverVisitor :: CNEXP - solving: {}", nmodl::to_nmodl(node)); + py::exec(R"( + from nmodl.ode import integrate2c + exception_message = "" + try: + solution = integrate2c(equation_string, t_var, dt_var, vars, use_pade_approx) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), locals); + } auto solution = locals["solution"].cast<std::string>(); + logger->debug("SympySolverVisitor :: -> solution: {}", solution); auto exception_message = locals["exception_message"].cast<std::string>(); if (!exception_message.empty()) { - logger->warn("SympySolverVisitor: python exception: " + exception_message); + logger->warn("SympySolverVisitor :: python exception: " + exception_message); } if (!solution.empty()) { auto statement = create_statement(solution); @@ -77,7 +100,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { - logger->warn("SympySolverVisitor: analytic solution to differential equation not possible"); + logger->warn("SympySolverVisitor :: solution to differential equation not possible"); } } diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 82f9eab0bc..12d40ec2d3 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -22,11 +22,15 @@ namespace nmodl { * \class SympySolverVisitor * \brief Visitor for differential equations in derivative block * - * When solver method is "cnexp", this class replaces the - * differential equations with their analytic solution using SymPy, - * i.e. it duplicates the functionality of CnexpSolveVisitor + * For solver method "cnexp": + * replace each the differential equation with its analytic + * solution using SymPy, optionally using the (1,1) order in dt + * Pade approximant to the solution. + * + * For solver method "euler": + * replace each the differential equation with a + * forwards Euler timestep * - * It will soon also deal with other solver methods. */ class SympySolverVisitor: public AstVisitor { @@ -36,8 +40,9 @@ class SympySolverVisitor: public AstVisitor { std::string solve_method; /// solver method names + const std::string euler_method = "euler"; const std::string cnexp_method = "cnexp"; - /// optionally replace solution with (1,1) pade approx + /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; public: diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 39f6f73e6b..6359b9218d 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -1583,7 +1583,7 @@ std::vector<DiffEqTestCase> diff_eq_constructs{ { "GluSynapse.mod", "A_AMPA' = A_AMPA*A_AMPA", - "DA_AMPA = DA_AMPA/(1.0-dt*(((1.0)*(A_AMPA)+(A_AMPA)*(1.0))))", + "A_AMPA = A_AMPA+dt*(A_AMPA*A_AMPA)", "euler" }, @@ -1640,42 +1640,42 @@ std::vector<DiffEqTestCase> diff_eq_constructs{ { "GluSynapse.mod", "A_AMPA' = -A_AMPA/tau_r_AMPA", - "DA_AMPA = DA_AMPA/(1.0-dt*((-1.0)/tau_r_AMPA))", + "A_AMPA = A_AMPA+dt*(-A_AMPA/tau_r_AMPA)", "euler" }, { "GluSynapse.mod", "m_VDCC' = (minf_VDCC-m_VDCC)/mtau_VDCC", - "Dm_VDCC = Dm_VDCC/(1.0-dt*((((-1.0)))/mtau_VDCC))", + "m_VDCC = m_VDCC+dt*((minf_VDCC-m_VDCC)/mtau_VDCC)", "euler" }, { "GluSynapse.mod", "cai_CR' = -(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR", - "Dcai_CR = Dcai_CR/(1.0-dt*((-((1.0))/tau_ca_CR)))", + "cai_CR = cai_CR+dt*(-(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR)", "euler" }, { "GluSynapse.mod", "effcai_GB' = -0.005*effcai_GB + (cai_CR - min_ca_CR)", - "Deffcai_GB = Deffcai_GB/(1.0-dt*((-0.005)*(1.0)))", + "effcai_GB = effcai_GB+dt*(-0.005*effcai_GB + (cai_CR - min_ca_CR))", "euler" }, { "GluSynapse.mod", "Rho_GB' = ( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB)", - "DRho_GB = DRho_GB/(1.0-dt*(((((((-1.0)*((1-Rho_GB))+(-Rho_GB)*(((-1.0)))))*((rho_star_GB-Rho_GB))+(-Rho_GB*(1-Rho_GB))*(((-1.0))))+(potentiate_GB*gamma_p_GB)*(((-1.0)))-(depress_GB*gamma_d_GB)*(1.0)))/((1e3)*tau_GB)))", + "Rho_GB = Rho_GB+dt*(( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB))", "euler" }, { "GluSynapse.mod", "Use_GB' = (Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB)", - "DUse_GB = DUse_GB/(1.0-dt*((((-1.0)))/((1e3)*tau_Use_GB)))", + "Use_GB = Use_GB+dt*((Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB))", "euler" }, @@ -1735,14 +1735,14 @@ std::vector<DiffEqTestCase> diff_eq_constructs{ { "syn_bip_gan.mod", "s' = (s_inf-s)/((1-s_inf)*tau*s)", - "Ds = Ds/(1.0-dt*((((s_inf-(s+0.001))/((1-s_inf)*tau*(s+0.001)))-((s_inf-s)/((1-s_inf)*tau*s)))/0.001))", + "s = s+dt*((s_inf-s)/((1-s_inf)*tau*s))", "euler" }, { "syn_rod_bip.mod", "s' = (s_inf-s)/((1-s_inf)*tau*s)", - "Ds = Ds/(1.0-dt*((((s_inf-(s+0.001))/((1-s_inf)*tau*(s+0.001)))-((s_inf-s)/((1-s_inf)*tau*s)))/0.001))", + "s = s+dt*((s_inf-s)/((1-s_inf)*tau*s))", "euler" }, diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index b7d1fbc449..63c33226de 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2037,10 +2037,10 @@ SCENARIO("SympySolver visitor", "[sympy]") { REQUIRE(result.empty()); } } - GIVEN("Derivative block with ODES, solver method is not cnexp") { + GIVEN("Derivative block with ODES, solver method is euler") { std::string nmodl_text = R"( BREAKPOINT { - SOLVE states METHOD derivimplicit + SOLVE states METHOD euler } DERIVATIVE states { m' = (mInf-m)/mTau @@ -2049,11 +2049,11 @@ SCENARIO("SympySolver visitor", "[sympy]") { } )"; - THEN("Solver method is not cnexp - do nothing") { + THEN("Construct forwards Euler solutions") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); - REQUIRE(result[0] == "m' = (mInf-m)/mTau"); - REQUIRE(result[1] == "h' = (hInf-h)/hTau"); + REQUIRE(result[0] == "m = (-dt*(m-mInf)+m*mTau)/mTau"); + REQUIRE(result[1] == "h = (-dt*(h-hInf)+h*hTau)/hTau"); } } GIVEN("Derivative block with linear ODES, solver method cnexp") { From a8c4d22e921fc96e0394c500b835a0b460f3ad94 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 6 Mar 2019 10:08:06 +0100 Subject: [PATCH 152/871] Safety mechanism for SympyConductance visitor (BlueBrain/nmodl#31) - if breakpoint block contains if-elseif-else block then do not insert conductance statement - add constructor in lookup visitor to accept vector of ast types Resolves BlueBrain/nmodl#29 NMODL Repo SHA: BlueBrain/nmodl@a6d48d2729065719fb02834757cad997330e83a9 --- .../language/templates/lookup_visitor.hpp | 2 ++ .../visitors/sympy_conductance_visitor.cpp | 31 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index 0b510562de..03f8aaa9b3 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -34,6 +34,8 @@ class AstLookupVisitor : public Visitor { types.push_back(type); } + AstLookupVisitor(std::vector<ast::AstNodeType> types) : types(types) {} + std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, ast::AstNodeType type); std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 34524d8097..40a3ad02a6 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -10,8 +10,9 @@ #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitor_utils.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace py = pybind11; @@ -23,6 +24,28 @@ using ast::AstNodeType; using ast::BinaryOp; using symtab::syminfo::NmodlType; + +/** + * Analyse breakpoint block to check if it is safe to insert CONDUCTANCE statements + * + * Most of the mod files have simple breakpoint blocks without any control flow + * statements. SympyConductanceVisitor just collects all the statements in the + * breakpoint block and insert conductance statements. If there are control flow + * statements like `IF { a } ELSE { b }` block with conflicting current statements + * inside IF and ELSE blocks or VERBATIM block then the resulting CONDUCTANCE + * statements may be incorrect. For now the simple approach is to not generate + * CONDUCTANCE statements if if-else statements exist in the block. + * + * @param node Ast node for breakpoint block + * @return true if it is safe to insert conductance statements otherwise false + */ +static bool conductance_statement_possible(ast::BreakpointBlock* node) { + AstLookupVisitor v({AstNodeType::IF_STATEMENT, AstNodeType::VERBATIM}); + node->accept(&v); + return v.get_nodes().empty(); +} + + // Generate statement strings to be added to BREAKPOINT section std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( ast::BreakpointBlock* node) { @@ -164,6 +187,12 @@ void SympyConductanceVisitor::visit_conductance_hint(ast::ConductanceHint* node) }; void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { + // return if it's not safe to insert conductance statements + if (!conductance_statement_possible(node)) { + logger->warn("SympyConductance :: Unsafe to insert CONDUCTANCE statement"); + return; + } + // add any breakpoint local variables to vars if (auto* symtab = node->get_statement_block()->get_symbol_table()) { for (const auto& localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { From 95cbb87120c03d064d488f7d8751263884a0dae7 Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Wed, 6 Mar 2019 12:44:11 +0100 Subject: [PATCH 153/871] Reduce number of object copies in AstLookupVisitor (BlueBrain/nmodl#33) - use list initializer in constructor - pass `std::vector` parameter in constructor as const reference - `AstLookupVisitor::get_nodes` now returns a const reference instead of a copy. NMODL Repo SHA: BlueBrain/nmodl@e0c9ea499410728ee1414514960e2da805d3eed5 --- src/nmodl/language/templates/lookup_visitor.hpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index 03f8aaa9b3..a76559f369 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -30,17 +30,15 @@ class AstLookupVisitor : public Visitor { AstLookupVisitor() = default; - AstLookupVisitor(ast::AstNodeType type) { - types.push_back(type); - } + AstLookupVisitor(ast::AstNodeType type) : types{type} {} - AstLookupVisitor(std::vector<ast::AstNodeType> types) : types(types) {} + AstLookupVisitor(const std::vector<ast::AstNodeType>& types) : types(types) {} std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, ast::AstNodeType type); std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); - std::vector<std::shared_ptr<ast::AST>> get_nodes() { + const std::vector<std::shared_ptr<ast::AST>>& get_nodes() const noexcept { return nodes; } From ce6f5c8a0054dcefe151eed49a5391f5c6eccc43 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 6 Mar 2019 16:43:34 +0100 Subject: [PATCH 154/871] Adapt to_nmodl for sympy passes (BlueBrain/nmodl#32) - to_nmodl now accept ast node types that can be ignored - update sympy passes to ignore units in the ast - add test Resolves BlueBrain/nmodl#28 NMODL Repo SHA: BlueBrain/nmodl@69ef5a20f32014671b05ecf909e389c62c95af85 --- .../language/templates/nmodl_visitor.cpp | 3 ++ .../language/templates/nmodl_visitor.hpp | 14 ++++++++ src/nmodl/pybind/pynmodl.cpp | 5 ++- .../visitors/sympy_conductance_visitor.cpp | 6 ++-- .../visitors/sympy_conductance_visitor.hpp | 6 ++++ src/nmodl/visitors/sympy_solver_visitor.cpp | 18 ++++++---- src/nmodl/visitors/sympy_solver_visitor.hpp | 5 +++ src/nmodl/visitors/visitor_utils.cpp | 5 ++- src/nmodl/visitors/visitor_utils.hpp | 2 +- test/nmodl/transpiler/visitor/visitor.cpp | 34 +++++++++++++++++++ 10 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index 74a5e938ca..37cdbba771 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -76,6 +76,9 @@ using namespace ast; {%- for node in nodes %} void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}* node) { + if (is_exclude_type(node->get_node_type())) { + return; + } {{ add_element(node.nmodl_name) -}} {% call guard(node.prefix, node.suffix) -%} {% if node.is_block_node %} diff --git a/src/nmodl/language/templates/nmodl_visitor.hpp b/src/nmodl/language/templates/nmodl_visitor.hpp index 05d7718e4f..2f11392dc5 100644 --- a/src/nmodl/language/templates/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/nmodl_visitor.hpp @@ -7,6 +7,8 @@ #pragma once +#include <set> + #include "ast/ast.hpp" #include "printer/nmodl_printer.hpp" @@ -17,10 +19,22 @@ class NmodlPrintVisitor : public Visitor { private: std::unique_ptr<NMODLPrinter> printer; + + /// node types to exclude while printing + std::set<ast::AstNodeType> exclude_types; + + /// check if node is to be excluded while printing + bool is_exclude_type(ast::AstNodeType type) const { + return exclude_types.find(type) != exclude_types.end(); + } + public: NmodlPrintVisitor() : printer(new NMODLPrinter()) {} NmodlPrintVisitor(std::string filename) : printer(new NMODLPrinter(filename)) {} NmodlPrintVisitor(std::ostream& stream) : printer(new NMODLPrinter(stream)) {} + NmodlPrintVisitor(std::ostream& stream, const std::set<ast::AstNodeType>& types) + : printer(new NMODLPrinter(stream)), + exclude_types(types) {} {% for node in nodes %} virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 3f9c4821a5..6a01c1f778 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include <memory> +#include <set> + #include <pybind11/iostream.h> #include <pybind11/pybind11.h> #include <pybind11/stl.h> @@ -49,7 +51,8 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { .def("parse_stream", &PyDriver::parse_stream) .def("ast", &PyDriver::ast); - m_nmodl.def("to_nmodl", nmodl::to_nmodl); + m_nmodl.def("to_nmodl", nmodl::to_nmodl, py::arg("node"), + py::arg("exclude_types") = std::set<nmodl::ast::AstNodeType>()); m_nmodl.def("to_json", nmodl::to_json, py::arg("node"), py::arg("compact") = false, py::arg("expand") = false); diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 40a3ad02a6..307b431ead 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -130,7 +130,7 @@ void SympyConductanceVisitor::visit_binary_expression(ast::BinaryExpression* nod auto lhs_str = std::dynamic_pointer_cast<ast::VarName>(node->lhs)->get_name()->get_node_name(); binary_expr_index[lhs_str] = ordered_binary_exprs.size(); - ordered_binary_exprs.push_back(nmodl::to_nmodl(node)); + ordered_binary_exprs.push_back(to_nmodl_for_sympy(node)); ordered_binary_exprs_lhs.push_back(lhs_str); } } @@ -157,7 +157,7 @@ void SympyConductanceVisitor::lookup_useion_statements() { for (const auto& useion_ast: use_ion_nodes) { auto ion = std::dynamic_pointer_cast<ast::Useion>(useion_ast).get(); std::string ion_name = ion->get_node_name(); - logger->debug("SympyConductance :: Found USEION statement {}", nmodl::to_nmodl(ion)); + logger->debug("SympyConductance :: Found USEION statement {}", to_nmodl_for_sympy(ion)); if (i_ignore.find(ion_name) != i_ignore.end()) { logger->debug("SympyConductance :: -> Ignoring ion current name: {}", ion_name); } else { @@ -176,7 +176,7 @@ void SympyConductanceVisitor::visit_conductance_hint(ast::ConductanceHint* node) // find existing CONDUCTANCE statements - do not want to alter them // so keep a set of ion names i_ignore that we should ignore later logger->debug("SympyConductance :: Found existing CONDUCTANCE statement: {}", - nmodl::to_nmodl(node)); + to_nmodl_for_sympy(node)); if (auto ion = node->get_ion()) { logger->debug("SympyConductance :: -> Ignoring ion current name: {}", ion->get_node_name()); i_ignore.insert(ion->get_node_name()); diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index fa092d21bd..eb3727ce50 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -18,6 +18,8 @@ #include "symtab/symbol.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/lookup_visitor.hpp" +#include "visitors/visitor_utils.hpp" + namespace nmodl { @@ -82,6 +84,10 @@ class SympyConductanceVisitor: public AstVisitor { void lookup_useion_statements(); void lookup_nonspecific_statements(); + static std::string to_nmodl_for_sympy(ast::AST* node) { + return to_nmodl(node, {ast::AstNodeType::UNIT}); + } + public: SympyConductanceVisitor() = default; void visit_binary_expression(ast::BinaryExpression* node) override; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index e02b2c7bb9..6c22438c5f 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -49,12 +49,15 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a PrimeName"); return; } - auto locals = py::dict("equation_string"_a = nmodl::to_nmodl(node), - "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, - "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, - "use_pade_approx"_a = use_pade_approx); + + const auto node_as_nmodl = to_nmodl_for_sympy(node); + const auto locals = py::dict("equation_string"_a = node_as_nmodl, + "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, + "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, + "use_pade_approx"_a = use_pade_approx); + if (solve_method == euler_method) { - logger->debug("SympySolverVisitor :: EULER - solving: {}", nmodl::to_nmodl(node)); + logger->debug("SympySolverVisitor :: EULER - solving: {}", node_as_nmodl); // replace x' = f(x) differential equation // with forwards Euler timestep: // x = x + f(x) * dt @@ -73,7 +76,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { // replace x' = f(x) differential equation // with analytic solution for x(t+dt) in terms of x(t) // x = ... - logger->debug("SympySolverVisitor :: CNEXP - solving: {}", nmodl::to_nmodl(node)); + logger->debug("SympySolverVisitor :: CNEXP - solving: {}", node_as_nmodl); py::exec(R"( from nmodl.ode import integrate2c exception_message = "" @@ -86,12 +89,15 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { )", py::globals(), locals); } + auto solution = locals["solution"].cast<std::string>(); logger->debug("SympySolverVisitor :: -> solution: {}", solution); + auto exception_message = locals["exception_message"].cast<std::string>(); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: python exception: " + exception_message); } + if (!solution.empty()) { auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement); diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 12d40ec2d3..458abf2814 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -42,9 +42,14 @@ class SympySolverVisitor: public AstVisitor { /// solver method names const std::string euler_method = "euler"; const std::string cnexp_method = "cnexp"; + /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; + static std::string to_nmodl_for_sympy(ast::AST* node) { + return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT}); + } + public: SympySolverVisitor(bool use_pade_approx = false) : use_pade_approx(use_pade_approx){}; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 16a0d268f6..d74241ba8e 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -107,14 +107,13 @@ std::set<std::string> get_global_vars(Program* node) { } -std::string to_nmodl(ast::AST* node) { +std::string to_nmodl(ast::AST* node, const std::set<ast::AstNodeType>& exclude_types) { std::stringstream stream; - NmodlPrintVisitor v(stream); + NmodlPrintVisitor v(stream, exclude_types); node->accept(&v); return stream.str(); } - std::string to_json(ast::AST* node, bool compact, bool expand) { std::stringstream stream; JSONVisitor v(stream); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 8f1e8004f0..f379a32817 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -40,7 +40,7 @@ std::shared_ptr<ast::Statement> create_statement(const std::string& code_stateme std::set<std::string> get_global_vars(ast::Program* node); /** Given AST node, return the NMODL string representation */ -std::string to_nmodl(ast::AST* node); +std::string to_nmodl(ast::AST* node, const std::set<ast::AstNodeType>& exclude_types = {}); /** Given AST node, return the JSON string representation */ std::string to_json(ast::AST* node, bool compact = false, bool expand = false); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 63c33226de..98482bf9db 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2876,3 +2876,37 @@ SCENARIO("SympyConductance visitor", "[sympy]") { } } } + + +//============================================================================= +// Sympy specific to_nmodl +//============================================================================= + +SCENARIO("Sympy specific AST to NMODL conversion") { + GIVEN("NMODL block with unit usage") { + std::string nmodl = R"( + BREAKPOINT { + Pf_NMDA = (1/1.38) * 120 (mM) * 0.6 + VDCC = gca_bar_VDCC * 4(um2)*PI*3(1/um3) + gca_bar_VDCC = 0.0372 (nS/um2) + } + )"; + + std::string expected = R"( + BREAKPOINT { + Pf_NMDA = (1/1.38)*120*0.6 + VDCC = gca_bar_VDCC*4*PI*3 + gca_bar_VDCC = 0.0372 + } + )"; + + THEN("to_nmodl can ignore all units") { + auto input = reindent_text(nmodl); + NmodlDriver driver; + driver.parse_string(input); + auto ast = driver.ast(); + auto result = to_nmodl(ast.get(), {AstNodeType::UNIT}); + REQUIRE(result == reindent_text(expected)); + } + } +} From 1a3a956f054afd16a2549982723fe1ce620f4593 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 6 Mar 2019 21:51:04 +0100 Subject: [PATCH 155/871] Fix issues in standalone lexer (BlueBrain/nmodl#36) - lexer now returns POD data structures but standalone lexer was not updated - add command line option `--text` to lex/parse nmodl as text - update README NMODL Repo SHA: BlueBrain/nmodl@aa8d28a9dd6a03f0ddfbed251654bc1b7ce72810 --- README.md | 40 ++++++++++++++++++++++++++----- src/nmodl/lexer/main_nmodl.cpp | 42 ++++++++++++++++++--------------- src/nmodl/parser/main_nmodl.cpp | 24 ++++++++++++------- 3 files changed, 73 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 81711c71e6..c74d6b768f 100644 --- a/README.md +++ b/README.md @@ -157,12 +157,6 @@ $ tree $HOME/nmodl/bin `-- nmodl_visitor ``` -The `nmodl_lexer` and `nmodl_parser` are standalone tools for testing mod files. If you want to test if given mod file can be successfully parsed by NMODL then you can do: - -``` -$ nmodl_parser <path>/hh.mod -``` - Main code generation program is `nmodl`. You can see all sub-commands supported using: ``` @@ -258,6 +252,40 @@ $ nmodl <path>/hh.mod \ This will generate hh.cpp in the current directory. +##### Lexer and Parser + +The `nmodl_parser` is a standalone parsing tool for NMODL that one can use to check if NMODL construct is valid or if it can be correctly parsed by NMODL. You can parse a mod file as: + +``` +$ nmodl_parser <path>/file.mod +``` + +Or, pass NMODL construct on the command line as: + +``` +$ nmodl_parser --text "NEURON{ SUFFIX hh }" + +[NMODL] [info] :: Processing text : NEURON{ SUFFIX hh } +``` + +The `nmodl_lexer` is a standaline lexer tool for NMODL. You can test a mod file as: + +``` +$ nmodl_lexer <path>/file.mod +``` + +Or, pass NMODL construct on the command line as: + +``` +$ nmodl_lexer --text "NEURON{ SUFFIX hh }" + +[NMODL] [info] :: Processing text : NEURON{ SUFFIX hh } + NEURON at [1.1-6] type 332 + { at [1.7] type 368 + SUFFIX at [1.9-14] type 358 + hh at [1.16-17] type 356 + } at [1.19] type 369 +``` #### Using NMODL With CoreNEURON diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index bc3c1e3d75..7ed5cc5b14 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -58,41 +58,36 @@ void tokenize(const std::string& mod_text) { case Token::VALENCE: case Token::DEL: case Token::DEL2: { - auto value = sym.value.as<ast::Name*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; + auto value = sym.value.as<ast::Name>(); + std::cout << *(value.get_token()) << std::endl; break; } /// token with prime ast class case Token::PRIME: { - auto value = sym.value.as<ast::PrimeName*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; + auto value = sym.value.as<ast::PrimeName>(); + std::cout << *(value.get_token()) << std::endl; break; } /// token with integer ast class case Token::INTEGER: { - auto value = sym.value.as<ast::Integer*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; + auto value = sym.value.as<ast::Integer>(); + std::cout << *(value.get_token()) << std::endl; break; } /// token with double/float ast class case Token::REAL: { - auto value = sym.value.as<ast::Double*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; + auto value = sym.value.as<ast::Double>(); + std::cout << *(value.get_token()) << std::endl; break; } /// token with string ast class case Token::STRING: { - auto value = sym.value.as<ast::String*>(); - std::cout << *(value->get_token()) << std::endl; - delete value; + auto value = sym.value.as<ast::String>(); + std::cout << *(value.get_token()) << std::endl; break; } @@ -118,16 +113,25 @@ void tokenize(const std::string& mod_text) { int main(int argc, const char* argv[]) { CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code"}; - std::vector<std::string> files; - app.add_option("file", files, "One or more NMODL files")->required()->check(CLI::ExistingFile); + std::vector<std::string> mod_files; + std::vector<std::string> mod_texts; + + app.add_option("file", mod_files, "One or more NMODL files")->check(CLI::ExistingFile); + app.add_option("--text", mod_texts, "One or more NMODL constructs as text"); CLI11_PARSE(app, argc, argv); - for (const auto& file: files) { + for (const auto& file: mod_files) { logger->info("Processing file : {}", file); std::ifstream f(file); - std::string mod((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>()); + std::string mod{std::istreambuf_iterator<char>{f}, {}}; tokenize(mod); } + + for (const auto& text: mod_texts) { + logger->info("Processing text : {}", text); + tokenize(text); + } + return 0; } diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 1b876e60cc..0ea98b9139 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -18,18 +18,26 @@ int main(int argc, const char* argv[]) { CLI::App app{"NMODL-Parser : Standalone Parser for NMODL"}; - std::vector<std::string> files; - app.add_option("file", files, "One or more MOD files to process") - ->required() - ->check(CLI::ExistingFile); + std::vector<std::string> mod_files; + std::vector<std::string> mod_texts; + + app.add_option("file", mod_files, "One or more NMODL files")->check(CLI::ExistingFile); + app.add_option("--text", mod_texts, "One or more NMODL constructs as text"); CLI11_PARSE(app, argc, argv); - for (const auto& f: files) { - nmodl::logger->info("Processing {}", f); - nmodl::parser::NmodlDriver driver; - driver.set_verbose(true); + nmodl::parser::NmodlDriver driver; + driver.set_verbose(true); + + for (const auto& f: mod_files) { + nmodl::logger->info("Processing file : {}", f); driver.parse_file(f); } + + for (const auto& text: mod_texts) { + nmodl::logger->info("Processing text : {}", text); + driver.parse_string(text); + } + return 0; } \ No newline at end of file From 4318ea03662b49bf2114c2680587c697ffceabc1 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Thu, 7 Mar 2019 22:06:33 +0100 Subject: [PATCH 156/871] Add SPARSE solver to SympySolver pass (BlueBrain/nmodl#34) For case when ODEs are coupled & linear: - constructs backwards Euler linear system - solves linear system by Gaussian elimination - declares required new local variables in each solve block - replaces ODEs (and adds new statements) with explicit solution of backwards Euler - optionally finds CSE (common sub expressions) - visitor logic altered to deal with each solve block separately - added --cse flag to sympy options to enable CSE generation - added -v,--verbose flag to enable debug logger output - updated README with the new flags - TODO: tests and integration tests, see BlueBrain/nmodl#40 Resolves BlueBrain/nmodl#26 NMODL Repo SHA: BlueBrain/nmodl@d7d22b4f0bb01337c4b9c73e911715945ababf2e --- README.md | 5 +- nmodl/ode.py | 123 +++++++++++++++- src/nmodl/nmodl/main.cpp | 27 +++- .../visitors/sympy_conductance_visitor.hpp | 1 - src/nmodl/visitors/sympy_solver_visitor.cpp | 134 ++++++++++++++---- src/nmodl/visitors/sympy_solver_visitor.hpp | 32 ++++- test/nmodl/transpiler/ode/test_ode.py | 17 ++- test/nmodl/transpiler/visitor/visitor.cpp | 129 ++++++++++++++++- 8 files changed, 426 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index c74d6b768f..74da15a3a8 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Positionals: Options: -h,--help Print this help message and exit -H,--help-all Print this help message including all sub-commands + -v,--verbose Verbose logger output -o,--output TEXT=. Directory for backend code output --scratch TEXT=tmp Directory for intermediate code output @@ -196,6 +197,7 @@ Positionals: Options: -h,--help Print this help message and exit -H,--help-all Print this help message including all sub-commands + -v,--verbose Verbose logger output -o,--output TEXT=. Directory for backend code output --scratch TEXT=tmp Directory for intermediate code output @@ -217,6 +219,7 @@ sympy Options: --analytic Solve ODEs using SymPy analytic integration --pade Pade approximation in SymPy analytic integration + --cse CSE (Common Subexpression Elimination) in SymPy analytic integration --conductance Add CONDUCTANCE keyword in BREAKPOINT passes @@ -245,7 +248,7 @@ To use code generation capability you can do: ``` $ nmodl <path>/hh.mod \ host --c \ - sympy --analytic --pade --conductance \ + sympy --analytic --pade --conductance --cse \ passes --inline --localize --localize-verbatim \ --local-rename --verbatim-inline --verbatim-rename ``` diff --git a/nmodl/ode.py b/nmodl/ode.py index d2c0cac4c3..37abfb64a9 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -12,6 +12,125 @@ raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") +def jacobian_is_linear(jacobian, state_vars): + for j in jacobian: + for x in state_vars: + if j.diff(x).simplify() != 0: + return False + return True + + +def make_unique_prefix(vars, default_prefix="tmp"): + prefix = default_prefix + # generate prefix that doesn't match first part + # of any string in vars + while True: + for v in vars: + # if v is long enough to match prefix + # and first part of it matches prefix + if ((len(v) >= len(prefix)) and (v[: len(prefix)] == prefix)): + # append undescore to prefix, try again + prefix += "_" + break + else: + # for loop ended without finding possible clash + return prefix + + +def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): + """Solve system of ODEs, return solution as C code. + + If system is linear, constructs the backwards Euler linear + system and solves analytically, optionally also + with Common Subexpression Elimination if do_cse is true. + + Otherwise, constructs F(x) such that F(x)=0 is solution + of backwards Euler equation, along with Jacobian of F, + for use in a non-linear solver such as Newton. + + Args: + diff_string: list of ODEs e.g. ["x' = a*x", "y' = 3"] + t_var: name of time variable in NEURON + dt_var: name of dt variable in NEURON + vars: set of variables used in expression, e.g. {"x", "y", a"} + do_cse: if True, do Common Subexpression Elimination + + Returns: + List of strings containing analytic integral of derivative as C code + List of strings containing new local variables + + Raises: + ImportError: if SymPy version is too old (<1.2) + """ + + sympy_vars = {var: sp.symbols(var, real=True) for var in vars} + + # generate prefix for new local vars that avoids clashes + prefix = make_unique_prefix(vars) + + old_state_vars = [] + for s in diff_strings: + vstr = s.split("'")[0] + old_state_var_name = f"{prefix}_{vstr}_old" + var = sp.symbols(old_state_var_name, real=True) + sympy_vars[old_state_var_name] = var + old_state_vars.append(var) + + state_vars = [sp.sympify(s.split("'")[0], locals=sympy_vars) for s in diff_strings] + diff_eqs = [sp.sympify(s.split("=", 1)[1], locals=sympy_vars) for s in diff_strings] + + t = sp.symbols(t_var, real=True) + sympy_vars[t_var] = t + + jacobian = sp.Matrix(diff_eqs).jacobian(state_vars) + + dt = sp.symbols(dt_var, real=True) + sympy_vars[dt_var] = dt + + code = [] + new_local_vars = [] + + if jacobian_is_linear(jacobian, state_vars): + # if linear system: construct implicit euler solution & solve by gaussian elimination + eqs = [] + for x_new, x_old, dxdt in zip(state_vars, old_state_vars, diff_eqs): + eqs.append(sp.Eq(x_new, x_old + dt * dxdt)) + for rhs in sp.linsolve(eqs, state_vars): + for x, x_old in zip(state_vars, old_state_vars): + new_local_vars.append(sp.ccode(x_old)) + code.append(f"{sp.ccode(x_old)} = {sp.ccode(x)}") + if do_cse: + my_symbols = sp.utilities.iterables.numbered_symbols( + prefix=prefix + ) + sub_exprs, simplified_rhs = sp.cse( + rhs, symbols=my_symbols, optimizations="basic", order="canonical" + ) + for v, e in sub_exprs: + new_local_vars.append(sp.ccode(v)) + code.append(f"{v} = {sp.ccode(e.evalf())}") + rhs = simplified_rhs[0] + for v, e in zip(state_vars, rhs): + code.append(f"{sp.ccode(v)} = {sp.ccode(e.evalf())}") + else: + # otherwise: construct implicit euler solution in form F(x) = 0 + # also construct jacobian of this function dF/dx + eqs = [] + for x_new, x_old, dxdt in zip(state_vars, old_state_vars, diff_eqs): + eqs.append(x_new - dt * dxdt - x_old) + for i, x in enumerate(state_vars): + code.append(f"X[{i}] = {sp.ccode(x)}") + for i, eq in enumerate(eqs): + code.append(f"F[{i}] = {sp.ccode(eq.evalf().simplify())}") + for i, jac in enumerate(sp.eye(jacobian.rows, jacobian.rows) - jacobian * dt): + code.append(f"J{i//jacobian.rows}[{i%jacobian.rows}] = {sp.ccode(jac.evalf().simplify())}") + new_local_vars.append("X") + new_local_vars.append("F") + for i in range(jacobian.rows): + new_local_vars.append(f"J{i}") + return code, new_local_vars + + def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): """Analytically integrate supplied derivative, return solution as C code. @@ -26,8 +145,8 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): t_var: name of time variable in NEURON dt_var: name of dt variable in NEURON vars: set of variables used in expression, e.g. {"x", "a"} - uses_pade_approx: if False: return exact solution - if True: return (1,1) Pade approx to solution + use_pade_approx: if False: return exact solution + if True: return (1,1) Pade approx to solution correct to second order in dt_var Returns: diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index cb0f154eb7..4b7ef034b5 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -10,12 +10,14 @@ #include <vector> #include "CLI/CLI.hpp" +#include "fmt/format.h" +#include "pybind11/embed.h" + #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_omp_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "pybind11/embed.h" #include "utils/common_utils.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" @@ -32,6 +34,7 @@ #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +using namespace fmt::literals; using namespace nmodl; using namespace codegen; using nmodl::parser::NmodlDriver; @@ -42,6 +45,9 @@ int main(int argc, const char* argv[]) { /// list of mod files to process std::vector<std::string> mod_files; + /// true if debug logger statements should be shown + bool verbose(false); + /// true if serial c code to be generated bool c_backend(true); @@ -60,6 +66,9 @@ int main(int argc, const char* argv[]) { /// true if Pade approximation to be used bool sympy_pade(false); + /// true if CSE (temp variables) to be used + bool sympy_cse(false); + /// true if conductance keyword can be added to breakpoint bool sympy_conductance(false); @@ -108,6 +117,8 @@ int main(int argc, const char* argv[]) { app.get_formatter()->column_width(40); app.set_help_all_flag("-H,--help-all", "Print this help message including all sub-commands"); + app.add_flag("-v,--verbose", verbose, "Verbose logger output")->ignore_case(); + app.add_option("file", mod_files, "One or more MOD files to process") ->ignore_case() ->required() @@ -130,6 +141,7 @@ int main(int argc, const char* argv[]) { auto sympy_opt = app.add_subcommand("sympy", "SymPy based analysis and optimizations")->ignore_case(); sympy_opt->add_flag("--analytic", sympy_analytic, "Solve ODEs using SymPy analytic integration")->ignore_case(); sympy_opt->add_flag("--pade", sympy_pade, "Pade approximation in SymPy analytic integration")->ignore_case(); + sympy_opt->add_flag("--cse", sympy_cse, "CSE (Common Subexpression Elimination) in SymPy analytic integration")->ignore_case(); sympy_opt->add_flag("--conductance", sympy_conductance, "Add CONDUCTANCE keyword in BREAKPOINT")->ignore_case(); auto passes_opt = app.add_subcommand("passes", "Analyse/Optimization passes")->ignore_case(); @@ -158,8 +170,12 @@ int main(int argc, const char* argv[]) { pybind11::initialize_interpreter(); } + if (verbose) { + logger->set_level(spdlog::level::debug); + } + /// write ast to nmodl - auto ast_to_nmodl = [&](ast::Program* ast, const std::string& filepath) { + auto ast_to_nmodl = [nmodl_ast](ast::Program* ast, const std::string& filepath) { if (nmodl_ast) { NmodlPrintVisitor v(filepath); v.visit_program(ast); @@ -173,10 +189,9 @@ int main(int argc, const char* argv[]) { auto modfile = remove_extension(base_name(file)); /// create file path for nmodl file - auto filepath = [&](std::string suffix) { + auto filepath = [scratch_dir, modfile](std::string suffix) { static int count = 0; - return scratch_dir + "/" + modfile + "." + std::to_string(count++) + "." + suffix + - ".mod"; + return "{}/{}.{}.{}.mod"_format(scratch_dir, modfile, std::to_string(count++), suffix); }; /// driver object creates lexer and parser, just call parser method @@ -234,7 +249,7 @@ int main(int argc, const char* argv[]) { if (sympy_analytic) { logger->info("Running sympy solve visitor"); - SympySolverVisitor v(sympy_pade); + SympySolverVisitor v(sympy_pade, sympy_cse); v.visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("sympy_solve")); } diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index eb3727ce50..f0ab37532a 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -43,7 +43,6 @@ namespace nmodl { * If an ion channel already has a CONDUCTANCE statement * then it does not modify it. * - * TODO: take into account any functions called in breakpoint */ class SympyConductanceVisitor: public AstVisitor { diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 6c22438c5f..581b2585d2 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -21,22 +21,19 @@ namespace nmodl { using symtab::syminfo::NmodlType; -void SympySolverVisitor::visit_solve_block(ast::SolveBlock* node) { - auto method = node->get_method(); - if (method) { - solve_method = method->get_value()->eval(); - } +void SympySolverVisitor::replace_binary_expression(ast::BinaryExpression* bin_expr, + const std::string& new_binary_expr) { + auto& lhs = bin_expr->lhs; + auto& rhs = bin_expr->rhs; + auto new_statement = create_statement(new_binary_expr); + auto new_expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(new_statement); + auto new_bin_expr = std::dynamic_pointer_cast<ast::BinaryExpression>( + new_expr_statement->get_expression()); + lhs.reset(new_bin_expr->lhs->clone()); + rhs.reset(new_bin_expr->rhs->clone()); } void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { - if ((solve_method != cnexp_method) && (solve_method != euler_method)) { - logger->debug( - "SympySolverVisitor :: solve method \"{}\" not {} or {}, so not integrating " - "expression analytically", - solve_method, cnexp_method, euler_method); - return; - } - auto& lhs = node->get_expression()->lhs; auto& rhs = node->get_expression()->rhs; @@ -49,13 +46,11 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a PrimeName"); return; } - const auto node_as_nmodl = to_nmodl_for_sympy(node); const auto locals = py::dict("equation_string"_a = node_as_nmodl, "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, "use_pade_approx"_a = use_pade_approx); - if (solve_method == euler_method) { logger->debug("SympySolverVisitor :: EULER - solving: {}", node_as_nmodl); // replace x' = f(x) differential equation @@ -88,42 +83,131 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { exception_message = str(e) )", py::globals(), locals); + } else { + // for other solver methods: just collect the ODEs & return + logger->debug("SympySolverVisitor :: adding ODE system: {}", to_nmodl_for_sympy(node)); + ode_system.push_back(to_nmodl_for_sympy(node)); + binary_expressions_to_replace.push_back(node->get_expression()); + return; } - + // replace ODE with solution in AST auto solution = locals["solution"].cast<std::string>(); logger->debug("SympySolverVisitor :: -> solution: {}", solution); auto exception_message = locals["exception_message"].cast<std::string>(); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: python exception: " + exception_message); + return; } if (!solution.empty()) { - auto statement = create_statement(solution); - auto expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement); - auto bin_expr = std::dynamic_pointer_cast<ast::BinaryExpression>( - expr_statement->get_expression()); - lhs.reset(bin_expr->lhs->clone()); - rhs.reset(bin_expr->rhs->clone()); + replace_binary_expression(node->get_expression().get(), solution); } else { logger->warn("SympySolverVisitor :: solution to differential equation not possible"); } } void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { - // get any local vars + // get all vars for this block, i.e. global vars + local vars + vars = global_vars; if (auto symtab = node->get_statement_block()->get_symbol_table()) { auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); for (const auto& v: localvars) { vars.insert(v->get_name()); } } + + // get user specified solve method for this block + solve_method = derivative_block_solve_method[node->get_node_name()]; + + // visit each differential equation: + // -for CNEXP or EULER, each equation is independent & is replaced with its solution + // -otherwise, each equation is added to ode_system (and to binary_expressions_to_replace) + ode_system.clear(); + binary_expressions_to_replace.clear(); node->visit_children(this); + + // solve system of ODEs in ode_system + if (solve_method == "sparse" && !ode_system.empty()) { + logger->debug("SympySolverVisitor :: Solving {} system of ODEs", solve_method); + auto locals = py::dict("equation_strings"_a = ode_system, + "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, + "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, + "do_cse"_a = elimination); + py::exec(R"( + from nmodl.ode import solve_ode_system + exception_message = "" + try: + solutions, new_local_vars = solve_ode_system(equation_strings, t_var, dt_var, vars, do_cse) + except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) + )", + py::globals(), locals); + // returns a vector of solutions, i.e. new statements to add to block: + auto solutions = locals["solutions"].cast<std::vector<std::string>>(); + // and a vector of new local variables that need to be declared in the block: + auto new_local_vars = locals["new_local_vars"].cast<std::vector<std::string>>(); + auto exception_message = locals["exception_message"].cast<std::string>(); + if (!exception_message.empty()) { + logger->warn("SympySolverVisitor :: solve_ode_system python exception: " + + exception_message); + return; + } + // sanity check: must have at least as many solutions as ODE's to replace: + if (solutions.size() < binary_expressions_to_replace.size()) { + logger->warn("SympySolverVisitor :: Solve failed: fewer solutions than ODE's"); + return; + } + // declare new local vars + if (!new_local_vars.empty()) { + for (const auto& new_local_var: new_local_vars) { + logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", + new_local_var); + add_local_variable(node->get_statement_block().get(), new_local_var); + } + } + // add new statements: firstly by replacing old ODE binary expressions + auto sol = solutions.cbegin(); + for (auto binary_expr: binary_expressions_to_replace) { + logger->debug("SympySolverVisitor :: -> replacing {} with statement: {}", + to_nmodl_for_sympy(binary_expr.get()), *sol); + replace_binary_expression(binary_expr.get(), *sol); + ++sol; + } + // then by adding the rest as new statements to the block + // get a copy of existing statements in block + auto statements = node->get_statement_block()->get_statements(); + while (sol != solutions.cend()) { + // add new statements to block + logger->debug("SympySolverVisitor :: -> adding statement: {}", *sol); + statements.push_back(create_statement(*sol)); + ++sol; + } + // replace old set of statements in AST with new one + node->get_statement_block()->set_statements(std::move(statements)); + } } void SympySolverVisitor::visit_program(ast::Program* node) { - vars = get_global_vars(node); + global_vars = get_global_vars(node); + + // get list of solve statements with names & methods + AstLookupVisitor ast_lookup_visitor; + auto solve_block_nodes = ast_lookup_visitor.lookup(node, ast::AstNodeType::SOLVE_BLOCK); + for (const auto& block: solve_block_nodes) { + if (auto block_ptr = std::dynamic_pointer_cast<ast::SolveBlock>(block)) { + std::string solve_method = block_ptr->get_method()->get_value()->eval(); + std::string block_name = block_ptr->get_block_name()->get_value()->eval(); + logger->debug("SympySolverVisitor :: Found SOLVE statement: using {} for {}", + solve_method, block_name); + derivative_block_solve_method[block_name] = solve_method; + } + } + node->visit_children(this); } -} // namespace nmodl +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 458abf2814..4667771fa7 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -15,6 +15,7 @@ #include "ast/ast.hpp" #include "symtab/symbol.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/lookup_visitor.hpp" namespace nmodl { @@ -31,11 +32,27 @@ namespace nmodl { * replace each the differential equation with a * forwards Euler timestep * + * For solver method "sparse": + * constructs backwards Euler timestep for coupled set of *linear* + * ODEs, solves resulting linear algebraic equation by + * Gaussian substitution, replaces differential equations + * with explicit solution of backwards Euler equations + * */ class SympySolverVisitor: public AstVisitor { private: + void replace_binary_expression(ast::BinaryExpression* bin_expr, + const std::string& new_binary_expr); + /// global variables + std::set<std::string> global_vars; + + /// local variables in current block + globals std::set<std::string> vars; + + /// map between derivative block names and associated solver method + std::map<std::string, std::string> derivative_block_solve_method{}; + /// method specified in solve block std::string solve_method; @@ -46,15 +63,24 @@ class SympySolverVisitor: public AstVisitor { /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; + // optionally do CSE (common subexpression elimination) for sparse solver + bool elimination; + + /// vector of coupled ODE equations to solve + std::vector<std::string> ode_system; + + /// vector of binary expressions to replace + std::vector<std::shared_ptr<ast::BinaryExpression>> binary_expressions_to_replace; + static std::string to_nmodl_for_sympy(ast::AST* node) { return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT}); } public: - SympySolverVisitor(bool use_pade_approx = false) - : use_pade_approx(use_pade_approx){}; + SympySolverVisitor(bool use_pade_approx = false, bool elimination = true) + : use_pade_approx(use_pade_approx) + , elimination(elimination){}; - void visit_solve_block(ast::SolveBlock* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; void visit_derivative_block(ast::DerivativeBlock* node) override; void visit_program(ast::Program* node) override; diff --git a/test/nmodl/transpiler/ode/test_ode.py b/test/nmodl/transpiler/ode/test_ode.py index f966af7d2d..a0e73bbbad 100644 --- a/test/nmodl/transpiler/ode/test_ode.py +++ b/test/nmodl/transpiler/ode/test_ode.py @@ -5,7 +5,22 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** -from nmodl.ode import differentiate2c +from nmodl.ode import differentiate2c, make_unique_prefix + + +def test_make_unique_prefix(): + + # if prefix matches start of any var, append underscores + # to prefix until this is not true + assert make_unique_prefix(["a", "b", "ccc", "tmp"], "z") == "z" + assert make_unique_prefix(["a", "b", "ccc", "tmp"], "a") == "a_" + assert make_unique_prefix(["a", "b", "ccc", "tmp"], "az") == "az" + assert make_unique_prefix(["a", "b", "ccc", "tmp"], "cc") == "cc_" + assert make_unique_prefix(["a", "b", "ccc", "tmp"], "ccc") == "ccc_" + assert make_unique_prefix(["a", "b", "ccc", "tmp"], "tmp") == "tmp_" + assert make_unique_prefix(["a", "b", "tmp_", "tmp"], "tmp") == "tmp__" + assert make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmp") == "tmp_" + assert make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmpvar") == "tmpvar" def test_differentiation(): diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 98482bf9db..89909606d4 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1969,7 +1969,11 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { // SympySolver visitor tests //============================================================================= -std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { +std::vector<std::string> run_sympy_solver_visitor( + const std::string& text, + bool pade = false, + bool cse = false, + AstNodeType ret_nodetype = AstNodeType::DIFF_EQ_EXPRESSION) { std::vector<std::string> results; // construct AST from text @@ -1982,12 +1986,12 @@ std::vector<std::string> run_sympy_solver_visitor(const std::string& text) { v_symtab.visit_program(ast.get()); // run SympySolver on AST - SympySolverVisitor v_sympy; + SympySolverVisitor v_sympy(pade, cse); v_sympy.visit_program(ast.get()); // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(ast.get(), AstNodeType::DIFF_EQ_EXPRESSION); + auto res = v_lookup.lookup(ast.get(), ret_nodetype); for (const auto& r: res) { results.push_back(to_nmodl(r.get())); } @@ -2144,6 +2148,55 @@ SCENARIO("SympySolver visitor", "[sympy]") { REQUIRE(AST_string == ast_to_string(ast.get())); } } + GIVEN("Derivative block of coupled & linear ODES, solver method sparse, no CSE") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + x' = a*z + b*h + y' = c + 2*x + z' = d*z - y + } + )"; + + std::string expected_result = R"( + DERIVATIVE states { + LOCAL tmp_x_old, tmp_y_old, tmp_z_old + tmp_x_old = x + tmp_y_old = y + tmp_z_old = z + x = (-a*dt*(dt*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old)-tmp_z_old)+(b*dt*h+tmp_x_old)*(2*a*pow(dt, 3)-d*dt+1))/(2*a*pow(dt, 3)-d*dt+1) + y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old)-tmp_z_old)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old))/(2*a*pow(dt, 3)-d*dt+1) + z = (-dt*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old)+tmp_z_old)/(2*a*pow(dt, 3)-d*dt+1) + })"; + + std::string expected_cse_result = R"( + DERIVATIVE states { + LOCAL tmp_x_old, tmp_y_old, tmp_z_old, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 + tmp_x_old = x + tmp_y_old = y + tmp_z_old = z + tmp0 = 2*a + tmp1 = -d*dt+pow(dt, 3)*tmp0+1 + tmp2 = 1/tmp1 + tmp3 = b*dt*h+tmp_x_old + tmp4 = c*dt+2*dt*tmp3+tmp_y_old + tmp5 = dt*tmp4-tmp_z_old + x = -tmp2*(a*dt*tmp5-tmp1*tmp3) + y = -tmp2*(pow(dt, 2)*tmp0*tmp5-tmp1*tmp4) + z = -tmp2*tmp5 + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + auto result_cse = run_sympy_solver_visitor(nmodl_text, true, true, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + REQUIRE(result_cse[0] == reindent_text(expected_cse_result)); + } + } } @@ -2214,6 +2267,76 @@ void run_sympy_conductance_passes(ast::Program* node) { SCENARIO("SympyConductance visitor", "[sympy]") { // Test mod files below all based on: // nmodldb/models/db/bluebrain/CortexSimplified/mod/Ca.mod + GIVEN("breakpoint block containing VERBATIM statement") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + VERBATIM + double z=0; + ENDVERBATIM + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + VERBATIM + double z=0; + ENDVERBATIM + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + THEN("Do nothing") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("breakpoint block containing IF/ELSE block") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + IF(gCabar<1){ + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } ELSE { + gCa = 0 + ica = 0 + } + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + IF(gCabar<1){ + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } ELSE { + gCa = 0 + ica = 0 + } + } + )"; + THEN("Do nothing") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } GIVEN("ion current, existing CONDUCTANCE hint & var") { std::string nmodl_text = R"( NEURON { From a3941230bf1fc2f57e628fbc1a4a04625821fcc6 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Fri, 8 Mar 2019 20:24:15 +0100 Subject: [PATCH 157/871] Add Newton solver (BlueBrain/nmodl#38) - newton solver, header only, implementation using Eigen matrix library (will be used by the derivimplicit integrator) - duplicates functionality of newton_thread from scopmath_core of coreneuron - should be more performant for small matrices (<=4) - added tests for non-linear systems of size : 1, 2, 3, 4, 5, 10 (for examples of use see test/newton) - add eigen as submodule for tests NMODL Repo SHA: BlueBrain/nmodl@748c3496cf3907778b0c18888a630d0875593474 --- cmake/nmodl/CMakeLists.txt | 1 + src/nmodl/solver/CMakeLists.txt | 16 ++ src/nmodl/solver/newton/newton.hpp | 119 ++++++++ test/nmodl/transpiler/CMakeLists.txt | 6 +- test/nmodl/transpiler/newton/newton.cpp | 362 ++++++++++++++++++++++++ 5 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 src/nmodl/solver/CMakeLists.txt create mode 100644 src/nmodl/solver/newton/newton.hpp create mode 100644 test/nmodl/transpiler/newton/newton.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9a87c3e534..e757773fcb 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -119,6 +119,7 @@ add_subdirectory(src/symtab) add_subdirectory(src/utils) add_subdirectory(src/visitors) add_subdirectory(src/pybind) +add_subdirectory(src/solver) # ============================================================================= # Memory checker options and add tests diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt new file mode 100644 index 0000000000..c88c944de6 --- /dev/null +++ b/src/nmodl/solver/CMakeLists.txt @@ -0,0 +1,16 @@ +# ============================================================================= +# Solver sources +# ============================================================================= +set(SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) + +# ============================================================================= +# Solver target for dependencies (only headers for now) +# ============================================================================= + +# ============================================================================= +# Install headers +# ============================================================================= +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/newton + DESTINATION include + FILES_MATCHING + PATTERN "*.h*") diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp new file mode 100644 index 0000000000..0a09af8390 --- /dev/null +++ b/src/nmodl/solver/newton/newton.hpp @@ -0,0 +1,119 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +// Implementation of Newton method for solving +// system of non-linear equations using Eigen + +#include <iostream> + +#include "Eigen/LU" + +namespace nmodl { +namespace newton { + +constexpr int MAX_ITER = 1e3; +constexpr double EPS = 1e-12; + +// Newton method +// given initial vector X +// and a functor that calculates F(X), J(X) +// solves for F(X) = 0, +// starting with initial value of X +// where J(X) is the Jacobian of F(X) +// by iterating: +// X_{n+1} = X_n - J(X_n)^{-1} F(X_n) +// +// when |F|^2 < eps^2, solution has converged +// returns number of iterations (-1 if failed to converge) +template <int N, typename FUNC> +int newton_solver(Eigen::Matrix<double, N, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { + // Vector to store result of function F(X): + Eigen::Matrix<double, N, 1> F; + // Matrix to store jacobian of F(X): + Eigen::Matrix<double, N, N> J; + // Solver iteration count: + int iter = -1; + + while (++iter < max_iter) { + // calculate F, J from X using user-supplied functor + functor(X, F, J); + // get error norm: here we use sqrt(|F|^2) + double error = F.norm(); + // std::cout.precision(15); + // std::cout << "[newton_solver] iter: " << iter << "\terror: " << error << "\n"; + // std::cout << "\n" << X << std::endl; + if (error < eps) { + // we have converged: return iteration count + return iter; + } + // update X + // use in-place LU decomposition of J with partial pivoting + // (suitable for any N, but less efficient than .inverse() for N <=4) + X -= Eigen::PartialPivLU<Eigen::Ref<Eigen::Matrix<double, N, N>>>(J).solve(F); + } + // If we fail to converge after max_iter iterations, return -1 + return -1; +} + +template <typename FUNC, int N> +int solver(Eigen::Matrix<double, N, 1>& X, FUNC functor, double eps, int max_iter) { + Eigen::Matrix<double, N, 1> F; + Eigen::Matrix<double, N, N> J; + int iter = -1; + while (++iter < max_iter) { + functor(X, F, J); + double error = F.norm(); + if (error < eps) { + return iter; + } + X -= J.inverse() * F; + } + return -1; +} + +// template specializations for N <= 4 +// use explicit inverse of F instead of LU decomposition +// (more efficient for small matrices - not safe for large matrices) +template <typename FUNC> +int newton_solver(Eigen::Matrix<double, 1, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { + return solver<FUNC, 1>(X, functor, eps, max_iter); +} + +template <typename FUNC> +int newton_solver(Eigen::Matrix<double, 2, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { + return solver<FUNC, 2>(X, functor, eps, max_iter); +} + +template <typename FUNC> +int newton_solver(Eigen::Matrix<double, 3, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { + return solver<FUNC, 3>(X, functor, eps, max_iter); +} + +template <typename FUNC> +int newton_solver(Eigen::Matrix<double, 4, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { + return solver<FUNC, 4>(X, functor, eps, max_iter); +} + +} // namespace newton +} // namespace nmodl diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 74a8b9dfee..366c2e9f72 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -1,7 +1,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/test) include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -include_directories(${PROJECT_SOURCE_DIR}/src/ext/catch ${PROJECT_SOURCE_DIR}/test) +include_directories(${PROJECT_SOURCE_DIR}/ext/catch ${PROJECT_SOURCE_DIR}/test) +include_directories(${PROJECT_SOURCE_DIR}/src/solver) +include_directories(${PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= # Common input data library @@ -20,6 +22,7 @@ add_executable(testparser parser/parser.cpp) add_executable(testvisitor visitor/visitor.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) +add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) @@ -34,6 +37,7 @@ add_test(NAME Parser COMMAND testparser) add_test(NAME Visitor COMMAND testvisitor) add_test(NAME Printer COMMAND testprinter) add_test(NAME Symtab COMMAND testsymtab) +add_test(NAME Newton COMMAND testnewton) set_tests_properties(Visitor PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) diff --git a/test/nmodl/transpiler/newton/newton.cpp b/test/nmodl/transpiler/newton/newton.cpp new file mode 100644 index 0000000000..91b6413ba4 --- /dev/null +++ b/test/nmodl/transpiler/newton/newton.cpp @@ -0,0 +1,362 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_MAIN + +#include <cmath> + +#include "catch/catch.hpp" +#include "newton/newton.hpp" + + +using namespace nmodl; + +constexpr double max_error_norm = 1e-12; + +SCENARIO("Non-linear system to solve with Newton") { + GIVEN("1 linear eq") { + struct functor { + void operator()(const Eigen::Matrix<double, 1, 1>& X, + Eigen::Matrix<double, 1, 1>& F, + Eigen::Matrix<double, 1, 1>& J) const { + // Function F(X) to find F(X)=0 solution + F[0] = -3.0 * X[0] + 3.0; + // Jacobian of F(X), i.e. the matrix dF(X)_i/dX_j + J(0, 0) = -3.0; + } + }; + Eigen::Matrix<double, 1, 1> X{22.2536}; + Eigen::Matrix<double, 1, 1> F; + Eigen::Matrix<double, 1, 1> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find the solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(X[0] == Approx(1.0)); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("1 non-linear eq") { + struct functor { + void operator()(const Eigen::Matrix<double, 1, 1>& X, + Eigen::Matrix<double, 1, 1>& F, + Eigen::Matrix<double, 1, 1>& J) const { + F[0] = -3.0 * X[0] + std::sin(X[0]) + std::log(X[0] * X[0] + 11.435243) + 3.0; + J(0, 0) = -3.0 + std::cos(X[0]) + 2.0 * X[0] / (X[0] * X[0] + 11.435243); + } + }; + Eigen::Matrix<double, 1, 1> X{-0.21421}; + Eigen::Matrix<double, 1, 1> F; + Eigen::Matrix<double, 1, 1> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find the solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(X[0] == Approx(2.19943987001206)); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 2 non-linear eqs") { + struct functor { + void operator()(const Eigen::Matrix<double, 2, 1>& X, + Eigen::Matrix<double, 2, 1>& F, + Eigen::Matrix<double, 2, 2>& J) const { + F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1] - 1.0; + F[1] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[1] + 0.4; + J(0, 0) = -3.0 * X[1] + 1.0; + J(0, 1) = -3.0 * X[0] + 2.0; + J(1, 0) = 4.0; + J(1, 1) = -0.59999999999999998 * X[1] + 1.0; + } + }; + Eigen::Matrix<double, 2, 1> X{0.2, 0.4}; + Eigen::Matrix<double, 2, 1> F; + Eigen::Matrix<double, 2, 2> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 3 non-linear eqs") { + struct functor { + double _x_old = 0.5; + double _y_old = -231.5; + double _z_old = 1.4565; + double dt = 0.2; + double a = 0.2354; + double b = 436.2; + double d = 23.1; + double e = 0.01; + double z = 0.99; + void operator()(const Eigen::Matrix<double, 3, 1>& X, + Eigen::Matrix<double, 3, 1>& F, + Eigen::Matrix<double, 3, 3>& J) const { + F(0) = -(-_x_old - dt * (a * std::pow(X[0], 2) + X[1]) + X[0]); + F(1) = -(_y_old - dt * (a * X[0] + b * X[1] + d) + X[1]); + F(2) = -(_z_old - dt * (e * z - 3.0 * X[0] + 2.0 * X[1]) + X[2]); + J(0, 0) = 2.0 * a * dt * X[0] - 1.0; + J(0, 1) = dt; + J(0, 2) = 0; + J(1, 0) = a * dt; + J(1, 1) = b * dt - 1.0; + J(1, 2) = 0; + J(2, 0) = -3.0 * dt; + J(2, 1) = 2.0 * dt; + J(2, 2) = dt * e - 1.0; + } + }; + Eigen::Matrix<double, 3, 1> X{0.21231, 0.4435, -0.11537}; + Eigen::Matrix<double, 3, 1> F; + Eigen::Matrix<double, 3, 3> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 4 non-linear eqs") { + struct functor { + double _X0_old = 1.2345; + double _X1_old = 1.2345; + double _X2_old = 1.2345; + double _X3_old = 1.2345; + double dt = 0.2; + void operator()(const Eigen::Matrix<double, 4, 1>& X, + Eigen::Matrix<double, 4, 1>& F, + Eigen::Matrix<double, 4, 4>& J) const { + F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - _X0_old + 2.0 * dt / X[1]); + F[1] = -(X[1] - _X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); + F[2] = -((X[2] * (X[2] - _X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / + X[2]); + F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - _X3_old + 6.0 * dt / X[2]); + J(0, 0) = 3.0 * X[2] * dt - 1.0; + J(0, 1) = 2.0 * dt / std::pow(X[1], 2); + J(0, 2) = 3.0 * X[0] * dt; + J(0, 3) = 0; + J(1, 0) = -4.0 * dt; + J(1, 1) = 6.2 * dt - 1.0; + J(1, 2) = 0; + J(1, 3) = -dt; + J(2, 0) = 0; + J(2, 1) = -1.2 * dt; + J(2, 2) = -1.0 - 0.3 * dt / std::pow(X[2], 2); + J(2, 3) = 0; + J(3, 0) = 4.0 * X[1] * X[2] * dt; + J(3, 1) = 4.0 * X[0] * X[2] * dt; + J(3, 2) = 4.0 * X[0] * X[1] * dt + 6.0 * dt / std::pow(X[2], 2); + J(3, 3) = -1.0; + } + }; + Eigen::Matrix<double, 4, 1> X{0.21231, 0.4435, -0.11537, -0.8124312}; + Eigen::Matrix<double, 4, 1> F; + Eigen::Matrix<double, 4, 4> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 5 non-linear eqs") { + struct functor { + void operator()(const Eigen::Matrix<double, 5, 1>& X, + Eigen::Matrix<double, 5, 1>& F, + Eigen::Matrix<double, 5, 5>& J) const { + F[0] = -3.0 * X[0] * X[2] + X[0] + 2.0 / X[1]; + F[1] = 4.0 * X[0] - 5.2 * X[1] + X[3]; + F[2] = 1.2 * X[1] + X[2] - 3.0 - 0.3 / X[2]; + F[3] = -4.0 * X[0] * X[1] * X[2] + X[3] + 6.0 / X[2]; + F[4] = (-4.0 * X[0] + (X[4] + cos(X[1])) * (X[1] * X[2] - X[3] * X[4])) / + (X[1] * X[2] - X[3] * X[4]); + J(0, 0) = -3.0 * X[2] + 1.0; + J(0, 1) = -2.0 / std::pow(X[1], 2); + J(0, 2) = -3.0 * X[0]; + J(0, 3) = 0; + J(0, 4) = 0; + J(1, 0) = 4.0; + J(1, 1) = -5.2; + J(1, 2) = 0; + J(1, 3) = 1.0; + J(1, 4) = 0; + J(2, 0) = 0; + J(2, 1) = 1.2; + J(2, 2) = 1.0 + 0.3 / std::pow(X[2], 2); + J(2, 3) = 0; + J(2, 4) = 0; + J(3, 0) = -4.0 * X[1] * X[2]; + J(3, 1) = -4.0 * X[0] * X[2]; + J(3, 2) = -4.0 * X[0] * X[1] - 6.0 / std::pow(X[2], 2); + J(3, 3) = 1.0; + J(3, 4) = 0; + J(4, 0) = -4.0 / (X[1] * X[2] - X[3] * X[4]); + J(4, 1) = 4.0 * X[0] * X[2] / std::pow(X[1] * X[2] - X[3] * X[4], 2) - + std::sin(X[1]); + J(4, 2) = 4.0 * X[0] * X[1] / std::pow(X[1] * X[2] - X[3] * X[4], 2); + J(4, 3) = -4.0 * X[0] * X[4] / std::pow(X[1] * X[2] - X[3] * X[4], 2); + J(4, 4) = -4.0 * X[0] * X[3] / std::pow(X[1] * X[2] - X[3] * X[4], 2) + 1.0; + } + }; + Eigen::Matrix<double, 5, 1> X; + X << 8.234, -245.46, 123.123, 0.8343, 5.555; + Eigen::Matrix<double, 5, 1> F; + Eigen::Matrix<double, 5, 5> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 10 non-linear eqs") { + struct functor { + void operator()(const Eigen::Matrix<double, 10, 1>& X, + Eigen::Matrix<double, 10, 1>& F, + Eigen::Matrix<double, 10, 10>& J) const { + F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1]; + F[1] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[1]; + F[2] = 2.0 * X[1] + X[2] + 2.0 * X[3] * X[5] * X[7] - 3.0 * X[4] * X[8] - X[5]; + F[3] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[3] - + X[4] * X[6] * X[7]; + F[4] = -3.0 * X[0] * X[7] + 2.0 * X[1] - 4.0 * X[3] * X[8] + X[4]; + F[5] = -X[2] * X[5] * X[8] + 4.0 * X[3] - 0.29999999999999999 * X[4] * X[9] + X[5]; + F[6] = -3.0 * X[0] * X[1] - 2.1000000000000001 * X[3] * X[4] * X[5] + X[6] + + 2.0 * X[8]; + F[7] = 4.0 * X[0] - 0.29999999999999999 * X[6] * X[7] + X[7]; + F[8] = -3.0 * X[0] * X[1] - X[2] * X[3] * X[4] * std::pow(X[5], 2) + 2.0 * X[5] + + X[8]; + F[9] = -0.29999999999999999 * X[2] * X[4] + 4.0 * std::pow(X[9], 2) + X[9]; + J(0, 0) = -3.0 * X[1] + 1.0; + J(0, 1) = -3.0 * X[0] + 2.0; + J(0, 2) = 0; + J(0, 3) = 0; + J(0, 4) = 0; + J(0, 5) = 0; + J(0, 6) = 0; + J(0, 7) = 0; + J(0, 8) = 0; + J(0, 9) = 0; + J(1, 0) = 4.0; + J(1, 1) = -0.59999999999999998 * X[1] + 1.0; + J(1, 2) = 0; + J(1, 3) = 0; + J(1, 4) = 0; + J(1, 5) = 0; + J(1, 6) = 0; + J(1, 7) = 0; + J(1, 8) = 0; + J(1, 9) = 0; + J(2, 0) = 0; + J(2, 1) = 2.0; + J(2, 2) = 1.0; + J(2, 3) = 2.0 * X[5] * X[7]; + J(2, 4) = -3.0 * X[8]; + J(2, 5) = 2.0 * X[3] * X[7] - 1.0; + J(2, 6) = 0; + J(2, 7) = 2.0 * X[3] * X[5]; + J(2, 8) = -3.0 * X[4]; + J(2, 9) = 0; + J(3, 0) = 4.0; + J(3, 1) = -0.59999999999999998 * X[1]; + J(3, 2) = 0; + J(3, 3) = 1.0; + J(3, 4) = -X[6] * X[7]; + J(3, 5) = 0; + J(3, 6) = -X[4] * X[7]; + J(3, 7) = -X[4] * X[6]; + J(3, 8) = 0; + J(3, 9) = 0; + J(4, 0) = -3.0 * X[7]; + J(4, 1) = 2.0; + J(4, 2) = 0; + J(4, 3) = -4.0 * X[8]; + J(4, 4) = 1.0; + J(4, 5) = 0; + J(4, 6) = 0; + J(4, 7) = -3.0 * X[0]; + J(4, 8) = -4.0 * X[3]; + J(4, 9) = 0; + J(5, 0) = 0; + J(5, 1) = 0; + J(5, 2) = -X[5] * X[8]; + J(5, 3) = 4.0; + J(5, 4) = -0.29999999999999999 * X[9]; + J(5, 5) = -X[2] * X[8] + 1.0; + J(5, 6) = 0; + J(5, 7) = 0; + J(5, 8) = -X[2] * X[5]; + J(5, 9) = -0.29999999999999999 * X[4]; + J(6, 0) = -3.0 * X[1]; + J(6, 1) = -3.0 * X[0]; + J(6, 2) = 0; + J(6, 3) = -2.1000000000000001 * X[4] * X[5]; + J(6, 4) = -2.1000000000000001 * X[3] * X[5]; + J(6, 5) = -2.1000000000000001 * X[3] * X[4]; + J(6, 6) = 1.0; + J(6, 7) = 0; + J(6, 8) = 2.0; + J(6, 9) = 0; + J(7, 0) = 4.0; + J(7, 1) = 0; + J(7, 2) = 0; + J(7, 3) = 0; + J(7, 4) = 0; + J(7, 5) = 0; + J(7, 6) = -0.29999999999999999 * X[7]; + J(7, 7) = -0.29999999999999999 * X[6] + 1.0; + J(7, 8) = 0; + J(7, 9) = 0; + J(8, 0) = -3.0 * X[1]; + J(8, 1) = -3.0 * X[0]; + J(8, 2) = -X[3] * X[4] * std::pow(X[5], 2); + J(8, 3) = -X[2] * X[4] * std::pow(X[5], 2); + J(8, 4) = -X[2] * X[3] * std::pow(X[5], 2); + J(8, 5) = -2.0 * X[2] * X[3] * X[4] * X[5] + 2.0; + J(8, 6) = 0; + J(8, 7) = 0; + J(8, 8) = 1.0; + J(8, 9) = 0; + J(9, 0) = 0; + J(9, 1) = 0; + J(9, 2) = -0.29999999999999999 * X[4]; + J(9, 3) = 0; + J(9, 4) = -0.29999999999999999 * X[2]; + J(9, 5) = 0; + J(9, 6) = 0; + J(9, 7) = 0; + J(9, 8) = 0; + J(9, 9) = 8.0 * X[9] + 1.0; + } + }; + Eigen::Matrix<double, 10, 1> X; + X << 8.234, -245.46, 123.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; + Eigen::Matrix<double, 10, 1> F; + Eigen::Matrix<double, 10, 10> J; + functor fn; + int iter_newton = newton::newton_solver(X, fn); + fn(X, F, J); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(F.norm() < max_error_norm); + } + } +} From a469d549194ca0dd6ad79ac3c1dee7b0d0fcbd46 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Fri, 8 Mar 2019 20:58:58 +0100 Subject: [PATCH 158/871] Fix code generation and compatibility issues with derivimplicit: (BlueBrain/nmodl#43) - fixes for using derivimplicit method with current coreneuron implementation - add missing include, fix typos in code generation - run verbatim renaming pass by default Resolves BlueBrain/nmodl#42 NMODL Repo SHA: BlueBrain/nmodl@a3d77f9c1a9d22f100a0810d7755a3813b167ee0 --- src/nmodl/codegen/codegen_c_visitor.cpp | 29 ++++++++++++++++---- src/nmodl/codegen/codegen_helper_visitor.cpp | 1 + src/nmodl/nmodl/main.cpp | 2 +- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 357a2d4862..60d4804433 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2165,6 +2165,7 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include <coreneuron/nrniv/nrniv_decl.h>"); printer->add_line("#include <coreneuron/nrniv/ivocvect.h>"); printer->add_line("#include <coreneuron/mech/mod2c_core_thread.h>"); + printer->add_line("#include <coreneuron/scopmath_core/newton_struct.h>"); printer->add_line("#include <_kinderiv.h>"); } @@ -2471,6 +2472,9 @@ void CodegenCVisitor::print_mechanism_register() { if (info.vectorize && (info.thread_data_index != 0)) { auto name = get_variable_name("ext_call_thread"); printer->add_line("thread_mem_init({});"_format(name)); + } + + if (!info.thread_variables.empty()) { printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); } @@ -2570,11 +2574,14 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->add_line("/** thread memory cleanup callback */"); printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); + // clang-format off if (info.vectorize && info.derivimplicit_used) { int n = info.derivimplicit_list_num; printer->add_line("free(thread[dith{}()].pval);"_format(n)); - printer->add_line("nrn_destroy_newtonspace(*newtonspace{}(thread));"_format(n)); + printer->add_line("nrn_destroy_newtonspace(static_cast<NewtonSpace*>(*newtonspace{}(thread)));"_format(n)); } + // clang-format on + if (info.top_local_thread_size != 0) { auto line = "free(thread[top_local_var_tid()].pval);"; printer->add_line(line); @@ -3469,6 +3476,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { auto stride = (layout == LayoutType::aos) ? "" : "*pnodecount+id"; printer->add_newline(2); + // clang-format off printer->start_block("int {}_{}({})"_format(node->get_node_name(), suffix, ext_params)); auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); @@ -3486,13 +3494,24 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_line(" savstate{}[i{}] = data[slist{}[i]{}];"_format(list_num, stride, list_num, stride)); printer->add_line("}"); - auto argument = "{}, slist{}, derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, solve_block_name, suffix, list_num + 1, ext_args); - printer->add_line("int reset = nrn_newton_thread(*newtonspace{}(thread), {});"_format(list_num, argument)); + auto argument = "{}, slist{}, _derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, solve_block_name, suffix, list_num + 1, ext_args); + printer->add_line("int reset = nrn_newton_thread(static_cast<NewtonSpace*>(*newtonspace{}(thread)), {});"_format(list_num, argument)); printer->add_line("return reset;"); printer->end_block(3); - printer->start_block("int newton_{}_{}({}) "_format(node->get_node_name(), info.mod_suffix, external_method_parameters())); + /* + * TODO : To be backward compatible with mod2c we have to generate below + * comment marker in the generated cpp file for kinderiv.py to + * process it and generate correct _kinderiv.h + */ + printer->add_line("/* _derivimplicit_ {} _{} */"_format(node->get_node_name(), info.mod_suffix)); + printer->add_newline(1); + + printer->start_block("int _newton_{}_{}({}) "_format(node->get_node_name(), info.mod_suffix, external_method_parameters())); printer->add_line(instance); + if (ion_variable_struct_required()) { + printer->add_line("IonCurVar ionvar = {0};"); + } printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); printer->add_line(slist1); printer->add_line(dlist1); @@ -3557,7 +3576,7 @@ void CodegenCVisitor::print_nrn_state() { if (info.derivimplicit_used) { auto args = - "{}, {}, {}, derivimplicit_{}_{}, {}" + "{}, {}, {}, _derivimplicit_{}_{}, {}" ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); auto statement = "derivimplicit_thread({});"_format(args); printer->add_line(statement); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 65bf847a7a..46d16cef83 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -224,6 +224,7 @@ void CodegenHelperVisitor::find_non_range_variables() { info.derivimplicit_var_thread_id = 0; info.thread_data_index = 3; info.derivimplicit_list_num = 1; + info.thread_callback_register = true; } if (info.euler_used) { diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 4b7ef034b5..013280588c 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -88,7 +88,7 @@ int main(int argc, const char* argv[]) { bool verbatim_inline(false); /// true if verbatim blocks - bool verbatim_rename(false); + bool verbatim_rename(true); /// directory where code will be generated std::string output_dir("."); From c8d751725606f585bb7fafd3b581fedb26ab558e Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Sun, 10 Mar 2019 13:19:33 +0100 Subject: [PATCH 159/871] Newton solver CUDA compatibility with Eigen (BlueBrain/nmodl#45) - mark newton solver routines with EIGEN_DEVICE_FUNC so that the solver can be used from cpu or gpu kernels - usable with latest master of eigen - update eigen submodule to latest master - add header nmodl.hpp which includes common solver header and set common settings for eigen Resolves BlueBrain/nmodl#41 BlueBrain/nmodl#47 NMODL Repo SHA: BlueBrain/nmodl@ad9c2efaaee02c8f476c668705d3475bb8510628 --- src/nmodl/nmodl/CMakeLists.txt | 1 + src/nmodl/nmodl/nmodl.hpp | 19 +++++++++++ src/nmodl/solver/newton/newton.hpp | 45 +++++++++++++------------ test/nmodl/transpiler/newton/newton.cpp | 2 +- 4 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 src/nmodl/nmodl/nmodl.hpp diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 8d6d0ed0c1..b53dd30910 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -14,3 +14,4 @@ target_link_libraries(nmodl printer codegen visitor symtab util lexer) # Install executable # ============================================================================= install(TARGETS nmodl DESTINATION bin) +install(FILES nmodl.hpp DESTINATION include) diff --git a/src/nmodl/nmodl/nmodl.hpp b/src/nmodl/nmodl/nmodl.hpp new file mode 100644 index 0000000000..82abf643b6 --- /dev/null +++ b/src/nmodl/nmodl/nmodl.hpp @@ -0,0 +1,19 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + + +/// no parallelization using openmp +#define EIGEN_DONT_PARALLELIZE + +/// keep host and CUDA code compatible +#ifndef EIGEN_DEFAULT_DENSE_INDEX_TYPE +#define EIGEN_DEFAULT_DENSE_INDEX_TYPE int +#endif + +#include "newton/newton.hpp" diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 0a09af8390..7d5303a7bb 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -32,10 +32,10 @@ constexpr double EPS = 1e-12; // when |F|^2 < eps^2, solution has converged // returns number of iterations (-1 if failed to converge) template <int N, typename FUNC> -int newton_solver(Eigen::Matrix<double, N, 1>& X, - FUNC functor, - double eps = EPS, - int max_iter = MAX_ITER) { +EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { // Vector to store result of function F(X): Eigen::Matrix<double, N, 1> F; // Matrix to store jacobian of F(X): @@ -65,7 +65,10 @@ int newton_solver(Eigen::Matrix<double, N, 1>& X, } template <typename FUNC, int N> -int solver(Eigen::Matrix<double, N, 1>& X, FUNC functor, double eps, int max_iter) { +EIGEN_DEVICE_FUNC int solver(Eigen::Matrix<double, N, 1>& X, + FUNC functor, + double eps, + int max_iter) { Eigen::Matrix<double, N, 1> F; Eigen::Matrix<double, N, N> J; int iter = -1; @@ -84,34 +87,34 @@ int solver(Eigen::Matrix<double, N, 1>& X, FUNC functor, double eps, int max_ite // use explicit inverse of F instead of LU decomposition // (more efficient for small matrices - not safe for large matrices) template <typename FUNC> -int newton_solver(Eigen::Matrix<double, 1, 1>& X, - FUNC functor, - double eps = EPS, - int max_iter = MAX_ITER) { +EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 1, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { return solver<FUNC, 1>(X, functor, eps, max_iter); } template <typename FUNC> -int newton_solver(Eigen::Matrix<double, 2, 1>& X, - FUNC functor, - double eps = EPS, - int max_iter = MAX_ITER) { +EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 2, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { return solver<FUNC, 2>(X, functor, eps, max_iter); } template <typename FUNC> -int newton_solver(Eigen::Matrix<double, 3, 1>& X, - FUNC functor, - double eps = EPS, - int max_iter = MAX_ITER) { +EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 3, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { return solver<FUNC, 3>(X, functor, eps, max_iter); } template <typename FUNC> -int newton_solver(Eigen::Matrix<double, 4, 1>& X, - FUNC functor, - double eps = EPS, - int max_iter = MAX_ITER) { +EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 4, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { return solver<FUNC, 4>(X, functor, eps, max_iter); } diff --git a/test/nmodl/transpiler/newton/newton.cpp b/test/nmodl/transpiler/newton/newton.cpp index 91b6413ba4..4978df0755 100644 --- a/test/nmodl/transpiler/newton/newton.cpp +++ b/test/nmodl/transpiler/newton/newton.cpp @@ -10,7 +10,7 @@ #include <cmath> #include "catch/catch.hpp" -#include "newton/newton.hpp" +#include "nmodl/nmodl.hpp" using namespace nmodl; From d7aecaf19674d727fd979205bbc3697b4c5d7a67 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Tue, 12 Mar 2019 16:36:39 +0100 Subject: [PATCH 160/871] Use find_python_module cmake module that allows to check version (BlueBrain/nmodl#56) - Reduce version requirements to early 2017/2018 releases - Use FindPythonModule.cmake from github.com/openturns/otsubsetinverse NMODL Repo SHA: BlueBrain/nmodl@a8135e4c75286e9ce4b6c07494908eb19d56465f --- cmake/nmodl/CMakeLists.txt | 10 ++-- cmake/nmodl/FindPythonModule.cmake | 76 ++++++++++++++++++++++++------ 2 files changed, 66 insertions(+), 20 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e757773fcb..58ad718c4b 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -54,11 +54,11 @@ include(GitRevision) # ============================================================================= message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) -find_python_module(jinja2 REQUIRED) -find_python_module(pytest REQUIRED) -find_python_module(sympy REQUIRED) -find_python_module(textwrap REQUIRED) -find_python_module(yaml REQUIRED) +find_python_module(jinja2 2.9.3 REQUIRED) +find_python_module(pytest 3.3.0 REQUIRED) +find_python_module(sympy 1.2 REQUIRED) +find_python_module(textwrap 0.9 REQUIRED) +find_python_module(yaml 3.12 REQUIRED) include_directories(${PROJECT_SOURCE_DIR} ${PROJECT_SOURCE_DIR}/src diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index e3736af63e..7ad83a9897 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -1,12 +1,34 @@ -# Find if a Python module is installed Found at -# http://www.cmake.org/pipermail/cmake/2011-January/041666.html To use do: find_python_module(PyQt4 -# REQUIRED) -function(find_python_module module) +# * Macro to find a python module +# +# Usage: find_python_module (module [VERSION] [REQUIRED]) +# +# Copyright 2005-2018 Airbus-EDF-IMACS-Phimeca +# +# Distributed under the OSI-approved BSD License (the "License"); see accompanying file +# Copyright.txt for details in: +# +# https://github.com/openturns/otsubsetinverse/blob/master/cmake/FindPythonModule.cmake + +macro(find_python_module module) + string(TOUPPER ${module} module_upper) - if(NOT PY_${module_upper}) - if(ARGC GREATER 1 AND ARGV1 STREQUAL "REQUIRED") - set(${module}_FIND_REQUIRED TRUE) + if(NOT ${module_upper}_FOUND) + + # parse arguments + set(${module}_FIND_OPTIONAL TRUE) + if(${ARGC} EQUAL 2) + if(${ARGV1} MATCHES REQUIRED) + set(${module}_FIND_OPTIONAL FALSE) + else() + set(${module}_FIND_VERSION ${ARGV1}) + endif() + elseif(${ARGC} EQUAL 3) + if(${ARGV2} MATCHES REQUIRED) + set(${module}_FIND_OPTIONAL FALSE) + endif() + set(${module}_FIND_VERSION ${ARGV1}) endif() + # A module's location is usually a directory, but for binary modules it's a .so file. execute_process( COMMAND "${PYTHON_EXECUTABLE}" "-c" @@ -15,12 +37,36 @@ function(find_python_module module) OUTPUT_VARIABLE _${module}_location ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _${module}_status) - set(PY_${module_upper} ${_${module}_location} + set(${module_upper}_LOCATION ${_${module}_location} CACHE STRING "Location of Python module ${module}") - endif(NOT _${module}_status) - endif(NOT PY_${module_upper}) - find_package_handle_standard_args(PY_${module} DEFAULT_MSG PY_${module_upper}) - if(NOT PY_${module_upper} AND ${module}_FIND_REQUIRED) - message(FATAL_ERROR "Could not find ${module}") - endif() -endfunction(find_python_module) + # retrieve version + execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" + "import ${module}; print(${module}.__version__)" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_version + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + + set(_${module_upper}_VERSION_MATCH TRUE) + if(NOT _${module}_status) + set(${module_upper}_VERSION_STRING ${_${module}_version}) + if(${module}_FIND_VERSION) + if(${module}_FIND_VERSION VERSION_GREATER ${module_upper}_VERSION_STRING) + set(_${module_upper}_VERSION_MATCH FALSE) + endif() + endif() + mark_as_advanced(${module_upper}_VERSION_STRING) + endif() + endif() + + find_package_handle_standard_args(${module} + REQUIRED_VARS + ${module_upper}_LOCATION + _${module_upper}_VERSION_MATCH + VERSION_VAR + ${module_upper}_VERSION_STRING) + if(NOT ${module}_FIND_OPTIONAL AND NOT _${module_upper}_VERSION_MATCH) + message(FATAL_ERROR "Missing python module ${module}") + endif() + mark_as_advanced(${module_upper}_LOCATION) + endif(NOT ${module_upper}_FOUND) +endmacro(find_python_module) From da33c280ad6b38cd83d261e50a090ca957136dd4 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Tue, 12 Mar 2019 22:49:58 +0100 Subject: [PATCH 161/871] Code generation support for coupled nonlinear odes (derivimplicit method) (BlueBrain/nmodl#48) * Added support for derivimplicit solve method * For each derivimplicit block, SympySolver - replaces ODEs with assignments: X[0]=m, X[1]=h, etc - constructs functor block in AST wtih EigenNewtonSolverBlock node * For the code generation - code generation specific ast node specicaiton moved to codegen.yaml - C code generation backend updated to emit functor and call to newton solver * Missing features / future improvements: - no check for name clashes with X - order of statements in derivative block is not preserved * Update tests - Add test with derivimplicit method for sympysolver - Add test with multiple solve blocks - Adapt test with ODEs order NMODL Repo SHA: BlueBrain/nmodl@60b94f84110478b115d68a59da9e8c2f03ca8b38 --- nmodl/ode.py | 19 +-- src/nmodl/ast/ast_common.hpp | 4 + src/nmodl/codegen/codegen_c_visitor.cpp | 83 ++++++++--- src/nmodl/codegen/codegen_c_visitor.hpp | 1 + src/nmodl/codegen/codegen_helper_visitor.cpp | 5 + src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.cpp | 16 +++ src/nmodl/codegen/codegen_info.hpp | 6 + src/nmodl/codegen/codegen_naming.hpp | 3 + src/nmodl/language/code_generator.py | 7 +- src/nmodl/language/codegen.yaml | 46 ++++++ src/nmodl/language/nmodl.yaml | 1 + src/nmodl/language/nodes.py | 6 + src/nmodl/language/parser.py | 8 +- src/nmodl/nmodl/main.cpp | 65 ++++----- src/nmodl/visitors/sympy_solver_visitor.cpp | 139 ++++++++++++++----- src/nmodl/visitors/sympy_solver_visitor.hpp | 35 +++-- src/nmodl/visitors/visitor_utils.cpp | 24 ++++ src/nmodl/visitors/visitor_utils.hpp | 4 + test/nmodl/transpiler/visitor/visitor.cpp | 137 +++++++++++++++++- 20 files changed, 491 insertions(+), 119 deletions(-) create mode 100644 src/nmodl/language/codegen.yaml diff --git a/nmodl/ode.py b/nmodl/ode.py index 37abfb64a9..38bdc75b6a 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -40,7 +40,7 @@ def make_unique_prefix(vars, default_prefix="tmp"): def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): """Solve system of ODEs, return solution as C code. - If system is linear, constructs the backwards Euler linear + If system is linear, constructs the backwards Euler linear system and solves analytically, optionally also with Common Subexpression Elimination if do_cse is true. @@ -115,19 +115,20 @@ def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): else: # otherwise: construct implicit euler solution in form F(x) = 0 # also construct jacobian of this function dF/dx + + # state vars to be stored in vector X for Newton solver + Xvecsubs = {} + for i, x_new in enumerate(state_vars): + Xvecsubs[x_new] = sp.symbols(f"X[{i}]") eqs = [] for x_new, x_old, dxdt in zip(state_vars, old_state_vars, diff_eqs): - eqs.append(x_new - dt * dxdt - x_old) - for i, x in enumerate(state_vars): - code.append(f"X[{i}] = {sp.ccode(x)}") + eqs.append((x_new - dt * dxdt).subs(Xvecsubs) - x_new) for i, eq in enumerate(eqs): code.append(f"F[{i}] = {sp.ccode(eq.evalf().simplify())}") for i, jac in enumerate(sp.eye(jacobian.rows, jacobian.rows) - jacobian * dt): - code.append(f"J{i//jacobian.rows}[{i%jacobian.rows}] = {sp.ccode(jac.evalf().simplify())}") - new_local_vars.append("X") - new_local_vars.append("F") - for i in range(jacobian.rows): - new_local_vars.append(f"J{i}") + # todo: fix indexing to be ascending order + flat_index = jacobian.rows*(i%jacobian.rows) + (i//jacobian.rows) + code.append(f"J[{flat_index}] = {sp.ccode(jac.subs(Xvecsubs).evalf().simplify())}") return code, new_local_vars diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 84e08ef27b..7c329c77af 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -306,6 +306,10 @@ struct AST: public std::enable_shared_from_this<AST> { return false; } + virtual bool is_eigen_newton_solver_block() { + return false; + } + virtual bool is_procedure_block() { return false; } diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 60d4804433..0fe283c8f0 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -465,13 +465,14 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { || node->is_while_statement()) { return false; } - // clang-format on if (node->is_expression_statement()) { - auto statement = dynamic_cast<ExpressionStatement*>(node); - if (statement->get_expression()->is_statement_block()) { + auto expression = dynamic_cast<ExpressionStatement*>(node)->get_expression(); + if (expression->is_statement_block() + || expression->is_eigen_newton_solver_block()) { return false; } } + // clang-format on return true; } @@ -1615,6 +1616,45 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = false; } +void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) { + // solution vector to store copy of state vars for Newton solver + printer->add_newline(); + auto float_type = default_float_data_type(); + printer->add_line("Eigen::Matrix<{}, {}, 1> X;"_format(float_type, info.num_primes)); + print_statement_block(node->get_setup_x_block().get(), false, false); + + // functor that evaluates F(X) and J(X) for Newton solver + printer->start_block("struct functor"); + printer->add_line("NrnThread* nt;"); + printer->add_line("{0}* inst;"_format(instance_struct())); + printer->add_line("int id;"); + + printer->add_line( + "functor(NrnThread* nt, {}* inst, int id) : nt(nt), inst(inst), id(id) {}"_format( + instance_struct(), "{}")); + + printer->add_indent(); + printer->add_text( + "void operator()(const Eigen::Matrix<{0}, {1}, 1>& X, Eigen::Matrix<{0}, {1}, " + "1>& F, " + "Eigen::Matrix<{0}, {1}, {1}>& Jm) const"_format(float_type, info.num_primes)); + printer->start_block(); + printer->add_line("{}* J = Jm.data();"_format(float_type)); + print_statement_block(node->get_functor_block().get(), false, false); + printer->end_block(1); + printer->end_block(0); + printer->add_text(";"); + printer->add_newline(); + + // call newton solver with functor and X matrix that contains state vars + printer->add_line("// call newton solver"); + printer->add_line("functor newton_functor(nt, inst, id);"); + printer->add_line("int newton_iterations = nmodl::newton::newton_solver(X, newton_functor);"); + + // assign newton solver results in matrix X to state vars + printer->add_line("// assign results to state vars"); + print_statement_block(node->get_update_states_block().get(), false, false); +} /****************************************************************************************/ /* Code-specific helper routines */ @@ -1928,7 +1968,7 @@ void CodegenCVisitor::print_namespace_stop() { */ void CodegenCVisitor::print_thread_getters() { - if (info.vectorize && info.derivimplicit_used) { + if (info.vectorize && info.derivimplicit_coreneuron_solver()) { int tid = info.derivimplicit_var_thread_id; int list = info.derivimplicit_list_num; @@ -2167,6 +2207,7 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include <coreneuron/mech/mod2c_core_thread.h>"); printer->add_line("#include <coreneuron/scopmath_core/newton_struct.h>"); printer->add_line("#include <_kinderiv.h>"); + printer->add_line("#include <newton/newton.hpp>"); } @@ -2282,7 +2323,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { printer->add_line("int* dlist1;"); codegen_global_variables.push_back(make_symbol("slist1")); codegen_global_variables.push_back(make_symbol("dlist1")); - if (info.derivimplicit_used) { + if (info.derivimplicit_coreneuron_solver()) { printer->add_line("int* slist2;"); codegen_global_variables.push_back(make_symbol("slist2")); } @@ -2546,7 +2587,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->add_line("/** thread memory allocation callback */"); printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); - if (info.vectorize && info.derivimplicit_used) { + if (info.vectorize && info.derivimplicit_coreneuron_solver()) { printer->add_line("thread[dith{}()].pval = NULL;"_format(info.derivimplicit_list_num)); } if (info.vectorize && (info.top_local_thread_size != 0)) { @@ -2575,7 +2616,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); // clang-format off - if (info.vectorize && info.derivimplicit_used) { + if (info.vectorize && info.derivimplicit_coreneuron_solver()) { int n = info.derivimplicit_list_num; printer->add_line("free(thread[dith{}()].pval);"_format(n)); printer->add_line("nrn_destroy_newtonspace(static_cast<NewtonSpace*>(*newtonspace{}(thread)));"_format(n)); @@ -2689,7 +2730,7 @@ void CodegenCVisitor::print_global_variable_setup() { } /// additional list for derivimplicit method - if (info.derivimplicit_used) { + if (info.derivimplicit_coreneuron_solver()) { auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); auto slist2 = get_variable_name("slist2"); auto nprimes = info.primes_size; @@ -2999,7 +3040,7 @@ void CodegenCVisitor::print_nrn_init() { printer->add_line("/** initialize channel */"); print_global_function_common_code(BlockType::Initial); - if (info.derivimplicit_used) { + if (info.derivimplicit_coreneuron_solver()) { printer->add_newline(); int nequation = info.num_equations; int list_num = info.derivimplicit_list_num; @@ -3030,7 +3071,7 @@ void CodegenCVisitor::print_nrn_init() { print_channel_iteration_tiling_block_end(); printer->end_block(1); - if (info.derivimplicit_used) { + if (info.derivimplicit_coreneuron_solver()) { printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); } print_kernel_data_present_annotation_block_end(); @@ -3463,7 +3504,6 @@ void CodegenCVisitor::print_net_receive() { * slist needs to added as local variable */ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { - assert(info.solve_node->is_derivative_block()); auto node = info.solve_node; codegen = true; @@ -3544,7 +3584,7 @@ void CodegenCVisitor::print_nrn_state() { } codegen = true; - if (info.derivimplicit_used) { + if (info.derivimplicit_coreneuron_solver()) { print_derivative_kernel_for_derivimplicit(); } @@ -3570,18 +3610,19 @@ void CodegenCVisitor::print_nrn_state() { auto num_primes = info.num_primes; auto suffix = info.mod_suffix; auto block_name = info.solve_block_name; - auto num = info.derivimplicit_used ? info.derivimplicit_list_num : info.euler_list_num; + auto num = info.derivimplicit_coreneuron_solver() ? info.derivimplicit_list_num + : info.euler_list_num; auto slist = get_variable_name("slist{}"_format(num)); auto dlist = get_variable_name("dlist{}"_format(num)); - if (info.derivimplicit_used) { - auto args = - "{}, {}, {}, _derivimplicit_{}_{}, {}" - ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); - auto statement = "derivimplicit_thread({});"_format(args); - printer->add_line(statement); - } else { - if (info.solve_node != nullptr) { + if (info.solve_node != nullptr) { + if (info.derivimplicit_coreneuron_solver()) { + auto args = + "{}, {}, {}, _derivimplicit_{}_{}, {}" + ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); + auto statement = "derivimplicit_thread({});"_format(args); + printer->add_line(statement); + } else { auto block = info.solve_node->get_statement_block(); print_statement_block(block.get(), false, false); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 4b62dda306..2075ddc314 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -933,6 +933,7 @@ class CodegenCVisitor: public AstVisitor { virtual void visit_float(ast::Float* node) override; virtual void visit_from_statement(ast::FromStatement* node) override; virtual void visit_function_call(ast::FunctionCall* node) override; + virtual void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; virtual void visit_if_statement(ast::IfStatement* node) override; virtual void visit_indexed_name(ast::IndexedName* node) override; virtual void visit_integer(ast::Integer* node) override; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 46d16cef83..e96ba6b1e1 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -489,6 +489,11 @@ void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { } +void CodegenHelperVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) { + info.eigen_newton_solver_exist = true; +} + + void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { auto name = node->get_node_name(); if (name == naming::NET_SEND_METHOD) { diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index b562f972e0..522dd0ab21 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -78,6 +78,7 @@ class CodegenHelperVisitor: public AstVisitor { void visit_conductance_hint(ast::ConductanceHint* node) override; void visit_procedure_block(ast::ProcedureBlock* node) override; void visit_function_block(ast::FunctionBlock* node) override; + void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; void visit_solve_block(ast::SolveBlock* node) override; void visit_statement_block(ast::StatementBlock* node) override; void visit_initial_block(ast::InitialBlock* node) override; diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index ddd2c37585..c4074cc1b0 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -74,5 +74,21 @@ bool CodegenInfo::function_uses_table(std::string& name) const { return false; } +/** + * Check if coreneuron internal derivimplicit solver needs to be used + * + * - if derivimplicit method is not used or solve block is empty + * then there is nothing to do + * - if eigen solver block is used then coreneuron solver is not needed + */ + +bool CodegenInfo::derivimplicit_coreneuron_solver() { + if (!derivimplicit_used || solve_node == nullptr || eigen_newton_solver_exist) { + return false; + } + return true; +} + + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index f69cb49bff..2a617d9351 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -344,6 +344,9 @@ struct CodegenInfo { /// all watch statements std::vector<ast::WatchStatement*> watch_statements; + /// true if eigen newton solver is used + bool eigen_newton_solver_exist = false; + /// if any ion has write variable bool ion_has_write_variable(); @@ -368,6 +371,9 @@ struct CodegenInfo { return (table_count > 0 && vectorize == true); } + /// if legacy derivimplicit solver from coreneuron to be used + bool derivimplicit_coreneuron_solver(); + bool function_uses_table(std::string& name) const; }; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 6742008029..eed5e057d3 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -28,6 +28,9 @@ const std::string EULER_METHOD("euler"); /// cnexp method in nmodl const std::string CNEXP_METHOD("cnexp"); +/// sparse method in nmodl +const std::string SPARSE_METHOD("sparse"); + /// net_event function call in nmodl const std::string NET_EVENT_METHOD("net_event"); diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index a1e9f3d6d6..fd1ca8d1b0 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -34,7 +34,12 @@ clang_format += args.clang_format_opts # parse nmodl definition file and get list of abstract nodes -nodes = LanguageParser("nmodl.yaml").parse_file() +nmodl_nodes = LanguageParser("nmodl.yaml").parse_file() +codegen_nodes = LanguageParser("codegen.yaml").parse_file() + +# combine ast nodes from NMODL specification and codegen specification +nodes = nmodl_nodes +nodes.extend(x for x in codegen_nodes if x not in nodes) templates = Path(__file__).parent / 'templates' diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml new file mode 100644 index 0000000000..96a631af53 --- /dev/null +++ b/src/nmodl/language/codegen.yaml @@ -0,0 +1,46 @@ +# ********************************************************************* +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU +# Lesser General Public License. See top-level LICENSE file for details. +# ********************************************************************* + +######################### NMODL Abstract Language Definition ############################## +# +# PURPOSE +# ======= +# +# NMODL language specification in nmod.yaml takes care of representing NMODL language +# AST generation. This AST is sufficient for NMODL level analysis and optimizations. +# In order to perform code generation transformations, we need to perform various +# transoformations on AST and add new node types. For example, PROCEDURE and FUNCTION +# nodes in NMODL doesn't have return type (double by default). Also, we can't represent +# code generation specific information (e.g. variable or function qualifiers) with the +# existing AST nodes. This yaml specification describe additional node types that can +# be used for code generation purpose. Note that they are using same inheritance +# hierarchy because we would like to use single AST to represent the NMODL with and +# without code generation transformations. + +- AST: + children: + - Node: + children: + - Expression: + children: + - Number: + - Identifier: + - Block: + children: + - EigenNewtonSolverBlock: + description: "Represent newton solver solution block based on Eigen" + members: + - setup_x_block: + description: "update X from states" + type: StatementBlock + - functor_block: + description: "odes as functor for eigen" + type: StatementBlock + - update_states_block: + description: "update back states from X" + type: StatementBlock + - Statement: \ No newline at end of file diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index c200d15dfe..7852ea652e 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -354,6 +354,7 @@ type: Statement vector: true public: true + add: true - DerivativeBlock: description: ".." nmodl: "DERIVATIVE " diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 747d3988f5..e862dc7e4c 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -382,4 +382,10 @@ def __repr__(self): return "Node(class_name='{}', base_class='{}', nmodl_name='{}')".format( self.class_name, self.base_class, self.nmodl_name) + def __eq__(self, other): + """ + AST node name (i.e. class_name) is supposed to be unique, just compare it for equality + """ + return self.class_name == other.class_name + __str__ = __repr__ diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 772a076f17..5120cb6177 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -127,10 +127,14 @@ def parse_yaml_rules(self, nodelist, base_class=None): # name of the ast class and it's properties as dictionary class_name, properties = next(iter(node.items())) + # no need to process empty nodes + if properties is None: + continue + args = Argument() - args.url = properties['url'] if 'url' in properties else None + args.url = properties.get('url', None) args.class_name = class_name - args.description = properties['description'] + args.description = properties.get('description', '') # yaml file has abstract classes and their subclasses with children as a property if 'children' in properties: diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 013280588c..c24ecf272e 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -177,8 +177,7 @@ int main(int argc, const char* argv[]) { /// write ast to nmodl auto ast_to_nmodl = [nmodl_ast](ast::Program* ast, const std::string& filepath) { if (nmodl_ast) { - NmodlPrintVisitor v(filepath); - v.visit_program(ast); + NmodlPrintVisitor(filepath).visit_program(ast); logger->info("AST to NMODL transformation written to {}", filepath); } }; @@ -198,20 +197,20 @@ int main(int argc, const char* argv[]) { NmodlDriver driver; driver.parse_file(file); + /// whether to update existing symbol table or create new + /// one whenever we run symtab visitor. + bool update_symtab = false; + /// parse mod file and construct ast auto ast = driver.ast(); - /// just visit the astt - { - AstVisitor v; - v.visit_program(ast.get()); - } + /// just visit the ast + AstVisitor().visit_program(ast.get()); /// construct symbol table { logger->info("Running symtab visitor"); - SymtabVisitor v(false); - v.visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); } if (show_symtab) { @@ -227,80 +226,72 @@ int main(int argc, const char* argv[]) { if (json_ast) { logger->info("Writing AST into {}", file); auto file = scratch_dir + "/" + modfile + ".ast.json"; - JSONVisitor v(file); - v.visit_program(ast.get()); + JSONVisitor(file).visit_program(ast.get()); } if (verbatim_rename) { logger->info("Running verbatim rename visitor"); - VerbatimVarRenameVisitor v; - v.visit_program(ast.get()); + VerbatimVarRenameVisitor().visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("verbatim_rename")); } if (sympy_conductance) { logger->info("Running sympy conductance visitor"); - SympyConductanceVisitor v1; - SymtabVisitor v2(false); - v1.visit_program(ast.get()); - v2.visit_program(ast.get()); + SympyConductanceVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("sympy_conductance")); } + /// once we start modifying (especially removing) older constructs + /// from ast then we should run symtab visitor in update mode so + /// that old symbols (e.g. prime variables) are not lost + update_symtab = true; + if (sympy_analytic) { logger->info("Running sympy solve visitor"); - SympySolverVisitor v(sympy_pade, sympy_cse); - v.visit_program(ast.get()); + SympySolverVisitor(sympy_pade, sympy_cse).visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("sympy_solve")); } { logger->info("Running cnexp visitor"); - CnexpSolveVisitor v; - v.visit_program(ast.get()); + CnexpSolveVisitor().visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("cnexp")); } if (nmodl_inline) { logger->info("Running nmodl inline visitor"); - InlineVisitor v; - v.visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("inline")); } if (local_rename) { logger->info("Running local variable rename visitor"); - LocalVarRenameVisitor v1; - SymtabVisitor v2(true); - v1.visit_program(ast.get()); - v2.visit_program(ast.get()); + LocalVarRenameVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("local_rename")); } if (localize) { // localize pass must follow rename pass to avoid conflict logger->info("Running localize visitor"); - LocalizeVisitor v1(localize_verbatim); - LocalVarRenameVisitor v2; - SymtabVisitor v3(true); - v1.visit_program(ast.get()); - v2.visit_program(ast.get()); - v3.visit_program(ast.get()); + LocalizeVisitor(localize_verbatim).visit_program(ast.get()); + LocalVarRenameVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("localize")); } if (json_perfstat) { auto file = scratch_dir + "/" + modfile + ".perf.json"; logger->info("Writing performance statistics to {}", file); - PerfVisitor v(file); - v.visit_program(ast.get()); + PerfVisitor(file).visit_program(ast.get()); } { // make sure to run perf visitor because code generator // looks for read/write counts const/non-const declaration - PerfVisitor v; - v.visit_program(ast.get()); + PerfVisitor().visit_program(ast.get()); } { diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 581b2585d2..122d2849cd 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -21,16 +21,45 @@ namespace nmodl { using symtab::syminfo::NmodlType; -void SympySolverVisitor::replace_binary_expression(ast::BinaryExpression* bin_expr, - const std::string& new_binary_expr) { - auto& lhs = bin_expr->lhs; - auto& rhs = bin_expr->rhs; - auto new_statement = create_statement(new_binary_expr); +void SympySolverVisitor::replace_diffeq_expression(ast::DiffEqExpression* expr, + const std::string& new_expr) { + auto new_statement = create_statement(new_expr); auto new_expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(new_statement); auto new_bin_expr = std::dynamic_pointer_cast<ast::BinaryExpression>( new_expr_statement->get_expression()); - lhs.reset(new_bin_expr->lhs->clone()); - rhs.reset(new_bin_expr->rhs->clone()); + expr->set_expression(std::move(new_bin_expr)); +} + +std::shared_ptr<ast::EigenNewtonSolverBlock> +SympySolverVisitor::construct_eigen_newton_solver_block( + const std::vector<std::string>& setup_x, + const std::vector<std::string>& functor, + const std::vector<std::string>& update_state) { + auto setup_x_block = create_statement_block(setup_x); + auto functor_block = create_statement_block(functor); + auto update_state_block = create_statement_block(update_state); + return std::make_shared<ast::EigenNewtonSolverBlock>(setup_x_block, functor_block, + update_state_block); +} + +void SympySolverVisitor::remove_statements_from_block(ast::StatementBlock* block, + const std::set<ast::Node*> statements) { + auto& statement_vec = block->statements; + statement_vec.erase(std::remove_if(statement_vec.begin(), statement_vec.end(), + [&statements](std::shared_ptr<ast::Statement>& s) { + return statements.find(s.get()) != statements.end(); + }), + statement_vec.end()); +} + +void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { + current_statement_block = node; + node->visit_children(this); +} + +void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement* node) { + current_diffeq_statement = node; + node->visit_children(this); } void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { @@ -46,12 +75,17 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a PrimeName"); return; } + + prime_variables.push_back(lhs->get_node_name()); + diffeq_statements.insert(current_diffeq_statement); + const auto node_as_nmodl = to_nmodl_for_sympy(node); const auto locals = py::dict("equation_string"_a = node_as_nmodl, "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, "use_pade_approx"_a = use_pade_approx); - if (solve_method == euler_method) { + + if (solve_method == codegen::naming::EULER_METHOD) { logger->debug("SympySolverVisitor :: EULER - solving: {}", node_as_nmodl); // replace x' = f(x) differential equation // with forwards Euler timestep: @@ -67,7 +101,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { exception_message = str(e) )", py::globals(), locals); - } else if (solve_method == cnexp_method) { + } else if (solve_method == codegen::naming::CNEXP_METHOD) { // replace x' = f(x) differential equation // with analytic solution for x(t+dt) in terms of x(t) // x = ... @@ -87,9 +121,9 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { // for other solver methods: just collect the ODEs & return logger->debug("SympySolverVisitor :: adding ODE system: {}", to_nmodl_for_sympy(node)); ode_system.push_back(to_nmodl_for_sympy(node)); - binary_expressions_to_replace.push_back(node->get_expression()); return; } + // replace ODE with solution in AST auto solution = locals["solution"].cast<std::string>(); logger->debug("SympySolverVisitor :: -> solution: {}", solution); @@ -101,7 +135,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { } if (!solution.empty()) { - replace_binary_expression(node->get_expression().get(), solution); + replace_diffeq_expression(node, solution); } else { logger->warn("SympySolverVisitor :: solution to differential equation not possible"); } @@ -120,15 +154,19 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { // get user specified solve method for this block solve_method = derivative_block_solve_method[node->get_node_name()]; - // visit each differential equation: - // -for CNEXP or EULER, each equation is independent & is replaced with its solution - // -otherwise, each equation is added to ode_system (and to binary_expressions_to_replace) + /// clear information from previous derivative block if any + diffeq_statements.clear(); + prime_variables.clear(); ode_system.clear(); - binary_expressions_to_replace.clear(); + + // visit each differential equation: + // - for CNEXP or EULER, each equation is independent & is replaced with its solution + // - otherwise, each equation is added to ode_system (and to binary_expressions_to_replace) node->visit_children(this); - // solve system of ODEs in ode_system - if (solve_method == "sparse" && !ode_system.empty()) { + /// if there are no odes collected then there is nothing to do + if (!ode_system.empty()) { + // solve system of ODEs in ode_system logger->debug("SympySolverVisitor :: Solving {} system of ODEs", solve_method); auto locals = py::dict("equation_strings"_a = ode_system, "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, @@ -146,48 +184,73 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { exception_message = str(e) )", py::globals(), locals); + // returns a vector of solutions, i.e. new statements to add to block: auto solutions = locals["solutions"].cast<std::vector<std::string>>(); // and a vector of new local variables that need to be declared in the block: auto new_local_vars = locals["new_local_vars"].cast<std::vector<std::string>>(); auto exception_message = locals["exception_message"].cast<std::string>(); + if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: solve_ode_system python exception: " + exception_message); return; } + // sanity check: must have at least as many solutions as ODE's to replace: - if (solutions.size() < binary_expressions_to_replace.size()) { + if (solutions.size() < ode_system.size()) { logger->warn("SympySolverVisitor :: Solve failed: fewer solutions than ODE's"); return; } + // declare new local vars if (!new_local_vars.empty()) { for (const auto& new_local_var: new_local_vars) { logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", new_local_var); - add_local_variable(node->get_statement_block().get(), new_local_var); + add_local_variable(current_statement_block, new_local_var); } } - // add new statements: firstly by replacing old ODE binary expressions - auto sol = solutions.cbegin(); - for (auto binary_expr: binary_expressions_to_replace) { - logger->debug("SympySolverVisitor :: -> replacing {} with statement: {}", - to_nmodl_for_sympy(binary_expr.get()), *sol); - replace_binary_expression(binary_expr.get(), *sol); - ++sol; - } - // then by adding the rest as new statements to the block - // get a copy of existing statements in block - auto statements = node->get_statement_block()->get_statements(); - while (sol != solutions.cend()) { - // add new statements to block - logger->debug("SympySolverVisitor :: -> adding statement: {}", *sol); - statements.push_back(create_statement(*sol)); - ++sol; + + if (solve_method == codegen::naming::SPARSE_METHOD) { + remove_statements_from_block(current_statement_block, diffeq_statements); + // get a copy of existing statements in block + auto statements = current_statement_block->get_statements(); + // add new statements + for (const auto& sol: solutions) { + logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); + statements.push_back(create_statement(sol)); + } + // replace old set of statements in AST with new one + current_statement_block->set_statements(std::move(statements)); + } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { + /// Construct X from the state variables by using the original + /// ODE statements in the block. Also create statements to update + /// state variables from X + std::vector<std::string> setup_x_eqs; + std::vector<std::string> update_state_eqs; + for (int i = 0; i < prime_variables.size(); i++) { + auto statement = prime_variables[i] + " = " + "X[ " + std::to_string(i) + "]"; + auto rev_statement = "X[ " + std::to_string(i) + "]" + " = " + prime_variables[i]; + update_state_eqs.push_back(statement); + setup_x_eqs.push_back(rev_statement); + } + + /// remove original ODE statements from the block where they initially appear + remove_statements_from_block(current_statement_block, diffeq_statements); + + /// create newton solution block and add that as statement back in the block + /// statements in solutions : put F, J into new functor to be created for eigen + auto solver_block = construct_eigen_newton_solver_block(setup_x_eqs, solutions, + update_state_eqs); + + if (vars.find("X") != vars.end()) { + logger->error("SympySolverVisitor :: -> X conflicts with NMODL variable"); + } + + current_statement_block->addStatement( + std::make_shared<ast::ExpressionStatement>(solver_block)); } - // replace old set of statements in AST with new one - node->get_statement_block()->set_statements(std::move(statements)); } } @@ -210,4 +273,4 @@ void SympySolverVisitor::visit_program(ast::Program* node) { node->visit_children(this); } -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 4667771fa7..99ea96480a 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -37,13 +37,21 @@ namespace nmodl { * ODEs, solves resulting linear algebraic equation by * Gaussian substitution, replaces differential equations * with explicit solution of backwards Euler equations - * */ class SympySolverVisitor: public AstVisitor { private: - void replace_binary_expression(ast::BinaryExpression* bin_expr, - const std::string& new_binary_expr); + /** Replace binary expression with new expression provided as string */ + static void replace_diffeq_expression(ast::DiffEqExpression* expr, const std::string& new_expr); + + /** Remove statements from given statement block if they exist */ + static void remove_statements_from_block(ast::StatementBlock* block, + const std::set<ast::Node*> statements); + + std::shared_ptr<ast::EigenNewtonSolverBlock> construct_eigen_newton_solver_block( + const std::vector<std::string>& setup_x, + const std::vector<std::string>& functor, + const std::vector<std::string>& update_state); /// global variables std::set<std::string> global_vars; @@ -53,13 +61,21 @@ class SympySolverVisitor: public AstVisitor { /// map between derivative block names and associated solver method std::map<std::string, std::string> derivative_block_solve_method{}; + /// prime variables from LHS of ODEs + std::vector<std::string> prime_variables; + + /// ODE statements appeared in the derivative block + std::set<ast::Node*> diffeq_statements; + + /// current expression statement being visited (to track ODEs) + ast::ExpressionStatement* current_diffeq_statement; + + /// current statement block being visited + ast::StatementBlock* current_statement_block; + /// method specified in solve block std::string solve_method; - /// solver method names - const std::string euler_method = "euler"; - const std::string cnexp_method = "cnexp"; - /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; @@ -69,9 +85,6 @@ class SympySolverVisitor: public AstVisitor { /// vector of coupled ODE equations to solve std::vector<std::string> ode_system; - /// vector of binary expressions to replace - std::vector<std::shared_ptr<ast::BinaryExpression>> binary_expressions_to_replace; - static std::string to_nmodl_for_sympy(ast::AST* node) { return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT}); } @@ -82,7 +95,9 @@ class SympySolverVisitor: public AstVisitor { , elimination(elimination){}; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; + void visit_expression_statement(ast::ExpressionStatement* node) override; void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_statement_block(ast::StatementBlock* node) override; void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index d74241ba8e..c27c6fc1ce 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -89,6 +89,30 @@ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { return statement; } +/** + * Convert given code statement (in string format) to corresponding ast node + * + * We create dummy nmodl procedure containing given code statement and then + * parse it using NMODL parser. As there will be only one block with single + * statement, we return first statement. + */ +std::shared_ptr<StatementBlock> create_statement_block( + const std::vector<std::string>& code_statements) { + nmodl::parser::NmodlDriver driver; + std::string nmodl_text = "PROCEDURE dummy() {\n"; + for (auto& statement: code_statements) { + nmodl_text += statement + "\n"; + } + nmodl_text += "}"; + driver.parse_string(nmodl_text); + auto ast = driver.ast(); + auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); + auto statement_block = std::shared_ptr<StatementBlock>( + procedure->get_statement_block()->clone()); + return statement_block; +} + + std::set<std::string> get_global_vars(Program* node) { std::set<std::string> vars; if (auto* symtab = node->get_symbol_table()) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index f379a32817..af92ac274c 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -36,6 +36,10 @@ ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& /** Create ast statement node from given code in string format */ std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); +/** Create ast statement block node from given code in string format */ +std::shared_ptr<ast::StatementBlock> create_statement_block( + const std::vector<std::string>& code_statements); + /** Return set of strings with the names of all global variables */ std::set<std::string> get_global_vars(ast::Program* node); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 89909606d4..192be833e9 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2197,6 +2197,141 @@ SCENARIO("SympySolver visitor", "[sympy]") { REQUIRE(result_cse[0] == reindent_text(expected_cse_result)); } } + GIVEN("Derivative block including ODES with sparse method (from nmodl paper)") { + std::string nmodl_text = R"( + STATE { + mc + m + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + mc' = -a*mc + b*m + m' = a*mc - b*m + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL tmp_mc_old, tmp_m_old + tmp_mc_old = mc + tmp_m_old = m + mc = (b*dt*tmp_m_old+b*dt*tmp_mc_old+tmp_mc_old)/(a*dt+b*dt+1) + m = (a*dt*tmp_m_old+a*dt*tmp_mc_old+tmp_m_old)/(a*dt+b*dt+1) + })"; + + THEN("Construct & solver linear system") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with derivimplicit method") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + DERIVATIVE states { + rates(v) + m' = (minf-m)/mtau - 3*h + h' = (hinf-h)/htau + m*m + n' = (ninf-n)/ntau + } + )"; + /// new derivative block with EigenNewtonSolverBlock node + std::string expected_result = R"( + DERIVATIVE states { + rates(v) + { + X[0] = m + X[1] = h + X[2] = n + }{ + F[0] = (dt*(X[0]+3*X[1]*mtau-minf)+mtau*(X[0]-m))/mtau + F[1] = (-dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(X[1]-h))/htau + F[2] = (dt*(X[2]-ninf)+ntau*(X[2]-n))/ntau + J[0] = dt/mtau+1 + J[3] = 3*dt + J[6] = 0 + J[1] = -2*X[0]*dt + J[4] = dt/htau+1 + J[7] = 0 + J[2] = 0 + J[5] = 0 + J[8] = dt/ntau+1 + }{ + m = X[0] + h = X[1] + n = X[2] + } + })"; + + THEN("Construct & solver linear system using newton solver") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Multiple derivative blocks each with derivimplicit method") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states1 METHOD derivimplicit + SOLVE states2 METHOD derivimplicit + } + + DERIVATIVE states1 { + m' = (minf-m)/mtau + h' = (hinf-h)/htau + m*m + } + + DERIVATIVE states2 { + h' = (hinf-h)/htau + m*m + m' = (minf-m)/mtau + h + } + )"; + /// EigenNewtonSolverBlock in each derivative block + std::string expected_result_0 = R"( + DERIVATIVE states1 { + { + X[0] = m + X[1] = h + }{ + F[0] = (dt*(X[0]-minf)+mtau*(X[0]-m))/mtau + F[1] = (-dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(X[1]-h))/htau + J[0] = dt/mtau+1 + J[2] = 0 + J[1] = -2*X[0]*dt + J[3] = dt/htau+1 + }{ + m = X[0] + h = X[1] + } + })"; + std::string expected_result_1 = R"( + DERIVATIVE states2 { + { + X[0] = h + X[1] = m + }{ + F[0] = (-dt*(-X[0]+pow(X[1], 2)*htau+hinf)+htau*(X[0]-h))/htau + F[1] = (-dt*(X[0]*mtau-X[1]+minf)+mtau*(X[1]-m))/mtau + J[0] = dt/htau+1 + J[2] = -2*X[1]*dt + J[1] = -dt + J[3] = dt/mtau+1 + }{ + h = X[0] + m = X[1] + } + })"; + + THEN("Construct & solver linear system using newton solver") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result_0)); + REQUIRE(result[1] == reindent_text(expected_result_1)); + } + } } @@ -3002,7 +3137,7 @@ SCENARIO("SympyConductance visitor", "[sympy]") { //============================================================================= -// Sympy specific to_nmodl +// to_nmodl with excluding set of node types //============================================================================= SCENARIO("Sympy specific AST to NMODL conversion") { From 7d2296f339deb3c768e2c638320dd412e0ca6c7a Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris <iomagkanaris@gmail.com> Date: Thu, 14 Mar 2019 16:16:10 +0100 Subject: [PATCH 162/871] Update python files in build directory on modifications (BlueBrain/nmodl#58) - added new custom_target named copy_python_files that depends on the python files that need to be on the nmodl folder - changed add_custom_command to copy the python files and the _nmodl target if the python files of the source directory have changed or the target _nmodl has been rebuilt NMODL Repo SHA: BlueBrain/nmodl@4e31e2c4abc5265b5802178a0a13c5f6b7528380 --- src/nmodl/pybind/CMakeLists.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index fcb6bc87f9..a66bcc787e 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,6 +3,11 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) +foreach(file ode.py dsl.py __init__.py) + list(APPEND NMODL_PYTHON_FILES_IN ${PROJECT_SOURCE_DIR}/nmodl/${file}) + list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) +endforeach() + set(PYNMODL_SOURCES ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp @@ -30,12 +35,14 @@ add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) -add_custom_command(TARGET _nmodl POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/nmodl +add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) +add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/nmodl ${CMAKE_BINARY_DIR}/nmodl COMMAND ${CMAKE_COMMAND} -E copy_if_different $<TARGET_FILE:_nmodl> - ${CMAKE_BINARY_DIR}/nmodl) - + ${CMAKE_BINARY_DIR}/nmodl + DEPENDS ${NMODL_PYTHON_FILES_IN} $<TARGET_FILE:_nmodl> + COMMENT "-- COPYING NMODL PYTHON FILES --") # ============================================================================= # Install python binding components # ============================================================================= From 8fc47d36c28a0737dd5c69df166b01b1ea135f15 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Thu, 14 Mar 2019 20:50:45 +0100 Subject: [PATCH 163/871] Implement constant folding visitor (BlueBrain/nmodl#63) - move WrappedExpression to codegen.yaml - fixed wrong return type for is_wrapped_expression - add define statements to symbol table - wrap more binary expressions under WrappedExpression so that it's easy to replace them - add constant folding pass & add tests NMODL Repo SHA: BlueBrain/nmodl@4c3eedb96e0bba042c703ec8f59d3e3ff6cbfe6d --- src/nmodl/ast/ast_common.hpp | 2 +- src/nmodl/language/CMakeLists.txt | 1 + src/nmodl/language/codegen.yaml | 6 + src/nmodl/language/nmodl.yaml | 7 +- src/nmodl/language/node_info.py | 1 + src/nmodl/nmodl/main.cpp | 11 ++ src/nmodl/parser/nmodl.yy | 41 +++-- src/nmodl/symtab/symbol_properties.cpp | 4 + src/nmodl/symtab/symbol_properties.hpp | 5 +- src/nmodl/visitors/CMakeLists.txt | 2 + .../visitors/constant_folder_visitor.cpp | 171 ++++++++++++++++++ .../visitors/constant_folder_visitor.hpp | 45 +++++ src/nmodl/visitors/symtab_visitor_helper.hpp | 5 + test/nmodl/transpiler/visitor/visitor.cpp | 130 +++++++++++++ 14 files changed, 411 insertions(+), 20 deletions(-) create mode 100644 src/nmodl/visitors/constant_folder_visitor.cpp create mode 100644 src/nmodl/visitors/constant_folder_visitor.hpp diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 7c329c77af..7828490561 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -395,7 +395,7 @@ struct AST: public std::enable_shared_from_this<AST> { } virtual bool is_wrapped_expression() { - return true; + return false; } virtual bool is_paren_expression() { diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index eef2827ebf..49bcda4f1c 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -22,6 +22,7 @@ add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} --base-dir ${PROJECT_BINARY_DIR}/src WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${PROJECT_SOURCE_DIR}/src/language/codegen.yaml DEPENDS ${PYCODE} DEPENDS ${TEMPLATE_FILES} COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 96a631af53..a9702468e1 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -43,4 +43,10 @@ - update_states_block: description: "update back states from X" type: StatementBlock + - WrappedExpression: + description: "Wrap any other expression type" + members: + - expression: + description: "Expression that is being wrapped" + type: Expression - Statement: \ No newline at end of file diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 7852ea652e..3646e6ab5c 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -744,12 +744,6 @@ - value: description: "..." type: ReactionOp - - WrappedExpression: - description: ".." - members: - - expression: - description: "..." - type: Expression - ParenExpression: description: ".." members: @@ -948,6 +942,7 @@ - name: description: "..." type: Name + node_name: true - value: description: "..." type: Integer diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 9bf661349c..777bebe3b8 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -77,6 +77,7 @@ "ExternVar", "PrimeName", "ConstantVar", + "Define", } # block nodes which will go into symbol table diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index c24ecf272e..502ebf01cc 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -22,6 +22,7 @@ #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" @@ -75,6 +76,9 @@ int main(int argc, const char* argv[]) { /// true if inlining at nmodl level to be done bool nmodl_inline(false); + /// true if perform constant folding at nmodl level to be done + bool nmodl_const_folding(false); + /// true if range variables to be converted to local bool localize(false); @@ -146,6 +150,7 @@ int main(int argc, const char* argv[]) { auto passes_opt = app.add_subcommand("passes", "Analyse/Optimization passes")->ignore_case(); passes_opt->add_flag("--inline", nmodl_inline, "Perform inlining at NMODL level")->ignore_case(); + passes_opt->add_flag("--const-folding", nmodl_const_folding, "Perform constant folding at NMODL level")->ignore_case(); passes_opt->add_flag("--localize", localize, "Convert RANGE variables to LOCAL")->ignore_case(); passes_opt->add_flag("--localize-verbatim", localize_verbatim, "Convert RANGE variables to LOCAL even if verbatim block exist")->ignore_case(); passes_opt->add_flag("--local-rename", local_rename, "Rename LOCAL variable if variable of same name exist in global scope")->ignore_case(); @@ -260,6 +265,12 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("cnexp")); } + if (nmodl_const_folding) { + logger->info("Running nmodl constant folding visitor"); + ConstantFolderVisitor().visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("constfold")); + } + if (nmodl_inline) { logger->info("Running nmodl inline visitor"); InlineVisitor().visit_program(ast.get()); diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 1d0f7e4f15..52c201b834 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1048,26 +1048,34 @@ varname : name intexpr : Name { $$ = $1; } | integer { $$ = $1; } - | "(" intexpr ")" { $$ = new ast::ParenExpression($2); } + | "(" intexpr ")" + { + auto expr = new ast::ParenExpression($2); + $$ = new ast::WrappedExpression(expr); + } | intexpr "+" intexpr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + $$ = new ast::WrappedExpression(expr); } | intexpr "-" intexpr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + $$ = new ast::WrappedExpression(expr); } | intexpr "*" intexpr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + $$ = new ast::WrappedExpression(expr); } | intexpr "/" intexpr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + $$ = new ast::WrappedExpression(expr); } | error { - + error(scanner.loc, "intexpr"); } ; @@ -1081,26 +1089,35 @@ expr : varname { $$ = $1; } $$ = $1; } | funccall { $$ = $1; } - | "(" expr ")" { $$ = new ast::ParenExpression($2); } + | "(" expr ")" + { + auto expr = new ast::ParenExpression($2); + $$ = new ast::WrappedExpression(expr); + } | expr "+" expr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + $$ = new ast::WrappedExpression(expr); } | expr "-" expr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + $$ = new ast::WrappedExpression(expr); } | expr "*" expr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + $$ = new ast::WrappedExpression(expr); } | expr "/" expr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + $$ = new ast::WrappedExpression(expr); } | expr "^" expr { - $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); + auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); + $$ = new ast::WrappedExpression(expr); } | expr OR expr { diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index e93e8a5e8f..061d901144 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -167,6 +167,10 @@ std::vector<std::string> to_string_vector(const NmodlType& obj) { properties.emplace_back("discrete_block"); } + if (has_property(obj, NmodlType::define)) { + properties.emplace_back("define"); + } + return properties; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 763cd4fad4..639c3489cb 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -211,7 +211,10 @@ enum class NmodlType : enum_type { table_dependent_var = 1L << 32, /** Discrete Block */ - discrete_block = 1L << 33 + discrete_block = 1L << 33, + + /** Define variable / macro */ + define = 1L << 34 }; diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 55cf25ffa3..463db77fb7 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -4,6 +4,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp new file mode 100644 index 0000000000..c9e49b7da6 --- /dev/null +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -0,0 +1,171 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "visitors/constant_folder_visitor.hpp" + + +namespace nmodl { + +/// check if given expression is a number +/// note that the DEFINE node is already expanded to integer +static bool is_number(const std::shared_ptr<ast::Expression>& node) { + return node->is_integer() || node->is_double() || node->is_float(); +} + +/// get value of a number node +/// TODO : eval method can be added to virtual base class +static double get_value(const std::shared_ptr<ast::Expression>& node) { + if (node->is_integer()) { + return std::dynamic_pointer_cast<ast::Integer>(node)->eval(); + } + if (node->is_float()) { + return std::dynamic_pointer_cast<ast::Float>(node)->eval(); + } + if (node->is_double()) { + return std::dynamic_pointer_cast<ast::Double>(node)->eval(); + } + throw std::runtime_error("Invalid type passed to is_number()"); +} + +/// operators that currently implemented +static bool supported_operator(ast::BinaryOp op) { + return op == ast::BOP_ADDITION || op == ast::BOP_SUBTRACTION || op == ast::BOP_MULTIPLICATION || + op == ast::BOP_DIVISION; +} + +/// Evaluate binary operation +/// TODO : add support for other binary operators like ^ (pow) +static double compute(double lhs, ast::BinaryOp op, double rhs) { + switch (op) { + case ast::BOP_ADDITION: + return lhs + rhs; + + case ast::BOP_SUBTRACTION: + return lhs - rhs; + + case ast::BOP_MULTIPLICATION: + return lhs * rhs; + + case ast::BOP_DIVISION: + return lhs / rhs; + + default: + throw std::logic_error("Invalid binary operator in constant folding"); + } +} + +/** + * Visit parenthesis expression and simplify it + * @param node AST node representing an expression with parenthesis + * + * AST could have expression like (1+2). In this case, it has following + * form in the AST : + * + * parenthesis_exp => wrapped_expr => binary_expression => ... + * + * To make constant folding simple, we can remove intermediate wrapped_expr + * and directly replace binary_expression inside parenthesis_exp : + * + * parenthesis_exp => binary_expression => ... + */ +void ConstantFolderVisitor::visit_paren_expression(ast::ParenExpression* node) { + node->visit_children(this); + auto expr = node->get_expression(); + if (expr->is_wrapped_expression()) { + auto e = std::dynamic_pointer_cast<ast::WrappedExpression>(expr); + node->set_expression(e->get_expression()); + } +} + +/** + * Visit wrapped node type and perform constant folding + * @param node AST node that wrap other node types + * + * MOD file has expressions like + * + * a = 1 + 2 + * DEFINE NN 10 + * FROM i=0 TO NN-2 { + * + * } + * + * which need to be turned into + * + * a = 1 + 2 + * DEFINE NN 10 + * FROM i=0 TO 8 { + * + * } + */ +void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* node) { + node->visit_children(this); + + /// first expression which is wrapped + auto expr = node->get_expression(); + + /// if wrapped expressesion is parentheses + bool is_parentheses = false; + + /// opposite to visit_paren_expression, we might have + /// a = (2+1) + /// in this case we can pick inner expression. + if (expr->is_paren_expression()) { + auto e = std::dynamic_pointer_cast<ast::ParenExpression>(expr); + expr = e->get_expression(); + is_parentheses = true; + } + + /// we want to simplify binary expressions only + if (!expr->is_binary_expression()) { + /// wrapped expression might be parenthesis expression like (2) + /// which we can simplify to 2 to help next evaluations + if (is_parentheses) { + node->set_expression(std::move(expr)); + } + return; + } + + auto binary_expr = std::dynamic_pointer_cast<ast::BinaryExpression>(expr); + auto lhs = binary_expr->get_lhs(); + auto rhs = binary_expr->get_rhs(); + auto op = binary_expr->get_op().get_value(); + + /// in case of expression like + /// a = 2 + ((1) + (3)) + /// we are in the innermost expression i.e. ((1) + (3)) + /// where (1) and (3) are wrapped expression themself. we can + /// remove these extra wrapped expressions + + if (lhs->is_wrapped_expression()) { + auto e = std::dynamic_pointer_cast<ast::WrappedExpression>(lhs); + lhs = e->get_expression(); + } + + if (rhs->is_wrapped_expression()) { + auto e = std::dynamic_pointer_cast<ast::WrappedExpression>(rhs); + rhs = e->get_expression(); + } + + /// once we simplify, lhs and rhs must be numbers for constant folding + if (!is_number(lhs) || !is_number(rhs) || !supported_operator(op)) { + return; + } + + /// compute the value of expression + auto value = compute(get_value(lhs), op, get_value(rhs)); + + /// if both operands are not integers or floats, result is double + if (lhs->is_integer() && rhs->is_integer()) { + node->set_expression(std::make_shared<ast::Integer>(int(value), nullptr)); + } else if (lhs->is_double() || rhs->is_double()) { + node->set_expression(std::make_shared<ast::Double>(value)); + } else { + node->set_expression(std::make_shared<ast::Float>(value)); + } +} + +} // namespace nmodl diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp new file mode 100644 index 0000000000..1b5d20b1e0 --- /dev/null +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -0,0 +1,45 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include <stack> +#include <string> + +#include "ast/ast.hpp" +#include "utils/logger.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { + +/** + * \class ConstantFolderVisitor + * \brief Perform constant folding of integer/float/double expressions + * + * MOD file from user could have binary expressions that could be + * expanded at compile time. For example, KINETIC blocks could have + * + * DEFINE NANN 10 + * + * KINETIC states { + * FROM i=0 TO NANN-2 { + * .... + * } + * } + * + * For passes like loop unroll, we need to evaluate NANN-2 at + * compile time and this pass perform such operations. + */ + +class ConstantFolderVisitor: public AstVisitor { + public: + ConstantFolderVisitor() = default; + void visit_wrapped_expression(ast::WrappedExpression* node) override; + void visit_paren_expression(ast::ParenExpression* node) override; +}; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 9ad9eb7a2a..9b93fc89d2 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -119,6 +119,11 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { } } + if (node->is_define()) { + auto define = dynamic_cast<ast::Define*>(node); + symbol->set_value(define->get_value()->to_double()); + } + /// visit children, most likely variables are already /// leaf nodes, not necessary to visit node->visit_children(this); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 192be833e9..7300e05a4e 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -17,6 +17,7 @@ #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" #include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/constant_folder_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" @@ -3168,3 +3169,132 @@ SCENARIO("Sympy specific AST to NMODL conversion") { } } } + + +//============================================================================= +// Constant folding tests +//============================================================================= + +std::string run_constant_folding_visitor(const std::string& text) { + NmodlDriver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +TEST_CASE("Constant Folding Visitor") { + SECTION("Perform Successful Constant Folding") { + GIVEN("Simple integer expression") { + std::string nmodl_text = R"( + PROCEDURE dummy() { + a = 1 + 2 + } + )"; + std::string expected_text = R"( + PROCEDURE dummy() { + a = 3 + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Complex expression") { + std::string nmodl_text = R"( + PROCEDURE dummy() { + a = 1 + (2) + (2 / 2) + (((1+((2))))) + } + )"; + std::string expected_text = R"( + PROCEDURE dummy() { + a = 7 + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Integer expression with define statement") { + std::string nmodl_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = N + (2*N) + (N / 2) + (((1+((N))))) + FROM i = 0 TO N-2 { + } + } + )"; + std::string expected_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = 46 + FROM i = 0 TO 8 { + } + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Only fold part of the statement") { + std::string nmodl_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = N + 2.0 + b + c = a + d + d = 2^3 + e = 2 || 3 + } + )"; + std::string expected_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = 12+b + c = a+d + d = 2^3 + e = 2 || 3 + } + )"; + THEN("successfully folds and keep other statements untouched") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Don't remove parentheses if not simplifying") { + std::string nmodl_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = ((N+1)+5)*(c+1+N)/(b - 2) + } + )"; + std::string expected_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = 16*(c+1+10)/(b-2) + } + )"; + THEN("successfully folds and keep other statements untouched") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + } +} From 952e7c9e97cbd5fb04d1a51308a5841f099f089b Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Fri, 15 Mar 2019 15:58:30 +0100 Subject: [PATCH 164/871] Add newton_numerical_diff Newton Solver (BlueBrain/nmodl#66) - alternative Newton Solver that - takes a functor that evaluates F(X), but - doesn't require the analytic Jacobian to be evaluated, instead - numerically approximates it with a symmetric difference (like the scopmath newton solver) - add same test cases as for Newton solver, without the analytic Jacobian Use case: - for derivimplicit when sympy fails or it cannot be used due to VERBATIM block etc. - as a consistency check for sympy derivimplicit solver Todo: - add codegen for a version of derivimplicit that uses this instead of the scopmath solver NMODL Repo SHA: BlueBrain/nmodl@58910481720d726b8bd1b8f58f88281b4465c734 --- src/nmodl/nmodl/nmodl.hpp | 2 +- src/nmodl/solver/newton/newton.hpp | 110 +++++++++++--- test/nmodl/transpiler/newton/newton.cpp | 191 +++++++++++++++++++++++- 3 files changed, 276 insertions(+), 27 deletions(-) diff --git a/src/nmodl/nmodl/nmodl.hpp b/src/nmodl/nmodl/nmodl.hpp index 82abf643b6..6d7ff2a38f 100644 --- a/src/nmodl/nmodl/nmodl.hpp +++ b/src/nmodl/nmodl/nmodl.hpp @@ -16,4 +16,4 @@ #define EIGEN_DEFAULT_DENSE_INDEX_TYPE int #endif -#include "newton/newton.hpp" +#include "newton/newton.hpp" \ No newline at end of file diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 7d5303a7bb..7309f1e2d8 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -7,12 +7,11 @@ #pragma once -// Implementation of Newton method for solving -// system of non-linear equations using Eigen +// Implementation of Newton method for solving system of non-linear equations using Eigen +// - newton_solver is the preferred option: requires user to provide Jacobian +// - newton_numerical_diff_solver is the fallback option: Jacobian not required -#include <iostream> - -#include "Eigen/LU" +#include <Eigen/LU> namespace nmodl { namespace newton { @@ -20,12 +19,12 @@ namespace newton { constexpr int MAX_ITER = 1e3; constexpr double EPS = 1e-12; -// Newton method +// Newton method with user-provided Jacobian: // given initial vector X // and a functor that calculates F(X), J(X) +// where J(X) is the Jacobian of F(X), // solves for F(X) = 0, // starting with initial value of X -// where J(X) is the Jacobian of F(X) // by iterating: // X_{n+1} = X_n - J(X_n)^{-1} F(X_n) // @@ -42,15 +41,11 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X, Eigen::Matrix<double, N, N> J; // Solver iteration count: int iter = -1; - while (++iter < max_iter) { // calculate F, J from X using user-supplied functor functor(X, F, J); // get error norm: here we use sqrt(|F|^2) double error = F.norm(); - // std::cout.precision(15); - // std::cout << "[newton_solver] iter: " << iter << "\terror: " << error << "\n"; - // std::cout << "\n" << X << std::endl; if (error < eps) { // we have converged: return iteration count return iter; @@ -64,11 +59,84 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X, return -1; } +// Newton method without user-provided Jacobian: +// given initial vector X +// and a functor that calculates F(X), +// solves for F(X) = 0, +// starting with initial value of X +// by iterating: +// X_{n+1} = X_n - J(X_n)^{-1} F(X_n) +// where J(X) is the Jacobian of F(X), which is +// approximated numerically using a symmetric +// finite difference approximation to the derivative + +// when |F|^2 < eps^2, solution has converged +// returns number of iterations (-1 if failed to converge) +constexpr double SQUARE_ROOT_ULP = 1e-7; +constexpr double CUBIC_ROOT_ULP = 1e-5; +template <int N, typename FUNC> +EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix<double, N, 1>& X, + FUNC functor, + double eps = EPS, + int max_iter = MAX_ITER) { + // Vector to store result of function F(X): + Eigen::Matrix<double, N, 1> F; + // Temporary storage for F(X+dx) + Eigen::Matrix<double, N, 1> F_p; + // Temporary storage for F(X-dx) + Eigen::Matrix<double, N, 1> F_m; + // Matrix to store jacobian of F(X): + Eigen::Matrix<double, N, N> J; + // Solver iteration count: + int iter = 0; + while (iter < max_iter) { + // calculate F from X using user-supplied functor + functor(X, F); + // get error norm: here we use sqrt(|F|^2) + double error = F.norm(); + if (error < eps) { + // we have converged: return iteration count + return iter; + } + ++iter; + // calculate approximate Jacobian + for (int i = 0; i < N; ++i) { + // symmetric finite difference approximation to derivative + // df/dx ~= ( f(x+dx) - f(x-dx) ) / (2*dx) + // choose dx to be ~(ULP)^{1/3}*X[i] + // https://aip.scitation.org/doi/pdf/10.1063/1.4822971 + // also enforce a lower bound ~sqrt(ULP) to avoid dx being too small + double dX = std::max(CUBIC_ROOT_ULP * X[i], SQUARE_ROOT_ULP); + // F(X + dX) + X[i] += dX; + functor(X, F_p); + // F(X - dX) + X[i] -= 2.0 * dX; + functor(X, F_m); + F_p -= F_m; + // J = (F(X + dX) - F(X - dX)) / (2*dX) + J.col(i) = F_p / (2.0 * dX); + // restore X + X[i] += dX; + } + // update X + // use in-place LU decomposition of J with partial pivoting + // (suitable for any N, but less efficient than .inverse() for N <=4) + X -= Eigen::PartialPivLU<Eigen::Ref<Eigen::Matrix<double, N, N>>>(J).solve(F); + } + // If we fail to converge after max_iter iterations, return -1 + return -1; +} + +// Newton method template specializations for N <= 4 +// Use explicit inverse of F instead of LU decomposition +// This is faster, as there is no pivoting and therefore no branches, +// but it is not numerically safe for N > 4 template <typename FUNC, int N> -EIGEN_DEVICE_FUNC int solver(Eigen::Matrix<double, N, 1>& X, - FUNC functor, - double eps, - int max_iter) { +EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix<double, N, 1>& X, + FUNC functor, + double eps, + int max_iter) { Eigen::Matrix<double, N, 1> F; Eigen::Matrix<double, N, N> J; int iter = -1; @@ -82,16 +150,12 @@ EIGEN_DEVICE_FUNC int solver(Eigen::Matrix<double, N, 1>& X, } return -1; } - -// template specializations for N <= 4 -// use explicit inverse of F instead of LU decomposition -// (more efficient for small matrices - not safe for large matrices) template <typename FUNC> EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 1, 1>& X, FUNC functor, double eps = EPS, int max_iter = MAX_ITER) { - return solver<FUNC, 1>(X, functor, eps, max_iter); + return newton_solver_small_N<FUNC, 1>(X, functor, eps, max_iter); } template <typename FUNC> @@ -99,7 +163,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 2, 1>& X, FUNC functor, double eps = EPS, int max_iter = MAX_ITER) { - return solver<FUNC, 2>(X, functor, eps, max_iter); + return newton_solver_small_N<FUNC, 2>(X, functor, eps, max_iter); } template <typename FUNC> @@ -107,7 +171,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 3, 1>& X, FUNC functor, double eps = EPS, int max_iter = MAX_ITER) { - return solver<FUNC, 3>(X, functor, eps, max_iter); + return newton_solver_small_N<FUNC, 3>(X, functor, eps, max_iter); } template <typename FUNC> @@ -115,7 +179,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 4, 1>& X, FUNC functor, double eps = EPS, int max_iter = MAX_ITER) { - return solver<FUNC, 4>(X, functor, eps, max_iter); + return newton_solver_small_N<FUNC, 4>(X, functor, eps, max_iter); } } // namespace newton diff --git a/test/nmodl/transpiler/newton/newton.cpp b/test/nmodl/transpiler/newton/newton.cpp index 4978df0755..1b06231d16 100644 --- a/test/nmodl/transpiler/newton/newton.cpp +++ b/test/nmodl/transpiler/newton/newton.cpp @@ -12,12 +12,190 @@ #include "catch/catch.hpp" #include "nmodl/nmodl.hpp" - using namespace nmodl; constexpr double max_error_norm = 1e-12; -SCENARIO("Non-linear system to solve with Newton") { +SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numerical]") { + GIVEN("1 linear eq") { + struct functor { + void operator()(const Eigen::Matrix<double, 1, 1>& X, + Eigen::Matrix<double, 1, 1>& F) const { + // Function F(X) to find F(X)=0 solution + F[0] = -3.0 * X[0] + 3.0; + } + }; + Eigen::Matrix<double, 1, 1> X{22.2536}; + Eigen::Matrix<double, 1, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find the solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(X[0] == Approx(1.0)); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("1 non-linear eq") { + struct functor { + void operator()(const Eigen::Matrix<double, 1, 1>& X, + Eigen::Matrix<double, 1, 1>& F) const { + F[0] = -3.0 * X[0] + std::sin(X[0]) + std::log(X[0] * X[0] + 11.435243) + 3.0; + } + }; + Eigen::Matrix<double, 1, 1> X{-0.21421}; + Eigen::Matrix<double, 1, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find the solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(X[0] == Approx(2.19943987001206)); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 2 non-linear eqs") { + struct functor { + void operator()(const Eigen::Matrix<double, 2, 1>& X, + Eigen::Matrix<double, 2, 1>& F) const { + F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1] - 1.0; + F[1] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[1] + 0.4; + } + }; + Eigen::Matrix<double, 2, 1> X{0.2, 0.4}; + Eigen::Matrix<double, 2, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 3 non-linear eqs") { + struct functor { + double _x_old = 0.5; + double _y_old = -231.5; + double _z_old = 1.4565; + double dt = 0.2; + double a = 0.2354; + double b = 436.2; + double d = 23.1; + double e = 0.01; + double z = 0.99; + void operator()(const Eigen::Matrix<double, 3, 1>& X, + Eigen::Matrix<double, 3, 1>& F) const { + F(0) = -(-_x_old - dt * (a * std::pow(X[0], 2) + X[1]) + X[0]); + F(1) = -(_y_old - dt * (a * X[0] + b * X[1] + d) + X[1]); + F(2) = -(_z_old - dt * (e * z - 3.0 * X[0] + 2.0 * X[1]) + X[2]); + } + }; + Eigen::Matrix<double, 3, 1> X{0.21231, 0.4435, -0.11537}; + Eigen::Matrix<double, 3, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 4 non-linear eqs") { + struct functor { + double _X0_old = 1.2345; + double _X1_old = 1.2345; + double _X2_old = 1.2345; + double _X3_old = 1.2345; + double dt = 0.2; + void operator()(const Eigen::Matrix<double, 4, 1>& X, + Eigen::Matrix<double, 4, 1>& F) const { + F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - _X0_old + 2.0 * dt / X[1]); + F[1] = -(X[1] - _X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); + F[2] = -((X[2] * (X[2] - _X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / + X[2]); + F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - _X3_old + 6.0 * dt / X[2]); + } + }; + Eigen::Matrix<double, 4, 1> X{0.21231, 0.4435, -0.11537, -0.8124312}; + Eigen::Matrix<double, 4, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 5 non-linear eqs") { + struct functor { + void operator()(const Eigen::Matrix<double, 5, 1>& X, + Eigen::Matrix<double, 5, 1>& F) const { + F[0] = -3.0 * X[0] * X[2] + X[0] + 2.0 / X[1]; + F[1] = 4.0 * X[0] - 5.2 * X[1] + X[3]; + F[2] = 1.2 * X[1] + X[2] - 3.0 - 0.3 / X[2]; + F[3] = -4.0 * X[0] * X[1] * X[2] + X[3] + 6.0 / X[2]; + F[4] = (-4.0 * X[0] + (X[4] + cos(X[1])) * (X[1] * X[2] - X[3] * X[4])) / + (X[1] * X[2] - X[3] * X[4]); + } + }; + Eigen::Matrix<double, 5, 1> X; + X << 8.234, -245.46, 123.123, 0.8343, 5.555; + Eigen::Matrix<double, 5, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(F.norm() < max_error_norm); + } + } + GIVEN("system of 10 non-linear eqs") { + struct functor { + void operator()(const Eigen::Matrix<double, 10, 1>& X, + Eigen::Matrix<double, 10, 1>& F) const { + F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1]; + F[1] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[1]; + F[2] = 2.0 * X[1] + X[2] + 2.0 * X[3] * X[5] * X[7] - 3.0 * X[4] * X[8] - X[5]; + F[3] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[3] - + X[4] * X[6] * X[7]; + F[4] = -3.0 * X[0] * X[7] + 2.0 * X[1] - 4.0 * X[3] * X[8] + X[4]; + F[5] = -X[2] * X[5] * X[8] + 4.0 * X[3] - 0.29999999999999999 * X[4] * X[9] + X[5]; + F[6] = -3.0 * X[0] * X[1] - 2.1000000000000001 * X[3] * X[4] * X[5] + X[6] + + 2.0 * X[8]; + F[7] = 4.0 * X[0] - 0.29999999999999999 * X[6] * X[7] + X[7]; + F[8] = -3.0 * X[0] * X[1] - X[2] * X[3] * X[4] * std::pow(X[5], 2) + 2.0 * X[5] + + X[8]; + F[9] = -0.29999999999999999 * X[2] * X[4] + 4.0 * std::pow(X[9], 2) + X[9]; + } + }; + Eigen::Matrix<double, 10, 1> X; + X << 8.234, -5.46, 1.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; + Eigen::Matrix<double, 10, 1> F; + functor fn; + int iter_newton = newton::newton_numerical_diff_solver(X, fn); + fn(X, F); + THEN("find a solution") { + CAPTURE(iter_newton); + CAPTURE(X); + REQUIRE(iter_newton > 0); + REQUIRE(F.norm() < max_error_norm); + } + } +} + +SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { GIVEN("1 linear eq") { struct functor { void operator()(const Eigen::Matrix<double, 1, 1>& X, @@ -38,6 +216,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find the solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(X[0] == Approx(1.0)); REQUIRE(F.norm() < max_error_norm); } @@ -60,6 +239,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find the solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(X[0] == Approx(2.19943987001206)); REQUIRE(F.norm() < max_error_norm); } @@ -86,6 +266,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(F.norm() < max_error_norm); } } @@ -126,6 +307,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(F.norm() < max_error_norm); } } @@ -171,6 +353,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(F.norm() < max_error_norm); } } @@ -223,6 +406,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(F.norm() < max_error_norm); } } @@ -347,7 +531,7 @@ SCENARIO("Non-linear system to solve with Newton") { } }; Eigen::Matrix<double, 10, 1> X; - X << 8.234, -245.46, 123.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; + X << 8.234, -5.46, 1.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; Eigen::Matrix<double, 10, 1> F; Eigen::Matrix<double, 10, 10> J; functor fn; @@ -356,6 +540,7 @@ SCENARIO("Non-linear system to solve with Newton") { THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); + REQUIRE(iter_newton > 0); REQUIRE(F.norm() < max_error_norm); } } From 4eacfa07e3d72463f128197c4bd1d2dc16506889 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@gmail.com> Date: Fri, 15 Mar 2019 19:11:10 +0100 Subject: [PATCH 165/871] Introducing ISPC code generation backend (BlueBrain/nmodl#61) * Added backend wrapper mechanism - while not really used in C, other targets (CUDA, ispc, ...) might need C wrapper functions to interact with coreneuron. - added addition code printer member and virtual member function for wrappers. * Added ISPC code generation visitor - renamed printer for variable qualifiers - overrode more methods from C codegen to specialize for ispc - added ispc and host target command line argument - added emitter for data structures * Updates to C and ispc visitors : - split net_receive printing routine into two parts so that backends can only print compute kernels - update ispc backend for atomic operations in nrn_cur - added placeholder data structures from coreneuron - added tweaks to output in float literals and functions for numerics - added common structs and includes to fast_math - parameter declaration tweaking to be able to add ispc qualifiers (wip) - generalized function parameter generation - added a number of changes in the net_receive method generators that make them more generic and usable in the context of accelerators and wrappers - simplified the way net_receive functions access NrnThread and Memb_list - implemented specializations of net_receive methods for ISPC - switched from strings to parameter building in more places * Added fast_math.ispc with exp implementation based on VDT and install into include dir NMODL Repo SHA: BlueBrain/nmodl@603880f4c0a648ad8252e00684b276a3cbdc23de --- cmake/nmodl/CMakeLists.txt | 8 + cmake/nmodl/RpathHelper.cmake | 28 + src/nmodl/codegen/CMakeLists.txt | 7 + src/nmodl/codegen/codegen_c_visitor.cpp | 266 ++++++--- src/nmodl/codegen/codegen_c_visitor.hpp | 105 +++- src/nmodl/codegen/codegen_cuda_visitor.cpp | 4 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 1 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 629 +++++++++++++++++++++ src/nmodl/codegen/codegen_ispc_visitor.hpp | 170 ++++++ src/nmodl/codegen/fast_math.ispc | 147 +++++ src/nmodl/nmodl/main.cpp | 18 + 11 files changed, 1278 insertions(+), 105 deletions(-) create mode 100644 cmake/nmodl/RpathHelper.cmake create mode 100644 src/nmodl/codegen/codegen_ispc_visitor.cpp create mode 100644 src/nmodl/codegen/codegen_ispc_visitor.hpp create mode 100644 src/nmodl/codegen/fast_math.ispc diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 58ad718c4b..4ab316c1c5 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,3 +1,10 @@ +# ============================================================================= +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. +# See top-level LICENSE file for details. +# ============================================================================= + cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR) project(NMODL CXX) @@ -48,6 +55,7 @@ include(FindClangFormat) include(FindPythonModule) include(FlexHelper) include(GitRevision) +include(RpathHelper) # ============================================================================= # Find required python packages diff --git a/cmake/nmodl/RpathHelper.cmake b/cmake/nmodl/RpathHelper.cmake new file mode 100644 index 0000000000..761441af6f --- /dev/null +++ b/cmake/nmodl/RpathHelper.cmake @@ -0,0 +1,28 @@ +# ============================================================================= +# Copyright (C) 2018-2019 Blue Brain Project +# +# This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. +# See top-level LICENSE file for details. +# ============================================================================= + +# ============================================================================= +# Set full RPATHs in build-tree, also set RPATHs in install for non-system libs +# ============================================================================= + +# use, i.e. don't skip the full RPATH for the build tree +set(CMAKE_SKIP_BUILD_RPATH FALSE) + +# when building, don't use the install RPATH already (but later on when installing) +set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) + +set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") + +# add the automatically determined parts of the RPATH which point to directories outside the build +# tree to the install RPATH +set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) + +# the RPATH to be used when installing, but only if it's not a system directory +list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) +if("${isSystemDir}" STREQUAL "-1") + set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") +endif("${isSystemDir}" STREQUAL "-1") diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 337f183565..5d7b40f7ad 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -14,6 +14,8 @@ set(CODEGEN_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/codegen_helper_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_ispc_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_ispc_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp) # ============================================================================= @@ -22,3 +24,8 @@ set(CODEGEN_SOURCE_FILES add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) add_dependencies(codegen lexer util) + +# ============================================================================= +# Install include files +# ============================================================================= +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc DESTINATION include/nmodl) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 0fe283c8f0..47fb851e61 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -65,7 +65,8 @@ void CodegenCVisitor::visit_float(Float* node) { if (!codegen) { return; } - printer->add_text(std::to_string(node->eval())); + auto value = node->eval(); + printer->add_text(float_to_string(value)); } @@ -181,6 +182,7 @@ void CodegenCVisitor::visit_else_statement(ElseStatement* node) { node->visit_children(this); } + void CodegenCVisitor::visit_while_statement(WhileStatement* node) { printer->add_text("while ("); node->get_condition()->accept(this); @@ -188,6 +190,7 @@ void CodegenCVisitor::visit_while_statement(WhileStatement* node) { node->get_statement_block()->accept(this); } + void CodegenCVisitor::visit_from_statement(ast::FromStatement* node) { if (!codegen) { return; @@ -444,6 +447,14 @@ std::string CodegenCVisitor::double_to_string(double value) { } +std::string CodegenCVisitor::float_to_string(float value) { + if (ceilf(value) == value) { + return "{:.1f}f"_format(value); + } + return "{:.16g}f"_format(value); +} + + /** * Check if given statement needs semicolon at the end of statement * @@ -967,6 +978,21 @@ std::vector<SymbolType> CodegenCVisitor::get_shadow_variables() { /* Routines must be overloaded in backend */ /****************************************************************************************/ +/** + * Print parameters + */ +std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { + std::stringstream param_ss; + for (auto iter = params.begin(); iter != params.end(); iter++) { + param_ss << "{}{} {}{}"_format(std::get<0>(*iter), std::get<1>(*iter), std::get<2>(*iter), + std::get<3>(*iter)); + if (!is_last(iter, params)) { + param_ss << ", "; + } + } + return param_ss.str(); +} + void CodegenCVisitor::print_channel_iteration_task_begin(BlockType type) { // backend specific, do nothing @@ -1225,7 +1251,7 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) { // note extra empty space for pretty-printing if we skip the symbol -std::string CodegenCVisitor::k_restrict() { +std::string CodegenCVisitor::ptr_type_qualifier() { return "__restrict__ "; } @@ -1418,7 +1444,8 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_newline(2); print_device_method_annotation(); - printer->start_block("void check_{}({})"_format(method_name(name), internal_params)); + printer->start_block( + "void check_{}({})"_format(method_name(name), get_parameter_str(internal_params))); { printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); printer->add_line(" return;"); @@ -1482,6 +1509,7 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->end_block(1); } + void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { auto name = node->get_node_name(); auto statement = get_table_statement(node); @@ -1566,6 +1594,7 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line("}"); } + void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& name) { printer->add_newline(2); print_function_declaration(node, name); @@ -1585,6 +1614,7 @@ void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& printer->end_block(1); } + void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { codegen = true; auto name = node->get_node_name(); @@ -1616,6 +1646,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = false; } + void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) { // solution vector to store copy of state vars for Newton solver printer->add_newline(); @@ -1669,14 +1700,32 @@ std::string CodegenCVisitor::internal_method_arguments() { } -std::string CodegenCVisitor::internal_method_parameters() { - std::string ion_var_arg; +std::string CodegenCVisitor::param_type_qualifier() { + return ""; +} + + +std::string CodegenCVisitor::param_ptr_qualifier() { + return ""; +} + + +/// @todo: figure out how to correctly handle qualifiers +CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { + auto params = ParamVector(); + params.emplace_back("", "int", "", "id"); + params.emplace_back(param_type_qualifier(), "int", "", "pnodecount"); + params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), + param_ptr_qualifier(), "inst"); if (ion_variable_struct_required()) { - ion_var_arg = " IonCurVar& ionvar,"; + params.emplace_back("", "IonCurVar&", "", "ionvar"); } - return "int id, int pnodecount, {}* inst,{} double* data, " - "{}Datum* indexes, ThreadDatum* thread, " - "NrnThread* nt, double v"_format(instance_struct(), ion_var_arg, k_const()); + params.emplace_back("", "double*", "", "data"); + params.emplace_back(k_const(), "Datum*", "", "indexes"); + params.emplace_back(param_type_qualifier(), "ThreadDatum*", "", "thread"); + params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); + params.emplace_back("", "double", "", "v"); + return params; } @@ -1743,6 +1792,7 @@ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { return name; } + /** * Processing commonly used constructs in the verbatim blocks. * @todo : this is still ad-hoc and requires re-implementation to @@ -2349,7 +2399,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { } if (info.vectorize) { - printer->add_line("ThreadDatum* {}ext_call_thread;"_format(k_restrict())); + printer->add_line("ThreadDatum* {}ext_call_thread;"_format(ptr_type_qualifier())); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } @@ -2558,7 +2608,8 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("add_nrn_artcell(mech_type, {});"_format(info.tqitem_index)); } if (net_receive_buffering_required()) { - printer->add_line("hoc_register_net_receive_buffering(net_buf_receive, mech_type);"); + printer->add_line("hoc_register_net_receive_buffering({}, mech_type);"_format( + method_name("net_buf_receive"))); } if (info.num_net_receive_parameters != 0) { printer->add_line("pnt_receive[mech_type] = {};"_format(method_name("net_receive"))); @@ -2650,23 +2701,24 @@ void CodegenCVisitor::print_mechanism_range_var_structure() { auto name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? k_const() : ""; - printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); + printer->add_line("{}{}* {}{};"_format(qualifier, type, ptr_type_qualifier(), name)); } for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? k_const() : ""; - printer->add_line("{}{}* {}{};"_format(qualifier, int_type, k_restrict(), name)); + printer->add_line( + "{}{}* {}{};"_format(qualifier, int_type, ptr_type_qualifier(), name)); } else { auto qualifier = var.is_constant ? k_const() : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->add_line("{}{}* {}{};"_format(qualifier, type, k_restrict(), name)); + printer->add_line("{}{}* {}{};"_format(qualifier, type, ptr_type_qualifier(), name)); } } if (channel_task_dependency_enabled()) { for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); - printer->add_line("{}* {}{};"_format(float_type, k_restrict(), name)); + printer->add_line("{}* {}{};"_format(float_type, ptr_type_qualifier(), name)); } } printer->end_block(); @@ -3011,30 +3063,32 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { print_kernel_data_present_annotation_block_begin(); printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); - printer->add_line("{}int* {}node_index = ml->nodeindices;"_format(k_const(), k_restrict())); - printer->add_line("double* {}data = ml->data;"_format(k_restrict())); - printer->add_line("{}double* {}voltage = nt->_actual_v;"_format(k_const(), k_restrict())); + printer->add_line( + "{}int* {}node_index = ml->nodeindices;"_format(k_const(), ptr_type_qualifier())); + printer->add_line("double* {}data = ml->data;"_format(ptr_type_qualifier())); + printer->add_line( + "{}double* {}voltage = nt->_actual_v;"_format(k_const(), ptr_type_qualifier())); if (type == BlockType::Equation) { - printer->add_line("double* {} vec_rhs = nt->_actual_rhs;"_format(k_restrict())); - printer->add_line("double* {} vec_d = nt->_actual_d;"_format(k_restrict())); + printer->add_line("double* {} vec_rhs = nt->_actual_rhs;"_format(ptr_type_qualifier())); + printer->add_line("double* {} vec_d = nt->_actual_d;"_format(ptr_type_qualifier())); print_rhs_d_shadow_variables(); } - printer->add_line("Datum* {}indexes = ml->pdata;"_format(k_restrict())); - printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(k_restrict())); + printer->add_line("Datum* {}indexes = ml->pdata;"_format(ptr_type_qualifier())); + printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(ptr_type_qualifier())); if (type == BlockType::Initial) { printer->add_newline(); printer->add_line("setup_instance(nt, ml);"); } // clang-format off - printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), k_restrict())); + printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); // clang-format on printer->add_newline(1); } -void CodegenCVisitor::print_nrn_init() { +void CodegenCVisitor::print_nrn_init(bool skip_init_check) { codegen = true; printer->add_newline(2); printer->add_line("/** initialize channel */"); @@ -3053,7 +3107,9 @@ void CodegenCVisitor::print_nrn_init() { // clang-format on } - printer->start_block("if (_nrn_skip_initmodel == 0)"); + if (skip_init_check) { + printer->start_block("if (_nrn_skip_initmodel == 0)"); + } print_channel_iteration_tiling_block_begin(BlockType::Initial); print_channel_iteration_block_begin(); @@ -3075,7 +3131,9 @@ void CodegenCVisitor::print_nrn_init() { printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); } print_kernel_data_present_annotation_block_end(); - printer->end_block(1); + if (skip_init_check) { + printer->end_block(1); + } codegen = false; } @@ -3134,12 +3192,12 @@ void CodegenCVisitor::print_watch_activate() { codegen = false; } + /** * Print kernel for watch activation * todo : similar to print_watch_activate, we are using only * first watch. need to verify with neuron/coreneuron about rest. */ - void CodegenCVisitor::print_watch_check() { if (info.watch_statements.empty()) { return; @@ -3207,19 +3265,23 @@ void CodegenCVisitor::print_watch_check() { } -void CodegenCVisitor::print_net_receive_common_code(Block* node) { +void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_inst) { printer->add_line("int tid = pnt->_tid;"); printer->add_line("int id = pnt->_i_instance;"); printer->add_line("double v = 0;"); - printer->add_line("NrnThread* nt = nrn_threads + tid;"); - printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); - printer->add_line("int nodecount = ml->nodecount;"); - printer->add_line("int pnodecount = ml->_nodecount_padded;"); + if (info.artificial_cell) { + printer->add_line("NrnThread* nt = nrn_threads + tid;"); + printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); + } + printer->add_line("{}int nodecount = ml->nodecount;"_format(param_type_qualifier())); + printer->add_line("{}int pnodecount = ml->_nodecount_padded;"_format(param_type_qualifier())); printer->add_line("double* data = ml->data;"); printer->add_line("double* weights = nt->weights;"); printer->add_line("Datum* indexes = ml->pdata;"); printer->add_line("ThreadDatum* thread = ml->_thread;"); - printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + if (need_mech_inst) { + printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + } /// rename variables but need to see if they are actually used auto parameters = info.net_receive_node->get_parameters(); @@ -3338,6 +3400,7 @@ void CodegenCVisitor::print_net_init() { codegen = false; } + void CodegenCVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); @@ -3358,34 +3421,59 @@ void CodegenCVisitor::print_send_event_move() { // todo : update net send buffer count on device } -void CodegenCVisitor::print_net_receive_buffering() { - if (!net_receive_required() || info.artificial_cell) { - return; - } - printer->add_newline(2); - printer->start_block("static inline void net_buf_receive(NrnThread* nt)"); + +std::string CodegenCVisitor::net_receive_buffering_declaration() { + return "void {}(NrnThread* nt)"_format(method_name("net_buf_receive")); +} + + +void CodegenCVisitor::print_get_memb_list() { printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->add_line("if (ml == NULL) {"); printer->add_line(" return;"); printer->add_line("}"); printer->add_newline(); +} - auto net_receive = method_name("net_receive_kernel"); - printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); + +void CodegenCVisitor::print_net_receive_loop_begin() { printer->add_line("int count = nrb->_displ_cnt;"); - printer->add_line("for (int i = 0; i < count; i++) {"); - printer->add_line(" int start = nrb->_displ[i];"); - printer->add_line(" int end = nrb->_displ[i+1];"); - printer->add_line(" for (int j = start; j < end; j++) {"); - printer->add_line(" int index = nrb->_nrb_index[j];"); - printer->add_line(" int offset = nrb->_pnt_index[index];"); - printer->add_line(" double t = nrb->_nrb_t[index];"); - printer->add_line(" int weight_index = nrb->_weight_index[index];"); - printer->add_line(" double flag = nrb->_nrb_flag[index];"); - printer->add_line(" Point_process* point_process = nt->pntprocs + offset;"); - printer->add_line(" {}(t, point_process, weight_index, flag);"_format(net_receive)); - printer->add_line(" }"); - printer->add_line("}"); + printer->start_block("for (int i = 0; i < count; i++)"); +} + + +void CodegenCVisitor::print_net_receive_loop_end() { + printer->end_block(1); +} + + +void CodegenCVisitor::print_net_receive_buffering() { + if (!net_receive_required() || info.artificial_cell) { + return; + } + printer->add_newline(2); + printer->start_block(net_receive_buffering_declaration()); + + print_get_memb_list(); + + auto net_receive = method_name("net_receive_kernel"); + + printer->add_line( + "NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;"_format(ptr_type_qualifier())); + print_net_receive_loop_begin(); + printer->add_line("int start = nrb->_displ[i];"); + printer->add_line("int end = nrb->_displ[i+1];"); + printer->start_block("for (int j = start; j < end; j++)"); + printer->add_line("int index = nrb->_nrb_index[j];"); + printer->add_line("int offset = nrb->_pnt_index[index];"); + printer->add_line("double t = nrb->_nrb_t[index];"); + printer->add_line("int weight_index = nrb->_weight_index[index];"); + printer->add_line("double flag = nrb->_nrb_flag[index];"); + printer->add_line("Point_process* point_process = nt->pntprocs + offset;"); + printer->add_line( + "{}(t, point_process, inst, nt, ml, weight_index, flag);"_format(net_receive)); + printer->end_block(1); + print_net_receive_loop_end(); if (info.net_send_used || info.net_event_used) { print_send_event_move(); @@ -3427,13 +3515,15 @@ void CodegenCVisitor::print_net_send_buffering() { } -void CodegenCVisitor::print_net_receive() { +void CodegenCVisitor::print_net_receive_kernel() { if (!net_receive_required()) { return; } + codegen = true; + printing_net_receive = true; auto node = info.net_receive_node; - /// rename arguments but need to see if they are actually used + /// rename arguments if same name is used auto parameters = node->get_parameters(); for (auto& parameter: parameters) { auto name = parameter->get_node_name(); @@ -3444,39 +3534,57 @@ void CodegenCVisitor::print_net_receive() { } } - codegen = true; - printing_net_receive = true; - std::string function_name = method_name("net_receive_kernel"); - std::string function_arguments = "double t, Point_process* pnt, int weight_index, double flag"; - - if (info.artificial_cell) { - function_name = method_name("net_receive"); - function_arguments = "Point_process* pnt, int weight_index, double flag"; + std::string name; + auto params = ParamVector(); + if (!info.artificial_cell) { + name = method_name("net_receive_kernel"); + params.emplace_back("", "double", "", "t"); + params.emplace_back("", "Point_process*", "", "pnt"); + params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), + param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); + params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); + params.emplace_back("", "int", "", "weight_index"); + params.emplace_back("", "double", "", "flag"); + } else { + name = method_name("net_receive"); + params.emplace_back("", "Point_process*", "", "pnt"); + params.emplace_back("", "int", "", "weight_index"); + params.emplace_back("", "double", "", "flag"); } - /// net receive kernel printer->add_newline(2); - printer->start_block("static void {}({}) "_format(function_name, function_arguments)); - print_net_receive_common_code(node); - + printer->start_block("static inline void {}({}) "_format(name, get_parameter_str(params))); + print_net_receive_common_code(node, info.artificial_cell); if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); } - printer->add_line("{} = t;"_format(get_variable_name("tsave"))); - printer->add_indent(); node->get_statement_block()->accept(this); printer->add_newline(); printer->end_block(); printer->add_newline(); - /// net receive function + printing_net_receive = false; + codegen = false; +} + + +void CodegenCVisitor::print_net_receive() { + if (!net_receive_required()) { + return; + } + codegen = true; + printing_net_receive = true; if (!info.artificial_cell) { - function_name = method_name("net_receive"); - function_arguments = "Point_process* pnt, int weight_index, double flag"; + std::string name = method_name("net_receive"); + auto params = ParamVector(); + params.emplace_back("", "Point_process*", "", "pnt"); + params.emplace_back("", "int", "", "weight_index"); + params.emplace_back("", "double", "", "flag"); printer->add_newline(2); - printer->start_block("static void {}({}) "_format(function_name, function_arguments)); + printer->start_block("static void {}({}) "_format(name, get_parameter_str(params))); printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); @@ -3662,7 +3770,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { auto block = node->get_statement_block().get(); printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline double nrn_current({})"_format(args)); + printer->start_block("static inline double nrn_current({})"_format(get_parameter_str(args))); printer->add_line("double current = 0.0;"); print_statement_block(block, false, false); for (auto& current: info.currents) { @@ -3812,7 +3920,6 @@ void CodegenCVisitor::print_nrn_cur() { /* Main code printing entry points */ /****************************************************************************************/ - void CodegenCVisitor::print_headers_include() { print_standard_includes(); print_backend_includes(); @@ -3863,6 +3970,7 @@ void CodegenCVisitor::print_compute_functions() { print_net_send_buffering(); print_watch_activate(); print_watch_check(); + print_net_receive_kernel(); print_net_receive(); print_net_receive_buffering(); print_nrn_init(); @@ -3893,6 +4001,11 @@ void CodegenCVisitor::print_codegen_routines() { } +void CodegenCVisitor::print_wrapper_routines() { + // nothing to do +} + + void CodegenCVisitor::visit_program(Program* node) { program_symtab = node->get_symbol_table(); @@ -3907,6 +4020,7 @@ void CodegenCVisitor::visit_program(Program* node) { update_index_semantics(); rename_function_arguments(); print_codegen_routines(); + print_wrapper_routines(); } } // namespace codegen diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 2075ddc314..5212e8683b 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -108,7 +108,7 @@ struct IndexVariableInfo { /** * \enum LayoutType - * \brief Represent memory layout to use for code generation + * \brief Represents memory layout to use for code generation * */ enum class LayoutType { @@ -122,9 +122,9 @@ enum class LayoutType { /** * \class ShadowUseStatement - * \brief Represent ion write statement during code generation + * \brief Represents ion write statement during code generation * - * Ion update statement need use of shadow vectors for certain backends + * Ion update statement needs use of shadow vectors for certain backends * as atomics operations are not supported on cpu backend. * * \todo : if shadow_lhs is empty then we assume shadow statement not required @@ -149,6 +149,8 @@ class CodegenCVisitor: public AstVisitor { protected: using SymbolType = std::shared_ptr<symtab::Symbol>; + using ParamVector = std::vector<std::tuple<std::string, std::string, std::string, std::string>>; + /// name of mod file (without .mod suffix) std::string mod_filename; @@ -168,7 +170,7 @@ class CodegenCVisitor: public AstVisitor { std::vector<IndexVariableInfo> codegen_int_variables; /// all global variables for the model - /// todo : this has become different than CodegenInfo + /// @todo: this has become different than CodegenInfo std::vector<SymbolType> codegen_global_variables; /// all ion variables that could be possibly written @@ -195,8 +197,14 @@ class CodegenCVisitor: public AstVisitor { /// all ast information for code generation codegen::CodegenInfo info; - /// code printer object - std::unique_ptr<CodePrinter> printer; + /// code printer object for target (C, CUDA, ispc, ...) + std::shared_ptr<CodePrinter> target_printer; + + /// code printer object for wrappers + std::shared_ptr<CodePrinter> wrapper_printer; + + /// pointer to active code printer + std::shared_ptr<CodePrinter> printer; /// list of shadow statements in the current block std::vector<ShadowUseStatement> shadow_statements; @@ -375,7 +383,11 @@ class CodegenCVisitor: public AstVisitor { /// convert given double value to string (for printing) - std::string double_to_string(double value); + virtual std::string double_to_string(double value); + + + /// convert given float value to string (for printing) + virtual std::string float_to_string(float value); /// get variable name for float variable @@ -427,6 +439,7 @@ class CodegenCVisitor: public AstVisitor { const std::string& separator, const std::string& prefix = ""); + std::string get_parameter_str(const ParamVector& params); /// check if function or procedure has argument with same name template <typename T> @@ -486,7 +499,7 @@ class CodegenCVisitor: public AstVisitor { /// parameters for internally defined functions - std::string internal_method_parameters(); + ParamVector internal_method_parameters(); /// arguments for external functions @@ -518,7 +531,13 @@ class CodegenCVisitor: public AstVisitor { /// restrict keyword - virtual std::string k_restrict(); + virtual std::string ptr_type_qualifier(); + + + virtual std::string param_type_qualifier(); + + + virtual std::string param_ptr_qualifier(); /// const keyword @@ -594,7 +613,7 @@ class CodegenCVisitor: public AstVisitor { /// structure that wraps all global variables in the mod file - void print_mechanism_global_var_structure(); + virtual void print_mechanism_global_var_structure(); /// structure that wraps all range and int variables required for mod file @@ -775,7 +794,7 @@ class CodegenCVisitor: public AstVisitor { /// common code section for net receive related methods - void print_net_receive_common_code(ast::Block* node); + void print_net_receive_common_code(ast::Block* node, bool need_mech_inst = true); /// kernel for buffering net_send events @@ -786,10 +805,25 @@ class CodegenCVisitor: public AstVisitor { void print_send_event_move(); + virtual std::string net_receive_buffering_declaration(); + + + virtual void print_get_memb_list(); + + virtual void print_net_receive_loop_begin(); + + + virtual void print_net_receive_loop_end(); + + /// kernel for buffering net_receive events void print_net_receive_buffering(); + /// net_receive kernel function definition + void print_net_receive_kernel(); + + /// net_receive function definition void print_net_receive(); @@ -848,11 +882,11 @@ class CodegenCVisitor: public AstVisitor { /// common code for global functions like nrn_init, nrn_cur and nrn_state - void print_global_function_common_code(BlockType type); + virtual void print_global_function_common_code(BlockType type); /// nrn_init function definition - void print_nrn_init(); + void print_nrn_init(bool skip_init_check = true); /// nrn_state / state update function definition @@ -876,7 +910,7 @@ class CodegenCVisitor: public AstVisitor { /// all includes - void print_headers_include(); + virtual void print_headers_include(); /// start of namespaces @@ -887,12 +921,12 @@ class CodegenCVisitor: public AstVisitor { void print_namespace_end(); - /// common getter + /// common getters void print_common_getters(); /// all classes - void print_data_structures(); + virtual void print_data_structures(); /// all compute functions for every backend @@ -903,13 +937,32 @@ class CodegenCVisitor: public AstVisitor { virtual void print_codegen_routines(); + /// entry point to code generation for wrappers + virtual void print_wrapper_routines(); + + + CodegenCVisitor(std::string mod_filename, + std::string output_dir, + LayoutType layout, + std::string float_type, + std::string extension, + std::string wrapper_ext) + : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) + , wrapper_printer(new CodePrinter(output_dir + "/" + mod_filename + wrapper_ext)) + , printer(target_printer) + , mod_filename(mod_filename) + , layout(layout) + , float_type(float_type) {} + + public: CodegenCVisitor(std::string mod_filename, std::string output_dir, LayoutType layout, std::string float_type, std::string extension = ".cpp") - : printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) + : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) + , printer(target_printer) , mod_filename(mod_filename) , layout(layout) , float_type(float_type) {} @@ -919,7 +972,8 @@ class CodegenCVisitor: public AstVisitor { std::stringstream& stream, LayoutType layout, std::string float_type) - : printer(new CodePrinter(stream)) + : target_printer(new CodePrinter(stream)) + , printer(target_printer) , mod_filename(mod_filename) , layout(layout) , float_type(float_type) {} @@ -1001,10 +1055,14 @@ bool has_parameter_of_name(const T& node, const std::string& name) { template <typename T> void CodegenCVisitor::print_function_declaration(const T& node, const std::string& name) { enable_variable_name_lookup = false; + auto type = default_float_data_type(); /// internal and user provided arguments auto internal_params = internal_method_parameters(); auto params = node->get_parameters(); + for (const auto& param: params) { + internal_params.emplace_back("", type, "", param.get()->get_node_name()); + } /// procedures have "int" return type by default std::string return_type = "int"; @@ -1014,15 +1072,8 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin print_device_method_annotation(); printer->add_indent(); - printer->add_text("inline {} {}({}"_format(return_type, method_name(name), internal_params)); - - /// print remaining of arguments to the function - if (!params.empty() && !internal_params.empty()) { - printer->add_text(", "); - } - auto type = default_float_data_type() + " "; - print_vector_elements(params, ", ", type); - printer->add_text(")"); + printer->add_text("inline {} {}({})"_format(return_type, method_name(name), + get_parameter_str(internal_params))); enable_variable_name_lookup = true; } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 015db5de53..dbb3375609 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -159,7 +159,7 @@ void CodegenCudaVisitor::print_compute_functions() { } print_net_send_buffering(); - print_net_receive(); + print_net_receive_kernel(); print_net_receive_buffering(); print_nrn_cur(); print_nrn_state(); @@ -185,7 +185,7 @@ void CodegenCudaVisitor::print_wrapper_routine(std::string wraper_function, Bloc void CodegenCudaVisitor::codegen_wrapper_routines() { print_wrapper_routine("nrn_cur", BlockType::Equation); - print_wrapper_routine("nrn_sate", BlockType::State); + print_wrapper_routine("nrn_state", BlockType::State); } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 8db1586850..5aa36ab161 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -45,6 +45,7 @@ class CodegenCudaVisitor: public CodegenCVisitor { /// setup method for setting matrix shadow vectors void print_rhs_d_shadow_variables() override; + /// if reduction block in nrn_cur required bool nrn_cur_reduction_loop_required() override; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp new file mode 100644 index 0000000000..bdceefcc06 --- /dev/null +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -0,0 +1,629 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <cmath> +#include <fmt/format.h> +#include <src/visitors/rename_visitor.hpp> + +#include "codegen/codegen_ispc_visitor.hpp" +#include "codegen/codegen_naming.hpp" +#include "symtab/symbol_table.hpp" +#include "utils/string_utils.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace fmt::literals; + +namespace nmodl { +namespace codegen { + +using symtab::syminfo::Status; + +/****************************************************************************************/ +/* Overloaded visitor methods */ +/****************************************************************************************/ + +/* + * Rename math functions for ISPC backend + */ +void CodegenIspcVisitor::visit_function_call(ast::FunctionCall* node) { + if (!codegen) { + return; + } + auto fname = node->get_name().get(); + RenameVisitor("fabs", "abs").visit_name(fname); + RenameVisitor("exp", "vexp").visit_name(fname); + CodegenCVisitor::visit_function_call(node); +} + + +/* + * Rename special global variables + */ +void CodegenIspcVisitor::visit_var_name(ast::VarName* node) { + if (!codegen) { + return; + } + auto celsius_rename = RenameVisitor("celsius", "ispc_celsius"); + node->accept(&celsius_rename); + CodegenCVisitor::visit_var_name(node); +} + + +/****************************************************************************************/ +/* Routines must be overloaded in backend */ +/****************************************************************************************/ + +std::string CodegenIspcVisitor::double_to_string(double value) { + if (std::ceil(value) == value) { + return "{:.1f}d"_format(value); + } + if ((value <= 1.0) or (value >= -1.0)) { + return "{:f}d"_format(value); + } else { + auto e = std::log10(std::abs(value)); + if (e < 0.0) { + e = std::pow(10, std::ceil(-e)); + return "({:f}d / {:.1f}d)"_format(value * e, e); + } else { + e = std::pow(10, std::floor(e)); + return "({:f}d * {:.1f}d)"_format(value / e, e); + } + } +} + + +std::string CodegenIspcVisitor::float_to_string(float value) { + if (std::ceil(value) == value) { + return "{:.1f}"_format(value); + } + if ((value <= 1.0f) or (value >= -1.0f)) { + return "{:f}"_format(value); + } else { + auto e = std::log10(std::abs(value)); + if (e < 0.0f) { + e = std::pow(10, std::ceil(-e)); + return "({:f} / {:.1f})"_format(value * e, e); + } else { + e = std::pow(10, std::floor(e)); + return "({:f} * {:.1f})"_format(value / e, e); + } + } +} + + +std::string CodegenIspcVisitor::compute_method_name(BlockType type) { + if (type == BlockType::Initial) { + return method_name("ispc_nrn_init"); + } + if (type == BlockType::State) { + return method_name("ispc_nrn_state"); + } + if (type == BlockType::Equation) { + return method_name("ispc_nrn_cur"); + } + throw std::runtime_error("compute_method_name not implemented"); +} + + +std::string CodegenIspcVisitor::net_receive_buffering_declaration() { + auto params = ParamVector(); + params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), + param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); + params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); + + return "export void {}({})"_format(method_name("ispc_net_buf_receive"), + get_parameter_str(params)); +} + + +void CodegenIspcVisitor::print_backend_includes() { + printer->add_line("#include \"nmodl/fast_math.ispc\""); + printer->add_line("#include \"coreneuron/nrnoc/nrnoc_ml.ispc\""); + printer->add_newline(); + printer->add_newline(); +} + + +std::string CodegenIspcVisitor::backend_name() { + return "ispc (api-compatibility)"; +} + + +void CodegenIspcVisitor::print_atomic_op(const std::string& lhs, + const std::string& op, + const std::string& rhs) { + std::string function; + if (op == "+") { + function = "atomic_add_local"; + } else if (op == "-") { + function = "atomic_subtract_local"; + } else { + throw std::runtime_error("ISPC backend error : {} not supported"_format(op)); + } + printer->add_line("{}(&{}, {});"_format(function, lhs, rhs)); +} + + +void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_update() { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + if (info.point_process) { + stringutils::remove_character(rhs_op, '='); + stringutils::remove_character(d_op, '='); + print_atomic_op("vec_rhs[node_id]", rhs_op, "rhs"); + print_atomic_op("vec_d[node_id]", d_op, "g"); + } else { + printer->add_line("vec_rhs[node_id] {} rhs;"_format(rhs_op)); + printer->add_line("vec_d[node_id] {} g;"_format(d_op)); + } +} + + +void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { + // no tiling for ispc backend but make sure variables are declared as uniform + printer->add_line("int uniform start = 0;"); + printer->add_line("int uniform end = nodecount;"); +} + + +/* + * Depending on the backend, print condition/loop for iterating over channels + * + * Use ispc foreach loop + */ +void CodegenIspcVisitor::print_channel_iteration_block_begin() { + printer->start_block("foreach (id = start ... end)"); +} + + +void CodegenIspcVisitor::print_channel_iteration_block_end() { + printer->end_block(); + printer->add_newline(); +} + + +void CodegenIspcVisitor::print_net_receive_loop_begin() { + printer->add_line("uniform int count = nrb->_displ_cnt;"); + printer->start_block("foreach (i = 0 ... count)"); +} + + +void CodegenIspcVisitor::print_get_memb_list() { + // do nothing +} + + +void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_reduction() { + // do nothing +} + + +void CodegenIspcVisitor::print_rhs_d_shadow_variables() { + // do nothing +} + + +bool CodegenIspcVisitor::nrn_cur_reduction_loop_required() { + return false; +} + + +std::string CodegenIspcVisitor::ptr_type_qualifier() { + if (wrapper_codegen) { + return CodegenCVisitor::ptr_type_qualifier(); + } else { + return "uniform "; // @note: extra space needed to separate qualifier from var name. + } +} + + +std::string CodegenIspcVisitor::param_type_qualifier() { + if (wrapper_codegen) { + return CodegenCVisitor::param_type_qualifier(); + } else { + return "uniform "; + } +} + + +std::string CodegenIspcVisitor::param_ptr_qualifier() { + if (wrapper_codegen) { + return CodegenCVisitor::param_ptr_qualifier(); + } else { + return "uniform "; + } +} + + +void CodegenIspcVisitor::print_backend_namespace_start() { + // no ispc namespace +} + + +void CodegenIspcVisitor::print_backend_namespace_stop() { + // no ispc namespace +} + + +CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( + std::string arg_qualifier) { + auto params = ParamVector(); + params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), + param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); + params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); + params.emplace_back(param_type_qualifier(), "int", "", "type"); + return params; +} + + +void CodegenIspcVisitor::print_global_function_common_code(BlockType type) { + std::string method = compute_method_name(type); + + auto params = get_global_function_parms(ptr_type_qualifier()); + print_global_method_annotation(); + printer->start_block("export void {}({})"_format(method, get_parameter_str(params))); + + print_kernel_data_present_annotation_block_begin(); + printer->add_line("uniform int nodecount = ml->nodecount;"); + printer->add_line("uniform int pnodecount = ml->_nodecount_padded;"); + printer->add_line( + "{}int* {}node_index = ml->nodeindices;"_format(k_const(), ptr_type_qualifier())); + printer->add_line("double* {}data = ml->data;"_format(ptr_type_qualifier())); + printer->add_line( + "{}double* {}voltage = nt->_actual_v;"_format(k_const(), ptr_type_qualifier())); + + if (type == BlockType::Equation) { + printer->add_line("double* {}vec_rhs = nt->_actual_rhs;"_format(ptr_type_qualifier())); + printer->add_line("double* {}vec_d = nt->_actual_d;"_format(ptr_type_qualifier())); + print_rhs_d_shadow_variables(); + } + printer->add_line("Datum* {}indexes = ml->pdata;"_format(ptr_type_qualifier())); + printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(ptr_type_qualifier())); + printer->add_newline(1); +} + + +void CodegenIspcVisitor::print_compute_functions() { + // print_top_verbatim_blocks(); @todo: see where to add this + + for (const auto& function: info.functions) { + if (!program_symtab->lookup(function->get_node_name()) + .get() + ->has_all_status(Status::inlined)) { + print_function(function); + } + } + for (const auto& procedure: info.procedures) { + if (!program_symtab->lookup(procedure->get_node_name()) + .get() + ->has_all_status(Status::inlined)) { + print_procedure(procedure); + } + } + print_net_receive_kernel(); + print_net_receive_buffering(); + print_nrn_init(false); + print_nrn_cur(); + print_nrn_state(); +} + + +// @todo : use base visitor function with provision to override specific qualifiers +void CodegenIspcVisitor::print_mechanism_global_var_structure() { + auto float_type = default_float_data_type(); + printer->add_newline(2); + printer->add_line("/** all global variables */"); + printer->add_line("struct {} {}"_format(global_struct(), "{")); + printer->increase_indent(); + + if (!info.ions.empty()) { + for (const auto& ion: info.ions) { + auto name = "{}_type"_format(ion.name); + printer->add_line("int {};"_format(name)); + codegen_global_variables.push_back(make_symbol(name)); + } + } + + if (info.point_process) { + printer->add_line("int point_type;"); + codegen_global_variables.push_back(make_symbol("point_type")); + } + + if (!info.state_vars.empty()) { + for (const auto& var: info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + printer->add_line("{} {};"_format(float_type, name)); + codegen_global_variables.push_back(make_symbol(name)); + } + } + } + + if (!info.vectorize) { + printer->add_line("{} v;"_format(float_type)); + codegen_global_variables.push_back(make_symbol("v")); + } + + auto& top_locals = info.top_local_variables; + if (!info.vectorize && !top_locals.empty()) { + for (const auto& var: top_locals) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->add_line("{} {}[{}];"_format(float_type, name, length)); + } else { + printer->add_line("{} {};"_format(float_type, name)); + } + codegen_global_variables.push_back(var); + } + } + + if (!info.thread_variables.empty()) { + printer->add_line("int thread_data_in_use;"); + printer->add_line("{} thread_data[{}];"_format(float_type, info.thread_var_data_size)); + codegen_global_variables.push_back(make_symbol("thread_data_in_use")); + auto symbol = make_symbol("thread_data"); + symbol->set_as_array(info.thread_var_data_size); + codegen_global_variables.push_back(symbol); + } + + printer->add_line("int reset;"); + codegen_global_variables.push_back(make_symbol("reset")); + + printer->add_line("int mech_type;"); + codegen_global_variables.push_back(make_symbol("mech_type")); + + auto& globals = info.global_variables; + auto& constants = info.constant_variables; + + if (!globals.empty()) { + for (const auto& var: globals) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->add_line("{} {}[{}];"_format(float_type, name, length)); + } else { + printer->add_line("{} {};"_format(float_type, name)); + } + codegen_global_variables.push_back(var); + } + } + + if (!constants.empty()) { + for (const auto& var: constants) { + auto name = var->get_name(); + auto value_ptr = var->get_value(); + printer->add_line("{} {};"_format(float_type, name)); + codegen_global_variables.push_back(var); + } + } + + if (info.primes_size != 0) { + printer->add_line("int* {} slist1;"_format("")); + printer->add_line("int* {} dlist1;"_format("")); + codegen_global_variables.push_back(make_symbol("slist1")); + codegen_global_variables.push_back(make_symbol("dlist1")); + if (info.derivimplicit_used) { + printer->add_line("int* slist2;"); + codegen_global_variables.push_back(make_symbol("slist2")); + } + } + + if (info.table_count > 0) { + printer->add_line("double usetable;"); + codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); + + for (const auto& block: info.functions_with_table) { + auto name = block->get_node_name(); + printer->add_line("{} tmin_{};"_format(float_type, name)); + printer->add_line("{} mfac_{};"_format(float_type, name)); + codegen_global_variables.push_back(make_symbol("tmin_" + name)); + codegen_global_variables.push_back(make_symbol("mfac_" + name)); + } + + for (const auto& variable: info.table_statement_variables) { + auto name = "t_" + variable->get_name(); + printer->add_line("{}* {};"_format(float_type, name)); + codegen_global_variables.push_back(make_symbol(name)); + } + } + + if (info.vectorize) { + printer->add_line("ThreadDatum* {}ext_call_thread;"_format("")); + codegen_global_variables.push_back(make_symbol("ext_call_thread")); + } + + printer->decrease_indent(); + printer->add_line("};"); + + printer->add_newline(1); + printer->add_line("/** holds object of global variable */"); + printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); +} + + +void CodegenIspcVisitor::print_data_structures() { + print_mechanism_global_var_structure(); + print_mechanism_range_var_structure(); + print_ion_var_structure(); +} + + +void CodegenIspcVisitor::print_wrapper_data_structures() { + print_mechanism_global_var_structure(); + print_mechanism_range_var_structure(); + print_ion_var_structure(); +} + + +void CodegenIspcVisitor::print_ispc_globals() { + printer->start_block("extern \"C\""); + printer->add_line("extern double ispc_celsius;"); + printer->end_block(); +} + + +/****************************************************************************************/ +/* Main code printing entry points and wrappers */ +/****************************************************************************************/ + +void CodegenIspcVisitor::print_net_receive_buffering_wrapper() { + if (!net_receive_required() || info.artificial_cell) { + return; + } + printer->add_newline(2); + printer->start_block("void {}(NrnThread* nt)"_format(method_name("net_buf_receive"))); + printer->add_line("Memb_list* ml = get_memb_list(nt);"); + printer->start_block("if (ml == NULL)"); + printer->add_line("return;"); + printer->end_block(1); + printer->add_line( + "{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); + + printer->add_line("{}(inst, nt, ml);"_format(method_name("ispc_net_buf_receive"))); + + printer->end_block(1); +} + + +void CodegenIspcVisitor::print_headers_include() { + print_backend_includes(); +} + + +void CodegenIspcVisitor::print_wrapper_headers_include() { + print_standard_includes(); + print_coreneuron_includes(); +} + + +void CodegenIspcVisitor::print_wrapper_routine(std::string wraper_function, BlockType type) { + auto args = "NrnThread* nt, Memb_list* ml, int type"; + wraper_function = method_name(wraper_function); + auto compute_function = compute_method_name(type); + + printer->add_newline(2); + printer->start_block("void {}({})"_format(wraper_function, args)); + printer->add_line("int nodecount = ml->nodecount;"); + // clang-format off + printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); + // clang-format on + + if (type == BlockType::Initial) { + printer->add_newline(); + printer->add_line("setup_instance(nt, ml);"); + printer->add_line("ispc_celsius = celsius;"); + printer->add_newline(); + printer->start_block("if (_nrn_skip_initmodel)"); + printer->add_line("return;"); + printer->end_block(); + printer->add_newline(); + } + + printer->add_line("{}(inst, nt, ml, type);"_format(compute_function)); + printer->end_block(); + printer->add_newline(); +} + + +void CodegenIspcVisitor::print_backend_compute_routine_decl() { + auto params = get_global_function_parms(""); + auto compute_function = compute_method_name(BlockType::Initial); + printer->add_line( + "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + + if (nrn_cur_required()) { + compute_function = compute_method_name(BlockType::Equation); + printer->add_line( + "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + } + + if (nrn_state_required()) { + compute_function = compute_method_name(BlockType::State); + printer->add_line( + "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + } + + if (net_receive_required()) { + auto net_recv_params = ParamVector(); + net_recv_params.emplace_back("", "{}*"_format(instance_struct()), "", "inst"); + net_recv_params.emplace_back("", "NrnThread*", "", "nt"); + net_recv_params.emplace_back("", "Memb_list*", "", "ml"); + printer->add_line("extern \"C\" void {}({});"_format(method_name("ispc_net_buf_receive"), + get_parameter_str(net_recv_params))); + } +} + + +void CodegenIspcVisitor::codegen_wrapper_routines() { + print_wrapper_routine("nrn_init", BlockType::Initial); + if (nrn_cur_required()) { + print_wrapper_routine("nrn_cur", BlockType::Equation); + } + if (nrn_state_required()) { + print_wrapper_routine("nrn_state", BlockType::State); + } +} + + +void CodegenIspcVisitor::print_codegen_routines() { + codegen = true; + print_backend_info(); + print_headers_include(); + + print_data_structures(); + + print_compute_functions(); + + // now print the ispc wrapper code + print_codegen_wrapper_routines(); +} + + +void CodegenIspcVisitor::print_codegen_wrapper_routines() { + printer = wrapper_printer; + wrapper_codegen = true; + print_backend_info(); + print_wrapper_headers_include(); + print_ispc_globals(); + print_namespace_begin(); + + print_nmodl_constant(); + print_mechanism_info(); + print_wrapper_data_structures(); + print_global_variables_for_hoc(); + print_common_getters(); + + print_thread_memory_callbacks(); + print_memory_allocation_routine(); + print_global_variable_setup(); + print_instance_variable_setup(); + print_nrn_alloc(); + print_check_table_thread_function(); + + + print_net_init(); + print_net_send_buffering(); + print_watch_activate(); + print_watch_check(); + print_net_receive(); + + print_backend_compute_routine_decl(); + + print_net_receive_buffering_wrapper(); + codegen_wrapper_routines(); + + print_mechanism_register(); + + print_namespace_end(); +} + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp new file mode 100644 index 0000000000..ee93c0a21a --- /dev/null +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -0,0 +1,170 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "codegen/codegen_c_visitor.hpp" + + +namespace nmodl { +namespace codegen { + +/** + * \class CodegenIspcVisitor + * \brief Visitor for printing ispc backend + */ +class CodegenIspcVisitor: public CodegenCVisitor { + void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); + + + /// flag to indicate if visitor should print the the wrapper code + bool wrapper_codegen = false; + + protected: + /// doubles are differently represented in ispc than in C + std::string double_to_string(double value) override; + + + /// floats are differently represented in ispc than in C + std::string float_to_string(float value) override; + + + /// name of the code generation backend + std::string backend_name() override; + + + /// return name of main compute kernels + std::string compute_method_name(BlockType type) override; + + + std::string net_receive_buffering_declaration() override; + + + std::string ptr_type_qualifier() override; + + + std::string param_type_qualifier() override; + + + std::string param_ptr_qualifier() override; + + + /// common includes : standard c/c++, coreneuron and backend specific + void print_backend_includes() override; + + + /// update to matrix elements with/without shadow vectors + void print_nrn_cur_matrix_shadow_update() override; + + + /// reduction to matrix elements from shadow vectors + void print_nrn_cur_matrix_shadow_reduction() override; + + + /// setup method for setting matrix shadow vectors + void print_rhs_d_shadow_variables() override; + + + /// if reduction block in nrn_cur required + bool nrn_cur_reduction_loop_required() override; + + + ParamVector get_global_function_parms(std::string arg_qualifier); + + + void print_global_function_common_code(BlockType type) override; + + + /// backend specific channel instance iteration block start + void print_channel_iteration_block_begin() override; + + + /// backend specific channel iteration bounds + void print_channel_iteration_tiling_block_begin(BlockType type) override; + + + /// backend specific channel instance iteration block end + void print_channel_iteration_block_end() override; + + + /// start of backend namespace + void print_backend_namespace_start() override; + + + /// end of backend namespace + void print_backend_namespace_stop() override; + + + void print_headers_include() override; + + + void print_wrapper_headers_include(); + + + /// all compute functions for every backend + void print_compute_functions() override; + + + void print_backend_compute_routine_decl(); + + + /// print wrapper function that calls ispc kernel + void print_wrapper_routine(std::string wraper_function, BlockType type); + + + /// wrapper/caller routines for nrn_state and nrn_cur + void codegen_wrapper_routines(); + + + /// structure that wraps all global variables in the mod file + void print_mechanism_global_var_structure() override; + + + void print_data_structures() override; + + + void print_wrapper_data_structures(); + + + void print_ispc_globals(); + + + void print_get_memb_list() override; + + + void print_net_receive_loop_begin() override; + + + void print_net_receive_buffering_wrapper(); + + + /// entry point to code generation + void print_codegen_routines() override; + + + void print_codegen_wrapper_routines(); + + public: + CodegenIspcVisitor(std::string mod_file, + std::string output_dir, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".ispc", ".cpp") {} + + + CodegenIspcVisitor(std::string mod_file, + std::stringstream& stream, + LayoutType layout, + std::string float_type) + : CodegenCVisitor(mod_file, stream, layout, float_type) {} + + void visit_function_call(ast::FunctionCall* node) override; + void visit_var_name(ast::VarName* node) override; +}; + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc new file mode 100644 index 0000000000..e937c8792d --- /dev/null +++ b/src/nmodl/codegen/fast_math.ispc @@ -0,0 +1,147 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + * + * Note that the fast ISPC exponentials Based on VDT implementation of + * D. Piparo et al. See https://github.com/dpiparo/vdt for additional + * license information. + *************************************************************************/ + + +static inline double uint642dp(unsigned int64 ll) { + return *((varying double*) (&ll)); +} + +static inline unsigned int64 dp2uint64(double x) { + return *((varying unsigned int64*) (&x)); +} + +static inline float uint322sp(int32 x) { + return *((varying float*) (&x)); +} + +static inline unsigned int sp2uint32(float x) { + return *((varying unsigned int32*) (&x)); +} + +static inline double dpfloor(const double x) { + int32 ret = x; + ret -= (sp2uint32(x) >> 31); + return ret; +} + +static inline float spfloor(const float x) { + int32 ret = x; + ret -= (sp2uint32(x) >> 31); + return ret; +} + +static inline uniform float f_inf() { + uniform unsigned int32 v = 0x7F800000; + return *((float*) (&v)); +} + +static inline uniform double inf() { + uniform unsigned int64 v = 0x7FF0000000000000; + return *((double*) (&v)); +} + + +static const uniform double EXP_LIMIT = 708.d; + +static const uniform double PX1exp = 1.26177193074810590878d * 0.0001d; +static const uniform double PX2exp = 3.02994407707441961300d * 0.01d; +static const uniform double PX3exp = 9.99999999999999999910d * 0.1d; +static const uniform double QX1exp = 3.00198505138664455042d * 0.000001d; +static const uniform double QX2exp = 2.52448340349684104192d * 0.001d; +static const uniform double QX3exp = 2.27265548208155028766d * 0.1d; +static const uniform double QX4exp = 2.00000000000000000009d; + +static const uniform double LOG2E = 1.4426950408889634073599d; + +static const uniform float MAXLOGF = 88.72283905206835f; +static const uniform float MINLOGF = -88.f; + +static const uniform float C1F = 0.693359375f; +static const uniform float C2F = -2.12194440e-4f; + +static const uniform float PX1expf = 1.9875691500E-4f; +static const uniform float PX2expf = 1.3981999507E-3f; +static const uniform float PX3expf = 8.3334519073E-3f; +static const uniform float PX4expf = 4.1665795894E-2f; +static const uniform float PX5expf = 1.6666665459E-1f; +static const uniform float PX6expf = 5.0000001201E-1f; + +static const uniform float LOG2EF = 1.44269504088896341f; + + +static inline double vexp(double initial_x) { + double x = initial_x; + double px = dpfloor(LOG2E * x + 0.5d); + const int32 n = px; + + x -= px * (6.93145751953125d * 0.1d); + x -= px * (1.42860682030941723212d * 0.000001d); + + const double xx = x * x; + + px = PX1exp; + px *= xx; + px += PX2exp; + px *= xx; + px += PX3exp; + px *= x; + + double qx = QX1exp; + qx *= xx; + qx += QX2exp; + qx *= xx; + qx += QX3exp; + qx *= xx; + qx += QX4exp; + + x = px / (qx - px); + x = 1.0d + 2.0d * x; + x *= uint642dp((((unsigned int64) n) + 1023) << 52); + + if (initial_x > EXP_LIMIT) + x = inf(); + if (initial_x < -EXP_LIMIT) + x = 0.d; + + return x; +} + +static inline float vexp(float initial_x) { + float x = initial_x; + float z = spfloor(LOG2EF * x + 0.5f); + + x -= z * C1F; + x -= z * C2F; + const int32 n = z; + const float x2 = x * x; + + z = x * PX1expf; + z += PX2expf; + z *= x; + z += PX3expf; + z *= x; + z += PX4expf; + z *= x; + z += PX5expf; + z *= x; + z += PX6expf; + z *= x2; + z += x + 1.0f; + + z *= uint322sp((n + 0x7f) << 23); + + if (initial_x > MAXLOGF) + z = f_inf(); + if (initial_x < MINLOGF) + z = 0.f; + + return z; +} diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 502ebf01cc..3f760bd547 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -16,6 +16,7 @@ #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" +#include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_omp_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "utils/common_utils.hpp" @@ -55,6 +56,9 @@ int main(int argc, const char* argv[]) { /// true if c code with openmp to be generated bool omp_backend(false); + /// true if ispc code to be generated + bool ispc_backend(false); + /// true if c code with openacc to be generated bool oacc_backend(false); @@ -136,6 +140,7 @@ int main(int argc, const char* argv[]) { auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, "C/C++ backend")->ignore_case(); host_opt->add_flag("--omp", omp_backend, "C/C++ backend with OpenMP")->ignore_case(); + host_opt->add_flag("--ispc", ispc_backend, "C/C++ backend with ISPC")->ignore_case(); auto acc_opt = app.add_subcommand("acc", "Accelerator code backends")->ignore_case(); acc_opt->add_flag("--oacc", oacc_backend, "C/C++ backend with OpenACC")->ignore_case(); @@ -168,6 +173,11 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); + // if any of the other backends is used we force the C backend to be off. + if (omp_backend || ispc_backend) { + c_backend = false; + } + make_path(output_dir); make_path(scratch_dir); @@ -313,11 +323,19 @@ int main(int argc, const char* argv[]) { CodegenCVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); } + if (omp_backend) { logger->info("Running OpenMP backend code generator"); CodegenOmpVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); } + + if (ispc_backend) { + logger->info("Running ISPC backend code generator"); + CodegenIspcVisitor visitor(modfile, output_dir, mem_layout, data_type); + visitor.visit_program(ast.get()); + } + if (oacc_backend) { logger->info("Running OpenACC backend code generator"); CodegenAccVisitor visitor(modfile, output_dir, mem_layout, data_type); From 967a3505fe6e54fef9cabe55b815a9dbc4f8552d Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Sat, 16 Mar 2019 08:31:01 +0100 Subject: [PATCH 166/871] Add KINETIC block parser (BlueBrain/nmodl#59) * Convert KINETIC block with equivalent ODEs - replaces reaction statements within each KINETIC block withODEs - then convert KINETIC block to a DERIVATIVE block - generates ODEs from reversible "<->" reaction statements - also for "->" reaction statements - also for "<<" reaction statements - also works when the statement contains non-state vars as reactants - then applies COMPARTMENT statements to ODEs * Parser & Visitor changes - existing SympySolver routines can then solves new ODEs - moved all sympy visitors after inlining in main.cpp - added unit tests, including two examples from NEURON book - added a notebook with kinetic block definitions and equations - changed parser to parse all vars in reaction statements as ReactVarNames - moved remove_statements_from_block to visitor_utils * Assumption : - assumes loops have been unrolled, and that no variables are redefined, - i.e. that it can just process the reaction statements as they are without checking any surrounding statements * TODO (see BlueBrain/nmodl#72) : - correctly parse array variables - correctly substitute for fflux/bflux variable use inside mod file (have to parse other statements for this) - implement CONSERVE statements (compiler hint: not high priority) - check for loops, verbatim, etc & if found abort - LONGITUDINAL DIFFUSION statement NMODL Repo SHA: BlueBrain/nmodl@54f3cd1d681f200d98479d14516f6cb07bd48b84 --- doc/notebooks/kinetic_schemes.ipynb | 240 +++++++++++++ src/nmodl/nmodl/main.cpp | 41 ++- src/nmodl/parser/nmodl.yy | 10 +- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/kinetic_block_visitor.cpp | 333 +++++++++++++++++ src/nmodl/visitors/kinetic_block_visitor.hpp | 104 ++++++ src/nmodl/visitors/sympy_solver_visitor.cpp | 12 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 5 +- src/nmodl/visitors/visitor_utils.cpp | 11 + src/nmodl/visitors/visitor_utils.hpp | 4 + test/nmodl/transpiler/visitor/visitor.cpp | 357 +++++++++++++++++++ 11 files changed, 1084 insertions(+), 35 deletions(-) create mode 100644 doc/notebooks/kinetic_schemes.ipynb create mode 100644 src/nmodl/visitors/kinetic_block_visitor.cpp create mode 100644 src/nmodl/visitors/kinetic_block_visitor.hpp diff --git a/doc/notebooks/kinetic_schemes.ipynb b/doc/notebooks/kinetic_schemes.ipynb new file mode 100644 index 0000000000..212bf25874 --- /dev/null +++ b/doc/notebooks/kinetic_schemes.ipynb @@ -0,0 +1,240 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL Kinetic Scheme\n", + "\n", + "Some definitions of reaction kinetics & mass action laws that are relevant for how `KINETIC` blocks are translated into a system of ODEs in NMODL" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Reaction Kinetics\n", + "\n", + "- We consider a set of reaction species $A_j$, with corresponding Molar concentrations $Y_j$\n", + "- They react according to a set of reaction equations:\n", + "\n", + "$$\\sum_j \\nu_{ij}^L A_j \\overset{k_i}{\\rightarrow} \\sum_j \\nu_{ij}^R A_j$$\n", + "\n", + "where\n", + "- $k_i$ is the rate coefficient\n", + "- $\\nu_{ij}^L$, $\\nu_{ij}^R$ are stochiometric coefficients - must be positive integers (including zero)\n", + "***\n", + "#### Law of Mass Action\n", + "\n", + "- This allows us to convert these reaction equations to a set of ODEs\n", + "\n", + "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} r_i$$\n", + "\n", + "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", + "$$r_i = k_i \\prod_j Y_j^{\\nu_{ij}^R}$$\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### KINETIC block format\n", + "\n", + "A reaction equation is specifed in the mod file as\n", + "```\n", + "~ A0 + 3A1 + 2A2 + ... <-> 2A0 + A1 + ... (kf, kb)\n", + "```\n", + "where\n", + "- `A0` etc are the species $A_j$\n", + "- the integer preceeding a species (with or without a space) is the corresponding stochiometric coefficient $\\nu_{ij}$ (by default 1 if not present)\n", + "- `kf` is the forwards reaction rate $k^{(f)}_j$\n", + "- `kb` is the backwards reaction rate $k^{(b)}_j$, i.e. the reaction rate for the same reaction with the LHS and RHS exchanged\n", + "***\n", + "We can convert these statements to a system of ODEs:\n", + "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} (r^{(f)}_i - r^{(b)}_i)$$\n", + "\n", + "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", + "$$\n", + "\\begin{align}\n", + "r^{(f)}_i &= k^{(f)}_i \\prod_j Y_j^{\\nu_{ij}^{L}} \\\\\n", + "r^{(b)}_i &= k^{(b)}_i \\prod_j Y_j^{\\nu_{ij}^{R}}\n", + "\\end{align}\n", + "$$\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Example\n", + "\n", + "Given the following KINETIC statement\n", + "```\n", + "~ h <-> m (a,b)\n", + "```\n", + "We have 2 state variables, and 1 reaction statement, so \n", + "- $i = \\{0\\}$\n", + "- $j = \\{0, 1\\}$\n", + "\n", + "i.e. \n", + "- the state vector $Y$ has 2 elements\n", + "$$\n", + "Y = \\left(\n", + "\\begin{align}\n", + "h \\\\\n", + "m \n", + "\\end{align}\n", + "\\right)\n", + "$$\n", + "- the stochiometric coefficients are 1x2 matrices\n", + "$$\n", + "\\nu^L = \\left(\n", + "\\begin{align}\n", + "1 && 0\n", + "\\end{align}\n", + "\\right)\n", + "$$\n", + "$$\n", + "\\nu^R = \\left(\n", + "\\begin{align}\n", + "0 && 1\n", + "\\end{align}\n", + "\\right)\n", + "$$\n", + "- the rate vectors contain 1 element:\n", + "$$\n", + "k^{(f)} = a\n", + "$$\n", + "$$\n", + "k^{(b)} = b\n", + "$$\n", + "\n", + "Using these we can construct the forwards and blackwards fluxes:\n", + "$$\n", + "r^{(f)} = a h\n", + "$$\n", + "$$\n", + "r^{(b)} = b m\n", + "$$\n", + "and finally we find the ODEs in matrix form:\n", + "$$\n", + "\\frac{dY}{dt} =\n", + "\\left(\n", + "\\begin{align}\n", + "-1 && 1\n", + "\\end{align}\n", + "\\right)\n", + "(ah - bm)\n", + "$$\n", + "which in terms of the state variables can be written:\n", + "$$\n", + "\\begin{align}\n", + "\\frac{dh}{dt} &= bm - ah \\\\\n", + "\\frac{dm}{dt} &= ah - bm\n", + "\\end{align}\n", + "$$\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Other types of reaction statement\n", + "\n", + "Can also have a reaction statement of the form\n", + "```\n", + "~ h << (a)\n", + "```\n", + "where the LHS must be a state variable, and the RHS is an expression inside parentheses.\n", + "\n", + "The meaning of this statement is to add `a` to the differential equation for `h`, i.e.\n", + "$$\n", + "\\frac{dh}{dt} += a\n", + "$$\n", + "***\n", + "Finally there is a statement of the form\n", + "```\n", + "x + 2y + ... -> (a)\n", + "```\n", + "which is a one-way reaction statement with no reaction products.\n", + "\n", + "This is just syntactic sugar for a special case of the standard `<->` reaction equation, where the backwards rate is set to zero and there are no states on the RHS of the reaction, so the above is equivalent to\n", + "```\n", + "x + 2y + ... <-> (a, 0)\n", + "```\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### [TODO] CONSERVE\n", + "\n", + "Converse statement allows specification of a conservation law, e.g.\n", + "\n", + "```\n", + "CONSERVE h + m = 0\n", + "```\n", + "This can then be used to eliminate one of these variables from the set of ODEs, for example by removing the dmdt equation, and instead calculating m in terms of the algebraic relation above.\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### [TODO] fflux / bflux\n", + "\n", + "Directly after each kinetic statement, NEURON provides the fflux and bflux variable that can be referenced inside the mod file (!). Note that it always refers to the fflux/bflux from the preceeding kinetic statement.\n", + "\n", + "So we should parse all non-kinetic statements in the mod file following a kinetic statement (until we get to a new kinetic statement), and replace any fflux or bflux variables with corresponding expression.\n", + "\n", + "***\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### [TODO] inlining\n", + "\n", + "The usual issues about inlining function calls, e.g. when the rate is a function call.\n", + "\n", + "In principle also allowed for it to be a function of state variables, in which case vital to inline this information for the ODEs (although we could also just say that this is not supported, as not necessary for the user to write the mod file in this way, state variable dependence can always be written explicitly)\n", + "***" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 3f760bd547..b82780ff73 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -26,6 +26,7 @@ #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -250,11 +251,10 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("verbatim_rename")); } - if (sympy_conductance) { - logger->info("Running sympy conductance visitor"); - SympyConductanceVisitor().visit_program(ast.get()); + { + logger->info("Running KINETIC block visitor"); + KineticBlockVisitor().visit_program(ast.get()); SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("sympy_conductance")); } /// once we start modifying (especially removing) older constructs @@ -262,19 +262,6 @@ int main(int argc, const char* argv[]) { /// that old symbols (e.g. prime variables) are not lost update_symtab = true; - if (sympy_analytic) { - logger->info("Running sympy solve visitor"); - SympySolverVisitor(sympy_pade, sympy_cse).visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("sympy_solve")); - } - - { - logger->info("Running cnexp visitor"); - CnexpSolveVisitor().visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("cnexp")); - } - if (nmodl_const_folding) { logger->info("Running nmodl constant folding visitor"); ConstantFolderVisitor().visit_program(ast.get()); @@ -303,6 +290,26 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("localize")); } + if (sympy_conductance) { + logger->info("Running sympy conductance visitor"); + SympyConductanceVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("sympy_conductance")); + } + + if (sympy_analytic) { + logger->info("Running sympy solve visitor"); + SympySolverVisitor(sympy_pade, sympy_cse).visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("sympy_solve")); + } + + { + logger->info("Running cnexp visitor"); + CnexpSolveVisitor().visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("cnexp")); + } + if (json_perfstat) { auto file = scratch_dir + "/" + modfile + ".perf.json"; logger->info("Writing performance statistics to {}", file); diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 52c201b834..225e764a0a 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1641,7 +1641,7 @@ conserve : CONSERVE react "=" expr } | CONSERVE error { - + error(scanner.loc, "conserve"); } ; @@ -1711,7 +1711,10 @@ reaction : REACTION react REACT1 react "(" expr "," expr ")" ; -react : varname { $$ = $1; } +react : varname + { + $$ = new ast::ReactVarName(nullptr, $1); + } | integer varname { $$ = new ast::ReactVarName($1, $2); @@ -1719,7 +1722,8 @@ react : varname { $$ = $1; } | react "+" varname { auto op = ast::BinaryOperator(ast::BOP_ADDITION); - $$ = new ast::BinaryExpression($1, op, $3); + auto variable = new ast::ReactVarName(nullptr, $3); + $$ = new ast::BinaryExpression($1, op, variable); } | react "+" integer varname { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 463db77fb7..bea5e35c3e 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -10,6 +10,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp new file mode 100644 index 0000000000..a5e5cc9ae7 --- /dev/null +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -0,0 +1,333 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <iostream> + +#include "fmt/format.h" +#include "kinetic_block_visitor.hpp" +#include "symtab/symbol.hpp" +#include "utils/logger.hpp" +#include "utils/string_utils.hpp" +#include "visitor_utils.hpp" + +using namespace fmt::literals; + +namespace nmodl { + +using symtab::syminfo::NmodlType; + +void KineticBlockVisitor::process_reac_var(const std::string& varname, int count) { + // lookup index of state var + const auto it = state_var_index.find(varname); + if (it == state_var_index.cend()) { + // not a state var + // so this is a constant variable in the reaction statement + // this should be included in the fluxes: + if (in_reaction_statement_lhs) { + non_state_var_fflux[i_statement] = varname; + logger->debug("KineticBlockVisitor :: adding non-state fflux[{}] \"{}\"", i_statement, + varname); + } else { + non_state_var_bflux[i_statement] = varname; + logger->debug("KineticBlockVisitor :: adding non-state bflux[{}] \"{}\"", i_statement, + varname); + } + // but as it is not a state var, no ODE should be generated for it, i.e. no nu_L or nu_R + // entry. + } else { + // found state var index + int i_statevar = it->second; + if (in_reaction_statement_lhs) { + // set element of nu_L matrix + rate_eqs.nu_L[i_statement][i_statevar] += count; + logger->debug("KineticBlockVisitor :: nu_L[{}][{}] += {}", i_statement, i_statevar, + count); + } else { + // set element of nu_R matrix + rate_eqs.nu_R[i_statement][i_statevar] += count; + logger->debug("KineticBlockVisitor :: nu_R[{}][{}] += {}", i_statement, i_statevar, + count); + } + } +} + +void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { + // NOTE! CONSERVE statement "implicitly takes into account COMPARTMENT factors on LHS" + // see p244 of NEURON book + // need to ensure that we do this in the same way: presumably need + // to either multiply or divide state vars on LHS of conserve statement by their COMPARTMENT factors? + logger->debug("KineticBlockVisitor :: CONSERVE statement ignored (for now): {} = {}", + to_nmodl(node->get_react().get()), to_nmodl(node->get_expr().get())); + statements_to_remove.insert(node); +} + +void KineticBlockVisitor::visit_compartment(ast::Compartment* node) { + // COMPARTMENT block has an expression, and a list of state vars it applies to. + // For each state var, the rhs of the differential eq should be divided by the expression. + // Here we store the expressions in the compartment_factors vector + auto expr = node->get_expression(); + std::string expression = to_nmodl(expr.get()); + logger->debug("KineticBlockVisitor :: COMPARTMENT expr: {}", expression); + for (const auto name_ptr: node->get_names()) { + std::string var_name = name_ptr->get_node_name(); + const auto it = state_var_index.find(var_name); + if (it != state_var_index.cend()) { + int var_index = it->second; + compartment_factors[var_index] = expression; + logger->debug( + "KineticBlockVisitor :: COMPARTMENT factor {} for state var {} (index {})", + expression, var_name, var_index); + } + } + // add COMPARTMENT state to list of statements to remove + // since we don't want this statement to be present in the final DERIVATIVE block + statements_to_remove.insert(node); +} + +void KineticBlockVisitor::visit_reaction_operator(ast::ReactionOperator* node) { + auto reaction_op = node->get_value(); + if (reaction_op == ast::ReactionOp::LTMINUSGT) { + // <-> + // reversible reaction + // we go from visiting the lhs to visiting the rhs of the reaction statement + in_reaction_statement_lhs = false; + } +} + +void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName* node) { + // react_var_name node contains var_name and integer + // var_name is the state variable which we convert to an index + // integer is the value to be added to the stochiometric matrix at this index + if(in_reaction_statement){ + auto varname = node->get_node_name(); + int count = node->get_value() ? node->get_value()->eval() : 1; + process_reac_var(varname, count); + } +} + +void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) { + statements_to_remove.insert(node); + + auto reaction_op = node->get_op().get_value(); + // special case for << statements + if (reaction_op == ast::ReactionOp::LTLT) { + logger->debug("KineticBlockVisitor :: '<<' reaction statement: {}", to_nmodl(node)); + // statements involving the "<<" operator + // must have a single state var on lhs + // and a single expression on rhs that corresponds to d{state var}/dt + // So if x is a state var, then + // ~ x << (a*b) + // translates to the ODE contribution x' += a*b + auto lhs = node->get_reaction1(); + + /// check if reaction statement is a single state variable + bool single_state_var = true; + if (lhs->is_react_var_name()) { + auto value = std::dynamic_pointer_cast<ast::ReactVarName>(lhs)->get_value(); + if (value && (value->eval() != 1)) { + single_state_var = false; + } + } + + if (!lhs->is_react_var_name() || !single_state_var) { + logger->warn( + "KineticBlockVisitor :: LHS of \"<<\" reaction statement must be a single state " + "var, but instead found {}: ignoring this statement", + to_nmodl(lhs.get())); + return; + } + auto rhs = node->get_expression1(); + std::string varname = std::dynamic_pointer_cast<ast::ReactVarName>(lhs)->get_node_name(); + // get index of state var + const auto it = state_var_index.find(varname); + if (it != state_var_index.cend()) { + int var_index = it->second; + std::string expr = to_nmodl(rhs.get()); + if (!additive_terms[var_index].empty()) { + additive_terms[var_index] += " + "; + } + // add to additive terms for this state var + additive_terms[var_index] += "({})"_format(expr); + logger->debug("KineticBlockVisitor :: '<<' reaction statement: {}' += {}", varname, + expr); + } + return; + } + + // forwards reaction rate + auto kf = node->get_expression1(); + // backwards reaction rate + auto kb = node->get_expression2(); + + // add reaction rates to vectors kf, kb + auto kf_str = to_nmodl(kf.get()); + logger->debug("KineticBlockVisitor :: k_f[{}] = {}", i_statement, kf_str); + rate_eqs.k_f.emplace_back(kf_str); + + if (kb) { + // kf is always defined, but for statements with operator "->" kb is not + auto kb_str = to_nmodl(kb.get()); + logger->debug("KineticBlockVisitor :: k_b[{}] = {}", i_statement, kb_str); + rate_eqs.k_b.emplace_back(kb_str); + } else { + rate_eqs.k_b.emplace_back(); + } + + // add empty non state var fluxes for this statement + non_state_var_fflux.emplace_back(); + non_state_var_bflux.emplace_back(); + + // add a row of zeros to the stochiometric matrices + rate_eqs.nu_L.emplace_back(std::vector<int>(state_var_count, 0)); + rate_eqs.nu_R.emplace_back(std::vector<int>(state_var_count, 0)); + + // visit each term in reaction statement and + // add the corresponding integer to the new row in the matrix + in_reaction_statement = true; + in_reaction_statement_lhs = true; + node->visit_children(this); + in_reaction_statement = false; + + // increment statement counter + ++i_statement; +} + +void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { + rate_eqs.nu_L.clear(); + rate_eqs.nu_R.clear(); + rate_eqs.k_f.clear(); + rate_eqs.k_b.clear(); + fflux.clear(); + bflux.clear(); + odes.clear(); + + // allocate these vectors with empty strings + compartment_factors = std::vector<std::string>(state_var_count); + additive_terms = std::vector<std::string>(state_var_count); + i_statement = 0; + + // construct stochiometric matrices and rate vectors + node->visit_children(this); + + // number of reaction statements + int Ni = static_cast<int>(rate_eqs.k_f.size()); + + // number of ODEs (= number of state vars) + int Nj = state_var_count; + + // generate fluxes + fflux = rate_eqs.k_f; + bflux = rate_eqs.k_b; + for (int i = 0; i < Ni; ++i) { + // contribution from state vars + for (int j = 0; j < Nj; ++j) { + std::string multiply_var = std::string("*").append(state_var[j]); + int nu_L = rate_eqs.nu_L[i][j]; + while (nu_L-- > 0) { + fflux[i] += multiply_var; + } + int nu_R = rate_eqs.nu_R[i][j]; + while (nu_R-- > 0) { + bflux[i] += multiply_var; + } + } + // contribution from non-state vars + if (!non_state_var_fflux[i].empty()) { + fflux[i] += std::string("*").append(non_state_var_fflux[i]); + } + if (!non_state_var_bflux[i].empty()) { + bflux[i] += std::string("*").append(non_state_var_bflux[i]); + } + } + for (int i = 0; i < Ni; ++i) { + logger->debug("KineticBlockVisitor :: fflux[{}] = {}", i, fflux[i]); + logger->debug("KineticBlockVisitor :: bflux[{}] = {}", i, bflux[i]); + } + + // generate ODEs + for (int j = 0; j < Nj; ++j) { + // rhs of ODE eq + std::string ode_rhs = additive_terms[j]; + for (int i = 0; i < Ni; ++i) { + int delta_nu = rate_eqs.nu_R[i][j] - rate_eqs.nu_L[i][j]; + if (delta_nu != 0) { + // if not the first RHS term, add + sign first + if (!ode_rhs.empty()) { + ode_rhs += " + "; + } + if (bflux[i].empty()) { + ode_rhs += "({}*({}))"_format(delta_nu, fflux[i]); + } else if (fflux[i].empty()) { + ode_rhs += "({}*(-{}))"_format(delta_nu, bflux[i]); + } else { + ode_rhs += "({}*({}-{}))"_format(delta_nu, fflux[i], bflux[i]); + } + } + } + // divide by COMPARTMENT factor if present + if (!compartment_factors[j].empty()) { + ode_rhs = "({})/({})"_format(ode_rhs, compartment_factors[j]); + } + // if rhs of ODE is not empty, add to list of ODEs + if (!ode_rhs.empty()) { + odes.push_back("{}' = {}"_format(state_var[j], ode_rhs)); + } + } + + for (const auto& ode: odes) { + logger->debug("KineticBlockVisitor :: ode : {}", ode); + } + + auto current_statement_block = node->get_statement_block(); + // remove reaction statements from kinetic block + remove_statements_from_block(current_statement_block.get(), statements_to_remove); + // add new statements + for (const auto& ode: odes) { + logger->debug("KineticBlockVisitor :: -> adding statement: {}", ode); + current_statement_block->addStatement(create_statement(ode)); + } + + // store pointer to kinetic block + kinetic_blocks.push_back(node); +} + +void KineticBlockVisitor::visit_program(ast::Program* node) { + // get state variables - assign an index to each + state_var_index.clear(); + state_var.clear(); + state_var_count = 0; + if (auto symtab = node->get_symbol_table()) { + auto statevars = symtab->get_variables_with_properties(NmodlType::state_var); + for (const auto& v: statevars) { + const auto& varname = v->get_name(); + logger->debug("state_var_index[{}] = {}", varname, state_var_count); + state_var_index[varname] = state_var_count++; + state_var.push_back(varname); + } + } + + // replace reaction statements within each kinetic block with equivalent ODEs + node->visit_children(this); + + // change KINETIC blocks -> DERIVATIVE blocks + auto blocks = node->get_blocks(); + for (auto* kinetic_block: kinetic_blocks) { + for (auto it = blocks.begin(); it != blocks.end(); ++it) { + if (it->get() == kinetic_block) { + auto dblock = std::make_shared<ast::DerivativeBlock>( + std::move(kinetic_block->get_name()), + std::move(kinetic_block->get_statement_block())); + ModToken tok{}; + dblock->set_token(tok); + *it = dblock; + } + } + } + node->set_blocks(std::move(blocks)); +} + +} // namespace nmodl diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp new file mode 100644 index 0000000000..8010a12067 --- /dev/null +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -0,0 +1,104 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/visitor_utils.hpp" +#include <map> +#include <memory> +#include <set> +#include <string> +#include <vector> + +namespace nmodl { + +/** + * \class KineticBlockVisitor + * \brief Visitor for kinetic block statements + * + * Replaces each KINETIC block with a DERIVATIVE block + * containing a system of ODEs that is equivalent to + * the original set of reaction statements + * + * Currently is only valid assuming the order + * of statements within the KINETIC block does not + * matter. Also does not yet support array variables. + * + */ + +class KineticBlockVisitor: public AstVisitor { + private: + /// update stochiometric matrices with reaction var term + void process_reac_var(const std::string& varname, int count = 1); + + /// stochiometric matrices nu_L, nu_R + /// forwards/backwards fluxes k_f, k_b + /// (see kinetic_schemes.ipynb notebook for details) + struct RateEqs { + std::vector<std::vector<int>> nu_L; + std::vector<std::vector<int>> nu_R; + std::vector<std::string> k_f; + std::vector<std::string> k_b; + } rate_eqs; + + /// multiplicative factors for ODEs from COMPARTMENT statements + std::vector<std::string> compartment_factors; + + /// additive constant terms for ODEs from reaction statements like ~ x << (a) + std::vector<std::string> additive_terms; + + /// multiplicate constant terms for fluxes from non-state vars as reactants + /// e.g. reaction statements like ~ x <-> c (a,a) + /// where c is not a state var, which is equivalent to the ODE + /// x' = a * (c - x) + std::vector<std::string> non_state_var_fflux; + std::vector<std::string> non_state_var_bflux; + + /// generated set of fluxes and ODEs + std::vector<std::string> fflux; + std::vector<std::string> bflux; + std::vector<std::string> odes; + + /// number of state variables + int state_var_count = 0; + + /// state variables vector + std::vector<std::string> state_var; + + /// map from state variable to corresponding index + std::map<std::string, int> state_var_index; + + /// true if we are visiting a reaction statement + bool in_reaction_statement = false; + + /// true if we are visiting the left hand side of reaction statement + bool in_reaction_statement_lhs = false; + + /// current statement index + int i_statement = 0; + + /// vector of kinetic block nodes + std::vector<ast::KineticBlock*> kinetic_blocks; + + /// statements to remove from block + std::set<ast::Node*> statements_to_remove; + + public: + KineticBlockVisitor() = default; + + void visit_reaction_operator(ast::ReactionOperator* node) override; + void visit_react_var_name(ast::ReactVarName* node) override; + void visit_reaction_statement(ast::ReactionStatement* node) override; + void visit_conserve(ast::Conserve* node) override; + void visit_compartment(ast::Compartment* node) override; + void visit_kinetic_block(ast::KineticBlock* node) override; + void visit_program(ast::Program* node) override; +}; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 122d2849cd..591b20c9f8 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -10,8 +10,8 @@ #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitor_utils.hpp" #include "visitors/sympy_solver_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace py = pybind11; @@ -42,16 +42,6 @@ SympySolverVisitor::construct_eigen_newton_solver_block( update_state_block); } -void SympySolverVisitor::remove_statements_from_block(ast::StatementBlock* block, - const std::set<ast::Node*> statements) { - auto& statement_vec = block->statements; - statement_vec.erase(std::remove_if(statement_vec.begin(), statement_vec.end(), - [&statements](std::shared_ptr<ast::Statement>& s) { - return statements.find(s.get()) != statements.end(); - }), - statement_vec.end()); -} - void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { current_statement_block = node; node->visit_children(this); diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 99ea96480a..fb0ac90f8d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -16,6 +16,7 @@ #include "symtab/symbol.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/lookup_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { @@ -44,10 +45,6 @@ class SympySolverVisitor: public AstVisitor { /** Replace binary expression with new expression provided as string */ static void replace_diffeq_expression(ast::DiffEqExpression* expr, const std::string& new_expr); - /** Remove statements from given statement block if they exist */ - static void remove_statements_from_block(ast::StatementBlock* block, - const std::set<ast::Node*> statements); - std::shared_ptr<ast::EigenNewtonSolverBlock> construct_eigen_newton_solver_block( const std::vector<std::string>& setup_x, const std::vector<std::string>& functor, diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index c27c6fc1ce..15384b4244 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -113,6 +113,17 @@ std::shared_ptr<StatementBlock> create_statement_block( } +void remove_statements_from_block(ast::StatementBlock* block, + const std::set<ast::Node*> statements) { + auto& statement_vec = block->statements; + statement_vec.erase(std::remove_if(statement_vec.begin(), statement_vec.end(), + [&statements](std::shared_ptr<ast::Statement>& s) { + return statements.find(s.get()) != statements.end(); + }), + statement_vec.end()); +} + + std::set<std::string> get_global_vars(Program* node) { std::set<std::string> vars; if (auto* symtab = node->get_symbol_table()) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index af92ac274c..1723491048 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -40,6 +40,10 @@ std::shared_ptr<ast::Statement> create_statement(const std::string& code_stateme std::shared_ptr<ast::StatementBlock> create_statement_block( const std::vector<std::string>& code_statements); +/** Remove statements from given statement block if they exist */ +void remove_statements_from_block(ast::StatementBlock* block, + const std::set<ast::Node*> statements); + /** Return set of strings with the names of all global variables */ std::set<std::string> get_global_vars(ast::Program* node); diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 7300e05a4e..6ded9013c1 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -21,6 +21,7 @@ #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/lookup_visitor.hpp" @@ -1966,6 +1967,362 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor") { } +//============================================================================= +// KineticBlock visitor tests +//============================================================================= + +std::vector<std::string> run_kinetic_block_visitor( + const std::string& text, + bool pade = false, + bool cse = false, + AstNodeType ret_nodetype = AstNodeType::DERIVATIVE_BLOCK) { + std::vector<std::string> results; + + // construct AST from text including KINETIC block(s) + NmodlDriver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(ast.get()); + + // run KineticBlock visitor on AST + KineticBlockVisitor v_kinetic; + v_kinetic.visit_program(ast.get()); + + // run lookup visitor to extract DERIVATIVE block(s) from AST + AstLookupVisitor v_lookup; + auto res = v_lookup.lookup(ast.get(), ret_nodetype); + for (const auto& r: res) { + results.push_back(to_nmodl(r.get())); + } + + return results; +} + +SCENARIO("KineticBlock visitor", "[kinetic]") { + GIVEN("KINETIC block with << reaction statement, 1 state var") { + std::string input_nmodl_text = R"( + STATE { + x + } + KINETIC states { + ~ x << (a*c/3.2) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (a*c/3.2) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with invalid << reaction statement with 2 state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + y << (2*z) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + })"; + THEN("Emit warning & do not process statement") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 1 state var") { + std::string input_nmodl_text = R"( + STATE { + x + } + KINETIC states { + ~ x -> (a) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(a*x)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 2 state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + y -> (f(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(f(v)*x*y)) + y' = (-1*(f(v)*x*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 2 state vars, CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + y -> (f(v)) + CONSERVE x + y = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(f(v)*x*y)) + y' = (-1*(f(v)*x*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block (ignore CONSERVE for now)") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var") { + // Here c is NOT a state variable + // see 9.9.2.1 of NEURON book + // c should be treated as a constant, i.e. + // -the diff. eq. for x should include the contribution from c + // -no diff. eq. should be generated for c itself + std::string input_nmodl_text = R"( + STATE { + x + } + KINETIC states { + ~ x <-> c (r, r) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(r*x-r*c)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement, 2 state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement, 2 state vars, CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x <-> y (a, b) + CONSERVE x + y = 0 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block (ignore CONSERVE for now)") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement & 1 COMPARTMENT statement") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + COMPARTMENT c-d {x y} + ~ x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = ((-1*(a*x-b*y)))/(c-d) + y' = ((1*(a*x-b*y)))/(c-d) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement & 2 COMPARTMENT statements") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + COMPARTMENT cx {x} + COMPARTMENT cy {y} + ~ x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = ((-1*(a*x-b*y)))/(cx) + y' = ((1*(a*x-b*y)))/(cy) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with two independent reaction statements") { + std::string input_nmodl_text = R"( + STATE { + w x y z + } + KINETIC states { + ~ x <-> y (a, b) + ~ w <-> z (c, d) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + w' = (-1*(c*w-d*z)) + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y)) + z' = (1*(c*w-d*z)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with two dependent reaction statements") { + std::string input_nmodl_text = R"( + STATE { + x y z + } + KINETIC states { + ~ x <-> y (a, b) + ~ y <-> z (c, d) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) + z' = (1*(c*y-d*z)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with a stoch coeff of 2") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ 2x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-2*(a*x*x-b*y)) + y' = (1*(a*x*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with duplicate state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-2*(a*x*x-b*y)) + y' = (1*(a*x*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with functions for reaction rates") { + // Example from sec 9.8, p238 of NEURON book + std::string input_nmodl_text = R"( + STATE { + mc m + } + KINETIC states { + ~ mc <-> m (a(v), b(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + mc' = (-1*(a(v)*mc-b(v)*m)) + m' = (1*(a(v)*mc-b(v)*m)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with stoch coeff 2, coupled pair of statements") { + // Example from sec 9.8, p239 of NEURON book + std::string input_nmodl_text = R"( + STATE { + A B C D + } + KINETIC states { + ~ 2A + B <-> C (k1, k2) + ~ C + D <-> A + 2B (k3, k4) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + A' = (-2*(k1*A*A*B-k2*C))+(1*(k3*C*D-k4*A*B*B)) + B' = (-1*(k1*A*A*B-k2*C))+(2*(k3*C*D-k4*A*B*B)) + C' = (1*(k1*A*A*B-k2*C))+(-1*(k3*C*D-k4*A*B*B)) + D' = (-1*(k3*C*D-k4*A*B*B)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } +} + //============================================================================= // SympySolver visitor tests //============================================================================= From cd52eeaad51f3c903934d77dac249e44204a2cf5 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 18 Mar 2019 14:13:26 +0100 Subject: [PATCH 167/871] Add version to executables and cleanup visitor client (BlueBrain/nmodl#73) - python nmodl module now have `__version__` for documentation (Resolves BlueBrain/nmodl#69) - update all executables to print version (with git commit with build time version) - cleanup visitor client to run all visitors easily NMODL Repo SHA: BlueBrain/nmodl@df46fa3dddd77bada14b9b29c6e0353d8e9721c8 --- nmodl/__init__.py | 1 + src/nmodl/lexer/main_c.cpp | 8 +- src/nmodl/lexer/main_nmodl.cpp | 6 +- src/nmodl/nmodl/main.cpp | 4 +- src/nmodl/parser/main_c.cpp | 12 +- src/nmodl/parser/main_nmodl.cpp | 14 +- src/nmodl/pybind/pynmodl.cpp | 4 + src/nmodl/version/version.h | 4 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 5 +- src/nmodl/visitors/main.cpp | 201 ++++++------------- 10 files changed, 102 insertions(+), 157 deletions(-) diff --git a/nmodl/__init__.py b/nmodl/__init__.py index e69de29bb2..af3c8702af 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -0,0 +1 @@ +from ._nmodl import __version__ diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 437df95c77..16c847933d 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -8,17 +8,23 @@ #include <fstream> #include "CLI/CLI.hpp" +#include "fmt/format.h" + #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" +#include "version/version.h" /** * Example of standalone lexer program for C codes that * demonstrate use of CLexer and CDriver classes. */ +using namespace fmt::literals; +using namespace nmodl; + int main(int argc, const char* argv[]) { - CLI::App app{"C-Lexer : Standalone Lexer for C Code"}; + CLI::App app{"C-Lexer : Standalone Lexer for C Code({})"_format(version::to_string())}; std::vector<std::string> files; app.add_option("file", files, "One or more C files to process") diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 7ed5cc5b14..12b49f096b 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -9,10 +9,13 @@ #include <streambuf> #include "CLI/CLI.hpp" +#include "fmt/format.h" + #include "ast/ast.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" +#include "version/version.h" /** * Stand alone lexer program for NMODL. This demonstrate basic @@ -21,6 +24,7 @@ * location. */ +using namespace fmt::literals; using namespace nmodl; using parser::NmodlDriver; @@ -111,7 +115,7 @@ void tokenize(const std::string& mod_text) { int main(int argc, const char* argv[]) { - CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code"}; + CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code({})"_format(version::to_string())}; std::vector<std::string> mod_files; std::vector<std::string> mod_texts; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index b82780ff73..41192519b7 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -21,6 +21,7 @@ #include "parser/nmodl_driver.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" +#include "version/version.h" #include "visitors/ast_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" @@ -43,7 +44,8 @@ using namespace codegen; using nmodl::parser::NmodlDriver; int main(int argc, const char* argv[]) { - CLI::App app{"NMODL : Source-to-Source Code Generation Framework"}; + CLI::App app{ + "NMODL : Source-to-Source Code Generation Framework [{}]"_format(version::to_string())}; /// list of mod files to process std::vector<std::string> mod_files; diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index 5e2fe29ccb..a4282539ef 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -8,16 +8,22 @@ #include <fstream> #include "CLI/CLI.hpp" +#include "fmt/format.h" + #include "parser/c11_driver.hpp" #include "utils/logger.hpp" +#include "version/version.h" /** * Standalone parser program for C. This demonstrate basic * usage of parser and driver class. */ +using namespace fmt::literals; +using namespace nmodl; + int main(int argc, const char* argv[]) { - CLI::App app{"C-Parser : Standalone Parser for C Code"}; + CLI::App app{"C-Parser : Standalone Parser for C Code({})"_format(version::to_string())}; std::vector<std::string> files; app.add_option("file", files, "One or more C files to process") @@ -27,11 +33,11 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); for (const auto& f: files) { - nmodl::logger->info("Processing {}", f); + logger->info("Processing {}", f); std::ifstream file(f); /// driver object creates lexer and parser - nmodl::parser::CDriver driver; + parser::CDriver driver; driver.set_verbose(true); /// just call parser method diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 0ea98b9139..abbc1c44c6 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -7,16 +7,22 @@ #include "CLI/CLI.hpp" +#include "fmt/format.h" + #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" +#include "version/version.h" /** * Standalone parser program for NMODL. This demonstrate * basic usage of parser and driver classes. */ +using namespace fmt::literals; +using namespace nmodl; + int main(int argc, const char* argv[]) { - CLI::App app{"NMODL-Parser : Standalone Parser for NMODL"}; + CLI::App app{"NMODL-Parser : Standalone Parser for NMODL({})"_format(version::to_string())}; std::vector<std::string> mod_files; std::vector<std::string> mod_texts; @@ -26,16 +32,16 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); - nmodl::parser::NmodlDriver driver; + parser::NmodlDriver driver; driver.set_verbose(true); for (const auto& f: mod_files) { - nmodl::logger->info("Processing file : {}", f); + logger->info("Processing file : {}", f); driver.parse_file(f); } for (const auto& text: mod_texts) { - nmodl::logger->info("Processing text : {}", text); + logger->info("Processing text : {}", text); driver.parse_string(text); } diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 6a01c1f778..527c393446 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -14,6 +14,7 @@ #include "parser/nmodl_driver.hpp" #include "pybind/pybind_utils.hpp" +#include "version/version.h" #include "visitors/visitor_utils.hpp" namespace py = pybind11; @@ -41,6 +42,9 @@ void init_ast_module(py::module& m); void init_symtab_module(py::module& m); PYBIND11_MODULE(_nmodl, m_nmodl) { + m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; + m_nmodl.attr("__version__") = nmodl::version::NMODL_VERSION; + py::class_<PyDriver> nmodl_driver(m_nmodl, "NmodlDriver"); // todo : what has changed ? fix this! diff --git a/src/nmodl/version/version.h b/src/nmodl/version/version.h index 2d687f5c70..d0622edd27 100644 --- a/src/nmodl/version/version.h +++ b/src/nmodl/version/version.h @@ -13,8 +13,8 @@ namespace nmodl { struct version { static const std::string GIT_REVISION; static const std::string NMODL_VERSION; - std::string to_string() { - return NMODL_VERSION + " [" + GIT_REVISION + "]"; + static std::string to_string() { + return NMODL_VERSION + " " + GIT_REVISION; } }; } // namespace nmodl diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index a5e5cc9ae7..154260a3dd 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -59,7 +59,8 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { // NOTE! CONSERVE statement "implicitly takes into account COMPARTMENT factors on LHS" // see p244 of NEURON book // need to ensure that we do this in the same way: presumably need - // to either multiply or divide state vars on LHS of conserve statement by their COMPARTMENT factors? + // to either multiply or divide state vars on LHS of conserve statement by their COMPARTMENT + // factors? logger->debug("KineticBlockVisitor :: CONSERVE statement ignored (for now): {} = {}", to_nmodl(node->get_react().get()), to_nmodl(node->get_expr().get())); statements_to_remove.insert(node); @@ -102,7 +103,7 @@ void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName* node) { // react_var_name node contains var_name and integer // var_name is the state variable which we convert to an index // integer is the value to be added to the stochiometric matrix at this index - if(in_reaction_statement){ + if (in_reaction_statement) { auto varname = node->get_node_name(); int count = node->get_value() ? node->get_value()->eval() : 1; process_reac_var(varname, count); diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 4bed8b82d7..cbe20ed17f 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -8,21 +8,31 @@ #include <sstream> #include "CLI/CLI.hpp" +#include "fmt/format.h" +#include "pybind11/embed.h" + #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" +#include "version/version.h" #include "visitors/ast_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" +#include "visitors/sympy_conductance_visitor.hpp" +#include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" + using namespace nmodl; +using namespace fmt::literals; /** * Standalone visitor program for NMODL. This demonstrate basic @@ -30,169 +40,74 @@ using namespace nmodl; **/ int main(int argc, const char* argv[]) { - CLI::App app{"NMODL Visitor : Runs standalone visitor classes"}; + CLI::App app{ + "NMODL Visitor : Runs standalone visitor classes({})"_format(version::to_string())}; bool verbose = false; std::vector<std::string> files; - app.add_flag("-v,--verbose", verbose, "Show intermediate output"); + app.add_flag("-v,--verbose", verbose, "Enable debug log level"); app.add_option("-f,--file,file", files, "One or more MOD files to process") ->required() ->check(CLI::ExistingFile); CLI11_PARSE(app, argc, argv); + if (verbose) { + logger->set_level(spdlog::level::debug); + } + + struct VisitorInfo { + std::shared_ptr<Visitor> v; + std::string id; + std::string description; + }; + + std::vector<VisitorInfo> visitors = { + {std::make_shared<AstVisitor>(), "astvis", "AstVisitor"}, + {std::make_shared<SymtabVisitor>(), "symtab", "SymtabVisitor"}, + {std::make_shared<JSONVisitor>(), "json", "JSONVisitor"}, + {std::make_shared<VerbatimVisitor>(), "verbatim", "VerbatimVisitor"}, + {std::make_shared<VerbatimVarRenameVisitor>(), "verbatim-rename", + "VerbatimVarRenameVisitor"}, + {std::make_shared<KineticBlockVisitor>(), "kinetic-rewrite", "KineticBlockVisitor"}, + {std::make_shared<ConstantFolderVisitor>(), "const-fold", "ConstantFolderVisitor"}, + {std::make_shared<InlineVisitor>(), "cnexp", "InlineVisitor"}, + {std::make_shared<LocalVarRenameVisitor>(), "local-rename", "LocalVarRenameVisitor"}, + {std::make_shared<SymtabVisitor>(), "symtab", "SymtabVisitor"}, + {std::make_shared<SympyConductanceVisitor>(), "sympy-cond", "SympyConductanceVisitor"}, + {std::make_shared<SymtabVisitor>(), "symtab", "SymtabVisitor"}, + {std::make_shared<SympySolverVisitor>(), "sympy-solve", "SympySolverVisitor"}, + {std::make_shared<CnexpSolveVisitor>(), "cnexp", "CnexpSolveVisitor"}, + {std::make_shared<LocalizeVisitor>(), "localize", "LocalizeVisitor"}, + {std::make_shared<PerfVisitor>(), "perf", "PerfVisitor"}, + }; + + pybind11::initialize_interpreter(); + for (const auto& filename: files) { - nmodl::logger->info("Processing {}", filename); + logger->info("Processing {}", filename); - std::string mod_filename = remove_extension(base_name(filename)); + std::string mod_file = remove_extension(base_name(filename)); /// driver object that creates lexer and parser - nmodl::parser::NmodlDriver driver; + parser::NmodlDriver driver; driver.parse_file(filename); /// shared_ptr to ast constructed from parsing nmodl file - auto ast = driver.ast(); - - { - AstVisitor v; - v.visit_program(ast.get()); - std::cout << "----AST VISITOR FINISHED----" << std::endl; - } - - { - /// constructor takes true/false argument for printing blocks - VerbatimVisitor v; - v.visit_program(ast.get()); - std::cout << "----VERBATIM VISITOR FINISHED----" << std::endl; - } - - { - std::stringstream ss; - JSONVisitor v(ss); - v.visit_program(ast.get()); - if (verbose) { - std::cout << "RESULT OF JSON VISITOR : " << std::endl << ss.str(); - } - std::cout << "----JSON VISITOR FINISHED----" << std::endl; - } - - { - SymtabVisitor v(false); - v.visit_program(ast.get()); - } - - { - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; - } - - { - VerbatimVarRenameVisitor v; - v.visit_program(ast.get()); - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.verbrename.mod"); - v.visit_program(ast.get()); - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.mod"); - v.visit_program(ast.get()); - } - - { - CnexpSolveVisitor v; - v.visit_program(ast.get()); - std::cout << "----CNEXP SOLVE VISITOR FINISHED----" << std::endl; - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.mod"); - v.visit_program(ast.get()); - } - + auto ast = driver.ast().get(); - { - InlineVisitor v; - v.visit_program(ast.get()); + /// run all visitors and generate mod file after each run + for (const auto& visitor: visitors) { + logger->info("Running {}", visitor.description); + visitor.v->visit_program(ast); + std::string file = mod_file + "." + visitor.id + ".mod"; + NmodlPrintVisitor(file).visit_program(ast); + logger->info("NMODL visitor generated {}", file); } + } + pybind11::finalize_interpreter(); - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.mod"); - v.visit_program(ast.get()); - } - - { - LocalVarRenameVisitor v; - v.visit_program(ast.get()); - std::cout << "----LOCAL VAR RENAME VISITOR FINISHED----" << std::endl; - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.mod"); - v.visit_program(ast.get()); - } - - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - { - /// for benchmarking/plotting purpose we want to enable unsafe mode - bool ignore_verbatim = true; - LocalizeVisitor v(ignore_verbatim); - v.visit_program(ast.get()); - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.mod"); - v.visit_program(ast.get()); - } - - { - LocalVarRenameVisitor v; - v.visit_program(ast.get()); - } - - { - SymtabVisitor v(true); - v.visit_program(ast.get()); - } - - { - std::stringstream stream; - auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); - std::cout << "----SYMTAB VISITOR FINISHED----" << std::endl; - } - - { - NmodlPrintVisitor v(mod_filename + ".nmodl.cnexp.in.ren.loc.ren.mod"); - v.visit_program(ast.get()); - } - - { - PerfVisitor v(mod_filename + ".perf.json"); - v.visit_program(ast.get()); - - auto symtab = ast->get_symbol_table(); - std::stringstream ss; - symtab->print(ss, 0); - std::cout << ss.str(); - - ss.str(""); - v.print(ss); - std::cout << ss.str() << std::endl; - std::cout << "----PERF VISITOR FINISHED----" << std::endl; - } - } return 0; } From 0c8da96b0ed7a2f4eaacb80951939ed677db2b89 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@gmail.com> Date: Mon, 18 Mar 2019 17:26:15 +0100 Subject: [PATCH 168/871] Fixed minor codegen issues to ensure successful coreneuron builds (BlueBrain/nmodl#80) - Mechanism instance wasn't being printed in C++ backend NMODL Repo SHA: BlueBrain/nmodl@8e1d306ce19a52719931fdb275151206fb535237 --- src/nmodl/codegen/codegen_c_visitor.cpp | 10 ++++++++-- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 2 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 4 ++-- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 47fb851e61..b4c80a7eab 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -19,6 +19,7 @@ #include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" +#include "visitors/visitor_utils.hpp" using namespace fmt::literals; @@ -2257,7 +2258,9 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include <coreneuron/mech/mod2c_core_thread.h>"); printer->add_line("#include <coreneuron/scopmath_core/newton_struct.h>"); printer->add_line("#include <_kinderiv.h>"); - printer->add_line("#include <newton/newton.hpp>"); + if (info.eigen_newton_solver_exist) { + printer->add_line("#include <newton/newton.hpp>"); + } } @@ -3447,7 +3450,7 @@ void CodegenCVisitor::print_net_receive_loop_end() { } -void CodegenCVisitor::print_net_receive_buffering() { +void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { if (!net_receive_required() || info.artificial_cell) { return; } @@ -3460,6 +3463,9 @@ void CodegenCVisitor::print_net_receive_buffering() { printer->add_line( "NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;"_format(ptr_type_qualifier())); + if (need_mech_inst) { + printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + } print_net_receive_loop_begin(); printer->add_line("int start = nrb->_displ[i];"); printer->add_line("int end = nrb->_displ[i+1];"); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 5212e8683b..aa882af9de 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -817,7 +817,7 @@ class CodegenCVisitor: public AstVisitor { /// kernel for buffering net_receive events - void print_net_receive_buffering(); + void print_net_receive_buffering(bool need_mech_inst = true); /// net_receive kernel function definition diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index bdceefcc06..c42297239b 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -307,7 +307,7 @@ void CodegenIspcVisitor::print_compute_functions() { } } print_net_receive_kernel(); - print_net_receive_buffering(); + print_net_receive_buffering(false); print_nrn_init(false); print_nrn_cur(); print_nrn_state(); diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 8a3b62f10a..0e6742fd1b 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -65,7 +65,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { - std::cerr << "cnexp solver not possible"; + logger->error("cnexp solver not possible"); } } else if (solve_method == euler_method) { std::string solution = diffeq_driver.solve(equation, solve_method); @@ -84,7 +84,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { symbol->created_from_state(); program_symtab->insert(symbol); } else { - std::cerr << "solver method '" + solve_method + "' not supported"; + logger->error("solver method '{}' not supported", solve_method); } } } From d6e1f2e57ff91d28d8e52c378524a15bd9be097f Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 18 Mar 2019 17:28:23 +0100 Subject: [PATCH 169/871] Make Def-Use analyser pass array variable aware (BlueBrain/nmodl#75) - previous implementation was not considering indexed variable but was doing exact match on the base variable name - if variable has index which is not a compile constant then make node as used / defined irrespective of index - update tests NMODL Repo SHA: BlueBrain/nmodl@4f3c2c2bc67afa3edc36ac4ba7df7bf86964becd --- src/nmodl/visitors/defuse_analyze_visitor.cpp | 59 +++++++++------ src/nmodl/visitors/defuse_analyze_visitor.hpp | 37 +++++++-- test/nmodl/transpiler/visitor/visitor.cpp | 75 ++++++++++++++----- 3 files changed, 127 insertions(+), 44 deletions(-) diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index e3c0265e22..9db97ab806 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -256,38 +256,55 @@ void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim* node) { } } -/** Update def-use chain if we encounter a variable that we are looking for. +/** + * Update the Def-Use chain for given variable + * + * @param name Name of the variable excluding index or dimension + * + * Update def-use chain if we encounter a variable that we are looking for. * If we encounter non-supported construct then we mark that variable as "use" * because we haven't completely analyzed the usage. Marking that variable "U" * make sures that won't get optimized. Then we distinguish between local and - * non-local variables. All variables that appear on lhs are maked as "definitions" + * non-local variables. All variables that appear on lhs are marked as "definitions" * whereas the one on rhs are marked as "usages". */ void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { - if (name == variable_name) { - auto symbol = current_symtab->lookup_in_scope(name); - // variable properties that make it local - auto properties = NmodlType::local_var | NmodlType::argument; - auto is_local = symbol->has_properties(properties); + auto symbol = current_symtab->lookup_in_scope(name); + assert(symbol != nullptr); + // variable properties that make variable local + auto properties = NmodlType::local_var | NmodlType::argument; + auto is_local = symbol->has_properties(properties); - if (unsupported_node) { - current_chain->push_back(DUInstance(DUState::U)); - } else if (visiting_lhs) { - if (is_local) { - current_chain->push_back(DUInstance(DUState::LD)); - } else { - current_chain->push_back(DUInstance(DUState::D)); - } + if (unsupported_node) { + current_chain->push_back(DUInstance(DUState::U)); + } else if (visiting_lhs) { + if (is_local) { + current_chain->push_back(DUInstance(DUState::LD)); } else { - if (is_local) { - current_chain->push_back(DUInstance(DUState::LU)); - } else { - current_chain->push_back(DUInstance(DUState::U)); - } + current_chain->push_back(DUInstance(DUState::D)); + } + } else { + if (is_local) { + current_chain->push_back(DUInstance(DUState::LU)); + } else { + current_chain->push_back(DUInstance(DUState::U)); } } } +void DefUseAnalyzeVisitor::process_variable(const std::string& name) { + if (name == variable_name) { + update_defuse_chain(name); + } +} + +void DefUseAnalyzeVisitor::process_variable(const std::string& name, int index) { + std::string fullname = name + "[" + std::to_string(index) + "]"; + if (fullname == variable_name) { + update_defuse_chain(name); + } +} + void DefUseAnalyzeVisitor::visit_with_new_chain(ast::Node* node, DUState state) { auto last_chain = current_chain; start_new_chain(state); @@ -300,7 +317,7 @@ void DefUseAnalyzeVisitor::start_new_chain(DUState state) { current_chain = ¤t_chain->back().children; } -DUChain DefUseAnalyzeVisitor::analyze(ast::Node* node, const std::string& name) { +DUChain DefUseAnalyzeVisitor::analyze(ast::AST* node, const std::string& name) { /// re-initialize state variable_name = name; visiting_lhs = false; diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 7299f1365e..a97453084b 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -13,7 +13,9 @@ #include "ast/ast.hpp" #include "printer/json_printer.hpp" #include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { @@ -189,6 +191,9 @@ class DefUseAnalyzeVisitor: public AstVisitor { /// starting visiting lhs of assignment statement bool visiting_lhs = false; + void process_variable(const std::string& name); + void process_variable(const std::string& name, int index); + void update_defuse_chain(const std::string& name); void visit_unsupported_node(ast::Node* node); void visit_with_new_chain(ast::Node* node, DUState state); @@ -238,13 +243,35 @@ class DefUseAnalyzeVisitor: public AstVisitor { } virtual void visit_var_name(ast::VarName* node) override { - update_defuse_chain(node->get_node_name()); - }; + std::string variable = to_nmodl(node); + process_variable(variable); + } virtual void visit_name(ast::Name* node) override { - update_defuse_chain(node->get_node_name()); - }; + std::string variable = to_nmodl(node); + process_variable(variable); + } + virtual void visit_indexed_name(ast::IndexedName* node) override { + std::string name = node->get_node_name(); + auto length = node->get_length(); + + /// index should be an integer (e.g. after constant folding) + /// if this is not the case and then we can't determine exact + /// def-use chain + if (!length->is_integer()) { + /// check if variable name without index part is same + auto variable_name_prefix = variable_name.substr(0, variable_name.find("[")); + if (name == variable_name_prefix) { + update_defuse_chain(variable_name_prefix); + std::string text = to_nmodl(node); + nmodl::logger->info("index used to access variable is not known : {} ", text); + } + return; + } + auto index = std::dynamic_pointer_cast<ast::Integer>(length); + process_variable(name, index->eval()); + } /// statements / nodes that should not be used for def-use chain analysis @@ -255,7 +282,7 @@ class DefUseAnalyzeVisitor: public AstVisitor { virtual void visit_argument(ast::Argument* /*node*/) override {} /// compute def-use chain for a variable within the node - DUChain analyze(ast::Node* node, const std::string& name); + DUChain analyze(ast::AST* node, const std::string& name); }; } // namespace nmodl diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 6ded9013c1..768dc1bb15 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1263,27 +1263,19 @@ std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::stri driver.parse_string(text); auto ast = driver.ast(); - { - SymtabVisitor v; - v.visit_program(ast.get()); - } - - { - InlineVisitor v1; - v1.visit_program(ast.get()); - } + SymtabVisitor().visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); - { - std::vector<DUChain> chains; - DefUseAnalyzeVisitor v(ast->get_symbol_table()); + std::vector<DUChain> chains; + DefUseAnalyzeVisitor v(ast->get_symbol_table()); - for (const auto& block: ast->get_blocks()) { - if (block->get_node_type() != AstNodeType::NEURON_BLOCK) { - chains.push_back(v.analyze(block.get(), variable)); - } - } - return chains; + /// analyse only derivative blocks in this test + auto blocks = AstLookupVisitor().lookup(ast.get(), AstNodeType::DERIVATIVE_BLOCK); + for (auto& block: blocks) { + auto node = block.get(); + chains.push_back(v.analyze(node, variable)); } + return chains; } SCENARIO("Running defuse analyzer") { @@ -1334,6 +1326,53 @@ SCENARIO("Running defuse analyzer") { } } + GIVEN("use of array variables") { + std::string nmodl_text = R"( + DEFINE N 3 + STATE { + m[N] + h[N] + n[N] + o[N] + } + DERIVATIVE states { + LOCAL tau[N] + tau[0] = 1 : tau[0] is defined + tau[2] = 1 + tau[1] + tau[2] : tau[1] is used; tau[2] is defined as well as used + m[0] = m[1] : m[0] is defined and used on next line; m[1] is used + h[1] = m[0] + h[0] : h[0] is used; h[1] is defined + o[i] = 1 : o[i] is defined for any i + n[i+1] = 1 + n[i] : n[i] is used as well as defined for any i + } + )"; + + THEN("Def-Use analyser distinguishes variables by array index") { + std::string input = reindent_text(nmodl_text); + { + auto m0 = run_defuse_visitor(input, "m[0]"); + auto m1 = run_defuse_visitor(input, "m[1]"); + auto h1 = run_defuse_visitor(input, "h[1]"); + auto tau0 = run_defuse_visitor(input, "tau[0]"); + auto tau1 = run_defuse_visitor(input, "tau[1]"); + auto tau2 = run_defuse_visitor(input, "tau[2]"); + auto n0 = run_defuse_visitor(input, "n[0]"); + auto n1 = run_defuse_visitor(input, "n[1]"); + auto o0 = run_defuse_visitor(input, "o[0]"); + + REQUIRE(m0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"},{"name":"U"}]})"); + REQUIRE(m1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"}]})"); + REQUIRE(h1[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); + REQUIRE(tau0[0].to_string() == R"({"DerivativeBlock":[{"name":"LD"}]})"); + REQUIRE(tau1[0].to_string() == R"({"DerivativeBlock":[{"name":"LU"}]})"); + REQUIRE(tau2[0].to_string() == + R"({"DerivativeBlock":[{"name":"LU"},{"name":"LD"}]})"); + REQUIRE(n0[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); + REQUIRE(n1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); + REQUIRE(o0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); + } + } + } + GIVEN("global variable definition in else block") { std::string nmodl_text = R"( NEURON { From 9b2690a5e2fc507085c42119d8ebde3c322fa836 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 18 Mar 2019 17:34:05 +0100 Subject: [PATCH 170/871] Implement loop unroll visitor (BlueBrain/nmodl#77) - perform unroll for loops with know iteration count - disable unroll if verbatim blocks exist - add tests and new command line option NMODL Repo SHA: BlueBrain/nmodl@2b4db5a92ba8829563140dbad3cdb848b6e7bceb --- .../language/templates/lookup_visitor.cpp | 4 +- .../language/templates/lookup_visitor.hpp | 4 +- src/nmodl/language/templates/pyvisitor.cpp | 4 +- src/nmodl/nmodl/main.cpp | 13 ++ src/nmodl/visitors/CMakeLists.txt | 2 + .../visitors/constant_folder_visitor.cpp | 9 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 164 ++++++++++++++++++ src/nmodl/visitors/loop_unroll_visitor.hpp | 55 ++++++ test/nmodl/transpiler/visitor/visitor.cpp | 140 +++++++++++++++ 9 files changed, 388 insertions(+), 7 deletions(-) create mode 100644 src/nmodl/visitors/loop_unroll_visitor.cpp create mode 100644 src/nmodl/visitors/loop_unroll_visitor.hpp diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/lookup_visitor.cpp index 68f3e6e1db..617ceeb841 100644 --- a/src/nmodl/language/templates/lookup_visitor.cpp +++ b/src/nmodl/language/templates/lookup_visitor.cpp @@ -25,7 +25,7 @@ void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name {% endfor %} -std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(Program* node, std::vector<AstNodeType>& _types) { +std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node, std::vector<AstNodeType>& _types) { nodes.clear(); types = _types; node->accept(this); @@ -33,7 +33,7 @@ std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(Program* node, s } -std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(Program* node, AstNodeType type) { +std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node, AstNodeType type) { nodes.clear(); types.clear(); types.push_back(type); diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index a76559f369..cc23da5b44 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -34,9 +34,9 @@ class AstLookupVisitor : public Visitor { AstLookupVisitor(const std::vector<ast::AstNodeType>& types) : types(types) {} - std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, ast::AstNodeType type); + std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST* node, ast::AstNodeType type); - std::vector<std::shared_ptr<ast::AST>> lookup(ast::Program* node, std::vector<ast::AstNodeType>& types); + std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST* node, std::vector<ast::AstNodeType>& types); const std::vector<std::shared_ptr<ast::AST>>& get_nodes() const noexcept { return nodes; diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 0af5b487f0..d9f5a67530 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -93,8 +93,8 @@ void init_visitor_module(py::module& m) { .def(py::init<ast::AstNodeType>()) .def("get_nodes", &AstLookupVisitor::get_nodes) .def("clear", &AstLookupVisitor::clear) - .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::Program*, ast::AstNodeType)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::Program*, std::vector<ast::AstNodeType>&)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*, ast::AstNodeType)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*, std::vector<ast::AstNodeType>&)) &AstLookupVisitor::lookup) {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &AstLookupVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 41192519b7..952013ef23 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -30,6 +30,7 @@ #include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" +#include "visitors/loop_unroll_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" @@ -83,6 +84,9 @@ int main(int argc, const char* argv[]) { /// true if inlining at nmodl level to be done bool nmodl_inline(false); + /// true if unroll at nmodl level to be done + bool nmodl_unroll(false); + /// true if perform constant folding at nmodl level to be done bool nmodl_const_folding(false); @@ -158,6 +162,7 @@ int main(int argc, const char* argv[]) { auto passes_opt = app.add_subcommand("passes", "Analyse/Optimization passes")->ignore_case(); passes_opt->add_flag("--inline", nmodl_inline, "Perform inlining at NMODL level")->ignore_case(); + passes_opt->add_flag("--unroll", nmodl_unroll, "Perform loop unroll at NMODL level")->ignore_case(); passes_opt->add_flag("--const-folding", nmodl_const_folding, "Perform constant folding at NMODL level")->ignore_case(); passes_opt->add_flag("--localize", localize, "Convert RANGE variables to LOCAL")->ignore_case(); passes_opt->add_flag("--localize-verbatim", localize_verbatim, "Convert RANGE variables to LOCAL even if verbatim block exist")->ignore_case(); @@ -270,6 +275,14 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("constfold")); } + if (nmodl_unroll) { + logger->info("Running nmodl loop unroll visitor"); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("unroll")); + SymtabVisitor(update_symtab).visit_program(ast.get()); + } + if (nmodl_inline) { logger->info("Running nmodl inline visitor"); InlineVisitor().visit_program(ast.get()); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index bea5e35c3e..f9ed334c7b 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -16,6 +16,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/loop_unroll_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/loop_unroll_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.ipp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index c9e49b7da6..28254e98b2 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "visitors/constant_folder_visitor.hpp" +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { @@ -107,7 +109,7 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* nod /// first expression which is wrapped auto expr = node->get_expression(); - /// if wrapped expressesion is parentheses + /// if wrapped expression is parentheses bool is_parentheses = false; /// opposite to visit_paren_expression, we might have @@ -155,6 +157,8 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* nod return; } + std::string nmodl_before = to_nmodl(binary_expr.get()); + /// compute the value of expression auto value = compute(get_value(lhs), op, get_value(rhs)); @@ -166,6 +170,9 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* nod } else { node->set_expression(std::make_shared<ast::Float>(value)); } + + std::string nmodl_after = to_nmodl(node->get_expression().get()); + logger->debug("ConstantFolderVisitor : expression {} folded to {}", nmodl_before, nmodl_after); } } // namespace nmodl diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp new file mode 100644 index 0000000000..60990eff97 --- /dev/null +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -0,0 +1,164 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "visitors/loop_unroll_visitor.hpp" +#include "parser/c11_driver.hpp" +#include "utils/logger.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/visitor_utils.hpp" + + +namespace nmodl { + +/** + * \class IndexRemover + * \brief Helper visitor to replace index of array variable with integer + * + * When loop is unrolled, the index variable like `i` : + * + * ca[i] <-> ca[i+1] + * + * has type `Name` in the AST. This needs to be replaced with `Integer` + * for optimizations like constant folding. This pass look at name and + * binary expressions under index variables. + */ +class IndexRemover: public AstVisitor { + private: + /// index variable name + std::string index; + + /// integer value of index variable + int value; + + /// true if we are visiting index variable + bool under_indexed_name = false; + + public: + IndexRemover(std::string index, int value) + : index(index) + , value(value) {} + + /// if expression we are visiting is `Name` then return new `Integer` node + std::shared_ptr<ast::Expression> replace_for_name(const std::shared_ptr<ast::Expression> node) { + if (node->is_name()) { + auto name = std::dynamic_pointer_cast<ast::Name>(node); + if (name->get_node_name() == index) { + return std::make_shared<ast::Integer>(value, nullptr); + } + } + return node; + } + + virtual void visit_binary_expression(ast::BinaryExpression* node) override { + node->visit_children(this); + if (under_indexed_name) { + /// first recursively replaces childrens + /// replace lhs & rhs if they have matching index variable + auto lhs = replace_for_name(node->get_lhs()); + auto rhs = replace_for_name(node->get_rhs()); + node->set_lhs(std::move(lhs)); + node->set_rhs(std::move(rhs)); + } + } + + virtual void visit_indexed_name(ast::IndexedName* node) override { + under_indexed_name = true; + node->visit_children(this); + /// once all children are replaced, do the same for index + auto length = replace_for_name(node->get_length()); + node->set_length(std::move(length)); + under_indexed_name = false; + } +}; + + +/// return underlying expression wrapped by WrappedExpression +static std::shared_ptr<ast::Expression> unwrap(const std::shared_ptr<ast::Expression>& expr) { + if (expr && expr->is_wrapped_expression()) { + auto e = std::dynamic_pointer_cast<ast::WrappedExpression>(expr); + return e->get_expression(); + } + return expr; +} + + +/** + * Unroll given for loop + * + * @param node From loop node in the AST + * @return expression statement represeing unrolled loop if successfull otherwise nullptr + */ +static std::shared_ptr<ast::ExpressionStatement> unroll_for_loop( + const std::shared_ptr<ast::FromStatement> node) { + /// loop can be in the form of `FROM i=(0) TO (10)` + /// so first unwrap all elements of the loop + const auto from = unwrap(node->get_from()); + const auto to = unwrap(node->get_to()); + const auto increment = unwrap(node->get_increment()); + + /// we can unroll if iteration space of the loop is known + /// after constant folding start, end and increment must be known + if (!from->is_integer() || !to->is_integer() || + (increment != nullptr && !increment->is_integer())) { + return nullptr; + } + + int start = std::dynamic_pointer_cast<ast::Integer>(from)->eval(); + int end = std::dynamic_pointer_cast<ast::Integer>(to)->eval(); + int step = 1; + if (increment != nullptr) { + step = std::dynamic_pointer_cast<ast::Integer>(increment)->eval(); + } + + ast::StatementVector statements; + std::string index_var = node->get_node_name(); + for (int i = start; i <= end; i += step) { + /// duplicate loop body and copy all statements to new vector + auto new_block = node->get_statement_block()->clone(); + IndexRemover(index_var, i).visit_statement_block(new_block); + statements.insert(statements.end(), new_block->statements.begin(), + new_block->statements.end()); + delete new_block; + } + + /// create new statement representing unrolled loop + auto block = new ast::StatementBlock(std::move(statements)); + return std::make_shared<ast::ExpressionStatement>(block); +} + + +/** + * Parse verbatim blocks and rename variable if it is used. + */ +void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { + node->visit_children(this); + + for (auto iter = node->statements.begin(); iter != node->statements.end(); iter++) { + if ((*iter)->is_from_statement()) { + auto statement = std::dynamic_pointer_cast<ast::FromStatement>((*iter)); + + /// check if any verbatim block exists + auto verbatim_blocks = AstLookupVisitor().lookup(statement.get(), + ast::AstNodeType::VERBATIM); + if (!verbatim_blocks.empty()) { + logger->debug("LoopUnrollVisitor : can not unroll because of verbatim block"); + continue; + } + + /// unroll loop, replace current statement on successfull unroll + auto new_statement = unroll_for_loop(statement); + if (new_statement != nullptr) { + (*iter) = new_statement; + std::string before = to_nmodl(statement.get()); + std::string after = to_nmodl(new_statement.get()); + logger->debug("LoopUnrollVisitor : \n {} \n unrolled to \n {}", before, after); + } + } + } +} + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp new file mode 100644 index 0000000000..00d1ce21ff --- /dev/null +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -0,0 +1,55 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include <string> + +#include "ast/ast.hpp" +#include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" + + +namespace nmodl { + +/** + * \class LoopUnrollVisitor + * \brief Unroll for loop in the AST + * + * Derivative and kinetic blocks have for loop with coupled set of ODEs : + * + * DEFINE NANN 4 + * KINETIC state { + * FROM i=0 TO NANN-2 { + * ~ ca[i] <-> ca[i+1] (DFree*frat[i+1]*1(um), DFree*frat[i+1]*1(um)) + * } + * } + * + * To solve these ODEs with SymPy, we need to get all ODEs from such loops. + * This visitor unroll such loops and insert new expression statement in + * the AST : + * + * KINETIC state { + * { + * ~ ca[0] <-> ca[0+1] (DFree*frat[0+1]*1(um), DFree*frat[0+1]*1(um)) + * ~ ca[1] <-> ca[1+1] (DFree*frat[1+1]*1(um), DFree*frat[1+1]*1(um)) + * ~ ca[2] <-> ca[2+1] (DFree*frat[2+1]*1(um), DFree*frat[2+1]*1(um)) + * } + * } + * + * Note that the index `0+1` is not expanded to `1` because we do not run + * constant folder pass within this loop (but could be easily done). + */ + +class LoopUnrollVisitor: public AstVisitor { + public: + LoopUnrollVisitor() = default; + + virtual void visit_statement_block(ast::StatementBlock* node) override; +}; + +} // namespace nmodl \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 768dc1bb15..ae8686525c 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -25,6 +25,7 @@ #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/lookup_visitor.hpp" +#include "visitors/loop_unroll_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/rename_visitor.hpp" @@ -3694,3 +3695,142 @@ TEST_CASE("Constant Folding Visitor") { } } } + + +//============================================================================= +// Loop unroll tests +//============================================================================= + +std::string run_loop_unroll_visitor(const std::string& text) { + NmodlDriver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + return to_nmodl(ast.get(), {AstNodeType::DEFINE}); +} + +TEST_CASE("Loop Unroll visitor") { + SECTION("Successful unrolls with constant folding") { + GIVEN("A loop with known iteration space") { + std::string input_nmodl = R"( + DEFINE N 2 + PROCEDURE rates() { + LOCAL x[N] + FROM i=0 TO N { + x[i] = x[i] + 11 + } + FROM i=(0+(0+1)) TO (N+2-1) { + x[(i+0)] = x[i+1] + 11 + } + } + KINETIC state { + FROM i=1 TO N+1 { + ~ ca[i] <-> ca[i+1] (DFree*frat[i+1]*1(um), DFree*frat[i+1]*1(um)) + } + } + )"; + std::string output_nmodl = R"( + PROCEDURE rates() { + LOCAL x[N] + { + x[0] = x[0]+11 + x[1] = x[1]+11 + x[2] = x[2]+11 + } + { + x[1] = x[2]+11 + x[2] = x[3]+11 + x[3] = x[4]+11 + } + } + + KINETIC state { + { + ~ ca[1] <-> ca[2] (DFree*frat[2]*1(um), DFree*frat[2]*1(um)) + ~ ca[2] <-> ca[3] (DFree*frat[3]*1(um), DFree*frat[3]*1(um)) + ~ ca[3] <-> ca[4] (DFree*frat[4]*1(um), DFree*frat[4]*1(um)) + } + } + )"; + THEN("Loop body gets correctly unrolled") { + auto result = run_loop_unroll_visitor(input_nmodl); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + + GIVEN("A nested loop") { + std::string input_nmodl = R"( + DEFINE N 1 + PROCEDURE rates() { + LOCAL x[N] + FROM i=0 TO N { + FROM j=1 TO N+1 { + x[i] = x[i+j] + 1 + } + } + } + )"; + std::string output_nmodl = R"( + PROCEDURE rates() { + LOCAL x[N] + { + { + x[0] = x[1]+1 + x[0] = x[2]+1 + } + { + x[1] = x[2]+1 + x[1] = x[3]+1 + } + } + } + )"; + THEN("Loop get unrolled recursively") { + auto result = run_loop_unroll_visitor(input_nmodl); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + + + GIVEN("Loop with verbatim and unknown iteration space") { + std::string input_nmodl = R"( + DEFINE N 1 + PROCEDURE rates() { + LOCAL x[N] + FROM i=((0+0)) TO (((N+0))) { + FROM j=1 TO k { + x[i] = x[i+k] + 1 + } + } + FROM i=0 TO N { + VERBATIM ENDVERBATIM + } + } + )"; + std::string output_nmodl = R"( + PROCEDURE rates() { + LOCAL x[N] + { + FROM j = 1 TO k { + x[0] = x[0+k]+1 + } + FROM j = 1 TO k { + x[1] = x[1+k]+1 + } + } + FROM i = 0 TO N { + VERBATIM ENDVERBATIM + } + } + )"; + THEN("Only some loops get unrolled") { + auto result = run_loop_unroll_visitor(input_nmodl); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + } +} \ No newline at end of file From 7090ea13d822409dff7e88aea6bafe54b9fe27b3 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 18 Mar 2019 17:36:50 +0100 Subject: [PATCH 171/871] Reduce number of variables passed to SymPy (BlueBrain/nmodl#78) - instead of passing all variables in global and local symbol tables, pass all names used in current breakpoint block - BlueBrain/nmodl#27 is not a bug but differentiate2c takes long time as GluSynapse.mod has ~80 variables - improve differentiate2c routine - print doubles as doubles using .evalf() in output - generate simpler expressions (don't substitute for constant vars) - faster text-comparison check if expr matches existing var - clearer naming of variables within routine - tests update - add GluSynapse CONDUCTANCE unit test - exclude UNIT_DEF type from to_nmodl_for_sympy() NMODL Repo SHA: BlueBrain/nmodl@a5b1137ca2ae59b1b3567451cb904ea7f84e857e --- nmodl/ode.py | 88 ++++++++----- .../visitors/sympy_conductance_visitor.cpp | 31 +++-- .../visitors/sympy_conductance_visitor.hpp | 6 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 2 +- test/nmodl/transpiler/ode/test_ode.py | 16 +-- test/nmodl/transpiler/visitor/visitor.cpp | 120 +++++++++++++++++- 6 files changed, 205 insertions(+), 58 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index 38bdc75b6a..2b59709996 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -28,7 +28,7 @@ def make_unique_prefix(vars, default_prefix="tmp"): for v in vars: # if v is long enough to match prefix # and first part of it matches prefix - if ((len(v) >= len(prefix)) and (v[: len(prefix)] == prefix)): + if (len(v) >= len(prefix)) and (v[: len(prefix)] == prefix): # append undescore to prefix, try again prefix += "_" break @@ -100,9 +100,7 @@ def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): new_local_vars.append(sp.ccode(x_old)) code.append(f"{sp.ccode(x_old)} = {sp.ccode(x)}") if do_cse: - my_symbols = sp.utilities.iterables.numbered_symbols( - prefix=prefix - ) + my_symbols = sp.utilities.iterables.numbered_symbols(prefix=prefix) sub_exprs, simplified_rhs = sp.cse( rhs, symbols=my_symbols, optimizations="basic", order="canonical" ) @@ -127,8 +125,10 @@ def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): code.append(f"F[{i}] = {sp.ccode(eq.evalf().simplify())}") for i, jac in enumerate(sp.eye(jacobian.rows, jacobian.rows) - jacobian * dt): # todo: fix indexing to be ascending order - flat_index = jacobian.rows*(i%jacobian.rows) + (i//jacobian.rows) - code.append(f"J[{flat_index}] = {sp.ccode(jac.subs(Xvecsubs).evalf().simplify())}") + flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) + code.append( + f"J[{flat_index}] = {sp.ccode(jac.subs(Xvecsubs).evalf().simplify())}" + ) return code, new_local_vars @@ -264,39 +264,65 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): # parse string into SymPy equation expr = sp.sympify(expression, locals=sympy_vars) - # parse previous equations into (lhs, rhs) pairs & reverse order - prev_eqs = [ - ( - sp.sympify(e.split("=", 1)[0], locals=sympy_vars), - sp.sympify(e.split("=", 1)[1], locals=sympy_vars), - ) - for e in prev_expressions - ] + # parse previous expressions in the order that they came in + # substitute any x-dependent vars in rhs with their rhs expressions, + # so that the x-dependence (if any) is explicit in each equation + # and add boolean x_dependent flag for each equation + prev_eqs = [] + for e in prev_expressions: + # parse into pairs of (lhs, rhs) SymPy expressions + e_lhs = sp.sympify(e.split("=", 1)[0], locals=sympy_vars) + e_rhs = sp.sympify(e.split("=", 1)[1], locals=sympy_vars) + # substitute in reverse order any x-dependent prior rhs + for lhs, rhs, x_dependent in reversed(prev_eqs): + if x_dependent: + e_rhs = e_rhs.subs(lhs, rhs) + # differentiate result w.r.t x to check if rhs depends on x + x_dependent = e_rhs.diff(x).simplify() != 0 + # include boolean x_dependent flag with equation + prev_eqs.append((e_lhs, e_rhs, x_dependent)) + + # want to substitute equations in reverse order prev_eqs.reverse() - # substitute each prev equation in reverse order: latest first - for eq in prev_eqs: - expr = expr.subs(eq[0], eq[1]) + # substitute each x-dependent prev equation in reverse order + for lhs, rhs, x_dependent in prev_eqs: + if x_dependent: + expr = expr.subs(lhs, rhs) # differentiate w.r.t. x diff = expr.diff(x).simplify() - # if expression is equal to one of the supplied vars, replace with this var - for v in sympy_vars: - if (diff - sympy_vars[v]).simplify() == 0: - diff = sympy_vars[v] - # or if equal to rhs of one of supplied equations, replace with lhs - for i_eq, eq in enumerate(prev_eqs): - # each supplied eq also needs recursive substitution of preceeding statements - # here, before comparison with diff expression - expr = eq[1] - for sub_eq in prev_eqs[i_eq:]: - expr = expr.subs(sub_eq[0], sub_eq[1]) - if (diff - expr).simplify() == 0: - diff = eq[0] + # try to simplify expression in terms of existing variables + # ignore any exceptions here, since we already have a valid solution + # so if this further simplification step fails the error is not fatal + try: + # if expression is equal to one of the supplied vars, replace with this var + # can do a simple string comparison here since a var cannot be further simplified + diff_as_string = sp.ccode(diff) + for v in sympy_vars: + if diff_as_string == sp.ccode(sympy_vars[v]): + diff = sympy_vars[v] + + # or if equal to rhs of one of the supplied equations, replace with lhs + # we need to substitute the constant prev expressions on both sides before comparing + for i_eq, prev_eq in enumerate(prev_eqs): + # each supplied eq, as well as our solution diff, need + # recursive substitution of any preceeding non-x-dependent statements + # before comparison + eq_rhs_sub = prev_eq[1] + diff_sub = diff + for lhs, rhs, x_dependent in prev_eqs[i_eq:]: + if not x_dependent: + eq_rhs_sub = eq_rhs_sub.subs(lhs, rhs) + diff_sub = diff_sub.subs(lhs, rhs) + if (diff_sub - eq_rhs_sub).simplify() == 0: + diff = prev_eq[0] + except Exception: + pass # return result as C code in NEURON format - return sp.ccode(diff) + return sp.ccode(diff.evalf()) def forwards_euler2c(diff_string, dt_var, vars): diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 307b431ead..88fc14ad5f 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -50,6 +50,19 @@ static bool conductance_statement_possible(ast::BreakpointBlock* node) { std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( ast::BreakpointBlock* node) { std::vector<std::string> statements; + + // instead of passing all variables in the symbol table find out variables + // that are used in the current block and then pass to sympy + // name could be parameter or unit so check if it exist in symbol table + auto names_in_block = AstLookupVisitor().lookup(node, AstNodeType::NAME); + string_set used_names_in_block; + for (auto& name: names_in_block) { + auto varname = name->get_node_name(); + if (all_vars.find(varname) != all_vars.end()) { + used_names_in_block.insert(varname); + } + } + // iterate over binary expression lhs's from breakpoint for (const auto& lhs_str: ordered_binary_exprs_lhs) { // look for a current name that matches lhs of expr (current write name) @@ -62,7 +75,7 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( ordered_binary_exprs.begin() + binary_expr_index[lhs_str] + 1); // differentiate dI/dV - auto locals = py::dict("expressions"_a = expressions, "vars"_a = vars); + auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block); py::exec(R"( from nmodl.ode import differentiate2c exception_message = "" @@ -91,11 +104,11 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( } else { std::string g_var = dIdV; // if conductance g_var is not an existing variable, need to generate - // a new variable name, declare it, and asign it - if (vars.find(g_var) == vars.end()) { + // a new variable name, declare it, and assign it + if (all_vars.find(g_var) == all_vars.end()) { // generate new variable name std::map<std::string, int> var_map; - for (auto const& v: vars) { + for (auto const& v: all_vars) { var_map[v] = 0; } g_var = get_new_name("g", i_name_str, var_map); @@ -122,7 +135,7 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( void SympyConductanceVisitor::visit_binary_expression(ast::BinaryExpression* node) { // only want binary expressions from breakpoint block - if (!breakpoint_block) { + if (!under_breakpoint_block) { return; } // only want binary expressions of form x = ... @@ -196,13 +209,13 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) // add any breakpoint local variables to vars if (auto* symtab = node->get_statement_block()->get_symbol_table()) { for (const auto& localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { - vars.insert(localvar->get_name()); + all_vars.insert(localvar->get_name()); } } // visit BREAKPOINT block statements - breakpoint_block = true; + under_breakpoint_block = true; node->visit_children(this); - breakpoint_block = false; + under_breakpoint_block = false; // lookup USEION and NONSPECIFIC statements from NEURON block lookup_useion_statements(); @@ -229,7 +242,7 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) } void SympyConductanceVisitor::visit_program(ast::Program* node) { - vars = get_global_vars(node); + all_vars = get_global_vars(node); AstLookupVisitor ast_lookup_visitor; use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); nonspecific_nodes = ast_lookup_visitor.lookup(node, AstNodeType::NONSPECIFIC); diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index f0ab37532a..21a225f59b 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -51,10 +51,10 @@ class SympyConductanceVisitor: public AstVisitor { private: /// true while visiting breakpoint block - bool breakpoint_block = false; + bool under_breakpoint_block = false; // set of all variables for SymPy - string_set vars; + string_set all_vars; // set of currents to ignore string_set i_ignore; @@ -84,7 +84,7 @@ class SympyConductanceVisitor: public AstVisitor { void lookup_nonspecific_statements(); static std::string to_nmodl_for_sympy(ast::AST* node) { - return to_nmodl(node, {ast::AstNodeType::UNIT}); + return to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } public: diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index fb0ac90f8d..47abe34f02 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -83,7 +83,7 @@ class SympySolverVisitor: public AstVisitor { std::vector<std::string> ode_system; static std::string to_nmodl_for_sympy(ast::AST* node) { - return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT}); + return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } public: diff --git a/test/nmodl/transpiler/ode/test_ode.py b/test/nmodl/transpiler/ode/test_ode.py index a0e73bbbad..cd90a51600 100644 --- a/test/nmodl/transpiler/ode/test_ode.py +++ b/test/nmodl/transpiler/ode/test_ode.py @@ -27,32 +27,30 @@ def test_differentiation(): # simple examples, no prev_expressions assert differentiate2c("0", "x", "") == "0" - assert differentiate2c("x", "x", "") == "1" + assert differentiate2c("x", "x", "") == "1.0" assert differentiate2c("a", "x", "a") == "0" assert differentiate2c("a*x", "x", "a") == "a" assert differentiate2c("a*x", "a", "x") == "x" assert differentiate2c("a*x", "y", {"x", "y"}) == "0" - assert differentiate2c("a*x + b*x*x", "x", {"a", "b"}) == "a + 2*b*x" + assert differentiate2c("a*x + b*x*x", "x", {"a", "b"}) == "a + 2.0*b*x" assert differentiate2c("a*cos(x+b)", "x", {"a", "b"}) == "-a*sin(b + x)" assert ( differentiate2c("a*cos(x+b) + c*x*x", "x", {"a", "b", "c"}) - == "-a*sin(b + x) + 2*c*x" + == "-a*sin(b + x) + 2.0*c*x" ) # single prev_expression to substitute assert differentiate2c("a*x + b", "x", {"a", "b", "c", "d"}, ["c = sqrt(d)"]) == "a" - assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x"]) == "a + 2" + assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x"]) == "a + 2.0" # multiple prev_eqs to substitute # (these statements should be in the same order as in the mod file) - assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2"]) == "0" - assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2"]) == "0" + assert differentiate2c("a*x + b", "x", {"a", "b", "c"}, ["b = 2*x", "a = 2*x*x*c"]) == "6.0*c*pow(x, 2) + 2.0" + assert differentiate2c("a*x + b", "x", {"a", "b", "c"}, ["b = 2*x^2", "a = c*cos(x)"]) == "-c*x*sin(x) + c*cos(x) + 4.0*x" # multiple prev_eqs to recursively substitute # note prev_eqs always substituted in reverse order - assert differentiate2c("a*x + b", "x", {"a", "b"}, ["a=3", "b = 2*a*x"]) == "9" - # if we can return result in terms of supplied var, do so - # even in this case where the supplied var a is equal to 3: + assert differentiate2c("a*x + b", "x", {"a", "b", "c"}, ["a=c*x", "b = 2*a*x"]) == "6.0*c*x" assert ( differentiate2c( "a*x + b*c", "x", {"a", "b", "c"}, ["a=3", "b = 2*a*x", "c = a/x"] diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index ae8686525c..50b8d22da1 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2744,12 +2744,16 @@ std::string run_sympy_conductance_visitor(const std::string& text) { auto ast = driver.ast(); // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(ast.get()); + SymtabVisitor(false).visit_program(ast.get()); + + // run constant folding, inlining & local renaming first + ConstantFolderVisitor().visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); + LocalVarRenameVisitor().visit_program(ast.get()); + SymtabVisitor(true).visit_program(ast.get()); // run SympyConductance on AST - SympyConductanceVisitor v_sympy; - v_sympy.visit_program(ast.get()); + SympyConductanceVisitor().visit_program(ast.get()); // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; @@ -2798,7 +2802,7 @@ void run_sympy_conductance_passes(ast::Program* node) { } SCENARIO("SympyConductance visitor", "[sympy]") { - // Test mod files below all based on: + // First set of test mod files below all based on: // nmodldb/models/db/bluebrain/CortexSimplified/mod/Ca.mod GIVEN("breakpoint block containing VERBATIM statement") { std::string nmodl_text = R"( @@ -3531,6 +3535,112 @@ SCENARIO("SympyConductance visitor", "[sympy]") { REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); } } + // based on neurodamus/bbp/lib/modlib/GluSynapse.mod + GIVEN("1 nonspecific current, no CONDUCTANCE hints, many eqs & a function involved") { + std::string nmodl_text = R"( + NEURON { + GLOBAL tau_r_AMPA, E_AMPA + RANGE tau_d_AMPA, gmax_AMPA + RANGE g_AMPA + GLOBAL tau_r_NMDA, tau_d_NMDA, E_NMDA + RANGE g_NMDA + RANGE Use, Dep, Fac, Nrrp, u + RANGE tsyn, unoccupied, occupied + RANGE ica_NMDA + RANGE volume_CR + GLOBAL ljp_VDCC, vhm_VDCC, km_VDCC, mtau_VDCC, vhh_VDCC, kh_VDCC, htau_VDCC + RANGE gca_bar_VDCC, ica_VDCC + GLOBAL gamma_ca_CR, tau_ca_CR, min_ca_CR, cao_CR + GLOBAL tau_GB, gamma_d_GB, gamma_p_GB, rho_star_GB, tau_Use_GB, tau_effca_GB + RANGE theta_d_GB, theta_p_GB + RANGE rho0_GB + RANGE enable_GB, depress_GB, potentiate_GB + RANGE Use_d_GB, Use_p_GB + GLOBAL p_gen_RW, p_elim0_RW, p_elim1_RW + RANGE enable_RW, synstate_RW + GLOBAL mg, scale_mg, slope_mg + RANGE vsyn, NMDA_ratio, synapseID, selected_for_report, verbose + NONSPECIFIC_CURRENT i + } + UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) + (nS) = (nanosiemens) + (pS) = (picosiemens) + (umho) = (micromho) + (um) = (micrometers) + (mM) = (milli/liter) + (uM) = (micro/liter) + FARADAY = (faraday) (coulomb) + PI = (pi) (1) + R = (k-mole) (joule/degC) + } + ASSIGNED { + g_AMPA (uS) + g_NMDA (uS) + ica_NMDA (nA) + ica_VDCC (nA) + depress_GB (1) + potentiate_GB (1) + v (mV) + vsyn (mV) + i (nA) + } + FUNCTION nernst(ci(mM), co(mM), z) (mV) { + nernst = (1000) * R * (celsius + 273.15) / (z*FARADAY) * log(co/ci) + } + BREAKPOINT { + LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC + g_AMPA = (1e-3)*gmax_AMPA*(B_AMPA-A_AMPA) + i_AMPA = g_AMPA*(v-E_AMPA) + gmax_NMDA = gmax_AMPA*NMDA_ratio + mggate = 1 / (1 + exp(slope_mg * -(v)) * (mg / scale_mg)) + g_NMDA = (1e-3)*gmax_NMDA*mggate*(B_NMDA-A_NMDA) + i_NMDA = g_NMDA*(v-E_NMDA) + Pf_NMDA = (4*cao_CR) / (4*cao_CR + (1/1.38) * 120 (mM)) * 0.6 + ica_NMDA = Pf_NMDA*g_NMDA*(v-40.0) + gca_bar_abs_VDCC = gca_bar_VDCC * 4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^(2/3) + gca_VDCC = (1e-3) * gca_bar_abs_VDCC * m_VDCC * m_VDCC * h_VDCC + Eca_syn = nernst(cai_CR, cao_CR, 2) + ica_VDCC = gca_VDCC*(v-Eca_syn) + vsyn = v + i = i_AMPA + i_NMDA + ica_VDCC + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC, nernst_in_0, g__0 + CONDUCTANCE g__0 + g__0 = (0.001*gmax_NMDA*mg*scale_mg*slope_mg*(A_NMDA-B_NMDA)*(E_NMDA-v)*exp(slope_mg*v)-0.001*gmax_NMDA*scale_mg*(A_NMDA-B_NMDA)*(mg+scale_mg*exp(slope_mg*v))*exp(slope_mg*v)+(g_AMPA+gca_VDCC)*pow(mg+scale_mg*exp(slope_mg*v), 2))/pow(mg+scale_mg*exp(slope_mg*v), 2) + g_AMPA = 0.001*gmax_AMPA*(B_AMPA-A_AMPA) + i_AMPA = g_AMPA*(v-E_AMPA) + gmax_NMDA = gmax_AMPA*NMDA_ratio + mggate = 1/(1+exp(slope_mg*-v)*(mg/scale_mg)) + g_NMDA = 0.001*gmax_NMDA*mggate*(B_NMDA-A_NMDA) + i_NMDA = g_NMDA*(v-E_NMDA) + Pf_NMDA = (4*cao_CR)/(4*cao_CR+0.724638*120(mM))*0.6 + ica_NMDA = Pf_NMDA*g_NMDA*(v-40) + gca_bar_abs_VDCC = gca_bar_VDCC*4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^0.666667 + gca_VDCC = 0.001*gca_bar_abs_VDCC*m_VDCC*m_VDCC*h_VDCC + { + LOCAL ci_in_0, co_in_0, z_in_0 + ci_in_0 = cai_CR + co_in_0 = cao_CR + z_in_0 = 2 + nernst_in_0 = 1000*R*(celsius+273.15)/(z_in_0*FARADAY)*log(co_in_0/ci_in_0) + } + Eca_syn = nernst_in_0 + ica_VDCC = gca_VDCC*(v-Eca_syn) + vsyn = v + i = i_AMPA+i_NMDA+ica_VDCC + } + )"; + THEN("Add 1 CONDUCTANCE hint using new var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } } From 1ff9971e2e7f0569b41542300a512a73db3ab64c Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 20 Mar 2019 21:28:03 +0100 Subject: [PATCH 172/871] Bug fix : segfault when solve method is not specified (BlueBrain/nmodl#82) - solve method in solve block is optional, avoid dereferencing nullptr - copied modifications from branch add_linear_solve_block_support NMODL Repo SHA: BlueBrain/nmodl@dc6f6bb7594985a9e91fc725d9677df6423ac039 --- src/nmodl/visitors/sympy_solver_visitor.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 591b20c9f8..85e3779bd1 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -252,7 +252,12 @@ void SympySolverVisitor::visit_program(ast::Program* node) { auto solve_block_nodes = ast_lookup_visitor.lookup(node, ast::AstNodeType::SOLVE_BLOCK); for (const auto& block: solve_block_nodes) { if (auto block_ptr = std::dynamic_pointer_cast<ast::SolveBlock>(block)) { - std::string solve_method = block_ptr->get_method()->get_value()->eval(); + std::string solve_method; + if (block_ptr->get_method()) { + // Note: solve method name is an optional especially LINEAR and NONLINEAR + // blocks do not have solve method specified + solve_method = block_ptr->get_method()->get_value()->eval(); + } std::string block_name = block_ptr->get_block_name()->get_value()->eval(); logger->debug("SympySolverVisitor :: Found SOLVE statement: using {} for {}", solve_method, block_name); From 5d059c08eab6df4100e77f756c146c9e28eafbed Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 20 Mar 2019 22:32:38 +0100 Subject: [PATCH 173/871] Bug fix with derivimplicit and nested blocks (BlueBrain/nmodl#86) - instead of last statementblock, keep track of block where ODEs are found - Todo : support for ODEs from different blocks, see BlueBrain/nmodl#85 - Todo : test added in this PR is not correct because of linear ODE Resolves BlueBrain/nmodl#84 NMODL Repo SHA: BlueBrain/nmodl@69860e1d5abf344f121b4baaa81c0cc469808028 --- src/nmodl/visitors/sympy_solver_visitor.cpp | 23 +++++++---- src/nmodl/visitors/sympy_solver_visitor.hpp | 7 +++- test/nmodl/transpiler/visitor/visitor.cpp | 46 +++++++++++++++++++++ 3 files changed, 67 insertions(+), 9 deletions(-) diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 85e3779bd1..23cb784493 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -43,8 +43,10 @@ SympySolverVisitor::construct_eigen_newton_solver_block( } void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { + auto prev_statement_block = current_statement_block; current_statement_block = node; node->visit_children(this); + current_statement_block = prev_statement_block; } void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement* node) { @@ -66,6 +68,14 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { return; } + /// all ode statements (typically) appear in the same statement block + /// so for now don't track track different blocks + if (block_with_odes != nullptr && block_with_odes != current_statement_block) { + logger->error( + "SympySolverVisitor :: differential equations are appearing in different blocks"); + } + block_with_odes = current_statement_block; + prime_variables.push_back(lhs->get_node_name()); diffeq_statements.insert(current_diffeq_statement); @@ -198,21 +208,21 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { for (const auto& new_local_var: new_local_vars) { logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", new_local_var); - add_local_variable(current_statement_block, new_local_var); + add_local_variable(block_with_odes, new_local_var); } } if (solve_method == codegen::naming::SPARSE_METHOD) { - remove_statements_from_block(current_statement_block, diffeq_statements); + remove_statements_from_block(block_with_odes, diffeq_statements); // get a copy of existing statements in block - auto statements = current_statement_block->get_statements(); + auto statements = block_with_odes->get_statements(); // add new statements for (const auto& sol: solutions) { logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); statements.push_back(create_statement(sol)); } // replace old set of statements in AST with new one - current_statement_block->set_statements(std::move(statements)); + block_with_odes->set_statements(std::move(statements)); } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { /// Construct X from the state variables by using the original /// ODE statements in the block. Also create statements to update @@ -227,7 +237,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { } /// remove original ODE statements from the block where they initially appear - remove_statements_from_block(current_statement_block, diffeq_statements); + remove_statements_from_block(block_with_odes, diffeq_statements); /// create newton solution block and add that as statement back in the block /// statements in solutions : put F, J into new functor to be created for eigen @@ -238,8 +248,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { logger->error("SympySolverVisitor :: -> X conflicts with NMODL variable"); } - current_statement_block->addStatement( - std::make_shared<ast::ExpressionStatement>(solver_block)); + block_with_odes->addStatement(std::make_shared<ast::ExpressionStatement>(solver_block)); } } } diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 47abe34f02..c8e87930bb 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -65,10 +65,13 @@ class SympySolverVisitor: public AstVisitor { std::set<ast::Node*> diffeq_statements; /// current expression statement being visited (to track ODEs) - ast::ExpressionStatement* current_diffeq_statement; + ast::ExpressionStatement* current_diffeq_statement = nullptr; /// current statement block being visited - ast::StatementBlock* current_statement_block; + ast::StatementBlock* current_statement_block = nullptr; + + /// block where ode statements are appeared + ast::StatementBlock* block_with_odes = nullptr; /// method specified in solve block std::string solve_method; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 50b8d22da1..6e1b436ed4 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2546,6 +2546,52 @@ SCENARIO("SympySolver visitor", "[sympy]") { REQUIRE(AST_string == ast_to_string(ast.get())); } } + GIVEN("Derivative block with cnexp solver method and conditional block") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + + DERIVATIVE states { + IF (mInf == 1) { + mInf = mInf+1 + } + m' = (mInf-m)/mTau + } + )"; + /// TODO : Note that the test is not correct because the ode is linear so the python + /// sympy solver call is returning the sparse solution, not the derivimplicit one + std::string expected_text = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + + DERIVATIVE states { + LOCAL tmp_m_old + IF (mInf == 1) { + mInf = mInf+1 + } + { + X[0] = m + }{ + tmp_m_old = m + m = (dt*mInf+mTau*tmp_m_old)/(dt+mTau) + }{ + m = X[0] + } + } + )"; + NmodlDriver driver; + driver.parse_string(nmodl_text); + auto ast = driver.ast(); + SymtabVisitor().visit_program(ast.get()); + SympySolverVisitor().visit_program(ast.get()); + std::string AST_string = ast_to_string(ast.get()); + + THEN("SympySolver correctly inserts ode to block") { + REQUIRE(AST_string == reindent_text(expected_text)); + } + } GIVEN("Derivative block of coupled & linear ODES, solver method sparse, no CSE") { std::string nmodl_text = R"( BREAKPOINT { From c157bb1528675f78411faacfcfd043c737e9371d Mon Sep 17 00:00:00 2001 From: Jorge Blanco Alonso <41900536+jorblancoa@users.noreply.github.com> Date: Mon, 25 Mar 2019 10:50:38 +0100 Subject: [PATCH 174/871] Add sphinx documentation (BlueBrain/nmodl#81) - Python binding documentation examples - Doctest snippets - Added jupyter notebooks - Include submodules (ast, visitor, symtab) - Created example contents page - Added C++ API documentation - Added version number to the Documentation - Fixes BlueBrain/nmodl#23 NMODL Repo SHA: BlueBrain/nmodl@52f61d97beedaf740392ff0eeab9e2ebbfca1f5a --- README.md | 103 ++++++++- docs/nmodl/transpiler/Makefile | 24 ++ docs/nmodl/transpiler/conf.py | 217 ++++++++++++++++++ docs/nmodl/transpiler/contents/visitors.rst | 128 +++++++++++ docs/nmodl/transpiler/index.rst | 41 ++++ docs/nmodl/transpiler/modules.rst | 7 + docs/nmodl/transpiler/nmodl.rst | 57 +++++ .../notebooks/kinetic-schemes.ipynb | 49 ++-- .../nmodl-python-sympy-examples.ipynb | 8 +- .../notebooks/nmodl-python-tutorial.ipynb | 28 +-- .../nmodl/transpiler}/notebooks/tree.js | 0 docs/nmodl/transpiler/readme.rst | 5 + .../nmodl/transpiler/test_status.txt | 0 nmodl/__init__.py | 3 + nmodl/ast.py | 1 + nmodl/dsl.py | 1 - nmodl/ode.py | 15 +- nmodl/symtab.py | 1 + nmodl/visitor.py | 1 + setup.py | 167 ++++++++++---- src/nmodl/language/templates/pyast.cpp | 46 +++- src/nmodl/language/templates/pysymtab.cpp | 46 +++- src/nmodl/language/templates/pyvisitor.cpp | 48 +++- src/nmodl/lexer/c11_lexer.hpp | 2 +- src/nmodl/lexer/diffeq_lexer.hpp | 2 +- src/nmodl/parser/c11_driver.hpp | 2 +- src/nmodl/parser/diffeq_driver.hpp | 2 +- src/nmodl/parser/nmodl_driver.hpp | 2 +- src/nmodl/pybind/pynmodl.cpp | 100 ++++++-- 29 files changed, 967 insertions(+), 139 deletions(-) create mode 100644 docs/nmodl/transpiler/Makefile create mode 100644 docs/nmodl/transpiler/conf.py create mode 100644 docs/nmodl/transpiler/contents/visitors.rst create mode 100644 docs/nmodl/transpiler/index.rst create mode 100644 docs/nmodl/transpiler/modules.rst create mode 100644 docs/nmodl/transpiler/nmodl.rst rename doc/notebooks/kinetic_schemes.ipynb => docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb (92%) rename {doc => docs/nmodl/transpiler}/notebooks/nmodl-python-sympy-examples.ipynb (99%) rename {doc => docs/nmodl/transpiler}/notebooks/nmodl-python-tutorial.ipynb (99%) rename {doc => docs/nmodl/transpiler}/notebooks/tree.js (100%) create mode 100644 docs/nmodl/transpiler/readme.rst rename doc/test_status.md => docs/nmodl/transpiler/test_status.txt (100%) create mode 100644 nmodl/ast.py create mode 100644 nmodl/symtab.py create mode 100644 nmodl/visitor.py diff --git a/README.md b/README.md index 74da15a3a8..2a0831aed1 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,16 @@ And if --target option was used, set PYTHONPATH as: export PYTHONPATH=$HOME/nmodl:$PYTHONPATH ``` +##### Using setuptools + +Finally, we can also build the project using setuptools: + +``` +python3 setup.py install --prefix=<pathInstallationDir> +``` + +This will install nmodl on your system, run all the tests and generate the documentation. + #### Testing Installed Module If you install NMODL using CMake, you can run tests from build directory as: @@ -117,7 +127,7 @@ We can use nmodl module from python as: ``` $ python3 >>> import nmodl.dsl as nmodl ->>> driver = nmodl.Driver() +>>> driver = nmodl.NmodlDriver() >>> driver.parse_string("NEURON { SUFFIX hh }") True >>> modast = driver.ast() @@ -129,15 +139,100 @@ NMODL is now setup correctly! #### Using Python API -The user documentation for NMODL is incomplete and not available on GitHub yet. The best way to understand the API and usage is using Jupyter notebooks provided in docs directory : +The best way to understand the API and usage is using Jupyter notebooks provided in docs directory : ``` -cd nmodl/doc/notebooks +cd nmodl/docs/notebooks jupyter notebook ``` -You can look at [nmodl-python-tutorial.ipynb](doc/notebooks/nmodl-python-tutorial.ipynb) notebook for python interface tutorial. There is also [nmodl-python-sympy-examples.ipynb](doc/notebooks/nmodl-python-sympy-examples.ipynb)showing how [SymPy](https://www.sympy.org/en/index.html) is used in NMODL. +You can look at [nmodl-python-tutorial.ipynb](docs/notebooks/nmodl-python-tutorial.ipynb) notebook for python interface tutorial. There is also [nmodl-python-sympy-examples.ipynb](docs/notebooks/nmodl-python-sympy-examples.ipynb)showing how [SymPy](https://www.sympy.org/en/index.html) is used in NMODL. + +##### Documentation + +If you installed nmodl using setuptools as shown in the previous section, you will have all the documentation generated on build/sphinx. + +Otherwise, if you have already installed nmodl and setup the correct PYTHONPATH, you can build the documentation locally from the docs/ folder. + +``` +cd docs +make html +``` + +Or using setuptools + +``` +python3 setup.py install_doc +``` + +If you dont want to change the PYTHONPATH, make sure that you have the shared library on the nmodl/ folder. It is copied there automatically by compiling and running the tests: + +``` +python3 setup.py test +``` + +Now you will have a new folder docs/_build/html or build/sphinx/html, depending if you run the first or the second command, +where you can open the index.html with your favourite browser. + +Another option is to create an httpServer on this folder and open the browser on localhost: + +``` +cd docs/_build/html +python2 -m SimpleHTTPServer 8080 +http://localhost:8080 +``` + +To check the coverage of your documentation you can run: + +``` +cd nmodl/docs +make coverage +``` + +The results will be stored on the docs/_build/coverage + +To run the code snippets on the documentation you can do: + +``` +cd nmodl/docs +make doctest +``` + +Or with setuptools + +``` +python3 setup.py doctest +``` + +The output will be stored on the docs/_build/doctest or build/sphinx/doctest depending on the command used. + +To add new modules you could call sphinx-apidoc. + +``` +sphinx-apidoc -o docs/ nmodl/ +``` + +This will generate "stubs" rst files for the new modules. +The file will look like something like this: + +``` +.. automodule:: mymodule + :members: + :no-undoc-members: + :show-inheritance: +``` + +If you want to generate documentation for all the symbols of the module, you could add :imported-members: + +``` +.. automodule:: mymodule + :members: + :imported-members: + :no-undoc-members: + :show-inheritance: +``` +After that you can run the "make html" command to generate documentation for the new modules. #### Using NMODL For Code Generation diff --git a/docs/nmodl/transpiler/Makefile b/docs/nmodl/transpiler/Makefile new file mode 100644 index 0000000000..dd895fa70b --- /dev/null +++ b/docs/nmodl/transpiler/Makefile @@ -0,0 +1,24 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = nmodl +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +clean: + $(RM) -f doxyoutput/ cpp_api/ + @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py new file mode 100644 index 0000000000..edd7d25e51 --- /dev/null +++ b/docs/nmodl/transpiler/conf.py @@ -0,0 +1,217 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# nmodl documentation build configuration file, created by +# sphinx-quickstart on Fri Mar 8 15:07:46 2019. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys +import textwrap + +sys.path.insert(0, os.path.abspath("..")) + +import nmodl # isort:skip + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +# +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "breathe", + "exhale", + "m2r", + "nbsphinx", + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.doctest", + "sphinx.ext.githubpages", + "sphinx.ext.imgmath", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", +] + +doctest_global_setup = textwrap.dedent( + """\ + import nmodl + + driver = nmodl.NmodlDriver() +""" +) + +doctest_global_cleanup = textwrap.dedent( + """\ + + del driver +""" +) + +# Setup the breathe extension +breathe_projects = {"NMODL C++ library": "./doxyoutput/xml"} +breathe_default_project = "NMODL C++ library" + +# Setup the exhale extension +exhale_args = { + # These arguments are required + "containmentFolder": "./cpp_api", + "rootFileName": "library_root.rst", + "rootFileTitle": "C++ API", + "doxygenStripFromPath": "..", + # Suggested optional arguments + "createTreeView": True, + # TIP: if using the sphinx-bootstrap-theme, you need + "treeViewIsBootstrap": True, + "exhaleExecutesDoxygen": True, + # TODO: change to ../include? + "exhaleDoxygenStdin": """ + INPUT = ../src + FILE_PATTERNS = *.c *.h *.cpp *.hpp *.ipp + """, +} + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = [".rst", ".md"] + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "nmodl" +copyright = "2019, BlueBrain HPC team" +author = "BlueBrain HPC team" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = nmodl.__version__ +# The full version, including alpha/beta/rc tags. +release = nmodl.__version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This patterns also effect to html_static_path and html_extra_path +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = "sphinx" + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# This is required for the alabaster theme +# refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars +html_sidebars = { + "**": [ + "relations.html", # needs 'show_related': True theme option to display + "searchbox.html", + ] +} + + +# -- Options for HTMLHelp output ------------------------------------------ + +# Output file base name for HTML help builder. +htmlhelp_basename = "nmodldoc" + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, "nmodl.tex", "nmodl Documentation", "BlueBrain HPC team", "manual") +] + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, "nmodl", "nmodl Documentation", [author], 1)] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + master_doc, + "nmodl", + "nmodl Documentation", + author, + "nmodl", + "One line description of project.", + "Miscellaneous", + ) +] diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst new file mode 100644 index 0000000000..dbb21d56e9 --- /dev/null +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -0,0 +1,128 @@ +Visitors +#### + +One of the strengths of NMODL python interface is access to inbuilt Visitors. +One can perform different queries and analysis on AST using different visitors. Let us start with examples of inbuilt visitors. + +Parsing Model and constructing AST +=========== + +Once the NMODL is setup properly we should be able to import nmodl module and create the channel: + + >>> import nmodl.dsl as nmodl + >>> channel = """ + ... NEURON { + ... SUFFIX CaDynamics + ... USEION ca READ ica WRITE cai + ... RANGE decay, gamma, minCai, depth + ... } + ... UNITS { + ... (mV) = (millivolt) + ... (mA) = (milliamp) + ... FARADAY = (faraday) (coulombs) + ... (molar) = (1/liter) + ... (mM) = (millimolar) + ... (um) = (micron) + ... } + ... PARAMETER { + ... gamma = 0.05 : percent of free calcium (not buffered) + ... decay = 80 (ms) : rate of removal of calcium + ... depth = 0.1 (um) : depth of shell + ... minCai = 1e-4 (mM) + ... } + ... ASSIGNED {ica (mA/cm2)} + ... INITIAL { + ... cai = minCai + ... } + ... STATE { + ... cai (mM) + ... } + ... BREAKPOINT { SOLVE states METHOD cnexp } + ... DERIVATIVE states { + ... cai' = -(10000)*(ica*gamma/(2*FARADAY*depth)) - (cai - minCai)/decay + ... } + ... FUNCTION foo() { + ... LOCAL temp + ... foo = 1.0 + gamma + ... } + ... """ + +Now we can parse any valid NMODL constructs using parsing interface. +First, we have to create nmodl parser object using :class:`nmodl.NmodlDriver` and then we can use :func:`nmodl.NmodlDriver.parse_string` method: + + >>> driver = nmodl.NmodlDriver() + >>> driver.parse_string(channel) + True + +The :func:`nmodl.NmodlDriver.parse_string` method will throw an exception with parsing error if the input is invalid. +Otherwise it returns True and internally creates :class:`nmodl.ast.AST` object. We can access the AST using :func:`nmodl.NmodlDriver.ast` method: + + >>> modast = driver.ast() + +If we simply print AST object, we can see JSON representation: + + >>> print ('%.100s' % modast) # only first 100 characters + {"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]}, + +Querying AST objects with Visitors +=========== + + +Lookup Visitor +----------- + +As name suggest, lookup visitor allows to search different NMODL constructs in the AST. The `visitor` module provides access to inbuilt visitors. In order to use this visitor, we create an object of :class:`nmodl.visitor.AstLookupVisitor`: + + >>> from nmodl.dsl import visitor + >>> from nmodl.dsl import ast + >>> lookup_visitor = visitor.AstLookupVisitor() + +Assuming we have created :class:`nmodl.ast` object (as shown here), we can search for any NMODL construct in the AST using :class:`nmodl.visitor.AstLookupVisitor`. For example, to find out `STATE` block in the mod file, we can simply do: + + >>> states = lookup_visitor.lookup(modast, ast.AstNodeType.STATE_BLOCK) + >>> for state in states: + ... print (nmodl.to_nmodl(state)) + STATE { + cai (mM) + } + + +Symbol Table Visitor +---------- + +Symbol table visitor is used to find out all variables and their usage in mod file. To use this, just create a visitor object as: + + >>> from nmodl.dsl import symtab + >>> symv = symtab.SymtabVisitor() + +Once the visitor object is created, we can run visitor on AST object to populate symbol table. Symbol table provides print method that can be used to print whole symbol table: + + >>> symv.visit_program(modast) + >>> table = modast.get_symbol_table() + >>> table_s = str(table) + +Now we can query for variables in the symbol table based on name of variable: + + >>> cai = table.lookup('cai') + >>> print (cai) + cai [Properties : prime_name dependent_def write_ion state_var] + + +Custom AST Visitor +---------- + +If predefined visitors are limited, we can implement new visitor using :class:`nmodl.visitor.AstVisitor` interface. Let us say we want to implement a visitor that prints every floating point numbers in MOD file. Here is how it can be done: + + >>> from nmodl.dsl import ast, visitor + >>> class DoubleVisitor(visitor.AstVisitor): + ... def visit_double(self, node): + ... print (node.eval()) # or, can use nmodl.to_nmodl(node) + >>> d_visitor = DoubleVisitor() + >>> modast.accept(d_visitor) + 0.05 + 0.1 + 0.0001 + 10000.0 + 2.0 + 1.0 + diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst new file mode 100644 index 0000000000..2db6af6b5d --- /dev/null +++ b/docs/nmodl/transpiler/index.rst @@ -0,0 +1,41 @@ +.. nmodl documentation master file, created by + sphinx-quickstart on Fri Mar 8 15:07:46 2019. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to nmodl's documentation! +================================= + +.. toctree:: + :maxdepth: 2 + :caption: Introduction: + + readme + +.. toctree:: + :maxdepth: 2 + :caption: Contents: + + contents/visitors + +.. toctree:: + :maxdepth: 3 + :caption: Jupyter Notebooks: + + notebooks/nmodl-python-sympy-examples + notebooks/nmodl-python-tutorial + notebooks/kinetic-schemes + +.. toctree:: + :maxdepth: 2 + :caption: Reference: + + modules.rst + cpp_api/library_root + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/nmodl/transpiler/modules.rst b/docs/nmodl/transpiler/modules.rst new file mode 100644 index 0000000000..d464425955 --- /dev/null +++ b/docs/nmodl/transpiler/modules.rst @@ -0,0 +1,7 @@ +Python package +===== + +.. toctree:: + :maxdepth: 4 + + nmodl diff --git a/docs/nmodl/transpiler/nmodl.rst b/docs/nmodl/transpiler/nmodl.rst new file mode 100644 index 0000000000..69781d6e0e --- /dev/null +++ b/docs/nmodl/transpiler/nmodl.rst @@ -0,0 +1,57 @@ +Module contents +================ + +.. automodule:: nmodl + :members: + :imported-members: + :undoc-members: + :show-inheritance: + + +Submodules +============= + +nmodl.ast module +---------------- + +.. automodule:: nmodl.ast + :members: + :imported-members: + :no-undoc-members: + :show-inheritance: + +nmodl.dsl module +---------------- + +.. automodule:: nmodl.dsl + :members: + :imported-members: + :undoc-members: + :show-inheritance: + +nmodl.ode module +---------------- + +.. automodule:: nmodl.ode + :members: + :imported-members: + :undoc-members: + :show-inheritance: + +nmodl.symtab module +------------------- + +.. automodule:: nmodl.symtab + :members: + :imported-members: + :undoc-members: + :show-inheritance: + +nmodl.visitor module +-------------------- + +.. automodule:: nmodl.visitor + :members: + :imported-members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/doc/notebooks/kinetic_schemes.ipynb b/docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb similarity index 92% rename from doc/notebooks/kinetic_schemes.ipynb rename to docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb index 212bf25874..db2dbd6090 100644 --- a/doc/notebooks/kinetic_schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb @@ -23,6 +23,7 @@ "where\n", "- $k_i$ is the rate coefficient\n", "- $\\nu_{ij}^L$, $\\nu_{ij}^R$ are stochiometric coefficients - must be positive integers (including zero)\n", + "\n", "***\n", "#### Law of Mass Action\n", "\n", @@ -50,16 +51,17 @@ "- the integer preceeding a species (with or without a space) is the corresponding stochiometric coefficient $\\nu_{ij}$ (by default 1 if not present)\n", "- `kf` is the forwards reaction rate $k^{(f)}_j$\n", "- `kb` is the backwards reaction rate $k^{(b)}_j$, i.e. the reaction rate for the same reaction with the LHS and RHS exchanged\n", + "\n", "***\n", "We can convert these statements to a system of ODEs:\n", "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} (r^{(f)}_i - r^{(b)}_i)$$\n", "\n", "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", "$$\n", - "\\begin{align}\n", - "r^{(f)}_i &= k^{(f)}_i \\prod_j Y_j^{\\nu_{ij}^{L}} \\\\\n", + "\\begin{aligned}\n", + "r^{(f)}_i &= k^{(f)}_i \\prod_j Y_j^{\\nu_{ij}^{L}}\\\\\n", "r^{(b)}_i &= k^{(b)}_i \\prod_j Y_j^{\\nu_{ij}^{R}}\n", - "\\end{align}\n", + "\\end{aligned}\n", "$$\n", "***" ] @@ -79,30 +81,33 @@ "- $j = \\{0, 1\\}$\n", "\n", "i.e. \n", + "\n", "- the state vector $Y$ has 2 elements\n", "$$\n", "Y = \\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "h \\\\\n", "m \n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "$$\n", + "\n", "- the stochiometric coefficients are 1x2 matrices\n", "$$\n", "\\nu^L = \\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "1 && 0\n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "$$\n", "$$\n", "\\nu^R = \\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "0 && 1\n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "$$\n", + "\n", "- the rate vectors contain 1 element:\n", "$$\n", "k^{(f)} = a\n", @@ -118,22 +123,24 @@ "$$\n", "r^{(b)} = b m\n", "$$\n", + "\n", "and finally we find the ODEs in matrix form:\n", "$$\n", "\\frac{dY}{dt} =\n", "\\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "-1 && 1\n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "(ah - bm)\n", "$$\n", + "\n", "which in terms of the state variables can be written:\n", "$$\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "\\frac{dh}{dt} &= bm - ah \\\\\n", "\\frac{dm}{dt} &= ah - bm\n", - "\\end{align}\n", + "\\end{aligned}\n", "$$\n", "***" ] @@ -154,6 +161,7 @@ "$$\n", "\\frac{dh}{dt} += a\n", "$$\n", + "\n", "***\n", "Finally there is a statement of the form\n", "```\n", @@ -180,6 +188,7 @@ "CONSERVE h + m = 0\n", "```\n", "This can then be used to eliminate one of these variables from the set of ODEs, for example by removing the dmdt equation, and instead calculating m in terms of the algebraic relation above.\n", + "\n", "***" ] }, @@ -193,7 +202,7 @@ "\n", "So we should parse all non-kinetic statements in the mod file following a kinetic statement (until we get to a new kinetic statement), and replace any fflux or bflux variables with corresponding expression.\n", "\n", - "***\n" + "***" ] }, { @@ -204,16 +213,10 @@ "\n", "The usual issues about inlining function calls, e.g. when the rate is a function call.\n", "\n", - "In principle also allowed for it to be a function of state variables, in which case vital to inline this information for the ODEs (although we could also just say that this is not supported, as not necessary for the user to write the mod file in this way, state variable dependence can always be written explicitly)\n", + "In principle also allowed for it to be a function of state variables, in which case vital to inline this information for the ODEs (although we could also just say that this is not supported, as not necessary for the user to write the mod file in this way, state variable dependence can always be written explicitly).\n", + "\n", "***" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { @@ -232,7 +235,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/doc/notebooks/nmodl-python-sympy-examples.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-sympy-examples.ipynb similarity index 99% rename from doc/notebooks/nmodl-python-sympy-examples.ipynb rename to docs/nmodl/transpiler/notebooks/nmodl-python-sympy-examples.ipynb index 5210f4e1e7..14980bbbbc 100644 --- a/doc/notebooks/nmodl-python-sympy-examples.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-sympy-examples.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### NMODL SymPy Visitor Examples\n", + "# NMODL SymPy Visitor Examples\n", "\n", "Some examples of the use of SymPy within NMODL to\n", "- solve differential equations to generate solutions for the CNEXP solver (`SympySolverVisitor`)\n", @@ -138,7 +138,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### ODE Solver example" + "## ODE Solver example" ] }, { @@ -316,7 +316,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### CONDUCTANCE example" + "## CONDUCTANCE example" ] }, { @@ -454,7 +454,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/doc/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb similarity index 99% rename from doc/notebooks/nmodl-python-tutorial.ipynb rename to docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index faa7a87ab2..362dd2a282 100644 --- a/doc/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -4,7 +4,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Visualization Library Setup" + "# NMODL Python Interface Tutorial\n", + "\n", + "## Visualization Library Setup" ] }, { @@ -350,15 +352,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n", - "## NMODL Python Interface Tutorial" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Introduction\n", + "## Introduction\n", "\n", "NMODL is a code generation framework for NEURON Modeling Language. It is primarily designed to support optimised code generation backends for morphologically detailed neuron simulators. It provides high level Python interface that can be used for model introspection as well as performing various analysis on underlying model.\n", "\n", @@ -377,7 +371,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Installation \n", + "## Installation \n", "<a id='installation'></a> NMODL can be installed as CMake project or using python setuptools. See README.md for detailed installation instructions. For example :\n", "\n", "```bash\n", @@ -393,7 +387,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Parsing Model And Constructing AST\n", + "## Parsing Model And Constructing AST\n", "\n", "Once the NMODL is setup properly we should be able to import `nmodl` module :" ] @@ -568,7 +562,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Querying AST object with Visitors\n", + "## Querying AST object with Visitors\n", "\n", "One of the strength of NMODL python interface is access to inbuilt [Visitors](https://en.wikipedia.org/wiki/Visitor_pattern). One can perform different queries and analysis on AST using different visitors. Lets start with the examples of inbuilt visitors." ] @@ -577,7 +571,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Lookup visitor\n", + "### Lookup visitor\n", "\n", "As name suggest, lookup visitor allows to search different NMODL constructs in the AST. The `visitor` module provides access to inbuilt visitors. In order to use this visitor, we create an object of AstLookupVisitor :" ] @@ -730,7 +724,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Symbol Table Visitor\n", + "### Symbol Table Visitor\n", "\n", "Symbol table visitor is used to find out all variables and their usage in mod file. To use this, first create a visitor object as: " ] @@ -895,7 +889,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Custom AST visitor\n", + "### Custom AST visitor\n", "\n", "If predefined visitors are limited, we can implement new visitor using AstVisitor interface. Lets say we want to implement a visitor that will print every floating point number in MOD file. Here is how we can do this: " ] @@ -1004,7 +998,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/doc/notebooks/tree.js b/docs/nmodl/transpiler/notebooks/tree.js similarity index 100% rename from doc/notebooks/tree.js rename to docs/nmodl/transpiler/notebooks/tree.js diff --git a/docs/nmodl/transpiler/readme.rst b/docs/nmodl/transpiler/readme.rst new file mode 100644 index 0000000000..312057d434 --- /dev/null +++ b/docs/nmodl/transpiler/readme.rst @@ -0,0 +1,5 @@ +Introduction +============ + +.. mdinclude:: ../README.md + diff --git a/doc/test_status.md b/docs/nmodl/transpiler/test_status.txt similarity index 100% rename from doc/test_status.md rename to docs/nmodl/transpiler/test_status.txt diff --git a/nmodl/__init__.py b/nmodl/__init__.py index af3c8702af..705805af7c 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -1 +1,4 @@ +from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa from ._nmodl import __version__ + +__all__ = ["NmodlDriver", "to_json", "to_nmodl"] diff --git a/nmodl/ast.py b/nmodl/ast.py new file mode 100644 index 0000000000..2002bd825f --- /dev/null +++ b/nmodl/ast.py @@ -0,0 +1 @@ +from ._nmodl.ast import * # noqa diff --git a/nmodl/dsl.py b/nmodl/dsl.py index dec6ecf29f..e99f2af1d0 100644 --- a/nmodl/dsl.py +++ b/nmodl/dsl.py @@ -1,2 +1 @@ - from ._nmodl import * diff --git a/nmodl/ode.py b/nmodl/ode.py index 2b59709996..e31708d674 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -138,8 +138,9 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): Derivative should be of the form "x' = f(x)", and vars should contain the set of all the variables referenced by f(x), for example: - -integrate2c("x' = a*x", "a") - -integrate2c("x' = a + b*x - sin(3.2)", {"a","b"}) + + -``integrate2c ("x' = a*x", "a")`` + -``integrate2c ("x' = a + b*x - sin(3.2)", {"a","b"})`` Args: diff_string: Derivative to be integrated e.g. "x' = a*x" @@ -236,8 +237,9 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): the prev_expressions, then it is simplified to this expression. Some simple examples of use: - -differentiate2c("a*x", "x", {"a"}) == "a" - -differentiate2c("cos(y) + b*y**2", "y", {"a","b"}) == "Dy = 2*b*y - sin(y)" + + - ``nmodl.ode.differentiate2c ("a*x", "x", {"a"}) == "a"`` + - ``differentiate2c ("cos(y) + b*y**2", "y", {"a","b"}) == "Dy = 2*b*y - sin(y)"`` Args: expression: expression to be differentiated e.g. "a*x + b" @@ -331,8 +333,9 @@ def forwards_euler2c(diff_string, dt_var, vars): Derivative should be of the form "x' = f(x)", and vars should contain the set of all the variables referenced by f(x), for example: - -forwards_euler2c("x' = a*x", "a") - -forwards_euler2c("x' = a + b*x - sin(3.2)", {"a","b"}) + + - ``forwards_euler2c("x' = a*x", "a")`` + - ``forwards_euler2c("x' = a + b*x - sin(3.2)", {"a","b"})`` Args: diff_string: Derivative to be integrated e.g. "x' = a*x" diff --git a/nmodl/symtab.py b/nmodl/symtab.py new file mode 100644 index 0000000000..8b38fa3ffa --- /dev/null +++ b/nmodl/symtab.py @@ -0,0 +1 @@ +from ._nmodl.symtab import * # noqa diff --git a/nmodl/visitor.py b/nmodl/visitor.py new file mode 100644 index 0000000000..9150b9570f --- /dev/null +++ b/nmodl/visitor.py @@ -0,0 +1 @@ +from ._nmodl.visitor import * # noqa diff --git a/setup.py b/setup.py index e92ba885a6..fd95fb51d1 100644 --- a/setup.py +++ b/setup.py @@ -5,23 +5,44 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** +import inspect import os import os.path as osp -import re -import shutil - -import sys import platform +import re import subprocess +import sys +import sysconfig +from distutils.version import LooseVersion -from setuptools import setup, Extension +from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -from setuptools.command.install_lib import install_lib -from distutils.version import LooseVersion +from setuptools.command.install import install +from setuptools.command.test import test + + +class lazy_dict(dict): + """When the value associated to a key is a function, then returns + the function call instead of the function. + """ + + def __getitem__(self, item): + value = dict.__getitem__(self, item) + if inspect.isfunction(value): + return value() + return value + + +def get_sphinx_command(): + """Lazy load of Sphinx distutils command class + """ + from sphinx.setup_command import BuildDoc + + return BuildDoc class CMakeExtension(Extension): - def __init__(self, name, sourcedir=''): + def __init__(self, name, sourcedir=""): Extension.__init__(self, name, sources=[]) self.sourcedir = osp.abspath(sourcedir) @@ -29,13 +50,17 @@ def __init__(self, name, sourcedir=''): class CMakeBuild(build_ext): def run(self): try: - out = subprocess.check_output(['cmake', '--version']) + out = subprocess.check_output(["cmake", "--version"]) except OSError: - raise RuntimeError("CMake must be installed to build the following extensions: " + - ", ".join(e.name for e in self.extensions)) + raise RuntimeError( + "CMake must be installed to build the following extensions: " + + ", ".join(e.name for e in self.extensions) + ) - cmake_version = LooseVersion(re.search(r'version\s*([\d.]+)', out.decode()).group(1)) - if cmake_version < '3.1.0': + cmake_version = LooseVersion( + re.search(r"version\s*([\d.]+)", out.decode()).group(1) + ) + if cmake_version < "3.1.0": raise RuntimeError("CMake >= 3.1.0 is required") for ext in self.extensions: @@ -44,50 +69,98 @@ def run(self): def build_extension(self, ext): extdir = osp.abspath(osp.dirname(self.get_ext_fullpath(ext.name))) extdir = osp.join(extdir, ext.name) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable] + cmake_args = [ + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + extdir, + "-DPYTHON_EXECUTABLE=" + sys.executable, + ] - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg] + cfg = "Debug" if self.debug else "Release" + build_args = ["--config", cfg] if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - build_args += ['--', '/m'] + cmake_args += [ + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir) + ] + if sys.maxsize > 2 ** 32: + cmake_args += ["-A", "x64"] + build_args += ["--", "/m"] else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - build_args += ['--', '-j2'] + cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg] + build_args += ["--", "-j{}".format(max(1, os.cpu_count() - 1))] env = os.environ.copy() - env['CXXFLAGS'] = '{} -DVERSION_INFO=\\"{}\\"'.format(env.get('CXXFLAGS', ''), - self.distribution.get_version()) + env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format( + env.get("CXXFLAGS", ""), self.distribution.get_version() + ) if not osp.exists(self.build_temp): os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env) - subprocess.check_call(['cmake', '--build', '.', '--target', '_nmodl'] + build_args, cwd=self.build_temp) + subprocess.check_call( + ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env + ) + subprocess.check_call( + ["cmake", "--build", "."] + build_args, cwd=self.build_temp + ) + + +class NMODLInstall(install): + def run(self): + if not self.skip_build: + self.run_command("test") # to install the shared library + self.run_command("install_doc") + super().run() + + +class NMODLTest(test): + """Custom disutils command that acts like as a replacement + for the "test" command. + + It first executes the standard "test" command, then runs the + C++ tests and finally runs the "doctest" to also validate + code snippets in the sphinx documentation. + """ + + def distutils_dir_name(self, dname): + """Returns the name of a distutils build directory""" + dir_name = "{dirname}.{platform}-{version[0]}.{version[1]}" + return dir_name.format( + dirname=dname, platform=sysconfig.get_platform(), version=sys.version_info + ) + + def run(self): + super().run() + subprocess.check_call( + [ + "cmake", + "--build", + os.path.join("build", self.distutils_dir_name("temp")), + "--target", + "test", + ] + ) + subprocess.check_call([sys.executable, __file__, "doctest"]) + +install_requirements = ["jinja2>=2.10", "PyYAML>=3.13", "sympy>=1.2", "pytest>=4.0.0"] setup( - name='NMODL', - version='0.1', - author='Blue Brain Project', - author_email='bbp-ou-hpc@groupes.epfl.ch', - description='NEURON Modelling Language Source-to-Source Compiler Framework', - long_description='', - packages=['nmodl'], - ext_modules=[CMakeExtension('nmodl')], - cmdclass=dict(build_ext=CMakeBuild), + name="NMODL", + version="0.1", + author="Blue Brain Project", + author_email="bbp-ou-hpc@groupes.epfl.ch", + description="NEURON Modelling Language Source-to-Source Compiler Framework", + long_description="", + packages=["nmodl"], + ext_modules=[CMakeExtension("nmodl")], + cmdclass=lazy_dict( + build_ext=CMakeBuild, + install=NMODLInstall, + test=NMODLTest, + install_doc=get_sphinx_command, + doctest=get_sphinx_command, + ), zip_safe=False, - setup_requires=['jinja2>=2.10', - 'PyYAML>=3.13', - 'sympy>=1.2', - 'pytest>=4.0.0' - ], - install_requires=['jinja2>=2.10', - 'PyYAML>=3.13', - 'sympy>=1.2', - 'pytest>=4.0.0' - ], - tests_require=['pytest>=4.0.0'] + setup_requires=["nbsphinx", "m2r", "exhale", "sphinx-rtd-theme", "sphinx<2"] + + install_requirements, + install_requires=install_requirements, + tests_require=["pytest>=4.0.0"], ) diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pyast.cpp index e9bde27cb4..2217ff6bbd 100644 --- a/src/nmodl/language/templates/pyast.cpp +++ b/src/nmodl/language/templates/pyast.cpp @@ -26,11 +26,47 @@ namespace py = pybind11; using namespace nmodl::ast; using nmodl::JSONVisitor; +using pybind11::literals::operator""_a; + +namespace docstring { + +static const char *binaryop_enum = R"( + BinaryOp enum +)"; + +static const char *astnodetype_enum = R"( + AstNodeType enum +)"; + +static const char *ast_class = R"( + AST class + + Attributes: + basetype (BaseType): Stores the AST base type (int, bool, none, object). Further type + information will come from the symbol table. +)"; + +static const char *visitchildren_method = R"( + Visit children + + Args: + v (Visitor): the visitor +)"; + +static const char *accept_method = R"( + Accept + + Args: + v (Visitor): the visitor +)"; + +} + void init_ast_module(py::module& m) { py::module m_ast = m.def_submodule("ast"); - py::enum_<BinaryOp>(m_ast, "BinaryOp") + py::enum_<BinaryOp>(m_ast, "BinaryOp", docstring::binaryop_enum) .value("BOP_ADDITION", BinaryOp::BOP_ADDITION) .value("BOP_SUBTRACTION", BinaryOp::BOP_SUBTRACTION) .value("BOP_MULTIPLICATION", BinaryOp::BOP_MULTIPLICATION) @@ -47,7 +83,7 @@ void init_ast_module(py::module& m) { .value("BOP_EXACT_EQUAL", BinaryOp::BOP_EXACT_EQUAL) .export_values(); - py::enum_<AstNodeType>(m_ast, "AstNodeType") + py::enum_<AstNodeType>(m_ast, "AstNodeType", docstring::astnodetype_enum) {% for node in nodes %} .value("{{ node.class_name|snake_case|upper }}", AstNodeType::{{ node.class_name|snake_case|upper }}) {% endfor %} @@ -55,10 +91,10 @@ void init_ast_module(py::module& m) { - py::class_<AST, PyAST, std::shared_ptr<AST>> ast_(m_ast, "AST"); + py::class_<AST, PyAST, std::shared_ptr<AST>> ast_(m_ast, "AST", docstring::ast_class); ast_.def(py::init<>()) - .def("visit_children", &AST::visit_children) - .def("accept", &AST::accept) + .def("visit_children", &AST::visit_children, "v"_a, docstring::visitchildren_method) + .def("accept", &AST::accept, "v"_a, docstring::accept_method) .def("get_shared_ptr", &AST::get_shared_ptr) .def("get_node_type", &AST::get_node_type) .def("get_node_type_name", &AST::get_node_type_name) diff --git a/src/nmodl/language/templates/pysymtab.cpp b/src/nmodl/language/templates/pysymtab.cpp index 30cc8747cb..705a873c18 100644 --- a/src/nmodl/language/templates/pysymtab.cpp +++ b/src/nmodl/language/templates/pysymtab.cpp @@ -27,9 +27,40 @@ {%- endmacro -%} namespace py = pybind11; +using pybind11::literals::operator""_a; + using namespace nmodl; using namespace symtab; +namespace docstring { + + static const char *declarationtype_enum = R"( + DeclarationType enum +)"; + +static const char *symbol_class = R"( + Symbol class +)"; + +static const char *symboltable_class = R"( + SymbolTable class + + Attributes: + name (str): name of the block + table (Table): table holding all symbols in the current block + node (AST): pointer to ast node for which current block symbol created + global (bool): true if current symbol table is global. blocks like neuron, + parameter defines global variables and hence they go into single global symbol table + parent (SymbolTable): pointer to the symbol table of parent block in the mod file + children (dict of (str, SymbolTable)): symbol table for each enclosing block in the current nmodl block construct. +)"; + +static const char *symtabvisitor_class = R"( + SymtabVisitor class +)"; + + +} class PySymtabVisitor : private VisitorOStreamResources, public SymtabVisitor { public: @@ -45,7 +76,7 @@ class PySymtabVisitor : private VisitorOStreamResources, public SymtabVisitor { void init_symtab_module(py::module& m) { py::module m_symtab = m.def_submodule("symtab"); - py::enum_<syminfo::DeclarationType>(m_symtab, "DeclarationType") + py::enum_<syminfo::DeclarationType>(m_symtab, "DeclarationType", docstring::declarationtype_enum) .value("function", syminfo::DeclarationType::function) .value("variable", syminfo::DeclarationType::variable) .export_values(); @@ -129,8 +160,8 @@ void init_symtab_module(py::module& m) { .def("__str__", &syminfo::to_string<syminfo::NmodlType>); - py::class_<Symbol, std::shared_ptr<Symbol>> symbol(m_symtab, "Symbol"); - symbol.def(py::init<std::string, ast::AST*>()); + py::class_<Symbol, std::shared_ptr<Symbol>> symbol(m_symtab, "Symbol", docstring::symbol_class); + symbol.def(py::init<std::string, ast::AST*>(), "name"_a, "node"_a); symbol.def("get_token", &Symbol::get_token) .def("is_variable", &Symbol::is_variable) .def("is_external_symbol_only", &Symbol::is_external_symbol_only) @@ -146,8 +177,8 @@ void init_symtab_module(py::module& m) { .def("has_all_status", &Symbol::has_all_status) .def("__str__", &Symbol::to_string); - py::class_<SymbolTable> symbol_table(m_symtab, "SymbolTable"); - symbol_table.def(py::init<std::string, ast::AST*, bool>()); + py::class_<SymbolTable> symbol_table(m_symtab, "SymbolTable", docstring::symboltable_class); + symbol_table.def(py::init<std::string, ast::AST*, bool>(), "name"_a, "node"_a, "global"_a); symbol_table.def("name", &SymbolTable::name) .def("type", &SymbolTable::type) .def("title", &SymbolTable::title) @@ -165,13 +196,12 @@ void init_symtab_module(py::module& m) { .def("insert_table", &SymbolTable::insert_table) .def("__str__", &SymbolTable::to_string); - py::class_<SymtabVisitor, AstVisitor, PySymtabVisitor> symtab_visitor(m_symtab, - "SymtabVisitor"); + py::class_<SymtabVisitor, AstVisitor, PySymtabVisitor> symtab_visitor(m_symtab, "SymtabVisitor", docstring::symtabvisitor_class); symtab_visitor.def(py::init<std::string, bool>(), py::arg("filename"), py::arg("update") = false); symtab_visitor.def(py::init<py::object, bool>(), py::arg("ostream"), py::arg("update") = false); symtab_visitor.def(py::init<bool>(), py::arg("update") = false) - .def("add_model_symbol_with_property", &PySymtabVisitor::add_model_symbol_with_property) + .def("add_model_symbol_with_property", &PySymtabVisitor::add_model_symbol_with_property, "node"_a, "property"_a) .def("setup_symbol", &PySymtabVisitor::setup_symbol) .def("setup_symbol_table", &PySymtabVisitor::setup_symbol_table) .def("setup_symbol_table_for_program_block", diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index d9f5a67530..3a125daec6 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -18,6 +18,42 @@ #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" +using pybind11::literals::operator""_a; + +namespace docstring { + +static const char *visitor_class = R"( + Visitor class +)"; + +static const char *astvisitor_class = R"( + AstVisitor class +)"; + +static const char *astlookupvisitor_class = R"( + AstLookupVisitor class + + Attributes: + types (list of :class:`AstNodeType`): node types to search in the AST + nodes (list of AST): matching nodes found in the AST +)"; + +static const char *nmodlprintvisitor_class = R"( + + NmodlPrintVisitor class +)"; + +static const char *sympysolvervisitor_class = R"( + + SympySolverVisitor class +)"; + +static const char *sympyconductancevisitor_class = R"( + + SympyConductanceVisitor class +)"; +} + #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" {% macro var(node) -%} @@ -65,21 +101,21 @@ class PyNmodlPrintVisitor : private VisitorOStreamResources, public NmodlPrintVi void init_visitor_module(py::module& m) { py::module m_visitor = m.def_submodule("visitor"); - py::class_<Visitor, PyVisitor> visitor(m_visitor, "Visitor"); + py::class_<Visitor, PyVisitor> visitor(m_visitor, "Visitor", docstring::visitor_class); visitor.def(py::init<>()) {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &Visitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} - py::class_<AstVisitor, Visitor, PyAstVisitor> ast_visitor(m_visitor, "AstVisitor"); + py::class_<AstVisitor, Visitor, PyAstVisitor> ast_visitor(m_visitor, "AstVisitor", docstring::astvisitor_class); ast_visitor.def(py::init<>()) {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &AstVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} - py::class_<PyNmodlPrintVisitor, Visitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor"); + py::class_<PyNmodlPrintVisitor, Visitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor", docstring::nmodlprintvisitor_class); nmodl_visitor.def(py::init<std::string>()); nmodl_visitor.def(py::init<py::object>()); nmodl_visitor.def(py::init<>()) @@ -88,7 +124,7 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - py::class_<AstLookupVisitor, Visitor> lookup_visitor(m_visitor, "AstLookupVisitor"); + py::class_<AstLookupVisitor, Visitor> lookup_visitor(m_visitor, "AstLookupVisitor", docstring::astlookupvisitor_class); lookup_visitor.def(py::init<>()) .def(py::init<ast::AstNodeType>()) .def("get_nodes", &AstLookupVisitor::get_nodes) @@ -100,11 +136,11 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor"); + py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor", docstring::sympysolvervisitor_class); sympy_solver_visitor.def(py::init<bool>(), py::arg("use_pade_approx")=false) .def("visit_program", &SympySolverVisitor::visit_program); - py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor"); + py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor", docstring::sympyconductancevisitor_class); sympy_conductance_visitor.def(py::init<>()) .def("visit_program", &SympyConductanceVisitor::visit_program); } diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 5b916bf366..3622df1ba7 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -28,7 +28,7 @@ namespace nmodl { namespace parser { /** - * \class Lexer + * \class CLexer * \brief Represent Lexer/Scanner class for C (11) language parsing * * Lexer defined to add some extra function to the scanner class from flex. diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp index d6232b544a..1c88781ab2 100644 --- a/src/nmodl/lexer/diffeq_lexer.hpp +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -28,7 +28,7 @@ namespace nmodl { namespace parser { /** - * \class Lexer + * \class DiffeqLexer * \brief Represent Lexer/Scanner class for differential equation parsing * * Lexer defined to add some extra function to the scanner class from flex. diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index ce16be7f2b..85a3265afb 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -25,7 +25,7 @@ class CParser; class location; /** - * \class Driver + * \class CDriver * \brief Class that binds all pieces together for parsing C verbatim blocks */ class CDriver { diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index bb27bec205..b7e9b5fb2c 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -16,7 +16,7 @@ namespace nmodl { namespace parser { /** - * \class NmodlDriver + * \class DiffeqDriver * \brief Class that binds all pieces together for parsing differential equations * * Driver class bind components required for lexing, parsing and ast diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index cef1ea78ef..ee890c0669 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -18,7 +18,7 @@ namespace nmodl { namespace parser { /** - * \class Driver + * \class NmodlDriver * \brief Class that binds all pieces together for parsing nmodl file * * Driver class bind components required for lexing, parsing and ast diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 527c393446..0c13b5ad85 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -18,7 +18,84 @@ #include "visitors/visitor_utils.hpp" namespace py = pybind11; +using pybind11::literals::operator""_a; +/** \brief docstring of Python symbols */ +namespace docstring { + +static const char* driver = R"( + This is the NmodlDriver class documentation +)"; + +static const char* driver_ast = R"( + Get ast + + Returns: + Instance of :py:class:`Program` +)"; + +static const char* driver_parse_string = R"( + Parse C provided as a string (testing) + + Args: + input (str): C code as string + Returns: + bool: true if success, false otherwise + + >>> driver.parse_string("DEFINE NSTEP 6") + True +)"; + +static const char* driver_parse_file = R"( + Parse C file + + Args: + filename (str): name of the C file + + Returns: + bool: true if success, false otherwise +)"; + +static const char* driver_parse_stream = R"( + Parse C file provided as istream + + Args: + in (file): ifstream object + + Returns: + bool: true if success, false otherwise +)"; + +static const char* to_nmodl = R"( + Given AST node, return the JSON string representation + + Args: + node (AST): AST node + excludeTypes (set of AstNodeType): Excluded node types + + Returns: + str: JSON string representation +)"; + +static const char* to_json = R"( + Given AST node, return the NMODL string representation + + Args: + node (AST): AST node + compact (bool): Compact node + expand (bool): Expand node + + Returns: + str: NMODL string representation + + >>> driver.parse_string("NEURON{}") + True + >>> ast = driver.ast() + >>> nmodl.to_json(ast, True) + '{"Program":[{"NeuronBlock":[{"StatementBlock":[]}]}]}' +)"; + +} // namespace docstring class PyDriver: public nmodl::parser::NmodlDriver { public: @@ -45,20 +122,17 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; m_nmodl.attr("__version__") = nmodl::version::NMODL_VERSION; - py::class_<PyDriver> nmodl_driver(m_nmodl, "NmodlDriver"); - - // todo : what has changed ? fix this! - // nmodl_driver.def(py::init<bool, bool>()); + py::class_<PyDriver> nmodl_driver(m_nmodl, "NmodlDriver", docstring::driver); nmodl_driver.def(py::init<>()) - .def("parse_string", &PyDriver::parse_string) - .def("parse_file", &PyDriver::parse_file) - .def("parse_stream", &PyDriver::parse_stream) - .def("ast", &PyDriver::ast); - - m_nmodl.def("to_nmodl", nmodl::to_nmodl, py::arg("node"), - py::arg("exclude_types") = std::set<nmodl::ast::AstNodeType>()); - m_nmodl.def("to_json", nmodl::to_json, py::arg("node"), py::arg("compact") = false, - py::arg("expand") = false); + .def("parse_string", &PyDriver::parse_string, "input"_a, docstring::driver_parse_string) + .def("parse_file", &PyDriver::parse_file, "filename"_a, docstring::driver_parse_file) + .def("parse_stream", &PyDriver::parse_stream, "in"_a, docstring::driver_parse_stream) + .def("ast", &PyDriver::ast, docstring::driver_ast); + + m_nmodl.def("to_nmodl", nmodl::to_nmodl, "node"_a, + "exclude_types"_a = std::set<nmodl::ast::AstNodeType>(), docstring::to_nmodl); + m_nmodl.def("to_json", nmodl::to_json, "node"_a, "compact"_a = false, "expand"_a = false, + docstring::to_json); init_visitor_module(m_nmodl); init_ast_module(m_nmodl); From 2074927aefd04be93660cd91d2f1636774536e12 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@gmail.com> Date: Mon, 25 Mar 2019 16:16:40 +0100 Subject: [PATCH 175/871] Support for multiple solver blocks in code generation (BlueBrain/nmodl#92) - multiple SOLVE constructs are now correctly handled in code generation - add new node type DerivimplicitCallbackExpression to simplify codegen - DerivimplicitCallbackExpression represent callback to derivimplicit method - visit_derivimplicit_callback_expression takes care of printing callback to newton solver - add SolveBlockVisitor after cnexp and sympy solver that insert correct node type in the ast - add tests NMODL Repo SHA: BlueBrain/nmodl@87fa2d9a42f86de58e383a622714480ab123f62d --- src/nmodl/ast/ast_common.hpp | 12 ++ src/nmodl/codegen/CMakeLists.txt | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 83 +++++++------ src/nmodl/codegen/codegen_c_visitor.hpp | 4 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 49 ++------ src/nmodl/codegen/codegen_helper_visitor.hpp | 4 +- src/nmodl/codegen/codegen_info.cpp | 5 +- src/nmodl/codegen/codegen_info.hpp | 28 ++--- src/nmodl/language/codegen.yaml | 24 +++- .../language/templates/symtab_visitor.hpp | 4 +- src/nmodl/nmodl/main.cpp | 7 ++ src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/cnexp_solve_visitor.cpp | 45 ++++--- src/nmodl/visitors/cnexp_solve_visitor.hpp | 22 ++-- src/nmodl/visitors/solve_block_visitor.cpp | 89 ++++++++++++++ src/nmodl/visitors/solve_block_visitor.hpp | 45 +++++++ src/nmodl/visitors/symtab_visitor_helper.hpp | 4 +- test/nmodl/transpiler/visitor/visitor.cpp | 111 ++++++++++++++++-- 18 files changed, 392 insertions(+), 148 deletions(-) create mode 100644 src/nmodl/visitors/solve_block_visitor.cpp create mode 100644 src/nmodl/visitors/solve_block_visitor.hpp diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 7828490561..2a5f24cd60 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -310,6 +310,18 @@ struct AST: public std::enable_shared_from_this<AST> { return false; } + virtual bool is_nrn_state_block() { + return false; + } + + virtual bool is_solution_expression() { + return false; + } + + virtual bool is_derivimplicit_callback() { + return false; + } + virtual bool is_procedure_block() { return false; } diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 5d7b40f7ad..971c681f1e 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -23,7 +23,7 @@ set(CODEGEN_SOURCE_FILES # ============================================================================= add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) -add_dependencies(codegen lexer util) +add_dependencies(codegen lexer util visitor) # ============================================================================= # Install include files diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index b4c80a7eab..e697011975 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -361,7 +361,7 @@ bool CodegenCVisitor::nrn_state_required() { if (info.artificial_cell) { return false; } - return info.solve_node != nullptr || info.currents.empty(); + return info.nrn_state_block != nullptr || info.currents.empty(); } @@ -480,7 +480,8 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { if (node->is_expression_statement()) { auto expression = dynamic_cast<ExpressionStatement*>(node)->get_expression(); if (expression->is_statement_block() - || expression->is_eigen_newton_solver_block()) { + || expression->is_eigen_newton_solver_block() + || expression->is_solution_expression()) { return false; } } @@ -3125,7 +3126,6 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_initial_block(info.initial_node); print_channel_iteration_block_end(); - print_shadow_reduction_statements(); print_channel_iteration_tiling_block_end(); printer->end_block(1); @@ -3617,22 +3617,19 @@ void CodegenCVisitor::print_net_receive() { * actual variable names? [resolved now?] * slist needs to added as local variable */ -void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { - auto node = info.solve_node; - codegen = true; - +void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { auto ext_args = external_method_arguments(); auto ext_params = external_method_parameters(); auto suffix = info.mod_suffix; auto list_num = info.derivimplicit_list_num; - auto solve_block_name = info.solve_block_name; + auto block_name = block->get_node_name(); auto primes_size = info.primes_size; auto stride = (layout == LayoutType::aos) ? "" : "*pnodecount+id"; printer->add_newline(2); // clang-format off - printer->start_block("int {}_{}({})"_format(node->get_node_name(), suffix, ext_params)); + printer->start_block("int {}_{}({})"_format(block_name, suffix, ext_params)); auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); auto slist1 = "int* slist{} = {};"_format(list_num, get_variable_name("slist{}"_format(list_num))); auto slist2 = "int* slist{} = {};"_format(list_num+1, get_variable_name("slist{}"_format(list_num+1))); @@ -3648,7 +3645,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_line(" savstate{}[i{}] = data[slist{}[i]{}];"_format(list_num, stride, list_num, stride)); printer->add_line("}"); - auto argument = "{}, slist{}, _derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, solve_block_name, suffix, list_num + 1, ext_args); + auto argument = "{}, slist{}, _derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, block_name, suffix, list_num + 1, ext_args); printer->add_line("int reset = nrn_newton_thread(static_cast<NewtonSpace*>(*newtonspace{}(thread)), {});"_format(list_num, argument)); printer->add_line("return reset;"); printer->end_block(3); @@ -3658,10 +3655,10 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { * comment marker in the generated cpp file for kinderiv.py to * process it and generate correct _kinderiv.h */ - printer->add_line("/* _derivimplicit_ {} _{} */"_format(node->get_node_name(), info.mod_suffix)); + printer->add_line("/* _derivimplicit_ {} _{} */"_format(block_name, info.mod_suffix)); printer->add_newline(1); - printer->start_block("int _newton_{}_{}({}) "_format(node->get_node_name(), info.mod_suffix, external_method_parameters())); + printer->start_block("int _newton_{}_{}({}) "_format(block_name, info.mod_suffix, external_method_parameters())); printer->add_line(instance); if (ion_variable_struct_required()) { printer->add_line("IonCurVar ionvar = {0};"); @@ -3670,7 +3667,7 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); - print_statement_block(node->get_statement_block().get(), false, false); + print_statement_block(block->get_statement_block().get(), false, false); printer->add_line("int counter = -1;"); printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); @@ -3683,9 +3680,36 @@ void CodegenCVisitor::print_derivative_kernel_for_derivimplicit() { printer->add_line("return 0;"); printer->end_block(); // clang-format on - codegen = false; } +void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback* node) { + if (!codegen) { + return; + } + auto thread_args = external_method_arguments(); + auto num_primes = info.num_primes; + auto suffix = info.mod_suffix; + int num = info.derivimplicit_list_num; + auto slist = get_variable_name("slist{}"_format(num)); + auto dlist = get_variable_name("dlist{}"_format(num)); + auto block_name = node->get_node_to_solve()->get_node_name(); + + auto args = + "{}, {}, {}, _derivimplicit_{}_{}, {}" + ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); + auto statement = "derivimplicit_thread({});"_format(args); + printer->add_line(statement); +} + +void CodegenCVisitor::visit_solution_expression(SolutionExpression* node) { + auto block = node->get_node_to_solve().get(); + if (block->is_statement_block()) { + auto statement_block = dynamic_cast<ast::StatementBlock*>(block); + print_statement_block(statement_block, false, false); + } else { + block->accept(this); + } +} /****************************************************************************************/ /* Print nrn_state routine */ @@ -3698,10 +3722,6 @@ void CodegenCVisitor::print_nrn_state() { } codegen = true; - if (info.derivimplicit_coreneuron_solver()) { - print_derivative_kernel_for_derivimplicit(); - } - printer->add_newline(2); printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); @@ -3720,26 +3740,8 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line(statement); } - auto thread_args = external_method_arguments(); - auto num_primes = info.num_primes; - auto suffix = info.mod_suffix; - auto block_name = info.solve_block_name; - auto num = info.derivimplicit_coreneuron_solver() ? info.derivimplicit_list_num - : info.euler_list_num; - auto slist = get_variable_name("slist{}"_format(num)); - auto dlist = get_variable_name("dlist{}"_format(num)); - - if (info.solve_node != nullptr) { - if (info.derivimplicit_coreneuron_solver()) { - auto args = - "{}, {}, {}, _derivimplicit_{}_{}, {}" - ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); - auto statement = "derivimplicit_thread({});"_format(args); - printer->add_line(statement); - } else { - auto block = info.solve_node->get_statement_block(); - print_statement_block(block.get(), false, false); - } + if (info.nrn_state_block) { + info.nrn_state_block->visit_children(this); } if (info.currents.empty() && info.breakpoint_node != nullptr) { @@ -3972,6 +3974,10 @@ void CodegenCVisitor::print_compute_functions() { for (const auto& function: info.functions) { print_function(function); } + for (const auto& callback: info.derivimplicit_callbacks) { + auto block = callback->get_node_to_solve().get(); + print_derivimplicit_kernel(block); + } print_net_init(); print_net_send_buffering(); print_watch_activate(); @@ -4004,6 +4010,7 @@ void CodegenCVisitor::print_codegen_routines() { print_check_table_thread_function(); print_mechanism_register(); print_namespace_end(); + codegen = false; } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index aa882af9de..8c96298dd9 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -829,7 +829,7 @@ class CodegenCVisitor: public AstVisitor { /// derivative kernel when derivimplicit method is used - void print_derivative_kernel_for_derivimplicit(); + void print_derivimplicit_kernel(ast::Block* block); /// block / loop for statement requiring reduction @@ -998,12 +998,14 @@ class CodegenCVisitor: public AstVisitor { virtual void visit_program(ast::Program* node) override; virtual void visit_statement_block(ast::StatementBlock* node) override; virtual void visit_string(ast::String* node) override; + virtual void visit_solution_expression(ast::SolutionExpression* node) override; virtual void visit_unary_operator(ast::UnaryOperator* node) override; virtual void visit_unit(ast::Unit* node) override; virtual void visit_var_name(ast::VarName* node) override; virtual void visit_verbatim(ast::Verbatim* node) override; virtual void visit_watch_statement(ast::WatchStatement* node) override; virtual void visit_while_statement(ast::WhileStatement* node) override; + virtual void visit_derivimplicit_callback(ast::DerivimplicitCallback* node) override; }; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index e96ba6b1e1..936c80fab9 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -10,6 +10,7 @@ #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" #include <fmt/format.h> @@ -227,10 +228,6 @@ void CodegenHelperVisitor::find_non_range_variables() { info.thread_callback_register = true; } - if (info.euler_used) { - info.euler_list_num = 1; - } - /// next thread id is allocated for top local variables if (info.vectorize && !info.top_local_variables.empty()) { info.top_local_thread_id = info.thread_data_index++; @@ -455,11 +452,15 @@ void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock* node) { void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock* node) { under_derivative_block = true; - info.solve_node = node; node->visit_children(this); under_derivative_block = false; } +void CodegenHelperVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback* node) { + info.derivimplicit_used = true; + info.derivimplicit_callbacks.push_back(node); +} + void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { under_breakpoint_block = true; @@ -469,6 +470,12 @@ void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { } +void CodegenHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock* node) { + info.nrn_state_block = node; + node->visit_children(this); +} + + void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { info.procedures.push_back(node); node->visit_children(this); @@ -516,24 +523,6 @@ void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { } -void CodegenHelperVisitor::visit_solve_block(SolveBlock* node) { - info.num_solve_blocks++; - if (under_breakpoint_block) { - info.solve_block_name = node->get_block_name()->get_node_name(); - if (node->get_method()) { - info.solve_method = node->get_method()->get_node_name(); - if (info.solve_method == naming::DERIVIMPLICIT_METHOD) { - info.derivimplicit_used = true; - } else if (info.solve_method == naming::EULER_METHOD) { - info.euler_used = true; - } else if (info.solve_method == naming::CNEXP_METHOD) { - info.cnexp_used = true; - } - } - } -} - - /** * Visit statement block and find prime symbols appear in derivative block * @@ -610,19 +599,6 @@ void CodegenHelperVisitor::visit_table_statement(ast::TableStatement* node) { } -void CodegenHelperVisitor::find_solve_node() { - if (info.solve_node != nullptr) { - return; - } - auto symbols = psymtab->get_variables_with_properties(NmodlType::to_solve); - if (!symbols.empty()) { - assert(symbols.size() == 1); - info.solve_node = dynamic_cast<Block*>(symbols.at(0)->get_node()); - info.solve_block_name = symbols.at(0)->get_name(); - } -} - - void CodegenHelperVisitor::visit_program(Program* node) { psymtab = node->get_symbol_table(); auto blocks = node->get_blocks(); @@ -636,7 +612,6 @@ void CodegenHelperVisitor::visit_program(Program* node) { find_range_variables(); find_non_range_variables(); find_ion_variables(); - find_solve_node(); find_table_variables(); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 522dd0ab21..110a59db09 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -58,7 +58,6 @@ class CodegenHelperVisitor: public AstVisitor { /// lhs of assignment in derivative block std::shared_ptr<ast::Expression> assign_lhs; - void find_solve_node(); void find_ion_variables(); void find_table_variables(); void find_range_variables(); @@ -79,11 +78,11 @@ class CodegenHelperVisitor: public AstVisitor { void visit_procedure_block(ast::ProcedureBlock* node) override; void visit_function_block(ast::FunctionBlock* node) override; void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; - void visit_solve_block(ast::SolveBlock* node) override; void visit_statement_block(ast::StatementBlock* node) override; void visit_initial_block(ast::InitialBlock* node) override; void visit_breakpoint_block(ast::BreakpointBlock* node) override; void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_derivimplicit_callback(ast::DerivimplicitCallback* node) override; void visit_net_receive_block(ast::NetReceiveBlock* node) override; void visit_bbcore_ptr(ast::BbcorePtr* node) override; void visit_watch(ast::Watch* node) override; @@ -91,6 +90,7 @@ class CodegenHelperVisitor: public AstVisitor { void visit_for_netcon(ast::ForNetcon* node) override; void visit_table_statement(ast::TableStatement* node) override; void visit_program(ast::Program* node) override; + void visit_nrn_state_block(ast::NrnStateBlock* node) override; }; } // namespace codegen diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index c4074cc1b0..8c8985f7d8 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -83,10 +83,7 @@ bool CodegenInfo::function_uses_table(std::string& name) const { */ bool CodegenInfo::derivimplicit_coreneuron_solver() { - if (!derivimplicit_used || solve_node == nullptr || eigen_newton_solver_exist) { - return false; - } - return true; + return !derivimplicit_callbacks.empty(); } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 2a617d9351..69cb1e51f2 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -7,7 +7,6 @@ #pragma once -#include <map> #include <string> #include "ast/ast.hpp" @@ -214,9 +213,6 @@ struct CodegenInfo { /// slist/dlist id for derivimplicit block int derivimplicit_list_num = -1; - /// slist/dlist id for for euler block - int euler_list_num = -1; - /// number of solve blocks in mod file int num_solve_blocks = 0; @@ -230,19 +226,12 @@ struct CodegenInfo { /// typically equal to number of primes int num_equations = 0; - /// block used in solve statement - /// typically derivative block or procedure - ast::Block* solve_node = nullptr; - - /// \todo: name of the solve block - std::string solve_block_name; - - /// solve method used - std::string solve_method; - /// derivative block ast::BreakpointBlock* breakpoint_node = nullptr; + /// nrn_state block + ast::NrnStateBlock* nrn_state_block = nullptr; + /// net receive block for point process ast::NetReceiveBlock* net_receive_node = nullptr; @@ -258,6 +247,9 @@ struct CodegenInfo { /// all procedures defined in the mod file std::vector<ast::ProcedureBlock*> procedures; + /// derivimplicit callbacks need to be emited + std::vector<ast::DerivimplicitCallback*> derivimplicit_callbacks; + /// all functions defined in the mod file std::vector<ast::FunctionBlock*> functions; @@ -329,12 +321,6 @@ struct CodegenInfo { /// if mod file used dervimplicit method bool derivimplicit_used = false; - /// if mod file used euler method - bool euler_used = false; - - /// if mod file used cnexp method - bool cnexp_used = false; - /// all top level global blocks std::vector<ast::Node*> top_blocks; @@ -359,7 +345,7 @@ struct CodegenInfo { /// if either read or write variable bool is_ion_variable(const std::string& name); - /// if a current + /// if given variable is a current bool is_current(const std::string& name); /// if watch statements are used diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index a9702468e1..6f9e68df2a 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -43,10 +43,32 @@ - update_states_block: description: "update back states from X" type: StatementBlock + - NrnStateBlock: + nmodl: "NRN_STATE " + description: "Represents the coreneuron nrn_state callback function" + members: + - solve_statements: + description: "solve blocks to be called or generated" + type: Statement + vector: true - WrappedExpression: description: "Wrap any other expression type" members: - expression: description: "Expression that is being wrapped" type: Expression - - Statement: \ No newline at end of file + - DerivimplicitCallback: + description: "Represent a callback to NEURON's derivimplicit solver" + members: + - node_to_solve: + description: "Block to be solved (typically derivative)" + type: Block + - SolutionExpression: + description: "Represent solution of a block in the AST" + members: + - solve_block: + type: SolveBlock + - node_to_solve: + description: "Block to be solved (callback node or solution node itself)" + type: Expression + - Statement: diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/symtab_visitor.hpp index fb3dcb5bc6..9797d10321 100644 --- a/src/nmodl/language/templates/symtab_visitor.hpp +++ b/src/nmodl/language/templates/symtab_visitor.hpp @@ -7,6 +7,8 @@ #pragma once +#include <set> + #include "visitors/json_visitor.hpp" #include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" @@ -20,7 +22,7 @@ class SymtabVisitor : public AstVisitor { symtab::ModelSymbolTable* modsymtab; std::unique_ptr<JSONPrinter> printer; - std::string block_to_solve; + std::set<std::string> block_to_solve; bool update = false; bool under_state_block = false; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 952013ef23..a126a961da 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -33,6 +33,7 @@ #include "visitors/loop_unroll_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" @@ -325,6 +326,12 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("cnexp")); } + { + SolveBlockVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("solveblock")); + } + if (json_perfstat) { auto file = scratch_dir + "/" + modfile + ".perf.json"; logger->info("Writing performance statistics to {}", file); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index f9ed334c7b..e5f31e8361 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -19,6 +19,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/loop_unroll_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/loop_unroll_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.ipp + ${CMAKE_CURRENT_SOURCE_DIR}/solve_block_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/solve_block_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 0e6742fd1b..a64097c0b9 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -19,41 +19,46 @@ namespace nmodl { void CnexpSolveVisitor::visit_solve_block(ast::SolveBlock* node) { + auto name = node->get_block_name()->get_node_name(); auto method = node->get_method(); - if (method) { - solve_method = method->get_value()->eval(); - } + solve_method = method ? method->get_value()->eval() : ""; + solve_blocks[name] = solve_method; +} + + +void CnexpSolveVisitor::visit_derivative_block(ast::DerivativeBlock* node) { + derivative_block_name = node->get_name()->get_node_name(); + derivative_block = true; + node->visit_children(this); + derivative_block = false; } + void CnexpSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { differential_equation = true; node->visit_children(this); differential_equation = false; } + void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto& lhs = node->lhs; auto& rhs = node->rhs; auto& op = node->op; - /// we have to only solve binary expressions in derivative block - if (!differential_equation) { - return; - } - - /// lhs of the expression should be variable - if (!lhs->is_var_name()) { - logger->warn("LHS of differential equation is not a VariableName"); + /// we have to only solve odes under derivative block where lhs is variable + if (!derivative_block || !differential_equation || !lhs->is_var_name()) { return; } + auto solve_method = solve_blocks[derivative_block_name]; auto name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); if (name->is_prime_name()) { auto equation = nmodl::to_nmodl(node); parser::DiffeqDriver diffeq_driver; - if (solve_method == cnexp_method) { + if (solve_method == codegen::naming::CNEXP_METHOD) { std::string solution; /// check if ode can be solved with cnexp method if (diffeq_driver.cnexp_possible(equation, solution)) { @@ -65,9 +70,9 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { - logger->error("cnexp solver not possible"); + logger->warn("cnexp solver not possible for {}", to_nmodl(node)); } - } else if (solve_method == euler_method) { + } else if (solve_method == codegen::naming::EULER_METHOD) { std::string solution = diffeq_driver.solve(equation, solve_method); auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement); @@ -75,14 +80,16 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { expr_statement->get_expression()); lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); - } else if (solve_method == derivimplicit_method) { + } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { auto varname = "D" + name->get_node_name(); auto variable = new ast::Name(new ast::String(varname)); lhs.reset(variable); - auto symbol = std::make_shared<symtab::Symbol>(varname, ModToken()); - symbol->set_original_name(name->get_node_name()); - symbol->created_from_state(); - program_symtab->insert(symbol); + if (program_symtab->lookup(varname) == nullptr) { + auto symbol = std::make_shared<symtab::Symbol>(varname, ModToken()); + symbol->set_original_name(name->get_node_name()); + symbol->created_from_state(); + program_symtab->insert(symbol); + } } else { logger->error("solver method '{}' not supported", solve_method); } diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/cnexp_solve_visitor.hpp index 42ade98e14..c26baa9c94 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.hpp @@ -27,28 +27,30 @@ namespace nmodl { class CnexpSolveVisitor: public AstVisitor { private: - /// method specified in solve block - std::string solve_method; - /// true while visiting differential equation bool differential_equation = false; - /// name of the cnexp method - const std::string cnexp_method = "cnexp"; + /// global symbol table + symtab::SymbolTable* program_symtab = nullptr; + + /// a map holding solve block names and methods + std::map<std::string, std::string> solve_blocks; - /// name of the derivimplicit method - const std::string derivimplicit_method = "derivimplicit"; + /// method specified in solve block + std::string solve_method; - /// name of the euler method - const std::string euler_method = "euler"; + /// visiting derivative block + bool derivative_block = false; - symtab::SymbolTable* program_symtab = nullptr; + /// the derivative name currently being visited + std::string derivative_block_name; public: CnexpSolveVisitor() = default; void visit_solve_block(ast::SolveBlock* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; + void visit_derivative_block(ast::DerivativeBlock* node) override; void visit_binary_expression(ast::BinaryExpression* node) override; void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp new file mode 100644 index 0000000000..5ea983fcc9 --- /dev/null +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -0,0 +1,89 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "visitors/solve_block_visitor.hpp" +#include "codegen/codegen_naming.hpp" +#include "utils/logger.hpp" +#include "visitors/lookup_visitor.hpp" + +namespace nmodl { + +void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { + in_breakpoint_block = true; + node->visit_children(this); + in_breakpoint_block = false; +} + +/// check if given node contains sympy solution +static bool has_sympy_solution(ast::AST* node) { + return !AstLookupVisitor().lookup(node, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK).empty(); +} + +/** + * Create solution expression node that will be used for solve block + * @param solve_block solve block used to describe node to solve and method + * @return solution expression that will be used to replace the solve block + * + * Depending on the solver used, solve block is converted to solve expression statement + * that will be used to replace solve block. Note that the blocks are clones instead of + * shared_ptr because DerivimplicitCallback is currently contain whole node + * instead of just pointer. + */ +ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( + ast::SolveBlock* solve_block) { + /// find out the block that is going to solved + std::string block_name = solve_block->get_block_name()->get_node_name(); + auto solve_node_symbol = symtab->lookup(block_name); + assert(solve_node_symbol != nullptr); + auto node_to_solve = solve_node_symbol->get_node(); + + /// in case of derivimplicit method if neuron solver is used (i.e. not sympy) then + /// the solution is not in place but we have to create a callback to newton solver + auto method = solve_block->get_method(); + std::string solve_method = method ? method->get_node_name() : ""; + if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD && + !has_sympy_solution(node_to_solve)) { + /// typically derivimplicit is used for derivative block only + assert(node_to_solve->get_node_type() == ast::AstNodeType::DERIVATIVE_BLOCK); + auto derivative_block = dynamic_cast<ast::DerivativeBlock*>(node_to_solve); + auto callback_expr = new ast::DerivimplicitCallback(derivative_block->clone()); + return new ast::SolutionExpression(solve_block->clone(), callback_expr); + } + + auto block_to_solve = node_to_solve->get_statement_block(); + return new ast::SolutionExpression(solve_block->clone(), block_to_solve->clone()); +} + +/** + * Replace solve blocks with solution expression + * @param node Ast node for SOLVE statement in the mod file + */ +void SolveBlockVisitor::visit_expression_statement(ast::ExpressionStatement* node) { + node->visit_children(this); + if (node->get_expression()->is_solve_block()) { + auto solve_block = dynamic_cast<ast::SolveBlock*>(node->get_expression().get()); + auto sol_expr = create_solution_expression(solve_block); + if (in_breakpoint_block) { + nrn_state_solve_statements.emplace_back(new ast::ExpressionStatement(sol_expr)); + } else { + node->set_expression(std::shared_ptr<ast::SolutionExpression>(sol_expr)); + } + } +} + + +void SolveBlockVisitor::visit_program(ast::Program* node) { + symtab = node->get_symbol_table(); + node->visit_children(this); + /// add new node NrnState with solve blocks from breakpoint block + if (!nrn_state_solve_statements.empty()) { + auto nrn_state = new ast::NrnStateBlock(nrn_state_solve_statements); + node->addNode(nrn_state); + } +} + +} // namespace nmodl diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp new file mode 100644 index 0000000000..bca40c900b --- /dev/null +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -0,0 +1,45 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "ast/ast.hpp" +#include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { + + +/** + * \class SolveBlockVisitor + * \brief Replace solve block statements with actual solution node in the AST + * + * Once sympy or cnexp passes are run, solve blocks can be replaced with solution + * expression node that represent solution that is going to be solved. All solve + * statements appearing in breakpoint block get added to NrnState block as solution + * expression. + */ + +class SolveBlockVisitor: public AstVisitor { + private: + symtab::SymbolTable* symtab = nullptr; + + bool in_breakpoint_block = false; + + /// solve expression statements for NrnState block + ast::StatementVector nrn_state_solve_statements; + + ast::SolutionExpression* create_solution_expression(ast::SolveBlock* solve_block); + + public: + SolveBlockVisitor() = default; + void visit_breakpoint_block(ast::BreakpointBlock* node) override; + void visit_expression_statement(ast::ExpressionStatement* node) override; + void visit_program(ast::Program* node) override; +}; + +} // namespace nmodl diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 9b93fc89d2..c6c268e319 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -136,7 +136,7 @@ void SymtabVisitor::add_model_symbol_with_property(ast::Node* node, NmodlType pr auto symbol = std::make_shared<Symbol>(name, node, *token); symbol->add_property(property); - if (name == block_to_solve) { + if (block_to_solve.find(name) != block_to_solve.cend()) { symbol->add_property(NmodlType::to_solve); } @@ -172,7 +172,7 @@ void SymtabVisitor::setup_symbol_table(ast::AST* node, const std::string& name, /// there is only one solve statement allowed in mod file if (node->is_solve_block()) { auto solve_block = dynamic_cast<ast::SolveBlock*>(node); - block_to_solve = solve_block->get_block_name()->get_node_name(); + block_to_solve.insert(solve_block->get_block_name()->get_node_name()); } /// not required at the moment but every node diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 6e1b436ed4..ed3d691603 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -29,6 +29,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/rename_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" @@ -1750,18 +1751,10 @@ std::string run_cnexp_solve_visitor(const std::string& text) { driver.parse_string(text); auto ast = driver.ast(); - { - SymtabVisitor v1; - v1.visit_program(ast.get()); - CnexpSolveVisitor v2; - v2.visit_program(ast.get()); - } - + SymtabVisitor().visit_program(ast.get()); + CnexpSolveVisitor().visit_program(ast.get()); std::stringstream stream; - { - NmodlPrintVisitor v(stream); - v.visit_program(ast.get()); - } + NmodlPrintVisitor(stream).visit_program(ast.get()); return stream.str(); } @@ -1884,6 +1877,102 @@ SCENARIO("CnexpSolver visitor solving ODEs") { } } +//============================================================================= +// SolveBlock visitor tests +//============================================================================= + +std::string run_solve_block_visitor(const std::string& text) { + NmodlDriver driver; + driver.parse_string(text); + auto ast = driver.ast(); + SymtabVisitor().visit_program(ast.get()); + CnexpSolveVisitor().visit_program(ast.get()); + SolveBlockVisitor().visit_program(ast.get()); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +TEST_CASE("SolveBlock visitor") { + SECTION("SolveBlock add NrnState block") { + GIVEN("Breakpoint block with single solve block in breakpoint") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + + NRN_STATE SOLVE states METHOD cnexp{ + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + + )"; + + THEN("Single NrnState block gets added") { + auto result = run_solve_block_visitor(nmodl_text); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + + GIVEN("Breakpoint block with two solve block in breakpoint") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE state1 METHOD cnexp + SOLVE state2 METHOD cnexp + } + + DERIVATIVE state1 { + m' = (mInf-m)/mTau + } + + DERIVATIVE state2 { + h' = (mInf-h)/mTau + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE state1 METHOD cnexp + SOLVE state2 METHOD cnexp + } + + DERIVATIVE state1 { + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + + DERIVATIVE state2 { + h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) + } + + NRN_STATE SOLVE state1 METHOD cnexp{ + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + SOLVE state2 METHOD cnexp{ + h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) + } + + )"; + + THEN("NrnState blok combining multiple solve nodes added") { + auto result = run_solve_block_visitor(nmodl_text); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + } +} //============================================================================= // Passes can run multiple times From e46898acb8680ac630331f38fb9c3846dcf68502 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Mon, 25 Mar 2019 17:04:26 +0100 Subject: [PATCH 176/871] Add support for LINEAR / NON_LINEAR solve blocks (BlueBrain/nmodl#94) - SympySolver visitor also visits LINEAR blocks now - for small systems, replaces eqs directly with solutions - for larger systems, replaces with matrix system to be solved (codegen required) this change resolves BlueBrain/nmodl#57 (numerical instability of sparse solver) - in both cases it inserts the equations in the right location in the statement block this change partially resolves BlueBrain/nmodl#55 (order of statements in DERIVATIVE block) - add unit tests - added code generation support for eigen linear solve block - added initialize & finalize block for newton solver - add number of state vars to eigen solve blocks : codegen needs to know how many state vars in each solve block and this might not be the number of prime vars (e.g. LINEAR block) - EIGEN blocks in codegen.yaml now contain n_state_vars integer Also - refactored solvers: KINETIC -> DERIVATIVE -> (NON_)LINEAR - first any KINETIC blocks are converted to DERIVATIVE blocks - then ODEs in DERIVATIVE blocks are converted to a system of (non)linear equations - these are then solved with the same solvers as LINEAR or NON_LINEAR blocks - reduces unnecessary duplication of functionality - this change resolves BlueBrain/nmodl#46 (sympy solver ignoring user specified solve method) Todo - check for name clashes for new local variable declarations Resolve BlueBrain/nmodl#64 Resolves BlueBrain/nmodl#57 Resolves BlueBrain/nmodl#46 Co-Authored-By: pramodk <pramod.s.kumbhar@gmail.com> NMODL Repo SHA: BlueBrain/nmodl@7533fa90d48e70c2ef1a0cc87fb30609a9955927 --- nmodl/ode.py | 159 ++--- src/nmodl/ast/ast_common.hpp | 4 + src/nmodl/codegen/codegen_c_visitor.cpp | 51 +- src/nmodl/codegen/codegen_c_visitor.hpp | 1 + src/nmodl/codegen/codegen_helper_visitor.cpp | 3 + src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 + src/nmodl/language/codegen.yaml | 53 +- src/nmodl/nmodl/main.cpp | 1 + src/nmodl/visitors/cnexp_solve_visitor.cpp | 6 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 496 +++++++++++---- src/nmodl/visitors/sympy_solver_visitor.hpp | 131 +++- src/nmodl/visitors/visitor_utils.cpp | 13 +- test/nmodl/transpiler/ode/test_ode.py | 20 +- test/nmodl/transpiler/visitor/visitor.cpp | 632 ++++++++++++++++--- 15 files changed, 1225 insertions(+), 349 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index e31708d674..dfa72ebd33 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -12,15 +12,7 @@ raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") -def jacobian_is_linear(jacobian, state_vars): - for j in jacobian: - for x in state_vars: - if j.diff(x).simplify() != 0: - return False - return True - - -def make_unique_prefix(vars, default_prefix="tmp"): +def _make_unique_prefix(vars, default_prefix="tmp"): prefix = default_prefix # generate prefix that doesn't match first part # of any string in vars @@ -37,69 +29,58 @@ def make_unique_prefix(vars, default_prefix="tmp"): return prefix -def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): - """Solve system of ODEs, return solution as C code. +def _sympify_eqs(eq_strings, vars, constants): + # parse eq_strings into sympy expressions + sympy_vars = {constant: sp.symbols(constant, real=True) for constant in constants} + state_vars = [] + for var in vars: + v = sp.symbols(var, real=True) + sympy_vars[var] = v + state_vars.append(v) + eqs = [ + sp.sympify(eq.split("=", 1)[1], locals=sympy_vars) + - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars) + for eq in eq_strings + ] + return eqs, state_vars, sympy_vars + - If system is linear, constructs the backwards Euler linear - system and solves analytically, optionally also - with Common Subexpression Elimination if do_cse is true. +def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=False): + """Solve linear system of equations, return solution as C code. - Otherwise, constructs F(x) such that F(x)=0 is solution - of backwards Euler equation, along with Jacobian of F, - for use in a non-linear solver such as Newton. + If system is small (small_system=True, typically N<=3): + - solve analytically by gaussian elimination + - optionally do Common Subexpression Elimination if do_cse is true + + If system is large (default): + - gaussian elimination may not be numerically stable at runtime + - instead return a matrix J and vector F, where J X = F + - this linear system can then be solved for X by e.g. LU factorization Args: - diff_string: list of ODEs e.g. ["x' = a*x", "y' = 3"] - t_var: name of time variable in NEURON - dt_var: name of dt variable in NEURON - vars: set of variables used in expression, e.g. {"x", "y", a"} + eqs: list of equations e.g. ["x + y = a", "y = 3 + b"] + vars: list of variables to solve for, e.g. ["x", "y"] + constants: set of any other symbolic expressions used, e.g. {"a", "b"} + small_system: if True, solve analytically by gaussian elimination + otherwise return matrix system to be solved do_cse: if True, do Common Subexpression Elimination Returns: - List of strings containing analytic integral of derivative as C code + List of strings containing assignment statements List of strings containing new local variables - - Raises: - ImportError: if SymPy version is too old (<1.2) """ - sympy_vars = {var: sp.symbols(var, real=True) for var in vars} - - # generate prefix for new local vars that avoids clashes - prefix = make_unique_prefix(vars) - - old_state_vars = [] - for s in diff_strings: - vstr = s.split("'")[0] - old_state_var_name = f"{prefix}_{vstr}_old" - var = sp.symbols(old_state_var_name, real=True) - sympy_vars[old_state_var_name] = var - old_state_vars.append(var) - - state_vars = [sp.sympify(s.split("'")[0], locals=sympy_vars) for s in diff_strings] - diff_eqs = [sp.sympify(s.split("=", 1)[1], locals=sympy_vars) for s in diff_strings] - - t = sp.symbols(t_var, real=True) - sympy_vars[t_var] = t - - jacobian = sp.Matrix(diff_eqs).jacobian(state_vars) - - dt = sp.symbols(dt_var, real=True) - sympy_vars[dt_var] = dt + eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) code = [] new_local_vars = [] - if jacobian_is_linear(jacobian, state_vars): - # if linear system: construct implicit euler solution & solve by gaussian elimination - eqs = [] - for x_new, x_old, dxdt in zip(state_vars, old_state_vars, diff_eqs): - eqs.append(sp.Eq(x_new, x_old + dt * dxdt)) + if small_system: + # small linear system: solve by gaussian elimination for rhs in sp.linsolve(eqs, state_vars): - for x, x_old in zip(state_vars, old_state_vars): - new_local_vars.append(sp.ccode(x_old)) - code.append(f"{sp.ccode(x_old)} = {sp.ccode(x)}") if do_cse: + # generate prefix for new local vars that avoids clashes + prefix = _make_unique_prefix(vars) my_symbols = sp.utilities.iterables.numbered_symbols(prefix=prefix) sub_exprs, simplified_rhs = sp.cse( rhs, symbols=my_symbols, optimizations="basic", order="canonical" @@ -111,27 +92,56 @@ def solve_ode_system(diff_strings, t_var, dt_var, vars, do_cse=False): for v, e in zip(state_vars, rhs): code.append(f"{sp.ccode(v)} = {sp.ccode(e.evalf())}") else: - # otherwise: construct implicit euler solution in form F(x) = 0 - # also construct jacobian of this function dF/dx - - # state vars to be stored in vector X for Newton solver - Xvecsubs = {} - for i, x_new in enumerate(state_vars): - Xvecsubs[x_new] = sp.symbols(f"X[{i}]") - eqs = [] - for x_new, x_old, dxdt in zip(state_vars, old_state_vars, diff_eqs): - eqs.append((x_new - dt * dxdt).subs(Xvecsubs) - x_new) - for i, eq in enumerate(eqs): - code.append(f"F[{i}] = {sp.ccode(eq.evalf().simplify())}") - for i, jac in enumerate(sp.eye(jacobian.rows, jacobian.rows) - jacobian * dt): + # large linear system: construct and return matrix J, vector F such that + # J X(t+dt) = F is the Euler linear system to be solved by e.g. LU factorization + matJ, vecF = sp.linear_eq_to_matrix(eqs, state_vars) + # construct vector F + for i, v in enumerate(vecF): + code.append(f"F[{i}] = {sp.ccode(v.simplify().evalf())}") + # construct matrix J + for i, element in enumerate(matJ): # todo: fix indexing to be ascending order - flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) - code.append( - f"J[{flat_index}] = {sp.ccode(jac.subs(Xvecsubs).evalf().simplify())}" - ) + flat_index = matJ.rows * (i % matJ.rows) + (i // matJ.rows) + code.append(f"J[{flat_index}] = {sp.ccode(element.simplify().evalf())}") + return code, new_local_vars +def solve_non_lin_system(eq_strings, vars, constants): + """Solve non-linear system of equations, return solution as C code. + + - returns a vector F, and its Jacobian J, both in terms of X + - where F(X) = 0 is the implicit equation to solve for X + - this non-linear system can then be solved with the newton solver + + Args: + eqs: list of equations e.g. ["x + y = a", "y = 3 + b"] + vars: list of variables to solve for, e.g. ["x", "y"] + constants: set of any other symbolic expressions used, e.g. {"a", "b"} + + Returns: + List of strings containing assignment statements + """ + + eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) + + jacobian = sp.Matrix(eqs).jacobian(state_vars) + + Xvecsubs = {x_new: sp.symbols(f"X[{i}]") for i, x_new in enumerate(state_vars)} + + code = [] + for i, eq in enumerate(eqs): + code.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(Xvecsubs).evalf())}") + for i, jac in enumerate(jacobian): + # todo: fix indexing to be ascending order + flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) + code.append( + f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(Xvecsubs).evalf())}" + ) + + return code + + def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): """Analytically integrate supplied derivative, return solution as C code. @@ -344,9 +354,6 @@ def forwards_euler2c(diff_string, dt_var, vars): Returns: String containing forwards Euler timestep as C code - - Raises: - ImportError: if SymPy version is too old (<1.2) """ # every symbol (a.k.a variable) that SymPy diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 2a5f24cd60..8b0ec15f37 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -322,6 +322,10 @@ struct AST: public std::enable_shared_from_this<AST> { return false; } + virtual bool is_eigen_linear_solver_block() { + return false; + } + virtual bool is_procedure_block() { return false; } diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index e697011975..0c80efcf90 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -481,6 +481,7 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { auto expression = dynamic_cast<ExpressionStatement*>(node)->get_expression(); if (expression->is_statement_block() || expression->is_eigen_newton_solver_block() + || expression->is_eigen_linear_solver_block() || expression->is_solution_expression()) { return false; } @@ -1653,7 +1654,9 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc // solution vector to store copy of state vars for Newton solver printer->add_newline(); auto float_type = default_float_data_type(); - printer->add_line("Eigen::Matrix<{}, {}, 1> X;"_format(float_type, info.num_primes)); + int N = node->get_n_state_vars()->get_value(); + printer->add_line("Eigen::Matrix<{}, {}, 1> X;"_format(float_type, N)); + print_statement_block(node->get_setup_x_block().get(), false, false); // functor that evaluates F(X) and J(X) for Newton solver @@ -1661,32 +1664,67 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->add_line("NrnThread* nt;"); printer->add_line("{0}* inst;"_format(instance_struct())); printer->add_line("int id;"); + printer->add_line("double v;"); + printer->add_line("Datum* indexes;"); + + print_statement_block(node->get_variable_block().get(), false, false); + printer->add_newline(); + + printer->start_block("void initialize()"); + print_statement_block(node->get_initialize_block().get(), false, false); + printer->end_block(2); printer->add_line( - "functor(NrnThread* nt, {}* inst, int id) : nt(nt), inst(inst), id(id) {}"_format( + "functor(NrnThread* nt, {}* inst, int id, double v, Datum* indexes) : nt(nt), inst(inst), id(id), v(v), indexes(indexes) {}"_format( instance_struct(), "{}")); printer->add_indent(); printer->add_text( "void operator()(const Eigen::Matrix<{0}, {1}, 1>& X, Eigen::Matrix<{0}, {1}, " "1>& F, " - "Eigen::Matrix<{0}, {1}, {1}>& Jm) const"_format(float_type, info.num_primes)); + "Eigen::Matrix<{0}, {1}, {1}>& Jm) const"_format(float_type, N)); printer->start_block(); printer->add_line("{}* J = Jm.data();"_format(float_type)); print_statement_block(node->get_functor_block().get(), false, false); + printer->end_block(2); + + // assign newton solver results in matrix X to state vars + printer->start_block("void finalize()"); + print_statement_block(node->get_finalize_block().get(), false, false); printer->end_block(1); + printer->end_block(0); printer->add_text(";"); printer->add_newline(); // call newton solver with functor and X matrix that contains state vars printer->add_line("// call newton solver"); - printer->add_line("functor newton_functor(nt, inst, id);"); + printer->add_line("functor newton_functor(nt, inst, id, v, indexes);"); + printer->add_line("newton_functor.initialize();"); printer->add_line("int newton_iterations = nmodl::newton::newton_solver(X, newton_functor);"); // assign newton solver results in matrix X to state vars - printer->add_line("// assign results to state vars"); print_statement_block(node->get_update_states_block().get(), false, false); + printer->add_line("newton_functor.finalize();"); +} + +void CodegenCVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) { + printer->add_newline(); + std::string float_type = default_float_data_type(); + int N = node->get_n_state_vars()->get_value(); + printer->add_line("Eigen::Matrix<{0}, {1}, 1> X, F;"_format(float_type, N)); + printer->add_line("Eigen::Matrix<{0}, {1}, {1}> Jm;"_format(float_type, N)); + printer->add_line("{}* J = Jm.data();"_format(float_type)); + print_statement_block(node->get_variable_block().get(), false, false); + print_statement_block(node->get_initialize_block().get(), false, false); + print_statement_block(node->get_setup_x_block().get(), false, false); + + printer->add_newline(); + printer->add_line( + "X = Eigen::PartialPivLU<Eigen::Ref<Eigen::Matrix<{0}, {1}, {1}>>>(Jm).solve(F);"_format( + float_type, N)); + print_statement_block(node->get_update_states_block().get(), false, false); + print_statement_block(node->get_finalize_block().get(), false, false); } /****************************************************************************************/ @@ -2262,6 +2300,9 @@ void CodegenCVisitor::print_coreneuron_includes() { if (info.eigen_newton_solver_exist) { printer->add_line("#include <newton/newton.hpp>"); } + if (info.eigen_linear_solver_exist) { + printer->add_line("#include <Eigen/LU>"); + } } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 8c96298dd9..1e81a05df4 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -988,6 +988,7 @@ class CodegenCVisitor: public AstVisitor { virtual void visit_from_statement(ast::FromStatement* node) override; virtual void visit_function_call(ast::FunctionCall* node) override; virtual void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; + virtual void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) override; virtual void visit_if_statement(ast::IfStatement* node) override; virtual void visit_indexed_name(ast::IndexedName* node) override; virtual void visit_integer(ast::Integer* node) override; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 936c80fab9..6a3cc3ef6e 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -500,6 +500,9 @@ void CodegenHelperVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolve info.eigen_newton_solver_exist = true; } +void CodegenHelperVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) { + info.eigen_linear_solver_exist = true; +} void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { auto name = node->get_node_name(); diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 110a59db09..9ab5254500 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -78,6 +78,7 @@ class CodegenHelperVisitor: public AstVisitor { void visit_procedure_block(ast::ProcedureBlock* node) override; void visit_function_block(ast::FunctionBlock* node) override; void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; + void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) override; void visit_statement_block(ast::StatementBlock* node) override; void visit_initial_block(ast::InitialBlock* node) override; void visit_breakpoint_block(ast::BreakpointBlock* node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 69cb1e51f2..5eb263dd02 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -333,6 +333,9 @@ struct CodegenInfo { /// true if eigen newton solver is used bool eigen_newton_solver_exist = false; + /// true if eigen linear solver is used + bool eigen_linear_solver_exist = false; + /// if any ion has write variable bool ion_has_write_variable(); diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 6f9e68df2a..796c20ea5c 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -31,9 +31,29 @@ - Identifier: - Block: children: + - NrnStateBlock: + nmodl: "NRN_STATE " + description: "Represents the coreneuron nrn_state callback function" + members: + - solve_statements: + description: "solve blocks to be called or generated" + type: Statement + vector: true - EigenNewtonSolverBlock: description: "Represent newton solver solution block based on Eigen" + nmodl: "EIGEN_NEWTON_SOLVE" members: + - n_state_vars: + description: "number of state vars used in solve" + type: Integer + prefix: {value: "["} + suffix: {value: "]"} + - variable_block: + description: "Statements to be declared in the functor" + type: StatementBlock + - initialize_block: + description: "Statement block to be executed before calling newton solver" + type: StatementBlock - setup_x_block: description: "update X from states" type: StatementBlock @@ -43,14 +63,33 @@ - update_states_block: description: "update back states from X" type: StatementBlock - - NrnStateBlock: - nmodl: "NRN_STATE " - description: "Represents the coreneuron nrn_state callback function" + - finalize_block: + description: "Statement block to be executed after calling newton solver" + type: StatementBlock + - EigenLinearSolverBlock: + description: "Represent linear solver solution block based on Eigen" + nmodl: "EIGEN_LINEAR_SOLVE" members: - - solve_statements: - description: "solve blocks to be called or generated" - type: Statement - vector: true + - n_state_vars: + description: "number of state vars used in solve" + type: Integer + prefix: {value: "["} + suffix: {value: "]"} + - variable_block: + description: "Statements to be declared in the functor" + type: StatementBlock + - initialize_block: + description: "Statement block to be executed before calling linear solver" + type: StatementBlock + - setup_x_block: + description: "update X from states" + type: StatementBlock + - update_states_block: + description: "update back states from X" + type: StatementBlock + - finalize_block: + description: "Statement block to be executed after calling linear solver" + type: StatementBlock - WrappedExpression: description: "Wrap any other expression type" members: diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index a126a961da..d663bf2dd0 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -263,6 +263,7 @@ int main(int argc, const char* argv[]) { logger->info("Running KINETIC block visitor"); KineticBlockVisitor().visit_program(ast.get()); SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("kinetic")); } /// once we start modifying (especially removing) older constructs diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index a64097c0b9..5b2a47d52c 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -55,7 +55,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); if (name->is_prime_name()) { - auto equation = nmodl::to_nmodl(node); + auto equation = to_nmodl(node); parser::DiffeqDriver diffeq_driver; if (solve_method == codegen::naming::CNEXP_METHOD) { @@ -70,7 +70,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { - logger->warn("cnexp solver not possible for {}", to_nmodl(node)); + logger->warn("CnexpSolveVisitor :: cnexp solver not possible for {}", to_nmodl(node)); } } else if (solve_method == codegen::naming::EULER_METHOD) { std::string solution = diffeq_driver.solve(equation, solve_method); @@ -91,7 +91,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { program_symtab->insert(symbol); } } else { - logger->error("solver method '{}' not supported", solve_method); + logger->error("CnexpSolveVisitor :: solver method '{}' not supported", solve_method); } } } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 23cb784493..a7f9ceb7aa 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -10,10 +10,11 @@ #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" +#include "utils/string_utils.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/visitor_utils.hpp" - namespace py = pybind11; using namespace py::literals; @@ -21,6 +22,33 @@ namespace nmodl { using symtab::syminfo::NmodlType; +void SympySolverVisitor::init_block_data(ast::Node* node) { + // clear any previous data + expression_statements.clear(); + eq_system.clear(); + state_vars_in_block.clear(); + last_expression_statement = nullptr; + block_with_expression_statements = nullptr; + eq_system_is_valid = true; + // get set of local block vars & global vars + vars = global_vars; + if (auto symtab = node->get_statement_block()->get_symbol_table()) { + auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); + for (const auto& localvar: localvars) { + vars.insert(localvar->get_name()); + } + } +} + +void SympySolverVisitor::init_state_vars_vector() { + state_vars.clear(); + for (const auto& state_var: all_state_vars) { + if (state_vars_in_block.find(state_var) != state_vars_in_block.cend()) { + state_vars.push_back(state_var); + } + } +} + void SympySolverVisitor::replace_diffeq_expression(ast::DiffEqExpression* expr, const std::string& new_expr) { auto new_statement = create_statement(new_expr); @@ -30,28 +58,243 @@ void SympySolverVisitor::replace_diffeq_expression(ast::DiffEqExpression* expr, expr->set_expression(std::move(new_bin_expr)); } -std::shared_ptr<ast::EigenNewtonSolverBlock> -SympySolverVisitor::construct_eigen_newton_solver_block( - const std::vector<std::string>& setup_x, - const std::vector<std::string>& functor, - const std::vector<std::string>& update_state) { - auto setup_x_block = create_statement_block(setup_x); - auto functor_block = create_statement_block(functor); - auto update_state_block = create_statement_block(update_state); - return std::make_shared<ast::EigenNewtonSolverBlock>(setup_x_block, functor_block, - update_state_block); +void SympySolverVisitor::check_expr_statements_in_same_block() { + /// all ode/kinetic/(non)linear statements (typically) appear in the same statement block + /// if this is not the case, for now return an error (and should instead use fallback solver) + if (block_with_expression_statements != nullptr && + block_with_expression_statements != current_statement_block) { + logger->warn( + "SympySolverVisitor :: Coupled equations are appearing in different blocks - not " + "supported"); + eq_system_is_valid = false; + } + block_with_expression_statements = current_statement_block; } -void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { - auto prev_statement_block = current_statement_block; - current_statement_block = node; - node->visit_children(this); - current_statement_block = prev_statement_block; +ast::StatementVector::iterator SympySolverVisitor::get_solution_location_iterator( + ast::StatementVector& statements) { + // find out where to insert solutions in statement block + // returns iterator pointing to the first element after the last (non)linear eq + // so if there are no such elements, it returns statements.end() + auto it = statements.begin(); + if (last_expression_statement != nullptr) { + while ((it != statements.end()) && + (std::dynamic_pointer_cast<ast::ExpressionStatement>(*it).get() != + last_expression_statement)) { + logger->debug("SympySolverVisitor :: {} != {}", to_nmodl((*it).get()), + to_nmodl(last_expression_statement)); + ++it; + } + if (it != statements.end()) { + logger->debug("SympySolverVisitor :: {} == {}", + to_nmodl(std::dynamic_pointer_cast<ast::ExpressionStatement>(*it).get()), + to_nmodl(last_expression_statement)); + ++it; + } + } + return it; } -void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement* node) { - current_diffeq_statement = node; - node->visit_children(this); +static bool has_local_statement(std::shared_ptr<ast::Statement> statement) { + return !AstLookupVisitor().lookup(statement.get(), ast::AstNodeType::LOCAL_VAR).empty(); +} + +void SympySolverVisitor::construct_eigen_solver_block( + const std::vector<std::string>& pre_solve_statements, + const std::vector<std::string>& solutions, + bool linear) { + // find out where to insert solution in statement block + auto& statements = block_with_expression_statements->statements; + auto it = get_solution_location_iterator(statements); + // insert pre-solve statements below last linear eq in block + for (const auto& statement: pre_solve_statements) { + logger->debug("SympySolverVisitor :: -> adding statement: {}", statement); + it = statements.insert(it, create_statement(statement)); + ++it; + } + // make Eigen vector <-> state var assignments + std::vector<std::string> setup_x_eqs; + std::vector<std::string> update_state_eqs; + for (int i = 0; i < state_vars.size(); i++) { + auto statement = state_vars[i] + " = X[" + std::to_string(i) + "]"; + auto rev_statement = "X[" + std::to_string(i) + "] = " + state_vars[i]; + update_state_eqs.push_back(statement); + setup_x_eqs.push_back(rev_statement); + logger->debug("SympySolverVisitor :: setup_x: {}", rev_statement); + logger->debug("SympySolverVisitor :: update_state: {}", statement); + } + // TODO: make unique name for Eigen vector if clashes + if (vars.find("X") != vars.end()) { + logger->error("SympySolverVisitor :: -> X conflicts with NMODL variable"); + } + for (const auto& sol: solutions) { + logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); + } + // statements after last diff/linear/non-linear eq statement go into finalize_block + ast::StatementVector finalize_statements{it, statements.end()}; + // remove them from the statement block + statements.erase(it, statements.end()); + // also remove diff/linear/non-linear eq statements from the statement block + remove_statements_from_block(block_with_expression_statements, expression_statements); + // move any local variable declarations into variable_block + ast::StatementVector variable_statements; + // remaining statements in block should go into initialize_block + ast::StatementVector initialize_statements; + for (auto s: statements) { + if (has_local_statement(s)) { + variable_statements.push_back(s); + } else { + initialize_statements.push_back(s); + } + } + // make statement blocks + auto n_state_vars = std::make_shared<ast::Integer>(state_vars.size(), nullptr); + auto variable_block = std::make_shared<ast::StatementBlock>(std::move(variable_statements)); + auto initialize_block = std::make_shared<ast::StatementBlock>(std::move(initialize_statements)); + auto update_state_block = create_statement_block(update_state_eqs); + auto finalize_block = std::make_shared<ast::StatementBlock>(std::move(finalize_statements)); + + if (linear) { + /// create eigen linear solver block + setup_x_eqs.insert(setup_x_eqs.end(), solutions.begin(), solutions.end()); + auto setup_x_block = create_statement_block(setup_x_eqs); + auto solver_block = + std::make_shared<ast::EigenLinearSolverBlock>(n_state_vars, variable_block, + initialize_block, setup_x_block, + update_state_block, finalize_block); + /// replace statement block with solver block as it contains all statements + ast::StatementVector solver_block_statements{ + std::make_shared<ast::ExpressionStatement>(solver_block)}; + block_with_expression_statements->set_statements(std::move(solver_block_statements)); + } else { + /// create eigen newton solver block + auto setup_x_block = create_statement_block(setup_x_eqs); + auto functor_block = create_statement_block(solutions); + auto solver_block = std::make_shared<ast::EigenNewtonSolverBlock>( + n_state_vars, variable_block, initialize_block, setup_x_block, functor_block, + update_state_block, finalize_block); + + /// replace statement block with solver block as it contains all statements + ast::StatementVector solver_block_statements{ + std::make_shared<ast::ExpressionStatement>(solver_block)}; + block_with_expression_statements->set_statements(std::move(solver_block_statements)); + } +} + +void SympySolverVisitor::solve_linear_system(const std::vector<std::string>& pre_solve_statements) { + // construct ordered vector of state vars used in linear system + init_state_vars_vector(); + // call sympy linear solver + bool small_system = (eq_system.size() <= SMALL_LINEAR_SYSTEM_MAX_STATES); + auto locals = py::dict("eq_strings"_a = eq_system, "state_vars"_a = state_vars, "vars"_a = vars, + "small_system"_a = small_system, "do_cse"_a = elimination); + py::exec(R"( + from nmodl.ode import solve_lin_system + exception_message = "" + try: + solutions, new_local_vars = solve_lin_system(eq_strings, + state_vars, + vars, + small_system, + do_cse) + except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) + )", + py::globals(), locals); + // returns a vector of solutions, i.e. new statements to add to block: + auto solutions = locals["solutions"].cast<std::vector<std::string>>(); + // and a vector of new local variables that need to be declared in the block: + auto new_local_vars = locals["new_local_vars"].cast<std::vector<std::string>>(); + // may also return a python exception message: + auto exception_message = locals["exception_message"].cast<std::string>(); + if (!exception_message.empty()) { + logger->warn("SympySolverVisitor :: solve_lin_system python exception: " + + exception_message); + return; + } + // find out where to insert solutions in statement block + auto& statements = block_with_expression_statements->statements; + auto it = get_solution_location_iterator(statements); + if (small_system) { + // for small number of state vars, linear solver + // directly returns solution by solving symbolically at compile time + logger->debug("SympySolverVisitor :: Solving *small* linear system of eqs"); + // declare new local vars + if (!new_local_vars.empty()) { + for (const auto& new_local_var: new_local_vars) { + logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", + new_local_var); + add_local_variable(block_with_expression_statements, new_local_var); + } + } + // insert pre-solve statements below last linear eq in block + for (const auto& statement: pre_solve_statements) { + logger->debug("SympySolverVisitor :: -> adding statement: {}", statement); + it = statements.insert(it, create_statement(statement)); + ++it; + } + // then insert new solution statements + for (const auto& sol: solutions) { + logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); + it = statements.insert(it, create_statement(sol)); + ++it; + } + /// remove original lineq statements from the block + remove_statements_from_block(block_with_expression_statements, expression_statements); + } else { + // otherwise it returns a linear matrix system to solve + logger->debug("SympySolverVisitor :: Constructing linear newton solve block"); + construct_eigen_solver_block(pre_solve_statements, solutions, true); + } +} + +void SympySolverVisitor::solve_non_linear_system( + const std::vector<std::string>& pre_solve_statements) { + // construct ordered vector of state vars used in non-linear system + init_state_vars_vector(); + // call sympy non-linear solver + auto locals = py::dict("equation_strings"_a = eq_system, "state_vars"_a = state_vars, + "vars"_a = vars); + py::exec(R"( + from nmodl.ode import solve_non_lin_system + exception_message = "" + try: + solutions = solve_non_lin_system(equation_strings, + state_vars, + vars) + except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) + )", + py::globals(), locals); + // returns a vector of solutions, i.e. new statements to add to block: + auto solutions = locals["solutions"].cast<std::vector<std::string>>(); + // may also return a python exception message: + auto exception_message = locals["exception_message"].cast<std::string>(); + if (!exception_message.empty()) { + logger->warn("SympySolverVisitor :: solve_non_lin_system python exception: " + + exception_message); + return; + } + logger->debug("SympySolverVisitor :: Constructing eigen newton solve block"); + construct_eigen_solver_block(pre_solve_statements, solutions, false); +} + +void SympySolverVisitor::visit_var_name(ast::VarName* node) { + if (collect_state_vars) { + std::string var_name = node->get_node_name(); + // if var_name is a state var, add it to set + if (std::find(all_state_vars.cbegin(), all_state_vars.cend(), var_name) != + all_state_vars.cend()) { + logger->debug("SympySolverVisitor :: adding state var: {}", var_name); + state_vars_in_block.insert(var_name); + } + } } void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { @@ -68,16 +311,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { return; } - /// all ode statements (typically) appear in the same statement block - /// so for now don't track track different blocks - if (block_with_odes != nullptr && block_with_odes != current_statement_block) { - logger->error( - "SympySolverVisitor :: differential equations are appearing in different blocks"); - } - block_with_odes = current_statement_block; - - prime_variables.push_back(lhs->get_node_name()); - diffeq_statements.insert(current_diffeq_statement); + check_expr_statements_in_same_block(); const auto node_as_nmodl = to_nmodl_for_sympy(node); const auto locals = py::dict("equation_string"_a = node_as_nmodl, @@ -119,8 +353,14 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { py::globals(), locals); } else { // for other solver methods: just collect the ODEs & return - logger->debug("SympySolverVisitor :: adding ODE system: {}", to_nmodl_for_sympy(node)); - ode_system.push_back(to_nmodl_for_sympy(node)); + std::string eq_str = to_nmodl_for_sympy(node); + std::string state_var_name = lhs_name->get_node_name(); + logger->debug("SympySolverVisitor :: adding ODE system: {}", eq_str); + eq_system.push_back(eq_str); + logger->debug("SympySolverVisitor :: adding state var: {}", state_var_name); + state_vars_in_block.insert(state_var_name); + expression_statements.insert(current_expression_statement); + last_expression_statement = current_expression_statement; return; } @@ -142,117 +382,119 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { } void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { - // get all vars for this block, i.e. global vars + local vars - vars = global_vars; - if (auto symtab = node->get_statement_block()->get_symbol_table()) { - auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); - for (const auto& v: localvars) { - vars.insert(v->get_name()); - } - } + /// clear information from previous block, get global vars + block local vars + init_block_data(node); // get user specified solve method for this block solve_method = derivative_block_solve_method[node->get_node_name()]; - /// clear information from previous derivative block if any - diffeq_statements.clear(); - prime_variables.clear(); - ode_system.clear(); - // visit each differential equation: // - for CNEXP or EULER, each equation is independent & is replaced with its solution - // - otherwise, each equation is added to ode_system (and to binary_expressions_to_replace) + // - otherwise, each equation is added to eq_system node->visit_children(this); - /// if there are no odes collected then there is nothing to do - if (!ode_system.empty()) { - // solve system of ODEs in ode_system + if (eq_system_is_valid && !eq_system.empty()) { + // solve system of ODEs in eq_system logger->debug("SympySolverVisitor :: Solving {} system of ODEs", solve_method); - auto locals = py::dict("equation_strings"_a = ode_system, - "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, - "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, - "do_cse"_a = elimination); - py::exec(R"( - from nmodl.ode import solve_ode_system - exception_message = "" - try: - solutions, new_local_vars = solve_ode_system(equation_strings, t_var, dt_var, vars, do_cse) - except Exception as e: - # if we fail, fail silently and return empty string - solutions = [""] - new_local_vars = [""] - exception_message = str(e) - )", - py::globals(), locals); - // returns a vector of solutions, i.e. new statements to add to block: - auto solutions = locals["solutions"].cast<std::vector<std::string>>(); - // and a vector of new local variables that need to be declared in the block: - auto new_local_vars = locals["new_local_vars"].cast<std::vector<std::string>>(); - auto exception_message = locals["exception_message"].cast<std::string>(); - - if (!exception_message.empty()) { - logger->warn("SympySolverVisitor :: solve_ode_system python exception: " + - exception_message); - return; + // construct implicit Euler equations from ODEs + std::vector<std::string> pre_solve_statements; + for (auto& eq: eq_system) { + auto split_eq = stringutils::split_string(eq, '='); + auto x_prime_split = stringutils::split_string(split_eq[0], '\''); + auto x = stringutils::trim(x_prime_split[0]); + auto dxdt = stringutils::trim(split_eq[1]); + auto old_x = "old_" + x; // TODO: do this properly, check name is unique + // declare old_x + logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); + add_local_variable(block_with_expression_statements, old_x); + // assign old_x = x + pre_solve_statements.push_back(old_x + " = " + x); + eq = x + " = " + old_x + " + " + codegen::naming::NTHREAD_DT_VARIABLE + " * (" + dxdt + + ")"; + logger->debug("SympySolverVisitor :: -> constructed euler eq: {}", eq); } - // sanity check: must have at least as many solutions as ODE's to replace: - if (solutions.size() < ode_system.size()) { - logger->warn("SympySolverVisitor :: Solve failed: fewer solutions than ODE's"); - return; + if (solve_method == codegen::naming::SPARSE_METHOD) { + solve_linear_system(pre_solve_statements); + } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { + solve_non_linear_system(pre_solve_statements); + } else { + logger->error("SympySolverVisitor :: Solve method {} not supported", solve_method); } + } +} - // declare new local vars - if (!new_local_vars.empty()) { - for (const auto& new_local_var: new_local_vars) { - logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", - new_local_var); - add_local_variable(block_with_odes, new_local_var); - } - } +void SympySolverVisitor::visit_lin_equation(ast::LinEquation* node) { + check_expr_statements_in_same_block(); + std::string lin_eq = to_nmodl_for_sympy(node->get_left_linxpression().get()); + lin_eq += " = "; + lin_eq += to_nmodl_for_sympy(node->get_linxpression().get()); + eq_system.push_back(lin_eq); + expression_statements.insert(current_expression_statement); + last_expression_statement = current_expression_statement; + logger->debug("SympySolverVisitor :: adding linear eq: {}", lin_eq); + collect_state_vars = true; + node->visit_children(this); + collect_state_vars = false; +} - if (solve_method == codegen::naming::SPARSE_METHOD) { - remove_statements_from_block(block_with_odes, diffeq_statements); - // get a copy of existing statements in block - auto statements = block_with_odes->get_statements(); - // add new statements - for (const auto& sol: solutions) { - logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); - statements.push_back(create_statement(sol)); - } - // replace old set of statements in AST with new one - block_with_odes->set_statements(std::move(statements)); - } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { - /// Construct X from the state variables by using the original - /// ODE statements in the block. Also create statements to update - /// state variables from X - std::vector<std::string> setup_x_eqs; - std::vector<std::string> update_state_eqs; - for (int i = 0; i < prime_variables.size(); i++) { - auto statement = prime_variables[i] + " = " + "X[ " + std::to_string(i) + "]"; - auto rev_statement = "X[ " + std::to_string(i) + "]" + " = " + prime_variables[i]; - update_state_eqs.push_back(statement); - setup_x_eqs.push_back(rev_statement); - } +void SympySolverVisitor::visit_linear_block(ast::LinearBlock* node) { + logger->debug("SympySolverVisitor :: found LINEAR block: {}", node->get_node_name()); - /// remove original ODE statements from the block where they initially appear - remove_statements_from_block(block_with_odes, diffeq_statements); + /// clear information from previous block, get global vars + block local vars + init_block_data(node); - /// create newton solution block and add that as statement back in the block - /// statements in solutions : put F, J into new functor to be created for eigen - auto solver_block = construct_eigen_newton_solver_block(setup_x_eqs, solutions, - update_state_eqs); + // collect linear equations + node->visit_children(this); - if (vars.find("X") != vars.end()) { - logger->error("SympySolverVisitor :: -> X conflicts with NMODL variable"); - } + if (eq_system_is_valid && !eq_system.empty()) { + solve_linear_system(); + } +} - block_with_odes->addStatement(std::make_shared<ast::ExpressionStatement>(solver_block)); - } +void SympySolverVisitor::visit_non_lin_equation(ast::NonLinEquation* node) { + check_expr_statements_in_same_block(); + std::string non_lin_eq = to_nmodl_for_sympy(node->get_lhs().get()); + non_lin_eq += " = "; + non_lin_eq += to_nmodl_for_sympy(node->get_rhs().get()); + eq_system.push_back(non_lin_eq); + expression_statements.insert(current_expression_statement); + last_expression_statement = current_expression_statement; + logger->debug("SympySolverVisitor :: adding non-linear eq: {}", non_lin_eq); + collect_state_vars = true; + node->visit_children(this); + collect_state_vars = false; +} + +void SympySolverVisitor::visit_non_linear_block(ast::NonLinearBlock* node) { + logger->debug("SympySolverVisitor :: found NONLINEAR block: {}", node->get_node_name()); + + /// clear information from previous block, get global vars + block local vars + init_block_data(node); + + // collect non-linear equations + node->visit_children(this); + + if (eq_system_is_valid && !eq_system.empty()) { + solve_non_linear_system(); } } +void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement* node) { + auto prev_expression_statement = current_expression_statement; + current_expression_statement = node; + node->visit_children(this); + current_expression_statement = prev_expression_statement; +} + +void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { + auto prev_statement_block = current_statement_block; + current_statement_block = node; + node->visit_children(this); + current_statement_block = prev_statement_block; +} + void SympySolverVisitor::visit_program(ast::Program* node) { global_vars = get_global_vars(node); @@ -263,8 +505,8 @@ void SympySolverVisitor::visit_program(ast::Program* node) { if (auto block_ptr = std::dynamic_pointer_cast<ast::SolveBlock>(block)) { std::string solve_method; if (block_ptr->get_method()) { - // Note: solve method name is an optional especially LINEAR and NONLINEAR - // blocks do not have solve method specified + // Note: solve method name is an optional parameter + // LINEAR and NONLINEAR blocks do not have solve method specified solve_method = block_ptr->get_method()->get_value()->eval(); } std::string block_name = block_ptr->get_block_name()->get_value()->eval(); @@ -274,6 +516,16 @@ void SympySolverVisitor::visit_program(ast::Program* node) { } } + // get set of all state vars + all_state_vars.clear(); + if (auto symtab = node->get_symbol_table()) { + auto statevars = symtab->get_variables_with_properties(NmodlType::state_var); + for (const auto& v: statevars) { + const auto& varname = v->get_name(); + all_state_vars.push_back(varname); + } + } + node->visit_children(this); } diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index c8e87930bb..b2e1081d14 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -22,33 +22,71 @@ namespace nmodl { /** * \class SympySolverVisitor - * \brief Visitor for differential equations in derivative block + * \brief Visitor for systems of algebraic and differential equations * - * For solver method "cnexp": - * replace each the differential equation with its analytic - * solution using SymPy, optionally using the (1,1) order in dt - * Pade approximant to the solution. + * For DERIVATIVE block, solver method "cnexp": + * - replace each ODE with its analytic solution + * - optionally using the (1,1) order Pade approximant in dt * - * For solver method "euler": - * replace each the differential equation with a - * forwards Euler timestep + * For DERIVATIVE block, solver method "euler": + * - replace each ODE with forwards Euler timestep + * + * For DERIVATIVE block, solver method "sparse": + * - construct backwards Euler timestep linear system + * - for small systems: solves resulting linear algebraic equation by + * Gaussian elimination, replaces differential equations + * with explicit solution of backwards Euler equations + * - for large systems, returns matrix and vector of linear system + * to be solved by e.g. LU factorization + * + * For DERIVATIVE block, solver method "derivimplicit": + * - construct backwards Euler timestep non-linear system + * - return function F and its Jacobian J to be solved by newton solver + * + * For LINEAR blocks: + * - for small systems: solve linear system of algebraic equations by + * Gaussian elimination, replace equations with solutions + * - for large systems: return matrix and vector of linear system + * to be solved by e.g. LU factorization + * + * For NON_LINEAR blocks: + * - return function F and its Jacobian J to be solved by newton solver * - * For solver method "sparse": - * constructs backwards Euler timestep for coupled set of *linear* - * ODEs, solves resulting linear algebraic equation by - * Gaussian substitution, replaces differential equations - * with explicit solution of backwards Euler equations */ class SympySolverVisitor: public AstVisitor { private: - /** Replace binary expression with new expression provided as string */ + /// clear any data from previous block & get set of block local vars + global vars + void init_block_data(ast::Node* node); + + /// construct vector from set of state vars in correct order + void init_state_vars_vector(); + + /// replace binary expression with new expression provided as string static void replace_diffeq_expression(ast::DiffEqExpression* expr, const std::string& new_expr); - std::shared_ptr<ast::EigenNewtonSolverBlock> construct_eigen_newton_solver_block( - const std::vector<std::string>& setup_x, - const std::vector<std::string>& functor, - const std::vector<std::string>& update_state); + /// raise error if kinetic/ode/(non)linear statements are spread over multiple blocks + void check_expr_statements_in_same_block(); + + /// return iterator pointing to where solution should be inserted in statement block + ast::StatementVector::iterator get_solution_location_iterator(ast::StatementVector& statements); + + /// construct solver block + void construct_eigen_solver_block(const std::vector<std::string>& pre_solve_statements, + const std::vector<std::string>& solutions, + bool linear); + + /// solve linear system (for "sparse" and "LINEAR") + void solve_linear_system(const std::vector<std::string>& pre_solve_statements = {}); + + /// solve non-linear system (for "derivimplicit" and "NONLINEAR") + void solve_non_linear_system(const std::vector<std::string>& pre_solve_statements = {}); + + /// return NMODL string version of node, excluding any units + static std::string to_nmodl_for_sympy(ast::AST* node) { + return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); + } + /// global variables std::set<std::string> global_vars; @@ -58,45 +96,68 @@ class SympySolverVisitor: public AstVisitor { /// map between derivative block names and associated solver method std::map<std::string, std::string> derivative_block_solve_method{}; - /// prime variables from LHS of ODEs - std::vector<std::string> prime_variables; + /// expression statements appearing in the block + /// (these can be of type DiffEqExpression, LinEquation or NonLinEquation) + std::set<ast::Node*> expression_statements; - /// ODE statements appeared in the derivative block - std::set<ast::Node*> diffeq_statements; + /// current expression statement being visited (to track ODEs / (non)lineqs) + ast::ExpressionStatement* current_expression_statement; - /// current expression statement being visited (to track ODEs) - ast::ExpressionStatement* current_diffeq_statement = nullptr; + /// last expression statement visited (to know where to insert solutions in statement block) + ast::ExpressionStatement* last_expression_statement = nullptr; /// current statement block being visited ast::StatementBlock* current_statement_block = nullptr; - /// block where ode statements are appeared - ast::StatementBlock* block_with_odes = nullptr; + /// block where expression statements appear (to check there is only one) + ast::StatementBlock* block_with_expression_statements = nullptr; /// method specified in solve block std::string solve_method; + /// vector of {ODE, linear eq, non-linear eq} system to solve + std::vector<std::string> eq_system; + + /// only solve eq_system system of equations if this is true: + bool eq_system_is_valid = true; + + /// true for (non)linear eqs, to identify all state vars used in equations + bool collect_state_vars = false; + + /// vector of all state variables (in order specified in STATE block in mod file) + std::vector<std::string> all_state_vars; + + /// set of state variables used in block + std::set<std::string> state_vars_in_block; + + /// vector of state vars used *in block* (in same order as all_state_vars) + std::vector<std::string> state_vars; + /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; // optionally do CSE (common subexpression elimination) for sparse solver bool elimination; - /// vector of coupled ODE equations to solve - std::vector<std::string> ode_system; - - static std::string to_nmodl_for_sympy(ast::AST* node) { - return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); - } + /// max number of state vars allowed for small system linear solver + int SMALL_LINEAR_SYSTEM_MAX_STATES; public: - SympySolverVisitor(bool use_pade_approx = false, bool elimination = true) + SympySolverVisitor(bool use_pade_approx = false, + bool elimination = true, + int SMALL_LINEAR_SYSTEM_MAX_STATES = 3) : use_pade_approx(use_pade_approx) - , elimination(elimination){}; + , elimination(elimination) + , SMALL_LINEAR_SYSTEM_MAX_STATES(SMALL_LINEAR_SYSTEM_MAX_STATES){}; + void visit_var_name(ast::VarName* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; - void visit_expression_statement(ast::ExpressionStatement* node) override; void visit_derivative_block(ast::DerivativeBlock* node) override; + void visit_lin_equation(ast::LinEquation* node) override; + void visit_linear_block(ast::LinearBlock* node) override; + void visit_non_lin_equation(ast::NonLinEquation* node) override; + void visit_non_linear_block(ast::NonLinearBlock* node) override; + void visit_expression_statement(ast::ExpressionStatement* node) override; void visit_statement_block(ast::StatementBlock* node) override; void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 15384b4244..9a5a76b6d2 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -127,13 +127,12 @@ void remove_statements_from_block(ast::StatementBlock* block, std::set<std::string> get_global_vars(Program* node) { std::set<std::string> vars; if (auto* symtab = node->get_symbol_table()) { - NmodlType property = NmodlType::global_var | NmodlType::range_var | - NmodlType::param_assign | NmodlType::extern_var | - NmodlType::prime_name | NmodlType::dependent_def | - NmodlType::read_ion_var | NmodlType::write_ion_var | - NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | - NmodlType::section_var | NmodlType::constant_var | - NmodlType::extern_neuron_variable | NmodlType::state_var; + NmodlType property = + NmodlType::global_var | NmodlType::range_var | NmodlType::param_assign | + NmodlType::extern_var | NmodlType::prime_name | NmodlType::dependent_def | + NmodlType::read_ion_var | NmodlType::write_ion_var | NmodlType::nonspecific_cur_var | + NmodlType::electrode_cur_var | NmodlType::section_var | NmodlType::constant_var | + NmodlType::extern_neuron_variable | NmodlType::state_var | NmodlType::factor_def; for (const auto& globalvar: symtab->get_variables_with_properties(property)) { vars.insert(globalvar->get_name()); } diff --git a/test/nmodl/transpiler/ode/test_ode.py b/test/nmodl/transpiler/ode/test_ode.py index cd90a51600..515d3a10cd 100644 --- a/test/nmodl/transpiler/ode/test_ode.py +++ b/test/nmodl/transpiler/ode/test_ode.py @@ -5,22 +5,22 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** -from nmodl.ode import differentiate2c, make_unique_prefix +from nmodl.ode import differentiate2c, _make_unique_prefix def test_make_unique_prefix(): # if prefix matches start of any var, append underscores # to prefix until this is not true - assert make_unique_prefix(["a", "b", "ccc", "tmp"], "z") == "z" - assert make_unique_prefix(["a", "b", "ccc", "tmp"], "a") == "a_" - assert make_unique_prefix(["a", "b", "ccc", "tmp"], "az") == "az" - assert make_unique_prefix(["a", "b", "ccc", "tmp"], "cc") == "cc_" - assert make_unique_prefix(["a", "b", "ccc", "tmp"], "ccc") == "ccc_" - assert make_unique_prefix(["a", "b", "ccc", "tmp"], "tmp") == "tmp_" - assert make_unique_prefix(["a", "b", "tmp_", "tmp"], "tmp") == "tmp__" - assert make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmp") == "tmp_" - assert make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmpvar") == "tmpvar" + assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "z") == "z" + assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "a") == "a_" + assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "az") == "az" + assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "cc") == "cc_" + assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "ccc") == "ccc_" + assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "tmp") == "tmp_" + assert _make_unique_prefix(["a", "b", "tmp_", "tmp"], "tmp") == "tmp__" + assert _make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmp") == "tmp_" + assert _make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmpvar") == "tmpvar" def test_differentiation(): diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index ed3d691603..caf003a2d9 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2113,12 +2113,10 @@ std::vector<std::string> run_kinetic_block_visitor( auto ast = driver.ast(); // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); // run KineticBlock visitor on AST - KineticBlockVisitor v_kinetic; - v_kinetic.visit_program(ast.get()); + KineticBlockVisitor().visit_program(ast.get()); // run lookup visitor to extract DERIVATIVE block(s) from AST AstLookupVisitor v_lookup; @@ -2469,8 +2467,7 @@ std::vector<std::string> run_sympy_solver_visitor( auto ast = driver.ast(); // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); // run SympySolver on AST SympySolverVisitor v_sympy(pade, cse); @@ -2512,7 +2509,7 @@ std::string ast_to_string(ast::Program* node) { return stream.str(); } -SCENARIO("SympySolver visitor", "[sympy]") { +SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { GIVEN("Derivative block without ODE, solver method cnexp") { std::string nmodl_text = R"( BREAKPOINT { @@ -2635,12 +2632,17 @@ SCENARIO("SympySolver visitor", "[sympy]") { REQUIRE(AST_string == ast_to_string(ast.get())); } } - GIVEN("Derivative block with cnexp solver method and conditional block") { +} + +SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit][sparse]") { + GIVEN("Derivative block with derivimplicit solver method and conditional block") { std::string nmodl_text = R"( + STATE { + m + } BREAKPOINT { SOLVE states METHOD derivimplicit } - DERIVATIVE states { IF (mInf == 1) { mInf = mInf+1 @@ -2648,45 +2650,43 @@ SCENARIO("SympySolver visitor", "[sympy]") { m' = (mInf-m)/mTau } )"; - /// TODO : Note that the test is not correct because the ode is linear so the python - /// sympy solver call is returning the sparse solution, not the derivimplicit one - std::string expected_text = R"( - BREAKPOINT { - SOLVE states METHOD derivimplicit - } - + std::string expected_result = R"( DERIVATIVE states { - LOCAL tmp_m_old - IF (mInf == 1) { - mInf = mInf+1 - } - { + EIGEN_NEWTON_SOLVE[1]{ + LOCAL old_m + }{ + IF (mInf == 1) { + mInf = mInf+1 + } + old_m = m + }{ X[0] = m }{ - tmp_m_old = m - m = (dt*mInf+mTau*tmp_m_old)/(dt+mTau) + F[0] = (-dt*(X[0]-mInf)+mTau*(-X[0]+old_m))/mTau + J[0] = -(dt+mTau)/mTau }{ m = X[0] + }{ } - } - )"; - NmodlDriver driver; - driver.parse_string(nmodl_text); - auto ast = driver.ast(); - SymtabVisitor().visit_program(ast.get()); - SympySolverVisitor().visit_program(ast.get()); - std::string AST_string = ast_to_string(ast.get()); - + })"; THEN("SympySolver correctly inserts ode to block") { - REQUIRE(AST_string == reindent_text(expected_text)); + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); } } - GIVEN("Derivative block of coupled & linear ODES, solver method sparse, no CSE") { + + GIVEN("Derivative block of coupled & linear ODES, solver method sparse") { std::string nmodl_text = R"( + STATE { + x y z + } BREAKPOINT { SOLVE states METHOD sparse } DERIVATIVE states { + LOCAL a, b, c, d, h x' = a*z + b*h y' = c + 2*x z' = d*z - y @@ -2695,27 +2695,27 @@ SCENARIO("SympySolver visitor", "[sympy]") { std::string expected_result = R"( DERIVATIVE states { - LOCAL tmp_x_old, tmp_y_old, tmp_z_old - tmp_x_old = x - tmp_y_old = y - tmp_z_old = z - x = (-a*dt*(dt*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old)-tmp_z_old)+(b*dt*h+tmp_x_old)*(2*a*pow(dt, 3)-d*dt+1))/(2*a*pow(dt, 3)-d*dt+1) - y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old)-tmp_z_old)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old))/(2*a*pow(dt, 3)-d*dt+1) - z = (-dt*(c*dt+2*dt*(b*dt*h+tmp_x_old)+tmp_y_old)+tmp_z_old)/(2*a*pow(dt, 3)-d*dt+1) + LOCAL a, b, c, d, h, old_x, old_y, old_z + old_x = x + old_y = y + old_z = z + x = (-a*dt*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(b*dt*h+old_x)*(2*a*pow(dt, 3)-d*dt+1))/(2*a*pow(dt, 3)-d*dt+1) + y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+old_x)+old_y))/(2*a*pow(dt, 3)-d*dt+1) + z = (-dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)+old_z)/(2*a*pow(dt, 3)-d*dt+1) })"; std::string expected_cse_result = R"( DERIVATIVE states { - LOCAL tmp_x_old, tmp_y_old, tmp_z_old, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 - tmp_x_old = x - tmp_y_old = y - tmp_z_old = z + LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 + old_x = x + old_y = y + old_z = z tmp0 = 2*a tmp1 = -d*dt+pow(dt, 3)*tmp0+1 tmp2 = 1/tmp1 - tmp3 = b*dt*h+tmp_x_old - tmp4 = c*dt+2*dt*tmp3+tmp_y_old - tmp5 = dt*tmp4-tmp_z_old + tmp3 = b*dt*h+old_x + tmp4 = c*dt+2*dt*tmp3+old_y + tmp5 = dt*tmp4-old_z x = -tmp2*(a*dt*tmp5-tmp1*tmp3) y = -tmp2*(pow(dt, 2)*tmp0*tmp5-tmp1*tmp4) z = -tmp2*tmp5 @@ -2733,8 +2733,7 @@ SCENARIO("SympySolver visitor", "[sympy]") { GIVEN("Derivative block including ODES with sparse method (from nmodl paper)") { std::string nmodl_text = R"( STATE { - mc - m + mc m } BREAKPOINT { SOLVE scheme1 METHOD sparse @@ -2746,14 +2745,15 @@ SCENARIO("SympySolver visitor", "[sympy]") { )"; std::string expected_result = R"( DERIVATIVE scheme1 { - LOCAL tmp_mc_old, tmp_m_old - tmp_mc_old = mc - tmp_m_old = m - mc = (b*dt*tmp_m_old+b*dt*tmp_mc_old+tmp_mc_old)/(a*dt+b*dt+1) - m = (a*dt*tmp_m_old+a*dt*tmp_mc_old+tmp_m_old)/(a*dt+b*dt+1) + LOCAL old_mc, old_m + old_mc = mc + old_m = m + mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) + m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) })"; THEN("Construct & solver linear system") { + CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); @@ -2761,6 +2761,9 @@ SCENARIO("SympySolver visitor", "[sympy]") { } GIVEN("Derivative block including ODES with derivimplicit method") { std::string nmodl_text = R"( + STATE { + m h n + } BREAKPOINT { SOLVE states METHOD derivimplicit } @@ -2774,32 +2777,40 @@ SCENARIO("SympySolver visitor", "[sympy]") { /// new derivative block with EigenNewtonSolverBlock node std::string expected_result = R"( DERIVATIVE states { - rates(v) - { + EIGEN_NEWTON_SOLVE[3]{ + LOCAL old_m, old_h, old_n + }{ + rates(v) + old_m = m + old_h = h + old_n = n + }{ X[0] = m X[1] = h X[2] = n }{ - F[0] = (dt*(X[0]+3*X[1]*mtau-minf)+mtau*(X[0]-m))/mtau - F[1] = (-dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(X[1]-h))/htau - F[2] = (dt*(X[2]-ninf)+ntau*(X[2]-n))/ntau - J[0] = dt/mtau+1 - J[3] = 3*dt + F[0] = (-dt*(X[0]+3*X[1]*mtau-minf)+mtau*(-X[0]+old_m))/mtau + F[1] = (dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(-X[1]+old_h))/htau + F[2] = (-dt*(X[2]-ninf)+ntau*(-X[2]+old_n))/ntau + J[0] = -(dt+mtau)/mtau + J[3] = -3*dt J[6] = 0 - J[1] = -2*X[0]*dt - J[4] = dt/htau+1 + J[1] = 2*X[0]*dt + J[4] = -(dt+htau)/htau J[7] = 0 J[2] = 0 J[5] = 0 - J[8] = dt/ntau+1 + J[8] = -(dt+ntau)/ntau }{ m = X[0] h = X[1] n = X[2] + }{ } })"; THEN("Construct & solver linear system using newton solver") { + CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); @@ -2807,6 +2818,9 @@ SCENARIO("SympySolver visitor", "[sympy]") { } GIVEN("Multiple derivative blocks each with derivimplicit method") { std::string nmodl_text = R"( + STATE { + m h + } BREAKPOINT { SOLVE states1 METHOD derivimplicit SOLVE states2 METHOD derivimplicit @@ -2825,42 +2839,55 @@ SCENARIO("SympySolver visitor", "[sympy]") { /// EigenNewtonSolverBlock in each derivative block std::string expected_result_0 = R"( DERIVATIVE states1 { - { + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_m, old_h + }{ + old_m = m + old_h = h + }{ X[0] = m X[1] = h }{ - F[0] = (dt*(X[0]-minf)+mtau*(X[0]-m))/mtau - F[1] = (-dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(X[1]-h))/htau - J[0] = dt/mtau+1 + F[0] = (-dt*(X[0]-minf)+mtau*(-X[0]+old_m))/mtau + F[1] = (dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(-X[1]+old_h))/htau + J[0] = -(dt+mtau)/mtau J[2] = 0 - J[1] = -2*X[0]*dt - J[3] = dt/htau+1 + J[1] = 2*X[0]*dt + J[3] = -(dt+htau)/htau }{ m = X[0] h = X[1] + }{ } })"; std::string expected_result_1 = R"( DERIVATIVE states2 { - { - X[0] = h - X[1] = m + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_h, old_m }{ - F[0] = (-dt*(-X[0]+pow(X[1], 2)*htau+hinf)+htau*(X[0]-h))/htau - F[1] = (-dt*(X[0]*mtau-X[1]+minf)+mtau*(X[1]-m))/mtau - J[0] = dt/htau+1 - J[2] = -2*X[1]*dt - J[1] = -dt - J[3] = dt/mtau+1 + old_h = h + old_m = m + }{ + X[0] = m + X[1] = h + }{ + F[0] = (dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(-X[1]+old_h))/htau + F[1] = (dt*(-X[0]+X[1]*mtau+minf)+mtau*(-X[0]+old_m))/mtau + J[0] = 2*X[0]*dt + J[2] = -(dt+htau)/htau + J[1] = -(dt+mtau)/mtau + J[3] = dt + }{ + m = X[0] + h = X[1] }{ - h = X[0] - m = X[1] } })"; THEN("Construct & solver linear system using newton solver") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + CAPTURE(nmodl_text); REQUIRE(result[0] == reindent_text(expected_result_0)); REQUIRE(result[1] == reindent_text(expected_result_1)); } @@ -3737,7 +3764,7 @@ SCENARIO("SympyConductance visitor", "[sympy]") { ica_NMDA = Pf_NMDA*g_NMDA*(v-40.0) gca_bar_abs_VDCC = gca_bar_VDCC * 4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^(2/3) gca_VDCC = (1e-3) * gca_bar_abs_VDCC * m_VDCC * m_VDCC * h_VDCC - Eca_syn = nernst(cai_CR, cao_CR, 2) + Eca_syn = FARADAY*nernst(cai_CR, cao_CR, 2) ica_VDCC = gca_VDCC*(v-Eca_syn) vsyn = v i = i_AMPA + i_NMDA + ica_VDCC @@ -3765,7 +3792,7 @@ SCENARIO("SympyConductance visitor", "[sympy]") { z_in_0 = 2 nernst_in_0 = 1000*R*(celsius+273.15)/(z_in_0*FARADAY)*log(co_in_0/ci_in_0) } - Eca_syn = nernst_in_0 + Eca_syn = FARADAY*nernst_in_0 ica_VDCC = gca_VDCC*(v-Eca_syn) vsyn = v i = i_AMPA+i_NMDA+ica_VDCC @@ -3941,6 +3968,443 @@ TEST_CASE("Constant Folding Visitor") { } } +//============================================================================= +// LINEAR solve block tests +//============================================================================= + +SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { + GIVEN("1 state-var numeric LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x + } + LINEAR lin { + ~ x = 5 + })"; + std::string expected_text = R"( + LINEAR lin { + x = 5 + })"; + THEN("solve analytically") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("1 state-var symbolic LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x + } + LINEAR lin { + ~ 2*a*x = 1 + })"; + std::string expected_text = R"( + LINEAR lin { + x = 0.5/a + })"; + THEN("solve analytically") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("2 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + ~ x + 4*y = 5*a + ~ x - y = 0 + })"; + std::string expected_text = R"( + LINEAR lin { + x = a + y = a + })"; + THEN("solve analytically") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("2 state-var LINEAR solve block, post-solve statements") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + ~ x + 4*y = 5*a + ~ x - y = 0 + x = x + 2 + y = y - a + })"; + std::string expected_text = R"( + LINEAR lin { + x = a + y = a + x = x+2 + y = y-a + })"; + THEN("solve analytically, insert in correct location") { + CAPTURE(reindent_text(nmodl_text)); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("2 state-var LINEAR solve block, mixed & post-solve statements") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + ~ x + 4*y = 5*a + a2 = 3*b + ~ x - y = 0 + y = y - a + })"; + std::string expected_text = R"( + LINEAR lin { + a2 = 3*b + x = a + y = a + y = y-a + })"; + THEN("solve analytically, insert in correct location") { + CAPTURE(reindent_text(nmodl_text)); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("3 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x y z + } + LINEAR lin { + ~ x + 4*c*y = -5.343*a + ~ a + x/b + z - y = 0.842*b*b + ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c + })"; + std::string expected_text = R"( + LINEAR lin { + x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)+(1*b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(1*b+4*c)-4*c*(5.343*a+b*(-1*a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + y = (1*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-1*pow(a, 2)*pow(b, 2)*(1*b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + z = pow(a, 2)*b*(c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-(1*b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + })"; + THEN("solve analytically") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("4 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + w x y z + } + LINEAR lin { + ~ w + z/3.2 = -2.0*y + ~ x + 4*c*y = -5.343*a + ~ a + x/b + z - y = 0.842*b*b + ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c + })"; + std::string expected_text = R"( + LINEAR lin { + EIGEN_LINEAR_SOLVE[4]{ + }{ + }{ + X[0] = w + X[1] = x + X[2] = y + X[3] = z + F[0] = 0 + F[1] = 5.343*a + F[2] = a-0.842*pow(b, 2) + F[3] = -1.43543/c + J[0] = -1 + J[4] = 0 + J[8] = -2 + J[12] = -0.3125 + J[1] = 0 + J[5] = -1 + J[9] = -4*c + J[13] = 0 + J[2] = 0 + J[6] = -1/b + J[10] = 1 + J[14] = -1 + J[3] = 0 + J[7] = -1 + J[11] = -1.3 + J[15] = 0.1/(pow(a, 2)*b) + }{ + w = X[0] + x = X[1] + y = X[2] + z = X[3] + }{ + } + })"; + THEN("return matrix system to solve") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("12 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + C1 C2 C3 C4 C5 I1 I2 I3 I4 I5 I6 O + } + LINEAR seqinitial { + ~ I1*bi1 + C2*b01 - C1*( fi1+f01) = 0 + ~ C1*f01 + I2*bi2 + C3*b02 - C2*(b01+fi2+f02) = 0 + ~ C2*f02 + I3*bi3 + C4*b03 - C3*(b02+fi3+f03) = 0 + ~ C3*f03 + I4*bi4 + C5*b04 - C4*(b03+fi4+f04) = 0 + ~ C4*f04 + I5*bi5 + O*b0O - C5*(b04+fi5+f0O) = 0 + ~ C5*f0O + I6*bin - O*(b0O+fin) = 0 + ~ C1*fi1 + I2*b11 - I1*( bi1+f11) = 0 + ~ I1*f11 + C2*fi2 + I3*b12 - I2*(b11+bi2+f12) = 0 + ~ I2*f12 + C3*fi3 + I4*bi3 - I3*(b12+bi3+f13) = 0 + ~ I3*f13 + C4*fi4 + I5*b14 - I4*(b13+bi4+f14) = 0 + ~ I4*f14 + C5*fi5 + I6*b1n - I5*(b14+bi5+f1n) = 0 + ~ C1 + C2 + C3 + C4 + C5 + O + I1 + I2 + I3 + I4 + I5 + I6 = 1 + })"; + std::string expected_text = R"( + LINEAR seqinitial { + EIGEN_LINEAR_SOLVE[12]{ + }{ + }{ + X[0] = C1 + X[1] = C2 + X[2] = C3 + X[3] = C4 + X[4] = C5 + X[5] = I1 + X[6] = I2 + X[7] = I3 + X[8] = I4 + X[9] = I5 + X[10] = I6 + X[11] = O + F[0] = 0 + F[1] = 0 + F[2] = 0 + F[3] = 0 + F[4] = 0 + F[5] = 0 + F[6] = 0 + F[7] = 0 + F[8] = 0 + F[9] = 0 + F[10] = 0 + F[11] = -1 + J[0] = f01+fi1 + J[12] = -b01 + J[24] = 0 + J[36] = 0 + J[48] = 0 + J[60] = -bi1 + J[72] = 0 + J[84] = 0 + J[96] = 0 + J[108] = 0 + J[120] = 0 + J[132] = 0 + J[1] = -f01 + J[13] = b01+f02+fi2 + J[25] = -b02 + J[37] = 0 + J[49] = 0 + J[61] = 0 + J[73] = -bi2 + J[85] = 0 + J[97] = 0 + J[109] = 0 + J[121] = 0 + J[133] = 0 + J[2] = 0 + J[14] = -f02 + J[26] = b02+f03+fi3 + J[38] = -b03 + J[50] = 0 + J[62] = 0 + J[74] = 0 + J[86] = -bi3 + J[98] = 0 + J[110] = 0 + J[122] = 0 + J[134] = 0 + J[3] = 0 + J[15] = 0 + J[27] = -f03 + J[39] = b03+f04+fi4 + J[51] = -b04 + J[63] = 0 + J[75] = 0 + J[87] = 0 + J[99] = -bi4 + J[111] = 0 + J[123] = 0 + J[135] = 0 + J[4] = 0 + J[16] = 0 + J[28] = 0 + J[40] = -f04 + J[52] = b04+f0O+fi5 + J[64] = 0 + J[76] = 0 + J[88] = 0 + J[100] = 0 + J[112] = -bi5 + J[124] = 0 + J[136] = -b0O + J[5] = 0 + J[17] = 0 + J[29] = 0 + J[41] = 0 + J[53] = -f0O + J[65] = 0 + J[77] = 0 + J[89] = 0 + J[101] = 0 + J[113] = 0 + J[125] = -bin + J[137] = b0O+fin + J[6] = -fi1 + J[18] = 0 + J[30] = 0 + J[42] = 0 + J[54] = 0 + J[66] = bi1+f11 + J[78] = -b11 + J[90] = 0 + J[102] = 0 + J[114] = 0 + J[126] = 0 + J[138] = 0 + J[7] = 0 + J[19] = -fi2 + J[31] = 0 + J[43] = 0 + J[55] = 0 + J[67] = -f11 + J[79] = b11+bi2+f12 + J[91] = -b12 + J[103] = 0 + J[115] = 0 + J[127] = 0 + J[139] = 0 + J[8] = 0 + J[20] = 0 + J[32] = -fi3 + J[44] = 0 + J[56] = 0 + J[68] = 0 + J[80] = -f12 + J[92] = b12+bi3+f13 + J[104] = -bi3 + J[116] = 0 + J[128] = 0 + J[140] = 0 + J[9] = 0 + J[21] = 0 + J[33] = 0 + J[45] = -fi4 + J[57] = 0 + J[69] = 0 + J[81] = 0 + J[93] = -f13 + J[105] = b13+bi4+f14 + J[117] = -b14 + J[129] = 0 + J[141] = 0 + J[10] = 0 + J[22] = 0 + J[34] = 0 + J[46] = 0 + J[58] = -fi5 + J[70] = 0 + J[82] = 0 + J[94] = 0 + J[106] = -f14 + J[118] = b14+bi5+f1n + J[130] = -b1n + J[142] = 0 + J[11] = -1 + J[23] = -1 + J[35] = -1 + J[47] = -1 + J[59] = -1 + J[71] = -1 + J[83] = -1 + J[95] = -1 + J[107] = -1 + J[119] = -1 + J[131] = -1 + J[143] = -1 + }{ + C1 = X[0] + C2 = X[1] + C3 = X[2] + C4 = X[3] + C5 = X[4] + I1 = X[5] + I2 = X[6] + I3 = X[7] + I4 = X[8] + I5 = X[9] + I6 = X[10] + O = X[11] + }{ + } + })"; + THEN("return matrix system to be solved") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } +} + +//============================================================================= +// NONLINEAR solve block tests +//============================================================================= + +SCENARIO("NONLINEAR solve block (SympySolver Visitor)", "[sympy][nonlinear]") { + GIVEN("1 state-var numeric NONLINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x + } + NONLINEAR nonlin { + ~ x = 5 + })"; + std::string expected_text = R"( + NONLINEAR nonlin { + EIGEN_NEWTON_SOLVE[1]{ + }{ + }{ + X[0] = x + }{ + F[0] = -X[0]+5 + J[0] = -1 + }{ + x = X[0] + }{ + } + })"; + THEN("return F & J for newton solver") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::NON_LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } +} //============================================================================= // Loop unroll tests From 9a7e67517a098e01d3451d30ac1434c95bc8e529 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@gmail.com> Date: Mon, 25 Mar 2019 17:47:58 +0100 Subject: [PATCH 177/871] Fixed floating point number output (BlueBrain/nmodl#104) - double precision ispc output was not correct according to ispc's syntax - additionally, updated base codegen to output `lhs [op] rhs` rather than `lhs[op]rhs` for all operators. Resolves BlueBrain/nmodl#103 NMODL Repo SHA: BlueBrain/nmodl@b63ab9a6ba4794e34749dc0b1be98a8403f244ab --- src/nmodl/codegen/codegen_c_visitor.cpp | 7 ++----- src/nmodl/codegen/codegen_ispc_visitor.cpp | 24 +++++++++++++--------- src/nmodl/codegen/fast_math.ispc | 16 +++++++-------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 0c80efcf90..79d09d5a3c 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -236,15 +236,12 @@ void CodegenCVisitor::visit_binary_expression(BinaryExpression* node) { if (op == "^") { printer->add_text("pow("); lhs->accept(this); - printer->add_text(","); + printer->add_text(", "); rhs->accept(this); printer->add_text(")"); } else { - if (op == "=" || op == "&&" || op == "||" || op == "==") { - op = " " + op + " "; - } lhs->accept(this); - printer->add_text(op); + printer->add_text(" " + op + " "); rhs->accept(this); } } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index c42297239b..89c30acbc1 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -61,16 +61,18 @@ std::string CodegenIspcVisitor::double_to_string(double value) { if (std::ceil(value) == value) { return "{:.1f}d"_format(value); } - if ((value <= 1.0) or (value >= -1.0)) { + if ((value <= 1.0) && (value >= -1.0)) { return "{:f}d"_format(value); } else { auto e = std::log10(std::abs(value)); if (e < 0.0) { - e = std::pow(10, std::ceil(-e)); - return "({:f}d / {:.1f}d)"_format(value * e, e); + e = std::ceil(-e); + auto m = std::pow(10, e); + return "{:f}d-{:d}"_format(value * m, static_cast<int>(e)); } else { - e = std::pow(10, std::floor(e)); - return "({:f}d * {:.1f}d)"_format(value / e, e); + e = std::floor(e); + auto m = std::pow(10, e); + return "{:f}d{:d}"_format(value / m, static_cast<int>(e)); } } } @@ -80,16 +82,18 @@ std::string CodegenIspcVisitor::float_to_string(float value) { if (std::ceil(value) == value) { return "{:.1f}"_format(value); } - if ((value <= 1.0f) or (value >= -1.0f)) { + if ((value <= 1.0f) && (value >= -1.0f)) { return "{:f}"_format(value); } else { auto e = std::log10(std::abs(value)); if (e < 0.0f) { - e = std::pow(10, std::ceil(-e)); - return "({:f} / {:.1f})"_format(value * e, e); + e = std::ceil(-e); + auto m = std::pow(10, e); + return "{:f}e-{:d}"_format(value * m, static_cast<int>(e)); } else { - e = std::pow(10, std::floor(e)); - return "({:f} * {:.1f})"_format(value / e, e); + e = std::floor(e); + auto m = std::pow(10, e); + return "{:f}e{:d}"_format(value / m, static_cast<int>(e)); } } } diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc index e937c8792d..0cba8450d7 100644 --- a/src/nmodl/codegen/fast_math.ispc +++ b/src/nmodl/codegen/fast_math.ispc @@ -51,12 +51,12 @@ static inline uniform double inf() { static const uniform double EXP_LIMIT = 708.d; -static const uniform double PX1exp = 1.26177193074810590878d * 0.0001d; -static const uniform double PX2exp = 3.02994407707441961300d * 0.01d; -static const uniform double PX3exp = 9.99999999999999999910d * 0.1d; -static const uniform double QX1exp = 3.00198505138664455042d * 0.000001d; -static const uniform double QX2exp = 2.52448340349684104192d * 0.001d; -static const uniform double QX3exp = 2.27265548208155028766d * 0.1d; +static const uniform double PX1exp = 1.26177193074810590878d-4; +static const uniform double PX2exp = 3.02994407707441961300d-2; +static const uniform double PX3exp = 9.99999999999999999910d-1; +static const uniform double QX1exp = 3.00198505138664455042d-6; +static const uniform double QX2exp = 2.52448340349684104192d-3; +static const uniform double QX3exp = 2.27265548208155028766d-1; static const uniform double QX4exp = 2.00000000000000000009d; static const uniform double LOG2E = 1.4426950408889634073599d; @@ -82,8 +82,8 @@ static inline double vexp(double initial_x) { double px = dpfloor(LOG2E * x + 0.5d); const int32 n = px; - x -= px * (6.93145751953125d * 0.1d); - x -= px * (1.42860682030941723212d * 0.000001d); + x -= px * 6.93145751953125d-1; + x -= px * 1.42860682030941723212d-6; const double xx = x * x; From fa6ce6a48749820b2d607b59dd52bf29abca3896 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Wed, 27 Mar 2019 13:21:16 +0100 Subject: [PATCH 178/871] Add support for array variables (BlueBrain/nmodl#112) - KineticBlockVisitor generates correct ODEs involving array variables - SympySolver can parse & solve ODEs involving array variables - nmodl parser now parses expressions with array prime vars as DiffEqs - cnexp sympy solver has fixed form of solution for constant & linear cases this is to avoid unnecessarily complicated functions including logs that are sometimes generated and that can cause numerical issues NMODL Repo SHA: BlueBrain/nmodl@cf12d88500fd75a70cceafdcdb731a17cc9e61a9 --- nmodl/ode.py | 349 ++++++++++------ src/nmodl/language/codegen.yaml | 2 +- src/nmodl/parser/nmodl.yy | 5 +- src/nmodl/visitors/cnexp_solve_visitor.cpp | 7 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 54 ++- src/nmodl/visitors/kinetic_block_visitor.hpp | 4 + src/nmodl/visitors/sympy_solver_visitor.cpp | 63 ++- src/nmodl/visitors/visitor_utils.cpp | 6 +- test/nmodl/transpiler/ode/test_ode.py | 132 +++++-- test/nmodl/transpiler/visitor/visitor.cpp | 395 +++++++++++++++++-- 10 files changed, 806 insertions(+), 211 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index dfa72ebd33..16dce4892f 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -13,9 +13,26 @@ def _make_unique_prefix(vars, default_prefix="tmp"): + """Generate a unique prefix + + Generates a prefix that doesn't match the first part + of any string in vars. + + Starting point is the supplied default_prefix, if + this doesn't match the first part of any string in vars + it is returned. + + Otherwise, underscores are appended until it doesn't match, + then this string is returned. + + Args: + vars: list of strings that the prefix should not match + default_prefix: desired prefix + + Returns: + the prefix as a string + """ prefix = default_prefix - # generate prefix that doesn't match first part - # of any string in vars while True: for v in vars: # if v is long enough to match prefix @@ -29,20 +46,98 @@ def _make_unique_prefix(vars, default_prefix="tmp"): return prefix -def _sympify_eqs(eq_strings, vars, constants): - # parse eq_strings into sympy expressions - sympy_vars = {constant: sp.symbols(constant, real=True) for constant in constants} - state_vars = [] +def _var_to_sympy(var_str): + """Return sympy variable from string representing variable + + If string contains "[" it is assumed to be an array variable + of the form "variable_name[N]", where N is the size of the array + + Args: + var_str: variable as string + + Returns: + (variable_name_as_string, variable_as_sympy_object) + """ + if "[" in var_str: + # var is an array variable with defined size, e.g. X[5] + var_name = var_str.split("[", 1)[0].strip() + var_len = int(var_str.split("[", 1)[1].split("]", 1)[0].strip()) + # SymPy equivalent of an array is IndexedBase: + return var_name, sp.IndexedBase(var_name, shape=(var_len,), real=True) + else: + # otherwise can use a standard SymPy symbol: + return var_str, sp.symbols(var_str, real=True) + + +def _sympify_diff_eq(diff_string, vars): + """Parse differential equation into sympy expression + + Given eq_string of the form "x' = df/dx", return sympy + objects representing x and df/dx + + If x is an array, then it should be declared in vars + as "x[N]", where N is the size of the array, and an + IndexedBase sympy object will be returned + + Args: + eq_string: string containing differential equation + vars: list of strings containing vars used in equation + + Returns: + x: sympy object representing x + dxdt: sympy expression representing df/dx + """ + sympy_vars = {} for var in vars: - v = sp.symbols(var, real=True) - sympy_vars[var] = v - state_vars.append(v) + var_name, sympy_object = _var_to_sympy(var) + sympy_vars[var_name] = sympy_object + + diff_string_lhs, diff_string_rhs = diff_string.split("=", 1) + + # parse dependent variable from LHS of equation: + x = sp.sympify(diff_string_lhs.replace("'", ""), locals=sympy_vars) + + # parse RHS of equation into SymPy expression + dxdt = sp.sympify(diff_string_rhs, locals=sympy_vars) + + return x, dxdt + + +def _sympify_eqs(eq_strings, state_vars, vars): + """Parse equations into sympy expressions + + Given lists of strings containing equations, state variables, + and constants, it parses the equations into sympy expressions. + + Args: + eq_strings: list of strings containing equations + state_vars: list of strings containing state vars, + if array then index must be specifiec + vars: list of strings containing constant vars + if array then size of array should be specified, e.g. X[10] + + Returns: + eqs: list of sympy expressions + state_vars: list of sympy objects for vars + sympy_vars: dict of name:sympy_object for all vars & constants + """ + + # convert all vars into sympy objects + sympy_vars = {} + for var in vars: + var_name, sympy_object = _var_to_sympy(var) + sympy_vars[var_name] = sympy_object + + # parse state vars using above sympy objects + sympy_state_vars = [] + for state_var in state_vars: + sympy_state_vars.append(sp.sympify(state_var, locals=sympy_vars)) eqs = [ sp.sympify(eq.split("=", 1)[1], locals=sympy_vars) - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars) for eq in eq_strings ] - return eqs, state_vars, sympy_vars + return eqs, sympy_state_vars, sympy_vars def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=False): @@ -66,8 +161,8 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal do_cse: if True, do Common Subexpression Elimination Returns: - List of strings containing assignment statements - List of strings containing new local variables + code: list of strings containing assignment statements + vars: list of strings containing new local variables """ eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) @@ -77,32 +172,35 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal if small_system: # small linear system: solve by gaussian elimination - for rhs in sp.linsolve(eqs, state_vars): - if do_cse: - # generate prefix for new local vars that avoids clashes - prefix = _make_unique_prefix(vars) - my_symbols = sp.utilities.iterables.numbered_symbols(prefix=prefix) - sub_exprs, simplified_rhs = sp.cse( - rhs, symbols=my_symbols, optimizations="basic", order="canonical" - ) - for v, e in sub_exprs: - new_local_vars.append(sp.ccode(v)) - code.append(f"{v} = {sp.ccode(e.evalf())}") - rhs = simplified_rhs[0] - for v, e in zip(state_vars, rhs): - code.append(f"{sp.ccode(v)} = {sp.ccode(e.evalf())}") + solution_vector = sp.linsolve(eqs, state_vars).args[0] + if do_cse: + # generate prefix for new local vars that avoids clashes + prefix = _make_unique_prefix(vars) + my_symbols = sp.utilities.iterables.numbered_symbols(prefix=prefix) + sub_exprs, simplified_solution_vector = sp.cse( + solution_vector, + symbols=my_symbols, + optimizations="basic", + order="canonical", + ) + for var, expr in sub_exprs: + new_local_vars.append(sp.ccode(var)) + code.append(f"{var} = {sp.ccode(expr.evalf())}") + solution_vector = simplified_solution_vector[0] + for var, expr in zip(state_vars, solution_vector): + code.append(f"{sp.ccode(var)} = {sp.ccode(expr.evalf(), contract=False)}") else: # large linear system: construct and return matrix J, vector F such that - # J X(t+dt) = F is the Euler linear system to be solved by e.g. LU factorization + # J X = F is the linear system to be solved for X by e.g. LU factorization matJ, vecF = sp.linear_eq_to_matrix(eqs, state_vars) # construct vector F - for i, v in enumerate(vecF): - code.append(f"F[{i}] = {sp.ccode(v.simplify().evalf())}") + for i, expr in enumerate(vecF): + code.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf())}") # construct matrix J - for i, element in enumerate(matJ): + for i, expr in enumerate(matJ): # todo: fix indexing to be ascending order flat_index = matJ.rows * (i % matJ.rows) + (i // matJ.rows) - code.append(f"J[{flat_index}] = {sp.ccode(element.simplify().evalf())}") + code.append(f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf())}") return code, new_local_vars @@ -127,46 +225,56 @@ def solve_non_lin_system(eq_strings, vars, constants): jacobian = sp.Matrix(eqs).jacobian(state_vars) - Xvecsubs = {x_new: sp.symbols(f"X[{i}]") for i, x_new in enumerate(state_vars)} + X_vec_map = {x: sp.symbols(f"X[{i}]") for i, x in enumerate(state_vars)} code = [] for i, eq in enumerate(eqs): - code.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(Xvecsubs).evalf())}") + code.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf())}") for i, jac in enumerate(jacobian): # todo: fix indexing to be ascending order flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) code.append( - f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(Xvecsubs).evalf())}" + f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(X_vec_map).evalf())}" ) return code -def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): +def integrate2c(diff_string, dt_var, vars, use_pade_approx=False): """Analytically integrate supplied derivative, return solution as C code. - Derivative should be of the form "x' = f(x)", + Given a differential equation of the form x' = f(x), the value of + x at time t+dt is found in terms of the value of x at time t: + x(t + dt) = g( x(t), dt ) + and this equation is returned in the format NEURON expects: + x = g( x, dt ), + where the x on the right is the current value of x at time t, + and the x on the left is the new value of x at time t+dt + + The derivative should be of the form "x' = f(x)", and vars should contain the set of all the variables referenced by f(x), for example: - -``integrate2c ("x' = a*x", "a")`` - -``integrate2c ("x' = a + b*x - sin(3.2)", {"a","b"})`` + -``integrate2c("x' = a*x", "dt", {"a"})`` + -``integrate2c("x' = a + b*x - sin(3.2)", "dt", {"a","b"})`` + + Optionally, the analytic result can be expanded in powers of dt, + and the (1,1) Pade approximant to the solution returned. + This approximate solution is correct to second order in dt. Args: - diff_string: Derivative to be integrated e.g. "x' = a*x" - t_var: name of time variable in NEURON - dt_var: name of dt variable in NEURON - vars: set of variables used in expression, e.g. {"x", "a"} + diff_string: Derivative to be integrated e.g. "x' = a*x + b" + t_var: name of time variable t in NEURON + dt_var: name of timestep variable dt in NEURON + vars: set of variables used in expression, e.g. {"a", "b"} use_pade_approx: if False: return exact solution if True: return (1,1) Pade approx to solution correct to second order in dt_var Returns: - String containing analytic integral of derivative as C code - + string containing analytic integral of derivative as C code Raises: - NotImplementedError: if ODE is too hard, or if fails to solve it. - ImportError: if SymPy version is too old (<1.2) + NotImplementedError: if the ODE is too hard, or if it fails to solve it. """ # only try to solve ODEs that are not too hard @@ -180,44 +288,48 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): "1st_linear_Integral", } - # every symbol (a.k.a variable) that SymPy - # is going to manipulate needs to be declared - # explicitly - sympy_vars = {} - t = sp.symbols(t_var, real=True, positive=True) - vars = set(vars) - vars.discard(t_var) + x, dxdt = _sympify_diff_eq(diff_string, vars) + # set up differential equation d(x(t))/dt = ... + # where the function x_t = x(t) is substituted for the symbol x # the dependent variable is a function of t - # we use the python variable name x for this - dependent_var = diff_string.split("=")[0].split("'")[0].strip() - x = sp.Function(dependent_var, real=True)(t) - vars.discard(dependent_var) - # declare all other supplied variables - sympy_vars = {var: sp.symbols(var) for var in vars} - sympy_vars[dependent_var] = x - sympy_vars[t_var] = t + t = sp.Dummy("t", real=True, positive=True) + x_t = sp.Function("x(t)", real=True)(t) + diffeq = sp.Eq(x_t.diff(t), dxdt.subs({x: x_t})) - # parse string into SymPy equation - diffeq = sp.Eq( - x.diff(t), sp.sympify(diff_string.split("=", 1)[1], locals=sympy_vars) - ) - - # classify ODE, if it is too hard then exit - ode_properties = set(sp.classify_ode(diffeq)) - if not ode_properties_require_all <= ode_properties: - raise NotImplementedError("ODE too hard") - if len(ode_properties_require_one_of & ode_properties) == 0: - raise NotImplementedError("ODE too hard") - - # try to find analytic solution + # for simple linear case write down solution in preferred form: dt = sp.symbols(dt_var, real=True, positive=True) - x_0 = sp.symbols(dependent_var, real=True) - # note dsolve can return a list of solutions, in which case this fails: - solution = sp.dsolve(diffeq, x, ics={x.subs({t: 0}): x_0}).subs({t: dt}).rhs + solution = None + c1 = dxdt.diff(x).simplify() + if c1 == 0: + # constant equation: + # x' = c0 + # x(t+dt) = x(t) + c0 * dt + solution = (x + dt * dxdt).simplify() + elif c1.diff(x) == 0: + # linear equation: + # x' = c0 + c1*x + # x(t+dt) = (-c0 + (c0 + c1*x(t))*exp(c1*dt))/c1 + c0 = (dxdt - c1 * x).simplify() + solution = (-c0 / c1).simplify() + (c0 + c1 * x).simplify() * sp.exp( + c1 * dt + ) / c1 + else: + # otherwise try to solve ODE with sympy: + # first classify ODE, if it is too hard then exit + ode_properties = set(sp.classify_ode(diffeq)) + if not ode_properties_require_all <= ode_properties: + raise NotImplementedError("ODE too hard") + if len(ode_properties_require_one_of & ode_properties) == 0: + raise NotImplementedError("ODE too hard") + # try to find analytic solution, with initial condition x_t(t=0) = x + # (note dsolve can return a list of solutions, in which case this currently fails) + solution = sp.dsolve(diffeq, x_t, ics={x_t.subs({t: 0}): x}) + # evaluate solution at x(dt), extract rhs of expression + solution = solution.subs({t: dt}).rhs.simplify() if use_pade_approx: - # (1,1) order pade approximant, correct to 2nd order in dt, - # constructed from coefficients of 2nd order taylor expansion + # (1,1) order Pade approximant, correct to 2nd order in dt, + # constructed from the coefficients of 2nd order Taylor expansion taylor_series = sp.Poly(sp.series(solution, dt, 0, 3).removeO(), dt) _a0 = taylor_series.nth(0) _a1 = taylor_series.nth(1) @@ -225,9 +337,40 @@ def integrate2c(diff_string, t_var, dt_var, vars, use_pade_approx=False): solution = ( (_a0 * _a1 + (_a1 * _a1 - _a0 * _a2) * dt) / (_a1 - _a2 * dt) ).simplify() + # special case where above form gives 0/0 = NaN + if _a1 == 0 and _a2 == 0: + solution = _a0 + + # return result as C code in NEURON format: + # - in the lhs x_0 refers to the state var at time (t+dt) + # - in the rhs x_0 refers to the state var at time t + return f"{sp.ccode(x)} = {sp.ccode(solution.evalf())}" + + +def forwards_euler2c(diff_string, dt_var, vars): + """Return forwards euler solution of diff_string as C code. + + Derivative should be of the form "x' = f(x)", + and vars should contain the set of all the variables + referenced by f(x), for example: + -forwards_euler2c("x' = a*x", ["a","x"]) + -forwards_euler2c("x' = a + b*x - sin(3.2)", {"x","a","b"}) + + Args: + diff_string: Derivative to be integrated e.g. "x' = a*x" + dt_var: name of timestep dt variable in NEURON + vars: set of variables used in expression, e.g. {"x", "a"} + + Returns: + String containing forwards Euler timestep as C code + """ + x, dxdt = _sympify_diff_eq(diff_string, vars) + # forwards Euler solution is x + dx/dt * dt + dt = sp.symbols(dt_var, real=True, positive=True) + solution = (x + dxdt * dt).simplify().evalf() # return result as C code in NEURON format - return f"{sp.ccode(x_0)} = {sp.ccode(solution)}" + return f"{sp.ccode(x)} = {sp.ccode(solution)}" def differentiate2c(expression, dependent_var, vars, prev_expressions=None): @@ -259,8 +402,8 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): to evaluate & substitute, e.g. ["b = x + c", "a = 12*b"] Returns: - String containing analytic derivative of expression (including any substitutions - of variables from supplied prev_expressions) w.r.t dependent_var as C code. + string containing analytic derivative of expression (including any substitutions + of variables from supplied prev_expressions) w.r.t. dependent_var as C code. """ prev_expressions = prev_expressions or [] # every symbol (a.k.a variable) that SymPy @@ -335,45 +478,3 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): # return result as C code in NEURON format return sp.ccode(diff.evalf()) - - -def forwards_euler2c(diff_string, dt_var, vars): - """Return forwards euler solution of diff_string as C code. - - Derivative should be of the form "x' = f(x)", - and vars should contain the set of all the variables - referenced by f(x), for example: - - - ``forwards_euler2c("x' = a*x", "a")`` - - ``forwards_euler2c("x' = a + b*x - sin(3.2)", {"a","b"})`` - - Args: - diff_string: Derivative to be integrated e.g. "x' = a*x" - dt_var: name of dt variable in NEURON - vars: set of variables used in expression, e.g. {"x", "a"} - - Returns: - String containing forwards Euler timestep as C code - """ - - # every symbol (a.k.a variable) that SymPy - # is going to manipulate needs to be declared - # explicitly - sympy_vars = {} - vars = set(vars) - dependent_var = diff_string.split("=")[0].split("'")[0].strip() - x = sp.symbols(dependent_var, real=True) - vars.discard(dependent_var) - # declare all other supplied variables - sympy_vars = {var: sp.symbols(var, real=True) for var in vars} - sympy_vars[dependent_var] = x - - # parse string into SymPy equation - diffeq_rhs = sp.sympify(diff_string.split("=", 1)[1], locals=sympy_vars) - - # forwards Euler solution is x + dx/dt * dt - dt = sp.symbols(dt_var, real=True, positive=True) - solution = (x + diffeq_rhs * dt).simplify().evalf() - - # return result as C code in NEURON format - return f"{sp.ccode(x)} = {sp.ccode(solution)}" diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 796c20ea5c..2e4f89937f 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -65,7 +65,7 @@ type: StatementBlock - finalize_block: description: "Statement block to be executed after calling newton solver" - type: StatementBlock + type: StatementBlock - EigenLinearSolverBlock: description: "Represent linear solver solution block based on Eigen" nmodl: "EIGEN_LINEAR_SOLVE" diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 225e764a0a..560d8ac393 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1010,7 +1010,10 @@ asgn : varname "=" expr { auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ASSIGN), $3); auto name = $1->get_name(); - if (name->is_prime_name()) { + if ((name->is_prime_name()) || + (name->is_indexed_name() && + std::dynamic_pointer_cast<ast::IndexedName>(name)->get_name()->is_prime_name())) + { $$ = new ast::DiffEqExpression(expression); } else { $$ = expression; diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/cnexp_solve_visitor.cpp index 5b2a47d52c..7f9a748bf3 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/cnexp_solve_visitor.cpp @@ -55,7 +55,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); if (name->is_prime_name()) { - auto equation = to_nmodl(node); + auto equation = to_nmodl(node); parser::DiffeqDriver diffeq_driver; if (solve_method == codegen::naming::CNEXP_METHOD) { @@ -70,7 +70,8 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { - logger->warn("CnexpSolveVisitor :: cnexp solver not possible for {}", to_nmodl(node)); + logger->warn("CnexpSolveVisitor :: cnexp solver not possible for {}", + to_nmodl(node)); } } else if (solve_method == codegen::naming::EULER_METHOD) { std::string solution = diffeq_driver.solve(equation, solve_method); @@ -91,7 +92,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { program_symtab->insert(symbol); } } else { - logger->error("CnexpSolveVisitor :: solver method '{}' not supported", solve_method); + logger->error("CnexpSolveVisitor :: solver method '{}' not supported", solve_method); } } } diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 154260a3dd..3153964bb7 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -104,7 +104,7 @@ void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName* node) { // var_name is the state variable which we convert to an index // integer is the value to be added to the stochiometric matrix at this index if (in_reaction_statement) { - auto varname = node->get_node_name(); + auto varname = to_nmodl(node->get_name().get()); int count = node->get_value() ? node->get_value()->eval() : 1; process_reac_var(varname, count); } @@ -133,7 +133,6 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) single_state_var = false; } } - if (!lhs->is_react_var_name() || !single_state_var) { logger->warn( "KineticBlockVisitor :: LHS of \"<<\" reaction statement must be a single state " @@ -142,7 +141,7 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) return; } auto rhs = node->get_expression1(); - std::string varname = std::dynamic_pointer_cast<ast::ReactVarName>(lhs)->get_node_name(); + std::string varname = to_nmodl(lhs.get()); // get index of state var const auto it = state_var_index.find(varname); if (it != state_var_index.cend()) { @@ -197,6 +196,15 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) ++i_statement; } +void KineticBlockVisitor::visit_statement_block(ast::StatementBlock* node) { + auto prev_statement_block = current_statement_block; + current_statement_block = node; + node->visit_children(this); + // remove processed statements from current statement block + remove_statements_from_block(current_statement_block, statements_to_remove); + current_statement_block = prev_statement_block; +} + void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { rate_eqs.nu_L.clear(); rate_eqs.nu_R.clear(); @@ -275,7 +283,13 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { } // if rhs of ODE is not empty, add to list of ODEs if (!ode_rhs.empty()) { - odes.push_back("{}' = {}"_format(state_var[j], ode_rhs)); + auto state_var_split = stringutils::split_string(state_var[j], '['); + std::string var_str = state_var_split[0]; + std::string index_str; + if (state_var_split.size() > 1) { + index_str = "[" + state_var_split[1]; + } + odes.push_back("{}'{} = {}"_format(var_str, index_str, ode_rhs)); } } @@ -283,13 +297,13 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { logger->debug("KineticBlockVisitor :: ode : {}", ode); } - auto current_statement_block = node->get_statement_block(); - // remove reaction statements from kinetic block - remove_statements_from_block(current_statement_block.get(), statements_to_remove); + auto kinetic_statement_block = node->get_statement_block(); + // remove any remaining kinetic statements + remove_statements_from_block(kinetic_statement_block.get(), statements_to_remove); // add new statements for (const auto& ode: odes) { logger->debug("KineticBlockVisitor :: -> adding statement: {}", ode); - current_statement_block->addStatement(create_statement(ode)); + kinetic_statement_block->addStatement(create_statement(ode)); } // store pointer to kinetic block @@ -297,6 +311,9 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { } void KineticBlockVisitor::visit_program(ast::Program* node) { + statements_to_remove.clear(); + current_statement_block = nullptr; + // get state variables - assign an index to each state_var_index.clear(); state_var.clear(); @@ -304,10 +321,23 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { if (auto symtab = node->get_symbol_table()) { auto statevars = symtab->get_variables_with_properties(NmodlType::state_var); for (const auto& v: statevars) { - const auto& varname = v->get_name(); - logger->debug("state_var_index[{}] = {}", varname, state_var_count); - state_var_index[varname] = state_var_count++; - state_var.push_back(varname); + std::string var_name = v->get_name(); + if (v->is_array()) { + // for array state vars we need to add each element of the array separately + var_name += "["; + for (int i = 0; i < v->get_length(); ++i) { + std::string var_name_i = var_name + std::to_string(i) + "]"; + logger->debug("KineticBlockVisitor :: state_var_index[{}] = {}", var_name_i, + state_var_count); + state_var_index[var_name_i] = state_var_count++; + state_var.push_back(var_name_i); + } + } else { + logger->debug("KineticBlockVisitor :: state_var_index[{}] = {}", var_name, + state_var_count); + state_var_index[var_name] = state_var_count++; + state_var.push_back(var_name); + } } } diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 8010a12067..413fa5716b 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -89,6 +89,9 @@ class KineticBlockVisitor: public AstVisitor { /// statements to remove from block std::set<ast::Node*> statements_to_remove; + /// current statement block being visited + ast::StatementBlock* current_statement_block = nullptr; + public: KineticBlockVisitor() = default; @@ -97,6 +100,7 @@ class KineticBlockVisitor: public AstVisitor { void visit_reaction_statement(ast::ReactionStatement* node) override; void visit_conserve(ast::Conserve* node) override; void visit_compartment(ast::Compartment* node) override; + void visit_statement_block(ast::StatementBlock* node) override; void visit_kinetic_block(ast::KineticBlock* node) override; void visit_program(ast::Program* node) override; }; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index a7f9ceb7aa..687a347cde 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -35,7 +35,11 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { if (auto symtab = node->get_statement_block()->get_symbol_table()) { auto localvars = symtab->get_variables_with_properties(NmodlType::local_var); for (const auto& localvar: localvars) { - vars.insert(localvar->get_name()); + std::string var_name = localvar->get_name(); + if (localvar->is_array()) { + var_name += "[" + std::to_string(localvar->get_length()) + "]"; + } + vars.insert(var_name); } } } @@ -173,7 +177,6 @@ void SympySolverVisitor::construct_eigen_solver_block( auto solver_block = std::make_shared<ast::EigenNewtonSolverBlock>( n_state_vars, variable_block, initialize_block, setup_x_block, functor_block, update_state_block, finalize_block); - /// replace statement block with solver block as it contains all statements ast::StatementVector solver_block_statements{ std::make_shared<ast::ExpressionStatement>(solver_block)}; @@ -288,6 +291,14 @@ void SympySolverVisitor::solve_non_linear_system( void SympySolverVisitor::visit_var_name(ast::VarName* node) { if (collect_state_vars) { std::string var_name = node->get_node_name(); + if (node->get_name()->is_indexed_name()) { + auto index_name = std::dynamic_pointer_cast<ast::IndexedName>(node->get_name()); + var_name += + "[" + + std::to_string( + std::dynamic_pointer_cast<ast::Integer>(index_name->get_length())->eval()) + + "]"; + } // if var_name is a state var, add it to set if (std::find(all_state_vars.cbegin(), all_state_vars.cend(), var_name) != all_state_vars.cend()) { @@ -306,7 +317,9 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { return; } auto lhs_name = std::dynamic_pointer_cast<ast::VarName>(lhs)->get_name(); - if (!lhs_name->is_prime_name()) { + if ((lhs_name->is_indexed_name() && + !std::dynamic_pointer_cast<ast::IndexedName>(lhs_name)->get_name()->is_prime_name()) || + (!lhs_name->is_indexed_name() && !lhs_name->is_prime_name())) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a PrimeName"); return; } @@ -315,7 +328,6 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { const auto node_as_nmodl = to_nmodl_for_sympy(node); const auto locals = py::dict("equation_string"_a = node_as_nmodl, - "t_var"_a = codegen::naming::NTHREAD_T_VARIABLE, "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, "use_pade_approx"_a = use_pade_approx); @@ -344,7 +356,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { from nmodl.ode import integrate2c exception_message = "" try: - solution = integrate2c(equation_string, t_var, dt_var, vars, use_pade_approx) + solution = integrate2c(equation_string, dt_var, vars, use_pade_approx) except Exception as e: # if we fail, fail silently and return empty string solution = "" @@ -354,11 +366,19 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { } else { // for other solver methods: just collect the ODEs & return std::string eq_str = to_nmodl_for_sympy(node); - std::string state_var_name = lhs_name->get_node_name(); + std::string var_name = lhs_name->get_node_name(); + if (lhs_name->is_indexed_name()) { + auto index_name = std::dynamic_pointer_cast<ast::IndexedName>(lhs_name); + var_name += + "[" + + std::to_string( + std::dynamic_pointer_cast<ast::Integer>(index_name->get_length())->eval()) + + "]"; + } logger->debug("SympySolverVisitor :: adding ODE system: {}", eq_str); eq_system.push_back(eq_str); - logger->debug("SympySolverVisitor :: adding state var: {}", state_var_name); - state_vars_in_block.insert(state_var_name); + logger->debug("SympySolverVisitor :: adding state var: {}", var_name); + state_vars_in_block.insert(var_name); expression_statements.insert(current_expression_statement); last_expression_statement = current_expression_statement; return; @@ -396,22 +416,28 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { if (eq_system_is_valid && !eq_system.empty()) { // solve system of ODEs in eq_system logger->debug("SympySolverVisitor :: Solving {} system of ODEs", solve_method); - // construct implicit Euler equations from ODEs std::vector<std::string> pre_solve_statements; for (auto& eq: eq_system) { auto split_eq = stringutils::split_string(eq, '='); auto x_prime_split = stringutils::split_string(split_eq[0], '\''); auto x = stringutils::trim(x_prime_split[0]); + std::string x_array_index = ""; + std::string x_array_index_i = ""; + if (x_prime_split.size() > 1 && stringutils::trim(x_prime_split[1]).size() > 2) { + x_array_index = stringutils::trim(x_prime_split[1]); + x_array_index_i = "_" + x_array_index.substr(1, x_array_index.size() - 2); + } auto dxdt = stringutils::trim(split_eq[1]); - auto old_x = "old_" + x; // TODO: do this properly, check name is unique + auto old_x = "old_" + x + x_array_index_i; // TODO: do this properly, + // check name is unique // declare old_x logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); add_local_variable(block_with_expression_statements, old_x); // assign old_x = x - pre_solve_statements.push_back(old_x + " = " + x); - eq = x + " = " + old_x + " + " + codegen::naming::NTHREAD_DT_VARIABLE + " * (" + dxdt + - ")"; + pre_solve_statements.push_back(old_x + " = " + x + x_array_index); + eq = x + x_array_index + " = " + old_x + " + " + codegen::naming::NTHREAD_DT_VARIABLE + + " * (" + dxdt + ")"; logger->debug("SympySolverVisitor :: -> constructed euler eq: {}", eq); } @@ -521,8 +547,15 @@ void SympySolverVisitor::visit_program(ast::Program* node) { if (auto symtab = node->get_symbol_table()) { auto statevars = symtab->get_variables_with_properties(NmodlType::state_var); for (const auto& v: statevars) { - const auto& varname = v->get_name(); - all_state_vars.push_back(varname); + std::string var_name = v->get_name(); + if (v->is_array()) { + for (int i = 0; i < v->get_length(); ++i) { + std::string var_name_i = var_name + "[" + std::to_string(i) + "]"; + all_state_vars.push_back(var_name_i); + } + } else { + all_state_vars.push_back(var_name); + } } } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 9a5a76b6d2..6f1422ebbc 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -134,7 +134,11 @@ std::set<std::string> get_global_vars(Program* node) { NmodlType::electrode_cur_var | NmodlType::section_var | NmodlType::constant_var | NmodlType::extern_neuron_variable | NmodlType::state_var | NmodlType::factor_def; for (const auto& globalvar: symtab->get_variables_with_properties(property)) { - vars.insert(globalvar->get_name()); + std::string var_name = globalvar->get_name(); + if (globalvar->is_array()) { + var_name += "[" + std::to_string(globalvar->get_length()) + "]"; + } + vars.insert(var_name); } } return vars; diff --git a/test/nmodl/transpiler/ode/test_ode.py b/test/nmodl/transpiler/ode/test_ode.py index 515d3a10cd..50b57c755a 100644 --- a/test/nmodl/transpiler/ode/test_ode.py +++ b/test/nmodl/transpiler/ode/test_ode.py @@ -5,13 +5,43 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** -from nmodl.ode import differentiate2c, _make_unique_prefix +from nmodl.ode import _make_unique_prefix, differentiate2c, integrate2c + +import sympy as sp + + +def _equivalent( + lhs, rhs, vars=["a", "b", "c", "d", "e", "f", "v", "w", "x", "y", "z", "t", "dt"] +): + """Helper function to test equivalence of analytic expressions + Analytic expressions can often be written in many different, + but mathematically equivalent ways. This helper function uses + SymPy to check if two analytic expressions are equivalent. + If the expressions contain an "=", each is split into two expressions, + and the two pairs of expressions are compared, i.e. + _equivalent("a=b+c", "a=c+b") is the same thing as doing + _equivalent("a", "a") and _equivalent("b+c", "c+b") + Args: + lhs: first expression, e.g. "x*(1-a)" + rhs: second expression, e.g. "-a*x + x" + vars: list of variables used in expressions, e.g. ["a", "x"] + Returns: + True if expressions are equivalent, False if they are not + """ + lhs = lhs.replace("pow(", "Pow(") + rhs = rhs.replace("pow(", "Pow(") + sympy_vars = {var: sp.symbols(var, real=True) for var in vars} + for l, r in zip(lhs.split("=", 1), rhs.split("=", 1)): + eq_l = sp.sympify(l, locals=sympy_vars) + eq_r = sp.sympify(r, locals=sympy_vars) + difference = (eq_l - eq_r).evalf().simplify() + if difference != 0: + return False + return True def test_make_unique_prefix(): - # if prefix matches start of any var, append underscores - # to prefix until this is not true assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "z") == "z" assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "a") == "a_" assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "az") == "az" @@ -23,50 +53,94 @@ def test_make_unique_prefix(): assert _make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmpvar") == "tmpvar" -def test_differentiation(): +def test_differentiate2c(): # simple examples, no prev_expressions - assert differentiate2c("0", "x", "") == "0" - assert differentiate2c("x", "x", "") == "1.0" - assert differentiate2c("a", "x", "a") == "0" - assert differentiate2c("a*x", "x", "a") == "a" - assert differentiate2c("a*x", "a", "x") == "x" - assert differentiate2c("a*x", "y", {"x", "y"}) == "0" - assert differentiate2c("a*x + b*x*x", "x", {"a", "b"}) == "a + 2.0*b*x" - assert differentiate2c("a*cos(x+b)", "x", {"a", "b"}) == "-a*sin(b + x)" - assert ( - differentiate2c("a*cos(x+b) + c*x*x", "x", {"a", "b", "c"}) - == "-a*sin(b + x) + 2.0*c*x" + assert _equivalent(differentiate2c("0", "x", ""), "0") + assert _equivalent(differentiate2c("x", "x", ""), "1") + assert _equivalent(differentiate2c("a", "x", "a"), "0") + assert _equivalent(differentiate2c("a*x", "x", "a"), "a") + assert _equivalent(differentiate2c("a*x", "a", "x"), "x") + assert _equivalent(differentiate2c("a*x", "y", {"x", "y"}), "0") + assert _equivalent(differentiate2c("a*x + b*x*x", "x", {"a", "b"}), "2*b*x+a") + assert _equivalent( + differentiate2c("a*cos(x+b)", "x", {"a", "b"}), "-a * sin(b + x)" + ) + assert _equivalent( + differentiate2c("a*cos(x+b) + c*x*x", "x", {"a", "b", "c"}), + "-a*sin(b+x) + 2*c*x", ) # single prev_expression to substitute - assert differentiate2c("a*x + b", "x", {"a", "b", "c", "d"}, ["c = sqrt(d)"]) == "a" - assert differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x"]) == "a + 2.0" + assert _equivalent( + differentiate2c("a*x + b", "x", {"a", "b", "c", "d"}, ["c = sqrt(d)"]), "a" + ) + assert _equivalent( + differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x"]), "a + 2" + ) # multiple prev_eqs to substitute # (these statements should be in the same order as in the mod file) - assert differentiate2c("a*x + b", "x", {"a", "b", "c"}, ["b = 2*x", "a = 2*x*x*c"]) == "6.0*c*pow(x, 2) + 2.0" - assert differentiate2c("a*x + b", "x", {"a", "b", "c"}, ["b = 2*x^2", "a = c*cos(x)"]) == "-c*x*sin(x) + c*cos(x) + 4.0*x" + assert _equivalent( + differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2*x*x"]), "-6*x*x+2" + ) + assert _equivalent( + differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x*x", "a = -2*x"]), "0" + ) # multiple prev_eqs to recursively substitute # note prev_eqs always substituted in reverse order - assert differentiate2c("a*x + b", "x", {"a", "b", "c"}, ["a=c*x", "b = 2*a*x"]) == "6.0*c*x" - assert ( + # and only x-dependent rhs's are substituted, e.g. 'a' remains 'a' here: + assert _equivalent( + differentiate2c("a*x + b", "x", {"a", "b"}, ["a=3", "b = 2*a*x"]), "3*a" + ) + assert _equivalent( differentiate2c( "a*x + b*c", "x", {"a", "b", "c"}, ["a=3", "b = 2*a*x", "c = a/x"] - ) - == "a" + ), + "a", ) - assert ( - differentiate2c("-a*x + b*c", "x", {"a", "b", "c"}, ["b = 2*x*x", "c = a/x"]) - == "a" + assert _equivalent( + differentiate2c("-a*x + b*c", "x", {"a", "b", "c"}, ["b = 2*x*x", "c = a/x"]), + "a", ) - assert ( + assert _equivalent( differentiate2c( "(g1 + g2)*(v-e)", "v", {"g", "e", "g1", "g2", "c", "d"}, ["g2 = sqrt(d) + 3", "g1 = 2*c", "g = g1 + g2"], - ) - == "g" + ), + "g", ) + + +def test_integrate2c(): + + # list of variables used for integrate2c + var_list = ["x", "a", "b"] + # pairs of (f(x), g(x)) + # where f(x) is the differential equation: dx/dt = f(x) + # and g(x) is the solution: x(t+dt) = g(x(t)) + test_cases = [ + ("0", "x"), + ("a", "x + a*dt"), + ("a*x", "x*exp(a*dt)"), + ("a*x+b", "(-b + (a*x + b)*exp(a*dt))/a"), + ] + for (eq, sol) in test_cases: + assert _equivalent( + integrate2c(f"x'={eq}", "dt", var_list, use_pade_approx=False), f"x = {sol}" + ) + + # repeat with solutions replaced with (1,1) Pade approximant + pade_test_cases = [ + ("0", "x"), + ("a", "x + a*dt"), + ("a*x", "-x*(a*dt+2)/(a*dt-2)"), + ("a*x+b", "-(a*dt*x+2*b*dt+2*x)/(a*dt-2)"), + ] + for (eq, sol) in pade_test_cases: + assert _equivalent( + integrate2c(f"x'={eq}", "dt", var_list, use_pade_approx=True), f"x = {sol}" + ) diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index caf003a2d9..c6fe327381 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2115,6 +2115,12 @@ std::vector<std::string> run_kinetic_block_visitor( // construct symbol table from AST SymtabVisitor().visit_program(ast.get()); + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + // run KineticBlock visitor on AST KineticBlockVisitor().visit_program(ast.get()); @@ -2147,6 +2153,24 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with << reaction statement, 1 array state var") { + std::string input_nmodl_text = R"( + STATE { + x[1] + } + KINETIC states { + ~ x[0] << (a*c/3.2) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x'[0] = (a*c/3.2) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with invalid << reaction statement with 2 state vars") { std::string input_nmodl_text = R"( STATE { @@ -2221,6 +2245,25 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with -> reaction statement, array of 2 state var") { + std::string input_nmodl_text = R"( + STATE { + x[2] + } + KINETIC states { + ~ x[0] + x[1] -> (f(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x'[0] = (-1*(f(v)*x[0]*x[1])) + x'[1] = (-1*(f(v)*x[0]*x[1])) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var") { // Here c is NOT a state variable // see 9.9.2.1 of NEURON book @@ -2448,6 +2491,41 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with loop over array variable") { + std::string input_nmodl_text = R"( + DEFINE N 5 + ASSIGNED { + a + b[N] + c[N] + d + } + STATE { + x[N] + } + KINETIC kin { + ~ x[0] << (a) + FROM i=0 TO N-2 { + ~ x[i] <-> x[i+1] (b[i], c[i]) + } + ~ x[N-1] -> (d) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE kin { + { + } + x'[0] = (a)+(-1*(b[0]*x[0]-c[0]*x[1])) + x'[1] = (1*(b[0]*x[0]-c[0]*x[1]))+(-1*(b[1]*x[1]-c[1]*x[2])) + x'[2] = (1*(b[1]*x[1]-c[1]*x[2]))+(-1*(b[2]*x[2]-c[2]*x[3])) + x'[3] = (1*(b[2]*x[2]-c[2]*x[3]))+(-1*(b[3]*x[3]-c[3]*x[4])) + x'[4] = (1*(b[3]*x[3]-c[3]*x[4]))+(-1*(d*x[4])) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } } //============================================================================= @@ -2469,9 +2547,14 @@ std::vector<std::string> run_sympy_solver_visitor( // construct symbol table from AST SymtabVisitor().visit_program(ast.get()); + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + // run SympySolver on AST - SympySolverVisitor v_sympy(pade, cse); - v_sympy.visit_program(ast.get()); + SympySolverVisitor(pade, cse).visit_program(ast.get()); // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; @@ -2519,7 +2602,6 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { m = m + h } )"; - THEN("No ODEs found - do nothing") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.empty()); @@ -2536,7 +2618,6 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { z = a*b + c } )"; - THEN("Construct forwards Euler solutions") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); @@ -2544,6 +2625,42 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { REQUIRE(result[1] == "h = (-dt*(h-hInf)+h*hTau)/hTau"); } } + GIVEN("Derivative block with ODE, 1 state var in array, solver method euler") { + std::string nmodl_text = R"( + STATE { + m[1] + } + BREAKPOINT { + SOLVE states METHOD euler + } + DERIVATIVE states { + m'[0] = (mInf-m[0])/mTau + } + )"; + THEN("Construct forwards Euler solutions") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == "m[0] = (dt*(mInf-m[0])+mTau*m[0])/mTau"); + } + } + GIVEN("Derivative block with ODE, 1 state var in array, solver method cnexp") { + std::string nmodl_text = R"( + STATE { + m[1] + } + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m'[0] = (mInf-m[0])/mTau + } + )"; + THEN("Construct forwards Euler solutions") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == "m[0] = mInf-(mInf-m[0])*exp(-dt/mTau)"); + } + } GIVEN("Derivative block with linear ODES, solver method cnexp") { std::string nmodl_text = R"( BREAKPOINT { @@ -2555,12 +2672,11 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { h' = hInf/hTau - h/hTau } )"; - THEN("Integrate equations analytically") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); - REQUIRE(result[0] == "m = mInf+(m-mInf)*exp(-dt/mTau)"); - REQUIRE(result[1] == "h = hInf+(h-hInf)*exp(-dt/hTau)"); + REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); + REQUIRE(result[1] == "h = hInf-(-h+hInf)*exp(-dt/hTau)"); } } GIVEN("Derivative block including non-linear but solvable ODES, solver method cnexp") { @@ -2573,12 +2689,83 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { h' = c2 * h*h } )"; - THEN("Integrate equations analytically") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); - REQUIRE(result[0] == "m = mInf+(m-mInf)*exp(-dt/mTau)"); - REQUIRE(result[1] == "h = -1/(c2*dt-1/h)"); + REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); + REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); + } + } + GIVEN("Derivative block including array of 2 state vars, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + STATE { + X[2] + } + DERIVATIVE states { + X'[0] = (mInf-X[0])/mTau + X'[1] = c2 * X[1]*X[1] + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau)"); + REQUIRE(result[1] == "X[1] = -X[1]/(c2*dt*X[1]-1)"); + } + } + GIVEN("Derivative block including loop over array vars, solver method cnexp") { + std::string nmodl_text = R"( + DEFINE N 3 + BREAKPOINT { + SOLVE states METHOD cnexp + } + ASSIGNED { + mTau[N] + } + STATE { + X[N] + } + DERIVATIVE states { + FROM i=0 TO N-1 { + X'[i] = (mInf-X[i])/mTau[i] + } + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau[0])"); + REQUIRE(result[1] == "X[1] = mInf-(mInf-X[1])*exp(-dt/mTau[1])"); + REQUIRE(result[2] == "X[2] = mInf-(mInf-X[2])*exp(-dt/mTau[2])"); + } + } + GIVEN("Derivative block including loop over array vars, solver method euler") { + std::string nmodl_text = R"( + DEFINE N 3 + BREAKPOINT { + SOLVE states METHOD euler + } + ASSIGNED { + mTau[N] + } + STATE { + X[N] + } + DERIVATIVE states { + FROM i=0 TO N-1 { + X'[i] = (mInf-X[i])/mTau[i] + } + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "X[0] = (dt*(mInf-X[0])+X[0]*mTau[0])/mTau[0]"); + REQUIRE(result[1] == "X[1] = (dt*(mInf-X[1])+X[1]*mTau[1])/mTau[1]"); + REQUIRE(result[2] == "X[2] = (dt*(mInf-X[2])+X[2]*mTau[2])/mTau[2]"); } } GIVEN("Derivative block including ODES that can't currently be solved, solver method cnexp") { @@ -2593,12 +2780,11 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { y' = c3 * y*y*y } )"; - THEN("Integrate equations analytically where possible, otherwise leave untouched") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 4); REQUIRE(result[0] == "z' = a/z+b/z/z"); - REQUIRE(result[1] == "h = -1/(c2*dt-1/h)"); + REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); REQUIRE(result[2] == "x = a*dt+x"); REQUIRE(result[3] == "y' = c3*y*y*y"); } @@ -2618,12 +2804,10 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { auto ast = driver.ast(); // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); // run SympySolver on AST - SympySolverVisitor v_sympy; - v_sympy.visit_program(ast.get()); + SympySolverVisitor().visit_program(ast.get()); std::string AST_string = ast_to_string(ast.get()); @@ -2692,7 +2876,6 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] z' = d*z - y } )"; - std::string expected_result = R"( DERIVATIVE states { LOCAL a, b, c, d, h, old_x, old_y, old_z @@ -2703,7 +2886,6 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+old_x)+old_y))/(2*a*pow(dt, 3)-d*dt+1) z = (-dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)+old_z)/(2*a*pow(dt, 3)-d*dt+1) })"; - std::string expected_cse_result = R"( DERIVATIVE states { LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 @@ -2751,7 +2933,6 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) })"; - THEN("Construct & solver linear system") { CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, @@ -2759,6 +2940,106 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] REQUIRE(result[0] == reindent_text(expected_result)); } } + GIVEN("Derivative block including ODES with sparse method - single var in array") { + std::string nmodl_text = R"( + STATE { + W[1] + } + ASSIGNED { + A[2] + B[1] + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + W'[0] = -A[0]*W[0] + B[0]*W[0] + 3*A[1] + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_W_0 + old_W_0 = W[0] + W[0] = (3*dt*A[1]+old_W_0)/(dt*A[0]-dt*B[0]+1) + })"; + THEN("Construct & solver linear system") { + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with sparse method - array vars") { + std::string nmodl_text = R"( + STATE { + M[2] + } + ASSIGNED { + A[2] + B[2] + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + M'[0] = -A[0]*M[0] + B[0]*M[1] + M'[1] = A[1]*M[0] - B[1]*M[1] + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_M_0, old_M_1 + old_M_0 = M[0] + old_M_1 = M[1] + M[0] = (dt*old_M_0*B[1]+dt*old_M_1*B[0]+old_M_0)/(pow(dt, 2)*A[0]*B[1]-pow(dt, 2)*A[1]*B[0]+dt*A[0]+dt*B[1]+1) + M[1] = -(dt*old_M_0*A[1]+old_M_1*(dt*A[0]+1))/(pow(dt, 2)*A[1]*B[0]-(dt*A[0]+1)*(dt*B[1]+1)) + })"; + THEN("Construct & solver linear system") { + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with derivimplicit method - single var in array") { + std::string nmodl_text = R"( + STATE { + W[1] + } + ASSIGNED { + A[2] + B[1] + } + BREAKPOINT { + SOLVE scheme1 METHOD derivimplicit + } + DERIVATIVE scheme1 { + W'[0] = -A[0]*W[0] + B[0]*W[0] + 3*A[1] + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + EIGEN_NEWTON_SOLVE[1]{ + LOCAL old_W_0 + }{ + old_W_0 = W[0] + }{ + X[0] = W[0] + }{ + F[0] = -X[0]+dt*(-X[0]*A[0]+X[0]*B[0]+3*A[1])+old_W_0 + J[0] = -dt*(A[0]-B[0])-1 + }{ + W[0] = X[0] + }{ + } + })"; + THEN("Construct newton solve block") { + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } GIVEN("Derivative block including ODES with derivimplicit method") { std::string nmodl_text = R"( STATE { @@ -2769,7 +3050,7 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] } DERIVATIVE states { rates(v) - m' = (minf-m)/mtau - 3*h + m' = (minf-m)/mtau - 3*h h' = (hinf-h)/htau + m*m n' = (ninf-n)/ntau } @@ -2808,8 +3089,7 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] }{ } })"; - - THEN("Construct & solver linear system using newton solver") { + THEN("Construct newton solve block") { CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); @@ -2883,8 +3163,7 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] }{ } })"; - - THEN("Construct & solver linear system using newton solver") { + THEN("Construct newton solve block") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); CAPTURE(nmodl_text); @@ -4101,6 +4380,28 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } + GIVEN("array state-var numeric LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + s[3] + } + LINEAR lin { + ~ s[0] = 1 + ~ s[1] = 3 + ~ s[2] + s[1] = s[0] + })"; + std::string expected_text = R"( + LINEAR lin { + s[0] = 1 + s[1] = 3 + s[2] = -2 + })"; + THEN("solve analytically") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } GIVEN("4 state-var LINEAR solve block") { std::string nmodl_text = R"( STATE { @@ -4404,6 +4705,50 @@ SCENARIO("NONLINEAR solve block (SympySolver Visitor)", "[sympy][nonlinear]") { REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } + GIVEN("array state-var numeric NONLINEAR solve block") { + std::string nmodl_text = R"( + STATE { + s[3] + } + NONLINEAR nonlin { + ~ s[0] = 1 + ~ s[1] = 3 + ~ s[2] + s[1] = s[0] + })"; + std::string expected_text = R"( + NONLINEAR nonlin { + EIGEN_NEWTON_SOLVE[3]{ + }{ + }{ + X[0] = s[0] + X[1] = s[1] + X[2] = s[2] + }{ + F[0] = -X[0]+1 + F[1] = -X[1]+3 + F[2] = X[0]-X[1]-X[2] + J[0] = -1 + J[3] = 0 + J[6] = 0 + J[1] = 0 + J[4] = -1 + J[7] = 0 + J[2] = 1 + J[5] = -1 + J[8] = -1 + }{ + s[0] = X[0] + s[1] = X[1] + s[2] = X[2] + }{ + } + })"; + THEN("return F & J for newton solver") { + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::NON_LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } } //============================================================================= @@ -4542,4 +4887,4 @@ TEST_CASE("Loop Unroll visitor") { } } } -} \ No newline at end of file +} From 513af6ad088cffa5456b957af68cc83c2c9f599e Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Wed, 27 Mar 2019 19:56:55 +0100 Subject: [PATCH 179/871] Kinetic block improvements (BlueBrain/nmodl#114) - moved kinetic block visitor after loop unrolling pass - added local_var type to get_global_vars, as local vars can be at global scope - added .expand() to _sympify_eqs, (implicitly) required by linear_eq_to_matrix NMODL Repo SHA: BlueBrain/nmodl@d0a16615b57137bc7f0ae94e59e1a1ddbfd70feb --- nmodl/ode.py | 7 ++++--- src/nmodl/nmodl/main.cpp | 14 +++++++------- src/nmodl/visitors/visitor_utils.cpp | 15 +++++++++------ test/nmodl/transpiler/visitor/visitor.cpp | 20 ++++++++++---------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index 16dce4892f..e95006f770 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -128,13 +128,13 @@ def _sympify_eqs(eq_strings, state_vars, vars): var_name, sympy_object = _var_to_sympy(var) sympy_vars[var_name] = sympy_object - # parse state vars using above sympy objects + # parse state vars & eqs using above sympy objects sympy_state_vars = [] for state_var in state_vars: sympy_state_vars.append(sp.sympify(state_var, locals=sympy_vars)) eqs = [ - sp.sympify(eq.split("=", 1)[1], locals=sympy_vars) - - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars) + (sp.sympify(eq.split("=", 1)[1], locals=sympy_vars) + - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars)).expand() for eq in eq_strings ] return eqs, sympy_state_vars, sympy_vars @@ -193,6 +193,7 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal # large linear system: construct and return matrix J, vector F such that # J X = F is the linear system to be solved for X by e.g. LU factorization matJ, vecF = sp.linear_eq_to_matrix(eqs, state_vars) + # construct vector F for i, expr in enumerate(vecF): code.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf())}") diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index d663bf2dd0..45364ef453 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -259,13 +259,6 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("verbatim_rename")); } - { - logger->info("Running KINETIC block visitor"); - KineticBlockVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("kinetic")); - } - /// once we start modifying (especially removing) older constructs /// from ast then we should run symtab visitor in update mode so /// that old symbols (e.g. prime variables) are not lost @@ -307,6 +300,13 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("localize")); } + { + logger->info("Running KINETIC block visitor"); + KineticBlockVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("kinetic")); + } + if (sympy_conductance) { logger->info("Running sympy conductance visitor"); SympyConductanceVisitor().visit_program(ast.get()); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 6f1422ebbc..4a07a7c170 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -127,12 +127,15 @@ void remove_statements_from_block(ast::StatementBlock* block, std::set<std::string> get_global_vars(Program* node) { std::set<std::string> vars; if (auto* symtab = node->get_symbol_table()) { - NmodlType property = - NmodlType::global_var | NmodlType::range_var | NmodlType::param_assign | - NmodlType::extern_var | NmodlType::prime_name | NmodlType::dependent_def | - NmodlType::read_ion_var | NmodlType::write_ion_var | NmodlType::nonspecific_cur_var | - NmodlType::electrode_cur_var | NmodlType::section_var | NmodlType::constant_var | - NmodlType::extern_neuron_variable | NmodlType::state_var | NmodlType::factor_def; + // NB: local_var included here as locals can be declared at global scope + NmodlType property = NmodlType::global_var | NmodlType::local_var | NmodlType::range_var | + NmodlType::param_assign | NmodlType::extern_var | + NmodlType::prime_name | NmodlType::dependent_def | + NmodlType::read_ion_var | NmodlType::write_ion_var | + NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | + NmodlType::section_var | NmodlType::constant_var | + NmodlType::extern_neuron_variable | NmodlType::state_var | + NmodlType::factor_def; for (const auto& globalvar: symtab->get_variables_with_properties(property)) { std::string var_name = globalvar->get_name(); if (globalvar->is_array()) { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index c6fe327381..f07bfab1af 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2846,7 +2846,7 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] }{ X[0] = m }{ - F[0] = (-dt*(X[0]-mInf)+mTau*(-X[0]+old_m))/mTau + F[0] = (-X[0]*dt+dt*mInf+mTau*(-X[0]+old_m))/mTau J[0] = -(dt+mTau)/mTau }{ m = X[0] @@ -3026,8 +3026,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] }{ X[0] = W[0] }{ - F[0] = -X[0]+dt*(-X[0]*A[0]+X[0]*B[0]+3*A[1])+old_W_0 - J[0] = -dt*(A[0]-B[0])-1 + F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3*dt*A[1]+old_W_0 + J[0] = -dt*A[0]+dt*B[0]-1 }{ W[0] = X[0] }{ @@ -3070,9 +3070,9 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] X[1] = h X[2] = n }{ - F[0] = (-dt*(X[0]+3*X[1]*mtau-minf)+mtau*(-X[0]+old_m))/mtau - F[1] = (dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(-X[1]+old_h))/htau - F[2] = (-dt*(X[2]-ninf)+ntau*(-X[2]+old_n))/ntau + F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]-3*X[1]*dt+old_m))/mtau + F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau + F[2] = (-X[2]*dt+dt*ninf+ntau*(-X[2]+old_n))/ntau J[0] = -(dt+mtau)/mtau J[3] = -3*dt J[6] = 0 @@ -3128,8 +3128,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] X[0] = m X[1] = h }{ - F[0] = (-dt*(X[0]-minf)+mtau*(-X[0]+old_m))/mtau - F[1] = (dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(-X[1]+old_h))/htau + F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]+old_m))/mtau + F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau J[0] = -(dt+mtau)/mtau J[2] = 0 J[1] = 2*X[0]*dt @@ -3151,8 +3151,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] X[0] = m X[1] = h }{ - F[0] = (dt*(pow(X[0], 2)*htau-X[1]+hinf)+htau*(-X[1]+old_h))/htau - F[1] = (dt*(-X[0]+X[1]*mtau+minf)+mtau*(-X[0]+old_m))/mtau + F[0] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau + F[1] = (-X[0]*dt+dt*minf+mtau*(-X[0]+X[1]*dt+old_m))/mtau J[0] = 2*X[0]*dt J[2] = -(dt+htau)/htau J[1] = -(dt+mtau)/mtau From 6d88c8b103754a925c294a5ee1c7c94904fbb861 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Fri, 29 Mar 2019 16:05:34 +0100 Subject: [PATCH 180/871] Bug fixes in code generation for traub and neocortex-mousify model (BlueBrain/nmodl#111) - change has_local_statement to is_local_statement as inlined expression can have local variables - add missing pnodecount argument to newton functor - print memory callback routines before its usage NMODL Repo SHA: BlueBrain/nmodl@a324b7de6d19a94f871200f880411bc0aeb1fc3e --- src/nmodl/codegen/codegen_c_visitor.cpp | 31 +++++++++++++------- src/nmodl/codegen/codegen_helper_visitor.cpp | 16 ++++++++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 4 +++ src/nmodl/codegen/codegen_info.cpp | 14 +++++++++ src/nmodl/codegen/codegen_info.hpp | 9 +++++- src/nmodl/nmodl/main.cpp | 27 +++++++++-------- src/nmodl/visitors/sympy_solver_visitor.cpp | 25 ++++++++++++++-- 7 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 79d09d5a3c..f88bc9fc04 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1660,9 +1660,12 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->start_block("struct functor"); printer->add_line("NrnThread* nt;"); printer->add_line("{0}* inst;"_format(instance_struct())); - printer->add_line("int id;"); + printer->add_line("int id, pnodecount;"); printer->add_line("double v;"); printer->add_line("Datum* indexes;"); + if (ion_variable_struct_required()) { + printer->add_line("IonCurVar ionvar = {0};"); + } print_statement_block(node->get_variable_block().get(), false, false); printer->add_newline(); @@ -1672,7 +1675,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->end_block(2); printer->add_line( - "functor(NrnThread* nt, {}* inst, int id, double v, Datum* indexes) : nt(nt), inst(inst), id(id), v(v), indexes(indexes) {}"_format( + "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes) : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes) {}"_format( instance_struct(), "{}")); printer->add_indent(); @@ -1696,7 +1699,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc // call newton solver with functor and X matrix that contains state vars printer->add_line("// call newton solver"); - printer->add_line("functor newton_functor(nt, inst, id, v, indexes);"); + printer->add_line("functor newton_functor(nt, inst, id, pnodecount, v, indexes);"); printer->add_line("newton_functor.initialize();"); printer->add_line("int newton_iterations = nmodl::newton::newton_solver(X, newton_functor);"); @@ -2351,10 +2354,9 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { } } - if (!info.vectorize) { - printer->add_line("{} v;"_format(float_type)); - codegen_global_variables.push_back(make_symbol("v")); - } + /// Neuron and Coreneuron adds "v" to global variables when vectorize + /// is false. But as v is always local variable and passed as argument, + /// we don't need to use global variable v auto& top_locals = info.top_local_variables; if (!info.vectorize && !top_locals.empty()) { @@ -2856,9 +2858,9 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line("{} = 0.0;"_format(global_name)); } } - if (!info.vectorize) { - printer->add_line("{} = 0.0;"_format(get_variable_name("v"))); - } + + /// note : v is not needed in global structure for nmodl even if vectorize is false + if (!info.thread_variables.empty()) { printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); } @@ -3769,6 +3771,9 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); + + /// todo : eigen solver node also emits IonCurVar variable in the functor + /// but that shouldn't update ions in derivative block if (ion_variable_struct_required()) { printer->add_line("IonCurVar ionvar = {0};"); } @@ -4039,8 +4044,8 @@ void CodegenCVisitor::print_codegen_routines() { print_data_structures(); print_global_variables_for_hoc(); print_common_getters(); - print_thread_memory_callbacks(); print_memory_allocation_routine(); + print_thread_memory_callbacks(); print_global_variable_setup(); print_instance_variable_setup(); print_nrn_alloc(); @@ -4064,6 +4069,10 @@ void CodegenCVisitor::visit_program(Program* node) { info = v.analyze(node); info.mod_file = mod_filename; + if (!info.vectorize) { + logger->warn("CodegenCVisitor : MOD file uses non-thread safe constructs of NMODL"); + } + codegen_float_variables = get_float_variables(); codegen_int_variables = get_int_variables(); codegen_shadow_variables = get_shadow_variables(); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 6a3cc3ef6e..dd4fb69fb1 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -624,5 +624,21 @@ codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program* node) { return info; } +void CodegenHelperVisitor::visit_linear_block(ast::LinearBlock* node) { + info.vectorize = false; +} + +void CodegenHelperVisitor::visit_non_linear_block(ast::NonLinearBlock* node) { + info.vectorize = false; +} + +void CodegenHelperVisitor::visit_discrete_block(ast::DiscreteBlock* node) { + info.vectorize = false; +} + +void CodegenHelperVisitor::visit_partial_block(ast::PartialBlock* node) { + info.vectorize = false; +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 9ab5254500..95036d292c 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -92,6 +92,10 @@ class CodegenHelperVisitor: public AstVisitor { void visit_table_statement(ast::TableStatement* node) override; void visit_program(ast::Program* node) override; void visit_nrn_state_block(ast::NrnStateBlock* node) override; + void visit_linear_block(ast::LinearBlock* node) override; + void visit_non_linear_block(ast::NonLinearBlock* node) override; + void visit_discrete_block(ast::DiscreteBlock* node) override; + void visit_partial_block(ast::PartialBlock* node) override; }; } // namespace codegen diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 8c8985f7d8..59a0e2b67d 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include "codegen/codegen_info.hpp" +#include "visitors/lookup_visitor.hpp" namespace nmodl { @@ -86,6 +87,19 @@ bool CodegenInfo::derivimplicit_coreneuron_solver() { return !derivimplicit_callbacks.empty(); } +/** + * Check if NrnState node in the AST has EigenSolverBlock node + * + * @return True if EigenSolverBlock exist in the node + */ +bool CodegenInfo::nrn_state_has_eigen_solver_block() const { + if (nrn_state_block == nullptr) { + return false; + } + return !AstLookupVisitor() + .lookup(nrn_state_block, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK) + .empty(); +} } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 5eb263dd02..921656b44a 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -133,7 +133,11 @@ struct CodegenInfo { /// name of the suffix std::string mod_suffix; - /// if mod file is vectorizable (always true for coreneuron) + /// true if mod file is vectorizable (which should be always true for coreneuron) + /// But there are some blocks like LINEAR are not thread safe in neuron or mod2c + /// context. In this case vectorize is used to determine number of float variable + /// in the data array (e.g. v). For such non thread methods or blocks vectorize is + /// false. bool vectorize = true; /// if mod file is thread safe (always true for coreneuron) @@ -364,6 +368,9 @@ struct CodegenInfo { bool derivimplicit_coreneuron_solver(); bool function_uses_table(std::string& name) const; + + /// true if EigenNewtonSolver is used in nrn_state block + bool nrn_state_has_eigen_solver_block() const; }; } // namespace codegen diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 45364ef453..d755db0995 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -259,11 +259,6 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("verbatim_rename")); } - /// once we start modifying (especially removing) older constructs - /// from ast then we should run symtab visitor in update mode so - /// that old symbols (e.g. prime variables) are not lost - update_symtab = true; - if (nmodl_const_folding) { logger->info("Running nmodl constant folding visitor"); ConstantFolderVisitor().visit_program(ast.get()); @@ -278,6 +273,21 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(ast.get()); } + /// note that we can not symtab visitor in update mode as we + /// replace kinetic block with derivative block of same name + /// in global scope + { + logger->info("Running KINETIC block visitor"); + KineticBlockVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("kinetic")); + } + + /// once we start modifying (especially removing) older constructs + /// from ast then we should run symtab visitor in update mode so + /// that old symbols (e.g. prime variables) are not lost + update_symtab = true; + if (nmodl_inline) { logger->info("Running nmodl inline visitor"); InlineVisitor().visit_program(ast.get()); @@ -300,13 +310,6 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("localize")); } - { - logger->info("Running KINETIC block visitor"); - KineticBlockVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("kinetic")); - } - if (sympy_conductance) { logger->info("Running sympy conductance visitor"); SympyConductanceVisitor().visit_program(ast.get()); diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 687a347cde..8087a25bfc 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -99,8 +99,27 @@ ast::StatementVector::iterator SympySolverVisitor::get_solution_location_iterato return it; } -static bool has_local_statement(std::shared_ptr<ast::Statement> statement) { - return !AstLookupVisitor().lookup(statement.get(), ast::AstNodeType::LOCAL_VAR).empty(); +/** + * Check if provided statemenet is local variable declaration statement + * @param statement AST node representing statement in the MOD file + * @return True if statement is local variable declaration else False + * + * Statement declaration could be wrapped into another statement type like + * expression statement and hence we try to look inside if it's really a + * variable declaration. + */ +static bool is_local_statement(std::shared_ptr<ast::Statement> statement) { + if (statement->is_local_list_statement()) { + return true; + } + if (statement->is_expression_statement()) { + auto e_statement = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement); + auto expression = e_statement->get_expression(); + if (expression->is_local_list_statement()) { + return true; + } + } + return false; } void SympySolverVisitor::construct_eigen_solver_block( @@ -145,7 +164,7 @@ void SympySolverVisitor::construct_eigen_solver_block( // remaining statements in block should go into initialize_block ast::StatementVector initialize_statements; for (auto s: statements) { - if (has_local_statement(s)) { + if (is_local_statement(s)) { variable_statements.push_back(s); } else { initialize_statements.push_back(s); From 788d476c09f000275ec3989b6f388a8281709f98 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Fri, 29 Mar 2019 22:23:20 +0100 Subject: [PATCH 181/871] Add STEADYSTATE support in SolverBlock and code generation (BlueBrain/nmodl#117) * add "steadystate" to nmodl solve block in same way as "method" * add SteadystateVisitor, for each STEADYSTATE solve statement: - clones each DERIVATIVE block with same name as DERIVATIVE block + "_steadystate" suffix - for sparse: sets dt=1e9 within the block - for derivimiplicit: sets dt=1e-9 within the block - should run after KineticBlockVisitor (so that kinetic blocks -> derivative blocks) * fixed nmodl printer issue with limited precision for doubles - constant folder was folding double expressions to ~7 digits of precision - this was due to the nmdol printer doing `<< dbl` without setting precision - changed this to use precision(16) - added some tests to check for loss of precision - updated a few tests affected by above changes (e.g. 1e10 -> 10000000000) NMODL Repo SHA: BlueBrain/nmodl@688afa94656aa76b65593e9765ff9e0d3bf7a694 --- src/nmodl/language/nmodl.yaml | 5 + src/nmodl/language/node_info.py | 2 + src/nmodl/language/nodes.py | 8 + .../language/templates/nmodl_visitor.cpp | 5 + src/nmodl/lexer/nmodl_utils.cpp | 2 + src/nmodl/lexer/token_mapping.cpp | 2 +- src/nmodl/nmodl/main.cpp | 8 + src/nmodl/parser/nmodl.yy | 10 +- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/steadystate_visitor.cpp | 128 +++++++++++++ src/nmodl/visitors/steadystate_visitor.hpp | 57 ++++++ src/nmodl/visitors/sympy_solver_visitor.cpp | 17 +- test/nmodl/transpiler/lexer/tokens.cpp | 2 + test/nmodl/transpiler/modtoken/modtoken.cpp | 6 +- .../transpiler/utils/nmodl_constructs.cpp | 36 +++- test/nmodl/transpiler/visitor/visitor.cpp | 179 +++++++++++++++++- 16 files changed, 448 insertions(+), 21 deletions(-) create mode 100644 src/nmodl/visitors/steadystate_visitor.cpp create mode 100644 src/nmodl/visitors/steadystate_visitor.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 3646e6ab5c..eeb51003a2 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -532,6 +532,11 @@ type: Name optional: true prefix: {value: " METHOD "} + - steadystate: + description: "..." + type: Name + optional: true + prefix: {value: " STEADYSTATE "} - ifsolerr: description: "..." type: StatementBlock diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 777bebe3b8..7b39147d77 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -161,6 +161,8 @@ NAME_NODE = "Name" BOOLEAN_NODE = "Boolean" INTEGER_NODE = "Integer" +FLOAT_NODE = "Float" +DOUBLE_NODE = "Double" STATEMENT_BLOCK_NODE = "StatementBlock" UNIT_BLOCK = "UnitBlock" diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index e862dc7e4c..ebefecd23d 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -106,6 +106,14 @@ def is_string_node(self): def is_integer_node(self): return self.class_name == node_info.INTEGER_NODE + @property + def is_float_node(self): + return self.class_name == node_info.FLOAT_NODE + + @property + def is_double_node(self): + return self.class_name == node_info.DOUBLE_NODE + @property def is_number_node(self): return self.class_name == node_info.NUMBER_NODE diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/nmodl_visitor.cpp index 37cdbba771..2ea288b672 100644 --- a/src/nmodl/language/templates/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/nmodl_visitor.cpp @@ -89,6 +89,11 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name if(node->get_macro() == nullptr) { printer->add_element(std::to_string(node->eval())); } + {% elif node.is_float_node or node.is_double_node %} + std::stringstream ss; + ss << std::setprecision(16); + ss << node->eval(); + printer->add_element(ss.str()); {% else %} std::stringstream ss; ss << node->eval(); diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index d8fcc0fef8..f64bb34809 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -225,6 +225,8 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_START1(token, pos); case Token::STATE: return Parser::make_STATE(token, pos); + case Token::STEADYSTATE: + return Parser::make_STEADYSTATE(token, pos); case Token::STEP: return Parser::make_STEP(token, pos); case Token::STEPPED: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 86f73b6e27..856acf966f 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -55,7 +55,7 @@ namespace internal { {"DEL", Token::DEL}, {"LOCAL", Token::LOCAL}, {"METHOD", Token::USING}, - {"STEADYSTATE", Token::USING}, + {"STEADYSTATE", Token::STEADYSTATE}, {"SENS", Token::SENS}, {"STEP", Token::STEP}, {"WITH", Token::WITH}, diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index d755db0995..f2a89f9dd2 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -34,6 +34,7 @@ #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/solve_block_visitor.hpp" +#include "visitors/steadystate_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" @@ -283,6 +284,13 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("kinetic")); } + { + logger->info("Running STEADYSTATE visitor"); + SteadystateVisitor().visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(ast.get()); + ast_to_nmodl(ast.get(), filepath("steadystate")); + } + /// once we start modifying (especially removing) older constructs /// from ast then we should run symtab visitor in update mode so /// that old symbols (e.g. prime variables) are not lost diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 560d8ac393..1f03310bcd 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -88,6 +88,7 @@ %token <ModToken> DERIVATIVE %token <ModToken> SOLVE %token <ModToken> USING +%token <ModToken> STEADYSTATE %token <ModToken> WITH %token <ModToken> STEPPED %token <ModToken> DISCRETE @@ -1454,11 +1455,16 @@ initstmt : INITIAL1 stmtlist "}" solveblk : SOLVE NAME_PTR ifsolerr { - $$ = new ast::SolveBlock($2, NULL, $3); + $$ = new ast::SolveBlock($2, NULL, NULL, $3); } | SOLVE NAME_PTR USING METHOD ifsolerr { - $$ = new ast::SolveBlock($2, $4.clone(), $5); + $$ = new ast::SolveBlock($2, $4.clone(), NULL, $5); + } + | + SOLVE NAME_PTR STEADYSTATE METHOD ifsolerr + { + $$ = new ast::SolveBlock($2, NULL, $4.clone(), $5); } | SOLVE error { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index e5f31e8361..98abcecdec 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -25,6 +25,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/steadystate_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/steadystate_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp new file mode 100644 index 0000000000..06c3436d3b --- /dev/null +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -0,0 +1,128 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <iostream> + +#include "fmt/format.h" + +#include "codegen/codegen_naming.hpp" +#include "symtab/symbol.hpp" +#include "utils/logger.hpp" +#include "utils/string_utils.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/steadystate_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace fmt::literals; + +namespace nmodl { + +using symtab::syminfo::NmodlType; + +std::shared_ptr<ast::DerivativeBlock> SteadystateVisitor::create_steadystate_block( + std::shared_ptr<ast::SolveBlock> solve_block, + const std::vector<std::shared_ptr<ast::AST>>& deriv_blocks) { + // new block to be returned + std::shared_ptr<ast::DerivativeBlock> ss_block; + + // get method & derivative block + const auto solve_block_name = solve_block->get_block_name()->get_value()->eval(); + const auto steadystate_method = solve_block->get_steadystate()->get_value()->eval(); + + logger->debug("SteadystateVisitor :: Found STEADYSTATE SOLVE statement: using {} for {}", + steadystate_method, solve_block_name); + + ast::DerivativeBlock* deriv_block_ptr = nullptr; + for (const auto& block_ptr: deriv_blocks) { + auto deriv_block = std::dynamic_pointer_cast<ast::DerivativeBlock>(block_ptr); + if (deriv_block->get_node_name() == solve_block_name) { + logger->debug("SteadystateVisitor :: -> found corresponding DERIVATIVE block: {}", + solve_block_name); + deriv_block_ptr = deriv_block.get(); + break; + } + } + + if (deriv_block_ptr != nullptr) { + // make a clone of derivative block with "_steadystate" suffix + ss_block = std::shared_ptr<ast::DerivativeBlock>(deriv_block_ptr->clone()); + auto ss_name = ss_block->get_name(); + ss_name->set_name(ss_name->get_value()->get_value() + "_steadystate"); + auto ss_name_clone = std::shared_ptr<ast::Name>(ss_name->clone()); + ss_block->set_name(std::move(ss_name)); + logger->debug("SteadystateVisitor :: -> adding new DERIVATIVE block: {}", + ss_block->get_node_name()); + + // create statements to alter value of dt within DERIVATIVE block + // TODO: make sure dt_tmp_var_name variable name does not clash + std::string dt_tmp_var_name = codegen::naming::NTHREAD_DT_VARIABLE + "_saved_value"; + std::string dt_save = dt_tmp_var_name + " = " + codegen::naming::NTHREAD_DT_VARIABLE; + std::string dt_assign = codegen::naming::NTHREAD_DT_VARIABLE + " = "; + std::string dt_restore = dt_assign + dt_tmp_var_name; + if (steadystate_method == codegen::naming::SPARSE_METHOD) { + dt_assign += "{:.16g}"_format(STEADYSTATE_SPARSE_DT); + } else if (steadystate_method == codegen::naming::DERIVIMPLICIT_METHOD) { + dt_assign += "{:.16g}"_format(STEADYSTATE_DERIVIMPLICIT_DT); + } else { + logger->warn("SteadystateVisitor :: solve method {} not supported for STEADYSTATE", + steadystate_method); + return nullptr; + } + auto statement_block = ss_block->get_statement_block(); + // declare tmp variable to save dt value (this will go into the LOCAL statement at the top + // of the statement block) + add_local_variable(statement_block.get(), dt_tmp_var_name); + // get a copy of existing statements + auto statements = statement_block->get_statements(); + // insert dt_save and dt_assign statements just below first LOCAL statement + auto insertion_point = statements.begin(); + while ((*insertion_point)->is_local_list_statement()) { + ++insertion_point; + } + insertion_point = statements.insert(insertion_point, create_statement(dt_save)); + ++insertion_point; + statements.insert(insertion_point, create_statement(dt_assign)); + // insert dt_restore statement at the end + statements.push_back(create_statement(dt_restore)); + // replace old set of statements in AST with new one + statement_block->set_statements(std::move(statements)); + + // update SOLVE statement: + // set name to point to new DERIVATIVE block + solve_block->set_block_name(std::move(ss_name_clone)); + // change from STEADYSTATE to METHOD + solve_block->set_method(std::move(solve_block->get_steadystate())); + solve_block->set_steadystate(nullptr); + } else { + logger->warn("SteadystateVisitor :: Could not find derivative block {} for STEADYSTATE", + solve_block_name); + return nullptr; + } + return ss_block; +} + +void SteadystateVisitor::visit_program(ast::Program* node) { + // get DERIVATIVE blocks + const auto& deriv_blocks = AstLookupVisitor().lookup(node, ast::AstNodeType::DERIVATIVE_BLOCK); + + // get list of STEADYSTATE solve statements with names & methods + const auto& solve_block_nodes = AstLookupVisitor().lookup(node, ast::AstNodeType::SOLVE_BLOCK); + + // create new DERIVATIVE blocks for the STEADYSTATE solves + for (const auto& solve_block_ptr: solve_block_nodes) { + if (auto solve_block = std::dynamic_pointer_cast<ast::SolveBlock>(solve_block_ptr)) { + if (solve_block->get_steadystate()) { + auto ss_block = create_steadystate_block(solve_block, deriv_blocks); + if (ss_block != nullptr) { + node->addNode(ss_block); + } + } + } + } +} + +} // namespace nmodl diff --git a/src/nmodl/visitors/steadystate_visitor.hpp b/src/nmodl/visitors/steadystate_visitor.hpp new file mode 100644 index 0000000000..9fba803e69 --- /dev/null +++ b/src/nmodl/visitors/steadystate_visitor.hpp @@ -0,0 +1,57 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { + +/** + * \class SteadystateVisitor + * \brief Visitor for STEADYSTATE solve statements + * + * For each STEADYSTATE solve statement, creates a clone + * of the corresponding DERIVATIVE block, with "_steadystate" + * appended to the name of the block + * + * If the original DERIVATIVE block was called "X", the + * new DERIVATIVE block will be called "X_steadystate" + * + * Only difference in new block is that the value of dt + * is changed for the solve, to: + * - dt = 1e9 for sparse + * - dt = 1e-9 for derivimplicit + * where these values for dt in the steady state solves are taken from NEURON + * see https://github.com/neuronsimulator/nrn/blob/master/src/scopmath/ssimplic_thread.c + * + * Also updates the solve statement to point to the new DERIVATIVE block + * as a METHOD solve, not a STEADYSTATE one, e.g. + * SOLVE X STEADYSTATE derivimplicit + * becomes + * SOLVE X_steadystate METHOD derivimplicit + * + */ + +class SteadystateVisitor: public AstVisitor { + private: + /// create new steady state derivative block for given solve block + std::shared_ptr<ast::DerivativeBlock> create_steadystate_block( + std::shared_ptr<ast::SolveBlock> solve_block, + const std::vector<std::shared_ptr<ast::AST>>& deriv_blocks); + const double STEADYSTATE_SPARSE_DT = 1e9; + const double STEADYSTATE_DERIVIMPLICIT_DT = 1e-9; + + public: + SteadystateVisitor() = default; + + void visit_program(ast::Program* node) override; +}; + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 8087a25bfc..1d8e3f8e07 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -435,6 +435,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { if (eq_system_is_valid && !eq_system.empty()) { // solve system of ODEs in eq_system logger->debug("SympySolverVisitor :: Solving {} system of ODEs", solve_method); + // construct implicit Euler equations from ODEs std::vector<std::string> pre_solve_statements; for (auto& eq: eq_system) { @@ -455,9 +456,10 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { add_local_variable(block_with_expression_statements, old_x); // assign old_x = x pre_solve_statements.push_back(old_x + " = " + x + x_array_index); + // replace ODE with Euler equation eq = x + x_array_index + " = " + old_x + " + " + codegen::naming::NTHREAD_DT_VARIABLE + " * (" + dxdt + ")"; - logger->debug("SympySolverVisitor :: -> constructed euler eq: {}", eq); + logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); } if (solve_method == codegen::naming::SPARSE_METHOD) { @@ -541,6 +543,8 @@ void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { } void SympySolverVisitor::visit_program(ast::Program* node) { + derivative_block_solve_method.clear(); + global_vars = get_global_vars(node); // get list of solve statements with names & methods @@ -548,16 +552,15 @@ void SympySolverVisitor::visit_program(ast::Program* node) { auto solve_block_nodes = ast_lookup_visitor.lookup(node, ast::AstNodeType::SOLVE_BLOCK); for (const auto& block: solve_block_nodes) { if (auto block_ptr = std::dynamic_pointer_cast<ast::SolveBlock>(block)) { - std::string solve_method; + const auto block_name = block_ptr->get_block_name()->get_value()->eval(); if (block_ptr->get_method()) { // Note: solve method name is an optional parameter // LINEAR and NONLINEAR blocks do not have solve method specified - solve_method = block_ptr->get_method()->get_value()->eval(); + const auto& solve_method = block_ptr->get_method()->get_value()->eval(); + logger->debug("SympySolverVisitor :: Found SOLVE statement: using {} for {}", + solve_method, block_name); + derivative_block_solve_method[block_name] = solve_method; } - std::string block_name = block_ptr->get_block_name()->get_value()->eval(); - logger->debug("SympySolverVisitor :: Found SOLVE statement: using {} for {}", - solve_method, block_name); - derivative_block_solve_method[block_name] = solve_method; } } diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index fd960f7f78..80ef86bd46 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -104,6 +104,8 @@ TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { REQUIRE(token_type("1.32E+3") == Token::REAL); REQUIRE(token_type("1.32e-3") == Token::REAL); REQUIRE(token_type("32e-3") == Token::REAL); + REQUIRE(token_type("1e+23") == Token::REAL); + REQUIRE(token_type("1e-23") == Token::REAL); } SECTION("Tests for Name/Strings") { diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index c869862bca..200d5cc849 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -40,14 +40,14 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 356"); + REQUIRE(ss.str() == " text at [1.1-4] type 357"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 356"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 357"); } } @@ -58,7 +58,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 362"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 363"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 6359b9218d..95fbfeac84 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -399,6 +399,21 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ } }, + { + "parameter_block_5", + { + "PARAMETER block with very small/large doubles", + R"( + PARAMETER { + tau_r_AMPA = 1e-16 + tau_d_AMPA = 1.7e-22 + tau_r_NMDA = 1e+21 + tau_d_NMDA = 1.3643e+27 + } + )" + } + }, + { "step_block_1", { @@ -889,6 +904,12 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ CONSERVE C+o = 1 CONSERVE pump+pumpca = TotalPump*parea*(1e+10) } + )", + R"( + KINETIC ihkin { + CONSERVE C+o = 1 + CONSERVE pump+pumpca = TotalPump*parea*(10000000000) + } )" } }, @@ -904,6 +925,14 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ COMPARTMENT (1e+10)*area1 {pump pumpca} COMPARTMENT i, diam*diam*vol[i]*1(um) {ca CaBuffer Buffer} } + )", + R"( + KINETIC ihkin { + COMPARTMENT voli {cai} + COMPARTMENT diam*diam*PI/4 {qk} + COMPARTMENT (10000000000)*area1 {pump pumpca} + COMPARTMENT i, diam*diam*vol[i]*1(um) {ca CaBuffer Buffer} + } )" } }, @@ -1323,16 +1352,11 @@ std::map<std::string, NmodlTestCase> nmodl_valid_constructs{ { "steadystate_statement_1", { - "SOLVE statement using STEADYSTATE (which gets replaced with METHOD)", + "SOLVE statement using STEADYSTATE", R"( INITIAL { SOLVE kin STEADYSTATE sparse } - )", - R"( - INITIAL { - SOLVE kin METHOD sparse - } )" } }, diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index f07bfab1af..255c85dd30 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -30,6 +30,7 @@ #include "visitors/perf_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/solve_block_visitor.hpp" +#include "visitors/steadystate_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" @@ -4060,9 +4061,9 @@ SCENARIO("SympyConductance visitor", "[sympy]") { mggate = 1/(1+exp(slope_mg*-v)*(mg/scale_mg)) g_NMDA = 0.001*gmax_NMDA*mggate*(B_NMDA-A_NMDA) i_NMDA = g_NMDA*(v-E_NMDA) - Pf_NMDA = (4*cao_CR)/(4*cao_CR+0.724638*120(mM))*0.6 + Pf_NMDA = (4*cao_CR)/(4*cao_CR+0.7246376811594204*120(mM))*0.6 ica_NMDA = Pf_NMDA*g_NMDA*(v-40) - gca_bar_abs_VDCC = gca_bar_VDCC*4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^0.666667 + gca_bar_abs_VDCC = gca_bar_VDCC*4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^0.6666666666666666 gca_VDCC = 0.001*gca_bar_abs_VDCC*m_VDCC*m_VDCC*h_VDCC { LOCAL ci_in_0, co_in_0, z_in_0 @@ -4155,6 +4156,23 @@ TEST_CASE("Constant Folding Visitor") { } } + GIVEN("Simple double expression") { + std::string nmodl_text = R"( + PROCEDURE dummy() { + a = 1.1 + 2e-10 + } + )"; + std::string expected_text = R"( + PROCEDURE dummy() { + a = 1.1000000002 + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + GIVEN("Complex expression") { std::string nmodl_text = R"( PROCEDURE dummy() { @@ -4888,3 +4906,160 @@ TEST_CASE("Loop Unroll visitor") { } } } + +//============================================================================= +// STEADYSTATE visitor tests +//============================================================================= +std::vector<std::string> run_steadystate_visitor( + const std::string& text, + std::vector<AstNodeType> ret_nodetypes = {AstNodeType::SOLVE_BLOCK, + AstNodeType::DERIVATIVE_BLOCK}) { + std::vector<std::string> results; + // construct AST from text + NmodlDriver driver; + driver.parse_string(text); + auto ast = driver.ast(); + + // construct symbol table from AST + SymtabVisitor().visit_program(ast.get()); + + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + + // Run kinetic block visitor first, so any kinetic blocks + // are converted into derivative blocks + KineticBlockVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + + // run SteadystateVisitor on AST + SteadystateVisitor().visit_program(ast.get()); + + // run lookup visitor to extract results from AST + auto res = AstLookupVisitor().lookup(ast.get(), ret_nodetypes); + for (const auto& r: res) { + results.push_back(to_nmodl(r.get())); + } + return results; +} + +SCENARIO("SteadystateSolver visitor", "[steadystate]") { + GIVEN("STEADYSTATE sparse solve") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states STEADYSTATE sparse + } + DERIVATIVE states { + m' = m + h + } + )"; + std::string expected_text1 = R"( + DERIVATIVE states { + m' = m+h + })"; + std::string expected_text2 = R"( + DERIVATIVE states_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1000000000 + m' = m+h + dt = dt_saved_value + })"; + THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { + auto result = run_steadystate_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "SOLVE states_steadystate METHOD sparse"); + REQUIRE(reindent_text(result[1]) == reindent_text(expected_text1)); + REQUIRE(reindent_text(result[2]) == reindent_text(expected_text2)); + } + } + GIVEN("STEADYSTATE derivimplicit solve") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states STEADYSTATE derivimplicit + } + DERIVATIVE states { + m' = m + h + } + )"; + std::string expected_text1 = R"( + DERIVATIVE states { + m' = m+h + })"; + std::string expected_text2 = R"( + DERIVATIVE states_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1e-09 + m' = m+h + dt = dt_saved_value + })"; + THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { + auto result = run_steadystate_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "SOLVE states_steadystate METHOD derivimplicit"); + REQUIRE(reindent_text(result[1]) == reindent_text(expected_text1)); + REQUIRE(reindent_text(result[2]) == reindent_text(expected_text2)); + } + } + GIVEN("two STEADYSTATE solves") { + std::string nmodl_text = R"( + STATE { + Z[3] + x + } + BREAKPOINT { + SOLVE states0 STEADYSTATE derivimplicit + SOLVE states1 STEADYSTATE sparse + } + DERIVATIVE states0 { + Z'[0] = Z[1] - Z[2] + Z'[1] = Z[0] + 2*Z[2] + Z'[2] = Z[0]*Z[0] - 3.10 + } + DERIVATIVE states1 { + x' = x + c + } + )"; + std::string expected_text1 = R"( + DERIVATIVE states0 { + Z'[0] = Z[1]-Z[2] + Z'[1] = Z[0]+2*Z[2] + Z'[2] = Z[0]*Z[0]-3.1 + })"; + std::string expected_text2 = R"( + DERIVATIVE states1 { + x' = x+c + })"; + std::string expected_text3 = R"( + DERIVATIVE states0_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1e-09 + Z'[0] = Z[1]-Z[2] + Z'[1] = Z[0]+2*Z[2] + Z'[2] = Z[0]*Z[0]-3.1 + dt = dt_saved_value + })"; + std::string expected_text4 = R"( + DERIVATIVE states1_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1000000000 + x' = x+c + dt = dt_saved_value + })"; + THEN("Construct DERIVATIVE blocks with steadystate solution & update SOLVE statements") { + auto result = run_steadystate_visitor(nmodl_text); + REQUIRE(result.size() == 6); + REQUIRE(result[0] == "SOLVE states0_steadystate METHOD derivimplicit"); + REQUIRE(result[1] == "SOLVE states1_steadystate METHOD sparse"); + REQUIRE(reindent_text(result[2]) == reindent_text(expected_text1)); + REQUIRE(reindent_text(result[3]) == reindent_text(expected_text2)); + REQUIRE(reindent_text(result[4]) == reindent_text(expected_text3)); + REQUIRE(reindent_text(result[5]) == reindent_text(expected_text4)); + } + } +} From 89b4f120d1ea8e37f84d2770681c8710cfdb3d11 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Sat, 30 Mar 2019 09:54:33 +0100 Subject: [PATCH 182/871] Add f_flux and b_flux support to kinetic visitor (BlueBrain/nmodl#120) - flux variables are now wrapped in the WrappedExpression last node - in the visitor, they are replaced with flux expressions - these flux expressions are constructed from the last ReactionStatement - by default they are 0, if no ReactionStatement has been seen Resolves BlueBrain/nmodl#72 NMODL Repo SHA: BlueBrain/nmodl@ef79e2aa364e771bd9c7322076bf5b0258e9b572 --- src/nmodl/lexer/nmodl.ll | 11 ++- src/nmodl/parser/nmodl.yy | 8 ++ src/nmodl/visitors/kinetic_block_visitor.cpp | 96 ++++++++++++++------ src/nmodl/visitors/kinetic_block_visitor.hpp | 7 ++ test/nmodl/transpiler/visitor/visitor.cpp | 59 +++++++++++- 5 files changed, 147 insertions(+), 34 deletions(-) diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 63533bbf13..ece9d58241 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -199,11 +199,18 @@ ELSE { /** value is not used */ return token_symbol(yytext, loc, type); } else { - + /** if flux variable is used in the kinetic block */ + if ( lexcontext == Token::KINETIC && + (strcmp(yytext, "f_flux") == 0 || strcmp(yytext, "b_flux") == 0)) { + nmodl::ast::Name value( new nmodl::ast::String(yytext) ); + ModToken tok(yytext, Token::FLUX_VAR, loc); + value.set_token(tok); + return NmodlParser::make_FLUX_VAR(value, loc); + } /** Check if name is already defined as macro. If so, return token * as integer with token as it's name. Otherwise return it as * regular name token. */ - if (driver.is_defined_var(yytext)) { + else if (driver.is_defined_var(yytext)) { auto value = driver.get_defined_var_value(yytext); return integer_symbol(value, loc, yytext); } else { diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 1f03310bcd..8849a6f803 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -189,6 +189,7 @@ %token <std::string> LINE_COMMENT %token <std::string> LINE_PART %token <ast::String> STRING +%token <ast::Name> FLUX_VAR %token <ModToken> OPEN_BRACE "{" %token <ModToken> CLOSE_BRACE "}" %token <ModToken> OPEN_PARENTHESIS "(" @@ -350,6 +351,7 @@ %type <ast::Integer*> INTEGER_PTR %type <ast::Name*> NAME_PTR %type <ast::String*> STRING_PTR +%type <ast::WrappedExpression*> flux_variable /** Precedence and Associativity : specify operator precedency and * associativity (from lower to higher. Note that '^' represent @@ -1085,6 +1087,7 @@ intexpr : Name { $$ = $1; } expr : varname { $$ = $1; } + | flux_variable { $$ = $1; } | real units { if($2) @@ -2229,6 +2232,11 @@ valence : { $$ = nullptr; } } ; + flux_variable : FLUX_VAR + { + $$ = new ast::WrappedExpression($1.clone()); + } + ; %% diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 3153964bb7..5be51057f1 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -192,10 +192,73 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) node->visit_children(this); in_reaction_statement = false; + // generate fluxes + modfile_fflux = rate_eqs.k_f.back(); + modfile_bflux = rate_eqs.k_b.back(); + + // contribution from state vars + for (int j = 0; j < state_var_count; ++j) { + std::string multiply_var = std::string("*").append(state_var[j]); + int nu_L = rate_eqs.nu_L[i_statement][j]; + while (nu_L-- > 0) { + modfile_fflux += multiply_var; + } + int nu_R = rate_eqs.nu_R[i_statement][j]; + while (nu_R-- > 0) { + modfile_bflux += multiply_var; + } + } + // contribution from non-state vars + if (!non_state_var_fflux[i_statement].empty()) { + modfile_fflux += std::string("*").append(non_state_var_fflux[i_statement]); + } + if (!non_state_var_bflux[i_statement].empty()) { + modfile_bflux += std::string("*").append(non_state_var_bflux[i_statement]); + } + fflux.emplace_back(modfile_fflux); + bflux.emplace_back(modfile_bflux); + + // for substituting into modfile, empty flux should be 0 + if (modfile_fflux.empty()) { + modfile_fflux = "0"; + } + if (modfile_bflux.empty()) { + modfile_bflux = "0"; + } + + logger->debug("KineticBlockVisitor :: fflux[{}] = {}", i_statement, fflux[i_statement]); + logger->debug("KineticBlockVisitor :: bflux[{}] = {}", i_statement, bflux[i_statement]); + // increment statement counter ++i_statement; } +std::shared_ptr<ast::Expression> create_expr(const std::string& str_expr) { + auto statement = create_statement("dummy = " + str_expr); + auto expr = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement)->get_expression(); + return std::dynamic_pointer_cast<ast::BinaryExpression>(expr)->get_rhs(); +} + +void KineticBlockVisitor::visit_wrapped_expression(ast::WrappedExpression* node) { + // If a wrapped expression contains a variable with name "f_flux" or "b_flux", + // this variable should be replaced by the expression for the corresponding flux + // which depends on the previous reaction statement. The current expressions are + // stored as strings in "modfile_fflux" and "modfile_bflux" + if (node->get_expression()->is_name()) { + auto var_name = std::dynamic_pointer_cast<ast::Name>(node->get_expression()); + if (var_name->get_node_name() == "f_flux") { + auto expr = create_expr(modfile_fflux); + logger->debug("KineticBlockVisitor :: replacing f_flux with {}", to_nmodl(expr.get())); + node->set_expression(std::move(expr)); + } else if (var_name->get_node_name() == "b_flux") { + auto expr = create_expr(modfile_bflux); + logger->debug("KineticBlockVisitor :: replacing b_flux with {}", to_nmodl(expr.get())); + node->set_expression(std::move(expr)); + } + } + node->visit_children(this); +} + void KineticBlockVisitor::visit_statement_block(ast::StatementBlock* node) { auto prev_statement_block = current_statement_block; current_statement_block = node; @@ -213,13 +276,15 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { fflux.clear(); bflux.clear(); odes.clear(); + modfile_fflux = "0"; + modfile_bflux = "0"; // allocate these vectors with empty strings compartment_factors = std::vector<std::string>(state_var_count); additive_terms = std::vector<std::string>(state_var_count); i_statement = 0; - // construct stochiometric matrices and rate vectors + // construct stoichiometric matrices and fluxes node->visit_children(this); // number of reaction statements @@ -228,35 +293,6 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { // number of ODEs (= number of state vars) int Nj = state_var_count; - // generate fluxes - fflux = rate_eqs.k_f; - bflux = rate_eqs.k_b; - for (int i = 0; i < Ni; ++i) { - // contribution from state vars - for (int j = 0; j < Nj; ++j) { - std::string multiply_var = std::string("*").append(state_var[j]); - int nu_L = rate_eqs.nu_L[i][j]; - while (nu_L-- > 0) { - fflux[i] += multiply_var; - } - int nu_R = rate_eqs.nu_R[i][j]; - while (nu_R-- > 0) { - bflux[i] += multiply_var; - } - } - // contribution from non-state vars - if (!non_state_var_fflux[i].empty()) { - fflux[i] += std::string("*").append(non_state_var_fflux[i]); - } - if (!non_state_var_bflux[i].empty()) { - bflux[i] += std::string("*").append(non_state_var_bflux[i]); - } - } - for (int i = 0; i < Ni; ++i) { - logger->debug("KineticBlockVisitor :: fflux[{}] = {}", i, fflux[i]); - logger->debug("KineticBlockVisitor :: bflux[{}] = {}", i, bflux[i]); - } - // generate ODEs for (int j = 0; j < Nj; ++j) { // rhs of ODE eq diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 413fa5716b..7d97080b2a 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -65,6 +65,12 @@ class KineticBlockVisitor: public AstVisitor { std::vector<std::string> bflux; std::vector<std::string> odes; + /// current expressions for the `fflux`, `bflux` variables that can be used in the mod file + /// and that are determined by the preceeding kinetic reaction statement, i.e. their + /// value changes depending on their location inside the kinetic block + std::string modfile_fflux; + std::string modfile_bflux; + /// number of state variables int state_var_count = 0; @@ -95,6 +101,7 @@ class KineticBlockVisitor: public AstVisitor { public: KineticBlockVisitor() = default; + void visit_wrapped_expression(ast::WrappedExpression* node) override; void visit_reaction_operator(ast::ReactionOperator* node) override; void visit_react_var_name(ast::ReactVarName* node) override; void visit_reaction_statement(ast::ReactionStatement* node) override; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 255c85dd30..dadb000a5f 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2172,6 +2172,28 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with << reaction statement, 1 array state var, flux vars") { + std::string input_nmodl_text = R"( + STATE { + x[1] + } + KINETIC states { + ~ x[0] << (a*c/3.2) + f0 = f_flux*2 + f1 = b_flux + f_flux + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + f0 = 0*2 + f1 = 0+0 + x'[0] = (a*c/3.2) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with invalid << reaction statement with 2 state vars") { std::string input_nmodl_text = R"( STATE { @@ -2189,16 +2211,20 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } - GIVEN("KINETIC block with -> reaction statement, 1 state var") { + GIVEN("KINETIC block with -> reaction statement, 1 state var, flux vars") { std::string input_nmodl_text = R"( STATE { x } KINETIC states { ~ x -> (a) + zf = f_flux + zb = b_flux })"; std::string output_nmodl_text = R"( DERIVATIVE states { + zf = a*x + zb = 0 x' = (-1*(a*x)) })"; THEN("Convert to equivalent DERIVATIVE block") { @@ -2265,7 +2291,7 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } - GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var") { + GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var, flux vars") { // Here c is NOT a state variable // see 9.9.2.1 of NEURON book // c should be treated as a constant, i.e. @@ -2277,9 +2303,11 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { } KINETIC states { ~ x <-> c (r, r) + c1 = f_flux - b_flux })"; std::string output_nmodl_text = R"( DERIVATIVE states { + c1 = r*x-r*c x' = (-1*(r*x-r*c)) })"; THEN("Convert to equivalent DERIVATIVE block") { @@ -2411,6 +2439,33 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with two dependent reaction statements, flux vars") { + std::string input_nmodl_text = R"( + STATE { + x y z + } + KINETIC states { + c0 = f_flux + ~ x <-> y (a, b) + c1 = f_flux + b_flux + ~ y <-> z (c, d) + c2 = f_flux - 2*b_flux + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + c0 = 0 + c1 = a*x+b*y + c2 = c*y-2*d*z + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) + z' = (1*(c*y-d*z)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with a stoch coeff of 2") { std::string input_nmodl_text = R"( STATE { From 8b659d98047fef147e72874b9598198b348ebde7 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 1 Apr 2019 14:19:37 +0200 Subject: [PATCH 183/871] Add conditional definition type to DefUse chains (BlueBrain/nmodl#124) - new state CD is added to the DefUse chain to indicate variable is conditionally defined - if every block has D or CD, then the variable can be effectively localized in the block - update localize visitor - add test for conditional definition Resolves BlueBrain/nmodl#123 Change-Id: Ic9017726605b8ca785802802c6a4bcfd618de375 NMODL Repo SHA: BlueBrain/nmodl@9674721b9a8d0ac03865cbf9cf5c7ed1fb140bc4 --- src/nmodl/visitors/defuse_analyze_visitor.cpp | 56 +++++++++++-------- src/nmodl/visitors/defuse_analyze_visitor.hpp | 2 + src/nmodl/visitors/localize_visitor.cpp | 53 ++++++++++-------- src/nmodl/visitors/localize_visitor.hpp | 9 +-- test/nmodl/transpiler/visitor/visitor.cpp | 30 +++++++++- 5 files changed, 96 insertions(+), 54 deletions(-) diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 9db97ab806..af0bcd63ff 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -21,6 +21,8 @@ std::string to_string(DUState state) { return "U"; case DUState::D: return "D"; + case DUState::CD: + return "CD"; case DUState::LU: return "LU"; case DUState::LD: @@ -88,35 +90,43 @@ DUState DUInstance::sub_block_eval() { result = child_state; break; } + if (child_state == DUState::CD) { + result = DUState::CD; + } } return result; } -/** Evaluate conditional block that contain sub-blocks like if, elseif and else. - * Note that sub-blocks are already evaluated by sub_block_eval() and has only - * single value. In order to find effective usage, we are using following rules: +/** + * Evaluate conditional block containing sub-blocks like if, elseif and else + * + * Note that sub-blocks are already evaluated by sub_block_eval() and has only + * leaf nodes. In order to find effective defuse, following rules are used: + * * - If variable is "used" in any of the sub-block then it's effectively - * "U". This is because any branch can be taken. + * "U". This is because any branch can be taken at runtime. + * * - If variable is "defined" in all sub-blocks doesn't mean that it's - * effectively "D". This is because if we can just have "if-elseif" - * which could be never be taken. Same for empty "if". In order to - * decide if it is "D", we make sure there is no empty block and there - * must be "else" block with "D". Note that "U" definitions are already - * covered in 1) and hence this rule is safe. + * effectively "D". This is because if we can have just "if-elseif" + * which could never be taken. Same for empty "if". In order to decide + * if it is "D", we make sure there is no empty block and there must + * be "else" block with "D". Note that "U" definitions are already + * covered in 1) and hence this rule is safe. + * * - If there is an "if" with "D" or empty "if" followed by "D" in "else" - * block, we can't say it's definition. In this case we return "NONE" - * which is safe. + * block, we can't say it's definition. In this case we return "CD". + * * - If there is empty "if" followed by "U" in "else" block, we can say - * it's "use". This is because for optimizations we don't want to "localize" - * this type of variable. This needs to be changed. + * it's "use". This is because we don't want to "localize" such variables. * - * \todo: Need to introduce new states like "conditional definition" to make that - * the variable is "can be" definition. And then we have to return appropriate - * state so that more analysis can be enabled. + * - If we reach else block with either D or CD and if there is no empty + * block encountered, this means every block has either "D" or "CD". In + * this case we can say that entire block effectively has "D". */ DUState DUInstance::conditional_block_eval() { DUState result = DUState::NONE; bool block_with_none = false; + bool block_non_def_cdef = false; for (auto& chain: children) { auto child_state = chain.eval(); @@ -127,13 +137,12 @@ DUState DUInstance::conditional_block_eval() { if (child_state == DUState::NONE) { block_with_none = true; } - if (chain.state == DUState::ELSE && child_state == DUState::D) { - if (block_with_none) { - result = DUState::NONE; - } else { - result = child_state; + if (child_state == DUState::D || child_state == DUState::CD) { + result = DUState::CD; + if (chain.state == DUState::ELSE && !block_with_none) { + result = DUState::D; + break; } - break; } } return result; @@ -163,6 +172,9 @@ DUState DUChain::eval() { result = re; break; } + if (re == DUState::CD) { + result = re; + } } return result; } diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index a97453084b..0d1f3f4d33 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -26,6 +26,8 @@ enum class DUState { U, /// global variable is defined D, + /// global variable is conditionally defined + CD, /// local variable is used LU, /// local variable is used diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 19c08b83a1..359948367b 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -99,10 +99,11 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { } void LocalizeVisitor::visit_program(ast::Program* node) { - /// symtab visitor pass must be run before + /// symtab visitor pass need to be run before program_symtab = node->get_symbol_table(); if (program_symtab == nullptr) { - throw std::runtime_error("localizer error : program node doesn't have symbol table"); + logger->warn("LocalizeVisitor :: symbol table is not setup, returning"); + return; } auto variables = variables_to_optimize(); @@ -124,28 +125,34 @@ void LocalizeVisitor::visit_program(ast::Program* node) { /// variable then we can't localize the variable auto it = block_usage.find(DUState::U); if (it == block_usage.end()) { - /// all blocks that are using variable should get local variable - for (auto& block: block_usage[DUState::D]) { - auto block_ptr = dynamic_cast<ast::Block*>(block.get()); - auto statement_block = block_ptr->get_statement_block(); - ast::LocalVar* variable; - auto symbol = program_symtab->lookup(varname); - if (symbol->is_array()) { - variable = add_local_variable(statement_block.get(), varname, - symbol->get_length()); - } else { - variable = add_local_variable(statement_block.get(), varname); + logger->debug("LocalizeVisitor : localized variable {}", varname); + + /// all blocks that are have either definition or conditional definition + /// need local variable + for (auto state: {DUState::D, DUState::CD}) { + for (auto& block: block_usage[state]) { + auto block_ptr = dynamic_cast<ast::Block*>(block.get()); + auto statement_block = block_ptr->get_statement_block(); + ast::LocalVar* variable; + auto symbol = program_symtab->lookup(varname); + + if (symbol->is_array()) { + variable = add_local_variable(statement_block.get(), varname, + symbol->get_length()); + } else { + variable = add_local_variable(statement_block.get(), varname); + } + + /// mark variable as localized in global symbol table + symbol->mark_localized(); + + /// insert new symbol in the symbol table of current block + auto symtab = statement_block->get_symbol_table(); + auto new_symbol = std::make_shared<Symbol>(varname, variable); + new_symbol->add_property(NmodlType::local_var); + new_symbol->mark_created(); + symtab->insert(new_symbol); } - - /// mark variable as localized in global symbol table - symbol->mark_localized(); - - /// insert new symbol into symbol table - auto symtab = statement_block->get_symbol_table(); - auto new_symbol = std::make_shared<Symbol>(varname, variable); - new_symbol->add_property(NmodlType::local_var); - new_symbol->mark_created(); - symtab->insert(new_symbol); } } } diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 536c99021d..ea3558fc73 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -13,6 +13,7 @@ #include "ast/ast.hpp" #include "printer/json_printer.hpp" #include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/rename_visitor.hpp" @@ -70,14 +71,6 @@ namespace nmodl { * - We are excluding procedures/functions because they will be still using global * variables. We need to have dead-code removal pass to eliminate unused procedures/ * functions before localizer pass. - * - For conditional block like below we are returning usage as NONE. May be better to - * return COND_D so that localizer can declare tau as LOCAL (this is artificial use - * case though) : - * BREAKPOINT { - * IF (1) { - * tau = 11 - * } - * } */ class LocalizeVisitor: public AstVisitor { diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index dadb000a5f..fa07384909 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -1399,7 +1399,7 @@ SCENARIO("Running defuse analyzer") { std::string input = reindent_text(nmodl_text); auto chains = run_defuse_visitor(input, "tau"); REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::NONE); + REQUIRE(chains[0].eval() == DUState::CD); } } @@ -1455,6 +1455,34 @@ SCENARIO("Running defuse analyzer") { } } + GIVEN("conditional definition in nested block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + IF(11) { + tau = 11.1 + exp(tau) + } + } ELSE IF(1) { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"},{"name":"U"}]}]}]},{"ELSEIF":[{"name":"D"}]}]}]})"; + + THEN("Def-Use chains should return DEF") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::CD); + } + } GIVEN("global variable usage in if-elseif-else block") { std::string nmodl_text = R"( From 96641f77a96e8bbb8b3d6cd322d72c2befece6a0 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 1 Apr 2019 14:54:04 +0200 Subject: [PATCH 184/871] Backend specific zero initialisation of ion variable (BlueBrain/nmodl#127) - c++ backend should have constructor for member initialization - c backend (e.g. ispc) uses C syntax for zero initialization Resolves BlueBrain/nmodl#125 Change-Id: Ie0dae410cde44752ec7c9bb60f40151e4e196511 NMODL Repo SHA: BlueBrain/nmodl@7e101bf33b3bdcafeaef0358a97f07a379d63a97 --- src/nmodl/codegen/codegen_c_visitor.cpp | 45 ++++++++++++++++++---- src/nmodl/codegen/codegen_c_visitor.hpp | 8 ++++ src/nmodl/codegen/codegen_ispc_visitor.cpp | 11 ++++++ src/nmodl/codegen/codegen_ispc_visitor.hpp | 6 +++ src/nmodl/printer/code_printer.cpp | 4 +- src/nmodl/printer/code_printer.hpp | 2 +- 6 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index f88bc9fc04..4e84d24a99 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1579,7 +1579,7 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" setup_instance(nt, ml);"); printer->add_line(" {0}* inst = ({0}*) ml->instance;"_format(instance_struct())); printer->add_line(" double v = 0;"); - printer->add_line(" IonCurVar ionvar = {0};"); + printer->add_line(" IonCurVar ionvar;"); for (const auto& function: info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); @@ -1664,7 +1664,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->add_line("double v;"); printer->add_line("Datum* indexes;"); if (ion_variable_struct_required()) { - printer->add_line("IonCurVar ionvar = {0};"); + print_ion_variable(); } print_statement_block(node->get_variable_block().get(), false, false); @@ -2778,22 +2778,51 @@ void CodegenCVisitor::print_ion_var_structure() { printer->add_newline(2); printer->add_line("/** ion write variables */"); printer->start_block("struct IonCurVar"); + + std::string float_type = default_float_data_type(); + std::vector<std::string> members; + for (auto& ion: info.ions) { for (auto& var: ion.writes) { - printer->add_line("{} {};"_format(default_float_data_type(), var)); + printer->add_line("{} {};"_format(float_type, var)); + members.push_back(var); } } for (auto& var: info.currents) { if (!info.is_ion_variable(var)) { - printer->add_line("{} {};"_format(default_float_data_type(), var)); + printer->add_line("{} {};"_format(float_type, var)); + members.push_back(var); } } + + print_ion_var_constructor(members); + printer->end_block(); printer->add_text(";"); printer->add_newline(); } +void CodegenCVisitor::print_ion_var_constructor(const std::vector<std::string>& members) { + /// constructor + printer->add_newline(); + printer->add_line("IonCurVar() : ", 0); + for (int i = 0; i < members.size(); i++) { + printer->add_text("{}(0)"_format(members[i])); + if (i + 1 < members.size()) { + printer->add_text(", "); + } + } + printer->add_text(" {}"); + printer->add_newline(); +} + + +void CodegenCVisitor::print_ion_variable() { + printer->add_line("IonCurVar ionvar;"); +} + + void CodegenCVisitor::print_global_variable_setup() { std::vector<std::string> allocated_variables; @@ -3066,7 +3095,7 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { } if (ion_variable_struct_required()) { - printer->add_line("IonCurVar ionvar = {0};"); + printer->add_line("IonCurVar ionvar;"); } /// read ion statements @@ -3701,7 +3730,7 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->start_block("int _newton_{}_{}({}) "_format(block_name, info.mod_suffix, external_method_parameters())); printer->add_line(instance); if (ion_variable_struct_required()) { - printer->add_line("IonCurVar ionvar = {0};"); + print_ion_variable(); } printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); printer->add_line(slist1); @@ -3775,7 +3804,7 @@ void CodegenCVisitor::print_nrn_state() { /// todo : eigen solver node also emits IonCurVar variable in the functor /// but that shouldn't update ions in derivative block if (ion_variable_struct_required()) { - printer->add_line("IonCurVar ionvar = {0};"); + print_ion_variable(); } auto read_statements = ion_read_statements(BlockType::State); @@ -3904,7 +3933,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); if (ion_variable_struct_required()) { - printer->add_line("IonCurVar ionvar = {0};"); + print_ion_variable(); } auto read_statements = ion_read_statements(BlockType::Equation); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 1e81a05df4..2eb4e78ed7 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -624,6 +624,14 @@ class CodegenCVisitor: public AstVisitor { void print_ion_var_structure(); + /// constructor of ion variables + virtual void print_ion_var_constructor(const std::vector<std::string>& members); + + + /// print ion variable + virtual void print_ion_variable(); + + /// return floating point type for given range variable symbol std::string get_range_var_float_type(const SymbolType& symbol); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 89c30acbc1..2356196a29 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -474,6 +474,17 @@ void CodegenIspcVisitor::print_ispc_globals() { } +void CodegenIspcVisitor::print_ion_var_constructor(const std::vector<std::string>& members) { + /// no constructor for ispc +} + + +void CodegenIspcVisitor::print_ion_variable() { + /// c syntax to zero initialize struct + printer->add_line("IonCurVar ionvar = {0};"); +} + + /****************************************************************************************/ /* Main code printing entry points and wrappers */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index ee93c0a21a..efcd8b7902 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -142,6 +142,12 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_net_receive_buffering_wrapper(); + void print_ion_var_constructor(const std::vector<std::string>& members) override; + + + void print_ion_variable() override; + + /// entry point to code generation void print_codegen_routines() override; diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index fd7f47cbb5..90188d1de2 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -47,10 +47,10 @@ void CodePrinter::add_text(const std::string& text) { *result << text; } -void CodePrinter::add_line(const std::string& text) { +void CodePrinter::add_line(const std::string& text, int num_new_lines) { add_indent(); *result << text; - add_newline(); + add_newline(num_new_lines); } void CodePrinter::add_multi_line(const std::string& text) { diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index bca2670a5f..d597a55751 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -52,7 +52,7 @@ class CodePrinter { void add_text(const std::string&); - void add_line(const std::string&); + void add_line(const std::string&, int num_new_lines = 1); void add_multi_line(const std::string&); From ba3729fb5fb963a108f431da3e38ff32fc16122b Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Mon, 1 Apr 2019 15:11:41 +0200 Subject: [PATCH 185/871] Code generation fixes for OpenACC backend : (BlueBrain/nmodl#129) - pragma acc parallel loop needs present clause with the list of arrays used in the loop body - include cuda_runtime_api.h header - avoid running host backends simultaenously, see BlueBrain/nmodl#128 - disable openacc annoitation for artificial cells - create global variables structure at the start and update onece it is initialized (see BlueBrain/mod2c/pull/28) Resolves BlueBrain/nmodl#101 Change-Id: I9a99ea85f1e94d2488fd26b66134870d3e12cdd8 NMODL Repo SHA: BlueBrain/nmodl@4f60b9b98bfed028f591ca780b5d71401118a690 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 46 +++++++++++++++++----- src/nmodl/codegen/codegen_acc_visitor.hpp | 8 +++- src/nmodl/codegen/codegen_c_visitor.cpp | 30 ++++++++++---- src/nmodl/codegen/codegen_c_visitor.hpp | 12 +++++- src/nmodl/codegen/codegen_cuda_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 2 +- src/nmodl/codegen/codegen_omp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_omp_visitor.hpp | 2 +- src/nmodl/nmodl/main.cpp | 25 ++++++------ 11 files changed, 96 insertions(+), 37 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 5ab088c8fb..7f6b9da1f3 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -31,18 +31,29 @@ namespace codegen { * for(int id=0; id<nodecount; id++) { * */ -void CodegenAccVisitor::print_channel_iteration_block_parallel_hint() { - printer->add_line("#pragma acc parallel loop"); +void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { + if (!info.artificial_cell) { + std::string present_clause = "present(node_index, data, voltage, indexes, thread"; + + if (type == BlockType::Equation) { + present_clause += ", vec_rhs, vec_d"; + } + present_clause += ")"; + printer->add_line("#pragma acc parallel loop {}"_format(present_clause)); + } } void CodegenAccVisitor::print_atomic_reduction_pragma() { - printer->add_line("#pragma acc atomic update"); + if (!info.artificial_cell) { + printer->add_line("#pragma acc atomic update"); + } } void CodegenAccVisitor::print_backend_includes() { printer->add_line("#include <cuda.h>"); + printer->add_line("#include <cuda_runtime_api.h>"); printer->add_line("#include <openacc.h>"); } @@ -82,10 +93,12 @@ void CodegenAccVisitor::print_memory_allocation_routine() { * } */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { - auto global_variable = "{}_global"_format(info.mod_suffix); - printer->add_line("#pragma acc data present(nt, ml, {})"_format(global_variable)); - printer->add_line("{"); - printer->increase_indent(); + if (!info.artificial_cell) { + auto global_variable = "{}_global"_format(info.mod_suffix); + printer->add_line("#pragma acc data present(nt, ml, {})"_format(global_variable)); + printer->add_line("{"); + printer->increase_indent(); + } } @@ -108,8 +121,10 @@ void CodegenAccVisitor::print_nrn_cur_matrix_shadow_reduction() { * End of print_kernel_enter_data_begin */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_end() { - printer->decrease_indent(); - printer->add_line("}"); + if (!info.artificial_cell) { + printer->decrease_indent(); + printer->add_line("}"); + } } @@ -122,5 +137,18 @@ bool CodegenAccVisitor::nrn_cur_reduction_loop_required() { return false; } + +void CodegenAccVisitor::print_global_variable_device_create_annotation() { + if (!info.artificial_cell) { + printer->add_line("#pragma acc declare create ({}_global)"_format(info.mod_suffix)); + } +} + +void CodegenAccVisitor::print_global_variable_device_update_annotation() { + if (!info.artificial_cell) { + printer->add_line("#pragma acc update device ({}_global)"_format(info.mod_suffix)); + } +} + } // namespace codegen } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index b1b8b79e9d..3c368277f6 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -28,7 +28,7 @@ class CodegenAccVisitor: public CodegenCVisitor { /// ivdep like annotation for channel iterations - void print_channel_iteration_block_parallel_hint() override; + void print_channel_iteration_block_parallel_hint(BlockType type) override; /// atomic update pragma for reduction statements @@ -63,6 +63,12 @@ class CodegenAccVisitor: public CodegenCVisitor { bool nrn_cur_reduction_loop_required() override; + /// create global variable on the device + void print_global_variable_device_create_annotation() override; + + /// update global variable from host to the device + void print_global_variable_device_update_annotation() override; + public: CodegenAccVisitor(std::string mod_file, std::string output_dir, diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 4e84d24a99..128a749d72 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1058,7 +1058,7 @@ void CodegenCVisitor::print_kernel_data_present_annotation_block_end() { * for(int id = 0; id < nodecount; id++) { * */ -void CodegenCVisitor::print_channel_iteration_block_parallel_hint() { +void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { printer->add_line("#pragma ivdep"); } @@ -1080,8 +1080,8 @@ bool CodegenCVisitor::shadow_vector_setup_required() { * For CPU backend we iterate over all node counts. For cuda we use thread * index to check if block needs to be executed or not. */ -void CodegenCVisitor::print_channel_iteration_block_begin() { - print_channel_iteration_block_parallel_hint(); +void CodegenCVisitor::print_channel_iteration_block_begin(BlockType type) { + print_channel_iteration_block_parallel_hint(type); printer->start_block("for (int id = start; id < end; id++) "); } @@ -2453,6 +2453,9 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { printer->add_newline(1); printer->add_line("/** holds object of global variable */"); printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + + /// create copy on the device + print_global_variable_device_create_annotation(); } @@ -2823,6 +2826,16 @@ void CodegenCVisitor::print_ion_variable() { } +void CodegenCVisitor::print_global_variable_device_create_annotation() { + // nothing for cpu +} + + +void CodegenCVisitor::print_global_variable_device_update_annotation() { + // nothing for cpu +} + + void CodegenCVisitor::print_global_variable_setup() { std::vector<std::string> allocated_variables; @@ -2932,6 +2945,9 @@ void CodegenCVisitor::print_global_variable_setup() { } } + /// update device copy + print_global_variable_device_update_annotation(); + printer->add_newline(); printer->add_line("setup_done = 1;"); printer->end_block(3); @@ -3185,7 +3201,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { } print_channel_iteration_tiling_block_begin(BlockType::Initial); - print_channel_iteration_block_begin(); + print_channel_iteration_block_begin(BlockType::Initial); print_post_channel_iteration_common_code(); @@ -3279,7 +3295,7 @@ void CodegenCVisitor::print_watch_check() { printer->add_line("/** routine to check watch activation */"); print_global_function_common_code(BlockType::Watch); print_channel_iteration_tiling_block_begin(BlockType::Watch); - print_channel_iteration_block_begin(); + print_channel_iteration_block_begin(BlockType::Watch); print_post_channel_iteration_common_code(); for (int i = 0; i < info.watch_statements.size(); i++) { @@ -3795,7 +3811,7 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); print_channel_iteration_tiling_block_begin(BlockType::State); - print_channel_iteration_block_begin(); + print_channel_iteration_block_begin(BlockType::State); print_post_channel_iteration_common_code(); printer->add_line("int node_id = node_index[id];"); @@ -3976,7 +3992,7 @@ void CodegenCVisitor::print_nrn_cur() { printer->add_line("/** update current */"); print_global_function_common_code(BlockType::Equation); print_channel_iteration_tiling_block_begin(BlockType::Equation); - print_channel_iteration_block_begin(); + print_channel_iteration_block_begin(BlockType::Equation); print_post_channel_iteration_common_code(); print_nrn_cur_kernel(info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 2eb4e78ed7..bdea75a3fb 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -680,6 +680,14 @@ class CodegenCVisitor: public AstVisitor { void print_global_variable_setup(); + /// pragma annotation to create global variables on the device + virtual void print_global_variable_device_create_annotation(); + + + /// pragma annotation to update global variables from host to the device + virtual void print_global_variable_device_update_annotation(); + + /// setup method for allocation of shadow vectors void print_shadow_vector_setup(); @@ -729,7 +737,7 @@ class CodegenCVisitor: public AstVisitor { /// ivdep like annotation for channel iterations - virtual void print_channel_iteration_block_parallel_hint(); + virtual void print_channel_iteration_block_parallel_hint(BlockType type); /// annotations like "acc enter data present(...)" for main kernel @@ -741,7 +749,7 @@ class CodegenCVisitor: public AstVisitor { /// backend specific channel instance iteration block start - virtual void print_channel_iteration_block_begin(); + virtual void print_channel_iteration_block_begin(BlockType type); /// backend specific channel instance iteration block end diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index dbb3375609..87a35669d5 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -107,7 +107,7 @@ void CodegenCudaVisitor::print_nrn_cur_matrix_shadow_update() { * For GPU backend its thread id less than total channel instances. Below we * assume we launch 1-d grid. */ -void CodegenCudaVisitor::print_channel_iteration_block_begin() { +void CodegenCudaVisitor::print_channel_iteration_block_begin(BlockType type) { printer->add_line("int id = blockIdx.x * blockDim.x + threadIdx.x;"); printer->start_block("if (id < end) "); } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 5aa36ab161..0031938541 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -51,7 +51,7 @@ class CodegenCudaVisitor: public CodegenCVisitor { /// backend specific channel instance iteration block start - void print_channel_iteration_block_begin() override; + void print_channel_iteration_block_begin(BlockType type) override; /// backend specific channel instance iteration block end diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 2356196a29..81cf1a1baf 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -180,7 +180,7 @@ void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType ty * * Use ispc foreach loop */ -void CodegenIspcVisitor::print_channel_iteration_block_begin() { +void CodegenIspcVisitor::print_channel_iteration_block_begin(BlockType type) { printer->start_block("foreach (id = start ... end)"); } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index efcd8b7902..031920fb9a 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -80,7 +80,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// backend specific channel instance iteration block start - void print_channel_iteration_block_begin() override; + void print_channel_iteration_block_begin(BlockType type) override; /// backend specific channel iteration bounds diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp index 01ae4f97d4..6996b1f07b 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -72,7 +72,7 @@ void CodegenOmpVisitor::print_channel_iteration_tiling_block_end() { * for(int id=0; id<nodecount; id++) { * */ -void CodegenOmpVisitor::print_channel_iteration_block_parallel_hint() { +void CodegenOmpVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { printer->add_line("#pragma omp simd"); } diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index 3517f79f4f..b516f683ec 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -48,7 +48,7 @@ class CodegenOmpVisitor: public CodegenCVisitor { /// ivdep like annotation for channel iterations - void print_channel_iteration_block_parallel_hint() override; + void print_channel_iteration_block_parallel_hint(BlockType type) override; /// atomic update pragma for reduction statements diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index f2a89f9dd2..4fef04a88d 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -359,17 +359,6 @@ int main(int argc, const char* argv[]) { { auto mem_layout = layout == "aos" ? codegen::LayoutType::aos : codegen::LayoutType::soa; - if (c_backend) { - logger->info("Running C backend code generator"); - CodegenCVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); - } - - if (omp_backend) { - logger->info("Running OpenMP backend code generator"); - CodegenOmpVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); - } if (ispc_backend) { logger->info("Running ISPC backend code generator"); @@ -377,12 +366,24 @@ int main(int argc, const char* argv[]) { visitor.visit_program(ast.get()); } - if (oacc_backend) { + else if (oacc_backend) { logger->info("Running OpenACC backend code generator"); CodegenAccVisitor visitor(modfile, output_dir, mem_layout, data_type); visitor.visit_program(ast.get()); } + else if (omp_backend) { + logger->info("Running OpenMP backend code generator"); + CodegenOmpVisitor visitor(modfile, output_dir, mem_layout, data_type); + visitor.visit_program(ast.get()); + } + + else if (c_backend) { + logger->info("Running C backend code generator"); + CodegenCVisitor visitor(modfile, output_dir, mem_layout, data_type); + visitor.visit_program(ast.get()); + } + if (cuda_backend) { logger->info("Running CUDA backend code generator"); CodegenCudaVisitor visitor(modfile, output_dir, mem_layout, data_type); From 52bb9dcda7510b50c6580ba05c1fa1a8b16643ef Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@gmail.com> Date: Mon, 1 Apr 2019 15:38:56 +0200 Subject: [PATCH 186/871] ISPC codegen compatibility fixes (BlueBrain/nmodl#105) This PR covers numerous fixes in the ISPC (and C) code generators working around many of real-world corner cases, which had been missed (or postponed) before: * Fallback to C when VERBATIM nodes are found in AST * Allow AstLookupVisitor::lookup to be called without a type * Uses an additional instance of CodegenCVisistor that is constructed inside the ispc code generator as a fallback code generator. * Removed the whole-file VERBATIM fallback in the main function. * added missing constants * filtered kernel routines, which contain verbatim blocks * emit uncalled procedures and functions in C wrapper * don't emit `printf` debug code * rename PI into ISPC_PI to avoid name clashes with ispc constant * emit functions that use tables in C * Make sure that global data is shared between wrapper and ispc target * Exclude Eigen node types from ISPC backend This closes BlueBrain/nmodl#83 NMODL Repo SHA: BlueBrain/nmodl@b8fc1301a3b4fef1a37b7d4b678779fb5af4d8a9 --- src/nmodl/codegen/codegen_c_visitor.cpp | 21 +- src/nmodl/codegen/codegen_c_visitor.hpp | 109 ++++--- src/nmodl/codegen/codegen_helper_visitor.cpp | 10 + src/nmodl/codegen/codegen_info.hpp | 5 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 270 +++++++++++++++--- src/nmodl/codegen/codegen_ispc_visitor.hpp | 37 ++- .../language/templates/lookup_visitor.cpp | 7 + .../language/templates/lookup_visitor.hpp | 2 + src/nmodl/language/templates/pyvisitor.cpp | 1 + src/nmodl/nmodl/main.cpp | 1 + src/nmodl/visitors/visitor_utils.cpp | 12 + src/nmodl/visitors/visitor_utils.hpp | 13 + 12 files changed, 389 insertions(+), 99 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 128a749d72..fb45b46c70 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1949,9 +1949,9 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& void CodegenCVisitor::print_nmodl_constant() { printer->add_newline(2); printer->add_line("/** constants used in nmodl */"); - printer->add_line("static double FARADAY = 96485.3;"); - printer->add_line("static double PI = 3.14159;"); - printer->add_line("static double R = 8.3145;"); + printer->add_line("static const double FARADAY = 96485.3;"); + printer->add_line("static const double PI = 3.14159;"); + printer->add_line("static const double R = 8.3145;"); } @@ -2323,7 +2323,7 @@ void CodegenCVisitor::print_coreneuron_includes() { * Note that static variables are already initialized to 0. We do the * same for some variables to keep same code as neuron. */ -void CodegenCVisitor::print_mechanism_global_var_structure() { +void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); @@ -3796,6 +3796,7 @@ void CodegenCVisitor::visit_solution_expression(SolutionExpression* node) { } } + /****************************************************************************************/ /* Print nrn_state routine */ /****************************************************************************************/ @@ -4107,7 +4108,12 @@ void CodegenCVisitor::print_wrapper_routines() { } -void CodegenCVisitor::visit_program(Program* node) { +void CodegenCVisitor::set_codegen_global_variables(std::vector<SymbolType>& global_vars) { + codegen_global_variables = global_vars; +} + + +void CodegenCVisitor::setup(Program* node) { program_symtab = node->get_symbol_table(); CodegenHelperVisitor v; @@ -4124,6 +4130,11 @@ void CodegenCVisitor::visit_program(Program* node) { update_index_semantics(); rename_function_arguments(); +} + + +void CodegenCVisitor::visit_program(Program* node) { + setup(node); print_codegen_routines(); print_wrapper_routines(); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index bdea75a3fb..3f81264593 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -47,7 +47,10 @@ enum class BlockType { State, /// watch block - Watch + Watch, + + /// net_receive block + NetReceive }; @@ -561,7 +564,7 @@ class CodegenCVisitor: public AstVisitor { /// nmodl constants - void print_nmodl_constant(); + virtual void print_nmodl_constant(); /// top header printed in generated code @@ -613,7 +616,7 @@ class CodegenCVisitor: public AstVisitor { /// structure that wraps all global variables in the mod file - virtual void print_mechanism_global_var_structure(); + virtual void print_mechanism_global_var_structure(bool wrapper = false); /// structure that wraps all range and int variables required for mod file @@ -764,26 +767,10 @@ class CodegenCVisitor: public AstVisitor { void print_function_prototypes(); - /// print check_function() for function/procedure using table - void print_table_check_function(ast::Block* node); - - - /// print replacement function for function/procedure using table - void print_table_replacement_function(ast::Block* node); - - /// print check_table functions void print_check_table_thread_function(); - /// nmodl function definition - void print_function(ast::FunctionBlock* node); - - - /// nmodl procedure definition - void print_procedure(ast::ProcedureBlock* node); - - /// print nmodl function or procedure (common code) void print_function_or_procedure(ast::Block* node, std::string& name); @@ -832,14 +819,6 @@ class CodegenCVisitor: public AstVisitor { virtual void print_net_receive_loop_end(); - /// kernel for buffering net_receive events - void print_net_receive_buffering(bool need_mech_inst = true); - - - /// net_receive kernel function definition - void print_net_receive_kernel(); - - /// net_receive function definition void print_net_receive(); @@ -901,18 +880,6 @@ class CodegenCVisitor: public AstVisitor { virtual void print_global_function_common_code(BlockType type); - /// nrn_init function definition - void print_nrn_init(bool skip_init_check = true); - - - /// nrn_state / state update function definition - void print_nrn_state(); - - - /// nrn_cur / current update function definition - void print_nrn_cur(); - - /// mechanism registration function void print_mechanism_register(); @@ -921,10 +888,6 @@ class CodegenCVisitor: public AstVisitor { void print_watch_activate(); - /// print watch activate function - void print_watch_check(); - - /// all includes virtual void print_headers_include(); @@ -994,6 +957,66 @@ class CodegenCVisitor: public AstVisitor { , layout(layout) , float_type(float_type) {} + + CodegenCVisitor(std::string mod_filename, + LayoutType layout, + std::string float_type, + std::shared_ptr<CodePrinter>& target_printer) + : target_printer(target_printer) + , printer(target_printer) + , mod_filename(mod_filename) + , layout(layout) + , float_type(float_type) {} + + + /// nrn_init function definition + void print_nrn_init(bool skip_init_check = true); + + + /// nrn_state / state update function definition + void print_nrn_state(); + + + /// nrn_cur / current update function definition + void print_nrn_cur(); + + + /// kernel for buffering net_receive events + void print_net_receive_buffering(bool need_mech_inst = true); + + + /// net_receive kernel function definition + void print_net_receive_kernel(); + + + /// print watch activate function + void print_watch_check(); + + + /// print check_function() for function/procedure using table + void print_table_check_function(ast::Block* node); + + + /// print replacement function for function/procedure using table + void print_table_replacement_function(ast::Block* node); + + + /// nmodl function definition + void print_function(ast::FunctionBlock* node); + + + /// nmodl procedure definition + virtual void print_procedure(ast::ProcedureBlock* node); + + + /** setup the Codgen, typically called from within visit_program but may be called from + * specialized targets to setup this Code generator as fallback. + */ + void setup(ast::Program* node); + + void set_codegen_global_variables(std::vector<SymbolType>& global_vars); + + virtual void visit_binary_expression(ast::BinaryExpression* node) override; virtual void visit_binary_operator(ast::BinaryOperator* node) override; virtual void visit_boolean(ast::Boolean* node) override; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index dd4fb69fb1..d20f597174 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -14,6 +14,7 @@ #include "visitors/rename_visitor.hpp" #include <fmt/format.h> +#include <set> using namespace fmt::literals; @@ -121,6 +122,15 @@ void CodegenHelperVisitor::find_ion_variables() { } } } + + /// check if worte_conc(...) will be needed + for (const auto& ion: info.ions) { + for (const auto& var: ion.writes) { + if (!ion.is_ionic_current(var) && !ion.is_rev_potential(var)) { + info.require_wrote_conc = true; + } + } + } } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 921656b44a..b5fba148f0 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -371,7 +371,10 @@ struct CodegenInfo { /// true if EigenNewtonSolver is used in nrn_state block bool nrn_state_has_eigen_solver_block() const; + + /// if we need a call back to wrote_conc in neuron/coreneuron + bool require_wrote_conc = false; }; } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 81cf1a1baf..fd2601a55b 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -12,7 +12,9 @@ #include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" #include "utils/string_utils.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" using namespace fmt::literals; @@ -33,6 +35,10 @@ void CodegenIspcVisitor::visit_function_call(ast::FunctionCall* node) { if (!codegen) { return; } + if (node->get_node_name() == "printf") { + logger->warn("Not emitted in ispc: {}"_format(to_nmodl(node))); + return; + } auto fname = node->get_name().get(); RenameVisitor("fabs", "abs").visit_name(fname); RenameVisitor("exp", "vexp").visit_name(fname); @@ -49,6 +55,8 @@ void CodegenIspcVisitor::visit_var_name(ast::VarName* node) { } auto celsius_rename = RenameVisitor("celsius", "ispc_celsius"); node->accept(&celsius_rename); + auto pi_rename = RenameVisitor("PI", "ISPC_PI"); + node->accept(&pi_rename); CodegenCVisitor::visit_var_name(node); } @@ -101,13 +109,16 @@ std::string CodegenIspcVisitor::float_to_string(float value) { std::string CodegenIspcVisitor::compute_method_name(BlockType type) { if (type == BlockType::Initial) { - return method_name("ispc_nrn_init"); + return method_name(naming::NRN_INIT_METHOD); } if (type == BlockType::State) { - return method_name("ispc_nrn_state"); + return method_name(naming::NRN_STATE_METHOD); } if (type == BlockType::Equation) { - return method_name("ispc_nrn_cur"); + return method_name(naming::NRN_CUR_METHOD); + } + if (type == BlockType::Watch) { + return method_name(naming::NRN_WATCH_CHECK_METHOD); } throw std::runtime_error("compute_method_name not implemented"); } @@ -266,6 +277,14 @@ CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( } +void CodegenIspcVisitor::print_procedure(ast::ProcedureBlock* node) { + codegen = true; + auto name = node->get_node_name(); + print_function_or_procedure(node, name); + codegen = false; +} + + void CodegenIspcVisitor::print_global_function_common_code(BlockType type) { std::string method = compute_method_name(type); @@ -294,8 +313,6 @@ void CodegenIspcVisitor::print_global_function_common_code(BlockType type) { void CodegenIspcVisitor::print_compute_functions() { - // print_top_verbatim_blocks(); @todo: see where to add this - for (const auto& function: info.functions) { if (!program_symtab->lookup(function->get_node_name()) .get() @@ -310,16 +327,29 @@ void CodegenIspcVisitor::print_compute_functions() { print_procedure(procedure); } } - print_net_receive_kernel(); - print_net_receive_buffering(false); - print_nrn_init(false); - print_nrn_cur(); - print_nrn_state(); + if (!emit_fallback[BlockType::NetReceive]) { + print_net_receive_kernel(); + print_net_receive_buffering(false); + } + if (!emit_fallback[BlockType::Initial]) { + print_nrn_init(false); + } + if (!emit_fallback[BlockType::Equation]) { + print_nrn_cur(); + } + if (!emit_fallback[BlockType::State]) { + print_nrn_state(); + } } // @todo : use base visitor function with provision to override specific qualifiers -void CodegenIspcVisitor::print_mechanism_global_var_structure() { +void CodegenIspcVisitor::print_mechanism_global_var_structure(bool wrapper) { + std::string decorator = ""; + if (!wrapper) { + decorator = "uniform "; + } + auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); @@ -329,13 +359,13 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure() { if (!info.ions.empty()) { for (const auto& ion: info.ions) { auto name = "{}_type"_format(ion.name); - printer->add_line("int {};"_format(name)); + printer->add_line("{}int {};"_format(decorator, name)); codegen_global_variables.push_back(make_symbol(name)); } } if (info.point_process) { - printer->add_line("int point_type;"); + printer->add_line("{}int point_type;"_format(decorator)); codegen_global_variables.push_back(make_symbol("point_type")); } @@ -344,14 +374,14 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure() { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(decorator, float_type, name)); codegen_global_variables.push_back(make_symbol(name)); } } } if (!info.vectorize) { - printer->add_line("{} v;"_format(float_type)); + printer->add_line("{}{} v;"_format(decorator, float_type)); codegen_global_variables.push_back(make_symbol("v")); } @@ -361,27 +391,28 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line("{} {}[{}];"_format(float_type, name, length)); + printer->add_line("{}{} {}[{}];"_format(decorator, float_type, name, length)); } else { - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(decorator, float_type, name)); } codegen_global_variables.push_back(var); } } if (!info.thread_variables.empty()) { - printer->add_line("int thread_data_in_use;"); - printer->add_line("{} thread_data[{}];"_format(float_type, info.thread_var_data_size)); + printer->add_line("{}int thread_data_in_use;"_format(decorator)); + printer->add_line( + "{}{} thread_data[{}];"_format(decorator, float_type, info.thread_var_data_size)); codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); symbol->set_as_array(info.thread_var_data_size); codegen_global_variables.push_back(symbol); } - printer->add_line("int reset;"); + printer->add_line("{}int reset;"_format(decorator)); codegen_global_variables.push_back(make_symbol("reset")); - printer->add_line("int mech_type;"); + printer->add_line("{}int mech_type;"_format(decorator)); codegen_global_variables.push_back(make_symbol("mech_type")); auto& globals = info.global_variables; @@ -392,9 +423,9 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line("{} {}[{}];"_format(float_type, name, length)); + printer->add_line("{}{} {}[{}];"_format(decorator, float_type, name, length)); } else { - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(decorator, float_type, name)); } codegen_global_variables.push_back(var); } @@ -404,43 +435,43 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure() { for (const auto& var: constants) { auto name = var->get_name(); auto value_ptr = var->get_value(); - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(decorator, float_type, name)); codegen_global_variables.push_back(var); } } if (info.primes_size != 0) { - printer->add_line("int* {} slist1;"_format("")); - printer->add_line("int* {} dlist1;"_format("")); + printer->add_line("int* {}slist1;"_format(decorator)); + printer->add_line("int* {}dlist1;"_format(decorator)); codegen_global_variables.push_back(make_symbol("slist1")); codegen_global_variables.push_back(make_symbol("dlist1")); if (info.derivimplicit_used) { - printer->add_line("int* slist2;"); + printer->add_line("int* {}slist2;"_format(decorator)); codegen_global_variables.push_back(make_symbol("slist2")); } } if (info.table_count > 0) { - printer->add_line("double usetable;"); + printer->add_line("{}double usetable;"_format(decorator)); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); for (const auto& block: info.functions_with_table) { auto name = block->get_node_name(); - printer->add_line("{} tmin_{};"_format(float_type, name)); - printer->add_line("{} mfac_{};"_format(float_type, name)); + printer->add_line("{}{} tmin_{};"_format(decorator, float_type, name)); + printer->add_line("{}{} mfac_{};"_format(decorator, float_type, name)); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); } for (const auto& variable: info.table_statement_variables) { auto name = "t_" + variable->get_name(); - printer->add_line("{}* {};"_format(float_type, name)); + printer->add_line("{}* {}{};"_format(float_type, decorator, name)); codegen_global_variables.push_back(make_symbol(name)); } } if (info.vectorize) { - printer->add_line("ThreadDatum* {}ext_call_thread;"_format("")); + printer->add_line("ThreadDatum* {}ext_call_thread;"_format(decorator)); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } @@ -448,8 +479,15 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure() { printer->add_line("};"); printer->add_newline(1); - printer->add_line("/** holds object of global variable */"); - printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + if (wrapper) { + printer->add_line("/** holds object of global variable */"); + printer->start_block("extern \"C\""); + printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + printer->end_block(2); + } else { + printer->add_line("/** holds object of global variable */"); + printer->add_line("extern {} {}_global;"_format(global_struct(), info.mod_suffix)); + } } @@ -461,7 +499,7 @@ void CodegenIspcVisitor::print_data_structures() { void CodegenIspcVisitor::print_wrapper_data_structures() { - print_mechanism_global_var_structure(); + print_mechanism_global_var_structure(true); print_mechanism_range_var_structure(); print_ion_var_structure(); } @@ -512,6 +550,14 @@ void CodegenIspcVisitor::print_headers_include() { print_backend_includes(); } +void CodegenIspcVisitor::print_nmodl_constant() { + printer->add_newline(2); + printer->add_line("/** constants used in nmodl. */"); + // we use here macros to work around ispc's PI being declared in global namespace + printer->add_line("static const uniform double FARADAY = 96485.3d;"); + printer->add_line("static const uniform double ISPC_PI = 3.14159d;"); + printer->add_line("static const uniform double R = 8.3145d;"); +} void CodegenIspcVisitor::print_wrapper_headers_include() { print_standard_includes(); @@ -551,16 +597,18 @@ void CodegenIspcVisitor::print_wrapper_routine(std::string wraper_function, Bloc void CodegenIspcVisitor::print_backend_compute_routine_decl() { auto params = get_global_function_parms(""); auto compute_function = compute_method_name(BlockType::Initial); - printer->add_line( - "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + if (!emit_fallback[BlockType::Initial]) { + printer->add_line( + "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + } - if (nrn_cur_required()) { + if (nrn_cur_required() && !emit_fallback[BlockType::Equation]) { compute_function = compute_method_name(BlockType::Equation); printer->add_line( "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); } - if (nrn_state_required()) { + if (nrn_state_required() && !emit_fallback[BlockType::State]) { compute_function = compute_method_name(BlockType::State); printer->add_line( "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); @@ -576,22 +624,120 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { } } +void CodegenIspcVisitor::determine_target() { + auto node_lv = AstLookupVisitor(incompatible_node_types); + + if (info.initial_node) { + emit_fallback[BlockType::Initial] = !node_lv.lookup(info.initial_node).empty() || + calls_function(info.initial_node, "net_send") || + info.require_wrote_conc; + } else { + emit_fallback[BlockType::Initial] = info.net_receive_initial_node || + info.require_wrote_conc; + } + + if (info.net_receive_node) { + emit_fallback[BlockType::NetReceive] = !node_lv.lookup(info.net_receive_node).empty() || + calls_function(info.net_receive_node, "net_send"); + } + + if (nrn_cur_required()) { + if (info.breakpoint_node) { + emit_fallback[BlockType::Equation] = !node_lv.lookup(info.breakpoint_node).empty(); + } else { + emit_fallback[BlockType::Equation] = false; + } + } + + if (nrn_state_required()) { + if (info.nrn_state_block) { + emit_fallback[BlockType::State] = !node_lv.lookup(info.nrn_state_block).empty(); + } else { + emit_fallback[BlockType::State] = false; + } + } +} + +void CodegenIspcVisitor::move_procs_to_wrapper() { + auto nameset = std::set<std::string>(); + + auto populate_nameset = [&nameset](ast::Block* block) { + auto name_lv = AstLookupVisitor(ast::AstNodeType::NAME); + if (block) { + auto names = name_lv.lookup(block); + for (const auto& name: names) { + nameset.insert(name->get_node_name()); + } + } + }; + populate_nameset(info.initial_node); + populate_nameset(info.nrn_state_block); + populate_nameset(info.breakpoint_node); + + auto node_lv = AstLookupVisitor(incompatible_node_types); + auto target_procedures = std::vector<ast::ProcedureBlock*>(); + for (auto it = info.procedures.begin(); it != info.procedures.end(); it++) { + auto procname = (*it)->get_name()->get_node_name(); + if (nameset.find(procname) == nameset.end() || !node_lv.lookup(*it).empty()) { + wrapper_procedures.push_back(*it); + } else { + target_procedures.push_back(*it); + } + } + info.procedures = target_procedures; + auto target_functions = std::vector<ast::FunctionBlock*>(); + for (auto it = info.functions.begin(); it != info.functions.end(); it++) { + auto procname = (*it)->get_name()->get_node_name(); + if (nameset.find(procname) == nameset.end() || !node_lv.lookup(*it).empty()) { + wrapper_functions.push_back(*it); + } else { + target_functions.push_back(*it); + } + } + info.functions = target_functions; +} void CodegenIspcVisitor::codegen_wrapper_routines() { - print_wrapper_routine("nrn_init", BlockType::Initial); + if (emit_fallback[BlockType::Initial]) { + logger->warn("Falling back to C backend for emitting Initial block"); + fallback_codegen.print_nrn_init(); + } else { + print_wrapper_routine(naming::NRN_INIT_METHOD, BlockType::Initial); + } + if (nrn_cur_required()) { - print_wrapper_routine("nrn_cur", BlockType::Equation); + if (emit_fallback[BlockType::Equation]) { + logger->warn("Falling back to C backend for emitting breakpoint block"); + fallback_codegen.print_nrn_cur(); + } else { + print_wrapper_routine(naming::NRN_CUR_METHOD, BlockType::Equation); + } } + if (nrn_state_required()) { - print_wrapper_routine("nrn_state", BlockType::State); + if (emit_fallback[BlockType::State]) { + logger->warn("Falling back to C backend for emitting state block"); + fallback_codegen.print_nrn_state(); + } else { + print_wrapper_routine(naming::NRN_STATE_METHOD, BlockType::State); + } } } +void CodegenIspcVisitor::visit_program(ast::Program* node) { + fallback_codegen.setup(node); + CodegenCVisitor::visit_program(node); +} + + void CodegenIspcVisitor::print_codegen_routines() { codegen = true; + determine_target(); + move_procs_to_wrapper(); print_backend_info(); print_headers_include(); + print_nmodl_constant(); print_data_structures(); @@ -610,29 +756,59 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { print_ispc_globals(); print_namespace_begin(); - print_nmodl_constant(); + CodegenCVisitor::print_nmodl_constant(); print_mechanism_info(); print_wrapper_data_structures(); print_global_variables_for_hoc(); print_common_getters(); - print_thread_memory_callbacks(); print_memory_allocation_routine(); + print_thread_memory_callbacks(); print_global_variable_setup(); + /* this is a godawful mess.. the global variables have to be copied over into the fallback + * such that they are available to the fallback generator. + */ + fallback_codegen.set_codegen_global_variables(codegen_global_variables); print_instance_variable_setup(); print_nrn_alloc(); - print_check_table_thread_function(); + print_top_verbatim_blocks(); + for (const auto& function: wrapper_functions) { + if (!program_symtab->lookup(function->get_node_name()) + .get() + ->has_all_status(Status::inlined)) { + fallback_codegen.print_function(function); + } + } + for (const auto& procedure: wrapper_procedures) { + if (!program_symtab->lookup(procedure->get_node_name()) + .get() + ->has_all_status(Status::inlined)) { + fallback_codegen.print_procedure(procedure); + } + } + + print_check_table_thread_function(); + print_net_init(); print_net_send_buffering(); print_watch_activate(); - print_watch_check(); + fallback_codegen.print_watch_check(); // requires C style variable declarations and loops + + if (emit_fallback[BlockType::NetReceive]) { + logger->warn("Found VERBATIM code in net_receive block, falling back to C backend"); + fallback_codegen.print_net_receive_kernel(); + fallback_codegen.print_net_receive_buffering(); + } + print_net_receive(); print_backend_compute_routine_decl(); - print_net_receive_buffering_wrapper(); + if (!emit_fallback[BlockType::NetReceive]) { + print_net_receive_buffering_wrapper(); + } codegen_wrapper_routines(); print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 031920fb9a..278f83f7d6 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -20,10 +20,24 @@ namespace codegen { class CodegenIspcVisitor: public CodegenCVisitor { void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); + /// ast nodes which are not compatible with ISPC target + const std::vector<ast::AstNodeType> incompatible_node_types{ + ast::AstNodeType::VERBATIM, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK, + ast::AstNodeType::EIGEN_LINEAR_SOLVER_BLOCK, ast::AstNodeType::WATCH_STATEMENT, + ast::AstNodeType::TABLE_STATEMENT}; /// flag to indicate if visitor should print the the wrapper code bool wrapper_codegen = false; + /// fallback C code generator used to emit C code in the wrapper when emitting ISPC is not + /// supported + CodegenCVisitor fallback_codegen; + + std::map<BlockType, bool> emit_fallback; + + std::vector<ast::ProcedureBlock*> wrapper_procedures; + std::vector<ast::FunctionBlock*> wrapper_functions; + protected: /// doubles are differently represented in ispc than in C std::string double_to_string(double value) override; @@ -104,11 +118,17 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_wrapper_headers_include(); + void print_nmodl_constant() override; + /// all compute functions for every backend void print_compute_functions() override; + /// nmodl procedure definition + void print_procedure(ast::ProcedureBlock* node) override; + + void print_backend_compute_routine_decl(); @@ -121,7 +141,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// structure that wraps all global variables in the mod file - void print_mechanism_global_var_structure() override; + void print_mechanism_global_var_structure(bool wrapper = false) override; void print_data_structures() override; @@ -148,6 +168,14 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_ion_variable() override; + /// find out for main compute routines whether they are suitable to be emitted in ISPC backend + void determine_target(); + + + /// move procedures and functions unused by compute kernels into the wrapper + void move_procs_to_wrapper(); + + /// entry point to code generation void print_codegen_routines() override; @@ -159,17 +187,20 @@ class CodegenIspcVisitor: public CodegenCVisitor { std::string output_dir, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".ispc", ".cpp") {} + : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".ispc", ".cpp") + , fallback_codegen(mod_file, layout, float_type, wrapper_printer) {} CodegenIspcVisitor(std::string mod_file, std::stringstream& stream, LayoutType layout, std::string float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) {} + : CodegenCVisitor(mod_file, stream, layout, float_type) + , fallback_codegen(mod_file, layout, float_type, wrapper_printer) {} void visit_function_call(ast::FunctionCall* node) override; void visit_var_name(ast::VarName* node) override; + void visit_program(ast::Program* node) override; }; } // namespace codegen diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/lookup_visitor.cpp index 617ceeb841..0a261c552e 100644 --- a/src/nmodl/language/templates/lookup_visitor.cpp +++ b/src/nmodl/language/templates/lookup_visitor.cpp @@ -41,4 +41,11 @@ std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node, AstNo return nodes; } + +std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node) { + nodes.clear(); + node->accept(this); + return nodes; +} + } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/lookup_visitor.hpp index cc23da5b44..6c095b108b 100644 --- a/src/nmodl/language/templates/lookup_visitor.hpp +++ b/src/nmodl/language/templates/lookup_visitor.hpp @@ -34,6 +34,8 @@ class AstLookupVisitor : public Visitor { AstLookupVisitor(const std::vector<ast::AstNodeType>& types) : types(types) {} + std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST *node); + std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST* node, ast::AstNodeType type); std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST* node, std::vector<ast::AstNodeType>& types); diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 3a125daec6..993f7f4958 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -129,6 +129,7 @@ void init_visitor_module(py::module& m) { .def(py::init<ast::AstNodeType>()) .def("get_nodes", &AstLookupVisitor::get_nodes) .def("clear", &AstLookupVisitor::clear) + .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*)) &AstLookupVisitor::lookup) .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*, ast::AstNodeType)) &AstLookupVisitor::lookup) .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*, std::vector<ast::AstNodeType>&)) &AstLookupVisitor::lookup) {% for node in nodes %} diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 4fef04a88d..ff0779bf48 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -13,6 +13,7 @@ #include "fmt/format.h" #include "pybind11/embed.h" +#include "ast/ast_decl.hpp" #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 4a07a7c170..b5718755de 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -13,6 +13,7 @@ #include "parser/nmodl_driver.hpp" #include "visitor_utils.hpp" #include "visitors/json_visitor.hpp" +#include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -148,6 +149,17 @@ std::set<std::string> get_global_vars(Program* node) { } +bool calls_function(ast::AST* node, const std::string& name) { + auto lv = AstLookupVisitor(ast::AstNodeType::FUNCTION_CALL); + for (const auto& f: lv.lookup(node)) { + if (std::dynamic_pointer_cast<ast::FunctionCall>(f)->get_node_name() == name) { + return true; + } + } + return false; +} + + std::string to_nmodl(ast::AST* node, const std::set<ast::AstNodeType>& exclude_types) { std::stringstream stream; NmodlPrintVisitor v(stream, exclude_types); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 1723491048..dad012cdad 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -22,34 +22,47 @@ std::string get_new_name(const std::string& name, const std::string& suffix, std::map<std::string, int>& variables); + /** Return pointer to local statement in the given block, otherwise nullptr */ ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); + /** Add empty local statement to given block if already doesn't exist */ void add_local_statement(ast::StatementBlock* node); + /** Add new local variable to the block */ ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); ast::LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname, int dim); + /** Create ast statement node from given code in string format */ std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); + /** Create ast statement block node from given code in string format */ std::shared_ptr<ast::StatementBlock> create_statement_block( const std::vector<std::string>& code_statements); + /** Remove statements from given statement block if they exist */ void remove_statements_from_block(ast::StatementBlock* block, const std::set<ast::Node*> statements); + /** Return set of strings with the names of all global variables */ std::set<std::string> get_global_vars(ast::Program* node); + +/** Checks whether block contains a call to a perticular function */ +bool calls_function(ast::AST* node, const std::string& name); + + /** Given AST node, return the NMODL string representation */ std::string to_nmodl(ast::AST* node, const std::set<ast::AstNodeType>& exclude_types = {}); + /** Given AST node, return the JSON string representation */ std::string to_json(ast::AST* node, bool compact = false, bool expand = false); From 9838320a83acb69e1661bdbc63f06f24c34d5864 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Mon, 15 Apr 2019 20:49:50 +0100 Subject: [PATCH 187/871] Add .ipynb notebooks to document sympy & related routines (BlueBrain/nmodl#139) - nmodl-odes-overview contains a higher level overview of the approach to solving ODEs in nmodl - nmodl-kinetic-schemes describes the Kinetic scheme maths & KineticBlockVisitor - nmodl-sympy-solver describes the SympySolver visitor (incomplete) - nmodl-linear-solver describes the sympy linear solver routines (incomplete) - nmodl-nonlinear-solver describes the Newton solver & related sympy routines (incomplete) - nmodl-sympy-conductance describes the SymyConductance visitor Also, - add README.md to notebooks folder, with links to interactive colab jupyter session - the links won't work yet as repo is still private & nmodl is not installed, - but once github repo is public & nmodl is on pypi, we can add `! pip install nmodl` to top of notebook and they should work - exposed inline/folding/renaming/kinetic visitors via pybind for use in these notebooks - added `nb-format` cmake target to run nbconvert and clean_ipynb on notebooks - updated yaml.load to avoid warning message: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation - use yaml.safe_load instead of yaml.load NMODL Repo SHA: BlueBrain/nmodl@fa7ce0ad3c8c1111e95e96886c64ebdf00701d24 --- cmake/nmodl/CMakeLists.txt | 17 + docs/nmodl/transpiler/index.rst | 10 +- docs/nmodl/transpiler/notebooks/README.md | 14 + .../notebooks/kinetic-schemes.ipynb | 243 ---------- .../notebooks/nmodl-kinetic-schemes.ipynb | 449 +++++++++++++++++ .../notebooks/nmodl-linear-solver.ipynb | 47 ++ .../notebooks/nmodl-nonlinear-solver.ipynb | 47 ++ .../notebooks/nmodl-odes-overview.ipynb | 102 ++++ .../notebooks/nmodl-python-tutorial.ipynb | 96 ++-- .../notebooks/nmodl-sympy-conductance.ipynb | 455 ++++++++++++++++++ ...xamples.ipynb => nmodl-sympy-solver.ipynb} | 148 +----- src/nmodl/language/parser.py | 2 +- src/nmodl/language/templates/pyvisitor.cpp | 51 +- 13 files changed, 1250 insertions(+), 431 deletions(-) create mode 100644 docs/nmodl/transpiler/notebooks/README.md delete mode 100644 docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb rename docs/nmodl/transpiler/notebooks/{nmodl-python-sympy-examples.ipynb => nmodl-sympy-solver.ipynb} (66%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4ab316c1c5..6fd242ad28 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -45,6 +45,23 @@ set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen FORCE) add_subdirectory(cmake/hpc-coding-conventions/cpp) +# ============================================================================= +# Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) +# ============================================================================= +add_custom_target(nb-format + jupyter + nbconvert + --to + notebook + --execute + --inplace + --ExecutePreprocessor.timeout=360 + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" + && + clean_ipynb + --keep-output + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") + # ============================================================================= # Include cmake modules # ============================================================================= diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 2db6af6b5d..063a9ef719 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -22,9 +22,13 @@ Welcome to nmodl's documentation! :maxdepth: 3 :caption: Jupyter Notebooks: - notebooks/nmodl-python-sympy-examples - notebooks/nmodl-python-tutorial - notebooks/kinetic-schemes + notebooks/nmodl-python-tutorial.ipynb + notebooks/nmodl-odes-overview.ipynb + notebooks/nmodl-kinetic-schemes.ipynb + notebooks/nmodl-sympy-solver.ipynb + notebooks/nmodl-linear-solver.ipynb + notebooks/nmodl-nonlinear-solver.ipynb + notebooks/nmodl-sympy-conductance.ipynb .. toctree:: :maxdepth: 2 diff --git a/docs/nmodl/transpiler/notebooks/README.md b/docs/nmodl/transpiler/notebooks/README.md new file mode 100644 index 0000000000..ffd4f708e8 --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/README.md @@ -0,0 +1,14 @@ +## NMODL jupyter notebooks + +To get started with the NMODL python interface: + - [nmodl-python-tutorial.ipynb](nmodl-python-tutorial.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-python-tutorial.ipynb) + +For an overview of ODEs in NODL: + - [nmodl-odes-overview.ipynb](nmodl-odes-overview.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-odes-overview.ipynb) + +For more specific implementation details: + - [nmodl-kinetic-schemes.ipynb](nmodl-kinetic-schemes.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-kinetic-schemes.ipynb) + - [nmodl-sympy-solver.ipynb](nmodl-sympy-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-sympy-solver.ipynb) + - [nmodl-linear-solver.ipynb](nmodl-linear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-linear-solver.ipynb) + - [nmodl-nonlinear-solver.ipynb](nmodl-nonlinear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-nonlinear-solver.ipynb) + - [nmodl-sympy-conductance.ipynb](nmodl-sympy-conductance.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-sympy-conductance.ipynb) diff --git a/docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb deleted file mode 100644 index db2dbd6090..0000000000 --- a/docs/nmodl/transpiler/notebooks/kinetic-schemes.ipynb +++ /dev/null @@ -1,243 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### NMODL Kinetic Scheme\n", - "\n", - "Some definitions of reaction kinetics & mass action laws that are relevant for how `KINETIC` blocks are translated into a system of ODEs in NMODL" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Reaction Kinetics\n", - "\n", - "- We consider a set of reaction species $A_j$, with corresponding Molar concentrations $Y_j$\n", - "- They react according to a set of reaction equations:\n", - "\n", - "$$\\sum_j \\nu_{ij}^L A_j \\overset{k_i}{\\rightarrow} \\sum_j \\nu_{ij}^R A_j$$\n", - "\n", - "where\n", - "- $k_i$ is the rate coefficient\n", - "- $\\nu_{ij}^L$, $\\nu_{ij}^R$ are stochiometric coefficients - must be positive integers (including zero)\n", - "\n", - "***\n", - "#### Law of Mass Action\n", - "\n", - "- This allows us to convert these reaction equations to a set of ODEs\n", - "\n", - "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} r_i$$\n", - "\n", - "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", - "$$r_i = k_i \\prod_j Y_j^{\\nu_{ij}^R}$$\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### KINETIC block format\n", - "\n", - "A reaction equation is specifed in the mod file as\n", - "```\n", - "~ A0 + 3A1 + 2A2 + ... <-> 2A0 + A1 + ... (kf, kb)\n", - "```\n", - "where\n", - "- `A0` etc are the species $A_j$\n", - "- the integer preceeding a species (with or without a space) is the corresponding stochiometric coefficient $\\nu_{ij}$ (by default 1 if not present)\n", - "- `kf` is the forwards reaction rate $k^{(f)}_j$\n", - "- `kb` is the backwards reaction rate $k^{(b)}_j$, i.e. the reaction rate for the same reaction with the LHS and RHS exchanged\n", - "\n", - "***\n", - "We can convert these statements to a system of ODEs:\n", - "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} (r^{(f)}_i - r^{(b)}_i)$$\n", - "\n", - "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", - "$$\n", - "\\begin{aligned}\n", - "r^{(f)}_i &= k^{(f)}_i \\prod_j Y_j^{\\nu_{ij}^{L}}\\\\\n", - "r^{(b)}_i &= k^{(b)}_i \\prod_j Y_j^{\\nu_{ij}^{R}}\n", - "\\end{aligned}\n", - "$$\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Example\n", - "\n", - "Given the following KINETIC statement\n", - "```\n", - "~ h <-> m (a,b)\n", - "```\n", - "We have 2 state variables, and 1 reaction statement, so \n", - "- $i = \\{0\\}$\n", - "- $j = \\{0, 1\\}$\n", - "\n", - "i.e. \n", - "\n", - "- the state vector $Y$ has 2 elements\n", - "$$\n", - "Y = \\left(\n", - "\\begin{aligned}\n", - "h \\\\\n", - "m \n", - "\\end{aligned}\n", - "\\right)\n", - "$$\n", - "\n", - "- the stochiometric coefficients are 1x2 matrices\n", - "$$\n", - "\\nu^L = \\left(\n", - "\\begin{aligned}\n", - "1 && 0\n", - "\\end{aligned}\n", - "\\right)\n", - "$$\n", - "$$\n", - "\\nu^R = \\left(\n", - "\\begin{aligned}\n", - "0 && 1\n", - "\\end{aligned}\n", - "\\right)\n", - "$$\n", - "\n", - "- the rate vectors contain 1 element:\n", - "$$\n", - "k^{(f)} = a\n", - "$$\n", - "$$\n", - "k^{(b)} = b\n", - "$$\n", - "\n", - "Using these we can construct the forwards and blackwards fluxes:\n", - "$$\n", - "r^{(f)} = a h\n", - "$$\n", - "$$\n", - "r^{(b)} = b m\n", - "$$\n", - "\n", - "and finally we find the ODEs in matrix form:\n", - "$$\n", - "\\frac{dY}{dt} =\n", - "\\left(\n", - "\\begin{aligned}\n", - "-1 && 1\n", - "\\end{aligned}\n", - "\\right)\n", - "(ah - bm)\n", - "$$\n", - "\n", - "which in terms of the state variables can be written:\n", - "$$\n", - "\\begin{aligned}\n", - "\\frac{dh}{dt} &= bm - ah \\\\\n", - "\\frac{dm}{dt} &= ah - bm\n", - "\\end{aligned}\n", - "$$\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### Other types of reaction statement\n", - "\n", - "Can also have a reaction statement of the form\n", - "```\n", - "~ h << (a)\n", - "```\n", - "where the LHS must be a state variable, and the RHS is an expression inside parentheses.\n", - "\n", - "The meaning of this statement is to add `a` to the differential equation for `h`, i.e.\n", - "$$\n", - "\\frac{dh}{dt} += a\n", - "$$\n", - "\n", - "***\n", - "Finally there is a statement of the form\n", - "```\n", - "x + 2y + ... -> (a)\n", - "```\n", - "which is a one-way reaction statement with no reaction products.\n", - "\n", - "This is just syntactic sugar for a special case of the standard `<->` reaction equation, where the backwards rate is set to zero and there are no states on the RHS of the reaction, so the above is equivalent to\n", - "```\n", - "x + 2y + ... <-> (a, 0)\n", - "```\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### [TODO] CONSERVE\n", - "\n", - "Converse statement allows specification of a conservation law, e.g.\n", - "\n", - "```\n", - "CONSERVE h + m = 0\n", - "```\n", - "This can then be used to eliminate one of these variables from the set of ODEs, for example by removing the dmdt equation, and instead calculating m in terms of the algebraic relation above.\n", - "\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### [TODO] fflux / bflux\n", - "\n", - "Directly after each kinetic statement, NEURON provides the fflux and bflux variable that can be referenced inside the mod file (!). Note that it always refers to the fflux/bflux from the preceeding kinetic statement.\n", - "\n", - "So we should parse all non-kinetic statements in the mod file following a kinetic statement (until we get to a new kinetic statement), and replace any fflux or bflux variables with corresponding expression.\n", - "\n", - "***" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "#### [TODO] inlining\n", - "\n", - "The usual issues about inlining function calls, e.g. when the rate is a function call.\n", - "\n", - "In principle also allowed for it to be a function of state variables, in which case vital to inline this information for the ODEs (although we could also just say that this is not supported, as not necessary for the user to write the mod file in this way, state variable dependence can always be written explicitly).\n", - "\n", - "***" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.6.7" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb new file mode 100644 index 0000000000..a319147bea --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -0,0 +1,449 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL Kinetic Scheme\n", + "\n", + "This notebook describes the reaction kinetics & mass action laws that apply within `KINETIC` blocks, and the implementation of the `KineticBlockVisitor` in NMODL which transforms `KINETIC` blocks into `DERIVATIVE` blocks containing an equivalent system of ODEs.\n", + "\n", + "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", + "\n", + "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Reaction Kinetics\n", + "\n", + "- We consider a set of reaction species $A_j$, with corresponding Molar concentrations $Y_j$\n", + "- They react according to a set of reaction equations:\n", + "\n", + "$$\\sum_j \\nu_{ij}^L A_j \\overset{k_i}{\\rightarrow} \\sum_j \\nu_{ij}^R A_j$$\n", + "\n", + "where\n", + "- $k_i$ is the rate coefficient\n", + "- $\\nu_{ij}^L$, $\\nu_{ij}^R$ are stoichiometric coefficients - must be positive integers (including zero)\n", + "***\n", + "#### Law of Mass Action\n", + "\n", + "- This allows us to convert these reaction equations to a set of ODEs\n", + "\n", + "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} r_i$$\n", + "\n", + "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", + "$$r_i = k_i \\prod_j Y_j^{\\nu_{ij}^R}$$\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### KINETIC block format\n", + "\n", + "A reaction equation is specifed in the mod file as\n", + "```\n", + "~ A0 + 3A1 + 2A2 + ... <-> 2A0 + A1 + ... (kf, kb)\n", + "```\n", + "where\n", + "- `A0` etc are the species $A_j$\n", + "- the integer preceeding a species (with or without a space) is the corresponding stochiometric coefficient $\\nu_{ij}$ (implicitly 1 if not specified)\n", + "- `kf` is the forwards reaction rate $k^{(f)}_j$\n", + "- `kb` is the backwards reaction rate $k^{(b)}_j$, i.e. the reaction rate for the same reaction with the LHS and RHS exchanged\n", + "***\n", + "We can convert these statements to a system of ODEs using the law of Mass Action:\n", + "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} (r^{(f)}_i - r^{(b)}_i)$$\n", + "\n", + "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", + "$$\n", + "\\begin{align}\n", + "r^{(f)}_i &= k^{(f)}_i \\prod_j Y_j^{\\nu_{ij}^{L}} \\\\\n", + "r^{(b)}_i &= k^{(b)}_i \\prod_j Y_j^{\\nu_{ij}^{R}}\n", + "\\end{align}\n", + "$$\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Other types of reaction statement\n", + "There is also have a reaction statement of the form\n", + "```\n", + "~ h << (a)\n", + "```\n", + "where the LHS must be a state variable, and the RHS is an expression inside parentheses.\n", + "\n", + "The meaning of this statement is to add `a` to the differential equation for `h`, i.e.\n", + "$$\n", + "\\frac{dh}{dt} += a\n", + "$$\n", + "***\n", + "Finally there is a statement of the form\n", + "```\n", + "~ x + 2y + ... -> (a)\n", + "```\n", + "which is a one-way reaction statement with no reaction products.\n", + "\n", + "This is just syntactic sugar for a special case of the standard `<->` reaction equation, where the backwards rate is set to zero and there are no states on the RHS of the reaction, so the above is equivalent to\n", + "```\n", + "~ x + 2y + ... <-> (a, 0)\n", + "```\n", + "***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### f_flux / b_flux variables\n", + "\n", + "Within the `KINETIC` block in the MOD file, the user can make use of the `f_flux` and `b_flux` variables, which refer to the forwards $r^{(f)}$ and backwards $r^{(b)}$ fluxes of the preceeding reaction statement.\n", + "\n", + "The `KineticBlockVisitor` substitutes the current expression for these fluxes for these variables within the `KINETIC` block.\n", + "\n", + "If these variables are referenced before a reaction statement then they are assumed to be zero.\n", + "***\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### [TODO] CONSERVE\n", + "Converse statement allows specification of a conservation law, e.g.\n", + "\n", + "```\n", + "CONSERVE h + m + z = 1\n", + "```\n", + "In NEURON, the ODE for the last state variable on the rhs of this expression is replaced with this algebraic expression, so in this case instead of replacing $z' = ...$ with the forwards or backwards Euler equation, it would be replaced with $z = 1 - h - m$\n", + "\n", + "In order to be consistent with NEURON, in particular the way `STEADYSTATE` is implemented, we should do this in the same way.\n", + "***\n", + "#### Implementation Tests\n", + "\n", + " - The unit tests may be helpful to understand what these functions are doing\n", + " - `KineticBlockVisitor` tests are located in [test/visitor/visitor.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/visitor.cpp#L2128)\n", + " ***" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl\n", + "\n", + "\n", + "def run_kinetic_visitor_and_return_derivative(mod_string):\n", + " # parse NMDOL file (supplied as a string) into AST\n", + " driver = nmodl.NmodlDriver()\n", + " driver.parse_string(mod_string)\n", + " AST = driver.ast()\n", + " # run SymtabVisitor to generate Symbol Table\n", + " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", + " # constant folding, inlining & local variable renaming passes\n", + " nmodl.visitor.ConstantFolderVisitor().visit_program(AST)\n", + " nmodl.visitor.InlineVisitor().visit_program(AST)\n", + " nmodl.visitor.LocalVarRenameVisitor().visit_program(AST)\n", + " # run KINETIC block visitor\n", + " nmodl.visitor.KineticBlockVisitor().visit_program(AST)\n", + " # return new DERIVATIVE block\n", + " return nmodl.to_nmodl(\n", + " nmodl.visitor.AstLookupVisitor().lookup(\n", + " AST, nmodl.ast.AstNodeType.DERIVATIVE_BLOCK\n", + " )[0]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 1\n", + "Given the following KINETIC statement\n", + "```\n", + "~ h <-> m (a,b)\n", + "```\n", + "We have 2 state variables, and 1 reaction statement, so \n", + "- $i = \\{0\\}$\n", + "- $j = \\{0, 1\\}$\n", + "\n", + "i.e. \n", + "- the state vector $Y$ has 2 elements\n", + "$$\n", + "Y = \\left(\n", + "\\begin{align}\n", + "h \\\\\n", + "m \n", + "\\end{align}\n", + "\\right)\n", + "$$\n", + "- the stoichiometric coefficients are 1x2 matrices\n", + "$$\n", + "\\nu^L = \\left(\n", + "\\begin{align}\n", + "1 && 0\n", + "\\end{align}\n", + "\\right)\n", + "$$\n", + "$$\n", + "\\nu^R = \\left(\n", + "\\begin{align}\n", + "0 && 1\n", + "\\end{align}\n", + "\\right)\n", + "$$\n", + "- the rate vectors contain 1 element:\n", + "$$\n", + "k^{(f)} = a\n", + "$$\n", + "$$\n", + "k^{(b)} = b\n", + "$$\n", + "\n", + "Using these we can construct the forwards and blackwards fluxes:\n", + "$$\n", + "r^{(f)} = a h\n", + "$$\n", + "$$\n", + "r^{(b)} = b m\n", + "$$\n", + "and finally we find the ODEs in matrix form:\n", + "$$\n", + "\\frac{dY}{dt} =\n", + "\\left(\n", + "\\begin{align}\n", + "-1 && 1\n", + "\\end{align}\n", + "\\right)\n", + "(ah - bm)\n", + "$$\n", + "which in terms of the state variables can be written:\n", + "$$\n", + "\\begin{align}\n", + "\\frac{dh}{dt} &= bm - ah \\\\\n", + "\\frac{dm}{dt} &= ah - bm\n", + "\\end{align}\n", + "$$" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE kin {\n", + " h' = (-1*(a*h-b*m))\n", + " m' = (1*(a*h-b*m))\n", + "}\n" + ] + } + ], + "source": [ + "ex1 = \"\"\"\n", + "STATE {\n", + " h m\n", + "}\n", + "KINETIC kin {\n", + " ~ h <-> m (a,b)\n", + "}\n", + "\"\"\"\n", + "print(run_kinetic_visitor_and_return_derivative(ex1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 2\n", + "Annihilation reaction statement\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE kin {\n", + " x' = (-1*(a*x))\n", + "}\n" + ] + } + ], + "source": [ + "ex2 = \"\"\"\n", + "STATE {\n", + " x\n", + "}\n", + "KINETIC kin {\n", + " ~ x -> (a)\n", + "}\n", + "\"\"\"\n", + "print(run_kinetic_visitor_and_return_derivative(ex2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 3\n", + "`<<` reaction statement" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE kin {\n", + " x' = (a)\n", + "}\n" + ] + } + ], + "source": [ + "ex3 = \"\"\"\n", + "STATE {\n", + " x\n", + "}\n", + "KINETIC kin {\n", + " ~ x << (a)\n", + "}\n", + "\"\"\"\n", + "print(run_kinetic_visitor_and_return_derivative(ex3))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 4\n", + "Annihilation & `<<` reaction statement for the same state variable" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE kin {\n", + " x' = (a)+(-1*(b*x))\n", + "}\n" + ] + } + ], + "source": [ + "ex4 = \"\"\"\n", + "STATE {\n", + " x\n", + "}\n", + "KINETIC kin {\n", + " ~ x << (a)\n", + " ~ x -> (b)\n", + "}\n", + "\"\"\"\n", + "print(run_kinetic_visitor_and_return_derivative(ex4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 5\n", + "Reaction statements and use of `f_flux`, `b_flux` variables" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE kin {\n", + " f = a*x-b*y\n", + " g = c*z\n", + " h = 0\n", + " x' = (-1*(a*x-b*y))\n", + " y' = (1*(a*x-b*y))\n", + " z' = (-1*(c*z))\n", + "}\n" + ] + } + ], + "source": [ + "ex5 = \"\"\"\n", + "STATE {\n", + " x y z\n", + "}\n", + "KINETIC kin {\n", + " ~ x <-> y (a,b)\n", + " f = f_flux - b_flux\n", + " ~ z -> (c)\n", + " g = f_flux\n", + " h = b_flux\n", + "}\n", + "\"\"\"\n", + "print(run_kinetic_visitor_and_return_derivative(ex5))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb new file mode 100644 index 0000000000..fe6739b81f --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb @@ -0,0 +1,47 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [wip] NMODL LINEAR solver\n", + "\n", + "`LINEAR` blocks contain a set of simultaneous equations.\n", + "\n", + "These are solved by `solve_lin_system` from [nmodl/ode.py](https://github.com/BlueBrain/nmodl/blob/master/nmodl/ode.py#L143).\n", + "\n", + "If the system is sufficiently small (by default $N\\leq3$), then Gaussian Elimination is used to directly construct the solution at compile time using SymPy to do the symbolic Gaussian Elimination.\n", + "\n", + "Otherwise at run time by LU factorization with partial pivoting." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb new file mode 100644 index 0000000000..9c9de2099f --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb @@ -0,0 +1,47 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### [wip] NMODL NONLINEAR solver\n", + "\n", + "`NONLINEAR` blocks contain a set of non-linear simultaneous equations.\n", + "\n", + "These are solved at runtime using Newton iteration to find the root of a function $F(X)$ with Jacobian $J = \\frac{\\partial F(X)_i}{\\partial X_j}$, where $F$ and $J$ are constructed by the [solve_non_lin_system](https://github.com/BlueBrain/nmodl/blob/master/nmodl/ode.py#L209) python routine which uses SymPy to analytically differentiate $F$ to find the Jacobian.\n", + "\n", + "The Newton solver is called `newton_solver` and is implemented in [src/solver/newton](https://github.com/BlueBrain/nmodl/blob/master/src/solver/newton/newton.hpp#L33) using the Eigen matrix algebra library.\n", + "\n", + "A fall-back solution if the analytic Jacobian is not available is to use the `newton_numerical_diff_solver` variant of this solver that uses a finite difference approximation to estimate the Jacobian numerically" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb new file mode 100644 index 0000000000..9b49adbcf1 --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb @@ -0,0 +1,102 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL integration of ODEs\n", + "\n", + "This is an overview of\n", + " - the different ways a user can specify the equations that define the system they want to simulate in the MOD file\n", + " - how these equations can be related to each other\n", + " - how these equations are solved in NMODL\n", + "***\n", + "The user can specify information about the system in a variety of ways:\n", + " - A high level way to describe a system is to specify a Mass Action Kinetic scheme of reaction equations in a `KINETIC` block\n", + " - Alternatively a system of ODEs can be specified in a `DERIVATIVE` block (any kinetic scheme can also be written as a system of ODEs)\n", + " - Finally a system of linear/non-linear algebraic equations can be specified in a `LINEAR`/`NONLINEAR` block (a numerical integration scheme, such as backwards Euler, transforms a system of ODEs into a system of linear or non-linear equations)\n", + "\n", + "To reduce duplication of functionality for dealing with these related systems, we implement a hierarchy of transformations:\n", + " - `KINETIC` blocks of reaction statements are translated to `DERIVATIVE` blocks of equivalent ODE systems using the law of Mass Action\n", + " - `DERIVATIVE` blocks of ODEs are translated to `(NON)LINEAR` blocks of algebraic equations using a numerical integration scheme\n", + "\n", + "After these transformations we are left with only `LINEAR`/`NONLINEAR` blocks that are then solved numerically (by Gaussian Elimination, LU factorization or Newton iteration)\n", + " ***\n", + "`KINETIC` block\n", + " - Mass Action kinetics: a set of reaction equations with associated reaction rates\n", + " - converted to a `DERIVATIVE` blocking containing an equivalent system of ODEs using the law of Mass Action\n", + " - see the [nmodl-kinetic-schemes](nmodl-kinetic-schemes.ipynb) notebook for more details\n", + "***\n", + "`DERIVATIVE` block\n", + " - system of ODEs & associated solve method: `cnexp`, `sparse`, `derivimplicit` or `euler`\n", + " - `cnexp`\n", + " - applicable if ODEs are linear & independent\n", + " - exact analytic integration\n", + " - `sparse`\n", + " - applicable if ODEs are linear & coupled\n", + " - backwards Euler numerical integration scheme: $f(t+\\Delta t) = f(t) + dt f'(t+ \\Delta t)$\n", + " - results in a linear algebraic system to solve\n", + " - numerically stable\n", + " - $\\mathcal{O}(\\Delta t)$ integration error\n", + " - `derivimplcit`\n", + " - always applicable\n", + " - backwards Euler numerical integration scheme: $f(t+\\Delta t) = f(t) + dt f'(t+ \\Delta t)$\n", + " - results in a non-linear algebraic system to solve\n", + " - numerically stable\n", + " - $\\mathcal{O}(\\Delta t)$ integration error\n", + " - `euler`\n", + " - always applicable\n", + " - forwards Euler numerical integration scheme: $f(t+\\Delta t) = f(t) + \\Delta t f'(t)$\n", + " - numerically unstable\n", + " - $\\mathcal{O}(\\Delta t)$ integration error\n", + " - not recommended due to instability of scheme\n", + " - see the [nmodl-sympy-solver](nmodl-sympy-solver.ipynb) notebook for more details\n", + "***\n", + "`LINEAR` block\n", + " - system of linear algebraic equations\n", + " - for small systems ($N<=3$)\n", + " - solve at compile time by Gaussian elimination\n", + " - for larger systems\n", + " - solve at run-time by LU factorization with partial pivoting\n", + " - see the [nmodl-linear-solver](nmodl-linear-solver.ipynb) notebook for more details \n", + "***\n", + "`NONLINEAR` block\n", + " - system of non-linear algebraic equations\n", + " - solve by Newton iteration\n", + " - construct $F(X)$, with Jacobian $J(X)=\\frac{\\partial F_i}{\\partial X_j}$\n", + " - such that desired solution $X^*$ satisfies condition $F(X^*) = 0$\n", + " - iterative solution given by $X_{n+1} = X_n + J(X_n)^{-1} F(X_n)$\n", + " - see the [nmodl-nonlinear-solver](nmodl-nonlinear-solver.ipynb) notebook for more details \n", + "***" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index 362dd2a282..53f790f783 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -15,8 +15,9 @@ "metadata": {}, "outputs": [], "source": [ - "from IPython.display import display, Javascript, HTML\n", - "import json" + "import json\n", + "\n", + "from IPython.display import HTML, Javascript, display" ] }, { @@ -259,7 +260,7 @@ } ], "source": [ - "Javascript(filename='tree.js')" + "Javascript(filename=\"tree.js\")" ] }, { @@ -313,7 +314,8 @@ } ], "source": [ - "HTML(\"\"\"\n", + "HTML(\n", + " \"\"\"\n", "<style>\n", "\n", ".node {\n", @@ -345,7 +347,8 @@ " stroke-width: 2px;\n", "}\n", "</style>\n", - "\"\"\")" + "\"\"\"\n", + ")" ] }, { @@ -464,7 +467,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we can parse any valid NMODL constructs using parsing interface. First, we have to create nmodl parser object using `nmodl::Driver` and then we can use `parse_string` method <a id='create-ast'></a>:" + "Now we can parse any valid NMODL constructs using parsing interface. First, we have to create nmodl parser object using `nmodl::NmodlDriver` and then we can use `parse_string` method <a id='create-ast'></a>:" ] }, { @@ -484,7 +487,7 @@ } ], "source": [ - "driver = nmodl.Driver()\n", + "driver = nmodl.NmodlDriver()\n", "driver.parse_string(channel)" ] }, @@ -525,8 +528,9 @@ } ], "source": [ - "print ('%.100s' % modast) # only first 100 characters\n", + "print(\"%.100s\" % modast) # only first 100 characters\n", "import json\n", + "\n", "json_data = json.loads(nmodl.to_json(modast, True))\n", "json_data_expand = json.loads(nmodl.to_json(modast, True, True))" ] @@ -540,7 +544,7 @@ "data": { "application/javascript": [ "(function(element){\n", - " require(['draw_tree'], function(draw) { draw(element.get(0), {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"SUFFIX\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"CaDynamics\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"Suffix\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ca\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"ReadIonVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"WriteIonVar\"}], \"name\": \"Useion\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}], \"name\": \"Range\"}], \"name\": \"StatementBlock\"}], \"name\": \"NeuronBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"mV\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millivolt\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mA\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"milliamp\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"faraday\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"coulombs\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"FactorDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"molar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"1/liter\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millimolar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"micron\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}], \"name\": \"UnitBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.05\"}], \"name\": \"Double\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"80\"}], \"name\": \"Integer\"}, {\"children\": [{\"children\": [{\"name\": \"ms\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.1\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.0001\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}], \"name\": \"ParamBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mA/cm2\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"DependentBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"InitialBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"StateBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"cnexp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"SolveBlock\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"BreakpointBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}, {\"children\": [{\"name\": \"1\"}], \"name\": \"Integer\"}], \"name\": \"PrimeName\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"-\"}], \"name\": \"UnaryOperator\"}, {\"children\": [{\"children\": [{\"name\": \"10000\"}], \"name\": \"Double\"}], \"name\": \"ParenExpression\"}], \"name\": \"UnaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"2\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"BinaryExpression\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ParenExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"DiffEqExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"DerivativeBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"temp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"LocalVar\"}], \"name\": \"LocalListStatement\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"name\": \"1\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"+\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"FunctionBlock\"}], \"name\": \"Program\"}) });\n", + " require(['draw_tree'], function(draw) { draw(element.get(0), {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"SUFFIX\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"CaDynamics\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"Suffix\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ca\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"ReadIonVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"WriteIonVar\"}], \"name\": \"Useion\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}], \"name\": \"Range\"}], \"name\": \"StatementBlock\"}], \"name\": \"NeuronBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"mV\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millivolt\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mA\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"milliamp\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"faraday\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"coulombs\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"FactorDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"molar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"1/liter\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millimolar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"micron\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}], \"name\": \"UnitBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.05\"}], \"name\": \"Double\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"80\"}], \"name\": \"Integer\"}, {\"children\": [{\"children\": [{\"name\": \"ms\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.1\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.0001\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}], \"name\": \"ParamBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mA/cm2\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"DependentBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"InitialBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"StateBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"cnexp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"SolveBlock\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"BreakpointBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}, {\"children\": [{\"name\": \"1\"}], \"name\": \"Integer\"}], \"name\": \"PrimeName\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"-\"}], \"name\": \"UnaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"10000\"}], \"name\": \"Double\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"UnaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"2\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"DiffEqExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"DerivativeBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"temp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"LocalVar\"}], \"name\": \"LocalListStatement\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"1\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"+\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"FunctionBlock\"}], \"name\": \"Program\"}) });\n", " })(element);" ], "text/plain": [ @@ -553,9 +557,12 @@ } ], "source": [ - "Javascript(\"\"\"(function(element){\n", + "Javascript(\n", + " \"\"\"(function(element){\n", " require(['draw_tree'], function(draw) { draw(element.get(0), %s) });\n", - " })(element);\"\"\" % json.dumps(json_data_expand) )" + " })(element);\"\"\"\n", + " % json.dumps(json_data_expand)\n", + ")" ] }, { @@ -582,8 +589,8 @@ "metadata": {}, "outputs": [], "source": [ - "from nmodl.dsl import visitor\n", - "from nmodl.dsl import ast\n", + "from nmodl.dsl import ast, visitor\n", + "\n", "lookup_visitor = visitor.AstLookupVisitor()" ] }, @@ -612,7 +619,7 @@ "source": [ "states = lookup_visitor.lookup(modast, ast.AstNodeType.STATE_BLOCK)\n", "for state in states:\n", - " print (nmodl.to_nmodl(state))" + " print(nmodl.to_nmodl(state))" ] }, { @@ -652,30 +659,30 @@ "if odes:\n", " print(\"%d differential equation(s) exist : \" % len(odes))\n", " for ode in odes:\n", - " print (\"\\t %s \" % nmodl.to_nmodl(ode))\n", - " \n", + " print(\"\\t %s \" % nmodl.to_nmodl(ode))\n", + "\n", "if primes:\n", - " print(\"%d prime variables exist :\" % len(primes), end='')\n", + " print(\"%d prime variables exist :\" % len(primes), end=\"\")\n", " for prime in primes:\n", - " print (\" %s\" % nmodl.to_nmodl(prime), end='')\n", + " print(\" %s\" % nmodl.to_nmodl(prime), end=\"\")\n", " print()\n", "\n", "if range_vars:\n", - " print(\"%d range variables exist :\" % len(range_vars), end='')\n", + " print(\"%d range variables exist :\" % len(range_vars), end=\"\")\n", " for range_var in range_vars:\n", - " print (\" %s\" % nmodl.to_nmodl(range_var), end='')\n", + " print(\" %s\" % nmodl.to_nmodl(range_var), end=\"\")\n", " print()\n", "\n", "if parameters:\n", - " print(\"%d parameter variables exist :\" % len(parameters), end='')\n", + " print(\"%d parameter variables exist :\" % len(parameters), end=\"\")\n", " for range_var in range_vars:\n", - " print (\" %s\" % nmodl.to_nmodl(range_var), end='')\n", + " print(\" %s\" % nmodl.to_nmodl(range_var), end=\"\")\n", " print()\n", - " \n", + "\n", "if units:\n", - " print(\"%d units uses :\" % len(units), end='')\n", + " print(\"%d units uses :\" % len(units), end=\"\")\n", " for unit in units:\n", - " print (\" %s\" % nmodl.to_nmodl(unit), end='')" + " print(\" %s\" % nmodl.to_nmodl(unit), end=\"\")" ] }, { @@ -700,7 +707,7 @@ ], "source": [ "functions = lookup_visitor.lookup(modast, ast.AstNodeType.FUNCTION_BLOCK)\n", - "function = functions[0] # first function\n", + "function = functions[0] # first function\n", "\n", "# expression statements include assignments\n", "new_lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.EXPRESSION_STATEMENT)\n", @@ -710,7 +717,7 @@ "statements = new_lookup_visitor.get_nodes()\n", "\n", "for statement in statements:\n", - " print (nmodl.to_nmodl(statement))" + " print(nmodl.to_nmodl(statement))" ] }, { @@ -795,7 +802,7 @@ "table = modast.get_symbol_table()\n", "table_s = str(table)\n", "\n", - "print (table_s)" + "print(table_s)" ] }, { @@ -819,8 +826,8 @@ } ], "source": [ - "cai = table.lookup('cai')\n", - "print (cai)" + "cai = table.lookup(\"cai\")\n", + "print(cai)" ] }, { @@ -855,7 +862,7 @@ "source": [ "range_vars = table.get_variables_with_properties(symtab.NmodlType.range_var)\n", "for var in range_vars:\n", - " print (var)" + " print(var)" ] }, { @@ -880,9 +887,11 @@ } ], "source": [ - "ions_var = table.get_variables_with_properties(symtab.NmodlType.read_ion_var | symtab.NmodlType.write_ion_var, False)\n", + "ions_var = table.get_variables_with_properties(\n", + " symtab.NmodlType.read_ion_var | symtab.NmodlType.write_ion_var, False\n", + ")\n", "for var in ions_var:\n", - " print (var)" + " print(var)" ] }, { @@ -915,11 +924,12 @@ "source": [ "from nmodl.dsl import ast, visitor\n", "\n", - "class DoubleVisitor(visitor.AstVisitor):\n", "\n", + "class DoubleVisitor(visitor.AstVisitor):\n", " def visit_double(self, node):\n", - " print (node.eval()) # or, can use nmodl.to_nmodl(node) \n", - " \n", + " print(node.eval()) # or, can use nmodl.to_nmodl(node)\n", + "\n", + "\n", "d_visitor = DoubleVisitor()\n", "modast.accept(d_visitor)" ] @@ -955,11 +965,10 @@ ], "source": [ "class ParameterVisitor(visitor.AstVisitor):\n", - " \n", " def __init__(self):\n", " visitor.AstVisitor.__init__(self)\n", " self.in_parameter = False\n", - " \n", + "\n", " def visit_param_block(self, node):\n", " self.in_parameter = True\n", " node.visit_children(self)\n", @@ -967,15 +976,16 @@ "\n", " def visit_name(self, node):\n", " if self.in_parameter:\n", - " print (nmodl.to_nmodl(node))\n", - " \n", + " print(nmodl.to_nmodl(node))\n", + "\n", " def visit_double(self, node):\n", " if self.in_parameter:\n", - " print (node.eval())\n", + " print(node.eval())\n", "\n", " def visit_integer(self, node):\n", " if self.in_parameter:\n", - " print (node.eval())\n", + " print(node.eval())\n", + "\n", "\n", "param_visitor = ParameterVisitor()\n", "modast.accept(param_visitor)" @@ -998,7 +1008,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb new file mode 100644 index 0000000000..f205a3584d --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb @@ -0,0 +1,455 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL CONDUCTANCE\n", + "\n", + "This notebook described the `CONDUCTANCE` keyword in NEURON, how it is implemented in NMODL, and shows some examples of the output generated by NMODL in different situations.\n", + "\n", + "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Introduction\n", + "\n", + "Motivation:\n", + " - during a NEURON simulation, a number of currents $I$ may be generated\n", + " - at each time step, for each current $I$ the corresponding conductance $dI/dV$ needs to be calculated\n", + " - by default in NEURON this is approximated by a forwards difference: $dI/dV \\simeq (I(v+\\Delta v)-I(v))/\\Delta v$, with $\\Delta v = 0.001$\n", + " - this introduces an $\\mathcal{O}(\\Delta v)$ numerical error\n", + " - it also requires two current calculations, which may be computationally inefficient\n", + "\n", + "Solution:\n", + " - the `CONDUCTANCE` keyword was added to the NMODL language\n", + " - this allows the user to manually specify the analytic expression for the conductance in the MOD file\n", + " - during the simulation, instead of the numerical differentiation, the user supplied expression is used\n", + " - this solves the problem, but requires additional effort from the user\n", + " - it also opens up room for user error: an incorrect expression will still run but the results will not be correct\n", + "\n", + "SymPy improvement:\n", + " - the currents in the mod file are differentiated analytically using SymPy\n", + " - the corresponding `CONDUCTANCE` statements are generated automatically\n", + " - no additional input required from the user\n", + " - avoids the possibility of user error" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Implementation\n", + "\n", + "The `SympyConductanceVisitor` is defined in [src/visitors/sympy_conductance_visitor.hpp](https://github.com/BlueBrain/nmodl/blob/master/src/visitors/sympy_conductance_visitor.hpp), and it makes use of the python function [differentiate2c](https://github.com/BlueBrain/nmodl/blob/master/nmodl/ode.py#L377) to perform the analytic differentation using the [SymPy](https://www.sympy.org/en/index.html) symbolic math Python library.\n", + "\n", + "* For each ion write statement $i = \\dots$ in the BREAKPOINT block\n", + " * Differentiate to find the conductance $g_i=di/dv$\n", + " * If this $g_i$ coincides with an existing variable, e.g. $g$, add to BREAKPOINT the statement:\n", + " * CONDUCTANCE g USEION ion_name\n", + " * If not, also need to declare and asign a variable for the calculated conductance:\n", + " * LOCAL g_i_0\n", + " * CONDUCTANCE g_i_0 USEION ion_name\n", + " * g_i_0 = ...\n", + " * But if there is an existing CONDUCTANCE statement, then do not modify it\n", + "\n", + "\n", + "* It may be the case that a variable in the write statement $i = \\dots$ itself depends on $v$, so to take this into account:\n", + " * an inlining visitor is first ran, after which all variable assignments occur within the BREAKPOINT block\n", + " * each preceeding expression is analysed in reverse order for $v$ dependence \n", + " * if it depends on $v$, the rhs of the expression is substituted for the lhs in all following statements\n", + " * the end result is a (complicated) expression $i = ...$ where all v dependence is explicit\n", + " * this is then differentiated w.r.t $v$ to give the conductance\n", + " * it then checks if this expression is equivalent to an existing variable\n", + " * for this step it is necessary to also substitute all non-$v$-dependent expressions on both sides & simplify\n", + "\n", + "#### Implementation Tests\n", + "\n", + " - The unit tests may be helpful to understand what these functions are doing\n", + " - `SympyConductanceVisitor` tests are located in [test/visitor/visitor.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/visitor.cpp#L3261)\n", + " - `differentiate2c` tests are located in [test/ode/test_ode.py](https://github.com/BlueBrain/nmodl/blob/master/test/ode/test_ode.py#L56)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Examples\n", + "Here are some examples of generated CONDUCTANCE statements for a variety of sample mod files." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl\n", + "\n", + "\n", + "def run_conductance_visitor_and_return_breakpoint(mod_string):\n", + " # parse NMDOL file (supplied as a string) into AST\n", + " driver = nmodl.NmodlDriver()\n", + " driver.parse_string(mod_string)\n", + " AST = driver.ast()\n", + " # run SymtabVisitor to generate Symbol Table\n", + " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", + " # constant folding, inlining & local variable renaming passes\n", + " nmodl.visitor.ConstantFolderVisitor().visit_program(AST)\n", + " nmodl.visitor.InlineVisitor().visit_program(AST)\n", + " nmodl.visitor.LocalVarRenameVisitor().visit_program(AST)\n", + "\n", + " # run CONDUCTANCE visitor\n", + " nmodl.visitor.SympyConductanceVisitor().visit_program(AST)\n", + " # return new BREAKPOINT block\n", + " return nmodl.to_nmodl(\n", + " nmodl.visitor.AstLookupVisitor().lookup(\n", + " AST, nmodl.ast.AstNodeType.BREAKPOINT_BLOCK\n", + " )[0]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 1\n", + " - simple USEION statement, conductance equal to existing variable\n", + " - add CONDUCTANCE statement using existing variable" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " CONDUCTANCE gna USEION na\n", + " ina = gna*(v-ena)\n", + "}\n" + ] + } + ], + "source": [ + "ex1 = \"\"\"\n", + "NEURON {\n", + " USEION na READ ena WRITE ina\n", + " RANGE gna\n", + "}\n", + "BREAKPOINT {\n", + " ina = gna*(v - ena)\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex1))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 2\n", + " - simple USEION statement, conductance not equal to existing variable\n", + " - declare new local variable\n", + " - assign conductance to it\n", + " - add CONDUCTANCE statement" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " LOCAL g_na_0\n", + " CONDUCTANCE g_na_0 USEION na\n", + " g_na_0 = 0.1*gna\n", + " ina = 0.1*gna*(v-ena)\n", + "}\n" + ] + } + ], + "source": [ + "ex2 = \"\"\"\n", + "NEURON {\n", + " USEION na READ ena WRITE ina\n", + " RANGE gna\n", + "}\n", + "BREAKPOINT {\n", + " ina = 0.1*gna*(v - ena)\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex2))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 3\n", + " - simple NONSPECIFIC_CURRENT statement, conductance equal to existing variable\n", + " - add CONDUCTANCE statement using existing variable" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " CONDUCTANCE g\n", + " i = g*v\n", + "}\n" + ] + } + ], + "source": [ + "ex3 = \"\"\"\n", + "NEURON {\n", + " NONSPECIFIC_CURRENT i\n", + " RANGE g\n", + "}\n", + "BREAKPOINT {\n", + " i = g*v\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex3))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 4\n", + " - non-linear NONSPECIFIC_CURRENT statement, conductance not equal to existing variable\n", + " - declare new local variable\n", + " - assign conductance to it\n", + " - add CONDUCTANCE statement" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " LOCAL g__0\n", + " CONDUCTANCE g__0\n", + " g__0 = g+2*v\n", + " i = g*v+v*v\n", + "}\n" + ] + } + ], + "source": [ + "ex4 = \"\"\"\n", + "NEURON {\n", + " NONSPECIFIC_CURRENT i\n", + " RANGE g\n", + "}\n", + "BREAKPOINT {\n", + " i = g*v + v*v\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex4))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 5\n", + " - several current statements, conductance equal to existing variables\n", + " - add CONDUCTANCE statements" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " CONDUCTANCE gl\n", + " CONDUCTANCE gk USEION k\n", + " CONDUCTANCE gna USEION na\n", + " gna = gnabar*m*m*m*h\n", + " ina = gna*(v-ena)\n", + " gk = gkbar*n*n*n*n\n", + " ik = gk*(v-ek)\n", + " il = gl*(v-el)\n", + "}\n" + ] + } + ], + "source": [ + "ex5 = \"\"\"\n", + "NEURON {\n", + " USEION na READ ena WRITE ina\n", + " USEION k READ ek WRITE ik\n", + " NONSPECIFIC_CURRENT il\n", + " RANGE gnabar, gkbar, gl, el, gna, gk\n", + "}\n", + "STATE {\n", + " m n h\n", + "}\n", + "BREAKPOINT {\n", + " gna = gnabar*m*m*m*h\n", + " ina = gna*(v - ena)\n", + " gk = gkbar*n*n*n*n\n", + " ik = gk*(v - ek)\n", + " il = gl*(v - el)\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex5))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 6\n", + " - current contains variables that depend on $v$, conductance equal to existing variable `x3`\n", + " - substitute all variables with $v$-dependence, differentiate to find conductance\n", + " - compare result to each existing variable (after substituting all preceeding declarations on both sides)\n", + " - identify that expression for conductance is equivalent to `x3`\n", + " - add CONDUCTANCE statement using this existing variable" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " CONDUCTANCE x3 USEION na\n", + " x1 = 0.2+3*v\n", + " x2 = v*v\n", + " x3 = 3*v*v+5*v-1.3\n", + " gna = x1+x2\n", + " ina = gna*(v-0.5)\n", + "}\n" + ] + } + ], + "source": [ + "ex6 = \"\"\"\n", + "NEURON {\n", + " USEION na READ ena WRITE ina\n", + " RANGE gna, x1, x2, x3\n", + "}\n", + "BREAKPOINT {\n", + " x1 = 0.2+3*v\n", + " x2 = v*v\n", + " x3 = 3*v*v+5*v-1.3\n", + " gna = x1 + x2\n", + " ina = gna*(v-0.5)\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex6))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 7\n", + " - current contains variables that depend on $v$, conductance not equal to existing variable\n", + " - substitute all variables with $v$-dependence, differentiate to find conductance\n", + " - compare result to each existing variable, no equivalent expression found\n", + " - declare new local variable, assign conductance to it\n", + " - add CONDUCTANCE statement using this new variable" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BREAKPOINT {\n", + " LOCAL g_na_0\n", + " CONDUCTANCE g_na_0 USEION na\n", + " g_na_0 = 3*pow(v, 2)+5*v-1.3\n", + " x1 = 0.2+3*v\n", + " x2 = v*v\n", + " gna = x1+x2\n", + " ina = gna*(v-0.5)\n", + "}\n" + ] + } + ], + "source": [ + "ex7 = \"\"\"\n", + "NEURON {\n", + " USEION na READ ena WRITE ina\n", + " RANGE gna, x1, x2\n", + "}\n", + "BREAKPOINT {\n", + " x1 = 0.2+3*v\n", + " x2 = v*v\n", + " gna = x1 + x2\n", + " ina = gna*(v-0.5)\n", + "}\n", + "\"\"\"\n", + "print(run_conductance_visitor_and_return_breakpoint(ex7))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-sympy-examples.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb similarity index 66% rename from docs/nmodl/transpiler/notebooks/nmodl-python-sympy-examples.ipynb rename to docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb index 14980bbbbc..ac4ebfcccf 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-sympy-examples.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb @@ -4,13 +4,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# NMODL SymPy Visitor Examples\n", + "### [wip] NMODL SympySolver\n", "\n", - "Some examples of the use of SymPy within NMODL to\n", - "- solve differential equations to generate solutions for the CNEXP solver (`SympySolverVisitor`)\n", - "- differentiate current expressions to generate CONDUCTANCE statements (`SympyConductanceVisitor`)\n", + "This notebook describes the implementation of the `SympySolverVisitor`, which solves the systems of ODEs defined in `DERIVATIVE` blocks.\n", "\n", - "Please see the [tutorial notebook](nmodl-python-tutorial.ipynb) for a more general tutorial on using the NMODL python interface, including installation instructions." + "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", + "\n", + "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb)." ] }, { @@ -138,7 +138,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## ODE Solver example" + "### ODE Solver example" ] }, { @@ -149,7 +149,7 @@ "source": [ "def parse_mod_to_ast(mod_string):\n", " # parse NMDOL file (supplied as a string)\n", - " driver = nmodl.Driver()\n", + " driver = nmodl.NmodlDriver()\n", " driver.parse_string(mod_string)\n", " modast = driver.ast()\n", " # run SymtabVisitor to generate Symbol Table\n", @@ -158,6 +158,7 @@ " # return AST\n", " return modast\n", "\n", + "\n", "lookup_visitor = visitor.AstLookupVisitor()" ] }, @@ -253,9 +254,9 @@ "text": [ "DERIVATIVE states {\n", " rates(v, celsius)\n", - " m = minf+(m-minf)*exp(-dt/mtau)\n", - " h = hinf+(h-hinf)*exp(-dt/htau)\n", - " n = ninf+(n-ninf)*exp(-dt/ntau)\n", + " m = minf-(-m+minf)*exp(-dt/mtau)\n", + " h = hinf-(-h+hinf)*exp(-dt/htau)\n", + " n = ninf-(-n+ninf)*exp(-dt/ntau)\n", "}\n" ] } @@ -311,131 +312,6 @@ " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", ")" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## CONDUCTANCE example" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The CONDUCTANCE keyword has been introduced to NEURON as well as CoreNEURON. If the i/v relation is ohmic in BREAKPOINT block then one can use CONDUCTANCE keyword for efficiency." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "USEION na READ ena WRITE ina\n", - "USEION k READ ek WRITE ik\n", - "NONSPECIFIC_CURRENT il\n" - ] - } - ], - "source": [ - "modast = parse_mod_to_ast(channel)\n", - "# print USEION and NONSPECIFIC current statements\n", - "for node in lookup_visitor.lookup(modast, ast.AstNodeType.USEION):\n", - " print(nmodl.to_nmodl(node))\n", - "for node in lookup_visitor.lookup(modast, ast.AstNodeType.NONSPECIFIC):\n", - " print(nmodl.to_nmodl(node))" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "BREAKPOINT {\n", - " SOLVE states METHOD cnexp\n", - " gna = gnabar*m*m*m*h\n", - " ina = gna*(v-ena)\n", - " gk = gkbar*n*n*n*n\n", - " ik = gk*(v-ek)\n", - " il = gl*(v-el)\n", - "}\n" - ] - } - ], - "source": [ - "# print BREAKPOINT\n", - "print(\n", - " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we run `SympyConductanceVisitor`, it does the following:\n", - "\n", - "* For each ion write statement $i = \\dots$ in the BREAKPOINT block\n", - " * Differentiate to find the conductance $g_i=di/dv$\n", - " * If this $g_i$ coincides with an existing variable, e.g. $g$, add to BREAKPOINT the statement:\n", - " * CONDUCTANCE g USEION ion_name\n", - " * If not, also need to declare and asign a variable for the calculated conductance:\n", - " * LOCAL g_i_0\n", - " * CONDUCTANCE g_i_0 USEION ion_name\n", - " * g_i_0 = ...\n", - " * But if there is an existing CONDUCTANCE statement, then do not modify it\n", - "\n", - "\n", - "* NOTE: currently we just differentiate the equation, assuming that the variables do not depend on v\n", - " * Ideally we should do something like:\n", - " * check after inlining which variables are written to in BREAKPOINT block\n", - " * get rhs of each such expression, and substitute it (recursively) for the lhs in SymPy\n", - " * this should give a (complicated) expression $i = ...$ where all v dependence is explicit\n", - " * then differentiate this w.r.t v\n", - " * then simplify and try to write the result in terms of an existing variable\n", - "\n", - "Here is the BREAKPOINT block after running `SympyConductanceVisitor`:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "BREAKPOINT {\n", - " CONDUCTANCE gna USEION na\n", - " CONDUCTANCE gl\n", - " CONDUCTANCE gk USEION k\n", - " SOLVE states METHOD cnexp\n", - " gna = gnabar*m*m*m*h\n", - " ina = gna*(v-ena)\n", - " gk = gkbar*n*n*n*n\n", - " ik = gk*(v-ek)\n", - " il = gl*(v-el)\n", - "}\n" - ] - } - ], - "source": [ - "sympy_conductance_visitor = visitor.SympyConductanceVisitor()\n", - "sympy_conductance_visitor.visit_program(modast)\n", - "# print BREAKPOINT block\n", - "print(\n", - " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.BREAKPOINT_BLOCK)[0])\n", - ")" - ] } ], "metadata": { @@ -454,7 +330,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 5120cb6177..249e6fcc7b 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -191,7 +191,7 @@ def parse_file(self): with open(self.filename, 'r') as stream: try: - rules = yaml.load(stream) + rules = yaml.safe_load(stream) _, nodes = self.parse_yaml_rules(rules) except yaml.YAMLError as e: print("Error while parsing YAML definition file {0} : {1}".format( diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pyvisitor.cpp index 993f7f4958..8126d53083 100644 --- a/src/nmodl/language/templates/pyvisitor.cpp +++ b/src/nmodl/language/templates/pyvisitor.cpp @@ -12,6 +12,10 @@ #include "pybind/pybind_utils.hpp" #include "pybind/pyvisitor.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" @@ -43,15 +47,36 @@ static const char *nmodlprintvisitor_class = R"( NmodlPrintVisitor class )"; -static const char *sympysolvervisitor_class = R"( +static const char *constantfoldervisitor_class = R"( - SympySolverVisitor class + ConstantFolderVisitor class +)"; + +static const char *inlinevisitor_class = R"( + + InlineVisitor class +)"; + +static const char *kineticblockvisitor_class = R"( + + KineticBlockVisitor class +)"; + +static const char *localvarrenamevisitor_class = R"( + + LocalVarRenameVisitor class )"; static const char *sympyconductancevisitor_class = R"( SympyConductanceVisitor class )"; + +static const char *sympysolvervisitor_class = R"( + + SympySolverVisitor class +)"; + } #pragma clang diagnostic push @@ -137,13 +162,29 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} - py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor", docstring::sympysolvervisitor_class); - sympy_solver_visitor.def(py::init<bool>(), py::arg("use_pade_approx")=false) - .def("visit_program", &SympySolverVisitor::visit_program); + py::class_<ConstantFolderVisitor, AstVisitor> constant_folder_visitor(m_visitor, "ConstantFolderVisitor", docstring::constantfoldervisitor_class); + constant_folder_visitor.def(py::init<>()) + .def("visit_program", &ConstantFolderVisitor::visit_program); + + py::class_<InlineVisitor, AstVisitor> inline_visitor(m_visitor, "InlineVisitor", docstring::inlinevisitor_class); + inline_visitor.def(py::init<>()) + .def("visit_program", &InlineVisitor::visit_program); + + py::class_<KineticBlockVisitor, AstVisitor> kinetic_block_visitor(m_visitor, "KineticBlockVisitor", docstring::kineticblockvisitor_class); + kinetic_block_visitor.def(py::init<>()) + .def("visit_program", &KineticBlockVisitor::visit_program); + + py::class_<LocalVarRenameVisitor, AstVisitor> local_var_rename_visitor(m_visitor, "LocalVarRenameVisitor", docstring::localvarrenamevisitor_class); + local_var_rename_visitor.def(py::init<>()) + .def("visit_program", &LocalVarRenameVisitor::visit_program); py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor", docstring::sympyconductancevisitor_class); sympy_conductance_visitor.def(py::init<>()) .def("visit_program", &SympyConductanceVisitor::visit_program); + + py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor", docstring::sympysolvervisitor_class); + sympy_solver_visitor.def(py::init<bool>(), py::arg("use_pade_approx")=false) + .def("visit_program", &SympySolverVisitor::visit_program); } #pragma clang diagnostic pop From 5d17f662b85d2fb7b4497827bb06ad2becdd8db5 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 17 Apr 2019 11:34:26 +0200 Subject: [PATCH 188/871] Install only *.py files from nmodl directory (BlueBrain/nmodl#142) - previously *.pyc and .so file from top level nmodl directory was getting copied to install prefix - this was causing issue mentioned in BlueBrain/nmodl#139 NMODL Repo SHA: BlueBrain/nmodl@2fb7c073a28f6e68231326da71f79027852a9e96 --- src/nmodl/pybind/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index a66bcc787e..a1ef02d0ca 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -47,4 +47,4 @@ add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} # Install python binding components # ============================================================================= install(TARGETS _nmodl DESTINATION lib/python/nmodl) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/nmodl DESTINATION lib/python/) +install(DIRECTORY ${PROJECT_SOURCE_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") From 5f9a701735072a78efab4eda0509b6760c137afa Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 17 Apr 2019 16:16:43 +0200 Subject: [PATCH 189/871] Lexer documentation update for Doxygen (BlueBrain/nmodl#141) - Add Doxyfile and logo files - Lexer, Parser related classes doxygen update - Update C lexer with --text option NMODL Repo SHA: BlueBrain/nmodl@e4144a0687861c144efcd12f735d4dacfe6d359e --- docs/nmodl/transpiler/Doxyfile | 2510 +++++++++++++++++++ docs/nmodl/transpiler/logo.graffle | Bin 0 -> 2378 bytes docs/nmodl/transpiler/logo.pdf | Bin 0 -> 18339 bytes src/nmodl/lexer/c11.ll | 13 +- src/nmodl/lexer/c11_lexer.hpp | 86 +- src/nmodl/lexer/diffeq.ll | 6 +- src/nmodl/lexer/diffeq_lexer.hpp | 53 +- src/nmodl/lexer/main_c.cpp | 60 +- src/nmodl/lexer/main_nmodl.cpp | 24 +- src/nmodl/lexer/modl.h | 62 +- src/nmodl/lexer/modtoken.cpp | 11 +- src/nmodl/lexer/modtoken.hpp | 84 +- src/nmodl/lexer/nmodl.ll | 45 +- src/nmodl/lexer/nmodl_lexer.hpp | 137 +- src/nmodl/lexer/nmodl_utils.cpp | 101 +- src/nmodl/lexer/nmodl_utils.hpp | 18 +- src/nmodl/lexer/token_mapping.cpp | 153 +- src/nmodl/lexer/token_mapping.hpp | 11 +- src/nmodl/lexer/verbatim.l | 53 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 18 +- 20 files changed, 3110 insertions(+), 335 deletions(-) create mode 100644 docs/nmodl/transpiler/Doxyfile create mode 100644 docs/nmodl/transpiler/logo.graffle create mode 100644 docs/nmodl/transpiler/logo.pdf diff --git a/docs/nmodl/transpiler/Doxyfile b/docs/nmodl/transpiler/Doxyfile new file mode 100644 index 0000000000..f81f3162f8 --- /dev/null +++ b/docs/nmodl/transpiler/Doxyfile @@ -0,0 +1,2510 @@ +# Doxyfile 1.8.15 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "NMODL" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = logo.pdf + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all generated output in the proper direction. +# Possible values are: None, LTR, RTL and Context. +# The default value is: None. + +OUTPUT_TEXT_DIRECTION = None + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is +# Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 0. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 0 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = YES + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = YES + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = YES + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if <section_label> ... \endif and \cond <section_label> +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = ../src ../test ../build + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: https://www.gnu.org/software/libiconv/) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, +# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.ipp \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# <filter> <input-file> +# +# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +INPUT += ../README.md +USE_MDFILE_AS_MAINPAGE = ../README.md + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 344 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via Javascript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have Javascript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: https://developer.apple.com/xcode/), introduced with OSX +# 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use <access key> + S +# (what the <access key> is depends on the OS and browser, but it is typically +# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down +# key> to jump into the search results window, the results can be navigated +# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel +# the search. The filter options can be selected when the cursor is inside the +# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys> +# to select a filter and <Enter> or <escape> to activate or cancel the filter +# option. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a web server instead of a web client using Javascript. There +# are two flavors of web server based searching depending on the EXTERNAL_SEARCH +# setting. When disabled, doxygen will generate a PHP script for searching and +# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing +# and searching needs to be provided by external tools. See the section +# "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SERVER_BASED_SEARCH = NO + +# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP +# script for searching. Instead the search results are written to an XML file +# which needs to be processed by an external indexer. Doxygen will invoke an +# external search engine pointed to by the SEARCHENGINE_URL option to obtain the +# search results. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: https://xapian.org/). +# +# See the section "External Indexing and Searching" for details. +# The default value is: NO. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH = NO + +# The SEARCHENGINE_URL should point to a search engine hosted by a web server +# which will return the search results when EXTERNAL_SEARCH is enabled. +# +# Doxygen ships with an example indexer (doxyindexer) and search engine +# (doxysearch.cgi) which are based on the open source search engine library +# Xapian (see: https://xapian.org/). See the section "External Indexing and +# Searching" for details. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHENGINE_URL = + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed +# search data is written to a file for indexing by an external tool. With the +# SEARCHDATA_FILE tag the name of this file can be specified. +# The default file is: searchdata.xml. +# This tag requires that the tag SEARCHENGINE is set to YES. + +SEARCHDATA_FILE = searchdata.xml + +# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the +# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is +# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple +# projects and redirect the results back to the right project. +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTERNAL_SEARCH_ID = + +# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen +# projects other than the one defined by this configuration file, but that are +# all added to the same external search index. Each project needs to have a +# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of +# to a relative location where the documentation can be found. The format is: +# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ... +# This tag requires that the tag SEARCHENGINE is set to YES. + +EXTRA_SEARCH_MAPPINGS = + +#--------------------------------------------------------------------------- +# Configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output. +# The default value is: YES. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: latex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. +# +# Note that when not enabling USE_PDFLATEX the default is latex when enabling +# USE_PDFLATEX the default is pdflatex and when in the later case latex is +# chosen this is overwritten by pdflatex. For specific output languages the +# default can have been set differently, this depends on the implementation of +# the output language. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate +# index for LaTeX. +# Note: This tag is used in the Makefile / make.bat. +# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file +# (.tex). +# The default file is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +MAKEINDEX_CMD_NAME = makeindex + +# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to +# generate index for LaTeX. In case there is no backslash (\) as first character +# it will be automatically added in the LaTeX code. +# Note: This tag is used in the generated output file (.tex). +# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat. +# The default value is: makeindex. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_MAKEINDEX_CMD = makeindex + +# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used by the +# printer. +# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x +# 14 inches) and executive (7.25 x 10.5 inches). +# The default value is: a4. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PAPER_TYPE = a4 + +# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names +# that should be included in the LaTeX output. The package can be specified just +# by its name or with the correct syntax as to be used with the LaTeX +# \usepackage command. To get the times font for instance you can specify : +# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times} +# To use the option intlimits with the amsmath package you can specify: +# EXTRA_PACKAGES=[intlimits]{amsmath} +# If left blank no extra packages will be included. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for the +# generated LaTeX document. The header should contain everything until the first +# chapter. If it is left blank doxygen will generate a standard header. See +# section "Doxygen usage" for information on how to let doxygen write the +# default header to a separate file. +# +# Note: Only use a user-defined header if you know what you are doing! The +# following commands have a special meaning inside the header: $title, +# $datetime, $date, $doxygenversion, $projectname, $projectnumber, +# $projectbrief, $projectlogo. Doxygen will replace $title with the empty +# string, for the replacement values of the other commands the user is referred +# to HTML_HEADER. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for the +# generated LaTeX document. The footer should contain everything after the last +# chapter. If it is left blank doxygen will generate a standard footer. See +# LATEX_HEADER for more information on how to generate a default footer and what +# special commands can be used inside the footer. +# +# Note: Only use a user-defined footer if you know what you are doing! +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_FOOTER = + +# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# LaTeX style sheets that are included after the standard style sheets created +# by doxygen. Using this option one can overrule certain style aspects. Doxygen +# will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_STYLESHEET = + +# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the LATEX_OUTPUT output +# directory. Note that the files will be copied as-is; there are no commands or +# markers available. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EXTRA_FILES = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is +# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will +# contain links (just like the HTML output) instead of page references. This +# makes the output suitable for online browsing using a PDF viewer. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate +# the PDF file directly from the LaTeX files. Set this option to YES, to get a +# higher quality PDF documentation. +# The default value is: YES. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode +# command to the generated LaTeX files. This will instruct LaTeX to keep running +# if errors occur, instead of asking the user for help. This option is also used +# when generating formulas in HTML. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BATCHMODE = NO + +# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the +# index chapters (such as File Index, Compound Index, etc.) in the output. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_HIDE_INDICES = NO + +# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source +# code with syntax highlighting in the LaTeX output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. See +# https://en.wikipedia.org/wiki/BibTeX and \cite for more info. +# The default value is: plain. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_BIB_STYLE = plain + +# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated +# page will contain the date and time when the page was generated. Setting this +# to NO can help when comparing the output of multiple runs. +# The default value is: NO. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_TIMESTAMP = NO + +# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) +# path from which the emoji images will be read. If a relative path is entered, +# it will be relative to the LATEX_OUTPUT directory. If left blank the +# LATEX_OUTPUT directory will be used. +# This tag requires that the tag GENERATE_LATEX is set to YES. + +LATEX_EMOJI_DIRECTORY = + +#--------------------------------------------------------------------------- +# Configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The +# RTF output is optimized for Word 97 and may not look too pretty with other RTF +# readers/editors. +# The default value is: NO. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: rtf. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF +# documents. This may be useful for small projects and may help to save some +# trees in general. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will +# contain hyperlink fields. The RTF file will contain links (just like the HTML +# output) instead of page references. This makes the output suitable for online +# browsing using Word or some other Word compatible readers that support those +# fields. +# +# Note: WordPad (write) and others do not support links. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# configuration file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. +# +# See also section "Doxygen usage" for information on how to generate the +# default style sheet that doxygen normally uses. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an RTF document. Syntax is +# similar to doxygen's configuration file. A template extensions file can be +# generated using doxygen -e rtf extensionFile. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_EXTENSIONS_FILE = + +# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code +# with syntax highlighting in the RTF output. +# +# Note that which sources are shown also depends on other settings such as +# SOURCE_BROWSER. +# The default value is: NO. +# This tag requires that the tag GENERATE_RTF is set to YES. + +RTF_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for +# classes and files. +# The default value is: NO. + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. A directory man3 will be created inside the directory specified by +# MAN_OUTPUT. +# The default directory is: man. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to the generated +# man pages. In case the manual section does not start with a number, the number +# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is +# optional. +# The default value is: .3. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_EXTENSION = .3 + +# The MAN_SUBDIR tag determines the name of the directory created within +# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by +# MAN_EXTENSION with the initial . removed. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_SUBDIR = + +# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it +# will generate one additional man file for each entity documented in the real +# man page(s). These additional files only source the real man page, but without +# them the man command would be unable to find the correct page. +# The default value is: NO. +# This tag requires that the tag GENERATE_MAN is set to YES. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that +# captures the structure of the code including all documentation. +# The default value is: NO. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: xml. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_OUTPUT = xml + +# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program +# listings (including syntax highlighting and cross-referencing information) to +# the XML output. Note that enabling this will significantly increase the size +# of the XML output. +# The default value is: YES. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_PROGRAMLISTING = YES + +# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include +# namespace members in file scope as well, matching the HTML output. +# The default value is: NO. +# This tag requires that the tag GENERATE_XML is set to YES. + +XML_NS_MEMB_FILE_SCOPE = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the DOCBOOK output +#--------------------------------------------------------------------------- + +# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files +# that can be used to generate PDF. +# The default value is: NO. + +GENERATE_DOCBOOK = NO + +# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in +# front of it. +# The default directory is: docbook. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_OUTPUT = docbook + +# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the +# program listings (including syntax highlighting and cross-referencing +# information) to the DOCBOOK output. Note that enabling this will significantly +# increase the size of the DOCBOOK output. +# The default value is: NO. +# This tag requires that the tag GENERATE_DOCBOOK is set to YES. + +DOCBOOK_PROGRAMLISTING = NO + +#--------------------------------------------------------------------------- +# Configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an +# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures +# the structure of the code including all documentation. Note that this feature +# is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# Configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module +# file that captures the structure of the code including all documentation. +# +# Note that this feature is still experimental and incomplete at the moment. +# The default value is: NO. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary +# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI +# output from the Perl module output. +# The default value is: NO. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely +# formatted so it can be parsed by a human reader. This is useful if you want to +# understand what is going on. On the other hand, if this tag is set to NO, the +# size of the Perl module output will be much smaller and Perl will parse it +# just the same. +# The default value is: YES. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file are +# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful +# so different doxyrules.make files included by the same Makefile don't +# overwrite each other's variables. +# This tag requires that the tag GENERATE_PERLMOD is set to YES. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all +# C-preprocessor directives found in the sources and include files. +# The default value is: YES. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names +# in the source code. If set to NO, only conditional compilation will be +# performed. Macro expansion can be done in a controlled way by setting +# EXPAND_ONLY_PREDEF to YES. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then +# the macro expansion is limited to the macros specified with the PREDEFINED and +# EXPAND_AS_DEFINED tags. +# The default value is: NO. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES, the include files in the +# INCLUDE_PATH will be searched if a #include is found. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by the +# preprocessor. +# This tag requires that the tag SEARCH_INCLUDES is set to YES. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will be +# used. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that are +# defined before the preprocessor is started (similar to the -D option of e.g. +# gcc). The argument of the tag is a list of macros of the form: name or +# name=definition (no spaces). If the definition and the "=" are omitted, "=1" +# is assumed. To prevent a macro definition from being undefined via #undef or +# recursively expanded use the := operator instead of the = operator. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this +# tag can be used to specify a list of macro names that should be expanded. The +# macro definition that is found in the sources will be used. Use the PREDEFINED +# tag if you want to use a different macro definition that overrules the +# definition found in the source code. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will +# remove all references to function-like macros that are alone on a line, have +# an all uppercase name, and do not end with a semicolon. Such function macros +# are typically used for boiler-plate code, and will confuse the parser if not +# removed. +# The default value is: YES. +# This tag requires that the tag ENABLE_PREPROCESSING is set to YES. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration options related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES tag can be used to specify one or more tag files. For each tag +# file the location of the external documentation should be added. The format of +# a tag file without this location is as follows: +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where loc1 and loc2 can be relative or absolute paths or URLs. See the +# section "Linking to external documentation" for more information about the use +# of tag files. +# Note: Each tag file must have a unique name (where the name does NOT include +# the path). If a tag file is not located in the directory in which doxygen is +# run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create a +# tag file that is based on the input files it reads. See section "Linking to +# external documentation" for more information about the usage of tag files. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES, all external class will be listed in +# the class index. If set to NO, only the inherited external classes will be +# listed. +# The default value is: NO. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will be +# listed. +# The default value is: YES. + +EXTERNAL_GROUPS = YES + +# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in +# the related pages index. If set to NO, only the current project's pages will +# be listed. +# The default value is: YES. + +EXTERNAL_PAGES = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of 'which perl'). +# The default file (with absolute path) is: /usr/bin/perl. + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram +# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to +# NO turns the diagrams off. Note that this option also works with HAVE_DOT +# disabled, but it is recommended to install and use dot, since it yields more +# powerful graphs. +# The default value is: YES. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see: +# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# You can include diagrams made with dia in doxygen documentation. Doxygen will +# then run dia to produce the diagram and insert it in the documentation. The +# DIA_PATH tag allows you to specify the directory where the dia binary resides. +# If left empty dia is assumed to be found in the default search path. + +DIA_PATH = + +# If set to YES the inheritance and collaboration graphs will hide inheritance +# and usage relations if the target is undocumented or is not a class. +# The default value is: YES. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz (see: +# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent +# Bell Labs. The other options in this section have no effect if this option is +# set to NO +# The default value is: NO. + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed +# to run in parallel. When set to 0 doxygen will base this on the number of +# processors available in the system. You can set it explicitly to a value +# larger than 0 to get control over the balance between CPU load and processing +# speed. +# Minimum value: 0, maximum value: 32, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_NUM_THREADS = 0 + +# When you want a differently looking font in the dot files that doxygen +# generates you can specify the font name using DOT_FONTNAME. You need to make +# sure dot is able to find the font, which can be done by putting it in a +# standard location or by setting the DOTFONTPATH environment variable or by +# setting DOT_FONTPATH to the directory containing the font. +# The default value is: Helvetica. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of +# dot graphs. +# Minimum value: 4, maximum value: 24, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the default font as specified with +# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set +# the path where dot can find it using this tag. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_FONTPATH = + +# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for +# each documented class showing the direct and indirect inheritance relations. +# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a +# graph for each documented class showing the direct and indirect implementation +# dependencies (inheritance, containment, and class references variables) of the +# class with other documented classes. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for +# groups, showing the direct groups dependencies. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside the +# class node. If there are many fields or methods and many nodes the graph may +# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the +# number of items for each type to make the size more manageable. Set this to 0 +# for no limit. Note that the threshold may be exceeded by 50% before the limit +# is enforced. So when you set the threshold to 10, up to 15 fields may appear, +# but if the number exceeds 15, the total amount of fields shown is limited to +# 10. +# Minimum value: 0, maximum value: 100, default value: 10. +# This tag requires that the tag HAVE_DOT is set to YES. + +UML_LIMIT_NUM_FIELDS = 10 + +# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and +# collaboration graphs will show the relations between templates and their +# instances. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +TEMPLATE_RELATIONS = NO + +# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to +# YES then doxygen will generate a graph for each documented file showing the +# direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDE_GRAPH = YES + +# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are +# set to YES then doxygen will generate a graph for each documented file showing +# the direct and indirect include dependencies of the file with other documented +# files. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH tag is set to YES then doxygen will generate a call +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable call graphs for selected +# functions only using the \callgraph command. Disabling a call graph can be +# accomplished by means of the command \hidecallgraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller +# dependency graph for every global function or class method. +# +# Note that enabling this option will significantly increase the time of a run. +# So in most cases it will be better to enable caller graphs for selected +# functions only using the \callergraph command. Disabling a caller graph can be +# accomplished by means of the command \hidecallergraph. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical +# hierarchy of all classes instead of a textual one. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the +# dependencies a directory has on other directories in a graphical way. The +# dependency relations are determined by the #include relations between the +# files in the directories. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. For an explanation of the image formats see the section +# output formats in the documentation of the dot tool (Graphviz (see: +# http://www.graphviz.org/)). +# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order +# to make the SVG files visible in IE 9+ (other browsers do not have this +# requirement). +# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo, +# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and +# png:gdiplus:gdiplus. +# The default value is: png. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# +# Note that this requires a modern browser other than Internet Explorer. Tested +# and working are Firefox, Chrome, Safari, and Opera. +# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make +# the SVG files visible. Older versions of IE do not have SVG support. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +INTERACTIVE_SVG = NO + +# The DOT_PATH tag can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the \dotfile +# command). +# This tag requires that the tag HAVE_DOT is set to YES. + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the \mscfile +# command). + +MSCFILE_DIRS = + +# The DIAFILE_DIRS tag can be used to specify one or more directories that +# contain dia files that are included in the documentation (see the \diafile +# command). + +DIAFILE_DIRS = + +# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the +# path where java can find the plantuml.jar file. If left blank, it is assumed +# PlantUML is not used or called during a preprocessing step. Doxygen will +# generate a warning when it encounters a \startuml command in this case and +# will not generate output for the diagram. + +PLANTUML_JAR_PATH = + +# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a +# configuration file for plantuml. + +PLANTUML_CFG_FILE = + +# When using plantuml, the specified paths are searched for files specified by +# the !include statement in a plantuml block. + +PLANTUML_INCLUDE_PATH = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes +# that will be shown in the graph. If the number of nodes in a graph becomes +# larger than this value, doxygen will truncate the graph, which is visualized +# by representing a node as a red box. Note that doxygen if the number of direct +# children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that +# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. +# Minimum value: 0, maximum value: 10000, default value: 50. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs +# generated by dot. A depth value of 3 means that only nodes reachable from the +# root by following a path via at most 3 edges will be shown. Nodes that lay +# further from the root node will be omitted. Note that setting this option to 1 +# or 2 may greatly reduce the computation time needed for large code bases. Also +# note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. +# Minimum value: 0, maximum value: 1000, default value: 0. +# This tag requires that the tag HAVE_DOT is set to YES. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not seem +# to support this out of the box. +# +# Warning: Depending on the platform used, enabling this option may lead to +# badly anti-aliased labels on the edges of a graph (i.e. they become hard to +# read). +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) support +# this, this feature is disabled by default. +# The default value is: NO. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page +# explaining the meaning of the various boxes and arrows in the dot generated +# graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# files that are used to generate the various graphs. +# The default value is: YES. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_CLEANUP = YES diff --git a/docs/nmodl/transpiler/logo.graffle b/docs/nmodl/transpiler/logo.graffle new file mode 100644 index 0000000000000000000000000000000000000000..2db9a94657f8c53c0dac624fcc7a043d3de39ee0 GIT binary patch literal 2378 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 literal 0 HcmV?d00001 diff --git a/docs/nmodl/transpiler/logo.pdf b/docs/nmodl/transpiler/logo.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a6ebe577c612071c594f7d1805f2c63e48ba30d6 GIT binary patch literal 18339 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 literal 0 HcmV?d00001 diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index afd586b139..a894fd0fc1 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -1,12 +1,13 @@ /********************************************************************************** + * Copyright (C) 2018-2019 Blue Brain Project * - * @brief Flex lexer implementation for C (11) - * - * NMODL has verbatim constructs that allow to specify C code sections within - * nmodl implementation. + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. * * CREDIT : This is based on flex specification available at * http://www.quut.com/c/ANSI-C-grammar-l-2011.html + * + * @brief C (11) lexer based *****************************************************************************/ %{ @@ -381,7 +382,7 @@ WS [ \t\v\n\f] {L}{A}* { driver.add_token(yytext); - return check_type(); + return get_token_type(); } {HP}{H}+{IS}? { @@ -693,7 +694,7 @@ int CFlexLexer::yylex() { } -nmodl::parser::CParser::symbol_type nmodl::parser::CLexer::check_type() { +nmodl::parser::CParser::symbol_type nmodl::parser::CLexer::get_token_type() { if (driver.is_typedef(yytext)) { return CParser::make_TYPEDEF_NAME(yytext, loc); } diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 3622df1ba7..194d8a4d2e 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -9,16 +9,20 @@ #include "parser/c/c11_parser.hpp" -/** Flex expects the declaration of yylex to be defined in the macro YY_DECL - * and C++ parser class expects it to be declared. */ +/** + * Flex expects the declaration of yylex to be defined in the macro YY_DECL + * and C++ parser class expects it to be declared. + */ #ifndef YY_DECL #define YY_DECL nmodl::parser::CParser::symbol_type nmodl::parser::CLexer::next_token() #endif -/** For creating multiple (different) lexer classes, we can use '-P' flag - * (or prefix option) to rename each yyFlexLexer to some other name like - * ‘xxFlexLexer’. And then include <FlexLexer.h> in other sources once per - * lexer class, first renaming yyFlexLexer as shown below. */ +/** + * For creating multiple (different) lexer classes, we can use `-P` flag + * (or prefix option) to rename each `NmodlFlexLexer` to some other name like + * `xxFlexLexer`. And then include <FlexLexer.h> in other sources once per + * lexer class, first renaming `yyFlexLexer` as shown below. + */ #ifndef __FLEX_LEXER_H #define yyFlexLexer CFlexLexer #include "FlexLexer.h" @@ -27,46 +31,82 @@ namespace nmodl { namespace parser { +/** + * @addtogroup lexer + * @{ + */ + /** * \class CLexer * \brief Represent Lexer/Scanner class for C (11) language parsing * * Lexer defined to add some extra function to the scanner class from flex. * Flex itself creates yyFlexLexer class, which we renamed using macros to - * C11FlexLexer. But we change the context of the generated yylex() function - * because the yylex() defined in C11FlexLexer has no parameters. Also, note - * that implementation of the member functions are in c11.ll file due to use - * of macros. */ + * NmodlFlexLexer. But we change the context of the generated yylex() function + * because the yylex() defined in NmodlFlexLexer has no parameters. Also, note + * that implementation of the member functions are in nmodl.l file due to use + * of macros. + */ class CLexer: public CFlexLexer { public: /** Reference to driver object which contains this lexer instance. This is * used for error reporting and checking macro definitions. */ + + /** + * \brief Reference to driver object where this lexer resides + * + * The driver object can be used from lexer to store/retrieve some + * global information. Currently this is used for storing all token + * encountered during lexing. + */ CDriver& driver; - /// For tracking location of the tokens + /// location of the parsed token location loc; - /** The streams in and out default to cin and cout, but that assignment - * is only made when initializing in yylex(). */ + /// \name Ctor & dtor + /// \{ + + /** + * \brief CLexer constructor + * + * @param driver CDriver where this lexer resides + * @param in Input stream from where tokens will be read + * @param out Output stream where output will be sent + */ explicit CLexer(CDriver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) : CFlexLexer(in, out) , driver(drv) {} ~CLexer() override = default; - ; - /** Main lexing function generated by flex according to the macro declaration - * YY_DECL above. The generated bison parser then calls this virtual function - * to fetch new tokens. Note that yylex() has different declaration and hence - * it can't be used for new lexer. */ - virtual CParser::symbol_type next_token(); + /// \} - /** Return type of the word : could be typedef, identifier or enum constant */ - CParser::symbol_type check_type(); + /** + * \brief Function for lexer to scan token (replacement for \c yylex()) + * + * This is main lexing function generated by `flex` according to the macro + * declaration \c YY_DECL. The generated bison parser then calls this virtual + * function to fetch new tokens. Note that \c yylex() has different declaration + * and hence can't be used for new lexer. + * + * @return Symbol encapsulating parsed token + */ + virtual CParser::symbol_type next_token(); - /// consume comment - std::string input_comment(); + /** + * \brief Get the type of token just parsed + * + * Check if parsed token is either \c enum, \c typedef or \c identifier. + * + * \attention Not needed anymore and could be removed? + * + * @return Symbol encapsulating parsed token + */ + CParser::symbol_type get_token_type(); }; +/** @} */ // end of lexer + } // namespace parser } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index e48b0fdb93..0635ecab12 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -4,6 +4,8 @@ * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. + * + * @brief Lexer for ODEs from NMODL *************************************************************************/ %{ @@ -11,6 +13,7 @@ #include <cstdlib> #include <iostream> #include <stdlib.h> + #include "lexer/diffeq_lexer.hpp" #include "parser/diffeq_driver.hpp" @@ -84,7 +87,7 @@ E [Ee][-+]?{D}+ :.* { /* ignore inline comments */ } -[ \t] { /* ignore spacing characters */ } +[ \t] { /* ignore white spaces */ } {D}+ | {D}+"."{D}*({E})? | @@ -97,7 +100,6 @@ E [Ee][-+]?{D}+ %% - int DiffEqFlexLexer::yylex() { throw std::runtime_error("next_token() should be used instead of yylex()"); } diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp index 1c88781ab2..2d3d76b794 100644 --- a/src/nmodl/lexer/diffeq_lexer.hpp +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -9,16 +9,20 @@ #include "parser/diffeq/diffeq_parser.hpp" -/** Flex expects the declaration of yylex to be defined in the macro YY_DECL - * and C++ parser class expects it to be declared. */ +/** + * Flex expects the declaration of yylex to be defined in the macro YY_DECL + * and C++ parser class expects it to be declared. + */ #ifndef YY_DECL #define YY_DECL nmodl::parser::DiffeqParser::symbol_type nmodl::parser::DiffeqLexer::next_token() #endif -/** For creating multiple (different) lexer classes, we can use '-P' flag - * (or prefix option) to rename each yyFlexLexer to some other name like - * ‘xxFlexLexer’. And then include <FlexLexer.h> in other sources once per - * lexer class, first renaming yyFlexLexer as shown below. */ +/** + * For creating multiple (different) lexer classes, we can use `-P` flag + * (or prefix option) to rename each `yyFlexLexer` to some other name like + * `xxFlexLexer`. And then include <FlexLexer.h> in other sources once per + * lexer class, first renaming `yyFlexLexer `as shown below. + */ #ifndef __FLEX_LEXER_H #define yyFlexLexer DiffEqFlexLexer #include "FlexLexer.h" @@ -27,6 +31,11 @@ namespace nmodl { namespace parser { +/** + * @addtogroup lexer + * @{ + */ + /** * \class DiffeqLexer * \brief Represent Lexer/Scanner class for differential equation parsing @@ -37,23 +46,39 @@ namespace parser { */ class DiffeqLexer: public DiffEqFlexLexer { public: - /// for tracking location of the tokens + /// location of the parsed token location loc; - /** The streams in and out default to cin and cout, but that assignment - * is only made when initializing in yylex(). */ + /// \name Ctor & dtor + /// \{ + + /* + * \brief DiffeqLexer constructor + * + * @param in Input stream from where tokens will be read + * @param out Output stream where output will be sent + */ DiffeqLexer(std::istream* in = nullptr, std::ostream* out = nullptr) : DiffEqFlexLexer(in, out) {} ~DiffeqLexer() override = default; - ; - /** Main lexing function generated by flex according to the macro declaration - * YY_DECL above. The generated bison parser then calls this virtual function - * to fetch new tokens. Note that yylex() has different declaration and hence - * it can't be used for new lexer. */ + /// \} + + /** + * \brief Function for lexer to scan token (replacement for \c yylex()) + * + * This is main lexing function generated by `flex` according to the macro + * declaration \c YY_DECL. The generated bison parser then calls this virtual + * function to fetch new tokens. Note that \c yylex() has different declaration + * and hence can't be used for new lexer. + * + * @return Symbol encapsulating parsed token + */ virtual DiffeqParser::symbol_type next_token(); }; +/** @} */ // end of lexer + } // namespace parser } // namespace nmodl diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 16c847933d..23975a8ce7 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include <fstream> +#include <sstream> #include "CLI/CLI.hpp" #include "fmt/format.h" @@ -16,38 +17,55 @@ #include "version/version.h" /** - * Example of standalone lexer program for C codes that - * demonstrate use of CLexer and CDriver classes. + * \file + * \brief Example of standalone lexer program for C code + * + * This example demonstrate use of CLexer and CDriver classes + * to scan arbitrary C code. */ using namespace fmt::literals; using namespace nmodl; + +void scan_c_code(std::istream& in) { + nmodl::parser::CDriver driver; + nmodl::parser::CLexer scanner(driver, &in); + + /// parse C file and print token until EOF + while (true) { + auto sym = scanner.next_token(); + auto token = sym.token(); + if (token == nmodl::parser::CParser::token::END) { + break; + } + std::cout << sym.value.as<std::string>() << std::endl; + } +} + + int main(int argc, const char* argv[]) { CLI::App app{"C-Lexer : Standalone Lexer for C Code({})"_format(version::to_string())}; - std::vector<std::string> files; - app.add_option("file", files, "One or more C files to process") - ->required() - ->check(CLI::ExistingFile); + std::vector<std::string> c_files; + std::vector<std::string> c_codes; + + app.add_option("file", c_files, "One or more C files to process")->check(CLI::ExistingFile); + app.add_option("--text", c_codes, "One or more C code as text"); CLI11_PARSE(app, argc, argv); - for (const auto& f: files) { - nmodl::logger->info("Processing {}", f); - std::ifstream file(f); - nmodl::parser::CDriver driver; - nmodl::parser::CLexer scanner(driver, &file); - - /// parse C file and print token until EOF - while (true) { - auto sym = scanner.next_token(); - auto token = sym.token(); - if (token == nmodl::parser::CParser::token::END) { - break; - } - std::cout << sym.value.as<std::string>() << std::endl; - } + for (const auto& file: c_files) { + nmodl::logger->info("Processing {}", file); + std::ifstream in(file); + scan_c_code(in); } + + for (const auto& code: c_codes) { + nmodl::logger->info("Processing {}", code); + std::istringstream in(code); + scan_c_code(in); + } + return 0; } diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 12b49f096b..7cb009f1c8 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -18,10 +18,13 @@ #include "version/version.h" /** - * Stand alone lexer program for NMODL. This demonstrate basic - * usage of scanner and driver classes. We parse user provided - * nmodl file and print individual token with it's value and - * location. + * \file + * + * \brief Example of standalone lexer program for NMODL + * + * This example demonstrate basic usage of scanner and driver classes. + * We parse user provided nmodl file and print individual token with + * it's value and location. */ using namespace fmt::literals; @@ -33,6 +36,7 @@ using SymbolType = parser::NmodlParser::symbol_type; using Token = parser::NmodlParser::token; using TokenType = parser::NmodlParser::token_type; + void tokenize(const std::string& mod_text) { std::istringstream in(mod_text); @@ -67,35 +71,35 @@ void tokenize(const std::string& mod_text) { break; } - /// token with prime ast class + /// token with prime ast class case Token::PRIME: { auto value = sym.value.as<ast::PrimeName>(); std::cout << *(value.get_token()) << std::endl; break; } - /// token with integer ast class + /// token with integer ast class case Token::INTEGER: { auto value = sym.value.as<ast::Integer>(); std::cout << *(value.get_token()) << std::endl; break; } - /// token with double/float ast class + /// token with double/float ast class case Token::REAL: { auto value = sym.value.as<ast::Double>(); std::cout << *(value.get_token()) << std::endl; break; } - /// token with string ast class + /// token with string ast class case Token::STRING: { auto value = sym.value.as<ast::String>(); std::cout << *(value.get_token()) << std::endl; break; } - /// token with string data type + /// token with string data type case Token::VERBATIM: case Token::BLOCK_COMMENT: case Token::LINE_PART: { @@ -104,7 +108,7 @@ void tokenize(const std::string& mod_text) { break; } - /// all remaining tokens has ModToken* as a vaue + /// all remaining tokens has ModToken* as a vaue default: { auto token = sym.value.as<ModToken>(); std::cout << token << std::endl; diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index cecaf8b73e..9398f4e2a7 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -5,64 +5,22 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -// @todo : This file has been added for legacy purpose and can be removed. - #pragma once /** - * Original implementation of nmodl use various flags to help - * code generation. These flags are implemented as bit masks in - * the token whic are later checked during code printing. We are - * using ast and hence don't need all bit masks. These are defined - * in modl.h file of original nmodl implementation. + * \file modl.h + * \brief Definitions from mod2c/nocmodl implementation * - * \todo Remove these bit masks as we incorporate type information - * into corresponding ast types. */ - -/// subtypes of the token -#define ARRAY 040 -#define DEP 010 -#define FUNCT 0100 -#define INDEP 04 -#define KEYWORD 01 -#define NEGATIVE 0400 -#define PARM 02 -#define PROCED 0200 -#define SEMI 01 -#define STAT 020 - -#define DISCF 010000 - -/// usage field (variable occurs in input file) -#define EXPLICIT_DECL 01 - -/// external definition -#define EXTDEF 0100000 - -//// functions that can take array or function name as an arguments -#define EXTDEF2 01000000L - -/// function that takes two extra reset arguments at beginning -#define EXTDEF3 04000000L - -/// function that takes an extra NrnThread* arg at beginning -#define EXTDEF4 020000000L - -//// not threadsafe -#define EXTDEF5 040000000L - -/// must be cast to double in expression -#define INTGER 010000000L + * Original implementation of NMODL use various flags to help + * code generation. These flags are implemented as bit masks + * which are later used during code printing. We are using ast + * and hence don't need all bit masks. + * + * \todo Add these bit masks as enum-flags and remove this legacy header + */ -/// method subtypes +/// bit masks for block types where integration method are used #define DERF 01000 #define KINF 02000 #define LINF 0200000 #define NLINF 04000 - -//// constants that do not appear in .var file -#define nmodlCONST 02000000L - -#define PARF 040000 -#define STEP1 020000 -#define UNITDEF 0400000L diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index e2a2615b37..c288001c0e 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -7,11 +7,8 @@ #include "lexer/modtoken.hpp" -/** Return position of the token as string. This is used used by other - * passes like scope checker to print the location in mod files. - * External token's position : [EXTERNAL] - * Token with unknown position : [UNKNOWN] - * Token from lexer with known position : [line.start_column-end_column] */ +namespace nmodl { + std::string ModToken::position() const { std::stringstream ss; if (external) { @@ -24,9 +21,9 @@ std::string ModToken::position() const { return ss.str(); } -/** Print token as : token at [line.start_column-end_column] type token - * Example: v at [118.9-14] type 376 */ std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { stream << std::setw(15) << mt.name << " at [" << mt.position() << "]"; return stream << " type " << mt.token; } + +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 56cd33ac52..2d42b56161 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -14,22 +14,37 @@ #include "parser/nmodl/location.hh" +namespace nmodl { + +/** + * @defgroup token Token Infrastructure + * @brief All token related implementation + * + * @defgroup token_modtoken Token Classes + * @ingroup token + * @brief Represent token classes for various scanners + * @{ + */ + /** * \class ModToken - * \brief Represent token return by scanner + * \brief Represent token returned by scanner + * + * Every token returned by lexer is represented by ModToken. Some tokens are + * also externally defined names like \c dt, \c t. These names are defined in + * NEURON and hence we set external property to true. Also, location class + * represent the position of the token in nmodl file. By default location is + * initialized to <c>line,column</c> as <c>1,1</c>. Some tokens are explicitly + * added during compiler passes. Hence we set the position to <c>0,0</c> so that we can + * distinguish them from other tokens produced by lexer. * - * Every token returned by lexer is represented by ModToken. - * Some tokens are also externally defined names like dt, t. - * These names are defined in NEURON and hence we set external - * property to true. Also, location class represent the position - * of the token in nmodl file. By default location is initialized - * to line,column as 1,1. Some tokens are explicitly added during - * compiler passes. Hence we set the position to 0,0 so that we - * can distinguish them from other tokens produced by lexer. + * \todo + * - \ref LocationType object is copyable except if we specify the stream name. + * It would be good to track filename when we go for multi-channel optimization + * and code generation. * - * \todo location object is copyable except if we specify the - * stream name. It would be good to track filename when we go - * for multi-channel optimization and code generation. + * \see + * - @ref token_test for how ModToken can be used */ class ModToken { @@ -42,10 +57,10 @@ class ModToken { /// token value returned by lexer int token = -1; - /// position of the token in mod file + /// position of token in the mod file LocationType pos; - /// if token is externally defined symbol + /// true if token is externally defined variable (e.g. \c t, \c dt in NEURON) bool external = false; public: @@ -56,32 +71,65 @@ class ModToken { : pos(nullptr, 0) , external(ext) {} - ModToken(std::string str, int tok, LocationType& loc) - : name(str) - , token(tok) - , pos(loc) {} + ModToken(std::string name, int token, LocationType& pos) + : name(name) + , token(token) + , pos(pos) {} + /// Return a new instance of token ModToken* clone() const { return new ModToken(*this); } + /// Return token text from mod file std::string text() const { return name; } + /// Return token type from lexer int type() const { return token; } + /// Return line number where token started in the mod file int start_line() const { return pos.begin.line; } + /// Return start of column number where token appear in the mod file int start_column() const { return pos.begin.column; } + /** + * Return position of the token as string + * + * This is used used by other passes like scope checker to print the location + * of token in mod files : + * \li external token position : `[EXTERNAL]` + * \li token with unknown position : `[UNKNOWN]` + * \li token with known position : `[line_num.start_column-end_column]` + */ std::string position() const; + /** + * Overload \c << operator to print object + * + * Overload ostream operator to print token in the form : + * + * \code + * token at [line.start_column-end_column] type token + * \endcode + * + * For example: + * + * \code + * v at [118.9-14] type 376 + * \endcode + */ friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt); }; + +/** @} */ // end of token_modtoken + +} // namespace nmodl diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index ece9d58241..e186642843 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -4,6 +4,8 @@ * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. + * + * @brief Lexer for NMODL specification *************************************************************************/ %{ @@ -192,7 +194,7 @@ ELSE { case Token::LINEAR: case Token::PARTIAL: case Token::KINETIC: - lexcontext = type; + lexical_context = type; break; } @@ -200,7 +202,7 @@ ELSE { return token_symbol(yytext, loc, type); } else { /** if flux variable is used in the kinetic block */ - if ( lexcontext == Token::KINETIC && + if ( lexical_context == Token::KINETIC && (strcmp(yytext, "f_flux") == 0 || strcmp(yytext, "b_flux") == 0)) { nmodl::ast::Name value( new nmodl::ast::String(yytext) ); ModToken tok(yytext, Token::FLUX_VAR, loc); @@ -280,19 +282,19 @@ ELSE { "~" { /** return token depending on the reaction context */ - if (lexcontext == Token::NONLINEAR) { + if (lexical_context == Token::NONLINEAR) { return token_symbol(yytext, loc, Token::NONLIN1); } - if (lexcontext == Token::LINEAR) { + if (lexical_context == Token::LINEAR) { return token_symbol(yytext, loc, Token::LIN1); } - if (lexcontext == Token::PARTIAL) { + if (lexical_context == Token::PARTIAL) { return token_symbol(yytext, loc, Token::TILDE); } - if (lexcontext == Token::KINETIC) { + if (lexical_context == Token::KINETIC) { return token_symbol(yytext, loc, Token::REACTION); } @@ -481,13 +483,17 @@ ELSE { %% -/** Some of the utility functions can't be defined outside due to macros. - * These are utility functions ported from original nmodl implementation. */ +/** + * Some of the utility functions can't be defined outside due to macros. + * These are utility functions ported from original nmodl implementation. + */ -/** This implementation of NmodlFlexLexer::yylex() is required to fill the - * vtable of the class NmodlFlexLexer. We define the scanner's main yylex - * function via YY_DECL to reside in the Lexer class instead. */ +/** + * This implementation of NmodlFlexLexer::yylex() is required to fill the + * vtable of the class NmodlFlexLexer. We define the scanner's main yylex + * function via YY_DECL to reside in the Lexer class instead. + */ int NmodlFlexLexer::yylex() { throw std::runtime_error("next_token() should be used instead of yylex()"); } @@ -497,11 +503,15 @@ void nmodl::parser::NmodlLexer::set_debug(bool b) { yy_flex_debug = b; } -/** Scan unit which is a string between opening and closing parenthesis. - * We store this in lexer itself and then consumed later from the parser. */ +/** + * Scan unit which is a string between opening and closing parenthesis. + * We store this in lexer itself and then consumed later from the parser. + */ void nmodl::parser::NmodlLexer::scan_unit() { - /** We are interested in unit after first parenthesis. - * So to get correct position update the location. */ + /** + * We are interested in unit after first parenthesis. + * So to get correct position update the location. + */ loc.step(); std::string str; @@ -520,7 +530,8 @@ void nmodl::parser::NmodlLexer::scan_unit() { } /** YY_USER_ACTION is not executed if are consuming input - * using yyinput and hence increase location */ + * using yyinput and hence increase location + */ loc.columns(str.size()); ModToken tok(str, Token::UNITS, loc); @@ -528,7 +539,7 @@ void nmodl::parser::NmodlLexer::scan_unit() { last_unit->set_token(tok); } -/** return last scanned unit, it shouln't be null pointer */ +/** Return last scanned unit, it shouln't be null pointer */ nmodl::ast::String* nmodl::parser::NmodlLexer::get_unit() { if (last_unit == nullptr) { throw std::runtime_error("Trying to get unscanned empty unit"); diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 173c9e7108..3d20cd6c3b 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -11,16 +11,20 @@ #include "parser/nmodl/nmodl_parser.hpp" -/** Flex expects the declaration of yylex to be defined in the macro YY_DECL - * and C++ parser class expects it to be declared. */ +/** + * Flex expects the declaration of yylex() to be defined in the macro YY_DECL + * and C++ parser class expects it to be declared. + */ #ifndef YY_DECL #define YY_DECL nmodl::parser::NmodlParser::symbol_type nmodl::parser::NmodlLexer::next_token() #endif -/** For creating multiple (different) lexer classes, we can use '-P' flag - * (or prefix option) to rename each yyFlexLexer to some other name like - * ‘xxFlexLexer’. And then include <FlexLexer.h> in other sources once per - * lexer class, first renaming yyFlexLexer as shown below. */ +/** + * For creating multiple (different) lexer classes, we can use `-P` flag + * (or prefix option) to rename each `NmodlFlexLexer` to some other name like + * `xxFlexLexer`. And then include <FlexLexer.h> in other sources once per + * lexer class, first renaming `yyFlexLexer` as shown below. + */ #ifndef __FLEX_LEXER_H #define yyFlexLexer NmodlFlexLexer #include "FlexLexer.h" @@ -29,6 +33,14 @@ namespace nmodl { namespace parser { +/** + * @defgroup lexer Lexer Infrastructure + * @brief All lexer classes implementation + * + * @addtogroup lexer + * @{ + */ + /** * \class NmodlLexer * \brief Represent Lexer/Scanner class for NMODL language parsing @@ -38,59 +50,100 @@ namespace parser { * NmodlFlexLexer. But we change the context of the generated yylex() function * because the yylex() defined in NmodlFlexLexer has no parameters. Also, note * that implementation of the member functions are in nmodl.l file due to use - * of macros. */ + * of macros. + */ class NmodlLexer: public NmodlFlexLexer { - public: - /** Reference to driver object which contains this lexer instance. This is - * used for error reporting and checking macro definitions. */ + /** + * \brief Reference to driver object where this lexer resides + * + * The driver object is used for macro definitions and error checking + */ NmodlDriver& driver; - /// For tracking location of the tokens - location loc; - - /// Units are stored in the scanner (could be stored in driver) + /// Units are stored in the scanner (could be stored in the driver though) ast::String* last_unit = nullptr; - /** For reaction ('~') we return different token based on the lexical - * context, store the current context. Note that this was returned from - * parser in original implementation. */ - int lexcontext = 0; + /** + * \brief Context of the reaction (`~`) token + * + * For reaction (`~`) we return different token based on one of the following + * lexical context: + * - NONLINEAR + * - LINEAR + * - KINETIC + * - PARTIAL + */ + int lexical_context = 0; + + public: + /// location of the parsed token + location loc; - /** The streams in and out default to cin and cout, but that assignment - * is only made when initializing in yylex(). */ - explicit NmodlLexer(NmodlDriver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + /// \name Ctor & dtor + /// \{ + + /** + * \brief NmodlLexer constructor + * + * @param driver NmodlDriver where this lexer resides + * @param in Input stream from where tokens will be read + * @param out Output stream where output will be sent + */ + explicit NmodlLexer(NmodlDriver& driver, + std::istream* in = nullptr, + std::ostream* out = nullptr) : NmodlFlexLexer(in, out) - , driver(drv) {} + , driver(driver) {} ~NmodlLexer() override = default; - ; - /** Main lexing function generated by flex according to the macro declaration - * YY_DECL above. The generated bison parser then calls this virtual function - * to fetch new tokens. Note that yylex() has different declaration and hence - * it can't be used for new lexer. */ - virtual NmodlParser::symbol_type next_token(); + /// \} - /// Enable debug output (via yyout) if compiled into the scanner. - void set_debug(bool b); + /** + * \brief Reset the column position of lexer to 0 + * + * Due to COPY mode the end position is not accurate. Set column to 0 to + * avoid confusion (see JIRA issue NOCMODL-25) + */ + void reset_end_position() { + loc.end.column = 0; + } - /** For units we have to consume string until end of closing parenthesis - * and store it in the scanner object. */ + /** + * \brief Function for lexer to scan token (replacement for \c yylex()) + * + * This is main lexing function generated by `flex` according to the macro + * declaration \c YY_DECL. The generated bison parser then calls this virtual + * function to fetch new tokens. Note that \c yylex() has different declaration + * and hence can't be used for new lexer. + * + * @return Symbol encapsulating parsed token + */ + virtual NmodlParser::symbol_type next_token(); + + /** + * \brief Scan subsequent text as unit + * + * For units we have to consume string until end of closing parenthesis + * and store it in the scanner. This will be later returned by get_unit(). + */ void scan_unit(); - /** Return unit object created by scan_unit(). Initialize to nullptr to avoid - * using empty units or units from last parsing. */ - ast::String* get_unit(); + /** + * \brief Input text until end of line + * + * For construct like TITLE we have to scan text until end of line + */ + std::string input_line(); - /// For TITLE we have to input string until end of line. - std::string inputline(); + /// Return last scanned unit as ast::String + ast::String* get_unit(); - /** Due to copy more the end position is not accurate. Set column 0 to avoid - * confusion (see NOCMODL-25). */ - void reset_end_position() { - loc.end.column = 0; - } + /// Enable debug output (via yyout) if compiled into the scanner. + void set_debug(bool b); }; +/** @} */ // end of lexer + } // namespace parser } // namespace nmodl diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index f64bb34809..03349b5174 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -18,16 +18,32 @@ namespace nmodl { using Parser = parser::NmodlParser; -/// create symbol for double/real ast class +/** + * Create a symbol for ast::Double AST class + * + * @param value Double value parsed by lexer + * @param pos Position of value in the mod file + * @return Symbol for double value + */ SymbolType double_symbol(double value, PositionType& pos) { ModToken token(std::to_string(value), Token::REAL, pos); - ast::Double floatvalue(value); - floatvalue.set_token(token); - return Parser::make_REAL(floatvalue, pos); + ast::Double float_value(value); + float_value.set_token(token); + return Parser::make_REAL(float_value, pos); } -/** Create symbol for integer ast class. Integer class also represent - * macro definition and hence could have associated text. */ + +/** + * Create a symbol for ast::Integer AST + * + * ast::Integer class also represent macro definition and + * hence could have associated text. + * + * @param value Integer value parsed by lexer + * @param pos Position of value in the mod file + * @param text associated macro for the value + * @return Symbol for integer value + */ SymbolType integer_symbol(int value, PositionType& pos, const char* text) { ast::Name* macro = nullptr; ModToken token(std::to_string(value), Token::INTEGER, pos); @@ -37,16 +53,25 @@ SymbolType integer_symbol(int value, PositionType& pos, const char* text) { macro->set_token(token); } - ast::Integer intvalue(value, macro); - intvalue.set_token(token); - return Parser::make_INTEGER(intvalue, pos); + ast::Integer int_value(value, macro); + int_value.set_token(token); + return Parser::make_INTEGER(int_value, pos); } -/** Create symbol for name ast class. + +/** + * Create symbol for ast::Name AST class + * + * @param text Text value parsed by lexer + * @param pos Position of value in the mod file + * @param type Token type returned by lexer (see parser::NmodlParser::token::yytokentype) + * @return Symbol for name value * - * \todo In addition to keywords and methods, there are also external - * definitions for math and neuron specific functions/variables. In the - * token we should mark those as external. */ + * \todo + * In addition to keywords and methods, there are also external + * definitions for math and neuron specific functions/variables. + * In the token we should mark those as external. + */ SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type) { ModToken token(text, type, pos); ast::Name value(new ast::String(text)); @@ -54,9 +79,17 @@ SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType typ return Parser::make_NAME(value, pos); } -/** Create symbol for prime ast class. Prime has name as well as - * order. Text returned by lexer has single quote (') as an order. - * We count order and remove quote from text */ + +/** + * Create symbol for ast::Prime AST class + * + * ast::Prime has name as well as order. Text returned by lexer has single + * quote (`'`) as an order. We count the order and remove quote from the text. + * + * @param text Prime variable name parsed by lexer + * @param pos Position of text in the mod file + * @return Symbol for prime variable + */ SymbolType prime_symbol(std::string text, PositionType& pos) { ModToken token(text, Token::PRIME, pos); auto order = std::count(text.begin(), text.end(), '\''); @@ -69,7 +102,13 @@ SymbolType prime_symbol(std::string text, PositionType& pos) { return Parser::make_PRIME(value, pos); } -/// create symbol for string ast class + +/** + * Create symbol for ast::String AST class + * @param text Text value parsed by lexer + * @param pos Position of value in the mod file + * @return Symbol for string value + */ SymbolType string_symbol(const std::string& text, PositionType& pos) { ModToken token(text, Token::STRING, pos); ast::String value(text); @@ -77,13 +116,21 @@ SymbolType string_symbol(const std::string& text, PositionType& pos) { return Parser::make_STRING(value, pos); } -/** Create symbol for generic / non-ast token. These tokens doesn't have - * specific value to pass to the parser. They are more part of grammar. - * Depending on the type of toke, we create appropriate symbol. From - * lexer we pass token type (which is optional). This is required for - * reaction parsing where we have "lexical context". Hence, if token - * type is passed then we don't check/search for the token type. */ +/** + * Create symbol for AST class + * + * These tokens doesn't have specific value to pass to the parser. They are more + * part of grammar. Depending on the type of toke, we create appropriate + * symbol. From lexer we pass token type (which is optional). This is required + * for reaction parsing where we have "lexical context". Hence, if token type is + * passed then we don't check/search for the token type. + * + * @param key Text parsed by lexer + * @param pos Position of value in the mod file + * @param type Token type returned by lexer (see parser::NmodlParser::token::yytokentype) + * @return Symbol for given token + */ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType type) { /// if token type is not passed, check if it is keyword or method if (type == Token::UNKNOWN) { @@ -94,9 +141,7 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ ModToken token(key, type, pos); - /// lookup for token type and create approrpiate symbol type switch (static_cast<int>(type)) { - /// Most of the nmodl keywords case Token::AFTER: return Parser::make_AFTER(token, pos); case Token::BBCOREPOINTER: @@ -322,8 +367,10 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ case Token::PERIOD: return Parser::make_PERIOD(token, pos); - /** We hit default case for missing token type. This is more likely - * a bug in the implementation where we forgot to handle token type. */ + /** + * we hit default case for missing token type. This is more likely + * a bug in the implementation where we forgot to handle token type. + */ default: auto msg = "Token type not found while creating a symbol!"; throw std::runtime_error(msg); diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index 74944bbcef..51510f7dcf 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -7,19 +7,19 @@ #pragma once -#include "parser/nmodl/location.hh" -#include "parser/nmodl/nmodl_parser.hpp" - /** - * \brief Utility functions for nmodl lexer + * \file nmodl_utils.hpp + * \brief Utility functions for NMODL lexer * - * From nmodl lexer we return different symbols to parser. - * Instead of writing those functions in the flex implementation - * file, those commonly used routines are defined here. Some of - * these tasks were implemented in list.c file in the oiginal mod2c - * implementation. + * From nmodl lexer we return different symbols to parser. Instead of writing + * those functions in the flex implementation file, those commonly used routines + * are defined here. Some of these tasks were implemented in list.c file in the + * original mod2c implementation. */ +#include "parser/nmodl/location.hh" +#include "parser/nmodl/nmodl_parser.hpp" + namespace nmodl { using PositionType = parser::location; diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 856acf966f..46c49eac14 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -19,14 +19,22 @@ using Token = parser::NmodlParser::token; using TokenType = parser::NmodlParser::token_type; using Parser = parser::NmodlParser; -namespace internal { +/// details of lexer tokens +namespace details { + // clang-format off - /** Keywords from NMODL language : name and token pair + + /** + * \brief Keywords from NMODL language + * + * Keywords are defined with key-value pair where key is name + * from scanner and value is token type used in parser. * * \todo Some keywords have different token names, e.g. TITLE * keyword has MODEL as a keyword. These token names are used * in multiple context and hence we are keeping original names. - * Once we finish code generation part then we change this. */ + * Once we finish code generation part then we change this. + */ static std::map<std::string, TokenType> keywords = { {"VERBATIM", Token::VERBATIM}, {"COMMENT", Token::BLOCK_COMMENT}, @@ -121,21 +129,37 @@ namespace internal { {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; // clang-format on -/// numerical methods supported in nmodl + +/** + * \class MethodInfo + * \brief Information about integration method + */ struct MethodInfo { - /// method types that will work with this method + /// block types where this method will work with int64_t subtype = 0; - /// if it's a variable timestep method - int varstep = 0; + /// true if it is a variable timestep method + int variable_timestep = 0; MethodInfo() = default; + MethodInfo(int64_t s, int v) : subtype(s) - , varstep(v) {} + , variable_timestep(v) {} }; + // clang-format off + /** + * Integration methods available in the NMODL + * + * Different integration methods are available in NMODL and they are used with + * different block types in NMODL. This variable provide list of method names, + * which blocks they can be used with and whether it is usable with variable + * timestep. + * + * \todo MethodInfo::subtype should be changed from integer flag to proper type + */ static std::map<std::string, MethodInfo> methods = {{"adams", MethodInfo(DERF | KINF, 0)}, {"runge", MethodInfo(DERF | KINF, 0)}, {"euler", MethodInfo(DERF | KINF, 0)}, @@ -157,24 +181,26 @@ struct MethodInfo { {"cvode_t_v", MethodInfo(0, 0)}}; // clang-format on -/** In the original implementation different vectors were created for - * extdef, extdef2, extdef3, extdef4 etc. Instead of that we are changing - * those vectors with <name, type> map. This will help us to search - * in single map and find it's type. The types are defined as follows: - * - * DefinitionType::EXT_DOUBLE : external names that can be used as doubles - * without giving an error message - * - * DefinitionType::EXT_2 : external function names that can be used with - * array and function name arguments - * - * DefinitionType::EXT_3 : function names that get two reset arguments + +/** + * Definition type similar to old implementation * - * DefinitionType::EXT_4 : functions that need a first arg of NrnThread* + * In the original implementation of NMODL (mod2c, nocmodl) different vectors were + * created for \c extdef, \c extdef2, \c extdef3, \c extdef4 etc. We are changing + * those vectors with <c><name, type></c> map. This will help us to search + * in single map and find it's type. The types are defined as follows: * - * DefinitionType::EXT_5 : the extdef names that are not threadsafe + * - DefinitionType::EXT_DOUBLE : external names that can be used as doubles + * without giving an error message + * - DefinitionType::EXT_2 : external function names that can be used with + * array and function name arguments + * - DefinitionType::EXT_3 : function names that get two reset arguments + * - DefinitionType::EXT_4 : functions that need a first arg of \c NrnThread* + * - DefinitionType::EXT_5 : external definition names that are not \c threadsafe * - * These types were used so that it's easy to it to old implementation. */ + * \todo These types were implemented for easy migration from old implementation. + * As first draft of code generation is complete, this can be now refactored. + */ enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; @@ -284,72 +310,83 @@ enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; {"nrn_random_play", DefinitionType::EXT_5}}; // clang-format on -/** Internal NEURON variables that can be used in nmod files. The compiler - * passes like scope checker need to know if certain variable is undefined. - * Note that these are not used by lexer/parser. */ +/** + * Variables from NEURON that are directly used in NMODL + * + * NEURON exposes certain variable that can be directly used in NMODLvar. + * The passes like scope checker needs to know if certain variable is + * undefined and hence these needs to be inserted into symbol table + */ static std::vector<std::string> neuron_vars = {"t", "dt", "celsius", "v", "diam", "area"}; + +/// Return token type for the keyword TokenType keyword_type(const std::string& name) { return keywords[name]; } -/** \todo: revisit implementation, this is no longer - * necessary as token_type is sufficient - */ -TokenType method_type(const std::string& /*name*/) { - return Token::METHOD; -} - -bool is_externdef(const std::string& name) { - return (extern_definitions.find(name) != extern_definitions.end()); -} - -DefinitionType extdef_type(const std::string& name) { - if (!is_externdef(name)) { - throw std::runtime_error("Can't find " + name + " in external definitions!"); - } - return extern_definitions[name]; -} - -} // namespace internal +} // namespace details -/// methods exposed to lexer, parser and compilers passes +/** + * Check if given name is a keyword in NMODL + * @param name token name + * @return true if name is a keyword + */ bool is_keyword(const std::string& name) { - return (internal::keywords.find(name) != internal::keywords.end()); + return (details::keywords.find(name) != details::keywords.end()); } + +/** + * Check if given name is an integration method in NMODL + * @param name Name of the integration method + * @return true if name is an integration method in NMODL + */ bool is_method(const std::string& name) { - return (internal::methods.find(name) != internal::methods.end()); + return (details::methods.find(name) != details::methods.end()); } -/// return token type for associated name (used by nmodl scanner) + +/** + * Return token type for given token name + * @param name Token name from lexer + * @return type of NMODL token + */ TokenType token_type(const std::string& name) { if (is_keyword(name)) { - return internal::keyword_type(name); + return details::keyword_type(name); } if (is_method(name)) { - return internal::method_type(name); + return Token::METHOD; } - throw std::runtime_error("get_token_type called for non-existent token " + name); + throw std::runtime_error("token_type called for non-existent token " + name); } -/// return all external variables + +/** + * Return variables declared in NEURON that are available to NMODL + * @return vector of NEURON variables + */ std::vector<std::string> get_external_variables() { std::vector<std::string> result; - result.insert(result.end(), internal::neuron_vars.begin(), internal::neuron_vars.end()); + result.insert(result.end(), details::neuron_vars.begin(), details::neuron_vars.end()); return result; } -/// return all solver methods as well as commonly used math functions + +/** + * Return functions that can be used in the NMODL + * @return vector of function names used in NMODL + */ std::vector<std::string> get_external_functions() { std::vector<std::string> result; - result.reserve(internal::methods.size()); - for (auto& method: internal::methods) { + result.reserve(details::methods.size()); + for (auto& method: details::methods) { result.push_back(method.first); } - for (auto& definition: internal::extern_definitions) { + for (auto& definition: details::extern_definitions) { result.push_back(definition.first); } return result; diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 55efc3b010..f8e3f2efab 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -7,14 +7,21 @@ #pragma once -#include "parser/nmodl/nmodl_parser.hpp" +/** + * \file token_mapping.hpp + * \brief Map different tokens from lexer to token types + */ + #include <string> +#include "parser/nmodl/nmodl_parser.hpp" + namespace nmodl { bool is_keyword(const std::string& name); bool is_method(const std::string& name); -nmodl::parser::NmodlParser::token_type token_type(const std::string& name); + +parser::NmodlParser::token_type token_type(const std::string& name); std::vector<std::string> get_external_variables(); std::vector<std::string> get_external_functions(); diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 10092e9d40..5d02874d2d 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -3,6 +3,8 @@ * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. + * + * @brief Lexer for verbatim constructs from NMODL (e.g. VERBATIM, COMMENT) *************************************************************************/ %{ @@ -13,17 +15,21 @@ #include "parser/verbatim_context.hpp" #include "parser/verbatim_parser.hpp" - /* the scanner state will include a field called yyextra + /** + * The scanner state will include a field called yyextra * that can be used for user-defined state */ #define YY_EXTRA_TYPE nmodl::parser::VerbatimContext* - /* we need to parse strings as well as files, we redefine YY_INPUT + /** + * We need to parse strings as well as files, we redefine YY_INPUT * and give it a C++ flavour. It will use the istream from the parser * context to read the next character. The parser context defaults is - * to cin, but we will set it to an istringstream later - * @TODO: with this I am not able recognize NEWLINE character! + * to cin, but we will set it to an istringstream later. + * + * \todo + * - not able recognize NEWLINE character */ #define YY_INPUT(buf,result,max_size) \ { \ @@ -39,60 +45,64 @@ %} -/* need to provide this option to flex otherwise complain: +/** + * need to provide this option to flex otherwise complain: * "error: 'yymore_used_but_not_detected' was not declared in * this scope */ %option yymore -/* lexer header file */ +/** lexer header file */ %option header-file="verbatim_lexer.hpp" -/* lexer implementation file */ +/** lexer implementation file */ %option outfile="verbatim_lexer.cpp" -/* lexer prefix */ +/** lexer prefix */ %option prefix="Verbatim_" -/* need to be reentrant */ +/** need to be reentrant */ %option reentrant -/* no need for includes */ +/** no need for includes */ %option noyywrap -/* need to unput in buffer for custom routines +/** + * need to unput in buffer for custom routines * \todo : due to unused function warning this * option is disabled */ %option nounput -/* need to input in buffer for custom routines +/** + * need to input in buffer for custom routines * \todo : due to unused function warning this * option is disabled */ %option noinput -/* not an interactive lexer, takes a file */ +/** not an interactive lexer, takes a file */ %option batch -/* debug, disable for production */ +/** debug, disable for production */ %option debug -/* bison compatible lexer */ +/** bison compatible lexer */ %option bison-bridge -/* bison location information */ +/** bison location information */ %option bison-locations -/* keep line information */ +/** keep line information */ %option yylineno -/* mode for verbatim or comment */ +/** mode for verbatim or comment */ %x COPY_MODE %% - /* Currently we do simple thing for VERBATIM and COMMENT + /** + * Currently we do simple thing for VERBATIM and COMMENT * just pass them as it is! * This will be revisited in the future once we have * everything in place working with clang! @@ -133,15 +143,14 @@ %% -/* initialize nmodlext lexer context */ - +/** initialize nmodlext lexer context */ void nmodl::parser::VerbatimContext::init_scanner() { yylex_init(&scanner); yyset_extra(this, scanner); } -/* delete nmodlext lexer context */ +/** delete nmodlext lexer context */ void nmodl::parser::VerbatimContext::destroy_scanner() { yylex_destroy(scanner); } diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 200d5cc849..b055033c87 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -14,11 +14,18 @@ #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" + +/** @file + * @defgroup token_test Token Tests + * @ingroup token + * @brief All tests for @ref token_modtoken + * @{ + */ + using namespace nmodl; using nmodl::parser::NmodlDriver; using nmodl::parser::NmodlLexer; -/// retrieve token from lexer template <typename T> void symbol_type(const std::string& name, T& value) { std::istringstream ss(name); @@ -31,9 +38,8 @@ void symbol_type(const std::string& name, T& value) { value = sym.value.as<T>(); } -/// test symbol type returned by lexer -TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { - SECTION("Symbol type : name ast class test") { +TEST_CASE("Lexer symbol type tests") { + SECTION("test for ast class of type Name") { ast::Name value; { @@ -51,7 +57,7 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { } } - SECTION("Symbol type : prime ast class test") { + SECTION("test for ast class of type Prime") { ast::PrimeName value; { @@ -63,3 +69,5 @@ TEST_CASE("Lexer symbol type tests", "[TokenPrinter]") { } } } + +/** @} */ // end of token_test From 4d5fa436516c73de5ff583b069372cd9cd6034c4 Mon Sep 17 00:00:00 2001 From: Jorge Blanco Alonso <41900536+jorblancoa@users.noreply.github.com> Date: Thu, 18 Apr 2019 15:10:14 +0200 Subject: [PATCH 190/871] Sphinx documentation improvements (BlueBrain/nmodl#138) - Build documentation explicitly (install_doc). - Always run jupyter notebooks. Stop build on error. - Fix notebook html display: align directives changed to aligned on latex - Change pytest minimum required to be on sync with BB5 NMODL Repo SHA: BlueBrain/nmodl@c6bc80e5cc310dcc8fb4f078278724a6a7d1f059 --- docs/nmodl/transpiler/conf.py | 3 +++ .../notebooks/nmodl-kinetic-schemes.ipynb | 26 +++++++++---------- setup.py | 14 ++-------- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index edd7d25e51..7d142eb08f 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -63,6 +63,9 @@ """ ) +nbsphinx_execute = "always" +nbsphinx_kernel_name = "python3" + # Setup the breathe extension breathe_projects = {"NMODL C++ library": "./doxyoutput/xml"} breathe_default_project = "NMODL C++ library" diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb index a319147bea..218e8dc747 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -61,10 +61,10 @@ "\n", "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", "$$\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "r^{(f)}_i &= k^{(f)}_i \\prod_j Y_j^{\\nu_{ij}^{L}} \\\\\n", "r^{(b)}_i &= k^{(b)}_i \\prod_j Y_j^{\\nu_{ij}^{R}}\n", - "\\end{align}\n", + "\\end{aligned}\n", "$$\n", "***" ] @@ -187,25 +187,25 @@ "- the state vector $Y$ has 2 elements\n", "$$\n", "Y = \\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "h \\\\\n", "m \n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "$$\n", "- the stoichiometric coefficients are 1x2 matrices\n", "$$\n", "\\nu^L = \\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "1 && 0\n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "$$\n", "$$\n", "\\nu^R = \\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "0 && 1\n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "$$\n", "- the rate vectors contain 1 element:\n", @@ -227,18 +227,18 @@ "$$\n", "\\frac{dY}{dt} =\n", "\\left(\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "-1 && 1\n", - "\\end{align}\n", + "\\end{aligned}\n", "\\right)\n", "(ah - bm)\n", "$$\n", "which in terms of the state variables can be written:\n", "$$\n", - "\\begin{align}\n", + "\\begin{aligned}\n", "\\frac{dh}{dt} &= bm - ah \\\\\n", "\\frac{dm}{dt} &= ah - bm\n", - "\\end{align}\n", + "\\end{aligned}\n", "$$" ] }, @@ -441,7 +441,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/setup.py b/setup.py index fd95fb51d1..bf17ad38cd 100644 --- a/setup.py +++ b/setup.py @@ -101,15 +101,6 @@ def build_extension(self, ext): ["cmake", "--build", "."] + build_args, cwd=self.build_temp ) - -class NMODLInstall(install): - def run(self): - if not self.skip_build: - self.run_command("test") # to install the shared library - self.run_command("install_doc") - super().run() - - class NMODLTest(test): """Custom disutils command that acts like as a replacement for the "test" command. @@ -140,7 +131,7 @@ def run(self): subprocess.check_call([sys.executable, __file__, "doctest"]) -install_requirements = ["jinja2>=2.10", "PyYAML>=3.13", "sympy>=1.2", "pytest>=4.0.0"] +install_requirements = ["jinja2>=2.9", "PyYAML>=3.13", "sympy>=1.2"] setup( name="NMODL", @@ -153,7 +144,6 @@ def run(self): ext_modules=[CMakeExtension("nmodl")], cmdclass=lazy_dict( build_ext=CMakeBuild, - install=NMODLInstall, test=NMODLTest, install_doc=get_sphinx_command, doctest=get_sphinx_command, @@ -162,5 +152,5 @@ def run(self): setup_requires=["nbsphinx", "m2r", "exhale", "sphinx-rtd-theme", "sphinx<2"] + install_requirements, install_requires=install_requirements, - tests_require=["pytest>=4.0.0"], + tests_require=["pytest>=3.7.2"], ) From b3e5cd3a709d2f0b17bc5c3cbe7e915889685441 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris <iomagkanaris@gmail.com> Date: Thu, 18 Apr 2019 18:15:04 +0200 Subject: [PATCH 191/871] Adding units parsing capability (BlueBrain/nmodl#133) - implement units parser based on flex & bison - units are now stored in UnitTable once nrnunits.lib is parsed - added nrnunits.lib which gets installed in share directory - add unit tests for lexer and parser Todo : - difference in dimensions of bohr radius only (mod2c wrong based on wikipedia) NMODL Repo SHA: BlueBrain/nmodl@1c9e65e413bdc77f367d12eb090a67528ea876a4 --- cmake/nmodl/CMakeLists.txt | 6 +- share/nmodl/nrnunits.lib | 621 ++++++++++++++++++ src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- .../version.cpp.in => config/config.cpp.in} | 15 +- src/nmodl/config/config.h | 51 ++ src/nmodl/lexer/CMakeLists.txt | 50 +- src/nmodl/lexer/main_c.cpp | 2 +- src/nmodl/lexer/main_nmodl.cpp | 2 +- src/nmodl/lexer/main_units.cpp | 54 ++ src/nmodl/lexer/unit.ll | 163 +++++ src/nmodl/lexer/unit_lexer.hpp | 88 +++ src/nmodl/nmodl/main.cpp | 2 +- src/nmodl/parser/CMakeLists.txt | 4 + src/nmodl/parser/main_c.cpp | 3 +- src/nmodl/parser/main_nmodl.cpp | 4 +- src/nmodl/parser/main_units.cpp | 54 ++ src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/parser/nmodl_driver.cpp | 2 +- src/nmodl/parser/nmodl_driver.hpp | 2 +- src/nmodl/parser/unit.yy | 217 ++++++ src/nmodl/parser/unit_driver.cpp | 75 +++ src/nmodl/parser/unit_driver.hpp | 77 +++ src/nmodl/pybind/pynmodl.cpp | 2 +- src/nmodl/units/units.cpp | 356 ++++++++++ src/nmodl/units/units.hpp | 281 ++++++++ src/nmodl/utils/CMakeLists.txt | 2 +- src/nmodl/version/version.h | 20 - src/nmodl/visitors/main.cpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 11 + test/nmodl/transpiler/units/lexer.cpp | 66 ++ test/nmodl/transpiler/units/parser.cpp | 190 ++++++ 31 files changed, 2383 insertions(+), 43 deletions(-) create mode 100644 share/nmodl/nrnunits.lib rename src/nmodl/{version/version.cpp.in => config/config.cpp.in} (51%) create mode 100644 src/nmodl/config/config.h create mode 100644 src/nmodl/lexer/main_units.cpp create mode 100755 src/nmodl/lexer/unit.ll create mode 100644 src/nmodl/lexer/unit_lexer.hpp create mode 100644 src/nmodl/parser/main_units.cpp create mode 100644 src/nmodl/parser/unit.yy create mode 100644 src/nmodl/parser/unit_driver.cpp create mode 100644 src/nmodl/parser/unit_driver.hpp create mode 100644 src/nmodl/units/units.cpp create mode 100644 src/nmodl/units/units.hpp delete mode 100644 src/nmodl/version/version.h create mode 100644 test/nmodl/transpiler/units/lexer.cpp create mode 100644 test/nmodl/transpiler/units/parser.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 6fd242ad28..9d9be30571 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -106,9 +106,9 @@ include_directories(${PROJECT_SOURCE_DIR}/ext/cli11/include) # ============================================================================= set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) -# generate file with version number from git -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/version/version.cpp.in - ${CMAKE_CURRENT_BINARY_DIR}/version.cpp @ONLY) +# generate file with version number from git and nrnunits.lib file path +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in + ${CMAKE_CURRENT_BINARY_DIR}/config.cpp @ONLY) # ============================================================================= # list of autogenerated files diff --git a/share/nmodl/nrnunits.lib b/share/nmodl/nrnunits.lib new file mode 100644 index 0000000000..1db06ff25a --- /dev/null +++ b/share/nmodl/nrnunits.lib @@ -0,0 +1,621 @@ +/ from gnu units distribution +/ Nov, 2017 updated faraday, R, e, planck, hbar, mole, k according to +/ https://physics.nist.gov/cuu/Constants/index.html + +/ primitive units + +m *a* +kg *b* +sec *c* +coul *d* +candela *e* +dollar *f* +bit *h* +erlang *i* +K *j* + +/ prefixes + +yotta- 1e24 +zetta- 1e21 +exa- 1e18 +peta- 1e15 +tera- 1e12 +giga- 1e9 +mega- 1e6 +myria- 1e4 +kilo- 1e3 +hecto- 1e2 +deka- 1e1 +deci- 1e-1 +centi- 1e-2 +milli- 1e-3 +micro- 1e-6 +nano- 1e-9 +pico- 1e-12 +femto- 1e-15 +atto- 1e-18 +zopto- 1e-21 +yocto- 1e-24 +/ +semi- .5 +demi- .5 +/ +/Y- yotta +/Z- zetta +/E- exa +/P- peta +/T- tera +/G- giga +/M- mega +/k- kilo +/h- hecto +/da- deka +/d- deci +/c- centi +/m- milli +/p- pico +/f- femto +/a- atto +/z- zopto +/y- yocto + +/ constants + +fuzz 1 +pi 3.14159265358979323846 +c 2.99792458+8 m/sec fuzz +g 9.80665 m/sec2 +au 1.49597871+11 m fuzz +mole 6.022169+23 fuzz +/mole 6.022140857+23 fuzz +e 1.6021917-19 coul fuzz +/e 1.6021766208-19 coul fuzz +energy c2 +force g +mercury 1.33322+5 kg/m2-sec2 +hg mercury + +/ dimensionless + +radian .5 / pi +degree 1|180 pi-radian +circle 2 pi-radian +turn 2 pi-radian +revolution turn +rev turn +grade .9 degree +arcdeg 1 degree +arcmin 1|60 arcdeg +ccs 1|36 erlang +arcsec 1|60 arcmin + +steradian radian2 +sphere 4 pi-steradian +sr steradian + +/ Time + +second sec +s sec +minute 60 sec +min minute +hour 60 min +hr hour +day 24 hr +da day +week 7 day +year 365.24219879 day fuzz +yr year +month 1|12 year +ms millisec +us microsec + +/ Mass + +gram millikg +gm gram +mg milligram +metricton kilokg + +/ Avoirdupois + +lb .45359237 kg +pound lb +lbf lb g +ounce 1|16 lb +oz ounce +dram 1|16 oz +dr dram +grain 1|7000 lb +gr grain +shortton 2000 lb +ton shortton +longton 2240 lb + +/ Apothecary + +scruple 20 grain +apdram 60 grain +apounce 480 grain +appound 5760 grain +troypound appound + +/ Length + +meter m +cm centimeter +mm millimeter +km kilometer +nm nanometer +micron micrometer +angstrom decinanometer + +inch 2.54 cm +in inch +foot 12 in +feet foot +ft foot +yard 3 ft +yd yard +rod 5.5 yd +rd rod +mile 5280 ft +mi mile + +british 1200|3937 m/ft +nmile 1852m + +acre 4840 yd2 + +cc cm3 +liter kilocc +ml milliliter + +/ US Liquid + +gallon 231 in3 +imperial 1.20095 +gal gallon +quart 1|4 gal +qt quart +pint 1|2 qt +pt pint + +floz 1|16 pt +fldr 1|8 floz + +/ US Dry + +dry 268.8025 in3/gallon fuzz +peck 8 dry-quart +pk peck +bushel 4 peck +bu bushel +chaldron 36 bushel + +/ British + +brgallon 277.420 in3 fuzz +brquart 1|4 brgallon +brpint 1|2 brquart +brfloz 1|20 brpint +brpeck 554.84 in3 fuzz +brbushel 4 brpeck + +/ Energy Work + +newton kg-m/sec2 +nt newton +N newton +joule nt-m +cal 4.1868 joule + +/ Electrical + +coulomb coul +C coul +ampere coul/sec +amp ampere +watt joule/sec +volt watt/amp +ohm volt/amp +mho /ohm +farad coul/volt +henry sec2/farad +weber volt-sec + +/ Light + +cd candela +lumen cd sr +lux cd sr/m2 + +/ Wall Street Journal, July 2, 1993 + +$ dollar +argentinapeso $ +australiadollar .66 $ +austriaschilling .83 $ +bahraindinar 2.6522 $ +belgiumfranc .028 $ +brazilcruzeiro .000019 $ +britainpound 1.49 $ +canadadollar .77 $ +czechkoruna .034 $ +chilepeso .0025 $ +chinarenminbi .174856 $ +colombiapeso .001495 $ +denmarkkrone .15 $ +ecuadorsucre .000539 $ +finlandmarkka .17 $ +francefranc .17 $ +germanymark .58 $ +greatbritainpound britainpound +greecedrachma .0043 $ +hongkongdollar .13 $ +hungaryforint .011 $ +indiarupee .03211 $ +indonesiarupiah .0004782 $ +irelandpunt 1.43 $ +israelshekel .3642 $ +italylira .00064 $ +japanyen .0093 $ +jordandinar 1.4682 $ +kuwaitdinar 3.3173 $ +lebanonpound .000578 $ +malaysiaringgit .338 $ +maltalira 2.6042 $ +mexicopeso .3205128 $ +netherlandsguilder .52 $ +newzealanddollar .539 $ +norwaykrone .139 $ +pakistanrupee .037 $ +perunewsol .5065 $ +philippinespeso .03738 $ +polandzloty .000059 $ +portugalescudo .00617 $ +saudiarabiariyal .26702 $ +singaporedollar .6157 $ +slovakkoruna .034 $ +southafricarand .21 $ +southkoreawon .001 $ +spainpeseta .007 $ +swedenkrona .13 $ +switzerlandfranc .66 $ +taiwandollar .038285 $ +thailandbaht .03962 $ +turkeylira .0000929 $ +unitedarabdirham .2723 $ +uruguaynewpeso .246852 $ +venezuelabolivar .011 $ + +mark germanymark +bolivar venezuelabolivar +peseta spainpeseta +rand southafricarand +escudo portugalescudo +sol perunewsol +guilder netherlandsguilder +hollandguilder netherlandsguilder +peso mexicopeso +yen japanyen +lira italylira +rupee indiarupee +drachma greecedrachma +franc francefranc +markka finlandmarkka +sucre ecuadorsucre +poundsterling britainpound +cruzeiro brazilcruzeiro + +/ computer + +baud bit/sec +byte 8 bit +block 512 byte +kbyte 1024 byte +megabyte 1024 kbyte +gigabyte 1024 megabyte +meg megabyte + + +/ Trivia + +% 1|100 +admiraltyknot 6080 ft/hr +apostilb cd/pi-m2 +are 1+2 m2 +arpentcan 27.52 mi +arpentlin 191.835 ft +astronomicalunit au +atmosphere 1.01325+5 nt/m2 +atm atmosphere +atomicmassunit 1.66044-27 kg fuzz +amu atomicmassunit +bag 94 lb +bakersdozen 13 +bar 1+5 nt/m2 +barie 1-1 nt/m2 +barleycorn 1|3 in +barn 1-28 m2 +barrel 42 gal +barye 1-1 nt/m2 +bev 1+9 e-volt +biot 10 amp +blondel cd/pi-m2 +boardfoot 144 in3 +bolt 40 yd +bottommeasure 1|40 in +britishthermalunit 1.05506+3 joule fuzz +btu britishthermalunit +refrigeration 12000 btu/ton-hour +buck dollar +cable 720 ft +caliber 1-2 in +calorie cal +carat 205 mg +caratgold 1|24 +cent centidollar +cental 100 lb +centesimalminute 1-2 grade +centesimalsecond 1-4 grade +century 100 year +cfs ft3/sec +chain 66 ft +circularinch 1|4 pi-in2 +circularmil 1-6|4 pi-in2 +clusec 1-8 mm-hg m3/s +coomb 4 bu +cord 128 ft3 +cordfoot cord +crith 9.06-2 gm +cubit 18 in +cup 1|2 pt +curie 3.7+10 /sec +dalton amu +decade 10 yr +dipotre /m +displacementton 35 ft3 +doppelzentner 100 kg +dozen 12 +drop .03 cm3 +dyne cm-gm/sec2 +electronvolt e-volt +ell 45 in +engineerschain 100 ft +engineerslink 100|100 ft +equivalentfootcandle lumen/pi-ft2 +equivalentlux lumen/pi-m2 +equivalentphot cd/pi-cm2 +erg cm2-gm/sec2 +ev e-volt +/ faraday 9.652000+04 coul +/ faraday from host: physics.nist.gov +/ path: /PhysRefData/fundconst/html/keywords.html +faraday 9.6485309+4 coul +/faraday 96485.33289 coul +fathom 6 ft +fermi 1-15 m +fifth 4|5 qt +fin 5 dollar +finger 7|8 in +firkin 9 gal +footcandle lumen/ft2 +footlambert cd/pi-ft2 +fortnight 14 da +franklin 3.33564-10 coul +frigorie kilocal +furlong 220 yd +galileo 1-2 m/sec2 +gamma 1-9 weber/m2 +gauss 1-4 weber/m2 +geodeticfoot british-ft +geographicalmile 1852 m +gilbert 7.95775-1 amp +gill 1|4 pt +gross 144 +gunterschain 22 yd +hand 4 in +hectare 1+4 m2 +hefnercandle .92 cd +hertz /sec +Hz hertz +hogshead 2 barrel +hd hogshead +homestead 1|4 mi2 +horsepower 550 ft-lb-g/sec +hp horsepower +hyl gm force sec2/m +hz /sec +imaginarycubicfoot 1.4 ft3 +jeroboam 4|5 gal +boltzmann 1.38064852-23 joule/K +k boltzmann +karat 1|24 +kcal kilocal +kcalorie kilocal +kev 1+3 e-volt +key kg +khz 1+3 /sec +kilderkin 18 gal +knot nmile/hr +lambert cd/pi-cm2 +langley cal/cm2 +last 80 bu +league 3 mi +lightyear c-yr +line 1|12 in +link 66|100 ft +longhundredweight 112 lb +longquarter 28 lb +lusec 1-6 mm-hg m3/s +mach 331.46 m/sec +magnum 2 qt +marineleague 3 nmile +maxwell 1-8 weber +metriccarat 200 mg +mgd megagal/day +mh millihenry +mhz 1+6 /sec +mil 1-3 in +millenium 1000 year +minersinch 1.5 ft3/min +minim 1|60 fldr +mo month +mpg mile/gal +mph mile/hr +nail 1|16 yd +nauticalmile nmile +nit cd/m2 +noggin 1|8 qt +nox 1-3 lux +ns nanosec +oersted 2.5+2 pi-amp/m +oe oersted +pace 36 in +palm 3 in +parasang 3.5 mi +parsec au-radian/arcsec +pascal nt/m2 +pc parsec +pennyweight 1|20 oz +pwt pennyweight +percent % +perch rd +pf picofarad +phot lumen/cm2 +pica 1|6 in +pieze 1+3 nt/m2 +pipe 4 barrel +point 1|72 in +poise gm/cm-sec +pole rd +poundal ft-lb/sec2 +pdl poundal +proof 1|200 +psi lb-g/in2 +quarter 9 in +quartersection 1|4 mi2 +quintal 100 kg +quire 25 +gasconstant 8.3144598 joule/K +R gasconstant +rad 100 erg/gm +ream 500 +registerton 100 ft3 +rehoboam 156 floz +rhe 10 m2/nt-sec +rontgen 2.58-4 curie/kg +rood 1.21+3 yd +rope 20 ft +rutherford 1+6 /sec +rydberg 1.36054+1 ev +sabin 1 ft2 +sack 3 bu +seam 8 bu +section mi2 +shippington 40 ft3 +shorthundredweight 100 lb +shortquarter 25 lb +siemens /ohm +sigma microsec +skein 120 yd +skot 1-3 apostilb +slug lb-g-sec2/ft +span 9 in +spat 4 pi sr +spindle 14400 yd +square 100 ft2 +stere m3 +sthene 1+3 nt +stilb cd/cm2 +stoke 1-4 m2/sec +stone 14 lb +strike 2 bu +surveyfoot british-ft +surveyyard 3 surveyfoot +surveyorschain 66 ft +surveyorslink 66|100 ft +tablespoon 4 fldr +teaspoon 4|3 fldr +tesla weber/m2 +therm 1+5 btu +thermie 1+6 cal +timberfoot ft3 +tnt 4.6+6 m2/sec2 +tonne 1+6 gm +torr mm hg +township 36 mi2 +tun 8 barrel +water gram g / cc +wey 40 bu +weymass 252 lb +Xunit 1.00202-13m +degC K + +kelvin K +brewster 1-12 m2/newton +degF 5|9 degC +degreesrankine degF +degrankine degreesrankine +degreerankine degF +degreaumur 10|8 degC +drachm 60 grain +poncelet 100 kg m g / sec +denier .05|450 gram / m +tex .001 gram / m +englishell 45 inch +scottishell 37.2 inch +flemishell 27 inch +planck 6.626-34 joule-sec +/planck 6.626070040-34 joule-sec +hbar 1.055-34 joule-sec +/hbar 1.054571800-34 joule-sec +electronmass 9.1095-31 kg +protonmass 1.6726-27 kg +neutronmass 1.6606-27 kg +V volt +eV e V +bohrradius 1|8.988e9 hbar2-C2 / N m2-e2-electronmass +becquerel 1|3.7+10 curie +fresnel 1+12 hertz +statcoul 1|2.99792458+9 coul +statamp 1|2.99792458+9 amp +statvolt 2.99792458+2 volt +statcoulomb statcoul +statampere statamp +debye 3.336-30 coul-m +pulsatance 2 pi/sec +rpm rev/minute +rps rev/sec +kilohm kiloohm +megohm megaohm +siderealyear 365.256360417 day +siderealday 23.934469444 hour +siderealhour 1|24 siderealday +lunarmonth 29.5305555 day +synodicmonth lunarmonth +siderealmonth 27.32152777 day +tropicalyear year +solaryear year +lunaryear 12 lunarmonth +cran 37.5 brgallon +kip 1000 lbf +frenchfoot 16|15 ft +frenchfeet frenchfoot +toise 6 frenchfeet +sievert 8.4 rontgen +candle 1.02 candela +militarypace 2.5 feet +metre meter +litre liter +gramme gram +iudiptheria 62.8 microgram +iupenicillin .6 microgram +iuinsulin 41.67 microgram +cottonyarncount 2520 ft/pound +linenyarncount 900 ft/pound +worstedyarncount 1680 ft/pound +metricyarncount meter/gram +jewlerspoint 2 milligram diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index fb45b46c70..dbec1b6606 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -12,10 +12,10 @@ #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" +#include "config/config.h" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" -#include "version/version.h" #include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" diff --git a/src/nmodl/version/version.cpp.in b/src/nmodl/config/config.cpp.in similarity index 51% rename from src/nmodl/version/version.cpp.in rename to src/nmodl/config/config.cpp.in index 284044e995..ec996c745a 100644 --- a/src/nmodl/version/version.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -5,7 +5,20 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "version/version.h" +#include "config/config.h" +/// Git version of the project const std::string nmodl::version::GIT_REVISION = "@GIT_REVISION@"; + +/// NMODL version const std::string nmodl::version::NMODL_VERSION = "@PROJECT_VERSION@"; + +/** + * \brief Path of nrnutils.lib file + * + * nrnunits.lib need to be loaded at runtime. Before project is + * installed it needs to be read from PROJECT_SOURCE_DIR and later + * from CMAKE_INSTALL_PREFIX. + */ +const std::vector<std::string> nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = + {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h new file mode 100644 index 0000000000..25bd1bb9bf --- /dev/null +++ b/src/nmodl/config/config.h @@ -0,0 +1,51 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include <fstream> +#include <string> +#include <vector> + +namespace nmodl { + +/** + * \brief Project version information + */ +struct version { + /// git revision id + static const std::string GIT_REVISION; + + /// project tagged version in the cmake + static const std::string NMODL_VERSION; + + /// return version string (version + git id) as a string + static std::string to_string() { + return NMODL_VERSION + " " + GIT_REVISION; + } +}; + +/** + * \brief Information about unit specification with nrnunits.lib + */ +struct NrnUnitsLib { + /// paths where nrnunits.lib can be found + static const std::vector<std::string> NRNUNITSLIB_PATH; + + /// from possible paths, return one that exists + static std::string get_path() { + for (const auto& path: NRNUNITSLIB_PATH) { + std::ifstream f(path.c_str()); + if (f.good()) { + return path; + } + } + throw std::runtime_error("Could not found nrnunits.lib"); + } +}; + +} // namespace nmodl diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 61c580d940..abbbde4871 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -9,10 +9,15 @@ set(BISON_GENERATED_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp) + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp) set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) +set(UNIT_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/units/units.hpp + ${PROJECT_SOURCE_DIR}/src/units/units.cpp) + set(NMODL_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) @@ -28,6 +33,9 @@ set(C_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) +set(UNIT_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp + ${PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) + set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modl.h ${CMAKE_CURRENT_SOURCE_DIR}/token_mapping.hpp @@ -44,15 +52,20 @@ set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp + ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp ${NMODL_DRIVER_FILES} ${DIFFEQ_DRIVER_FILES} - ${C_DRIVER_FILES}) + ${C_DRIVER_FILES} + ${UNIT_DRIVER_FILES}) # ============================================================================= # Directories for parsers (as they need to be in separate directories) # ============================================================================= -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR}/src/parser/diffeq - ${PROJECT_BINARY_DIR}/src/parser/c) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl + ${PROJECT_BINARY_DIR}/src/parser/diffeq + ${PROJECT_BINARY_DIR}/src/parser/c + ${PROJECT_BINARY_DIR}/src/parser/unit) # ============================================================================= # Lexer & Parser commands @@ -113,6 +126,20 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") +# command to generate Units parser +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${PROJECT_SOURCE_DIR}/src/parser/unit.yy + DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/unit.yy + COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") + # command to generate nmodl lexer add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp @@ -146,6 +173,13 @@ add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") +# command to generate Units lexer +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") # ============================================================================= # Libraries & executables # ============================================================================= @@ -154,20 +188,24 @@ add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES}) + ${AST_SOURCE_FILES} + ${UNIT_SOURCE_FILES}) + set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_library(lexer STATIC $<TARGET_OBJECTS:lexer_obj>) add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) +add_executable(units_lexer main_units.cpp) target_link_libraries(nmodl_lexer lexer util) target_link_libraries(c_lexer lexer util) - +target_link_libraries(units_lexer lexer util) # ============================================================================= # Install executable # ============================================================================= install(TARGETS nmodl_lexer DESTINATION bin/lexer) install(TARGETS c_lexer DESTINATION bin/lexer) +install(TARGETS units_lexer DESTINATION bin/lexer) add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 23975a8ce7..40690ebad9 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -11,10 +11,10 @@ #include "CLI/CLI.hpp" #include "fmt/format.h" +#include "config/config.h" #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" -#include "version/version.h" /** * \file diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 7cb009f1c8..cc2f88ead4 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -12,10 +12,10 @@ #include "fmt/format.h" #include "ast/ast.hpp" +#include "config/config.h" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" -#include "version/version.h" /** * \file diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp new file mode 100644 index 0000000000..08f8dff1b5 --- /dev/null +++ b/src/nmodl/lexer/main_units.cpp @@ -0,0 +1,54 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <fstream> + +#include "CLI/CLI.hpp" +#include "fmt/format.h" + +#include "config/config.h" +#include "lexer/unit_lexer.hpp" +#include "parser/unit_driver.hpp" +#include "utils/logger.hpp" + +/** + * \file + * Example of standalone lexer program for Units that + * demonstrate use of UnitLexer and UnitDriver classes. + */ + +using namespace fmt::literals; +using namespace nmodl; + +int main(int argc, const char* argv[]) { + CLI::App app{"Unit-Lexer : Standalone Lexer for Units({})"_format(version::to_string())}; + + std::vector<std::string> files; + app.add_option("file", files, "One or more units files to process") + ->required() + ->check(CLI::ExistingFile); + + CLI11_PARSE(app, argc, argv); + + for (const auto& f: files) { + nmodl::logger->info("Processing {}", f); + std::ifstream file(f); + nmodl::parser::UnitDriver driver; + nmodl::parser::UnitLexer scanner(driver, &file); + + /// parse Units file and print token until EOF + while (true) { + auto sym = scanner.next_token(); + auto token = sym.token(); + if (token == nmodl::parser::UnitParser::token::END) { + break; + } + std::cout << sym.value.as<std::string>() << std::endl; + } + } + return 0; +} diff --git a/src/nmodl/lexer/unit.ll b/src/nmodl/lexer/unit.ll new file mode 100755 index 0000000000..a18a4a6938 --- /dev/null +++ b/src/nmodl/lexer/unit.ll @@ -0,0 +1,163 @@ +/********************************************************************************** + * + * @brief Flex lexer implementation reading Units of Neuron like MOD2C + * + * @brief Parsing unit definition file for use in NMODL + * https://github.com/BlueBrain/mod2c/blob/master/share/nrnunits.lib + *****************************************************************************/ + +%{ + #include <iostream> + + #include "lexer/unit_lexer.hpp" + #include "parser/unit_driver.hpp" + #include "parser/unit/unit_parser.hpp" + #include "utils/string_utils.hpp" + + /** + * YY_USER_ACTION is called before each of token actions and + * we update columns by length of the token. Node that position + * starts at column 1 and increasing by yyleng gives extra position + * larger than exact columns span of the token. Hence in ModToken + * class we reduce the length by one column. + */ + #define YY_USER_ACTION { loc.step(); loc.columns(yyleng); } + + /** + * By default yylex returns int, we use token_type. Unfortunately + * yyterminate by default returns 0, which is not of token_type. + */ + #define yyterminate() return UnitParser::make_END(loc); + + /** + * Disables inclusion of unistd.h, which is not available under Visual + * C++ on Win32. The C++ scanner uses STL streams instead. + */ + #define YY_NO_UNISTD_H + +%} + +D [0-9] +E [Ee]*[-+]?{D}+ +DBL ([-+]?{D})|([-+]?{D}+"."{D}*({E})?)|([-+]?{D}*"."{D}+({E})?)|([-+]?{D}+{E}) + +/** we do use yymore feature in copy modes */ +%option yymore + +/** name of the lexer header file */ +%option header-file="unit_base_lexer.hpp" + +/** name of the lexer implementation file */ +%option outfile="unit_base_lexer.cpp" + +/** change the name of the scanner class (to "UnitFlexLexer") */ +%option prefix="Unit" + +/** enable C++ scanner which is also reentrant */ +%option c++ + +/** no plan for include files for now */ +%option noyywrap + +/** need to unput characters back to buffer for custom routines */ +%option unput + +/** need to put in buffer for custom routines */ +%option input + +/** not an interactive lexer, takes a file instead */ +%option batch + +/** enable debug mode (disable for production) */ +%option debug + +/** + * instructs flex to generate an 8-bit scanner, i.e., + * one which can recognize 8-bit characters. + */ +%option 8bit + +/** show warning messages */ +%option warn + +/* to insure there are no holes in scanner rules */ +%option nodefault + +/* keep line information */ +%option yylineno + +/* enable use of start condition stacks */ +%option stack + +/* mode for preprocessor directive */ +%x P_P_DIRECTIVE + +/* mode for multi-line comment */ +%x COMMENT + + +%% + +"*"[a-j]"*" { + return UnitParser::make_BASE_UNIT(yytext, loc); + } + +"*"[k-zA-Z]"*" { + return UnitParser::make_INVALID_BASE_UNIT(yytext, loc); + } + +^[a-zA-Z$\%]+{D}* { + return UnitParser::make_NEW_UNIT(yytext, loc); + } + +[a-zA-Z$\%]+ { + return UnitParser::make_UNIT(yytext, loc); + } + +^[a-zA-Z]+"-" { + return UnitParser::make_PREFIX(yytext, loc); + } + +[a-zA-Z]+[2-9]+ { + return UnitParser::make_UNIT_POWER(yytext, loc); + } + +{DBL} { + return UnitParser::make_DOUBLE(yytext, loc); + } + +{DBL}"|"{DBL} { + return UnitParser::make_FRACTION(yytext, loc); + } + +"-" { + return UnitParser::make_UNIT_PROD(yytext, loc); + } + +^"/".* { + return UnitParser::make_COMMENT(yytext, loc); + } + +"/"|"1/" { + return UnitParser::make_DIVISION(yytext, loc); + } + +[ \t] { + // do nothing + } + +\n { + return UnitParser::make_NEWLINE(yytext, loc); + } + +. { + return UnitParser::make_INVALID_TOKEN(loc); + } + +%% + + +int UnitFlexLexer::yylex() { + throw std::runtime_error("next_token() should be used instead of yylex()"); +} + diff --git a/src/nmodl/lexer/unit_lexer.hpp b/src/nmodl/lexer/unit_lexer.hpp new file mode 100644 index 0000000000..47f5930e6c --- /dev/null +++ b/src/nmodl/lexer/unit_lexer.hpp @@ -0,0 +1,88 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "parser/unit/unit_parser.hpp" + +/** + * Flex expects the declaration of yylex to be defined in the macro YY_DECL + * and Unit parser class expects it to be declared. + */ +#ifndef YY_DECL +#define YY_DECL nmodl::parser::UnitParser::symbol_type nmodl::parser::UnitLexer::next_token() +#endif + +/** + * For creating multiple (different) lexer classes, we can use `-P` flag + * (or prefix option) to rename each yyFlexLexer to some other name like + * `xxFlexLexer`. And then include <FlexLexer.h> in other sources once per + * lexer class, first renaming `yyFlexLexer` as shown below. + */ +#ifndef __FLEX_LEXER_H +#define yyFlexLexer UnitFlexLexer +#include "FlexLexer.h" +#endif + +namespace nmodl { +namespace parser { + +/** + * \class UnitLexer + * \brief Represent Lexer/Scanner class for Units parsing + * + * Lexer defined to add some extra function to the scanner class from flex. + * Flex itself creates yyFlexLexer class, which we renamed using macros to + * UnitFlexLexer. But we change the context of the generated yylex() function + * because the yylex() defined in UnitFlexLexer has no parameters. + */ +class UnitLexer: public UnitFlexLexer { + /** + * \brief Reference to driver object where this lexer resides + * + * Currently driver object is not used within lexer but could be + * used for accessing previous units if needed. + */ + UnitDriver& driver; + + public: + /// location of the parsed token + location loc; + + /// \name Ctor & dtor + /// \{ + + /** + * \brief UnitLexer constructor + * + * @param driver UnitDriver where this lexer resides + * @param in Input stream from where tokens will be read + * @param out Output stream where output will be sent + */ + explicit UnitLexer(UnitDriver& driver, std::istream* in = nullptr, std::ostream* out = nullptr) + : UnitFlexLexer(in, out) + , driver(driver) {} + + ~UnitLexer() override = default; + + /// \} + + /** + * \brief Function for lexer to scan token (replacement for \c yylex()) + * + * This is main lexing function generated by `flex` according to the macro + * declaration \c YY_DECL. The generated bison parser then calls this virtual + * function to fetch new tokens. Note that \c yylex() has different declaration + * and hence can't be used for new lexer. + * + * @return Symbol encapsulating parsed token + */ + virtual UnitParser::symbol_type next_token(); +}; + +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index ff0779bf48..baa0a66b12 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -19,10 +19,10 @@ #include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_omp_visitor.hpp" +#include "config/config.h" #include "parser/nmodl_driver.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" -#include "version/version.h" #include "visitors/ast_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index c5d0dacf75..6734e8e9a5 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -6,12 +6,16 @@ add_executable(nmodl_parser main_nmodl.cpp) add_executable(c_parser main_c.cpp) +add_executable(units_parser main_units.cpp) target_link_libraries(nmodl_parser lexer util) target_link_libraries(c_parser lexer util) +target_link_libraries(units_parser lexer util) # ============================================================================= # Install executable # ============================================================================= install(TARGETS nmodl_parser DESTINATION bin/parser) install(TARGETS c_parser DESTINATION bin/parser) +install(TARGETS units_parser DESTINATION bin/parser) +install(FILES ../../share/nrnunits.lib DESTINATION share) diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index a4282539ef..aae6df9923 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -10,11 +10,12 @@ #include "CLI/CLI.hpp" #include "fmt/format.h" +#include "config/config.h" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" -#include "version/version.h" /** + * \file * Standalone parser program for C. This demonstrate basic * usage of parser and driver class. */ diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index abbc1c44c6..a67ebc3b7c 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -9,9 +9,9 @@ #include "CLI/CLI.hpp" #include "fmt/format.h" +#include "config/config.h" #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" -#include "version/version.h" /** * Standalone parser program for NMODL. This demonstrate @@ -46,4 +46,4 @@ int main(int argc, const char* argv[]) { } return 0; -} \ No newline at end of file +} diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp new file mode 100644 index 0000000000..d36a4a3a21 --- /dev/null +++ b/src/nmodl/parser/main_units.cpp @@ -0,0 +1,54 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <fstream> + +#include "CLI/CLI.hpp" +#include "fmt/format.h" + +#include "config/config.h" +#include "parser/unit_driver.hpp" +#include "utils/logger.hpp" + +/** + * Standalone parser program for Units. This demonstrate basic + * usage of parser and driver class. + * + * \todo This is a placeholder and needs to be changed to parse + * NMODL file and then show corresponding units. + */ + +using namespace fmt::literals; +using namespace nmodl; + +void parse_units(std::vector<std::string> files) { + for (const auto& f: files) { + logger->info("Processing {}", f); + std::ifstream file(f); + + /// driver object creates lexer and parser + parser::UnitDriver driver; + driver.set_verbose(true); + + /// just call parser method + driver.parse_stream(file); + } +} + +int main(int argc, const char* argv[]) { + CLI::App app{"Unit-Parser : Standalone Parser for Units({})"_format(version::to_string())}; + + std::vector<std::string> files; + files.push_back(NrnUnitsLib::get_path()); + app.add_option("file", files, "One or more Units files to process"); + + CLI11_PARSE(app, argc, argv); + + parse_units(files); + + return 0; +} diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 8849a6f803..03d8fdc344 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -70,7 +70,7 @@ /** Initializations before parsing : Use filename from driver to initialize location object */ %initial-action { - @$.begin.filename = @$.end.filename = &driver.streamname; + @$.begin.filename = @$.end.filename = &driver.stream_name; }; /** Tokens for lexer : we return ModToken object from lexer. This is useful when diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 5dad57af03..f6d0585704 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -34,7 +34,7 @@ bool NmodlDriver::parse_stream(std::istream& in) { //// parse nmodl file bool NmodlDriver::parse_file(const std::string& filename) { std::ifstream in(filename.c_str()); - streamname = filename; + stream_name = filename; if (!in.good()) { return false; diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index ee890c0669..bce44a2ddf 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -69,7 +69,7 @@ class NmodlDriver { public: /// file or input stream name (used by scanner for position), see todo - std::string streamname; + std::string stream_name; /// root of the ast std::shared_ptr<ast::Program> astRoot = nullptr; diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy new file mode 100644 index 0000000000..b549a98153 --- /dev/null +++ b/src/nmodl/parser/unit.yy @@ -0,0 +1,217 @@ +/********************************************************************************** + * + * @brief Bison grammar and parser implementation for Units of Neuron like MOD2C + * + * Parsing Unit definition file for use in NMODL. + * + * The parser is designed to parse the nrnunits.lib file which is also used by MOD2C + * https://github.com/BlueBrain/mod2c/blob/master/share/nrnunits.lib + *****************************************************************************/ + +%code requires +{ + #include <string> + #include <sstream> + + #include "parser/unit_driver.hpp" + #include "units/units.hpp" +} + +/** use C++ parser interface of bison */ +%skeleton "lalr1.cc" + +/** require modern bison version */ +%require "3.0.2" + +/** print verbose error messages instead of just message 'syntax error' */ +%define parse.error verbose + +/** enable tracing parser for debugging */ +%define parse.trace + +/** add extra arguments to yyparse() and yylex() methods */ +%parse-param {class UnitLexer& scanner} +%parse-param {class UnitDriver& driver} +%lex-param {nmodl::UnitScanner &scanner} +%lex-param {nmodl::UnitDriver &driver} + +/** use variant based implementation of semantic values */ +%define api.value.type variant + +/** assert correct cleanup of semantic value objects */ +%define parse.assert + +/** handle symbol to be handled as a whole (type, value, and possibly location) in scanner */ +%define api.token.constructor + +/** specify the namespace for the parser class */ +%define api.namespace {nmodl::parser} + +/** set the parser's class identifier */ +%define parser_class_name {UnitParser} + +/** keep track of the current position within the input */ +%locations + +%token END 0 "end of file" +%token INVALID_TOKEN + +%token <std::string> BASE_UNIT +%token <std::string> INVALID_BASE_UNIT +%token <std::string> UNIT +%token <std::string> NEW_UNIT +%token <std::string> UNIT_POWER +%token <std::string> PREFIX +%token <std::string> DOUBLE +%token <std::string> FRACTION +%token <std::string> COMMENT +%token <std::string> NEWLINE +%token <std::string> UNIT_PROD +%token <std::string> DIVISION + +%type <std::shared_ptr<std::vector<std::string>>> units_nom +%type <std::shared_ptr<std::vector<std::string>>> units_denom +%type <std::shared_ptr<nmodl::units::Unit>> nominator +%type <std::shared_ptr<nmodl::units::Unit>> item +%type <std::shared_ptr<nmodl::units::Prefix>> prefix +%type <std::shared_ptr<nmodl::units::UnitTable>> table_insertion + +%{ + #include "lexer/unit_lexer.hpp" + #include "parser/unit_driver.hpp" + + using nmodl::parser::UnitParser; + using nmodl::parser::UnitLexer; + using nmodl::parser::UnitDriver; + + /// yylex takes scanner as well as driver reference + /// \todo: check if driver argument is required + static UnitParser::symbol_type yylex(UnitLexer &scanner, UnitDriver &/*driver*/) { + return scanner.next_token(); + } +%} + + +%start unit_table + +%% + +unit_table + : END + | table_insertion END + +table_insertion + : { + $$ = driver.table; + } + | table_insertion item { + $1->insert($2); + $$ = $1; + } + | table_insertion prefix { + $1->insert_prefix($2); + $$ = $1; + } + | table_insertion no_insert { + $$ = $1; + } + ; + +units_nom + : { + $$ = std::make_shared<std::vector<std::string>>(); + } + | UNIT units_nom { + $2->push_back($1); + $$ = $2; + } + | UNIT_POWER units_nom { + $2->push_back($1); + $$ = $2; + } + | UNIT_PROD units_nom { + $$ = $2; + } + ; + +units_denom + : { + $$ = std::make_shared<std::vector<std::string>>(); + } + | UNIT units_denom { + $2->push_back($1); + $$ = $2; + } + | UNIT_POWER units_denom { + $2->push_back($1); + $$ = $2; + } + | UNIT_PROD units_denom { + $$ = $2; + } + ; + +nominator + : units_nom { + auto newunit = std::make_shared<nmodl::units::Unit>(); + newunit->add_nominator_unit($1); + $$ = newunit; + } + | DOUBLE units_nom { + auto newunit = std::make_shared<nmodl::units::Unit>(); + newunit->add_nominator_unit($2); + newunit->add_nominator_double($1); + $$ = newunit; + } + | FRACTION units_nom { + auto newunit = std::make_shared<nmodl::units::Unit>(); + newunit->add_nominator_unit($2); + newunit->add_fraction($1); + $$ = newunit; + } + ; + +prefix + : PREFIX DOUBLE { + $$ = std::make_shared<nmodl::units::Prefix>($1,$2); + } + | PREFIX UNIT { + $$ = std::make_shared<nmodl::units::Prefix>($1,$2); + } + +no_insert + : COMMENT + | NEWLINE + | INVALID_TOKEN { + error(scanner.loc, "item"); + } +item + : NEW_UNIT BASE_UNIT { + auto newunit = std::make_shared<nmodl::units::Unit>($1); + newunit->add_base_unit($2); + $$ = newunit; + } + | NEW_UNIT nominator { + $2->add_unit($1); + $$ = $2; + } + | NEW_UNIT nominator DIVISION units_denom { + $2->add_unit($1); + $2->add_denominator_unit($4); + $$ = $2; + } + | NEW_UNIT INVALID_BASE_UNIT { + error(scanner.loc, "Base units should be named by characters a-j"); + } + ; + +%% + + +/** Bison expects error handler for parser */ + +void UnitParser::error(const location &loc , const std::string &message) { + std::stringstream ss; + ss << "Unit Parser Error : " << message << " [Location : " << loc << "]"; + throw std::runtime_error(ss.str()); +} diff --git a/src/nmodl/parser/unit_driver.cpp b/src/nmodl/parser/unit_driver.cpp new file mode 100644 index 0000000000..fc4849ab75 --- /dev/null +++ b/src/nmodl/parser/unit_driver.cpp @@ -0,0 +1,75 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <fstream> +#include <sstream> + +#include "lexer/unit_lexer.hpp" +#include "parser/unit_driver.hpp" + +namespace nmodl { +namespace parser { + +UnitDriver::UnitDriver(bool strace, bool ptrace) + : trace_scanner(strace) + , trace_parser(ptrace) {} + +/// parse Units file provided as istream +bool UnitDriver::parse_stream(std::istream& in) { + UnitLexer scanner(*this, &in); + UnitParser parser(scanner, *this); + + this->lexer = &scanner; + this->parser = &parser; + + scanner.set_debug(trace_scanner); + parser.set_debug_level(trace_parser); + return (parser.parse() == 0); +} + +/// parse Units file +bool UnitDriver::parse_file(const std::string& filename) { + std::ifstream in(filename.c_str()); + stream_name = filename; + + if (!in.good()) { + return false; + } + return parse_stream(in); +} + +/// parser Units provided as string (used for testing) +bool UnitDriver::parse_string(const std::string& input) { + std::istringstream iss(input); + return parse_stream(iss); +} + +void UnitDriver::error(const std::string& m) { + std::cerr << m << std::endl; +} + +void UnitDriver::error(const std::string& m, const location& l) { + std::cerr << l << " : " << m << std::endl; +} + +void UnitDriver::scan_string(std::string& text) { + std::istringstream in(text); + UnitLexer scanner(*this, &in); + UnitParser parser(scanner, *this); + this->lexer = &scanner; + this->parser = &parser; + while (true) { + auto sym = lexer->next_token(); + auto token = sym.token(); + if (token == UnitParser::token::END) { + break; + } + } +} + +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/parser/unit_driver.hpp b/src/nmodl/parser/unit_driver.hpp new file mode 100644 index 0000000000..1a451f1454 --- /dev/null +++ b/src/nmodl/parser/unit_driver.hpp @@ -0,0 +1,77 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "units/units.hpp" +#include <algorithm> +#include <map> +#include <memory> +#include <string> +#include <vector> + +namespace nmodl { +namespace parser { + +/// flex generated scanner class (extends base lexer class of flex) +class UnitLexer; + +/// parser class generated by bison +class UnitParser; + +/// represent location of the token +class location; + +/** + * \class UnitDriver + * \brief Class that binds all pieces together for parsing C units + */ +class UnitDriver { + private: + /// enable debug output in the flex scanner + bool trace_scanner = false; + + /// enable debug output in the bison parser + bool trace_parser = false; + + /// pointer to the lexer instance being used + UnitLexer* lexer = nullptr; + + /// pointer to the parser instance being used + UnitParser* parser = nullptr; + + /// print messages from lexer/parser + bool verbose = false; + + public: + /// shared pointer to the UnitTable that stores all the unit definitions + std::shared_ptr<nmodl::units::UnitTable> table = std::make_shared<nmodl::units::UnitTable>(); + + /// file or input stream name (used by scanner for position), see todo + std::string stream_name; + + UnitDriver() = default; + UnitDriver(bool strace, bool ptrace); + + void error(const std::string& m); + bool parse_stream(std::istream& in); + bool parse_string(const std::string& input); + bool parse_file(const std::string& filename); + void scan_string(std::string& text); + void error(const std::string& m, const location& l); + + void set_verbose(bool b) { + verbose = b; + } + + bool is_verbose() const { + return verbose; + } +}; + +} // namespace parser +} // namespace nmodl diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 0c13b5ad85..13ed109e9f 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -12,9 +12,9 @@ #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include "config/config.h" #include "parser/nmodl_driver.hpp" #include "pybind/pybind_utils.hpp" -#include "version/version.h" #include "visitors/visitor_utils.hpp" namespace py = pybind11; diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp new file mode 100644 index 0000000000..448fa87ae6 --- /dev/null +++ b/src/nmodl/units/units.cpp @@ -0,0 +1,356 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + + +#include <algorithm> +#include <array> +#include <fstream> +#include <iostream> +#include <map> +#include <regex> +#include <sstream> +#include <string> +#include <vector> + +#include "units.hpp" + +namespace nmodl { +namespace units { + +Prefix::Prefix(std::string name, std::string factor) { + if (name.back() == '-') { + name.pop_back(); + } + prefix_name = name; + prefix_factor = std::stod(factor); +} + +void Unit::add_unit(std::string name) { + unit_name = name; +} + +void Unit::add_base_unit(std::string name) { + // name = "*[a-j]*" which is a base unit + const auto dim_name = name[1]; + const int dim_no = dim_name - 'a'; + unit_dimensions[dim_no] = 1; + add_nominator_unit(name); +} + +void Unit::add_nominator_double(std::string double_string) { + unit_factor = parse_double(double_string); +} + +void Unit::add_nominator_dims(std::array<int, MAX_DIMS> dimensions) { + std::transform(unit_dimensions.begin(), unit_dimensions.end(), dimensions.begin(), + unit_dimensions.begin(), std::plus<int>()); +} + +void Unit::add_denominator_dims(std::array<int, MAX_DIMS> dimensions) { + std::transform(unit_dimensions.begin(), unit_dimensions.end(), dimensions.begin(), + unit_dimensions.begin(), std::minus<int>()); +} + +void Unit::add_nominator_unit(std::string nom) { + nominator.push_back(nom); +} + +void Unit::add_nominator_unit(const std::shared_ptr<std::vector<std::string>> nom) { + nominator.insert(nominator.end(), nom->begin(), nom->end()); +} + +void Unit::add_denominator_unit(std::string denom) { + denominator.push_back(denom); +} + +void Unit::add_denominator_unit(const std::shared_ptr<std::vector<std::string>> denom) { + denominator.insert(denominator.end(), denom->begin(), denom->end()); +} + +void Unit::mul_factor(const double double_factor) { + unit_factor *= double_factor; +} + +void Unit::add_fraction(const std::string& fraction_string) { + double nom, denom; + std::string nominator; + std::string denominator; + std::string::const_iterator it; + for (it = fraction_string.begin(); it != fraction_string.end() && *it != '|'; ++it) { + nominator.push_back(*it); + } + // pass "|" char + ++it; + for (auto itm = it; itm != fraction_string.end(); ++itm) { + denominator.push_back(*itm); + } + nom = parse_double(nominator); + denom = parse_double(denominator); + unit_factor = nom / denom; +} + +double Unit::parse_double(std::string double_string) { + double d_number, d_magnitude; + std::string s_number; + std::string s_magnitude; + std::string::const_iterator it; + // if double is positive sign = 1 else sign = -1 + // to be able to be multiplied by the d_number + int sign = 1; + if (double_string.front() == '-') { + sign = -1; + double_string.erase(double_string.begin()); + } + // if *it reached an exponent related char, then the whole double number is read + for (it = double_string.begin(); + it != double_string.end() && *it != 'e' && *it != '+' && *it != '-'; ++it) { + s_number.push_back(*it); + } + // then read the magnitude of the double number + for (auto itm = it; itm != double_string.end(); ++itm) { + if (*itm != 'e') { + s_magnitude.push_back(*itm); + } + } + d_number = std::stod(s_number); + if (s_magnitude.empty()) { + d_magnitude = 0.0; + } else { + d_magnitude = std::stod(s_magnitude); + } + return d_number * std::pow(10.0, d_magnitude) * sign; +} + +void UnitTable::calc_nominator_dims(std::shared_ptr<Unit> unit, std::string nominator_name) { + double nominator_prefix_factor = 1.0; + int nominator_power = 1; + std::string nom_name = nominator_name; + auto nominator = table.find(nominator_name); + + // if the nominator_name is not in the table, check if there are any prefixes or power + if (nominator == table.end()) { + int changed_nominator_name = 1; + + while (changed_nominator_name) { + changed_nominator_name = 0; + for (const auto& it: prefixes) { + auto res = std::mismatch(it.first.begin(), it.first.end(), nominator_name.begin()); + if (res.first == it.first.end()) { + changed_nominator_name = 1; + nominator_prefix_factor *= it.second; + nominator_name.erase(nominator_name.begin(), + nominator_name.begin() + it.first.size()); + } + } + } + // if the nominator is only a prefix, just multiply the prefix factor with the unit factor + if (nominator_name.empty()) { + for (const auto& it: prefixes) { + auto res = std::mismatch(it.first.begin(), it.first.end(), nom_name.begin()); + if (res.first == it.first.end()) { + unit->mul_factor(it.second); + return; + } + } + } + + // if the nominator_back is a UNIT_POWER save the power to be able + // to calculate the correct factor and dimensions later + char nominator_back = nominator_name.back(); + if (nominator_back >= '2' && nominator_back <= '9') { + nominator_power = nominator_back - '0'; + nominator_name.pop_back(); + } + + nominator = table.find(nominator_name); + + // delete "s" char for plural of the nominator_name + if (nominator == table.end()) { + if (nominator_name.back() == 's') { + nominator_name.pop_back(); + } + nominator = table.find(nominator_name); + } + } + + // if the nominator is still not found in the table then output error + // else multiply its factor to the unit factor and calculate unit's dimentions + if (nominator == table.end()) { + std::stringstream ss; + ss << "Unit " << nominator_name << " not defined!" << std::endl; + throw std::runtime_error(ss.str()); + } else { + for (int i = 0; i < nominator_power; i++) { + unit->mul_factor(nominator_prefix_factor * nominator->second->get_factor()); + unit->add_nominator_dims(nominator->second->get_dims()); + } + } +} + +void UnitTable::calc_denominator_dims(std::shared_ptr<Unit> unit, std::string denominator_name) { + double denominator_prefix_factor = 1.0; + int denominator_power = 1; + std::string denom_name = denominator_name; + auto denominator = table.find(denominator_name); + + // if the denominator_name is not in the table, check if there are any prefixes or power + if (denominator == table.end()) { + int changed_denominator_name = 1; + + while (changed_denominator_name) { + changed_denominator_name = 0; + for (const auto& it: prefixes) { + auto res = std::mismatch(it.first.begin(), it.first.end(), + denominator_name.begin()); + if (res.first == it.first.end()) { + changed_denominator_name = 1; + denominator_prefix_factor *= it.second; + denominator_name.erase(denominator_name.begin(), + denominator_name.begin() + it.first.size()); + } + } + } + // if the denominator is only a prefix, just multiply the prefix factor with the unit factor + if (denominator_name.empty()) { + for (const auto& it: prefixes) { + auto res = std::mismatch(it.first.begin(), it.first.end(), denom_name.begin()); + if (res.first == it.first.end()) { + unit->mul_factor(it.second); + return; + } + } + } + + // if the denominator_back is a UNIT_POWER save the power to be able + // to calculate the correct factor and dimensions later + char denominator_back = denominator_name.back(); + if (denominator_back >= '2' && denominator_back <= '9') { + denominator_power = denominator_back - '0'; + denominator_name.pop_back(); + } + + denominator = table.find(denominator_name); + + // delete "s" char for plural of the denominator_name + if (denominator == table.end()) { + if (denominator_name.back() == 's') { + denominator_name.pop_back(); + } + denominator = table.find(denominator_name); + } + } + + if (denominator == table.end()) { + std::stringstream ss; + ss << "Unit " << denominator_name << " not defined!" << std::endl; + throw std::runtime_error(ss.str()); + } else { + for (int i = 0; i < denominator_power; i++) { + unit->mul_factor(1.0 / (denominator_prefix_factor * denominator->second->get_factor())); + unit->add_denominator_dims(denominator->second->get_dims()); + } + } +} + +void UnitTable::insert(std::shared_ptr<Unit> unit) { + // check if the unit is a base unit and + // then add it to the base units vector + auto unit_nominator = unit->get_nominator_unit(); + auto only_base_unit_nominator = + unit_nominator.size() == 1 && unit_nominator.front().size() == 3 && + (unit_nominator.front().front() == '*' && unit_nominator.front().back() == '*'); + if (only_base_unit_nominator) { + // base_units_names[i] = "*i-th base unit*" (ex. base_units_names[0] = "*a*") + base_units_names[unit_nominator.front()[1] - 'a'] = unit->get_name(); + // if unit is found in table replace it + auto find_unit_name = table.find(unit->get_name()); + if (find_unit_name == table.end()) { + table.insert({unit->get_name(), unit}); + } else { + table.erase(unit->get_name()); + table.insert({unit->get_name(), unit}); + } + return; + } + // calculate unit's dimensions based on its nominator and denominator + for (const auto& it: unit->get_nominator_unit()) { + calc_nominator_dims(unit, it); + } + for (const auto& it: unit->get_denominator_unit()) { + calc_denominator_dims(unit, it); + } + // if unit is found in table replace it + auto find_unit_name = table.find(unit->get_name()); + if (find_unit_name == table.end()) { + table.insert({unit->get_name(), unit}); + } else { + table.erase(unit->get_name()); + table.insert({unit->get_name(), unit}); + } +} + +void UnitTable::insert_prefix(std::shared_ptr<Prefix> prfx) { + prefixes.insert({prfx->get_name(), prfx->get_factor()}); +} + +void UnitTable::print_units() const { + for (const auto& it: table) { + std::cout << std::fixed << std::setprecision(8) << it.first << " " + << it.second->get_factor() << ": "; + for (const auto& dims: it.second->get_dims()) { + std::cout << dims << " "; + } + std::cout << "\n"; + } +} + +void UnitTable::print_base_units() const { + int first_print = 1; + for (const auto& it: base_units_names) { + if (it != "") { + if (first_print) { + first_print = 0; + std::cout << it; + } else { + std::cout << " " << it; + } + } + } + std::cout << "\n"; +} + +void UnitTable::print_units_sorted(std::stringstream& units_details) { + std::vector<std::pair<std::string, std::shared_ptr<Unit>>> sorted_elements(table.begin(), + table.end()); + std::sort(sorted_elements.begin(), sorted_elements.end()); + for (const auto& it: sorted_elements) { + units_details << std::fixed << std::setprecision(8) << it.first << " " + << it.second->get_factor() << ":"; + for (const auto& dims: it.second->get_dims()) { + units_details << " " << dims; + } + units_details << "\n"; + } +} + +void UnitTable::print_base_units(std::stringstream& base_units_details) { + int first_print = 1; + for (const auto& it: base_units_names) { + if (it != "") { + if (first_print) { + first_print = 0; + base_units_details << it; + } else { + base_units_details << " " << it; + } + } + } + base_units_details << "\n"; +} + +} // namespace units +} // namespace nmodl diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp new file mode 100644 index 0000000000..784b072343 --- /dev/null +++ b/src/nmodl/units/units.hpp @@ -0,0 +1,281 @@ +#include <utility> + +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include <array> +#include <cmath> +#include <fstream> +#include <iomanip> +#include <iostream> +#include <regex> +#include <sstream> +#include <string> +#include <unordered_map> +#include <utility> +#include <vector> + + +namespace nmodl { +namespace units { + +/// Maximum number of dimensions of units (maximum number of base units) +static const int MAX_DIMS = 10; + +/** + * \class Unit + * \brief Class that stores all the data of a Unit + * + * The Unit class encapsulates all the variables and containers that are related to + * the definition of a unit. Those are: + * - Unit factor + * - Unit dimensions + * - Unit name + * - Nominator units + * - Denominator units + * + * The unit factor is calculated based on the unit factor if it is stated and the + * factors of the units that describe the units and are on the nominator or the + * denominator of the unit. The calculation of the factor is done by the UnitTable + * class, as it is needed to check the factors and dimensions of the units that + * this unit is based upon and are stored into the UnitTable table that keeps all + * the units parsed from a units file (nrnunits.lib by default) or the mod files. + * The dimensions of the unit represent which basic units are used to define this + * unit. They are represented by an array of ints of size MAX_DIMS. An array was + * used, as the base units do not change based on the SI units. If a base unit is + * used in this unit's definition then there is a 1 in the responding or else 0. + * If a unit is on the nominator of the unit definition, then its factor is + * multiplied by the unit's factor, while if it is on the denominator, its factor + * is divided by the unit's factor. The dimensions of the nominator units are added + * to the dimensions of the units and the dimensions of the denominator are + * subtracted from the unit's dimensions. The Unit representation is designed based + * on the units representation of the MOD2C parser. + */ +class Unit { + private: + /// Double factor of the Unit + double unit_factor = 1.0; + + /// Array of MAX_DIMS size that keeps the Unit's dimensions + std::array<int, MAX_DIMS> unit_dimensions = {0}; + + /// Name of the Unit + std::string unit_name; + + /// Vector of nominators of the Unit + std::vector<std::string> nominator; + + /// Vector of denominators of the Unit + std::vector<std::string> denominator; + + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor of Unit + Unit() = default; + + /// Constructor for simply creating a Unit with a given name + explicit Unit(std::string name) + : unit_name(std::move(name)) {} + + /// Constructor that instantiates a Unit with its factor, dimensions and name + Unit(const double factor, std::array<int, MAX_DIMS> dimensions, std::string name) + : unit_factor(factor) + , unit_dimensions(dimensions) + , unit_name(std::move(name)) {} + + /// \} + + /// Add unit name to the Unit + void add_unit(std::string name); + + /// If the Unit is a base unit the dimensions of the Unit should be calculated + /// based on the name of the base unit (ex. m \*a\* => m has dimension 0) + void add_base_unit(std::string name); + + /// Takes as argument a double as string, parses it as double and stores it to + /// the Unit factor + void add_nominator_double(std::string double_string); + + /// Add the dimensions of a nominator of the unit to the dimensions of the Unit + void add_nominator_dims(std::array<int, MAX_DIMS> dimensions); + + /// Subtract the dimensions of a nominator of the unit to the dimensions of the Unit + void add_denominator_dims(std::array<int, MAX_DIMS> dimensions); + + /// Add a unit to the vector of nominator strings of the Unit, so it can be processed + /// later + void add_nominator_unit(std::string nom); + + /// Add a vector of units to the vector of nominator strings of the Unit, so they can + /// be processed later + void add_nominator_unit(std::shared_ptr<std::vector<std::string>> nom); + + /// Add a unit to the vector of denominator strings of the Unit, so it can be processed + /// later + void add_denominator_unit(std::string denom); + + /// Add a vector of units to the vector of denominator strings of the Unit, so they can + /// be processed later + void add_denominator_unit(std::shared_ptr<std::vector<std::string>> denom); + + /// Multiply Unit's factor with a double factor + void mul_factor(double double_factor); + + /// Parse a fraction given as string and store the result to the factor of the Unit + void add_fraction(const std::string& fraction_string); + + /// Parse a double number given as string. The double can be positive or negative and + /// have all kinds of representations + double parse_double(std::string double_string); + + /// Getter for the vector of nominators of the Unit + std::vector<std::string> get_nominator_unit() const { + return nominator; + } + + /// Getter for the vector of denominators of the Unit + std::vector<std::string> get_denominator_unit() const { + return denominator; + } + + /// Getter for the name of the Unit + std::string get_name() const { + return unit_name; + } + + /// Getter for the double factor of the Unit + double get_factor() const { + return unit_factor; + } + + /// Getter for the array of Unit's dimensions + std::array<int, MAX_DIMS> get_dims() const { + return unit_dimensions; + } +}; + +/** + * \class Prefix + * \brief Class that stores all the data of a prefix + * + * Prefixes of units can also be defined in the units file. Those + * prefixes are then checked during a unit's insertion to the UnitTable + * to multiply their factor to the unit's factor. + * + */ +class Prefix { + private: + /// Prefix's double factor + double prefix_factor = 1; + + /// Prefix's name + std::string prefix_name; + + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor for Prefix + Prefix() = default; + + /// Constructor that instantiates a Prefix with its name and factor + Prefix(std::string name, std::string factor); + + /// \} + + /// Getter for the name of the Prefix + std::string get_name() const { + return prefix_name; + } + + /// Getter for the factor of the Prefix + double get_factor() const { + return prefix_factor; + } +}; + +/** + * \class UnitTable + * \brief Class that stores all the units, prefixes and names of base units used + * + * The UnitTable encapsulates all the containers that are needed to store all the + * Units defined. Those containers are: + * - A table (hash map) that stores all the Units + * - A hash map that stores all the Prefixes + * - An array to store the base units names + * + * The names and the design is based on the corresponding ones of MOD2C. + * The table is a hash map that uses as key the name of the Unit and as objects + * a shared_ptr to a Unit. This is needed as the parser passes to the UnitTable + * shared_ptrs, so that there is no need to allocate more space for the units or + * take care of the deallocation of the memory, which is done automatically by + * the smart pointer. + * A shared_ptr to a UnitTable is kept by the UnitDriver, to be able to populate the + * UnitTable by using multiple sources (files and strings). + * The UnitTable takes care of inserting Units, Prefixes and base units to the table + * calculating or the needed factors and dimensions. + * + */ +class UnitTable { + private: + /// Hash map that stores all the Units + std::unordered_map<std::string, std::shared_ptr<Unit>> table; + + /// Hash map that stores all the Prefixes + std::unordered_map<std::string, double> prefixes; + + /// Hash map that stores all the base units' names + std::array<std::string, MAX_DIMS> base_units_names; + + public: + /// Default constructor for UnitTable + UnitTable() = default; + + /// Calculate unit's dimensions based on its nominator unit named nominator_name which is + /// stored in the UnitTable's table + void calc_nominator_dims(std::shared_ptr<Unit> unit, std::string nominator_name); + + /// Calculate unit's dimensions based on its denominator unit named denominator_name which is + /// stored in the UnitTable's table + void calc_denominator_dims(std::shared_ptr<Unit> unit, std::string denominator_name); + + /// Insert a unit to the UnitTable table and calculate its dimensions and factor based on the + /// previously stored units in the UnitTable + /// The unit can be a normal unit or unit based on just a base unit. In the latter case, the + /// base unit is also added to the base_units_name array of UnitTable + void insert(std::shared_ptr<Unit> unit); + + /// Insert a prefix to the prefixes of the UnitTable + void insert_prefix(std::shared_ptr<Prefix> prfx); + + /// Get the unit_name of the UnitTable's table + std::shared_ptr<Unit> get_unit(const std::string& unit_name) { + return table[unit_name]; + } + + /// Print the details of the units that are stored in the UnitTable + void print_units() const; + + /// Print the details of the units that are stored in the UnitTable + /// to the stringstream units_details in ascending order to be printed + /// in tests in specific order + void print_units_sorted(std::stringstream& units_details); + + /// Print the base units that are stored in the UnitTable + void print_base_units() const; + + /// Print the base units that are stored in the UnitTable to the + /// stringstream base_units_details + void print_base_units(std::stringstream& base_units_details); +}; + +} // namespace units +} // namespace nmodl diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 4073ad572c..85500808ef 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -11,7 +11,7 @@ set(UTIL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.hpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp - ${CMAKE_BINARY_DIR}/version.cpp) + ${CMAKE_BINARY_DIR}/config.cpp) # ============================================================================= # Symbol table library diff --git a/src/nmodl/version/version.h b/src/nmodl/version/version.h deleted file mode 100644 index d0622edd27..0000000000 --- a/src/nmodl/version/version.h +++ /dev/null @@ -1,20 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -#include <string> - -namespace nmodl { -struct version { - static const std::string GIT_REVISION; - static const std::string NMODL_VERSION; - static std::string to_string() { - return NMODL_VERSION + " " + GIT_REVISION; - } -}; -} // namespace nmodl diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index cbe20ed17f..94c2fc2216 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -11,9 +11,9 @@ #include "fmt/format.h" #include "pybind11/embed.h" +#include "config/config.h" #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" -#include "version/version.h" #include "visitors/ast_visitor.hpp" #include "visitors/cnexp_solve_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 366c2e9f72..61510a0bdd 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -13,6 +13,11 @@ add_library(test_util ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) +# ============================================================================= +# Common input data library +# ============================================================================= +add_library(config STATIC ${PROJECT_BINARY_DIR}/config.cpp) + # ============================================================================= # Test executables # ============================================================================= @@ -23,6 +28,8 @@ add_executable(testvisitor visitor/visitor.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) +add_executable(testunitlexer units/lexer.cpp) +add_executable(testunitparser units/parser.cpp) target_link_libraries(testmodtoken lexer) target_link_libraries(testlexer lexer) @@ -30,6 +37,8 @@ target_link_libraries(testparser lexer test_util) target_link_libraries(testvisitor symtab visitor lexer util test_util printer) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) +target_link_libraries(testunitlexer lexer util) +target_link_libraries(testunitparser lexer test_util config) add_test(NAME ModToken COMMAND testmodtoken) add_test(NAME Lexer COMMAND testlexer) @@ -38,6 +47,8 @@ add_test(NAME Visitor COMMAND testvisitor) add_test(NAME Printer COMMAND testprinter) add_test(NAME Symtab COMMAND testsymtab) add_test(NAME Newton COMMAND testnewton) +add_test(NAME UnitLexer COMMAND testunitlexer) +add_test(NAME UnitParser COMMAND testunitparser) set_tests_properties(Visitor PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) diff --git a/test/nmodl/transpiler/units/lexer.cpp b/test/nmodl/transpiler/units/lexer.cpp new file mode 100644 index 0000000000..f532bcb399 --- /dev/null +++ b/test/nmodl/transpiler/units/lexer.cpp @@ -0,0 +1,66 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_MAIN + +#include <string> + +#include "catch/catch.hpp" +#include "lexer/unit_lexer.hpp" +#include "parser/unit_driver.hpp" + +using namespace nmodl; + +using nmodl::parser::UnitDriver; +using nmodl::parser::UnitLexer; +using parser::UnitParser; +using Token = UnitParser::token; +using TokenType = UnitParser::token_type; +using SymbolType = UnitParser::symbol_type; + +/// just retrieve token type from lexer +TokenType token_type(const std::string& name) { + std::istringstream ss(name); + std::istream& in = ss; + + UnitDriver driver; + UnitLexer scanner(driver, &in); + return scanner.next_token().token(); +} + +TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { + SECTION("Tests for comments") { + REQUIRE(token_type("/ comment") == Token::COMMENT); + REQUIRE(token_type("/comment") == Token::COMMENT); + } + + SECTION("Tests for doubles") { + REQUIRE(token_type("27.52") == Token::DOUBLE); + REQUIRE(token_type("1.01325+5") == Token::DOUBLE); + REQUIRE(token_type("1") == Token::DOUBLE); + REQUIRE(token_type("-1.324e+10") == Token::DOUBLE); + REQUIRE(token_type("1-1") == Token::DOUBLE); + REQUIRE(token_type("1|100") == Token::FRACTION); + REQUIRE(token_type(".03") == Token::DOUBLE); + REQUIRE(token_type("1|8.988e9") == Token::FRACTION); + } + + SECTION("Tests for units") { + REQUIRE(token_type("*a*") == Token::BASE_UNIT); + REQUIRE(token_type("*k*") == Token::INVALID_BASE_UNIT); + REQUIRE(token_type("planck") == Token::NEW_UNIT); + REQUIRE(token_type(" m2") == Token::UNIT_POWER); + REQUIRE(token_type(" m") == Token::UNIT); + REQUIRE(token_type("yotta-") == Token::PREFIX); + } + + SECTION("Tests for special characters") { + REQUIRE(token_type("-") == Token::UNIT_PROD); + REQUIRE(token_type(" / ") == Token::DIVISION); + REQUIRE(token_type("\n") == Token::NEWLINE); + } +} diff --git a/test/nmodl/transpiler/units/parser.cpp b/test/nmodl/transpiler/units/parser.cpp new file mode 100644 index 0000000000..d1850fe7e9 --- /dev/null +++ b/test/nmodl/transpiler/units/parser.cpp @@ -0,0 +1,190 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_MAIN + +#include <sstream> +#include <string> +#include <utility> + +#include "catch/catch.hpp" +#include "config/config.h" +#include "parser/diffeq_driver.hpp" +#include "parser/unit_driver.hpp" +#include "test/utils/test_utils.hpp" + +//============================================================================= +// Parser tests +//============================================================================= + +// Driver is defined as global to store all the units inserted to it and to be +// able to define complex units based on base units +nmodl::parser::UnitDriver driver; + +bool is_valid_construct(const std::string& construct) { + return driver.parse_string(construct); +} + +std::string parse_string(const std::string& unit_definition) { + nmodl::parser::UnitDriver correctness_driver; + correctness_driver.parse_file(nmodl::NrnUnitsLib::get_path()); + correctness_driver.parse_string(unit_definition); + std::stringstream ss; + correctness_driver.table->print_units_sorted(ss); + correctness_driver.table->print_base_units(ss); + return ss.str(); +} + +bool is_substring(const std::string& str1, const std::string& str2) { + return str1.find(str2) != std::string::npos; +} + +SCENARIO("Unit parser definition of units validity") { + GIVEN("A base unit") { + WHEN("Base unit is *a*") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("m *a*\n")); + } + } + WHEN("Base unit is *b*") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("kg\t\t\t*b*\n")); + } + } + WHEN("Base unit is *d*") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("coul\t\t\t*d*\n")); + } + } + WHEN("Base unit is *i*") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("erlang\t\t\t*i*\n")); + } + } + WHEN("Base unit is *c*") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("sec\t\t\t*c*\n")); + } + } + } + GIVEN("A double number") { + WHEN("Double number is writen like 3.14") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("pi\t\t\t3.14159265358979323846\n")); + } + } + WHEN("Double number is writen like 1") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("fuzz\t\t\t1\n")); + } + } + WHEN("Double number is negative") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("ckan\t\t\t-32.19976 m\n")); + } + } + } + GIVEN("A dimensionless constant") { + WHEN("Constant expression is double / constant") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("radian\t\t\t.5 / pi\n")); + } + } + } + GIVEN("A power of another unit") { + WHEN("Power of 2") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("steradian\t\tradian2\n")); + } + } + WHEN("Power of 3") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("stere\t\t\tm3\n")); + } + } + } + GIVEN("Divisions and multiplications of units") { + WHEN("Units are multiplied") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("degree\t\t\t1|180 pi-radian\n")); + } + } + WHEN("There are both divisions and multiplications") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("newton\t\t\tkg-m/sec2\n")); + } + } + WHEN("There is only division") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("dipotre\t\t\t/m\n")); + } + } + } + GIVEN("A double number and some units") { + WHEN("Double number is multiplied by a power of 10 with division of multiple units") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("c\t\t\t2.99792458+8 m/sec fuzz\n")); + } + } + WHEN("Double number is writen like .9") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("grade\t\t\t.9 degree\n")); + } + } + } + GIVEN("A fraction and some units") { + WHEN("Fraction is writen like 1|2") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("ccs\t\t\t1|36 erlang\n")); + } + } + WHEN("Fraction is writen like 1|8.988e9") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("statcoul\t\t1|2.99792458+9 coul\n")); + } + } + } +} + +SCENARIO("Unit parser definition of units correctness") { + GIVEN("Parsed the nrnunits.lib file") { + WHEN("Multiple units definitions based on the units defined in nrnunits.lib") { + THEN("parser accepts the units correctly") { + std::string units_definitions = R"( + mV millivolt + mM milli/liter + mA milliamp + KTOMV 0.0853 mV/degC + B 0.26 mM-cm2/mA-ms + dummy1 .025 1/m2 + dummy2 1|4e+1 m/m3 + dummy3 25-3 / m2 + dummy4 -0.025 /m2 + dummy5 2.5 % + R k-mole + R1 8.314 volt-coul/degC + R2 8314 mV-coul/degC + )"; + std::string parsed_units = parse_string(reindent_text(units_definitions)); + REQUIRE(is_substring(parsed_units, "mV 0.00100000: 2 1 -2 -1 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "mM 1.00000000: -3 0 0 0 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "mA 0.00100000: 0 0 -1 1 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "KTOMV 0.00008530: 2 1 -2 -1 0 0 0 0 0 -1")); + REQUIRE(is_substring(parsed_units, "B 26.00000000: -1 0 0 -1 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "dummy1 0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "dummy2 0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "dummy3 0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "dummy4 -0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "dummy5 0.02500000: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE(is_substring(parsed_units, "R 8.31449872: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE(is_substring(parsed_units, "R1 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE(is_substring(parsed_units, "R2 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE(is_substring(parsed_units, "m kg sec coul candela dollar bit erlang K")); + } + } + } +} From a84b7e6db7c71896ada88d72bc8e61498deb66e8 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Tue, 23 Apr 2019 13:03:56 +0200 Subject: [PATCH 192/871] Update processing of Jinja template files (BlueBrain/nmodl#147) - move templates to sub-directories ast, visitors and pybind - remove hardcoded directory names - copy .clang-format to tempfile to get generated templates formated - trim block set to false to allow empty lines - pass clang-format options from cmake NMODL Repo SHA: BlueBrain/nmodl@867354667c3bf8de4a79844a8e7a1df9bbfa33da --- src/nmodl/language/CMakeLists.txt | 5 +- src/nmodl/language/code_generator.py | 81 ++++++++++--------- .../language/templates/{ => ast}/ast.cpp | 0 .../language/templates/{ => ast}/ast.hpp | 0 .../language/templates/{ => ast}/ast_decl.hpp | 0 .../language/templates/{ => pybind}/pyast.cpp | 0 .../language/templates/{ => pybind}/pyast.hpp | 0 .../templates/{ => pybind}/pysymtab.cpp | 0 .../templates/{ => pybind}/pyvisitor.cpp | 0 .../templates/{ => pybind}/pyvisitor.hpp | 0 .../templates/{ => visitors}/ast_visitor.cpp | 0 .../templates/{ => visitors}/ast_visitor.hpp | 0 .../templates/{ => visitors}/json_visitor.cpp | 0 .../templates/{ => visitors}/json_visitor.hpp | 0 .../{ => visitors}/lookup_visitor.cpp | 0 .../{ => visitors}/lookup_visitor.hpp | 0 .../{ => visitors}/nmodl_visitor.cpp | 0 .../{ => visitors}/nmodl_visitor.hpp | 0 .../{ => visitors}/symtab_visitor.cpp | 0 .../{ => visitors}/symtab_visitor.hpp | 0 .../templates/{ => visitors}/visitor.hpp | 0 21 files changed, 46 insertions(+), 40 deletions(-) rename src/nmodl/language/templates/{ => ast}/ast.cpp (100%) rename src/nmodl/language/templates/{ => ast}/ast.hpp (100%) rename src/nmodl/language/templates/{ => ast}/ast_decl.hpp (100%) rename src/nmodl/language/templates/{ => pybind}/pyast.cpp (100%) rename src/nmodl/language/templates/{ => pybind}/pyast.hpp (100%) rename src/nmodl/language/templates/{ => pybind}/pysymtab.cpp (100%) rename src/nmodl/language/templates/{ => pybind}/pyvisitor.cpp (100%) rename src/nmodl/language/templates/{ => pybind}/pyvisitor.hpp (100%) rename src/nmodl/language/templates/{ => visitors}/ast_visitor.cpp (100%) rename src/nmodl/language/templates/{ => visitors}/ast_visitor.hpp (100%) rename src/nmodl/language/templates/{ => visitors}/json_visitor.cpp (100%) rename src/nmodl/language/templates/{ => visitors}/json_visitor.hpp (100%) rename src/nmodl/language/templates/{ => visitors}/lookup_visitor.cpp (100%) rename src/nmodl/language/templates/{ => visitors}/lookup_visitor.hpp (100%) rename src/nmodl/language/templates/{ => visitors}/nmodl_visitor.cpp (100%) rename src/nmodl/language/templates/{ => visitors}/nmodl_visitor.hpp (100%) rename src/nmodl/language/templates/{ => visitors}/symtab_visitor.cpp (100%) rename src/nmodl/language/templates/{ => visitors}/symtab_visitor.hpp (100%) rename src/nmodl/language/templates/{ => visitors}/visitor.hpp (100%) diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 49bcda4f1c..04855643a1 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -3,8 +3,8 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) -file(GLOB TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") +file(GLOB_RECURSE TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" + "${PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") file(GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py") @@ -20,6 +20,7 @@ add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src + --clang-format-opts="--style=file" WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml DEPENDS ${PROJECT_SOURCE_DIR}/src/language/codegen.yaml diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index fd1ca8d1b0..04a6bf5a77 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -9,11 +9,10 @@ import filecmp import logging import os -from pathlib import Path +from pathlib import Path, PurePath import shutil import subprocess import tempfile - import jinja2 from parser import LanguageParser @@ -24,8 +23,9 @@ logging.basicConfig(level=logging.INFO, format='%(message)s') parser = argparse.ArgumentParser() parser.add_argument('--clang-format', help="Path to clang-format executable") -parser.add_argument('--clang-format-opts', help="Path to clang-format executable", nargs='+') +parser.add_argument('--clang-format-opts', help="clang-format options", nargs='+') parser.add_argument('--base-dir') + args = parser.parse_args() clang_format = args.clang_format if clang_format: @@ -33,54 +33,59 @@ if args.clang_format_opts: clang_format += args.clang_format_opts -# parse nmodl definition file and get list of abstract nodes +# parse NMODL and codegen definition file to get AST nodes nmodl_nodes = LanguageParser("nmodl.yaml").parse_file() codegen_nodes = LanguageParser("codegen.yaml").parse_file() -# combine ast nodes from NMODL specification and codegen specification +# combine NMODL and codegen nodes for whole AST nodes = nmodl_nodes nodes.extend(x for x in codegen_nodes if x not in nodes) -templates = Path(__file__).parent / 'templates' +# directory containing all templates +templates_dir = Path(__file__).parent / 'templates' + +# destination directory to render templates +destination_dir = Path(args.base_dir) or Path(__file__).resolve().parent.parent -basedir = Path(args.base_dir) or Path(__file__).resolve().parent.parent -for subdir in ['pybind', 'visitors', 'ast']: - (basedir / subdir).mkdir(parents=True, exist_ok=True) +# templates will be created and clang-formated in tempfile. +# copy .clang-format file for correct formating +clang_format_file = Path(Path(__file__).resolve().parent.parent.parent / ".clang-format") +if clang_format_file.exists(): + temp_dir = tempfile.gettempdir() + shutil.copy2(clang_format_file, temp_dir) -env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates)), - trim_blocks=True, +env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates_dir)), + trim_blocks=False, lstrip_blocks=True) env.filters['snake_case'] = utils.to_snake_case updated_files = [] -for fn in templates.glob('*.[ch]pp'): - if fn.name.startswith('py'): - filename = basedir / 'pybind' / fn.name - elif 'visitor' in fn.name: - filename = basedir / 'visitors' / fn.name - else: - filename = basedir / 'ast' / fn.name - template = env.get_template(fn.name) - content = template.render(nodes=nodes, node_info=node_info) - if filename.exists(): - # render template in temporary file - # and update target file ONLY if different - # to save a lot of build time - fd, tmp_path = tempfile.mkstemp() - os.write(fd, content.encode('utf-8')) - os.close(fd) - if clang_format: - subprocess.check_call(clang_format + ['-i', tmp_path]) - if not filecmp.cmp(str(filename), tmp_path): - shutil.move(tmp_path, filename) - updated_files.append(str(fn.name)) - else: - with filename.open('w') as fd: - fd.write(content) - updated_files.append(str(fn.name)) - if clang_format: - subprocess.check_call(clang_format + ['-i', filename]) +for path in templates_dir.iterdir(): + sub_dir = PurePath(path).name + (destination_dir / sub_dir).mkdir(parents=True, exist_ok=True) + for filepath in path.glob('*.[ch]pp'): + source_file = os.path.join(sub_dir, filepath.name) + destination_file = destination_dir / sub_dir / filepath.name + template = env.get_template(source_file) + content = template.render(nodes=nodes, node_info=node_info) + if destination_file.exists(): + # render template in temporary file and update target file + # ONLY if different (to save a lot of build time) + fd, tmp_path = tempfile.mkstemp() + os.write(fd, content.encode('utf-8')) + os.close(fd) + if clang_format: + subprocess.check_call(clang_format + ['-i', tmp_path]) + if not filecmp.cmp(str(destination_file), tmp_path): + shutil.move(tmp_path, destination_file) + updated_files.append(str(filepath.name)) + else: + with destination_file.open('w') as fd: + fd.write(content) + updated_files.append(str(filepath.name)) + if clang_format: + subprocess.check_call(clang_format + ['-i', destination_file]) if updated_files: logging.info(' Updating out of date template files : %s', ' '.join(updated_files)) diff --git a/src/nmodl/language/templates/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp similarity index 100% rename from src/nmodl/language/templates/ast.cpp rename to src/nmodl/language/templates/ast/ast.cpp diff --git a/src/nmodl/language/templates/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp similarity index 100% rename from src/nmodl/language/templates/ast.hpp rename to src/nmodl/language/templates/ast/ast.hpp diff --git a/src/nmodl/language/templates/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp similarity index 100% rename from src/nmodl/language/templates/ast_decl.hpp rename to src/nmodl/language/templates/ast/ast_decl.hpp diff --git a/src/nmodl/language/templates/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp similarity index 100% rename from src/nmodl/language/templates/pyast.cpp rename to src/nmodl/language/templates/pybind/pyast.cpp diff --git a/src/nmodl/language/templates/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp similarity index 100% rename from src/nmodl/language/templates/pyast.hpp rename to src/nmodl/language/templates/pybind/pyast.hpp diff --git a/src/nmodl/language/templates/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp similarity index 100% rename from src/nmodl/language/templates/pysymtab.cpp rename to src/nmodl/language/templates/pybind/pysymtab.cpp diff --git a/src/nmodl/language/templates/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp similarity index 100% rename from src/nmodl/language/templates/pyvisitor.cpp rename to src/nmodl/language/templates/pybind/pyvisitor.cpp diff --git a/src/nmodl/language/templates/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp similarity index 100% rename from src/nmodl/language/templates/pyvisitor.hpp rename to src/nmodl/language/templates/pybind/pyvisitor.hpp diff --git a/src/nmodl/language/templates/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp similarity index 100% rename from src/nmodl/language/templates/ast_visitor.cpp rename to src/nmodl/language/templates/visitors/ast_visitor.cpp diff --git a/src/nmodl/language/templates/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp similarity index 100% rename from src/nmodl/language/templates/ast_visitor.hpp rename to src/nmodl/language/templates/visitors/ast_visitor.hpp diff --git a/src/nmodl/language/templates/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp similarity index 100% rename from src/nmodl/language/templates/json_visitor.cpp rename to src/nmodl/language/templates/visitors/json_visitor.cpp diff --git a/src/nmodl/language/templates/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp similarity index 100% rename from src/nmodl/language/templates/json_visitor.hpp rename to src/nmodl/language/templates/visitors/json_visitor.hpp diff --git a/src/nmodl/language/templates/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp similarity index 100% rename from src/nmodl/language/templates/lookup_visitor.cpp rename to src/nmodl/language/templates/visitors/lookup_visitor.cpp diff --git a/src/nmodl/language/templates/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp similarity index 100% rename from src/nmodl/language/templates/lookup_visitor.hpp rename to src/nmodl/language/templates/visitors/lookup_visitor.hpp diff --git a/src/nmodl/language/templates/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp similarity index 100% rename from src/nmodl/language/templates/nmodl_visitor.cpp rename to src/nmodl/language/templates/visitors/nmodl_visitor.cpp diff --git a/src/nmodl/language/templates/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp similarity index 100% rename from src/nmodl/language/templates/nmodl_visitor.hpp rename to src/nmodl/language/templates/visitors/nmodl_visitor.hpp diff --git a/src/nmodl/language/templates/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp similarity index 100% rename from src/nmodl/language/templates/symtab_visitor.cpp rename to src/nmodl/language/templates/visitors/symtab_visitor.cpp diff --git a/src/nmodl/language/templates/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp similarity index 100% rename from src/nmodl/language/templates/symtab_visitor.hpp rename to src/nmodl/language/templates/visitors/symtab_visitor.hpp diff --git a/src/nmodl/language/templates/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp similarity index 100% rename from src/nmodl/language/templates/visitor.hpp rename to src/nmodl/language/templates/visitors/visitor.hpp From a5b2f65c19b71afd84b4ded6437cc74a8e86452e Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Tue, 23 Apr 2019 22:15:49 +0200 Subject: [PATCH 193/871] Updating NMODL YAML specification to allow doxygen documentation for full AST (BlueBrain/nmodl#149) - add new field brief to have one line summary - description field will be used for description with NMODL examples - Jinja2 trim block set back to true to avoid extra lines in python ast classes - get_description method added in node.py to return description in doxygen form - replace description with brief in ast generation python code - update doxygen settings, add images, footer and css - udpating of YAML specification with documentation and examples : WIP but this will be updated gradually - generate Doxyfile with correct paths NMODL Repo SHA: BlueBrain/nmodl@f2558eab92c2c6c4118bf3d52a4fbfbc1f4858fd --- cmake/nmodl/CMakeLists.txt | 3 + .../transpiler/{Doxyfile => Doxyfile.in} | 36 +- docs/nmodl/transpiler/background.png | Bin 0 -> 48429 bytes docs/nmodl/transpiler/doxygen_nmodl.css | 51 + docs/nmodl/transpiler/footer.html | 13 + docs/nmodl/transpiler/logo.png | Bin 0 -> 20637 bytes src/nmodl/language/argument.py | 1 + src/nmodl/language/code_generator.py | 2 +- src/nmodl/language/codegen.yaml | 46 +- src/nmodl/language/nmodl.yaml | 1067 +++++++++++------ src/nmodl/language/nodes.py | 18 +- src/nmodl/language/parser.py | 5 + src/nmodl/language/templates/ast/ast.hpp | 14 +- src/nmodl/language/templates/pybind/pyast.cpp | 2 +- 14 files changed, 834 insertions(+), 424 deletions(-) rename docs/nmodl/transpiler/{Doxyfile => Doxyfile.in} (99%) create mode 100644 docs/nmodl/transpiler/background.png create mode 100644 docs/nmodl/transpiler/doxygen_nmodl.css create mode 100644 docs/nmodl/transpiler/footer.html create mode 100644 docs/nmodl/transpiler/logo.png diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9d9be30571..49e11c4444 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -110,6 +110,9 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/config.cpp @ONLY) +# generate Doxyfile with correct source paths +configure_file(${PROJECT_SOURCE_DIR}/docs/Doxyfile.in ${PROJECT_SOURCE_DIR}/docs/Doxyfile) + # ============================================================================= # list of autogenerated files # ============================================================================= diff --git a/docs/nmodl/transpiler/Doxyfile b/docs/nmodl/transpiler/Doxyfile.in similarity index 99% rename from docs/nmodl/transpiler/Doxyfile rename to docs/nmodl/transpiler/Doxyfile.in index f81f3162f8..b4c0b9f330 100644 --- a/docs/nmodl/transpiler/Doxyfile +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -32,7 +32,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = "NMODL" +PROJECT_NAME = "User Guide" # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -51,7 +51,7 @@ PROJECT_BRIEF = # pixels and the maximum width should not exceed 200 pixels. Doxygen will copy # the logo to the output directory. -PROJECT_LOGO = logo.pdf +PROJECT_LOGO = logo.png # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path # into which the generated documentation will be written. If a relative path is @@ -313,7 +313,8 @@ OPTIMIZE_OUTPUT_SLICE = NO # Note that for custom extensions you also need to set FILE_PATTERNS otherwise # the files are not read by doxygen. -EXTENSION_MAPPING = +EXTENSION_MAPPING = .yaml=Python +EXTENSION_MAPPING += .ispc=C # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -746,7 +747,7 @@ CITE_BIB_FILES = # messages are off. # The default value is: NO. -QUIET = NO +QUIET = YES # The WARNINGS tag can be used to turn on/off the warning messages that are # generated to standard error (stderr) by doxygen. If WARNINGS is set to YES @@ -813,7 +814,10 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = ../src ../test ../build +INPUT = @PROJECT_SOURCE_DIR@/src +INPUT += @PROJECT_SOURCE_DIR@/test +INPUT += @PROJECT_BINARY_DIR@/src/ast +INPUT += @PROJECT_BINARY_DIR@/src/visitors # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -844,17 +848,17 @@ FILE_PATTERNS = *.c \ *.cpp \ *.c++ \ *.ipp \ + *.ispc \ *.h \ *.hh \ *.hxx \ *.hpp \ *.h++ \ - *.m \ *.markdown \ *.md \ *.mm \ *.dox \ - *.py \ + *.yaml \ # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -869,7 +873,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = +EXCLUDE = @PROJECT_SOURCE_DIR@/src/pybind/pybind_utils.hpp # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded @@ -885,7 +889,7 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = */src/language/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -992,7 +996,7 @@ USE_MDFILE_AS_MAINPAGE = ../README.md # also VERBATIM_HEADERS is set to NO. # The default value is: NO. -SOURCE_BROWSER = NO +SOURCE_BROWSER = YES # Setting the INLINE_SOURCES tag to YES will include the body of functions, # classes and enums directly into the documentation. @@ -1005,7 +1009,7 @@ INLINE_SOURCES = NO # Fortran comments will always remain visible. # The default value is: YES. -STRIP_CODE_COMMENTS = YES +STRIP_CODE_COMMENTS = NO # If the REFERENCED_BY_RELATION tag is set to YES then for each documented # entity all documented functions referencing it will be listed. @@ -1145,7 +1149,7 @@ HTML_HEADER = # that doxygen normally uses. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_FOOTER = +HTML_FOOTER = footer.html # The HTML_STYLESHEET tag can be used to specify a user-defined cascading style # sheet that is used by each HTML page. It can be used to fine-tune the look of @@ -1170,7 +1174,7 @@ HTML_STYLESHEET = # list). For an example see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = doxygen_nmodl.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note @@ -1180,7 +1184,7 @@ HTML_EXTRA_STYLESHEET = # files will be copied as-is; there are no commands or markers available. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_FILES = +HTML_EXTRA_FILES = background.png # The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen # will adjust the colors in the style sheet and background images according to @@ -1467,7 +1471,7 @@ DISABLE_INDEX = NO # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -GENERATE_TREEVIEW = NO +GENERATE_TREEVIEW = YES # The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that # doxygen will group on one line in the generated HTML documentation. @@ -1522,7 +1526,7 @@ FORMULA_TRANSPARENT = YES # The default value is: NO. # This tag requires that the tag GENERATE_HTML is set to YES. -USE_MATHJAX = NO +USE_MATHJAX = YES # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: diff --git a/docs/nmodl/transpiler/background.png b/docs/nmodl/transpiler/background.png new file mode 100644 index 0000000000000000000000000000000000000000..73a1fe77e4908c473e580680bdfbc7adac9d1482 GIT binary patch literal 48429 zcmV*6Ky$x|P)<h;3K|Lk000e1NJLTq00961009691^@s6Tym&p00006VoOIv0RI60 z0RN!9r;`8x010qNS#tmY3ljhU3ljkVnw%H_000McNliru;{+235+tF_Scd=rAOJ~3 zK~#9!l%4HT99fcW6Oj@~pi-&2#`@0tKit^0o+%a0H%Q|CNEWeT;eqC2R#phe%!m`` z_{a9`+mD`}o}P9W7rWi3PrKdC&2G2fAO8FP{qW!8<8Jry<KbsFH@n@}ue;sF#csFT z9e(%z{qowMKX<!#?+*X}@L{(*J3IW%!^7ddcDuv-+}`eX$H#})?RJOPTwNVrb9J@b z{r>xI_xInsm#+^WmiOiFfBxL<&d(2@|6l*L+x_|H;d9Q;cDrxi4!?VNIQ-}L`Tl+R z-1qNyFZaIN9lk$4UcdI;yTkAK-EMdI{msqtI@XQPxVk!ge|fpveg3?>2lx2>`||gE z#@*d+cY3<pefe@&vw#14c@`f&96n>WTffh4cetk8+uiQRkHh`^{CW5r*7V)GW!-t7 zFJG2t$UX3kxGuhUZvNsLc@6J(ceh;E*RO}?z?$6N9{%p?>Ts<eKOSDop7Gb+-EQ~K ze=gU^b3Z>ny#DLg!@uwEm%VUua`^kx)5Gie@_uG-culxJ_V8|Zc#q4=<#~VjuskPU z56|`I&*ioJEo*vyzI+B}>GE<pQ@h=6_pg5~dzttC^yzR<tRd_9?%i_TkB^5n-R%zR z!oFk;K7Cs6|Fg5*?)v)h`se+&e_4q4@7E9i!-r)vfB(Kb*vH3Z5!iJ6oCWIxWl?w! z|INF1%X{!S@7}EseczAI=IiKac>ufJ;ded!ef{_E%jfbL{M(!3^z`ua<KyKz`o6rM z4@|G&^mM(EcyE4xe7s)&r>DdFK0U2B-ObJV-j9zDo9X_3Sx@eTXNa-m5c%Nq^Y`x$ z@5?jv7lz(nY*ya?^mP4ye-?Y|{rhE;a$kP7H9j_4yq@c}Bl!M(c^^BP$#{7!IYd0u z$H&7Ocz=1V_xt5}d2Ly94jIpuL(Tf}|NZ`OxcS03zJGssj{Y4EgD(y>YsGW<@L_#l zte1`Z<Kx<yynFX-0Qe;q*$esbu->S)bY2`j%$|@hHa+jbM!UOP7WLub@G}n1{r%zh zCntvk{`9mq2w2@upAMGs=xFV=eE2vVpFSP#^X_ihfOmJxJ>TCS-iHU}-`Rk%PHes( zKh{Q$wL3pwZ<77~VEs={mWSjuoW8g(M=w`b%bNPY+E{W;TsyyecvuVu?|po{tP6*D zGIo5=2IfBBzh8SpUTdSg-@o1i^}PM!Ir_7|e_tOE@6*S}<q#hquOk}%t_O_2^}ann zKSUE4ex7X&3VXxfV^DO+UZPbZvfJCkGvMBDZq}c3bF&x^f98h|hoAXcY*bix-iIj7 zdxdp>zTf?;MX$H57w2aEz5h?x^Hvy%cV_`UeL8G9hic!yufrrBrXMN`^7ZR(_s1V= zFXixuFzobnS=^hO#fm%qu_5!}^<m<G@bjh_UP}&z-<Q|fSajVv*hEDS4~ud9@nhMz zeh&Bd%cddpby&^cc_a84)%e?Z_aG6)?Dy+*;Q07pSo|zbPnXvnA20v0E`FX|AFuOu zvtDd^u9r2nLE`^BE7C*npR=>W^YP#F9RB=sy*_7WZ%0HYC(HW#GYPl-_3&``ds0Qt z)4O+zVJ21MxssMVJRIH+!{UhO=4N>o>_hgB&!_g)ItAduy|6!jE@3cM$>C;0OK)}- zl{|%<tx<zhfPQ%aZ2%m?9~~V&*VeF}hlj-LxMvQsjS)GeugBJv4bA6spc{VrK>5CH zH2MC=kL57cGdc7<IawPWJO{=B<JwUE$BzZq;j^Be*6VBU{q(dp5=3iT#{a{I*M@~d z@$|GDZmz@6$j0HvkJnzb`+<msb+7kz1jIAtFcKxOZeSAN6F3j@bfPZa$5@Q_?)CNZ zoLMUlA@|BY11ouWIQ;wmetFhMN9%iHtuexlyt!}oTT{uV+uo-?e<}jtO?<$(00Tv~ zAcXH{XX`we4<zs5X1vkP&kw(2Q}u9o(YSHE8}Dxrh@WvE4%__x{+aV3Ha44pT>JR= z@I7H08@RZ~lwb0o4A>b<c=v9JMjX*NZ?5NcSj_d^-Y)C=+i$Nuz|WuSwf9<_pD*iI zAhR2d14?*pqrx+~y<MJd*G3y}1*-MXdVRc^ZE(8>{QJkpbwtTJH@fQk8Ux|p4e(80 z>~GEn`<dsI1GjoF9O%>2vZlq+0Hoi)Km2!gHm84%61}cO1sKiR(^Z&R0IcC7VKlk1 z4+1yraM=%@pWy|(Nxj&-pEt<E!vd6eQN1~Mt$`0Vkl)Lj>izpesAn$>g3beFVF}y3 z4!nj3yR{~cT<-4|FXn^E8W?27Sowi^L-87Ze{{6YnK3BW*NYdlM{&i%;WpTWaReV6 zKKJ-|{W^@By)MuD{{FR5`1$kjT)%%mTsx5!2F=mNyLZdJ*aG%=J<s#Qhjm)#YqZhn z?>LA)xKjk@y*Q<H{2L<-cCz0e4l>4zbi+WQQxEnlku+71el_*xHB?guC$)ch_|B<L zPL?pH){=+qRKcKxe+FQICGtUYgz@3SI`lO<?}y{f&7s5FaA=JT`}Gr|`uMm2GlWkD zsv5q#e1!G(=J4-bLb4}5I$As_KxES)?q@>AC(q@>eR8rGIr7#X0!MG)BSd+;juglD zcYORh_idU4NV$a{`S}vzTwX2_&xa4ohV>qE<-ym$fw{h3M@R;)iBd4`z9+vY-m@u= zf8NmC-{0Ra5fzTXzyI;$aDHxY7jM1W9YAdM3{ePW^l#sm&v&Vh_r@3)har_`FZC=b zo!!665nLL%yIVFw%QaK!#dXptpJ!vfd$-h**`%L8A2u(a4c_2GL}`mqh=+f2vfdOu zkbY3p1LT4I_^~W5oA&f{o$K0ou`Ug(`<WbKj4Fre{{Ha$qoc)O@qq8{mP!fNf)Qks zaF}@T4nysQct1X?>5K1==kLl0kq`N;!O@mKClBo^P#p@PtRtSL1$^D#FZbfKbONjk z98Wp1Q;MyexnJxNu$UTX_6W|z(JK3iJ<i{d0{CZk9Jja2Uc*rOo*eNMn{z5@hw*Im z_Adk&=T8RID8;#y)Eo6;@#cic_AXfBpFbCi?Z|@s=<C<TGq?82V(@bS8*)D0%bS3( zyWZLlN92f?1%%{6{S+_mGSXH^#-YW)0i6E!+uAbo*^sOV+Z;u>%xKJjbpZJJ{r3Y- zQR4-0d3U!qLX9K<^57O=WLPI}Y|wstoJLtrpIn}t!f<a+qRcN}7UBw#Y55ujlG(pb zA18i+4IRLv4;AG`M`En?r%#KQ29Gc~$j+`&DCxyygt!*cs4riZYM7%z@7F1XF89Fc zIi#WI*X&;wnm@j|Sr)l9+J?}!=C(>(rNl-86X|0jk=WZCDC2Pb=YOutG<@%>fXNqq z*u^<WOAMUh`8l}Wh#<g(;^fxkt<GU7>i~elZ*cz4q51S_LF(;c%^^5CTCU9{Kn}AJ zde*^Zxu>TkDloRdd-@sI+c(rD{N?u-7wh-;_cnlLF~R|1T}dan4wrAe7O+bn9@d|O zLF16PBv(+ZMwiz(Tz7hA(66CBhqlbYrWZ~_CVs)~?Q$=-x3Bl5?}3QS`*AY#IKPvV zX9HlAk-8{=!*9PWhmK9#1MX0(KoJkk5rH>JNfvlr*X79x`MIg6jSx)DE$ZygG(yUM zM@Nfcy1F_<7Swjh@4O)~QpGunhw%0B<2sz4Sa&Xo7W^}-ko$9b@c#Yb8u)qhLJSz* zqop|3!Nw6o;QQL|*E|cCt&Gr5_XqCb$Pq&|aVdO&eO*lt4cKy!O`FANun$crn;aTZ z5Y%1%uGkLu%bMV%I1~PTt1rG6a|e9?|M<sZ^f2-~OP8YSNbM+z%7?H0n6I(nL1gXS zYFS#Q>iaY_DzRoO3gE#R!Nl5`;p7_nWR$pm>iQf2qpg1MA3rW^xjyI#F!dJzn2nNi za`JFC>c@|V>tk(=p4*5Rb0Md16$CF)!{Tx(n~49PoV=#GHq9Yw`TqTIcukA8QNn<> z%<1x>uf^BqLsj;lbK^2fTd9IbW21o4aoF#)YVz_JbDW-Yc00FL&&*+{A>+UrM{r-o zM0tPzehsMN@r}K-s>U_7SA`TR(fWROaj{r^Z``Y^!wp|uEuNQ~bB)+YF^lC3<RS7J ze%~e+*GQ>-w)w(;=Yvx3z~A5A9zG8YfQN|XG-7RTyCVhXwrqGFnk}q7d*7p*q0xyq zk{N)`4a+8TX|cp74k1RN01#dT(A6|yLTC(04_l)uHXbF^Evl>yfkWo9J6qQB0VsZh zu_|4b_jQ?>6lm)(WBiGBK7W3@+B2299o29@kiA^aG<HU~?&~ZQ(*}x&7!cRS#3>FD z9**buc){IhCj0hnx8HsKyntxl?7#oMpzEXqTQtb&i`V?~pX;9!S&*kYoFP0jw|pXD z@OL&O4QCCM_?r3qiRCvnrF0X!1c@PV*!}b8>oS<jJKVonTdnQagHa16tg`{B7i>e9 zy@nBUJf_2P^9KBk4d%HjQlsmzx}{V{31%>s9=+c$9dPau>0z~J<MjZYpFgh;)D*Gs zz8J5GeaBiFn`vW^)1iTAvr37)|0qd<+c>3yBjCMme!q>&<WvaxZ6sS+cs@J(7cV)H z9PaLxhhYMcJ@cne>&<A(`~LmX!sx>cs_xC<8-Ogq`?D}EM>c1~1G^qPHE9r1lSX(` z;hpS}_`JKjb@^?|**|_<Z^V|eniJcYxD@B|Du;<M4}_Sywe!sD>w{5(#><0tqZGe0 z?c1Oq8{5oF*7*K@xgKy9ABtajNLOOKPwHKresKVto^Wu!d|AqrfV>=b1G2`G%=xh4 zH9$5oS{)(SD7X4ytZc#=cs*cy+ljmHOgW}X<SG*D;F5aF_GKdqyV<Y0%wXJ2-mfKx z$$NV9Y=91{W)DECsa89MK~RqUUOgNxPjW904+pP+)wkvKBHiAuABsbF_t`fpGG~Dh zyX7k51Z)5|r|^Fa8Q#zUo`Id_&Y;6YJ~ZTbqu@a(-?=>7F3#Q$X%w{DhTrZ5@P?*b zXk%z2PK0Lgw$+(NME;uzZ{V6C__*PRwJd+Y+=mj}=rA-Wd2zAuHnyV0x2j=VE$)u4 zNWJ+Suo<FGjH9u!>8tI*yzbK{fq-5xTZ}JX77N2eG6{p~0l)7ibv{JSJyj95=$5v| zgtC3;c-=-6EEctKZ)^@Q55Bei3D{)gP>Z)11BcLp4E~&n81(qK7#j<)^vldl4x-m( z!VlWMe0{w(mgS1OoHgMoU@dsTsT$<QDgZg28S7z6w;c!i2MxRN)b{=@p_Pqi{)i9z z`S~H;btff}kF^>oMLLxzZa^;Y=UH~3qaarXN>Hi}gITQP`rL~-IhU6UL(oJmo+W$H zK(14`o15MK<;|+5;s@r7q$J{es#OASdgrcfZkP^8$u$*Iur+sj;8G7cxq&Sf8}N%< z+y}`I&0)CPO<hxe%eosFwSjDEg7GLDuT4h<;@Wp_0B$M+Vc6|Ota;#unAVm7iD^rn zqPmi3s7zEagab{7AfP3GThHtRO8Vn8;QD$E{6U9yWYY3s2~Nghcn+th%es*+T2aSq z?dMyDrTHa(##{SmYQQQsMdWC{jse{&U)hMhDg*kDL5F$a>FHAMw$<eWw^&y7n&$fB zaa@aU9ysyIjS3TXRdAwcxet@01;j6G$oyUoWjXk+EVwUyx=#aUE{8(-G^l2Pr!{mp zEt$+@oP$zc4-OHEQD!zLUht1U*3{H3zJI+f-o<Hvsl$+R%80Yq#6TE_!PvGQ-&C+T zkXzNid|4k{w^F;_PP);^kcbQZM}JqmtF?U6Kc3YTSz<K!9hk?IiWy^TWx)~Nlx|e2 zZb(P>uMRCYfp;y{*Q~Q5oLl9bhC}^XTY&Laec%PriiWoV$cjGXSQrrF2V4)xtHUX< z0%*c4tZBpTC^G_&p&-I=2VV~x2dm&328UX_YunV`?cu%YOR9nF#4}_Qvgv=t;4GkJ z=`*VeO?@H8fBd|mcl$7vE?qBbnzf@K46s2p))FAv`+z+Sxr&Gv1A2G2a0@<uT*7Ml zI8II$BZ||yxL6x`a05!LU{dH1Ot{<C)xkhpS!e?I%zQN-#a`w4nkvn6{Q2`a1!zv_ zP|F7d4@Y0T%Ogeg-}-{yJLTMM@-Umr2W_k4Z!$J52bo1?Cf$b*OX^{9Mj{J`U`|~O z3c4lK=?}mShXC;F=xCwyI@fI7-<)+H4mOs1Auu++Pa1*D<!^qA=d5QlzT;@np0xDw zhRJ;|{{8f{q-7JrO_P{OGxes8P$r-dk8Ee+d*L%oJ13eT(&ez@_&$GrO*{9#aF?bJ zIye%~sMvxJa`zJZ#Am@3E*hn5h|84KIoZD`%KrGVu;vKmo}Ly2^yz5{KS5Fv3`N4o zVYF+b5D|E|CXE=_G0BHR>zjLvf6&GuM+gl^&F8Mp9}f~>(Pf>j>W1@TlX@!ff#F2w z5MF?pfsKS(i=XrW2)V&<-s*ZRa}#|;qs@3%A_5{QcS|-R=>J{jbO$5XeROo7nX~t} zSIW-pkN^9B4|G_tohtHH60MO!9Rqu`X<sn~NNpCgF&OLjd~092k!mYkcWJWJ>@9~% z<aKuTT9MoA%&V&<TEXz$+`L31`<HObhvocyc`z=i-~lF7wy5wH$J%}HQ{T10EL*xp z9Mi%Sz-t6BG4e1t236b+?b`5`;t`}8Q-u8>_&dUHEASfB>}IT9U85E}1BVI2#qTO4 z+CziUXl}|HSpbDYY_=Q*r&S44m;LXyk(0`Bcuh`nYGTsZRyQD#Th*W@x_Jd^8Zo)s zlIlrIg$7rR0ZiA)-#HBTzbnEziH};-fY-Pg2#59e-><6{W`~07J@3E$s?0OVA=^5) z-tP?yGHGNVf<`yB^E`)K?mb5(WvT+0r(TUVvjX|vb=0@gm8uH)=Tm4~7F-!>rE;3y zjInL$2+Qtk1I)X<T{_+D<-dPla#NQR9hI5lZNoZAgmqooP~(uE=jssN$T`<T6bSg| zy7bY}@_McpwEgL6@ygZvKRa9II=IKeRg#tt+l1D>gblFl1v7C3Mzx9R06Y@U6aaK~ zO`qJ~9(*f>og%9;a=Cw(!|MPolq2J?U&(QkCwC#d`N^XuS*~)yr+PYz@b&9DFE+q< zcDCF7_kXWL#+#c1t@zuw<r<1nkis}BA>478RI$dAGn{s~r@@lS7NzLxrg|>zy+-6G z0+hShtm@+io@(L4nDf2ip?MFJiLS0@VTlfsC<*NHa;}TBhb*kw5cvH3_2)f4F3n!7 z75jqC{f~dVT{0$3!&|f29sRg{)+ta`nOko4b2QlO&b_JJ=x10wshHban8w5;bs9)G zPj$zRz^@|(al7@ibKJk;>TlKI?bXZoe+xaP6hc`AZ|3rrwFX;D<zXHlAKnK&b&M9F zt!bJVEJ9F|AGSQUK{k7Ary`C}s*cNsvUo>Zb|-uQI<HG-HdJ^&ZzOLnK4U63O@ynn zv$fVjLAfIR4)virH`I3z$5wPzo&XVm5BKBaQnti!d0LP2<-5BDrMHq4X^IE1RAAPJ zw>*_PGO!Sr<)-qWQ#KC-;eLzfOj6`;VF)G4(XfTSgP(sRHYytIUm4i7Dlm&pgOKU4 zY+}@L&9Sy;YMA1BqzO2_SPz<T9oXS=Ra#p<X@y`*V4(|X+2=02y8%5GtoP1+7}0eK z!sZ|>rnKk--N7nvRa0!7_}<|xA`+CRh=ed=?z+6cU&>%~P7<QxH89L>Ii5&1-kdG( zx=po!syP*`nOj#oNylnZHa-ApjgA<kg5h(N=teXQa<Lol7Zb~TZD1IamnT<uwCl*k zRAb7!c=u_~zJI@@Q5ZCf-?TC@Mdr`H+rLr|v1T9M$8<{wDuh$5#ZPim{GGiG7smr| z69yr^_1H~!oNC+!99@TYE!ORm975{)&XrB4okpAW8nzl?Ogv(JUFIs5;HYELEu2vO z=6$H_fYrE<<LYX`5%@QoiH&`9v>58Ar?sKA!MnIv@_WQt-BQe&G1&=JyIhHK2DoO7 ze6{=D3f-I95D8jRtsQ|j8tr7XVV!8*6YyMhD3^-;?6(@&%A#$f&=%o`^K^0XI<htZ ztM}WFobQIgt;IM0`BekN)({B)+bH)|Q$tA~mB2ROQRI^ijsbIsPOabFbzH|J_3X{s zes*u5D(19GVwvlycfR%WNd<?`#p~kvP0}*!vW-QgPJp;>B7&p9geE!(Arx__kB<*g zqi6rwkaK?>XhI6mv2LVX7Z-~FXNP_mpP*a}ILiIPF(6HClnUR!2l=fCr&8ezE1kq( zCx__!?{uf>`~dbk6*xOv8oJn9Zo)I^%otQ9%yzroenrS>9%;m#Jl_jwbsbxO4h<7^ zxe10=&{o<~Al{jr-F4)t=4{Vg0?V|RJrMstUsL^m(iCu>Y5sy!2_6zrk5ODxJZ%_# zf0kQ!IkR4?>Ow7J@t!?gpFc02+QUt3aQr*>J&=9@=(2yxaR+a)_vTsh-lR>=u}yeU zSKCW?ZGhN^_~>Y9QZg~ffUwgrQnjWlCaSgB$7{MJShCds)U?^Zo2D5%68C2tf2jEC zz#QDaDk*FoAizBL9D5P{;RXpk9f-1^n^g#Ze3)wyCjpOY!A~;)wlDyD=1B<C59{h` z38S2++WT8cxlKK;Aw&EzPGP*FG~3Frb?8V3cT5J$QfBrfTUdjKV@wg8nUgjErJ7EA z$fY-P4!E|W^sSY5^3PQEYNx1MrHi$oy<d#MrOvMJq(VHg5WPRQNMr7B^#Ql4mdCLb zpq<igkB&E(I+fISyWM^@TB$eFx_4)iyB*QUJ7_2u<3<-Z;kcy6!<dj#=U>jlY(#7* z8g9F+Mm{v@(lzY%0-6Twwc`FP3D!vC<YbZkHsOWC=GRQZAX^9AWg#o;HdU$C-juTf z9wL0=l!6@DgC~tu*!!1H(!<kfJ)~+iIGs<>fiwVaJtW{8Aong|BJ_ECyO57AE|&dQ z4LzqbJV!enB89JC7kvO%e2kqCMf<&3Pfu2Yk$Zl=9>G@O)G5_)%GH))r+~C-pPcOW zD{;bxa#O7ubP}Lx5@?)CgC!4HvwW(F8fEXK6QT*zKPNu4b>E$4^qM@T+dU7?tt>`i z48#X+5)0+MZ0)VW!)H`Xa*M^*(osXt=A!jxE&=kMX5H=g>yoNHSM?r?Ke%4rwQyUp zS(i=|92xTaD&v%b+zHN}tTUyFs7Ki;Sl{34KOyMtVc~No$5eK#)01g1uAPyekD~;u z6Hoi3BC_7ou6jJL*?yIiwN!I;wE#d3vrB-O!t^N|o797A$vj{Y;RB<)rRrjNJ#M)v z1ohl1>-d3|X6k_-C8(I~=JzX|xJdAi9}5!g9L=OB=YI_~Cs4_==Sn^*qRkq8{J79U z{hUn2H9&=7aJcS96Gv1$OOHOo*fp|ojov(oVT#Qwe5zqLo8IdH$HL{ziG4YhSl!{r z9)O|f$l&YOrH8>djse3)6YLXn3|dxpgw!TE_8jS{pS#mlb84DKJUs07E05r|Nx~GU zx-9`YyqaXf`(VtM_P;p{rI=1R4hN_nyy2!Lw)@ceENl9I_;BD*pPsHs6+{<=ep5bJ z&H<ia!c=T9+TG{_*!gvSJ}y`J(3teZJriyd@^RRy_Y-3FT8xW;{Cf^VXYIL}#r5oo zVQT!teH1vI_OQDWWg!qE2OCCLbSiJ|d3(P~uS$itk=*Jxnb7-ReTEH&OX;4)&`OZg zwX?HzSsb}G3=2Pla$KzA1q7VDVF8b}X<{Jip&DL#;Vi^~VX;J*<)GT<zXg0uBb{C9 zV<0C6;PUc-ax)I?_I4Ri3CW3&mmKc%=iTn#|6Vxj<|wqC^xL<!PMp<x-0@{D#amyU z7VOvZdW(x1L^IQkT)uQ)!hU-h8#fyU3=K#<uV>gL!lBkkn2>IUWRk;opo;Gk18)M6 z^X<~EC)^1V)Rba1#Vyj_3Wlk+ULTWr3aYvi=6t^7AV&;#DphIfx>_5d15qt{V%Fpf z2*|?H(BMUNqm6T3lzA#zG4Tkd`aNOQG|${#8ILm!lyJCc#JIov?YG6aaeua&ED(!A z{##*CypAIk_b^PrOsfx1Plp3SV+fuSQqC57-fGE2bSi-OmRB>mhA_PhO-EakG&|aU zZW(4BkB^rwKcWw)t8V{oUD`^;gte|>SQh~4a(C9@a9Vn^5|M#o?^k16*4&3BSprxM zhxbl(3+!N9akDlho|`jfnsk|>=7O|x<yLRsFa0Pwm-`iX;RpcWfg7gtsx6}~d$`l@ zt$u=rQ<FGI#U35*nc0Pv+iI?<7y_lUhUlj8QgSjVHBBlupyS%UIoLK5R4a^bL+EhS zvc<QUCg&2KPUD{&HDP3Y@800mJ0OJKY7#2W0?N)oF&;7v?<|hex;3ent1MHQ*O<_+ zA}YV0sZ3mt?{ng96eE~=Eq1$wiRU#V+M(wIl%FWmRRx-^e8@X@(YS%v!p5h44)o*b z`R`x4vEcs~^(5pW?6P;Z&_mT%ts8fFxoFpU9F_qVtP>BC@?tyO3LteVj@4<3ESucX z!%q}Z#MBjmdV2rNl+-CVw4R=I<7lP!@Y5b3bL`z`zeRGYNAUVt+=H-nn)+-G#Ldl` z7Vef;6M7JGafoUhfHm?!RPO2MXz3XA{0a6MZNGe7Tr52)JS(>gBVa?Q?dk)LjQs~- z$$H%1zd28DC15iFnstsYMb}_=1RQ{+rS4gS+uJ1?V?9`JdQ>hh*7GZ<C~$U>8GDZU z*HwTmU4N5aKwm$7S~qTZVhNkp<(-KrVXgBmV8!T{d5Hi3AOJ~3K~%UvySSW2&;e$l z4O?uC79o~D3v2>qT6hh3l%_@gY;XfYbn~vQD#M25^UV07M<12m^Yh({QWL<Wy@x5S zO$7+4ImAat>#@_OqMG<q?s-9@)6;cn4f(Xn#oyelm7Lf=u48)$D+UJ5JxD$VGTEDs zA~^g+5l+oIt{Vr%=Xzh_kh~sXHCqE*Cyd6C4d=v-MvesCWac%eA&&j*Ydt$#N6Mbp z;IrluF4u0Vd$6AUN_V`6uY7&yn2>%tnyO@t)6=DlXQGAm%<ZWRXmwgGIgO1~K|j}3 z>lr=wuBiq&m%}DDJ;n;d;McTr&@tpTaGmf9p!o6Q5*hIGqS&Ub+IV{H_<NI;*pvWa z7`BpL+Pe;6$#UU+P5^%Ho~sv})N>oUI?AK7&SkJxmmEz^sJkmo^}ZIau|jk47nep2 z))K9;ZjN}`HR!ak6Q4Tl1WwEC?IQeQJqrxw!^1%r15s0phPpG<_>QY#X(-#jnw!?# zrJQQBzibQ|HP{#!^uhXh)X@}TRmRGP)3tRxwmIIF-S$Q=AD+<r=4NRO!GfC*)dnB# zhYi3%wXBufp!<bqc9e2{{<<vYT-l&(fjPg2ua%AD(jO>1o)L-nvI!^R)Wk?+&tR-Q zeaDPPOP!nS(waVL5rznn)UG4fK9yXZE0;`TY49zw)ubtSc6O-pc(1!$_!bt^Ny5cX zx>jzB=l&_nvLETj?BG$N0?spr0}McV6Z@4)4L3Gf?}qok>Rb2b1`)QkV?#Udce$*H zHk+%K)9-2iJ%*;6*dv%qfu16REo6RbklE@xsGOA>rwlG}56;;<YoH#n{S=+KXOhzO z^+N0_dfj0u^@5HxEF5dZ-Nu%oAJ&jyMj@#Q%tDWcb7{C^x83PxxiJHZ6C2J|qAhMo z4Tt+RrfI_5ubHULyBk^1aXF1Dvoo~jrtAML!~gNe5@jQj!!vg@g31yYj<LS??|1vv zq~6kZD~)C20I<hGc-9`_h*8eU8MoHy@`?pQN+K!}Z^YRDHznDutMV84`uzDd0BXc^ z@)lDG&>PiSavqz-Ca6I%m%Ec(Oj)&Zkozen9esOb8%%FlgJhj-vt`_^Gz*C4F=K74 za;?5mg^eSsLv0T0fET}4=MmOm*IPT)?1Wu_I7g~>7IaE>4|+UX(+BTG&#Le>kuKld zEGdxj2U5@<KMs1CM7^d48<6fA6pVaH0ji%~;Y^3*&atsN_DtNIODj-{t;^rCicxCt zfi^TuWuEf%T@&?dz1i6$F4cYdw3J?azZ|F<#%VU)gk`kO3{izp-(VqL>Gt;UfQ!j^ zT9E-ibM-CIQV&?ruu{~l2RKJV)_S$}81%W@V_jXX`vzbv@^@S-j;POgOJl;*c&8N5 zRf`&HkJrMmaL=YNTWXB8yt-PX(uf$qOW14Hp!Bme2xn4`fp9x0J1~Q;wCcOL%nZVw zX&YHpxAtT1<cIHku8lhtMV!iGTd6@UmBV(f!?~`37>BrJd3BziXVL?JFzw~@=Vf86 zQwaF@_usFFaDe&~{czoE-i|FdZo)>zl5=1|%{_9gxJ*ScxYtUSmrMQ@!>m00mb9ie zYnSh8gx!tj5FPfV$wdy^r-raey?NsPH)3_FX)MRa_^-bf)}nd+q-qxzOS<BNOw>ba z;P>S@;K?~N25(VmrW)kVKx0*pkIP`v-n*SJ`=le}TDgnYcWd>2cXzks-yY(?!MeO$ zKIrLb@%kPKMJWr5Ww|VuubfL+Qp_HFYvgiKXm12>Qc6hew&J0nUQ=Y9IeB<tw;$8S zX`+v7^}g0t9ZCr`38m^XTZ9$lr~5u{eUVOd$j4Tv;Z%Yx=Hd_=qgvxbiH?YY2+Nqj z-Q61hae7&?lj1<l^-Edz7tiqX=QSS#AzX7WoNJ$-uLV&3d|cr%S&AqK1K|D<zqd6U zn*LZ!rE6Q<!xD2_Lu84bj3IeHef;<g0@|uHr>>@$57d-b@S$eX-Q29fL+8a)Sm(&X zUKT@AN~gy>mmzn1yWk0I;;PH=04J}qMa`{eb;RM)n%jMC7!4{}`N-hsH2#d7w<$&e zGj!^`E*BRI1I+*^A*&B$1x+z{7<!L<_vSOm2<O60N1X%N_w^=yOY8(x+*6(>QCg2# zGmV}9fBm{<Y%)TeG=cYb4c-G+*&8;FuIW3HJw2VHRY#eXt?!zFg?RniN<Pt)tE2cF zZSP-&?d0)oDYO-~`i-p>q2UxZRsxJD1Mjf)pgSqnYBHs$La_uOVQS7>p0AA@ooGaU zUNC3!4i3XioN6uDpsC9~W-XEjB886evliT>Xlzi*oC6N02{af5A7rm@wH~H>wI_Y6 zv1Y4$3AQnjZYo<ofr%LTzNb4v&FwEmyj%g_Gx;^wK$W8VsLrjalpRJn{B|B*8=S89 zW?Q|z9(FuEf95_IYkQdvJdJUx-kNWQ1vL?Y5DsdmH;i+810$WW*7A+R=&dk$(xESs zUViz+R>Vt9>N$PjH#h6avd*EWMyUOMsb}+kG`rNwdz6<;v<0!O(BrN-_kNdr(k7W2 zTR$Uj^hs!B;)?XEE!bcm{+T17hO0jOrFUaI86fKWV&CAk5xXHZw9u=aTb-B>)}-J( zjft;U_E~ULRpaU2nWmCB42*2$_h^hF<#B{E5vlad_A3uDc}mX(oHk3#QFvUVm0N(I zYoikhq%`7|_{s>Y_bF5C@p18_5IU@x?^-uF!IoB{b?Du%4%wxxm|K%;`8TLI5BS}? zbr?x6MLpeax5ikegUx$DK5C_60vR#JZOwMA-||>y9eQ$6K|M-_7)={n!tKsHaLeu1 zAWrAglW0nNMmosDU#(EI(ajAvjz-_VUmAg?2or$MARY4Ol|KWM^YdqY0HCW8U|F`w zQSut$A^TSct0RtW6q@rJ$TJuD>}-8-Enj3o2^$LMpe}v558TYasdB;fcaC3m{W#o& zolYAJaB+S1fNVy()U1KeBv$(G&1Jv7-tB(>eN80s`*m}BN{$bpBP<S*`4t@W_R9CZ zwmjBU4eHzmTAR0K*cEj)ZdAi$oT$2vgszSnnwsFD&A%Xxwc@g)3OAdbpD*gw)_LH2 zQULGIvP^kRo5-dCxs%#&bq}X~J=E}DA^+UJ`zsoO*vP%~0!%rVhiBBom~%`OfUx-6 zw{_#e#E5EsXb{X2V{I^+2s|7(=Tp9RgI&(KU3N6<4R7om+7SkShmiw7`tV`tv~nkz zLstMYlyJ=tuvfPCnTC>-`s*~zhJa^dul-gYOqe`XX1Zy;hK?oxIH!n&`||7tV@phY zvWK1K0h%x1eZgMATN;eU+cw&C%5-|V2A#d8TjVIGGJvjJTjv{azFZP6)3w|XI21M# zTxYML((nv=okqAn8^Qgn4Zxe3YFbpbqnErf%w{uTgoQL9Vp2;zgT*(^`8QDJMeC+E zf^P7IZo3J1w1;YHG}WA2cAWBm>lIM{1%v^Z^1-zCF!9O2rvIL)MjgQp$DxM1&&Ofx z<KrR&=hVgR%DzXZYSv8b=5`25N1n&P@j8rRY1;0AfY#jd7;EwHaEP?nuS5(kiPf-~ zpMU}LzMj1HlrsBydLLR5$WdlB6J27ZyOn>t<V?gmk$#M;b?C191zwnd94b>r+f36C ztgJE{50f!ErPP+RM#6*=Lap{rS32DFUv3cPAD5F_GoJEDv)F8btspz)R)mPI3m5!( zcsN`W>O@AI@g9DCF$uhh8Ey8`)yAK~&6W|Jk9rT(h*d!c@=PVCOqikEWoFvMMl3PO zDaRxd)11Hk5jI+7D)!m2*I4`G<7FMIU)WB+zMo5?lrtR_eg3>28taM#Y1z}$9HEh} z{QdVrRB{E)5&ZrAg7d)p0MM-SeZKzvi{)m)$>mKuY{P~1*u4?X&JJM^I_X9{d0<Ai zCrR;Vg2GCi#I)gd*Aezl^Xr^$xYxSU;-&~Y&+~Oo5?@vHZJElOfPA)%K&^-KS(9Rs zLB%rtJojJ{45`s<NzS==N1<6}4KHe?AiTGYI%@)O<@b228sQS$)J0kL9Y*-em$eSy zq!&0D4?n+l?;*PBLpF@p8cW_S31VDmA9p0%RELPj1g@^d1c-Xg`2PBOE$oX9<}=o} z3<mLGfB$~XjjyCvH(sg0Ty)7Iz7bCpBIomGOoKqC_F;pMRyFQWlMbe|H!W{_8s}6X z5b{Sc6N7R9Qb$Ki1W<1X6Z7e5O;~{%Y&k4syd5n~2WpGH>rGS;tEu4B`df~JqbcY2 zTVvE}@H;cnd#K~CUDuy#<$$t~gZSN6)c}X#NZ0c&jH=_YG0q;=LBv=hm!n35d+nkH z_aJI*W#HSl!~XgG_jQZ4D=wZfNO}O9mB_;mj?ccn-t9;6$&-^c1F(jmjRoahThhri zT8Ecz+pV~$>$onfOhZDP?>oHl&ny$o7c4NB?WVf8ODlxGY;u%d4E*p}Y_KZnI2Xjb znl$9Y=}AV`24H|E57R>~SOZ61ErFSH(N<c1RxN^V;0Z&)nUt(Dh*6FafDM`fQj?y{ z1>oP+sdRG<PQ)G%?^-U&mhNL2dfu0f^1N<TGFoM^fh969&f<!eWyE-YJBs200L-p# z+0e7!pVw<Y_Ql(4R1K)0nNxttBUp&RNbPj!nrAii!s~wev{-oz6`F-0!V_{neO4KO z(*z>}nbxHzBsMkEX#v*;_(g6%&ELa=blOnq&{oVfu+_0#*VnI`V_aqNJ$p7o8EGz~ zq3>u}E4l#D2xJdbNXElZy_awf?Lzfj2g{&0CFA`+e_l2_b$z_6fkKmKIC$J|g+d%{ z`1y34w+;5?<>BZ5_{ZV<I=?Ah>|6xy>*i+BaDYl|Ek=JqvMSTISPQt!^L6Yul}+s} z*Z{5PZq+4i?fJJ4PkZ+#xnvs|wCgLd>G$b|YdiB>A+8U-X}d;L3xK)yN(qP>ynjyl z=lXh)@p5>`Ms|Na9d!sia@Zzg%;6}N)jjmAi-&_aa%sBcXUt(Hs-U`n_vN|O;P)8- zez}Rr;;60Wv~=aNty3~^l`XOvhrbw+wG*Zu0)ud_eog%u-Zva@WWwJXl+!f1C0CEC z_t`Y>!;)ke0MB-G_a*y8e31QW+DjLs(@LfkpbO^#6&*_G{MtJG<kHm*G%3SE3Kh%X z3*fLyw9R>q^iKw&A)mo8kXvI3{yRuU@FMs-fYu76`Wo2O?hSDB4X-7v<p0yCGfH|* zX?$?~Y}mAZM)pp4u@YlS1$UZOjG~9eW8xbDOyxEkfA%J6n4dNGR~|ms<?(lb!;V@h z*E&rwklE){>bqHvQy+x|TKpLX4=F`jYqBNBiA+@LxA_a4TQM>BuZSjIM62|a(dAsv z2EpYjBhChW8s0ZO*y1jG?wS!<LTdmslRJ!>dmzbMeerlla02cJB2RNgFSa-Cv?<+J zQAsh6{p$0Wuo#E26GH4xIfvY?r`tPB5#V$f9mbaK>_(}o^!C6wEx<ULkw=8!jc?EG zD92P<cmi(ZV)B!<7y$!eSeix0T73I<c<w}H9H6VKB^sjc5ATB1!&unV#cD4<rGKOY zQ={7S)xoId3v{5(^V;uU)y3Q1Y3n^j0F4?vC%_(l3hk$sMu-^A2fKO7=)(Kr5j^O_ zHD*4e9c0c`9eGUUJ=av5a`@+!W`Zj0&4yr!|JKrFe4d-M49>FAefaR|c<c7immAUf zds9Dwt0m@zI1m2#aa&gIf3tz7$wrM3nC<7N$z!yw9|#TuM&MFs(?6bvJ+2vt1)wi3 z)|i2Z=(=iCdGe{U!g>}c#W?s-8^m=QOsdnUaH^cKuin4cU=R~J7+vLyFAh*JPsrkC zqfo;khlmD_oX?dQVG=}}l3<lhv(yp#m|8Nu@EE+#y?12ba$-FvpI^!|sO;p?*~Grr zw&!|sj=Kt7ern$UKbvOUmHs^SY#0f*0q*l<O_^C>vXX&f({x)la}$h>hf(-Re}ZfC zVYM`ROU~tbR6o(xgGQ-<&L)0MIljk(JMv{+X}q!$^AweyovkNcyV8LZ_C)VCzd;kP z$PVv$b0a~JUs+*Wq2dG1-#q(m%1O4^SX-o(Nk_1u>C|-5&*9*Blb1Q>lA22}o+%H7 zw~|(Ph)E;R3-8Tqqi7{sKUn9Bs4aB>XNgZPff_vB3dJWXDF@Yb=gC|9KBma3M5U5~ zrj%prS@xbf%8r9^a<X)LwyNL*QX!FomZS(a3K%Izx{gpv!YZG{(V#!m|A&lae5k{7 zcqRqA-B`87=TMrtf3Xl{-MIwA=CuX2=%T|+6L<QvX<(x3o(64<8<@#tjyG&4kK3L= zk%~iZ)wBH?M*?^X4_9z$r#;~grF~E)b|o9VgG}lPVBcYw%o^l-A5?PLMgW$iGKK?a z%j?}WM=tB8uBkpwc!`nRGK6ojDtXTySPyYEiKhIE3AF9rf@D<#WucqtJ-lx~*{3-g zrFKsMyB$r>#AIko(YE8oP#Na}&myw0Fv{j|P#-+dQLL;jyBE8_mMtQ=##A*R&$bq! zx&DM>94=Zn`CAN!H;$2izUQ+sXoT|j_v=BH);TPINZ3D#JoJ!HB}ju)9cto->3TSx zza*!rE3L-VOgnpZUl&o2Nh}y^21r=Kyc2{7Wx1!R)WxB7#O4U6xx7naluzGke|67% zg<iHs-FLk(2LAomuge-#oRjw@w`PyJ3SuIVpM_hjTlJ_!emOKy*KNeM$V-zE=_tGx zQP3@}co)%zqI&5TOTZwMNYn^`@!~85R9JvRw>V;aU>2Hq)6$7IJP(EIqdn5{TV;It zaF*+DPg=yEd2XFr>j#aTbO|{f4erJi449(?PYd?sB6|e0#X3Z#CC3SQwimWmLi^KO zzu%f9ME;DP!IVOn4Oy?}d$2ZS!N`dU?g-QDN|R>1zwNXp8m|q#p9PUx0Xiy29)8jT zTlu}Ixs24osWW@d&&V2t?v)t&EA}g@EpJ4q>RPW`Hvj&60j<dU*`y%PkaIZPlu=Aj z01=-gpbd&J&BUs!7JVSRa&Nr{Tr>>z0L-!)r>VlQ3oD9J>pKl^v?2NDKWE_&Z(!$; z{ydM>f(uaZ+wk1R>i>u9?+t73)dMx5`p3uX@}mtEd!T}+Z;c*zH0EwPKcm0?T3>4$ zr)vCspd2o{RO&tr90yUHhn1LH@B1b?s*Ya)!RcNnN&L27Z!uf_d=09S(q)SGe)Rx4 zpMFBp@w9#jt`MA_t`%vDKw|_t<+l9*zyH2I4D>{^q1`|+fk{=S>4X=f`W$X1!Z4#9 z;E(z)v`oM;=Tw%+g1Tv~c&8e*`qP0bkYvrm&MR#HdY{#Vi%^9zFz6Cq6?>|J(>xR` zz|1pV&&DlzWg3CS_x8vIH>-VUC(^1+aR86?o+Rf182LL#8WzUkHB`~Au6Dcs_>b3m zh7-lyMrg|IeTYAOS{pM4f=tmh&)TH9X#$d**8ZhDz^43<|5(BoKFHmA2BoTrZW2rB zy;Gn0bkn7n(o93mm2z53G;MD!p{AVfsl@1TvpRAXRUohZ{CWME4$({AAUc_P6ebX? z5rESk=jNUYQ`+@3L&3=RL~%8c)d@uH)hI*Gk&3UKke3bWdV3=fm)IzeS$6K^WU0X$ zA#e2KYi_H1*M)s-YIy}vnj#lB`10koc*-=oZW0%ro|Rjj-Rak<+-<p^A`2@_CsN(7 zHa`9QQ>XzyU<!3uRBj$bxdfDMzJ^d<RPzlm1mx-thiqUvII|5P-oWJ%IErw+-eR4{ z$7|ht@DD!AJ@I|emNcIZeyN-W;gD;0KJAg;Y|`uNb&gx(Y7gWt!&V!b`b;IgiP$xb z8!J-{!$>}AWn)J;t+KcT=e>ky<ulvAF=^tJ_=IuC`<J-p5+={aIX15$&vwpjr_dIA z&tm}SE{1b^`+A-LDY+@f#g_bxObm16e0KJ10PHO>0M=-`zFwC|Jd@mC;0bOG-csM; zP~-LA$^$9=*fVky+?>1PrdHEwtdz?zjab}zijm`Blnsb>e+>*603hEq!N!~1Lo{st zNl{3h8vajp@}@<t{o6yfRk{;*>=o<zI@+7-ov;VrtE&}^TVn&8-Btk<1GfI`3dMlr zTOgzCO@m}^<1JsnC1BU*t837i0nN*O?KYBS``L5<&;P8W2hUJwua3!bPH*^J>n1$E zZog7|F@1IdfgC1c&8IS;1`oViECVnudv%kzj6%6?V%l|~9ez7cWFV(mg7%1QCB3*< z=$&j3HY&Lrj7jVGb6EJf0W}V;2Ys>*#Z4?MYLJpBq>Uj=9%|~+ys!NS4RZmZwx%pQ z0-Pq$7}PXhWb1lZ4_`9}cT$XM3W6GxTeMw`qAAPYmyBe#t39mu;KW{?26a@cuw~Cl z4arrQsicZAEwIfqa{61+7AaQ=Z_iF*|B6LsZ2*IV4!f*C^1uCZ*Wc85QnJQGHgTBD zO-;i|y)m86SPb+nQA>clmXBJO?$DC0A(k8nc<dZ{#6&yUx1-bQ(Hkn`6<mHYi_he# zl3;2COuDi$cDt|h<;jRlQHi_vd>AmS)`qkUc}tYWk}%E|4*Atr!+XCc|89gg5rNt_ zvBf|w-{CDOv`K5zlaqK3)!~@RlxC1xWg5KVIjY~U%4$<@^j6MC8E=}gZo^>ut0RMQ z=lLvqnyJ*m!*(sVCB)`LeCF-#S}qy7=-Jufdkh-_8GWzvwHp<+`>jU8LZjX^R3O@C zVZ&l0Gy#V$C30U552x`_U>NTAM_2{Cr)|GPKF&8g0^5}U8)|p}{Ee}Smep7r{_WDG z`SwmJ-YSii>RjB%TzUhzL@2IcxPtVS0INAFrJpx~CH1lQZXZVPtH7=Int32TYsO6; z9(Mbe@N1gORxLSOX%kq?a;mYIX115j=6n|U>;gZw%sx22$%=w_6S=Rzppy-V`wn>U zlhCC%i9uA;RgI>be%mfb4%*~xxDO9EC1>_~+vs+jnn#9hl^IR{_5DplORY5H`)*3m zaxr_T03IQ(qo?XYb}&iz+0=7y24g1{m11mAxDi(A@&3HB{U%mub@^Ma^!k1Kw%#A6 zW^=6`)XN@p!;nX`Ls@njk?Y;B#ve@$B3t<+ALK<as_c<TejpePOEoz>G|P}1ba~6; zpQed2<z%-KK$&SvGTR5D({}J)HY`&^hs#rh`BN#ad3M*|N<?fJcfC2ht4FjEeM?cO z6NyO~>=CWf%NpR_$|hvfv(Bs`8<A&Qq7Hb0Y2UE<oQm2AkVZ{3Y|joTA!!<jQnsX< z+ngS>XJ*nkcuO&u=g0kF<UGuz^D*s+ncILmm9K^JDqy_ZoE;?zC)ls@y`})J15Z8u zM@MU~G0l);AqhWPU$x=zfiOrovCcfeq#bVDxF-{p6*p<cTh5FM#RSYugEhKwjCJ(= zxA@vyn94L5m34FbHV?NPhmwP~H12(0<cWShZYgzXvZ56XFF8M7$W_i~Y0It*mWf9b z(X6y(hvJhmt^q|b5x4~gk`XO6FzfrY(K6D`J#Foes!7v>0_g0h(dAh%A@++)lboRf zl1|4a!($**f3qKZ=N%4ueAX1^Y&Cf}?{P~ip^59L_8c|@?FTm?hv&0ww$*>}y!=hQ zz8_A>N>gOe(iabKI+!0nzOGAu_;9eykB<ie5f9aK3MXW`yaq>GlYXNCyMjd?Hri)t z!qQgMXMm~N_x{|@gmm|26@FbBWs0xqxvtc7-`V@S)w|J{m6?ru@E(xaN|bWiXAVaN zWt|!k^{_^DGJd{hcOD-vJuf&9N4Rwq6YkYACGX`vfy{8+uNKM!G%eWJ5Y>PNXnNZ# zA?{R_^{v!m!VugTQ3V>nIt+q0gvl{@A$ti^I<K#n9tQG$9;7=9y%7oLpn^J2v|ytj zjFpCH_@Oz*A+tfiEs)D~>@5;i(fU?Jwo;L54!VZkUIkBtVl)?=b8l8h8W$Ibe3#10 z)z#s@)6;`NEJtChRk~!S9x%(bHy1Y+)H!>;M>89Vwru#S^+yHCxS7Q~SkuW<BcL*I zSNxp4K5cxOdP4wGhtm1}Rp@$ivyeXQxwmqfE!9&jt3!Q4zN4e1F8}lA(%}TJz;$;U zn$m0eSqV5q7@Z0{P1oUX%upo9qI73$!w>fHaWQla?fG|U)Dyv|6M?L*)G_1E>1^;! zUv7l3MZ5L}Vm)XwvS{npv-fK|OOa|y<+p4k-G=QM3nt`vO`FSloLx)FO>-;-=G4uh z_0*c{@n$^p2}|)l#3>e^a0*-_KuYCEwb~JOq(r*NftMpgx>d2AoT2^8!AF+bQqfpv z(0M}mr>7-MAz$%BAde|I$7QH`uQrHO*wZvkV+L<>?s`Sa+Z$%L+|QgC{G7(Y}Kk z(#y*=@(k{Ab+t@vVWYI2)vx1T`5TKq0Ir>$F5x5jYQ37H7$d??kL(dm7@G7EjbOLj z)cr8^Kv+G=d&m;rg!I(<xlUIoJdvq0{amRj?V0BR*5OIRb|6kC@Z_bDyLZK`*7mj3 z5!BOLBZJg*QUadv8)IRR%BIRtjKPuuSXsw;ZV`F@*5Rhxb1h$FZ_LA_o2h_Qy-6jz zrx19HcW;aCvXUpPwgJRexlJrnXIa70HgmL8S#Yx+YMOgc4Tddn4CbM6gtn7T!zBDZ z6;R<C4D^}v;X_V{>HNE6-<%fVKyU~+3?eXx_E3b`H@v3Lg*0zsuW~JuLb7V&FW{Wz zzRCnVIazXQ1Cos5vT^pI1DGUovT_g1PCH%SSG{+uC!XAGIerhVDJkexHy{?rWjp`Q z5@F~DpakoiYOCesC3}e6LTM`6@rVEbAOJ~3K~w<4^VTsiM@LH(^6}$BT;Y8H)Icl0 zj5Hg#F|`zZg45H*C^$c)-fD}zHE++~Ph+H=_Zob1uLTd@I727RQropR>UgVqI3J|u z%MP7c1GDgK47Y~3y5*Snzq?yDt>-oRP<nuhRc`&cmi#6RcYk}QO!>MT8Cpb^>)K)s zP7|@wVRYJOV@4`7<@3|{ZsThlUY;Y7kXxiXd8@q`PL*u5+p+6(T=>qS3XHeFlJNkX zcbO6Q@NifheqVA$=b;;Muk;rO(SV*GC_T|NARH*bKp#YtA>4^)@{CK3CB{$>pO}&0 z0$6RAYj{9}xD6wxF=<SUo#rW+f@@@)VVTo>#c2fC6hYy&OEw{OGUz)+Y?dOop*=c! z-TTfO5XyTKPmM#qC%o}A?3LQLg<1VYKZgf?m{Dn*#e0%#tjcwxMOQqGJ-B(O%~qu) z8+@JwdVNhTiJqSy1W|kjaEchtuU{7j`s2r{jeWmjYE@Lx`C1RkB>Y%Q?=p(fRgm}Q zi3l#R@qN=<n`4sFHkTVuiUD!WyfV}Fj-)vTkenJgoa7p56aDgK4ceHFT#o|)2@uNt zef;=ZNW{!U=c)z0CjP+0qbNUMtMR66!L0_LjGY?)hQX$uXGj#$NxVK_t&B7kbSJ1& z1@?6hS1TVjni$)rmQ#hwp|*e4$Q5snW4yRn_z9@{g2O?$LOto|XxYD9mobwnBsoO_ zoHTooXZh}3Gyyq;4)M4F4qb%`3?xyvx85K55jR6K$)ql0)YT6H%gzFr204{~CVq4` zq;o@BizkhL9-M2l4OuJhScTw;bTrlZ-A&jqee#qgXLaa>@r>dEUArRMrB_>(*-~&+ zLby&0fM)<Y0wi*@QDM5y<Kw|wc~FMe(72PQ2W_Q56}<I(wYR`z9gI+COjwo2&)wpo z7-rAHEcvbJHIWCcy8fN<hjwz7(aE}9U%%E6m}L2P76o<Q(Xe%*2b<^Z7UD9>+~4hJ z!euxtU_1C+igWnwYQkilF*<FQF;N17&=!NMp&w-}?vsA=j@Rxa7OVGcd6akW4j#W< zb?vX<-%K3#XRszt5o!#ll3bN!I^D*gk#V94cr~`+>v{KXiSFLLJFJnPtHX9YVvSY> zJxmoQbuCw;Y(3V*4NB}4_c&B33S&3Xg&mRp@yEgo@wsq04mmsU9Y&Usc0W9<K}K`? z;q*6pt24x)A(iH687K1mn*Az7w8F2gqw{iV<ZYBGXB&&$1{yZk7Ma8czX#N~#56@{ zD>R*^3mFU{{N5UiRn*>_+lCTikjpt%aYfeL2I%bUa6OlouRFfn5LMj6C^wBx1`g?P z^s@m~=0LoAw-&CP#<+E}bMMZX&0gzhani49ML-rJ%ERD-_qgH4>u*QJT7UuAsG0o2 zeq`Sm$M8sVm+;)3$>(?CkLAqEg6v@g5N)dFYXG2bRMKdwd)zt?q>o?%Oi8-9czr*g zK0WsW_>lGx*(*=AM8dzR2cL!U!(gM9q~Z<z7Wt?kVKD}mZa8EdMoN)HFOy#WL_6hs z4zoeAtz7r5`gtP`th>4PQy$OYQ$D|#4V#$vu_{j2%~6FFk?oCLB4zW?C*gzd-!pl( zX$UI0^a;2jteA|~yyBX8G)rMSMcZ2Yt%0!4k3F8w;GUm#JM`M!H-UE^+ie=L&z(n& z@qF8gTtLvc0`EzRW#(%&IC#ISD|G|10F!8;2PU#H-V!!2`>%A=N&n9hVV34@$D8{E z(5w6Wd7V>{r+)mnCTw&P&lb(Oa|ViJTlcU!`o0%W=WTk@`<slf+n=5H8MHO{Xo{&1 zPg}a1lTP5Dm4BS4+e1z>ARix>E;Z6RjB&@iPm0si$TaTRHE`Fo+iLE6b)}<q`LgQ( zrYCT@YCguqcs^~x=HON|WGnURKAXrtycY)8pf@}NQf{x4L1Kfn=jYED0N%5MI}pym zgI-=P^>H-nsLNw%iaJ+P+M#|WyPU^O$iMS?ZuWovXWdX!wwM7+R|s6D^20NKyboo+ zpC=x-e;iNO4M>%sDL|Eex<*d7D$mV(crviL?lvrC92WS!zh6K;##_5fw4eL&;{yKT zjcCLo|75LOmaJ!<8eD1sDiG_cilaZ5Hp?~X41j6)X#r8!*UP7H!&B4FN{E|7;8}*0 zIVs;!u5>hF?nI|M_lxrU*4zfI{eBF&jYQho6TU-i@u1Bj$erXusS7WDbhM}nx$m8F z2u6hksMsObWou{VArG`lydDzT8T4)wbv@thq$L1#vp6N_+7OCOkwu4wTAZW}EWX~U zTXG`om<zx<ffsZPRU;ISoVGDCZ{Ms*3~>oMzW-9c+0=jiwM>aZPl5ddGw}9y85VPL zvH*DIOH3QyB~6RB6z3sD?Zh9}rUOT&-h*;KOkipxSZc7V4E~I*RJf_m)cxviPXUbk zF>m1zxs9?H?M+ff7L6np^qfFJe$K&fzGKe1)pczdi(gE@-Bi<^j2?gMmSR5Z^78e; z^=2z+itnc!*WTQQ!eEfYeD@L1Q*NS1qa3U4BHF^l9(x}n)1*ypAaPlf=%Api4X=Sp zQbimH2FKlxjSgYM{o^0&5oM6GDD4qBSp>u3a8onyDc)n6&DXSS(vWmo%<DXU&h580 z$ebw*c{^Qeyb5$%n_#PX$?0)Z!GV>ye?`?L)MitdXkbzfxt<r<YCTgf)e<8c=;OyV zm}n}f8-HwoJhY@T+r~6@yQTl$2yuZ-qwbXmYpJg~@zZtrd-uaT5-3-pW3^fav@A3g zg}?t^%7C9gFZzN;)2B^s+O|D$OJd~mQ-#H?*=Dpky_$ehM|T)MV+LlsVN7rmo}EWU zVVd$|kr+2f*<o}qZl$bkJLkC&m69rb`EoEs=0v!J$TfS^n6GC7{JIZY3Gx1dVaxNk z8rGEF?55$MzNpeyO?2?TIjsJBA2@qvho_Yr_gJRZzcC8jH0U;1fN7vSAeR$8+`}WZ zOX>7s_K?mAJ+}AT5)<LxkqvLiOiE(0j|u$-U2kIt`S72AuCHsF8p|44tkb<6fR|47 zIzfp4Q?6>H@bt7c7#&g7bg8PmUN`bsk1U&_IHy7d3k!0x)?WMW0S-*L`%{mL=UW&= zr^VUit13-9%C&^oq;oJS3)|pgyt(g5MQKpi+P|W~dST5xasyBeL-RO4Y}01_P&+2s z5l9b@|84P)hSDI_p1Ib@z&HX`3e$A`+Kz0RG!wT7F*b4trff-mVc{n><D@?0O=#g0 zREO*Z9X<l`VSs3=diU<|e^7sZ=Fe@()Dx0zEZ9$XckBJ&-!tH(A@`)73%$5<ah~;P z?K_Xk!U1%i#a1L^nzKPf&!uo$==H}))yZ{EL%%P1&cdZut>PfNXNK3}l!=DjZChs> z9AW?R@H*Mnltp_J10n{H+9vAo%Ut#?YUWe|nz+rK{WgIhv_x7ZWvg$&RgWtDcAS~J z6FIn)j%e!yFW~!|n<d0G5vT~YH%_(Wtq$CwC}n}+Wh#u~p&~XqJa2gOfBdmFh(ujZ ztqg#6da;|VXcIS8*!>hXXpUMM-8f{f0kl^mWt}QWQwmVf+S&_FkGjTm#&+GgZsP+T zk!~?np0iUfu95vXDK%R&0SD$b@QI`Z_F*vd@o~3bwVcxSYvq(uFuj<Qm#GKu?D8Vs zQ~k4?=XTZ^k#?AB!@&ZX`DglR^U}VaO=-}^A*}(r%4|(z!V#Vz>@+Ea>o`4KZ~)^e zrF^!wJViS`7-rmAY@=nV$HxVQpN3;vxygs#quC2a+G7$*Q$4~{Pn;4fQu&%E0JnOI z#RMvd;s!1Mdy*6LbY1rG<>j*1iE3FtpN$V64j2uN%w3RvfA`c(x|L?p8ehY?@VP&J z9G)MkfE&z8bADzf?q4C~eHfF7gaHc^BFqBw>o|}W!EEh(qVM`zK$LsfKzuD>1wqJ! z5Y&sdssJpcL9DXR%v5Zr-uLedx{RdOln&S;PYf@6X0Ma+30FVpA#naWarw>KYxwF^ z=J(&%efOZ>PMh43Rxjz{Eso;6w>+o><L0o{O<)*$bP3TM#0!&Bx>3xU_$Iy(<=o!B zF1gm&@*E7n*&(?dmw_rJNKM*?j>c+iswaVb9`{m>1fM_S72q%y^mWNlCENWg!OLHb z`?6&<c!5_ds_F09lpSQ)0|@!ZBoGcCqoF<X?dQ*>?D*ls5|*1BQiD`g70-?DWZ@E6 zwv1Nu$e%wC-op7RU{P6F2CVEgErV8e-&D3TxarbU$Cf$DI6YlJ)#kVp#o5MSN*$-o zKLMi?U6yBR4hUhjn~>hW-|c?;?RA*%dnz^ErE6ms(`2Vf?{Xqy;e>eZR`78x-c<n3 z!0qjV#rV8fq{jaTi-X&5{X_SA(8@jyNwH$kelI-$=h!VD8P#prI+0)|;zG+AExoa^ zxDLyB;e}w>(M$va%lCMRs`%6ja@fiwbBn1x+!ly8(UzyN+8EZBMhUg6<hrHg!<t+1 z1q0Io79{{pbM~sh(;oSPqJ7ry-mM8zU^OK|b!;_8zAdu$z@8^C;W9NO1-Z=H>(pMS znT_<U235ylY@!%|2}14GbD7TN;0aVL<CC^toS-{CNj(ZYf*la?VJ!GN-P3-RkGXbO za*wU#)Kfn75Ohp2q0jyOtkZ8#0@Ki{DW<XlRIz}3?}yv||5|1YmAe5g%4pDWi+%~R zZf^yR+OPBBE@N$?z!sBeg9=}Wk5n0X;v+Z(`1*CdF^b6;_;Vu=pASk6OR@|(cTBp9 zGJ&`T%<xj>UASc1?|q2uHM?&9z4;K<0GtRo#+gV#x(`fhGX2^4da<f4x~|i~{tc0h z_mZOm?+^FaOk>vHzZVan2kfok5N<p50#)XTL(ifh73JK9oXr9Tcrb%}1+{u$+RG2J zYF3~(@D$qGqjdE1G`ZE-!~|rOB5Ky8`|9z6op@Z&-)X=$gx%<PnQ3NY$ANE2e^)c1 z^6X7X0FjNs6nA+4exdi8_rY}#`T2D{$ZfouxB+hsnB5u>3afCEQ7_rX>j=m@0m8rf zvT^o3(mm<cd7?CTShBAWwV8m_8X#$J?qAg-@q`u;oYYk;j^P7P?YMFTY|Y8#A-LER zaie%+FGLrb&GO~T;*EG9Z%HzHvzX{nw7KD8fllY0aMR6Jo2m{NTp|(6p-p4O2u}^P zw!6&@Ce|dd_PxFPbhyfr^g5FoaTuC1kh?-##`rrL^57J&=OkWXNjFe;8>LEv^Vw$p z?e|N>iSaM6+J22TO}U8+<7#PmB34gS?!XWjnh?<pBo>Eb&(k|&JwK;qcXx}#-4sQ- z`_O|-Rw+O?Mip7C%Hq~bK8Q&v^@tYCG;%swY-|71K0Ngun1c;yWp&-w^(XNKAK1<j zn8GoWExgfOcdZ9DJsCW}a?h?Kv$1W_eXuZS93rFR4mTZH{OYUy;WkV>XV#i^HreQ} zzYctXYU3LN@EQT+I+C*i@ji3uxLlFqE#@3_&Y(TL8GkmO93&znq7po$J*?ZJ>DeeY zGDTD7*%)MOYCWY!#@sAQQtv)RJ7zCd;$45UUs+?qh!QuZi8>8ax!|h#ut=Ae>+stW zN}deE`+1}kG+Nq`9S(!8VwLG4n<^Ju;lCHT*ag)EE7x=cGn)c^cf5f;S`~<=l%|Kj z*T}}>|BTk^p*Q!yk;zosZxrPNiI-!2Zf}=ph5w_v+iNY7jYo$GXr4m}R#Q5)#lSS6 z_7>X`FKf`7ycuxXhT9=Ehu7VqUAK=P*Ai!K1Gj;&{5=fK-R_V!*_hX8ZmmUUD4N$n z<;<iYv2^z@Avm0V9<&bu<qAST=%NjYjph@MdB|dW3fQEe{CtLKvE(hb;*K1Q2ava8 z5IQHF*KuI4uGSiU6YA^6BJROuwuv)8ZOG=`7!i+~g4I<Vh1xtG*#ZzAEK$OaDWsDu zc>CfDk{L`gXzR~2m%|(Y4F31;%VFXkNCQo>BK_&O_$i|4h;@6pR$vU4+8H>Sn2bi{ zyS!G`gs?2V_sfK#xg6<z<$VHMbkemnSlY@)uYDR^!`VDKk}NpH9G7W|_E!nUc}pj> zxQ5yZtKKLM^-8ko{5ALK(@AFJmVC3ufyG9O+U>;7rCsuAd$BEM<*>>lU{gWPG5Lq^ znlP^O39C)-Hc@1vIV3z6*1o`#BbrW*>EGdD906E@`Yk?)jjz38g)F?6CL@cn>)+7t zF)0&U?Wt84;~#we_19|^qYm)&XH)}{Yq8${mS?tX86KZKyOp9<ZMs;Ldjc>V?lx@$ zmM1oE^a1QXd>CZGO*fo!;v(tp=rc3R^+uPe+%xYBbpFSCRerfR1Yb{2hyHQr*i)Db zGTkt|>c$;J>M1eSTAPR=Y)I#o{4Jp#AvAztd$)VQNI#B_*78(br|)%ZB-xa!66SlY zZJb=iX!NsH&U4xbKI9U!^ZqR{lZi?9D`J}S?>68V#rN}XDb9xzPt5Z<KVNrA7EGN$ z@2zp-K39`0m<3um{AXvc8H-!}Eu>#OE1y-;$CuGygcKD|z(e{&+y~&MhN72_0G!KA zK!lAA-lDChO#zzEnO}}!p*dqJYw~biPBNIq`w;4Kk2Km$9hBV!ti{v<3M<lJ5K)jj z!MaXPE2c0#)_vvCTZ(Jh(fNOyv;2I$Ne$YX9!}Z?BePLc!*+wHs481?zY$ge-@aGr z&ep>t`s)B$u9>}MO-BBGdb&87^Yg=N%AWQ27;U~#!J_KH^L9F2fRYH=<JK&>=C)^R zaeBYwgb;^8IP}Ml-F^iQ_(5>-exnjTe6#KDYR%ZKz-B9XUf)C;b{gT4()Pr)gq||w z2V-E1>-hS0QTwsd&dtr?{#kIRARG)fk{=v~(?FrM3Ooy8YKVd<*M_J*)UpcgtsIK7 zL0mO43(YA^dk`xA>I%Wd#rpYu{kjZ*X=i5-8znW;PrCSi|9#o?Y*dIhonAy|B$x+A zoivN}?mF{tPd+YN)nnG3cY8GZ7U(!(EtXQVW1;-bd)Z)+=kwXtyr6Zv>7>&R4{s?G zSLwt4MW4@y$8+Ib4&m>J1hDp{{~FxDqod32MIyAqD&iTi)L83DMYq6Hy}4gg4_xo< zes8?5g)y8{nV979)dtqUDLgxIn#|%22v`OhZT>gNc?Yi45EyXum~S5p_Z9eHI|`aO z3lk-`gE*`&P^aS0y1P64hbM#*jnO4aD4WjZVk!`5O1e>|ps>>lr>ow3mSr=haeKQw z-{a%;<gDL*Tc^MuKOT$``_U!TjtX;{#~K;*qx+L6!$V=9D!V}laU5EwF|e$+_srFm zx&aYea4zFozc&kE3vUEp+rU*(Z<A*1nJj=nZp>z-(!iqQ=`BvuW+5Zs=jUrFtEr?^ zv>TRUCBaQOb$N`Rv*9~k7#b&>hW&oG``5pgs)x&0)<nQ)n#o5Q&A5O$;T&K>{jJ>4 z*g-)o3^*HnYZjkLI;N*ON;9#EAu{{@di0qm;TmlIpZ{5&1pwy+X1Xlrb;H0-(~#|L zF<>xI@vO#!oC0$oZS<;uJ&Cs1xlZlO;6R<2<*0zlhPzBze|ltS=wcoI{qNTX-TU<U zGhR;ynrN%d1>IhHe7wjRV|^@R#b(7?(N9gDe08<_okc)hkEh$JocGqTc`vN5y^;~> z$siP1>C~gL#-=t{?iugr_c}W}JQV)zfsghEO$7jih>m!`eg?$==mju+bsG2Pd-E_C z(yR%ww$=iFZ#12Q){xmP!Zv=@c|dC1gF3qh%Y1BWc_Tco$1FgMD$jxFh={M9eQss; z9<8jMm6Cd<S}rF=F4enfgr(CN(8K3=mVsv%xHkcnZ9k{y&)Bbkl6rkB-S|U4eOkh{ zt+`~ihBWLD@{qeT^pi-yC_iDI>&WgIcUkDqKNkaJkjSY53t(9#tZ++^6H%vLa{>dc zD(vw1?r!Z3`X2O0Q2VZaJmDmmf;k(^Eo6hxOW{&$d*fUBgR=Xtf2}<ox#m{66nw#R zCD<SJb{!SZGjNF+BRtI@bmW3j>F^Dwg_AtE8)(28Dm;Ucb);D3W2ZE(6d9a*cv#El zlh<P)Eb`IyaTLzA@_Ptiur>KTMjWmM7!H5u2;ij(Q1w@U377l$w{v`7u$5e$i?P9& zeaAz&ygY0+qvjX}6FMM^n3Cz{2}@5;yzUw%rxGkIxhHBoCTY}&H0RwSkxY$4#yDu> zw5NB9MFit0rk!e2B-DtFjo7f3vg0Idadx&CQZ_AKk+h7k9%Dy7ZLk=`yPnM5Y&C+c zy*ub^%yCkz8CX%viGIfRi0=3^52Fn^PA^JdcLNutPD{UoCpqGB)agD21aizVz);)3 z@?IAg2S@kq+nNXCN}P==peo?KyJw&OX8&?S(}*R@d36kvOM#|VPGzy9qr>lrBtU;n z$T6MKgFsB!un?%*MnU7Pqvpl5aqxpTsDuaW^i}7z4%IsUZi}gA{KMhi@$o`MwaN>h zLuiWU;ZU`-S$#o=#XO@e$+qdsRUYFBUc1kqUk_u!c#=!A(c6dxfHgH-c}$lh$E}o& z!?|Vji4F>Sa$vy~xHs@4<49aPhRd>fZTX(WbulL8rF3ex)rFm=u}`LZo2m{?@i0ml zi__Dk3i18BQh;fO+LT*V7{qyybGL$M&NrPm`+!a~+6no0)WOD~L`VbDG?jR3>{x|2 zwt$@;?uiRf)?**8*O`OB^}Aml!!%KJ+jL?N*m^#jaLP$1V<wvDs=BbnvA3Y78<HmR zQh>LPI@{XZ)3r`A-kmdPgXeqeWT~l_rIGE#@puc1D*ja>m&?q6b~R8=JxcWQd81rz z)hCPD(4W#yUG3lfYJ++aI-zE2R3T?)6T9>Vx(Wfo;6u^Xw;DKV6r7V`<)He~SY&L1 z&n9I!I(n@$PpDmV*Qk0&Ik_j_A>Y(+W8(?tf#<OHocrA=`AidxOg>>PUEj7u8z2xo ze3$CXh{Pj)`}SI@%m#q9gKg=)5O@IOpp><&)C}Iz&CVK8&+ow_GATa6Iw226iK}Jb z8c|2C=8|;$cMV3dg^3#Ns*RsT(>1dqn_8RHX5;Kgyp31w$~DixN?J5Mtpq_zn3!s? zlMn|;{^R44pAgy;#(w*@u&5|&eE4wqxkDDS2Wy1v)hyNkv4ZfCLvVk;&L6EU$mC!? z<J-5j@iVdK>}(yHzop>v7Cp0Vo<6|n-GOPae!hMT$`(t~{CALe%4pnkNk#4bpu_Nw zKVIj(ZdM_bv@T;)g0k`qHro4>XS3-6)kq=WG!Uh_JZH%^zP^54%4!X}pPpVjv8ffh za|t|#jeF#07|Y^W96+9rY1}6JxD;;A3(p3=1R$X6{}UOhC43J|y}n-P`geD`-TnQu z0cbAI!|5!zPOaHOwL#p;uLyPACTUevEA0aMxuL={*?glGIhd};Ufl!{Z$NupuE$`B z8B{I*b)w5eoZ)W)S*>*s-@*AD)KJ1#PyS_H(QWW&_MlC9vHRL>0PJZ9Q;B2>EPC9y zLw~0}b`<a49m=2Q=SzvyA$Lc@b8vZH;6c^7H>LnS;%n`6tG9Y0Fg8;spXVqz#WAsH zD;>mG^GrRhhmKoPL#tr9IzYtg^FkB{Ujjgs&$DrC<m&Y}Ie9h!J{<modSonz^NRkR za~G^TLJLOer!b0=M)~;--)i+-QXv$@suDIq+L@;1@-uR&mU<|`VHA&!7EZV)?AQpA z9{u^}GI*0xXq$+vGhj@E=V2La*23Szlj!s6z)OdZfM={J2F2@aY{;m3>D6vuoxTjH z!c1cve8*gw&Bt0e@^bZN8sW9Yjo6ARCkN0f0FI*juG01XIyzeS3b<9B$PP!)%FerY zYq~J$;gpsa+~n^}4WI7)dA;|q27;PDUcL1m+-X9MXYhG3OlbHO3SBUc2ii%yZcK86 z4WS^L-FdD>9jb6_?_$7+5~8sU_nmW)Oad~!`19w(-~aX3nl_9<bu-o^?PO0_jlfnY z+Hpo|<f!NNc3~v84cxD7*xqQzc_-<EBbXi(U$>1f_*3iR8~|4@JfIT{qL{(YpNDH{ zlte|#e-Dz6LH_r@4^bCVTkiEIwd`;lV;RolTRAcwMfA6I9xP{OA=w&Vq9V@4`S}40 zbB8HL##N0M1JFi+Ng<fuQzEHlcin#)^!Zkateegct*A8?-*Q}R2BH8TAot39u3G!o zdmRvROh=x2$vqU(*XxF*dO&}<Gt%rWPr~VIc-U22c1UX)b|unXC$~{%vzruVF_AvU zNlR~QQg5R@!gV*<uwh#%F<|Q)+(777WyXY{KATn`+*4oiQPbnyNjMGkAbR7#k=hcG zdhc;>7>2D0#k@Ze6ZnEZpM!mMwKzJWkV(ax<r)v#R8uU=3hPudi>-7+_)f}gt^^Qc z{X0KAi)8rAT5d*&O%!p8LUR()1+VueuXS!qt;H_4suo-2vZ;FDF2Jc@qPT(^ZXlyz z?5xO(2bdIg%^+g~fiX-a#)+QVaxo1)&Bu*5kPq8<d0nP{1xG4Vd3Z~oz$(ff9J-Yf z08ry>T*9?@EE07tKTcq%$F!TT|L)zwr)XO1XXJ*Ww-kq)_IPTy8`}Q*YqbXZ6@73D z#hrU~v-@|1Os!R4UA>mj@~{HWZ+F)nVZA*OhKG${QVk>{4rN6qj49A#&G#HWzqUGS zJ^+(&i2rX^<kzpu;inYW;gfv*_S<gv-~YYa{qvveiAJ4<Yi~&RW+Mx%{@1Td=v&(D z?d_U<$EJ4U4B(|{ztxYhhcqYT`}adg{pHJU_h0|DFgs6A*Xq;$`toJz6=D6{WM&?U z@eJ00eTuFChOHflC#qQh03ZNKL_t)pHhkW9RDFElxW>|x9qF3o2);poftivVOd>h< zEZ)0Bv<i}%00v0txfr}RGXj`EWYQSG>4y)y{ma6zN!q??$?uLB@&@KcOMSE@n!NYa zEJ1?<Ar}(KSWlCOxIs`=Lg;CRJ;nf3-ZI-h)LXy~fSaYg9O{>nT=31C-66ESV~wnl zc!!^j1bUx+20TA|dn3eb_W%6PwWo2Xr&BP~XT3irrlo&w<KPZd56#3Wbg!JAF2<qK z^=8~v0}+t&Eqy@)h^8RB_hAA#J<^;=$3P}d%f0!og1N%<@#C5h*3|Oz=QTiUYH`b< zu2fO3G)wf4Kc1<;MkBjG95^GGlYobtDH{4UFK;OAgUQXI!s=lL9$P)};i-2|VkH<h zGs#@;n@UCX&=xNJ-(VVzn%qC`sXOi(w9)b*q3xQGxkFDYP+`MDWX8JV8TdKr!&YOC zYvT^j%btU5<dlu;rHqA#y1hNDC3upfFYbqYnJDf3`@`XPKFj_vxyr59O$};LS~0PK zEya2&qjleuo(skSh|F`uFkW6Be)sX?;U4%so}S?#2E8G`0or3U{Jt)s+reR6*k||m zssf-S>kT@o(6m#|V8!L-x~+8Ub#(H1qna({NjZ0SYXr2&YrS8?Ge3-$oV;1wW!6$y zJ&IY6tU+;xsT@aSVlWbfmIKP?P(^6YS~l7=e6vvi>%ign8oO=O9(bZk8-&6*Ikfa4 zaOWj7ZPu756Hde=lwxM3*?M%6c7`R-fP1!pifjDHw3)0%iU$B)$~b?=dvbu>WYsmJ zoLp+WCA{4DV-TK5g7<YFf~#y?t9wM;$>`7J-t079dI6?po{pQw%R1k5__N<1LKHv~ zKKSu*P3|DHA!jEHpS~zt)Ed_t@i+X(&-@w)H!W9gO@uYGZxX|NtDI@f095_MheZ&? zQ)aHNW=U}yH@9cE>B;wDRb7{b3Mkqzbrxc4+m(g~12DMf)X=KUEmzT6&U+ZZoNC;a z1-D1T5L7&~0wbkub7-uLMCAm=WGT9Cg5$9EnmM=_bU;ndXCTV{{CQng=B$_>;FJqs z)_9qfuR2MpgKmtkVF;fqdixb<*F4oIxe<GZd0qfQ7_5x*Dl@yhV5LbmbZZ&R#1nT9 zI*%`6>;p_*?VjoX&DYkz5eq~ZxwfDQM(OL<*YsZZ{<~ac56#AHnnsx1ffTN-vk@&P zUQbnnXm93Pv+Kw{7#&Ad9YjZ1uCBNQ=~RRbO7u4gWi(oJG>U<-LBz-bb{hP%fvrxU z!)njEu#)nWDzV;QzAS(wU55NE5fyvFe>V}{I;{4`kApF(AWBDx*@>j0w}1I?56Gw` z##{4n+X!Q8)0(I?{T+Ur&{N<7^wfeME(`KE)LBhDasRi&$Q~$Y$8=_45YTkPI8Af< zEb!Ro5k~+X%|ry?)W)9Qk;uu(YmNY;p?jDKk+*26+?Nl*RCe=0^(e4LTs?H80B${Y z>4|GMat~<RCK(HKxYiC}%iiID^E;wco<k|&_5N*<<~i_wt^~RFz|X|<|1ge31~y2R zs3+Yddh@;`_jePWBeJO*6oas(AnoVYs@U`O?^h4m0tk~nJr}gqQfYF6i8GBD__sj` zhv)!1eo$-*Q%)%-QRdiEXf?=GvIPfYYqIOaXYl5~yj+MGrWAX#lvCijfWDp%S+Ph) z`2$>oyV&Z5CNPrEJ3E`(t6heg5ODMyFzi!LzQ=sKRAvELhp?6Tc2wg|Rl3+NF4pC` zIvWIMjIi5~xBJpKluNI5XiP8W*}J0R8Jdm&jNuq>z&M-x;p^oZCI)U*nWiex)Z%XK z#OKoJenmPV9|Y0GQ(asfyzFnkEg@OkhO6>Y&jK^a-iP<FWOhA<K`B2Ju8jw0k6R5o zM-HCs^6;>fl`J{l)TT9WlW^$d!xLddjW4iXU%o7%E?&f1`}L>>oooo%VABB9dW#7= zA+qsi^#7~gOse$wxE>2;oqi19)6){#Qg8O)&}vH%5@Q4`D(lD%BMMv5ib|dZ@%{V3 zK>1)z{1x^GsJcUcdvYH<umY!h)e&s(Oe4n$wC+$@IyZSH>=_Si?MQmx>y`?2dpEv+ ze<lJ=4Heb^;Ee!qnv0v9u=O={>On+dvzXvuOUH)dJ@~mdam!efDh~@_Dy$ojCNKNe zR}a?X<8q+w`8dQTXW*$=Lx+5BA?0%>T>=2Ut$`VQk=}Jwx=Afg4~!<$rN$a}cAqz- zy^LwMjxId_h5MeS3$eCNb)5T}4G1{M`jo-xs?%1xwnJ<08Dz_GMy$b)A8SGxzccu0 z+<@ygo@OR3=Lr&#@5MbZ9#3Xz`mIu>4Z*<b+`nq5D3PaOtHT!yY52>iey8bn^FkYS zf3;O#d@UX%VK8MPd3a8I^Gwj!dORb?CLwi`xHRa{9|oR{01pw*Rly506s-$yf{Q(( z$uHm}uGu%|GpJO`YiH{D(75B!sbCRx?;lbaM^Oa{y^jnUH%|u4Gm;P1V?BJ=*^iEv z17<^hbhPfKbmTL6(K<te&UTm+AlnGi1Uf$#j5_BHW9x3dGHwl2y8p(_M_WX+?u%!` zu<ix0M>it+<Htg!?1kCF*t%{>BExO=rgXSmf(h^6>B1&m+}^HPQszK+5QZCMTyOSk zr*5_h1Z@87rjE`vK>f#`Wwsb7KO5uo=eayO&7g1BnNx+fv2ImYI(erdWCvQ{0l8*2 zBtRZOufuhmgGX_Jmk=TRpZ~My-<t<NMKHx`%EutOu&6BS>+++gQcvq!@mYI#ONw{0 z(lpW9XJd*sy;iLPOmi_^?J^UV=-3<va}J*O-u~sny#c0XkS|{j4+Sq(l8Fz;?d@7> zdrME>6I8e;Kimp=`scOqY}$f>1~EaPEkNQ1qOV_H*UbA6`(<N*r`hdZsFp7e>C>kr z1#s71b2*F`ueEZ~MEl(WFWrz*)mUQ+UZW{z>|yXYw(9Z`Nda!TM(y@(S59cXEtuB^ zG-D=^*2)**%c7p#Gm!<Io^F+OUx>r}Rsfvxb-k*k;9e50k2DrX>M?STf~*?cXV4B^ zN6Pm4RakBr{v`#NYO>R;IKnHyw5zM77Rp2RlplLxfINrlmZ-w=x?|2(59{T_TI1t+ zdiLh-1Vb}`@!jt7@`2)d%^vF^XoNC#%bA|)XbNen>+5wbd81alq6VY!&*1{RY$WGr z$tLy&+Zt1CLs9SJ9Gbtu(|8yLPQvwdA_a3bex(gj<IO#p5$CYoBqIxIbgB|*f@!j> zD{E^{Y2~Lzxb5bv!L+d}I~9-I=*<yuW%-D*&|mb-&CcJP0MMvg?N@X{{$KHd)~ZeL z@hD>+uqU$e{yq>Ew`dBmHHyl)APbEkrB#3Zpghc^B$9@vMQqt1o;u96y8P1U$M@h6 z-*XR0wa(AiKvwJgo&0O8#CfqjgFoBG#U)zjj$6%7&4;^*3O3Pq?p_VRKkCjHV1tIv zw_U2U*h%Z(M2wVwi2!*IK8x3~{!>Z?ufSw46W1m|Pp`Ep^o~Mp&^xuLs$1m!o~haQ zDdyner!WhRKRNv~Gc?iue)R%3aad~DLWvg_OTKMwbw6-hEhNOO3bfT~%mHq@>C^$| zu~S47Zu@LL*bVDL#fEfwj7`mkb+lsIbms@SisD&yPfts!um_EBvj)Y3G69kbEV-2A zy0r7>KmXjW0=TQM6xxmy<GJ9?r%uQTTVYm!^J%YXb6}?otSyF^<`>IfdtZ5snkyA9 zCs)*#AvG-#PjfN~+)kc;YRZ~AKWRjB)yq9H29hb2USF?UkX>o&B&DgUm$lxnkaSNp z#k!gMPg}0N>fPOv^Iu;d7853$^Jmfrboo5!0?**^p7f$4zP%B9h`32x(1I;SN&zA% z)4636u&NJ`ToBa1K%+NOC-N{4k-zu#whB>?gE2Ee2^)=``AI40?ChX1z_no9|M|~@ z8nQ=5xr9aO)%*m)Z?oWx1%b{R+wf*RJ6pn6^BPR6^&#$_umn7x=VylVa#x?PpKF=K zKRc<{>FT64Xyz%7!z?@Rhbb!XXH1igP)#DmG^4N_6~J{5Pc0$HS^+Bzz!tXSng}~Q zGvNGu=~OEqL1f@r=TA?Ix9rVKx`FMytL$=X;Z7|a*7kiSk6U`A^IIds4#Oru5CceE z+ns40qi*J#y|2L_>&e^j@%bK0g@N`+Gs~HE3J=!^7J`ul*CCfKr`=Ik8H5v6y48(; z|9cTrwb)4kvx1g*@3I<AC3d&t_4QI({_*4RGek!01$!`gZ(oN?qeLz4x1hA>_wbwq zu7US<Wa8W(gTk}0(o{qLE$oK(?(fUzA$od_wDvED%$<5}EGfziTgeuTsDYM4Q$s5^ z-jE6F#Oqw&xAe0;m?g3hLV$7RYxCx8y5ah2hisVhj)G6iG)9FXicBDoznm|6_~}IF z^i-coeyh2(LvKn}#w)7d$eIvZnxHZbcAeOrQ>oXaGJ{rb90EK9sH+}72hAa^@6|u2 zWXN7*)}D14-@o7O{_&5shzR@E2e<TIH~!Fy%z80PqHNLPS{_&4QDE2COEm1kx9%5d z`OkZmd*ylo2K`z)Zc=utQ7;C-xpKo^mnYm!<*u{K%XOG+DW*wRzT>NE7(6R|3#B$6 zzha9PF2S%hy0Le+?!W|mxg0q)+*o_CIsumdayOq_OA%i2q$SIe^?J&>G#|YUB6tQ5 z4sq(^kaq&ey57ES(g{a*o*C)zyoRH~ERN2Ys>{J+ji?4Wb);L>ic_Q;JX=UdM@P$G z6L*R_rDX3+It4b=69s$XXLuj5Kdj4OfX<+GIRzb5;x12Q?aX&Dz}%h-ocqqApi_uZ z)W0K$8)i61`toIcBh8~)?lH2;g7EoRNAv|<$0zS}N0{rxRc>`&)zVpK(Km(ol?K41 z9%IgewfQ<C&7+>DI=q>66L{)>I|_P>p6%LtHTP^hZIn=bGH_{j+R4d+&QIk`gT|E! z#{+{SVQ^icad{D3!g~zEV|>NyU_p#(Kl)nw_fuolRwEAl27`WnE}k&lk>`z&xqllZ zU(+;J&ym-b*2MF5?O(ziZ&dT0`8^?oDW9fDc33Dm7orb@I}`HTvJ(-8n}v}4@@8c- zdJGne?~u`k%w@~1W*;})R86;F6=A3E%L=r94><Xjtg@Fj4cow9Lq8f`pv!Kl^HRyN zXP(rHihMV6n>=1;Dmd?l6trb9-x|;f(C6rk=*i)<m5IP_Y+TB0t$MEyYsFuQQ20ET z*l-kHb3ZfgqjKg>R~(5=6NXx?sJwXB(QU>PMpeB4_2TSTR@0u|)){MB`lJ`mV3E;Z zTg0ii{;kbnJfbyI!e%#@SOL}x=%x%4l%~+v9${hy_P_vedBz=|-6jr~WKK@jEw832 zyGex4@{9nU$<W?8ma%+j%gyJyyk>9d;7u*Wq(K(sK<Vh{=#YQAfrW6s6&sAWH}Mo& z8*eFS2cXJ(n66w*!$#Gd3jW<(dWxRfIog>RM&fy%oFk8>--;-<(qDfM!0cWUN3m72 zl9s@JCGuzy2Zs`Fmoz#JwULsUnN#k%^L#sdYExXBCUny2ma*{WZ~DT9v^Q$_?}KfF zR)DMJBYPGGa#{zkULOzOjm?-SPl0LvNBUEtOM3*D;;1cqjoU0mE@%p_*U^SzYaRHk zjx=vd<gr?go{o;zio;G>oE{-5hXJ>SAtSaqxb7@<MB@H=hyG?fT4&IEv3t{{yR9^l z=UMe5O4Zlb>r$u<t67z#xRsLUta+v<X_`AQjk|5ozV|QIamwlW;z7~0fkk03f>~?M zwI?8t%{&craK73>Anh76=vWDAhyHvP8>W=bYTK8g)<z6I*HMML%wRcU_(*-Wn1e1g znZoOcpx4`bu!!r_RAQ99qA#9EjFB;AxO8uWdX=1FO{~~+f4|7Q<5}Iy-`ROzzb+n) z!_XnJJX=%Njl#F2`xc;VOsJ8M(?}D?Y~&qv)p-<eYD&Os=*%o79mnO=aB_^l|Gu7G zu+=c-*FC?_eud%^raJ^~bYQPk;ZJW;9yC_mBn6kE{CPbf&V?o*V+*Y0;aGf<i7%DW zwhEDnG&VdI5^0KZbF<W>IcTMaPLfOwrMKFe%>=UoY(sg6v~88O)yHUa9WHA+s-jZR zh8Lm>4o7DTFz(#*0FB6Rfy>?pzyJQ)^R|*iWYOtNgv4%hc6qqUJyXZ38F^Jl0($^o z;Tk7;^VXcl>Aof-H4Wu4hXUh66@*f5uiMopmo`^aU_T67(~4y{r1AJIzBl=0YB1?A zOy8IRo(V^2dBxW=DcNFZAht|ViD{JfMn;gG_Dpqh4+HJ62R!4?pX<2-<w=;UZrUh3 zgQ?9|<(!wpfYIt9n-J;AgHOD7Kr4(yD+ZQZ<NjTiY<e((Ra4oGsYj*O+W0wwVlT5^ z-8)nn3g#l=?J8G|h9lChrz090_(`v?oQ*nzU(GMr0#CV5@TD5c$?%+<?DnggFyMs6 z1l`A9>&Am@$!*Kpbz}SCIGk(4MDwePRJMf2!-8ZnHTtydQm%GOdFEYrsHAZYI?1^2 z*6|&pnUO}+RAY$o=s*e!abQ>+MmnvUIO&s7@WJe48?FPej}-9p=Vcl#wRYH^?iaYY zSPU_qsvHIC+XmjI1JQ%w$dTrtmSvk}wk4Z?ldgCQvG*4uCe_Mp9^aDf);^w4{V5t~ zGh0(JhO!tVX(fbcR~X7R<k%i_EDNEINd|7(Fls&-1eY!>hwTmM-sP&<b|KoAzGZv} z@jC#7YcaKSs{0z`Y?t5t{W9zDEkkH;#wPSoNfT1)xrm%xy5hAw)UCE;8?X{t%)sM* z-Du<A{r20kH@Fv*iQw*A<Ikm1Sa_r!j#Rw)J<qWcWD~c*6`~TDwnm`4bZ1qb357RK z<gxFST5nZi%WzGj(=l|Wwew7?naLhScn112id5Az^f2CB(|(+)>QU8R<f7QNW>cxi zhmyS9XtV3~zxsk0;(T!{yn2hE!veC{1@X2(B760A{<+59DZj0?PnO+EE<Ct;L0Xfi z`n!!rMMk|j!4cYE;}F}rj9VCkBR-yq8;J<r4eU|N_WexhrRPvo%4`BmeSc7Dw{mM6 z38!L2vmG1GIe_9!iezp;LFl)LWMx=7!m3er4~`qe+BgR1!Zc-^nrY8<h|lk{f1xFk zvjN!M-Y%#=U$?jGkxMKjEs91W?ai5n<8lm&zSD38^9Clsga>a#+Xl=bHaV(2jE9?c z;Azv7&Ml-iY|nRgw!Fub<~Z753M~0L`Em`g*O$X_cej*XyAeHU$i~SYmxFEngsJ*d z<k^g}wp5#&FioV{0=F6^`Dc?BpQ|FjA}3We#@9wUx`xH0x!5-b#pyS&#z51QO3oLk z-J%g2+}qN{@N7!f)l;aA%Mle?DXD!m&&F&2qH|tgq~T55R9*J*Mwq_Xd=_?e-QMWC zxd-)}G|%86_)HEHc{n#}MqJxNyHT5;;T1lAUca{aJg76j<KdTlG4+p61Q0iXSZ9v| zV8h6Uq1s`kAZze*xXEAP8~E=y9Bq}oyIauxVkT_HNyL={)BVGJvk5UeJf}bZTuY~$ zl;-JGW@b($yb53%1977euV=4&RDB}~@D}#mG|sGh!>LfC;pY7H)~*a0r1!LE@D<jy zLuH6YT}Bj8y?=EP)1XG9rPYC}dJuo>six$BM)!}8m(D`xdQ;O18yQdOJgnoVJ=YCm z0{BEe)y^yDohD)!0iHY@8-#uY8$~k@r*fLTo)vf<0bO6Ox%aI;&{4M~VCuXcg3S5( zx)xs#KfTt6zc>l^;E0d16h@ylXPRw=KKMEN#`o&h<ZALca&y<DOOT~d8vr&o;?eXt zm;#dKMO<90m61B&#eeTFPir=*%w3P{<1OlZDfvF^zhc_jxxIU*!63XC&&hwr0SpM4 z+rL%o=Dp3?wt}oxhn#-6JIZ10)L8}{cWTnGzJ2pe<^#6m;}C0VA9cCSAv2x<it<#? zpF%-DM6Y*=JuRIX44MXkG{l}b@V@S?zH@Ubz#$mdjUlOYSHawFKn~;8)dB|c^GRX4 ze2@;{XnC+oMi}vUzZI{*+JF4s+!q7U1sV(fy_NoK@$?;SIGt|!(F{f7Q<IE%v58`O zKYu=hfOr|J!`K2jgmUTRe^aV)9$F?B5rr)#*K&5Y7zc<cMy9>7r_W|FFE3x0wQTgh zd|87{7@EpKne|7x%j6o9em;GAUBAY#U^w}=)pe%2@7>)&Vc4Xdf?5;*+c_d$!^2tJ z{z|!$>j0wwuh?pXex5P^0`hgP!!-GcRIWzQB}r=&l-~hZ3eDX5eLjeU$w*|=MohF( zem)!w@@E`WIUqK$7z~q&+P!J|uEYMyv~O_-rk)4(fdStM{BDF`C-|(zupi52-e~sv z=D52g)3IQ-*u3V;mvsrz{rhh*qF^XG@2N$c(h$N<tFYK=n#EPdmU|j{O<*SGO}LbV z@tmqF9duITS)vuAwJt7}5S|bc;E$iX?B($JSNw-9dg=sFaZj#DxHsV~Hk+fH_KWZt zI0RpxwIKjmeUJ>Iy2R^t+$pb~>hzX8_vcS7)gBPrh>J+N6Ru3Z-qM_4KX~|zzlesn z8nhhU)G@N=s-o>*d4o0sag%I3tPTjHb2{fJm1Ayww8j7r1&x%l6=5b`nzqnJ03Ne! z5U_{%>})N_(30k)25Sz$#l>=OAj($eWzDR=)*RLb0Z_DIvjsh-keKk4h>62Ql>l(k zXg?JN(gC>U*Vl93c#Wp7tB8BK?>1(WbLbG6=C@nm>2zo&QjQO>->39rSCOnz)xBYc zEZBh4*R6&rJC#ZA(QCl(xt7ULfmciprlmyBO?0HBzuUi{ovz~o<V=>P4|j{`P}0m) z*YAa+t>-pcA36g(4QD$q-V84vqKk{={jH}zS<sF_2Q|hD+as2u>OO->_S~IZPHn2I zFg2Bc_&3xu@^2+w&lYT&^y$<3vyJ~$rknp``0yx9_c1%PI86y-?!arRIdx-B#`CQM z*9f)^Z!Iq~c8>iw=^l1$+pl;iQzOs>u=_l)Z=v&Ub;54xSxiyhmMF~6pS%4^2*jcr zY!EN1l~(Y?lzUvmw>-F0h=wG7Xs4%(Afm(T*5@l}<#tX#RPsERjLe?1;_z0}22+TA z{{{i=O$wUzL0Jk4;Av9#nY`Q7_)`&8@==U-K{8W~3m~~(jzJ^3>$TBXg=#Hc*26~Z z06LRlixW?8{UitPN=nltmqZ<{U0f{Bu5IlW$mH4JaM~1Qr&g!271{X=OtU5VT(FG& zeodU>`NNelpv^#1Q`G(S_3JwNp3*Tr%l(URgYz^47u8DRq5u8&S_hy9++n{-5fge_ z<aZKp@I1QGaSaGjO0rIz7vF>=6Pz5ec%Z2PME@NR0z*jo%D^Fp!rg%$L^08KO^^0x zK{@WmvsbdzHpb7SAM5r(W^x0PBa8d{CB1Viw$qGh*hPCK{ChL(EZNoe*;YNu&}+2m z=jnPrQHj%1Foms1)|iG5+!St`L(o*^t!Yv4AFSjvReC%j+4vDWj*k8En!JVO@ZXOg zyZ!EuKh^^weBqX0!t;5$uj`asTx)*5)#&inI4m}4%R8|7oZ}f};jlSUa5<;5<+jol z8eb4)uyHUrRsA|}2?M6heO;b%wS)U2B(=h=D>YjYl{b&)EpoUWW;^7!iVg48VI`Jm z#}M(eDS9gK*`_L!dz?zSThv6B4(l*2yfYwdl%UAE8_>;ImWZ3E&&GIc=qv&6hN-Rv z{r>D<gN>E}_jKn*hW;7RtL4i$18-?SR#)PM2hdsUY)HQuJ<x2%)@@BNXt?9vbC+yv znI9jQrks}Y%p&~w@pZGqWCdOGEKS(@ay|?W-HirMy7a~%o}QLG*w<|jLdnt5l-G9} z(AkaDwQcxX-T*)2Aos9Z1qqg5!*7gKQGRRhTl#9N3Q#gm2{|UlOrWgq`Sy0bnWrws zDMar8P9J1f97^i3WW7^8B<YDfKYm<h8JICx(%hsr)KPqt;SE~XNlw&tz(c^^E%BQd z3;-rx^IX80NuqnoJ(`2t!@;YjZPdBHNi(HF8r7e;1ztS<)_J+rcqo@axS$q&cJ_Ld zw+THQ8fwXSJ=daLlSP05-U1K9A!p|en$dQ9yH=l>z#K;~TNdBB{WMOx+;f9+?oBAk z%0|kD^WkB^ZHlD(nK0zlo4Bch%X!69CQgD$KE{k3Ik~UJGXz~eH1KTf0_Wte&CAQf zfwz>m2e^Rh^BSiSChZ&_FPxDYsygAx$hv36<zN$`s%E6J{C<VFn{QuM9QE%lHk8?f zrm8aWr0TWJ>)HZ*e!kSd8#X!GaCycY@^ZkvVWAVVAb<R^KA;$;dPB3rAe5MORwTO* zu~>^e8(!3mOxKYg9@dA)n1&4)>jP4cw*-qY$Dilt$v;zSRxbmfXyalv8q0jUvf+I; z@l1IB){pD~umdrPrX6%P9#*olQ*nQSQ&vluV{pAb7`;xJY6Yod>5XqV@8@rsO#!pg zEx{8X!@L0-E5`ac_4MDr;sG=~sHS<TmMp%-3AaL36Fs(Mi|c*+Ga7)3+E2{D9;(*d zYkZJt-WnphRo-#L<NrK()8o7BX{nG*RRD|{kr#RYet(!o!^Sgv(;b^^Ao5<kAk`W~ zN_`NfHH1V}{yfHrwRGP3tFo8Z&x%G?dM4exzkgj(vGiEIphsX$b$_=TPt@-T(RZ4- zCG25uOP-EXn7Ri!Lv5A!XB6}By1SCYDE2Ax+d5}4xcgV*h--CAB{Uz~NIV<DYBl78 zvM>$+03ZNKL_t*Tg#JE^(}AmGmD_D=h+4Cs!p(XXlzS`INvLbjwN=mE0y9~&j%bGP z^!@vyOIUrxwuusslA5tzL;z1uYk<@0z!p8>KdLoM+;J(?ROODs;=W3yMSSAuXwewF zx>~9RY<Oc!Cb>{SAoB3DaO<-VM~P+}FxG((-{nNFG1uiGo6hy818%&FTz(qzIRV(@ z@00v|f$ydc_UK!w_5@&Cm%(XF1w{oW?pGaalVrAYE_+p5HHJADOd(YEY`B*6932!) zG?C}=aoK1+0nIxy#57(^12Sd%A=XLNgN6R`W!*Xq((jpTL=PSTMu}-_^Gsia;Esl> zdk8*&SEKp>CjsiKfk@ZmNsZuBcxYtP6@yr=<Nxu;^8KHGE<hg}41<Q&f;s=Jn{kc~ zN#Wi+OVSt+`VuAU@Hcf5q)U!i+%dQnoz&P*DbRFq?MV$@wp@rKG{#ngRO<3prEE(l z(0|MNdWfqlDue9Ze#O$7K8VoUhGGiaTQi>oCb#(3TVcFum<7gMKQ^mw>fY)IEndeW znJ((cqwG10fONqL{~6;ny{2u{O_}(afj~;0yaxvm^<AeB5JyU4xw%;k!o=*PiHC9+ z9+pT6?gHK$!{fGFa!X@BPRH=p{NJ-008I@rlVVm2pv_$lk6|TFoss4X@QmM5v*PE* zEeu+BYE<dSlajT=_$hVnh&;;BIHU^gwDLjPS7-LdM6UfR^qQu|bQoa|tq)SEuv7Xl zS-Jksi)XI7+fU0FbBigVAh|c!etEep9-fLw!yDT|A_!@i15Ri~e?1<Ee9R0=8&wW5 z(TpDq8wl=$NiZG{hOy!r9XYga&-)NL*mIW2==HRbBSN6;RRnv=)BQf?&pV8sj6R0M z+yI`}BqdG+*0ihM+jA>wxV;`#fQEvO(J>{M%F|zeEm7G7_L^K*x;f9#U47Pnumf=g zseoheyPdDC4Cs9AR}U0K*_+uM`?CH#AhLoB1=gmp#X5TB`SWkrWjlA?r6Xv`280NS z4Xk|q`Z`}PjoE$UmBDh^?!$+r6y(ooxpbOL%t4;G-ab^WDO+!lL&W+!_jXmryo3Tq zZn82Cf<(5Fd$$1FctPWJX%Qpl=YM{_^qgR*eMp-7w#%;R<Fv1M9~)l!H@qLGGOM4n z_a{W1PC7Q9#d)_6xhG23csj};a>E&wGzR+>iZh}vj-(r%Q@OB6i(m8n%=Rmw#J%{` z72(uZmdK4N%q4T(b!z&tHeZ`G%+O*pLlPp-!%|Md765J@^X)K^!${s~B8}&_^ZAp4 zkI5oa?{{_K{4+D`7=3M`h|8f!Xji$`6k10KO{whdxn_u5osG~27XZ4)$Mv&A5f}bM z=ia$FsWS&`tUdLIXZPvT(iPdLU=nJX^5m9zeV-iyGL5cp+0Ny_hlhpa<J6KU!k*jj z(Q1V&4#s{u5!%eb65Gzt*Eo=SI@n9!zaOrbpRosr%EKB1us-@*3c3BBTzLY+0OP7L z=)=K9@-tXw5HK9JV_BJ(W-h?wA*S5Ncn4OuhmAvA4ZdmqAKzCet`R{&On+9(fd2i& zkaJDjM3Vp4*tsr8ktADJVhSV#YBfxc*)cos|8!@j8%BUobNhaj7P)ejNAZ;z2Ix|i z85!Z1?c2B8xN)s`WKXs+*~%q1o+TTGjSaC0GL${0CBH5&*8?>Hie1X$xf;l<1iHyP zZe=$0ds1Km1f0~6ir!akY3dcIZl$AQPvhyK?+EY?6sfZ)N5o_-;|JzSn12D`jmWr- zQu@5ZN#0)CWp&C8qPg|fo7D`riRop{z*<4`ygz>2%1fP{j#ukQrisdG5=M=kQxPV^ zxJ*>?1s={oSt-)4;S%av+>;G%IjkBeuOkPZC<vt*E9`jfj7EcX&`{*(grKB>Q^Io& zxFfHYl^hi|_3_l=3EahiPiVWJon_a!M>7enh-`Yg_ra>lR2APz`G-mh&(I1>FJEq$ zQ=j4W^~J@%|Gg)!t!Ual{0y?GF>(4`BVfSTlbL?}I0eHE5WjdK5YXzmB_ZG)T$d&! zbc?LZaP@k9xZa!-^UQ6y4v*d7VlBaT5>8(K@o~QxaQL}b=$?dhuH|!m91eRq*p!q8 zeIUEBNN3#C(_Vvi?oD)Sryw;tj?9qw!qA%PYavsI$UO*l0*>O|yxAKNo)0QUcXxZ# z-D0m6Z^fH;`qUKO+sOER+W_YEr2EkhV4}CL%5bLeIu!#0nkG+G?%tEPIa6Q0Y*C|m zD?A^k5P$r!ceG_V`k6Te<}+Om-BkfNurThbtO~6Vw3`>?4Rfwvy*kZ<jgA%{AiSI! zLCQJz!!xUjhmLV-m!E++hpmKUL@6wG^WNr)+>__o`+mF@gkjtZn}|>ubl01V=)fb@ zLH}7tHzIlSmhb@;nB0|gjAzTWn#*34y4w6c=pG<KrN|_*c36J@zTqD<Nr5-Kd$-*K zIF8GJ93;G>@t>x7?izJeS3|cNK)Pxb_Arpg$KyRbJZwWati;qPq(JG^;!HHd^_W45 zVdJyfUs9!JgSVVvf8NuzY!LuMKinVgaLPzz%N@KaMq$eLZIM03*;Z-F&nC@xTeqn+ zxl)g5Tc@WV*GLhff_S%<KZz|)Tm}r>{rzbtA=fjlkr81d;y(O6V4fL2VKt7DOw7WA zRX(i|*g4$>uEk;UL`$VCY3AERy{;ZUcm+nm)Zd1|E8NG??1bak==;T?vqK{VZO19j zhVPd@(*ZK25nIB`gstv<pT)|^{r&egcf-{dH?}=R4u^}EFSnaVA@}Cyw3bL{>q#^f z9$=%c4*#!SENF*(Oo}*DsMd+bG|L!-YJ-bKHQh*p_vA4C`R9HTuJb9ZyBUT~P5%D- zXaM+KA}U*Q>kK+?>r8@4UcEW057y+#xIT-9@T@!Qt{ad;$wuX1xN<{jtcMD)*8~-( zCK%s|mhKKd=foZ9<%0u&hH^8{hiDO!+w0fI2xKDIAcOv|e{BRUm#9tIZi-=$&p;r1 z&7d+QEGiBd^G3CnxFaoc=O>Zw+qXvt1As_m!QW5%2!|c|`~3OVEK-3A(;oS{0U+eI zpu4ikER8+&WwX#sX2r;`pwvBG`{&`C0Afa(*?g7|!`kvauA#z_^*)n2u-}*W`|Y=R z5GR|E2X}e7r3)B@ySuFdVvoszp)z7AG3V?Yvi4J4Tx>%(JPV<-0xfKTahL{Oat$07 zdO&*39g5LGpBz??^5%KGdpFOEgtE=Qt=!b<zvei2NQ=``Gw3Xb=CseV5~s+kb0i!s z8SyWujPZ8lIqCVE$cB^j^d;Z3>CpyyjV*YH?TZ&r%|LBvsi*0zfQFWM8v_|^rsil< zick#!6J=h$+=GoREgJoV(bW`U8t3I&@QTQ@;$gG-eGqJf+T?-av9$(7#yAP#AZ{4U zxx753xmr)ryb4z+3^I~7K@DcZ(*6xT!J#gb5q#qMdYecKHvj|X=A$;fSPGoK9S!i@ z?S<XjPXp4_2*y2no&9~MPE83t0HowHqNw6QTq99jCn*<mauXA4YeJC!?^MEu&F$_; z#1Vx!l`_lK=d`Lw&!0aH2PGVtx}&@~dE@4&)QNwkXk$%skGm@EwZjrtqLKw#Jg{Us zxc>i&=wp%x*4;UmLv23){(fKAwPF$L=FqKzr1q|zOym2khxW!Y&)jqET!wmh*eVpy zo^8xL=b;=R9~g)0Toa+CRdd)_9^*!ufpM9p**HQdsm77O+qe7maHQvQsdH_(5;r%; zd;0zNIa;b3bE6rrv2l+Ay)F55U)e!WuB%wBN0k?-EMH+#1!nKrp)<snbNcjohZ80g zhp4^#W!)Lzu^5QG&D4m%@43H!1=yGt+*xmC?v>zUL*Rq*<HyE7@N}LYBwst;n2ta* ztMKNn>B5n>cV(;?L>hA<9NkQSqZyN4v#YBuG9U_YV-nBA&+PX0xC!v|7$`H;IIQI( zc#~Q7tYt1&Z=C<G^!;41X=B$kA=+h5w+2rVa721W#fw|fwrkcVItv&i4-_R5Bc*cc zXk4u8P$%Sa_N4FcYi*i3;RBPnO8FUKi5`Cb2ZbCT3Lj?6D_anPP0ivkKAc%{Zm{ua zYv)ncwC_W1$*U}@iT!1d<y62k=_k&Bx$WlO+i*EIV<R-w?S}9JcWzk{%`{fKW7y0Q z@Mg2IB?RZ539Dh=v2lqOI%IR2V`$H48frNoZdRE}XihCLROV+?Yr>(kpCxs48!il) z`%9*7ySDgxA5DE5-IJXzv?8+g)vv*SAW8uQY)9(Ep=rw4Yxmik2Gl-9MTZj$WpYKk zhiDH4p*MPNIwubUfYA@iQre~N@_la}48|13JM44FKdAwEfvc%LjqGxs=e*w=3sl+~ z0pzN5Bs!hqp_22NtA2B{FKOBHQBo?�t&cNJIyIAAm=m7x}C23p@gbCC~HfY9l}a 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 literal 0 HcmV?d00001 diff --git a/docs/nmodl/transpiler/doxygen_nmodl.css b/docs/nmodl/transpiler/doxygen_nmodl.css new file mode 100644 index 0000000000..29b25564e0 --- /dev/null +++ b/docs/nmodl/transpiler/doxygen_nmodl.css @@ -0,0 +1,51 @@ +#projectname { + display: none; +} + +#projectlogo img { + margin: 0 10px !important; + width: 180px !important; +} + +div.line { + margin: 4px !important; + line-height: 1.2 !important; +} + +address.footer, hr.footer { + display: none; +} + +span.lineno { + border-color: #b3acb1; +} + +.header, .contents { + width: 80%; + max-width: 1140px; + margin: auto !important; + padding: 10px; + background: white; +} + +div.header { + margin-top: 20px !important; +} + +div#doc-content { + background-image: url("background.png"); + background-repeat: repeat; + /* background: rgba(234, 201, 201, 0.3); */ +} + +#nav-sync img { + display: none; +} + +span.comment { + color: #848080; +} + +span.keyword { + color: #152292; +} diff --git a/docs/nmodl/transpiler/footer.html b/docs/nmodl/transpiler/footer.html new file mode 100644 index 0000000000..3b35440ffe --- /dev/null +++ b/docs/nmodl/transpiler/footer.html @@ -0,0 +1,13 @@ +<!-- HTML footer for doxygen 1.8.15--> +<!-- start footer part --> +<!--BEGIN GENERATE_TREEVIEW--> +<div id="nav-path" class="navpath"> + <ul> + $navpath + </ul> +</div> +<hr class="footer"/><address class="footer"><small> +</small></address> +<!--END !GENERATE_TREEVIEW--> +</body> +</html> diff --git a/docs/nmodl/transpiler/logo.png b/docs/nmodl/transpiler/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c2d7188c7b70c7e319c1eac8e2f1e031cbb41320 GIT binary patch literal 20637 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 literal 0 HcmV?d00001 diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 1cd4fdd81a..6a57682fae 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -17,6 +17,7 @@ def __init__(self): self.force_prefix = "" self.force_suffix = "" self.separator = "" + self.brief = "" self.description = "" # ChildNode diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 04a6bf5a77..8e0a79207a 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -55,7 +55,7 @@ shutil.copy2(clang_format_file, temp_dir) env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates_dir)), - trim_blocks=False, + trim_blocks=True, lstrip_blocks=True) env.filters['snake_case'] = utils.to_snake_case diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 2e4f89937f..63762a9be0 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -33,81 +33,81 @@ children: - NrnStateBlock: nmodl: "NRN_STATE " - description: "Represents the coreneuron nrn_state callback function" + brief: "Represents the coreneuron nrn_state callback function" members: - solve_statements: - description: "solve blocks to be called or generated" + brief: "solve blocks to be called or generated" type: Statement vector: true - EigenNewtonSolverBlock: - description: "Represent newton solver solution block based on Eigen" + brief: "Represent newton solver solution block based on Eigen" nmodl: "EIGEN_NEWTON_SOLVE" members: - n_state_vars: - description: "number of state vars used in solve" + brief: "number of state vars used in solve" type: Integer prefix: {value: "["} suffix: {value: "]"} - variable_block: - description: "Statements to be declared in the functor" + brief: "Statements to be declared in the functor" type: StatementBlock - initialize_block: - description: "Statement block to be executed before calling newton solver" + brief: "Statement block to be executed before calling newton solver" type: StatementBlock - setup_x_block: - description: "update X from states" + brief: "update X from states" type: StatementBlock - functor_block: - description: "odes as functor for eigen" + brief: "odes as functor for eigen" type: StatementBlock - update_states_block: - description: "update back states from X" + brief: "update back states from X" type: StatementBlock - finalize_block: - description: "Statement block to be executed after calling newton solver" + brief: "Statement block to be executed after calling newton solver" type: StatementBlock - EigenLinearSolverBlock: - description: "Represent linear solver solution block based on Eigen" + brief: "Represent linear solver solution block based on Eigen" nmodl: "EIGEN_LINEAR_SOLVE" members: - n_state_vars: - description: "number of state vars used in solve" + brief: "number of state vars used in solve" type: Integer prefix: {value: "["} suffix: {value: "]"} - variable_block: - description: "Statements to be declared in the functor" + brief: "Statements to be declared in the functor" type: StatementBlock - initialize_block: - description: "Statement block to be executed before calling linear solver" + brief: "Statement block to be executed before calling linear solver" type: StatementBlock - setup_x_block: - description: "update X from states" + brief: "update X from states" type: StatementBlock - update_states_block: - description: "update back states from X" + brief: "update back states from X" type: StatementBlock - finalize_block: - description: "Statement block to be executed after calling linear solver" + brief: "Statement block to be executed after calling linear solver" type: StatementBlock - WrappedExpression: - description: "Wrap any other expression type" + brief: "Wrap any other expression type" members: - expression: - description: "Expression that is being wrapped" + brief: "Expression that is being wrapped" type: Expression - DerivimplicitCallback: - description: "Represent a callback to NEURON's derivimplicit solver" + brief: "Represent a callback to NEURON's derivimplicit solver" members: - node_to_solve: - description: "Block to be solved (typically derivative)" + brief: "Block to be solved (typically derivative)" type: Block - SolutionExpression: - description: "Represent solution of a block in the AST" + brief: "Represent solution of a block in the AST" members: - solve_block: type: SolveBlock - node_to_solve: - description: "Block to be solved (callback node or solution node itself)" + brief: "Block to be solved (callback node or solution node itself)" type: Expression - Statement: diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index eeb51003a2..c5a676255c 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -78,370 +78,668 @@ # specification - AST: - description: "Base class for AST" + brief: "Top level AST base class" + description: | + Ast is a top level, abstract base class defining the interface + for all nodes of Abstract Syntax Tree (AST). children: - Node: - description: "Base class for node in the AST" + brief: "Base class for all AST node" + description: | + Base class for all nodes in the AST. This can replace ast::Ast in + the next refactoring. children: - Expression: - description: "Represents a(n) expression" + brief: "Base class for all expressions in the NMODL" + description: | + Base class for all expression types. This is one of the top level node + in the AST representing higher level expression constructs. %Expressions + can be a variable itself or complex binary expressions. + + \sa ast::Statement children: - String: - description: "Represents a(n) double quoted string" members: - value: - description: "string value" + brief: "value of string" type: std::string + brief: "Represents a string" + description: | + All statements encapsulating text block are stored in the AST as ast::String. + For example, nodes like ast::LineComment and ast::Verbatim block use ast::String::value + to store the underlying text: + + \code{.mod} + COMMENT + This text is stored as String + ENDCOMMENT + + VERBATIM + int *x; + *x = ... + ENDVERBATIM + \endcode - Number: - description: "Represents a(n) number" + brief: "Base class for all numbers" + description: | + Base class for all number types like ast::Integer, ast::Float and ast::Double. children: - Integer: - description: "Represents a(n) integer number" members: - value: - description: "integer value" + brief: "value of integer" type: int - macro: - description: "macro name for integer" + brief: "if integer is a macro then it's name" type: Name optional: true + brief: "Represents an integer variable" + description: | + Non floating value in the mod file is parsed as an integer. For example, + in the below code, integer literals like `0` and `11` are stored as ast::Integer + in the AST : + + \code{.mod} + FROM i=0 TO N { + tau = X[i] + 11 + } + \endcode + + \sa ast::Float ast::Double - Float: - description: "Represents a(n) float number" members: - value: - description: "float value" + brief: "value of float" type: float + brief: "Represents a float variable" + description: | + Single precision float value in the mod file can be represented by ast::Float. + + \note Currently every float variable in the NMODL is stored as ast::Double and hence + this type is note used. This will be changed soon for variables like ast::ParamAssign. + + \sa ast::Integer ast::Double - Double: - description: "Represents a(n) double number" members: - value: - description: "double value" + brief: "value of double" type: double + brief: "Represents a double variable" + description: | + %Double precision float value in the mod file is represented by ast::Double. + For example, float literals like `0.1` in the mod file are parsed as double + and stored using ast::Double::value : + + \code{.mod} + PROCEDURE foo() { + LOCAL x + x = 0.1 + tau + } + \endcode + + Note that the variables are not classified ast integer or float in the AST. The + decision about variable types is done by code generation backends. + + \sa ast::Integer ast::Float - Boolean: - description: "Represents a(n) boolean variable" members: - value: - description: "bool value" + brief: "value of boolean" type: int + brief: "Represents a boolean variable" + description: | + %Boolean values in the mod file can be represented by ast::Boolean. + + \note Currently this type is used as only flag in some of the AST nodes. Similar to ast::Float, + this type was introduced for data type specific code generation support in the future. - Identifier: - description: "Represents a(n) identifier" + brief: "Base class for all identifiers" + description: | + Base class for all variable types like ast::Name, ast::PrimeName and ast::Argument. + + \sa ast::Number children: - Name: - description: "Represents a(n) name" members: - value: - description: "string value" + brief: "value of name" type: String node_name: true + brief: "Represents a name" + description: | + Whenever lexer encounters string variable, it returns a ast::Name + type. So, along with ast::Integer, ast::Double ast::String and ast::PrimeName, + ast::Name is one of the fundamental type in the AST. Many other variable types + (e.g. ast::GlobalVar, ast::RangeVar) have underlying value stored as ast::Name. + + \note This node should be able to use std::string as value type instead of ast::String - PrimeName: - description: "Represents a(n) prime variable" members: - value: - description: "variable name" + brief: "name of prime variable" type: String node_name: true - order: - description: "order of prime" + brief: "order of ODE" type: Integer - - VarName: - description: "Represents a(n) NMODL variable" + brief: "Represents a prime variable (for ODE)" + description: | + In case of ODE specification, all state variables appearing on LHS + with \` as suffix are parsed by lexer as ast::PrimeName. For example, + in below NMODL construct, m\` is stored as ast::PrimeName with `m` as a + ast::PrimeName::value and `1` as an ast::PrimeName::order. + + \code + DERIVATIVE states { + m` = m + h + } + \endcode + + - IndexedName: members: - name: - description: "variable name" + brief: "name of array variable" type: Identifier node_name: true - - at: - description: "todo : Michael to define the semantics of this" - type: Integer - optional: true - prefix: {value: "@"} - - index: - description: "index value in case of array" + - length: + brief: "legth of an array or index position" type: Expression - optional: true prefix: {value: "["} suffix: {value: "]"} - - IndexedName: - description: ".." + brief: "Represents specific element of an array variable" + description: | + If variable is declared as an array or when array element is accessed, + it is stored in the ast as ast::IndexedName. For example, in below NMODL, + construct `m[4]` is stored as ast::IndexedName with `m` as ast::IndexedName::name + and `4` as ast::IndexedName::legth. + + \code + STATE { + m[4] + } + \endcode + - VarName: members: - name: - description: "..." + brief: "name of variable" type: Identifier node_name: true - - length: - description: "..." + - at: + brief: "value specified with `@`" + type: Integer + optional: true + prefix: {value: "@"} + - index: + brief: "index value in case of array" type: Expression + optional: true prefix: {value: "["} suffix: {value: "]"} + brief: "Represents a variable" + description: | + This type was introduced to store variables of different types like + ast::Name or ast::IndexedName in the AST. + + \note With ast::Identifier as top level base class, this type can be + removed in the future refactoring. - Argument: - description: ".." members: - name: - description: "..." + brief: "name of the argument" type: Identifier node_name: true - unit: - description: "..." + brief: "unit of the argument" type: Unit optional: true + brief: "Represents an argument to function" + description: | + In case of function definitions from different ast nodes like ast::FunctionBlock, + ast::ProcedureBlock, the arguments are store in the ast::Argument. For example, + in below NMODL construct, `weight` is stored as ast::Argument::name and `uS` is + stored as ast::Argument::unit: + + \code {.mod} + NET_RECEIVE(weight (uS)) { + g = g + weight + } + \endcode - ReactVarName: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: Integer optional: true prefix: {value: " "} - name: - description: "..." + brief: "..." type: VarName node_name: true - ReadIonVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - WriteIonVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - NonspecificCurVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - ElectrodeCurVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - SectionVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - RangeVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - GlobalVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - PointerVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - BbcorePointerVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - ExternVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - ThreadsafeVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - Block: - description: ".." + brief: ".." children: - ParamBlock: - description: ".." nmodl: "PARAMETER " members: - statements: - description: "..." + brief: "vector of parameters" type: ParamAssign vector: true + brief: "Represents a `PARAMETER` block in the NMODL" + description: | + Variables whose values are normally specified by the user are parameters + and are declared in a `PARAMETER` block. Here is an example : + + \code{.mod} + PARAMETER { + gkbar=.01 (mho/cm2) : Maximum Permeability + d1 = .84 + k2 = .13e-6 (mM) + abar = .28 (/ms) + lcai (mV) + } + \endcode + + All parameters are stored in the ast::ParamBlock::statements as vector. + - StepBlock: - description: ".." nmodl: "STEPPED " members: - statements: - description: "..." + brief: "vector of statements" type: Stepped vector: true + brief: "Represents a `STEPPED` block in the NMODL" + description: | + `STEPPED` has following form in the NMODL specification : + + \code{.mod} + STEPPED { + name1 = number1 (mM) + name2 = number2, number3 + } + \endcode + + \todo Check ModelDB and other databse for example of channel. - IndependentBlock: - description: ".." nmodl: "INDEPENDENT " members: - definitions: - description: "..." + brief: "..." type: IndependentDef vector: true + brief: "Represents a `INDEPENDENT` block in the NMODL" + description: | + `INDEPENDENT` has following form in the NMODL specification : + + \code{.mod} + INDEPENDENT { + t FROM 0 TO 1 WITH 1 (ms) + } + \endcode + - DependentBlock: - description: ".." nmodl: "ASSIGNED " members: - definitions: - description: "..." + brief: "vector of assigned variables" type: DependentDef vector: true + brief: "Represents a `ASSIGNED` block in the NMODL" + description: | + The `ASSIGNED` block is used for declaring two kinds of variables : + - those that are given values outside the mod files + - those that appear on the left hand side of assignment statements within + the mod file + + Below is an example of `ASSIGNED` block in the mod file: + + \code{.mod} + ASSIGNED { + ina (mA/cm2) + gna (pS/um2) + mtau (ms) htau (ms) + tadj + } + \endcode + - StateBlock: - description: ".." nmodl: "STATE " members: - definitions: - description: "..." + brief: "vector of state variables" type: DependentDef vector: true + brief: "Represents a `STATE` block in the NMODL" + description: | + If a model involves differential equations, families of algebraic equations, + or kinetic reaction schemes, their dependent variables or unknowns are to be + listed in the `STATE` block. Below is an example of `STATE`: + + \code{.mod} + STATE { + m + h + } + \endcode + + Note that the state variable specification has form of ast::DependentDef and + hence can have associated unit specification. - PlotBlock: - description: ".." members: - plot: - description: "..." + brief: "vector of plot variables" type: PlotDeclaration + brief: "Represents a `PLOT` statement in the NMODL" + description: | + `PLOT` construct doesn't have block scope but it's standalone, global scoped + statement of the form: + + \code{.mod} + PLOT name1, index_var2 VS name3 + \endcode + + \todo Check ModelDB and other databse for example of channel. + - InitialBlock: - description: ".." nmodl: "INITIAL " members: - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents a `INITIAL` block in the NMODL" + description: | + The code in the `INITIAL` block is executed when the hoc function `finitialize()` + is called. Here is an example : + + \code{.mod} + INITIAL { + rates(v+vshift) + m = minf + h = hinf + tadj = q10^((celsius - temp)/10) + } + \endcode + - ConstructorBlock: - description: ".." nmodl: "CONSTRUCTOR " members: - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents a `CONSTRUCTOR` block in the NMODL" + description: | + The code in the `CONSTRUCTOR` is called when the channel is instantiated. Like any + other global block, `CONSTRUCTOR` block can have any statements. It often used with + `VERBATIM` block for initialization purpose : + + \code{.mod} + CONSTRUCTOR { + VERBATIM + if (ifarg(1)) { id= *getarg(1); } else { id= -1; } + ENDVERBATIM + } + \endcode + + \sa ast::DestructorBlock + - DestructorBlock: - description: ".." nmodl: "DESTRUCTOR " members: - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents a `DESTRUCTOR` block in the NMODL" + description: | + The code in the `DESTRUCTOR` is called when the channel instance is deleted. It often + used with `VERBATIM` block for finalization purpose : + + \code{.mod} + DESTRUCTOR { + VERBATIM { + nsyn = maxsyn = 0; + free(PRECAST); + link = 0; + } + ENDVERBATIM + } + \endcode + + \sa ast::ConstructorBlock - StatementBlock: - description: ".." members: - statements: - description: "..." + brief: "vector of statements" type: Statement vector: true public: true add: true + brief: "Represents block encapsulating list of statements" + description: | + Statement block is used to hold list of statements between `{ }`. This + represents a new block scope in the mod file and has following form : + + \code{.mod} + { + statement1 + { + statement2 + } + } + \endcode + + Note that the statement blocks can be nested where inner block will + be wrapped as statement with ast::ExpressionStatement. - DerivativeBlock: - description: ".." nmodl: "DERIVATIVE " members: - name: - description: "..." + brief: "name of the derivative block" type: Name node_name: true suffix: {value: " "} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents `DERIVATIVE` block in the NMODL" + description: | + This block is used to assign values to the derivatives of those + `STATE`s that are described by differential equations. The statements + in this block are of the form \f$y' = expr\f$. Here is an example : + + \code{.mod} + DERIVATIVE states { + rates(v) + m' = (minf-m)/mtau + h' = (hinf-h)/htau + } + \endcode + - LinearBlock: - description: ".." nmodl: "LINEAR " members: - name: - description: "..." + brief: "name of the linear block" type: Name node_name: true suffix: {value: " "} - solvefor: - description: "..." + brief: "..." type: Name vector: true separator: "," prefix: {value: " SOLVEFOR "} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents `LINEAR` block in the NMODL" + description: | + + A set of simultaneous equations can be specified in a `LINEAR` block. + Here is an example : + + \code{.mod} + LINEAR clamp { + LOCAL t1, t2 + t1 = tau1/dt + t2 = tau2/dt + ~ vi = v + fac*vo - fac*v + ~ t2*vo - t2*vo0 + vo = -gain * e + ~ -stim - e + vi - e + t1*vi - t1*e - t1*(vi0 - e0) = 0 + } + \endcode + - NonLinearBlock: - description: ".." nmodl: "NONLINEAR " members: - name: - description: "..." + brief: "name of the non-linear block" type: Name node_name: true - solvefor: - description: "..." + brief: "name of the integration method" type: Name vector: true separator: "," prefix: {value: " SOLVEFOR "} suffix: {value: " ", force: true} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents `NONLINEAR` block in the NMODL" + description: | + + A set of simultaneous equations can be specified in a `NONLINEAR` block. + Here is an example : + + \code{.mod} + NONLINEAR nonlin { + ~ s[0] = 1 + ~ s[1] = 3 + ~ s[2] + s[1] = s[0] + } + \endcode + - DiscreteBlock: - description: ".." + brief: ".." nmodl: "DISCRETE " members: - name: - description: "..." + brief: "..." type: Name node_name: true suffix: {value: " "} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - PartialBlock: - description: ".." + brief: ".." nmodl: "PARTIAL " members: - name: - description: "..." + brief: "..." type: Name node_name: true suffix: {value: " "} - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - FunctionTableBlock: - description: ".." + brief: ".." nmodl: "FUNCTION_TABLE " members: - name: - description: "..." + brief: "..." type: Name node_name: true - parameters: - description: "..." + brief: "..." type: Argument vector: true prefix: {value: "(", force: true} @@ -449,20 +747,20 @@ separator: ", " getter: {override: true} - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - FunctionBlock: - description: ".." + brief: ".." nmodl: "FUNCTION " members: - name: - description: "..." + brief: "..." type: Name node_name: true - parameters: - description: "..." + brief: "..." type: Argument vector: true prefix: {value: "(", force: true} @@ -470,25 +768,25 @@ separator: ", " getter: {override: true} - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} suffix: {value: " ", force: true} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - ProcedureBlock: - description: ".." + brief: ".." nmodl: "PROCEDURE " members: - name: - description: "..." + brief: "..." type: Name node_name: true - parameters: - description: "..." + brief: "..." type: Argument vector: true prefix: {value: "(", force: true} @@ -496,19 +794,19 @@ separator: ", " getter: {override: true} - unit: - description: "..." + brief: "..." type: Unit optional: true - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - NetReceiveBlock: - description: ".." + brief: ".." nmodl: "NET_RECEIVE " members: - parameters: - description: "..." + brief: "..." type: Argument vector: true prefix: {value: "(", force: true} @@ -516,79 +814,79 @@ separator: ", " getter: {override: true} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - SolveBlock: - description: ".." + brief: ".." nmodl: SOLVE members: - block_name: - description: "..." + brief: "..." type: Name prefix: {value: " "} - method: - description: "..." + brief: "..." type: Name optional: true prefix: {value: " METHOD "} - steadystate: - description: "..." + brief: "..." type: Name optional: true prefix: {value: " STEADYSTATE "} - ifsolerr: - description: "..." + brief: "..." type: StatementBlock optional: true prefix: {value: " IFERROR "} - BreakpointBlock: - description: ".." + brief: ".." nmodl: "BREAKPOINT " members: - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - TerminalBlock: - description: ".." + brief: ".." nmodl: "TERMINAL " members: - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - BeforeBlock: - description: ".." + brief: ".." nmodl: "BEFORE " members: - bablock: - description: "..." + brief: "..." type: BABlock - AfterBlock: - description: ".." + brief: ".." nmodl: "AFTER " members: - bablock: - description: "..." + brief: "..." type: BABlock - BABlock: - description: ".." + brief: ".." members: - type: - description: "..." + brief: "..." type: BABlockType suffix: {value: " "} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - ForNetcon: - description: ".." + brief: ".." nmodl: "FOR_NETCONS " members: - parameters: - description: "..." + brief: "..." type: Argument vector: true prefix: {value: "(", force: true} @@ -596,977 +894,994 @@ separator: ", " getter: {override: true} - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - KineticBlock: - description: ".." + brief: ".." nmodl: "KINETIC " members: - name: - description: "..." + brief: "..." type: Name node_name: true suffix: {value: " "} - solvefor: - description: "..." + brief: "..." type: Name vector: true separator: "," - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - MatchBlock: - description: ".." + brief: ".." nmodl: MATCH members: - matchs: - description: "..." + brief: "..." type: Match vector: true separator: " " prefix: {value: " { "} suffix: {value: " }"} - UnitBlock: - description: ".." + brief: ".." nmodl: "UNITS " members: - definitions: - description: "..." + brief: "..." type: Expression vector: true - ConstantBlock: - description: ".." + brief: ".." nmodl: "CONSTANT " members: - statements: - description: "..." + brief: "..." type: ConstantStatement vector: true - NeuronBlock: - description: ".." + brief: "Represent `NEURON` block in the mod file" + description: | + The keyword `NEURON` introduces a special block which contains statements + that tell NMODL how to organize the variables for access at the NEURON user + level. Here is an example of `NEURON` block from `HH` channel: + + \code{.mod} + NEURON { + SUFFIX hh + USEION na READ ena WRITE ina + USEION k READ ek WRITE ik + NONSPECIFIC_CURRENT il + RANGE gnabar, gkbar, gl, el, gna, gk + RANGE minf, hinf, ninf, mtau, htau, ntau + THREADSAFE + } + \endcode + url: "https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl2.html#neuron" nmodl: "NEURON " members: - statement_block: - description: "..." + brief: "block with statements vector" type: StatementBlock getter: {override: true} - Unit: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: String node_name: true prefix: {value: "("} suffix: {value: ")"} - DoubleUnit: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: Double - unit: - description: "..." + brief: "..." type: Unit optional: true - LocalVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Identifier node_name: true - Limits: - description: ".." + brief: ".." members: - min: - description: "..." + brief: "..." type: Double prefix: {value: "<"} suffix: {value: ","} - max: - description: "..." + brief: "..." type: Double suffix: {value: ">"} - NumberRange: - description: ".." + brief: ".." members: - min: - description: "..." + brief: "..." type: Number prefix: {value: "<"} suffix: {value: ","} - max: - description: "..." + brief: "..." type: Number suffix: {value: ">"} - PlotVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Identifier - index: - description: "..." + brief: "..." type: Integer optional: true prefix: {value: "["} suffix: {value: "]"} - ConstantVar: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - value: - description: "..." + brief: "..." type: Number prefix: {value: " = "} - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - BinaryOperator: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: BinaryOp - UnaryOperator: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: UnaryOp - ReactionOperator: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: ReactionOp - ParenExpression: - description: ".." + brief: ".." members: - expression: - description: "..." + brief: "..." type: Expression prefix: {value: "("} suffix: {value: ")"} - BinaryExpression: - description: ".." + brief: ".." members: - lhs: - description: "..." + brief: "..." type: Expression public: true - op: - description: "..." + brief: "..." type: BinaryOperator public: true - rhs: - description: "..." + brief: "..." type: Expression public: true - DiffEqExpression: - description: "Represents differential equation in DERIVATIVE block" + brief: "Represents differential equation in DERIVATIVE block" members: - expression: - description: "Differential Expression" + brief: "Differential Expression" type: BinaryExpression public: true - UnaryExpression: - description: ".." + brief: ".." members: - op: - description: "..." + brief: "..." type: UnaryOperator - expression: - description: "..." + brief: "..." type: Expression - NonLinEquation: - description: ".." + brief: ".." nmodl: "~ " members: - lhs: - description: "..." + brief: "..." type: Expression suffix: {value: " = "} - rhs: - description: "..." + brief: "..." type: Expression - LinEquation: - description: ".." + brief: ".." nmodl: "~ " members: - left_linxpression: - description: "..." + brief: "..." type: Expression suffix: {value: " = "} - linxpression: - description: "..." + brief: "..." type: Expression - FunctionCall: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true - arguments: - description: "..." + brief: "..." type: Expression vector: true separator: ", " prefix: {value: "(", force: true} suffix: {value: ")", force: true} - FirstLastTypeIndex: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: FirstLastType - Watch: - description: ".." + brief: ".." members: - expression: - description: "..." + brief: "..." type: Expression prefix: {value: "("} suffix: {value: ")"} - value: - description: "..." + brief: "..." type: Expression prefix: {value: " "} - QueueExpressionType: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: QueueType - Match: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Identifier - expression: - description: "..." + brief: "..." type: Expression optional: true - BABlockType: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: BAType - UnitDef: - description: ".." + brief: ".." members: - unit1: - description: "..." + brief: "..." type: Unit node_name: true - unit2: - description: "..." + brief: "..." type: Unit prefix: {value: " = "} - FactorDef: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name node_name: true suffix: {value: " ="} - value: - description: "..." + brief: "..." type: Double optional: true prefix: {value: " "} - unit1: - description: "..." + brief: "..." type: Unit prefix: {value: " "} - gt: - description: "Todo: @Michael : rename variable gt as well" + brief: "Todo: Michael : rename variable gt as well" type: Boolean nmodl: " ->" optional: true - unit2: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - Valence: - description: ".." + brief: ".." members: - type: type: Name - description: "..." + brief: "..." prefix: {value: " "} suffix: {value: " "} - value: - description: "..." + brief: "..." type: Double - Statement: - description: ".." + brief: ".." children: - UnitState: - description: ".." + brief: ".." members: - value: - description: "..." + brief: "..." type: UnitStateType - LocalListStatement: - description: ".." + brief: ".." nmodl: "LOCAL " members: - variables: - description: "..." + brief: "..." type: LocalVar vector: true separator: ", " public: true - Model: - description: ".." + brief: ".." nmodl: TITLE members: - title: - description: "..." + brief: "..." type: String - Define: - description: ".." + brief: ".." nmodl: "DEFINE " members: - name: - description: "..." + brief: "..." type: Name node_name: true - value: - description: "..." + brief: "..." type: Integer prefix: {value: " "} - Include: - description: ".." + brief: ".." nmodl: "INCLUDE " members: - filename: - description: "..." + brief: "..." type: String - ParamAssign: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Identifier node_name: true - value: - description: "..." + brief: "..." type: Number optional: true prefix: {value: " = "} - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - limit: - description: "..." + brief: "..." type: Limits optional: true prefix: {value: " "} - Stepped: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Name - values: - description: "..." + brief: "..." type: Number vector: true prefix: {value: " = "} separator: ", " - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - IndependentDef: - description: ".." + brief: ".." members: - sweep: - description: "..." + brief: "..." type: Boolean optional: true nmodl: "SWEEP " - name: - description: "..." + brief: "..." type: Name - from: - description: "..." + brief: "..." type: Number prefix: {value: " FROM "} - to: - description: "..." + brief: "..." type: Number prefix: {value: " TO "} - with: - description: "..." + brief: "..." type: Integer prefix: {value: " WITH "} - start: - description: "..." + brief: "..." type: Number prefix: {value: " START "} optional: true - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - DependentDef: - description: ".." + brief: ".." members: - name: - description: "..." + brief: "..." type: Identifier node_name: true - length: - description: "..." + brief: "..." type: Integer optional: true prefix: {value: "["} suffix: {value: "]"} - from: - description: "..." + brief: "..." type: Number prefix: {value: " FROM "} optional: true - to: - description: "..." + brief: "..." type: Number prefix: {value: " TO "} optional: true - start: - description: "..." + brief: "..." type: Number prefix: {value: " START "} optional: true - unit: - description: "..." + brief: "..." type: Unit optional: true prefix: {value: " "} - abstol: - description: "..." + brief: "..." type: Double prefix: {value: " <"} suffix: {value: ">"} optional: true - PlotDeclaration: - description: ".." + brief: ".." nmodl: "PLOT " members: - variables: - description: "..." + brief: "..." type: PlotVar vector: true separator: ", " - name: - description: "..." + brief: "..." type: PlotVar prefix: {value: " VS "} - ConductanceHint: - description: ".." + brief: ".." nmodl: "CONDUCTANCE " members: - conductance: - description: "..." + brief: "..." type: Name - ion: - description: "..." + brief: "..." type: Name optional: true prefix: {value: " USEION "} - ExpressionStatement: - description: ".." + brief: ".." members: - expression: - description: "..." + brief: "..." type: Expression - ProtectStatement: - description: ".." + brief: ".." nmodl: "PROTECT " members: - expression: - description: "..." + brief: "..." type: Expression - FromStatement: - description: ".." + brief: ".." nmodl: "FROM " members: - name: - description: "..." + brief: "..." type: Name node_name: true - from: - description: "..." + brief: "..." type: Expression prefix: {value: " = "} - to: - description: "..." + brief: "..." type: Expression prefix: {value: " TO "} - increment: - description: "..." + brief: "..." type: Expression prefix: {value: " BY "} suffix: {value: " ", force: true} optional: true - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - ForAllStatement: - description: ".." + brief: ".." nmodl: "FORALL " members: - name: - description: "..." + brief: "..." type: Name suffix: {value: " "} - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - WhileStatement: - description: ".." + brief: ".." nmodl: "WHILE " members: - condition: - description: "..." + brief: "..." type: Expression prefix: {value: "("} suffix: {value: ") "} - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - IfStatement: - description: ".." + brief: ".." nmodl: "IF " members: - condition: - description: "..." + brief: "..." type: Expression prefix: {value: "("} suffix: {value: ") "} - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - elseifs: - description: "..." + brief: "..." type: ElseIfStatement vector: true - elses: - description: "..." + brief: "..." type: ElseStatement optional: true - ElseIfStatement: - description: ".." + brief: ".." nmodl: " ELSE IF " members: - condition: - description: "..." + brief: "..." type: Expression prefix: {value: "("} suffix: {value: ") "} - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - ElseStatement: - description: ".." + brief: ".." nmodl: " ELSE " members: - statement_block: - description: "..." + brief: "..." type: StatementBlock getter: {override: true} - PartialEquation: - description: ".." + brief: ".." members: - prime: - description: "..." + brief: "..." type: PrimeName - name1: - description: "..." + brief: "..." type: Name - name2: - description: "..." + brief: "..." type: Name - name3: - description: "..." + brief: "..." type: Name - PartialBoundary: - description: ".." + brief: ".." nmodl: "~ " members: - del: - description: "..." + brief: "..." type: Name optional: true suffix: {value: " "} - name: - description: "..." + brief: "..." type: Identifier - index: - description: "..." + brief: "..." type: FirstLastTypeIndex optional: true prefix: {value: "["} suffix: {value: "]"} - expression: - description: "..." + brief: "..." type: Expression optional: true prefix: {value: " = "} - name1: - description: "..." + brief: "..." type: Name optional: true prefix: {value: " = "} suffix: {value: "*"} - del2: - description: "..." + brief: "..." type: Name optional: true suffix: {value: "("} - name2: - description: "..." + brief: "..." type: Name optional: true suffix: {value: ")"} - name3: - description: "..." + brief: "..." type: Name optional: true prefix: {value: "+"} - WatchStatement: - description: ".." + brief: ".." nmodl: "WATCH " members: - statements: - description: "..." + brief: "..." type: Watch vector: true separator: "," add: true - MutexLock: - description: ".." + brief: ".." nmodl: MUTEXLOCK - MutexUnlock: - description: ".." + brief: ".." nmodl: MUTEXUNLOCK - Reset: - description: ".." + brief: ".." nmodl: RESET - Sens: - description: ".." + brief: ".." nmodl: "SENS " members: - variables: - description: "..." + brief: "..." type: VarName vector: true separator: ", " - Conserve: - description: ".." + brief: ".." nmodl: CONSERVE members: - react: - description: "..." + brief: "..." type: Expression prefix: {value: " "} - expr: - description: "..." + brief: "..." type: Expression prefix: {value: " = "} - Compartment: - description: ".." + brief: ".." nmodl: COMPARTMENT members: - name: - description: "..." + brief: "..." type: Name optional: true prefix: {value: " "} suffix: {value: ","} - expression: - description: "..." + brief: "..." type: Expression prefix: {value: " "} - names: - description: "..." + brief: "..." type: Name vector: true prefix: {value: " {"} suffix: {value: "}"} separator: " " - LonDifuse: - description: ".." + brief: ".." nmodl: LONGITUDINAL_DIFFUSION members: - name: - description: "..." + brief: "..." type: Name optional: true prefix: {value: " "} suffix: {value: ","} - expression: - description: "..." + brief: "..." type: Expression prefix: {value: " "} - names: - description: "..." + brief: "..." type: Name vector: true prefix: {value: " {"} suffix: {value: "}"} separator: " " - ReactionStatement: - description: ".." + brief: ".." nmodl: "~ " members: - reaction1: - description: "..." + brief: "..." type: Expression - op: - description: "..." + brief: "..." type: ReactionOperator prefix: {value: " "} - reaction2: - description: "..." + brief: "..." type: Expression prefix: {value: " "} optional: true - expression1: - description: "..." + brief: "..." type: Expression prefix: {value: " ("} - expression2: - description: "..." + brief: "..." type: Expression prefix: {value: ", "} suffix: {value: ")", force: true} optional: true - LagStatement: - description: ".." + brief: ".." nmodl: "LAG " members: - name: - description: "..." + brief: "..." type: Identifier - byname: - description: "..." + brief: "..." type: Name prefix: {value: " BY "} - QueueStatement: - description: ".." + brief: ".." members: - qtype: - description: "..." + brief: "..." type: QueueExpressionType - name: - description: "..." + brief: "..." type: Identifier prefix: {value: " "} - ConstantStatement: - description: ".." + brief: ".." members: - constant: - description: "..." + brief: "..." type: ConstantVar - TableStatement: - description: ".." + brief: ".." nmodl: "TABLE " members: - table_vars: - description: "..." + brief: "..." type: Name vector: true separator: "," - depend_vars: - description: "..." + brief: "..." type: Name vector: true prefix: {value: " DEPEND "} separator: "," - from: - description: "..." + brief: "..." type: Expression prefix: {value: " FROM "} - to: - description: "..." + brief: "..." type: Expression prefix: {value: " TO "} - with: - description: "..." + brief: "..." type: Integer prefix: {value: " WITH "} - Suffix: - description: ".." + brief: ".." members: - type: - description: "..." + brief: "..." type: Name suffix: {value: " "} - name: - description: "..." + brief: "..." type: Name node_name: true - Useion: - description: ".." + brief: ".." nmodl: "USEION " members: - name: - description: "..." + brief: "..." type: Name node_name: true - readlist: - description: "..." + brief: "..." type: ReadIonVar vector: true prefix: {value: " READ "} separator: ", " - writelist: - description: "..." + brief: "..." type: WriteIonVar vector: true prefix: {value: " WRITE "} separator: ", " - valence: - description: "..." + brief: "..." type: Valence optional: true - Nonspecific: - description: ".." + brief: ".." nmodl: "NONSPECIFIC_CURRENT " members: - currents: - description: "..." + brief: "..." type: NonspecificCurVar vector: true separator: ", " - ElctrodeCurrent: - description: ".." + brief: ".." nmodl: "ELECTRODE_CURRENT " members: - currents: - description: "..." + brief: "..." type: ElectrodeCurVar vector: true separator: ", " - Section: - description: ".." + brief: ".." nmodl: "SECTION " members: - sections: - description: "..." + brief: "..." type: SectionVar vector: true separator: ", " - Range: - description: ".." + brief: ".." nmodl: "RANGE " members: - variables: - description: "..." + brief: "..." type: RangeVar vector: true separator: ", " - Global: - description: ".." + brief: ".." nmodl: "GLOBAL " members: - variables: - description: "..." + brief: "..." type: GlobalVar vector: true separator: ", " - Pointer: - description: ".." + brief: ".." nmodl: "POINTER " members: - variables: - description: "..." + brief: "..." type: PointerVar vector: true separator: ", " - BbcorePtr: - description: ".." + brief: ".." nmodl: "BBCOREPOINTER " members: - variables: - description: "..." + brief: "..." type: BbcorePointerVar vector: true separator: ", " - External: - description: ".." + brief: ".." nmodl: "EXTERNAL " members: - variables: - description: "..." + brief: "..." type: ExternVar vector: true separator: ", " - ThreadSafe: - description: ".." + brief: ".." nmodl: THREADSAFE members: - variables: - description: "..." + brief: "..." type: ThreadsafeVar vector: true separator: ", " prefix: {value: " "} - Verbatim: - description: "Represents a C code block" + brief: "Represents a C code block" nmodl: VERBATIM members: - statement: - description: "C code as a string" + brief: "C code as a string" type: String suffix: {value: "ENDVERBATIM"} - LineComment: - description: "Represents a one line comment in NMODL" + brief: "Represents a one line comment in NMODL" members: - statement: - description: "comment text" + brief: "comment text" type: String - BlockComment: - description: "Represents a multi-line comment in NMODL" + brief: "Represents a multi-line comment in NMODL" nmodl: COMMENT members: - statement: - description: "comment text" + brief: "comment text" type: String suffix: {value: "ENDCOMMENT"} - Program: - description: "Represents top level AST node for whole NMODL input" + brief: "Represents top level AST node for whole NMODL input" members: - blocks: - description: "..." + brief: "..." type: Node vector: true add: true diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index ebefecd23d..e4f69533f0 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -29,6 +29,7 @@ def __init__(self, args): self.separator = args.separator self.force_prefix = args.force_prefix self.force_suffix = args.force_suffix + self.brief = args.brief self.description = args.description self.is_abstract = False @@ -357,7 +358,7 @@ def public_members(self): """ Return public members of the node """ - members = [[child.member_typename, child.varname, child.description] + members = [[child.member_typename, child.varname, child.brief] for child in self.children if child.is_public] @@ -367,7 +368,7 @@ def private_members(self): """ Return private members of the node """ - members = [[child.member_typename, child.varname, child.description] + members = [[child.member_typename, child.varname, child.brief] for child in self.children if not child.is_public] @@ -386,6 +387,19 @@ def private_members(self): def non_base_members(self): return [child for child in self.children if not child.is_base_type_node] + def get_description(self): + """ + Return description for the node in doxygen form + """ + lines = self.description.split('\n') + description = "" + for i, line in enumerate(lines): + if i == 0: + description = ' ' + line + '\n' + else: + description += ' * ' + line + '\n' + return description + def __repr__(self): return "Node(class_name='{}', base_class='{}', nmodl_name='{}')".format( self.class_name, self.base_class, self.nmodl_name) diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 249e6fcc7b..66d2867fe2 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -82,6 +82,10 @@ def parse_child_rule(self, child): if 'node_name' in properties: args.get_node_name = properties['node_name'] + # brief description of member variable + if 'brief' in properties: + args.brief = properties['brief'] + # description of member variable if 'description' in properties: args.description = properties['description'] @@ -134,6 +138,7 @@ def parse_yaml_rules(self, nodelist, base_class=None): args = Argument() args.url = properties.get('url', None) args.class_name = class_name + args.brief = properties.get('brief', '') args.description = properties.get('description', '') # yaml file has abstract classes and their subclasses with children as a property diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 8cdaafb21e..489f4f8255 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -31,18 +31,22 @@ namespace nmodl { namespace ast { {% for node in nodes %} - {{ '/* ' + node.description + ' */'}} + /** + * \brief {{ node.brief }} + * + * {{- node.get_description() -}} + */ class {{ node.class_name }} : public {{ node.base_class }} { {% if node.private_members() %} - private: + private: {% for member in node.private_members() %} - {{ '// ' + member[2] }} + {{ '/// ' + member[2] }} {{ member[0] }} {{ member[1] }}; {% endfor %} {% endif %} - public: + public: {% for member in node.public_members() %} - {{ '// ' + member[2] }} + {{ '/// ' + member[2] }} {{ member[0] }} {{ member[1] }}; {% endfor %} diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 2217ff6bbd..ede5de9505 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -113,7 +113,7 @@ void init_ast_module(py::module& m) { {% for node in nodes %} py::class_<{{ node.class_name }}, std::shared_ptr<{{ node.class_name }}>> {{ var(node) }}(m_ast, "{{ node.class_name }}", {{ node.base_class | snake_case }}_); - {{ var(node) }}.doc() = "{{ node.description }}"; + {{ var(node) }}.doc() = "{{ node.brief }}"; {% if node.children %} {{ var(node) }}.def(py::init<{{ args(node.children) }}>()); {% endif %} From df9763fb90ca0224eefbc15a28435eb4b0bda452 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.s.kumbhar@gmail.com> Date: Wed, 24 Apr 2019 01:23:37 +0200 Subject: [PATCH 194/871] Documentation overhaul : update documentation, add doxygen config and cmake/calng-format (BlueBrain/nmodl#143) Doxygen updates : - replace logo.pdf with logo.png to be visible across browsers - add doxygen config, layout, footer and css - updated documentation of most of the codebase with consistency - updated nmodl.yaml and codegen.yaml is still WIP and will be gradually updated - Doxygen now uses mathjax Submodules update : - updated pybind to latest master in order to document every value of enum structure. - updated cmake/hpc-coding-conventions to use latest clang-format Code changes for consistency and semantics : - Rename nmodl::ast::AST to nmodl::ast::Ast - when refering to Abstract Syntax Tree, AST was often refer AST and not class ast::AST - replace AST with Ast and update documentaiton and tests - nmodl driver API parse_x now returns AST instead of boolean - previously parse_string, parse_file and parse_string was returning true if parsing was successfully and then one has to use ast() method to access underlying AST - change it now return AST object directly - update tests and documentation - symbol table updates - remove unnecessary methods like type and name - remove members model_suffix, breakpoint_exist - renamed PyDriver to PyNmodlDriver - PerfStat and TableData moved under utils namespace - renamed version to Version - introduce visitor namespace - All visitors were initially in nmodl space - add visitor namespace for all visitors - Rename CnexpSolveVisitor to NeuronSolveVisitor - Introduce new namespace printer and update documentation. - Introduce test_utils namespace in test directory - lambda capture : change this with & in code generation backend - make asRoot private member in nmodl driver and rename ast() to get_ast() - rename is_symbol_external_variable() to is_external_variable() NMODL Repo SHA: BlueBrain/nmodl@fa46e071420ee02b686f06af45ae5a7790f754d5 --- README.md | 4 +- cmake/nmodl/hpc-coding-conventions | 2 +- docs/nmodl/transpiler/Doxyfile.in | 3 +- docs/nmodl/transpiler/DoxygenLayout.xml | 226 ++++ docs/nmodl/transpiler/contents/visitors.rst | 8 +- docs/nmodl/transpiler/doxygen_nmodl.css | 21 + docs/nmodl/transpiler/footer.html | 7 +- docs/nmodl/transpiler/logo.graffle | Bin 2378 -> 3041 bytes docs/nmodl/transpiler/logo.pdf | Bin 18339 -> 0 bytes .../notebooks/nmodl-kinetic-schemes.ipynb | 4 +- .../notebooks/nmodl-python-tutorial.ipynb | 54 +- .../notebooks/nmodl-sympy-conductance.ipynb | 2 +- .../notebooks/nmodl-sympy-solver.ipynb | 2 +- src/nmodl/ast/ast_common.hpp | 970 ++++++++++++++++-- src/nmodl/codegen/codegen_acc_visitor.cpp | 4 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 14 +- src/nmodl/codegen/codegen_c_visitor.cpp | 88 +- src/nmodl/codegen/codegen_c_visitor.hpp | 54 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 4 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 14 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 12 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 24 +- src/nmodl/codegen/codegen_info.cpp | 2 + src/nmodl/codegen/codegen_info.hpp | 12 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 24 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 20 +- src/nmodl/codegen/codegen_naming.hpp | 1 + src/nmodl/codegen/codegen_omp_visitor.hpp | 14 +- src/nmodl/codegen/fast_math.ispc | 7 +- src/nmodl/config/config.cpp.in | 4 +- src/nmodl/config/config.h | 16 +- src/nmodl/language/code_generator.py | 2 +- src/nmodl/language/nmodl.yaml | 6 +- src/nmodl/language/nodes.py | 46 +- src/nmodl/language/parser.py | 6 +- src/nmodl/language/templates/ast/ast.cpp | 24 +- src/nmodl/language/templates/ast/ast.hpp | 297 +++++- src/nmodl/language/templates/ast/ast_decl.hpp | 51 +- src/nmodl/language/templates/pybind/pyast.cpp | 257 +++-- src/nmodl/language/templates/pybind/pyast.hpp | 96 +- .../language/templates/pybind/pysymtab.cpp | 318 +++--- .../language/templates/pybind/pyvisitor.cpp | 112 +- .../language/templates/pybind/pyvisitor.hpp | 29 +- .../templates/visitors/ast_visitor.cpp | 2 + .../templates/visitors/ast_visitor.hpp | 31 +- .../templates/visitors/json_visitor.cpp | 2 + .../templates/visitors/json_visitor.hpp | 62 +- .../templates/visitors/lookup_visitor.cpp | 10 +- .../templates/visitors/lookup_visitor.hpp | 70 +- .../templates/visitors/nmodl_visitor.cpp | 2 + .../templates/visitors/nmodl_visitor.hpp | 82 +- .../templates/visitors/symtab_visitor.cpp | 2 + .../templates/visitors/symtab_visitor.hpp | 52 +- .../language/templates/visitors/visitor.hpp | 28 +- src/nmodl/lexer/c11_lexer.hpp | 6 +- src/nmodl/lexer/main_c.cpp | 2 +- src/nmodl/lexer/main_nmodl.cpp | 2 +- src/nmodl/lexer/main_units.cpp | 2 +- src/nmodl/lexer/modl.h | 2 +- src/nmodl/lexer/modtoken.hpp | 5 + src/nmodl/lexer/token_mapping.cpp | 480 +++++---- src/nmodl/lexer/unit_lexer.hpp | 6 + src/nmodl/lexer/verbatim.l | 8 +- src/nmodl/nmodl/main.cpp | 20 +- src/nmodl/parser/c11.yy | 4 +- src/nmodl/parser/c11_driver.hpp | 7 + src/nmodl/parser/diffeq.yy | 4 +- src/nmodl/parser/diffeq_driver.hpp | 20 +- src/nmodl/parser/diffeq_helper.hpp | 6 +- src/nmodl/parser/main_c.cpp | 2 +- src/nmodl/parser/main_nmodl.cpp | 2 +- src/nmodl/parser/main_units.cpp | 2 +- src/nmodl/parser/nmodl.yy | 12 +- src/nmodl/parser/nmodl_driver.cpp | 19 +- src/nmodl/parser/nmodl_driver.hpp | 51 +- src/nmodl/parser/unit.yy | 4 +- src/nmodl/parser/unit_driver.hpp | 11 + src/nmodl/parser/verbatim.yy | 33 +- ...rbatim_context.hpp => verbatim_driver.hpp} | 28 +- src/nmodl/printer/code_printer.cpp | 4 +- src/nmodl/printer/code_printer.hpp | 32 +- src/nmodl/printer/json_printer.cpp | 2 + src/nmodl/printer/json_printer.hpp | 43 +- src/nmodl/printer/nmodl_printer.cpp | 2 + src/nmodl/printer/nmodl_printer.hpp | 25 +- src/nmodl/pybind/pybind_utils.hpp | 1 + src/nmodl/pybind/pynmodl.cpp | 96 +- src/nmodl/solver/newton/newton.hpp | 94 +- src/nmodl/symtab/symbol.cpp | 57 +- src/nmodl/symtab/symbol.hpp | 288 ++++-- src/nmodl/symtab/symbol_properties.cpp | 10 - src/nmodl/symtab/symbol_properties.hpp | 176 ++-- src/nmodl/symtab/symbol_table.cpp | 51 +- src/nmodl/symtab/symbol_table.hpp | 189 ++-- src/nmodl/units/units.cpp | 26 +- src/nmodl/units/units.hpp | 17 + src/nmodl/utils/common_utils.cpp | 4 +- src/nmodl/utils/common_utils.hpp | 27 +- src/nmodl/utils/logger.cpp | 8 +- src/nmodl/utils/logger.hpp | 8 +- src/nmodl/utils/perf_stat.cpp | 2 + src/nmodl/utils/perf_stat.hpp | 22 +- src/nmodl/utils/string_utils.hpp | 22 +- src/nmodl/utils/table_data.cpp | 9 +- src/nmodl/utils/table_data.hpp | 24 +- src/nmodl/visitors/CMakeLists.txt | 4 +- .../visitors/constant_folder_visitor.cpp | 2 + .../visitors/constant_folder_visitor.hpp | 30 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 9 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 66 +- src/nmodl/visitors/inline_visitor.cpp | 7 +- src/nmodl/visitors/inline_visitor.hpp | 79 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 34 +- src/nmodl/visitors/kinetic_block_visitor.hpp | 21 +- .../visitors/local_var_rename_visitor.cpp | 2 + .../visitors/local_var_rename_visitor.hpp | 24 +- src/nmodl/visitors/localize_visitor.cpp | 9 +- src/nmodl/visitors/localize_visitor.hpp | 23 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 5 +- src/nmodl/visitors/loop_unroll_visitor.hpp | 46 +- src/nmodl/visitors/main.cpp | 30 +- ...e_visitor.cpp => neuron_solve_visitor.cpp} | 18 +- ...e_visitor.hpp => neuron_solve_visitor.hpp} | 36 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 7 +- src/nmodl/visitors/perf_visitor.cpp | 26 +- src/nmodl/visitors/perf_visitor.hpp | 54 +- src/nmodl/visitors/rename_visitor.cpp | 2 + src/nmodl/visitors/rename_visitor.hpp | 21 +- src/nmodl/visitors/solve_block_visitor.cpp | 5 +- src/nmodl/visitors/solve_block_visitor.hpp | 22 +- src/nmodl/visitors/steadystate_visitor.cpp | 9 +- src/nmodl/visitors/steadystate_visitor.hpp | 54 +- .../visitors/sympy_conductance_visitor.cpp | 9 +- .../visitors/sympy_conductance_visitor.hpp | 66 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 54 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 38 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 9 +- src/nmodl/visitors/var_usage_visitor.cpp | 2 + src/nmodl/visitors/var_usage_visitor.hpp | 16 +- .../visitors/verbatim_var_rename_visitor.cpp | 7 +- .../visitors/verbatim_var_rename_visitor.hpp | 32 +- src/nmodl/visitors/verbatim_visitor.cpp | 2 + src/nmodl/visitors/verbatim_visitor.hpp | 19 +- src/nmodl/visitors/visitor_utils.cpp | 23 +- src/nmodl/visitors/visitor_utils.hpp | 40 +- test/nmodl/transpiler/CMakeLists.txt | 8 +- test/nmodl/transpiler/parser/parser.cpp | 4 +- test/nmodl/transpiler/printer/printer.cpp | 8 +- test/nmodl/transpiler/pybind/conftest.py | 2 +- test/nmodl/transpiler/pybind/test_ast.py | 2 +- test/nmodl/transpiler/pybind/test_symtab.py | 4 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 13 +- test/nmodl/transpiler/units/parser.cpp | 2 + .../transpiler/utils/nmodl_constructs.cpp | 67 +- .../transpiler/utils/nmodl_constructs.hpp | 12 +- test/nmodl/transpiler/utils/test_utils.cpp | 6 + test/nmodl/transpiler/utils/test_utils.hpp | 8 +- test/nmodl/transpiler/visitor/visitor.cpp | 169 ++- 158 files changed, 4498 insertions(+), 1950 deletions(-) create mode 100644 docs/nmodl/transpiler/DoxygenLayout.xml delete mode 100644 docs/nmodl/transpiler/logo.pdf rename src/nmodl/parser/{verbatim_context.hpp => verbatim_driver.hpp} (70%) rename src/nmodl/visitors/{cnexp_solve_visitor.cpp => neuron_solve_visitor.cpp} (84%) rename src/nmodl/visitors/{cnexp_solve_visitor.hpp => neuron_solve_visitor.hpp} (65%) diff --git a/README.md b/README.md index 2a0831aed1..5e956a2af0 100644 --- a/README.md +++ b/README.md @@ -128,9 +128,7 @@ We can use nmodl module from python as: $ python3 >>> import nmodl.dsl as nmodl >>> driver = nmodl.NmodlDriver() ->>> driver.parse_string("NEURON { SUFFIX hh }") -True ->>> modast = driver.ast() +>>> modast = driver.parse_string("NEURON { SUFFIX hh }") >>> print ('%.100s' % modast) {"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]}, ``` diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 6b2f7346db..72226841ce 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 6b2f7346db5cf5d438aa35ee16d4cb7fe5a71e46 +Subproject commit 72226841ce176b11512468e407aff03c07e603ba diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index b4c0b9f330..192d31e419 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -726,7 +726,7 @@ FILE_VERSION_FILTER = # DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE # tag is left empty. -LAYOUT_FILE = +LAYOUT_FILE = DoxygenLayout.xml # The CITE_BIB_FILES tag can be used to specify one or more bib files containing # the reference definitions. This must be a list of .bib files. The .bib @@ -2160,6 +2160,7 @@ SKIP_FUNCTION_MACROS = YES # run, you must also specify the path to the tagfile here. TAGFILES = +# TAGFILES += "cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/" # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to diff --git a/docs/nmodl/transpiler/DoxygenLayout.xml b/docs/nmodl/transpiler/DoxygenLayout.xml new file mode 100644 index 0000000000..ed94bbe4e0 --- /dev/null +++ b/docs/nmodl/transpiler/DoxygenLayout.xml @@ -0,0 +1,226 @@ +<doxygenlayout version="1.0"> + <!-- Generated by doxygen 1.8.15 --> + <!-- Navigation index tabs for HTML output --> + <navindex> + <tab type="mainpage" visible="yes" title="Overview"/> + <tab type="pages" visible="yes" title="Tutorials" intro=""/> + <tab type="modules" visible="yes" title="Components" intro=""/> + <tab type="namespaces" visible="yes" title=""> + <tab type="namespacelist" visible="yes" title="" intro=""/> + <tab type="namespacemembers" visible="yes" title="" intro=""/> + </tab> + <tab type="interfaces" visible="yes" title=""> + <tab type="interfacelist" visible="yes" title="" intro=""/> + <tab type="interfaceindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="interfacehierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="classes" visible="yes" title=""> + <tab type="classlist" visible="yes" title="" intro=""/> + <tab type="classindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="hierarchy" visible="yes" title="" intro=""/> + <tab type="classmembers" visible="yes" title="" intro=""/> + </tab> + <tab type="structs" visible="yes" title=""> + <tab type="structlist" visible="yes" title="" intro=""/> + <tab type="structindex" visible="$ALPHABETICAL_INDEX" title=""/> + </tab> + <tab type="exceptions" visible="yes" title=""> + <tab type="exceptionlist" visible="yes" title="" intro=""/> + <tab type="exceptionindex" visible="$ALPHABETICAL_INDEX" title=""/> + <tab type="exceptionhierarchy" visible="yes" title="" intro=""/> + </tab> + <tab type="files" visible="yes" title=""> + <tab type="filelist" visible="yes" title="" intro=""/> + <tab type="globals" visible="yes" title="" intro=""/> + </tab> + <tab type="examples" visible="yes" title="" intro=""/> + </navindex> + + <!-- Layout definition for a class page --> + <class> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <inheritancegraph visible="$CLASS_GRAPH"/> + <collaborationgraph visible="$COLLABORATION_GRAPH"/> + <memberdecl> + <nestedclasses visible="yes" title=""/> + <publictypes title=""/> + <services title=""/> + <interfaces title=""/> + <publicslots title=""/> + <signals title=""/> + <publicmethods title=""/> + <publicstaticmethods title=""/> + <publicattributes title=""/> + <publicstaticattributes title=""/> + <protectedtypes title=""/> + <protectedslots title=""/> + <protectedmethods title=""/> + <protectedstaticmethods title=""/> + <protectedattributes title=""/> + <protectedstaticattributes title=""/> + <packagetypes title=""/> + <packagemethods title=""/> + <packagestaticmethods title=""/> + <packageattributes title=""/> + <packagestaticattributes title=""/> + <properties title=""/> + <events title=""/> + <privatetypes title=""/> + <privateslots title=""/> + <privatemethods title=""/> + <privatestaticmethods title=""/> + <privateattributes title=""/> + <privatestaticattributes title=""/> + <friends title=""/> + <related title="" subtitle=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <enums title=""/> + <services title=""/> + <interfaces title=""/> + <constructors title=""/> + <functions title=""/> + <related title=""/> + <variables title=""/> + <properties title=""/> + <events title=""/> + </memberdef> + <allmemberslink visible="yes"/> + <usedfiles visible="$SHOW_USED_FILES"/> + <authorsection visible="yes"/> + </class> + + <!-- Layout definition for a namespace page --> + <namespace> + <briefdescription visible="yes"/> + <memberdecl> + <nestednamespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection visible="yes"/> + </namespace> + + <!-- Layout definition for a file page --> + <file> + <briefdescription visible="yes"/> + <includes visible="$SHOW_INCLUDE_FILES"/> + <includegraph visible="$INCLUDE_GRAPH"/> + <includedbygraph visible="$INCLUDED_BY_GRAPH"/> + <sourcelink visible="yes"/> + <memberdecl> + <interfaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <structs visible="yes" title=""/> + <exceptions visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <constantgroups visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <functions title=""/> + <variables title=""/> + </memberdef> + <authorsection/> + </file> + + <!-- Layout definition for a group page --> + <group> + <briefdescription visible="yes"/> + <groupgraph visible="$GROUP_GRAPHS"/> + <memberdecl> + <nestedgroups visible="yes" title=""/> + <dirs visible="yes" title=""/> + <files visible="yes" title=""/> + <namespaces visible="yes" title=""/> + <classes visible="yes" title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + <membergroups visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + <memberdef> + <pagedocs/> + <inlineclasses title=""/> + <defines title=""/> + <typedefs title=""/> + <sequences title=""/> + <dictionaries title=""/> + <enums title=""/> + <enumvalues title=""/> + <functions title=""/> + <variables title=""/> + <signals title=""/> + <publicslots title=""/> + <protectedslots title=""/> + <privateslots title=""/> + <events title=""/> + <properties title=""/> + <friends title=""/> + </memberdef> + <authorsection visible="yes"/> + </group> + + <!-- Layout definition for a directory page --> + <directory> + <briefdescription visible="yes"/> + <directorygraph visible="yes"/> + <memberdecl> + <dirs visible="yes"/> + <files visible="yes"/> + </memberdecl> + <detaileddescription title=""/> + </directory> +</doxygenlayout> diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index dbb21d56e9..cbcd5cc596 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -51,19 +51,17 @@ Now we can parse any valid NMODL constructs using parsing interface. First, we have to create nmodl parser object using :class:`nmodl.NmodlDriver` and then we can use :func:`nmodl.NmodlDriver.parse_string` method: >>> driver = nmodl.NmodlDriver() - >>> driver.parse_string(channel) - True + >>> modast = driver.parse_string(channel) The :func:`nmodl.NmodlDriver.parse_string` method will throw an exception with parsing error if the input is invalid. -Otherwise it returns True and internally creates :class:`nmodl.ast.AST` object. We can access the AST using :func:`nmodl.NmodlDriver.ast` method: - - >>> modast = driver.ast() +Otherwise it return :class:`nmodl.ast.AST` object. If we simply print AST object, we can see JSON representation: >>> print ('%.100s' % modast) # only first 100 characters {"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]}, + Querying AST objects with Visitors =========== diff --git a/docs/nmodl/transpiler/doxygen_nmodl.css b/docs/nmodl/transpiler/doxygen_nmodl.css index 29b25564e0..4ccb451132 100644 --- a/docs/nmodl/transpiler/doxygen_nmodl.css +++ b/docs/nmodl/transpiler/doxygen_nmodl.css @@ -49,3 +49,24 @@ span.comment { span.keyword { color: #152292; } + +/* +body { + max-width: 1440px; + margin: auto; + background: #404040; +} + +#side-nav { + left: auto; + padding-left: 10px; + padding-right: 15px; + background-image: url(background.png); +} + +div#titlearea { + background: white; + background-image: url(background.png); +} +*/ + diff --git a/docs/nmodl/transpiler/footer.html b/docs/nmodl/transpiler/footer.html index 3b35440ffe..2a0c0bd4f2 100644 --- a/docs/nmodl/transpiler/footer.html +++ b/docs/nmodl/transpiler/footer.html @@ -6,8 +6,11 @@ $navpath </ul> </div> -<hr class="footer"/><address class="footer"><small> -</small></address> +<hr class="footer"/> +<address class="footer"> + <small> + </small> +</address> <!--END !GENERATE_TREEVIEW--> </body> </html> diff --git a/docs/nmodl/transpiler/logo.graffle b/docs/nmodl/transpiler/logo.graffle index 2db9a94657f8c53c0dac624fcc7a043d3de39ee0..0c6ac7f6e9c239655fd0a25f5f51dc3d556f10a4 100644 GIT binary patch literal 3041 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 literal 2378 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 diff --git a/docs/nmodl/transpiler/logo.pdf b/docs/nmodl/transpiler/logo.pdf deleted file mode 100644 index a6ebe577c612071c594f7d1805f2c63e48ba30d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18339 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 diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb index 218e8dc747..e85e883549 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -153,7 +153,7 @@ " # parse NMDOL file (supplied as a string) into AST\n", " driver = nmodl.NmodlDriver()\n", " driver.parse_string(mod_string)\n", - " AST = driver.ast()\n", + " AST = driver.get_ast()\n", " # run SymtabVisitor to generate Symbol Table\n", " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", " # constant folding, inlining & local variable renaming passes\n", @@ -441,7 +441,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.2" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index 53f790f783..0332530e2a 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -474,37 +474,17 @@ "cell_type": "code", "execution_count": 7, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "driver = nmodl.NmodlDriver()\n", - "driver.parse_string(channel)" + "modast = driver.parse_string(channel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The `parse_string` method will throw an exception with parsing error if input is invalid. Otherwise it returns `True` and internally creates [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) object. We can access the AST using `ast()` method :" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ - "modast = driver.ast()" + "The `parse_string` method will throw an exception with parsing error if input is invalid. Otherwise it returns [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) object." ] }, { @@ -516,7 +496,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -537,7 +517,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -551,7 +531,7 @@ "<IPython.core.display.Javascript object>" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -585,7 +565,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -603,7 +583,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -633,7 +613,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -694,7 +674,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -738,7 +718,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -756,7 +736,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -814,7 +794,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -845,7 +825,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -874,7 +854,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -905,7 +885,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -945,7 +925,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 20, "metadata": {}, "outputs": [ { diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb index f205a3584d..0d913262f8 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb @@ -94,7 +94,7 @@ " # parse NMDOL file (supplied as a string) into AST\n", " driver = nmodl.NmodlDriver()\n", " driver.parse_string(mod_string)\n", - " AST = driver.ast()\n", + " AST = driver.get_ast()\n", " # run SymtabVisitor to generate Symbol Table\n", " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", " # constant folding, inlining & local variable renaming passes\n", diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb index ac4ebfcccf..0771d5dfa6 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb @@ -151,7 +151,7 @@ " # parse NMDOL file (supplied as a string)\n", " driver = nmodl.NmodlDriver()\n", " driver.parse_string(mod_string)\n", - " modast = driver.ast()\n", + " modast = driver.get_ast()\n", " # run SymtabVisitor to generate Symbol Table\n", " symv = symtab.SymtabVisitor()\n", " symv.visit_program(modast)\n", diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 8b0ec15f37..9ea78b1c15 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -7,6 +7,14 @@ #pragma once +/** + * \dir + * \brief AST Implementation + * + * \file + * \brief Implementation of AST base class and it's properties + */ + #include <memory> #include <string> @@ -16,678 +24,1458 @@ #include "visitors/visitor.hpp" namespace nmodl { + +/// Abstract Syntax Tree (AST) related implementations namespace ast { -/* enumaration of all binary operators in the language */ +/** + * @defgroup ast AST Infrastructure + * @brief All AST related implementation details + * + * @defgroup ast_prop AST Properties + * @ingroup ast + * @brief Properties used with different members of AST classes + * @{ + */ + +/** + * \brief enum type for binary operators in NMODL + * + * NMODL support different binary operators and this + * type is used to store their value in the AST. + */ typedef enum { - BOP_ADDITION, - BOP_SUBTRACTION, - BOP_MULTIPLICATION, - BOP_DIVISION, - BOP_POWER, - BOP_AND, - BOP_OR, - BOP_GREATER, - BOP_LESS, - BOP_GREATER_EQUAL, - BOP_LESS_EQUAL, - BOP_ASSIGN, - BOP_NOT_EQUAL, - BOP_EXACT_EQUAL + BOP_ADDITION, ///< \+ + BOP_SUBTRACTION, ///< -- + BOP_MULTIPLICATION, ///< \c * + BOP_DIVISION, ///< \/ + BOP_POWER, ///< ^ + BOP_AND, ///< && + BOP_OR, ///< || + BOP_GREATER, ///< > + BOP_LESS, ///< < + BOP_GREATER_EQUAL, ///< >= + BOP_LESS_EQUAL, ///< <= + BOP_ASSIGN, ///< = + BOP_NOT_EQUAL, ///< != + BOP_EXACT_EQUAL ///< == } BinaryOp; -static const std::string BinaryOpNames[] = {"+", "-", "*", "/", "^", "&&", "||", - ">", "<", ">=", "<=", "=", "!=", "=="}; +/** + * \brief string representation of ast::BinaryOp + * + * When AST is converted back to NMODL or C code, ast::BinaryOpNames + * is used to lookup the corresponding symbol for the operator. + */ +static const std::string BinaryOpNames[] = + {"+", "-", "*", "/", "^", "&&", "||", ">", "<", ">=", "<=", "=", "!=", "=="}; -/* enumaration of all unary operators in the language */ +/// enum type for unary operators typedef enum { UOP_NOT, UOP_NEGATION } UnaryOp; + +/// string representation of ast::UnaryOp static const std::string UnaryOpNames[] = {"!", "-"}; -/* enumaration of types used in partial equation */ +/// enum type for partial equation types typedef enum { PEQ_FIRST, PEQ_LAST } FirstLastType; + +/// string representation of ast::FirstLastType static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; -/* enumaration of queue types */ +/// enum type for queue types typedef enum { PUT_QUEUE, GET_QUEUE } QueueType; + +/// string representation of ast::QueueType static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; -/* enumaration of type used for BEFORE or AFTER block */ +/// enum type to distinguish BEFORE or AFTER blocks typedef enum { BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP } BAType; + +/// string representation of ast::BAType static const std::string BATypeNames[] = {"BREAKPOINT", "SOLVE", "INITIAL", "STEP"}; -/* enumaration of type used for UNIT_ON or UNIT_OFF state*/ +/// enum type used for UNIT_ON or UNIT_OFF state typedef enum { UNIT_ON, UNIT_OFF } UnitStateType; + +/// string representation of ast::UnitStateType static const std::string UnitStateTypeNames[] = {"UNITSON", "UNITSOFF"}; -/* enumaration of type used for Reaction statement */ +/// enum type used for Reaction statement typedef enum { LTMINUSGT, LTLT, MINUSGT } ReactionOp; + +/// string representation of ast::ReactionOp static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; -/* enum class for ast types */ -enum class Type; +/** @} */ // end of ast_prop -/* define abstract base class for all AST nodes - * this also serves to define the visitable objects. + +/** + * \page ast_design Design of Abstract Syntax Tree (AST) + * + * This page describes the AST design aspects. + * + * \tableofcontents + * + * \section sec_1 AST Class Hierarchy + * This section describes the AST design + * + * \section sec_2 Block Scoped Nodes + * This section describes block scoped nodes. + * + * \section sec_3 Symbol Table Nodes + * This section describes nodes with symbol table. */ -struct AST: public std::enable_shared_from_this<AST> { - /* all AST nodes have a member which stores their - * basetype (int, bool, none, object). Further type - * information will come from the symbol table. - * BaseType basetype; - */ - /* all AST nodes provide visit children and accept methods */ - virtual void visit_children(Visitor* v) = 0; +/** + * @defgroup ast_class AST Classes + * @ingroup ast + * @brief Classes for implementing Abstract Syntax Tree (AST) + * @{ + */ + +/** + * \brief Base class for all Abstract Syntax Tree node types + * + * Every node in the Abstract Syntax Tree is inherited from base class + * ast::Ast. This class provides base properties and pure virtual + * functions that must be implemented by base classes. We inherit from + * std::enable_shared_from_this to get a `shared_ptr` from `this` pointer. + * + * \todo With the ast::Node as another top level node, this can be removed + * in the future. + */ +struct Ast: public std::enable_shared_from_this<Ast> { + /// \name Ctor & dtor + /// \{ + + Ast() = default; + + virtual ~Ast() {} + + /// \} + - virtual void accept(Visitor* v) = 0; + /// \name Pure Virtual Functions + /// \{ + /** + * \brief Return type (ast::AstNodeType) of AST node + * + * Every node in the ast has a type defined in ast::AstNodeType. + * This type is can be used to check/compare node types. + */ virtual AstNodeType get_node_type() = 0; + /** + * \brief Return type (ast::AstNodeType) of ast node as std::string + * + * Every node in the ast has a type defined in ast::AstNodeType. + * This type name can be returned as a std::string for printing + * ast to text/json form. + * + * @return name of the node type as a string + * + * \sa Ast::get_node_name + */ virtual std::string get_node_type_name() = 0; - virtual std::string get_node_name() { - throw std::logic_error("get_node_name() not implemented"); + /** + * \brief Accept (or visit) the AST node using current visitor + * + * Instead of visiting children of AST node, like Ast::visit_children, + * accept allows to visit the current node itself using the concrete + * visitor provided. + * + * @param v Concrete visitor that will be used to recursively visit children + * + * \note Below is an example of `accept` method implementation which shows how + * visitor method corresponding to ast::IndexedName node is called allowing + * to visit the node itself in the visitor. + * + * \code{.cpp} + * void IndexedName::accept(visitor::Visitor* v) override { + * v->visit_indexed_name(this); + * } + * \endcode + * + */ + virtual void accept(visitor::Visitor* v) = 0; + + /** + * \brief Visit children i.e. member of AST node using provided visitor + * + * Different nodes in the AST have different members (i.e. children). This method + * recursively visits children using provided concrete visitor. + * + * @param v Concrete visitor that will be used to recursively visit node + * + * \note Below is an example of `visit_children` method implementation which shows + * ast::IndexedName node children are visited instead of node itself. + * + * \code{.cpp} + * void IndexedName::visit_children(visitor::Visitor* v) { + * name->accept(v); + * length->accept(v); + * } + * \endcode + */ + virtual void visit_children(visitor::Visitor* v) = 0; + + /** + * \brief Create a copy of the current node + * + * Recursively make a new copy/clone of the current node including + * all members and return a pointer to the node. This is used for + * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the + * ast. + * + * @return pointer to the clone/copy of the current node + */ + virtual Ast* clone() { + throw std::logic_error("clone not implemented"); } - virtual AST* clone() { - throw std::logic_error("clone() not implemented"); - } + /// \} - /* @todo: revisit, adding quickly for symtab */ - virtual ModToken* get_token() { /*std::cout << "\n ERROR: get_token not implemented!";*/ - return nullptr; - } - virtual void set_symbol_table(symtab::SymbolTable* newsymtab) { - throw std::runtime_error("set_symbol_table() not implemented"); + /// \name Not implemented + /// \{ + + /** + * \brief Return name of of the node + * + * Some ast nodes have a member marked designated as node name. For example, + * in case of ast::FunctionCall, name of the function is returned as a node + * name. Note that this is different from ast node type name and not every + * ast node has name. + * + * @return name of the node as std::string + * + * \sa Ast::get_node_type_name + */ + virtual std::string get_node_name() { + throw std::logic_error("get_node_name() not implemented"); } - virtual symtab::SymbolTable* get_symbol_table() { - throw std::runtime_error("get_symbol_table() not implemented"); + /** + * \brief Return associated token for the AST node + * + * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor + * can insert new nodes in the ast as a solution of ODEs. In this case, we return + * nullptr to store in the nmodl::symtab::SymbolTable. + * + * @return pointer to token if exist otherwise nullptr + */ + virtual ModToken* get_token() { + return nullptr; } + /** + * \brief Return associated symbol table for the AST node + * + * Certain ast nodes (e.g. inherited from ast::Block) have associated + * symbol table. These nodes have nmodl::symtab::SymbolTable as member + * and it can be accessed using this method. + * + * @return pointer to the symbol table + * + * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor + */ + virtual symtab::SymbolTable* get_symbol_table() { + throw std::runtime_error("get_symbol_table not implemented"); + } + + /** + * \brief Return associated statement block for the AST node + * + * Top level block nodes encloses all statements in the ast::StatementBlock. + * For example, ast::BreakpointBlock has all statements in curly brace (`{ }`) + * stored in ast::StatementBlock : + * + * \code + * BREAKPOINT { + * SOLVE states METHOD cnexp + * gNaTs2_t = gNaTs2_tbar*m*m*m*h + * ina = gNaTs2_t*(v-ena) + * } + * \endcode + * + * This method return enclosing statement block. + * + * @return pointer to the statement block if exist + * + * \sa ast::StatementBlock + */ virtual std::shared_ptr<StatementBlock> get_statement_block() { throw std::runtime_error("get_statement_block not implemented"); } - // implemented in Number sub classes - virtual void negate() { - throw std::runtime_error("negate() not implemented"); - } - - // implemented in Identifier sub classes + /** + * \brief Set symbol table for the AST node + * + * Top level, block scoped nodes store symbol table in the ast node. + * nmodl::visitor::SymtabVisitor then used this method to setup symbol table + * for every node in the ast. + * + * \sa nmodl::visitor::SymtabVisitor + */ + virtual void set_symbol_table(symtab::SymbolTable* /*symtab*/) { + throw std::runtime_error("set_symbol_table not implemented"); + } + + /** + * \brief Set name for the AST node + * + * Some ast nodes have a member marked designated as node name (e.g. nodes + * derived from ast::Identifier). This method is used to set new name for those + * nodes. This useful for passes like nmodl::visitor::RenameVisitor. + * + * \sa Ast::get_node_type_name Ast::get_node_name + */ virtual void set_name(std::string /*name*/) { - throw std::runtime_error("set_name() not implemented"); + throw std::runtime_error("set_name not implemented"); } - virtual ~AST() {} + /** + * \brief Negate the value of AST node + * + * Parser parse `-x` in two parts : `x` and then `-`. Once second token + * `-` is encountered, the corresponding value of ast node needs to be + * multiplied by `-1` for ast::Number node types or apply `!` operator + for the nodes of type ast::Boolean. + */ + virtual void negate() { + throw std::runtime_error("negate not implemented"); + } + /// \} - virtual std::shared_ptr<AST> get_shared_ptr() { - return std::static_pointer_cast<AST>(shared_from_this()); + /// get std::shared_ptr from `this` pointer of the AST node + virtual std::shared_ptr<Ast> get_shared_ptr() { + return std::static_pointer_cast<Ast>(shared_from_this()); } + /** + *\brief Check if the ast node is an instance of ast::Ast + * @return true if object of type ast::Ast + */ virtual bool is_ast() { return true; } + /** + *\brief Check if the ast node is an instance of ast::Node + * @return true if object of type ast::Node + */ + virtual bool is_node() { + return false; + } + + /** + *\brief Check if the ast node is an instance of ast::Statement + * @return true if object of type ast::Statement + */ virtual bool is_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Expression + * @return true if object of type ast::Expression + */ virtual bool is_expression() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Block + * @return true if object of type ast::Block + */ virtual bool is_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Identifier + * @return true if object of type ast::Identifier + */ virtual bool is_identifier() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Number + * @return true if object of type ast::Number + */ virtual bool is_number() { return false; } + /** + *\brief Check if the ast node is an instance of ast::String + * @return true if object of type ast::String + */ virtual bool is_string() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Integer + * @return true if object of type ast::Integer + */ virtual bool is_integer() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Float + * @return true if object of type ast::Float + */ virtual bool is_float() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Double + * @return true if object of type ast::Double + */ virtual bool is_double() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Boolean + * @return true if object of type ast::Boolean + */ virtual bool is_boolean() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Name + * @return true if object of type ast::Name + */ virtual bool is_name() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PrimeName + * @return true if object of type ast::PrimeName + */ virtual bool is_prime_name() { return false; } + /** + *\brief Check if the ast node is an instance of ast::VarName + * @return true if object of type ast::VarName + */ virtual bool is_var_name() { return false; } + /** + *\brief Check if the ast node is an instance of ast::IndexedName + * @return true if object of type ast::IndexedName + */ virtual bool is_indexed_name() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Argument + * @return true if object of type ast::Argument + */ virtual bool is_argument() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ReactVarName + * @return true if object of type ast::ReactVarName + */ virtual bool is_react_var_name() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ReadIonVar + * @return true if object of type ast::ReadIonVar + */ virtual bool is_read_ion_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::WriteIonVar + * @return true if object of type ast::WriteIonVar + */ virtual bool is_write_ion_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::NonspecificCurVar + * @return true if object of type ast::NonspecificCurVar + */ virtual bool is_nonspecific_cur_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ElectrodeCurVar + * @return true if object of type ast::ElectrodeCurVar + */ virtual bool is_electrode_cur_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::SectionVar + * @return true if object of type ast::SectionVar + */ virtual bool is_section_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::RangeVar + * @return true if object of type ast::RangeVar + */ virtual bool is_range_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::GlobalVar + * @return true if object of type ast::GlobalVar + */ virtual bool is_global_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PointerVar + * @return true if object of type ast::PointerVar + */ virtual bool is_pointer_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BbcorePointerVar + * @return true if object of type ast::BbcorePointerVar + */ virtual bool is_bbcore_pointer_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ExternVar + * @return true if object of type ast::ExternVar + */ virtual bool is_extern_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ThreadsafeVar + * @return true if object of type ast::ThreadsafeVar + */ virtual bool is_threadsafe_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ParamBlock + * @return true if object of type ast::ParamBlock + */ virtual bool is_param_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::StepBlock + * @return true if object of type ast::StepBlock + */ virtual bool is_step_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::IndependentBlock + * @return true if object of type ast::IndependentBlock + */ virtual bool is_independent_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DependentBlock + * @return true if object of type ast::DependentBlock + */ virtual bool is_dependent_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::StateBlock + * @return true if object of type ast::StateBlock + */ virtual bool is_state_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PlotBlock + * @return true if object of type ast::PlotBlock + */ virtual bool is_plot_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::InitialBlock + * @return true if object of type ast::InitialBlock + */ virtual bool is_initial_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ConstructorBlock + * @return true if object of type ast::ConstructorBlock + */ virtual bool is_constructor_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DestructorBlock + * @return true if object of type ast::DestructorBlock + */ virtual bool is_destructor_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::StatementBlock + * @return true if object of type ast::StatementBlock + */ virtual bool is_statement_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DerivativeBlock + * @return true if object of type ast::DerivativeBlock + */ virtual bool is_derivative_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LinearBlock + * @return true if object of type ast::LinearBlock + */ virtual bool is_linear_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::NonLinearBlock + * @return true if object of type ast::NonLinearBlock + */ virtual bool is_non_linear_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DiscreteBlock + * @return true if object of type ast::DiscreteBlock + */ virtual bool is_discrete_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PartialBlock + * @return true if object of type ast::PartialBlock + */ virtual bool is_partial_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::FunctionTableBlock + * @return true if object of type ast::FunctionTableBlock + */ virtual bool is_function_table_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::FunctionBlock + * @return true if object of type ast::FunctionBlock + */ virtual bool is_function_block() { return false; } - virtual bool is_eigen_newton_solver_block() { - return false; - } - - virtual bool is_nrn_state_block() { - return false; - } - - virtual bool is_solution_expression() { - return false; - } - - virtual bool is_derivimplicit_callback() { - return false; - } - - virtual bool is_eigen_linear_solver_block() { - return false; - } - + /** + *\brief Check if the ast node is an instance of ast::ProcedureBlock + * @return true if object of type ast::ProcedureBlock + */ virtual bool is_procedure_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::NetReceiveBlock + * @return true if object of type ast::NetReceiveBlock + */ virtual bool is_net_receive_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::SolveBlock + * @return true if object of type ast::SolveBlock + */ virtual bool is_solve_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BreakpointBlock + * @return true if object of type ast::BreakpointBlock + */ virtual bool is_breakpoint_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::TerminalBlock + * @return true if object of type ast::TerminalBlock + */ virtual bool is_terminal_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BeforeBlock + * @return true if object of type ast::BeforeBlock + */ virtual bool is_before_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::AfterBlock + * @return true if object of type ast::AfterBlock + */ virtual bool is_after_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BABlock + * @return true if object of type ast::BABlock + */ virtual bool is_ba_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ForNetcon + * @return true if object of type ast::ForNetcon + */ virtual bool is_for_netcon() { return false; } + /** + *\brief Check if the ast node is an instance of ast::KineticBlock + * @return true if object of type ast::KineticBlock + */ virtual bool is_kinetic_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::MatchBlock + * @return true if object of type ast::MatchBlock + */ virtual bool is_match_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::UnitBlock + * @return true if object of type ast::UnitBlock + */ virtual bool is_unit_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ConstantBlock + * @return true if object of type ast::ConstantBlock + */ virtual bool is_constant_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::NeuronBlock + * @return true if object of type ast::NeuronBlock + */ virtual bool is_neuron_block() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Unit + * @return true if object of type ast::Unit + */ virtual bool is_unit() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DoubleUnit + * @return true if object of type ast::DoubleUnit + */ virtual bool is_double_unit() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LocalVar + * @return true if object of type ast::LocalVar + */ virtual bool is_local_var() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Limits + * @return true if object of type ast::Limits + */ virtual bool is_limits() { return false; } + /** + *\brief Check if the ast node is an instance of ast::NumberRange + * @return true if object of type ast::NumberRange + */ virtual bool is_number_range() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PlotVar + * @return true if object of type ast::PlotVar + */ virtual bool is_plot_var() { return false; } - virtual bool is_binary_operator() { + /** + *\brief Check if the ast node is an instance of ast::ConstantVar + * @return true if object of type ast::ConstantVar + */ + virtual bool is_constant_var() { return false; } - virtual bool is_wrapped_expression() { + /** + *\brief Check if the ast node is an instance of ast::BinaryOperator + * @return true if object of type ast::BinaryOperator + */ + virtual bool is_binary_operator() { return false; } - virtual bool is_paren_expression() { + /** + *\brief Check if the ast node is an instance of ast::UnaryOperator + * @return true if object of type ast::UnaryOperator + */ + virtual bool is_unary_operator() { return false; } - virtual bool is_unary_operator() { + /** + *\brief Check if the ast node is an instance of ast::ReactionOperator + * @return true if object of type ast::ReactionOperator + */ + virtual bool is_reaction_operator() { return false; } - virtual bool is_reaction_operator() { + /** + *\brief Check if the ast node is an instance of ast::ParenExpression + * @return true if object of type ast::ParenExpression + */ + virtual bool is_paren_expression() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BinaryExpression + * @return true if object of type ast::BinaryExpression + */ virtual bool is_binary_expression() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DiffEqExpression + * @return true if object of type ast::DiffEqExpression + */ + virtual bool is_diff_eq_expression() { + return false; + } + + /** + *\brief Check if the ast node is an instance of ast::UnaryExpression + * @return true if object of type ast::UnaryExpression + */ virtual bool is_unary_expression() { return false; } + /** + *\brief Check if the ast node is an instance of ast::NonLinEquation + * @return true if object of type ast::NonLinEquation + */ virtual bool is_non_lin_equation() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LinEquation + * @return true if object of type ast::LinEquation + */ virtual bool is_lin_equation() { return false; } + /** + *\brief Check if the ast node is an instance of ast::FunctionCall + * @return true if object of type ast::FunctionCall + */ virtual bool is_function_call() { return false; } + /** + *\brief Check if the ast node is an instance of ast::FirstLastTypeIndex + * @return true if object of type ast::FirstLastTypeIndex + */ virtual bool is_first_last_type_index() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Watch + * @return true if object of type ast::Watch + */ virtual bool is_watch() { return false; } + /** + *\brief Check if the ast node is an instance of ast::QueueExpressionType + * @return true if object of type ast::QueueExpressionType + */ virtual bool is_queue_expression_type() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Match + * @return true if object of type ast::Match + */ virtual bool is_match() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BABlockType + * @return true if object of type ast::BABlockType + */ virtual bool is_ba_block_type() { return false; } + /** + *\brief Check if the ast node is an instance of ast::UnitDef + * @return true if object of type ast::UnitDef + */ virtual bool is_unit_def() { return false; } + /** + *\brief Check if the ast node is an instance of ast::FactorDef + * @return true if object of type ast::FactorDef + */ virtual bool is_factor_def() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Valence + * @return true if object of type ast::Valence + */ virtual bool is_valence() { return false; } + /** + *\brief Check if the ast node is an instance of ast::UnitState + * @return true if object of type ast::UnitState + */ virtual bool is_unit_state() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LocalListStatement + * @return true if object of type ast::LocalListStatement + */ virtual bool is_local_list_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Model + * @return true if object of type ast::Model + */ virtual bool is_model() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Define + * @return true if object of type ast::Define + */ virtual bool is_define() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Include + * @return true if object of type ast::Include + */ virtual bool is_include() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ParamAssign + * @return true if object of type ast::ParamAssign + */ virtual bool is_param_assign() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Stepped + * @return true if object of type ast::Stepped + */ virtual bool is_stepped() { return false; } + /** + *\brief Check if the ast node is an instance of ast::IndependentDef + * @return true if object of type ast::IndependentDef + */ virtual bool is_independent_def() { return false; } + /** + *\brief Check if the ast node is an instance of ast::DependentDef + * @return true if object of type ast::DependentDef + */ virtual bool is_dependent_def() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PlotDeclaration + * @return true if object of type ast::PlotDeclaration + */ virtual bool is_plot_declaration() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ConductanceHint + * @return true if object of type ast::ConductanceHint + */ virtual bool is_conductance_hint() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ExpressionStatement + * @return true if object of type ast::ExpressionStatement + */ virtual bool is_expression_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ProtectStatement + * @return true if object of type ast::ProtectStatement + */ virtual bool is_protect_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::FromStatement + * @return true if object of type ast::FromStatement + */ virtual bool is_from_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ForAllStatement + * @return true if object of type ast::ForAllStatement + */ virtual bool is_for_all_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::WhileStatement + * @return true if object of type ast::WhileStatement + */ virtual bool is_while_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::IfStatement + * @return true if object of type ast::IfStatement + */ virtual bool is_if_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ElseIfStatement + * @return true if object of type ast::ElseIfStatement + */ virtual bool is_else_if_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ElseStatement + * @return true if object of type ast::ElseStatement + */ virtual bool is_else_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PartialEquation + * @return true if object of type ast::PartialEquation + */ virtual bool is_partial_equation() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Ast + * @return true if object of type ast::Ast + */ virtual bool is_partial_boundary() { return false; } + /** + *\brief Check if the ast node is an instance of ast::PartialBoundary + * @return true if object of type ast::PartialBoundary + */ virtual bool is_watch_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::MutexLock + * @return true if object of type ast::MutexLock + */ virtual bool is_mutex_lock() { return false; } + /** + *\brief Check if the ast node is an instance of ast::MutexUnlock + * @return true if object of type ast::MutexUnlock + */ virtual bool is_mutex_unlock() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Reset + * @return true if object of type ast::Reset + */ virtual bool is_reset() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Sens + * @return true if object of type ast::Sens + */ virtual bool is_sens() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Conserve + * @return true if object of type ast::Conserve + */ virtual bool is_conserve() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Compartment + * @return true if object of type ast::Compartment + */ virtual bool is_compartment() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LonDifuse + * @return true if object of type ast::LonDifuse + */ virtual bool is_lon_difuse() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ReactionStatement + * @return true if object of type ast::ReactionStatement + */ virtual bool is_reaction_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LagStatement + * @return true if object of type ast::LagStatement + */ virtual bool is_lag_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::QueueStatement + * @return true if object of type ast::QueueStatement + */ virtual bool is_queue_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ConstantStatement + * @return true if object of type ast::ConstantStatement + */ virtual bool is_constant_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::TableStatement + * @return true if object of type ast::TableStatement + */ virtual bool is_table_statement() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Suffix + * @return true if object of type ast::Suffix + */ virtual bool is_suffix() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Useion + * @return true if object of type ast::Useion + */ virtual bool is_useion() { return false; } - /// \todo : how is this different from is_nonspecific_cur_var ? + /** + *\brief Check if the ast node is an instance of ast::Nonspecific + * @return true if object of type ast::Nonspecific + */ virtual bool is_nonspecific() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ElctrodeCurrent + * @return true if object of type ast::ElctrodeCurrent + */ virtual bool is_elctrode_current() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Section + * @return true if object of type ast::Section + */ virtual bool is_section() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Range + * @return true if object of type ast::Range + */ virtual bool is_range() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Global + * @return true if object of type ast::Global + */ virtual bool is_global() { return false; } - /// \todo : how is this different from is_pointer_var ? + /** + *\brief Check if the ast node is an instance of ast::Pointer + * @return true if object of type ast::Pointer + */ virtual bool is_pointer() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BbcorePtr + * @return true if object of type ast::BbcorePtr + */ virtual bool is_bbcore_ptr() { return false; } + /** + *\brief Check if the ast node is an instance of ast::External + * @return true if object of type ast::External + */ virtual bool is_external() { return false; } + /** + *\brief Check if the ast node is an instance of ast::ThreadSafe + * @return true if object of type ast::ThreadSafe + */ virtual bool is_thread_safe() { return false; } + /** + *\brief Check if the ast node is an instance of ast::Verbatim + * @return true if object of type ast::Verbatim + */ virtual bool is_verbatim() { return false; } + /** + *\brief Check if the ast node is an instance of ast::LineComment + * @return true if object of type ast::LineComment + */ virtual bool is_line_comment() { return false; } + /** + *\brief Check if the ast node is an instance of ast::BlockComment + * @return true if object of type ast::BlockComment + */ virtual bool is_block_comment() { return false; } - virtual bool is_node() { + /** + *\brief Check if the ast node is an instance of ast::Program + * @return true if object of type ast::Program + */ + virtual bool is_program() { return false; } - virtual bool is_program() { + /** + *\brief Check if the ast node is an instance of ast::NrnStateBlock + * @return true if object of type ast::NrnStateBlock + */ + virtual bool is_nrn_state_block() { return false; } - virtual bool is_constant_var() { + /** + *\brief Check if the ast node is an instance of ast::EigenNewtonSolverBlock + * @return true if object of type ast::EigenNewtonSolverBlock + */ + virtual bool is_eigen_newton_solver_block() { return false; } - virtual bool is_diff_eq_expression() { + /** + *\brief Check if the ast node is an instance of ast::EigenLinearSolverBlock + * @return true if object of type ast::EigenLinearSolverBlock + */ + virtual bool is_eigen_linear_solver_block() { + return false; + } + + /** + *\brief Check if the ast node is an instance of ast::WrappedExpression + * @return true if object of type ast::WrappedExpression + */ + virtual bool is_wrapped_expression() { + return false; + } + + /** + *\brief Check if the ast node is an instance of ast::DerivimplicitCallback + * @return true if object of type ast::DerivimplicitCallback + */ + virtual bool is_derivimplicit_callback() { + return false; + } + + /** + *\brief Check if the ast node is an instance of ast::SolutionExpression + * @return true if object of type ast::SolutionExpression + */ + virtual bool is_solution_expression() { return false; } }; +/** @} */ // end of ast_class + } // namespace ast -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 7f6b9da1f3..2c841b0e97 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -85,12 +85,14 @@ void CodegenAccVisitor::print_memory_allocation_routine() { * to accelerator. In this case, at very top level, we print pragma * for data present. For example: * + * \code{.cpp} * void nrn_state(...) { * #pragma acc data present (nt, ml...) * { * * } * } + * \endcode */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { if (!info.artificial_cell) { @@ -151,4 +153,4 @@ void CodegenAccVisitor::print_global_variable_device_update_annotation() { } } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 3c368277f6..c5099a09cb 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -7,15 +7,25 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::codegen::CodegenAccVisitor + */ + #include "codegen/codegen_c_visitor.hpp" namespace nmodl { namespace codegen { +/** + * @addtogroup codegen_backends + * @{ + */ + /** * \class CodegenAccVisitor - * \brief Visitor for printing c code with OpenMP backend + * \brief %Visitor for printing C code with OpenACC backend */ class CodegenAccVisitor: public CodegenCVisitor { protected: @@ -83,5 +93,7 @@ class CodegenAccVisitor: public CodegenCVisitor { : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; +/** @} */ // end of codegen_backends + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index dbec1b6606..bbffc00a45 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -28,6 +28,10 @@ namespace codegen { using namespace ast; +using visitor::AstLookupVisitor; +using visitor::RenameVisitor; +using visitor::VarUsageVisitor; + using symtab::syminfo::NmodlType; using SymbolType = std::shared_ptr<symtab::Symbol>; @@ -492,7 +496,7 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { bool CodegenCVisitor::defined_method(const std::string& name) { auto function = program_symtab->lookup(name); auto properties = NmodlType::function_block | NmodlType::procedure_block; - return function && function->has_properties(properties); + return function && function->has_any_property(properties); } @@ -735,10 +739,10 @@ bool CodegenCVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { - if (symbol->has_properties(NmodlType::read_ion_var)) { + if (symbol->has_any_property(NmodlType::read_ion_var)) { is_constant = true; } - if (symbol->has_properties(NmodlType::param_assign) && symbol->get_write_count() == 0) { + if (symbol->has_any_property(NmodlType::param_assign) && symbol->get_write_count() == 0) { is_constant = true; } } @@ -776,7 +780,7 @@ void CodegenCVisitor::update_index_semantics() { info.first_pointer_var_index = index; } int size = var->get_length(); - if (var->has_properties(NmodlType::pointer_var)) { + if (var->has_any_property(NmodlType::pointer_var)) { info.semantics.emplace_back(index, naming::POINTER_SEMANTIC, size); } else { info.semantics.emplace_back(index, naming::CORE_POINTER_SEMANTIC, size); @@ -835,7 +839,8 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { std::sort(dependents.begin(), dependents.end(), comparator); auto variables = info.range_parameter_vars; - variables.insert(variables.end(), info.range_dependent_vars.begin(), + variables.insert(variables.end(), + info.range_dependent_vars.begin(), info.range_dependent_vars.end()); variables.insert(variables.end(), info.state_vars.begin(), info.state_vars.end()); variables.insert(variables.end(), dependents.begin(), dependents.end()); @@ -907,7 +912,7 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { for (const auto& var: info.pointer_variables) { auto name = var->get_name(); - if (var->has_properties(NmodlType::pointer_var)) { + if (var->has_any_property(NmodlType::pointer_var)) { variables.emplace_back(make_symbol(name)); } else { variables.emplace_back(make_symbol(name), true); @@ -984,9 +989,11 @@ std::vector<SymbolType> CodegenCVisitor::get_shadow_variables() { std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { std::stringstream param_ss; for (auto iter = params.begin(); iter != params.end(); iter++) { - param_ss << "{}{} {}{}"_format(std::get<0>(*iter), std::get<1>(*iter), std::get<2>(*iter), + param_ss << "{}{} {}{}"_format(std::get<0>(*iter), + std::get<1>(*iter), + std::get<2>(*iter), std::get<3>(*iter)); - if (!is_last(iter, params)) { + if (!utils::is_last(iter, params)) { param_ss << ", "; } } @@ -1027,12 +1034,14 @@ void CodegenCVisitor::print_channel_iteration_tiling_block_end() { * to accelerator. In this case, at very top level, we print pragma * for data present. For example: * + * \code{.cpp} * void nrn_state(...) { * #pragma acc data present (nt, ml...) * { * * } * } + * \endcode */ void CodegenCVisitor::print_kernel_data_present_annotation_block_begin() { // backend specific, do nothing @@ -1755,8 +1764,10 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { auto params = ParamVector(); params.emplace_back("", "int", "", "id"); params.emplace_back(param_type_qualifier(), "int", "", "pnodecount"); - params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), - param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), + "{}*"_format(instance_struct()), + param_ptr_qualifier(), + "inst"); if (ion_variable_struct_required()) { params.emplace_back("", "IonCurVar&", "", "ionvar"); } @@ -2193,6 +2204,7 @@ std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name * Return variable name in the structure of mechanism properties * * @param name variable name that is being printed + * @param use_instance if variable name should be with the instance object qualifier * @return use_instance whether print name using Instance structure (or data array if false) */ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use_instance) { @@ -2209,28 +2221,31 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use // clang-format on /// float variable - auto f = std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), + auto f = std::find_if(codegen_float_variables.begin(), + codegen_float_variables.end(), symbol_comparator); if (f != codegen_float_variables.end()) { return float_variable_name(*f, use_instance); } /// integer variable - auto i = std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), - index_comparator); + auto i = + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); if (i != codegen_int_variables.end()) { return int_variable_name(*i, varname, use_instance); } /// global variable - auto g = std::find_if(codegen_global_variables.begin(), codegen_global_variables.end(), + auto g = std::find_if(codegen_global_variables.begin(), + codegen_global_variables.end(), symbol_comparator); if (g != codegen_global_variables.end()) { return global_variable_name(*g); } /// shadow variable - auto s = std::find_if(codegen_shadow_variables.begin(), codegen_shadow_variables.end(), + auto s = std::find_if(codegen_shadow_variables.begin(), + codegen_shadow_variables.end(), symbol_comparator); if (s != codegen_shadow_variables.end()) { return ion_shadow_variable_name(*s); @@ -2260,7 +2275,7 @@ void CodegenCVisitor::print_backend_info() { time_t tr; time(&tr); auto date = std::string(asctime(localtime(&tr))); - auto version = nmodl::version::NMODL_VERSION + " [" + nmodl::version::GIT_REVISION + "]"; + auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; printer->add_line("/*********************************************************"); printer->add_line("Model Name : {}"_format(info.mod_suffix)); @@ -2460,7 +2475,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { void CodegenCVisitor::print_mechanism_info() { - auto variable_printer = [this](std::vector<SymbolType>& variables) { + auto variable_printer = [&](std::vector<SymbolType>& variables) { for (const auto& v: variables) { auto name = v->get_name(); if (!info.point_process) { @@ -2497,21 +2512,21 @@ void CodegenCVisitor::print_mechanism_info() { * vector elements of type global and thread variables. */ void CodegenCVisitor::print_global_variables_for_hoc() { - auto variable_printer = [this](const std::vector<SymbolType>& variables, bool if_array, - bool if_vector) { - for (const auto& variable: variables) { - if (variable->is_array() == if_array) { - auto name = get_variable_name(variable->get_name()); - auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); - auto length = variable->get_length(); - if (if_vector) { - printer->add_line("{}, {}, {},"_format(ename, name, length)); - } else { - printer->add_line("{}, &{},"_format(ename, name)); + auto variable_printer = + [&](const std::vector<SymbolType>& variables, bool if_array, bool if_vector) { + for (const auto& variable: variables) { + if (variable->is_array() == if_array) { + auto name = get_variable_name(variable->get_name()); + auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); + auto length = variable->get_length(); + if (if_vector) { + printer->add_line("{}, {}, {},"_format(ename, name, length)); + } else { + printer->add_line("{}, &{},"_format(ename, name)); + } } } - } - }; + }; auto globals = info.global_variables; auto thread_vars = info.thread_variables; @@ -3028,7 +3043,7 @@ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) | NmodlType::bbcore_pointer_var | NmodlType::extern_neuron_variable; // clang-format on - bool need_default_type = symbol->has_properties(with); + bool need_default_type = symbol->has_any_property(with); if (need_default_type) { return default_float_data_type(); } @@ -3068,9 +3083,8 @@ void CodegenCVisitor::print_instance_variable_setup() { if (default_type == range_var_type) { printer->add_line("inst->{} = ml->data+{}{};"_format(name, id, stride)); } else { - printer->add_line( - "inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format(name, id, - stride)); + printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format( + name, id, stride)); variables_to_free.push_back(name); } id += var->get_length(); @@ -3631,8 +3645,10 @@ void CodegenCVisitor::print_net_receive_kernel() { name = method_name("net_receive_kernel"); params.emplace_back("", "double", "", "t"); params.emplace_back("", "Point_process*", "", "pnt"); - params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), - param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), + "{}*"_format(instance_struct()), + param_ptr_qualifier(), + "inst"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); params.emplace_back("", "int", "", "weight_index"); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 3f81264593..047676d759 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -7,6 +7,14 @@ #pragma once +/** + * \dir + * \brief Code generation backend implementations for CoreNEURON + * + * \file + * \brief \copybrief nmodl::codegen::CodegenCVisitor + */ + #include <algorithm> #include <cmath> #include <ctime> @@ -24,10 +32,20 @@ using namespace fmt::literals; - namespace nmodl { +/// encapsulates code generation backend implementations namespace codegen { +/** + * @defgroup codegen Codegen Infrastructure + * @brief Implementations of code generation backends + * + * @defgroup codegen_details Codegen Helpers + * @ingroup codegen + * @brief Helper routines/types for code generation + * @{ + */ + /** * \enum BlockType * \brief Helper to represent various block types @@ -138,20 +156,33 @@ struct ShadowUseStatement { std::string rhs; }; +/** @} */ // end of codegen_details + + +using printer::CodePrinter; + + +/** + * @defgroup codegen_backends Codegen Backends + * @ingroup codegen + * @brief Code generation backends for CoreNEURON + * @{ + */ /** * \class CodegenCVisitor - * \brief Visitor for printing c code compatible with legacy api + * \brief %Visitor for printing C code compatible with legacy api of CoreNEURON * - * \todo : - * - handle define statement (i.e. macro statement printing) - * - return statement in the verbatim block of inlined function not handled - * for example, see netstim.mod where we removed return from verbatim block + * \todo + * - Handle define statement (i.e. macros) + * - If there is a return statement in the verbatim block + * of inlined function then it will be error. Need better + * error checking. For example, see netstim.mod where we + * have removed return from verbatim block. */ -class CodegenCVisitor: public AstVisitor { +class CodegenCVisitor: public visitor::AstVisitor { protected: using SymbolType = std::shared_ptr<symtab::Symbol>; - using ParamVector = std::vector<std::tuple<std::string, std::string, std::string, std::string>>; /// name of mod file (without .mod suffix) @@ -1059,7 +1090,7 @@ void CodegenCVisitor::print_vector_elements(const std::vector<T>& elements, for (auto iter = elements.begin(); iter != elements.end(); iter++) { printer->add_text(prefix); (*iter)->accept(this); - if (!separator.empty() && !is_last(iter, elements)) { + if (!separator.empty() && !utils::is_last(iter, elements)) { printer->add_text(separator); } } @@ -1114,11 +1145,14 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin print_device_method_annotation(); printer->add_indent(); - printer->add_text("inline {} {}({})"_format(return_type, method_name(name), + printer->add_text("inline {} {}({})"_format(return_type, + method_name(name), get_parameter_str(internal_params))); enable_variable_name_lookup = true; } +/** @} */ // end of codegen_backends + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 87a35669d5..24275e387a 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -31,10 +31,10 @@ bool CodegenCudaVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { - if (symbol->has_properties(NmodlType::read_ion_var)) { + if (symbol->has_any_property(NmodlType::read_ion_var)) { is_constant = true; } - if (symbol->has_properties(NmodlType::param_assign)) { + if (symbol->has_any_property(NmodlType::param_assign)) { is_constant = true; } } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 0031938541..36f6d92ada 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -7,14 +7,24 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::codegen::CodegenCudaVisitor + */ + #include "codegen/codegen_c_visitor.hpp" namespace nmodl { namespace codegen { +/** + * @addtogroup codegen_backends + * @{ + */ + /** * \class CodegenCudaVisitor - * \brief Visitor for printing CUDA backend + * \brief %Visitor for printing CUDA backend */ class CodegenCudaVisitor: public CodegenCVisitor { void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); @@ -103,5 +113,7 @@ class CodegenCudaVisitor: public CodegenCVisitor { : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; +/** @} */ // end of codegen_backends + } // namespace codegen } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index d20f597174..6eb5cd565a 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -49,13 +49,15 @@ using symtab::syminfo::Status; */ void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector<SymbolType>& symbols) { /// first sort by global id to get in reverse order - std::sort(symbols.begin(), symbols.end(), + std::sort(symbols.begin(), + symbols.end(), [](const SymbolType& first, const SymbolType& second) -> bool { return first->get_id() > second->get_id(); }); /// now order by name (to be same as neuron's bucket) - std::sort(symbols.begin(), symbols.end(), + std::sort(symbols.begin(), + symbols.end(), [](const SymbolType& first, const SymbolType& second) -> bool { return first->get_name()[0] < second->get_name()[0]; }); @@ -199,7 +201,7 @@ void CodegenHelperVisitor::find_non_range_variables() { // some variables like area and diam are declared in parameter // block but they are not global if (var->get_name() == naming::DIAM_VARIABLE || var->get_name() == naming::AREA_VARIABLE || - var->has_properties(NmodlType::extern_neuron_variable)) { + var->has_any_property(NmodlType::extern_neuron_variable)) { continue; } @@ -407,7 +409,7 @@ void CodegenHelperVisitor::find_range_variables() { auto properties = NmodlType::read_ion_var | NmodlType::write_ion_var; // clang-format on - if (variable->has_properties(properties)) { + if (variable->has_any_property(properties)) { info.ion_state_vars.push_back(variable); info.dependent_vars.push_back(variable); } @@ -560,7 +562,7 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { auto name = assign_lhs->get_node_name(); auto symbol = psymtab->lookup(name); if (symbol != nullptr) { - auto is_prime = symbol->has_properties(NmodlType::prime_name); + auto is_prime = symbol->has_any_property(NmodlType::prime_name); auto from_state = symbol->has_any_status(Status::from_state); if (is_prime || from_state) { if (from_state) { diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 95036d292c..7adef36535 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::codegen::CodegenHelperVisitor + */ + #include <string> #include "codegen/codegen_info.hpp" @@ -16,25 +21,30 @@ namespace nmodl { namespace codegen { +/** + * @addtogroup codegen_details + * @{ + */ + /** * \class CodegenHelperVisitor - * \brief Helper visitor to gather iformation for code generation + * \brief Helper visitor to gather AST information to help code generation * * Code generation pass needs various information from AST and symbol * table. Different code generation backends will need this information. * This helper pass visit ast and collect all information into single * class object of CodegenInfo. * - * \todo: - * - determine vectorize as part of the pass - * - determine threadsafe as part of the pass + * \todo + * - determine `vectorize` as part of the pass + * - determine `threadsafe` as part of the pass * - global variable order is not preserved, for example, below gives different order: * NEURON block: GLOBAL gq, gp * PARAMETER block: gp = 11, gq[2] * - POINTER rng and if it's also assigned rng[4] then it is printed as one value. * Need to check what is correct value. */ -class CodegenHelperVisitor: public AstVisitor { +class CodegenHelperVisitor: public visitor::AstVisitor { using SymbolType = std::shared_ptr<symtab::Symbol>; /// holds all codegen related information @@ -98,5 +108,7 @@ class CodegenHelperVisitor: public AstVisitor { void visit_partial_block(ast::PartialBlock* node) override; }; +/** @} */ // end of codegen_details + } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 59a0e2b67d..fa360ad97d 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -12,6 +12,8 @@ namespace nmodl { namespace codegen { +using visitor::AstLookupVisitor; + /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() { for (const auto& ion: ions) { diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index b5fba148f0..63b05f4b22 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief Variour types to store code generation specific information + */ + #include <string> #include "ast/ast.hpp" @@ -15,6 +20,11 @@ namespace nmodl { namespace codegen { +/** + * @addtogroup codegen_details + * @{ + */ + /** * \class Conductance * \brief Represent conductance statements used in mod file @@ -376,5 +386,7 @@ struct CodegenInfo { bool require_wrote_conc = false; }; +/** @} */ // end of codegen_backends + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index fd2601a55b..49ac21aa1e 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -24,6 +24,9 @@ namespace codegen { using symtab::syminfo::Status; +using visitor::AstLookupVisitor; +using visitor::RenameVisitor; + /****************************************************************************************/ /* Overloaded visitor methods */ /****************************************************************************************/ @@ -126,8 +129,10 @@ std::string CodegenIspcVisitor::compute_method_name(BlockType type) { std::string CodegenIspcVisitor::net_receive_buffering_declaration() { auto params = ParamVector(); - params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), - param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), + "{}*"_format(instance_struct()), + param_ptr_qualifier(), + "inst"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); @@ -268,8 +273,10 @@ void CodegenIspcVisitor::print_backend_namespace_stop() { CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( std::string arg_qualifier) { auto params = ParamVector(); - params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), - param_ptr_qualifier(), "inst"); + params.emplace_back(param_type_qualifier(), + "{}*"_format(instance_struct()), + param_ptr_qualifier(), + "inst"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); params.emplace_back(param_type_qualifier(), "int", "", "type"); @@ -628,9 +635,9 @@ void CodegenIspcVisitor::determine_target() { auto node_lv = AstLookupVisitor(incompatible_node_types); if (info.initial_node) { - emit_fallback[BlockType::Initial] = !node_lv.lookup(info.initial_node).empty() || - calls_function(info.initial_node, "net_send") || - info.require_wrote_conc; + emit_fallback[BlockType::Initial] = + !node_lv.lookup(info.initial_node).empty() || + visitor::calls_function(info.initial_node, "net_send") || info.require_wrote_conc; } else { emit_fallback[BlockType::Initial] = info.net_receive_initial_node || info.require_wrote_conc; @@ -638,7 +645,8 @@ void CodegenIspcVisitor::determine_target() { if (info.net_receive_node) { emit_fallback[BlockType::NetReceive] = !node_lv.lookup(info.net_receive_node).empty() || - calls_function(info.net_receive_node, "net_send"); + visitor::calls_function(info.net_receive_node, + "net_send"); } if (nrn_cur_required()) { diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 278f83f7d6..1402b47f44 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -7,23 +7,35 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::codegen::CodegenIspcVisitor + */ + #include "codegen/codegen_c_visitor.hpp" namespace nmodl { namespace codegen { +/** + * @addtogroup codegen_backends + * @{ + */ + /** * \class CodegenIspcVisitor - * \brief Visitor for printing ispc backend + * \brief %Visitor for printing C code with ISPC backend */ class CodegenIspcVisitor: public CodegenCVisitor { void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); /// ast nodes which are not compatible with ISPC target const std::vector<ast::AstNodeType> incompatible_node_types{ - ast::AstNodeType::VERBATIM, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK, - ast::AstNodeType::EIGEN_LINEAR_SOLVER_BLOCK, ast::AstNodeType::WATCH_STATEMENT, + ast::AstNodeType::VERBATIM, + ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK, + ast::AstNodeType::EIGEN_LINEAR_SOLVER_BLOCK, + ast::AstNodeType::WATCH_STATEMENT, ast::AstNodeType::TABLE_STATEMENT}; /// flag to indicate if visitor should print the the wrapper code @@ -203,5 +215,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { void visit_program(ast::Program* node) override; }; +/** @} */ // end of codegen_backends + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index eed5e057d3..f76a48ae05 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -13,6 +13,7 @@ namespace nmodl { namespace codegen { +/// different variable names used in code generation namespace naming { /// nmodl language version diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index b516f683ec..468010ce89 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -7,15 +7,25 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::codegen::CodegenOmpVisitor + */ + #include "codegen/codegen_c_visitor.hpp" namespace nmodl { namespace codegen { +/** + * @addtogroup codegen_backends + * @{ + */ + /** * \class CodegenOmpVisitor - * \brief Visitor for printing c code with OpenMP backend + * \brief %Visitor for printing C code with OpenMP backend */ class CodegenOmpVisitor: public CodegenCVisitor { protected: @@ -73,5 +83,7 @@ class CodegenOmpVisitor: public CodegenCVisitor { : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; +/** @} */ // end of codegen_backends + } // namespace codegen } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc index 0cba8450d7..1bd20577e9 100644 --- a/src/nmodl/codegen/fast_math.ispc +++ b/src/nmodl/codegen/fast_math.ispc @@ -9,6 +9,10 @@ * license information. *************************************************************************/ +/** + * \file + * \brief Implementation of different math functions with ISPC + */ static inline double uint642dp(unsigned int64 ll) { return *((varying double*) (&ll)); @@ -76,7 +80,7 @@ static const uniform float PX6expf = 5.0000001201E-1f; static const uniform float LOG2EF = 1.44269504088896341f; - +/// double precision exp function static inline double vexp(double initial_x) { double x = initial_x; double px = dpfloor(LOG2E * x + 0.5d); @@ -114,6 +118,7 @@ static inline double vexp(double initial_x) { return x; } +/// single prevision exp function static inline float vexp(float initial_x) { float x = initial_x; float z = spfloor(LOG2EF * x + 0.5f); diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index ec996c745a..c6158e1ddd 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -8,10 +8,10 @@ #include "config/config.h" /// Git version of the project -const std::string nmodl::version::GIT_REVISION = "@GIT_REVISION@"; +const std::string nmodl::Version::GIT_REVISION = "@GIT_REVISION@"; /// NMODL version -const std::string nmodl::version::NMODL_VERSION = "@PROJECT_VERSION@"; +const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; /** * \brief Path of nrnutils.lib file diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index 25bd1bb9bf..e6d4e9b9cb 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -7,6 +7,14 @@ #pragma once +/** + * \dir + * \brief Global project configurations + * + * \file + * \brief Version information and units file path + */ + #include <fstream> #include <string> #include <vector> @@ -16,7 +24,7 @@ namespace nmodl { /** * \brief Project version information */ -struct version { +struct Version { /// git revision id static const std::string GIT_REVISION; @@ -30,13 +38,15 @@ struct version { }; /** - * \brief Information about unit specification with nrnunits.lib + * \brief Information of units database i.e. `nrnunits.lib` */ struct NrnUnitsLib { /// paths where nrnunits.lib can be found static const std::vector<std::string> NRNUNITSLIB_PATH; - /// from possible paths, return one that exists + /** + * Return path of units database file + */ static std::string get_path() { for (const auto& path: NRNUNITSLIB_PATH) { std::ifstream f(path.c_str()); diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 8e0a79207a..205dcd9199 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -33,7 +33,7 @@ if args.clang_format_opts: clang_format += args.clang_format_opts -# parse NMODL and codegen definition file to get AST nodes +# parse NMODL and codegen definition files to get AST nodes nmodl_nodes = LanguageParser("nmodl.yaml").parse_file() codegen_nodes = LanguageParser("codegen.yaml").parse_file() diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index c5a676255c..33a078423d 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -77,7 +77,11 @@ # TODO : Add detailed information about YAML specification by porting old text based rule # specification -- AST: +## @package nmodl_yaml +# Specification of NMODL in YAML format +# + +- Ast: brief: "Top level AST base class" description: | Ast is a top level, abstract base class defining the interface diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index e4f69533f0..3be9171d61 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -196,10 +196,17 @@ def member_typename(self): def get_add_methods(self): s = '' if self.add_method: - method = f"""void add{self.class_name}({self.class_name} *n) {{ + method = f""" + /** + * \\brief Add member to {self.varname} by raw pointer + */ + void add{self.class_name}({self.class_name} *n) {{ {self.varname}.emplace_back(n); }} + /** + * \\brief Add member to {self.varname} by shared_ptr + */ void add{self.class_name}(std::shared_ptr<{self.class_name}> n) {{ {self.varname}.push_back(n); }} @@ -212,23 +219,48 @@ def get_node_name_method(self): if self.get_node_name: # string node should be evaluated and hence eval() method method_name = "eval" if self.is_string_node else "get_node_name" - method = f"""virtual std::string get_node_name() override {{ + method = f""" + /** + * \\brief Return name of of the node + * + * Some ast nodes have a member marked designated as node name. For example, + * in case of this ast::{self.class_name} has {self.varname} designated as a + * node name. + * + * @return name of the node as std::string + * + * \\sa Ast::get_node_type_name + */ + virtual std::string get_node_name() override {{ return {self.varname}->{method_name}(); }}""" s = textwrap.dedent(method) return s - def get_getter_method(self): + def get_getter_method(self, class_name): getter_method = self.getter_method if self.getter_method else "get_" + to_snake_case(self.varname) getter_override = " override" if self.getter_override else "" return_type = self.member_typename - return f"{return_type} {getter_method}(){getter_override}{{ return {self.varname}; }}" - - def get_setter_method(self): + return f""" + /** + * \\brief Getter for member variable \\ref {class_name}.{self.varname} + */ + {return_type} {getter_method}(){getter_override}{{ + return {self.varname}; + }}""" + + def get_setter_method(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename reference = "" if self.is_base_type_node else "&&" - return f"void {setter_method}({setter_type}{reference} {self.varname}) {{ this->{self.varname} = {self.varname}; }}" + return f""" + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} + */ + void {setter_method}({setter_type}{reference} {self.varname}) {{ + this->{self.varname} = {self.varname}; + }} + """ def __repr__(self): return "ChildNode(class_name='{}', nmodl_name='{}')".format( diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index 66d2867fe2..e8312fbf2c 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -150,8 +150,8 @@ def parse_yaml_rules(self, nodelist, base_class=None): abstract_nodes.extend(child_abstract_nodes) nodes.extend(child_nodes) - # classes like AST which don't have base class - # are not added (we print AST class separately) + # classes like Ast which don't have base class + # are not added (we print Ast class separately) if base_class is not None: args.base_class = base_class node = Node(args) @@ -160,7 +160,7 @@ def parse_yaml_rules(self, nodelist, base_class=None): if self.debug: print('Abstract {}'.format(node)) else: - args.base_class = base_class if base_class else 'AST' + args.base_class = base_class if base_class else 'Ast' # check if we need token for the node # todo : we will have token for every node diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index a74f979b6b..10337ac505 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -8,26 +8,37 @@ #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" +/** + * \file + * \brief Auto generated AST classes implementations + */ namespace nmodl { namespace ast { {% for node in nodes %} - /* visit method for {{ node.class_name }} ast node */ - void {{ node.class_name }}::visit_children(Visitor* v) { + /// + /// {{node.class_name}} member functions definition + /// + + void {{ node.class_name }}::visit_children(visitor::Visitor* v) { {% for child in node.non_base_members %} {% if child.is_vector %} + /// visit each element of vector for (auto& item : this->{{ child.varname }}) { item->accept(v); } {% elif child.optional %} + /// optional member could be nullptr if (this->{{ child.varname }}) { this->{{ child.varname }}->accept(v); } {% elif child.is_pointer_node %} + /// use -> for pointer member {{ child.varname }}->accept(v); {% else %} + /// use . for object member {{ child.varname }}.accept(v); {% endif %} (void)v; @@ -35,30 +46,33 @@ namespace ast { } {% if node.children %} - /* constructor for {{ node.class_name }} ast node */ + {{ node.ctor_definition() }} {% if node.has_ptr_children() %} {{ node.ctor_shrptr_definition() }} {% endif %} - /* copy constructor for {{ node.class_name }} ast node */ + /// copy constructor implementation {{ node.class_name }}::{{ node.class_name }}(const {{ node.class_name }}& obj) { - {% for child in node.children %} {% if child.is_vector %} + /// copy each element of vector for (auto& item : obj.{{ child.varname }}) { this->{{ child.varname }}.emplace_back(item->clone()); } {% elif child.is_pointer_node or child.optional %} + /// pointer member must be reseted with the new copy if (obj.{{ child.varname }}) { this->{{ child.varname }}.reset(obj.{{ child.varname }}->clone()); } {% else %} + /// object member can be just copied by value this->{{ child.varname }} = obj.{{ child.varname }}; {% endif %} {% endfor %} {% if node.has_token %} + /// if there is a token, make copy if (obj.token) { this-> token = std::shared_ptr<ModToken>(obj.token->clone()); } diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 489f4f8255..0be173ee30 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -7,6 +7,14 @@ #pragma once +/** + * \dir + * \brief Auto generated AST Implementations + * + * \file + * \brief Auto generated AST classes declaration + */ + #include <iostream> #include <memory> #include <string> @@ -18,10 +26,13 @@ #include "utils/common_utils.hpp" #include "visitors/visitor.hpp" + +{# add virtual qualifier if node is an abstract class #} {% macro virtual(node) -%} {% if node.is_abstract %} virtual {% endif %} {% endmacro %} +{# add override qualifier if node is not an abstract class #} {% macro override(node) %} {% if not node.is_abstract %} override {% endif %} {% endmacro %} @@ -30,6 +41,12 @@ namespace nmodl { namespace ast { + /** + * @addtogroup ast_class + * @ingroup ast + * @{ + */ + {% for node in nodes %} /** * \brief {{ node.brief }} @@ -43,6 +60,7 @@ namespace ast { {{ '/// ' + member[2] }} {{ member[0] }} {{ member[1] }}; {% endfor %} + {% endif %} public: {% for member in node.public_members() %} @@ -50,6 +68,10 @@ namespace ast { {{ member[0] }} {{ member[1] }}; {% endfor %} + + /// \name Ctor & dtor + /// \{ + {% if node.children %} {{ node.ctor_declaration() }} {% if node.has_ptr_children() %} @@ -64,81 +86,284 @@ namespace ast { virtual ~{{ node.class_name }}() {} - {% for child in node.children %} - {{ child.get_add_methods() }} + /// \} - {{ child.get_node_name_method() }} + /// \name Not implemented + /// \{ - {{ child.get_getter_method() }} + {% if node.is_base_block_node %} + virtual ArgumentVector get_parameters() { + throw std::runtime_error("get_parameters not implemented"); + } + {% endif %} - {{ child.get_setter_method() }} + {% if node.is_number_node %} + {{ virtual(node) }}double to_double() { + throw std::runtime_error("to_double not implemented"); + } + {% endif %} - {% endfor %} + /// \} - {{ virtual(node) }} void visit_children (Visitor* v) override; + /** + *\brief Check if the ast node is an instance of ast::{{ node.class_name }} + * @return true as object is of type ast::{{ node.class_name }} + */ + bool is_{{ node.class_name | snake_case }} () override { + return true; + } - {{ virtual(node) }} void accept(Visitor* v) override { v->visit_{{ node.class_name | snake_case }}(this); } + /** + * \brief Return a copy of the current node + * + * Recursively make a new copy/clone of the current node including + * all members and return a pointer to the node. This is used for + * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the + * ast. + * + * @return pointer to the clone/copy of the current node + */ + {{ virtual(node) }} {{ node.class_name }}* clone() override { + return new {{ node.class_name }}(*this); + } - {{ virtual(node) }} {{ node.class_name }}* clone() override { return new {{ node.class_name }}(*this); } - {{ virtual(node) }} std::shared_ptr<AST> get_shared_ptr() override { - return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); + /// \name Getters + /// \{ + + /** + * \brief Return type (ast::AstNodeType) of ast node + * + * Every node in the ast has a type defined in ast::AstNodeType and this + * function is used to retrieve the same. + * + * \return ast node type i.e. ast::AstNodeType::{{ node.ast_enum_name }} + * + * \sa Ast::get_node_type_name + */ + {{ virtual(node) }} AstNodeType get_node_type() override { + return AstNodeType::{{ node.ast_enum_name }}; } - {{ virtual(node) }} AstNodeType get_node_type() override { return AstNodeType::{{ node.ast_enum_name }}; } - - {{ virtual(node) }} std::string get_node_type_name() override { return "{{ node.class_name }}"; } + /** + * \brief Return type (ast::AstNodeType) of ast node as std::string + * + * Every node in the ast has a type defined in ast::AstNodeType. + * This type name can be returned as a std::string for printing + * node to text/json form. + * + * @return name of the node type as a string i.e. "{{ node.class_name }}" + * + * \sa Ast::get_node_name + */ + {{ virtual(node) }} std::string get_node_type_name() override { + return "{{ node.class_name }}"; + } - bool is_{{ node.class_name | snake_case }} () override { return true; } + /** + * \brief Get std::shared_ptr from `this` pointer of the current ast node + */ + {{ virtual(node) }} std::shared_ptr<Ast> get_shared_ptr() override { + return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); + } {% if node.has_token %} - {{ virtual(node) }}ModToken* get_token(){{ override(node) }} { return token.get(); } - - void set_token(ModToken& tok) { token = std::shared_ptr<ModToken>(new ModToken(tok)); } + /** + * \brief Return associated token for the current ast node + * + * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor + * can insert new nodes in the ast as a solution of ODEs. In this case, we return + * nullptr to store in the nmodl::symtab::SymbolTable. + * + * @return pointer to token if exist otherwise nullptr + */ + {{ virtual(node) }}ModToken* get_token(){{ override(node) }} { + return token.get(); + } {% endif %} {% if node.is_symtab_needed %} - void set_symbol_table(symtab::SymbolTable* newsymtab) override { symtab = newsymtab; } - - symtab::SymbolTable* get_symbol_table() override { return symtab; } + /** + * \brief Return associated symbol table for the current ast node + * + * Only certain ast nodes (e.g. inherited from ast::Block) have associated + * symbol table. These nodes have nmodl::symtab::SymbolTable as member + * and it can be accessed using this method. + * + * @return pointer to the symbol table + * + * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor + */ + symtab::SymbolTable* get_symbol_table() override { + return symtab; + } {% endif %} {% if node.is_program_node %} - symtab::ModelSymbolTable* get_model_symbol_table() { return &model_symtab; } + /** + * \brief Return global symbol table for the mod file + */ + symtab::ModelSymbolTable* get_model_symbol_table() { + return &model_symtab; + } {% endif %} - {% if node.is_base_class_number_node %} - void negate() override { value = {{ node.negation }}value; } + {# doxygen for these methods is handled by nodes.py #} + {% for child in node.children %} + {{ child.get_add_methods() }} - double to_double() override { return value; } - {% endif %} + {{ child.get_node_name_method() }} + + {{ child.get_getter_method(node.class_name) }} + {% endfor %} + + /// \} + + + /// \name Setters + /// \{ {% if node.is_name_node %} - {{ virtual(node) }}void set_name(std::string name){{ override(node) }} { value->set(name); } + /** + * \brief Set name for the current ast node + * + * Some ast nodes have a member marked designated as node name (e.g. nodes + * derived from ast::Identifier). This method is used to set new name for those + * nodes. This useful for passes like nmodl::visitor::RenameVisitor. + * + * \sa Ast::get_node_type_name Ast::get_node_name + */ + {{ virtual(node) }}void set_name(std::string name){{ override(node) }} { + value->set(name); + } {% endif %} - {% if node.is_number_node %} - {{ virtual(node) }}double to_double() { throw std::runtime_error("to_double not implemented"); } + {% if node.has_token %} + /** + * \brief Set token for the current ast node + */ + void set_token(ModToken& tok) { token = std::shared_ptr<ModToken>(new ModToken(tok)); } {% endif %} - {% if node.is_base_block_node %} - virtual ArgumentVector get_parameters() { throw std::runtime_error("get_parameters not implemented"); } + {% if node.is_symtab_needed %} + /** + * \brief Set symbol table for the current ast node + * + * Top level, block scoped nodes store symbol table in the ast node. + * nmodl::visitor::SymtabVisitor then used this method to setup symbol table + * for every node in the ast. + * + * \sa nmodl::visitor::SymtabVisitor + */ + void set_symbol_table(symtab::SymbolTable* newsymtab) override { + symtab = newsymtab; + } + {% endif %} + + {# if node is base data type but not enum then add set method #} + {% if node.is_data_type_node and not node.is_enum_node %} + /** + * \brief Set new value to the current ast node + * \sa {{ node.class_name }}::eval + */ + void set({{ node.get_data_type_name() }} _value) { + value = _value; + } + {% endif %} + + {# doxygen for these methods is handled by nodes.py #} + {% for child in node.children %} + {{ child.get_setter_method(node.class_name) }} + {% endfor %} + + /// \} + + + /// \name Visitor + /// \{ + + /** + * \brief visit children i.e. member variables of current node using provided visitor + * + * Different nodes in the AST have different members (i.e. children). This method + * recursively visits children using provided visitor. + * + * @param v Concrete visitor that will be used to recursively visit children + * + * \sa Ast::visit_children for example. + */ + {{ virtual(node) }} void visit_children (visitor::Visitor* v) override; + + /** + * \brief accept (or visit) the current AST node using provided visitor + * + * Instead of visiting children of AST node, like Ast::visit_children, + * accept allows to visit the current node itself using provided concrete + * visitor. + * + * @param v Concrete visitor that will be used to recursively visit node + * + * \sa Ast::accept for example. + */ + {{ virtual(node) }} void accept(visitor::Visitor* v) override { + v->visit_{{ node.class_name | snake_case }}(this); + } + + /// \} + + + {% if node.is_base_class_number_node %} + /** + * \brief Negate the value of current ast node + * + * Parser parse `-x` in two parts : `x` and then `-`. Once second token + * `-` is encountered, the corresponding value of ast node needs to be + * multiplied by `-1` for ast::Number node types. + */ + void negate() override { + value = {{ node.negation }}value; + } + + /** + * \brief Return value of the current ast node as double + */ + double to_double() override { return value; } {% endif %} {% if node.is_data_type_node %} {# if node is of enum type then return enum value #} {% if node.is_enum_node %} - std::string eval() { return {{ node.get_data_type_name() }}Names[value]; } - {# But if basic data type then eval return their value #} + /** + * \brief Return enum value in string form + * + * Enum variables (e.g. ast::BinaryOp, ast::UnitStateType) have + * string representation when they are converted from AST back to + * NMODL. This method is used to return corresponding string representation. + */ + std::string eval() { return {{ + node.get_data_type_name() }}Names[value]; + } + {# but if basic data type then eval return their value #} {% else %} - {{ node.get_data_type_name() }} eval() { return value; } - - void set({{ node.get_data_type_name() }} _value) { value = _value; } + /** + * \brief Return value of the ast node + * + * Base data type nodes like ast::Inetegr, ast::Double can be evaluated + * to their literal values. This method is used to access underlying + * literal value. + * + * \sa {{ node.class_name }}::set + */ + {{ node.get_data_type_name() }} eval() { + return value; + } {% endif %} {% endif %} }; {% endfor %} + /** @} */ // end of ast_class + } // namespace ast } // namespace nmodl diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index e53f81d46e..5e5d205b9a 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -11,25 +11,52 @@ #include <utility> #include <vector> +/// \file +/// \brief Auto generated AST node types and aliases declaration namespace nmodl { namespace ast { - {% for node in nodes %} - class {{ node.class_name }}; - {% endfor %} +/// forward declaration of ast nodes - /* Type for every ast node */ - enum class AstNodeType { - {% for node in nodes %} - {{ node.class_name|snake_case|upper }}, - {% endfor %} - }; +{% for node in nodes %} +class {{ node.class_name }}; +{% endfor %} - /* std::vector for convenience */ +/** + * @defgroup ast_type AST Node Types + * @ingroup ast + * @brief Enum node types for all ast nodes + * @{ + */ + +/** + * \brief Enum type for every AST node type + * + * Every node in the ast has associated type represented by this + * enum class. + * + * \sa ast::Ast::get_node_type ast::Ast::get_node_type_name + */ +enum class AstNodeType { {% for node in nodes %} - using {{ node.class_name }}Vector = std::vector<std::shared_ptr<{{ node.class_name }}>>; + {{ node.class_name|snake_case|upper }}, ///< type of ast::{{ node.class_name }} {% endfor %} +}; + +/** @} */ // end of ast_type + +/** + * @defgroup ast_vec_type AST Vector Type Aliases + * @ingroup ast + * @brief Vector type alias for AST node + * @{ + */ +{% for node in nodes %} +using {{ node.class_name }}Vector = std::vector<std::shared_ptr<{{ node.class_name }}>>; +{% endfor %} + +/** @} */ // end of ast_vec_type } // namespace ast -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index ede5de9505..eda6784a7b 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -5,13 +5,23 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "pybind/pyast.hpp" -#include "visitors/json_visitor.hpp" #include <pybind11/pybind11.h> #include <pybind11/stl.h> +#include "pybind/pyast.hpp" +#include "visitors/json_visitor.hpp" + + +/** + * \file + * \brief All AST classes for Python bindings + */ + #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" + + +// clang-format off {% macro var(node) -%} {{ node.class_name | snake_case }}_ {%- endmacro -%} @@ -19,95 +29,181 @@ {% macro args(children) %} {% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} {%- endmacro -%} +// clang-format on -namespace py = pybind11; +namespace nmodl { +namespace docstring { +static const char* binary_op_enum = R"( + Enum type for binary operators in NMODL -using namespace nmodl::ast; -using nmodl::JSONVisitor; + NMODL support different binary operators and this + type is used to store their value in the AST. See + nmodl::ast::Ast for details. +)"; -using pybind11::literals::operator""_a; +static const char* ast_nodetype_enum = R"( + Enum type for every AST node type -namespace docstring { + Every node in the ast has associated type represented by + this enum class. See nmodl::ast::AstNodeType for details. -static const char *binaryop_enum = R"( - BinaryOp enum )"; -static const char *astnodetype_enum = R"( - AstNodeType enum +static const char* ast_class = R"( + Base class for all Abstract Syntax Tree node types + + Every node in the Abstract Syntax Tree is inherited from base class + Ast. This class provides base properties and functions that are implemented + by base classes. )"; -static const char *ast_class = R"( - AST class +static const char* accept_method = R"( + Accept (or visit) the current AST node using current visitor - Attributes: - basetype (BaseType): Stores the AST base type (int, bool, none, object). Further type - information will come from the symbol table. + Instead of visiting children of AST node, like Ast::visit_children, + accept allows to visit the current node itself using the concrete + visitor provided. + + Args: + v (Visitor): Concrete visitor that will be used to recursively visit node )"; -static const char *visitchildren_method = R"( - Visit children +static const char* visit_children_method = R"( + Visit children i.e. member of current AST node using provided visitor + + Different nodes in the AST have different members (i.e. children). This method + recursively visits children using provided concrete visitor. Args: - v (Visitor): the visitor + v (Visitor): Concrete visitor that will be used to recursively visit node )"; -static const char *accept_method = R"( - Accept +static const char* get_node_type_method = R"( + Return type (ast.AstNodeType) of the ast node +)"; - Args: - v (Visitor): the visitor +static const char* get_node_type_name_method = R"( + Return type (ast.AstNodeType) of the ast node as string )"; -} +static const char* get_node_name_method = R"( + Return name of of the node -void init_ast_module(py::module& m) { + Some ast nodes have a member designated as node name. For example, + in case of ast.FunctionCall, name of the function is returned as a node + name. Note that this is different from ast node type name and not every + ast node has name. +)"; - py::module m_ast = m.def_submodule("ast"); - - py::enum_<BinaryOp>(m_ast, "BinaryOp", docstring::binaryop_enum) - .value("BOP_ADDITION", BinaryOp::BOP_ADDITION) - .value("BOP_SUBTRACTION", BinaryOp::BOP_SUBTRACTION) - .value("BOP_MULTIPLICATION", BinaryOp::BOP_MULTIPLICATION) - .value("BOP_DIVISION", BinaryOp::BOP_DIVISION) - .value("BOP_POWER", BinaryOp::BOP_POWER) - .value("BOP_AND", BinaryOp::BOP_AND) - .value("BOP_OR", BinaryOp::BOP_OR) - .value("BOP_GREATER", BinaryOp::BOP_GREATER) - .value("BOP_LESS", BinaryOp::BOP_LESS) - .value("BOP_GREATER_EQUAL", BinaryOp::BOP_GREATER_EQUAL) - .value("BOP_LESS_EQUAL", BinaryOp::BOP_LESS_EQUAL) - .value("BOP_ASSIGN", BinaryOp::BOP_ASSIGN) - .value("BOP_NOT_EQUAL", BinaryOp::BOP_NOT_EQUAL) - .value("BOP_EXACT_EQUAL", BinaryOp::BOP_EXACT_EQUAL) - .export_values(); +static const char* clone_method = R"( + Create a copy of the AST node +)"; + +static const char* get_token_method = R"( + Return associated token for the AST node +)"; - py::enum_<AstNodeType>(m_ast, "AstNodeType", docstring::astnodetype_enum) +static const char* get_symbol_table_method = R"( + Return associated symbol table for the AST node + + Certain ast nodes (e.g. inherited from ast.Block) have associated + symbol table. These nodes have nmodl.symtab.SymbolTable as member + and it can be accessed using this method. +)"; + +static const char* get_statement_block_method = R"( + Return associated statement block for the AST node + + Top level block nodes encloses all statements in the ast::StatementBlock. + For example, ast.BreakpointBlock has all statements in curly brace (`{ }`) + stored in ast.StatementBlock : + + BREAKPOINT { + SOLVE states METHOD cnexp + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + } + + This method return enclosing statement block. +)"; + +static const char* negate_method = R"( + Negate the value of AST node +)"; + +static const char* set_name_method = R"( + Set name for the AST node +)"; + +static const char* is_ast_method = R"( + Check if current node is of type ast.Ast +)"; + +static const char* eval_method = R"( + Return value of the ast node +)"; + +} // namespace docstring +} // namespace nmodl + + +namespace py = pybind11; +using namespace nmodl::ast; +using nmodl::visitor::JSONVisitor; +using pybind11::literals::operator""_a; + + +void init_ast_module(py::module& m) { + py::module m_ast = m.def_submodule("ast", "Abstract Syntax Tree (AST) related implementations"); + + py::enum_<BinaryOp>(m_ast, "BinaryOp", docstring::binary_op_enum) + .value("BOP_ADDITION", BinaryOp::BOP_ADDITION) + .value("BOP_SUBTRACTION", BinaryOp::BOP_SUBTRACTION) + .value("BOP_MULTIPLICATION", BinaryOp::BOP_MULTIPLICATION) + .value("BOP_DIVISION", BinaryOp::BOP_DIVISION) + .value("BOP_POWER", BinaryOp::BOP_POWER) + .value("BOP_AND", BinaryOp::BOP_AND) + .value("BOP_OR", BinaryOp::BOP_OR) + .value("BOP_GREATER", BinaryOp::BOP_GREATER) + .value("BOP_LESS", BinaryOp::BOP_LESS) + .value("BOP_GREATER_EQUAL", BinaryOp::BOP_GREATER_EQUAL) + .value("BOP_LESS_EQUAL", BinaryOp::BOP_LESS_EQUAL) + .value("BOP_ASSIGN", BinaryOp::BOP_ASSIGN) + .value("BOP_NOT_EQUAL", BinaryOp::BOP_NOT_EQUAL) + .value("BOP_EXACT_EQUAL", BinaryOp::BOP_EXACT_EQUAL) + .export_values(); + + py::enum_<AstNodeType>(m_ast, "AstNodeType", docstring::ast_nodetype_enum) + // clang-format off {% for node in nodes %} - .value("{{ node.class_name|snake_case|upper }}", AstNodeType::{{ node.class_name|snake_case|upper }}) + .value("{{ node.class_name|snake_case|upper }}", AstNodeType::{{ node.class_name|snake_case|upper }}, "AST node of type ast.{{ node.class_name}}") {% endfor %} .export_values(); + // clang-format on - - - py::class_<AST, PyAST, std::shared_ptr<AST>> ast_(m_ast, "AST", docstring::ast_class); + py::class_<Ast, PyAst, std::shared_ptr<Ast>> ast_(m_ast, "Ast", docstring::ast_class); ast_.def(py::init<>()) - .def("visit_children", &AST::visit_children, "v"_a, docstring::visitchildren_method) - .def("accept", &AST::accept, "v"_a, docstring::accept_method) - .def("get_shared_ptr", &AST::get_shared_ptr) - .def("get_node_type", &AST::get_node_type) - .def("get_node_type_name", &AST::get_node_type_name) - .def("get_node_name", &AST::get_node_name) - .def("clone", &AST::clone) - .def("get_token", &AST::get_token) - .def("get_symbol_table", &AST::get_symbol_table, py::return_value_policy::reference) - .def("get_statement_block", &AST::get_statement_block) - .def("negate", &AST::negate) - .def("set_name", &AST::set_name) - .def("is_ast", &AST::is_ast) + .def("visit_children", &Ast::visit_children, "v"_a, docstring::visit_children_method) + .def("accept", &Ast::accept, "v"_a, docstring::accept_method) + .def("get_node_type", &Ast::get_node_type, docstring::get_node_type_method) + .def("get_node_type_name", &Ast::get_node_type_name, docstring::get_node_type_name_method) + .def("get_node_name", &Ast::get_node_name, docstring::get_node_name_method) + .def("get_token", &Ast::get_token, docstring::get_token_method) + .def("get_symbol_table", + &Ast::get_symbol_table, + py::return_value_policy::reference, + docstring::get_symbol_table_method) + .def("get_statement_block", + &Ast::get_statement_block, + docstring::get_statement_block_method) + .def("clone", &Ast::clone, docstring::clone_method) + .def("negate", &Ast::negate, docstring::negate_method) + .def("set_name", &Ast::set_name, docstring::set_name_method) + .def("is_ast", &Ast::is_ast, docstring::is_ast_method) + // clang-format off {% for node in nodes %} - .def("is_{{ node.class_name | snake_case }}", &AST::is_{{ node.class_name | snake_case }}) + .def("is_{{ node.class_name | snake_case }}", &Ast::is_{{ node.class_name | snake_case }}, "Check if node is of type ast.{{ node.class_name}}") {% if loop.last -%};{% endif %} {% endfor %} @@ -120,31 +216,34 @@ void init_ast_module(py::module& m) { {% if node.is_program_node or node.is_ptr_excluded_node %} {{ var(node) }}.def(py::init<>()); {% endif %} - - {{ var(node) }}.def("__repr__", []({{ node.class_name }}& n) { - std::stringstream ss; - JSONVisitor v(ss); - v.compact_json(true); - n.accept(&v); - return ss.str(); - }); - + // clang-format on + + {{var(node)}} + .def("__repr__", []({{node.class_name}} & n) { + std::stringstream ss; + JSONVisitor v(ss); + v.compact_json(true); + n.accept(&v); + return ss.str(); + }); + + // clang-format off {% for member in node.public_members() %} {{ var(node) }}.def_readwrite("{{ member[1] }}", &{{ node.class_name }}::{{ member[1] }}); {% endfor %} - {{ var(node) }}.def("visit_children", &{{ node.class_name }}::visit_children) - .def("accept", &{{ node.class_name }}::accept) - .def("clone", &{{ node.class_name }}::clone) - .def("get_shared_ptr", &{{ node.class_name }}::get_shared_ptr) - .def("get_node_type", &{{ node.class_name }}::get_node_type) - .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name) + {{ var(node) }}.def("visit_children", &{{ node.class_name }}::visit_children, docstring::visit_children_method) + .def("accept", &{{ node.class_name }}::accept, docstring::accept_method) + .def("clone", &{{ node.class_name }}::clone, docstring::clone_method) + .def("get_node_type", &{{ node.class_name }}::get_node_type, docstring::get_node_type_method) + .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name, docstring::get_node_type_name_method) {% if node.is_data_type_node %} - .def("eval", &{{ node.class_name }}::eval) + .def("eval", &{{ node.class_name }}::eval, docstring::eval_method) {% endif %} - .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}); + .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}, "Check if node is of type ast.{{ node.class_name}}"); {% endfor %} + // clang-format on } #pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 3fcde3abd0..32a03436d2 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -7,9 +7,9 @@ #pragma once - #include <pybind11/pybind11.h> #include <pybind11/stl.h> + #include "ast/ast.hpp" #include "lexer/modtoken.hpp" #include "symtab/symbol_table.hpp" @@ -18,62 +18,106 @@ using namespace nmodl; using namespace ast; -struct PyAST: public AST { - public: - using AST::AST; +/** + * \file + * \brief Base AST class for Python bindings + */ + +/** + * @defgroup nmodl_python Python Interface + * @brief Python Bindings Implementation + */ - void visit_children(Visitor* v) override { - PYBIND11_OVERLOAD_PURE(void, // Return type - AST, // Parent class - visit_children, // Name of function in C++ (must match Python name) - v // Argument(s) +/** + * + * @defgroup ast_python AST Python Interface + * @ingroup nmodl_python + * @brief Ast classes for Python bindings + * @{ + */ + +/** + * \brief Class mirroring nmodl::ast::Ast for Python bindings + * + * \details \copydetails nmodl::ast::Ast + * + * This class is used to interface nmodl::Ast with the Python + * world using `pybind11`. + */ +struct PyAst: public Ast { + + void visit_children(visitor::Visitor* v) override { + PYBIND11_OVERLOAD_PURE(void, /// Return type + Ast, /// Parent class + visit_children, /// Name of function in C++ (must match Python name) + v /// Argument(s) ); } - void accept(Visitor* v) override { PYBIND11_OVERLOAD_PURE(void, AST, accept, v); } + void accept(visitor::Visitor* v) override { + PYBIND11_OVERLOAD_PURE(void, Ast, accept, v); + } + + + Ast* clone() override { + PYBIND11_OVERLOAD(Ast*, Ast, clone, ); + } AstNodeType get_node_type() override { PYBIND11_OVERLOAD_PURE(AstNodeType, // Return type - AST, // Parent class + Ast, // Parent class get_node_type, // Name of function in C++ (must match Python name) // No argument (trailing ,) ); } std::string get_node_type_name() override { - PYBIND11_OVERLOAD_PURE(std::string, AST, get_node_type_name, ); + PYBIND11_OVERLOAD_PURE(std::string, Ast, get_node_type_name, ); } - std::string get_node_name() override { PYBIND11_OVERLOAD(std::string, AST, get_node_name, ); } - - AST* clone() override { PYBIND11_OVERLOAD(AST*, AST, clone, ); } - - std::shared_ptr<AST> get_shared_ptr() override { PYBIND11_OVERLOAD(std::shared_ptr<AST>, AST, get_shared_ptr, ); } + std::string get_node_name() override { + PYBIND11_OVERLOAD(std::string, Ast, get_node_name, ); + } - ModToken* get_token() override { PYBIND11_OVERLOAD(ModToken*, AST, get_token, ); } + std::shared_ptr<Ast> get_shared_ptr() override { + PYBIND11_OVERLOAD(std::shared_ptr<Ast>, Ast, get_shared_ptr, ); + } - void set_symbol_table(symtab::SymbolTable* newsymtab) override { - PYBIND11_OVERLOAD(void, AST, set_symbol_table, newsymtab); + ModToken* get_token() override { + PYBIND11_OVERLOAD(ModToken*, Ast, get_token, ); } symtab::SymbolTable* get_symbol_table() override { - PYBIND11_OVERLOAD(symtab::SymbolTable*, AST, get_symbol_table, ); + PYBIND11_OVERLOAD(symtab::SymbolTable*, Ast, get_symbol_table, ); } + std::shared_ptr<StatementBlock> get_statement_block() override { - PYBIND11_OVERLOAD(std::shared_ptr<StatementBlock>, AST, get_statement_block, ); + PYBIND11_OVERLOAD(std::shared_ptr<StatementBlock>, Ast, get_statement_block, ); } - void negate() override { PYBIND11_OVERLOAD(void, AST, negate, ); } + void set_symbol_table(symtab::SymbolTable* newsymtab) override { + PYBIND11_OVERLOAD(void, Ast, set_symbol_table, newsymtab); + } - void set_name(std::string name) override { PYBIND11_OVERLOAD(void, AST, set_name, name); } + void set_name(std::string name) override { + PYBIND11_OVERLOAD(void, Ast, set_name, name); + } - bool is_ast() override { PYBIND11_OVERLOAD(bool, AST, is_ast, ); } + void negate() override { + PYBIND11_OVERLOAD(void, Ast, negate, ); + } + + bool is_ast() override { + PYBIND11_OVERLOAD(bool, Ast, is_ast, ); + } {% for node in nodes %} bool is_{{node.class_name | snake_case}}() override { - PYBIND11_OVERLOAD(bool, AST, is_{{node.class_name | snake_case}}, ); + PYBIND11_OVERLOAD(bool, Ast, is_{{node.class_name | snake_case}}, ); } {% endfor %} }; + +/** @} */ // end of ast_python diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 705a873c18..5f04db769b 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -5,44 +5,38 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include <pybind11/pybind11.h> #include <pybind11/iostream.h> +#include <pybind11/pybind11.h> #include <pybind11/stl.h> #include "ast/ast.hpp" #include "pybind/pybind_utils.hpp" -#include "symtab/symbol_properties.hpp" #include "symtab/symbol.hpp" +#include "symtab/symbol_properties.hpp" #include "symtab/symbol_table.hpp" #include "visitors/symtab_visitor.hpp" #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" -{% macro var(node) -%} - {{ node.class_name | snake_case }}_ -{%- endmacro -%} -{% macro args(children) %} - {% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} -{%- endmacro -%} +/** + * \file + * \brief Symbol Table related Python bindings + */ -namespace py = pybind11; -using pybind11::literals::operator""_a; - -using namespace nmodl; -using namespace symtab; +namespace nmodl { namespace docstring { - static const char *declarationtype_enum = R"( - DeclarationType enum +static const char* sym_decl_type_enum = R"( + Variable DeclarationType enum )"; -static const char *symbol_class = R"( +static const char* symbol_class = R"( Symbol class )"; -static const char *symboltable_class = R"( +static const char* symbol_table_class = R"( SymbolTable class Attributes: @@ -55,166 +49,208 @@ static const char *symboltable_class = R"( children (dict of (str, SymbolTable)): symbol table for each enclosing block in the current nmodl block construct. )"; -static const char *symtabvisitor_class = R"( +static const char* symtabvisitor_class = R"( SymtabVisitor class )"; +} // namespace docstring +} // namespace nmodl -} -class PySymtabVisitor : private VisitorOStreamResources, public SymtabVisitor { -public: - using SymtabVisitor::SymtabVisitor; +namespace py = pybind11; +using pybind11::literals::operator""_a; + +using namespace nmodl; +using namespace symtab; +using namespace visitor; + + +// clang-format off +{% macro var(node) -%} + {{ node.class_name | snake_case }}_ +{%- endmacro -%} - explicit PySymtabVisitor(bool update = false) : SymtabVisitor(update) { }; - PySymtabVisitor(std::string filename, bool update = false) : SymtabVisitor(filename, update) {}; - PySymtabVisitor(py::object object, bool update = false) : VisitorOStreamResources(object), - SymtabVisitor(*ostream, update) { }; +{% macro args(children) %} + {% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} +{%- endmacro -%} +// clang-format on + +/** + * + * @defgroup visitor_python Visitor Python Interface + * @ingroup nmodl_python + * @brief Visitor classes for Python bindings + * @{ + */ + +/** + * \brief Class mirroring nmodl::visitor::SymtabVisitor for Python bindings + * + * \details \copydetails nmodl::visitor::SymtabVisitor + * + * This class is used to interface nmodl::visitor::SymtabVisitor with the Python + * world using `pybind11`. + */ +class PySymtabVisitor: private VisitorOStreamResources, public SymtabVisitor { + public: + explicit PySymtabVisitor(bool update = false) + : SymtabVisitor(update){}; + PySymtabVisitor(std::string filename, bool update = false) + : SymtabVisitor(filename, update){}; + PySymtabVisitor(py::object object, bool update = false) + : VisitorOStreamResources(object) + , SymtabVisitor(*ostream, update){}; }; +/** @} */ // end of visitor_python + void init_symtab_module(py::module& m) { py::module m_symtab = m.def_submodule("symtab"); - py::enum_<syminfo::DeclarationType>(m_symtab, "DeclarationType", docstring::declarationtype_enum) - .value("function", syminfo::DeclarationType::function) - .value("variable", syminfo::DeclarationType::variable) - .export_values(); + py::enum_<syminfo::DeclarationType>(m_symtab, + "DeclarationType", + docstring::sym_decl_type_enum) + .value("function", syminfo::DeclarationType::function) + .value("variable", syminfo::DeclarationType::variable) + .export_values(); py::enum_<syminfo::Scope>(m_symtab, "Scope") - .value("external", syminfo::Scope::external) - .value("global", syminfo::Scope::global) - .value("local", syminfo::Scope::local) - .value("neuron", syminfo::Scope::neuron) - .export_values(); + .value("external", syminfo::Scope::external) + .value("global", syminfo::Scope::global) + .value("local", syminfo::Scope::local) + .value("neuron", syminfo::Scope::neuron) + .export_values(); py::enum_<syminfo::Status> e_status(m_symtab, "Status", py::arithmetic()); e_status.value("created", syminfo::Status::created) - .value("from_state", syminfo::Status::from_state) - .value("globalized", syminfo::Status::globalized) - .value("inlined", syminfo::Status::inlined) - .value("localized", syminfo::Status::localized) - .value("renamed", syminfo::Status::renamed) - .value("thread_safe", syminfo::Status::thread_safe) - .export_values(); - e_status.def("__or__",[](const syminfo::Status& x, syminfo::Status y) { - return x | y; - }) - .def("__and__",[](const syminfo::Status& x, syminfo::Status y) { - return x & y; - }) - .def("__str__", &syminfo::to_string<syminfo::Status>); + .value("from_state", syminfo::Status::from_state) + .value("globalized", syminfo::Status::globalized) + .value("inlined", syminfo::Status::inlined) + .value("localized", syminfo::Status::localized) + .value("renamed", syminfo::Status::renamed) + .value("thread_safe", syminfo::Status::thread_safe) + .export_values(); + e_status.def("__or__", [](const syminfo::Status& x, syminfo::Status y) { return x | y; }) + .def("__and__", [](const syminfo::Status& x, syminfo::Status y) { return x & y; }) + .def("__str__", &syminfo::to_string<syminfo::Status>); py::enum_<syminfo::VariableType>(m_symtab, "VariableType") - .value("array", syminfo::VariableType::array) - .value("scalar", syminfo::VariableType::scalar) - .export_values(); + .value("array", syminfo::VariableType::array) + .value("scalar", syminfo::VariableType::scalar) + .export_values(); py::enum_<syminfo::Access>(m_symtab, "Access") - .value("read", syminfo::Access::read) - .value("write", syminfo::Access::write) - .export_values(); + .value("read", syminfo::Access::read) + .value("write", syminfo::Access::write) + .export_values(); py::enum_<syminfo::NmodlType> e_nmodltype(m_symtab, "NmodlType", py::arithmetic()); e_nmodltype.value("argument", syminfo::NmodlType::argument) - .value("bbcore_pointer_var", syminfo::NmodlType::bbcore_pointer_var) - .value("constant_var", syminfo::NmodlType::constant_var) - .value("dependent_def", syminfo::NmodlType::dependent_def) - .value("derivative_block", syminfo::NmodlType::derivative_block) - .value("discrete_block", syminfo::NmodlType::discrete_block) - .value("electrode_cur_var", syminfo::NmodlType::electrode_cur_var) - .value("extern_method", syminfo::NmodlType::extern_method) - .value("extern_neuron_variable", syminfo::NmodlType::extern_neuron_variable) - .value("extern_var", syminfo::NmodlType::extern_var) - .value("vactor_def", syminfo::NmodlType::factor_def) - .value("function_block", syminfo::NmodlType::function_block) - .value("function_table_block", syminfo::NmodlType::function_table_block) - .value("global_var", syminfo::NmodlType::global_var) - .value("kinetic_block", syminfo::NmodlType::kinetic_block) - .value("linear_block", syminfo::NmodlType::linear_block) - .value("local_var", syminfo::NmodlType::local_var) - .value("man_linear_block", syminfo::NmodlType::non_linear_block) - .value("nonspecifig_cur_var", syminfo::NmodlType::nonspecific_cur_var) - .value("param_assign", syminfo::NmodlType::param_assign) - .value("partial_block", syminfo::NmodlType::partial_block) - .value("pointer_var", syminfo::NmodlType::pointer_var) - .value("prime_name", syminfo::NmodlType::prime_name) - .value("procedure_block", syminfo::NmodlType::procedure_block) - .value("range_var", syminfo::NmodlType::range_var) - .value("read_ion_var", syminfo::NmodlType::read_ion_var) - .value("section_var", syminfo::NmodlType::section_var) - .value("state_var", syminfo::NmodlType::state_var) - .value("table_dependent_var", syminfo::NmodlType::table_dependent_var) - .value("table_statement_var", syminfo::NmodlType::table_statement_var) - .value("to_solve", syminfo::NmodlType::to_solve) - .value("unit_def", syminfo::NmodlType::unit_def) - .value("useion", syminfo::NmodlType::useion) - .value("write_ion_var", syminfo::NmodlType::write_ion_var) - .export_values(); - e_nmodltype.def("__or__",[](const syminfo::NmodlType& x, syminfo::NmodlType y) { - return x | y; - }) - .def("__and__",[](const syminfo::NmodlType& x, syminfo::NmodlType y) { - return x & y; - }) - .def("__str__", &syminfo::to_string<syminfo::NmodlType>); + .value("bbcore_pointer_var", syminfo::NmodlType::bbcore_pointer_var) + .value("constant_var", syminfo::NmodlType::constant_var) + .value("dependent_def", syminfo::NmodlType::dependent_def) + .value("derivative_block", syminfo::NmodlType::derivative_block) + .value("discrete_block", syminfo::NmodlType::discrete_block) + .value("electrode_cur_var", syminfo::NmodlType::electrode_cur_var) + .value("extern_method", syminfo::NmodlType::extern_method) + .value("extern_neuron_variable", syminfo::NmodlType::extern_neuron_variable) + .value("extern_var", syminfo::NmodlType::extern_var) + .value("vactor_def", syminfo::NmodlType::factor_def) + .value("function_block", syminfo::NmodlType::function_block) + .value("function_table_block", syminfo::NmodlType::function_table_block) + .value("global_var", syminfo::NmodlType::global_var) + .value("kinetic_block", syminfo::NmodlType::kinetic_block) + .value("linear_block", syminfo::NmodlType::linear_block) + .value("local_var", syminfo::NmodlType::local_var) + .value("man_linear_block", syminfo::NmodlType::non_linear_block) + .value("nonspecifig_cur_var", syminfo::NmodlType::nonspecific_cur_var) + .value("param_assign", syminfo::NmodlType::param_assign) + .value("partial_block", syminfo::NmodlType::partial_block) + .value("pointer_var", syminfo::NmodlType::pointer_var) + .value("prime_name", syminfo::NmodlType::prime_name) + .value("procedure_block", syminfo::NmodlType::procedure_block) + .value("range_var", syminfo::NmodlType::range_var) + .value("read_ion_var", syminfo::NmodlType::read_ion_var) + .value("section_var", syminfo::NmodlType::section_var) + .value("state_var", syminfo::NmodlType::state_var) + .value("table_dependent_var", syminfo::NmodlType::table_dependent_var) + .value("table_statement_var", syminfo::NmodlType::table_statement_var) + .value("to_solve", syminfo::NmodlType::to_solve) + .value("unit_def", syminfo::NmodlType::unit_def) + .value("useion", syminfo::NmodlType::useion) + .value("write_ion_var", syminfo::NmodlType::write_ion_var) + .export_values(); + e_nmodltype + .def("__or__", [](const syminfo::NmodlType& x, syminfo::NmodlType y) { return x | y; }) + .def("__and__", [](const syminfo::NmodlType& x, syminfo::NmodlType y) { return x & y; }) + .def("__str__", &syminfo::to_string<syminfo::NmodlType>); py::class_<Symbol, std::shared_ptr<Symbol>> symbol(m_symtab, "Symbol", docstring::symbol_class); - symbol.def(py::init<std::string, ast::AST*>(), "name"_a, "node"_a); + symbol.def(py::init<std::string, ast::Ast*>(), "name"_a, "node"_a); symbol.def("get_token", &Symbol::get_token) - .def("is_variable", &Symbol::is_variable) - .def("is_external_symbol_only", &Symbol::is_external_symbol_only) - .def("get_id", &Symbol::get_id) - .def("get_status", &Symbol::get_status) - .def("get_properties", &Symbol::get_properties) - .def("get_node", &Symbol::get_node) - .def("get_original_name", &Symbol::get_original_name) - .def("get_name", &Symbol::get_name) - .def("has_properties", &Symbol::has_properties) - .def("has_all_properties", &Symbol::has_all_properties) - .def("has_any_status", &Symbol::has_any_status) - .def("has_all_status", &Symbol::has_all_status) - .def("__str__", &Symbol::to_string); - - py::class_<SymbolTable> symbol_table(m_symtab, "SymbolTable", docstring::symboltable_class); - symbol_table.def(py::init<std::string, ast::AST*, bool>(), "name"_a, "node"_a, "global"_a); + .def("is_variable", &Symbol::is_variable) + .def("is_external_variable", &Symbol::is_external_variable) + .def("get_id", &Symbol::get_id) + .def("get_status", &Symbol::get_status) + .def("get_properties", &Symbol::get_properties) + .def("get_node", &Symbol::get_node) + .def("get_original_name", &Symbol::get_original_name) + .def("get_name", &Symbol::get_name) + .def("has_any_property", &Symbol::has_any_property) + .def("has_all_properties", &Symbol::has_all_properties) + .def("has_any_status", &Symbol::has_any_status) + .def("has_all_status", &Symbol::has_all_status) + .def("__str__", &Symbol::to_string); + + py::class_<SymbolTable> symbol_table(m_symtab, "SymbolTable", docstring::symbol_table_class); + symbol_table.def(py::init<std::string, ast::Ast*, bool>(), "name"_a, "node"_a, "global"_a); symbol_table.def("name", &SymbolTable::name) - .def("type", &SymbolTable::type) - .def("title", &SymbolTable::title) - .def("is_method_defined", &SymbolTable::is_method_defined) - .def("get_variables", &SymbolTable::get_variables) - .def("get_variables_with_properties", &SymbolTable::get_variables_with_properties, - py::arg("properties"), py::arg("all") = false) - .def("get_variables_with_status", &SymbolTable::get_variables_with_status, - py::arg("status"), py::arg("all") = false) - .def("get_parent_table", &SymbolTable::get_parent_table) - .def("get_parent_table_name", &SymbolTable::get_parent_table_name) - .def("lookup", &SymbolTable::lookup) - .def("lookup_in_scope", &SymbolTable::lookup_in_scope) - .def("insert", &SymbolTable::insert) - .def("insert_table", &SymbolTable::insert_table) - .def("__str__", &SymbolTable::to_string); - - py::class_<SymtabVisitor, AstVisitor, PySymtabVisitor> symtab_visitor(m_symtab, "SymtabVisitor", docstring::symtabvisitor_class); - symtab_visitor.def(py::init<std::string, bool>(), py::arg("filename"), + .def("title", &SymbolTable::title) + .def("is_method_defined", &SymbolTable::is_method_defined) + .def("get_variables", &SymbolTable::get_variables) + .def("get_variables_with_properties", + &SymbolTable::get_variables_with_properties, + py::arg("properties"), + py::arg("all") = false) + .def("get_variables_with_status", + &SymbolTable::get_variables_with_status, + py::arg("status"), + py::arg("all") = false) + .def("get_parent_table", &SymbolTable::get_parent_table) + .def("get_parent_table_name", &SymbolTable::get_parent_table_name) + .def("lookup", &SymbolTable::lookup) + .def("lookup_in_scope", &SymbolTable::lookup_in_scope) + .def("insert", &SymbolTable::insert) + .def("insert_table", &SymbolTable::insert_table) + .def("__str__", &SymbolTable::to_string); + + py::class_<SymtabVisitor, AstVisitor, PySymtabVisitor> symtab_visitor( + m_symtab, "SymtabVisitor", docstring::symtabvisitor_class); + symtab_visitor.def(py::init<std::string, bool>(), + py::arg("filename"), py::arg("update") = false); symtab_visitor.def(py::init<py::object, bool>(), py::arg("ostream"), py::arg("update") = false); symtab_visitor.def(py::init<bool>(), py::arg("update") = false) - .def("add_model_symbol_with_property", &PySymtabVisitor::add_model_symbol_with_property, "node"_a, "property"_a) - .def("setup_symbol", &PySymtabVisitor::setup_symbol) - .def("setup_symbol_table", &PySymtabVisitor::setup_symbol_table) - .def("setup_symbol_table_for_program_block", - &PySymtabVisitor::setup_symbol_table_for_program_block) - .def("setup_symbol_table_for_global_block", - &PySymtabVisitor::setup_symbol_table_for_global_block) - .def("setup_symbol_table_for_scoped_block", - &PySymtabVisitor::setup_symbol_table_for_scoped_block) + .def("add_model_symbol_with_property", + &PySymtabVisitor::add_model_symbol_with_property, + "node"_a, + "property"_a) + .def("setup_symbol", &PySymtabVisitor::setup_symbol) + .def("setup_symbol_table", &PySymtabVisitor::setup_symbol_table) + .def("setup_symbol_table_for_program_block", + &PySymtabVisitor::setup_symbol_table_for_program_block) + .def("setup_symbol_table_for_global_block", + &PySymtabVisitor::setup_symbol_table_for_global_block) + .def("setup_symbol_table_for_scoped_block", + &PySymtabVisitor::setup_symbol_table_for_scoped_block) + // clang-format off {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &PySymtabVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} - + // clang-format on } #pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index 8126d53083..e7c7c811d6 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -22,19 +22,26 @@ #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" -using pybind11::literals::operator""_a; +#pragma clang diagnostic push +#pragma ide diagnostic ignored "OCDFAInspection" + +/** + * \file + * \brief Visitor related Python bindings + */ +namespace nmodl { namespace docstring { -static const char *visitor_class = R"( - Visitor class +static const char* visitor_class = R"( + Base Visitor class )"; -static const char *astvisitor_class = R"( +static const char* ast_visitor_class = R"( AstVisitor class )"; -static const char *astlookupvisitor_class = R"( +static const char* ast_lookup_visitor_class = R"( AstLookupVisitor class Attributes: @@ -42,45 +49,43 @@ static const char *astlookupvisitor_class = R"( nodes (list of AST): matching nodes found in the AST )"; -static const char *nmodlprintvisitor_class = R"( - +static const char* nmodl_print_visitor_class = R"( NmodlPrintVisitor class )"; -static const char *constantfoldervisitor_class = R"( - +static const char* constant_folder_visitor_class = R"( ConstantFolderVisitor class )"; -static const char *inlinevisitor_class = R"( - +static const char* inline_visitor_class = R"( InlineVisitor class )"; -static const char *kineticblockvisitor_class = R"( - +static const char* kinetic_block_visitor_class = R"( KineticBlockVisitor class )"; -static const char *localvarrenamevisitor_class = R"( - +static const char* local_var_rename_visitor_class = R"( LocalVarRenameVisitor class )"; -static const char *sympyconductancevisitor_class = R"( - +static const char* sympy_conductance_visitor_class = R"( SympyConductanceVisitor class )"; -static const char *sympysolvervisitor_class = R"( - +static const char* sympy_solver_visitor_class = R"( SympySolverVisitor class )"; -} +} // namespace docstring +} // namespace nmodl -#pragma clang diagnostic push -#pragma ide diagnostic ignored "OCDFAInspection" + +using pybind11::literals::operator""_a; +namespace py = pybind11; + + +// clang-format off {% macro var(node) -%} {{ node.class_name | snake_case }}_ {%- endmacro -%} @@ -89,7 +94,6 @@ static const char *sympysolvervisitor_class = R"( {% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} {%- endmacro -%} -namespace py = pybind11; {% for node in nodes %} void PyVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) { @@ -102,24 +106,39 @@ void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_nam PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, node); } {% endfor %} +// clang-format on -class PyNmodlPrintVisitor : private VisitorOStreamResources, public NmodlPrintVisitor { -public: +/** + * \brief Class mirroring nmodl::NmodlPrintVisitor for Python bindings + * + * \details \copydetails nmodl::NmodlPrintVisitor + * + * This class is used to interface nmodl::NmodlPrintVisitor with the Python + * world using `pybind11`. + */ +class PyNmodlPrintVisitor: private VisitorOStreamResources, public NmodlPrintVisitor { + public: using NmodlPrintVisitor::NmodlPrintVisitor; using VisitorOStreamResources::flush; PyNmodlPrintVisitor() = default; - PyNmodlPrintVisitor(std::string filename) : NmodlPrintVisitor(filename) {}; - PyNmodlPrintVisitor(py::object object) : VisitorOStreamResources(object), - NmodlPrintVisitor(*ostream) { }; + PyNmodlPrintVisitor(std::string filename) + : NmodlPrintVisitor(filename){}; + + PyNmodlPrintVisitor(py::object object) + : VisitorOStreamResources(object) + , NmodlPrintVisitor(*ostream){}; + + // clang-format off {% for node in nodes %} void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override { NmodlPrintVisitor::visit_{{ node.class_name|snake_case }}(node); flush(); } {% endfor %} + // clang-format on }; @@ -128,63 +147,72 @@ void init_visitor_module(py::module& m) { py::class_<Visitor, PyVisitor> visitor(m_visitor, "Visitor", docstring::visitor_class); visitor.def(py::init<>()) + // clang-format off {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &Visitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} - {% endfor %} + {% endfor %} // clang-format on - py::class_<AstVisitor, Visitor, PyAstVisitor> ast_visitor(m_visitor, "AstVisitor", docstring::astvisitor_class); + py::class_<AstVisitor, Visitor, PyAstVisitor> + ast_visitor(m_visitor, "AstVisitor", docstring::ast_visitor_class); ast_visitor.def(py::init<>()) + // clang-format off {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &AstVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} + // clang-format on - py::class_<PyNmodlPrintVisitor, Visitor> nmodl_visitor(m_visitor, "NmodlPrintVisitor", docstring::nmodlprintvisitor_class); + py::class_<PyNmodlPrintVisitor, Visitor> + nmodl_visitor(m_visitor, "NmodlPrintVisitor", docstring::nmodl_print_visitor_class); nmodl_visitor.def(py::init<std::string>()); nmodl_visitor.def(py::init<py::object>()); nmodl_visitor.def(py::init<>()) + // clang-format off {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &PyNmodlPrintVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} - {% endfor %} + {% endfor %} // clang-format on - py::class_<AstLookupVisitor, Visitor> lookup_visitor(m_visitor, "AstLookupVisitor", docstring::astlookupvisitor_class); + py::class_<AstLookupVisitor, Visitor> + lookup_visitor(m_visitor, "AstLookupVisitor", docstring::ast_lookup_visitor_class); + // clang-format off lookup_visitor.def(py::init<>()) .def(py::init<ast::AstNodeType>()) .def("get_nodes", &AstLookupVisitor::get_nodes) .def("clear", &AstLookupVisitor::clear) - .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*, ast::AstNodeType)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector<std::shared_ptr<ast::AST>> (AstLookupVisitor::*)(ast::AST*, std::vector<ast::AstNodeType>&)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector<std::shared_ptr<ast::Ast>> (AstLookupVisitor::*)(ast::Ast*)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector<std::shared_ptr<ast::Ast>> (AstLookupVisitor::*)(ast::Ast*, ast::AstNodeType)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector<std::shared_ptr<ast::Ast>> (AstLookupVisitor::*)(ast::Ast*, std::vector<ast::AstNodeType>&)) &AstLookupVisitor::lookup) {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &AstLookupVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} {% endfor %} - py::class_<ConstantFolderVisitor, AstVisitor> constant_folder_visitor(m_visitor, "ConstantFolderVisitor", docstring::constantfoldervisitor_class); + py::class_<ConstantFolderVisitor, AstVisitor> constant_folder_visitor(m_visitor, "ConstantFolderVisitor", docstring::constant_folder_visitor_class); constant_folder_visitor.def(py::init<>()) .def("visit_program", &ConstantFolderVisitor::visit_program); - py::class_<InlineVisitor, AstVisitor> inline_visitor(m_visitor, "InlineVisitor", docstring::inlinevisitor_class); + py::class_<InlineVisitor, AstVisitor> inline_visitor(m_visitor, "InlineVisitor", docstring::inline_visitor_class); inline_visitor.def(py::init<>()) .def("visit_program", &InlineVisitor::visit_program); - py::class_<KineticBlockVisitor, AstVisitor> kinetic_block_visitor(m_visitor, "KineticBlockVisitor", docstring::kineticblockvisitor_class); + py::class_<KineticBlockVisitor, AstVisitor> kinetic_block_visitor(m_visitor, "KineticBlockVisitor", docstring::kinetic_block_visitor_class); kinetic_block_visitor.def(py::init<>()) .def("visit_program", &KineticBlockVisitor::visit_program); - py::class_<LocalVarRenameVisitor, AstVisitor> local_var_rename_visitor(m_visitor, "LocalVarRenameVisitor", docstring::localvarrenamevisitor_class); + py::class_<LocalVarRenameVisitor, AstVisitor> local_var_rename_visitor(m_visitor, "LocalVarRenameVisitor", docstring::local_var_rename_visitor_class); local_var_rename_visitor.def(py::init<>()) .def("visit_program", &LocalVarRenameVisitor::visit_program); - py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor", docstring::sympyconductancevisitor_class); + py::class_<SympyConductanceVisitor, AstVisitor> sympy_conductance_visitor(m_visitor, "SympyConductanceVisitor", docstring::sympy_conductance_visitor_class); sympy_conductance_visitor.def(py::init<>()) .def("visit_program", &SympyConductanceVisitor::visit_program); - py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor", docstring::sympysolvervisitor_class); + py::class_<SympySolverVisitor, AstVisitor> sympy_solver_visitor(m_visitor, "SympySolverVisitor", docstring::sympy_solver_visitor_class); sympy_solver_visitor.def(py::init<bool>(), py::arg("use_pade_approx")=false) .def("visit_program", &SympySolverVisitor::visit_program); + // clang-format on } #pragma clang diagnostic pop diff --git a/src/nmodl/language/templates/pybind/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp index 989c178740..6ecb3df927 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.hpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.hpp @@ -7,14 +7,29 @@ #pragma once +/** + * \file + * \brief Visitors extending base visitors for Python interface + */ + +#include <pybind11/pybind11.h> +#include <pybind11/stl.h> + #include "ast/ast.hpp" #include "visitors/visitor.hpp" #include "visitors/ast_visitor.hpp" -#include <pybind11/pybind11.h> -#include <pybind11/stl.h> using namespace nmodl; +using namespace visitor; +/** + * \brief Class mirroring nmodl::visitor::Visitor for Python bindings + * + * \details \copydetails nmodl::visitor::Visitor + * + * This class is used to interface nmodl::visitor::Visitor with the Python + * world using `pybind11`. + */ class PyVisitor : public Visitor { public: using Visitor::Visitor; @@ -24,7 +39,15 @@ class PyVisitor : public Visitor { {% endfor %} }; -/* Python interface of basic visitor implementation */ + +/** + * \brief Class mirroring nmodl::visitor::AstVisitor for Python bindings + * + * \details \copydetails nmodl::visitor::AstVisitor + * + * This class is used to interface nmodl::visitor::AstVisitor with the Python + * world using `pybind11`. + */ class PyAstVisitor : public AstVisitor { public: using AstVisitor::AstVisitor; diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index d970bcb7e2..494e11d24b 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -9,6 +9,7 @@ namespace nmodl { +namespace visitor { using namespace ast; @@ -19,4 +20,5 @@ void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* n {% endfor %} +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index 9d3adbb8cc..c1f9406f31 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -7,19 +7,38 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::AstVisitor + */ + #include "ast/ast.hpp" #include "visitors/visitor.hpp" namespace nmodl { +namespace visitor { -/* Basic visitor implementation */ -class AstVisitor : public Visitor { +/** + * @ingroup visitor_classes + * @{ + */ - public: - {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; - {% endfor %} +/** + * \brief Concrete visitor for all AST classes + * + * This class defines interface for all concrete visitors implementation. + * Note that this class only provides interface that could be implemented + * by concrete visitors like ast::AstVisitor. + */ +class AstVisitor : public Visitor { + public: + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index e68c4c50f9..04f382e87e 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -9,6 +9,7 @@ namespace nmodl { +namespace visitor { using namespace ast; @@ -42,4 +43,5 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* {% endfor %} +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index dc56c222a5..5653e649ac 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -7,29 +7,61 @@ #pragma once -#include "visitors/ast_visitor.hpp" +/** + * \file + * \brief \copybrief nmodl::visitor::JSONVisitor + */ + #include "printer/json_printer.hpp" +#include "visitors/ast_visitor.hpp" namespace nmodl { +namespace visitor { -/* Concrete visitor for printing AST in JSON format */ -class JSONVisitor : public AstVisitor { +/** + * @addtogroup visitor_classes + * @{ + */ - private: - std::unique_ptr<JSONPrinter> printer; +/** + * \class JSONVisitor + * \brief %Visitor for printing AST in JSON format + * + * Convert AST into JSON form form using AST visitor. This is used + * for debugging or visualization purpose. + */ +class JSONVisitor: public AstVisitor { + private: + std::unique_ptr<printer::JSONPrinter> printer; - public: - JSONVisitor() : printer(new JSONPrinter()) {} - JSONVisitor(std::string filename) : printer(new JSONPrinter(filename)) {} - JSONVisitor(std::stringstream &ss) : printer(new JSONPrinter(ss)) {} + public: + JSONVisitor() + : printer(new printer::JSONPrinter()) {} - void flush() { printer->flush(); } - void compact_json(bool flag) { printer->compact_json(flag); } - void expand_keys(bool flag) { printer->expand_keys(flag); } + JSONVisitor(std::string filename) + : printer(new printer::JSONPrinter(filename)) {} - {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; - {% endfor %} + JSONVisitor(std::stringstream& ss) + : printer(new printer::JSONPrinter(ss)) {} + + void flush() { + printer->flush(); + } + void compact_json(bool flag) { + printer->compact_json(flag); + } + void expand_keys(bool flag) { + printer->expand_keys(flag); + } + + // clang-format off + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} + // clang-format on }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index 0a261c552e..a101a2fe21 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -10,6 +10,7 @@ namespace nmodl { +namespace visitor { using namespace ast; @@ -25,15 +26,14 @@ void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name {% endfor %} -std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node, std::vector<AstNodeType>& _types) { +std::vector<std::shared_ptr<ast::Ast>> AstLookupVisitor::lookup(Ast* node, std::vector<AstNodeType>& _types) { nodes.clear(); types = _types; node->accept(this); return nodes; } - -std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node, AstNodeType type) { +std::vector<std::shared_ptr<ast::Ast>> AstLookupVisitor::lookup(Ast* node, AstNodeType type) { nodes.clear(); types.clear(); types.push_back(type); @@ -41,11 +41,11 @@ std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node, AstNo return nodes; } - -std::vector<std::shared_ptr<ast::AST>> AstLookupVisitor::lookup(AST* node) { +std::vector<std::shared_ptr<ast::Ast>> AstLookupVisitor::lookup(Ast* node) { nodes.clear(); node->accept(this); return nodes; } +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index 6c095b108b..564c8173dd 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -7,51 +7,67 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::AstLookupVisitor + */ + #include "ast/ast.hpp" #include "visitors/visitor.hpp" - namespace nmodl { +namespace visitor { /** - * \class AstLookupVisitor - * \brief Visitor to find ast nodes based on on the ast types + * @addtogroup visitor_classes + * @{ */ -class AstLookupVisitor : public Visitor { - private: - /// node types to search in the ast - std::vector<ast::AstNodeType> types; - - /// matching nodes found in the ast - std::vector<std::shared_ptr<ast::AST>> nodes; +/** + * \class AstLookupVisitor + * \brief %Visitor to find AST nodes based on their types + */ +class AstLookupVisitor: public Visitor { + private: + /// node types to search in the ast + std::vector<ast::AstNodeType> types; - public: + /// matching nodes found in the ast + std::vector<std::shared_ptr<ast::Ast>> nodes; - AstLookupVisitor() = default; + public: + AstLookupVisitor() = default; - AstLookupVisitor(ast::AstNodeType type) : types{type} {} + AstLookupVisitor(ast::AstNodeType type) + : types{type} {} - AstLookupVisitor(const std::vector<ast::AstNodeType>& types) : types(types) {} + AstLookupVisitor(const std::vector<ast::AstNodeType>& types) + : types(types) {} - std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST *node); + std::vector<std::shared_ptr<ast::Ast>> lookup(ast::Ast* node); - std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST* node, ast::AstNodeType type); + std::vector<std::shared_ptr<ast::Ast>> lookup(ast::Ast* node, ast::AstNodeType type); - std::vector<std::shared_ptr<ast::AST>> lookup(ast::AST* node, std::vector<ast::AstNodeType>& types); + std::vector<std::shared_ptr<ast::Ast>> lookup(ast::Ast* node, + std::vector<ast::AstNodeType>& types); - const std::vector<std::shared_ptr<ast::AST>>& get_nodes() const noexcept { - return nodes; - } + const std::vector<std::shared_ptr<ast::Ast>>& get_nodes() const noexcept { + return nodes; + } - void clear() { - types.clear(); - nodes.clear(); - } + void clear() { + types.clear(); + nodes.clear(); + } - {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; - {% endfor %} + // clang-format off + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} + // clang-format on }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 2ea288b672..e51e7a72df 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -10,6 +10,7 @@ namespace nmodl { +namespace visitor { using namespace ast; @@ -122,4 +123,5 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% endfor %} +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 2f11392dc5..b30b6e8564 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -7,40 +7,68 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::NmodlPrintVisitor + */ + #include <set> #include "ast/ast.hpp" #include "printer/nmodl_printer.hpp" namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + +/** + * \class NmodlPrintVisitor + * \brief %Visitor for printing AST back to NMODL + */ +class NmodlPrintVisitor: public Visitor { + private: + std::unique_ptr<printer::NMODLPrinter> printer; + + /// node types to exclude while printing + std::set<ast::AstNodeType> exclude_types; + + /// check if node is to be excluded while printing + bool is_exclude_type(ast::AstNodeType type) const { + return exclude_types.find(type) != exclude_types.end(); + } + + public: + NmodlPrintVisitor() + : printer(new printer::NMODLPrinter()) {} -/* Visitor for printing AST back to NMODL */ -class NmodlPrintVisitor : public Visitor { - - private: - std::unique_ptr<NMODLPrinter> printer; - - /// node types to exclude while printing - std::set<ast::AstNodeType> exclude_types; - - /// check if node is to be excluded while printing - bool is_exclude_type(ast::AstNodeType type) const { - return exclude_types.find(type) != exclude_types.end(); - } - - public: - NmodlPrintVisitor() : printer(new NMODLPrinter()) {} - NmodlPrintVisitor(std::string filename) : printer(new NMODLPrinter(filename)) {} - NmodlPrintVisitor(std::ostream& stream) : printer(new NMODLPrinter(stream)) {} - NmodlPrintVisitor(std::ostream& stream, const std::set<ast::AstNodeType>& types) - : printer(new NMODLPrinter(stream)), - exclude_types(types) {} - - {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; - {% endfor %} - template<typename T> - void visit_element(const std::vector<T>& elements, std::string separator, bool program, bool statement); + NmodlPrintVisitor(std::string filename) + : printer(new printer::NMODLPrinter(filename)) {} + + NmodlPrintVisitor(std::ostream& stream) + : printer(new printer::NMODLPrinter(stream)) {} + + NmodlPrintVisitor(std::ostream& stream, const std::set<ast::AstNodeType>& types) + : printer(new printer::NMODLPrinter(stream)) + , exclude_types(types){} + + // clang-format off + {% for node in nodes %} + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} + // clang-format on + + template <typename T> + void visit_element(const std::vector<T>& elements, + std::string separator, + bool program, + bool statement); }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp index 182631dd59..4daeddef3b 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.cpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.cpp @@ -11,6 +11,7 @@ namespace nmodl { +namespace visitor { using namespace ast; using symtab::syminfo::NmodlType; @@ -37,4 +38,5 @@ void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { {% endif %} {% endfor %} +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index 9797d10321..8793bdbfa7 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -7,35 +7,56 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::SymtabVisitor + */ + #include <set> -#include "visitors/json_visitor.hpp" -#include "visitors/ast_visitor.hpp" #include "symtab/symbol_table.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/json_visitor.hpp" namespace nmodl { - -/* Concrete visitor for constructing symbol table from AST */ -class SymtabVisitor : public AstVisitor { - -private: +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + +/** + * \class SymtabVisitor + * \brief Concrete visitor for constructing symbol table from AST + */ +class SymtabVisitor: public AstVisitor { + private: symtab::ModelSymbolTable* modsymtab; - std::unique_ptr<JSONPrinter> printer; + std::unique_ptr<printer::JSONPrinter> printer; std::set<std::string> block_to_solve; bool update = false; bool under_state_block = false; -public: - explicit SymtabVisitor(bool update = false) : printer(new JSONPrinter()), update(update) {} - SymtabVisitor(std::ostream &os, bool update = false) : printer(new JSONPrinter(os)), update(update) {} - SymtabVisitor(std::string filename, bool update = false) : printer(new JSONPrinter(filename)), update(update) {} + public: + explicit SymtabVisitor(bool update = false) + : printer(new printer::JSONPrinter()) + , update(update) {} + + SymtabVisitor(std::ostream& os, bool update = false) + : printer(new printer::JSONPrinter(os)) + , update(update) {} + + SymtabVisitor(std::string filename, bool update = false) + : printer(new printer::JSONPrinter(filename)) + , update(update) {} void add_model_symbol_with_property(ast::Node* node, symtab::syminfo::NmodlType property); void setup_symbol(ast::Node* node, symtab::syminfo::NmodlType property); - void setup_symbol_table(ast::AST* node, const std::string& name, bool is_global); + void setup_symbol_table(ast::Ast* node, const std::string& name, bool is_global); void setup_symbol_table_for_program_block(ast::Program* node); @@ -43,11 +64,16 @@ class SymtabVisitor : public AstVisitor { void setup_symbol_table_for_scoped_block(ast::Node* node, const std::string& name); + // clang-format off {% for node in nodes %} {% if node.is_symtab_method_required %} void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; {% endif %} {% endfor %} + // clang-format on }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 0d7a90a8bb..92dcb3ef2f 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -9,16 +9,40 @@ namespace nmodl { +/// Implementation of different AST visitors +namespace visitor { -/* Abstract base class for all visitor implementations */ +/** + * @defgroup visitor Visitor Infrastructure + * @brief All visitors related implementation details + * + * @defgroup visitor_classes Visitors + * @ingroup visitor + * @brief Different visitors implemented in NMODL + * @{ + */ + +/** + * \brief Abstract base class for all visitors implementation + * + * This class defines interface for all concrete visitors implementation. + * Note that this class only provides interface that could be implemented + * by oncrete visitors like ast::AstVisitor. + * + * \sa ast::AstVisitor + */ class Visitor { public: virtual ~Visitor() = default; {% for node in nodes %} + /// visit node of type ast::{{ node.class_name }} virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) = 0; {% endfor %} }; -} // namespace nmodl \ No newline at end of file +} // namespace visitor +} // namespace nmodl + +/** @} */ // end of visitor_classes \ No newline at end of file diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 194d8a4d2e..8f383c17ab 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -74,9 +74,9 @@ class CLexer: public CFlexLexer { * @param in Input stream from where tokens will be read * @param out Output stream where output will be sent */ - explicit CLexer(CDriver& drv, std::istream* in = nullptr, std::ostream* out = nullptr) + explicit CLexer(CDriver& driver, std::istream* in = nullptr, std::ostream* out = nullptr) : CFlexLexer(in, out) - , driver(drv) {} + , driver(driver) {} ~CLexer() override = default; @@ -109,4 +109,4 @@ class CLexer: public CFlexLexer { /** @} */ // end of lexer } // namespace parser -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 40690ebad9..809c5f21aa 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -45,7 +45,7 @@ void scan_c_code(std::istream& in) { int main(int argc, const char* argv[]) { - CLI::App app{"C-Lexer : Standalone Lexer for C Code({})"_format(version::to_string())}; + CLI::App app{"C-Lexer : Standalone Lexer for C Code({})"_format(Version::to_string())}; std::vector<std::string> c_files; std::vector<std::string> c_codes; diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index cc2f88ead4..9f3cd7c985 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -119,7 +119,7 @@ void tokenize(const std::string& mod_text) { int main(int argc, const char* argv[]) { - CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code({})"_format(version::to_string())}; + CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code({})"_format(Version::to_string())}; std::vector<std::string> mod_files; std::vector<std::string> mod_texts; diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index 08f8dff1b5..a3aeee0c18 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -25,7 +25,7 @@ using namespace fmt::literals; using namespace nmodl; int main(int argc, const char* argv[]) { - CLI::App app{"Unit-Lexer : Standalone Lexer for Units({})"_format(version::to_string())}; + CLI::App app{"Unit-Lexer : Standalone Lexer for Units({})"_format(Version::to_string())}; std::vector<std::string> files; app.add_option("file", files, "One or more units files to process") diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index 9398f4e2a7..0e1de5cd68 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -9,7 +9,7 @@ /** * \file modl.h - * \brief Definitions from mod2c/nocmodl implementation + * \brief Legacy macro definitions from mod2c/nocmodl implementation * * Original implementation of NMODL use various flags to help * code generation. These flags are implemented as bit masks diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 2d42b56161..d8172e4d4a 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -64,6 +64,9 @@ class ModToken { bool external = false; public: + /// \name Ctor & dtor + /// \{ + ModToken() : pos(nullptr, 0){}; @@ -76,6 +79,8 @@ class ModToken { , token(token) , pos(pos) {} + /// \} + /// Return a new instance of token ModToken* clone() const { return new ModToken(*this); diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 46c49eac14..c383be783c 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -22,112 +22,108 @@ using Parser = parser::NmodlParser; /// details of lexer tokens namespace details { -// clang-format off - - /** - * \brief Keywords from NMODL language - * - * Keywords are defined with key-value pair where key is name - * from scanner and value is token type used in parser. - * - * \todo Some keywords have different token names, e.g. TITLE - * keyword has MODEL as a keyword. These token names are used - * in multiple context and hence we are keeping original names. - * Once we finish code generation part then we change this. - */ - static std::map<std::string, TokenType> keywords = { - {"VERBATIM", Token::VERBATIM}, - {"COMMENT", Token::BLOCK_COMMENT}, - {"TITLE", Token::MODEL}, - {"CONSTANT", Token::CONSTANT}, - {"PARAMETER", Token::PARAMETER}, - {"INDEPENDENT", Token::INDEPENDENT}, - {"ASSIGNED", Token::DEPENDENT}, - {"INITIAL", Token::INITIAL1}, - {"TERMINAL", Token::TERMINAL}, - {"DERIVATIVE", Token::DERIVATIVE}, - {"EQUATION", Token::BREAKPOINT}, - {"BREAKPOINT", Token::BREAKPOINT}, - {"CONDUCTANCE", Token::CONDUCTANCE}, - {"SOLVE", Token::SOLVE}, - {"STATE", Token::STATE}, - {"STEPPED", Token::STEPPED}, - {"LINEAR", Token::LINEAR}, - {"NONLINEAR", Token::NONLINEAR}, - {"DISCRETE", Token::DISCRETE}, - {"FUNCTION", Token::FUNCTION1}, - {"FUNCTION_TABLE", Token::FUNCTION_TABLE}, - {"PROCEDURE", Token::PROCEDURE}, - {"PARTIAL", Token::PARTIAL}, - {"DEL2", Token::DEL2}, - {"DEL", Token::DEL}, - {"LOCAL", Token::LOCAL}, - {"METHOD", Token::USING}, - {"STEADYSTATE", Token::STEADYSTATE}, - {"SENS", Token::SENS}, - {"STEP", Token::STEP}, - {"WITH", Token::WITH}, - {"FROM", Token::FROM}, - {"FORALL", Token::FORALL1}, - {"TO", Token::TO}, - {"BY", Token::BY}, - {"if", Token::IF}, - {"else", Token::ELSE}, - {"while", Token::WHILE}, - {"START", Token::START1}, - {"DEFINE", Token::DEFINE1}, - {"KINETIC", Token::KINETIC}, - {"CONSERVE", Token::CONSERVE}, - {"PLOT", Token::PLOT}, - {"VS", Token::VS}, - {"LAG", Token::LAG}, - {"RESET", Token::RESET}, - {"MATCH", Token::MATCH}, - {"MODEL_LEVEL", Token::MODEL_LEVEL}, - {"SWEEP", Token::SWEEP}, - {"FIRST", Token::FIRST}, - {"LAST", Token::LAST}, - {"COMPARTMENT", Token::COMPARTMENT}, - {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, - {"PUTQ", Token::PUTQ}, - {"GETQ", Token::GETQ}, - {"IFERROR", Token::IFERROR}, - {"SOLVEFOR", Token::SOLVEFOR}, - {"UNITS", Token::UNITS}, - {"UNITSON", Token::UNITSON}, - {"UNITSOFF", Token::UNITSOFF}, - {"TABLE", Token::TABLE}, - {"DEPEND", Token::DEPEND}, - {"NEURON", Token::NEURON}, - {"SUFFIX", Token::SUFFIX}, - {"POINT_PROCESS", Token::SUFFIX}, - {"ARTIFICIAL_CELL", Token::SUFFIX}, - {"NONSPECIFIC_CURRENT", Token::NONSPECIFIC}, - {"ELECTRODE_CURRENT", Token::ELECTRODE_CURRENT}, - {"SECTION", Token::SECTION}, - {"RANGE", Token::RANGE}, - {"USEION", Token::USEION}, - {"READ", Token::READ}, - {"WRITE", Token::WRITE}, - {"VALENCE", Token::VALENCE}, - {"CHARGE", Token::VALENCE}, - {"GLOBAL", Token::GLOBAL}, - {"POINTER", Token::POINTER}, - {"BBCOREPOINTER", Token::BBCOREPOINTER}, - {"EXTERNAL", Token::EXTERNAL}, - {"INCLUDE", Token::INCLUDE1}, - {"CONSTRUCTOR", Token::CONSTRUCTOR}, - {"DESTRUCTOR", Token::DESTRUCTOR}, - {"NET_RECEIVE", Token::NETRECEIVE}, - {"BEFORE", Token::BEFORE}, - {"AFTER", Token::AFTER}, - {"WATCH", Token::WATCH}, - {"FOR_NETCONS", Token::FOR_NETCONS}, - {"THREADSAFE", Token::THREADSAFE}, - {"PROTECT", Token::PROTECT}, - {"MUTEXLOCK", Token::NRNMUTEXLOCK}, - {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; -// clang-format on +/** + * \brief Keywords from NMODL language + * + * Keywords are defined with key-value pair where key is name + * from scanner and value is token type used in parser. + * + * \todo Some keywords have different token names, e.g. TITLE + * keyword has MODEL as a keyword. These token names are used + * in multiple context and hence we are keeping original names. + * Once we finish code generation part then we change this. + */ +static std::map<std::string, TokenType> keywords = {{"VERBATIM", Token::VERBATIM}, + {"COMMENT", Token::BLOCK_COMMENT}, + {"TITLE", Token::MODEL}, + {"CONSTANT", Token::CONSTANT}, + {"PARAMETER", Token::PARAMETER}, + {"INDEPENDENT", Token::INDEPENDENT}, + {"ASSIGNED", Token::DEPENDENT}, + {"INITIAL", Token::INITIAL1}, + {"TERMINAL", Token::TERMINAL}, + {"DERIVATIVE", Token::DERIVATIVE}, + {"EQUATION", Token::BREAKPOINT}, + {"BREAKPOINT", Token::BREAKPOINT}, + {"CONDUCTANCE", Token::CONDUCTANCE}, + {"SOLVE", Token::SOLVE}, + {"STATE", Token::STATE}, + {"STEPPED", Token::STEPPED}, + {"LINEAR", Token::LINEAR}, + {"NONLINEAR", Token::NONLINEAR}, + {"DISCRETE", Token::DISCRETE}, + {"FUNCTION", Token::FUNCTION1}, + {"FUNCTION_TABLE", Token::FUNCTION_TABLE}, + {"PROCEDURE", Token::PROCEDURE}, + {"PARTIAL", Token::PARTIAL}, + {"DEL2", Token::DEL2}, + {"DEL", Token::DEL}, + {"LOCAL", Token::LOCAL}, + {"METHOD", Token::USING}, + {"STEADYSTATE", Token::STEADYSTATE}, + {"SENS", Token::SENS}, + {"STEP", Token::STEP}, + {"WITH", Token::WITH}, + {"FROM", Token::FROM}, + {"FORALL", Token::FORALL1}, + {"TO", Token::TO}, + {"BY", Token::BY}, + {"if", Token::IF}, + {"else", Token::ELSE}, + {"while", Token::WHILE}, + {"START", Token::START1}, + {"DEFINE", Token::DEFINE1}, + {"KINETIC", Token::KINETIC}, + {"CONSERVE", Token::CONSERVE}, + {"PLOT", Token::PLOT}, + {"VS", Token::VS}, + {"LAG", Token::LAG}, + {"RESET", Token::RESET}, + {"MATCH", Token::MATCH}, + {"MODEL_LEVEL", Token::MODEL_LEVEL}, + {"SWEEP", Token::SWEEP}, + {"FIRST", Token::FIRST}, + {"LAST", Token::LAST}, + {"COMPARTMENT", Token::COMPARTMENT}, + {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, + {"PUTQ", Token::PUTQ}, + {"GETQ", Token::GETQ}, + {"IFERROR", Token::IFERROR}, + {"SOLVEFOR", Token::SOLVEFOR}, + {"UNITS", Token::UNITS}, + {"UNITSON", Token::UNITSON}, + {"UNITSOFF", Token::UNITSOFF}, + {"TABLE", Token::TABLE}, + {"DEPEND", Token::DEPEND}, + {"NEURON", Token::NEURON}, + {"SUFFIX", Token::SUFFIX}, + {"POINT_PROCESS", Token::SUFFIX}, + {"ARTIFICIAL_CELL", Token::SUFFIX}, + {"NONSPECIFIC_CURRENT", Token::NONSPECIFIC}, + {"ELECTRODE_CURRENT", Token::ELECTRODE_CURRENT}, + {"SECTION", Token::SECTION}, + {"RANGE", Token::RANGE}, + {"USEION", Token::USEION}, + {"READ", Token::READ}, + {"WRITE", Token::WRITE}, + {"VALENCE", Token::VALENCE}, + {"CHARGE", Token::VALENCE}, + {"GLOBAL", Token::GLOBAL}, + {"POINTER", Token::POINTER}, + {"BBCOREPOINTER", Token::BBCOREPOINTER}, + {"EXTERNAL", Token::EXTERNAL}, + {"INCLUDE", Token::INCLUDE1}, + {"CONSTRUCTOR", Token::CONSTRUCTOR}, + {"DESTRUCTOR", Token::DESTRUCTOR}, + {"NET_RECEIVE", Token::NETRECEIVE}, + {"BEFORE", Token::BEFORE}, + {"AFTER", Token::AFTER}, + {"WATCH", Token::WATCH}, + {"FOR_NETCONS", Token::FOR_NETCONS}, + {"THREADSAFE", Token::THREADSAFE}, + {"PROTECT", Token::PROTECT}, + {"MUTEXLOCK", Token::NRNMUTEXLOCK}, + {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; /** @@ -149,37 +145,35 @@ struct MethodInfo { }; -// clang-format off - /** - * Integration methods available in the NMODL - * - * Different integration methods are available in NMODL and they are used with - * different block types in NMODL. This variable provide list of method names, - * which blocks they can be used with and whether it is usable with variable - * timestep. - * - * \todo MethodInfo::subtype should be changed from integer flag to proper type - */ - static std::map<std::string, MethodInfo> methods = {{"adams", MethodInfo(DERF | KINF, 0)}, - {"runge", MethodInfo(DERF | KINF, 0)}, - {"euler", MethodInfo(DERF | KINF, 0)}, - {"adeuler", MethodInfo(DERF | KINF, 1)}, - {"heun", MethodInfo(DERF | KINF, 0)}, - {"adrunge", MethodInfo(DERF | KINF, 1)}, - {"gear", MethodInfo(DERF | KINF, 1)}, - {"newton", MethodInfo(NLINF, 0)}, - {"simplex", MethodInfo(NLINF, 0)}, - {"simeq", MethodInfo(LINF, 0)}, - {"seidel", MethodInfo(LINF, 0)}, - {"_advance", MethodInfo(KINF, 0)}, - {"sparse", MethodInfo(KINF, 0)}, - {"derivimplicit", MethodInfo(DERF, 0)}, - {"cnexp", MethodInfo(DERF, 0)}, - {"clsoda", MethodInfo(DERF | KINF, 1)}, - {"after_cvode", MethodInfo(0, 0)}, - {"cvode_t", MethodInfo(0, 0)}, - {"cvode_t_v", MethodInfo(0, 0)}}; -// clang-format on +/** + * Integration methods available in the NMODL + * + * Different integration methods are available in NMODL and they are used with + * different block types in NMODL. This variable provide list of method names, + * which blocks they can be used with and whether it is usable with variable + * timestep. + * + * \todo MethodInfo::subtype should be changed from integer flag to proper type + */ +static std::map<std::string, MethodInfo> methods = {{"adams", MethodInfo(DERF | KINF, 0)}, + {"runge", MethodInfo(DERF | KINF, 0)}, + {"euler", MethodInfo(DERF | KINF, 0)}, + {"adeuler", MethodInfo(DERF | KINF, 1)}, + {"heun", MethodInfo(DERF | KINF, 0)}, + {"adrunge", MethodInfo(DERF | KINF, 1)}, + {"gear", MethodInfo(DERF | KINF, 1)}, + {"newton", MethodInfo(NLINF, 0)}, + {"simplex", MethodInfo(NLINF, 0)}, + {"simeq", MethodInfo(LINF, 0)}, + {"seidel", MethodInfo(LINF, 0)}, + {"_advance", MethodInfo(KINF, 0)}, + {"sparse", MethodInfo(KINF, 0)}, + {"derivimplicit", MethodInfo(DERF, 0)}, + {"cnexp", MethodInfo(DERF, 0)}, + {"clsoda", MethodInfo(DERF | KINF, 1)}, + {"after_cvode", MethodInfo(0, 0)}, + {"cvode_t", MethodInfo(0, 0)}, + {"cvode_t_v", MethodInfo(0, 0)}}; /** @@ -204,111 +198,109 @@ struct MethodInfo { enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; -// clang-format off - static std::map<std::string, DefinitionType> extern_definitions = { - {"first_time", DefinitionType::EXT_DOUBLE}, - {"error", DefinitionType::EXT_DOUBLE}, - {"f_flux", DefinitionType::EXT_DOUBLE}, - {"b_flux", DefinitionType::EXT_DOUBLE}, - {"fabs", DefinitionType::EXT_DOUBLE}, - {"sqrt", DefinitionType::EXT_DOUBLE}, - {"sin", DefinitionType::EXT_DOUBLE}, - {"cos", DefinitionType::EXT_DOUBLE}, - {"tan", DefinitionType::EXT_DOUBLE}, - {"acos", DefinitionType::EXT_DOUBLE}, - {"asin", DefinitionType::EXT_DOUBLE}, - {"atan", DefinitionType::EXT_DOUBLE}, - {"atan2", DefinitionType::EXT_DOUBLE}, - {"sinh", DefinitionType::EXT_DOUBLE}, - {"cosh", DefinitionType::EXT_DOUBLE}, - {"tanh", DefinitionType::EXT_DOUBLE}, - {"floor", DefinitionType::EXT_DOUBLE}, - {"ceil", DefinitionType::EXT_DOUBLE}, - {"fmod", DefinitionType::EXT_DOUBLE}, - {"log10", DefinitionType::EXT_DOUBLE}, - {"log", DefinitionType::EXT_DOUBLE}, - {"pow", DefinitionType::EXT_DOUBLE}, - {"printf", DefinitionType::EXT_DOUBLE}, - {"prterr", DefinitionType::EXT_DOUBLE}, - {"exp", DefinitionType::EXT_DOUBLE}, - {"threshold", DefinitionType::EXT_DOUBLE}, - {"force", DefinitionType::EXT_DOUBLE}, - {"deflate", DefinitionType::EXT_DOUBLE}, - {"expfit", DefinitionType::EXT_DOUBLE}, - {"derivs", DefinitionType::EXT_DOUBLE}, - {"spline", DefinitionType::EXT_DOUBLE}, - {"hyperbol", DefinitionType::EXT_DOUBLE}, - {"revhyperbol", DefinitionType::EXT_DOUBLE}, - {"sigmoid", DefinitionType::EXT_DOUBLE}, - {"revsigmoid", DefinitionType::EXT_DOUBLE}, - {"harmonic", DefinitionType::EXT_DOUBLE}, - {"squarewave", DefinitionType::EXT_DOUBLE}, - {"sawtooth", DefinitionType::EXT_DOUBLE}, - {"revsawtooth", DefinitionType::EXT_DOUBLE}, - {"ramp", DefinitionType::EXT_DOUBLE}, - {"pulse", DefinitionType::EXT_DOUBLE}, - {"perpulse", DefinitionType::EXT_DOUBLE}, - {"step", DefinitionType::EXT_DOUBLE}, - {"perstep", DefinitionType::EXT_DOUBLE}, - {"erf", DefinitionType::EXT_DOUBLE}, - {"exprand", DefinitionType::EXT_DOUBLE}, - {"factorial", DefinitionType::EXT_DOUBLE}, - {"gauss", DefinitionType::EXT_DOUBLE}, - {"normrand", DefinitionType::EXT_DOUBLE}, - {"poisrand", DefinitionType::EXT_DOUBLE}, - {"poisson", DefinitionType::EXT_DOUBLE}, - {"setseed", DefinitionType::EXT_DOUBLE}, - {"scop_random", DefinitionType::EXT_DOUBLE}, - {"boundary", DefinitionType::EXT_DOUBLE}, - {"romberg", DefinitionType::EXT_DOUBLE}, - {"legendre", DefinitionType::EXT_DOUBLE}, - {"invert", DefinitionType::EXT_DOUBLE}, - {"stepforce", DefinitionType::EXT_DOUBLE}, - {"schedule", DefinitionType::EXT_DOUBLE}, - {"set_seed", DefinitionType::EXT_DOUBLE}, - {"nrn_pointing", DefinitionType::EXT_DOUBLE}, - {"state_discontinuity", DefinitionType::EXT_DOUBLE}, - {"net_send", DefinitionType::EXT_DOUBLE}, - {"net_move", DefinitionType::EXT_DOUBLE}, - {"net_event", DefinitionType::EXT_DOUBLE}, - {"nrn_random_play", DefinitionType::EXT_DOUBLE}, - {"at_time", DefinitionType::EXT_DOUBLE}, - {"nrn_ghk", DefinitionType::EXT_DOUBLE}, - {"romberg", DefinitionType::EXT_2}, - {"legendre", DefinitionType::EXT_2}, - {"deflate", DefinitionType::EXT_2}, - {"threshold", DefinitionType::EXT_3}, - {"squarewave", DefinitionType::EXT_3}, - {"sawtooth", DefinitionType::EXT_3}, - {"revsawtooth", DefinitionType::EXT_3}, - {"ramp", DefinitionType::EXT_3}, - {"pulse", DefinitionType::EXT_3}, - {"perpulse", DefinitionType::EXT_3}, - {"step", DefinitionType::EXT_3}, - {"perstep", DefinitionType::EXT_3}, - {"stepforce", DefinitionType::EXT_3}, - {"schedule", DefinitionType::EXT_3}, - {"at_time", DefinitionType::EXT_4}, - {"force", DefinitionType::EXT_5}, - {"deflate", DefinitionType::EXT_5}, - {"expfit", DefinitionType::EXT_5}, - {"derivs", DefinitionType::EXT_5}, - {"spline", DefinitionType::EXT_5}, - {"exprand", DefinitionType::EXT_5}, - {"gauss", DefinitionType::EXT_5}, - {"normrand", DefinitionType::EXT_5}, - {"poisrand", DefinitionType::EXT_5}, - {"poisson", DefinitionType::EXT_5}, - {"setseed", DefinitionType::EXT_5}, - {"scop_random", DefinitionType::EXT_5}, - {"boundary", DefinitionType::EXT_5}, - {"romberg", DefinitionType::EXT_5}, - {"invert", DefinitionType::EXT_5}, - {"stepforce", DefinitionType::EXT_5}, - {"schedule", DefinitionType::EXT_5}, - {"set_seed", DefinitionType::EXT_5}, - {"nrn_random_play", DefinitionType::EXT_5}}; -// clang-format on +static std::map<std::string, DefinitionType> extern_definitions = { + {"first_time", DefinitionType::EXT_DOUBLE}, + {"error", DefinitionType::EXT_DOUBLE}, + {"f_flux", DefinitionType::EXT_DOUBLE}, + {"b_flux", DefinitionType::EXT_DOUBLE}, + {"fabs", DefinitionType::EXT_DOUBLE}, + {"sqrt", DefinitionType::EXT_DOUBLE}, + {"sin", DefinitionType::EXT_DOUBLE}, + {"cos", DefinitionType::EXT_DOUBLE}, + {"tan", DefinitionType::EXT_DOUBLE}, + {"acos", DefinitionType::EXT_DOUBLE}, + {"asin", DefinitionType::EXT_DOUBLE}, + {"atan", DefinitionType::EXT_DOUBLE}, + {"atan2", DefinitionType::EXT_DOUBLE}, + {"sinh", DefinitionType::EXT_DOUBLE}, + {"cosh", DefinitionType::EXT_DOUBLE}, + {"tanh", DefinitionType::EXT_DOUBLE}, + {"floor", DefinitionType::EXT_DOUBLE}, + {"ceil", DefinitionType::EXT_DOUBLE}, + {"fmod", DefinitionType::EXT_DOUBLE}, + {"log10", DefinitionType::EXT_DOUBLE}, + {"log", DefinitionType::EXT_DOUBLE}, + {"pow", DefinitionType::EXT_DOUBLE}, + {"printf", DefinitionType::EXT_DOUBLE}, + {"prterr", DefinitionType::EXT_DOUBLE}, + {"exp", DefinitionType::EXT_DOUBLE}, + {"threshold", DefinitionType::EXT_DOUBLE}, + {"force", DefinitionType::EXT_DOUBLE}, + {"deflate", DefinitionType::EXT_DOUBLE}, + {"expfit", DefinitionType::EXT_DOUBLE}, + {"derivs", DefinitionType::EXT_DOUBLE}, + {"spline", DefinitionType::EXT_DOUBLE}, + {"hyperbol", DefinitionType::EXT_DOUBLE}, + {"revhyperbol", DefinitionType::EXT_DOUBLE}, + {"sigmoid", DefinitionType::EXT_DOUBLE}, + {"revsigmoid", DefinitionType::EXT_DOUBLE}, + {"harmonic", DefinitionType::EXT_DOUBLE}, + {"squarewave", DefinitionType::EXT_DOUBLE}, + {"sawtooth", DefinitionType::EXT_DOUBLE}, + {"revsawtooth", DefinitionType::EXT_DOUBLE}, + {"ramp", DefinitionType::EXT_DOUBLE}, + {"pulse", DefinitionType::EXT_DOUBLE}, + {"perpulse", DefinitionType::EXT_DOUBLE}, + {"step", DefinitionType::EXT_DOUBLE}, + {"perstep", DefinitionType::EXT_DOUBLE}, + {"erf", DefinitionType::EXT_DOUBLE}, + {"exprand", DefinitionType::EXT_DOUBLE}, + {"factorial", DefinitionType::EXT_DOUBLE}, + {"gauss", DefinitionType::EXT_DOUBLE}, + {"normrand", DefinitionType::EXT_DOUBLE}, + {"poisrand", DefinitionType::EXT_DOUBLE}, + {"poisson", DefinitionType::EXT_DOUBLE}, + {"setseed", DefinitionType::EXT_DOUBLE}, + {"scop_random", DefinitionType::EXT_DOUBLE}, + {"boundary", DefinitionType::EXT_DOUBLE}, + {"romberg", DefinitionType::EXT_DOUBLE}, + {"legendre", DefinitionType::EXT_DOUBLE}, + {"invert", DefinitionType::EXT_DOUBLE}, + {"stepforce", DefinitionType::EXT_DOUBLE}, + {"schedule", DefinitionType::EXT_DOUBLE}, + {"set_seed", DefinitionType::EXT_DOUBLE}, + {"nrn_pointing", DefinitionType::EXT_DOUBLE}, + {"state_discontinuity", DefinitionType::EXT_DOUBLE}, + {"net_send", DefinitionType::EXT_DOUBLE}, + {"net_move", DefinitionType::EXT_DOUBLE}, + {"net_event", DefinitionType::EXT_DOUBLE}, + {"nrn_random_play", DefinitionType::EXT_DOUBLE}, + {"at_time", DefinitionType::EXT_DOUBLE}, + {"nrn_ghk", DefinitionType::EXT_DOUBLE}, + {"romberg", DefinitionType::EXT_2}, + {"legendre", DefinitionType::EXT_2}, + {"deflate", DefinitionType::EXT_2}, + {"threshold", DefinitionType::EXT_3}, + {"squarewave", DefinitionType::EXT_3}, + {"sawtooth", DefinitionType::EXT_3}, + {"revsawtooth", DefinitionType::EXT_3}, + {"ramp", DefinitionType::EXT_3}, + {"pulse", DefinitionType::EXT_3}, + {"perpulse", DefinitionType::EXT_3}, + {"step", DefinitionType::EXT_3}, + {"perstep", DefinitionType::EXT_3}, + {"stepforce", DefinitionType::EXT_3}, + {"schedule", DefinitionType::EXT_3}, + {"at_time", DefinitionType::EXT_4}, + {"force", DefinitionType::EXT_5}, + {"deflate", DefinitionType::EXT_5}, + {"expfit", DefinitionType::EXT_5}, + {"derivs", DefinitionType::EXT_5}, + {"spline", DefinitionType::EXT_5}, + {"exprand", DefinitionType::EXT_5}, + {"gauss", DefinitionType::EXT_5}, + {"normrand", DefinitionType::EXT_5}, + {"poisrand", DefinitionType::EXT_5}, + {"poisson", DefinitionType::EXT_5}, + {"setseed", DefinitionType::EXT_5}, + {"scop_random", DefinitionType::EXT_5}, + {"boundary", DefinitionType::EXT_5}, + {"romberg", DefinitionType::EXT_5}, + {"invert", DefinitionType::EXT_5}, + {"stepforce", DefinitionType::EXT_5}, + {"schedule", DefinitionType::EXT_5}, + {"set_seed", DefinitionType::EXT_5}, + {"nrn_random_play", DefinitionType::EXT_5}}; /** @@ -318,7 +310,7 @@ enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; * The passes like scope checker needs to know if certain variable is * undefined and hence these needs to be inserted into symbol table */ -static std::vector<std::string> neuron_vars = {"t", "dt", "celsius", "v", "diam", "area"}; +static std::vector<std::string> NEURON_VARIABLES = {"t", "dt", "celsius", "v", "diam", "area"}; /// Return token type for the keyword @@ -371,7 +363,7 @@ TokenType token_type(const std::string& name) { */ std::vector<std::string> get_external_variables() { std::vector<std::string> result; - result.insert(result.end(), details::neuron_vars.begin(), details::neuron_vars.end()); + result.insert(result.end(), details::NEURON_VARIABLES.begin(), details::NEURON_VARIABLES.end()); return result; } diff --git a/src/nmodl/lexer/unit_lexer.hpp b/src/nmodl/lexer/unit_lexer.hpp index 47f5930e6c..5a846ff936 100644 --- a/src/nmodl/lexer/unit_lexer.hpp +++ b/src/nmodl/lexer/unit_lexer.hpp @@ -31,6 +31,12 @@ namespace nmodl { namespace parser { +/** + * @addtogroup lexer + * @addtogroup units + * @{ + */ + /** * \class UnitLexer * \brief Represent Lexer/Scanner class for Units parsing diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 5d02874d2d..d08094ec7b 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -12,14 +12,14 @@ #include <cstdlib> #include <iostream> #include <stdlib.h> - #include "parser/verbatim_context.hpp" + #include "parser/verbatim_driver.hpp" #include "parser/verbatim_parser.hpp" /** * The scanner state will include a field called yyextra * that can be used for user-defined state */ - #define YY_EXTRA_TYPE nmodl::parser::VerbatimContext* + #define YY_EXTRA_TYPE nmodl::parser::VerbatimDriver* /** @@ -144,14 +144,14 @@ /** initialize nmodlext lexer context */ -void nmodl::parser::VerbatimContext::init_scanner() { +void nmodl::parser::VerbatimDriver::init_scanner() { yylex_init(&scanner); yyset_extra(this, scanner); } /** delete nmodlext lexer context */ -void nmodl::parser::VerbatimContext::destroy_scanner() { +void nmodl::parser::VerbatimDriver::destroy_scanner() { yylex_destroy(scanner); } diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index baa0a66b12..e477ddee8c 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -24,7 +24,6 @@ #include "utils/common_utils.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" @@ -32,6 +31,7 @@ #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/solve_block_visitor.hpp" @@ -45,11 +45,12 @@ using namespace fmt::literals; using namespace nmodl; using namespace codegen; +using namespace visitor; using nmodl::parser::NmodlDriver; int main(int argc, const char* argv[]) { CLI::App app{ - "NMODL : Source-to-Source Code Generation Framework [{}]"_format(version::to_string())}; + "NMODL : Source-to-Source Code Generation Framework [{}]"_format(Version::to_string())}; /// list of mod files to process std::vector<std::string> mod_files; @@ -189,8 +190,8 @@ int main(int argc, const char* argv[]) { c_backend = false; } - make_path(output_dir); - make_path(scratch_dir); + utils::make_path(output_dir); + utils::make_path(scratch_dir); if (sympy_opt) { pybind11::initialize_interpreter(); @@ -211,7 +212,7 @@ int main(int argc, const char* argv[]) { for (const auto& file: mod_files) { logger->info("Processing {}", file); - auto modfile = remove_extension(base_name(file)); + auto modfile = utils::remove_extension(utils::base_name(file)); /// create file path for nmodl file auto filepath = [scratch_dir, modfile](std::string suffix) { @@ -221,15 +222,14 @@ int main(int argc, const char* argv[]) { /// driver object creates lexer and parser, just call parser method NmodlDriver driver; - driver.parse_file(file); + + /// parse mod file and construct ast + auto ast = driver.parse_file(file); /// whether to update existing symbol table or create new /// one whenever we run symtab visitor. bool update_symtab = false; - /// parse mod file and construct ast - auto ast = driver.ast(); - /// just visit the ast AstVisitor().visit_program(ast.get()); @@ -335,7 +335,7 @@ int main(int argc, const char* argv[]) { { logger->info("Running cnexp visitor"); - CnexpSolveVisitor().visit_program(ast.get()); + NeuronSolveVisitor().visit_program(ast.get()); ast_to_nmodl(ast.get(), filepath("cnexp")); } diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index 6520f86402..801257c0db 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -692,8 +692,8 @@ declaration_list /** Bison expects error handler for parser */ -void CParser::error(const location &loc , const std::string &message) { +void CParser::error(const location &loc , const std::string &msg) { std::stringstream ss; - ss << "C Parser Error : " << message << " [Location : " << loc << "]"; + ss << "C Parser Error : " << msg << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); } diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 85a3265afb..07eb485d4e 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -24,6 +24,11 @@ class CParser; class location; +/** + * @addtogroup parser + * @{ + */ + /** * \class CDriver * \brief Class that binds all pieces together for parsing C verbatim blocks @@ -100,5 +105,7 @@ class CDriver { } }; +/** @} */ // end of parser + } // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 4a42799d2f..0074fd24b6 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -228,8 +228,8 @@ arg : e { %% -void DiffeqParser::error(const location &loc , const std::string &message) { +void DiffeqParser::error(const location &loc , const std::string &msg) { std::stringstream ss; - ss << "DiffEq Parser Error : " << message << " [Location : " << loc << "]"; + ss << "DiffEq Parser Error : " << msg << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); } diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index b7e9b5fb2c..dc7c89dfc6 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -15,6 +15,17 @@ namespace nmodl { namespace parser { +/// flex generated scanner class (extends base lexer class of flex) +class DiffeqlLexer; + +/// parser class generated by bison +class DiffeqParser; + +/** + * @addtogroup parser + * @{ + */ + /** * \class DiffeqDriver * \brief Class that binds all pieces together for parsing differential equations @@ -22,13 +33,6 @@ namespace parser { * Driver class bind components required for lexing, parsing and ast * generation for differential equations. */ - -/// flex generated scanner class (extends base lexer class of flex) -class DiffeqlLexer; - -/// parser class generated by bison -class DiffeqParser; - class DiffeqDriver { private: std::string solve_equation(std::string& state, @@ -54,5 +58,7 @@ class DiffeqDriver { bool cnexp_possible(const std::string& equation, std::string& solution); }; +/** @} */ // end of parser + } // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index 8c6744689e..56ab3ca9e8 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -9,6 +9,9 @@ #include "parser/diffeq_context.hpp" +namespace nmodl { +namespace parser { + /** * \brief Helper functions for solving differential equations * @@ -20,9 +23,6 @@ * Need to revisit this, may be using better library like symengine * altogether. */ - -namespace nmodl { -namespace parser { namespace diffeq { /// operators beign supported as part of binary expressions diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index aae6df9923..95eb41326d 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -24,7 +24,7 @@ using namespace fmt::literals; using namespace nmodl; int main(int argc, const char* argv[]) { - CLI::App app{"C-Parser : Standalone Parser for C Code({})"_format(version::to_string())}; + CLI::App app{"C-Parser : Standalone Parser for C Code({})"_format(Version::to_string())}; std::vector<std::string> files; app.add_option("file", files, "One or more C files to process") diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index a67ebc3b7c..d3b3a8298b 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -22,7 +22,7 @@ using namespace fmt::literals; using namespace nmodl; int main(int argc, const char* argv[]) { - CLI::App app{"NMODL-Parser : Standalone Parser for NMODL({})"_format(version::to_string())}; + CLI::App app{"NMODL-Parser : Standalone Parser for NMODL({})"_format(Version::to_string())}; std::vector<std::string> mod_files; std::vector<std::string> mod_texts; diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index d36a4a3a21..d6c181622d 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -40,7 +40,7 @@ void parse_units(std::vector<std::string> files) { } int main(int argc, const char* argv[]) { - CLI::App app{"Unit-Parser : Standalone Parser for Units({})"_format(version::to_string())}; + CLI::App app{"Unit-Parser : Standalone Parser for Units({})"_format(Version::to_string())}; std::vector<std::string> files; files.push_back(NrnUnitsLib::get_path()); diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 03d8fdc344..6d2c2bcf07 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -368,12 +368,12 @@ %{ #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" - #include "parser/verbatim_context.hpp" + #include "parser/verbatim_driver.hpp" using nmodl::parser::NmodlParser; using nmodl::parser::NmodlLexer; using nmodl::parser::NmodlDriver; - using nmodl::parser::VerbatimContext; + using nmodl::parser::VerbatimDriver; /// yylex takes scanner as well as driver reference /// \todo: check if driver argument is required @@ -414,7 +414,7 @@ top : all { - driver.astRoot.reset($1); + driver.set_ast($1); } | error { @@ -2250,7 +2250,7 @@ valence : { $$ = nullptr; } std::string parse_with_verbatim_parser(std::string str) { auto is = new std::istringstream(str.c_str()); - VerbatimContext extcontext(is); + VerbatimDriver extcontext(is); Verbatim_parse(&extcontext); std::string ss(*(extcontext.result)); @@ -2264,8 +2264,8 @@ std::string parse_with_verbatim_parser(std::string str) { * and report all errors. For now simply abort. */ -void NmodlParser::error(const location &loc , const std::string &message) { +void NmodlParser::error(const location &loc , const std::string &msg) { std::stringstream ss; - ss << "NMODL Parser Error : " << message << " [Location : " << loc << "]"; + ss << "NMODL Parser Error : " << msg << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); } diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index f6d0585704..cf1c457348 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -10,6 +10,7 @@ #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" +#include "utils/logger.hpp" namespace nmodl { namespace parser { @@ -19,7 +20,7 @@ NmodlDriver::NmodlDriver(bool strace, bool ptrace) , trace_parser(ptrace) {} /// parse nmodl file provided as istream -bool NmodlDriver::parse_stream(std::istream& in) { +std::shared_ptr<ast::Program> NmodlDriver::parse_stream(std::istream& in) { NmodlLexer scanner(*this, &in); NmodlParser parser(scanner, *this); @@ -28,24 +29,28 @@ bool NmodlDriver::parse_stream(std::istream& in) { scanner.set_debug(trace_scanner); parser.set_debug_level(trace_parser); - return (parser.parse() == 0); + parser.parse(); + return astRoot; } //// parse nmodl file -bool NmodlDriver::parse_file(const std::string& filename) { +std::shared_ptr<ast::Program> NmodlDriver::parse_file(const std::string& filename) { std::ifstream in(filename.c_str()); stream_name = filename; if (!in.good()) { - return false; + logger->error("Can not open file : {}", filename); + return nullptr; } - return parse_stream(in); + parse_stream(in); + return astRoot; } /// parser nmodl provided as string (used for testing) -bool NmodlDriver::parse_string(const std::string& input) { +std::shared_ptr<ast::Program> NmodlDriver::parse_string(const std::string& input) { std::istringstream iss(input); - return parse_stream(iss); + parse_stream(iss); + return astRoot; } void NmodlDriver::error(const std::string& m, const class location& l) { diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index bce44a2ddf..ebcc287753 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -12,11 +12,28 @@ #include "ast/ast.hpp" -/** The nmodl namespace encapsulates everything related to nmodl parsing - * which includes lexer, parser, driver, keywords, token mapping etc. */ + +/// encapsulates everything related to NMODL code generation framework namespace nmodl { +/// encapsulate lexer and parsers implementations namespace parser { +/** + * @defgroup parser Parser Infrastructure + * @brief All parser and driver classes implementation + * + * @addtogroup parser + * @{ + */ + + +/// flex generated scanner class (extends base lexer class of flex) +class NmodlLexer; + +/// parser class generated by bison +class NmodlParser; + + /** * \class NmodlDriver * \brief Class that binds all pieces together for parsing nmodl file @@ -40,13 +57,6 @@ namespace parser { * location object used in scanner takes string pointer which could * be invalid when we copy location object. */ - -/// flex generated scanner class (extends base lexer class of flex) -class NmodlLexer; - -/// parser class generated by bison -class NmodlParser; - class NmodlDriver { private: /// all macro defined in the mod file @@ -67,13 +77,13 @@ class NmodlDriver { /// print messages from lexer/parser bool verbose = false; + /// root of the ast + std::shared_ptr<ast::Program> astRoot = nullptr; + public: /// file or input stream name (used by scanner for position), see todo std::string stream_name; - /// root of the ast - std::shared_ptr<ast::Program> astRoot = nullptr; - NmodlDriver() = default; NmodlDriver(bool strace, bool ptrace); @@ -84,10 +94,9 @@ class NmodlDriver { bool is_defined_var(const std::string& name); int get_defined_var_value(const std::string& name); - bool parse_stream(std::istream& in); - - bool parse_string(const std::string& input); - bool parse_file(const std::string& filename); + std::shared_ptr<ast::Program> parse_stream(std::istream& in); + std::shared_ptr<ast::Program> parse_string(const std::string& input); + std::shared_ptr<ast::Program> parse_file(const std::string& filename); void set_verbose(bool b) { verbose = b; @@ -97,10 +106,18 @@ class NmodlDriver { return verbose; } - std::shared_ptr<ast::Program> ast() const { + /// return previously parsed AST otherwise nullptr + std::shared_ptr<ast::Program> get_ast() const { return astRoot; } + + /// set new ast root + void set_ast(ast::Program* node) { + astRoot.reset(node); + } }; +/** @} */ // end of parser + } // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index b549a98153..a7066957f7 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -210,8 +210,8 @@ item /** Bison expects error handler for parser */ -void UnitParser::error(const location &loc , const std::string &message) { +void UnitParser::error(const location &loc , const std::string &msg) { std::stringstream ss; - ss << "Unit Parser Error : " << message << " [Location : " << loc << "]"; + ss << "Unit Parser Error : " << msg << " [Location : " << loc << "]"; throw std::runtime_error(ss.str()); } diff --git a/src/nmodl/parser/unit_driver.hpp b/src/nmodl/parser/unit_driver.hpp index 1a451f1454..4c6703b21d 100644 --- a/src/nmodl/parser/unit_driver.hpp +++ b/src/nmodl/parser/unit_driver.hpp @@ -26,6 +26,12 @@ class UnitParser; /// represent location of the token class location; +/** + * @addtogroup parser + * @addtogroup units + * @{ + */ + /** * \class UnitDriver * \brief Class that binds all pieces together for parsing C units @@ -54,9 +60,14 @@ class UnitDriver { /// file or input stream name (used by scanner for position), see todo std::string stream_name; + /// \name Ctor & dtor + /// \{ + UnitDriver() = default; UnitDriver(bool strace, bool ptrace); + /// \} + void error(const std::string& m); bool parse_stream(std::istream& in); bool parse_string(const std::string& input); diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index 36634eb287..4d20ea1d9d 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -/* +/** * Bison specification for NMODL Extensions which includes * VERBATIM and COMMENT blocks */ @@ -15,31 +15,32 @@ #include <cstdlib> #include <iostream> #include <cstring> - #include "parser/verbatim_context.hpp" + + #include "parser/verbatim_driver.hpp" %} -/* print out verbose error instead of just message 'syntax error' */ +/** print out verbose error instead of just message 'syntax error' */ %error-verbose -/* make a reentrant parser */ +/** make a reentrant parser */ %pure-parser -/* parser prefix */ +/** parser prefix */ %name-prefix "Verbatim_" -/* enable location tracking */ +/** enable location tracking */ %locations -/* generate header file */ +/** generate header file */ %defines -/* yyparse() takes an extra argument context */ -%parse-param {nmodl::parser::VerbatimContext* context} +/** yyparse() takes an extra argument context */ +%parse-param {nmodl::parser::VerbatimDriver* context} -/* reentrant lexer needs an extra argument for yylex() */ +/** reentrant lexer needs an extra argument for yylex() */ %lex-param {void * scanner} -/* token types */ +/** token types */ %union { char str; @@ -62,14 +63,14 @@ %{ - using nmodl::parser::VerbatimContext; + using nmodl::parser::VerbatimDriver; /* a macro that extracts the scanner state from the parser state for yylex */ #define scanner context->scanner extern int yylex(YYSTYPE*, YYLTYPE*, void*); - extern int yyparse(VerbatimContext*); - extern void yyerror(YYLTYPE*, VerbatimContext*, const char *); + extern int yyparse(VerbatimDriver*); + extern void yyerror(YYLTYPE*, VerbatimDriver*, const char *); %} @@ -81,7 +82,7 @@ top : verbatimblock { $$ = $1; context->result = $1; } | commentblock { $$ = $1; context->result = $1; } - | error { printf("\n _ERROR_"); } + | error { printf("\n _ERROR_"); } ; verbatimblock : VERBATIM charlist ENDVERBATIM { $$ = $2; } @@ -97,7 +98,7 @@ charlist : { $$ = new std::string(""); } %% /** \todo : better error handling required */ -void yyerror(YYLTYPE* /*locp*/, VerbatimContext* /*context*/, const char *s) { +void yyerror(YYLTYPE* /*locp*/, VerbatimDriver* /*context*/, const char *s) { std::printf("\n Error in verbatim parser : %s \n", s); std::exit(1); } diff --git a/src/nmodl/parser/verbatim_context.hpp b/src/nmodl/parser/verbatim_driver.hpp similarity index 70% rename from src/nmodl/parser/verbatim_context.hpp rename to src/nmodl/parser/verbatim_driver.hpp index e577cf2d5b..03e328b033 100644 --- a/src/nmodl/parser/verbatim_context.hpp +++ b/src/nmodl/parser/verbatim_driver.hpp @@ -13,31 +13,43 @@ namespace nmodl { namespace parser { -class VerbatimContext { +/** + * @addtogroup parser + * @{ + */ + +/** + * \class VerbatimDriver + * \brief Class that binds lexer and parser together for parsing VERBATIM block + */ +class VerbatimDriver { + + protected: + void init_scanner(); + void destroy_scanner(); + public: void* scanner = nullptr; std::istream* is = nullptr; std::string* result = nullptr; - VerbatimContext(std::istream* is = &std::cin) { + VerbatimDriver(std::istream* is = &std::cin) { init_scanner(); this->is = is; } - virtual ~VerbatimContext() { + virtual ~VerbatimDriver() { destroy_scanner(); if (result) { delete result; } } - - protected: - void init_scanner(); - void destroy_scanner(); }; +/** @} */ // end of parser + } // namespace parser } // namespace nmodl -int Verbatim_parse(nmodl::parser::VerbatimContext*); \ No newline at end of file +int Verbatim_parse(nmodl::parser::VerbatimDriver*); \ No newline at end of file diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 90188d1de2..a754ccff30 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -9,6 +9,7 @@ #include "utils/string_utils.hpp" namespace nmodl { +namespace printer { CodePrinter::CodePrinter(const std::string& filename) { if (filename.empty()) { @@ -73,4 +74,5 @@ void CodePrinter::end_block(int num_newlines) { add_newline(num_newlines); } -} // namespace nmodl \ No newline at end of file +} // namespace printer +} // namespace nmodl diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index d597a55751..33b1efd597 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -7,21 +7,36 @@ #pragma once +/** + * \dir + * \brief Code printer implementations + * + * \file + * \brief \copybrief nmodl::printer::CodePrinter + */ + #include <fstream> #include <iostream> #include <memory> #include <sstream> namespace nmodl { +/// implementation of various printers +namespace printer { + +/** + * @defgroup printer Code Printers + * @brief Printers for translating AST to different forms + * @{ + */ /** * \class CodePrinter * \brief Helper class for printing C/C++ code * - * This class provides common functionality required by code generation - * visitor to print C/C++/Cuda code. + * This class provides common functionality required by code + * generation visitor to print C/C++/Cuda code. */ - class CodePrinter { private: std::ofstream ofs; @@ -33,8 +48,10 @@ class CodePrinter { public: CodePrinter() : result(new std::ostream(std::cout.rdbuf())) {} + CodePrinter(std::stringstream& stream) : result(new std::ostream(stream.rdbuf())) {} + CodePrinter(const std::string& filename); ~CodePrinter() { @@ -44,8 +61,7 @@ class CodePrinter { /// print whitespaces for indentation void add_indent(); - /// start of new block scope (i.e. start with "{") - /// and increases indentation level + /// start a block scope (i.e. start with "{") void start_block(); void start_block(std::string&&); @@ -67,7 +83,6 @@ class CodePrinter { } /// end of current block scope (i.e. end with "}") - /// and decreases indentation level void end_block(int num_newlines = 0); int indent_spaces() { @@ -75,4 +90,7 @@ class CodePrinter { } }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of printer + +} // namespace printer +} // namespace nmodl diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index ab26cef021..c338907074 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -9,6 +9,7 @@ namespace nmodl { +namespace printer { /// Dump output to provided file JSONPrinter::JSONPrinter(const std::string& filename) { @@ -80,4 +81,5 @@ void JSONPrinter::flush() { } } +} // namespace printer } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 465ec989df..713f1062d4 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -7,35 +7,43 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::printer::JSONPrinter + */ + #include <fstream> #include <iostream> #include <stack> #include "json/json.hpp" -using json = nlohmann::json; namespace nmodl { +namespace printer { + +using json = nlohmann::json; + +/** + * @addtogroup printer + * @{ + */ /** * \class JSONPrinter - * \brief Helper class for printing AST in JSON format + * \brief Helper class for printing AST in JSON form * - * We need to print AST in human readable format for - * debugging or visualization of in memory structure. - * This printer class provides simple interface to - * construct JSON object from AST like data structures. - * We use nlohmann's json library which considerably - * simplify implementation. + * We need to print AST in human readable format for debugging or visualization + * of in memory structure. This printer class provides simple interface to + * construct JSON object from AST like data structures. We use nlohmann's json + * library which considerably simplify implementation. * - * \todo : We need to explicitly call flush() in order - * to get write/return results. We simply can't dump - * block in popBlock() because block itself will be - * part of other parent elements. Also we are writing - * results to file, stringstream and cout. And hence - * we can't simply reset/clear previously written text. + * \todo We need to explicitly call flush() in order to get write/return + * results. We simply can't dump block in popBlock() because block itself will + * be part of other parent elements. Also we are writing results to file, + * stringstream and cout. And hence we can't simply reset/clear previously + * written text. */ - class JSONPrinter { private: std::ofstream ofs; @@ -90,4 +98,7 @@ class JSONPrinter { } }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of printer + +} // namespace printer +} // namespace nmodl diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index 894ca9c306..ad656462a9 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -9,6 +9,7 @@ #include "utils/string_utils.hpp" namespace nmodl { +namespace printer { NMODLPrinter::NMODLPrinter(const std::string& filename) { if (filename.empty()) { @@ -50,4 +51,5 @@ void NMODLPrinter::pop_level() { *result << "}"; } +} // namespace printer } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp index a48ea3f7d3..1cbfc5c7d1 100644 --- a/src/nmodl/printer/nmodl_printer.hpp +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -7,23 +7,33 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::printer::NMODLPrinter + */ + #include <fstream> #include <iostream> #include <memory> #include <sstream> namespace nmodl { +namespace printer { + +/** + * @addtogroup printer + * @{ + */ /** * \class NMODLPrinter * \brief Helper class for printing AST back to NMDOL test * - * NmodlPrintVisitor transforms AST back to NMODL. This class - * provided common functionality required by visitor to print - * nmodl ascii file. + * NmodlPrintVisitor transforms AST back to NMODL. This class provided common + * functionality required by visitor to print nmodl ascii file. * - * \todo : Implement Printer as base class to avoid duplication - * code between JSONPrinter and NMODLPrinter. + * \todo Implement Printer as base class to avoid duplication code between + * JSONPrinter and NMODLPrinter. */ class NMODLPrinter { private: @@ -58,4 +68,7 @@ class NMODLPrinter { void pop_level(); }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of printer + +} // namespace printer +} // namespace nmodl diff --git a/src/nmodl/pybind/pybind_utils.hpp b/src/nmodl/pybind/pybind_utils.hpp index bdac60644c..855ebe9cc2 100644 --- a/src/nmodl/pybind/pybind_utils.hpp +++ b/src/nmodl/pybind/pybind_utils.hpp @@ -27,6 +27,7 @@ struct CopyFromPython { } }; + template <> struct CopyFromPython<str> { void operator()(char* start, size_t n, str data) { diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 13ed109e9f..fd06f319eb 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -17,10 +17,23 @@ #include "pybind/pybind_utils.hpp" #include "visitors/visitor_utils.hpp" + +/** + * \dir + * \brief Python Interface Implementation + * + * \file + * \brief Top level nmodl Python module implementation + */ + + namespace py = pybind11; using pybind11::literals::operator""_a; -/** \brief docstring of Python symbols */ + +namespace nmodl { + +/** \brief docstring of Python exposed API */ namespace docstring { static const char* driver = R"( @@ -35,50 +48,53 @@ static const char* driver_ast = R"( )"; static const char* driver_parse_string = R"( - Parse C provided as a string (testing) + Parse NMODL provided as a string Args: input (str): C code as string Returns: - bool: true if success, false otherwise + AST: ast root node if success, throws an exception otherwise - >>> driver.parse_string("DEFINE NSTEP 6") - True + >>> ast = driver.parse_string("DEFINE NSTEP 6") )"; static const char* driver_parse_file = R"( - Parse C file + Parse NMODL provided as a file Args: filename (str): name of the C file Returns: - bool: true if success, false otherwise + AST: ast root node if success, throws an exception otherwise )"; static const char* driver_parse_stream = R"( - Parse C file provided as istream + Parse NMODL file provided as istream Args: in (file): ifstream object Returns: - bool: true if success, false otherwise + AST: ast root node if success, throws an exception otherwise )"; static const char* to_nmodl = R"( - Given AST node, return the JSON string representation + Given AST node, return the NMODL string representation Args: node (AST): AST node excludeTypes (set of AstNodeType): Excluded node types Returns: - str: JSON string representation + str: NMODL string representation + + >>> ast = driver.parse_string("NEURON{}") + >>> nmodl.to_nmodl(ast) + 'NEURON {\n}\n' )"; static const char* to_json = R"( - Given AST node, return the NMODL string representation + Given AST node, return the JSON string representation Args: node (AST): AST node @@ -86,20 +102,23 @@ static const char* to_json = R"( expand (bool): Expand node Returns: - str: NMODL string representation + str: JSON string representation - >>> driver.parse_string("NEURON{}") - True - >>> ast = driver.ast() + >>> ast = driver.parse_string("NEURON{}") >>> nmodl.to_json(ast, True) '{"Program":[{"NeuronBlock":[{"StatementBlock":[]}]}]}' )"; } // namespace docstring -class PyDriver: public nmodl::parser::NmodlDriver { + +/** + * \class PyNmodlDriver + * \brief Class to bridge C++ NmodlDriver with Python world using pybind11 + */ +class PyNmodlDriver: public nmodl::parser::NmodlDriver { public: - bool parse_stream(py::object object) { + std::shared_ptr<nmodl::ast::Program> parse_stream(py::object object) { py::object tiob = py::module::import("io").attr("TextIOBase"); if (py::isinstance(object, tiob)) { py::detail::pythonibuf<py::str> buf(object); @@ -113,26 +132,45 @@ class PyDriver: public nmodl::parser::NmodlDriver { } }; +} // namespace nmodl + // forward declaration of submodule init functions void init_visitor_module(py::module& m); void init_ast_module(py::module& m); void init_symtab_module(py::module& m); + PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; - m_nmodl.attr("__version__") = nmodl::version::NMODL_VERSION; + m_nmodl.attr("__version__") = nmodl::Version::NMODL_VERSION; - py::class_<PyDriver> nmodl_driver(m_nmodl, "NmodlDriver", docstring::driver); + py::class_<nmodl::PyNmodlDriver> nmodl_driver(m_nmodl, "NmodlDriver", nmodl::docstring::driver); nmodl_driver.def(py::init<>()) - .def("parse_string", &PyDriver::parse_string, "input"_a, docstring::driver_parse_string) - .def("parse_file", &PyDriver::parse_file, "filename"_a, docstring::driver_parse_file) - .def("parse_stream", &PyDriver::parse_stream, "in"_a, docstring::driver_parse_stream) - .def("ast", &PyDriver::ast, docstring::driver_ast); - - m_nmodl.def("to_nmodl", nmodl::to_nmodl, "node"_a, - "exclude_types"_a = std::set<nmodl::ast::AstNodeType>(), docstring::to_nmodl); - m_nmodl.def("to_json", nmodl::to_json, "node"_a, "compact"_a = false, "expand"_a = false, - docstring::to_json); + .def("parse_string", + &nmodl::PyNmodlDriver::parse_string, + "input"_a, + nmodl::docstring::driver_parse_string) + .def("parse_file", + &nmodl::PyNmodlDriver::parse_file, + "filename"_a, + nmodl::docstring::driver_parse_file) + .def("parse_stream", + &nmodl::PyNmodlDriver::parse_stream, + "in"_a, + nmodl::docstring::driver_parse_stream) + .def("get_ast", &nmodl::PyNmodlDriver::get_ast, nmodl::docstring::driver_ast); + + m_nmodl.def("to_nmodl", + nmodl::to_nmodl, + "node"_a, + "exclude_types"_a = std::set<nmodl::ast::AstNodeType>(), + nmodl::docstring::to_nmodl); + m_nmodl.def("to_json", + nmodl::to_json, + "node"_a, + "compact"_a = false, + "expand"_a = false, + nmodl::docstring::to_json); init_visitor_module(m_nmodl); init_ast_module(m_nmodl); diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 7309f1e2d8..1a3bf2ae52 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -7,29 +7,48 @@ #pragma once -// Implementation of Newton method for solving system of non-linear equations using Eigen -// - newton_solver is the preferred option: requires user to provide Jacobian -// - newton_numerical_diff_solver is the fallback option: Jacobian not required +/** + * \dir + * \brief Solver implementations + * + * \file + * \brief Implementation of Newton method for solving system of non-linear equations + */ #include <Eigen/LU> namespace nmodl { +/// newton solver implementations namespace newton { +/** + * @defgroup solver Solver Infrastructure + * @brief Solver implementation details + * + * Implementation of Newton method for solving system of non-linear equations using Eigen + * - newton::newton_solver is the preferred option: requires user to provide Jacobian + * - newton::newton_numerical_diff_solver is the fallback option: Jacobian not required + * + * @{ + */ + constexpr int MAX_ITER = 1e3; constexpr double EPS = 1e-12; -// Newton method with user-provided Jacobian: -// given initial vector X -// and a functor that calculates F(X), J(X) -// where J(X) is the Jacobian of F(X), -// solves for F(X) = 0, -// starting with initial value of X -// by iterating: -// X_{n+1} = X_n - J(X_n)^{-1} F(X_n) -// -// when |F|^2 < eps^2, solution has converged -// returns number of iterations (-1 if failed to converge) +/** + * \brief Newton method with user-provided Jacobian + * + * Newton method with user-provided Jacobian: given initial vector X and a + * functor that calculates `F(X)`, `J(X)` where `J(X)` is the Jacobian of `F(X)`, + * solves for \f$F(X) = 0\f$, starting with initial value of `X` by iterating: + * + * \f[ + * X_{n+1} = X_n - J(X_n)^{-1} F(X_n) + * \f] + * when \f$|F|^2 < eps^2\f$, solution has converged. + * + * @return number of iterations (-1 if failed to converge) + */ template <int N, typename FUNC> EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X, FUNC functor, @@ -50,8 +69,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X, // we have converged: return iteration count return iter; } - // update X - // use in-place LU decomposition of J with partial pivoting + // update X use in-place LU decomposition of J with partial pivoting // (suitable for any N, but less efficient than .inverse() for N <=4) X -= Eigen::PartialPivLU<Eigen::Ref<Eigen::Matrix<double, N, N>>>(J).solve(F); } @@ -59,21 +77,26 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, N, 1>& X, return -1; } -// Newton method without user-provided Jacobian: -// given initial vector X -// and a functor that calculates F(X), -// solves for F(X) = 0, -// starting with initial value of X -// by iterating: -// X_{n+1} = X_n - J(X_n)^{-1} F(X_n) -// where J(X) is the Jacobian of F(X), which is -// approximated numerically using a symmetric -// finite difference approximation to the derivative - -// when |F|^2 < eps^2, solution has converged -// returns number of iterations (-1 if failed to converge) constexpr double SQUARE_ROOT_ULP = 1e-7; constexpr double CUBIC_ROOT_ULP = 1e-5; + +/** + * \brief Newton method without user-provided Jacobian + * + * Newton method without user-provided Jacobian: given initial vector X and a + * functor that calculates `F(X)`, solves for \f$F(X) = 0\f$, starting with + * initial value of `X` by iterating: + * + * \f[ + * X_{n+1} = X_n - J(X_n)^{-1} F(X_n) + * \f] + * + * where `J(X)` is the Jacobian of `F(X)`, which is approximated numerically + * using a symmetric finite difference approximation to the derivative + * when \f$|F|^2 < eps^2\f$, solution has converged/ + * + * @return number of iterations (-1 if failed to converge) + */ template <int N, typename FUNC> EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix<double, N, 1>& X, FUNC functor, @@ -128,10 +151,12 @@ EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix<double, N, 1>& return -1; } -// Newton method template specializations for N <= 4 -// Use explicit inverse of F instead of LU decomposition -// This is faster, as there is no pivoting and therefore no branches, -// but it is not numerically safe for N > 4 +/** + * Newton method template specializations for \f$N <= 4\f$ Use explicit inverse + * of `F` instead of LU decomposition. This is faster, as there is no pivoting + * and therefore no branches, but it is not numerically safe for \f$N > 4\f$. + */ + template <typename FUNC, int N> EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix<double, N, 1>& X, FUNC functor, @@ -150,6 +175,7 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix<double, N, 1>& X, } return -1; } + template <typename FUNC> EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 1, 1>& X, FUNC functor, @@ -182,5 +208,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix<double, 4, 1>& X, return newton_solver_small_N<FUNC, 4>(X, functor, eps, max_iter); } +/** @} */ // end of solver + } // namespace newton } // namespace nmodl diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index a1d5a6331f..d5944f75f0 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -17,61 +17,10 @@ namespace symtab { using syminfo::NmodlType; using syminfo::Status; -/** if symbol has only extern_token property : this is true - * for symbols which are external variables from neuron - * like v, t, dt etc. - * \todo: check if we should check two properties using has_property - * instead of exact comparisons - */ -bool Symbol::is_external_symbol_only() { - return (properties == NmodlType::extern_neuron_variable || - properties == NmodlType::extern_method); -} - -/// check if symbol has any of the common properties - -bool Symbol::has_properties(NmodlType new_properties) { - return static_cast<bool>(properties & new_properties); -} - -/// check if symbol has all of the given properties -bool Symbol::has_all_properties(NmodlType new_properties) { - return ((properties & new_properties) == new_properties); -} - - -/// check if symbol has any of the status -bool Symbol::has_any_status(Status new_status) { - return static_cast<bool>(status & new_status); -} - -/// check if symbol has all of the status -bool Symbol::has_all_status(Status new_status) { - return ((status & new_status) == new_status); -} - -/// add new properties to symbol with bitwise or -void Symbol::add_properties(NmodlType new_properties) { - properties |= new_properties; -} - -void Symbol::add_property(NmodlType property) { - properties |= property; -} - -/** Prime variable will appear in different block and could have - * multiple derivative orders. We have to store highest order. - * - * \todo Check if analysis passes need more information. - */ -void Symbol::set_order(int new_order) { - if (new_order > order) { - order = new_order; - } -} -/// check if symbol is of variable type in nmodl bool Symbol::is_variable() { + // if symbol has one of the following property then it + // is considered as variable in the NMODL // clang-format off NmodlType var_properties = NmodlType::local_var | NmodlType::global_var @@ -89,7 +38,7 @@ bool Symbol::is_variable() { | NmodlType::argument | NmodlType::extern_neuron_variable; // clang-format on - return has_properties(var_properties); + return has_any_property(var_properties); } std::string Symbol::to_string() { diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 887310889b..92cb06b8bd 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief Implement class to represent a symbol in Symbol Table + */ + #include <map> #include <memory> @@ -17,83 +22,97 @@ namespace nmodl { namespace ast { -class AST; +class Ast; } +/// %Symbol table related implementations namespace symtab { +/** + * @ingroup sym_tab + * @{ + */ + /** * \class Symbol * \brief Represent symbol in symbol table * * Symbol table generator pass visit the AST and insert symbol for - * each node into symbol table. Symbol could appear multiple times - * in a block or different global blocks. NmodlType object has all + * each node into symtab::SymbolTable. Symbol could appear multiple + * times in a block or different global blocks. NmodlType object has all * nmodl properties information. * - * \todo Multiple tokens (i.e. location information) for symbol should - * be tracked - * \todo Scope information should be more than just string - * \todo Perf block should track information about all usage of the symbol - * (would be helpful for perf modeling) + * \todo + * - Multiple tokens (i.e. location information) for symbol should + * be tracked + * - Scope information should be more than just string + * - Perf block should track information about all usage of the symbol + * (would be helpful for perf modeling) + * - Need to keep track of all renaming information, currently only we + * keep last state */ - class Symbol { - private: /// name of the symbol std::string name; - /// original name in case of renaming + /// original name of the symbol if renamed std::string renamed_from; /// unique id or index position when symbol is inserted into specific table int id = 0; - /// ast node where symbol encountered first time - /// node is passed from visitor and hence we have - /// raw pointer instead of shared_ptr - ast::AST* node = nullptr; + /// first AST node for which symbol is inserted + /// Variable can appear multiple times in the mod file. This node + /// represent the first occurance of the variable in the input. Currently + /// we don't track all AST nodes. + ast::Ast* node = nullptr; - /// token for position information + /// token associated with symbol (from node) ModToken token; - /// properties of symbol from whole mod file + /// properties of symbol as a result of usage across whole mod file syminfo::NmodlType properties{syminfo::NmodlType::empty}; - /// status of symbol during various passes + /// status of symbol after processing through various passes syminfo::Status status{syminfo::Status::empty}; - /// scope of the symbol (block name) + /// scope of the symbol (nmodl block name where it appears) std::string scope; - /// number of times symbol is read - int read_count = 0; - - /// number of times symbol is written - int write_count = 0; - - /// order of derivative (for prime node) + /// order in case of state / prime variable int order = 0; - /// order in which symbol arrives + /// order in which symbol appears in the mod file + /// Different variables appear in different blocks (NEURON, PARAMETER, STATE) + /// and accordingly they appear in the data array (in NEURON). This order is + /// based on appearance in the mod file. int definition_order = -1; - /// value (for parameters, constant etc) + /// associated value in case of parameters, constant variable std::shared_ptr<double> value; - /// true if array variable + /// true if symbol represent array variable bool array = false; - /// number of elements + /// dimension/length in case of array variable int length = 1; - // number of values in case of table variable + /// number of values that variable can take in case of table variable int num_values = 0; + /// number of times symbol is read + int read_count = 0; + + /// number of times symbol is written + int write_count = 0; + public: + /// \name Ctor & dtor + /// \{ + Symbol() = delete; - Symbol(std::string name, ast::AST* node) + Symbol(std::string name, ast::Ast* node) : name(name) , node(node) {} @@ -101,27 +120,41 @@ class Symbol { : name(name) , token(token) {} - Symbol(std::string name, ast::AST* node, ModToken token) + Symbol(std::string name, ast::Ast* node, ModToken token) : name(name) , node(node) , token(token) {} - void set_scope(std::string s) { - scope = s; + /// \} + + /// increment read count + void read() { + read_count++; } - std::string get_name() { - return name; + /// increment write count + void write() { + write_count++; } - int get_id() { - return id; + /// \name Setter + /// \{ + + void set_scope(std::string s) { + scope = s; } void set_id(int i) { id = i; } + /** + * Set new name for the symbol + * + * If symbol is already renamed, do not rename it again + * as we want to keep track of original name and not intermediate + * renames + */ void set_name(std::string new_name) { if (renamed_from.empty()) { renamed_from = name; @@ -129,6 +162,68 @@ class Symbol { name = new_name; } + /** + * Set order in case of prime/state variable + * + * Prime variable will appear in different block and could have + * multiple derivative orders. We have to store highest order. + */ + void set_order(int new_order) { + if (new_order > order) { + order = new_order; + } + } + + void set_definition_order(int order) { + definition_order = order; + } + + void set_value(double val) { + value = std::make_shared<double>(val); + } + + void set_as_array(int len) { + array = true; + length = len; + } + + void set_num_values(int n) { + num_values = n; + } + + void set_original_name(std::string new_name) { + renamed_from = new_name; + } + + /// \} + + /// \name Getter + /// \{ + + int get_length() { + return length; + } + + int get_num_values() { + return num_values; + } + + std::string get_original_name() { + return renamed_from; + } + + std::shared_ptr<double> get_value() { + return value; + } + + std::string get_name() { + return name; + } + + int get_id() { + return id; + } + std::string get_scope() { return scope; } @@ -141,15 +236,7 @@ class Symbol { return status; } - void read() { - read_count++; - } - - void write() { - write_count++; - } - - ast::AST* get_node() { + ast::Ast* get_node() { return node; } @@ -169,25 +256,60 @@ class Symbol { return definition_order; } - bool is_external_symbol_only(); + /// \} - /// \todo : rename to has_any_property - bool has_properties(syminfo::NmodlType new_properties); + /** + * Check if symbol represent an external variable + * + * External variables are the variables that are defined in NEURON + * and available in mod file. + * + * \todo: Need to check if we should check two properties using + * has_any_property instead of exact comparison + * + * \sa nmodl::details::NEURON_VARIABLES + */ + bool is_external_variable() { + return (properties == syminfo::NmodlType::extern_neuron_variable || + properties == syminfo::NmodlType::extern_method); + } - bool has_all_properties(syminfo::NmodlType new_properties); + /// check if symbol has any of the given property + bool has_any_property(syminfo::NmodlType new_properties) { + return static_cast<bool>(properties & new_properties); + } - bool has_any_status(syminfo::Status new_status); + /// check if symbol has all of the given properties + bool has_all_properties(syminfo::NmodlType new_properties) { + return ((properties & new_properties) == new_properties); + } - bool has_all_status(syminfo::Status new_status); + /// check if symbol has any of the status + bool has_any_status(syminfo::Status new_status) { + return static_cast<bool>(status & new_status); + } + + /// check if symbol has all of the status + bool has_all_status(syminfo::Status new_status) { + return ((status & new_status) == new_status); + } - void add_properties(syminfo::NmodlType new_properties); + /// add new properties to symbol + void add_properties(syminfo::NmodlType new_properties) { + properties |= new_properties; + } - void add_property(syminfo::NmodlType property); + /// add new property to symbol + void add_property(syminfo::NmodlType property) { + properties |= property; + } - void inlined() { + /// mark symbol as inlined (in case of procedure/function) + void mark_inlined() { status |= syminfo::Status::inlined; } + /// mark symbol as newly created (in case of new variable) void mark_created() { status |= syminfo::Status::created; } @@ -196,6 +318,7 @@ class Symbol { status |= syminfo::Status::renamed; } + /// mark symbol as localized (e.g. from RANGE to LOCAL conversion) void mark_localized() { status |= syminfo::Status::localized; } @@ -204,62 +327,25 @@ class Symbol { status |= syminfo::Status::thread_safe; } + /// mark symbol as newly created variable for the STATE variable + /// this is used with legacy euler/derivimplicit solver where DState + /// variables are created void created_from_state() { mark_created(); status |= syminfo::Status::from_state; } - void set_order(int new_order); - - void set_definition_order(int order) { - definition_order = order; - } - - void set_value(double val) { - value = std::make_shared<double>(val); - } - - void set_as_array(int len) { - array = true; - length = len; - } - bool is_array() { return array; } - void set_length(int len) { - length = len; - } - - int get_length() { - return length; - } - - int get_num_values() { - return num_values; - } - - void set_num_values(int n) { - num_values = n; - } - - std::string get_original_name() { - return renamed_from; - } - - std::shared_ptr<double> get_value() { - return value; - } - - void set_original_name(std::string new_name) { - renamed_from = new_name; - } + /// check if symbol is a variable in nmodl + bool is_variable(); std::string to_string(); - - bool is_variable(); }; +/** @} */ // end of sym_tab + } // namespace symtab } // namespace nmodl diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 061d901144..f90c6c19b9 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -16,16 +16,7 @@ namespace nmodl { namespace symtab { namespace syminfo { -/// check if any property is set -bool has_property(const NmodlType& obj, NmodlType property) { - return static_cast<bool>(obj & property); -} - -bool has_status(const Status& obj, Status state) { - return static_cast<bool>(obj & state); -} -/// helper function to convert properties to string std::vector<std::string> to_string_vector(const NmodlType& obj) { std::vector<std::string> properties; @@ -215,7 +206,6 @@ std::ostream& operator<<(std::ostream& os, const NmodlType& obj) { return os; } - std::ostream& operator<<(std::ostream& os, const Status& obj) { os << to_string(obj); return os; diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 639c3489cb..3eec57ec82 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -7,223 +7,222 @@ #pragma once +/** + * \file + * \brief Implement various classes to represent various Symbol properties + */ + #include <sstream> #include "utils/string_utils.hpp" -//@todo : error from pybind if std::underlying_typ is used -using enum_type = long long; namespace nmodl { namespace symtab { + +/// %Symbol information namespace syminfo { -/** kind of symbol */ +/// \todo Error with pybind if std::underlying_typ is used +using enum_type = long long; + +/// kind of symbol enum class DeclarationType : enum_type { - /** variable */ + /// variable variable, - /** function */ + /// function function }; -/** scope within a mod file */ +/// scope within a mod file enum class Scope : enum_type { - /** local variable */ + /// local variable local, - /** global variable */ + /// global variable global, - /** neuron variable */ + /// neuron variable neuron, - /** extern variable */ + /// extern variable external }; -/** state during various compiler passes */ +/// state during various compiler passes enum class Status : enum_type { - empty = 0, - /** converted to local */ + /// converted to local localized = 1L << 0, - /** converted to global */ + /// converted to global globalized = 1L << 1, - /** inlined */ + /// inlined inlined = 1L << 2, - /** renamed */ + /// renamed renamed = 1L << 3, - /** created */ + /// created created = 1L << 4, - /** derived from state */ + /// derived from state from_state = 1L << 5, - /** variable marked as thread safe */ + /// variable marked as thread safe thread_safe = 1L << 6 }; -/** usage of mod file as array or scalar */ +/// usage of mod file as array or scalar enum class VariableType : enum_type { - /** scalar / single value */ + /// scalar / single value scalar, - /** vector type */ + /// vector type array }; -/** variable usage within a mod file */ +/// variable usage within a mod file enum class Access : enum_type { - /** variable is ready only */ + /// variable is ready only read = 1L << 0, - /** variable is written only */ + /// variable is written only write = 1L << 1 }; -/** @brief nmodl variable properties +/** + * \brief NMODL variable properties * - * Certain variables/symbols specified in various places in the - * same mod file. For example, RANGE variable could be in PARAMETER - * bloc, ASSIGNED block etc. In this case, the symbol in global - * scope need to have multiple properties. This is also important - * while code generation. Hence, in addition to AST node pointer, - * we define NmodlType so that we can set multiple properties. - * Note that AST pointer is no longer pointing to all pointers. - * Same applies for ModToken. + * Certain variables/symbols specified in various places in the same mod file. + * For example, RANGE variable could be in PARAMETER bloc, ASSIGNED block etc. + * In this case, the symbol in global scope need to have multiple properties. + * This is also important while code generation. Hence, in addition to AST node + * pointer, we define NmodlType so that we can set multiple properties. Note + * that AST pointer is no longer pointing to all pointers. Same applies for + * ModToken. * * These is some redundancy between NmodlType and other enums like - * DeclarationType and Scope. But as AST will become more abstract - * from NMODL (towards C/C++) then other types will be useful. + * syminfo::DeclarationType and syminfo::Scope. But as AST will become more + * abstract from NMODL (towards C/C++) then other types will be useful. * - * \todo Rename param_assign to parameter_var - * \todo Reaching max limit (31), need to refactor all block types - * into separate type + * \todo + * - Rename param_assign to parameter_var */ enum class NmodlType : enum_type { empty = 0, - /** Local Variable */ + /// Local Variable local_var = 1L << 0, - /** Global Variable */ + /// Global Variable global_var = 1L << 1, - /** Range Variable */ + /// Range Variable range_var = 1L << 2, - /** Parameter Variable */ + /// Parameter Variable param_assign = 1L << 3, - /** Pointer Type */ + /// Pointer Type pointer_var = 1L << 4, - /** Bbcorepointer Type */ + /// Bbcorepointer Type bbcore_pointer_var = 1L << 5, - /** Extern Type */ + /// Extern Type extern_var = 1L << 6, - /** Prime Type */ + /// Prime Type prime_name = 1L << 7, - /** Dependent Def */ + /// Dependent Def dependent_def = 1L << 8, - /** Unit Def */ + /// Unit Def unit_def = 1L << 9, - /** Read Ion */ + /// Read Ion read_ion_var = 1L << 10, - /** Write Ion */ + /// Write Ion write_ion_var = 1L << 11, - /** Non Specific Current */ + /// Non Specific Current nonspecific_cur_var = 1L << 12, - /** Electrode Current */ + /// Electrode Current electrode_cur_var = 1L << 13, - /** Section Type */ + /// Section Type section_var = 1L << 14, - /** Argument Type */ + /// Argument Type argument = 1L << 15, - /** Function Type */ + /// Function Type function_block = 1L << 16, - /** Procedure Type */ + /// Procedure Type procedure_block = 1L << 17, - /** Derivative Block */ + /// Derivative Block derivative_block = 1L << 18, - /** Linear Block */ + /// Linear Block linear_block = 1L << 19, - /** NonLinear Block */ + /// NonLinear Block non_linear_block = 1L << 20, - /** constant variable */ + /// constant variable constant_var = 1L << 21, - /** Partial Block */ + /// Partial Block partial_block = 1L << 22, - /** Kinetic Block */ + /// Kinetic Block kinetic_block = 1L << 23, - /** FunctionTable Block */ + /// FunctionTable Block function_table_block = 1L << 24, - /** factor in unit block */ + /// factor in unit block factor_def = 1L << 25, - /** neuron variable accessible in mod file */ + /// neuron variable accessible in mod file extern_neuron_variable = 1L << 26, - /** neuron solver methods and math functions */ + /// neuron solver methods and math functions extern_method = 1L << 27, - /** state variable */ + /// state variable state_var = 1L << 28, - /** need to solve : used in solve statement */ + /// need to solve : used in solve statement to_solve = 1L << 29, - /** ion type */ + /// ion type useion = 1L << 30, - /** variable is used in table statement */ + /// variable is used in table statement table_statement_var = 1L << 31, - /** variable is used in table as dependent */ + /// variable is used in table as dependent table_dependent_var = 1L << 32, - /** Discrete Block */ + /// Discrete Block discrete_block = 1L << 33, - /** Define variable / macro */ + /// Define variable / macro define = 1L << 34 }; - -/// check if any property is set -bool has_property(const NmodlType& obj, NmodlType property); - -/// check if any status is set -bool has_status(const Status& obj, Status state); - template <typename T> inline T operator|(T lhs, T rhs) { using utype = enum_type; @@ -243,10 +242,23 @@ inline T& operator|=(T& lhs, T rhs) { } +/// check if any property is set +inline bool has_property(const NmodlType& obj, NmodlType property) { + return static_cast<bool>(obj & property); +} + +/// check if any status is set +inline bool has_status(const Status& obj, Status state) { + return static_cast<bool>(obj & state); +} + std::ostream& operator<<(std::ostream& os, const syminfo::NmodlType& obj); std::ostream& operator<<(std::ostream& os, const syminfo::Status& obj); +/// helper function to convert nmodl properties to string std::vector<std::string> to_string_vector(const syminfo::NmodlType& obj); + +/// helper function to convert symbol status to string std::vector<std::string> to_string_vector(const syminfo::Status& obj); template <typename T> @@ -263,4 +275,4 @@ std::string to_string(const T& obj) { } // namespace syminfo } // namespace symtab -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index d99ad8ef4c..4d246decc3 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -12,7 +12,6 @@ #include "utils/logger.hpp" #include "utils/table_data.hpp" - namespace nmodl { namespace symtab { @@ -20,6 +19,7 @@ using namespace ast; using syminfo::NmodlType; using syminfo::Status; + int SymbolTable::Table::counter = 0; /** @@ -54,10 +54,6 @@ SymbolTable::SymbolTable(const SymbolTable& table) { } -std::string SymbolTable::type() const { - return node->get_node_type_name(); -} - bool SymbolTable::is_method_defined(const std::string& name) const { auto symbol = lookup_in_scope(name); if (symbol != nullptr) { @@ -71,6 +67,7 @@ bool SymbolTable::is_method_defined(const std::string& name) const { return false; } + std::string SymbolTable::position() const { auto token = node->get_token(); std::string position; @@ -83,7 +80,6 @@ std::string SymbolTable::position() const { } -/// insert new symbol table of one of the children block void SymbolTable::insert_table(const std::string& name, std::shared_ptr<SymbolTable> table) { if (children.find(name) != children.end()) { throw std::runtime_error("Trying to re-insert SymbolTable " + name); @@ -103,7 +99,7 @@ std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables_with_properties( variables.push_back(symbol); } } else { - if (symbol->has_properties(properties)) { + if (symbol->has_any_property(properties)) { variables.push_back(symbol); } } @@ -116,7 +112,7 @@ std::vector<std::shared_ptr<Symbol>> SymbolTable::get_variables(NmodlType with, auto variables = get_variables_with_properties(with, true); decltype(variables) result; for (auto& variable: variables) { - if (!variable->has_properties(without)) { + if (!variable->has_any_property(without)) { result.push_back(variable); } } @@ -279,8 +275,8 @@ void ModelSymbolTable::update_order(const std::shared_ptr<Symbol>& present_symbo const std::shared_ptr<Symbol>& new_symbol) { auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; - bool is_parameter = new_symbol->has_properties(NmodlType::param_assign); - bool is_dependent_def = new_symbol->has_properties(NmodlType::dependent_def); + bool is_parameter = new_symbol->has_any_property(NmodlType::param_assign); + bool is_dependent_def = new_symbol->has_any_property(NmodlType::dependent_def); if (symbol->get_definition_order() == -1) { if (is_parameter || is_dependent_def) { @@ -319,7 +315,7 @@ std::shared_ptr<Symbol> ModelSymbolTable::insert(const std::shared_ptr<Symbol>& * that means symbol are duplicate. */ if (current_symtab->global_scope()) { - if (search_symbol->has_properties(symbol->get_properties())) { + if (search_symbol->has_any_property(symbol->get_properties())) { emit_message(symbol, search_symbol, true); } else { search_symbol->add_properties(symbol->get_properties()); @@ -349,7 +345,7 @@ std::shared_ptr<Symbol> ModelSymbolTable::insert(const std::shared_ptr<Symbol>& * them we simply append counter. * \todo We should add position information to make name unique */ -std::string ModelSymbolTable::get_unique_name(const std::string& name, AST* node, bool is_global) { +std::string ModelSymbolTable::get_unique_name(const std::string& name, Ast* node, bool is_global) { static int block_counter = 0; std::string new_name(name); if (is_global) { @@ -369,17 +365,13 @@ std::string ModelSymbolTable::get_unique_name(const std::string& name, AST* node * symbol table within a node. */ SymbolTable* ModelSymbolTable::enter_scope(const std::string& name, - AST* node, + Ast* node, bool global, SymbolTable* node_symtab) { if (node == nullptr) { throw std::runtime_error("Can't enter with empty node"); } - if (node->is_breakpoint_block()) { - breakpoint_exist = true; - } - /** * All global blocks in mod file have same global symbol table. If there * is already symbol table setup in global scope, return the same. @@ -451,16 +443,21 @@ void ModelSymbolTable::set_mode(bool update_mode) { void SymbolTable::Table::print(std::stringstream& stream, std::string title, int indent) { + using stringutils::text_alignment; + using utils::TableData; if (!symbols.empty()) { TableData table; table.title = std::move(title); - table.headers = {"NAME", "PROPERTIES", "STATUS", "LOCATION", - "VALUE", "# READS", "# WRITES"}; - table.alignments = {text_alignment::left, text_alignment::left, text_alignment::right, - text_alignment::right, text_alignment::right}; + table.headers = { + "NAME", "PROPERTIES", "STATUS", "LOCATION", "VALUE", "# READS", "# WRITES"}; + table.alignments = {text_alignment::left, + text_alignment::left, + text_alignment::right, + text_alignment::right, + text_alignment::right}; for (const auto& symbol: symbols) { - auto is_external = symbol->is_external_symbol_only(); + auto is_external = symbol->is_external_variable(); auto read_count = symbol->get_read_count(); auto write_count = symbol->get_write_count(); @@ -491,8 +488,9 @@ void SymbolTable::Table::print(std::stringstream& stream, std::string title, int /// construct title for symbol table -std::string SymbolTable::title() { - auto name = symtab_name + " [" + type() + " IN " + get_parent_table_name() + "] "; +std::string SymbolTable::title() const { + auto node_type = node->get_node_type_name(); + auto name = symtab_name + " [" + node_type + " IN " + get_parent_table_name() + "] "; auto location = "POSITION : " + position(); auto scope = global ? "GLOBAL" : "LOCAL"; return name + location + " SCOPE : " + scope; @@ -519,10 +517,5 @@ void SymbolTable::print(std::stringstream& ss, int level) { } } - -void ModelSymbolTable::print(std::stringstream& ss) { - symtab->print(ss, 0); -} - } // namespace symtab } // namespace nmodl diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 00c18c6eb4..7774dd6767 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -7,40 +7,54 @@ #pragma once +/** + * \dir + * \brief Symbol table implementation + * + * \file + * \brief Implement classes for representing symbol table at block and file scope + */ + #include <map> #include <memory> #include <vector> #include "symtab/symbol.hpp" - namespace nmodl { namespace symtab { + +/** + * @defgroup sym_tab Symbol Table Infrastructure + * @brief All %Symbol Table implementation details + * + * @{ + */ + /** - * \class SymbolTable - * \brief Represent symbol table for nmodl block + * \brief Represent symbol table for a NMODL block * * Symbol Table is used to track information about every block construct - * encountered in the nmodl. In NMODL, block constructs are NEURON, - * PARAMETER NET_RECEIVE etc. Each block is considered a new scope. + * encountered in the nmodl. In NMODL, block constructs are NEURON, PARAMETER + * NET_RECEIVE etc. Each block is considered a new scope. * - * NMODL supports nested block definitions (i.e. nested blocks). One - * specific example of this is INITIAL block in NET_RECEIVE block. In this - * case we need multiple scopes for single top level block of NMODL. In - * the future if we enable block level scopes, we will need symbol tables - * per block. Hence we are implementing BlockSymbolTable which stores - * all symbol table information for specific NMODL block. Note that each - * BlockSymbolTable implementation is recursive because while symbol - * lookup we have to search first into local/current block. If lookup is - * unsuccessfull then we have to traverse parent blocks until the end. + * NMODL supports nested block definitions (i.e. nested blocks). One specific + * example of this is INITIAL block in NET_RECEIVE block. In this case we need + * multiple scopes for single top level block of NMODL. In the future if we + * enable block level scopes, we will need symbol tables per block. Hence we are + * implementing BlockSymbolTable which stores all symbol table information for + * specific NMODL block. Note that each BlockSymbolTable implementation is + * recursive because while symbol lookup we have to search first into + * local/current block. If lookup is unsuccessful then we have to traverse + * parent blocks until the end. * - * \todo Revisit when clone method is used and implementation of copy - * constructor - * \todo Name may not require as we have added AST node + * \todo + * - Revisit when clone method is used and implementation of copy + * constructor + * - Name may not require as we have added AST node */ class SymbolTable { - private: /** * \class Table * \brief Helper class for implementing symbol table @@ -52,6 +66,7 @@ class SymbolTable { * \todo Re-implement pretty printing */ class Table { + /// number of tables static int counter; public: @@ -67,17 +82,18 @@ class SymbolTable { /// pretty print void print(std::stringstream& stream, std::string title, int indent); }; + /// name of the block std::string symtab_name; /// table holding all symbols in the current block Table table; - /// pointer to ast node for which current block symbol created - ast::AST* node = nullptr; + /// pointer to ast node for which current symbol table created + ast::Ast* node = nullptr; - /// true if current symbol table is global. blocks like neuron, - /// parameter defines global variables and hence they go into + /// true if current symbol table is global. blocks like NEURON, + /// PARAMETER defines global variables and hence they go into /// single global symbol table bool global = false; @@ -91,40 +107,55 @@ class SymbolTable { std::map<std::string, std::shared_ptr<SymbolTable>> children; public: - SymbolTable(std::string name, ast::AST* node, bool global = false) + /// \name Ctor & dtor + /// \{ + + SymbolTable(std::string name, ast::Ast* node, bool global = false) : symtab_name(name) , node(node) , global(global) {} SymbolTable(const SymbolTable& table); - std::string name() const { - return symtab_name; + /// \} + + + /// \name Getter + /// \{ + + SymbolTable* get_parent_table() const { + return parent; } - std::string type() const; + std::string get_parent_table_name() const { + return parent ? parent->name() : "None"; + } - std::string title(); + std::vector<std::shared_ptr<Symbol>> get_variables(syminfo::NmodlType with, + syminfo::NmodlType without); - bool is_method_defined(const std::string& name) const; + std::vector<std::shared_ptr<Symbol>> get_variables_with_properties( + syminfo::NmodlType properties, + bool all = false); - /// todo: set token for every block from parser - std::string position() const; + std::vector<std::shared_ptr<Symbol>> get_variables_with_status(syminfo::Status status, + bool all = false); - bool global_scope() const { - return global; - } + /// \} - SymbolTable* get_parent_table() const { - return parent; + /// convert symbol table to string + std::string to_string() { + std::stringstream s; + print(s, 0); + return s.str(); } - std::string get_parent_table_name() { - return parent ? parent->name() : "None"; + std::string name() const { + return symtab_name; } - std::shared_ptr<Symbol> lookup(const std::string& name) { - return table.lookup(name); + bool global_scope() const { + return global; } void insert(std::shared_ptr<Symbol> symbol) { @@ -139,55 +170,56 @@ class SymbolTable { return table.symbols.size(); } - /// \todo: revisit the usage as tokens will be pointing to old nodes + /** + * Create a copy of symbol table + * \todo revisit the usage as tokens will be pointing to old nodes + */ SymbolTable* clone() { return new SymbolTable(*this); } - std::shared_ptr<Symbol> lookup_in_scope(const std::string& name) const; - - std::vector<std::shared_ptr<Symbol>> get_variables(syminfo::NmodlType with, - syminfo::NmodlType without); - - std::vector<std::shared_ptr<Symbol>> get_variables_with_properties( - syminfo::NmodlType properties, - bool all = false); + /// check if symbol with given name exist in the current table (but not in parents) + std::shared_ptr<Symbol> lookup(const std::string& name) { + return table.lookup(name); + } - std::vector<std::shared_ptr<Symbol>> get_variables_with_status(syminfo::Status status, - bool all = false); + /// check if symbol with given name exist in the current table (including all parents) + std::shared_ptr<Symbol> lookup_in_scope(const std::string& name) const; + /// check if currently we are visiting global scope node bool under_global_scope(); + /// insert new symbol table as one of the children block void insert_table(const std::string& name, std::shared_ptr<SymbolTable> table); void print(std::stringstream& ss, int level); - std::string to_string() { - std::stringstream s; - print(s, 0); - return s.str(); - } + std::string title() const; + + std::string position() const; + + /// check if procedure/function with given name is defined + bool is_method_defined(const std::string& name) const; }; /** - * \class ModelSymbolTable - * \brief Represent symbol table for nmodl block + * \brief Hold top level (i.e. global) symbol table for mod file * - * SymbolTable is sufficient to hold information about all symbols in the - * mod file. It might be sufficient to keep track of global symbol tables - * and local symbol tables. But we construct symbol table using visitor - * pass. In this case we visit ast and recursively create symbol table for - * each block scope. In this case, ModelSymbolTable is provide interface - * to build symbol table with visitor. + * symtab::SymbolTable is sufficient to hold information about all symbols in + * the mod file. It might be sufficient to keep track of global symbol tables + * and local symbol tables. But we construct symbol table using visitor pass. In + * this case we visit ast and recursively create symbol table for each block + * scope. In this case, ModelSymbolTable provides high level interface to build + * symbol table as part of visitor. * - * \note For included mod file it's not clear yet whether we need to maintain - * separate ModelSymbolTable. - * \note See command project in compiler teaching course for details + * \note + * - For included mod file it's not clear yet whether we need to maintain + * separate ModelSymbolTable. + * - See command project in compiler teaching course for details * * \todo Unique name should be based on location. Use ModToken to get position. */ class ModelSymbolTable { - private: /// symbol table for mod file (always top level symbol table) std::shared_ptr<SymbolTable> symtab = nullptr; @@ -195,7 +227,7 @@ class ModelSymbolTable { SymbolTable* current_symtab = nullptr; /// return unique name by appending some counter value - std::string get_unique_name(const std::string& name, ast::AST* node, bool is_global); + std::string get_unique_name(const std::string& name, ast::Ast* node, bool is_global); /// name of top level global symbol table const std::string GLOBAL_SYMTAB_NAME = "NMODL_GLOBAL"; @@ -207,11 +239,6 @@ class ModelSymbolTable { /// current order of variable being defined int definition_order = 0; - /// if breakpoint block exist in the model - bool breakpoint_exist = false; - - std::string model_suffix = ""; - /// insert symbol table in update mode std::shared_ptr<Symbol> update_mode_insert(const std::shared_ptr<Symbol>& symbol); @@ -225,7 +252,7 @@ class ModelSymbolTable { public: /// entering into new nmodl block SymbolTable* enter_scope(const std::string& name, - ast::AST* node, + ast::Ast* node, bool global, SymbolTable* node_symtab); @@ -238,21 +265,17 @@ class ModelSymbolTable { /// lookup for symbol into current as well as all parent tables std::shared_ptr<Symbol> lookup(const std::string& name); - /// pretty print - void print(std::stringstream& ss); - /// re-initialize members to throw away old symbol tables /// this is required as symtab visitor pass runs multiple time void set_mode(bool update_mode); - bool has_breakpoint() const { - return breakpoint_exist; - } - - std::string model_name() const { - return model_suffix; + /// pretty print + void print(std::stringstream& ss) { + symtab->print(ss, 0); } }; +/** @} */ // end of sym_tab + } // namespace symtab } // namespace nmodl diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 448fa87ae6..edb389405c 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -18,6 +18,11 @@ #include "units.hpp" +/** + * \file + * \brief Units processing while being processed from lexer and parser + */ + namespace nmodl { namespace units { @@ -46,13 +51,19 @@ void Unit::add_nominator_double(std::string double_string) { } void Unit::add_nominator_dims(std::array<int, MAX_DIMS> dimensions) { - std::transform(unit_dimensions.begin(), unit_dimensions.end(), dimensions.begin(), - unit_dimensions.begin(), std::plus<int>()); + std::transform(unit_dimensions.begin(), + unit_dimensions.end(), + dimensions.begin(), + unit_dimensions.begin(), + std::plus<int>()); } void Unit::add_denominator_dims(std::array<int, MAX_DIMS> dimensions) { - std::transform(unit_dimensions.begin(), unit_dimensions.end(), dimensions.begin(), - unit_dimensions.begin(), std::minus<int>()); + std::transform(unit_dimensions.begin(), + unit_dimensions.end(), + dimensions.begin(), + unit_dimensions.begin(), + std::minus<int>()); } void Unit::add_nominator_unit(std::string nom) { @@ -107,7 +118,8 @@ double Unit::parse_double(std::string double_string) { } // if *it reached an exponent related char, then the whole double number is read for (it = double_string.begin(); - it != double_string.end() && *it != 'e' && *it != '+' && *it != '-'; ++it) { + it != double_string.end() && *it != 'e' && *it != '+' && *it != '-'; + ++it) { s_number.push_back(*it); } // then read the magnitude of the double number @@ -204,8 +216,8 @@ void UnitTable::calc_denominator_dims(std::shared_ptr<Unit> unit, std::string de while (changed_denominator_name) { changed_denominator_name = 0; for (const auto& it: prefixes) { - auto res = std::mismatch(it.first.begin(), it.first.end(), - denominator_name.begin()); + auto res = + std::mismatch(it.first.begin(), it.first.end(), denominator_name.begin()); if (res.first == it.first.end()) { changed_denominator_name = 1; denominator_prefix_factor *= it.second; diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 784b072343..3da2bf43dc 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -9,6 +9,14 @@ #pragma once +/** + * \dir + * \brief Data structures for storing units + * + * \file + * \brief Classes for storing different units specification + */ + #include <array> #include <cmath> #include <fstream> @@ -23,8 +31,15 @@ namespace nmodl { +/// encapsulates unit database and tables implementation namespace units { +/** + * @defgroup units Unit Infrastructure + * @brief Units handling implementation details + * @{ + */ + /// Maximum number of dimensions of units (maximum number of base units) static const int MAX_DIMS = 10; @@ -277,5 +292,7 @@ class UnitTable { void print_base_units(std::stringstream& base_units_details); }; +/** @} */ // end of units + } // namespace units } // namespace nmodl diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 6884baff0b..163b3e4cf2 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -13,6 +13,7 @@ namespace nmodl { +namespace utils { bool is_dir_exist(const std::string& path) { struct stat info {}; @@ -54,4 +55,5 @@ bool make_path(const std::string& path) { } } -} // namespace nmodl \ No newline at end of file +} // namespace utils +} // namespace nmodl diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index bb3486ceeb..e187a4612e 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -7,29 +7,48 @@ #pragma once +/** + * + * \dir + * \brief Utility classes and function + * + * \file + * \brief Common utility functions for file/dir manipulation + */ namespace nmodl { +/// file/string manipulation functions +namespace utils { -/** Check if the iterator is pointing to last element in the container */ +/** + * @defgroup utils Utility Infrastructure + * @brief Utility classes and function implementation + * @{ + */ + +/// Check if the iterator is pointing to last element in the container template <typename Iter, typename Cont> bool is_last(Iter iter, const Cont& cont) { return ((iter != cont.end()) && (next(iter) == cont.end())); } -/** Given full file path, returns only name of the file */ +/// Given full file path, returns only name of the file template <class T> T base_name(T const& path, T const& delims = "/\\") { return path.substr(path.find_last_of(delims) + 1); } -/** Given the file name, returns name of the file without extension */ +/// Given the file name, returns name of the file without extension template <class T> T remove_extension(T const& filename) { typename T::size_type const p(filename.find_last_of('.')); return p > 0 && p != T::npos ? filename.substr(0, p) : filename; } -/** Given directory path, create sub-directories */ +/// Given directory path, create sub-directories bool make_path(const std::string& path); +/** @} */ // end of utils + +} // namespace utils } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 8404ee6edd..4872f20da3 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -9,11 +9,18 @@ #include "utils/logger.hpp" +/** + * \file + * \brief \copybrief nmodl::Logger + */ namespace nmodl { using logger_type = std::shared_ptr<spdlog::logger>; +/** + * \brief Logger implementation based on spdlog + */ struct Logger { logger_type logger; Logger(const std::string& name, std::string pattern) { @@ -22,7 +29,6 @@ struct Logger { } }; - Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); logger_type logger = nmodl_logger.logger; diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index 244d75da6e..fa1535df7b 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -7,7 +7,13 @@ #pragma once +/** + * \file + * \brief Implement logger based on spdlog library + */ + // clang-format off +// disable clang-format to keep order of inclusion #include "spdlog/spdlog.h" #include "spdlog/sinks/stdout_color_sinks.h" // clang-format on @@ -17,4 +23,4 @@ namespace nmodl { using logger_type = std::shared_ptr<spdlog::logger>; extern logger_type logger; -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index e33d4a5677..64247a42e0 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -13,6 +13,7 @@ #include "utils/table_data.hpp" namespace nmodl { +namespace utils { PerfStat operator+(const PerfStat& first, const PerfStat& second) { PerfStat result; @@ -114,4 +115,5 @@ std::vector<std::string> PerfStat::values() { return row; } +} // namespace utils } // namespace nmodl diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index cdf9dad077..f24edd6f0b 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -7,13 +7,24 @@ #pragma once +/** + * \file + * \brief Implement class for performance statistics + */ + #include <sstream> namespace nmodl { +namespace utils { + +/** + * @addtogroup utils + * @{ + */ /** - * \class PerfStat + * \struct PerfStat * \brief Helper class to collect performance statistics * * For code generation it is useful to know the performance @@ -21,9 +32,7 @@ namespace nmodl { * groups performance characteristics of a single block in * nmodl. */ - -class PerfStat { - public: +struct PerfStat { /// name for pretty-printing std::string title; @@ -88,10 +97,15 @@ class PerfStat { int n_unique_constant_write = 0; friend PerfStat operator+(const PerfStat& first, const PerfStat& second); + void print(std::stringstream& stream); std::vector<std::string> keys(); + std::vector<std::string> values(); }; +/** @} */ // end of utils + +} // namespace utils } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 853894c76b..adb67b2a52 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -7,23 +7,30 @@ #pragma once +/** + * \file + * \brief Implement string manipulation functions + * + * String trimming and manipulation functions based on + * stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring + */ + #include <algorithm> #include <sstream> #include <vector> - namespace nmodl { +/// string utility functions +namespace stringutils { /** - * \brief String manipulation functions - * - * String trimming and manipulation functions based on - * stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring + * @addtogroup utils + * @{ */ +/// text alignment when printing in the tabular form enum class text_alignment { left, right, center }; -namespace stringutils { /// Trim from start static inline std::string& ltrim(std::string& s) { s.erase(s.begin(), @@ -117,6 +124,7 @@ static inline std::string tolower(std::string text) { return text; } -} // namespace stringutils +/** @} */ // end of utils +} // namespace stringutils } // namespace nmodl diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 815d169a92..98f6d70661 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -13,6 +13,7 @@ namespace nmodl { +namespace utils { /** * Print table data in below shown format: title as first row (centrally aligned), @@ -44,7 +45,7 @@ void TableData::print(std::stringstream& stream, int indent) { /// alignment is optional, so fill remaining withh right alignment for (unsigned i = alignments.size(); i < ncolumns; i++) { - alignments.push_back(text_alignment::center); + alignments.push_back(stringutils::text_alignment::center); } /// calculate space required for each column @@ -79,7 +80,8 @@ void TableData::print(std::stringstream& stream, int indent) { std::stringstream header; header << "| "; for (size_t i = 0; i < headers.size(); i++) { - auto text = stringutils::align_text(headers[i], col_width[i], text_alignment::center); + auto text = + stringutils::align_text(headers[i], col_width[i], stringutils::text_alignment::center); header << text << " | "; } @@ -88,7 +90,7 @@ void TableData::print(std::stringstream& stream, int indent) { /// title row if (!title.empty()) { - title = stringutils::align_text(title, row_width - 3, text_alignment::center); + title = stringutils::align_text(title, row_width - 3, stringutils::text_alignment::center); stream << "\n" << gutter << separator_line; stream << "\n" << gutter << "|" << title << "|"; } @@ -116,4 +118,5 @@ void TableData::print(int indent) { std::cout << ss.str(); } +} // namespace utils } // namespace nmodl diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index 9d3ccac087..0c29ed3de4 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief Implement generic table data structure + */ + #include <sstream> #include <vector> @@ -14,15 +19,20 @@ namespace nmodl { +namespace utils { + +/** + * @addtogroup utils + * @{ + */ /** * \class TableData * \brief Class to construct and pretty-print tabular data * - * This class is used to construct and print tables (like symbol - * table and performance tables). + * This class is used to construct and print tables (like + * nmodl::symtab::SymbolTable and performance tables). */ - struct TableData { using TableRowType = std::vector<std::string>; @@ -36,10 +46,14 @@ struct TableData { std::vector<TableRowType> rows; /// alignment for every column of data rows - std::vector<text_alignment> alignments; + std::vector<stringutils::text_alignment> alignments; void print(int indent = 0); + void print(std::stringstream& stream, int indent = 0); }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of utils + +} // namespace utils +} // namespace nmodl diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 98abcecdec..0eb972400d 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -2,8 +2,8 @@ # Visitor sources # ============================================================================= set(VISITOR_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/cnexp_solve_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/neuron_solve_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/neuron_solve_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 28254e98b2..6d2b96eed4 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -11,6 +11,7 @@ namespace nmodl { +namespace visitor { /// check if given expression is a number /// note that the DEFINE node is already expanded to integer @@ -175,4 +176,5 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* nod logger->debug("ConstantFolderVisitor : expression {} folded to {}", nmodl_before, nmodl_after); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index 1b5d20b1e0..956e58cf38 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::ConstantFolderVisitor + */ + #include <stack> #include <string> @@ -14,7 +19,14 @@ #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" + namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class ConstantFolderVisitor @@ -23,18 +35,19 @@ namespace nmodl { * MOD file from user could have binary expressions that could be * expanded at compile time. For example, KINETIC blocks could have * - * DEFINE NANN 10 + * \code{.mod} + * DEFINE NANN 10 * - * KINETIC states { - * FROM i=0 TO NANN-2 { - * .... + * KINETIC states { + * FROM i=0 TO NANN-2 { + * .... + * } * } - * } + * \endcode * - * For passes like loop unroll, we need to evaluate NANN-2 at + * For passes like loop unroll, we need to evaluate `NANN-2` at * compile time and this pass perform such operations. */ - class ConstantFolderVisitor: public AstVisitor { public: ConstantFolderVisitor() = default; @@ -42,4 +55,7 @@ class ConstantFolderVisitor: public AstVisitor { void visit_paren_expression(ast::ParenExpression* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index af0bcd63ff..9ba16c53ba 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -11,7 +11,9 @@ #include "visitors/defuse_analyze_visitor.hpp" namespace nmodl { +namespace visitor { +using printer::JSONPrinter; using symtab::syminfo::NmodlType; /// DUState to string conversion for pretty-printing @@ -193,7 +195,7 @@ void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node* node) { void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall* node) { std::string function_name = node->get_node_name(); auto symbol = global_symtab->lookup_in_scope(function_name); - if (symbol == nullptr || symbol->is_external_symbol_only()) { + if (symbol == nullptr || symbol->is_external_variable()) { node->visit_children(this); } else { visit_unsupported_node(node); @@ -285,7 +287,7 @@ void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { assert(symbol != nullptr); // variable properties that make variable local auto properties = NmodlType::local_var | NmodlType::argument; - auto is_local = symbol->has_properties(properties); + auto is_local = symbol->has_any_property(properties); if (unsupported_node) { current_chain->push_back(DUInstance(DUState::U)); @@ -329,7 +331,7 @@ void DefUseAnalyzeVisitor::start_new_chain(DUState state) { current_chain = ¤t_chain->back().children; } -DUChain DefUseAnalyzeVisitor::analyze(ast::AST* node, const std::string& name) { +DUChain DefUseAnalyzeVisitor::analyze(ast::Ast* node, const std::string& name) { /// re-initialize state variable_name = name; visiting_lhs = false; @@ -348,4 +350,5 @@ DUChain DefUseAnalyzeVisitor::analyze(ast::AST* node, const std::string& name) { return usage; } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 0d1f3f4d33..1b809e6397 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::DefUseAnalyzeVisitor + */ + #include <map> #include <stack> @@ -19,8 +24,9 @@ namespace nmodl { +namespace visitor { -/// state in def-use chain +/// Represent a state in Def-Use chain enum class DUState { /// global variable is used U, @@ -52,19 +58,22 @@ std::ostream& operator<<(std::ostream& os, DUState state); * \class DUInstance * \brief Represent use of a variable in the statement * - * For a given variable, say tau, when we have statement like a = tau + c + tau - * then def-use is simply [DUState::U, DUState::U]. But if we have if-else block like: + * For a given variable, say tau, when we have statement like `a = tau + c + tau` + * then def-use is simply `[DUState::U, DUState::U]`. But if we have if-else block + * like: * - * IF (...) { - * a = tau - * tau = c + d - * } ELSE { - * tau = b - * } + * \code{.mod} + * IF (...) { + * a = tau + * tau = c + d + * } ELSE { + * tau = b + * } + * \endcode * - * Then to know the effective result, we have to analyze def-use in IF and ELSE + * Then to know the effective result, we have to analyze def-use in `IF` and `ELSE` * blocks i.e. if variable is used in any of the if-elseif-else block then it needs - * to mark as "DUState::U". Hence we keep the track of all children in case of + * to mark as `DUState::U`. Hence we keep the track of all children in case of * statements like if-else. */ class DUInstance { @@ -87,13 +96,13 @@ class DUInstance { /// evaluate global usage i.e. with [D,U] states of children DUState conditional_block_eval(); - void print(JSONPrinter& printer); + void print(printer::JSONPrinter& printer); }; /** * \class DUChain - * \brief Def-Use chain for ast node + * \brief Def-Use chain for an AST node */ class DUChain { public: @@ -115,14 +124,20 @@ class DUChain { }; +/** + * @addtogroup visitor_classes + * @{ + */ + /** * \class DefUseAnalyzeVisitor - * \brief Visitor to return Def-Use chain for a given variable in the block/node + * \brief %Visitor to return Def-Use chain for a given variable in the block/node * * Motivation: For global to local variable transformation aka localizer * pass, we need to compute Def-Use chains for all global variables. For * example, if we have variable usage like: * + * \code{.mod} * NEURON { * RANGE tau, beta * } @@ -141,7 +156,7 @@ class DUChain { * z = beta * 0.1 * alpha = x * y * } - * + * \endcode * * In the above example if we look at variable beta then it's defined before it's * usage and hence it can be safely made local. But variable alpha is used in first @@ -152,15 +167,17 @@ class DUChain { * The analysis of if-elseif-else needs special attention because the def-use chain * needs re-cursive evaluation in order to find end-result. For example: * - * IF(...) { - * beta = y - * } ELSE { + * \code{.mod} * IF(...) { - * beta = x + * beta = y * } ELSE { - * x = beta - * } - * } + * IF(...) { + * beta = x + * } ELSE { + * x = beta + * } + * } + * \endcode * * For if-else statements, in the above example, if the variable is used * in any of the if-elseif-else part then it is considered as "used". And @@ -284,7 +301,10 @@ class DefUseAnalyzeVisitor: public AstVisitor { virtual void visit_argument(ast::Argument* /*node*/) override {} /// compute def-use chain for a variable within the node - DUChain analyze(ast::AST* node, const std::string& name); + DUChain analyze(ast::Ast* node, const std::string& name); }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 37ffdcc04a..048e40b15c 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -10,6 +10,8 @@ namespace nmodl { +namespace visitor { + using namespace ast; bool InlineVisitor::can_inline_block(StatementBlock* block) { @@ -193,7 +195,7 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { auto symbol = program_symtab->lookup_in_scope(function_name); /// nothing to do if called function is not defined or it's external - if (symbol == nullptr || symbol->is_external_symbol_only()) { + if (symbol == nullptr || symbol->is_external_variable()) { return; } @@ -216,7 +218,7 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { } if (inlined) { - symbol->inlined(); + symbol->mark_inlined(); } } @@ -310,4 +312,5 @@ void InlineVisitor::visit_program(Program* node) { node->visit_children(this); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 661000bf98..3e8dc39e11 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::InlineVisitor + */ + #include <map> #include <stack> @@ -19,14 +24,21 @@ namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class InlineVisitor - * \brief Visitor to inline local procedure and function calls + * \brief %Visitor to inline local procedure and function calls * * Motivation: Mod files often have function and procedure calls. Procedure * typically has use of range variables like: * + * \code{.mod} * NEURON { * RANGE tau, alpha, beta * } @@ -41,6 +53,7 @@ namespace nmodl { * tau = xx * 0.12 * some_var * beta = yy * 0.11 * } + * \endcode * * One can reduce the memory bandwidth pressure by inlining rates() and then * replacing tau and beta with local variables. Many mod files from BlueBrain @@ -75,47 +88,50 @@ namespace nmodl { * * Examples: * - PROCEDURE rates_1() { - LOCAL x - rates_2(23.1) - } - - PROCEDURE rates_2(y) { - LOCAL x - x = 21.1*v+y - } - + * \code{.mod} + * PROCEDURE rates_1() { + * LOCAL x + * rates_2(23.1) + * } + * + * PROCEDURE rates_2(y) { + * LOCAL x + * x = 21.1*v+y + * } + * \endcode + * * The result of inlining : - - PROCEDURE rates_1() { - LOCAL x, rates_2_in_0 - { - LOCAL x, y_in_0 - y_in_0 = 23.1 - x = 21.1*v+y_in_0 - rates_2_in_0 = 0 - } - } - - PROCEDURE rates_2(y) { - LOCAL x - x = 21.1*v+y - } - + * + * \code{.mod} + * PROCEDURE rates_1() { + * LOCAL x, rates_2_in_0 + * { + * LOCAL x, y_in_0 + * y_in_0 = 23.1 + * x = 21.1*v+y_in_0 + * rates_2_in_0 = 0 + * } + * } + * + * PROCEDURE rates_2(y) { + * LOCAL x + * x = 21.1*v+y + * } + * \endcode + * * - Arguments for function call are copied into local variables * - Local statement gets added to callee block (if doesn't exist) * - Procedure body gets appended with extra assignment statement with variable * used for returning value. * - * \todo: + * \todo * - Recursive function calls are not supported and need to add checks to avoid stack explosion * - Currently we rename variables more than necessary, this could be improved [low priority] * - Function calls as part of an argument of function call itself are not completely inlined [low - priority] + * priority] * - Symbol table pass needs to be re-run in order to update the definitions/usage * - Location of symbol/nodes after inlining still points to old nodes */ - class InlineVisitor: public AstVisitor { private: /// statement block containing current function call @@ -180,4 +196,7 @@ class InlineVisitor: public AstVisitor { virtual void visit_program(ast::Program* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 5be51057f1..d1826b2764 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -14,11 +14,12 @@ #include "utils/string_utils.hpp" #include "visitor_utils.hpp" -using namespace fmt::literals; namespace nmodl { +namespace visitor { using symtab::syminfo::NmodlType; +using namespace fmt::literals; void KineticBlockVisitor::process_reac_var(const std::string& varname, int count) { // lookup index of state var @@ -29,11 +30,13 @@ void KineticBlockVisitor::process_reac_var(const std::string& varname, int count // this should be included in the fluxes: if (in_reaction_statement_lhs) { non_state_var_fflux[i_statement] = varname; - logger->debug("KineticBlockVisitor :: adding non-state fflux[{}] \"{}\"", i_statement, + logger->debug("KineticBlockVisitor :: adding non-state fflux[{}] \"{}\"", + i_statement, varname); } else { non_state_var_bflux[i_statement] = varname; - logger->debug("KineticBlockVisitor :: adding non-state bflux[{}] \"{}\"", i_statement, + logger->debug("KineticBlockVisitor :: adding non-state bflux[{}] \"{}\"", + i_statement, varname); } // but as it is not a state var, no ODE should be generated for it, i.e. no nu_L or nu_R @@ -44,12 +47,16 @@ void KineticBlockVisitor::process_reac_var(const std::string& varname, int count if (in_reaction_statement_lhs) { // set element of nu_L matrix rate_eqs.nu_L[i_statement][i_statevar] += count; - logger->debug("KineticBlockVisitor :: nu_L[{}][{}] += {}", i_statement, i_statevar, + logger->debug("KineticBlockVisitor :: nu_L[{}][{}] += {}", + i_statement, + i_statevar, count); } else { // set element of nu_R matrix rate_eqs.nu_R[i_statement][i_statevar] += count; - logger->debug("KineticBlockVisitor :: nu_R[{}][{}] += {}", i_statement, i_statevar, + logger->debug("KineticBlockVisitor :: nu_R[{}][{}] += {}", + i_statement, + i_statevar, count); } } @@ -62,7 +69,8 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { // to either multiply or divide state vars on LHS of conserve statement by their COMPARTMENT // factors? logger->debug("KineticBlockVisitor :: CONSERVE statement ignored (for now): {} = {}", - to_nmodl(node->get_react().get()), to_nmodl(node->get_expr().get())); + to_nmodl(node->get_react().get()), + to_nmodl(node->get_expr().get())); statements_to_remove.insert(node); } @@ -81,7 +89,9 @@ void KineticBlockVisitor::visit_compartment(ast::Compartment* node) { compartment_factors[var_index] = expression; logger->debug( "KineticBlockVisitor :: COMPARTMENT factor {} for state var {} (index {})", - expression, var_name, var_index); + expression, + var_name, + var_index); } } // add COMPARTMENT state to list of statements to remove @@ -152,7 +162,8 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) } // add to additive terms for this state var additive_terms[var_index] += "({})"_format(expr); - logger->debug("KineticBlockVisitor :: '<<' reaction statement: {}' += {}", varname, + logger->debug("KineticBlockVisitor :: '<<' reaction statement: {}' += {}", + varname, expr); } return; @@ -363,13 +374,15 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { var_name += "["; for (int i = 0; i < v->get_length(); ++i) { std::string var_name_i = var_name + std::to_string(i) + "]"; - logger->debug("KineticBlockVisitor :: state_var_index[{}] = {}", var_name_i, + logger->debug("KineticBlockVisitor :: state_var_index[{}] = {}", + var_name_i, state_var_count); state_var_index[var_name_i] = state_var_count++; state_var.push_back(var_name_i); } } else { - logger->debug("KineticBlockVisitor :: state_var_index[{}] = {}", var_name, + logger->debug("KineticBlockVisitor :: state_var_index[{}] = {}", + var_name, state_var_count); state_var_index[var_name] = state_var_count++; state_var.push_back(var_name); @@ -397,4 +410,5 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { node->set_blocks(std::move(blocks)); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 7d97080b2a..05669d1849 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::KineticBlockVisitor + */ + #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -17,10 +22,16 @@ #include <vector> namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class KineticBlockVisitor - * \brief Visitor for kinetic block statements + * \brief %Visitor for kinetic block statements * * Replaces each KINETIC block with a DERIVATIVE block * containing a system of ODEs that is equivalent to @@ -31,13 +42,12 @@ namespace nmodl { * matter. Also does not yet support array variables. * */ - class KineticBlockVisitor: public AstVisitor { private: - /// update stochiometric matrices with reaction var term + /// update stoichiometric matrices with reaction var term void process_reac_var(const std::string& varname, int count = 1); - /// stochiometric matrices nu_L, nu_R + /// stoichiometric matrices nu_L, nu_R /// forwards/backwards fluxes k_f, k_b /// (see kinetic_schemes.ipynb notebook for details) struct RateEqs { @@ -112,4 +122,7 @@ class KineticBlockVisitor: public AstVisitor { void visit_program(ast::Program* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 35e0573045..dda91981f6 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -11,6 +11,7 @@ namespace nmodl { +namespace visitor { using symtab::SymbolTable; @@ -70,4 +71,5 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { } } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index 7de23e6a05..bb9ae7dd3d 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::LocalVarRenameVisitor + */ + #include <map> #include <stack> @@ -15,14 +20,21 @@ #include "visitors/ast_visitor.hpp" namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class LocalVarRenameVisitor - * \brief Visitor to rename local variables conflicting with global scope + * \brief %Visitor to rename local variables conflicting with global scope * * Motivation: During inlining we have to do data-flow-analysis. Consider * below example: * + * \code{.mod} * NEURON { * RANGE tau, beta * } @@ -36,6 +48,7 @@ namespace nmodl { * PROCEDURE rates() { * tau = beta * 0.12 * some_var * } + * \endcode * * When rates() will be inlined into states(), local definition of tau will * conflict with range variable tau. Hence we can't just copy the statements. @@ -43,10 +56,10 @@ namespace nmodl { * this pass before inlining and pre-rename any local-global variable conflicts. * As we are renaming local variables only, it's safe and there are no side effects. * - * \todo: currently we are renaming variables even if there is no inlining candidates. - * In this case ideally we should not rename. + * \todo + * - Currently we are renaming variables even if there is no inlining candidates. + * In this case ideally we should not rename. */ - class LocalVarRenameVisitor: public AstVisitor { private: /// non-null symbol table in the scope hierarchy @@ -63,4 +76,7 @@ class LocalVarRenameVisitor: public AstVisitor { virtual void visit_statement_block(ast::StatementBlock* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 359948367b..159baf2060 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -11,6 +11,7 @@ #include "visitors/localize_visitor.hpp" namespace nmodl { +namespace visitor { using symtab::Symbol; using symtab::syminfo::NmodlType; @@ -56,7 +57,7 @@ bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { if (node->is_procedure_block()) { auto symbol = program_symtab->lookup(node->get_node_name()); - if (symbol && symbol->has_properties(NmodlType::to_solve)) { + if (symbol && symbol->has_any_property(NmodlType::to_solve)) { return true; } } @@ -91,7 +92,7 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { std::vector<std::string> result; for (auto& variable: variables) { - if (!variable->has_properties(excluded_var_properties)) { + if (!variable->has_any_property(excluded_var_properties)) { result.push_back(variable->get_name()); } } @@ -137,7 +138,8 @@ void LocalizeVisitor::visit_program(ast::Program* node) { auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { - variable = add_local_variable(statement_block.get(), varname, + variable = add_local_variable(statement_block.get(), + varname, symbol->get_length()); } else { variable = add_local_variable(statement_block.get(), varname); @@ -158,4 +160,5 @@ void LocalizeVisitor::visit_program(ast::Program* node) { } } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index ea3558fc73..1035a0974e 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::LocalizeVisitor + */ + #include <map> #include <stack> @@ -21,15 +26,22 @@ namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class LocalizeVisitor - * \brief Visitor to transform global variable usage to local + * \brief %Visitor to transform global variable usage to local * * Motivation: As NMODL doesn't support returning multiple values, * procedures are often written with use of range variables that * can be made local. For example: * + * \code{.mod} * NEURON { * RANGE tau, alpha, beta * } @@ -44,11 +56,13 @@ namespace nmodl { * tau = xx * 0.12 * some_var * beta = yy * 0.11 * } + * \endcode * * In above example we are only interested in variable alpha computed in * DERIVATIVE block. If rates() is inlined into DERIVATIVE block then we * get: * + * \code{.mod} * DERIVATIVE states() { * ... * { @@ -57,6 +71,7 @@ namespace nmodl { * } * alpha = tau + beta * } + * \endcode * * Now tau and beta could become local variables provided that their values * are not used in any other global blocks. @@ -67,12 +82,11 @@ namespace nmodl { * already inlined). * - If every block has "definition" first then that variable is safe to "localize" * - * \todo: + * \todo * - We are excluding procedures/functions because they will be still using global * variables. We need to have dead-code removal pass to eliminate unused procedures/ * functions before localizer pass. */ - class LocalizeVisitor: public AstVisitor { private: /// ignore verbatim blocks while localizing @@ -95,4 +109,7 @@ class LocalizeVisitor: public AstVisitor { virtual void visit_program(ast::Program* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 60990eff97..e61744e6a1 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -13,6 +13,7 @@ namespace nmodl { +namespace visitor { /** * \class IndexRemover @@ -120,7 +121,8 @@ static std::shared_ptr<ast::ExpressionStatement> unroll_for_loop( /// duplicate loop body and copy all statements to new vector auto new_block = node->get_statement_block()->clone(); IndexRemover(index_var, i).visit_statement_block(new_block); - statements.insert(statements.end(), new_block->statements.begin(), + statements.insert(statements.end(), + new_block->statements.begin(), new_block->statements.end()); delete new_block; } @@ -161,4 +163,5 @@ void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { } } +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index 00d1ce21ff..347a521387 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -7,14 +7,24 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::LoopUnrollVisitor + */ + #include <string> #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" - namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class LoopUnrollVisitor @@ -22,29 +32,32 @@ namespace nmodl { * * Derivative and kinetic blocks have for loop with coupled set of ODEs : * - * DEFINE NANN 4 - * KINETIC state { - * FROM i=0 TO NANN-2 { - * ~ ca[i] <-> ca[i+1] (DFree*frat[i+1]*1(um), DFree*frat[i+1]*1(um)) - * } - * } + * \code{.mod} + * DEFINE NANN 4 + * KINETIC state { + * FROM i=0 TO NANN-2 { + * ~ ca[i] <-> ca[i+1] (DFree*frat[i+1]*1(um), DFree*frat[i+1]*1(um)) + * } + * } + * \endcode * * To solve these ODEs with SymPy, we need to get all ODEs from such loops. * This visitor unroll such loops and insert new expression statement in * the AST : * - * KINETIC state { - * { - * ~ ca[0] <-> ca[0+1] (DFree*frat[0+1]*1(um), DFree*frat[0+1]*1(um)) - * ~ ca[1] <-> ca[1+1] (DFree*frat[1+1]*1(um), DFree*frat[1+1]*1(um)) - * ~ ca[2] <-> ca[2+1] (DFree*frat[2+1]*1(um), DFree*frat[2+1]*1(um)) - * } - * } + * \code{.mod} + * KINETIC state { + * { + * ~ ca[0] <-> ca[0+1] (DFree*frat[0+1]*1(um), DFree*frat[0+1]*1(um)) + * ~ ca[1] <-> ca[1+1] (DFree*frat[1+1]*1(um), DFree*frat[1+1]*1(um)) + * ~ ca[2] <-> ca[2+1] (DFree*frat[2+1]*1(um), DFree*frat[2+1]*1(um)) + * } + * } + * \endcode * * Note that the index `0+1` is not expanded to `1` because we do not run * constant folder pass within this loop (but could be easily done). */ - class LoopUnrollVisitor: public AstVisitor { public: LoopUnrollVisitor() = default; @@ -52,4 +65,7 @@ class LoopUnrollVisitor: public AstVisitor { virtual void visit_statement_block(ast::StatementBlock* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 94c2fc2216..dd233b3036 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -15,13 +15,13 @@ #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" @@ -32,16 +32,17 @@ using namespace nmodl; +using namespace visitor; using namespace fmt::literals; /** - * Standalone visitor program for NMODL. This demonstrate basic - * usage of different visitor and driver classes. + * \file + * \brief Standalone program demonstrating usage of different visitors and driver classes. **/ int main(int argc, const char* argv[]) { CLI::App app{ - "NMODL Visitor : Runs standalone visitor classes({})"_format(version::to_string())}; + "NMODL Visitor : Runs standalone visitor classes({})"_format(Version::to_string())}; bool verbose = false; std::vector<std::string> files; @@ -68,17 +69,19 @@ int main(int argc, const char* argv[]) { {std::make_shared<SymtabVisitor>(), "symtab", "SymtabVisitor"}, {std::make_shared<JSONVisitor>(), "json", "JSONVisitor"}, {std::make_shared<VerbatimVisitor>(), "verbatim", "VerbatimVisitor"}, - {std::make_shared<VerbatimVarRenameVisitor>(), "verbatim-rename", + {std::make_shared<VerbatimVarRenameVisitor>(), + "verbatim-rename", "VerbatimVarRenameVisitor"}, {std::make_shared<KineticBlockVisitor>(), "kinetic-rewrite", "KineticBlockVisitor"}, {std::make_shared<ConstantFolderVisitor>(), "const-fold", "ConstantFolderVisitor"}, - {std::make_shared<InlineVisitor>(), "cnexp", "InlineVisitor"}, + {std::make_shared<InlineVisitor>(), "inline", "InlineVisitor"}, {std::make_shared<LocalVarRenameVisitor>(), "local-rename", "LocalVarRenameVisitor"}, {std::make_shared<SymtabVisitor>(), "symtab", "SymtabVisitor"}, - {std::make_shared<SympyConductanceVisitor>(), "sympy-cond", "SympyConductanceVisitor"}, - {std::make_shared<SymtabVisitor>(), "symtab", "SymtabVisitor"}, + {std::make_shared<SympyConductanceVisitor>(), + "sympy-conductance", + "SympyConductanceVisitor"}, {std::make_shared<SympySolverVisitor>(), "sympy-solve", "SympySolverVisitor"}, - {std::make_shared<CnexpSolveVisitor>(), "cnexp", "CnexpSolveVisitor"}, + {std::make_shared<NeuronSolveVisitor>(), "neuron-solve", "NeuronSolveVisitor"}, {std::make_shared<LocalizeVisitor>(), "localize", "LocalizeVisitor"}, {std::make_shared<PerfVisitor>(), "perf", "PerfVisitor"}, }; @@ -88,21 +91,20 @@ int main(int argc, const char* argv[]) { for (const auto& filename: files) { logger->info("Processing {}", filename); - std::string mod_file = remove_extension(base_name(filename)); + std::string mod_file = utils::remove_extension(utils::base_name(filename)); /// driver object that creates lexer and parser parser::NmodlDriver driver; - driver.parse_file(filename); /// shared_ptr to ast constructed from parsing nmodl file - auto ast = driver.ast().get(); + auto ast = driver.parse_file(filename); /// run all visitors and generate mod file after each run for (const auto& visitor: visitors) { logger->info("Running {}", visitor.description); - visitor.v->visit_program(ast); + visitor.v->visit_program(ast.get()); std::string file = mod_file + "." + visitor.id + ".mod"; - NmodlPrintVisitor(file).visit_program(ast); + NmodlPrintVisitor(file).visit_program(ast.get()); logger->info("NMODL visitor generated {}", file); } } diff --git a/src/nmodl/visitors/cnexp_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp similarity index 84% rename from src/nmodl/visitors/cnexp_solve_visitor.cpp rename to src/nmodl/visitors/neuron_solve_visitor.cpp index 7f9a748bf3..cb85eef9d2 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -11,14 +11,15 @@ #include "parser/diffeq_driver.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitors/cnexp_solve_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { +namespace visitor { -void CnexpSolveVisitor::visit_solve_block(ast::SolveBlock* node) { +void NeuronSolveVisitor::visit_solve_block(ast::SolveBlock* node) { auto name = node->get_block_name()->get_node_name(); auto method = node->get_method(); solve_method = method ? method->get_value()->eval() : ""; @@ -26,7 +27,7 @@ void CnexpSolveVisitor::visit_solve_block(ast::SolveBlock* node) { } -void CnexpSolveVisitor::visit_derivative_block(ast::DerivativeBlock* node) { +void NeuronSolveVisitor::visit_derivative_block(ast::DerivativeBlock* node) { derivative_block_name = node->get_name()->get_node_name(); derivative_block = true; node->visit_children(this); @@ -34,14 +35,14 @@ void CnexpSolveVisitor::visit_derivative_block(ast::DerivativeBlock* node) { } -void CnexpSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { +void NeuronSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { differential_equation = true; node->visit_children(this); differential_equation = false; } -void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { +void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto& lhs = node->lhs; auto& rhs = node->rhs; auto& op = node->op; @@ -70,7 +71,7 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { lhs.reset(bin_expr->lhs->clone()); rhs.reset(bin_expr->rhs->clone()); } else { - logger->warn("CnexpSolveVisitor :: cnexp solver not possible for {}", + logger->warn("NeuronSolveVisitor :: cnexp solver not possible for {}", to_nmodl(node)); } } else if (solve_method == codegen::naming::EULER_METHOD) { @@ -92,14 +93,15 @@ void CnexpSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { program_symtab->insert(symbol); } } else { - logger->error("CnexpSolveVisitor :: solver method '{}' not supported", solve_method); + logger->error("NeuronSolveVisitor :: solver method '{}' not supported", solve_method); } } } -void CnexpSolveVisitor::visit_program(ast::Program* node) { +void NeuronSolveVisitor::visit_program(ast::Program* node) { program_symtab = node->get_symbol_table(); node->visit_children(this); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/cnexp_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp similarity index 65% rename from src/nmodl/visitors/cnexp_solve_visitor.hpp rename to src/nmodl/visitors/neuron_solve_visitor.hpp index c26baa9c94..6b4bfe380c 100644 --- a/src/nmodl/visitors/cnexp_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -7,25 +7,38 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::NeuronSolveVisitor + */ + #include <string> #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" + namespace nmodl { +namespace visitor { /** - * \class CnexpSolveVisitor - * \brief Visitor that solves and replaces ODEs using cnexp method - * - * This pass solves ODEs in derivative block if cnexp method is used. - * The original ODEs get replaced with the solution. This transformation - * is performed at ast level. This is useful for performance modeling - * purpose where we want to measure performance metrics using perfvisitor - * pass. + * @addtogroup solver + * @addtogroup visitor_classes + * @{ */ -class CnexpSolveVisitor: public AstVisitor { +/** + * \class NeuronSolveVisitor + * \brief %Visitor that solves ODEs using old solvers of NEURON + * + * This pass solves ODEs in derivative block using `cnexp`, `euler` and + * `derivimplicit`method. This solved mimics original implementation in + * nocmodl/mod2c. The original ODEs get replaced with the solution and + * transformations are performed at AST level. + * + * \sa nmodl::visitor::SympySolverVisitor + */ +class NeuronSolveVisitor: public AstVisitor { private: /// true while visiting differential equation bool differential_equation = false; @@ -46,7 +59,7 @@ class CnexpSolveVisitor: public AstVisitor { std::string derivative_block_name; public: - CnexpSolveVisitor() = default; + NeuronSolveVisitor() = default; void visit_solve_block(ast::SolveBlock* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; @@ -55,4 +68,7 @@ class CnexpSolveVisitor: public AstVisitor { void visit_program(ast::Program* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index 4766d3ec0e..63acd8ebbc 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -11,10 +11,12 @@ namespace nmodl { +namespace visitor { /** Helper function to visit vector elements * * @tparam T + * @param elements vector of nodes/elements * @param separator separator to print for individual vector element * @param program true if provided elements belong to program node * @param statement true if elements in vector of statement type @@ -34,7 +36,7 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, (*iter)->accept(this); /// print separator (e.g. comma, space) - if (!separator.empty() && !is_last(iter, elements)) { + if (!separator.empty() && !utils::is_last(iter, elements)) { printer->add_element(separator); } @@ -46,7 +48,7 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, /// if there are multiple inline comments then we want them to be /// contiguous and only last comment should have extra line. bool extra_newline = false; - if (!is_last(iter, elements)) { + if (!utils::is_last(iter, elements)) { extra_newline = true; if ((*iter)->is_line_comment() && (*(iter + 1))->is_line_comment()) { extra_newline = false; @@ -63,4 +65,5 @@ void NmodlPrintVisitor::visit_element(const std::vector<T>& elements, } } +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index b1ca4b7704..41be534521 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -11,10 +11,13 @@ namespace nmodl { +namespace visitor { +using printer::JSONPrinter; using symtab::Symbol; using symtab::syminfo::NmodlType; using symtab::syminfo::Status; +using utils::PerfStat; PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) {} @@ -118,7 +121,7 @@ void PerfVisitor::add_perf_to_printer(PerfStat& perf) { * all children visited, we get total performance by summing * perfstat of all children. */ -void PerfVisitor::measure_performance(ast::AST* node) { +void PerfVisitor::measure_performance(ast::Ast* node) { start_measurement = true; node->visit_children(this); @@ -177,7 +180,7 @@ void PerfVisitor::visit_function_call(ast::FunctionCall* node) { auto symbol = current_symtab->lookup_in_scope(name); auto method_property = NmodlType::procedure_block | NmodlType::function_block; - if (symbol != nullptr && symbol->has_properties(method_property)) { + if (symbol != nullptr && symbol->has_any_property(method_property)) { current_block_perf.n_int_func_call++; } else { current_block_perf.n_ext_func_call++; @@ -223,9 +226,9 @@ void PerfVisitor::count_variables() { auto variables = current_symtab->get_variables_with_properties(property); for (auto& variable: variables) { - if (!variable->has_properties(NmodlType::global_var)) { + if (!variable->has_any_property(NmodlType::global_var)) { num_instance_variables++; - if (variable->has_properties(NmodlType::param_assign)) { + if (variable->has_any_property(NmodlType::param_assign)) { num_constant_instance_variables++; } if (variable->has_any_status(Status::localized)) { @@ -253,11 +256,11 @@ void PerfVisitor::count_variables() { variables = current_symtab->get_variables_with_properties(property); num_global_variables = 0; for (auto& variable: variables) { - auto is_global = variable->has_properties(NmodlType::global_var); + auto is_global = variable->has_any_property(NmodlType::global_var); property = NmodlType::range_var | NmodlType::dependent_def; - if (!variable->has_properties(property) || is_global) { + if (!variable->has_any_property(property) || is_global) { num_global_variables++; - if (variable->has_properties(NmodlType::param_assign)) { + if (variable->has_any_property(NmodlType::param_assign)) { num_constant_global_variables++; } if (variable->has_any_status(Status::localized)) { @@ -399,12 +402,12 @@ void PerfVisitor::visit_unary_expression(ast::UnaryExpression* node) { bool PerfVisitor::symbol_to_skip(const std::shared_ptr<Symbol>& symbol) { bool skip = false; - auto is_method = symbol->has_properties(NmodlType::extern_method | NmodlType::function_block); + auto is_method = symbol->has_any_property(NmodlType::extern_method | NmodlType::function_block); if (is_method && under_function_call) { skip = true; } - is_method = symbol->has_properties(NmodlType::derivative_block | NmodlType::extern_method); + is_method = symbol->has_any_property(NmodlType::derivative_block | NmodlType::extern_method); if (is_method && under_solve_block) { skip = true; } @@ -416,7 +419,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr<Symbol>& symbol) { bool is_local = false; /// in the function when we write to function variable then consider it as local variable auto properties = NmodlType::local_var | NmodlType::argument | NmodlType::function_block; - if (symbol->has_properties(properties)) { + if (symbol->has_any_property(properties)) { is_local = true; } return is_local; @@ -425,7 +428,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr<Symbol>& symbol) { bool PerfVisitor::is_constant_variable(const std::shared_ptr<Symbol>& symbol) { bool is_constant = false; auto properties = NmodlType::param_assign; - if (symbol->has_properties(properties)) { + if (symbol->has_any_property(properties)) { is_constant = true; } return is_constant; @@ -492,4 +495,5 @@ void PerfVisitor::update_memory_ops(const std::string& name) { } } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index ee0717f157..133e053bce 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::PerfVisitor + */ + #include <set> #include <stack> @@ -17,10 +22,16 @@ namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class PerfVisitor - * \brief Visitor for measuring performance related information + * \brief %Visitor for measuring performance related information * * This visitor used to visit the ast and associated symbol tables * to measure the performance of every block in nmodl file. For @@ -31,18 +42,18 @@ namespace nmodl { * be skipped (i.e. without visiting children). Note that this * pass must be run after symbol table generation pass. * - * \todo : To measure the performance of statements like if, elseif - * and else, we have to find maximum performance from if,elseif,else - * and then use it to calculate total performance. In the current - * implementation we are doing sum of all blocks. We need to override - * IfStatement (which has all sub-blocks) and get maximum performance - * of all statements recursively. + * \todo + * - To measure the performance of statements like if, elseif + * and else, we have to find maximum performance from if,elseif,else + * and then use it to calculate total performance. In the current + * implementation we are doing sum of all blocks. We need to override + * IfStatement (which has all sub-blocks) and get maximum performance + * of all statements recursively. * - * \todo : In order to avoid empty implementations and checking - * start_measurement, there should be "empty" ast visitor from - * which PerfVisitor should be inherited. + * - In order to avoid empty implementations and checking + * start_measurement, there should be "empty" ast visitor from + * which PerfVisitor should be inherited. */ - class PerfVisitor: public AstVisitor { private: /// symbol table of current block being visited @@ -50,16 +61,16 @@ class PerfVisitor: public AstVisitor { /// performance stats of all blocks being visited /// in recursive chain - std::stack<PerfStat> blocks_perf; + std::stack<utils::PerfStat> blocks_perf; /// total performance of mod file - PerfStat total_perf; + utils::PerfStat total_perf; /// performance of current block - PerfStat current_block_perf; + utils::PerfStat current_block_perf; /// performance of current all childrens - std::stack<PerfStat> children_blocks_perf; + std::stack<utils::PerfStat> children_blocks_perf; /// whether to measure performance for current block bool start_measurement = false; @@ -78,7 +89,7 @@ class PerfVisitor: public AstVisitor { bool under_net_receive_block = false; /// to print to json file - std::unique_ptr<JSONPrinter> printer; + std::unique_ptr<printer::JSONPrinter> printer; /// if not json, all goes to string std::stringstream stream; @@ -129,11 +140,11 @@ class PerfVisitor: public AstVisitor { void count_variables(); - void measure_performance(ast::AST* node); + void measure_performance(ast::Ast* node); void print_memory_usage(); - void add_perf_to_printer(PerfStat& perf); + void add_perf_to_printer(utils::PerfStat& perf); public: PerfVisitor() = default; @@ -144,7 +155,7 @@ class PerfVisitor: public AstVisitor { printer->compact_json(flag); } - PerfStat get_total_perfstat() { + utils::PerfStat get_total_perfstat() { return total_perf; } @@ -295,4 +306,7 @@ class PerfVisitor: public AstVisitor { } }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 7d4e2bac08..2cb62302e1 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -10,6 +10,7 @@ namespace nmodl { +namespace visitor { /// rename matching variable void RenameVisitor::visit_name(ast::Name* node) { @@ -55,4 +56,5 @@ void RenameVisitor::visit_verbatim(ast::Verbatim* node) { statement->set(result); } +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index c21bb5ba0f..ffe8ceeb47 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::RenameVisitor + */ + #include <string> #include "ast/ast.hpp" @@ -15,10 +20,16 @@ namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** - * \class VarRenameVisitor - * \brief "Blindly" rename given variable to new name + * \class RenameVisitor + * \brief `Blindly` rename given variable to new name * * During inlining related passes we have to rename variables * to avoid name conflicts. This pass "blindly" rename any given @@ -27,9 +38,8 @@ namespace nmodl { * local renaming pass should be done from inner-most block to top * level block; * - * \todo : Add log/warning messages. + * \todo Add log/warning messages. */ - class RenameVisitor: public AstVisitor { private: /// variable to rename @@ -62,4 +72,7 @@ class RenameVisitor: public AstVisitor { virtual void visit_verbatim(ast::Verbatim* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 5ea983fcc9..8dcc13894d 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -11,6 +11,7 @@ #include "visitors/lookup_visitor.hpp" namespace nmodl { +namespace visitor { void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { in_breakpoint_block = true; @@ -19,7 +20,7 @@ void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { } /// check if given node contains sympy solution -static bool has_sympy_solution(ast::AST* node) { +static bool has_sympy_solution(ast::Ast* node) { return !AstLookupVisitor().lookup(node, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK).empty(); } @@ -75,7 +76,6 @@ void SolveBlockVisitor::visit_expression_statement(ast::ExpressionStatement* nod } } - void SolveBlockVisitor::visit_program(ast::Program* node) { symtab = node->get_symbol_table(); node->visit_children(this); @@ -86,4 +86,5 @@ void SolveBlockVisitor::visit_program(ast::Program* node) { } } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp index bca40c900b..63951191c7 100644 --- a/src/nmodl/visitors/solve_block_visitor.hpp +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -7,23 +7,32 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::SolveBlockVisitor + */ + #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" namespace nmodl { +namespace visitor { +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class SolveBlockVisitor * \brief Replace solve block statements with actual solution node in the AST * - * Once sympy or cnexp passes are run, solve blocks can be replaced with solution - * expression node that represent solution that is going to be solved. All solve - * statements appearing in breakpoint block get added to NrnState block as solution - * expression. + * Once SympySolverVisitor or NeuronSolveVisitor is ran, solve blocks can be replaced + * with solution expression node that represent solution that is going to be solved. + * All solve statements appearing in breakpoint block get added to NrnState block as + * solution expression. */ - class SolveBlockVisitor: public AstVisitor { private: symtab::SymbolTable* symtab = nullptr; @@ -42,4 +51,7 @@ class SolveBlockVisitor: public AstVisitor { void visit_program(ast::Program* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 06c3436d3b..9b1216a66c 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -17,15 +17,16 @@ #include "visitors/steadystate_visitor.hpp" #include "visitors/visitor_utils.hpp" -using namespace fmt::literals; namespace nmodl { +namespace visitor { +using namespace fmt::literals; using symtab::syminfo::NmodlType; std::shared_ptr<ast::DerivativeBlock> SteadystateVisitor::create_steadystate_block( std::shared_ptr<ast::SolveBlock> solve_block, - const std::vector<std::shared_ptr<ast::AST>>& deriv_blocks) { + const std::vector<std::shared_ptr<ast::Ast>>& deriv_blocks) { // new block to be returned std::shared_ptr<ast::DerivativeBlock> ss_block; @@ -34,7 +35,8 @@ std::shared_ptr<ast::DerivativeBlock> SteadystateVisitor::create_steadystate_blo const auto steadystate_method = solve_block->get_steadystate()->get_value()->eval(); logger->debug("SteadystateVisitor :: Found STEADYSTATE SOLVE statement: using {} for {}", - steadystate_method, solve_block_name); + steadystate_method, + solve_block_name); ast::DerivativeBlock* deriv_block_ptr = nullptr; for (const auto& block_ptr: deriv_blocks) { @@ -125,4 +127,5 @@ void SteadystateVisitor::visit_program(ast::Program* node) { } } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/steadystate_visitor.hpp b/src/nmodl/visitors/steadystate_visitor.hpp index 9fba803e69..8d5a71257f 100644 --- a/src/nmodl/visitors/steadystate_visitor.hpp +++ b/src/nmodl/visitors/steadystate_visitor.hpp @@ -7,45 +7,60 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::SteadystateVisitor + */ + #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class SteadystateVisitor - * \brief Visitor for STEADYSTATE solve statements - * - * For each STEADYSTATE solve statement, creates a clone - * of the corresponding DERIVATIVE block, with "_steadystate" - * appended to the name of the block - * - * If the original DERIVATIVE block was called "X", the - * new DERIVATIVE block will be called "X_steadystate" + * \brief %Visitor for STEADYSTATE solve statements * - * Only difference in new block is that the value of dt + * For each `STEADYSTATE` solve statement, creates a clone of the corresponding + * DERIVATIVE block, with `_steadystate` appended to the name of the block. If + * the original `DERIVATIVE` block was called `X`, the new `DERIVATIVE` block will + * be called `X_steadystate`. Only difference in new block is that the value of `dt` * is changed for the solve, to: * - dt = 1e9 for sparse * - dt = 1e-9 for derivimplicit - * where these values for dt in the steady state solves are taken from NEURON - * see https://github.com/neuronsimulator/nrn/blob/master/src/scopmath/ssimplic_thread.c * - * Also updates the solve statement to point to the new DERIVATIVE block - * as a METHOD solve, not a STEADYSTATE one, e.g. - * SOLVE X STEADYSTATE derivimplicit + * where these values for `dt` in the steady state solves are taken from NEURON see + * https://github.com/neuronsimulator/nrn/blob/master/src/scopmath/ssimplic_thread.c + * + * Also updates the solve statement to point to the new `DERIVATIVE` block as a + * `METHOD solve`, not a `STEADYSTATE` one, e.g. + * + * \code{.mod} + * SOLVE X STEADYSTATE derivimplicit + * \endcode + * * becomes - * SOLVE X_steadystate METHOD derivimplicit * + * \code{.mod} + * SOLVE X_steadystate METHOD derivimplicit + * \endcode */ - class SteadystateVisitor: public AstVisitor { private: /// create new steady state derivative block for given solve block std::shared_ptr<ast::DerivativeBlock> create_steadystate_block( std::shared_ptr<ast::SolveBlock> solve_block, - const std::vector<std::shared_ptr<ast::AST>>& deriv_blocks); + const std::vector<std::shared_ptr<ast::Ast>>& deriv_blocks); + const double STEADYSTATE_SPARSE_DT = 1e9; + const double STEADYSTATE_DERIVIMPLICIT_DT = 1e-9; public: @@ -54,4 +69,7 @@ class SteadystateVisitor: public AstVisitor { void visit_program(ast::Program* node) override; }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 88fc14ad5f..66dff4fb63 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -19,12 +19,12 @@ namespace py = pybind11; using namespace py::literals; namespace nmodl { +namespace visitor { using ast::AstNodeType; using ast::BinaryOp; using symtab::syminfo::NmodlType; - /** * Analyse breakpoint block to check if it is safe to insert CONDUCTANCE statements * @@ -91,7 +91,8 @@ std::vector<std::string> SympyConductanceVisitor::generate_statement_strings( solution = "" exception_message = str(e) )", - py::globals(), locals); + py::globals(), + locals); auto dIdV = locals["solution"].cast<std::string>(); auto exception_message = locals["exception_message"].cast<std::string>(); if (!exception_message.empty()) { @@ -178,7 +179,8 @@ void SympyConductanceVisitor::lookup_useion_statements() { std::string ion_write = w->get_node_name(); logger->debug( "SympyConductance :: -> Adding ion write name: {} for ion current name: {}", - ion_write, ion_name); + ion_write, + ion_name); i_name[ion_write] = ion_name; } } @@ -250,4 +252,5 @@ void SympyConductanceVisitor::visit_program(ast::Program* node) { node->visit_children(this); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index 21a225f59b..442d68ee1b 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::SympyConductanceVisitor + */ + #include <map> #include <set> #include <vector> @@ -22,29 +27,37 @@ namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class SympyConductanceVisitor - * \brief Visitor for generating CONDUCTANCE statements for ions + * \brief %Visitor for generating CONDUCTANCE statements for ions * - * This class visits each ion expresion I = ... in the breakpoint - * and symbolically differentiates it to get dI/dV, - * i.e. the conductance. + * This class visits each ion expression `I = ...` in the `BREAKPOINT` + * and symbolically differentiates it to get `dI/dV`, i.e. the conductance. + * If this coincides with an existing global variable `g` (e.g. when + * `I` is linear in `V`) then it will add * - * If this coincides with an existing global variable g - * (e.g. when I is linear in V) then it will add - * CONDUCTANCE g USEION I + * \code{.mod} + * CONDUCTANCE g USEION I + * \endcode * - * If dI/dV is a more complicated expression, it generates - * a new unique variable name g_unique, and adds two lines - * CONDUCTANCE g_unique USEION I - * g_unique = [dI/dV expression] + * If `dI/dV` is a more complicated expression, it generates a new unique + * variable name `g_unique`, and adds two lines * - * If an ion channel already has a CONDUCTANCE statement - * then it does not modify it. + * \code{.mod} + * CONDUCTANCE g_unique USEION I + * g_unique = [dI/dV expression] + * \endcode * + * If an ion channel already has a `CONDUCTANCE` statement then it does + * not modify it. */ - class SympyConductanceVisitor: public AstVisitor { typedef std::map<std::string, std::string> string_map; typedef std::set<std::string> string_set; @@ -53,37 +66,37 @@ class SympyConductanceVisitor: public AstVisitor { /// true while visiting breakpoint block bool under_breakpoint_block = false; - // set of all variables for SymPy + /// set of all variables for SymPy string_set all_vars; - // set of currents to ignore + /// set of currents to ignore string_set i_ignore; - // map between current write names and ion names + /// map between current write names and ion names string_map i_name; bool NONSPECIFIC_CONDUCTANCE_ALREADY_EXISTS = false; - // list in order of binary expressions in breakpoint + /// list in order of binary expressions in breakpoint std::vector<std::string> ordered_binary_exprs; - // ditto but for LHS of expression only + /// ditto but for LHS of expression only std::vector<std::string> ordered_binary_exprs_lhs; - // map from lhs of binary expression to index of expression in above vector + /// map from lhs of binary expression to index of expression in above vector std::map<std::string, std::size_t> binary_expr_index; - // use ion ast nodes - std::vector<std::shared_ptr<ast::AST>> use_ion_nodes; + /// use ion ast nodes + std::vector<std::shared_ptr<ast::Ast>> use_ion_nodes; - // non specific currents - std::vector<std::shared_ptr<ast::AST>> nonspecific_nodes; + /// non specific currents + std::vector<std::shared_ptr<ast::Ast>> nonspecific_nodes; std::vector<std::string> generate_statement_strings(ast::BreakpointBlock* node); void lookup_useion_statements(); void lookup_nonspecific_statements(); - static std::string to_nmodl_for_sympy(ast::AST* node) { + static std::string to_nmodl_for_sympy(ast::Ast* node) { return to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } @@ -95,4 +108,7 @@ class SympyConductanceVisitor: public AstVisitor { void visit_program(ast::Program* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 1d8e3f8e07..b39d48be37 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -19,6 +19,7 @@ namespace py = pybind11; using namespace py::literals; namespace nmodl { +namespace visitor { using symtab::syminfo::NmodlType; @@ -85,7 +86,8 @@ ast::StatementVector::iterator SympySolverVisitor::get_solution_location_iterato while ((it != statements.end()) && (std::dynamic_pointer_cast<ast::ExpressionStatement>(*it).get() != last_expression_statement)) { - logger->debug("SympySolverVisitor :: {} != {}", to_nmodl((*it).get()), + logger->debug("SympySolverVisitor :: {} != {}", + to_nmodl((*it).get()), to_nmodl(last_expression_statement)); ++it; } @@ -181,10 +183,12 @@ void SympySolverVisitor::construct_eigen_solver_block( /// create eigen linear solver block setup_x_eqs.insert(setup_x_eqs.end(), solutions.begin(), solutions.end()); auto setup_x_block = create_statement_block(setup_x_eqs); - auto solver_block = - std::make_shared<ast::EigenLinearSolverBlock>(n_state_vars, variable_block, - initialize_block, setup_x_block, - update_state_block, finalize_block); + auto solver_block = std::make_shared<ast::EigenLinearSolverBlock>(n_state_vars, + variable_block, + initialize_block, + setup_x_block, + update_state_block, + finalize_block); /// replace statement block with solver block as it contains all statements ast::StatementVector solver_block_statements{ std::make_shared<ast::ExpressionStatement>(solver_block)}; @@ -193,9 +197,13 @@ void SympySolverVisitor::construct_eigen_solver_block( /// create eigen newton solver block auto setup_x_block = create_statement_block(setup_x_eqs); auto functor_block = create_statement_block(solutions); - auto solver_block = std::make_shared<ast::EigenNewtonSolverBlock>( - n_state_vars, variable_block, initialize_block, setup_x_block, functor_block, - update_state_block, finalize_block); + auto solver_block = std::make_shared<ast::EigenNewtonSolverBlock>(n_state_vars, + variable_block, + initialize_block, + setup_x_block, + functor_block, + update_state_block, + finalize_block); /// replace statement block with solver block as it contains all statements ast::StatementVector solver_block_statements{ std::make_shared<ast::ExpressionStatement>(solver_block)}; @@ -208,8 +216,11 @@ void SympySolverVisitor::solve_linear_system(const std::vector<std::string>& pre init_state_vars_vector(); // call sympy linear solver bool small_system = (eq_system.size() <= SMALL_LINEAR_SYSTEM_MAX_STATES); - auto locals = py::dict("eq_strings"_a = eq_system, "state_vars"_a = state_vars, "vars"_a = vars, - "small_system"_a = small_system, "do_cse"_a = elimination); + auto locals = py::dict("eq_strings"_a = eq_system, + "state_vars"_a = state_vars, + "vars"_a = vars, + "small_system"_a = small_system, + "do_cse"_a = elimination); py::exec(R"( from nmodl.ode import solve_lin_system exception_message = "" @@ -225,7 +236,8 @@ void SympySolverVisitor::solve_linear_system(const std::vector<std::string>& pre new_local_vars = [""] exception_message = str(e) )", - py::globals(), locals); + py::globals(), + locals); // returns a vector of solutions, i.e. new statements to add to block: auto solutions = locals["solutions"].cast<std::vector<std::string>>(); // and a vector of new local variables that need to be declared in the block: @@ -278,8 +290,8 @@ void SympySolverVisitor::solve_non_linear_system( // construct ordered vector of state vars used in non-linear system init_state_vars_vector(); // call sympy non-linear solver - auto locals = py::dict("equation_strings"_a = eq_system, "state_vars"_a = state_vars, - "vars"_a = vars); + auto locals = + py::dict("equation_strings"_a = eq_system, "state_vars"_a = state_vars, "vars"_a = vars); py::exec(R"( from nmodl.ode import solve_non_lin_system exception_message = "" @@ -293,7 +305,8 @@ void SympySolverVisitor::solve_non_linear_system( new_local_vars = [""] exception_message = str(e) )", - py::globals(), locals); + py::globals(), + locals); // returns a vector of solutions, i.e. new statements to add to block: auto solutions = locals["solutions"].cast<std::vector<std::string>>(); // may also return a python exception message: @@ -347,7 +360,8 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { const auto node_as_nmodl = to_nmodl_for_sympy(node); const auto locals = py::dict("equation_string"_a = node_as_nmodl, - "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, + "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, + "vars"_a = vars, "use_pade_approx"_a = use_pade_approx); if (solve_method == codegen::naming::EULER_METHOD) { @@ -365,7 +379,8 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { solution = "" exception_message = str(e) )", - py::globals(), locals); + py::globals(), + locals); } else if (solve_method == codegen::naming::CNEXP_METHOD) { // replace x' = f(x) differential equation // with analytic solution for x(t+dt) in terms of x(t) @@ -381,7 +396,8 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { solution = "" exception_message = str(e) )", - py::globals(), locals); + py::globals(), + locals); } else { // for other solver methods: just collect the ODEs & return std::string eq_str = to_nmodl_for_sympy(node); @@ -558,7 +574,8 @@ void SympySolverVisitor::visit_program(ast::Program* node) { // LINEAR and NONLINEAR blocks do not have solve method specified const auto& solve_method = block_ptr->get_method()->get_value()->eval(); logger->debug("SympySolverVisitor :: Found SOLVE statement: using {} for {}", - solve_method, block_name); + solve_method, + block_name); derivative_block_solve_method[block_name] = solve_method; } } @@ -584,4 +601,5 @@ void SympySolverVisitor::visit_program(ast::Program* node) { node->visit_children(this); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index b2e1081d14..9a7bd5a36e 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::SympySolverVisitor + */ + #include <pybind11/embed.h> #include <pybind11/stl.h> #include <set> @@ -19,19 +24,25 @@ #include "visitors/visitor_utils.hpp" namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class SympySolverVisitor - * \brief Visitor for systems of algebraic and differential equations + * \brief %Visitor for systems of algebraic and differential equations * - * For DERIVATIVE block, solver method "cnexp": + * For DERIVATIVE block, solver method `cnexp`: * - replace each ODE with its analytic solution - * - optionally using the (1,1) order Pade approximant in dt + * - optionally using the `(1,1)` order Pade approximant in dt * - * For DERIVATIVE block, solver method "euler": + * For `DERIVATIVE` block, solver method `euler`: * - replace each ODE with forwards Euler timestep * - * For DERIVATIVE block, solver method "sparse": + * For `DERIVATIVE` block, solver method `sparse`: * - construct backwards Euler timestep linear system * - for small systems: solves resulting linear algebraic equation by * Gaussian elimination, replaces differential equations @@ -39,21 +50,19 @@ namespace nmodl { * - for large systems, returns matrix and vector of linear system * to be solved by e.g. LU factorization * - * For DERIVATIVE block, solver method "derivimplicit": + * For `DERIVATIVE` block, solver method `derivimplicit`: * - construct backwards Euler timestep non-linear system * - return function F and its Jacobian J to be solved by newton solver * - * For LINEAR blocks: + * For `LINEAR` blocks: * - for small systems: solve linear system of algebraic equations by * Gaussian elimination, replace equations with solutions * - for large systems: return matrix and vector of linear system * to be solved by e.g. LU factorization * - * For NON_LINEAR blocks: + * For `NON_LINEAR` blocks: * - return function F and its Jacobian J to be solved by newton solver - * */ - class SympySolverVisitor: public AstVisitor { private: /// clear any data from previous block & get set of block local vars + global vars @@ -83,7 +92,7 @@ class SympySolverVisitor: public AstVisitor { void solve_non_linear_system(const std::vector<std::string>& pre_solve_statements = {}); /// return NMODL string version of node, excluding any units - static std::string to_nmodl_for_sympy(ast::AST* node) { + static std::string to_nmodl_for_sympy(ast::Ast* node) { return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } @@ -136,7 +145,7 @@ class SympySolverVisitor: public AstVisitor { /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; - // optionally do CSE (common subexpression elimination) for sparse solver + /// optionally do CSE (common subexpression elimination) for sparse solver bool elimination; /// max number of state vars allowed for small system linear solver @@ -162,4 +171,7 @@ class SympySolverVisitor: public AstVisitor { void visit_program(ast::Program* node) override; }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index c6c268e319..739ad9915b 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -14,6 +14,7 @@ namespace nmodl { +namespace visitor { using symtab::Symbol; using symtab::syminfo::NmodlType; @@ -68,7 +69,7 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { /// if so we have to return to avoid duplicate definition error. if (property == NmodlType::range_var || property == NmodlType::nonspecific_cur_var) { auto s = modsymtab->lookup(name); - if (s && s->has_properties(NmodlType::nonspecific_cur_var | NmodlType::range_var)) { + if (s && s->has_any_property(NmodlType::nonspecific_cur_var | NmodlType::range_var)) { s->add_property(property); return; } @@ -161,7 +162,7 @@ static void add_external_symbols(symtab::ModelSymbolTable* symtab) { } -void SymtabVisitor::setup_symbol_table(ast::AST* node, const std::string& name, bool is_global) { +void SymtabVisitor::setup_symbol_table(ast::Ast* node, const std::string& name, bool is_global) { /// entering into new nmodl block auto symtab = modsymtab->enter_scope(name, node, is_global, node->get_symbol_table()); @@ -221,7 +222,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(ast::Node* node, const s /** * Visit table statement and update symbol in symbol table * - * @todo : we assume table statement follows variable declaration + * @todo we assume table statement follows variable declaration */ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { auto update_symbol = [this](const ast::NameVector& variables, NmodlType property, int num_values) { @@ -239,5 +240,5 @@ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { update_symbol(node->get_depend_vars(), NmodlType::table_dependent_var, num_values); } - +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 519d016fdd..5c6852705c 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -11,6 +11,7 @@ namespace nmodl { +namespace visitor { /// rename matching variable void VarUsageVisitor::visit_name(ast::Name* node) { @@ -27,4 +28,5 @@ bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { return used; } +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index 2607efa35b..f65ee8ae87 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::VarUsageVisitor + */ + #include <string> #include "ast/ast.hpp" @@ -14,12 +19,18 @@ namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class VarUsageVisitor * \brief Check if variable is used in given block * - * \todo : check if macro is considered as variable + * \todo check if macro is considered as variable */ class VarUsageVisitor: public AstVisitor { @@ -36,4 +47,7 @@ class VarUsageVisitor: public AstVisitor { virtual void visit_name(ast::Name* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 3592bce0a8..9a8882fe58 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -10,7 +10,7 @@ namespace nmodl { - +namespace visitor { void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { if (node->get_statements().empty()) { @@ -47,11 +47,11 @@ void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { bool rename_plausible = false; auto new_name = name; - if (name.find(local_prefix) == 0) { + if (name.find(LOCAL_PREFIX) == 0) { new_name.erase(0, 2); rename_plausible = true; } - if (name.find(range_prefix) == 0) { + if (name.find(RANGE_PREFIX) == 0) { new_name.erase(0, 3); rename_plausible = true; } @@ -84,4 +84,5 @@ void VerbatimVarRenameVisitor::visit_verbatim(ast::Verbatim* node) { statement->set(result); } +} // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index 23a60a1c34..62291ae111 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::VerbatimVarRenameVisitor + */ + #include <stack> #include <string> @@ -14,6 +19,12 @@ #include "visitors/ast_visitor.hpp" namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class VerbatimVarRenameVisitor @@ -21,20 +32,18 @@ namespace nmodl { * * Verbatim blocks in NMODL use different names for local * and range variables: - * - * - if local variable is xx then translated name of variable - * in C file is _lxx - * - if range (or any other global) variable is xx then translated - * name of the variable is _p_xx + * - if local variable is `xx` then translated name of variable + * in C file is `_lxx` + * - if range (or any other global) variable is `xx` then translated + * name of the variable is `_p_xx` * * This naming convention is based on NEURON code generation convention. * As part of this pass, we revert such usages of the variable to original * names. We do this only if variable is present in symbol table. * - * \todo : check if symbol table lookup is ok or there are cases where this - * could be error prone. + * \todo Check if symbol table lookup is ok or there are cases where this + * could be error prone. */ - class VerbatimVarRenameVisitor: public AstVisitor { private: /// non-null symbol table in the scope hierarchy @@ -44,10 +53,10 @@ class VerbatimVarRenameVisitor: public AstVisitor { std::stack<symtab::SymbolTable*> symtab_stack; /// prefix used for local variable - const std::string local_prefix = "_l"; + const std::string LOCAL_PREFIX = "_l"; /// prefix used for range variables - const std::string range_prefix = "_p_"; + const std::string RANGE_PREFIX = "_p_"; std::string rename_variable(std::string); @@ -58,4 +67,7 @@ class VerbatimVarRenameVisitor: public AstVisitor { virtual void visit_statement_block(ast::StatementBlock* node) override; }; +/** @} */ // end of visitor_classes + +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 82b0e30a49..c81ce2c540 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -10,6 +10,7 @@ namespace nmodl { +namespace visitor { void VerbatimVisitor::visit_verbatim(ast::Verbatim* node) { std::string block; @@ -26,4 +27,5 @@ void VerbatimVisitor::visit_verbatim(ast::Verbatim* node) { blocks.push_back(block); } +} // namespace visitor } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 030cda4115..c57527b1b5 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -7,16 +7,27 @@ #pragma once +/** + * \file + * \brief \copybrief nmodl::visitor::VerbatimVisitor + */ + #include <vector> #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ /** * \class VerbatimVisitor - * \brief Visitor for verbatim blocks of AST + * \brief %Visitor for verbatim blocks of AST * * This is simple example of visitor that uses base AstVisitor * interface. We override visitVerbatim method and store all @@ -24,7 +35,6 @@ namespace nmodl { * generating report of all verbatim blocks from all mod files * in ModelDB. */ - class VerbatimVisitor: public AstVisitor { private: /// flag to enable/disable printing blocks as we visit them @@ -47,4 +57,7 @@ class VerbatimVisitor: public AstVisitor { } }; -} // namespace nmodl \ No newline at end of file +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index b5718755de..d5143b485e 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -18,6 +18,7 @@ namespace nmodl { +namespace visitor { using namespace ast; using symtab::syminfo::NmodlType; @@ -82,8 +83,7 @@ LocalVar* add_local_variable(StatementBlock* node, const std::string& varname, i std::shared_ptr<Statement> create_statement(const std::string& code_statement) { nmodl::parser::NmodlDriver driver; auto nmodl_text = "PROCEDURE dummy() { " + code_statement + " }"; - driver.parse_string(nmodl_text); - auto ast = driver.ast(); + auto ast = driver.parse_string(nmodl_text); auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); auto statement = std::shared_ptr<Statement>( procedure->get_statement_block()->get_statements()[0]->clone()); @@ -105,8 +105,7 @@ std::shared_ptr<StatementBlock> create_statement_block( nmodl_text += statement + "\n"; } nmodl_text += "}"; - driver.parse_string(nmodl_text); - auto ast = driver.ast(); + auto ast = driver.parse_string(nmodl_text); auto procedure = std::dynamic_pointer_cast<ProcedureBlock>(ast->blocks[0]); auto statement_block = std::shared_ptr<StatementBlock>( procedure->get_statement_block()->clone()); @@ -117,7 +116,8 @@ std::shared_ptr<StatementBlock> create_statement_block( void remove_statements_from_block(ast::StatementBlock* block, const std::set<ast::Node*> statements) { auto& statement_vec = block->statements; - statement_vec.erase(std::remove_if(statement_vec.begin(), statement_vec.end(), + statement_vec.erase(std::remove_if(statement_vec.begin(), + statement_vec.end(), [&statements](std::shared_ptr<ast::Statement>& s) { return statements.find(s.get()) != statements.end(); }), @@ -149,7 +149,7 @@ std::set<std::string> get_global_vars(Program* node) { } -bool calls_function(ast::AST* node, const std::string& name) { +bool calls_function(ast::Ast* node, const std::string& name) { auto lv = AstLookupVisitor(ast::AstNodeType::FUNCTION_CALL); for (const auto& f: lv.lookup(node)) { if (std::dynamic_pointer_cast<ast::FunctionCall>(f)->get_node_name() == name) { @@ -159,17 +159,20 @@ bool calls_function(ast::AST* node, const std::string& name) { return false; } +} // namespace visitor -std::string to_nmodl(ast::AST* node, const std::set<ast::AstNodeType>& exclude_types) { + +std::string to_nmodl(ast::Ast* node, const std::set<ast::AstNodeType>& exclude_types) { std::stringstream stream; - NmodlPrintVisitor v(stream, exclude_types); + visitor::NmodlPrintVisitor v(stream, exclude_types); node->accept(&v); return stream.str(); } -std::string to_json(ast::AST* node, bool compact, bool expand) { + +std::string to_json(ast::Ast* node, bool compact, bool expand) { std::stringstream stream; - JSONVisitor v(stream); + visitor::JSONVisitor v(stream); v.compact_json(compact); v.expand_keys(expand); node->accept(&v); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index dad012cdad..ccd0518bb8 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \file + * \brief Utility functions for visitors implementation + */ + #include <map> #include <set> #include <string> @@ -14,56 +19,59 @@ #include "ast/ast.hpp" namespace nmodl { +namespace visitor { -/** Return new name variable by appending "_suffix_COUNT" where COUNT is number - * of times the given variable is already used. - */ +/// Return new name variable by appending `_suffix_COUNT` where `COUNT` is +/// number of times the given variable is already used. std::string get_new_name(const std::string& name, const std::string& suffix, std::map<std::string, int>& variables); -/** Return pointer to local statement in the given block, otherwise nullptr */ +/// Return pointer to local statement in the given block, otherwise nullptr ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); -/** Add empty local statement to given block if already doesn't exist */ +/// Add empty local statement to given block if already doesn't exist void add_local_statement(ast::StatementBlock* node); -/** Add new local variable to the block */ +/// Add new local variable to the block ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); ast::LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname, int dim); -/** Create ast statement node from given code in string format */ +/// Create ast statement node from given code in string format std::shared_ptr<ast::Statement> create_statement(const std::string& code_statement); -/** Create ast statement block node from given code in string format */ +/// Create ast statement block node from given code in string format std::shared_ptr<ast::StatementBlock> create_statement_block( const std::vector<std::string>& code_statements); -/** Remove statements from given statement block if they exist */ +/// Remove statements from given statement block if they exist void remove_statements_from_block(ast::StatementBlock* block, const std::set<ast::Node*> statements); -/** Return set of strings with the names of all global variables */ +/// Return set of strings with the names of all global variables std::set<std::string> get_global_vars(ast::Program* node); -/** Checks whether block contains a call to a perticular function */ -bool calls_function(ast::AST* node, const std::string& name); +/// Checks whether block contains a call to a perticular function +bool calls_function(ast::Ast* node, const std::string& name); + +} // namespace visitor + +/// Given AST node, return the NMODL string representation +std::string to_nmodl(ast::Ast* node, const std::set<ast::AstNodeType>& exclude_types = {}); -/** Given AST node, return the NMODL string representation */ -std::string to_nmodl(ast::AST* node, const std::set<ast::AstNodeType>& exclude_types = {}); +/// Given AST node, return the JSON string representation +std::string to_json(ast::Ast* node, bool compact = false, bool expand = false); -/** Given AST node, return the JSON string representation */ -std::string to_json(ast::AST* node, bool compact = false, bool expand = false); } // namespace nmodl diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 61510a0bdd..deacc1bfe8 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -31,10 +31,10 @@ add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) -target_link_libraries(testmodtoken lexer) -target_link_libraries(testlexer lexer) -target_link_libraries(testparser lexer test_util) -target_link_libraries(testvisitor symtab visitor lexer util test_util printer) +target_link_libraries(testmodtoken lexer util) +target_link_libraries(testlexer lexer util) +target_link_libraries(testparser lexer test_util util) +target_link_libraries(testvisitor visitor symtab lexer util test_util printer) target_link_libraries(testprinter printer) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 6a4196a742..74d6e72652 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -15,13 +15,15 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.hpp" +using namespace nmodl::test_utils; + //============================================================================= // Parser tests //============================================================================= bool is_valid_construct(const std::string& construct) { nmodl::parser::NmodlDriver driver; - return driver.parse_string(construct); + return driver.parse_string(construct) != nullptr; } diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 8ea7b2e3c9..07ba940254 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -12,10 +12,12 @@ #include "catch/catch.hpp" #include "printer/json_printer.hpp" +using nmodl::printer::JSONPrinter; + TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Stringstream test 1") { std::stringstream ss; - nmodl::JSONPrinter p(ss); + JSONPrinter p(ss); p.compact_json(true); p.push_block("A"); @@ -29,7 +31,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Stringstream test 2") { std::stringstream ss; - nmodl::JSONPrinter p(ss); + JSONPrinter p(ss); p.compact_json(true); p.push_block("A"); @@ -47,7 +49,7 @@ TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { SECTION("Test with nodes as separate tags") { std::stringstream ss; - nmodl::JSONPrinter p(ss); + JSONPrinter p(ss); p.compact_json(true); p.expand_keys(true); diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/pybind/conftest.py index bb0d27e83f..9aee8aa936 100644 --- a/test/nmodl/transpiler/pybind/conftest.py +++ b/test/nmodl/transpiler/pybind/conftest.py @@ -34,4 +34,4 @@ def ch_ast(): d = NmodlDriver() d.parse_string(CHANNEL) - return d.ast() + return d.get_ast() diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py index 274d715ebb..f487394c0b 100644 --- a/test/nmodl/transpiler/pybind/test_ast.py +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -9,7 +9,7 @@ from nmodl.dsl import ast, visitor import pytest -class TestAST(object): +class TestAst(object): def test_empty_program(self): pnode = ast.Program() assert str(pnode) == '{"Program":[]}' diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py index 0785ad173f..7afe21527f 100644 --- a/test/nmodl/transpiler/pybind/test_symtab.py +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -20,8 +20,8 @@ def test_symtab(ch_ast): mInf = s.lookup('mInf') assert mInf is not None assert mInf.get_name() == "mInf" - assert mInf.has_properties(symtab.NmodlType.range_var) is True - assert mInf.has_properties(symtab.NmodlType.local_var) is False + assert mInf.has_any_property(symtab.NmodlType.range_var) is True + assert mInf.has_any_property(symtab.NmodlType.local_var) is False variables = s.get_variables_with_properties(symtab.NmodlType.range_var, True) assert len(variables) == 2 diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 7f763fbcd9..42d96b9f38 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -100,19 +100,19 @@ SCENARIO("Symbol operations") { WHEN("added external property") { symbol.add_property(NmodlType::extern_neuron_variable); THEN("symbol becomes external") { - REQUIRE(symbol.is_external_symbol_only() == true); + REQUIRE(symbol.is_external_variable() == true); } } WHEN("added multiple properties to symbol") { symbol.add_property(property1); symbol.add_property(property2); THEN("symbol has multiple properties") { - REQUIRE(symbol.has_properties(property1) == true); + REQUIRE(symbol.has_any_property(property1) == true); - REQUIRE(symbol.has_properties(property3) == false); + REQUIRE(symbol.has_any_property(property3) == false); symbol.add_property(property3); - REQUIRE(symbol.has_properties(property3) == true); + REQUIRE(symbol.has_any_property(property3) == true); auto property = property1 | property2; REQUIRE(symbol.has_all_properties(property) == true); @@ -130,9 +130,9 @@ SCENARIO("Symbol operations") { WHEN("combined properties") { NmodlType property = NmodlType::factor_def | NmodlType::global_var; THEN("symbol has union of all properties") { - REQUIRE(symbol.has_properties(property) == false); + REQUIRE(symbol.has_any_property(property) == false); symbol.add_properties(property); - REQUIRE(symbol.has_properties(property) == true); + REQUIRE(symbol.has_any_property(property) == true); property |= symbol.get_properties(); REQUIRE(symbol.get_properties() == property); } @@ -154,7 +154,6 @@ SCENARIO("Symbol table operations") { THEN("all members are initialized") { REQUIRE(table->under_global_scope()); REQUIRE_THAT(table->name(), Catch::Contains("Na")); - REQUIRE_THAT(table->type(), Catch::Contains("Program")); REQUIRE_THAT(table->get_parent_table_name(), Catch::Contains("None")); REQUIRE_THAT(table->position(), Catch::Contains("UNKNOWN")); } diff --git a/test/nmodl/transpiler/units/parser.cpp b/test/nmodl/transpiler/units/parser.cpp index d1850fe7e9..b21d164798 100644 --- a/test/nmodl/transpiler/units/parser.cpp +++ b/test/nmodl/transpiler/units/parser.cpp @@ -21,6 +21,8 @@ // Parser tests //============================================================================= +using namespace nmodl::test_utils; + // Driver is defined as global to store all the units inserted to it and to be // able to define complex units based on base units nmodl::parser::UnitDriver driver; diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 95fbfeac84..66ccd03535 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -7,42 +7,54 @@ #include "test/utils/nmodl_constructs.hpp" -/** Guidelines for adding nmodl text constructs +namespace nmodl { +/// custom type to represent nmodl construct for testing +namespace test_utils { + +/** + * Guidelines for adding nmodl text constructs * * As nmodl constructs are used to for testing ast to nmodl transformations, * consider following points: - * * - Leading whitespaces or empty lines are removed - * * - Use string literal to define nmodl text * When ast is transformed back to nmodl, each statement has newline. * Hence for easy comparison, input nmodl should be null terminated. * One way to use format: - - R"( - TITLE nmodl title - )" - + * + * \code + * R"( + * TITLE nmodl title + * )" + * \endcode + * * - Do not use extra spaces (even though it's valid) - - LOCAL a,b - - instead of - - LOCAL a, b, c - + * + * \code + * LOCAL a,b + * \endcode + * + * instead of + * + * \code + * LOCAL a, b, c + * \endcode + * * - Use well indented blocks - - NEURON { - RANGE x - } - - instead of - - NEURON { - RANGE x - } - + * + * \code + * NEURON { + * RANGE x + * } + * \endcode + * + * instead of + * + * \code + * NEURON { + * RANGE x + * } + * \endcode * * If nmodl transformation is different from input, third argument could be * provided with the expected nmodl. @@ -1772,3 +1784,6 @@ std::vector<DiffEqTestCase> diff_eq_constructs{ // clang-format on }; + +} // namespace test_utils +} // namespace nmodl diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.hpp b/test/nmodl/transpiler/utils/nmodl_constructs.hpp index 7b74ccace1..8e64f64b15 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.hpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.hpp @@ -11,7 +11,10 @@ #include <string> #include <vector> -/// represent nmodl construct test +namespace nmodl { +namespace test_utils { + +/// represent nmodl test construct struct NmodlTestCase { /// name of the test std::string name; @@ -37,7 +40,7 @@ struct NmodlTestCase { , output(output) {} }; -/// represent differential equation test +/// represent differential equation test construct struct DiffEqTestCase { /// name of the mod file std::string name; @@ -54,4 +57,7 @@ struct DiffEqTestCase { extern std::map<std::string, NmodlTestCase> nmdol_invalid_constructs; extern std::map<std::string, NmodlTestCase> nmodl_valid_constructs; -extern std::vector<DiffEqTestCase> diff_eq_constructs; \ No newline at end of file +extern std::vector<DiffEqTestCase> diff_eq_constructs; + +} // namespace test_utils +} // namespace nmodl diff --git a/test/nmodl/transpiler/utils/test_utils.cpp b/test/nmodl/transpiler/utils/test_utils.cpp index e7669516b8..6dcadb303e 100644 --- a/test/nmodl/transpiler/utils/test_utils.cpp +++ b/test/nmodl/transpiler/utils/test_utils.cpp @@ -7,6 +7,9 @@ #include "utils/string_utils.hpp" +namespace nmodl { +namespace test_utils { + int count_leading_spaces(std::string text) { int length = text.size(); nmodl::stringutils::ltrim(text); @@ -73,3 +76,6 @@ std::string reindent_text(const std::string& text) { } return indented_text; } + +} // namespace test_utils +} // namespace nmodl diff --git a/test/nmodl/transpiler/utils/test_utils.hpp b/test/nmodl/transpiler/utils/test_utils.hpp index 8025e4d5d7..70a7647587 100644 --- a/test/nmodl/transpiler/utils/test_utils.hpp +++ b/test/nmodl/transpiler/utils/test_utils.hpp @@ -7,4 +7,10 @@ #pragma once -std::string reindent_text(const std::string& text); \ No newline at end of file +namespace nmodl { +namespace test_utils { + +std::string reindent_text(const std::string& text); + +} // namespace test_utils +} // namespace nmodl \ No newline at end of file diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index fa07384909..4be517fcf8 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -16,7 +16,6 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" -#include "visitors/cnexp_solve_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" @@ -26,6 +25,7 @@ #include "visitors/localize_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/rename_visitor.hpp" @@ -40,6 +40,9 @@ using json = nlohmann::json; using namespace nmodl; +using namespace visitor; +using namespace test_utils; + using ast::AstNodeType; using nmodl::parser::NmodlDriver; using symtab::syminfo::NmodlType; @@ -59,8 +62,7 @@ int main(int argc, char* argv[]) { std::vector<std::string> run_verbatim_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); VerbatimVisitor v; v.visit_program(ast.get()); @@ -92,8 +94,7 @@ TEST_CASE("Verbatim Visitor") { std::string run_json_visitor(const std::string& text, bool compact = false) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); return to_json(ast.get(), compact); } @@ -193,8 +194,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { )"; NmodlDriver driver; - driver.parse_string(nmodl_text); - auto ast = driver.ast(); + auto ast = driver.parse_string(nmodl_text); WHEN("Symbol table generator pass runs") { SymtabVisitor v; @@ -203,19 +203,19 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { THEN("Can lookup for defined variables") { auto symbol = symtab->lookup("m"); - REQUIRE(symbol->has_properties(NmodlType::dependent_def)); - REQUIRE_FALSE(symbol->has_properties(NmodlType::local_var)); + REQUIRE(symbol->has_any_property(NmodlType::dependent_def)); + REQUIRE_FALSE(symbol->has_any_property(NmodlType::local_var)); symbol = symtab->lookup("gNaTs2_tbar"); - REQUIRE(symbol->has_properties(NmodlType::param_assign)); - REQUIRE(symbol->has_properties(NmodlType::range_var)); + REQUIRE(symbol->has_any_property(NmodlType::param_assign)); + REQUIRE(symbol->has_any_property(NmodlType::range_var)); symbol = symtab->lookup("ena"); - REQUIRE(symbol->has_properties(NmodlType::read_ion_var)); + REQUIRE(symbol->has_any_property(NmodlType::read_ion_var)); } THEN("Can lookup for defined functions") { auto symbol = symtab->lookup("hBetaf"); - REQUIRE(symbol->has_properties(NmodlType::function_block)); + REQUIRE(symbol->has_any_property(NmodlType::function_block)); } THEN("Non existent variable lookup returns nullptr") { REQUIRE(symtab->lookup("xyz") == nullptr); @@ -275,8 +275,7 @@ SCENARIO("Symbol table generation and Perf stat visitor pass") { std::string run_nmodl_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); std::stringstream stream; NmodlPrintVisitor v(stream); @@ -305,8 +304,7 @@ SCENARIO("Test for AST back to NMODL transformation") { std::string run_var_rename_visitor(const std::string& text, std::vector<std::pair<std::string, std::string>> variables) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); { for (const auto& variable: variables) { RenameVisitor v(variable.first, variable.second); @@ -413,8 +411,7 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor") { std::string run_local_var_rename_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); { SymtabVisitor v; @@ -666,8 +663,7 @@ SCENARIO("Presence of local variable in verbatim block") { std::string run_inline_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); { SymtabVisitor v; @@ -1263,8 +1259,7 @@ SCENARIO("Procedure inlining handles local-global name conflict") { std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string& variable) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); SymtabVisitor().visit_program(ast.get()); InlineVisitor().visit_program(ast.get()); @@ -1564,8 +1559,7 @@ SCENARIO("Running defuse analyzer") { std::string run_localize_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); { SymtabVisitor v1; @@ -1777,11 +1771,10 @@ SCENARIO("Localizer test with multiple global blocks") { std::string run_cnexp_solve_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); SymtabVisitor().visit_program(ast.get()); - CnexpSolveVisitor().visit_program(ast.get()); + NeuronSolveVisitor().visit_program(ast.get()); std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); return stream.str(); @@ -1912,10 +1905,9 @@ SCENARIO("CnexpSolver visitor solving ODEs") { std::string run_solve_block_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); SymtabVisitor().visit_program(ast.get()); - CnexpSolveVisitor().visit_program(ast.get()); + NeuronSolveVisitor().visit_program(ast.get()); SolveBlockVisitor().visit_program(ast.get()); std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); @@ -2009,8 +2001,7 @@ TEST_CASE("SolveBlock visitor") { void run_visitor_passes(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); { SymtabVisitor v1; InlineVisitor v2; @@ -2052,7 +2043,7 @@ SCENARIO("Running visitor passes multiple time") { // Ast lookup visitor tests //============================================================================= -std::vector<std::shared_ptr<ast::AST>> run_lookup_visitor(ast::Program* node, +std::vector<std::shared_ptr<ast::Ast>> run_lookup_visitor(ast::Program* node, std::vector<AstNodeType>& types) { AstLookupVisitor v; return v.lookup(node, types); @@ -2061,8 +2052,7 @@ std::vector<std::shared_ptr<ast::AST>> run_lookup_visitor(ast::Program* node, SCENARIO("Searching for ast nodes using AstLookupVisitor") { auto to_ast = [](const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - return driver.ast(); + return driver.parse_string(text); }; GIVEN("A mod file with nodes of type NEURON, RANGE, BinaryExpression") { @@ -2138,8 +2128,7 @@ std::vector<std::string> run_kinetic_block_visitor( // construct AST from text including KINETIC block(s) NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); // construct symbol table from AST SymtabVisitor().visit_program(ast.get()); @@ -2625,8 +2614,7 @@ std::vector<std::string> run_sympy_solver_visitor( // construct AST from text NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); // construct symbol table from AST SymtabVisitor().visit_program(ast.get()); @@ -2884,8 +2872,7 @@ SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { )"; // construct AST from text NmodlDriver driver; - driver.parse_string(nmodl_text); - auto ast = driver.ast(); + auto ast = driver.parse_string(nmodl_text); // construct symbol table from AST SymtabVisitor().visit_program(ast.get()); @@ -2939,8 +2926,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("SympySolver correctly inserts ode to block") { CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } @@ -2988,10 +2975,10 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("Construct & solve linear system for backwards Euler") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); - auto result_cse = run_sympy_solver_visitor(nmodl_text, true, true, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + auto result_cse = + run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); REQUIRE(result_cse[0] == reindent_text(expected_cse_result)); } @@ -3019,8 +3006,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } @@ -3048,8 +3035,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } @@ -3080,8 +3067,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } @@ -3119,8 +3106,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("Construct newton solve block") { CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } @@ -3175,8 +3162,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] })"; THEN("Construct newton solve block") { CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } @@ -3248,8 +3235,8 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] } })"; THEN("Construct newton solve block") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); CAPTURE(nmodl_text); REQUIRE(result[0] == reindent_text(expected_result_0)); REQUIRE(result[1] == reindent_text(expected_result_1)); @@ -3265,8 +3252,7 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] std::string run_sympy_conductance_visitor(const std::string& text) { // construct AST from text NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); // construct symbol table from AST SymtabVisitor(false).visit_program(ast.get()); @@ -3290,8 +3276,7 @@ std::string run_sympy_conductance_visitor(const std::string& text) { std::string breakpoint_to_nmodl(const std::string& text) { // construct AST from text NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); // construct symbol table from AST SymtabVisitor v_symtab; @@ -4194,8 +4179,7 @@ SCENARIO("Sympy specific AST to NMODL conversion") { THEN("to_nmodl can ignore all units") { auto input = reindent_text(nmodl); NmodlDriver driver; - driver.parse_string(input); - auto ast = driver.ast(); + auto ast = driver.parse_string(input); auto result = to_nmodl(ast.get(), {AstNodeType::UNIT}); REQUIRE(result == reindent_text(expected)); } @@ -4209,8 +4193,7 @@ SCENARIO("Sympy specific AST to NMODL conversion") { std::string run_constant_folding_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); SymtabVisitor().visit_program(ast.get()); ConstantFolderVisitor().visit_program(ast.get()); @@ -4366,8 +4349,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { x = 5 })"; THEN("solve analytically") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4384,8 +4367,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { x = 0.5/a })"; THEN("solve analytically") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4404,8 +4387,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { y = a })"; THEN("solve analytically") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4429,8 +4412,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { })"; THEN("solve analytically, insert in correct location") { CAPTURE(reindent_text(nmodl_text)); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4454,8 +4437,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { })"; THEN("solve analytically, insert in correct location") { CAPTURE(reindent_text(nmodl_text)); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4476,8 +4459,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { z = pow(a, 2)*b*(c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-(1*b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) })"; THEN("solve analytically") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4498,8 +4481,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { s[2] = -2 })"; THEN("solve analytically") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4552,8 +4535,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { } })"; THEN("return matrix system to solve") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4766,8 +4749,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { } })"; THEN("return matrix system to be solved") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4801,8 +4784,8 @@ SCENARIO("NONLINEAR solve block (SympySolver Visitor)", "[sympy][nonlinear]") { } })"; THEN("return F & J for newton solver") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::NON_LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4845,8 +4828,8 @@ SCENARIO("NONLINEAR solve block (SympySolver Visitor)", "[sympy][nonlinear]") { } })"; THEN("return F & J for newton solver") { - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::NON_LINEAR_BLOCK); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } @@ -4858,8 +4841,7 @@ SCENARIO("NONLINEAR solve block (SympySolver Visitor)", "[sympy][nonlinear]") { std::string run_loop_unroll_visitor(const std::string& text) { NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); SymtabVisitor().visit_program(ast.get()); ConstantFolderVisitor().visit_program(ast.get()); @@ -5000,8 +4982,7 @@ std::vector<std::string> run_steadystate_visitor( std::vector<std::string> results; // construct AST from text NmodlDriver driver; - driver.parse_string(text); - auto ast = driver.ast(); + auto ast = driver.parse_string(text); // construct symbol table from AST SymtabVisitor().visit_program(ast.get()); From fbb1220b2f88369eed5f3fb277e0bfb64fce0102 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Thu, 25 Apr 2019 06:42:29 +0100 Subject: [PATCH 195/871] Add support for CONSERVE statement (BlueBrain/nmodl#148) In KineticBlock visitor: - CONSERVE statement now parsed & rewritten, e.g. - CONSERVE x + y + .. + z = expr - is rewritten as - CONSERVE z = (expr - x*x_comp - y*y_comp - ...)/z_comp - where x_comp is the COMPARTMENT factor for x (if any), etc. - z is the last state var on LHS of statement, which is the one NEURON replaces the ODE for - if it contains an array state var (without an index), this is expanded to a sum over all elements - e.g. CONSERVE X = 1 - where X is an array of size 4 - is expanded to CONSERVE X[0] + X[1] + X[2] + X[3] = 1 - then rewritten as CONSERVE X[3] = 1 - X[0] - X[1] - X[2] - this is an equivalent CONSERVE statement to the original one In SympySolver visitor: - now parses CONSERVE statements: must be of form CONSERVE x = expr - where "x" is a state var - and "expr" can be any expression, e.g. 1 - 3.2*y + a - otherwise CONSERVE statement is ignored - the Euler equation for the ODE for x is then replaced with this expression in sparse/derivimplicit solver - this reproduces the NEURON implementation of CONSERVE in kinetic blocks - also makes the STEADYSTATE solver match NEURON implementation Add test with 1 and 2 conserve statements NMODL Repo SHA: BlueBrain/nmodl@aeaf72cfcb1e3bb40010f19b6d394b0ce5978b4e --- src/nmodl/visitors/kinetic_block_visitor.cpp | 110 ++++++-- src/nmodl/visitors/kinetic_block_visitor.hpp | 32 ++- src/nmodl/visitors/sympy_solver_visitor.cpp | 59 ++++- src/nmodl/visitors/sympy_solver_visitor.hpp | 5 + test/nmodl/transpiler/visitor/visitor.cpp | 262 ++++++++++++++++++- 5 files changed, 427 insertions(+), 41 deletions(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index d1826b2764..0b08109469 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -62,16 +62,80 @@ void KineticBlockVisitor::process_reac_var(const std::string& varname, int count } } +void KineticBlockVisitor::process_conserve_reac_var(const std::string& varname, int count) { + // subtract previous term from both sides of equation + if (conserve_equation_statevar != "") { + if (conserve_equation_factor.empty()) { + conserve_equation_str += " - " + conserve_equation_statevar; + + } else { + conserve_equation_str += " - (" + conserve_equation_factor + " * " + + conserve_equation_statevar + ")"; + } + } + // construct new term + auto compartment_factor = compartment_factors[state_var_index[varname]]; + if (compartment_factor.empty()) { + if (count == 1) { + conserve_equation_factor = ""; + } else { + conserve_equation_factor = std::to_string(count); + } + } else { + conserve_equation_factor = compartment_factor + "*" + std::to_string(count); + } + // if new term is not a state var raise error + if (state_var_index.find(varname) == state_var_index.cend()) { + logger->error( + "KineticBlockVisitor :: Error : CONSERVE statement should only contain state vars " + "on LHS, but found {}", + varname); + } else { + conserve_equation_statevar = varname; + } +} + +std::shared_ptr<ast::Expression> create_expr(const std::string& str_expr) { + auto statement = create_statement("dummy = " + str_expr); + auto expr = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement)->get_expression(); + return std::dynamic_pointer_cast<ast::BinaryExpression>(expr)->get_rhs(); +} + void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { - // NOTE! CONSERVE statement "implicitly takes into account COMPARTMENT factors on LHS" + // rewrite CONSERVE statement in form x = ... + // where x was the last state var on LHS, and whose ODE should later be replaced with this + // equation note: CONSERVE statement "implicitly takes into account COMPARTMENT factors on LHS" + // this means that each state var on LHS must be multiplied by its compartment factor + // the RHS is just an expression, no compartment factors are taken into account // see p244 of NEURON book - // need to ensure that we do this in the same way: presumably need - // to either multiply or divide state vars on LHS of conserve statement by their COMPARTMENT - // factors? - logger->debug("KineticBlockVisitor :: CONSERVE statement ignored (for now): {} = {}", - to_nmodl(node->get_react().get()), - to_nmodl(node->get_expr().get())); - statements_to_remove.insert(node); + logger->debug("KineticBlockVisitor :: CONSERVE statement: {}", to_nmodl(node)); + conserve_equation_str = ""; + conserve_equation_statevar = ""; + conserve_equation_factor = ""; + + in_conserve_statement = true; + // construct equation to replace ODE in conserve_equation_str + node->visit_children(this); + in_conserve_statement = false; + + conserve_equation_str = to_nmodl(node->get_expr().get()) + conserve_equation_str; + if (!conserve_equation_factor.empty()) { + // divide by compartment factor of conserve_equation_statevar + conserve_equation_str = "(" + conserve_equation_str + ")/(" + conserve_equation_factor + + ")"; + } + + auto lhs = create_expr(conserve_equation_statevar); + // set react (lhs) of CONSERVE to the state variable whose ODE should be replaced + node->set_react(std::move(lhs)); + // set expr (rhs) of CONSERVE to the equation that should replace the ODE + auto rhs = create_expr(conserve_equation_str); + // note: this is still a valid (and equivalent) CONSERVE statement. + // later this block will become a DERIVATIVE block where it is no longer valid + // to have a CONSERVE statement, but it is parsed without issues, and + // the SympySolver will use to it replace the ODE (to replicate what neuron does) + node->set_expr(std::move(rhs)); + logger->debug("KineticBlockVisitor :: --> {}", to_nmodl(node)); } void KineticBlockVisitor::visit_compartment(ast::Compartment* node) { @@ -110,13 +174,22 @@ void KineticBlockVisitor::visit_reaction_operator(ast::ReactionOperator* node) { } void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName* node) { - // react_var_name node contains var_name and integer - // var_name is the state variable which we convert to an index - // integer is the value to be added to the stochiometric matrix at this index + // ReactVarName node contains a VarName and an Integer + // the VarName is the state variable which we convert to an index + // the Integer is the value to be added to the stoichiometric matrix at this index + auto varname = to_nmodl(node->get_name().get()); + int count = node->get_value() ? node->get_value()->eval() : 1; if (in_reaction_statement) { - auto varname = to_nmodl(node->get_name().get()); - int count = node->get_value() ? node->get_value()->eval() : 1; process_reac_var(varname, count); + } else if (in_conserve_statement) { + if (array_state_var_size.find(varname) != array_state_var_size.cend()) { + // state var is an array: need to sum over each element + for (int i = 0; i < array_state_var_size[varname]; ++i) { + process_conserve_reac_var(varname + "[" + std::to_string(i) + "]", count); + } + } else { + process_conserve_reac_var(varname, count); + } } } @@ -192,7 +265,7 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) non_state_var_fflux.emplace_back(); non_state_var_bflux.emplace_back(); - // add a row of zeros to the stochiometric matrices + // add a row of zeros to the stoichiometric matrices rate_eqs.nu_L.emplace_back(std::vector<int>(state_var_count, 0)); rate_eqs.nu_R.emplace_back(std::vector<int>(state_var_count, 0)); @@ -244,12 +317,6 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) ++i_statement; } -std::shared_ptr<ast::Expression> create_expr(const std::string& str_expr) { - auto statement = create_statement("dummy = " + str_expr); - auto expr = std::dynamic_pointer_cast<ast::ExpressionStatement>(statement)->get_expression(); - return std::dynamic_pointer_cast<ast::BinaryExpression>(expr)->get_rhs(); -} - void KineticBlockVisitor::visit_wrapped_expression(ast::WrappedExpression* node) { // If a wrapped expression contains a variable with name "f_flux" or "b_flux", // this variable should be replaced by the expression for the corresponding flux @@ -363,6 +430,7 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { // get state variables - assign an index to each state_var_index.clear(); + array_state_var_size.clear(); state_var.clear(); state_var_count = 0; if (auto symtab = node->get_symbol_table()) { @@ -370,6 +438,8 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { for (const auto& v: statevars) { std::string var_name = v->get_name(); if (v->is_array()) { + // CONSERVE statement needs to know this is an array state var, and its size: + array_state_var_size[var_name] = v->get_length(); // for array state vars we need to add each element of the array separately var_name += "["; for (int i = 0; i < v->get_length(); ++i) { diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 05669d1849..6310c37849 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -37,9 +37,12 @@ namespace visitor { * containing a system of ODEs that is equivalent to * the original set of reaction statements * - * Currently is only valid assuming the order - * of statements within the KINETIC block does not - * matter. Also does not yet support array variables. + * Note: assumes that the order of statements between the first and last + * reaction statement (those starting with "~") does not matter. + * + * If there is a CONSERVE statement it is rewritten in an equivalent + * form which is then be used by the SympySolver visitor to replace + * the ODE for the last state variable on the LHS of the CONSERVE statement * */ class KineticBlockVisitor: public AstVisitor { @@ -47,7 +50,10 @@ class KineticBlockVisitor: public AstVisitor { /// update stoichiometric matrices with reaction var term void process_reac_var(const std::string& varname, int count = 1); - /// stoichiometric matrices nu_L, nu_R + /// update CONSERVE statement with reaction var term + void process_conserve_reac_var(const std::string& varname, int count = 1); + + /// stochiometric matrices nu_L, nu_R /// forwards/backwards fluxes k_f, k_b /// (see kinetic_schemes.ipynb notebook for details) struct RateEqs { @@ -90,12 +96,28 @@ class KineticBlockVisitor: public AstVisitor { /// map from state variable to corresponding index std::map<std::string, int> state_var_index; + /// map from array state variable to its size (for summing over each element of any array state + /// vars in a CONSERVE statement) + std::map<std::string, int> array_state_var_size; + /// true if we are visiting a reaction statement bool in_reaction_statement = false; /// true if we are visiting the left hand side of reaction statement bool in_reaction_statement_lhs = false; + /// true if we are visiting a CONSERVE statement + bool in_conserve_statement = false; + + /// conserve statement equation as string + std::string conserve_equation_str; + + /// conserve statement: current state variable being processed + std::string conserve_equation_statevar; + + /// conserve statement: current state var multiplicative factor being processed + std::string conserve_equation_factor; + /// current statement index int i_statement = 0; @@ -125,4 +147,4 @@ class KineticBlockVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index b39d48be37..dca61a285d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -31,6 +31,7 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { last_expression_statement = nullptr; block_with_expression_statements = nullptr; eq_system_is_valid = true; + conserve_equation.clear(); // get set of local block vars & global vars vars = global_vars; if (auto symtab = node->get_statement_block()->get_symbol_table()) { @@ -436,6 +437,29 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { } } +void SympySolverVisitor::visit_conserve(ast::Conserve* node) { + // Replace ODE for state variable on LHS of CONSERVE statement with + // algebraic expression on RHS (see p244 of NEURON book) + logger->debug("SympySolverVisitor :: CONSERVE statement: {}", to_nmodl(node)); + expression_statements.insert(node); + std::string conserve_equation_statevar; + if (node->get_react()->is_react_var_name()) { + conserve_equation_statevar = node->get_react()->get_node_name(); + } + if (std::find(all_state_vars.cbegin(), all_state_vars.cend(), conserve_equation_statevar) == + all_state_vars.cend()) { + logger->error( + "SympySolverVisitor :: Invalid CONSERVE statement for DERIVATIVE block, LHS should be " + "a state variable, instead found: {}. Ignoring CONSERVE statement", + to_nmodl(node->get_react().get())); + return; + } + auto conserve_equation_str = to_nmodl_for_sympy(node->get_expr().get()); + logger->debug("SympySolverVisitor :: --> replace ODE for state var {} with equation {}", + conserve_equation_statevar, conserve_equation_str); + conserve_equation[conserve_equation_statevar] = conserve_equation_str; +} + void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { /// clear information from previous block, get global vars + block local vars init_block_data(node); @@ -464,18 +488,29 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { x_array_index = stringutils::trim(x_prime_split[1]); x_array_index_i = "_" + x_array_index.substr(1, x_array_index.size() - 2); } - auto dxdt = stringutils::trim(split_eq[1]); - auto old_x = "old_" + x + x_array_index_i; // TODO: do this properly, - // check name is unique - // declare old_x - logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); - add_local_variable(block_with_expression_statements, old_x); - // assign old_x = x - pre_solve_statements.push_back(old_x + " = " + x + x_array_index); - // replace ODE with Euler equation - eq = x + x_array_index + " = " + old_x + " + " + codegen::naming::NTHREAD_DT_VARIABLE + - " * (" + dxdt + ")"; - logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); + std::string state_var_name = x + x_array_index; + auto var_eq_pair = conserve_equation.find(state_var_name); + if (var_eq_pair != conserve_equation.cend()) { + // replace the ODE for this state var with corresponding CONSERVE equation + eq = state_var_name + " = " + var_eq_pair->second; + logger->debug( + "SympySolverVisitor :: -> instead of Euler eq using CONSERVE equation: {} = {}", + state_var_name, var_eq_pair->second); + } else { + // no CONSERVE equation, construct Euler equation + auto dxdt = stringutils::trim(split_eq[1]); + auto old_x = "old_" + x + x_array_index_i; // TODO: do this properly, + // check name is unique + // declare old_x + logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); + add_local_variable(block_with_expression_statements, old_x); + // assign old_x = x + pre_solve_statements.push_back(old_x + " = " + x + x_array_index); + // replace ODE with Euler equation + eq = x + x_array_index + " = " + old_x + " + " + + codegen::naming::NTHREAD_DT_VARIABLE + " * (" + dxdt + ")"; + logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); + } } if (solve_method == codegen::naming::SPARSE_METHOD) { diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 9a7bd5a36e..3ab0af815d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -142,6 +142,10 @@ class SympySolverVisitor: public AstVisitor { /// vector of state vars used *in block* (in same order as all_state_vars) std::vector<std::string> state_vars; + /// map from state vars to the algebraic equation from CONSERVE statement that should replace + /// their ODE, if any + std::map<std::string, std::string> conserve_equation; + /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; @@ -161,6 +165,7 @@ class SympySolverVisitor: public AstVisitor { void visit_var_name(ast::VarName* node) override; void visit_diff_eq_expression(ast::DiffEqExpression* node) override; + void visit_conserve(ast::Conserve* node) override; void visit_derivative_block(ast::DerivativeBlock* node) override; void visit_lin_equation(ast::LinEquation* node) override; void visit_linear_block(ast::LinearBlock* node) override; diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp index 4be517fcf8..7e67b369f8 100644 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ b/test/nmodl/transpiler/visitor/visitor.cpp @@ -2280,10 +2280,36 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { })"; std::string output_nmodl_text = R"( DERIVATIVE states { + CONSERVE y = 1-x x' = (-1*(f(v)*x*y)) y' = (-1*(f(v)*x*y)) })"; - THEN("Convert to equivalent DERIVATIVE block (ignore CONSERVE for now)") { + THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 2 state vars, CONSERVE & COMPARTMENT") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + COMPARTMENT a { x } + COMPARTMENT b { y } + ~ x + y -> (f(v)) + CONSERVE x + y = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE y = (1-(a*1*x))/(b*1) + x' = ((-1*(f(v)*x*y)))/(a) + y' = ((-1*(f(v)*x*y)))/(b) + })"; + THEN( + "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement inc COMPARTMENT " + "factors") { auto result = run_kinetic_block_visitor(input_nmodl_text); CAPTURE(input_nmodl_text); REQUIRE(result[0] == reindent_text(output_nmodl_text)); @@ -2363,10 +2389,67 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { })"; std::string output_nmodl_text = R"( DERIVATIVE states { + CONSERVE y = 0-x x' = (-1*(a*x-b*y)) y' = (1*(a*x-b*y)) })"; - THEN("Convert to equivalent DERIVATIVE block (ignore CONSERVE for now)") { + THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + // array vars in CONSERVE statements are implicit sums over elements + // see p34 of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7812&rep=rep1&type=pdf + GIVEN("KINETIC block with array state vars, CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x[3] y + } + KINETIC states { + ~ x[0] <-> x[1] (a, b) + ~ x[2] <-> y (c, d) + CONSERVE y + x = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE x[2] = 1-y-x[0]-x[1] + x'[0] = (-1*(a*x[0]-b*x[1])) + x'[1] = (1*(a*x[0]-b*x[1])) + x'[2] = (-1*(c*x[2]-d*y)) + y' = (1*(c*x[2]-d*y)) + })"; + THEN( + "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " + "array elements, with last state var on LHS") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + // array vars in CONSERVE statements are implicit sums over elements + // see p34 of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7812&rep=rep1&type=pdf + GIVEN("KINETIC block with array state vars, re-ordered CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x[3] y + } + KINETIC states { + ~ x[0] <-> x[1] (a, b) + ~ x[2] <-> y (c, d) + CONSERVE x + y = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE y = 1-x[0]-x[1]-x[2] + x'[0] = (-1*(a*x[0]-b*x[1])) + x'[1] = (1*(a*x[0]-b*x[1])) + x'[2] = (-1*(c*x[2]-d*y)) + y' = (1*(c*x[2]-d*y)) + })"; + THEN( + "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " + "array elements, with last state var on LHS") { auto result = run_kinetic_block_visitor(input_nmodl_text); CAPTURE(input_nmodl_text); REQUIRE(result[0] == reindent_text(output_nmodl_text)); @@ -2392,6 +2475,36 @@ SCENARIO("KineticBlock visitor", "[kinetic]") { REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with two CONSERVE statements") { + std::string input_nmodl_text = R"( + STATE { + c1 o1 o2 p0 p1 + } + KINETIC ihkin { + evaluate_fct(v, cai) + ~ c1 <-> o1 (alpha, beta) + ~ p0 <-> p1 (k1ca, k2) + ~ o1 <-> o2 (k3p, k4) + CONSERVE p0+p1 = 1 + CONSERVE c1+o1+o2 = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE ihkin { + evaluate_fct(v, cai) + CONSERVE p1 = 1-p0 + CONSERVE o2 = 1-c1-o1 + c1' = (-1*(alpha*c1-beta*o1)) + o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) + o2' = (1*(k3p*o1-k4*o2)) + p0' = (-1*(k1ca*p0-k2*p1)) + p1' = (1*(k1ca*p0-k2*p1)) + })"; + THEN("Convert to equivalent DERIVATIVE block, re-order both CONSERVE statements") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with one reaction statement & 2 COMPARTMENT statements") { std::string input_nmodl_text = R"( STATE { @@ -3004,13 +3117,154 @@ SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit] mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) })"; - THEN("Construct & solver linear system") { + THEN("Construct & solve linear system") { + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block with ODES with sparse method, CONSERVE statement of form m = ...") { + std::string nmodl_text = R"( + STATE { + mc m + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + mc' = -a*mc + b*m + m' = a*mc - b*m + CONSERVE m = 1 - mc + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_mc + old_mc = mc + mc = (b*dt+old_mc)/(a*dt+b*dt+1) + m = (a*dt-old_mc+1)/(a*dt+b*dt+1) + })"; + THEN("Construct & solve linear system, replace ODE for m with rhs of CONSERVE statement") { + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN( + "Derivative block with ODES with sparse method, invalid CONSERVE statement of form m + mc " + "= ...") { + std::string nmodl_text = R"( + STATE { + mc m + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + mc' = -a*mc + b*m + m' = a*mc - b*m + CONSERVE m + mc = 1 + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_mc, old_m + old_mc = mc + old_m = m + mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) + m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) + })"; + THEN("Construct & solve linear system, ignore invalid CONSERVE statement") { CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result[0] == reindent_text(expected_result)); } } + GIVEN("Derivative block with ODES with sparse method, two CONSERVE statements") { + std::string nmodl_text = R"( + STATE { + c1 o1 o2 p0 p1 + } + BREAKPOINT { + SOLVE ihkin METHOD sparse + } + DERIVATIVE ihkin { + LOCAL alpha, beta, k3p, k4, k1ca, k2 + evaluate_fct(v, cai) + CONSERVE p1 = 1-p0 + CONSERVE o2 = 1-c1-o1 + c1' = (-1*(alpha*c1-beta*o1)) + o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) + o2' = (1*(k3p*o1-k4*o2)) + p0' = (-1*(k1ca*p0-k2*p1)) + p1' = (1*(k1ca*p0-k2*p1)) + })"; + std::string expected_result = R"( + DERIVATIVE ihkin { + EIGEN_LINEAR_SOLVE[5]{ + LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_p0 + }{ + evaluate_fct(v, cai) + old_c1 = c1 + old_o1 = o1 + old_p0 = p0 + }{ + X[0] = c1 + X[1] = o1 + X[2] = o2 + X[3] = p0 + X[4] = p1 + F[0] = -old_c1 + F[1] = -old_o1 + F[2] = -1 + F[3] = -old_p0 + F[4] = -1 + J[0] = -alpha*dt-1 + J[5] = beta*dt + J[10] = 0 + J[15] = 0 + J[20] = 0 + J[1] = alpha*dt + J[6] = -beta*dt-dt*k3p-1 + J[11] = dt*k4 + J[16] = 0 + J[21] = 0 + J[2] = -1 + J[7] = -1 + J[12] = -1 + J[17] = 0 + J[22] = 0 + J[3] = 0 + J[8] = 0 + J[13] = 0 + J[18] = -dt*k1ca-1 + J[23] = dt*k2 + J[4] = 0 + J[9] = 0 + J[14] = 0 + J[19] = -1 + J[24] = -1 + }{ + c1 = X[0] + o1 = X[1] + o2 = X[2] + p0 = X[3] + p1 = X[4] + }{ + } + })"; + THEN( + "Construct & solve linear system, replacing ODEs for p1 and o2 with CONSERVE statement " + "algebraic relations") { + CAPTURE(nmodl_text); + auto result = run_sympy_solver_visitor(nmodl_text, false, false, + AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } GIVEN("Derivative block including ODES with sparse method - single var in array") { std::string nmodl_text = R"( STATE { @@ -3978,7 +4232,7 @@ SCENARIO("SympyConductance visitor", "[sympy]") { tau_d_AMPA = 1.7 (ms) : IMPORTANT: tau_r < tau_d tau_r_NMDA = 0.29 (ms) : dual-exponential conductance profile tau_d_NMDA = 43 (ms) : IMPORTANT: tau_r < tau_d - Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) + Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) Dep = 100 (ms) : relaxation time constant from depression Fac = 10 (ms) : relaxation time constant from facilitation e = 0 (mV) : AMPA and NMDA reversal potential From 75306bff3cc3fd7b0fa70cb5d25f70d569d81a5c Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@gmail.com> Date: Fri, 26 Apr 2019 22:38:20 +0200 Subject: [PATCH 196/871] Update documentation for C code generator (BlueBrain/nmodl#154) - C and ISPC code generation backends are updated with basic doxygen documentation using hpc coding conventions - Todo : need to update OpenACC and CUDA backends NMODL Repo SHA: BlueBrain/nmodl@cead9e46d3359842eb5c29b2191b8ceec18bb3d8 --- docs/nmodl/transpiler/Doxyfile.in | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 280 ++--- src/nmodl/codegen/codegen_c_visitor.hpp | 1157 ++++++++++++++++---- src/nmodl/codegen/codegen_ispc_visitor.cpp | 8 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 4 +- 5 files changed, 1056 insertions(+), 395 deletions(-) diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index 192d31e419..043d580910 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -195,7 +195,7 @@ SHORT_NAMES = NO # description.) # The default value is: NO. -JAVADOC_AUTOBRIEF = NO +JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index bbffc00a45..bcc72dff6a 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -110,7 +110,9 @@ void CodegenCVisitor::visit_prime_name(PrimeName* node) { } -/// \todo : validate how @ is being handled in neuron implementation +/** + * \todo : Validate how @ is being handled in neuron implementation + */ void CodegenCVisitor::visit_var_name(VarName* node) { if (!codegen) { return; @@ -268,7 +270,7 @@ void CodegenCVisitor::visit_unary_operator(UnaryOperator* node) { /** - * Statement block is top level construct (for every nmodl block). + * \details Statement block is top level construct (for every nmodl block). * Sometime we want to analyse ast nodes even if code generation is * false. Hence we visit children even if code generation is false. */ @@ -312,9 +314,7 @@ void CodegenCVisitor::visit_verbatim(Verbatim* node) { /** - * Check if given statement needs to be skipped during code generation - * - * Certain statements like unit, comment, solve can/need to be skipped + * \details Certain statements like unit, comment, solve can/need to be skipped * during code generation. Note that solve block is wrapped in expression * statement and hence we have to check inner expression. It's also true * for the initial block defined inside net receive block. @@ -387,7 +387,7 @@ bool CodegenCVisitor::net_receive_required() { /** - * When floating point data type is not default (i.e. double) then we + * \details When floating point data type is not default (i.e. double) then we * have to copy old array to new type (for range variables). */ bool CodegenCVisitor::range_variable_setup_required() { @@ -433,9 +433,7 @@ int CodegenCVisitor::position_of_int_var(const std::string& name) { /** - * Convert double value to string - * - * We can directly use to_string method but if user specify 7.0 then it gets + * \details We can directly use to_string method but if user specify 7.0 then it gets * printed as 7 (as integer). To avoid this, we use below wrapper. But note * that there are still issues. For example, if 1.1 is not exactly represented * in floating point, then it gets printed as 1.0999999999999. May be better @@ -458,9 +456,7 @@ std::string CodegenCVisitor::float_to_string(float value) { /** - * Check if given statement needs semicolon at the end of statement - * - * Statements like if, else etc. don't need semicolon at the end. + * \details Statements like if, else etc. don't need semicolon at the end. * (Note that it's valid to have "extraneous" semicolon). Also, statement * block can appear as statement using expression statement which need to * be inspected. @@ -501,9 +497,7 @@ bool CodegenCVisitor::defined_method(const std::string& name) { /** - * Return "current" for variable name used in breakpoint block - * - * Current variable used in breakpoint block could be local variable. + * \details Current variable used in breakpoint block could be local variable. * In this case, neuron has already renamed the variable name by prepending * "_l". In our implementation, the variable could have been renamed by * one of the pass. And hence, we search all local variables and check if @@ -573,9 +567,7 @@ int CodegenCVisitor::int_variables_size() { /** - * For given block type, return statements for all read ion variables - * - * Depending upon the block type, we have to print read/write ion variables + * \details Depending upon the block type, we have to print read/write ion variables * during code generation. Depending on block/procedure being printed, this * method return statements as vector. As different code backends could have * different variable names, we rely on backend-specific read_ion_variable_name @@ -689,13 +681,13 @@ std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType /** - * Often top level verbatim blocks use variables with old names. + * \details Often top level verbatim blocks use variables with old names. * Here we process if we are processing verbatim block at global scope. */ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { const std::string& name = token; - /** + /* * If given token is procedure name and if it's defined * in the current mod file then it must be replaced */ @@ -703,9 +695,9 @@ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { return method_name(token); } - /** + /* * Check if token is commongly used variable name in - * verbatim block like nt, _threadargs etc. If so, replace + * verbatim block like nt, \c \_threadargs etc. If so, replace * it and return. */ auto new_name = replace_if_verbatim_variable(name); @@ -713,7 +705,7 @@ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { return get_variable_name(new_name, false); } - /** + /* * For top level verbatim blocks we shouldn't replace variable * names with Instance because arguments are provided from coreneuron * and they are missing inst. @@ -729,9 +721,7 @@ bool CodegenCVisitor::ion_variable_struct_required() { /** - * Check if variable is qualified as constant - * - * This can be override in the backend. For example, parameters can be constant + * \details This can be override in the backend. For example, parameters can be constant * except in INITIAL block where they are set to 0. As initial block is/can be * executed on c/cpu backend, gpu/cuda backend can mark the parameter as constnat. */ @@ -751,7 +741,7 @@ bool CodegenCVisitor::is_constant_variable(std::string name) { /** - * Once variables are populated, update index semantics to register with coreneuron + * \details Once variables are populated, update index semantics to register with coreneuron */ void CodegenCVisitor::update_index_semantics() { int index = 0; @@ -800,7 +790,7 @@ void CodegenCVisitor::update_index_semantics() { info.semantics.emplace_back(index++, naming::NET_SEND_SEMANTIC, 1); } - /** + /* * Number of semantics for watch is one greater than number of * actual watch statements in the mod file */ @@ -816,11 +806,8 @@ void CodegenCVisitor::update_index_semantics() { } -/** - * Return all floating point variables required for code generation - */ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { - /// sort with definition order + // sort with definition order auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { return first->get_definition_order() < second->get_definition_order(); }; @@ -829,7 +816,7 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { auto states = info.state_vars; states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); - /// each state variable has corresponding Dstate variable + // each state variable has corresponding Dstate variable for (auto& variable: states) { auto name = "D" + variable->get_name(); auto symbol = make_symbol(name); @@ -861,8 +848,6 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { /** - * Return all integer variables required for code generation - * * IndexVariableInfo has following constructor arguments: * - symbol * - is_vdata (false) @@ -940,7 +925,7 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { } /** - * Variables for watch statements : note that there is one extra variable + * \note Variables for watch statements : there is one extra variable * used in coreneuron compared to actual watch statements for compatibility * with neuron (which uses one extra Datum variable) */ @@ -954,9 +939,7 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { /** - * Return all ion write variables that require shadow vectors during code generation - * - * When we enable fine level parallelism at channel level, we have do updates + * \details When we enable fine level parallelism at channel level, we have do updates * to ion variables in atomic way. As cpus don't have atomic instructions in * simd loop, we have to use shadow vectors for every ion variables. Here * we return list of all such variables. @@ -983,9 +966,6 @@ std::vector<SymbolType> CodegenCVisitor::get_shadow_variables() { /* Routines must be overloaded in backend */ /****************************************************************************************/ -/** - * Print parameters - */ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { std::stringstream param_ss; for (auto iter = params.begin(); iter != params.end(); iter++) { @@ -1011,9 +991,6 @@ void CodegenCVisitor::print_channel_iteration_task_end() { } -/* - * Depending on the backend, print loop for tiling channel iterations - */ void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { // no tiling for cpu backend, just get loop bounds printer->add_line("int start = 0;"); @@ -1021,16 +998,13 @@ void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType type) } -/** - * End of tiled channel iteration block - */ void CodegenCVisitor::print_channel_iteration_tiling_block_end() { // backend specific, do nothing } /** - * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded + * \details Each kernel such as \c nrn\_init, \c nrn\_state and \c nrn\_cur could be offloaded * to accelerator. In this case, at very top level, we print pragma * for data present. For example: * @@ -1048,45 +1022,40 @@ void CodegenCVisitor::print_kernel_data_present_annotation_block_begin() { } -/** - * End of print_kernel_enter_data_begin - */ void CodegenCVisitor::print_kernel_data_present_annotation_block_end() { // backend specific, do nothing } /** - * Depending programming model and compiler, we print compiler hint + * \details Depending programming model and compiler, we print compiler hint * for parallelization. For example: * + * \code * #pragma ivdep * for(int id = 0; id < nodecount; id++) { * * #pragma acc parallel loop * for(int id = 0; id < nodecount; id++) { - * + * \endcode */ void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { printer->add_line("#pragma ivdep"); } -/// if reduction block in nrn_cur required bool CodegenCVisitor::nrn_cur_reduction_loop_required() { return channel_task_dependency_enabled() || info.point_process; } -/// if shadow vectors required + bool CodegenCVisitor::shadow_vector_setup_required() { return (channel_task_dependency_enabled() && !codegen_shadow_variables.empty()); } -/* - * Depending on the backend, print condition/loop for iterating over channels - * - * For CPU backend we iterate over all node counts. For cuda we use thread +/** + * \details For CPU backend we iterate over all node counts. For cuda we use thread * index to check if block needs to be executed or not. */ void CodegenCVisitor::print_channel_iteration_block_begin(BlockType type) { @@ -1372,8 +1341,6 @@ void CodegenCVisitor::print_top_verbatim_blocks() { /** - * Rename function arguments that have same name with default inbuilt arguments - * * \todo: issue with verbatim renaming. e.g. pattern.mod has info struct with * index variable. If we use "index" instead of "indexes" as default argument * then during verbatim replacement we don't know the index is which one. This @@ -1596,9 +1563,11 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" {}({});"_format(name, arguments)); } - /// todo : check_table_thread is called multiple times from coreneuron including - /// after finitialize. If we cleaup the instance then it will result in segfault - /// but if we don't then there is memory leak + /** + * \todo `check_table_thread` is called multiple times from coreneuron including + * after `finitialize`. If we cleaup the instance then it will result in segfault + * but if we don't then there is memory leak + */ printer->add_line(" // cleanup_instance(ml);"); printer->add_line("}"); } @@ -1646,7 +1615,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { auto name = node->get_node_name(); auto return_var = "ret_" + name; - /// first rename return variable name + // first rename return variable name auto block = node->get_statement_block().get(); RenameVisitor v(name, return_var); block->accept(&v); @@ -1759,7 +1728,9 @@ std::string CodegenCVisitor::param_ptr_qualifier() { } -/// @todo: figure out how to correctly handle qualifiers +/** + * @todo: figure out how to correctly handle qualifiers + */ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { auto params = ParamVector(); params.emplace_back("", "int", "", "id"); @@ -1795,10 +1766,6 @@ std::string CodegenCVisitor::external_method_parameters(bool table) { } -/** - * Function call arguments when function or procedure is external (i.e. - * not visible at nmodl level) - */ std::string CodegenCVisitor::nrn_thread_arguments() { if (ion_variable_struct_required()) { return "id, pnodecount, ionvar, data, indexes, thread, nt, v"; @@ -1806,6 +1773,7 @@ std::string CodegenCVisitor::nrn_thread_arguments() { return "id, pnodecount, data, indexes, thread, nt, v"; } + /** * Function call arguments when function or procedure is defined in the * same mod file itself @@ -1819,7 +1787,7 @@ std::string CodegenCVisitor::nrn_thread_internal_arguments() { /** - * Commonly used variables in the verbatim blocks and their corresponding + * Replace commonly used variables in the verbatim blocks into their corresponding * variable name in the new code generation backend. */ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { @@ -1827,8 +1795,10 @@ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { name = naming::VERBATIM_VARIABLES_MAPPING.at(name); } - /// if function is defined the same mod file then the arguments must - /// contain mechanism instance as well. + /** + * if function is defined the same mod file then the arguments must + * contain mechanism instance as well. + */ if (name == naming::THREAD_ARGS) { if (internal_method_call_encountered) { name = nrn_thread_internal_arguments(); @@ -1857,8 +1827,8 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { for (size_t i = 0; i < tokens.size(); i++) { auto token = tokens[i]; - /// check if we have function call in the verbatim block where - /// function is defined in the same mod file + // check if we have function call in the verbatim block where + // function is defined in the same mod file if (program_symtab->is_method_defined(token) && tokens[i + 1] == "(") { internal_method_call_encountered = true; } @@ -1917,20 +1887,16 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, * like ionic current contributions needs to be atomically updated. In this * case we first update current mechanism's shadow vector and then add statement * to queue that will be used in reduction queue. - * - * @param statement Statement that might require reduction - * @param type Type of the block - * @return Original statement is reduction requires otherwise original statement */ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& statement, BlockType type) { - /// when there is no operator or rhs then that statement doesn't need shadow update + // when there is no operator or rhs then that statement doesn't need shadow update if (statement.op.empty() && statement.rhs.empty()) { auto text = statement.lhs + ";"; return text; } - /// blocks like initial doesn't use shadow update (e.g. due to wrote_conc call) + // blocks like initial doesn't use shadow update (e.g. due to wrote_conc call) if (block_require_shadow_update(type)) { shadow_statements.push_back(statement); auto lhs = get_variable_name(shadow_varname(statement.lhs)); @@ -1939,7 +1905,7 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& return text; } - /// return regular statement + // return regular statement auto lhs = get_variable_name(statement.lhs); auto text = "{} {} {};"_format(lhs, statement.op, statement.rhs); return text; @@ -1952,12 +1918,9 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& /** - * NMODL constants from unit database - * - * todo : this should be replaced with constant handling from unit database + * \todo this should be replaced with constant handling from unit database */ - -void CodegenCVisitor::print_nmodl_constant() { +void CodegenCVisitor::print_nmodl_constants() { printer->add_newline(2); printer->add_line("/** constants used in nmodl */"); printer->add_line("static const double FARADAY = 96485.3;"); @@ -2055,9 +2018,7 @@ void CodegenCVisitor::print_namespace_stop() { /** - * Print getter methods used for accessing thread variables - * - * There are three types of thread variables currently considered: + * \details There are three types of thread variables currently considered: * - top local thread variables * - thread variables in the mod file * - thread variables for solver @@ -2200,13 +2161,6 @@ std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name } -/** - * Return variable name in the structure of mechanism properties - * - * @param name variable name that is being printed - * @param use_instance if variable name should be with the instance object qualifier - * @return use_instance whether print name using Instance structure (or data array if false) - */ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use_instance) { std::string varname = update_if_ion_variable_name(name); @@ -2220,7 +2174,7 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use }; // clang-format on - /// float variable + // float variable auto f = std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), symbol_comparator); @@ -2228,14 +2182,14 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use return float_variable_name(*f, use_instance); } - /// integer variable + // integer variable auto i = std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); if (i != codegen_int_variables.end()) { return int_variable_name(*i, varname, use_instance); } - /// global variable + // global variable auto g = std::find_if(codegen_global_variables.begin(), codegen_global_variables.end(), symbol_comparator); @@ -2243,7 +2197,7 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use return global_variable_name(*g); } - /// shadow variable + // shadow variable auto s = std::find_if(codegen_shadow_variables.begin(), codegen_shadow_variables.end(), symbol_comparator); @@ -2255,13 +2209,13 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use return "nt->_" + naming::NTHREAD_DT_VARIABLE; } - /// t in net_receive method is an argument to function and hence it should - /// ne used instead of nt->_t which is current time of thread + // t in net_receive method is an argument to function and hence it should + // ne used instead of nt->_t which is current time of thread if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { return "nt->_" + naming::NTHREAD_T_VARIABLE; } - /// otherwise return original name + // otherwise return original name return varname; } @@ -2322,9 +2276,7 @@ void CodegenCVisitor::print_coreneuron_includes() { /** - * Print all static variables at file scope - * - * Variables required for type of ion, type of point process etc. are + * \details Variables required for type of ion, type of point process etc. are * of static int type. For any backend type (C,C++), it's ok to have * these variables as file scoped static variables. * @@ -2369,9 +2321,9 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { } } - /// Neuron and Coreneuron adds "v" to global variables when vectorize - /// is false. But as v is always local variable and passed as argument, - /// we don't need to use global variable v + // Neuron and Coreneuron adds "v" to global variables when vectorize + // is false. But as v is always local variable and passed as argument, + // we don't need to use global variable v auto& top_locals = info.top_local_variables; if (!info.vectorize && !top_locals.empty()) { @@ -2469,7 +2421,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { printer->add_line("/** holds object of global variable */"); printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); - /// create copy on the device + // create copy on the device print_global_variable_device_create_annotation(); } @@ -2558,9 +2510,7 @@ void CodegenCVisitor::print_global_variables_for_hoc() { /** - * Print register function for mechanisms - * - * Every mod file has register function to connect with the simulator. + * \details Every mod file has register function to connect with the simulator. * Various information about mechanism and callbacks get registered with * the simulator using suffix_reg() function. * @@ -2584,10 +2534,10 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("/** register channel with the simulator */"); printer->start_block("void _{}_reg() "_format(info.mod_file)); - /// allocate global variables + // allocate global variables printer->add_line("setup_global_variables();"); - /// type related information + // type related information auto mech_type = get_variable_name("mech_type"); auto suffix = add_escape_quote(info.mod_suffix); printer->add_newline(); @@ -2600,7 +2550,7 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_newline(); printer->add_line("_nrn_layout_reg(mech_type, get_memory_layout());"); - /// register mechanism + // register mechanism auto args = register_mechanism_arguments(); auto nobjects = num_thread_objects(); if (info.point_process) { @@ -2609,7 +2559,7 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("register_mech({}, {});"_format(args, nobjects)); } - /// types for ion + // types for ion for (const auto& ion: info.ions) { auto type = get_variable_name(ion.name + "_type"); auto name = add_escape_quote(ion.name + "_ion"); @@ -2617,7 +2567,7 @@ void CodegenCVisitor::print_mechanism_register() { } printer->add_newline(); - /** + /* * If threads are used then memory is allocated in setup_global_variables. * Register callbacks for thread allocation and cleanup. Note that thread_data_index * represent total number of thread used minus 1 (i.e. index of last thread). @@ -2641,18 +2591,18 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("_nrn_thread_table_reg(mech_type, {});"_format(name)); } - /// register read/write callbacks for pointers + // register read/write callbacks for pointers if (info.bbcore_pointer_used) { printer->add_line("hoc_reg_bbcore_read(mech_type, bbcore_read);"); printer->add_line("hoc_reg_bbcore_write(mech_type, bbcore_write);"); } - /// register size of double and int elements + // register size of double and int elements // clang-format off printer->add_line("hoc_register_prop_size(mech_type, float_variables_size(), int_variables_size());"); // clang-format on - /// register semantics for index variables + // register semantics for index variables for (auto& semantic: info.semantics) { auto args = "mech_type, {}, {}"_format(semantic.index, add_escape_quote(semantic.name)); printer->add_line("hoc_register_dparam_semantics({});"_format(args)); @@ -2662,7 +2612,7 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("nrn_writes_conc(mech_type, 0);"); } - /// register various information for point process type + // register various information for point process type if (info.net_event_used) { printer->add_line("add_nrn_has_net_event(mech_type);"); } @@ -2684,7 +2634,7 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("hoc_register_net_send_buffering(mech_type);"); } - /// register variables for hoc + // register variables for hoc printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); printer->end_block(1); } @@ -2695,7 +2645,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { return; } - /// thread_mem_init callback + // thread_mem_init callback printer->add_newline(2); printer->add_line("/** thread memory allocation callback */"); printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); @@ -2724,7 +2674,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->end_block(3); - /// thread_mem_cleanup callback + // thread_mem_cleanup callback printer->add_line("/** thread memory cleanup callback */"); printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); @@ -2822,7 +2772,7 @@ void CodegenCVisitor::print_ion_var_structure() { void CodegenCVisitor::print_ion_var_constructor(const std::vector<std::string>& members) { - /// constructor + // constructor printer->add_newline(); printer->add_line("IonCurVar() : ", 0); for (int i = 0; i < members.size(); i++) { @@ -2863,7 +2813,7 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line(" return;"); printer->add_line("}"); - /// offsets for state variables + // offsets for state variables if (info.primes_size != 0) { auto slist1 = get_variable_name("slist1"); auto dlist1 = get_variable_name("dlist1"); @@ -2882,7 +2832,7 @@ void CodegenCVisitor::print_global_variable_setup() { } } - /// additional list for derivimplicit method + // additional list for derivimplicit method if (info.derivimplicit_coreneuron_solver()) { auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); auto slist2 = get_variable_name("slist2"); @@ -2897,7 +2847,7 @@ void CodegenCVisitor::print_global_variable_setup() { allocated_variables.push_back(slist2); } - /// memory for thread member + // memory for thread member if (info.vectorize && (info.thread_data_index != 0)) { auto n = info.thread_data_index; auto alloc = "(ThreadDatum*) mem_alloc({}, sizeof(ThreadDatum))"_format(n); @@ -2906,7 +2856,7 @@ void CodegenCVisitor::print_global_variable_setup() { allocated_variables.push_back(name); } - /// initialize global variables + // initialize global variables for (auto& var: info.state_vars) { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); @@ -2916,13 +2866,13 @@ void CodegenCVisitor::print_global_variable_setup() { } } - /// note : v is not needed in global structure for nmodl even if vectorize is false + // note : v is not needed in global structure for nmodl even if vectorize is false if (!info.thread_variables.empty()) { printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); } - /// initialize global variables + // initialize global variables for (auto& var: info.global_variables) { if (!var->is_array()) { auto name = get_variable_name(var->get_name()); @@ -2936,7 +2886,7 @@ void CodegenCVisitor::print_global_variable_setup() { } } - /// initialize constant variables + // initialize constant variables for (auto& var: info.constant_variables) { auto name = get_variable_name(var->get_name()); auto value_ptr = var->get_value(); @@ -2960,7 +2910,7 @@ void CodegenCVisitor::print_global_variable_setup() { } } - /// update device copy + // update device copy print_global_variable_device_update_annotation(); printer->add_newline(); @@ -3025,15 +2975,10 @@ void CodegenCVisitor::print_setup_range_variable() { /** - * Floating point type for the given range variable (symbol) - * - * If floating point type like "float" is specified on command line then + * \details If floating point type like "float" is specified on command line then * we can't turn all variables to new type. This is because certain variables * are pointers to internal variables (e.g. ions). Hence, we check if given * variable can be safely converted to new type. If so, return new type. - * - * @param symbol Symbol for the range variable - * @return Floating point type (float/double) */ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) { // clang-format off @@ -3128,13 +3073,13 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { printer->add_line("IonCurVar ionvar;"); } - /// read ion statements + // read ion statements auto read_statements = ion_read_statements(BlockType::Initial); for (auto& statement: read_statements) { printer->add_line(statement); } - /// initialize state variables (excluding ion state) + // initialize state variables (excluding ion state) for (auto& var: info.state_vars) { auto name = var->get_name(); auto lhs = get_variable_name(name); @@ -3142,13 +3087,13 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { printer->add_line("{} = {};"_format(lhs, rhs)); } - /// initial block + // initial block if (node != nullptr) { auto block = node->get_statement_block(); print_statement_block(block.get(), false, false); } - /// write ion statements + // write ion statements auto write_statements = ion_write_statements(BlockType::Initial); for (auto& statement: write_statements) { auto text = process_shadow_update_statement(statement, BlockType::Initial); @@ -3249,9 +3194,7 @@ void CodegenCVisitor::print_nrn_alloc() { } /** - * Print nrn_watch_activate function used as a callback for every - * WATCH statement in the mod file. - * Todo : number of watch could be more than number of statements + * \todo number of watch could be more than number of statements * according to grammar. Check if this is correctly handled in neuron * and coreneuron. */ @@ -3266,7 +3209,7 @@ void CodegenCVisitor::print_watch_activate() { printer->start_block( "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id) "_format(inst)); - /// initialize all variables only during first watch statement + // initialize all variables only during first watch statement printer->add_line("if (watch_id == 0) {"); for (int i = 0; i < info.watch_count; i++) { auto name = get_variable_name("watch{}"_format(i + 1)); @@ -3274,7 +3217,7 @@ void CodegenCVisitor::print_watch_activate() { } printer->add_line("}"); - // todo : similar to neuron/coreneuron we are using + /// \todo : similar to neuron/coreneuron we are using // first watch and ignoring rest. for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; @@ -3296,8 +3239,7 @@ void CodegenCVisitor::print_watch_activate() { /** - * Print kernel for watch activation - * todo : similar to print_watch_activate, we are using only + * \todo similar to print_watch_activate, we are using only * first watch. need to verify with neuron/coreneuron about rest. */ void CodegenCVisitor::print_watch_check() { @@ -3385,7 +3327,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_ printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); } - /// rename variables but need to see if they are actually used + // rename variables but need to see if they are actually used auto parameters = info.net_receive_node->get_parameters(); if (!parameters.empty()) { int i = 0; @@ -3411,8 +3353,8 @@ void CodegenCVisitor::print_net_send_call(FunctionCall* node) { std::string weight_index = "weight_index"; std::string pnt = "pnt"; - /// for non-net-receieve functions there is no weight index argument - /// and artificial cell is in vdata which is void** + // for non-net-receieve functions there is no weight index argument + // and artificial cell is in vdata which is void** if (!printing_net_receive) { weight_index = "-1"; auto var = get_variable_name("point_process"); @@ -3421,7 +3363,7 @@ void CodegenCVisitor::print_net_send_call(FunctionCall* node) { } } - /// artificial cells don't use spike buffering + // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { printer->add_text("artcell_net_send(&{}, {}, {}, nt->_t+"_format(tqitem, weight_index, pnt)); @@ -3448,7 +3390,7 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { std::string weight_index = "-1"; std::string pnt = "pnt"; - /// artificial cells don't use spike buffering + // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { printer->add_text("artcell_net_move(&{}, {}, {}, nt->_t+"_format(tqitem, weight_index, pnt)); @@ -3506,7 +3448,7 @@ void CodegenCVisitor::print_net_init() { void CodegenCVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); - // todo : update net send buffer on host + /// \todo Update net send buffer on host printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); printer->add_line(" int type = nsb->_sendtype[i];"); printer->add_line(" int tid = nt->id;"); @@ -3520,7 +3462,7 @@ void CodegenCVisitor::print_send_event_move() { // clang-format on printer->add_line("}"); printer->add_line("nsb->_cnt = 0;"); - // todo : update net send buffer count on device + /// \todo Update net send buffer count on device } @@ -3628,7 +3570,7 @@ void CodegenCVisitor::print_net_receive_kernel() { printing_net_receive = true; auto node = info.net_receive_node; - /// rename arguments if same name is used + // rename arguments if same name is used auto parameters = node->get_parameters(); for (auto& parameter: parameters) { auto name = parameter->get_node_name(); @@ -3712,7 +3654,7 @@ void CodegenCVisitor::print_net_receive() { /** - * Todo: data is not derived. Need to add instance into instance struct? + * \todo Data is not derived. Need to add instance into instance struct? * data used here is wrong in AoS because as in original implementation, * data is not incremented every iteration for AoS. May be better to derive * actual variable names? [resolved now?] @@ -3751,8 +3693,8 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->add_line("return reset;"); printer->end_block(3); - /* - * TODO : To be backward compatible with mod2c we have to generate below + /** + * \todo To be backward compatible with mod2c we have to generate below * comment marker in the generated cpp file for kinderiv.py to * process it and generate correct _kinderiv.h */ @@ -3834,8 +3776,10 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); - /// todo : eigen solver node also emits IonCurVar variable in the functor - /// but that shouldn't update ions in derivative block + /** + * \todo Eigen solver node also emits IonCurVar variable in the functor + * but that shouldn't update ions in derivative block + */ if (ion_variable_struct_required()) { print_ion_variable(); } @@ -4064,7 +4008,7 @@ void CodegenCVisitor::print_common_getters() { void CodegenCVisitor::print_data_structures() { - print_mechanism_global_var_structure(); + print_mechanism_global_var_structure(false); print_mechanism_range_var_structure(); print_ion_var_structure(); } @@ -4101,7 +4045,7 @@ void CodegenCVisitor::print_codegen_routines() { print_backend_info(); print_headers_include(); print_namespace_begin(); - print_nmodl_constant(); + print_nmodl_constants(); print_mechanism_info(); print_data_structures(); print_global_variables_for_hoc(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 047676d759..d979637860 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -163,10 +163,10 @@ using printer::CodePrinter; /** - * @defgroup codegen_backends Codegen Backends - * @ingroup codegen - * @brief Code generation backends for CoreNEURON - * @{ + * \defgroup codegen_backends Codegen Backends + * \ingroup codegen + * \brief Code generation backends for CoreNEURON + * \{ */ /** @@ -183,771 +183,1404 @@ using printer::CodePrinter; class CodegenCVisitor: public visitor::AstVisitor { protected: using SymbolType = std::shared_ptr<symtab::Symbol>; + + /** + * A vector of parameters represented by a 4-tuple of strings: + * + * - type qualifier (e.g. \c const) + * - type (e.g. \c double) + * - pointer qualifier (e.g. \c \_\_restrict\_\_) + * - parameter name (e.g. \c data) + */ using ParamVector = std::vector<std::tuple<std::string, std::string, std::string, std::string>>; - /// name of mod file (without .mod suffix) + /** + * Name of mod file (without .mod suffix) + */ std::string mod_filename; - /// flag to indicate if visitor should print the visited nodes + /** + * Flag to indicate if visitor should print the visited nodes + */ bool codegen = false; - /// variable name should be converted to instance name (but not for function arguments) + /** + * Variable name should be converted to instance name (but not for function arguments) + */ bool enable_variable_name_lookup = true; - /// symbol table for the program + /** + * Symbol table for the program + */ symtab::SymbolTable* program_symtab = nullptr; - /// all float variables for the model + /** + * All float variables for the model + */ std::vector<SymbolType> codegen_float_variables; - /// all int variables for the model + /** + * All int variables for the model + */ std::vector<IndexVariableInfo> codegen_int_variables; - /// all global variables for the model - /// @todo: this has become different than CodegenInfo + /** + * All global variables for the model + * \todo: this has become different than CodegenInfo + */ std::vector<SymbolType> codegen_global_variables; - /// all ion variables that could be possibly written + /** + * All ion variables that could be possibly written + */ std::vector<SymbolType> codegen_shadow_variables; - /// true if currently net_receive block being printed + /** + * \c true if currently net_receive block being printed + */ bool printing_net_receive = false; - /// true if currently printing top level verbatim blocks + /** + * \c true if currently printing top level verbatim blocks + */ bool printing_top_verbatim_blocks = false; - /// true if internal method call was encountered while processing verbatim block + /** + * \c true if internal method call was encountered while processing verbatim block + */ bool internal_method_call_encountered = false; - /// index of watch statement being printed + /** + * Index of watch statement being printed + */ int current_watch_statement = 0; - /// data type of floating point variables + /** + * Data type of floating point variables + */ std::string float_type = codegen::naming::DEFAULT_FLOAT_TYPE; - /// memory layout for code generation + /** + * Memory layout for code generation + */ LayoutType layout; - /// all ast information for code generation + /** + * All ast information for code generation + */ codegen::CodegenInfo info; - /// code printer object for target (C, CUDA, ispc, ...) + /** + * Code printer object for target (C, CUDA, ispc, ...) + */ std::shared_ptr<CodePrinter> target_printer; - /// code printer object for wrappers + /** + * Code printer object for wrappers + */ std::shared_ptr<CodePrinter> wrapper_printer; - /// pointer to active code printer + /** + * Pointer to active code printer + */ std::shared_ptr<CodePrinter> printer; - /// list of shadow statements in the current block + /** + * List of shadow statements in the current block + */ std::vector<ShadowUseStatement> shadow_statements; - /// nmodl language version + /** + * Return Nmodl language version + * \return A version + */ std::string nmodl_version() { return codegen::naming::NMODL_VERSION; } - + /** + * Add quotes to string to be output + * + * \param text The string to be quoted + * \return The same string with double-quotes pre- and postfixed + */ std::string add_escape_quote(const std::string& text) { return "\"" + text + "\""; } - /// operator for rhs vector update (matrix update) + /** + * Operator for rhs vector update (matrix update) + */ std::string operator_for_rhs() { return info.electorde_current ? "+=" : "-="; } - /// operator for diagonal vector update (matrix update) + /** + * Operator for diagonal vector update (matrix update) + */ std::string operator_for_d() { return info.electorde_current ? "-=" : "+="; } - /// data type for the local variables + /** + * Data type for the local variables + */ std::string local_var_type() { return codegen::naming::DEFAULT_LOCAL_VAR_TYPE; } - /// default data type for floating point elements + /** + * Default data type for floating point elements + */ std::string default_float_data_type() { return codegen::naming::DEFAULT_FLOAT_TYPE; } - /// data type for floating point elements specified on command line + /** + * Data type for floating point elements specified on command line + */ std::string float_data_type() { return float_type; } - /// default data type for integer (offset) elements + /** + * Default data type for integer (offset) elements + */ std::string default_int_data_type() { return codegen::naming::DEFAULT_INTEGER_TYPE; } - /// function name for net send + /** + * Checks if given function name is \c net_send + * \param name The function name to check + * \return \c true if the function is net_send + */ bool is_net_send(const std::string& name) { return name == codegen::naming::NET_SEND_METHOD; } - /// function name for net move + /** + * Checks if given function name is \c net_move + * \param name The function name to check + * \return \c true if the function is net_move + */ bool is_net_move(const std::string& name) { return name == codegen::naming::NET_MOVE_METHOD; } - /// function name for net event + /** + * Checks if given function name is \c net_event + * \param name The function name to check + * \return \c true if the function is net_event + */ bool is_net_event(const std::string& name) { return name == codegen::naming::NET_EVENT_METHOD; } - /// name of structure that wraps range variables + /** + * Name of structure that wraps range variables + */ std::string instance_struct() { return "{}_Instance"_format(info.mod_suffix); } - /// name of structure that wraps range variables + /** + * Name of structure that wraps range variables + */ std::string global_struct() { return "{}_Store"_format(info.mod_suffix); } - /// name of function or procedure + /** + * Constructs the name of a function or procedure + * \param name The name of the function or procedure + * \return The name of the function or procedure postfixed with the model name + */ std::string method_name(const std::string& name) { auto suffix = info.mod_suffix; return name + "_" + suffix; } - /// name for shadow variable + /** + * Constructs a shadow variable name + * \param name The name of the variable + * \return The name of the variable prefixed with \c shadow_ + */ std::string shadow_varname(const std::string& name) { return "shadow_" + name; } - /// create temporary symbol + /** + * Creates a temporary symbol + * \param name The name of the symbol + * \return A symbol based on the given name + */ SymbolType make_symbol(std::string name) { return std::make_shared<symtab::Symbol>(name, ModToken()); } - /// check if given variable is state variable + /** + * Checks if the given variable name belongs to a state variable + * \param name The variable name + * \return \c true if the variable is a state variable + */ bool state_variable(std::string name); - /// check if net receive/send buffering kernels required + /** + * Check if net receive/send buffering kernels required + */ bool net_receive_buffering_required(); - /// check if nrn_state function is required + /** + * Check if nrn_state function is required + */ bool nrn_state_required(); - /// check if nrn_cur function is required + /** + * Check if nrn_cur function is required + */ bool nrn_cur_required(); - /// check if net_receive function is required + /** + * Check if net_receive function is required + */ bool net_receive_required(); - /// check if net_send_buffer is required + /** + * Check if net_send_buffer is required + */ bool net_send_buffer_required(); - /// check if setup_range_variable function is required + /** + * Check if setup_range_variable function is required + * \return + */ bool range_variable_setup_required(); - /// check if net_receive node exist + /** + * Check if net_receive node exist + */ bool net_receive_exist(); - /// check if breakpoint node exist + /** + * Check if breakpoint node exist + */ bool breakpoint_exist(); - /// if method is defined the mod file + /** + * Check if given method is defined in this model + * \param name The name of the method to check + * \return \c true if the method is defined + */ bool defined_method(const std::string& name); - /// check if give statement should be skipped during code generation + /** + * Check if given statement should be skipped during code generation + * \param node The AST Statement node to check + * \return \c true if this Statement is to be skipped + */ bool statement_to_skip(ast::Statement* node); - /// check if semicolon required at the end of given statement + /** + * Check if a semicolon is required at the end of given statement + * \param node The AST Statement node to check + * \return \c true if this Statement requires a semicolon + */ bool need_semicolon(ast::Statement* node); - /// number of threads to allocate + /** + * Determine the number of threads to allocate + */ int num_thread_objects() { return info.vectorize ? (info.thread_data_index + 1) : 0; } - /// num of float variables in the model + /** + * Number of float variables in the model + */ int float_variables_size(); - /// num of integer variables in the model + /** + * Number of integer variables in the model + */ int int_variables_size(); - /// for given float variable name, index position in the data array + /** + * Determine the position in the data array for a given float variable + * \param name The name of a float variable + * \return The position index in the data array + */ int position_of_float_var(const std::string& name); - /// for given int variable name, index position in the data array + /** + * Determine the position in the data array for a given int variable + * \param name The name of an int variable + * \return The position index in the data array + */ int position_of_int_var(const std::string& name); - /// when ion variable copies optimized then change name (e.g. ena to ion_ena) + /** + * Determine the updated name if the ion variable has been optimized + * \param name The ion variable name + * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena) + */ std::string update_if_ion_variable_name(const std::string& name); - /// name of the code generation backend + /** + * Name of the code generation backend + */ virtual std::string backend_name(); - /// convert given double value to string (for printing) + /** + * Convert a given \c double value to its string representation + * \param value The number to convert + * \return Its string representation + */ virtual std::string double_to_string(double value); - /// convert given float value to string (for printing) + /** + * Convert a given \c float value to its string representation + * \param value The number to convert + * \return Its string representation + */ virtual std::string float_to_string(float value); - /// get variable name for float variable + /** + * Determine the name of a \c float variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol + */ std::string float_variable_name(SymbolType& symbol, bool use_instance); - /// get variable name for int variable + /** + * Determine the name of an \c int variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param name The name of the index variable + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol + */ std::string int_variable_name(IndexVariableInfo& symbol, const std::string& name, bool use_instance); - /// get variable name for global variable + /** + * Determine the variable name for a global variable given its symbol + * \param symbol The symbol of a variable for which we want to obtain its name + * \return The C string representing the access to the global variable + */ std::string global_variable_name(SymbolType& symbol); - /// get ion shadow variable name + /** + * Determine the variable name for a shadow variable given its symbol + * \param symbol The symbol of a variable for which we want to obtain its name + * \return The C string representing the access to the shadow variable + */ std::string ion_shadow_variable_name(SymbolType& symbol); - /// get variable name for given name. if use_instance is true then "Instance" - /// structure is used while returning name (implemented by derived classes) - virtual std::string get_variable_name(const std::string& name, bool use_instance = true); + /** + * Determine variable name in the structure of mechanism properties + * + * \param name Variable name that is being printed + * \param use_instance Should the variable be accessed via instance or data array + * \return The C string representing the access to the variable in the neuron thread + * structure + */ + std::string get_variable_name(const std::string& name, bool use_instance = true); - /// name of the current variable used in the breakpoint bock + /** + * Determine the variable name for the "current" used in breakpoint block taking into account + * intermediate code transformations. + * \param current The variable name for the current used in the model + * \return The name for the current to be printed in C + */ std::string breakpoint_current(std::string current); - /// populate all index semantics needed for registration with coreneuron + /** + * populate all index semantics needed for registration with coreneuron + */ void update_index_semantics(); - /// return all float variables required during code generation + /** + * Determine all \c float variables required during code generation + * \return A \c vector of \c float variables + */ std::vector<SymbolType> get_float_variables(); - /// return all int variables required during code generation + /** + * Determine all \c int variables required during code generation + * \return A \c vector of \c int variables + */ std::vector<IndexVariableInfo> get_int_variables(); - /// return all ion write variables that require shadow vectors during code generation + /** + * Determine all ion write variables that require shadow vectors during code generation + * \return A \c vector of ion variables + */ std::vector<SymbolType> get_shadow_variables(); - /// print vector elements (all types) + /** + * Print the items in a vector as a list + * + * This function prints a given vector of elements as a list with given separator onto the + * current printer. Elements are expected to be of type nmodl::ast::Ast and are printed by being + * visited. Care is taken to omit the separator after the the last element. + * + * \tparam The element type in the vector, which must be of type nmodl::ast::Ast + * \param elements The vector of elements to be printed + * \param prefix A prefix string to printed before each element + * \param separator The seperator string to be printed between all elements + */ template <typename T> void print_vector_elements(const std::vector<T>& elements, const std::string& separator, const std::string& prefix = ""); + /** + * Generate the string representing the procedure parameter declaration + * + * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. + * + * \param params The parameters that should be concatenated into the function parameter + * declaration \return The string representing the declaration of function parameters + */ std::string get_parameter_str(const ParamVector& params); - /// check if function or procedure has argument with same name - template <typename T> - bool has_argument_with_name(const T& node, std::string name); - - /// any statement block in nmodl with option to (not) print braces + /** + * Print any statement block in nmodl with option to (not) print braces + * + * The individual statements (of type nmodl::ast::Statement) in the StatementBlock are printed + * by accepting \c this visistor. + * + * \param node A (possibly empty) statement block AST node + * \param open_brace Print an opening brace if \c false + * \param close_brace Print a closing brace if \c true + */ void print_statement_block(ast::StatementBlock* node, bool open_brace = true, bool close_brace = true); - /// check if structure for ion variables required + /** + * Check if a structure for ion variables is required + * \return \c true if a structure fot ion variables must be generated + */ bool ion_variable_struct_required(); - /// process verbatim block for possible variable renaming + /** + * Process a verbatim block for possible variable renaming + * \param text The verbatim code to be processed + * \return The code with all variables renamed as needed + */ std::string process_verbatim_text(std::string text); - /// process token in verbatim block for possible variable renaming + /** + * Process a token in a verbatim block for possible variable renaming + * \param token The verbatim token to be processed + * \return The code after variable renaming + */ std::string process_verbatim_token(const std::string& token); - /// rename function/procedure arguments that conflict with default arguments + /** + * Rename function/procedure arguments that conflict with default arguments + */ void rename_function_arguments(); - //// statements for reading ion values + /** + * For a given output block type, return statements for all read ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the reading of ion variables + */ std::vector<std::string> ion_read_statements(BlockType type); - //// minimal statements for reading ion values + /** + * For a given output block type, return minimal statements for all read ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the reading of ion variables + */ std::vector<std::string> ion_read_statements_optimized(BlockType type); - //// statements for writing ion values + /** + * For a given output block type, return statements for writing back ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the write-back of ion variables + */ std::vector<ShadowUseStatement> ion_write_statements(BlockType type); - /// return variable name and corresponding ion read variable name + /** + * Return ion variable name and corresponding ion read variable name + * \param name The ion variable name + * \return The ion read variable name + */ std::pair<std::string, std::string> read_ion_variable_name(std::string name); - /// return variable name and corresponding ion write variable name + /** + * Return ion variable name and corresponding ion write variable name + * \param name The ion variable name + * \return The ion write variable name + */ std::pair<std::string, std::string> write_ion_variable_name(std::string name); - /// function call / statement for nrn_wrote_conc + /** + * Generate Function call statement for nrn_wrote_conc + * \param ion_name The name of the ion variable + * \param concentration The name of the concentration variable + * \param index + * \return The string representing the function call + */ std::string conc_write_statement(const std::string& ion_name, const std::string& concentration, int index); - /// arguments for internally defined functions + /** + * Arguments for functions that are defined and used internally. + * \return the method arguments + */ std::string internal_method_arguments(); - /// parameters for internally defined functions + /** + * Parameters for internally defined functions + * \return the method parameters + */ ParamVector internal_method_parameters(); - /// arguments for external functions + /** + * Arguments for external functions called from generated code + * \return A string representing the arguments passed to an external function + */ std::string external_method_arguments(); - /// parameters for external functions + /** + * Parameters for functions in generated code that are called back from external code + * + * Functions registered in NEURON during initialization for callback must adhere to a prescribed + * calling convention. This method generates the string representing the function parameters for + * these externally called functions. + * \param table + * \return A string representing the parameters of the function + */ std::string external_method_parameters(bool table = false); - /// arguments for register_mech or point_register_mech function + /** + * Arguments for register_mech or point_register_mech function + */ std::string register_mechanism_arguments(); - /// arguments for "_threadargs_" macro in neuron implementation + /** + * Arguments for "_threadargs_" macro in neuron implementation + */ std::string nrn_thread_arguments(); - /// arguments for "_threadargs_" macro in neuron implementation + /** + * Arguments for "_threadargs_" macro in neuron implementation + */ std::string nrn_thread_internal_arguments(); - /// replace commonly used verbatim variables + /** + * Replace commonly used verbatim variables + * \param name A variable name to be checked and possibly updated + * \return The possibly replace variable name + */ std::string replace_if_verbatim_variable(std::string name); - /// return name of main compute kernels + /** + * Return the name of main compute kernels + * \param type A block type + */ virtual std::string compute_method_name(BlockType type); - /// restrict keyword + /** + * The used pointer qualifier. + * + * For C code generation this is \c \_\_restrict\_\_ to ensure that the compiler is aware of + * the fact that we are working only with non-overlapping memory regions + * \return \c \_\_restrict\_\_ + */ virtual std::string ptr_type_qualifier(); + /** + * The used parameter type qualifier + * \return an empty string + */ virtual std::string param_type_qualifier(); + /** + * The used parameter pointer type qualifier + * \return an empty string + */ virtual std::string param_ptr_qualifier(); - /// const keyword + /** + * Returns the \c const keyword + * \return \c const + */ virtual std::string k_const(); - /// start of coreneuron namespace + /** + * Prints the start of the \c coreneuron namespace + */ void print_namespace_start(); - /// end of coreneuron namespace + /** + * Prints the end of the \c coreneuron namespace + */ void print_namespace_stop(); - /// start of backend namespace + /** + * Prints the start of namespace for the backend-specific code + * + * For the C backend no additional namespace is required + */ virtual void print_backend_namespace_start(); - /// end of backend namespace + /** + * Prints the end of namespace for the backend-specific code + * + * For the C backend no additional namespace is required + */ virtual void print_backend_namespace_stop(); - /// nmodl constants - virtual void print_nmodl_constant(); + /** + * Print the nmodl constants used in backend code + * + * Currently we define three basic constants, which are assumed to be present in NMODL, directly + * in the backend code: + * + * \code + * static const double FARADAY = 96485.3; + * static const double PI = 3.14159; + * static const double R = 8.3145; + * \endcode + */ + virtual void print_nmodl_constants(); - /// top header printed in generated code + /** + * Print top file header printed in generated code + */ void print_backend_info(); - /// memory allocation routine + /** + * Print memory allocation routine + */ virtual void print_memory_allocation_routine(); - /// standard c/c++ includes + /** + * Print standard C/C++ includes + */ void print_standard_includes(); - /// includes from coreneuron + /** + * Print includes from coreneuron + */ void print_coreneuron_includes(); - /// backend specific includes + /** + * Print backend specific includes (none needed for C backend) + */ virtual void print_backend_includes(); - /// use of shadow updates at channel level required + /** + * Determine whether use of shadow updates at channel level required + * + * + * \param type The backend block type + * \return \c true if shadow updates are needed + */ virtual bool block_require_shadow_update(BlockType type); - /// channel execution with dependency (backend specific) + /** + * Determine whether this backend is performing channel execution with dependency + * \return \c true if task dependency is enabled + */ virtual bool channel_task_dependency_enabled(); - /// check if shadow_vector_setup function is required + /** + * Check if \c shadow\_vector\_setup function is required + */ bool shadow_vector_setup_required(); - /// ion variable copies are avoided + /** + * Check if ion variable are copies avoided + */ bool optimize_ion_variable_copies(); - /// if reduction block in nrn_cur required + /** + * Check if reduction block in \c nrn\_cur required + */ virtual bool nrn_cur_reduction_loop_required(); - /// if variable is qualified as constant + /** + * Check if variable is qualified as constant + * \param name The name of variable + * \return \c true if it is constant + */ virtual bool is_constant_variable(std::string name); - /// char array that has mechanism information (to be registered with coreneuron) + /** + * Print backend code for byte array that has mechanism information (to be registered + * with coreneuron) + */ void print_mechanism_info(); - /// structure that wraps all global variables in the mod file - virtual void print_mechanism_global_var_structure(bool wrapper = false); + /** + * Print the structure that wraps all global variables used in the NMODL + * \param wrapper + */ + virtual void print_mechanism_global_var_structure(bool wrapper); - /// structure that wraps all range and int variables required for mod file + /** + * Print the structure that wraps all range and int variables required for the NMODL + */ void print_mechanism_range_var_structure(); - /// structure of ion variables used for local copies + /** + * Print structure of ion variables used for local copies + */ void print_ion_var_structure(); - /// constructor of ion variables + /** + * Print constructor of ion variables + * \param members The ion variable names + */ virtual void print_ion_var_constructor(const std::vector<std::string>& members); - /// print ion variable + /** + * Print the ion variable struct + */ virtual void print_ion_variable(); - /// return floating point type for given range variable symbol + /** + * Returns floating point type for given range variable symbol + * \param symbol A range variable symbol + */ std::string get_range_var_float_type(const SymbolType& symbol); - /// function that initialize range variable with different data type + /** + * Print the function that initialize range variable with different data type + */ void print_setup_range_variable(); - /// function that initialize instance structure + /** + * Print the function that initialize instance structure + * + */ void print_instance_variable_setup(); - /// char arrays that registers scalar and vector variables for hoc interface + /** + * Print byte arrays that register scalar and vector variables for hoc interface + * + */ void print_global_variables_for_hoc(); - /// getter method for thread variables and ids + /** + * Print the getter method for thread variables and ids + * + */ void print_thread_getters(); - /// getter method for memory layout + /** + * Print the getter method for memory layout + * + */ void print_memory_layout_getter(); - /// getter method for index position of first pointer variable + /** + * Print the getter method for index position of first pointer variable + * + */ void print_first_pointer_var_index_getter(); - /// getter methods for float and integer variables count + /** + * Print the getter methods for float and integer variables count + * + */ void print_num_variable_getter(); - /// getter method for getting number of arguments for net_receive + /** + * Print the getter method for getting number of arguments for net_receive + * + */ void print_net_receive_arg_size_getter(); - /// getter method for returning membrane list from NrnThread + /** + * Print the getter method for returning membrane list from NrnThread + * + */ void print_memb_list_getter(); - /// getter method for returning mechtype + /** + * Print the getter method for returning mechtype + * + */ void print_mech_type_getter(); - /// setup method that initializes all global variables + /** + * Print the setup method that initializes all global variables + * + */ void print_global_variable_setup(); - /// pragma annotation to create global variables on the device + /** + * Print the pragma annotation to create global variables on the device + * + * \note This is not used for the C backend + */ virtual void print_global_variable_device_create_annotation(); - /// pragma annotation to update global variables from host to the device + /** + * Print the pragma annotation to update global variables from host to the device + * + * \note This is not used for the C backend + */ virtual void print_global_variable_device_update_annotation(); - /// setup method for allocation of shadow vectors + /** + * Print the setup method for allocation of shadow vectors + * + */ void print_shadow_vector_setup(); - /// setup method for setting matrix shadow vectors + /** + * Print the setup method for setting matrix shadow vectors + * + */ virtual void print_rhs_d_shadow_variables(); - /// backend specific device method annotation + /** + * Print the backend specific device method annotation + * + * \note This is not used for the C backend + */ virtual void print_device_method_annotation(); - /// backend specific global method annotation + /** + * Print backend specific global method annotation + * + * \note This is not used for the C backend + */ virtual void print_global_method_annotation(); - /// call to internal or external function + /** + * Print call to internal or external function + * \param node The AST node representing a function call + */ void print_function_call(ast::FunctionCall* node); - /// net_send call + /** + * Print call to \c net\_send + * \param node The AST node representing the function call + */ void print_net_send_call(ast::FunctionCall* node); - /// net_move call + /** + * Print call to net\_move + * \param node The AST node representing the function call + */ void print_net_move_call(ast::FunctionCall* node); - /// net_event call + /** + * Print call to net\_event + * \param node The AST node representing the function call + */ void print_net_event_call(ast::FunctionCall* node); - /// channel iterations from which task can be created + /** + * Print channel iterations from which tasks are created + * + * \note This is not used for the C backend + * \param type + */ virtual void print_channel_iteration_task_begin(BlockType type); - /// end of task for channel iteration + /** + * Print end of channel iteration for task + * + * \note This is not used for the C backend + */ virtual void print_channel_iteration_task_end(); - /// backend specific block start for tiling on channel iteration + /** + * Print block start for tiling on channel iteration + */ virtual void print_channel_iteration_tiling_block_begin(BlockType type); - /// backend specific block end for tiling on channel iteration + /** + * Print block end for tiling on channel iteration + */ virtual void print_channel_iteration_tiling_block_end(); - /// ivdep like annotation for channel iterations + /** + * Print pragma annotations for channel iterations + * + * This can be overriden by backends to provide additonal annotations or pragmas to enable + * for example SIMD code generation (e.g. through \c ivdep) + * The default implementation prints + * + * \code + * #pragma ivdep + * \endcode + * + * \param type The block type + */ virtual void print_channel_iteration_block_parallel_hint(BlockType type); - /// annotations like "acc enter data present(...)" for main kernel + /** + * Print accelerator annotations indicating data presence on device + */ virtual void print_kernel_data_present_annotation_block_begin(); - /// end of annotation like "acc enter data" + /** + * Print matching block end of accelerator annotations for data presence on device + */ virtual void print_kernel_data_present_annotation_block_end(); - /// backend specific channel instance iteration block start + /** + * Print backend specific channel instance iteration block start + * \param type The block type in which we currently are + */ virtual void print_channel_iteration_block_begin(BlockType type); - /// backend specific channel instance iteration block end + /** + * Print backend specific channel instance iteration block end + */ virtual void print_channel_iteration_block_end(); - /// common code post channel instance iteration + /** + * Print common code post channel instance iteration + */ void print_post_channel_iteration_common_code(); - /// function and procedures prototype declaration + /** + * Print function and procedures prototype declaration + */ void print_function_prototypes(); - /// print check_table functions + /** + * Print check_table functions + */ void print_check_table_thread_function(); - /// print nmodl function or procedure (common code) + /** + * Print nmodl function or procedure (common code) + * \param node the AST node representing the function or procedure in NMODL + * \param name the name of the function or procedure + */ void print_function_or_procedure(ast::Block* node, std::string& name); - /// thread related memory allocation and deallocation callbacks + /** + * Print thread related memory allocation and deallocation callbacks + */ void print_thread_memory_callbacks(); - /// top level (global scope) verbatim blocks + /** + * Print top level (global scope) verbatim blocks + */ void print_top_verbatim_blocks(); - /// prototype declarations of functions and procedures + /** + * Print prototype declarations of functions or procedures + * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) + * \param node The AST node representing the function or procedure block + * \param name A user defined name for the function + */ template <typename T> void print_function_declaration(const T& node, const std::string& name); - /// initial block + /** + * Print initial block statements + * + * Generate the target backend code corresponding to the NMODL initial block statements + * + * \param node The AST Node representing a NMODL initial block + */ void print_initial_block(ast::InitialBlock* node); - /// initial block in the net receive block + /** + * Print initial block in the net receive block + */ void print_net_init(); - /// common code section for net receive related methods + /** + * Print the common code section for net receive related methods + * + * \param node The AST node representing the corresponding NMODL block + * \param need_mech_inst \c true if a local \c inst variable needs to be defined in generated + * code + */ void print_net_receive_common_code(ast::Block* node, bool need_mech_inst = true); - /// kernel for buffering net_send events + /** + * Print kernel for buffering net_send events + * + * This kernel is only needed for accelerator backends where \c net\_send needs to be executed + * in two stages as the actual communication must be done in the host code. + */ void print_net_send_buffering(); - /// send event move block used in net receive as well as watch + /** + * Print send event move block used in net receive as well as watch + */ void print_send_event_move(); + /** + * Generate the target backend code for the \c net\_receive\_buffering function delcaration + * \return The target code string + */ virtual std::string net_receive_buffering_declaration(); + /** + * Print the target backend code for defining and checking a local \c Memb\_list variable + */ virtual void print_get_memb_list(); + + /** + * Print the code for the main \c net\_receive loop + */ virtual void print_net_receive_loop_begin(); + /** + * Print the code for closing the main \c net\_receive loop + */ virtual void print_net_receive_loop_end(); - /// net_receive function definition + /** + * Print \c net\_receive function definition + */ void print_net_receive(); - /// derivative kernel when derivimplicit method is used + /** + * Print derivative kernel when \c derivimplicit method is used + * + * \param block The corresponding AST node represening an NMODL \c derivimplicit block + */ void print_derivimplicit_kernel(ast::Block* block); - /// block / loop for statement requiring reduction + /** + * Print block / loop for statement requiring reduction + * + */ void print_shadow_reduction_block_begin(); - /// end of block / loop for statement requiring reduction + /** + * Print end of block / loop for statement requiring reduction + * + */ void print_shadow_reduction_block_end(); - /// atomic update pragma for reduction statements + /** + * Print atomic update pragma for reduction statements + * + */ virtual void print_atomic_reduction_pragma(); - /// print all reduction statements + /** + * Print all reduction statements + * + */ void print_shadow_reduction_statements(); - /// process shadow update statement : if statement requires reduction then - /// add it to vector of reduction statement and return statement using shadow update + /** + * Process shadow update statement + * + * If the statement requires reduction then add it to vector of reduction statement and return + * statement using shadow update + * + * \param statement The statement that might require shadow updates + * \param type The target backend code block type + * \return The generated target backend code + */ std::string process_shadow_update_statement(ShadowUseStatement& statement, BlockType type); - /// main body of nrn_cur function + /** + * Print main body of nrn_cur function + * \param node the AST node representing the NMODL breakpoint block + */ void print_nrn_cur_kernel(ast::BreakpointBlock* node); - /// nrn_cur_kernel will use this kernel if conductance keywords are specified + /** + * Print the \c nrn\_cur kernel with NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is used in the \c breakpoint block, then + * CodegenCVisitor::print_nrn_cur_kernel will use this printer + * + * \param node the AST node representing the NMODL breakpoint block + */ void print_nrn_cur_conductance_kernel(ast::BreakpointBlock* node); - /// nrn_cur_kernel will use this kernel if no conductance keywords are specified + /** + * Print the \c nrn\_cur kernel without NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is \b not used in the \c breakpoint block, then + * CodegenCVisitor::print_nrn_cur_kernel will use this printer + */ void print_nrn_cur_non_conductance_kernel(); - /// nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified + /** + * Print the \c nrn_current kernel + * + * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified + * \param node the AST node representing the NMODL breakpoint block + */ void print_nrn_current(ast::BreakpointBlock* node); - /// update to matrix elements with/without shadow vectors + /** + * Print the update to matrix elements with/without shadow vectors + * + */ virtual void print_nrn_cur_matrix_shadow_update(); - /// reduction to matrix elements from shadow vectors + /** + * Print the reduction to matrix elements from shadow vectors + * + */ virtual void print_nrn_cur_matrix_shadow_reduction(); - /// nrn_alloc function definition + /** + * Print nrn_alloc function definition + * + */ void print_nrn_alloc(); - /// common code for global functions like nrn_init, nrn_cur and nrn_state + /** + * Print common code for global functions like nrn_init, nrn_cur and nrn_state + * \param type The target backend code block type + */ virtual void print_global_function_common_code(BlockType type); - /// mechanism registration function + /** + * Print the mechanism registration function + * + */ void print_mechanism_register(); - /// print watch activate function + /** + * Print watch activate function + * + */ void print_watch_activate(); - /// all includes + /** + * Print all includes + * + */ virtual void print_headers_include(); - /// start of namespaces + /** + * Print start of namespaces + * + */ void print_namespace_begin(); - /// end of namespaces + /** + * Print end of namespaces + * + */ void print_namespace_end(); - /// common getters + /** + * Print common getters + * + */ void print_common_getters(); - /// all classes + /** + * Print all classes + * + */ virtual void print_data_structures(); - /// all compute functions for every backend + /** + * Print all compute functions for every backend + * + */ virtual void print_compute_functions(); - /// entry point to code generation + /** + * Print entry point to code generation + * + */ virtual void print_codegen_routines(); - /// entry point to code generation for wrappers + /** + * Print entry point to code generation for wrappers + */ virtual void print_wrapper_routines(); @@ -966,6 +1599,24 @@ class CodegenCVisitor: public visitor::AstVisitor { public: + /** + * Constructs the C code generator visitor + * + * This constructor instantiates an NMODL C code generator and allows writing generated code + * directly to a file in \c [output_dir]/[mod_filename].[extension]. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param output_dir The directory where target C file should be generated. + * \param layout The memory layout to be used for data structure generation. + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + * \param extension The file extension to use. This defaults to \c .cpp . + */ CodegenCVisitor(std::string mod_filename, std::string output_dir, LayoutType layout, @@ -977,7 +1628,23 @@ class CodegenCVisitor: public visitor::AstVisitor { , layout(layout) , float_type(float_type) {} - + /** + * \copybrief CodegenCVisitor(std::string, std::string, LayoutType, std::string, std::string) + * + * This constructor instantiates an NMODL C code generator and allows writing generated code + * into a \c std::stringstream. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param stream The \c std::stringstream onto which to write the generated code + * \param layout The memory layout to be used for data structure generation. + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ CodegenCVisitor(std::string mod_filename, std::stringstream& stream, LayoutType layout, @@ -989,6 +1656,24 @@ class CodegenCVisitor: public visitor::AstVisitor { , float_type(float_type) {} + /** + * \copybrief CodegenCVisitor(std::string, std::string, LayoutType, std::string, std::string) + * + * This constructor instantiates an NMODL C code generator and allows writing generated code + * using an nmodl::printer::CodePrinter defined elsewhere. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param layout The memory layout to be used for data structure generation. + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + * \param target_printer A printer defined outside this visitor to be used for the code + * generation + */ CodegenCVisitor(std::string mod_filename, LayoutType layout, std::string float_type, @@ -1000,51 +1685,88 @@ class CodegenCVisitor: public visitor::AstVisitor { , float_type(float_type) {} - /// nrn_init function definition + /** + * Print the \c nrn\_init function definition + * \param skip_init_check \c true if we want the generated code to execute the initialization + * conditionally + */ void print_nrn_init(bool skip_init_check = true); - /// nrn_state / state update function definition + /** + * Print nrn_state / state update function definition + */ void print_nrn_state(); - /// nrn_cur / current update function definition + /** + * Print nrn_cur / current update function definition + */ void print_nrn_cur(); - /// kernel for buffering net_receive events + /** + * Print kernel for buffering net_receive events + * + * This kernel is only needed for accelerator backends where \c net\_receive needs to be + * executed in two stages as the actual communication must be done in the host code. \param + * need_mech_inst \c true if the generated code needs a local inst variable to be defined + */ void print_net_receive_buffering(bool need_mech_inst = true); - /// net_receive kernel function definition + /** + * Print \c net\_receive kernel function definition + */ void print_net_receive_kernel(); - /// print watch activate function + /** + * Print watch activate function + */ void print_watch_check(); - /// print check_function() for function/procedure using table + /** + * Print \c check\_function() for functions or procedure using table + * \param node The AST node representing a function or procedure block + */ void print_table_check_function(ast::Block* node); - /// print replacement function for function/procedure using table + /** + * Print replacement function for function or procedure using table + * \param node The AST node representing a function or procedure block + */ void print_table_replacement_function(ast::Block* node); - /// nmodl function definition + /** + * Print NMODL function in target backend code + * \param node + */ void print_function(ast::FunctionBlock* node); - /// nmodl procedure definition + /** + * Print NMODL procedure in target backend code + * \param node + */ virtual void print_procedure(ast::ProcedureBlock* node); - /** setup the Codgen, typically called from within visit_program but may be called from - * specialized targets to setup this Code generator as fallback. + /** Setup the target backend code generator + * + * Typically called from within \c visit\_program but may be called from + * specialized targets to setup this Code generator as fallback. */ void setup(ast::Program* node); + + /** + * Set the global variables to be generated in target backend code + * \param global_vars + */ void set_codegen_global_variables(std::vector<SymbolType>& global_vars); @@ -1080,9 +1802,6 @@ class CodegenCVisitor: public visitor::AstVisitor { }; -/** - * Print elements of vector with given separator and prefix string - */ template <typename T> void CodegenCVisitor::print_vector_elements(const std::vector<T>& elements, const std::string& separator, @@ -1100,10 +1819,10 @@ void CodegenCVisitor::print_vector_elements(const std::vector<T>& elements, /** * Check if function or procedure node has parameter with given name * - * @tparam T Node type (either procedure or function) - * @param node AST node (either procedure or function) - * @param name Name of parameter - * @return True if argument with name exist + * \tparam T Node type (either procedure or function) + * \param node AST node (either procedure or function) + * \param name Name of parameter + * \return True if argument with name exist */ template <typename T> bool has_parameter_of_name(const T& node, const std::string& name) { @@ -1118,10 +1837,8 @@ bool has_parameter_of_name(const T& node, const std::string& name) { /** - * Print prototype declaration for function and procedures - * - * If there is an argument with name (say alpha) same as range variable (say alpha), - * we want avoid it being printed as instance->alpha. And hence we disable variable + * \details If there is an argument with name (say alpha) same as range variable (say alpha), + * we want to avoid it being printed as instance->alpha. And hence we disable variable * name lookup during prototype declaration. Note that the name of procedure can be * different in case of table statement. */ @@ -1130,14 +1847,14 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin enable_variable_name_lookup = false; auto type = default_float_data_type(); - /// internal and user provided arguments + // internal and user provided arguments auto internal_params = internal_method_parameters(); auto params = node->get_parameters(); for (const auto& param: params) { internal_params.emplace_back("", type, "", param.get()->get_node_name()); } - /// procedures have "int" return type by default + // procedures have "int" return type by default std::string return_type = "int"; if (node->is_function_block()) { return_type = default_float_data_type(); @@ -1152,7 +1869,7 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin enable_variable_name_lookup = true; } -/** @} */ // end of codegen_backends +/** \} */ // end of codegen_backends } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 49ac21aa1e..d8e94e462c 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -499,7 +499,7 @@ void CodegenIspcVisitor::print_mechanism_global_var_structure(bool wrapper) { void CodegenIspcVisitor::print_data_structures() { - print_mechanism_global_var_structure(); + print_mechanism_global_var_structure(false); print_mechanism_range_var_structure(); print_ion_var_structure(); } @@ -557,7 +557,7 @@ void CodegenIspcVisitor::print_headers_include() { print_backend_includes(); } -void CodegenIspcVisitor::print_nmodl_constant() { +void CodegenIspcVisitor::print_nmodl_constants() { printer->add_newline(2); printer->add_line("/** constants used in nmodl. */"); // we use here macros to work around ispc's PI being declared in global namespace @@ -745,7 +745,7 @@ void CodegenIspcVisitor::print_codegen_routines() { move_procs_to_wrapper(); print_backend_info(); print_headers_include(); - print_nmodl_constant(); + print_nmodl_constants(); print_data_structures(); @@ -764,7 +764,7 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { print_ispc_globals(); print_namespace_begin(); - CodegenCVisitor::print_nmodl_constant(); + CodegenCVisitor::print_nmodl_constants(); print_mechanism_info(); print_wrapper_data_structures(); print_global_variables_for_hoc(); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 1402b47f44..3caf42c800 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -130,7 +130,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_wrapper_headers_include(); - void print_nmodl_constant() override; + void print_nmodl_constants() override; /// all compute functions for every backend @@ -153,7 +153,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// structure that wraps all global variables in the mod file - void print_mechanism_global_var_structure(bool wrapper = false) override; + void print_mechanism_global_var_structure(bool wrapper) override; void print_data_structures() override; From 48f3a9fc7b8b81cbe909c3d8f3ef2f15fb99ad46 Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Sat, 27 Apr 2019 13:28:55 +0100 Subject: [PATCH 197/871] Split visitor tests (BlueBrain/nmodl#155) - create separate test/scenario for different visitors but still a single catch executable for visitor tests in `test/visitor/main.cpp` - tests are now split into multiple .cpp files in this folder - `test/visitor/x.cpp` contains tests for `src/visitors/x_visitor.cpp` - plus a few misc. tests in `test/visitor/misc.cpp` - use Catch2 cmake module to use catch_discover_tests feature for fine grain test execution and reports. resolves BlueBrain/nmodl#71 NMODL Repo SHA: BlueBrain/nmodl@2bedcb4e8477d3958fe057de8867f326d2635457 --- cmake/nmodl/CMakeLists.txt | 1 + cmake/nmodl/Catch.cmake | 160 + cmake/nmodl/CatchAddTests.cmake | 104 + src/nmodl/visitors/sympy_solver_visitor.cpp | 6 +- test/nmodl/transpiler/CMakeLists.txt | 57 +- test/nmodl/transpiler/lexer/tokens.cpp | 15 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 8 +- test/nmodl/transpiler/newton/newton.cpp | 17 +- test/nmodl/transpiler/parser/parser.cpp | 14 +- test/nmodl/transpiler/printer/printer.cpp | 2 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 6 +- test/nmodl/transpiler/units/lexer.cpp | 2 +- test/nmodl/transpiler/units/parser.cpp | 4 +- .../transpiler/visitor/constant_folder.cpp | 162 + .../transpiler/visitor/defuse_analyze.cpp | 322 + test/nmodl/transpiler/visitor/inline.cpp | 605 ++ test/nmodl/transpiler/visitor/json.cpp | 63 + .../transpiler/visitor/kinetic_block.cpp | 618 ++ test/nmodl/transpiler/visitor/localize.cpp | 223 + test/nmodl/transpiler/visitor/lookup.cpp | 97 + test/nmodl/transpiler/visitor/loop_unroll.cpp | 158 + test/nmodl/transpiler/visitor/main.cpp | 24 + test/nmodl/transpiler/visitor/misc.cpp | 96 + .../nmodl/transpiler/visitor/neuron_solve.cpp | 155 + test/nmodl/transpiler/visitor/nmodl.cpp | 47 + test/nmodl/transpiler/visitor/perf.cpp | 158 + test/nmodl/transpiler/visitor/rename.cpp | 365 ++ test/nmodl/transpiler/visitor/solve_block.cpp | 118 + test/nmodl/transpiler/visitor/steadystate.cpp | 184 + .../transpiler/visitor/sympy_conductance.cpp | 932 +++ .../nmodl/transpiler/visitor/sympy_solver.cpp | 1313 ++++ test/nmodl/transpiler/visitor/verbatim.cpp | 49 + test/nmodl/transpiler/visitor/visitor.cpp | 5383 ----------------- 33 files changed, 6039 insertions(+), 5429 deletions(-) create mode 100644 cmake/nmodl/Catch.cmake create mode 100644 cmake/nmodl/CatchAddTests.cmake create mode 100644 test/nmodl/transpiler/visitor/constant_folder.cpp create mode 100644 test/nmodl/transpiler/visitor/defuse_analyze.cpp create mode 100644 test/nmodl/transpiler/visitor/inline.cpp create mode 100644 test/nmodl/transpiler/visitor/json.cpp create mode 100644 test/nmodl/transpiler/visitor/kinetic_block.cpp create mode 100644 test/nmodl/transpiler/visitor/localize.cpp create mode 100644 test/nmodl/transpiler/visitor/lookup.cpp create mode 100644 test/nmodl/transpiler/visitor/loop_unroll.cpp create mode 100644 test/nmodl/transpiler/visitor/main.cpp create mode 100644 test/nmodl/transpiler/visitor/misc.cpp create mode 100644 test/nmodl/transpiler/visitor/neuron_solve.cpp create mode 100644 test/nmodl/transpiler/visitor/nmodl.cpp create mode 100644 test/nmodl/transpiler/visitor/perf.cpp create mode 100644 test/nmodl/transpiler/visitor/rename.cpp create mode 100644 test/nmodl/transpiler/visitor/solve_block.cpp create mode 100644 test/nmodl/transpiler/visitor/steadystate.cpp create mode 100644 test/nmodl/transpiler/visitor/sympy_conductance.cpp create mode 100644 test/nmodl/transpiler/visitor/sympy_solver.cpp create mode 100644 test/nmodl/transpiler/visitor/verbatim.cpp delete mode 100644 test/nmodl/transpiler/visitor/visitor.cpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 49e11c4444..029d2faf34 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -66,6 +66,7 @@ add_custom_target(nb-format # Include cmake modules # ============================================================================= list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +include(Catch) include(ClangTidyHelper) include(CompilerHelper) include(FindClangFormat) diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake new file mode 100644 index 0000000000..d0b81ffe50 --- /dev/null +++ b/cmake/nmodl/Catch.cmake @@ -0,0 +1,160 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or +# https://cmake.org/licensing for details. + +#[=======================================================================[.rst: +Catch +----- + +This module defines a function to help use the Catch test framework. + +The :command:`catch_discover_tests` discovers tests by asking the compiled test +executable to enumerate its tests. This does not require CMake to be re-run +when tests change. However, it may not work in a cross-compiling environment, +and setting test properties is less convenient. + +This command is intended to replace use of :command:`add_test` to register +tests, and will create a separate CTest test for each Catch test case. Note +that this is in some cases less efficient, as common set-up and tear-down logic +cannot be shared by multiple test cases executing in the same instance. +However, it provides more fine-grained pass/fail information to CTest, which is +usually considered as more beneficial. By default, the CTest test name is the +same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. + +.. command:: catch_discover_tests + + Automatically add tests with CTest by querying the compiled test executable + for available tests:: + + catch_discover_tests(target + [TEST_SPEC arg1...] + [EXTRA_ARGS arg1...] + [WORKING_DIRECTORY dir] + [TEST_PREFIX prefix] + [TEST_SUFFIX suffix] + [PROPERTIES name1 value1...] + [TEST_LIST var] + ) + + ``catch_discover_tests`` sets up a post-build command on the test executable + that generates the list of tests by parsing the output from running the test + with the ``--list-test-names-only`` argument. This ensures that the full + list of tests is obtained. Since test discovery occurs at build time, it is + not necessary to re-run CMake when the list of tests changes. + However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set + in order to function in a cross-compiling environment. + + Additionally, setting properties on tests is somewhat less convenient, since + the tests are not available at CMake time. Additional test properties may be + assigned to the set of tests as a whole using the ``PROPERTIES`` option. If + more fine-grained test control is needed, custom content may be provided + through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` + directory property. The set of discovered tests is made accessible to such a + script via the ``<target>_TESTS`` variable. + + The options are: + + ``target`` + Specifies the Catch executable, which must be a known CMake executable + target. CMake will substitute the location of the built executable when + running the test. + + ``TEST_SPEC arg1...`` + Specifies test cases, wildcarded test cases, tags and tag expressions to + pass to the Catch executable with the ``--list-test-names-only`` argument. + + ``EXTRA_ARGS arg1...`` + Any extra arguments to pass on the command line to each test case. + + ``WORKING_DIRECTORY dir`` + Specifies the directory in which to run the discovered test cases. If this + option is not provided, the current binary directory is used. + + ``TEST_PREFIX prefix`` + Specifies a ``prefix`` to be prepended to the name of each discovered test + case. This can be useful when the same test executable is being used in + multiple calls to ``catch_discover_tests()`` but with different + ``TEST_SPEC`` or ``EXTRA_ARGS``. + + ``TEST_SUFFIX suffix`` + Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of + every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may + be specified. + + ``PROPERTIES name1 value1...`` + Specifies additional properties to be set on all tests discovered by this + invocation of ``catch_discover_tests``. + + ``TEST_LIST var`` + Make the list of tests available in the variable ``var``, rather than the + default ``<target>_TESTS``. This can be useful when the same test + executable is being used in multiple calls to ``catch_discover_tests()``. + Note that this variable is only available in CTest. + +#]=======================================================================] + +# ------------------------------------------------------------------------------ +function(catch_discover_tests TARGET) + cmake_parse_arguments("" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" + ${ARGN}) + + if(NOT _WORKING_DIRECTORY) + set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + if(NOT _TEST_LIST) + set(_TEST_LIST ${TARGET}_TESTS) + endif() + + # Generate a unique name based on the extra arguments + string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") + string(SUBSTRING ${args_hash} + 0 + 7 + args_hash) + + # Define rule to generate test list for aforementioned test executable + set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") + set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") + get_property(crosscompiling_emulator TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR) + add_custom_command(TARGET + ${TARGET} + POST_BUILD + BYPRODUCTS + "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D + "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>" -D + "TEST_EXECUTOR=${crosscompiling_emulator}" -D + "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_PREFIX=${_TEST_PREFIX}" -D "TEST_SUFFIX=${_TEST_SUFFIX}" -D + "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" -P + "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n") + + if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") + else() + # Add discovered tests as directory TEST_INCLUDE_FILE if possible + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + if(NOT ${test_include_file_set}) + set_property(DIRECTORY PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}") + else() + message(FATAL_ERROR "Cannot set more than one TEST_INCLUDE_FILE") + endif() + endif() + +endfunction() + +# + +set(_CATCH_DISCOVER_TESTS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake) diff --git a/cmake/nmodl/CatchAddTests.cmake b/cmake/nmodl/CatchAddTests.cmake new file mode 100644 index 0000000000..2b4cd9b22f --- /dev/null +++ b/cmake/nmodl/CatchAddTests.cmake @@ -0,0 +1,104 @@ +# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or +# https://cmake.org/licensing for details. + +set(prefix "${TEST_PREFIX}") +set(suffix "${TEST_SUFFIX}") +set(spec ${TEST_SPEC}) +set(extra_args ${TEST_EXTRA_ARGS}) +set(properties ${TEST_PROPERTIES}) +set(script) +set(suite) +set(tests) + +function(add_command NAME) + set(_args "") + foreach(_arg ${ARGN}) + if(_arg MATCHES "[^-./:a-zA-Z0-9_]") + set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument + else() + set(_args "${_args} ${_arg}") + endif() + endforeach() + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) +endfunction() + +macro(_add_catch_test_labels LINE) + # convert to list of tags + string(REPLACE "][" + "]\\;[" + tags + ${line}) + + add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES LABELS "${tags}") +endmacro() + +macro(_add_catch_test LINE) + set(test ${line}) + # use escape commas to handle properly test cases with commans inside the name + string(REPLACE "," + "\\," + test_name + ${test}) + # ...and add to script + add_command(add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "${test_name}" + ${extra_args}) + + add_command(set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY + "${TEST_WORKING_DIR}" + ${properties}) + list(APPEND tests "${prefix}${test}${suffix}") +endmacro() + +# Run test executable to get list of available tests +if(NOT EXISTS "${TEST_EXECUTABLE}") + message(FATAL_ERROR "Specified test executable '${TEST_EXECUTABLE}' does not exist") +endif() +execute_process(COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests + OUTPUT_VARIABLE output + RESULT_VARIABLE result) +# Catch --list-test-names-only reports the number of tests, so 0 is... surprising +if(${result} EQUAL 0) + message(WARNING "Test executable '${TEST_EXECUTABLE}' contains no tests!\n") +elseif(${result} LESS 0) + message(FATAL_ERROR "Error running test executable '${TEST_EXECUTABLE}':\n" + " Result: ${result}\n" " Output: ${output}\n") +endif() + +string(REPLACE "\n" + ";" + output + "${output}") +set(test) +set(tags_regex "(\\[([^\\[]*)\\])+$") + +# Parse output +foreach(line ${output}) + # lines without leading whitespaces are catch output not tests + if(${line} MATCHES "^[ \t]+") + # strip leading spaces and tabs + string(REGEX + REPLACE "^[ \t]+" + "" + line + ${line}) + + if(${line} MATCHES "${tags_regex}") + _add_catch_test_labels(${line}) + else() + _add_catch_test(${line}) + endif() + endif() +endforeach() + +# Create a list of all discovered tests, which users may use to e.g. set properties on the tests +add_command(set ${TEST_LIST} ${tests}) + +# Write CTest script +file(WRITE "${CTEST_FILE}" "${script}") diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index dca61a285d..44e3371c7f 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -456,7 +456,8 @@ void SympySolverVisitor::visit_conserve(ast::Conserve* node) { } auto conserve_equation_str = to_nmodl_for_sympy(node->get_expr().get()); logger->debug("SympySolverVisitor :: --> replace ODE for state var {} with equation {}", - conserve_equation_statevar, conserve_equation_str); + conserve_equation_statevar, + conserve_equation_str); conserve_equation[conserve_equation_statevar] = conserve_equation_str; } @@ -495,7 +496,8 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { eq = state_var_name + " = " + var_eq_pair->second; logger->debug( "SympySolverVisitor :: -> instead of Euler eq using CONSERVE equation: {} = {}", - state_var_name, var_eq_pair->second); + state_var_name, + var_eq_pair->second); } else { // no CONSERVE equation, construct Euler equation auto dxdt = stringutils::trim(split_eq[1]); diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index deacc1bfe8..ab64a26da1 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -24,7 +24,26 @@ add_library(config STATIC ${PROJECT_BINARY_DIR}/config.cpp) add_executable(testmodtoken modtoken/modtoken.cpp) add_executable(testlexer lexer/tokens.cpp) add_executable(testparser parser/parser.cpp) -add_executable(testvisitor visitor/visitor.cpp) +add_executable(testvisitor + visitor/main.cpp + visitor/constant_folder.cpp + visitor/defuse_analyze.cpp + visitor/inline.cpp + visitor/json.cpp + visitor/kinetic_block.cpp + visitor/localize.cpp + visitor/lookup.cpp + visitor/loop_unroll.cpp + visitor/misc.cpp + visitor/neuron_solve.cpp + visitor/nmodl.cpp + visitor/perf.cpp + visitor/rename.cpp + visitor/solve_block.cpp + visitor/steadystate.cpp + visitor/sympy_conductance.cpp + visitor/sympy_solver.cpp + visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) @@ -40,25 +59,33 @@ target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) target_link_libraries(testunitparser lexer test_util config) -add_test(NAME ModToken COMMAND testmodtoken) -add_test(NAME Lexer COMMAND testlexer) -add_test(NAME Parser COMMAND testparser) -add_test(NAME Visitor COMMAND testvisitor) -add_test(NAME Printer COMMAND testprinter) -add_test(NAME Symtab COMMAND testsymtab) -add_test(NAME Newton COMMAND testnewton) -add_test(NAME UnitLexer COMMAND testunitlexer) -add_test(NAME UnitParser COMMAND testunitparser) - -set_tests_properties(Visitor PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) +# ============================================================================= +# Use catch_discover instead of add_test for granular test report +# ============================================================================= +foreach(test_name + testmodtoken + testlexer + testparser + testvisitor + testprinter + testsymtab + testnewton + testunitlexer + testunitparser) + catch_discover_tests(${test_name} + PROPERTIES + ENVIRONMENT + PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) +endforeach() # ============================================================================= # pybind11 tests # ============================================================================= - add_test(NAME Ode COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/ode) -set_tests_properties(Ode PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) add_test(NAME Pybind COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) -set_tests_properties(Pybind PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) +foreach(test_name Ode Pybind) + set_tests_properties(${test_name} + PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) +endforeach() diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 80ef86bd46..c1e6c6c854 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -81,14 +81,14 @@ TokenType token_type(const std::string& name) { return token; } -TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { - SECTION("Tests for some keywords") { +TEST_CASE("NMODL Lexer returning valid token types", "[Lexer]") { + SECTION("Some keywords") { REQUIRE(token_type("VERBATIM Hello ENDVERBATIM") == Token::VERBATIM); REQUIRE(token_type("INITIAL") == Token::INITIAL1); REQUIRE(token_type("SOLVE") == Token::SOLVE); } - SECTION("Tests for language constructs") { + SECTION("NMODL language keywords and constructs") { REQUIRE(token_type(" h' = (hInf-h)/hTau\n") == Token::PRIME); REQUIRE(token_type("while") == Token::WHILE); REQUIRE(token_type("if") == Token::IF); @@ -98,7 +98,7 @@ TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { REQUIRE(token_type("ELSE") == Token::ELSE); } - SECTION("Tests for valid numbers") { + SECTION("Different number types") { REQUIRE(token_type("123") == Token::INTEGER); REQUIRE(token_type("123.32") == Token::REAL); REQUIRE(token_type("1.32E+3") == Token::REAL); @@ -108,12 +108,12 @@ TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { REQUIRE(token_type("1e-23") == Token::REAL); } - SECTION("Tests for Name/Strings") { + SECTION("Name/Strings types") { REQUIRE(token_type("neuron") == Token::NAME); REQUIRE(token_type("\"Quoted String\"") == Token::STRING); } - SECTION("Tests for (math) operators") { + SECTION("Logical operator types") { REQUIRE(token_type(">") == Token::GT); REQUIRE(token_type(">=") == Token::GE); REQUIRE(token_type("<") == Token::LT); @@ -121,10 +121,9 @@ TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { REQUIRE(token_type("!=") == Token::NE); REQUIRE(token_type("<->") == Token::REACT1); REQUIRE(token_type("~+") == Token::NONLIN1); - // REQUIRE( token_type("~") == Token::REACTION); } - SECTION("Tests for braces") { + SECTION("Brace types") { REQUIRE(token_type("{") == Token::OPEN_BRACE); REQUIRE(token_type("}") == Token::CLOSE_BRACE); REQUIRE(token_type("(") == Token::OPEN_PARENTHESIS); diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index b055033c87..9ffbdda520 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -38,10 +38,9 @@ void symbol_type(const std::string& name, T& value) { value = sym.value.as<T>(); } -TEST_CASE("Lexer symbol type tests") { - SECTION("test for ast class of type Name") { +TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { + SECTION("test for ast::Name") { ast::Name value; - { std::stringstream ss; symbol_type("text", value); @@ -57,9 +56,8 @@ TEST_CASE("Lexer symbol type tests") { } } - SECTION("test for ast class of type Prime") { + SECTION("test for ast::Prime") { ast::PrimeName value; - { std::stringstream ss; symbol_type("h'' = ", value); diff --git a/test/nmodl/transpiler/newton/newton.cpp b/test/nmodl/transpiler/newton/newton.cpp index 1b06231d16..c21db4e47f 100644 --- a/test/nmodl/transpiler/newton/newton.cpp +++ b/test/nmodl/transpiler/newton/newton.cpp @@ -16,7 +16,7 @@ using namespace nmodl; constexpr double max_error_norm = 1e-12; -SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numerical]") { +SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numerical][solver]") { GIVEN("1 linear eq") { struct functor { void operator()(const Eigen::Matrix<double, 1, 1>& X, @@ -38,6 +38,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer REQUIRE(F.norm() < max_error_norm); } } + GIVEN("1 non-linear eq") { struct functor { void operator()(const Eigen::Matrix<double, 1, 1>& X, @@ -58,6 +59,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 2 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix<double, 2, 1>& X, @@ -78,6 +80,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 3 non-linear eqs") { struct functor { double _x_old = 0.5; @@ -108,6 +111,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 4 non-linear eqs") { struct functor { double _X0_old = 1.2345; @@ -136,6 +140,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 5 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix<double, 5, 1>& X, @@ -161,6 +166,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 10 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix<double, 10, 1>& X, @@ -195,7 +201,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer } } -SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { +SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") { GIVEN("1 linear eq") { struct functor { void operator()(const Eigen::Matrix<double, 1, 1>& X, @@ -221,6 +227,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { REQUIRE(F.norm() < max_error_norm); } } + GIVEN("1 non-linear eq") { struct functor { void operator()(const Eigen::Matrix<double, 1, 1>& X, @@ -244,6 +251,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 2 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix<double, 2, 1>& X, @@ -270,6 +278,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 3 non-linear eqs") { struct functor { double _x_old = 0.5; @@ -311,6 +320,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 4 non-linear eqs") { struct functor { double _X0_old = 1.2345; @@ -357,6 +367,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 5 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix<double, 5, 1>& X, @@ -410,6 +421,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { REQUIRE(F.norm() < max_error_norm); } } + GIVEN("system of 10 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix<double, 10, 1>& X, @@ -530,6 +542,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic]") { J(9, 9) = 8.0 * X[9] + 1.0; } }; + Eigen::Matrix<double, 10, 1> X; X << 8.234, -5.46, 1.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; Eigen::Matrix<double, 10, 1> F; diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 74d6e72652..aa287b998f 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -27,7 +27,7 @@ bool is_valid_construct(const std::string& construct) { } -SCENARIO("NMODL can define macros using DEFINE keyword") { +SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { GIVEN("A valid macro definition") { WHEN("DEFINE NSTEP 6") { THEN("parser accepts without an error") { @@ -87,7 +87,7 @@ SCENARIO("NMODL can define macros using DEFINE keyword") { } } -SCENARIO("Macros can be used anywhere in NMODL program") { +SCENARIO("Macros can be used anywhere in the mod file") { std::string nmodl_text = R"( DEFINE NSTEP 6 PARAMETER { @@ -101,7 +101,7 @@ SCENARIO("Macros can be used anywhere in NMODL program") { } } -SCENARIO("Parser for empty unit") { +SCENARIO("NMODL parser accepts empty unit specification") { std::string nmodl_text = R"( FUNCTION ssCB(kdf(), kds()) (mM) { @@ -114,7 +114,7 @@ SCENARIO("Parser for empty unit") { } } -SCENARIO("Parser test for valid NMODL grammar constructs") { +SCENARIO("NMODL parser running number of valid NMODL constructs") { for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { @@ -125,7 +125,7 @@ SCENARIO("Parser test for valid NMODL grammar constructs") { } } -SCENARIO("Parser test for invalid NMODL grammar constructs") { +SCENARIO("NMODL parser running number of invalid NMODL constructs") { for (const auto& construct: nmdol_invalid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { @@ -147,7 +147,7 @@ std::string solve_construct(const std::string& equation, std::string method) { return solution; } -SCENARIO("Solving differential equations using NEURON's implementation") { +SCENARIO("Legacy differential equation solver from NEURON solve number of ODE types") { GIVEN("A differential equation") { int counter = 0; for (const auto& test_case: diff_eq_constructs) { @@ -162,4 +162,4 @@ SCENARIO("Solving differential equations using NEURON's implementation") { counter++; } } -} +} \ No newline at end of file diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 07ba940254..72b88eeddc 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -14,7 +14,7 @@ using nmodl::printer::JSONPrinter; -TEST_CASE("JSON Printer Tests", "[JSONPrinter]") { +TEST_CASE("JSON printer converting object to string form", "[printer][json]") { SECTION("Stringstream test 1") { std::stringstream ss; JSONPrinter p(ss); diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 42d96b9f38..8c1af9ca9c 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -90,7 +90,7 @@ SCENARIO("Symbol properties can be added and converted to string") { // Symbol test //============================================================================= -SCENARIO("Symbol operations") { +SCENARIO("Multiple properties can be added to Symbol") { NmodlType property1 = NmodlType::argument; NmodlType property2 = NmodlType::range_var; NmodlType property3 = NmodlType::discrete_block; @@ -144,7 +144,7 @@ SCENARIO("Symbol operations") { // Symbol table test //============================================================================= -SCENARIO("Symbol table operations") { +SCENARIO("Symbol table allows operations like insert, lookup") { GIVEN("A global SymbolTable") { auto program = std::make_shared<ast::Program>(); auto table = std::make_shared<SymbolTable>("Na", program.get(), true); @@ -257,7 +257,7 @@ SCENARIO("Symbol table operations") { // Model symbol table test //============================================================================= -SCENARIO("Model symbol table operations") { +SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { GIVEN("A Model symbolTable") { ModelSymbolTable mod_symtab; diff --git a/test/nmodl/transpiler/units/lexer.cpp b/test/nmodl/transpiler/units/lexer.cpp index f532bcb399..0d709a801f 100644 --- a/test/nmodl/transpiler/units/lexer.cpp +++ b/test/nmodl/transpiler/units/lexer.cpp @@ -32,7 +32,7 @@ TokenType token_type(const std::string& name) { return scanner.next_token().token(); } -TEST_CASE("Lexer tests for valid tokens", "[Lexer]") { +TEST_CASE("Unit Lexer tests for valid tokens", "[lexer][unit]") { SECTION("Tests for comments") { REQUIRE(token_type("/ comment") == Token::COMMENT); REQUIRE(token_type("/comment") == Token::COMMENT); diff --git a/test/nmodl/transpiler/units/parser.cpp b/test/nmodl/transpiler/units/parser.cpp index b21d164798..08886af492 100644 --- a/test/nmodl/transpiler/units/parser.cpp +++ b/test/nmodl/transpiler/units/parser.cpp @@ -45,7 +45,7 @@ bool is_substring(const std::string& str1, const std::string& str2) { return str1.find(str2) != std::string::npos; } -SCENARIO("Unit parser definition of units validity") { +SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { GIVEN("A base unit") { WHEN("Base unit is *a*") { THEN("parser accepts without an error") { @@ -152,7 +152,7 @@ SCENARIO("Unit parser definition of units validity") { } } -SCENARIO("Unit parser definition of units correctness") { +SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][parser]") { GIVEN("Parsed the nrnunits.lib file") { WHEN("Multiple units definitions based on the units defined in nrnunits.lib") { THEN("parser accepts the units correctly") { diff --git a/test/nmodl/transpiler/visitor/constant_folder.cpp b/test/nmodl/transpiler/visitor/constant_folder.cpp new file mode 100644 index 0000000000..ea837e5037 --- /dev/null +++ b/test/nmodl/transpiler/visitor/constant_folder.cpp @@ -0,0 +1,162 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Constant folding tests +//============================================================================= + +std::string run_constant_folding_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +SCENARIO("Perform constant folder on NMODL constructs") { + GIVEN("Simple integer expression") { + std::string nmodl_text = R"( + PROCEDURE dummy() { + a = 1 + 2 + } + )"; + std::string expected_text = R"( + PROCEDURE dummy() { + a = 3 + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Simple double expression") { + std::string nmodl_text = R"( + PROCEDURE dummy() { + a = 1.1 + 2e-10 + } + )"; + std::string expected_text = R"( + PROCEDURE dummy() { + a = 1.1000000002 + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Complex expression") { + std::string nmodl_text = R"( + PROCEDURE dummy() { + a = 1 + (2) + (2 / 2) + (((1+((2))))) + } + )"; + std::string expected_text = R"( + PROCEDURE dummy() { + a = 7 + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Integer expression with define statement") { + std::string nmodl_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = N + (2*N) + (N / 2) + (((1+((N))))) + FROM i = 0 TO N-2 { + } + } + )"; + std::string expected_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = 46 + FROM i = 0 TO 8 { + } + } + )"; + THEN("successfully folds") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Only fold part of the statement") { + std::string nmodl_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = N + 2.0 + b + c = a + d + d = 2^3 + e = 2 || 3 + } + )"; + std::string expected_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = 12+b + c = a+d + d = 2^3 + e = 2 || 3 + } + )"; + THEN("successfully folds and keep other statements untouched") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } + + GIVEN("Don't remove parentheses if not simplifying") { + std::string nmodl_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = ((N+1)+5)*(c+1+N)/(b - 2) + } + )"; + std::string expected_text = R"( + DEFINE N 10 + + PROCEDURE dummy() { + a = 16*(c+1+10)/(b-2) + } + )"; + THEN("successfully folds and keep other statements untouched") { + auto result = run_constant_folding_visitor(nmodl_text); + REQUIRE(reindent_text(result) == reindent_text(expected_text)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/visitor/defuse_analyze.cpp new file mode 100644 index 0000000000..1b1294381f --- /dev/null +++ b/test/nmodl/transpiler/visitor/defuse_analyze.cpp @@ -0,0 +1,322 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/defuse_analyze_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + +//============================================================================= +// DefUseAnalyze visitor tests +//============================================================================= + +std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string& variable) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); + + std::vector<DUChain> chains; + DefUseAnalyzeVisitor v(ast->get_symbol_table()); + + /// analyse only derivative blocks in this test + auto blocks = AstLookupVisitor().lookup(ast.get(), AstNodeType::DERIVATIVE_BLOCK); + for (auto& block: blocks) { + auto node = block.get(); + chains.push_back(v.analyze(node, variable)); + } + return chains; +} + +SCENARIO("Perform DefUse analysis on NMODL constructs") { + GIVEN("global variable usage in assignment statements") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + tau = 1 + tau = 1 + tau + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"name":"D"},{"name":"U"},{"name":"D"}]})"; + + THEN("Def-Use chains for individual usage is printed") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string(true) == expected_text); + REQUIRE(chains[0].eval() == DUState::D); + } + } + + GIVEN("block with use of verbatim block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + VERBATIM ENDVERBATIM + tau = 1 + VERBATIM ENDVERBATIM + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"name":"U"},{"name":"D"},{"name":"U"}]})"; + + THEN("Verbatim block is considered as use of the variable") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string(true) == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("use of array variables") { + std::string nmodl_text = R"( + DEFINE N 3 + STATE { + m[N] + h[N] + n[N] + o[N] + } + DERIVATIVE states { + LOCAL tau[N] + tau[0] = 1 : tau[0] is defined + tau[2] = 1 + tau[1] + tau[2] : tau[1] is used; tau[2] is defined as well as used + m[0] = m[1] : m[0] is defined and used on next line; m[1] is used + h[1] = m[0] + h[0] : h[0] is used; h[1] is defined + o[i] = 1 : o[i] is defined for any i + n[i+1] = 1 + n[i] : n[i] is used as well as defined for any i + } + )"; + + THEN("Def-Use analyser distinguishes variables by array index") { + std::string input = reindent_text(nmodl_text); + { + auto m0 = run_defuse_visitor(input, "m[0]"); + auto m1 = run_defuse_visitor(input, "m[1]"); + auto h1 = run_defuse_visitor(input, "h[1]"); + auto tau0 = run_defuse_visitor(input, "tau[0]"); + auto tau1 = run_defuse_visitor(input, "tau[1]"); + auto tau2 = run_defuse_visitor(input, "tau[2]"); + auto n0 = run_defuse_visitor(input, "n[0]"); + auto n1 = run_defuse_visitor(input, "n[1]"); + auto o0 = run_defuse_visitor(input, "o[0]"); + + REQUIRE(m0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"},{"name":"U"}]})"); + REQUIRE(m1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"}]})"); + REQUIRE(h1[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); + REQUIRE(tau0[0].to_string() == R"({"DerivativeBlock":[{"name":"LD"}]})"); + REQUIRE(tau1[0].to_string() == R"({"DerivativeBlock":[{"name":"LU"}]})"); + REQUIRE(tau2[0].to_string() == + R"({"DerivativeBlock":[{"name":"LU"},{"name":"LD"}]})"); + REQUIRE(n0[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); + REQUIRE(n1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); + REQUIRE(o0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); + } + } + } + + GIVEN("global variable definition in else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + LOCAL tau + tau = 1 + } ELSE { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]},{"ELSE":[{"name":"D"}]}]}]})"; + + THEN("Def-Use chains should return NONE") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::CD); + } + } + + GIVEN("global variable usage in else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + } ELSE { + tau = 1 + tau + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"U"},{"name":"D"}]}]}]})"; + + THEN("Def-Use chains should return USE") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("global variable definition in if-else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + IF (1) { + tau = 11.1 + exp(tau) + } ELSE { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"},{"name":"U"}]},{"ELSE":[{"name":"D"}]}]}]})"; + + THEN("Def-Use chains should return DEF") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::D); + } + } + + GIVEN("conditional definition in nested block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + IF(11) { + tau = 11.1 + exp(tau) + } + } ELSE IF(1) { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"},{"name":"U"}]}]}]},{"ELSEIF":[{"name":"D"}]}]}]})"; + + THEN("Def-Use chains should return DEF") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::CD); + } + } + + GIVEN("global variable usage in if-elseif-else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + tau = 1 + } + tau = 1 + tau + IF (0) { + beta = 1 + } ELSE IF (2) { + tau = 1 + } + } + + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"}]}]},{"name":"U"},{"name":"D"},{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSEIF":[{"name":"D"}]}]}]})"; + + THEN("Def-Use chains for individual usage is printed") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("global variable used in nested if-elseif-else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + DERIVATIVE states { + IF (1) { + LOCAL tau + tau = 1 + } + IF (0) { + IF (1) { + beta = 1 + } ELSE { + tau = 1 + } + } ELSE IF (2) { + IF (1) { + beta = 1 + IF (0) { + } ELSE { + beta = 1 + exp(tau) + } + } + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]}]},{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"D"}]}]}]},{"ELSEIF":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"U"}]}]}]}]},{"name":"D"}]}]}]})"; + + THEN("Def-Use chains for nested statements calculated") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } +} diff --git a/test/nmodl/transpiler/visitor/inline.cpp b/test/nmodl/transpiler/visitor/inline.cpp new file mode 100644 index 0000000000..0d937cd000 --- /dev/null +++ b/test/nmodl/transpiler/visitor/inline.cpp @@ -0,0 +1,605 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Procedure/Function inlining tests +//============================================================================= + +std::string run_inline_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +SCENARIO("Inlining of external procedure calls", "[visitor][inline]") { + GIVEN("Procedures with external procedure call") { + std::string nmodl_text = R"( + PROCEDURE rates_1() { + hello() + } + + PROCEDURE rates_2() { + bye() + } + )"; + + THEN("nothing gets inlinine") { + std::string input = reindent_text(nmodl_text); + auto result = run_inline_visitor(input); + REQUIRE(result == input); + } + } +} + +SCENARIO("Inlining of simple, one level procedure call", "[visitor][inline]") { + GIVEN("A procedure calling another procedure") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + rates_2(23.1) + } + + PROCEDURE rates_2(y) { + LOCAL x + x = 21.1*v+y + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + { + LOCAL x, y_in_0 + y_in_0 = 23.1 + x = 21.1*v+y_in_0 + } + } + + PROCEDURE rates_2(y) { + LOCAL x + x = 21.1*v+y + } + )"; + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inlining of nested procedure call", "[visitor][inline]") { + GIVEN("A procedure with nested call chain and arguments") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, y + rates_2() + rates_3(x, y) + } + + PROCEDURE rates_2() { + LOCAL x + x = 21.1*v + rates_3(x, x+1.1) + } + + PROCEDURE rates_3(a, b) { + LOCAL c + c = 21.1*v+a*b + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, y + { + LOCAL x, rates_3_in_0 + { + LOCAL c, a_in_0, b_in_0 + a_in_0 = x + b_in_0 = x+1.1 + c = 21.1*v+a_in_0*b_in_0 + rates_3_in_0 = 0 + } + x = 21.1*v+rates_3_in_0 + } + { + LOCAL c, a_in_1, b_in_1 + a_in_1 = x + b_in_1 = y + c = 21.1*v+a_in_1*b_in_1 + } + } + + PROCEDURE rates_2() { + LOCAL x, rates_3_in_0 + { + LOCAL c, a_in_0, b_in_0 + a_in_0 = x + b_in_0 = x+1.1 + c = 21.1*v+a_in_0*b_in_0 + rates_3_in_0 = 0 + } + x = 21.1*v+rates_3_in_0 + } + + PROCEDURE rates_3(a, b) { + LOCAL c + c = 21.1*v+a*b + } + )"; + THEN("Nested procedure gets inlined with variables renaming") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inline function call in procedure", "[visitor][inline]") { + GIVEN("A procedure with function call") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + x = 12.1+rates_2() + } + + FUNCTION rates_2() { + LOCAL x + x = 21.1*12.1+11 + rates_2 = x + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL x + x = 21.1*12.1+11 + rates_2_in_0 = x + } + x = 12.1+rates_2_in_0 + } + + FUNCTION rates_2() { + LOCAL x + x = 21.1*12.1+11 + rates_2 = x + } + )"; + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inling function call within conditional statement", "[visitor][inline]") { + GIVEN("A procedure with function call in if statement") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + IF (rates_2()) { + rates_1 = 10 + } ELSE { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL rates_2_in_0 + { + rates_2_in_0 = 10 + } + IF (rates_2_in_0) { + rates_1 = 10 + } ELSE { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + THEN("Procedure body gets inlined and return value is used in if condition") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inline multiple function calls in same statement", "[visitor][inline]") { + GIVEN("A procedure with two function calls in binary expression") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + IF (rates_2()-rates_2()) { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL rates_2_in_0, rates_2_in_1 + { + rates_2_in_0 = 10 + } + { + rates_2_in_1 = 10 + } + IF (rates_2_in_0-rates_2_in_1) { + rates_1 = 20 + } + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("A procedure with multiple function calls in an expression") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + LOCAL x + x = (rates_2()+(rates_2()/rates_2())) + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL x, rates_2_in_0, rates_2_in_1, rates_2_in_2 + { + rates_2_in_0 = 10 + } + { + rates_2_in_1 = 10 + } + { + rates_2_in_2 = 10 + } + x = (rates_2_in_0+(rates_2_in_1/rates_2_in_2)) + } + + FUNCTION rates_2() { + rates_2 = 10 + } + )"; + + THEN("Procedure body gets inlined and return values are used in an expression") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inline nested function calls withing arguments", "[visitor][inline]") { + GIVEN("A procedure with function call") { + std::string input_nmodl = R"( + FUNCTION rates_2() { + IF (rates_3(11,21)) { + rates_2 = 10.1 + } + rates_2 = rates_3(12,22) + } + + FUNCTION rates_1() { + rates_1 = 12.1+rates_2()+exp(12.1) + } + + FUNCTION rates_3(x, y) { + rates_3 = x+y + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_2() { + LOCAL rates_3_in_0, rates_3_in_1 + { + LOCAL x_in_0, y_in_0 + x_in_0 = 11 + y_in_0 = 21 + rates_3_in_0 = x_in_0+y_in_0 + } + IF (rates_3_in_0) { + rates_2 = 10.1 + } + { + LOCAL x_in_1, y_in_1 + x_in_1 = 12 + y_in_1 = 22 + rates_3_in_1 = x_in_1+y_in_1 + } + rates_2 = rates_3_in_1 + } + + FUNCTION rates_1() { + LOCAL rates_2_in_0 + { + LOCAL rates_3_in_0, rates_3_in_1 + { + LOCAL x_in_0, y_in_0 + x_in_0 = 11 + y_in_0 = 21 + rates_3_in_0 = x_in_0+y_in_0 + } + IF (rates_3_in_0) { + rates_2_in_0 = 10.1 + } + { + LOCAL x_in_1, y_in_1 + x_in_1 = 12 + y_in_1 = 22 + rates_3_in_1 = x_in_1+y_in_1 + } + rates_2_in_0 = rates_3_in_1 + } + rates_1 = 12.1+rates_2_in_0+exp(12.1) + } + + FUNCTION rates_3(x, y) { + rates_3 = x+y + } + )"; + + THEN("Procedure body gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inline function call in non-binary expression", "[visitor][inline]") { + GIVEN("A function call in unary expression") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + x = (-rates_2(23.1)) + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL y_in_0 + y_in_0 = 23.1 + rates_2_in_0 = 21.1*v+y_in_0 + } + x = (-rates_2_in_0) + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + THEN("Function gets inlined in the unary expression") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("A function call as part of function argument itself") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + rates_1 = 10 + rates_2( rates_2(11) ) + } + + FUNCTION rates_2(x) { + rates_2 = 10+x + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + LOCAL rates_2_in_0, rates_2_in_1 + { + LOCAL x_in_0 + x_in_0 = 11 + rates_2_in_0 = 10+x_in_0 + } + { + LOCAL x_in_1 + x_in_1 = rates_2_in_0 + rates_2_in_1 = 10+x_in_1 + } + rates_1 = 10+rates_2_in_1 + } + + FUNCTION rates_2(x) { + rates_2 = 10+x + } + )"; + THEN("Function and it's arguments gets inlined recursively") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + + +SCENARIO("Inline function call as standalone expression", "[visitor][inline]") { + GIVEN("Function call as a statement") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + rates_2(23.1) + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + LOCAL y_in_0 + y_in_0 = 23.1 + rates_2_in_0 = 21.1*v+y_in_0 + } + } + + FUNCTION rates_2(y) { + rates_2 = 21.1*v+y + } + )"; + THEN("Function gets inlined but it's value is not used") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inline procedure call as standalone statement as well as part of expression", + "[visitor][inline]") { + GIVEN("A procedure call in expression and statement") { + std::string input_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x + x = 10 + rates_2() + rates_2() + } + + PROCEDURE rates_2() { + } + )"; + + std::string output_nmodl = R"( + PROCEDURE rates_1() { + LOCAL x, rates_2_in_0 + { + rates_2_in_0 = 0 + } + x = 10+rates_2_in_0 + { + } + } + + PROCEDURE rates_2() { + } + )"; + THEN("Return statement from procedure (with zero value) is used") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Inlining pass handles local-global name conflict", "[visitor][inline]") { + GIVEN("A procedure with local variable that exist in global scope") { + /// note that x in rates_2 should still update global x after inlining + std::string input_nmodl = R"( + NEURON { + RANGE x + } + + PROCEDURE rates_1() { + LOCAL x + x = 12 + rates_2(x) + x = 11 + } + + PROCEDURE rates_2(y) { + x = 10+y + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE x + } + + PROCEDURE rates_1() { + LOCAL x_r_0 + x_r_0 = 12 + { + LOCAL y_in_0 + y_in_0 = x_r_0 + x = 10+y_in_0 + } + x_r_0 = 11 + } + + PROCEDURE rates_2(y) { + x = 10+y + } + )"; + + THEN("Caller variables get renamed first and then inlining is done") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} diff --git a/test/nmodl/transpiler/visitor/json.cpp b/test/nmodl/transpiler/visitor/json.cpp new file mode 100644 index 0000000000..0a1a2ed6f5 --- /dev/null +++ b/test/nmodl/transpiler/visitor/json.cpp @@ -0,0 +1,63 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/json_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +using json = nlohmann::json; + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// JSON visitor tests +//============================================================================= + +std::string run_json_visitor(const std::string& text, bool compact = false) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + return to_json(ast.get(), compact); +} + +TEST_CASE("Convert NMODL to AST to JSON form using JSONVisitor", "[visitor][json]") { + SECTION("JSON object test") { + std::string nmodl_text = "NEURON {}"; + json expected = R"( + { + "Program": [ + { + "NeuronBlock": [ + { + "StatementBlock": [] + } + ] + } + ] + } + )"_json; + + auto json_text = run_json_visitor(nmodl_text); + json result = json::parse(json_text); + + REQUIRE(expected == result); + } + + SECTION("JSON text test (compact format)") { + std::string nmodl_text = "NEURON {}"; + std::string expected = R"({"Program":[{"NeuronBlock":[{"StatementBlock":[]}]}]})"; + + auto result = run_json_visitor(nmodl_text, true); + REQUIRE(result == expected); + } +} diff --git a/test/nmodl/transpiler/visitor/kinetic_block.cpp b/test/nmodl/transpiler/visitor/kinetic_block.cpp new file mode 100644 index 0000000000..720aad371f --- /dev/null +++ b/test/nmodl/transpiler/visitor/kinetic_block.cpp @@ -0,0 +1,618 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/loop_unroll_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + +//============================================================================= +// KineticBlock visitor tests +//============================================================================= + +std::vector<std::string> run_kinetic_block_visitor(const std::string& text) { + std::vector<std::string> results; + + // construct AST from text including KINETIC block(s) + NmodlDriver driver; + auto ast = driver.parse_string(text); + + // construct symbol table from AST + SymtabVisitor().visit_program(ast.get()); + + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + + // run KineticBlock visitor on AST + KineticBlockVisitor().visit_program(ast.get()); + + // run lookup visitor to extract DERIVATIVE block(s) from AST + AstLookupVisitor v_lookup; + auto res = v_lookup.lookup(ast.get(), AstNodeType::DERIVATIVE_BLOCK); + for (const auto& r: res) { + results.push_back(to_nmodl(r.get())); + } + + return results; +} + +SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][visitor]") { + GIVEN("KINETIC block with << reaction statement, 1 state var") { + std::string input_nmodl_text = R"( + STATE { + x + } + KINETIC states { + ~ x << (a*c/3.2) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (a*c/3.2) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with << reaction statement, 1 array state var") { + std::string input_nmodl_text = R"( + STATE { + x[1] + } + KINETIC states { + ~ x[0] << (a*c/3.2) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x'[0] = (a*c/3.2) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with << reaction statement, 1 array state var, flux vars") { + std::string input_nmodl_text = R"( + STATE { + x[1] + } + KINETIC states { + ~ x[0] << (a*c/3.2) + f0 = f_flux*2 + f1 = b_flux + f_flux + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + f0 = 0*2 + f1 = 0+0 + x'[0] = (a*c/3.2) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with invalid << reaction statement with 2 state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + y << (2*z) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + })"; + THEN("Emit warning & do not process statement") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 1 state var, flux vars") { + std::string input_nmodl_text = R"( + STATE { + x + } + KINETIC states { + ~ x -> (a) + zf = f_flux + zb = b_flux + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + zf = a*x + zb = 0 + x' = (-1*(a*x)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 2 state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + y -> (f(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(f(v)*x*y)) + y' = (-1*(f(v)*x*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 2 state vars, CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + y -> (f(v)) + CONSERVE x + y = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE y = 1-x + x' = (-1*(f(v)*x*y)) + y' = (-1*(f(v)*x*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, 2 state vars, CONSERVE & COMPARTMENT") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + COMPARTMENT a { x } + COMPARTMENT b { y } + ~ x + y -> (f(v)) + CONSERVE x + y = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE y = (1-(a*1*x))/(b*1) + x' = ((-1*(f(v)*x*y)))/(a) + y' = ((-1*(f(v)*x*y)))/(b) + })"; + THEN( + "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement inc COMPARTMENT " + "factors") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with -> reaction statement, array of 2 state var") { + std::string input_nmodl_text = R"( + STATE { + x[2] + } + KINETIC states { + ~ x[0] + x[1] -> (f(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x'[0] = (-1*(f(v)*x[0]*x[1])) + x'[1] = (-1*(f(v)*x[0]*x[1])) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var, flux vars") { + // Here c is NOT a state variable + // see 9.9.2.1 of NEURON book + // c should be treated as a constant, i.e. + // -the diff. eq. for x should include the contribution from c + // -no diff. eq. should be generated for c itself + std::string input_nmodl_text = R"( + STATE { + x + } + KINETIC states { + ~ x <-> c (r, r) + c1 = f_flux - b_flux + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + c1 = r*x-r*c + x' = (-1*(r*x-r*c)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement, 2 state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement, 2 state vars, CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x <-> y (a, b) + CONSERVE x + y = 0 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE y = 0-x + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + // array vars in CONSERVE statements are implicit sums over elements + // see p34 of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7812&rep=rep1&type=pdf + GIVEN("KINETIC block with array state vars, CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x[3] y + } + KINETIC states { + ~ x[0] <-> x[1] (a, b) + ~ x[2] <-> y (c, d) + CONSERVE y + x = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE x[2] = 1-y-x[0]-x[1] + x'[0] = (-1*(a*x[0]-b*x[1])) + x'[1] = (1*(a*x[0]-b*x[1])) + x'[2] = (-1*(c*x[2]-d*y)) + y' = (1*(c*x[2]-d*y)) + })"; + THEN( + "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " + "array elements, with last state var on LHS") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + // array vars in CONSERVE statements are implicit sums over elements + // see p34 of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7812&rep=rep1&type=pdf + GIVEN("KINETIC block with array state vars, re-ordered CONSERVE statement") { + std::string input_nmodl_text = R"( + STATE { + x[3] y + } + KINETIC states { + ~ x[0] <-> x[1] (a, b) + ~ x[2] <-> y (c, d) + CONSERVE x + y = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + CONSERVE y = 1-x[0]-x[1]-x[2] + x'[0] = (-1*(a*x[0]-b*x[1])) + x'[1] = (1*(a*x[0]-b*x[1])) + x'[2] = (-1*(c*x[2]-d*y)) + y' = (1*(c*x[2]-d*y)) + })"; + THEN( + "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " + "array elements, with last state var on LHS") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement & 1 COMPARTMENT statement") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + COMPARTMENT c-d {x y} + ~ x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = ((-1*(a*x-b*y)))/(c-d) + y' = ((1*(a*x-b*y)))/(c-d) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with two CONSERVE statements") { + std::string input_nmodl_text = R"( + STATE { + c1 o1 o2 p0 p1 + } + KINETIC ihkin { + evaluate_fct(v, cai) + ~ c1 <-> o1 (alpha, beta) + ~ p0 <-> p1 (k1ca, k2) + ~ o1 <-> o2 (k3p, k4) + CONSERVE p0+p1 = 1 + CONSERVE c1+o1+o2 = 1 + })"; + std::string output_nmodl_text = R"( + DERIVATIVE ihkin { + evaluate_fct(v, cai) + CONSERVE p1 = 1-p0 + CONSERVE o2 = 1-c1-o1 + c1' = (-1*(alpha*c1-beta*o1)) + o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) + o2' = (1*(k3p*o1-k4*o2)) + p0' = (-1*(k1ca*p0-k2*p1)) + p1' = (1*(k1ca*p0-k2*p1)) + })"; + THEN("Convert to equivalent DERIVATIVE block, re-order both CONSERVE statements") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with one reaction statement & 2 COMPARTMENT statements") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + COMPARTMENT cx {x} + COMPARTMENT cy {y} + ~ x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = ((-1*(a*x-b*y)))/(cx) + y' = ((1*(a*x-b*y)))/(cy) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with two independent reaction statements") { + std::string input_nmodl_text = R"( + STATE { + w x y z + } + KINETIC states { + ~ x <-> y (a, b) + ~ w <-> z (c, d) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + w' = (-1*(c*w-d*z)) + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y)) + z' = (1*(c*w-d*z)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with two dependent reaction statements") { + std::string input_nmodl_text = R"( + STATE { + x y z + } + KINETIC states { + ~ x <-> y (a, b) + ~ y <-> z (c, d) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) + z' = (1*(c*y-d*z)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with two dependent reaction statements, flux vars") { + std::string input_nmodl_text = R"( + STATE { + x y z + } + KINETIC states { + c0 = f_flux + ~ x <-> y (a, b) + c1 = f_flux + b_flux + ~ y <-> z (c, d) + c2 = f_flux - 2*b_flux + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + c0 = 0 + c1 = a*x+b*y + c2 = c*y-2*d*z + x' = (-1*(a*x-b*y)) + y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) + z' = (1*(c*y-d*z)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with a stoch coeff of 2") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ 2x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-2*(a*x*x-b*y)) + y' = (1*(a*x*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with duplicate state vars") { + std::string input_nmodl_text = R"( + STATE { + x y + } + KINETIC states { + ~ x + x <-> y (a, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x' = (-2*(a*x*x-b*y)) + y' = (1*(a*x*x-b*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with functions for reaction rates") { + // Example from sec 9.8, p238 of NEURON book + std::string input_nmodl_text = R"( + STATE { + mc m + } + KINETIC states { + ~ mc <-> m (a(v), b(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + mc' = (-1*(a(v)*mc-b(v)*m)) + m' = (1*(a(v)*mc-b(v)*m)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with stoch coeff 2, coupled pair of statements") { + // Example from sec 9.8, p239 of NEURON book + std::string input_nmodl_text = R"( + STATE { + A B C D + } + KINETIC states { + ~ 2A + B <-> C (k1, k2) + ~ C + D <-> A + 2B (k3, k4) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + A' = (-2*(k1*A*A*B-k2*C))+(1*(k3*C*D-k4*A*B*B)) + B' = (-1*(k1*A*A*B-k2*C))+(2*(k3*C*D-k4*A*B*B)) + C' = (1*(k1*A*A*B-k2*C))+(-1*(k3*C*D-k4*A*B*B)) + D' = (-1*(k3*C*D-k4*A*B*B)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } + GIVEN("KINETIC block with loop over array variable") { + std::string input_nmodl_text = R"( + DEFINE N 5 + ASSIGNED { + a + b[N] + c[N] + d + } + STATE { + x[N] + } + KINETIC kin { + ~ x[0] << (a) + FROM i=0 TO N-2 { + ~ x[i] <-> x[i+1] (b[i], c[i]) + } + ~ x[N-1] -> (d) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE kin { + { + } + x'[0] = (a)+(-1*(b[0]*x[0]-c[0]*x[1])) + x'[1] = (1*(b[0]*x[0]-c[0]*x[1]))+(-1*(b[1]*x[1]-c[1]*x[2])) + x'[2] = (1*(b[1]*x[1]-c[1]*x[2]))+(-1*(b[2]*x[2]-c[2]*x[3])) + x'[3] = (1*(b[2]*x[2]-c[2]*x[3]))+(-1*(b[3]*x[3]-c[3]*x[4])) + x'[4] = (1*(b[3]*x[3]-c[3]*x[4]))+(-1*(d*x[4])) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/localize.cpp b/test/nmodl/transpiler/visitor/localize.cpp new file mode 100644 index 0000000000..36b630f220 --- /dev/null +++ b/test/nmodl/transpiler/visitor/localize.cpp @@ -0,0 +1,223 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Localizer visitor tests +//============================================================================= + +std::string run_localize_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + SymtabVisitor().visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); + LocalizeVisitor().visit_program(ast.get()); + + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + + +SCENARIO("Localizer test with single global block", "[visitor][localizer]") { + GIVEN("Single derivative block with variable definition") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + LOCAL tau + tau = 11.1 + exp(tau) + } + )"; + + THEN("tau variable gets localized") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Localizer test with use of verbatim block", "[visitor][localizer]") { + GIVEN("Verbatim block usage in one of the global block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + VERBATIM ENDVERBATIM + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + VERBATIM ENDVERBATIM + } + )"; + + THEN("Localization is disabled") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } +} + + +SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { + GIVEN("Multiple global blocks with definition of variable") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, beta + } + + INITIAL { + LOCAL tau + tau = beta + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + IF (1) { + tau = beta + } ELSE { + tau = 11 + } + + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau, beta + } + + INITIAL { + LOCAL tau + tau = beta + } + + DERIVATIVE states { + LOCAL tau + tau = 11.1 + exp(tau) + } + + BREAKPOINT { + LOCAL tau + IF (1) { + tau = beta + } ELSE { + tau = 11 + } + } + )"; + + THEN("Localization across multiple blocks is done") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } + + + GIVEN("Two global blocks with definition and use of the variable") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + } + + BREAKPOINT { + IF (1) { + tau = 22 + } ELSE { + tau = exp(tau) + 11 + } + + } + )"; + + std::string output_nmodl = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + } + + BREAKPOINT { + IF (1) { + tau = 22 + } ELSE { + tau = exp(tau)+11 + } + } + )"; + + THEN("Localization is not done due to use of variable") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_localize_visitor(input); + REQUIRE(result == expected_result); + } + } +} diff --git a/test/nmodl/transpiler/visitor/lookup.cpp b/test/nmodl/transpiler/visitor/lookup.cpp new file mode 100644 index 0000000000..359ffa6dc3 --- /dev/null +++ b/test/nmodl/transpiler/visitor/lookup.cpp @@ -0,0 +1,97 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; +using symtab::syminfo::NmodlType; + + +//============================================================================= +// Ast lookup visitor tests +//============================================================================= + +std::vector<std::shared_ptr<ast::Ast>> run_lookup_visitor(ast::Program* node, + std::vector<AstNodeType>& types) { + return AstLookupVisitor().lookup(node, types); +} + +SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") { + auto to_ast = [](const std::string& text) { + NmodlDriver driver; + return driver.parse_string(text); + }; + + GIVEN("A mod file with nodes of type NEURON, RANGE, BinaryExpression") { + std::string nmodl_text = R"( + NEURON { + RANGE tau, h + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + h' = h + 2 + } + + : My comment here + )"; + + auto ast = to_ast(nmodl_text); + + WHEN("Looking for existing nodes") { + THEN("Can find RANGE variables") { + std::vector<AstNodeType> types{AstNodeType::RANGE_VAR}; + auto result = run_lookup_visitor(ast.get(), types); + REQUIRE(result.size() == 2); + REQUIRE(to_nmodl(result[0].get()) == "tau"); + REQUIRE(to_nmodl(result[1].get()) == "h"); + } + + THEN("Can find NEURON block") { + AstLookupVisitor v(AstNodeType::NEURON_BLOCK); + ast->accept(&v); + auto nodes = v.get_nodes(); + REQUIRE(nodes.size() == 1); + + std::string neuron_block = R"( + NEURON { + RANGE tau, h + })"; + auto result = reindent_text(to_nmodl(nodes[0].get())); + auto expected = reindent_text(neuron_block); + REQUIRE(result == expected); + } + + THEN("Can find Binary Expressions and function call") { + std::vector<AstNodeType> types{AstNodeType::BINARY_EXPRESSION, + AstNodeType::FUNCTION_CALL}; + auto result = run_lookup_visitor(ast.get(), types); + REQUIRE(result.size() == 4); + } + } + + WHEN("Looking for missing nodes") { + THEN("Can not find BREAKPOINT block") { + std::vector<AstNodeType> types{AstNodeType::BREAKPOINT_BLOCK}; + auto result = run_lookup_visitor(ast.get(), types); + REQUIRE(result.size() == 0); + } + } + } +} diff --git a/test/nmodl/transpiler/visitor/loop_unroll.cpp b/test/nmodl/transpiler/visitor/loop_unroll.cpp new file mode 100644 index 0000000000..3697dc9c46 --- /dev/null +++ b/test/nmodl/transpiler/visitor/loop_unroll.cpp @@ -0,0 +1,158 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/loop_unroll_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Loop unroll tests +//============================================================================= + +std::string run_loop_unroll_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + return to_nmodl(ast.get(), {AstNodeType::DEFINE}); +} + +SCENARIO("Perform loop unrolling of FROM construct", "[visitor][unroll]") { + GIVEN("A loop with known iteration space") { + std::string input_nmodl = R"( + DEFINE N 2 + PROCEDURE rates() { + LOCAL x[N] + FROM i=0 TO N { + x[i] = x[i] + 11 + } + FROM i=(0+(0+1)) TO (N+2-1) { + x[(i+0)] = x[i+1] + 11 + } + } + KINETIC state { + FROM i=1 TO N+1 { + ~ ca[i] <-> ca[i+1] (DFree*frat[i+1]*1(um), DFree*frat[i+1]*1(um)) + } + } + )"; + std::string output_nmodl = R"( + PROCEDURE rates() { + LOCAL x[N] + { + x[0] = x[0]+11 + x[1] = x[1]+11 + x[2] = x[2]+11 + } + { + x[1] = x[2]+11 + x[2] = x[3]+11 + x[3] = x[4]+11 + } + } + + KINETIC state { + { + ~ ca[1] <-> ca[2] (DFree*frat[2]*1(um), DFree*frat[2]*1(um)) + ~ ca[2] <-> ca[3] (DFree*frat[3]*1(um), DFree*frat[3]*1(um)) + ~ ca[3] <-> ca[4] (DFree*frat[4]*1(um), DFree*frat[4]*1(um)) + } + } + )"; + THEN("Loop body gets correctly unrolled") { + auto result = run_loop_unroll_visitor(input_nmodl); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + + GIVEN("A nested loop") { + std::string input_nmodl = R"( + DEFINE N 1 + PROCEDURE rates() { + LOCAL x[N] + FROM i=0 TO N { + FROM j=1 TO N+1 { + x[i] = x[i+j] + 1 + } + } + } + )"; + std::string output_nmodl = R"( + PROCEDURE rates() { + LOCAL x[N] + { + { + x[0] = x[1]+1 + x[0] = x[2]+1 + } + { + x[1] = x[2]+1 + x[1] = x[3]+1 + } + } + } + )"; + THEN("Loop get unrolled recursively") { + auto result = run_loop_unroll_visitor(input_nmodl); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + + + GIVEN("Loop with verbatim and unknown iteration space") { + std::string input_nmodl = R"( + DEFINE N 1 + PROCEDURE rates() { + LOCAL x[N] + FROM i=((0+0)) TO (((N+0))) { + FROM j=1 TO k { + x[i] = x[i+k] + 1 + } + } + FROM i=0 TO N { + VERBATIM ENDVERBATIM + } + } + )"; + std::string output_nmodl = R"( + PROCEDURE rates() { + LOCAL x[N] + { + FROM j = 1 TO k { + x[0] = x[0+k]+1 + } + FROM j = 1 TO k { + x[1] = x[1+k]+1 + } + } + FROM i = 0 TO N { + VERBATIM ENDVERBATIM + } + } + )"; + THEN("Only some loops get unrolled") { + auto result = run_loop_unroll_visitor(input_nmodl); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/main.cpp b/test/nmodl/transpiler/visitor/main.cpp new file mode 100644 index 0000000000..c4095d9ceb --- /dev/null +++ b/test/nmodl/transpiler/visitor/main.cpp @@ -0,0 +1,24 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_RUNNER + +#include "catch/catch.hpp" +#include "utils/logger.hpp" +#include <pybind11/embed.h> + +using namespace nmodl; + +int main(int argc, char* argv[]) { + // initialize python interpreter once for entire catch executable + pybind11::scoped_interpreter guard{}; + // enable verbose logger output + logger->set_level(spdlog::level::debug); + // run all catch tests + int result = Catch::Session().run(argc, argv); + return result; +} diff --git a/test/nmodl/transpiler/visitor/misc.cpp b/test/nmodl/transpiler/visitor/misc.cpp new file mode 100644 index 0000000000..c0b6553e6a --- /dev/null +++ b/test/nmodl/transpiler/visitor/misc.cpp @@ -0,0 +1,96 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/localize_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Passes can run multiple times +//============================================================================= + +void run_visitor_passes(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + { + SymtabVisitor v1; + InlineVisitor v2; + LocalizeVisitor v3; + v1.visit_program(ast.get()); + v2.visit_program(ast.get()); + v3.visit_program(ast.get()); + v1.visit_program(ast.get()); + v1.visit_program(ast.get()); + v2.visit_program(ast.get()); + v3.visit_program(ast.get()); + v2.visit_program(ast.get()); + } +} + + +SCENARIO("Running visitor passes multiple times", "[visitor]") { + GIVEN("A mod file") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + tau = 11.1 + exp(tau) + } + )"; + + THEN("Passes can run multiple times") { + std::string input = reindent_text(nmodl_text); + REQUIRE_NOTHROW(run_visitor_passes(input)); + } + } +} + +//============================================================================= +// to_nmodl with excluding set of node types +//============================================================================= + +SCENARIO("Sympy specific AST to NMODL conversion") { + GIVEN("NMODL block with unit usage") { + std::string nmodl = R"( + BREAKPOINT { + Pf_NMDA = (1/1.38) * 120 (mM) * 0.6 + VDCC = gca_bar_VDCC * 4(um2)*PI*3(1/um3) + gca_bar_VDCC = 0.0372 (nS/um2) + } + )"; + + std::string expected = R"( + BREAKPOINT { + Pf_NMDA = (1/1.38)*120*0.6 + VDCC = gca_bar_VDCC*4*PI*3 + gca_bar_VDCC = 0.0372 + } + )"; + + THEN("to_nmodl can ignore all units") { + auto input = reindent_text(nmodl); + NmodlDriver driver; + auto ast = driver.parse_string(input); + auto result = to_nmodl(ast.get(), {AstNodeType::UNIT}); + REQUIRE(result == reindent_text(expected)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/neuron_solve.cpp b/test/nmodl/transpiler/visitor/neuron_solve.cpp new file mode 100644 index 0000000000..997e1a9ebb --- /dev/null +++ b/test/nmodl/transpiler/visitor/neuron_solve.cpp @@ -0,0 +1,155 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/neuron_solve_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// CnexpSolve visitor tests +//============================================================================= + +std::string run_cnexp_solve_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(ast.get()); + NeuronSolveVisitor().visit_program(ast.get()); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + + +SCENARIO("NeuronSolveVisitor visitor solves different ODE types") { + GIVEN("Derivative block with cnexp method in breakpoint block") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + m = m + h + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + h = h+(1-exp(dt*((((-1)))/hTau)))*(-(((hInf))/hTau)/((((-1)))/hTau)-h) + m = m+h + } + )"; + + THEN("ODEs get replaced with solution") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("Derivative block without any solve method specification") { + std::string nmodl_text = R"( + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + std::string output_nmodl = R"( + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + THEN("ODEs don't get solved") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("Derivative block with non-cnexp method in breakpoint block") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + + DERIVATIVE states { + Dm = (mInf-m)/mTau + Dh = (hInf-h)/hTau + } + )"; + + THEN("ODEs don't get solved but state variables get replaced with Dstate ") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } + + GIVEN("Derivative block with ODEs that needs non-cnexp method to solve") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + A_AMPA' = tau_r_AMPA/A_AMPA + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + A_AMPA' = tau_r_AMPA/A_AMPA + } + )"; + + THEN("ODEs don't get replaced as cnexp is not possible") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_cnexp_solve_visitor(input); + REQUIRE(result == expected_result); + } + } +} diff --git a/test/nmodl/transpiler/visitor/nmodl.cpp b/test/nmodl/transpiler/visitor/nmodl.cpp new file mode 100644 index 0000000000..e5cde15f89 --- /dev/null +++ b/test/nmodl/transpiler/visitor/nmodl.cpp @@ -0,0 +1,47 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/nmodl_constructs.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/nmodl_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// AST to NMODL printer tests +//============================================================================= + +std::string run_nmodl_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +SCENARIO("Convert AST back to NMODL form", "[visitor][nmodl]") { + for (const auto& construct: nmodl_valid_constructs) { + auto test_case = construct.second; + std::string input_nmodl_text = reindent_text(test_case.input); + std::string output_nmodl_text = reindent_text(test_case.output); + GIVEN(test_case.name) { + THEN("Visitor successfully returns : " + input_nmodl_text) { + auto result = run_nmodl_visitor(input_nmodl_text); + REQUIRE(result == output_nmodl_text); + } + } + } +} diff --git a/test/nmodl/transpiler/visitor/perf.cpp b/test/nmodl/transpiler/visitor/perf.cpp new file mode 100644 index 0000000000..aef1c1aeea --- /dev/null +++ b/test/nmodl/transpiler/visitor/perf.cpp @@ -0,0 +1,158 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; + +using nmodl::parser::NmodlDriver; +using symtab::syminfo::NmodlType; + + +//============================================================================= +// Symtab and Perf visitor tests +//============================================================================= + +SCENARIO("Symbol table generation with Perf stat visitor", "[visitor][performance]") { + GIVEN("A mod file and associated ast") { + std::string nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar, A_AMPA_step : range + anything = range (2) + GLOBAL Rstate : global + anything = global + POINTER rng : pointer = global + BBCOREPOINTER coreRng : pointer + assigned = range + } + + PARAMETER { + gNaTs2_tbar = 0.00001 (S/cm2) : range + parameter = range already + tau_r = 0.2 (ms) : parameter = global + tau_d_AMPA = 1.0 : parameter = global + tsyn_fac = 11.1 : parameter + assigned = range + } + + ASSIGNED { + v (mV) : only assigned = range + ena (mV) : only assigned = range + tsyn_fac : parameter + assigned = range already + A_AMPA_step : range + assigned = range already + AmState : only assigned = range + Rstate : global + assigned == global already + coreRng : pointer + assigned = range already + } + + STATE { + m : state = range + h : state = range + } + + BREAKPOINT { + CONDUCTANCE gNaTs2_t USEION na + SOLVE states METHOD cnexp + { + LOCAL gNaTs2_t + { + gNaTs2_t = gNaTs2_tbar*m*m*m*h + } + ina = gNaTs2_t*(v-ena) + } + { + m = hBetaf(11+v) + m = 12/gNaTs2_tbar + } + { + gNaTs2_tbar = gNaTs2_tbar*gNaTs2_tbar + 11.0 + gNaTs2_tbar = 12.0 + } + } + + FUNCTION hBetaf(v) { + hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) + } + )"; + + NmodlDriver driver; + auto ast = driver.parse_string(nmodl_text); + + WHEN("Symbol table generator pass runs") { + SymtabVisitor v; + v.visit_program(ast.get()); + auto symtab = ast->get_model_symbol_table(); + + THEN("Can lookup for defined variables") { + auto symbol = symtab->lookup("m"); + REQUIRE(symbol->has_any_property(NmodlType::dependent_def)); + REQUIRE_FALSE(symbol->has_any_property(NmodlType::local_var)); + + symbol = symtab->lookup("gNaTs2_tbar"); + REQUIRE(symbol->has_any_property(NmodlType::param_assign)); + REQUIRE(symbol->has_any_property(NmodlType::range_var)); + + symbol = symtab->lookup("ena"); + REQUIRE(symbol->has_any_property(NmodlType::read_ion_var)); + } + THEN("Can lookup for defined functions") { + auto symbol = symtab->lookup("hBetaf"); + REQUIRE(symbol->has_any_property(NmodlType::function_block)); + } + THEN("Non existent variable lookup returns nullptr") { + REQUIRE(symtab->lookup("xyz") == nullptr); + } + + WHEN("Perf visitor pass runs after symtab visitor") { + PerfVisitor v; + v.visit_program(ast.get()); + + auto result = v.get_total_perfstat(); + auto num_instance_var = v.get_instance_variable_count(); + auto num_global_var = v.get_global_variable_count(); + auto num_state_var = v.get_state_variable_count(); + auto num_const_instance_var = v.get_const_instance_variable_count(); + auto num_const_global_var = v.get_const_global_variable_count(); + + THEN("Performance counters are updated") { + REQUIRE(result.n_add == 2); + REQUIRE(result.n_sub == 4); + REQUIRE(result.n_mul == 7); + REQUIRE(result.n_div == 3); + REQUIRE(result.n_exp == 1); + REQUIRE(result.n_global_read == 7); + REQUIRE(result.n_unique_global_read == 4); + REQUIRE(result.n_global_write == 3); + REQUIRE(result.n_unique_global_write == 2); + REQUIRE(result.n_constant_read == 4); + REQUIRE(result.n_unique_constant_read == 1); + REQUIRE(result.n_constant_write == 2); + REQUIRE(result.n_unique_constant_write == 1); + REQUIRE(result.n_local_read == 3); + REQUIRE(result.n_local_write == 2); + REQUIRE(result.n_ext_func_call == 1); + REQUIRE(result.n_int_func_call == 1); + REQUIRE(result.n_neg == 3); + REQUIRE(num_instance_var == 9); + REQUIRE(num_global_var == 4); + REQUIRE(num_state_var == 2); + REQUIRE(num_const_instance_var == 2); + REQUIRE(num_const_global_var == 2); + } + } + } + + WHEN("Perf visitor pass runs before symtab visitor") { + PerfVisitor v; + THEN("exception is thrown") { + REQUIRE_THROWS_WITH(v.visit_program(ast.get()), Catch::Contains("table not setup")); + } + } + } +} diff --git a/test/nmodl/transpiler/visitor/rename.cpp b/test/nmodl/transpiler/visitor/rename.cpp new file mode 100644 index 0000000000..b076806c76 --- /dev/null +++ b/test/nmodl/transpiler/visitor/rename.cpp @@ -0,0 +1,365 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_var_rename_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// Variable rename tests +//============================================================================= + +std::string run_var_rename_visitor(const std::string& text, + std::vector<std::pair<std::string, std::string>> variables) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + for (const auto& variable: variables) { + RenameVisitor(variable.first, variable.second).visit_program(ast.get()); + } + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +SCENARIO("Renaming any variable in mod file with RenameVisitor", "[visitor][rename]") { + GIVEN("A mod file") { + // sample nmodl text + std::string input_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PARAMETER { + gNaTs2_tbar = 0.1 (S/cm2) + } + + STATE { + m + h + } + + COMMENT + m and gNaTs2_tbar remain same here + ENDCOMMENT + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + } + + FUNCTION mAlpha() { + } + )"; + + /// expected result after renaming m, gNaTs2_tbar and mAlpha + std::string output_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE new_gNaTs2_tbar + } + + PARAMETER { + new_gNaTs2_tbar = 0.1 (S/cm2) + } + + STATE { + mm + h + } + + COMMENT + m and gNaTs2_tbar remain same here + ENDCOMMENT + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = new_gNaTs2_tbar*mm*mm*mm*h + ina = gNaTs2_t*(v-ena) + } + + FUNCTION mBeta() { + } + )"; + + std::string input = reindent_text(input_nmodl_text); + std::string expected_output = reindent_text(output_nmodl_text); + + THEN("existing variables could be renamed") { + std::vector<std::pair<std::string, std::string>> variables = { + {"m", "mm"}, + {"gNaTs2_tbar", "new_gNaTs2_tbar"}, + {"mAlpha", "mBeta"}, + }; + auto result = run_var_rename_visitor(input, variables); + REQUIRE(result == expected_output); + } + + THEN("non-existing variables will be ignored") { + std::vector<std::pair<std::string, std::string>> variables = { + {"unknown_variable", "doesnot_matter"}}; + auto result = run_var_rename_visitor(input, variables); + REQUIRE(result == input); + } + } +} + +//============================================================================= +// Local variable rename tests +//============================================================================= + +std::string run_local_var_rename_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(ast.get()); + + VerbatimVarRenameVisitor().visit_program(ast.get()); + LocalVarRenameVisitor().visit_program(ast.get()); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +SCENARIO("Renaming with presence of local and global variables in same block", + "[visitor][rename]") { + GIVEN("A neuron block and procedure with same variable name") { + std::string nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar + gNaTs2_tbar = 2.1 + ena + } + )"; + + std::string expected_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar_r_0 + gNaTs2_tbar_r_0 = 2.1+ena + } + )"; + + THEN("var renaming pass changes only local variables in procedure") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(expected_nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == expected_result); + } + } +} + +SCENARIO("Renaming in the absence of global blocks", "[visitor][rename]") { + GIVEN("Procedures containing same variables") { + std::string nmodl_text = R"( + PROCEDURE rates_1() { + LOCAL gNaTs2_tbar + gNaTs2_tbar = 2.1+ena + } + + PROCEDURE rates_2() { + LOCAL gNaTs2_tbar + gNaTs2_tbar = 2.1+ena + } + )"; + + THEN("nothing gets renamed") { + std::string input = reindent_text(nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == input); + } + } +} + +SCENARIO("Variable renaming in nested blocks", "[visitor][rename]") { + GIVEN("Mod file containing procedures with nested blocks") { + std::string input_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PARAMETER { + gNaTs2_tbar = 0.1 (S/cm2) + tau = 11.1 + } + + STATE { + m + h + } + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + { + LOCAL gNaTs2_t, h + gNaTs2_t = m + h + { + LOCAL m + m = gNaTs2_t + h + { + LOCAL m, h + VERBATIM + _lm = 12 + ENDVERBATIM + } + } + } + } + + PROCEDURE rates() { + LOCAL x, m + m = x + gNaTs2_tbar + { + { + LOCAL h, x, gNaTs2_tbar + m = h * x * gNaTs2_tbar + tau + } + } + } + )"; + + std::string expected_nmodl_text = R"( + NEURON { + SUFFIX NaTs2_t + USEION na READ ena WRITE ina + RANGE gNaTs2_tbar + } + + PARAMETER { + gNaTs2_tbar = 0.1 (S/cm2) + tau = 11.1 + } + + STATE { + m + h + } + + BREAKPOINT { + LOCAL gNaTs2_t + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + { + LOCAL gNaTs2_t_r_0, h_r_1 + gNaTs2_t_r_0 = m+h_r_1 + { + LOCAL m_r_1 + m_r_1 = gNaTs2_t_r_0+h_r_1 + { + LOCAL m_r_0, h_r_0 + VERBATIM + m_r_0 = 12 + ENDVERBATIM + } + } + } + } + + PROCEDURE rates() { + LOCAL x, m_r_2 + m_r_2 = x+gNaTs2_tbar + { + { + LOCAL h_r_2, x_r_0, gNaTs2_tbar_r_0 + m_r_2 = h_r_2*x_r_0*gNaTs2_tbar_r_0+tau + } + } + } + )"; + + THEN("variables conflicting with global variables get renamed starting from inner block") { + std::string input = reindent_text(input_nmodl_text); + auto expected_result = reindent_text(expected_nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == expected_result); + } + } +} + + +SCENARIO("Renaming in presence of local variable in verbatim block", "[visitor][rename]") { + GIVEN("A neuron block and procedure with same variable name") { + std::string nmodl_text = R"( + NEURON { + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar, x + VERBATIM + _lx = _lgNaTs2_tbar + #define my_macro_var _lgNaTs2_tbar*2 + ENDVERBATIM + gNaTs2_tbar = my_macro_var + 1 + } + + PROCEDURE alpha() { + VERBATIM + _p_gNaTs2_tbar = 12 + ENDVERBATIM + } + )"; + + std::string expected_nmodl_text = R"( + NEURON { + RANGE gNaTs2_tbar + } + + PROCEDURE rates() { + LOCAL gNaTs2_tbar_r_0, x + VERBATIM + x = gNaTs2_tbar_r_0 + #define my_macro_var gNaTs2_tbar_r_0*2 + ENDVERBATIM + gNaTs2_tbar_r_0 = my_macro_var+1 + } + + PROCEDURE alpha() { + VERBATIM + gNaTs2_tbar = 12 + ENDVERBATIM + } + )"; + + THEN("var renaming pass changes local & global variable in verbatim block") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(expected_nmodl_text); + auto result = run_local_var_rename_visitor(input); + REQUIRE(result == expected_result); + } + } +} diff --git a/test/nmodl/transpiler/visitor/solve_block.cpp b/test/nmodl/transpiler/visitor/solve_block.cpp new file mode 100644 index 0000000000..8ce6221867 --- /dev/null +++ b/test/nmodl/transpiler/visitor/solve_block.cpp @@ -0,0 +1,118 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/neuron_solve_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// SolveBlock visitor tests +//============================================================================= + +std::string run_solve_block_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + SymtabVisitor().visit_program(ast.get()); + NeuronSolveVisitor().visit_program(ast.get()); + SolveBlockVisitor().visit_program(ast.get()); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(ast.get()); + return stream.str(); +} + +TEST_CASE("Solve ODEs using legacy NeuronSolveVisitor", "[visitor][solver]") { + SECTION("SolveBlock add NrnState block") { + GIVEN("Breakpoint block with single solve block in breakpoint") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + + NRN_STATE SOLVE states METHOD cnexp{ + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + + )"; + + THEN("Single NrnState block gets added") { + auto result = run_solve_block_visitor(nmodl_text); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + + GIVEN("Breakpoint block with two solve block in breakpoint") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE state1 METHOD cnexp + SOLVE state2 METHOD cnexp + } + + DERIVATIVE state1 { + m' = (mInf-m)/mTau + } + + DERIVATIVE state2 { + h' = (mInf-h)/mTau + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE state1 METHOD cnexp + SOLVE state2 METHOD cnexp + } + + DERIVATIVE state1 { + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + + DERIVATIVE state2 { + h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) + } + + NRN_STATE SOLVE state1 METHOD cnexp{ + m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + } + SOLVE state2 METHOD cnexp{ + h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) + } + + )"; + + THEN("NrnState blok combining multiple solve nodes added") { + auto result = run_solve_block_visitor(nmodl_text); + REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); + } + } + } +} diff --git a/test/nmodl/transpiler/visitor/steadystate.cpp b/test/nmodl/transpiler/visitor/steadystate.cpp new file mode 100644 index 0000000000..361c5c696e --- /dev/null +++ b/test/nmodl/transpiler/visitor/steadystate.cpp @@ -0,0 +1,184 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/loop_unroll_visitor.hpp" +//#include "visitors/nmodl_visitor.hpp" +#include "visitors/steadystate_visitor.hpp" +//#include "visitors/sympy_solver_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// STEADYSTATE visitor tests +//============================================================================= + +std::vector<std::string> run_steadystate_visitor( + const std::string& text, + std::vector<AstNodeType> ret_nodetypes = {AstNodeType::SOLVE_BLOCK, + AstNodeType::DERIVATIVE_BLOCK}) { + std::vector<std::string> results; + // construct AST from text + NmodlDriver driver; + auto ast = driver.parse_string(text); + + // construct symbol table from AST + SymtabVisitor().visit_program(ast.get()); + + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + + // Run kinetic block visitor first, so any kinetic blocks + // are converted into derivative blocks + KineticBlockVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + + // run SteadystateVisitor on AST + SteadystateVisitor().visit_program(ast.get()); + + // run lookup visitor to extract results from AST + auto res = AstLookupVisitor().lookup(ast.get(), ret_nodetypes); + for (const auto& r: res) { + results.push_back(to_nmodl(r.get())); + } + return results; +} + +SCENARIO("Solving ODEs with STEADYSTATE solve method", "[visitor][steadystate]") { + GIVEN("STEADYSTATE sparse solve") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states STEADYSTATE sparse + } + DERIVATIVE states { + m' = m + h + } + )"; + std::string expected_text1 = R"( + DERIVATIVE states { + m' = m+h + })"; + std::string expected_text2 = R"( + DERIVATIVE states_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1000000000 + m' = m+h + dt = dt_saved_value + })"; + THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { + auto result = run_steadystate_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "SOLVE states_steadystate METHOD sparse"); + REQUIRE(reindent_text(result[1]) == reindent_text(expected_text1)); + REQUIRE(reindent_text(result[2]) == reindent_text(expected_text2)); + } + } + GIVEN("STEADYSTATE derivimplicit solve") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states STEADYSTATE derivimplicit + } + DERIVATIVE states { + m' = m + h + } + )"; + std::string expected_text1 = R"( + DERIVATIVE states { + m' = m+h + })"; + std::string expected_text2 = R"( + DERIVATIVE states_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1e-09 + m' = m+h + dt = dt_saved_value + })"; + THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { + auto result = run_steadystate_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "SOLVE states_steadystate METHOD derivimplicit"); + REQUIRE(reindent_text(result[1]) == reindent_text(expected_text1)); + REQUIRE(reindent_text(result[2]) == reindent_text(expected_text2)); + } + } + GIVEN("two STEADYSTATE solves") { + std::string nmodl_text = R"( + STATE { + Z[3] + x + } + BREAKPOINT { + SOLVE states0 STEADYSTATE derivimplicit + SOLVE states1 STEADYSTATE sparse + } + DERIVATIVE states0 { + Z'[0] = Z[1] - Z[2] + Z'[1] = Z[0] + 2*Z[2] + Z'[2] = Z[0]*Z[0] - 3.10 + } + DERIVATIVE states1 { + x' = x + c + } + )"; + std::string expected_text1 = R"( + DERIVATIVE states0 { + Z'[0] = Z[1]-Z[2] + Z'[1] = Z[0]+2*Z[2] + Z'[2] = Z[0]*Z[0]-3.1 + })"; + std::string expected_text2 = R"( + DERIVATIVE states1 { + x' = x+c + })"; + std::string expected_text3 = R"( + DERIVATIVE states0_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1e-09 + Z'[0] = Z[1]-Z[2] + Z'[1] = Z[0]+2*Z[2] + Z'[2] = Z[0]*Z[0]-3.1 + dt = dt_saved_value + })"; + std::string expected_text4 = R"( + DERIVATIVE states1_steadystate { + LOCAL dt_saved_value + dt_saved_value = dt + dt = 1000000000 + x' = x+c + dt = dt_saved_value + })"; + THEN("Construct DERIVATIVE blocks with steadystate solution & update SOLVE statements") { + auto result = run_steadystate_visitor(nmodl_text); + REQUIRE(result.size() == 6); + REQUIRE(result[0] == "SOLVE states0_steadystate METHOD derivimplicit"); + REQUIRE(result[1] == "SOLVE states1_steadystate METHOD sparse"); + REQUIRE(reindent_text(result[2]) == reindent_text(expected_text1)); + REQUIRE(reindent_text(result[3]) == reindent_text(expected_text2)); + REQUIRE(reindent_text(result[4]) == reindent_text(expected_text3)); + REQUIRE(reindent_text(result[5]) == reindent_text(expected_text4)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/visitor/sympy_conductance.cpp new file mode 100644 index 0000000000..534bc20f89 --- /dev/null +++ b/test/nmodl/transpiler/visitor/sympy_conductance.cpp @@ -0,0 +1,932 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/sympy_conductance_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// SympyConductance visitor tests +//============================================================================= + +std::string run_sympy_conductance_visitor(const std::string& text) { + // construct AST from text + NmodlDriver driver; + auto ast = driver.parse_string(text); + + // construct symbol table from AST + SymtabVisitor(false).visit_program(ast.get()); + + // run constant folding, inlining & local renaming first + ConstantFolderVisitor().visit_program(ast.get()); + InlineVisitor().visit_program(ast.get()); + LocalVarRenameVisitor().visit_program(ast.get()); + SymtabVisitor(true).visit_program(ast.get()); + + // run SympyConductance on AST + SympyConductanceVisitor().visit_program(ast.get()); + + // run lookup visitor to extract results from AST + AstLookupVisitor v_lookup; + // return BREAKPOINT block as JSON string + return reindent_text( + to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); +} + +std::string breakpoint_to_nmodl(const std::string& text) { + // construct AST from text + NmodlDriver driver; + auto ast = driver.parse_string(text); + + // construct symbol table from AST + SymtabVisitor().visit_program(ast.get()); + + // run lookup visitor to extract results from AST + AstLookupVisitor v_lookup; + // return BREAKPOINT block as JSON string + return reindent_text( + to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); +} + +void run_sympy_conductance_passes(ast::Program* node) { + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(node); + + // run SympySolver on AST several times + SympyConductanceVisitor v_sympy1; + v_sympy1.visit_program(node); + v_symtab.visit_program(node); + v_sympy1.visit_program(node); + v_symtab.visit_program(node); + + // also use a second instance of SympySolver + SympyConductanceVisitor v_sympy2; + v_sympy2.visit_program(node); + v_symtab.visit_program(node); + v_sympy1.visit_program(node); + v_symtab.visit_program(node); + v_sympy2.visit_program(node); + v_symtab.visit_program(node); +} + +SCENARIO("Addition of CONDUCTANCE using SympyConductance visitor", "[visitor][solver][sympy]") { + // First set of test mod files below all based on: + // nmodldb/models/db/bluebrain/CortexSimplified/mod/Ca.mod + GIVEN("breakpoint block containing VERBATIM statement") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + VERBATIM + double z=0; + ENDVERBATIM + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + VERBATIM + double z=0; + ENDVERBATIM + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + THEN("Do nothing") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("breakpoint block containing IF/ELSE block") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + IF(gCabar<1){ + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } ELSE { + gCa = 0 + ica = 0 + } + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + IF(gCabar<1){ + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } ELSE { + gCa = 0 + ica = 0 + } + } + )"; + THEN("Do nothing") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("ion current, existing CONDUCTANCE hint & var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + THEN("Do nothing") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("ion current, no CONDUCTANCE hint, existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, gCa, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + } + )"; + THEN("Add CONDUCTANCE hint using existing var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("ion current, no CONDUCTANCE hint, no existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + RANGE gCabar, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0 + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + } + )"; + THEN("Add CONDUCTANCE hint with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("2 ion currents, 1 CONDUCTANCE hint, 1 existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + USEION na READ ena WRITE ina + RANGE gCabar, gNabar, ica, ina + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + gNabar = 0.00005 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ina (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_na_0 + CONDUCTANCE g_na_0 USEION na + g_na_0 = gNabar*h*m + CONDUCTANCE gCa USEION ca + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + )"; + THEN("Add 1 CONDUCTANCE hint with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("2 ion currents, no CONDUCTANCE hints, 1 existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + USEION na READ ena WRITE ina + RANGE gCabar, gNabar, ica, ina + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + gNabar = 0.00005 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ina (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_na_0 + CONDUCTANCE g_na_0 USEION na + CONDUCTANCE gCa USEION ca + g_na_0 = gNabar*h*m + SOLVE states METHOD cnexp + gCa = gCabar*m*m*h + ica = gCa*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints, 1 with existing var, 1 with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("2 ion currents, no CONDUCTANCE hints, no existing vars") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + USEION na READ ena WRITE ina + RANGE gCabar, gNabar, ica, ina + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + gNabar = 0.00005 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ina (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0, g_na_0 + CONDUCTANCE g_na_0 USEION na + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + g_na_0 = gNabar*h*m + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ina = (gNabar*m*h)*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints with 2 new local vars") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("1 ion current, 1 nonspecific current, no CONDUCTANCE hints, no existing vars") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + NONSPECIFIC_CURRENT ihcn + RANGE gCabar, ica + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ihcn (mA/cm2) + gCa (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ihcn = (0.1235*m*h)*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0, g__0 + CONDUCTANCE g__0 + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + g__0 = 0.1235*h*m + SOLVE states METHOD cnexp + ica = (gCabar*m*m*h)*(v-eca) + ihcn = (0.1235*m*h)*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints with 2 new local vars") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + GIVEN("1 ion current, 1 nonspecific current, no CONDUCTANCE hints, 1 existing var") { + std::string nmodl_text = R"( + NEURON { + SUFFIX Ca + USEION ca READ eca WRITE ica + NONSPECIFIC_CURRENT ihcn + RANGE gCabar, ica, gihcn + } + + UNITS { + (S) = (siemens) + (mV) = (millivolt) + (mA) = (milliamp) + } + + PARAMETER { + gCabar = 0.00001 (S/cm2) + } + + ASSIGNED { + v (mV) + eca (mV) + ica (mA/cm2) + ihcn (mA/cm2) + gCa (S/cm2) + gihcn (S/cm2) + mInf + mTau + mAlpha + mBeta + hInf + hTau + hAlpha + hBeta + } + + STATE { + m + h + } + + BREAKPOINT { + SOLVE states METHOD cnexp + gihcn = 0.1235*m*h + ica = (gCabar*m*m*h)*(v-eca) + ihcn = gihcn*(v-eca) + } + + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + } + + INITIAL{ + m = mInf + h = hInf + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL g_ca_0 + CONDUCTANCE gihcn + CONDUCTANCE g_ca_0 USEION ca + g_ca_0 = gCabar*h*pow(m, 2) + SOLVE states METHOD cnexp + gihcn = 0.1235*m*h + ica = (gCabar*m*m*h)*(v-eca) + ihcn = gihcn*(v-eca) + } + )"; + THEN("Add 2 CONDUCTANCE hints, 1 using existing var, 1 with new local var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + // based on bluebrain/CortextPlastic/mod/ProbAMPANMDA.mod + GIVEN( + "2 ion currents, 1 nonspecific current, no CONDUCTANCE hints, indirect relation between " + "eqns") { + std::string nmodl_text = R"( + NEURON { + THREADSAFE + POINT_PROCESS ProbAMPANMDA + RANGE tau_r_AMPA, tau_d_AMPA, tau_r_NMDA, tau_d_NMDA + RANGE Use, u, Dep, Fac, u0, mg, NMDA_ratio + RANGE i, i_AMPA, i_NMDA, g_AMPA, g_NMDA, g, e + NONSPECIFIC_CURRENT i, i_AMPA,i_NMDA + POINTER rng + RANGE synapseID, verboseLevel + } + PARAMETER { + tau_r_AMPA = 0.2 (ms) : dual-exponential conductance profile + tau_d_AMPA = 1.7 (ms) : IMPORTANT: tau_r < tau_d + tau_r_NMDA = 0.29 (ms) : dual-exponential conductance profile + tau_d_NMDA = 43 (ms) : IMPORTANT: tau_r < tau_d + Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) + Dep = 100 (ms) : relaxation time constant from depression + Fac = 10 (ms) : relaxation time constant from facilitation + e = 0 (mV) : AMPA and NMDA reversal potential + mg = 1 (mM) : initial concentration of mg2+ + mggate + gmax = .001 (uS) : weight conversion factor (from nS to uS) + u0 = 0 :initial value of u, which is the running value of Use + NMDA_ratio = 0.71 (1) : The ratio of NMDA to AMPA + synapseID = 0 + verboseLevel = 0 + } + ASSIGNED { + v (mV) + i (nA) + i_AMPA (nA) + i_NMDA (nA) + g_AMPA (uS) + g_NMDA (uS) + g (uS) + factor_AMPA + factor_NMDA + rng + } + STATE { + A_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_r_AMPA + B_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_d_AMPA + A_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_r_NMDA + B_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_d_NMDA + } + BREAKPOINT { + SOLVE state METHOD cnexp + mggate = 1.2 + g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA + g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics + g = g_AMPA + g_NMDA + i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal + i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal + i = i_AMPA + i_NMDA + } + DERIVATIVE state{ + A_AMPA' = -A_AMPA/tau_r_AMPA + B_AMPA' = -B_AMPA/tau_d_AMPA + A_NMDA' = -A_NMDA/tau_r_NMDA + B_NMDA' = -B_NMDA/tau_d_NMDA + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + CONDUCTANCE g + CONDUCTANCE g_NMDA + CONDUCTANCE g_AMPA + SOLVE state METHOD cnexp + mggate = 1.2 + g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA + g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics + g = g_AMPA + g_NMDA + i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal + i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal + i = i_AMPA + i_NMDA + } + )"; + THEN("Add 3 CONDUCTANCE hints, using existing vars") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } + // based on neurodamus/bbp/lib/modlib/GluSynapse.mod + GIVEN("1 nonspecific current, no CONDUCTANCE hints, many eqs & a function involved") { + std::string nmodl_text = R"( + NEURON { + GLOBAL tau_r_AMPA, E_AMPA + RANGE tau_d_AMPA, gmax_AMPA + RANGE g_AMPA + GLOBAL tau_r_NMDA, tau_d_NMDA, E_NMDA + RANGE g_NMDA + RANGE Use, Dep, Fac, Nrrp, u + RANGE tsyn, unoccupied, occupied + RANGE ica_NMDA + RANGE volume_CR + GLOBAL ljp_VDCC, vhm_VDCC, km_VDCC, mtau_VDCC, vhh_VDCC, kh_VDCC, htau_VDCC + RANGE gca_bar_VDCC, ica_VDCC + GLOBAL gamma_ca_CR, tau_ca_CR, min_ca_CR, cao_CR + GLOBAL tau_GB, gamma_d_GB, gamma_p_GB, rho_star_GB, tau_Use_GB, tau_effca_GB + RANGE theta_d_GB, theta_p_GB + RANGE rho0_GB + RANGE enable_GB, depress_GB, potentiate_GB + RANGE Use_d_GB, Use_p_GB + GLOBAL p_gen_RW, p_elim0_RW, p_elim1_RW + RANGE enable_RW, synstate_RW + GLOBAL mg, scale_mg, slope_mg + RANGE vsyn, NMDA_ratio, synapseID, selected_for_report, verbose + NONSPECIFIC_CURRENT i + } + UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) + (nS) = (nanosiemens) + (pS) = (picosiemens) + (umho) = (micromho) + (um) = (micrometers) + (mM) = (milli/liter) + (uM) = (micro/liter) + FARADAY = (faraday) (coulomb) + PI = (pi) (1) + R = (k-mole) (joule/degC) + } + ASSIGNED { + g_AMPA (uS) + g_NMDA (uS) + ica_NMDA (nA) + ica_VDCC (nA) + depress_GB (1) + potentiate_GB (1) + v (mV) + vsyn (mV) + i (nA) + } + FUNCTION nernst(ci(mM), co(mM), z) (mV) { + nernst = (1000) * R * (celsius + 273.15) / (z*FARADAY) * log(co/ci) + } + BREAKPOINT { + LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC + g_AMPA = (1e-3)*gmax_AMPA*(B_AMPA-A_AMPA) + i_AMPA = g_AMPA*(v-E_AMPA) + gmax_NMDA = gmax_AMPA*NMDA_ratio + mggate = 1 / (1 + exp(slope_mg * -(v)) * (mg / scale_mg)) + g_NMDA = (1e-3)*gmax_NMDA*mggate*(B_NMDA-A_NMDA) + i_NMDA = g_NMDA*(v-E_NMDA) + Pf_NMDA = (4*cao_CR) / (4*cao_CR + (1/1.38) * 120 (mM)) * 0.6 + ica_NMDA = Pf_NMDA*g_NMDA*(v-40.0) + gca_bar_abs_VDCC = gca_bar_VDCC * 4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^(2/3) + gca_VDCC = (1e-3) * gca_bar_abs_VDCC * m_VDCC * m_VDCC * h_VDCC + Eca_syn = FARADAY*nernst(cai_CR, cao_CR, 2) + ica_VDCC = gca_VDCC*(v-Eca_syn) + vsyn = v + i = i_AMPA + i_NMDA + ica_VDCC + } + )"; + std::string breakpoint_text = R"( + BREAKPOINT { + LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC, nernst_in_0, g__0 + CONDUCTANCE g__0 + g__0 = (0.001*gmax_NMDA*mg*scale_mg*slope_mg*(A_NMDA-B_NMDA)*(E_NMDA-v)*exp(slope_mg*v)-0.001*gmax_NMDA*scale_mg*(A_NMDA-B_NMDA)*(mg+scale_mg*exp(slope_mg*v))*exp(slope_mg*v)+(g_AMPA+gca_VDCC)*pow(mg+scale_mg*exp(slope_mg*v), 2))/pow(mg+scale_mg*exp(slope_mg*v), 2) + g_AMPA = 0.001*gmax_AMPA*(B_AMPA-A_AMPA) + i_AMPA = g_AMPA*(v-E_AMPA) + gmax_NMDA = gmax_AMPA*NMDA_ratio + mggate = 1/(1+exp(slope_mg*-v)*(mg/scale_mg)) + g_NMDA = 0.001*gmax_NMDA*mggate*(B_NMDA-A_NMDA) + i_NMDA = g_NMDA*(v-E_NMDA) + Pf_NMDA = (4*cao_CR)/(4*cao_CR+0.7246376811594204*120(mM))*0.6 + ica_NMDA = Pf_NMDA*g_NMDA*(v-40) + gca_bar_abs_VDCC = gca_bar_VDCC*4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^0.6666666666666666 + gca_VDCC = 0.001*gca_bar_abs_VDCC*m_VDCC*m_VDCC*h_VDCC + { + LOCAL ci_in_0, co_in_0, z_in_0 + ci_in_0 = cai_CR + co_in_0 = cao_CR + z_in_0 = 2 + nernst_in_0 = 1000*R*(celsius+273.15)/(z_in_0*FARADAY)*log(co_in_0/ci_in_0) + } + Eca_syn = FARADAY*nernst_in_0 + ica_VDCC = gca_VDCC*(v-Eca_syn) + vsyn = v + i = i_AMPA+i_NMDA+ica_VDCC + } + )"; + THEN("Add 1 CONDUCTANCE hint using new var") { + auto result = run_sympy_conductance_visitor(nmodl_text); + REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/visitor/sympy_solver.cpp new file mode 100644 index 0000000000..94629022f4 --- /dev/null +++ b/test/nmodl/transpiler/visitor/sympy_solver.cpp @@ -0,0 +1,1313 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/constant_folder_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/loop_unroll_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/sympy_solver_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// SympySolver visitor tests +//============================================================================= + +std::vector<std::string> run_sympy_solver_visitor( + const std::string& text, + bool pade = false, + bool cse = false, + AstNodeType ret_nodetype = AstNodeType::DIFF_EQ_EXPRESSION) { + std::vector<std::string> results; + + // construct AST from text + NmodlDriver driver; + auto ast = driver.parse_string(text); + + // construct symbol table from AST + SymtabVisitor().visit_program(ast.get()); + + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(ast.get()); + LoopUnrollVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(ast.get()); + + // run SympySolver on AST + SympySolverVisitor(pade, cse).visit_program(ast.get()); + + // run lookup visitor to extract results from AST + AstLookupVisitor v_lookup; + auto res = v_lookup.lookup(ast.get(), ret_nodetype); + for (const auto& r: res) { + results.push_back(to_nmodl(r.get())); + } + + return results; +} + +void run_sympy_visitor_passes(ast::Program* node) { + // construct symbol table from AST + SymtabVisitor v_symtab; + v_symtab.visit_program(node); + + // run SympySolver on AST several times + SympySolverVisitor v_sympy1; + v_sympy1.visit_program(node); + v_sympy1.visit_program(node); + + // also use a second instance of SympySolver + SympySolverVisitor v_sympy2; + v_sympy2.visit_program(node); + v_sympy1.visit_program(node); + v_sympy2.visit_program(node); +} + +std::string ast_to_string(ast::Program* node) { + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(node); + return stream.str(); +} + +SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", + "[visitor][sympy][cnexp][euler]") { + GIVEN("Derivative block without ODE, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m = m + h + } + )"; + THEN("No ODEs found - do nothing") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.empty()); + } + } + GIVEN("Derivative block with ODES, solver method is euler") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD euler + } + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = (hInf-h)/hTau + z = a*b + c + } + )"; + THEN("Construct forwards Euler solutions") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "m = (-dt*(m-mInf)+m*mTau)/mTau"); + REQUIRE(result[1] == "h = (-dt*(h-hInf)+h*hTau)/hTau"); + } + } + GIVEN("Derivative block with ODE, 1 state var in array, solver method euler") { + std::string nmodl_text = R"( + STATE { + m[1] + } + BREAKPOINT { + SOLVE states METHOD euler + } + DERIVATIVE states { + m'[0] = (mInf-m[0])/mTau + } + )"; + THEN("Construct forwards Euler solutions") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == "m[0] = (dt*(mInf-m[0])+mTau*m[0])/mTau"); + } + } + GIVEN("Derivative block with ODE, 1 state var in array, solver method cnexp") { + std::string nmodl_text = R"( + STATE { + m[1] + } + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m'[0] = (mInf-m[0])/mTau + } + )"; + THEN("Construct forwards Euler solutions") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 1); + REQUIRE(result[0] == "m[0] = mInf-(mInf-m[0])*exp(-dt/mTau)"); + } + } + GIVEN("Derivative block with linear ODES, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m' = (mInf-m)/mTau + z = a*b + c + h' = hInf/hTau - h/hTau + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); + REQUIRE(result[1] == "h = hInf-(-h+hInf)*exp(-dt/hTau)"); + } + } + GIVEN("Derivative block including non-linear but solvable ODES, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m' = (mInf-m)/mTau + h' = c2 * h*h + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); + REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); + } + } + GIVEN("Derivative block including array of 2 state vars, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + STATE { + X[2] + } + DERIVATIVE states { + X'[0] = (mInf-X[0])/mTau + X'[1] = c2 * X[1]*X[1] + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 2); + REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau)"); + REQUIRE(result[1] == "X[1] = -X[1]/(c2*dt*X[1]-1)"); + } + } + GIVEN("Derivative block including loop over array vars, solver method cnexp") { + std::string nmodl_text = R"( + DEFINE N 3 + BREAKPOINT { + SOLVE states METHOD cnexp + } + ASSIGNED { + mTau[N] + } + STATE { + X[N] + } + DERIVATIVE states { + FROM i=0 TO N-1 { + X'[i] = (mInf-X[i])/mTau[i] + } + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau[0])"); + REQUIRE(result[1] == "X[1] = mInf-(mInf-X[1])*exp(-dt/mTau[1])"); + REQUIRE(result[2] == "X[2] = mInf-(mInf-X[2])*exp(-dt/mTau[2])"); + } + } + GIVEN("Derivative block including loop over array vars, solver method euler") { + std::string nmodl_text = R"( + DEFINE N 3 + BREAKPOINT { + SOLVE states METHOD euler + } + ASSIGNED { + mTau[N] + } + STATE { + X[N] + } + DERIVATIVE states { + FROM i=0 TO N-1 { + X'[i] = (mInf-X[i])/mTau[i] + } + } + )"; + THEN("Integrate equations analytically") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "X[0] = (dt*(mInf-X[0])+X[0]*mTau[0])/mTau[0]"); + REQUIRE(result[1] == "X[1] = (dt*(mInf-X[1])+X[1]*mTau[1])/mTau[1]"); + REQUIRE(result[2] == "X[2] = (dt*(mInf-X[2])+X[2]*mTau[2])/mTau[2]"); + } + } + GIVEN("Derivative block including ODES that can't currently be solved, solver method cnexp") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + z' = a/z + b/z/z + h' = c2 * h*h + x' = a + y' = c3 * y*y*y + } + )"; + THEN("Integrate equations analytically where possible, otherwise leave untouched") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 4); + REQUIRE(result[0] == "z' = a/z+b/z/z"); + REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); + REQUIRE(result[2] == "x = a*dt+x"); + REQUIRE(result[3] == "y' = c3*y*y*y"); + } + } + GIVEN("Derivative block with cnexp solver method, AST after SympySolver pass") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + DERIVATIVE states { + m' = (mInf-m)/mTau + } + )"; + // construct AST from text + NmodlDriver driver; + auto ast = driver.parse_string(nmodl_text); + + // construct symbol table from AST + SymtabVisitor().visit_program(ast.get()); + + // run SympySolver on AST + SympySolverVisitor().visit_program(ast.get()); + + std::string AST_string = ast_to_string(ast.get()); + + THEN("More SympySolver passes do nothing to the AST and don't throw") { + REQUIRE_NOTHROW(run_sympy_visitor_passes(ast.get())); + REQUIRE(AST_string == ast_to_string(ast.get())); + } + } +} + +SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", + "[visitor][sympy][derivimplicit]") { + GIVEN("Derivative block with derivimplicit solver method and conditional block") { + std::string nmodl_text = R"( + STATE { + m + } + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + DERIVATIVE states { + IF (mInf == 1) { + mInf = mInf+1 + } + m' = (mInf-m)/mTau + } + )"; + std::string expected_result = R"( + DERIVATIVE states { + EIGEN_NEWTON_SOLVE[1]{ + LOCAL old_m + }{ + IF (mInf == 1) { + mInf = mInf+1 + } + old_m = m + }{ + X[0] = m + }{ + F[0] = (-X[0]*dt+dt*mInf+mTau*(-X[0]+old_m))/mTau + J[0] = -(dt+mTau)/mTau + }{ + m = X[0] + }{ + } + })"; + THEN("SympySolver correctly inserts ode to block") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + + GIVEN("Derivative block of coupled & linear ODES, solver method sparse") { + std::string nmodl_text = R"( + STATE { + x y z + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b, c, d, h + x' = a*z + b*h + y' = c + 2*x + z' = d*z - y + } + )"; + std::string expected_result = R"( + DERIVATIVE states { + LOCAL a, b, c, d, h, old_x, old_y, old_z + old_x = x + old_y = y + old_z = z + x = (-a*dt*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(b*dt*h+old_x)*(2*a*pow(dt, 3)-d*dt+1))/(2*a*pow(dt, 3)-d*dt+1) + y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+old_x)+old_y))/(2*a*pow(dt, 3)-d*dt+1) + z = (-dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)+old_z)/(2*a*pow(dt, 3)-d*dt+1) + })"; + std::string expected_cse_result = R"( + DERIVATIVE states { + LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 + old_x = x + old_y = y + old_z = z + tmp0 = 2*a + tmp1 = -d*dt+pow(dt, 3)*tmp0+1 + tmp2 = 1/tmp1 + tmp3 = b*dt*h+old_x + tmp4 = c*dt+2*dt*tmp3+old_y + tmp5 = dt*tmp4-old_z + x = -tmp2*(a*dt*tmp5-tmp1*tmp3) + y = -tmp2*(pow(dt, 2)*tmp0*tmp5-tmp1*tmp4) + z = -tmp2*tmp5 + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + auto result_cse = + run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + REQUIRE(result_cse[0] == reindent_text(expected_cse_result)); + } + } + GIVEN("Derivative block including ODES with sparse method (from nmodl paper)") { + std::string nmodl_text = R"( + STATE { + mc m + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + mc' = -a*mc + b*m + m' = a*mc - b*m + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_mc, old_m + old_mc = mc + old_m = m + mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) + m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) + })"; + THEN("Construct & solve linear system") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block with ODES with sparse method, CONSERVE statement of form m = ...") { + std::string nmodl_text = R"( + STATE { + mc m + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + mc' = -a*mc + b*m + m' = a*mc - b*m + CONSERVE m = 1 - mc + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_mc + old_mc = mc + mc = (b*dt+old_mc)/(a*dt+b*dt+1) + m = (a*dt-old_mc+1)/(a*dt+b*dt+1) + })"; + THEN("Construct & solve linear system, replace ODE for m with rhs of CONSERVE statement") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN( + "Derivative block with ODES with sparse method, invalid CONSERVE statement of form m + mc " + "= ...") { + std::string nmodl_text = R"( + STATE { + mc m + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + mc' = -a*mc + b*m + m' = a*mc - b*m + CONSERVE m + mc = 1 + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_mc, old_m + old_mc = mc + old_m = m + mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) + m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) + })"; + THEN("Construct & solve linear system, ignore invalid CONSERVE statement") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block with ODES with sparse method, two CONSERVE statements") { + std::string nmodl_text = R"( + STATE { + c1 o1 o2 p0 p1 + } + BREAKPOINT { + SOLVE ihkin METHOD sparse + } + DERIVATIVE ihkin { + LOCAL alpha, beta, k3p, k4, k1ca, k2 + evaluate_fct(v, cai) + CONSERVE p1 = 1-p0 + CONSERVE o2 = 1-c1-o1 + c1' = (-1*(alpha*c1-beta*o1)) + o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) + o2' = (1*(k3p*o1-k4*o2)) + p0' = (-1*(k1ca*p0-k2*p1)) + p1' = (1*(k1ca*p0-k2*p1)) + })"; + std::string expected_result = R"( + DERIVATIVE ihkin { + EIGEN_LINEAR_SOLVE[5]{ + LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_p0 + }{ + evaluate_fct(v, cai) + old_c1 = c1 + old_o1 = o1 + old_p0 = p0 + }{ + X[0] = c1 + X[1] = o1 + X[2] = o2 + X[3] = p0 + X[4] = p1 + F[0] = -old_c1 + F[1] = -old_o1 + F[2] = -1 + F[3] = -old_p0 + F[4] = -1 + J[0] = -alpha*dt-1 + J[5] = beta*dt + J[10] = 0 + J[15] = 0 + J[20] = 0 + J[1] = alpha*dt + J[6] = -beta*dt-dt*k3p-1 + J[11] = dt*k4 + J[16] = 0 + J[21] = 0 + J[2] = -1 + J[7] = -1 + J[12] = -1 + J[17] = 0 + J[22] = 0 + J[3] = 0 + J[8] = 0 + J[13] = 0 + J[18] = -dt*k1ca-1 + J[23] = dt*k2 + J[4] = 0 + J[9] = 0 + J[14] = 0 + J[19] = -1 + J[24] = -1 + }{ + c1 = X[0] + o1 = X[1] + o2 = X[2] + p0 = X[3] + p1 = X[4] + }{ + } + })"; + THEN( + "Construct & solve linear system, replacing ODEs for p1 and o2 with CONSERVE statement " + "algebraic relations") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with sparse method - single var in array") { + std::string nmodl_text = R"( + STATE { + W[1] + } + ASSIGNED { + A[2] + B[1] + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + W'[0] = -A[0]*W[0] + B[0]*W[0] + 3*A[1] + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_W_0 + old_W_0 = W[0] + W[0] = (3*dt*A[1]+old_W_0)/(dt*A[0]-dt*B[0]+1) + })"; + THEN("Construct & solver linear system") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with sparse method - array vars") { + std::string nmodl_text = R"( + STATE { + M[2] + } + ASSIGNED { + A[2] + B[2] + } + BREAKPOINT { + SOLVE scheme1 METHOD sparse + } + DERIVATIVE scheme1 { + M'[0] = -A[0]*M[0] + B[0]*M[1] + M'[1] = A[1]*M[0] - B[1]*M[1] + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + LOCAL old_M_0, old_M_1 + old_M_0 = M[0] + old_M_1 = M[1] + M[0] = (dt*old_M_0*B[1]+dt*old_M_1*B[0]+old_M_0)/(pow(dt, 2)*A[0]*B[1]-pow(dt, 2)*A[1]*B[0]+dt*A[0]+dt*B[1]+1) + M[1] = -(dt*old_M_0*A[1]+old_M_1*(dt*A[0]+1))/(pow(dt, 2)*A[1]*B[0]-(dt*A[0]+1)*(dt*B[1]+1)) + })"; + THEN("Construct & solver linear system") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with derivimplicit method - single var in array") { + std::string nmodl_text = R"( + STATE { + W[1] + } + ASSIGNED { + A[2] + B[1] + } + BREAKPOINT { + SOLVE scheme1 METHOD derivimplicit + } + DERIVATIVE scheme1 { + W'[0] = -A[0]*W[0] + B[0]*W[0] + 3*A[1] + } + )"; + std::string expected_result = R"( + DERIVATIVE scheme1 { + EIGEN_NEWTON_SOLVE[1]{ + LOCAL old_W_0 + }{ + old_W_0 = W[0] + }{ + X[0] = W[0] + }{ + F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3*dt*A[1]+old_W_0 + J[0] = -dt*A[0]+dt*B[0]-1 + }{ + W[0] = X[0] + }{ + } + })"; + THEN("Construct newton solve block") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Derivative block including ODES with derivimplicit method") { + std::string nmodl_text = R"( + STATE { + m h n + } + BREAKPOINT { + SOLVE states METHOD derivimplicit + } + DERIVATIVE states { + rates(v) + m' = (minf-m)/mtau - 3*h + h' = (hinf-h)/htau + m*m + n' = (ninf-n)/ntau + } + )"; + /// new derivative block with EigenNewtonSolverBlock node + std::string expected_result = R"( + DERIVATIVE states { + EIGEN_NEWTON_SOLVE[3]{ + LOCAL old_m, old_h, old_n + }{ + rates(v) + old_m = m + old_h = h + old_n = n + }{ + X[0] = m + X[1] = h + X[2] = n + }{ + F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]-3*X[1]*dt+old_m))/mtau + F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau + F[2] = (-X[2]*dt+dt*ninf+ntau*(-X[2]+old_n))/ntau + J[0] = -(dt+mtau)/mtau + J[3] = -3*dt + J[6] = 0 + J[1] = 2*X[0]*dt + J[4] = -(dt+htau)/htau + J[7] = 0 + J[2] = 0 + J[5] = 0 + J[8] = -(dt+ntau)/ntau + }{ + m = X[0] + h = X[1] + n = X[2] + }{ + } + })"; + THEN("Construct newton solve block") { + CAPTURE(nmodl_text); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + REQUIRE(result[0] == reindent_text(expected_result)); + } + } + GIVEN("Multiple derivative blocks each with derivimplicit method") { + std::string nmodl_text = R"( + STATE { + m h + } + BREAKPOINT { + SOLVE states1 METHOD derivimplicit + SOLVE states2 METHOD derivimplicit + } + + DERIVATIVE states1 { + m' = (minf-m)/mtau + h' = (hinf-h)/htau + m*m + } + + DERIVATIVE states2 { + h' = (hinf-h)/htau + m*m + m' = (minf-m)/mtau + h + } + )"; + /// EigenNewtonSolverBlock in each derivative block + std::string expected_result_0 = R"( + DERIVATIVE states1 { + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_m, old_h + }{ + old_m = m + old_h = h + }{ + X[0] = m + X[1] = h + }{ + F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]+old_m))/mtau + F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau + J[0] = -(dt+mtau)/mtau + J[2] = 0 + J[1] = 2*X[0]*dt + J[3] = -(dt+htau)/htau + }{ + m = X[0] + h = X[1] + }{ + } + })"; + std::string expected_result_1 = R"( + DERIVATIVE states2 { + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_h, old_m + }{ + old_h = h + old_m = m + }{ + X[0] = m + X[1] = h + }{ + F[0] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau + F[1] = (-X[0]*dt+dt*minf+mtau*(-X[0]+X[1]*dt+old_m))/mtau + J[0] = 2*X[0]*dt + J[2] = -(dt+htau)/htau + J[1] = -(dt+mtau)/mtau + J[3] = dt + }{ + m = X[0] + h = X[1] + }{ + } + })"; + THEN("Construct newton solve block") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + CAPTURE(nmodl_text); + REQUIRE(result[0] == reindent_text(expected_result_0)); + REQUIRE(result[1] == reindent_text(expected_result_1)); + } + } +} + + +//============================================================================= +// LINEAR solve block tests +//============================================================================= + +SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { + GIVEN("1 state-var numeric LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x + } + LINEAR lin { + ~ x = 5 + })"; + std::string expected_text = R"( + LINEAR lin { + x = 5 + })"; + THEN("solve analytically") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("1 state-var symbolic LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x + } + LINEAR lin { + ~ 2*a*x = 1 + })"; + std::string expected_text = R"( + LINEAR lin { + x = 0.5/a + })"; + THEN("solve analytically") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("2 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + ~ x + 4*y = 5*a + ~ x - y = 0 + })"; + std::string expected_text = R"( + LINEAR lin { + x = a + y = a + })"; + THEN("solve analytically") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("2 state-var LINEAR solve block, post-solve statements") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + ~ x + 4*y = 5*a + ~ x - y = 0 + x = x + 2 + y = y - a + })"; + std::string expected_text = R"( + LINEAR lin { + x = a + y = a + x = x+2 + y = y-a + })"; + THEN("solve analytically, insert in correct location") { + CAPTURE(reindent_text(nmodl_text)); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("2 state-var LINEAR solve block, mixed & post-solve statements") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + ~ x + 4*y = 5*a + a2 = 3*b + ~ x - y = 0 + y = y - a + })"; + std::string expected_text = R"( + LINEAR lin { + a2 = 3*b + x = a + y = a + y = y-a + })"; + THEN("solve analytically, insert in correct location") { + CAPTURE(reindent_text(nmodl_text)); + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("3 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x y z + } + LINEAR lin { + ~ x + 4*c*y = -5.343*a + ~ a + x/b + z - y = 0.842*b*b + ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c + })"; + std::string expected_text = R"( + LINEAR lin { + x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)+(1*b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(1*b+4*c)-4*c*(5.343*a+b*(-1*a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + y = (1*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-1*pow(a, 2)*pow(b, 2)*(1*b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + z = pow(a, 2)*b*(c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-(1*b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + })"; + THEN("solve analytically") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("array state-var numeric LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + s[3] + } + LINEAR lin { + ~ s[0] = 1 + ~ s[1] = 3 + ~ s[2] + s[1] = s[0] + })"; + std::string expected_text = R"( + LINEAR lin { + s[0] = 1 + s[1] = 3 + s[2] = -2 + })"; + THEN("solve analytically") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("4 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + w x y z + } + LINEAR lin { + ~ w + z/3.2 = -2.0*y + ~ x + 4*c*y = -5.343*a + ~ a + x/b + z - y = 0.842*b*b + ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c + })"; + std::string expected_text = R"( + LINEAR lin { + EIGEN_LINEAR_SOLVE[4]{ + }{ + }{ + X[0] = w + X[1] = x + X[2] = y + X[3] = z + F[0] = 0 + F[1] = 5.343*a + F[2] = a-0.842*pow(b, 2) + F[3] = -1.43543/c + J[0] = -1 + J[4] = 0 + J[8] = -2 + J[12] = -0.3125 + J[1] = 0 + J[5] = -1 + J[9] = -4*c + J[13] = 0 + J[2] = 0 + J[6] = -1/b + J[10] = 1 + J[14] = -1 + J[3] = 0 + J[7] = -1 + J[11] = -1.3 + J[15] = 0.1/(pow(a, 2)*b) + }{ + w = X[0] + x = X[1] + y = X[2] + z = X[3] + }{ + } + })"; + THEN("return matrix system to solve") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("12 state-var LINEAR solve block") { + std::string nmodl_text = R"( + STATE { + C1 C2 C3 C4 C5 I1 I2 I3 I4 I5 I6 O + } + LINEAR seqinitial { + ~ I1*bi1 + C2*b01 - C1*( fi1+f01) = 0 + ~ C1*f01 + I2*bi2 + C3*b02 - C2*(b01+fi2+f02) = 0 + ~ C2*f02 + I3*bi3 + C4*b03 - C3*(b02+fi3+f03) = 0 + ~ C3*f03 + I4*bi4 + C5*b04 - C4*(b03+fi4+f04) = 0 + ~ C4*f04 + I5*bi5 + O*b0O - C5*(b04+fi5+f0O) = 0 + ~ C5*f0O + I6*bin - O*(b0O+fin) = 0 + ~ C1*fi1 + I2*b11 - I1*( bi1+f11) = 0 + ~ I1*f11 + C2*fi2 + I3*b12 - I2*(b11+bi2+f12) = 0 + ~ I2*f12 + C3*fi3 + I4*bi3 - I3*(b12+bi3+f13) = 0 + ~ I3*f13 + C4*fi4 + I5*b14 - I4*(b13+bi4+f14) = 0 + ~ I4*f14 + C5*fi5 + I6*b1n - I5*(b14+bi5+f1n) = 0 + ~ C1 + C2 + C3 + C4 + C5 + O + I1 + I2 + I3 + I4 + I5 + I6 = 1 + })"; + std::string expected_text = R"( + LINEAR seqinitial { + EIGEN_LINEAR_SOLVE[12]{ + }{ + }{ + X[0] = C1 + X[1] = C2 + X[2] = C3 + X[3] = C4 + X[4] = C5 + X[5] = I1 + X[6] = I2 + X[7] = I3 + X[8] = I4 + X[9] = I5 + X[10] = I6 + X[11] = O + F[0] = 0 + F[1] = 0 + F[2] = 0 + F[3] = 0 + F[4] = 0 + F[5] = 0 + F[6] = 0 + F[7] = 0 + F[8] = 0 + F[9] = 0 + F[10] = 0 + F[11] = -1 + J[0] = f01+fi1 + J[12] = -b01 + J[24] = 0 + J[36] = 0 + J[48] = 0 + J[60] = -bi1 + J[72] = 0 + J[84] = 0 + J[96] = 0 + J[108] = 0 + J[120] = 0 + J[132] = 0 + J[1] = -f01 + J[13] = b01+f02+fi2 + J[25] = -b02 + J[37] = 0 + J[49] = 0 + J[61] = 0 + J[73] = -bi2 + J[85] = 0 + J[97] = 0 + J[109] = 0 + J[121] = 0 + J[133] = 0 + J[2] = 0 + J[14] = -f02 + J[26] = b02+f03+fi3 + J[38] = -b03 + J[50] = 0 + J[62] = 0 + J[74] = 0 + J[86] = -bi3 + J[98] = 0 + J[110] = 0 + J[122] = 0 + J[134] = 0 + J[3] = 0 + J[15] = 0 + J[27] = -f03 + J[39] = b03+f04+fi4 + J[51] = -b04 + J[63] = 0 + J[75] = 0 + J[87] = 0 + J[99] = -bi4 + J[111] = 0 + J[123] = 0 + J[135] = 0 + J[4] = 0 + J[16] = 0 + J[28] = 0 + J[40] = -f04 + J[52] = b04+f0O+fi5 + J[64] = 0 + J[76] = 0 + J[88] = 0 + J[100] = 0 + J[112] = -bi5 + J[124] = 0 + J[136] = -b0O + J[5] = 0 + J[17] = 0 + J[29] = 0 + J[41] = 0 + J[53] = -f0O + J[65] = 0 + J[77] = 0 + J[89] = 0 + J[101] = 0 + J[113] = 0 + J[125] = -bin + J[137] = b0O+fin + J[6] = -fi1 + J[18] = 0 + J[30] = 0 + J[42] = 0 + J[54] = 0 + J[66] = bi1+f11 + J[78] = -b11 + J[90] = 0 + J[102] = 0 + J[114] = 0 + J[126] = 0 + J[138] = 0 + J[7] = 0 + J[19] = -fi2 + J[31] = 0 + J[43] = 0 + J[55] = 0 + J[67] = -f11 + J[79] = b11+bi2+f12 + J[91] = -b12 + J[103] = 0 + J[115] = 0 + J[127] = 0 + J[139] = 0 + J[8] = 0 + J[20] = 0 + J[32] = -fi3 + J[44] = 0 + J[56] = 0 + J[68] = 0 + J[80] = -f12 + J[92] = b12+bi3+f13 + J[104] = -bi3 + J[116] = 0 + J[128] = 0 + J[140] = 0 + J[9] = 0 + J[21] = 0 + J[33] = 0 + J[45] = -fi4 + J[57] = 0 + J[69] = 0 + J[81] = 0 + J[93] = -f13 + J[105] = b13+bi4+f14 + J[117] = -b14 + J[129] = 0 + J[141] = 0 + J[10] = 0 + J[22] = 0 + J[34] = 0 + J[46] = 0 + J[58] = -fi5 + J[70] = 0 + J[82] = 0 + J[94] = 0 + J[106] = -f14 + J[118] = b14+bi5+f1n + J[130] = -b1n + J[142] = 0 + J[11] = -1 + J[23] = -1 + J[35] = -1 + J[47] = -1 + J[59] = -1 + J[71] = -1 + J[83] = -1 + J[95] = -1 + J[107] = -1 + J[119] = -1 + J[131] = -1 + J[143] = -1 + }{ + C1 = X[0] + C2 = X[1] + C3 = X[2] + C4 = X[3] + C5 = X[4] + I1 = X[5] + I2 = X[6] + I3 = X[7] + I4 = X[8] + I5 = X[9] + I6 = X[10] + O = X[11] + }{ + } + })"; + THEN("return matrix system to be solved") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } +} + +//============================================================================= +// NONLINEAR solve block tests +//============================================================================= + +SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][sympy][nonlinear]") { + GIVEN("1 state-var numeric NONLINEAR solve block") { + std::string nmodl_text = R"( + STATE { + x + } + NONLINEAR nonlin { + ~ x = 5 + })"; + std::string expected_text = R"( + NONLINEAR nonlin { + EIGEN_NEWTON_SOLVE[1]{ + }{ + }{ + X[0] = x + }{ + F[0] = -X[0]+5 + J[0] = -1 + }{ + x = X[0] + }{ + } + })"; + THEN("return F & J for newton solver") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } + GIVEN("array state-var numeric NONLINEAR solve block") { + std::string nmodl_text = R"( + STATE { + s[3] + } + NONLINEAR nonlin { + ~ s[0] = 1 + ~ s[1] = 3 + ~ s[2] + s[1] = s[0] + })"; + std::string expected_text = R"( + NONLINEAR nonlin { + EIGEN_NEWTON_SOLVE[3]{ + }{ + }{ + X[0] = s[0] + X[1] = s[1] + X[2] = s[2] + }{ + F[0] = -X[0]+1 + F[1] = -X[1]+3 + F[2] = X[0]-X[1]-X[2] + J[0] = -1 + J[3] = 0 + J[6] = 0 + J[1] = 0 + J[4] = -1 + J[7] = 0 + J[2] = 1 + J[5] = -1 + J[8] = -1 + }{ + s[0] = X[0] + s[1] = X[1] + s[2] = X[2] + }{ + } + })"; + THEN("return F & J for newton solver") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } +} diff --git a/test/nmodl/transpiler/visitor/verbatim.cpp b/test/nmodl/transpiler/visitor/verbatim.cpp new file mode 100644 index 0000000000..947dc6bf67 --- /dev/null +++ b/test/nmodl/transpiler/visitor/verbatim.cpp @@ -0,0 +1,49 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "visitors/verbatim_visitor.hpp" + +using namespace nmodl; +using namespace visitor; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// Verbatim visitor tests +//============================================================================= + +std::vector<std::string> run_verbatim_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + VerbatimVisitor v; + v.visit_program(ast.get()); + return v.verbatim_blocks(); +} + +TEST_CASE("Parse VERBATIM block using Verbatim Visitor") { + SECTION("Single Block") { + std::string text = "VERBATIM int a; ENDVERBATIM"; + auto blocks = run_verbatim_visitor(text); + + REQUIRE(blocks.size() == 1); + REQUIRE(blocks.front() == " int a; "); + } + + SECTION("Multiple Blocks") { + std::string text = "VERBATIM int a; ENDVERBATIM VERBATIM float b; ENDVERBATIM"; + auto blocks = run_verbatim_visitor(text); + + REQUIRE(blocks.size() == 2); + REQUIRE(blocks[0] == " int a; "); + REQUIRE(blocks[1] == " float b; "); + } +} diff --git a/test/nmodl/transpiler/visitor/visitor.cpp b/test/nmodl/transpiler/visitor/visitor.cpp deleted file mode 100644 index 7e67b369f8..0000000000 --- a/test/nmodl/transpiler/visitor/visitor.cpp +++ /dev/null @@ -1,5383 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#define CATCH_CONFIG_RUNNER - -#include <string> - -#include "catch/catch.hpp" -#include "utils/logger.hpp" -#include <pybind11/embed.h> - -#include "parser/nmodl_driver.hpp" -#include "test/utils/nmodl_constructs.hpp" -#include "test/utils/test_utils.hpp" -#include "visitors/constant_folder_visitor.hpp" -#include "visitors/defuse_analyze_visitor.hpp" -#include "visitors/inline_visitor.hpp" -#include "visitors/json_visitor.hpp" -#include "visitors/kinetic_block_visitor.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "visitors/localize_visitor.hpp" -#include "visitors/lookup_visitor.hpp" -#include "visitors/loop_unroll_visitor.hpp" -#include "visitors/neuron_solve_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" -#include "visitors/perf_visitor.hpp" -#include "visitors/rename_visitor.hpp" -#include "visitors/solve_block_visitor.hpp" -#include "visitors/steadystate_visitor.hpp" -#include "visitors/sympy_conductance_visitor.hpp" -#include "visitors/sympy_solver_visitor.hpp" -#include "visitors/symtab_visitor.hpp" -#include "visitors/verbatim_var_rename_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" - -using json = nlohmann::json; - -using namespace nmodl; -using namespace visitor; -using namespace test_utils; - -using ast::AstNodeType; -using nmodl::parser::NmodlDriver; -using symtab::syminfo::NmodlType; - -int main(int argc, char* argv[]) { - // initialize python interpreter once for - // entire catch executable - pybind11::scoped_interpreter guard{}; - logger->set_level(spdlog::level::debug); - int result = Catch::Session().run(argc, argv); - return result; -} - -//============================================================================= -// Verbatim visitor tests -//============================================================================= - -std::vector<std::string> run_verbatim_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - VerbatimVisitor v; - v.visit_program(ast.get()); - return v.verbatim_blocks(); -} - -TEST_CASE("Verbatim Visitor") { - SECTION("Single Block") { - std::string text = "VERBATIM int a; ENDVERBATIM"; - auto blocks = run_verbatim_visitor(text); - - REQUIRE(blocks.size() == 1); - REQUIRE(blocks.front() == " int a; "); - } - - SECTION("Multiple Blocks") { - std::string text = "VERBATIM int a; ENDVERBATIM VERBATIM float b; ENDVERBATIM"; - auto blocks = run_verbatim_visitor(text); - - REQUIRE(blocks.size() == 2); - REQUIRE(blocks[0] == " int a; "); - REQUIRE(blocks[1] == " float b; "); - } -} - -//============================================================================= -// JSON visitor tests -//============================================================================= - -std::string run_json_visitor(const std::string& text, bool compact = false) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - return to_json(ast.get(), compact); -} - -TEST_CASE("JSON Visitor") { - SECTION("JSON object test") { - std::string nmodl_text = "NEURON {}"; - json expected = R"( - { - "Program": [ - { - "NeuronBlock": [ - { - "StatementBlock": [] - } - ] - } - ] - } - )"_json; - - auto json_text = run_json_visitor(nmodl_text); - json result = json::parse(json_text); - - REQUIRE(expected == result); - } - - SECTION("JSON text test (compact format)") { - std::string nmodl_text = "NEURON {}"; - std::string expected = R"({"Program":[{"NeuronBlock":[{"StatementBlock":[]}]}]})"; - - auto result = run_json_visitor(nmodl_text, true); - REQUIRE(result == expected); - } -} - -//============================================================================= -// Symtab and Perf visitor tests -//============================================================================= - -SCENARIO("Symbol table generation and Perf stat visitor pass") { - GIVEN("A mod file and associated ast") { - std::string nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar, A_AMPA_step : range + anything = range (2) - GLOBAL Rstate : global + anything = global - POINTER rng : pointer = global - BBCOREPOINTER coreRng : pointer + assigned = range - } - - PARAMETER { - gNaTs2_tbar = 0.00001 (S/cm2) : range + parameter = range already - tau_r = 0.2 (ms) : parameter = global - tau_d_AMPA = 1.0 : parameter = global - tsyn_fac = 11.1 : parameter + assigned = range - } - - ASSIGNED { - v (mV) : only assigned = range - ena (mV) : only assigned = range - tsyn_fac : parameter + assigned = range already - A_AMPA_step : range + assigned = range already - AmState : only assigned = range - Rstate : global + assigned == global already - coreRng : pointer + assigned = range already - } - - STATE { - m : state = range - h : state = range - } - - BREAKPOINT { - CONDUCTANCE gNaTs2_t USEION na - SOLVE states METHOD cnexp - { - LOCAL gNaTs2_t - { - gNaTs2_t = gNaTs2_tbar*m*m*m*h - } - ina = gNaTs2_t*(v-ena) - } - { - m = hBetaf(11+v) - m = 12/gNaTs2_tbar - } - { - gNaTs2_tbar = gNaTs2_tbar*gNaTs2_tbar + 11.0 - gNaTs2_tbar = 12.0 - } - } - - FUNCTION hBetaf(v) { - hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) - } - )"; - - NmodlDriver driver; - auto ast = driver.parse_string(nmodl_text); - - WHEN("Symbol table generator pass runs") { - SymtabVisitor v; - v.visit_program(ast.get()); - auto symtab = ast->get_model_symbol_table(); - - THEN("Can lookup for defined variables") { - auto symbol = symtab->lookup("m"); - REQUIRE(symbol->has_any_property(NmodlType::dependent_def)); - REQUIRE_FALSE(symbol->has_any_property(NmodlType::local_var)); - - symbol = symtab->lookup("gNaTs2_tbar"); - REQUIRE(symbol->has_any_property(NmodlType::param_assign)); - REQUIRE(symbol->has_any_property(NmodlType::range_var)); - - symbol = symtab->lookup("ena"); - REQUIRE(symbol->has_any_property(NmodlType::read_ion_var)); - } - THEN("Can lookup for defined functions") { - auto symbol = symtab->lookup("hBetaf"); - REQUIRE(symbol->has_any_property(NmodlType::function_block)); - } - THEN("Non existent variable lookup returns nullptr") { - REQUIRE(symtab->lookup("xyz") == nullptr); - } - - WHEN("Perf visitor pass runs after symtab visitor") { - PerfVisitor v; - v.visit_program(ast.get()); - - auto result = v.get_total_perfstat(); - auto num_instance_var = v.get_instance_variable_count(); - auto num_global_var = v.get_global_variable_count(); - auto num_state_var = v.get_state_variable_count(); - auto num_const_instance_var = v.get_const_instance_variable_count(); - auto num_const_global_var = v.get_const_global_variable_count(); - - THEN("Performance counters are updated") { - REQUIRE(result.n_add == 2); - REQUIRE(result.n_sub == 4); - REQUIRE(result.n_mul == 7); - REQUIRE(result.n_div == 3); - REQUIRE(result.n_exp == 1); - REQUIRE(result.n_global_read == 7); - REQUIRE(result.n_unique_global_read == 4); - REQUIRE(result.n_global_write == 3); - REQUIRE(result.n_unique_global_write == 2); - REQUIRE(result.n_constant_read == 4); - REQUIRE(result.n_unique_constant_read == 1); - REQUIRE(result.n_constant_write == 2); - REQUIRE(result.n_unique_constant_write == 1); - REQUIRE(result.n_local_read == 3); - REQUIRE(result.n_local_write == 2); - REQUIRE(result.n_ext_func_call == 1); - REQUIRE(result.n_int_func_call == 1); - REQUIRE(result.n_neg == 3); - REQUIRE(num_instance_var == 9); - REQUIRE(num_global_var == 4); - REQUIRE(num_state_var == 2); - REQUIRE(num_const_instance_var == 2); - REQUIRE(num_const_global_var == 2); - } - } - } - - WHEN("Perf visitor pass runs before symtab visitor") { - PerfVisitor v; - THEN("exception is thrown") { - REQUIRE_THROWS_WITH(v.visit_program(ast.get()), Catch::Contains("table not setup")); - } - } - } -} - -//============================================================================= -// AST to NMODL printer tests -//============================================================================= - -std::string run_nmodl_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - std::stringstream stream; - NmodlPrintVisitor v(stream); - - v.visit_program(ast.get()); - return stream.str(); -} - -SCENARIO("Test for AST back to NMODL transformation") { - for (const auto& construct: nmodl_valid_constructs) { - auto test_case = construct.second; - std::string input_nmodl_text = reindent_text(test_case.input); - std::string output_nmodl_text = reindent_text(test_case.output); - GIVEN(test_case.name) { - THEN("Visitor successfully returns : " + input_nmodl_text) { - auto result = run_nmodl_visitor(input_nmodl_text); - REQUIRE(result == output_nmodl_text); - } - } - } -} - -//============================================================================= -// Variable rename tests -//============================================================================= -std::string run_var_rename_visitor(const std::string& text, - std::vector<std::pair<std::string, std::string>> variables) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - { - for (const auto& variable: variables) { - RenameVisitor v(variable.first, variable.second); - v.visit_program(ast.get()); - } - } - std::stringstream stream; - { - NmodlPrintVisitor v(stream); - v.visit_program(ast.get()); - } - return stream.str(); -} - -SCENARIO("Renaming any variable in mod file with RenameVisitor") { - GIVEN("A mod file") { - // sample nmodl text - std::string input_nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar - } - - PARAMETER { - gNaTs2_tbar = 0.1 (S/cm2) - } - - STATE { - m - h - } - - COMMENT - m and gNaTs2_tbar remain same here - ENDCOMMENT - - BREAKPOINT { - LOCAL gNaTs2_t - gNaTs2_t = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t*(v-ena) - } - - FUNCTION mAlpha() { - } - )"; - - /// expected result after renaming m, gNaTs2_tbar and mAlpha - std::string output_nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE new_gNaTs2_tbar - } - - PARAMETER { - new_gNaTs2_tbar = 0.1 (S/cm2) - } - - STATE { - mm - h - } - - COMMENT - m and gNaTs2_tbar remain same here - ENDCOMMENT - - BREAKPOINT { - LOCAL gNaTs2_t - gNaTs2_t = new_gNaTs2_tbar*mm*mm*mm*h - ina = gNaTs2_t*(v-ena) - } - - FUNCTION mBeta() { - } - )"; - - std::string input = reindent_text(input_nmodl_text); - std::string expected_output = reindent_text(output_nmodl_text); - - THEN("existing variables could be renamed") { - std::vector<std::pair<std::string, std::string>> variables = { - {"m", "mm"}, - {"gNaTs2_tbar", "new_gNaTs2_tbar"}, - {"mAlpha", "mBeta"}, - }; - auto result = run_var_rename_visitor(input, variables); - REQUIRE(result == expected_output); - } - - THEN("non-existing variables will be ignored") { - std::vector<std::pair<std::string, std::string>> variables = { - {"unknown_variable", "doesnot_matter"}}; - auto result = run_var_rename_visitor(input, variables); - REQUIRE(result == input); - } - } -} - -//============================================================================= -// Local variable rename tests -//============================================================================= - -std::string run_local_var_rename_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - { - SymtabVisitor v; - v.visit_program(ast.get()); - } - - { - VerbatimVarRenameVisitor v; - v.visit_program(ast.get()); - } - - { - LocalVarRenameVisitor v; - v.visit_program(ast.get()); - } - std::stringstream stream; - { - NmodlPrintVisitor v(stream); - v.visit_program(ast.get()); - } - return stream.str(); -} - -SCENARIO("Presence of local and global variables in same block") { - GIVEN("A neuron block and procedure with same variable name") { - std::string nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar - } - - PROCEDURE rates() { - LOCAL gNaTs2_tbar - gNaTs2_tbar = 2.1 + ena - } - )"; - - std::string expected_nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar - } - - PROCEDURE rates() { - LOCAL gNaTs2_tbar_r_0 - gNaTs2_tbar_r_0 = 2.1+ena - } - )"; - - THEN("var renaming pass changes only local variables in procedure") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(expected_nmodl_text); - auto result = run_local_var_rename_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Absence of global blocks") { - GIVEN("Procedures containing same variables") { - std::string nmodl_text = R"( - PROCEDURE rates_1() { - LOCAL gNaTs2_tbar - gNaTs2_tbar = 2.1+ena - } - - PROCEDURE rates_2() { - LOCAL gNaTs2_tbar - gNaTs2_tbar = 2.1+ena - } - )"; - - THEN("nothing gets renamed") { - std::string input = reindent_text(nmodl_text); - auto result = run_local_var_rename_visitor(input); - REQUIRE(result == input); - } - } -} - -SCENARIO("Variable renaming in nested blocks") { - GIVEN("Mod file containing procedures with nested blocks") { - std::string input_nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar - } - - PARAMETER { - gNaTs2_tbar = 0.1 (S/cm2) - tau = 11.1 - } - - STATE { - m - h - } - - BREAKPOINT { - LOCAL gNaTs2_t - gNaTs2_t = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t*(v-ena) - { - LOCAL gNaTs2_t, h - gNaTs2_t = m + h - { - LOCAL m - m = gNaTs2_t + h - { - LOCAL m, h - VERBATIM - _lm = 12 - ENDVERBATIM - } - } - } - } - - PROCEDURE rates() { - LOCAL x, m - m = x + gNaTs2_tbar - { - { - LOCAL h, x, gNaTs2_tbar - m = h * x * gNaTs2_tbar + tau - } - } - } - )"; - - std::string expected_nmodl_text = R"( - NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar - } - - PARAMETER { - gNaTs2_tbar = 0.1 (S/cm2) - tau = 11.1 - } - - STATE { - m - h - } - - BREAKPOINT { - LOCAL gNaTs2_t - gNaTs2_t = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t*(v-ena) - { - LOCAL gNaTs2_t_r_0, h_r_1 - gNaTs2_t_r_0 = m+h_r_1 - { - LOCAL m_r_1 - m_r_1 = gNaTs2_t_r_0+h_r_1 - { - LOCAL m_r_0, h_r_0 - VERBATIM - m_r_0 = 12 - ENDVERBATIM - } - } - } - } - - PROCEDURE rates() { - LOCAL x, m_r_2 - m_r_2 = x+gNaTs2_tbar - { - { - LOCAL h_r_2, x_r_0, gNaTs2_tbar_r_0 - m_r_2 = h_r_2*x_r_0*gNaTs2_tbar_r_0+tau - } - } - } - )"; - - THEN("variables conflicting with global variables get renamed starting from inner block") { - std::string input = reindent_text(input_nmodl_text); - auto expected_result = reindent_text(expected_nmodl_text); - auto result = run_local_var_rename_visitor(input); - REQUIRE(result == expected_result); - } - } -} - - -SCENARIO("Presence of local variable in verbatim block") { - GIVEN("A neuron block and procedure with same variable name") { - std::string nmodl_text = R"( - NEURON { - RANGE gNaTs2_tbar - } - - PROCEDURE rates() { - LOCAL gNaTs2_tbar, x - VERBATIM - _lx = _lgNaTs2_tbar - #define my_macro_var _lgNaTs2_tbar*2 - ENDVERBATIM - gNaTs2_tbar = my_macro_var + 1 - } - - PROCEDURE alpha() { - VERBATIM - _p_gNaTs2_tbar = 12 - ENDVERBATIM - } - )"; - - std::string expected_nmodl_text = R"( - NEURON { - RANGE gNaTs2_tbar - } - - PROCEDURE rates() { - LOCAL gNaTs2_tbar_r_0, x - VERBATIM - x = gNaTs2_tbar_r_0 - #define my_macro_var gNaTs2_tbar_r_0*2 - ENDVERBATIM - gNaTs2_tbar_r_0 = my_macro_var+1 - } - - PROCEDURE alpha() { - VERBATIM - gNaTs2_tbar = 12 - ENDVERBATIM - } - )"; - - THEN("var renaming pass changes local & global variable in verbatim block") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(expected_nmodl_text); - auto result = run_local_var_rename_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -//============================================================================= -// Procedure/Function inlining tests -//============================================================================= - -std::string run_inline_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - { - SymtabVisitor v; - v.visit_program(ast.get()); - } - - { - InlineVisitor v; - v.visit_program(ast.get()); - } - - std::stringstream stream; - { - NmodlPrintVisitor v(stream); - v.visit_program(ast.get()); - } - return stream.str(); -} - -SCENARIO("External procedure calls") { - GIVEN("Procedures with external procedure call") { - std::string nmodl_text = R"( - PROCEDURE rates_1() { - hello() - } - - PROCEDURE rates_2() { - bye() - } - )"; - - THEN("nothing gets inlinine") { - std::string input = reindent_text(nmodl_text); - auto result = run_inline_visitor(input); - REQUIRE(result == input); - } - } -} - -SCENARIO("Simple procedure inlining") { - GIVEN("A procedure calling another procedure") { - std::string input_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x - rates_2(23.1) - } - - PROCEDURE rates_2(y) { - LOCAL x - x = 21.1*v+y - } - )"; - - std::string output_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x - { - LOCAL x, y_in_0 - y_in_0 = 23.1 - x = 21.1*v+y_in_0 - } - } - - PROCEDURE rates_2(y) { - LOCAL x - x = 21.1*v+y - } - )"; - THEN("Procedure body gets inlined") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Nested procedure inlining") { - GIVEN("A procedure with nested call chain and arguments") { - std::string input_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x, y - rates_2() - rates_3(x, y) - } - - PROCEDURE rates_2() { - LOCAL x - x = 21.1*v + rates_3(x, x+1.1) - } - - PROCEDURE rates_3(a, b) { - LOCAL c - c = 21.1*v+a*b - } - )"; - - std::string output_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x, y - { - LOCAL x, rates_3_in_0 - { - LOCAL c, a_in_0, b_in_0 - a_in_0 = x - b_in_0 = x+1.1 - c = 21.1*v+a_in_0*b_in_0 - rates_3_in_0 = 0 - } - x = 21.1*v+rates_3_in_0 - } - { - LOCAL c, a_in_1, b_in_1 - a_in_1 = x - b_in_1 = y - c = 21.1*v+a_in_1*b_in_1 - } - } - - PROCEDURE rates_2() { - LOCAL x, rates_3_in_0 - { - LOCAL c, a_in_0, b_in_0 - a_in_0 = x - b_in_0 = x+1.1 - c = 21.1*v+a_in_0*b_in_0 - rates_3_in_0 = 0 - } - x = 21.1*v+rates_3_in_0 - } - - PROCEDURE rates_3(a, b) { - LOCAL c - c = 21.1*v+a*b - } - )"; - THEN("Nested procedure gets inlined with variables renaming") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Inline function call in procedure") { - GIVEN("A procedure with function call") { - std::string input_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x - x = 12.1+rates_2() - } - - FUNCTION rates_2() { - LOCAL x - x = 21.1*12.1+11 - rates_2 = x - } - )"; - - std::string output_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x, rates_2_in_0 - { - LOCAL x - x = 21.1*12.1+11 - rates_2_in_0 = x - } - x = 12.1+rates_2_in_0 - } - - FUNCTION rates_2() { - LOCAL x - x = 21.1*12.1+11 - rates_2 = x - } - )"; - THEN("Procedure body gets inlined") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Function call within conditional statement") { - GIVEN("A procedure with function call in if statement") { - std::string input_nmodl = R"( - FUNCTION rates_1() { - IF (rates_2()) { - rates_1 = 10 - } ELSE { - rates_1 = 20 - } - } - - FUNCTION rates_2() { - rates_2 = 10 - } - )"; - - std::string output_nmodl = R"( - FUNCTION rates_1() { - LOCAL rates_2_in_0 - { - rates_2_in_0 = 10 - } - IF (rates_2_in_0) { - rates_1 = 10 - } ELSE { - rates_1 = 20 - } - } - - FUNCTION rates_2() { - rates_2 = 10 - } - )"; - - THEN("Procedure body gets inlined and return value is used in if condition") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Multiple function calls in same statement") { - GIVEN("A procedure with two function calls in binary expression") { - std::string input_nmodl = R"( - FUNCTION rates_1() { - IF (rates_2()-rates_2()) { - rates_1 = 20 - } - } - - FUNCTION rates_2() { - rates_2 = 10 - } - )"; - - std::string output_nmodl = R"( - FUNCTION rates_1() { - LOCAL rates_2_in_0, rates_2_in_1 - { - rates_2_in_0 = 10 - } - { - rates_2_in_1 = 10 - } - IF (rates_2_in_0-rates_2_in_1) { - rates_1 = 20 - } - } - - FUNCTION rates_2() { - rates_2 = 10 - } - )"; - - THEN("Procedure body gets inlined") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } - - GIVEN("A procedure with multiple function calls in an expression") { - std::string input_nmodl = R"( - FUNCTION rates_1() { - LOCAL x - x = (rates_2()+(rates_2()/rates_2())) - } - - FUNCTION rates_2() { - rates_2 = 10 - } - )"; - - std::string output_nmodl = R"( - FUNCTION rates_1() { - LOCAL x, rates_2_in_0, rates_2_in_1, rates_2_in_2 - { - rates_2_in_0 = 10 - } - { - rates_2_in_1 = 10 - } - { - rates_2_in_2 = 10 - } - x = (rates_2_in_0+(rates_2_in_1/rates_2_in_2)) - } - - FUNCTION rates_2() { - rates_2 = 10 - } - )"; - - THEN("Procedure body gets inlined and return values are used in an expression") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Nested function calls withing arguments") { - GIVEN("A procedure with function call") { - std::string input_nmodl = R"( - FUNCTION rates_2() { - IF (rates_3(11,21)) { - rates_2 = 10.1 - } - rates_2 = rates_3(12,22) - } - - FUNCTION rates_1() { - rates_1 = 12.1+rates_2()+exp(12.1) - } - - FUNCTION rates_3(x, y) { - rates_3 = x+y - } - )"; - - std::string output_nmodl = R"( - FUNCTION rates_2() { - LOCAL rates_3_in_0, rates_3_in_1 - { - LOCAL x_in_0, y_in_0 - x_in_0 = 11 - y_in_0 = 21 - rates_3_in_0 = x_in_0+y_in_0 - } - IF (rates_3_in_0) { - rates_2 = 10.1 - } - { - LOCAL x_in_1, y_in_1 - x_in_1 = 12 - y_in_1 = 22 - rates_3_in_1 = x_in_1+y_in_1 - } - rates_2 = rates_3_in_1 - } - - FUNCTION rates_1() { - LOCAL rates_2_in_0 - { - LOCAL rates_3_in_0, rates_3_in_1 - { - LOCAL x_in_0, y_in_0 - x_in_0 = 11 - y_in_0 = 21 - rates_3_in_0 = x_in_0+y_in_0 - } - IF (rates_3_in_0) { - rates_2_in_0 = 10.1 - } - { - LOCAL x_in_1, y_in_1 - x_in_1 = 12 - y_in_1 = 22 - rates_3_in_1 = x_in_1+y_in_1 - } - rates_2_in_0 = rates_3_in_1 - } - rates_1 = 12.1+rates_2_in_0+exp(12.1) - } - - FUNCTION rates_3(x, y) { - rates_3 = x+y - } - )"; - - THEN("Procedure body gets inlined") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Function call in non-binary expression") { - GIVEN("A function call in unary expression") { - std::string input_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x - x = (-rates_2(23.1)) - } - - FUNCTION rates_2(y) { - rates_2 = 21.1*v+y - } - )"; - - std::string output_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x, rates_2_in_0 - { - LOCAL y_in_0 - y_in_0 = 23.1 - rates_2_in_0 = 21.1*v+y_in_0 - } - x = (-rates_2_in_0) - } - - FUNCTION rates_2(y) { - rates_2 = 21.1*v+y - } - )"; - THEN("Function gets inlined in the unary expression") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } - - GIVEN("A function call as part of function argument itself") { - std::string input_nmodl = R"( - FUNCTION rates_1() { - rates_1 = 10 + rates_2( rates_2(11) ) - } - - FUNCTION rates_2(x) { - rates_2 = 10+x - } - )"; - - std::string output_nmodl = R"( - FUNCTION rates_1() { - LOCAL rates_2_in_0, rates_2_in_1 - { - LOCAL x_in_0 - x_in_0 = 11 - rates_2_in_0 = 10+x_in_0 - } - { - LOCAL x_in_1 - x_in_1 = rates_2_in_0 - rates_2_in_1 = 10+x_in_1 - } - rates_1 = 10+rates_2_in_1 - } - - FUNCTION rates_2(x) { - rates_2 = 10+x - } - )"; - THEN("Function and it's arguments gets inlined recursively") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - - -SCENARIO("Function call as standalone expression") { - GIVEN("Function call as a statement") { - std::string input_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x - rates_2(23.1) - } - - FUNCTION rates_2(y) { - rates_2 = 21.1*v+y - } - )"; - - std::string output_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x, rates_2_in_0 - { - LOCAL y_in_0 - y_in_0 = 23.1 - rates_2_in_0 = 21.1*v+y_in_0 - } - } - - FUNCTION rates_2(y) { - rates_2 = 21.1*v+y - } - )"; - THEN("Function gets inlined but it's value is not used") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Procedure call as standalone statement as well as part of expression") { - GIVEN("A procedure call in expression and statement") { - std::string input_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x - x = 10 + rates_2() - rates_2() - } - - PROCEDURE rates_2() { - } - )"; - - std::string output_nmodl = R"( - PROCEDURE rates_1() { - LOCAL x, rates_2_in_0 - { - rates_2_in_0 = 0 - } - x = 10+rates_2_in_0 - { - } - } - - PROCEDURE rates_2() { - } - )"; - THEN("Return statement from procedure (with zero value) is used") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Procedure inlining handles local-global name conflict") { - GIVEN("A procedure with local variable that exist in global scope") { - /// note that x in rates_2 should still update global x after inlining - std::string input_nmodl = R"( - NEURON { - RANGE x - } - - PROCEDURE rates_1() { - LOCAL x - x = 12 - rates_2(x) - x = 11 - } - - PROCEDURE rates_2(y) { - x = 10+y - } - )"; - - std::string output_nmodl = R"( - NEURON { - RANGE x - } - - PROCEDURE rates_1() { - LOCAL x_r_0 - x_r_0 = 12 - { - LOCAL y_in_0 - y_in_0 = x_r_0 - x = 10+y_in_0 - } - x_r_0 = 11 - } - - PROCEDURE rates_2(y) { - x = 10+y - } - )"; - - THEN("Caller variables get renamed first and then inlining is done") { - std::string input = reindent_text(input_nmodl); - auto expected_result = reindent_text(output_nmodl); - auto result = run_inline_visitor(input); - REQUIRE(result == expected_result); - } - } -} - - -//============================================================================= -// DefUseAnalyze visitor tests -//============================================================================= - -std::vector<DUChain> run_defuse_visitor(const std::string& text, const std::string& variable) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - SymtabVisitor().visit_program(ast.get()); - InlineVisitor().visit_program(ast.get()); - - std::vector<DUChain> chains; - DefUseAnalyzeVisitor v(ast->get_symbol_table()); - - /// analyse only derivative blocks in this test - auto blocks = AstLookupVisitor().lookup(ast.get(), AstNodeType::DERIVATIVE_BLOCK); - for (auto& block: blocks) { - auto node = block.get(); - chains.push_back(v.analyze(node, variable)); - } - return chains; -} - -SCENARIO("Running defuse analyzer") { - GIVEN("global variable usage in assignment statements") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - tau = 1 - tau = 1 + tau - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"name":"D"},{"name":"U"},{"name":"D"}]})"; - - THEN("Def-Use chains for individual usage is printed") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string(true) == expected_text); - REQUIRE(chains[0].eval() == DUState::D); - } - } - - GIVEN("block with use of verbatim block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - VERBATIM ENDVERBATIM - tau = 1 - VERBATIM ENDVERBATIM - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"name":"U"},{"name":"D"},{"name":"U"}]})"; - - THEN("Verbatim block is considered as use of the variable") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string(true) == expected_text); - REQUIRE(chains[0].eval() == DUState::U); - } - } - - GIVEN("use of array variables") { - std::string nmodl_text = R"( - DEFINE N 3 - STATE { - m[N] - h[N] - n[N] - o[N] - } - DERIVATIVE states { - LOCAL tau[N] - tau[0] = 1 : tau[0] is defined - tau[2] = 1 + tau[1] + tau[2] : tau[1] is used; tau[2] is defined as well as used - m[0] = m[1] : m[0] is defined and used on next line; m[1] is used - h[1] = m[0] + h[0] : h[0] is used; h[1] is defined - o[i] = 1 : o[i] is defined for any i - n[i+1] = 1 + n[i] : n[i] is used as well as defined for any i - } - )"; - - THEN("Def-Use analyser distinguishes variables by array index") { - std::string input = reindent_text(nmodl_text); - { - auto m0 = run_defuse_visitor(input, "m[0]"); - auto m1 = run_defuse_visitor(input, "m[1]"); - auto h1 = run_defuse_visitor(input, "h[1]"); - auto tau0 = run_defuse_visitor(input, "tau[0]"); - auto tau1 = run_defuse_visitor(input, "tau[1]"); - auto tau2 = run_defuse_visitor(input, "tau[2]"); - auto n0 = run_defuse_visitor(input, "n[0]"); - auto n1 = run_defuse_visitor(input, "n[1]"); - auto o0 = run_defuse_visitor(input, "o[0]"); - - REQUIRE(m0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"},{"name":"U"}]})"); - REQUIRE(m1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"}]})"); - REQUIRE(h1[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); - REQUIRE(tau0[0].to_string() == R"({"DerivativeBlock":[{"name":"LD"}]})"); - REQUIRE(tau1[0].to_string() == R"({"DerivativeBlock":[{"name":"LU"}]})"); - REQUIRE(tau2[0].to_string() == - R"({"DerivativeBlock":[{"name":"LU"},{"name":"LD"}]})"); - REQUIRE(n0[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); - REQUIRE(n1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); - REQUIRE(o0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); - } - } - } - - GIVEN("global variable definition in else block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - IF (1) { - LOCAL tau - tau = 1 - } ELSE { - tau = 1 - } - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]},{"ELSE":[{"name":"D"}]}]}]})"; - - THEN("Def-Use chains should return NONE") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::CD); - } - } - - GIVEN("global variable usage in else block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - IF (1) { - } ELSE { - tau = 1 + tau - } - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"U"},{"name":"D"}]}]}]})"; - - THEN("Def-Use chains should return USE") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::U); - } - } - - GIVEN("global variable definition in if-else block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - IF (1) { - tau = 11.1 - exp(tau) - } ELSE { - tau = 1 - } - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"},{"name":"U"}]},{"ELSE":[{"name":"D"}]}]}]})"; - - THEN("Def-Use chains should return DEF") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::D); - } - } - - GIVEN("conditional definition in nested block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - IF (1) { - IF(11) { - tau = 11.1 - exp(tau) - } - } ELSE IF(1) { - tau = 1 - } - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"},{"name":"U"}]}]}]},{"ELSEIF":[{"name":"D"}]}]}]})"; - - THEN("Def-Use chains should return DEF") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::CD); - } - } - - GIVEN("global variable usage in if-elseif-else block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - IF (1) { - tau = 1 - } - tau = 1 + tau - IF (0) { - beta = 1 - } ELSE IF (2) { - tau = 1 - } - } - - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"D"}]}]},{"name":"U"},{"name":"D"},{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSEIF":[{"name":"D"}]}]}]})"; - - THEN("Def-Use chains for individual usage is printed") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::U); - } - } - - GIVEN("global variable used in nested if-elseif-else block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - DERIVATIVE states { - IF (1) { - LOCAL tau - tau = 1 - } - IF (0) { - IF (1) { - beta = 1 - } ELSE { - tau = 1 - } - } ELSE IF (2) { - IF (1) { - beta = 1 - IF (0) { - } ELSE { - beta = 1 + exp(tau) - } - } - tau = 1 - } - } - )"; - - std::string expected_text = - R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]}]},{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"D"}]}]}]},{"ELSEIF":[{"CONDITIONAL_BLOCK":[{"IF":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"ELSE":[{"name":"U"}]}]}]}]},{"name":"D"}]}]}]})"; - - THEN("Def-Use chains for nested statements calculated") { - std::string input = reindent_text(nmodl_text); - auto chains = run_defuse_visitor(input, "tau"); - REQUIRE(chains[0].to_string() == expected_text); - REQUIRE(chains[0].eval() == DUState::U); - } - } -} - - -//============================================================================= -// Localizer visitor tests -//============================================================================= - -std::string run_localize_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - { - SymtabVisitor v1; - v1.visit_program(ast.get()); - InlineVisitor v2; - v2.visit_program(ast.get()); - LocalizeVisitor v3; - v3.visit_program(ast.get()); - } - - std::stringstream stream; - { - NmodlPrintVisitor v(stream); - v.visit_program(ast.get()); - } - return stream.str(); -} - - -SCENARIO("Localizer test with single global block") { - GIVEN("Single derivative block with variable definition") { - std::string nmodl_text = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - tau = 11.1 - exp(tau) - } - )"; - - std::string output_nmodl = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - LOCAL tau - tau = 11.1 - exp(tau) - } - )"; - - THEN("tau variable gets localized") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_localize_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -SCENARIO("Localizer test with use of verbatim block") { - GIVEN("Verbatim block usage in one of the global block") { - std::string nmodl_text = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - tau = 11.1 - exp(tau) - } - - BREAKPOINT { - VERBATIM ENDVERBATIM - } - )"; - - std::string output_nmodl = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - tau = 11.1 - exp(tau) - } - - BREAKPOINT { - VERBATIM ENDVERBATIM - } - )"; - - THEN("Localization is disabled") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_localize_visitor(input); - REQUIRE(result == expected_result); - } - } -} - - -SCENARIO("Localizer test with multiple global blocks") { - GIVEN("Multiple global blocks with definition of variable") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, beta - } - - INITIAL { - LOCAL tau - tau = beta - } - - DERIVATIVE states { - tau = 11.1 - exp(tau) - } - - BREAKPOINT { - IF (1) { - tau = beta - } ELSE { - tau = 11 - } - - } - )"; - - std::string output_nmodl = R"( - NEURON { - RANGE tau, beta - } - - INITIAL { - LOCAL tau - tau = beta - } - - DERIVATIVE states { - LOCAL tau - tau = 11.1 - exp(tau) - } - - BREAKPOINT { - LOCAL tau - IF (1) { - tau = beta - } ELSE { - tau = 11 - } - } - )"; - - THEN("Localization across multiple blocks is done") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_localize_visitor(input); - REQUIRE(result == expected_result); - } - } - - - GIVEN("Two global blocks with definition and use of the variable") { - std::string nmodl_text = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - tau = 11.1 - } - - BREAKPOINT { - IF (1) { - tau = 22 - } ELSE { - tau = exp(tau) + 11 - } - - } - )"; - - std::string output_nmodl = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - tau = 11.1 - } - - BREAKPOINT { - IF (1) { - tau = 22 - } ELSE { - tau = exp(tau)+11 - } - } - )"; - - THEN("Localization is not done due to use of variable") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_localize_visitor(input); - REQUIRE(result == expected_result); - } - } -} - - -//============================================================================= -// CnexpSolve visitor tests -//============================================================================= - -std::string run_cnexp_solve_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - SymtabVisitor().visit_program(ast.get()); - NeuronSolveVisitor().visit_program(ast.get()); - std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); - return stream.str(); -} - - -SCENARIO("CnexpSolver visitor solving ODEs") { - GIVEN("Derivative block with cnexp method in breakpoint block") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - m = m + h - } - )"; - - std::string output_nmodl = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - - DERIVATIVE states { - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) - h = h+(1-exp(dt*((((-1)))/hTau)))*(-(((hInf))/hTau)/((((-1)))/hTau)-h) - m = m+h - } - )"; - - THEN("ODEs get replaced with solution") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_cnexp_solve_visitor(input); - REQUIRE(result == expected_result); - } - } - - GIVEN("Derivative block without any solve method specification") { - std::string nmodl_text = R"( - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - )"; - - std::string output_nmodl = R"( - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - )"; - - THEN("ODEs don't get solved") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_cnexp_solve_visitor(input); - REQUIRE(result == expected_result); - } - } - - GIVEN("Derivative block with non-cnexp method in breakpoint block") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD derivimplicit - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - )"; - - std::string output_nmodl = R"( - BREAKPOINT { - SOLVE states METHOD derivimplicit - } - - DERIVATIVE states { - Dm = (mInf-m)/mTau - Dh = (hInf-h)/hTau - } - )"; - - THEN("ODEs don't get solved but state variables get replaced with Dstate ") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_cnexp_solve_visitor(input); - REQUIRE(result == expected_result); - } - } - - GIVEN("Derivative block with ODEs that needs non-cnexp method to solve") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - - DERIVATIVE states { - A_AMPA' = tau_r_AMPA/A_AMPA - } - )"; - - std::string output_nmodl = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - - DERIVATIVE states { - A_AMPA' = tau_r_AMPA/A_AMPA - } - )"; - - THEN("ODEs don't get replaced as cnexp is not possible") { - std::string input = reindent_text(nmodl_text); - auto expected_result = reindent_text(output_nmodl); - auto result = run_cnexp_solve_visitor(input); - REQUIRE(result == expected_result); - } - } -} - -//============================================================================= -// SolveBlock visitor tests -//============================================================================= - -std::string run_solve_block_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - NeuronSolveVisitor().visit_program(ast.get()); - SolveBlockVisitor().visit_program(ast.get()); - std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); - return stream.str(); -} - -TEST_CASE("SolveBlock visitor") { - SECTION("SolveBlock add NrnState block") { - GIVEN("Breakpoint block with single solve block in breakpoint") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - } - )"; - - std::string output_nmodl = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - - DERIVATIVE states { - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) - } - - NRN_STATE SOLVE states METHOD cnexp{ - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) - } - - )"; - - THEN("Single NrnState block gets added") { - auto result = run_solve_block_visitor(nmodl_text); - REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); - } - } - - GIVEN("Breakpoint block with two solve block in breakpoint") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE state1 METHOD cnexp - SOLVE state2 METHOD cnexp - } - - DERIVATIVE state1 { - m' = (mInf-m)/mTau - } - - DERIVATIVE state2 { - h' = (mInf-h)/mTau - } - )"; - - std::string output_nmodl = R"( - BREAKPOINT { - SOLVE state1 METHOD cnexp - SOLVE state2 METHOD cnexp - } - - DERIVATIVE state1 { - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) - } - - DERIVATIVE state2 { - h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) - } - - NRN_STATE SOLVE state1 METHOD cnexp{ - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) - } - SOLVE state2 METHOD cnexp{ - h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) - } - - )"; - - THEN("NrnState blok combining multiple solve nodes added") { - auto result = run_solve_block_visitor(nmodl_text); - REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); - } - } - } -} - -//============================================================================= -// Passes can run multiple times -//============================================================================= - -void run_visitor_passes(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - { - SymtabVisitor v1; - InlineVisitor v2; - LocalizeVisitor v3; - v1.visit_program(ast.get()); - v2.visit_program(ast.get()); - v3.visit_program(ast.get()); - v1.visit_program(ast.get()); - v1.visit_program(ast.get()); - v2.visit_program(ast.get()); - v3.visit_program(ast.get()); - v2.visit_program(ast.get()); - } -} - - -SCENARIO("Running visitor passes multiple time") { - GIVEN("A mod file") { - std::string nmodl_text = R"( - NEURON { - RANGE tau - } - - DERIVATIVE states { - tau = 11.1 - exp(tau) - } - )"; - - THEN("Passes can run multiple times") { - std::string input = reindent_text(nmodl_text); - REQUIRE_NOTHROW(run_visitor_passes(input)); - } - } -} - - -//============================================================================= -// Ast lookup visitor tests -//============================================================================= - -std::vector<std::shared_ptr<ast::Ast>> run_lookup_visitor(ast::Program* node, - std::vector<AstNodeType>& types) { - AstLookupVisitor v; - return v.lookup(node, types); -} - -SCENARIO("Searching for ast nodes using AstLookupVisitor") { - auto to_ast = [](const std::string& text) { - NmodlDriver driver; - return driver.parse_string(text); - }; - - GIVEN("A mod file with nodes of type NEURON, RANGE, BinaryExpression") { - std::string nmodl_text = R"( - NEURON { - RANGE tau, h - } - - DERIVATIVE states { - tau = 11.1 - exp(tau) - h' = h + 2 - } - - : My comment here - )"; - - auto ast = to_ast(nmodl_text); - - WHEN("Looking for existing nodes") { - THEN("Can find RANGE variables") { - std::vector<AstNodeType> types{AstNodeType::RANGE_VAR}; - auto result = run_lookup_visitor(ast.get(), types); - REQUIRE(result.size() == 2); - REQUIRE(to_nmodl(result[0].get()) == "tau"); - REQUIRE(to_nmodl(result[1].get()) == "h"); - } - - THEN("Can find NEURON block") { - AstLookupVisitor v(AstNodeType::NEURON_BLOCK); - ast->accept(&v); - auto nodes = v.get_nodes(); - REQUIRE(nodes.size() == 1); - - std::string neuron_block = R"( - NEURON { - RANGE tau, h - })"; - auto result = reindent_text(to_nmodl(nodes[0].get())); - auto expected = reindent_text(neuron_block); - REQUIRE(result == expected); - } - - THEN("Can find Binary Expressions and function call") { - std::vector<AstNodeType> types{AstNodeType::BINARY_EXPRESSION, - AstNodeType::FUNCTION_CALL}; - auto result = run_lookup_visitor(ast.get(), types); - REQUIRE(result.size() == 4); - } - } - - WHEN("Looking for missing nodes") { - THEN("Can not find BREAKPOINT block") { - std::vector<AstNodeType> types{AstNodeType::BREAKPOINT_BLOCK}; - auto result = run_lookup_visitor(ast.get(), types); - REQUIRE(result.size() == 0); - } - } - } -} - - -//============================================================================= -// KineticBlock visitor tests -//============================================================================= - -std::vector<std::string> run_kinetic_block_visitor( - const std::string& text, - bool pade = false, - bool cse = false, - AstNodeType ret_nodetype = AstNodeType::DERIVATIVE_BLOCK) { - std::vector<std::string> results; - - // construct AST from text including KINETIC block(s) - NmodlDriver driver; - auto ast = driver.parse_string(text); - - // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); - - // unroll loops and fold constants - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); - - // run KineticBlock visitor on AST - KineticBlockVisitor().visit_program(ast.get()); - - // run lookup visitor to extract DERIVATIVE block(s) from AST - AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(ast.get(), ret_nodetype); - for (const auto& r: res) { - results.push_back(to_nmodl(r.get())); - } - - return results; -} - -SCENARIO("KineticBlock visitor", "[kinetic]") { - GIVEN("KINETIC block with << reaction statement, 1 state var") { - std::string input_nmodl_text = R"( - STATE { - x - } - KINETIC states { - ~ x << (a*c/3.2) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = (a*c/3.2) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with << reaction statement, 1 array state var") { - std::string input_nmodl_text = R"( - STATE { - x[1] - } - KINETIC states { - ~ x[0] << (a*c/3.2) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x'[0] = (a*c/3.2) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with << reaction statement, 1 array state var, flux vars") { - std::string input_nmodl_text = R"( - STATE { - x[1] - } - KINETIC states { - ~ x[0] << (a*c/3.2) - f0 = f_flux*2 - f1 = b_flux + f_flux - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - f0 = 0*2 - f1 = 0+0 - x'[0] = (a*c/3.2) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with invalid << reaction statement with 2 state vars") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ x + y << (2*z) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - })"; - THEN("Emit warning & do not process statement") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with -> reaction statement, 1 state var, flux vars") { - std::string input_nmodl_text = R"( - STATE { - x - } - KINETIC states { - ~ x -> (a) - zf = f_flux - zb = b_flux - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - zf = a*x - zb = 0 - x' = (-1*(a*x)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with -> reaction statement, 2 state vars") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ x + y -> (f(v)) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = (-1*(f(v)*x*y)) - y' = (-1*(f(v)*x*y)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with -> reaction statement, 2 state vars, CONSERVE statement") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ x + y -> (f(v)) - CONSERVE x + y = 1 - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - CONSERVE y = 1-x - x' = (-1*(f(v)*x*y)) - y' = (-1*(f(v)*x*y)) - })"; - THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with -> reaction statement, 2 state vars, CONSERVE & COMPARTMENT") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - COMPARTMENT a { x } - COMPARTMENT b { y } - ~ x + y -> (f(v)) - CONSERVE x + y = 1 - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - CONSERVE y = (1-(a*1*x))/(b*1) - x' = ((-1*(f(v)*x*y)))/(a) - y' = ((-1*(f(v)*x*y)))/(b) - })"; - THEN( - "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement inc COMPARTMENT " - "factors") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with -> reaction statement, array of 2 state var") { - std::string input_nmodl_text = R"( - STATE { - x[2] - } - KINETIC states { - ~ x[0] + x[1] -> (f(v)) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x'[0] = (-1*(f(v)*x[0]*x[1])) - x'[1] = (-1*(f(v)*x[0]*x[1])) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var, flux vars") { - // Here c is NOT a state variable - // see 9.9.2.1 of NEURON book - // c should be treated as a constant, i.e. - // -the diff. eq. for x should include the contribution from c - // -no diff. eq. should be generated for c itself - std::string input_nmodl_text = R"( - STATE { - x - } - KINETIC states { - ~ x <-> c (r, r) - c1 = f_flux - b_flux - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - c1 = r*x-r*c - x' = (-1*(r*x-r*c)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with one reaction statement, 2 state vars") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ x <-> y (a, b) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with one reaction statement, 2 state vars, CONSERVE statement") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ x <-> y (a, b) - CONSERVE x + y = 0 - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - CONSERVE y = 0-x - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y)) - })"; - THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - // array vars in CONSERVE statements are implicit sums over elements - // see p34 of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7812&rep=rep1&type=pdf - GIVEN("KINETIC block with array state vars, CONSERVE statement") { - std::string input_nmodl_text = R"( - STATE { - x[3] y - } - KINETIC states { - ~ x[0] <-> x[1] (a, b) - ~ x[2] <-> y (c, d) - CONSERVE y + x = 1 - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - CONSERVE x[2] = 1-y-x[0]-x[1] - x'[0] = (-1*(a*x[0]-b*x[1])) - x'[1] = (1*(a*x[0]-b*x[1])) - x'[2] = (-1*(c*x[2]-d*y)) - y' = (1*(c*x[2]-d*y)) - })"; - THEN( - "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " - "array elements, with last state var on LHS") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - // array vars in CONSERVE statements are implicit sums over elements - // see p34 of http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.42.7812&rep=rep1&type=pdf - GIVEN("KINETIC block with array state vars, re-ordered CONSERVE statement") { - std::string input_nmodl_text = R"( - STATE { - x[3] y - } - KINETIC states { - ~ x[0] <-> x[1] (a, b) - ~ x[2] <-> y (c, d) - CONSERVE x + y = 1 - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - CONSERVE y = 1-x[0]-x[1]-x[2] - x'[0] = (-1*(a*x[0]-b*x[1])) - x'[1] = (1*(a*x[0]-b*x[1])) - x'[2] = (-1*(c*x[2]-d*y)) - y' = (1*(c*x[2]-d*y)) - })"; - THEN( - "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " - "array elements, with last state var on LHS") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with one reaction statement & 1 COMPARTMENT statement") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - COMPARTMENT c-d {x y} - ~ x <-> y (a, b) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = ((-1*(a*x-b*y)))/(c-d) - y' = ((1*(a*x-b*y)))/(c-d) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with two CONSERVE statements") { - std::string input_nmodl_text = R"( - STATE { - c1 o1 o2 p0 p1 - } - KINETIC ihkin { - evaluate_fct(v, cai) - ~ c1 <-> o1 (alpha, beta) - ~ p0 <-> p1 (k1ca, k2) - ~ o1 <-> o2 (k3p, k4) - CONSERVE p0+p1 = 1 - CONSERVE c1+o1+o2 = 1 - })"; - std::string output_nmodl_text = R"( - DERIVATIVE ihkin { - evaluate_fct(v, cai) - CONSERVE p1 = 1-p0 - CONSERVE o2 = 1-c1-o1 - c1' = (-1*(alpha*c1-beta*o1)) - o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) - o2' = (1*(k3p*o1-k4*o2)) - p0' = (-1*(k1ca*p0-k2*p1)) - p1' = (1*(k1ca*p0-k2*p1)) - })"; - THEN("Convert to equivalent DERIVATIVE block, re-order both CONSERVE statements") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with one reaction statement & 2 COMPARTMENT statements") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - COMPARTMENT cx {x} - COMPARTMENT cy {y} - ~ x <-> y (a, b) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = ((-1*(a*x-b*y)))/(cx) - y' = ((1*(a*x-b*y)))/(cy) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with two independent reaction statements") { - std::string input_nmodl_text = R"( - STATE { - w x y z - } - KINETIC states { - ~ x <-> y (a, b) - ~ w <-> z (c, d) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - w' = (-1*(c*w-d*z)) - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y)) - z' = (1*(c*w-d*z)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with two dependent reaction statements") { - std::string input_nmodl_text = R"( - STATE { - x y z - } - KINETIC states { - ~ x <-> y (a, b) - ~ y <-> z (c, d) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) - z' = (1*(c*y-d*z)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with two dependent reaction statements, flux vars") { - std::string input_nmodl_text = R"( - STATE { - x y z - } - KINETIC states { - c0 = f_flux - ~ x <-> y (a, b) - c1 = f_flux + b_flux - ~ y <-> z (c, d) - c2 = f_flux - 2*b_flux - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - c0 = 0 - c1 = a*x+b*y - c2 = c*y-2*d*z - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) - z' = (1*(c*y-d*z)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with a stoch coeff of 2") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ 2x <-> y (a, b) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = (-2*(a*x*x-b*y)) - y' = (1*(a*x*x-b*y)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with duplicate state vars") { - std::string input_nmodl_text = R"( - STATE { - x y - } - KINETIC states { - ~ x + x <-> y (a, b) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - x' = (-2*(a*x*x-b*y)) - y' = (1*(a*x*x-b*y)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with functions for reaction rates") { - // Example from sec 9.8, p238 of NEURON book - std::string input_nmodl_text = R"( - STATE { - mc m - } - KINETIC states { - ~ mc <-> m (a(v), b(v)) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - mc' = (-1*(a(v)*mc-b(v)*m)) - m' = (1*(a(v)*mc-b(v)*m)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with stoch coeff 2, coupled pair of statements") { - // Example from sec 9.8, p239 of NEURON book - std::string input_nmodl_text = R"( - STATE { - A B C D - } - KINETIC states { - ~ 2A + B <-> C (k1, k2) - ~ C + D <-> A + 2B (k3, k4) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - A' = (-2*(k1*A*A*B-k2*C))+(1*(k3*C*D-k4*A*B*B)) - B' = (-1*(k1*A*A*B-k2*C))+(2*(k3*C*D-k4*A*B*B)) - C' = (1*(k1*A*A*B-k2*C))+(-1*(k3*C*D-k4*A*B*B)) - D' = (-1*(k3*C*D-k4*A*B*B)) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } - GIVEN("KINETIC block with loop over array variable") { - std::string input_nmodl_text = R"( - DEFINE N 5 - ASSIGNED { - a - b[N] - c[N] - d - } - STATE { - x[N] - } - KINETIC kin { - ~ x[0] << (a) - FROM i=0 TO N-2 { - ~ x[i] <-> x[i+1] (b[i], c[i]) - } - ~ x[N-1] -> (d) - })"; - std::string output_nmodl_text = R"( - DERIVATIVE kin { - { - } - x'[0] = (a)+(-1*(b[0]*x[0]-c[0]*x[1])) - x'[1] = (1*(b[0]*x[0]-c[0]*x[1]))+(-1*(b[1]*x[1]-c[1]*x[2])) - x'[2] = (1*(b[1]*x[1]-c[1]*x[2]))+(-1*(b[2]*x[2]-c[2]*x[3])) - x'[3] = (1*(b[2]*x[2]-c[2]*x[3]))+(-1*(b[3]*x[3]-c[3]*x[4])) - x'[4] = (1*(b[3]*x[3]-c[3]*x[4]))+(-1*(d*x[4])) - })"; - THEN("Convert to equivalent DERIVATIVE block") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); - } - } -} - -//============================================================================= -// SympySolver visitor tests -//============================================================================= - -std::vector<std::string> run_sympy_solver_visitor( - const std::string& text, - bool pade = false, - bool cse = false, - AstNodeType ret_nodetype = AstNodeType::DIFF_EQ_EXPRESSION) { - std::vector<std::string> results; - - // construct AST from text - NmodlDriver driver; - auto ast = driver.parse_string(text); - - // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); - - // unroll loops and fold constants - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); - - // run SympySolver on AST - SympySolverVisitor(pade, cse).visit_program(ast.get()); - - // run lookup visitor to extract results from AST - AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(ast.get(), ret_nodetype); - for (const auto& r: res) { - results.push_back(to_nmodl(r.get())); - } - - return results; -} - -void run_sympy_visitor_passes(ast::Program* node) { - // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(node); - - // run SympySolver on AST several times - SympySolverVisitor v_sympy1; - v_sympy1.visit_program(node); - v_sympy1.visit_program(node); - - // also use a second instance of SympySolver - SympySolverVisitor v_sympy2; - v_sympy2.visit_program(node); - v_sympy1.visit_program(node); - v_sympy2.visit_program(node); -} - -std::string ast_to_string(ast::Program* node) { - std::stringstream stream; - { - NmodlPrintVisitor v(stream); - v.visit_program(node); - } - return stream.str(); -} - -SCENARIO("SympySolver visitor: cnexp or euler", "[sympy][cnexp][euler]") { - GIVEN("Derivative block without ODE, solver method cnexp") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - DERIVATIVE states { - m = m + h - } - )"; - THEN("No ODEs found - do nothing") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.empty()); - } - } - GIVEN("Derivative block with ODES, solver method is euler") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD euler - } - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - z = a*b + c - } - )"; - THEN("Construct forwards Euler solutions") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 2); - REQUIRE(result[0] == "m = (-dt*(m-mInf)+m*mTau)/mTau"); - REQUIRE(result[1] == "h = (-dt*(h-hInf)+h*hTau)/hTau"); - } - } - GIVEN("Derivative block with ODE, 1 state var in array, solver method euler") { - std::string nmodl_text = R"( - STATE { - m[1] - } - BREAKPOINT { - SOLVE states METHOD euler - } - DERIVATIVE states { - m'[0] = (mInf-m[0])/mTau - } - )"; - THEN("Construct forwards Euler solutions") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 1); - REQUIRE(result[0] == "m[0] = (dt*(mInf-m[0])+mTau*m[0])/mTau"); - } - } - GIVEN("Derivative block with ODE, 1 state var in array, solver method cnexp") { - std::string nmodl_text = R"( - STATE { - m[1] - } - BREAKPOINT { - SOLVE states METHOD cnexp - } - DERIVATIVE states { - m'[0] = (mInf-m[0])/mTau - } - )"; - THEN("Construct forwards Euler solutions") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 1); - REQUIRE(result[0] == "m[0] = mInf-(mInf-m[0])*exp(-dt/mTau)"); - } - } - GIVEN("Derivative block with linear ODES, solver method cnexp") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - DERIVATIVE states { - m' = (mInf-m)/mTau - z = a*b + c - h' = hInf/hTau - h/hTau - } - )"; - THEN("Integrate equations analytically") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 2); - REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); - REQUIRE(result[1] == "h = hInf-(-h+hInf)*exp(-dt/hTau)"); - } - } - GIVEN("Derivative block including non-linear but solvable ODES, solver method cnexp") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = c2 * h*h - } - )"; - THEN("Integrate equations analytically") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 2); - REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); - REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); - } - } - GIVEN("Derivative block including array of 2 state vars, solver method cnexp") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - STATE { - X[2] - } - DERIVATIVE states { - X'[0] = (mInf-X[0])/mTau - X'[1] = c2 * X[1]*X[1] - } - )"; - THEN("Integrate equations analytically") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 2); - REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau)"); - REQUIRE(result[1] == "X[1] = -X[1]/(c2*dt*X[1]-1)"); - } - } - GIVEN("Derivative block including loop over array vars, solver method cnexp") { - std::string nmodl_text = R"( - DEFINE N 3 - BREAKPOINT { - SOLVE states METHOD cnexp - } - ASSIGNED { - mTau[N] - } - STATE { - X[N] - } - DERIVATIVE states { - FROM i=0 TO N-1 { - X'[i] = (mInf-X[i])/mTau[i] - } - } - )"; - THEN("Integrate equations analytically") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 3); - REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau[0])"); - REQUIRE(result[1] == "X[1] = mInf-(mInf-X[1])*exp(-dt/mTau[1])"); - REQUIRE(result[2] == "X[2] = mInf-(mInf-X[2])*exp(-dt/mTau[2])"); - } - } - GIVEN("Derivative block including loop over array vars, solver method euler") { - std::string nmodl_text = R"( - DEFINE N 3 - BREAKPOINT { - SOLVE states METHOD euler - } - ASSIGNED { - mTau[N] - } - STATE { - X[N] - } - DERIVATIVE states { - FROM i=0 TO N-1 { - X'[i] = (mInf-X[i])/mTau[i] - } - } - )"; - THEN("Integrate equations analytically") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 3); - REQUIRE(result[0] == "X[0] = (dt*(mInf-X[0])+X[0]*mTau[0])/mTau[0]"); - REQUIRE(result[1] == "X[1] = (dt*(mInf-X[1])+X[1]*mTau[1])/mTau[1]"); - REQUIRE(result[2] == "X[2] = (dt*(mInf-X[2])+X[2]*mTau[2])/mTau[2]"); - } - } - GIVEN("Derivative block including ODES that can't currently be solved, solver method cnexp") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - DERIVATIVE states { - z' = a/z + b/z/z - h' = c2 * h*h - x' = a - y' = c3 * y*y*y - } - )"; - THEN("Integrate equations analytically where possible, otherwise leave untouched") { - auto result = run_sympy_solver_visitor(nmodl_text); - REQUIRE(result.size() == 4); - REQUIRE(result[0] == "z' = a/z+b/z/z"); - REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); - REQUIRE(result[2] == "x = a*dt+x"); - REQUIRE(result[3] == "y' = c3*y*y*y"); - } - } - GIVEN("Derivative block with cnexp solver method, AST after SympySolver pass") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states METHOD cnexp - } - DERIVATIVE states { - m' = (mInf-m)/mTau - } - )"; - // construct AST from text - NmodlDriver driver; - auto ast = driver.parse_string(nmodl_text); - - // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); - - // run SympySolver on AST - SympySolverVisitor().visit_program(ast.get()); - - std::string AST_string = ast_to_string(ast.get()); - - THEN("More SympySolver passes do nothing to the AST and don't throw") { - REQUIRE_NOTHROW(run_sympy_visitor_passes(ast.get())); - REQUIRE(AST_string == ast_to_string(ast.get())); - } - } -} - -SCENARIO("SympySolver visitor: derivimplicit or sparse", "[sympy][derivimplicit][sparse]") { - GIVEN("Derivative block with derivimplicit solver method and conditional block") { - std::string nmodl_text = R"( - STATE { - m - } - BREAKPOINT { - SOLVE states METHOD derivimplicit - } - DERIVATIVE states { - IF (mInf == 1) { - mInf = mInf+1 - } - m' = (mInf-m)/mTau - } - )"; - std::string expected_result = R"( - DERIVATIVE states { - EIGEN_NEWTON_SOLVE[1]{ - LOCAL old_m - }{ - IF (mInf == 1) { - mInf = mInf+1 - } - old_m = m - }{ - X[0] = m - }{ - F[0] = (-X[0]*dt+dt*mInf+mTau*(-X[0]+old_m))/mTau - J[0] = -(dt+mTau)/mTau - }{ - m = X[0] - }{ - } - })"; - THEN("SympySolver correctly inserts ode to block") { - CAPTURE(nmodl_text); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - - GIVEN("Derivative block of coupled & linear ODES, solver method sparse") { - std::string nmodl_text = R"( - STATE { - x y z - } - BREAKPOINT { - SOLVE states METHOD sparse - } - DERIVATIVE states { - LOCAL a, b, c, d, h - x' = a*z + b*h - y' = c + 2*x - z' = d*z - y - } - )"; - std::string expected_result = R"( - DERIVATIVE states { - LOCAL a, b, c, d, h, old_x, old_y, old_z - old_x = x - old_y = y - old_z = z - x = (-a*dt*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(b*dt*h+old_x)*(2*a*pow(dt, 3)-d*dt+1))/(2*a*pow(dt, 3)-d*dt+1) - y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+old_x)+old_y))/(2*a*pow(dt, 3)-d*dt+1) - z = (-dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)+old_z)/(2*a*pow(dt, 3)-d*dt+1) - })"; - std::string expected_cse_result = R"( - DERIVATIVE states { - LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 - old_x = x - old_y = y - old_z = z - tmp0 = 2*a - tmp1 = -d*dt+pow(dt, 3)*tmp0+1 - tmp2 = 1/tmp1 - tmp3 = b*dt*h+old_x - tmp4 = c*dt+2*dt*tmp3+old_y - tmp5 = dt*tmp4-old_z - x = -tmp2*(a*dt*tmp5-tmp1*tmp3) - y = -tmp2*(pow(dt, 2)*tmp0*tmp5-tmp1*tmp4) - z = -tmp2*tmp5 - })"; - - THEN("Construct & solve linear system for backwards Euler") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - auto result_cse = - run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - REQUIRE(result_cse[0] == reindent_text(expected_cse_result)); - } - } - GIVEN("Derivative block including ODES with sparse method (from nmodl paper)") { - std::string nmodl_text = R"( - STATE { - mc m - } - BREAKPOINT { - SOLVE scheme1 METHOD sparse - } - DERIVATIVE scheme1 { - mc' = -a*mc + b*m - m' = a*mc - b*m - } - )"; - std::string expected_result = R"( - DERIVATIVE scheme1 { - LOCAL old_mc, old_m - old_mc = mc - old_m = m - mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) - m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) - })"; - THEN("Construct & solve linear system") { - CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Derivative block with ODES with sparse method, CONSERVE statement of form m = ...") { - std::string nmodl_text = R"( - STATE { - mc m - } - BREAKPOINT { - SOLVE scheme1 METHOD sparse - } - DERIVATIVE scheme1 { - mc' = -a*mc + b*m - m' = a*mc - b*m - CONSERVE m = 1 - mc - } - )"; - std::string expected_result = R"( - DERIVATIVE scheme1 { - LOCAL old_mc - old_mc = mc - mc = (b*dt+old_mc)/(a*dt+b*dt+1) - m = (a*dt-old_mc+1)/(a*dt+b*dt+1) - })"; - THEN("Construct & solve linear system, replace ODE for m with rhs of CONSERVE statement") { - CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN( - "Derivative block with ODES with sparse method, invalid CONSERVE statement of form m + mc " - "= ...") { - std::string nmodl_text = R"( - STATE { - mc m - } - BREAKPOINT { - SOLVE scheme1 METHOD sparse - } - DERIVATIVE scheme1 { - mc' = -a*mc + b*m - m' = a*mc - b*m - CONSERVE m + mc = 1 - } - )"; - std::string expected_result = R"( - DERIVATIVE scheme1 { - LOCAL old_mc, old_m - old_mc = mc - old_m = m - mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) - m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) - })"; - THEN("Construct & solve linear system, ignore invalid CONSERVE statement") { - CAPTURE(nmodl_text); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Derivative block with ODES with sparse method, two CONSERVE statements") { - std::string nmodl_text = R"( - STATE { - c1 o1 o2 p0 p1 - } - BREAKPOINT { - SOLVE ihkin METHOD sparse - } - DERIVATIVE ihkin { - LOCAL alpha, beta, k3p, k4, k1ca, k2 - evaluate_fct(v, cai) - CONSERVE p1 = 1-p0 - CONSERVE o2 = 1-c1-o1 - c1' = (-1*(alpha*c1-beta*o1)) - o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) - o2' = (1*(k3p*o1-k4*o2)) - p0' = (-1*(k1ca*p0-k2*p1)) - p1' = (1*(k1ca*p0-k2*p1)) - })"; - std::string expected_result = R"( - DERIVATIVE ihkin { - EIGEN_LINEAR_SOLVE[5]{ - LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_p0 - }{ - evaluate_fct(v, cai) - old_c1 = c1 - old_o1 = o1 - old_p0 = p0 - }{ - X[0] = c1 - X[1] = o1 - X[2] = o2 - X[3] = p0 - X[4] = p1 - F[0] = -old_c1 - F[1] = -old_o1 - F[2] = -1 - F[3] = -old_p0 - F[4] = -1 - J[0] = -alpha*dt-1 - J[5] = beta*dt - J[10] = 0 - J[15] = 0 - J[20] = 0 - J[1] = alpha*dt - J[6] = -beta*dt-dt*k3p-1 - J[11] = dt*k4 - J[16] = 0 - J[21] = 0 - J[2] = -1 - J[7] = -1 - J[12] = -1 - J[17] = 0 - J[22] = 0 - J[3] = 0 - J[8] = 0 - J[13] = 0 - J[18] = -dt*k1ca-1 - J[23] = dt*k2 - J[4] = 0 - J[9] = 0 - J[14] = 0 - J[19] = -1 - J[24] = -1 - }{ - c1 = X[0] - o1 = X[1] - o2 = X[2] - p0 = X[3] - p1 = X[4] - }{ - } - })"; - THEN( - "Construct & solve linear system, replacing ODEs for p1 and o2 with CONSERVE statement " - "algebraic relations") { - CAPTURE(nmodl_text); - auto result = run_sympy_solver_visitor(nmodl_text, false, false, - AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Derivative block including ODES with sparse method - single var in array") { - std::string nmodl_text = R"( - STATE { - W[1] - } - ASSIGNED { - A[2] - B[1] - } - BREAKPOINT { - SOLVE scheme1 METHOD sparse - } - DERIVATIVE scheme1 { - W'[0] = -A[0]*W[0] + B[0]*W[0] + 3*A[1] - } - )"; - std::string expected_result = R"( - DERIVATIVE scheme1 { - LOCAL old_W_0 - old_W_0 = W[0] - W[0] = (3*dt*A[1]+old_W_0)/(dt*A[0]-dt*B[0]+1) - })"; - THEN("Construct & solver linear system") { - CAPTURE(nmodl_text); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Derivative block including ODES with sparse method - array vars") { - std::string nmodl_text = R"( - STATE { - M[2] - } - ASSIGNED { - A[2] - B[2] - } - BREAKPOINT { - SOLVE scheme1 METHOD sparse - } - DERIVATIVE scheme1 { - M'[0] = -A[0]*M[0] + B[0]*M[1] - M'[1] = A[1]*M[0] - B[1]*M[1] - } - )"; - std::string expected_result = R"( - DERIVATIVE scheme1 { - LOCAL old_M_0, old_M_1 - old_M_0 = M[0] - old_M_1 = M[1] - M[0] = (dt*old_M_0*B[1]+dt*old_M_1*B[0]+old_M_0)/(pow(dt, 2)*A[0]*B[1]-pow(dt, 2)*A[1]*B[0]+dt*A[0]+dt*B[1]+1) - M[1] = -(dt*old_M_0*A[1]+old_M_1*(dt*A[0]+1))/(pow(dt, 2)*A[1]*B[0]-(dt*A[0]+1)*(dt*B[1]+1)) - })"; - THEN("Construct & solver linear system") { - CAPTURE(nmodl_text); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Derivative block including ODES with derivimplicit method - single var in array") { - std::string nmodl_text = R"( - STATE { - W[1] - } - ASSIGNED { - A[2] - B[1] - } - BREAKPOINT { - SOLVE scheme1 METHOD derivimplicit - } - DERIVATIVE scheme1 { - W'[0] = -A[0]*W[0] + B[0]*W[0] + 3*A[1] - } - )"; - std::string expected_result = R"( - DERIVATIVE scheme1 { - EIGEN_NEWTON_SOLVE[1]{ - LOCAL old_W_0 - }{ - old_W_0 = W[0] - }{ - X[0] = W[0] - }{ - F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3*dt*A[1]+old_W_0 - J[0] = -dt*A[0]+dt*B[0]-1 - }{ - W[0] = X[0] - }{ - } - })"; - THEN("Construct newton solve block") { - CAPTURE(nmodl_text); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Derivative block including ODES with derivimplicit method") { - std::string nmodl_text = R"( - STATE { - m h n - } - BREAKPOINT { - SOLVE states METHOD derivimplicit - } - DERIVATIVE states { - rates(v) - m' = (minf-m)/mtau - 3*h - h' = (hinf-h)/htau + m*m - n' = (ninf-n)/ntau - } - )"; - /// new derivative block with EigenNewtonSolverBlock node - std::string expected_result = R"( - DERIVATIVE states { - EIGEN_NEWTON_SOLVE[3]{ - LOCAL old_m, old_h, old_n - }{ - rates(v) - old_m = m - old_h = h - old_n = n - }{ - X[0] = m - X[1] = h - X[2] = n - }{ - F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]-3*X[1]*dt+old_m))/mtau - F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau - F[2] = (-X[2]*dt+dt*ninf+ntau*(-X[2]+old_n))/ntau - J[0] = -(dt+mtau)/mtau - J[3] = -3*dt - J[6] = 0 - J[1] = 2*X[0]*dt - J[4] = -(dt+htau)/htau - J[7] = 0 - J[2] = 0 - J[5] = 0 - J[8] = -(dt+ntau)/ntau - }{ - m = X[0] - h = X[1] - n = X[2] - }{ - } - })"; - THEN("Construct newton solve block") { - CAPTURE(nmodl_text); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - } - } - GIVEN("Multiple derivative blocks each with derivimplicit method") { - std::string nmodl_text = R"( - STATE { - m h - } - BREAKPOINT { - SOLVE states1 METHOD derivimplicit - SOLVE states2 METHOD derivimplicit - } - - DERIVATIVE states1 { - m' = (minf-m)/mtau - h' = (hinf-h)/htau + m*m - } - - DERIVATIVE states2 { - h' = (hinf-h)/htau + m*m - m' = (minf-m)/mtau + h - } - )"; - /// EigenNewtonSolverBlock in each derivative block - std::string expected_result_0 = R"( - DERIVATIVE states1 { - EIGEN_NEWTON_SOLVE[2]{ - LOCAL old_m, old_h - }{ - old_m = m - old_h = h - }{ - X[0] = m - X[1] = h - }{ - F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]+old_m))/mtau - F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau - J[0] = -(dt+mtau)/mtau - J[2] = 0 - J[1] = 2*X[0]*dt - J[3] = -(dt+htau)/htau - }{ - m = X[0] - h = X[1] - }{ - } - })"; - std::string expected_result_1 = R"( - DERIVATIVE states2 { - EIGEN_NEWTON_SOLVE[2]{ - LOCAL old_h, old_m - }{ - old_h = h - old_m = m - }{ - X[0] = m - X[1] = h - }{ - F[0] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau - F[1] = (-X[0]*dt+dt*minf+mtau*(-X[0]+X[1]*dt+old_m))/mtau - J[0] = 2*X[0]*dt - J[2] = -(dt+htau)/htau - J[1] = -(dt+mtau)/mtau - J[3] = dt - }{ - m = X[0] - h = X[1] - }{ - } - })"; - THEN("Construct newton solve block") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - CAPTURE(nmodl_text); - REQUIRE(result[0] == reindent_text(expected_result_0)); - REQUIRE(result[1] == reindent_text(expected_result_1)); - } - } -} - - -//============================================================================= -// SympyConductance visitor tests -//============================================================================= - -std::string run_sympy_conductance_visitor(const std::string& text) { - // construct AST from text - NmodlDriver driver; - auto ast = driver.parse_string(text); - - // construct symbol table from AST - SymtabVisitor(false).visit_program(ast.get()); - - // run constant folding, inlining & local renaming first - ConstantFolderVisitor().visit_program(ast.get()); - InlineVisitor().visit_program(ast.get()); - LocalVarRenameVisitor().visit_program(ast.get()); - SymtabVisitor(true).visit_program(ast.get()); - - // run SympyConductance on AST - SympyConductanceVisitor().visit_program(ast.get()); - - // run lookup visitor to extract results from AST - AstLookupVisitor v_lookup; - // return BREAKPOINT block as JSON string - return reindent_text( - nmodl::to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); -} - -std::string breakpoint_to_nmodl(const std::string& text) { - // construct AST from text - NmodlDriver driver; - auto ast = driver.parse_string(text); - - // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(ast.get()); - - // run lookup visitor to extract results from AST - AstLookupVisitor v_lookup; - // return BREAKPOINT block as JSON string - return reindent_text( - nmodl::to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); -} - -void run_sympy_conductance_passes(ast::Program* node) { - // construct symbol table from AST - SymtabVisitor v_symtab; - v_symtab.visit_program(node); - - // run SympySolver on AST several times - SympyConductanceVisitor v_sympy1; - v_sympy1.visit_program(node); - v_symtab.visit_program(node); - v_sympy1.visit_program(node); - v_symtab.visit_program(node); - - // also use a second instance of SympySolver - SympyConductanceVisitor v_sympy2; - v_sympy2.visit_program(node); - v_symtab.visit_program(node); - v_sympy1.visit_program(node); - v_symtab.visit_program(node); - v_sympy2.visit_program(node); - v_symtab.visit_program(node); -} - -SCENARIO("SympyConductance visitor", "[sympy]") { - // First set of test mod files below all based on: - // nmodldb/models/db/bluebrain/CortexSimplified/mod/Ca.mod - GIVEN("breakpoint block containing VERBATIM statement") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - RANGE gCabar, gCa, ica - } - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - VERBATIM - double z=0; - ENDVERBATIM - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - VERBATIM - double z=0; - ENDVERBATIM - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } - )"; - THEN("Do nothing") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("breakpoint block containing IF/ELSE block") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - RANGE gCabar, gCa, ica - } - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - IF(gCabar<1){ - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } ELSE { - gCa = 0 - ica = 0 - } - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - IF(gCabar<1){ - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } ELSE { - gCa = 0 - ica = 0 - } - } - )"; - THEN("Do nothing") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("ion current, existing CONDUCTANCE hint & var") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - RANGE gCabar, gCa, ica - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - gCa (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } - )"; - THEN("Do nothing") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("ion current, no CONDUCTANCE hint, existing var") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - RANGE gCabar, gCa, ica - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - gCa (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - } - )"; - THEN("Add CONDUCTANCE hint using existing var") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("ion current, no CONDUCTANCE hint, no existing var") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - RANGE gCabar, ica - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - SOLVE states METHOD cnexp - ica = (gCabar*m*m*h)*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL g_ca_0 - CONDUCTANCE g_ca_0 USEION ca - g_ca_0 = gCabar*h*pow(m, 2) - SOLVE states METHOD cnexp - ica = (gCabar*m*m*h)*(v-eca) - } - )"; - THEN("Add CONDUCTANCE hint with new local var") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("2 ion currents, 1 CONDUCTANCE hint, 1 existing var") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - USEION na READ ena WRITE ina - RANGE gCabar, gNabar, ica, ina - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - gNabar = 0.00005 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - ina (mA/cm2) - gCa (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - ina = (gNabar*m*h)*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL g_na_0 - CONDUCTANCE g_na_0 USEION na - g_na_0 = gNabar*h*m - CONDUCTANCE gCa USEION ca - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - ina = (gNabar*m*h)*(v-eca) - } - )"; - THEN("Add 1 CONDUCTANCE hint with new local var") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("2 ion currents, no CONDUCTANCE hints, 1 existing var") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - USEION na READ ena WRITE ina - RANGE gCabar, gNabar, ica, ina - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - gNabar = 0.00005 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - ina (mA/cm2) - gCa (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - ina = (gNabar*m*h)*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL g_na_0 - CONDUCTANCE g_na_0 USEION na - CONDUCTANCE gCa USEION ca - g_na_0 = gNabar*h*m - SOLVE states METHOD cnexp - gCa = gCabar*m*m*h - ica = gCa*(v-eca) - ina = (gNabar*m*h)*(v-eca) - } - )"; - THEN("Add 2 CONDUCTANCE hints, 1 with existing var, 1 with new local var") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("2 ion currents, no CONDUCTANCE hints, no existing vars") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - USEION na READ ena WRITE ina - RANGE gCabar, gNabar, ica, ina - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - gNabar = 0.00005 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - ina (mA/cm2) - gCa (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - SOLVE states METHOD cnexp - ica = (gCabar*m*m*h)*(v-eca) - ina = (gNabar*m*h)*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL g_ca_0, g_na_0 - CONDUCTANCE g_na_0 USEION na - CONDUCTANCE g_ca_0 USEION ca - g_ca_0 = gCabar*h*pow(m, 2) - g_na_0 = gNabar*h*m - SOLVE states METHOD cnexp - ica = (gCabar*m*m*h)*(v-eca) - ina = (gNabar*m*h)*(v-eca) - } - )"; - THEN("Add 2 CONDUCTANCE hints with 2 new local vars") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("1 ion current, 1 nonspecific current, no CONDUCTANCE hints, no existing vars") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - NONSPECIFIC_CURRENT ihcn - RANGE gCabar, ica - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - ihcn (mA/cm2) - gCa (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - SOLVE states METHOD cnexp - ica = (gCabar*m*m*h)*(v-eca) - ihcn = (0.1235*m*h)*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL g_ca_0, g__0 - CONDUCTANCE g__0 - CONDUCTANCE g_ca_0 USEION ca - g_ca_0 = gCabar*h*pow(m, 2) - g__0 = 0.1235*h*m - SOLVE states METHOD cnexp - ica = (gCabar*m*m*h)*(v-eca) - ihcn = (0.1235*m*h)*(v-eca) - } - )"; - THEN("Add 2 CONDUCTANCE hints with 2 new local vars") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - GIVEN("1 ion current, 1 nonspecific current, no CONDUCTANCE hints, 1 existing var") { - std::string nmodl_text = R"( - NEURON { - SUFFIX Ca - USEION ca READ eca WRITE ica - NONSPECIFIC_CURRENT ihcn - RANGE gCabar, ica, gihcn - } - - UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) - } - - PARAMETER { - gCabar = 0.00001 (S/cm2) - } - - ASSIGNED { - v (mV) - eca (mV) - ica (mA/cm2) - ihcn (mA/cm2) - gCa (S/cm2) - gihcn (S/cm2) - mInf - mTau - mAlpha - mBeta - hInf - hTau - hAlpha - hBeta - } - - STATE { - m - h - } - - BREAKPOINT { - SOLVE states METHOD cnexp - gihcn = 0.1235*m*h - ica = (gCabar*m*m*h)*(v-eca) - ihcn = gihcn*(v-eca) - } - - DERIVATIVE states { - m' = (mInf-m)/mTau - h' = (hInf-h)/hTau - } - - INITIAL{ - m = mInf - h = hInf - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL g_ca_0 - CONDUCTANCE gihcn - CONDUCTANCE g_ca_0 USEION ca - g_ca_0 = gCabar*h*pow(m, 2) - SOLVE states METHOD cnexp - gihcn = 0.1235*m*h - ica = (gCabar*m*m*h)*(v-eca) - ihcn = gihcn*(v-eca) - } - )"; - THEN("Add 2 CONDUCTANCE hints, 1 using existing var, 1 with new local var") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - // based on bluebrain/CortextPlastic/mod/ProbAMPANMDA.mod - GIVEN( - "2 ion currents, 1 nonspecific current, no CONDUCTANCE hints, indirect relation between " - "eqns") { - std::string nmodl_text = R"( - NEURON { - THREADSAFE - POINT_PROCESS ProbAMPANMDA - RANGE tau_r_AMPA, tau_d_AMPA, tau_r_NMDA, tau_d_NMDA - RANGE Use, u, Dep, Fac, u0, mg, NMDA_ratio - RANGE i, i_AMPA, i_NMDA, g_AMPA, g_NMDA, g, e - NONSPECIFIC_CURRENT i, i_AMPA,i_NMDA - POINTER rng - RANGE synapseID, verboseLevel - } - PARAMETER { - tau_r_AMPA = 0.2 (ms) : dual-exponential conductance profile - tau_d_AMPA = 1.7 (ms) : IMPORTANT: tau_r < tau_d - tau_r_NMDA = 0.29 (ms) : dual-exponential conductance profile - tau_d_NMDA = 43 (ms) : IMPORTANT: tau_r < tau_d - Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) - Dep = 100 (ms) : relaxation time constant from depression - Fac = 10 (ms) : relaxation time constant from facilitation - e = 0 (mV) : AMPA and NMDA reversal potential - mg = 1 (mM) : initial concentration of mg2+ - mggate - gmax = .001 (uS) : weight conversion factor (from nS to uS) - u0 = 0 :initial value of u, which is the running value of Use - NMDA_ratio = 0.71 (1) : The ratio of NMDA to AMPA - synapseID = 0 - verboseLevel = 0 - } - ASSIGNED { - v (mV) - i (nA) - i_AMPA (nA) - i_NMDA (nA) - g_AMPA (uS) - g_NMDA (uS) - g (uS) - factor_AMPA - factor_NMDA - rng - } - STATE { - A_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_r_AMPA - B_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_d_AMPA - A_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_r_NMDA - B_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_d_NMDA - } - BREAKPOINT { - SOLVE state METHOD cnexp - mggate = 1.2 - g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA - g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics - g = g_AMPA + g_NMDA - i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal - i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal - i = i_AMPA + i_NMDA - } - DERIVATIVE state{ - A_AMPA' = -A_AMPA/tau_r_AMPA - B_AMPA' = -B_AMPA/tau_d_AMPA - A_NMDA' = -A_NMDA/tau_r_NMDA - B_NMDA' = -B_NMDA/tau_d_NMDA - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - CONDUCTANCE g - CONDUCTANCE g_NMDA - CONDUCTANCE g_AMPA - SOLVE state METHOD cnexp - mggate = 1.2 - g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA - g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics - g = g_AMPA + g_NMDA - i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal - i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal - i = i_AMPA + i_NMDA - } - )"; - THEN("Add 3 CONDUCTANCE hints, using existing vars") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } - // based on neurodamus/bbp/lib/modlib/GluSynapse.mod - GIVEN("1 nonspecific current, no CONDUCTANCE hints, many eqs & a function involved") { - std::string nmodl_text = R"( - NEURON { - GLOBAL tau_r_AMPA, E_AMPA - RANGE tau_d_AMPA, gmax_AMPA - RANGE g_AMPA - GLOBAL tau_r_NMDA, tau_d_NMDA, E_NMDA - RANGE g_NMDA - RANGE Use, Dep, Fac, Nrrp, u - RANGE tsyn, unoccupied, occupied - RANGE ica_NMDA - RANGE volume_CR - GLOBAL ljp_VDCC, vhm_VDCC, km_VDCC, mtau_VDCC, vhh_VDCC, kh_VDCC, htau_VDCC - RANGE gca_bar_VDCC, ica_VDCC - GLOBAL gamma_ca_CR, tau_ca_CR, min_ca_CR, cao_CR - GLOBAL tau_GB, gamma_d_GB, gamma_p_GB, rho_star_GB, tau_Use_GB, tau_effca_GB - RANGE theta_d_GB, theta_p_GB - RANGE rho0_GB - RANGE enable_GB, depress_GB, potentiate_GB - RANGE Use_d_GB, Use_p_GB - GLOBAL p_gen_RW, p_elim0_RW, p_elim1_RW - RANGE enable_RW, synstate_RW - GLOBAL mg, scale_mg, slope_mg - RANGE vsyn, NMDA_ratio, synapseID, selected_for_report, verbose - NONSPECIFIC_CURRENT i - } - UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (uS) = (microsiemens) - (nS) = (nanosiemens) - (pS) = (picosiemens) - (umho) = (micromho) - (um) = (micrometers) - (mM) = (milli/liter) - (uM) = (micro/liter) - FARADAY = (faraday) (coulomb) - PI = (pi) (1) - R = (k-mole) (joule/degC) - } - ASSIGNED { - g_AMPA (uS) - g_NMDA (uS) - ica_NMDA (nA) - ica_VDCC (nA) - depress_GB (1) - potentiate_GB (1) - v (mV) - vsyn (mV) - i (nA) - } - FUNCTION nernst(ci(mM), co(mM), z) (mV) { - nernst = (1000) * R * (celsius + 273.15) / (z*FARADAY) * log(co/ci) - } - BREAKPOINT { - LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC - g_AMPA = (1e-3)*gmax_AMPA*(B_AMPA-A_AMPA) - i_AMPA = g_AMPA*(v-E_AMPA) - gmax_NMDA = gmax_AMPA*NMDA_ratio - mggate = 1 / (1 + exp(slope_mg * -(v)) * (mg / scale_mg)) - g_NMDA = (1e-3)*gmax_NMDA*mggate*(B_NMDA-A_NMDA) - i_NMDA = g_NMDA*(v-E_NMDA) - Pf_NMDA = (4*cao_CR) / (4*cao_CR + (1/1.38) * 120 (mM)) * 0.6 - ica_NMDA = Pf_NMDA*g_NMDA*(v-40.0) - gca_bar_abs_VDCC = gca_bar_VDCC * 4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^(2/3) - gca_VDCC = (1e-3) * gca_bar_abs_VDCC * m_VDCC * m_VDCC * h_VDCC - Eca_syn = FARADAY*nernst(cai_CR, cao_CR, 2) - ica_VDCC = gca_VDCC*(v-Eca_syn) - vsyn = v - i = i_AMPA + i_NMDA + ica_VDCC - } - )"; - std::string breakpoint_text = R"( - BREAKPOINT { - LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC, nernst_in_0, g__0 - CONDUCTANCE g__0 - g__0 = (0.001*gmax_NMDA*mg*scale_mg*slope_mg*(A_NMDA-B_NMDA)*(E_NMDA-v)*exp(slope_mg*v)-0.001*gmax_NMDA*scale_mg*(A_NMDA-B_NMDA)*(mg+scale_mg*exp(slope_mg*v))*exp(slope_mg*v)+(g_AMPA+gca_VDCC)*pow(mg+scale_mg*exp(slope_mg*v), 2))/pow(mg+scale_mg*exp(slope_mg*v), 2) - g_AMPA = 0.001*gmax_AMPA*(B_AMPA-A_AMPA) - i_AMPA = g_AMPA*(v-E_AMPA) - gmax_NMDA = gmax_AMPA*NMDA_ratio - mggate = 1/(1+exp(slope_mg*-v)*(mg/scale_mg)) - g_NMDA = 0.001*gmax_NMDA*mggate*(B_NMDA-A_NMDA) - i_NMDA = g_NMDA*(v-E_NMDA) - Pf_NMDA = (4*cao_CR)/(4*cao_CR+0.7246376811594204*120(mM))*0.6 - ica_NMDA = Pf_NMDA*g_NMDA*(v-40) - gca_bar_abs_VDCC = gca_bar_VDCC*4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^0.6666666666666666 - gca_VDCC = 0.001*gca_bar_abs_VDCC*m_VDCC*m_VDCC*h_VDCC - { - LOCAL ci_in_0, co_in_0, z_in_0 - ci_in_0 = cai_CR - co_in_0 = cao_CR - z_in_0 = 2 - nernst_in_0 = 1000*R*(celsius+273.15)/(z_in_0*FARADAY)*log(co_in_0/ci_in_0) - } - Eca_syn = FARADAY*nernst_in_0 - ica_VDCC = gca_VDCC*(v-Eca_syn) - vsyn = v - i = i_AMPA+i_NMDA+ica_VDCC - } - )"; - THEN("Add 1 CONDUCTANCE hint using new var") { - auto result = run_sympy_conductance_visitor(nmodl_text); - REQUIRE(result == breakpoint_to_nmodl(breakpoint_text)); - } - } -} - - -//============================================================================= -// to_nmodl with excluding set of node types -//============================================================================= - -SCENARIO("Sympy specific AST to NMODL conversion") { - GIVEN("NMODL block with unit usage") { - std::string nmodl = R"( - BREAKPOINT { - Pf_NMDA = (1/1.38) * 120 (mM) * 0.6 - VDCC = gca_bar_VDCC * 4(um2)*PI*3(1/um3) - gca_bar_VDCC = 0.0372 (nS/um2) - } - )"; - - std::string expected = R"( - BREAKPOINT { - Pf_NMDA = (1/1.38)*120*0.6 - VDCC = gca_bar_VDCC*4*PI*3 - gca_bar_VDCC = 0.0372 - } - )"; - - THEN("to_nmodl can ignore all units") { - auto input = reindent_text(nmodl); - NmodlDriver driver; - auto ast = driver.parse_string(input); - auto result = to_nmodl(ast.get(), {AstNodeType::UNIT}); - REQUIRE(result == reindent_text(expected)); - } - } -} - - -//============================================================================= -// Constant folding tests -//============================================================================= - -std::string run_constant_folding_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - SymtabVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - - std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); - return stream.str(); -} - -TEST_CASE("Constant Folding Visitor") { - SECTION("Perform Successful Constant Folding") { - GIVEN("Simple integer expression") { - std::string nmodl_text = R"( - PROCEDURE dummy() { - a = 1 + 2 - } - )"; - std::string expected_text = R"( - PROCEDURE dummy() { - a = 3 - } - )"; - THEN("successfully folds") { - auto result = run_constant_folding_visitor(nmodl_text); - REQUIRE(reindent_text(result) == reindent_text(expected_text)); - } - } - - GIVEN("Simple double expression") { - std::string nmodl_text = R"( - PROCEDURE dummy() { - a = 1.1 + 2e-10 - } - )"; - std::string expected_text = R"( - PROCEDURE dummy() { - a = 1.1000000002 - } - )"; - THEN("successfully folds") { - auto result = run_constant_folding_visitor(nmodl_text); - REQUIRE(reindent_text(result) == reindent_text(expected_text)); - } - } - - GIVEN("Complex expression") { - std::string nmodl_text = R"( - PROCEDURE dummy() { - a = 1 + (2) + (2 / 2) + (((1+((2))))) - } - )"; - std::string expected_text = R"( - PROCEDURE dummy() { - a = 7 - } - )"; - THEN("successfully folds") { - auto result = run_constant_folding_visitor(nmodl_text); - REQUIRE(reindent_text(result) == reindent_text(expected_text)); - } - } - - GIVEN("Integer expression with define statement") { - std::string nmodl_text = R"( - DEFINE N 10 - - PROCEDURE dummy() { - a = N + (2*N) + (N / 2) + (((1+((N))))) - FROM i = 0 TO N-2 { - } - } - )"; - std::string expected_text = R"( - DEFINE N 10 - - PROCEDURE dummy() { - a = 46 - FROM i = 0 TO 8 { - } - } - )"; - THEN("successfully folds") { - auto result = run_constant_folding_visitor(nmodl_text); - REQUIRE(reindent_text(result) == reindent_text(expected_text)); - } - } - - GIVEN("Only fold part of the statement") { - std::string nmodl_text = R"( - DEFINE N 10 - - PROCEDURE dummy() { - a = N + 2.0 + b - c = a + d - d = 2^3 - e = 2 || 3 - } - )"; - std::string expected_text = R"( - DEFINE N 10 - - PROCEDURE dummy() { - a = 12+b - c = a+d - d = 2^3 - e = 2 || 3 - } - )"; - THEN("successfully folds and keep other statements untouched") { - auto result = run_constant_folding_visitor(nmodl_text); - REQUIRE(reindent_text(result) == reindent_text(expected_text)); - } - } - - GIVEN("Don't remove parentheses if not simplifying") { - std::string nmodl_text = R"( - DEFINE N 10 - - PROCEDURE dummy() { - a = ((N+1)+5)*(c+1+N)/(b - 2) - } - )"; - std::string expected_text = R"( - DEFINE N 10 - - PROCEDURE dummy() { - a = 16*(c+1+10)/(b-2) - } - )"; - THEN("successfully folds and keep other statements untouched") { - auto result = run_constant_folding_visitor(nmodl_text); - REQUIRE(reindent_text(result) == reindent_text(expected_text)); - } - } - } -} - -//============================================================================= -// LINEAR solve block tests -//============================================================================= - -SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { - GIVEN("1 state-var numeric LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x - } - LINEAR lin { - ~ x = 5 - })"; - std::string expected_text = R"( - LINEAR lin { - x = 5 - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("1 state-var symbolic LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x - } - LINEAR lin { - ~ 2*a*x = 1 - })"; - std::string expected_text = R"( - LINEAR lin { - x = 0.5/a - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("2 state-var LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x y - } - LINEAR lin { - ~ x + 4*y = 5*a - ~ x - y = 0 - })"; - std::string expected_text = R"( - LINEAR lin { - x = a - y = a - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("2 state-var LINEAR solve block, post-solve statements") { - std::string nmodl_text = R"( - STATE { - x y - } - LINEAR lin { - ~ x + 4*y = 5*a - ~ x - y = 0 - x = x + 2 - y = y - a - })"; - std::string expected_text = R"( - LINEAR lin { - x = a - y = a - x = x+2 - y = y-a - })"; - THEN("solve analytically, insert in correct location") { - CAPTURE(reindent_text(nmodl_text)); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("2 state-var LINEAR solve block, mixed & post-solve statements") { - std::string nmodl_text = R"( - STATE { - x y - } - LINEAR lin { - ~ x + 4*y = 5*a - a2 = 3*b - ~ x - y = 0 - y = y - a - })"; - std::string expected_text = R"( - LINEAR lin { - a2 = 3*b - x = a - y = a - y = y-a - })"; - THEN("solve analytically, insert in correct location") { - CAPTURE(reindent_text(nmodl_text)); - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("3 state-var LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x y z - } - LINEAR lin { - ~ x + 4*c*y = -5.343*a - ~ a + x/b + z - y = 0.842*b*b - ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c - })"; - std::string expected_text = R"( - LINEAR lin { - x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)+(1*b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(1*b+4*c)-4*c*(5.343*a+b*(-1*a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - y = (1*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-1*pow(a, 2)*pow(b, 2)*(1*b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - z = pow(a, 2)*b*(c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-(1*b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("array state-var numeric LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - s[3] - } - LINEAR lin { - ~ s[0] = 1 - ~ s[1] = 3 - ~ s[2] + s[1] = s[0] - })"; - std::string expected_text = R"( - LINEAR lin { - s[0] = 1 - s[1] = 3 - s[2] = -2 - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("4 state-var LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - w x y z - } - LINEAR lin { - ~ w + z/3.2 = -2.0*y - ~ x + 4*c*y = -5.343*a - ~ a + x/b + z - y = 0.842*b*b - ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c - })"; - std::string expected_text = R"( - LINEAR lin { - EIGEN_LINEAR_SOLVE[4]{ - }{ - }{ - X[0] = w - X[1] = x - X[2] = y - X[3] = z - F[0] = 0 - F[1] = 5.343*a - F[2] = a-0.842*pow(b, 2) - F[3] = -1.43543/c - J[0] = -1 - J[4] = 0 - J[8] = -2 - J[12] = -0.3125 - J[1] = 0 - J[5] = -1 - J[9] = -4*c - J[13] = 0 - J[2] = 0 - J[6] = -1/b - J[10] = 1 - J[14] = -1 - J[3] = 0 - J[7] = -1 - J[11] = -1.3 - J[15] = 0.1/(pow(a, 2)*b) - }{ - w = X[0] - x = X[1] - y = X[2] - z = X[3] - }{ - } - })"; - THEN("return matrix system to solve") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("12 state-var LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - C1 C2 C3 C4 C5 I1 I2 I3 I4 I5 I6 O - } - LINEAR seqinitial { - ~ I1*bi1 + C2*b01 - C1*( fi1+f01) = 0 - ~ C1*f01 + I2*bi2 + C3*b02 - C2*(b01+fi2+f02) = 0 - ~ C2*f02 + I3*bi3 + C4*b03 - C3*(b02+fi3+f03) = 0 - ~ C3*f03 + I4*bi4 + C5*b04 - C4*(b03+fi4+f04) = 0 - ~ C4*f04 + I5*bi5 + O*b0O - C5*(b04+fi5+f0O) = 0 - ~ C5*f0O + I6*bin - O*(b0O+fin) = 0 - ~ C1*fi1 + I2*b11 - I1*( bi1+f11) = 0 - ~ I1*f11 + C2*fi2 + I3*b12 - I2*(b11+bi2+f12) = 0 - ~ I2*f12 + C3*fi3 + I4*bi3 - I3*(b12+bi3+f13) = 0 - ~ I3*f13 + C4*fi4 + I5*b14 - I4*(b13+bi4+f14) = 0 - ~ I4*f14 + C5*fi5 + I6*b1n - I5*(b14+bi5+f1n) = 0 - ~ C1 + C2 + C3 + C4 + C5 + O + I1 + I2 + I3 + I4 + I5 + I6 = 1 - })"; - std::string expected_text = R"( - LINEAR seqinitial { - EIGEN_LINEAR_SOLVE[12]{ - }{ - }{ - X[0] = C1 - X[1] = C2 - X[2] = C3 - X[3] = C4 - X[4] = C5 - X[5] = I1 - X[6] = I2 - X[7] = I3 - X[8] = I4 - X[9] = I5 - X[10] = I6 - X[11] = O - F[0] = 0 - F[1] = 0 - F[2] = 0 - F[3] = 0 - F[4] = 0 - F[5] = 0 - F[6] = 0 - F[7] = 0 - F[8] = 0 - F[9] = 0 - F[10] = 0 - F[11] = -1 - J[0] = f01+fi1 - J[12] = -b01 - J[24] = 0 - J[36] = 0 - J[48] = 0 - J[60] = -bi1 - J[72] = 0 - J[84] = 0 - J[96] = 0 - J[108] = 0 - J[120] = 0 - J[132] = 0 - J[1] = -f01 - J[13] = b01+f02+fi2 - J[25] = -b02 - J[37] = 0 - J[49] = 0 - J[61] = 0 - J[73] = -bi2 - J[85] = 0 - J[97] = 0 - J[109] = 0 - J[121] = 0 - J[133] = 0 - J[2] = 0 - J[14] = -f02 - J[26] = b02+f03+fi3 - J[38] = -b03 - J[50] = 0 - J[62] = 0 - J[74] = 0 - J[86] = -bi3 - J[98] = 0 - J[110] = 0 - J[122] = 0 - J[134] = 0 - J[3] = 0 - J[15] = 0 - J[27] = -f03 - J[39] = b03+f04+fi4 - J[51] = -b04 - J[63] = 0 - J[75] = 0 - J[87] = 0 - J[99] = -bi4 - J[111] = 0 - J[123] = 0 - J[135] = 0 - J[4] = 0 - J[16] = 0 - J[28] = 0 - J[40] = -f04 - J[52] = b04+f0O+fi5 - J[64] = 0 - J[76] = 0 - J[88] = 0 - J[100] = 0 - J[112] = -bi5 - J[124] = 0 - J[136] = -b0O - J[5] = 0 - J[17] = 0 - J[29] = 0 - J[41] = 0 - J[53] = -f0O - J[65] = 0 - J[77] = 0 - J[89] = 0 - J[101] = 0 - J[113] = 0 - J[125] = -bin - J[137] = b0O+fin - J[6] = -fi1 - J[18] = 0 - J[30] = 0 - J[42] = 0 - J[54] = 0 - J[66] = bi1+f11 - J[78] = -b11 - J[90] = 0 - J[102] = 0 - J[114] = 0 - J[126] = 0 - J[138] = 0 - J[7] = 0 - J[19] = -fi2 - J[31] = 0 - J[43] = 0 - J[55] = 0 - J[67] = -f11 - J[79] = b11+bi2+f12 - J[91] = -b12 - J[103] = 0 - J[115] = 0 - J[127] = 0 - J[139] = 0 - J[8] = 0 - J[20] = 0 - J[32] = -fi3 - J[44] = 0 - J[56] = 0 - J[68] = 0 - J[80] = -f12 - J[92] = b12+bi3+f13 - J[104] = -bi3 - J[116] = 0 - J[128] = 0 - J[140] = 0 - J[9] = 0 - J[21] = 0 - J[33] = 0 - J[45] = -fi4 - J[57] = 0 - J[69] = 0 - J[81] = 0 - J[93] = -f13 - J[105] = b13+bi4+f14 - J[117] = -b14 - J[129] = 0 - J[141] = 0 - J[10] = 0 - J[22] = 0 - J[34] = 0 - J[46] = 0 - J[58] = -fi5 - J[70] = 0 - J[82] = 0 - J[94] = 0 - J[106] = -f14 - J[118] = b14+bi5+f1n - J[130] = -b1n - J[142] = 0 - J[11] = -1 - J[23] = -1 - J[35] = -1 - J[47] = -1 - J[59] = -1 - J[71] = -1 - J[83] = -1 - J[95] = -1 - J[107] = -1 - J[119] = -1 - J[131] = -1 - J[143] = -1 - }{ - C1 = X[0] - C2 = X[1] - C3 = X[2] - C4 = X[3] - C5 = X[4] - I1 = X[5] - I2 = X[6] - I3 = X[7] - I4 = X[8] - I5 = X[9] - I6 = X[10] - O = X[11] - }{ - } - })"; - THEN("return matrix system to be solved") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } -} - -//============================================================================= -// NONLINEAR solve block tests -//============================================================================= - -SCENARIO("NONLINEAR solve block (SympySolver Visitor)", "[sympy][nonlinear]") { - GIVEN("1 state-var numeric NONLINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x - } - NONLINEAR nonlin { - ~ x = 5 - })"; - std::string expected_text = R"( - NONLINEAR nonlin { - EIGEN_NEWTON_SOLVE[1]{ - }{ - }{ - X[0] = x - }{ - F[0] = -X[0]+5 - J[0] = -1 - }{ - x = X[0] - }{ - } - })"; - THEN("return F & J for newton solver") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } - GIVEN("array state-var numeric NONLINEAR solve block") { - std::string nmodl_text = R"( - STATE { - s[3] - } - NONLINEAR nonlin { - ~ s[0] = 1 - ~ s[1] = 3 - ~ s[2] + s[1] = s[0] - })"; - std::string expected_text = R"( - NONLINEAR nonlin { - EIGEN_NEWTON_SOLVE[3]{ - }{ - }{ - X[0] = s[0] - X[1] = s[1] - X[2] = s[2] - }{ - F[0] = -X[0]+1 - F[1] = -X[1]+3 - F[2] = X[0]-X[1]-X[2] - J[0] = -1 - J[3] = 0 - J[6] = 0 - J[1] = 0 - J[4] = -1 - J[7] = 0 - J[2] = 1 - J[5] = -1 - J[8] = -1 - }{ - s[0] = X[0] - s[1] = X[1] - s[2] = X[2] - }{ - } - })"; - THEN("return F & J for newton solver") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } -} - -//============================================================================= -// Loop unroll tests -//============================================================================= - -std::string run_loop_unroll_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - SymtabVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - return to_nmodl(ast.get(), {AstNodeType::DEFINE}); -} - -TEST_CASE("Loop Unroll visitor") { - SECTION("Successful unrolls with constant folding") { - GIVEN("A loop with known iteration space") { - std::string input_nmodl = R"( - DEFINE N 2 - PROCEDURE rates() { - LOCAL x[N] - FROM i=0 TO N { - x[i] = x[i] + 11 - } - FROM i=(0+(0+1)) TO (N+2-1) { - x[(i+0)] = x[i+1] + 11 - } - } - KINETIC state { - FROM i=1 TO N+1 { - ~ ca[i] <-> ca[i+1] (DFree*frat[i+1]*1(um), DFree*frat[i+1]*1(um)) - } - } - )"; - std::string output_nmodl = R"( - PROCEDURE rates() { - LOCAL x[N] - { - x[0] = x[0]+11 - x[1] = x[1]+11 - x[2] = x[2]+11 - } - { - x[1] = x[2]+11 - x[2] = x[3]+11 - x[3] = x[4]+11 - } - } - - KINETIC state { - { - ~ ca[1] <-> ca[2] (DFree*frat[2]*1(um), DFree*frat[2]*1(um)) - ~ ca[2] <-> ca[3] (DFree*frat[3]*1(um), DFree*frat[3]*1(um)) - ~ ca[3] <-> ca[4] (DFree*frat[4]*1(um), DFree*frat[4]*1(um)) - } - } - )"; - THEN("Loop body gets correctly unrolled") { - auto result = run_loop_unroll_visitor(input_nmodl); - REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); - } - } - - GIVEN("A nested loop") { - std::string input_nmodl = R"( - DEFINE N 1 - PROCEDURE rates() { - LOCAL x[N] - FROM i=0 TO N { - FROM j=1 TO N+1 { - x[i] = x[i+j] + 1 - } - } - } - )"; - std::string output_nmodl = R"( - PROCEDURE rates() { - LOCAL x[N] - { - { - x[0] = x[1]+1 - x[0] = x[2]+1 - } - { - x[1] = x[2]+1 - x[1] = x[3]+1 - } - } - } - )"; - THEN("Loop get unrolled recursively") { - auto result = run_loop_unroll_visitor(input_nmodl); - REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); - } - } - - - GIVEN("Loop with verbatim and unknown iteration space") { - std::string input_nmodl = R"( - DEFINE N 1 - PROCEDURE rates() { - LOCAL x[N] - FROM i=((0+0)) TO (((N+0))) { - FROM j=1 TO k { - x[i] = x[i+k] + 1 - } - } - FROM i=0 TO N { - VERBATIM ENDVERBATIM - } - } - )"; - std::string output_nmodl = R"( - PROCEDURE rates() { - LOCAL x[N] - { - FROM j = 1 TO k { - x[0] = x[0+k]+1 - } - FROM j = 1 TO k { - x[1] = x[1+k]+1 - } - } - FROM i = 0 TO N { - VERBATIM ENDVERBATIM - } - } - )"; - THEN("Only some loops get unrolled") { - auto result = run_loop_unroll_visitor(input_nmodl); - REQUIRE(reindent_text(output_nmodl) == reindent_text(result)); - } - } - } -} - -//============================================================================= -// STEADYSTATE visitor tests -//============================================================================= -std::vector<std::string> run_steadystate_visitor( - const std::string& text, - std::vector<AstNodeType> ret_nodetypes = {AstNodeType::SOLVE_BLOCK, - AstNodeType::DERIVATIVE_BLOCK}) { - std::vector<std::string> results; - // construct AST from text - NmodlDriver driver; - auto ast = driver.parse_string(text); - - // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); - - // unroll loops and fold constants - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); - - // Run kinetic block visitor first, so any kinetic blocks - // are converted into derivative blocks - KineticBlockVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); - - // run SteadystateVisitor on AST - SteadystateVisitor().visit_program(ast.get()); - - // run lookup visitor to extract results from AST - auto res = AstLookupVisitor().lookup(ast.get(), ret_nodetypes); - for (const auto& r: res) { - results.push_back(to_nmodl(r.get())); - } - return results; -} - -SCENARIO("SteadystateSolver visitor", "[steadystate]") { - GIVEN("STEADYSTATE sparse solve") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states STEADYSTATE sparse - } - DERIVATIVE states { - m' = m + h - } - )"; - std::string expected_text1 = R"( - DERIVATIVE states { - m' = m+h - })"; - std::string expected_text2 = R"( - DERIVATIVE states_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt - dt = 1000000000 - m' = m+h - dt = dt_saved_value - })"; - THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { - auto result = run_steadystate_visitor(nmodl_text); - REQUIRE(result.size() == 3); - REQUIRE(result[0] == "SOLVE states_steadystate METHOD sparse"); - REQUIRE(reindent_text(result[1]) == reindent_text(expected_text1)); - REQUIRE(reindent_text(result[2]) == reindent_text(expected_text2)); - } - } - GIVEN("STEADYSTATE derivimplicit solve") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE states STEADYSTATE derivimplicit - } - DERIVATIVE states { - m' = m + h - } - )"; - std::string expected_text1 = R"( - DERIVATIVE states { - m' = m+h - })"; - std::string expected_text2 = R"( - DERIVATIVE states_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt - dt = 1e-09 - m' = m+h - dt = dt_saved_value - })"; - THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { - auto result = run_steadystate_visitor(nmodl_text); - REQUIRE(result.size() == 3); - REQUIRE(result[0] == "SOLVE states_steadystate METHOD derivimplicit"); - REQUIRE(reindent_text(result[1]) == reindent_text(expected_text1)); - REQUIRE(reindent_text(result[2]) == reindent_text(expected_text2)); - } - } - GIVEN("two STEADYSTATE solves") { - std::string nmodl_text = R"( - STATE { - Z[3] - x - } - BREAKPOINT { - SOLVE states0 STEADYSTATE derivimplicit - SOLVE states1 STEADYSTATE sparse - } - DERIVATIVE states0 { - Z'[0] = Z[1] - Z[2] - Z'[1] = Z[0] + 2*Z[2] - Z'[2] = Z[0]*Z[0] - 3.10 - } - DERIVATIVE states1 { - x' = x + c - } - )"; - std::string expected_text1 = R"( - DERIVATIVE states0 { - Z'[0] = Z[1]-Z[2] - Z'[1] = Z[0]+2*Z[2] - Z'[2] = Z[0]*Z[0]-3.1 - })"; - std::string expected_text2 = R"( - DERIVATIVE states1 { - x' = x+c - })"; - std::string expected_text3 = R"( - DERIVATIVE states0_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt - dt = 1e-09 - Z'[0] = Z[1]-Z[2] - Z'[1] = Z[0]+2*Z[2] - Z'[2] = Z[0]*Z[0]-3.1 - dt = dt_saved_value - })"; - std::string expected_text4 = R"( - DERIVATIVE states1_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt - dt = 1000000000 - x' = x+c - dt = dt_saved_value - })"; - THEN("Construct DERIVATIVE blocks with steadystate solution & update SOLVE statements") { - auto result = run_steadystate_visitor(nmodl_text); - REQUIRE(result.size() == 6); - REQUIRE(result[0] == "SOLVE states0_steadystate METHOD derivimplicit"); - REQUIRE(result[1] == "SOLVE states1_steadystate METHOD sparse"); - REQUIRE(reindent_text(result[2]) == reindent_text(expected_text1)); - REQUIRE(reindent_text(result[3]) == reindent_text(expected_text2)); - REQUIRE(reindent_text(result[4]) == reindent_text(expected_text3)); - REQUIRE(reindent_text(result[5]) == reindent_text(expected_text4)); - } - } -} From 5bf4b1e1fba4a5a1e104a7be9bb91cd5544619d9 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Sun, 28 Apr 2019 10:51:57 +0200 Subject: [PATCH 198/871] Documentation for AST yaml specification (BlueBrain/nmodl#153) - nmodl.yaml partly updated with examples - Capitalize todos for better docs appearance - Remove `..` to avoid Doxygen warnings - Move brief & description at the bottom - DoxygenLayout.xml updated to have description at the top - Add test prefix with catch_discover_tests NMODL Repo SHA: BlueBrain/nmodl@c32eebd579e9a830cf3325b8399201d09a7a818e --- docs/nmodl/transpiler/DoxygenLayout.xml | 10 +- docs/nmodl/transpiler/doxygen_nmodl.css | 27 + src/nmodl/ast/ast_common.hpp | 6 +- src/nmodl/codegen/codegen_c_visitor.cpp | 20 +- src/nmodl/codegen/codegen_c_visitor.hpp | 4 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 4 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 10 +- src/nmodl/codegen/codegen_info.hpp | 2 +- src/nmodl/language/nmodl.yaml | 765 ++++++++++-------- .../templates/visitors/ast_visitor.hpp | 4 + .../language/templates/visitors/visitor.hpp | 2 +- src/nmodl/lexer/modtoken.hpp | 2 +- src/nmodl/lexer/nmodl.ll | 2 +- src/nmodl/lexer/nmodl_lexer.hpp | 7 +- src/nmodl/lexer/verbatim.l | 9 +- src/nmodl/nmodl/main.cpp | 5 + src/nmodl/parser/c11.yy | 2 +- src/nmodl/parser/diffeq_context.cpp | 2 +- src/nmodl/parser/diffeq_context.hpp | 4 +- src/nmodl/parser/diffeq_driver.cpp | 2 +- src/nmodl/parser/diffeq_helper.hpp | 2 +- src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/parser/nmodl_driver.hpp | 13 +- src/nmodl/parser/unit.yy | 2 +- src/nmodl/parser/verbatim.yy | 2 +- src/nmodl/printer/json_printer.hpp | 6 +- src/nmodl/solver/newton/newton.hpp | 4 +- src/nmodl/symtab/symbol.hpp | 2 +- src/nmodl/symtab/symbol_table.hpp | 4 +- src/nmodl/units/units.hpp | 2 +- src/nmodl/utils/common_utils.hpp | 2 +- .../visitors/constant_folder_visitor.hpp | 4 + src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.cpp | 4 +- src/nmodl/visitors/var_usage_visitor.hpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 2 + 37 files changed, 552 insertions(+), 394 deletions(-) diff --git a/docs/nmodl/transpiler/DoxygenLayout.xml b/docs/nmodl/transpiler/DoxygenLayout.xml index ed94bbe4e0..00930c097f 100644 --- a/docs/nmodl/transpiler/DoxygenLayout.xml +++ b/docs/nmodl/transpiler/DoxygenLayout.xml @@ -39,6 +39,7 @@ <!-- Layout definition for a class page --> <class> <briefdescription visible="yes"/> + <detaileddescription title=""/> <includes visible="$SHOW_INCLUDE_FILES"/> <inheritancegraph visible="$CLASS_GRAPH"/> <collaborationgraph visible="$COLLABORATION_GRAPH"/> @@ -76,7 +77,6 @@ <related title="" subtitle=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <inlineclasses title=""/> <typedefs title=""/> @@ -98,6 +98,7 @@ <!-- Layout definition for a namespace page --> <namespace> <briefdescription visible="yes"/> + <detaileddescription title=""/> <memberdecl> <nestednamespaces visible="yes" title=""/> <constantgroups visible="yes" title=""/> @@ -113,7 +114,6 @@ <variables title=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <inlineclasses title=""/> <typedefs title=""/> @@ -129,6 +129,7 @@ <!-- Layout definition for a file page --> <file> <briefdescription visible="yes"/> + <detaileddescription title=""/> <includes visible="$SHOW_INCLUDE_FILES"/> <includegraph visible="$INCLUDE_GRAPH"/> <includedbygraph visible="$INCLUDED_BY_GRAPH"/> @@ -149,7 +150,6 @@ <variables title=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <inlineclasses title=""/> <defines title=""/> @@ -166,6 +166,7 @@ <!-- Layout definition for a group page --> <group> <briefdescription visible="yes"/> + <detaileddescription title=""/> <groupgraph visible="$GROUP_GRAPHS"/> <memberdecl> <nestedgroups visible="yes" title=""/> @@ -190,7 +191,6 @@ <friends title=""/> <membergroups visible="yes"/> </memberdecl> - <detaileddescription title=""/> <memberdef> <pagedocs/> <inlineclasses title=""/> @@ -216,11 +216,11 @@ <!-- Layout definition for a directory page --> <directory> <briefdescription visible="yes"/> + <detaileddescription title=""/> <directorygraph visible="yes"/> <memberdecl> <dirs visible="yes"/> <files visible="yes"/> </memberdecl> - <detaileddescription title=""/> </directory> </doxygenlayout> diff --git a/docs/nmodl/transpiler/doxygen_nmodl.css b/docs/nmodl/transpiler/doxygen_nmodl.css index 4ccb451132..9fb15cdc2b 100644 --- a/docs/nmodl/transpiler/doxygen_nmodl.css +++ b/docs/nmodl/transpiler/doxygen_nmodl.css @@ -50,6 +50,33 @@ span.keyword { color: #152292; } +dl.reflist dt { + color: #481120; +} + +h2.groupheader { + margin-top: 0.75em; +} + +tr.heading h2 { + margin-top: 4px; +} + +h1 { + color: #7B3548; + font-size: 130%; +} + +h2 { + color: #7B3548; + font-size: 115%; +} + +h3 { + color: #7B3548; + font-size: 100%; +} + /* body { max-width: 1440px; diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 9ea78b1c15..9a7d9b7477 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -29,17 +29,17 @@ namespace nmodl { namespace ast { /** - * @defgroup ast AST Infrastructure + * @defgroup ast AST Implementation * @brief All AST related implementation details * * @defgroup ast_prop AST Properties * @ingroup ast - * @brief Properties used with different members of AST classes + * @brief Properties and types used with of AST classes * @{ */ /** - * \brief enum type for binary operators in NMODL + * \brief enum Type for binary operators in NMODL * * NMODL support different binary operators and this * type is used to store their value in the AST. diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index bcc72dff6a..0def68807d 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -573,7 +573,7 @@ int CodegenCVisitor::int_variables_size() { * different variable names, we rely on backend-specific read_ion_variable_name * and write_ion_variable_name method which will be overloaded. * - * \todo: After looking into mod2c and neuron implementation, it seems like + * \todo After looking into mod2c and neuron implementation, it seems like * Ode block type is not used (?). Need to look into implementation details. */ std::vector<std::string> CodegenCVisitor::ion_read_statements(BlockType type) { @@ -664,7 +664,7 @@ std::vector<ShadowUseStatement> CodegenCVisitor::ion_write_statements(BlockType } else if (ion.is_extra_cell_conc(concentration)) { index = 2; } else { - /// \todo: unhandled case in neuron implementation + /// \todo Unhandled case in neuron implementation throw std::logic_error("codegen error for {} ion"_format(ion.name)); } auto ion_type_name = "{}_type"_format(ion.name); @@ -944,7 +944,7 @@ std::vector<IndexVariableInfo> CodegenCVisitor::get_int_variables() { * simd loop, we have to use shadow vectors for every ion variables. Here * we return list of all such variables. * - * \todo: if conductances are specified, we don't need all below variables + * \todo If conductances are specified, we don't need all below variables */ std::vector<SymbolType> CodegenCVisitor::get_shadow_variables() { std::vector<SymbolType> variables; @@ -1341,7 +1341,7 @@ void CodegenCVisitor::print_top_verbatim_blocks() { /** - * \todo: issue with verbatim renaming. e.g. pattern.mod has info struct with + * \todo Issue with verbatim renaming. e.g. pattern.mod has info struct with * index variable. If we use "index" instead of "indexes" as default argument * then during verbatim replacement we don't know the index is which one. This * is because verbatim renaming pass has already stripped out prefixes from @@ -1918,7 +1918,7 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& /** - * \todo this should be replaced with constant handling from unit database + * \todo This should be replaced with constant handling from unit database */ void CodegenCVisitor::print_nmodl_constants() { printer->add_newline(2); @@ -3194,7 +3194,7 @@ void CodegenCVisitor::print_nrn_alloc() { } /** - * \todo number of watch could be more than number of statements + * \todo Number of watch could be more than number of statements * according to grammar. Check if this is correctly handled in neuron * and coreneuron. */ @@ -3217,8 +3217,10 @@ void CodegenCVisitor::print_watch_activate() { } printer->add_line("}"); - /// \todo : similar to neuron/coreneuron we are using - // first watch and ignoring rest. + /** + * \todo Similar to neuron/coreneuron we are using + * first watch and ignoring rest. + */ for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; printer->start_block("if (watch_id == {})"_format(i)); @@ -3239,7 +3241,7 @@ void CodegenCVisitor::print_watch_activate() { /** - * \todo similar to print_watch_activate, we are using only + * \todo Similar to print_watch_activate, we are using only * first watch. need to verify with neuron/coreneuron about rest. */ void CodegenCVisitor::print_watch_check() { diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index d979637860..8f90bfd896 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -37,7 +37,7 @@ namespace nmodl { namespace codegen { /** - * @defgroup codegen Codegen Infrastructure + * @defgroup codegen Code Generation Implementation * @brief Implementations of code generation backends * * @defgroup codegen_details Codegen Helpers @@ -148,7 +148,7 @@ enum class LayoutType { * Ion update statement needs use of shadow vectors for certain backends * as atomics operations are not supported on cpu backend. * - * \todo : if shadow_lhs is empty then we assume shadow statement not required + * \todo If shadow_lhs is empty then we assume shadow statement not required */ struct ShadowUseStatement { std::string lhs; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 6eb5cd565a..f76033894a 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -218,7 +218,7 @@ void CodegenHelperVisitor::find_non_range_variables() { sort_with_mod2c_symbol_order(info.thread_variables); /** - * \todo: Below we calculate thread related id and sizes. This will + * \todo Below we calculate thread related id and sizes. This will * need to do from global analysis pass as here we are handling * top local variables, global variables, derivimplicit method. * There might be more use cases with other solver methods. @@ -338,7 +338,7 @@ void CodegenHelperVisitor::find_range_variables() { * and those are not considered as range+state variables while printing instance * variables. Such read/write ion variables are dependent variables and hence they * will be printed at laster stage. - * \todo: need to validate with more models and mod2c details. + * \todo Need to validate with more models and mod2c details. */ // clang-format off with = NmodlType::state_var; diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 7adef36535..6f850f8e31 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -36,11 +36,11 @@ namespace codegen { * class object of CodegenInfo. * * \todo - * - determine `vectorize` as part of the pass - * - determine `threadsafe` as part of the pass - * - global variable order is not preserved, for example, below gives different order: - * NEURON block: GLOBAL gq, gp - * PARAMETER block: gp = 11, gq[2] + * - Determine `vectorize` as part of the pass + * - Determine `threadsafe` as part of the pass + * - Global variable order is not preserved, for example, below gives different order: + * - NEURON block: GLOBAL gq, gp + * - PARAMETER block: gp = 11, gq[2] * - POINTER rng and if it's also assigned rng[4] then it is printed as one value. * Need to check what is correct value. */ diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 63b05f4b22..a62c485106 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -134,7 +134,7 @@ struct IndexSemantics { * Code generation passes require different information from AST. This * information is gathered in this single class. * - * \todo : need to store all Define i.e. macro definitions? + * \todo Need to store all Define i.e. macro definitions? */ struct CodegenInfo { /// name of mod file diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 33a078423d..191362e30e 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -105,7 +105,7 @@ - String: members: - value: - brief: "value of string" + brief: "Value of string" type: std::string brief: "Represents a string" description: | @@ -131,7 +131,7 @@ - Integer: members: - value: - brief: "value of integer" + brief: "Value of integer" type: int - macro: brief: "if integer is a macro then it's name" @@ -153,7 +153,7 @@ - Float: members: - value: - brief: "value of float" + brief: "Value of float" type: float brief: "Represents a float variable" description: | @@ -166,7 +166,7 @@ - Double: members: - value: - brief: "value of double" + brief: "Value of double" type: double brief: "Represents a double variable" description: | @@ -188,7 +188,7 @@ - Boolean: members: - value: - brief: "value of boolean" + brief: "Value of boolean" type: int brief: "Represents a boolean variable" description: | @@ -206,7 +206,7 @@ - Name: members: - value: - brief: "value of name" + brief: "Value of name" type: String node_name: true brief: "Represents a name" @@ -220,7 +220,7 @@ - PrimeName: members: - value: - brief: "name of prime variable" + brief: "Name of prime variable" type: String node_name: true - order: @@ -242,7 +242,7 @@ - IndexedName: members: - name: - brief: "name of array variable" + brief: "Name of array variable" type: Identifier node_name: true - length: @@ -265,11 +265,11 @@ - VarName: members: - name: - brief: "name of variable" + brief: "Name of variable" type: Identifier node_name: true - at: - brief: "value specified with `@`" + brief: "Value specified with `@`" type: Integer optional: true prefix: {value: "@"} @@ -289,14 +289,14 @@ - Argument: members: - name: - brief: "name of the argument" + brief: "Name of the argument" type: Identifier node_name: true - unit: - brief: "unit of the argument" + brief: "Unit of the argument" type: Unit optional: true - brief: "Represents an argument to function" + brief: "Represents an argument to functions and procedures" description: | In case of function definitions from different ast nodes like ast::FunctionBlock, ast::ProcedureBlock, the arguments are store in the ast::Argument. For example, @@ -309,102 +309,108 @@ } \endcode - ReactVarName: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: Integer optional: true prefix: {value: " "} - name: - brief: "..." + brief: "TODO" type: VarName node_name: true - ReadIonVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - WriteIonVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - NonspecificCurVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - ElectrodeCurVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - SectionVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - RangeVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - GlobalVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - PointerVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - BbcorePointerVar: - brief: ".." members: - name: - brief: "..." + brief: "Variable name" type: Name node_name: true + brief: "Represent a single variable of type BBCOREPOINTER" + description: | + See ast::BbcorePtr for an example. - ExternVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - ThreadsafeVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - Block: - brief: ".." + brief: "Base class for all block scoped nodes" + description: | + NMODL has different local and global block scoped nodes like + ast::NeuronBlock, ast::ParamBlock, ast::IfStatement etc. Ast::Block + is base class and defines common interface for these nodes. children: - ParamBlock: nmodl: "PARAMETER " members: - statements: - brief: "vector of parameters" + brief: "Vector of parameters" type: ParamAssign vector: true brief: "Represents a `PARAMETER` block in the NMODL" @@ -428,7 +434,7 @@ nmodl: "STEPPED " members: - statements: - brief: "vector of statements" + brief: "Vector of statements" type: Stepped vector: true brief: "Represents a `STEPPED` block in the NMODL" @@ -447,7 +453,7 @@ nmodl: "INDEPENDENT " members: - definitions: - brief: "..." + brief: "TODO" type: IndependentDef vector: true brief: "Represents a `INDEPENDENT` block in the NMODL" @@ -464,7 +470,7 @@ nmodl: "ASSIGNED " members: - definitions: - brief: "vector of assigned variables" + brief: "Vector of assigned variables" type: DependentDef vector: true brief: "Represents a `ASSIGNED` block in the NMODL" @@ -489,7 +495,7 @@ nmodl: "STATE " members: - definitions: - brief: "vector of state variables" + brief: "Vector of state variables" type: DependentDef vector: true brief: "Represents a `STATE` block in the NMODL" @@ -510,7 +516,7 @@ - PlotBlock: members: - plot: - brief: "vector of plot variables" + brief: "Vector of plot variables" type: PlotDeclaration brief: "Represents a `PLOT` statement in the NMODL" description: | @@ -527,7 +533,7 @@ nmodl: "INITIAL " members: - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} brief: "Represents a `INITIAL` block in the NMODL" @@ -548,7 +554,7 @@ nmodl: "CONSTRUCTOR " members: - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} brief: "Represents a `CONSTRUCTOR` block in the NMODL" @@ -571,7 +577,7 @@ nmodl: "DESTRUCTOR " members: - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} brief: "Represents a `DESTRUCTOR` block in the NMODL" @@ -594,7 +600,7 @@ - StatementBlock: members: - statements: - brief: "vector of statements" + brief: "Vector of statements" type: Statement vector: true public: true @@ -619,12 +625,12 @@ nmodl: "DERIVATIVE " members: - name: - brief: "name of the derivative block" + brief: "Name of the derivative block" type: Name node_name: true suffix: {value: " "} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} brief: "Represents `DERIVATIVE` block in the NMODL" @@ -645,18 +651,18 @@ nmodl: "LINEAR " members: - name: - brief: "name of the linear block" + brief: "Name of the linear block" type: Name node_name: true suffix: {value: " "} - solvefor: - brief: "..." + brief: "TODO" type: Name vector: true separator: "," prefix: {value: " SOLVEFOR "} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} brief: "Represents `LINEAR` block in the NMODL" @@ -680,18 +686,18 @@ nmodl: "NONLINEAR " members: - name: - brief: "name of the non-linear block" + brief: "Name of the non-linear block" type: Name node_name: true - solvefor: - brief: "name of the integration method" + brief: "Name of the integration method" type: Name vector: true separator: "," prefix: {value: " SOLVEFOR "} suffix: {value: " ", force: true} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} brief: "Represents `NONLINEAR` block in the NMODL" @@ -709,41 +715,41 @@ \endcode - DiscreteBlock: - brief: ".." + brief: "TODO" nmodl: "DISCRETE " members: - name: - brief: "..." + brief: "Name of the discrete block" type: Name node_name: true suffix: {value: " "} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - PartialBlock: - brief: ".." + brief: "TODO" nmodl: "PARTIAL " members: - name: - brief: "..." + brief: "Name of the partial block" type: Name node_name: true suffix: {value: " "} - statement_block: - brief: "..." + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - FunctionTableBlock: - brief: ".." + brief: "TODO" nmodl: "FUNCTION_TABLE " members: - name: - brief: "..." + brief: "Name of the function table block" type: Name node_name: true - parameters: - brief: "..." + brief: "Vector of the parameters" type: Argument vector: true prefix: {value: "(", force: true} @@ -751,20 +757,20 @@ separator: ", " getter: {override: true} - unit: - brief: "..." + brief: "Unit if specified" type: Unit optional: true prefix: {value: " "} - FunctionBlock: - brief: ".." + brief: "TODO" nmodl: "FUNCTION " members: - name: - brief: "..." + brief: "Name of the function" type: Name node_name: true - parameters: - brief: "..." + brief: "Vector of the parameters" type: Argument vector: true prefix: {value: "(", force: true} @@ -772,25 +778,25 @@ separator: ", " getter: {override: true} - unit: - brief: "..." + brief: "Unit if specified" type: Unit optional: true prefix: {value: " "} suffix: {value: " ", force: true} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - ProcedureBlock: - brief: ".." + brief: "TODO" nmodl: "PROCEDURE " members: - name: - brief: "..." + brief: "Name of the procedure" type: Name node_name: true - parameters: - brief: "..." + brief: "Vector of the parameters" type: Argument vector: true prefix: {value: "(", force: true} @@ -798,19 +804,19 @@ separator: ", " getter: {override: true} - unit: - brief: "..." + brief: "Unit if specified" type: Unit optional: true - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - NetReceiveBlock: - brief: ".." + brief: "TODO" nmodl: "NET_RECEIVE " members: - parameters: - brief: "..." + brief: "Parameters to the net receive block" type: Argument vector: true prefix: {value: "(", force: true} @@ -818,79 +824,112 @@ separator: ", " getter: {override: true} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - SolveBlock: - brief: ".." + brief: "TODO" nmodl: SOLVE members: - block_name: - brief: "..." + brief: "Name of the block to solve" type: Name prefix: {value: " "} - method: - brief: "..." + brief: "Name of the integration method" type: Name optional: true prefix: {value: " METHOD "} - steadystate: - brief: "..." + brief: "Name of the integration method" type: Name optional: true prefix: {value: " STEADYSTATE "} - ifsolerr: - brief: "..." + brief: "Block to be executed on error" type: StatementBlock optional: true prefix: {value: " IFERROR "} - BreakpointBlock: - brief: ".." nmodl: "BREAKPOINT " members: - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents a `BREAKPOINT` block in NMODL" + description: | + The `BREAKPOINT` block is used to update current and conductance. + at each time step. Here is an example of `BEFORE` : + + \code{.mod} + BREAKPOINT { + SOLVE states METHOD cnexp + gna = gnabar*m*m*m*h + ina = gna*(v - ena) + gk = gkbar*n*n*n*n + ik = gk*(v - ek) + il = gl*(v - el) + } + \endcode + + \sa ast::DerivativeBlock ast::InitialBlock - TerminalBlock: - brief: ".." + brief: "TODO" nmodl: "TERMINAL " members: - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - BeforeBlock: - brief: ".." nmodl: "BEFORE " members: - bablock: - brief: "..." + brief: "Block to be called before" type: BABlock + brief: "Represents a `BEFORE` block in NMODL" - AfterBlock: - brief: ".." nmodl: "AFTER " members: - bablock: - brief: "..." + brief: "Block to be called after" type: BABlock + brief: "Represents a `AFTER` block in NMODL" + description: | + This represents a block to be executed before another block. + Here is an example of `BEFORE` : + + \code{.mod} + BEFORE STEP { + if (mode==1) { + if (ica<imax) { + imax = ica + timax = t + } + } + } + \endcode - BABlock: - brief: ".." members: - type: - brief: "..." + brief: "Type of NMODL block" type: BABlockType suffix: {value: " "} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} + brief: "Represents a block to be executed before or after another block" + description: | + This represents a block to be executed before or after another + block in NMODL. See ast::BeforeBlock and ast::AfterBlock for usage. - ForNetcon: - brief: ".." + brief: "TODO" nmodl: "FOR_NETCONS " members: - parameters: - brief: "..." + brief: "Arguments to the for netcon block" type: Argument vector: true prefix: {value: "(", force: true} @@ -898,55 +937,73 @@ separator: ", " getter: {override: true} - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - KineticBlock: - brief: ".." + brief: "TODO" nmodl: "KINETIC " members: - name: - brief: "..." + brief: "Name of the kinetic block" type: Name node_name: true suffix: {value: " "} - solvefor: - brief: "..." + brief: "Solve for specification (TODO)" type: Name vector: true separator: "," - statement_block: - brief: "block with statements vector" + brief: "Block with statements vector" type: StatementBlock getter: {override: true} - MatchBlock: - brief: ".." + brief: "TODO" nmodl: MATCH members: - matchs: - brief: "..." + brief: "Vector of match statements" type: Match vector: true separator: " " prefix: {value: " { "} suffix: {value: " }"} - UnitBlock: - brief: ".." + brief: "TODO" nmodl: "UNITS " members: - definitions: - brief: "..." + brief: "Vector of unit statements" type: Expression vector: true - ConstantBlock: - brief: ".." nmodl: "CONSTANT " members: - statements: - brief: "..." + brief: "Vector of constant statements" type: ConstantStatement vector: true + brief: "Represent `CONSTANT` block in the mod file" + description: | + Here is an example of `CONSTANT` block in mod file: + + \code{.mod} + CONSTANT { + q10 = 3 + + cvm = 28.9 (mV) + ckm = 6.2 (mV) + ctm = 0.000505 (s) + } + \endcode - NeuronBlock: + nmodl: "NEURON " + members: + - statement_block: + brief: "Block with statements vector" + type: StatementBlock + getter: {override: true} brief: "Represent `NEURON` block in the mod file" description: | The keyword `NEURON` introduces a special block which contains statements @@ -966,131 +1023,141 @@ \endcode url: "https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl2.html#neuron" - nmodl: "NEURON " - members: - - statement_block: - brief: "block with statements vector" - type: StatementBlock - getter: {override: true} - Unit: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: String node_name: true prefix: {value: "("} suffix: {value: ")"} - DoubleUnit: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: Double - unit: - brief: "..." + brief: "TODO" type: Unit optional: true - LocalVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Identifier node_name: true - Limits: - brief: ".." + brief: "TODO" members: - min: - brief: "..." + brief: "TODO" type: Double prefix: {value: "<"} suffix: {value: ","} - max: - brief: "..." + brief: "TODO" type: Double suffix: {value: ">"} - NumberRange: - brief: ".." + brief: "TODO" members: - min: - brief: "..." + brief: "TODO" type: Number prefix: {value: "<"} suffix: {value: ","} - max: - brief: "..." + brief: "TODO" type: Number suffix: {value: ">"} - PlotVar: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Identifier - index: - brief: "..." + brief: "TODO" type: Integer optional: true prefix: {value: "["} suffix: {value: "]"} - ConstantVar: - brief: ".." members: - name: - brief: "..." + brief: "Name of the variable" type: Name node_name: true - value: - brief: "..." + brief: "Value of the constant" type: Number prefix: {value: " = "} - unit: - brief: "..." + brief: "Unit for the variable" type: Unit optional: true prefix: {value: " "} + brief: "Represents a variable in the ast::ConstantBlock" + - BinaryOperator: - brief: ".." members: - value: - brief: "..." + brief: "Opearator" type: BinaryOp + brief: "Operator used in ast::BinaryExpression" - UnaryOperator: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: UnaryOp - ReactionOperator: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: ReactionOp - ParenExpression: - brief: ".." + brief: "TODO" members: - expression: - brief: "..." + brief: "TODO" type: Expression prefix: {value: "("} suffix: {value: ")"} - BinaryExpression: - brief: ".." members: - lhs: - brief: "..." + brief: "LHS of the binary expression" type: Expression public: true - op: - brief: "..." + brief: "Opearator" type: BinaryOperator public: true - rhs: - brief: "..." + brief: "RHS of the binary expression" type: Expression public: true + brief: "Represents binary expression in the NMODL" + description: | + Any binary expression in the mod file is represented by this node type. + For example, in below example, there are three binary expressions : + + \code{.mod} + BREAKPOINT { + SOLVE states METHOD cnexp + ina = gna*(v - ena) + } + \endcode + + Note that the statement itself is stored in another type ast::ExpressionStatement. + + \sa ast::ExpressionStatement + - DiffEqExpression: brief: "Represents differential equation in DERIVATIVE block" members: @@ -1099,116 +1166,121 @@ type: BinaryExpression public: true - UnaryExpression: - brief: ".." + brief: "TODO" members: - op: - brief: "..." + brief: "TODO" type: UnaryOperator - expression: - brief: "..." + brief: "TODO" type: Expression - NonLinEquation: - brief: ".." + brief: "TODO" nmodl: "~ " members: - lhs: - brief: "..." + brief: "TODO" type: Expression suffix: {value: " = "} - rhs: - brief: "..." + brief: "TODO" type: Expression - LinEquation: - brief: ".." + brief: "TODO" nmodl: "~ " members: - left_linxpression: - brief: "..." + brief: "TODO" type: Expression suffix: {value: " = "} - linxpression: - brief: "..." + brief: "TODO" type: Expression - FunctionCall: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - arguments: - brief: "..." + brief: "TODO" type: Expression vector: true separator: ", " prefix: {value: "(", force: true} suffix: {value: ")", force: true} - FirstLastTypeIndex: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: FirstLastType - Watch: - brief: ".." + brief: "TODO" members: - expression: - brief: "..." + brief: "TODO" type: Expression prefix: {value: "("} suffix: {value: ")"} - value: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " "} - QueueExpressionType: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: QueueType - Match: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Identifier - expression: - brief: "..." + brief: "TODO" type: Expression optional: true - BABlockType: - brief: ".." members: - value: - brief: "..." + brief: "block type" type: BAType + brief: "Type to represent different block types for before/after block" + description: | + Different NMODL blocks can be used with ast::BeforeBlock and ast::AfterBlock. + This type is used to represent such block types. + + \sa ast::BeforeBlock as::AfterBlock - UnitDef: - brief: ".." + brief: "TODO" members: - unit1: - brief: "..." + brief: "TODO" type: Unit node_name: true - unit2: - brief: "..." + brief: "TODO" type: Unit prefix: {value: " = "} - FactorDef: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true suffix: {value: " ="} - value: - brief: "..." + brief: "TODO" type: Double optional: true prefix: {value: " "} - unit1: - brief: "..." + brief: "TODO" type: Unit prefix: {value: " "} - gt: @@ -1217,647 +1289,680 @@ nmodl: " ->" optional: true - unit2: - brief: "..." + brief: "TODO" type: Unit optional: true prefix: {value: " "} - Valence: - brief: ".." + brief: "TODO" members: - type: type: Name - brief: "..." + brief: "TODO" prefix: {value: " "} suffix: {value: " "} - value: - brief: "..." + brief: "TODO" type: Double - Statement: - brief: ".." + brief: "TODO" children: - UnitState: - brief: ".." + brief: "TODO" members: - value: - brief: "..." + brief: "TODO" type: UnitStateType - LocalListStatement: - brief: ".." + brief: "TODO" nmodl: "LOCAL " members: - variables: - brief: "..." + brief: "TODO" type: LocalVar vector: true separator: ", " public: true - Model: - brief: ".." + brief: "TODO" nmodl: TITLE members: - title: - brief: "..." + brief: "TODO" type: String - Define: - brief: ".." nmodl: "DEFINE " members: - name: - brief: "..." + brief: "Name of the macro" type: Name node_name: true - value: - brief: "..." + brief: "Value of the macro" type: Integer prefix: {value: " "} + brief: "Represents a `DEFINE` statement in NMODL" - Include: - brief: ".." + brief: "TODO" nmodl: "INCLUDE " members: - filename: - brief: "..." + brief: "TODO" type: String - ParamAssign: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Identifier node_name: true - value: - brief: "..." + brief: "TODO" type: Number optional: true prefix: {value: " = "} - unit: - brief: "..." + brief: "TODO" type: Unit optional: true prefix: {value: " "} - limit: - brief: "..." + brief: "TODO" type: Limits optional: true prefix: {value: " "} - Stepped: - brief: ".." + brief: "TODO" members: - name: - brief: "..." + brief: "TODO" type: Name - values: - brief: "..." + brief: "TODO" type: Number vector: true prefix: {value: " = "} separator: ", " - unit: - brief: "..." + brief: "TODO" type: Unit optional: true prefix: {value: " "} - IndependentDef: - brief: ".." + brief: "TODO" members: - sweep: - brief: "..." + brief: "TODO" type: Boolean optional: true nmodl: "SWEEP " - name: - brief: "..." + brief: "TODO" type: Name - from: - brief: "..." + brief: "TODO" type: Number prefix: {value: " FROM "} - to: - brief: "..." + brief: "TODO" type: Number prefix: {value: " TO "} - with: - brief: "..." + brief: "TODO" type: Integer prefix: {value: " WITH "} - start: - brief: "..." + brief: "TODO" type: Number prefix: {value: " START "} optional: true - unit: - brief: "..." + brief: "TODO" type: Unit optional: true prefix: {value: " "} - DependentDef: - brief: ".." members: - name: - brief: "..." + brief: "Name of the variable" type: Identifier node_name: true - length: - brief: "..." + brief: "Length in case of array" type: Integer optional: true prefix: {value: "["} suffix: {value: "]"} - from: - brief: "..." + brief: "TODO" type: Number prefix: {value: " FROM "} optional: true - to: - brief: "..." + brief: "TODO" type: Number prefix: {value: " TO "} optional: true - start: - brief: "..." + brief: "TODO" type: Number prefix: {value: " START "} optional: true - unit: - brief: "..." + brief: "TODO" type: Unit optional: true prefix: {value: " "} - abstol: - brief: "..." + brief: "TODO" type: Double prefix: {value: " <"} suffix: {value: ">"} optional: true + brief: "Represents a statement in `ASSIGNED` or `STATE` block" - PlotDeclaration: - brief: ".." + brief: "TODO" nmodl: "PLOT " members: - variables: - brief: "..." + brief: "TODO" type: PlotVar vector: true separator: ", " - name: - brief: "..." + brief: "TODO" type: PlotVar prefix: {value: " VS "} - ConductanceHint: - brief: ".." nmodl: "CONDUCTANCE " members: - conductance: - brief: "..." + brief: "Conductance variable" type: Name - ion: - brief: "..." + brief: "Ion name" type: Name optional: true prefix: {value: " USEION "} + brief: "Represents `CONDUCTANCE` statement in NMODL" + description: | + If `I/V` relation in the `BREAKPOINT` block is ohomic then one can + specify `CONDUCTANCE` hint for optimised code generation: + + \code{.mod} + CONDUCTANCE g USEION I + \endcode + + \sa nmodl::visitor::SympyConductanceVisitor + - ExpressionStatement: - brief: ".." + brief: "TODO" members: - expression: - brief: "..." + brief: "TODO" type: Expression - ProtectStatement: - brief: ".." + brief: "TODO" nmodl: "PROTECT " members: - expression: - brief: "..." + brief: "TODO" type: Expression - FromStatement: - brief: ".." + brief: "TODO" nmodl: "FROM " members: - name: - brief: "..." + brief: "TODO" type: Name node_name: true - from: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " = "} - to: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " TO "} - increment: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " BY "} suffix: {value: " ", force: true} optional: true - statement_block: - brief: "..." + brief: "TODO" type: StatementBlock getter: {override: true} - ForAllStatement: - brief: ".." + brief: "TODO" nmodl: "FORALL " members: - name: - brief: "..." + brief: "TODO" type: Name suffix: {value: " "} - statement_block: - brief: "..." + brief: "TODO" type: StatementBlock getter: {override: true} - WhileStatement: - brief: ".." + brief: "TODO" nmodl: "WHILE " members: - condition: - brief: "..." + brief: "TODO" type: Expression prefix: {value: "("} suffix: {value: ") "} - statement_block: - brief: "..." + brief: "TODO" type: StatementBlock getter: {override: true} - IfStatement: - brief: ".." + brief: "TODO" nmodl: "IF " members: - condition: - brief: "..." + brief: "TODO" type: Expression prefix: {value: "("} suffix: {value: ") "} - statement_block: - brief: "..." + brief: "TODO" type: StatementBlock getter: {override: true} - elseifs: - brief: "..." + brief: "TODO" type: ElseIfStatement vector: true - elses: - brief: "..." + brief: "TODO" type: ElseStatement optional: true - ElseIfStatement: - brief: ".." + brief: "TODO" nmodl: " ELSE IF " members: - condition: - brief: "..." + brief: "TODO" type: Expression prefix: {value: "("} suffix: {value: ") "} - statement_block: - brief: "..." + brief: "TODO" type: StatementBlock getter: {override: true} - ElseStatement: - brief: ".." + brief: "TODO" nmodl: " ELSE " members: - statement_block: - brief: "..." + brief: "TODO" type: StatementBlock getter: {override: true} - PartialEquation: - brief: ".." + brief: "TODO" members: - prime: - brief: "..." + brief: "TODO" type: PrimeName - name1: - brief: "..." + brief: "TODO" type: Name - name2: - brief: "..." + brief: "TODO" type: Name - name3: - brief: "..." + brief: "TODO" type: Name - PartialBoundary: - brief: ".." + brief: "TODO" nmodl: "~ " members: - del: - brief: "..." + brief: "TODO" type: Name optional: true suffix: {value: " "} - name: - brief: "..." + brief: "TODO" type: Identifier - index: - brief: "..." + brief: "TODO" type: FirstLastTypeIndex optional: true prefix: {value: "["} suffix: {value: "]"} - expression: - brief: "..." + brief: "TODO" type: Expression optional: true prefix: {value: " = "} - name1: - brief: "..." + brief: "TODO" type: Name optional: true prefix: {value: " = "} suffix: {value: "*"} - del2: - brief: "..." + brief: "TODO" type: Name optional: true suffix: {value: "("} - name2: - brief: "..." + brief: "TODO" type: Name optional: true suffix: {value: ")"} - name3: - brief: "..." + brief: "TODO" type: Name optional: true prefix: {value: "+"} - WatchStatement: - brief: ".." nmodl: "WATCH " members: - statements: - brief: "..." + brief: "Vector of watch statements" type: Watch vector: true separator: "," add: true + brief: "Represent WATCH statement in NMODL" - MutexLock: - brief: ".." nmodl: MUTEXLOCK + brief: "Represent MUTEXLOCK statement in NMODL" - MutexUnlock: - brief: ".." nmodl: MUTEXUNLOCK + brief: "Represent MUTEXUNLOCK statement in NMODL" - Reset: - brief: ".." nmodl: RESET + brief: "Represent RESET statement in NMODL" - Sens: - brief: ".." nmodl: "SENS " members: - variables: - brief: "..." + brief: "TODO" type: VarName vector: true separator: ", " + brief: "Represent SENS statement in NMODL" - Conserve: - brief: ".." nmodl: CONSERVE members: - react: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " "} - expr: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " = "} + brief: "Represent CONSERVE statement in NMODL" - Compartment: - brief: ".." nmodl: COMPARTMENT members: - name: - brief: "..." + brief: "TODO" type: Name optional: true prefix: {value: " "} suffix: {value: ","} - expression: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " "} - names: - brief: "..." + brief: "TODO" type: Name vector: true prefix: {value: " {"} suffix: {value: "}"} separator: " " + brief: "Represent COMPARTMENT statement in NMODL" - LonDifuse: - brief: ".." nmodl: LONGITUDINAL_DIFFUSION members: - name: - brief: "..." + brief: "TODO" type: Name optional: true prefix: {value: " "} suffix: {value: ","} - expression: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " "} - names: - brief: "..." + brief: "TODO" type: Name vector: true prefix: {value: " {"} suffix: {value: "}"} separator: " " + brief: "Represent LONGITUDINAL_DIFFUSION statement in NMODL" - ReactionStatement: - brief: ".." + brief: "TODO" nmodl: "~ " members: - reaction1: - brief: "..." + brief: "TODO" type: Expression - op: - brief: "..." + brief: "TODO" type: ReactionOperator prefix: {value: " "} - reaction2: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " "} optional: true - expression1: - brief: "..." + brief: "TODO" type: Expression prefix: {value: " ("} - expression2: - brief: "..." + brief: "TODO" type: Expression prefix: {value: ", "} suffix: {value: ")", force: true} optional: true - LagStatement: - brief: ".." nmodl: "LAG " members: - name: - brief: "..." + brief: "Name of the variable (TODO)" type: Identifier - byname: - brief: "..." + brief: "Name of the variable (TODO)" type: Name prefix: {value: " BY "} + brief: "Represents a LAG statement in the mod file" + description: | + An example of LAG statement usage: + + \code{.mod} + PROCEDURE lates() { + LAG ina BY tau + neo = lag_ina_tau + if (ena < 70) {ena = 70} + } + \endcode - QueueStatement: - brief: ".." members: - qtype: - brief: "..." + brief: "queue type (put/get)" type: QueueExpressionType - name: - brief: "..." + brief: "Name of the variable" type: Identifier prefix: {value: " "} + brief: "Represent queue statement in NMODL" - ConstantStatement: - brief: ".." members: - constant: - brief: "..." + brief: "single constant variable" type: ConstantVar + brief: "Represent statement in CONSTANT block of NMODL" + description: | + \todo As ConstantStatement wraps a single ConstantVar, + this or ast::ConstantVar can be redundant in the future. - TableStatement: - brief: ".." nmodl: "TABLE " members: - table_vars: - brief: "..." + brief: "Variables in the table" type: Name vector: true separator: "," - depend_vars: - brief: "..." + brief: "dependent variables" type: Name vector: true prefix: {value: " DEPEND "} separator: "," - from: - brief: "..." + brief: "from value" type: Expression prefix: {value: " FROM "} - to: - brief: "..." + brief: "to values" type: Expression prefix: {value: " TO "} - with: - brief: "..." + brief: "an increment factor" type: Integer prefix: {value: " WITH "} + brief: "Represents TABLE statement in NMODL" - Suffix: - brief: ".." members: - type: - brief: "..." + brief: "type of channel" type: Name suffix: {value: " "} - name: - brief: "..." + brief: "Name of the channel" type: Name node_name: true + brief: "Represents SUFFIX statement in NMODL" - Useion: - brief: ".." nmodl: "USEION " members: - name: - brief: "..." + brief: "Name of ion" type: Name node_name: true - readlist: - brief: "..." + brief: "Variables being read" type: ReadIonVar vector: true prefix: {value: " READ "} separator: ", " - writelist: - brief: "..." + brief: "Variables being written" type: WriteIonVar vector: true prefix: {value: " WRITE "} separator: ", " - valence: - brief: "..." + brief: "(TODO)" type: Valence optional: true + brief: "Represents USEION statement in NMODL" - Nonspecific: - brief: ".." nmodl: "NONSPECIFIC_CURRENT " members: - currents: - brief: "..." + brief: "Vector of non specific variables" type: NonspecificCurVar vector: true separator: ", " + brief: "Represents NONSPECIFIC_CURRENT variables statement in NMODL" - ElctrodeCurrent: - brief: ".." nmodl: "ELECTRODE_CURRENT " members: - currents: - brief: "..." + brief: "Vector of electrode current variables" type: ElectrodeCurVar vector: true separator: ", " + brief: "Represents ELECTRODE_CURRENT variables statement in NMODL" - Section: - brief: ".." nmodl: "SECTION " members: - sections: - brief: "..." + brief: "Vector of section variables" type: SectionVar vector: true separator: ", " + brief: "Represents SECTION variables statement in NMODL" - Range: - brief: ".." nmodl: "RANGE " members: - variables: - brief: "..." + brief: "Vector of range variables" type: RangeVar vector: true separator: ", " + brief: "Represents RANGE variables statement in NMODL" - Global: - brief: ".." + brief: "TODO" nmodl: "GLOBAL " members: - variables: - brief: "..." + brief: "Vector of global variables" type: GlobalVar vector: true separator: ", " + brief: "Represents GLOBAL statement in NMODL" - Pointer: - brief: ".." nmodl: "POINTER " members: - variables: - brief: "..." + brief: "Vector of pointer variables" type: PointerVar vector: true separator: ", " + brief: "Represents POINTER statement in NMODL" - BbcorePtr: - brief: ".." nmodl: "BBCOREPOINTER " members: - variables: - brief: "..." + brief: "Vector of bbcore pointer variables" type: BbcorePointerVar vector: true separator: ", " + brief: "Represents BBCOREPOINTER statement in NMODL" + description: | + Here is an example of BBCOREPOINTER statement: + + \code{.mod} + NEURON { + THREADSAFE + POINT_PROCESS ProbAMPANMDA_EMS + BBCOREPOINTER rng, data + \endcode - External: - brief: ".." nmodl: "EXTERNAL " members: - variables: - brief: "..." + brief: "Vector of external variables" type: ExternVar vector: true separator: ", " + brief: "Represents EXTERNAL statement in NMODL" - ThreadSafe: - brief: ".." nmodl: THREADSAFE members: - variables: - brief: "..." + brief: "Vector of thread safe variables" type: ThreadsafeVar vector: true separator: ", " prefix: {value: " "} + brief: "Represents THREADSAFE statement in NMODL" - Verbatim: brief: "Represents a C code block" nmodl: VERBATIM @@ -1885,7 +1990,7 @@ brief: "Represents top level AST node for whole NMODL input" members: - blocks: - brief: "..." + brief: "Vector of top level blocks in the mod file" type: Node vector: true add: true diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index c1f9406f31..45ee3a5775 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -8,6 +8,10 @@ #pragma once /** + * + * \dir + * \brief Auto generated visitors + * * \file * \brief \copybrief nmodl::visitor::AstVisitor */ diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 92dcb3ef2f..789c2cf185 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -13,7 +13,7 @@ namespace nmodl { namespace visitor { /** - * @defgroup visitor Visitor Infrastructure + * @defgroup visitor Visitor Implementation * @brief All visitors related implementation details * * @defgroup visitor_classes Visitors diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index d8172e4d4a..caa15c9eda 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -17,7 +17,7 @@ namespace nmodl { /** - * @defgroup token Token Infrastructure + * @defgroup token Token Implementation * @brief All token related implementation * * @defgroup token_modtoken Token Classes diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index e186642843..0f2530c311 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -298,7 +298,7 @@ ELSE { return token_symbol(yytext, loc, Token::REACTION); } - /* \todo : should be parser error instead of exception */ + /* \todo Should be parser error instead of exception */ auto msg = "Lexer Error : Invalid context, no token matched for ~"; throw std::runtime_error(msg); } diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 3d20cd6c3b..2c795c7b99 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \dir + * \brief Lexer implementations + */ + #include "ast/ast.hpp" #include "parser/nmodl/nmodl_parser.hpp" @@ -34,7 +39,7 @@ namespace nmodl { namespace parser { /** - * @defgroup lexer Lexer Infrastructure + * @defgroup lexer Lexer Implementation * @brief All lexer classes implementation * * @addtogroup lexer diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index d08094ec7b..7e2b414d68 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -28,8 +28,7 @@ * context to read the next character. The parser context defaults is * to cin, but we will set it to an istringstream later. * - * \todo - * - not able recognize NEWLINE character + * \todo Not able recognize NEWLINE character */ #define YY_INPUT(buf,result,max_size) \ { \ @@ -69,15 +68,13 @@ /** * need to unput in buffer for custom routines - * \todo : due to unused function warning this - * option is disabled + * \todo Check if unused function warning appears */ %option nounput /** * need to input in buffer for custom routines - * \todo : due to unused function warning this - * option is disabled + * \todo Check unused function warning */ %option noinput diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index e477ddee8c..5bbca19354 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -42,6 +42,11 @@ #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +/** + * \dir + * \brief Main NMODL code generation program + */ + using namespace fmt::literals; using namespace nmodl; using namespace codegen; diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index 801257c0db..d85aec397c 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -171,7 +171,7 @@ using nmodl::parser::CDriver; /// yylex takes scanner as well as driver reference - /// \todo: check if driver argument is required + /// \todo Check if driver argument is required static CParser::symbol_type yylex(CLexer &scanner, CDriver &/*driver*/) { return scanner.next_token(); } diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 7361b781f0..743e8ae40e 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -163,7 +163,7 @@ std::string DiffEqContext::get_non_cnexp_solution() { /** * Return the solution for differential equation based on method used. * - * \todo: Currently we have tested cnexp, euler and derivimplicit methods with + * \todo Currently we have tested cnexp, euler and derivimplicit methods with * all equations from BBP models. Need to test this against various other mod * files, especially kinetic schemes, reaction-diffusion etc. */ diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 2ec179bacb..3a4cbde0dc 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -29,7 +29,7 @@ struct Term { /// derivative of the expression std::string deriv = "0.0"; - /// \todo : need to check in neuron implementation? + /// \todo Need to check in neuron implementation? std::string a = "0.0"; std::string b = "0.0"; @@ -96,7 +96,7 @@ class DiffEqContext { std::string get_cvode_linear_diffeq(); std::string get_cvode_nonlinear_diffeq(); - /// \todo: methods inherited neuron implementation + /// \todo Methods inherited neuron implementation std::string cvode_deriv(); std::string cvode_eqnrhs(); diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index d55b14c1a6..e20a455d79 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -58,7 +58,7 @@ std::string DiffeqDriver::solve_equation(std::string& state, return context.get_solution(cnexp_possible); } -/// \todo : instead of using neuron like api, we need to refactor +/// \todo Instead of using neuron like api, we need to refactor bool DiffeqDriver::cnexp_possible(const std::string& equation, std::string& solution) { std::string state, rhs; int order = 0; diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index 56ab3ca9e8..a2a6fabf52 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -19,7 +19,7 @@ namespace parser { * of various binary expressions and those are provided below. This * implementation is based on original neuron implementation. * - * \todo : The implementations here are verbose and has duplicate code. + * \todo The implementations here are verbose and has duplicate code. * Need to revisit this, may be using better library like symengine * altogether. */ diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 6d2c2bcf07..1c39cb5825 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -376,7 +376,7 @@ using nmodl::parser::VerbatimDriver; /// yylex takes scanner as well as driver reference - /// \todo: check if driver argument is required + /// \todo Check if driver argument is required static NmodlParser::symbol_type yylex(NmodlLexer &scanner, NmodlDriver &/*driver*/) { return scanner.next_token(); } diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index ebcc287753..6111b5552c 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -7,6 +7,11 @@ #pragma once +/** + * \dir + * \brief Parser implementations + */ + #include <map> #include <string> @@ -19,9 +24,11 @@ namespace nmodl { namespace parser { /** - * @defgroup parser Parser Infrastructure + * @defgroup parser Parser Implementation * @brief All parser and driver classes implementation * + * + * * @addtogroup parser * @{ */ @@ -48,12 +55,12 @@ class NmodlParser; * Parsing actions generate ast and it's pointer is stored in driver * class. * - * \todo lexer, parser and ast member variables are used inside lexer/ + * \todo Lexer, parser and ast member variables are used inside lexer/ * parser instances. The local instaces are created inside parse_stream * and hence the pointers are no longer valid except ast. Need better * way to handle this. * - * \todo stream name is not used as it will need better support as + * \todo Stream name is not used as it will need better support as * location object used in scanner takes string pointer which could * be invalid when we copy location object. */ diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index a7066957f7..ef8b81ce6a 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -85,7 +85,7 @@ using nmodl::parser::UnitDriver; /// yylex takes scanner as well as driver reference - /// \todo: check if driver argument is required + /// \todo Check if driver argument is required static UnitParser::symbol_type yylex(UnitLexer &scanner, UnitDriver &/*driver*/) { return scanner.next_token(); } diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index 4d20ea1d9d..feb58adeaa 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -97,7 +97,7 @@ charlist : { $$ = new std::string(""); } %% -/** \todo : better error handling required */ +/** \todo Better error handling required */ void yyerror(YYLTYPE* /*locp*/, VerbatimDriver* /*context*/, const char *s) { std::printf("\n Error in verbatim parser : %s \n", s); std::exit(1); diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 713f1062d4..2680bc0374 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -38,10 +38,10 @@ using json = nlohmann::json; * construct JSON object from AST like data structures. We use nlohmann's json * library which considerably simplify implementation. * - * \todo We need to explicitly call flush() in order to get write/return - * results. We simply can't dump block in popBlock() because block itself will + * \todo We need to explicitly call `flush()` in order to get write/return + * results. We simply can't dump block in `popBlock()` because block itself will * be part of other parent elements. Also we are writing results to file, - * stringstream and cout. And hence we can't simply reset/clear previously + * `stringstream` and `cout`. And hence we can't simply reset/clear previously * written text. */ class JSONPrinter { diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 1a3bf2ae52..50ad84570c 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -9,7 +9,7 @@ /** * \dir - * \brief Solver implementations + * \brief Newton solver implementations * * \file * \brief Implementation of Newton method for solving system of non-linear equations @@ -22,7 +22,7 @@ namespace nmodl { namespace newton { /** - * @defgroup solver Solver Infrastructure + * @defgroup solver Solver Implementation * @brief Solver implementation details * * Implementation of Newton method for solving system of non-linear equations using Eigen diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 92cb06b8bd..da168ac2f3 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -264,7 +264,7 @@ class Symbol { * External variables are the variables that are defined in NEURON * and available in mod file. * - * \todo: Need to check if we should check two properties using + * \todo Need to check if we should check two properties using * has_any_property instead of exact comparison * * \sa nmodl::details::NEURON_VARIABLES diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 7774dd6767..c684aca5aa 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -26,7 +26,7 @@ namespace symtab { /** - * @defgroup sym_tab Symbol Table Infrastructure + * @defgroup sym_tab Symbol Table Implementation * @brief All %Symbol Table implementation details * * @{ @@ -172,7 +172,7 @@ class SymbolTable { /** * Create a copy of symbol table - * \todo revisit the usage as tokens will be pointing to old nodes + * \todo Revisit the usage as tokens will be pointing to old nodes */ SymbolTable* clone() { return new SymbolTable(*this); diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 3da2bf43dc..08249d0d43 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -35,7 +35,7 @@ namespace nmodl { namespace units { /** - * @defgroup units Unit Infrastructure + * @defgroup units Unit Implementation * @brief Units handling implementation details * @{ */ diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index e187a4612e..f6ccf89c61 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -21,7 +21,7 @@ namespace nmodl { namespace utils { /** - * @defgroup utils Utility Infrastructure + * @defgroup utils Utility Implementation * @brief Utility classes and function implementation * @{ */ diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index 956e58cf38..42a55ee9b1 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -8,6 +8,10 @@ #pragma once /** + * + * \dir + * \brief Visitors implementation + * * \file * \brief \copybrief nmodl::visitor::ConstantFolderVisitor */ diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 9ba16c53ba..f9d783d951 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -261,7 +261,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement* node) { /** We are not analyzing verbatim blocks yet and hence if there is * a verbatim block we assume there is variable usage. * - * \todo: one simple way would be to look for p_name in the string + * \todo One simple way would be to look for p_name in the string * of verbatim block to find the variable usage. */ void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim* node) { diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 048e40b15c..be06914245 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -53,7 +53,7 @@ void InlineVisitor::add_return_variable(StatementBlock* block, std::string& varn * - if expression is wrapped expression * - if wrapped expression is a function call * - * \todo: add method to ast itself to simplify this implementation + * \todo Add method to ast itself to simplify this implementation */ bool InlineVisitor::can_replace_statement(const std::shared_ptr<Statement>& statement) { if (!statement->is_expression_statement()) { @@ -161,7 +161,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, RenameVisitor visitor(function_name, new_varname); inlined_block->visit_children(&visitor); - /// \todo: have to re-run symtab visitor pass to update symbol table + /// \todo Have to re-run symtab visitor pass to update symbol table inlined_block->set_symbol_table(nullptr); /// each argument is added as new assignment statement diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index f65ee8ae87..50128bf709 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -30,7 +30,7 @@ namespace visitor { * \class VarUsageVisitor * \brief Check if variable is used in given block * - * \todo check if macro is considered as variable + * \todo Check if macro is considered as variable */ class VarUsageVisitor: public AstVisitor { diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index d5143b485e..5e5860fe2b 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -77,7 +77,7 @@ LocalVar* add_local_variable(StatementBlock* node, const std::string& varname, i * parse it using NMODL parser. As there will be only one block with single * statement, we return first statement. * - * \todo : Need to revisit this during code generation passes to make sure + * \todo Need to revisit this during code generation passes to make sure * if all statements can be part of procedure block. */ std::shared_ptr<Statement> create_statement(const std::string& code_statement) { diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index ab64a26da1..9b7edc157c 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -73,6 +73,8 @@ foreach(test_name testunitlexer testunitparser) catch_discover_tests(${test_name} + TEST_PREFIX + "${test_name}/" PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) From 6f699b4f808745d66f0087e0332774a79cd5ef60 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Thu, 2 May 2019 11:32:40 +0200 Subject: [PATCH 199/871] Update Bison legacy productions naming scheme (BlueBrain/nmodl#157) - nmodl.yy was derived from parse1.y of NEURON and production names were not updated (for debugging/testing purpose). Change naming scheme to be more understandable. - Rename some AST nodes - Dependent to Assigned - dependent_def to dependent_definition - BbcorePtr to BbcorePointer - Minor updates docs/conf.py NMODL Repo SHA: BlueBrain/nmodl@97b9c3a2a65e437be05ec3d9bd794b6fa82ce5b1 --- docs/nmodl/transpiler/conf.py | 12 +- docs/nmodl/transpiler/contents/visitors.rst | 2 +- docs/nmodl/transpiler/index.rst | 4 +- .../notebooks/nmodl-python-tutorial.ipynb | 50 +- src/nmodl/ast/ast_common.hpp | 24 +- src/nmodl/codegen/codegen_c_visitor.cpp | 18 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 34 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 2 +- src/nmodl/codegen/codegen_info.hpp | 10 +- src/nmodl/language/nmodl.yaml | 136 +- src/nmodl/language/node_info.py | 8 +- .../language/templates/pybind/pysymtab.cpp | 4 +- src/nmodl/lexer/nmodl_utils.cpp | 4 +- src/nmodl/lexer/token_mapping.cpp | 2 +- src/nmodl/parser/nmodl.yy | 1506 ++++++++++------- src/nmodl/symtab/symbol.cpp | 2 +- src/nmodl/symtab/symbol_properties.cpp | 8 +- src/nmodl/symtab/symbol_properties.hpp | 8 +- src/nmodl/symtab/symbol_table.cpp | 4 +- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 5 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 6 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 2 +- test/nmodl/transpiler/pybind/test_symtab.py | 2 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 9 +- test/nmodl/transpiler/visitor/perf.cpp | 2 +- 27 files changed, 1144 insertions(+), 724 deletions(-) diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index 7d142eb08f..f21207012e 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -101,7 +101,7 @@ master_doc = "index" # General information about the project. -project = "nmodl" +project = "NMODL" copyright = "2019, BlueBrain HPC team" author = "BlueBrain HPC team" @@ -199,7 +199,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [(master_doc, "nmodl", "nmodl Documentation", [author], 1)] +man_pages = [(master_doc, "NMODL", "NMODL Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -210,11 +210,11 @@ texinfo_documents = [ ( master_doc, - "nmodl", - "nmodl Documentation", + "NMODL", + "NMODL Documentation", author, - "nmodl", - "One line description of project.", + "NMODL", + "Code Generation Framework For NEURON MODeling Language ", "Miscellaneous", ) ] diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index cbcd5cc596..fe6d38fcf3 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -103,7 +103,7 @@ Now we can query for variables in the symbol table based on name of variable: >>> cai = table.lookup('cai') >>> print (cai) - cai [Properties : prime_name dependent_def write_ion state_var] + cai [Properties : prime_name assigned_definition write_ion state_var] Custom AST Visitor diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 063a9ef719..b8d68b1981 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -3,8 +3,8 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -Welcome to nmodl's documentation! -================================= +About NMODL +=========== .. toctree:: :maxdepth: 2 diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index 0332530e2a..22837cd671 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -524,7 +524,7 @@ "data": { "application/javascript": [ "(function(element){\n", - " require(['draw_tree'], function(draw) { draw(element.get(0), {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"SUFFIX\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"CaDynamics\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"Suffix\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ca\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"ReadIonVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"WriteIonVar\"}], \"name\": \"Useion\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}], \"name\": \"Range\"}], \"name\": \"StatementBlock\"}], \"name\": \"NeuronBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"mV\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millivolt\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mA\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"milliamp\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"faraday\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"coulombs\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"FactorDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"molar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"1/liter\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millimolar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"micron\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}], \"name\": \"UnitBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.05\"}], \"name\": \"Double\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"80\"}], \"name\": \"Integer\"}, {\"children\": [{\"children\": [{\"name\": \"ms\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.1\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.0001\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}], \"name\": \"ParamBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mA/cm2\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"DependentBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"InitialBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"DependentDef\"}], \"name\": \"StateBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"cnexp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"SolveBlock\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"BreakpointBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}, {\"children\": [{\"name\": \"1\"}], \"name\": \"Integer\"}], \"name\": \"PrimeName\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"-\"}], \"name\": \"UnaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"10000\"}], \"name\": \"Double\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"UnaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"2\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"DiffEqExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"DerivativeBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"temp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"LocalVar\"}], \"name\": \"LocalListStatement\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"1\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"+\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"FunctionBlock\"}], \"name\": \"Program\"}) });\n", + " require(['draw_tree'], function(draw) { draw(element.get(0), {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"SUFFIX\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"CaDynamics\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"Suffix\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ca\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"ReadIonVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"WriteIonVar\"}], \"name\": \"Useion\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}], \"name\": \"Range\"}], \"name\": \"StatementBlock\"}], \"name\": \"NeuronBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"mV\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millivolt\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mA\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"milliamp\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"faraday\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"coulombs\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"FactorDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"molar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"1/liter\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millimolar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"micron\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}], \"name\": \"UnitBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.05\"}], \"name\": \"Double\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"80\"}], \"name\": \"Integer\"}, {\"children\": [{\"children\": [{\"name\": \"ms\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.1\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.0001\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}], \"name\": \"ParamBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mA/cm2\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"AssignedDefinition\"}], \"name\": \"AssignedBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"InitialBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"AssignedDefinition\"}], \"name\": \"StateBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"cnexp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"SolveBlock\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"BreakpointBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}, {\"children\": [{\"name\": \"1\"}], \"name\": \"Integer\"}], \"name\": \"PrimeName\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"-\"}], \"name\": \"UnaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"10000\"}], \"name\": \"Double\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"UnaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"2\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"DiffEqExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"DerivativeBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"temp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"LocalVar\"}], \"name\": \"LocalListStatement\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"1\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"+\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"FunctionBlock\"}], \"name\": \"Program\"}) });\n", " })(element);" ], "text/plain": [ @@ -744,27 +744,27 @@ "output_type": "stream", "text": [ "\n", - "------------------------------------------------------------------------------------------------------------------------------\n", - "| NMODL_GLOBAL [Program IN None] POSITION : UNKNOWN SCOPE : GLOBAL |\n", - "------------------------------------------------------------------------------------------------------------------------------\n", - "| NAME | PROPERTIES | STATUS | LOCATION | VALUE | # READS | # WRITES | \n", - "------------------------------------------------------------------------------------------------------------------------------\n", - "| ca | ion | | UNKNOWN | | 0 | 0 | \n", - "| ica | dependent_def read_ion | | UNKNOWN | | 0 | 0 | \n", - "| cai | prime_name dependent_def write_ion state_var | | UNKNOWN | | 0 | 0 | \n", - "| decay | range parameter | | UNKNOWN | 80.000000 | 0 | 0 | \n", - "| gamma | range parameter | | UNKNOWN | 0.050000 | 0 | 0 | \n", - "| minCai | range parameter | | UNKNOWN | 0.000100 | 0 | 0 | \n", - "| depth | range parameter | | UNKNOWN | 0.100000 | 0 | 0 | \n", - "| mV | unit_def | | UNKNOWN | | 0 | 0 | \n", - "| mA | unit_def | | UNKNOWN | | 0 | 0 | \n", - "| FARADAY | factor_def | | 11.5-11 | | 0 | 0 | \n", - "| molar | unit_def | | UNKNOWN | | 0 | 0 | \n", - "| mM | unit_def | | UNKNOWN | | 0 | 0 | \n", - "| um | unit_def | | UNKNOWN | | 0 | 0 | \n", - "| states | derivative_block to_solve | | 36.1-10 | | 0 | 0 | \n", - "| foo | function_block | | 40.1-8 | | 0 | 0 | \n", - "------------------------------------------------------------------------------------------------------------------------------\n", + "------------------------------------------------------------------------------------------------------------------------------------\n", + "| NMODL_GLOBAL [Program IN None] POSITION : UNKNOWN SCOPE : GLOBAL |\n", + "------------------------------------------------------------------------------------------------------------------------------------\n", + "| NAME | PROPERTIES | STATUS | LOCATION | VALUE | # READS | # WRITES | \n", + "------------------------------------------------------------------------------------------------------------------------------------\n", + "| ca | ion | | UNKNOWN | | 0 | 0 | \n", + "| ica | assigned_definition read_ion | | UNKNOWN | | 0 | 0 | \n", + "| cai | prime_name assigned_definition write_ion state_var | | UNKNOWN | | 0 | 0 | \n", + "| decay | range parameter | | UNKNOWN | 80.000000 | 0 | 0 | \n", + "| gamma | range parameter | | UNKNOWN | 0.050000 | 0 | 0 | \n", + "| minCai | range parameter | | UNKNOWN | 0.000100 | 0 | 0 | \n", + "| depth | range parameter | | UNKNOWN | 0.100000 | 0 | 0 | \n", + "| mV | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| mA | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| FARADAY | factor_def | | 11.5-11 | | 0 | 0 | \n", + "| molar | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| mM | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| um | unit_def | | UNKNOWN | | 0 | 0 | \n", + "| states | derivative_block to_solve | | 36.1-10 | | 0 | 0 | \n", + "| foo | function_block | | 40.1-8 | | 0 | 0 | \n", + "------------------------------------------------------------------------------------------------------------------------------------\n", "\n", " --------------------------------------------------------------------------------------------------\n", " | StatementBlock4 [StatementBlock IN foo] POSITION : 40.16 SCOPE : LOCAL |\n", @@ -801,7 +801,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "cai [Properties : prime_name dependent_def write_ion state_var]\n" + "cai [Properties : prime_name assigned_definition write_ion state_var]\n" ] } ], @@ -861,8 +861,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "ica [Properties : dependent_def read_ion]\n", - "cai [Properties : prime_name dependent_def write_ion state_var]\n" + "ica [Properties : assigned_definition read_ion]\n", + "cai [Properties : prime_name assigned_definition write_ion state_var]\n" ] } ], diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 9a7d9b7477..0fcdfc699a 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -611,10 +611,10 @@ struct Ast: public std::enable_shared_from_this<Ast> { } /** - *\brief Check if the ast node is an instance of ast::DependentBlock - * @return true if object of type ast::DependentBlock + *\brief Check if the ast node is an instance of ast::AssignedBlock + * @return true if object of type ast::AssignedBlock */ - virtual bool is_dependent_block() { + virtual bool is_assigned_block() { return false; } @@ -1091,18 +1091,18 @@ struct Ast: public std::enable_shared_from_this<Ast> { } /** - *\brief Check if the ast node is an instance of ast::IndependentDef - * @return true if object of type ast::IndependentDef + *\brief Check if the ast node is an instance of ast::IndependentDefinition + * @return true if object of type ast::IndependentDefinition */ - virtual bool is_independent_def() { + virtual bool is_independent_definition() { return false; } /** - *\brief Check if the ast node is an instance of ast::DependentDef - * @return true if object of type ast::DependentDef + *\brief Check if the ast node is an instance of ast::AssignedDefinition + * @return true if object of type ast::AssignedDefinition */ - virtual bool is_dependent_def() { + virtual bool is_assigned_definition() { return false; } @@ -1371,10 +1371,10 @@ struct Ast: public std::enable_shared_from_this<Ast> { } /** - *\brief Check if the ast node is an instance of ast::BbcorePtr - * @return true if object of type ast::BbcorePtr + *\brief Check if the ast node is an instance of ast::BbcorePointer + * @return true if object of type ast::BbcorePointer */ - virtual bool is_bbcore_ptr() { + virtual bool is_bbcore_pointer() { return false; } diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 0def68807d..038270a265 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -533,9 +533,9 @@ int CodegenCVisitor::float_variables_size() { }; int float_size = count_length(info.range_parameter_vars); - float_size += count_length(info.range_dependent_vars); + float_size += count_length(info.range_assigned_vars); float_size += count_length(info.state_vars); - float_size += count_length(info.dependent_vars); + float_size += count_length(info.assigned_vars); /// for state variables we add Dstate variables float_size += info.state_vars.size(); @@ -812,7 +812,7 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { return first->get_definition_order() < second->get_definition_order(); }; - auto dependents = info.dependent_vars; + auto assigned = info.assigned_vars; auto states = info.state_vars; states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); @@ -821,16 +821,16 @@ std::vector<SymbolType> CodegenCVisitor::get_float_variables() { auto name = "D" + variable->get_name(); auto symbol = make_symbol(name); symbol->set_definition_order(variable->get_definition_order()); - dependents.push_back(symbol); + assigned.push_back(symbol); } - std::sort(dependents.begin(), dependents.end(), comparator); + std::sort(assigned.begin(), assigned.end(), comparator); auto variables = info.range_parameter_vars; variables.insert(variables.end(), - info.range_dependent_vars.begin(), - info.range_dependent_vars.end()); + info.range_assigned_vars.begin(), + info.range_assigned_vars.end()); variables.insert(variables.end(), info.state_vars.begin(), info.state_vars.end()); - variables.insert(variables.end(), dependents.begin(), dependents.end()); + variables.insert(variables.end(), assigned.begin(), assigned.end()); if (info.vectorize) { variables.push_back(make_symbol(naming::VOLTAGE_UNUSED_VARIABLE)); @@ -2448,7 +2448,7 @@ void CodegenCVisitor::print_mechanism_info() { printer->add_line(add_escape_quote(info.mod_suffix) + ","); variable_printer(info.range_parameter_vars); printer->add_line("0,"); - variable_printer(info.range_dependent_vars); + variable_printer(info.range_assigned_vars); printer->add_line("0,"); variable_printer(info.state_vars); printer->add_line("0,"); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index f76033894a..652dba1d13 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -189,7 +189,7 @@ void CodegenHelperVisitor::find_non_range_variables() { // clang-format off auto with = NmodlType::param_assign; auto without = NmodlType::range_var - | NmodlType::dependent_def + | NmodlType::assigned_definition | NmodlType::global_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var @@ -273,7 +273,7 @@ void CodegenHelperVisitor::find_non_range_variables() { // find special variables like diam, area // clang-format off - properties = NmodlType::dependent_def + properties = NmodlType::assigned_definition | NmodlType::param_assign; vars = psymtab->get_variables_with_properties(properties); for (auto& var : vars) { @@ -318,25 +318,25 @@ void CodegenHelperVisitor::find_range_variables() { std::sort(info.range_parameter_vars.begin(), info.range_parameter_vars.end(), comparator); /** - * Second come dependent variables which are range variables. + * Second come assigned variables which are range variables. */ // clang-format off with = NmodlType::range_var - | NmodlType::dependent_def; + | NmodlType::assigned_definition; without = NmodlType::global_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var | NmodlType::state_var | NmodlType::param_assign; // clang-format on - info.range_dependent_vars = psymtab->get_variables(with, without); - std::sort(info.range_dependent_vars.begin(), info.range_dependent_vars.end(), comparator); + info.range_assigned_vars = psymtab->get_variables(with, without); + std::sort(info.range_assigned_vars.begin(), info.range_assigned_vars.end(), comparator); /** * Third come state variables. All state variables are kind of range by default. * Note that some mod files like CaDynamics_E2.mod use cai as state variable * and those are not considered as range+state variables while printing instance - * variables. Such read/write ion variables are dependent variables and hence they + * variables. Such read/write ion variables are assigned variables and hence they * will be printed at laster stage. * \todo Need to validate with more models and mod2c details. */ @@ -353,16 +353,16 @@ void CodegenHelperVisitor::find_range_variables() { /** * Remaining variables are: - * - all dependent variables without range - * - read ion variables which appear in parameter or dependent block + * - all assigned variables without range + * - read ion variables which appear in parameter or assigned block * - state variables which are not range but with ion variable of read/write type */ /** - * first get dependent definition without read ion variables + * first get assigned definition without read ion variables */ // clang-format off - with = NmodlType::dependent_def; + with = NmodlType::assigned_definition; without = NmodlType::global_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var @@ -371,7 +371,7 @@ void CodegenHelperVisitor::find_range_variables() { | NmodlType::extern_neuron_variable | NmodlType::read_ion_var; // clang-format on - info.dependent_vars = psymtab->get_variables(with, without); + info.assigned_vars = psymtab->get_variables(with, without); /** * Now just use read-ion variables because every read-ion variable @@ -388,7 +388,7 @@ void CodegenHelperVisitor::find_range_variables() { | NmodlType::extern_neuron_variable; // clang-format on auto variables = psymtab->get_variables(with, without); - info.dependent_vars.insert(info.dependent_vars.end(), variables.begin(), variables.end()); + info.assigned_vars.insert(info.assigned_vars.end(), variables.begin(), variables.end()); /* * We want to have state variables which are read or write ion variables. @@ -411,7 +411,7 @@ void CodegenHelperVisitor::find_range_variables() { // clang-format on if (variable->has_any_property(properties)) { info.ion_state_vars.push_back(variable); - info.dependent_vars.push_back(variable); + info.assigned_vars.push_back(variable); } } } @@ -420,8 +420,8 @@ void CodegenHelperVisitor::find_range_variables() { void CodegenHelperVisitor::find_table_variables() { auto property = NmodlType::table_statement_var; info.table_statement_variables = psymtab->get_variables_with_properties(property); - property = NmodlType::table_dependent_var; - info.table_dependent_variables = psymtab->get_variables_with_properties(property); + property = NmodlType::table_assigned_var; + info.table_assigned_variables = psymtab->get_variables_with_properties(property); } @@ -587,7 +587,7 @@ void CodegenHelperVisitor::visit_binary_expression(BinaryExpression* node) { } -void CodegenHelperVisitor::visit_bbcore_ptr(BbcorePtr* node) { +void CodegenHelperVisitor::visit_bbcore_pointer(BbcorePointer* node) { info.bbcore_pointer_used = true; } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 6f850f8e31..c76e14511a 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -95,7 +95,7 @@ class CodegenHelperVisitor: public visitor::AstVisitor { void visit_derivative_block(ast::DerivativeBlock* node) override; void visit_derivimplicit_callback(ast::DerivimplicitCallback* node) override; void visit_net_receive_block(ast::NetReceiveBlock* node) override; - void visit_bbcore_ptr(ast::BbcorePtr* node) override; + void visit_bbcore_pointer(ast::BbcorePointer* node) override; void visit_watch(ast::Watch* node) override; void visit_watch_statement(ast::WatchStatement* node) override; void visit_for_netcon(ast::ForNetcon* node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index a62c485106..01eb9b11a2 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -275,11 +275,11 @@ struct CodegenInfo { /// range variables which are parameter as well std::vector<SymbolType> range_parameter_vars; - /// range variables which are dependent variables as well - std::vector<SymbolType> range_dependent_vars; + /// range variables which are assigned variables as well + std::vector<SymbolType> range_assigned_vars; - /// reamining dependent variables - std::vector<SymbolType> dependent_vars; + /// reamining assigned variables + std::vector<SymbolType> assigned_vars; /// state variables std::vector<SymbolType> state_vars; @@ -318,7 +318,7 @@ struct CodegenInfo { /// table variables std::vector<SymbolType> table_statement_variables; - std::vector<SymbolType> table_dependent_variables; + std::vector<SymbolType> table_assigned_variables; /// function or procedures with table statement std::vector<ast::Block*> functions_with_table; diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 191362e30e..deebbcc0a7 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -86,13 +86,17 @@ description: | Ast is a top level, abstract base class defining the interface for all nodes of Abstract Syntax Tree (AST). + children: + - Node: brief: "Base class for all AST node" description: | Base class for all nodes in the AST. This can replace ast::Ast in the next refactoring. + children: + - Expression: brief: "Base class for all expressions in the NMODL" description: | @@ -101,7 +105,9 @@ can be a variable itself or complex binary expressions. \sa ast::Statement + children: + - String: members: - value: @@ -123,11 +129,14 @@ *x = ... ENDVERBATIM \endcode + - Number: brief: "Base class for all numbers" description: | Base class for all number types like ast::Integer, ast::Float and ast::Double. + children: + - Integer: members: - value: @@ -150,6 +159,7 @@ \endcode \sa ast::Float ast::Double + - Float: members: - value: @@ -163,6 +173,7 @@ this type is note used. This will be changed soon for variables like ast::ParamAssign. \sa ast::Integer ast::Double + - Double: members: - value: @@ -185,6 +196,7 @@ decision about variable types is done by code generation backends. \sa ast::Integer ast::Float + - Boolean: members: - value: @@ -196,13 +208,16 @@ \note Currently this type is used as only flag in some of the AST nodes. Similar to ast::Float, this type was introduced for data type specific code generation support in the future. + - Identifier: brief: "Base class for all identifiers" description: | Base class for all variable types like ast::Name, ast::PrimeName and ast::Argument. \sa ast::Number + children: + - Name: members: - value: @@ -217,6 +232,7 @@ (e.g. ast::GlobalVar, ast::RangeVar) have underlying value stored as ast::Name. \note This node should be able to use std::string as value type instead of ast::String + - PrimeName: members: - value: @@ -262,6 +278,7 @@ m[4] } \endcode + - VarName: members: - name: @@ -286,6 +303,7 @@ \note With ast::Identifier as top level base class, this type can be removed in the future refactoring. + - Argument: members: - name: @@ -308,6 +326,7 @@ g = g + weight } \endcode + - ReactVarName: brief: "TODO" members: @@ -320,6 +339,7 @@ brief: "TODO" type: VarName node_name: true + - ReadIonVar: brief: "TODO" members: @@ -327,6 +347,7 @@ brief: "TODO" type: Name node_name: true + - WriteIonVar: brief: "TODO" members: @@ -334,6 +355,7 @@ brief: "TODO" type: Name node_name: true + - NonspecificCurVar: brief: "TODO" members: @@ -341,6 +363,7 @@ brief: "TODO" type: Name node_name: true + - ElectrodeCurVar: brief: "TODO" members: @@ -348,6 +371,7 @@ brief: "TODO" type: Name node_name: true + - SectionVar: brief: "TODO" members: @@ -355,6 +379,7 @@ brief: "TODO" type: Name node_name: true + - RangeVar: brief: "TODO" members: @@ -362,6 +387,7 @@ brief: "TODO" type: Name node_name: true + - GlobalVar: brief: "TODO" members: @@ -369,6 +395,7 @@ brief: "TODO" type: Name node_name: true + - PointerVar: brief: "TODO" members: @@ -376,6 +403,7 @@ brief: "TODO" type: Name node_name: true + - BbcorePointerVar: members: - name: @@ -384,7 +412,8 @@ node_name: true brief: "Represent a single variable of type BBCOREPOINTER" description: | - See ast::BbcorePtr for an example. + See ast::BbcorePointer for an example. + - ExternVar: brief: "TODO" members: @@ -392,6 +421,7 @@ brief: "TODO" type: Name node_name: true + - ThreadsafeVar: brief: "TODO" members: @@ -399,13 +429,16 @@ brief: "TODO" type: Name node_name: true + - Block: brief: "Base class for all block scoped nodes" description: | NMODL has different local and global block scoped nodes like ast::NeuronBlock, ast::ParamBlock, ast::IfStatement etc. Ast::Block is base class and defines common interface for these nodes. + children: + - ParamBlock: nmodl: "PARAMETER " members: @@ -449,12 +482,13 @@ \endcode \todo Check ModelDB and other databse for example of channel. + - IndependentBlock: nmodl: "INDEPENDENT " members: - definitions: brief: "TODO" - type: IndependentDef + type: IndependentDefinition vector: true brief: "Represents a `INDEPENDENT` block in the NMODL" description: | @@ -466,12 +500,12 @@ } \endcode - - DependentBlock: + - AssignedBlock: nmodl: "ASSIGNED " members: - definitions: brief: "Vector of assigned variables" - type: DependentDef + type: AssignedDefinition vector: true brief: "Represents a `ASSIGNED` block in the NMODL" description: | @@ -496,7 +530,7 @@ members: - definitions: brief: "Vector of state variables" - type: DependentDef + type: AssignedDefinition vector: true brief: "Represents a `STATE` block in the NMODL" description: | @@ -511,8 +545,9 @@ } \endcode - Note that the state variable specification has form of ast::DependentDef and + Note that the state variable specification has form of ast::AssignedDefinition and hence can have associated unit specification. + - PlotBlock: members: - plot: @@ -597,6 +632,7 @@ \endcode \sa ast::ConstructorBlock + - StatementBlock: members: - statements: @@ -621,6 +657,7 @@ Note that the statement blocks can be nested where inner block will be wrapped as statement with ast::ExpressionStatement. + - DerivativeBlock: nmodl: "DERIVATIVE " members: @@ -727,6 +764,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - PartialBlock: brief: "TODO" nmodl: "PARTIAL " @@ -740,6 +778,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - FunctionTableBlock: brief: "TODO" nmodl: "FUNCTION_TABLE " @@ -761,6 +800,7 @@ type: Unit optional: true prefix: {value: " "} + - FunctionBlock: brief: "TODO" nmodl: "FUNCTION " @@ -787,6 +827,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - ProcedureBlock: brief: "TODO" nmodl: "PROCEDURE " @@ -811,6 +852,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - NetReceiveBlock: brief: "TODO" nmodl: "NET_RECEIVE " @@ -827,6 +869,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - SolveBlock: brief: "TODO" nmodl: SOLVE @@ -850,6 +893,7 @@ type: StatementBlock optional: true prefix: {value: " IFERROR "} + - BreakpointBlock: nmodl: "BREAKPOINT " members: @@ -874,6 +918,7 @@ \endcode \sa ast::DerivativeBlock ast::InitialBlock + - TerminalBlock: brief: "TODO" nmodl: "TERMINAL " @@ -882,6 +927,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - BeforeBlock: nmodl: "BEFORE " members: @@ -889,6 +935,7 @@ brief: "Block to be called before" type: BABlock brief: "Represents a `BEFORE` block in NMODL" + - AfterBlock: nmodl: "AFTER " members: @@ -910,6 +957,7 @@ } } \endcode + - BABlock: members: - type: @@ -924,6 +972,7 @@ description: | This represents a block to be executed before or after another block in NMODL. See ast::BeforeBlock and ast::AfterBlock for usage. + - ForNetcon: brief: "TODO" nmodl: "FOR_NETCONS " @@ -940,6 +989,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - KineticBlock: brief: "TODO" nmodl: "KINETIC " @@ -958,6 +1008,7 @@ brief: "Block with statements vector" type: StatementBlock getter: {override: true} + - MatchBlock: brief: "TODO" nmodl: MATCH @@ -969,6 +1020,7 @@ separator: " " prefix: {value: " { "} suffix: {value: " }"} + - UnitBlock: brief: "TODO" nmodl: "UNITS " @@ -977,6 +1029,7 @@ brief: "Vector of unit statements" type: Expression vector: true + - ConstantBlock: nmodl: "CONSTANT " members: @@ -997,6 +1050,7 @@ ctm = 0.000505 (s) } \endcode + - NeuronBlock: nmodl: "NEURON " members: @@ -1023,6 +1077,7 @@ \endcode url: "https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl2.html#neuron" + - Unit: brief: "TODO" members: @@ -1032,6 +1087,7 @@ node_name: true prefix: {value: "("} suffix: {value: ")"} + - DoubleUnit: brief: "TODO" members: @@ -1042,6 +1098,7 @@ brief: "TODO" type: Unit optional: true + - LocalVar: brief: "TODO" members: @@ -1049,6 +1106,7 @@ brief: "TODO" type: Identifier node_name: true + - Limits: brief: "TODO" members: @@ -1061,6 +1119,7 @@ brief: "TODO" type: Double suffix: {value: ">"} + - NumberRange: brief: "TODO" members: @@ -1073,6 +1132,7 @@ brief: "TODO" type: Number suffix: {value: ">"} + - PlotVar: brief: "TODO" members: @@ -1085,6 +1145,7 @@ optional: true prefix: {value: "["} suffix: {value: "]"} + - ConstantVar: members: - name: @@ -1108,18 +1169,21 @@ brief: "Opearator" type: BinaryOp brief: "Operator used in ast::BinaryExpression" + - UnaryOperator: brief: "TODO" members: - value: brief: "TODO" type: UnaryOp + - ReactionOperator: brief: "TODO" members: - value: brief: "TODO" type: ReactionOp + - ParenExpression: brief: "TODO" members: @@ -1128,6 +1192,7 @@ type: Expression prefix: {value: "("} suffix: {value: ")"} + - BinaryExpression: members: - lhs: @@ -1165,6 +1230,7 @@ brief: "Differential Expression" type: BinaryExpression public: true + - UnaryExpression: brief: "TODO" members: @@ -1174,6 +1240,7 @@ - expression: brief: "TODO" type: Expression + - NonLinEquation: brief: "TODO" nmodl: "~ " @@ -1185,6 +1252,7 @@ - rhs: brief: "TODO" type: Expression + - LinEquation: brief: "TODO" nmodl: "~ " @@ -1196,6 +1264,7 @@ - linxpression: brief: "TODO" type: Expression + - FunctionCall: brief: "TODO" members: @@ -1210,12 +1279,14 @@ separator: ", " prefix: {value: "(", force: true} suffix: {value: ")", force: true} + - FirstLastTypeIndex: brief: "TODO" members: - value: brief: "TODO" type: FirstLastType + - Watch: brief: "TODO" members: @@ -1228,6 +1299,7 @@ brief: "TODO" type: Expression prefix: {value: " "} + - QueueExpressionType: brief: "TODO" members: @@ -1314,6 +1386,7 @@ - value: brief: "TODO" type: UnitStateType + - LocalListStatement: brief: "TODO" nmodl: "LOCAL " @@ -1324,6 +1397,7 @@ vector: true separator: ", " public: true + - Model: brief: "TODO" nmodl: TITLE @@ -1331,6 +1405,7 @@ - title: brief: "TODO" type: String + - Define: nmodl: "DEFINE " members: @@ -1343,6 +1418,7 @@ type: Integer prefix: {value: " "} brief: "Represents a `DEFINE` statement in NMODL" + - Include: brief: "TODO" nmodl: "INCLUDE " @@ -1350,6 +1426,7 @@ - filename: brief: "TODO" type: String + - ParamAssign: brief: "TODO" members: @@ -1372,6 +1449,7 @@ type: Limits optional: true prefix: {value: " "} + - Stepped: brief: "TODO" members: @@ -1389,7 +1467,8 @@ type: Unit optional: true prefix: {value: " "} - - IndependentDef: + + - IndependentDefinition: brief: "TODO" members: - sweep: @@ -1422,7 +1501,8 @@ type: Unit optional: true prefix: {value: " "} - - DependentDef: + + - AssignedDefinition: members: - name: brief: "Name of the variable" @@ -1461,6 +1541,7 @@ suffix: {value: ">"} optional: true brief: "Represents a statement in `ASSIGNED` or `STATE` block" + - PlotDeclaration: brief: "TODO" nmodl: "PLOT " @@ -1474,6 +1555,7 @@ brief: "TODO" type: PlotVar prefix: {value: " VS "} + - ConductanceHint: nmodl: "CONDUCTANCE " members: @@ -1502,6 +1584,7 @@ - expression: brief: "TODO" type: Expression + - ProtectStatement: brief: "TODO" nmodl: "PROTECT " @@ -1509,6 +1592,7 @@ - expression: brief: "TODO" type: Expression + - FromStatement: brief: "TODO" nmodl: "FROM " @@ -1535,6 +1619,7 @@ brief: "TODO" type: StatementBlock getter: {override: true} + - ForAllStatement: brief: "TODO" nmodl: "FORALL " @@ -1547,6 +1632,7 @@ brief: "TODO" type: StatementBlock getter: {override: true} + - WhileStatement: brief: "TODO" nmodl: "WHILE " @@ -1560,6 +1646,7 @@ brief: "TODO" type: StatementBlock getter: {override: true} + - IfStatement: brief: "TODO" nmodl: "IF " @@ -1581,6 +1668,7 @@ brief: "TODO" type: ElseStatement optional: true + - ElseIfStatement: brief: "TODO" nmodl: " ELSE IF " @@ -1594,6 +1682,7 @@ brief: "TODO" type: StatementBlock getter: {override: true} + - ElseStatement: brief: "TODO" nmodl: " ELSE " @@ -1602,6 +1691,7 @@ brief: "TODO" type: StatementBlock getter: {override: true} + - PartialEquation: brief: "TODO" members: @@ -1617,6 +1707,7 @@ - name3: brief: "TODO" type: Name + - PartialBoundary: brief: "TODO" nmodl: "~ " @@ -1661,6 +1752,7 @@ type: Name optional: true prefix: {value: "+"} + - WatchStatement: nmodl: "WATCH " members: @@ -1671,15 +1763,19 @@ separator: "," add: true brief: "Represent WATCH statement in NMODL" + - MutexLock: nmodl: MUTEXLOCK brief: "Represent MUTEXLOCK statement in NMODL" + - MutexUnlock: nmodl: MUTEXUNLOCK brief: "Represent MUTEXUNLOCK statement in NMODL" + - Reset: nmodl: RESET brief: "Represent RESET statement in NMODL" + - Sens: nmodl: "SENS " members: @@ -1689,6 +1785,7 @@ vector: true separator: ", " brief: "Represent SENS statement in NMODL" + - Conserve: nmodl: CONSERVE members: @@ -1701,6 +1798,7 @@ type: Expression prefix: {value: " = "} brief: "Represent CONSERVE statement in NMODL" + - Compartment: nmodl: COMPARTMENT members: @@ -1722,6 +1820,7 @@ suffix: {value: "}"} separator: " " brief: "Represent COMPARTMENT statement in NMODL" + - LonDifuse: nmodl: LONGITUDINAL_DIFFUSION members: @@ -1743,6 +1842,7 @@ suffix: {value: "}"} separator: " " brief: "Represent LONGITUDINAL_DIFFUSION statement in NMODL" + - ReactionStatement: brief: "TODO" nmodl: "~ " @@ -1769,6 +1869,7 @@ prefix: {value: ", "} suffix: {value: ")", force: true} optional: true + - LagStatement: nmodl: "LAG " members: @@ -1790,6 +1891,7 @@ if (ena < 70) {ena = 70} } \endcode + - QueueStatement: members: - qtype: @@ -1800,6 +1902,7 @@ type: Identifier prefix: {value: " "} brief: "Represent queue statement in NMODL" + - ConstantStatement: members: - constant: @@ -1809,6 +1912,7 @@ description: | \todo As ConstantStatement wraps a single ConstantVar, this or ast::ConstantVar can be redundant in the future. + - TableStatement: nmodl: "TABLE " members: @@ -1836,6 +1940,7 @@ type: Integer prefix: {value: " WITH "} brief: "Represents TABLE statement in NMODL" + - Suffix: members: - type: @@ -1847,6 +1952,7 @@ type: Name node_name: true brief: "Represents SUFFIX statement in NMODL" + - Useion: nmodl: "USEION " members: @@ -1871,6 +1977,7 @@ type: Valence optional: true brief: "Represents USEION statement in NMODL" + - Nonspecific: nmodl: "NONSPECIFIC_CURRENT " members: @@ -1880,6 +1987,7 @@ vector: true separator: ", " brief: "Represents NONSPECIFIC_CURRENT variables statement in NMODL" + - ElctrodeCurrent: nmodl: "ELECTRODE_CURRENT " members: @@ -1889,6 +1997,7 @@ vector: true separator: ", " brief: "Represents ELECTRODE_CURRENT variables statement in NMODL" + - Section: nmodl: "SECTION " members: @@ -1898,6 +2007,7 @@ vector: true separator: ", " brief: "Represents SECTION variables statement in NMODL" + - Range: nmodl: "RANGE " members: @@ -1907,6 +2017,7 @@ vector: true separator: ", " brief: "Represents RANGE variables statement in NMODL" + - Global: brief: "TODO" nmodl: "GLOBAL " @@ -1917,6 +2028,7 @@ vector: true separator: ", " brief: "Represents GLOBAL statement in NMODL" + - Pointer: nmodl: "POINTER " members: @@ -1926,7 +2038,8 @@ vector: true separator: ", " brief: "Represents POINTER statement in NMODL" - - BbcorePtr: + + - BbcorePointer: nmodl: "BBCOREPOINTER " members: - variables: @@ -1944,6 +2057,7 @@ POINT_PROCESS ProbAMPANMDA_EMS BBCOREPOINTER rng, data \endcode + - External: nmodl: "EXTERNAL " members: @@ -1953,6 +2067,7 @@ vector: true separator: ", " brief: "Represents EXTERNAL statement in NMODL" + - ThreadSafe: nmodl: THREADSAFE members: @@ -1963,6 +2078,7 @@ separator: ", " prefix: {value: " "} brief: "Represents THREADSAFE statement in NMODL" + - Verbatim: brief: "Represents a C code block" nmodl: VERBATIM @@ -1971,12 +2087,14 @@ brief: "C code as a string" type: String suffix: {value: "ENDVERBATIM"} + - LineComment: brief: "Represents a one line comment in NMODL" members: - statement: brief: "comment text" type: String + - BlockComment: brief: "Represents a multi-line comment in NMODL" nmodl: COMMENT diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 7b39147d77..112fbbd848 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -61,7 +61,7 @@ SYMBOL_VAR_TYPES = {"LocalVar", "ParamAssign", "Argument", - "DependentDef", + "AssignedDefinition", "UnitDef", "FactorDef", "RangeVar", @@ -104,7 +104,7 @@ "UnitBlock", "StepBlock", "IndependentBlock", - "DependentBlock", + "AssignedBlock", "StateBlock", "ConstantBlock", } @@ -112,8 +112,8 @@ # when translating back to nmodl, we need print each statement # to new line. Those nodes are are used from this list. STATEMENT_TYPES = {"Statement", - "IndependentDef", - "DependentDef", + "IndependentDefinition", + "AssignedDefinition", "ParamAssign", "ConstantStatement", "Stepped", diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 5f04db769b..26266f7bdf 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -149,7 +149,7 @@ void init_symtab_module(py::module& m) { e_nmodltype.value("argument", syminfo::NmodlType::argument) .value("bbcore_pointer_var", syminfo::NmodlType::bbcore_pointer_var) .value("constant_var", syminfo::NmodlType::constant_var) - .value("dependent_def", syminfo::NmodlType::dependent_def) + .value("assigned_definition", syminfo::NmodlType::assigned_definition) .value("derivative_block", syminfo::NmodlType::derivative_block) .value("discrete_block", syminfo::NmodlType::discrete_block) .value("electrode_cur_var", syminfo::NmodlType::electrode_cur_var) @@ -174,7 +174,7 @@ void init_symtab_module(py::module& m) { .value("read_ion_var", syminfo::NmodlType::read_ion_var) .value("section_var", syminfo::NmodlType::section_var) .value("state_var", syminfo::NmodlType::state_var) - .value("table_dependent_var", syminfo::NmodlType::table_dependent_var) + .value("table_assigned_var", syminfo::NmodlType::table_assigned_var) .value("table_statement_var", syminfo::NmodlType::table_statement_var) .value("to_solve", syminfo::NmodlType::to_solve) .value("unit_def", syminfo::NmodlType::unit_def) diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 03349b5174..643ce9a1d0 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -166,8 +166,8 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_DEFINE1(token, pos); case Token::DEPEND: return Parser::make_DEPEND(token, pos); - case Token::DEPENDENT: - return Parser::make_DEPENDENT(token, pos); + case Token::ASSIGNED: + return Parser::make_ASSIGNED(token, pos); case Token::DERIVATIVE: return Parser::make_DERIVATIVE(token, pos); case Token::DESTRUCTOR: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index c383be783c..b3908ea1e9 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -39,7 +39,7 @@ static std::map<std::string, TokenType> keywords = {{"VERBATIM", Token::VERBATIM {"CONSTANT", Token::CONSTANT}, {"PARAMETER", Token::PARAMETER}, {"INDEPENDENT", Token::INDEPENDENT}, - {"ASSIGNED", Token::DEPENDENT}, + {"ASSIGNED", Token::ASSIGNED}, {"INITIAL", Token::INITIAL1}, {"TERMINAL", Token::TERMINAL}, {"DERIVATIVE", Token::DERIVATIVE}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 1c39cb5825..81b2038a63 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -79,54 +79,94 @@ * lexer executable or tests, it's useful to return ModToken. Note that UNKNOWN * token is added for convenience (with default arguments). */ -%token <ModToken> MODEL +%token <ModToken> AFTER +%token <ModToken> BBCOREPOINTER +%token <ModToken> BEFORE +%token <ModToken> BREAKPOINT +%token <ModToken> BY +%token <ModToken> COMPARTMENT +%token <ModToken> CONDUCTANCE +%token <ModToken> CONSERVE %token <ModToken> CONSTANT -%token <ModToken> INDEPENDENT -%token <ModToken> DEPENDENT -%token <ModToken> STATE -%token <ModToken> INITIAL1 +%token <ModToken> CONSTRUCTOR +%token <ModToken> DEFINE1 +%token <ModToken> DEPEND +%token <ModToken> ASSIGNED +%token <ModToken> DERFUNC %token <ModToken> DERIVATIVE -%token <ModToken> SOLVE -%token <ModToken> USING -%token <ModToken> STEADYSTATE -%token <ModToken> WITH -%token <ModToken> STEPPED +%token <ModToken> DESTRUCTOR %token <ModToken> DISCRETE -%token <ModToken> FROM +%token <ModToken> ELECTRODE_CURRENT +%token <ModToken> ELSE +%token <ModToken> EQUATION +%token <ModToken> EXTERNAL +%token <ModToken> FIRST %token <ModToken> FORALL1 -%token <ModToken> TO -%token <ModToken> BY -%token <ModToken> WHILE +%token <ModToken> FOR_NETCONS +%token <ModToken> FROM +%token <ModToken> FUNCTION1 +%token <ModToken> FUNCTION_TABLE +%token <ModToken> GETQ +%token <ModToken> GLOBAL %token <ModToken> IF -%token <ModToken> ELSE -%token <ModToken> START1 -%token <ModToken> STEP -%token <ModToken> SENS -%token <ModToken> SOLVEFOR -%token <ModToken> PROCEDURE -%token <ModToken> PARTIAL -%token <ModToken> DEFINE1 %token <ModToken> IFERROR -%token <ModToken> PARAMETER -%token <ModToken> DERFUNC -%token <ModToken> EQUATION -%token <ModToken> TERMINAL +%token <ModToken> INCLUDE1 +%token <ModToken> INDEPENDENT +%token <ModToken> INITIAL1 +%token <ModToken> KINETIC +%token <ModToken> LAG +%token <ModToken> LAST +%token <ModToken> LIN1 %token <ModToken> LINEAR -%token <ModToken> NONLINEAR -%token <ModToken> FUNCTION1 %token <ModToken> LOCAL -%token <ModToken> LIN1 +%token <ModToken> LONGDIFUS +%token <ModToken> MATCH +%token <ModToken> MODEL +%token <ModToken> MODEL_LEVEL +%token <ModToken> NETRECEIVE +%token <ModToken> NEURON %token <ModToken> NONLIN1 -%token <ModToken> PUTQ -%token <ModToken> GETQ -%token <ModToken> TABLE -%token <ModToken> DEPEND -%token <ModToken> BREAKPOINT -%token <ModToken> INCLUDE1 -%token <ModToken> FUNCTION_TABLE -%token <ModToken> PROTECT +%token <ModToken> NONLINEAR +%token <ModToken> NONSPECIFIC %token <ModToken> NRNMUTEXLOCK %token <ModToken> NRNMUTEXUNLOCK +%token <ModToken> PARAMETER +%token <ModToken> PARTIAL +%token <ModToken> PLOT +%token <ModToken> POINTER +%token <ModToken> PROCEDURE +%token <ModToken> PROTECT +%token <ModToken> PUTQ +%token <ModToken> RANGE +%token <ModToken> REACT1 +%token <ModToken> REACTION +%token <ModToken> READ +%token <ModToken> RESET +%token <ModToken> SECTION +%token <ModToken> SENS +%token <ModToken> SOLVE +%token <ModToken> SOLVEFOR +%token <ModToken> START1 +%token <ModToken> STATE +%token <ModToken> STEADYSTATE +%token <ModToken> STEP +%token <ModToken> STEPPED +%token <ModToken> SWEEP +%token <ModToken> TABLE +%token <ModToken> TERMINAL +%token <ModToken> THREADSAFE +%token <ModToken> TO +%token <ModToken> UNITS +%token <ModToken> UNITSOFF +%token <ModToken> UNITSON +%token <ModToken> USEION +%token <ModToken> USING +%token <ModToken> VS +%token <ModToken> WATCH +%token <ModToken> WHILE +%token <ModToken> WITH +%token <ModToken> WRITE + %token <ModToken> OR %token <ModToken> AND %token <ModToken> GT @@ -136,60 +176,28 @@ %token <ModToken> NE %token <ModToken> NOT %token <ModToken> GE -%token <ModToken> PLOT -%token <ModToken> VS -%token <ModToken> LAG -%token <ModToken> RESET -%token <ModToken> MATCH -%token <ModToken> MODEL_LEVEL -%token <ModToken> SWEEP -%token <ModToken> FIRST -%token <ModToken> LAST -%token <ModToken> KINETIC -%token <ModToken> CONSERVE -%token <ModToken> REACTION -%token <ModToken> REACT1 -%token <ModToken> COMPARTMENT -%token <ModToken> UNITS -%token <ModToken> UNITSON -%token <ModToken> UNITSOFF -%token <ModToken> LONGDIFUS -%token <ModToken> NEURON -%token <ModToken> NONSPECIFIC -%token <ModToken> READ -%token <ModToken> WRITE -%token <ModToken> USEION -%token <ModToken> THREADSAFE -%token <ModToken> GLOBAL -%token <ModToken> SECTION -%token <ModToken> RANGE POINTER -%token <ModToken> BBCOREPOINTER -%token <ModToken> EXTERNAL -%token <ModToken> BEFORE -%token <ModToken> AFTER -%token <ModToken> WATCH -%token <ModToken> ELECTRODE_CURRENT -%token <ModToken> CONSTRUCTOR -%token <ModToken> DESTRUCTOR -%token <ModToken> NETRECEIVE -%token <ModToken> FOR_NETCONS -%token <ModToken> CONDUCTANCE + %token <ast::Double> REAL %token <ast::Integer> INTEGER %token <ast::Integer> DEFINEDVAR + %token <ast::Name> NAME %token <ast::Name> METHOD %token <ast::Name> SUFFIX %token <ast::Name> VALENCE %token <ast::Name> DEL %token <ast::Name> DEL2 +%token <ast::Name> FLUX_VAR + %token <ast::PrimeName> PRIME + %token <std::string> VERBATIM %token <std::string> BLOCK_COMMENT %token <std::string> LINE_COMMENT %token <std::string> LINE_PART + %token <ast::String> STRING -%token <ast::Name> FLUX_VAR + %token <ModToken> OPEN_BRACE "{" %token <ModToken> CLOSE_BRACE "}" %token <ModToken> OPEN_PARENTHESIS "(" @@ -207,155 +215,158 @@ %token <ModToken> COMMA "," %token <ModToken> TILDE "~" %token <ModToken> PERIOD "." + %token END 0 "End of file" %token UNKNOWN %token INVALID_TOKEN -/** Define terminal and nonterminal symbols : Instead of using AST classes - * directly, we are using typedefs like program_ptr. This is useful when we - * want to transparently change naming/type scheme. For example, raw pointer - * to smakrt pointers. Manually changing all types in bison specification is - * time consuming. Also, naming of termina/non-terminal symbols is kept same - * as NEURON. This helps during development and debugging. This could be - * once all implementation details get ported. */ - -%type <ast::Program*> all -%type <ast::Name*> Name -%type <ast::Number*> NUMBER -%type <ast::Double*> real -%type <ast::Expression*> intexpr -%type <ast::Integer*> integer -%type <ast::Model*> model -%type <ast::Unit*> units -%type <ast::Integer*> optindex -%type <ast::Unit*> unit -%type <ast::Block*> proc -%type <ast::Limits*> limits -%type <ast::Double*> abstol -%type <ast::Identifier*> name -%type <ast::Number*> number -%type <ast::Expression*> primary -%type <ast::Expression*> term -%type <ast::Expression*> leftlinexpr -%type <ast::Expression*> linexpr -%type <ast::NumberVector> numlist -%type <ast::Expression*> expr -%type <ast::Expression*> aexpr -%type <ast::Statement*> ostmt -%type <ast::Statement*> astmt -%type <ast::StatementBlock*> stmtlist -%type <ast::LocalListStatement*> locallist -%type <ast::LocalVarVector> locallist1 -%type <ast::VarName*> varname -%type <ast::ExpressionVector> exprlist -%type <ast::Define*> define1 -%type <ast::QueueStatement*> queuestmt -%type <ast::Expression*> asgn -%type <ast::FromStatement*> fromstmt -%type <ast::WhileStatement*> whilestmt -%type <ast::IfStatement*> ifstmt -%type <ast::ElseIfStatementVector> optelseif -%type <ast::ElseStatement*> optelse -%type <ast::SolveBlock*> solveblk -%type <ast::WrappedExpression*> funccall -%type <ast::StatementBlock*> ifsolerr -%type <ast::Expression*> opinc -%type <ast::Number*> opstart -%type <ast::VarNameVector> senslist -%type <ast::Sens*> sens -%type <ast::LagStatement*> lagstmt -%type <ast::ForAllStatement*> forallstmt -%type <ast::ParamAssign*> parmasgn -%type <ast::Stepped*> stepped -%type <ast::IndependentDef*> indepdef -%type <ast::DependentDef*> depdef -%type <ast::Block*> declare -%type <ast::ParamBlock*> parmblk -%type <ast::ParamAssignVector> parmbody -%type <ast::IndependentBlock*> indepblk -%type <ast::IndependentDefVector> indepbody -%type <ast::DependentBlock*> depblk -%type <ast::DependentDefVector> depbody -%type <ast::StateBlock*> stateblk -%type <ast::StepBlock*> stepblk -%type <ast::SteppedVector> stepbdy -%type <ast::WatchStatement*> watchstmt -%type <ast::BinaryOperator> watchdir -%type <ast::Watch*> watch1 -%type <ast::ForNetcon*> fornetcon -%type <ast::PlotDeclaration*> plotdecl -%type <ast::PlotVarVector> pvlist -%type <ast::ConstantBlock*> constblk -%type <ast::ConstantStatementVector> conststmt -%type <ast::MatchBlock*> matchblk -%type <ast::MatchVector> matchlist -%type <ast::Match*> match -%type <ast::Identifier*> matchname -%type <ast::PartialBoundary*> pareqn -%type <ast::FirstLastTypeIndex*> firstlast -%type <ast::ReactionStatement*> reaction -%type <ast::Conserve*> conserve -%type <ast::Expression*> react -%type <ast::Compartment*> compart -%type <ast::LonDifuse*> ldifus -%type <ast::NameVector> namelist -%type <ast::UnitBlock*> unitblk -%type <ast::ExpressionVector> unitbody -%type <ast::UnitDef*> unitdef -%type <ast::FactorDef*> factordef -%type <ast::NameVector> solvefor -%type <ast::NameVector> solvefor1 -%type <ast::UnitState*> uniton -%type <ast::NameVector> tablst -%type <ast::NameVector> tablst1 -%type <ast::TableStatement*> tablestmt -%type <ast::NameVector> dependlst -%type <ast::ArgumentVector> arglist -%type <ast::ArgumentVector> arglist1 -%type <ast::Integer*> locoptarray -%type <ast::NeuronBlock*> neuronblk -%type <ast::Useion*> nrnuse -%type <ast::StatementVector> nrnstmt -%type <ast::ReadIonVarVector> nrnionrlist -%type <ast::WriteIonVarVector> nrnionwlist -%type <ast::NonspecificCurVarVector> nrnonspeclist -%type <ast::ElectrodeCurVarVector> nrneclist -%type <ast::SectionVarVector> nrnseclist -%type <ast::RangeVarVector> nrnrangelist -%type <ast::GlobalVarVector> nrnglobalist -%type <ast::PointerVarVector> nrnptrlist -%type <ast::BbcorePointerVarVector> nrnbbptrlist -%type <ast::ExternVarVector> nrnextlist -%type <ast::ThreadsafeVarVector> opthsafelist -%type <ast::ThreadsafeVarVector> threadsafelist -%type <ast::Valence*> valence -%type <ast::ExpressionStatement*> initstmt -%type <ast::BABlock*> bablk -%type <ast::ConductanceHint*> conducthint -%type <ast::StatementVector> stmtlist1 -%type <ast::InitialBlock*> initblk -%type <ast::ConstructorBlock*> constructblk -%type <ast::DestructorBlock*> destructblk -%type <ast::FunctionBlock*> funcblk -%type <ast::KineticBlock*> kineticblk -%type <ast::BreakpointBlock*> brkptblk -%type <ast::DerivativeBlock*> derivblk -%type <ast::LinearBlock*> linblk -%type <ast::NonLinearBlock*> nonlinblk -%type <ast::ProcedureBlock*> procedblk -%type <ast::NetReceiveBlock*> netrecblk -%type <ast::TerminalBlock*> terminalblk -%type <ast::DiscreteBlock*> discretblk -%type <ast::PartialBlock*> partialblk -%type <ast::FunctionTableBlock*> functableblk -%type <ast::Integer*> INTEGER_PTR -%type <ast::Name*> NAME_PTR -%type <ast::String*> STRING_PTR -%type <ast::WrappedExpression*> flux_variable - -/** Precedence and Associativity : specify operator precedency and - * associativity (from lower to higher. Note that '^' represent - * exponentiation. */ + +/** Bison production return types */ + + +%type <ast::Program*> all + +%type <ast::Double*> double +%type <ast::Identifier*> name +%type <ast::Integer*> integer +%type <ast::Name*> Name +%type <ast::Number*> NUMBER +%type <ast::Number*> number +%type <ast::VarName*> variable_name + +%type <ast::Model*> model +%type <ast::Unit*> units +%type <ast::Integer*> optional_index +%type <ast::Unit*> unit +%type <ast::Block*> procedure +%type <ast::Limits*> limits +%type <ast::Double*> abs_tolerance +%type <ast::Expression*> integer_expression +%type <ast::Expression*> primary +%type <ast::Expression*> term +%type <ast::Expression*> left_linear_expression +%type <ast::Expression*> linear_expression +%type <ast::NumberVector> number_list +%type <ast::Expression*> expression +%type <ast::Expression*> watch_expression +%type <ast::Statement*> statement_type1 +%type <ast::Statement*> statement_type2 +%type <ast::StatementBlock*> statement_list +%type <ast::LocalListStatement*> local_statement +%type <ast::LocalVarVector> local_var_list +%type <ast::ExpressionVector> expression_list +%type <ast::Define*> define +%type <ast::QueueStatement*> queue_statement +%type <ast::Expression*> assignment +%type <ast::FromStatement*> from_statement +%type <ast::WhileStatement*> while_statement +%type <ast::IfStatement*> if_statement +%type <ast::ElseIfStatementVector> optional_else_if +%type <ast::ElseStatement*> optional_else +%type <ast::WrappedExpression*> function_call +%type <ast::StatementBlock*> if_solution_error +%type <ast::Expression*> optional_increment +%type <ast::Number*> optional_start +%type <ast::VarNameVector> sens_list +%type <ast::Sens*> sens +%type <ast::LagStatement*> lag_statement +%type <ast::ForAllStatement*> forall_statement +%type <ast::ParamAssign*> parameter_assignment +%type <ast::Stepped*> stepped_statement +%type <ast::IndependentDefinition*> independent_definition +%type <ast::AssignedDefinition*> dependent_definition +%type <ast::Block*> declare +%type <ast::ParamAssignVector> parameter_block_body +%type <ast::IndependentDefinitionVector> independent_block_body +%type <ast::AssignedDefinitionVector> dependent_block_body +%type <ast::SteppedVector> step_block_body +%type <ast::WatchStatement*> watch_statement +%type <ast::BinaryOperator> watch_direction +%type <ast::Watch*> watch +%type <ast::ForNetcon*> for_netcon +%type <ast::PlotDeclaration*> plot_declaration +%type <ast::PlotVarVector> plot_variable_list +%type <ast::ConstantStatementVector> constant_statement +%type <ast::MatchVector> match_list +%type <ast::Match*> match +%type <ast::Identifier*> match_name +%type <ast::PartialBoundary*> partial_equation +%type <ast::FirstLastTypeIndex*> first_last +%type <ast::ReactionStatement*> reaction_statement +%type <ast::Conserve*> conserve +%type <ast::Expression*> react +%type <ast::Compartment*> compartment +%type <ast::LonDifuse*> longitudinal_diffusion +%type <ast::NameVector> name_list +%type <ast::ExpressionVector> unit_block_body +%type <ast::UnitDef*> unit_definition +%type <ast::FactorDef*> factor_definition +%type <ast::NameVector> optional_solvefor +%type <ast::NameVector> solvefor +%type <ast::UnitState*> unit_state +%type <ast::NameVector> optional_table_var_list +%type <ast::NameVector> table_var_list +%type <ast::TableStatement*> table_statement +%type <ast::NameVector> optional_dependent_var_list +%type <ast::ArgumentVector> optional_argument_list +%type <ast::ArgumentVector> argument_list +%type <ast::Integer*> optional_array_index +%type <ast::Useion*> use_ion_statement +%type <ast::StatementVector> neuron_statement +%type <ast::ReadIonVarVector> read_ion_list +%type <ast::WriteIonVarVector> write_ion_list +%type <ast::NonspecificCurVarVector> nonspecific_var_list +%type <ast::ElectrodeCurVarVector> electrode_current_var_list +%type <ast::SectionVarVector> section_var_list +%type <ast::RangeVarVector> range_var_list +%type <ast::GlobalVarVector> global_var_list +%type <ast::PointerVarVector> pointer_var_list +%type <ast::BbcorePointerVarVector> bbcore_pointer_var_list +%type <ast::ExternVarVector> external_var_list +%type <ast::ThreadsafeVarVector> optional_threadsafe_var_list +%type <ast::ThreadsafeVarVector> threadsafe_var_list +%type <ast::Valence*> valence +%type <ast::ExpressionStatement*> initial_statement +%type <ast::ConductanceHint*> conductance +%type <ast::StatementVector> optional_statement_list +%type <ast::WrappedExpression*> flux_variable + +%type <ast::AssignedBlock*> dependent_block +%type <ast::BABlock*> before_after_block +%type <ast::BreakpointBlock*> breakpoint_block +%type <ast::ConstantBlock*> constant_block +%type <ast::ConstructorBlock*> constructor_block +%type <ast::DerivativeBlock*> derivative_block +%type <ast::DestructorBlock*> destructor_block +%type <ast::DiscreteBlock*> discrete_block +%type <ast::FunctionBlock*> function_block +%type <ast::FunctionTableBlock*> function_table_block +%type <ast::IndependentBlock*> independent_block +%type <ast::InitialBlock*> initial_block +%type <ast::KineticBlock*> kinetic_block +%type <ast::LinearBlock*> linear_block +%type <ast::MatchBlock*> match_block +%type <ast::NetReceiveBlock*> net_receive_block +%type <ast::NeuronBlock*> neuron_block +%type <ast::NonLinearBlock*> non_linear_block +%type <ast::ParamBlock*> parameter_block +%type <ast::PartialBlock*> partial_block +%type <ast::ProcedureBlock*> procedure_block +%type <ast::SolveBlock*> solve_block +%type <ast::StateBlock*> state_block +%type <ast::StepBlock*> step_block +%type <ast::TerminalBlock*> terminal_block +%type <ast::UnitBlock*> unit_block + +%type <ast::Integer*> INTEGER_PTR +%type <ast::Name*> NAME_PTR +%type <ast::String*> STRING_PTR + +/* + * Precedence and Associativity : specify operator precedency and + * associativity (from lower to higher. Note that '^' represent + * exponentiation. + */ %left OR %left AND @@ -398,7 +409,7 @@ * Note that YYLLOC_DEFAULT is not sufficient for all rules and hence * we need to update @$ (especially @$.begin). Consider below example: * - * nrnstmt RANGE nrnrangelist + * neuron_statement RANGE range_var_list * * In this case we have to do : @$.begin = $1.begin (@$.end is already * set to $3.end and hence don't need to update). @@ -431,12 +442,12 @@ all : { $1->addNode($2); $$ = $1; } - | all locallist + | all local_statement { $1->addNode($2); $$ = $1; } - | all define1 + | all define { $1->addNode($2); $$ = $1; @@ -448,12 +459,12 @@ all : { } | all MODEL_LEVEL INTEGER_PTR declare { - /** todo : This is discussed with Michael Hines. Model level was inserted + /** todo This is discussed with Michael Hines. Model level was inserted * by merge program which is no longer exist. This was to avoid the name * collision in case of include. Idea was to have some kind of namespace! */ } - | all proc + | all procedure { $1->addNode($2); $$ = $1; @@ -478,7 +489,7 @@ all : { $1->addNode(statement); $$ = $1; } - | all uniton + | all unit_state { $1->addNode($2); $$ = $1; @@ -499,14 +510,14 @@ model : MODEL LINE_PART ; -define1 : DEFINE1 NAME_PTR INTEGER_PTR +define : DEFINE1 NAME_PTR INTEGER_PTR { $$ = new ast::Define($2, $3); driver.add_defined_var($2->get_node_name(), $3->eval()); } | DEFINE1 error { - error(scanner.loc, "define1"); + error(scanner.loc, "define"); } ; @@ -515,32 +526,57 @@ Name : NAME_PTR { $$ = $1; } ; -declare : parmblk { $$ = $1; } - | indepblk { $$ = $1; } - | depblk { $$ = $1; } - | stateblk { $$ = $1; } - | stepblk { $$ = $1; } - | plotdecl +declare : parameter_block + { + $$ = $1; + } + | independent_block + { + $$ = $1; + } + | dependent_block + { + $$ = $1; + } + | state_block + { + $$ = $1; + } + | step_block + { + $$ = $1; + } + | plot_declaration { $$ = new ast::PlotBlock($1); } - | neuronblk { $$ = $1; } - | unitblk { $$ = $1; } - | constblk { $$ = $1; } + | neuron_block + { + $$ = $1; + } + | unit_block + { + $$ = $1; + } + | constant_block + { + $$ = $1; + } ; -parmblk : PARAMETER "{" parmbody"}" +parameter_block : PARAMETER "{" parameter_block_body"}" { $$ = new ast::ParamBlock($3); } ; -parmbody : { +parameter_block_body : + { $$ = ast::ParamAssignVector(); } - | parmbody parmasgn + | parameter_block_body parameter_assignment { $1.emplace_back($2); $$ = $1; @@ -548,7 +584,7 @@ parmbody : { ; -parmasgn : NAME_PTR "=" number units limits +parameter_assignment : NAME_PTR "=" number units limits { $$ = new ast::ParamAssign($1, $3, $4, $5); } @@ -562,19 +598,24 @@ parmasgn : NAME_PTR "=" number units limits } | error { - error(scanner.loc, "parmasgn"); + error(scanner.loc, "parameter_assignment"); } ; -units : { $$ = nullptr; } - | unit { $$ = $1; } +units : { + $$ = nullptr; + } + | unit + { + $$ = $1; + } ; unit : "(" { scanner.scan_unit(); } ")" { - // @todo : empty units should be handled in semantic analysis + // @todo Empty units should be handled in semantic analysis auto unit = scanner.get_unit(); auto text = unit->eval(); $$ = new ast::Unit(unit); @@ -582,7 +623,7 @@ unit : "(" { scanner.scan_unit(); } ")" ; -uniton : UNITSON +unit_state : UNITSON { $$ = new ast::UnitState(ast::UNIT_ON); } @@ -593,23 +634,27 @@ uniton : UNITSON ; -limits : { $$ = nullptr; } - | LT real "," real GT +limits : { + $$ = nullptr; + } + | LT double "," double GT { $$ = new ast::Limits($2, $4); } ; -stepblk : STEPPED "{" stepbdy "}" +step_block : STEPPED "{" step_block_body "}" { $$ = new ast::StepBlock($3); } ; -stepbdy : { $$ = ast::SteppedVector(); } - | stepbdy stepped +step_block_body : { + $$ = ast::SteppedVector(); + } + | step_block_body stepped_statement { $1.emplace_back($2); $$ = $1; @@ -617,20 +662,20 @@ stepbdy : { $$ = ast::SteppedVector(); } ; -stepped : NAME_PTR "=" numlist units +stepped_statement : NAME_PTR "=" number_list units { $$ = new ast::Stepped($1, $3, $4); } ; -numlist : number "," number +number_list : number "," number { $$ = ast::NumberVector(); $$.emplace_back($1); $$.emplace_back($3); } - | numlist "," number + | number_list "," number { $1.emplace_back($3); $$ = $1; @@ -638,12 +683,21 @@ numlist : number "," number ; -name : Name { $$ = $1; } - | PRIME { $$ = $1.clone(); } +name : Name + { + $$ = $1; + } + | PRIME + { + $$ = $1.clone(); + } ; -number : NUMBER { $$ = $1; } +number : NUMBER + { + $$ = $1; + } | "-" NUMBER { $2->negate(); @@ -652,17 +706,32 @@ number : NUMBER { $$ = $1; } ; -NUMBER : integer { $$ = $1; } - | REAL { $$ = $1.clone(); } +NUMBER : integer + { + $$ = $1; + } + | REAL + { + $$ = $1.clone(); + } ; -integer : INTEGER_PTR { $$ = $1; } - | DEFINEDVAR { $$ = $1.clone(); } +integer : INTEGER_PTR + { + $$ = $1; + } + | DEFINEDVAR + { + $$ = $1.clone(); + } ; -real : REAL { $$ = $1.clone(); } +double : REAL + { + $$ = $1.clone(); + } | integer { $$ = new ast::Double(double($1->eval())); @@ -671,22 +740,23 @@ real : REAL { $$ = $1.clone(); } ; -indepblk : INDEPENDENT "{" indepbody "}" +independent_block : INDEPENDENT "{" independent_block_body "}" { $$ = new ast::IndependentBlock($3); } ; -indepbody : { - $$ = ast::IndependentDefVector(); +independent_block_body : + { + $$ = ast::IndependentDefinitionVector(); } - | indepbody indepdef + | independent_block_body independent_definition { $1.emplace_back($2); $$ = $1; } - | indepbody SWEEP indepdef + | independent_block_body SWEEP independent_definition { $1.emplace_back($3); $3->set_sweep(std::make_shared<ast::Boolean>(1)); @@ -695,13 +765,13 @@ indepbody : { ; -indepdef : NAME_PTR FROM number TO number withby integer opstart units +independent_definition : NAME_PTR FROM number TO number withby integer optional_start units { - $$ = new ast::IndependentDef(NULL, $1, $3, $5, $7, $8, $9); + $$ = new ast::IndependentDefinition(NULL, $1, $3, $5, $7, $8, $9); } | error { - error(scanner.loc, "indepdef"); + error(scanner.loc, "independent_definition"); } ; @@ -710,17 +780,18 @@ withby : WITH ; -depblk : DEPENDENT "{" depbody"}" +dependent_block : ASSIGNED "{" dependent_block_body "}" { - $$ = new ast::DependentBlock($3); + $$ = new ast::AssignedBlock($3); } ; -depbody : { - $$ = ast::DependentDefVector(); +dependent_block_body : + { + $$ = ast::AssignedDefinitionVector(); } - | depbody depdef + | dependent_block_body dependent_definition { $1.emplace_back($2); $$ = $1; @@ -728,64 +799,76 @@ depbody : { ; -depdef : name opstart units abstol +dependent_definition : name optional_start units abs_tolerance { - $$ = new ast::DependentDef($1, NULL, NULL, NULL, $2, $3, $4); + $$ = new ast::AssignedDefinition($1, NULL, NULL, NULL, $2, $3, $4); } - | name "[" integer "]" opstart units abstol + | name "[" integer "]" optional_start units abs_tolerance { - $$ = new ast::DependentDef($1, $3, NULL, NULL, $5, $6, $7); + $$ = new ast::AssignedDefinition($1, $3, NULL, NULL, $5, $6, $7); } - | name FROM number TO number opstart units abstol + | name FROM number TO number optional_start units abs_tolerance { - $$ = new ast::DependentDef($1, NULL, $3, $5, $6, $7, $8); + $$ = new ast::AssignedDefinition($1, NULL, $3, $5, $6, $7, $8); } - | name "[" integer "]" FROM number TO number opstart units abstol + | name "[" integer "]" FROM number TO number optional_start units abs_tolerance { - $$ = new ast::DependentDef($1, $3, $6, $8, $9, $10, $11); + $$ = new ast::AssignedDefinition($1, $3, $6, $8, $9, $10, $11); } | error { - error(scanner.loc, "depdef"); + error(scanner.loc, "dependent_definition"); } ; -opstart : { $$ = nullptr; } - | START1 number { $$ = $2; } +optional_start : + { + $$ = nullptr; + } + | START1 number + { + $$ = $2; + } ; -abstol : { $$ = nullptr; } - | LT real GT { $$ = $2; } +abs_tolerance : + { + $$ = nullptr; + } + | LT double GT + { + $$ = $2; + } ; -stateblk : STATE "{" depbody "}" +state_block : STATE "{" dependent_block_body "}" { $$ = new ast::StateBlock($3); } ; -plotdecl : PLOT pvlist VS name optindex +plot_declaration : PLOT plot_variable_list VS name optional_index { $$ = new ast::PlotDeclaration($2, new ast::PlotVar($4,$5)); } | PLOT error { - error(scanner.loc, "plotdecl"); + error(scanner.loc, "plot_declaration"); } ; -pvlist : name optindex +plot_variable_list : name optional_index { $$ = ast::PlotVarVector(); auto variable = new ast::PlotVar($1, $2); $$.emplace_back(variable); } - | pvlist "," name optindex + | plot_variable_list "," name optional_index { $$ = $1; auto variable = new ast::PlotVar($3, $4); @@ -794,64 +877,115 @@ pvlist : name optindex ; -optindex : { $$ = nullptr; } - | "[" INTEGER_PTR "]" { $$ = $2; } +optional_index : + { + $$ = nullptr; + } + | "[" INTEGER_PTR "]" + { + $$ = $2; + } ; -proc : initblk { $$ = $1; } - | derivblk { $$ = $1; } - | brkptblk { $$ = $1; } - | linblk { $$ = $1; } - | nonlinblk { $$ = $1; } - | funcblk { $$ = $1; } - | procedblk { $$ = $1; } - | netrecblk { $$ = $1; } - | terminalblk { $$ = $1; } - | discretblk { $$ = $1; } - | partialblk { $$ = $1; } - | kineticblk { $$ = $1; } - | constructblk { $$ = $1; } - | destructblk { $$ = $1; } - | functableblk { $$ = $1; } - | BEFORE bablk +procedure : initial_block + { + $$ = $1; + } + | derivative_block + { + $$ = $1; + } + | breakpoint_block + { + $$ = $1; + } + | linear_block + { + $$ = $1; + } + | non_linear_block + { + $$ = $1; + } + | function_block + { + $$ = $1; + } + | procedure_block + { + $$ = $1; + } + | net_receive_block + { + $$ = $1; + } + | terminal_block + { + $$ = $1; + } + | discrete_block + { + $$ = $1; + } + | partial_block + { + $$ = $1; + } + | kinetic_block + { + $$ = $1; + } + | constructor_block + { + $$ = $1; + } + | destructor_block + { + $$ = $1; + } + | function_table_block + { + $$ = $1; + } + | BEFORE before_after_block { $$ = new ast::BeforeBlock($2); } - | AFTER bablk + | AFTER before_after_block { $$ = new ast::AfterBlock($2); } ; -initblk : INITIAL1 stmtlist "}" +initial_block : INITIAL1 statement_list "}" { $$ = new ast::InitialBlock($2); } ; -constructblk : CONSTRUCTOR stmtlist "}" +constructor_block : CONSTRUCTOR statement_list "}" { $$ = new ast::ConstructorBlock($2); } ; -destructblk : DESTRUCTOR stmtlist "}" +destructor_block : DESTRUCTOR statement_list "}" { $$ = new ast::DestructorBlock($2); } ; -stmtlist : "{" stmtlist1 +statement_list : "{" optional_statement_list { $$ = new ast::StatementBlock($2); $$->set_token($1); } - | "{" locallist stmtlist1 + | "{" local_statement optional_statement_list { $3.insert($3.begin(), std::shared_ptr<ast::LocalListStatement>($2)); $$ = new ast::StatementBlock($3); @@ -860,7 +994,7 @@ stmtlist : "{" stmtlist1 ; -conducthint : CONDUCTANCE Name +conductance : CONDUCTANCE Name { $$ = new ast::ConductanceHint($2, NULL); } @@ -871,18 +1005,18 @@ conducthint : CONDUCTANCE Name ; -locallist : LOCAL locallist1 +local_statement : LOCAL local_var_list { $$ = new ast::LocalListStatement($2); } | LOCAL error { - error(scanner.loc, "locallist"); + error(scanner.loc, "local_statement"); } ; -locallist1 : NAME_PTR locoptarray +local_var_list : NAME_PTR optional_array_index { $$ = ast::LocalVarVector(); if($2) { @@ -893,7 +1027,7 @@ locallist1 : NAME_PTR locoptarray $$.emplace_back(variable); } } - | locallist1 "," NAME_PTR locoptarray + | local_var_list "," NAME_PTR optional_array_index { if($4) { auto variable = new ast::LocalVar(new ast::IndexedName($3, $4)); @@ -907,25 +1041,32 @@ locallist1 : NAME_PTR locoptarray ; -locoptarray : { $$ = nullptr; } - | "[" integer "]" { $$ = $2; } +optional_array_index : + { + $$ = nullptr; + } + | "[" integer "]" + { + $$ = $2; + } ; -stmtlist1 : { +optional_statement_list : + { $$ = ast::StatementVector(); } - | stmtlist1 ostmt + | optional_statement_list statement_type1 { $1.emplace_back($2); $$ = $1; } - | stmtlist1 astmt + | optional_statement_list statement_type2 { $1.emplace_back($2); $$ = $1; } - | stmtlist1 LINE_COMMENT + | optional_statement_list LINE_COMMENT { auto statement = new ast::LineComment(new ast::String($2)); $1.emplace_back(statement); @@ -934,19 +1075,34 @@ stmtlist1 : { ; -ostmt : fromstmt { $$ = $1; } - | forallstmt { $$ = $1; } - | whilestmt { $$ = $1; } - | ifstmt { $$ = $1; } - | stmtlist "}" +statement_type1 : from_statement + { + $$ = $1; + } + | forall_statement + { + $$ = $1; + } + | while_statement + { + $$ = $1; + } + | if_statement + { + $$ = $1; + } + | statement_list "}" { $$ = new ast::ExpressionStatement($1); } - | solveblk + | solve_block { $$ = new ast::ExpressionStatement($1); } - | conducthint { $$ = $1; } + | conductance + { + $$ = $1; + } | VERBATIM { auto text = parse_with_verbatim_parser($1); $$ = new ast::Verbatim(new ast::String(text)); @@ -955,26 +1111,59 @@ ostmt : fromstmt { $$ = $1; } { auto text = parse_with_verbatim_parser($1); $$ = new ast::BlockComment(new ast::String(text)); } - | sens { $$ = $1; } - | compart { $$ = $1; } - | ldifus { $$ = $1; } - | conserve { $$ = $1; } - | lagstmt { $$ = $1; } - | queuestmt { $$ = $1; } + | sens + { + $$ = $1; + } + | compartment + { + $$ = $1; + } + | longitudinal_diffusion + { + $$ = $1; + } + | conserve + { + $$ = $1; + } + | lag_statement + { + $$ = $1; + } + | queue_statement + { + $$ = $1; + } | RESET { $$ = new ast::Reset(); } - | matchblk + | match_block { $$ = new ast::ExpressionStatement($1); } - | pareqn { $$ = $1; } - | tablestmt { $$ = $1; } - | uniton { $$ = $1; } - | initstmt { $$ = $1; } - | watchstmt { $$ = $1; } - | fornetcon + | partial_equation + { + $$ = $1; + } + | table_statement + { + $$ = $1; + } + | unit_state + { + $$ = $1; + } + | initial_statement + { + $$ = $1; + } + | watch_statement + { + $$ = $1; + } + | for_netcon { $$ = new ast::ExpressionStatement($1); } @@ -988,28 +1177,31 @@ ostmt : fromstmt { $$ = $1; } } | error { - error(scanner.loc, "ostmt"); + error(scanner.loc, "statement_type1"); } ; -astmt : asgn +statement_type2 : assignment { $$ = new ast::ExpressionStatement($1); } - | PROTECT asgn + | PROTECT assignment { $$ = new ast::ProtectStatement($2); } - | reaction { $$ = $1; } - | funccall + | reaction_statement + { + $$ = $1; + } + | function_call { $$ = new ast::ExpressionStatement($1); } ; -asgn : varname "=" expr +assignment : variable_name "=" expression { auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ASSIGN), $3); auto name = $1->get_name(); @@ -1022,22 +1214,22 @@ asgn : varname "=" expr $$ = expression; } } - | nonlineqn expr "=" expr + | nonlineqn expression "=" expression { $$ = new ast::NonLinEquation($2, $4); } - | lineqn leftlinexpr "=" linexpr + | lineqn left_linear_expression "=" linear_expression { $$ = new ast::LinEquation($2, $4); } ; -varname : name +variable_name : name { $$ = new ast::VarName($1, nullptr, nullptr); } - | name "[" intexpr "]" + | name "[" integer_expression "]" { $$ = new ast::VarName(new ast::IndexedName($1, $3), nullptr, nullptr); } @@ -1045,137 +1237,163 @@ varname : name { $$ = new ast::VarName($1, $3, nullptr); } - | NAME_PTR "@" integer "[" intexpr "]" + | NAME_PTR "@" integer "[" integer_expression "]" { $$ = new ast::VarName($1, $3, $5); } ; -intexpr : Name { $$ = $1; } - | integer { $$ = $1; } - | "(" intexpr ")" +integer_expression : Name + { + $$ = $1; + } + | integer { - auto expr = new ast::ParenExpression($2); - $$ = new ast::WrappedExpression(expr); + $$ = $1; } - | intexpr "+" intexpr + | "(" integer_expression ")" { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::ParenExpression($2); + $$ = new ast::WrappedExpression(expression); } - | intexpr "-" intexpr + | integer_expression "+" integer_expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + $$ = new ast::WrappedExpression(expression); } - | intexpr "*" intexpr + | integer_expression "-" integer_expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + $$ = new ast::WrappedExpression(expression); + } + | integer_expression "*" integer_expression + { + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + $$ = new ast::WrappedExpression(expression); } - | intexpr "/" intexpr + | integer_expression "/" integer_expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + $$ = new ast::WrappedExpression(expression); } | error { - error(scanner.loc, "intexpr"); + error(scanner.loc, "integer_expression"); } ; -expr : varname { $$ = $1; } - | flux_variable { $$ = $1; } - | real units +expression : variable_name + { + $$ = $1; + } + | flux_variable + { + $$ = $1; + } + | double units { if($2) $$ = new ast::DoubleUnit($1, $2); else $$ = $1; } - | funccall { $$ = $1; } - | "(" expr ")" + | function_call { - auto expr = new ast::ParenExpression($2); - $$ = new ast::WrappedExpression(expr); + $$ = $1; } - | expr "+" expr + | "(" expression ")" { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::ParenExpression($2); + $$ = new ast::WrappedExpression(expression); } - | expr "-" expr + | expression "+" expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); + $$ = new ast::WrappedExpression(expression); } - | expr "*" expr + | expression "-" expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); + $$ = new ast::WrappedExpression(expression); } - | expr "/" expr + | expression "*" expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); + $$ = new ast::WrappedExpression(expression); } - | expr "^" expr + | expression "/" expression { - auto expr = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); - $$ = new ast::WrappedExpression(expr); + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); + $$ = new ast::WrappedExpression(expression); } - | expr OR expr + | expression "^" expression + { + auto expression = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); + $$ = new ast::WrappedExpression(expression); + } + | expression OR expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_OR), $3); } - | expr AND expr + | expression AND expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_AND), $3); } - | expr GT expr + | expression GT expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_GREATER), $3); } - | expr LT expr + | expression LT expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_LESS), $3); } - | expr GE expr + | expression GE expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_GREATER_EQUAL), $3); } - | expr LE expr + | expression LE expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_LESS_EQUAL), $3); } - | expr EQ expr + | expression EQ expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_EXACT_EQUAL), $3); } - | expr NE expr + | expression NE expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_NOT_EQUAL), $3); } - | NOT expr + | NOT expression { $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NOT), $2); } - | "-" expr %prec UNARYMINUS + | "-" expression %prec UNARYMINUS { $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NEGATION), $2); } | error { - error(scanner.loc, "expr"); + error(scanner.loc, "expression"); } ; - /** \todo Add extra rules for better error reporting : - | "(" expr { yyerror("Unbalanced left parenthesis followed by valid expressions"); } - | "(" error { yyerror("Unbalanced left parenthesis followed by non parseable"); } - | expr ")" { yyerror("Unbalanced right parenthesis"); } + /** + \todo Add extra rules for better error reporting + + | "(" expression + { + yyerror("Unbalanced left parenthesis followed by valid expressions"); + } + | "(" error + { + yyerror("Unbalanced left parenthesis followed by non parseable"); + } + | expression ")" + { + yyerror("Unbalanced right parenthesis"); + } */ @@ -1187,27 +1405,36 @@ lineqn : LIN1 ; -leftlinexpr : linexpr { $$ = $1; } +left_linear_expression : linear_expression + { + $$ = $1; + } ; -linexpr : primary { $$ = $1; } +linear_expression : primary + { + $$ = $1; + } | "-" primary { $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NEGATION), $2); } - | linexpr "+" primary + | linear_expression "+" primary { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); } - | linexpr "-" primary + | linear_expression "-" primary { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); } ; -primary : term { $$ = $1; } +primary : term + { + $$ = $1; + } | primary "*" term { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); @@ -1219,10 +1446,22 @@ primary : term { $$ = $1; } ; -term : varname { $$ = $1; } - | real { $$ = $1; } - | funccall { $$ = $1; } - | "(" expr ")" { $$ = new ast::ParenExpression($2); } +term : variable_name + { + $$ = $1; + } + | double + { + $$ = $1; + } + | function_call + { + $$ = $1; + } + | "(" expression ")" + { + $$ = new ast::ParenExpression($2); + } | error { error(scanner.loc, "term"); @@ -1230,7 +1469,7 @@ term : varname { $$ = $1; } ; -funccall : NAME_PTR "(" exprlist ")" +function_call : NAME_PTR "(" expression_list ")" { auto expression = new ast::FunctionCall($1, $3); $$ = new ast::WrappedExpression(expression); @@ -1238,10 +1477,10 @@ funccall : NAME_PTR "(" exprlist ")" ; -exprlist : { +expression_list : { $$ = ast::ExpressionVector(); } - | expr + | expression { $$ = ast::ExpressionVector(); $$.emplace_back($1); @@ -1251,12 +1490,12 @@ exprlist : { $$ = ast::ExpressionVector(); $$.emplace_back($1); } - | exprlist "," expr + | expression_list "," expression { $1.emplace_back($3); $$ = $1; } - | exprlist "," STRING_PTR + | expression_list "," STRING_PTR { $1.emplace_back($3); $$ = $1; @@ -1264,51 +1503,57 @@ exprlist : { ; -fromstmt : FROM NAME_PTR "=" intexpr TO intexpr opinc stmtlist "}" +from_statement : FROM NAME_PTR "=" integer_expression TO integer_expression optional_increment statement_list "}" { $$ = new ast::FromStatement($2, $4, $6, $7, $8); } | FROM error { - error(scanner.loc, "fromstmt"); + error(scanner.loc, "from_statement"); } ; -opinc : { $$ = nullptr; } - | BY intexpr { $$ = $2; } +optional_increment : + { + $$ = nullptr; + } + | BY integer_expression + { + $$ = $2; + } ; -forallstmt : FORALL1 NAME_PTR stmtlist "}" +forall_statement : FORALL1 NAME_PTR statement_list "}" { $$ = new ast::ForAllStatement($2, $3); } | FORALL1 error { - error(scanner.loc, "forallstmt"); + error(scanner.loc, "forall_statement"); } ; -whilestmt : WHILE "(" expr ")" stmtlist "}" +while_statement : WHILE "(" expression ")" statement_list "}" { $$ = new ast::WhileStatement($3, $5); } ; -ifstmt : IF "(" expr ")" stmtlist "}" optelseif optelse +if_statement : IF "(" expression ")" statement_list "}" optional_else_if optional_else { $$ = new ast::IfStatement($3, $5, $7, $8); } ; -optelseif : { +optional_else_if : { $$ = ast::ElseIfStatementVector(); } - | optelseif ELSE IF "(" expr ")" stmtlist "}" + | optional_else_if ELSE IF "(" expression ")" statement_list "}" { $1.emplace_back(new ast::ElseIfStatement($5, $7)); $$ = $1; @@ -1316,15 +1561,17 @@ optelseif : { ; -optelse : { $$ = nullptr; } - | ELSE stmtlist "}" +optional_else : { + $$ = nullptr; + } + | ELSE statement_list "}" { $$ = new ast::ElseStatement($2); } ; -derivblk : DERIVATIVE NAME_PTR stmtlist "}" +derivative_block : DERIVATIVE NAME_PTR statement_list "}" { $$ = new ast::DerivativeBlock($2, $3); $$->set_token($1); @@ -1332,7 +1579,7 @@ derivblk : DERIVATIVE NAME_PTR stmtlist "}" ; -linblk : LINEAR NAME_PTR solvefor stmtlist "}" +linear_block : LINEAR NAME_PTR optional_solvefor statement_list "}" { $$ = new ast::LinearBlock($2, $3, $4); $$->set_token($1); @@ -1340,7 +1587,7 @@ linblk : LINEAR NAME_PTR solvefor stmtlist "}" ; -nonlinblk : NONLINEAR NAME_PTR solvefor stmtlist "}" +non_linear_block : NONLINEAR NAME_PTR optional_solvefor statement_list "}" { $$ = new ast::NonLinearBlock($2, $3, $4); $$->set_token($1); @@ -1348,43 +1595,43 @@ nonlinblk : NONLINEAR NAME_PTR solvefor stmtlist "}" ; -discretblk : DISCRETE NAME_PTR stmtlist "}" +discrete_block : DISCRETE NAME_PTR statement_list "}" { $$ = new ast::DiscreteBlock($2, $3); - // todo : disabled symbol table, remove this + // todo Disabled symbol table, remove this //$$->set_token($1); } ; -partialblk : PARTIAL NAME_PTR stmtlist "}" +partial_block : PARTIAL NAME_PTR statement_list "}" { $$ = new ast::PartialBlock($2, $3); $$->set_token($1); } | PARTIAL error { - error(scanner.loc, "partialblk"); + error(scanner.loc, "partial_block"); } ; -pareqn : "~" PRIME "=" NAME_PTR "*" DEL2 "(" NAME_PTR ")" "+" NAME_PTR +partial_equation : "~" PRIME "=" NAME_PTR "*" DEL2 "(" NAME_PTR ")" "+" NAME_PTR { $$ = new ast::PartialBoundary(NULL, $2.clone(), NULL, NULL, $4, $6.clone(), $8, $11); } - | "~" DEL NAME_PTR "[" firstlast "]" "=" expr + | "~" DEL NAME_PTR "[" first_last "]" "=" expression { $$ = new ast::PartialBoundary($2.clone(), $3, $5, $8, NULL, NULL, NULL, NULL); } - | "~" NAME_PTR "[" firstlast "]" "=" expr + | "~" NAME_PTR "[" first_last "]" "=" expression { $$ = new ast::PartialBoundary(NULL, $2, $4, $7, NULL, NULL, NULL, NULL); } ; -firstlast : FIRST +first_last : FIRST { $$ = new ast::FirstLastTypeIndex(ast::PEQ_FIRST); } @@ -1395,15 +1642,15 @@ firstlast : FIRST ; -functableblk : FUNCTION_TABLE NAME_PTR "(" arglist ")" units - { +function_table_block : FUNCTION_TABLE NAME_PTR "(" optional_argument_list ")" units + { $$ = new ast::FunctionTableBlock($2, $4, $6); $$->set_token($1); - } + } ; -funcblk : FUNCTION1 NAME_PTR "(" arglist ")" units stmtlist "}" +function_block : FUNCTION1 NAME_PTR "(" optional_argument_list ")" units statement_list "}" { $$ = new ast::FunctionBlock($2, $4, $6, $7); $$->set_token($1); @@ -1411,19 +1658,23 @@ funcblk : FUNCTION1 NAME_PTR "(" arglist ")" units stmtlist "}" ; -arglist : { +optional_argument_list : + { $$ = ast::ArgumentVector(); } - | arglist1 { $$ = $1; } + | argument_list + { + $$ = $1; + } ; -arglist1 : name units +argument_list : name units { $$ = ast::ArgumentVector(); $$.emplace_back(new ast::Argument($1, $2)); } - | arglist1 "," name units + | argument_list "," name units { $1.emplace_back(new ast::Argument($3, $4)); $$ = $1; @@ -1431,139 +1682,151 @@ arglist1 : name units ; -procedblk : PROCEDURE NAME_PTR "(" arglist ")" units stmtlist "}" +procedure_block : PROCEDURE NAME_PTR "(" optional_argument_list ")" units statement_list "}" { - $$ = new ast::ProcedureBlock($2, $4, $6, $7); $$->set_token($1); + $$ = new ast::ProcedureBlock($2, $4, $6, $7); $$->set_token($1); } ; -netrecblk : NETRECEIVE "(" arglist ")" stmtlist "}" +net_receive_block : NETRECEIVE "(" optional_argument_list ")" statement_list "}" { $$ = new ast::NetReceiveBlock($3, $5); } | NETRECEIVE error { - error(scanner.loc, "netrecblk"); + error(scanner.loc, "net_receive_block"); } ; -initstmt : INITIAL1 stmtlist "}" +initial_statement : INITIAL1 statement_list "}" { $$ = new ast::ExpressionStatement(new ast::InitialBlock($2)); } ; -solveblk : SOLVE NAME_PTR ifsolerr +solve_block : SOLVE NAME_PTR if_solution_error { $$ = new ast::SolveBlock($2, NULL, NULL, $3); } - | SOLVE NAME_PTR USING METHOD ifsolerr + | SOLVE NAME_PTR USING METHOD if_solution_error { $$ = new ast::SolveBlock($2, $4.clone(), NULL, $5); } | - SOLVE NAME_PTR STEADYSTATE METHOD ifsolerr + SOLVE NAME_PTR STEADYSTATE METHOD if_solution_error { $$ = new ast::SolveBlock($2, NULL, $4.clone(), $5); } | SOLVE error { - error(scanner.loc, "solveblk"); + error(scanner.loc, "solve_block"); } ; -ifsolerr : { $$ = nullptr; } - | IFERROR stmtlist "}" { $$ = $2; } +if_solution_error : + { + $$ = nullptr; + } + | IFERROR statement_list "}" + { + $$ = $2; + } ; -solvefor : { $$ = ast::NameVector(); } - | solvefor1 { $$ = $1; } +optional_solvefor : + { + $$ = ast::NameVector(); + } + | solvefor + { + $$ = $1; + } ; -solvefor1 : SOLVEFOR NAME_PTR +solvefor : SOLVEFOR NAME_PTR { $$ = ast::NameVector(); $$.emplace_back($2); } - | solvefor1 "," NAME_PTR + | solvefor "," NAME_PTR { $1.emplace_back($3); $$ = $1; } | SOLVEFOR error { - error(scanner.loc, "solvefor1"); + error(scanner.loc, "solvefor"); } ; -brkptblk : BREAKPOINT stmtlist "}" +breakpoint_block : BREAKPOINT statement_list "}" { $$ = new ast::BreakpointBlock($2); } ; -terminalblk : TERMINAL stmtlist "}" +terminal_block : TERMINAL statement_list "}" { $$ = new ast::TerminalBlock($2); } ; -bablk : BREAKPOINT stmtlist "}" +before_after_block : BREAKPOINT statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_BREAKPOINT), $2); } - | SOLVE stmtlist "}" + | SOLVE statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_SOLVE), $2); } - | INITIAL1 stmtlist "}" + | INITIAL1 statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_INITIAL), $2); } - | STEP stmtlist "}" + | STEP statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_STEP), $2); } | error { - error(scanner.loc, "bablk"); + error(scanner.loc, "before_after_block"); } ; -watchstmt : WATCH watch1 +watch_statement : WATCH watch { $$ = new ast::WatchStatement(ast::WatchVector()); $$->addWatch($2); } - | watchstmt "," watch1 + | watch_statement "," watch { $1->addWatch($3); $$ = $1; } | WATCH error { - error(scanner.loc, "watchstmt"); + error(scanner.loc, "watch_statement"); } ; -watch1 : "(" aexpr watchdir aexpr ")" real +watch : "(" watch_expression watch_direction watch_expression ")" double { $$ = new ast::Watch( new ast::BinaryExpression($2, $3, $4), $6); } ; -watchdir : GT +watch_direction : GT { $$ = ast::BinaryOperator(ast::BOP_GREATER); } @@ -1574,56 +1837,65 @@ watchdir : GT ; -fornetcon : FOR_NETCONS "(" arglist ")" stmtlist "}" +for_netcon : FOR_NETCONS "(" optional_argument_list ")" statement_list "}" { $$ = new ast::ForNetcon($3, $5); } | FOR_NETCONS error { - error(scanner.loc, "fornetcon"); + error(scanner.loc, "for_netcon"); } ; -aexpr : varname { $$ = $1; } - | real units +watch_expression : variable_name + { + $$ = $1; + } + | double units { $$ = new ast::DoubleUnit($1, $2); } - | funccall { $$ = $1; } - | "(" aexpr ")" { $$ = new ast::ParenExpression($2); } - | aexpr "+" aexpr + | function_call + { + $$ = $1; + } + | "(" watch_expression ")" + { + $$ = new ast::ParenExpression($2); + } + | watch_expression "+" watch_expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_ADDITION), $3); } - | aexpr "-" aexpr + | watch_expression "-" watch_expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_SUBTRACTION), $3); } - | aexpr "*" aexpr + | watch_expression "*" watch_expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_MULTIPLICATION), $3); } - | aexpr "/" aexpr + | watch_expression "/" watch_expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_DIVISION), $3); } - | aexpr "^" aexpr + | watch_expression "^" watch_expression { $$ = new ast::BinaryExpression($1, ast::BinaryOperator(ast::BOP_POWER), $3); } - | "-" aexpr %prec UNARYMINUS + | "-" watch_expression %prec UNARYMINUS { $$ = new ast::UnaryExpression(ast::UnaryOperator(ast::UOP_NEGATION), $2); } | error { - error(scanner.loc, "aexpr"); + error(scanner.loc, "watch_expression"); } ; -sens : SENS senslist +sens : SENS sens_list { $$ = new ast::Sens($2); } @@ -1634,12 +1906,12 @@ sens : SENS senslist ; -senslist : varname +sens_list : variable_name { $$ = ast::VarNameVector(); $$.emplace_back($1); } - | senslist "," varname + | sens_list "," variable_name { $1.emplace_back($3); $$ = $1; @@ -1647,7 +1919,7 @@ senslist : varname ; -conserve : CONSERVE react "=" expr +conserve : CONSERVE react "=" expression { $$ = new ast::Conserve($2, $4); } @@ -1658,34 +1930,34 @@ conserve : CONSERVE react "=" expr ; -compart : COMPARTMENT NAME_PTR "," expr "{" namelist "}" +compartment : COMPARTMENT NAME_PTR "," expression "{" name_list "}" { $$ = new ast::Compartment($2, $4, $6); } - | COMPARTMENT expr "{" namelist "}" + | COMPARTMENT expression "{" name_list "}" { $$ = new ast::Compartment(NULL, $2, $4); } ; -ldifus : LONGDIFUS NAME_PTR "," expr "{" namelist "}" +longitudinal_diffusion : LONGDIFUS NAME_PTR "," expression "{" name_list "}" { $$ = new ast::LonDifuse($2, $4, $6); } - | LONGDIFUS expr "{" namelist "}" + | LONGDIFUS expression "{" name_list "}" { $$ = new ast::LonDifuse(NULL, $2, $4); } ; -namelist : NAME_PTR +name_list : NAME_PTR { $$ = ast::NameVector(); $$.emplace_back($1); } - | namelist NAME_PTR + | name_list NAME_PTR { $1.emplace_back($2); $$ = $1; @@ -1693,7 +1965,7 @@ namelist : NAME_PTR ; -kineticblk : KINETIC NAME_PTR solvefor stmtlist "}" +kinetic_block : KINETIC NAME_PTR optional_solvefor statement_list "}" { $$ = new ast::KineticBlock($2, $3, $4); $$->set_token($1); @@ -1701,43 +1973,43 @@ kineticblk : KINETIC NAME_PTR solvefor stmtlist "}" ; -reaction : REACTION react REACT1 react "(" expr "," expr ")" +reaction_statement : REACTION react REACT1 react "(" expression "," expression ")" { auto op = ast::ReactionOperator(ast::LTMINUSGT); $$ = new ast::ReactionStatement($2, op, $4, $6, $8); } - | REACTION react LT LT "(" expr ")" + | REACTION react LT LT "(" expression ")" { auto op = ast::ReactionOperator(ast::LTLT); $$ = new ast::ReactionStatement($2, op, NULL, $6, NULL); } - | REACTION react "-" GT "(" expr ")" + | REACTION react "-" GT "(" expression ")" { auto op = ast::ReactionOperator(ast::MINUSGT); $$ = new ast::ReactionStatement($2, op, NULL, $6, NULL); } | REACTION error { - /** \todo Need to revisit reaction implementation */ + /** \todo Need to revisit reaction_statement implementation */ } ; -react : varname +react : variable_name { $$ = new ast::ReactVarName(nullptr, $1); } - | integer varname + | integer variable_name { $$ = new ast::ReactVarName($1, $2); } - | react "+" varname + | react "+" variable_name { auto op = ast::BinaryOperator(ast::BOP_ADDITION); auto variable = new ast::ReactVarName(nullptr, $3); $$ = new ast::BinaryExpression($1, op, variable); } - | react "+" integer varname + | react "+" integer variable_name { auto op = ast::BinaryOperator(ast::BOP_ADDITION); auto variable = new ast::ReactVarName($3, $4); @@ -1746,18 +2018,18 @@ react : varname ; -lagstmt : LAG name BY NAME_PTR +lag_statement : LAG name BY NAME_PTR { $$ = new ast::LagStatement($2, $4); } | LAG error { - error(scanner.loc, "lagstmt"); + error(scanner.loc, "lag_statement"); } ; -queuestmt : PUTQ name +queue_statement : PUTQ name { $$ = new ast::QueueStatement(new ast::QueueExpressionType(ast::PUT_QUEUE), $2); } @@ -1768,19 +2040,19 @@ queuestmt : PUTQ name ; -matchblk : MATCH "{" matchlist "}" +match_block : MATCH "{" match_list "}" { $$ = new ast::MatchBlock($3); } ; -matchlist : match +match_list : match { $$ = ast::MatchVector(); $$.emplace_back($1); } - | matchlist match + | match_list match { $1.emplace_back($2); $$ = $1; @@ -1792,13 +2064,13 @@ match : name { $$ = new ast::Match($1, NULL); } - | matchname "(" expr ")" "=" expr + | match_name "(" expression ")" "=" expression { auto op = ast::BinaryOperator(ast::BOP_ASSIGN); auto lhs = new ast::ParenExpression($3); auto rhs = $6; - auto expr = new ast::BinaryExpression(lhs, op, rhs); - $$ = new ast::Match($1, expr); + auto expression = new ast::BinaryExpression(lhs, op, rhs); + $$ = new ast::Match($1, expression); } | error { @@ -1807,7 +2079,10 @@ match : name ; -matchname : name { $$ = $1; } +match_name : name + { + $$ = $1; + } | name "[" NAME_PTR "]" { $$ = new ast::IndexedName($1, $3); @@ -1815,22 +2090,22 @@ matchname : name { $$ = $1; } ; -unitblk : UNITS "{" unitbody "}" +unit_block : UNITS "{" unit_block_body "}" { $$ = new ast::UnitBlock($3); } ; -unitbody : { +unit_block_body : { $$ = ast::ExpressionVector(); } - | unitbody unitdef + | unit_block_body unit_definition { $1.emplace_back($2); $$ = $1; } - | unitbody factordef + | unit_block_body factor_definition { $1.emplace_back($2); $$ = $1; @@ -1838,18 +2113,18 @@ unitbody : { ; -unitdef : unit "=" unit +unit_definition : unit "=" unit { $$ = new ast::UnitDef($1, $3); } | unit error { - error(scanner.loc, "unitdef "); + error(scanner.loc, "unit_definition "); } ; -factordef : NAME_PTR "=" real unit +factor_definition : NAME_PTR "=" double unit { $$ = new ast::FactorDef($1, $3, $4, NULL, NULL); $$->set_token(*($1->get_token())); @@ -1871,17 +2146,18 @@ factordef : NAME_PTR "=" real unit ; -constblk : CONSTANT "{" conststmt "}" +constant_block : CONSTANT "{" constant_statement "}" { $$ = new ast::ConstantBlock($3); } ; -conststmt : { +constant_statement : + { $$ = ast::ConstantStatementVector(); } - | conststmt NAME_PTR "=" number units + | constant_statement NAME_PTR "=" number units { auto constant = new ast::ConstantVar($2, $4, $5); $1.emplace_back(new ast::ConstantStatement(constant)); @@ -1890,28 +2166,34 @@ conststmt : { ; -tablestmt : TABLE tablst dependlst FROM expr TO expr WITH INTEGER_PTR +table_statement : TABLE optional_table_var_list optional_dependent_var_list FROM expression TO expression WITH INTEGER_PTR { $$ = new ast::TableStatement($2, $3, $5, $7, $9); } | TABLE error { - error(scanner.loc, "tablestmt"); + error(scanner.loc, "table_statement"); } ; -tablst : { $$ = ast::NameVector(); } - | tablst1 { $$ = $1; } +optional_table_var_list : + { + $$ = ast::NameVector(); + } + | table_var_list + { + $$ = $1; + } ; -tablst1 : Name +table_var_list : Name { $$ = ast::NameVector(); $$.emplace_back($1); } - | tablst1 "," Name + | table_var_list "," Name { $1.emplace_back($3); $$ = $1; @@ -1919,12 +2201,18 @@ tablst1 : Name ; -dependlst : { $$ = ast::NameVector(); } - | DEPEND tablst1 { $$ = $2; } +optional_dependent_var_list : + { + $$ = ast::NameVector(); + } + | DEPEND table_var_list + { + $$ = $2; + } ; -neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE +neuron_block : NEURON OPEN_BRACE neuron_statement CLOSE_BRACE { auto block = new ast::StatementBlock($3); block->set_token($2); @@ -1933,58 +2221,61 @@ neuronblk : NEURON OPEN_BRACE nrnstmt CLOSE_BRACE ; -nrnstmt : { $$ = ast::StatementVector(); } - | nrnstmt SUFFIX NAME_PTR +neuron_statement : + { + $$ = ast::StatementVector(); + } + | neuron_statement SUFFIX NAME_PTR { $1.emplace_back(new ast::Suffix($2.clone(), $3)); $$ = $1; } - | nrnstmt nrnuse + | neuron_statement use_ion_statement { $1.emplace_back($2); $$ = $1; } - | nrnstmt NONSPECIFIC nrnonspeclist + | neuron_statement NONSPECIFIC nonspecific_var_list { $1.emplace_back(new ast::Nonspecific($3)); $$ = $1; } - | nrnstmt ELECTRODE_CURRENT nrneclist + | neuron_statement ELECTRODE_CURRENT electrode_current_var_list { $1.emplace_back(new ast::ElctrodeCurrent($3)); $$ = $1; } - | nrnstmt SECTION nrnseclist + | neuron_statement SECTION section_var_list { $1.emplace_back(new ast::Section($3)); $$ = $1; } - | nrnstmt RANGE nrnrangelist + | neuron_statement RANGE range_var_list { $1.emplace_back(new ast::Range($3)); $$ = $1; } - | nrnstmt GLOBAL nrnglobalist + | neuron_statement GLOBAL global_var_list { $1.emplace_back(new ast::Global($3)); $$ = $1; } - | nrnstmt POINTER nrnptrlist + | neuron_statement POINTER pointer_var_list { $1.emplace_back(new ast::Pointer($3)); $$ = $1; } - | nrnstmt BBCOREPOINTER nrnbbptrlist + | neuron_statement BBCOREPOINTER bbcore_pointer_var_list { - $1.emplace_back(new ast::BbcorePtr($3)); + $1.emplace_back(new ast::BbcorePointer($3)); $$ = $1; } - | nrnstmt EXTERNAL nrnextlist + | neuron_statement EXTERNAL external_var_list { $1.emplace_back(new ast::External($3)); $$ = $1; } - | nrnstmt THREADSAFE opthsafelist + | neuron_statement THREADSAFE optional_threadsafe_var_list { $1.emplace_back(new ast::ThreadSafe($3)); $$ = $1; @@ -1992,206 +2283,212 @@ nrnstmt : { $$ = ast::StatementVector(); } ; -nrnuse : USEION NAME_PTR READ nrnionrlist valence +use_ion_statement : USEION NAME_PTR READ read_ion_list valence { $$ = new ast::Useion($2, $4, ast::WriteIonVarVector(), $5); } - | USEION NAME_PTR WRITE nrnionwlist valence + | USEION NAME_PTR WRITE write_ion_list valence { $$ = new ast::Useion($2, ast::ReadIonVarVector(), $4, $5); } - | USEION NAME_PTR READ nrnionrlist WRITE nrnionwlist valence + | USEION NAME_PTR READ read_ion_list WRITE write_ion_list valence { $$ = new ast::Useion($2, $4, $6, $7); } | USEION error { - error(scanner.loc, "nrnuse"); + error(scanner.loc, "use_ion_statement"); } ; -nrnionrlist : NAME_PTR +read_ion_list : NAME_PTR { $$ = ast::ReadIonVarVector(); $$.emplace_back(new ast::ReadIonVar($1)); } - | nrnionrlist "," NAME_PTR + | read_ion_list "," NAME_PTR { $1.emplace_back(new ast::ReadIonVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnionrlist"); + error(scanner.loc, "read_ion_list"); } ; -nrnionwlist : NAME_PTR +write_ion_list : NAME_PTR { $$ = ast::WriteIonVarVector(); $$.emplace_back(new ast::WriteIonVar($1)); } - | nrnionwlist "," NAME_PTR + | write_ion_list "," NAME_PTR { $1.emplace_back(new ast::WriteIonVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnionwlist"); + error(scanner.loc, "write_ion_list"); } ; -nrnonspeclist : NAME_PTR +nonspecific_var_list : NAME_PTR { $$ = ast::NonspecificCurVarVector(); $$.emplace_back(new ast::NonspecificCurVar($1)); } - | nrnonspeclist "," NAME_PTR + | nonspecific_var_list "," NAME_PTR { $1.emplace_back(new ast::NonspecificCurVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnonspeclist"); + error(scanner.loc, "nonspecific_var_list"); } ; -nrneclist : NAME_PTR +electrode_current_var_list : NAME_PTR { $$ = ast::ElectrodeCurVarVector(); $$.emplace_back(new ast::ElectrodeCurVar($1)); } - | nrneclist "," NAME_PTR + | electrode_current_var_list "," NAME_PTR { $1.emplace_back(new ast::ElectrodeCurVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrneclist"); + error(scanner.loc, "electrode_current_var_list"); } ; -nrnseclist : NAME_PTR +section_var_list : NAME_PTR { $$ = ast::SectionVarVector(); $$.emplace_back(new ast::SectionVar($1)); } - | nrnseclist "," NAME_PTR + | section_var_list "," NAME_PTR { $1.emplace_back(new ast::SectionVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnseclist"); + error(scanner.loc, "section_var_list"); } ; -nrnrangelist : NAME_PTR +range_var_list : NAME_PTR { $$ = ast::RangeVarVector(); $$.emplace_back(new ast::RangeVar($1)); } - | nrnrangelist "," NAME_PTR + | range_var_list "," NAME_PTR { $1.emplace_back(new ast::RangeVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnrangelist"); + error(scanner.loc, "range_var_list"); } ; -nrnglobalist : NAME_PTR +global_var_list: NAME_PTR { $$ = ast::GlobalVarVector(); $$.emplace_back(new ast::GlobalVar($1)); } - | nrnglobalist "," NAME_PTR + | global_var_list "," NAME_PTR { $1.emplace_back(new ast::GlobalVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnglobalist"); + error(scanner.loc, "global_var_list"); } ; -nrnptrlist : NAME_PTR +pointer_var_list : NAME_PTR { $$ = ast::PointerVarVector(); $$.emplace_back(new ast::PointerVar($1)); } - | nrnptrlist "," NAME_PTR + | pointer_var_list "," NAME_PTR { $1.emplace_back(new ast::PointerVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnptrlist"); + error(scanner.loc, "pointer_var_list"); } ; -nrnbbptrlist : NAME_PTR +bbcore_pointer_var_list : NAME_PTR { $$ = ast::BbcorePointerVarVector(); $$.emplace_back(new ast::BbcorePointerVar($1)); } - | nrnbbptrlist "," NAME_PTR + | bbcore_pointer_var_list "," NAME_PTR { $1.emplace_back(new ast::BbcorePointerVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnbbptrlist"); + error(scanner.loc, "bbcore_pointer_var_list"); } ; -nrnextlist : NAME_PTR +external_var_list : NAME_PTR { $$ = ast::ExternVarVector(); $$.emplace_back(new ast::ExternVar($1)); } - | nrnextlist "," NAME_PTR + | external_var_list "," NAME_PTR { $1.emplace_back(new ast::ExternVar($3)); $$ = $1; } | error { - error(scanner.loc, "nrnextlist"); + error(scanner.loc, "external_var_list"); } ; -opthsafelist : { $$ = ast::ThreadsafeVarVector(); } - | threadsafelist { $$ = $1; } +optional_threadsafe_var_list : + { + $$ = ast::ThreadsafeVarVector(); + } + | threadsafe_var_list + { + $$ = $1; + } ; -threadsafelist : NAME_PTR +threadsafe_var_list : NAME_PTR { $$ = ast::ThreadsafeVarVector(); $$.emplace_back(new ast::ThreadsafeVar($1)); } - | threadsafelist "," NAME_PTR + | threadsafe_var_list "," NAME_PTR { $1.emplace_back(new ast::ThreadsafeVar($3)); $$ = $1; @@ -2199,12 +2496,15 @@ threadsafelist : NAME_PTR ; -valence : { $$ = nullptr; } - | VALENCE real +valence : + { + $$ = nullptr; + } + | VALENCE double { $$ = new ast::Valence($1.clone(), $2); } - | VALENCE "-" real + | VALENCE "-" double { $3->negate(); $$ = new ast::Valence($1.clone(), $3); diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index d5944f75f0..415d570ff0 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -29,7 +29,7 @@ bool Symbol::is_variable() { | NmodlType::pointer_var | NmodlType::bbcore_pointer_var | NmodlType::extern_var - | NmodlType::dependent_def + | NmodlType::assigned_definition | NmodlType::read_ion_var | NmodlType::write_ion_var | NmodlType::nonspecific_cur_var diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index f90c6c19b9..fb6c4e92ea 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -54,8 +54,8 @@ std::vector<std::string> to_string_vector(const NmodlType& obj) { properties.emplace_back("prime_name"); } - if (has_property(obj, NmodlType::dependent_def)) { - properties.emplace_back("dependent_def"); + if (has_property(obj, NmodlType::assigned_definition)) { + properties.emplace_back("assigned_definition"); } if (has_property(obj, NmodlType::unit_def)) { @@ -110,8 +110,8 @@ std::vector<std::string> to_string_vector(const NmodlType& obj) { properties.emplace_back("table_statement_var"); } - if (has_property(obj, NmodlType::table_dependent_var)) { - properties.emplace_back("table_dependent_var"); + if (has_property(obj, NmodlType::table_assigned_var)) { + properties.emplace_back("table_assigned_var"); } if (has_property(obj, NmodlType::constant_var)) { diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 3eec57ec82..46893a816b 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -141,8 +141,8 @@ enum class NmodlType : enum_type { /// Prime Type prime_name = 1L << 7, - /// Dependent Def - dependent_def = 1L << 8, + /// Assigned Definition + assigned_definition = 1L << 8, /// Unit Def unit_def = 1L << 9, @@ -213,8 +213,8 @@ enum class NmodlType : enum_type { /// variable is used in table statement table_statement_var = 1L << 31, - /// variable is used in table as dependent - table_dependent_var = 1L << 32, + /// variable is used in table as assigned + table_assigned_var = 1L << 32, /// Discrete Block discrete_block = 1L << 33, diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 4d246decc3..484bd1141d 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -276,10 +276,10 @@ void ModelSymbolTable::update_order(const std::shared_ptr<Symbol>& present_symbo auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; bool is_parameter = new_symbol->has_any_property(NmodlType::param_assign); - bool is_dependent_def = new_symbol->has_any_property(NmodlType::dependent_def); + bool is_assigned_definition = new_symbol->has_any_property(NmodlType::assigned_definition); if (symbol->get_definition_order() == -1) { - if (is_parameter || is_dependent_def) { + if (is_parameter || is_assigned_definition) { symbol->set_definition_order(definition_order++); } } diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 159baf2060..0750db8752 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -78,7 +78,7 @@ std::vector<std::string> LocalizeVisitor::variables_to_optimize() { | NmodlType::section_var; NmodlType global_var_properties = NmodlType::range_var - | NmodlType::dependent_def + | NmodlType::assigned_definition | NmodlType::param_assign; // clang-format on diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 41be534521..e8555fbbdf 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -222,7 +222,8 @@ void PerfVisitor::count_variables() { /// assigned block are not treated as range num_instance_variables = 0; - NmodlType property = NmodlType::range_var | NmodlType::dependent_def | NmodlType::state_var; + NmodlType property = NmodlType::range_var | NmodlType::assigned_definition | + NmodlType::state_var; auto variables = current_symtab->get_variables_with_properties(property); for (auto& variable: variables) { @@ -257,7 +258,7 @@ void PerfVisitor::count_variables() { num_global_variables = 0; for (auto& variable: variables) { auto is_global = variable->has_any_property(NmodlType::global_var); - property = NmodlType::range_var | NmodlType::dependent_def; + property = NmodlType::range_var | NmodlType::assigned_definition; if (!variable->has_any_property(property) || is_global) { num_global_variables++; if (variable->has_any_property(NmodlType::param_assign)) { diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 739ad9915b..dfb42638f8 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -94,8 +94,8 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { } } - if (node->is_dependent_def()) { - auto variable = dynamic_cast<ast::DependentDef*>(node); + if (node->is_assigned_definition()) { + auto variable = dynamic_cast<ast::AssignedDefinition*>(node); auto length = variable->get_length(); if (length) { symbol->set_as_array(length->eval()); @@ -237,7 +237,7 @@ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { }; int num_values = node->get_with()->eval() + 1; update_symbol(node->get_table_vars(), NmodlType::table_statement_var, num_values); - update_symbol(node->get_depend_vars(), NmodlType::table_dependent_var, num_values); + update_symbol(node->get_depend_vars(), NmodlType::table_assigned_var, num_values); } } // namespace visitor diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 5e5860fe2b..2f67da90a2 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -131,7 +131,7 @@ std::set<std::string> get_global_vars(Program* node) { // NB: local_var included here as locals can be declared at global scope NmodlType property = NmodlType::global_var | NmodlType::local_var | NmodlType::range_var | NmodlType::param_assign | NmodlType::extern_var | - NmodlType::prime_name | NmodlType::dependent_def | + NmodlType::prime_name | NmodlType::assigned_definition | NmodlType::read_ion_var | NmodlType::write_ion_var | NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | NmodlType::section_var | NmodlType::constant_var | diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 9ffbdda520..66da722853 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -62,7 +62,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 363"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 364"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/pybind/test_symtab.py index 7afe21527f..836ed76d69 100644 --- a/test/nmodl/transpiler/pybind/test_symtab.py +++ b/test/nmodl/transpiler/pybind/test_symtab.py @@ -25,7 +25,7 @@ def test_symtab(ch_ast): variables = s.get_variables_with_properties(symtab.NmodlType.range_var, True) assert len(variables) == 2 - assert str(variables[0]) == "mInf [Properties : range dependent_def]" + assert str(variables[0]) == "mInf [Properties : range assigned_definition]" def test_visitor_python_io(ch_ast): diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 8c1af9ca9c..aae02952d4 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -188,9 +188,10 @@ SCENARIO("Symbol table allows operations like insert, lookup") { REQUIRE(variables.empty()); WHEN("added global symbol") { auto next_symbol = std::make_shared<Symbol>("gamma", ModToken()); - next_symbol->add_property(NmodlType::dependent_def); + next_symbol->add_property(NmodlType::assigned_definition); table->insert(next_symbol); - auto variables = table->get_variables_with_properties(NmodlType::dependent_def); + auto variables = table->get_variables_with_properties( + NmodlType::assigned_definition); THEN("table has global variable") { REQUIRE(variables.size() == 1); } @@ -216,7 +217,7 @@ SCENARIO("Symbol table allows operations like insert, lookup") { symbol1->add_property(NmodlType::range_var | NmodlType::param_assign); symbol2->add_property(NmodlType::range_var | NmodlType::param_assign | NmodlType::state_var); - symbol3->add_property(NmodlType::range_var | NmodlType::dependent_def | + symbol3->add_property(NmodlType::range_var | NmodlType::assigned_definition | NmodlType::pointer_var); symbol4->add_property(NmodlType::range_var); @@ -240,7 +241,7 @@ SCENARIO("Symbol table allows operations like insert, lookup") { with = NmodlType::range_var; - without = NmodlType::param_assign | NmodlType::dependent_def; + without = NmodlType::param_assign | NmodlType::assigned_definition; result = table->get_variables(with, without); REQUIRE(result.size() == 1); REQUIRE(result[0]->get_name() == "delta"); diff --git a/test/nmodl/transpiler/visitor/perf.cpp b/test/nmodl/transpiler/visitor/perf.cpp index aef1c1aeea..41f36a6ca7 100644 --- a/test/nmodl/transpiler/visitor/perf.cpp +++ b/test/nmodl/transpiler/visitor/perf.cpp @@ -91,7 +91,7 @@ SCENARIO("Symbol table generation with Perf stat visitor", "[visitor][performanc THEN("Can lookup for defined variables") { auto symbol = symtab->lookup("m"); - REQUIRE(symbol->has_any_property(NmodlType::dependent_def)); + REQUIRE(symbol->has_any_property(NmodlType::assigned_definition)); REQUIRE_FALSE(symbol->has_any_property(NmodlType::local_var)); symbol = symtab->lookup("gNaTs2_tbar"); From 0f9f972af6a9310c7ceb7e391da38aa99165fe9e Mon Sep 17 00:00:00 2001 From: Liam Keegan <liam@keegan.ch> Date: Thu, 2 May 2019 11:59:54 +0100 Subject: [PATCH 200/871] Update documentation in sympy notebooks (BlueBrain/nmodl#169) - update & extend existing notebooks - add separate notebook for cnexp, sparse and derivimplicit solvers - add `! pip install nmodl` to each notebook for use on colab cloud jupyter notebook service - remove outdated sympy-solver notebook - update README and index accordingly NMODL Repo SHA: BlueBrain/nmodl@3c58ce6d062ce4550c46b4383a6cdf452a12b7b5 --- docs/nmodl/transpiler/index.rst | 4 +- docs/nmodl/transpiler/notebooks/README.md | 16 +- .../notebooks/nmodl-kinetic-schemes.ipynb | 23 +- .../notebooks/nmodl-linear-solver.ipynb | 6 +- .../notebooks/nmodl-nonlinear-solver.ipynb | 34 +- .../notebooks/nmodl-odes-overview.ipynb | 4 +- .../notebooks/nmodl-python-tutorial.ipynb | 24 -- .../notebooks/nmodl-sympy-conductance.ipynb | 27 +- .../notebooks/nmodl-sympy-solver-cnexp.ipynb | 297 +++++++++++++++ .../nmodl-sympy-solver-derivimplicit.ipynb | 243 +++++++++++++ .../notebooks/nmodl-sympy-solver-sparse.ipynb | 243 +++++++++++++ .../notebooks/nmodl-sympy-solver.ipynb | 338 ------------------ 12 files changed, 864 insertions(+), 395 deletions(-) create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb create mode 100644 docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb delete mode 100644 docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index b8d68b1981..9f9937a22e 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -25,7 +25,9 @@ About NMODL notebooks/nmodl-python-tutorial.ipynb notebooks/nmodl-odes-overview.ipynb notebooks/nmodl-kinetic-schemes.ipynb - notebooks/nmodl-sympy-solver.ipynb + notebooks/nmodl-sympy-solver-cnexp.ipynb + notebooks/nmodl-sympy-solver-sparse.ipynb + notebooks/nmodl-sympy-solver-derivimplicit.ipynb notebooks/nmodl-linear-solver.ipynb notebooks/nmodl-nonlinear-solver.ipynb notebooks/nmodl-sympy-conductance.ipynb diff --git a/docs/nmodl/transpiler/notebooks/README.md b/docs/nmodl/transpiler/notebooks/README.md index ffd4f708e8..76cbf981f5 100644 --- a/docs/nmodl/transpiler/notebooks/README.md +++ b/docs/nmodl/transpiler/notebooks/README.md @@ -1,14 +1,16 @@ ## NMODL jupyter notebooks To get started with the NMODL python interface: - - [nmodl-python-tutorial.ipynb](nmodl-python-tutorial.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-python-tutorial.ipynb) + - [nmodl-python-tutorial.ipynb](nmodl-python-tutorial.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-python-tutorial.ipynb) For an overview of ODEs in NODL: - - [nmodl-odes-overview.ipynb](nmodl-odes-overview.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-odes-overview.ipynb) + - [nmodl-odes-overview.ipynb](nmodl-odes-overview.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-odes-overview.ipynb) For more specific implementation details: - - [nmodl-kinetic-schemes.ipynb](nmodl-kinetic-schemes.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-kinetic-schemes.ipynb) - - [nmodl-sympy-solver.ipynb](nmodl-sympy-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-sympy-solver.ipynb) - - [nmodl-linear-solver.ipynb](nmodl-linear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-linear-solver.ipynb) - - [nmodl-nonlinear-solver.ipynb](nmodl-nonlinear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-nonlinear-solver.ipynb) - - [nmodl-sympy-conductance.ipynb](nmodl-sympy-conductance.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/add_sympy_documentation/docs/notebooks/nmodl-sympy-conductance.ipynb) + - [nmodl-kinetic-schemes.ipynb](nmodl-kinetic-schemes.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-kinetic-schemes.ipynb) + - [nmodl-sympy-solver-cnexp.ipynb](nmodl-sympy-solver-cnexp.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-cnexp.ipynb) + - [nmodl-sympy-solver-sparse.ipynb](nmodl-sympy-solver-sparse.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-sparse.ipynb) + - [nmodl-sympy-solver-derivimplicit.ipynb](nmodl-sympy-solver-derivimplicit.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-derivimplicit.ipynb) + - [nmodl-linear-solver.ipynb](nmodl-linear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-linear-solver.ipynb) + - [nmodl-nonlinear-solver.ipynb](nmodl-nonlinear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-nonlinear-solver.ipynb) + - [nmodl-sympy-conductance.ipynb](nmodl-sympy-conductance.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-conductance.ipynb) diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb index e85e883549..5f67bb7b4f 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -145,6 +145,16 @@ "execution_count": 1, "metadata": {}, "outputs": [], + "source": [ + "%%capture\n", + "! pip install nmodl" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ "import nmodl.dsl as nmodl\n", "\n", @@ -152,8 +162,7 @@ "def run_kinetic_visitor_and_return_derivative(mod_string):\n", " # parse NMDOL file (supplied as a string) into AST\n", " driver = nmodl.NmodlDriver()\n", - " driver.parse_string(mod_string)\n", - " AST = driver.get_ast()\n", + " AST = driver.parse_string(mod_string)\n", " # run SymtabVisitor to generate Symbol Table\n", " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", " # constant folding, inlining & local variable renaming passes\n", @@ -244,7 +253,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -280,7 +289,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -315,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": { "scrolled": true }, @@ -352,7 +361,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": { "scrolled": true }, @@ -390,7 +399,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { diff --git a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb index fe6739b81f..a2cc24b0b2 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb @@ -4,15 +4,15 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### [wip] NMODL LINEAR solver\n", + "### NMODL LINEAR solver\n", "\n", "`LINEAR` blocks contain a set of simultaneous equations.\n", "\n", "These are solved by `solve_lin_system` from [nmodl/ode.py](https://github.com/BlueBrain/nmodl/blob/master/nmodl/ode.py#L143).\n", "\n", - "If the system is sufficiently small (by default $N\\leq3$), then Gaussian Elimination is used to directly construct the solution at compile time using SymPy to do the symbolic Gaussian Elimination.\n", + "If the system is sufficiently small (by default $N\\leq3$), then Gaussian elimination is used to directly construct the solution at compile time using SymPy to do the symbolic Gaussian elimination. Optionally Common Subexpression Elimination (CSE) can also be performed.\n", "\n", - "Otherwise at run time by LU factorization with partial pivoting." + "For larger matrices it may not be numerically safe to solve them at compile time by Gaussian elimination, so instead the matrix equation is constructed and then solved at run time by LU factorization with partial pivoting, using the `PartialPivLU()` function from the Eigen header-only matrix algebra library." ] }, { diff --git a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb index 9c9de2099f..ccaf052521 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb @@ -4,15 +4,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### [wip] NMODL NONLINEAR solver\n", + "### NMODL NONLINEAR solver\n", "\n", - "`NONLINEAR` blocks contain a set of non-linear simultaneous equations.\n", + "`NONLINEAR` blocks contain a set of non-linear simultaneous equations. They can be directly specified by the user in the MOD file, or more commonly can be constructed from `KINETIC` or `DERIVATIVE` blocks by the `KineticBlock` and `SympySolver` visitors.\n", "\n", - "These are solved at runtime using Newton iteration to find the root of a function $F(X)$ with Jacobian $J = \\frac{\\partial F(X)_i}{\\partial X_j}$, where $F$ and $J$ are constructed by the [solve_non_lin_system](https://github.com/BlueBrain/nmodl/blob/master/nmodl/ode.py#L209) python routine which uses SymPy to analytically differentiate $F$ to find the Jacobian.\n", + "These equations are rewritten in the form\n", "\n", - "The Newton solver is called `newton_solver` and is implemented in [src/solver/newton](https://github.com/BlueBrain/nmodl/blob/master/src/solver/newton/newton.hpp#L33) using the Eigen matrix algebra library.\n", + "$$\n", + "F(X) = 0,\n", + "$$\n", "\n", - "A fall-back solution if the analytic Jacobian is not available is to use the `newton_numerical_diff_solver` variant of this solver that uses a finite difference approximation to estimate the Jacobian numerically" + "where $X$ is a vector containing the state variables involved in the equations, and this equation is then solved at runtime using [Newton's method](https://en.wikipedia.org/wiki/Newton%27s_method#k_variables,_k_functions)\n", + "\n", + "This is an iterative method: given an approximate solution $X^{(n)}$, a better approximation is given by\n", + "\n", + "$$\n", + "X^{(n+1)} = X^{(n)} - J(X^{(n)})^{-1} F(X^{(n)})\n", + "$$\n", + "\n", + "where $J(X) = \\frac{\\partial F(X)_i}{\\partial X_j}$ is the Jacobian of $F$.\n", + "\n", + "#### Newton solver\n", + "\n", + "$F$ and $J$ are constructed by the [solve_non_lin_system](https://github.com/BlueBrain/nmodl/blob/master/nmodl/ode.py#L209) python routine which uses SymPy to analytically differentiate $F$ to find the exact Jacobian at compile time. The initial approximate solution $X_0$ is chosen to simply be the current values of the state variables: as long as $dt$ is not too large the solution at $t+dt$ should not lie too far away.\n", + "\n", + "The iterative Newton solver is called `newton_solver` and is implemented in [src/solver/newton](https://github.com/BlueBrain/nmodl/blob/master/src/solver/newton/newton.hpp#L33) using the Eigen header-only matrix algebra library. Unit tests are available in [test/newton/newton.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/newton/newton.cpp) with the tags \"`[analytic][solver]`\".\n", + "\n", + "#### Fall-back solver\n", + "\n", + "A fall-back solution if the analytic Jacobian is not available (for example if the equations use some custom neuron function calls that SymPy doesn't know how to differentiate, or if `VERBATIM` blocks are used within the `NONLINEAR` block) is to use the `newton_numerical_diff_solver` variant of this solver that uses a finite difference approximation to estimate the Jacobian numerically.\n", + "\n", + "This solver is also implemented in [src/solver/newton](https://github.com/BlueBrain/nmodl/blob/master/src/solver/newton/newton.hpp#L33), with Unit tests in [test/newton/newton.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/newton/newton.cpp) with the tags \"`[numerical][solver]`\".\n", + "\n", + "**NB: this solver is implemented, but the visitor to set up the solve is not yet implemented**" ] }, { diff --git a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb index 9b49adbcf1..3885bdefee 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb @@ -32,25 +32,27 @@ " - `cnexp`\n", " - applicable if ODEs are linear & independent\n", " - exact analytic integration\n", + " - see the [nmodl-sympy-solver-cnexp](nmodl-sympy-solver-cnexp.ipynb) notebook for more details\n", " - `sparse`\n", " - applicable if ODEs are linear & coupled\n", " - backwards Euler numerical integration scheme: $f(t+\\Delta t) = f(t) + dt f'(t+ \\Delta t)$\n", " - results in a linear algebraic system to solve\n", " - numerically stable\n", " - $\\mathcal{O}(\\Delta t)$ integration error\n", + " - see the [nmodl-sympy-solver-sparse](nmodl-sympy-solver-sparse.ipynb) notebook for more details\n", " - `derivimplcit`\n", " - always applicable\n", " - backwards Euler numerical integration scheme: $f(t+\\Delta t) = f(t) + dt f'(t+ \\Delta t)$\n", " - results in a non-linear algebraic system to solve\n", " - numerically stable\n", " - $\\mathcal{O}(\\Delta t)$ integration error\n", + " - see the [nmodl-sympy-solver-sparse](nmodl-sympy-solver-derivimplicit.ipynb) notebook for more details\n", " - `euler`\n", " - always applicable\n", " - forwards Euler numerical integration scheme: $f(t+\\Delta t) = f(t) + \\Delta t f'(t)$\n", " - numerically unstable\n", " - $\\mathcal{O}(\\Delta t)$ integration error\n", " - not recommended due to instability of scheme\n", - " - see the [nmodl-sympy-solver](nmodl-sympy-solver.ipynb) notebook for more details\n", "***\n", "`LINEAR` block\n", " - system of linear algebraic equations\n", diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index 22837cd671..b4ff1018de 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -359,33 +359,9 @@ "\n", "NMODL is a code generation framework for NEURON Modeling Language. It is primarily designed to support optimised code generation backends for morphologically detailed neuron simulators. It provides high level Python interface that can be used for model introspection as well as performing various analysis on underlying model.\n", "\n", - "The main goals of the NMODL framework are :\n", - "\n", - "* Support for full NMODL specification\n", - "* Providing modular tools for lexing, parsing and analysis\n", - "* Optimised code generation for modern architectures\n", - "* Compatibility with exisiting simulators\n", - "* Ability to implement new simulator backends with minimal efforts\n", - "\n", "This tutorial provides introduction to python API with examples." ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Installation \n", - "<a id='installation'></a> NMODL can be installed as CMake project or using python setuptools. See README.md for detailed installation instructions. For example :\n", - "\n", - "```bash\n", - "python3 -m venv myenv\n", - ". myenv/bin/activate\n", - "pip3 install nmodl_source_directory/\n", - "```\n", - "\n", - "With this you should have nmodl installed." - ] - }, { "cell_type": "markdown", "metadata": {}, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb index 0d913262f8..367e043b19 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb @@ -86,6 +86,16 @@ "execution_count": 1, "metadata": {}, "outputs": [], + "source": [ + "%%capture\n", + "! pip install nmodl" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], "source": [ "import nmodl.dsl as nmodl\n", "\n", @@ -93,8 +103,7 @@ "def run_conductance_visitor_and_return_breakpoint(mod_string):\n", " # parse NMDOL file (supplied as a string) into AST\n", " driver = nmodl.NmodlDriver()\n", - " driver.parse_string(mod_string)\n", - " AST = driver.get_ast()\n", + " AST = driver.parse_string(mod_string)\n", " # run SymtabVisitor to generate Symbol Table\n", " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", " # constant folding, inlining & local variable renaming passes\n", @@ -123,7 +132,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -163,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -203,7 +212,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -243,7 +252,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -283,7 +292,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -339,7 +348,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -388,7 +397,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb new file mode 100644 index 0000000000..142e286f58 --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb @@ -0,0 +1,297 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL SympySolver - cnexp\n", + "\n", + "This notebook describes the implementation of the `cnexp` part of the `SympySolverVisitor`, which solves the systems of ODEs defined in `DERIVATIVE` blocks when these ODEs are *linear* and *independent*.\n", + "\n", + "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", + "\n", + "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "***\n", + "#### Implementation\n", + "The `SympySolverVisitor` for solver method `cnexp` does the following:\n", + "\n", + "* Get list of all global scope variables from the Symbol Table, as well as any local variables in DERIVATIVE block\n", + "* For each differential equation in DERIVATIVE block:\n", + " * Parse equation into SymPy, giving it the list of variables\n", + " * This gives us a differential equation of the form:\n", + " * $\\frac{dm}{dt} = f(m, \\dots)$\n", + " * where the function $f$ depends on $m$, as well as possibly other variables reprensented by $\\dots$ which we assume do not depend on $m$ or $t$\n", + " * Solve equation analytically using [sympy.dsolve](https://docs.sympy.org/latest/modules/solvers/ode.html) to give a solution of the form:\n", + " * $m(t+dt) = g(m(t), dt, \\dots)$\n", + " * where $g$ is some function that depends on the value of $m$ at time t, the timestep $dt$, and the other variables ($\\dots$).\n", + " * Replace ODE with analytic solution as C code using [sympy.printing.ccode](https://docs.sympy.org/latest/_modules/sympy/printing/ccode.html)\n", + " * If we fail to find a solution then do nothing - so currently NMODL reverts to using the legacy CNEXP solver routine (same as mod2c or nocmodl)\n", + "***\n", + "#### Pade approximant\n", + "There is an option `use_pade_approx` which if enabled does the following extra step:\n", + "\n", + "* Given the analytic solution $f(t)$:\n", + " * Expand the solution in a Taylor series in `dt`, extract the coefficients $a_i$\n", + " * $f(t + dt) = f(t) + dt f'(t) + dt^2 f''(t) / 2 + \\dots = a_0 + a_1 dt + a_2 dt^2 + \\dots$\n", + " * Construct the (1,1) Pade approximant to the solution using these Taylor coefficients\n", + " * $f_{PADE}(t+dt) = (a_0 a_1 + (a_1^2 - a_0 a_2) dt)/(a_1 - a_2 dt)$\n", + " * Return this approximate solution (correct to second order in $dt$) as C code\n", + "\n", + "(Replacing the exponential with a Pade aproximant here was suggested in sec 5.2 of (https://www.eccomas2016.org/proceedings/pdf/7366.pdf) - since the overall numerical integration scheme in NEURON is only correct to first or second order in $dt$, it is valid to expand the analytic solution here to the same order and so avoid evaluating the exponential function)\n", + "***\n", + "#### Implementation Tests\n", + "The unit tests may be helpful to understand what these functions are doing\n", + " - `SympySolverVisitor` tests are located in [test/visitor/sympy_solver.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/sympy_solver.cpp), and tests involving `cnexp` have the tag \"`[cnexp]`\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "! pip install nmodl" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl\n", + "\n", + "\n", + "def run_sympy_solver(mod_string, pade=False):\n", + " # parse NMDOL file (supplied as a string) into AST\n", + " driver = nmodl.NmodlDriver()\n", + " AST = driver.parse_string(mod_string)\n", + " # run SymtabVisitor to generate Symbol Table\n", + " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", + " # constant folding, inlining & local variable renaming passes\n", + " nmodl.visitor.ConstantFolderVisitor().visit_program(AST)\n", + " nmodl.visitor.InlineVisitor().visit_program(AST)\n", + " nmodl.visitor.LocalVarRenameVisitor().visit_program(AST)\n", + " # run SympySolver visitor\n", + " nmodl.visitor.SympySolverVisitor(use_pade_approx=pade).visit_program(AST)\n", + " # return contents of new DERIVATIVE block as list of strings\n", + " return nmodl.to_nmodl(\n", + " nmodl.visitor.AstLookupVisitor().lookup(\n", + " AST, nmodl.ast.AstNodeType.DERIVATIVE_BLOCK\n", + " )[0]\n", + " ).splitlines()[1:-1]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 1\n", + "Single constant ODE" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exact solution:\t m = 4*dt+m\n", + "pade approx:\t m = 4*dt+m\n" + ] + } + ], + "source": [ + "ex1 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + "}\n", + "DERIVATIVE states {\n", + " m' = 4\n", + "}\n", + "\"\"\"\n", + "print(\"exact solution:\\t\", run_sympy_solver(ex1, pade=False)[0])\n", + "print(\"pade approx:\\t\", run_sympy_solver(ex1, pade=True)[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 2\n", + "Single linear ODE" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exact solution:\t m = m*exp(a*dt)\n", + "pade approx:\t m = -m*(a*dt+2)/(a*dt-2)\n" + ] + } + ], + "source": [ + "ex2 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + "}\n", + "DERIVATIVE states {\n", + " m' = a*m\n", + "}\n", + "\"\"\"\n", + "print(\"exact solution:\\t\", run_sympy_solver(ex2, pade=False)[0])\n", + "print(\"pade approx:\\t\", run_sympy_solver(ex2, pade=True)[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 3\n", + "Single linear ODE" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exact solution:\t m = minf-(-m+minf)*exp(-dt/mtau)\n", + "pade approx:\t m = (-dt*m+2*dt*minf+2*m*mtau)/(dt+2*mtau)\n" + ] + } + ], + "source": [ + "ex3 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + "}\n", + "DERIVATIVE states {\n", + " m' = (minf-m)/mtau\n", + "}\n", + "\"\"\"\n", + "print(\"exact solution:\\t\", run_sympy_solver(ex3, pade=False)[0])\n", + "print(\"pade approx:\\t\", run_sympy_solver(ex3, pade=True)[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 4\n", + "Single linear ODE that can be simplified" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exact solution:\t m = minf+(m-minf)*exp(dt/mtau)\n", + "pade approx:\t m = (-dt*m+2*dt*minf-2*m*mtau)/(dt-2*mtau)\n" + ] + } + ], + "source": [ + "ex4 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + "}\n", + "DERIVATIVE states {\n", + " m' = (minf-m)/mtau - m/mtau - 2*minf/mtau + 3*m/mtau\n", + "}\n", + "\"\"\"\n", + "print(\"exact solution:\\t\", run_sympy_solver(ex4, pade=False)[0])\n", + "print(\"pade approx:\\t\", run_sympy_solver(ex4, pade=True)[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 5\n", + "Single nonlinear ODE: unsupported, so does not modify DERIVATIVE block, leaves it to a later visitor pass to deal with" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exact solution:\t m' = m^3\n", + "pade approx:\t m' = m^3\n" + ] + } + ], + "source": [ + "ex5 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + "}\n", + "DERIVATIVE states {\n", + " m' = m^3\n", + "}\n", + "\"\"\"\n", + "print(\"exact solution:\\t\", run_sympy_solver(ex5, pade=False)[0])\n", + "print(\"pade approx:\\t\", run_sympy_solver(ex5, pade=True)[0])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb new file mode 100644 index 0000000000..1cacc49131 --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb @@ -0,0 +1,243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL SympySolver - derivimplicit\n", + "\n", + "This notebook describes the implementation of the `derivimplicit` part of the `SympySolverVisitor`, which solves the systems of ODEs defined in `DERIVATIVE` blocks when these ODEs are *nonlinear*.\n", + "\n", + "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", + "\n", + "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "***\n", + "#### Implementation\n", + "The `SympySolverVisitor` for solver method `derivimplicit` does the following:\n", + "\n", + "* Construct the implicit Euler equations to convert the sysytem of ODEs to a `NONLINEAR` block\n", + "* This `NONLINEAR` block is then solved as described in [nmodl-nonlinear-solver](nmodl-nonlinear-solver.ipynb)\n", + "***\n", + "#### Implementation Tests\n", + "The unit tests may be helpful to understand what these functions are doing\n", + " - `SympySolverVisitor` tests are located in [test/visitor/sympy_solver.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/sympy_solver.cpp), and tests involving the `derivimplicit` solver method have the tag \"`[derivimplicit]`\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "! pip install nmodl" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl\n", + "\n", + "\n", + "def run_sympy_solver(mod_string, cse=False):\n", + " # parse NMDOL file (supplied as a string) into AST\n", + " driver = nmodl.NmodlDriver()\n", + " AST = driver.parse_string(mod_string)\n", + " # run SymtabVisitor to generate Symbol Table\n", + " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", + " # constant folding, inlining & local variable renaming passes\n", + " nmodl.visitor.ConstantFolderVisitor().visit_program(AST)\n", + " nmodl.visitor.InlineVisitor().visit_program(AST)\n", + " nmodl.visitor.LocalVarRenameVisitor().visit_program(AST)\n", + " # run SympySolver visitor\n", + " nmodl.visitor.SympySolverVisitor().visit_program(AST)\n", + " # return new DERIVATIVE block\n", + " return nmodl.to_nmodl(\n", + " nmodl.visitor.AstLookupVisitor().lookup(\n", + " AST, nmodl.ast.AstNodeType.DERIVATIVE_BLOCK\n", + " )[0]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 1\n", + "2 coupled linear ODEs" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE states {\n", + " LOCAL old_mc, old_m, tmp0, tmp1, tmp2\n", + " old_mc = mc\n", + " old_m = m\n", + " tmp0 = a*dt\n", + " tmp1 = b*dt\n", + " tmp2 = 1/(tmp0+tmp1+1)\n", + " mc = tmp2*(old_m*tmp1+old_mc*tmp1+old_mc)\n", + " m = tmp2*(old_m*tmp0+old_m+old_mc*tmp0)\n", + "}\n" + ] + } + ], + "source": [ + "ex1 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD sparse\n", + "}\n", + "STATE {\n", + " mc m\n", + "}\n", + "DERIVATIVE states {\n", + " mc' = -a*mc + b*m\n", + " m' = a*mc - b*m\n", + "}\n", + "\"\"\"\n", + "print(run_sympy_solver(ex1, cse=True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 2\n", + "5 coupled linear ODEs" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE ihkin {\n", + " EIGEN_LINEAR_SOLVE[5]{\n", + " LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_o2, old_p0, old_p1\n", + " }{\n", + " evaluate_fct(v, cai)\n", + " old_c1 = c1\n", + " old_o1 = o1\n", + " old_o2 = o2\n", + " old_p0 = p0\n", + " old_p1 = p1\n", + " }{\n", + " X[0] = c1\n", + " X[1] = o1\n", + " X[2] = o2\n", + " X[3] = p0\n", + " X[4] = p1\n", + " F[0] = -old_c1\n", + " F[1] = -old_o1\n", + " F[2] = -old_o2\n", + " F[3] = -old_p0\n", + " F[4] = -old_p1\n", + " J[0] = -alpha*dt-1\n", + " J[5] = beta*dt\n", + " J[10] = 0\n", + " J[15] = 0\n", + " J[20] = 0\n", + " J[1] = alpha*dt\n", + " J[6] = -beta*dt-dt*k3p-1\n", + " J[11] = dt*k4\n", + " J[16] = 0\n", + " J[21] = 0\n", + " J[2] = 0\n", + " J[7] = dt*k3p\n", + " J[12] = -dt*k4-1\n", + " J[17] = 0\n", + " J[22] = 0\n", + " J[3] = 0\n", + " J[8] = 0\n", + " J[13] = 0\n", + " J[18] = -dt*k1ca-1\n", + " J[23] = dt*k2\n", + " J[4] = 0\n", + " J[9] = 0\n", + " J[14] = 0\n", + " J[19] = dt*k1ca\n", + " J[24] = -dt*k2-1\n", + " }{\n", + " c1 = X[0]\n", + " o1 = X[1]\n", + " o2 = X[2]\n", + " p0 = X[3]\n", + " p1 = X[4]\n", + " }{\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "ex2 = \"\"\"\n", + "STATE {\n", + " c1 o1 o2 p0 p1\n", + "}\n", + "BREAKPOINT {\n", + " SOLVE ihkin METHOD sparse\n", + "}\n", + "DERIVATIVE ihkin {\n", + " LOCAL alpha, beta, k3p, k4, k1ca, k2\n", + " evaluate_fct(v, cai)\n", + " c1' = (-1*(alpha*c1-beta*o1))\n", + " o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2))\n", + " o2' = (1*(k3p*o1-k4*o2))\n", + " p0' = (-1*(k1ca*p0-k2*p1))\n", + " p1' = (1*(k1ca*p0-k2*p1))\n", + "}\n", + "\"\"\"\n", + "print(run_sympy_solver(ex2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb new file mode 100644 index 0000000000..5c37d2242f --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb @@ -0,0 +1,243 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### NMODL SympySolver - sparse\n", + "\n", + "This notebook describes the implementation of the `sparse` part of the `SympySolverVisitor`, which solves the systems of ODEs defined in `DERIVATIVE` blocks when these ODEs are *linear* and *coupled*.\n", + "\n", + "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", + "\n", + "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "***\n", + "#### Implementation\n", + "The `SympySolverVisitor` for solver method `sparse` does the following:\n", + "\n", + "* Construct the implicit Euler equations to convert the sysytem of ODEs to a `LINEAR` block\n", + "* This `LINEAR` block is then solved as described in [nmodl-linear-solver](nmodl-linear-solver.ipynb)\n", + "***\n", + "#### Implementation Tests\n", + "The unit tests may be helpful to understand what these functions are doing\n", + " - `SympySolverVisitor` tests are located in [test/visitor/sympy_solver.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/sympy_solver.cpp), and tests using the `sparse` solver method have the tag \"`[sparse]`\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Examples" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture\n", + "! pip install nmodl" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import nmodl.dsl as nmodl\n", + "\n", + "\n", + "def run_sympy_solver(mod_string, cse=False):\n", + " # parse NMDOL file (supplied as a string) into AST\n", + " driver = nmodl.NmodlDriver()\n", + " AST = driver.parse_string(mod_string)\n", + " # run SymtabVisitor to generate Symbol Table\n", + " nmodl.symtab.SymtabVisitor().visit_program(AST)\n", + " # constant folding, inlining & local variable renaming passes\n", + " nmodl.visitor.ConstantFolderVisitor().visit_program(AST)\n", + " nmodl.visitor.InlineVisitor().visit_program(AST)\n", + " nmodl.visitor.LocalVarRenameVisitor().visit_program(AST)\n", + " # run SympySolver visitor\n", + " nmodl.visitor.SympySolverVisitor().visit_program(AST)\n", + " # return contents of new DERIVATIVE block as list of strings\n", + " return nmodl.to_nmodl(\n", + " nmodl.visitor.AstLookupVisitor().lookup(\n", + " AST, nmodl.ast.AstNodeType.DERIVATIVE_BLOCK\n", + " )[0]\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 1\n", + "2 coupled linear ODEs" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE states {\n", + " LOCAL old_mc, old_m, tmp0, tmp1, tmp2\n", + " old_mc = mc\n", + " old_m = m\n", + " tmp0 = a*dt\n", + " tmp1 = b*dt\n", + " tmp2 = 1/(tmp0+tmp1+1)\n", + " mc = tmp2*(old_m*tmp1+old_mc*tmp1+old_mc)\n", + " m = tmp2*(old_m*tmp0+old_m+old_mc*tmp0)\n", + "}\n" + ] + } + ], + "source": [ + "ex1 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD sparse\n", + "}\n", + "STATE {\n", + " mc m\n", + "}\n", + "DERIVATIVE states {\n", + " mc' = -a*mc + b*m\n", + " m' = a*mc - b*m\n", + "}\n", + "\"\"\"\n", + "print(run_sympy_solver(ex1, cse=True))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 2\n", + "5 coupled linear ODEs" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "DERIVATIVE ihkin {\n", + " EIGEN_LINEAR_SOLVE[5]{\n", + " LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_o2, old_p0, old_p1\n", + " }{\n", + " evaluate_fct(v, cai)\n", + " old_c1 = c1\n", + " old_o1 = o1\n", + " old_o2 = o2\n", + " old_p0 = p0\n", + " old_p1 = p1\n", + " }{\n", + " X[0] = c1\n", + " X[1] = o1\n", + " X[2] = o2\n", + " X[3] = p0\n", + " X[4] = p1\n", + " F[0] = -old_c1\n", + " F[1] = -old_o1\n", + " F[2] = -old_o2\n", + " F[3] = -old_p0\n", + " F[4] = -old_p1\n", + " J[0] = -alpha*dt-1\n", + " J[5] = beta*dt\n", + " J[10] = 0\n", + " J[15] = 0\n", + " J[20] = 0\n", + " J[1] = alpha*dt\n", + " J[6] = -beta*dt-dt*k3p-1\n", + " J[11] = dt*k4\n", + " J[16] = 0\n", + " J[21] = 0\n", + " J[2] = 0\n", + " J[7] = dt*k3p\n", + " J[12] = -dt*k4-1\n", + " J[17] = 0\n", + " J[22] = 0\n", + " J[3] = 0\n", + " J[8] = 0\n", + " J[13] = 0\n", + " J[18] = -dt*k1ca-1\n", + " J[23] = dt*k2\n", + " J[4] = 0\n", + " J[9] = 0\n", + " J[14] = 0\n", + " J[19] = dt*k1ca\n", + " J[24] = -dt*k2-1\n", + " }{\n", + " c1 = X[0]\n", + " o1 = X[1]\n", + " o2 = X[2]\n", + " p0 = X[3]\n", + " p1 = X[4]\n", + " }{\n", + " }\n", + "}\n" + ] + } + ], + "source": [ + "ex2 = \"\"\"\n", + "STATE {\n", + " c1 o1 o2 p0 p1\n", + "}\n", + "BREAKPOINT {\n", + " SOLVE ihkin METHOD sparse\n", + "}\n", + "DERIVATIVE ihkin {\n", + " LOCAL alpha, beta, k3p, k4, k1ca, k2\n", + " evaluate_fct(v, cai)\n", + " c1' = (-1*(alpha*c1-beta*o1))\n", + " o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2))\n", + " o2' = (1*(k3p*o1-k4*o2))\n", + " p0' = (-1*(k1ca*p0-k2*p1))\n", + " p1' = (1*(k1ca*p0-k2*p1))\n", + "}\n", + "\"\"\"\n", + "print(run_sympy_solver(ex2))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb deleted file mode 100644 index 0771d5dfa6..0000000000 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver.ipynb +++ /dev/null @@ -1,338 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### [wip] NMODL SympySolver\n", - "\n", - "This notebook describes the implementation of the `SympySolverVisitor`, which solves the systems of ODEs defined in `DERIVATIVE` blocks.\n", - "\n", - "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", - "\n", - "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb)." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import nmodl.dsl as nmodl\n", - "from nmodl.dsl import ast, symtab, visitor" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Starting with the `hh.mod` input file:" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "channel = \"\"\"\n", - "NEURON {\n", - " SUFFIX hh\n", - " USEION na READ ena WRITE ina\n", - " USEION k READ ek WRITE ik\n", - " NONSPECIFIC_CURRENT il\n", - " RANGE gnabar, gkbar, gl, el, gna, gk\n", - " RANGE minf, hinf, ninf, mtau, htau, ntau\n", - "}\n", - " \n", - "UNITS {\n", - " (mV) = (millivolt)\n", - " (S) = (siemens)\n", - "}\n", - " \n", - "PARAMETER {\n", - " gnabar = .12 (S/cm2)\n", - " gkbar = .036 (S/cm2)\n", - " gl = .0003 (S/cm2)\n", - " el = -54.3 (mV)\n", - " celsius\n", - "}\n", - " \n", - "STATE {\n", - " m h n\n", - "}\n", - " \n", - "ASSIGNED {\n", - " v (mV)\n", - " \n", - " gna (S/cm2)\n", - " gk (S/cm2)\n", - " minf\n", - " hinf\n", - " ninf\n", - " mtau (ms)\n", - " htau (ms)\n", - " ntau (ms)\n", - "}\n", - " \n", - "BREAKPOINT {\n", - " SOLVE states METHOD cnexp\n", - " gna = gnabar*m*m*m*h\n", - " ina = gna*(v - ena)\n", - " gk = gkbar*n*n*n*n\n", - " ik = gk*(v - ek)\n", - " il = gl*(v - el)\n", - "}\n", - " \n", - "INITIAL {\n", - " rates(v, celsius)\n", - " m = minf\n", - " h = hinf\n", - " n = ninf\n", - "}\n", - " \n", - "DERIVATIVE states {\n", - " rates(v, celsius)\n", - " m' = (minf-m)/mtau\n", - " h' = (hinf-h)/htau\n", - " n' = (ninf-n)/ntau\n", - "}\n", - " \n", - "PROCEDURE rates(v, celsius)\n", - "{\n", - " LOCAL alpha, beta, sum, q10\n", - " \n", - " q10 = 3^((celsius - 6.3)/10)\n", - " \n", - " :\"m\" sodium activation system\n", - " alpha = .1 * vtrap(-(v+40),10)\n", - " beta = 4 * exp(-(v+65)/18)\n", - " sum = alpha + beta\n", - " mtau = 1/(q10*sum)\n", - " minf = alpha/sum\n", - " \n", - " :\"h\" sodium inactivation system\n", - " alpha = .07 * exp(-(v+65)/20)\n", - " beta = 1 / (exp(-(v+35)/10) + 1)\n", - " sum = alpha + beta\n", - " htau = 1/(q10*sum)\n", - " hinf = alpha/sum\n", - " \n", - " :\"n\" potassium activation system\n", - " alpha = .01*vtrap(-(v+55),10)\n", - " beta = .125*exp(-(v+65)/80)\n", - " sum = alpha + beta\n", - " ntau = 1/(q10*sum)\n", - " ninf = alpha/sum\n", - "}\n", - " \n", - "FUNCTION vtrap(x,y) {\n", - " : use built in exprelr(z) = z/(exp(z)-1), which handles the z=0 case correctly\n", - " vtrap = y*exprelr(x/y)\n", - "}\n", - "\"\"\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### ODE Solver example" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "def parse_mod_to_ast(mod_string):\n", - " # parse NMDOL file (supplied as a string)\n", - " driver = nmodl.NmodlDriver()\n", - " driver.parse_string(mod_string)\n", - " modast = driver.get_ast()\n", - " # run SymtabVisitor to generate Symbol Table\n", - " symv = symtab.SymtabVisitor()\n", - " symv.visit_program(modast)\n", - " # return AST\n", - " return modast\n", - "\n", - "\n", - "lookup_visitor = visitor.AstLookupVisitor()" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "SOLVE states METHOD cnexp\n" - ] - } - ], - "source": [ - "# print solve method\n", - "modast = parse_mod_to_ast(channel)\n", - "print(nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.SOLVE_BLOCK)[0]))" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DERIVATIVE states {\n", - " rates(v, celsius)\n", - " m' = (minf-m)/mtau\n", - " h' = (hinf-h)/htau\n", - " n' = (ninf-n)/ntau\n", - "}\n" - ] - } - ], - "source": [ - "# print DERIVATIVE block\n", - "print(\n", - " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we run `SympySolverVisitor`, it does the following:\n", - "\n", - "* If the solver method is \"cnexp\":\n", - " * Get list of all global scope variables from the Symbol Table, as well as any local variables in DERIVATIVE block\n", - " * For each differential equation in DERIVATIVE block:\n", - " * Parse equation into SymPy, giving it the list of variables\n", - " * This gives us a differential equation of the form:\n", - " * $\\frac{dm}{dt} = f(m, \\dots)$\n", - " * where the function $f$ depends on $m$, as well as possibly other variables reprensented by $\\dots$ which we assume do not depend on $m$ or $t$\n", - " * Solve equation analytically using [sympy.dsolve](https://docs.sympy.org/latest/modules/solvers/ode.html) to give a solution of the form:\n", - " * $m(t+dt) = g(m(t), dt, \\dots)$\n", - " * where $g$ is some function that depends on the value of $m$ at time t, the timestep $dt$, and the other variables ($\\dots$).\n", - " * Return solution from SymPy as C code using [sympy.printing.ccode](https://docs.sympy.org/latest/_modules/sympy/printing/ccode.html)\n", - " * If we failed to find a solution then revert to existing CNEXP solver routine (same as mod2c or nocmodl)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [], - "source": [ - "sympy_solver_visitor = visitor.SympySolverVisitor()\n", - "sympy_solver_visitor.visit_program(modast)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "If we print the DERIVATIVE block again we see the results:" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DERIVATIVE states {\n", - " rates(v, celsius)\n", - " m = minf-(-m+minf)*exp(-dt/mtau)\n", - " h = hinf-(-h+hinf)*exp(-dt/htau)\n", - " n = ninf-(-n+ninf)*exp(-dt/ntau)\n", - "}\n" - ] - } - ], - "source": [ - "print(\n", - " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is an option `use_pade_approx` which if enabled does the following extra step:\n", - "\n", - "* Given the analytic solution $f(t)$:\n", - " * Expand the solution in a Taylor series in `dt`, extract the coefficients $a_i$\n", - " * $f(t + dt) = f(t) + dt f'(t) + dt^2 f''(t) / 2 + \\dots = a_0 + a_1 dt + a_2 dt^2 + \\dots$\n", - " * Construct the (1,1) Pade approximant to the solution using these Taylor coefficients\n", - " * $f_{PADE}(t+dt) = (a_0 a_1 + (a_1^2 - a_0 a_2) dt)/(a_1 - a_2 dt)$\n", - " * Return this approximate solution (correct to second order in $dt$) as C code\n", - "\n", - "(Replacing the exponential with a Pade aproximant here was suggested in sec 5.2 of (https://www.eccomas2016.org/proceedings/pdf/7366.pdf) - since the overall numerical integration scheme in NEURON is only correct to first or second order in $dt$, it is valid to expand the analytic solution here to the same order and so avoid evaluating the exponential function)\n", - "\n", - "If we now run `SympySolverVisitor` with `use_pade_approx=True`, and print the DERIVATIVE block again, we see the results:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "DERIVATIVE states {\n", - " rates(v, celsius)\n", - " m = (-dt*m+2*dt*minf+2*m*mtau)/(dt+2*mtau)\n", - " h = (-dt*h+2*dt*hinf+2*h*htau)/(dt+2*htau)\n", - " n = (-dt*n+2*dt*ninf+2*n*ntau)/(dt+2*ntau)\n", - "}\n" - ] - } - ], - "source": [ - "modast = parse_mod_to_ast(channel)\n", - "sympy_solver_visitor = visitor.SympySolverVisitor(use_pade_approx=True)\n", - "sympy_solver_visitor.visit_program(modast)\n", - "# print DERIVATIVE block\n", - "print(\n", - " nmodl.to_nmodl(lookup_visitor.lookup(modast, ast.AstNodeType.DERIVATIVE_BLOCK)[0])\n", - ")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.2" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From a5e8a6af472cf298e00eea3fa1eda797bd0c84f6 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Thu, 2 May 2019 20:51:43 +0200 Subject: [PATCH 201/871] Make ode solver aware of custom functions in some cases (BlueBrain/nmodl#160) Resolve BlueBrain/nmodl#118 but there are a few caveats - Ideally we would want to use mathematical and external functions much more flexibly, but the ODE solver is not really done for that and especially when the ODEs are solved analytically we only handle simple ODEs. - Now we added the ability to use in ODEs user functions (known C math functions and others) and at least for numerical ODE solving, they are taken into account by the sympy C code printer. NMODL Repo SHA: BlueBrain/nmodl@9f349f37ede797b666875215d30f5d7a910901df --- nmodl/ode.py | 36 +++++++++++++++---- src/nmodl/visitors/sympy_solver_visitor.cpp | 25 +++++++++---- src/nmodl/visitors/sympy_solver_visitor.hpp | 3 ++ .../nmodl/transpiler/visitor/sympy_solver.cpp | 19 ++++++++++ 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index e95006f770..431444afd6 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -5,12 +5,27 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** +from importlib import import_module + import sympy as sp +# import known_functions through low-level mechanism because the ccode +# module is overwritten in sympy and contents of that submodule cannot be +# accessed through regular imports +known_functions = import_module('sympy.printing.ccode').known_functions_C99 +known_functions.pop('Abs') +known_functions['abs'] = 'fabs' + major, minor = (int(v) for v in sp.__version__.split(".")[:2]) if not ((major >= 1) and (minor >= 2)): raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") +def _get_custom_functions(fcts): + custom_functions = {} + for f in fcts: + if not f in known_functions.keys(): + custom_functions[f] = f + return custom_functions def _make_unique_prefix(vars, default_prefix="tmp"): """Generate a unique prefix @@ -140,7 +155,7 @@ def _sympify_eqs(eq_strings, state_vars, vars): return eqs, sympy_state_vars, sympy_vars -def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=False): +def solve_lin_system(eq_strings, vars, constants, function_calls, small_system=False, do_cse=False): """Solve linear system of equations, return solution as C code. If system is small (small_system=True, typically N<=3): @@ -156,6 +171,7 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal eqs: list of equations e.g. ["x + y = a", "y = 3 + b"] vars: list of variables to solve for, e.g. ["x", "y"] constants: set of any other symbolic expressions used, e.g. {"a", "b"} + function_calls: set of function calls used in the ODE small_system: if True, solve analytically by gaussian elimination otherwise return matrix system to be solved do_cse: if True, do Common Subexpression Elimination @@ -166,6 +182,7 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal """ eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) + custom_fcts = _get_custom_functions(function_calls) code = [] new_local_vars = [] @@ -188,7 +205,7 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal code.append(f"{var} = {sp.ccode(expr.evalf())}") solution_vector = simplified_solution_vector[0] for var, expr in zip(state_vars, solution_vector): - code.append(f"{sp.ccode(var)} = {sp.ccode(expr.evalf(), contract=False)}") + code.append(f"{sp.ccode(var)} = {sp.ccode(expr.evalf(), contract=False, user_functions=custom_fcts)}") else: # large linear system: construct and return matrix J, vector F such that # J X = F is the linear system to be solved for X by e.g. LU factorization @@ -201,12 +218,12 @@ def solve_lin_system(eq_strings, vars, constants, small_system=False, do_cse=Fal for i, expr in enumerate(matJ): # todo: fix indexing to be ascending order flat_index = matJ.rows * (i % matJ.rows) + (i // matJ.rows) - code.append(f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf())}") + code.append(f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}") return code, new_local_vars -def solve_non_lin_system(eq_strings, vars, constants): +def solve_non_lin_system(eq_strings, vars, constants, function_calls): """Solve non-linear system of equations, return solution as C code. - returns a vector F, and its Jacobian J, both in terms of X @@ -217,6 +234,7 @@ def solve_non_lin_system(eq_strings, vars, constants): eqs: list of equations e.g. ["x + y = a", "y = 3 + b"] vars: list of variables to solve for, e.g. ["x", "y"] constants: set of any other symbolic expressions used, e.g. {"a", "b"} + function_calls: set of function calls used in the ODE Returns: List of strings containing assignment statements @@ -224,6 +242,8 @@ def solve_non_lin_system(eq_strings, vars, constants): eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) + custom_fcts = _get_custom_functions(function_calls) + jacobian = sp.Matrix(eqs).jacobian(state_vars) X_vec_map = {x: sp.symbols(f"X[{i}]") for i, x in enumerate(state_vars)} @@ -235,7 +255,7 @@ def solve_non_lin_system(eq_strings, vars, constants): # todo: fix indexing to be ascending order flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) code.append( - f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(X_vec_map).evalf())}" + f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}" ) return code @@ -348,7 +368,7 @@ def integrate2c(diff_string, dt_var, vars, use_pade_approx=False): return f"{sp.ccode(x)} = {sp.ccode(solution.evalf())}" -def forwards_euler2c(diff_string, dt_var, vars): +def forwards_euler2c(diff_string, dt_var, vars, function_calls): """Return forwards euler solution of diff_string as C code. Derivative should be of the form "x' = f(x)", @@ -361,6 +381,7 @@ def forwards_euler2c(diff_string, dt_var, vars): diff_string: Derivative to be integrated e.g. "x' = a*x" dt_var: name of timestep dt variable in NEURON vars: set of variables used in expression, e.g. {"x", "a"} + function_calls: set of function calls used in the ODE Returns: String containing forwards Euler timestep as C code @@ -370,8 +391,9 @@ def forwards_euler2c(diff_string, dt_var, vars): dt = sp.symbols(dt_var, real=True, positive=True) solution = (x + dxdt * dt).simplify().evalf() + custom_fcts = _get_custom_functions(function_calls) # return result as C code in NEURON format - return f"{sp.ccode(x)} = {sp.ccode(solution)}" + return f"{sp.ccode(x)} = {sp.ccode(solution, user_functions=custom_fcts)}" def differentiate2c(expression, dependent_var, vars, prev_expressions=None): diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 44e3371c7f..e9a7487542 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -44,6 +44,10 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { vars.insert(var_name); } } + auto lv = AstLookupVisitor(ast::AstNodeType::FUNCTION_CALL); + for (const auto& call: lv.lookup(node->get_statement_block().get())) { + function_calls.insert(call->get_node_name()); + } } void SympySolverVisitor::init_state_vars_vector() { @@ -221,7 +225,8 @@ void SympySolverVisitor::solve_linear_system(const std::vector<std::string>& pre "state_vars"_a = state_vars, "vars"_a = vars, "small_system"_a = small_system, - "do_cse"_a = elimination); + "do_cse"_a = elimination, + "function_calls"_a = function_calls); py::exec(R"( from nmodl.ode import solve_lin_system exception_message = "" @@ -229,6 +234,7 @@ void SympySolverVisitor::solve_linear_system(const std::vector<std::string>& pre solutions, new_local_vars = solve_lin_system(eq_strings, state_vars, vars, + function_calls, small_system, do_cse) except Exception as e: @@ -291,15 +297,18 @@ void SympySolverVisitor::solve_non_linear_system( // construct ordered vector of state vars used in non-linear system init_state_vars_vector(); // call sympy non-linear solver - auto locals = - py::dict("equation_strings"_a = eq_system, "state_vars"_a = state_vars, "vars"_a = vars); + auto locals = py::dict("equation_strings"_a = eq_system, + "state_vars"_a = state_vars, + "vars"_a = vars, + "function_calls"_a = function_calls); py::exec(R"( from nmodl.ode import solve_non_lin_system exception_message = "" try: solutions = solve_non_lin_system(equation_strings, state_vars, - vars) + vars, + function_calls) except Exception as e: # if we fail, fail silently and return empty string solutions = [""] @@ -363,7 +372,8 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { const auto locals = py::dict("equation_string"_a = node_as_nmodl, "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, "vars"_a = vars, - "use_pade_approx"_a = use_pade_approx); + "use_pade_approx"_a = use_pade_approx, + "function_calls"_a = function_calls); if (solve_method == codegen::naming::EULER_METHOD) { logger->debug("SympySolverVisitor :: EULER - solving: {}", node_as_nmodl); @@ -374,7 +384,7 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { from nmodl.ode import forwards_euler2c exception_message = "" try: - solution = forwards_euler2c(equation_string, dt_var, vars) + solution = forwards_euler2c(equation_string, dt_var, vars, function_calls) except Exception as e: # if we fail, fail silently and return empty string solution = "" @@ -391,7 +401,8 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { from nmodl.ode import integrate2c exception_message = "" try: - solution = integrate2c(equation_string, dt_var, vars, use_pade_approx) + solution = integrate2c(equation_string, dt_var, vars, + use_pade_approx) except Exception as e: # if we fail, fail silently and return empty string solution = "" diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 3ab0af815d..a274b709d1 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -102,6 +102,9 @@ class SympySolverVisitor: public AstVisitor { /// local variables in current block + globals std::set<std::string> vars; + /// custom function calls used in ODE block + std::set<std::string> function_calls; + /// map between derivative block names and associated solver method std::map<std::string, std::string> derivative_block_solve_method{}; diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/visitor/sympy_solver.cpp index 94629022f4..104533d2fb 100644 --- a/test/nmodl/transpiler/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/visitor/sympy_solver.cpp @@ -118,6 +118,25 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", REQUIRE(result[1] == "h = (-dt*(h-hInf)+h*hTau)/hTau"); } } + GIVEN("Derivative block with calling external functions passes sympy") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD euler + } + DERIVATIVE states { + m' = sawtooth(m) + n' = sin(n) + p' = my_user_func(p) + } + )"; + THEN("Construct forward Euler interpreting external functions as symbols") { + auto result = run_sympy_solver_visitor(nmodl_text); + REQUIRE(result.size() == 3); + REQUIRE(result[0] == "m = dt*sawtooth(m)+m"); + REQUIRE(result[1] == "n = dt*sin(n)+n"); + REQUIRE(result[2] == "p = dt*my_user_func(p)+p"); + } + } GIVEN("Derivative block with ODE, 1 state var in array, solver method euler") { std::string nmodl_text = R"( STATE { From 0169dd112d69b5effc3dbbfa30d4a2559dd1dd1e Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Thu, 2 May 2019 20:54:28 +0200 Subject: [PATCH 202/871] Add python properties for private class members (BlueBrain/nmodl#166) - Fixes BlueBrain/nmodl#165 - Also fixed an issue where the `symtab` private member was being called `symtab = nullptr`. Introduced an additional element in the member tuple. - Added a brief tutorial for pybind to be used for presentation - Added additional const reference setters that can be used from python They in turn needed a more verbose pybind11 binding declaration using a static cast to be correctly resolved. - Also removed a jupyter notebook but moved relevant content to the right notebook. NMODL Repo SHA: BlueBrain/nmodl@1a6e4f724c985e8fb24970ab9d3f7a0ab92512a9 --- .../notebooks/nmodl-python-tutorial.ipynb | 195 +++++++++++++++--- src/nmodl/language/nodes.py | 56 +++-- src/nmodl/language/templates/ast/ast.hpp | 12 +- src/nmodl/language/templates/pybind/pyast.cpp | 10 + 4 files changed, 234 insertions(+), 39 deletions(-) diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index b4ff1018de..ef88e88315 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -254,7 +254,7 @@ "<IPython.core.display.Javascript object>" ] }, - "execution_count": 3, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -265,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -308,7 +308,7 @@ "<IPython.core.display.HTML object>" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -373,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -389,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -448,7 +448,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -472,7 +472,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -493,7 +493,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -507,7 +507,7 @@ "<IPython.core.display.Javascript object>" ] }, - "execution_count": 9, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -541,7 +541,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -559,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -589,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -650,7 +650,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -694,7 +694,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -712,7 +712,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -770,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -801,7 +801,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -830,7 +830,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -861,7 +861,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -901,7 +901,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -946,6 +946,153 @@ "param_visitor = ParameterVisitor()\n", "modast.accept(param_visitor)" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Easy code generation using AST visitors\n", + "\n", + "With a little more code we can even create a code generator for python using a the visitor pattern." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": {}, + "outputs": [], + "source": [ + "mfunc_src = \"\"\"FUNCTION myfunc(x, y) {\n", + " if (x < y) {\n", + " myfunc = x + y\n", + " } else {\n", + " myfunc = y\n", + " }\n", + "}\n", + "\"\"\"\n", + "import nmodl.dsl as nmodl\n", + "from nmodl.dsl import ast\n", + "\n", + "driver = nmodl.NmodlDriver()\n", + "mfunc_ast = driver.parse_string(mfunc_src)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [], + "source": [ + "from nmodl.dsl import ast\n", + "from nmodl.dsl import visitor\n", + "\n", + "class PyGenerator(visitor.AstVisitor):\n", + " def __init__(self):\n", + " visitor.AstVisitor.__init__(self)\n", + " self.pycode = ''\n", + " self.indent = 0\n", + " self.func_name = \"\"\n", + " \n", + " def visit_function_block(self, node):\n", + " params = []\n", + " self.func_name = node.get_node_name()\n", + " for p in node.parameters:\n", + " params.append(p.get_node_name())\n", + " params_str = \", \".join(params)\n", + " self.pycode += f\"def {node.get_node_name()}({params_str}):\\n\"\n", + " node.visit_children(self)\n", + " \n", + " def visit_statement_block(self, node):\n", + " self.indent += 1\n", + " node.visit_children(self)\n", + " self.indent -= 1\n", + " \n", + " def visit_expression_statement(self, node):\n", + " self.pycode += \" \"*4*self.indent\n", + " expr = node.expression\n", + " if type(expr) is ast.BinaryExpression and expr.op.eval() == \"=\":\n", + " rhs = expr.rhs\n", + " lhsn = expr.lhs.name.get_node_name()\n", + " if lhsn == self.func_name:\n", + " self.pycode += \"return \"\n", + " rhs.accept(self)\n", + " else:\n", + " node.visit_children(self)\n", + " else:\n", + " node.visit_children(self)\n", + " self.pycode += \"\\n\"\n", + "\n", + " \n", + " def visit_if_statement(self, node):\n", + " self.pycode += \" \"*4*self.indent + \"if \"\n", + " node.condition.accept(self)\n", + " self.pycode += \":\\n\"\n", + " node.get_statement_block().accept(self)\n", + " for n in node.elseifs:\n", + " n.accept(self)\n", + " if node.elses:\n", + " node.elses.accept(self)\n", + "\n", + " def visit_else_statement(self, node):\n", + " self.pycode += \" \"*4*self.indent + \"else:\\n\"\n", + " node.get_statement_block().accept(self)\n", + " \n", + " \n", + " def visit_binary_expression(self, node):\n", + " lhs = node.lhs\n", + " rhs = node.rhs\n", + " op = node.op.eval()\n", + " if op == \"^\":\n", + " self.pycode += \"pow(\"\n", + " lhs.accept(self)\n", + " self.pycode += \", \"\n", + " rhs.accept(self)\n", + " self.pycode += \")\"\n", + " else:\n", + " lhs.accept(self)\n", + " self.pycode += f\" {op} \"\n", + " rhs.accept(self)\n", + " \n", + " def visit_var_name(self, node):\n", + " self.pycode += node.name.get_node_name()\n", + " \n", + " def visit_integer(self, node):\n", + " self.pycode += nmod.to_nmodl(node)\n", + " \n", + " def visit_double(self, node):\n", + " self.pycode += nmodl.to_nmodl(node)" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "def myfunc(x, y):\n", + " if x < y:\n", + " return x + y\n", + " else:\n", + " return y\n", + "\n" + ] + } + ], + "source": [ + "pygen = PyGenerator()\n", + "pygen.visit_program(mfunc_ast)\n", + "print(pygen.pycode)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -964,7 +1111,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.6.7" } }, "nbformat": 4, diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 3be9171d61..c86eebeda3 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -253,14 +253,31 @@ def get_setter_method(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename reference = "" if self.is_base_type_node else "&&" - return f""" - /** - * \\brief Setter for member variable \\ref {class_name}.{self.varname} - */ - void {setter_method}({setter_type}{reference} {self.varname}) {{ - this->{self.varname} = {self.varname}; - }} - """ + if self.is_base_type_node: + return f""" + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} + */ + void {setter_method}({setter_type} {self.varname}) {{ + this->{self.varname} = {self.varname}; + }} + """ + else: + return f""" + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} (rvalue reference) + */ + void {setter_method}({setter_type}&& {self.varname}) {{ + this->{self.varname} = {self.varname}; + }} + + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} + */ + void {setter_method}(const {setter_type}& {self.varname}) {{ + this->{self.varname} = {self.varname}; + }} + """ def __repr__(self): return "ChildNode(class_name='{}', nmodl_name='{}')".format( @@ -390,7 +407,7 @@ def public_members(self): """ Return public members of the node """ - members = [[child.member_typename, child.varname, child.brief] + members = [[child.member_typename, child.varname, None, child.brief] for child in self.children if child.is_public] @@ -400,18 +417,31 @@ def private_members(self): """ Return private members of the node """ - members = [[child.member_typename, child.varname, child.brief] + members = [[child.member_typename, child.varname, None, child.brief] for child in self.children if not child.is_public] if self.has_token: - members.append(["std::shared_ptr<ModToken>", "token", "token with location information"]) + members.append(["std::shared_ptr<ModToken>", "token", None, "token with location information"]) if self.is_symtab_needed: - members.append(["symtab::SymbolTable*", "symtab = nullptr", "symbol table for a block"]) + members.append(["symtab::SymbolTable*", "symtab", "nullptr", "symbol table for a block"]) if self.is_program_node: - members.append(["symtab::ModelSymbolTable", "model_symtab", "global symbol table for model"]) + members.append(["symtab::ModelSymbolTable", "model_symtab", None, "global symbol table for model"]) + + return members + + def properties(self): + """ + Return private members of the node destined to be pybind properties + """ + members = [[child.member_typename, child.varname, child.is_base_type_node, None, child.brief] + for child in self.children + if not child.is_public] + + if self.has_token: + members.append(["std::shared_ptr<ModToken>", "token", True, None, "token with location information"]) return members diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 0be173ee30..bc7f64fd7c 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -57,15 +57,23 @@ namespace ast { {% if node.private_members() %} private: {% for member in node.private_members() %} - {{ '/// ' + member[2] }} + {{ '/// ' + member[3] }} + {% if member[2] is none %} {{ member[0] }} {{ member[1] }}; + {% else %} + {{ member[0] }} {{ member[1] }} = {{ member[2] }}; + {% endif %} {% endfor %} {% endif %} public: {% for member in node.public_members() %} - {{ '/// ' + member[2] }} + {{ '/// ' + member[3] }} + {% if member[2] is none %} {{ member[0] }} {{ member[1] }}; + {% else %} + {{ member[0] }} {{ member[1] }} = {{ member[2] }}; + {% endif %} {% endfor %} diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index eda6784a7b..15a6c5c5fb 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -232,6 +232,16 @@ void init_ast_module(py::module& m) { {{ var(node) }}.def_readwrite("{{ member[1] }}", &{{ node.class_name }}::{{ member[1] }}); {% endfor %} + {% for member in node.properties() %} + {% if member[2] == True %} + {{ var(node) }}.def_property("{{ member[1] }}", &{{ node.class_name }}::get_{{ member[1] }}, + &{{ node.class_name }}::set_{{ member[1] }}); + {% else %} + {{ var(node) }}.def_property("{{ member[1] }}", &{{ node.class_name }}::get_{{ member[1] }}, + static_cast<void ({{ node.class_name }}::*)(const {{ member[0] }}&)>(&{{ node.class_name }}::set_{{ member[1] }})); + {% endif %} + {% endfor %} + {{ var(node) }}.def("visit_children", &{{ node.class_name }}::visit_children, docstring::visit_children_method) .def("accept", &{{ node.class_name }}::accept, docstring::accept_method) .def("clone", &{{ node.class_name }}::clone, docstring::clone_method) From 9f61006a09c48aa50711400565288bcdb11f9c06 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris <iomagkanaris@gmail.com> Date: Fri, 3 May 2019 00:07:57 +0200 Subject: [PATCH 203/871] Unit parser and visitor for mod files with code generation (BlueBrain/nmodl#145) * Install nrnunits.lib file and related fixes to the code - unit_parser automatically parses the nrnunits.lib file. If there is another file given as argument, it parses the file in the argument - nrnunits.lib path is set by cmake. If CMAKE_INSTALL_PREFIX is different than the default one, then the nrnunits.lib file which is installed is used, else the nrnunits.lib file of the source directory is used * Improvements based on reviews - Turned raw pointers of parser into shared_ptr - Added tests for checking if units are parsed correctly - Added doxygen comments in units.hpp and some more comments on the code * Implementation of parsing units from modfile - Created unit visitor to parse the nrnunits.lib file and visit the AST to add the units that are defined in UNITS block of mod files to the UnitTable that stores all the units - Created visitor for the codegen that visits the FactorDefs nodes of the AST to print the defined libraries to the cpp file - Handle nrnunit.lib file directory using cmake_configure_file * Added test for Unit Visitor and small fixes * Small fixes to parser from mod files - Added ability to read unit declaration of the following type dhummy = (12345e-2) (m/sec2) - Added command option to read specific file with units declaration * Added visitor for constant vars to add them to the UnitTable Fixed passing to AST the UnitTable value, instead the one on the mod file like MOD2C * Multiple fixes in units calculation - Used long double to calculate double factors of units (minimum error on factors) - Added a new rule for NEW_UNIT token (unit1 unit2) - The block of unit definition is printed to the cpp file only if the are unit definitions - Added visitor tests with all the different unit definitions that exist on the modfiles from BBP and ModelDB (they don't pass) - AST node has value of UnitTable if there is no value defined in the modfile * Fixed parsing of different units definitions in mod files of nmodldb - Changed algorithm of units_visitor to parse the units read from modfiles - Added test cases to the lexer and visitor tests to cover new cases - Changed some lexer rules to be able to parse all the units from the mod files - Added definition of (mol) and (degK) to nrnunits.lib to be able to parse already created mod files that do not define them themselves but still use them. New mod files should define all the units that are not defined in the nrnunits.lib file and are used by the mod file - Changed parser/main_units.cpp to a demonstration of how parser of units file (nrnunits.lib) works * Added "--units" flag to README.md `./bin./nmodl -h/-H` section NMODL Repo SHA: BlueBrain/nmodl@085137fce94822e221f50487b06d94c880e12ba9 --- README.md | 4 + share/nmodl/nrnunits.lib | 10 + src/nmodl/codegen/codegen_c_visitor.cpp | 18 +- src/nmodl/codegen/codegen_c_visitor.hpp | 1 + src/nmodl/codegen/codegen_helper_visitor.cpp | 6 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 + src/nmodl/lexer/unit.ll | 22 +- src/nmodl/nmodl/main.cpp | 12 ++ src/nmodl/parser/CMakeLists.txt | 2 +- src/nmodl/parser/main_units.cpp | 34 ++-- src/nmodl/parser/unit.yy | 6 + src/nmodl/units/units.cpp | 38 +++- src/nmodl/units/units.hpp | 7 +- src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/main.cpp | 2 + src/nmodl/visitors/units_visitor.cpp | 158 +++++++++++++++ src/nmodl/visitors/units_visitor.hpp | 92 +++++++++ test/nmodl/transpiler/CMakeLists.txt | 1 + test/nmodl/transpiler/units/lexer.cpp | 5 + test/nmodl/transpiler/visitor/units.cpp | 200 +++++++++++++++++++ 21 files changed, 581 insertions(+), 43 deletions(-) create mode 100644 src/nmodl/visitors/units_visitor.cpp create mode 100644 src/nmodl/visitors/units_visitor.hpp create mode 100644 test/nmodl/transpiler/visitor/units.cpp diff --git a/README.md b/README.md index 5e956a2af0..09578edd1c 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,8 @@ Options: -v,--verbose Verbose logger output -o,--output TEXT=. Directory for backend code output --scratch TEXT=tmp Directory for intermediate code output + --units TEXT=<path>/share/nrnunits.lib + Directory of units lib file Subcommands: host HOST/CPU code backends @@ -293,6 +295,8 @@ Options: -v,--verbose Verbose logger output -o,--output TEXT=. Directory for backend code output --scratch TEXT=tmp Directory for intermediate code output + --units TEXT=<path>/share/nrnunits.lib + Directory of units lib file Subcommands: host diff --git a/share/nmodl/nrnunits.lib b/share/nmodl/nrnunits.lib index 1db06ff25a..b8c5fdb15b 100644 --- a/share/nmodl/nrnunits.lib +++ b/share/nmodl/nrnunits.lib @@ -68,6 +68,11 @@ c 2.99792458+8 m/sec fuzz g 9.80665 m/sec2 au 1.49597871+11 m fuzz mole 6.022169+23 fuzz +mol 1 +/ mol is explicitly defined as a constant +/ with value 1 to avoid "undefined unit" +/ error with mod files that don't define +/ it themselves /mole 6.022140857+23 fuzz e 1.6021917-19 coul fuzz /e 1.6021766208-19 coul fuzz @@ -555,6 +560,11 @@ Xunit 1.00202-13m degC K kelvin K +degK K +/ degK is explicitly defined as K to +/ avoid "undefined unit" error with +/ mod files that don't define it +/ themselves brewster 1-12 m2/newton degF 5|9 degC degreesrankine degF diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 038270a265..ecc636846c 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1918,14 +1918,20 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& /** - * \todo This should be replaced with constant handling from unit database + * NMODL constants from unit database + * */ void CodegenCVisitor::print_nmodl_constants() { - printer->add_newline(2); - printer->add_line("/** constants used in nmodl */"); - printer->add_line("static const double FARADAY = 96485.3;"); - printer->add_line("static const double PI = 3.14159;"); - printer->add_line("static const double R = 8.3145;"); + if (!info.factor_definitions.empty()) { + printer->add_newline(2); + printer->add_line("/** constants used in nmodl */"); + for (const auto& it: info.factor_definitions) { + std::stringstream ss; + ss << "static const double " << it->get_node_name() << " = "; + ss << it->get_value()->get_value() << ";"; + printer->add_line(ss.str()); + } + } } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 8f90bfd896..eefbe5a879 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -18,6 +18,7 @@ #include <algorithm> #include <cmath> #include <ctime> +#include <sstream> #include <string> #include <utility> diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 652dba1d13..bdee6374de 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -577,6 +577,10 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { } } +void CodegenHelperVisitor::visit_factor_def(ast::FactorDef* node) { + info.factor_definitions.push_back(node); +} + void CodegenHelperVisitor::visit_binary_expression(BinaryExpression* node) { if (node->get_op().eval() == "=") { @@ -614,7 +618,7 @@ void CodegenHelperVisitor::visit_table_statement(ast::TableStatement* node) { } -void CodegenHelperVisitor::visit_program(Program* node) { +void CodegenHelperVisitor::visit_program(ast::Program* node) { psymtab = node->get_symbol_table(); auto blocks = node->get_blocks(); for (auto& block: blocks) { diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index c76e14511a..6dae6e4819 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -101,6 +101,7 @@ class CodegenHelperVisitor: public visitor::AstVisitor { void visit_for_netcon(ast::ForNetcon* node) override; void visit_table_statement(ast::TableStatement* node) override; void visit_program(ast::Program* node) override; + void visit_factor_def(ast::FactorDef* node) override; void visit_nrn_state_block(ast::NrnStateBlock* node) override; void visit_linear_block(ast::LinearBlock* node) override; void visit_non_linear_block(ast::NonLinearBlock* node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 01eb9b11a2..a6df78c8e3 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -267,6 +267,9 @@ struct CodegenInfo { /// all functions defined in the mod file std::vector<ast::FunctionBlock*> functions; + /// all factors defined in the mod file + std::vector<ast::FactorDef*> factor_definitions; + /// ions used in the mod file std::vector<Ion> ions; diff --git a/src/nmodl/lexer/unit.ll b/src/nmodl/lexer/unit.ll index a18a4a6938..cc149c6ca9 100755 --- a/src/nmodl/lexer/unit.ll +++ b/src/nmodl/lexer/unit.ll @@ -37,10 +37,24 @@ %} +/** regex for digits, exponents and double numbers */ D [0-9] E [Ee]*[-+]?{D}+ DBL ([-+]?{D})|([-+]?{D}+"."{D}*({E})?)|([-+]?{D}*"."{D}+({E})?)|([-+]?{D}+{E}) +/** + * regex for valid base units names and invalid base units names + * (maximum number of base units should be 10 and named from a to j) + */ +VALIDBASEUNIT [a-j] +INVALIDBASEUNIT [k-zA-Z] + +/** regex for valid characters of units */ +CHAR [a-zA-Z$\%] + +/** regex for name of new defined unit */ +NEWUNIT ({CHAR}+{D}*)|({CHAR}+{D}*"_"{CHAR}+{D}*)|({CHAR}+{D}*"/"{CHAR}+{D}*)|({CHAR}+{D}*"-"{CHAR}*{D}+) + /** we do use yymore feature in copy modes */ %option yymore @@ -98,19 +112,19 @@ DBL ([-+]?{D})|([-+]?{D}+"."{D}*({E})?)|([-+]?{D}*"."{D}+({E})?)|([-+]?{D}+{E}) %% -"*"[a-j]"*" { +"*"{VALIDBASEUNIT}"*" { return UnitParser::make_BASE_UNIT(yytext, loc); } -"*"[k-zA-Z]"*" { +"*"{INVALIDBASEUNIT}"*" { return UnitParser::make_INVALID_BASE_UNIT(yytext, loc); } -^[a-zA-Z$\%]+{D}* { +^{NEWUNIT} { return UnitParser::make_NEW_UNIT(yytext, loc); } -[a-zA-Z$\%]+ { +{CHAR}+ { return UnitParser::make_UNIT(yytext, loc); } diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 5bbca19354..c8195b6fdb 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -21,6 +21,7 @@ #include "codegen/codegen_omp_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" +#include "parser/unit_driver.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" @@ -39,6 +40,7 @@ #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/units_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -120,6 +122,9 @@ int main(int argc, const char* argv[]) { /// directory where intermediate file will be generated std::string scratch_dir("tmp"); + /// directory where units lib file is located + std::string units_dir(NrnUnitsLib::get_path()); + /// true if ast should be converted to json bool json_ast(false); @@ -152,6 +157,7 @@ int main(int argc, const char* argv[]) { ->ignore_case(); app.add_option("--scratch", scratch_dir, "Directory for intermediate code output", true) ->ignore_case(); + app.add_option("--units", units_dir, "Directory of units lib file", true)->ignore_case(); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, "C/C++ backend")->ignore_case(); @@ -297,6 +303,12 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("steadystate")); } + /// Parsing units fron "nrnunits.lib" and mod files + { + logger->info("Parsing Units"); + UnitsVisitor(units_dir).visit_program(ast.get()); + } + /// once we start modifying (especially removing) older constructs /// from ast then we should run symtab visitor in update mode so /// that old symbols (e.g. prime variables) are not lost diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 6734e8e9a5..7b9dc69ec8 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -10,7 +10,7 @@ add_executable(units_parser main_units.cpp) target_link_libraries(nmodl_parser lexer util) target_link_libraries(c_parser lexer util) -target_link_libraries(units_parser lexer util) +target_link_libraries(units_parser util visitor lexer) # ============================================================================= # Install executable diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index d6c181622d..ac9973e27f 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -16,39 +16,35 @@ /** * Standalone parser program for Units. This demonstrate basic - * usage of parser and driver class. + * usage of parser and driver class to parse the `nrnunits.lib` + * file. * - * \todo This is a placeholder and needs to be changed to parse - * NMODL file and then show corresponding units. */ using namespace fmt::literals; using namespace nmodl; -void parse_units(std::vector<std::string> files) { - for (const auto& f: files) { + +int main(int argc, const char* argv[]) { + CLI::App app{"Unit-Parser : Standalone Parser for Units({})"_format(Version::to_string())}; + + std::vector<std::string> units_files; + units_files.push_back(NrnUnitsLib::get_path()); + app.add_option("units_files", units_files, "One or more Units files to process"); + + CLI11_PARSE(app, argc, argv); + + for (const auto& f: units_files) { logger->info("Processing {}", f); std::ifstream file(f); - /// driver object creates lexer and parser + // driver object creates lexer and parser parser::UnitDriver driver; driver.set_verbose(true); - /// just call parser method + // just call parser method driver.parse_stream(file); } -} - -int main(int argc, const char* argv[]) { - CLI::App app{"Unit-Parser : Standalone Parser for Units({})"_format(Version::to_string())}; - - std::vector<std::string> files; - files.push_back(NrnUnitsLib::get_path()); - app.add_option("file", files, "One or more Units files to process"); - - CLI11_PARSE(app, argc, argv); - - parse_units(files); return 0; } diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index ef8b81ce6a..f304d6a186 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -200,6 +200,12 @@ item $2->add_denominator_unit($4); $$ = $2; } + | NEW_UNIT nominator DIVISION DOUBLE { + $2->add_unit($1); + $2->mul_factor(1/std::stod($4)); + $2->add_denominator_unit($4); + $$ = $2; + } | NEW_UNIT INVALID_BASE_UNIT { error(scanner.loc, "Base units should be named by characters a-j"); } diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index edb389405c..a452213347 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -105,7 +105,8 @@ void Unit::add_fraction(const std::string& fraction_string) { } double Unit::parse_double(std::string double_string) { - double d_number, d_magnitude; + long double d_number; + double d_magnitude; std::string s_number; std::string s_magnitude; std::string::const_iterator it; @@ -128,18 +129,25 @@ double Unit::parse_double(std::string double_string) { s_magnitude.push_back(*itm); } } - d_number = std::stod(s_number); + d_number = std::stold(s_number); if (s_magnitude.empty()) { d_magnitude = 0.0; } else { d_magnitude = std::stod(s_magnitude); } - return d_number * std::pow(10.0, d_magnitude) * sign; + return static_cast<double>(d_number * powl(10.0, d_magnitude) * sign); } void UnitTable::calc_nominator_dims(std::shared_ptr<Unit> unit, std::string nominator_name) { double nominator_prefix_factor = 1.0; int nominator_power = 1; + + // if the nominator is DOUBLE, divide it from the unit factor + if (nominator_name.front() >= '1' && nominator_name.front() <= '9') { + unit->mul_factor(1 / std::stod(nominator_name)); + return; + } + std::string nom_name = nominator_name; auto nominator = table.find(nominator_name); @@ -190,7 +198,7 @@ void UnitTable::calc_nominator_dims(std::shared_ptr<Unit> unit, std::string nomi } // if the nominator is still not found in the table then output error - // else multiply its factor to the unit factor and calculate unit's dimentions + // else multiply its factor to the unit factor and calculate unit's dimensions if (nominator == table.end()) { std::stringstream ss; ss << "Unit " << nominator_name << " not defined!" << std::endl; @@ -198,7 +206,7 @@ void UnitTable::calc_nominator_dims(std::shared_ptr<Unit> unit, std::string nomi } else { for (int i = 0; i < nominator_power; i++) { unit->mul_factor(nominator_prefix_factor * nominator->second->get_factor()); - unit->add_nominator_dims(nominator->second->get_dims()); + unit->add_nominator_dims(nominator->second->get_dimensions()); } } } @@ -206,6 +214,13 @@ void UnitTable::calc_nominator_dims(std::shared_ptr<Unit> unit, std::string nomi void UnitTable::calc_denominator_dims(std::shared_ptr<Unit> unit, std::string denominator_name) { double denominator_prefix_factor = 1.0; int denominator_power = 1; + + // if the denominator is DOUBLE, divide it from the unit factor + if (denominator_name.front() >= '1' && denominator_name.front() <= '9') { + unit->mul_factor(std::stod(denominator_name)); + return; + } + std::string denom_name = denominator_name; auto denominator = table.find(denominator_name); @@ -263,7 +278,7 @@ void UnitTable::calc_denominator_dims(std::shared_ptr<Unit> unit, std::string de } else { for (int i = 0; i < denominator_power; i++) { unit->mul_factor(1.0 / (denominator_prefix_factor * denominator->second->get_factor())); - unit->add_denominator_dims(denominator->second->get_dims()); + unit->add_denominator_dims(denominator->second->get_dimensions()); } } } @@ -295,7 +310,8 @@ void UnitTable::insert(std::shared_ptr<Unit> unit) { for (const auto& it: unit->get_denominator_unit()) { calc_denominator_dims(unit, it); } - // if unit is found in table replace it + // if unit is not in the table simply insert it, else replace with it with + // new definition auto find_unit_name = table.find(unit->get_name()); if (find_unit_name == table.end()) { table.insert({unit->get_name(), unit}); @@ -312,9 +328,9 @@ void UnitTable::insert_prefix(std::shared_ptr<Prefix> prfx) { void UnitTable::print_units() const { for (const auto& it: table) { std::cout << std::fixed << std::setprecision(8) << it.first << " " - << it.second->get_factor() << ": "; - for (const auto& dims: it.second->get_dims()) { - std::cout << dims << " "; + << it.second->get_factor() << ":"; + for (const auto& dims: it.second->get_dimensions()) { + std::cout << " " << dims; } std::cout << "\n"; } @@ -342,7 +358,7 @@ void UnitTable::print_units_sorted(std::stringstream& units_details) { for (const auto& it: sorted_elements) { units_details << std::fixed << std::setprecision(8) << it.first << " " << it.second->get_factor() << ":"; - for (const auto& dims: it.second->get_dims()) { + for (const auto& dims: it.second->get_dimensions()) { units_details << " " << dims; } units_details << "\n"; diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 08249d0d43..114c39cca8 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -172,7 +172,7 @@ class Unit { } /// Getter for the array of Unit's dimensions - std::array<int, MAX_DIMS> get_dims() const { + std::array<int, MAX_DIMS> get_dimensions() const { return unit_dimensions; } }; @@ -290,6 +290,11 @@ class UnitTable { /// Print the base units that are stored in the UnitTable to the /// stringstream base_units_details void print_base_units(std::stringstream& base_units_details); + + /// Get base unit name based on the ID number of the dimension + std::string get_base_unit_name(int id){ + return base_units_names[id]; + } }; /** @} */ // end of units diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 0eb972400d..d860287a2f 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -32,6 +32,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/units_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/units_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index dd233b3036..25d0cc6378 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -27,6 +27,7 @@ #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/units_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" @@ -84,6 +85,7 @@ int main(int argc, const char* argv[]) { {std::make_shared<NeuronSolveVisitor>(), "neuron-solve", "NeuronSolveVisitor"}, {std::make_shared<LocalizeVisitor>(), "localize", "LocalizeVisitor"}, {std::make_shared<PerfVisitor>(), "perf", "PerfVisitor"}, + {std::make_shared<UnitsVisitor>(NrnUnitsLib::get_path()), "units", "UnitsVisitor"}, }; pybind11::initialize_interpreter(); diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp new file mode 100644 index 0000000000..575b4aa2f1 --- /dev/null +++ b/src/nmodl/visitors/units_visitor.cpp @@ -0,0 +1,158 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include <iostream> +#include <memory> + +#include "ast/ast.hpp" +#include "visitors/units_visitor.hpp" + +/** + * \file + * \brief AST Visitor to parse the ast::UnitDefs and ast::FactorDefs from the mod file + * by the Units Parser used to parse the \c nrnunits.lib file + */ + +namespace nmodl { +namespace visitor { + +void UnitsVisitor::visit_program(ast::Program* node) { + units_driver.parse_file(units_dir); + node->visit_children(this); +} + +/** + * \details units::Unit definition is based only on pre-defined units, parse only the + * new unit and the pre-defined units. <br> + * Example: + * \code + * (nA) = (nanoamp) => nA nanoamp) + * \endcode + * The ast::UnitDef is converted to a string that is able to be parsed by the + * unit parser which was used for parsing the \c nrnunits.lib file. + * On \c nrnunits.lib constant "1" is defined as "fuzz", so it must be converted. + */ +void UnitsVisitor::visit_unit_def(ast::UnitDef* node) { + std::stringstream ss; + /* + * In nrnunits.lib file "1" is defined as "fuzz", so there + * must be a conversion to be able to parse "1" as unit + */ + if (node->get_unit2()->get_node_name() == "1") { + ss << node->get_unit1()->get_node_name() << "\t"; + ss << UNIT_FUZZ; + } else { + ss << node->get_unit1()->get_node_name() << "\t" << node->get_unit2()->get_node_name(); + } + + // Parse the generated string for the defined unit using the units::UnitParser + units_driver.parse_string(ss.str()); +} + +/** + * \details The new unit definition is based on a factor combined with + * units or other defined units. + * In the first case the factor saved to the ast::FactorDef node and + * printed to \c .cpp file is the one defined on the modfile. The factor + * and the dimensions saved to the units::UnitTable are based on the + * factor and the units defined in the modfile, so this factor will be + * calculated based on the base units of the units::UnitTable. <br> + * Example: + * \code + * R = 8.314 (volt-coul/degC)) + * \endcode + * In the second case, the factor and the dimensions that are inserted + * to the units::UnitTable are based on the ast::FactorDef::unit1, like + * in MOD2C. + * \b However, the factor that is saved in the ast::FactorDef and printed + * in the \c .cpp file is the factor of the ast::FactorDef::unit1 divided + * by the factor of ast::FactorDef::unit2. <br> + * Example: + * \code + * R = (mole k) (mV-coulomb/degC) + * \endcode + * `unit1` is `mole k` and unit2 is `mV-coulomb/degC` <br> + * To parse the units defined in modfiles there are stringstreams + * created that are passed to the string parser, to be parsed by the + * unit parser used for parsing the \c nrnunits.lib file, which takes + * care of all the units calculations. + */ +void UnitsVisitor::visit_factor_def(ast::FactorDef* node) { + std::stringstream ss; + auto node_has_value_defined_in_modfile = node->get_value() != nullptr; + if (node_has_value_defined_in_modfile) { + /* + * In nrnunits.lib file "1" is defined as "fuzz", so + * there must be a conversion to be able to parse "1" as unit + */ + if (node->get_unit1()->get_node_name() == "1") { + ss << node->get_node_name() << "\t" << node->get_value()->eval() << " "; + ss << UNIT_FUZZ; + } else { + ss << node->get_node_name() << "\t" << node->get_value()->eval() << " "; + ss << node->get_unit1()->get_node_name(); + } + // Parse the generated string for the defined unit using the units::UnitParser + units_driver.parse_string(ss.str()); + } else { + std::stringstream ss_unit1, ss_unit2; + std::string unit1_name, unit2_name; + /* + * In nrnunits.lib file "1" is defined as "fuzz", so + * there must be a conversion to be able to parse "1" as unit + */ + if (node->get_unit1()->get_node_name() == "1") { + unit1_name = UNIT_FUZZ; + } else { + unit1_name = node->get_unit1()->get_node_name(); + } + if (node->get_unit2()->get_node_name() == "1") { + unit2_name = UNIT_FUZZ; + } else { + unit2_name = node->get_unit2()->get_node_name(); + } + /* + * Create dummy unit "node->get_node_name()_unit1" and parse + * it to calculate its factor + */ + ss_unit1 << node->get_node_name() << "_unit1\t" << unit1_name; + units_driver.parse_string(ss_unit1.str()); + /* + * Create dummy unit "node->get_node_name()_unit2" and parse + * it to calculate its factor + */ + ss_unit2 << node->get_node_name() << "_unit2\t" << unit2_name; + units_driver.parse_string(ss_unit2.str()); + + // Parse the generated string for the defined unit using the units::UnitParser + ss << node->get_node_name() << "\t" << unit1_name; + units_driver.parse_string(ss.str()); + + /** + * \note If the ast::FactorDef was made by using two units (second case), + * the factors of both of them must be calculated based on the + * units::UnitTable and then they must be divided to produce the unit's + * factor that will be printed to the \c .cpp file. <br> + * Example: + * \code + * FARADAY = (faraday) (10000 coulomb) + * \endcode + * In the \c .cpp file the printed factor will be `9.64853090` but in the + * units::UnitTable the factor of `FARADAY` will be `96485.30900000` + */ + + auto node_unit_name = node->get_node_name(); + auto unit1_factor = units_driver.table->get_unit(node_unit_name + "_unit1")->get_factor(); + auto unit2_factor = units_driver.table->get_unit(node_unit_name + "_unit2")->get_factor(); + auto unit_factor = unit1_factor / unit2_factor; + auto double_value_ptr = std::make_shared<ast::Double>(ast::Double(unit_factor)); + node->set_value(std::move(double_value_ptr)); + } +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/units_visitor.hpp b/src/nmodl/visitors/units_visitor.hpp new file mode 100644 index 0000000000..942162adc5 --- /dev/null +++ b/src/nmodl/visitors/units_visitor.hpp @@ -0,0 +1,92 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::UnitsVisitor + */ + +#include <sstream> +#include <string> +#include <vector> + +#include "ast/ast.hpp" +#include "parser/unit_driver.hpp" +#include "visitors/ast_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + +/** + * \class UnitsVisitor + * \brief Visitor for Units blocks of AST + * + * This is simple example of visitor that uses base AstVisitor + * interface. We override AstVisitor::visit_program, AstVisitor::visit_unit_def + * and AstVisitor::visit_factor_def method. Furthermore it keeps the + * parser::UnitDriver to parse the units file and the strings generated by the + * units in the mod files. + */ + +class UnitsVisitor: public AstVisitor { + private: + /// Units Driver needed to parse the units file and the string produces by + /// mod files' units + parser::UnitDriver units_driver; + + /// Directory of units lib file that defines all the basic units + std::string units_dir; + + /// Declaration of `fuzz` constant unit, which is the equivilant of `1` + /// in mod files UNITS definitions + const std::string UNIT_FUZZ = "fuzz"; + + public: + /// \name Ctor & dtor + /// \{ + + /// Default UnitsVisitor constructor + UnitsVisitor() = default; + + /// UnitsVisitor constructor that takes as argument the units file to parse + /// the units from + explicit UnitsVisitor(std::string t_units_dir) + : units_dir(std::move(t_units_dir)) {} + + /// \} + + /// Function to visit all the ast::UnitDef nodes and parse the units defined as + /// ast::UnitDef in the UNITS block of mod files + void visit_unit_def(ast::UnitDef* node) override; + + /// Function to visit all the ast::FactorDef nodes and parse the units defined + /// as ast::FactorDef in the UNITS block of mod files + void visit_factor_def(ast::FactorDef* node) override; + + /// Override visit_program function to parse the \c nrnunits.lib unit file + /// before starting visiting the AST to parse the units defined in mod files + void visit_program(ast::Program* node) override; + + /// Get the parser::UnitDriver to be able to use it outside the visitor::UnitsVisitor + /// scope keeping the same units::UnitTable + parser::UnitDriver get_unit_driver() { + return units_driver; + } +}; + +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 9b7edc157c..adbb967dfb 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(testvisitor visitor/steadystate.cpp visitor/sympy_conductance.cpp visitor/sympy_solver.cpp + visitor/units.cpp visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) diff --git a/test/nmodl/transpiler/units/lexer.cpp b/test/nmodl/transpiler/units/lexer.cpp index 0d709a801f..cd96177525 100644 --- a/test/nmodl/transpiler/units/lexer.cpp +++ b/test/nmodl/transpiler/units/lexer.cpp @@ -46,6 +46,7 @@ TEST_CASE("Unit Lexer tests for valid tokens", "[lexer][unit]") { REQUIRE(token_type("1-1") == Token::DOUBLE); REQUIRE(token_type("1|100") == Token::FRACTION); REQUIRE(token_type(".03") == Token::DOUBLE); + REQUIRE(token_type("12345e-2") == Token::DOUBLE); REQUIRE(token_type("1|8.988e9") == Token::FRACTION); } @@ -53,8 +54,12 @@ TEST_CASE("Unit Lexer tests for valid tokens", "[lexer][unit]") { REQUIRE(token_type("*a*") == Token::BASE_UNIT); REQUIRE(token_type("*k*") == Token::INVALID_BASE_UNIT); REQUIRE(token_type("planck") == Token::NEW_UNIT); + REQUIRE(token_type("mse-1") == Token::NEW_UNIT); + REQUIRE(token_type("mA/cm2") == Token::NEW_UNIT); REQUIRE(token_type(" m2") == Token::UNIT_POWER); REQUIRE(token_type(" m") == Token::UNIT); + REQUIRE(token_type(" m_2") == Token::UNIT); + REQUIRE(token_type(" m_unit2") == Token::UNIT); REQUIRE(token_type("yotta-") == Token::PREFIX); } diff --git a/test/nmodl/transpiler/visitor/units.cpp b/test/nmodl/transpiler/visitor/units.cpp new file mode 100644 index 0000000000..c934f875dc --- /dev/null +++ b/test/nmodl/transpiler/visitor/units.cpp @@ -0,0 +1,200 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "ast/ast.hpp" +#include "parser/nmodl_driver.hpp" +#include "src/config/config.h" +#include "test/utils/nmodl_constructs.hpp" +#include "test/utils/test_utils.hpp" +#include "utils/logger.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/units_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Unit visitor tests +//============================================================================= + +std::string run_units_visitor(const std::string& text) { + NmodlDriver driver; + driver.parse_string(text); + auto ast = driver.get_ast(); + + // Parse nrnunits.lib file and the UNITS block of the mod file + std::string units_lib_path(NrnUnitsLib::get_path()); + UnitsVisitor units_visitor = UnitsVisitor(units_lib_path); + + units_visitor.visit_program(ast.get()); + + // Keep the UnitTable created from parsing unit file and UNITS + // block of the mod file + parser::UnitDriver units_driver = units_visitor.get_unit_driver(); + std::shared_ptr<units::UnitTable> unit_table = units_driver.table; + + std::stringstream ss; + + // Visit AST to find all the ast::UnitDef nodes to print their + // unit names, factors and dimensions as they are calculated in + // the units::UnitTable + auto unit_defs = AstLookupVisitor().lookup(ast.get(), ast::AstNodeType::UNIT_DEF); + + for (const auto& unit_def: unit_defs) { + auto unit_name = unit_def->get_node_name(); + unit_name.erase(remove_if(unit_name.begin(), unit_name.end(), isspace), unit_name.end()); + auto unit = units_driver.table->get_unit(unit_name); + ss << std::fixed << std::setprecision(8) << unit->get_name() << " "; + ss << unit->get_factor() << ":"; + // Dimensions of the unit are printed to check that the units are successfully + // parsed to the units::UnitTable + int dimension_id = 0; + auto constant = true; + for (const auto& dimension: unit->get_dimensions()) { + if (dimension != 0) { + constant = false; + ss << " " << units_driver.table->get_base_unit_name(dimension_id); + ss << dimension; + } + dimension_id++; + } + if (constant) { + ss << " constant"; + } + ss << "\n"; + } + + // Visit AST to find all the ast::FactorDef nodes to print their + // unit names, factors and dimensions as they are calculated to + // be printed to the produced .cpp file + auto factor_defs = AstLookupVisitor().lookup(ast.get(), ast::AstNodeType::FACTOR_DEF); + for (const auto& factor_def: factor_defs) { + auto unit = units_driver.table->get_unit(factor_def->get_node_name()); + ss << std::fixed << std::setprecision(8) << unit->get_name() << " "; + auto factor_def_class = dynamic_cast<nmodl::ast::FactorDef*>(factor_def.get()); + ss << factor_def_class->get_value()->eval() << ":"; + // Dimensions of the unit are printed to check that the units are successfully + // parsed to the units::UnitTable + int dimension_id = 0; + auto constant = true; + for (const auto& dimension: unit->get_dimensions()) { + if (dimension != 0) { + constant = false; + ss << " " << units_driver.table->get_base_unit_name(dimension_id); + ss << dimension; + } + dimension_id++; + } + if (constant) { + ss << " constant"; + } + ss << "\n"; + } + + return ss.str(); +} + + +SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units]") { + GIVEN("UNITS block with different cases of units definitions") { + std::string nmodl_text = R"( + UNITS { + (nA) = (nanoamp) + (mA) = (milliamp) + (mV) = (millivolt) + (uS) = (microsiemens) + (nS) = (nanosiemens) + (pS) = (picosiemens) + (umho) = (micromho) + (um) = (micrometers) + (mM) = (milli/liter) + (uM) = (micro/liter) + (msM) = (ms mM) + (fAm) = (femto amp meter) + (mol) = (1) + (M) = (1/liter) + (uM1) = (micro M) + (mA/cm2) = (nanoamp/cm2) + (molar) = (1 / liter) + (S ) = (siemens) + (mse-1) = (1/millisec) + (um3) = (liter/1e15) + (molar1) = (/liter) + (degK) = (degC) + FARADAY1 = (faraday) (coulomb) + FARADAY2 = (faraday) (kilocoulombs) + FARADAY3 = (faraday) (10000 coulomb) + PI = (pi) (1) + R1 = (k-mole) (joule/degC) + R2 = 8.314 (volt-coul/degC) + R3 = (mole k) (mV-coulomb/degC) + R4 = 8.314 (volt-coul/degK) + R5 = 8.314500000000001 (volt coul/kelvin) + dummy1 = 123.45 (m 1/sec2) + dummy2 = 123.45e3 (millimeters/sec2) + dummy3 = 12345e-2 (m/sec2) + KTOMV = 0.0853 (mV/degC) + B = 0.26 (mM-cm2/mA-ms) + TEMP = 25 (degC) + } + )"; + + std::string output_nmodl = R"( + nA 0.00000000: sec-1 coul1 + mA 0.00100000: sec-1 coul1 + mV 0.00100000: m2 kg1 sec-2 coul-1 + uS 0.00000100: m-2 kg-1 sec1 coul2 + nS 0.00000000: m-2 kg-1 sec1 coul2 + pS 0.00000000: m-2 kg-1 sec1 coul2 + umho 0.00000100: m-2 kg-1 sec1 coul2 + um 0.00000100: m1 + mM 1.00000000: m-3 + uM 0.00100000: m-3 + msM 0.00100000: m-3 sec1 + fAm 0.00000000: m1 sec-1 coul1 + mol 1.00000000: constant + M 1000.00000000: m-3 + uM1 0.00100000: m-3 + mA/cm2 0.00001000: m-2 sec-1 coul1 + molar 1000.00000000: m-3 + S 1.00000000: m-2 kg-1 sec1 coul2 + mse-1 1000.00000000: sec-1 + um3 0.00100000: m3 + molar1 1000.00000000: m-3 + degK 1.00000000: K1 + FARADAY1 96485.30900000: coul1 + FARADAY2 96.48530900: coul1 + FARADAY3 9.64853090: coul1 + PI 3.14159265: constant + R1 8.31449872: m2 kg1 sec-2 K-1 + R2 8.31400000: m2 kg1 sec-2 K-1 + R3 8314.49871704: m2 kg1 sec-2 K-1 + R4 8.31400000: m2 kg1 sec-2 K-1 + R5 8.31450000: m2 kg1 sec-2 K-1 + dummy1 123.45000000: m1 sec-2 + dummy2 123450.00000000: m1 sec-2 + dummy3 123.45000000: m1 sec-2 + KTOMV 0.08530000: m2 kg1 sec-2 coul-1 K-1 + B 0.26000000: m-1 coul-1 + TEMP 25.00000000: K1 + )"; + + THEN("Print the units that were added") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_units_visitor(input); + auto reindented_result = reindent_text(result); + REQUIRE(reindented_result == expected_result); + } + } +} From 74e4ae99c09e8683208d1019a2583a93b48307d1 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Fri, 3 May 2019 01:06:45 +0200 Subject: [PATCH 204/871] Update README with minimal introduction/overview (BlueBrain/nmodl#170) - README provides overview of framework, build instructions moved to INSTALL.md - Fix URLs and added TODO in URL as a reminder NMODL Repo SHA: BlueBrain/nmodl@2eafe14e44bf8d718a8fe865353622c70bc9b821 --- INSTALL.md | 181 ++++++ README.md | 579 +++++------------- .../transpiler/images/nmodl-perf-stats.png | Bin 0 -> 1288039 bytes docs/nmodl/transpiler/images/nmodl.ast.png | Bin 0 -> 302573 bytes src/nmodl/units/units.hpp | 2 +- 5 files changed, 328 insertions(+), 434 deletions(-) create mode 100644 INSTALL.md create mode 100644 docs/nmodl/transpiler/images/nmodl-perf-stats.png create mode 100644 docs/nmodl/transpiler/images/nmodl.ast.png diff --git a/INSTALL.md b/INSTALL.md new file mode 100644 index 0000000000..182dd7f000 --- /dev/null +++ b/INSTALL.md @@ -0,0 +1,181 @@ +### Getting Started + +These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. + +#### Cloning Source + +This project uses git submodules which must be cloned along with the repository itself: + +``` +git clone --recursive https://github.com/BlueBrain/nmodl.git +``` + +#### Prerequisites + +To build the project from source, modern C++ compiler with c++11 support is necessary. Make sure you have following packages available: + +- flex (>=2.6) +- bison (>=3.0) +- CMake (>=3.1) +- Python (>=3.6) +- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.2), textwrap + +##### On OS X + +Often we have older version of flex and bison. We can get recent version of dependencies via brew/macport and pip: + +``` +brew install flex bison cmake python3 +pip3 install Jinja2 PyYAML pytest sympy +``` + +Make sure to have latest flex/bison in $PATH : + +``` +export PATH=/usr/local/opt/flex:/usr/local/opt/bison:/usr/local/bin/:$PATH +``` + +##### On Ubuntu + +On Ubuntu (>=16.04) flex/bison versions are recent enough. We can install Python3 and dependencies using: + +``` +apt-get install python3 python3-pip +pip3 install Jinja2 PyYAML pytest sympy +``` + +> On Blue Brain B5 Supercomputer, use : module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 python3-dev + +#### Build Project + +##### Using CMake + +Once all dependencies are in place, build project as: + +``` +mkdir -p nmodl/build +cd nmodl/build +cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl +make -j && make install +``` + +And set PYTHONPATH as: + +``` +export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH +``` + +#### Testing Installed Module + +If you install NMODL using CMake, you can run tests from build directory as: + +``` +$ make test +Running tests... +Test project /Users/kumbhar/workarena/repos/bbp/incubator/nocmodl/cmake-build-debug + Start 1: testmodtoken/NMODL Lexer returning valid ModToken object + 1/60 Test #1: testmodtoken/NMODL Lexer returning valid ModToken object ................................... Passed 0.01 sec + Start 2: testlexer/NMODL Lexer returning valid token types + 2/60 Test #2: testlexer/NMODL Lexer returning valid token types .......................................... Passed 0.00 sec + Start 3: testparser/Scenario: NMODL can define macros using DEFINE keyword + 3/60 Test #3: testparser/Scenario: NMODL can define macros using DEFINE keyword .......................... Passed 0.01 sec + Start 4: testparser/Scenario: Macros can be used anywhere in the mod file + 4/60 Test #4: testparser/Scenario: Macros can be used anywhere in the mod file ........................... Passed 0.01 sec + Start 5: testparser/Scenario: NMODL parser accepts empty unit specification + 5/60 Test #5: testparser/Scenario: NMODL parser accepts empty unit specification ......................... Passed 0.01 sec + Start 6: testparser/Scenario: NMODL parser running number of valid NMODL constructs + 6/60 Test #6: testparser/Scenario: NMODL parser running number of valid NMODL constructs ................. Passed 0.04 sec + Start 7: testparser/Scenario: NMODL parser running number of invalid NMODL constructs + 7/60 Test #7: testparser/Scenario: NMODL parser running number of invalid NMODL constructs ............... Passed 0.01 sec + Start 8: testparser/Scenario: Legacy differential equation solver from NEURON solve number of ODE + 8/60 Test #8: testparser/Scenario: Legacy differential equation solver from NEURON solve number of ODE ... Passed 0.00 sec + ... +``` + +We can use nmodl module from python as: + +```python +$ python3 +>>> import nmodl.dsl as nmodl +>>> driver = nmodl.NmodlDriver() +>>> modast = driver.parse_string("NEURON { SUFFIX hh }") +>>> print ('%s' % modast) +{"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]},{"Name":[{"String":[{"name":"hh"}]}]}]}]}]}]} +>>> print (nmodl.to_nmodl(modast)) +NEURON { + SUFFIX hh +} +``` + +NMODL is now setup correctly! + + +#### Generating Documentation + +Once you have installed NMODL and setup the correct PYTHONPATH, you can build the documentation locally from the docs folder as: + +``` +cd docs +doxygen # for API documentation +make html # for user documentation +``` + + +### Development Conventions + +If you are developing NMODL, make sure to enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` +CMake variables to ensure that your contributions follow the coding conventions of this project: + +```cmake +cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON <path> +``` + +The first variable provides the following additional targets to format +C, C++, and CMake files: + +``` +make clang-format cmake-format +``` + +The second option activates Git hooks that will discard commits that +do not comply with coding conventions of this project. These 2 CMake variables require additional utilities: + +* [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) +* [cmake-format](https://github.com/cheshirekow/cmake_format) +* [pre-commit](https://pre-commit.com/) + +clang-format can be installed on Linux thanks +to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a +[brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) +to install clang-format 7. _cmake-format_ and _pre-commit_ utilities can be installed with *pip*. + + +##### Memory Leaks and clang-tidy + +If you want to test for memory leaks, do : + +``` +valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer +``` + +Or using CTest as: + +``` +ctest -T memcheck +``` + +If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: + +``` +cmake .. -DENABLE_CLANG_TIDY=ON +``` + +##### Flex / Bison Paths + +If flex / bison is not in default $PATH, you can provide path to cmake as: + +``` +cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ + -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ + -DCMAKE_INSTALL_PREFIX=$HOME/nmodl +``` diff --git a/README.md b/README.md index 09578edd1c..89a66cea90 100644 --- a/README.md +++ b/README.md @@ -1,462 +1,175 @@ -## NMODL -> NEURON MOdeling Language Code Generation Framework - -NMODL is a code generation framework for [NEURON Modeling Language](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html). The main goals of this framework are : - -* Support for full NMODL specification -* Providing modular tools for parsing, analysis and optimization -* High level python interface -* Optimised code generation for modern CPU/GPU architectures -* Code generation backends compatible with existing simulators -* Flexibility to implement new simulator backend with minimal efforts - -It is primarily designed to support optimised code generation backends but the underlying infrastructure can be used with high level python interface for model introspection and analysis. - -### Getting Started - -These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. - -#### Cloning Source - -This project uses git submodules which must be cloned along with the repository itself: - -``` -git clone --recursive git@github.com:BlueBrain/nmodl.git -``` - -#### Prerequisites - -To build the project from source, modern C++ compiler with c++11 support is necessary. Make sure you have following packages available: - -- flex (>=2.6) -- bison (>=3.0) -- CMake (>=3.1) -- Python (>=3.6) -- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.2), textwrap - -##### On OS X - -Often we have older version of flex and bison. We can get recent version of dependencies via brew/macport and pip: - -``` -brew install flex bison cmake python3 -pip3 install Jinja2 PyYAML pytest sympy -``` - -Make sure to have latest flex/bison in $PATH : - -``` -export PATH=/usr/local/opt/flex:/usr/local/opt/bison:/usr/local/bin/:$PATH -``` - -##### On Ubuntu - -On Ubuntu (>=16.04) flex/bison versions are recent enough. We can install Python3 and dependencies using: - -``` -apt-get install python3 python3-pip -pip3 install Jinja2 PyYAML pytest sympy -``` - -##### On BB5 - -On Blue Brain 5 system, we can load following modules : - -``` -module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 python3-dev -``` - -#### Build Project - -##### Using CMake - -Once all dependencies are in place, build project as: - -``` -mkdir -p nmodl/build -cd nmodl/build -cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -make -j && make install -``` - -And set PYTHONPATH as: - -``` -export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH -``` - -##### Using pip - -Alternatively, we can build the project using pip as: - -``` -pip3 install nmodl/. --target=$HOME/nmodl # remove --target option if want to install -``` - -And if --target option was used, set PYTHONPATH as: - -``` -export PYTHONPATH=$HOME/nmodl:$PYTHONPATH -``` - -##### Using setuptools - -Finally, we can also build the project using setuptools: - -``` -python3 setup.py install --prefix=<pathInstallationDir> -``` - -This will install nmodl on your system, run all the tests and generate the documentation. - -#### Testing Installed Module - -If you install NMODL using CMake, you can run tests from build directory as: - -``` -$ make test -Running tests... -Test project /home/kumbhar/nmodl/build - Start 1: ModToken -1/7 Test #1: ModToken ......................... Passed 0.01 sec +## The NMODL Framework + +The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L**anguage ([NMODL](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html)). It is designed with modern compiler and code generation techniques to: + +* Provide **modular tools** for parsing, analysing and transforming NMODL +* Provide **easy to use**, high level Python API +* Generate **optimised code** for modern compute architectures including CPUs, GPUs +* **Flexibility** to implement new simulator backends +* Support for **full** NMODL specification + +### About NMODL + +Simulators like [NEURON](https://www.neuron.yale.edu/neuron/) use NMODL as a domain specific language (DSL) to describe a wide range of membrane and intracellular submodels. Here is an example of exponential synapse specified in NMODL: + +``` +NEURON { + POINT_PROCESS ExpSyn + RANGE tau, e, i + NONSPECIFIC_CURRENT i +} +UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) +} +PARAMETER { + tau = 0.1 (ms) <1e-9,1e9> + e = 0 (mV) +} +ASSIGNED { + v (mV) + i (nA) +} +STATE { + g (uS) +} +INITIAL { + g = 0 +} +BREAKPOINT { + SOLVE state METHOD cnexp + i = g*(v - e) +} +DERIVATIVE state { + g' = -g/tau +} +NET_RECEIVE(weight (uS)) { + g = g + weight +} +``` + +### Installation + +See [INSTALL.md](INSTALL.md) for detailed instructions to build the NMODL from source. + +### Using the Python API + +Once the NMODL Framework is installed, you can use the Python parsing API as: + +```python +import nmodl.dsl as nmodl +driver = nmodl.NmodlDriver() +mod_ast = driver.parse_file("expsyn.mod") +``` + +`parse_file()]` returns Abstract Syntax Tree ([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) representation of input NMODL file. One can look at the AST in JSON form as: + +```python +>>> print (nmodl.to_json(mod_ast)) +{ + "Program": [ + { + "NeuronBlock": [ + { + "StatementBlock": [ + { + "Suffix": [ + { + "Name": [ + { + "String": [ + { + "name": "POINT_PROCESS" + } + ... +``` +You can also use AST visualization API to look at the AST: + +![alt text](docs/images/nmodl.ast.png "AST representation of expsyn.mod") + +The central *Program* node represents the whole MOD file and each of it's children represent the block in the input NMODL file i.e. **expsyn.mod**. Once the AST is built, one can use exisiting visitors to perform various analysis/optimisations or one can write his own custom visitor using Python Visitor API. See [Python API tutorial](docs/notebooks/nmodl-python-tutorial.ipynb) for details. + +One can also transform AST back into NMODL form simply as : + +```python +>>> print (nmodl.to_nmodl(mod_ast)) +NEURON { + POINT_PROCESS ExpSyn + RANGE tau, e, i + NONSPECIFIC_CURRENT i +} + +UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) +} + +PARAMETER { + tau = 0.1 (ms) <1e-09,1000000000> + e = 0 (mV) +} ... ``` -We can use nmodl module from python as: - -``` -$ python3 ->>> import nmodl.dsl as nmodl ->>> driver = nmodl.NmodlDriver() ->>> modast = driver.parse_string("NEURON { SUFFIX hh }") ->>> print ('%.100s' % modast) -{"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]}, -``` - -NMODL is now setup correctly! - -#### Using Python API - -The best way to understand the API and usage is using Jupyter notebooks provided in docs directory : - -``` -cd nmodl/docs/notebooks -jupyter notebook -``` - -You can look at [nmodl-python-tutorial.ipynb](docs/notebooks/nmodl-python-tutorial.ipynb) notebook for python interface tutorial. There is also [nmodl-python-sympy-examples.ipynb](docs/notebooks/nmodl-python-sympy-examples.ipynb)showing how [SymPy](https://www.sympy.org/en/index.html) is used in NMODL. - -##### Documentation - -If you installed nmodl using setuptools as shown in the previous section, you will have all the documentation generated on build/sphinx. - -Otherwise, if you have already installed nmodl and setup the correct PYTHONPATH, you can build the documentation locally from the docs/ folder. - -``` -cd docs -make html -``` - -Or using setuptools - -``` -python3 setup.py install_doc -``` - -If you dont want to change the PYTHONPATH, make sure that you have the shared library on the nmodl/ folder. It is copied there automatically by compiling and running the tests: +### High Level Analysis and Code Generation -``` -python3 setup.py test -``` - -Now you will have a new folder docs/_build/html or build/sphinx/html, depending if you run the first or the second command, -where you can open the index.html with your favourite browser. - -Another option is to create an httpServer on this folder and open the browser on localhost: - -``` -cd docs/_build/html -python2 -m SimpleHTTPServer 8080 -http://localhost:8080 -``` - -To check the coverage of your documentation you can run: - -``` -cd nmodl/docs -make coverage -``` - -The results will be stored on the docs/_build/coverage - -To run the code snippets on the documentation you can do: - -``` -cd nmodl/docs -make doctest -``` - -Or with setuptools - -``` -python3 setup.py doctest -``` - -The output will be stored on the docs/_build/doctest or build/sphinx/doctest depending on the command used. - -To add new modules you could call sphinx-apidoc. - -``` -sphinx-apidoc -o docs/ nmodl/ -``` - -This will generate "stubs" rst files for the new modules. -The file will look like something like this: - -``` -.. automodule:: mymodule - :members: - :no-undoc-members: - :show-inheritance: -``` - -If you want to generate documentation for all the symbols of the module, you could add :imported-members: - -``` -.. automodule:: mymodule - :members: - :imported-members: - :no-undoc-members: - :show-inheritance: -``` - -After that you can run the "make html" command to generate documentation for the new modules. - -#### Using NMODL For Code Generation - -Once you install project using CMake, you will have following binaries in the installation directory: - -``` -$ tree $HOME/nmodl/bin - -|-- lexer -| |-- c_lexer -| `-- nmodl_lexer -|-- nmodl -|-- parser -| |-- c_parser -| `-- nmodl_parser -`-- visitor - `-- nmodl_visitor -``` - -Main code generation program is `nmodl`. You can see all sub-commands supported using: - -``` -$ ./bin/nmodl -h - -NMODL : Source-to-Source Code Generation Framework -Usage: ./bin/nmodl [OPTIONS] file... [SUBCOMMAND] - -Positionals: - file TEXT:FILE ... REQUIRED One or more MOD files to process - -Options: - -h,--help Print this help message and exit - -H,--help-all Print this help message including all sub-commands - -v,--verbose Verbose logger output - -o,--output TEXT=. Directory for backend code output - --scratch TEXT=tmp Directory for intermediate code output - --units TEXT=<path>/share/nrnunits.lib - Directory of units lib file - -Subcommands: - host HOST/CPU code backends - acc Accelerator code backends - sympy SymPy based analysis and optimizations - passes Analyse/Optimization passes - codegen Code generation options -``` +The NMODL Framework provides rich model introspection and analysis capabilities using [various visitors TODO](). Here is an example of theoretical performance characterisation of channels and synapses from rat neocortical column microcircuit [published in 2015](https://www.cell.com/abstract/S0092-8674(15)01191-5): -To see all command line options from every sub-command, you can do: +![alt text](docs/images/nmodl-perf-stats.png "Example of performance characterisation") -``` -$ ./bin/nmodl -H - -NMODL : Source-to-Source Code Generation Framework -Usage: ./bin/nmodl [OPTIONS] file... [SUBCOMMAND] - -Positionals: - file TEXT:FILE ... REQUIRED One or more MOD files to process - -Options: - -h,--help Print this help message and exit - -H,--help-all Print this help message including all sub-commands - -v,--verbose Verbose logger output - -o,--output TEXT=. Directory for backend code output - --scratch TEXT=tmp Directory for intermediate code output - --units TEXT=<path>/share/nrnunits.lib - Directory of units lib file - -Subcommands: -host - HOST/CPU code backends - Options: - --c C/C++ backend - --omp C/C++ backend with OpenMP - -acc - Accelerator code backends - Options: - --oacc C/C++ backend with OpenACC - --cuda C/C++ backend with CUDA - -sympy - SymPy based analysis and optimizations - Options: - --analytic Solve ODEs using SymPy analytic integration - --pade Pade approximation in SymPy analytic integration - --cse CSE (Common Subexpression Elimination) in SymPy analytic integration - --conductance Add CONDUCTANCE keyword in BREAKPOINT - -passes - Analyse/Optimization passes - Options: - --inline Perform inlining at NMODL level - --localize Convert RANGE variables to LOCAL - --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist - --local-rename Rename LOCAL variable if variable of same name exist in global scope - --verbatim-inline Inline even if verbatim block exist - --verbatim-rename Rename variables in verbatim block - --json-ast Write AST to JSON file - --nmodl-ast Write AST to NMODL file - --json-perf Write performance statistics to JSON file - --show-symtab Write symbol table to stdout - -codegen - Code generation options - Options: - --layout TEXT:{aos,soa}=soa Memory layout for code generation - --datatype TEXT:{float,double}=soa Data type for floating point variables -``` +To understand how you can write your own introspection and analysis tool, see [this tutorial](docs/notebooks/nmodl-python-tutorial.ipynb). -To use code generation capability you can do: +Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, [C++ TODO](), [OpenACC TODO](), [OpenMP TODO](), [CUDA TODO]() and [ISPC TODO]() backends are implemented and one can choose backends on command line as: ``` -$ nmodl <path>/hh.mod \ - host --c \ - sympy --analytic --pade --conductance --cse \ - passes --inline --localize --localize-verbatim \ - --local-rename --verbatim-inline --verbatim-rename +$ nmodl expsyn.mod host --ispc acc --cuda sympy --analytic ``` -This will generate hh.cpp in the current directory. +Here is an example of generated ISPC kernel for [DERIVATIVE](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html#derivative) block : -##### Lexer and Parser +```c++ +export void nrn_state_ExpSyn(uniform ExpSyn_Instance* uniform inst, uniform NrnThread* uniform nt ...) { + uniform int nodecount = ml->nodecount; + const int* uniform node_index = ml->nodeindices; + const double* uniform voltage = nt->_actual_v; -The `nmodl_parser` is a standalone parsing tool for NMODL that one can use to check if NMODL construct is valid or if it can be correctly parsed by NMODL. You can parse a mod file as: + int uniform start = 0; + int uniform end = nodecount; -``` -$ nmodl_parser <path>/file.mod + foreach (id = start ... end) { + int node_id = node_index[id]; + double v = voltage[node_id]; + inst->g[id] = inst->g[id] * vexp( -nt->_dt / inst->tau[id]); + } +} ``` -Or, pass NMODL construct on the command line as: +To know more about code generation backends, [see here TODO](). -``` -$ nmodl_parser --text "NEURON{ SUFFIX hh }" +### Documentation -[NMODL] [info] :: Processing text : NEURON{ SUFFIX hh } -``` +We are working on user documentation, you can find current drafts of : -The `nmodl_lexer` is a standaline lexer tool for NMODL. You can test a mod file as: +* User documentation on [Read the Docs TODO]() +* Developer / API documentation with [Doxygen TODO]() -``` -$ nmodl_lexer <path>/file.mod -``` -Or, pass NMODL construct on the command line as: +### Support / Contribuition -``` -$ nmodl_lexer --text "NEURON{ SUFFIX hh }" - -[NMODL] [info] :: Processing text : NEURON{ SUFFIX hh } - NEURON at [1.1-6] type 332 - { at [1.7] type 368 - SUFFIX at [1.9-14] type 358 - hh at [1.16-17] type 356 - } at [1.19] type 369 -``` +If you see any issue or need help, feel free to raise a ticket. If you would like to improve this framework, see open issues and [contribution guidelines](CONTRIBUTING.md). -#### Using NMODL With CoreNEURON +### Citation -We can use NMODL instead of MOD2 for CoreNEURON. You have to simply use extra CMake argument `-DMOD2C` pointing to `nmodl` binary: +If you are referencing NMODL Framework in a publication, please cite the following paper: -``` -git clone --recursive https://github.com/BlueBrain/CoreNeuron.git coreneuron -mkdir coreneuron/build && cd coreneuron/build -cmake .. -DMOD2C=<path>/bin/nmodl -make -j -make test -``` - -Note that the latest master has changed command line option. Use released version **`0.1`** for now. - - -### Development Conventions +* Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform source-to-source compiler framework for the NEURON MODeling Language. In Eprint arXiv: TODO. -If you are developing NMODL, make sure to enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` -CMake variables to ensure that your contributions follow the coding conventions of this project: +### Authors -```cmake -cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON <path> -``` - -The first variable provides the following additional targets to format -C, C++, and CMake files: - -``` -make clang-format cmake-format -``` +See [contributors](https://github.com/BlueBrain/nmodl/graphs/contributors). -The second option activates Git hooks that will discard commits that -do not comply with coding conventions of this project. These 2 CMake variables require additional utilities: +### Funding -* [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) -* [cmake-format](https://github.com/cheshirekow/cmake_format) Python package -* [pre-commit](https://pre-commit.com/) Python package - -_ClangFormat_ can be installed on Linux thanks -to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a -[brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) -to install clang-format 7. _cmake-format_ and _pre-commit_ utilities can be installed with *pip*. - - -##### Memory Leaks and Clang Tidy - -If you want to test for memory leaks, do : - -``` -valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer -``` - -Or using CTest as: - -``` -ctest -T memcheck -``` - -If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: - -``` -cmake .. -DENABLE_CLANG_TIDY=ON -``` - -##### Flex / Bison Paths - -If flex / bison is not in default $PATH, you can provide path to cmake as: - -``` -cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ - -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ - -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -``` +This work has been funded by the EPFL Blue Brain Project (funded by the Swiss ETH board), NIH grant number R01NS11613 (Yale University) and partially funded by the European Union's Horizon 2020 Framework Programme for Research and Innovation under Grant Agreement number 785907 (Human Brain Project SGA2). diff --git a/docs/nmodl/transpiler/images/nmodl-perf-stats.png b/docs/nmodl/transpiler/images/nmodl-perf-stats.png new file mode 100644 index 0000000000000000000000000000000000000000..bf9b34d0257254857452b217640b5802aefb3e96 GIT binary patch literal 1288039 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�R1JGnkiz|!gsgALeDT3&4<zh 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%0Dj6p`I}#iF{-_KTCURT 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%0DvGM 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%0DkhYq#H-x19z+aj{DliGShw?A9MvZlPgo$ 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 Y0jU6CV8A+}-DS}kQ2 zL)fpi9QCs`do&VAWtz$m6*WB;i>D@sCsk?eCTiFGvi(04>J>6WL2S|0YgWr;RIr?^ zD}(%ST&*ZlYbT!2j6(S;?$KIkS_c-+z95vAQAy>^-ShVo8KP~Pclc4W#P!Jgn5{LE zX?1ZoV~B13MI?PR1BobnT2Ws?y}PATnT2259mEy$8M$@X<u0S7GeI#gmJZ7?OfC>A z6-djZH8zC*N6bP${~>#-m6`QteKR=j@SZOXTuJKv7fMT7OYn$ZBIxkjwH+UX7GAaV zt9oEt>3+Ghy+@B}k6dY9@$g_>{7Se0E<qa3$82Tkr>sL={a$%(M0L?SJ;0D?q<%bU z&xFMj!3>{4pj%GG2&gS)0!;(e6vEpKDqPOn=l7dDbhV~@6~aQ}UOO88pVPs6kQQ6B z$FJ>3kS<o0egS#u72{gWzLnMy#pPz8yntX5a$az(guW4uvSrTnl@E0avz07HlGNr8 z%kSUlT%F<>E_RJ2pVPQ>WR5z$d)jTAkY<agNomJPvAOPzO$i>JeUsDFLo#;YKcn-% zW53p8gvl#y>Z?iT-E8Vk`sd8pF1pOGKAZ<&uI};E<3Z&)Jk*6aR}l~loagr5fW-Mk z^EsBIZm*VV__Trow7k?$iV$Dt%)LI=NI>_N_s+$p3*2%m`mWSj;C}g^w@vMTdXJ?0 z3fNc+<c>(>to>|Isj9>=TEW~`H9mT_YeCMa^-@v~`7za@hI`^XX}%E1sqV|eHC|?I zX*U+Qe5|i6@S6(MKykQS+%m)2yn9;mYiiJ!k0aeIg{bo+Pmx8Y^r~#7@&x4|lmlY> zD4_CTrbyZ<xgVLVkHq!w<cOG`2N#U%tmis<<XeNT@ofE(Ls3sekdiU^9whogI;{9( z&7cTdq1W4Hib-ZsyIKjsyZm-A0r(m!pnz5xP58-t&Zt_GlQUN!>P};bw>co|<2;<8 z#f8z)H}04htMl>LJAwnfDmfQOyW);Sh_&LKIjX&0EyN0r9*Nj$2Y?p<DR+ID<YYZ7 z(C`TJ(UA&hMbC`ruU+gt2)SB>z+&(eGrJTUq5^1GF=SQu7{v0Tf_Cw<lkQig{<zF` z!?^{POg+>ckb%-jucn9pOmF&u6%vhZx?dUjEIavHE!pTGn$dgfng!B?!uR2c3sXy; z{}xh)R}~k=Rzrm4tij8B)4cQaSIfNA)6-6W5Zy8`65(^Qwpu;P-so&IFj~}3f8(4} zxgmO(QxkNwBd)3W?j0okm}>T8Iw)h8l7CHTX`{<#a;jRJlr3^?<hWb|!g{0Y?Cad= z*x~+EN?YjYeg{0s&b{SHO1@k?$jZ58ez_L~{qN`hW9zNM+I*L;(IgPuCAd3<0xeM7 zo#O66i@RF`#T|+jcY+5fQmmoH9U34=ffg%PK?{^}*x%m!J?HzK_nJR)<<BR1?t5m| zTC=9b4Rh%PWX80POp*uG?XL=yd4{bB1rc*T(<_zmg%IjgOjQPinH^Q5==2L8MyTg$ zFGwVNLS)+V!pIM40l)j6i8U4XbYO{$a>yy;tK!Xn?VHHT_8CEOYa|z<0B!o4&ofPs zyufnZ`YZ-<4rT49_T$|wsZqkUk25($JD$I`*TkWz{`U1bH^r}*9;Z$*gv*9sQU|4L zcTo=qvO>D51Q)X?I=pS@tEcuC187()(#_Jfql5Oa+h+~f+V&GufApKbrxFBUNNSKg zuQeay;L;44!ZE6=Ow8W^wC1at)l3zdV-IhsB!Z#2Pev9FKNICfH<&v|e4<i>84R&h z#OXEpzhKi9CQh2?e-v*5Oq_;4HD9VfL4QHu>tP-Rsynw99*VS1(<vGTV5^EECj^H! zT9WSqr&qGpX)c;gFs0>qCJamUp7J{!5t;IV3G#>lxrD$Ky(`MvD#)~FK&j|{$a2D8 zDkk7RBI*Sf=&^^Di5iyeD_T(R5euwRT$+I0!z^*1>GJiVAy@(X;OW;k6n87qurH|o z`7R2M_JP<h=P}r4{so;t#y4baJ=a<8jnh@WBIqp46rxQ8xEkw_P*17fLDJy_;}vEW zi@HZ2zlvk}CmdCjr?51;+y3Bx^6_J*2j8y`Kcy?^<`jFh380yMgN5JILb>$^Y2`iN zJRS+m-`IN?92I8S{<$?E;wEKaV+<@&HaP5IF%hbwr8`p#fkTa=4xjz!d7`+I9@d{r z>vjK`kpX$V!YSIznrNt!$tgb;cKQ<TRs!|W69?-m3&LAt)TpF%%j|wx!8rN<yuVCU zq61eHZcmwASYZ`a#d_2Uc`TD>4fkN^F!;$BH`wY-h7!Mb#a3ZW<gD|DaiMD7@~d-l zIQwVKG~qhe$L+W$k0>BpyU)a>v~is^j<*r+b0p5IyH#)P@nAe#T>Rgu(7_IX*KeWL zJAA(<>(doAVe)<>SxJUUSu&M%f@)k#&94y{uqRP(>(P749>-5qV3SaDdlywI1p_v1 zVRm}zr0s+7ZU<_lF=Kg3?5ZJtZeZ%N7A+pv)C+9_ygJ)?_k=ScLL%xjZlhgcUZ+I{ zO}DV`p(2+R^}}~lSkDA;%HaoJUs^F<6sIbk*^QcEU<tlYt|Why4%D80)6yW}fBs5z zfW#H<ubaO*TtJ-rs#tI@pt-KpOq6kT{N$18Ik2F>=krwi%1Hd}hvcP$6AR0_rUJ$r zPpL0giv7gzuW^3=BmVqg15hXQsJPpCfOJk!L7zeX;I$vGcye9QJ<^YZUDG;BP&Jqo zWZoLVVc1pX8oR2Z+p35d7QHg+u;<h4y(tkmJXDC4hG9RXzP+SvDTOj$ofdzUC^%68 z&=}_N=Yc<ZLMbZD^8_*ro+49&m#I9WjU`0<AaY@Muaq&0H5K9v5`b5;j*+tLQ`%d0 zOyM4yZ;#`+c^wkL3|Ml%v}G&i;k$X5c`bVoJ?%x_cmfkSYi0Cav8#Lz?CMp1N4awR z4F^Na!&M)!QDJSNOBXAVHcFACTDtVnIq56;?GUV+7sb^Pb7P;of=I6(;qD;9OmwBY z_1xPUqfFs8+TiH0#zFJ;P1dBlQIR_OZlP*2cB{iawRiiChki**tSVuA%(wlASBzE8 zpyLbazBmh3hHh||Y)zb@2M~5B^D`S@Fod5?EuynFVT`Xpc=u+Tt<bmsz#A%#_pwdZ zgrU~9r15#k2iAmNHl-)25<iZfhRG(S!~U;8eoBR%{v>K9dzftoN=MVTb$u$)ztI|z zj2CX|dsah~P=}9MKsH{v_n<%Rz#j5c!yBDs37DAT<v%^3Y~T9DrTrtCb3#MtfG;9; zCsjiLS7Li!$7dt#1yfJ!;?sIPw?-YRtcFKdv)=vO!3L1XhSl$jPp>>3@z(}|dfeWN z56FM+tPf<o2bIi}JU+FI|E@luR?NC2Nc|<8)o6PHPcG+eBaa~Eg1Of5teZu5R|;z~ z){vt@A7)urPrlfh>-uMg54BHyWb6OWQy~kbK#XD+obRI|Z)89^k|2<-dUJr#IDLjI z(;mXG?ED+z_h#K=#5;f-c-!nUV)edb4=SN83VT*X7Tc2rGdF_FaP;@A4TZMA=ASP} zv6vC*Sx>ZXzxKLt-KE6>Ho_8WU>mc;DqF9>&{}y3IUY?{V?rDPzT)0+vlsTj&#kQ? zOf`XAgOYT1N@}OFCSP^Fnqi?&=7qVR6yWP8>LYE91ax(?`eRRTvS!;UMCyYR4=LVj zhTv@(w!uwfhXN}wO&F=PPc(jg2^uwYdfr-~9*sX@9LC}b?R}Pmw?vUaMTjM1?VVMG zrIzv;ie$^X!<sNO3z%Fu1XFJjrg0QIZB_u`$=g(1G{(_rSHpzyfMdRVgGxLcg0<9` z;rr8{8FZ5u1Bai#x{7<DkIWG7(_e~~@nm<VU=e2f$QeXPiVs9Dpo|J7=M3$dX3{)P z`kkAa@N?(-eAe9V6dKbmWg{@d{}F$$NSXI%m~GY-9P627Uk;qHI%M#}G~jf<-fX<2 z|4HE@GG&8&4yzsNfq2&g#)Qq3Jgs^REPPAs^9MaPUb$z1n>$@9;TCLH>A>3*Dmb+s z*STKQqG{2FIS49LV%^dOp2h>dt?INWpUa<7<u+w7|AeSk^avcWRN<EPu-}fLS^AYl z>j3(#@hS%8#2R799FNKUlW_(M{**y(0Tz1#GZ1_Ylm?SLttUMG*t$H)GL*pHhKv&! z{VR}(=iTnnR4m<KM;b}7xg}5j${?YySdYqfL$%4&6ptrdUjB(Of1JL66y&%^)9qOv zEC=ztDowKcCMkytu57i@O6|yHf?sUic-RC2n5Nv>a658e$;1xP3X614xXoB?g0{|! zz^M#w(BuJZ7RQvs@4oVa0Ifw;Rby;1N}>CU++Sudl}>6;2&9H-x)bD-av>trN|~V8 z4VtT}mv=R(o=Az(6b7h9yo*eO_(4~g=x1TZ=E3sPAt3*guj%G34XE!c_m+{d><*8H zxBbM6WfT9eBMwVP7V?73+gh@8f?TaoF4c{AdJ)@rN=tJ;{37{uS99*;7pxcg+I}?P zJs-XWi~sg}jXM-4+JPN6KatE-Am&vhC$K9vU*RhL#VnP>arQg{vj`8A@C~Gm3t-QR zLCV%*9M#2@LWh233-0`V`fNFR18l`w=$&%dsp?UW(R*%ceK?T3ZQL2*M=Da1IlV|V zrRO)vvbc|uDcBr(*B}Q0DjAImRUgP1nwEP3D?mtN)y!wKe*X3GUsb;`Z<n#j6@@hE z>bniB*60MN{@G*OdWfHaNdS+)|D+Wu36ObwuJUG)@)gD9X)I@F1YFz7b-Ad?+K+Zc z2vAR&@?zz%pOd8b)V^k+w7gL`i&7aXnK~pw>#!LFWHy>XVmlmpGrN<veG_;~Qn;Y{ z;uH2GsP%!w&+HB2>xeg6$1&1m6cwScB0DW@E6l8E<`0pcX<tOf?Ug>3D=(QYWPfWn zwNbGGm^c1Tj(A)sgQ|a9_XhEtx2O;tVOY!xZ1^3nk(sfR84%4o?n?IvKM9Bb90Os# z1QqyXnc3(*F769>&e3Y=1=5}`K_moYnHpxB-h+?H?dj0vGIEwl4lgzQD&7h`zaZdQ zaq(cnPlQmlB(s20=$Q4AEh$AIf^(|)cm?JB=~zo;-lR_MA#+_bzoLw86u*Lri-s(; z(r=$_WJioblJBSlKm~_FiAIf+v|#lYE+2o3rWO{4bLkf_!fFuvYY&K3M`##?l<9v- z*ivq!a|-R*wU{LfpmRu|*KE+npzyOngP_y%uzguohNU_!J^i;pNe*ratvo<2v$`68 zJ+2zgYNFLgW1@bJ!-|rVk08(TiYiWp7+zHD2w1X!h+fE1`vjHXS1U^C_b1d~{0UH) z1G@P5XO%gnFSh8*yK@p|%IiqoE}@R$q9bQx-=q0+Q(ZQCV-^bpD`Uy34_CbjTs6+j zN%h)eExD^aUANY--j{h4!lq19pVa@3nuTwqlDW5x@zGwN&01$wEH0E%L2abNFiccs z{MkgcFg`RX)HcD+_%*E{>~-rFS{&Ly(vXe?YYNnIHF*1&o2`BkiG_|~vMud8V`V2% z<I*y#>iv-7ORyG5nb~%LzbI#$Hn!KCU4vypU35g|Tz@OI#p6!mLgsuU&G-8<qu4x+ zGdDf0iFb~|FE90WWn}W{!#jl~W#lpUoCNAxD1kkPiVk}^oaGgKQJ+do<j%rQZNF~9 z+iFuLF}Dakl1M4QLz?2?q*o*!aadn6Hq7WA<V;Icu0NYu*(sU9hf7Rh!f0gg{(UI^ zbuj)LKg)1%#8CDAjq(C^^683rs>G|==Dkz-`!q0K2@Y7$SUCQo%DWDoI8oJ;I*S$s z9C=E0hu{LDL+L(O#ztbtWvV)#awRLT3`GO37?N#eHSbPV(7m&(#(SM-QE$m+o^JnX zJA7QHoFu4E`Gum^cO~}>-ynFApS@+X;ycetagj8VbazFs#9ZeOUsCOIg}HHhQRL$D zn4&V`Wm%u33GG-seXS<r4zX<+_zz_*8ij9(Au&%&ScYTH>p|bE<52B(Og%5Gfe2o} z*g@$tr|l`!O5_>o&xa8Ef%BU(`z)<nw)Ej{69a+nbWAC10KebBs(R4%h;KGgb;Srl zB4~p0sRg&eMQdgLpm%OgTPK&6kHamOCjRf)=649fU=3s)ZQ@U5>M_#X;I0v2Qh@Y$ zvyOV0S{C(3IS9;k{1!wmE=ai)p<hAEr|YP&TNkZWjMM`LhPUOBu=3<TP2&TsJgdlD zKza&%w0oQu#;wKy@unZ3w8Vk(J~_$4k{@I<sRfF!Cj8QbTyPXh@U8}8VoSk*CA!{9 zQ;%M364Z2xZC1PUA%J|gaoDibqHz9mY+&6G0;f<263o|3Jj#A6Y=r=#0s^w(r(vru zD|SJfH>vyGGmIv^=8ZPW1*NB$s$WH6db&hUM;-)(oF5uj5o}23-C$_D44=E4f&N*& z?@WwpW3bORJf&g$V~G#^wB%HJ1&10ghcl(g^cl;+IL^Gij>!-?;cq*s4wId$vlhJp zQBHGiKcC9>0`5PkBL5Lv2P$$9g05QA_f5RGYoG)+a6Qbybi&}%^$@?(rX>F2Gjd42 zL7i4>#u|vddN>$Z=g_s>44LUs$o0fVG*eBI_hLHNV<%QTrsQZ+HK-PTO>}#rC!qHd zliba>$o-cn)dhnbKINkvK7LwU(oq4RbG2!dPA-Z{`6H(|fWf$<aH<#2z8ebxf2H*i zo~^}D4AuafU4Y@&kD=7*tv7U_=XlQED1f5v-z~m=<xuZ4`*4QNGDv%t)O#?EQc`yk zIbJWn8r|nZW&e~L?rm)kyxtH|To1Z~hMvRs1&voEaIh@uYp5WVtVOuVNhTEdq*&)9 zP!Ngtmn3#0x5G9I-}|jt$EReLXx#$c!X|lxW%w{a$jBV>Rsf^KJgR2tswOGmCKjt; zV3#BrX+Ijg9R6c1SaYlHwa_xbxBA}CoqIAJnq6RK{`UE%LL%Xu5^oO_^!rFNws^l+ z6h)6*l(Jr`3K1s#IHds_1h4{<)>wQFc&~A2GGM=G&x6sD)3VNdL|S_D)TmZ@*Kwv& zy!gkv(wpIH@_#m1eUC|Xm-voOJt-G?GDi;-rI=q299h=CgcF^=qL=blN^7}P&3&e_ z)F42EA1xxH`r29#BX%_@lo<8s@RgL{qZ3^zMEY&Upr$Lk35lF`kk=t|-RRdC1@dk8 z$G9!v5`7?<Ev4xg%g{v<Hxs~?=gLb?aI>*>hG<}4S*`R1okHpiSH-kXlCt>qpY|RP zm4*4JB3h#~-Y3GLa)vB`HVqtaysbBfV`}V-p(;dz;9y<_9=?j2RLu?9h!`n+9IDf~ z{nvS(Un&&%E9oL<EYwfTd)Hg{E4?auP7D8C?_Vo#X><MgGjlMz^RJqKGYyvYH6?BE zm%lbB>5!bcJ_1#{iXOFcH<IsA4U~+U4zHK#5L4EXb_o^P?^c{)DU<<*g~BZo%XP8k z-eRnU#uX2#x$4$Fm{j85W@U5G_#=7tXfoDECUNo6U{Qz={(&DwE5uk)Lo_aj0jfxa zT60(!6L-`YP@k~n8cJELdN+@e&ki}yey7B(kpt#)qy7p`aR(;uyl!$*B&b}?<$=Pd zoaReD1FDRk>+WVL@#8OXq$Y%PIB5c#5c7}7V8&&0ydpD`BDlgv08c!iDOj+TL)jvY zrbVS4(81Ry0D$^bBPw1e=kfLZemt^jH{-^<#@kbnYFOJXcg3B~O%2t;hPARb-r4QF zW@1<q(<00<#)H5ND5ybIMlK+Dg1U3P60D|zKYuw-TiyU6vQ)VDri6ZNYt?A72JkU3 zGy3GaIq_c58bX4`&ojG|-u1h>*Ub(4-<5w{1W45_9#tsr;1}=E!XnelkXAQH-&|z8 zh2?<I01>sTKT|NTqK7M{nnp|(5pPFZZN96XnX5t7;IsjlHwuo$%dhLygDL@Ntolwz zUvTWQI%G5W{!Ht5WV-CLUbC7#!t~S$?`by;e-n`yuPz&`uXtkQX43w!kl*({tvm<Q zD|$8Fw+64<xpuLgB}wn>>(B*_!XX!X5p!tSDn89l6}>5ci&>tz+C&e&VP-{4Pc2?| zlJe>EQEmMoX&;6o-|TFSOerV$5og;e9AlvePduieVgW(BtRD3ZhuW46q4Gv=S@&pe zD8s6(0Us`I2YGK_Kw{vU22{#v_;dtGvFlxrB-%1r^F0(YJCEf%)V!`QVpPC<r$I9M zXwL9ToWu;Il~~o<D(~wu71x<Z%+s{TZ8tt*w|$nH$C1#;1Fhu=Jl+l-4XnsD!nW7+ zvTmQha4W()O)>y)P{Yu1C`J9uB$mn;7P+=)34a!K=+}vUg6_@d*NwGhR}=bpeFbjv zIe~xI@T5la7QFs%sPtdoS|Gq1a{j{vl_l@8@>7<217cbQc0Wpxy?A69x(74-X;}$e zq0MW7>u~N<renx!srMs4SPj%-O>o;LGcPLD|Jr<srG=c^abx}_k@UbWS}+TFsn*uS zZsXFEW}D35on%?`H>L5WNQ>!?D57yA`S_-Xe||=X*jrWlPdrj(EkoG)ctGi~&b?b8 zW?jtL0B&b5%y6n7_Vzu6&%L}K6=SNZ+%)UOrrN9&W|6rxxzOWVzs)n6LIUg;A~Ubz zLd}1Sn@4*6$hwmq53-GHW-QzMciTC`1q$l<vhOGJ67m4*MRloRX4j^fGS31Dap^fO zczAR?%5gY4eUoE;?}_<M=)HlI>YS}|y$fG4#*s4Onr~i6bQbi4^%xXSd31foe5Ldx z{p~j?E^#MPnW6ho(r0?npsk@qR)B6MhQatmMbhf0p%FNTh}j~9PD-u~Mf*T@wCJ;y z5rjaGnE8w`TvqPLE*_@Kr>E`2wXkT^5&E9703G%l@Y(AK8%u8j0KeG@zwL_$2}L7T z-J&XA-|HP77aLs)q(vvIBa>YXh`sKkevR&f-Tn-XY_3%ti9Yx&-%)U4G`Cg}d*|VX z^>^t$e(&7>dD1v)cs&|7Qc(FzvK>3d(B6lsV8>Xo@(Cm83q68b^nE}MzJ-UdTv|>m zZW<qP9$ec8-!>xoB2yCgGw;+{_p59-FRI%lQO!F$Mx3nS?kRD)RH&Uz`}wzwJ$Z*F z{0BRng~_G$xF%^fnRv@x7I%XD_1uG+_i=Rh>^5ZYTmI2fJ~WkXDKN+fFC9c{O(aQh zy^%{xcI{Y1OKJ-!ffgf+wPiPGQ5K|zJEi3M<M@}M|MO~5@D?0Hs4{K01n}sS^3<z! z>KY8rt(2&XS87ala(*cVxDwh^-;D!?f*%GpEk6=mwgAj&8yFn54UJv9^oJH$Pd$(d z^%0l9`?0<26-ul@8?AUEX83X$)uuU{h!9vJmK;Z>jKX8)2ZRA2rVM^@tN&Jc+$Al` z@NH@nhKP<zyPZTFGaCrUA(P}(xum>s@||6#G_|a1PQB&a7(C$H3*pALF~qe!k)<_y z&l1HQiy<dubKQ@T&#O#SzCzmOWtRTNDk1a){oxBGwstQGlu+B!4GoHGbI2!c{~~fc zISMfZ@CG1?+^pqVO^gWbo1Psa^_4Q$x|-y*mfXrFhQ)aNAZO<H&0Sf)`QLb{6hCwG zT!R*%w_Xx8Vu?MXrxzn5^sc@5-mFdD%E3yc-mlygcM*b{;ch&LC!^G8>i%2uwv-`w z6aS=LX#ZB`rMZ{X%zDYvySwgx9)GVIoS_Vk)kUJmY~-Q*JICpgAq$p^bbe#sXwo?W ze0`$s@}F#{wjXXqBBc7E0Yk!-#mqY;2hT;rL$RO4{RG%0w0c2$r8yiTa&q^>0mA+u z>-y!gXm|T$B~W}L%R;zXgmCS0WswbbLc)_b^3YoP5VCPro-fwU-+QG>-Ae~1g*rA0 zF{C<33)=I7RF986t||x&pV~gr2@hy0v;em5-$#6DIi5JLZ&((Ul}ukwx(;&h-~9VS z3`1=9-(Y>zS@Hu^Uu@HO_a+>3!<STdL)>g9&?9@$+1l*PW6`8DLSMi)kU$FmkMLrJ z`1SH<9&|&z((T0Q4+x-<7Fn0v6cZbA{M$7Dhj3^hX{Q&K#kDhQuha&Yy7k)m!E@4w zsXCqA-?ksUYV-uLp>N-^*rQh2_t`(mQ~soQu+J3FB?WLFUkZ+5NyTslnRigBGryR` ziIF+f0Ve6%xwQu~A|)OJv10p}@_5Rsvnv2|#?>!0w^UyLn!`fmVPxEeEC9qP2l22^ zh`o7hf0d=F696=kJW)00Y7&2%zrtD_?z)PQWTmOCqwu-6u+M$BeX;$?Q^YOac!1$9 zZQFT=?-7kyyIhY+%(SO84d3}9E$!mD(1c5)<PAS@x^-NU&IZ%vd2K3|fC~NI8ssWl zty;bBbGIqV!Kyq$`&fmVDSbMdUMEE~T7TTtsvBw{@SZ$5Il{0WYWR$rc-+M8tX6re zjyws^UGlfD?zwlxqDz4B|JL=Bla4tPtQW1zg>=}>23W&rJ}ZF_z<E6z)HW=mkYK!F z3*_^*ok>l;YhKM4O?g1pf{Cwy9JV@@aZep)-jgoO1eC*jjtQO0V|3^u<O3q0b|Xyb zZ;3)UI*-yCGvS3O&`at2jI4xb_VD*`Eu^W(7(>-p?wO<^`FEg$FM*60X%)Eg{0j+G zC~*|vLr}TDMfJdnmHQg2YuV%2&?X!vK&>4PcfGumurP$%)DcCmtX{0-$IO|x&)=N- zuA#FqrLlhvB#aumA;MW8P{88KUqE&TJgLTw60xCc@JwL7AdMk)ZRX|e60|h51k^|O zHZH?z42dzd#<(z$)+iO*-Xteu2R|7D`@NAJ+Q64jkET2M5A^K$T~KAejhqylYk|&q zVFODWcV#@y)tFmGjO(3bzN%`)pXwVtUwbea@4{*=F<>qfu&=otSe^TlfLi#nZj<?? zPUFjMH%Pjt?W69uoTE$dCKN0&!DK;BtKk!daBL0~9X$u4aT>d$nRK!ml=)1(`M;!j zuRR>NDhru9+kwIC92d)S<Z<es5L`Ll*lZTNBsHER4{a~5N*QWUg+v_G$FjWTRjN%M zqkeM9t94DX8HNBj2NC^FQUO?f4Z)_g6q+w7jiq%Yd~cj;ST38TOI{~n2)EgmdOv=D zp#1zG51u9|xi#b5a>Hx;_f{)Z(4BOfp<Jq?jOoI70~5NoejcXgL9J<fRWd7j6=vRo z@tuy=RL3n?w)x-Hx+2?FlmRhe>ixf($}Jz(GL#?K6Z8#3Mx|bLjeVdE_NhG|16S?d z;`LNiW6{~*HbHn#dIF|?XAkh~-3QmdM1fSeiQWX&A#|}d;<%PTw2P3kdZL3wI+Sg0 zibTwrux5ho+blGg6;)GJAj~#joE<D>KTY!Wf__MtR5$oZ{zf&~36|KUc087vU9YK0 z3iSyD1EG}0N+IPyi*Lt-371x;Nz(SN@!Jz6cIJb2v2;msLtwS~)U$o*TBT?r-&Ssv zl&(p1m863_<4(4lg4!Ei{Z84}yG-dIaDz|j3z$wvN$h87m;u^g&U;=+!x5~#R%b$w zs57x*jqioM>-YC50dh}fbS#Fu0s36Igvsan%wN@Le+;`B>NUM^obJ@p>nPyhw$;i+ zRjcdQ{&ohiV`w`L&+}`(zf9u)$=4DP2C<5{?(f$m$5vDjLccLYGe1U$V`iaG$oz$r zN2J(Nj{t@}k)I8R$NrbMWmXQehP%UB)s~mFrGNc{1W``q{_3oO<W68eOiYd(Dy7PU zn_(Dz(gn~me?=vyAy*fe)q4fP1M+CO$S!!Js>aHJlnk};GnXuB^@!ZzJ}f1r(>`LC za<g~NCLU9EQc3M3vi`@^i&Z$g)e!bLi63=9-Heuf`qz4$33(Y9NfV#!lS$_Yx!Z~w zz<BB`0jiSlwHV@&Z`#uUFj$x!>HkhR9Cr*)L-g`9cIW3vQkVg`Jg*vOh9pd6g<^R1 ziLwt~Mba1-8H^XMx0fO8DLB@#P3p(j)YV~6Xc+(M0N2k!0UfKk*opZZBFs`G2gez^ z)okvHNzhM0U{*!eox6$xF?02(P>XW_sT_t`pW701aD~c~A`8knL%BwUs2XTEb}RKh zUGg<vq{dxPLNQa40mlxtN88aiz?OdHPK>I`OM9LRo8y%ifPN=5HKd+q6r*r6e0UFg z*&_~bsi@$Eif({=yHyaZ?(#br36(Uh<q~BQg!ozNdKE7U(S7pax|01TgYuB!-jXuw z<UKIyi##hKiie!rk&Rk`5r^W>eQxu&-)3%QvoPnb1)E3k+M18i=_98>lB_M?4aq5j ztChg8B>&P}P@x3(IaIgMJV0)pI}{&FRyK4>Lz+UgxN@RZWJaBQ2v}?RDtgu#xRNcw zZX#U~5r8CjXI;dR;xPK^Of8vwB_6@XO`CFBRVh7~c-Q>jio(koQ_B00knp|iyL6-l z7sxMb?j!Dmx|HUHb4v)`x4}_!7&8#=`Rkelsx!lSxy`weltGbs!-M28lfzU*ui61k z07S8YK#>}l!|emwgfo|os?KJP*Dqv!!w*6a_c3L1$4_aNEQ_Z`16_?C49pnhj1%2? zF-<|N;MFAMmq>MnMrs5ao~pTKr}vU#{=6(F<C8zP3!m8ii#>9k_`Z>74Cdu0hgsPs z)U2ZuAPfSSx#9?u(Zez+aTy~v`C*V)lj$@jC5id@KIz;R&f*X?p<W_wD_Zs~$H3Na z3UiP%FM7ZMn0)6?_x)yJF3hy`X@WBL_!_Q;Zqni)u7(9}Q#uwN6iqTBOb+5vklwn# zasA-nkyLTQP}n6wq%_R`Aqmr19IVAOXCWrlxtr6nc6tenHsQAJ2x@E$@Q@h(n*Lp@ zf)gj5_t$(xA~YwSLbel}!COuGG}dKifY&=3S16Z^a+5NGlz#5H&s^Y5Ri5htoM3)C z740AS|I5gI(yW3LDuEIHU}K+{g%d}LDs$}+S9Lq7G0!YI#VY35lO;c2>+Hgx!WjWJ zDGDCsFvHjayOI{Z%UtwcM~DuuA*&Wan@qhvd^{_I={Y8ZSJy2GXkY|TEsr#`^iMKS zJpgFp^X-Z?vhA8Q-rLO-Dt;S#V=`e0+4&}d)V=miI3*YV3+Vs_B-@+dKdj{>_4`Yd z^2;`+KZJl<y#s5k9xNV2G{$S!T$%00``EO)XZQt*heQ+_h<3+xPI?eBR|ISFE8k-R z^qo{e+Ip&l@o+Rqcy;r{w6_@T56d#JZWk|W;_x#XB=v{BQLN&w^8%gz^;J4%E{;L> zJnM25t@O+n+IW(l5CqFnjr)8}C@C?3CmC~7GvJZ>-OpPEv9HMpD6qRHJNkM0joOiy zmgC4gy4?hE^sUp*#FgF={#QZaO-HAD)T%cSz+Xzh;Oy%2utq%jLkj<Pq!<|qbA#^- zs%KZpfDOdCWSU2m{t{@;q@3ZcbfefxBZFmeZL!gnXJpyc%!{zfnJ^iX{n@YogS!74 zeg7G%3QHOU)~fgw34aTd>O6cfH-pv0H{LAlrqE*L5qE994EowAq5P&5X%IRly)8yq z7ms*et20+Ux-b!s&iG`%#dH$3jAQz8O?e}huXV#U;~8znu)!hoh1k-Yu24b!Zle+# zRYH@4>(`zS;tBb$YTOC+Bxhb)USbRJ3D;Ls$LjHyx3~XMTu4=t2Fu~!(G=H}=00R% z<Wr_7F#feFED+*P5tF!rEw$LF#J(<A)LQ<!W1X;(X$WDB_$ZMXQG{;+2t7<!T>`Ef zHXt&zKk()TGL(Iifl$k9iP8W{OplkxShCtecephuIgIhoC<-N7Db)}_c|u(yjXd^# zzP%utN+t37>>TA}rJgiW#$zwKvx4FK!*HOOdU*VYJxfhoF>dx-ZR+<==rV#o#X#j{ zR24*HY#BiLNr;WtNvIU8513{1`RozM>-`>Qin6c#5+K+(-ldPL0W39|VCnp4%)4`7 zo)*1<M8y|UkHNO@Cknmrtk<$)lT>0&;Exr9e*_6)90uuW0r|gcpJY@(kODhi3>Ojh z2_Vstij}t%Bf(3c-yBf1i+Y8fN*f&iYe~QFFyxt6PmPje&}7mQ5g*yek6PGQ1?+-) zpO<bR?O&GFG!<9x_~Z<SFh@dAj#5eGBw@)U03&Wex-LbKB@1DZT+20)ODOnd@!q-G z0#$JO+J{uh<t8ci^^2?@D^?xocQb>fQ~!X|w*Ih|(c0w`0(7i6@|sqy?U{ZWj0V!q zu9&ozSwlwqX6^pgt)g%`>cm|QifKi?`nW`46!03j@~}2Po*?C+;e^MFNfeQ8JngiF zFt)Bf>+D7pbGbPWi|vNXF|e5yR6hZCF=pl~u2qR{yo@$of5Qhb`$HV!X&n6csgmEQ z;WLN(Nvu@RA^wU1j*^n}0<3TTk>nCrX%iNSG+c6TXI-;^f-q}cxODZGyYmCBA_CN? z!&6h6!FRvOXo~&@oR8rgp@<&$E?8v|CA4?#232orw>!<A2#a?jDyv+Uvlt42=M$=< z?58`ofV@WK?>ZYTAb9umwca*i5Q4<Zq%jV(m~yp`Qlpf!CWS<*&yTWtClIpKTauV{ zQm{2wYuhWhzJEctY)oG{6-Jjlz@tQd7$4Tfn;T{k#-H%WN|LBYG%rB1my&0~8r#G@ z@|Kp@oPJWo4d3vs_l!8KPS1Dz!|OOLxJC72g6s(%M^duy!zwHtA?6!I@KM8du`SxN zZr3imiy*t<zl*8%>nk(+*iIY}TUq$^@9BU1TMy-HCMmN=<AUaaS^%Dr>B=XMnm%Hw z5(Z1gsd!D_&^qmaN1uC-Z@x=@7nZfsEriREhTJCj){|Ft#!rV-R*imP<X{i2M%S8k z7xAO~SJ8(%cL5Kz&LK0;q^BqAkJ=0|2`yKyP|6lZ@;ay90Zkjt{w6%$;bm`C8vSU& zyy?}g4ubFLzy1#$_LDStkM*Ilo?YkvMSe;e?58UCuEDU{J)x1eMW9V^!(p&LGe)qh zA?<|kqVD5C(a>*Pqyc*+$5t&1w3-)Io4-RtO)5ucSc>>@w8$%p2Q!*)%zw~}wf4R~ zMD;D$GxGhJeF~6xQp)^=ORuC2DQl<qo+zOqsmLvMB+7KjHhR&zWBh}7YCJFg9k<x9 zDR>FWIadq-P^@4c^(4gp4b3!nlo??Z#M*h41J0!eCg1DD5Q2|)qgl(ILsbF6w4gsd z)2#2223l3lkBV$~UJm#QV^96p3qXkmFT+TUjSWdwS+%0859PjojA7UTg<(%K67Tt^ zucBTJ@R+xxze^jjbE2}fL;*eZwoN54hrrO}IdR>rG$RZrvj#KXumR&W9<2wu=EibY zsqXqBolmw$%T~eC7Rq&-*W@k?90S(-%GG|T;kBdIN%0Y(#M~ikx0-$NL#Sepsv?}S zeg#8QR{tzsLJ`0#jeOfGYs>Y=WFwYxO!YeLn`ZTR(Y7&Ew(Wjn;jaK*gY@(@{p^X( z?myCk_g_+7Pit87K_x-JEATHoxhids!mEOv4w7eI-(YAP>A4jE(llCk$Tw8#^>G6} zSVF>3tl}E&ZQmNs27GN&8|wEtMc0k(C>f8T*yy*OgpOu`x&;=BEL~PB7>BH{f68n% zzFQQN2@E9J8R0{xv+z5hI0F(`MO+)AS8HIlg1}?S8X&Ja`-))GXB8p=GDS%ji(+yV z2xb4~BgGslYfq_B0`_i`k;yEE-!>0RJ+`MDlEOpJiBdudqF)okJ#OMLCP&^>GaP@U z>C@Iitev1c)t+e1=%i+$bek1o-H1q%eMeV<=!zhHQ@_T_{)#EEB++s=F7xm89A#+f z<M7rs4vKIFGG8Px@L;NNKX*ihSB{GWb(?PJ-|K&3B{^`wL1>oM2_54#$CjO<*Awav zXYcS~p90-c*jreV(ys`f$aYUCmhq%fW+jPX>XUn3@vXeWG<BcN{WO;5S=p1LQ1<1o z;V*)^3cKok%;*GHN)c-X>!ZDKTIniVa(TN%?NV}A=&h~c<YN(calM@%#oK$~r8909 zzMbzK*QO^U#!F^g{=olR{r>mUD-(y?p7Ps(nTr{l+g$u0jiUG&o2a?KImTFn`m_Px z4Ar><FZ!a%SC?!r-eY3;`neR&o2>^F$ssB6g5~Ade5EG%lT#@qKrUxLIHiDJyjinJ zC%#Fq&`9%;m1k6+tGEPJtXKU($>CJhCVK7OR+i#5WapBqS3o|r4&Cv^=;+00HmmT{ z@QOPG;`-{}60D7E(x1q>XS*I}uP;f1JJ}FeI<`oJKE2(%wX2emgP{Oak83OY)F`T! zQ8h#(OqIQY6bjFdxmu#f5`flNK@L80z_{fUI?O*`BGTwmH6$6+O^`gs$$kYS`cKFn zyzAsCGP?zw@un(sV@i)jb4j(RDJ0o<wdFe(<b0B_lQ$d=`9|>bm5CMZ=Lq1fI0Np+ zwyHY7zXU6eSYi#1PYak&b8w()Lt_(h!Xgm6aC`^w>+tb$A}7CI^O<9YsdtQZ!2mIE zzH8!wVM2BjVY)LnvSiQPaX89>Vq7dYs=(k?F6dU)w=zF-*hYrl{FP6SNCBg%<CK%I z1{^~1KIOK2_f}cIwJ|wBxwn`1@pqc{RdPHdrdXf?-X}2Urd$pxfl64%^!o3!*4Fpm zYm-Q6XeDNSYIrS_PAbh>oFZ>RBWPAtU&abk$ng%_^pQl!a$0`h-!MbV`Vu>&w@G9X z&$?JFWQW|JRa=2tIzLJ+vj2y8{C`E1NDEnzCS-%S7Mn;c$F@I6tSKvb>EwEbsEmt8 zfuA7(mmtfaw7#;(utMd57Q&-Fc-&F<sqfkTnSFN1KKjVYv$DqU2MDXD63cMGgS(DP z@Qva@;M9xS0E&GwztAib#81VQoYhKsEi!!*e&s`0lwgs*YzoFvK<ffzUSXOrf#%Q> zRnLDkk|Fkihn+kPc>b1RJSHRuh52zlaS{IX(ghP$Ac<mxi#w8V)r&|*IXqFa>J%F( zDB6y8ebC?-VV0F^HxMNk^;v1?0pcryCtcYulp_<gur%JY7NcPJtp*wTEKs)QCoD)6 zOVO7ir*{u)Qez5XJ<WJN?8nd*JvWO9`$5Zn0D2Ggv^_jd`c`HtDE}zK`Z**5UFati zPGc17?rBNA6hC;`38uPiTy|dZ6G27ko%aXqa=IFr@Rx#!46c*{G{APm69eP1RD8{f zF*J6L!I}@QWUd)05fwBsB*5D4+yX{G)gc4YMjGj)m7Utk)aY#`gjtwBYxf`%t~l&l z@6?p_Ph9=JA?`VIbua*dX?DQ>!x{a5+ik{2AdOnRO%J0r(I|G7wvlc3deR;C%=y|k zw#z2*1oAmQ#p1K_9*NZ68$`Lw&kN!rE>!|9G0D{N8%~Hsbm9x(S}!Y!-m&CP#-Rz^ zjSj*30hYqwI&|$DpY>;bMHwP_^3pf2-TP->%PeolNiRn-o@&~e#Iam_W)XRX{Xjj_ z-~IjfwKD7buh!aZKr+(vL5%(t2S-W-rc9saly=gWzG@g7vt*_R)%=3SJKlas*DWob zMSQ*8I7Zyk5JY+HB>K?cn9KB+7t#&~Gr`rr(V%681E~+j{Y92xq;6M?jf;Obg#pW= zfP)|plPcJs>*5Ro8G!ICW#wdrGL=esW{y|59;P!<POL|qG^4o+cqSnPlfcP$ccPas z0ZFIgjXu8LQ|ux5kZ&%KnB=0fX>N*qjAO?-iNwUmA~mGY`bUD@YlG`O@BUulHxxUm z$rPIkk5qUi)RPZ`DZE}ZaM`I4^hz-EJTu)iUw?@*hFP0~=pO;KIV8H0AdvHINGiEd zlWouo%l#Cm5o#pLJ#3U!?nRQUX?go?nWVmxFabbomy0Vcpyb#}$;UPQU89`M)%38| zj~V*U(z~N8{huA+>nk22DUm@x<^f+rloY?=R9pre-xx<5yr^6cTCM#Rm;Kfn4pP{5 z^B#B+No%q_dj6ks>0cV6e?{;*4i2Jpm!{qTn=ejL&eYR@*6=v?JJ<pNyWeFS!$FY< zA*&$F_n5h71jy`cFML#u!)Fs6M3u~RP>$w|8n5b`kzzDHEWZG~n#fmXb$+chq0J*Y z?}NX{M#{Nkl$ymaGMr*w^}bOJXE_kAQ?PTDRu*O1fU<f4=TbIve!ACQ^_O(4&e8!z zA<m(++zBAl+9ofQUpJi~qDSiC(DjxXk8}FbsbI*NVYnI4=@!G@Wql4lPN34{!?GY( z<3)KjX@Ac0cP0nuP|De`2;l&nIk+$4NMwBXHt||pofQM?<caD4z6#ZKRhK-vjd<`% zh3qniUA=NSJGDvdH^Ue>JmU*{0mmPYP%gr*hNC${=AzVow44SBkE!%JRvDlt%e7dQ zoxQS}Tm_bH+;b@9#+kyQte#pEo7G$f``gG@Be<?cDGeXk8hxcDshWJhgjg%G9~k6) z&Nq)zE`=HO2!N#P=2<h=Q^JIIWtI}O+O1pdk`=%|v)yr%X#+|Xv*fQ?WMaSLe`K}> z|2#>p<Mdx@K9rvk2qXXhAs@eSq<BFSEY;_$3hyr2C47dGO@6F5syt{kZpgqLV-7+i zr+r#8_xVVkI2^bmFN-I=t<rS$03luniu36WF2;>CVO7n8Us_K*k2JI4%klRNQ5a7` zm6*837F)V+53@&WGlb6@k9zjEdxIjUHtRFa1w;k(;+yyuZl7jH0CoC>XzuN`SAYCh zYxfiVulzeol(RYiFFaeFjTv0=ng&@38xDkIbeS@kykd50eTi+{oZFbw$q0-l)7|}o z%bDDdXuyzrK1m&0#fs(Kfq)En`p78kCk_B0<dj)ER%H7iw-h$WH&xr$Laon{Uj-<F zHH@kF7zLn)R2=uW_K&j+i?y&tF<?fvzog<a8K6!3fA)$l9}xo4OQhU2^r9gp*`53Z z2Ey$u&;|--lcWxtFMK!7%*eUiui5T05DIMY#Pdv3Gz!=6c=JY>$}`vR=u1m6Jip9G zPaCUjiij_NFw$uNh-b_JXoS^hTB`cs;Uh<ZS_gkAgask5%46GU2%OEL;8DoR*w~Tm z;3%-0_sL{&Nyp<8i^|0gA0ow)vudd-0A~k>(EOS0W^Ozhs{4~Y>{hT<KtiN>v^aUs zZ~4-6aom+FY8Pb>klhVs?MXRXkNFin^j_jBq~tenpKJ_~!H+x3G64u(?>#sxx~<YW z9VE38lYYP2DWkIOav%0AVjzRHAe$(-pYa(`?5gb7@)>I2=8ybY-xdA++Gc>Z^jnw6 z^q#$ofzZgJJCW}<^y$6t(|)bB(?ng|y+mE@tQ7T^=KP1%Muw=!9SjRy`}c@}ZeOD% zy8=C!!uo&xVNJ!%c7LS^5C+M)urPixtrg{vBiUlvcpb1QL1_W1DS)<fdFN0x5Ih>F zSg+P54Pgnqz9QA=vj=E~O^cRZ9kbH`QG-2c>*0L~9(+h5EJ%}52UY;Z*`qq6Y{=e& z91c*yneluN(8Q~6x9~M0#t5oE3PL!Bp))s|QR3f+R`T`%ZxxWx{3F}sJfJSEX-iQB z0Q@a6)JgwUS@f!L#B;5|U<(V2r6Un{37MLZR{cam-D&=D%10*>l+PEPy0jb9T#c!= z7T$_<zTQDYM0^eR^yMDbiqU}|?^~_}1pzd7(IxTr+1;0|Z?{*eP<9}#re{V8Pmvbb z+l7F_8_4<m&WBtCUL8kOU0ZaCi`VIf)|_FjA`7q#ck;vaFOOuM=8yWN=)NRJ$qp+b zv4`UQusSb+wByS!`Z*N!9K}UNbniyC_7MvwfrdU<u8sg{NW_x>Nj0iL`42J6SD50N z;|`VJrPK22*n~K`+C;{N3T@;27PK_SOQ-KgJAH~D2$~!?Dc0IMI}8{MnHnwq_>W$E zYnmKrmiTPD#vo$?_}If?jCcSipup%5v7Ig9#M3Jy?%91Z|IQgHo|P3|WPeH@4SLro zYCPmIlaDXMq#k?dZ8z=YBJKWh%@h`-dTucG5hL{3X=AtBcK3Dkb_nWVfBp8C)ve5+ zK-<x4*5-AI=7n2v^>l_`PW|71V=7)-{dXRsq!lN43-vTm@sIxlobO8Ny;bLLyIgci zL2%3~$BJEo=h0O@d_xI6{9V5Fzyq76I1Lu%1i}mm$(+n=`Az_FzK^JZkE3^=8!(so z>3+1fdnV75IQ+#2HcP91IZ>02g{=h?YS<TnshelimXoqD&;g1j-oi`#849Hu0mF7_ zzhxiXQH+H792VonpFQ`03{Rp0HABJ$+>r`lw95xBKV_1y^hQE7W)!Wq^0&~IN<{|A z-`XZ$OG8V4H52Va7MNY8oD)*g`H-B7E4^|C${+e{D@M63V$qtIf>W7e6*%6#Ket)v z@J}asVsY{Ggrd^LTaM7)JisMV$5(qGz7uafpkar7YQNQyzQ|OWu1?7Fgahh^K_Tda z-He@m0l{vtgI=%wI7dDjeKT(xGB$p<rt=5{68PK`fKNA(`LOzJWrj#ph;EA~BJ%dq zLsCra;Yak4c^`g|WdsI<k8yf``tqe*(y4H7>F?jaG{G9m8}zW!k>0?Q0zxc|t6rq; zw9}{QFJFg~m&#do2kdM2$D9*Bcl&(ge4R-5V<SYyfmi2G=N^}!NkNV1?rrlsUdDjz zujXiV<KI87tf5mk)F&<5=e?J1!nEJ7ZIP`sga!<i)NV}0mBt?v;_f?__Dx4Mms^&J z*MwC+lLKd$MrGVLyKZl9A`b@+eY~DiN)GF`8a}^xH$V&`I_%i^v>&>$?k^DVBVaRt z>Bn8TpUwU1|Gd4>sEJR2<jz69j+0fuQ6D3e2SW`^%Kl9rN4<%*8c|f8L~$nPn9x&c zW~M;RvMFQLZsMBp7}599NvAXjkva(!NZlVzc@(WQ^kT;s9gkn=_bydA2!S1C8&iFS zsvAy2@R%I5DMskZFLKw;;V?TWHAf95eq-Mm1nG~GAQq<zb}n3{Fk#(l=FD$~xQRu^ zY{i(~g+I)=!o0KE$C$Wl%Yi~A>BrPRQRBgfhmAD1zw(w2cjPRJ&OQ){hGalT3{JT< zsuJw0L^)8Vj>+M!W*r<a-Kg+Qw@$3qR2D_q<5bTqU_F4mUqKRk<`{gqMy113Em-m4 z>inXrTHSEp&*+eZ9pBBGbJvoU<KJj)Ecnfnb_lYCHoz9FTZo~1;B{|23-Vpvq7ddd zRn@Q(?mkU&<Wima>RqcSg?Li>On3fs1nCEYvO2{CE?A95R1N%-C>^i<C?4W7z6Gjq z4HX~-0>Zn^XbAHOz9y~-doZ##C9tF9T#lIjMmWE`PmiEWH5x0vS3#c)CmY??P(5ty z2Hpe9rSHwIpWggqUQz0ZRQHBoxmq-WcoCQ9J1t*N|KZy&A{j^R_~|M1Hh~c$p`2-` zyL#2;1+NPag|R(9oI^(qo$Yh~BMRg9dsDUwp3<CNKVHW#H)!_yhp9!#&Bg`DWezIh z)jsVoBC^(hHqHD**QvGhHX%`Ja8&5mikDRlW1}vW@v)$E8-?9KsM3G*?+=yhnTni8 z@P8^KsYRSA+Y?48h8{~4p;@@+N2Jk05dz+~){XI<Bv|9t(^Dg@Sc?fal+idCVLIKa zeMxx~XPa-{4~A`L50X57+EA{&2;TA`^CzT|qNsINdT?k)a9ba&RBZUZ@l_MG8N6g6 ztt<rpA>pXW_j{nyvy1cm!E>n(23NDd$@gS&zIXw4;*MV;@G8x3hd)z3mH6-^<0#Py zCMM?8ToWg!0^?)l<FVsAHH6BEb#WwK2&qQ~3%~83=WieaYL6d++>$beZ#k08c`<n{ z{%pMB=YQW#QS;<7v=INg&}YGI*g;!SekoL>fEnao?D6DwL`8x93cwkRV8;@+B8wt0 zMaK(!Xchq4%Bbx}eY~YQEDn$&YOL9Hta8R2FpQ8_Zl0^!rN%9v>Hf?So~<3xMzM4z zzx+k@()RxEk9$w1>oL{i`#+`keppFlh@FX?N0)DA!$)N9uV?Km^}C4geiE-iZl!VL z-j?LUIPXuZ1y*Wh?p$s+e%x8#)|KAyU~|szgYv_7AKA42Yu#M?%ir-Yz2)E+J|F_= z`#)!wgT#?@GC!k;(XsecGXN7+Rn^OJoydiOU#VVbRoNT8+bE{rQSBkk>Gzv5_|$CB zrAIA&cK3Ii_YqhqG8~nymaBoQ{>$c|{>i8w(I_F1tc$hH-_yKVfD<UP|K|t%%kM$& z*Q`c4ah@~NN6r5|djEH_ShGw781NXV7B(BUv2Di;$oXqiWg2<nX5%MBjy#kv+~e2H zOIs9wHhWQaxB2}?)B9IFFOq)j)#N{lORBc3|M79L!Q}JZ!oblj@79LIOiwATW;&Hk z_`G%G_3rXr?>lkT|GCMwrWt^{-TDb+%)A&eG2)n8r2`DrMvNTz+W}#9y%;2TVDkj* zwr`WVuj?`LU2W&Nj%5B?z8n4vo@l<2+C8IXZs??_cg882N^`Frm}GtV^{^`Y>{y=n zz&4C3)<V?mySW4wHL>oaSZOQOxQ<wnQW8Nho*|1yTQ$n9&VZGN*7j9U8#UkqtvPX7 zE=)Z*@~j+9FH8ydTfJcPYXP-Y<bI8adYLVt42!K9L^Tt7n!QgaWcgTU{YBrs#dtLq z6A{wjsR?S&2Nc=8<fefrNRif6EO?rWfGJ`ldc;^rZ+|HCB>>cSQ8w*=YDkoiHSjSS za~h``;IbmRg|y^H<cGZ(w2iGRJLundv7i^W7bY}SqB{HQsXPw3bt24#!U&NT^^oq* z?LR?K_~Rt2T9G;W+tb0tN1{;#2+XbK)s|0nW?j86%zh}-?JyLsS9JD1I5|vHB^C9$ zzR4&MaV~$mY9Gn11@=H*_jPR;Q_dfF-NeRs{}$iI!Ib$3aTu3oB{Yi&?l}sPKIrw) zafz_5z1;sVH}F5rF1}KkddcUaN{QIi>jqbCo>Upfw5@JcBHrlBAqmP*I=gr&JMt!( za@9&&*Qi?+@vz@Q995)?Y&`Yd_2sN%o8t8yUE=#7uQQ2@#2W@*>CA^lFrxP9BDCJt zdUGTeY0#UB&+R6^Q1v)d<GZs^#UC=#Y`MOf{V*l!yV3swsV$0_qCJN#ADts{9@M)J z`dw@#xudbRsNR|Au7{~>4UK7opN7LxwYnvCTpS2hxPf}LYkwk;e=EEFAXx&IY24Ou z(?29&Q50>SmV}RgMhSsyX56uihtmlzdakz;-1d@e8Kzj39&hGSx-+{HF8FZF3X7g5 z4&y_<U}VFo*`gVNrTJHjDNtasyAAG3dgME&8G}n3f)D|)H~47}=(f*%u&o@6B&o=n zxk)faeMSm@1IL*@hHoP|)e{YdNcp<6=dz!E{K(Qt+Jc^O9%GeCGBc9Xr(hQ+#2scG z(H1~E{$lz`RYE2V!dY-3ZA+Wo^8;=<MtT><-nSZs3KNUEP)zYVf7DJdfYs=x(YAft zti6sV%K_HwuWTj#KWu$vR2*HDZFkdnu*QSCYjEul+}$05lLXg>29n?|!GgO>a8Gb| z4H6)DAh^TpZ)U!kH*dXlepLUe>RRj8z5ATA_dXcCm9)oN0>VL`VjSioi7T(pB2$of zd&GPBV~KiwhFIH=2r1$eCr0(I4OUDh4$Url4vjMGdYk$f%?F9QZe;GuyYARfF(A|m z8Bi?{?&DDQL-^zP$wLfM?B1(V=1L$P4rW}s@cM>6unx?3TT6A%gP?ZbgDf9=s2+mI zyp(?^nCDWiGqJFud*`*-Q33S>NaA#xRy>OFx(_kUZi11#J5FFHZdm!DU!NN~s~w*v zo*&fi^Cj=-yf}f$?v<L5>8n5MPk*k?C7;i)#MjTov9<Gst730|NItwl1>=XwMo5E} z&~7dhC2te4_ZWV2{Nak5;Q>ek2P96z)r6qqhVhCUEWrMN9ov9@pchzG%lK(O`)Nqx z=`?m7nRmvYFJ-61P5k<&+Z9{a85?I-Y^$Wm)t`2S-^!=YeBBbO)hf7|rq7c&k8$^^ z2b~Xy?Xdh2af4FWWx1Kh{jj2Z_F)^#TfRT}Cp)_m*TPM)+B0jXF<$Loe;ih?{s%hS zAdT&|IlDnoPM)#VpG8073%OB8u1Nw+xA1p->JV!5Mb>qnix6K}sz<-|e?}u5W#~me z`$^>aE5kwS`>!Ys^OwNy)AMBn#o0)G>6f+cLOnZeQjhu9dJgTrF(sm{b0$R3M(~)T zI2vl<9PPd{$tyo-3c#xG0k&=CfVoCEn0k#m%Oc19m5aTkLA@W#Ub}mv2Etzs_wUg9 zV}O%fg#Qv#X${9Gwje7V;{&nq#tXLgB><aSIu@SrxPZrUpRPFv3x8Qa?zll-OhZ|8 zNs*AmMtzP(Q`yn*5JFMFCB#YO&Dv>Bij^3jdqe+e<0U=z(J<Sr$Yq=e8iKxP`5bB@ zl1$o=Y1vVxG=41BqRnfh4-@j3&}`1j1NAn16Xsth(Ai`)|K@ub%nyv61IYEw#)6Wg zfjzPBe}a$rU4+vd@)^4iIG|U;*Cz)Hw#*v2VqzB4tp;R9%*mw`PCu_x$r>Ct(AYau zm(bz^_y3<p+252Hxa&EFcqWlKHT#8nT&t*6=kBHs?tbpekicRkUuJ#J>Y?N7BiKUq z!882`o`>`{+R0n9%b7RhMeP)uf=`PVQ6)@+_gcOJZ+zA8!!QZ&c(J+-J^IVe*c*P< z!nM*DwCq&nf0gO26uqm+{jegfaFarzm8b#?Mu#@GVw=hR^!V|}^<QP$5C4>DNjrrD z91vyNLNv4zz8;igkrY_nfgfi@aIe{~2R8cJx<B~1wyI~MqX3!skydwI^ee~#?*Nzn zo)?jyKcvSrWo+%^r$?m44V^`l4w5-Wh!2I{>kfO=`%a&?AV*(SH;d29_=f6$L<}f? zMRVI0+ijRkAkn(QJJP8b^ANrG4}VaUNTvo~g{M=CqAxD-d^?29LnW+EDLj4j4?&vy zhF_4Mc%l<=t>c}5G%=!wn5daj$%>H|Jfr8hM4k#EIZkzGW(7n&2ji;PnZ`#DrOsd} zdeDdDUE`2NJdmmd%z}>u>J<L{MerfbI)(_7QaZ(Xh|!VUR<KRz`Gr0)X|IqSKLx(% zo)xmt?^bMkh`F8xMYk5K)8ZK^7MlLj(hyH~;%Dcso8X45m4R_OmutBSd#~&;1KCV# zwRo3_opvr^V_?}{mk@O_24ode{33+=Yzw<v@qOF%D(3asMY1W9`7c3ghvTJam#h7m zz@VDyj2|<SGh1I@!zqEX7Zobz<UvJGH?hx)Ku_y(CWf+D*pU@BAMJ-hU$Y>u%-`f2 zd71s`aC%=mF<{t*Z$~v$R<jXeF0*^{$?OKXSe`*W9w7C6O7(1!eK%YDQvzfH8)@>7 zY*_hI-(pW5U96JBChi^p`~=>E{I!Es@t4SN9gwgHRRVcIANFBEHttb7r7EoIpxM3F zhS=FUQ{R0CTb7%q!22d_C>?hqKtnTo+UKnsGXr~2rTg&-0$X}{hPJAfy4xozCeKE? zbDA`D3w2%lb?sImG=l@^itjWxroCHjSYGanE%P1w9=BFqG8VFyYM`SPz}&?H{M^9= z|J*&$Ey4^WxxZG?B`9LZqsxBTv2gvXKKrqvK7Q6qd0^i!&+p<FPT(TUt00H*lDmVc zZRGNLf9ln#-@k&uOh53ApU>5^|5IS#ZuNoi=*Psd_)rS-azP0sQDC#p=_5*D#_(oX zIzw%xVWRC>3Tfkq5>*4AFj@klO?1pB#E{D}MbrXb{!|(Lsmj@smjHi=1Lo@x8$du+ zbj)J?IRE@t8MFJ@X=89i9iSpf(70Y|xQ63sa0m>1IVC;Wm4H9(^V4p7tz9yRz*R^% zZwIoKW~`>WgOIy_GZ##!#9GPQ+1JwIuy#SUV943$4xJJ&TH^lo-ch*l%Cd_wP^(F( z2wRcW!O~5YvytS|gp<^=Ww%b;$7R;B1kvQw&|+xM&Lih9M!5OBL)Iz(Q!`>Ul0tDd z$(X}^R`Jpko-Fba2VfbATP|kF%3m(*7G5jZ<@T&o9gi7Pv-}n9p>TM^eQOq^_I3|Q zrBfrcOZiQsV|g=xuSEhwk!-d3U0U0F@`39z41=7=Gwb_JiQk&m;w+{vW9Ac1svPuE zVFI>eO-db}rEe`l(=j&ES9GbWP4Bb+#XSFWeg6xM`7+r`UJIjI|9EUdJBiYN%J91Q zg%Gl8Es+Wr<A_$woMDZG(8%@)8Fj&PqIx~SEaym=G36NsW+1EQ@%o-<Sd_&#K`94h z0~l(_nXC*NOMt>x<hY18zx}7Xvy3sczoJ`v4>GhDTn8T<|0s5SzNI|>D#Aa=P;tMU z+!(o3Ii!M$a0U#~%k~|mzFYmz##mMmnpg$$^9$uMwI0L)xKT+2<j3u&etr+gDgz2E z7h_USCb{bH065XakO6YSrzC%q64eQziUKem0hdL~k}1Ha)V|xK&%nsvE-TYfpFUIo z%I8K8*x{&rU=aJsOZsu*JaSRghgu60TZJFD){z}KjIWXT%P<6B-6Ha%7N+%!feAg! zS?raXA-7+r&@@cd1Sn+q%NY`a#Opw!oq31>N{$9nTEM|X{zjm0CZH)djM+}xj=dJc z+KQ&sFsP3<;X2p`$W@_hSuEIr#f*`osnPHnFp1nyE`kLtu93p0U0cgT18@hYCnx&3 zI$`iW_$B&*i~td7>&+H!N7JkzM}~(JlY5rbDtM02;!15!lOwqQh_LF<N0-<|989St z08y`7#Ps;yokwxhT+2;+Lrr_h_cEN>0^wD#p}N^?0P89jYRaZ~hp7-QVKh$F8_nj{ z&}F>{?ZXAz3I8~JI&3%Ok82By4o)AM(sq9OE`C-ARUY5_f_DG5=P1m+s8Rg2Cc$Q; zpiD>w^n3TA8kzELb^Xx<e^5F?!!a(go96Iy;uW!-9IMK6kJV@=NN4Ckc>@Al_%`7? zawB27`=sQu|9BGE_4sq*F;mM0LZWc5Fr^S7(RFFm^}F8$DSv+8(BOQAB=Gi2;679q zWTjB6$f~`5p|b9d5<J!Nww6&n5^k{mFt&cQQ`CaF#}JpY!#SDYbOTmSm`(<0f>a)l zCY~z%<cAah=_6mbR^C5fy%&8;!}1>eMWM<9r+IY$W1rcB%sm2X*?VDu<R0&;pIeax z9!I(!B3H~7O5GH{$`7!hbX_@i?d)*Q(FVOM=)V*UJQuWu^8!2FCBB+1w8j*hK5t1r z<tywfo<E0Y-=Gsq;w-x~{mzvnkSwiW30S}Vy1ws3kDN9AxFAk2s0!y_#SALAc%14; zSuNOPhi1cFHGzp|shw<r&)0z`onO|kVe7k2L<<|8p{m~l|JxejvkbMRhB#xa?iG_g z-S5zhj3?DNohr?r_X(S0;@J<E33E?0AEN?T-?Aj7sE18k_0T>HMb#70tpKHGt1fT+ zaBzTd$b%(2a}jq`>3e``CGoG_5-BWO9%@E30%JuoepZ9q)8bKQP1nc%4^WJ?Mz7aN zi=|9el+y!RME9|!&ck_@qS1^m9QtKdSOF#Uv|;^#oh_&DI^`wfgWg(&-@W}oxpa&R zOD1?MU>D8Dp7p7Z;e0pZvOW%^^J4(|Arv~>7`Jr%w5Rz5%&QvylWzd&T<zq6IEk`$ z6fwSmJ0W6?k1-)|Bj7v%b;X_{F6se4YO!Kj%n47ZIn7T$z-_+9{L$udWD37V#c;KT zqeU@Lp1sg?BqUuYV>`e*U#*tB!rZHq)vDy~hRjq3y-C2@Y4D{c&e~5`hpKNzO$w3A zY$rxkHM2uUy)S?M_{(Vhg=_%V0+5MU|1zg^E%KyB;MNe1?B|=9eoEr!HBRga$tak8 zZEQ5|fg&nx-&G4z(a3M*TG<JIbo`Gz@c%ZP3u<}|*7h4ZFfylRVHU=04K-=f^tcnR zVxCHuY<aNeDa<-h$64KH6WjZMH~MBaf7*eVx5bx`du{((j2myk2cTNYhxXwL_d{9S ziX2EG<eYd`(`{VRHZ)2&syB-&sSy@jNm&rasHo%N`jiUG_i(w&QRw)5ZN0is?ks#L z!QydQs)D`$9JPbANwXQrSbg~4tdH(`#5_%m+yA5kZe-FzO_@oCaeZdV=%A`OPfd0Z zyIM0Jhp(RwNAzjX?A(&6w1`^sEq5Th>`VQ3ssga@6g~5vtlHa~HMp~>&El$+qErTi zBcGcaP`xoyxK~5wA)a+(PS6Q+)(nXXYQ=OE8tS59cXOUH0c1cL$Xr*0k&hK5+Fh0V z$#s@;1FJH*d<wS?M;e77B@SMgYu23^bAVNG6ZdJZ)940ftL4O5bC?5+Lkeas<o0Oo zuI?2t>WN3Hh>%KWlc6%|LMJliqp2w&*#T(D-(kLFWo6G3aL_8LQ2#m^M_qf2FATMv zH<!9*yacFYl&V<BXcW9EKRQ-t{@Zmu<93c&rU|$AY|Jg6p+_fG!q64e7-;Q7x@r;; ziCmiP7ksb3ELrQ&WaZ4h%nJJT;V~lviR(j``?@t#-P-JT%LD8e3^l<8u6ooggi2me zO<%QabfJ7lk7^c53=`CAU7idL{olV9oFwm-BT8Mz`H}1Ec5iK0)FIqQkU4kX5W!W{ zKIIJ>U{C<!OJ5K22dCc^n%yy=4wsH_zTLW~0SqJZPf1bu+?UP^2CsH)AM37#T~WHp zxt$_f)kfE4cg~<s;5@@}9`p_Rx8=ys&KM-kjZG`$Z|*VM&%6S!y*$Bj?-dhO;Pe#5 zJ^?FDu$8pB#BK!;5kh4QNZKj7bu_JGoNd5BYiw&fTwBlw&7#2z51(RAZm<O#;e47O z?+}TnBWk~hn3(%HaoAzt({A8TKM)L^uh5o7qru4eI?V~YP`1DuZG%CE<*`b5<V@w9 zNMCu~-XsGm1oBrnJv2h>-4u&T^Y1gR-Y@Iz5)LXsS)%jZy26HoQ2#gu7##;zF7Epk z|M~sJ)N`G9H4s?ru66;Nyp~?qf^{`?b>gf)uC61juMSVL0UtnDu*X9)LEzTfdDgWM zAun)M;F+u?5ao&DzrMd9L0%B8LRfse;^Sb?Lf83caE$d#<2bRI+9A>$6~1=S9kX=d zED~WDwn?`t9uozyH!Z$?z(TgsPyR<v^rKW57SbhHV3ErmsQIhXBnkc%%@ljjK`0hz zQAvHnkyIoS)u5SSIQIshAU1~D@kpTG9|b++Q$SFaw7ifkx&xGRHb-Lro97?g?|qhH z@KhQZ?cZyFZ#A<-Mk1)jMcO9(DNmh*28vk8NFReAx=(Ye)rXOVWRCIWPgB;*=fug3 z#%yNQev1K>Ws*qgw$tedGRm;Dix1+YWVMXGHPaFl&c>zY6X-zSmC9E}xPV;A4KD3L z!L2$#Z52CdWpz-NMkv2S?$~E5!IlopFSX>Ei_L-N`3$-*>uA^kCK|=FEOoNmIV|0! zvd(W8ii@OzAI)HKhEif^$0lYY!L%HX3+#m`M)M_Gsb&!o3r-Pz1JU>q3J0WUJet`K zUS5?hq{t(?+4UqNR~P0sTjf?6IJ-jr;Cbf(-SY49wow(IEQz3vZ$k_t*8hKIKAzEV z-VgD(h9#<(8Zm}|cOr*^V${rq@@U%^Wh?GSNtcgOaGIc8%GPAvo?ma|*r^BGHf8Bg zE~&36i(BDtd*Fki!G<@gq~U0IhXQ@}lwCOk)UMY(?(d%65|+_&bjUo)D2X;X4pKhL zlabTE<(RXqUYM6ppS9#)DSPLVm106=mQOWb+(389BD3QmwsgpEFB$W%COopc{ITzh z=IkFz4ZaL?e}3r=S5nBTkKyB4)I-wU<dBoS;DBKT<1M3xj%!)JJQ6je3T?dZgea$& zIlJ{RVL9m~aw{JXZh;g7iWIg{x+f6x4{3Bvr^f*xNnZZzRT)%q@n8;UhL{LXWnQkK zZLTbeJ2RR<svzuS*F5%)eU#rg--_30vL;oZtiR!dqNRQ)gYsKAJz@G~B=SIaW7td0 z?oEDZ4AFs>tLr4l4<k~T3$Prfqk2~5m)lFB$#<V0rSk%)F=cF#NiOR>sNY_|f)p3# z#zX*chARQ7fpQwvZX0p5bcM`&%mA_Zj0ERy&D%e3GOVhLr6uvW%^!ukP)qba0ZgIZ zAO4X4+?>IT61N-$Ql}wxn{#i^(9Bat;ZJtk9h=m=G>=yYR17So%AxHks3fJ@27^9C zp;rIaGQ}W~$=ysB6jRF6%^tEd_JTqgN)D=0r%!yizkNAU>itdilZP$4Yang#uwG4J ztK4Dzb@RGfGgpU1$P<^G#>2a{_WfD+f2w4n>Qk`~gRw$|EI~oX$SUaiXVLHdfBa{4 zEgq^0;;<(V@&#i<NcvX4%(S3y)BTNOKP(u$AnQ6M!_K6i42P>wEZ%`AV0&^B+gErw z@(5}hVIYVDy@p;ro*)A(5Ai;Hm3<r4aWSoQOaq7-XNw#8()I8bVHn4As1l*IP-WtF zzOLPna5fqHv=?jGv?7i~v!BTl;Rv!vm(O>cym17iSQagS1kkw~Q8@W)t7iQoR<H=# z!Shp@1-*sq$o;<8ZR@b2;KD8N#uRyhdw~n`reR3OyxT&3e*$ZAx7kZMH$)@ii^-9| z^?CJ4EWEQRBK!FFm*@MDabB0o>B5tuPA$;rep~TL{&7R+gxSq^V&jTJzQU7`D?)%; znb}>;UR&VF_2pCjz1P3wz_mRzv=<HZ3wkVcxH{y3n_nkF&k4H;v5B@yr}FnhoR;YJ zudDT_%N7Luo|J{mli2U3kuOnp9gsZB;ztgz(Q#BtlJ|^ZZ9Ebi?=h)q2#Q6R>yP=b z?Jx>FMaM``&xN46bo~i`vH|*<8PoOsbBu=)L1hIZgkT2<fRlG|&iZTOEku_W-~olM z;3_Y7I2U7hh>BVS{vPAbA4+~hkS^tri+0!*xBNLOgFI9O@U{pUD}n`H_L8YWy=#zF zEGNx;R&lsK(Hirw9);-lpt;*Nw#y~;ZW`_4U^{zZ3_~SR6lTIjNRAIWpggJWDCrJU zT~`xNG^0h!ZKJ{82HAuOH8Ole*>eqEe8vE%0@9O=uPz3ClzN;exh@HW93ru^0n~>+ zi<R<BAU7VzIr0W;<_Br~%9>YaQC06~9m$K8NAGPY(PRI^@2nbyq4Jr-JZ9u+ur33& zk^F_mC33UQfNzJ)4@=QHjgKaSaFlVv?zaOh1y~OJPYNZM4u5(*HPQ^<EmTgx_K+Mh zc!)z6roT-6e?+YBf1t^qG@+oC1SQI0#KRO|r;{qqU<dMR>QEwe=6ngonhh<L>l~G` zI*@AeER1&W3tu?M0e|`e_5JXeffRNLzcrF+ooUAThX^eksN{Y-ZE+MeytmY8$xXCp zv+a|P4=O;%2*8>yKpQGLfO{1<s7)o79okNoo5wy^3chy@DtV{&PGgrs-6*e<jXUkX zNv2stXks>U4{4cg*-8KCtZcyYj0{{a*<Np(@dFBu^2xPgFN=O^X4{Lx3?E%%Bt6z% zEX}UuJbpU*Vq%uOt$jXwfn1Pqrk$26@`-nj7OW5c;E>$#{UawYKm=1CjRRtp4sZZY zR>%`uk?H+zTQ$d`l*d;IhRh?3J7qS)P@v&CpQ{D1J{M$8>!-rEG!B6bY%h6)725A9 zJkRMfYP`F+!={Pi#T<Ji3_h4<y%mqoPe}%3MgU%M0G&VSU-PGG=>a&Ri%BQ7<7ksf zAZ$beD)@=ZNd)Ec%NE|d-1G_aiV8{sUa)RMk%Hx;nu&<h4aRufRF-5^mp4jR<0^&v zECA9`pdUNnWiKHQyPajliO4xR02d5+(YxoFk>#6d{bf-_eikSEEjp6F_e`|L`f4rF zMqEK-%<Vx<Tfl+RHe78~ZbEax(DHcz_F;lKFj;A~;zl`#1y}^SZU<>rc70?UiM|xn zu+oe<73Zv#vY$$2RXTdX|8(bjoZhSTP*|X#h28vq<Xqw$iA%8?hlLe(RiEtvhb%3k z^U+erQaa{L?({e|umBaoz6mN{D~1c`FLx2>Otn&x$P1APisEf3+@oLQ5B$?k@oI<4 zfz0gLfeqG`Ox*Dp>SOOa(6{p@B(7>>h0FPY=>g}0fin`9rE0=8DLV}6Bfc}W$2ZYD z^ZCw1bH0yfBU~$7&u3h1=T$m61jMEGu)CRT&lFgCR|Ois;8&$=%%2Q}dHevmT_S+; z`6R!iWKaViPLL<~3IbK+S<4JbYV!2-%v_Eqh@L+%K6IR_U&Cisf${*otyxOE74`?; z$HqYWpVd4jtgo5>W+ru9JkH;P8qs@;PtHOmA1rL!m@*__*>7{1$%d-~F3XSC_C;?R zs$pY@jYqz4t3bc#zR0f{hK&0Y{3Pz{8G!RI7A&R+;zv_#0i^K>m+6mfTNQjSBVxtH zPRLR0%x?Z<SNdgqNYr*a7xVYxcE+LDrps<?k`zIiCLQ}BILUr64~LMsaFtHGq~M*A z+%E+H^PJu~Nt{v6%xtNXRiR$-JNq2NHozj-+}`E8J}wIYR9b}xC{M0PD(9mz0ATR( z|29Sp(sGd=s}-_&S}AG#%w9C{)l&P*ga|xj`7-N|p$-15yX{DL8@+qspq7k2K&bDd zM+eBFuY^g5x|pChcf$ApFu57*pG{JV%{(|JjA0}eJ@?B46Aqd8L@!8^9W(Hls1@d_ zku>9cZzPbSbC;4#z%(M<2z%hLNvNqKjq#<&4gc6Rb<V<1m{_9uqYtnG{zSaToG;8n zHKFd4C(%>Hl~vs9v2Yc!>x81fFcK<oXxU>b<CALaoUo?x)nOW3>gohmcbv&n^9Xi& zvRVE7cCo$ObXrhG*qG-v4W?hGB{Xx;%T3$!l{Z~T%pfVMCrF>QwTEY&IzVgjH#3G@ ziLH)CnnTCrw{aztNb~O(B@@`HJDz;{iv%*}wvquacbT4CTSRvP|E=-<&$V5cF30c< zIK^`s#h-q9e{@dz+5}591@|RlxQ5W=Ov17g#$uU5mu#1$T_pKyiNa$zpBjfo!a8cz z#s(UpZF+YH*-7ZEI7pR)t@#VLB+F2hiHdech2Yl(VGH$v3S(|WDfp$9E&0s72348D zi5#Ge5njpIp!Vr7j2{O(V6lB+OY*?r^6*o?gkrNw|GO!iHe_$d4JRBoxhS7mv?sTJ z7uk8#KL#(h-yU21!C5w7fwMsmsbXsl%q#2!_w>CM|HLB=bozA3cN8$XFaGA7cYH-- zXW~%@Ki}nw%$fg!H5nKLb$XlS$(umsbgiaD#{YOOT51>rSQ2#7!I~MG^X>GxoV`-J z2spTe30Gmd73eMij|<$N`3Z;gjP>ch<T$j{Td@BgVe?CJNRE$eI|NIK6a@}BGWLW? zvl(}PlkKyQs+7T>cc(y+qQ5Nbdoii=sqY-Y_Jp{<e8FQyIQV_1rVS}8Ltn<p3O2Rd zT!^dAw@2|RlQsuPQu0xQGR1kB3f@WWEjOw`<mB*TKb?0G@<#sxZ$TF+XYTVY1q+dH zln}hn&vt#+b9bC)0yLNPA}3UT^->@L3NQZo0nD4^N(jZN?a@n)1pVmYpVSgPS}1OD z6{^3uNl~dsiUhbcTwhmy{vDQ7JT3sU0GopVzq{3Ye~_yy0$7ewW9Qt450j^pR`l?v zYK>PMW`u0>-*vX1GhRy#?1^gP$~7gHw;mszeKSM9={MP+LcWV$pM7D-MuQIc*3`7C z{S{1-ke=AB1>$!OhJtb9c@v<oC7-q=k1PIgnspv=%2H=lBUmrkqu+cyKx7@)9VZQD zk8@`HK+OUKrVLLi|JDW7)6`Emh^^R_xaq+WNzUePmY+yp36;2@xgV61*fhngpw0OL zH3QKr6xzEPQMKK!{V+ZJ>1#*%QZL0P`-jNM^ZhInqMdxXPF`}DPxV-W^bVb#0XGpK zs98V#1Nco9Rj+~|RPlS@wSV<x8l{Nr{Q-G~gRzJ*fD-tQ&lc7xyyiQuIfckD2-?7F zw}>A1Yt+W&cELl~<M|_=`Vq1aeu)B6%=)Ft`tMG;yJP=jw`@0@RXW(jKZG42ey+Z& zL;bV;wo3#9XQG1(zRo0htKc;OHpZtuKF@w`*TTmKr~*V+sepekI<EiIln`<7*F%86 zP0L61UqRUA6$^(^3vslQwjb!HE(?y~T4NaSFF7NAxK!q>YSfye7W{2pe_~w}DzX#L zttdh(GXUB;t|wXScS5-YCDx@(ujzt{!0rj4IgJQjdm(FJyOpR1(c%z}!w}~cCVm@e z(S|(^Z%bf-2v+Q%RrZ7N*ton)6r{uOE8#q|+ZUxq4Qu1*Sv_%?LGS$}IE*^{g4Q9@ zVc|3ME0{jt!DZiHx>7y(cvuNwGirjH7vV}mJ15uB^ja*(2fB}2gN9s4s;Kn9`Dew) zRK}!1^(e;N@D)l~hk@CaA}r#T<MS*-O{YdaB+@T@SsS~B9w1Gm^c%yo5ts=|#tyQI zT4dLSwO3idc-}^#ZzmXt<x4|VzBo#spyv=?6M9r}K#h=Vho=uczrD({G0cl0j8FVM zzWF|UQ<qnTV`ouiGU=V|Yb`otKK?^>R1w}GR=qD2zs}&kdnFUbx~xSSiRI7FOMWwq zLsjJm&p&x~M~Bl59U2&|$jz4)v3;b??|D$Zr=p=^)1sL|ZwS}-p4<PTax)@EOV#ka zCQ;sajUr|#xv{i(pzF1e<hC{{`WSd<y>@(bpjmFD`DJ@vxUyoj{fF@Gv7b`I^^#+? zNACMGRi}P2;;nLRuji!28w9`kH+z|XHQt1?^R@Cd?PxEc;=2$M?tpO(jBMVFM@=PO zD7)u#vgP~lmC4To9ov!%naKZdm=Y7=?iIi08PGg@k|wvk`|diXAd5G?@oL13ll_BY zA4exd!nUG5^cI|*9gK;wt+QBV!|?1bcAK*D(u!H^MRBi8?<eHL-UC5;yGZcQx^=#* zGCbC$txBp1#!?TvZ2b~mdMKo7_oK6iB_`obWO$ie?q`6`vh0n;#FWp$eIZ}1n#U&R zsr70n6_cDzYNmR@8y+#?ln9yI!izuSE<_*xBfQ^oY#;^&SBRUu9|u*JA%9H&GQ-5B z%DI8T-Nyu#aQ(CW)1X&uJiQamxq9pJ9o}_j^~@aWuIuHWr-vl#^-0AmjtcASOwFK< z*%C(qaU3{gD2xZ1&qKKAI>eSH0W1P%PV1o5Ajt?Vks{`(>xOvhOuwWTl*dlumJ0&V z0--wI)mD@T>MF4B-#b_v<KHs_q*zAO<8TLo>iXrii^n|@iGZV*^<0cBRB;7=0g4}m zPVKZ)32q}3dZ8Og<lhx0<mu7436OdhhYoB;pBMl|3<#5wSLU^yEz;kkSdTvqkUzwP zTZ~7<h5c1X(D$>#G1*G0J1m#?2E&J;I%xtyQq27<`?uj9Wk1@E=*<Vbt$7y>G@<mv zIla-0L176zKi`w&i*@r+LdkT})~5+V-P2Y$bv99vUiloePJqrnwtZ3dy9Swa9DVm^ z*N>1UuzQ6W#i}4(3Dt~3-HUvIv0fXrSOE5z7A`M0z-emDPM;_{eJ5G9?tD?V*5{+! zndyf$Mv^1ABZ!Yl0&{cTU3T38oI@2{_cG-IdPXXs3JJS@XG2UJaOv*SuKTvG`VEF! zX`LKO4_Xw4Aa5`Pk3?~!41=2XikauH*!@wxz-wJBR_?fdJad_DmZ4*GK8ATJ7?F^S zdBs0%=X&a7%)!a<1_kObW&8JqN?+K2B;HL+j44@RhFSN>`YIn!V(id-7&TDoPIp|W zB2}ofr+mK6ezwQP#yPrtc#;cTACw>lJfkb;pR#o)NW2#qdjriN_>9JbYu{y>ZhHwk z_l8x$^+lQv0?$cIvH><`x5Iiz0jr6E?Z2>^xJ{fXPbAjg0mZp|O`rebJWqmttvy{e z8EvG|lQldhW<M0bP>_}Vc173iU;Bf=2WhzSRSAFbjnehY|KaoK-z(K=PDE_qyPsrv zZ=#Q&TxE3KX6W9XUYxt!kNzaG1^!0_>5YIg)>17~KxW3BO$D^y;MFpFo<D&lv%#nK z7-$ZF4SR=jZ;uB|{yy<n{^PG{eT4nr8-EfoX9N}$ajO`hu(qsSW*%5|%k*LsDI?1d z#xnMDX#uog<rRN^1QVi)jyjLJ4EUSYZK!G>xh@|5MWKd3U{MXd6HZ8z_LXTRMI+%Y zyu$r*!4^ERV>081wenPu3}&Jd;NSfw&Qv<uPcp7A(89xlZn}TSTmx0ZsdW0NXzVK0 z0ki<g?*4dLNdcpqIo4MOxcFGIR<m8Odg>ib<DStm%05zySKRn_BNkzrb+q)NxEi<# z{YtOGy^F8iC6MBh@FLNL`kGayq%~4slha+Tg48vXfeN8FcCV>sgrNeKk^b?3l00>A zPRhc%ZD$)nz{es{^ic_`8+auOUh<)-<x4;#k(Df2x8^6R!(z+%R-flyfd;2JbHwbW zz3*z(FCw3HUW6fs+hZ*H5ypC7kR4yDd|L@6jFcjcS2y|e3FAYm{3uaxYywl1A>fan zTkq^{k#He~|5D@J27iJSjKtT7gK9F#_RYz~`M+KOHg4<6fRM2rdOh=*;bzmmNjBU^ z*4sQGS?5dL+popzdIOmg6B&)7*{465$3g!)aPpA1bv_S%Y|7`9p}o1OSm`tmxm92R z3HO~P<43H1;okhr8c}V$bvbm$nkLbZ)U4`5`W3B~Q*-Mo-*a_-+v{E{;%7W57ZSyw zmq?;<b}>$w<&s@`Xau!u<4OQO9+s+>vYneluZX+7DjG&CP>r7@Oz*W!qFzV<S;WMi zC_Q5>b=l=@xvA+x@jQiNjoKf9>o~uGm}qe$=qqVe696lZTPexjZ~4it8}^@g|NeO{ zo1i(fWMa|bKVFD!*n7+fdTf4~aNb-t*sqqv7eX%FI8>U*o+UR|Z~b^61oK_+{RUM= z2$JzlONp|V`U9^UN}rLtX%wy$%GBKO`OAti;XQ)`7Dt-=HTdt+hO`OS2j#o32tY!^ z{z0(?u~>{@R&Vr6T+3>08K#xkXT7@7|BN!3XX=-rhHcZB5jUjUWJpVin5Of*P?u>* znAKuJp8JW6`RD))C&?~&6Twl0$Su;OyEDb;g)I+FaK?iFAiG_;q}MXP&|ym{%$eei z_8B|!mj4_lMxy>Tjx%ljVRBpOyMgvA{J@df^UtNFFJHd#qbGKc+$OQ5zi{hT(@KDR ziD#LGd6$^H0macH=;R6WKc<+hESHLgfwNTrpeEE3h#stu{(>I@Hl-5&M)}i-sJ&s? ze?+Z}If7$?{&aS<cu=pWbM7-28_MF_!fP~#$zvpcS->0hZy;rtuOKB0$@`5(Y!4<6 zWQTS)@hchHPsK44ie8o1Zg%!`5COO7E1H?M)qYiET?Aoy&Db%0vv`F_s6oOADN~6J z9ROo?Z<)G&CjP})T{6%?z`Qr{bTlz)6)SE7Cc@+1F6ZV{&{ajugH8z}R8V=H>%@T@ z*Ud}_m%sm}GOA+vb{QNcPpi_+jNE;oRBiSt*>>n8E494$@5S52q^C2k=V`^f$Grhb zf%iy@n$e}5bL%_$%bt2FU89GL0xUpd)mIXTjhp(5^=&UBy@5bbfy5Ikl^~*&+hws7 z4$ds?C(chIX?1@DNjsaCL)BltF((TeamQNb+Lr)!lu48c@0)B;1Wj2>4&CTLza5YS z&2>IMo~-zYpZ4PK>=$P@=4962m5sZcG#Ns#UpuKMEX=&BzNfk0rws2<%K4TJ+9+Qf zagTz-?<h*rkAqSdFW5`C#Aoirr|R=0F8t_llkbS2>afGC)*beZ|A@blR={@hwi>Zf zpaM_~@}X>f@pFS`B=Vy`<RlUXABrdNjvXD`k^xfr3fROePfCX?tH4ziK4(&ns)K|| zN%GlHSS^ESa(rvGe*K}Y#5&jU>E$tM-r-Lzp+6+vy7jb}`3fxi-Uawb*gam*n1Do| z1r#HsyI(*K)2Ub19=y|dV!!P&oRqzKkJyCyg7?BxRfdp(ZKb|dC#lZ<=eG$U+z1TU z2Pd4UmWfZ>XD;&UA8U3{ZUhaq1<^TghK>f04pQ6SqKu@qNMxpc?DHj*@9mW*4(p)& zoNT|CLlWYmXaWupvj{E4L&dVlYfZ-*iy`r60=>qg`&E9QpijI)@RWQUVi-OqrC-u2 zzlsGU9<zvk8I8gSH(=d140-9)NCDLXp8Xo!_`Lv`87nmmHE1Rh9KGD=9(_rW6%}_u zHV!o8Ax1_K{P|{+BY_BAhI`~Qz1UIBm0UcaKorc!pN3bAi@YWH(dfZ>-BuZ{tB#zp zDN?(FSI^L1t2KXX(kT8>Qa`jbs+niwsVt&1=*aJtp@Mxcd>#Zf<mitfqgPG=X;cEW z2Zz~gwITWYpYRs4_gDJgEXw}AdZK_RU11zg#1fV`dITO%pjGMp*ID|XCo0f3Xdw}K znWxiFvmUa~W_BJ`j_R#V3yZdL@+umf8Uw1$oz@RYulj-xh%{uMP}GxM#Uo%&Gp=H% z(Ix{X*Y!Sm^KIg13Pr%JB$J6msw($E)`i0F7e+5viwNGL2{HJ%MY|rL-J7hWkSC5? zAQvq@r+SyF9%(U|%`wK(awog$Efe9nh5af+yn=1ONR2Hl{wUW;gD!S1M8|s>PWpvJ zO@<3ezeJmyo`}`R-+zUD;$X_z?O&YBz_C#$U0uL$VurxEuQAb%wQlu6D-#Cb1tmC3 z?r%0ZwGtoU041V~;>nzG7sh%wL{XgPE6=<K^`l`N(V0YW<q*gNu-6++QNOU4%F-~q z6s8EWX7VRL7DLfSnv|P{;b9jto79_QGM2=#o{y3z=-YuJN)GcBC`kZHl#%yXtayz` z`!-tW2-Z7MbFBstf#+CaTfo|^_w*F>n04m-{!#7HSi2NxDC`=RJwPXEhSu#n&=F$_ zyTbixP!y(BAzX9vKA@R04J~Ir4CBPr6AJkp@#jk7$Y5|EIRBS;aCrz|XjfdFd8$eN zw+^|S5T-c%mv<;6$sGE^2*x1YE5y(u@tKuL{+UKHnbt8mp4lF>9j2WYvvsVXs_di% zKzawJD9%>9lUF!U4#{PaKXU)>JYx)?YzdH>eqConukhg~RyYx6iY7SkmRE1de2(v0 zM=F#Z!|I&2hq@+x?KUV{SbB*it5P%JGxq}tk9+t#y_GqNfxT4}lRJrDYvcmTPi|Fj zc6EN#9*U*!7`%IZS-zdRbIvM=93d*tso1FOqz)4Qhl&0Q(dB=(dOE0n8h$|hs`uFp zXR0vHhSG!5_X>Bl%qfSi!c9s+sOVsZeMw-F0MhDicHkPX{{BsD;KP=zlNczO4^0Z0 zcZ&St(Qo}e>uve;ZE;%e?7DsT3}%|zz`IWO%yB}v(hI!|w;2g%d^^Pj_I>`PjUQA` zH9iY%Yj1mAfx4W6^H~kv0DIg!K#B^}>I3rF$9I>9MD&aq=x7WX=uY{Ev--<iH7Q3$ z5`0{jbmqx$kz{dxLH<9)TS-;FN{h4V0D=@-%c}-Z&ly;<bir|l{?x@!9TjN9L7|@x z|LVj+=OJa_qoY~VwHovXOn*w^x@BT1;GZh=lCA89D23PMfsUW#`c1!}u1Sm6e?!|P zkfC>fWNkezv)uFX>^Di|$?8uw9K0v$Eo}?S#eX;QkhLhrZ|}SG6{xl6+Fb2-0jV(- zf)v1%p>R|~K#_=LvgLZ*pM-7vJ%yZ7k$^6e(uzzf^D-1bxqy>$<WZ^wK+_MaA7^wb zDwW1AEp&zA!z+MG^NW@mS1&dcHrUCg7iLbO<lALIS;i0YP9Jjlksfr?1<c8ebZNQ_ zWB865`TY-9QC#6Ohm5~K5V%&ZYSC}@P1Ifw{=2_Io}{vjwyy&?Q%cb_Hvp9};iBJe zsAm1-C+93Y`<T9utsXdKAUicuCa--eI8EiF2@Ox?rpEh#+;*&jt>VW^zZS&t<?ICd ziIa66x?}95)v74PgAciOH~&`o>(!Wo^$VPsF@ahmESA%pHaT|L*Qh)OOAIlaBtp5$ zTnx9MNvt1u6UH>IBy=-!dGN*R?N+DMUNlyH12n!lY0ITMKwZJ;4{@^YGY^m>q0T06 zvzi#u@%vYdkCvgX@b|t@v0NcV9@GKv=Gz|REdB`jRFvx=tFS=r-{PA-=rtXinwXiO zW=2vO1*e-{Yh+~|kgZHRYKtXBgoZaKhQzKIlW7pF#Gi|-2Ft4&YB>U{truUXBE7DO zx;H)&sMb;_JO3I%HfGTMzhZ;<Cn!Q}o2*=3#^p;vx8BrXC9%y?9&<rW5Vi)^nn+7F zsp}qrcX_k<k5{@h{@sqG9)Is$hUaEkT+q<-=5lSZq+V?Fn3g*vO7YArFU1HC5O2+v zGL|Hf6l*?M@e*+@^KG-*82_FU+hh(ZaOhRUvdkr_)e8~R4Hb*&bj)5XNqS!oWgXw$ zqR5!%VW8+Fbi#MLQtnz4M2ZMRA$Wcf=c-n-1WLvs1mX?Z$zJVZTzL#st*ZXJV!v1f zU7F>8nF<k|L%i1p@ifRXHJ#lRa~#jRtQf<h%$_WPzk3P^qgUd#+0dl3ONDL}SQeEw z5m4XDg`)n3A-I6lAF76>pV4@bMOydQ43U2b|E^I{P^8^xBX{zNX)*f|FZ~^uGX^Fq z11p5TToD(ZER)#yO(&qS5IOP6r+YYN<01v1T#$|BRWUbEWliG>H-MDL!^&bK@1ko{ zl8kl%?d;G`JWAb!44TB&kP7$D6XYU7W6Qcp=Q_|JlNm!)do(zKr3(ZuuX3JaV5rYg zhxAu-SpN8f6!IO-+&YJ<ef=5*BROU>rZ!pRa{_G=AIjsT9D!RF1fBgTq$2kK)E%V4 zzIgopp2l2CHm;U{BwAXIf;+uzb|mCYl<fLoY#hxmu_{OYJ}p>{2{4vuv*lf^pN=Na zFPV1lrfwF91}HyS(Ry!<rR0|<9#3UcWj(RZ1O_XgZ5{pH-tsUAqX^txy6AQB6a9#e zw4MvLue5_Ic@vae<+qb&%dReckIapiKWoXc!~{r&s<oaF+HLcFP=vDr0|TDUvoo4H z{y3-b*lRth?Ut>-oJj94>1O5Hl_*sM+FWSjLls6<efcfSR-8Z_FofK&epD}gT~6W* z@BHbyYdtuttOcQ(gAvapRT*q>Dzt<b?oG*2HFPl{hRHD(!9WY&ZoW<lx2|EMt1W4d zN?R5{)Kv!1l?%VzCDH$4RML2>7oG3Yhrzg^%U?Zq_#u0u5$?qNH|@AK3$<2ZXxw-H z+xyl4&WjfjDo!&`I<X+r$G>ut-|qh@0Njh_bk4?hzXJ`5pAFKb)T)eXgZgzQl;=YI z{>iNV3GW7W_;cuc7E(vyYys@0zpwR_`nbddIR>?2fGb+ol=hM~2Q{?&ndBSlJ!}l0 z)x8p~vwC<Lib@I0bPmrzaxyp9mg$g^vW-*`$O=~DR+=HT4y*c$Y!4@nq-`J2piNlC z^@|<W)b}jDVW;7Kmqau&AE~-rbCE|ivn^{Jy3H9TllnEyEc4O<)0#BsL1<P)HKPaT z!vl>8bN*j!*$uuENi;@N|1}UZ!8sAeyrv|^dzEuXK$dLH>As4enndIy?UZC7u!<H~ zw+d+IBS{N;Z15!$1!BD1y2nRliNW1q!`(xXo=Q!15RwcM_i9Zzpv(Q`D3$<by%@9s zsk(eM4oxkw1gPV#g9@Z{aqXcOSIUe10}kK$ka35YcP!g8rJ}<8C}_i*Wi*D`8=<*= zC2^m9T)Rj*!rTd4X{*9Jzl@49ac<Mpt{ibvh%&k$9VV&jvTrw$G4Amc?UaaM6=@{2 z*V@dbWga)wP&w@6G>tncsw)GxStv~#=4Og9PQw;?Hzh_=B+_!Fpe)B6iPmSMseUK% zV3rI$<>h;-SFtQ-xrvt*<=7|pZ4vK>m$$V4N_wsjxFQK*{_haGAyTi<wS8PKw)-f* zGm6RS%<T0lOsmuCwM)CD=%2E5|E-*yZurkgZRvM6H}eL&{+7|it~oj=CtBQ^+}WCr zpBuIEh*2i(ZTV|e6dYD4A3y_{9$Afubb6O7tW<Ve=pbr|_Bc($dInFFV}s@Qf`c-& zw$L=fs;uVsr@P9=`)%Fj!HI>z(y&Ke9)#7S?C(jUvBf;dr!dd8^f7T$D1Y4ltUL|O zn!2s@dU$>JA;OPT-?gEalOJ``6`cAO6QDzN8!}*_H2lG7phhsfM}b}2^Hhha{kuw) zY4Ev^b6ri?V`x4pAeSa`70!5IL@z1jR*Qv3J2ZAVHTvSa$dM-8U5ID2IAxmuM`;pK zV*xu<s75_-RXG+%1(RDRN}0;Qvt5@cb{8pwe({~BJa1I#F^Z71Cv`}vLX?&S1m{;$ zpK}6{6N5`)Drw9Ke{A2A&kt#OYtYUs&>&x7D=qcQoW;}tpzmd?wpmkrI5i0gQIYD| z_OFL9Ve*V!{Uk|y6Yj`)nXPPU;V|-0-IF(CSOa?YK?KEqEc@RW=cb1BH^3uIS?14F z82YkOo3xQnD+_4g<r;koiuj70JNT`Gsz(AuWa7{x3J14$hCh2h*A1W42oji|{Rnum z@OGw?Aj1ahGYf<cd7AWdtW9X-QTIlEog0I>JVYP&<#^kGII|Br`)><B%*<e5g|<7r z0L!-6@DTh7d`!CqXfFRq7^&&xXCnA;|7v%DrfSs^2owb~L%fSYK4^fmHbf~kMd9F| zg5z6DNSw50z<b!C5r^Y++xp|X^RFlV1B!>}VHXGm;g74+>gT&^-2>H}92N74tysx- zLGsy87sX@65|=EP<dE`2{d6!l9#SIxxO-<eljQ+kiCsO8ivM}rPTOr6r?NQ-&<YH{ zy=v-menQaamxt>SY*SK+F{6!=vTz<HWj)YbNgyz%H;O?ZcbL(a`0u`69B~pjL*g)f zZbGp06zlvS*|?*`gHhMpAeHsI`KF_mR%{;K%NE%&1$gmw`?dIvquF!%+s1_G-ZKW= zIa|ql1I*654OB_H-PR#S1m8*oKO_DwX*3t0o6%TO)z^{j3|umsXn{JKJ!}$J_OAWY zyTkOJ?s$6Gm3+@|y>^z;l<L)a{<UdIFQ<IMf9KWC+3yh|?cyzP9KN~YkrRU8A855y z5r80J81#Spr?P9~<#jDE)K_o2+bHs1-|Wvo%t5*2b?U{@;lRoDDaCoi72W5h4#B4< zy>m#(+xYRZ2^9HOh?4P<aUSy)&Ao_f%xLnd+s?APb=B4`ws|rnZYWg8p&T2E!JXKM z6>mR|&THsm;h9m!&N)W2(x4XLgCiV9VVba{RdMn2FUe<pf0}w*(g?~w0;7HJP^pu7 zBq^Rq7|Tk9vryZ&%8{BivsLa=FxyAPH>=U?Q8>Ou>KugKBF!@fpvOhj_u=+ZsxQ)t z#`u%xiy5!;lg<s!Kn%_kXi*WBH&%8r|I2=A9tQJVbP>;H=NE8-`>9MtJ7l&5w5ZX1 z;=4f~ud%r;e0*h#YX$+I<}TZQKy3dAm6aAiUqA>bCTr#ffjr+o#KwJBQnuaEV2PQ1 z1?Q`-k_wL#vb2U#I8<2F2N#wq^k70fnMSM<$&Ce`3Z3!)h}%3c67Gy@RPId+64C$> z%}vs05uO<<zoK0R8(fvq-z4+J!kXJGa9B}RVpQrM^`%Z^vdZXqCqdm5uC@RtO;h1> zVk6ye>w>5F#43}-tP%PL0IEAyz~j=vk_KbnI*|REo@Ykj>-jn+f0md>?YpatNA{NZ zLhBK%>wbYd-F&XtGOv+z6nXa5Tfs$>=Wy@4GT1vk)UWFk$l)b*=Ss>4EGP_|?LIua zg6M(?h^_Abj&7v4LD+z7Y-AiJ0_T#AhZM#plG(V+OhU0!lXw%Upa;IM>68inb0Vb# zP=^tGBcZM+Uv9b5n|ueMUmljBVa6cgd?T_af!j}E9-E_%OHz<FGnW@GvE5n%tKIg3 z;{|PnB*kG|Xuovt-~YLg=~}Ak_H0Mdh?mpiQ~`r6^B{-X%DJ{7V$&GXg?XeAii737 z%M<BLZB7+A?<$g=iUmVs1}l%cdE29v?}I@p_9!7W0PPVPWD5LEx#4>j<%zH<UY>w` zOZViv_p7^OYd@L(8`4?=Q8Rs6blrA^FvaYM^hNDKvJ|<$+Tmk%sHEVak}Lf%Vpr2? zY^Fg$dVn`;pib2i+x(t#@;s^*(e;n`ecD7^Q2Lp9isl<>q=V^C2H!XMH~~zxs{^m2 zK2YnV#nDE_Z~2dCUAR#HSi+qy>t%Ns+>ED3bI^WtI3?K^{^>}*yy%|aV&Ki<N<=RO zU{4mRb!~@{zsEt3?l!9H%ua7f!1uv~>Nt2h!n4d>hyS`WoyXfV)g}|MTcm<=`!@MX zKg;uyjv`%UnTrT;^zQ*?%aWZJ)k>07<B#P_qUMzmeu<c@^yc|_BAw8)@3x%9#Erz< zMaEX;)*MOAi;VmDJ=F9*QU2jcsf&6J@YKBt{1Gg4tVuv-?bNn1TTZ;HXM{6$)~ov6 z0P|pX^YnW->H(lg6-~bXBj2k8bfK#R!HeFzAEI|;+6nc0*!mF}R&1r>O8JosY7JgP z{-f3({iz|rgJ@E7ZyJOj4Vcz^Pq;XEmzqB=Y{}IgYQK=P%q#;bFG;bBB+THyNrKCP zLjBJ(*7-mm(exSYyG{bG+jNIds1V9fj=%EV1VL~xE98Sc{tbcyVSA5{KT|&;;n?~2 z6IW{X!#0y{r)qbOVwtL|XToa)*gaBoNG~Qy0oN@bY270PqGgYufxml3jMTse@Vncy zFI`TrmH)$JB*+{z6o#z06~?cmaz3wgK5q>E2<hH&Z~O4;dvqr<JwE)vD`3})eNSnZ z9Pl)4j0~E&_gg!sWJA|83EknOo<l(N*N#ZQdN9#UX0HH4M#OJLc*d9?jro_O{M+HD zy23lJKgS%@ei+*Yws$SNS@rN%IzXOpQEx)guJQdp{k#<kzl<n^BKo2NqrGTXapU!4 z=U!^@`t7&dA+<oud;A+!Wk=r__iQdx@MxLsH2=Q~c;O@Rx0@;%^OX-Kr{lLJI$l1v z<)3VvXFy`F;U?^joK>rAdic1UuorQuBfuu>US#5Q96GlqwJ5Fh5i2VAq%wUy5#L<@ zZuJik14}3RksL^+k3jvxK^ubPo(^<K2NXeGX=8?WQc{<TD9dAHgWF$@>KOA{xU@N6 z8L61^?DqHK`PKAekDgl*=ISqmrYi1&(m@q{*&G1=i?P&ShLFjSQxCo_*#{7sk_#>} zO`*S6RXA6xF^L^rQV)=>aG;kA-;!`g*%+dJORnCIcr>7x7XAaoXu*J?w)w#x_l;nO z&FYbd5#!sKH*m8E$>C<1)83RN3}8K-N)`kYxi`2Bm6%&mAbWEG=k$bZM4*pMKRbZn z+D;X@Pmlq?O{fR1d5t`H$RE!Wm3W9?H%Gqgl4cJw&WxVfi((Y^4vY*YB#jpud=ZD{ z#8@=MFAePDs1$eLY#ylDBr?9pGZ9n>B{`5K>h(k-!MMGo(=RjcNWei%KbYtM;AS}z zY?hI`MTd&fY4uNj;?7#%ZpJizqi3f3*q)TF=>F3wBU|rFl3TO@n3Y*9sNrVLPQqL` z<8{m1s_K5_pQ-;glKu}_?;X{|yM+rUp%Z#XP>Km1DbfXuAia~&TL@JI=}I>s9i#-% z&_U@SRf>WVAXI5emtLeJU8MTq`JHpmx%XSM)@1&hHES~OyZ5u7@}FPoSRy>tOu{|1 zf6^IGNpfbeR)<Jqs@7xeG<gTH#X){j+I3B@r(XS+;`RR7@nHn~ai&heBks`3>KjXq zKA+E);LgkNf={V;76L#4BL~*%t`KP2$RXWfTEQ9l5_N=0`M&Yx?4jP)RTVd=Fx=6S z<8?<9-d<bDR}f@KCS<gxmC{t!)(kKj`-MiT=Lrh#s6Ef9U%K5Xd1g}Vr&{%NhR1j| zqRmg&Rc<-h#Z6W%6gt33$T?R}@x`*P(m+N1qaR7Z$F=57-G7?4Y9<dH{|V>c;(rDO zlufk5VqWQ&==w3Ar`lgUl>Ppo*-Sma0CQ)Ld<V5B?9>`BA`^+BW^d2&F}#(s*X2)m z^uA2HYx-?~d_PZ-ni6W|cz};+JAB3pFZBpn%*9Cj^q6gmgnb_?a7eMynw+K2bfwI6 zpdi3T;T${+0~C;t;Qe=S;*yWK_lrB9W0({f@j&QzYf@Nj*%}?M^dq<Q)A&sn&29@B z+@dZsnw~<xF7#SV^`1LFe{(xa@q!q~j}bC<p^40`RhE>2#X6ST{@PN=G7}htDmM+w z8(%}e548u|e}T@K(78c$)J~FKf~&)~lYRV@zWzqXK7$b2dJBAb#ZnzbWa*1+0#evP zbUfeoT8w8k-EuX)qso&>HE{7E7rXu2e`H-!`<+fF$w@k*U0?xw5IinE7hW(R#kd6F zlsEk#UqI-#q)Zzrt_V^CQV9ZM(k!bMuktC?Kn^!=KBAK1{3ynHvUp3F6CH(<1Ko;Q zd_*tw=DNmH<+xl|6K>LU)dBHpWN7Ez1gifFne^_gz3mZ$egL{6O@*8)0$&SwVCFh2 zkGh{<biXp#X#I2E`p!OQvIxJ#Ioy{yf8iLIFj)a%F7ZSxa?^|l&YfG|Qv*ho`Mdm~ z*ORyXm(Hv9q0xcw_p6l@z3-<x+uNpj@vFi~@skA-MU_@|o2HUhL%WxAyXnizc7fM| ze*QpGk3vNO(Rjt@zZ(@@*KWV!*ZA=0^XofSwG+QXse=|GTk_9FOi^(dN}qnc=V$OQ z=8(WkxowNxAIC={dKUT#UF8{rBm1C7pEh*dYjLG6RAXX4FFS?v)|7GlQ-^=aa8G7L z6#fKU=a>bE45j}(EDAn$)p9W$%19{9(Ybl}tMOCzpGN<N`x^Y@(Iso96-JpueQ`sB zHy8gs3-kCRocG7B>d)tcu5VSB&B5N5zm`%KRHjuNZ<e)gfwVTP{&*<vtR)9o2c63j z5m?lNg;GyswBn}Jgiu3C{T8(}lwiTnze&|BuA`2~6vgyoJY9fTO$tODg(%}RQ3jo# z0HD84j<=wiFtQBw<m{M#tZf=y6fGZp-v!T8(@IkQq<w}5Z@_Bzg9!p{3<7aJ#(lsL zGN_k#DAs?ynC#~xedPVp*e47{*IZlmW$DIkRK2w~hbr%?PJilkQk|b?m>yO07W=|i zPwU%H$+VwE`h*Yi=H$1$kSUA!qs0D@?v1r}@|U+BFCMDInU=w8`KSYT*=bB+4<44a zrw~sl{n?-)w#F7RX)OT;kD;HUZrMC2tP5&HwOuakHk>iOFnP<K{(U=x6|~UDcCQ*~ zFq}VWJ8Ky08D@oZ@L78)+Qstp`xii77_E3j&PHQ^<E!-?8Ve7X@nR06tSSz0;82X( zq4LjNbU5W9<z@|;$TrraS&co)y{w>Tf73|w3H7;|6kll3(+zdHt&^JB`MKR?yw${i z7R&$sKC4Dw%rI>?Ns3j(Mx?Q9Hx!{<FFZS)4rakiSk_aTUR#n^5ls5Ysq#Jdq4kO% zvd2E|z6|lnx$xyml3$8|+wnbLuA{=j3-Yr19m_ov^nwtgSB@zy^O!f6@qV#%v~1=x z0r=IgCwo&=&u@o0=;Di77I^Y#i-idDK#&Gv!<gVcas7QOT<Iobja9!<4ccTIuHE~* zCCR*)rj<z0L^CwjA*=nT1{D|JL(W4Hlx|JHM!h<71C7SFXz69U%A?WFCmB1d+q;V& zXY>DgF6t5@sk>X$>(Gy1ysqJ|CSLGDQg5PWi3HQ-TDZ!F*$o2)tGmqz-bTO)J19pk zOL*!I<lHS}LV@zxmo^z6S@6oTkA0Vz_FW(n$~2{w|11CYvBy;g8vAQf`j%MLDFrX9 zPw;5Xw_wmZp-e*kkLZ;)3N@_2lPtu+ts>R1+uF7dmRN>$05zuA?sEnYm-eMUo;Mw# zS$j08pCxL8%Ze{O9&|n%63Pfpxnp-X>V|JoxY}Dg=3l&Z`5Q0oMtbb(IL(qGdR!Jp znL`Dtl5d0;YB6@HsQXH#tx-!h#3w>jdRbWWl9Wt^0guYQIX=YM0A3@X9e5<YI|+&N zlyHxyBp64@iFRAw{=fl7e7lT&XHP7ok2YzMG8#AXQc7q2X-DQ<0ilHt0<56wHk^!D z%hrBT2wGkA;Q&)F)i$^4?aA_%tw}p2-y`g4{JJ~aI6sl#!6ah^t`go3fVa2Xu@)gc zYBD$>dfNne+saSkOf*R==}tK{$eT6l*If0oXT6<d+mKauu?_Vz-L;Zya%-tv?@$b- zhPIBJ4U%?;UJHYLC3Qaf!TtI9v@kpcZ3?<UhKF#*u>`%hS#$3jCWAqcU!$&Fm}(5s z8N~@EBZ|OeLiBY3i7o}G$>{=wYLsHWY5=owhH|PgG8**P1AZHyLAaiRLz_k>CgN$* zG4;*2TeQ*TA9=FR*X<I`(c^zH3=6hL>{A+O;g?3CEoGktn#oG84FR@`w!TWnKGIw> zp3u*J>sDKD+T=qSkgZrGH~y)$n#kU%CNjoI^H=I<&d<h-9EGVrLeqWYdWxq7M0Dhc zLMJ1g>0^o*Gwl=A*4qJ6KJ$8ao2KYzsQ*u$`JM=&A_z#XBJz3FLclb2+?b;$q#wu1 zESi{L)<|ab;cX{lY#J~$6Wk)_sE1^yBAaZ$Q8OQofQAv(jv<@v6wt_0decltQKjC? z9Xf(&F#$-C{>5D@f?SLcPQ}`T!fPQEJq@Zp-!_pany#22@QCQQYJs^ad8_u`xHe!i zA+vkxBKWkN1&u)^|L}UUDtYy5QU4q-iSjxCi0w1K{IYuQb1(^dTq?ef_sW~+$Bp;v zH)~)yUc%k@TQB03sJ3f$P<g9jYe`|0p>Y7!Q$UVVK%y-!yN0wiFYloSQTweCatL#! zDe2SiR1v%i*6129mh9Tz8KM0sah5vGSY1Rxa{JG6W6?^z<M-CLz$d^w{^zO{?e!uI z#YUcF-F+llPNG`J#@mPnQae$#O?K8ps`wUBrLmaAfSz0t8s7e7&ewf5RJT2qyr3qo zZ^e>LhNwuYb=#ymPmtv=Ajn+N#1H$8^PB@FpezCCS0@g})ycJ6Hk`sZmS2$Rl=J`W zO8@XQ{yBS`Fabyjoy#b_cjAKx%rd8Sg@L-X$i<6tzc5QO#S5i{Th}!@T`FyByoRrS zH7&-eCYC94mgbxW<p1@%`#gKtXV7HP`<Pm~%e=e6%cgTe9<hmyk|;ur<JpR1>s;^Y zP!aDJH_v$(G-}?CTB*~2W#}fSDP-Qy4UU4w`57b2ZtNo{hf{HIu3ZqklkjZ-bCPC_ z`+}rxB}Sez;u7yjwC_$nn}$Y^ohbKPqlyi;3uba~E}+VHFhoWN`LMBg$g%&Yv7(>~ z+WIzg({4WOw{!nSlgBfMpLM0L`=mPD;{gzr%&~d%?I()YBY#Hl!b-0~`tqce(+?Y1 zS)4k=wWDMdDP|$@mc*4q*kf><^y>ANl16}Dg&`QT5+(GzTc{n(wnFxsz(Z3Q9Z{yG z6qLKG_G3nW0^m&EP%5bf=OhUd3$J9<XYNH)kObo|HX%B4i|l!H=%_66M;L&4M^)QH zC{y`zK!?zp7X5;AnG2WwR}KOGI#<#G5r45ts<V-BA+3si5ZCWguN(qC+nmU<gVfI& z@C6Y3)H3g@RZ<$4nf^lybeiD^@yrT*Uolj26OB)Ri0)zhsRuAF;PkI?WVbvbhg|5O zG31!5>#PVvc3$PmTQo2>Q44*N1;P^CR<aqIwU39ODS^)ELl(8)&E!w4peLG5Ps?N@ zuUFcy`dhv_!Ou7z#VG*bRsuCOejIzL2X}`SzY0!yWRn)?{#xTYBhoUumhS-25w7VD z>D-<jL{&k~L?lu$nJw~}hSw<IweA%05l(Naj!dr0z-gl9X+26^$oqee5?a$%KLkM7 zOp{l>$&PJRcoV78(royQ$b!r^e2wWB)GN#aW=l&sfXJhd6Gh&>!ehtlL2k|Plv(?` zp?>xRH=h@K3uN{_sr(cKc2&Gv+xk6wyK0_3l*DFTaU(=i@$3I-XGuChAhk!yO#m3< z!l+ipJtZpLtyWa}w~$_*exMX(3P$BJ|IxzycKxq`Wd%rC%*`C$7MUEq7=@!}xAq!v z1})?;xH4L2Bo#!`KSnGHHRjX!y>TADgNn<z&0Bm(SVDfmg)Ft}+7fGc@J3IC2Uh=G zA?QNPJM8q;4u+7OCx7QHv1u8Y-iT64@B<WuKL)7jn62c{|1oW1I9S5;G+arBw4f37 zC65}Gg;T<HmdNR-ve;<u5A;p47xsLA@B<Q7M2stc5`KS3rcM@D=GR>-b}H&ggqJh( zX@g{?(5*S(AJk*o$<7Feh&%)&444;2$>R3N#8)eh)>wQZw`~kxEWYl%K&S{!rWOXl z;DAXMxTl(!+yc*DI*YSzZeRNQCJxLGK4hPO!|shW$q((fv9*;Q-|qcz1RR8YkMkS2 zt5ahIJTe~@!3rll?(Rkp0FL1DwTRIEuRRQQ<jPn$+6P&!wI2<z^4gX7g<i9JXo$Y! zU{R&xh2pT${kcAs&vei7KYW{iy10MNeqvx6qBiuy<mg0h^np|+?>ab-1MF<RJ8W*H zxy=L9v+xQ==c3BFl@uW*-k*Qxt@Px&pt9*m!8o{Nbiz^ZR|foZsAneSS*l$P2~B!Z zo%uVCIdQ@1^KYQy@nC*+nW`?+G3SglheFinv*K0Sk<Dl}w1A?Z0tA=3bSJdzy4y-O zHry>|QR|Ls2|}rg!8;XC!rQatBJP4weHErOak0M_R=37?@qXXcas-yGBGexml*?}1 z9Ak!9itfB?cuz93#!)Do`t3$Dc<yIisnyBfQma_#nmS)hvGn;b;63vs6dIjF|64_{ z%%Db(?BN(8%-6Sta}icA1^($GGWHU4$b!Q|GK}-oS7~BmWcamTtf*-JsHa_K&>(0X zcT`XiZ5~!<OjYHGM87p%c&(iq^@91$A-k~Y!{TqvFpg#<(etnoX1%1b#{_w0Z8><5 z9EoYGmipzLywaOxDJf=Q@_4#&{z(S_FQceBwEE!>gc456p}uiVR)Y>$6_gcF%biZg zqHd9T+#}>^$o4I5umpQYBVoZZpzRK)AoL!xslD87Y4QZ%)TTzn0+utI16Ohp$wRI7 z&?}1fu*e^}hn|<hXd=PY@1V9$mV0f#wCrVraJuw-7I9CzM}cWm4AkEu*^xl_5B8)H z%qfy&HeXu^wJdw+f?d7cYQF?-jb9=5hLRD8zqDHk_7<9Otke%t<T>ACf5ya9S^EVH zFeU@*$>!@>6xDp*YSc9wdAcKMA9^oT*V;Klgiy&pfB89W!+iP8TMyYTxMzw7WakDH zyiXpDpnlf<3w6eAb%V;RE3HAwH3hDr;vfPYI6%KzLr3T>;t5?r<+J4L@H~ZatQDlE zelMtp`-pJQkX%Odb8SstiCDa$EJ9ZY(p3LRSF*}-meB&?T=?sKh>%9B_07G6-1Oy@ z;ZfdTv{Iogj7JYVlX#4*6sAKJk)0PaB|*(#??$vfS825N3HF?vd|={u9!$tKnq=~} z#6TndxpgNI<VJxz^KVJNy3L~eYY~}eOj8~pUgCH&k`5dgG$7K?q;2Z9LQ2~PJEIac zwtGO)@nkYvLN_#gE{n3X;1roi4%7CHsplMpDgsG~(ENnqs|Jw86#G4O^Z2*;EE?uc zyf&+6G~|p_K*)nU)Ell%O*$Q&xz6kAo=$YWiZ1kWebag6s%e9N2#nz1oYI~`-om>> zal-9fU#-9LR=_{;{F;{Hz2#{<F3KDr{8PExlJ&k571eWl3_ZCK;kx0EW2xTP!Yh66 z5VEBV<o9Eh0p{LMl9Wc}y0<%`>tE!Tm-$LH|Az?rzh%ZAoe)eEPR)kbXLtjzFkqCQ zTkl=M?sZP^6Sxiaaz#^Wz_PKgy+l|)#AGcov$4<0_%9O}E`*D(@JTh4a=t*NWVO5; z*hl0EtQIVCUj%2GPsylobbiY;^vO5L|J~VzET)pHs@nSP?oFN%NK{fAd0T=pITZDv zGXZ40;dgGH%cr5#iD5!?s+8-ipap-I_;qGAegq1dW-OVpR8@S%!&I_Nf{T0ZLzs?J zslUli$}HO;6?@_?zJ1I+6i@7`QR$CVdS2Q2&qfn^_#RE5n_XN#YdQ#O#WD4GV)1tz z${Y7{Z@Ju_RXcX4EY@B>d9e}p5Hy$-RQ}p08$yYUJ1`5^)1c{(BP}Xtc*SS;R9v96 z)LPV+#n*I!`H;@O8e#t0(NI%L4+qIYWvx2Ps@jH?5&X6UJ6mh9t7|;fcJv;3^z^|T z!Dl6^VJUx%OtK{V&P{+8EXL!PZMMMbIM4<^jgakND*W8}P~|@OOvjMrjUm(ihZzuO zszf8$H0PD2A8^_zow6jUXlWK5D}wZ(Luv#kjgm6m4SUf)`eU;F&W|<>CW2P?9wr>( zm`d@rrZ`iLgO)6jFH{ozOHejZ7FLBA`_cc0T%1j~(!oh1j8L@{3H19?tGzgbVh%I` z#E22pah}$|0z|+2_egLA*O8r5-=!wvukHTGHDr6MP0VP|Q&Ox@T<bQ@UYL#znEOF* z95A;3bN2rtI{l?REg03JAv0b<*7B|w{-#B*@>vHJu-SN6g!{mAMcA7Pc`>z^3qP#6 z!b%iZv>S*a7GTs%z&z|i=&ZS>u2^NEHZF&<&Fw_hsATtVFgNh;R9WbckzYVW&Io^% zO&tsma&aq8sjXftQW4Re-h!1Ue~|Cp$o2PnQkC<r;1AwKYXtS){x@FbUIgVP;r$S; z+8^jRMDn}NEzFL!5GSLyJ|OuTX?~@7gJ#-3-HAz{E6@4i;ZG9q2$+wY%i6GtfB!2) z?IVyw(J?3FWeBpuYGF%+8E+$M7<#G1)aW1%<%0lw{PSN{;j6X0@^5DFs|Zgtb*+Y} zY(tTPOYg#JE_u{CHjN;n2O!h9BolYs_ssUIdv95I>^`?k3iIL-M;z@kD#H<rF*tdE zZS3nL32FFTX0_GMkuB?-R20EbJWt`&hGf5WW+qXbyPWcyB)BHst>Msgng)K3asHym zDFU5jy)-Pvnw?^J%PXcT1v*Mf1;W~f$Nz;z{(B`d2J&iFjx{#-%tWP;`LhK5erX)G zT0@lJdM7QEXTpGP1R^Wc#Ng<ITt4Qv)Z-&=wJNIGBz^u_^*$==yR(24t%m^Do!X9T zI)946R)^BXpJ=6*;Y*ZmHYyh7RH=Gq+)kAj;{rlW^}8=^t+NxK%1N&w2C_H2xW*b^ zmL@+otV<Geb=}v=`n<W=MRFeOTTp4fLLjhe1H|}Lq6})H-<>V-k_4_)y*Xfo^M=UV z*#~Z4UgTTan<}A^(Lk$z>Jb?=@cPiJe?Q<e2jF<~0L#0GH`;*PX{+4eF$-TKg{tA5 ziKL&Ig$L2@zM8v5-d~th3PaUeS!<uM#_fSzZpPCA9;iP~BHT;fc>6G&$P=e5sxP@5 zUAF#NR^zeI@NK{DQ?Wt0=+ZGIIjUTh@j$RZtG72O&WQ3u0#7gSp7Ql*PUj;UqD*pQ z!b+enihYX<$X7i|xABo7QsBc&;26DE|LcCO3V0k8!;mEQ15hl_bfSee(JJdXJT>a# z)%Y6L*2@`{jNi`u%1gBt79Nh)lgSx1RMwdmt-)3T6o0_Q8Oy5ufY&d1=O;^Kagw48 zD~yGq3?kEWfUOu4>lW3ebb_N1ypGJh3C3co<B9vJ0zew0SPk-KrXB~#%p3Npgz<=O zotb%F0GEZ&jfz6A2w~UY<O#x8{47P}Wg}37b>FEdACeN;)9;nC|1U2Gz=Z5&;4n{+ zukwEK)UvCE?NwV{Q4#;+fgmbV$J(Q$fw;XtjP!u@d@ti#KUCBWF1WN+I|$68c|b?q z@zI>*>s{P~t5av~3g!D*%$_aSB0>*3nnxPR9e@}GV7cM1$C<yhibU{VhisqF@d@;* zIx?%4OaFX7K!k?toLT2lGoRhVYlv=vtTB+hf<KV2YAg<!D9%phSXObxMBbrHGrOVp zNldBi%IRWa7Xf8=eO?2E7jsG6$W5B8ybFH%nTSO$h2IH2;a4v$J<BxZGI|rHa?FpJ zUp~SgA>SOciAn%-FqWMdEcfdpsfjU8MUBUJzG{!;KJ>urYk2|#@_nu9zB<7k%fw_h zJC<tS<fbesh+<Wz<pm;t<-!nK%k4V+q_JO2tkWmQ0&Sd~bUV{vAf6%@2Pft~7U!UU zyKdeje|89X?N811?oO{W%+d;#_x6Ef<nqk><al>sr4gG^TS8UhYD$8=4l6jQwhvh? zfK?;wDU)A-e(=0H#6_76r>3l1C^-Kt{=bIse+TloFYf_Qu5~5z?|UUF^e=k&l_nZ2 zGQ7aC^uL`>_9dZ;8vN$eBm5RIY^mkd5p#`MAI{n}pxXw53#nm6W2C6TV}Q+%J}1)~ z{9XF&NN)!A?&p%IJbtmH1C!#+B@TvfZZkds^N-J(wf8_e#;^Rh$L5+=Tsq8n-JUkc znea@xazpdJMdsLKuO#N&pf>aWw_tW~`npB;onv~oEDOE}vw-Q2rbe6zR(O74YaV@< zB1$>{<>28nzN*q;+GcKY*{PTvJ8pjwzRX^W6A3UD{1i2k1$ufzN7ZpDQRiJ0p|Vh_ ztWFF89kqd_^u2-4tnkxpN?slq+r0w1=J0o$j<Wzxs6;XxTa*x6R0@B3>$VDQpC@s_ z3R!d~Rx`nMu?epAgw9=~Q;KmCQ17J?k^~sOSgGD8ql06J5jF6Jm)kd|?Go}`<Z{`8 zmLC(AZ(>UtsjJJ}V#SOo)X4={_|{TY;XA^bh$#VL*Rdimxx(+~_KK)G`XwNm=67Nf zNr$e!GQHr_aw0O$8mrQeAGece0i+H*CO8>SJD88~TRNn%^JY9PO1kwc39jDZTkkgr zn59PDQKI3Q@i%nw1GqE~!0mAhEYjm4%+vnIm8Pq0NrMRt6>4<CD({fuH2!+tXWd<U z?{l!<gOr&@TC48=F64i&s&a@x7fUR;O;nR1L?}6%c|kl$0*j6KchIFEW|+I#G5^>D z`21-1RhLO84-ttF@KPJju|Qv;-U5C|zmT#_MPQEVW#(6`-+x1>FA227E8RzC#$`+G z`bNi#qo}Ku0ca3817Y;0G%_ZFQDfVXz_jQ{bN_|1>6`a9t_YC5#y7ohAp|H4l!zg4 zfYl3~#HEptAX|yH#EUu550Cb{dOlf>e3+Zf6xZI*Y_=VCt}nU^Va009L`af9<)3;n z+0B&1Non1FaE~{K6;6|EFln&-jog9U1DOdt1aUIKgO+HeS7<rk*5e750vOCKCF@xY z1CG^5LjkW17*cS*5gz+FD@QKAODPE(w8}ykx9JN-&1uig9(e)uRBkp1Us-Wj7CoN~ zdV8nCs|sLW+60Xvl7<$6lV;aB8>JiK;XPge0eTOelM&uzTf4Vn{Y^bdfxht&GKGP= z23~|xqOm5M>iH+7nXKGwA??@T9LiH%LeaVA&hpRg`z~*IImF32Zz%&z;j!9G-S{wN zZAvgxB4)plwOnsrlyJJuWcBI&?EjyZJd4OWk|+q37{GVB_sRi7_jXiRrvi>=mTFir zz5GJt2a;gKwDQRVOazV^bU(fcA9?8pvZ1|~lx0Q~5>EMf^!{YlbQoPggjrcZm?}W< z>9p}%_2r8CU^fe|3VS?R?Y7vMY_Zz&Pr>)UP?i5auTWUC&Hjs|HAzWF{tHQ)`WKS+ z`QDQ!*MA}duyb!<7wGIT>7K^DQ&gj)9E6zg4c*Pn!&DbNWE_S%K>AJs(_n|ID)jD` zQ@|^6!B1+lpo1XRS|)ll>|My0a0ofXHXr{$!#5NM%XS=*52yc>MX6yg%0(=k$2R6A zzkZ7yDLaz0Bh8QU)~3rRSw_IT+Uc9=kx-*2)0#f)Csb*ZUWdh@_5z3uippDO6G<*k zY2j<42v3l;RKF=gu^6Em=Oq;jhsRST_vU^7I4<_!GE8j34z3M+F@_9r5oHDz177e~ zlO~Y~nGIB3%O9VWyLG7{V!eaQYE8{vo>pbrpDAI>!c^MtICh_YBjA16(Zu!CENN7R zbBa4-dNfZ{sh#^_zo@7%pL_E_@QCxS*R9^<t^3ku80_j`LMgm=CprGZJ2Hgoo5F(4 z&+>8)SC_E;)HtEC6aEQNhxRKV5IjJCh_HH^`pJE3<Bn98oSxlbd)bVd(XOEAx10VN z+4G&hojADB*c?fn?*G7C{}wls%z~O<1|PoqWOFwW@_Mg*a2`m9+(fNm975a2>O=BY zxv2GbG1N&>e1G)w_>8E*Ln63T$WhUw>*gG+2%g`iCDBi4Q~Iozr8EU%tm`1`C1&b_ zLX(*o=oZ-29xx-}*}GPg`AHQWyA&;@8yym3q(&4=Z4juxBFvy#S3|s+(r$D-pfx9s zKd;md2EW@?*^$E#L6C6UTFh-wu-_ZcA?Miqb%i`B|8RfHGgmqj>t>{21>h6}Y8G1V z0;Fbz#ni;+Vc&fKUl`)F*R;IFvog7aOeACFH>)2E__?c}4cdTyfo7np0%uyb>jU46 zyxML%X6R5Qi|X;^`DKYlPr^8bmKeoMLxHy~qqPH_rY!+YsWh27Km?gg;qPK+P}y5# z(eMoM1S_w!j(azrq25nmE(yW;Q~<d~8MEfnvo*$?lMYARUKnjBKzCk};-!e<p2)J$ zIEhM|R%mD6`@|XcmZe+E_F_Qkr$YN$4?%<N$xZJ4XMPG|Ul+;jAUu(bkI$2NwRe-d zbyS7K0nSg|e8$-65`SnJwcK^8(b=K<vL7;-k6(S~oBRL&qHoE7`B0X<$Ms2a9JP^^ zV;3(~w+%G!?5bLb!G36=pyX!a<Xq%G%or|a^V0;r^IXFGoZviMxKgD&{vW<R0<t&S zDcA^%6`xZ~YMwF&whITRD2I6pnBdm`vW@>aaIDuHiqgCBOHzM}wU#EL)Gkvoy|%gV z+d(zTz6Zp9o8)9+To`6dhF{Gp+>M6p=tb8<raZlu>^bby=-3@(jJ+x$uEc&ZTkJLt z0R`}WVIqR{b{ovS36KRg`NZcv<YA!K0H<*+BrFHQ6?8if#Ww*w2LnOk(syB2zGWFN zlI-jR*Z|aN5U95J!_CAMLZpC(jVXXxfk<c_ET5)^P>RlvEjy$EAozdr7&Q?L#<q}g zmet(6eJv^<-eFT-^i2q|5wpPR>xL+Wp$Q`kC~srv`V&?N;;qzg{Y(ibP1<HlmQcFb zRn5!S1LnO`r+bc0ulS;;RE+!#is<Z#4<~Xp5_rQK^PnL#TvNT~=?F!O@cWXJe%&eQ z=1CFMox??3t$B=_0O-AMCVT8p=CmI_obCG_Pw4oE$#cx>m`D*J9e>M1uUY%{y6VW% zH*b}XzO58$dg|=yz9WzHn@GvuhsKp`retjyCK&-j;7a!L#1c^t^U5~MM}CeT3x23V z`{oV&KA*JK{4Vm;tZHSlQsm!*v44NchYdJ*pu406160TxYh^PN{QO68RNX|xj${}0 zP&6fEIce&3tk_=jKI7dv=HDyeiw($^7!?mKIItKm6A>h-_%S0<Pv}*tIhukHyvgtS z{)>FO-fPpR<S_F4i4)xSrS2=1de2E!4ZBV>Vqc(02f@fY?L!=G;=bZhz`ic#rWR3^ zn3yt@UPjv!-*z#OP6VntA|kd9*&s*EJtjWEFW01KCHfk-eR8fIk{FYmv|Seglpf&2 zeHMW+_Yw$BcUOvu4f%;hJ}6VGm+}fgcqan5Z|3y(+h<{AD{3i&{_r5n!({rw;3WXs zW~P<K##pGHzs8WNx)gFnT2iKKdzkIX5#0A5FMzhmQNk7mjDC-8R>s}j8&@Gh4T2Jf z7fcnOKjoxTL^(zW!;LyTB+XLrk&|zlOun4<XARrf`P(;;y}e+Xu$q_Nz{<Qdg;c<U z=VEXV(<ViEua<}wzKo{|j&=;);S~s2oEt~019Jpw-bNulri)Qke2Pq@T>UWnvOjwA zVBsM25kW(2jn&&$H}mtrq6GU7*t#V;ChJv$J)!Wg!;qdh=4Nzv!#W9%H5;#N;?*j_ zv%y^PrAa77U%*qB`WyKzs4toC7IN+YMmCx@u}P*@xx0l%%PaCGE`q;W>H_uWeRD=$ ziw{mIe0(<u5(Lj|d>EnqUbVJso$G#?vu=5LHM8~M=iGla4=vxA^urfYx}8-iAw4mR z4A1wTZK~cjhD|QNnluaq!;Jkxik#)Ozea;zFh9CB`LF@xxaNe<Bw@=^^{OcsQU~<* z3U7Q;O;D1YE6$X1U|b&E$o|><f7S>9Q&U{4_Mq-Rb)u66tT&vD5&v3E^p?TYg}z{y znQ^_}OC`V<DX$|yoxhnNcD_~z2qL$QGxYT(?jb9oF(Q=R^zKnlf?y*p5Y+mY+BTms zoxh<Kx?CPilcj_VO6u*dfp_Zw&38w=<jEMt?vhFT(6A;TQKXzM-z2aXE{Yp{Upo0j z!nXR2m-##U{no78m$3FXx9XFQos*GC#=pp$APWdpqXuVW?{`2EiYuq{4^eN5r<|BV zmJczZWrU?l$_jDti}tXHSbKn3c6G-y`%A)}WHyemhiY1MQ7h^%BC6gIHyqwbLUgVU z^9BvSVNnZcQ4`fs>DR-AHt?U;U~y6rFGXK;&uEsdgICLOTra=(Kh89Hz2kc~^Od90 zQlYG(L?_ZYW2d1s*>&#){5gnWraI3G1L3c7M;E2q(*z4|8_sw=p782JNu({?*Sa1@ z7jL=O07BT#8F)>l1m=v)-OJZ&=uf2W9$3T!iB9@LoboLn7$<1=x=Kh4S$%S?4EhrV zXk>{S8LDGE9F@Q5X?O%MEw_8^ed|j8dUWvP-?XBCkX7WZ4Mi85sv>^zARG~RQ40-c zy?Td(dtX?)X-!E*Xz(qb#|tcTU0o2`NVMHIuBg#`n4;hHC=RfN&x=|K7(_-{EqH_2 zP=P&f6ooV}wRV4em%_gM?!LM7dZ+8vlzTlur87P~^YQOp5;*x?BqpQ7v6HlCi;Tr6 zv`7>DN-ok?!_{y!8G9>_U*k6;!wbt>H!}%XT-Ux7rBSrNvW`R}czEMvIV;5~)O+9y z1XVXa^C-5`0n4(en#R#s$l^zmxcm+J*Q0wWYV09y8lG)PrLTmraw=EzmR<dz8fOVt zp_RMIXt(4gE{zy^K}G?kq!o@D46q^<_ss}NaNl0O%g9y(t?;%flsiD9L`D8RV`ffN zye$(viT0cHHsXj`fCndiCw)kW4V9Zq-+`kU^COMfrE+akW+`;R^GU)V6Ba$F$Rqqi zu{Rn%$xloO^?ylcW1|8k_miJ}-vF?y)*<RSvUT^??jP-@njLmEDEPVu_cl_wSywYP zn~z+}kJA3ssN&pKb7T-p=FbpX4+`p5&}L)!B(IeGWjy|rSN?VnCu+Bh&N{j5@tPel zRH4$U*)e4A1qW8Q*lzAx&zteYgf6i>kh2<I|6HB@<2Ajc>RRlaagzlDZS)t}Sy;dY z{W+hHqIH91nO%lt&%QVd`C>7Lx?}ax9);R#ezfTwk1v?BxcH;gsXyjBGjRG7OEM2S zvnW_|mn%g7B+7Yk^Y*YcHM$HP4x-%j3k4I!eybZ>__L8Y)Ec5!Y5mFNWb2voP(|Rc zTNi7+R{8&05O9y@Kuz9aAEJ5o`z&i`z52^}q!U{GGc#8%PdB2&Xkx{YC22Rhv)MdJ zaQrr$LXsD!Xj9bJymqCl7OC=*Our}vIouPI8G27DuEEtksj3J1XqT7+T1~Txgf+<j zcw|)&={gK#8~tOM^|K8mQ|u)krQrzWTj}`O=%S7wgb*V%$Ui=pknX+<Q#()|a>f)y zBVEYvVo1L-#p?pn*Lkn$xr^#sTA=e0jPLQFpQ_PuOpFG1o+OE%CO8WM0uYVaR&0*5 zISxV&FfPWj-w>&5{SFU~KVmDmnF{1#>fS3nJgyTc_%P7FAQqva0<mM-y3KTI=0y@T z3b2HBRlvZe>?ya+44e&&V#uM)lQ|?1F=Sa80SjGc1<ATVNs4^ZRRcxXvbAo~X67=! zoSG$`ki1-M!T@piE=%pEYa+nIH!kB5pyU8(XT{7Hw}VQLEut43D_($ASVEUid&yut zWgZs#BW3Nkgkzz#L0_64KR~g@+%R(M*#<R2h42wWHqv5a-<f~i$xzYu6~1SLerFj0 zO2@1uJ7DGHSBgM6sBI&{_RJd-c2k9>E5>1Ovubl)5)By_V;`1uHGT_5<#N|>^HbFd z26Z`3zO(l<k9kLQ!pX&&#%YDM>ri^y#uG5H^oEJf3Gvw`>UeX_q|!SoZTlN!sH)8g zDL?sv2{kX*YCEg`|9}XwM5}<YHFJ;grC;TvkYboW@Cp?js5>U^-8;cqR)|IjVMOZi zpASH?4RppP<j^2;D5Nz?M!J)|x>@|WRVTYtNd!zOh%C=3&EBSp3QKBSC`LE>78<W5 zI`isB&4=O2Of@;HI<?R?vXFBltOLpKX?T!T+Fc2cEa?11p~nMJ0Wlpigrrgzb>hU4 zOZwDxAHZCrceCf@7l|V^%~0o)jxN<3_b5(S0()8j;hw@SAZ{WwK2?Sfz~4J>;;EjX zpbOCTZHg3qj3q#Y0v{Mek6P2)v=C{H+`$zndJg8@%2?LFR{CN(W^OEXJ6ZUm1RcAb zPJg1vi2WVuOnr)Jjph`Wk&T_HEP@X*g5utQyGU9cDNVZ-cO2Ab3RvQnD1uJqykjA= zQ923*7fc-lcXvPyVW+*ya-z`4edVS2fR~StW845W2Kx$`9WpA5DmLQtDg65#v2HHD z#p~tN^iQ63{~lqAVDC4>mAYA@tC}Q6`ZTKJ^_q$&n;PwI1fIVdi$$ljGvI6{*7}M5 zWXrz4;WiS)&@kJLB!A5L2ul?c#Unq@@Lj*!Zkft;1BUpu-@JPte|2<it}t|QZRhpD zzk_WK8EW7bGDJxJ_49S~OXlBzQ_*W%+drUvPnW+&iC8s`;89n1_xZz_kb{(5>|biH z^!-1w2S-w`ek8A**y_&kqhoObjMHgz_pej_u%AACY9@vaINm+%6bbpdetCVfst<nq zOTr7U0&}YGEr!;ibDEuUgU2r>o?pHW{`tPa^W&rGzk<5l<Jnm&u)xK5<7Wq;$=N5r z&C=rjs>|<HW_x)oUq=b=jPAySNY7pVd^RsRxqCGEKP3!K@DcD*2h5Lol1$Aiq`SBC z1T@j<_xQnQ@~-i%vxhfCmvwdMez~>DIf#TaJ~KU>D8<wDiKU8e2_&x2R@ejmiI?c6 z*ITdkwz17=aruvCZp631`5k9|HDYH7yVuyOX43v=GLreXr_y=cH1zkQIC%oR(1My0 zCXjnd*=aVPQ{vQQ-ZLlV!OxTx56LJLrLES+M8ET*5fmPpWG^U)OWCV2IDO2|xY|;O zJk8Flhd)kwon6o7XkB&G)knW`h5(3T+%#a^7|QGbw(fh9%BL+5_8@^2`jv}U`>%8! z{zkt7G1<W}O7s|IVWxa;4CIy<bLYZjt#H0gx9@vx5tj@r7=oV+c-<UCrqxXfh++g< zI*1m8V2Y!6u&mxUf7~k~{Ou45@7ett<GZbO#~KZx0?81<qW9DCqJf$}>Lb$`2$>NU z6UdU-Z#~;dqWj79`oyc>AwbSFEEXTPdXoX7S*;;QH2JW$XZOCnR~hZyk$g-*A|6Mz zAnfW;E@?mO+tC1~V_1G`C$N+6ds527>sixTe|)WxphJX%(lnU#wBe7Z;x~^h=B~|> zG7?XxnjDpIVG6@S?-_4N>E~<COm|jV{WK`na3CcUrLqBGGbocuShKK1X&?C~gsW3A zOj6NiB;sY!6Dy1>>$#C~yT&iepZ(A7@^@Rg<paK0w|{aq-^K!?AtKU`&uJ#XqnVo} zacb2ieedu?uO3?Vy=LdAA>2kVAfT^nO5xet281+mo=ksaME0xpr6eEUY+h-BV%$z6 zeR8oyn@y8kDmO+{2*~77pbMWwqTk=*W2_>3b`9_kxH{zBYTRG@_q@o~rv~tm4<iyy zNHdLUiFGJ#Gco7i9|K60K37E|=0L-v`$FNg<$w}NG55*u#1f3~g-{{s8pF(`_)n{f z=yEPQeHfafB{l|W#pt2=S?-WYzI@@F3{7o;U$l{+Ulp1EP}xKR8`otU|8CaXL^-mH zOT9svG<Q$D&BN#@nJ%RO6$nirA3uor?Al>T+!KFJ44}`efEtED4=I6&2!=F5v3)8k zznE?7hqd`+DWhT%jov>E`j5++xRG;|bj%?iMU^46BzP+~dw@5~(~fGVAjT5?l5Zs) z6-wJu^7+oFn0GPd!&J-}ooDC2PGZ|ytXKk<Z`xJTk+9o7L-Bg!f_H3})wi4RVt`!3 zpEgsocSGp&UZIH1HO17t)E^Kp2q`pqQm>ZAS&e+>y7Pw9;odY6Kw7NsM=DE_Q`QNq z%00&DGECSCf89{h&gi9Ir%+ya28`pc2Z|6@qhIbj&+urPph>@z;vwr{sa|lO{B>mL zs=}`S5T%C^k;li^F{Jk2)ac+8iUK`fSx_I(2A_Sl%LzKrI9!!(of1Dq1e2@#ic~1j zFRcs(|MXtPHkkolMIjZZ?TO0He0QsC!0R7I4u9$WInq;)=hi5@PbZ<^1TuP*i3=K% zKa6)U4!k;D8@NtFozHL&In4Z(&bXBtVsf=WeT8eeHrQ$$svA7-Gx>~q5cpR<Vbj{> zYx46y23gn8%vGQN?*q(M-2RBswwD&YpXOpAiu8&}6>mcOAx~rDkd>#@f=Y1<JgD#| zjRCj50P8#o66u+mq#olpysq@FZpah{OAgwqd+ADdPzB1cG2@}Ko{si~l4ZIx|I^hy z*UX~*r@FFkAo5U3E1;3tBuD2b5&gLXUJ)}IXa<BE_r@`rYEb!(;mH|6a^#1s{W?P1 zV9toTn}(-1JERIPK_D|GfwBQ+2n&zULF0{cA$%3&7e(BhV_Qwn;MgS(hEDl~Iq^ev zeMIu4DJV-5_<)~)-Y{RdIt3%<eX${CH^!w*UME&d&8w{?q`|J0=0nhn3x*kLRKo^A z!zK(FVK1~WT!IayM7`8<T**phZS3iCgMlv|+qUwZdyZ<A5@Y)LOUXqfIkmMlpOP{# zNCFu;g)wl5jU#x3z`o!xXhp0+!Z`781PVU)rXK?8{_y_N&nTqkn68Zl+v!JSJ_TqG zoU3OXsWaD4<dycdZ$0Z0LcS9K-1#vb=(xi5KwMQdIrdO7CPguA|I<EhG=Zw*Ajh07 zdvkHTs?UJ)3&{+*C`&Fsi!@$%JcKoFNUN7yDShi%Q`KbBW9-*I+bcZ~Q8Nd`I+M`V zhS|c`8WE#7i#&$5xbCoLq>{leRCWhNY#{kB1_nPcR{KakJ5gtU5c+)3u6gmFSM#6q z^$=|SURFly`JAt}x>&gHw?ThUQ`l;H_t~n+vuUYUG|`=Rs+UD80m55ki-J-}w|7x! zJAhu{@9*G?6{rdkS;$8ZS*@UM_FS9t74v({d8K4%)r$9Oq6I~=A<ZbN=RmALVG`i! z%o5Ete*5SBTh{x1JjhbTtB@|!+W;$s+DL9!lKebN5&vl|W|h9AFRUn<0I%e5(mPM> zSY(tbZiCj^sz6h-XkPx>v{1j$5jspo-7?ZE#)Dj9O~mQQ;N(I);FWjD+fSr`^li>; z1V@{+c02XS-^x>h7Mi&{huz%jvL7oC-h<6qc3A*2=pMgb`HdLi+MQ*};k>WdX<c)_ zpyYM|7Wme4nxKJ1n8ly3VmO~ag6dW$_d8-3A35UHB*jb`5lUt9ld7$n?uBIJ%dH#e zcRyG>SSIM&%o*v*e?tU0JH>Ud4Z6|ym;g=UH9CxVw(-_R=?Opd57J0vL+6!c6>a7_ zm_9Ea993-|fvD0WHWm<zvVEo*UxSlE){>qy4ceLZbTNDsmA$uW-7(r=d%w&2mXtHS zg@z(zO;I>0AglN;8a~2^jU9p;ed@SMOX;CyGwOWk;2wFtA8WdxnpM2bsDm!rV%QG4 z*2PQywFnSKYr-(sk;wPOcCT%yo+KYMzb0$BRvn3>_GmpcbtsHc8_D=|m>I{P-`&C; z2%=Zt=jR*y6)^Mke45?mZha+2-Q>ewK0fwS6-Re)T2Nur8qieAY~n;c5n%y6Ob;Lc zERYI?2dv(|&b6O*(usFLJ)o!A{bF-fOGKgh#;}_dQn=H4@uk)EIrG)Y^FI?oi(s2- z3dDCF{?^S8K~`+jRxSU5iywD@VgoO*exweRLEEuylPF9NTaVgWv6W3CA0Sj@Jbr&M z6HmF0p6UE}Z|&_7m-!-%jdflrh~s&kzrZk!ho*Ax?1~YvSSoSEX~)`72*LMY<$us< zez45KDF2J5s=t+!$Sg<;n^k%HG1n^pd{w4YYKd<xW1uivuA1%#+LumaLM)t)6BQF% z8$+Zn{FW*nPMKxG^sAI7p@jSnGS@s3MBu-WQld#LBYEtNgX>(+XT>^ZAj61WMKJ|` z^#BQgsc#sjF9M38OHSMP;yFhEfr4rTrXb{0wE|5V>Z7k?HlS)p%8}ulq%Tr-fv>oP zfr)VBuk=4a@cLakRx<*~XzF7EPNpG15YR|dgS3C+d3ZTT8F!{<a2FIFR*_rA%#uQ6 zs)wP6Nc-$4#yxrfmW^fcSI9bpVwmQL(E74#It61^89O{_bCg5uK!GpS5l5{Au4x2H zeN8`GCZx^&(AK$HLJY1aNm*B~kM8VzIu7&k<<$b%o%;1S&J)hwdS5@8mIo!EdDX9R zs~>1bNu|TarXF&8HG^R8!%LAF$njTV=Pm^)H^mg#fx42vVje%*iS?=Zg<mv7J{Fub zrmhMpHI+elMRh0$3=?2jp3++V!mS2G1ULZ=!149Q)0Tc)7XGF(Ygw7M-u*1pD!Sb3 zu%vJB{lCqp7Jv<CY!Ova=?C)jVHercAQ596cdu5QZC^lt8b@C<;J%l8xsJfMBuMyf zOm-RZcoEeLc>D?o#z94%Tz;6e3QpQDC&*|Q=560YW+;K!_d+OG%3@ZYL10EL1xRi+ za}e=VCot~nD30){I<QlD=krw5z_s;Id<xyrZvYz46Oo(f{~cY_V4L9r=&RL+8-Gj; zZdwK*$db}lyW>BAJ+zST#z4<sD7_2`j(U?~_vOiBC@;2A2e3d;Tn&4P{4CdpEHTPL z%UhWZ>AZfEbhGA6N~vxv9((^utuGu%JjTb#6(f2#3Qq|E?bR1=3kkiz+VfQl9%8Wm zp}TGmlupTDcL7NBTM7y*#e)E#XC@k62odFL;ft}$cnL0=n!ZNT7r3E{fKT{Xf9x8F z#)5odlf|s#$nU{oZ{^E~Ai?RKDfTEd9M~J5YAQsKW!SFo@Pb<bbx@b=c9@}l<f-Gx zrp#`~o4_0`@ch=(l8YO-QnDypYHU5vq=P8*>o-CcwfJ;)C{H$!X?fIKL~0<|{%l?A zAlxNZ+CK0SZm4F+?f=2fEmuGMwp7^$ohTzfAox`{N;6A<M?fr$|H(TXbwfwsS?H(j z&dOCUy<){Y5)*AWY9%@7y0X2F*c-Wj2^V}Yz5{wQB66VR{K<M~-AFPt`}5_Fy9MXy z;9vq#vHZ2WlJpsKlS+k&&7ZRgVJ-bmngTmW)W6c&rD2wgek$T<)Bxsv9zbX}V4}I= zVnHk0Thip@3TX<%@r$W!e3-oWquT!G&-MMjPkeR+>T{e8M`;(2)WkdM#BTnTmYNOc zq2XuQzpp)|+#etUm|6kOnn;D4hm&@Rvgw~mmJK{j{*kim;Y?w3Phi`loK@oHs(4xk ziHfR9#1%}F5hcOHQkueR(0_Lk@FQYVN)rzAcABq(6m!p8_g$AtS@r;TiC~0ocu?%p zO<=<)H{>elrF*DFx!YOkC4aX-CD#$kTKeRJ-hVL<Dw!WSe}^@pCjB5y&VIA3=OGkq z^ww+86)E$I^~N4!%k9xU8}Q0n$qi!pLLz%H9gPING!RIa5psV;BF?SPNi2kxfa;B$ zsZRIlb*hHUA{Q*5iH^y)?902+7gR7M5IO&^w&rleL)BV)?Oc8@7EI+KaEB0;W&Xm$ zP>f6L<=Hx3qB|-pYQ?gj`&|L(@vuG+6$>0XG8NTRm(WhG?9t$1*|TU1BX=vyA<EKJ zH>6-CAjQTc=dh*l_mVR8G~f&i`<`|ce*E5Xy^JRW!2X0}hEr&po|2`#)BHsG5JK|} ztMZ(ONndHU7$nyRhyyB`8>VM_yMg4P5Nk}fng+WiDx-%+No|X=G@(#@`nH&D$(i+- zCz6>P9VT!b)@jxHHJngWTYoDW9Uh$?uR}aC>Ot-x?<9$R_F{8euJ;voi0i~4YMx1% zulIp((*()3@!GAl7QLEjGKM3-O4sJ$@7Q!b62u-a#Jx#Xb1)dgQf8lM2B~m+rZZUq zPaXtE@`H2Qr2<ZBx|Q88>n)hrd^3mY%+PFfylE6Y{s}u>f2EH9J!JI2&TXcOHl)MW zIV(GZ^OxB#gLF?be|1t1sJUG_+JQA@9|hLyxN5R4WGfv6AAEv#qZ2=(Pc^zrI0^Jv z{;;AGCrkX+`uv2Gt|w+)4z?VLA>ksyS-;iyfP*o5{ZzODE3`cIa@7PMUz1#R=9jk= zQl%I>le|)<&@dPrRcj8AqXUmczn;0I>ZHHNq@PBBXRQs!HBh0sFEp=CC!46Z5X`>{ zSdk(&+YOf3f-`YcmIhOLhr`QiPKldK9OK6^%&$a^(pC*H2bY8Znh61x)WTZskQn}W zXYTIV!lM`W9`T6&po_pc#<3GG(0us$8T|+kWg>ql*71hrnxCkqUZKxz^iw*f&RkRy z7|^$dC#~LdP9^Os-MQ*mt$s?V%;<ST%qSd-B)(ReS{g1j@{)Nib=)4=IML1ww2iyn zR784CCd25h9LqmJcbBPw_HOl)$f(_2H?C*aQO73KoZo_qh8}}V<39-J+?T4G@?GkT zc(i-;lKQs;;O!V~jb5=ztEdi?7lKspiFPaV4-IU1nH}Ra=u+-a#vVl(^MH-v=aGxW zig)xDz+fP{brqyf8%VAWmF+|T1W+n|ac-_}oHWtVo6x|6&hJLMzfPPk2{)}Ptjl>1 zDr9G?+@KSFhmAi%UV5Ee{@s&)#n`TiTrG*j<J3XvK}+<x)v&?_n9rEVpV?=@%RM7& zl|Q9hPqxmDZkpcYeZ^m6y>Tp0VF>cwfb!)1bqiVjn`*b+asyv>|6-AP;BUZaU>3m4 z=5o{IB}-*az$QO<yzH!^%VW1n54<i*!>L(SMwVB+wo!iVBqANWa+^a;@BXC(nbnnE z-(k_37H13AmnO92$ZFa*2Y^831zdah4_NRE1j^#tT@?R^_`HzTfWEqlS*&Nm<DD2Z z5R{E`Z|{(}r<Z_zNle}02lu^$R-!F+NI0geO8m>cbz8rT8D~X3X0z9-Ls0dceP<(i zv%W|xv3M}+91bbT|6Se!wvc{GdtdW=uy0F?&;KAJ0^q&kFaKt;n+$@4=IeU0GRx^# z*i8ATrxkbAPq@gQCCBbjAiuWxJQ6SaL=;J0B2f(}MG_^EPhfn^*R3M0=t$KS8MNp- z8deAbGCG_Q_B9(K0MoQ;qvQpej$~%e;Kpzxe!F|RGMhArJGU9TQJ-5Jb9aE>o(WQC z;0bYg;VOG#;0y0btT{S6%=F-S7M~-(z2IJ+PPV6<Pz#5&Iyvdy3NH`$DXn(N>Jv=( zqbHeM6{f(Sjnr1#fMSbD-tm1_<^PYWw+w2#-M+t*;O>;-ZpEz>*WwzSVu2R-;uhR3 zxD+VG9ZGQtR=l`tky4~caf&?YIp@Bg-+!)|<P9@<@l7t)-h1uOiZ#Ii@aB5CsKH88 zbj*ez28;HxxFk7S_%0+_*um|rLHbRYNHEqYDH|$N-pgUZyF9w*=-!ps<Jz<}>Kh!M z)GSl9_{4jz(UUIE@m|3j22_B9%ZJ>8KfLAUP~wb5{TOB5^OO=~&YF+gQPE^aJ!EG< z!4RCB;-64{FJKw@V)^SJWu04O=@DxiRT@8U#eE-R@$GuJ2_ab)?{H>5?%2;8ICesy zT?xzJRd!k-;SDP>*|<HJs!MkFF0Z^Vn*>pt3f5OTWx_Zrkf&&U7YbiJbyldIyZpcE z3*m<EGLnj2os(&P(?b!+@(I%c5qS%eDnpe;dG2}U*9oE~<bR+1)2TPTaVV+kg?V^5 zogMP%Amx+TGaO>3AwmBGE(cAuopTwu%uHi~{}<;7=zBgmztksy9Np5|L8d|*LS+Ax zb&w-ggew<m83R5wB?l1A%o0w{5~zY@i~exTq+SVv6X-DD-mnN!rG#-kONs(q6wvQm zTQ!5kI|qtyiBZpR*#>Ea5KLD?-!T+VjvC;U@D}+N?dlF|9{)SLCf1_@Z*sG&!zt*^ zfz?VfF9Np0FvGwzWd4H+NiXDlUJkN0phw*h!Nj2sD`F+~tHX180R<1Gu|Ttoh?C$^ z6auVqw9JOo^tCuHBUdUPhvw%KmU{E^EwF-A?+5Cz450jEr2-#8N)^PXvq?8KvcNG+ zu|~JO96^8@u_t4l?_0%8S;Dwqt>_T!;d37o7)_hv?W0bLY^6LeDnKyPwNxRJ`8m&^ z_@cR&m+Bun#t{Vqc^aM~Do}|C^DUq0aOK<`qD_InnbTIWd`D2;mFN3x2&={-#;&jc z6<&>Yqefd1wikgQg``w7-Scg+h%;0!-0}b6t9M`e^(5(MeaASg`{5zQFzbu{rNpP? zFad^OMts_&B%WC8cV<YcoA6C^(O!bbt?ogB$4%~jdwK+&@vC_q(DG^k$QQ-57zU&p zpZEKfW|(&Sm}mS!T(H_Hz9|cM*{SWJyX$top0YHvw|bttM|Z{0l?ypZ@@7L%_gU-h zAZ`7z3dN1vv<tuiQIi&})((Eb`=&J9n6#%p?Fq@g69N$ON-Jd_{cY1DC5stP&i2?J z_yG}%v=(ZFf%$({_l^+3NYR~_)y^K<Qz8TU-FLcZFkeIkY2&r-Q;O73RbBmx^y6QI zoVuMFECkemPP;7)jx=0we!)@K=U%&aB)V~%(78oYTlbeQ>WTTzKKPS*zQnJ^(SCL& z+Wf_mKty`+%6Od_mg?nYZP1#MqOzU#UfKThxs#MU(WfI4y{nUT(0|Xu4Cwcl3Svf` z(^58h+61kLDCT@K8rJ~`nSUj;*mno24QZy6$NK&G`;<o{^+Tvclz4xEaqk{NG85e! z-}*v6Y#9MvVGyc^_$vHT{J^8Ztc)6Ibp`q3+(JIIjSiH54kniOrj7;1(=q2J=V`8? zl)5#ZZTPtkV8OuLO#ZiY?6wNjlGUwb`qgLLxw*5VSccJFe`GkuQW$i=#_<JED0@>b z)=}9Ow#W<iFx)A1%eEw9|LRp{;PhifkHB90L)!ZT(#cTwh5$%LMbas2Gw_PG^BG@3 zRK;XcIGfK%D+~qnY<IH#svLq!sVi?c!NfNNLe?=>@%^UQ7&eq90KoXNg0oVRErLf_ z8OLoTy!>VVcLURSN*s+QKArTK%<Zvob8KiiCs$GdfHMt<O;o0QI6s3tjP_O2Nr(eg zSeJV1l0PuG#SK`X$X<YN<MWKPW+&wBv!K4*HuLhyhlpQxn3FLe-<{iZPWx)889f_3 z2=rz*7vTk8tN^|5cK<fj<si^#izbx?W8<{KYM}uNMJ<^!U}ymaNJb+3)JUFL!c>Nd zld_RC-`0efZI+XngXMIKGAp;f=*B9VhC$(}7enO{s=dJSlGvskai>1>1tk)ZdFEr| z;?+7|_o;&+``Lro)xDDNl~s3u*{Zo|oJrbuc7=s@fC}uV=49W~LYM25$n_4YQ`GwN z1m}OgBLBSW4}z-fPyQIYJ*?VKpv_J<OJLp$AW4m7ep+R_hhvdGQlF||C!n{s5i2>t zRoO)*+5MuP^7<4hBA*}0D6XF=pAJuW0@>B<D9tRUat(ly;@GGcpOMaI`SK~C#bvgN zQM!Eh`gb;RvYGj;Qs&Zu*gW=VSEaZGY}IowuMNtwZ@%y!i4TzXzihX}#9}?QNk0oh zA>EQ3pc);zbWEQxeAXF7T~PW<%VFO|hLEm*PeFpByvJ8b+lS|c?&<z#s+@$=&gfT^ zZc6BU{(gNEr3x=2$R%zW<*u@MU5Hg%*{*;?NDOnS2GWFqkodQ5>b?H3BLgw!oVuY~ z;$)Yi%I(beBI<gz$eR_&E)^KUR!U0ozdn;*nkN`l&<2=#J%lqdy2ZTfA8Hgm>Or2X zCt1q9QFbS(&Da?$WmJF>V5{@kMlT74O)b=`vE{ko0g&d^NDhf>&u)V+MRNcx=6_6# ztgv5@a=t|P6ZqfItlSyitsD`7lU?Yj30HJF;Hz0HI=nR3a)?aJ$qU1Yq~+oXzm>JF zCDUu7XxaEq;@K9nel>q^T#?@^B#pCg=P3v6P_W`~`vk5O(^rduV0I)>BWaaO*A92B z!VHm1L<UTY@95HA`OUu9wSweb^22GQWMg$TrxiU@`EX}%I%Iz8(H;l1w6s+J>zCu; zh5Je8waBRsiO^08i^g)P<b&<t<0oE>BDV?gYhfh`UbX7l+LiY8OD?n`Lea@D)Yq5o zDUTx>3n1UFAaq4dsI<5-NUigyJhxlbJ2By_l9!#sj8A17@q;u$s|YVerZ2*bu@$Hg zh{*jEk;-2jM-2_eI4YHRW^s^mUvxKnzMs<!{ojFKHbVV>W){A$SNBlaalverc!<{S zh^W<P!eb~-lB=NJk&YnFEaExQQz5oKe|e@v(XP@Dxm#h|h23wS-PMSJZn2+{{niyv zPqQ}P;E3qB$K38Lge@g!oSgrAX(iO6G{S!V_e0_CPCzB=qc~<ypl3O`c7S6=w<RIU z3q?p=|BqSgU~12D;*v5%jB)97vw6<6Iib~g<4>AR%|TdQ!LqN8>GwquT1G;AXslkG zygCAkRWZu6n*`=`9D2c;rD>aAcQwsE24>V~(Zj$`g&Mj=P}wNQZbBPD?D>&8?-Sh* zD3ZRSlT}8mDGj6Pax;9<H>_Fw4H@;6=ERXD-5b=2=rb2|s#+`x1Tk~4vq^=i8PMj# zZVYIOJl#@_i0&41&s*tL4OE|%$6=#&A<Kd>_7nMFgg{F*!`z9o#vxq^YWHv9=jo;0 zP5#y@l@E#u=IwT)cA8W3=B;+~84k$awg*KzWb}I|j5={vinO8te?kD8^^^p~1waF* zDooC72$UNV5w)ZE<+CM;DR3?(QqYN}2g~E<q8Xq<5&8md3|xpOSJ5j6PjE>QFT|Mt z9`U6*wEuXr>P$cw{M{}sY=}OMnNH{Y4CEFc8<!tObY%Wfc~+OKi{Hx;MSFxN+Ev99 z3VHc7FxjAxWXbrW^+$Ybq}T7NPOpI@0S=QqpM&fTP>{4U@ZS@>{Wh5XvXUMfsEcd= z`|TY3>t-ItB<%tL^eL}as3&qXIf*o>qPGdVTYEDo1SbZF+5u2g!o?fzq>kN%n6QJN zo1pkJn(I*Lm1?)(MA;H)>Mqr5d3*&zU-)m3H5CIaFszPIL26paQ);L7wzvYi3VgXC z0GYgpuQqQ4FLB4zLCR^-&bhMR2y`I@?-m=zZAomq$QCjoaql7JizfS4K>P*JLz+e6 z2s*O}k3LQmKYr)Chy0eWbe|fF`eI*28&)8zXbTskhaL8ghrek7STvA?BG#~^z(Y4m z(>%0Ym;nA=NEFChfNnl34v_dgu@rVrt!-1USdXl#v-@soxz0ijSWHkva81ip9D{dz ztP`}Sn2yyi$lSE}tvL!+1X?US!3v?{s_2~;EGc6Q&l2wO6g2oK1EK%v+Y%gSD{(7L zpx7k0D!5cnAkT<wpf_rKfnv!$$fZ@#Ae-BCOHncVG6L6n&WKUSZg8H38WGNg+(0zr zs(h68n*&3!cu!d=W$91xgzx+X<LM7mr|!=iN9`-eSED5k+w$^wCm}@^enPBoTHj0f z)hPj7xJTxzwji3g?9`?of^dxbp&%mJcqn+G)inr3C`BOOz}@sWEMxJ<j}oM~sOY_U zW9KjWUBA<Ck=+LRm)wO(ls-l8D`QK-zR!Gd?l^mGTq1Swa1zwL=DFhdhtJ>l3K<r2 z(_Gm9nd_BgKv(*90B-4Vd)sT$p8y#W8|8_|m5oQw>)eUYsp6+_q+_4E_3N%ZfRArY zzuOgpO}Waeg+a!onZLMv5f^jjfZVr{#czFthZs?=o=p1(E!Y@6^8NFVavh{m2lX#R z4WHR{&GHX%WB+E)tVgQ~$E)g*XNnXKrqZWC$tDNiuWw2=;5uy(iF{bW58t6V?d01f z!L?_oDZR0&W$P$Q+QZOY{MOVt)zaCvoY@g)$+RxI-tB|s{l7B=GJ1PHZVX49%?bml zwAGx@ej{iJ;~!>|eZJU3`%TS;=^;;p0h)fb3Tyy-{UlAaAtsORSFd`_akx$ZTJ8vt zl<6!kt3pojgsingTG;C2IU^yV4jW6IKF*(x?Jz8xu-r9}wwuaOpPG4T3?g%)LZkrE zyhtKr`wXoOzadan5ABfKMe=6g3{-Z5V7g#NuztGxs8eHHX@Z2E2ADfYg8nqh50V%h z7tG8vQU_V4UIY1DZ3($Wb;N7El#DL^n71(EDTV>ytSq*)nwpIGL|Bkr^yiYKb1WC~ zF`#*7DZS?fRqK{RhbG7Xl)5PP80f)80Y*E~whh>jy6<Avq-;f~J+eYT-wo9;ma;At zQBQuL_d~vk8F50e@=!_K#-;eE*`l$EFe>ZR(yUTOdr__PMuW^#QsqYRf7G~I0wb#B z7c0q0ags%P<^>5AcBKbPo>7;w-{-y^d$nb{prJ{UAYlHf^8gAaVZZGIqRQy?#cOQV z80tYh#n;FK(0F!Z8blezs1KHQo_;liIxci*F+Bn^F--AP7C7JBM`a#m<Bgh4{^v3M zeN_K>Tx14Pu}kOGH`IiZieEJf*wAtzkz(>_Nj({=KEuTv;MNXw2K!HOM3LMtHj^+x zB9@uX$v@ny)S>Y@YnnYzW~jM3)*eMV+4LJP8}b<l^np7%GRz&I(L>v<m?TSXc+-1e zkQY{W6JC_!f>V#c7i}UN%uU$+@75CJ-mv~2E{88jm}1%e{Ot6a!5|C5kFTvd8!;%j z-QhGPsUh{?O<?65Mhri>e;!~usdBY6@uR_~in#Da`5px+GlbeliU1yH9}!q_O6eSp zT71wAM%va_r8{N{E|G$<S@)n=$hpZAL$d(tuU5*vfutL3@bWnKJ|m)ik4(3E4a=BW zYS)2GNP^t&{kA56{i0y#61$6=A5Q>EfUyR*0l)1c$nDpwI4u%@k*>qxw>@(5l$3kY z8EzpMRqSdEYDOy2p*2;8s+NOZ3@OHSlG_PgDS91y*uHHGf8#N86?lAzISooq*itMq zba)0p7bspxJv;Dm*(~eC`swE??np?fd^HDfJ1PnI%&;11eTq~^?U{AzPx3Hv=Z|V4 z9t##IJ#=0B6j7z14lVNP*rvLQc-hhOH_%~)MdA$OKTd?^Rkv6r%#(}#8C*&)9h!+t zPhJ&_mTCEP?df1pGkO?(R%81~*9zmDmyP?x$O+5?kE(dX`vWI!K}nvw5*QP3dAHEL z?$q)f6)lxLLNC(w1Q9h}ZJ7qs$w!L(ef?ul^08k0Vp3LpxpvKI<*zT}+T_T=SN#68 z@9O8L#AUPl0kcIDuQfr-I(CuEf6KXb&_WOBuASwIm1RL;m0@20nLB<rTLze)_%$nj zvTFY31N)7hf1`n6$C2~4mMjau6?m}%y(G^YJqJWGz`9YlfX=Il#Nsp=iDb&l)g4PC z?ov^g9&}B5`k%wf0guW0UtxeK2i!u?bV!_&NG1z!>9LlXDxinLBbCLxFn4H>vg7=% z^izsv6idJ$JT)}~&WX5lc9jy9P0Eac+QrbS)EjgbB=%?v;a>gp_huaCu*H(sfHZR- zOoL6lY%IM`BEBRpQ)Di%`crO~^z>dNxWue114dMz+Ap`>+um~*Qa-$jO=bdVSno71 z?Tc>~#^F}IzgraOL+)%zE3{$MN{$3^qyW~6DKs59tPB!bURIu&E2j;1<CS@O9`sC= zngcJZy%1)%XB_@YA`?vV0GOKVCfJ~$0l%c=`}UY)Y#2%w@eW@IJB&I_h{M3qL1vCm zcaKG%)B$53K4K1emP|-lCKDn>TgKxABgM^}rR|YP^L{<eP?WD7ii|1MWaqqauwtxU zk!XBYG>*bM<Wsa-O4ks%9gd>%VoY9-7+n=!`K@V59soYv!_fT7u{bclX>_(6PrB;% z`R$k>aOkTeP*_ktJJD}#fSF7zNxVf8xP;{t9c%GE=qL4`l%pP@&rf;pGxZ&<Sv^7V z126xTKK~U#!IjRyd#-Bx(FZr~uCbs60CH~3Rnvq)xK2JTG;Wjo^Et99Z0x-_=rpu8 z?;VqrIwX9&3d>K22A%}xr~BrjnyV=r=uE<#Md(l=%ER@GwzFFPUe9O6$w~J}?(@5s zO?DhLwTmKM9+8%_kzW@%teZF{i8sqb3)tfI)e1gHFGqf_z`{BqgqAK|(n)qLgod*{ zLytCyVm}huENqd1e9T^=uUCZo^DFtf*Rx@_$;Ld>E;^^zwVS9A0?3Y&<y6?B330+T zd9L-8w|om(LuHcU$l_SBjkU`@O0zG)5#7!*$WlnHc#Y`m5gzz;KCY+Zbnw2C)2n(# z<QBFBKC&V}5P^o_IjYJ}vo9-#*gtLJK50nO31KFE59J>f8a$k*1c~&Oq^M7ffVYrw zp3hehz{?wD=w5%?hq1(@Fke*q4JokHE`<7cF|2)&*p5*w2^(wu96o0D$=X&_#?;X~ zbbazp5g9l0G0D78#vj%PstQbiaOA8dFS%>q@4${N3$RCyw%1Xw=hmzrGJkcx?{}ze zszh5Deca2mZj5+iLBW%Q<sB1R`@MZJss3?2v*vp^UORC&5{Mv1X%At)N<?5$#Qo;$ zf1-<BE=>f^|LyWfsem-2r}m8mcoiSM(>#7e`iwwrOS(!C4__-KoFH-*YD`*OdL$6t z_7%tGjjtzgO7~~(XR6?ZE!>AdGt05=rH%U^FFzn}JX~*_z_noCMQ@IN`k0xRcyKd) z82DOp-p|40*)&M)H`aB(%iX@Gqa6e?-uHSv*m0m+>lWt-(GXocIcdJU|JnJ!=1$-S z2d#69+^TlsVcl=f+=GZ5q<V#{4%MtTlROo#o;Dl@Td7NUh6ua+`$;vHe~J}KG47tM z5n0{E5MeNaNNQdT7Ife~j27zzdlmMcBdMJg7?fs{-!LgW!L=SM|Ec&~KSR;fshUNP z_xv0<TqZYC@Vv#dp^u_{#83@B74sxw^snuDSfxz>GHX%ioKz~x6=IBVTQVe=-DfDC zJ+bciTS7ofXPp|5(rrvf>cgUGp<SyAUr{z4TE?b168Z=cS*bxk@z9VR)N3S}vpy;h z6nJ36yZ#)=Os7Sj%%7>%4pJ|KSRj{`O|<H29U~QuNMG#e<e({YX^!JdQ5jP>Z;pMI zqM=ulF1{qTmLzT`sikBtW&7rd+@4;M^ChXfy)`ryRhAx@99l7A*pdDwFJ%ne%*evd zh!ipOSln){6hZp|EhYU9l=W8Hz9$&YQIni%zLZm+s`#h_3==$jOX`f$g?Z%9UC*Vv zFhjn5Jj0uIVa3QH8^et>M$E}j-_yZ9bT!u`#7Pa%1i!Cqh6gRWf26e{Psab)>)!&f zsTlTu-WTX11Lt3Q(|l2{fPhkduK{n7D+Nq3zj#TP<{DDr?0})DJa<dC{eIxse1p}y z8Gl%Mi|Lqw8z#Z@wT}S^O*moXlmTT|Nz)%OAF(i-m3VL(cS-kFqYfn=hHU))uN?hH z=OBbByRp<9{PV(*j9X9GSytx+C0}bDNEw5SA>VjockV#A6FHM`RObWA?*=5ZF~-~1 z$V{^JY_6tJ$a(iO?B)fC&^{cv1`%0q1jblBpwe?!fk<?An`^8!wkbxnsiv7f%+<4P zXzukz=V&V%VIWbZdzVbar)<~~8wy2EA5~3zn3dspQv~=BengDqzz$B*8vM>lhq2zP zQ8QaUk84pvg3MMX{A7%RM>m*o`fD%?ss)V}aNPph!_%>%%Eb;0WyS~qiWS=4Q)3nm zkZ-Wl^jdMI^(i*rS9N#}ZM?3t=q@u(!cA)q<}cia+My``b$Qr6^BDmlI1M|ikF6fj zxqs>0AcTTyYnwtAp9wYpcF9)q#_Im!E&A!v7GRzYl1-KbmP?MyjKsjI#`^3W9g@ke z?P;4Y992&eXE9={%F4Pg;XB_JU{G5Ce9_0a%Af@4Pnn5by*5R_VSrFhvZ|Iv^w(xi ziC?nX?>+UsgU78=p8MvD%f=AB9W1nMU-8)=56J(}qkucl+xF!=Ur_o|&Fg5}XUh<{ z{$m~EZt<(I@c!{BuYE=!1}#psuNSXKTZCGbKr^ah@d|lHcb7`gUFXb6Fyn3i55_C1 z;mv?eBpM!IS=ZfM?Q-|yVfS)05zA2z8jS$&(HlqnsIM2F^B=|nZ%56Re>HD7U9WlN zUs|LPxf~d8+@%K`fb-p^Z6HX#Jy@o7c)4xM+iekvQQfn;O;D&8=zDS1NyiVtT3+5< z6Aa=q%Wj1Evpr+y?!qA^K$(`~^6c2Fd*vYzUFUL$A#rga_Sst3me&!x#<153qOJVj z<06kGp!tn8amW%7l|6vouS?v42F;2f&d|`UQ%~{ZDDMgb#j?L5y`5KhHBJ}4k68az zif>Upi&)MzGxHy}_@E6Ms^4nZ7>Ml0L|Q!y9X!mfcVq4P|Ld+X!_B*zyfK{qpbLa3 z@@^v4q&J_Bw0f}<aL_K)DxZq9c0%QAemHk40ztW_-^@eBh-(SESa(^W*A$2yYN=ME zNOu5F#jP5L>8zDuR}yHu7{w*baB(xz-EeKrs<tH}Bds7YFW^XhXJj(?@OpkZ{8o`K z01yje+yzxAgeEDU1NO1Kiw*K$&M(%Zs45hC;v}7J-$POUM9F;(<Gtch#;Mc9PGTYc zSXIQ8i;S`t-^5|Hs{SppzD_{HOndX)bMj4{GCAZ%WiYS!_W}(J^aw^OO;COGMPs=t zn#eAD<*=0vEi?>}va6$~8Ib3(h<C%rf?q(Kd&n2K*aBkb3K2#XW_l$lC0`K{Da{UX z!cu(KZ)JPYrl}mq#j)>Bf|$EA@|Ls%>Z9-(amZ$zaeTVmx=S0pd`0dsTngBvv_D;i z#HZ^4WCfFEl8B>Y&4u&pr>}R;HkPfBl%!F(d9>01HsLQj0h_bg9!Wy`$>@k%X`ds* zVY7ABPaum(h}j&5av7-@PP@4R&~n9kPSX!)a`kgEb_N%ti(Ye$e_SYHV|)KQVfC2K z^~(s!wM>J(n`02c|D%wCx9xy?zt8#xc<)_E`ux3)ipb<2FC1vh786v)a_(I*X{9P> zfogN!d#pvLB}1D)Y#D8CrXQ?RN>mX=e2NeFN!mE+Wqi0cF(*M?Dg<&|o)zqW%s+w@ zXQM1~a6FT)?Pv4{niOsheAd(jSlOjaJFvlNF|>`ePet*CP~Nj1XHhDvvE<Qr4z{Sq z93=B0Vw|(jP6ZjDQitCZaLwD|2k2w5IGWf7r0ke}z}&vk9ujN=uq}<HR7nuIt_ZY- z!m9^zY^^!FQ9WkIl`+O}Y5f=t04a&8>hD8$D45Bi=pX`jGcW%Aic>ZRIBB{*RR&Ax z0)>|YE`0Fa>Y3Sxe$+VaId7Cr9c*)3Rf~a|Vw24JD1{Kp%~Uc=AGKDcl|B>EAXmHC z!$t7IV2#VsGV@?HQQzDoOK!v%hFOrAc4B=6tEKlbeOOO_z^F<(QZC_nO{@j$$WYQn z6XIGD7DBO|x9xiz6*$POQtmJ;G?%@W+hiJ0<u_CifAN`OK+!~%K50GBp%=~2kXPj= zH4pG()LX?HbaWEdh*cy-lKjh5Uhr1tQs6)$t`th{i=D?+8_6$!&Yvt6`LDRzx|`y8 zxe<s>_2Zp3U(A||0@Kd#w&F6}!LMZ=rl;wpg09#7YCHeh$2T6Yn$;tY{-#nPWpo_9 z<#yk!sC-#yH~~@*xZkFESr7c1j_OWJ693s8lQ2rY7IH750+0h%ean|Tk<x?F%)aqI zCl$5<ikrcLh+~&y5yQDsWb`*?X3gAvTuj4V3!TRv$8UI5_r0wB{8qkw5Zx<cdMPAy zbItBT7~p#i?Fya0-uOo@)kLD~xMCs~FGPjB=i{q-(x`qN{5m2?tcZ-t+0WS~=6OlN zezSD2FXQc5U-rFkxoMsAG9&2cZ;XwnI?^N}*E0qrt-#PH6aRu2)Se@XzXNXqMUH*% zMk2=toX`JPsI^Ft3UAd;T|STNu0RMVIDa#g63iJTBAKWW-t9GfOEePbT$`)#u(xhK zT6TO<c_F*_z%@7aOf<k=Gphq05~LU|RlVMnVi-q>OtLV;h0CFYTy$LmNT!EtlGvCC zwuQy)JYUiIv2)M3{}LsWAEcQEem<#WUUzWZB7a!-Ccj=lYk<90A6l|3@P$;0a()k} zSbsVUdJrhmkV9g)a>dwVyjHU7FXGc&sl$ZQl;yVsV$<58r_7L2CA~4<Z&jZ2M6u`& z2B_!080?DsDz9t?y0>CaD+v)}c+Ly*7G(5Ea$jRZM1ee)W@2%JD+nDJFs;#QNu4Iu zIbtfZv!H-v(>__dkq`^t0*d)6ss^Y@&R<y(jE~1pVZ?dH6nsP#whuiQV&as6(XD~v z=6>}#>3YOE%{e`rB!=?W7>hCFK8F2}0>81JHEje+m8HsyFxg+_b5N5HKNauMG!I`f zr!Z+jQ_ZjqD*U&{amAP-xCHe-f0r`4i}>ihUuusXl64l!+0iP7suc%TvO*%QKUsq< zC3@>`4Nw{9`r)jzj2#xg-1S6iw$E}Up<5GdY3vUBpVs~FlmlMh2$d55{YOTr<U74c zx0dAUXn=blgCEdrlWY4Bi-#$_oUAS`2BT5Kbr7F<kyoPCp!_GylEFTM0#KS1*!L9H zj9xe1uXJ<ywJ_&8{2vwo#}>n`J|;NfW6LLHIuv+0p=#@1TRQ;CdWX(B2G=q%-@sxY z<v6`IFFlxgvQRnwyi$5NLPD(}Yjt;_4D|4Dh!&O~4)ayFz_|1HHGp!!oP@EO;1Ju3 zSx6$K3~g*NMWr|yBl^mvSYX{xfdq+6{bu=t@zxo{0U@bqVcQxS$W=*xo#tAjxi3!p zPFW}SqkFx+dPhMorZq8y<T4BF%@<~C$G`TG+;5N^P|94FH9N1jpPz~Obq(-!qYN91 z%TpbY$w{cd+1sC%&gTDoMsj-yJ&X^M68DEk$p_7bRIJHqdwg<PuwrznIaWWQ23aZ| zlE{Q<&kk67=ap_~=fBARaghNjlaP>Ko@F5kQ_igeZxc1knbpy;0m;!3Pf6yhwkgAv zvGi*KXqJIPMiP#5pZ0DZ+K=bwgx<<vt~1#Qv<%k`4{@|SH+ZSI3-Oo%oom^PA!%yr z_c^io=(BZ5+=%;xG)dXhYWBjY<8XcEnUt{b2)f7ol6-KYecPR&Lw|#XMDG?qmRa8m z8XvZLbK8JI8`y~7-|fNfn*r`4OPW8Ke<MNNRdrsxc;Pw9+5ARV3U=I021O3L>wVU> zX@xYvp&Kl3*brcBY>cZ}Y>6B<eddcXNuQpN2<MnNUR>yYyktJr0iwV7@na*g8Ra4u zOPHxH1?mIRFu^cqfv?NriSJ|<ts`o|6)dwGnd>;Et=}a%P7Npid!q9|Xr;AgmCl{` zEB7C9DaWGg^H-n$X#(AtG)P5@$uKz4Ca=4OQ`Bb$p7ZjmqcH0&zQ@D%gidfaZG580 z6GWj0-`97jtA}N~+^%cbxP3v8&^3Qs{O}rGsMYM?r1>@=iKdQdcWnvs@%|rmC5-S1 zqz2)4pyaWTPV&_~rr4J$I|PosQQTv}9Ifr-Z?5l$G?6Cqi^(dP*!<Ft#{5vZFlSRi ze|+IPUyoA>rqnFgHXNr9NC_JlhLIL4H0eU7C~n2$KZ^xB+D#*E>NFwqq`^gt9pc{8 zjQ(OIex0TZRTNYy`+Dr?y!g9r5F-3kl#Gv|_!=1+H2WJP_F!lvS)-1CWH-Q|h>#GK z4Mw(voX#lqCfnm%$X(P<A9Hjzsbx$^*e^!lPV@H9J*Ez~#qu4S4A+(%X{t>fD~1H_ z<XQYpb_)XWly_ed_&s<zpg>o<y61f{yB+CTE0&h-`L<|5xx+PO!V`1sZJk*%6hA38 zn$MGvY`QL`F`_|6C*GQ46SIYBxbs%tFuruef4-&EW1Rxf!sSWSe(IgkS4vtdRFy8{ zqcUA8*YH}n_|r1B01B-maB`M<Mn~)W@we~Kha-5PA0+Mf?|iIR0Q)^Qzjm+%!i(~e z{Kw7U;z7$if9jsT2*2L`E=WVIH0=^c)tvSFQ>ImVf-fxiS%7%t37hmhj^d@XrpsI) zOJ3jiyPAJ|9shj~eBy+*m$t~&-ZDQWYr=g}?l^uFJg0qa&T_l;nal=&Zi8K^4Ki28 z+B{IO-)R}9q%k6@3j#>l#ML7BSBvK8jVPx{iWxg+0~U~9%I2eh#Jd!-SDlm|Eyy%} z&v*hp=+CKtsb~UG93G=L6|dp$IlF&ALi$nJMw~JiB@=brf-Dsa!6Z`yXI7B&2!44X zXyD4?84{R5Dt&<DjTynYSk2l*ONv3F59iq`8puLoY*TzwsShv%K<;>cRU|PGyHu!h zAW`VR?@2_{0G7If%m6DU1BXulHgN-YzRW7t*1YE^qTxtTaLjWyU%;MKd%x+LL_G!y z8yHfCer0%j)l(EYZt+bX)~>_!6@dPFLNSt?!DIc4nv?|tz}hE-5lvMGV}peVsy@cl zf{tDuSw!6sMIvPosyy13CNh}RU(hScMOH}6=9<{>9l$2BMY`3@=jJ#Lw%0=uCaGnR zZ&8VBl%;GL7XxYy*uS-3??T_!nG-_27-(7UcYoOfpDV*Z!DJmkg3rEF^W>1Q+<@Nb zTnSWskI&?Jra2NYFUl=t=abyewjMhq)%-?i{!g*#b=PMCzqgAPBFwJ@=nIy*>k_(O z<GiaSTYei}y6}0QTEK!^)Z-u(DKRyA=1a8=Y|n4PZ{0Fc{3;VE0==<l;^nGKsO3_Y zWRvh-dDr18>e)g3%XORB*jNof&8qu^_|@N-Pl@xaiN)sb2S>k~?~!YQ?Zi|Va+iNc z|G*Yx9j%o9g3M6l3z+}1irJmE-|h>ueJEJNL-ny`+X5Jj%v4&sr#`VXJw5$);=w|1 zac#{5ipbJ7y{yo0b-z5>TJzd)q`BWjqk?Np{B#k1Ke4;AekF8`jS0PR2-Lu(h~4<{ zZ(VpA#|b?#L=z^V+ZLybWSAt@iuZ^%WxhFJf7voi%&i6BGIUPJ|8o1le=UM4oJR23 zN*6+-6#Go#wm9KkeyR#$v*?j~ElN!&B0{Ke6QT?)8$Wj(sV9O645Vh@*gw)fE&Vgz zF))N9l_t#nx=IOHH#VQBT{Y{Q3;UqJIt6_$Jfb_-poI~EnZO{}f1HH8$NLu8=|Ybu z0y{WZYQ#qqra953T&*|t!O$*DuRO)EaJREEVQ-f`+!@-?ax{@yX~T|1z)_~XhnWFI zjXazs<!+c!7zNt&n?%r0=IEcNzlmt$LPL{U(G_JfrND|Stpg5CSS~s}T24r>v$d5Z zq}pVrgP1&RwM-A2lvNppf678tR&?xNDoXSlz0GT|QHL<vH0)68DVb`1Q7OJ7oNd5x z3!)$^mJ4?uYoW4<8Msb54+ZWiwN2OwZ7#F9m-p$B*m4cwz5DQ)(fAvm!rePA%ZkOp znRMMOKQdeMRo>hO=#K0+h;sr`ZNFks>o}*tVct*s{zTI0fVAE0G-E0igCel5|8pKg zD!7pVh_(c6JBoSJz^G=KY;rb9zO!+;<wykfdhDmW+nJaH*t#`iG1zsW)Txm0{`Ctw zG)2Q3f=<bYao^OpfwMZ4i-+*k{J=<!b8M%q6uepP!S&j?nWG+>zqYggdrliUpfgPQ z8i(`eYGx#TV^Z3mCzca)Uf;uHWsrH$77QJ9-Mqe-XtGmr!*y`rnn<4Wq?>s_G$-(8 z9hQoALvtAyT!+M51Bpgb?wd2ZO@(L;5Hc;(1y@kq9$;uM?{r|vfx@I9@;!W2&6Pa5 zH~<J;aw%{Mc`+N1Ne<c~FsR_xB-mtgWEg{YMs`MfLvUXG%n^P^u!&e#E82I##K%C@ zFN~}TnhN&i+rlD2&>i}1afJtNHrKzb^&|)al`tlg!EYb{IIRK*S-wL%p;}DUu@T9h z2%&Vhq5+;89nyRt#G;D&E=cgqZQ7PHp9l-bLUd`Z%O(vMTZz4~93-%9^l{PkQ&S3W z<8QL2YRYuuEv8JDNZa^xyC&JO<U}2LYC}+L>T<}%IY5o6-x%o!4Sstu41gw;W`V8v zB}$jEmh8Abf*|pI5<T%YQI(cTb8f>x4sxy?pGIcyqC`igk+l>7rXbg)(SV*4V~ueY zB1*QNYKPR%CL=NDxhp{&oKf*A$B&0B5ugc@BM23#O9g=Y-`yRVLhhE4lu%?2TNMF* z1bZ#(sV-CFc_<63ELZzq4-}hP!5)CU9~8vh_oSyw1wRL=JjVltBewMkMUUf8vQ2K_ z;TLhyynB5#k9E~GHEj!GL$dIs_T#ouiQmbIivibb3d!0_RQ$Ag>#M7bsru40DKCRR zA!`yC&-!n)c}`vbZCq9B9X33G9F^ELtF&QQsT}zLZjd5I75(XmSWfD<pgqxz{e)*J zNsK6XSGPM1mwt&UDAeAYKw|@gMKtMg$(+a~i{6bKW9QB}r>@&~q6i)J=h|ej91nPp z{DUYqE%4>Vo5Ne8!_++Ye+@?+D1rJMwEC@}zA7?LB1o~wtMs@VOGQ%Ujw-YqDKLZo z8TsIgAEJwm#d5iPJ(8ok=rXJC6j~$uy?|1zd$pAe>ZB@;apdTiJ2kfc@-uq`x&m*1 zD@ML)6CUgK`=4uok^v#GW_jC|0xKq*v4)~&RYn``Xg3wUqaSqMaJ(x=MsthlGn&Ub z)yWEXjj1%mwzk+9HmWn1^y+H!7gDRMCM3k$J;RMFCtArZ^uX!U4z;sGQY8UK*}XKH z$JH-eewU1(i?IQ;5*+P%fJMqWtU3roRLE^v2RadcT6<%rrOJb&J{uQQHyXuXPn8#t zUhqUYNmPskb&<o0y{Eyw3bWg{jq0GJ<x*N07J+4C_Cf_A-#sbn=J=*5I2lDi-+K7b zRvQsM$6>LGekQFcJ^Iiy9D^d|q@!3!MYM%Kh(7%I3NBLt@W2Blq-{}S(%x;sh<&a> z4qy-suP13l>6~t6WxB)a;Bm#{k4359fo?xB$lch{`Ju`c)C-5K8m3i{Y_s<4CHA-b zIDdOSPK>Cz9KDZVsc!f2;1ct%<XjvpE(8Te;ZkANp;*6Y(o#+{oq(F6*DY#Hw~-&V zQ%X6y&QM5UV=kfVlFLE=ot^&QS8rd^q&>DEJrwGUH(G#~TwGcLp%dQ%hfj(-h!=)B z=kjiuZzVeWM~u*TbV(`+&@Rla4)E>c0Dlyns)4Z%f*}Lw!yQQ@Z#|mD*)t(uT+H49 zgF+ZRX<Rf0v)@A1`e~H|7q-YZOH<TCAPjv9uaE6PQLkysg8^VoVH}@gTmydhjZqyG zo$^(i)b=JLCk8JC$_R~d?6n)CFC_K^=T;9`P9j<C4QrSZf1bDB-vRbdcm=f9dTN*y zccWc}plob3_~-bdiY0!SQdNy}bYq3yAtT4+;Y<veBIu9rV4D|NDX(1OG@&IRfNn5U zRaEg=1jYP`Qy+)CBnS)0PESs+SH3dOwaIwYh5NHA@{MmwrbU#hclOIH7VQ8Ydo??$ z>N-nRRj{QhZ<RjCtQY}xWuH)i=8_u@j6lC;kKGaL^jB7sVc;B#oxP4MOyhE+-c3Vk z2O_wW%OSBDpkwfSs8XD;&{j5UZTtCQzdp{_AP5FvE$Yuo{^Bhs)CJm1T0qH#G0qr9 z%4>z2qr(LyuFoERcc(g~T$4Fl?CK5LpBN@0Jd`F8`@@x#3RjTXo6-lgFb|?ao_i`i zKd#UIb0njJO-tLy%sXePHrnp=Qu5s($wtkU-#30+ap2**ZrO#K-+yL^cn+FYoZJiW z0@B}Pt?%VPm1}**?Q@H=ny~O$f0R4>Z&NgNbsa;NZnyU&W6ypih#vDO{fIWSGsRVd z!k!`QlDJ`o6X83iu3b$SnoL3*^*4%58m^xW>ic2E*SGa{Z$LUqGHQc%PtvH>h0afb z9h?Q$hkwlk7-GeHuQzIua|v%W5IWZHoI_hg<Bus~^G4L=W1f%rC|dsBwxg@$pHfSp zxGaisNnY&>@tpHPi4TXAVppvPd*<B}JMGXI(C9%iGsRD@1Lf{o5se_0nC--d4+&En z7~_UP3V9>l!1MFTR$r>Q_a>K>3tE<wt!;rosrl8IZrg#Iyehm8mA4fnPwSL(`JzKl zr=*#mrJ^*};&Oz@os8n&jHGVG@VXA~I)FbDUA$4Mai(Jsp;z#zL%HWly)mI`W^_(S zR9lz*y~e-yXLHJ*MO_PO;Jxl9T1-8yui1BX`?c5+J%#fN2N!kD#-8NAcbvxGJMOIG zByfI00c#V7-N0?Oa7-hxK!tgT>215v*q;bUI{9yMqA^_;<un6)C_Ab&N_xz|W!x8Y zCKfKqldMcGtKin$_mq}8z4RBa^_tnUXWlNR7XnGf8M)ZwiJB<6=?SKoT-H7|_&~OQ zgW|ya`(4jhX1TWMfWhzc+3P-GaDe+bNgv83aM_{VWi_Z#jO7x{zeqoSSFqL+qkF}Z zsq{d1i!Y?F$U$pNE5z<IM46LJrkPI!(1&&Xlua?6P!y6|<PICgtkaWXYT3czC%v*H zWP4FW@5^WzA$$V)fY~batPoR%WR*vP;60k~g^>!4#Y&tHuE<{XETcJ;O@$4nZI4qh z&88v(YIM|Qs)<9>`}%Q#3{k>7*WH={YKL9a+Kc&Us=BE8enM@ZC)`tDAZsGiGfN}s zU)|y1YX1b=%1KgvPCx$A;0^PXwoa4#Lm`kW$J3MJ6`qZq7n<MX$w9Vd&hLWqUd6|? z|Ht2inENdmNlit@N?sv7W4b=Ixc$Cr@bLZ*;K`+atMp9}u!b0mt*M+~vY(hI%EUH` zNUTU$&H)U=t3~pyNIQEvrw;UZCr;+rWZ-`i(}MAikuHgpP0v7jo~y7{nejGC4WL!2 zcm#Z;d#A})U}D&~yG(9g+4W0X4FR$tD~Y(-1gR1T*+40=+_|8oLT;<SM#3J=C)I#2 zbfb99Ek>dVKot%4bsJpZ)1(Svq#$E>afoY0YOjB>6&2xow#Pr{w@0oxo|Ixwu%8(K zy<KQ>q0!s`74RYM>(QqT(lISC<YUMCasg5N$rxdv2n&Q^3+rJa-0{VsLGwcW@-*9F z4<wC+=bO;7&{25P=c#%}Ovk4Bw=y#HI&nP2isg=iFgk`wRn-rF%!!8>=L@05Bdc4O zN`XHlY-z(=#=LOJuPkG9AONekq63#6wReZ2LO+T43DKJLoa*kjKm%-x(SEFJm$nq` zAaZa6cLlsLhalyMFX79o-U@F)dAZ3V<;uMJh<J%q)T=oam`0>yEabzR-+ujxueb;D zeW8?<sXORQI3d-<ejk;FaJv}tZH6#p993zEci;^uyF42{!>PDD#W__A#V-SL2R0~6 z_iTG(pK5((Jgy@=$7=&NLzKx5FRj!tOgdvqtQ@;HUJn0Y`{=2IU9~4oXbqCsKkv~4 zsNy<tcfGLXD2OrDC^jkYE}f2){NZh|xyc_;Y`AcEISK%yC0<XgOUK@vAUxtk(B3}a z;fMPbk%&(Dr>28eBKJJ9KbV^1{@u8ffYGdkg?hBCheor_Pd?E2yob=IcZVB4sU2?a zn)S?=LjUa5`UpzecVu@nT1<Qpwf>-P{X@Slu(Ce-xt3d&<9x!U=*tZO4W%y!C$Jv| zJkP1$H6x7fUN}o?V=fSws7?EA7hYiC{n_BJt%2A-o0Xw|E???C#JtkF-mT;PN%;^! z4E~V$TyVYjih_co#J&tqX&V#SziwEl`*z&<&4%^2T-SxEfXBJoz}+Ke^9HiBgtn`m z$NTGT>tGQHk5GeRRbD^Isur2pPQWpr<&X&#VY}+tL@<h3lZF}nuCuV~$jJxDC%_zx z<+2*3!Yw#cG~QYV&K8W#?Wor+PeH`L>*Kn1_`UHN!`Z)HuWl?-)<^n#uDF539>mXY zbIHKmSxYDIBc*hPn|ZQn?52_ZJj)k<y@q(%GDqRK8yHd`IxEwuPVB*|ClmeBU$pJO z^05{7>XOD&B<wj8yZ?*fgqnBTWJ+xvb`<dYX8`aQbCYS1M|8|SsZvx5BeUo4E*vGY zC_}rE5Z@l5;y^+xdd8VjrLVRN6@XeDMKQ?7c8!Jt;1Fx!o#7voOCA6-MP)<FM2NRK zAynDPN+{wGiPBlG<1KQ+xB*ALP4JsfMdUuSYjkgWd4X_FU@2(lx3-k2Od1^)fod|G z!PYf|{TJ2F3!#0MLWmfPk|zCd0g9*6Aqq!77F<Z$*O|QTbVbrS4T$wrHHS9V+Ti~p zZ4oQ|O^1{W(!nxm3Y~Ol%@8#pPA(GQpd*k$jsWsm^!O;#=3_FFYE<>izL3pqvSTC# zs9(0^Bi(&ZZ$lwOp@3wNgE9$MW_7l$S9aqWNn$bg!_S4*)@P&jj7(7kJU)l3=bdbx zW#s?*KUUm-EV*JNSgaCS91O3^KV?I5vXHo?V5Tu10M#=$d0K!AqeV3}Dl|HRhdBN^ zGq}-)3$+6EOr4XRVIjllnUpG@t+1pI`U^1n3!N-b*lQn~mx}4+W`U}4ykS1J_AS93 zH}v13=~@D~F+$3t=IKq2DWF*Dxe>O(Hg8o0e6QhZ{Wo)Rb~Pr_{h}qp;fo(5<4i){ z`#g6^-6AI8PgT}6W`9|YHKUbSaj1&4ZjURBQot&0B#P81$TUyZqn_3~N!%-N7$;4` ztL>jV9MrGYk59*|9xsz>vF?K1p-fisYLmDMoFX))oy#9C{c`grSh?O0sj?BM<sp}* z^WzO!C3@*sSy+|eR^dkjPySwz0eV!<LEXe_${AbjpV9zA!pJJOUcx^kyBL-rGSr{8 zbxDS+KjCEIR=zU4u@GT^!74Rl1B;N%ox|#q2d_@R+a(BLuslRJOoQ!AM|qP)4E#km zqBw@v8T4qRUhN7KcEecmfSZ5#Hq<olH~(6u{*8qy7&w=i0oN9zfIY*j6Vw^#P+U)M z#dR<Mp0N;6;nCJVgYq1@nkil=UzuQ=KzQ?P&xS*~OdPRTdn&_@#3+U=7Kja$F&wxJ zJu^M-n>Joz5kLKS=!#!Sy!P4vLAZS{UM4R84Zrrgm{Op+QJ^IQIx{|$Wn_-yhP)Zj zyFe72lfmAmkH0fSHIpC5byGCx@Ta(gZdF*SD}oXi=I4+88b-Z!E{cDFPJXuhIq81F zID#&W_>$K`<u-1=c^#eSKXp7Htf8LM83V$C+`P|XdNku)Aq=@_8W=8@cu%KzSBO7Z z4UR&9O&>>&JFiXAUT5cr)_k(R|I_`{j!{t5C2{c)@0GFS+{PK=lSi+LxdVoSLj=3} zz7e_XBWxOte#fnY>FMct0Q2dM3B=sf?>}6wglcsF8?bctFHd#xFWYAE(DYiwZ=rpC z_c4LG<10g=7$oTFI_N}7Kf7{A=8|&%`Si_So?%NM%BqO>afkT*-nM-(<_NV)KX%Mg zL|K?>bV`DqrJIChrupd2UWT~F%`tB)6|dUck?2W7(_Jqa69Sc%0_QGp%`RDKs)`n% z=eDhjtn^0snbJ3PZ7Tt0Gx`u<gYdcd?ZiLM`oUQcwXlb(b4UKw`2@)A0(p{P$QF-c zmziy^Zb)1he<m-ZY@1}PHNbzW9foyaSx=tKA+-oBNQPl7bG^d$6z`$wgfWHYhN~9c z(34)4LURzN_NXNw?Ezh1ojM6m?EbhQX_YZgb;A(#g>V99L0#vdP<ZIr?OUrm{Cjr} z9U?|P8Kvv^rfRW+WU(mL$7-@$G=M&jHNlaHeunEFBv;wD17Qdis%rC}PA{h?cXFYY zE_%mU-lhs*QQ|La3*P>Wd>hMW-k=@Zm5$gC-w(C3B*bDOo$I3`9$fqsU)mM75lFq= z&l?^0-r;+cJ%K8!P6gIaMxK;#MnOt4oYtfw`Ad=5^IuF~Wd$!$l>i1cC5+B|73C&< zkHA4PSi}r4_L4&&mA#@xLS$$XC%dji@}sgCJ1-s#gKjvAU3blu2m`x@2FxVh&t(aU zLWFk_O#AcuboEJtQ&U!~&I{G%CsJIXhLxRBVjJV6Kq>IF1*zLu#8Hs%zl#U|-geJO zGdYd79oju@35?I;$f8(zU1B#ts`Aj{eu3f%qYLWqFGm+h+EYU^k^<Q2%d`PgaLEK| z{^)Q)X$gNn;Rb2Y436$(yqgG;8qm#7*POGLW??Jf+am#OZuL|;XQF4Tdi)}5BktTV z0)=do!hEKk|9HC8n%7$$1y$t$dqA~00brbU%OsiQ41#lP)=Wk3XXfmggfH+F!fxh0 zYV<#eh_WJl$KMu8X3)Q~DT_PMM;K4d@a0KtR!n)XKwyNyQgTNxh`7s=LK#SQlUSkQ zn5SSS5Hcwti_39pJz6L4v@didQ-!<ADBqF9LFkBp1t0+{LS2$uEYoS6E|C02ha1gC z&UW}Atq8MiiwUk;%_OC1=};#nt>pJXxXgKUtxNagTTJtl_~aSIm45O|*kuFNYZQ0l ztLT#0I=KNZLjo(}Z-89o=(iP&PZj;FkQEP9%dgMVXg&d@xE;s|7G_D!%R>k_$RMZZ z#5J=>#!fCLId49<+dg+_72oQ99s9>%*N1MN|Cl$8D-Po|TX3=WE=awH@o3TjvUt!p zhSCRmPi&>tug+9QjMQ?0-p2B~{9%%RB)TKDxpm$#{e2tmv%myq_9nbegvH^%JqqtD z(iWHRApr<FX=82RWn}`VBDNd|gdxUsJN1{k<q9jm^U@EJL|FGuUyrGZIOlXvK`cdl zeltn{;B1c_QEK~$Wtyh{c)b90)x21_=LlZljFJkegPh1UfBtP85@+WQ6SvP*icf^N zP1j#P>aYJyJg?g~Jg~u_HM?9Se`;RMzixMRcD*A51c=a&62Hn^hrGV)9#PwlcOkZ? zUtN`no^;IA3sYIaU?z`<>u+V;&@<!W2Z9N<UMhI-7Zr%>UF<%fCpdx5B#M=EiC72P z2%dUGz*wiLTb>Y04)*-rmM6-PDnf4+cNpcd7`m#ew%JYZGs>SgYR<kXqj+yn^*-WJ zF+j86E<tytK?ez1G`_Eq!)7Q2n~a@WcN@C$=j9%HChW1~#42Nny`z-8$Y?Dd7#+&M zzZGO5In45m=j)ZLr|6rhnCtEtk>*HtYzbTGkVskM5znpc!tx<zX59Dnh3-eO;-9@p zT=DIaHJ0(jxg_C1|2zy~J<?Nnxur+@gH>Wa$`vF1YCn@(<jl~Myl&GvN+zL{8I)~! zGTtzCoxInme~%RFRE0zF#qgx;mJFccRrh(!kM~m54%nwk4evn*9E)M{f1vieb!wL^ zqC!RmGOd_A(E~^3t3a{2X1!`BiXOgob~pne#%`l*@hMqTIcY$hw=$w9&(|*US}1{6 z&7}*)x3FuejiY}dOW!b>P#2BiLvo@IFbb0b3lOANc2z>2C4Wto;Od@1g?{gT7gZ)u zn+!z<_~Nwa6(eg#vA-Y5`0$KM&pu&_9l46hSX<69mbPq}(cH5Pmj<s*5r7L6VJi<^ z7gGxMS1xv+WJ;<69fKTF=amWts)NN8!tL^Xn{YBQHi@xF*`(!2FcR?fDw7WPbb>TI zlUe>hs@^iJ&1l=&PJ&Bu_u_6X6n7}@?(XhVAW+<kyE_zjcc)OKSaElE`_gmHe)skL zdXk^H)|0j7nsbbM00z`B+}KU{%gv_kX;c;DJetZFX}soow$Gi$0Nh9bt@5fA<e?g( zWdP!`eZpWsk=hfrQB8CDR3bCSsKfNS>1&`FC0n1w?C7;rF{&!gFfo6!OxA<Ke~zF3 zJ~f(zHbckyUG>d3?{RqNU{+M|0%gY8o=}m|ff1VJ@N9oAu;bwwv9}|U6sg{uBdSR* z(-cGd;;@o<*aUm)A{Bm(wKc+&*RrklD+ldletTfM6lx7pMS6A?5?SdF;3u%}8`FWK zRIANGufrUW^onu)5KK(h<tgM$hG7^H7!3R7LyV@5eG&J>pdA#OrKrXa(aM4Nu={I} zq(|g<$OX!Psu7Yz&^4?GFRGwjs^Y&<Ua}XxqEZ7+f5CVa3Vf=~YE=j|2!A$GgVebS z;e%2oFOv}IOi4B3(0fZFff$8*`FcZH08{CTKclq8hMsJfe}W#NqpA^A?1y~x?X=(- z>||v%U?p)D=*SN-R9J&{XL^n%BYsx>Fg@k8ohe`P=BVv`;tVE~w0305HBNSdWH>Gc z-6QzQ+m3_HoH~2j`bS{<>&(9E5u$sHNH-RlF(gc9vEZfk<nyk{#T~#`x|f^Jn<p*8 z1JZ_C5H!mr@+QLv{)lY#TlTw`=<NVpRzjAzDnC+55@9AEeuDiBBRM&GH7YR{e}n(w zL>a#lpLzpp6o1L1`|xAC@iv)9rr$x8)f0Hy5Y}ao=kI&T+$47O^&IL*t-lzXf>bx& z|C1mP=^%wsNrFGJAtMajK%k>Doi|!S^gu&s6pa3OupH0ZKY9ZcM!4OP;M=w!k)d?Y z#{l;TYQ_0~U(<yIf=&Ovzf8SzNp<b+nqi72fjN!&y$|w2Pof>Z6<GNI#ps_}an92d zQu77gE@RsvS&N5E4qlPi&MxU0c%^UGg733S($^T*c@Tyb3-(V#|C!6WLx`;39!?+~ zj_UQUx!bK$7G!`BRrp8(O@;kRe8)_9?AiHmCdv&+!7GXo7}zLk{l`jlt5o$^_igTk z2udXEHsYc4!4Afq7&UUwq>woU92UdVbn53lns(f}v^(741KmjjWQ`+*jZdp40aJ)Q zt47zCD-Z{CKC-!XegJbYq6uQZ-jOhK(@_m7XZG{Qx@CL-J+|Ko_n%+>%u0@1rca(E zjlw7OKxyI_ts_IhCwW~<Rv=u<>sflH3b{Hx9heW=wrIT-olTJ#L5SqPLywqN7(U0N zR6&5>)9^URev<%B4M6{xF0wpsp^704L}Nownqn-jZ|RZ~uf|KETv^zm(u$M=sOe!q zqww8g5lJz%$;h*0-;|;h@8KPGMhyrB*}Dypzr!oe#c=#2%5%yo0%y8s28m%Vwd^_u zAJGg24H$^qs({mnGP|cZlN9xsgs>1|Wy4W2kJv^7n5@f>DMi4>y)R-#j-=s2LQ=xA zRPHvx!PnIDzte!5b!_h-6itQ{w&EJmUDf=QGV*E^&RRut;zF)I%iq?OS)o)ob7r|b z^BZP38!qvLuB0OS+Wqq5Ac{fNpAPbsL0CZ`+0n;Aa51gdo)G;ggABMGN79i|MNYmi z@Ms^$qzoM|Q4L>SnX-spBx(PTu@R}^EEZ?^C=RkZjpIdGn;(0<f`kd7l2YQSJ8eES zx1vdu2?n#J58OzoB#)wVl$ns$?VrGTjX#W4b3gkzV4?_>jq?f<wva{@O`#tT2VKA= zEi8bJEkgUcg8{mD_D^&J+qWy*7RUd;`RE3dV<VFz9kB*uipwB3oaQ6XW4X76Q3%7^ zBCk{^0<BuXf#_wlF)_y=H>NR>2)+ho{FT;oMpAqheKT>3g-EDz;L+xAEMV5OvJMr{ zi6+@Ity;^x(pJ-N|G|Tf(F%~L?Ko_S<z@`Vraalg5QYMZK%{uF8ZbSolo8A01Z)65 z2yV9_;m#Hat0#36Jir42!knl$iDR;A#QNehwo@UB8)Fz^j^)w-Dy1Jz+s?wa;{CJ> z;@byxTTz}(6NnU(HJdYcK4yG*3a9#@Su|$T^Z`i)bnQ@2(Zpc-02yfN>P#q^^%xm< z)A#Di)C}ECF9TAwM;^6sAG-$I8r8L%U?v^9L!o)ZSt9}Y$>BQG62HGjwlK@vQSjR! zNLC^Mm|#sYBe>&XH)uy~99ckMt8K%6cm+c==9N49(e!qm<eRnu)(^&s*jP7`Z*+!5 z;a(mc30xbh(`4CR!mDHS#)nFjqC`ok?a5hG`UEO~Sy{VQ=?=siYZukVYpaY!w?TRJ zaNinXz;@zsLJM7J(L$|^n-L9(;zR!PWMkE0gp<*V7D($}(~wTfV2UQkORw<#_IjUb zb=l3m#khOw639I@dHP!Q^U%vb=J{^*KN9wrH%%e392URJNNS;}=k<3#np*0n!jCZV zUshccURNPlH(!VlqZT6CIE8qpo4N0(0-O=G1c8MEJgQ*(h%_WsD#ba%^#=)`>x}i2 z!Z(4Z-?7VIx-LJ^c+e=u_lSb@!^4k0IC%Ew#XF1pYh3Ha2O4W)EFPM%_@uoq<-DdP zOk-|T0QeCksV`Df%YJ3^A8RJLR_>hc?K5A*=^N`kK|uFLB?>ufa8!rq-96H=f^RJ* z*E5+7c6%&*ulR@^R4dXVHF)2jFcOlM*Iy>rTWk*Xe;J6{s<efI6c67<-UzN|tp;zQ z=h7YGU0(_WpD1V+Xr{Az+~bcKD2euCZ*w4uIAddMueI6KZ)n^YQXY~7x{tVn@Yixv z=V5r%R5<aBZSLdMD73N&M%^+ttQ%)+@~sw_aS1=ZZ1v`xo4?mzk#Q?;QugW!28C8l zLo4{cr*3^WmKnV*!(L#zd-V7}sd>P*1+d%C`Q@xNr?wgFg8qqUJ__cJP6}#+gYhO5 zka|J$!wT&<5ofu5L_u=T7=cE0LdAyBbA06lG}H=F8;%cA(I+q*qMWOK(NO5YqdMf? zAt;SWzUG4!Ys`i+Ri6K}A|fOMboOiflm1iOPDqaU*e$NV$BCrB>vKr0^$_BG?O3Xb z6oIL6hH_F9205T4g!S`$nPhU0{sg(V0Dubyo;G)e%XTvLXW#F*;J1Nva4$w7yh@Ll zie{-a70$}@appo^db>4mwAzVcJ*Tb8hogKKTPiub(OVdmDg^3?${-2@Z5&AiiPADx zLC34`xU516{E#v!1O$P*qFpn3M)^fc)q3GS_1+)AxWcyhA>|m@yH~$4=}9RG@fyqg z404&j2j5wx9Nlw(RRKAlr~0+!^UXw(?(nP+0on&)(x+g*-1@q2xUP70gz7ZXLPM<i z#`FTgP5iy~--E(Z2i$=JW!7Ea`6+i(P)fPhHJe8VubBhTviQurgDDlDmH*Eo|6flM zq*dD5AhfxA#^R4l`WK}OU_L|rGd2=p|Gq<jrlQtcBXX2J-^$0hR+XO4_h37=R^l_a zAncg~)kg1(-jaOm?_?wDCrNU$4P=QDlojqpqt;}*Ob+|vE%m`lKnfVM)GebcHUY}` zQMDS_dm!c#{OU`y^i{Qw>u$)SM<{Rso+~)ieg#mX9?Ck%d08ukwnGW)O!)wvsXcJM zp0_B*&W(<&g?<iYI{axX8BcI^P~dUIn`pKqotH8@ot!@v6tPT>bAZu(a`!cgr6EX@ zvU<+*E26J-ucQZI4Z2We1bw#=VQy{#qKtZUYjD(0ZDI8q>HL7cxFUDkFHGt`u&0dJ zEwx<oj5dcxLb3#AynGS)!%)KqMYiZiM=E!iATh!lWns0S#<dOUh6@L@$;XsuJ0rFR z+^-^=xT`dMps35TnD&H_J&uO>(*(pz>yw=fhIA-0Zj8w7L79;BFNIVU=~|gO@zSpo zp*My-e?g1gM-?MypLLo#RiJjnFMomaQ;a@bVC_ddhCA)+EE>Ws2}xZU>D)V>L|3<* znVk;^#)qi}$jJX?lp0C&bjUZx+^WgU$EU)<ra<BnHy`q=Z#9bLd$J)EXe)xdN^g!z z?UZ8dJPA-)_<Q#j_`g+pw+v34>t7WAYsy9%$GLxAN6kcNfw-Uy0B3ye%HO}P;<*Iv zJNvF3Z%r~dXJiKwWN^gQ-M%16h^EZ<k98x_#nC@=Q>CbI2|x#=6h3+=O8OMH>V5ok z;e&2HWMaG62u+DGn+Q>Y8URsdG6#=tHol{_c_DnJuF>pmDtr~6<)c#Yh`H6MWe)e1 znh8CU$z_mwL|~;x82Cp&9JHD`&|6>uYhRn7Z^*EFwJEgWUgy%SVg|H*h763mC)vcX zw|ykOb2-KjQPd>%XoA8I)Bd@pHUO5_cYKcN@7@!PXd4t7Bgb5Kw22a+2pmQ-!Mvwf z(R6Z}(QMQ(J1mpd7r3{2O%Kt>MXZGOj;MUTK`oFxf0{oNx5E|V8Ja*uwC2@WD97s? zJlC#YN_N9+|F651{|Gy?9sXTB_g9E-uP<>t%W--%u#(rP&@~^Dg1+Bmk8}Oden|c% zjtFl#VdEmi!2*|K_*;kNG46SzjG_vcEE-qgImU_JL|6a@J_&}Bn$ioHD*gQRH%1TT zVr=P^owA!Qg%a9anQ?gytpaxMXzHL3Kmw{f7bZPDdEh3I2etxZIFPk(UHue<v<O`a zBKjc3a}uRYZ=>aTUW+F`&&7js({2dF_+XqOk`uTCHUD?I?JmtGXKcHJNYe~)WzKPY zRE3ggJ=6(tBKb7m%|NsbieF%_C!ZT>6Nq$P88@3!_@y+yirlD|oX2gv>3e|zd4Q79 zC5bOm#=wLiG_)GG6&)nDEj$e?cMfMXb$+d<T=7@Av@S~9nP?0V?5=dBY7n@p)RQJ$ zvDaN1Ks?V53g6j+>5<PE@iDY8<s-Uf!te#jasQ#a(CuaK?ZAP<S;i`&PzYQd5SfSO zXjUz{%7VGGi)YkX@g;Q72gBIfQVD4qr_*yt26;};DK+;v{6}#8ALWQ=GEQ7GgXdiD zK;-rJrLtG!BxfNy0h=Gh9_oUtUjo#jlQAUA4GU)8!bm@Wg(3k($=08GREbG7kq+>K zDQ+!+=6ba<jfPIrM$0fniGU}gye<r-Lu3nun7Kx<8kBJ+LYE&$`L-YwPWdpP=4gP- z*9SRQ`U`&hv5hJm1W}JRU9y;xh<8xrt7B}-b}1&GX*$4)UOP>GDOJ!h*a%y5LM~py z6%Z5<DNgIGL{#D(%``;Rd%;VjYCExi`D=t^7>5{93X<GJnU@UEr{C-!9+{gr^hw9i zNd&x7##i^lHflzRl{Lm4RhSO&yKVJJL|Z$qF@(>LQ)0r~%%#xx3~G=SuVe;UYTASJ zjcFE;!z{?RhB%dpd>h!Fz*EJI)5>@y>`7iz_K7|f(&nh@6{o%1>Px{=c;<y>XL5F4 z2;4mc!z~#{_KwS;dDd_l>}M@w9Aj8RVq$t6U==8Ot$~~IPJ|&jBpw-cZ2_axMsZub z<+H&#o{{97PC!_crX>@J)?aZ9#|(izs>eHKk3+xOfPF?qGhWUyV4Ym6g)y^?r+~zO zgUPQP!{L9}3y)571YCJ4l4F4J3RM7)n6clvROj3)$Aj2s1tk=dHrR!$l>e;n!)hZq z75p#AjHkGW_pGHq!0yIFos66f7h#725Tn1$4EPNv_#oVDFd?3W5?l?g-ph!=)JXzx z#|L3LO5{+jlw2#UInLZNNst4>C{by9e!s2XKmMxfwFu~dcGcF@bgXl~Ho>pi(_!UA zaJ@Ow=w3c+J7ylvrD`hsF6aE+IN)inL|_-M`By(P9a(~qc{eO2H9r5+9(J{7u$>(+ z+Yd)hR;%qRfyVD08~6!o!^9>T`sx-B9tlKvV<^n9V=9)`=kgJXF<40P=HA0qYmWe? z`9lZMw;>{lQL2(}Wk!LLO6!onpM}VYT+u>XMcm$88z#uEqqSZ!8o1U*#)LmK(+rb( zOf>q?a6R%nQ!|S@L8Fb2J~;i<g}Xjaayc|=+oD)u?yl=9#;U_?1E|iaSq$=|knaA! zVH^?eKkbK8>#Sckgu`PD3^tHvl+_B-pz=r)H<!{KNmH#8%7BqjHr^ELv@OxbCej3m zQ)tGZcxT**KLNp5J$Y<1m?QjAaitk?nrNEk@j|oGZjC#)LyC>%=kgYjkcoUnjV8$- z9IZ^)Am|6nkLP{L9JK<Ks&woG1d36!dLcjQdmbm*)yQoQvyGs_ly7^33Pp2vL`u5t zZdc{@Phib7<KqqrWVOG7-1EuP#CH%AN>yGvzMqK03zhLKH(Y6&R4m7_m5i7h;;#f> zd2-7Fk{cz}N0x>-LGmz=-mdwsEzU;=wnnq257!VrJqQ{D*7%0t<2qY;7PM$1GB`Z= z9$jr78mZF?{8@ZTo;6!*9%>YShFt}N1qieldZ3PfC>R_u7;zG8K*v&0n}kVf=q*+= zViPfO4P}TUGx~AQb^{H-XAc8L;u#Oe^UoKYxg<U7?Pc;9*6~C>1M9*um(t6x0Od!H zUV~J~5sUerg06gi1^++u(mxAR&uk4KHRF|9F%iS&O6k(lBImZt16u#|<a!PE>%*9c z4^UglGhcvmo@Tv?`W@#&Mh98t;Z3cJAsPL67Ho`;9d#3rXjoSF6YAv<&?5|kGL2AK z>zWKqE>CccqMHFE56%yNBUv6bH~d15P~yk%@OyU#F}Dn&4q<%29OtX8eNeD1EP6l& z%wXYn`PChlS5Q$ghx5zvfP2djo=?dqRCq^j#RoW5j5E>0Lrp<Jkeo^c7y^na?gQlm z&_O9`mdcPDX2eHtRV&mmx3QX$@?R#>l^E=?^xFmzI*^cM^xA6iAYdtd6&mg>0PEv6 z?hfl_AA2TdtYfNM$qJ`rDI~v#lI%x>JyvKyrgL~J_<=zjM^~&F$k=ac05lM<ab(h@ z0gZ^mC-+TcIGufq4CAN&8!C`=KuEY>8QiL_VPB7kFp1IR{v`*~xr%C9btbQeTJ(%R zw?+a1Ym}UP+(I-$hMgI-!Gs)!8`|;b2eO!mW76|%(egK66U*;%sVz--I-dcH;wLad zlY_z6-)AiNU5)Y7(Gf>yGP#r(4d^cagrz!)IjhO)n8g3UlzKMO;P-ew_jyeUlWWwQ zayrP~`i|R%vz|8G*b>zp@_#GbbKU?0ZRrK+vhVkF!Hmx^N8K8W#z$orKIlWBrMwpj zJ%Zj@+%7|tQUD66`taJ(*UpTLLr7h9x>)f)aTx-TSTnAnysso)1Hf2HHkv&uz5V~e zrK%u^QBunRTj#2)K27C+UbuC6Qrd>R7={)B?a<luJCCj(1EL04i)zab9h!yA;!hJg z_j*;1WVUh8WcOdz-(Pc}q{H20WMx4=q>2?>y1v`}<E`rYS8aEa9_cL5pOhV!DMU-< z?62H+zxzJ#`@dN<D}ZUU3R2F7A%-cpFGG;wsD%N&;(ob*wA`>2a@#xHb5{4zr^m|L zh)Zb~zyBHg%7eLm?<aM@>*N|YBpAn(yW)QPkG`=!$w3bn1A5TpJUa+-Qzv14@d$q2 zw1t=>rIIjo&;*~-x;hhXaIQt;{mXcG3&Ua6pG)O5RAlin0rUc{-CymGi%27!Fox9g zdfsYk^>}9dul_TS>C%Dt39`>U7N@3kHmo#-!UNFZeMYq`FAbzZ;QiIFoZ`<`2+%g_ z4wy!}2zAtOO3E-Kx<tATsJ>vsQsYcAj5Z}4Vy>tLPGrYJv4P9zoe@9U%243U(n#LJ zB-F|c6aGPc{!>W-_cgdtJI2<4d#M&y?JrvrLfBVFk~;SqwZXD-RLu!$VOEu1!wIh6 zpIQ-ycnKB__cM#K>@e`a!St%66bizW(pO-igd<D!*HzskY)ZmXPx(N6GKNH1VzQrX z;SqeOQl(_`Ks+nW?0B>uB^r2BVj_G#zl~fA$rVVzUoh8+S>P<y&|;SI;{=h*g2L?6 zE{x1kMJDQl6u}CszLr)U%81kXO(klav~cCtwiKOv+iI(3jbv_V`RX8)sy$M!o94I{ zK(f@4z@SAEMt~i^kzZPexKar8r;O}(OBsEr`1pXO8yVh0-k62h-*&wAd7cdLP8>^+ z6@9L=E%b%#iHXTQqi%fOhSHLQ3+a9HBkF$}{{Q);Q75#yH@1@GuhmjspS~_*VY7}! zlRmvuCvNBK*2Ng(otCnNhaEUK&Prb<1lBm|VWfWp_riB`<^`GzJcnIv0WmgK`r*~} z04%Iq5<pvip-@l+xig-_rksdKtR`3iJ@Rdm9a*Xofu->JE?z9U1p#Uv(XfPghzm+j zkDzsnYB4?zX<?^$Kms`P?G<_&6I{k4Q~_XBMj5xgq^J0aD|fj9q;_*Ibf(72zA-E$ z2JGa;k~sCVGHI242?{TR(PDDK9;Ab&rbLXx8_@j77LNh5QD6%LUZB%-ZeKKx6y3sM z1ML8ukVmAE`v=TS_R{-0bjan$!-CzzJ2`px?SPM$fM>G-5ReTya9>O+NHq$`n3}+h zIwU%@OoJo#F*85SyTYy$r#Aj(1NAd-1?#)Gu1kx{@1`*P;YM7g3*m_ZE}LpOqkdw$ z8%C;2?LcC#;#6_6cJT2s`vkV`FtaMtLBFzi#{f8Y6SM=9p>~}dktIgJKkmc1yd+dt zZNiJSo0#f>`KPvZ-0)MC6cM&6Ei<}_$cI0MwqIb0IAB>hg6vF2@RY$SX{I1A*d$k_ z*-sVf9aCMzO-BB)@Bg)dI8HK>u|}HC-+0JONE?g)L;hJ9Jk6vKnZ!{ZzCFagFP?Q= z{~Aw?tokejjHK+PX`%|KhL(*-&R$G~M#iZ53a*?jQOE$OfoSa<9C*q8jO*XAH$#A3 z*EA0^>#q!Y*J&pU-ZqzCbig^^7Z_|mu9Qj{D@Mu3fBpu+W1I;YT=!4_Q$Jfs-5}&r z?v?zrY*t>HpVj~JKa?>zvh{N8;E`zJlG(HY&dqp2i|~3~6+$IV=1>?WpOjx7w^@6q z+~lX0BKEBCZ6`iqtE8r<=0U4wkan-I-J3L#yh5{>zlUwT^o0z@5IS`H2L2w{qxw_z z{W)?wW|em;<9RC0wds$6rp9@E6hqe(-zY!QrFd;$Z&UZ+X?KH%KLlpsTVHQ+cKEWk zWnJ&Gf3JIV+WJ~Z{=;*|EPFr)TrA@J>9)n_RXODe)3}0yxg<YX>=K@_rS^(jmO60P zZS35x%G1V={>kDy*OWy1%SV1|Ozu>0rX;!rJ&EdOo3rkb^*du1Z8HZf*Zf2{++MZI zfaa_De>!LT)iBvDfwwPN6*+1DiV2k(!0}(MQjmh26jve<IHKdb3@s%k&Y$Oz>CK&L z2$yz{iphOAzA=?MbEd`}P<B}ir5HWNfWn2X$fSsUU_~xbvN)tlw4;iWIBQoP;sL}} zLPxGC5utJmKMMR-b4Z_N6AhySukpjlFwd_BiVP`DaP;HOCs+nDy-yanG~S40lJZ_w zbG5ej`9*;Ev1qe$B(A0*5Z7=|C*m<?4_&4;OelwxyP_HYBpgm1D>F_-;WBIxj0|>9 zB>*tz%155}k(${I51tEqu0l48O~FHoAg(fOxlqZwUHyAe8Jn4t-XSyw-s%tnA*`g{ zTv<@Da;DVf`<#d^5&Da~x9_o$V5(u3x`&9{V;IEkXtM*TrKZ#a4H5rfG_qE(1qJqU zCS~$lsL<!wOmYg<bJJVFV8{*GQesIX{ryYvw8=^Zkf4Na+yA9K6xh3U6v4FWUNK1X zr?jGEEX(Et0(qh)y@1RwC17ZeyR;#8?U>)i<pBCK(|<O;|2{*tg#-$9_1QjLCM*=~ zfDCkJjho_8K+@c!0&Cbgr5B<YbH>U)EsZkmc+|U&0EcZL#@)3`BkUF0kfoFxQBX(V z=a#MoTH_vk3&xSyeykPR`_mBWne~Tj1aOHm{3m9iY$+5QpkJK?9e%qRSVbo(I&C5d zP27cvQl1_i)2d9MAO}Njk>9vVE;ZY_s7su3c?PXm8v{TAkj<a~E9-6HCayIZ^>wBy zwt!ab2Huc8{KZJQXV&qfRIP%y;Ccj%bZhCiCGkzZYgWN3mV<0&v<r;CG*++*!?<S; z^uwd!&FORi5(9$DS^pqE_6lr9EEaEMknJG=lz;I3^+t}v9K@2=CR3^MjGe(F1bj}% zXm`SGA+-$+r?X(>c9Q1`cC!Y;Rt;ilxnhvycct63v8WXS7)HwPfjXzkj4nS!k})M= z)D2^m>w+e7sa9%^vJILm+pUr~rLY!b`+2~0ni1a#WHoEH>E*C82mdfT^@#&KmUw>4 zaMkTfqat*FSfLIf`qTuJ7Bya1CR!|YM)j0`0A{e}GO)3er3k?AQJF^wjIQ?np}x;W zs`xV^bvYSG%%767Z~r%hg1#A#;Xf<@|A`nu=X<pkr_L&$dFmbByZ=-iAvkEI;!z5M z551_638x`xK-RBJZj*D*Vcruv2j2{+Gd|)WGNAQY`?<idDivBRKmcSJo_AP4`dSM0 z0Iy3akw4{kSC3Ldr5FnqLFK=Rd;OP=Yhqun^zvxhlWoDZJ1)=Aqukb|4>R)h90XYP zIq!@qlcJHbKHm%_{a<Z`NhAu4?o29&2b-N#b`602^~6HZ<*wzlY2~h0=2zA#uhu@I z;@v+rNI-OhmdW#R_nQrzbGk*}j6lGW{`2xeI>0<QdZ$2=MB#O$$@`G2R{aofQi0tU zWOe9=vtQQzSlo(R{dhWPu{tG!SaZ<6SmF^a<&M^$6qA*+|Kc=!dCh1iR2j&^6hcvi zhQ)BeuO5|ecj9sxg*@P~R1J`Kvbmcawac_yc-@*_vN^BsvLrmOck}wQAMVqz^z-+z zY}ZM5#JT<ZiRrz{aB0D~c5QI&?+Aw!V&{RBc(4N2RK*;5bRudvKz@Gpnk$b>TpTU* zwuC`$<Pmuki6ah5<8PtvB(#?kTqb>0NkVN-1u`(w&C)`a2M;)?Y#83oQZX<f{KKTo zdy9_8UN+NZu_a>s>A#Z)BOq!f56S(~)C{C()Dgn)hQirP@>EQ6|0HoIEURRS`|I9( z`Obz>cYOl%4V*wvC&;}Ye}Gw$2vF9RB<*ja%vFW);;&Vds$vOGcFH7F{SZrxCO!AF z(=ZL@A*8Xz(XIoE0SiEf^U5IC!>hEylerG(iM`2ZAv_Pvl(q-z%QGQB3CXDh-o;l5 z>NeNJp1@!atHmRK1k+0~l}0Q=k6~;l5?OvbY5Cmfj%<R-s18NRLhThjAPS|{9^q0Y z8jm9Z;~9G*mWFsi?~DbEJdQ!Z!S6swtes`HRWs8)UUj4Y2#fQnN+9ypSF_l=kt4>) z2t4=zE{D;$ineQItOHXBnNY-Yfs}g_gR_c9o<68r$$O{-PG!>Hmws4D7SezjJYh6< zazTT)t*f;+q5qa>Sk4JACW&|YCBM-yfU;)`@g9M*T90{c-&h(0UbS%ni;)KU4@mCA zK}ArfHX}&`Ku%oMuYais?K?oCQl-NaC8PsMj00R`1CJ*QZckhglcIfM2>dX)TjM^k z*N<}h|GKOHugkiRL;Fp%we4!wf-XcRfmzp(ihz=h_;}Ewvtg2oj7upzkXcq7BvJGY zecGU_0-(CQU$y;hUMp6-+c?G=s2dSCroZDWa*}{noe089%OB8YT~wFGKBjyauwBQu z>*Z=3)1)*JxbAKoskUn$_@sjK4AZAPs{ofi<9YFzfZQtNEK4_tkBNv|Tq8MOT6zZc z?I<O#k?akxh6U;~hm@#hK5f-Vcrt(hp@mYrME06SU!_|(hUj0O062RiwNZ}!lN>+@ z8$(;G-kjkygo&eEowx@e`%x?{KR&NVs*doQ(L!<&?JE_ypw2ldHS!XHS6!s3quywo zL1x5VWswblHP}cIm{OJj$JiS2MR}`V00AT34WBJ=NLR4cmVp5%PKjHKAI!w;vN^GN z<}h$ym+A}jik2+SEMoV~dv6g3v?UudGDpL4emilJ`NPuli=*Mld2%ZW6NmZdq^4_a zI0id#;q$Kf4T`07WoO8Z;h#LqlI6xhc1E?J?<-MOVka{yx2^2j;O+ENo?4vI5%*d4 z2nCESE;bprUs=t)Ji%O838gIqFc$TB25&Cs-25#{qiirbnkn~7bvl=sdVVMK&zywI zx;-AZ%)37S*+U<Qo55iIe=#1X|AK8#{t;cFIy?TwejqD>0O4r|WS0A7vQis#z}NwS zBI?{|$|uV862B?PM(h9F>~HgrT6FqPu#4Mu5qt*;pPUKsJ8RiSNhr(#tA7pf$sWVY z9%BQ&SymF`Q4Ug5IsWLXu+jy2TIs3wPzQ8$T}C?)aZ?l1)Fce~E$Df0ZzT4}#0w<K zu0!HOAn6-yY{Y*CY^g%1fw^;t_`UrxNyzJ81+LH+41jMKic&pvOaCKAt}oe}hJN{3 z%pG<sz0FsSeV5EMmlMO(guO3mu4IBqv1D{FIZVn|!gbHp&HIityUo9qbDTx`QoR`R z<=b+mo6i>RiznNTdbnU_Q06!17$qgi->x0@1#7oPcU9*vCH)N!4*p8;?1QMu$K~-b zyH+>x$g4q6t)JH$-)B6sXcn}#=UrHj6*LT2);v%=uBQbSJXr+J?9!f@)4Hn0QH!Vw zyN&uk&}LR|HEV3qUu{dC6Z5qmKu@LphWz3xy{&uIN);7n0eoND_fIzLSXrtU^+03d zrM;z6u8yeHK`uAV+1mlGA`h>N|4M)i{>MModGEKk2GVE%_ppcxu{mcFPUke0%Bo`+ z2T^zdFA~eC<vGXQF>NKB+uFgN^k9hbS=lj8WH=q<J!9M@r1IMoyd#W_&$qKRz{vrW zO>zgS1KFS6PdXp#&9v0cc^A&wXDw8i%$+&J#`neJNLs~z(~^;)36&X=Sws8x`l8>H zvc90c0O7^0#J;OTi{%Q<3H8x**ce{7xXzn<r2GCl%eWQDX`SL}b=l`uHCN}HuheRE z`PunLw;tanoB!)Iu?beV5Jy!|^qGC>9SfOc?naeEkcclalmuNS?e;0($2yFu=ux_l z)3m~PiAh=HHS!mQ>MVLdL$zajaxZrIqj}6A6AJOo0Ahx1E25;M2|97M><uY>sKGhF zoT+K)$G|Z>2?3aIiI!nK&&!rtdZLZS8vsA7;B@%RJ{hn4{eDiR`Z3m{_K6iXU?o8^ zYtv`wu-`a`FXW4ee|QI{;|yEq^5$4P!kmNigo-n(BsIc3xTYFeRmCpQLDq;L$B%oG zO;6xdt@B@g1<^hs+5wC$rh+#~L=ZmHnh<ffOle~B$HY``r};7lg<6`#T5#6Rd?v%` zsjid-dbmwxA^F4#5X|Cp%CF%h!(-}|>)pJ>{~;)QNmW!5wiv;aU8i}d7_RNtjh3d0 zlNQBRiM)>dnw^H*_ie4!K>2|r`200ekpa;Fsgc=hN8(%B?QxWY?+<f&x0{U_9ob#^ zV%9X$toxrS^Q|~AA--j%wZv1T)m$jUE*$#du?yyYf+anCFsdC-wJpVZE(>i<^&G_h zCUxt-AAen4XM(t4oDp}hTFkIgEqGtTL#R>`I!FGnvvYs)@Bif69O2(fH^%rtAl9Xf z_KrraQj^%RXY_X>sz0Ogw3R*j#m*J)w^ZOeq8J0{9kc0)_+dHi(u(UaF3_I1B;MPS zqM`v}dU;6AukN{ysR4hzrMDW=DLh#tyffdRJCkj&C(Ch#5vD_pR{V=9)ja%Aa>Mo~ ztw0f}liq|TD>DQ*{3oy^JN4=B=e4N%C|q|XuRu#VZ`m(EHx4N}eTD=c$De31n62(X z9iN0>tW-DgfyU9mZ`e-AQjX;K=Lq)X%6&812e=t*y9U8?F4z$3`cA&UHI0XN>9!&{ zTJ+|}=i5G%!!vJi#n=d)BGF?+FFN{yP}eql5CNJUB1M)`V-y$1&}M#96G2RMqbEsP z0Bsw;X|FAdx}+rQToO=ToDHtv5QXocpEni_8fh3kA9G=4b=v<y1kVFDPbB?enYcN4 zrj}PUV8nop_@ii;S<+~>De&fpPBYt^@Ki%P%z<rW3WTIwfR+3suXsd|p!#Xr&jwmf zC0~Y#6qSJ{j51`?8qC+|u~%F3yqJa;>=BW<&&9&PtSG(nbwkGdTWbhm)pK^V_5?3O zA>l}`2Sq;nnvP;v^h7&3PYMeg%m1$K2;D_g5x9*!K*s;h!XKvq6Jdz0Xf0CzCjUZ3 z@K!}5Df^t2{MYz`MD{<Z6PilPKUsA6*8<io9yj_9NKj~O+4&3ev}D|Gl5gQXnS^@K zAqW>3(`I8rDu<7HHH*v3+9&#YsoP(o^<k}ftaXoQV~BZitSrmBt0D9#Hkh2Lw2)~| z)Y+9XUI>l+xo--hO#GTHxs2e90ZtNk<Jm|$_!}l)QBi@u$hn@q5Jh6%eRR&A6~3+Z zb-|X?<o$YKf4{Cl?CG;7UV#~Z1j(YLQSo#AVmEq)?BT)h5#`W;qdG~ICd^?@li0m> zFNtg!K{L!?wA-yn@i*BEc@Dn_G2^RGk?AXB3=+gXK|fsN*qCj+8ciGheI{Mc0*e}! zAc;F9;$BjE9i->nbMRGLX$ZJPKG3>@&lfk9ZdnSMkyFEuUiR&Cqsy0gNMG&v%`foC zQHw4Hu7;_Im<h0nzqDBJ)-R{LRuqVnF4=@p_{cbZ6iYqo=@JTqC!)ZVY)E0v?kA9~ zQ7D9W4M*8)=+EJ0kNWmBc$0Mvg{R^XWLH2*E3>svRU#xGkO^u|1-<Q=Qyr1ku{>Ue zC=0o1=>PkZj?g($!fco+IX^qVGjJsS@&ufV6}?kRWY3J$=nwaYxjM#v0H6Wm*iQ%G zi(Yd(NDAcC04i#%^p*g;M2*(3Eou~^Y3`^OGiZYkxg8iliPO``nnY#NZJ`-JanQXJ zeu$S_+jnPwqF<Ov)_is$<*jvQQFI|T(Z7o&S(FHELon7xRr2L-0Zj8sx(gdok)FCV ztj{Xsd>;{LIdXTudKvTqdNc_OcP*j(j#8-cn8P?>gk%&|lmNIJRQw&g3l*%Z<A^K- zU&~pSch<6-rVPITEb~hm=|0gH?p0t&-~cr?*{+rwN`CP$0klC;MIN4I73>1?AEVf0 zPYet3(Cn<!MwBU@(hxJZx~~YVpy2pq!?Y-~7UdIdFO<{cMU2Dt7oj&)h>e;$SPq8N zv5}L}L%wi^wTKh)KdltKBWbdzC*j-;Lo>m)pW@kBOCI&mpE4E*l`?MnPCmraFDsiL zDpm@Yi>-alX&~~p6ob^0Ng_!L79?*jAUt2|lPi6!qx_m+p#s}2=>Yi;9TL{S9<-aB zw3rxz(c5kTi3o~hA)@hHhu0%*)ypf$EtEDyNyEtLXpXMq9*F&`lMtJa-b2;4)Rmpl zv-qYZeNDBrtfNja=}WAJD(q5<bm`+I-<petk09dS#P7?p8g5xDmqxq2+nm2xj&jH0 zWhn(?Jl)5;Ep>Gms#j)fdGsJPtn!BIzsK}MxC>z<S@dRhq6!m4C-*yk?~e!nUK!6l zr46AkOb!z?bt4z)F5cu1b@(}4Do_0kI@nqw0=+(3=!RI`EATXz-`-2>MsM(nFB7s@ zdJB$ESIgtHQL1;ThcSNJ@fJ+h$e1o&Vp+gbumR}_dhSuf`P6z{w0HeO4-_)EY#ZXd zsL8^WKR*x<N{E%qfv!HtllzD@a+J@(rxh-RjuR)DEb|xOQ6<nr5Wjt}xh9Eah-F*U zBcNxB7<M(NwUS8L1U&wT0|lAF5gYP)S*co$N??<5luYAC^i<ZG&&7bBVHeP7(!)s@ zCiF&?tcZ@*N5T>u<_cMLB~_Ji5rh^$I>m-@!BP`WaM?bR4Rjk?O1jFb)8Qr&vU4P- zXht@?jHq#L8$V?5-04S{eKXl5z@5r7+4cCO9W4V50WxxR>pz25ScNPB+$(9~7ZK-D z_=eSl5qv6QU9x2gthCE32)x^;(_$nxcC&9NH)P}vw@nG(0CF5oJ-tgi@qxc7GdGq| zSiaR0&#Yb-64OxDm0`%W1}J>U6xOuDkV7(D1cw>5j})Aa5j0qn!+k`(YZiGwNy9U< zhJQyl8+?_&OF`c;(W|vb<l4nb_33NJptX3CfL6A?7KY6NF<*D^b<(Idr|N(?n{yv< zm8EmUr*IeMnD8Xl%>Hc{|F-$R#)v2J&UX-Bi!3YudIdy?eUaSUK_&4WL$@IF<g0!; zkTjp<P}_}nZ2O-uoCqQlf1~KQN~(@RPa|IsMj7r(oc)v)vw!DRKL$Z5_d%M-br-AW zeqREk&$^z^Qb8=UKe^pi@c*Uy#r|lGn%-?Jh%mvOyNT?6h*Tn_)|@zyE1q~x%Xv)m zJZFU}X~5Rp-IpFy`h3M-7OL`|S7q9j-Jr%|Y09#0=kD*vS0bMkG39^mvyOzycOk@w z%eVV5CyWStz5U#FE&sO&H5`$;b8h~@{?qG3(!^n!{;*ng!7#kecoiAFr%b`V1Pw~+ zxfu~@veTUY5Vv*-N`Vr7F#Xsk4I#eudN-?hXETLXKiyO2M{(A~b(04AA>`*6+rnS| zu!S>9W+V4(>m0;RLVpfTT6@_7YdtEsW=-maGYu(UP&P(te|~^_JT2YQl;F~(^}yDO zR3Q>9CeOy%-<s>FBm#{4YqG_wHx!X=BQ(Uv6ichZy25uwJ_h<DI)j!p;#{htoUax3 z_Z2)4Eom;lIKAC-X1FvhJg)G)>tw8UuYJAUeQRn*)r<67;pQS;QS^UH7&_HYoo{4$ z=Ch7D+N-AxX*yhso6kDGrX^_epA@W@{E}1edkQ$yIFnl#t7mLFy?<;aaTjdg%RKQ} zyNjD#<q@<Z`SsDqKSs}+gcEKh;mq&b5Kq(behoi2;Yu{0H0eT=iC2f42ZtLfVZBS! z2P<DZUlgA`Z+)F<F1@MZ>!$xH0n@NX@Y*83E_E+8i%VQs5AR+VH?vY@Aip5~C~11U zJjz}5!xFF8#~}11z`Y<?%sK<U(Z%PZngFw5yzx|(8gMTaaacJ@<M)8jKYrv%@3hCD zmnv#2D8?vUsGdbnslc6YQyb>+L^xT~d?yAgpH3T#wz0xQ&6feJL9UHcTSs1oNAN3^ zieH4e1*I;lB*fPSC4Y+u>$4qUk*xe?N+(@SydmiQw(<cQF`s^ZX17N5Fo@aeS(f*s zV>zuOqG;gn62-$%Vbs_&eFFJDyOIfRy77Zq2~;~itIwa8QYGSE0za~uk=%t7z{Z3V zabrYI74BVyJhE)t>Ce3qgpwRqV06n^M|0X$-?7ErNC0{Oc9*tRWdS$6?Vq=>aw*-Z zV_2<uPg$3iQ7Dp#UNl5UmFAH8@8$gw){zLs(nZ3em3tBIqdw1@{|zkR8y+#1?m_uo z+y?(Bo?1i|v<oZZj=r~d&{wJjRnN}La>ei62_=w`-UT(~6)p0+9>&Uy`rE1heH+Q4 zB$a)j)2CIzFS{G|MtHw)Nz8mRV_{2Qi@x1BR$UWIVEeT>Vr<XZx!QgEdBSqc<KYT3 zi7&0oQa9!Qb}ei+m9chHd=a71K^`c`zNa#zS2SesI&Tn+3qgAR){&K<Z}*2;9iV47 z`RDZQGkCu>_*(QsHi8s?CMxY0o@Vti_I*=xCzE&X(t0%g9TTgp_?#4Z-ea1Dyn|}D zS9@~^sETR5aV^%nAbb0Ml%q=Im*YeCjZ8NdzlqrljJogp^Li{;Fc>i@^bebwP!qMU zdhiA+#Lw%gAilTF_Rws;=T-f`b*ZPh3YoLcOF;}T`^X7gBvm{6RNCY<um0C&c9rRV z)bS9TsA*dC516{8eJcxFN)8TK_MYB0sTaDz-s$l((Q$Htxrk!bw~(`?+X3UF6#};f zbgnh-k{SDwS*cwWaBP!1n#|kO;g%KoMn|kNUEC;m>BC{q4JM#!`f4FAT2Td2SOf+5 z!YeTv1}X=zkPcTUV>q0eiCC#6YxF&_n*<52(cXnq2D1g+_;8s1z)_Y?DwURqu#Wv< z=17)CMu-*lu%a1JHiT}(YR<~C6N+$n<En4TY}d1?j(?0#4t4*-2M{b=fG&{^+8`B2 zEf!U!HHF>d?-4eucOe;$r6*%3WF)e-4wk@k=SW75(~|snKvb3ivZZeba}{cH*ss^t zMQZ+X4&a;r&YUrg&K*)KyE3p6_|8Ictm-&W2B<2>&?^@ztkPQ(z2|eDxVTf)BOhDH z63YlgDR%CzT%@`BDwLcdKP9?<n)cU1d_V7c^zO4NF!<+M7Y<KRQOzgl28o@rGtrDZ zel9`+s69?sJ}&u|Fxo1Ph2(W4JUi);eDP&k^;E(i(NI0jSZOg|9gBa+q9OC(KCn6o z$#{@wv}L<VEIQ1EI4J*yHlM42KWLNk-CO-vs>Z31mGmUUf+UnMSSd%u9TV%Cc&H<v z-PQFk0&#Wsf^hHM?w4A<wO$_!?qOyYtA3>VJ*N0`OXy()xj@=PgE!#fBo%6np#`n; zY@f5oaM;DiyPSj8UmEx5hld7ly9UOE#j)RZDA?K(%b@0+%8<r|#2_7h#W|(r{jHAu z!;i6!PdA5LsBTV2)MwrAZ}6Y%CM$(HPi`V&wYry_E`^`fOE!Nef<c9I72cCw(Uj&8 zqb=z-)?adZW8Z!QuOL=W{iv7SMZ4j-@FPWA1+vBN_vh~6TSHgIg=n|Bt(LjpRxfJz z`7X;pS#8a?y`)L<O0ckPKMVU!TXjaG_MvbFcsHY|+a#qy@p9r7D5}-a;eEK<(rNLl zk2wzC(-cU|nf`9FyZV(3`%2z5aj*UBe%`2Y9g^Uxc!j=B_+ibn*xaBjrm)6tpYp*8 zni&qhMbO`ZiQ(;=uuS8HmjX!E#-mV4BnX9^XIvre-H0lfgr|{)c$i41r6Juep$au& zO1KOl|7k15uzGOoFDzF*FLg#&$Iuq$<GlqUOoL47-klQpjaQfE%j-DN(!9*NWJd6- z4V3c~+hpbI<huY%%xTRz501_I5;@s-P8{K$KqnN2D-;Qv20>u~iG&H|{$C3o29Bh! zALU<<HXHF<7fc(bn@kG_>Mf>7PHL~G+WkjaTKo?``z~f1x1Y)JytcFOzo+DoCTojn zX0PPzt#mBA9B%104iY!MFBiMaaQ*<storZT$QB&CT){4r?0E&)GG2)XPMr30kNDuz z8u_7s)*9X5!O6{6Su4q|_r3^S$r4{?n>LAjJ+9)eysGXCo|>yVN?wARWQC8k^bRSe zsE8S_h$9X8jz<<OQJsCq`zL2<3U;IUNb=p<eyr}=?f<(}QP6?QlS$F><<r{nLunHB z?;o9+qH<nL;WiTUjOA8R`dSFR%H^z^>f81D#^OoHA$JpAC;K*#r8R$GY`Itx#`I_L zQT{MM8LU^8kFIFmByFfzIzdv3iFPQ)t;Bct166I)KRQqY1?+8K%Y%`W!dEGa#E>@T z;^Y|7p|O!B8jaME3R80@$t6NvwZnf@ebxuNkVjyVEYnsak-OfKHT^;>#Dt9$!G`%} z5^a$IsDw3Y2YrvauZi*oe(*#P?dYjw(;s;4)VDODJP<~Xe*(TQ8zJHz%yJqrM}1Zs z=uwt%S!);qn81zD@|F5D<T3LPJ}C7)=Yz`We+g^TGb&p2nJ@KnIt7d<W}I4S_A4q? z89L@pDVme}G(;}AmKr81(o&1(9&$?HIZE4RW2dG()agNy|6NLqS5p=}NZaf~qir1W z%5c9ckT3GgC$t;;?5h%hFsu-SWA%ih{8p<pXu1#1{ATc}IKYKd=<if(VG_7t-!rJt z+x_|y?*r=Z;O}h$4muIUOKiOR?WYoCYJ6~EUj}XHUd}K4sowFckF%UVMP}c~Z0`4s zUaWgg;n_Xht7BuM(<eis7psZL4%K8!O))whegwo@sj`uaegyAmM@~dXl_E#&H&M+p z^cQHX)o0&Uj+{LF>ac(mh)zHG1>RilD8eN6)L>sFkUqu==64K57lK97Y#vo|o>X-g z(%`~H(kJR<+W;a{*f?rOWoWKU^tQBIM^Y6vR3=q%d5T->VIYbByPC(TY?KKH(Dsik zu>bY!;yQUnT;j+6Tf9138V4+@!}^8|RthYFrb^;Azvo9x4pb2rj_rqfi5`33Y(Zo# zA1Q(E2^nb^;V&+h-?Z`oFv^9|7csix#B@g@*Xw_`_|i7pwQ0!3)ZdRm8X+kUMZT!M znNY$d8Ra+~$HLMts^c%a+zIP8!9hzciF%DQM$xM~$w^0lk)K^ff?80dw7AAgm>edn zM#ixYrj>)OV<OM#4GKk&Ctn$%Z08C|^!~J=>IGARNTgkiXILC^NPcA>#WhI85Us9B z0Q`281@{*!4C&E4sDr)e{7?)kcp%8`94SIXy|<@kbrofPRYFsoxtxPutjjv+RL}E4 z3fD_|4Kq9Yi&;=bVK5XN%9pMd3``xGct?v4IDS|o2XJi=kw2#L<>Rk(1`aIqZAC)# zM^5no3GGg1c2)nWhC{1GOZEk|gUG+GOCpBMtc8UKE5?<$VK0%oA(E6}DTe5^U4+jH z%6#0z72sbnp_qg+c`GDAYZQfa{s+C&T^F=U_RmHzq3PxYLH*GriIqs6VHh1vKO$ws z_URWc(}&S%iP6~#pya~HIP+rZ)X8Ttd9bX6QlgUl(F`@^E(?SY%y9!$sy+-Sru?Tz z>ba*Zw4K?5|3Q!7cJ9;?oW8LApx2))Arq~c79ibU<lOv}_x(83)@p34$*z!^g4c?% zvFqxg8iGg}Iv#r<uqKYRy2^fk**7p}>`-zTb$a<b^{VUoQja=Z-k%#HxL+!)0hJRn zYVeq35YEFzQE3q(kaPJL%j>9w-K6xh+It+nHb<fMFz3(hR~t5aYohtZhXjBWf$q-) zeD-MMUA&_9L%jJC_*9kHIxgJdo1rgVY)9%u?J?==Pk)-XQhfzMf4_a@ktPry=iHCv zAw1@SC0@`qSV~W;PRTL&2=nkf{#FSKkxf$)lat!rh4BTyn$07V5$Rm?@puooE^92_ zxcwX=E2!MHMD1lCoWMAWui%noS;bM;NOl_PQ4$!_Nab+UmgJed*v>kg9)1g??dSK4 z+*m0PtM{M##Y!zB30%EtKsk?QterA9NQCLgB@=cnO|5@rbziXIQJh$<NQ5EQFTC$% zN{`sWaLu{ASx9pBGEs1XT%;cV?hD|)L4w9UE}OIa$Z|5>qxoAHKnORX3y`Fq%X=5U z_mLyL*aDNsaR@cWvf{V=cD4(LZ|l8{#ZwQQ09tST)Z@~A(EFqR`;_H1H*zJV=b&o$ z+upNIwcOaNYpue)#Jw8D7sM#Yxxa?#6@koE<a>;n^B^|2-R?1Q&X5)@wI)omwdQjk z%{TheWlVhgO!~Yyyp>&Hci?aLQl0w5dAuDyy>ORxobz^=69Ne?V+{1d*-vdeUo2|( z>DU)wh`q-3zr_8`Y1H%m9wIY!k~5w1ocl$6@BraK_(N}66->Lcp&~o`yJaD8LT<y$ zV{4I+Gn9~9RbQNhto_US)UAMr-xY@cy@x;Usfz@5qyPH0acxyc?sf{?W23=yBi@d% zV`4}CXPg)6b7Q^z4exl;@wEb*(T1@<E=(<FG2Xb5uLjlUrF&QL|BAEOP|&9#7JK@a z3o&svBC?nJsM?N1jm&+*>M-bwQ*>G?e_Is$ae05d<@Ob^Tpq$0+%}`&8+)HoJI4$r zfC%x-C6E!6I|nB@564AyuSJz*fH3%0Iv2G7t(K&8<>QN-B3&oIm#R~^pAl3T3hMak zN++b+Xogn4jZ?O=JomlG-2QhvphlT{^~0_docT&zHM}dJ$kflLF9RS_CRhy;n=y<- z3Bw5P6DY&<cEe}+J9HTbpq`oS2pD$x@l?FefcUnV#^$RaIsfJKPPD|PTtwLzi%r^< z6!uF4f{-LkE8S=HKG((Qh}%LfB)uV*%sGso^U;nZ{ld7x-hobqV%cZxR-etQ{!Rc5 zXA1-3=sY8i_oX)PryuQ0x8>F}fgJYP$mHJQxF7J|8lYLjKhWBlxE|c~rG97#x!fj` z%MqeWZ8SB>l7gxXM-l=}|Kfo~7FKDWj@=GYCCzf#m-;TO%7V5$9%4w{ne(I1+{qnA z*r<nW7cSv@*9<X>6W`>g8dcZ&Kl40oIxabK-_&CZ#=O_O7%yA=1=^iQyHV;{>+NgI zc{a~(Y8~%y)-7I%!s1f+oVUAb{y(nXDk{!s>Dp~9xVsYw?v1;<yA#|AB)B(DaCdhI z8r<F8EjYp5oxij9+26&v?3;ItzF4(ZRn7TKiv(H{wF<Hi?{6+w4!ZjydoTZN|B?ei z<7u*$ks{ZnCWD?gkwvB{=y4)<9{5aMV~)dH!`^0*KjkB(-SC?j^xHOIbKY-Dz}Z&2 zey=f?VAhi$FsAWyoh$g&yqtVzeh%z!K3luTH2+3ajkM(Vw&WM=;P<kIKEW*Z*V|kX z_rUYlrpCqxW&ue478T;y2dpHF-r(=LMC?KX?-g(*Vspv<LluovSt3-OLV*YlI0nWf zUVLo5s2r6{f1W$r3^Ei!AVf%{`v6iNU=mutJO6KzrSFC?>^y?%8DyTw<(%`-29L>~ z{<gX%D^P35zYDo^)s_R1uN;!H7tu_`A`9d9S#z)ruhXaBkRpZ7md?^fU48l1XId@! z8{~j~wQPTB4-$89oiyqJ;<7$SOH5eXC6WN-71@X-Hq|H^c)J;`<=WUStj8Vb9A?5E z@=y)Pq{BMI6s-ItU9M>YUGvu^U!roG#^m`#(+BTdmnn$ju@-@cT2}lk9k~<bFvdC# zzbmXI1`<5QG5Eh=L^Xc-8VZ&t`*H>Ot;?+Ck@&0457{OPs{p+A4vyO7jz!F1i_yYw zXXFWM9;QuWq<iNuCK-m&h8c>}q4<i-gA(1uyD1SgM8zvNa&a5iaJRZ_jEQv$bbdiC zk_TWP!Ks;rZ)Lipn_6K~C>n2#Jl5-$k$kh=KykDQ`y6zr%g60M-DtQe$M_|L{OB~c zR&v+G3Uh)s4XnW3P8BWO5H+W-NYspLd}Nl`|6yz<F+ERpy<=LacoCnCda0%zSd(tT zI~<Q^i{QeqN!Xl@`xBwv0G^TP+aiR{ncW-rsXh93<JuK6u<Fb074R!$X+t|Cs+S$I z4pKqcBrB+h%IC0i=#{r3jwysF$h6dxCHu+Pyl*V#e|sqKD@jvL*tBY^=s@tHWyZ>7 z**!7tAWBn**hP}r=SZ%ANygN=m~ZslQtGe!oyg){f`91z+8?kiYlfy&P-;E;cWXC^ zi_&of)5=oaCx`ACUk)ooG`8#c0&x_T&AsKU6ablZ;WrV}EOwQHq$nky72B^btI`oj zjztM6&j?X1$HUNpGHf|2Umy0lx_Ed=CkZ90&1|L$h$GI3B`IEJyI&WZIyokLYCR`R zHhFck!3(fr1$L9*i*Fm<M_#PD9a0opaK&TJ9s~soeR$TgeO8=H%iJH!F>xG`3?^9z zfQs@yObh4H<G(|;g@6ks#U*xk-|_d_sI!6k9b0j$^==Onc(SC)&rU>F9ImcDr1U0S z(vCE&^wN33eUPrv3<I3x+S(c9nU!&Qg&)d?<PV3btmq5_L#65@1qJZEvVr0uE^1L+ zHia<M7%adLV8$<g5*YoN%x+q9<}vfz8a0q|?e~7791e{f^b!>Msx$OkP6F(`DBD#_ zaM>5`4u9zd8x%HS?I|EN-X?B#1s*W{*&rqU(EEaqxB%o=hyIKFH~f~7E31PM-<RrJ zMtIpLutSR=EB5WIDU0j%nUHDZPi%wvnhqA#i*q%)^T|6~SBdslg3_^%5-^;oIj0?* z$k!Qk;Mj$YwCm%~b<T)m{RK~657bTc5*O<Dkp^4J<DdqS;i*j+*KH!gbG|NhMO7R4 z>v5<heNlTWC5~;q4VC`>fZD5TrL%NmvSa?h<#Dmai{x^bs1uC2l++6W<g1k^8eF4v zT)aGP!3yEogF(oWRX>PU*Z)kx@SG^0x5bvkNj1+5JV~dZ`W%XVzep*G4Gj}2M-(bq zCCw^ufmwU<Ei7WkNIiRD<1IT;CXgllDAv~sC}pS`Q}Vq|xOxY}+1-Q>2nD8|?wz*g z`8ocr%E1qjVZl+%Ufw}crbh)|ZS}81^c%}uEW>7l)vs0)zPnY<$6~4X#H!;XgNWI( ztmN`n>KlaFu<e2IX$lSnp5)GMo_U)qku$|qL3zN{`9vByY;tAz*xzn*Pf?>FH2{qU zgm#h)GwUo|90OEyz7ILC#{DF#HrA#)ZOTuihE*}g?&X@FO<Y=Ou}-}=1TgJ4eMk_8 zA!-LG#3AlF4%^rr;<`P+xU}IWSVx4SR>|l=RwVR2N9vsgXLlyxBv|T{CvzO~`3zTO zo*Hf%vm3v7R{F(V29*jh(8xk$lz5n#<{=#WgMyaPv|bor2_fnS@#O+=rhr9P9-h&Q zXuwOi+?XuaL0HOs@N40^8Ke_xiV#)9V3<NxcZilSd{^khGX<_|-$=uEZbiK~y&-Uk zs_CnM34^dm`;CroKRWNaKo!747=_8~&SWJ!&n$W=a^?J!cZih=jgzZGSe`+Z-j&1) z%XAcOw|it-e?b{yu0e`hf1f!^b>AqFi9*)@)TGBK%)im|&m0daZrtvT>CgV0tY-&T z9>Xh=oC?QtzgmQk@gU+ZF~ae$?Jzb>^(g{gP%*UVfZOSusPj**&cO;}VIy`U>gACb z4CIdD!Be}e(scgniX-kL-p3LL961RdtVF*Mh|^T?ai*{Vzsc+JwQ@<*8o(8Z3s|z^ zlg8VlfdC7sZuZ(v1$Bm}aSPX<{_tfu+o!$YjN7mPW+WG%RobDMp*+^qW!%H#Sy}j# z9$BJjY<5&gs~d8QFZB3RQ?6EkA8b`9>Br;HcZ1HGtcys|jZNPphdwF6*SO^n(d2-% zlc9z$1x(8}hF0KDFy+Yn_iD_3#=7JZ^kj8Pd`tLwJ0GU}yLKaH=#>B0ew>$JKDkfO zZ?EB@*6UO{>qMU|U5ZU_Kze%Dq=#n8-0*62u|_waIIie>8l*45t}x3sTW}6DB`7(} ztdPhQp%nt%h4AuapqIfuynt4Nz!=Gh`p88z%h_84BvC{VXIo<6{OTOQm>Z9l*C>2T z*)3i4BW1XgVm9B@j)en8#YXLE#wO58x-c10gX}Gck(&H0=meXH5aaQOh?*%#-SqL= zey@ZUkPodqRR{+c{((RMy9_(zWsv{UJ6BhNV^sj82{O+Fa<&ys55<{7PLI&2K!z1d zwqYN_Olgy8gwbA8iEFM7<Q1`&1(|XW%$I%@l}yDDuvryv>_+P;JVM{n8cTE?r9#Uh z7f3E2+m2o|$ZJ}X6bXABBqEgaoQ^S3UbiPVM6b!_s3AJ`F^fl^f?`-J`xYV<5?PrN zWEzDDTD<R33n<<ap|C;-#V|y9oBM2y92Fld0soA)k}5q2KU13+L8bRL?Sp0LeraTN zJC$&ZR_&gRFIp`~22>+>X0n;U%=6rtRIX%<qas0%Kl~cVDEw96xUNw@<-6g^$mz-H zSKRo4;Qp8N$_n?t#qUnPo;%7C#`(tnGb{yf{)dV{o(cbw-e;sKm7KLzBym^@45hQ_ z=n1Hz_A5|i1jbdKalv<2VH|TrcWi@k8^HiHCGEvpsN{^^He3nHL~SXoT1-)`rh)&B zs?`26hbJGZ3p`{AG_FoNld_ENa`aMG)6u){Ns}-AG5&84oewYvR3+zgv+U}`+`@|# z_DKiQ<%*wWB$&v#Q|G(nXx~L(4Krjd-r%lO!iqdc!1RGGR&_H%?qT_66UIRoLPj5z zM!@Q`7~`bzQEJGX;dh_;N!^9v*lgo6geypGybfttka;N$vK=;pEL*_+4-+8QfX#1y zX#2}iJ{Le`0fy#msco|3tD2J<(LzC8t2b9!Wx>#xuiSHu`t}`HY*ccbp7fRfY3_Rt z;VXj#PgKFC!qXC&+OT9668TAGO{2IbD$*y&k$O4Wg8`?%T={q8i9I<}L<AOp9tO6l z$$vvDsZ8Z`0ezll@TC$4O;~$IVxJe@KoAiRHy2a#2+eL5t$Y7c6Tg#QaV>4iJ)o(z z`+}nwQHuq!_09KaAFwW7)@<rY`~vO!I)(&>e9U~6?wT|R!@Odqbe1~$1dOiSmJd!h zzprFZ@uECvJOmDOc)wTTUk!&zp8G|MY*oJm<sKB+aowj(NMt0E;Xc4u>zwKdU!y_@ z@qCqQlp17y`EZ}`S?EwF_K{ROu=^rSp|{exMRvg#Q^7q1OX5zp3Y}T7d8S4(P|Gk& z<8^KmJvroBZ`tv(ei1#nv_f>Sc9s5O$LV|Faqe<t(@$sbcs>`+NPjIH5F$Tt&^C6M zzMc{dIp!^NP22U-)<tMfQAK$`Z=^$DJ~IW)+@eIvIao14?ddGYTR^BFC!EM1dcz%= zGrm#a;uGrY_Gi)MTNSDr<(QOp-S^W@hiYr6_^;zuWN(>W-Ya+RO<HoHe^%Wau*Oaf zu-xNS$xM+1MjZ|LfBSp=MEqucZb`x+>kb?UH!`@;yGN~K4t?h{HB!*pMjfUzoG2JU z5pt*KmUrL5T9Rf}L2+}Q3Y|E@iDWGMqFfOpZEF-Rh&?aS&D}SOkh8EX<^p}gvK+ok zka)!}ywds8#IHbleVZwJ(XJm18IZtwH5HYI+FI!Y7|FsfuUIutOM?xm%HiF#{K;u9 z2)O(K_!oWp7tYUxZC0VGey}yTM_>MoC!;y12y;cS>`Dn4o7ypP^bu_QHNd;Ig6rNR zccdG5(Xrs`q41vfjg;0QZg2N8h9vtH5b!k>H!8jpjJKA@Ud4Bt$^~5{U_ee)dUBke zx%Q%bg0x4N@uc`9vxOCzkOLW#{3DKk!|qF4ZWyzXd<!uEwHN}4mS3EE#W^L)I)*(o zi^<2-%;<(SN?eB-MK(XTecTHTed2Bm!9Ijz6|#+!rwNnyfD{Re+)^VsO~Q0M6D}c) zS`JzPJ`>oQ7!k-iuz-#m?wnJZqw|tEbir2SA2F^TdX>KP^muW~H}&k-xOIOqh4Z<g z-o^u&0zCdhh=3K5%w4R2(QBa{I6}$$y5IOijaQ%*6Q3*z$1`ALnm|=UQvdHznOaG- zZ!90s)KlOe!!2s7SRQ{ztkGlB=Rv|ebX<S%a)M1i`=juJPR=Y-JYa0k0_k^T^o)<$ z^WC@GR~vldrJs->qcWsh$~nq8kAFSozEY;Q?+vXK1h%VZY;!ag2K0K%Cw)YO2v4<P z=U<5nV>2ATau0Ox-R*X@u}IAK3^J-@U=RhU?uZ0r;k-xD=|~;_zgsW_OMCBr4<SiI z-j$Pn{_^9k8ukuVv2;Md!7?{)<>}PZjQSxb3if^Ec;(%j?Yi!IUjrnJ`M6DoLPlYZ zCkcfcRcJg<I~z`c9;JqrL_1?5Bhsl31AwtA8NV^#OTwhNnmW~oo8v?f&)-wD5f4uq zl03(QP`uXfcny22h^VONT1BQ$z|NF)Ohnrg?}Gm=UH1oRtdN4VXAm(F14pypUxGS~ z_4gpOd9mrayqg%jPF^rHtI6v!wP>1jvjhWFUx%Xl9{4@fQdHPo8Wnq#Is|>0j1vTD zR9erMKw1LoBPUgCi!&5AJqD{qk>jPMk`dYsC+d$Q=So}db8=r13RxMpuyf#+)9HIR zG|PV#?irGE@Qicj{<27F?kJ=<MVBc@R<=*DjCixrc1X}g&xzW>7?m<hMC3jyG?RH? zB>qK8T}1k0XwfxQ(Djo=idFBl$NfH!a7++;a4@ELnhiw>a%{}jQIeKh(`3j5n~->7 zuXTyw=U}p<Kj;PbWT(oZQM^akGK{lKnQ%!r3)5G`fNR<B*{lrsYLPi|xMKrV9V8x> z`-VE(9dDv+iKMGsREDxBB(1fS>scW`5Ow>lY`Ge%+wcBwwhAV$zm2qSpW8^LkAN}P zk)QM-bdSCF@1$0oMz=>#l|pv3=h@8SkcsAYQaMhUaICkTA|(2%o~s7(MW&}3u%dc> zeN|`R{It6ScV9G@N+2|Mi;P(BC?ig~ABvsQa^#iTq$8eKwWUCyvQ)(!L1GF;vzdYY zlfMT`agcF-L0qBu7XdT`WHGnUuTH+1G-ZtBg9IW+RCQ^g^05#*W>o@tHS+gJzum@D z_vL<RLTG4UraENyaxn0Gd$Luatdk)#=!svdX<@fH*<PjtF#Har@i)&cgYfo)RuoD6 ztccTBH_USwE)z+T#Tuk+9`C;&S+>IG{6$MMFDo^f@Lik7sTMEGJ#4&SrT6<^untht z|CDRC<D6&8V-lit=!CbJ5d9Dw_-g_^Q5rLOjF`^~mY#<K@I+Y8Lm#?s-fO^21e;Q( zKUVSq(vhBf7d2f=l9t{ha)uZ^6loKKSItWZf+`1<C<aCm0Ne-U9`@t80}3h&r)kXs z(^gl?tOVkwGTjE&x5<lFMQ9sh^S1B&(PMUKsDh71L~5U2RbemMGef=dH&N+l#n(IE z5E8$m{&s(xFH@TlS*<-xyzp+%K9k|SC#01lKuDg|+|$l%tM4#;_I*i=?le7v3!R}L z)Z%QktMYZ*e0W2YWIImwYFH}jcqzGOJUDyBiEt^byv5|cZ$A)Vo}4&1oAYR$M2g;e zkRV`ed=T$i3%%HJc;9!}@zxIvo$2!>YP%SAj#`B`Agr3HaNmSepA|lMc<6lS_+DwX zi;iT$&NxSW!f>$}cOu5JNxm81;8qZ0r{pmLerRnSDMF+TRLC0v{=O`KHI}usW?;vI z;}3r&kKF!iXL^4-up37D&>8-ArQ@CRVw@tavKGwoo7TvProQjk43o+OYjVe$QF>Gr zhDd|T>;#J?7ElJi(QA%%zW{Oc%7PUc_G-4y8h+Wc0i#Nl$wp3J_T>_zYUUHdMG)y6 z-Nu3oNZhoyq?q=IYMCEeAqqhb59T*K3uz=yf(<2PNh1WKy<HY!FiI{3xi)1`q=F{g z2W(%qiMX_cSe>;-dQ^>Kd8Yy1BWu~0VmxeUj_nZSScKhfcU%X{Ana$_tt5V8M7!RX zP;Z=X0Qd}17Y~kkJ(?5aNKf~0+jSj^$aAK=nopp{QFjN^(6CWA3c4tp!V$7#^|&XA zYS?^UWC&*a{5>Hkr?2MOa^p?82M_pYBc`*raV93?sc-5w8tq<}2$*lkMsz(+V=XoG zQmsQ=qsyukZl8@{)Qsod#JXOTm%T29#B?cN&pr+MC%W?4w_ppVh1G8M%X_9ZzzfT( zP`IFb6la8x^3NoYzg^2*cH>G@f#?XD^wpVIq6pyFq(8gQ+>qd<ELDQJyUuU|NTQY* z#?J*BA!1o}bSsYEZsYC@$kt`aD2K4$ME)FJO?O6?bkpK9^+<>Cq{yg0ZFfjmFcQXR zLj#FCgO1>-LH(T2S?&sn&;Ry+W3ksOH$WNRXu24T@VkFuk7n)WHfEFN0P_&>cP}Li z{i68=VC|#~GxyqEfpF}^w&v%_n0ztcH*hDHn`5g9$|Mt6vfTfkPzeJATaZs1HY`kk znbxb+qZXSB8FC=0d*jgWk^&zUJ$vUpzd3f7Cb1#=e{)Sl7>jGTt$doqOE1O`>KOOw z<@b~bT$FPn58#y8wf;Q^$&*yJt7c-{o^8vZP~4bAo*~m=kCd}MK%n^W1oD2SHdnX5 zjg;gMO_4yixLlGe_XaPHA}3wmCsKAf?664Dy)|qz7|4zggEm>2F4#jW==?GOR7Jrc zw4f&>sQ}D!9ozEf^f};6E(_J+HIk8RXJ=4>RKzXLnz^vzF!J>zs{(1oF&^^={@3UZ zNHVqRwm={M+x<rPI~zE4ji4CXm*SbaaO8H9zddCoVp5gx-C<ZKELU6gTL*k>yW>F} z+y!FZ@T`xgfa(W9^Z=>60bf0eRn2g<u^@FnUVHf?F8m$9XDd32Gr?d1JgZ-e_~=LB z;ALsBdd7Ik22x<q<&F3iF`7$rb3~7cB}VzMRxsrk%@EtxK81Xumv6H(EOxgtDnFu~ z&n4626;Y;BLNOz*mA9eef|Eq1e!Ll5{UHguUrfWdk|oQSX+?{&i8U_JK&ZU4IONb0 z>++3!sKvUUjB7+4aKPt@1Q_MX62uaqS0pX~d31NUNgzzFhCQ#^hoL0Qy7&xTy|h1c z@|jLJ{Sd=nCu=`^PveKbHW}f0r2KD|d5M$@df94Mc_?EsA9{6WB;a)6WHU%aSB^Z) zI>L-ee^6OEur5Sbr)Ze8m&{NVDiH)SbNNFuXf^9D8xqNTfr1^+Iiog5EGR25e993} zJ>?3Nj&37w+6v^ufP8W@Zcm%q*gD1j-WuI2B--LwxS31l=HlU32(E#2Dz4btk6s+T z$u8K~mQv(O==g4raKdzZxOJgM<q*Qb3=pKJp?IC?WHp3wKlFM+W<e#mcAT693*N@f zyho_{9kg5-QwdZ;UD6O|ei4lreok+Ux6O}w<!^tL-uINqw}#+US2<q=IFmBEX<e2B zRNg9a;0hH6-qb@)T>ccB*PXW$_Y2PdB>XG;)`DJHanaI<C!!2ax_3Ce*%^MqEQ$u{ zu6e(#ZIaAQr3;8(CZ$Q%qgu1+GE8cr#nG-pzgsEIrU^kQ!rxzk2f7e?0o~Db(E<fI zQV~*Xs(u%55{}VcF|I7c1u*O#2LcyA0Yyq+j(ePiWPQ7C#+=ITP3s%{A?xqi9qslD zZMNx@&AJ`DLb>FQ0S5elnu?27^kIfrMbI?r&rWCcwZ?<D@!CHwyU)Lg>wdiUkiLr4 z?;0qukJ&|PIbro5tYUE&5lc?zI%zX>3N7V=BV7Wju>IFp&3>C?p`T3g$YDY_&MNE0 z)PFydb$Xdwq-mC2n>1!0U2TsW@`i1Wo1a*lq`mx7&}~O=;$)&<>^6r7ku!{n2FaA6 z8oAnk=c6Z4;kJy0fj2`ADw!4{lr-&DZkEAS0is5XiMt*LRZc!R>IL*>M>R&d2}@_t ze5|=60*ZU3jHFjPAxKuhl;UcQFm7{#@Xt$w1Co*Q8i!ek>p}DGg@?aVRZ(L#pw}*; zboX1Rx(vU_ce6Ds*@RO~F@>tgs?)1NNZzN(6yqTd7V6^N^zkpRAOxP(6#~rqFqg&Z z%gn5^ctlZG_m=Qku#c|cpxW3e1K>$o*8r)lcpOyX5wgm|T9;p{6{LMcJj2hi9AJ+% zWE$eW;3+7#D`~7kEtaEQmG%6&gCULG%;=s9$}?9J=G&w?GsxHD(HMYIz#l8sJP0!e zUF@%N{vAr}e2ACi7CY_)Yv^?0L|xixW(6oLAZ~5qz6Bm?s6i(n9K9k$vY|gINcWkQ z{go?%;{9nvD<X@>5fU{1S1gP+dyTSuF-FwyBu|+q1Q%=6CyM(I$T!W1qOQcT*B$>W zw3PMd8fFkU)E0hPzfS2>@($ifT{CoO+J@<;_;mlGnh=&xt9Ub^uGG-+K@U)&?0PmN z?7U=`h3+0oe)Z5<R)KdOYKYxsv?PpmrD|La2NZk>Khctyck~D4SQ1-ul(KlS#llGL za;2SxLhyao);4eq!Hj-M3!vOD%kT~<65l=L!WR|$$*)Tp-S_6t_M2BD^TS^vWFII1 z79{3^AFoQhDLTnVo#0c_*HU*tPEo1l&r^YoXNGrkeVY%0EI5(5oQFc?M}whCJCL`U z!Bizn5-pH#&gJj%_?HHd?|9%OsdUo{nbeyB`O}ZON-WJ0|Ayhum6}4!%dO|ylihz! zA~nMPKFN-Q*w5y!q?0V!M2>;kMBXj#1Sj7G+F%I3EAP;L9CDlRG!OhdARtH#9lgwB zHBsls;3!q5-kSb$Ya|7`E4ln>dOlQ%0sHb$ToSA%XKFuVU#E!jNU?pSu9@P-dC4K5 zM39O%O!EZ!VYt6y39hJn6|d0u{)+iYm$XyCy6ue`D3)U-t&mA0e?t7^m2Viw%&!g* zz)q*<7MK=9A%-lWy4<B8qKT|OkuRnjSa@0g9}7VK`#iuWI1<_~rhgt3Q$2kVgtdwJ zI70VBmY2-Tvl>|sb=tOtWOP_|pfL0U>Il|aPHeE0uNzLRZ8DU#M4MdxLVyMxKcZ9( zSrsz51j>wM597%<-~I?MVdtsJ+e0UB!^({<a*`j5;Pm0{4%4{4lVG)yQk)`FIuS9~ zel_nx0AyG=FP&^18m5ln6xrR-;YF3m*(96?A>CIS{Ad`N;`v7@RqJ<%6Otm#&p7f# z4Z1?DQ)ID>VRhWeS3pH7?Jx8{!r7FViN6K%jpBfXwZ2k#>%My2a`kJ7s8E7@bBKLR z*NC=gS4bFGq8*|by#(mLV>8NK%sM}fuJanc`vNqvSx&K@62nZ)(?zSNO6c1oNw~HH zc4@i&+yAGm{Etm&iU#4(%=KSKZjcilxg_RSysc&s4q_<KCdQfB!<X38W7Tmt)Hoz2 zpF){wLCkJN_UdQN<RVxmlnuSw;xf*py+=92W)*Oip9>}4j#cg7HZ5BYkl>o&ZomIb zE*4;14p=Qy!{5^PuY*Bq0qXBPSl{emeeW$5L*37g?-2`3)<ov^*~8p}&&HJJ$PfN( z-G9|ahMe0Xi;!0rst>V_0tv6I<(0Y#Q`E9Z{O9$uJsvEkInIJ<FauEBEl7@_Uw%%N zsR>mH)vv}O4vH!+coSb6M+6H*@nmQs3Mpbo!}uhRSbfU7@gTT);eVaH|6BHm0KwiU zpVi;bXaDTU5uBK3UwpNMzRlB}0q@PtN<6lHf=D0~B@yaLvm)Q%s_t~om_vCEULJB$ z47<Tzt`ohD>*T=F@BnM)+$9UsRt|{9TB2(y)5I@5V#g<JS?!*F_6CyHzpZXm+;V1= zTM>q@bdHRa0h>M4U|ty9jwjguKx%KH<5e4H`zq!6Bnb1y56%a9>4L!}bdrZ0cE0=y zt@N~x<vgj75kki4Z+PCd${RUa<TLf~ciDjOW}vgd+UjLmM4#~IJ@U(S`h~(;u2;=V z^~vXg8E?L&-9K*Rspnwpkd?k(*MnCV?&dE*<I2VEfFV&y+_?PA!RtRUYwr4Y65)eC z=g{tdiJ&i9vR?@u&)g?2BnxI2%j{M3qMdIp#&{;HcOwKgo9q7UxbIqJG-ax7$tXZT zh#eLi!>M!^z@^kC!)sLY8nf+jF(nVYA-iJ12$j)>WU#8vEB7ngC$S??gXjOQrnAc~ zp_aWQvVV(`c8wcc$({McGEpMwBs_V@)sO*-MSySR1@^Pbb~8dWhCS-6K<rWPRVLK6 zydk620qn$icVxCkJZywQhqxXNz-MGk5jB=x@u=%3Xq4tsLVpPnND7h2J@Le%*-2C5 z_9hr8<U_<yg4XuX*VlPSmige>^&`Lt*dSKK2mh-+kV(*)g{qLIcWkebJ_c+9y}MGj z%Ki1Vhu)K55h{B!Qqi^tI!^+|&)zz1o*2)NEW3u*Uk%1eHwUTjNB9ui7Q>A>W)6ZW z?2*uY6ful`O#v>O;hGwrKe{y=FP_chsj)U7*!hQ@r*M1E%b${~&x5E3IyfxBTX7Xz zGuu+^MA;VzjGi$iP@KluY{XLXJ)tTV!w)3r-RuPHzZCe(AVOu#yDbR5VYDAm2#2#m zBZ+E5J)#`z<zjW0;Ty>Uk`4mOADgw&cz3)4@TDi**2El5Zm?2ugb2j21b2pTgIRWr zJ8MXGB@7j0S5c!RCvPm0osk$%AAa|&kE`gfYK^w0sGzM^UW<LwwUCgMEq0ExzOt@{ z=l49b{Ysd|icJLk?1?uVJL*0P7j@a%wP^AJ90VRVI<x`hNx&_R<LPeAm4IQ+q*k%o ztp}oTE>hlsFHYLrkS%@J$9z6$CLaqDivSzVto_dFdq)P&Y!{T8aC)l}GfXgWDK{Xm zHzyz{ve{PkbDs-yU$vvf8yHhZ4ctcHTN3ksoX`Izxcc9D4nGeq8aDaq8)T|HGu1<h z$sabRiD><ZgT<dq6;QZsew(KJ54sg?ep4@38v`e}FPKDZ389f#E?0j}3LVAdm!Z9P zYd6k<CIo@Vue_{3rT;DoBb5TOZEjYICuKltbu9O)I$ejKynh7Js7jD8DLTSUgR4@b zx!V2rO)E9XoJf>~p{;4egE;!pJfs?G6-7W8mULI>GNz6AIHQK6_@TqD9fT1)z7eE@ zpj?826E|&S90V1}HPJZy^4a_m41y_vxQ+sqN~WpYvDhhdL7b`^w?~n9<NVf0tA+Or z?=BPUE;E1h$yc6%;1}=)|BzusZhNboUd{7aC7y+S-!fjc`z%Es6L+qJhMvNNW6rb} z|6Z_(7|14+pQEbX`C=*h5N(<#)<tQS;%N`(5bix+)Ima8qeSbAq%$g%#fU);gUU4( zQpx1R%r5g5+9!*DfUkOry(N>d3o3S`Gj@vcQ7WICW-@@o{Un59K;$alMl)dmj?>!s zUaX_jNj&v{Dc%CP*VyOH3Cl{%7mvje$Ki27@wftE&DSd46HuQgVe|YK^G?~I=LRz- z8>mt&8&C|8$1dW#PCYqEj=;Ez+1{}op8D^L;-8iueB)Stg4q+Bcl*bVh~odri%~5Q zm#?C05Y9{B!q~#}_owpBPXYQEp8guAJih)GQ(5Rg*4#7X&shBB(PUgrECfVcs^l|N z$iKa;<hX4r(K}pp^ri|W%g@u;`Y25`48;anPlgOvV^EIv5LyaHDZi%L28f9z;-YaS zek4Mc>wJu|hy$|0Mdzm(eQr7Iw9WM3dyu~As1Wgaoe^X<CKp^A%H;Ls?eXuZ5cxUS zZn~F)YfAI&6Uv8cW+haF$Cslab+dpHXrE@mNZCSUakPZ42n!i)=%n(w`whO0tLV}( zoG<zIeb;}P{dkUBU8KP>iI2C(c1~1(5gZYX<0hB{GxT?Theg(?oGCcp*?W`19HHj| zAq23(WGSDmcC$`ee$`U`s`&j6oC2hCd&$PS9qRE~mK4rsT35k`7$@v;lZ?p)SLmM} z2Gc2=#s5N;XK<Xch6he0sel1--2`1P?Qv1iDe%X<KF`jXN}n)pI(_hc<b2-$dGVxS z3+U2=CAy>I(jt>xIzVaxE0Ozu`F?*q?YJE=5#!jaRiU1lMN5#SyGCw4Ms0@KN*JX% zv;9c0-J1g|y}awL^gZF)XVLW?)4M4)(_Fh4WD{(M+>(o~_`(-cIDD~j^{WHAphd0y zoY&J`FTojtmqp&tT}uHg`o9+M+&>)NE!@F*4EX-eH~gU&gEUD2gYrX<X;a=*vr~tb zh6xq{#rlZ8O`S8VuP;B=5s5Hk-6{6H@T@IX3;WLZmj2^Ll~mpW2*VU;?+xkfw+_P! z2!LHy0;k9;_qoSSpt#&{m0Uq?R5XMEU~c+1^m=-gluO(Y9jVU>o~`!e!q_rHOD1A` z;Kb*Q1c7$w@H{O8@S!-qs+cJb7q=rwfmXBcw^EbL!Qd`x?bOpn#g+b68wuIDM@88U zPZq6`ILfly0J+gu=!%E2U=!3N>QWEu6t%j+%JyyzA1rWmbz0Ksg~MzM(xjOkyEDi0 zfR;=RRL{khgz$ASu7`j##-jmWb#qFX6azRyvjc@<elZZEKncUFFrC8hG;#Mvzlo(I zabJKxX59g~Wg5s5?Zxyg52?gq>A~^DBHeJRFwYJ^Zh_CPSa!<oF%S$209qMSE=+%0 z2`h$OL5R{XVfJn;D3<UlYmm~@KojiTU#wH8_@?kwW#o?K@Q1ee6e8$su!hHF9s^NK z=BvmZf@q&C954z#5$eV$Sb=JbTN|+m?xK_l08|tvTI}p-MfBTQ{oZ@%+({6{_XRSn zlYu*4Bi*Xw%th<dBH95ukOE?b^v^En#1;90)YYf`RXanR*W@r};}hQ*d*8vBd1bQ@ zuv`CpSepUpR7HC-COzu>(=H`0d1N?M75{)*)lAoH3#c5COPfZBZ=5D7u(pq1L_M1_ zX7xQ<skGyDo0<^@tJ7@M4>LrsC{qF^D<Bml5{!rcJ?&eXWr0jfBeuAK-@aAKlV&bK z{Or&Lf;sTE%42PMP0Hd=SIkMHWKKQS1Z?tuYY(*$j2SBoW75wbX1Np)>d#PSGvAX` zeh(|x-J~gIkHK!0!Q}^dj90DfBWVZE_ob}7tu(AW=;YknW~i5xVrA9l`eK5!zq>7= zGIgrrRimB%5)-4xT1s*!CISRk&Y51D&<k`yg$Rn>0Z0H?;`GZk<i`gl$>7i4sg)@* zbJRw3kR|soL<ybkp;kZ=(@4=zbm9jvvtQj-ue{-Nl_*w_I<{f-P)%p=6)06T!_+wj zbWDW{i^$RAM++Y`L(<7IA|+4#FI9tse81btNPnbU@)M{D%Q2EaqkKnU9RS7S+3#Y- zAM5+e7a5q!dI}sQpnWe}TSxKA5Jx2;%nN@8<jJ>i;X{xE;~B~F13R#()9_fE-lPu& zo_+RG6-%o&agr}usq3Z7xQ?@Wdj953Q#cqB8|nCgi27?n0rkab%o4t;o%EkE7uGtI zHz)`Atcp2*=c=?yx-T_xenV|Y7`8-KB^*4|3y*X!&w(;?*&gEFI}JHPa3-hpo*lyL z<4@643R&27+vTex2wAf})VIRCb45#5_=TddHf=22HOugqABL5qmn^9YT?esyzEl@p zI*0o;w^@>^+-QsTj>iOz4odL^g8c-o9I5tv5o^$A=*mX>>$VTe`~R=6VL&J)WR35; zqdUe}XxaNaA_nm^&&huc2$?%no9ewXLeAFkI4=I~cX>g3H28NZfq)bD6d#we3`Uo= zRC5*q8R%gbs_0ClQ|fOITjC?=H7962+hz;{8<~%?CWQ@B3xZ#q+s+;A`4gu$%tC^| zJ(8ly8jSd-cVyQLF*M#VfTFgM*<S@vh}T6{2AuhJVYTQu{DqtaH*|R@WO`}8A<6nz zZT4Ns6*rXO6$egCt)Jp=+m|Mk^C`cnsT^G_wElu`JWyYb`hV><cQqW>!Za{IY9vK0 zd_^?7J9Ma%5NOP$u&0r9!a&-s!yyigDl|h7dZ|b4VZ*VfST&90FTAN~Bpn0ip0>iB z0)w;gh4<|RSN%`r-da2zFHW?XX$G$F3}hVRc^~bNYG>a3Dr~>dh=}>UANY~<=&zQ{ zSN?Lqoi5lgBunVdC`IIc6wBn;;TZZXe5^8C+fu@f+rw&zJ6@D+2-5r{`vWDQwSt*u z<U3p<(7Xa1Fs3CL`a>Vgs7=r5OUhoewD8k~K7KrJ@G*bDU90ardal=i^8|V(3G?<i z%r4t82Bf2U89w7to*G<uYSq8-a8Y=}lg)nSuk$&?`&KSd;A`NJ5fW|MsaB8$Zw72l zIhp3+b{jgIdDv1JdO&iwWarq-0o7Gh)z??qit0^)1IEtbcrne)iO)DY`PR^t|GL@# zlq9|B;I;f{gid^V*nZmK)A5E~+Vw)oY$Z`2UjwsE*mH%Um3vNGax_FP5{<qfvIGG` z^rIsUAu05Gl$A*{#b#oz!PT>?Rm6-6ZI-6nAtRG|1acfQdsJ@(=$<aw95F0OKbJKf zN_v<GAcBZhO|vhgh0xAM{mE`#Uj|9wQJrEOUQ+0Bm<%DV@Cy!*kn1J_0a81LHPVEn zkuGxnj!%XY0!KooK&tr(mQZSJFd|ktJysIZ+`DBhq($P%;7hA2@>cRp(Q+gt=nDmT zQ9y+e)^vUyM5I`p?=j8x37~qg&nYNOfHdhM;d+FHEPRo>j4iuvhTb^9qyxH%zX#bT z+|3EXq9{nq6Z`oT(0dHfCwNGmJ0~GQq*-F6ioEnaN98t&lvdfYtZ?=pR;7F<6-i+Q z(P5uqf@Hip)@wQ%qEtz?2ylnI@Pq+29GZGFzj#=(66ZtwZ*ghM9!&~q8BzA3|1g97 zO56o`d39qU605O)G2K{;`9zJOtq`CTW&tu$jz&Rccnaaqp*Wl}u1$}EkWPs1eo8P3 zwJ56XOWWfpJ`7!t{j|9<N|HKQUE2)EFJ(=E;|t`wR8HhT{rM*OGWl?%r11=-%5Dsb zOMTn8o;aTOVJSh3G!cAz5p^KQ7fAJ}yEc{M1$NpPONVItt3piQWd)-xiuIv2;ln8x zufz;+oa-*p_i>cwmKh!La`0E#ABq%6#UQni2nc3)$qG6x=?eabYLv~772Bcyy^>rv zT9B^h>5c2huW~<~2Yk5W9hH><0a5+1A@@m+F1;noiiGu;B_<DxPt&8BQqZ>WM0DTB zh4lp0q(TnkpIZ&Tg;32nzKG`$L#^VIHO+^W>d~r;q7FrK#+59V458D8&5H|3dNBgu zDOF>bxA$f)n&R>-+Pfw()FJ`Jr_o7ILt(X&js)>IHL9fp^B!b*VQ3Pqj+un%m8Qn{ z!U<$dYxwAlZl2xMV&LqD^7ppF^<S8zS}hi<ln4yF#E_X7LF%dZ0Fmuf42qF>Dk#sC z@9Elxf{MI7*2|Ew?9iv|d4f+5g*e_qreYz|aT4Jg?Y1gt4~<fV$53BFC?-G$QiT+m zD7ie>il3pJL<&-wyf125c$L&uu}suJR(E_k#5RO(v5P1xea?ZP=}$D#53ygn6CkK4 zo;Beqg3&#$!f1Qv76A$xZZCNtf3a1B^wrNvo^6YW^b;z}Mt>B{hJN4V-!{4RqImgU z^iug;j}thD+!lSKWzdKTy)&IcXc+W7Y>+CvUZaR2jVr!f)Ed;M^86hKdI980iYKJA z=YiSLN5JkfPkbpdvt^P1g6`60i$vHb78k6kLgelDj|U?z?+YmdfO^v>9vC^SjmOd5 zZ5SaXI??@v|1)R(*KiDOQc6f+5;LAJYhrqo=#{MHR=TbA2!no4M=`M~d_^Th1&XLb z+!|Yi7a;7GQE=6V5$W~fL!b0hax}<~oFHbCONlNIptulud>RSnlaeo%@#9E!>{d|; zYcG_@{(MIw`l%O6>e0)3wtEDs4wYBj-M!=D0dc>G2KF1;!owqmx6lVIJ#9|<zS4B! z<au_+tWw(wk~#;}S9rSAK^iTRj=|W&#T$2rcdx~=rTZzE9P<OibfK=>v3ICYVkX6d znzp9~YGH`YycJaFgz=NO1re)X=qzlgk-zD4W(-Y&$nAmnR-;~!vP}A^JmT=YqvWz4 zQ=AL7h$f@J5O)E3$R?ZulCn+BoNuHzTckOx0kK9{)rkBUuV1)jy2E$T02b+8TwPBS z=&@!WHv=BpUjY%WRBN*<I?5^bCtD7#aP}ID0u}BC#9b~6GgDAT{7Z&K8-`o*u1cW9 z-C2~aAuP9Og><;`85A-a8Rc}&GLL5x&XA!t3$N%iu&2p^Xo{bhe+h>wj0Wo0MAN~m zw_!#Gh_gpZN7<htwFj(U*pc3RI{59h8R+zbvExX=A^bwJCaICH#|5%B^Vb&pP#nb1 z{Uwvd#Rd|XKpU<rFh;7*-AwVc)6$-G!wqRwy?vh#{gLYM8}4+{B-rA6P<^Hvn{~0! zg}PaHve7_;r{q-(5Lv3X-R1kZa5HiKwSNEYm9I_-N>m$KlUVY_HtRxV>Th>h0Zqc! zDcVb<LXckixDn)>mVZlC?k%<*TaYnF(bu2qUL3+|N$9j2TZ<uJe=i(1SMa0`-jG5* zdyhM0z$#s3F#J~DLQilF>sh~oFB_4V&R10P)C~ea8FcWgQCAc#!a0FT1sLoG%STo| zmx~miG_AE(SWH18T?&|%38i_LF&23&RW>9~q2CyQImW&f+?QI!Lcy8S_|lA}&MK&G zN{2WbZ0vnV6*UctV@|l1+vf@>4wH-z8@ISgfmKrIfXFRJX|`^Vrb8KAg{|OFlnGYh znQYe*3!r!J{S;+Lx`mfL0Nk|Mk#UQgRxGpB*2<Te(agBIcl$}Qu(3Tv!h%CflnkG> zM#XRzByC`AHf=dBjSdBHq~)iiyM=WRcP+zK71r`<bGH<$GmoT(;w-?LiU5X$Dubxf z6E4M-O5kMvJiPUf&}dr8LyY67op0X=8bJ|8N{{teEO_1v<jq1jIwn7K1{&|-@uZ?7 z<rjtFFwxKAej+R0ggVOpHfIUjjWv9U*Ad|opF*evflrQ)thP4-&giXW*q2PcLQ7*} zqj0LC4jA9$Js@6;m9O0@vt{&zhLdJpP5jJ(EOgv#rfv=*iV1Tc<1g^2d^11OuWrm9 z=DxOcK!U;Q{Y|uvK<N9MT<pwkvnwEWXrLiiE9hjs;o&-^i@!?3shNKM(4zpfla4ii zusRh~Q4#Z+`rl3ijNgieRAEJ~PJ3J<1t&9jznN3){BWiIG5y*XjpnbqXzmqTl;guc z)j8x7NO-wn7$_E<1TBdlCv~ZgADlHLk29;cW~B~emb4)a8-Zos%4+Ermm&QNmZe;B zZw-O}$*3l(ZwTd1n$Pl9^T`6FalI3D`~zCwKo|ubO%sw#-6-31R<dv=Fr8JUYuH6Z zuESV}J$$V2C@_@kFXtv^zJi7$Ku7MoHehbv_>ZaDENiEBVOBTo{xsze?BUP(YUuv( z)QPOu(X#L4il2FZ$%o*P<|jy^U*alKWL7g(2XUg?j}=wcgr2g|a;pvvoB&FbFzo3D zMhe2(wa9+Yrq*w(4QZ8Q%#`6gtH~2^dlR)2??I}Cl(fZ=F>lR`Zt$n0y-DMrbLY~M zt7Cs>XI<YotK^fR<nAu^pG}rlG3}2hd3%ThEASh_=cIkw2n!S^_5{?jF(>fAiUJPg zHK^HvgsfNbT{CSUBRRU~8mbU@D~~Z@BiwJY8T=`Jjb$D#oo-zwJoYckGC?r0h??Fw z*QgUxP78rZ`6umi?Y^`<ZN^Bha$|=wB3}18u@x%@l~a{{aqqzy7>aPow6`Q3$>{fu zn6~Y&oppg2aqy>I<aB@Pf#!l!U6wrZzR7Mw)A~365d-~ato45yGy+wCpi;&pett=G zBI_2EQY>}>O6tepHHamx_=w{8TfQkDuxHm*x-n)EWJmlO8ua@Tm>5bm90*E8Ta4W2 z6BR5qF;&^%tH=9}AQkGa=jz@07{>1ji*J5zm|*GbiAT3Pz7VqzCloS_!8@Cnw`qbd zZZ0ci4hx}vm1znsJEI*b`S-DmEkZfTU^fhqSHD*$t9yMoQ?>8pebr`C^CP5@d&-*5 zFh+Fc)u{Q~iqLrH;|01|YsJO@HB<bzs`!%80O^=$=c@Y&o&}A;_Z)_S2Q!tW*VU!Y zd$JvMPQe}`DG2R(1IyO)iq4@51(!d9ZKb=KsNc}V>)n|*wsI<vyS%a&vZ|Rc#i>mp z)QZC~bMp=V><IL*@{;LE@g;2iV%Slu=of0V&j{#)w!=prxqpRwWKGI#eR%S%v#cxN z^$`&Xn3|uO3e5CYB$)hKr!;i;4<RMV=X_c$lgQ)?ooo#|ykD#^Dy^?n$St{sooE)4 zg4FH8VNdXV<S8v)xh7$Yi_KD+OASeOO@fz4%ifyT6Hy(QK2O}ViN2ZgP=5uVj{&+3 z^?M9uJ<Hj|74g<$o9sI-9{-w~+)X`n*bF1vqUepg3K=C&6?>9(;eF84OjK|o14=mQ zDb-$Fu&|!5d;qJ=Se?SQgpYQIOxjkl!33NS4#`Fg^}F|nE}_?)i?<xdkGBUYzGB<) zT;w#US7Sf9(22i4^~e0xl!82;>UdzXL$G3DSfuWQNJj{0ynED22d_HMLJ(9^Yyf$Y z{2WXi5L}fK5?y6?<@%&(24&)_?bow1s@O)*%Y3A*b58Dnl)l+jdt>X?5Ye_!@)t;X z$53@=jn(Ba>w8(6hbT@Bvh{(|`JHE8Bw*}k?taehG6DYVrTbNy_n9zM!11thMK+uq zVLe|(@cLPYaz9x&C&{L)Xm~qDCp`R8xVUh7At3G&Ye*jb6V0LRt2FKw|C|m}B%Hb| zJO6Mj++;7DYS}~Ij}<J$-DwE+1?ogQUBM77uBTovKo<ZhZ5Hi{j6GKeqCiA6Y#7?2 zILyCL(rOiJ-y4_FH>VQ=M5pmg)9cK2+)%@A9ZU>PEX$7SE3Skbj3*zAtXB<falJ7j z!DFJk<**2}W9v+GWBuk3Qn(uIZ<#=Ai?sD2zi6ap%2F4m5;C;lDCS%SS{j0*kU}l! zPdqqo5~5eXXh-bE`V=FOFL?%h7|P8vBq(%lM&kcF2Fj*AJWrfpg^4?Nq8*GH9uV?y zUn$xCy8l-#e#RRdRzZND8sl?n8uwF2Tz||Pjx5fD$K_bveH-yBUSIa8-J1|d$}FgY z0yG-)SrHe%Y0RtBeZbNz(e9rQB91ok=K_>)43t0ZJ!Qd{xm$63Lba+RYqDht$!E`C zBe1yX^;A=>yXGR`GBUAv%QQ?E1`!5Z#M4kK)%nfRIIfQM#Qrmzy};V^O^<W2IsbsS zVNV~&QMv}&^^=D`gDtht{{Of*m3g3wLOinm7@y!5iMz+hzD-hG@6Z9i%5-98fs8UY z4PL$9)H~llJfQKbZb+ltM;c`>FC~#jRUhEXuiN_tfslCY;@@*Z=w?a?@}L~%Bv#H- z<z$eHd?_=jpN~E5aq(?_>Rx_`2XKVj#;c1WI%*(ydZS8(VjR`OteaUs6PZGEtN*;8 z__6`Tz@byT4dnNLjcxOGuY|BF!$^J+cQ;}1It>j7wHq=KFAWN-_xEu?^4p*=@RW6~ z=nZ?R7Z3@d$4Dv@W@j*`m|H{!1R}@zj8iwuf7#^7SJ9B=wn_~kN~0|LSbj%?Oe?Oo zGo!Scmt2UsWMu-P3L6uH%qyAV8glc|OXlxOAUj=2C8+rO+&X^a<8J(2Xh_Iw6%BJS zXAUppT4b$9l7-r`3Ob@rGO8|<w_5gnHaoxL{DG>-Gvgm9SPW`<9&4D^Q=?+s+6z!I zPf@>^cIjxw7|jTr_B-5W5r4nN$Aj2Y7gnR$(Y$ypo5rgQ+V*qln|x9{{Dx^Q>tWaR zrF|NK>=)Hp(_$D^aTAa8AvtD~4uf)%Q<h~-pQAYMu23<aP*vH=HOCK~0Dgnm=-ep_ zho2c-ziOCnb4KL;r|tXSV!nGW2lCjdfl~i4I##Y9v1psKTMapj<Qo=+jkAs!N(XN| z-3o*AA7B0|T-cv~H-3~-2=)BDaGI@qz$BK5torpBkZIaqCa8Ed0gS7NP7lnbwa1rQ z*CJt8tpJn{mu5&3x5WeHf~Y2*C*L)gPBB<rgS?F-lLstZntPHkKT~;;^C4vC4?Kzp zE(=*~)={LyjahV;xkvd~kkOxguXB<Rsy2A-WOjIT)V!Y(7(bu<W~Ol}wDFU=?uy&* zWbk?AO`EYz$ZPdQ&1^^`A1@|e=S!W=3D?CY?e!e6T6%dgq0A}*4UUbJ_*izZgv7J9 zbW;=ce6#9kSzg6*&3DQ02D~_6feeKHLR<{BR8`PH3B?27KI5_M+JJ7(Nn%eMpjf|~ zx-&6vCAgbfv3vK=wjTX_>-Qx-Q6=Zpsk6UTpG7qGp2nXc2OP9O5i0|V1C<Z3P738@ z=?(GN1da}7t?TjgGWm<I`k!|^Y^3fhuEo33+`Vpa{aE}bp6TJ2>`TviF!9oQHc(E! zeX#l(L3#4QqMY<l*oEF-ZwZGm>%aD3qRt?q6N;TPXK1X6zxi@BS3;3hcemBvK{X-o zL7gfCPGn=Mf?X=SShg}-y{o@@dKYlZoE@}{UZPR0qCSi1M6KpyS~Azv@{+fRNF%L& zfa!e$^VRJ$6E3En;5VEU@Lk4eG<_pCC{yJK3#QC%y*fKiJOt;|4W@AZi`#S8RTdts z!0oU|n3jeW|Jf?s+Un%!x5v!LDLvyxbg3Q)r^1&ff>lnAo-04YLQ@fpb3_*%b=N2; zMS$2XSlLjs!HXUp2u#oCzrd`OUHHCPs_KnEvktyG@Y7oJW!Hwnj>T6$v3I<pK<X0& zpKU8z+2^2A@|VDHU?;nyOshM2a1ixHxaBW3W&{gm;(ZDSHLGIZv$PyFhsAe}h6efJ z!ST`@=;09~6z6OWX};4N&sk`(Nm6x_0dx~M30Zb~5!<7U&Gaj`U=-*Oy46A=8C|1( z_ZRzr7O&n#$%e!3HlyV9m4hhq3}nZlY%lk!Upg@gGP<#<s>%~)4$=LE9*wPeSy*gv zN+@=iH5$o>I+%3|RT8<XVa|ZPN)vs>6q?-zYNYbA?86|m$b;X}pF((7f(y>LmVtJp zRaU=bVa60GmuF|>7reC~t)B`ep=BDKD4Jg8CJA>b#HIw9!f_U+OHX0i%NdKsApG!H z`{oUA7glC17?z$CuxG*5j|XHk<lbDC<RAHHLC1>y9IKE)Y>AXHp0<Ui@(h#%k^z|( zN5XT_(umVk1t2<ai<k!-Q-S072>Sa2rRp2FXKx93A5V>EVitq<=5P|ZV~2Pvqx>uR z)An<g@|9HNhVvi0tOF~{xG|FwA>al5i*Du=J>0qn!lzr^H9b3_kN3S?5QqlCw@RsR zir{#`*$de3N$PH(geRi%|HwMas5+W$U9Sae;qLD49^5UsyL+(U5Oe`Sg1c*Qm*DR1 z3GNacf<w^DxA)oSo^$W0AN{At=pLi1XVskb&gZQziBk2*re`B~)ao75NC_P}Lw(#{ zF3BZ+y%u@pL?HDRgv)VpI2K9>Ve@yH5GIxSPaG=ENZ~frP_f?JMJJ(Ce;qH^TcX)% zq_{WxN~a%glKeu;H-H6y$oRY5%6C4h8G{p3oo(c^bM^gakufyflR;<lOt<)0g`*kj z<P8*`2@qc~^xJ&U0g01?g5=WsDZ&Rm1V;EzBR<(G_LUU0Y+X?CWln<hH`D!#l-)he z%HcOjiagRegNFcNdbcOZCd0w_kdda3sIAb#!eUJ`Vg)pDaHwa62Go<r(!la}#<mz^ zp=1;H%1mB2Aq?_&ZaIV%@>mjcQ6+J!%pHS#+Gf=N#V@qe#ucm~^G4BbUkmBqe+?t` zxkLKqBELOQj}BPPH0e`0<Wj7Es0HxQ#e?FLw|;jgZTczXCWwGlRzB?<K7)U}T?~hE z?4pO9%$R#2HcZaE7%hf1Hh^KomND_xiGmM*MSes;ezow}oTVKy5hpUv>kpTAB^32- zLqLZHecL-R=?s#wV<y@(Q0n4w5MSH)60G9w_94l0C_gk&4tu#2VY=3LINKDw(TY;T zzd=QTO>$1W5iIxj0Yy->!WOY4H`-f=A13*e@rA)5FqIN4hxuhPDnLj?!F5E)`IE|w zeds0SkV219q-Sl!>{!w{j^pMpC;=?nC-wF+;)Cqp|Fv!Sm+0$Xs8<Yxk*2;K%95?E zTIL69f^(y?VI=a6_VW;;ReF#205>sr(|a%C*n2)h#o&lYIF3W~m-iZE?Ym+i2PmO| zbh>5yNf?wE4(BGib#LNrICTHHmD7Y2ZytG_v<M3qa%rhGeavk^aK@I!_%?}K;~wW1 z#jn{fwmWuY1+v)hIk*y=xMvd(iTjA3a#ixjq1<=qj4ttNnrU$Jv<vE#T4Mdpaow+s z`*=(J)QrbUIsq$P=?M?n^&2HA<=B9oppD^e8N1KN4o6g@gJ7no^1e5ecJN-?cvwJx zP2q%TL9G%^iqB!Btd(*C1w``EAojbWgwN<dzGQIX-tXOL#KaqoKj7&8?%;OgxvyEF zjsmoFyP-6qYziG@<Gx9?N8P2JW}KB$F2d`ICgF>N3Q9fijd)PSSb{VgHsQoM409pb zQYLXfs?m}9+-!@jF?oOg`0TT~+V|e|#`1|1<uERQD3;5&V`mA3R(8<Ld;ZU(XX!Wp ztU#aY6DGwhu0%#bsA^BXEk)RC>wooI^|$h@ov!5N0ZPnWa}YRBTZ0roWyUF44PD?D zsC@Flv}n-rm1fi3$9lbt4G`3*lYhIJes%)|e_8irL6dI}y+cyxB;n~AY^vFBzh-Ax zczU;hgU(Bue%e4<Gr6GyDpjK|o^SvJ7WX?8=jsq_1Om=|qc|*K&<X;XyKn=wx;_N6 z!Z9RT8U5TS=v1ps=VIs2_+!RSu{=L<VtcaRQ&v%=WAyF=z!?I?_Ju>y93mBR`FDm0 zItw9qM(lEpmLO@i7$iRrR7?imD1`UDmqggWOuv>9XlW0`u{9oXHs&+ojX;w2dO1*O za?Sl;G{yx01!fr{NN7RD$9`BhY<8VXO|!*Ta_>DJ=ps?;Rfwi7$fAff4z8B8kUE6% z!U_OdeT-UXn0P1aHLQ>B0G$fe;0Bz8WJUr@EPIicV4*K*9sF6J4Xde`4h^w!L~p;W z`bSLwx^y6LE6=c_tppOBW{2M|-l^0UhOI}^15jrFwFP5D1(h^yMjYPZNvOvR{oOX+ zMVuyx2}d#+DvN6w8Iz~+^^Pj3E(pss{Io{J;l0Gbmvx$$h->)>J|_G<9<Jm~i))J~ zz7!WB=~j+96{>e2Ug!!n`Q-QAN9-D=jK5f3GNI3h-;^>tun>T8xE*-l83d#SOneuE zkz5fG-0~r7pe=mV>2p$n&Wj`a3Zm2@f#nTJ^>6_;cB__fd6`7@(gZur=1QflU~mn> zy0N$a=KRgNoieR1t(DYm<B}foB~+oI)d;{5!x0gB*x;xogO)VW=x#A*(PBAcv0zaR z${oQQhrs33R|lwHE2KIs7w2>MdA#QA)*-K^vcLVq#KN~rr#?Y&nl%@Xi`Zhp&y%7# zl7l<t#FwBGl@C>5n_S<U>Poq*E}ZU|de1r(E?nQONG~!LJ7|2b5_6F@pZ7md>Wt08 zWw|1!W6W_M=uM8LZ7wqTzUP;r<?b)-!dMy>P1sFQvcFvsfdA<GLE4r9th)Mt+5e5I z=L0(;gZJ{S=}~_1kXA=Yx)VFUFbr&KZ9BgN6yMy@-|j<|n2iZ_rMXPIzT5icoaw~} z3yokNF;X~<X&`vx_&7#Hp053QCLN~fvBvfH<Dkx9{?yz~cktv(8dohq9ihG`t8CtJ z7P`6(s#k~J$R3kCpuS?_ko{mt>9B!v1xj5m2wxm0BTD~7uQE|KL&G|VmTn;mS|4p7 zj3^wCAES$8^o>hJcwsqW0MI3a1wVpe!iXf&7%bqct47!Q`zSx0X#z~5!yCq1;6}0J z7>ip=Wmd2m=x*6K3jOwIi3H?s>X3Nt;PDHRCh_qp<|6p%cNvpE9-u%<XDqW&u(9Ti zh;^%$P<<R8H+$*|-n~KD_d;cMTM~*^(>YxSzZ8SAZ7=euEwXv{dDSw4q(9LjS2;7T zIcLQ^xvOtwBS#bOqxz}O>fwE$x!J_!YK#vo-eCx@DjcEhpY&LIx->WS?jkP#>0+Y= z48|{dxHC8Xd-DU}KupPjJ2r#plW(rEgPlX`tu6i-^VQQB+O5eAN@A)lkDfB5Rp$;7 zu}J)D9YU&JCG&u|uvgHJ4e2bz91KZrGsWl@5tu(s&OpE_iunQYuzPBwJ7$df(35@e zw^l(%@qjP5|LJr2FZI_u{{#x5=b`sCWWAU78863Z`H+R8Yym)NsGXxCfK+xfGED4% z#wjo$wec(~_fApj3#dub?n`$<!GxZ|cuGdR*p=$DjNTee#+Fv+php5|a}Q5IZ#<Xc zZ|RUY;6oL%Fiz-A!|dUMc+N9|Sb<4SymWGR-NCMZ0b5Dmmw(3>v7BsWuuNE}lUN?Y ze%^<(U$P|TMLqfh_(AD5aEJ+wg7?tldQ}Cj)*EUrw5NX}V>17G4QD!&({y)2M<|2A z_^fu1d?aG3-QL5u`Psv?qbNzXcYE?f`+>w$WCP#L)UNT(vi#oIi;Npaz-@&wvGCj9 zX_*7lGvah%{o(Bz-`|X?Z_HcLgi#JlDWpIeRDg@xJu}^)UGD3z5_7lWf9#cn0VeqQ z2ZWms(oqYPTZ0oQ!ki)`4$2!(|1jv($B*26kAIaxA3T1k)j@Bsyd!atl?JK}SCbf{ zsV?lR_LTnhAc?hIa9<0u&Dy-qeS021HJh6rk%bxtDf_*5)5OHr<b+p^@TEEaBN09v zEY)`%-|WUc>3BC;it>|}kG%1mz(;OqhgW&J2ooXdmHy|YQBNJ01+q#Kr$0S$A{Tlz z)cD_4pfg!eu{yW}wKh1|Y+gTW)_7}zMm`3g=n{pAp(}jkGX}Bvo#<~yc;GC?olrwx zue%>)Uu#ls$q`23i#BX_?qJvO>U8T$9aKYipbskL=147$Ewsd(OK)(Lrz>4bXViD` zj4(lIFHA-Azb`+8Qp+9d?-R%rp|pjyhb>v<5iyDZV4p!niS^rSEkWAUU~TJ_NlpVF zEZEpzE8Z52p_(@Yh;6f0B}r5Ye1sP*&2B)~6VR8erjWxZ!{xt~Zb1Xcpr?-=fR*6i z1@zP(MHlcSdZOg7jq$AJ1iuomoSsROw1`qs2dWoWTnu-hP&Pvakp!4N2x2|jmNDWH zgg0>I|M|YL?~SXX5LO2ZDUZGv#{|Ta(Ydhq7+lA<g1C~;a|*PPaDQtG7RPSu`Z6`7 zP2chCik2Do*%iRKes?hnZb$nr<lhlVH;@WoC}MbBX0r2AtBJa-77i1+CYgpJ!7NQe z>tp~n8d4zqLNMAM{seG^YoS&<1oF~rk9&D@T79=?zMuXo{fB*yYNsj&HuRp>%RpJS zZWcV#2>np5B7#${XcKEHdXWUF!6VR?81|pdG%*<&s5-N$wQ+=W?F$V|8kCLx@@)o+ z&(PI}ZIczB24mZhbzGd!A!f=V7ytfA#Wu`rLA*CiC=CS4hIVj)bE3*7PJPjT-PEUc zGF3};q9cJbLZ}<7qbm6VEp3q;Y~z6mml(awagtcgQ6r`kDee|G77w6`3C`Rm)}L3^ zo}G*w%KOQ8pT(_~{j2O)tkZ3tuvDlMQ$yyna))<X^VptlbhiKeyv*3t-Jh*3=12^S z_N>c(eo~KwO3%yNI$Y6K(IBfGiDQg{|LP(%37x~$@@S|`EJ0{(L~5koGl#ju0`Yi_ zUW&0>?k>q3bP{RY5dV*^c#Q&~$E;6WhIO@INfBE!sG@@vQGhY)Z}*~!W~3Ke5};Z` zOkVR+5JymR{mp3k&1eW>k^z2|Nf2|GRPFQvspQu#3i!ai`CbtJu3LmjG~-<){@R(3 z0qEdFfsyEG$NavY5)X|qf=`^q>G610tMF@seTuAjj0K%rj(XQI5U#Tm0UaHvt5`80 z$k4w^{V)?Ft1rC2N#TNi&M;!;Be;2d_LbmDE<fS}xvyq~wr7yKG=7|VS?>stX)s>k zjYX8Kv(j#aVB*Uh^u({tfo(Hp4WNGXx3mhTRHY;zCz4O>{*PL^ta)W{#@Z6CjYv-A zG3pAq%Oi9H@oUmqW;xVZsvxB!KB&;hL*+uTn%x)Kq^fnH9t+Bq!rX>y9U|?yHsYk& zQ8wRGsCuHKVbzC}%n`EF>1)e1+AJSJT_lq^2in*$AfW6xJzIrlKKw<g)Oc?tk6V8F zgIUax0pS?`PY>ECQZdI)Q7rQwEklbE_WqA43lgrKXm{aJ$#GDA&r4LN!Y#w^EDXt+ zh@+d7`0lu$DH+@k=7Ea_VO*)nFLng(a2ry)3$lUVC#k5YTJW~z^A=%rCj+*9I<nuk ziVr(74b&F;R{lWWxS0OO@aF$6HCy;nz*{?^hu$!c5y3zvH$vZr`dL$^b8d8Lw3u`; zUK|FCE>Wc=>zDu+*ZH^HPELmes`jy)+ok)7vC1laC{09iG)gV<LQ^zxNFW5?L^O2S zgfor(E%aUbheHU1gf`S=t3zEEED9B9Q#LQp+-iM_ets?ZVXmT}o})s4z0rNKQ!@u0 zf7}0MV>f>LVC_2R-V!vRNjMLj+gFU()`XtbZZaX>0{UOqR~xjf$FZizYm>8lN@wN& zeDQEo*VcQl+`isr^-SR;*PUi~dtG2a+Pl?e@!`*0En(f1<kqjz7OfhQ@>~Tt!p)7^ zcm*|J(|X!?1cV+zdxQ}^>XIPV@x1@5`7d`2ks7N!bS1s4+-V_LRG43L*uyDdkzji> zJ_IgL{9B86=%)(XD-=iY{BcjP;7`Uo!+n0HeM4;#uR-W#F}CoN*?o?wG4nL-zn7$M zmlw0K{#*>qY4>coW`P}*(ju3bBIvF{mck=-ItKn{D~(GvCgiRH98l{W?wlOYX(IH2 z3Q(1DN1VNE)Jv9rr~cY$A6z=u`yQ-w{do4BK;ZPBm%dZ0>@78RSDTl_o9JSS)fMes zMnQGY>qIGk8Sa@gT|cDmdX4#w-%oibHOkH;wUW<k5%qfqkf_J#t0eQW!h=B%51u~W zn?I(UAGr@~{S`0(z2?@2tWS5h|1|r@nB>%>9-P!MeDaE3D`7fq5(!lIhZ(!?Xe#m> z7oG^@)5b5eKurWH5H6M2tMC75h}>eJ#%;XTHD`Ki&y8LAIUQqfwQrX-{DtSv{cO9- zpTxf8{`T4y*b(cW0t(~9b<L=cOh>%>!8{wB+m70=M-4bJ{q1}{(Y2v0U)+@dP5^Jj z;pk`zOzWaq$CJF5%$jiVv#gkISQ%?4-B?3}6MU{EdIU9)PnM<#f=5Vx*9GNqP~C^} z?r^Yh^QX!(?U}aRtEFkgisOC?*%-oUDR!{9&s)kopFxd00c2Qd5L8k<mih``er4nh zsA7KtSYf!;oi@a`Zw;-knuL&sWnC{iA+!Tpr8O+4fr^@?6FJai8U41&ls{5c&kV_t zx2yPJow&9QU^Og0t9P#NS*dT!=SInm$h4Dh{xuu<=w@-6*w`eIJovis_1G?foEhij zKtl*FL+zcENrB^(aD_%mByS01-B;;6?n(G)1AV%Zaof+FfajWkosZI=h$2BK^9J<+ zxtuYBH!w9EG{iJDT`FljF`N&5SdnrT%Fkq=AGTQFS3YLvbm-Dc1kZvKjJTb5%>}6# zH#PF%Hvq~OSDMi1*iO>Zi+qKu9)c8GO|%Y2w^AcyWzL>SCrh>1d=CbMfK=+ju8~i) zHozC$$(CcAi$o`@u*I~fD^UC(Eu2Hpv1oFP%NHXNb$+XQrN1A(Czd3dG7@74YD2|> zn3ml<+&$c6K^b0IF~0CE5dzuIPMiNiL5NIuqh!?w5bF@*2GZUAv9o;h$fqn*|MVsY z`iJopyG2Xf>BF5ZQUT+(mi<F4)b)laMyx%nX|VHA{!gbf)7HV<i9_6h_14`1LhZrO z{!qGD`7mDTD3zsX-7DYi=f1_WpMWvHm#DVb+{Iu2OqYL68D1PGp~bVQMt!5!m(WTP zNsNcz=3TolIm6@8WM!dS^l8Z;4Q>Due5s2aX0zMnAY~6i0O^E^eFcm1QZY!WZt|kP zd1c(mykV;jIUHdG&Au|(9?SdDB$7aLmJ%=X&NOZ3dElem1~wId27C$g8bIv*-49Ku z`3mQS8$c;$CJ!&0?0$|UU4c{Ylv5#RKf{-F=7E0fm!1W<o&aDf7e{qt@*Af4r=J+0 zVFgbD+|pY{^q8*MN}vFX_HC$Euy65Q`+!;m1kEda*MXWEH$Bzjsf&DR-vo48&5KqZ zI<<Ir+ce^gpW)`yKpu3YI5$&ch9)qR-O?9WfI4h?$F2}W9qT8N%HeF@G0}|LLaXeA zM~Ew#qQBcj+ba{7&PC-(Nlx1vODLcuMrV&||LC)OANQ$h06Kw>s8PL~8PpCpUQ9ee zdSg7ro?h2lcz^iWe$Wrcb50SYG9fnmz^qNLb`qoV(-86r<rjA6%^yovVbg<Vjd{B5 zGjN7!^^a`ssxFx2VG|Bi3{gr(^v{<PE^|BFTLVFt3cSDYTzzth0aHQ|`?{Dm$ErHQ z*_^s$|6^=j*7cx+NvDB1<?)AIU;j9gkG9diWXK?-mEoz=i~3X9ZJQ?wcxx(Jr@QYM zHRAH?(h{+R^Hq*)HI`2_4RFd|aSb7cA`P?YDFVT_7UM#n_Z*-w=Q#x-{^zy>p+FpZ zB}soP?kC}ic1?2xgbzv|dNm{<S~#;KuD7xTJl`<9_l6Ke)$s3dh^P^3d+3byfkokd z7HRx-+X$zYXuIzqprvK&5H+8_>`Hhlx{~S6%v1G85^M-XN;Td%s4l$wq@ae89CPRM z)P&d;k2Af8&9|oj88pT88eP(gzX_yIb)=DGc!3;H(jB4uCZLVd5tH*GgYLUE2KKbi z&e{4rIoa9SaH8ll6qqw@a=~_+P{mA=JOQo?cto*wM?noqx2`e2Gg{|7arWAqSn!6d zNO1%apPir2|LC<(3MO7BbB~yh?fZfTblg5q$!tw_W?VPMZDytJilDOXo~-n<j|l#5 zY0P)=IEhlS&K`G7BV`n`nz{{mj(tNvYkd^pKcVZks+Yx6@khyuY~>6vrX@z>y-5`L zn}`mgJraZ0<D|WekM*VE;Do1PbTI8Ep)^msj*r^f+9I3kzi((XZ(zM9TDdW8bcr!i zM_gE}IXik^rCJqgqI1K3+}*BtRV7_=9voSZ?jOji!JXax!!~A5EnR;$up^B9g2xOj zAqh?X$@d=o$#8T6i>)9<9un3r!BTz}RFa;E0)6R1p{nJ<{Vm_@H?u5yqVBDjR+LSX z8JrUI(Lvym(99<~yD%v~#)eD)7r=x$tbMy>RI%HW&ka^N1kpT;m5RvLmB@aj5{47g zUR@qPbB8etJFF(qb&XH@d+gY6KI1mdp;-X0R53s%wT4Zl49YKqH(ba>vY)y>P&0u5 z()2xR3#){GI9|{h%QYrl4d^VG74(Z(ZLvo{LoPT4Ves{ue6xLuk&1}_y$+Z=gb64F zFO~L}!mvpZ0}aZfhajU)qDSMlN8d#`ph^OGCi~g<ZR2Fw(r3OppFmZr$<)eC0`x6; zFIl#%A>Bb3J|+nayoY{m-Vrj4OfD7=WKj{<<xz2LUo9DrU(tO>PQi|dae5yVYJ@Sb zHn=e;R>>MOK@1u(Y|%I`YripEg{;UDl8-p?=UTn!X_z{;HynjEQJ*=o^8$^00}K06 zdRllyGljJ!6(btgKa-~B2s$Ai+8^SrR~)4s5#`QzOz~Sd7X57YKa(Hct2Fnk5`6n& z?p5?E^m*A$&;7ye@DDi4C5vC2|7TI+36cAZJZ<MGr%)Fkb6&xzwH|G|{YmcXRpZs{ z3o?(r^_gc)cYJ;9O&U|&zs3bjb5BOiaedp#4zGy4AV$9epwEH|r9-*JsK#C@cyjE7 z#NRJAEpHWMF#d-I%eNb$oX#UM^RzxGO8bmp>x==z3o-5U$uOieT%B%h$@ZOyM{q|c zd8p=FKmYaUlR27h5e_6f<_AqrIe4oG!6m?9;gFsPXtEpR8;rLr`>iV~2w5kkyu;rO zw99lkXeL*5K-0ql5R0BA@1fTDd}0`(L6ASV^EZiiMCvxmRc9t1pK_MYCx0mzVoneJ z6a7@1O&s`LFM+GnEEVBYj?dgv%RNxN*7E4$-Zq-;&@oK^Js$03QD15RT}BOL6L&d= z2FL-$<)$0puS_a-v3>8R6hF*y?&6>;`>qNlb3Z2WU8j*i759}mAU)=fnXMAv+wO~q zWDHkH8@pR9HHZJPT4Jnl>^((fLu9C<7p()^tMHjL-}Xr2Yz>}^>8!0$rhIQ(v76$q zHp~k@_|&5xjPfPnQ`%Y!x<}3?{QQY~gteZ*u#(NsgQ5A!89yJ^hPby1C&hl0(J*~` zSda&m0>y1dVc36I0Fbfi8OFxO>D+b>`@NlLCQ?P$5yz6yTN{|fsf_>nhX4MOaZDGo zOnIzr%IE{GPjsKR@Ev!%_1Uf-eI;E;vD~oeEG+hfnQ+iWZ_6`iX-LX3t8P^1T8if2 z-#E;I2(E&_{?9?S$aR9}{L<?dva>HV2?yAW!c(?E`R~2lcVDKO)pj|Qr6AaaE!lb& z^uiSa(*i{OFbdD%Y~lHX5;k7vL+or)QquGhCy{)|q)TZ7Q}8AT5Uf3*GwHp1BuRIy zrFktn+^q_XVa|=>%VEY3QF6p-F!JB03WuOfP+!^sFQTZKNVRpqLqBPZ31(#2WF%Y! zJFJ<JrI5$nT2PC{aose%lmS%8L~ctBS)#ksx4&7Wf8$BN^*1Un4o}<0oCT9Un-B#L zr~ai7llrGAkzjahjW{w>j*`PE{R4w>vvCm&_}1(Bu~o1cd>QlefW)(AA1|xqFXfYQ z?$1Z(J4#c?7NNtgAn3hvXmfefnel`;7vKAF^<9Bg<X~J@wK~TRr;ylS;<sUh*lRY` z`;r~p<SHE0*)es}be(la{gmL^oMw<h@1R9;aO?J@CLCPDm8yN+wTppta63PA;FoeX zUYAUTw6a@icOYC}1z&mP>iEWUaKw%?EOI@;IqPv=kv-<O6NCCNyc|G{`cPQ*WD#f) zI6Mi(1i6+nw461Z(ZK!G!q4HCKH`zJA=QYe0}C&dx*6-Zm30<|vI6>20G(z_Sc^GO z&RJg0w@?lNWBBe4Nl_7c=PS8in}~rq{7Cl=F)&w6q1oo8vUhw@Ytiy35>R&+q?U@^ z>R%G}U=&0ef5WA>W#)qq<qp0eP}@bzedR&<5}g^XIjd!WD^bYmL?r+zMza9qNm;T_ z=v*Je^f1L>8-!r$)~<AeCJHCklgjrlXW@h#Fll+SvPyWrdb@#ozU?w*e$Efr1N=2J zqPSJbRo7H<$zK}&lHE%J9@n(GgIw-&Q}O1d8`jQr4exk|W#%7f`1n9XjE0Yl6+YS= zssp=r&WHFe+TtV0o<zmX33!HnIDLAifDa?gU$Ir^76pj+k@e>r$MuwHlr_;5_GqF| zE*0-92Y^jzhCWn;zK}0|k5C`%W;Dd$0Fh!la#)Vzk7EJGkOUfAOq(UkIBVJocxW*( zBSJ^9PwQflc2!z%JT@%U$0!>=G=SZNNi2MdUXekCG>r^9MvB9#C9*4Or0PB{%olRG zWpf-g9^Ec)|2L+Bbhidx&=o==(Q(rZ5i)}-BYtE5v|OrTqJWh5Q*<J=`iW`?`8q9^ z9#i3fRF8}^&1|5Uj=5KW)k_b&L6){7-wmpuelT)iGlhjbUeVpG;P&0YL<SA8BNko^ z4aokO1rE}lv6cU3)0~X+>3@T{`DI0gxKm9(w}A*yQ{M@vzKl3kmr{ylGnoH;7P@g^ zWrO-{`Qku~w%5MM{*m2;1|WfD7Y7EWh5V6ZQZ+|G#@V<&VyS$m+<bVvk+O$ntDt!( z4bdR8&%^Qk6)vKM-3e(K;knVr70<+@V3m%79!G!rWqI^{(_XGJ^d%)&Pp9YABBoFt zNz;%dacK#He1FDfAtZGb#11u-Q5Pp5AS|xhApunQQ_V|NGX1yCWsm8*qlPK9u$h2{ z6l;{7lQZyP<pgcI$6j;S+lk)D(O~8o4Zn|Y0(z**E3i{bXsPAB`GOTmYeK!Fu|r92 zUy7lLJ_w(^hJ2!}Q`mXaa({5tg^Gkw7Qj;~T}EM^cBXbwFVn%ZM*xo#iKIb8XC&<B zo$~)g%lCNMqTloQ8Jqn(=Clc`$K@CLF8|4Bl*mgKe|A;PL~$dF0d2l)?d1Y8T9oxd z*>0u4rzjoj{o>-<MKw1g9JztCV6T0&)-FcD3_D#Zijt{a@8;fM07HfK)YW1vU%nLj z0?iNo5~(a1-(5bXKaL&jUjsd)jf!p1F<X8@e~L-=Qr>~m@hEsEAuID$f(g&k1A5s~ zj}H4#Q%v;Axu1-DMtu%Mz6hLnj9w_Sn}YhV*7b|V%chg=p<@@k0GGy2YbNkHdv$R! z@|TbjWa`T>9M2c-gJxqOqvOe0$L=(viZH5%tM}T;0q@D*)cUu_7BD`%(+{UNBdMpH zpRLLEhQI$Dpu=Wy58W<KW!!&pz38~-j~!0VZN9qRYW00l<(}?zJn)I$oj<kfQXU<? z-Y<+^5IMN#-wgI*ecvK})l|<_E(CWJ4W2diUtLUWy|}%|-^}NJ0jDKsmPZUoV1}0Q zoT|9fsexIrncNsXKLn~W)Ihjkh%`eq$09Z>;oCCg_LvC2!XU2JE5di^Y3Z5HeFej- zD7*8ZAOb$l2MvC<*h8_^fA9k2()7=Oc^h@pL*Qd>3+sq=D+I&fqmg84n}~jpehZvM zE=Q(8&!RrTCUH81(n*;7mI!SI#p~XJ%Ta22QY$@QR-5BK>vv`_<XPe&hW7JYfBbbw zhY1nWeWGYu2z}%}-BCzHLa+ppYFd%x2TMPOL{@{MfTbEZ3YQ*3O$>z95nVVMe!$<( zy$&0HG|UU(kKd>@ny*{QXwqt=sq~k+bq&A59H<tXDq*+!0a>Nb(O^h~F8C4eAxRO? zJ%$2QuvXIrf1;5;mg~TGnK=6^6}iVKx>|7KBr(SxntN=UjWnAKh_6r*#xs#EyRxyP zOZJ5fOsM<WaC}Tl+A0IvYu794e%gfLGYq>tuFk~pU=U@)6=<HJ-6TIGkJi`?mP*Lu zC94%3W90;F2iT56)yzZj|I4hTyRiSyW2OH`nUd_p|4PsQoup4Si$-mF`Gn>Q29JJ% z`!Tmsi1GXR`#=kTp-0xS3KLCpUW)Sh)|98vBp-U0F|X7f$?+3t%NDW+3c;17i;DQP zMB#Y6)vbLI$di|>>EWm<)m5_yAGu3t>UsSA0c-gZUW#^rU;r6;?-+;eFh~pGUBJD2 zOv6p6VS;%b75g5K-hOC_b*KbKm&t=Y7`f;YN|gXG=#)}3d@sMeHu6TOWTgF|Oc+im z+LdlEgS@E8zNGfka0iDhM(h#vRO_TGtbt-R4%upq+zGKiNAL|%?ocCqtSlf~B!iTa z<=34;*FhO9h|+u)4;&H{Kxt>wGwg$L5LMSrzW)|z73Gj`@Q_@AE{I(;xX3b^z&90t z%!#j3l2^o0Ob7Gi9xg&Kpp;x*smi3bSZad`YmXDfi?%~*RyD%asB9MKichL#GF@&g zh2u}SZ&FPf&Sgfl^uv)2*WOy8fl4V;{7K)w-%T?+G?)@EwM64Hl<&#t{`{HhA2rll zpo_mOnpR9WSuEc{noJ>r;*Ck|!=`l^H6qpjGX$$~;vhEPa#_8`ezixb<E~&KrsaAu zE4K2wu*>S_)tYYqO{L5IwbSvi0F5Q9w<jb$l4FgP_$z#HAIs|SpnJ_^CmSj#?i0NW zp!<1>czQw~O+?b^7xCu{M0W9{9UNXMCKIFL_hUL7EO%L@ftTIbMCj#!fi89GvGtd| z-%r<~VeIKLOy7x@DJrl`%7TapHCa?$vgKaq^A?6RQOG#tlDi9sKO};U<Vp4#iLR~c zmmdupLZY<at^;1DwJqp4a#;xHKGiOH2gE-9&AeR^KEIahDU*2_RXzw%*AdmOj`=a) z;F8*OzA}hQauNGZs^#x!@%H3(X?|hI%_2zZDUC}0t0xSm<3>WJy3WYEx8t?;j^In~ zY!*|;|A4kxqSqRf$p5=j7u=&^4h=ywA=b%GY8!V>De2%5OX=&<-z8)+`4dW=fEfKs zZCl^mFP!{SpH1CCHDn#!yVfI?qiIqVr1}Y*VOB>HR>!^it*5MQ6ok`$P-`^F$dq<~ zo%M;KSIlnFsT8ri$ufc=u0et;cJP5@ep=fUvH}-PX;h%rX3wXfE)P*>%9i=kJ$Ohr zK>*0w*N}sDW7vl@0V1TfG+-s?gGm0S7-%&lOR7={)CaTCw6Ygsbo4zS&eX^|*2-r+ z`lgcuc~J$zIID*_Zfd_I`<+C6>5KLvH&5j7W7@)H^A7oqBLB*oP?nGI3~0xsvhT5N zge2Q~L0n@*x8U@((>=w+t>D=lR<@j>j}Xz%0gVU@L*Ng;vxCar85&W4XCrJgLrN;s zaJhH}Q4)jw7|MWEz^w9-UyU0kKN(_7(#5;JFHs++12c{}7T|~fpsV!QP_JUOVq3t# zi=TgPs1;7Qd4{j;**(V-0;ZBFvj5L&5fS3nt@<TWI0D)f8reJxAd|+wwufG~lp+lQ zq?#NQO8`bu1Sl~tJADhb8u-X^*bE?4G$|4)bn)g*=7YabMvU<D{Y#+VxsIsZtK$Qj zDOKa6>Jk^u+C9GI0pnfI*pJe*!(kfS5g^`S10~5)7?>_tuVGptU<i$<GN2>Iq-G`4 zO~MCv20brtkj6Z)v0OUY(i6TEK5rpwCt|c-f}9sZVem}EV^9w=4H|j~TB?+*)FRJ` z_tjc{w-3N%-id!rXektxd%rt^BM{PPh9zc_)C^Q2|E!7($fAL*^&}60tvv;wdIvs0 zSC{F0uFKo9fN~l$X%M5fkA65pdEa(gRwKbgsblZV!Ke|<w+fmJ*eF^&@qFvH#jO() zsdwRKW++?VBVcDi5J~ZoMcWW3K?5XLL78j3jNw$O68$XJOL@J9hN!Wkzq8^H2_wsv z0sd7b^pNF|e!RP~Suec15}&13c(f^sBqNY&V|c4xRO>SB9g%9Xzhr{tNZm-Gbryy_ zp+|wT9@BHobQW{yoyI^|eNxpO@0V+GAvUO$;{yqPOMb<A(f>7ic5Q*aTh#t1%r3a= zF#euV&f(DSr@A@{qjrFs(QsD|{F14}8G&gWKuRA;W&23d{l)eDs6cvVe6*+7We{KZ zYV;-X<cLFD@G(y9B1bHzK1eD1I){}~<h`S&vYYoP?{iN7{gl%}@)4nWWe(=*8fk8Z zKT-=w4n9rIf4T#g<161ZXM`$(#z(DSQx)r|Ba5XY^7q-bMTGe9QVTE8tpD%Ttfc;+ zrZLmapuQ*3A&>`<>}gMKEkj*<MudA7i2o7Rk#MMBe5UeFXQnyGB#?3_!_R=wLnDN- z)!8cuqJw7b>;q2Y2XMKAbiT=%;hYk`V3c@9*yr@nXOb0%+N;N_B0OsJsJb!~Jpt@_ zfN!;xq(il$io8}UpkMv1t88+k;>pl?8CpEz6`uIgMRI8*9eAOGj(BdRd!hK{+U;`U z02msjvxItcn;yXY&I5sxxs-(n;vQmojw?Vt#%Uno1u3k9BfsA3elSj*+yK&7qodjB z91p&e=RN0r)sVJkDjnXxsfvQ(rA{kHnJG^@h5c55V@mn!Bb$mf8UpS$P|;FW8<c<! z6wS)4_eD-XFcj5L#DE>v5Vepy25x28U4zLesR6S{o3OmxqM@H7$Icl{ieYxKr?_fd z6lEo`mg-GRNeJkke?!B-6yT`&buF%c=g)+gsuG79CeWJWte)gGAJ-~=F|?)<jCh5# zUfnw6Z7sz$#)USK&GFHoU-fOv{di`;z#MZ`h}+VkAf1z<sS>+&gIPN9z~pj=dpPCC zh^kkMxA;bib<@RJ$bs-A{JsPGO3bH_eOdVnseApScf$X_1*fS8IPw^=0f-~;b+~XP z8$Brw7Gse#Bw1km?71U|j<E#bG6)hy59_v=SLfm$EZ}_3zx!Dhc?lU}m*z#{u2A85 zvvHoqIzqXb1)wQ17j~auLNt+eKONNS0vCY1^CbiY>%G>gAt{o>^`;6ll0#V!&`#<( z&GbnoS(rHYKsC!BIAA6~S6phX%TwCYX<FJLhIz=34^2^NX3|;TnIw_5))_=u7B4MB z%Xw3bIB#g)y*YVIiaPNbh*&>~-0}}t0Ac`2`7(Gbwf5Ymm{a&C;a+E2Mu@nfKHCV8 zIv4;|Q6KY;idE<ANheW%R+PldY!)B0$sEUBMIzcLlG!)sW4{Pt6OL*HQ)20yj*NS8 zQivY1==|l56)%5Rt63(0F+-+5hnb6R@v*WqQj}3WG;aO%7+0fwrA6h`664a3N3Oal z9_MITjts;~J{UaCT=rdFi|4V>U#&67)eayHAdlT*yj0ky3O<;ucg%E9$zd3(M2Hu1 zqX<t9=WB<U?;<6;cmuu&-K7HRA6U*|(7;O+S>r#IhyOKzex~yb@3!)EEyZ*YBC*Kf zWSM7%GYO-Q>0MB{LL4g?tG=T>^j9cIpzc-zo_M%`Pd8>GBdQ|<4Lyq=-c)!5PuYMP zTmv$jg*x3XB(jJ{ohIEBy-6nm>uDe6X|qyg%mJ_Wbf9m4v8Q}ICD5|H<fPExF&9GJ z?yGBVB6t$TY^4QdXFsB#F}3KjiN{Qd+H*s}Brg>jjb<FZ{QFH|Z397B-g*0q*&_5f zOF<5Y64Ul!pF1LQm|iC(K0o^Kh6EK0zGZ>^f7AH7sjs?kTWPYaj9}y3*wxB&$2pWH zL+FVmAs-R5)^&+ZG^)n?c=K|Xfe|hm5<g12Dr+Amq27=n8q&A*365VNRS2SU4GX(z z4#iV_ukbeIa#toXQG?{C8k%xoo@>8CDi6F>uBbMB(}DnQdoGrcN&AoU01ga9F?CoC z=FPo$SB^U~-QWlKblCDX?lY)^hcpKyn+o<o@=pg~QCZnjpnZLx*w(MDJ{UhXQc+PF zMEjtKq-qt~RjI#s7dY?`N}cRBHY1>e*)u-kIKC!wdZh|eN5O~QOQt@=x%vQzvSOaT z14LW%5gVcrXOWiJFyahh<c4VQzEI-4#-@!^l(1&`ciU(S3(SSM0!(dA^%T(n9<%5) z729k-dduezMZhWu0HS2vXqfBo-u(A^E-zM0udJn7+Q-nyF}yXhVv&{aoJaP3sDsCF z+wcRRGJGlFL`?s?x!=2)?<#JS$&_Ki)S&PnP%SY2Qv^h2MgyM?%^Cz$%Z%D+^oIhE z?We;{o00{~c;5PQN8J4HGpK*gqqY=31!9+mayvdhv#+v~mGfY)XyVL}>zYBUNj7o` z)<9A(sxDV6xFdwaQKbvA*`&lfK4yU#;NS2!GrZ)<rU0R9EZwa1ZfOhQ>TyUlYV*K9 zta*e8a=e8wkq(&fXOV^wd8`_y_=QX~SgsFk+i)I5B$aAlH9zwmnp*V^nTDXOd*8IM zssecXM)FgD6~Dy<PUi&Bik`dctEw%#%oWj4B}|>NMHa<fdUR<13X^yVtSlUT)SWNp z7gvDfS1oQ8^`IaG9%Gw5NO4jUSz)@m3P+U(#m@yri28?bjY%Du#FX0~2++d}wghV( z`N|qoroTC(ghR)MWg$1wr~4ykg^x0<>gRuG!D}lGC;fvSnE^<@@8ePXW2hSt&rd|k z5!ULAzi2ii@g3D;9axuuJG2%FDI?}#ocL=jhEwPga>n6o)PHz2Wo0&08BWH4$%ScV ztsiotT_9UblNMpOU;|x@v1FBxbZU&<M4)ex7R1~-_ByQGi5IhRZKmV<`=jYtc8T{^ zH}=v=c5t*4*uz@-v1d9}=|3gs|6DH~^hE(P;Y&Shfe19IbOWDv1UAp`b2)K)g|P}U zSw{iycH8?|hSPrOLGf@V6iq9K_Cw;}1c{1gWGRLd#kaMVW2ZLK>bsH5i$4?6zBsP< zBuWNu+Gl{zxNPS`L=j(*{r{*TiOb@8Qfssnd@3nGpJ<7yV<tOk<l5;7iiXgQU0{z9 zQV8hanLl_h3oai4ll~M9|0jNh*#<UvU#U!HE8ST&LK19@TctElDD7?QS#%3?p^tTe zMJdkCU%pKYh@+vBWq)M1U?-&qqn)WiLc!RzDLBEQ^g3&X(lZ(&K}i<*<<Ly!6v26G zs~AqI78&KYUmOrl=72o~yKF<fj*W4KHpWnK0|d7ZS~gj-kIW|387kBr6a4w^4VWzZ z0=)A-oYJ`ajBdpbdz9w$A0IHnpUeT`yGQJh5<+xAYKV22BX?{FJWPjf$}zrqww!Gt zr%ClOaW`5$C6T+(I!sT=f>r4)jH&qyyna-GEJsh>DgsVha!-ZI>^i;?EHAhp15$;q z*?+rYWdj9MLD@gd%D9!J)OJ(FR6i`*<J!|w6lH?zq?Y(~FsPN4kcvhYfYqV>X}e`t zVcN+*OdSIotoAh#-UV}M{K|Z($SM){3rYd@y3_@xc|s#YNn4wEZsQ{k2by`I=&|V! zAyim78{T({?@yZKEuk0(y-_h(7$_J5oEr6LBT0NA+erLP@+h51!}sn3V}#q6V5g(8 z-*mCgnGOhOh9Jex8#TH+)1ILC<7(-i_*-81`DLg+i`=n`Kjy3aKxMwz|CSW|Q?tn7 zMPyR;RiZTl4s3a_IJ^=r@jos#J+Xgya`{p7G`mT-RB4S$+^vRt-{A9isggW5N)czt z{CU@2iAmI1HX15c7sV#LrA!WqqraraBObX##WL!KXokY#dLLk~LlmZpGe)(q3r`3r z;h~pZ+WnHmJRDZD-9LUM085kv9WiE#oWqO)fa%?k@5XnQK>gd&>yf$ysKwYyW43t6 zDNyr8k+mJ$5qYS<3*a(oAyDUaf!_Ls&ZMK?uX<<!C|C+mG99uPq;OO%hyE*IM%^og zXogp-ogXau1v8}t<_RZJ+Q!+GSBM=bH47CScbG^#MiGZ@QVbu}ieD!evXeonQ(*-4 zs{!c6Bml9*_2EuFf;Mn}bnS*zd3x{S4PKce$fo&7en*dWP*e23q}V+SWj0%uDDSU& zAc_8lJ}i4QnQ3|r*WTZPU0!>y#%vV)say`ID4iemEes0&`nP*Tc<8g)h`haephtyw z@_@`i6zWQuvSk$ei|E|L_-_NwYOpA?f6YZ(2sd`3g7ASW*+~-BG>7s$UDV&3)mIi@ z?^n;L^q_Q^nUsME5&yroOO;*Omm}OBkE|!?NDSE=|08MqS9p)t1Eg|4jy;uX`I_cJ z`S^E(R_*&~YnDa?qCB;FNOv+8seT^t38t1%rW}#Sua`gq5Ypt+y98r)r|J$rlS1+8 zp{Zko7-yA5>?%j>WXd-&HPZw<E|Vl8`hYee!k18;4e^qn>zTn0M|(Bg{SfoJ+;rS+ zp@4w-=%6QYOI#}Cg34`+*bam-dQN~kj0TSY)3mN|^~Oh$|CGu|^1&ER$LIti*^i1A zz}a11kxDL+wvTmNM(*Z;SbAkGQk@n!8;CfWu0u^D!?ldj{VhCFET}280CjZ+b6ln+ z);NE(!@{3SqS|dR7)C?%0q-iN$>j6k<T5Cma_-fTGTW%nsUfOG67KBtJyZZ5pwOh3 z@~}#1sctZWWsfR0K+m;DZv2s|0YE_fb+R_oL5<u{hT}U13uJ|K!P*p;r~J`A7Hmyq z$&iAbI6`NN!+|aI@ov&mGl<%*4H019Ku7@Sr!z)LzJt*O*ILBO9zvO{z+ff+Xz85! z@jGBjP9(y!*7WS_`D`Q=wJesKfawy<l4d7X8;cXP{aEQ*ab+ghixFPO#+qFuXdF-! z$RBCKIq)hT_9e<`-x`vJLxqqmUdz#DA_x{O<0GrB!ztcri!k3T{|%X@VQSf>>UT*7 zs*S@TF@WoR1*oJ_ma<I96PG_B%tZYB%n@s7!GGtK&4mQg`3j~Y${RMbR~X;?n4m+T zDjCX5!w?towkO_4p1cZc+@WL^_*J%j`qpkB1F487LL$AmIt&WGdxQ(yM{qanS0i)~ ze0lE=KYp;FCLkvwc`K+y(D*%d{LfI11B1FI07OqZVWV{n8Cx2R_vx;vI3`r3rQZU! zUrPd^OHevbw4drpN$nm0*4clmgGsg!cQ1K~dU9G(FT9~d*A}4QHUkx{0KDJBh*EbA zKF9d+5`|JywP!^>wfs4H5tI(qxOWXW+xNdayVm}Bx_QZ`H0`5xt#|YE!h3%Gc&l^Z zg=9W8G$c**<H`FB^|F9T!--_-#f`M<fJmC{jMwA-x@|et+-|pK<v{mZ#I8em%uZDK zqJH_pSvys!C8FBl3-P6&vVk(`v%X$uzw%oRiB`?uA!rZv=#XMaxb%Sf52&rA$}YC< z^Bq#-Lr>Fk;!x)%I9NytPdIFpBgalk6z^zn;};^8j(TN-+OMOfP)=oI2gWy(?>;A% zL(ky>Lj$N1J7vOG8}2yUCuhKIR5foJoI~qfS*<Nw-cvK7q=f_7fyN)`e#-q~1%yT2 z*2|#cO6MgZvLQ+<ev>TMH0vMeAvE<`K!tJ=+*&4}5yhPydKgzYXZhD)_o4Y>uObwI znv-W7b&YSBmM7VBZ>4x-6)2H7rgF)BiSQp|UoOSG${r}Bh-7LH7bu;|<(}Q1pv(g4 zetH5B7oz>};)nBN);TQM0P>nFhLQs2w}vjJDql07=Glr>ECeU}Cr@BaofOt&iDyC{ z!Cl(%%pG$m<BZT2i;c|Dpka*SGT+!~w7EYy&Go$=A%Eh954_xsX)C&v{9QCoOP+O@ zTXA07E9xY&?VkQ_$Zzki<{~VXV4VsE@t6{CwF`(yBaON5KL2hZ?Nynh|4)eccQri$ zOhSveMOfkFY$q6|>Q23AP4Zn2M*kpc-awtbErhfiE}15RYJk0FwvMI*A`vrfM9JFt z5#>|tfef(=qqSGn<MuEbP$9g6zjh_3z1R%c(uxtet^C1U#w=&z-Z#^bx?i(;8jlht z$&(10kTqnFQdBb&P|r{UqeCMW$iR#>gB6F1Wga0rf({-sO#2auTw`z`_}@v~@Enw9 zwT(!MghH?!j@L7@N-bKB$eT7muIu6uhH-apNZ<Jp@UoRR8iPeI;=Z)RV2@l6@4knZ zQAH5`$bW2?>;&>U3TN2kL)?cdvSoUx(J6Mr)1IG(D#p=3LQJup06i#B*bFZYPe!W7 zw)sU7^_%5}crXM;@<$#L)+z!sb~>u?9}#4KMmv4=@xy5*)b1zITo!;N@aRX*Fbu^< zGZ>%u(S&pu7bzCq(SRqYU?>pY2I{E3sf5(Yc@z&G#xWf6bvYsa<)gy*D9*+nr}`X2 zxpu(YYg#HboD$z)gH>x&7ru!Xd}rP0yRuBtdx66y#-<T{ClN-az0l`Bww#`|8{n_d zhq<L|B?X;V98@S3((^r7a5t-PHOO^8igQd%XI(G{YRqicKub(PhIa%qQ%=|jAs~e! zS!z`3EA*tdfHZ-Wv2P1%2dXux?@^qK8E1yBl%p?!V&mwkS9k|m@>q$k_}{Q_y9#2N zYmcbNMx3q?)9ZZ`*?$i&jSP?g>M}>de#u-rg-nZv9bcXD=am_Xs?%7X&!0w$(wIMA zsu68JLw{#Lfs1=Tz8;&FxtC}D?-g?kz7_(>nf5z(MNhhJYVKr)@>vMv4EpA{-iB@D zwH@Mgy*~-Zf>C;X^HVvD^{Ji}F*7q0U6h)w1T+UXREH`iUILT~`kdo;x7@;ecp2n0 zWk}}>xx+=GIy`tsBfC9RbU#+`()508f`NorRaISZ8((v83;p?`>@}{xfe~o-+ez>) z_w8?Pf9}h2Zpixj`UUdl1@bmD>4RtsT){-!+1lp)+Yak1#q&>*7j+Px-@7q^kJo+s zrk_lJWw*5ug2!XO$a6ohl9)zGW>dTOt*zH$`v!K`V?{T=L6<L|QS8sBFWGN5%5PkP zZ_n4;`rWtsyRR|s*4DJo@8WG|kjFb)?GNG~uVI06g3R2&e=Mq<kzx!py$pr!-d?)j zaC5ugUjCqJR~xk2y_O7L7QT=$=4EsFcZ-%TwOF-fIM%6vNDa-tBfNIhB)8M6J6!f8 zkk*U;{jmS1%lN$i=hFg_CbHEUpA;qdQn{}5>OLm~mu`ft5W!)uSw?pHjB?X69oX<q zm%(l{guz(~H%={?cj|IJt~0KVUZ!3ktgci}Od7`S3`3s?ebqtjK@~cDl$0p62X>eR zdw<ES8wJ3m`@QrNc_2H&%OjQ*0|t_xs0vZ0CN7rBL^z)bh1K@vS`R2?HOtEup6wW~ zwPQut?|KMkyBB6I;A)mdIx(&nX-#UX=HumL)~@BvBppI}U=o392L~+)W4O#Qvl;hs zc=7XcG##jw+aKt(KxNQ*q18u<K~y4IjFUCthz5H`JV=p9z-}9&!y@sW6?#b)ht}u@ zg-MUFlR%+kAidvtsrA(?O*QGK`Bw)F+824{upNg1cL&i_dAx2qGhH%vqf@jWkh2?p z%XRFZSd9WvyRW_=t=zF6Pgg=k$Aq%Y&8|D+GBf4AD2$h=3}D;Xp68>a|E$0NzQ#6# z!>OZk9iy$V!qpGKkn#$QVIhNIsEY*AyhG!H=gSO>N4bgy_(Pll?5|{*$ws_Go`yVH z>`JZUkGoMzN8#6>$xv;GysS=k4l@~bQ85-h3wg84aiOG1mZ#TYh<-@K^Y*t?;1|#@ zHcKLOI==39NWWIFk(Na9)fvYn%fovXtx%eb<?wEV{da0XVu$ENEBDW@wsor~3i^B1 z8XNZ9s=Ngy1ceHBz;v^TQ9kX9wj3x=`_q}IWH?9yE~yLW!yazwR|i${bx;o%)g`yY zlqo$OLP={}2m%ZPP@Qkuj%=4u_I{L?`tV)#-&5KlF7l3F#DzH7hM$n%_Ne)#Ej01@ zP;&@!o7TEe$>^qrypHG3%SdnYqK26h8R2X-r`C#2I;uTK8j$wXd@@J$s5B~_1`@~H z=yQ7)*b>O%q>+`^0-qHQsNi&%P&N2ZO08v8M@Ew3Rct5}m&ixihGM6HY??aUHEdz! z3VN;#l^<229+k-%>+tJG1*2kQ>de{^paH+%tSon$sJwJ`(iZV$dau_kU|yP+t(Blm zQwRN8Eb@z`V9-?bdgvXMJW9zn5&1^jj^N@_Mnkk%6+Y5M<<}YB)O_`Rs{$n8eTZEC zau(;RB8K8r?h+NessH!heP{-T*K*KBY%auCA?Zkc#k>&~HRdM*a(a1b-&EX*t4Ps( z3hWXwKy#M3=L&oQ|F~{OdhzSh(S}5CnOXGY#(HmH?F2O^#-E**_<A>7*tCV=|FHFz zL2*T0w{CYM!Gb18(BRT|aM$4O?(T%(8r<D21a}MW?(XjH4vk#$p6~tcxmCAzRsZg) zU3;!I=XmBAckF8xh$S3fxUb#>YE>D#KG?r=+l6D#{P}U5(wJiZc^XhKC*B_ZF|8NJ z?XSmZ&sR`<7f>;cg(n)>k>_udgfH?BGX=b#edP&Aj^l}Nfb5{awWqmyQPG_C=izoF z8&IqfA-d5vFDS~W0Nw89{O$F8^WN+Hev1(l(NtN*ZM6~K7Vbk16nd{w?Vn#a317c5 zaSrD1sO9^RrZ;a?(tBNo2{d!RBVF}CaWcE3x7YPIT%hXut@iroeUf0Q3FSrZsO#A_ z?<)##^iDNdo?E4d;)<u@<__^)#sb27z3KNjuF(@s_p)C#EtpGpOMfjl-P`uvxAq$5 zQ(ExO|NYAzdlzkJ-UR83xEF}a;Zz=x%h+|PE=H$~U%S`6vp3t4h)*JtYUK96jZES~ znYkicNS&=a>3mK4@b+DBJ?@JM*2VB%&B3oH!5bFWfbsN%fme-gtg=m%`Kq$E8ZLF7 zRwCY13}&e!^h`${ZCrX^<u={S-yXG)`KjV;J8sK>@2^ZD6o7}3$7LR$q7lK*eesHN zm*2LrwHo+iLXderuzyF8)acQm#d?de4`%Z0`NC}Qg-EQ2?qSIY9g_h<Kqm^wK<4=1 z?Pwem1hseF0$ms+r)~Dr?dyJe9~V@Z=u(8-@tk1LxFObt(z+S~kqWG2>gPGfKBW#) z77|u&B7%|oi8oj=7Cbh+@_>&n3h+T<UiBduxJgS5kRHHeG&?mSB5%7PzhaF>ni?zs zcT+KAM<`++!=Majitu~CSo8FHLO~|Ha;Tz>WFVJKML(+fHI^O?ERr&Xf0=x;0@^ni zIA&Buw+w5QPThm~ZrZKn$<c0xmRI!Bjt@PG>IYgYaLc-PpMSsHBruc$=Dw`L)&H~& z6zWhzERjVbJ1lmCK}CB<^{g68Yz)^AzyE)9w|^>JN1O%FP>rlbx69T>vG!t)*OmAR z%d}~DFH7#NvXGku7ET%ge+-JD^~<k1F?PI33SSLdb;Jk@5^&lM{TO%SCR8KoAR<_g zEuXXG@OZs@J!&(+!Lw2mhXflNDLCm9L5lmDV&`x3D3Uu}2GQ>NxQm@#W(aY@-eY(% zPU!8gSPxJ^aK8$Y;!iP(ed(j!%&D6H4$S&{K=ACh$y&kR!rAIznDANgat%BXzaN0< zkAUN{{j(Cr_^MgQy&~;WQR96i6~>A4TWUjUq*&$=o@jQ6Wz=DFVqN&YK72->J>y9b z>&aJDb>E#96q-=#zDB0aJ^>=3rDyfxmJp@u(k1p7KTn^Y1;|-;5CF2<fIfvGKXhT9 z<~THFNp!Lt`-a1RpFipeb8am}SdqQVCo&du!c7l}_$V{bj~EF#5HpzUDyFOPE85#s z@^r?sn526yq?O)8I<lJ2TIJyLz$>d|fwjyW`b6UUNf_ni0;C-}LodW32H4%~WN@WK ziwp%@Zj(Z>InqpB7G~Vre&A6DkXmuR5llC>gO^pUkFP|2$y)YzznqVNkZOw#$!e)t za{9nKM%>Ti77pVn-RO_OE%o@KN>TIglwQMMG!1C1&sOlQSm)P|RQU_x{qTcA{1Tv? z;`EszOUeF(?m|}a-_c?rudLl6AKgE}LM!nI_feq}A+r*q2zmM65J6Nwq-mhOy|AC) zpZPCc`1FX&1pG|MHcI$G-OP0#^Hm;woE=zbAId)=CuZ#))=0C>rk4)Oy>$3FEvw)0 zrvtB5H8fIV;GeHZS|&|n%Wvs6I_JYM+0}E<+4!?}fd(j#Hx<qghY$aK)etjaCpSnR z8hMcD7PtKpck^HH67;l3zq7{&uYk6p;?|of$xUakedn%<=b;K=aW){|^71kom?Zk> zpK}$ldoEFG`x~SkrTrnS9h>($WnB3E$Rkltg#PMze(z;#B!Vt?yZ>&@Zl6^H;M{d3 zy!m7J)I-6G=5@C3MHeZ*G5>+@q0{bt|Bn4<r^MKI@?zbG0Nr4wA53x__vmt@T*~I% z?`6C_(n_kDw`>@zY21_#vgm>gR9j*l0=8Lgsiqh6vz+cFx1Sxi(RsYgdwbTpx@M!h zMJnpWjl&ed`)GWS8Pp(Sp4kW3d-4%6q_hp)pRR8YIhN|aUE%FwL#sY6q6HgYb5}e3 z$H3}j-SR0x_c38IaJNeKhGc$`uV6Z9d3_aH4&EzNVdc3zS2sesLn#gvh!;pmlV@yQ zwmp8l>*k3`3MB<Ma&eshkf7+p)0W?k!<Jc*b$|0X-b{X`+M9*-&A4UyBchr?h$4V7 zqOhmj2&-$-Z#~AW!M7b8GUcOI(%0o`!iaEb_zM)xD;`P?7I!L4HCoAl@*;j;m=1{2 z9tX<I>V*p0WQ0cNNC&_qY}B_ZhLU0S@qTLh!I^dv*0R=Frgl@6o1Lp?r5q~8$$s&U z&lfX@%yL!AZ7$<}0JBl59M+3}LWNR5V%mQe>SH6LTNHAz?O9h4(IW--O52O|L}3`z zp;e89Ka{J@Y_SyC**|OAV#jT;4<m8&@Ry@6%cKru;Yya4=g7|NAcduU(Equ5WwTHT za8+i5R7ziF0ErwwdzAfT(VryY{SuwEC;;neUyCKWU^((!ZljCPI;?7dWHBklMZ(&G ze1T+sXAn}$rSVHEFk0J03!l?XxoYUB<<Rv1r&#LwqFqj6p$z6l(=;IY6qQ4HHGM79 zmbzF?$R(<ekzcud@==6_<GK+k)S|dEt9$xd5ogMf%>D<*9X>twv|gaEU+uhnuc}%G zZ67sv1z*h#4rUX0cqxZUkB*CNIuouBP+Z0T(6u4?8xoEk3%5JQoce2;ToGYDsIV-& z>f>V#ao=m-TRIq+f7T>?81R3-pZ--)c|+bmwDf|Lw(`o?F7T#KYqEVh!Y!k<eX0BQ z*=(}$2iRu!JTgo=J94AwVBz<l4&o*S#NzMsg{bA-cS^pgPBIBMNI2~X{9xn9VxPp) zti%9nzx-4zZqUTctF>Od1NwJmJ+)hFwd_=V#~&FmulW3|41}FpEap0X*5+YDCdF0i z{)BS3Ey-&+UmiIfyssxLH?QuZFKOr$TYb7Es()J~ux!We7Dt{4!giPqf?m*lFuQ)y z#FF(<g<1CQh?QoVc==>hUy}OeH{r1jMd^1K<l(#T43Le$15B*Z5gB-m*(b@NF!7jI z5Bym>N^UL8Seiu;Vt&Elguoxos_$pTSRDkc?GafK;a9bnNR2Z5NOJc{(g84t!hAz~ zltd?p=Kq>d|2bcCoXFUB1$J9W&~DmNWLQ5|W3B-ir&JA^%Iwn>G42~pdhOIPK+a;) zlwg-F9zEU8T5^4TT&M4E^C)NDnP|cebat)QV8^Jnohm7+-i8IiNnm9?E^=~zeA@>= z)zj!_m!Y0G!X$UrU&y~u0&!|2g?KR-w2f7?QMD-du{g1ZUd|4zPM!IJNNb;I7-M!# zK;T#A%*~p^mA)GBhpsZ*&46*w$TptMcXuVn<ooT>^q$$g0@m(%KxR|N1Hswic|Vc@ z;eT72`(couj#13hzlbDCHj0DYcee@;qackBqYqk%#EbdR!viKan<(DwT0X|2{4?|~ zQ}iPF<N{pt<=SE4663rOtA|9}$3(abv>#+blZ5Z7tp3hz*BO`WyQk2kgTXghuhZUc zMkm64$<Y2UtLrb?@uiS|Q2~pw@huo~xuqG7@vh<>F-%VHjj{V5@b7SGR)NpEcX%%x z^(dYPLe63+2vTwO5=GrLMt3@=EVtZ#)33wckMx`EZ})EmzFNCgI=DF5UQY$yoPM<} zuzof1iZ|>P^$z}^5<0-^Q=jc~p9`(T9QFNWW7_z*Wt;QU3ufC9o98rJz(0pkfs2KC zuhV&0oWHEBYA$Wxb@wy2#oe{vCi}89te&FzA44=Rdh~^=SvBs8SiNiz85FSKkCBOr z2|E2olsJDprG&v{gM?ui%!jTS+xGnTnUn@)jNj_V|8maqc+UmXGA;Z`?fKZ>qu7Tf znF-ao-|)r05ATr|9n>x266(_N@LONGy0n25|EWiA^&MY{>10mS8ldb|D<*{j_f*>D z5$SaPMF|6DGrZ>8cx$<M4({zd@q`K<3C$gAS5o9FDy+x_UgMrC)3~gt@az3~LOtYL zxthz1m}rL6ylBC<nbA8|Ca4&;Unm74<08d~15<A%;Q@n|U8Kt&6gQS+VKG6By;c zUVi_W(zd|T57m#L_+(4iWhPZ>FNZ_i%7`$UgE$cpXRraPTR(Js&NwGAANygc$<VDW z2x>!N2tSXXE)!^(uF#W5M~AT&G{X1=Y;1C3nQ5kBWqbRQh7qcH_d!2QFp2u}aU5nZ zqbx=gGNr{=QHj&RT7K6Gne`gky<6EDng8i}{`Ec$^)TU{$m)pRn`Y>4J_Sz?lg<VO zYVAqV@P~Gek~jgI6_WYmn`%DN_VB)nBipr*$_tf2?Vg@rTq~=QA&q^*_6Mk{>BsY5 zB?&M%7_;n(QGQV>b463rL$#6Hi-tAW3;Vj@K$R1+z!<S)qIaiYT)O-c@hcTnDcZ)} z5w$@tk$;dLsXdzU43|=n{Gb2jLZYGfH}pjm&-IJrze_I_;=xizgEP1s@t6AjBsA}1 zJ0<vY<Uo>(2e%)L71?ttX)`O3pH^T>RorJZiLPlIZc5eW_FLF=4d>KP_F!6m`CE?9 z0}Nk3<1v?^bU{yivYh!{-+)$k-D)S?dK<hQ-;G;O2SPPK%Ovfv<2LyHN@PU`ErU-| zGJ$mZDr#oKjSu@M%I>%H?T>>7c?Cj8Ngp10fgdRVQ0n^U<3Z5Q6!acV9~=4Lr1_`c z`hEh7-109sx<tnEY50N2R_ZlF=o6Szzbb`ej&g{*gh3tTI@fs07|4j!#o}&1)C*M^ z$eZfl9jQBnvy}TH1PrPL8hO>5HAp|x8EsdxHOh$8C<G2YQNavAaw(oUU`fBNdvK1p z>-IgMbbnhn9vK2nQM@HC*mhRI=?F`oGi-BbN@{53u^QzYNGPY@N9fRhd&;Pkd=p|8 zKIwZ)eh&Ut+eb%AUkHUV+}-UWO@o^z=70+5;GJDs4<llvIsOjFz#OpzI;uu(WQ)id z2@X=N*A(sZY+Ub#h~M!;MC|x|FiyL}(pFTENC6wYaKn_t(BauUKt)oVmz&Y5diY7Q zJ5?sr!4IZ`Er|<r<I4vZL>{l0gVWybk&;JfC~O-8-QaMWR$8s?8hO8!GxMYylm9uo zJJRMrAoFtFc7nY7cy$bynz2|VnH9+5^*A3PG(Y^v<9pk@2^WhNeZ8yux+|cPKMsVe zqfaS-K7lKa)joPT_x6sr#c<hmKij_h=NLDHfw&ffQw04rVc!EgYV7#k(1Eaf{dsg< z*e8m~1Jp&Q+lJRE6e+OCu}{=Jh4jweQlBc3mkirCH574Y-M@{$E}M2cj`Lvo3Z0#| zjcM~Igi8+r62Y>d()O9XDjpAtl!{u%#>yjCyb>aoiUhD~W`$L?IQ+AN)q8zkCv4lI zX(QG-zj~R!KEQ5?d>?+Uqxzco6|~evUdwAO&od|W@2c_PTuyhQBsF1$C2(i8IceWf z2`Kp$$e;tDV&9=x13-BIZxs=>fa23!zrAM9x^Y2lLg-sn2KNe!wXQ9?&<5)E;x(=a z!6=7R&kav+#p%WeHY%irT|5qS?6@ULtzMKh;z$dO(pkOnQ~4j&!0u8jHN?J@Z8l9O zTE&_42Xo(a9Sqn4oUl9QJz`3*TVJ}+pdF4wl85YV%zn92fj%L}5C5mG7FrB?5n@0# zu^o~-n3@5IfueF(K#C^@u=?43mksrRaG{DDty=O6OlYMricFN-ZRg4Bn}QH%P?{#6 zz(b1MEWYC0U?soOsaA+yF|IdcT?2mts$|*bGW3G8L&#Q(b1|>L4$zF#{4Mm^HR(|u z9D9Bia40Ee4d}Dbf%b92$uFWsXYj!4@$WI0#4uHkfi02=u`hM7)k}KKW3aaIC#x#F z?2c{goA1|eLv*x~*&N)E7+6DSJ1(+kFC~m7!C_)W(0_PU*Ov9>UBTYIsm+XEZ;g83 zIbP3LcOpamo8InC&&X=TcYjHs>GhTbN%y*0J7L_vJ%p@9tw<1+zqqwuUV<-5wtwi3 zpR%7q1X%kIR#v&^)eSj4U$3z5>FcK<rEfOt1cuG3Z)f)srvl5{31e@f9^R|3k#9Ql z8wq=+x;7PiP3;Q0|7Fkq-3O;J8O|1QM!RoeVNQBJT03Iw%9PJX-PzqymD?gy>JYjp z(yV)0M-@8v-~|IH=GM7>h5Z&-(QDgpdGMC;_#w<)DK78b>la?5kyRKj7_)51*BX^g z<)F(rVc3q)7{*(w2nqV6k-mxY80+9UlP2fwCoAHi1Rx&n_i2d)O4EW13q(|pR%*)c z{x1pJ9g%cn#?U)t3h)l<|AJ9VcSd7xvS7Eaeh>~~=Cr#kQ!e0aV3Mm^z}tnmSXKLY zp&hnig2g^K@yjp>cXGi<DpG(7sHc9@AxEF#R-4gA!%ZkbO?+bVpi?ttkRH8y7qepi zSQ5X@2gx5faPvln^d50sAq?A*{3zl!StRgL4hp!DG?UOpO1qZ81~#P1iEF7#0Zf6Z z8Uw5Wx<i6(=<YQJs2Y;k32F=J0DwYWg)gbL=+v0P2_8oiUg(U}9!1HxzIp2Qge<_b zlco2ExJ2awU2s95p^j+{`9`#Zsh$LXCLUD;k-%LMC5~7ZP{;uZB|Fe3F9r`M=<Yz} zfN|e7?2n7CWSWwiO0#~bb${iY%!ed^bWF4lho#Jv(MU@o_+kDrsT=xlIvy}GXKRI- znegU2HXxD<zFX8A^~fjFA+ls%bCYQ>h_k<?@SA};$EIG)F->j)*<jAecbRG?OXrwY zqC>_nfyh#dP79x)TNkzLm^o_3TT1KByLo4U{Ij39dw<HZwL{CI$$wTj8gl4u2ov%{ z1}aHLwom9iFJLcz9AYiJz$Aqw0n}vzhHwd?%t+)9w`vE&(gSW98r3343w+kMwi?3* z(JY+C(Rw*n#|fFj5)eo3Yu0B}|66o%$3f(B>#uk9LI2r}9g(P}ds#bw(UI~h8}D;J z)?)|`m#U`KZvIJ2ZYFJ);`15KsCv#lD}*_&UZM-0HCA7|j!b|XRK{I^Bm?>z?+}+V zuuUj3H3g~KMjg0qyKkju5~u3i$7@k7Kie(IJ#k;V&K$VeIcB12lwBM$%2IG5xbEF= z!^X*qpPr`6{H~XBq9U=N-QseI%&)jOUgCM<aYvOFPgGv*dWOqp<8SDJHco>$<L0Mx zkru9~LugHZVBtw%%vW(MAD!a(KD&H9g+qsQVYQ+1>Ap{-G-%!K?cJJfs>K`etJ&Lv zbk~8kH9$KHjCM}|h63&&*J2^;!(`YU0zV5UP!6BCo;F#6&vkGA8Cv-7Rqi>rsd76( zpw5tDaFNFE!L3KejRFoB^fWxlF<+Mt{GR}kC)oL#TM3IEY*tRozSVGBpF>p>JqQ80 zU#{;fT68jq(T6`8Wuc2H3l!T1w)5n_mqw8Huk+8Z{1=_Hygn(R>+T~lg>-lrvb2w5 zDI|}rcsPo{uB~LU7b$&h4NOcAaLiv#befCDip>&Pgiy2SZd_E9&MwUkmDwgQM7>2g zyLm7uvv^hdsS&-HCl1Z0>A5CLiE=S}02Wj4NZd?07RRIi6lztlsZ~PC=wpv#kLl== zNyF0bhn-}<<g{je2cKG)@89a1f4jwO;TbmmJx6k{>1_ze34I*<s1{5yV^kUbw<_Y( zQ?DI^GG+|%m#yZ*vu{Yc*$<{>M&Ch3%~-IuLWK5+5UdY9JnmKyeX+c!@CGZGZ0d@G z?g*Bxp3G>%HI$z=r~amVZ&pUqpTxjwwJlk$o(cA!*-M};>X##Ylm#k|*GCIRS-wPe zwR_#^lGv$oC`ZviZrs=0i#-vd6FyJwdCs_pL3A4($li<d`Z>XN8<PYAl1gly5iz&t z**v6~i0NE1=o8mtVMGg-Yr8#x=_<-x6dKar%dqF)yz$}2B*QRoww|@)eY@9c#>}>J z*NPd@W1_9<tlRHV*91Q+j;q_=;$Bydl_Ve*hNpYCQ>rtG$5}kP0e{;o5;odyEssol z+A%6hzzL?>*A%O3=CA+l=_=Miig+IC1tVx$zT)-qCp0Du)Y4G~XMd*hH+;8{@$ToH z1yLwOsREUB<)l`RbU(H(3SdVDqcyB&%#O6+5l7UItPd@WY^<yz@XJA~mF$eAtX||e zc<$*9*sERB+EH&+stkW|c5?e+H6kLwUM0rZ^4B1h*fej)1<CA;leqPNflAT8{2-ZD z`QloumH&DHRA_y1uI|u&M_`S@kT*HyiajErT#itDsRGuB!uHjFFnRuh0^U^Spfq`- zv8tI>#EBJ-Sn8WD_I%Adn!eYwFW#de^&BI9lgclGo$bV1&pf+$^-8XS<MS0Mp37@$ zM=*oSjvM=t0CTiwg#s8410)2Z3U@Zn90mO9V@((VqdOqk{iz)Fa#yiX4AC~!=OQ^| zumyA+X(N#EtKq!<NHF?EltCrtjX{i$iD=9^6a|nzZ54yL0bT>I$Sg6GDyG+zF$apc z19zz@iYkeye6a|M!6wT43^Mv*Q2csO&wI%E6XrVNW0zsb-0*;mpQC+lSo{=tyO$qh z3E2SlolF5pn@RwAfnG;gW>qI2{EN<*o@8P|#y{u1IRq917t%#zc9vZ}32~p)kx|6G z<a$RNh%bUIu1W=$3@1LY&+fHqGIkbv7=(G2GyQbH-{jn`7Hqp{(u4u^T34Z<T9WdB zV{yPke+QImQSM?U0b)*^2YMFQaTabEf^j0W5~;OSe|%$H5l8Jzm;t`p)NS4!dU{R7 zhER49BFpl)Ap+$b^Q8xhr#kyz7{}qSd<`+X3&5!3>O`!Aj^7Ol?y$TK4yfmAdCe+v zpVGI5tR4$}ci@nWO-XGDwiu?0{ud>ILQL+7bvk(0eeA!H<HN`%z*jf~llK^*GkrCg zQZ^y;`~c*Xc%f=E1&!WIFSS8d&yn9Jx9w<T09}vEULqgSM3TG}?jS2+^1kO);R<m! zHjHAMWB9-C=i=66PnMNBZ|Viz?jv{7wd-a8)|TN-T!U`f#588)%MZs9-CJed&vC7U zkcIvnZ7Jwfv2Tr*vCBp|=<ehdFji&b(~j|)i}mM8nvbbbpnPtfL7uqbUHi5(O9{}> z>km$jSFn_xU%gZ@BolrFviQ7WCWNuW<O(l#-ISUwd)lei5gUYK-yI%c-6`j8Xz(7L zf(t4Pc8$NW?2^hO4d5?EeCRh!KaCAIf<v7RCKaSQ#WK#f_k1MO{CuZ|lM?s<QTBL% z)ufw1;N$L-R`QVwNsIrHTonPJToK$eP&y2h_BfD!$Bw?s{RBvUf(#0apnHbM^4dN% zSD_%Or9;_=PEfG={X)R82Plr1i^oWbPQ4pDDJb`)`$eFYY9MX}rjM&6<b;3uyy^&0 zSJcro>1Pn?d{rdF%<)bMN_d!CF1H(_w&%|v6-?TSI149ET=Huu!%9tR2Wyq-AlK08 zfsMW)QwA5o^%%Sr=_6Hj#8%Po-3U8u1}_+-Kw;;_?nED59$t}O4|nypcFSq4qimBT zhegbmqcbA|)Gq7gi`OdLUnS6xfm|{ucY624dn^9NN9Y|RGJJSM_+EBWIWV~4I$L<| zfSfH4V4t>m&=}_aIe=kv9X8<*O9N5~!+ggAm=!ddADq2OEhmn3F`tft=mCM^S+64n zf3(W7)dU7WE-kVTttT(LvadU`UH`1+`u2A{Yg>}i+59!R>bvOQuIT&Ej|iWB6E=N3 zsFFRGTr-T1ynUrU=-F%Sa<ANfC_jHHhx<VXRP|H&j{^F9RPk!@@XzCQk?7@BxBWu~ zTO$3gQ~wr^s9o*-*Zp&z4oF*vLI2*1>C4)mkGE*tqCS_&{swVd4EK<xinphWJJZaN zYx@4;k1c$|29HIup45H=+!bEp@O_woQ<h2&SYv+}`=mNf9tucXAR+0+yw{Lp#v1)g zTeS~wTwB)Nd=?t^`OCT`IkwKDaYf_2N=Mzi@<#USDzldCo656QVq5<CqjS#bOZY>w z#{UkOJAPGmpj}RdeV@iA8Jw|X{LYwYjnIokHpI2#8|;228RsN1i^N${@2(&BJRyBW z%hC3tPW!~pAVWJKFMnsBzn)p-_toVHAUAy)Y+c4C`_l-Buy2Opec_(;(kP7!mu?R? zgpcVZHxTpT1b}E053aREk@+v!vmtHl3;7Y`uDD1lP5!>`U;xBttDEwkj={LaAOuQP z*%726_S*R5g*KRtlqg-!QAbQv$X{v!rt}5cLD&U=NnN1&dCnbr^mGd>J6s)A;*OAG zQ&27ex;uNFV=UAMKv2^u4XJOPPzKB?5@zn46<aL@+m;tI=IQ>dSK)m*TmnKfb8)-4 zgjKZg!Dcx99t7aZf7aa-&zwmm^JtGU*sMXg_@PfffCaf2Ks>XBO~a3ic^9_24@6lt z+RaS0?Nb-2&gmrf&ikd*jZ6f_L`nkt(BZPrgDF+S(J9N=D8$+N$lXb4>$I$F<t{tA zJjNcw3@Hr`X|@|TswG7B$Z2So#AZfwdh|cr%^3NEMP>57ZNqE{D}Zo#`;;f{)3*zJ z@>4~A<#xvJAYKbUF_;3&05eL!hn;q@a?;FTYFl4Lm7w|PY19K&?N|>zt2FQ{-$y!e z*aBRi0x4*bXsk}rPM520{lbfuQ9-kPIJr4Hm|V{8Yh6$115?5Fsa0yv1(<^weQt>~ z%47Yj+-K}ws3AgFVyr5|tnay;UCE?$*Y#7p`;X6@dSJ|lK8EOsey#CIwE52+I3Zv7 z!am1-L;Kxl%5;s_)|X5J##9lj)A72WzZkF{3OEbG3m&*|I<#E-oN6xL(6V&+fA(fK zLh5`5dzm`T6iOWWJd32LQ%|Y~iS=`)dlvsqfrTI0fXd|ftNmWkqYtm|!x7!S7Z@}6 zFR_mTbw~oDOl<Ilu_;5)X@fFA9`c0KZ2`8pLh^X++)pbtV-9+POO5a@)Ny5?IH`x% z`L`!s!>h*HwCx{+7{2642|JEw<ey;B6E+y*Ldm0~(KBU6h6eNuR2fpY?U^qNrHwk( z*4J~Z08|iK0j;wNYiV-Cg{M2)x4VJ6+RW_42nkKZVkSm)a@z6g>!-)Lb$Z?V$?f6w z)&6}@FP(PNr_--`Bd!U9kXx^*Yly8chfh97tPM%Brm@Z`FuTdemKHI=!Asg89##`t z0hm+GFZAXjoSqY(6#}iU>#0qsMP8h$u&Ol=O5xq~!_2o^6k6Q@`TWLPbjTsQE9mgW z&PH%7Kfb9qQNDs65b8eoaS0Gj4{t!sp9=>RG$kuOO#UYSExsZI?puHLYuJG6Tcbr_ z;JiHxHOIP`@p~wV?NkEUg-W1z1mWuUEOS7F6k)PMlY`DHabO5yoDkMMkR9z{#O)A2 zYhCLSo!66M4iti;0-!42#%vWx1wE7x9q_XM?DZA^WiHw6kh?F?!}-Esr%cjKL-AoO zL<Cn1{QNec)jPg?6{Nv5MU}Z+(t(bZMu}nZFvsf?)0v^W@1E@Qc|y#cq*VGHhE1SU zW%}H#Z-Zg<f~*fpz06oTL^J)7g)C(Bl(=9bk@tWcKoTL;R$$X%A_-R2=Pt8Owx{dN z7x^9e6?4-1KyhZW{I5RtGE&g{0m>0Y`Gn(`(ZY(jwK?EFeD5!Mxz8bwdY*e{LA!BZ zzTPb{{<QkSvwqEU*}M$#UH|KSXX=gl9|?A-llL7}xon<1zjgP#ce6RK0%f#hE!i$Y z7s4e!@xAT8^|+UI7@pIxo)fOQ|3~1}i&^jehvIGVbX~%k{YT9e=mO~y2w$(g{v-D; z*Qkzj*Iz&dEo_H~?0x62XXlac1kL4=EKxH5@jr8>P@?TC>?p{b?aSQ7KPx*>4m81X z3ku@JYo6{?-oT04+O~%_pD@)Xir2+2`5_f9cl_Fv`<>}hMS?$`tFq?$zUzMDbLXch zT{5;iyf0w*P)NYLZv4==H;i;)PNTzheh=FJ2iPz@33}}rW#70ZOm#kaEj=9rdmnLb zQy*UcVLd_rsF<*}4ctXu^OOJf08te%g}%WEKe-0{@1q7?<&-8_`#NmtDCH`m`{6%= zKF(d9OIkwZ1~JwhC`butRmWH>jI0KW5SZSh1c$g-_`j_cwU6kz^|v8g^CQ{)`hH9z z!?w=1_YtSy!B!?m8az#e#YB?K2&=|oz<{7q+{3zvnhl{tAY|F%x)x$oeD`)f*CL1~ z_%C3*wt?w#L33(r2bH%;f!I<7_UX>Q-bcYSy?9oMD#216cRJ8Q>btyV^ixwCiW+^1 zDM18N)(nkoVTa&j9@6|pQ$X%#U>YqXhehp%)Nm%Wf&Ep13M~6Uq)0}A#I7CoaebIb z@(d(Obp8Tm1awL$(Q*w0sd1X5s9G~hCVo3AFAfLDnaE|X8Nxlpcz~-z{Ps^@AoTvo zo^-~k1_X|^H9;>0(T9I>Is?4(Xw<kJzG_}fB+utp>doE*WljO<sRO~iT3;pxY<1u> zXmpX|KFu!CcPjMGXd8{E6nM@YNLD84QRm&p?_!Iigw>P_ufz%mcjSZALqK;W9xcDL z*<mN*!jf?|s_$SCAJS~$Ap@JVNzLGgX};Hczpr2o26Esl;{}5{V+xxX0TM^h7%LG4 zH6{~vZwaYYwIT9^x$2Q0Om!&~7YMO1c!e>6HS95gAuvM1s{&D}9}7mo<++(x_%-^P z%Cw>9eg2$q>3FKPW7V0j{Pj|hnnGu*#;Yd^$(FvwYs3>XPKbu~nstMUWptL+8;8Fa z&4fiP^W6{EWB_PF`l~Bi&oszrW{ea`-2<Pux!WVEE8c(7Z(OGHfr*BFDVHvqyy)w$ zX+kp*6;J_IOqmp`L@87A(d#nf9LaGHJ&53g>)Zk1gr!G*DycoJt!pau6#T#iFoW7D z?SD3>FZ!x{?q9Dp;3LsijL;PVi~e!2^^363LmvCiUZ&6(R02j@lcf9q5DB=g%f|O? zC4TaLXb2^m8L=Wc9GSnw#>~4n#~TNzRLX}pe8-rBEzZ}*nAzMCed4?BePf6fMhE#b z-nrYfmkoI_Oh^hTW{**<+)v6=@<NFT1$YF_`bKzDV{~b^<@){jMuBFF<}2)R^H|li z)F0svk}fnV<!)N$H4Z1c*gQ$_260Juf05B??x_eR)8gB+ylUykb(u;^+s|F3_R>aJ zsXRI5Tia~iUIAbq-J*5elw1H6eBhETT~`1rfLgs^A=StB#*^zIx4O!vLn3eFuT=#} zBnRw{BmW8m9tr+;rmqD@O|W*<P{DDkAp+57Hjj7=V}1{me_RY;8O+72#Qzc@7j}yw z_RpxPXci}x^f1;)pomgpm-HzyGM{o}MS=KgVGG3@TRF0e$r#6zAB^Mx>FJ=KIdDei z=un+4)Oyrwf^->OiEZ{c7#ZE=!&=PG$9{SOK2v_lCPNF%Z9&wTzxWP0V!b$gN)Z@! zU{4j>-V~DCF1QCluYsDH`oX1cDEulIhZi&qLoyBs+R-9dH_EkoDM#gFY!rzcqRDnB zp&Q0Q?L5Zx2H}F~f?vlbJ7pFK(T-0PK2;O(9y>4MW`9vWb=rATg08r##|>kHmi;Qv z{u)K^Z)gN{J;;4iu7mW8Mi$6@9JuM<ZtiiHq1j{7kD>Mxr`_8bN-cbS>SM&mEhNLl zzbYh&+8^X1qVaKh7e=6w9Ja0#JZUKhQ<_f<7)^LXM9`xxMw2Tx&EW&DnwQTVee3^& z^K?p8A3PsKy_!8t1o3|A<k`KiEx6=TU?^K(WId1Db#8=|c@rkRAZR`M5<d9Cp}IXC z7Hr#-$sMbD?Wz*H9HzBDrjei)wm2^&rEPyoCTnPH*@suT@=`dn3?kbYyynWkbq_SW z)j7Cty$3;V+crGMuerwV-%;)u<*E5r=OM~mo*OLa?}$Vg9lOWXmV-!Sazl_@K*{>M zBL49`|KU3c!-(MxJYE?8UtP#wUBvfLRBvqczc)B|zq~?x3?MV)Od6cL=Zc^S?w>Dh zr;bsRvN2?n9B%6x_0Hd{p6r8QpoI|wpWfq7Z{benzE8G+Hd;))pZmS>tSc)1v{Njn zdi{nx;Jmz3?b9w1E%{FpObV{)S(5jcHUD=L0aXJyJNFmvz81Ut7#w!=@bcN{3EVZ_ zgr(X@VsH$Z((_=tkFmZtq|07D==?GJ5d+)vHcF-*o1~M~iJb=yE47EIz~f~ZVq2ML z3ntAtJ+!%ShBFz+LfzlV?GJghI9dL^VZg2dEExGSfVZM`5-95B;|Jd>GXS}LTJV9` z|L>-gt<Jx3f7<<0ehQB~rXHhK0X-<sZY@Ws5bAj9?5HX@@;wnbES{!+IyZ^ysV(6G z@58{m0geK?-%H`A@~+C6?yqI6V)4KkdtY}IUj;lnIs6dLynATb#Y9s0zR@uK6~Qi` z31vut8~a?^0MiUWW>*Q?r3vN=ZM!dzp-h{UY7!pzGj$_HH^a&*u=VY6g5M^p7QloB zE>v{oIkXCrKPQ$_Wj~&lF9e1U5p!W)@5$R?aD+{m=<~#B532w9o8eR^SZ7aF`vF(~ z*d;+Jc>{@Nn&OvI#3R)&0eUnkW0<=E35Vi|gj1p#fm;@<4{<{LYTzF^HLZ9;gu|g; zqEp!eg$GJ?IkFB0*HjDIQQgr;*9hC-%Ioz0meHI<B&B3$0no0?5}b8%BRP18UMv-i zBL3OVx_HPToN+ANQqWjrPINEQe|5?dP+2CTAaEFV>2Ki}%SyYDSqIdFPp@`ZlZb}- zIa2gGmcKobp=dm6U>k7kLO-F@Vn@OEO!^R$>^S0gDVvC+Rw!}c12~rRr4KGH_xKQ! zgs?rxntfm@!1hB0tl5j&SNyEPZt(l=U=FcT`jpWNyZQOV-YP+_q^XqoPQVPqjHSXV zkXm_LIhG$(2rXl5bJA>(;nztGLvox%$s_w;g?)rpFLn|-Nt`jx#LrL4>wQXp9^q5; z{_7c|J=pUnZA8K^SgI`*_1Xo8OzI(Zf<@g*P+BD)9oq<95jDX|%!Zta(8y8C<|&H2 zTpBt}j1}H$N>&9LV}upMi~OBEbj+%WEorlHxYs^+Q-w~1Pj<s}ZMni0P$EYi=TyO@ z$_tg^wxl^VMAw95>ZpBACjZlS@40^8yHoSxYSaI~)qc&zxcGc1>&>}z_ixX{kniqm z^kDAQ;j7H@LT&?OasP0y!DHR1cHWyO<*aRK>{yq4iYIAV-H~_o9XjFF;Ex8_6iX>o zOW>nJ;dOB5MYZ<+LHDBBBhga#Fj(0*z_3+7Vzk~zvvU#_=r!wF7oCKc-oj*%d8j&_ z*mYB(0;QlCj`AbV>N#joipYEwm+AEBC6`PtLNF}f<_VS3OZU}xhWo7S=@EAy{n)pk zcv#=pV3eW3M8KO-xPjlv`B!=qfywg|4>-Sal-OVA*vfx!4S4w(27!kJE|l-VrpOg} zPmu_hF05>Ji)10k0Z9C?_VgynFV0(}z!1YYwi`Acco?B6_KbFYL+6iHs{SVrJ-tV( zM;(h=Q{P$qL`7t)Lb_8R%n|jRl2UCL=sRmPA_$uySGcI3-V=b&AEIs0wu@ak2c3xT zgEx%GBS@5kO1%U>y@g4lmIZOHa^xA|z3;6AsO3XLsz0XLWf=zIDEQtXZqL&(9n_2J zrzQXC=U85@{82@l9LA`=s2lE-oL5i+_;C!<IiBJ2nFQp?bMSu6|HSQtlx#7;O8zqg z8zs}HFkU#^yTo{MQk$^%3dy-OMu)wM|52O5OiXTk{Pkp9l&JK+IqM-LD;VsvHb2*n z_#%_oDo;*%UU6stj&nZXAGP+c&!^oJoyYj4L*u~rTs3+=t(NYTo+FCgJ=C;yx5_?0 zX1yo;vbc|5tDR%4U(v3kpKp2<EJM>%l^i=aTE#MHy2@6|rue_U_n@qivxbt1l)za~ zOgzG~?%T1q2x|NbA|A%Y4y4+kb)V-vWc#iSQWfP1UVHxzH&MQGwZ?%+`i&teS&u1Z zxck(Q9M3~{v-is<-@`9H?D5?fvFv!qw|{4p3-WpWT9}b%e1L>z#j;{{RNciT7x2w& z_zLfG=deEoGdf_}(q#Je?X>-+{^0(g`v8OTLlFwecJga@n?E?itL<6pWk&Wz>Dl9` zeSniXT7rtt*GGc3&WK{4v*7==p1dFe>s{7g`Z7+9{xJ+aavm_2>xm>Jpv=wgdU(yi zoDDOOx_rmFuQjAYZ`kyLlr`MNfuS6kxB0rZj59zOys+1Im?%NEu1(fsjiN#bj0#k6 zZXHD2Q2_B5+NFHI;>H}gp-OFsiL(~9DKHtcv}dkewBTqX+Q*Z<xCF@H)R<-c@8Zi^ zI;L1h5!=3#bd4K`_M*By|6UYCK}Xco&}<U^5JsQQp5d3LW@75Zr-DdH^EEg))=-WY zoZ8rhj};ihWmXqc3Ap(7hso_5iW5N52{VzVQ8|kgU+tNYjb3D8X2rr1E0IF1Mqqbo z+!?JrEf7A8%dt~g2^_URt)zoe^m8po!PU_|Y-oGG%j#aVu%<&7#!AWbH~sh%DLE^U z%V{gUnDn;=7z}?h#BVZN#4lH28#>dM({-*u540FcRg!d8`ynD&A?lDr*ry-d&7&`` zxDh{Hs^R;QBrah^vZPX6YQ!$mIHPF<yBck2_QSjoREVHi9l%dM<aEm&Q5~`POXyIq zoSjx#JFvVM&F#i18kmlL(L<KpY=(fV2n|GYBAP=d7ekNSn^ha-j@e7J5H2aE@d*{o zuw*7v2PPwtDs#^}ba?>L{{r>!H)o;>gVmym0P2`DRrQ78x))7|>0G2FCV<%^XlB}6 zzGB2;A{`;H<JDpC3tdrdCNoZ)xb)r<vHa9oTyCpWHLPg92JQv+yc3Y$f_vp7<hzpp zxGuDovlx>JJZk91FQR<dVO2y1HQc#M);de&-)nga+}-8Xl8y7!%U%01l5irkp{z-k z_WMD}_XH}6p>h_wP-Rn;D90;@@7K})^Vo_ZChL8AAWicR5sgbD{~{guPYMRPyi+x! zJxNw5Hc!}~e5tO~jZbPu4ztkd)pUF}LVTZele7i<;8F^uN>i~tqv5~B6_ZQg{|RX} z1#%23ZDJc|Gr*foV?F*`s)_5n{}KgREC|QB+vbk9h_x2_8DW~~N`BXx*+P4|`)D+V zD!j)b#X2-ZHes<^es<0Q{nNbvR=A~qfjdk5=(eZXGZo;>Rq>eVnKWp$ScK=yVcJh? znGlXaKJj;&Qz+!ctxP(#siHrjKht^a=IK|xPprF8ER@8>pg$M%iDS{S-ey~5RutB_ zQ}rWJvIIGGd{uXc=LcGTMcm&|4~Kg>AA^h0ntH@JAZI2KYCCfGjzZvAd49$U&39%D zHUOqaLHgNQ2M%K)bFY$jRP>>bWx<ZhqDm~s!b3>YZ$(gw4&-h!kT%TItVPWDePrqj zpa;rDWD&}{WOd@~f(xvW%><O`BqCEoY;5vNcnzN?@Bd`aPS&T)aIeL|@1y=Xw+~7L zT!TxGiasXv$C+xxy~psDHS3uB=F3SxBl)+dk;g(HYH=syjd7JIER;$FaU?7wf8>s6 z*jAc=B|+*oj9&HE07k=5a{xDx&QjOEb>D#=Ty;Id*!NNKK@9vC52gD=g!Jj|j!F+0 z9RTf)JQ4CN!QPb!@yRd|+G<a$d|VT@sZfT;qC88kXXe)lo5;-+-79XY!i@5|eL6i} zUq1cbr(NZiStib{`iH-e<e-ExlE0V{Mo=-AKflLUF>}0u*RM1@=3y@E%_BVGgRX1i z<4OduKfjaEgk~JVFz))=n6uglH<bJI0=edUyB4@8)*}E46TYhhXAk9d6zvqP-zgO- zCWg+H1th)p5rwb3NFQ8U6Yh~aOZ<|c2@vOA-cDK<D@f;^&KgOWiTAmMGKz`Ah|8#1 zX4?3n=2cID1Wx8hL%(JMX;J(>-em3Yb)vXkhXK1QS7D91qq+Y+8}VSDdR;hm6N^;m zGw*PJfAiv!{Zu29K^$gOB>_H@-W#Mc=wGABVVYZd&v;X<LRs4)AW0Tsh(qxnFIm^i zTGeF;GrWG)<0DYbC-fDO_~<5klSu+6uJW~i$CL^F$^E>c)qD79rzO7rT8GF^GP`#B zUT5zT=Fv?nC7^}EE$Yg}#`KzZm%DYm<@M<p&F<6^f*M|?eS>e*j}Et6^`6-_6!R#r zBAOD0_ul>hsyz{QE?*B`Rt?Wt&#nC`R>mNO8-$RzT+hf=MvpJL?k`H&R-Ko;+!Q)A znWn&>ptYA3$1RrK$4^ANu2)Z&>6Ex9EZHcOb`suwp4+$R%yn6!$GMCY|Klj?VF5<< z%t4jW3u~?Z-O&nb$!z&VZQIlJQoxF4;Aj9<%n&RY9Kl{*z2xNKD6vb=6Iyj+#u%)y zyhk<Cz&(XDqgUgHaPM34I>CCzw9&AqmaL$pt?w==YjONlg3=ivvr3|p)`Q(IW*UeP z%qW3GN^k^j760%3GzJ{)qewrsDCPKwqzSN%g89-ZoSjkkvvAJb7!opS?cxhnTvM;j z!AjL5$cXrsm(>k6=CBi2rlsHdi8I>}PXKjt7-X{suiEbu)CYwb`yDURTJ1u27NEou zMQmo#OYD;2^+oKI1?&FM1mK7sw#$Giy^5SjC7?+2R9yA;14LQm^#vKLzT3)Ar<MuL zC;SMI_oY&$)9KS3?LwlOQgO@xrIDVX&N!)%081_Bw1lBBIe0HCt`K#hhnjDLY6qan zoh%+Faz>Y(kMX3=7M<KZT~<@6YuGk)SoR>bF{81cCekJDiJf}y?IYF8g$HGaT{q3R z4xqC<X-CpK=vVs|q+Fs^xdCH|j+p+@N74EziArLN5~o0-1n63R*%nxq$nX=M7gv6l z$M<)Uc%B)7dU~bfFC&DFT)1@5O|8LismBNt+$Rr!nrT5L(FmKoMARpV^dxjQa;KY> z;pQ+qtiV$8JNm>*+2FM;NYqko+O-Ou{n+K0m=c_*CCF%52q8CWm5bCtDtbih6UzGY zI78Dl-L)v)HbONq*aQm~pg0g&i>Q{rG~jNBzx&aMXg~ao?ghg|zE58T$_M?sQdYO= z{x#GBffs*Q5F_`sD2U2E;&=y39Cn?7GJMWuuUqVc)c?Hiha0typ5%_N7<ttO&t3G- zf)7jcZI}YGATOyv<q-uxTRM=Z)Wi@-KSvdo>|MHGTOFS#SCf8DjV?)MlIrrB8qKZl zPt5BUt|X$sD6O|y4TV?Q-)b>JWo#@6*GahWTv?DJXljo-?-(M28zeo)wQWI*3mSW@ zHc_}N(S5Oy;$046WxF&3y)aeA!AlX^9Cw@eEi@)?urj(<rF4AjEL$(|T=lL`sr`ZH z_s8gs>G9N*TyS+xxK`ZK*O5FKCQ&;*YWpV_<esY{z`~=s|EaX?PAVg-{NP;W=Y;5G z`jvo_1_3YEY$D<5Vq2n|Tm~BADAh=E*Xxh6X87-)sB1%vp*K%haq?1!0=nCl$nUVD zWW=e7_9LFx0vGhekXZgK>5GIL(Edm|rR7Z75}Mabc5>hVf9D8s%)#CYWYweOcY4SC zqQS{%^R>}!!x!~z4?|>(P!)Q4U*fC?<<kZ6V40@^=Wmm2MDt7h8b~N?4z6u;IA8(0 zEP<;kC(ZsZDh#UPPJi{H;T4ujnm*6=u;cwv*;gy6K_zzv&`i>EE?YmTUpCkHGg>lj z9enqRJSY(Y+eH(iYvOmy4Yy<9|CAZ(Q)Bc?7xU_%v=mOku)WnsI{VUZJdi^o+2-@x z;yjOA%TwI^7P<Pk^47o|QSe<{H8bqN4iw^7JdxcMG1F;9j+@hgPG<oaeg3%MLeYa* zz-$DFp$l5H>@>yLT8Fjjh6!&8#O8QwBX-2Dcq9uZVjs^fm_8mk=f-*_e8Zm$S@h{+ zBsAd0_QVx~15F!oEreP!$e~%%0!qtV_pGXtdL_6$c)cgMAc_fDtvxOwWCn-4X}sMk z29`+u-6=fld+plZ`$poiF_-S^5FO~<l@?cKk8Rxb;QL{1A4`@ciO4ZXF=SYZj9;7~ zzP<e%Kz()pqy90?{q*QB+*s`5_YT|MGc{l#GzsEDF~894YvlB+k>ID5<I9%wA<*uN znz?|l*xEFH)CO_?mZw8f>(Dw?O2wMT_51$EYDufC>-fd%_GPQHt5l}~$g%~?P4?PL zVRgDC$)z1tjRY`!zS2*gESX4?QnoU`%}(yCv=#)D@SfU0e_xincB6PQ@1EhfXzi6> z@q0C6{y*y>^qz;3D1FV%)^0DAFfCrtX@}DXk_Q#=h4Yb_AAHwA**8Mm^G}MPnVYN@ zWN!MibC#{!uW~v>tH%}X1mo@V8vi>c8U&4ZfiQb*2WF9CijL2}iX&x*@NX0mGzrHD zwR4czOflB0<$c$hC2r6Can~&Zzbu4XBTbc?rBJw4=T}N#-o#od6aaaU>cNWo=1GN9 z5d7Dw*!KtS@dVX7uDSwGt5!E+yV-NS@T4utuTt#u?dqP_as7Wg4;?Tf_bS(Fm~q;- zG$UWx-!OJ71edFIW7_FRGWzj5hKy?2@o&BO&=T^0kNwVnfuWbPS34w_I|u>HXxt)y zPuF0HJ|W5Xmv%?00Q`>*WJt!@EVV+eJUNzY_wIqx_}@jTcfVjNo!2#ez`Ks#>HIo7 z3Q%BIp?k0x0_pw?#UldX_$Za$0%LQSvZw=L5wEO^62wG>&3P~%!VXElVOCPhtIG|9 z2h=qAkD3W#>h-xSeFUhVP^eK!ps=bJnp_sk2<C|Vb&)X^7Qg-32l7A$3_l{IYG8fr zkS0YEGl~E6br3E&`>Z{Lf>+o)D!|)EXo?%Yr<l-_fM{_zJVWH^3p{FuH6|tE(+Tmf zst$cZdjgd+q4rW?8(+m1T5<mopiD+arax7}M;ODxn3Vx&b`~H8t!sw<In{RRh`0e* z-{naDcBE<?Q(p#TD$4Q<`|<3{^(7MP!acRP9}bfzxorHx#&X%UZKBCO`o<~-n*zn; zSqZC$J}K3n7<##0TUY3&O9PyCEE+y2{p7($ZPoPy{6~H}QnXS*TX~)}E_UKYP`8qG z)Uj_*cQ`2{dDJW(^cN#M7f0TP@cCKx6s*)B{cDt8l~3)_;^{SI!0{TZ9r@!o3Zja) zu11#-%L8@7H}x?Ve478UFxe<l>&Q(GQ6?mBF%o2=GEtZf8Z{~gqmhqM9}@AgiJRw0 z)lp$Ik}Ivs@JB-6)D(tfh$7K>$%R+nh>7~!GJi7vw#ZR43471L;tOGNCVw-UWQef@ zsluY+a7tBE7wt6g6`GT%gQX}Du$g~*zT2TBe@gQ0+*@%@yDq~n|H;>Wa@*_aTwg^< zxHNga=h&O`m2${YPq0yf`uxmtN{1+AJW{g`EiQw+;<VrYZ&u35>s3-ao^|c8nSrj~ z2WaaQy<2X#43D|AzXY?3m=ihPqj^}XLKTd>TbsJsR5rtZe^+R<RxE^4bE$YLpYx5> zyvV3yw=;`QA+Ku!W}Of|-(AcmoLm<Yu+iOMKxSe}4^ZR26)D(sKPeHlvK0_N(k45G z!4Q2l1Y@poebVY+7EPCfA__Pj-;daZ!{I$D(m&tL>8XeP=3n7C#41GERw0*d=krN# zPHrvA0BetDJJ~Rl3>k}I9K9xZuxQ)SHu|P+LfCGSpclE!i9p@(xKes_!5d!BvbSO2 z8v}BOJIlh4V9!vp>YsQ8W^(coM!Fn0L66Xu%=squIDJ}wEMsvHX@t`G0+2;<Tc30e z<PVrC#)J10{T<TN&@OQpwV@)~rVP=SXM=IRknk(EeKpYp45aQ=s@}d215`Hwt~|)F zVbhrGWG)t*UxJ!3w*YDyx6!OHaMO)2(<`5ogE^WMc4zkF<hmb$h2-w@=&lirRoL<v zj3Izh1UW?>_q96Jn2Ir3quW|f6V(cyaeI%*^N-M_CKTisPZ(h1laZ7qOj;bF>kSM; zt@?$ZcNpBAN=Vn2cdf0w%_=>9#s3deXBpIH+il?l2rk9l-Q69E7I!Cjad!(8DN@|M zP#lUCx8M#n+@0d?a_D!?`+jGb36nqhlga(ud+oKZHRTSC$9yAJyngRIXFvN;bi>g@ z8LA4ZN7m`QCH`Ypd1R$^&aVSW)v$V&MR7=~8(sZz`ylb6ePMDwsJnuAvJ%7(lMbhg z6mT9y`*HZB^M>J*_k8E*CbG_R=jy}jXB^*J8vF-pGaN)KG2-7;ou%t}7&C=GwBy`~ zRWJU~AWj_o(3#FOup20iq(9&IdiSry@z{J`G3}&hD)l}yS??S@yX(B+I$>N;gl1ZJ z1mTD9O_~ByhpLYo@rPK|<XX*o*8a1G330rq!3XN#-@H39_A`)M^dp1&rVVzhVU~2v z!{|B7g9W}yD)+&-?Yf?UNGWdQ^Yd^}q^*mbeM28qN$ot-K*E~je^1|6l41#{qoKdh z^PHU<c_4nQ66d3b6mkB8?TV-3xkzSfGH)lAosX(LY}P*iH8_=M(0`r!!d$cXTjYhL zK%qc4%t!GKQsN_Qi;h*lXPn@;TZJ7{k1=U?U;abql@iEKm`L0gn5G-+${QO=f8y|H zkAqKx3c>&DZZwhqyPrVuHTd;KtQ`-USt-)IH!Bm5?S9*;vyMT&?l~xD^!v*5@$_-z zDRD%ziJfBP4M0>z0(X+34O3uZ>^r?}E&LL<W^`c??YJAQ0T*Jj3Q##0kR*uYNumNr z1i+(I9xC+fd|;~5RI9FcGP1xd*F%%`k5IWh&p@`;?VQhN+9OnBv|>VLhS!sK=u`Q< z=XvJ-PC|C`d*RQ|oZ>MBl|&kW%o566vegZ2f)LdF76n=kL|SgvT7D1~i1vL|tV{*n z^bb~iia*V=ZUthcZh%PBOtJUH0L$FO4gp$U7vx){I}|*3dzQA@guMh1QxW@Bhkc~( z*>|8lu)<*1&NSJNAnS4trqqrGRuK#33)^0vs|zYKew9~RkNqc+Wgn^DO`Kd8sL3dn zY)Nq(gln$8N-+=IzMj%}>-nDZh3?(4AYXl(S*(R~_=gjV3lbGQAF(d%uk{d9@x4AB zZ48uGeWocU(78EaqZ{UcFV-HZ%-+w1057y;QomKPo_mZD3x*ntw>g-gw{!9AmT8gK zu-1Yye$IE>{qq%5yr>>t1~+JdvPiL0B>c0T{7*QrmaTTAI>Kf=x33H1d2c`JV%Q6K z(I+r(>MF|S)Rlkh7B<e5^m)@O{rcangc%EXqgJI*QQV4O-wJCkEPaG)KK=Q==0%7L zWR%lVA2<q^iBpYt2e&t@2(G`lnl+7AFbol)Q|-PTV3%F{-Q0Cm?IWVk=(N&7*dyFN zj*TCG!Lyp}qLViqmb;lUAWe}TWrXcZ=Vdi>+Q@<|W~x+szXE5<YlFR04_RvSWDc!9 ztTgXUKr3B%HWw%7VV{Z`^69oL49tRD3&bs+=~ijSNrJQUvt95}8?$|`I~WrgT*mwB zu{7w~L7}aVv}nauaa9&#y6#?wFaaH60U4)wa0RShHN_ZNn=!XfY?P|oSc4q2Dl@80 zWS<L|V&E-<D*$W@9s|;?qU51=dGHCNL6ZP1#%)&E=}$r2hbZGc2+eC$uk1B-BMwr1 z5jA?Recqd1Pd)Ea(!%r=bpc}4K4Bc`tpm)8C6-g0(Vv(VfPLF^9FsSusa$eBVEYcW zs<eLD(aY)b<4I`?WNBQA651hSH{*jM!$47en8_^6AwW(AWvR&tY1jZDtqfELn0-$S zzcSe-A>~gz#==OV4;(c3Wm=`x<4O*@N7!Sctz@=E`_*;^fnx+qyb9p2e+n?xEK-#m zBV#dmcirp*+ioM0at%8fmhxbhfrqWd0iVd|{RytX#NT4oJdKJ=fBR8*%q(oQb~i`B zcJeXJfrnwT_iTqv$;c66Bf5nSiXbF9eQ<StJI)RBCA_+=(*^N;#mmCGwe*h<AIZ}k z+{1m!C38H}=bWQh)^-fyThOAU*h=!cc@M`ZsVUw8LIW2AqS^2IN`-4g`5tP2-IXq$ zY?$<O9S`NB3JX23Qv7)wn_F+{zEJ%LmueHh_)V$f&}3~IKL7MWD74jNn?W3AEXb%i zR1^*o={iwRL6$6-9W>=fej2pqY&N}Ql8_p!&L!@PfpxmoslHZGz!<`ae}efe+g?4C z$g5D!{qW|uVTj)2a_Zfmo8XrlYUVG23mx8f{;ah!Yn1?|6WT6(k^`SkXCG1RiPdBJ zrzfwf(BNg$f@F(i!FR-n$8LxB2%&9o(+ii3-(;?gNM~(Ml&7D{iINvbMYfFsjxL-o z2v%MmMUF34v^K1RcAlPuSMO9r$elaRWIJl?BY%s{!7QjQ-hYq(j`7Ba?fsPf>0hsI z;RywSV({i)sD?%9>dW&DwQ%Na(`@dVka&adlRsPct0P?Yd%^75L^l8C?(?FrP8XO& z=|1@%6A57*#1$$00MB-FdS!mCvgj4U@rw!POpEA7DMS-*T$A~=LE*!alw{R+`jR%$ zAOzEAGw~9k<19TpiLZ7toYI&va23vz`J@))t1|cVdM$zL{;OHsgjmE!%EM<+j)613 zdBTfEiH#pSq9$uNB^;kp|MA;GQS3r+?z#%-`IHYDtV7(r<H9W@g%-7q#WzDVZ@{VP zH<)n6zLVZjX%#u0hypNMg(i(S<$QX%qqJrCNkCV6Y~@d0b45wM-{w(nY-s$-MBYyV zl}EU&rY{AVO!eCZu^cxygjiG~KVy`YbQ3g)a^XQr;PggLdGI+J&CCSt!)dia&?0kX z=NOAd|J>T4&?iX6=JlS*dkkmySW1)`fb(t8{BdGNOGFLI8ghAzAWAxetpo5K3(X*| zJ09&jymVxM#<D1rfr5%d8V$csiccFM7Orvy(#9<k^8UD|!VAhaseG8_I3-}G!ZQH{ z4kzVGPfun*$|Ys`%yLAHhD6P%)BFdrf0ceMeWn@=(IVRxGHPgPltp4FfJJuT2uvnx zQ{<vY!8p9(P_+`F&s6zCJY+aR@f3HB6*huyI&6a0tF+Buk+wf<h-g<V&tTGc#C-qp z`lyyBaRFgz8YI9=YH0?&wE+ZkoLm#F{ZYt5%H$nMrHyI)1q*S_IY2vk65$I?zVg9I zbMD5)b55pd6bfVCXA$5puQw%IE~tQ=0Wql?%$1dCdZJV7Y`!5@Loy*xlqG=v=B2yE zY?r-;#g)nHH1VA&iS?a;9bS}9-}eGc?KnquqJ>r1if0#<6#dg=^jK#2TJ4N&0u#uT znQJ%B#g*~MOUXhYSDv?AVoEXg0jYL*r-?2Eq<}QaS-0Or7iHC{MkYT4Ot#{}$-qei zRVZd0UtcM7ydKzNbR3E$wAi8b5n3a<*bpwwjM`MR{{G7ujB}H$fg3Km3=V!lCpQCY zH0)@)u;2~)ASybP%D1&^v_LDEB(D%TavpK)kAKyqO&8R3TK{EPL*XFDeZe!Irdg9f z);!Zpb)|D*_~zBzm<K--rKT|~Yp&m6eo9imB4f4w@@L4fPg1{V9_EO<XGo@;U*!0c zB2~3H#7<x57w@{Oa)-X+xExmxNV;r5qnH4;k^K+(@Q_l9Ys}%_+kQWtu^G5`<0VQ# zUE5gVn()+5*2YTY<Rz_I;<AHIpxndIx3^=L-en%-8Y{dTL_B4GlB-GBU1Rg?W2BNn zu0zu+siy|6z0DlGDiO7-X=dO$1Xo!bv<yV}3Zi<i6lt|#%7^Mh)sAKkI574${O6n= zKH<pXs9ST`4>-u)fDZ{+IVA2!8_dMNJQ%bfp5CrEj?$6C3E9am@7X`r=nz<uw@OH0 z*heeD=~(p(=@ZwgJK{1o8d8hp=SFm3`0GH7k=K4FS&V9FlR!z@<8gKv9kd4lu{y+G z&3r3>VBv*s<TrMsdL_T-ICLTU^%l7O@6dL%V+rz6+vna`64ViqX!K`VHNd0H@GRwZ zVJ{?m`Lyb)ZNXF~yCPy|uYCC7#p99wUAPSk9Z>D@5rw;lc+L-vqH5AY>9Gw)ix1(q z4w<LLx>5|q#>csBaC}QzjQM~sw!=2>w&$B&C^xyg6H`L&lvm*MA<6QJedMf(;PJur zS?=Qf!b`L2?xM@2{ArokB;h9v6mXuLL>*mSaf`6@U7~HMSI}L0u;Bkn^i0C5&D~E& zKBlM5KUdfHZ@&A-+-T_gJu2Vi87^NuD&rYxER$EPn$R;*D1Z~7^e5~qhEIWC^FT~@ z?W`XPySvFW)wHG`t8Z2)Ya8#EZ<@Qz{TD=a8m9(qn$Nm+NZ9zgfAOE@v3(q@TcS&w z4Zi)fdi&|rzWdt#2U#j<fES4v8_c(kU|$mPn!)`Bi|VHCmUs&1oAUckpOD*53ARiw zQQc9ZV=haR996%UGEhaZ=lzFKl2N8&Q~Fb5!{@(XyN>58(M$U0p_g0o9|%8JPl;~Y zW}ZX;Tw+@yU93ip=jD+H%F{l=!684pXYbz!c<}s5{s#nQ8G_*)LgdgoF<_fgX{fQr z!NOQl0Bv0~i4+Y847`kA9xhe8BDKRY9;!gjbtOs82~g|?y0cF)NAIxnoisqx82~+{ ziHyh!XIXczKkHiqI&B{%G86xDzL>+hn?%-QDLf8pIT^`!l?8iY{b7vJG0OSD$o@5b zJkv@4J|1Fh|2BAVPT6No+x&#_3-j|7sd|59HC?#b`T#T)adGkO*)Vv!x0WCqC46Hz z0cM7oh9MZH-DrW0S<=S_tL}b$`-AfQjXnWP8qpXDuXqG#6nXQg>40eZ_opWgM-v;) zKygc!uQzsmE-hl{R|D;kz{%GjFhM~*0cIzMfo9J{mws0qOSwBlZUQgnsg{G3N7pPM z-vV_7#>KM<r0n{I7b$dPWz6hn5aDhhdXO!SH4Bkf4C^n=!9r|jeJH}M43;Wb6=VLR z7$o<P`JhiG^=<8-ECoI;vOEG)&6?5KB9QU3bM+y)JDTS<jDEtQ8rlj>0xyMN=x;d2 ztYPa$bD(`6r-9bnS+fm0^$<1s%mgG^3)Cz_Zj1F>W7t3OoVa`3Nywv@Ry4Zx$7B?& zc#o=jYo%O)1GjdwWp)U$(*^bVYrj9PD=QSR=wwSc3MNxyazLzK5C9pI@Sg)ccrWis z@((3)&+?g$5Z*_97E{NLZHmyhgYWz8KwiSQLw)i4LuKS9<7!>UlUKl7MPq!s^5?I{ zon*btHTA2wJ~c$^DQ^|xQa;B5Kp2G00c=^2vIH6SHWq+;p1plubp(CgrhD#QKkp9k zVD^J4CG9%xZP;dTp@P_mY|cw3v6T-lDy7X<Ci$~ML`KuUJEChm4j|*)ZuaV<wPgfH zrZz1zNUQU8bH*8B#dT4?Rk4UnlGIbaFm<Hl!#HJAe>hWZhyjtJb5efnIv-`m%%Nr& zp>ZA<_u!<wS1{<m8_D@g#2s$3#4?DW9Rh32yN)DS73xVOxtC-4PHMxW?G{ZiaU_n| zVh@DYD;ep!d$-pFY%`y^i&rLXt8$6Y6ImW|Q2uG<>IwRxGC8pI4a$Rk7uUF8{Q?$` zyZUP#r(TRIV0fsSOt+|=QdCi|&7delme|n~?Oy0@)fqXlJFJgJ61gy1h@9<<1V(5- z1>og1KhvwM!OD4{_^Hb}M+Y;=m#-}_0fxSn(K+?~{9O`SI0VzmQe*B}@|by=u`WDy z7H{>1i)Z1_d6d$hF2$Sq_`t4|Pg35X53J`Kb=-Id+)zbY8G1C~p;C4qlE(DgWABgQ z@SlHlzM|#H;c@UBbSxkm0J6QBFQcC=A3d>fJa=%dhN@ox6xuGi2`Y&6fYP)Es!MyT zxyVsZw2K26$oJ^L1Jb!b!Dm#c{ABk4QQZeh<quM^ITT>!Lk(n|ULA^R^H^OA;dns` zh3{gxb8s$-BkowQ@_1l+Kc*mH=&_dEpdat<57UC`j3H1S*QH{pd%E(<e%&FENVcDy z>uKi3EXa;u3L&$XByHh7)|cy;Hh&^>D656;!0npj_nSM6%8~Y3G<=-p-%)6#r8Z`M z0r_pD=1AtTEARLL&Oo}xIT}85+Gw?%A@{;zuFIQ`cVAzrGh4l~wbqyqdLrmP6|1Sm z2ah@aD}?PoOmNADZh{8;W=})`QG{W&>v$pqe2+~dvR2#ei@vM_Pu^nKJlkG`Lmk@f z*zk>uW?Bt^k!uIoEMAR0Ip7@@+0^y$;=I@;zW*IDbD7qNXb9DF6Ecqa#?@c<6BWbF z>$YkC!$aRpQy#1Nd#E&iFnwU~8FzDPu_B(t_SNGjd>7iaxrh3y1jW^$*EMr=mAKPx zXYcBM{9=Xw$0tMwOcKvb_5#$Ezh>3}L6i+F`6AJd*)guI&e}HT_pq4_9YZF3x(`_L z)F0c;l`qM*L}wnHJxv#KvmGK8FIy+9ZsdS{p{*i$4)X2KqfX*~t~OaInDdn-e6|;D zr=rb950>2vFt!D+MGzHhvSzn#Uaf&XXh=rMPZ|;LYCwVb&AJQfYMl^|z_(9X%l5VV z%%cBnjA>8;^icW<V)Tj?j$gYCZWvnLhYKfL2~d#jzmXmWJh~c25y^Rs-gps|?rDLw z(xooo!=Jd_`O`^;A8b<COB0GwhDSHD<gzkPL^mQEs#Uum0u@Mf(&#^nbP<oj!XS7` zpRO8irPwVEU0smd27IGH=OyEMR{A#?H}LIm{yAfpcVeI02AIpM8>+8|<LdT&P01~? zHFY)^?#_>sI0fVRxFD`2pDw~+yK=4X2<s<q4+{xhX6&QH1^<pL{xf*L?9x%Vk=ZY4 zwDhG0cX|fJ^3HmKn&1mq-Ta3yw-6|X7x`iP<1>Pp6cyM}>cdI8{M|H#?tZlb&)Ss1 zw5Tzae`KG*mON}BXJD$`*4-^!Z4#InDk^Z;aGlC=yo$)2@hMB*s;ZnNpOgI@LyP6& zf^NEddw_!~b*^~EA(6yBocTgsKys$AM=CIL%<HXnC?oeD#H|I2&z$bvR<MzoK?#$! z6@h4g@-Kz@^HE9BuK0tLn&wcoHmaxHQ2!Mt%7Gpj@u+AzmCK|7|C>hJ^Bm+0yd-jE zh$VL(J!@t|22afAdCYMg-a!#aTrdwx_6`+~NxKvbwar;d7prkeb1Rsk+)NnqN#Hni zbYNcCIr;rCxD-Z3W?L>7IU#T`4;TPjh*vV_X2|LqwMi}~7b>}!n3GXQ!4KkT>@|ry z>LD9%&QS&`Q5s&LOH^}eUqdSPA(iL$ET>(UTv18P0q_=hfd<>o5|}j;KBy%_IH(eE z>UH{6F!b>-6siE+DUp3RdTu6>=hc@+)R*MlG4s@mW3jqYwA~jJe*X2By`)R<I4bYI zta{6k_rT<=_CuUheI<jeoU1dlYyj0fosx$RxTpdHyMznt)@NlR?6BIf+5CJeEPuod z+cU0J@lr?@d%9iudm;rLdAsrsV`f9ZAzyMR`9Rz?<({H`@r`}F@Ye?c3`vt~vki?A zs(BJ6t`j=iqrU!*-D1U{eU$d@_+`h+x|Upp!0kdOGZgF!+>ccYbfV&|pvyXZf$003 z*De_n_+s?ulb|s<(L-MMFI!bwvUoAK<{y>%T)Lv6^j5b5#wT~JsxEpKv6|7WIypmc zjw25S;A>%K#ZI^+ga)-}U1bN|tKVZCSjI*cqM4`T)(1SX0q6iy7J4>TE8C{%5_B+d z8y{Fc*rdLiin>U=Rzpv}fXd1#y3`Au-N9jE|F8gLmv}-Xk(J(n8SoBYr_<NkwMNHi zWdIZ5!RbkzEK7o`W7=&!FGLCS;CPD`1))OMrcYb=ZbQm=j#f8n8CJCxILP=xXV<Dk zBL7!ACc$(YJQ$g7Q3rpLhM*n}4YT+3gzRMre~@qPwpM^D$_;_e9JSxOk?r9}aJqXT zPaBAb7@&kQ7m1I(!C-9yE0Bt5Se^NBc1RE&D-2j=cxHH|{x74T0C=1D3I@=jvXVvw zj6qG99bV3OJEefi+r86NmAM3$Oqd*Qczw)Zl6<y46MhUw2@mDDc2wWyiDah$smi=h z`JXr|2?!Y9CNn0wW==KZM^A4XuBC7maNgjhYzS^jnx1w07-O2AXV0>}us)_wy!7!Z z7Cev`L^-b87UpbUkmRE53-TRpIv3smf>k?B?;jY4wXI1*gkF`CV6s>E-FfiK$amTL zfuqh{L;=iGn&DCZnj7qp(s~{wjx!OhiA_YM|5*Qr)3yS&YoohIR^C}<U@<T+{(AK_ ziOcQKKDc#P47mS!$epS<VH4AR@MT{jjjYmDT)gjCT$1(e`HM)6TVo@Pf{Q`QaLb8v zZMKEot?JCFfi6a~j&_MeG+_!9k*{~oJ(NmVAJK7tS*sXziW{1OJ8JoKGq&uX+H`8f zK)H8^xwlj2-8@ff>i1Lz>JVcVeA|5`qCFc`6F9eY%+>D4I-*$4a{kiWjhi|B{2IlM zE^K$by`SwSdj+=!?E;Cj#<fjg^QK~UxyWdq{<(F@<J}_S4G$ynrrT`smu#74nMEN- zR#3eQIQ3>s5_8ym(adm-!+u7Tn>)s`x>rKKJiRzs_wU?&t$tj<tLvfofTp;beY<XM zxZZ$)&!Oe~6?4ldX~zq~@jZeFT9(`OZX?glJ>UP_(M{;G8>-4@vL^1%&J=+d*S3ee z&pPOXI8%=27oNU6#2&}l`HygXd#S&cBs^N`jAh&Z*R{Bdmtf0upD5Pde2Xt`2{etL ziQnYM<S2e8{|hJ@+yZS|mO}VG?U^G-GW{KfXIc!5nE?vU`RV3|<wipaCsl10B_tW- zq_YDkx#u7;?%5TAw$dd62r3jn?%1+SYih$>wE&f7&5Aty;h)Ocv(WcFWjx}PNFfd~ zxpuZmm=||RG&qqZtD*ZLWJ618l$|ic4G~YaQ2{Ge?z}ADJNQNtG$r?`a9R^GBseou zc?m1Er@ucOj9Uv_p(SA<Fke>a=U|8>*Xm_%0f;n-;1VEuOzHez3VTlU11xPsEFf}h zgV~6ib-?e95sAz(=R_pgAhsjJliFIuKu2#LIW9O^HocB-WuKZqM`^Bx0-5PEIYGa9 z)_vo0v$eA7+c5M{HZ4&ktOd>rJ`x=rNfo<>#XNfoajFr*jvL%$u%LXtXa;hjCA<{G z!>nnNuq%YZC@yr?<>(E=06U1>S+K=+bs_9z-_Q{BxV(Ux4NtHVnlwgDx^hc&)CP0l z&FNfk>6BfI^#wTfCy1R+1r(%Wb%f5>0^p3s*L%c%I6M7{`;{PO)5j%MC!0fD2`|E^ z?8UDd_2sQh7Z+h(sLgze=iU5))h+GJSF3|Ij!GNB6pyNZhEDt?n0tnM*8CFd-jiQ^ zI~W$G$>WL#!%F9`kz?mXq_=zs$tU=Wk#*rnhbO@=4|Or7q2){?2S(p^<N|_^>GyZh z5#i}@z&R89kdkN@kbWmwDxoMc%GYm+qni(!(fDv`%5b!~?9t+<3gTcyJVhm&WETgJ zR}JykwaViL7dAr;%LADW%A&nO*moxosYJSbZ2d6J>*V!6Z}^sVXXMi{z<$WOQOXH# z5KhAd$i&#|P)ux001<^#(_1_^{RAjbFmr%s@dL$c#djDEML`*GElEhCl8>QMXz@?Y z3|MXKqv;Gx{6~5s<qbM00dG@-9+2k^C%PQwp3I{zOiorl)N(OY8*St$_DBaCSpcq( z($(aInsCKZi9mPWNzyI@4U9Xu!%N<KJsk?FR2`435;)VSMiEAz3fx*rg=d-fUMbiM zuQ*FJSbAjN)EXo~Q(=X+igrziw3%V(RF6FQbC_VVa~`jZv4@u^F+J||4^pN}zp|25 z33sso1oZ(mo_$$@>~cA(JJ#|dl%HM2LrV#Sn<}||Hwh)}>eS?Qta>#1trhXp0DX#s zm^~Vp0^44)uG(+6o_9HcQ?EyHSyUj=tc#lXE@r&+d5N$Py13UP?3`co^nJjClA*$x zhDx29V>88mX53om2W=>3u=LZ!H>7ARj;)g_4ZpvQA2=b}Fc7$ymTC$k+Q~;!9Wm`e zQo<}t5v{>P(hMKVt=5bdERruvIbfQew=iW6p&D=$NPL(OOVglW*Wl(Wk03SdoUH)M zbd3n*H^~KrL(I3^R4$`8aR>e{H#fb(Pf;&KUDu5@G6`QVk_1O1o9Z!uYS%Srmi-zf z6GN{OIW<bk4fggz&RFovd2sw1gX50TxBNiFM^6$Mk4_tug)6cxKK?(uRC9xyD94^; zp$i@OCSI!#6zu_N-LoctB}ITJ;l#Jr=MS@4Ka*h|0<IfctOr`>97BPxkir+v`qU>D z7Rx;U8uc3P8nj|&(TJL-s{5O7i*kOs|0l`6@`GJj5}Zr6Tvg6&&#K*{H_U>$WY6NP zNIVfvQwEs0p2Y(29}?tn!Exdl>L0OIe%uT``~((hT2%505@mi#neUprv`WJf9X7G2 z94wyjcUYbKuzS&ajF`m)Qts@lyKHCh$@n2IecBRQLbPRS-H(M&Y&t|~Fy1xWLl%_q zuK<H$6C$S@WZJ@5f<%iF5W!L~gxA?bI+Pl(r_qB5^Vz6)r)N^uZ<JgZ)X8{$0P zX^aMT%C%GRre1%Vr$Sn}n+L+oXH826FmbP>XJ<P`%&(Nd2o6?d<c#QjhbgJJ)ZeUR zpJ-8<-m5h5kXi#u7B~o}-6VwOQyDgAGbputV7^B`pT<bH=TN5dBVB;vgrHxDV!D*^ zmZ~%Ic89fwHDtKVB|RG<`7pBOLMZuU?jtZw1Cz#h@b+nTeF<SJa435LdNyVxP%VU! z?v=}+49l_F&qN(s+{Cigv;{T3n{SdTblz93L`{z}W3k>J5i*|=L)7byrvcv8pR)`m z^uKZWe;$um{-s(SsRWn0TK!Pz0`jlP_&(<joeQ&R)K72*NL2hNkHQbUpj>Pe5@k|S z$ZD$PbkM1!MY*YE#f9{<Bu*Y=f462495OMBLAs}5hDklY&gCdQ|NRV7*=DI2u)gC} z3E&JTbU-VJ2{h&*SGQFqqj4f<2eBdcfMdugnqVgpv`g(L+tUX>?D*pIc%EG2@eEK^ z7nDCTX;;@!u)A+FX#`TlcBGLVUIN)gT4uLsv+pvaNJ~hvL}DHDJUKKrTM`moS4ge? zpW|x;CZ851BjxdDdKb<%jIv^O+3(r=mWd!G2$OFaOY+G17}hoUjX?g@lWQtaUcDaJ zD_4+Gr5On+y9P_P&Dg$`WeU+`dyN0cOD8a-cqT(KjzBQw!ywmY-f9%Kcwv%(7R6nb zw1NQDtaV}Mko$Awg+kGPDE8BzJ0p@Sn!vyeQEeBZhC=H!S6Bl1bh>KhAjN=Ty$D}G zrB}exN)chZ|I2v~+vO+0brmirgte66i%gQ4V#tW_1=l5m$o%flB>)eW5l*06o-R!3 z>FOuWoocpCa4<F?HoqXE1vv^0qAXa|dBHg*rE3OY13M8xEkO)pEp-B0+;bi%5^CX- z0zYw4n2;?!nCddQ$oyV-&A&}14V4hm)&F|)3nwRx&h;luJ61?IFIONl#EclKO))qZ zu`5995Bv`Q=IH1bImMJp{A;xkcV>xDKcT0CCI~jsai5^696LmXWE1iG%vdf#(I=od znk?5_!67tMnK2i)V7!Ic)@8^i=6c0kZiVWPMV1*b656uh#HJs;k3D>fqJuDnOju=X z=rQ5}juHb0x9#BCVXwiM0EyrZdRgOT*zZ^Qk)KKOhHSiEUI~g7xtQ4QU7Je;D;ExU zg2tBCSl=(0yU&@||1wI8EdL{5Gng_J$Uzed7zmN9R_|h74J5;DMIIFpoF3XxC9mx6 z(r2$RqT5&EPEDe(Vp@q|v(vzY<q_s@nl*Wfn4}7rhKg0QDVr-p(?Ff8?g!&5eO{HJ zm*(JyX3Sd}`;;{r+7mdNBR(r*P;_oyt}HYLT5}SxE8L#lzsY=70+(%(LU~{lv}3ao z6#BIzQh=P+RkUR2!P)fQ8567tw6%=ZC{fd4rCo-TYR%<)h<~|_pKEr<H)<4CJ@Hm# zK(=f5y-){FOT#KVvo%6?q4sLB3X&Qo%?qm+Wsaic_XK&=fe_=h<f@d=Wy`l03pYfk zB#fK8p3cE}!BgAbud7d=z+sY|hvf^OKhU3%|0G{VU{t{&M0HB;Az?Q}YM5eY9d)Ws z9|8ti$r&#W9k=e$8C7iG=uTUC-ZP+a2v0LGWUGZK=QOc8eVp_;RK7bVgH-p1dNg-_ zA7>bb7p6-~^sV>;UYa7a>w1N>_PG%Qgj0*7Ujga~yUbCku_w#cp{5wZU+bbk2lWbQ zkB^<}B71y0ZXe52&NbDk|DAR5r~dYAY~=ppDVpj4xhxpX1r6)>|N2GfkvJP+K}ZWv zb`Cg0*WVfeya|U#Sz*aRkWl$y@8M5SqL}jo@Ss^$5*IUC56i3naV2ItnqD&_qpXcB ztq(EnTz{|}@n>NvEy{Olol$$%ZM?4<<*0KJUt}z}WCzV)T#(b~nSzAhm4W$VI9L^g zQnZX31WX;=Y7G%^o}KWQK{pJ{Hp~DHtM8-2H04^QEC%erw@6$}Ljw1jIcBd}nk1Ae zlX^%1vBV^mQkjS;Z#ySq0;PRk-?z8C(jl8B^mW@CUr}}6dD$m$%$*MF`Apq9(7GT) zb)`Kph}_w}7%b;fD%XXiG9x^Ot)c%Uf^<q~&&&1n24HjOsT2+jc#+-<T6AEvREz|W zIEPp+w}rr>Ln~Bdyd+^{PE~+!6w8tF)jJS1lX%9aIjYJtjG=tmJXxHaSoq^2vpJLU zM+0J>lM48!TAvE&hRStI`e`CA>ve3{udn;I-lTD0F^87TJxE?CE`S@Rmlw_M`uO{@ zWK0N~&+WCuf{urzf;>`D(WlSag+=FtJO8&<=%3eH4+Ch}bIt3JC2Nhno$HmHP3|d6 z*R$%g`6&aDci#4#r%`nh1{~c>_}t7DXd{1k)U2~E<mRh*=lK!W@=F#VN9Yqpg|_8q zEQw;%2Z7lp^tqC{h`@9ouxvm*5oR;ROmh`$FJ=-R=uFd^B|JF4Va;?BAw&Zw!{x#3 zEvGy_R0Ts~jIhg3Qp8Q7_!_=IJrJV+mxA0?=6-h&UdEP|n}T8UO@PnSIa{4}ypMxa zhbtFrA23!$7YOl;GF6;+Ug&p24f1tUJd`#HkG(ELkUnITpf-OcCBY**>iH~=?j@Gx zQyF$~*-DN#!|T<P^lkM&kgRFVK+MwMq$HIjCy)T#$l8EnLeAeQ-`3g@c|xAb<<5o= z0kYr_(`1?V+-#ES^y4<L7tUhTeAICxaTzFjWfg)AysopI?atvpFyubJD>3i(#__9N z=D8p48k+NJzjFJA+R%<O&oM|;&~W`u{uP@_7vTzTF&IJ0;#BVVu^15XN$^EF&M<PR zxqvGaXUw;19O(^(Rfpj~XAZa6T2J@8jW4ko<vfG^vc+1%9GRF7PWe&Wing|!;q`%W ze~4C53N|{@q^mrXa(LEqL?TO!sC%qQsHpv%CNAi2rLU<n^WnA+`Q{T-$gz#6785mG z6&u^C*Yf@%reifMVG`jKNBX}w<Okk&LQ<AjHMURw)3X$+XlZyq_S(oa+_J^F3qcZ% zJ9n??e)hYS$#`KpjqX017I<=KR_(qJfT{7)4~6p{QDNvyjn%7HY0Y={FyT_aYiD;) zQI@5@>2}?eO<Bf9-1f%RV|MB%58*gI5eTf#$p>)saD^7bIgAKcQK`>V<Uh3L#{g>g z#c22bGzT3t7sggma1ZUe!pY8`{GFPHc9#YH92_mMzgdtYC16jSeSUer>HKPZ|KY55 zt(L}J!<t!QN&2g~#b;9F<0>=e;oa3jO5lV$Q-TUS2Qc{M6yxO$(APxnn!5ITmv=C| z_$A~Q`hSnnUA$q&0O!v{8q_;c;Rp4~guilvYqEuD@O-j6!eYCOksHO6@)f>HzO+S{ z$^m3HK&wkzQ3!?7C-$NnrF2Ri+~y@iv}8Lt<>M?`lIWsX@P~N{_l!VgtH2H1fT4+d z<Hs9pv7EY8&L4Nf^tU&}+x-gUu1j<SRJ?iY7HwQAIhY76<J!QPje<qa6S5l_zv`&T ziOccQE0Or8ar$iZjMp3WX^~~fH;soP55AvzNg%N%kjorZ=(Z?Lm&)o)dti+pm!C)w zW~HtEZ(PbLDOXuq4=ClQHAYf#k|~WHYKRhTT!S0@;-sD>{MiXr!+D_01H+-!giEoA zMcor$8JXo{S^rACfolQLPb`*Wc5pNi^=7kVUd^m9qH<=Ii&3_PUU}K~uXR&&+0?d@ zwEUAdu{1p?VZgCNj~?#57ok2znd=6M*09#miW8wK4PLy(t5OetUpX!yQl?-*d9pWn zr0rcea5YJ<wnD%{vJ>!K%j>%L%Z*0Hw;I`|a!)jy+~<_1%85*2X;UuMR0d?c60Rfw zgYhPI@)|&(Q78I?WlzL1pU!1Hc%TCJ;E>D93MiQ_Fe#Y}@^{b$kC8w!QhzF1f@OBT zBi;bXwgK4&0V_j-ro4Z$?V@1T^c)9U2<39`w4jVu)H9`8k@?FQjo4doNPKNtqkNWC z3$}!ka^-APQH4ambx4ooLdyUJxRnR%(5&m5zS{&i1g=2ZA(x)ld=1N7&JxC0^C~KH z9vY$>Q#bYdePp7I(SR@i%|rjiot}p3LoJJ0Flsul4RYpx$t#XJP*Fg;@??YAGe*P{ z{}7xrus74d6TlX{LLrN_xk=R3)}*?*f*x*%5Fi!Dz-z&XQH6q3te~c&)mtp~OpqJo zogk-1N)^=6WSx067Yf%-)1yWl&U`x!DkzP;tv~_TaFJ6PMjH79g*yUidzS1u!^gW& zBy<JAIg>v=eHU25uz`fAq@iulLNqOD?EvnBw+rt!$O11z;Rd@vOozcW(-`#J$bx{N zWm2psHHLa`t9+PLf{X(BAldOcWKlgu9eG!N)e~8Y>8#DxVT>Dg8KNC}0)(e{dm1_G zeifYDDmd9A<8>e))<=n$CuO66<E`OjW(|shZzRE|saZ%*+F|MM7410wDpe_<#Fw7A zlw)ETD$3r_v-I1@mI{?^Z|m(FCs+VidoT%|_i@t4_u(IiYa({mfnQWZGbfut<2(oe zz7MW;(yO@#N8iQ1bW32OJ7}`wQUk1Q%)S-QMN;9k)Rp}h>Fva7+N>hJ##pGEqX{hr z>*|EQuA|Gao!<PBhTuTz-IN}g3D9;nZZiuQ%{fpKsfNSrjMAXxU-9R=Ng8K?lB?3Q zKaRhNkIbrb)z&Z5`xapAEw27VmM=WafRrJxZGPq2dwd=7JuI)W186ATQhZbfj1W7& z(v@BTlP_33(m`z5Nf;sWp$T5|4;6urMz+F|()CaCa6EU!EQ?cr>6C-3-tjirdRx1p zEo`Cca2zACv~NcJr9fflEv9#)x*PtI2HY8|IjRG6G({;@lE){`UFQx?>}zAIaWRl` zI}(29l>oqZx9-1%0k6ro9sWM%nhva8YCx5F-u++L*!xvui7Y-JQgDVB&c~8(pNFvC z6j!IpcleNrG_7&${_zpDwj%C+cQnFUu-yH)%`OMclVGTUoob$q3ku&oj4^%Opr()_ z9KFPbd+|j>!n%C)XGIBfHzoYlk@sYx!(4+@6aN3q*J58m<8?eNaG%u-5xmr)e7LC` zWXHarRLz!XlAV+F3cjyCB~)vbO!cGqVkVI-ZaB}oUe!%K9DGJCG+IrN!Vx)@i(3<Q zlSJh(R)176O-G|H%II{!rcT+4oZ(RJ32%4YwJ2Hq*^l8%WYvH4t7VANSjq#}yYam# zW4DU^!HGCmt!f_vhDs*93v?P`nwvZ-$?#3&<QX>^?+d;7yNd)A3D3BHuBbNe|N1c^ z22~;z@ou+R3mO`(EUS(KYu3THc)Rhmw?`Z9M*Z9L6l_Ens4a|QE=kp~UUl<Buzx}w z5m=r#Sb(*oL{ErWqY{^kp0Gy;mCSfsvp0HGGiJCQASmM6k7$`o8DJ51yv)k(1)o3( zYI!uRcJ|O`?l55VlfBlPssh~wHBer&p_$}rkpugsfHkQg$}G<!Ai6}L%$^{PZ_jo% zp>o*xyY<qd!iJ{$YugX1^vkVD*|I<LBz=*ApawrsWfZ`;4QQ@s&PPdpt{g_7su>bc zmfxb$bd%5|oVvIA=tz+-nabk+m61~(hGqe;q<s7ff((W|L2lKx!&L%<<&u38f!~3P ze7N*sn<^@YMeHZSq)<qyHOp(jf7G(5@ZL{S>a%QUVf>>B3UsawP!;wRO(enRi2%~4 zv{7G!zV@@(xtQ2}!0rkaO0UqJvTJx}1&Zm!;8o>?mb(qVEg83rqM<mMK5(K=cm277 zqM7viuwwI+BHXq1!Am>oBGr+%o^#76KcDIxY*5ntgLj_L`Rg?|_1{1INf@>~v1)5m z)rRg;-mg<q;)BegLB|cl;-!juE-8na{`Gk4G3|S+#e2}38?dTSE?b#sf!?Obm*~$a z;cqw-Aup!Q5ZL^D_qIVBGK~1XBNFSvAj~+HM+!P-fF5Y0P(tZfzq1pCecfBOj<9Ne zxuyVK;smA0<?>~uK$u*3imw}Q_?E~xhX~d%5XjYA>daST3CdyLSH4@1p!mW$WLkw# z2i<glL5wbpCQ_;ug-Cc}UA~LfRFn`y3%Ci|MMhrfM;+wvr<yRw%(8^t;dsjmz+sIt z9BfGDAjjN=!a01@>|Vg|%<zzG0gxGJ29ow=GrZ#j2~5)+sv$3#CvJmbV1LupuO5?k zkz^G$_vZAXg$Ta`*kMDN@Vxlx<y$Rm(BF~t6y28&Tv&?~aabY(>`7#Y_mzd@(+%P3 z8|Jgz`NT6_L@ZzTJBUCRNBK5X3kke?;x7Z){Wblf7MAGg<Bl?B<2ZtMS41=9N$d!L z4)3(v&omE>C~zonilr0}5veF)N(z^R!Q})f#HRVC2&gu~PQm!KpIC!%`Ol{KkiC!H zCG<3r1NeqV#pCJP^*-5u9?)ab6pwd(ebmlpS_)vLG@#_TDZwbiB_qPsjr^o}KeYO8 z{srzcqIFVr>AlIDB+iyVB#!wbO^IYE1JJNu3+a9ReCd$4X}mtL;PESrO5pQFe!SAV zc87Pz)CI$vNQxD4+YJS2HNj5@iInm#vF<pJR0$)Se&fgh>Vq^?br?%FSY<)<2ao4z zu^>7QS&mN>g!4^Ht5YIz?(-n!)=sYrVJov`RY>N9AmiQ6Ql(%&`{c`CbA3ID_O9)$ zSo>n}EV%=4YE1Q&eiF_eu#WQM*+e*xTm0s}1NCrAFj;2wWclkkegK{aLwn|I<M`e* zuiL!u<Jym6)_-u<uifd5kny(afiy?EPgEl$o^i_KA4K+FG2q;(hr(*hy%}pij#pl= z1BEO|$d*oBG!WSs?&&CdgBDi|e1$#CIx3P4=ZC-eDHx`1rKM4u;{<TTJbDvX(9zzN zC|~mh&9c3>^QJb*?xkYKPQluJ$ythNO%6xZ!_T7DOnY4?O7qIzCpyL{{|tLSbtF;r z^5+{#2d9daPBNGyrrd91)|I@8arHRzJeFYAG2eCUG`B!y=kbw&_>Pd6mXz&pZ*ZpG zTJQ2!CJ8qw&EL1l-y|++AB*}LHuY|Wg2u)DNk<Ut=h?1h<&Y#ajOI$sTX23D0}~53 zEp-nqu6TZ8e=c|UAVI^FkiI9aDVRG^FtK|jsj|)8b!>?SMqZl=1?~G{Lvaeqd9&uu zStiVa43w<bUA)+NBAg=vqBSR7JtuTx-3LO0gM3)4u1(NwSbWF<IQxZY>Hr<PzTp(= zm^y2qM)lD!`T$IE%rC;t$%I@ASj-&@EO_RM+##DEsF{BAlf>feAMTh=jL|0SYwEx) z__WMSH#LJW-npR3aTyPiw|*#^c<j4c{<DD0=@j=`jOVqcWC%3aT|IYm7t)}pmp_AD z40Np@6DiyBh9j^uHxHM&_^A{YN4_{qRLx<JHy0k5h}ilL`eQfccfvixT~z{5$ybt@ zJmpW(6ngQxdlJjl4@h)0XMHH7uB_k5qiOmhhOt4OvlTCu&*&y>-iBun0SeysdF8AW zX?ZXFQ@)z9v)lXPO(o6IA2QF|x!%3!VzI^nCgKu1o4Btj<RQg;e3>P_|F`V?cY^29 z19<bJ?%Z~=bg7=L*La#uqy{2$OpuASQ&i-wF-i#E0M*c5%Lea<9`mW0^RP_`epN+% z+Q$j3it^rPnVUTuQ<aV+fa5cmtNhw%IY7F`M*T;w?yV$c6si_6LD>u|GG@Xh+phP= z^fohTB{dtc^5aWUXVBc?RWDLFiInIuC1VSv6SKe|nUW%CtrRJBmfIW)8;<V#>@cK~ z!<9JmL@4M9lcHeYy^@-G{Fy@D33btI`0}VDeeG|ysBNKINnlo1PX`>zDe_>2V4cbv z0qx!;6oD@)xUI1Y_U4G22~yTZ4x9*39NO`_aThQWg9WQZSRMHr`WY32^$dub!a42F z5ayk3AWtKffDA+Ct8Bp^TQD^N5d*wM(lWS-c3H@X*xa^#|22{6L$F>`DJ`_(?x?-i zmQ=Gg6i#S2MkwbfR6Lx|7X@Ij`DRU9G$(fkIOOWU15Cx&SE-#K91!Ra^N($}?uf~i z;DM^34j+<mqz<&i#n9xrtN4-72hT-Punvk#70twD+Egyyi4TTyy&=ZVnq$m-Xs5sF zZFhdQ(OVMsXM5pMoc#5|CxDi}NhBD5pFbu57l=4_b{3@Uk`zB?aFu&q4W&tUY<|cD zQ}xh|=4y|S-2^T@2eIjm3=Pmh<N>o*%4^XQ_~iK6fZ}C3J=v1d_@7I&t;5Jx{!1cW z57)P1Uc227PXn{6Zh36S$t&V3FrhneGos~Bl?RWmyVtI>yPI+eUH@H~`*5LT?7pu8 zXNNB940nc~yo*^K=`@;QVG}I*z7?idcx9$vdYM1bxmJmk7mapJSLWRu^mGS2xIGm` zzDbob&1!D@4tL`)Y#K{&5!!<K%q%8|!9@d`p|&RO6Dfj<OvD=y+-<%T&tvrBFq$9O zWkxKiDd_I^o<mqz@P-k}m|2=~?SFjK^XAf>ke_qyx<fzAob50Gc9WQl(A6e|nx7*M z!qaUde$6`7d_`3I(dI(h&G@Fa6F9}~M0?jjWXJL<ImV*pqo=>lUT+Q_lL-GF#3mdl z3}^+V^tB9r0Y9RC`Bu<X6w&w~K;z?fVH0rBe>wF@PR~cz2fVf-S7M$A(2}Nd2&Ky3 zs+)}42|_6DvE_e*`nd<RN$b=^zn=7QHLA*Z5qpSNDMBLmiD?+CQt~LdIYTh*0`l!_ zAyUsnKItres*74OrY|r*1LQ?X;`=;2@Pq`sCQCkTF3|j`wO-dQ?UK0vioRD7kpfm( zZFC61(5t=eMosL^3*Q*Gv$edd$D=4GjDkr5-L0tak*bAs4i96v1SnD6DqdFz#iIe} zNqLrg88~}>_{CD5Y`EOFx&@`tIDn_AkV14x&!0Z1op+SD<w&{TQ-2Qi#UQ+ZL!9y9 z1R)e_GHK+;`9E=3aXO|}ms6D1=hiK+U#++e7K*KLI+*kdq$XA&2HqoFubQg%nMYs3 zPmgIH>S;LoxqOR1gzFi!`ot8ahb2~sk%YUBj0BGt*pP0DY+R`+*ezNy{6Iq%r+}WD zD4(^-Q<G4px7v-b<Lx9D6@5>p-N0{xcGsBR0$=d#cVgalSoY`NKIZ>k^)PIJOFG6% zQLc9{pGyupjBBn^9ouEDoF3+rp9b3FK(O8)frE*GcGymrM73f-8<<nK@Nj>N*P<bB zSsFMNj#o}SSbfDub{=^XK3jWE86XyCt$z@9MpE#DuuG)gTlaK21`mu9u*--0D-f?x zH44Ns`kqBUqXcZRLj6o>240WA{zkAN1pbPCd=2qgQ<?eTs#C%otz~%?u3#%A(!?_f z`xckbMFG_zp;dquppOPN&dLE;6KP)jjQtr1a_C-0gQm8K))7$Dfmn38k<Bbe!~OYx ze+<Uclz7WUg9QIUp;<O0(h<b$l)MoGKi$Rje%!1Nm%wtIkpfKV5`9}5a_J3QJ4u#= ze9`vQuuO)kYQN)yQJYvdj7iGXj6grvl5{Km{kJvp7+!4e9c5L^T`1FaGonSkOW2WJ zzez(gc%Ut?HwRz-l2_V2|IVz&0NVwbFA=wOVDnAXwWfLR86$vw&@Nk(GiX<g%dWrM zH-9eX7I!}nF^B3sO`4T?zu$9nCt@bKI}0lxSJRF!^~cyQygdGjEquu0OubLtD&E`1 zqdvGeC8%?6(4%<ptxm>s>3C<{=7#o!Tt0Y_Lgt*Spq6;@2xJ=%o6@m*xwKn!coP&T zHgzI4Y9ux)Hsv12zEGF{`&qSZImSAlC;y+Xf7cm^BDHYwppN$TD#7;x`Hlo-jr^qB z01{}!Q|c1B0f&qqyPj9}M|?Bbrp$(dv;L|Rq@pj-wb|rnx>Uqag!Jo}rq4V6Y<A#- zrP((Ebt5qpQe^G@&|o-awAF3kuapWVUJ;*Ya|u&N<MWsg-%LX^A61_aQFRRmFgAbs z45KVQ%qJ08sDQt4$#(=F?2E@VaX%p@xpn2#u!1Wn#2UUY6QH+ay;u%9PG;-z;ho3a zC-o?hWDbz^b8=p{Zix(eP}Z^H3=o*_LZ{60$olHo>P{N!j~cRcg;ZV}8KChUpj(jg zUk~V-OudAM#bl?D%Z=eKy%4{Upujsn(a@5OjibA9PYI@%A(`8cO86Y%MW71tWUG;> z`O=*Hbx3{f)FyCm^yw<Dyvs>VmL>4%EEMzv`iTWCuiA4|%gv=`|52+!gghNvad_n; zUkHSt=j~yEQ>PbEn2@Y<)1|9lH1>dTS#<(pk-2uCl#D-xUr4RtB;jVN2*$a%yCGZr zB*gs<C<CaZYH`%)mziw5VUu^0IKZiYvX|F=BoL!!xccGB5W_hBPRo?)gDc0p8YNH~ zcTx}SR6`6Q07PKR>Z0ghr^e<I%>uG84M)fFCYF$?<R)>(qE{rBofn1b3rf&bjlqbn zv4m*SAX&b$ioOX+x%+=0B~bU!3J<aJkJK`wPLu@QMR%D)UEv9$_jm1|(1VzFCCbGH z%Nq1JR?$-rq@i}2P;+h?&S;`db2h`;oLtlQdAjA+R(a+kyk8{i4<PyAocapsmg$fY z%F4qc9tF`Qa+O{raO3omw}9MmPy!sIQvlo6air1u%;zqboJ+p{<2X?K`+v<sPofz& zKGhtECx1l$JnvN&?|9)ajgc9&A;exYC7r*#H>fOLtN-94yWWc>?my6b_BRDfO=z<A z;(hvwxStHB%(6&Gw#yHez*gCz&is{wloDQ}?-&toN3@witE4t4Rtm9m!Z?93f=Th% zE*_4h;bwcRUB}TWr3`w(6btI1x(*w-7Od=<AUtgUI@!mhdHO^K$d~OV6`MWmNdS#u zx%q@P;?bU);^;yfi1FJ~%^NbTrspCXn?GKZcl)z%-CiKff}4OM+NQl<WJtbKlTq%6 zQZ`2hnB@C1eCCfE2@%@NR)+uWT?=KS*1?cS;(X&sO{~)_aqfs6VbiY{yL|fd)J1uT z2SLD!MWZ$ljhI)j;KUQXml0Y+be9kcgI@e_n2k&zHYdm%8aFr>f7X^^D*mM+pj-K; z1#Za){=TO_NY@9{2YHcyge%MFR?SUvv*kYf_{N)Kn`7(yStL=!Xs`M|aTyu@P9Ud= z2n*L!(~8KQN%j>WR${#TtVWj~2?Iz&g0)zB@4u<<KtL<aqS)g_4AnHTd1u}SdQvvq z%A)?$9QN66;DL24<>IU&pk`Vy9wn}of{ylQ%<9hvZpYIYPBBX3a@zkxh5ZxVqd_rp z0v1*7z%-SE99sK{nYYeZ!dCINOuORq_o?#}GzIDM?0S?IInsQSta}jG8298YT&0$1 zVY2tWt9Y7BwQE$=D93ujH?uDg#9vQx(r<;8yvwjx7hz01W|M=IOs*8ZF)B9~;{Jk} zK`V1&HL<BrYTg4n%jd;LGnW8r)hil1sJKt;YC>L7Lao^`#3DyGXMjh+sd+2y74(5L zEEhxT<1s)?DA{fzrbU_}29{}BSy#$dd12>N4C6+uz?7jH$&;ASz!B=>lEu)bxKmN7 z==^Wf{}jJKFPdN7<)Sa{39_&HvleTuGRfLuV1hVyE*ZuI3VxJpS5@q%GLH}q*E2#@ z^@3E2{~_xwqvGnCZQ<^Q#@!u)ySux)yIZh8aBtilg1ZwEG)REpPH=Y#?(XE{Iq!MS zx%a!HeyshYdyL+_x@*;*HET|CdQ@@^48*a^1}}hs*_5zPYAwH-n$gabmCG}NrDpHW z2M0N*WcXs;!d$2?BQB;&M1WD$ua~i4FPxU_Q&y(zI(c6dhS};LDqNmIhESVx#ZTU? zrc?OkwVNM;)Dh8CX|luSE;t(_n&mii`u~K9vSH%e1H|MHn35tF-E{prJve4jjH%cl zy)8dUaeEs)V%nkAktsQUYDzIWU)l2}gU5YgG8m?V)Nf7GE@MykcyIjC9(7~^)d_lx zBEtRW^ZkEB6gBOK?ZAY%HI7`#%w{8|a*VTPpO2WV&4P1jvSKZV!lfQ-1EF2DNe-=b zs7)5VMW?&i4A<Il*m=0S$-EE<XDjfW%vD19dP3`%Q{Rr0X15mOcj}NQ^+d18v!QB> z%qe@R>xy>#U{yNh3Z)}_+?a!4<Wl;V)>ujN$cV+kFLTF`GN(kz!sKUvl<UQva+d#v z0RFwo>jE)v9VyGCH$>Zb6?ax)iJt|t)iifip2C=$%tMQzTF&HczQaB~*O)lz8)f=F zQl!U997N=63jkIM$PwB`Xa6zDWp_ax{Qan~MIjzAFd$cr7kKPAa14%l(zZhEs;qMs z3IA}nCaM_P4zR*8lVI-we3QKM13?ZWAG+|&6~Q;DAWVGIV|gt+3V?JKI%;Fm!dsn0 zwA%POax+bgv$PsQk+)gl4Y7-awD||mF{}W_ih(bW+%(pidYfXaDLl(+(=0UcL*II= zt2NBqweA5?qAx!3RSGyS@cEJU+^n?d252eRwMx6S##WRl^cy0d<|Rgp5_iBZDHuVz z@hL@=o1bI-c*%;1sHxw1D?~_ZZ*!AtCK?|CaI*SXX}wKZPE;?b)-+$~G3$gw4x@t; z2{xLe^nS8B^#4J2*{58E_auNVY-<Uc6<{M*cgmeGLHwrkJ#?AmG97my><cC<$tkWG zE5->lAsd?p8+cNRDC~<l9vUt8JuShZIZL<MK>Rzl43u<HLNwKj(fy5wYyb+*=IF%V ztObLq6xM4pX<%BMXCpS8YQ|1YXr2#fN!Q;}yzl+upN7Y%ZnOS})Ak=Ty9+tpg7}HG zV8<%vgGgSR`11~G(M6CwNuXpVgc|!8>oLv{|CGi>gif^9s^W{FbF$LIBDC^pBTN{H z&~`O{wLGD#I&PLPQ{=Y+tiu{rnMyS`HIx82Q@F5H$7IIfLUL#MKFo%njfcylUUSZb zc;!Q-v`giRk5+APIms^cdqL3FLchz*ip}t1ECyikQ88?>3eaj)IWabD+7nLHASKZ9 zN%n|+OoZE{$shl8$;NAOdu8n?`2Qy(wr~EUl8gHSU4G~v&Qy?ci)W%*u7IGjlzP#) zEume>)R5{-=3!ZgrxIg>lkKi32j`HDQi5EN2IrY{N(m5$edMW1xlEP-ZXU{;2Kl+7 zKDO>823^$jBiInB5;e8BDMsBRaF-!7s=AXLbNS4TC~%r)Zo7=!N1V*)jrT@63?a=1 z65_WJBxqX@qs-QjtXctIC}-0M-DV*<tAR@=PyzKmzQo!>IP<9AIhb?gC^?ESqf(_{ zW;AjsWntl?grgvZ!{B5rzGOu7s0m(sKpD3-VhWtDQiTt-USd*(t_F4o0RXPouU_ep za!|^n$w-3B-d%j12C5q`6^2p8bcy_NiUJPL_OoJ(xO&k^-KI&GlhkLa)*dbogN8Lo z|HB~=dz>SgU7MS1PA89Tu%UsLf)>RNYG*dN3MgNH2({nJ&vdzJ_#lzycWM^_JqFa8 z%V7W-{lRBGvv4W_L%t$1EWF_jql+0u==46N>GZ-pt;Q{kg?*n{`M2`@-?iKfxZrM- z9gjFGK&%fovcNX}QjF1<^cfC(p8aseJdT(#ZRH`dRQ#p><Rb5Heno5USWPon1>JLT zV$Nwt>~AiCy7vz`+wLAs7{5=HyJfO_@ELa1FnyYRWPKbXcaM5?M3U4+ZT1j_6dztj zv*FzC_)&7txXVv~^Hf&c!XL=BQMZnGZm77mhiB*t@SA4cZEjnHfC*uzw|#`|MD5&A zKpjlLYZh4_9t92Dhc!{J5Kb~SX=TRb_ZH2_5L>=Q{DQukKD&&$zw{LBiOhg8&X+{w zsJgLa@;Jc=u9!@wC0r-EKhseufLQq0=s4ZMbex*m#!Ii(-ybguO^dKiD^b9dkFg;9 z0Y^6F6uj*VdBmJKRB=M>O}5M5S(Pq}UL+H~0~eIG%8@R3t|<ud9n?sj$1X@3mIt{> zL>bQ8*}d$IwNr210k$2Dq(`8(s!K<i$IY5L?TC`WA9vwIJ+x!6_Yx>GF2n*>tlq<I z?!g^)eWCuk0WrdtYPe+hms<l~$-G|T)jYpES;mxAGIlar(p)|p3E|y`6qn-uFIC{* zt9)fd$)-vw2aP_K0X7A5aFE8+7U`_HgS6mhal@iUZ=E~66hzX=R2Ur?0i4)_mKDLA zYK@p{vhPnBhLWAFDr$(;oqv>>2R!#OxMO_<J8+kbU88j>+{iY0&uQ+IY;H~qna|D_ zxPOujQ2Z4S`q{>9Hd&8n!9ZNUR`zr}xoU0Z6tk#dT~$8)PH-PNhfgJ+z}7?!Dj{R= z`n`{r(3K8U%}iG7t1pzLN>ovQLxcZvN|`^Mm^ad{PV=98$Y=jYwW$0gv~-IavAq*K z-a+2MWB<c5SJHD<qMArOk0X~UvvixI#>GRSgc`J&nH=W&K8x<O(tP#)Pmc0H4MURV zb&{10C8CUnD;j3Y+aYT#>vhH+^zAjj#R_esh(?a^Y}f*-+KGYhkS4XD1}cdLb)9Dk z5~nfBYfFxNM#%pPcUAJMd$>ZB80afv`0KG5|C}cw;ZYdq^n~{ZSxEIEz<!?ZAfP@? zuYAjc%Bg=M=tA|MUtFLB9Z(V}VF0JQ%|mdMUyZ-Q(F}1rCl2vri&-GGgiH2@kL+M4 zfZ>DT?>O->VR%b#w0eV>Dw%6XjQ9j}rqjN%I#*D9OA1P+2kNB_LX$4B*Ymrxx=LGH ztPeO1Sz_@;p;wXb#d`iW<j64$h3<uyOJT@p9sD@N>*1Yn`i>YnYpFHc`9~A;v3dk< zQ29-8M8z&|x#LyeiSt@HmoHXNh^CG#eze!EE*re>y;<`VPci*7FsdW*_Q(?Y*!_cb z)pBxOJt`nik-hSEGw19k$(Z{8DOClP5OqBYJL7l~phg4?7qP))Vf_e<njuut)vAmU zGV>LDE<H#afl<9aY(zetE_7@DxYjft2?1w6;PSRjv4Un7W@;BC2v9y=dNjeElPd^Y zO_^l=0*4{huVWZq2H;r@FZ{syf#BGKsJ&(8%TNBxt_k>>3dx7*Q#diO*YFZIWGBuA z=`SNHb<0ztr)rvF=K-Kt@_7p-PA1Q8dr4e+YLN|UE}^yh_@oPOz)~=w&$Ut!{!F0Z zNDW4>46IY<^ystYLzf}5RAhS$6iC=4$wCI(cU?@^T~@)lsEO2K1M^yv=|5bQ?NSPQ zl=TB-7*bqvycr_8=$Kx@mK_+Hjb@!W=k|bxqVA{$y9n6A&IQB>z}KH!lsO;xgv09W zhr<WZMj)10ovAcf-DrVWh`P;*hnP85Y_XS{!Mrwdm!qGhRo&Q8jrJx3ZxLd3DXpqa zTdTc$z`ShdulNP!d|`rs8S;xLifm{MB6paE`Jd7l+ncmEHFuTZ;3$Pw@x`A5p68*L z#EYJE`g#W6PQ4~b(}~&r|8J_tf6+ZYHi{yLCGBnVz8^T3@2ZJ&S~-)?ubb5KgP_#5 z9jTV+WGUr>t6#p`U(+Fe><Gf<-LmdPp5!Qz)hg7)9$m8j5DBF#Rm|qOh0aNYXcF%C zNg*CU*2uwqy)`y}kfJs4rP#<}#+Kt3?_cEsItwKVl%719EHx>{#lp_AcT1BMifo}J z*rXT8C3<JSA@QZkJlO)ZDT37()kIhI_=&nqPx5(&2-Z7{uh8as$rDXlOnx=Q{{KT- zxkzH_veW%9UNuWNcwC4g{|4Ym!tZL3Kip}F<3}?jH<`5Zcp}4W%utLgrsKrWGIHcZ zUj^2M|1#~3xiv2Cmq;)>aoCxP;QsvuNb89K#~eC}HrG#QRIM8sp)^8&^27fkFV5vD zv1G`!0@N~BHjS@~8S@A!t1gmOHuLtu{Zhg~UmC&6r{oOLOFjQUY;)CRXQO@6J>T#U z=Kx0eQ|*f-{i@R|xKVuk%os4y3k_3EqP2;PevjFuri=&=<M!m$q11GfbaY%y$0o5? zRAv~;ZPrErVOT1g2b9k*QN{vMl>p8^l6;5W8K9a{H8PrUJB!>;y!DM-)4|J)>z8<d zse=Z|DpohJpq1>F3p4LsQONaZ#&s4)*EzVNk=!h?;V(hgJYD%=#;bwr0B^1NCYEo$ zPKw3CXZWEQ@3b7$YkHR<j~msU7qf|z16s?48qmWwO(0xMMpvbThk8~p+Y-0DT25YA zx51~drQwgZsxJN)M+{~OgINm{v?el?64I19)Pj4<d4H1Au+`{y3wO$R{ylI0cg=@@ zosD*~*f#O}r?TDz-yEPw4PdHp=DL+2Y)sox2Q~GHED$@U$mp#HuTB_h+Eopm_`RkD z{Ts(!_R+mtYn~HyC{DV>3Z`r4dO>&&^U|m=B*tk5!u0}u1ALJb6hcw$k)Oz5CiHv5 z!pWj<!W%h77{WEgpgwXGy*!i;RYD0Wg0(RlF2LNyF!L`k>IcX#Z|lPXg(qEK49JkZ z1W}RWQ(Ra1EOgObyLAYUD9Ps`_45ypBPP|6rQ@cCRlNv54DZD&nLQ)6Q8NOe;N!E{ z1C$0VZHHFN`hw96>du?!4d&PIR!n!O8I}1wjGR$o@pNjX=w@t6eUt_9ixC`5Rw%}U zzBNT@Zf+gX5?S8&tg)i>5=pb5Xzt*F!Th&VWYtccgxpYzwX~MQ--$x|-_mOB+@qc& z&uQoN7-(c^fAEJPBewRMv60QU5o|(HRB?S@`^ZkMGaRb1o%uYt1-Pr?a_EQJRw=M% z!!i0G@{ZY9DFP=6xYz9|<p>~OaR6h~kg_sJYmPx|`e6Ve(<u>6$G=k1|90fw2N6>Z zHpy2~!$@NW%*Hk}GfPl&$m?|Fa@-M)XA@V&vhn(Qz6^4IWnsh+3&`#Q$@to=%%eEl z4MLrRi0t-rMKvW(Vc*_Dq~F5uN)*~teuobwr4*AnRq5Sy65c#6zeH@(O_Qf{V=%~P z*z~1St<1-<ED5u(UM=-gLtP<EzRx*Wtz6}zE|DWd`w!6-b~OL4q3fk-z+#h!STS(i zTdy-E0~1Zem)lEdse;Q=1*!oWBrg)En-cLnj(Pv}z;l6L|Bfm6hbwzz9oh7Q_Sci+ z(~ZgFbK);TJkAo2X4R|4$oUc?2%cpMu$14wl8S6bRe5PTF_of?r4p>l5Mwtwr6UX* zWP;gbJ3G!lBsbcdfcs@{yt)Zn$2rzyKT5PKHb(Vhi!NDKxh=mD^H6;fc;FLE>4t7@ z*xd!C<^6iu1X~v!eQJ`nE_;f#Sa4AECQQu3yV@0<*0Mt0x2rslnuw3Ej{-#UrR1vt zp&_lXU`=Ktx~NcD@JiX|U@8A>?W07%VtG*;bYtpamjKV-@=rmA+3k^O6B=dQM9P(s zdFqEsb3y9(FFNM=M4MP`tgqL9Ko&?~53y(ppWTc}jiLnOas=NwiM<d>(e4MggY=vT z@LjdqoS{U|r?Hyr+$a8<FUPeaAM_;9x<*9&orUZ8O4lj*0GXMLV&+g%aWaH}>JvC$ zLnuxwt5%8=j&sZbuQBg)R<dNVvZQJp(zlBUGq-1MMg7Um^%nFyWAC)1yKmq4p@m_y za|BTV90Mick63^-(<(Nx;IvnUF0W+Mj}iX|_v&AiL0AnDEp^jJ8ZZa?8yGFJA+2vP znMzj0E$nAIVuz@~^!9aP<ibC(rb@(@v=x3~i0AjmmUto)OjecHkVsa2*1=!zg6Q#$ zAhcQ3Uj7aEYI3KKWHe4<gY2*+p$Yj=tMGmD_YV#svxfP)1#vme$q*_wv5>kiwd71I zlGyFz)fv=Ig_4v{FIM4h#($*`5(s3+x`SuwdyPZHQ8U~R!@MN5%Y1Tt^T}h8<hs<q zP>O2A@q<ZYp3UG9=MGoJzj)|kV$Mc>&>opb#+~&BG_f;o>q*zp4M)(iEte^8LsZ4y zr=(SKO}`C4Aea|$H5p-;{FbiWLy1Uwq_<g>@3x^9OPoQ(%76G3;s5=@q}iPaW|6%Y z=Qq4M$(Xh4IVEF)3Li7<PY<E7noiJ~6AP$}n=QOwSH6wZf}u-k^p1VCW&=4ltm}vV zfqIk?51Tm$O!1XZNTV^IXqKudt_O4i_8qE#S@`UhZHOCr?6AUO{6gO+cI)5k?YQG_ zohg%RNQqa)0Jt_?)t)2~>udV2B*vu^#X>bZ!U?Tq7XSYO&kqlAcA;UC8fDnK>323P zTX<AdKI&9bz;!C`G09kmZ|%GX9g=r7i(`)Xsu~5#2rZKLq#`$b4nLdD?dHq_IgQ8< z*u;bNKg?37*v=z8aaISao6};+f)f1}zk=PHOEQG5eVJps7<s|mE8{FtFt3q0G8rz; z(NZret-@9hj9sma5Y8}8ji}IqB<EO<t&D)14C@@rHzv-ZHULsgG;@2rq6Rzu*E@1k zJLunlF?A}oboFz!aih<WjD_NB8j(L1(#gWg|E-VT@(-u(r)|=Wu->MIJy1xHn2!W~ zDRloPU|b{2p@#c&{>aCeF?HVBTKqbB65=J@j}VEGXc1FtQr@}XvUgGJ+;(@tu^&PR z|7rnnt$T~ysM&nzL@p|CwGDsNl-GLdOB%=Z+-X=|;!j7U&$Y@q+Dwec59kK@-(w9D zUtoO%zE~%|#d=0bw3ew0M0}$w=l}3YjVrdeh0(RD+APBEmSU)~E2$bPSRY74kXW)v zqc#mpGnuKIWs4B2L;&S9MZ5bFeH?kw*2bGGXZIq^c!yyS5^_~4rpJ4nS;d7De$}P{ zim@|cx~;&77yNU1!5N$d?1iC9Rffro=sDqx5&LBWhWf8gv=x2EJ*IO2UaGiLkn<uE z-LGJ&z*0mdNY1?W7ReP6)5)Z53fv*?N>bM^t#W;#V*O>SS8%*g#O=X3*MQTUhsW!3 zQ*=mO5wbXWNWQ<AhA@iDB+!d)il_CR<FW27Adh_a-2~OQrh`DAQYaO%4xhFW-j>xH zDOh`Pa7P*MJd(_8jF0FF?^>UlRj*BvyI)%<zti8VU@qd2?|+&d|F%6gDf9s{x_>o1 zG<QqED#P?E7ME|6`|cz#tU|fJwd-tmihQ3Y)lOr)HZ9o^<ED%N>M_odL|$Umsu(*% zEh{B(f6!RfzInz;J$2L;_7six_{Tli&I^JBrN)BF*CF2X(SP*5k;cos=diWkxP8oh zT&@t~I3;xdeL65E;;PfvGN_X#u>t+N(dC6i=uUzsN^+P!tL)%ybWx=aWW^tS2-JA< zeHa|AIc2N{tp&>Bh2g?ph^(r0RS|4RofcM4iGumvW7(tanmwJC9}qB`i+m&ur9aim zbr)zU2XO%%$a_>rYETY_Wa3>-@EF)QGF!_i@PdIV?!0;G{+Uv&eZ0Z>B=6+?+SmG! z*GSkHZ`<)G$9EIJK6Z+d>GI&ApI8Nk6xK@(^;xF9zXU%BriZKB4A_Xyh~~y!i9Ku` zD6OY%?Y`}c*8l{i{P1ByYFm9#H?bHya7KBR2Q+BTj@35V5j87O2{pqOqV6MSA`x|J z2@}rSv0-{GQpNVnNb&nfVhyDft9T)UJT>{e(i2mU7SHhhgO{VCFkLaU8eS%`n4}wP zG*`&0T}0_UG;d}9?<0t+UKjh(-Zoa$B~Y^Yq2C<=$sDQp{9#K!iiI&zW^<OyFJPy3 zuul0fL<WcEGdF5$#m}$uxA2DBf7i-tI$$fl0u&o{4NU97O77+5=)77yBK?6HSgP86 z>aG18ZAyc=UNqVbF}YQ>I&CXnewg@}D9Tf<ZJd<>=}Wr7)iJ++$EEGDp^`gCtGrAS zlde1@`>9jYRsnTKWR4+F-Rx2>GM4G=JTl`_bE!j&R%&nvABIZMP}!WH?|FsQ75=r^ zko?D1B|lP6qx@aXe-xDNWiyrexw7f1V!Y#P1VlrVA1XR3KHHSeL;{n5P`P}w0>d;x zln%dCUgx{N;04Sn=HP@pP)vt6pm3O+E#3rku%<WITS&qfI5H2D)*d&U2zIjEclJ0A zj&{!C8JXT%Y)V=%m`Hc%?Ge#}V*m&gog8cOPb3az+?t^LW`+;F)T5>S@Ez)jPOmi` z+fM1WaokEw%RLVKLiPMxRzB8w6;<?r2e0AES1*R|Xg0q&$Df~fp6M6qbzK@NVa^m+ ztR91Ixn&Z)9QCAYb#<QF@S01I;bdxnFr)qtWcASM<l~nG_c!<ag+&xOq32OJvZ7X5 zm>d3QGt!G${}-dVdcqTtVsU@v<PseyNEj#C5F^B#0Ygw$9Z<pF^=VvZ4BeM0=`TYC z`33;Q;ZjGgM+W#N4fb+~w?}<<oRcK=S#Z6;<V$AS(M$lC0{$Guj(~%M`fc%~&wmkO zk|J02J?B3g+LHEYYMnNpa=qQcWqk*tjpm4Fi#!ABzr;F1iz?|Gi)sGi{)lb$qG*_- zV!~k?dFtwS2&?&Lu>6MR|4k+qdZK_iXcIRvLDSoYv<ZJv<%PB(%Z3Vnkh2Va=ZHwh ziRn`t7oNKf<Iu+M<-+)cHdGELr<|Q_K|nz}JXu9T4tVV6<6b~Uw;LzTn{H3NL7mcE z6zsgL0z^>!>N$Y7xTRR2`JfnGdYADg>4m{1bP!Sy87!_a9EOj9M7Wr#Kja9NHDda9 z3aF(wm7`BOTX%lpUvW?RYrsP*{{fmI?aI3IcPK2o7Z@I~#T)2s^nv6^&9-MBP^}GU z*&CX-YM+o(#2S#nDL0L!x*RM^(ijSseDuM(8-*Ge$ho|?zbP;Aju%og9*bJAcF-UP zW?*v6#>D~Xtey>;D)D?URKA6?iXabc(SP<Xl&gcmK2<=F*T(ww^7kZnztd7rtJ8DW z%VH2-^d1h5J{g^gHiS|NEAbEFYqQ_he(@(sXsO`jM_@3S8YNyAD{JqVTTAO_{?7d+ zt$mN%(-n>xju|1Q22anmDt?=M;(0#zaeZI^1y6U;XyhNo(2C#SntEDvxoz0+>iv!V z8A)Cc`?(ASGxT72y-0XlM3(VAwUxth0JS|h5W1qe9}N54xLs7!U>u%#2^Uui2egCf zwP3b;brCm@HnVznHYApHR4+S3Uq`GNRAsX|_RmBdOzc4)!;Lj^XdsLlW%eu+BG2Co zcOPOF<A-#-mQPv>5h4*0U8lxekd0@0Q<{JQT*>(B*n?LkTv-J27_$FYJcuv>xu)5< z<^3Bm{W*MSq|=ZcGO!&G{3O+qI=ktLC_HP^n=ZSn%{<d)(-Q2(4P-jO0dZCviq#HT zD0=yyNk`P7*p;>5$q%uI=7SQdq_R$_m-ytNoBHgQX&$nupS~S<9!@e8L*zy{{m{wW zt&Y=FKvg(hOqx%sRPL=!#g0tbw2#0k>YjVH0pY6bJ;4o^)i!QuZXF0)^CW<Aj;xd{ zUqW#(g7|}}{0`Q@D*jljnGKKSGPe*%yR3efuz**ea{Mu`qr!jRR*M=uV<I|_k8V{U z1xBLyXGT`&i_p>`aRYo&X*sH(h`rnBm{d06<?W0?^@%%@%HPF~fBX&4TV$5=&l`U4 z#CvwS-mg9G?Ogd9UpYSB@7}k+wmWvJXkyWhDD#zl)&vdp+0z@t(z}mjIgZGK83QY= zm`r>b=LcQ40%|nU6mfbe-aESM3!u`I1$)o$H&T<>kgp*0&O&C>XjzL~1Zm3|m6)M0 zF7nto+3&=LNJ`|*3=&-$fbwWSP&DHi=IEHW5l}L;hmX>Tc$2U|6cRB)dnS1-uETGe z57&^^CJ}RvO$3~)Kg87qybIofWRuOR{sjN}_=uMstJaA16ISSn+C6+H%`y5k;(Grl zJ>XYWQ40?sMNXL^Sj3{#u&I&`QXMO}6g-GfP%^+P2@}E$`LJ9t5nEwdpkDF_g(SJm z&vO2`gWx!21;jU=wt{4u5`-Asq^;Mr;V>%^QQ^8>Am<x&f_ES4(ZG$qz)zatFNcPI zD2(D<JIh9!MY~OrgRF5zbBj}n3Jm%<HuS<NsaA8qb-8-n$m@-<4~j<&@QvtqT`w7j zSpg`A8U6VMn*8`|e(>oN`l9GlqQDu_DTfUiK^uXg$Kt>=Z1#*?5wv2%B4D)%7-^S> z0)}CUgZ*9(k4aj*IjUaDu{~mQ?&7+(VLvBe37zkq_uzN)WA;|*ukA*&2A_|Jpp!#F z>A1@v@!FSWSGRX7=>7YH<5QKD6zJstBS=!9XD!rvJ_{)->|Np{NMVC#6O`=;L;&(M z0OefU#2%Cm_jFCavU3ji{9RiU_MQ2-t^w^O63j%1kea?@xE4-(44|Mf9pOg`ppsw< z8g@ZeqFF0DETWu!b3_s`ThlUI9F>`?csv$^H1Zs-6zCB{*KFpm-w#7)9^(#BMxMOZ z`B^f_VMP=lB7Q8{_^oljYBQ+H9U=-S6^B*DR#Y|3tj(A8g{3-?1z^+llmp5mwWGvP zz$XwfZ%k1fQxZo(hz3=#emLYy*-}77idK6hd!8B8Z#};xLGD4cyVBPwV_rTXu&g}_ zYmB(Kw$(4fYBn;2p9^dK;RF%#n2E{ug!YR;j*g-*)B+2pBC#wYSY^KHJ@AV~1K;9m zg_8UcRnZ3%WP8)i?6G;Y#nD$}(WXBGb#Ru-N_yx4S@fpnIEgV+Ys3!2h#`Lq|Hc$f zMi|93Mdv9j)0$!q)H#3zEfmX;t1b5Px~WgkkR?3#yIK9vCb6o2O`(hVE>m^@MLC6O zV}5A!mF-7LL@$(XvhB)jqU^U*;-O&az@*@NW7fSU5|eyk+<A&b{{WpimPc&2G8=_s z9iYWBaj_Rb9SRmWMd6<!X}^8lSY0Ze|G#%Q8JZ*?de1>)ZiqYVO#vcfna$4s0-uV{ zf3y3&T4OF_+aSMNOUgI201-`2=h^y7UYf}k<v?`nZ~fD9*RSM{O$%*!cF;{j+*-3F z21Litx(0{>6`9?CC}j_Alco?Zu<)m9xZbM2iUC42{qR}GzecZF+@YO@?2N#f#8tmb zABmj{mrWWG#Q^C1tovca*g)kkXLzpoTM}B_4WsE4ru4lL(!JMge0(OAl<@zFRSbAW z@7YgsfeDqExVJEpd$*#q2&q$a1`&FtOAf~1t~$0LK0aO<*559sCFo=!!j4+Hf9&9B zhe%};K5`~>CSUWC{XLloxNC4eoGzVw7t@v7&`J%ty}fnLa?{PG;Vyi$NxB%ysD9oI zK^?Flx?XCq*?CErcRg#HoDxAYi0$|I_LYd_8|{vtAq`N^%K=4t+U0DK)aRhEWyoi{ zFUmA`_w{*lOF(+p2c|%ZQ2uTA(p^kuBk=F5%SuDuZM7E)u|@=J=PN?l74L4HUylF2 z#kCM=_CoM=qse|tD?G(`QvaMx_VvVhz$S;W1SsFq|4Il4Tj@^9%JnUC*XB+&9c;AV zR=iH-tEnlkHtmcdsWs{7O?bJM)kyeUh>BY(rva3Y1p8r4XVH2u#?eC{<ObM-quOBJ zDin$7N_Zsowb8;MarbY{643XFF6eb*4=*-95sH!RKxyQo*hxc@7>c%?NT5z&f$dKZ z`V;IGvu`T&U{QIif$1&_X9&4KNU2<+MworsyC&)8Osf6ptgpoPR`d}YOc}X%!fPEV z4%SBO6IDAN^74|7?zqvUW(T_U&4-6!7x@FqqfQlr;}XE12$*Bc&lxTZQgV6d-a<P( zdg?GG9650p(nEi%J~Oo-ezaL1o!99hFq=6QX4FRNkBq`V$)y8S;%`zcmBk)>N9Qu8 z1eHwi9ip`wl9&QE(F#*09iXDc#~*?t9Wpd}n`8^sB*UeBaOX99I{F72ym{9$5<=Yk z!<cgLVM{PtNt-Gi)o(sD<9vM8KI4r!8yAA#*%_&e;l@HX$m~zM!^y|W_9O<HC>KgS z#a37C1XXebngry1Ht8#iRE7s?P095N1GPa_ir0gL<apr?VY$vKNvVX!y$d&9t~`o> zi#0ifBxyJ9F&lqZ-}ZoG&?goPUdK<L25mAX;&H}INAH#Lc?1LmF4up`M`Im(C3H+} z`_J1%zs5QL@fI16d1VUpY`gb==J)wKk<hx{wnKc?cCBY4@`XT|DgRm~AAZ4pC#gwb z+^;&`S5G^^&2ZWCS+?tX|3?k;!H;dvnvXT;?mfHve=VlLt>d$FvwH3GUlKa~t#hT5 zDHGr0;^vE}A;W%aZu*PHua9y}!(WMt(@4&uNmT{l1<6iHd~V>Vu8|NQj{$LwADW(v zy^n;`CLaHK#lC)lhg8HzT}f?_0Hc>q%~))&Q_UiYPe3_h8xcmhoX8%10lJy!SRkz_ z))nZIFl6z~u%uNB0Jlc4T$Q9KA-h;R9Fh#{c5;?4`*_bshawC{tzryDy+)38;$-#^ zthM88K)Gc$$W%&H4&9(|j#4V@@KJhEb5R^r+(>owTb$R(M&z(PrkD7{b6qckln0=c z#-q!^1{Z4U@I+_Pt7+Pj+hYo@)N&)q_~{hRE}}iTh^nCuM94WZk4cr8E&txndxQX^ zmPvaBMC#d%F(~ZjrI$`^8#m7qK1FqGeN)ajGeMvT=4g!2p2S7XD$m^LE#%p5$#on+ zpipnX8L_XXNE8-q<~(7@(;XW;fnfHwSZ33+s;J7LlWE}>h@71!2b_zYBxvi>_{)Uw zrOS=#m>N<1f_AAyb!|UH90g2g5zn)DoDVX0fU=@rnSK%>1zDNF7FPA3ojjrM#}{NE zc$k&8in{{JO!2b23AjTF?WC)^>EJq$2(6ujIi$l4LrY_{7>$Du8ihKloE**E7Hh9v zJ?(v!JE;CUOa4!`{Eoqz*x@n64Hhn^U(qBq8QHPS)>oYUp%bFrWSX0NxnLUemDBVj zQ}J)bFMR*>Ws*^;H2l3XQRgQ$iU}k8hr;j@>V(6obWx3K>RKDsf#*f%uX9{42r@UH zvamR<(4=EsE}pr<E;lWrsx^?^ynoGGu9wpJ6Uf9AFIXS!V6tPvvC*gY=lB%|frRse z88u2gQE%fO^qw!B&j?zo{jDvb;0+dO(#?9grm2_9PYgJ1M*lb06TtVaJbpVos=F<k zx!dlK+1hwJ-RMyXe3~J9ke11eUov?JGl^s&Zp0z}jw*N|>UMF&>H56pxj_u%;&UFl zgJynxkP$)HAJ0YHpU6*8ZW!dw8Z=R`$-jQs<LShImq~pNbU|_&C0?=z5dKG={)wvw zrSs;+gghHZty(w^SE_6J`Q7&8;vdnUt;`8OYp>^(4U&Leoq*k}I)?lj|5N)d3(|Kz z<g2-w0C%r_ferlJBX5Jjj$_Cy{NJUC0PYvKrFW_L8q^w}xvxbM(@*^Mffpg9q5t?C z#Bcss=s0uVV*2DgYSwo4?k>_vD*VumDs4JL`nE+HzAt>=8gcFVH}GXJFf{NXA`s4V z)kWsrQ2im~;NZsZW#aFP%0nNGFnVgY!1`spms9%w*&?(hA5HH4r-1A1-kR6t8Y$SL zgus`b3zc{Ix2=Ab0GEf~?>Zaye>_$V^O;xno=5_<S`2^OevT&8>vPXWSkSTx==j~t zeD{bGByr;E*-kx$;(~`KMT9MoI@<!NX@G?J1!;iQmHPbeIFhZRZrn;LT&IX?#7dNm z&OLjpq40Jf{nzNvI7a*>_%WwY<em+C^mGRVX3(})lG!sEDc#_JqENzbs#a`o0I5bq zG`lc+#r<gS1_s7P6|>LD%1x3hW#^cZTwq1)nKO6yb|_17`JB1){`Qkh@-{5=32>_V z3i|_@eE1tR@(}}6qv<nfI*e<I<{KxV5+<4(NreL`2m=M2_7pYlkGpFXz|IbkiHv!6 z`ToFb9n10+-MyPU04ib_AwCS>f13mz`$EeCpl>myH&n+CQH#OymlChm`-y548#B@k zF@>W2IJKA^%B~o-NYdaxV9&UNofTLYC1Dfhs$So1>|igf2L1&|No^Hg?qww}?eCUA z4Sl<?DQrhbv@a&H3valOa*A9?8nngdg%x?0CUg<MG+6zyh`zB$_y-|-VGx!x-QZ;; z-^<T|w#kZfB=lGdkam6*%l3J@zRj%oqW7X`JFwrCSA;Hx*{Pe50)Ymh6HPfnAaitp z>EQCo2oC492lp4+uYX*eY7ev5Z^|x`)7$@C1e_+V8~oihd86_>(#S_(@tKP0Sy{hp z8gviDDliwwe;l(Rhf{#0^xIVT``)r|{_&phHGcTVYHhO(k~*kPbR33=8+`AYzvZin z>TBfa;6Nn24<6W?xE!3`o8k|4^n(%52L1$DeaYvLIW9kMPKRzMX=i?WH}2s%5<3DJ zhrHWg<%^I8T;c|@9iilzO#I+aai48~Mp^gX&%N{+ac54@D#sf0$E}nFyZS1X{O3$c zq;&R`gRFE`ZTqoh7gh#d2RbER&{d_pxM$~oYcq`ZGg`;CkmP6lgE%AtFEI^6*E!KN z<!|P&9PTf#P5+x}CDBoSGWDdU7lDk(ZT7>*R+dDJ@0=h`ElRT9#(7C$drvF>={~dT z#$}q`+&QI;*a0)r$s|$ATr@U-1&nj_fK9C-TZ8EKr^Rw<NF<EeH#vbt)Q&T4ywekf zcQ?I5PLG%_##qb~(3iEke)yl{9nDFY!SUkRUFZ%;AkkVbx4&sfclx4oWvAKQXPkDq zRIkP{rYJaDazJB7v}&<h`Lz3BBqa()<aknE=H>jK%M-i^ym0)rsOQ8d;d6&DbR%_9 zsfg6d*iCE6VuS*)ix?&MwwS9y{u-%El|iRce|)$XyYm2Db03Tb;0!+cQ_P;XFjS+> zPLs@u?{}}Ut{+DbfLOjYiPx=_IPT$3IGiAOvzPhbsKV$}{0`C(6E}>n+>ZU2>{KI7 z$dS=}Ctu<@7_RSF6OqP2Sol-93s<z9Rv;_8kn{!~X;A_<!>~o9n(P$_Bq4rknTHq8 zLIpTJOAPdS(`|27eEJ>+Q&6d{4dtMXkirZkHL_$Z&D<A}rtsEMPb}rPna#w<{Eh=L zYPI65`TX_mN#Qc5^xs|nr~dd)6hKG6^Yu@1MxYB&^2--FLXM*F4lX6BO5`0jZ~BM$ zwPIqGtOo=bAMBm^!)t%mx}bP63fsOMW_(EeKw$mYeXs&-T5>=6%G^!&r1D({#zH-f zgGG=@M`vmXGnL9KI#9=tP+{&sxObfH&*9U<x~U-5fZ4^p+R6;YTr=#V7R(Pq(;`B; zWzIz{ux=>=vPMlWInt<RH`$9fn8AfLWIA1vtWi<J3{?Q3ZMU{~alYN|Df_=ayYn%n zvjMQ&&0$#brgg%f8RTTyQG3whz9nZm-nQ|2b`h=9P9A`paXpm~z{x-7R)5HdsYo%j zKU=9$7mu?4De%}}>-}|3t2GTL*0}qHnCCP3Z*C6{gCZokz4ajnSo3Q8?a&N-)Vaau z{^!2Eqb6@x64KOzG!=)}`G5VOjdKM&nfZbFuO~JpI~nWC%h6X)lM%vLVhVS~y)SiG z-AaES^Z()tKMt!%FST!iS$Hj7CeaTc92OoA{IJ?7pS8{^UKRph7kEVyo?qY3w_au; z+&r1VzwZYbS8&;9STANmAfR5lV+;9H<o?lxsj$~C_bNo*nafv&&Krd-D;8h9oHj+u z;NjF7veun0%TY%mf8k=eN{2!`1ZZ&^SLFMo7(iuj{bZjX)T#zxC^aeomNBhC9y>CX z`<BE_-ob*@eUG@17X=8v#W<v@jQA&W+?uvqW6`ca_BX6KeLHu25R@op@VyqJ#kzyG zD-ERdiJ@F?wUfbN`n&xdW>ISpLq9sfu27}sC2^w!c-Pl!5=<K`0Ts5agFHAG+kZ*! zS`#^9bCu+Xu&F6$MUc3gsO1u*`2ts?tsa#;a7}`+gN`8N(%rbvmG@(LmZrxb?92Jl z8MbaoH0@)2;8@EkMQ@aP9zfGPXids{=A~b4ri_lhXkQ{Mdls4F_}ZWfa8+fl#Ccc) zaW$8%@FE7UhK9}bMA{xG+Y+6G(H}C&`^i+$Zt|MSG9`7d&KXvQ^DC18X(^sq)3zPD zysS?%_{m2BMMTcZhOG%*xZrWbD1Ds>sL@x}OCchZvu9Gvi0iFCOOv!7vJ#&~T$_Fl z(!RZjN1NcBwybS8`T4HaE_N<9Epi5q9((W6`P~|6NPVf&-Od(B4D<k49(K0mbSkg; zAzj-wZ`Y_jQqcnGx0>_Fbq3S)5E5?#Uj9pH5NWr=fLb^oo{zQ9zT3C>-gT64<6%Je zUY;T0H09nlU+sR)(**j(M+7H!TWIO@jv;YrBT|YihB270>uAd7@@G9$cY`nm^dMYI zJ*t?&d!2>ac`eY1nZK(Nsx>K`8a5^w9N@qH&gm@9&0VqST<G!qb)9>Wl;B5cykNX< zECAY@J70gAZ<c-)>uM+abju{RWGrLM6Fg*cowd++efFb@`Qf%EQ0U0;e%<#IbjgNr zgA#?@e<PbiRGS^^njcHyB+VG?3Q21!q9Ohd#+o7~CoxA9CaRy(k9L`iU%yK)0c|Np z58+leIUQtdmdlc96Sd8WPyk%R@%q$oCD9ya(icNx1?X-!a_HRp4w*t-N^{&EGL^(x zi;`dsS5&jI9%cI3g&zG5{5-ZL6x^>f7tTSA{?L$Aut23w&zr_<B5PN%`0^=9bv?9I z^{2$En%KC;K;R@c|H}Y}xQkR1-3d+%9aHTn0ibgEmAeUahs&rLDvCKK*13qO6)dU0 zh(ZBS(Zl>wc`MLU`zt;UVvPLt8;m^Aw@PtyAk{m~>=zSyC@_NMF7tCaXp;&`6k*5$ zZqnZK6D*G2G!!$r0+~e^KGq6sfjX{1f8-^*J!>g-6Plv&5>_;AMi_?!euxvhs13(D z11oGgQ2PhlmGZ1|XfDd<(<$AM_wh{7&^sfD=1G1Sp!nM~d%B$|X|G1~#1Q@Y$NY!a z0h9tdki2}3!u}kMyKgzrSGR$b`)hl&h3Ut!v>2Q4_*I+VmiOiNVXyf@vW9XjWc9Vc z?Ab905fR5PncmPdt4n%2sPEMg!piaH1AHfFi@h16vrH(7R0|2|rbKL{+cuUulTAAv zL%#6Tn{(G+x5idf!lp>Lw=`7QZ3D7xvH&l8KF=Dgp|e)WqW@QPIZ^;;oG{y2+haSS zpFvc2iH-;&gXaBj@Nx4$7KZr24d)0R5+CJIf9p<gD|vKx8DJk2OQp@hVFYO`hN`V_ zZxU;-JNylr?3)*h8>Hza^(3X^=IayTGE)*sIUGhFy%j{+#LE1!zmk5x>-dos6dHYo zb~IOEBHcU);%!>&hs&RY?hxqBqle3w4o@+vk}E(yFSIj#L-$`jKn}JrxBI04UE6Rc zl5oK5q&n!4%T4_+jztN-s@+AG+s&>t%t=`ws~W3_UKX3udiY<4H(yi7G?=Rb4i!wI z8HxC%!AQfDWRuc&)cC1xUta&vh(wL^<hJpze5ScpO12?N-+BGVpYTE_@Kz^@?=Y># z&&tvj<ACB~&`qAd+v?_mYEwbG(nc@@JRQcNzuU!kgyg9#^0I}x<1)_i&I4I$d^amO zm$E5(7Tl}D!iFV|zdN)cJ`4Vln&fyeHV;n+87XqG3Yva&`1=?jBH%Hhff7GmM@N`8 zrW)K29Y$<5Tb~dM9z2fI+3!G6&AqH+zD>*BY&ChEGl^5aX9;{9t^1T)qK59ni22vM z22%(zMy?9USnCUS#oS{w!@>0g<MntNFP7D3#J;dwxMU1QaN}+*T%<YMDi(~ZGuC-o z09x11F`8`(saRW1XV2u2qcJ13iW|=49#Y9GmXycjI@0s;1k&7yv@Qj<5jKq^Iv~wK z5=S2uYG4Y+d3GRkll&Eno?_-Qtp&!uWR3wUhd$2Ky?5(uwG2>V9h?dSDO%(<N1(vz zKBljAIpaLJrJAf_QJ6<c#2~BY_0PF+#2LnoZoeG`!6~HxBW1#?3r$h+zv<etWM`?s zhRfHGkuHz@<P{q0SG(g+kb43s(YXTnF6gvaRgZWnt2COnEEkEPgg^LnNT+()lfnoU z{kCUSmRd?qE{0eXY;@zZ9fx|jGNlf-6_#~|UZd}FnhDZw&4J!eM2Ub-T2CvH^zChd zjTd2RE3KMmQM~PU<vNcNFxZjGnP?ocI~rM~qMV7kEpHG_j#HEH!g1~-6Ar4{W&pi> zVPKr=$Rxc)B1XBizPrX-z9Tv4m`p#Ex5`g;kwHuOsCj+N*QsNC{)-^UL(rz9Q-AQe z`CcnO=FwHkxjHuB7h+_+S7-Y0(aOe~hu5jS3E>&oYuj~!`((ogGKlDL$#>>4NJ%zw zcirST`uwxNK&(|bza#(oN{?bvyQ|vWlHTwAH|*0}EqZq<2v)A-c{sVmKj9@-U}hLh z9yz50)?7ybWDtw)_0xq$?7QvH3;<glDDD~g@1@NVCFsJha^HKc3KM=^dk|gr+`U4X zZgJhw7?H;Sw_S)TTRQE(O0kPT36R=feOj~c1}0OOHS@6FFvK)pqVy^FAyqB0>W^$X zq%Kx(5;#pPz|?Kz`AR$9DNcW4#5HJJx5?tKten{J(D-R)YDHrqLX+8x!XFm5x3QjG zgHgi9)zTah2rESO+fC_3ZvM_8ZBEK_AW_?wAP%Ach`9RCTrNw;$T%Dd)xY77EOK>p zV+oXZ3+r0lH*3C{C6kl4DrfQGF8;{;p#$JdlRa-{*XuU!`tIVA_p+$bj0vZ1v$|^x z%K#NVpp@qhErsRHeRhxJRH(M6L&+qKKAYIZ7NTa(3ifwPlpy6^<tMAbb|_`%RtQJ= zwXuX)zHk;9t8%0}1B^9T{LGc<2vkl()(1vXQD}1a7ETrZM$)5=n1_d`D4qN;riH4e z*OqIJxh|0sh;u?;Dxm0jpxV(4;*Q7BxUtHhM))S0B|N5KDTKl#ropO|`E!yl&J2~X z8a=dOgkNC`!q%&gqOtgKu}RzXIqRe+M@qEoEd&*iRV4Ow|MR28vts#TN$kRnABV$c zkr)rdqTXT|RW4C2f+zF%q(&0c>zqp#Cs1+@m^=sYpUcX_ESlVD-6EE#rzB)^A8F6B zA=o;p$=%8!-|Xk|cgy3s6%c{M?K}3l2vNOm%np|h{Quh)PLd$07fh~cp9R@rrsA`b z&<>En_g|SbUk@~f^vad(E$}r9NUGz)S;(K1W?%Id+R7rClCkYfOfd6rg^=Oq-?zh^ zdDHwuWN=+p{HFoKcpQTq({$v^R!(}Q7ZxYJ=>*`1;07dXzOo@K#k{)~6A@+nO`UPd z-S5R#>oh@mi#Ff190=9F;u0>RPjORTL4ktp5t=eZ>1M8eH<@M~+1(J$JaEC5A<KdO z6V2kFh3~3s(Z)415%C&98h>w1{$dm72Hwa7V&$6GJ7(zb{)5jH_s-1<^Up)?=ZFLf zvopfyp;zB*p$77neSdy`!|eeP_iiG}?0x&b(A49X5Z~_{p)~D!5sjwQxt*W{%fY7< zk;jySNN%oNWdq{Tvcs9o`yf<5vfv^&vSZr$x@_FzBj@=j#^;E5kst+c;$8D93NfWG zgzj+1mV%yVhHm6pJ<>A8NYpsp`gyS3y73w`Wt;R%rEa_k_4S{KGx;yvrT1P(gG-@S z?-bc8G}gFDR9u4Qb&Lfk>QxREUq^I)VFc{(1W=T{C@8B^g;ou4Vd5_C_=bxAZocMW zr_uYp{RWqnY=jWza%ogXmsz5#oOf)`%4k)R%}`J=Qi6r8-7Q-5s-+5s@4w#J_Hpn& zM15^roM1SrZ`7h@l8lr3Lqc$!5qTWdMW_1UKdpZwN7Mb2tMcV%;VyYQ;H%WL#lY+b zcT$COUPRfCLA~Z<cwjz!Gb5S~i3Tl1Oq?GvCzy+zFp69jPE=h)^^%!SHKSjD8{*Hk zJCjC<exQhG9cvF~D4Azys1>2u&5gM%dtz{Irvr@Ak1I-d%_oKhl1VrA%SNJ26vQG! zenm?<h33Y5SfSD<D;tq;wKnI7&t>}p8pJ`VVti4NC{|H+f#}3tiY*cvn=8T=E(Ow7 zpPYy8bw|=VTQNZ*4m<(sJL}AY$4Vd6g95rNfgV}m==G$RgoK3;JZ9xZSAGFH0sPnJ zF`dCU5v!ltUQv|ci112e9b`!fCv*bH{Y92Ve|?&oqzLS&n(&HSTHVfgoXA3!X^SLm z$!jSvUq)PP`Tot-eTHXuB{c@ItYM|p%Mk$~>BppvK1$wIz=JK_V0#3D?XB=xgY+G7 ze&Kqj{~26?k%>vdy~p|7{i>@<`=|Z;^_ZJ|4%srvd|=t$bzVO1Aa%=#O{#w^kQL|g z6`>FNfU%1q5C(1lgLn5@4Kn|a@}taV>V(wbJM?pjn<OS%Tk8<QgWyq5><+CLnVNF_ z4@h*+tJ6K2K>gKE`S7(e1+uT2n6d!*nXgz}ALVnF_xELgkaM8T@KZ3{TtYi@$NR*m zy3EryZYN%lCTRB6-^(ANiJ(^WdU>IpyUDx3y}Ml5r*fY%=%2_BgfAG7c23pL5<?A# zhO4-3u#S7S`gSF>@S#SHXNgN1c7uF(1)6Nei1G6$P5_6?3l$N3!?3ZXRn`~43&b)D zM2jJ-mFZS%Vz))st9($b0GwUIb4b!Ax<GT#V85|0sbHwp-GF&<)9I4VGel@B89(k) z)uIOdL>l_|PUtojh4o>k7HfesnALZ|t6IEo=%J5QJJ36@JerWU#vDMJjsVa*WNNqK zA7wDZd{M-&<<8=knkhhb$O3NWEkEh=Ho*5{j_I0tV!<qawg4)#-4cDAtUH%CIwdWU zAzZeGEjcAL`#KzLW!h4IaD75ljRCJj;C>q6g1d<&KxhS$_j!J2$yEUi7;d9*yRCz! zMl`X)=IL@szCykL)Y)o2r~H!ptJAApY09Bh8&khc5VQywkQE7tBaWhgy3#vgSa$ks zJiMpY4-<R%cn7=aL6RU8#ZlHA)oYW6wKq1Qx#C+J7{{w4bW-~po(@2|iWmaRk}MX0 zk~I?efhyhv_&|B!lmM5l1`kMwRAL)TRV#06l^IBwYqz|UuN%<9hvu*6HpLB$V9DI; z$2rYFPHA#xaGOXnD#JrQdq?(Z=}ji||EA6VT~$h<8?zd%j_Q@rEjK|O4jRSsghqzB z55o*TC3kQwi|hwLQUz>c{-<P0Th=ysG0H)lIcpI6-sA7fPPBsLQ()H7woP|l-I{f& zl>F(Meb3$g>2F7gncLrX&_mbfRoj$b8-tZFpg6xT^Zhl)Ziq(v2(riWfV<NrCjJZY zWK6V(pY&G+9nZlX$8i+?^!T!4IvY3EEpr0M{SaI>bimpcx)FJw@$-WJlw?J3Ipbf9 zXol(^jL0%-vXJ3H*5>=TL1wI*pYT54L{z*Au^lMf?aSkLi#e>%{Qh9$VR@>9!3lm9 z_sQjf>%9ARgKbuKKTy0P8^zb4F&!4~VFmK9spA4M@us=M|G@jj4NH!#A<Glq=$yOl z(1wSxIJ9>~xE%+RK>y6u=5ZD8;h_SJ3+d>c$)xIDwZVF2>)j*oA^b9EDLzz<C-|KA z$94JOqH2olh<$3$=>0A>;xF;f2dkef{RZcL<h$;V_g}yL;~Ahfp0{E}^X4)r^sUJ| z_mE38Gq%QSty2H#otUR#NV~71X}HstT}EVs4N%A|ph|j0rz|zkV2IEh%8tr@QGVu= zjlRzqQIYIWhQ|MDnq41{-a<c0rM{qKX3PF=tv$5vQjEV;X!()dKiJMOPRhHuyPSw; ztDO|hdi4yYhaP=JLX?yuD<_${Cb-_n#4*DC`{)N{2zh4&JR}jmi>|n0RMHmLB@!tr zPgpc*O;nDFt-L}$rHPz^T_A*Du9ZHI{M;%Bib`ELwY)?1&qH+^F@Yqn=Am13Qc{xe z|3}tYMzz&;-8u=uDJ|~qS_oPsxE6PJmlk(-D^lFGxVvj{cPri!thiIk;d$Th`OX>R zjQmLc@9cf=wdS1H{0!n(dpj(>@m!1b<!wB{gz{j}gf8>#0JD^zAe-t{cX7<*^$rb= z<%%W~{SH?ax%rVs$wqC?4Z!^(y)k@|6+(W*KV7T^nBb!NwViY;`$mQ6tAS}yc%Qdy z<Uu%cea^N~8Kg<&ZC;~;_5*OWgYlheES1a59^ag@13hl%3iSwn#)Ap)lSF^}i? z)4zA`|7_HFe)xea^u4OiuzQMx+$VY&$^U?96UK>2i9dO4JLOX}Kcr&Rk*fWNd;6jh zv2fX(Sjr%}rPXYNA1(%O=nw7wK*u0eYdimR6M%Yta?FN)L`$RR3N|fOJF?hR^LPJh z#=A2BGneY2p`Y8=Ep1LtgEluA>hALkps!mF(!#L9tl`;&ul)(2o-Uh?tpTOrOfYif zulvN_pS(NY{U{tgjXrGh0^KvbDc6J-nObTX89KoCtzIpjo+n~yX4#8J6Ng5_BcYnP zb&j1$25RxF`Io<Zr`f(?v679?-nJqbbs`b-72A+GZ(dG1r0UuP`=Wkz%G|amVCc%R z8?aR&&wk3e`T_fH+i&|J`M*zg>cE$EOeMk0QZW;(w0`+*wZAH4*>&<7OQjO5E^QTR ze5CNDJI1LJzA-o|Lpc2ikN!<_$cTX8W+2-rC(#?QC{1~77vZ-JI!15lwQiEMthHzR zrCZ}*eWB>zMn-TVba1%6b%A3|ZYz2ImyU&F)ke#CipFS3by7p>D=D?<2dvj3^!j0| zWgq&50Wn<VnssqU;Dia&BGU}0B)+PS5?-l-)jO_Pb0HQ}4nPK>k<hz5_M#rr?Q%kL zwqSJ)=`%sNqQ$;ug5|n#o5c})^}}~3fm8#I3P=Uyd+vG-(?&*XxgftFjFv$?%N53_ z6=66y?t7lTVkXn+Max&dm=fbajx7LLjIy2(Q6&00sHg~um$Ufi3_jL7)$bZ*0A`4< zz9gG$og$;t^@+`%ixw5VjZQAP?@8I8pH@=as-t{%<PpG5?NXpFCa1a~o4h=bTHa{t z=rgBr1}5jX8~mzwo1?f9%;5o*E$?r|o*{vZVs?^tbC5nRO`BO(`>a)~@TN7dmG`wd zoc5uPxz|U;92=w|+J?GXM)<*^w*<=kdu}vlwkz}jzl*=<;4{8GDItUvj&WIe3A2(F zh5ioyZT0<m=g02BryVl!6$Ikq2D&n6i_&RX#lKhhzu)U}Aexh;M+c^$5+#RGgdkrP zsxBT8iZ;T(W^c4}d%<GIlt82sX_$Syj9K*Hux(!GcA06iDvkyf?}U{JVMiY^Cs)Z& zb21u;O#Zy$wKj=nDQ=+ZJu%x%byXlqphz+B0}n#nk`bB{g7`UhkTUn1V5-uytk@KW zbRo%aO|>KnVX93Xq7BAuUz$e~3Z{dN((T;oCP=%5<HM8*b;_5UJ%5f|bIFkZ=d+Fb zal09$a5%Hz$TXgyz>(b#a(9UzrTykCXL)=$UsA{+HxpZl^SV>zp==0^0fF3|EZ3HM zUOmm#Uh|oo*muWh-K&FY5)cJsW=K;^tN^SqajNc+bBW+YFKhT8)$U#&b~AhNpl>vf zYIxs&ApQ66T{js4E*N<=WrBil+T85?8}X;_1f^7ZQuQ%PeC7U5TZ+5)cDmsWXF+pd zjYE&5WLXW2=v??}&&ryti_r(vQp|;K4Ofx1uad3ap;_w9&xYJ*J2}t$j-IEEa7gzi zyJFn$wt~>P!5c2b%2Kt<%SlX4xiU-jqx<)MJ6C_QTl9>hU2A-@TTFGYxM9l~TMznW z5h*}MWE4;*)|PhT`(FA%1OZI?;Gx6%QsypeDmhV0l=z6ypJlJ~-Rzn2=m1h$IF(38 z%ko0{JFTLedL!0W8vuD}$1<|5Z?Cr<S%IE{5}*8AWqYmx!h}(MN)$=vOCrZCM<4Tr z;MGWY??;Zuc7D>{MsXB@0=%doH;0+&W`LJV^C4MYOo1_bPf*ZijsZFjemd>Ks2dr& zJ!R`LvtQgug+2t5UyJ7zQ}W%B<I-F%@;V1in!~Cbg_td!S!3e;SOZFJ<OqxbJ?c^B zXFvO<&qrSNV)~7s3LRd^QoK0=FEa<qI-zoNBfvrS*N=8{%B@H#I(Yc#>wqCqqM>TW zA@trd$|sEvZ<BGAPcAy0Kk-Tw<6Q*9>=5p4YTX03_+(!q`TN&KEyMQF2v^ysk{Dob zYn8%8wml_fF9f;28ouSWiu@cst;s#%UaM`h{y`VdIpG7UlnfB|QSwDn^FCW<&@0?& zE@H;w++!}>?CqY*0~uN@{qV{t%TpqIGo`hp@Q;mpC|OmZ!0)W-usENeR2Kw?T{FKw z)E>5SgEvtaQ@)KY)CPTp$86hx6`es#%&y0Q%HzN9J1<@J653>yQcfbFcXB0++nVQ5 za>5|ZPRq>ZMfNeLUunLv=|hVb9{P_b=MTFaa}OV43#Y&DJqoo?HlGZ*`D)hGzK2=9 zl>PXNIkGwq-tvSlzBnm$3G?2F_xXWlx`qUXkHjD`<(kOoYSWmV_PR%s28PA{De%89 z^fX&~7Uq!tJLTtWVk)U)Q*#Xh80$;z%^ZA?J@T!bTeqCl3YBku5PgmHu0hpRqN|)n z7xg7sqALiylg<Q)TlH}-(rD#l%-;=`p7cSP<x~67?PPIO`niDO=3V0$0yuqQd`OZV z#&1$eWD>ErJJ_~&YmtiXNsWm{m~~C%d>0b!8cAqZ2z=+3F>nH*anM5ONr1Vm6G<>E zdwP)arkqoC2%2GXw}1?`Hr@mHmQ>l`*H$5>S;u784gFD!kKM(z7z}aoBp6Og5_2cB z1fVH{Gtg$Aq`;wab$GHXx+HcnqkyOAPr57A-fWw0Mv*oPz%cea$j)l=1|s0Iz3DBG zQYCf0lOLOVYKFAz&6^D4tfN0-_%5hxW+SB|-u^_H4i3UX*Bj92?%RoYuG{`8q~1T` ztcEE57Db1dH@vCf<b`qZa<Xe_3Elu8KL6XiuV|`p0UxP|*^3$Vq{j*ir}+5EXZG{Q zjbA9T#K0$<(qePvPO$)&^v%MNHb$!H{jx;ko2d?fWj4|};<xkjm)n8cZ^3MBXjW0( zNiEpJ62pqJ!<52}px!c2FXyk~56-nxMjykeuSeP^no6o{%Lf=0(#QGt92_%~;v_4F z6A8jS#{Wh<c|X)thFNZHm<cHM0Y0tDsY?Cd7yJMIT22%OKIKV{IR>gqD2uGTfm<yG zC<b1(Z5k`u@t4C+9f%41G7pX=d_jQlsLZ0e`Q|o*KGsp%b&B;(k(yyctgE#7mHr40 ztIntwv5q4z+eNq)|6xEyIn5u_qpN7sy^dRoTjhK!2h`w^JC-;pZ|f0-zgSmfgvI;& zyoo+~+N8sl@CSBEP#lEhL^BQR0h4tk+xt55ysq}v>M5_5vIof0_>B~ST8`}h=nuWG z%S+$87Wr8US|vtA;30jYgz&qmmhPLXgOj<7<OZfgpXYz1-1)0RUT)VcbS~}m(~h(5 z5k6z@rFnu1IQf7$1-0z@i@pcyX(*!9Y9%_Kh+bR6(gW|?K=tuK_Cu=_nV(j(JIqw! zl_)}gYsiMLxt6dRiyCw!;pc%U^jN&jm2CIg2n@qXpWUY*JS!!+$)UaU*;_)77jY)u ze8lk8ZHhzmBne}Bu=X;?N+zR8PftIv;jdWfRj3(Conm$d-}sfU6zo>38@6XqZQIT` zyyuOsa@7gjuKLxk*fY)OO7G4rY546<|K=A)QP64W<;mEmKfa}Uj~niNJmio16YqQf zipRD*Fuf)vKQ6+y8Suud3U`0A7aU3j#F?C0;r;xAPOg(N=<I`CE4CwNG}~>**dr1r zy-)G30@Kx~`NH8bNA#FaqK=F#ewmP5O(GmHg)mQRM&C}8bVN&DrjDO?8xcT?$5_jo z&_IMxf#6at8LlWDn&1kw%JZ0x=+(HGYPLGVr1!_qSxD*HtoMuav*eX;cEo%aC=d2H z&_j8{`3v(IhZa!RsyV6koeVC?29k<)TZU|!iwo}?Utn59TZNy_e1%3o$1yB>?be1K zkEx_BM`sek!yqcl=G&wT2vK^xb1aWhu9pg!k!6PzVEa46rBx2Wt6F0IGBBVsM~DRh zEDM(+XIMS~&<YKktCk7#yT%^K88c${UWftToP>6u3nVAKI!%~se`0lDxp<80Sq+F6 zz1Lvdg!;Hr2gQXft|$v!rsr26EzB18rK)lexE+B7r+~hV4};{GRRjA}*cV5%;3D{b zNI8X-tyF>vzj%}}@|fkiIt-n>#)LX-A2Z2Z=KrEtpcbix=-BzOe%4Wp>oWFF+&q@M zVHCcxHGI-B)3_vMmA>(kIIpw524g+<k^K=dOU1M^;cNV-T{+H9AjsT2fDfJ5waabe zw9A0JYFk%CH7Ky_?rGw2p#+K}(h4mB_h}Ia=i!N{f^=U;0fEFtB@6oDbPfd+pZ3`T zQsZ^#FmdqK)@4eIIC;%<jE7;ztfcC*?kmhjaxvBHZSv^E?!z_VrR(XBwzQP#V*`ga z;okRt7tuR~WY1RMk+PQu{)8`c(=QKXF9bQyS(d|`{zvo>oeewv^Ar?j*OdR6so7Ei zM^uDVjKyV*u)`;Ced`EIgiVkyr#ab+sdne!w5rQWuz`@?ra!$}`Ce_kC*(&UV=1>F zZK!^J3;YThcj*}6?CKXQT{WPDyd<$I{~Kp)hSM-XJA#$s#!xv=U>XAzexaTrD&4&S z<-JKJ`$<9YWO*ad)*i>VRe|u-F6a$s6`YPuzlA!=anY@9VJqJ1XEw>eym<WVNsr=$ z^ptIkv@%F#G2iou9@k*xjAgZH&_@Ib;g2imYrkn+&QqfA4n(L7RCB(;fn>~%7~zii zW>VOzT6^DXe^Y9|GwP<%rBL1^6(V2XGSIR-Ek=3Ug-RiCz1QmNbXm=|)=6hZH2pm! zfY4?X83OOYc_{x*0$z2b*)l&)K^hy?G`!0C7Z^xE*RMmOO%YtRNu%Z$Fa2j4pb!Xv zW8wy3FQL9sAs%BCn0M3p9CC^<q2zi3o;p*P1YKoggg`vwn*j>)4|Z#kBHHy9Z>&n> zdu2MBNHr94QI28L;Y*S8Y#(vNP&*u(f=QwRqZUk2#6(miR=!0Q?P2k%2Q;DDYz?r! zu`$-lvaJz_UMBSSM9zBL^d}8Ddl?2E&X!$cV-GjiX&_Gvd^CDfud4IEUeA9Ws{&or zN|J9sp}p^mieil`=BHt~sV)B;2sp~rk~|no_1bgDxL&gWM~2~ROQqO{{W|3cI)U5E z`r<Dc1?`oPw*w^~(+o@s$03xBcRXgKxHA(%(DX7*q_HDzID@MhT(3Pz)MhnZCk?n~ zAa3!2wDnVj_{zT|Duvvz-k%glLYO~?`Tb(f)s?FPeD0<G;aDL1+$Q)=_Z9#Wu175* zqTc4^pqC8hWY`}@hW<~!s+EPh*Z$8f1M7*lOVQDfIfCO3bWRd|h~xq1d>}1H*rC5m z>lo*DGfe2^A>o&d)LMzq^C42uJGGy6#qTg@sPLsr8eCnsp%_ZjH`H|ZqOeN9H)2k6 z{p@AK$GOyk#5KMtP2toV7mG|cB}+Gh%Ds!A5OBaKKIK5t)ztj8=YMK((xzq}$0cBe z3S!xDZFa82|0rld`F87PphXuYrqAoi6ykL4u~H;EOUlJT-DWwLyxHrY+$UIiiP|{U zt_nLzqK#^$T>6Egf{ZeA$P7K7rl!w8^<b9|QrVXee^OvC*MCnSF9qTsD!BK}FAm4T z2BHWcapXh+iWvCG!A0P6<^Yw@*7sY}Za@|0^J&$a)q?_K232Kn1N!m+DF&!8G$`g$ zd*{&#;K3*SFlkl5`D#|XIV$?j1PXw>(SMb?kb6~gZ^uIotonu3HjRPAh!XH6Vv(hx z)};VxlZmH48b(bvx9{ue720JUS)A+_-rkCl#4AafiK!bY#tk~qtCg5ihHA@9-QEJ_ zfjOAeV(fwx0vh>}ve7m)VxwWkD0zxNay9r`Rj?*SA(~q$Lr*X}%573R0pUf`f;Wd& zrL4Sp4QL8=TPvSd+8KU{F^g1OJV{d~d<nrLc}F;}rw^Z8BP30H`Ck@*##<&9VfBE1 zbb#5-N9N@{qq6*MRADSm?(g|Vx$~FE`?V;w<sd5**$pgSX4>DSo%0OJ7jLrIulEZ4 z4DcJw!}f~&*^r(EiTTC5d8U70Fw$}FISjCFv(LThXF-j|fh`Q)43@<p8lJ`7*_W9o zcn>{iWa`8eL`Vj1!OKzDil|(WesipC)ztSLE(q=v0=oMpb5G*M=!QS-=IEZo$_}oS zi@8)|vyxs{Kf|20#fs1WxSn&ZgW_YMaF_ew?SHD8zX<E((gaERSfF^-3j&)VmwIRS zo!NVvSEhDS4)qL9T|7@dI1w}+gD~m8c2^sg(dH3UGnP^0Mg-s<r29cwR4=2*95mJL zPBO|FYl5kCh#jf?mH>8T_2B74WnbLhq5(2ch0ZTZ30Ma0!0)x`YcRNCxQg{17GHL} z8hZAiuJ|>!H6L7srk`Hz&L4=klaaDic`meo+ik2?`u_YpVtAcRDj)yX+Rps3S3UIM zN}PU1AT+nEGBh^EbEr1VU!8rVKwwRZ2iQuap=4Xf1ANNbw96kj3uWaNC+qS7Hzdhb zC$lqj(mk=q8zTz#pb3}C9JQ;TM5rWUe?NG?+8S&Vn-4cr-p_t4$z>|)Ds!e$##JX` zWYGU%hCUXw#^ENFF8zzJY>>&Aq{}OqJ-Jc2h$~1RFA1X<DFx*=n!JFoQcA+E2<4V1 zOBT>>bOF!CHK37VU#>{l#U9A8hi=zZx|GQR_{mh%Ol5vY1Ckg*$_JsU12-ypO)7aQ zgefP7r|zHhPw<C|1X~t2km-iS-Oo(lpFC5?v}hUjY7kTkF<t9e)+y?&*e)1KbM2)e zTc64q8hovb1j@}kJ$iSrD;2eg=yWEl&sA!QqLzr|NBV+<ZTw^8yjRcwC9-Vy&5XfF zXiSh?qzoO~kWDXVV1`b;>xt&G2*Nh1uJPr$23V<W5;DKv?`a|%=je%mZ$oAGqX}ay z^Eg4f!g)*NKv3ENoqG$xO^JZJ4E^@E2Z`lZAj4}Dk3C*yVa?#vuKns<^G8F3dt#Ro zV_WZu$0#6J3cbBOGV}7wc*w%#$Jy%#<aUOxZ<7C?pv!}71zRwVsxs;r5lGIzLd_sd ze5)%oGr6R6hp<;`iGUs?6m>NJCde#K_c+<N`Rbh;;P9b-13xXG<R>A{n%FT8oT)V` z$&1%FMWZntgpTAJ^eSv9WCxV+NYKlmzPAUPCJj)<w{)?18>%z_rN5R(jGNTZ2{k{$ zX5Tu-2nnO(Rj}?Ef8-nf&ft|M3H)1jLK(myqIxf95-vlh$@f3!)Q@J&?L%j8>})fT zk&6v-2xKx7ot?yxucP~E<7}u=9AcLSRiLXyf1jNXp*Xz8k=y&YkGfg{#y$*r{>P`E z{s9a=44J>U_3pXz-uWjn05;tyHNBmsPH8FOSWAs={)8n?4H#`}m!Hv;OBnW%w{^k( zlh{&tb@yS~Q$fI>m@Ye#56Ly-_?>(11cg-eWm2EmaQl>aG<ju9&-3pKUUQ0bS-J+N zq--%<tn95Zy@$i?<{W0tt%oW1T|bf-z7AJ`qlpEEbUcy^K28?C+H5gsT<26_7ZZ^o z?y9JZ&-<1gOOhi#{!~>ms7{As<};(AO+vYU_j`Zb8Ru4(ni7!pjvjIVWtDg{K<Xhq z`Sd<2ckkkAjXXu^)@O2h@lRI1+N1tpaw-F1{WxHqRcNC8_n&qnMBX%JPDe23;b&e` z$Bg5u?gqT0C;p<CJ)U5HBgyV=gn={XFDOck{S#^%>%c-dy(kLx2$P)Mm^1|fj@dr= z*ct`CnS4>6XgE=n6~?mLC{USE8Y4h5<HD2EgraB6MBv$y@~=6EP7*H;KukRgMWUd! zLY(gopqH%W!rJQvkAW?xS~O_YB(n%)VTRyyMhMi@aImO!$UCMOdEnP#P~`!2an|V% zIVc<XJH)f#kaUHlG7#6ofzc#p6)Qob<?pz69ILjoN|)UGp+g{&xuqZP(t52Vj_*0F zv~C&q?HavH+Hi}DCh^}Iefzo)3A_!HRHq`FWB;z&!`a4;;&leWSP}PZG#;@1^h#(Z zAuq$3T160jCV(A4-c%uFnfSyd%>_PbfhBgpk&*!Pv=3G&!2wT>Kstp^+TELN?Go}H zt;@T?<!d)n%=1lKLalV*KNhZGY#&gl4?3wPm=M+Aar3Sl57@d{*cFGe&$XByusp2m z1HI3}6+d>q3nu$xWQL5nu<I5$29>&m1wMP|-)AM5k0o^$n~W3G8QirmON7xAG}=8y zVn2=MwkQ^5yPQ;|1}v9M``vW8cOC0rJUU?;F!|qF`s2Ei*7)CzGcsx^3aeG4mJ_8H zD3a$sE%rdK(x=6JrJsjvKX%Qj^??4w9uc(o4V4Z5yW^z?x`^K_kE}Jki{vv~nDsTZ z^Jf11;D`nIt4fH&t3DL_B^vgL-ww?IXLcDPEkFC@F0C)PL1(}9h+=&^wu9cwpE{H3 z4Uaoyyu#A=PL~YxM1<P;HO)t1SLKb^Vca787=6Q~<w^vOaUoy^kjz6-a|(JOUTY}U z`%BVz?n+g4rBG^Qaz3chvof$)`vgd?ZcOB|h~hRua$w2TE9JOU46``lSR*()3#AMw z<-V0ijGQo%%I<gKqFo94z5+IUw-6|}K*ehsF(q^|F;BkiiQAm6*tlHB<yIRA(YT40 zG)c^u9<dH)<GeH32GRBbqy~V2)a&c1{j89+kkYZ<#8rGrqaYE4DphdXf=Mm6#+We% zFV~SYNvIu=B)$5xbo_XHKr5!Hu8=2+<c`LHZED}AraCW17s3;|DoL_DO~<#0Z!gO% z#U#JC^|KOyGLAkq542K@pDLSzKD+}#+iFRMieQPZHlH#{(IX!m2_3%1D1a3BWgbQ6 zAPOz-!{(-uBt74Cd{b2W0QvNJp`S3IjP-E*!?;L2eek5I_|p!T^S6y-->7Bw`tLt} zxw>>N^B;|Fo${*uYrFlIm(a?E=^*&ASvNYD<>*_RJtj{p%ZmfnR;Yyfd2(7o4Rr&P zxoEFgj~`_1ZHoz-m|g66(d_0)Jb;A%TOOHmCi8njFL2_7(cj5ivffxqKyComuJXQU z{n4ec@)6Pf$1(7|;*Gq12~t+Zb_*kFH=hbbHA~>@^uT;r_N#-l(^`rX34uLcv{tRh zhwvO5N&qt89S^U%lR0x27M^5u>2Zt-DmKc<5V65XFU}fHJ_;ZH_V(Z1(`#s@+Sf@y zVsE~Uf#Yi|b9Vcg_tuli7+H2rke^xZ2uT&l!{D{`Qc@~4fV5Bgp?}5qZUYYp<?C}{ zMx}rJ0}~=seYw|<Paa17JDcG1I@OBN?2o3k{~jyd@d1nJgXHbjC|XdJypDCZybgXW zjndVQ-TC}kjsgA>iH`@+kn9x~J1g*&y8ca<RV-;lKPViOP(D5&o(AfHMlYwA-1UMc zT~ttYu4sBEpzy_qc`dQex@U+&_aXv{L6u3l&bn@&)=Pd1={R=hSQ<*fa?TRv$FAs0 z$Jv=vOG8+jNd*1cHs@~}WG&Uc-(v|qN&KEr-_qNQ!P#FoF~1#_Fs>-bLx}(T;I`?! z@u~VqcbFw*&>Q^mAwBg8tk7oV3mhsTnHQPyV7}s0j|r!pkoIkwh9ib!E=oo8T{;BH zB+Pvp<2^1iD$Nf@w_u~b0f;250dqluNk(es`5URTdj`(p_^H+n-`T^|@njm5z#?X9 z_mxARG1*pPrm5z)*}KY>;i!gw+HjL~6<ZYZf=|@6sa(aqv4-ewQ@zALxwA2^@e&$! zQY~&8{UYeZay3{*O-}A$<6Q#SB^M49YH8Cju147YL_4o>d8}qTtnYnMC8ov6!s5cu z!B-4)OtK014pJbbERP0sjM?e-mDLTOiT*N-^lY{e&zR<=Ge2d4D9OC=*))pcEtRtG zk;2ic{(%>^dImD<iwRrD2Kss?R9W?&H7l0)qwLu@Z<Qj;Uc0MF0qEk1tU+RkJ9^($ z=S-d$<01-Uk*BNj=>2kMfF=vu^+3+#oiqldBrGKfl8V9cVW1v?BsA;#FPK{CCm;8l zN^89?OVo(RR~s+ES(nsV4>TIQmcgw^9vPqmEQ9j+kltTH|9C{-`4bRtJ#F5F{ZlCz z9)Oz*e@m5_H0{vGusPmE>zjg_23f2&Z8`l%6e72hEhf)(1?i{MY!Q(d{?!}V`h0z_ z_EPrx^ln}LQiccrq7B|I8z?(|wS5Vf>ESiTE`Jdg2s{K;IxFSoF|8|qR>8YoQ(gYx z|JMhc_x>2tocO<+&etP=k!t28ZO-fOhYY%d?~Ey$YO=0BOKpd$l#>m8+Hc?R%2kp- zVVq|m<v?ac_Zjx+q7Hs=39J5MV3q?5%hih9)-gqiL-1styZUeC@uT>RvBEU%(So>) zBq3az(p_5`Pk<7m@TzbNUJ7XxgbhkBp;8Jmxik&ujkYo8lkW(R{B9$!-Mk<)<!qfy zVN9(`r1P1a1E9E9S`zLw^`h&?Cj|Y@fJ9(0{uP=-e5HKRwC&fN4jwqRQ2l^pz&%PP zU|rf$T#_-8^>FZMMFCW@0Q(qIf>8Jgo^6h>Sxl2CGU<>y2Y`l<i6cTu;B4t!`MyFu zSuFr(AM)cg!2*ukkQiRw;W53BBr_nlRssRv5-UhgfDDZSS2Zb)e+4V4o?$Mb!)+wO zHH?vY_jdzbL>UDa#Z)Nz?2lvHNe9CHawvN8=;nYig6ik4-XG&dFt|qUcx#l~8VqY_ zJ=HPj)|l}8@YVo-oYOTxzd*2FP0rXLM8CpmUz~o!6B4ub%fa!^X~nk`i}5WMGe>zS z-yv3JEr=&)7QMnf(EuYrgJ`JK{$$~q^`vLz3syA+n{k{^AInW9^k2LA|Fd4ngI4}! z^<Z57BC$E4F8%j0kc1=2QdO2rS$FT0?xRWl`Vt}@P;oMt&ti}<kSudHES>2Y_1E+0 z(RrHNLUzl=Ti4mEicE0I`{T>US1ZaSI_tI}xnOQg*UB4sUyK#xWhQfw>d}feRS;`+ z%PBv#l9_n#$2XbsPnWgy{=c&sVc-3##pj7NF2wo*$N54t#dQldY1KV6pQ7c+B_PrG z4o*cLtnPIrQ+8iNtwLt0t&Tu`hM+sx&|CuC-15J#5$h_T790oJ_&7V+;-B-gG`gWv zs$>B{9xhXUFHc)>t}cx_F7b!b4IZ7b5E6@?=k*>ElJVC&1&=Z_3{5qt%B*2-)q;^P zP#zQ(8{}kS8g~_krV!Kx{&TQ0&rWwBy+jY&&B}_Kf{Lie(&Uai>OV3sr^6h_gx{^; z$yojr_`%AQ6x+8)JORqye3f=3Z&|Zp-7aS1O3C9BP*)44&)J(0$brXl8LMgpIoq#7 zRTaA8sZTxpf3q%Zwf$ll6h~dMc6WJ`gPolILNo=RR;QuFal=Sym~yn+A8XT14NeEx znHsVrjt`)BLRHatUk)eL&H<obgOgYFMa=t|DO-D|-}tf;JM|utK5P34)u%cqVN_+4 z0IDfAHq=A+KQd1(ufJ|rjHv<b41IcP@1Dgj_#BM+sJz;3NbS}jT)EB=6TR$a@%AW+ zWx7vqa64o^3<dc{o~ifx&E)m@g(#B)P#u;^1A{>&A!STnKi;>Cg8}~i5h9UQlO%)* z*3o_qUMxw|n^-or?28c*BaFTo6eb-g4cO>kfM|MMw1*c5rf4^d(1ps5K`-8f-yxWX zT-)B}4p!S9Bq+RCC`97qd8LZ!y4k+)35GG+{Q3PqOur+dxCCy+T61OqT1GlWl3I<* zkRj}Ewvj@!u0zC2P_qaJ+_<bN!`Ph*5h}Ij4{GyNiK&bBh3j&wrhW&|EB%dm(7$Qz z$*BJPYl>g=ctM7k&&8~-4RcB9UU7{bW|SH61?Oi~Vc`%o{KS=<0|UlTY)EY%3FG#= z^Q$~DF^v`?O&pU?47k%ioBbzjbU)tnXFM%uY03wkb<4X;Gjn&*GW-t4({(7%ep7Mh zahC6Y#p92q?Lm4bY`^6;mCcVQG68A;-#<Ryw)Bv!lEQC4{<!#X(4`Bgo=}6<_(I3X z83u~1!M=aJ6@^aIZ#{<Y&6=(}9u<ZKpva}UrBtO%NGDefG|OZQ$d!9{fBtw){%jAh z%L%HGn`Uk(ZI3?5{A*#BHs&hg&ty;%bbHnY%c&6Fj{E%cu=L+MgH8&hd7M<9^eyyC zWS8`2l7Tz0IF>K&Z2Z^A@_@b$L2(I(y>4h2Sg~*Md%>*;SRuXHM1|6xnC}jMe_4OU ziKz(VJy%q|>!mpF$MO?xG-*ou5WWEvG_~TLAq?d#w0=@K72^?(n1E>4UZ{j!T-uSI z#F`Ph%LVB6qxUzAsZ+b~wQF*_iwGhv#jv10+6H|sJK%euL%SSbbod>5Kke-_%5c1x zz|kIoUArkjNVuprK>=Rc`VcNNT=?Ap`#~rG{m4Y^T1I}Egt)dhPB5(elTNqno7MWs zv`B6SxbO<F42FyHQhq=T2Z19&wNqKcN>9e`aLL6+ztD4~)PqkM2Ecd8>a{Q)?!yX< zdF7Zqvy9{19F{56-dg0@rDp5$ZsbECBB0x1h11n#CK;>3*tP`C%0`axJ2I^#m)!!7 z%yJ7BLap!{Bf@GsNTvk^+A<jd=eHK?=8y5Hc?{|Oav0rujdf8KRZ4;#1Y>{_lq#<g zNpQYEi2j|bo!Y*-ME4tE)kM{bd@lqdR|#@;imY9q#`UJ!ErxKxx!>tt(*PWY*VncT zssC^NO^r++Sc+UoR!$`uZIRD}I~xhFnyfYa6{tz8=l-RPw?8-Eo<pJ}+iKmYkr|!e z$6sKMWmmN{uKAT-0VhGcQ7RYJb|mwM3am3B_2iM!y<;kQFNoe=d$zgSTaD#V6i`bh zotWbaz$G~+#LBDMj(;<RgpljHuO8>7^_@qBE`E=^Hzaxk(jr`4{VKr|_VO*#c6p$o z)m8lx?e)A47cX@39RA~6$x6inq|NWfu&)u#y*?l@liGf|8QTP|DoFDitZspLo4^_E zn?P&tf7McZy1@0<!UHp>S89V6iFQyF7(QK6IzI9J@qy1IiCLSt>kta<SLI%87QP{u zc4XYk4L38~nLwt}Lr^bqoVFVq%w90T{b>$^SuDjGz6WVUCcG~1eYP*|s-PL*;lxsi zXjRk+Fmv;`$etFQ*?s-(y~O^zJ_)`_`*mm>)0Dc=;%xniolEXzEy-{Rm2P-Z876%4 zuPGUzT=4_e4E1_a9>de}Xf{8q{rHus_b2C=9Dehx<yl96kZ_coI;hEKl=bV~ueMbA z@#`Rnp(iVR088%Ybq<N#z1pt5fY&1ehuT4nAx2btjlTEDDgh`faWgx1<NDpRhnW#e z5)BLEOz*8GPSQ3d;G0(mhE6TZjUx)SS&?XBlck|)<4<JaS3cd}BLlJ}njiS|w-9D| zrTS;V>9vY{O}kKpG1t&W{6R?$H6S=ynv8vIFZ2^XWtD;DZ#(=e=&x2xgs7A9Oqzm0 z`E0zBvHLL(kW!OnOhu!gJAGAEIH5H!V-XrLASMIg1&Q(&=Zm%u#Jj_NOR|H%GN?UA zNiu{o=GNEbmAUJVjnBWJ$g7So6y-xeCnr)|5lW#{+=YVi4xYgOo&+y{8G>05*kHQ* zEOUoAb|2$H%=jSDtD|Z#z$jd6!clL{uoffZ!WJRts5Oj1?hz&I)G60VUoZd0Q3I{H zEu5Pu-}e#iO!LK!O8N*ppaBlOQ%(%e6XOes!ui+8Xm$(na0k;IxZ(&to_24)W)q&n zX_lmbJGJ@5(K)lVrB`cGPxPJT+35HnEIPpKtm?h*;YX^(!n@D8Uy7@43^L$seQ7tX zR>NH<U|%Oxg4E=)X|Ym#`MU3VUY-*+=s@lFzDSji|AZ7)Yrqwqdn9KOF`}EtB;hyA z&}AQ%t*6gsc4Nc0SuN9)z`o{Wq3BOVRcd!jvc0xiBqI94@f`r$8}RgoFJR7Bd%<x* zIVBqQBWcKB{fOmA_z3Cf4X8w5$THlkD7OnLK>NR@#dR@IweHiL=XOS0XvtkZ!%lbW z7t0_=l%vxR5o4vle}rhOMZ2cHcns&xM!YPv=!O`n(XN9TWBLK)YQT*jrW~rtZ{0=R z(OS#gQecy^D{ODv9>`Cy5sXWMSQV5c0^uKy@Hf&PeUBz)Ds;d)oHMp##V4wPcOS&! zk))jigjtAbRnv-^55l?L21uFW?%E;-<d^c0n|mbmMxgWEis|z2FuMsqFIdEvl?!*w zSB3YRy+F`9*nU=LAum><te{FEao>Azi{nz?;hIT2V>qv!onc@*_Ug@6008PELYsm{ zM2v=S^5Ox-r{=xNTz2$7?K!zAhyvOXjLm0g4h8_2APJ+*D!?b5P@sqOx%kK4<UZYN zE-)w8_np1A$YR7ZtdkA~G<ll&Z&WQ9eYnMkOpt26W?#5-t<3A)RChWy#j=`7&>X7$ zAJlH2eLPLxPYt&ba;iD!3;>?=Vp=xhY5PPbOT8I2>na>Z_Z$#s6M<@-<x0}3`Owq5 zTb0kygIT_<>3*FiC?%I+s`N#6t&Z*+ro~02@KxJvM;_xn{X@!V1qv4=P69RyQ`ye_ zea*=~S1M<k&|<}ZcLo1`3*BJ~vxw78Q8rX%xEjdXG3|ZE;QjTO)LVw@<v$5P=9JNP z2)~zjH0_0U{t=LD)97iQBw}%%Sx~U*<yM!hu~fSkAK_Kv$5Fj8w%)KxRLp~l>{X7t zjiAj9(3X|L;C_xFtSHsXxw2y1lYjfIf(LFxI^mnLH)mPxB*clpt!<i^akR_tf_7(E zlRVK|7`8b<O$<RLxPFPIaV2kwMOXaqX_j-~y?;wu{Z3>D1JXz{_fQGkkowPrW>9Z7 zae<5@2BsAM`|o^F#cW;MyT(S>hovg8u7=C;)0K|cLs$-1xJVMvKoG>Aa?%eD>b&4t zq-M{EYo;?F7(dm>@SdIOa9ynaF_IQ!F&kYU2#s52$h`Bt*596axPnokMxYB3@VHE+ zf07J)J6yh}Rg#m(C^TN`(0DJsm~QDC?V9tH1(TF~`-}XYoWs(rny064oMYcb9dD$o z0%}x!)j#n0+5A$|jU;YQqA3xFL;&kA#)_0JXE%nYhI>IoKIHoPQwUXxaLK%-f|OB8 z{lgC!6mPmO+Tgn2U}9pr5H`MNa2|vwe!g%i05%<!q={FcWuCrB^d*HgJoMc2RC{nZ zY&j3w$*>D#sTjPk!HP>GE4lRf^4G_e$fUwVIZ}WVtY>q_<ga})eZK*Z`yDkP+wMJM z=M@U`X~TUFx{kj+7iVNM=@cLRIRJI_v*38zmP)?w*R8j+eO=izbjgD;*O+|djJ4|O zAN;(I<jeweX!uqe-%3#CR#+O9uhVcz7=wFDF};}R#9H1spbUqE1*#rnI<kp+Rv*or z^9)sxUnTkOiaRFgle9)97AizVJO1g8_*<HAIc;T^g;h{(!HdEt?sE6|WUL>$9)Tna ze5WR|jcs4gOjd}$oz!n_;_q1QlllD`k7Mo=c(pvu8@_#&uu@fQ&V9nc11T(n@}spe zYCyZBde(Che7d|9>N>fk(*S<*>>aD}`l4~d!EfY*9r5`F_8fF<*VSU$0I<esCu%r+ zEF7#+4?CvpVG59Q1lbf60KSDwiMq*~Og0`{@cC2xwhKHyTr$n04%%Grs^34mvYoIj znwCq4hfnu-iFv}CeuMb4YaaIX4EouJ6&d&9FGGL~t`vcnj~j{>Zle=+OuazeFH5=Z z4O4me1Hmm=6*>JDy!wOBg}2Yt#e53<2rQ=8ss-8R(z8|1N8z8G94NM`!RN)W`c67m zaL6*$RK?$cxS7F<HhEnal*y|mzhQN>U*mUEfpJfrw!X`C>~@1AawPT2V`2iX{>A;H zUs{5sp{W?}Il0%xde-yu_g<IxH4L|4`xV{M+Yrmz<)3?B@h!6Wjd}I1_;a$<2d|FX z6Q94QabD<K8ydaj7y5`H<mCri04s5)mI(DIuX$JT66aUSwTf*gj!pv{;NuFIZ37oT zDvz8-zhhJ?Z)8hJ_!=>-#j<%JN$jdk=sQ`pdAIJvcyx-Oon%>*Tk{EXCg4i|oDp5{ zsv8KE!?dj25H$-T2nWZkhJ<-|7Sg{NI`fJhHNE1DQDDl4tVhv`4o7Y)r5PjMGj~jA zEh*qQHX03Hy8)3l5LYaP)K0rI7k1rEN&uS-ze6x?N7EYp2p)TkXw$3AmX2JIIgi}M zp0IoA9$9Cq(bN#Uur-kA;nZ9(Eh>N~oq}*IqCAa|Be)F43*Ug>jsWS(xI*)6PQqtj z7kQT!1`&K3JErsiOF+?3K#2?HETae+Z#3Vzp>=eljH&?`Ujh(r{m6Kb-t;O<B4~(q zAPYyck7_Iq1KFt;H?RK+W}(G6lYBC=`wF#dHC5RsNzIv7<)@Orzs8i)*Y!@bz?XMK zJIy!Z7~@oLV?vZSwnffZ8*;oR=l%hO!8w<Dpui?K^rVS|SI5NW*wIB1dw(drIF;_j zTlZ5h^;&S2ca61e#d8thLO96LAB%jxn?Y`cv1WRc#q?LBu{`fD4VzmHNY<*Dbe3ZO z?_=@bQA(Eu*kl^rJS=V+nylXlo5{o$%lu-?tXw_nARFRtwj`1I`>VPsS@W82fF#^e z%uE><-M-ZDKIui=ssQ6b{g=N3U=QN&!f$VOjqTyK7j)42{k|jQoMcaw{TPlF#@p;y zoE0~&j;9qodCeywXQ+e=%`x;sZm!!t`B$-4kZ289l$-R(Q)VvBBG|oshZT65NPwH& zT~_$5ctc{H+kavQzCIcIwcljSJydj$x@?Bx$|~`$DB>+0VU83Ln0!c&(OWk^?sr32 zm1>sMF<DhwH{HMZfql$Y`v6tG53Y9U)l$RN6H>)aSb0=%p;L+^x;;0#JviDNDM0>u zvmc0Xfm52=$YzT(A8ax~PGA$?J)*4$`y=7`SquK4+8jH}bYB!=uSr`2ERxyc@Q2}7 zSlPis%8oEwQmhH=r#q~ofzRCzwj90ZgdVF-*Zg!w-j%{|<3f$%;x+ow0Q1S|@9A_p z_9Zvzafk2W3b*`MbeUh2^tP)Mhgzo=flH}VRwOAw(=HD0@s3|j_A)HT!Ie~?A*T<p zOUF&VM|f%96H~J2JRTUsjO=jxjhRY9-*?Xk!S{R*=6LfDHL44Bu-`D(oRqUD=@0p6 z!>OVXPR_clLbqiYm%iPjzcp_&5m<whtv84Mqy_!fzH}F2U6W9}S^qQm*T?&yUoylb zZV2h<Z6P_S_cukL#NaE6mj-wGcmtI=Cr+$Kp)qihWej{<`j>D*(N$Z5jjf#72~K71 z`@!BjtLhx&Utc=<@AIURIA%BWoCj}I^PAfVbVv%y95rG_+y!^;8OAt|jyugsC~Iln z@ITK$)r`{NvatzLliENfE?L;hC$^@zmxt4X=j;V(0CX35XlA|alqI6zP#YV$t7HK1 zBbzFA^&NSd!q0Dz$NUk>jwoMz4>ngjaQK%gv_K2y)!~ce2rWEbb<m1Bn3t<hI^!af z3G252jjXTq&nvJ7qpDHwPBdJYG)}L9dD1i7kdG%Dif$Jp24lkv+al>*IE7IVsW5>m z0k<@l$`CT<FmLKeat|Te&z;!3vfOPS&v(7pJq=2y+td^{@RfqIyyr<JPUF<u&y(Ke zcV@Z1<9A|#3ec;~^Dv(8u%f)VUwowQr#}R~Y{vOr7oPtUPKNTBU0?YfJCAs`2bicH zc)kdEO*aXx*VX*8tO1Pxt2f7e{Z@L0pDJ$}0KYUlcX2L0Y+v<T=H597jVGCx_x^fV zDMk}1#uj*8TxT_`e`>sEuKzrIeG%s`a0}xY_ay#(Cb_O~^SK(Fp=Sw^7&W17eSGyB z9vJh<+@Rc$)VLpe<Xjc7{^X(&merT2O<w+bV|ZJiI><Q{bkIrL3EhA-F$fF2Qbw`o z|2xd~s?r69da{%Cit5vf>BYb+-y{W&CHZEMmd}NwsU9p3Mc@0p-*Xf$jAO3*fc2Wm z_iw{?q{j|pUU~bz&nh~3CO})-N44ln=(qU6KcMLIfk*4+1WH^7Wez<gI;06LT_KVs zIC95kbF4WrY~v94Z8h5X{)5x!N*q$!dGSK_H^&DK5;=-NU*x3v!sCAfp+}@4muu;@ zo=0~0Z{(;9P*Q9A5N6SWU8BgqX#2lE$=~~2IKPZEQ~F!z-B2o-8SBKw&+LGBg%Ga` z6?_x^>qxEK>$=2vkv;&<H8X)_2T8zDAv?ElsAD`|9Bd0s3XSQ-c!@fhJdo7>q^ZL= zE912k>v%KB-bM%;>H0uDGoLCiY9a8|dtWYCP{IE+0(Vs~+KymKIwyt9HU~Ig$QPqS zRAAqbMI}OU+vp>80vW3r>IdJX960<06{B#MP)?hzgty#^xgVS08}Rm4i@*eWNZoQI zY&vOg!<DIraUOH*&%#Fs?R(8um}Uu@N+XD)T$H-tI$*D0t`(>rxIqwP>^G#<^03S0 z9OO0Kd%G_F2~{;gam81)S5+T>*r<*5blZ*{-V}$Ziu5R;bgY(A^8;*<uxy?E9R|sM zR-SLhAOhEjWQr+<yS6p9MQxDh8?r9+xXh)*HkL#YmN|;8qnz9c?V{%YmyZ1v?B_?F zr@?Jf<xkWz3M4PvFlMOCPyEa|W{k5XQlXEM*}(WTv=hMmTD|VIK%}(QxEEKnSoS%k zq-!R`x}19`r;qz00C|<;u_<xZVm75USwdq2(j97CM%;X^opp2)qH-3fbf7Q!Sz}tr zLSW6OU8a_9UzUHXCWH6(U2id=mnT_nJZs9{m<K`y+cv|EyX&6;6JH;4_yWEC_RF1| z2Sl`BQDpzU6Xx5*Zc>Ec4zq6((gM(OD+Q7dMMzfV-9023-fNb%>kWT>r1O1>K0Rjq z8(Q`LZ=&fZ=U~oM%|EnF3|<<J00uYk2vU&Oxb7yVh}gIS>)mlo@*9~vdGv+Z{NlZd z+y5y4%?#Pr#TZ`ykV1~P&=AW3d9U8H@c$4CJn04kPe?(<%(2I>xU^5i6$*L;LSRak z;CL6w<C%u)-0Yew?}u?i(pNTfuEfmO!xsSqi?<~HK}}{+b21)<61AyDv{btAl&iN+ z{C>De#Xm0V$Pl%hPJ`Ns<MtLJ4}Z9{!s?eUSOIbmts8tnc>-~_rd#z<hxo40=5)M3 zOHhT&`SJN(hSn$knmZStT&4i}z@Q{_5jnq{&(Wxd7`;iK_aY=Vw4~r6jA0{B?*wQ} z6J(2q&sX7%xHMmP>CRp&2o;m3;VRt6pYpTds7=@fV#kXgN(^j>23=PsxX(l{sE^1` z!ffo}$uY_E_2$1li<q*+*dFJ@$NQ7WE<&OYOmXYJn2=)33YrA=#2PEF@2(N##aHhx zkUJ6a0F*byvd92o5eRALMCm&MBOhmR>;v?vV$(s)kYNusv#zQ8t_R)d7**y|OG!!R zNiLvc;2<bqa98N};D?<*^z~Mx%LPEKFOdp4lS}D{7(4iI@UaOLvPxsvzuFdxsuyZ` zaSO=VJnGTW@lN%fSH&svTf`Po*=!WHQ1`GULzL+THTYxN@M5GHMR%iL{U=hglHVH? z3K++_OA3Pjke$Z%ySWo<WG=vE;j9+7zBERlf9_qtfu*AUEO(z$3gy;J^G>MKU!h>k zbU5MyI-m!Qb$)ZQr6D(xHLAE1$~7LJMv<Z<KiKp>`*J96u6|9L0hi@-=l9s$(#7KQ zdgWv7_+O(0JP(~WZO6|Gu!!YyUt$)hr5udWdAH3OR^3k5p$Hi7*u1Gm!n`Fgd6Haz zV!mzLb%YLg8m`prUGP5fSKkX<{t|%KUfsMo+8<tT7RHC|IYM*ghz{yf%(E$kXuGy@ zFwg&#T|J+k{r$9nypXVvD=_>I9G1Lv>mhrqZNDY2nyw-vz&lXo162vkKwpGJjP1!6 z@O#m60e&&Fb7}6z(c1vkTJ1k$fg!RLhj<6jS9DVRMs>qvsG)ZnR4RA1-Ced`c5>UM z^L`%74bkfu?5q1kIlM5jt|x9Br&T#@aq!MlUt*MIg?|mrTv!%ouIY<R7f+=f8~VxD z;E%?io8Q<T^R-WmIv`j|x(UnjxSm}ya?MN`=VbKCaxyMBqR5F6`F0`%t6`CR+*TVZ z@RjtssO%Upo0-*A{96Bs#xC}DwhCnj;s*D<uJ$7^$t2G_i03g>X649Dw4oM<`<5(> zC2H@OP0b!ZO^w2r^alJXLBqQM9WkIhU_e3yDT|QU#UW<`=RU@?WQF;HXc)V8z;fl? zw@cnE@<~q&w=Em<cekUfEj-%D*<Kv4?Iy~^vrv4{=G?yMaIh<KIipHJs!W?myX#!Y z+4b;FEPF3yY_>7#mpU;4isB=po7?^Xu53y-zwDbB$Zg8>HDU9|3Jvmd(hfU!RBw2V za#o-*olNw-U0T%{XXqAOW7n655Rq0jh#BWPj9Ad;gWMfHmh)t^U8ls576zwln5BSo ztyRVp>3%Cq$oLDTQQ2!-NO9xyWnoR{bPZ?qdw`2%`5mwehgVZVSZxtLz8nDfk_dy3 z%+}ohVa#)t(a)=~O+2k(WV+!K0iak3KAg9`*1)ZaMB~pKo%^sSNAo{f&;KSq$z`_7 zQhP-iICZ}(kfmq=R64|!D*GtCi}gc8%P|#NY>b0PVo7aZf=VVJvq0fR*b_d<`<Bnu zatIah*-h*rT(067yy<B(LY6CNLB*1_BLyLbWq#36f==cB>e)DsBRzEe0R1m^;Q@*8 zR#EH*s{4{8!S1joQ(UOVcXt}{F&AG1L1Im_Z5*wpjH=0C!jz|a?fIKkyxWfkW*L;< zkfAC37<iENfgwmCiU`@-`!_3nkF|eIOh|EIT4yJqjCScC;#+9`?Af01xZ5SdKg0KA z$^JdtUK;^ERV-p<oAc=yzC136d!M$^1$j7GmTlwr@ph*Kaw_PCrz$kR;v%==h7aSL zTFFbW)B#_Oas*g%?2$y<(_`y@%rZx(Y_nvy@ZB&61<M6nW_%bXj~ID;Xsx0;4v#(f z0X)%j$vH+-lLVD#H9QGUCAl>p+P0Cbdoh3ehDAbGFeOg%J9e`9%H(D2_r{r@JNUx~ z1oPwDU%Ng@P3RAAeGkNT{>ikslpoJb9Y3BvB(Z;d7?I_&5c#l7omGOaxIUWw;z`D* zD1Sxm95v;hu0cBOb9L)iy;;~lM3yAl)KamUhYMrKc`tOe+hbIIPoXJFg%o9Oq7g@> zg+jyWAICHXD%bmT$9wG5jmk{ifH@V!j(T2H%(lz34(|dNKu3yoyp}GQZw2B8-AHma zq9YcdA$)h#MBycgIF)#alNCh0NVK3L1c?>2MeC8d;@;L_m=JJuvp&CWzu<bm-bEt! zRM=#P`aqYh8bqG;(=v;%6Ag854GH6<T`4{;Cfx!_^2t&6GcdB>Ff$c5M+89DMjX`C ztcKz5SY<DgLN5rmnQc_D5M`1hO#9Ksf=SU)=lG4zIn|pV<cBzYK~(@L<gD_lcN;Ux z&K5$h7DbPlcJ@-|*v2y}pd5Z||NYl80CmY~Q?zJGNExJsAU0!Xh!FklPbsA)JJrC0 zGvQj^a3w@Kj2c#Drd033^Z5<q6>8aUPPIhssqcZ8<zf4Ws_bL@FDz5v+U_eFB!<z^ zQ(_h(v{xv94f>IPr{M!3RaqDqyf6D<gN%fU)5v%~;C%)uU97v8!=8D3h0VPO6?WFJ zZaH`5qHvDpO7M}|bMy?O*nRaKgT>x$6)uP0*#eb7+HuEQw_C4h$RSw#bUU$+xh|;d z^yjMbY?=5)+pmU6ukbRt-5e%=fvGyrfPoSi|XHJV#gz6K0%xd~82_<<kZXI+jH zq_7{c-WK|YT)yqu)OoRm2766^-j$JeL0JrzFEVx?dCO6~Hp=-?sQ(XJZy8l*vuq3F z?(XjH?h;%V5+u0$g5d58cXxLJ1ef4Jg1fuBL(q?X_I>xc_k3s67^|PZy}G)3R`u*z zpRAaK!E=L4h~v4>ChJoIpE_gFkh1>&*&9hv8lku9dmHv>;^da$IaMQShFbG($ifY3 z)`_2wHyomr;EFNsFnTqYSp0YgLyy^oY8BhtVH5$SwRmZI=to?{yI%u@K~TV>t~v%| zOC%fGzD6ip^lJ5#UM)VcMOG+?Oo^QDZm`?mU5S35Bdgoz3&zy>9x2qNAZbY6-64XN zWj6HnDV{Paie02P=>1-L7U(HK4iZ}l=wN{~U<eVYxGkMaGp+~~G3+bN(0VS|Y(skh z*Plmz#Vb<bmLp!GI{M62F~Dku<@Klrd7G6KXTz0>&XcBp<AtqThm8-K`29YtM`}x_ z%uh2~t~B<(ha2KEiY-O}Kbj5ob$4t+iH``to(Z2M66KL_@duMd42QLAr3}!cK5K*^ zt3h8#n#g{6d^WxX)1nCg#99-4IxH@Y$p=TE(~)n}eUmCDnPSe=?H@iZW0R9dqv-ZV zY#70%_!M0$(WtvncQ6J@*yl!GOaB$}uI)L{U+Ii{n1Isex)P&?Nb=2G&n#-OGGwch ziKGJOR65}A5xM?f+wC4`u(Q%<#7c9>wfJIypfYFm9qHMjPET&nf^m2F;uF(Vpv4Z% z3)3<yZh<X1L>Y%2M|IuR$Fg@CDSrdkpUaiivCVDtUb3W=bxq;W-$qc(#i3x}KdsBP zyJno<zu0>fiHb*&Sh0&iX7<Vkm3CAm%Ke_rJ9yxEn{a&`Hewqd8<ppv{Q1M2|IOU( ze8tjW&;7!E;&0}%KgZucA7%?^6K8R^5!0fFGRP8zes>5$AKKY4OzxeaNl$g&=BR-G z$9hns!1(KV(93bEum9-S$O!L8^Eg`eFGZ89BM-OH#=9QqKmjY+TRMKPsgX_pYJMZN zuCozy{-so<7v&O^+85Y^ou~c177bV#j7G(EOU;|ZTxx+aiXBO{7OdYm=xW1rSK;ep z!txxsrn~cfwQD!xTPQBTnBat#ng0@ywGiySMZ66X>q`k=WPJ+2Td$m-pO@V9nsa&O zbbiL|{B14Y^aI<p>YbysbRc_3k_l`9E)I31YqAZX-v|?>phtScXBh=~jw8e+CFR^Y z=;ren{(t7z!%7M9B7eY{S%o8_Ih+_A`VJJtkr$PA7QpcJ`;^pQ8LSLXE4tPYO49qC zw!vlDW^e@TL>U)%@oF7OIKFoKzVGqWhS2Y<KDE1DI;n}Zush^HZmO?pH@9oL3UKkV zF~qLc1buu~pJWuA)mMqIS2eGES}X8aEtA9Rryt{XU4*_2dSI)5Y?HqCl2B3vM&_f! z8w~k=aEs{u)JiCo3P>n+&AaWwjieP>p`E!rFOj?_+&9jP{h<SO0NJ%_>D!sszJO8o z6=V)vmz-W(7X#&+Lo5r*vB=S%XibVF*`2hgsPzSxmqEWOpO{8M&_~_$gZx&BC^bD0 z)ylu@uT>d=UCTyVpBH6kI-oJ_g@;j&H-@M}qucaXFu>{H?~$Q40N^@AQ+5b;q%GkI z-4^Irom%?>M_6_2Xq6cX&9E4Xu~Hc|$Fn7<M-VnpH2LD<`DJVzdo5$|<fG+&+wUH* z#|6~SRdrC}K$|k~qul8oK8_p8mJaH+m<9?HsgT*BLsIY3$+bKGG5KhsW~>0~(|G!J zDT$?Su9|o84H+0AG2z6;J@~i6+9~HmlRD8h#RfLM?R)-E+k7o6O;}q14%w+8j-X2- zmxc0<!S)L0%m?gY*%@R0l@;a0*Y<RW`T=7E4qTS6cdXk4SYHpCV5rO*)3?0L%}<+I z2?KY#fq4Q|?;_a1qY5P`O!NdPCjkf4k6hnZ$5CM3?oW^|#q7!TT&e%*oqH!7<I0`; zGw^ly_L}eQC4UHYc@K>WmNB7h?&0$KE9%e`kyIl_n*x8xLA5{Wozp5SCj^Dc+l6~K z=jSU3$4h@e-ZS!%d+jG>6~JHQoF4b)zn6wj&TESc{%L`R+E7oYFMhLs-t^uoIwfom zDT>ZSn^*vQ_oF$Fo43T9G8j@<5w7*rF)1&q5*LhzWf0kFJgs8W%@h^nr}2xtXl0FL z29akH6S>VkGVKPXhw<rXy5jM2e+Y-Q_*zTL_738mwa{Y5n(%Kg=Z=jW<e?~SZ(b<y zbY_w*1}f}rW8BeV!&Xc1Z?Al0dxRYnxZZBV_<$CTOpoMr2`w^PsQ}quKm|c~CrQP3 zQs70tw3+%rBU#*7WU8MM<iXD7&z?xm9--o^X*g=G6K93&KAu0jo05YHEM3fdHDU6R zD_2c|22ooqvSCY9rR(k;uQ--fg1h4H91Iv_(eh=CE4e>^gpfohQB)73nR~Q~q=-8< z>o6Ue9cdUx!D2t%GQX(c##fOc#q=U0n^oGCy0te7Lj9rVRV?GgL~V0(=CN0|h$H`D ze-k>QJr&>jt9N;>xH}^_%V~3;>aZV?jS!0a;hVO?j(h|}TGfM%ab}g^teOY^D_kqx z``geqZ#&<heWYyyXDURXm2?RGDUYVp27GtQB3jPG-`KzJ6|1>Y_wT?Mjf>aIZJgb$ z`N2yo+owF>Vf)-<viHY35cTnc=!XKx!S{}o@49HjH#Vt_{Tr12|6_joP{}_VDwKga zi;Yni)ojo6d=~bG%*P>?WU7M{YSI=Bh-ZF?`i7bc352#moF`6?I6eVJrg<J0dUw-a z`ZdpX26mG-`|ql!w@7If#W08?^=V#Tz<cO=)vf{e@JdSjoKE6qY%2F@amABn?^`_4 zbZG4wsdNw9t`%BZH#sEiFDvy-ertI{0?Nej`jxQI&i7qp8b(sTTe3n}E2G9Lkkv3V zVkK_Jjg`G3Z&O?|BdJxFg8@;#Zh`|;nEa-`dk_yr9isotE7uq*ml$|r9If&CtdVfm z`s2a#VlRW#nRw{*Z;5D?2R+BqzuKc@c7MEdw_K<?zhMNPSMUklF9X=)tKwHaz8M(k zwlMHVmDim#hoh5K{DjXKMHo`NO3&A^!4|yzd(GIzM|FrQB823zbR2)zyTniA<zHvc zuCvGx(>t^d(=aZWZTz)34Ukfn3Pz!%<BaW+xQZnWZI?{8yLHrP&@hZt0phoObtnWg z=Yr&3)8?0!F2|5Es(hOeP#(k*w&@Y;ci%z5&RUD6hq?m;5ruN>`S1Sh9)j>kTBesU zUlV^=Hsq&ong|tG-EE<=5Acd~FXxqj)6PLav%wuP`Gu-hHMCu9NkrX+eu%8<-l$H? zHc3K8sh)<a#vmqs!LBk6UYU|Ws8!A5OYxO{B^w~sYy?&j<;w(&eELp^^Fbc^MA61B zqnACUAVJ?!f!E|E2|h%5L2KFA)XK-}?)znoPJb%4U;co8#)HB{P4d${K>!AZiuCU- z%;O)AGRZ{lo0<HF#9M0^!tWpX7d5f~=Zy9)_P6yu16~}R=J>6cHZ7*$7b53{ph-}0 z@8HQx%5r#KytkJY`cr}b;`@>u>5!<%#}Y7h=y=l=J@5U%@%=Z}>&F8<#~9EMdrwLo zC-AIH-~(v)_<H+A?C~~y4RC(D=F*$Tn|N`?0`V`K6ortPD{Mx{(fT8&*>vo(KODsW zaOs^3DFu0EacjWevLH14mDyAN<#>A3BvQrxkB}?}yp5#-mdPdqMAFs$^tVMuM|vSL zDm?ME0^-g5LC==f#t98%doqNk^WeS4){Y>q`OM{ITTa-+Id&AOaj*>}Pk|2{Ijh1N zW9KR*<AudbI=r<^D;6d;lXoL3SpY`T6IyYIp2W&&&bZ|PlqEI4CUxrsiZwE>c=5># zCg9FAd4F#Qj;=wf=AyOI=T8$1O|TPoKty%AgaaVH?hFNszzgfRhZv-opNzn}N(9TW zb70xnoG<;Q{;4QN{BT?e5;$7KJWL1q+38OuT1tqJ93vvbly->zH)}7K-jA^N`I}1? zQ*hMc=<X1q6pY<v?n!CMDwn+5okVCL0~l@zpYpMescr#k-cTzlt^RJJR$#s`XQm`c z*0gglKSQ*7f+3ROik9zsWkJm*3~zOl^@h05YF|*I(NW!Btb{&XTAE_%JW->V0P<G& z(vJP>*spt7Ea`x&NdCM8y{%Z+R?Q)Dzq{Mn5W3%sR8D3O&%!LP)3i?sum~0xtm-bn ztp5k&1w{4yj18!R`n#IC)?TXVHu&r#Y|@qVDo81cGSWp$wTY1y*Nd9A_vQ{q<kkqG z66L;y11BH;tR%x5_3b8TVV9E)q4l6l-p5}IEEME3fGXF<R<zH{x8%@6z;~NS*bm=d zk1L~hz~b{bw?NvPU6*~c9CudsEC*q*Bt}r=b-m|cv)cl>8)K#UBt9_#CFHue6!^m_ z<K```r<~Gylx8M7`w*I0)gX91{no0dlN;#t1?)FLb*<7}0GDfLZlYI=tUWn*Mx5eD zYn8Vfy3>D|hXuOF!a#*34kK@lC^>{p#GUBu^AD2*&ArmR!gTtf3e!JyO~-)Ko;;H^ z(d~xZ>sl(HuK40;lNW_mg4Pdv3pd$Gluxl&yNj~P`<<)mG5t$Y5mBFUcW;Qq2jj(; z7+!fZ^1pY8M2W==S~#!yPDUjV6sl^fQ++^G!^-YZyW%%Bj~Dsx%c7xZ$&#t%sLZJp z+1bVKwMKVQUsJ%4nz7V$LCOt9HL{BHKUc%4ntHEKpkOPZ+^LrfY5J2NJkZif=<s}G zwa^_jNO&qKaL<Wr$r!E)&+&_jS_?>)DKK5>8dfQRkpp-Hxbm={Dz-4fhsX5ap_uMq znYIWwyvZYhwFPD*lDbbixQt-2YYkkms6jY(biYgS>Y3C$er~A<_JFQ+1159%q-Vz8 z;K;M38>X=95$odhv(xu{92|JDLNRu#p}w)x%C*#dpvB7eqG9HNzLKlb@|3i-wQ&*G zJQsGivi+dpWKe8}4Oe=^DNNUq*lKULP>wHcjeUWD0;JqX?9s35{Ve#}&;Q-QF@JLr zNcMO}apg%tR+#*ro0c^%iCa}R;r41Uu&#n$ohz{5dqA=EAG_+LAcII=m(`BTN}lUu z&V_>CTidy<BF<}A)p`n8dwPn`C!?l)@Jy|;6@SmUgAR()Q(u~~Gr_q?u|@o$DhoP6 zK0o0pE%tpF6$P)KA}G>}x80l?je8XlS#CM+M{eE&67SfAAkN`A&%&c9x--=|GU_fS zu#ocRNqad;BDSCM7N4RuhaDQF(x;%oiNb#c7b?P8c22V|AsOO73GL=yUGGEs`bv7I zH=*PWuU}6A0^Bu7u8RGNLo3ln<XJO;B;xsby++kxdxdOy&{Y??PS*-Lnnw$$9hciB zcMHrCs2xI;U(uYipj4kb!xl(Iv{Hpcu@oY};bC`BMz8H@r71yv0`WWa%3z07A*j!w z&=!TspqV|8Xkfum$?D|CZ+^2M$6Je^!JrhUqb|`a_(uysoqZB&aV_K?oU|a4O%c`L z^hd2?`i~m1k_UA*tN;rEw%&N(5Z5J*H9RTF+xa8zaMy$Jd4JA9X8S2^qRGFpS#?@Y z9P9-vx6_rAB$JMXu!62>*>RRdJVC5&54h2p9Pit-ZbbdU9dNDx!(P|W<K3jP*2-$p zG&HK;(mMUP3uk|evoY^zNWICJRpK*u;PNw911CyW#7AJnAx<mIg~Itlqga#`P|4=~ zt|p(#WF|<Op@}6foN9b!I<5(&PDi_3jg2@@&;`W+(C^c)d5WXG9yogi_wP%ku151t zRE_a$g9z`W=S)n~02L-Qm+$a9?(?H0j+8bswre(BF2BL!zQ$jXnPfgDM##3JjLG(5 z;o}T)*YwZc$GB$SkbwAO`>Q4ZQ`lx8v`rMx*S`PEOXvlTZ)fgjw{-Uye5F%Ov#S52 zyZ;b`qpei&GkGj}Ub`;y-lzUucT4EqF&3f!A<w;WYw}_Q8l~rPL2G_cfQ-0UF@Dq7 zseR(Gx54V<xi|F>J9(Hm(<2X@MQ?g+51eHX_K9%)m(63LGrK6fHB;t|pU=v<mA9u= zZ;u3ShDTd?veI3FNUlS>iOo)WvSK#-;ky`0mx^KEZ0TN>>NwFusFrggp{a1oY5^Qb zBaw>~%C7N#mE#+JJo)GSk@fE7<)+z%cY27{X}ov<t8y~x`Z@<pcl!2yzQHvv!9X-R z6`FBXY6Wm)wLi^qbPB7Z67~;1%mYPmD(RN`UQ4SOk|sMYIR&~_Q_epuSqyk6!XL(C zalE;yD$|R*B$;d3A&44^gGz&pG!fRvN9U>p_3l8k1r=aW40lU<GG}w$dg$PnViZL& z!^cx&zZ)S7DAbYE5}A_gT)<QLN?xFt3VYCsZg4y+HNeC8l6hOkU?1r<uovThVLXts zAsaysMIFKDoSX~J{%FM|`q^#SZW<nQJ(j*1sSB}jt(PCp`^v7DTu;TeZrRcX=kps5 z1XmKi`hO$ucUPG$@(lT<LUaeR@Qok-x0(7&xPg%&D=ZpCg0a1VbGT@_Q<dKC=C3;o zA$$AmRK#0|d;vm5(4rc917^e05`rSKs=^_;YQd{m@a`G8TMVI90PQ6n%oLfTUnn-% zdwEcY6}?Ut22_wqy9nOl{j<~i43#cPH5&nO4oV{UGo=?u4T)>+{Pj-NP{><}U!TB< zFs_&BH!rPZMQp9|!Z9!HL%bchxNk8Eq`|kNl7$Ln)u#(54!iJ`6gl#{NNT8yTQ5{| z^}gU&_-QA0Fm}O;s^*d@L;DmWr0@cF_>I<9@}^ua`@Rb%&&M~Yw?QsKnusaHH}WLe z$aKU(+<gHCr)wJ(q7Z3uIMIW{OL5X~GCzP>&S8n`HvKKC+j4C4gZl=KXA0pKXQkvf zp!3EJovfGb-pjV_5t7BW$5q=E9Ae>^&US2k+pA%^^TFV49(huuQ}Y0UQ)|}^D;i)V z&{$j%z_;pBz9Xd<b3rh(#Jo6J`%b^;3(i7vv+mwcs<NB=PulR`OOvY!RgBqML~Mx6 zE`!qX!`d>3Jh85m;%2!3R^+&LeAqBkX(o%CJ1IN66QQgTU$iU?e0Jzy-jL>RiW8Mo zbYjY4<IAs5F6mz?Rd<=s(dS<!zH8nNqq~>Kv_svrr<&@1e7j2NybVqCxaxZ+`1SJe zB+n;a^DJ?t#%K(0DD-Ovg$NL2$*>Y@Y1AL??SWogh1;t5ZL-{3?Fuc-!=?x`e5BXu z`!?D^HpVGIblMKjKKYEl&1|E~2|ed_PB7hG#oL<Yzftff4rYa@CHVV#G4jLW3xz<1 z>&!I#-eebBn?eLz$xxm`-?Pi%&1<YEO%fHbfsle;tD$ugrveA$`6Ja;9_?ONiXr#$ z@;(zb>YSvQv_GgVJm=I;6nKkjs=#e8r;#l=aug`L5Bz1o0${3stid_u|4IFgIM=?8 zPI_x)4;epg_R$suunCnZ9E&*9zvkh9->cv%{@|{mdc{~QXC9!(d8PMWigiY>RTa5C zm6GZst_WP;L;)6NmcNlohjSxX>fXzVqfM2vhN`9*A$MQ`BTr;Q`jc4GsFh+qsBp?# zFGL36qbLI4lqodv;l2_}v9(DLM(+ZK(a;$n%ahT{baNpiiO{8(@|v-cU*y)mC7J!8 zFO3!?D#H6xOpo3fl$@?RM2~>yT*Z)DN^cc6Bm%!#aW$JDds<K!!xJ|JTGCPmB%%7c zj0POu@l2MT`9mMkegEuAR3SGAuW|336<LnrNjSIHC|(U5KVh}7NK+ak{X2$;h_xDF zUt>(B<a$|Ky_-y}L{AsTs|3CHxk8VD@`bQe9*raMCaa3zSQJ|4WiL;%j~miG|HHO^ zN^Qv&RyI*6F)-mWB$V$-l{BvwFEsbm6`3~^m4*>BZ&xx7t7DNkvB~d`Qkn3y`G_eX zwy|Q5;E$f+?=k9nU$XR)AYz(<{5e*>wHQS~z%nF0A}p4Zz*Y^~Nd5_L%NU~M3|G}~ zQm60Z#pdUIA}bo-H1aiPpXJC2)}*muDNMeCS&|stBdLeMKqfMuZxCXm9qXvUr5Px& zNf;la$Y9HxYCjo;*FOGexMhFBriVYQ<VR81YLQ){RBq!TD$^h`&K2Hch+y);c~Gmu z6VM2l@38|nJweZ#A;|H?bi%5K>v1F>dUS<}l?%Yv;~Yr%Y{RU%D?%OW8dt=M+f#={ zrkaTA3#Sm2xVBHwIp247b5v#!D8r)7h^cRwfq6B8<eYY;$J$Kb(DHW944d4#<m@96 zO?idIJgp%6H9rQ`kF&4H8adGBJWEaKk6SX4tQAx4EvKw0pHgU9QrBu~tzo(i{SFrD zlleQ+%$x$-c>1LX(R=W18|r@tgnzzrC7}WYEHs-B3~}IkM@$XlDpbHsg`^1Ho(TsK zF2@3RR@h19Ro7=Fe29NZ&AsjG-c=kotK`txXc;9d$O7C!%JOU}U~m=uPus>Q1_@PF z#M@|#^xTIYmw!yBn1PYJecoF!w2#KB{EmYdsbUm>oc)TLFIQ@0smmRJ^WFg3vdZN} zqeAV{5C2}JIbo6Zz=|;hBA3dFMvDnoocK~CR~kJTubrM;&&yUv=Ytf5hKrp4965gY z=v#R@|2m5{;9ird%JQ76C%xxIXOEmS8we-8tT!WpD$cNhM{nox^~<yk8nObywMr(d zl<*d6M1w4oB=VM&64+3b3Uv*^b#xOZ-Eiu@gN43+BV~HKmQ+VrbbG4OldvyL3L4Z? zB#7r5c$%C)yjS?{<OP}{%mz!XrQ&N7%Q=E?eupTmsO<556<58{og1I>eKi_r45P|s zI!gnC0?A=Z<$sp~580Mq`PSH&a0Se|Ay+cTVsH3nsZ|#bfdy6o0#Mxy>1z&DP=q9> zIE_=)|9Vz-=@b{=U~g`gH5Jc|9C4Dt(sZM`Z$d!ClA&li!VE-GPm$>bPY?P+w1X8M zJG`JL3!qS@#>BZXA1B~+OZXYkUF;;emM&K<fvBt{UtB`6yvg}X-Gjbz?v~shu7rht zJl)ekteAl&RL5=}CLWz{BC(8&Pp&s!%mx7*3Yqy?7o^F|@FW7wH7#<D3ZaAb&49a2 z&OuF@i9Ywp>r!R(&rp^h!QJIWnQFV#oJ5!X1*qV;2&5{_K4AY(l>YD0PyoWL#0`M( zEul<)tSVWMLZ!*}98ORuyYxA-6E}^TwU0biziUX1-t@3~WfU-K*I!R>Wl0V$skNS7 z+{p>OWQjY0PHkop1gSBbMl)1Wacf#tyb%D+Vyhd>+qg`LDoRS>6u+ykWGgtqyJWC} zv#rF&b$U0aK`o6UMS6|a3)2Um(2O)dpO8wxrYeTmbM0WRtDA7?6l+t;swB?X`8B<` zyhgPM+y#{1q;D4;K4ltxB{@tJ@Y#m;U8S~DH-*-;Wq>~KRkVOP)NsZGO+1ozW&!Mw z(WRF{l!_i}g(MUy&JB}&i8A17F3m=x{IA@12!8m4FxW6TCn%CJtNVm7scPwT^>n5$ zWD*4ucwI27-0{^A@LFGfRXCkjhH@*HI@MB`OlM}S1zMxWD%D}vq%Xryv^dv5d}<1G z?7ye14Mh_&V5|XcBLJ~mMS5h3*RA0|aB<`WxLW<~<z(0pUbd7PSvX~5`Dz^B^+-Vh z)hH1^)Vdj=?bORKVU)}N4HCFQ@$4N?s7ME7r@>2HzUtjrk)V|)zbOcDl$12fb^ji@ zPyT`lH`WVL$xXyQd)O)#aW<dH3iHQoAA89>;tKM$PtF&fKV6%(cQlwvp9WE{whXFF zYEDN)c9Po|y%Oq1Dtm@xMUIu!-Z^V#m(p;xOD}}I`5P%jx)?VRzmk%{?NjD0nHy=K zw8kY^xKNw5lk10=(C)btO{uh@{EKL%mdZsfs<{ER57lA?mfI4f)$O$D8ty-SHAx8( z%Xcqy5u6no)ssgGP7NIGyQ>~gEcb93+m4+ax*Qyvwh+Zt2!}#jo%v-^f*+-wP~xRK zp>u;p#Skn&_!okR9H)f*>qxKskk{ARhxjC+rd*@xH`(%rVum)2h;>QhK2x<-LHzu8 z>6-P1rMN21%j0aHA5!VP5G%`rpT4*>^~_Wrb!Nt?gmG0HZZ)v;8n1;#iL)1j^P$Fo zyRn2sCxS#rm)!c5n`*9}D3=504cOLj5}NHP;fw)9&@0=OUX|!a*_n%l5~Qinwvu8x zQFDp{`@&)`A*%z^pfw;gL|5Q~@lIs&RWYl(jzZc(sOc8a5fio(j5YPAkljV(a{qV9 zrCQS;U{xGheTwbMk^OO4FE<2=L?m|Lu*!fK06<qPKV_S@fPG+Q=cezEf<{2wc!I~i znKU-X8?!uvQVakF?HLUH4WM;B0_hUisD8ID5t`K4n(mS~u3OJwT!Es2W)`D6p?dui zBKgZbiNLoHkLooyAZV@ZPVu_8zw{v-`KHSfDEL1!(^k0GBe-&<JP@z*?{P)rnS&Ln zduldIOJ_Jm5$0#vU(5z@I&-Ao_3DP=FRiqP(BYY1=5&L?{NvM{@*yl0tJW0Tk<VFS z_;k#-Fhk(R*oK-aN~k?x9OOdyJ@m;atgQpTsdIsx($2ls60Gi9qK5ePC8i*LL|Xu& zkiDQtg<B=hI{|QF8_#-}c8+Q`-WD-Vlpz|)+<dz;FcMUZZ9=_q4SeLA4oII;E#bBf zl-kU7QbDSPVM22ie1Sg!E&WP}ubk066nhsP2<l6VjfHsr=0P?Rf?X+tY$9I=VEgZz zR{U;}{@Q$%u;{bq84#{Ao9{+(+AgeP&)T(v7Sah4mDgD1Hse6eCjZpL1C7JzW)86H ztH`K{L&Q*!q`M=t81WimMj`>~j36xFRmhZ-EK9psfa8PHj3ws+i^-C81hXdkqjvUv z>VrEI)xZA!o*Q72Bo>*JvWgl@ZM$PtL2EmcW^?mzwOYsCK_^)Wav5N6Jif*Fy^3lr z>hAx)EQkq(7)i&M;;S&XMV&^YZyy-?61|ep;5H1SBmKSf?NH8dBtY20dq!QWA0vmF zjLVk4Mc3!OK%+GPR&Ed$xR34(M%TJJsX8#YG0XFB<JpA<YC+G<)vMV-vu2*|1M-u> zP8urDD01`ndv*>?!>ZIBQz$e*zXVM$zs9Yn|Lhd83x-?$j^BJUll2L2s+c(~PuWC? z0PNSjqSeI9G|&?+$h61PN-n_4yLU8c$jM~h%^7UsJKj>}c+yK7>OV=%XDEAO-1x6! zru!96mb6Agl?zWextK&<y~d36C*YidCq9?N$0JXKH1XWvLc>dJq1PLWo5PG=Rzm|v zmf=xZ<BPJ!j?g-@DxX-R`YH5y??cqV4;+J&?<+z?S)EbZbW#0XH@MpL{gxVpPV7PY z1CmKd*86GZ@o@`B>C&+cw+E;P^YxCCJFioQS(tN6^O>I0JVh5@=sFg$`041xa4P3N zRRZTAB%`iObq$LLiUyBInvcDm)|n&2?UU1)p}`HM=s9$MSU3nt8K_t98I)Z6oSBDX z@r0duBkC{(nH@Z415mUBFqbEI2FgP8n*#Y#AT2Q3`0I}@qs&U{B;QXek?o(!^h2>I zTQ>su!{}OFE8uX?>`&8W6s9S2bGNlZ`!)0mPiXjQrzeNU_60P}8yo44;A?l#nAwXX zujt$cNMexbuadE%S9(Jhg32fH*A6oiu^$o2DmpOg0#=N}VjK~+PgT>5U`Ut0vjY6s zIctMoW>_=mVzqPQV*(Tzc9vvfT}l7{B`Kc*I1tx-BJayR_do^V%0vQZQjs>_Vk)tH zZ-$KgAA!6DZ)n8x?LBD32bWoUt8s1h0J2LKt7UB}+Py_?lMvfAYw5ELiR!+;@b%Kz z%2xjDV6b-wHhSB9(zZw-{#ifiN*{ZN4x&(YzQM!n#x+}sVYyaj`{nMQ`w@nmeV$M_ z$tNo~{Hl1yRE@FdZ>8~RzY{#wzF0`EiW*}!QJ?j~f~S{=@2QdKy9hUtmysn-Yt>QE z4h`e@K#MG&RZBXCL2Jth-V+skV#OhHyd5oxQ%8_KINo3q84P&LcMSA4U&zvo{LT&Q z_u^ONLn?%^S>9muMgC&3KnWi?!tP4R<ek8zo-F3b2R8*h=Qo+L4j)J`aShG^a)j5$ zU+vu2=h%yCS=WEpkSrTnL%uGM(M3c!azr9sjo6{>W#2H#(&Fg__wqPyX6{_z^upbO zX&_t2@relYc4$NXB$9N#Wt<`jkYR^Nb-N^U*ataJEM>vIDd3$UzJaY<|AXrPd-U7+ z+#)|7+^Gk0I^I_XFw&BGlI#J$@{i(Y&P`rtq(H*TJ$x8$X02C#KTB<)T_f{ig{0pC zpF{LZsRSZybqlNR`SC_^cu^K>2%=Sx3rNbeFhBT+ocj`^7NCG%<n7Hi9bS74D*4NK zAXz;4E`o*66!KP-<j*BpT5ArjdP@=T2I$!|ksl&x49_C~Xyq5+eGy0yXlImP{w^&z z;=iEs&KWt7mj-tAe6^KT8@gHD-TmGzb;eM>oE%-nLf*&Y;PTHk!#lbW|LDIrek2@S z%g^OZUtnvoWS=D_=UcC&P9_mZwvc%w0&3Qexglj(#W|;Nt4>8;CrE2mSXipCaJ+vb zLSBjiV~jXzHqEEmym!M?k^01q%RD@so(N~GYa6SDI#G4$$p93URIRF}tT#lD^C;bU zts+B1eZ2V%+v4zCRZ-G{0zd7f!#uGm(NCE(8JXF|x;3o%7x(?SXa*LwbR*zT7`zRP zE(cp~JG*QfTY#d5vqRqL?bp`DJ+GZ9)mA$4(3%Q_;`joIWHgdcxJt4!38AGS$&qz& zm{X^8_0#SIto5aO7(Slf1_~}Pa9ML^Xb?eKRjSP(j#vXlL9UuUyawI08QxD;D{#0v ztH0U!MlxAT5|UOK3gwN`@<ARY2az_jydpW76sKd%UA@xTHf^ZrW<RdUxLAi8A$0&! z#%nOE+B%~rI3WS$ve#;-vD49U_;G!*5`k7u3fG{W5U&VUDsQ8#k-;>a&`pKhodqxa zlNfqv9De<MqVAncxH6e<BUd>oAiB=~EX}{~f&TUT)Q5gGvPrq-GEYOXrzYuaql5LB z#4c}EjMdm?`v)2EO+i*7{yUkXeOCp(1hT-X1G?B=<2j*NrT{GB)>Md>?oN!RSJ)}D zeMc`1V)kdpR3^!t8t4ydySxs2@Gnb&G8>ujzB>pZ_>5PQ{YAgw<?-kP=$g!-eT~1? z(fY>$7tb@IaK)txnOoS>VA~i2LNukJL*p0p#h4I6gP)X-dV`s;;d!s=U`>A-qoAMv z5&TW4REAboT+)+*D^uw==o;iGl+YqnN4S)qP#0nu*X%+3#4Lt3JB>s91^*@SP!Dbi z6SWQ;&t%Yrd5j7h#*D=<2+QmFFP|#>pYMEb_auD;c}o;WM<H^2nhJ<g+aiWvVVSTc zUn8AjDtX%4MlgPcJaLt18g+vGaauDXkQ|MSh_U41U!<|4!DXuD?EV$RcRzuk5(~yT z8fNjH=5OA&czDpz&FOf{YoX`xc^z8S5yDl1;|mgbb&+Dj?16t=`Cl@w?g6;72zlyS z<wY9CjZC7nGC?9;-*$uYi|qtGZ?qt!+Um-C)$uxq)@{6Ao(hq>9PgXCJeRixX#^i^ z^&(koN0zb*G+J7{P5y<z?+`PWXl38xo+T0eJSva!`7g?Yv4l)${))FxbY}4NNI*7J zY+PLtJ@mhk!t=38AgKqg=B`ETGB_1NF$cr<y~FufU|$GXufbcwdc$D~d!odiSMc9| zILDM{3!q6SV@*eo8tu3Vdq#^`oFQy0vUK)8^)P%U_5X%2uMiYH-TMFKX;1qyTUN-G zP;t@cBIq6dz*Z@o1bd#LL#pK5mFoy8Y8k3uY7lFZ*@=i)Z-k8}_HqDcft^rl{(HJ| z<_H97asP0aY2-_}IpO5BumyOnCMNaN>>9kuqL`or+oXw(5Hn3Nz6KXF(nL@5-y|I# zlKK@B%Jgnx0g){hSs>Yvs-#Fb=WdLG$OV_Fcq?%62E^;Kg86UZg>CwsoXcL8CSI2A zM2kF3x47<&y?9gKDcJzSQdN=71gT8L(W&(Ddpua&2(l_!iOu*}oWEPOdg~en!%-47 zS^F>@0L(gd8$)PiFy*)~W3)yN!bakukz^%rUlKFJc+hQJ)Cy_7R)e9R32G#On<N$} z8FiPKL#xB1mz4*dA>x{i+ejCDXe1wLWKVgj<;5M+RV3xKN%pGcOZ{l&g?H-^vAH>> z;=x_4hsI+}yh?R>(Dzm;7zm!*S5suCmnPo9Uz@hfcB4WcVE%}oUd&cthOU9=ODqGU z&ofb>w_8(3_13BC+4^-y$7pvd@+MU3Q+wH}qCc8fPmF?-xkWx0p_%ilWBLv{Ub_7N z?XHpklcW7J8gfDO@%AC9Niy;xAm|R@`juNFZ-|{;cf>bcZJ3e#6=pthb5114$p{D^ z=087~G4r`d5OnNw!D%!7-D=udLr=L@(he6igMuE4v#;h-6`Ffvm}n4zm#RPn^hJPO zPn7;XfVy{TE{h0+Q$yY(H%4oEnjmNXHXX?tI>)8AK>^iE67k@dJ4v()xMLi>l^ZJ( z1OxyN?hZ~fZjqk~XsjR6RlgR%wie5BxWwdB21iq6bN2x&;ekyhuAUw3gY<|y7<f_x z?0W=o3O+>5cm&e!;kWja9`(Z-Js3^9rA2wxJ($ivN6CUciGD(?rfktQwZtR-2q#Qo zQy{uK2r&rsj7f439SCHU@I?m7dq~x(*gvD~K$+^Eupc6oZAqE2)ctk?72yoV>{s#} z^HOAOi{0CpH5i*Mz;Vt^NSr@^iSNi9AFbZ(>;=n^2n`XoBX=SBrtdVSO(UE!E8>e3 zc0HbT5~DMd*x8W)75Zj1c{tf-1p4>J`S0-jUm?0gdJ6=USVg(~<TOg@_8rbw%hv_k zu*R2$6>0w_{Uv55-h$?QzndEWXc(Oc*#>)q#s4e2?0rt@_9}Tb7!l(H5uUqXsa=uS zMhJDnlV~-ZuO!a9u4f2OvNCwsRi1egl3$USS1*{aQycl%yBTi`_XTcD*!A?u=_9s1 z%|<@!yBW<EVcW;AmcS&{;VHxf6;jfPm)r@G_2*OpxEeSh^g|C9yhEq+=t`yE-G<R+ zx0;f*%<7%n*xR$l`u<!0n&5wN`99fvdF&7-3a#UxRpu>iTyW~NA*kE8sGo1y<gav? zjm$Spo)OsQOiJDuc5ysdsUuV)?0aNdljNgU0j-p-E<Yy8ckiNNjvYqm-09$C`Mmkr z#HBdmQ-qqXDXp8DRq!u$tS)szF6RwY-i~7F_t}49sabJ-c^m6<|HTF`?-)1uqzgsK z=xaglhqfB}Aa+hXXAlhi%u<5aSI^CGnOi6>i(&Yk5OBew1PapGBA2KTUarN9>Vws) z^p*~^Oy=UH%V)&!pg#Uv%Y~UxMdt^in!mYnipMyvDk+@06&WaCC|KVBNkG2R4a-vv zQJB^n#yoeLlao5?u2z~zcZlDuTL|G7EFEyr#osj|p7|!4^XmcAao%0?idEwwilNyW z*W$i7k|`t8xNssYv`R^%6}y#^{VsyTh9B{is)}}bs6fz}UTAExQfqSYPjycPJ5*6t zl#&q9oqfrcP(_<Ukizt=)6AX>q7fT}f(B_Ye8xY^qoORkPNy7<lN&w*n`i^yU)Gb` zBQsi?1?^9JZe)@p_X=5JF**3A7azOts`>O0-X%OV?YKjmx3sYD$G^_nWPqCB2=rKQ z-}~DW{u`+N`^&EoZb{mwE=X5$V6TI|2D1^wI`2Zb1%)VHCSEWti4-Jf_N7%klxmxB z<*MclQ{T>()BXaDZ%vE=0Z7|U_DC{waDf7ZdIC?=7p7uRv%SQ5EQO*_|Ba{T1eOmz zJwy;RsrU1NOeqG|g3H_bw^^L#S~_uTHSs`2z-{?beyT5G2tm?#48FcUWErfQB*$jU zJlW8u!AxzO^jo_08#Ce^Nz@qtz08Nmb|leM{qQP4(`L=#N3Zem`pVdFiHfFOKD2bW zs!0YeS|OcSz&Edn<p6MmVRimR_8EuxhPfF^@TfDVMCb@~#zJ`G3cZRLV{jI*xWF8O zOXgPi9b5=>mABjU@S7W`o8&mjI(XhjX`)Q-zSP+CVd6ji_X?voU_NPyDrIPSd#`$m zrKQ;uWXgf|S|C>zAZ)PRJUF3V6Lc9^qd5v8oW?9v;c}LvlAkNI8-|-Xq#%;A%J|*s z4<(MQKu5GpD5K%&#nLW_ymg@8!PdC$t|6O3LaPY8-C6ks>i^kZ|02x(+X5}I15E{m zjI%VPsHNs#+&px}Wje*bZMRdWJx-2mx9-RG3aixQ{24!{-lK0?ct{Iv3!S<vi!b9s zjcP(GAK>ODJRql7bY}s)G`)rQCe3PyDV0GA-c<s9OaMw2sXGLAdduvo%xFX?9<rs+ z!+4eSA#;rwO{Y~o=H9M<-Zm<Ko}qO*Cc&@s+PI*QM6zLcuxe3ojjP^wL%Og%KJuIx z6ED@UmIscwwiIH*76Qk4C6{tJ{Y$U*N{6ld1Rv;oi^#cCP>b&O3N9r7nbc?W_$(oE z!ffWONPpf;^;j6a8sE;y9H)=(<(JkusX%W#c|8O^QQagBD+`Yl%WxcvL*2~a!{_b| z#TS0<+-x~5y6;O30YVo*4^fzhrz+0ZJ1I{~Krt9z02=+SIAld6D@y-X9ef#kEyCE| zPD$cwu)0JJCm8i;Oa!Qr85j5~{DEugQBCY3%uBan8%%saK?j4PT8kM)A4J2Lr!idN zaR;wFiKS``IpQ|z?JCR3L-n_T>$R0ZFDMI`nxUvOtv49)X6B`@^@bI<3_nd)V-%mT zTrVA->xZvZI-9L=8Z|*AvmT5e`J#c_T#n#oqdl~ytLwz|kQqT48cHHD?Vy*zY({-r zDcvhp%&zrj{UQSl2ntt0A4=oI&BB89;=(c`FqsYkLz+9dHnM^lFdXq85gu0SYFW?r z<#!NUmC$|ownw#@{bR>K11nl!0<#cilFlJy@VBCxR0x)gK+{aH)4gBV1dynsSXK=Z z>CP)8N?Q>rI1U^CE(wCn1FhH&{UkLD#<+450}<rvT-R^`5WxNTnf0mrNp1pScbI%h zeawzMEfDLMl;ZAku-+$+R(D)Dcs`j~d%qR6F6$RFyr?*Qbh`0!@IwXKjyg)Do%8(1 zZ~smCwuc(-PX3)Ra<N!-e}L%leflR6Zb$i~?KXs9X!xy1e0&9SUL2wATy@Qjj&x{U zXkGupj6kil>)LiTA~7k25KHp{am1R>cblMI{=M9ZxhltA>GD$G@VAlc2pk_uDLkES zE?zkCN;Jjd2&WB*Am#Nzi{FG5g?a;I#8J08*Rjn3ycW<jpkRKpV$Kjk1pyHc3+|v0 zX#)}_Ra(Yg&21xh)6pz{p8cCqDb~`xIaW^}<TiP{z<RWt;#Sp~8~44%m~#U3E&Y<y z02p(4S?)!a!?nmDsWvgH-t1rocGF>9Mg(xd1Ny@ateNDpwuWKD#4JZA%v?QmBoAcX zs^~U*a?8JCO|YwXt)iZcpHIo-?(8H+Q4g-Vp)qG}Sdyl`jJ&<mz%fW|<<epOMeLb> z#))ehSjHtOrZ%Kdlu+nHi>7wpFhR(L<b_B4o;A}*$nQL&8KqBX*KY2B+`8xREem`w z<7Bm^bj4W(6*?NNVd-dJfs;>g6;U-k<o!=OxLIQczql1fZb<191MfZ2c9dP?+W*Zz z`!^HqDk=i(uZU-T_V@jOEHb17#0z&RYL*b9))K9FnL~@|$#UPRUs*R%sqdscB`=H{ zf+}j>+X_aJZIV9Zw8i0Ur+%Ldbo5d#)a|O`5H@IQ`P(aUQt*7Ph@tSa&0AnkW;?8w z%@@AShWC$(TaD_!3h{|@V{aZ(5iLy>0r?~TW`MFtEZPdRvqdYv@EXWJSdDg@7PN4@ zCO=`ylD9$?AriuOD}*S?`O-l<W7keBn1ypV;>f7;-<g=!0#o4|i5fVXHaj+7c3J0Y ze@GD}l5>4nb?xtlcR2oY(EW0P5_x(&Ld@IDnfMoKp^XkuH((~wYlu|>OZg?Nxa|b3 zo;qYvqhMe7#~Y{OagP?H%6y@;Pr!j$OuJUriARBL#sC>HDxf<3To696>&j$O#1-aj z8rLHCliCc}nK;s3tlASXpw9r*(@o0IYr1lWFV6wNaXY&e$_8|yjlE$|^Og0}q&Q&4 zd!}PhRU%+$otMN1?+tTttlQZ%7r_@3dim^nXcWE1a=gp;lUF8FWEZa#$p&F3!Si5Y zS^b#iX$2(E@!rLkTSXK=1Rku%k7+~iQOI)GFw^1E>x_KK?%!QoDIV1dJCh3>|C1T= zYfD`y5{%i!k=*qDY`5Ynz`6a#CaEYLjl5!0LSVK~6gm!NzI83xMB`lk^<0<|M!uC% z_L~jy68cXp3LUX}%5!zBP03XR${+L)U|<q+^9)8FY^`BYJ^D_u*?$ikUsPXbrlb21 z5D?Znb|P^C%g3_ZP&G*23C=>V!+R3NMstPcGk_5E3IU;>!cE<8s#{>(VQ=s?t#TA9 zY98$F>T{e&(YYs-a|F@SDiS2|^G{06L}aw}L42u=tac@tv>T~HgSPae-Uk3C+=K3K zzqmhcK3+MyW&F}m!N-9+dDm@4b$9oM{^ka1M;T7Jw7XaMlb2!tuw(xXzMm4&@wP>t z5xAWTp-oFp4f+0}8_U}q{RB57HQ;3`R6wqAy(to`z}ARY@Ur<cMB%4c1^1F)w^bF& z^QoSbak5v;2qXG3TI1F|Y$)z0QNFD*{6mdrk+&6dJtNyiV;K=nffIlWLOan*+LF)m zqAgWD706CzSj*>P5%Smdabfbj3eiHdkNCq}Out`osya{wV3RSj;9E|t%)+OLruI{2 zZUr%=oAkg0(-7roX=^>P1#p=k_}35V|3waA#M!oP$#G`w+b5jeCkij8fN~K}3YrRi zf(X-5ark5hto5t46qtKrpM&NHkgTSs#ic5rq0^0Q3)yP{OrJDILrzP6Wld)i7+h(i zU@9}B+DS@7+}=@06(}Laps2;YF{(x?4Ms%LL&Me5OEnRe;wf)v?bebMe*3o`B5^rm z*aBH7k<r%h`jr&Q=Q;-fNaAh<qkv4Nf^9nZlN|ZUBA}*-#!y6G3xLgZ1kUO%6-N2R zi$J8>iL}H5z@aKBd8QnTIw6PFN5tK2EDV*#FXk7Rf&?QeTf>0k|4SLm;{v_zLV1pZ z3T|YS@wEK(^QA#RP_ehD?+uXu^x!yjnVPi+tlm>{F$&xjscAndbU$}#Pw3uc_*ux% zHjNMpvrjj!k}EZTA3&?F(1RBksb5L~C*z#6GAS7>>Wkv7dtey>=mUc2q_UsqN;&4I zT^aI~P2rV><jB`bhl`v?9~~k<fH{Q#eTL1i1z!@7$dv-GpOlx3qZl0~T$P?N2hIXM z(@uH{)fTYW34#@-cd7Udd&kFnHfaQ+19Br?+AW&jQEKnu7;{yGBQiY5>*$D0r-Z0I z{OvUVDXj>d{ukl$m#~8~3xuc;MB$*1jEh>>a6J|91@{FgR1r3hqcT#^Ad6GZd^>27 zJjx_@C*@~S@NGiP*-ikXTq@hdGbbcvukL_9Og5B3Pl^#WyexJBO@qKA{`=2z$2~D= zU_R~iWed{=>%Q!ukI~2Fp=&y3L{~V$YP)(t`UPVfdqPzr6D?I9_edEC5VN8`+!oR7 z!)^LB+h(nb)QCkY|Fck=yXHXo3k@H>Dj2;|gfst~P+k80yju*D;LNM+9e&D()AtiT z(x_Wz2c`2=v#&2vTm9Ti6oY327^Qo^O9r1wOyZW$6%qRA$lYopG~<2(a**+JT5Wc` z=J;sY7h)l#14j!PU3Mi$F^IMay=^H_>-wC$iX<WUjS4*S1K~oW`$GD`3muYANk(lV zxp@yTUbK@J$3y{rKioVR!q{*duMRrP$Czul8CvD(L*qb&(*433fpUw0DqomUXY>xf zn_#6EZ1rH(oWvE=<Y8a=QBMTE2lu-pG~}SZK0#<G>oM+bVh@|T6|x95eK{b}uHW74 zK;u9G%=Z*e<s2H`L5n{sc9O8Ne!P`p$1S54+XT0DgB&K`Bnnry)k`<dU#-qW3{OxA zpjU1YUx`W|OO($icK<`{{@1~JHAs1NIT1$l>dq>clalZES81SPa~$@-1O&-4qOBxS zncYZ%iy2W#V_7|n%y?si+NS}!(XqdY9fV3K(Oq-dXQ@(JqRSV~BN5^EmSnu*;FsP4 zr^#<J2vGn?O>`nySnMi|5xsKS;CL{H(zU9L!GTN0*`6|+L@mfLBa^7s>TbpA26NNQ z3qpalm__G|05jCD3j>6>3}*adwLIzZECq-&I9IHHkngp;O$>}thh<AAS~nY2ncWb$ z@-YC7k)2}0(TbpFL(no4!)Bn`8e~>4Rg1}|{tUy)H14fzg~Ts-_nr_xCo}%s70NpM zyj$t!yv7NydhV7$00_c45u!GMUjmxmY5d@f_TW;2PEEEsCJ3h>gbt=Z5}HpyY+$5~ zsM(oE%1;r0kvQt6XBA|K`TfS<uE@j_Za7&k4EUC4;<I4jsC{<6iIFkVa7G>5jmw<> z<)LeBQ|V0y_G$)|<rqOdR{D4cznqbd<DV~wQ^W)!!rBBsoiLj4G_{Uxn7vye>Z1LU z)R(xn9Cq}$NyD6-6yz*|-d!ChbcyOuyGp8y{SUM0AGqMxg+3M8QqnKf6#JIJs&~=W z%+y2rtdeoKn{rmxpr$uTo|jsr0jH(PD0Ip}jjKIRZ-?TI7jc@HPN`yeCI^^<<thy! zc3jwd+?%^aV!BE=ZP}xTLMMY_Ec{huAyN5jlka207$NYpE*RKJeJw|J{hAPS*NaUY z>w76DfdG%&!(mht&Yl10=6z)$Lrgv7t9{!Mm%*Yq>P3`Wo%lt&^3;n0M8Q8eAdQ+g zioo`Hfe2}hfNQ+XXAiyDP}=PLMOg7d<I>*7zYvGg)&{N8zJdkDy0YwG5Y|mQ1O3iO zP{m0H4JH~xJftD*<zJ<q>13CPvkLKEYL|bW&h&7`^%VJ&=l-=@zx&t!B0>N@U3yj; zqPti2qLjxdy{m(;*{`PpUzi;bYbeQ(2uP*|9D86`q%+5deia%D9NjW7Bfwf)Y^9~A z9Kcwh6n_+0E3^U}&W`>VLYGqp2rQWP>MOqa!SWx+0(r_a_6h&|B9V^qhQN+hA~`YL zTVu+v!tVFGU@s=8e$^}8sJq&zKO+4>W+Gl}t)}`Z%N2Et4oEOnlYNP(us$%%xbm@J z7}jWu<HQ<;^K$xX?k3AYD2Al2(2BxaJ|&6&NnEhjD%Gv;>HeA-TPU`vR;|{lni<YC z#^`LLZADy0CfY=pyyGKJXe%*PDC**fu~wBV`59TILd=36Z3+%E#szq3?|PN_S}ua& zH1o-)i)3H_g2o{jS9Gwzp@~fdL+h~UX$Hv7Bv%5FDrg6qH4OtfOcWWVE7+<Df)J!- z|MY;@YRw{tQfZ(@9Q^SGbRYsV?vmmm(`3~sbbY@As`=d{3#6q#k>MPOLhj3%WbCZa z<|-FXC^qR$`tr3`zhmW;qx^|0$OsYZvJsv>ILL1UZLPWdms$=lR`%YBc_1jJ&Sd1; z&Bp+^E+W+c##In*2(0mrzTG$$JtS;1`s@545KuMg$?`wm!1qiN2X+gLj2SA<At{$j z;9sR}&f(}mF;Fkq_$tZU6XzuhHZvJ$TZALd5f79=7rzoALxj-X{0_redRg$<TU7iO z^`b6am6C_Y@5%+kI983RsV>jm$t8HxY2^bmVyWzHJN#x#l-EPlyET{0=8>!tC&RZ! zfw#;aePi9G3ysD8{G3Vj7OUl2+_Rm1_XvH6$6m``b-yy^5=k;!AUB*aX~lW5JJ&Hy zA+pp1i}`&jKlK<N5=Dt2jov9fuD>#_18aD|L;tgzbr+NVppY+{%5a+u`JCUCB+~0m zyP!aYq4+{!m$a8dZ6U4|1DR=t-ms38*x}9!ec;pr>K#Nv6^!MX8zE+}j090=%}geR zG*!!TU^su>|3}tau*DT6Yunw8Lx9HJt#J!3!QI^<xH|+1?h@SH-Q6v?YamE)2u^U% z$IO{I-^_cuuB-PC*lX9SRqJ`~5<}KwIGf{Z2S#Q6klaE?cdEWx(|et}2%OOwwPmd7 z1B!=Ye%bhGX#`8_`Ej|4qGW$>aM7qzAZ=?|9BIH;GRlD^mdjKRfdha~`!F{m&Cu&> zQKQKnD2~je(t{_8DO+kmorb|a`0**kn(jyO9c-4<w-YQblbg(VyqrMU{yaQf4p^58 zyRcDJ-F6pc%6jgLIjXkSf4BGl{p>x3horttXq&r?=R%Et^be^Wj^G(tlIB2e=h0?m z#}yeQp7)mRRetN3ksPxuCn_uUm^}zD^AysG^yEJ}fA^SC?Wl?Bn;Rk(@b;%U$Kekb z+tJoAp(_@jb$lnM$=<!xZ7R>+#V6y8c=1JPd(aucoR$j+zS<%~syS8MHKAB#zW94- zWQ+jyu0e3ADFqI`p5KKSH<n-w!!umwn4}z|jP!N;jlx}kCP6A_rQiZJSAzPp$1>qt zIP`RL=Khfkdm?mocp9yBPeSQKmCl|ok#Jf`yjrb=-sAZSZtL=(uZl!meTrm*OlYN) zfMtPqSBy(4xYWJvU8scls<H9!Bkko^imh<fnfqUD>Fw|@M`+xuEj?d~*j#;tbQGSU zWTWSe8{cK02|CeX#UO;pAzP{qh9YmBB8$0oUdeLDe0K*-;3Q}u30_HSVBRlaJ=GeB zP(EB|3K6^(%M-~y%h%zA!fvr*%{TGZcpREf<x7^fgo4K#rHlq@kIh;S#KaPy!L9wI zk7$bURrHQhgRcH1R#vnBRHWfUi}ag<^~I`~I!`&_^1LBpOrmsek)rV!$wdX223kdt z02j}y24jL3a|$%CYWA>|b{fOaH-DvHa>Dgx<zI!K78l{FcdXvIWz~jcVa1)w-ZDCC z4q}>&Dt5fs^OsP;iq)B%K2B0EMbA+<K~lKs=RWDiXo4iG;jQ!tiec)LPP^j+-)C1D z>Cb_JQ+!?gHKgK&Be*ch*Sz^CT28FZIQ>&GuwS9#XV`{uRlw~9X-x)ctVIofqB%p? zMfK(Tmjzi>#LP}9chn{`q0H-?e2-(hpBHm1)!9bS;E-u`MuBqo;@4NC+qbkXMN<Wm z-eU2;CsPV0b@hM{qrIlpHD7NVckqI>l&yUDwFY!uTwu_OcQh4Dmt=rkt<Ug+4UhNZ zpa5~~<fGx==JLOxDYOZ_Uq8#uxy;PGof)oq{`UIcxBRcBg-whQARn?a@2*h2?wj)M zYm^4!O$vpR8Hy2Ft|TQIT&%%Luw)dCTK4Q69=X!52FS0S%g9P=MJ6WP?TGb3kzTtE zxZ(1`h;k9V@X<qbDNS^FS8(-@HW`?A4WY7o=^0TH!zP2rC*WGqR&F)iAKIKWR^yO{ z(kT*9Bv@ENqfCILhzfgbH$I%dY2?I87|#5UlPg_6qAnu1un}Wa^V?YX5iU(79rSmh zH0w@aAb9nFEVzt7N*GdwfqfzBtY}C{5iWTd(#O-bmM{~f_d7<R=0m7dzg7SuGvTI? z<#_uYU3TMjd%O4xs|1h;s?>N(qxwZ%oZwOsDnN#DDEXa)-u!_*60vPSrylgsSE@v6 zVZie!V>=gP89#mdQSUm={b&k)0GsEjhx__rq~DX&bg9Yw!g6F|C^?;ij)&?HN6?!~ zkX66~;c_70w9+_!^+uWhQejVJ;xL2$Z(_N6;I32i@R&^}yJyLa6Cjh^61ck^HN?Zq zzNeX25Id^*s0;7PkIH0;>@n^Zpq<n?vbD~x*T!~v2EVh8McGIdl92GcAxP)X-$VX? zTnSsLSU|U$2gAQ-Fs7bZNTX!Em()9x0JbT^xK_j@wY^0S&stuq(`?tVdQZkLn~2I% zVGv3of8rqdsz!_lD!|F<MmDj0j$ThbcB7~VS*kA25wW!Dsw#Ksj@|`lGS8UtAQ61S zC*kRM?kB41Bc0W)?w{3Tk4~u_U(yOnu8BV%&*=%?rqt=OhFf!quGjUER7k(XF~VVB zRsEHmJo6<oh*90KN1GsNFk8%cat|bMgw?>XI`-MYrcl+@-sNu&G7bGL-kiQ$lIlFL z-wqe%+R4ZATUfy5h36>!DN?WQNfZYh@fIXR#Q*R@vCtIe<ma5_Oe0DsQt|DdS7-k) zX>9$J@b^RhXG`w#Bpe`}s_#loyLjzHp`H4DPSBgrcRH{cbH!R8u6?AiY*rT?3+cCW zJUQOQ<1|HlQwZXyk~#44$m_ee1iEk}^g<Nbr#YEGpcDg{VMK69Wl|~330@H`s_sH& z>!RT(akA@LK^P(#zY463BgTN0G}7<U#-Fh>sF_#Z&1T_gngk1_86nT`t>pUBW1gX9 zIU`zCoN--6#}J;sp(AnPkGRe5!rzL8toRYN%Pca4i-!qx5viJ-r8r6sMi!+5p(3Gs z5MdyNe5(dcm^7==%aC#1E_c32QnZQ?;?{EFf|N*nGS*w#6<1NT(zf%AeoPzcm=fqf zTt*O(lrh~UBFqMsOxEs*7yEd>fKn!jO2J8h7`n(&BL6q6k?=NIfh8p<o-+XE_J}y{ zl3A(BLF&@RKPeIz=@JX-3Vu<4w@VK<%e9rE8x;gb_DKx!b1-7bSW78&ATJ8{kcc=( z?{sYcLimb>=>QA)#CERD0G0p_e#)07oi-e$I(@xRYE4?I{@LYY@q=qKV`FFf7s<2? z6|^y?7&8eNU7^h&>GyVU3X7h1-e!PtB$Q5HH<CxS-PMxKb?V=@O?JPSzXO?4_CLSc zQ`h>LM9COF+?>1qdx!p?=lga1#=alt#-~>j%2ipmR_0u91jPr#*Vy6Aw;^IKcxEB~ z<Ds`hZG`GLIjJ1_0O7#E&q6AErep$=k2W}KR(#9u*6Q+w7CLw8aV7X!d6W%OmNYo| z1sbxwYy-fEKvA*$ypw+5UakS>kkxqUlDJt(Q}T&Z(5eXUxK>?me#XkF1~ufD$mwUa z?8_>jaAiy;<8WD4`)ZD9d24!mOT|=C((!Z`DSCmw=LCrX5ym>3)m8{AyO<{-M$nh? z)5RO{pJI!T&8H1ye51E0c0F>}y=m|Dw^;|#5YlM+fa4}Hv}do_+?How5m~oV!!<DF z`t=Tz(>DUtIh&s1qype}&Y+wSy9S=+eI=1%U3O>sI0-4zEO(n>?`O^HV1qt3kukSm zwNY{<CSnAW!_$4``C9J$=dFbGD~m)BuR)EG9gI~3zt=$fDn?f2^mM-_x2?3<do2}E z^6uu!=g+c<-(x2Ya{RJ{NV&R>-)qEV6bJh<D-mI3UTO}+rgN&$SwQg}=ZHl!N~0%q zXGS~l_ndc$uk6!8jTE!)XO%7q|D;m4yRv>-<CNRc0)w$lTRwfC>8t&JI#mzF5`b?_ zkGTd7g0JjVWSb+=9AB3tbeuo83q@2hGu8!9A{Wsv!E^52>WX1ei4_|V36d&$R?I+% ze7njQ{lkUC%JrS6cW<EP7hlN;YdKyVtbV2jkeMTppM~9$Wee5E`}<#;ZN+LzOt)Q3 zf}-c7f7?gF-c!6CF>~jx-i*0b6yD8!8lC8MU~%hN0qDW5ODRqGQj9DgpnX^TrJ;LY zu=uer0*RF~Josq_<53`YU$2#z=9QK_!{^M*5B)A|G5GkGYt!%VtM_Ax)=b!g&F`af z`3^#Xw^<itUykk$xD}Ga+<g?~XQscuZvU&PaoH%j^|qOx>qBk-PcFtMd1x_X6<NVf zq6FgvlltRkQu!Z}4bll!rdk9u0(2JT9Rj(Ocvv6lpHpzD0+YtbWcS;o>iafio1LT@ zyrp<m0x~hLqSr%Dd3d;x?%y8QJxVbaHhN4R_=ZD@f>pe8{apkToSJ*DB?%9m&ZiJn z5>{`B4w`cDx(h@TS#l)3BOIgDt1<EPHkpdHfB<Qy0U5_Rm8h5m&^Rnh!1>w-QsgsE z$Xsg(p77TzElX4_7E}RSOJ%~ttYvIGucrMDdj?apvBj&7&-pzVKg)sv%}=2-N}3DT znazF+)f$w4!MSZe6s#R}6g(gmf6Vvip#|H-&PM!*LlWEaR(9{3K>f`TJQIwvn?rF! zwhGK6wD4-b98Cv~aF;7>8VjZNZ1ifRGtYJ90%(vv`_<c#DV#y_J}4=BKsGut=e2}8 z6V9@mcZrmRWYP_X4*B$YOrdO8(eA5(ZSZB%`LCi}@hLJ-MP;~+pY^5$2(r`0ey)EY zRVXh;(6rORib%mE=iCGUdPACEf2&lYJ+F!S2%q%Sm27&*%c8S$99h~Y;6Pr(1_O7( zn6EJJe})B@JTnfg+zg#~JO4z@zEJGQ1>mz{<4ylRI=0>ec*w<B&tojawpZP{H4NGC zDzMoR`R<bHAdbCeh%pRdeV2gE<TFZ-W(Kvz56ZrPaLMgf?ix;R@$JQ64`ZAy+2BUC z(E*QWRkhjVxDsR84C{Uv>mW4Fa8$5?E0BkAuRLk}>o<DLacFii!g;0jxQV4I!#Vfo zd9@`_01P>dPVnIv43oMEdnq?tpL(-VJ%5r?;kvNKYWmdOIHk>WcRM?)LVM7>r_b}h zSpbd8=I#B!D8hELD|DyDXtyxFiz9nk8w4@brjZE|y%xIFAa<PnLB3!Nb2p@OV5~Cq zA5wX<YfW>Ww!UwXFg}>mIqMGnC<H9uzI1;?hAxaVQC0?{U4l#@v0Cu8XMM=u;4}z! ze+|wGdfEqV73%EVG||et@rnlrvj4C~;-8%QJ@kPsbG`bPh`BLJtS^SOCK@T}kq!&y z;73)_HxPz2CRzZ3Vb;{>E0mNz$6)blm1fhKgk3e^8je$>O~UU|O?3n~eG<9dAok)} zhwxa%%-$_bO=fsC-AG}o=Z%1RSn$$|-!Jm{pXons2pc4Tyx1&+BQO<Wr+-H<V36yq zF|&60IT{#e92|2y);3f=_T%Bw7`|@ZfAUPd{?yTZUj1J@lz))ntjE5-8r}t`=GqKb z#L5}&V#=aSd=*(T)JnQYc94M|$oLDLr9{3C0aIZLn>qKi3aakajZ0o2i0m~oo>yZ9 zUa~3MIP%*1Cld3@cYjGQswcORmSQ!x6QKGliJ?F}SyllIu=3)0q|24`uTELlZf0c$ zr+Euor5K?)`F#u{>5UJobT4X`-MgfzDn04g$1d@bm(o8JOt*+uoIVoDP_an#czgF~ zZ{jO@Xy|Ja+YP`VOMvJJ?U_kqrA_l^&bbB|eT2R`{%#)m^l<D;vO%MB>lrTWHOPUW zMJ-_&ba#+=y{LrsAnukS9152582aCJKEw78`2mANRAy3CUu;Al>o-a~>WI!wsX^NS zVH8O^UdzT~ojYX>>%=c-iUXy~DtudYrlbV@k%1qFR2A5k$CM+XH!6C0!qy8<x%~k; z@*ZU*RVSF$_g#2Vf50K5=RD@1#E4y4?3Uy_S@HbGa%8F4paocGEP?$eBD@l_CwiyF zuUiyz1I&fDdIOq*_NwF>w{7THm~8Hp3s7qKtYd#+IfRQ_{NjqluB2%t#Nw)sld&&# z_1me)Lok6K7`Zsv)efu$MvQr;{y>T#>5_AD4SMVbk_D#|j5gPd)g%CJ7(-E}N}T1a zpl*m<SzP?wyOgR?7TuHpQ6EQ)R$o`BpCaEQKgA!$zdQjwpdc9D<lX)@N&=wodmj{O z&el9gcFSS`s8JSc`rbWH10GOGT9x^IP2IXzt{q5^6<iUTFlGN`Y^8k+wv39@t6a+| zLzqeQXEyua6cE&x<*Rwy&K}8Th{B&adG}rcnEVX)h$SPjlNi?E`iOK9kxVB;3CQ<X z5KiqHeWT}f^_W8qnVb_5jn)ixlJo<zSQCmgsc$jlv9W(}Fd9R6_8l`48@htC?t+$d zqr}`V)|YT7s~|)$@O@^x(B*}}&~aumTIiJ^zyK>la6)@M@4u_Acf*?dpOoN{gWbK) zhF%Mv&1JP=LyUXI8Hw4cIb*#`d;zJ?uSqVVNXCED#P|dGlTk=N34cAt7en9c_j$jK zgh>y3<=sE!mUr7qFVnvEH#gHSA8u=Z3TM=MAQlP_{OA^DOj^)vP?CiWSJc)~GIT%B zKnvItd@f?{qu9G}E;0t{Ass-i=Ym$3hQNvZt7;dVe5#~Z;Y#Yk*Qw@T?>Oj=leOKN z8em&L8sYE%%<Ot+xlyW$(V%HjeI{l+n^3nEhQtJ&6#q%pjE1rb9}s5a<4U~V1kAx6 zpxZ8zZh@V~DSWM2b2gdEc|V2|ai;<Cb>2_m9$^0bt)(`prK=e9&k?xNigQ7Y75hE% zhGG7NX7)>=S;Xt9vx0p6j3@S;^*i70vy9qt=7$09;8fD+a-1!?1Byu=AjwWyCV7Vt zaln;8(d!q>9?vk?ESoCvj0U@>(<ZqQLLEo3x-~5eL%DdT-T@fTH;@J0Wf8oK?YEc^ z?q<)C$2a)LZ@IF_4pl$`1yWyR9!ZIMs)PK%;H0}J1Zm}(6hc<+dGs>yg5E_9B#VKp z+e%>zwezU8mL&cE_T*X7@(yO-mj4o=-4K+#u<yB4;6xkX|J{6Hrp&3jUGtg%t`V}L zJO_K#Ip@sRR8uogfni5^zQC1~Rc&eSfAg{FA<xmBS3~xcscGK~o23+CDvRoR2XxBS zFbjMwi=l8%45VBlA#N!t#D~6C;T0?mC7oZy2ewp`e%*`^yehmTHGJILBT6g(l#naf zZ~;f+G99_=qF0Iz828a06an-sW(g(~$g*6>AK|ikg;W*yt3PDuz;mRdDinO?Zybnd z+k4uV@!rH%;$X9;Ct_UpwkFEu%?+j?{?UI7LRX#uW3LCoggE8!AALT4(m8u7Srl2U zxu8hB9<17s{P&_ZWCQ64{If~lS4$0|+|#+~p6T)+1C6q-EmYJ;qNq682xk`6X~v6e zCNflB&boY6EU?rf)TBpf+!o$pScb64BuK%FmtjH!7$PeCEvc~mM7E8^I(e{{m%M5W z8!UU{5p`lj2qoDnpcm-~l+e774=24mTYHKmVMgvxOW)>C9W+}xmVk`M)F$lh2Ff1b za(FX9NC3JK85b?CXiKLkNx@3!3PnhL(x|A8ps9+rgVmJe4jol<3wX&bDNl-cQZD4` zoo>JT4CxoZJSu~>mXd>chK;LWXh(3I+ZBRw?hBx^U?mv}_+UctWSZEI%E8q)8@fzH z_r->Yeg|_Tr0FIKeR|>x)(|dQH@R$<$Ty}DS}Z*HYPj~%Xo<<}hxoL6J>4L3y@(el zBwUJp{N&kMHOGFpW0*tuHoyI+@uW;yy&}+hJ83nor{pb5-tyf~7Eax8)#GdheN)3C zK(Ol;SIAx`JK@aDMkAG+(=Y5>ALP06cc7;NvfayEtkM?k6wzQ4&`$Pc2s!fBCfuAL zk{`2%v|$K`A`$)M<JO#LhILq6F{z4hU*gqpLUG*%hal=JG+CPV2>{DU$-s!#yyI^o zXxdK?1IPHOe9V{YEcY~7edaG}Tj^_9luzvScZc6Jd;tWb|1Zcv89M3m57@kZW-r*w zZisk*dw{`9*0^%Uqwo`B4Vh+4IZCh*0?BEDNX%+}9urtCDK7dL3<P*~>XnE{36je% zfuFU1wVq2L=~Yl;Qb~YaHH@OacJDy0^ss*s=vAWYoD3Zyh(uxNd`|2P%C`*&ApBI! z;yCa)i5;l!X@fIoGpflGT!6WbmhC%ry(rg}O!djm(ZqTzHQC_{xdIt!e4%~OND8)F zCX5S1_46VKuA~RREkwLJ&ST|k54(__p16yeCQr44DMs@dg`{)~`w(J!%FVG2;pmgM z0lDKrEW^o;Ln-j*9igfD!}`kMs}`}~_4=5s%h6))nOdo)mB{CY!i~`fQFpzJQy>fU z+kMZ->-ilZv3CNuUhT$!Lk5MVa0XjWfM?kcuwwBO#p?MS7)=x6h(TH5j8Z~CI!!u~ z!rP-eze6CmjU;*0;-Ww0Veh0H7XH1}+WD2~L17@yrp^F`SU_<KU9X)YluIu+T$OiO z3AN5pWSjbctM4yE1ReCnR02k#@HjrTNn@X<%yM#&D!s7*#g(#+TY<5kZcWi=(et<e zk)Zth-{TjWk-mQQ$n)A?f;1zvfz)hfo}y3B-G7+g_|R>yj-WK!bAyMtyl8Ai?PYoh z;@~7H%gJAHxL4kJs}9;+h7>SALuuO9r_rQIDIb8=21~AYvG_nq6Z@xPJe0C{(zd%j zmg0;ctR|AIG=|~T!yneMT&4VTtcQ#+!6f22#*lXT2<|0P+zYn=znve>ZyY1BXnunk zxg|95%|fZfidF9eg!sN0*1OLHWkjyJju`a>K4AsC8NrU9EpscJjlN2{>ct1uZ!GTb zoriW{xsfipyPlVF%F8NY?j(gt3Fg5jt1u(=Ii_&mF0ziyaBIY<*644tlAizk=NjJf zl;}e%%{laOYIH_>h(}YpbC;7t9V3%2bxY_VQ68jabzg}MRy{U-eoE_`kVY(a@RKZP zqpX*hn;7Sapkw%oh@PW!U2pU&0=hUj^M{+yjBi<9H|yqi?_(EuSuRXunfUA#Q=f_y zZ^BJk7*h4`{=qqW4U!w{qIJjJX(7%}M!N=P(i)hU{P5!1_?fhasz?zjfkNLwUx#)) zn}_8i?;O#PF1pnvXdH*7E|=V6?SwL;RrYAZxwpycQD|XD`(?xt&?0bWD~f2V(;~(1 zKn18TjO@4|ondRNz=;$Q@jpc)$1VDmxiI44NuIMz<RNmdA`?BjnCI}*_cf|b``5ID zVr3;zIv}s6=|nai_eLw2@C55Xn}*<zL&+`)pVv73rFJ8(0Xg_ZO!m2vA6AnVX#fFa zeKrwvUf<Zr;clRHSO0WB1|2E2qC$qn22-uWsfhY_*-YSV4hvT*@KOfFv%v&HX>QqG zAlsG1Kaw*JqC;6;EG$?ks!L!hK~{}SRN3D<T<Wu4rrx*Mk`Urdsu!BoNEUMmCo|{( zrq|<QyqsL8ze?^d4klTpFB}+Jj(z~go2#`T;teLW!eEOgD+A}t)yvMOUed}}2h98& z0I&Pz%oCgoDC==@*MjHOqMMVe?Z1y2?^COXXa=Zds>X&W;p;1ZbfxqKmF@<%Q#JXU zQ0BGaggB;MT{>wBxeoXs7%vh{m%1UJ3-=R6twWnzkxn(Chy3<{M&e*xw<f6)>G8$K zI%v`j<fs`<*EAQS??U`Dz|=`<*UJ*bnIFY7h@as0s=2g<TsJwk6FdC9y5(Tq6LA}^ zw*vW@@+V&P6R9HWU$mMgUCfQvy%m8$QadsaSmHQsB_V08t$WQjrfoJDod9T9&u~r; zYo2pd1tm5La=bug$9*Cpcl<>5i#|#Kxv|i%4xKpBS_Fb+n20K3q1ka<z0!)&S~4Sq zr+sc?tV#Bb_`+Q}^`H}ep;)eB^(|BQ7KnU(o)sD6*peU%5H812-q~^$qtulGp7XCN z$|hM^I5^z{5J#QA)qk4|&xC&9x8774n;<B<=fo*3cbRc_M8i6pSM0MnoP<foE5<yD zUXQd4I;eU_AaAO$&r`wUT7eC|eA!Yb78FPP`{B=rG)cyno4D`;`>;k9m@*(YOCh+( zIL<8}6gCzilSTz-Qb=Ui=~}RN>hmNW+2`kkS8FNzvxL8qP=-P&a3t9Db93XZ_<7;~ z-FkS$g+!dP()p3_du|4$u=nFC)|J<wj1zonZyvYJH!pPMP|GwarNt^QqqE#3-g48; zl1KTDf~c&Div~zLB=)CL7+{v9^%+2eWN0Fx06FDZa_{)r+rV}H0{48MGxI|;xSzd; ziu<kTM0H$o2s>DhO)Vp;EhN6DsKd8MnuqbRU%<KV!mY#@3q5iBFcS?FL2&-$!N%UT zfRXfVIx1(l%8z}UD}4T7qaX^2w($y^k=4Q$nDb{N3V#@<gp*UkEb1;Z8{IEkWtjwZ zWCQNOm)1$f%v=7z^N~64?LHz5k*Ywp;0BZjkX~r$ZsKy4s7;L``90agzEPR?rzY?J zZG`cYUv9|!xK4|GXJEXuMPnlY5e?{uy0+Zl(o=Hgpr6n)Z~X-5yyz#yRJE^xKiqjG z_R0+?$<RFUV+<3Cn3T!)_}79B=@(7?oW%pbF+0afI$N(@L<oti*fA!7;3XK!?pq;+ z#VWqc<@<WPVdyH@W!>qkZ^9V`+2W{=GL`2WC}x2M??d1!_cy_*<Olp!i0B4l>xA^6 z*brETDlTw5xn4O)ug22fjz^S_L7yI8A7QW@zeH(@u61z=1TSb><;u&cjgd~Yj4fh+ zW%Z3rBpwIVGSkTz5%B}iUcOPa2E)fIY`QRYHL$3=8~_j;j|c=IVZo>TnQEJwY|1Ra z&a)$~h|AfDR6L3GzxEbzMxLfeS2F-p5T*?KlNr{?1ovN)X$Xg+E0^!$&*>86?ufx4 zO@!*h1b)XBT~YClh^ykQb#BxywDgnB>}hoXt3gv2Zxvz(a(b3R$k<f+JgIMBKS`%E zU$_{ri7?ryajV>JKQwY@?fzz4%0p+i8Y4?B&v<MCPu*m1+&l;1F86qwRB-u;E%o&J z5sQa|Eh@a6?K>JZhxt4|ci3981$^pLU@KB$1xZB%Rb+Dm!M#*c(!q(}07tC_LVrG$ zOI1x^)}pdV^ZERdr)(WzecDNMq;dV11^QoF(q;4jxW^Lt&}1CJ^@m^Pw>oAekay)~ zj(o*W<+DLFU9EojcDDp+23CNszuV|Nm*?~<CnL*?fR#*eIjqP#P{M1Xkw!$_R}#O2 zWx-ee{6l(JfiSDsM>b!p7FJ3E$JyxmFPPLC+=S8}&=+!Qm@PAXo~xzN*wyx?JGWul zyIC~Mu=jq`;?nz*VlANf1Q)7~n}m=Gx6xpllc5=$wwOD2at57zB3<n}IzFAe5skz- zAi~EabVj4N@eR4kn!p@G2pA175JL&21t-AIK2%aM$r-U=34kq<_AZf^KZAXYVv&BJ zJ4Ip>8)Xq1E5!xj^f7V_L1j)TlJSudYujm-4ET7keH!A3yj#W0FKI$}hsd;W#MFTl zSNjs^Or@YPcD44AL5;;`LsYE?RC`7B-G<Nlw-(*P@IkVR@LW}6j{vT!Q8`i<AD7h3 z4eVCq<|sIu!EhN6O9-AkZO-h~%Ph*A2vN+1{ky;B@g>zrWq4Ir?W{g>@HZn&1gNVK z_NqcZS4z$LG5_#ED5v2fB1Gq&D(<(+<soH_eEI>n>_<!W&M^8r;B1I!=f0nL5dGlJ zf8Rqt7O?ck<ekg<|C3k$DbTBB0evFnYEr0p_KNOT#*WgwIezcU$q|pJLZfo8C2$!g zcH(TIA#kd(uOt?aezy`OR$7Y(2yA^O(j7b7ub{tD%fm-WKSA6c%u7X54Yu4`xK4Op z!d3Px?f|y;<ecHZqV@zW>X$75{O>?$*@~rKqYni+h@XGxorLoHMPK$$YsaV(^e9dB zp_DjzG6t?+5~gk}3y~a)7=5&3Ey!@<XKig8cW?P+`l;uysFupKj&qh>Ura@78b@>P zmkY<a;$fJofs7P#%v;**t8)3}5Zqu9q+@Tw(lnDv6U~aJ!9x<Bb6ga((qmTc$%OAY zyaCUX|H|uIhtd9Tw2uFfd-c_lf-d$=DgE6WrR(R}?7{}+J&TejkGqMFALR;00_#>2 z<JB+T8L)SCoXtaUtT#&owdi<}V%aUQ<oIOURLc9cXgUTSc-ElRjr2^eHmUf*LX<n> zDl73}uQb+#Q7>*>&qmdvrHhC2NLHz;<i$p<UtgM1XCKW^ML`i_fq~HC2-qE(O{3uQ z5r>u39nmRY>68ppwgGXhR7weYGMY0-6Pd@HtWD~5BJl*F5Ullz`R%@FmM$GT#BkO2 zKV(MDl=Fhib1c5)cg|%kuSYf_q7EluE~-`Q8-Wnq1e;u}XOxK_*hc{toUV{AHX3lc zyBe#r&it7EIQ{YsI1JadjFyEFk@*z$k2M=Kz=@IANUVr9zEmd?C?B9gkf03B`XMGo zeg`23rCAsL$hPl|by^iG#^~{hAo1n1Ebu)!4yEGe?UySIFxHLnnKH+cOGoeYW2-Md z#W2$!Xu9NXd!Qx0t~{v@mZfl#IH%KD!I1B$LCwiABDih1@7~YPfr<jE)T2H&m-GqS zic4KPl^`1E;kvkgMugXt$H~B2iiDJ%b*nmwghU2w;Jfk&ON*X-kW$Oni4Z!zO0V^W zRg3A<>igguN-#On@>d|2$iw$p`fK_pvw{?PZ}#28%9@1#dy6%R7UwKflx2c$S0C*x zN@YUXK7h4%niH=2G<oi4Nl~*rz-hfqI>Z``Timfryzi#A9f_Z56A4zwNSH`eT<EB6 z0l{ML>q^PrqJPoCUdhC=S9_Ry`+J&wpGm3d_U2GLy@A0crIRo8QJZ`P_Y-V%KkWBi z65CG?sF$CHQBfZH5B}f~5SRoaP~!y&!<z;M&zB$w5LI?oRQBZFyWXF^|ITfbG&(dZ zj?RxOydAPDe)1gV-20%uoqg1H>P$v%o#%$3z%8cz`a>N$hjUN_He9_#x2)p4&9ZKn z4STg1oLT$pg8CNYp)F}@x0fmE4+qc>@R4eeusS(LH{hloPMZqux7v0a>E}OLE2h!& zs@)?GWm59XWaN(D=Au2K(<lPsz@T9KT^8Bfa#9IvK-Q`dnRTkE6zq4tqOPqELh9f= zPVugi#{)A|5<O@<+Or+CM9op<Q6h9^^>Bn^j4T75wLxiC6SZ(jb3Q!+=`=*r3V1jA zslrfs^(SMR4Os4w$)v{<N@Ar1EHoIes0m%<WjhSTEvZj3JaOL=aP7+TC#qSuFwHP4 zZIESfK-4C0tjh_Ac}07uTpx&Naa1GgI$97eE6w*}lemF3lkXTct>$$o0CfuI{#zC0 z%l3%#b|-x=+rB4yx`d7f*>e7aqc--CkXoc-6JA;51LZN;1r1e&xL7}bk>bx*8nl`W zo6K60g_%TF>4VDOqx44Th@2Vvj5|gypZ2sBtdWg!BFU*Eg>U=Mx7e5e6>T{%VqQ#G zGnPb#?a8ZjQG0Q%WDp1{#%^P+=y*&-7(RE+p_Z=VSQIxrflJ<E(u4m!L)OfvL_bnV zG&<D$uaUs2k4U3%{<&WMaYcP}&UHg-nl7k6ne*lmPi`@GsMKqej(9>em+jixfF`tt zsw^6xRqC_cvRqKzYLt{BH8GxSz>T<Z(%dX}x(izH0O)US@R;`mj|Nl|sq%dgG(ltA zEHn$kCDX9CrZCV&F!wjDLl8ND?hDEv5XGc44WY{t(yS&Z1?azHtUQrCZuc9OE}-Uh z*^<{y#Fkse!oNB^j7k0#-=cfb(CUZva!<=6YwI7H@5jDR2aH?q;n>PZDi-AM3-k+g z-v#4HzBi_GC8W@hg(#Sr_+@KnPs(}|#BIjyQ4N^^DAunSoCj20G=K%oNKi^tkD?MZ z11g|x2fP{8F*2R@q6z#fP{5GEIfqJzn3MB<-eJ~|c75lskVC`DtqE8iNf{L&LO_Df zEO6Qu@LPKSAkMYHDKH)T1W1p}?EkA&59+!S@R3eymKkubCXEO^`Gs5wER+jtRE8U& zHkN%riB!S@BO;{b{J@^|3-7}T{63XxU10yZ@i|rv4Y5l%L}1JvC=Ci|7^b9@V3?kX zbK`)i0SJMMY180cU~I(!Xn-Fg=wt7=yvYlcyoP`pw?I{t&H}<Xv3Fqq-q}PC_XbbD zDBYCC6P$c-5GsIy(uGWddpRKyQr--3|BMq*tEQmX>gtI!bYq`)vjlCaj>6P-^dpDB z06;q=%pqh>_5r;B(RNgtQdmZ28%ExCWQAjCjT(>TdpaDk*&~Ds%wTB@3zB41FPTT7 zQ{_-A0mM$JmtdpNjo?7!_YVyme`*Jmz(dlq0IGkEJEAxh+AMt*FdJoR^gp24sl*@} zwSjZA2@4w{u!b=(3g<!G>bdU%D>;n(CUM)}#!R0hBmQ0>7e8YK6ij-w>h`-__npYx zd#_yj$~h_yvVK{U^5u5`{zn9?*#TXA_x&1<x>vs6Rro~R({0mSg(W~;?Vf2RpFxop z%`fpv?vF%#hEA~(k_(3<GaC736wE{C(Ij3~8iO#?D;D%yYNCK+LKs3v*d@+q!Idfl zcBpPYiKBy15Nm0D--6M9(Nj6~pJoxrx8T7QQyIre8-!4yC|T|Th3;?U(|hs!i_s9` zvbP7_An!M|IPu(^5cTB~s6jltd=>Es-V$8}`tY~Vq~gj>=|uDcFy4Tm*_f)bUYo?C z2gObrt#fsvoY|lx{Q2<tAb!0@whLUDK5}sqk~UPIrZC<Gni;Rm_#IWnOxQOW<BGU6 zA<CG~GUX008`M4#O)TGuu}+JEg+rYk#ZJ|az_n>IjDmdiQekb?F_Ft+E$UU}SD19^ zlF>>5b*!J6L;=>SGUVqmo##DYL$t({0RVd2BwC3gb)^WEbb$@NWNf*ovaiZd`~gYq zL+1N<LMD`=)P-q-2*=8U9G)2D(IWIhs#Jf(JtEtii_It9ezKeB8$JeIq!rs4pRDmc z)mz_uwJ@T)W9nCPp`x=L=1*{hS<BOd)nW{nQh|x{Bp>CYtc1=RmudlX%om@>LFfhz zUz$pB;taQpqM+lP!f6iWH`lvMR4Ar}O&jnwx}9F7{_$P^XL$5g1!R(#_Z$ycU>V7% zQz#{J479_OkcW*jYfOiH6%VfM25D6w0*f?I^iui9a05N0e~B5D$8p2grfEs^RO7!L z?sW6d?HhFC#nD?*J^Xw)hHGHIUWj?1HSWjX^C(w99*rISzCm&jzMI+!ik~lzfjKfp z$?D+geXl2WPAXsAky%uv&S~OMd$XWhJ^(Udvf1}6E}N5kc^7$A&I7wZjN*--Zq8nI z<Ip!m!!L+r@I&e5XcBs2mIQ(*O#pOJL^1qC-i>}$-op|Vr6}bTfl32SwQFj~MT$$w z*w)>V!UhfI{_FzGE(FOdTA3N$GzaHsE(Z`aUEdRX#UzW0z~90lJVb{-yVn-3cRi6j zkh+{19rq1WG|!u#=&!LoV04Yn7t?}DTd#iI(2*{gy`>7)X))nO^~r&hwp~SeBmyJE zfCPBJp2LsWI<iZV*rM7BkQh(e&>(ay(z^+@aV74fYGfH{(x}y7<+@w`XP#JbPf2+@ zc@d<az^Vu;54y5|a4V%8g=mZMWf=beT<G4UYnq#KR>#PRxRBr37U`(~4*y~!A%d;Q z6B#2dIPRte#A7*z%jFYw&8dT{<``261!Iur#@Bc2O46;L#%~}ta3xasnhS{C+?JCb zGlYNvwFb;k(e9wVCSvCT)_~LsWI)jn^Bzjs0$4ze`T^1szla%q(vvy@JDxX}(Ayrv z%g7oZC0j>bGLYSAvMR5M^r{}l$4pAvEWs00w9m_sr+7pUH4Y8sGkoucPH95utuCb| z9SN&&e|7g+d|22Zl`TlzVW***h-;k!K@=n!sSEtzB}qy-9ONt&U~T7;{aK$*Q6&dN zIP~QhE~bNsf{#9J3|Lg0t-i)~K4V6w>V*rPfq$bHI-lgP%3gy}s4M~nJ5v6v2ePEI zWP=m3TlK5c?kgEVqe;~oSYg66+v9$%{kszJC~BH5c07x9g1P|4cwL4nevU!sS%z0b ziE-oGMC!W3-R+Igb<OZRY8~eKkHq(nBpASF351f~UeL)3j4f-?>dNlL(UOUI-Qj!8 z=G8EJR;vk!#jXTc)|)iN?gh&vXad!$p#<?&oV`C%YDfc5O$8QByqV$+nXzb|#*N3- zX(}Nq+0OKC{vw3Dq=MFf46C@NwCzJUN)`<OTymU5Xqjd*9h1VT8YPitX_KM17%P7Q zvB}FZZAB}TkjnS&0Gouma5B`U_y~b`eaE1Fq_&@BjI0LuU&5P3T17AuS<4o!uu{bb zW!ba#5TZ|T2c*WIS?QF4baiBR$4%k!;fx{xB|epY<7PopPC5c2Ibb^3_hrcqI#Lu? zquB8$U>iQrUB%Xz>eFwFV}LK(^FE~+^#&O!i<%G66K@vLCNjOc0mp6}>n`GF8^&b7 zHM7W`lC66NdN>WQeF^$D<fA^WeP>t+O`q!&R;4*59NQ^zVz_jMe`H%s;DcS0+nl`e zPnot_qUq`aBVC`pXx(obq&9NvFqrQ%06$`&b44Vus<ah+4P7izlYn}g`jeT1YK+Vw zAnetoRw0_-sOG5}h;`H3_Je1Oi7XzifmS0}avE>2zD8cIFu`ZAq3(GsrEV&B3@{L8 zbvvIw4ZqI!DBTqt^FwxCuKf%x^x)paX@hx;|0VA~^W85{EjAK_v_1`%M!j~tJbbXv zd*V}g5lX#CjAaC<3aYEn?Vg7WI#=ZpXQEP0|J%bJrl1AE&LGp<k^h5;e+gBanf-jR z1D~IOt$BRLbD3l_fJ~fRNpFN^QN{;q;2z)Cb6QVgL>S~WMdl<f#Y9CDN((e(ENK3| z(0*>AFCJ?5@th9&GR|{XDYN_IJwQpV?unEVi7p)i%U>WZps{xitWaU+c?rv?VZ}kn zk%9bVGDWNK*W>*Bt^5k3?WyH2WT8UWnG4#dhH_Zd>>J{twEVb4m-EVmIV-tCVgYOl zICVq&6f@Cq+Hi*2h<5*2ZCDAHd8$Q=UzOcBBjHdy*J4Goy^1Es@5i^CPrY@2%~Bml zJ|)3}T`P*8^_~4A2ftqbvm$QO#a8lN{l`ZVpxfe~#Z5}WFmAX}*s(8M^DDq|9DgkV zmV4J3u^D7*_oo79t#(Ksdm4C}<|3&b?wkkXfbF*r9xvyTjM&{f-A)qaF*gYy$Wur7 z{$u=DIt201;MAf4z_8L7_dygNH3fND7C<laoGiTj^llaM*MhAhYbX8K+oyW<OQ{qQ z@BlxF6%74hF$_DhB-lr<z@O@es~J=aAW{zPeVRbjwcZ4@dJ!3-d`168)#Q5I(}GGo z9YD;CyUiFpA^gK^2?&?2f{8-jq5j^Vu7Q)9z<vd?CsT4ZAX+POhtQ$LgV-X6>&k%~ zLox+*bR+|?5nKwLKk;iRwFU=6a{y794fy^k65P(>sF{5_O6|R*YcM9T@ReH~w1=f3 zo)Ml?Mo}Y_?14<u$bqSP_khwSnVmwELZ3cCD)@tYo9zwfnsF=FP?pvBBO*41fU1*W z{64A#3K{bxV=+hX=m;`cZUorm6Ip;10)H_Z4z`11Y&}c8?)$cTByq+ea~=Piw4Urx z+o<$)xKXLkV8-C<w7+VI7Hk&tteW%RASNES3|1kW^i;pCu2+-;2f01ldd2b(`a*d9 z+MIVxz3W1l$}dr`ijiG@lLony8cvuL6LdA0T{gzeE|~w0^Cp<fFU?<V1TR6+#=r92 zCvcK#%d|rFM6H~@^CNsVjO59C2(r_D8!*PzTGv1f8oI_WzEOC$->m2JL)C|m?;!w0 zb3KFSG5YeP-JsOhIE#0c#0O|J3kve04F%8J72sJSpd}U13nkW-Y#cg#BmeRX1w<M< zLSfo)yoO3qUs)8V$e!k3{V<^waZ<?2w}tVyWZWS8f!}$y8cEPXVq`0Y70oI(R<`ON zCv=+Zgi?6{RuA`FA<_K7u+2(%uMh25p-v+>;Ac2uF%{0?1v!!#<FXW4_Xnd3HKYNC zGm0MG^knm#aa3l8e&&E;#*Z>d!~d=UhQnn6>hfu#u<0ef72@HcXVDQ1NQK5o@OB{? zYiqlV6h6>p@>pw;$l+xQD(0p^o~q{p6x6E&!ve-b9LBxiD@o3xq)o^d1UhAq;#g~L z9y)(l+di8yUvuPQ8*$r=n}S1!8)n@+!hJgJo%ToOf4F&*EZ2c~wB_=6Cj-PE_+Cr= z7<Mr!zLw!knzB;<@h9eM-mm<XT7uZuD5)krF56k9{V)|UJX^8=c=A-S@5soR2qI!o zZ2Mm8ay8}?y_5QSURUmO|KFKT3-DKw^qu7o#YNkq99^Ip_1O{rj}0<QbF?S4PW^pk z0Jc-yPv>(Jkf^g=I1cL@O}B~&s_|9-_Zo{Y9r0cZ{*1s`SHifMt-p-$dledF#_c~w z8Ra^UwW{_PdTV+C@UE4<wYFRb<fYNska%GO(SCJ6y?~x4a{V;zj(?%BYDPhlvwF>p zG4@VFp^n(*QTt)UpYT#jyyi_7+&j*#MXBBCi|=pC6I@@Z26%7ZAR#E<VAp=sr^{SM zwE1?IRM0|6L6D8v%dIzlN%$SB%w)m8#2uI|-~rQGvMiiMic@HV)3dsguhgG@8F&sS z6|!Ao&Kxt(<hs2Yc1u|-_m-@ABO_M2v`Wi85epf(ZG?4Z6LxIi>^=V@!?js3Y-uW; zr|a(1JUO@cGY;1Ze&CiMT<W}#f=4E9Qs~I@=^abPuNxzD;W^}X<mQJ>nJ=hb8P=hM zyM*#tO_oZ?C=6q#+Db^~)uJS_-QOHPEGOyv#8PpFD<r9sEn<&wh~%i;A@9dfrRp_f z?Xo+W!tl$6_)ccl=(ZFl!@5v#$k>Y~SUumfd@LO*3u^!$u)P<EH5C-%kpQBiIt^@) z)DEt<p=Lp$2BcF@5`?*mTmiq)EPp<iUjbuf^+1>0ei&h*_?h=KuhTc3q!*CSxI~Mr z9@0KXS<hnpQqB{vhf;M+cxW(r_~FnSezc5}`Z4bhkEYa%|3wfd|D{f1(NJWRQh%gW z9(Mxmttn~_C!lc7)t6^{H*QO5E3bhRZQ4?*fegx#vwENemU3Sr2_j}-J=`4PUq=V1 zpra%}Oq(!PpP*TH0Cep9n&kk>yOrl>BTLUJNhnkCG2{bVrqrFnMK?@-A>Fh<mT9(< z_UxYZy8MOO_UbmIb*id6#v+gpf}C-Lhl&^bjMaFiDcRVv_Ku=hc0QNbR9TuKWE4ch zN~f<KwH+s*8gOd9k1e(BNL$kPoGYxh5Vw2e{k!Mq^<J<4XRM<kjXtV>B}EVA=we}v zTO}_?N9)3glQf&%`>vZsF`|jol_g3o^x+)fujbFjcnU%p(hTYP9OJg?#<-JGw*qs; z=IjmdRSBojUUx2kKaf+K0m`a?))_bM>(>14Cw0lHI|&~?bp~{(;^(W#(q7117gf5F z@h6p?vylJ`pG+3ZjTkE@+n_u!(G!QhGf|<qZ#0EOG672BSZxMbu#yWaa~unYrC+|l zqaLLYpt*cJPU@dlPFY=ZLE{`FtN3tLzZP7EzPlU?tFIZ9hhAgr=u@_-QqdKGs1Bm% z&%0<4?;7Hd=+h#s%`2JkR;yD(Bv0DGbpP-)s51%U)r6!xfE8gnJn-rPN2T{fpXjCu z#-J6{dK%EEscirSbw&&@JFUX>Y5EVZ%M2}+6mV8_2^fQo>%0H-okf4yILfjiJ0Dew zq#LZz7!yuFgc*ruGH|tOl^vDJ8uGELs_h&`NS3nozldB<Po`Vuby*@~C1SNPL%qb= zU*3Q<VqPrySRdr{{bhLLSMJ1HYVD6sA1xiTN8a2CHeb~jl>)gckEs|i@5y|A>z4%0 zqi>5?qI4M@6Hta`g1)V-{{Inu^lP9&%Bx=Nc3dAe?OffC-|fsySY`fo6867|1-k%O zvc1<{8<iCzu`Wq`<wT-xGOt76RlbJ}y3CD2e7K+ADr0Z}n22K>Sc7;lCR&Nlp6O5m zn?7B+as1G><bvo%)a0eg=mIcy<JZlKi7VXjt{v}RoSZ3Yz|?F__Z7gaGNE^@)jb00 zC`xO;p+q)rCuu;wW?M>8hYuzWzaE%}ZcqdC?B)ZcxqjG68YW2COm9K5t?o*{TQ^pv ziHwIaP;GZJu-Kb1KgFmw6NxTuxCY6|dwzQNw~(f~583JPYdw#v@-N&mZ0_9H-?(n+ z@*hU6Z-2`<jlKV$0t^P}vbOA>ow!!pchb6K?AKZG`)-$QsdW^GLkaVcpc4F_48Jmu zF{2Jp9F=Gt6LDw!-2xeg(%(z1J~aeDn7WS%ODcE4k3B754qE5WEcyi063%95<@{;2 z1|<?|h^#u`b<(k~2)!5`K`JxF-#-vwl+^RRQH`w-TFU&Y!7F)HE%`BC4ALn8C8ocf zev~%dfn-GA+3mue!&1K_r9$bSz?+C<KUv@xp~^+U8V_<u-xUH1VTARdjM9VDICeAk zjF7mCuMqK298vm42$i#XaS!W;Z{g`EnGMW%WS%BCEU?dA`lLi<P~0JpVKi1oHR-0# z<qM3L$$^PwB1e&&W04;KSf{~sY<1F26SpdKg@N+>H*6aIub|<``GBZQ?u?{cDcYGi zIq0H+0HQoEED)?syk*{nSj2}G1cm#aU71f*GCKCHvgsl+dSzWCW_&WHF0xRvPI$&N zmpzmK#5VWs1GSpkqFkwjdhQpQCL>;ZqTAsirA_`b4aHiOfuM{f7IKE)3Efs80HRom zgJBlE{07YI;#bqZ3%Y&(6<g|70Yk>MLuhRNf;Q#&BtA7Ci)TDWon5giA+-534_D-$ zAt~uW#hA5#jCsKrTHvJ=?Q%W~)<L;VDCI|`hAH;(yZki1B8K&k1s}QSf1x3&(QRl3 z;G(IHGnamC8zs=;CJ~`T0BXW848{U<I4bN~vGWav^i*D+^r&iq@@9}9KaTwi(RTog z>G<7^UGB<F`dmvT*4NO-&5$28=$(`{ocA7!E@)08X+vk2ml|v<!dQr#hU2PWrcnPb z<DF#055K#&VQ?@u#X$mNA}XU)8f7f8X<Cd>+*AqDMOsrpM2hGL<yvbQ+%zo)%9d~? zf4Y)EjBoH*ki{C(QOvUC7;UN4y!q${E18R60Z!QOvzq?n4j5T#BWABS8xQ=5dU}$c zAUFE)^btU<F{X*zUK+PC!BV-hdv0Zlo4+c^4K4O#I9G&p$)L9A_>Ea~sdv)H)f&%8 zHlMPsP-6*~3r(GRUGZ!>nv(QemF2+Cn_mGU6JxOcjqoK+xR(s&>_rkjo~@~WKN$YH zyKelSlgz(6{B3Xy{tni`DDt=N9ey{({3E<Q^+^Fw%RqLbCd{zi#6m|(^dMvC5Vd8S zJzY9NdPBWPRncjID`(7#c-aV;EOZ`Ygh}43trKM4zXl5!1`;mzP$H4x^#i|~kO&~< z6|3W{I&jXu$!(S&9`}So#Xlpm;)o^d!ykRGCs+=vNo-=AuD9oKFvgZ-f_Z}yp5uU3 zt&$N8!N1ru^>>d?m2P`0A3a^>wtGx={v^6XNUN^B()qF@VSw|eVf5p;HUhMHFz(JF zZ6GWy>rRputGXNZ4wK#Zz0_wJ_q(ZoEyOJuq~E!I|E%oo=Rg56Q_UpC+jx7=jCXF0 zrJ2WZ;Qb8$J|$99P`NmQcI}lHyFZr>2MHhp9~7lrE-1sZ@<)c(5@SoIhG^7>o)3aW z4)wX-(10@nmd95Z%TX<;NI3ICbojS&OzhAXO*1(m)8R`_PVU#lBCsP;JGsK3c5>um z9#`(}XRd@tIEECsYW4A(?LHOi@9~acQb;u0@^QV;rl!0DE)*b{Eh#~U&~vOcf!NUw zAhQs1%{5fcyh8pYC(Ty&!4SE0?lh22J<txGBaI-YS4h>r4%6fJbKu|@e3)}XXBY^R z)1+7F4mM)|AvxMgB#;^$6^QNuJb85zi&Odtpt72F<5F!$icvKl8192*-9H3phTL`| zcjOL!C}wqLC4`(Qdv6Ik3s4fo1*2DrCRAtiCNmV}I-_RxpQ1SRKpnrr$Vo=TMpMf4 zcOwmTSuhQUrzQNDZBK|Q{^4HdxK5uCz2DYUzjHMWi>OI|?yI$_vgH+z?`NDFj+L*S z0@0gtZ&Awd%DD>io;1n-=(ZV@blCYSqssNX{(Sx4=dLao13YZ7XP;WJ*w18JhMKA& zYA=dUuYHjXLJB(EsrWU(66lP15nfaDWZcv%JJ#%=V#lsOBSW0SVH#Obm!)IIUMNQ< zETK8^6<yFhUO#9g-ka}zY6%!ofP#!!KSV8vRgDm}eOvZ;g)WT-@EFP_RmC?>6?#hs z$Z8cY0Ln{31?uN#G)WL9-?iwQi9Oms{j9W8@p}0nqiVx+YT$TlKIB$`thFnL_daTU zYaw_LK(0~K(m2u%S#8*Q7fRCumZMd?rKK1K{y(z5GANF<TX$w~_W^<v+$AJ9gTvs1 zJAn`gt_jXSfZ#evaCavJ3osB|13?plOK^9)>~p?-Zk>CpfAo*;>Z-2pde?d%TXl@F zA*E2XNZoMbw+{TXnL3|Yk?hReGRb?JqKY5ORM?aBD@Y!Rq?mdri6ATqEZD1Yno)a* z0Nx1Qw@K{$AOPQLrRuE*0pD@GuI8yxl#$byewjynR6>X~1Cl+jp0STqOZJkFweJ@i z?#vqP6sy`RX1!?CO2%tO-<WX0$phM}S2~5l9UWgcpHXEjNFFS$N;cUg*vm}LGk+cZ z5YagonC^xr^pf6~DLOpA8DkhmTOT1q|B&<ORR%gOs=M3mKobb)gb@otC#RbGv6N|U zO_!vCH-Oon`N!+hV<Q^Bx{^4~Yg8}ASbY(VoaONL_LA%uo~B#nc5%S@+cI@Z_qn`h z;!*f>JxYIBXl2Q}w)w3S;WZlHlLyFu0LP%zPiO-{O5S!=>hnSm#id4JRVx{%zWeM$ z8jIhZof4@^dArnRB|{Mj&<m}h43i+t9c?qRMAJq3c@ksP(m}AN8^bz5web20N$hn5 zF1EkA=}%pKfLal}7_`c*^2W3?>RC(cOK3~G2Z1pSMC$b?BPma^@><k&pFT=0G!nBA zy3<%s;5rQRTd7=k%Z=Yuj?+c*!bkX>$~>uUWLnt5h;Zmpb&|)TQ2L1^icX1M1{;ye z%8o+D9jej;u>46jM`gj03I}xbgbTZc__#tP*)hb0zLV=?Nt%`_v_?!gdAxt{`meVQ z@|^*_Y9e+WH>=KRZbD|&K?XtQ$9oN9`^dKe>?Q=_-vAiw;%YHAWsDJM05~QoQMsD1 z!bSFY?%c7ZYpF||%#bD^#LW^5hq_m$O<cZARhhjEp;MMfWND`op}b^Yr?|CTh%u@Q zQAZ}XBDPU7pIfu@x5$cEH0s6zRbOsF1Thl9Msee<Jk0~*NT(HL^8i5?78I~%7IbIB zZ@Csybc?vMJ*Dh#6WD3p^up{Z*$+!58R*nwivfah`vaK=Op<!!V@U?~s?eK)gG>AP z8^frQD&dN;9+iEH_YpmE!E2Sh6_j>5B1JE7g_*6{Ge_P9hPx}*2k-LtC;Bru96kv{ zBFc3K{9drK44-Se6)2fTW0T4s|E(FKW-q{~0NRqcgZNmA#j%nzL!?Ei>e>7jbu~+d zT4<SZ^S`Nehj}OS4QkD$Z{!7~5hrs1`IsBolexKcZZyiZ7job|-@m}8$L>iWBL@}K zw|NJG+8<jW5;DY;10Ab-td5D`h7>AoFcT}K0I65(fyPTE4;O#(Fzuuznl41n`-BR# z7ec>R9!^Z_zaFgqBxqjm+5T;oeRzV=6?pQ$nDHa9m#z_nji#63uH><5p5x_1bJYQ{ zP1-Y}fEs}Ii)0jDGG0vKqB)eM=1kB{$yD}Ke4(m!gw6FB?5{kt9c{%66EUR)<)M$` zET+T+Vr;YdH>egI`C~7CFoqN2aSo^U!i#>f9!*d=y?V~Vu9d{N!lym&4u=VqN$Xna zqj~5m;&G@R7esVD>oMg&9z+!?(X#&;W6yP?2nb2ji;;s5%~a+2O<Rv-UdhaISq=cq zJLhPO#3v23lp&V+)V&<lRnl^@7v10<6w3ri<!dDdk(wuJ*Z!+%?0Ak<Q+#tNHlO`B z{bnpXH4bYY-Gva?3Il|r8xQi@VwC<Z;e?B15YQD4XQDhNdI!I5U-q15RHDs#xBm67 zAl;bBL|L=#$E%p#rnI1tAk6XAW86t1=Dvs}fVy?GVZoUh(~Rg7nffyohw1x0*r2?n zCE^$9as0rms1(9Z4}iL|>xVg%!bWr=^yEc6C~Y-=5mvdGEX6@28phlkxy&~aG>Na= z36n7yUP!2<^1PmC3v4k46H=`2%{LqjvgUYvU-{9#t>)|&Cw>wxVS^#f&~7$<fAmSp z10>#lzwi8kRU`Y`p11IU-(~np;8_7(3l2HY$e_lou-01V8qJ|goa7B#Cnw_SOQ3VL zYd5!_P$+Iu3)YJn(1KS3veq~)Sn8a)9vR}9X=BJc)#-fs=fe`&cn3sXOib+Sxh{Hg zAbEz`)V-eG0?Xq|TGWe}t4p)-gonOTDVm=acxv!U>z^OSB`!0wJ_MhXV2qp7u9>(0 z2YWnF!x_*gN>lE5uk{uXc+}rQ`ru*Gkif`aFTJEwax@#acIu+u<4SYqmpuBLN}Ex0 zqRWlzv}Uk<IVuWi8fj4-&=}}FB4p!P#mlaN?;1d;J><I|9c89zxqXH2uN2)J3q5M- z!a*O)i9^Lgv#z{Z47X$I2{#$=Vv&I=l;Z#<A3V~;uaf~q=55*cIEzvi4+`ceEDe5C zEtT=Y14HKtmoT4gVQyXA?<ty_N5V}#H>4I&R208ZB(%Dqkc(LIxCq)FH?ST3wJD-g zVLc%&d~-13!{oTcSmTY=f7kf~G8|jaiT{Su8v%d!=RZ9D9?kX}^5KZv-(J{3o}Pxr z6R0~hU$w`zS)lzO#u=y-23>Vwt+y-p3=Jmzgu<jFwSeVH$Wfqpof|Lu*SSgbLYY5G z%k0$70vOa9iPC$HWg1wcI`zE8OW|w%Tn-v8GpOC5z30|tjQe@@J;LW?nZjT?tz0)i zgo!rJG0IfbdskJoMWZ~r%tdRIOcH<@wuNsBtk-ga_ND<xpY5@!te(6Lp8Zu*F@3qM zdlO$7KLn-}T!(8>#-`~QtTrZ)v{EfVJ)3D?J)@Oqr@08)@=3NL0smE@Ry0-XzJv;s zmYQHGt+SL`PJCdqp?mY-AuNNQQvbf5_Y_*|2p83*_>ReXTpO72+8<@jCIAF|?PIVl zGIo#KW<BQsl_>_?<uvIp;Fjn$7AeksGfM=}D?k5XB<xVY5vt5)U{>NVV4)`u)g7vx zbF{}n<6-}h!1TH@5ni~Mo5{e8hL)Z_0&^o{3SR&5ovW$dKQes8d$u9I*{f)qW4Crq zd-LU5y36N&;Y|r*TE{fJ5+#}N#(G)ye)oIRCeH`7RCDq1dvuS+F3aDaKPg2zxZS53 zEDsxR{m)PPf7>^wX%IF;<z4n5^~z5uHyDmc-WG(!==nR&>tpQLntAE^<;V3%p29Zb zWxmL6l}8J^Y5*{y<;}N+gwP1Z9?4y8c8T+am$@Z~aX~^Ok-AYtBAzy)%nf0CD@wlF z_pGA{0`mti*i-9wJ?3~uXha6>=EhRR<*MU0X|2nfM!NZC)#7;vHJ&|Fj+s%u0dh%; ze46%ym-5&(0>bptg_<Vz+i2-)*dq?9yA`n}o^Q*5^E#Jtu~p#h#Fkn7l7Sc}%Hae# z?6FB%0M$`I0j3wTrePA3PsmbEUVeoD5y)3p_(RC#QMgxi=sQbNzuxSzSz?{qW-!1) z5DBZdqPI3h>H~YQpGVFKh;8a<@5x2${h?~Z>}5Y;9doq!8M{|rDDe`g$s!Y`<B&Ws zPrKLZ)45sxv(=sm<4KZ!v(>cVMNY<(cx!~LA+nmDeO2jjT?DWXxHYCWPp;=53}?@+ zn6isP?UDQ?bH$KgT-6m~BLMxY<Zn07h(~FhaWNXcLHcZ)=<{Q3*-n!4hb|B(^ls_# zKIis#=e_aeh~Rj0oZtHMgYn0^@jv$Fw?-&L+$AnVe1EniFGaMojAVlxtgP0G;o5ED z#^#SVvv{S<fQWQBC#Vn;!U!26ev`kY0bmI-M9pkX+P}L#J$QOXx|qE3(1-S2Q}tOd zLN+hf{tN$35AYA8#O&MgfQtqvPtR%plcXFEo3YHgz}u7tpOsMZh1AYF>&u@x4<6>9 z)y8)|5=j1)yr_$tKa7;zrF`l>ujx=Inw&ga{>=iwwq+yCh6lz;Jv0L=Q!K=TiXX0X z9_JyEhyI7Y`z_|N8~^h&9=JXYKf)fFQuyqNpT<#D_~*PDN;HN0Xfn;Bb5bLZ-vxiM zNpF7d5NgCR-xStawL^hInzF9RtD&t9WjZH*G>3kjNijET|GgWaO?(@}L3JSGG1<tz zouwKPh1hBi`U<M+ggV(l@fDad!>?b3TX7KfD_<CBi11H`3+&Tu43qluP`$*~sgA>b zeoNkVcTle%&hk9A&dt9NDXe&`S?#xA-A{8b&96-$6Q%RM$iQ>7&p#&$KYmtr1X)E` z;TBA4tZ3X;yeT%n`^*CBn)0mV>c6#UPinM};3H%i)r&N#1><EloKhxZ*Ne>Izz6$G z_J#bmeFOj#dmPu~>IWst{Twy<;^%g*tp>koR98&y&-5-yh*V-YbRem9kT8@3DZ)fs zS!o&`Okzz18WIpr#8VQ|f)Y$KYf4zt6%qh)=!2{)CABgI_`u8TryS(gw;=A01~zkt zEKg=T4QmLNn8AWHrdMQ7uy>+cHCpXt`THEJ_2me8yg4?L-yXg4@wW{W=z{oc7WqBr z7IuftMDyXKeX<HzNYq*bOPq7z-7^%7aiMfP@>jd)593O1Owk&qcqkuw5dbVh`Yfy; zOoftihwul>8!SV_P;c7bT!CH*yw}INdb66VGRjbEevfa}pVPgW;T3KI%iL=XX>>^9 zv@QkSkCAB1Bs!(*Kk#SAkwanL>W-bjhUd)R*96>&zQ(#tW>M_$E$xzkAM*mY$lFXX zE?-B%!bhypRp>*fc`S@78a7$5ko<GZB#GkIp=+p{<*T(STB;9qmH<oOBpy$2ow0ql z!P4rwX~(+!AVOlu!=bvb`lqUerKY5nYM_52Wji;=``kXXVTpicY4RM%C~he4|HmwT zLpz8@TQ|1v@^7(?5sVtQlOla?5Z_y|%I#<CI}HSc<I441eqF{BDIk3sQ2(jbtB3mJ z^|m&WgVZHKlrbgf3cw)C@rCnDSJUl)vFNJLR<uYNL#j>oow!cw(WrdnJoDg>Fz;_r zt}iH*Z$zK9HfC*Q{T7RgBvy4eRt1Qv0bic~X8n_U4Y(I;QeGgQ)=CN;NI>D)iG$5j z?HQW$L%Vb>Gw{L*Dcv=_UCVxl#f#TVKp(n7@Wp7r6B&To@(_-m%pa*J;CuI2UUmuE zbvQy;Qr{1Ss1W2*KL0NF@0Z;^&<CRL%z}H}!m>lg>#$-n7wiUWN4PfL&0ZW3T19-5 zH-k3E#zb4L6A@wXt<#~71`z$xgxDjZgeg8W(Ww^SHZt)eW6s|>?5)y0FAg(&e!JAQ zU9kwU6?LtScnYlisHx&&c)Q0qG13Z;JwNxQ5Qh17z<9<c1uz&I{?RfK%lrA~evdOM z5-~gRpf5W;NuH9sL<u)XU-&T`&qZqbub-P~j}g`N4UwGOv8mSgE6KWM3*DUVUl>D{ z70R~7k9$ZCSf#Ebf}~I}iTHNtg`vthCD}ehI%TNX{F>`(s*2VRv|h_V*3c5`mX4B~ zfL}VLU(Dg$WoKN5m!O6PJV2686Y5*<UE`%4v!|t$DtwcLXFSI(&Hyr`=@Z-Nzk*}# z@_0n>XmR;~LU#oxI!zhR%=Qf`mb~BAcrp%U;{kIEHJJgq*)|e2FpJwRf`Dbo)6e4+ zgj)TjcST6Ooi$myoA{vPvxQx8VC7jQuXONb`25|Ae_P4e*TCyqe)=4I5dd&Xa07DW z-uP&wwiGqTN8cBJSUJ+?nr`wY`N|k5E`(;w6smu{{Y>3e88xq+Y3wsfiPJ~fn53Lu z1eIddt1O`fW$H`tNBLF&)!9u$O3tj35n^}OrG5Rga1+SzTi7oh%+hxvD)ML_-x1JL z4mGJPlUmLF7CkONs+LE)HM(B6780u<H=nux(2<>1xT<uEoVAZMSI`Mp1{U_tX&#Rz z#|W#`#g%`_<zt=*A>LTUIyOJti*j;V`4&_xS4gyNwm;xhy*GDw{GUCTa5t#`{?@Cf zEFkrXBSChct;d9;akDC@PyD%1Kid34;iSGWnb3AX4Iax7m;P1{z%anwgB4xF&eDnj z-T)AdP6c*)sZ>?4E$eH|`YS_xfDsVn=E=_AwYm9gn0-iz>wzj9I*60zD!q%k$Q}Dz zbqAWx3TJ@64tJy0`KxidC9L;%emqzLM@ml*MN=E_9Px`hQ&x(JZ<WJ(8{pPkDm=|f z&Xqufs4Z89<Z{cq!9#t*Y{Gb~h)lOsJnf=cMH#fTlG2k!5|jp?@+%LA!FC$yZ2;{# z#^NupAy^TaYa_?~DZs&4%q^X$V^{`uZ+S{guaVMrlJ{^+m8D|xC>5v0N&|Fvblu{^ z%pZz(^Pk}+Xb{e{rR3V(9iZF&85#PWyXq?XJ*PgmR!?w@S*FAxcCvBd7kLPX&1xmE z+mq#N$<{NCidh_tEHl}7nA0dx4OU~m*_-JG*C5}T&btU@?yz8G?Mtx7jE?dxHv{o~ z8p3+nN)oaHCwj%U{)i6d%xtT@61&gMsLXj!(w8dO>1B68=l|IEzgU2OIDzG_21vcg z4acGFDHgBcdet0-_|vMdS7vo#W3O@OxzaKdlo$}WpSJm=j2tY7!f@U^Nvntr>}Z#i z0(G~-ddStF-9l`)3hW*1t{<V!C}}Vp*L3+<HsQdKnt*(xW+5fUhChzvPO?65)x?@C z7p$TyyVCL4vhS~)mTZ<QJe>RI`dm>6Q%SSJ8Qwmp=t7|85KB!1*wC(y@DPDiTtkPN zdK!B>@xn~9*LpjRwGaRX(6W3+Ga6(fg#IN&ySjW!FyU2g($LvPyn46Kux^BYIw1P} z{L=FjnHlV5(p2Sd(xjoHWV4g~EYYtVdrJ>Ct3@I5*vwT3VRlw0tndj-ge@7lVKkG< zb^xC`R+P7bxE91V!br}%!h~u=CWV`!BN8aM4Gz-HACK7GzgYOSgD&@s)VXdj?SzPf zD$iGcU-PY<`*(nj3jJ_BZ?D$;fgIVAsb~9U{j;KLB^kCl&F7mUe&52#p3?>k57ILy zS)$gxEtU}q;}c*|W&}(l)Ub86SA~+#GCr2Q(c2p`__c>|fBim#%c8Sj!8v>IpI#50 zln5fXqrrh!=G(G>dM51WEQC|0psWVnct9{s@q>~pYif+?ROln2N%w;}cM(C#XLsdT zN+-kS50mZtj?V5mH&Z35gErpZ5IH(Ks`nMBzX17(H~K&C&f5D7X`Rg%kOb`bSmLL5 zr{ir)cbl)!!M?}6<aol<K#J`k?pGEgzJE1?0G;Pm&hw`oe$#@Tek`e}`AeSy?`H!& z$Hh;hZ||2L{VyXl{JOLIoda)wALKl2=Pc-VoRFbBg}p-o6?BhR{lx<3#+~QJ7pmLy z4{?uI1ecqyI_I~aGU2L^QPoLZpyLNxof^Z(_Z9ykSCJBSKojbOe*L4kji2uaDF;eS zn<e$z*{-+zV@=EAjT3%GO9_1lsPT#PujyoV1W0qlWR+ZvM+KqEU?1<guhR7LyN~Za z^EW-@EEYsNdQrJ_C~yJ^F|St1w@GsO=<(xx%P04dVgio8aWh6Ru5D<&W7m%Gi%L|$ zcsuAB9lljBy3<8WY4FlCXXu<rkVe?&df&RnWoOGfEq1k3zC}{zrPJ{F*6{h;9&;3( zif4qsMD9<*)KKoW;GM@^qPnK^8KmoNu)CG^CGj;MUUBCTgyl*bt>pd5*d6P?5bK)3 zGSd9x8udZkKP#x0xAF4Zg@U5n{uYn`nxg<J8~Jg^Na0-wF`04{F<hPgC<&kr_lBO} zTlm@fEU{7|L9EIc`!9YZ4=GaQcgi+OQ9p?&5@19igZTiRs!T+gMe=WNC=0z1K3ei2 zrFH}^wXEKQNNPtP{4>C?mj<qe6}{73Xu14D=+jnwh`dmw{2Af0CgUdofJcwDHCHS6 z%=jZe4gL4@SoCUnVn!G!6f9`gZCQ%c<<Ky2sp4PKrQltDraL2DuKV?ZSBNWCpB<N( z_C?CNtqL&>nw>UUY!jcY=U$S#8p#kfsO`yZ+quX4tHSA*+<YYL10|%!kfE(d_<^0c zWvHMs9`M?X$%c`boXBA>cs(@ng&r$)i5G}UykP2e%w#ZLA~4-J%u2k7wn?Xiqpx!7 zMwtl#noNLT@9vI}_qXOw(O>3``j?-GCgRm<L5cHT!KLZGdo1}(>`Poy4-zujCHOji z+Z5tyJ&fK(6KV=2P98E17vcHMlk-fl@Nwn8Y$g9#Oa47vD!_!ALoY4y-Cv$t9W;!6 z;IgFQK8j)h5Dp??Ad2}u8{@kzafFA;&=Nzh9C^I-QQdUOQ>rf!czt+HtRmW6iT#_X z!}@tXlugD^Vr~E^8DJZ!!&SRxUr}i;EuMGs+wt2Szg-jMu_9Sf@x&12011u0ax0JA z_EQWo>8fBVypV;jmD8S0IzcsF6s9`APx}4{bE%{2&MK{ozfMCNTZ4O*`2H0-1t{Uo zQ=&`1<vljyfP?w255Yl)r!}%F3cc8hly1(ZM)u<Z^b87KSY(_m06wQvKmXB>NpCn( z?ho06CmT%l=jeO7C31;iYf8&}BT<_uVb+sTwu5?~5_?vlE+dA#Z5gB(4uY{jwszyt z@`^$^tz0Ox%qT})9<%9`$UW$9B&CXL0xA3yxTEp0F2kv~hiA^p#<%*VJQs0Z^Yj9$ z3(Gkj5d^>ST~6XwTMraL<}6fEjUg=`Lck0*>Ulwi_D=DI1c>7`<5P_DPs>P<JQkCX zu3x{9npf5i8DKiZi-TD&i=%onbN<DLYzh<+m-AUQ8Jf(n*g%mN8JEMa8PicN$RU4M zKGD5Qre@#5Jjx^X26=I*M4LLc!VZZDH!_y)ij#a0i1(;(ME!5R_&(k)9WFjxG*J;M zTVYz{2M~NUzu(bEr%z~|vJZEq^FNyyhHLIT{sUPq-7a3@{>^`wruF&DPy(|VesD$? zFLDcNUF7xORyp7**XA(3x*iW))cLe{J@%nh563N?)@aF03v2*NE2Q>Z0flt-9wNJZ ztNeXl20WbRw7vSaf_K3Cc*6VV>CuwDdgs2b2MZ#XPiVl>#U2jwGRpj}8y*~ZQxdpC z>$}HalIZ7?{UQYt%CW&pm`qw4aN894`%mEG{p|f8A9>0@=F$JPU?M9<LBhUz3UNK4 z;oD*+c~m#kPThyYa1y(#qvpMS`>47?(d1QGm6oA9F@))cJD3oxQ+=57>_IbZ@}Twt zPNo0qFK@m5e2}K3LS^nyX3R)l<^+E_X`x<4MKvSZYd0CjsM3X{m4=euo)eD7zl3uJ zJ!i}!S#!(jj!NKgH~-ber(q)uv;r~@LK=)Oi;Vv?tfUE}x`jjUN2&N30;&pvBLLb! zT}|o`ud*^|dlLp`=i@4lj>Tt^clhQ%J;Tn;eg7koGWibDuU0o*vv^GYr-`gUf<Uxo zXW5z;rMNAGN2D8EAUkvW#&&AZd(bJ9W-Ov4m(oBczfjd&(`usa7yt7z4;ph9>XA3e zM^-`Em8bwtfDAM|*Vej!88s@1CjJekODztVyOGa%5Ep!lHZ{rEP9=yxavUDoo3@Si zF12c_d^<ZzOA|JTtZ!G?x6>(f`O^3!OW=_oQ2Pu}1RV_auOUF%MSiI$rl4>sg9hir zDq8D?P!hj{8}Q}Kj)rm9SujSqR$be^t{^Oh4p|D<z)CrKZ_r(YQ08>&0p`zv@m8`T z1D58bB7jOTvU-ez8B$B>&Z#^D_~iDdJ~nbUT}(Nj`Hef@)UX>SA+9bv#NG+Mufw7> ziy~+_DZ3E`i%eD@)jdh5_-K|<I)yoyt31VgEn_K1>k~>38R<stZ^dd8vss`WjL;=! zW1{>LqkJ}iTduMA=Q#s^u*Acvvr?Kz%f9Zc_i{i}pNuO=$vzat7*#<GUea?ykpK|D zvKV`%J(St;kIm$N9}AIaOtY;^sA-ekQj5iUh<8C9{o|V$bVvQ6V-x(E324H^kRb>R z79y03xLMZB^DcxiyO2TH6)Q=~L(EP3XZtkX{_OUlT6*vh51aqCt3W7g5W%wWIw&h3 z0tR50uTIT#(RN<jC9(hEtaO(kt3w!JiB^lSj0&IYmWE>jU#L+?mFM03lH-p&S*;^A z`>aQPUc!QN#OR6IL+&|d5A-3n<qYYGm(5S(A4BWIXMkY}lgU<7kVFkq^S@8abJ1OX z+(NvC?Il4hBMdbOfH=DBE$z~SuDyJ|(d7IqA=k`&-K-?5?f7=}9F6^+)|NfB))I)? z!bo{8dV!(%-dlENq`d<JeQj`$I3*yJXqeEj84Q)$F2{U6_m+t4FFw!kIhI#)9BRkV zANps_?TaGXJL&x9y>hw~Kk?w&Sa#+ZU8o*i65OV(5_w-N4vaD)i|k4<$U{N|p)p%d zRd|1KkJ5DNs;8%%L%KYQILgd88PeE_H<*ITdPY(~$!Ryma=o`AY1r1Bt50rX)cnc1 zZ*!-m?)xo28*C1zbF!zsJJI7U7AS7K3)K`&ebaX~zP=iLH<r+3xOnm4Tqs+{gXO`b zOU<t8f7!Mq1pkC&1cj86&Nrqb83Hdq1a5VlW*zQ*EJg@V-X}*U-H;>SVabTziSJ+f zBT0v?3VF^Oa_&d&&!2+hlV2sBAT~LWKQB8U{W*EB^aIcJ4<rt~$2&x40s{NJ3Len1 ziT=tDY5UaF(FNYQY7>hpkUzxx{s4jg`d@vgdl~vd!rc6BmAs}I8+dk>1vLzK!Xv+I zNq6G-JeXa!&dv*-m_MGI7jrSY<Yb?s3M|2r)oMUAcAYPi_els<>~ww=7Jl^`tD#sp z{8v2VC2s)X*Ub9X`)nOQ=&=aXvz_JfgPiJ*bouJAK)*c&t{JA{znYDVYlrIUm4xa8 zgYn~^wVdu!o^>Py74N`{?K0rS0I`WY#!fTnwPT+x2}Y0GK-{*$@JqF+3eo&<CCA8? zu^|1_ah-$_Hmdy6P-3=6!-lT97`Q4ZWWG5yr|B`GW*BjYhe}`<Zmj;qeUz;makT|5 zPdL9ogzuvuwRH!M7(|NT)QM%PSqtmQCeK<**^#!%o4XRX)YloN&&SySEkR;lXQ+2( z|8|MPT+2b)sh__G{_~c6ILnpg;${}h>}D!yzjUdci80V(DF^6@@nCU`^SLBF>nwGe z96?&LqNFolMe1r(C+t1t4;dS?mFYDl#FQ*xvyc_eI$+Z8JSdt+_5+ZmPPmMi@T!9P zBa}31`|Dk_Ne2YVp-2IMKxY7yp1be}B}vsLQ-M8gg^#^{X}`#H%6ho6>)Enl5BVB2 zQKG3oExzHpAckaI_)gz$3TFucjPb^H-zzusy+`{b2s;TnE)*8(;;*iyNM5eRKGKa( zQ+8Q&OvqmAHTcnp8LxZP4s}xEXo51LnThr{&@bio&{^YeB{sT$(4(*5UyJ^1PN@y$ z{RPPCnS8JGx#QRnV1fV)8ls|Pf(4c+1zLZS{vdx@io}di#+;^T3d0k07&nr_xn&|~ z4oGIj68i>S!LiAh4%`Byh9;rDlu%}v+xR1Sv0NCu4hghb&Yy7S<9%=7g=2a;rQFml zV@^hCkj#XLHAr^VJ;)b}{qs^aj>kV%Ig^dSavXW5e*E+Q^<zC5PcmxZmW02BKLGP5 z6{9#W%r`r}fWnBSW-QS%CnoVTeT&fZVJ4PLgm5i*5y%l{gJv7DSJ^A_b)+&4by9|( zhU9DXLOD_iF}0{*ZKwJe+`ir;frc7S4L%|Kp(_V#ll>O-H<60yvUKkPH#kg*JZ3b- zUgj>o9)=lYnNJbSubW?~Ht$j<YfUKjUP;G;-fSw|opCjGXS3Y6<m?Z^%No;t&N4w^ z81F6DRl`7_oAoGjR~pkDygBSjE|))?Oc0_gQp3!l9XVRKD(AAwao(L}pcd{g(==Kq zv}wTnHcGj;u!lm>QesprWs>-~2X|6|1%zY44WhdeVB$s1T)j(lj~)<UT71p}i!PWw zC!un}U9{$0MDvhaP}HH%jfGf;;Oo4=5Efde1-d)~w>j-H7N*kTQY#5?@GmaF&+^lH zOJ{n5-rsIs@=+nVI+Ze&u&!oAXQ%NoZQNclqi<hob~}C_C?-h$UaKfug4Ob6Db`0y zgKLh`uj)%UOkB55e_OWB9-%Z;*exGj5b`wN`gKnB7ZESJr%xgoh374Cl`VoP$5PuZ z+ZFp8pSL-H?f1gH9-(Y?TawAv<{!Gda3ZulLbmnGfWymL!`Jj|WqcAR1JBV*Ta7H0 zk<ZHx{LU-PJxkQH>S1qTh{V=doeI=$7}l3pPjq&1{d<YrffZL&1dNWu`TfcI)11*Z z!pMgW^M~F(LN<G>8`0It>J}Ab%7<@jH0WutmX1$!0dxL;W$6Sgo<NrsA+aO##UyY% z^fNrjXXbnlJLJRF{FES<5C@I>=E!IN<s2S^@i5YY!dw<1_T+^m*soU|$9O2Om1CgR z4`)yN=_&UrSJcYM(^f1t-CI<E38)G`E>PpyjK;5j<#yOO@!Zm{h2-SPT=0&g06M8H zy`RS7Q!C4fjb`=nd)=rpOt)nF>F(I-Z_0H_P|vz}<HEId&hs?^$Pcfkg*A>-hE8Mb zSudoucT^bi?oJ%bqcQ;og_bn01fXQW*EcWneks_INyMh-Jeb8spxCVYoxM7(M<<N* z04t{sUzi<t4V~i+WO>rh&`bUR2sbLSStYe+JGRjGQv2(8ig5~Y#N?VqL(7vU5ztfq zPEsvk^2Tr91Ml$jQotJB?f>vBPiBZ9?GJs`9s15e`eA4m)Z^2+N_?1U9uJ1n%Z^&c zk~u-S<$z!p7Zs#f&{N}fq3T^88e*$U&kT;d{ObBwNl(YnkTr<GMWf7(U~_%tZvrZt zAZdldj-o1BCT!027ZfSOym9=LuQs97n7M`zDF|jlS9ca#dd@Cw&Q(6%=L_&dHDOeS zTV;aipJ_U+G6g|UX5QyaQV7m%F%P0}yQFe)2w7N`)K)caR@ZSdX~;Z*Q?PbRT9Vi* zon6FA{0P}QiVL(kv>rlLH*pJtHR!^+(oZl0E@YGK!Z!=Gi`LAYXRB$Vj@Taq1=I8v zH<S}qS1Tu2o?o{!TAGp7ZLYg<fl1+<BS=kpzSA$HoXowt*5a)dU@Q{&Np~)ia__qv z!PjP}3o_f*!UhUTNiwZYLehVFFH?pyyLK1Ty8B{l<XBY|b5G!#7rx7H?+^L9tz+9s zPUk`dEzgK+L26~LSm@}xPxFFNUfQ3w4!hH%l}kz|Qv8B)v9bMhcWw8A`2Qz^;opt& zKWCQ~G<@NtuSeC$)ny_KNv;N<=-30svQ>bnCPkS_1S;sqAn8*#Q*`1dnuT^u{Me4_ zqJzCBi~E|2o;87q7@%*D_?s;Qp-_ANiVLU;Oy}s1%%~%xZ*E`7wnyQB$+WQ+>D}F6 z2T1hxn4<KWuraV{1MRv$Xo#>_J1{<<wHV>IDnu-!6mMMIJZ}mX(9N<BXVd5aDKl`h zef|`Pf8LYaLpRTrS7?HH+Z<}CLr{Di9&j{x&1+$jyf+r^nUB6sYDt&;jWaxk@TVnD z-EdeuqXoP~kkpIaoz~WG$R#c!Iem(xCXQYk09H}_{ViXnEeu4;gqQr1_wTEm*ZRO> zr3ICrAHsZ6hH@~d8?cYc7DMtR_KDmH<0hI67VwCv&H-)tE>yrw=r6_~2O{F`^#gZq zCCO>}-!F_x71*CKj?Kq0f-gN!EO&~xZdXrS_IqUGVS1VZW9%5_gg>K!`POFz-k6m_ zkAcT`dx}gqBVXyV{<cff`ZOkO-O6;x_NO_zE}R(nn@~S(6N$M%>f83rf`#$ckK2NW z%>~t9@olrifW8K%Haf5O)0d{U27lUl<~+zggkArg_kWjxWB^IGAICU<DV+MLA?0XM z)bwqG8$CQxEzV9<aZB><==%Y|!!m(tuZ@+}_o<AqN|L+3ic$hc5kpC)RdEG3rU%-O z+L&q}bN{1{bT2f2irU$MDoT5n;sWoiv{8wX(EBB}D>(=_$o6Yf)1QB^!M2B|YisD` zM7fhq`o(Cdev`#EO{33d$RZ4Qj5%P#_3=Jn{FLCSTYR~f_gNGCKKBEPl9uDGq;qD4 za&e?1|ByVF6LA4k?MK}>dM6spH#(NE@eH``Gjn<l$Mo?oezVUhm--w31?U{@e1WK1 zTA>lQLXYt1&4=kEel?_jk~12n)6~ij;x*lH9`idqc3^dDTus5132_tfMmeNpyqNB5 zdt1{ZEpi?I`d$)6jjZ^EXUkHhM%z{6^VO7Rr{FEn)`bB#i*ubQ(|h{&+Sc&}>33~d z%)hkDeb`Do24~m2*wxFhhXqVT%2D=z`pHoQ3CpC(54K|pU`>$Z)XYZ9c1xpJb^y!u zr*GA@)8iKKIr`A9%T{{1XXRgex*Sv{i#Gw*eX?#2|7DMyet~F(J%1LTvijhB>*j{9 zVGI32<786;&anIX@fhUBA#CHjNpU`LU8$II<Z^|8hZ?1F5!8+JCe3biY`);g)-40v zC2P;mJ*YT~(&1~L5|hVB<_1GKx)fI}4=Xt_Kup|y$-fY#L$&29Q<=CPJu(`(C*(|Q zcC~rKZ^%l>(S=>|Kth$28$c8=3+GfJ=ifk(mo1Z~hN!D9)GVYHA&68(>k0|$iYnq# zK+#ihnEb*%W*f?c;o-X3*BvN>CAFCj$TVVgWq|0ECQ`#cLBaaN0nYem-56jJW6r%g zioHAiUJ<>81YmvowAKLeoZZBiW@qL*fL#n0SJ<3SXpfAHTd+eoj*>biDEX5kKwlN{ zrmHe;RBM-*Q>;7>C@&*}bBpyg+l5~+5py2*;Z=VaO>jZxEj>Mzaw5c<>~_w{KE_?& zBx!vVn%MG%0Ou)a?CkDTl~l|*+apVmptUbT%_@j@tIcovIRncON8?W_K0{I_&xB#! zT;_-+ey-a=hPP3n7&i>Q|5KU%?^@_0gfnM(5!7q#=bQjuS4!bXu)t@o5iYtJmmHRE zj1iW7_!?A<iU<beRv#s(lr8W;V2sYCnRMy`RFVxh`VNdg64<qc*iZ4ov5>G25anHd zoNYpW)KIOca58M@uMGrI4Gdhv#PgZZehOd~6CqLD19F7uG!R=eZDwkPIgOZb#N*FA z8Nv{gX+*|(XuIftBsaDIoLYSv$9gd#5RAZpj1KJTB8V#ofVyx1Qf^2F&<1}bT$+Ny zVLhDe^tC2?ww5n@Ez+XrScS|bgRcLuy!}h)JQ7|3y7OQI^R;SiZV0hFl)^65FDPXr zcmR=EwxDN`fZW;vI^qoaFcmJvmZJkS@L`@U#EjC>5d{ZCuRBcrg8UUd4{rGPw>|Gc z>bi(D`fEPbZF?Qpb9C#Ii}xm98$Vj=paCGir(cHGMbeO;g_nUJKL>egG)t`60JHPb zemBzvt0=sC-(gbln0YH)MB4Ypn7#oOPBB}gh*USb6TYLKc>FeMetv%E6{Fg)MnIe2 zerlD<dr@Wm&dUV+iFUPs_hQUk2V8I1u}sSoFw~&P<*;9G$aC?h>nR4p_Vm*NXSQcs ze4F?KQ7Q}kGm`2s@bUJKwuZ#UKlH9}nR;1e+Rz@&H0qhxY}=ysma?Z}(Wp+bGay14 zKpa$Zc3pq#rK6}n&kBh|vaqUW*7E-Y_hmriAnY6{Y)HgSC#7aen9hk>xi+W|Q~Wvo z^vS($@%o2OC|4~i8umirzROSdf-Rftz4qHbhhJ^Z{$aU+1?d1!G&#TZ1jU6Csrz@F zzU=Xf^9?ptdxh{oSSD;<>VB;KiJ1{1oGl7DS|Pf(`FGRkI}18B37N_*{J|ttHhecH zOJ3Z`Eep_lhqffQvEtOD7DD)g>6HKjy0ps7XzBXvy7U`^<{zKJ+IAGR&#H3JUwBT2 za}1~L9$~!^Hu;c7#3^mo)h3~bLQS{#-qas+5bXtt2Z_3vPjKWPh3jx?BJb*t323=x zfFm7t&8e=ml_OM-eULJLN(ISn0y%6t4(k?7)W0HlA*B+ZtBD_ZH_>}jZ2xuEG-)BK zyardZcmWObkK#MGFb$tkk-f{mOaJ0dPlywb^@(#4d0_QFXdve>2nvJSeOi4Rb%lRX z{1J;QlwE%o`Z6(B1Phay!C(MaGs{g#r|`-)w>-G_J(L=?woIC@Zp9)I)g|dc{Ij$` zHx&4C#Y~^=1NoZ4ffGMERvflKA)7W9eJ=;WGSgVsgwS#VQl&PN1pJTz@G8CP2T3@r zmBxU`GL49z_kKA{e8%NoXzC7@W=+iAk27@?&c6XUz+$xrV_?MfXROR+qI;3(oXZ>$ zMDMs9g}*~B3Yq9Rm>@D>QSOQE2Te+>dDO7x*v3$<n`9Ynwi&?=4|$xz5q?&0ZMSwH zoKvV}t51m~4`wF`Z>lXe*3UX%xSf<hi3;VQ3@XT0iHQuoG&FE`wg00#`^p3pN0Od0 z&lmaXTgf-{y))%xCTPiOBL1F_JP)YQRU7$5D=c1aSa1A5m!_>!U<nvC^7GWjREfQ} zR?D4;zTPeaMlqQ)eLtP)S%po(1+S-LSuuVNDR-eWD)m%zQ~ot(LI8NeoOLuz0UU!U z#@<mIo~L$>lm#Pg#s}v$|J%p?lv(i>XIYBs6q3s-be;Dr+ON*bIbeH1DzupIQY+aY zI|uHperG|?e&-E7MN1T@F~Xn*C}VLk$0q};a9_y#$tX*(>#qC|%g1R7ayx)Gv!I-P zdBfJvW$+1x#W0~+6k<XW=J_=gyNcRrHIgABL@4$hv1{~Ei@PN`5xWAQaE8IGr-z-f z<gZsUQ4S#qDHUgkXpb~N+RiT)ZD*4I>W<8Te>ojJQk$)Ue8mQXZ_pkjG|?YI;TV~y z3p2IuyNONowL+xZQL7Ag5uPZCJx7OpwnLvN>=z;8mx<Z#LwpQpU&_ig%fR#Hp?+%7 zMZ0Vac2L-5wetOPVf(+?a7eV7xKo{+Th8u8EW^-|B~c-6Y#!2?>=6Wl^ECG3;XtSo z*<kqX_ivn~*XT)6JWp<f&sz0_M`=hMS1bk$G~+C*nBO%a4;}=yzrY+wM0&-G@EZDM z)E=rBTlDP=-!A{GAD3xf)#waNMBnex*CMTj1?UW<wwH&pQafhasHo-YGMNL<Ow4Nz zhZS4Za#A|Uo66dL&&BAt{iEWeJle*Px(d2IN$uRD4ZJ&k4unQFHY!Ui?!bHz924%0 zfJB}5sJZoCcPv{a?$bG>e%W5DSe!t%+7y6p=iLS`)~&Pp;0{a4nuaf#A!PihK6+6L zs;Q#6OblVpML+^TD%&knf*i;^sA2i~(&59w;jhD98>w4E6p9^Ri6s!q&WEC|CAJ{a z_R0B*zYM3TJ5RmY(M}zpqZp~d_^Be=ENnanC3kfkscU3mf5|92`=mhWZ%#nx8E8DZ z7CfOxLza0jE-U!ZJIK;_>5$m&pn4O$>oD`jFHBi@$=~~~TDj<JO~87Q2%^V<`N4{D z5{vnF5t}DsytD((m-L&0*oN>Un}SZ#eys|Y*7qSF&Q42nlYNYfViWUP1?oN~s#5iR zU}5Wxgr*melJ-OmI%YGHqFrZgtpjTP#8vM<vA-OJAKw0UeV}@NV!C+!@?Xkgp%zWt zWhYE+Q{#!n4K+r>HEXlWN=Bf#^t9maqRJ3=JFd4(vZGobr7MZaVM@dWVx>qQJuJx$ ze#n#<ct@=$5-or=mlWqAR)El2iBdFqNHaJ%(XDGp)L$Fs1RzP3q!Fr;HJJTDBixCR z7Wptw6EBY0BU3abr^NIFnc!G9LXCTrDeqT%&69nOcc+sVElVa}L~ps0jVdpiN0&D1 zR>=xdPw-=R-9d|n&h2=$a|7cn;)j7LzYs=-CDC-JrL|8}@LCe_)fk1=eVJcE=Qir) zrD4+e?@^!sAQC}cjy;RBk7gtxVQ}k;{raQBrADNaDK9_m%}iw*t-~P$GnT#a^C47A z3<tc;A+U>kBC5#ParC;<q-|RWaro=xhgussHyLbw2Gx_-%B$9m2dgjbOmrcds7lsZ zyPEIh(V%mjKic=)#-MxY2yo?o)`y9UD8LW5w5Srot9&K}Iver7?iG}>r(X*b0Xk0} z1D&Q6WiH@A`A7q_KEHRCCb$UkCVmi<G(}mfe4`&^6!u5g%2b)vY>#L))Oa}1$K5c4 zm6DY2^}Cm0-}Y|B9)olcG5%YMC(r+_Gyk5`(AKH#Ns#Y42VGH>db+>)mmK2p^>RKm znmFvwJ6|DZz;Mn4A(<+Z;hLg1UI5`CfVKi5eHcF<nQPEgxkv0X|6H5hm%<(pgTzEl zB`rNXz@Cnx1UU(bst8Nyi@bV|g28Z#kM7S9I*f~hzP{D?_AT98a5=ef7);u`EwQ-_ z+c58$yF?G`1c08%y0O9Hk36Aj@PRz^_kJryt)9v2E{V*vX9oa_pQZYqo~uPeUdzNX z@pk2jDui-Jhk}?C$&}?^q*LecI;pHn-X!hr<1L&mR99S%qlD*Eo9B8rgcA%GDBzO4 z(+cPYYl2*ALq%)ghNhO3I*om#AD#z<)7O<c_zX54RZ%&9z&n~Y`d*DN*3fLyWqO$+ zTeI84L;UuMV^=>esnf`2Fb<7*3wzv{tHt9-t5+zU8$4Jn5@QQihO$e#3hz+a9(|=R zBD~#}3e_bWN{frgay#h9++desAK=*8fYH4txQ|P1L_^i_`Tcuw^WksY!@y94|GvF> zgPRv9u(C2&(?55AZ?Abg@Zl7_niwu;eN%|<`FMZXHa)G2B3{;+;YiMF=8<!|u(c5Q zuoVcCsozx37JpZSLg#lRt=&MJyQ6m_C0_G;es-nfe#>_l1d_nQW&VMHYZhmIZANXM zZQ#}ANZuovQy!0n>)p_3YM)$k?ZEyK!R&wb->d7#y}SH#7%<y@J?i|GI6%~F{PG{J z<5H~iRP5j%x*3@qh)*7N!TaBpu!bIIfJQ@!t#ZmLd@XdBqz&*}#PW}Sz{HK10Mpnu zvQ!EDaPgG5wDFrGu%eu?W>~qC$UL*thA~{l8kzynQN90#r|%p`@fISkrI_$4KhFMT z!ZA=SdEJ@O^4l}dk?$7c8<D!P!dhR`AgZ%q+5DEX4~d_ZSM-$lH=3)bg$t>FO4UUU zMa)+82xPvR@K($;5~#`KU}eYFUK<O`NZ=gCni<os7KeQHx>^0{<n<sfo|~bs*FNKM z|KZ<Su4oM5)cp_fH^Oa^%PxTGIF;>^rnaw)vvG_6j5e1TAK|OLDl=lKotDp(3A97a zW~8KNFBM=G;S0etf-%P4gKiMo-02Nh_!NrF=cpmrU-*28Tr|`JOW7vaIrpQ#2m51X zd?C#@D`HMBeTgMpBr+s|@nV|kny+hR)YY~jZPL#xj}qtVJWc-0>#rTx-FPH=)GeUo zuOhZ`*&HEPa~K}D6c83VQqcWDEs+`puFWu6DtOa`z={4wY5@bqzr%%>;m?&Jr3nC5 zWD?ci_qvOK;vAR?90_^{%FQkgN?_KY2pSuub*PM<q=0%e^<kow%G`Pi>1wNHdJ(D8 zQsX$DNn<d#2v7Gz0%-+6wa7ADP%jIuVGf{%Lr8MT2xG)Vm+h?IV&P<ZVT4$<XWWg9 zu7tjACW<r#eA9ty;Rirk5{Rd30W3cAzgLaWtJMvAHnBY$UY8Oyl{!mmmk~dRW4<UV zoP6LMWM)R7=GVVTS{#?E2zi~gX6L}|P}{e_il9Ulv(9A)mVUu%ep?fDuMaBwn@$+k z{jr)PdO;atf_+j=PVnczd?hn_yqsb$S~AYbi^V*@xEGcjNtj`TQ;b2bo~k|BP;_|9 z)THwu({Gz_wy{urM;pN(AOUWwE*KRo-)Cbvn6B1(ul)`$nN^cyP4A*DL9%+LX({jn zS8gJikGcoJdGxN~4+)*gJ%x;HjPrt9-uIV9^6^yrgLH@gQOdaVb0Is=jpkE%pEP#6 zhn6elqe1-NQV-t^|GApFQSWG!+?9Cj8+OK9Z;LE_ZNcWBeLJ=x{itgNJp26oMoW9O z^S=1-D%v(lEa;aOc5|7EIN`GPt4kh$(N2DcT+1h{+(IV;$WQrKEMwtO65&|JPG1`~ z%6lXohqfArX#mi+YUpRoei=W!)q!;I22fx3s8=wYcDr;Z(QP(#tI4$Mnn*5^<-tSK z0finm0&m@WdE9T7>OKdM&}9P-n7{1k*PpQzKQ<sGR)oY3yjLbZ^5?h6tl~!MNz9gN zFM{~Mj7A}xJ)lw%w5yFGCAJ<5G^W@VkqAo7_ppU{rFSK-@*U{603JgveXenn%LuHR zsR&sJ*Ea2xt~zzTs$Jc}f-?cxqbi%Bdgco>TkvYq=PLV!8gLJ$i)qvoYt(q2V9X}H z=8-DG*SQ0g`&)J^;S4*iN(|A=WYRT$`u^{7klG!8o{V(1>;EW=%S5r}j(d3nCg6-9 zKWc}63~Nao$(CUn{yR^BPlw)dK*ja(``gaPwjhO(&RG<Xk7^G^2(QAq=TgVK539VD zkE5e%r<7>v#LMgN`v`^;9Tv&kyE4wP#Z5X<K!6>?6pktIqZy8HsevL~GC6<Bm;H+} z_s)@Ek_`Uc;$?@8HE+@K#SdA6mKwhePQ8o6_Cx=g+b0*FgMUuJx45B;vcO)wVb3!C zT;+S_e-#84L9H_zRXy*w6#xbJaw4DhNDQ*&m~VF?tw;7Hv!$4W{gq;il%x6J!cIGS zDXJnbdWh&6!eN^@KXk@?qc&oN4~}>u-!)e~OjXG&hm+2lR<Wn-CTh1fTYEDTb?&Y^ zFrH9~JPy(-np!J32+Mhvzec@RQA7pgLSF`V-#2gA$#SxWnA8^NV22QJ6l<1Q4N2bb zOU&u;NEY}EH#Z!x#IL(Uw9|E^19ld8$G?A++Ir?q4#LOQ!_YYUAMEX*8O=%j)T6&- z9pfJ+)=QZySMDQeu|d2RCp(Q+tYHg_tAC4rcw4am81d6eBE#za){r*b1B8eTKL5$p zv*q;ug7PL8K~uS$+cEUP@upj3x?6m?tW<f_miqw-;&mf_#=pz~OfVSbwE+a&2qWQN zUp$yNVrznNHLFTfxh~`oixqXvWq~~gH#X9ptHr=&cD>QvsYItfl38_LI3?q|9~!3W zA`moyWwHiPQ5Tpxz%ae44Uro3lIsTPXB`#^%S$53qEk;rm?<a{LzDn&q13w+zg_Pp zUviK$y!XTnAEdxMv2`6HVfzt-IH4JVF<pCqH{29O!^9~B%VWjPFHYgzn)YqC4T+Y( zq>R(Zfh7XarI>~~?f~614JG=5kVK)raLv3(pt%|c-fY31LiCf!!)jEB<PTjU4GOWC z$R&DPDl=8|8MSZWpP&r9*IJsAavP7t(C6SYv3!%|=I%yKuvN#!Nblbm|7JukcJ!h% z`H;l(clmI6Wsa9<y!^&la5-^&HT_iHjJFQj5M`)_^|!=Rcm$6w`+1eh8pdogI>{VQ zr%Rk%XT816p{b+1PV;T>Y_r;j$S*V;_Pno2=%JNb%(y-96x2tm#UpfMR8w^ooO`L; z`SFFOj&ts$gGVHWq{ZI!#wBl){?m<Y<YE_PCE33)UeO3?aI<wjnVST|Dj|Y)D)DKO z_O93TN~-?5zRaT8fe+p&Hrv$v?u}`FH)rpxlIYC5X81eW{hodlcs3F9@RtGJ<)myh zX8b|`J$=?eUba8*$Yb0y8UdP_nZboLf9p5O)imFFZC6!O^IP!gmjBAt`KLDX3gV>w z99ZqxF&n=A&YJn^`qK&7`Fuz1xceh1e4<;U@lZ~a@HM2UWW#T$|BL#WcGS!blAwK} zST^O>;c?Y>akm0r{f@nCGBO<gX!1vvDDskWgBBagJJR~Of{473ZFBQ}Qs}tGhHHvE zd4SaL6<dZW<!S+n4+rorq!wE#s^CZtS7^2QE!m^*Z(T!+8YEY?v=1hO+KX^qD0E&8 zAU7RR0tLWGZt#w$NuVyMTzjbw9C7-DbeIOkF6tZ&f~%j1yJe(5$7Zg}^axEUkHr1D z+Vw4+T;=4))c-^<_WPK^xXY=UNOomF)`tP#B9ky)M}fUPF)@Uh8LI@KKS?V`Vwqit z1(YALHwsSI|7hSuanfY4V=>2*$^V(Um)#*XPmY@85l&QaCcJ?IbS>&(#yilhRHn22 z36#V68X~%rym^;?*65U=^9e1HI7xDC@vX^Pnw$m>*sGFa@E|R=Lq%=UNbzfnsl&{$ zRZ3f|`2S(*Eu-3swlCfgLeb*x*5Y2=T}p8+6n805+yfMME0p5yF2#dWpm=eL6I_Ga zOYeR6|K3<1GDgnG7$+ajK6}qK*Ka<n99sl%{Q5c6ojnh?j-KG<+<x`F_{H%0j0SfY zSFiC%_5qZUv=+GLf7AM}rwgI)z8&CLQ4P4jYiVz{A5mkre`|bZVpw~$Wmj{7<DLzn z2Nn+xX}tiI%2;!iu3{N4^9A>`pVn9-?7nkq{_6R+^W04yvF=Q~GddP}eD<9I0+s)x z?=>+FJe>MLh1r^MaFps^TDqScqhJ2n!wnp|V;tR@e6m6J4}<(&riDzY>jp`&uuij$ zf0vuBa?|xDs<K@~osb`<>i*Bt)e+1Jejs{<@aBPbHksNL4><x-Sqm*K*2{G&*G#N} zx*n{Yiyf>#`Mb%rIt%Xo@%UYpE0h|?$Tmc+OJ*Iwe2#jc0sIhaSF(aZP-+ghyK3Y5 zI-Gu3`EEO-J^X@AO1^q_hz1yD{!@awe`*x!f@hXqc0nhfSyZj1ZjqwdOTM#iSF6V} z+tW`OG;(zn$x^rTCrrD=N=zva`4Cl*l(|^KAv~P66YSs49M1hFhsS8qIeC}aBb`Sn zg=3^;qHP1c`|Yb0*Ajl*i1U8y|NCDn+UQ>Ev!8fdsKyM|cWe+u`19NtgNWX3*KTeD zGo;Eb5io?z#=l$xn2~rPY9Gm|l2IUK7{DYnfn7_Lz{!XZ+;zO%oTPp!HKbduT;kO6 z9!=KBhA-oKU(EE+FqvVpOvns`7hd5S9!ngjDYDM+#$UQU&Dzpe7e-HY(=lQWeau>N z*IylR2fXt~{j8PahCeKPKtF`R$0C-27>6P<inhUa$!u!%dQOYL*__@K4Lq17HD!Jn z$<Q`|#3$A`6;m@T#V?H#cUqv65%mqAaucjkFhd3t!b<M^*8Gx{M80wlGg^}=O3$3D zN0u_dUy+*86bI;qi_M%*K7?^q%{A_exKr{qiPB61eUEqhqcvc`4qRqNIEUE`xHfHU zKCnCF)urRA6Yg3MqgQ>kVA-kE8pjJpI!sX|ccxO?>n%m`j$QS-3GVY_-+tBx2_Xn* ze|fPsltx2069|fwl#np$-L)rbi}7=N-{^U_DCY8;Q2*TY`}%Z+$8w+t(d+GEHLzji zVJ%aUCEx7RjQ_5ld(90#m0Jx{PGQH|&0-!XhO(`FKE<v4+i5OrxSgn>&DWfYQW1>J zrTDVz^gKwokSZ`q+YXQz4fSQ)L>IVPoCugpFEUG~98eNnxp{x3KM@aR{b$)-o$r2j zfBzNb?2Fb+Q?`)%$bY2strTT-Txs-Q|1bjxzrInEUM(Mc#FA8hA)63;IO;M>yq`CN zQ9W-^ecT87Uq=th>pvE)l}zKC=>_h;H?Eh-z}0R$_4$hw@BnXd9>8K)g0A5cx(f}t z+utEN%&WA{c_ZBl-XCuC9#S8txr9`Q{%;WU>`ODiDBthH-ND?h*yhF4^I@+j{>gJv z&9L_u#y{+rx@p2tV~VFsNUKoR2lTfTBAThQKQ!bxq_lOPUftgGSnOT&z~cc%bn*I> znF^q=??SGE0dj0ipM+lx%apYNjYV`d5Ri)B=2j<6c->(OskzWr+W-fXMygOqzC@G3 zVMPdEBbb;IG|@H_rqa`qz?x&gj<~5V)POHws+#Wyr-fQ}JMrOTwsL~)B#JR0B!k2^ zq!hFKiEXi8QdObcChzcXp9LXh5i@A)I@>{NC=9!J+}S4*LHW*#fq3|p^)y8yjATZ( zfWOm1V&NleHJYD0(ORuOCd5$Ztru&IZ5leLTfjo0RDD0I-Aq#PurV&X?A47p3D2cO zow42k_!jB%dNY|FA!-^lgl9G`IaeBV2<oz-cC>25Pw!pqxY=T+C@2NsLc`2nY;_ge zeYab05yd68_46aef^X{O!tVxC8SGW+f&S#L3m%DHt+PEvc_-WLQm&J+UCALUdTV;n zdOX+|n!v?S;N#Hl3{$q?icCNPb3A<ZrCv4H=PpWB{Hbw3TqFIDw>W&D@3jd>!Xvlb z$Lk{Eo@VK-QPtUV+7gtGt56QV9*GYZ6n{zp40acEf4a?Z6Zt;B4NLfv0De#vIsZD~ zo)NE9!nT#ZWB7E)G65Wgo}Q)d3;O(P4W0k$ep>Es?S6Ua_7s2Iz<PZ|kHjo`7<g?R zxckr7_(}3n-4B*^>Nf<?_%|A}`klubLyd8O4*?gcKWRE;P{;WGc}kv&_sg#h@7sz0 z9nEW4(a%)7gGz*M|88NVzS%@iEaJH7W&h-=RLR-fQk_JV<QupuhY_5T`pN}|x$(nh z>RW%tVm&!0U04v6;a6=5%^wc`EIhhDW(c4x)YOzD+=G<;afCZ4NZ9T9`$9#0vSPzu z#K?2GaLwY-wy-GfioNAFBQztcE>O|bQ{L-lADLhSdonY2zqPe(_Dc&Z?KBMb+c3KP zK@f|)O-z2!NS>Z8Y=w^r%zPlFFsqTR4);wd5E1b{CmHEFm6BsxKh`u$a7{J7_&*HT ziM~XI)_>OSj*o2MhQR~0=;P9#dZKn1T=`m*=%MhyU904Yu#Z}5(y{W8Z@?j*pdJi! z(mOiB=_bbU-qw*z8duWX3y@>_mNU2H>5uwZki3VCyuduB;&g7<iUzt4kL7l5T)lz{ zQ@2p06F^R*B%`LZK}yUtU`+D`9BIm_qXFJ0q`n>^7q{SY<y|Up8;magDbr)4w_%^M zE4>jJ`b?>$$|ZqU@hHJ9re+M&FRJm*!_0J0y4wMlC?(M&4aFm<d@!+{{h|X?&*p|2 z@R=9}72~ri-2d^j8)PaU9!zr6bNsWK!20#k{I|Mk6n?`V@jC8R6T+SrV<J?cBc{&# z8&3=F+}A6H4m=Zv6Zr}R>;r1&C|{y?2nCLr&R3}!hY7<AN-eR1T!oL>3pl7x$P~@Y zh%-)t1r8h3&WIydI|{RNr{aol_Yn9o4|Fa#S$WnST+3F4dWMuz1)073454HG>=W71 z-rVn_o!j!g4LV4NBRiw#2hxz^`c(XqetmPSHX2~Kw)M3AP7bk+!;OxELhjqzC8ZSg zzhSTE$oQR}48N~1^wBnxh!V1%$Zi_YWSg^}x(q@1a@X`f768>Fd=6IYMk=~QV^=V$ ztn=u2J->?3F1~%QTjW#@9=_#0TXB!qV1VHg)b;n`Tg%V+uIncGw}K#mFTA_yK%?i6 zDnWN$Ntjk7bG2X|uiq=V#^jP;7!I&7+h=a2$&DRUPm(err=XLFb=V{MTfa0~vA>9o zXPnyxf6@aRtzofFeg$u^-Co=Ri@1wGX(Oe6PvYGt&uh?^943{0C74L0X_fE8?N<D< zx0(6N>C1BsLMbTy^5$vBed5XCzp#wzTJT*Y`p{YKjMuG%@X6Wu@*|0s4uOa3!%M@> zuJJbu?bYW<VO7Do$<<O0ePh$eGc?R$Rudk^2<}pd*{`+TkL>PuA;Y=o7AYhIGDd2d zMR|Gc6>ZE=VXSliY*(Ior`7SX^6F~l^7irlxHj)CxlGfc$cWB~=c5TZDzUFY9$})K zG{dHYZ+%maR5YhIc>zMdBRaA%hVtUF|ArHkD}!Ya9|{Kq_4K$tc~Qwr;~C@!yPAEa z;M@9{xPRp=CX<VITq}g>T#ggQniTIWv%Lp{M~cubRAzi-`5{E+g@Zt~C${(aI+j!e z&uRgy_`0G77tpi$tW2$gwVLgtRP}MMvRV4UPW)&CgV;67LvP&IRq8=bOcm9R%U@P5 zqX#~PJ?NwfF=}{aUhonUBDO_`0-P&lKGMn%Q&-;=nyp3uArpI34DR<*owx^<ShU~@ zgs#*^k8mFA)W&|Xn5X-+K{NJ|clwcJB^Z}jQXf&Toms%-z<$9IJXL>4j#P|N=s)<U zD3*)+h%Ua*<#n+^Ce5sG=gT%eo7l;9PQmeQdwbN=qrcYX#DJl+>XgS+@#V{*gH@Ko z^Z3;`0^3geLZ7%1on?OTJrn-X*jWX*d3U%sM+$lOgPpidcWE*@_#>uKf;tSY-^Sf{ z=6b~&U;uDH7tJl5YzN`NMYD%Z$RI6S^xJ*0D^Wxhj5a-X$0Msl?Lb2diYBKyCnX+7 zM)waSN`Dwc=d^+kvWq|PQ%if41Jk=#24lMnaw;W&A3^mC#>aVD*=xAYae_I~cc<i@ z#_FJV|28Lam#S~A5Q`hbGq;M1+&+&qHam3NaG;*d6#v;}+AWgkhYQS48?HGIh9Dv| z;OzKw)!KcPz{|JO?tH3!9!2W^)cPGmkWlk5=`-ci+mO<}@jvrBiTQ0|()xx?P5_31 z8?C$tcB&&!hg?5!VEDTC@i$UvB@p)jcZ{>Ce#h1O_ycM!@hrzzc4f_Cs3w-N*im6| z^|Cx*z`_Z)+H}p3`fMeJNVQWPD!<!tb5T*q#(@2#r&^MSJ2Z!K#9{8L>^+pN^R@x! z;sD`QA5^2G=9n>vg=A~Y6xwEbj-4LK1AMRt^`d7V(5#U-#w#rUHRvWLDS?>}^frAb zKMBQA)(kF&2`={eAJ~|?HVIJiaHP@){kPibgWwF4_rWT^=}}iSan;U~^T{NzY1&zR z3T{cVFmkD_>t6KgI!sH-<oTF1`YZu17u--MV_7n*pgF(ED{aEqDMEeI2}K?*)kWmV zkpch3Vh;|)Bw&w|7Z3XGqR<dOf{N%f9o&g+rDrlBB;z*1XZi8XJ=-25zcyt~b-}zl zsMa{kE$q@1C0e7#pDVLs6H<Eg^*n-6n7GJFfMVFYC%983LMi6x^STHMFi+2<s`mEp zdOFU?KEC&uP6S(8<a>Pt*2C9wh0|nV?t}*+%ZWax!QXQ8)1_3abo4jVClc*Lx#pC9 z*DkSz`>ScHH(5!38e}?T({xI;xfIGRi`jE^h)e&RAArwmItxqTbGOmj_x$y2gL?~) zmm!JnU%V&X7aB2j?@&UEBZRd*a8ccW6)vo@%p>M79h=1lk>axYK5397Fc-p1SSlY^ z-Vm1ru)BG3FYVmJ-Dq_mP_djgQEfdVq8MlFdp--Pb$r87*r|K|4aKQt9w)(nLaCSH z16I22iC7|0e3zz_jx=ewou9#Cs-*m0Vg(*Hqc93@*ZZ<u^i=Z0#Ip_${ex{~>+mUU zIeNMAZ7&gRx`IUH)F@%Ri=?|?@6sB`xOM#$Sb_dLZb0NXKR<7ta<vJxbE!nXE5>rJ z)3*L<LJ*Hvx#|6U?GWw%P4K<-u+!t9Q>I?Y)>NLEZ*aCnId^BM1UG*cX57Gl*-$A@ zZ-g5KPv9AHAa=n5Qnvs7Oir8$3xZ%h&i5#DZ-o2BIEJj3qc`i1$?D1_K?ALMGb1Sk za@zl3VzDYf3hQ8`m;L1z{P^TDNbCL;5q(hz<e|_0=;rwN_-w@3*RtcJ#r$MYZbn=Y z_}}t00*YHshG(#3yjy3GklE|>hh37_YGPzS-l~<5qPUsSbgFO;&knd{VS;17%4oSz zzFU+d-|`0542H{aU3T%H30il{K^9`$1`kHa=RpynX!r<vn!((~oU*?yGNt6T12cMN zg?C)@RVX!g>!{joWSBc&MZ=2Q4xd4jxVCMhWZ(paD&4Twa@nPBQWLt!h$E9YHj9r@ zxE^^Jy3z~wh`9_gZyZ=4x)cl5wTYmwv3s6kefyKPvd+oS(w2fdyh*9HuHDvJ)iX(| zWh)VGZ9xg{gD{Dx`??)IMOg%}Rm@+$9!j1vPel4zB}4<<^%*bZFqUa|eoBDA6?L;J zbl3$imdY`RmH1&bSpO#|`2_rq*pLuS{UoNUxwmGjBDc=XLil!{T<HE_>UlSCia9dS zHNseAO!b(TC64SGV_ue1tI>B8>PLl`N!52c)gJUA-Id)a(Z(2V-m@0B#XGP51k+@^ zeo0s-!S--5jOP9sy{b;+`|G_Ct{8m#EZ;7@J0a^kaM#h)M9@R|N(P_0L`s#&?TvZm znDP=J^aHJl3}@6sBl7H%in&w$v-o|1c$7)0r5y7yDTyz}&jeGYPg<Rtq8gAWpRzI0 zVx{?APBq!(-hfPPw_V5VWs&9|%KT4VH=P_En*=oxe6cyr$s?D>z%=$xQI~72#hvv6 zFHJj^l&+sqOOr+Qf2_4SI461>(k7_c-nGjnq(H=5%sxOKWFLO{FTG!a72mr)FFl(r z*CoMJlO+CAkV`$FzReboO`niu)ZzZr!&V`}4_j%lHbHGF%cgZI`44*NLd-jVD`fSj z&*m@hOyx-n&__SJ8*=;cfs1`=m9Gx5a20|-k98R^gA=}R`@r!nmVsnQ!xg0%St2*0 zBTKdS51L5!p?qW@Z>{$W+V|Wlv*UG%XYMc>KsxRU72<ZZQe%t7)3N61=55p(kysP= zm+4w)A4ve?BTHt>autBw0ChDM_>R|jw1uic41rrUck8q9U^8U?3q9p`cO=z63HGVi zPU?2(qVEIWqSF7TR$@R=46xtiKjIW|6>z<(-%AY45_l1E@B}G5S81RU2c?P=Mrbn= zr7Zs-soC=i(yS*v7L_aHWz}G{DB#;-0%WO<{OpLIW_D&SzCje&0h$&OT~KIT;Lv39 z3L1M9rn(b;kMznG!PG&v=qh`@#RQ@hC3aQ7l8RZyMb?Qmlybk%t0#><$`wZrWAoZH zO2w!6o0DTU9WdLTMbJ~{3&L)C?f_jjod5_ifQ1%|nN_8(p32JiA+Nvp3!;e}5wsT{ zo(p98T2ZYG{dm|8z@ohdBkRqf)w5wIpE3QJiWG33Y|@x1whf3^iuj2tU>A#h$sj0K zJV%bCx2_eo!VGIy8SgkqkNB+9@TUGiYG=s^!8w#GVZ&zl?n7*AFptEWHFiswusIIj z&nbRCa$>2h+K&cra0A4dZ^H{hYT-dViKwQp<$v?JH4LviL(vk?r2`Njznn;tW3ZT# zsS$VK%ssg5rkJqj4`eC6aR7u>R4L577bkZmZ+Qg_+#;?GbfJ3q@P{}HotSu$E7vZ7 z9QiV^PH<?U<J&kt1;%rAvSj4LhH)bm;7=%`giW(+%WuRBNO(5uCjqgzdwk(*&HEuF zm4dQWkQgep3yrpO0(!1B0nvMRpjRBLvT71^mC-H>R|pg{+gwenq%98~8adbwt}<ci z`53I_Fd8xUZ<yKC-kv46b=>a3Du4x|Ckwi_g$lgH0$#?f#qh*l?k41QXlAU4J00n( zpSU$vY@CUbj|)slvv`Dfk+Q#+|E}|jQAyM~5k(!wrxs2J2o_YXoyU3h4+LZ(0QW!> zf>>_U_!1HnXs7VA_2$!hKuv?|-@Dw)9`93;hwg4zpp#(dNGC(tYymVF<VQ#_&h~KV z%Gx#Rly@mB+T!!f_Krlg?$a7c*ZY;n&r~4lHgBiw-LID-{Lz?ay{MF)tdHhdV#)qG z25CcCI?!I*^NIY}%?{Z+e1GH0o9~aG5rqbie{8Wzq{(&$Ea@H^R<`V!PqmndP?~?5 zv-SVXkfocMBX){|nrkh+Pg)KLpHx!JLYA&7BbziI<>Yn2kxuhA*tj7b=}@1;lTg(r z&VB{Z8C>BHE@77OZP=usYh)0m&C3$!WRd-*e}t=_GyIuI?qGpjH)x+}h_c-DG{(lf zxRZ}~aM&(}*(>iS|Dr1I?E6}Tz{4hL4_XUA(WB53&jM&&s~r&~lA35*^PT;I2qgZ# z5IYlcQN9Hr#${E@a)zAB{g~jk<FB>M(mLR(Me<mZmVj|Ce5+d|g>rlyxqo}q5~Jv= zalq8Pdt?TmDAph`#1u_;cvx`xTJn|VsJTH!_#L~L%o@rgL}d#Euzn2z7Ty|o12~DG ziCr9rfXDmARJB$GCD&lb-J1xajZ)59t0CAvxL(1vTG}+YvqgoBV*4|x_(Y_~%9L#z z$?n19Ip*)UR`)zWqU2k|`U#dE&XJ6F)Z2h<(le4Cwa4P8`r6-;8Yd-~hmk~Er8&el z>HV|vKO8Q<#V>ITehC*hACYN$oM9KsL2@ZQ%z;%l2Z|XZ;o2s9Jv|zAAU}T24UG|8 zq7$2W`ZSq%-w9?~Q++<x$!rn$ygX-h*0N(=GqD#OJCd>&x|*x9Uk7ZAy6@awyM1u_ z>g0cDz-7|+a9g+;;69<-egB8+^$%9P>-p2*zQ<3wF<a>q=gD@857}6_XZM7c6WwXh z9P`;h9FZ%yuTEr1XyDOhJd;lzWP+M7UF=y+a5=2%=%ESlQPp>7nAF{j`~O}F{&Wa4 z9lsZIC-43ksOWs6UEc@z$^f+V<@?G)c`@}o3Yz`5YwA8C=(2ztUvc(gE^de!sep?~ z!Nu%@j<hz+b_3Rb0`2-(nVl&CwFrFEnCnc7jr5&D?0YU7q+M@=Cu?R#<1uX})0n%; zf&%jKwoPha8pJVTZ_^8b!v{Z0o+V9`s1esixSva>XT%h0D19oO0GW2!)T&Cj7S5Q( zOw*SGT07YhnaXR=+wpz3BdTYi>zRUfoYzP+C!P!o0=Eg9ZGE<}MNj|REVXC?tM!VK z=o9GAI#L04bBVo0^-;Tr<*0{Cj6Q^f#7-ofyfrjJfO<@mQrz;L#yBG-GR8E){Y`Fy zF=lEel9I4+84)V%;|{6WOId&pJiD&WrPB6ZFDP6Ltz4%^v6WR%EL$%kY=C*#$D(I3 zWlIYy98j*;=w`z^s3Gk_nH#SHV0x_&KEk=0WB}$0@>o*4Cxh7;ItMIzr$qrA>zu@( zs1Qac3C3jNNKVW1dYl=nF+Jz3>4~4%%(dZuGh2QBnOF~;W*gLiO6?RTI7ir=x1iLW z6k?a!Ff2s3Do1kMIliD%o#^`m>5kn$TMkT$31@4#&melZU;~6MF2=N;U36>!S`qYW zrXHgvHO$`+5^&JRM)bVcHfkdP?*?n?rX|MhubP~j0z(zfB^Rhp!d!n}=6yu&XqHRy zinNJ&vIjt}sV9RS(F%$_>B|PyeEl=`jy$2)$wELtf@^*rO99&ADu7OrKl8!GhV1zT zwizI)C4>u7yW1a3e|)g%re=~fH|ir6-DpY~KvSD_0r5kJT$`#R$|pEHdtreH;jP99 ztG|Pj=~?>R`1BefCsxSSPNPU_lA870zsg~7X8{+yuWuX~ofj-O&MYSzc-@fm`XB3u zqKA=vy4{%>APXKTh!-OjgXLij$odgkA!YbI9U8|2-eh61F9;2}6QkJO&BK3jdk1$R zJaC~s&_nz6pY)$UAlCzkNP--k>I6O$1xGU)WRb|FkX==66`JH+_+^o^qaEtu$P^jI zFnTIK@8*BuB3zESxiK@}KlPOYjynaZjtgq)dO0GA-K`#0NJ~O{=jL=`Mm%y{XSP63 zg7Tez$6aooF2!G5GZ~!xw#W)*dY{VR$4FaL0c6WVR~14Py&TJ6b^q);LuRFOzpEMR z><LL|Qy`ISZR|~0k;%HjW2eB0N1zbqDN|2z7t2wIf8C3iCQ|VM#?4csz}trn*SSlJ zS1?hU(VPkB&XymcLCw%TWi9)mNGz{~OU$)wLZRbr$IW;34h|8OMT44C$p<?sKP>$E zOHo?Q$a%$ZAB1(b08fk;_&wrDW(fU>))EA!SIt|;Ll>Io9qPQQ{qZUtmMNi4t8Z=A z17Dzlbb5<IL=Wv=0rwA-$pFb$Y7@O>{h~_}9AQl5Ahi)Kk9w}y^sOg$R*hOYl1VPe z#^m%q;66agr<3{Kxhgg*=(VYkxTb+}1wa6xqK&k=UYHPR2RO4GuA4LdW{LJASH+*_ z@24V!hdMl^S2oxyCO_~Tux9cmY6&nNC#`-d#2V2$Bc<S9qfZcIBBs73R^j95wL(%u zY%YD}P!B<n#%i+f_sjmK5N#x=N?~v6sP&qozj|2sM_%)}+AzOK<N@T>pQ26;4lRB@ z>8-pfH8Cte`R|VSW|Rq*d*E!e3x6+6aS*3E%+!|X48LraM*~*%`Rj%dbe%P!OxHcI zUr^Vi=%Z;&l_YNr%E!_wV8Egwx}^j2nJEL|xphV1g*~|j|MdWX<OHT<8Ul&-^5^`O z?Grp+5wYf&@>1YPqZaksB$NC{4Dw*6KZ*0l#za%RXx5$qK?V}3Irr)3h3yrc*ueXV z4#Xkj(<Q3W;Gg!djn@+H=VRSCa|%W$rt=Bc=)^cyzeP{Dw=|5zF0}-#*?%6jtP5B> zrnL7TJ#E;z6p`~C^-Mh#nz$6LbvR|sN$V1Sp?5C0gGOYj*2}IctE-+H>CM`Hn-D$> z_$bEbuL|EP|0^%b_W*zGb{Vx9l<p7+^`GSuf@^owpTxWtSwd(SAH`HUal@5BDtYtk zF#<YmPi2}M$x>vmJ{=KOCctA|LQH|nGY!BGi>7}AxzxV8I3Q;FluA3JcV(5Hn5FGl zydTy_j_E>*H)f2}pVXqbu3(p>yFM%ArlM7DzpWTMa#;l+sr(X=ec)&PDFe(csg3k+ zsKZsNfTENJ{qhW&crR?>cN{s)$4h1s%}Nk1ES3GR;8tv<TB-RSsAQZBN`lu(0;t+} zqf<Jz(C*(CC;6b+v)M{k<~sQ|#alCFeNI3<Zo*A-v|vpl-)NqR+rsC{UsA&8U|<4R zpw<hkZx&;Y38Jy>3RXlTWFkD9%2?z;i3*hllWioh1y?SJAG?yXY_aA`-}Sc%cPD<~ z;HZoEv~xQ}sSW`*ykg9JsQo~K<X8AvH%_m_urj9>Qaxi5L1!)z_)&%$+C)IxD8JVz zgN}V&;bTZQAVb9&hbg&lz|hGqhgdeM7h0pEo|TGY{smzbMi9j#ACyJ0o)A7@ha<r2 z+LPs45u^B)MW@#Joe2iE;^%&M)LiX`cdzYbq?{0yNWT|jS#~iyADD1xy%If;-{7+Y zwk_h^L41jsV@XwT>0Sc}Q_E$15v4T67MYo_U9`Y9o4y+#sF~B&gbU>ydM=uT7Z3!R zu)vi>sgD|X$SP%Im{<x03br+wSjQJ~jLBgIagRF7JI(}1ewB%mx+`HY^E4O{Sw(QK zP1@G(H-$>G4XPX0RS~8>{Sa2#_17dru=wC2nKJd9l#j(9-~BNSbIB#&g)j%OkY2ly z_2MKs&G#Om?xqdZ9eyUHdmJS~SdFDvUUj(JZjKtbuBxiq^O{p+b)Ehv$ltD(PWMi= z8-`HRkrcWq{J1zut?{!2(d}bA_cMs$NNUK5y}vpQt{OM$(G*Iq(tS%+P|#_t9AcFL zp<T@y-y{=c{`R2h*U}yLevbN(U^`#hwC?BnLq1U_BM?4vxaY@LuOr_Thf*+YnaceX z?%yIVS1_7y>Gl(PxG?A9*v3ck688(`6H%>Z>kU0MwPZrb<;KI>OTbEIe6SKIy8~9l z@fu6~(b<g<rI#~!ZR;7{Fn((kIP*@Z1!Od=d<D1b@*6n?xe1k{g{QgF{SNXyy%(qQ z*>0E?d~|xbc5-3Sdv*)E!?<bPdBpEOxYXg?ql*{8g?6}3uZfIX8=V`rU?O}Ka<jy8 z{=2rnDas)5#~KQ!Ft?&;!CDVpmjYV7vAW=PU`!z12g$}g48oM{2dVYy9Zxtsg)Sr$ zQfg*+(vk(oIKN;7-lYYSobKp$9%yf1!HJ?<4Qmgu%FWBwn`QWD*RNYl9;Bptmhnpx z6Cc$zt{UIfL$iCG9;K1RoA}e|e>iBa*E3F?Pc6O6f1jJZK1DrycTEtkX~F?3zGkEh z6YeiV;?L)|)i4UC3h61UsWr35wcA1d{1`u7!D~dA-dbMsMr-l7eu{z1v;WI?njonc zq1gL|TYq)EY$Ei)Ojvt21r?9@#ZjQ3o^Md7H!U75V>u?EJnT28Sh_s3s%aQ-8dQto z8!|i3{ocK`JA7}5TkHF#%DSdGlGU4F{K36GHTqD~iXPwB*KYy3bVpxNwVLQ8<fM;M z`8?DjwJ;Zd27iXfkOfmJ6-rH*%48u#kzPKOiWS(yu)s6eoYOy*&_sf1naO^unO5&C z0thQ|zK$pfYX$7Spf4hdhtLH9l3oLhsFRA5D>lxS!0XAPM?ZLhZ^jeFY6v{)ezZC2 zsf@p{mi1E6b{fOotEUq~wIxe{ALHRmg0ND|wvy{*DFjRcoeZq6gWG7$cm{DXh1)w% z!nRH33DK1uQ?~gwclK1xEB31lecPmb#Me@nQe@L+4rq??nrJPmjmA0%pD7@7!nFCm zFW6uvl~6uOnkD`rSu#Wu?MC>U8BHRk*@B8JW#YEQgvp`NrroB%ONChx$e;j$Hl1$3 z0}y1FOTCO)sSbn(s{kKzJXS8e_kEKfJc$w+x)U-V_ZR*vc|Yg^?^WQ7zC~4g{OLv` zx0E#>lF<wxVFr^s7t}2a`j9x+&RMOMV}Hx0gJ5%Yju?XymBaG^HKSMeVl1Y1=3_kM z4dMjW8P2h}@1pKdn&&Tv-ZoE|aHfFFLO@**DXd6i$s9$9T15yOLe#TfuR#Jb7PJcA zJQK2?b3ej>13${pJI5QvePT!-%I|}brLAMKJV?Ho&Lrxc<tSgb^qtko_rlajE0XWI z*L){u2AM*6gY_FjH2#S`|LSK0u7EG^{djYh-M^RH&)>Ka=wqy7L-~U~+bi%8W<8Wa z#T}`?4a_!n-&5AXsb>HQaXTXv{GYENw8SYFGyn#@Z0>}|?P&9myJ31~OTwZD8349^ zR++%491@uZW1c&v`WO9vIrLWvviEhbq89)@7YI=`>v`;>hjN%QJ08^{QBAlD$-2%G ze9ML3sCPioYX$6M7(h20PYtU5OBCsR;8L=Gt)7f~GOe@M0+#l{4`THgDT5wVjk3Ij z>5!i^460*lDencKhI%=rw->&7C8QM9tRG5nH1~M-5OKCoSa7aP4lw9|I`Z51a!e;n z41~L6joN)>#4}RV@3}0#F~y}&#(lTamXBt7OyE&pOK68bO*QTU)wEliLKCWV>%GI* zd^q&>rcSldc{2mxg+7xWoTMH;-bDnAKd)o2?eso%NA!Wzupy-fg@i(kYit_#i=R#T z1z?C5Ht<HunSrve%k12Fxa;tT4ceI%Z)#4oJE+W7+rVj}?Ey-u^}x9JwwImh-@!P& z8>X<nGN@R5;AAVSfWja}5Z%Ao2+A&$%M=w|&5SG^w=bhf=MY3JnS{jsJGaM!ej*E6 zx(Q5mL7@jWmvOGs7_+9oSbpn>Q<CeMa~rG?1MdPp3t_sUtQ_Q5K~Xtx4|KapDWK8j z5jO-Xm;75P{7B(o1US*YI{-fV-;Ylcx7zqh=#-*FB82u$&=ym=Mi*wt+}iW;nw(BD z+^=(m^D~$A-|B9n)n$T<@H#lNW0!lvOVrXU@mv`@<Wj{sbAIhKxP%qR)G;{j8lU=W z@L@pc8O|6L9e3{u2o~vJ0M)D<Ja*m`run#WC_n@mO0~a<Ba{;6SdZK1r~~!>G5f;c zuQh=Zq}^LqLYpm@?%U{YVgz2XQNI)K?DaRADTLJX!lEh<8~$(3ez^>*?1{p`gpJjI zt^XFp18y&79yBNI&RLrSgDWlK8!%5T5h}#Gk!sk3V;+AuR^&XxO14A~MQuM_Z^#Wm zyK+r-puS_C-zqicPK7lG8P+&P#>MVC67GI^nw%@2d&Xb30P%HT#<fdnU9h6t>`$P2 z)T4h+YN8;^W?O@6&vX6FpKzgz$j|eB)jTC<;3mn$7@_BLbJj=_XcEV4!}WQ7%G2|H z`5d1q&eu-YB`iF{-Qf3Xyw}LoO-ZA7pjdfd!DC<m?lr2has|Jn?z~vgVp&})<<bHd zvNLn+6g2$!D*dffZ>LZu&TwrTq5G9IP}0sE61KNO2UfVcLeHzi0-<tFftGXU>HmDX zBxicQu2ToROop?wY9`}ck1Qc_F61wp@Hfr#Ig3rkKdPTSemKf<&6{7s2!EYkS<9kS zam@KO)J~yxB%;~Qz9Mhrjwe5jfi75Tj$djA$aJ9)&VPJer$3mtBbWWz@#`;qC;(E) zt*p)!7xIL>;KmZeL&g0nVC|G5?D?-L=(Bd+6K(A;o5P~~l%Not4&Why2T~qczrV@* zsctUnI@Iftb4y_%Pnot(hMStnHP%Y)`TcH(mK7}3n&-B;0|Co99cI%9lQCD^W>X2> z`RhSbg7Dx(=o<vjm768nDfWGPYda^i1|M;|7nk3S?>AjMj$Sz$%?H7Dx>CRsb~&q` z;pfKxe~B>AgR@y6jP)uw!$DV3j`y(a_ET)NS)oczOA2-d#-aKTva8=4x9_tEqt}E$ ziAOuHgNNp&Yn~-A#(p~HGLco7xp0QjJmbw=Wff2nJo4SK!vz`#^R;X%moeY6U-Mt@ z%IzR%F&Yd6zk^7E_f&_~xXq8va0H6#EY?v-gv`oMcpQ=MBOSltWF}Q*p;u`7akOjt z@mw~AFp((8r3IpR*&z#`evPi1Y{fBO4e3;xD|@wG#q;Bn0LRmJxE}Mbob7QHAY}&0 z20|=~>wlBMACsdW270@d(&EUw<U>bJaVozwb9=ZV7RX5=2NyGXr-izGPjMJ+^ySHv zDl{EsU{~ssL2B2S1Kq+~RO!?4p?*J&1qu0ROz!nVQoI2fU+@)rRO%=JVvBE;ya;Lu z+_&4~^U}YNqp!+;j$iPI<g<spLCoYOfCPOp1UkQy*3aGq*PJcm&z0dgzzJYkF5)RB z=_8mCpqe>nQV4Tsu;UNQxT;M?#<T>SizG#~Q!<>tGQ?`uDk=5bicv{@ciY6{f7Eq) zIfs|jelB4Vcm%Le)ycHpu4N}2l3kZNk~5ODHj~L%YTY(=o8$we)M;|~1nJw%bF>13 z_*ewglwht=`Psjizh2Uf{KaI&ulZ6iO&x5(24^ZO&of-kS~>EhVEJNuJ0>p`;0m!2 zNjEeENk<F<^2?6+=Oa*p-0e;Q-zw-#(zUTjj!vEh9M)4xH^>F!9_LW=k`b(>RkYgX z#byvpx0`L*L8^CT7x6<(ocBcUr;ayWyKe3SC+?5$=}v2$)Z=Klb>KR>Qv{}!{yVpW z3^RqH<1a3h_dk@$xWDa@e{)xKjvD{`T_p;J6Aksr9wRR9NIowEq;<f}#&M9UAxd9X zpoUwv?@@k(S6%jxyLLdGgQKIYwTDaG)xknDl~9i(-Cuj+U7^lsjiVdi^Y;Dk<Q3!3 z|7<fc)GU&y+|~pbRMBw{`PDFQCk4pio*&`**|=vH4^S}^i+gmK@^XhGP_Wp{O)R5J z1Dc9qXXJ^10#pV0qby;|&?p=%uRVLLbYOwNUNJX`fC_8G=XQSjZ$2(H3J`DRQmt3? z&`eh?IguRwdS<w|x)yb*HCY140M610C{gDyZGo0<{Vo<6Y-X*$SIPntm1P;yM|`(c z2v6TUy!>Mdo`2f%hx>&(-f(xJO2Im5Y?RbeZi*nYY}p&Z`BCqb1}^m&8ggmkZ?fnj zO1++$If7JyF@6kLFiK(j%&s2}x)NzeQFEuhzn)}&%sKf!pFF6Z)ph%3LilnBvYooo z7MD17*m+Wh2*8z_tC}&tPfI?bAJ(!4*FBzGj(FBzT-~!6Kzpm|`9DH0%PqzniPk^m zUOT03ZE6aa+@6S$sBSr(<TxFa+w5Eltqh$$^S6YH41RI*dlVAi^$L8n4H$nEaPyxw zwl4Z*IB)%&HsKUDH;#Zai@j057oe~digpttk%gFUZ~udn_frQprXD_3OtW;uC#<L6 zu6gjRcsXim9g6S(YeGyN1fIMGCtLt<4rQ0Y&4swcDplXh<VsTG4gpdQ(Q`fNnQUt# zV+N#unSQTqU;Dtv-|KNj{qCWC!><7`Srfek#QLMwfcKcrpXA8QU_-0aG2t4M&+xdP z#N=&wckrj?r5e*53~8$QUn%R!xEDtNvk~h8XA-cc%^$@a<b>=kdE^Jv6)K>Do!P-V z(>y7T=y`MY*28C{TWz{O?nv!rDLD5C5T=kTyRV`=i8S^B_T)MF=2CB)M~v7ijUa+k zJ=c+O8C%jSkA1dgvsY+*%2;z{T;{7Z>*oGxjij|td?vrpIA0w~L<aF2(k(if*M<wZ zPGQxw7TsnBzJK{&-|_!jnMIuF)#N<W+{=ePb~D|fY2UUX)VqQ)*M>M~d}rMc>WpE? zdo}B6F)3S1MA2`C!YXjoy6UBhV-OG$ICm*!>sTp0Mgz%~HGl$dd{5yE5rtlf&$T;v z&4z3pzgf*~DRWAyQ#lt^^4V&C_<SZ)&&CY=ViV!GeLlbVSbWG5R)rniO~i}~kIM}$ zM6$7qijQ&Id{tF5{Ox_hJ@$iz+ns#jetV3v>VBV;KPmgCqpZ%QuT5_>Ub3(uUsgBv z-~X>a+>j&zR1sx4FG@}ZozS3`R+d59^g)Ugf{u^-^s?itEn0vli`pra2yR*9&psgw zC{1<iIdxvD*@7C>lSCv%AeIes6npwg0eZ*2zkI8b-yM6yX1gR#1XB7bqaVT_YK7ft zA<R4<AAq)Rt=e(mS$e+Yr=aLt=2iAxDt?saGJUK+<D`|Etc?xWMtQ0{61Wx(=`8o) zR-fWp3CZIzOzE;f-dPXRto-bV@4Y=5z@HW)J!tzB8I#rdVncjN0`ns>jJjCA6JMKy zQj|5tK27HI6u#^8)|+;bqv9qf5X4MjDnoKlPX)P^jp4qY+{2TNb<v<gc<hz@sA*+H z7p+1?0TLiu;qL(pf~zXf7|Z_BYO7`y1x_1m!-<?3UOF)ROf_aela9jtd{5rA^`ozV zPxY|~rCnM(teq-@Xd>}`ONvqVona}Ue7;uNSm$8s-1)W1V#GQ52Qu18MPEsozVYWw z_i`P%E9<dyAK%r*xEVu0b{l(V=oQhRw3}Vl<VNG+H+qu{`K<GQ0s|;)&te{@qVI{m zq@nVC{Ce%TDQV>TI3tQ>(tvs^{PER+@t>;l)kic)HX~~vR5t={Q0>|%WW$@*iS>iO z>F1KitN1L|Rr6@sE{L(;^!OM1B>O8|<4U5%85bwhhUS$0t#obeTITwuHFTT|zT7-^ z3iMTuhi^w|ZDgE7kGO#Jpd=Iw{7M?(_)FQdph_E{5PUI)p}=@PrLW`nFmliM9cbo6 zx%Rmt=@Nh=midFVW}X-kpqUV+#R$KXVb(W$gVsML-Yk|XH!y>TQvA3{^+m>8?d&?l zpSQ#@Utm>%QR^A5voxH)?mnXl=NJQ@PTbDL@ZN0;fUf=EaKb2R*;9T=g}515b#P|~ z)`w3FbeT7KH|v|1f3}VrZt~1;M7q%#FR}?>jKk`j-@belxsls~E&Mz8UJE|3U@Ik5 z0vK};>&H-mcwhI0r#&8F6zeW<%JmCHO=Q`%-#9*)eTaTfr<g;NrH$b9H`$f#ou}oF z4*n3Ev!>uJ>*c-Bs~o3ikF~T?;R~#JK0IQAAdQ1zhY^N@l*`j%n)$b!R|a+BJd@x7 z3tB7zqcZ|N#N|gl!WZ)_J~Oh=#`qV3zv63%YQF#o2m){P%utD$M8Tqc*8bvHSJxQV zB~6?*ZU9cl<O5%cs4iwm4s!3vU`SrQKX@jOEJ%6gceoVQq&j-K#_QVI^iWuUJ)IT; zT~(izcIYRMUSL2@D?gG1@5TduLgI}K2}C_4Vp~XAF<_#OZE!`Jo(7BOC=z*oqQ_zx z=NX?aox*<bk@6I9OY2<d6A&Wn3)UR|9F{DJ;{h;eVz`>5pUgK3IYvv+dfi@yIP4NZ z0i%PmN^-KDObI;^CmQ*26VHsl<s-V`h-qUWP$fI=gZXj>uYS|ojp!~su9te5G#*+I zTv8YM9JdoMB)Al^V+RV?MR(TBQX=H|rTCiS*q&F(6fyVDU$Fuxg?Jc+!wp=M?i(z# z=(Fh~nZ=noFKZ!e$-i2&AhUiKh$k)>PjRSj?pgFZ^s}rrp}*cj>a+6yd)EK&i64H* z$9e;%T<Ongc;#gm;bcCR@e=&qhZUPgogCX?sw^?poP=05qbrk5`qu>Mf?;UVFYCrW zl#^i{%EH*+wLzV;%VdMiaV*`h18vYPMH>RKu`q4yT7*$p+UKPPmz-kc#0y>@e<3M^ z%OcdDZu|GH%n>)r@cv|579ObHTiopRQ$K7O*NNAYdeBOeF%WuKFF5U_qA$b)O4=4a z2q&wBkl4FcDXCpb3jBOzgp^U47Z|EyI-B{+3tvqgeHZ+CUQ=U2M_4a@SO_!t-x|!O zNR3ePS<zv?kUq#)6Id)hI_r$SQ+C^twd&5*rEaa>PI)N@E#7PREh~!$P}M;OAbByL zN(!P$QIXOUe+N7t9fy(Zvq`MV5SC-eOW9!WpC!L+@DoP^PT}EMmcTbUxsaT7YH+h# zGdkJ}L<_613G&_9P%oky0-L2~leLZEW*&}o!{s5oDPhZWmvKo$@Wl{@I;+h_7QVvf z?jk2el=$eYnv?;2%c>Mc2h>uq@U$y<Ox{O8$_Vu*yc>Ox@$M@RZ87Uwp9pZX0Bf>N zH>BDJ7X$JEj9hxAXvrxT5oPyrV9gk@MlSXx#(`|z?1cK$TPc^HdYt~)FsE0}b`f9x z_5<{cokDRi!_|G+#sKQ6?zmYRG5AhHZvGXf?SZeMqozN<Tp^q8aJyB<*dpokhYkO6 zqxwcO|9Yp>FK8_xwTulj(uCO7zx1WyO3;ePdho7z9IM9}Pz+jzn5EJk#ta|tVZ+GZ zb3vmf?*76~9lwE9Oc!&F9+u|A{s949W;R%CZ0d$nXp)B*#Lgr4Sm*BA{~5pD3awde zPjdNb;G{H;$?f>vWD1<dr}Je;Ci~W9^(I#Gd<?HM;Y84%C}eG6<CGh^9fCt|hLs94 zE$EY0r6EBofj!1$EA6=00_$&cf3-5-76Nz#R6#!kJhOfvIgfj5j!{56nC>%?$368e zGO>b-Kzp4B^Tq@Lw*^#T;&;j7k+})~c&|8|iD1o}lT0u6t1)OsE*byg1gME{g5rCu z@{N_t$vrE<0@LJhV=^!W6q%7dZSJ3q&WvmLW?j`ezI*kxb1b0nG~lNF+<RY?R(RQr zR4k|S`j6P6>S;^P#<O8pd`h@Ep~;Tu;e`j!j%h=i3yknZ2*VBehmTac`G&Vvzd z<{M6>*5x>AzGb3PdYe&~@02<w#S<X?q7K55wL`7OW3J*l0>d{DMYTT{3!mMMn1yqk zy80|L>$9Y~_Mg6L6<|gMl)uJFRl--Y7<}0qe%`YB-k>mWisT$MVMt9-sjQEXdEvx2 zDen*vwJpgK56#u23M9N5w~v2WN|PpRh&6mAlF~Dh;y!tWz>*4}Zb~<38W$20y$0Ub z4#h;&{(UQJ9L=XgERFTUy5HYGdY+`Vbj?hq$@!g(0kV8Q#)BEqtDL>J6u~R%;TQF5 zYH=<87E^8_*v*YpDs||qp)moEeqW~%S1XC9H$c{mKYyGhx?c(5DCnn6fVGe9+p!Jn z<6=R%?}ewzl@{K*2P=_kPHO?;>=L5PSxW<CIFWAl|0>ZPxthGr)%lU~*ZrP<8i$Jo z#{{^gMJ5k$Xd4wu=WdAY!E`)Lq|6N91B7VTaVRY)3f?@T=(|(^pQBtBE=ME1TsJje z>5Cs}d-i%iS3nK<C7t*gfwgmy_a_Ml&fNXYU$wpbs>s^rP5zo0UX2aLIp7}(ZE&yU zMv^@{q;hBce*Z}Lo3T3{MS#TS;k?pdZ(yp<b0Y+5@ZJ8S+$&kEQ>luw$p87C|M%&u z2DCcXD@#Kl?{SOHw$q<eSBKKEq^>i&AD6fVH2$jYIj<ertcdW?=8|))dX-z&lrvzp z)Q_Zvj`e$xEA_7p7(ps)^ccTHY)chUU@xTYI6~~iXc-IZ<T!zrqF~bsje+kMSYyDG zH!q%n;4u5#INr_QzL1>$omBv_xJ5ITuhyP$X_5LJfpD!1Sb44|P}?+i`CJ@z;mC}) zvgti&9BzhAu9RLk_Ab^Si36xo;4Vl}eY~GIvVlwA|E-o!KvV(uW!Vg7W!zg^rFBeu zWg+$h0SdjwA;d5qgf|m^PA#wIKj+e0%b5JA=D>57#^=kH=_*j}8==P|C?&)H<zd*L z9qa6YBEU>$JH{nMguvzk7;2F0&S(IiwsIwr%f90?sQSvRKE_74T_M~o=_5SNt&9YR z#T<fIT1dgALWR3CIGh;A9h9U3pphq0Za(Gn-+-=<Z*WN^i0QtjMz&qMh~)xwiCw?L z@JYs_k}zK%y!+S|#Lc>#LS0G<E{nP3$wY0~_=+DC6Md<~L-=@yZ<*?%k}7-YK_#R! zZG9%*O~JG<!a_>tn&lg{WwtF~lE;jQHoDBhmEkW%D*52Lq<x)6I!;PbVpohQ_>-`i zN}O7ui(oF`dLYl8vSi)TqWkw%t*zS)pg;@kUm=+h{hF4rS@Cy?^6u)EF}dWEy=eZ2 z7;XmH@Oelhk&&kbxg$u}?PJL;Q|dAY0yHgdCEPTN`zd)`m*Pyw16?Ai>YyX7OopL$ zd<_dO!*V&MI8X_tbYG;_uoCyEUd=qnAz%NyFg!>D_~rA#^%~_!dyd6?=-Bz<V-1)A zfg{eo?&X^C@B6<)@)B$}-eVWLE<(K&$zyat47gHxBRiQ=&#b5BFnTUqEWq-9Cp!r| zuc#K}Ix+jPKEHKhGt-S7-3^=39XFxJ6_jz?o{I^sUPt~u4Jv-@eXkIWG^z8ANs_RB ziglvZSBbieXMzd!)&VDFi>E~4Y8T+QJHoo%LHLfoR7ow@1X2_Dba+m3(x7M(KlT9_ z3wnG+k5R4H`bXixG%zq|+4=5E7vv-ERfGB$H-&z$6yR|8<0jdJ=~SA322-rxCC>gX zYyCV<Iw9fahq)_WDLwV{gT6%agj4*vzYgGy)9l2i=iyBN;qV*4sL*lC@$L9U%fI<< zoWh;cF1+K03ACZThMq0|67MHgBjh#rI7PL&s`?KE{)W%^kKX&!MS9&&e&k^sL6)Nz z24bgOx7EOrj!2XoGf2RvTi=c4slQ0F@1B3TTd3q2UW#Mb4ndfX&<L-f63miXtQHT; zIKv~1w(&VRqjyU}$bc{mTpX9LDwNSo-7fFmnGAh8#9lw6A5-Dh%o|m$C72xS^>HHR zoBECn&cQ_BsN!rH386&a_D?S-&FhN7l%@AXt(}SR?M9`pnA`m%UmL<Vme+&FIPZ7_ zx3H=LR8lR$zz+9ZQJ}fDT<-8N7+rsrTjT1=Bc#9inxCUjKo%lrlh7O=kQTw76AM+} zA(4E;4ejL$#iNE&7!-d#B60H{njD%t>@{k_GC$iv&8_qYtL;6<YS6uk)&i(xjR=HZ zn$`Lf53W)w>k!<xOF5%UGJ<N<yki`4IJ?Mos=OjQY9r<ym%fK()lxLe&r2oGLSu1{ z+#fy7T;kN?S-Yy#IUEv_{F*1i96t_P9lBq_x3iyVIlt!p^{GoX>|4-%__CdaP0eSn zvK_;J!$S!j17XOo<s0>E#BcO_KMu*H7%#V9iHSt>eBgPB4)@(a8fIpD6h)UT*8Hzg z;h$OIUu84kaw4roF+HFxQRdBvS=xy5Le3LK&8Yib7(;w&Z0`jaG+T5z<dDaiYe$>M zx4OI&o<I<LC-pz12rE!ieQvf~zEMXO4K|#9T=eHO8vPG)3;IpTuf9kX*c@;@Wshl= zpJfm*5FBIAT*a1vYD|z@t#G27WzoxyHi&u*McAWo+w*4WZHhuf8hP3HYKMYA<!Rik zvfSkshz`ofqXo=R<iEGZ4}ZxZ_3kJD^dHrqmwL7SclV#$LDToY9EohpIThFMm=RLA zO68>SN`jQAR2niv_ceKyo5WN4V~{Q@l~dTtm@ke;8B<HMYiV}`qc4Hw0Pau(G^lbB z*)dOvipFx?l9;DqseGKb*6$Sn4EsZ-bXf)hW_=_E)C+Y|BYDduKAekH!IE%F`z_ru zt4TB(q#UDUC&eZ>AkI9<5lLQA>>Gg;Hc}aX1SrQPX1y9yqQc4(RBS_D&`!6EZ_dY? z2VtTk<s{1Qn;iIZ$g$y7U_M2Ylm;-QsYnV!XI_`FgtDKUo+Q`DKX05xNM53AzF|IO zev!c%b9PfkNK~E|HjSaPtZ&9C-ky!~2)c4vuq{$9?<c&dO3pBMA{rWKsxV#gk+ey; zuJxe=6GizfSkBngWe*GSVy0gHsg2s~mecaeF}DJ@C=CyZY*^yopCp63mINePSz~s~ z#4@MSX71Ck*C<i<A_DJ5jVb?LH@C=IcQB=X%c>S?`hU23%cwTOck4TW;O;KPp}1Rd zFYa1`6?gYQixhXKxVu~NA_a=OI~1q5zv=m(^Pcsbb+0w^Ig{j`nSK5C-WFRI8ez5Q z*4bBrk?!-O3{*~1Lq7m`+5Pxv5@ZMe%0V%KI?X%Y!3qCRfWaL%?v*gJol7G03jT>^ zrPM;R^;c=j8405^9CJ!carUTg-|9`4AS9Tc)tVW@P%NTGH;L|DTDEVSUrg<!ljrGO zM_}#nzGIZF8en5`)2j9#TM;YN%(1;pvke@d-TkZ7!`j^Rb9!aLU$QlK<Um_~Z~QQF z@sFt(HweNTcU*D+sUt;P5FR|8!b>sV(!cn>$@f^!6n#vkVjmJ<UdgFcgRxK~q1To5 zcn8!NKmA9wUOrgrs~r?h809#__zMrU9o5yBJdhGx@0^dD1&NTm(nn1A1_?KBefZ-# zv;7(f1^STQ&V}CCx`aq;GJAA_zEzN(UoHBlY_An4R*rf`xM0%ky}XZ8+>Eo+SU(Wy zk90MIdp64yIVWq$9B_W>!V&)S`DH}@q21oze!%0$Ih0-`mRfn3PVK_oAQGW?uKwYl z(#p~$n%n@Dq%}CzvO&{PWNib1qy;W`X+^?*^zZv&Hhm-KM7otnC)C*|9-{?dUTm|N zlnHudcGEQK(nrGvP7sS-@(c|iA^yD@9K>nEW|JQQWm)}W-aVpRZn*ZabmFMD!}N=U zhf4F7n}c-dK^(o5M$1G6Pq9;%VeGW0(D@UZ+bc5TUk~d75qLt&P2i)ug&9|6CD!ES z&nByn()h)KV(+E{bPB7BVi9VE-JQ8Bsl!P3OD|u6;)Jz^m+MESu44eRWUwP~pUd2k z^%M}F3x_=<<-5sypm7ACGJ!WjvG>{&StvPF{+qPN0c8p@BRM#yT+5_SLK*S27Kwy$ zC(hrar?onMq^7e|QgtK_fZP)@T(0QWuvuXx%lx|?>E;@U^qY<yfRj{L-;ULunOr-X z_6VCmK5aRpICgHXJXoDU_BXpaIU@?r;Oy7>Uah);xGeQ=%I1}sh+dLaHUUayJ*QC+ zx}Dn{lAyp(;Lm^~QuKz&(?q7u(AW1u^D|5W_6V|66N)dBN-651jO~_<Po_@j3(ksh zqacG{v!`b>cUZ9|ZowrA!h941=ub5iqQsA)%lqa>T@Nuod2>!hxpXI3sR|GdH=Y?^ z?Q9hf=>MN#8V!<gDlO%)W`}ktUODODBEpc*Ltm~Nk()`K5q*e<o-tgx=oI<ABO)$> zqD7A{s^Gg7xp_KTqbK0)76GGZk#olWIS_`m+LR(nU?No6#x`#$^C{5>O?%RVL+k@J z#6z<W9;}FZIvAp?S!YdrpJEOTbq&u_tsHUlhn>06S_aiqL@VhH^V3?D0lo^b6Hg%O zGp6U)#2D$h4XUW;t5lOpe14X+ORaE038Nw!MpDY@iY&kWNaj`oRapw=4i$u6zFz<T z+HIBGW?9QpRaW-+KOmx2g$D9DR3f|XKIbwW$3Od6N=*Jk!(ozDO@X4ziBsyegOrty z9G)yLhc~QlR57w{{NFBQzQ{+O#bmiAOeTg|jwYDV34bx254a{hO5R9PBIB`sQXgt% zrNQ{<U!!INcpjUO36FcOomoH4)TLEq47@!OPt7w7fK+xMk^<RDl4yGY+;p5Bo&a*x z+cJ0vTCYBHD_>NcM`sq?C6gI*|5v;Mcy_>~idldIp4L*a=KOnp*oJZB7_^L@si3=H zP5kTF;j>aHosakSPT-TXTmu#B8e(*!fGt22=j<DVBmccggsUmMoE`j(rl<RdsM5@J zQX%42;u8Fs8=jHhlL<?<VQzT*)`JAC9=3);`PHx<sCn4BVu=0QL%_Ea70D}8+APVI z^KtmX-9;2Us-+5;5>Q&+EQK;hWjJxo2WkW<JKC2kW|~~rQKitr;ZSF8;Hsq{RrW=_ z+i+!GVk>y8R-UX^@Bj`EDdDj}^a+YId3y2*L;G4Ty~w76s})msokLugRmffgF9`y% zveyJ_x}l|3QV8cYZ;v&yvN!n!59#Q2gsGJDy>NoFYb}Ou*cnVmMo@!Eo#dyXA3L~G zW^e0IhCl`o9Hk+cYa!(<9Ng#T#kOuMSP=-V-xGovR}@Gb>7)hr;sq0fvWH166g~h| z@egY4I2{A7GW-9v`xzr}UJ#>jUXmWv^`O=?T^Tg<=-G?hl&OULW0N_OQz3#YSOBjM zajd%(DGkT7Vi2hOan*VKK^SN3APdiHcVk>*DFZoF>>&5x-tMN2872A3sJ0wJe2i^q z@c{K2kcw-=WUv~w;~up3e7A%JfB49IJ9luhEi`b~6ZrfTD5Whrv`P4K-S!mY`2&is z__ML#_iC&!I2a?;9zQ8Sk~Z09Y##3nHXi2@)tLbLkq0QIoOs|~mpzW)!lXp^WrQFd z2bcon$;4d!mG$N2h@r=a^a(#Y5qdvk{HTZ#&j$f;yXer~lPvp8KD0?sNnZl@f3g7f zH|RTC@MA9@Omk9WZPi<HnZ>~|N=wBqBs5Ogn-OioVT~8>N|D*WS$zMb)cFOCY@;Vi z?5A7k2bg7s-NPw?C~-;`Q)FjW^G1tSU4HmUNwA9z*O0bl!7$xQDw2X2o2v`rr1e+V zJm(<I6~i47#%dBn%egbgO0^1pl2Az1BgBEOF%a=Mc}&iIlJCRGS8{ROFp#Gr(C`;8 zk9ZB^7L8SPb3gfcGtreoN(JMXl5dn_zjHq;F088VY7W?QtdtAhQ#D62YwH>FM?t3v z1y_FemRX+z=Atx>7YgRC%?DJXlG||xn7($Cr)3Y_p@57uo8Y=6=M?4%%ROr%2hV=M zrsnr(2|QIj6sOHY!_a;ZM8sq69@<8S#uGE-_N6xs4TrYiML08ldPm<2dou|=f)gFn zK2!HcjPToE$-j@MFyj&jc&o*y^Sc7eOwev+R67m$Nm@*(fU1GR;GsG2BYz+;8EzxK z#9(j{ZT%OZ{&(C;R3vtgy6_CW9kpE}$Arrpd4p0q%GBo>y23z*x(l`aXI8T_*X+l! zc+<D%*p4eUooMU4&J2bBmO%ewR{VDaJn}`DpmpSj)Z}CM$cjqk_2K`W!Y9P+TpQ{t zH4(ZuX3eY$B822|o{%pU)h8odJY~tIE(ByCkl?zPnoj7IOnwtMZ?03;<H#*aXrAx= z#9(4}a|}wzy;@nwWL)MfKCXyjI(*c(qPHFO3P2n?44otHHRr@JunUH~p<ZZ;><WkM zjr#}%7b{>Q8d~~YXv8pMyghxZ<qJp>BMP4V^QOAQhO++Xm>*R-k>t-Z&B|Nn>EJNS zr{*k3^WP?UN`=kCZ#jkW|6w0OLAu=IAecJc?<nS`dH2@(H}WMNfKC|o5($M4XoTp# zqfA-k2v3CjJRqjQ7tP*q6Bods{uI(qNlRUSr<;Uu0WNImDPvL)P}_WfXd<W<jo>G5 zc@2ZH<K4^QQD!3m6!@icLRtp&{0N_~4K_9Hr8@jcF<CuuPF1a|LXc2S6MwOGB>)-e z0ZhHPd%@m#vL=z-K0Jg?<^0T?_*s|Qld}0uWvjzZGfPTKoDjx(rC(Ob6g`vBe!)hf zIGsE(jdM3Y!s4~HNqq!>R>&OEz-svMj>Otm|Au?zSxz%mWwW$m4+B8G(>~@dRyLZm z6T`h_GE*5lF{nQky-k^)Po^Syk|nmE4n1TVIW&r73f*<@d&ct<QyK6wtP%s=iQ5nj zf!|5VS9cl$9&1lSFM}#BBV!0oHf5`{NQl_e^30iG$~V#o)q##sx_~lH`7SuS5d7}a zP@LM)d(<E%`>dY(j}@O*eD|-u0$06*rYHjZXuj-?=8D8mJrOx)1V0PEtZvwCxVk@Y zC|-K;MQdj77u!z%s_MUY258a~MZ+1WUS=L-`pEYLfnk@VNjqMgR&S4?h7{-LGu_lk zGu)oIkxoWnO`hw^zoM>fVJu;)$fSi0TWC8ChHP%uf)|$g)^n2hpo+N0Kl5%kiasXc zyR@^boHsw=!C6Djrp}jB$JidY15Kz{)glQ412&JvzPB9df);8;$Da;cLZ_+|0k;iX zSYj39*1c*l2H7+9EJ(9hQA|(YCtP}SUQciT`D21V!d-i@4L=|F1wu;{$;8D6xZXUX z@=E&;8Ye#Iq_6wppVI>s{t>+~8w4>6Pnx&I78kK7mEVs$k6c%9zuvA~E?G`AJVPmA zKalVs8_FBM_J$h8)8f!}!@6g*7@fub)8t*ae?oGtm+`}@&U_aMNCi1UJyZn8ujUsG zEyLn5cmtHf(q&*jb07^25ITO?taG5pyL%GG_Jj6QJ`gkF+&%qiT@^my78{YbFZ7=f zgP{kOQ;AOgK-b7HA*>yaUJEkf<O1Ex!${xG5`boVq~Hp$yleIDLzCUV^k7ySc{@^E z%<`YWCZq!jGiJ?$u~++HMBcNIEHaW^-~X~83`6{!<`jE}_-X~4aij>`m_#7@?qL#P z?^-igOH_VnWwtM=m?JJGLxeexyHe5jVMfmYjchsK$47k^b@g3#&Qm~m`>^*q3hng# zZ}2x_vsfz~Et5SSr5p=nRRxq^+`e28^_jCcH~%?R;H*Z9ymcr3XOdbcB`<o;sxT>C zogv>m-`0s>VP;JY00fkp1mMbU6l9yxxEl!|>}gIm(fQTf6tD&NZey(9Z1`{)=%IrO z?q%IxtZ&9>mWG0Uk$h%c8EeC56`l0``u1PsyzGQEemtRa#=&_{0BqpR6Dk@^-<I^k z7K4#K)+X&-SCdYslrg)}&CY%}<$fF5_NDN)FeLeFh{+R^&3K6BQJr-hQAS`t(hF47 z7S5Cv#-Rh`(tVO#=CSbpaTocNGqFc6jvr{|XxD6YU;5H<jWy(#x~u?=x%hvVFdvO- ztuk3YpGqxCN@PaGpC+Y-1@X6<(uIVGRqi>Cok6HPd6P51R-p8iYfJZ6R~OxqJsY60 zT|zsS<26RYz3a8q^7Xe^9I^OIw7+^WPWUuQa$l{8<bmjtvbPUP;d_J=t=<%3ET*o? zA%dZbk9WhloP(druQuu&Sj0xdeS;?p<mE#X)SnC$XIfAx8_rDL*iQ^!O$fO?B=Ht> zNH!uoBIpjeSU!=QVxE5Y58kf8gLzy!t8Ch0P)?lYBJ1T6crBUQ=lWwT$P3+x|0xyP z-~O8w=9h$FWg6wCWe=D3ouMLybr|PYD$-L`11WD24u59;S>QH`uN54^wBi?c&JSQ~ zxL5LWGM9v*Ny3sR16z#vF7zq-;bRRv5H6yRRQ`DYDb`WNz;DDx-sSaQUQgj-vydWc zQT@^zk9l1EY6USVz&m0NZ3zdj4?V<f&1P-d4bQ7ukrcKwK;4dS%+#DDs@i3h+lsqT zWCg6x&RVCc+|5O>sCJJ=Lukf;8eI>b`Vl2bLyX2;e}I=A`BnKCwbYD1O4VTQGS@#l z%EQ^m?c4WxZ+KUnDIm_YEvW&TO)JB?2I@B?HJpb*8$!oVhy}#)=Z&(XSo{`G*Sk@D zz+#G3lF*|{T)KJ5$4(<s3W9JOD_@oc@9XpFK=sdy0igaD&=BU|veYLN*r7?#13I<U zQCV>)yA_!mJ^SB;<^J$cp^9YDxeNc-R7#8>70#2xO(T!4BPo1_ZD;A_cVtb1#_st> z?QV=e-d@h31VZ2fQstGdY}2@(9-rIolRw8z`p_^8RgVHVLD@)LvEI+C65Td(j7+fH zol};6nsH(D^)PGR+o40SVFN1rhWxXHOttxqSYyd;wz{($oOJu&L!*941FrZ2x#npn z2fI*yK5in25&}5{f2KZ}QJl0Ku#H|e-p;&sya(3AFFfAV1p0&Y5S`-4MyL{#7M34$ z#xC<cKRNc|j3XUXy6dK9ZsLlo2Uj5%lN>}mWk*8E%T-n+L_6%m9bs42M`2F;(g~6v z;b8OEYn3<d;>auS6H>S8M?049I%fo47DmCZ=9Q0q1YMZN7e<>|$^HuL$rGMH$(Aec z{SRg&!L4=%sukcF(~X*!<(iOx0Ir%jT(0)Tto49%+XE>dS_xqAU(wHHR|=j2Wy*wk z&S?lL{_)=rpN;(nSH7ER#gIE1vxs0cH#bv>DR>BMSl=2s)mq;R%j!LxU^ZGT>uLOa zT)(~>IQZs|3D5?l<3FiC+jlgxE&e`kL?yi9-}nYod(r}6eNw$%*g(Ovan5<t5J(Vv zN7-HeapT)xZuYtOPr|ffW(2C!yuHL9@b8t}`TC06B9!W5S%Q=jbdUf<L66YH5Lx5u zDlku@P^~c537}PBei`;81wFS^3-``HSoU*0PA}C8HGAPNQhS)$BhGQyVBuB10#*V` z;;{pAW>r|b=vNVsnRRi{sb(u1NhXAG0cmk@Wsnr=Y}Lf@Eqn;wVB$v1VlZ)ac2eAm z<5`d>f;A)d>9Anh!97hcca(A(K*X=akNUyo9stn+PHNgmKiO04GrB~mnD-xPaGf+r z25Z63=F|;SXl#(J`POY01ey;WecB3uFxJ3d8lq)ZH+YLAEhw0f(X9-P0^(wVGz_)d z6A7#z!9_4aGU>+mU$(?sB}Tf?zlSKKixJIW+TiptXHW}~!%v0k*>%hBwq#;0#uE`1 zltoJB^ZZo)dCTOB5mCEMe5sm_05@0HfQl{=!*wFMlpea)&z_sj3aEM~#?q@EZ=p-g zyqrd_GCaW8u~-8Q%>Un~-2V>e=%fKQEzv#JWsa%Ce`B+4Wv&Af_Ynl48v+fJF%X&Q z;fy@C-o8%nqfF)TaWjvTrqF#+>^R75LhOsMg|+<d>?)d4FK=~nvW?Q$EjdIjgx{=; zVnnepf)@Ewni?cgcf)le;wMj!S3$gW@F&{WHI!-QhLJ%rBCWb+5bOR^{k#uDoduv2 z**8?wG#z`gzWa*bV>+4LKr$3m*qB)h?|Hcd_FYv_tLCa6CI6#HY!0XQPFj%TY$9JZ z?+eRFUA7@AzR;01S0VrXxWDPUzd3!%wEu~TI#bv@X<1o2{b&P)8{{{?Cjf|XkS%4d z-5|j{GB@>BMpC!ywG}pY5Vs3l;MkV1#${eyFnO@JHTH}FC<}uln+)a6I~muv3MJ1C z{a`Dp^h6SWaESu!9HBP2s~cXap_;SrKm9pQnYB2}wT>i&uOONUxuFYaTB&yL<X6YO z4(m0Wi(#KMC5R4TRGYyO)5cLZNE#bJ3$a<p28)9(*Kr`Z0ihbs5KN`@JoI#Y7yN^d z%%uba47(zmG&A=+&R}yGxydohLl-VFW1yJkFKKTJ2vsI*8u6WnQySni(~dki5NGL@ zLwTF{S#7}WTZng-yBGG~<lpQ@Ylw;1F}N{_4`s2vrr;*q*OFu+geVLr22ucxZ6f)u za10=e6v0g0yk~ok=0-|y6IX$UOl**!%BS{blXT<`XlOspmqjl~K6uL}h`3)>mUI_7 zIqLEW&rHl_N%QOn2qg2TaK2XPcP~?B=$|WPL79>&v4ns4(?PZS;n(>~yQSsjZG37Y z+Hq^X$c-1Hupd#3f__yiDibV^c{m0`Eq`J`I^JW}1ML=CSUps_JtCvT?(+iDM$oJq z&+JLT^T0iw0NUFkr`*xcrbh)N$$O6uZhnt1X9_*X)N&)oPxYyt%u&F^M_tb|mhtX^ zPXfl-2M#d85p5?N;~3&v4)kl(kvE0iiJ<S%phtiO=usdHE^I_ltI+3ai3Y4v8<U%X zMHyl0J-xFA+8pkqf!dmlgJgpJ6qo2n6Tuq2Ipcr++0f$aCfY%tykAe5f(`wZGZj=k zm~+-*`h7p<re^j~5y8Uw;8`Q8*meVWk*S_Kx}Q3RM`*niR8y>V;Zz>(T$~k~S>hI) z^<$s><~wQ#8~$SY6^((if6ZGMuO09|8$ptK7-zgQnl&a9IgQ}I>y?Nx<?y5=uB&zF zB)2glD}5+M{ZT#OJLg%j*+!*QaD&&4XXeHnBQLQ0xel*;qa{b#Y!{0!Bv&7+<Tf7q zs<1|}(<|HUYnS)sUhnzT89&Boo#)Nuk1;xNJ_SWC1xBNItH_}Uka8kQ?$4yf2J+oX z8X(qQH`YVm`gfC^%Zu%zVzrJ=>~tuFHju{z-%#q!5vCBkMbl)RFpgtc^e1cYdT9cF z%@Q*8{^8eISC+xa;3khKHTHM8COs1V=~gy+F-MIC(r<cjsB?5fMyY0_hc_P9sSNvJ z0Po}`_j`L7%=chetvaX|DPn7)mOAMRYI{E!j3mI-9bgsxxe&gx2WGC?B?pHe2FD%) zp(n{(xK}1^@(l~fZj@F^SF2=Ea`+}`K1E;{N~`UlEs@wY4~&>|O3HF{4Ar(L5enue zTO_DI^AY$AD9P>UOObGqQXsI9nEd5&C$EmlD`~b29FyKe|NE<MYkzQNhV655fP47! z84i(X$W5w#Z>L4!&(fc`r&aFXvpHd0-`c&6EK;Z0*o)VJK6W|2Z7u#hFvW_$=o@A= zuF4OM9{(>H26Z;0e*|c?M<`8-w}{f{N^Z{9R~0sYkaF~_upQnlJP~;lW60j<OAU5} z4IKE5I|K76$-SJfPfPrwzk%i6#s-jn1Y)<qTR^BaJG8@aT40JHvVSY7jF=(0;@2y= z<x+>_z-0Zko4IlSMODvBZIolzB;D&sb*!mPmD}Ztz9P{*b;DwNbq95>ooR8h9PV&R zKj?>urE}V~(ZX0<qcHw4VBqEfj|o9ae&E_Kdb&$yYvUWF*A$T6%xw~0W!Qc}QU8l$ zpi0fyk6%63jCj_o(;_UcY<tLiHbgZr|0*h{)kbl1$2*@>uJB(}9KR9_iptXry8P3B z*`S4xN@?oivbGMtMULj~P|x5-UaHI#s$ioZjH==RrJ38qSR>aU#heg@eRuRi$6<L| zaXEkwhNAYqH2_h|W^#WSuF)u9BanM{0U)OYFizInY|hQisGiXD*Bg}ZNN%6}<3bu; zJkBtqqQP;kTIrJ1g|+z|HotE{ZF)sYXF*L}Rtif6j-YPto&}IaES_j16La~B=ZAXj z+)6yc|6%!rgyVCs93Cl2xfN4}U(qonN{n60ZFMV4BPcT&LX>AvpE`^2oChl=yYY$+ zuKGd<7epK>5$S^7SAk58$6u&x8#AUkNDmk-mI^$`zA}lJw)bsy#QeqW7FO&cu9X}- zH=d=cxHm<wkWu~hH$njG81FB6bX6srrL|s%MXM~7J>J+?-l`Ac$O&KUAy)hX;mWZO z_aM5;alCtSMKHB)G5#pw>>_ZXB*<CI<T;!-8JY^%X5#8$Rzg-FUW&71-^MKD`m(lz zviYr>;6#1&VEH?`36N26Zh}cLN2UCrQ6xtzwR=gnWzVJJhGikK>+PC$^9C<>)1zS9 z&z*I>wD7^18mVX_CmM7#rj_H$8i&M`5M2ZKjhG*QDu+)MYu#hozw+F^588g-5q&iS zDRlk|1_M(ZG-tZ%fVDtwSHhi%t`E~iALD<iVlMnMwGHE1`LcN(!j+otA>KL*{|l$? z-+;odVPx0nqc;U=mKn#GB5!b1NOrh1wCsxPi*2q4RB7^<NsQOZOu-YY4w@h_q?pHl z<1oEk#Skll;2(Y|_l!=z0}ws_wo%26v`5}$+w&{;w!su6HElxmgrt`yhdY#?30TuU zaDMG7lSwYV*Hg08GbuZUpR?*6_V#(2*yuYW9l7@p4$iw4mJENmKmL?=FN~xz81*wr zbz46Iz<fom-faI3WE~U4_Y(i{Qw;HVxQI7`{?X#~;JM>~+3!vTmRwFUX!?WB{nS!4 z^5^M-XD)(T6i)TH1K4ZJVBEi*SUofl#hc%?eHG<_{NX)iM7*Pb4*_A^TbZ!BWwj*e z;ltI=RV3_6N?>1`zgeZDpcNZgyj6d+Wk=XEBEqTPt0b@<f77v+=0OJ>Lq~uB#3w{w zVFGwS2|H>Z5Nm<pP^B~Gta?w3+YQM#-w@$M*(uEWdKIKp_9Iq~mLHbC=_pwA&vc1W zFxSyF2CQk3Fj_}tmx{a+-*K;pbT_>KBRrOP>LxPR!?*11w_}C#5!THD*kGmFy5--K zAWGwR_r)OoV~Jx!U0Y!+k6NVIAdTQmpX;)`H94*NPkQec+M+tSs0SWio78l(T`*t+ zTsHIu^3tno;H+$iZ-`A|qk*wT6+oLW)~&CUe{G#v;QHPP<;>uiuWYQKb-q^#{f??p z8=!>TGpiR}wDCWq{Qo_F-@&%3Yu*TUxA|HCtFa|GBWJYHbGcrLt)IvVoV^!heI{)l zU0#Ln_*G|HT*YIOErD?kdmT@7YghGp5;3}{6}?U#kSRVaxcud6$M-%5z7W60x|{4& zsDn<gW!appcb>y{m~nt7^A$uAdP2{sNmjE3#!Q|EZ<go820cs#7?qoG^P_gCh!DdY zrA0MaI0sr7`6;q1yD#2y)TX?!e$>i(Y+?_@DC_Sgt8d2a7iA5}dQlQ`TKD|&f)-8} zZXTiZyNTsjVTjRb7e6}n6HwVYfXAn1s9iW0-uAtXu)}uDs|kCcnOl6ohL_TPj0cU@ z_kUirjeoT5S;xok@L4oYut=>(iu&IaF~6dWGLt7>f$Y=g{$^^$X%<_E_I%?udnanS z^+5wE5>=(tT8ihOLq(NsNXr0_15o=lOM_W1(~+n}ip_BPB!!Xmja;lZCI(CE2l5E^ z&1N!~!k@ZRO7WX4t_#tS9NGayGHqLts1KV*`Z7_wIS95W<ck<MJ|+cgIVbX!>xj6< zLxN(!WxhKiG5D2y^C$me^q)tMXl~10izJ1}z=~G1c-boPc0y7@qR8{$@5KzuGAs+j z=VU<|kPep_TSMw>craV?C3dO5`=X#&b!Zu<?xK!o%*}RGdR)2VvZ1uE@ldaG;fZS> zCiOmmK7v`bUOZbfDuSE}fx6gMs>U;5zn8u2JkfMVLG$8{(7@9TlQLw_2eat|jsABz zm%hca809qm3rz073MozgmwpH~L(?Jq^*K<PRIh|sb$VWaHTfVF<hJPmRcK`!#<voC z<OjlElw>fLMBT))wX%c+W86P&$cMe@`O9_F{+IT&DvyNNdYR5p@O?A)AAlP#pfp)- zO|PUuueuN2;R;ulThAVt;$%YUHC`S%6hFdLnk)WoV-}NzqO4@%0<Wiz^gz>`Q$qjZ z?tdP(mxtSyZZeU_so25BN=_N11iWkW@{WIFv8{Mn@^2G?IhUiOR$T=6?ubDj=ie=k z%!a_kBrTj0N&-J)|Db_QIp^rr92uqJFJHVfe!DToBDFZuty%gjB;SUNm?A%-q+Kcv zPPF&(SA1h^RlH^ERsT-kBfW&UiS1_5-eaNt{>imgg^6>#&iJ-Bfc36_=d8PrS)C)s zfWya9o1<e=3r-7o@(Zy0^BQxRFm#C=SXztEPukHJZHq8BJtfl<+OCjQA?uEv=V#dT z8_PMHqK|igzWiks-sEO!7FPh}g(wIY2v7Tk*JW|f1)$x4Ze7{0yl5Wez3T~4??kVx zBV9pYnBCYwBeP4=!(k`?-2@&c<Kin`B)n0Y<{z0H^*%FOBPPq@_6Jm(=aKb02_2i9 zJADc{Q21sk0k>8$&{lt<Ezg5MU`0q~h+-yYxb(nj{RaNtH*9U?xrxZZV_d!P#rdg4 zto%<LCGK>|0>BBqV$nz`*xcrMJXZ*0$eo`0;mlPKdvMPEafGe0rQky_i+X=EXOp+u z=dq{tMBgY1-!Q_|!AFHs*PNuuCk4YyC3T(F4|Ekc%`0h#M~vRcf0%fwZ^j%o@-u)+ z)@JWw2StW@7dwpes|Oz@8O(AlRGRb}JMZ;ipXc7~ZHnCfa?(c7g%={XUKyR8n&&Vr z7gtV0j`;cc=u-d{Da`VTS&cGL#YAO;fvz3POZI=wWB%hv{m=1YH3_n>-77dZa7)Cx zY|QYp?G=|W-Q%jMHrPnI60!HJ@W700JDq6^?s!&<&<~@r!J&@O<<qfA|Fy{jFT1GC z?bXQjRa#kr(Lixq(C<y6YK_@TjyZrN8r`D70#V47;ET=*n3F`!lW!Tv0@gE*Y>N4v zZyflM3}RjgZ_LVd()4BRy-DC5XS!6aY}GI;#U=vP2OpG;80%Z;4mf&~b+cxoTxX2l z4nIEydK)$`O8-o-mTn@HQhPdb|2P7gCv;1+Fi%Ox)<ugX6i~j;a^w(`A4=KSd06yY zBQSML7{9=M7{mC%^>$=i5e4RpjMDHdj@GpOFDar&9(z+M^*<xHo_LUI@h}kwg3=!d zxL3G%Y37epgLKH)bpV<A5il(x_kEId@=_%nIA4<cHL1~E>gF>SKo}CGfW`22+i>5D zFtr{jCJqZsGsBfEw}sHkWOH~c4)R0U#08fN_gg!AC0`&+Gh=cv`<2CT<wk`<snq~Q zT;8umsyWJ%uEL0?>n4Y=%E*x1sA4N8!*#o`%Vo(tV=_#O7hQ(BG|`m+=3OCQNU%0_ ztIe=8$IYiCpbbgHnHZeGEjd-$C1S4A4rb7$0z{9tn6}Vi2HPQV8)hg1J;VkHE}1*v z(JBhUZe&a_6x4-!GlAq!QZP22aS#^*sDB7x8;z6klqaG>J4TL4Pb5Xa*<IThbnbIM z-n=-@3JkxpG4ZS?z<XvLQ-6BcF|nw}X5y#%yVUg^cDdO3+Emn)8gICP<ye!1Hh>+p zidSPy>h~-=UvUOV|LaTSt+S}^{VFGRaMh0?N=hX3Z)D!9ToOI*U23+$^54nRPew2z zC{V9yAkU9I7RPU6YA{*<(hQ^^vxzDqL^J*ApJ%y?f-CTY%8G?}{IlCbh7kL(D$UBZ zHHwjW6Sm|rWB2|`=sZU(7nG;>;$4uyjE&Oy2l`Xz4sjrY9{<_>H;{t673Hl~ioYcr zeaxMrY8|daV#oe!K`aE6;A9gZ@73TG6i`mj6jlLOF~THoRYAg&R@k2V*nU339U_l# z8f{Z$CfTRIPU1EMr$YtFZNikjgMgNQiG^FTqK|4niX;8&Yeo({0}ed5Z!XfHJw%Ch z=btFj3qh%!w@o!@jy^M)&{2I&(`GrvhsOd$nJd8#y+9^8vFuqqi{w)rlx~!881NUW z$k!t(ZXbTiys;24ESV_)w)tyh5B`lV49-4)Nd!pq`;N?CAHzK(!=Sk=iyOOA%vJD_ z1!bF)2M(ZEr@~PSB9YfFr<(g3c?G<s3r3~`Y`ejuPVKzsvcnP6g>lm^EvawE4qfE6 z*R}uH$W#0(*h|84R`7RSWQ{7COq_yqx%tmIAyL%SsVuuuy2p?DI&>knJ0aJ~@dpc4 z+2U>6Tid*!ewik!X#v#+8Gx?*SimSqUZA^V5o4LS_;i%;9F$r5bxpl&w6BqT<ozOu z2cphTq7Q0qd>`CAOxw#redJQr-ADC|&O4{&0^4jW^&MqILMo*WP*)LSLV8sEN2nWk zjO($kHtbrmj(h;lw$ZKuC&oR-0@5$Z%L*)};P`28tD>$7f~z!!^3>_IOp}k}Omy7k z_ZE7ZnMT9+cs{WbD+o*0Q<F3+8T|glVkk5jT9&pMgp1E^eHv4H3?9oyN90_BmaNl~ zeri*UxccHnA(T|G#oQ9t^?zY0C@ytQlY6!zv*4J&5R10E-!ayt(IaJ+{|>$hO_2eH zDb#oI{GPEfWVSQMq?Z)Q_V8~e3(UvKbH`-&@~EmQ2#fZ7S=$=_dHBwx`LSumVhoU+ zuV4Ws*anC$1oq1c@Yx!YoW7}2;iHCa;T)dVmTX2M!LC%=V&dJGfD4X#tW`WU_KnhY zpAIC*-qPs)NXg>^cQK3n_i)QtoTkMv=`^YL+|z`4zne5CezT0JSuLG16S=ES9m!XN zDwtFhU!>&^U?Z_iCwzHI7Vqg32zOw-J~5|iC5etk^2grmhfEwe<-H~AW0;BJeK&{y zY>G?&q6!-J5(AX}i>RW*&4w+Wk^EK<kQ({sC$i?uA$rmtI8~}Rg;0d5pK15ewYjWx zY@2;_UiZS-G3<{;+;iu9hEL2Fp2R0YDmH>i?D&taRK>7Z!vc&%rs!s>Zvt3TQ$*MW zzpM+;sAu58WiAAe85~H7S5h8n#e|mHrwZnM6;;%sLVI=VlUrF6vq`ky?_!X%;Oe!1 zN$~VdsSi36X!8OHZzKzpl#l#K=~kp*PuwM63{lwU40)-p4@Uo7X9$M!!#ql{PY~)b z&+@(V5unI_t}j*D7RjHH50M=|m9L4#j>Lfl=+52&$dloew|>OAmq&8-yFtt#8j=v* z1}$7?Yc=OjPP?mHz2AiRac)&h`G{WY(>X0Osrtx<D&yCxt_UP?1gg|zOBTMNCjdHT z0kWeEL*ji!XguZ)cXQ%eNklfvHeJx5wQOpZ{CZ#atoQt!)uU~{>m()i?>eNiCf|6k z>qB<F>Z^Old=v|7P$U)%bmwOi?917a{J8d?th&|$><e#3hC^72dojh2^kTB}@AMQw zm#s*L@gZ`PYHCp)*Ze`!7_f$TNz@IAV3J;ue-TxE|L9Z1kCO?C^79Q%P3}YAr-H4Z ztWnG0twkR;sb*o-)fy$CLT*A&DTV0cPOZ1YmEbMi(_6;E54zxB(wZK+Uh&|fN?E#+ zf3zwVGi~k8_CVy8y`!&zungur)nsYhj9w18qtM8wxC}`3o)7YaAny}PxB}*^3)b+i zuWAb0&};ibCu=<|YjCZw%w|&AE7bV<N*zOAEGd~+uY62EX_*81!V=to4zL;CrM*{F zT}6Rr*b8}(r4HTt-IE6?s^bkHeleA@8XOB+<z5AZy12cJK|-i0wMzTGzL!j<e1E-& z?c>dgqXkk^OT8T=XQY>Z!nDpad=C#us=N5!k$_hE4bLl>uQstY$08#k2)QV*3j?A~ zW?<H&@+iIU>hHsngpN3}2sa5bn5%pdv#M!s-KW7tFxM1+Adi=+j;X4bvFU0LLIwPg zx%Pyt8<iLj(xSF*AV5koG7BqqzT;4GQ?p1TS&A3+ISaC(>#E-h?A!|-|Aaj^=^o~W z`nmj%?lfSawSCSJiePPGqm?3zMaA0eIfGh(Cd)hnvJghID#WZ2gw>KTli(MQ?JVmJ zx6DQ6#rLW5NUaJ!H%!e@irqwbchxZXKV<Y>(t=5B-(P#;7;H1DT83DL_LMa}`Yaw( zgieW2ay=TYNEHhszmFlR)g((P6NHo{c#ydiA1})G*Q&H%GAU=N`$vyDHGUmf1V#Mm z`GvU1A?*k6tAmEnE2!Bgl#0Z#6jFyGmR^QI5r|53iYkFJxHfe*$G-c@#gY?Tm;L{P z@c;Yvd`6FSh|FH=3wNPOknIhEohq%5$gW2}X6^!g5x=b&aNC_hIbg4<t*Lc)C`$9* z=~d3oy+G<hvv0H1ZWJIcoU5itAkONhX2i~!TD&xFr)3!Z-P~B@pebBflwC>|a?0Tu zljS!Lar6ITE&7>=vC2*2PY}YTYcFq8^cM>q->I&#Q8#YX4xK<!pRmQ$+qy%P908F$ zR09TEs6>E!+gSH_KN*qexHVHpvfCAi*b6wYTuje~=Di}Ja85leMl8lr6Mp?;Bl4~k zK1bOhS9Qu}N^~Vgj_FPAzb81a$<SbcweAIxXkmFUE0ADT&u~IF+)`SY$xQ&|FA!5z z^UOhC*-DRf5+x(}m1`qp>M#yB5E3=}sX-8&ueDgL%RdpWjaivk;FP}{G}#TQ|7K=- zPlKuyy94M^6Bh~pAww*RKccj!Q(i~-DMOYQ`fK1_kTbfJO3Mom{g9-r&Th_J*b#0_ zd-}5wPAoFSRTAAN4ASHClqWOWIMQUzOK3#6eZoHNI~@Fk83y21lHyHF*xRIkUL0V| zGc!YGkSpoe&@U|C!yJ;q+nm*8C$JdjxA+PBO6)2zqrDzt9nq|WQJP?=25(c#&;S!n z+_+IWp8Qw`X3j{bEKnyQinx&d6Z6Ouc;tiwrmZ?fp>u!c9_DBkej7@{@z?zkW#hQz zWnWo?rKC#BNP)FZG>AUp>P>>V0DUbkH)G|Y<OQf;2^UEY2dHsNnO?(IYTVo)=Q8M( zaKfC%qNh=WPFRf#(yk<-8Y@AxBVPmrnf-+kXg3W%juA}sqNKKG{Szxx<izM`dpUzm z%;T9L8o|~elI)a}-y7!ffBj+^A(A4oY|v*)pj=p37}i%>%o4awQ><av4g_OJy7?eB zxDf%<a59CGZ3U0eLKF^i16VOQ(U@|)L&-`A8hwc`N;D&l98<4n70^DeV-Z(|X?k9H z2@si~ikubNco<)t7-*05Iq9!3zB#)1H}ScZ&ApK4Y*c}%fzIY@1~mO(3MyBeai!z3 za(RcS0F@v0kEvMl-0>Htj$8Qg#ohijko9|ZU1jrAe7Y&uk-pG+*OLEw?G1}W8g`05 zl-CUt3iI&gHOfD}H>L2ERd?i2-}NJ5kJwRjrOc!h9Hly{q*%8!!h~#LwU8>==Xc*{ zx4f;Rlwjo|LhOX$u!JpYW#PdLss0hUsIlX6D@ujpCsIiG<-`jueO>xsH8T9NqiS)I z!i!Oz$zg;%1=RseA=?nB`E}S#w<-)T#xdcwcQXd*<y2k8j33cGDijDQe<%2A4mXJ# z)gf>f(nH^$klAQ(^GcFXOafj=@{t+)vz5fXm0=9&;v~%e7bO$V&19ZM43j}@w1i}T zQp9v;#6ulz<S(WmoR(_-Hjq+|L$pTYR5alg1}OOq`j}LUL>De;Ih43aO?Fgc>bnPN zZJ*@>SAt5y=0nhWG%8T+I<IUSm#xBFqg}TY9`w_wGKrd{k^0n4zsrvz<XS&DjdKoA zy>72fBo6>#NTKN>TDvpBv=yja1Ta0#yFS~knA`(OzukG#3UIaCLJ#1*`DyYyMjK>8 zt88P9#Fd3ivQd;IVi;lvJR;JQME+ls(!bs93<U{Hfk=D=e(Q52X|B_Q#}m^?Mi(an zu@I_8R&AuJ5m3nCk+k}uc(m8gV${cUy2PMTh4ovT%ll-=duB`uv_v@;iRXUQ3rrqB zcAp$u<D3eJ^Om1y3<ASfZYWx7X5^uFoKn&skK9zp9xLa7Z9U`{TBZdE6bQf>rYt`z zSxnHL8f1)Pp6MRL(C=zO{htjqf=!f3se#9jpKR{?1nZr82ZcUVTps(l^btJTXLMQ< z!M}$%-W;{nss$pZZ<0_)Z=~IR22GD$$S#Tc2(b+X;tEY${`c@5^6%-68w2`5b&yIA ziOV8g=@Gy<c*^)|t^iT5hHcZoT-a#H`to7e28!qOR!sRfWg)!gc?rLn=mo{J3l4v# zZHF_=%tQajq@fQyq`^X>!OqC4s`3Z$?!XLWoPu^|E5Z*B#PDP`r_phBxhh=<NlMcX zbzT%sUrPyf%F(Dg)c&d+a^<%Sy*Q*Lz;=A)3MmS#--~Yt!irfAFum9(i0o{}S)G<E z>NUcR|JteV6}hefe8HcQIIP`#2AY*`etlO5?s>u1xGeKaA#p{}Q|9(ww-9EyEv{mi zImeElChhaW<fBj@;ytYKm$@dS-%WAYLdCo&s&^Pc!?Rq*&2<YGW}}D+A0w1)W&(c1 zFQAec?m%!FneO>TRPaNfMP#fWzN-K#fbaAJLy-W?rFupA_EIIVAH>B8#%`cpWS^B1 zEOgRuMTRWzE(l%A<;!ugt!yx(32879tv-Wf49uP(U60j|E!Wa__9VInGiijx{b_J= z;rL&~<Rn;tOnFRNS}`2nUngcBzFW$A70POwYiet2+mv=z2>S_IVjO1;>)O_Mb~=(K zvZ4&pimSux&QY3ZN+vjtSw?;ZlYXVp7#H|C<w;ItTj6|inS>u!cwip%KZ`FL4m5|z z53R^uO#yOSiW2<X={FlEKkyY78tuv6yV@9Dfax-~Nhq>zW0_E!Tv(#Rlq>6MP@;@~ z$+o|HdOmR`==7|%JZSU#;pza}c;T#e)!Nc|Y9Ehw#22u`*Y_`wby$p6)rM$u*Ex96 zFY6{g<+MRK1v`LKZ>`Oh9%dA@#L{CnI#jfPVG^`wWbFrT1-ol|Z!W^J=I2x5;+e(> z-dXfVI|OXR-So>N41C>)wG)TWMW#=;eBSvD&k-a|4k?Y>!O1nFIAoQgOa>HGe%nwX z&7(HH4viP6oX_tGYT&GPhihI5c9q%boLrQ(fvOFHrUY^Bd7GX-V#3>wDrAtPgdJC8 z&QJs+Q%s1)PvZ1kis$E`>FxW|dV3`h)8EyYtE~+b(-<P)U?b9Dxq{tv(Wbb!#Pi=) z^heKv)k&&RpxN4T$mt(;&6;->zs{}DT^%dFO3|Sj6<{O5?h?A}0&R!0M!`JkIC#C| z$$a5K;j%J-Ob@+&x=T3}q2J%?;8!3`fW$dASiANgNHWp<F&nXa^FNhIqS2bqbtH6h z-|@2a@~%R9%YisLN&7;u8TwhGvI=#4r|Re8eZ}sKNzK)vr0jYiMZJ4ia0HMbEOGW1 z_^|5h>6*#mI^4DQ(;oX$8&X`8<rL3`nlLp3Yt#Sy0{-{RR{-afy*4UqEHoP)oAI+z zW4C(9b<y{iFQI<&FqRfW>0$qouN+W-Rs|(%T2fCj{p{mUl@^op)A$;%$<J6mRV(j4 z>Ea%eEW~xQr{-PXb+S{!lnnYQpALP1Ov1q?f1As|J&T3k{lc11NZobv`_ZI+897-g zEz7*CsmfGxu|Ohc6?rBd^9_pr6JaA{%m}Qc*#dVoJP_l>j?>&Dwo`&aSTW{wy9cPz zTufOa4s}>VfE+W~HEKT+V<nRXq(WKgEd848D#D0avy+(PpzDIJ7UJtuMvFp&@}rCy z5=Pf9J&7BWJkI`|w1>t@0#1=d!(F7*$n6IJ6_{8;0a2{QRy_-JY5wa^N3!iXO!bv$ zD6zFi<EQvg-8C?raa2w<&JEEY`nD}0v*Z#AYm9m;Z?#FfAg4PERE)O^$J=$qAseGW zo~42nhKpkya<01YM;c@_+Kuuyr$M08bTD9y|7K{~MXg&0Gh0+)cQ2WO+YTv5)~k*~ zWJbhR>|(?Uk0m;4`Ut5qME9W5O#^pu{KD2NNh<zC@6AC%4JW>##nWBIus8%CB|tqz zSs{^L?x5Gm36O)yzs~MNogPXdvX!#FAC3-uUsAPBN1yPA%T=D!Rgi|s+<UpGZ-wdH zJ%L&&y^r5~oEX8kWA|%rC9U@orQT=)_?hamzJVme_hmAar(7~{)(ye~Yd%oW_k2|x zSwiqQ-`gUs&3xSqEf@;t)+e+=Y#r$_b5<0NYcbnjQ<KePonN--(XHeNNDrWaUhV&^ zA8jm7`20@sJSji{0<ycC$Fru&y<?ssS%GRW15r=c^B$Wlkw}oT%7>d-#?9HkUKlnM z=^YNMtyn>N6pxc(R4cRIl{%yTB?}nVK{%Aj=&S)<luWdj7eW1qqGbD2rSak7;qPzJ zzll3_eF%%bLfzL^s1fi;J$D|wOl|lGDF>|Ma*a=1^p=^&*lGL(!Ew5Bs_oT^@c@<4 zI;c%Cp3O*K)0tIpKSu%2X3^vNNho6{sV*UG6Q%4OBbsm|%+Lu(7;&IT>^aew10pd+ zpqeB$3742A7XLtkq5~o_Nl&pk$6%>e>{o*Z#?ANJ0zvX^Q5uZ`(`!=FxY?*Cnaf*v zE-N~`u{3Dul8M`if^Y60$HLPXjxoMTf}8Y%Fv?>!Da4Vs)R>QWL;skKl0nof7AaRm z!#Vxoz$`?LBqVl=Br~lkMcNhSa?e#?Z|%@77Fk`7!{FiG{T6_5*`;DdW<l%Ge{tGJ zD@ZR^$4Yv`f|rx4RA~joAlDJj_?B}3i#gd2lYoN<_AdYTV{z)5=$8<$$`Qk;DrUvD zjkL&sY1ewO|Fe2pqTrkYxJ-#kZGZ8_eGzLGs;CJ=b76LW{tG;*hY5%>W5Nwi8g4^X z8(bU+`^4w}<sJEaAg@_1{IO={_8l;nc?K0@dW8LK9zzh&7EXkJusM`-+T$Jl?aL#B z$Ludl(lCh{PSAiV?ks5(_$Tdao3ZO6d1dn&T0Wf%n10NFW%_tmvXA`l3A-!V$r`%< zF4_Tpz2PLXZf^a%E0A!}@=&;!y+iIz?r+IoDtovasVgLTjXvqZIG0<c#Kqaxi+PKm z<zhZVFCt;cW!?DCF5}|?Uo;J$$+D821h{{zYl<ZfqOozP!|<C;Z!=yGFd=sk9(|zb zEx@~2rB4?LDfo7Pa(G}!^ZX@>ts#+FRoew6BVR#~>WlCKntgeUKfa>ob=B%;Vj2Jy zSfP!dhPE#aH1HKExE;gph`_Gc(w*sB@-b|Wrs641K*>B-1)g6?ynRF^K+^eplqQYj z-(~$$`^vsuP&hI~Eu<|8Y(_j0H$;h*J0ontN?qQZyp&mY%VZ6~=EQM@D2Hfm>ftrh zGO@(%UtH~v2(!b!!u|jaTcdK#Ms?9a#%BSk$l7JXr+P9q9kmgUm_nO06SXx_8aH@M ztMcYQZ&;+^waj7Pk6HE!e>TiS9*TdLr;u%%KMs$#om&y3mko~0huK1x4}CuzHY+y^ zzGN@n&hKuB_Gjl|M(5cl2PomK@E`g5f&|T&ev#xM9NOA#!>b*WC-_IZyq_geFam$n z>-j+uli31i#d#8!s3?B?Lxxt!CA99-%esm47{`G}JB?mziFr=_$ch2W|GD)z%Ag>A zRd=|grg)I^de}>)_FeoET)?VdLs@qIdUlRZao>Z`=jp%TDP7hd8520;Ujl2bhHxut zGpc+bFz=OIqcW}wDP4?Wz4$^6QNYy9*^UxWtP#=#_50B{F-e9}831YE<dA4))ar)z zSb8+={f>ojzdL0qYCs}5nPSX}U{5~Q@szVSsjoupvay}H<~hyG$T&jTMNWXbK$@bJ z50cpV{D5K2FFnYi^gC!YokKs(1nSg-R=*683%D}GjT2(?sU?xl|8X6o<M(UQ3^l-G zt-zzCr+sA(hcK24v%zNdS@NyIM&XfoQ2j-5-IW~b=opshVTXf$eV}lgP!{uZ{h8!i zWA#tC<@&oIi|+D<@nnum*>vYJI;_^cfB@!wLduqJG^RJ>4utB3dCcJB5t&kbP@jK5 zGm@rFPfv%SLp&ivSSS*v-6z&}uwEN9z&e5hrMf@igtk#pUO|=M<hII&#=pP(T^za7 zz33M$yzZ0{A;o{kl_ccEH4IfUoJWDu6u~9>KlAnt)j#6P{hxK8(j<26`5>O>Au|^D z&O{j|DBoSri%|yR-5e6zJ(m>*+aqck;IbXV1V;>0TKp5a1GiUH{Clv|f&0UR`*tE) z#NJ(aPSROGlhb&b+@z+`ywRs2sjmbh{E!DJ%DDtl$3P<&dRrbBZVX7)U(G9Ot%$o4 z{JsrK8P_;jSVDWxaJYrv)B*^ZW|#$ES8!`i&O^t?(ON;~HSJ&vbepa)<hv{{^CC5L z1kHVt5@FRV_U$osTOLi`>dPp<P1KuYqiQlZVL0{h{nGYDOBq832HOy4^Hm<jNWDzj z%udttp^b;6IWAKbvk`=>6<C%{aST&C3(OrU^6M-&!~fbIdyZS63QuPMNijKe`v_u& z;NWDF1WK_T-G6X&W+*TN)zSvg>yDu9J~?QLZq}Pu_XW{i;m|%N{T2dl^3(nG<5ZSr z#~V|@x-eg$0l4Uu>hl*#isY;Zy_0uJ5@XmOhi{4T3-t?~fJ@$zhUZ7HR8RABMr1EV z(M68aQ2i_^(ESGxmWceVX>QV7vP<#C6Hg#ID*7QWm#Ms<!fp{mNwJ5Yr`MY_k<!{U zfph#4wP$8bO1ziO$1Iu^XcQuDWo|~pR!WNC#|^!EwM~~R3$(;?Iq<-)vnVsxyYuXa z(nvw`>`J4)kC=yiAuf8XO$HY#ZYHIUIFq>RW*>1(#lsGd=mSPVUuP=KsZ?3MAjaI( zr#lhOEQoxIuopAY_T~%645bm50nC>}7Ko2EiH3XJ4;(;Bd1w-xF!EyD9mjHHF$dai zln)%z1zBO!`~q`m@#@->%g$VFefg4ue#azM4m)63?aCrE{2`xC23!oNd|KFNuVH^# z%GBCvaK1JZiM4TWS)*k03+8{9<0q6u>dtfjbCiF@b5uQl)Yv5%w-{!U#){pacO2b{ z>xKeusI8C#^>PFT8HUi8sA5bc>M;iJgV^skOES7n`f~PK2Szo9E<&Ss5pyLXY8I(q z7Y|Iff=`-lA;pW6Z0G=OH9{=2^d|RJHIXaA!cH^yTP<Cu1zQtkX%36dpF;N+_cyeU z{~uXj9n@CaZ=2xmP~6?!-Q5BdcPlQ%-HJOD3+^sON^uKrEfj5Wr?~5-?|Z&`&bfE? z%+6#ce<ZWBfBRYTtW}5oE-ve}L>~=g)miDAcXK|S<sQ+E*@iqno2yO-+4qtsL_w44 zNGKr#R1usH!}J6we(#5+m*WegX_K00lpw?2%qAw9Q}B(W&#Rb-=D85WBE-=rN1au7 zOEiyXVAg>B?%${m2ihJ~ObzQ0PCD>=(68u4lgY%g$Z*R=eRLISnng>mJd<#Z46dPV zl<ncAVV;rIDZuudg>x8O`|5{J$aV@qg2sSrsPw?i5)C%q3qk2}&}C}&!vmXcB5Ce& zlKKe*fIq<YB_hT&q8R@X?5*_8#Cmj4yOHYhip_*pf2Vyx!?c^2qrGW?-+|`5iUQBp zBP}tFW@*NL>4spY(pjo(j#6<Vy1#Q3LSi*8V)G=Ee;}Yo{{I~7Ib%>twnZbXR;A(X z*rj7;za~vXCcQ~$#^ZZz@vsd);og3pXdis1QC7b&L{<*~OQL~^W#DCv2#P1vgdMng z8euO_a*YXak=+<>V;2(OD^MPA3_ePBPw?)XsjfVY38+9SDFh0TU|%>;J*POv$+YRY zozs#v^6Bg0?T(dWkJ|>RL}C`*&6CR+6zk2Wu>s!+CwjGpKi)Wf3Uto@xUz(QtsFV* zjLc|p!P9FSoSNN2eC_2fzE^)x0^dWc!^*Q#=zFafW6C>v&`a0J2ge;DGO0nrhy3g} zD6*R(3nPUW&#i%5iS>%_#I_rRgKVbt=MY<V{41op>#DGJrd#RdM7yj7|58J=<MVu5 zy<5)j*jrMo*UI{T;n<_zU(=OM)|F3FFJB=nXIGv~k%X86-Nl?LyKzD^R9CLt#`%m_ z+D0lY5_~HLzKl%2!e>eR4o<9#5U3M|il&4uUs6q9g7m2wiN?x+*peZo;FubJKroFv zuBt6XY#N$K4XAI@mtF&Thx1L5#UrO{zUPM%5KGvD*#(&okY=Iq4jqjcq`|svL?vdL zM98Iw%c4PluF=Rsgh@sD8rq~W=?9&HqXYT^Lk*RVJyBT#$B1ipsLR#H5M9ThtY(Q< zF(?~GDA$Q@SAjIdoC+uzeDa-$4<D^TbBPg%nqYJNJqz0h9L7vo0CsG%E6<ztRQ(ap zQT$E?S~lPH*mi*iBwDc;>oC#me?GT1>kuoGg!ZHG;$M8jvwF65^^t@?HCHbo9IaIN zpG93BA5~bn0R;@Rd}gW^oOl7jAQncYDwP+UIsP$opGYAm4JP7tQN40~mhA@!wOM9j z!tI4C>4^wWiN3kXE<>Gjj%e5Mt+VfDtn)jz6I3TnsTh5NK4s2dLY}TuL{Af62^KCj zp0YdpxKw`8{a<wRhAAcRFjlv>G<f=SMKwDTVLx`_p`%B;#`WS6Z`_f8;{Uz+4hJXX z<(yx2z+{5nJSyi`BjM8JyKb89-OZiz^VxTDl)_{72AyWVTux*K)+<>fFmP-KDO2&H zVUZ+|7;|UmV=t3F@U8i#C_4JzvXU%B^VJNhmzf2zX#sJy7hPE2R%U~!G{X`%<`B4# zRuR)M3XL2l@T(sYq>h{2gY|E%{0|6lzXQsX?nq_DaUAO6#OQSPn>Kz4>}xtmXuwEm zcUg}i{wE9Ie3(vz_fK@&*q}MrE`JXB$}hu0ewZ^H*V(x1G=QHTZyvBJoBWRmY|UKL zOsIR=qn0_gP&xzi2VADY&kOy);||5#cTlbT?`5tvN!AxS(Q|Nn86Z43?54{rOUS=+ z8dh#-&HiN7Qe}ynm%Qh$uhc%0Z{<t68R$R<nSTw<l+IskB}T;w9#7CM11Q&O-bt8E zOx(pjnjs*c*Z!VFxM1vynU6cyCoSdq#Qw+A*dI7Ehe|yWJsg#&6J-HMwG<G~lmN+Y z3YIF_s5VR-`HcKHXa4klLL}fX74W~0vq|%i{kcuPR09D<Svocg*Qh4{Z2%_{x66y( zBvaR#HV)g*XwU<W3;mDxtp#DamY;~h2hk#%;r2L672xx+FJ!CY)i@arZ)PaisJD!i z5vib^dx7?BNMB%uI<wm#X~)MB*P{Czu1O!6UX1lhykH_^RpVL$lWeZh$icT(g>q{} z<T}3S*(CM+o22WbLHrVB)lr>h)BW6&h=D1`L*<d7RWF(IGe5DnXjN$vSc94*Im~V8 z3eQ0I17?|)3{5{4t=2p!WG{h<wow}m7;d`vAC0jF{Nt5%mMsC^b`gv9GQfaT{7T4C z3OVD23PI!NX#vj5COjpS^`qODpwp0w;Jysypcfdj=knhK={m5TCd*<zS3gxmXy5+f zhL$1jo-M3`!T}k7X%tj@;Ila927h=XmfyyHE<f*AA}4*ZF*tnsFdjuMwOJs(2g+Qu z6nkQvN)SOBQlD0G^x<N$_HupvKzCrY%17SGHlV8G+R&GDvlvDz-riV>8WjsmGRs`3 z2Al%2((r6lPw)<DG&1i1a-X6@rVTD6xFvc5FeSE5b3tkzqGOp{#3)3S%n7+94iVG` z8fqCE4Vh6>6kQ?|j7TdXsxO3FMxkglwr8oeNT6xk)8xplYl+!I=SjLj!<B<!K5^nS zv3n%<8(74TDO>Vi!dfiPeaTBTr8n4Ew-(z&jN;6Y%HTkxYGB^`N;_rSCQ&36oDQIc zM3>GAmh-m>EVRZzE9rldM$UQWAM~n(8XYFt=D1PtohulR5Qzzw*ns|7X;t0|))S$) zJ_h!od2YPmLxBky%xu@^25ker<5T0{QONj{a)_>161F;nve>CVSft<hc%?LvJX17L zvWHb(*UH{1;3}eMb&4gH(_qE%-4yFJEA*k)M=V%L_FL!2oI{@=GoegX8}7~V)l9aV zEa%eoYnOuXt;|yS9D3(m{}+bu&G`idVT7<BPRQ)pZ-kHp1s<y*6^*S{e9qDtUJEM* zA&S%IC5qLnvCYthR@}J}Nbj6OFyXy_HZg4sV5PaByj%uDg`>eD1Up(5jOoa;0(u$f z8|YU@A(8P|JBrD%U$B%~d?P|J%yAdvceNfr_lInM8<ZGCzWYj3LXQ-~LOTf4VjVx} zn(0i?bn;Z8m`8F<$%PgbNg06|LGoisaUYXx0$)jXNnjKy70K^;V21Ly-K9@NKk5!_ zHK0JB$lyu<so2PJQYoXCp5m8H2!d#kkE*g1IMw|G2J)MLLlx~SV{sI9ue7~`Irw{t zdMVIFkx)-Gbmv_|W&zVrUAv|eO|sWKMQevrWnT`0PaJV=VY$Y(P<K^MjUj6g+K0RG zNqf6Egfr$IS0Swm{QA_vO#D-h7qCFn)uLrqHYT)6!>lcCv?LZ6NZcr}+t#VTi!O2l zvY|w8kv7nBlM0xrN)GY5Wtxr{$S_H8`=rd7S42I8<eC6W3hm(g<f=^0O#QgLDn-XJ zZeDN&{vYG#jKvCgOZe__^Mph6Bmm-!YhgEF+2Nd2?Sr+(PxfS$r5m3JBip!;5B*v4 z^A^3cWFQqp?Va+k{zRn))jt?i+EFBFsQL8h2^XIh{PQI-mcWD~D$Gx}$lp3n-{WpZ zC=E(^Y5c$^`m)hV=5tc6R#}Lmnd%q=9@l`|xbu?=yIYqNp5-g|+pom<PH&zZzGggU ziKkO3wOh$>zT{@&zFE?zk9}isAvduid6nB|r2B<+whh2(ITYQ22G<tFM-Ct5x+7DZ z`=a`@(gBwpI}U_uUQ+40M`@}@?Ar;618eyF(X24`je&ZOXYY^+w28z|U!99Ug+>5p zV<PK`b8ZSEL#^mmQ*5Z3y}KtR!xfDUX1Gu*wE$?#_&k*A9{0O`&-izFzeb7oraw6+ zBWHb1X2y7ylAselv*tiZ+_%m3Bqqx$b;0AR(3ki{z6VIJJXWKdqOf2(1<=zw<$+{K zuu;*RToiW#KM6~xHTNpObxCXUku%&bDM(s2h%|1gyo4S)Y~MER3c9&vNX|lhNPwA8 zLbw$TkgYFrn^54O57tLIEL0|a0xHz6%lvhb022og__$Fs0MKQ3R2>|fv@;-U0p4^? zYy=7;ekRx=e_yEh-HuUbuR%}S+hnIh^8?CABn*6<dXpi6OB|4p<O-f6de%i$-cg#v z<!7aopv9^k+;}=7>1M{)bLSQ=M0g1suAc@4A-^aE8v7rii7y1Of(ly9DQSxy^gOkw z2VFgku^}3yF6aWi4naiiF@d_nc{*XzmC_$7r?{Dz?Mi)X3h<}k-ii_XyG8IT4yi2| zp~_vpb8)w*@zi~og+`Gn^(l^zgP%vh?gi;M4v&HbGnM-Hx`69EskAkvK<}!}E^&2j z40W8vH-K+1Y0ACjD{pe-m&y(`xoOjR>V-aV>E-n4UC)!@+^*=i@@bSCxb9q#%;olv zoWckdA}6HI5R1~f)PMN3e_6J_=P6zBay|&B18Ke@k~eW+b1y@P92`by`Po7GXC}AJ zRDAf21A}wqYRt5zD;Y3(#aQ64BvI<A;17xlZ{M-Pe=2SDgbCgWY(^XmpXr+C&~z6L zqu+Q&VGo_+#thW)&#SGa_~Js7mt!?%5+~`X0?|V&?xmpXTgS(K+R{_Wf++G2+T>x- z>3VZYak7`QhpW@7OG=_}gwNxVfwzwS=wuFLR_osYIsItx%YVvqYelK-YJGTP6wS5{ z#tS3-bD6c%5F!a+u_Fg1sCaMe-2v)@NU)kj7AYhG+FLtiZoYTh34Owxn<<R0lBt=c zgEj>2%s}HYS~+t1L;a}8Bz889No5$TkqiwSuJRGHWts0_n8^uqLL?<so_ewf20V|6 zQ%b5pzjq8$yK&Oe#-Zm`?16*40*wII0nG6^Ok2h#Wj%SUtz}RbWU-KI8J*3`Bn~lQ z9x={vI*5h1^GY)lBZI_fyVza0OXi8w79%K0OC4$eGByBU*M@I3q=Dp6Bcqs1#X4^y z)^7}(hs(__*yHn(WdGlpbk>XK!shfztOV!!xujU-cSpvzq}&T#GIZ!{y{jEvQPQ$R zc;nS9XqJg%VDJ7`$KCSzJLU$J8_R%z-~NUjdv~3tg<biz!cK8&dxC6-mlJd5YkAf2 z2FHa=dtIxa1}Lx$?n=3P*g#R8t8jOPfv0<@H~Z6yu|l^}NQt?(NTyFiw|N$8bzN}w z2R=voH6|-xX{WFA4m^{cExH|%il(P*e4GxhiA+OeluN!41Xx<&lI;?o4KRqJdzfP` zl`?KCFmt{ynqE8rmRY<%8U#8%J@*SE-cE+|TdJN%ZXslA);T|Wj9m+)Yd`2hSNtet z_cxz#DbuqwKJt_lfSB5J@72mIHZE_0-T#agVec8xL88h8f4p88bW~A#v3lFdhupt4 zX)Iu0@PxI<reQ23?6P{o3l;Lg&B;7j#w}1y9mGd@G4J0J=4PE-=-QaP!>O?eF0UG7 zO7=kVJG1w5p1K)=;w)LIHh3?!Gsd-@Nng_rZELsCxuoHA?9fAZDnhmnl2v7Z&eX;@ zO|W!<;!f|V<FNqk>_iLsXz`ahpvd}LX8sB*Jrt!CCD@Gjr?9RLam4aGif?v16naC~ z>ou^deg-x@DyM(%zwSzA@+y&7p{4phDZfly!tH(+96Nq}82v)OPn_E2%^_b{8x$3g zd5_MS6J=8z?b2i5`4+A_0TnRw9J@^l@mXRQ7+SJ8X&>H6rQnQQ@9fkjIT{1CWHyW3 zxx&$iHlf25QmA=TXzs_daX_6E9Gr{DEgzV;lS=ln=LFo+0tU9KlNKxLv6xS8`_pcv zQeXq9c{oFD`A&vmW<5e-fVO!cr`pE$#2Q)EqN4sX$yu7{-fx96xcJ;VUs-YYsd_D# zoSsrAqGk~m%w%VT;fb|qF;io+-MC2QcqUv$5sf()>#Y~1e0p)KhW4siVmh-@6L?1g znNGOIW|4WX-H8{Fwh)73+<Z8Vk{7cy+Mi*LHLvlu)McC~_XBGsDN7YFtkQ340K#v- zNR3CjS{W(=DTTO7XBex-YNo*!)_nwR2Ckm65B{qiHv@%pp;3F_aGSoRz(5|Y_a{U2 zL1cxW0JvhT&Uj@7*u9nB0dhad`C;{MCRjvuUA<1r%cmAraACI!Ck~*jz{?v|<r&k) zp^v2FmnaXxjH3m~zDJ2xgWU4}--=Dic!6QzbS`H<i189nLL+a}nlsP|=*>tAZ(1#) zh=Wi>i4`1yH!N(a=$FXwhIiHU*w_6<hhO7_e%!sfdMz$OC}9Agf-rz?U))<QFcLI^ zW)xqPMba`WJrw;Ju80_PR0+Caud$VKY8$WQ^`TD1mR!QE0zaJp#}ZgM>H%3COYX%u zUIKP%r_2tn1e1DUV*;{H8BIHWAryGkcz!d~8{@IWN>PZI=y3$x5Tih2#75|Z8UdjE zdi=;Npp(QCk<5CKAhcZYYNdP{H>)LT$a+xg>w2L><50GP8V-xJ_<}~LYGt&n2K3_K z!Js#8ify5kNjN(Vz>?=!gJD1?HbV?P4i}DkvO+fBBI7`oiP+7b45Z^Ti1hJ~qGsKq zgyz#!F8eP5lph1Gox)tY1-sOA@<6PWb^DA)?D;=ZmiMwu*X$g49j0EZUwivwpI=nH zqt6#^=vmtv-<8qoO!=ND7fU|1$bv1-<reOd@GhrJ6S~>R=(LpaMzSBt<DJx6ihp#X zCr;f4;($H1Zp^t43DAGuRwkG%5WUTT|7IO+nAd-=7-2oHtgLJ-x7g#D*X0B3@FG1; z(^of~?@msfV9*is^en`#FLuVw?Cp=Fef3@9hY{8rP5MM`yzl_qx(WA#*lL4;Q_zeo zkifKQzu>l>ho{R8lh+-Sev&85JqCb$Qu1YI;Ipsyu+VQzUOqk(Gw6lazx}mlK|x}F zw?81R-RG_m>;>XkQsW<e$$xgZTVCkKd(%$;N+y5v0^cvLcY2VJTRc^&1u!lzUN%od z#g&Nkymj+N?B)5fo|Fdu?T=Zo2L@g|viWZyZ~Se4-1K=pS$pmPU1G0ZU5v9u_T1<6 zMDIO62ECqAc`L36y`K)aS+W`U>v=;!5%N=UTI(ix*&utNFg`kc>^?;%B4S@{D1T&I z{C@d-JW7`@oOSARq?9iLg$xF`o>5J*NWZ2mG)h8?8Rw@`!tLBO5e|{Cs~ou4KxEKo z3HdKFMy5YTQOM$qs{xOh4HpS+SB-<P@`3{@p)wDzy;~KnO=uIAN_#3Lkm@e_dwvGh z73^x1deiLX30;({u|XbN*r!8H$ifo2QnN6b5XS646GmL`+|57-&~V21u0r&F_%_~< zIKv6kE}#&$596_udqM#9f>)%FnYQ2UaPqllXq8;AR^f2g)PV#Mhn~a~jWcu}HC|;h zdbS4hbh%vkXj~2-P}|7}xrNEu%J^-Ot0KCC=33Rb3afH2a;cQnTTELsF!I}K(9kE= z5XWAq;IvO17Y{$UW6%%h{JlW8WR3n#A=Kj~AHZ4P_R7On-!)?M^KFkG5BG>%>F0kQ z%V%Gi-}K4E@$VnNmLbFDA@AK`$oi;Vl(~2zTvaNoeQW^YF3qAu%ZPP)@Cs#<O5>&T zmSJv}g9Wx+-k}Ze3vyM1%tA36P+qY-vC1(R)TDa9Obw4u91Jqx?{}ziUi(z|A#!SN z*Bb3xla6q0?GLt`V-W8i`f2$00?aC{KM1vg%T;B*9T5C#KzRk;%m>m<1a#o()rmu- z_AX^#Rd1!Ck3=Z&MefD?O_;2z@@G#RgVF1B%~d?9hM@}$86mogG#e?COrZLc4J)tL zB}$ZBu#uS(r!+y;zvc0>ywiEp5V|`%<s*ja{2*=myfuHpp%0ArhlDSL(~WN8Ea>{t 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%0D4$I?2M&PK_1o>VgH&TOJ^{1IzTDjKQN9!e7$W zdjHM*4W&L#T{)<8n?JeiDa!X5u(ZgaxI{^@ms9J?t^RB=&fYAimJCzAkeQDtwTU+v zJ&bdg%S^)KfviOkn~=ew|88JdOl6{l05;>s^x-u}EWf-u!&1xoHXHKIT-~LtO>)y4 zp(d+^Ix>}mTSX=GsLXwzkDCjy15))HHq%Dy!FbbV+}7Ip&`IwhVPVf_3c{dtCgT%+ zxqE(ZHYP2<`OD!C5^PX<4*48XX~pp14T=G&6!^ul)>YnOy%tOqGnNoHr#*o5x8Fas z5l>PjZ5fChcV<qXn-+zlVT1ZO<6PPkG+#|}ojU_L20SF(>qlp0{a#rJnkbf4I3AO6 z$`juPLL^|$Y=`$1_yA^-fy#7>kPMIyGdj)_O-h84fX)Uc)^YgE+EzW^dG;}Nhkq+P z8wugvp$I(HM9s4G2Hk?(LeEV00bbIFY;09rBfjD8%0T6w<K4>S5^-wI<&A!5ro?<1 zg<+<7m!W!QA09Dg1U2ogZ}mbQ>+A++)$jdzG-;h*#NVu=qH1eWD*DvXN!&|P(!kGw zS(pOdN^!=>21pF?EDOL>z5=cdZ<0BwWUll;3>}3zy{!0h+*i^1I!8V2IT9}TE^Ic4 zs>`3H{BV-xUFIjX0*e7GTWlF7&<ma=lSo|+8HO*;?|9Tf;!O&ixgW3|5J**?lAQd{ z<LbT$xU%V2sAzaa@D>IjI`N=?vijcxOFhda)Dtx+JPt(s`?m6tR8c5hU}3U27>pf# zTBjG7r=M!<iO=!WV>L<&(5U!!-iJQK{{z&1YK`H~fo`7G3UNwVcO(pp0Pka4?2jti z%H`LrV4Tp%WBNPp3%8-sW>)JpF+=`*m3&{wbNT{^rg#ZU-I+OcxpriPn-Z=Zc!?b* zO|jLjWd8ZnK>~%67K|J5em|`>hcw|S8yd}le?K{8QDdT9>DSdj$qLwdhN?o-%=Z;^ z=mojt-I4?&o+5=#nu@zDpWW{A=N`<+D_@bai;=c$#rSGLC#0-ipycdXgQyH~)T^-! zVGsLX=$g;EhEZkNM2u@vN|AG-j*?gB%?d@D+m)Q%Py?&}^&{bEgh74}YrjI;-UsGC zp$QG>RgGUdlJe&g@Vm&L4G^5Ch542fl<U&WHZ{&>!in(o@1q&IKqk#P%V}e|1Q+ZC zdpzt12gvsWZN<yTaCs9>uzb-s1`jqb<psk~J~reeIyvk`SQ~O*Z@~l)ovd;t#yPTO z?T0A+Q0sa_2ird%r=ObIb=u2r?9m8NoeFA)E~q5(De_4_CfF_5vOlXNgo4SRIWNV{ z#d@X`<^903Pnn1kx};tII=j}0=IV|-eHxd<LnbD{R7y&2Enfn+5-bM0E&GAg<!YT3 zhCzMTp-;JuKh3#3DCmT6iP6TWR|u1g&G1^TtK7X!&@R06yq$1mKWHn0YSal^)fi9X z+KB45&cB<Fa*rDtY;^@h<5H=zfE_^e+rJW8UG=FkMZ&-z{QFj9murPmVd2$i7m2cG z7{`2>t?4!6lK8zrA<rPhEg`yIwz$Ef_Fr98OjOJ8T!5~Hw{QRMD*t_6OTn_Vn|o0? zntiG}HF8;XSIQJw1;2E$YLom57Fq0blb#w<`v`=fur{NwJqMKM_19N8egG8F*do`J z6w7O1-0<z`u=cW*YdNijSz@~uWVlkMu9KW+pqxXl{F^;QK_<fkrIn~al6Pl~SOnvH z^9$P}98IKvLUGyWa)d5KvnwO<2Y8~>2kz1JCoP7D_2rHOeEgu~VoCvyYW+;#!@~;% zW^%pVH`*dWRS#c4X&p@|lR|_VHpx}+DaWVlZ8w^RdJlc!mtVNm+S#7AG3<U7e{28S z6ossEs>hCU#z3QaWGcB{kh-FUR&y$8(d>P{D*~-~CEoSt&5#}85C!m3Av++_r)cs( ztLmO6PF5@+jYEB@C*!5K>AsfWrd@QG9{PrvO<Ey)20PI!if{^!%?ypt<$VYla|(KE zzLypob0;OU5|<T^!^~O|u>zB#7KB^(H17c>_BMr6q52FN;JI*3vsT=;od}^&eYn)^ z*gxbwe;NGNzsAC3e^&S*FDJg>7sXHWXE)V83m9bDd=kcizXpYtsqA08D{~a+Etdyp zVJ6EiKkErc>582a?{nh#I>_?N%u8GCd+Up1vPX}%(-#zYs~+8g{k>XP{n+`ptqQGM zLVUH1T`7XiP?S$QVW2P-H?H-p4B0;kLYVCz3DK#(`C0lMDQ`R=BMLY*N>OR;KG&f8 zKCE0-KK1q+o4H;lE(RjSv@I<A91qU!yw62urDGyAl|=|g!za4HwI=C0_VtVpp1h}q zf*C%UxVWI@Q?F+o_dN-Czjb~G{P(+lw<Z25yxe&K`!ekuC<6x*4}CC5`Z1l)P5rFC zt^Tu19;>d@kd`xMhEXaZ9}Q*WeRRzDGN|el#KC1iFYz$TQKj9`-I|?kZwwZ`n+NT8 zvb@m*>E^?W43U627K3Oo8EWK)v#0;f*{Amn!wy3parC1B0CylbjIv71ykRz4u1QJ~ zGBE6BVH=H}`pVprp%Xm60tVv=SPM{|k$VO-AZwt&>jfiA5qZgD^8IWkv~LSc)$9u} z!$cg*HpS}oo!f~cj2FOLmk0*iBV9?56T>&n3NEDhidbxFlb2mPfEOR9)^(>P%Yj-7 zrrI5`rbxv6owyVXYjV_Hu^(O(6(4;j^>pjn7Si%0e^TQb77Q#pq;TBXz_v;G-nEu^ zdrdsWs)*|xnH^XDNQc+mP!0qPT*~`Ypg|4RS!4T&p_EOBW#{+~N#AnfasdSe<jsP> z36KhQWBv|{52v1j@H4OK*Wyl|G1at0HzqTq^FqB}F8-m^_zRuZk%2R&TI7tB8Y*K$ zWPLi*`xl#RYUiAq{e`h+(kPjy(ZJyvPL8N7@*^#JS*sRRAeBp8ptzU;{C#_8s9cO? znElhrgE-1Bot-Wt>SD1JvT)`FCO}Tb!1igwCzBqLGKu#?g2q^G(kz?6P@F3-Pa?vq z-vFxWbfiNpol5l=Vn?b)*dzngdZBbs9}oHIc%=cdN+DTRR}2>naETz+JyQ9hx_hLL zfod+omOxVHmfXDzlcdM?ouu%*N1+b52=(X76rpE7F?cdfUw2b-9i9R_7PUw{%JVsa z-pw?2h{#b`fw?W^S`Z$7NlT}+Bj&W+n-G67Yg%M{#+{{|ch6iIKms!jLYHdacA{nL z^WLZikjQ!{bfN8|u3)4nY*i*+s^wrG7<^`Lz1h5yes&OoWe*J;Ya5HWYFE-JBb(u1 z)v@IeG*`6gG+^K}YE4DMrP6Jez-E9fTS5Q43-cJvGtVZVA)EW8U}~Rs056z8k%kHX zPfEq#_Od5Js0?4#O{v?tfz}!;1CMR!=Sqp>1EW4;$D2fndFJihlrs&*M}{V@%W#gC z*AOOoR*ihj%)$iyw<vu5=Z)OPZgF$B^2JhU;W0dbI?X?){+l%vYL8~nE63tTMvrBX z7>x2&6hy<zkwR|E2(OUJV<g#kQnz<)Q8)ax3toA@$K3Nack0a~$_vib{8WQE+V4FG z<+pF^R~FuclHNJuDHi-e2q7#ksjfF2XW9E`TF2FLHs4>W9x+<&EvI96SdYgpE?4TO z?XKPZ$2#XAWnw>LdjNf${!dZMAOZ<`2(d7f2f;SK10Vz@%yaS%Fw2gTggg%m&A zfvL(qf%L9(IxOOX!f(fG9gY{;UJOMMM!$e>idNpfg!^!tXlIsus6wjGZ^VFe&2J^e zbILtqpI_!0F8#R)S2n=aj!?8;Nv(cEQ?8IcML0^r3zLlmnm3^E2LK`UjIy~ARtk!W z7T}F}<W5{4?tWID1>#ak5(<k`XcZC$S^P|iHeDqyAOE?{Mg1X^G;do66ZfLbdko(< zw@011i4i+L%;C-k6GMO)X!4y&7_%^+P(#N*6xQ!{h(ccRJ>pP|xtW@o7Q%#R^qw>} z{T`q<5oXW*Yj1oaPHj2^lxnB%b~9A4ddxe}5~mu#6ITAmB2<WYkK3g1nzcyX>rZI1 z6*R46k{%9K$Ph05XrA#r>7#FVxlMYWbV_)cx~Wt{=JC4UYRfnoY=NN#Z&XZsNaStb zmw#%o{<_)TRKOYZ3Aj3!7?YQllix&%fo7YaM<_s>7*0P!S(&mi<|@)56p|tNWDpi7 z=FpHioI_R(+e{W#9%*ji%fpF$O7!y3k43>7jb$c4VTi*6p?*V0qpB!@Ra+P)Hg+Zf zOX#S>J;d3Rt061uVyMGDhOa&sq1B;db>ZEgySRl-6Pj=!tg>GcL#ilMeoc9sDZ!xN z7c!g)=R!aO&HT_zHGmZM5Y<u1n1GGIc$Hiy#mkf)wT1MsTQlVsVj+1!4Vpsh-f`-( z2W6sCS<Z5zP!k>&#f4xUOPy=WI#I+SY$`~C)@rv5f&$31GqlUS1(Pg$*rU&fIhq={ zIX?U0=Iezwh+iv6Q->0Y!#cUzO#Cg-jcKRKo{~*of6{@K#-dClicDr--AQ1VZ9phk zyG53QfB$$%v&+7kt%+(asMf7md7wF-n(zxgKMcPm)Z<Vr1MeMG0f;Ga>mIJ}r8FM+ zU!(TlhV4_wXn>D9fTc{*QJ!Q07#fo&@#1TvLg#R`aeEMdMbp&@9D2bdI~$@m5ma`E zP;sk;BU!A3!Mpvsi@MVx1Y+e^T)n>)R6HNkGORud&e8N$zAap|@^kn3BA-w_!M>Xz z=NRQ%Q-EWBwiLeX%{kgbC=2e#=)#FaU4GN!dxE?vM>xZJ`R2Ic0INPaKK`mszf`cW z_k(XqE4+B?HwA(G*LKzbjyv{P75&+b01^5eefMsja_;*Nzk*Gj4EOiQJSzDi)x7(C z_;Y;3KXdl{xK2iMrl|M+g%F)qN5RmP11g<LzmQP}y^aJ7Q?65%i=w>HLBN98NX9Dh zTkzF07XfMAKUfh%G;+i)@|EW^D0!^0<tcYdnM<TDi4ZNFbxk`wg91gyXzVn}ecW>m zb8!cA2AL347(GN)_U#nFC#HBUL6iapD=LuOLiQ*oSXT$q3<YJ+lE0gQR|KPYkD5Zw zn-i5#XSW05ePT^@4i-0z^g~LNFbU+^j@8v0J8|g&5I$>zDu7d1%$S31O+*(wE;IHg zU1;=7-?t}^3tc}DD~2uAI&?d&?_!-o0d6KZr#3ADA-5CE2BsH~!539woS$jWDJCs2 z%$`WVnn1bVHc<>?eK;n)C+Dj5Nvt&~QMogw$*&C_d(TGQ*=x92V@_$-0!@BB1VJzK zjxK+5bK+&vFZqd-n2M7aCVMOuwk4~l+YF_bsC?<Rr&i<RJP$McHu2M54aGsZmC6oy z{tz6<G=-?S6+HsA?#~I&QUPCOn(Ct$99EN}PH=zy|L*uriImAlX=ezuHsi~g#SCr* z7IMZkUEL6QgM)izPYp~b7IZ5Pj>|wHZgCnZEG9tNjK~PKf+tyl;MC=iHuGUic8Der z!#<47zIj_1os27}UtD-zOwJ39co}pykmUcciBUO~Rbj#vw+#f?%P9qkJ76TzK)(k6 zWT-%!j_Z$o5nUEZ!Xhc?=X!ROK#<>i=lh0B)jsT9Pq(;+lFB$o0WcB6zn}goBXGEr z*Dh&TQP9WcNZE`YqAjss8hrb~pPG5zO1n(b5+Obx#tisKu$!UzX{P*RF9*XjY&)v* z*#bo08^$%$RcprW$Rey?`3hj3t>52Ez+N^Yu<?q>3!W`XP-C^kH&X;<*qARuQ-@Xy zWaK!9wv4L_P*zsXX7m{`xWf?$GvgezbzZm(TZU>zwg(6TngRO1D4(|O?vBJAzf<qW zE-z;qgl1;DJ5G*gmW34m=^F^=>{xAU^7-~>QPJrV>ohRnA3n#wUSglrC#s_nWUun2 z>AsKzG1yGWxIzl%uu;II0WiCmrSP}|wLrCrcdi3vwLyxNi2}gBtDS9-M`7DRO6M`T zk@c5Qz5LkVX=KlSEM*^cN4Mt4sE?Xzy6W`f9RWoCu6zp@+Q))wa=%>Va4Y<M9XNg2 zgLPM$!w@0QJGy-^;CA+eiOg?8X}%YKSjuu1H{K-*#xSW;6RJaPIT`b`JDO-uWkLwO zEwIYx3Lv#qfXVnpKd4oURYwR9dl1Z{SS}~vjj<@c;FjAs`&%;yk45)hN5s0^ynX|s zHbF)>tkYs)bnbg6C?@ft=**-v6zbRzXCXVKxWBIlu*zA8l=4Y6=$R5TFNxL{pL|0{ zQ6;d!geO$(WJyt<>sWrB;BA9M|0v@@QF<UG`F;T?f1=*Gq&Z3|@h0g+)~WLpx6fZz z?3sUT%+=YlQyi+~Mm=rn<D!o$B0y>Mb#vs=qA*N4VT{rQ(QvY0+8rA2wPulV0^?CE zb{l?2%eE_xk?vBsDwPjdd~x_l#;)GURk^&Y-b4{^3xf;I?SWyopy!(OWVn1Jm^o<% zgt4iNN=Y&2rF_Eby0Je1E16WV@~iG8G+WjTp+0jxy{70&+Gp|fWb4)vWJ~_V7RE|w zj%FW|L)b?r5@(1{?H?y~F9csczZZ$GSo*V&i!v9;pYPa<!x-oZw27Rd<@W};IM%uX zzDl@tqD1K-VbTG6oSf9FGm;4%1a|r5r9Eek1OS>|%MH6kfSIHc8L<%LD|yv3;ZE?U zr4#44<F)@=9rQ1SPy)Vc4m;jQj@x8rg+wjEZo6lUU)ii$Ue5^AG32$&o5fLb*tL%w z=!-hsmkFnGB;md-Jyv@^ULQl9x6aVfW#=-`3bJ%n4hVN_J>*OX*a7l{`p=>cYGscW z;NIYZ<zQhUZZ2Rl6Bs;JwOTB@7Y}1QHcE3pq4O<f8IR-#)%URh#^|hlB&7UIrP`v? zg7bSf(<Lgoup=|BZ(idD1x3ALbxyoB0XPf5kuYeaBOQ=9uUr4+1ja}kd0SwwKhtQU z<xc`eL!ChYJOlBDGiQrHmDoL9U10+|A_d6;%g9PrC<Rt%*Oki1vPx1)8I3KI?dr#( z9?MFe#=DQl#S`Alu_`SKg|Hd&iu|!Eb0pTx_1*RvH7(=^e;K$lq~SbP30V~Ph}p+Z zalwGsrY~~N!B`xLS411T!Y4=d*PGa?dbJ@flshZ$V7@biGdfW`Q5}3Vo^O|$LuAlA zW9*J^4~bqAjD^VE{V!9vL^N!K^o}wGQNpkzf9!}yk9bXc`xrcxa7KiDI>i&NuJ1wh zwERuDqnoD)JIKXM!}lPeg|v}Yz77^4utq96TFEpMjF%%V2&djhnXz&>rbN8_5OrKK zRj-z*_m1BV>NI!%sQO%4ZJ?U#1Cs>4F8=E4YgBnP^=v&7mZK4^_gzvI*~nSq&mfoC z0_XjL;A!F_qWw&Ku<mpC!tq89n3{hI#bjE0In8{t0?>V=Bth^oJ);iIrXuo;$l!TL z!EA@cqWLCsDUo52r{>JIRgZNyv&74Ph0C1wNJAdEfBxlz3&Q0BSwO@$D~BPy7<~bj z&iO@7at%zg7s1x043aK4Hwum%-r@7-!}7JkriX|UM3oM6%j*G#pEwKL^F7)@OfYCh z(qi(iyI&wg0SKWrxnnrWyLV6c4J8>1n@j^6!xTBN&J2i#?a;A&c41JAF#<*yEv#sO ziiW|H)r5HuBQ|US-DIXu-GPlDZcJb=2~|p&ZZ*}C7UMj|N?ta93bMmCZQvI`L7O4r z#Oc7!G;fs`IwJUv1SK|{<5sniP3@?`@*^AQG?}Qu=9MhjNXa}_uWDHxoTYRgS>7_* zeb)9k)DE?L>U&9I!7YCsHv)spr;IE;VfxHD+kUxJce3yle_bvhBjKK1M+11nDJ<Fo z8ju-E9}b~U$GwSiIWc_ojp%qOmEnjKR*E^0bw0m!*LR5bl^&(@DsN2Zx7E`GKF>G; zRWPh%Cv$o*!Gvh0UoVL0%$L1sDJimjE;~&SuNRj=+``18R2sS`3t)S|UsqZh(fOsx z+jWAuy6sM(w=;I&{c-Ad{eQ8ze}8kP0J`z+w5q48zbIS`%QBR&r+cljT{+U{>@ZR+ z<@qD=G0D6mBUFeP_LATb4#9IpPEc2!l0+XKHkJ8GkWl;LR>*{z0-L6)ENy5@^EqqU z8Z)<Z(uDg%-fHOp@EM=UTUM=lGAa}9!SQcc1ei3XbkwRwrfEu|iK^N}opE7Auc~g? zv}$SN5!X5h(g>R%`56~LB3&MNKBbSTIguZIoPy`IBP_}hi?N(3UT90nMnh3|0moLc zX{c?B73&6}UW#z<n8%vkqPVFJeua^T5cy1GqDi<Z{xRSyn`LTUOFf5Tgf(`8#sWYj zELc-Eb{_C81wddf+u$D8%+uy)1%;`l(Ul{!(zTXps_{yqW72=H#7fTjurTQvE$Yil z7pt1+5|5ltk(;F>?O1TVs1etKc&X!D(P720jfOJPbChHGhhBeHXjx76&F?J@R~dy& zBhKmv<ktD0%XBbno@Ui^+ROey7N1MCzD-|><q+n=!3Yg8)Zn24_S3&x<6rrO|Ng+y z@PTP}N$!<z)K<hEIOlMzzsq2|c3Aj!rK5}V>lBtIOC>b=Q5Vu=E)IuIsQq??EZ4-N zo_75#O~{2RbzLeIIbAZ5lp)s&p=F5|hjn&dcD_$8!=I>=ohbigq=|~opz%$0ETvfB z68e#XDStxZek)E%GQLmuSg8SjPTpV8sO4l>g5yqTVy2{m#}3N;?c;2!9i5=pv#p^1 zctA=sYt}nzD)nfbGEKzR)biR@iT&j7AN0kDP+<d&=(EPbJ$mK08(nZ{g<`R|BKhuS z#XRO{F)I(R3}EwTmy_LSg!lGeW#OqXT)zN5U>*ItMlgZwxFT*9%yubo0GS3!B@B`_ z&+D6P8Cpw1NSf9d1WkKZbsro#?zq%4eIsSw(O9y9`D`$4zR2Gr(YX<A>%Sf<XFO_* zpxOu*a|dFAu~CgW4spU57t|6U+6MNL^mO{NJ5enF_i4-kbeqzyFV@YCK}t`8^m)VW zx%k`T-lV+Y5VK(W3Awu|jo^UnB!=G367rAurL(>@Vx?OUDYL52dP;8w>5%Yd+DYp$ zsq?B`yiq1269tUA@EJ$NAe%;ezV)cAmt7j=A5hYGOUuQ##QCZ0_+@nu0Q#c+JnQQu z<?I~5vSnpgObb#NHyM(S*5OBlF@TS-Uatf5U8-@`FM+*9@#t$D&i${;T8%%!pB2~3 z$@7?Vm;r-S@6-pM)h6o9sT~N}y$vl>ZzS5ky(O~xe14;)*Y#&hw`<&1FXq-7L>MsC zRSB&tu3Lwe9mz2*#V1Rnoz!!Lx;X7{DHR%nbeIz6uUu*cuU{`Z>gfiQHocD#PkWDh z5&&}M^9!u|pHukPS%iP42i3f%#n<3s@oYTv5tVI~da@jkECj7JPk(z)=h7;ohXjyy zyNrOVj8toSMKl5OSYY5oj{7FJWu5F=0yA`Wk?ni|?qRT9vsg5x(G`}`RX88s_F;#` z8=c{Drm_m2Hjz9IA}gdbcl+>Dp_<`yG!l<^N<Zy&PF7=#Jx!B2Jn<x(<97~XTc)fB z-M<*uehI>AP$d)auR?mhqRsDK0`+3k*)2YJer4jrt{I-PMzFiQ9V#SEMJGa)vw-+Z z-c7#Wz}qnxh>`)ce1Mi}F;1qeORA?9zl8(<4sIjbBPvQEk*`e;^)e?SG{-%p=7mGm zB4Y((EYJR+<TAoCcAF$IlU;T+XaUfm_E{Gn$439BR3VRN*R>`OAHP3wgi(nBca3I| z-z#XRP(>K~mxJ3BX?qnTBaf(hVt+zIUaC0NJGD7&B;WJ+2{qo7LhaS*hv+P>>zt}( zbS^K|(P$3(17If0NY@j4{5s-hz~r5O-;IA8$Nys=$E5>pkG;$S?kN7mRXq@R6^SST z?6Z_VG(4A0l{d-%RZlPW&R#~VbgrQ&?K>IdDvxBCMw9!8Hs8>%l!Gs0N?RUvIMmaX z2|6#s`t7`HYSkqq)msoT3PW3?SMrYXm3Q1rk|6FMV-fFicu(+=ywoFTuN95J()6~y zd(J{HHNy6tJD2)*%)^CZ?^G@;OWfpQ8H`21Q8%($yN$<vCDlAv2lq|eEG}2&JYGI| zyZ6c(r7hf9wxM{p2g&Bip0hg_Z00>4ZF&T&<MILN2PGXASP2R;$-S)2Ib0h7&g#5x zwwI^0U5>8<svrFS>PyD8=$-Gce;<II-#-7VQfN!V9Kr()8%nHKkwHore1$32jIQcv zbUvP1#C$KZVpo_5-3B|)7`#sMEGTKz>J)m1+A7YmG6-0>Sbe(ezh{{Q>wi{A8WJ&K zSb9Mcz(pQs^Dd8)_yPdHpal1i#9M{;bdyZV0`leItd;?taZgLc29Q=QI#GTXm7uxX zPokF@$2usQ(-*oXaFVfi6uIK-vbhjrY$j>mWo>I8o~E}|3KpXLL4WxXIvpHoG<m~f zOUWlUpwDo;@oate?iCUl$<+EHogr40wV-Q6AQj61LvCDbT$Bc$T3=prGlQJ*rm#_| zS^|1j|4q+ojbCeT$>zg`!cmcjW$a*9aRV_R%Wksl$8^@Gm)7!Rq=Vul0cA>>FJ}r3 zb=KxDP{6N4I*xm@b1@dUJK}k+U<y%atSX#B#YdZwtCIK%BT&ECfWel8uv|o`);jC% z?t9ooxTa~o(yJEt2i?$yB~_#t{r=xmnZ1vcC~PpOW%9zu$A`xS=)KC^q|cj?dml!> zl=v_Af%CX^GX6vt>7UmAR#g4{Tvb5kH(^y~TE`w1z<p)l=B8$1`Bn*}oFxCyU2f>4 zqL|0Ja4u(?62NwNt621G0IM#ZUw2oD*8bVh_ww-5FClf1)fa`y&l55gI7$74vXRJH z&W`at#OK7%Y&)N8Z6(lZ@u8oF(&t)srvGtE{gehPwR=J<d9}>~(?tK26Ps6c-Q!ke z0W!=EBLpLh)Mob)>>abnL&V~}WVmWs853os>EF@WU14g)a0LSy@#({T{{8SPv8AUO z7GsXL^d`MR5C|2r8t2KY$-1?GwcB+Gu|YjXxn;B<C9(jAV)fbr1~$111(FH;L-DB! z+npXN%+>^TJHtwkp>tq%Vlp(x^j4ex%D^+Gib#_xN)uY?iv2zKLk|(ShVfTnR%M-9 zE}$v{FT|-raFD;0DkCc!NP}e9Z0qE(!tGRzq@sz<IVr=hgv)tC4G}0eRT}!;iuU7A zyL>_4z)Rsz3ubjqD0|%HpM3r3)+dqmUcc6G=d-mEaR@9j#`7>+PcAJAKKNfshqDKA z)9;$yz6d=TJ7_0PMgpeAcQ0JBA6Weo>#Vv-@hVkfy0e37X8r+|JnO8NA~&Q2QnoL~ z{itt#U}c}oj(cd7B9G`LEgcv*eFq<a<D-OJXnQd%2(=+qSFP91jDv~i?s_r?gh-0T zvkG0GD$qP8)b@j#zPH4t%@e49?GHyJHn<y46b7Ff<=|ssm&f{q;n_uX^(vp6FO6OU zxh~5d%j&6KPGEUv5qulFtcvrs4wM*&7V^oLgFWO$i%un3ci&-T5n@q49J5OLQcZ0B z9=Cco2uG9~O6FvS_Wwu2bixaw61aR*cr>v?e;P+$XUFlhFSI95F;>%t2Q#!OFz09D zr7W==2HynN`9`z7(2u&;D+0M1Nxh;lrC3xNmh=^n_)h~b%RoD$I%D)9`E{DPq68`G zZ`-5|ua2-n^v2=<Gi4P8x4^IYomyWd@>oU53Qc-)QLbJV>jqP(Gwv{DPEXR*ryZeY z2PY3GU{0^e2G3})0BDF~ND&=?QdynINDFH$MD|5IZwnHQrb|G6a=pn1ESOe;MjFF+ zrJA;WA3MN%%mwcRHEA6+NtQSL>OmEw_r57M?G_(@fh%m|7#30PzsbB@<;@`HdnL{x z9&D8MVKgT?9%91fFJE@48ZIVV#X2|q&JGYy0Vvxigq!(ibC>YHl`$iUH0bA84K%^; zUOjH*c8x~~A+Xuc8kkJlW6f(uisGv8)a1CZ_x8p@?teSDRm!JmqJp08HCPhR6sbEN z>sFhH1)(58^+6fJg3K22G&}}eOk90Ae?)0CGO4@nu>A4k;Y{W+?N9vfqa!YG!nJYj z1o`*E--Y4d&o@ozEMP=Qr#7xzpmrfkDNztCYYAVph^k0$w`Ng%@Cjrr`-*A9kf+YL z<lv@)|5BqcoyZBLgbvNif?%gU`^_5CI(1IMM<f?IFR#q;_+WywvUjMe6%~n}#1Un? z-}a*PWXuOWNV@A$xXGbwVP$;zS9+(NzlZn6g+xG#8d)zC_%Z0DvDR@f(ctB+E1xO& z{bdzRvBnDo-J<{|D6ndcN)0^4UBnwkl0}ZsP<x>NeqIA|bf@&@Tk9<R+7KQXM+VU^ zr;pNND?xGM`#DVR&!4?FTj-C>LMrF2TPbl+9|koeze0j@Jb}3bZ1umeGKFX1c62LK zg7puDJW25OU3Op23NL{`7#s2J`*0s++BjsX!5Qv$rG_dp(?n)Blpilk`J3~oh!!NT z*K0TnT7H^gQJF1uD^mCPmUx;uk<Zby$639=^0Z4hlfQ~2zb0lh83#K_dn#imtA74@ zJ(U+XsdHB<6?^%_+PdQ$*`ChVkg^1p4{!Z%Wx*A>LJ&L-4nU2VtRbaA#suV6qfR|H z4nVONB?p4qi)35H!K8W7{E2m?+dGvWfvZp3zM1BnzsHk7AGep9!$xFQxy$TH5dOrM z+m(otGJeC8BEU^^Idr?5Cx35R)`iwIhO`zy?~2ZeZJ<bSr@Wzz6prhwiJ;}`4$bc@ zsO3|~?x+b!cYg=Tp20n|^NH-*fO5<nw0ckOEf&9A+FG5=^wkRN-br71ouy}<dEh>x z?9^`mlKAop_r1LT!`dZy`Sp<SJ*9r1mZgjRe*$e!jp*TlDgRE7u8x2;P;wG{9nv!U zK>=W}u4$_q4n~-$7Mgx+a?BxyDWzU8_2?jF_WoHRu0tngv2H_a{Pb~^=M{55s)`o5 zSglHM^)3=kr60##Q7WUKNU0(*l2h-s@;8iy3lyFov~r{vESjJqHZ{M3DL_?37WA3r z_{1DzENcA>y$*(3CzhqQfO$?MqZT<w(f1#+HynT}`gN6l@BL4$r*$;OcHP2t=m7fi zMEdpQ%};0Dx;=2zQwA$>S#z4M?k1Sbw_CR3Xp%qf$3aBZ8d4h1Ivf7{=-nPTGm}36 zEy4Mu#kCp2UOdGl?-*{$PJL$p$eOl{S$C#xA{LQ^cg!dN6_hkFTtk#8)n^1P$nNpB zeSLmtI4@g3Gq!cypDn*VQiO2_m020=nJTQ8r!$3#40K$n?OChlMyciawwoD7{BfNq z=Lb|+8`pXjHRbU@Cp&fLbzjj~k*6IW!y66PE~9n9U)Aw;UhTTB{a(AI{C)FJWP#cO zS)&lWsz~!%>4?rGrse3)@^X0W3l3ixQ<9s_**oj#aHZ_z3Xns!<-}gq@E{(iB*KNn zDN9DMI|6^?yTTR51zwq5fYHu6hEJfZQHJX0Z)(Nu!G}R({Oa=l<MXT|3hG&GCi={L ztV3g!Wuw7wE-})G;%ck&g76Dbi9LN0R5}RP)jJ%T;uV>1d%^Wj<-YLU(E`!>BqFKn zgyx6F=V$lm_|M$zC@&dxth^xk_|WITRZLVsX<7I*iTr1^u@5@fyPy|h1ms3W`f+E_ z#;==i%!{gGNC8ncX!Dd(i&1xb6ziIpc^r-&)$cD@`=a@88hC4h+L`jndy|VuDy1IC z!fN~@4XLDqJe4HGkco#-eZty_+wRf*I?2zuYtrnSS<NnziuATxynI_8vnN=#JRN*L zEfL33ty~GF`U{d~DVDR>v@!!``E=&1b=$N*W7(}Z2n=o%fT~dLGZ!VnYO;=tl46In z>#Sq1sYFXkpPU$}AFlC5oF3AD3;%1#{>LEUtcpA|CpVx?60k+gDq8maK4CHWPRiN- zyAO+t_~DSSwM6Du$K2jSPi1NZOT#`CSMui<&w$L#0s8_0#+`DW{icj?VZyY*7>B8n zabKk;=Y;E7G_>^QUU4|Rz&eJm9<FQmcIEXW#idrX`xq{5LfOEwLo&OvW0du0p8h-; z>n&JD<VtEnbFOOI#dP<JCYp8$P(8i!JE>~Hl5roou_nQOVLzi>{kbYnGl{qPUY-qU zxK)Cxz8yZD{wj~v{~Ax>OR-NBVb_ZH-IPCacRM;uv|2%#rq7&-YGY5pjlc5AtMB+W zm;+5!J5|^n&rhLY-)#Fg@4Hx2Nt#SmN2lXw94zEa9Y(n%9JZHiKOtErZev+6rnc57 zahzaaC*EdF*l0|u!uC7c9WqFs)&3@3Cv^X6-X6^ANQ6au<yN4>%qL9&$UfsG+u>tz z`;egWIfERZCz(yVpRNNg&gzKrjiYLFxp@A-?%hijU}@jB7U5Sgx9qa(bPIB2%*AGJ z6o%FdRu&ef#?b0I3l2|LQF2}ER~TqlBT>l%(xIHv-z*a-<hh$s>*N>9mokTFIe~ri zGX20PsJLfXr*jdXm^V&ck8CaXkE6-Fbt73vHVyxXjiq^umDk0v1jhknPnj?HOE|7a z=#Z4A`u-c7x%A!~fe(&v;O<$w$iF<LzIE*k9DTB_spHA3uKcl4;dFAUyfz40Di{6_ zF7w|b6Ld`&q_n?d_vw<=xw!XQB{XDsu-Ly+FTXdiCb*9_A$h5<jwD^a4?U6Zq76yu zS9P4bUBh;^5Ne)ULehysHo8kfD>)ET#hYB!%K$RJ-s}P?Th*cn&r-X8cs$I${b-@9 zIXJQ8Hc{18nPy4?ky;>f>G6<6`TA0v?>E}-&%ei1=@IA>68K;uNj2ox40u2><PSMU zc4GlPnYkxpBm0N@75g>E`#DG5pQ^r%_@8v&kB=U|*~>X0$m_EJ&krP^KVvMS3j;v9 zbH%UF#SgbNsKxxzh5VJp608xNWeSW4lHn2JacnH|z|eFNVXKtxJe+isNNpLEN`)hy zquA;ZkFu6eJWk@+sz`Oorwe#AI^Hk0&oN0hJu=n5UR?X}58QG8j58Izh6jyRX&Hz6 ziHr8lW87O=E|m~!3WdEWz?=z^obGIQOiA%T*v)ZhJ5Eje*8ybkE4P>J2pGO`OBuel zz`?)NDF#D4*os$&9pp9nvM#HImp||0Y~v+&MKvPA<DG1JMGp)D$F){Hk%(94uhb@# zq9d`(K9?X=S@X(mNKdtL7ge?iDabd7D{8?tL2mna2{WPBej=ml2F-HNW_+*?XU)>g zF1VD<px9`c`bbE)r_Ez+Q-NLNVes!P053lL$A88W{wtbLItDhacd0<*nY{g2e225B zxBaamj0=2ZnPS|si-uIGN{Gm&;JEUkWpE*~!@8qxIQUq+YT1f3&b_^=vU3CCkEPv! zf?Aa%<8;G>(3j4{dqLNpZ_hOaLu?wb)cR)FWIFImG2E#Yi>(w?#KlpVrB&nUOVFC% zANdH+N71%X9`(pYOR!aP)ia|y6>4F;E<XpTj6D}mFsi%h0JqvI5k>1GvU?~lH&Fjn z;7jdp1}kTsH{WP#pR&06Q@DTkQ5pDSn>F|!yOYbSr$NEv72Si^le*w3%<2L|ax{Rm z0kggd1=fqB>`A=yB-<~g^hhqsjj2Qux2=DkM~sd00!n%K@lyqs>np~sV4ZcZS&#WS zLCIteo;!}ouMjSj5nvW!9iCBA)2^%n@w|9wU5<jTkjXUVYamWyp!^sMaJ^VsE~W3Y z1ib~IL(i>&Rq>Z1tkME=^!W_)6+Tqlgt?i};y#h~GNWgN1f8{%7FxCtw5Wk@UkHM> zW8hVYf2Se8;5husrlPy;oR=geAq^>Hjc3|wRf3mdZozLz%A&a9XS6bhIzUX7J!Ls~ zUom(p@U?e0_<_~?Abos7R^`M#g%Pj=(sJ-j4g`&?=Y4H~z|2O5jQ-qiD&28b+I9bW z=57@l?!de=JmxK>HK2@ztnaJ_;n4U#n@I{u>zWEv6RpOFZV9J!b4UGnG;>ts<wNN| z;KC=sFp~+yJQ~Ey9h0TbrlwxP$c!eq4VP8!(Hg0%5TxNjSJf;)SYW-J0r4^3qW~)` z$m7Ct8dsotalRT5QJOr>NfZD2rj)?=t7@2!y{H}DnaS^Z!7m8APcP*wX;XcbUwn>K zKiZxw+1?i1@0g^g9AZmX@{XP6gbtVEYRoarUrAAtjugb>B1g#6Fcl>bOuLyKnS^E> zA$u%FHfsUHfZsCe({oi(%gDth;7sBFhpo4cYU|(Dy_4Wj9D=)Rfda+dodhWE6bKH* zp#&@LS~R$OaY}*WRwTH$Nb%wnDlhxo^V{dW``nQ+vhw#@S#y5poX_)_OjJI~i`8y` z!D4j!En3_W;m&-c8(v|5({}C?SRR{=m2{Hmh?qCkk=D?kXG-?~<<GQJK%`sM0Vktb zW&OCFWRs$cUKzEhB@NvV@HvhM3jrxR2Zmsr&RL!HldA~K%-ML4;^HIn(b2n4OSCi* z<G4HUr65Af(b?7bcvVP+na6}-KX+<!xH<{bNp0S~O*^>lvj~#9YimdB?oHaGS=Q=h z&nHBV!pW`FcGYJXc6c|eMu<>cj*^OW&DnWJCrph`Xq*P=5lA2bIeRZY<^+-1iVF?d z``>1(K|=VT4s~&DT+6#7a$6e)xXy^Fr$t}YuG}j7qhT@>W5L}y+g1{w&i<|V-WY;2 zT2+iFh$#Shs>usig$E!ZOh;m(3F*nKthlC<L@R!cpXDzDPZ<<9^Jo*joIQSHZveZL zuTvien>CAA1m4|Bb}C*E)Jy%v<H`wM)4b#Gjec3C=#ll7<do+nb!$9q%so6O-H}K5 zM)@r9Y*{UMY6bDC6q|LSO7du;_lWr4^2Hv5f9ZIaI4}S7NP*nSF)@)JV)YATG`Zum z#OZIM0J&h?7>xRiH_)jByvK%1z=FluH<k@~7ns%Orl4nW-(<s?LtIF~FL8EHnAQ@A z(1lQ%{OT7jOTqv@AiNoj(=;g`;x=mc<uq*Q>5ZPhsJ(}+HGcA{vHUn|<q^yYCv%P# ztTjOg)Tp5_@4QIR6)2!2_RkJ?0%Z^7**AjIQxEtiaVQ1!b8vuO(P(+roTnVMXJ?_L zAm59(>^c-*M7OyU^pTb-Ep(XAvp!GYxn|DkRDG!R`HdFWj&{koLVM2ddpdfv)Zr6a zfh33cD)=xkq3g(7#S0<eP<Ugib^OFgy^sUQd1JZl&#m`yfAj0xkwa+|^VQ)M8XWnh zve$}aZDX8ce{!54wV%&ipBNkHez2r{R6sS!;?GpjLNVJgsrhQ&FPZ=QbpCWNZ!wd_ zIW^Irk?O=%av+w_RjNpIl(E}DnnkwEGbE`k(|D$8ts^HEp1u{fHDdDPy46^gJl*=P zM^OYU3q&)TGpsf)e8S&E<D_88mILFyMOfd9*rt<C!(f>|u#a@Dwwn9vr+m&0-=#t) zds$!*qO=6!70A<mG2L)hB}FgWvnN;zFd{mlX#mXX$M;#@`P|?I_w~DOKwG5^rBi*| zd&C3hllAMhY<|_7pUz>YzNn4Wkzxk;1Pji>Z4~2OJLZKsk)5relc5!gZR7{wij~t; z>Tg!Wb7Uv#B%|w3wXgua+%D8r%vN=&P)$v#LI5HMS;_HLGTrsj-59{jQJ!pQ3NBP( z0zY8K>N9wczR6rgB+VgZhd3$jq74hN3mD5uk-f&R8jQW|BG*u(v2V=yM_=hY1N)Xq zqXxCUZ0+H)q&PSx$l@C84A!IKSs<iH$OSf0ptMn58m0;G4kmWC`aKz%)F|}5tBu;x z8^pq*V8lXVdRg~5gTcAK@&Ds|9{Li3nx347ktG>GxI4|5>V(sDh^NP0S?ErY`_Afw zDE9<}<7v3C`}3OVyT#zOFz2#&O;+m8K7X+`{StPUQkKX5<M2#$^HG{zA+m`1EL~T6 zs@er(v&-$^#aoWU=VgJumEo0C`k35qd6sf!%Lfl{3NZ94I_fzki>J8US{xf~Hq1j@ zG4MLeMJyX5VI+Hy4lTFJ&v3T0{%qQemXn(LIb|c?YG^;}O@i~7V%yz6!^=J=BA`L> zKQ(wZp$^_im``+Y0r$1@c<~dhOAKFdh#w@^1UPs8J?IAzWZ6z{>GHGWJUK>|8>);9 z4=^N_I(WNdPUOnL%h7A@NKlt;R1EYz8$ntrAJjcKD^}nnX^Lx04nfJtc>+w;YW00% z!am17ki?N5?1SyHQ>h>yY92NR2!zZ<O-_aAjUQv8zZndZ`VE|xWs;4B=DjdL2`h%T z7>E1684j+n7E(GBLZxTrlI9tB!tf7OrQ0y=A7mfyPEmMGl2^=y+PVME2fN0lXnmDJ zAy-OMP``Jj_@ou@{Si=_cchnOU!`TW#J1&FWvlp2ULEKX$=BE*;rrQ`J%AKYJNOi` zicD?=+iU=&OKco#pH+7$NnCPxGyL*F2wDCXwSJH7lSd^%I0X5T-6fm&Z)eP5hOk*f zTZPct&+q>`r&95Nc4O2?_2r|Z8D`Q^mXno>$;$#zBT{Q96aw6DgHF!U-4$UnRX$B< z<2!b<%70+h&p$Zy=7m__5Y!j{ihUr2=<n_$#g_Ln0(ZYhA`^9{HFe;{KJ=}HT;@XW z@k_DANG2=Xpfb>vO|yu`MRq7XR3Ta1!BS%mh+u{*ks`|zk?vO?gYD}W7OII@wmAAq z6-F5VMq@iPl*GFaPkTyDMl)TpN2_CD`b=_%*l}kZ;Bd4~U*cvJoSW+Op?i{4Wkg5x zOmSD;K#5VynA%j%WQhk{h@StwEmJhhvePU22!t>x!P*Dymhhh5#VSBv7!Mx1bA+Z< z%%z?k$d7ZgjFz!r9lLV>>QN3tm-g*|-4xf&CREC##9*};$!M-DH02R5%51Hri7YP_ zAuR!y%fvooWHGbvmwhJJXL1N!;z!G40gZ!UpV+lT8Dl^p)=;z*-TUYneudxd>NJ9E zKA19SE2xmMjD7FngNL+tr%87!ZhWr_F9FrWv)7;g5uE=crte{3<QtmHF;=u>ZJ*KF zq<cf3JcIH6U`N}JRIPjLW`a;?R+bK)q;ir>=_g&jB=7qU5|_gn$rt)nFGpUf0=;UE z-=EJWq`8xQYe}>WePOT5i=?<b`JpA<II`vN>PmQujSinD>v61y!^8THX@TP?TKK@w zAb)Q8sZt<r*7&&4guB@x*@f#EX$*_f8VQC$Pr4QHG5M?J+jx0t+8Ml%RoX}Fv}-~> z)H;D@7dlKM|2F%6k@#1ud{X{Q`#aMQ)>t_BEPsv!25pR0Pfwg~J@CrXg2j?5H7Z>J ze&~%A2Xw|Y6TMxoV6El@Q;(|k)kf^5yi*EYO{awv5Y2bS8;RIonEM2maX&Zwh_7q! z*~doy#rl2J@hijot(M~U&mJcjYZA0c+_ZpFh*#v$F9{U~9cF_P4+2nm>_P;?V_}Mn zHb4nWgyj*_=&r00uT+>N3cG^6EI#+OlD9}B>ym(8^D}bJP+3|5Hql^A_Kbn`0*D}T zCLLGh$cvKnMfM8p5AszT&zb4aRTtiJ4DhjG4zcs14_cOkE)m9=Kn|?PF|FX$e}?)r z`7xv}0XpL&dmRk6y{)EhaLErEdBUntLK486`)5)T`E=ulL)SW1$ckA=g8j5ysq9r5 zQG@4(pmoPPk-;uz${A#`P_&Ghb&)%bV3eL#Oh$dOXG<dOS(g26xcE@#z4`rrs=T(n z34j9fO>fGEs~_O&a~v8RRwVx6J$VX&QranC06~nzPCCr4{~*0Q(Nnzc?AVJ#*OAyk zEfzZAG=D;ze21~NhUA5Ge~c01y}?$~V#)L$*F{kwBf;Qx<57o8?q14L)kq<2QPOR% z5RNaFm+jh}E?7$mJ$EYSo81!>gY{~fcP3${LKSDsaeXVNYwTlMm1*^cZQcuz+TFoz zp8*3#rI`o>s7Z+`&;w}?1uq;6D$JCJ?M(TrasQ$Z$Lv53`?$qcSgL6<4Me-Y23nSa zhW$Tu+NBx9uJR^4^SsUO%$jGwhBnqWtxq?xWF5H(GuQ9l@ca1b;zh@;KF3*V;&rps z#QSNBAGwjIta8$L4>c=Y(-qM2Sw`VoRMf{QMkOOKs3NOc0daV7{BeftW{jspb76#q zC3iMQrK|_FDik$QYCjm%j0qI0D?=2iCf(wG3cZna&7KeW@&2r>_E7;s0m-^%eP4%& z?G6)j&FyGS)pWI>$xRY2O$x@O4bB5};uBcqEeszrJ^KD{ovpbAA^ic%iZ_B)8N0|8 z8!ZQ>gJ1P7U`|uMG9+xkB_@Zb_Rz*MNs35T0+LX^8XDA9IwBChE~U!g4OpK$$ol9w zN_i7H3bw%{Pn(Y2dqiR?vuu5yW}DFyEkF0<bcdNYYkY5yj_=jU&q=}>v$0z+M#eBm z)@yZwRBfgn#3ggFekh!exGeWX9V647G_Id@52TCj-EOn)CX#lmJ!dnj82&geN%dkl zxheB_KFTcl@A{M9<-d%45XS9LXcm!@baYam2_FciGAfo-iWK511+e@ejypeawSJdo zdSoahGHxa#JNmxhE%g{nOaqEh4DR$k+~6?^;pJf1F_>9O-R4fw_v_uM=I~Jtdy&qA z1`ED}bz=vepg5qW(O<>Qc$1C)Oq-`XhM-tRk=6-~o<r8BoLtMEFEL?Xo;0pAjGNj9 zhiW#$0eD>vil7X|%c<@h_*H`4gXPWmX2Uc@4qAe4BA)(0dWAzK3|4;cie6%dbFak_ zCoeGkzQ~lc-9V+QL3iS5Khy-hgT<bo&-X1xEs*xc9_4`ny=?qtrUWqK&F8*0eq10q z%eVaQ%)8q@_^gJ5+uUdop$v2z7WXstdxf9~>O;U$?$<HLH~H7>d1gv-DXc4-*iEr= zXNo#D4}$MN*;gq!#4r>r8O8ayr4%@{AN|qnz*nfB{MyJh$n8hrZoHsx!A)o3p7(>- z|1Fp8p=D|`H1*}6m%cai@sR^$q#Enf(4(>MZ8twGw%jtjd}(xk=%<&ZR{KbeQ1fqH z+rJqKqT3`#Fnd3%78_6VPu(+zV?*eNI3U4Px0Uj)azsVw^t0adkB)_+z&>kppn-Qk zHiCo1HskZegOxBh&+*d|zYLC15Ccp(gX)Wow0RRr>qgKs1U4UZ(mDi)>FD{0r+~wm zdP*g{)?+pQ>JzXZ#m?As)|roiE1?6KqQA<$NF0TAh1r-`>6t*<2Z>iO15#MIeSvKh z=*P@PKd^^K6QTrg=H~g9xZJoL#Ro;1(v)!zK!KKahBsAh*@$ML2!>v~ekPh!e4Gl_ z3^q8F@L)n<pP<38`GF`umbbbm?|E^X+|78|<e(P3@vt8gGJ+cCHH^iTm`=neY#x4P ztPFT=z25tlrnQO(#`@Lo%@;HT44799DZL3u*2Sx;Yh-f+k~>K*weiP7<t6YJ(nPv! zu1o^QwJ{`Y*a*du+27^gtk6~9Jtc@<t-2Ti*9Bw4`-B$Y;@7CgMf@rY9YnDYQRnCU zT?f~Syn#>ukIHvdL8dkJh>?&M5BY@vXw(}2z=34l?Y?5(`1spT8hk*mYjQgGnJ`5m z_M>vH7G0-zhzkA*l?+i5ROP(mwMO}O9jn}rqgP}jro%1MCPhm#s68K+j9c|evr+cT zC}X}T9ErGiWy$BW>szLL{j#ZdrJF=urPXT6-Dz-Cz1ZqxAWuTC*AQ+#XK)(3A=x4C z6y2p(wDSvg5wUjt(of2bs$8D?vG*X|eF;VdHZS8XUb1QUfj|#%==a*({`2C$i-b@d z<tGFlFSS6(Q=7#^0%R=6D*Mb51EYDmWLD>_#qcej++PuEYudhbyrrgAgzR>DIo?HY zrv9cQ6>ca3B`#M134o=zKvC(7R*$E1nZ%T^M2{e4w8rb`A4)$6TU(jSlz_T>0SjgD zq5FYOSji=F!wN`yD}b9opwxu&`$ew|<UKiIvG<FL07*+-sYd?E@2TI!xvO6R3~zM( zDVdaWlWo}yokkD3^lZ?77|L9+5dMgY(o17ZoDciWX!OOdrk}~Ki7vI>ZglPS$*Yak z@+3HR7U#2RIz3=)YeNb(^vAUuO2NQucXyMIPbIZr5R7l#DhKDi-hfQ@=Fyt_x$qEH zW=P}4i{<mwK|5yq_r9(At^As~!Q5PxbOoBzb!ris2f1pF>XSj6I6nyq4*>fyZ?gmp zD`S;fJn1<|{TruGQ9eZ7Cmzzm5cVA`f^}h+Xte8T_HS?P{fhqR{oe(0Ll$PoVDeap zeY2tGNr~;Oq_`pz5TPUJ8FvFxKlJr<tdJ`Uki88;Qr9aH(vR~Taec{i3q2wSh_h_d zS4oHpyh~l!JIM~RV8&P81VoofYz-qSSdq{3Tnm=H;zw~SpC}b{VVW=|xGL*5_@|$! zjM>dC9=zgBeS|tKH4r`4j?TPnpS^eQGLRD=r|DY#d|fub+MeOc&2**B==iC?_T^UM z@SU{&ogVb*9Vo3#@^Zdvp;DXmSG6&lH7(XsGC+wlD!vuxK9(}P27VCgLaUHqP~m#` zdQHg*7ob9;4hTzV;%TtHd)2Sa2ts7M(%rKA^E>YWBQEI;>USKcSj-I81vaasW5$AJ z#fGtRzFsTGAj=lpanpX?qU9ri%|}Av5G3zOZm~$lv=UO8uU^qpHJSouY85<0$?m4= z!kH4;eYP^U2&mOBd(DYod72&NH2Q(F&AZiZ==IPU=&jgbCX*eV%iY<JIr@p`G5XW= zqTYF<G}eg|#yvy9F7v34%se$E@g^>B0J?Rv^eevf)gI%mfDX^`j`g7YktOtr(3^(; zOy}nFn1D>q(s=bh=P){>>y#^#Z?yb7O$W+&r@|`$>sRKqpd8Oc@SXh@=T<2t)woz* zo;Y)e*6YeITGr8YLV0Z26VPSkykm6FoOvr_IamYnspefrNWfj;`!Vou`O$;xQiVQI z-`qQo0Hl&odW$W-VHw>bx(I2_%g=_`x@;y*0S#6gJ=zN+V@>=?i&cfC!eu$Pd3A{4 zWvX*yr<sB!W=7x!y*YuqbC%e(6;#6B`ESa9w^93sn4YpZS<-vy0p;yFDj=}X)~RW( z2_TM^+f0uq{!s8R?_b1_Qc6hRTq}-N33|kQQuy8+A~j>!42ZNA)UcRv&dU--McjRe z@9g^c$e&8pMK*?!ha^<d)FB0pV(Qo6muRPK9Obiw`cx#uW^gE=JV#JO<+TmKm_Bb^ z&@+b}>db3+7&T_LD&yGzFI3}DzTo!e7@JPikDrOC#E~gyvJ>^gbjL0allW+px{2vs zr!(p2a#H$QyMMj(K@dfx3|*OZJMDyb^%eb0n!C1Yy%`GpYDNzepv@85)=yI*vDXJ> zUJvajY2c?<NQ(dJ0O){a`qbggaJ#sOzP|G_CJ;8WaBylASDFk|qsCx<JXpZL!m_y3 zCM)%a%6oZ=N}nIPjB_p#=N6_M?@twP7i3D?wtB9ag>MFC=91qKA~<?aB+lG+B_{mu z|1*QzF#-T;fSgPA3$LF?)(a+In6JtT$nfn_$EPwTx+6UcTJeQqFW*FMN>2x@yvp1} zGFjuLO+r5>e@VOA!04X!tM+c}0r!nB&_fm@jd1O9y*z>#GR%&i9ihojanz<=S#+Y) ztH61uabyu9-;2p3=Xe15Fq$=Yql~m}<plCZ?FeI{PhNGirS?7t<x@Vl+!zF`RM*49 zISHKw@_iB%sXFo`o-L}bE<!Roa-8olOtAqxyf)Z;)!FgvIC4%h3eh9%EMs60TxY;* z>?bsbYKKy$;#8kXAS%z85sOopy1F04)I<Gw1VEr4-U#?vioa7NjX2~R_AwC-X1Bx2 z0|W9v6?<o)GR7V&3NF=l#C=MvLUou_*9=RVwb;(<?`f?D+VETJdYF9x&D5$OyUpy? z(oTr?H)z>~)X!VUfVRZ|QLPMUf9DJ+Oij^O#hIj)`wj3$AH9es-d@<lvi|Yx>q>~N zAet2Eb~S#Vi;CGB%%;*|-;!z3Bag_XV*>3QWEi9<HF%4lG2^Mg=)trc4)(oSx$sr5 zm|ysRnPe!G0NdIzIEpJ#c``J+wGAfM7L6r7tzXjiE7d%7oilRuRsYInuG`yfG{LB1 z(QbW!$zNSXWs7{&w&d}c8M{qyhksG#9w%?D!P}nxlV9k#q&(_>YG~!98w-SITU5T5 z2u^y$(>df=SjD^1$~A_7gPW<cR@2)c!HJS5NPA88M^8ZpCDNlN&7%tc?!(>-+Fxkx z#IVt|aFc5{VV%8?)~Zo&=Y#^>kuc$1+0K8@P($sBf%tg=!XCHw7leM_l;O5IoXWCM zksbvT3|RhTdlfblJzl)JaodeqDCy;;dV9$Cw>nis6DBJMj=8mfS50aq9z}}Rsgc1P zChw;o`}mowxjnNOGv-Cu1?Kbs3ir_DS96#$@2e2Wqegm3KqgXgcrrjLQZsuuZw*+{ z*qMy`j!%6a^zKE&f((7jNHeI9WGooyUX7i>@LGzPlE))<Di5G<Z3g(>yc7L;fLf5{ zhhFJ7Bx1JQG{?q{E%hSX1J`>x1LYxB*x10#ti5r<Vl36tyN`qBbCaJbMg#&?f|R=t z3NN*#AYwJd5*Y3XR5A2(`}kpcfeSD%>_T>Xos6zt{&Pk!h3pQjkw(Z(!t;P5<E^wS zv{up0@m)E0UuI2J8lZMDXpc=g`%M`)FjcsrFxXpWBGC;n#WD5y-#v?ea`P8@5HA-q zMxK52-EylMZ3g>Mu!|b`7`c8S`x0y6qDk{E*Ng1V0P6F=57LilhOWoip4^tC_$+gj zzD+6LFe>RHO-!_I78mFlT~Dldc)iV>e}eSPQabGJnPx~0yjg^zWbfUvJ8h0gZ>Mg% zgo(GYw{@DEUlSTo0CB#{F3Nj=_l2;v_!qwtV(ThC98o(DQrp1l3@YqUNHW66aVs9$ zaJ#nT^<$e6=CaN>8D)QHp`p(pfVTs9&#Cbmu}8-7=yfz+*a#=+gvgO%!}Ua_ot5%Q zOg~y(D1Bztli;w?1DMkQ`Z#spVRID2T4y|KXXQuP<VV3#QN&K}&Niebo+qeSc@wi7 zT>DlfnbDP<j3@2#&iwBLiL7B+;~n-dNQxsk=F2HrpM23?#6=Mwqy`tYwJ2}KG1snX zE<=3kK?ZR#MQ2<uTV$-bo~CfQ@)@aC;y@97Z}1VR3d1X%J4i6JM6@~FWi<IUI@Gu~ z_w1MbpBe8TR{g&8X*qlchm1QJIt^F~`X%5xrDZo%9;X4*hLK9x9-#6XppaqhW{5}^ zZ-Cx!UIu$LY+Q}C98FZYTuA&qJ^ZW3{a?bYe|+r!HC!%_hab_L^h`YY#aXLEI>vKM z<y1cCt^|M8tm5*P^+yyu!ynyZ{#b!m(_S{rJkcvU8@A#1Vsdyn1nO1!oO$g`eL70r z^LG3pr;bH)?YJ1J{!!89V6s@oTTzxLn{IBT$R`N-)vDew_uDw0JNA(2O^|6yF@f%j zg!9C@%$JoElplO5zZo2I%`66FuaX2(yP=%my506VVT}(swR)$=iYg~%f2lOFN4>wE zp`$yu_@@JCs`W3ZtmoJ7%1<vhu`)a!qUx4`nl<#sHr>$j+(ud!!n+pxf@uE{Bu7Lw zer9d+`P2dvuprNg1(>bM&7o&E7JWj-+|87#&H;^!zUvL>ytTB61*(?8m43$WqpSL@ zI~dO*Qa}@wY1qgd!*%p+5-a>Hd@Bh{P2D-c<ga?9;&?oBIJV60q=Pm`@z`*t;UQM* z@i;>`TMiz`h(-4GZ~kv7E)zb}L-Jl}d-NCVe(105Vq!u3+oEwQq>PXnVCj{LA?#h3 zzHXHfs79iHfDbN|P&g#f?{;z0g(~O|(2jHg@4s(IxXruNd%v;&Oi&9#$%@gPeB_wD zzKkl;n&4tJ;bWzRfALw1A}J@P2n7DkwB~By0`h}lpttW|1dMm0<#e?>z7D|L-g^T) z3%=Gc?=%>HA(|!~^B;p_0HKaq(L-OTeI`?Jo(|Su-R4STSyACH4-u$u{}32Q{qiZ0 zKSB|vVLFyv5ZJvgL%QCGxJaic=rxS8=3UTu*!cRIw^yR4ObPm|YU!&T#5WeXraGY1 zasZL2H=0Dj?>0M~=JSK~ip#I&8(aayVa&Q|r|VL1PE4BJZwsi~BnnYMu^TkrYPuzh zeM$}E60pzm<q0VkUQ-W&f!T)gZS+}7Eal<6vFeq_<y6a2HpJ5dA(0Q|x_TT!m1gwT zlM88#IizKpl`mp7Pnao_rLm{cVv4cZYX-~Ru)kL)#zIiGU!hyEC{w5(bzopD-|;Jl zmdgRu<R~KZ#2E^jw?+ZV6t!otGRLXaK0KYHQ{p9Cl^!pEKEn-xWffpsPw7e~33dBD ztjV~>>NG)E!TIa#r=i~n-ob~4TIh2DTKlH4vt<xKm$4+m5bYh6_dXcK{p)&O7jVXj zW}%wXYyR7`Wo)ZCdZV-XR>yhdBS>O*WcJm#-5*om;nphtxm;jzF}l`$;*f6ss#Zb) zdW%u0>5Fd<Pv+3z_sdu^EOtD1lQfv*Oo(Y5=eYC9HUA$$Aw^fos%CQ|pi+uw)ENHV zrToA7{)yB7TpPkMpJ*J6FX81yFe;YgYdW!n&Q%fxl+>A#!U{Z=mp+Ipvj>@L3fe^! zA_p~};18PnLX`5+hRMazCdh8DS9nLE6YB~AG2a<nbk^2^eS&2^9N(kXbB|-lbtZN{ zq6EmBrX8zG8ZRIOTJdKG3>NE}pT=#~_tWi%e~_%pampguAGo-L@#t?Xidd7OqiDh$ zt7q|N5PO}+85QHYdgC`VYku61VZmP;kemO`HrkT^>X0MlPqd#XrRa%pN?e$lh!Yk# zZj4CX`E0QG_0zaFFk%;y@Kjj~JIYmdGL&)9g5)>Ut+T8LMTgLR5gZaF-gp=qt67R$ zqB1=EQ>wjtnlyua2;DZ$Mo)rc#!(}r*2VVgP2hG3q*{x6*B)M>XNYvrB4NITVuOrq zqwA>`O$5m}Gm(yXeKIOoifUJKZN6AEanDSiHN6Cdj@Z(%@9|7!GQT`{0Ygi#Z#*;{ zo#`@tPdBKOLPfk3@mFo%%*@edEmYG&!;Yb~3M*-E284+N6Lc3-rfBYi4rLFI@#N|} zd!~9nWa;K3To+Tl<czJuqvAm~X7;T#a_C)mh6aq3M1Qs3BWLcGgB?=8^6%U8&%>1h zHTa_@&O=zH<1GB@EHWlx^Nd2GbuA~*O|7$2N;KoCUN@T%mR(4b8M`L%#G|hY13cDB zSFHgV$*2<<QK4w>2_&2NSk|QoO%8yfDI%Zg=*gY&D!r7+@$4C~-@V%1xAV1xs*t{i zE60<54r^=Wfon6#Kv3XNL;0EQ=oLCYu&AWQ2{nuPWpD}yBix+Gzgjn3bHzT-fI}QW z@^rM4OWZ)~(yi1&d!d)zg#-dBj!ICSIw>D90CCy!C<{hL`Z59UeR70d>Ms4&@&P-b zZ6qb2j3BaXwj(Lf>h+Yt+GQ&#Re!<0`|(}*B6wNZ=6e^`xoJEZ7nb=K1%;wShnM0= zO%!od5#jKJf(i2^e*#uDs(Qk;m}fe(&laj+x+7S8ZHug4Mv5D1xcMZRJtw$T3(lUH zaM%#UnfG0LBX*v<M#dG8J=`aO$8wv*n}#nQP$fFAL}S=SuRq*{<5!L22b;W;JA$ri zz2(|JFSvj%jqT&<n~jFMJ<F2*D`Q#7SLtOAtZEcS6)YRq(!>*rEae5$7u6^a-jQZg z;nxb2?>R*O#44E826jwt_RGhjD3jgAPoHXk`wx}K4-zJse5>Vy{*!_wxjfEF$laUo zTha7?N;?aWScZ9h87gfO>fR227PiYJz4Y4a_YyML+-@WZ^OriCvxb<p$>|oYUlR$h z{H=r7>wXf{jVG-tHc_wgP7Khhw$m!@JEGdd2uyOGZ<lzYS|^E98R(>q8Jo)=G@dJ0 zYT6-Xx)z1KzdG1ie)fhNaTf44Iu3T)ZPnmO=o|H~nhMQ(!x%#QZCCDothtB2crg2? z@Yjla5`*T0N&gl+^~EGXFpWx%A|;eDdjOykd32@HT+2LBzw!f97DB|aFPs?&RJChL z;!HDK15xA)y-PKh(GJbtw2rKC1Wv3r9ud#)Oln?{I)xqp7CivuSeybrVekq8@U9~S z)n?fGkC^x1Kp?-}q(G68j%;^~%?AKpq|p-PK!=t=F8AV9j_72vaiqA+0B#^?CIeZ9 zEu?R|HG7=G@kLe>E!_SEhe_zm63Zewj}RBxB3(cxC8AUxJ`77!ctu&MsS2(xX3lnv zX14ojQ6avmXDVpf#Kpy?4mOeGdJrZba8R~xn+<vb1-9C77!p=Ql#ijpvrirj*7!)- zP2ZSsX*d(hWM_Z%=WjwGXTq`X`{wnN?0;U>e|~)t1A)Xk#T2lv=f-XdX#WLc+tv3D zHR-e1^yrOK&o+{<Knk4sF3>@8Jfm}nM;hHa)P6T9*5i(<MMQhrW9+fR2=q#*MV$Br z$NXe^X%vtzy=CMGgwhMMm`-TFn;0fZ&|D5U3vTNzM>I4T_JF>}<Z#M4a(o84EsERr zzje6S_X01h35S;g^F!4qD{)Gl^}5RCON`p3=n*70B_G=4V&E=*8n^>*UmT?oC^c_% zp29NtFyc|!t26*)Kw3491Wu5o)p=I?v2bxFz-MTg9imGWc|w+`=B|z+V-Vxr@g%$6 zeumH&eiy_KkLYhgd+c0BLC3Co&Kcny7Mmfp#N$}ws$Lm+Yticb>41BZrPYL)07@T{ zUI~d@dA3br5WxOoAx80uI~)dDzEH{=r^q~*tjWH1{tzyA#wBR_u1TnASAEGkR~D5+ zWGi}ADanA?q_5;b>B#t1(}mNIo~WT6sU+ZHFw}78p*pW)ZKNVNoh(d~hVBba0$a1_ z?6)6*ONij?0%-1BoLmAWt!gK>W&ElxozyoT&k!k{Zn^e}{}pWA!Z6t@N((r;uSIN& z*Z>)xQa5s_Ik4nHDb-DX!CtEavy+)C@+vT-{Lvwo#6;0Gd1A;qO~^+qT!&vOBBD<M ztspG<N9hwv6m&#(lk{d9_kpHe_nuVs*9Y^cbrDZ$y-Qx(gci)-_L{La;nT4<PA*3; zssYxF@a0<Zmcs(fN7!!VZq4d#+}iEOMPzw@o^8ltvi(Qt$e>HuUs9(*??96Sv>NlW zmoII9z3L!4qWCv=a=S{YuK^*k@Z+IB7<Cg)NB~gXW|5IK-d>9BuzCIY-pp`Ta!%ia zdXyo1&B@TvG8dzxa(#ngr@Vyz^}Qq20ba&+Y(`yqq>M(TC~1@#N&TqWIgd%9r{Dof zvfenjNbPcC@GWD#_49teX5H^+^VH<V40KWwSJw5<3yWkFy%6IP#5_kY4&(srw02aY z$SLodR3zKsaycJ*dhToPlPE)P1?DESw}EJ2I1URp%eI(#EEH0XZ52}iFR}gExa=Tm zXk0fN4Tu(^G7h&%MO_p?OH>^oZN{XfR91|s&M2GWf8p@mK_nJ#)P<jk3zpD~R>D=6 zicL!1R~zU&UVeosC`4V9sMty{|9!^ctnEiY9BYKLNI8iKinXWPJN1LB=l?{r|8Y$f z=%c3BR)ala>uf9Ha%5wUJf!OGzKoe>oD`o>A{{7Y)-ltUN@n!BhAyBmf?Iss_rYjz zEbr((eUN5GH%PO}Rdmm^ntyQ>DMFwom%Y3^lYs)GIhhRqfENQ&$A*iuL`2#;O~`>S z%Bo3_i##}#nYZNaTWBKQG=1Q6*}TycUZII+GK7oYs#yS>t+}}3%Ok^DGAwZDXYS_3 z)fvXiKOb;Tzny8~ob|+J&<MRUY@)1WZPXVY)lD5B(!No|l2v#@I<~oyHqX?E4OMCQ zP@zoc&c0DKpzvKd$_InJw?E-A7+@mw4tmz{6cfx*Zx|8?zA{*5T+EnqL7Jy@k0hC0 zC5MZ_vo747Q52<_VUHZ$SQ}B)5LGi6^mN$NMePnI+OvM1&OiK8_aw|o{@VElHb1Uq zX||t|X^KHMH^S;(he8m455vsY-c9y~o8z85d)0U=P5{T=_oX8+QTwg5;<Z&JvK!O3 z=lC3bE)ZmVsKAhf=BM^z*JqloYxyUI1Q+iW_794@$-;sE9=ZOX!Rr%4^-xJ>j3%bM z_UCS+u3V<OYW)$#rDa8gI`0XQ?Op-dhM<&x3vQ(wY7R(ZZ#ZPoLr++=TM8S)k8Z>+ zy*!oC(Ii(l4J`^uLa=cdDWU6GyGi;avYv}MQf|X*oxvV_<8Wy?p@jp0bEXI2)Q&&* zG@~>TzPQ*YrGk2G$ojV{n$06xq*fwH=Cg<zZi`9VAgu`pOSM@(F8H9t=wq@Cq(zRc zO<>s_O-+$hP^<q&DiyMgNy{_J`czPFSyB7z-Q5$!?_VF0e|aapUpxH5cv1y6VLB|l zSAb$et?j!@iQzgi0jXn&S}|8lZt+9piJd%q?3ARC9L^|1K5l`rG@BG<bU=cJ4A)bq z-B4|;YOwfA(`Pz{S&~rG$^Gd42rIK!*z}Q4Z#`M&>1G=om6(T>wkPT9d;~fMJ3B^k z?E5TAP?__+$g5SO7Wd$_)HTTnMB^8DKX>x1UM3a1!#bPjlIZz$`*&SuX8ByNI?;sC z*b^z8Bg)H%WxS*doL$d~WdPpLe&&=?JXt$44ETb=lI@lLsM?QO=6e-?HOn@ShY(@; zpVdm`<_$Jv0=??G;X+D*X!IG`R8=;%-Cs)wmh<FtiGENfq;tV5SgoIE(L$MJBfo6^ z&i}{retP07$}%|XcstE{z8#d?ge`hJIXHr?m3DgJn3!nUQOuA1$4}_uMm@hhY_8L4 z3d8GO90yob*HDV&w8%TOhwX7;1$-H!&#z=JWo$(vE)sG`E+N8C%VU&RIN%p%M)tB6 zN9r&VK2tm*j`I?&iFL-KWJ}y_z5Zgc-K6Yw$$_}%xhkZu`l`D0eHlV#h{piB2_@Bv zF53#!FNegK$T-Sy`nWpx>pMZoSkCDaKLZUf_Leg+;F+8R5CQDa^Z`Z7QY?eu68^RT zL9@$LO3+$&Km~4SnX7l7NmMc?2o}NbPzby-{6<(~P-=ihZ#P+*T(hst)z+gr)gFf? z>U>PZXMklry0b~*kvN8;7o~g6$q1`W5Q=MS(_Mk_E2kKp4ZZq&yrkMb*o2=<ikblv zZ=a^zid5h}qup)*FU_>=z=NZ0%*IE%4#cuGTl3)1=F_yuLfY=nB#*twchwj_UGrDv zJ(M5m{yDAu&$ZzkGo3hP5~!Jw86X779gtcNt=M<G7<w)@GP}i&f#hDkQuTn%o_CB| z9*IV;BOdHhd~3eZU$&jpp^P5xkz^R($bI*aIAi7V5zUKcZ&_Ag&YBqJy@8`5Oskrr zF}|?;uKVg9QqS8X@}00yeqFBkMibUH;{(uA-Qe7jAV!u8V6xRCce|*TC>NE+?#6A` zx>``|Ki)-Gbhy2&TDy&&_wCwJ^5}R?b*dRxfFi*%A*I4wk<oUCyB%`z-*1}V$-nq# zHc``b|B_xs_sz<+q#6rRtfLJ630k21shxw78uC#`xV*{1KqRpD=Ei^Sqc()I=3|Qt z6uaz1(3pGqIroFiUvU#@X1xP1Kb4-bcV=?tHR5p}Q(56Un!30{9NcuxA5n4$D-AuS ztU=0#&h+v_DD))9r>UuXSi&MQY^i6uEiQ^B@+uB~Gn3g0@Nx%&cq6|CU`}<ab4`&> zpDjVIslVSCszB(Z;dO(xF1e>BmO&7d<)G(Ps?L{GaQp=rrYk5A?MB<svZy^-CZae$ z;PS#p%b!p2<&R$*-wGiMYF;_Cj%etCYm|+nk-l7y?X^1Rr2#05^7B?rwo5OSIBT$M zOo^J~H1B%Gg6i9A5+gmLhGQ_ad5XUk{%0O6@JCJOi%WJV9C&xRRuYxnR|ca<^{B{> zL)+|;v0P81LW4uQK6afhk3&j5;T#w_VJAfcU(Jssq;sxN*+<Z+cF&Qbp4sekDWpsQ z4Ty<c5!oka=j+C^eNoF$NpKO$fI|F$i|C0-*IFy6wVZM+Fn%Mia{Q3t(Q*iI4$@zf z!a2#yHtm(o*PYPK94M*Zy*Fx!tfK{RrAXr`RchThs_9(mlw^x*%HYafAt+V>`n4qL z7iy5QLD*`v0Fz?VxTWgBA)uDbCsdx2>5UMmPn!@?MLBuM+A2R8&eea0U&fZ+(nkJC zZi7IW9JRF~NW&CM>tnv5G}tT98tc5~Ee8=hryL)T3#5><G{D<?qM2){GZX#MdTOME zTTguITw9FSrRPg$l-CdaV5Sel*f$1J=<Gtf#@)g=Cd*FGoafA2;+JC1FfP1bqJR=~ zOsu<J`qw?wj|Ge5A!N`h)o+8Dj$T&CD2{6VG~k|E{nS&78Eq;!VBEZRDi$`&saanR zh~pf{YNPPJ?p?%Pws=HVR&0n3%}FI)dh$;zWacVw)wLwf-#gVxe=rvO{DIc9-z7G+ zFTFK1>{H8|p8Y(`jwcLQXc^}BK#>f6Zj;DQkJ@kV$hwcZYodgMldeUawuboaC69x| z&ixO>j}8C!{xc3e<^z>3v9u<nm&bQ~>o5m#lUPpBycum+F!84jaVR^xmM^un>kdB3 z89~wHfKhVl=9hJ;3Q>IGqM10v!jd7)5M5kH^0CjO>vKP*f3kSPbl{2Lh-OI?ZF=*| zSTZyy75+R`^Zp}#`qds%N3{AAiySb?9Lt@CI@ei6k(|ZUJc-_RokD|$Lk^SfSKzT0 zs{SWMp4N@+t@Gu!Fqdw#kCBlg5qVL&C3mlXZrA+2pdEj5p67-Woe#%G68AyH3V(sJ z>d*U`evAJT$C9Xkb)5e-<=W;U1wssYUbj?IvllIb7*q=<oHIrpi<H3r%#*w=A9b9` zqe%bi7z7HEr1y8C4+1x5DbH&YBE%f};%7X#g~eWr+>*24JWLyWBNUL`i-qJiF9@Zs z2pOU-U#9dGOWUzH{OXZz#!iR<Wwy3~ZiOT;q^2B{X6Uz<byOwzRZM88fE{W;O*Gxl z)xfDRxFR<tC(%?jB0}GW!63Uik2OsFvhzCCL*Q1Io68#xC3&t7&}A|_6Q~;ncx&c3 zjV@Rdi>$O$9OTZ5xC`UK8}h!U7bwOhKevplnfoZ(w6|U|E0uua3^-nUZp;M_#_O0E zCK1N)bpVkX;0u%2YYAAi0w;fXpKxbS2j}KsH#ZZ=alv)p2J&5PS3NTB{yS#=pEJr6 zZrDtzZ{ay}`)*4p46azfHDt=6k+1w`##$U33RHs{zwy<?T)8^c4T(=W`AMtM@2CWL zml}rLXKf2ymc!i62sNafap<Gey3x{2WUnx+RQ4A}ZzL%RVD7#`%{{YrTPkli7h1gF z>1=kr5Niu=sV}=MtZA!VwK9k2C=5p%tZA{ehYeYv%GJoc_cc�$(cRzg3N?Fdz2N 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 A}Z3|yq^p9YzS0o69@T#?x)R#mWPjx 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�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 f` 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 literal 0 HcmV?d00001 diff --git a/docs/nmodl/transpiler/images/nmodl.ast.png b/docs/nmodl/transpiler/images/nmodl.ast.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc08f36b611f22597c3504b17d201c9b2c8e5bc GIT binary patch literal 302573 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ℜI`&l@gePCQL1%#9iaP zWdYZeZ$|D83V9{dUjUmxWWUI$U>rG;imkut@W#BcRQSz^g+Cv_ww38<7vqBvQot{) zwRDZQZ@H(*4-$=_m<pMepCn_PlNvjAQ)p^-K80H{9|7{H;OyQVJF#`u8uXhv1N}yi zre{mCuUadQ5FNL+7h=iGXLI-E(mKr(z!H9ge4*{A6=-$xI<~fMEB$F23Md|nrlIpi z?;<~dJ%<fNCRtH_o&PPy&%6=d1b@d~3KmRDDIs|8&fPeE>;T4&cmO2_%aE!|fp-sY z0o}=RmwcUz-bnf3vJ{X#7oicJSh{5}40<!(d1f~jd@})w`-_m3QH*<@=!Q?;-i6WQ zI}0nJE-MRmq)3F2JDn8?ZWo3la0pvyNGJ|E?#J2V#}V7JhcaIm&W5ZblVv{NqY7sT zQKT|474CgJQGhC(J6;U4p$Y~nvUl5q-4WZF!T~R$P;*suR=Ne!6y3*~1qSw1aC0P2 z#O%AfQNn4D+RDjKzgUKuKFe|2--^lW#l{B0;HR<KQ8*rlLMg48DPZBd4wU!e*+q-& zP1(phm@jbpFSa%>*<)JIl9FA6qT_`E{VR|>cOWG)pNb;}#!{u|V0Ya4U~kNr6OXkk z6Oo^%r%MJA&#^3|OWvTB{wVkWg$h$e^n#;>!h7WC?ua2bPevzmcO+2#rwto6qO7n8 zP8u}=2)cdR6OSRbM|To}rE1UC%p<I3*6Q$Eqt1xDeR(K3TMD-h8X=2tZk%ZHI<}gQ zD;;TS3Mh(2Q&aqc`!PdfrGTv_lkT`3o3)<!`SUN}9~uI0%B3(<?#0<7#}OFc3AaD@ zG6MX=kx$IC_*fA#R%SxmLklgzpE4As(i%<yvB41XN<Z>Ym`vp;NUXug&*Ct3mII!- zdmZ$ZMHn$IQYaOfDS8y>a^ORA#klCQVo|pjd_DeN<K{*dG$-f_W!S>HM=76W3UCP1 zhp!w#nSBM)&lX|B+q?1J+!*w_DH6L5W#Y}Z_h9NADW1H}J|~|p-nkGTTY9U>yTNEm zEur263x?5gUZVdcq&(&3lpy&`E;3L;-pUH`J&(nf6fagR7#WJIi*s0%(wdI~vOvlq zP+UL7kYs>IG!;?v=gQ1i3DjWG4GdJPgbVO#qFgEY)g32~=iv2Q_hYMG2bD)9#*Obr zNOpdhKD%r44cwaUOmr(vFQpj&Jeab{Yt&97V*H)R+v!gKg%gB(ApxG06Z(_AgFQSb zT-AXJ+{#U!L-}vzy&ekGVYY>&^yX7d73M!)y`>8DwuJImyWZQdj$04q+T>S?Vqpc- z&Wx>wgij8U89r?qx(^wGb0<zBFDsj3`BfM+XAb;Bf+=y>MfBFMj}IlcIuo{e0pUdF ziDFGYy?x>0Psvi;>M#N63+46dC?MgOI};S)EiXsn<tNwR_6K@l%B}6Oe^(l&-P9Ay z79Yk>AD+SkPx??|w^1k$^=(js*)jr2xrvV9FdoON>q;9tnl1NleKTCZMVgJ(c<!ks z=-DX(9yMz0+MQ3xT{of6;4ncI+1h_0iCB?fuLW;>oWyNIap>0kOvoGZ*)ux4^Y4ws z2Pg*ofEmm7Q`jS1peDpP(fGNh#uoB23dbwgmqSJT!J5Y=^|*DvO3%0rJUV!w^js+l z6N@OZo(iOLsOnYnVh%6KKafvr)|RZmq-fG5R{xrehyK?QOU6#ey!ZFwzWysQ;0`bR z?eT$N@2b3hS~ry~qq95%Oc!pP;9hvLh>8bNk~=X3zrlVY&&7@uaHb3Y<1k~dfB@+P z%PSjuF7zz_=>X*qi30NaC3<r`mHb2bC_G#wibVPi^cAqnB4VP27;uznZ5j$Fibd1V z`LcJZb;*(t>Pa#A{O`iXOp+UKSZ+~d6{Gm^Jlcfxr}W4m+-PlAEf`^knX1^(#KGbe z<>e|VAe01RrW2-hgo+f5?>|kz_#4|(ZHpoJ^@sg<{g#8c_0?$n_+uKJ3#u`qge;62 zH-Q9ISHa*7%$bylRH_DBQe1+d$jGbgN!vSI<1SefT`7fN(hXhk=HK^Xd1em2c&aCc z4{0w5ayd_gy{kPcNwKJ=gnV0$C20XorE<pLL9vMM<BPB;52y&x;k&PPVEJ!Haq9!U zVQZ<K<4B4HOPn0~q4KjB6I#IdT5A`@^G>Xpt8l9$)FM2jtV5XyQ<W33FFSUN0LRu~ zW2O<>?pnhBS0OV^kB?qBjd^SOz*DQnldlZJ-Tx@TFJB&KIa+G6@ORmpwBQ41jDnVy zL9=Jmm_y+>6nbV%C#1wc75w`7o<~Zvc#EXD;WMf6{%JX1Q7SIK7V3}-rI}^OqhgRo zLM2m2s^K@l7Y<azOEe~HTMos$JSp4U5sG4AMN=*dq!(JCY88sworF3~4>MaX{Rj97 zL>;zr>9*>GOW3Ek58<jyP?Q&5%Zi3;)m@QE?ciXCF*n4Be+*yx?Yx~>_+ARu>SjUf z<BBco&tclg1L!+20mFxrFi6&A&Z&^;?nL2FTseLJ-aQEG*%^N1dEzF|wN(?LnL5X` zV?lt6TzL_VmuKeUySMtIuhtLwyL43boD%0%7UfwMuIluaH#tYDP;JT~FA)hKuyWa( z*8-2(KhO;`@9ZK*Vk)ghsiBfKY+&z(e9XRwDwb2Y0XGK%7+@jETx2PpN7Q2A0i`t~ z1uW#(c9e?d6(tsn+7=8AEEe-6rbp%#Mx9Ai*jAC}nXkjmY^uOLuWK=M^hW$RzYrtF z#KG53gZrQCT|3xTEz}VskQEAE<LuEXqkNMh%DLkBetyn<`g_BpJsA)xBG&)_KmbWZ zK~&T@1X6A?1Lwc#vmk%n@8si@_xdTo$^sFM4LHSQxYX^?iFNGN%L`tVPsE|uwTj0j z)^`0DSH5lS6i^fkE1cmAh0Y8A7G1VH%v{0PEmA|c-Hsx0O@Bg<^pzR#jQ5aoFRox? zvqBPBRhQ(Af9txlSo2dRe%w8kawePzJ*ouXf3+8CgyZ99$aD1UHjEhB6+Tp;iE}O( z9V$CJ8+-TeMs{)zW>33a8dohxwyI%TRcBbKIGm7<-UENbAHR*lq-kA{y*Cf*zCMny z%tG{=(Lv-I$a7P-^O9oW2>nR|sw<_0dLf)xsqif=2OFwUQ-$3-PZ3B%I!-1Su=r~| z2ww~<_m2`vg*;ZSzRg47P$<z@3sW}tx=WzhKC=hOJ=P6H$BLni*FxoenOY-4fFs3{ zYg8O7K()CB9v!K!i||TO5k4<9CXVTc5yMKcer+QB{C~zTKg47Fq^?xs#NoVf*K8xc z&NrrYEh`iysa)k;FT%|nX}|Ls=nc0}4Jj9v@KWVDXq?Hg(6L-!fxLt~=#LXZpSL6Y z`uh^3^7*h#MNexSa<|5-D2hcZm{k@yI8n`iAbBqcOFfSSFor7RL^ei_a+WtYh4Eg~ zY|DyW*WQ8n^!H$>-5imTs>gGWtU~WWfq3`RN!YpR3}#JUjAt?zz&|L6JQpsMbZe&L zQsEI0gwezAL{*YK%KXdV5=gl+%3NG@E@T1vC?#wE`uz}0q;RP9tCFzn_hb0rvt+!T z+X)eV?(iMt2QC;VA76Vx5=IymhEbt7E*M%#IH~>JN<Xk}ka^Y!XFCmryL7-mUs1u; zSxw2NvT!PC4~`l~k;Gk1MH?B0S6Hl-_0;^cYMBE~J2w<06bj3_P%NZrtG9o$!YdNL zPnAGT3EP?|jW~A8h9tcD;7+(uRP5|~JL2Zq12A=FJdP*iz=?`La=XN(#qU=8!nC3$ zkH5ZwR8xMGkt&auQOHv%CA6Ee&E&1KhcCtUyAh4JsHV6E?SH~2(PD`qhSB^lG7lj- zGJJ9lc`3{~Gc?g^N{07Q6u9P}p%&8vrH)05nW~F-R0zJr6j;Du1%n0G8e$G5rwOIx zi~*oM$eDzb+GIk$l){8BPSA_|;!Fv_j<lhtQ!SClZ(WJ;H$~x&zxAa}-X6yf>rhov zjfY;I2wRmSDv0JPPcICZJQ<^Iz6ox=UWBhs1+Qp>RQWoK_|(4$#22}5Tl-p8<T}L% zA&cns<h`qqcqkiVC&uCSx$Uv_$XRHLY@u~_fJ?BelzU*=6lI!LQ3zD=(P9b{bP*vb z*1X<j%bz`Y7dv+kKv1wJmj9ZB-CNR;l3YkuYBk(E==qhOS$gNS#EONQYIWE-Qn5z* zPo`U2TQ62MUYZ-8CnFhkSgCa*CF7#=!aSP}W{`ngieIlNse~`*2~b^(lrx2R=7#N< zw_z;2h+kxrcQpB227-dL7&yEgt$E90u)J9n4ft8KG+vjY@tftNpa=Jx{?&e_-#8zt z(pZTyisvslTnODx9oPeJ&NYKQ^PEG-vHfa#=6TI?lb?5Me!fLVt^7r%BPYWbP@a!& zn~uC^)$r-#{r~J;2Vj)t`Tu2)%g!a2%?gmP!`={<fVgqi@vl{D-PYEs)mCk-qqP<5 z-Ww~TqKNFh!Y1q;Ap4SBc5+$#f6x2f7a{_RlH_vvUdWB_zVW`#yPo-b5HTPE60@eC zW6;{kJJSiOXILzppcBqZ6=b27g`4jpTtttGqNTz|`RrQPFzAXKT{tsh3RA0!<6P~F z@%mFJpeLbn_c(z&|2!C@Cv{Sn&HG|EW<0tPZ_d4xXhiOaNDM>2(cA!oPEIdVoIj$+ zM<IJr4sw>|AZC1w7HDYn!)ZycZ(<epQrvuRKTNtPiD*e$5O!Et2+SwkQJGPO-1T{g z86jBzG67B&VLbGF4?h;@ee$Z-`=pPpL;DKw+ezP{-zC1t%cw;F-CeF9_7%R|c@^Da z1j1C9F$%4OGc6|qkp!qW22r+N^^GSB1sp?A`ollo54P>aN@x(K?6k#kei>nE%MVu| zw0~%0Fn&&UF=CSf5gHnZ#j}s&iI>MCZS6sP{^oxC<-akfz1=T9C~MIfiY_2XmCtlq zqt(FCF1eK>x}lLa`t2q5Ni2%>HT9@va!7kojoPCsj#dyRx&hwd-b{|^4ZkE`A|RSn z%;5JR)g9~R8HG0eLB{(}qJYp*T%W8PD9jNu**C%m5kn&gND!c1uhyL=9s85+YMj(w zDR71ruf39^jkjB$_l`w3G&vOh(Iy2ECw`JuITS7kVHiqSM9~20f=C;0b&FMBat#Q> z8xtR_{)B$BXKu%gd$!=^1yivz{TSYVW*=f{I<73Y<H=XYAv!i#)AVqQl|?#woF-gk zQ@Dto5Tk@ZqaS|Qg|g8JGjs9f{TqMWPdxvH-|fP-H8%8N5aHDRiKt*4MJ@%7s3A<4 zr;mSL;lbcSpKu1wGa$At2o^G~qygK~58}dGI^foOhvLZx7UTB&M#J5=78}=OV$#Ke zh^3w9O|5oaX)$Wb2stZ24UReANG#(+t5856Ya;j+r<b6NaRH&pA!n?MO6s<)2wu!G zAI9@MJT>7tsYf)*PprqYe_Mco6GIUhWx}>i+33_G@{ITT#XmHeKd0WSQ9$4iXE{y9 ziipsl1P8}^1BHirYQh_8`A#jka#5_PLk)$Ant~eC@iS<F@u4sg)IEq6cR%=q`l`4= z75uJOrs)_zTP5`KjrX%tpb0%IvX}=XtpIkqorUxaMtHw)<xbkTt{C(_J1vc0zefQ> zu=pN1T;z*m5fElYg1QAc%Ae4*McXwKXJu9;%qihbp!wKtW8wHoov#>P(pBCoD{HY~ zNj_$!Pe=OpW4L(w5<GZ&H$3_LR4iSz3m?Cpjz2s(N;CJ%-MNqlb_}HD;skOQ=OTMK zg$r6PBp#q~0sl$0XdfpvWoFX19!KB)U*L@ygYefEMq~BLLzuntFm9NVgi^XqNJeV0 z$m!Qe9t+<nUnY_)QA~}ygJE-a%6T#9zoN7P9%QuK>%1Ajo`t-e3iR*qfOd2=z=vo! zO5<xMQ)U#Wp@6IlQvB$8>K*E(attZKq6rhHPH=oLJy=TgGCQJEBT-IhYU@rb-JEEE zqY~rk*C(P=;-|QJstx=1<>A=26S(94(c0xo5aqJ%c%C|VjxQ3!aDwhflIum>kkmWD z4kVCVKo3sTb6FxPko-a3d0h6C6O8Lo$M{+9Ayx<Z6E~U@B23UlPu+{38{cbru6*aM z1nEo?J^C^iQwTvu#Io+qtj%I&^kl-vrox00)P$tcPGgTc#~wb*>5lOy{ZqK3h}p`E zRKH3dkPr&E;#Mtv{Y%EzS)OkE<l+=C1Pd4LU|AGoaS*0T77K~J$lqY4*}WXq)@qf2 zNqm46(<KXy6l3w$=x~--eX*68i)Y@NfU1fU_~PT;$T)lxwr$A9>{;v4F+Lg@yE7UW zXK{5=`y*e2ItQU)%n9T!r*I*b3kny40%I)XCyB1pV&OM?5nwW5&dfbn@NFt4P1WF7 zCrvWp?w1$i`dhu3TQ>yO^@Y@O`XC@7KruiPPb3x#@e{5oV$dgpyq&q|V`|`l?s)(C zukqsF*CDQ(AHf0shkria7sJMMRMpo7CvO4>xG|BQ3Qng~D*xoaejjIUt4r67lU&BG z%xB+dKZt8jEj999;tu6t=Ja<v$E9s2lj%kKAhc@;cJ8s_KYw3}cV=he!?#A^`riyi zUF}`ivoi<91yx9SGL}eCT8@|R^|N%nLjaI>v4V&dppMY2l6_uT2psPXJXXig!sJWf zzY-9dyq~de&bBXbpHX~2=bVg5v4(pv7R{Sh7!R(Ip3D^Q$@!;!=64DY^1Cpv3V7g@ zdkg)f@K^2kGgiv@;hZU;k3rI0#z<aTKFVnI^|8?PX^^-+X{b;7`g_jzH!d(JKnghb zZfkVWFQ!1#4M;TOF=L}ql2A(2k|N|R%|$tbV<U(hC2<*2j0NSVlUyKCMOt9ObY@5P zLuExZF1fllX1%`yBgTJ@HOnqno8zXn#~GAMw<qGPD_?qE7t%dgxd!B~v{Japrf?Cf z0_1e2<EPicwa;9+W;doy8Hkt;WYU<|K}uOkE!M5whnefQ;D+tpFmZgMLg2|=m4~>= zWaubRNb%u#_YCwzRelvr4ES{B(&%PK_{>*6yM#h&A>tFlX{Cur?*UP;6*6gBII}sE z8IvNJnLRzd;8rC;th8<@4WluC6>(bmMHpf!{t^dss9cRduTFuS+M-;L-!$&7m;v~8 z4A_nuL^nQa@O$;M;K3&*prT?v<0+2g-?tpZYioxgCYpHz*uT0^A#pYJv{Z?JP)o?+ z8XNzL9q+bUWjT=NyOY6pqnn^G`eFsp-#z$QEP5h*pZwz}Vgg2Wjyle?lHx8aLW4s9 z5KDs^fAU=&*RBz_Ly3P7VL_}8a*uN@YTpsss7Idr_2>G$_IQ5Ny<MPtts4u8`c#Er z!W*0Who6kq;_z(BH?&7u8EyV$6fgvfUq-(+ceQLV5@X>P=8HmREiEH*l)ccd5I2Go zgSE{>*J`x6*0pejx&Miv;N|0n|GTR{9-g!wsrLt>YnK>WE`EicWE4JpXB}?5Wj@|{ zc?zz%X(0TXplgKn3Zrmw0#+h$<gCs`?ARFPB47v?CmCda&j8fZP2-mPhTx&w7T^Gb zo73hM!4%?2=IciE>XpD`d4!-Onoj2YY(gaFDGQKjNWBQlD!%JgOqAD_N_92!Q@Gbs zgM9$UkCf8=YaNlH0`ZSmCli%STH;2b!X0JqRut|%4x3*%f+Ir^85>8&PZC!dE)34l z-^xnCiipKPFM8ipG0B@C9{I=lA*d5m%sWb_L^ED~?<$1RJ!sYS`|#1*JMrJQ^YE8j z<8k+W!*HUimQbM8WC*KOe1eBBg#?aqFAC7!6sS!JL`)(it8yu76cot#bKMuqv>X3K zun^{72@Dh>oS_(uY%}+QE-Y|uDBNLFkn|#~MhuTYSnn`RU=&M{Ls{0$wJ~n*;uJ6h z3m5O;(=|^vH(v&PMyEz|BPxZJL0fst^B}PoV!bdD4xNr_r$44^ntS$%!5dp9;PByG zBy=z%hOm$?KD!(nmlPl?^Bye!?kJx9%Mv{H+(i1%YXO>a$8aXmlAy-IjYY`y$y2#V z8eP+zW6wW*!<+f2o2W-wuDxjh?z#FqOuQ}%$8xO5Ip&AIyfPd;dM2PCzXET*vH~VQ zKiqQ9aAxF=K;E)E<uhy<5(!BpCl(9Iz%4<e9@GjubJ6$pRf~?|z1t6A!b9OmVVfpi z>VpN}971U^-4`wX$US-tYZolU(VaU{T~-3DXn<cxDB_ZnFzTWUncY3!5H6gcqur^& z7bSr}sH!4-8<{!rYgg6^!qG<ej#Aow`Mrv+DIa~X8a7)ch79V4G2^<UrzHkgryN0$ zpASmb7oe8041Nr54(&tNR^}{HmIvNb)(rg*=Q_+;FWr?+t0-T8WbL6?qOOtm#?s_t z<1yny>r#MytGbANQ&U!p!mWiW?#wsZ7m-6E2=2n1N>bE$Mh9xdFkXKt1>DGF%8)Wj zJEMTEIg@5fyhUMp5v><x@FFu7PQ38oZox+0i*pzgP3c+3;Kjc`yZ~jkdOY>g1@NWz z++Njyt0sSd=iiuy#O`L~=MvJ-*OU1P{1l^+N7z)`wqk^)gfhdaMJbM&-t!!);xeb| zjCMhO88)xYM$i5ch)W1V0h4CE{=!-;`T8ivPVPu+Q3?_|g{jyIvHX+~E-`A1MUCNt zZ3VDxEk?|w7?nIy+d!S0M70z&MPO|zfIe~05)2+6hd~2<@%Cf?KqT{`3>-cT77ND; ztrYpp@V;q%8uIh=apeQ|qhnH%AzZX_H%t7>k<T&^*ew82!=qF@g9sLq3@>wDmO?-h zSa8WXB>ny2+v{;8y%HU|`EyLK#Ep|;ksL)!RG2@e-`J0d9z9{AdzG?0@cgDcebc6@ zEnVN$PvI56CkV(zr~KaZKE~%ZP62%ZWiu)=D^$X}n!*|c62@2*FJjeIMHfEWcynE; z`Wjc>b}i3_#ln?F<2kfNnVuaBidZjVQe#jQS%ji(Hsr3L^@6Z-qD_~8SXG)VQs?lP zc5|xGQ(Sh08O%6hMMOjp9)4;t<0`Z%_R>FBV#tVCgquzH;H9nDz5f_4XBOSTBRVR# zgh(#*5>RPl#$It-h#VZD`MI{^MPID)GVIKefw<t(?rJPqb1QKD)c+y%!g##@&2+?f z4sIM{;x8W2-wb<!9r?^W?;T1Ud^6F|PS{|#*_B{%($Br|GN=3C<>kSkT_(XRtwu#< zHOk7$v2<Z3A&%XUvu!cD_ejS03nnO@P%)8gL5GeV5l2hKisj2Od&Ylp>r;P2P#9B= z8}qi+`qzUoRg#=kVj`r7P$6IfHlmm*m((7Fq2-tr14Bi5JvOXAjtBp51lF!RiWhI~ z4gXL-%=nyf7H^M35ETwTD6%%Nd~b>1oKb7LAuwpfX{WEcQc94##5!hON3=1zzeSFU zL}=0}aP!7kajH?$z&_IwV7A?kF$4>z8d5D>8GYPxXA;koaQa$`Mog?1CA&+syca1B z>xF2@zPE$5a9!KL<8%#kV)sZSKBVEz3t#uI$CqLMu0p)>$>op)XgA&c6|U&j2fd$( zQ>~IsKp2fES}J5wDcn$~HUTr4WQ}{|X?kC^0Xm$|DvEF+<E}wy#4&jIKoO!7z3|ZA zCN@51*Y<3zTYU&)$0s8($qdU#3$o^AB9EDSqnSL<Gr&V-^9}16sxe)SS31`RT?QyF ztj6<yUxo!QP|#<`2cixSf0zujUm^A`FU07Ju293FuCA`JJu-$oJUlRB#7N|3Wg~6z z5=^}6O5rk{tC8)fHX=xf`<)0A0>vr;&Sgi-P=2HW=Aleh8sXPSjVi*<mTs_PVclNr zJXnRBpXp7Dra!8J{4soN=hIuC99?Vla3dLw52S#$jxga}Ne!%<3sfwRUz8sr8BZaA z0;(8$xl@i6#S2i`P($=KTR}A}vCa)TawZ}gg2kCA)1Hp&I}=wV6?-w7S-WY)urcpN z?#g_HY9fKgI&zdDV4Qt$$ign%uxJkd^W;h#&nUrbpH9dAJ$bnQ>IGQz?X^hgNY@<! zglEyMN8*zmMMhY(OxAS;$`ws=GqhKsLJ>z1h^`_I203ML3_5p+!Uu0`KzMi{wro9w zIZqYf?pJyuHbJvq_*?uCF)TtuJ);YNAN`00_OqG`wTx?Bo7Z@MC;6bu06v7{yyAu= z+;T574*U8dFpvSc)O_E2&C}?YJOG7-ClFyiDO4yD@nR7){7wGo(zPoN?%V~4U}2P2 zrGTeD^NrAAp>jA7=%H|Xk;*D9FyAt-cWuZ)11)SBw6NXvr-4N0F=OkaY1p_UTOol3 z_-ew3S|eJuCC0;@o&pYGLDrGVoJ!=a$%ox)M@X*_E#V#AoE!IUeb0CLM!9wcWiQhE zMCSMj`bDl^$rw83Y1*;67%$D5ihv*=r`@y8)H@!Jvz+Ni<9IuxfEw6hhI5B7XQp_6 z)H2|!>_9mrGp7d`9$#ks72CbiiU@g(1?mTbL>6{gBm^<aY%$@=8~ftW{z5!({d_$7 z-;o$QsVl-GOz<K!V`JepO6yF^g^7%?y~M79?A-m_;m72Ls<d-0lh8-dkb3mBV8qyY zDA=2i2~)e_Z|{sjOhOR8{cJxvCNklcpP=M;p@w+=g1lp*)~7PFQZaP8me`pYHF1TC ziVnrm1EpBI>=+h)vk&jSwh?d7sYZS#&^f^uoe5tm<LuPp&Ug!(&4w&mE&7ig!@1mK zjGcP2@x9q8P<o^c^%eC9CcLy*D@u2lB79&t0y_l4cCZA`-LeEX|1%Bl4K=v>zJZ8K z3`IX$E@!@x4sS0{+)6Yw-JP=8D{DNU%~C)m5)@ZECN{L~wjqy5gCfjYMn=NSEZTw+ z=Bx^pHcRw#thn$4QuMt?LC>?#<hy?C!#C0}_v1r^o{q-5Gxnh0$PjetW^Vlb93R<g zryCZFRwKQ0K3ENK2LtOvNLoqWR=lqmMf3~KS(2lIz`h@Q!FHU6!Ew&kwQQ|~mfd<p zpq~EnGoD+5TOR6*5viS733~dI#`<ES*Gj%C=9!SR64WV~xvgtxxgfKsviAy|+p%z= z(<>;%7t7~QMX!`-R8&>q*~eDm{Z}%OO_!nVgq`gZ7OODz!}^3NcfP9JDtIwdb;a>Y z1a}G6(4m~1j;}tOhC3cwj~V~$jm}A-95?YO&)SbW?@UJOs7@#?`yqs`j2#gyYHMrN znDJy5Z=<v>1w=998RV&~7A1R2lyEEVC`FqKQL(=qe|mBxy#KcqRVT_Zw&NFg;KgpZ z^xEFI`w`}^hz};XgY#P0T9>lM({&1{y@U*^Bu30nv!ZlQDg0t-wI%A4iAix)In92f zUvIoOGX)%bjkxkkk^Yj6T)vl<RKsd5L415TQU7Z3<tKab?pM<h5^6%%UZMEoh}9Tg z+L<|Qyyc=cQzUN8C<X<-PXS#}-~<rAV8OYLnLtz>Vx&v#Mb@HhlpZcsE;~x7P!r|* zw=lkm;2?_#ecx$;zVxbI=-DUAL6(ymic{&Nm}k;cH15%KN%M*DK|ZxlB@Dn*Memza zFKj2@g`Pe>p3F!ci>Du1iZLC(LI07VShn*DEL$)U|9N~J17Yoo8e(+|>K3F>&jh!> zI;V=8FLOk&D0ofv$vIz=`kJwC=XJQ}594sfwJD4pu@D6+8XGrf;j8Zs;=rLYHNN!P z3tdD26`>+GCkGL6vDyICPcYtFoC2anmh8r!R1{P(b9Qx}9RVE!$oQ3D_o8F?^qZ~N zQIwB|{xJ?SUcCn2?3;?RO2%JAe2SiZ=P9N`Dy=ahEna=&fzD0=$G$83OeOQ8$i6ID zv;|o(R`zR$P$6rBq5Nx|&Qx=k{ZmujfMtvJaBVJ8>$dFkM-LU_;hUEtvFq1({>kMK zG|8AmZ`g>^DP_Rm1oXed17H0wU42*ms6ILWiy>Gze;>784Rt1#>yj{OG2>zsjAwmi zJ#rT3B4<^u(vmbTT8@RT^(yR)Pu7SyaY`?AOJ;`MpHg^<EFzLAU2=^Gx{+j+E<IHG zQ?70o=aiM&ap6^+vB`QRre8Z4-FljlkPw0QzR5*R4NXLjB7g*9t&j}I;wDhRTrE;` z>xbhsr+X7?%!;M^@Xx2e#qHOBikl{Xi3M}q@bXJGTz>ZstXfs1g0)q}xk-to!ka1J z4<0&1zx_=ZI%$&DCW9+&wZ4@xCjs1&Ag`RUK9W<Vf!X7aZq36}FYZEVf<JQx`6H4R zj4f#y$jd6no%c?_@uQbB_stc|yiQav7G2ZXY7H?S>~s{+g#{5_3z+CO^V>|+GFaP^ zYGIt7MJcALnLi~bQxp*TM(%XYaEb~kTApE@{JG4$ew|QWK6?|yA%igJc1KQ|xu5T# z+wM$Ue_waxXJ3akbMkQDU?%$Z?to_=U4eS0z9}rIz}^)#_;S`x=D4X>{dk6IIt3k@ zR>Fv}Xi5#^^XVzzU~~mPUHtw1qI{`|t3~;ta-}KpV9>EJ!Lo_AXIGum5l^k@qA;Qe ze=h60YzDF*Ndhc?B6?IXMxc~=MnuT)kvNxjTJ%lQ6ySqSUCi+J_eM@u30{3}Jzn|O zIs`Y=Ba~L2-YGGP-ePeR7}|<s6)JW}k`V?<d;*U-W08$R#z|#m6-JMri%TYS#q=9` zp|Zk`)Jqfa`-kF?_wF)ybdJENVHQp|ZO+x#)nmtwbS(IMHb(#II`kbeT)WY^3_52d zNL#FvC|KAn_98po?<&+hwindED}Y(x8L0iv`@1pi&z*74??<CE$MBmkuEYJ;ti$S$ zjv~jNjf<!EMPQKcIjt9MkxWLNe+C5{V@uXS5m<BA<e_LwkxH5vMfcgD&Vi_^qE(jX z6&04#O?0_pN<+f^IHua~zHuf>%O0!t6w<SyzFq-f)Dr~bL<3RWsI1ZVo1eniXu^be zBgoRL?cefT?fzi?sz`kG=?--0Y({N^9dEv!j{m$c9-(G`Bo7M4??<k~pI#Y(ty?m% z_}l$hICnpO_wWGZ9j!q~xHoe(g{ZaUWS!2`s@6Yl2o|m12W_GTYA`z%HQ68o5(4Qa z<%Y^*WO(<LQo^o>Z@6z`7Fk2UXwt13@7HM9Y4XciKkAE<Br+1Xz;YrRl^!mKPq>c~ z<Qf;nv);uuf7ILl?u(mn!z3n;n-GNG-9Hk4{M#se_{w(F*43bUFXjf}#^D?01AC!e z<+2cj9{)H7&T|YqYmtqqfdbN@C05-1_)v838HwVeYLr%%;lk--VCoi#51-gW?bo-+ zJa!C+jvd6>Rm+i?m50$+UxndQCaa)rokq^uPM5A1`>-m%N(r>mpNi?IJ%L(}20T6E zIPQJCAAubLFz>6Kxc1lUu;sv2xbL6C@Y+8&phvGLL`Da+879J|`_YLS*K|G$m`e7t z9JT+3_6b#Jf;wuW1OQhs-lA8(7%ZN1829~U0e;N{i;+>rnEGCK>Out%9^SPZ-+lNI z7JoDoYrpvxo0cp=q16g=bTs?|0(b@|@fk`3^H<5wngGE$UQ-WiZn;A_z&MIxJLb*V zsem|+(S<eRXf7_hG6iLM46r8xWKvHHA|gYu-+BagTHEh`XdIK~y5rKTd!q~C(?6cE z3#k`(L{w}b*96CN&QKR;Icx|P&e9WYp&pL8CFWPjyWmgsA|JC46F}7|^d0f@6x?=| zR8P)z2qJBvY0X{7GcQYLf3hxIc$vK5XYoZj1&gx7%-S943-2HXbhTp|3vC+Q?6~R9 zo?L|c!V(ie&3-*ml=dPp#1jLC#w%nlK{@mE_drDkwZ}4cDG-||TsUh{j41c2nGf&B z;44BA9UG#wkng>=3ByMw!fJEFq2r~v^)FN4<`;sxTyJ#l-X9Zgz7k3OQ#2%~CYz?S zw$l~sr347a=3kLhp#TGf84!dX5oTjkV^QTz)UNCT<+3+n>OyQzy9_;gN0B#i!;YOt zFkx~s`~xIdQ`3IBBAtvjog)SG`OL9Yw5td?%ktpH&*q_K1ySt5{a8?jWKE1q2t_sB zm2-1S(PvO3{LsLJn{`MT6t5nt7~pe!yi1-g`v<KQ%f9*w-@Nc5;yZRk-;{pnmXw5; z*cfE(KY)cFeGGGl1VqNho>tJ8`L0_w%1i2q;O0R=q8h*MxDZhTJqQ)<hX<}+g4zE( zj60u6!T2d%aog18xayW}Na_`VyC$s0b@wF^CfpP89fDCs_sOK*k=VO45AV#_jQJmB z;IiL!z~~9x*tcy%sF1N@2o^3mW16>7vM`l0ld-ptCw%Av6_601vPV}Ott8W1&IPW4 z$9>>VAwv}|fFjWw<>ygA=q(GFFeX0C!%#}CN5#={3L1Wz=Di)#SEor#T&U8-6){J} z*R!_ai9al%W+wo@`~3*`($b?Z#zMP#GIy0kh7u-E#;gdz!u09I88%$0uM|&D57dN} zVD9X_=-V#=OBd|Jzh2G4`Wc7t?5AmX?)mW;IWYyDyCos8B7z%XCq(q3Yn0A_=r_`C zXS_Be1sZA2XUG`R$59zwMXU1|h{<Kp)7ul4(H15k^`o1f1>byn2)Ew56YEz`!JuIs zRPB~8Jb)GSmH*XkDeAk?h0WM$ZROVb+Rniss84z8^HI2|2>wKUiXym2kOOvFDNG!w zLnaEPwxTMGANVaEe{MJ?OzDp6MlD9);X(B4XI>;0{kCm|4ClL^`kYN$v=}Qs{1}(q za6JYN8~}^Og0S!~MAK5yIk5{}k$o}m-S^OS&_D!-g(==fpWjj^`7HBx=Ev*t>ATyg zET~48#3-!Yvmf_8x&=2}-5trp{4xLiW4QBgLuiTgM}vC>wrxK`K$jF`SdYQVoFgfN zV+lbYpsdq!-!QW;x~BwT;^mz&Y*ZJukIC`2Qrt-3CR=M;skTwi)}nxnmRTQd#+U!w z#R=$#ORngHNf&ol)e-Y-!KOk~Wmmz+;)9665eRT-hV_ZuTJ?5?=jt>pJ64XIMLF<b zMrlF+5Er!eyi;u3kcCI?UPQ>gVEp#MK}hTtL2}f=&!1Ku$rU1lmK%VbCTT%Vsj<}{ za%d#N=t6L6(P@0@@s|9q*CC(g^hfVrfP%7OL|cOJ@Dq~|6w3S@L>Q4xh<b4Txgm2A z12+k4+If2CmaL}nEJcBnELt&LCnPapQC=Pc>zPZ%YC@!60>_{)B1T3qcg0Cplv-xu zuA}>?kB=8-y_b$1+jFpNRwiDWa{-x*ID@wBfC4hl8#NnTpPDkP(tLd#kk7KW=B~~| zU2z?PdITYANF>}CKc-nJGzM2buUmdZxlK;Kv<GIsw*gD$9>qIfT!~%VvvJpj3vv1F z-7)3LM08D#P}jCcU;v%2g+$ML|L_06&|yQ7l9EC<q?$&W%Q*tqElSdb^S(nJ6OUeT z+pX~S^Hb+YpGbdw`rc;z=hxfuuf=0%W%R&j@9)B`_YB3Bwb}T`Z`NbZ;mZ&m6OPHI zPw>>T!4wj@!kS--NXuu~zxN_0c_nILbq!+Tg4KOxNSxXy3c-+x^Z2+nQw)nmo9U|7 zt!O>{2xY);T7MK5Ok07k=3b2cgFE2)htgn)_d>@m;mS2flIn?uP7;!d%Z^;<P3wh+ zo(%5fKHRzuYJ*Q#8xL0=3EuW(7S+=IrOJ<6U<EyBg4GwyHrTXZRAq5>>NhA#B@<h< z_%NRM<6^8@eh{C%x(@>w&=?v{EfF_eF9yPkwW21w7M0ayI1yOKWO9Xc8>k{;6wTP& zG=#>jO-H>B0f9ca=&By*(>npF7j%WM$(=#n)i`pf0JA^b#cbs6h+{xs+3|7;OSK4R z-VUR*90l}oQb&el?feCpO?L`gRu&P+%27<F<!E*~_Uzt-$nJ6II5bfMw{TR?gFqOP z2(g}wOZM>+<rejar$?e^KOK^~<=SC9sbwi3<3|2;7aE!G@+VBJ6n*~7yC{r`_SlfS zESFvZ?uZ;7frx%)R8$i@g*gz!Wlqi$6rq`KZ^5!9`>=Y+aTJ%7;a9&Nitj$(i<0tE zOuR4|o%@<_B*Tiq!#gpXv*t#tp3$<+Z}q3?^LFQk4anWMA7jRk6QV02uW6su(@n~Y zNPB^yp;-F)7wFA|qo$ys#=fhh1?fM}F30mTFT{XhaTE?hFe<eR6W_XH;D}g+b#1`M zFYUp_*Crupq6vQ*xgJ;jA&EIE{4r(-bJUP8it7-fVlni7*ZV^oBbwal&bmqU+EOtx zD7B^DYNfi0EZ{-rO^S?f6Fd<R;LD7Vfq4AYQ4A8BkFwGP1O)m*VgW=OEZL3=Hd;}z zzCc+p%!AA-qqN*Wr1G{lQB>od;l)9?;KU#WpPIKk4>>Dx2?ZSOaEoeR#FeomL5cGB z-mB~J^~?h>`T5|U`^Mu~PBvbBdL#b+%0#;DX&I<Z9sIF(-BxU#w-DC1_j98#!Cqa> zxS%kMyy7wp7&BT6)@#K<s&9OS1!dGaZpPs~WjMaM7FAXPBqY#Rnt|$_x`ohsA%6Ir zn`BUh0TH1|VxNrC0u<o*(3ybwpUg!1f`yoH=_Q0bPCyWuk2(sfg@uKTnb?7a^WQ*l zQV_cJ>!&KFUnfIMGRaTAOf0^d)wcy|ZaktDDWK1783UE2r4$aB4~hf96cmVnkPuZf zK_rr#G&QtBTDgZx!aILL6Gsk>fM0~4I%oe5D}FnC0T$$6qZrfIUtEpt8}jkytm!B# zuED6#kBCAPju{_bL?Pl$=1_{JpMF>MCpS>bo5OWV-LM_Ll-P>N#{|HS3MG|P^c1E< z`bhf5&({~8RFK;3T7D#*RGwUS>#E(bA#FdZ>&qDzk%KvJ9mn2PRhY48D8`QMh4)|D zfwWaek=(Nb`dqh*R*F2N42(zWlrHKv`gqXqslUs!PpOXnQ=51<1dBG&O|4r=5hjXl zmDsZ37)GZi;V*yfhzD-|7Vpft3SKl9P74iY(2}Q0_)<gdUDxDD1jL)DS@&k3nGMA| zijloA8^Jw-l~z^YX;tCIYQ$b>r1&|np-CZ%nap36uku{PFz1C>F4~t{ls1S8@+$Gk z9Y^uj;R|t)8onL-4q)1}L3sVzL#Y0b!~^+Lt5=5&YZqhj%6Bj_bqX%(I#uO@kj1^I zs0fQcn}z*bw&2RU?o?W;pVKGj^O`!M2w8{@G=6F)WM|k>TkVcPBjOQGH^ZhyAa#{s zVK)SergUl6&yHPx_omI*zF-k9CB$#%&Yj4h)G3z=Su-OdB9J;Z6<+RMnDgrE_}z2Q zARt(Cd1%Twh&4@$+)|Dk#Wg9QGm+)RHl(lLfPI^{py+renb!t{Fj%;A?_RX9jz+LK zoOw^mk-y%G8p0HZ^$&x&Zx}onGp668_kd_jx+e<%dU6>adwPmWrr6+Vr?LqIuJFbe z`!Av5Zx;5XhY>mM8U}A`G!?6e8!esfdYEa`O~H?g5ci{n=j(Krb7h9gcgaoh`^QG& z!I7)s>y?d(e-6h(FAPxeEn_DxMb`0a@yOF-F?96DjQ^X4CuU3}-L;1|bz%F_=eNNV z3|FW&*lDd%<%M7G$LiGw7zma`bcr~8`}uxk=jUVM)E=1hYX+Bodk|0FyaxSKBZ#*g zMkcO7<zSE)3omL`tC&r)f`Q%Dgw6D%Rl}Q^rL`H#));%~&+bxx<`tm<3B~EfsHv>g z0_mBfq3J^Svv1x;F4UMby6NrDK<6GN3>w}MZ$Fog4`17iQ8z^*b&`gtacI{reD>&* zxa|LagMNLPD<CiszSKsU{QY4zo6#jXnaSlg!dhTO@_+%Y)_Q4=^q*J&6T3!Z(cA-A zyl^kF2v0cgn;i&YQniE*jzB_c9jl1OR-JE0P-38(bL~s8Y_)Du5B`z@LvpX4%167p zTGME1t_yTep(};BR)mFzV>g+|;20(trA6aZ;Xxn2ryg(JmKje!pA^v7<D%R=eE#w) zIIwyxl9Q7#WY`cjetgL6Z(q6sd-rZfaD6nYch#ZZUdsfjQII5`Oz(+;+zMJBe3V<K zSi1TTj>kRI*P_P|FWhw7aQyG}y;LmKAh~z6a{nCC*B|NIj&uFf+;5~k+E_cEEo(#f zatIV8wNL`K9L7YYZ%5be-6<sasN5Az=b-eN#98E9^Rau+UJSc%3jDcesH@c)4aCNW zVEXTqaoxi`FmilnL`8=oJ}wfo|Gp1XZnR*~&`!Aix~}NngF)eD6XX6gm~(y3tDCvt zi(#>F!A@<CCIyB1puCJ5Cnu-B$r~@tya>S|?_$*0Zc5OY`N39vN5SF0gvQj1#8-$5 zRWOtGn22Chu)Po^J4=w2pM@}LU_yI_GWNpZx*?O^5HNJo{iJt60%1%S!uy63_xc11 zHx?q78%{LQLL^zCzM1``o3+uCWPuj#k00Us?tF9rZs@lVSN$;;m)@9&bOtQ`-z~$D zmsg6y>>_Mk{vF29-6^SiH^ubTk@3@qoX{$qs2{15FTl57%|XVfJ?Kb5qqS-H<>zVm z?Da!<>h*CDE#<O>`{>8Jm|C%D<*O^*CW$L$Ue*&o-6$<U0ge?>`IgYtsj93D(NQT5 z|9H(;Uax@&6(U>&Fz~Z$-`+U5Jso`+DC*|nu-NJ+8t*Pm0euaruBgPEH{XIMty(w! z?zc?t$#_IZu|!3oCoL9>7B0Y?cVETDm;Vk4BRdmTT(eH@+M0_yCd|j|-B%$dUR>>% z2$Y~5^L9?7VDcG`WyIk>A56xuxLGQ3XbOdnDVOz7Yok10N>irU@z{3rruTs~xURB} zIiV`ybDS{V`8C+Ndnbks875{3HD7h2czb&*_owyCSEA4OR3@@B>py+({pjIBY+ip1 zmtNCH!GeD|b33lMHv*kHMyvA@yJ>M#?WrFeT;2ERWPC9w@B<2r9M>5qDtz(8pT1+T zVKFieS=Bi~%n-Y0XD)I#HeluANvNu1AS00}L`o2$qN1#Zh!i<E;p2v=;TBj@Ewos8 zGWNnsEbbgsWLIj>5)MyW|FrMM>scwFcpC~2q8)Xog~HrF9HIR~QL?K<p@)djp*E6s zKvMw~kVU&^-&lphG4YBH7&tf)Q!eU(*IryoRF0SM*3+wCJAMe=dL%0?vLw#aHOoRL zVFJaCFD@<)p$yhLvUi_Ge<5@W7kv}7SDnC`g}He3t4ZiHFj{z7jJu#a{`zo~$~LZ7 zO9>YI(m<pkqclGS^bt{AQ2`H*8z0eja-2y2oLb~uPcJ68^fz%W)comBJ;(U&+7!?k z`onv6BXiq!jHmKKeETI1M+8s#tE;UgdYCuHrj8@ZpBr-A4>SrDLY<T$ahU$6&UpL} z3s6g!M}eaj#a&X*D17ktAUtvRO2TXh;jMWiuz2nvCTY_~sjQ9ie7z2?P3hCL&M{e~ z{dG)^TD-3qne(%eO?1HE;BZX5`39_8yck>P0xNMAV!@U_nbW0=fmynE2?}gQ7%_DU zW7)iv0^w9zhJ^WI;a3?L8vG@0>A#3cSsU=+lj9IXmo!m&Nfl%pX;1xN2o|Rqi^iev zQ$Qw#m|mZLZ8CcIkHS-Ttw-Mhi}3a5Bj}b9fZM4_dE(Py7&X3&f>Ili`Ud{?*w;wk zdYpl=_4xN4+fgaj4wjIv!HAy{hu~gBvtd4l?8VtAOfNzM7t2QV@%JBZd^?2#IuldM zAiXt9GjQ}^vC>*c4vIiXpAZ$(B6#Oq-!z&!7S~g5<5FK`(UwI#o+wGazFv6oh3^O| zumHW7GhplHU!l$`5c&DR@b=WuS5CtNDtdU(^ldVkP)TGTDXl>|X^Zr+kDnU`jtR$y zuWv(PUKL#-s_@OM9a#Bpu`<D{3i8E`ZIU@!ZwMATNt*RtXDk9JfYy>ZtgNh5f~oXb z(;{C*c&(vJONq^fkO)GAQV=mp`=fv)V%xlQIR=hPMF`_;M2Vq~BcX<T7REM+t~Nb- z^}+6q8yXiMeO%ps?<nMF)Z)Xp(-Z+kXpqvoR|lkjQ_A(P9%CkS$8YW)!n`SGTH(|s z?dOGL5Nm$%{t{#_%|_0WT+}n6uP8Vz<08<1@(5gd-~CuQe<9|~{sL>(tYI+r2CP`V z9J82-J2&5o>mK@(%D^rCAPOkGu**~tG^kf+UWU(ijK!<#Cg6AXjiJk$mzIoG1UsIk zpQ~T%>hFdtl&kmfd1#+Z3Yk>m0(JQ{DU7SA##3A<g91(X`297QbaM>GPV9!GM+z}+ z@)9hbe<K%96AtbwMdpzT{Cn<L1c&-E%cTepZVJ6ftQY>#ekj~r1nW90s&c9j-Y-lg zeN$DIJ=!sG&qEVj<`%*{%Z4Js$}c>#1X+9Qh=W~+8}3QQ)XRFoLPV|;^(SE4W>djw zk<2Bc=-3YEsc*Dv7G|Pnaso<<nfD=BLs6M<c?JG*)f(J<s|*E=5kKu>&v7mryr;Lr z7q+!Vj-$UWx^w{kdf#F^^!qRA`rw1iT@83?!DQ7gWtHMsXtY?^HCHInk!jmmYgN8| zJl8}fFd{w<hYuY_H~MJ{)1d{&Y73XFnO<H@R#se$-RsjZ{n1C1U?InwGL`LvTJ5`) zGFK(fNf%~t7Qg*7q7acxf+}V&ui!Z~gf7+KWM$nqF)s06?@uB)%KwniF%%P~CMlw( zZ$FAbR|?d$JFR1G2y-AuwMUAdI=9M>l)<*U7*$zSDq*jAuo=N!g9w4IA?gW@dJh?b zsDH&{`<it)x_1vjGVJgtO4ks&&GsHLlyMa&^Us`)rt-{yU@s&GYpzcs5%Q37M?T&8 z?ctaqShR;;J`Zh_jZh|#lt8*aNvXA;z}7W|m^?j^%q;NuUCZ(L%pn+mL9+UN(cGn+ zpl-O}q9oV~E65m`)N#ce(|sp6F&O?){>V?uN7+FJb~C^`JS7Z4U4l-c8_5-hfT8Fn z<ky+!jcJFm<>ea8tGNP_NbOg>zD9>Gp@bETg=H9p3u<YKwiGIDcEkYsh6)WG%&NMX z3%}g<$3cy)lSE|u_hn<oed*{BRg0p+9K=LNk`^?LQvlN$JYfh0v%H9Y^MVl7ze7ju z9;cN)8gJihAM_ZRFtb1Y`_aWXcDR`B$bscICb~SBD5T5A<*1^Hv7mo2;bx7}vJ@Cj z7l{x5`XoAa?t-M`?rM$HnHvwrQk2tOYUPrph)wB>j@`PpY~_rnIS&Q6{>pd|%Z0v= zo?1D5+(>Q{QNT6Tj?20)`(|R28S7R}!Guu@aqn+4QOcyB1^MN8p79lOj(%@_T|4y| zuKHc4o#Iotu2&M!qG*Q=6~`*!LG-5Jo<Rsr4uwzHNkl!3ozeDy2m&iixbg~(JW47! z$#m%VJ`?S9-pIiosXe8Ywv97YvBi%Ys8B6V^0xaxnLyekB45VE&y$$>z4}|=L0F<? z3l1SO(}sKgIF{O8cg9;B#J_Ldj{nZS1ZnFIF~IB#Oq*;b5|0VKOjM>VfQ-W+^lV^< zKzK8wa1~uWO7@izznhvGCh+lMEJk}`h<|Kk$YQl@K7DUVKR@PbSiI~2rd-_x=13Ew z`<}qp?`I%&Y8U!~x<iuY)s)t#m<w^05dp3}&{E$Ms%h@twj~44J-QON{4O1D%^rXO z<6`m6JIk0{Fp~bwo($IZR({Gd7DcEKt>Wrct5HR4#_?BQ#qsUcD0uu>-!#wfGT(sw zoN~<g$4Wf-`(4CMw_*BqThJ>}VoGAvV{6D<lpiUFe~h2j&payg)F{nQfzZfE_=kmJ z{-+<Jj*^~_kFWBbuVm6iK^I%{-Fy^KP`&EDdl5u%06j-R^ViIHf-_M-W@C27Q55Ip zQV8wr*hfz)HuSp6yxqHJ59(>v=s#wReptP0doSHRi3oSa^bT<It3r1s#k}Le5r|+O zADsbiS94#}b!zmaKv6}PQR~J6dH~BnG`~R<(B+9<70k62Im|+#B1A*XYeG?3A{%L? zZyfS_leJT)X_L!N`%&(5^6#{#w5x-L#iCtx_xWk8nve|4mPsdYYx3u3^1z639he>1 zgTdWpxOL)k%-%MYTH#uxPFRZ9Ug%9f#!S5R&mDN`webj#pw^X)oD?^Q^}?4yPz75H zQM#`b_Ch<6qe7KcL&xdYEf;5<>`fPl^V3|Xxl3trHU56*8qE0o0>%zRqwj<eeD=XM z{PzCgIJmD63EfR}f6}Te`7EMFM<b^`8&)PM6dH#Sw$Rmu3w>-@^U86}&@{X>w+H_5 z(o_V}^!?V$rTFJ}q5NBePyXHwojUYZjG*Ai7Zem=+qSLPyCWSp{`D{LC1Q(yuhtp> zQZK=rpZQ@LeX<*{_xLq*fimF_kCov5-+Y6CLpm~19${w*?JPyC97bt53h3iw@Pr9) zuk^;UPiAAovZb`lM$(#FLv3;~Ql?&jD=xhh!GxSu=Qr`uZn>HoPijpH=>0B&!rKo% z$bjpfNbJ%@xkKsol5-?rIGY*UH!>Du`a=)uKYj0Am{&Qsd#?!fr~jy)(S#w^kGt%< zZhdzwd19d`*;k@GAH?N4sCy7XlIam4aO|=!Is_1%hxh?q&Op0H!Q%%P7zfTq0Yk7j z8+DA|&PoA&krQ5ClHy5`fZ-3`T#vh+?uK5yWAMOjU*nCJd*L^Cj6hWdA@Ty=!)=e` zP_RgBbnB5tQ6|2aDPzaQFc*VKF~fOG{8o`wiHQDY_|U&x-Jj<+PIi74bRQ`aB*`Dt zpQ|pibzCsJkMTvPM8-|9{NL?^@X#Ieap~24G3}B*@a4u-Vyniwl^K|LVRve1JP<uL z8d(gG&tGFzZc$<mKI7u5?%K9rbXT#c*g$MAn@-D)7Z!fA2d_MzhAv&gaqs{2M}+A( z;aWG~(+_vS6kt*s=E9;v)Y;tdtH&NiEH#SGG=|Em%5mZ4JrEriq&mkG7={qehg>Gn z62YRLZf~j>f<=oCUOkSbuCgA{-ieqx{#Hax4M%a2m5D>W5oTecQ2NUYAb>NCrxqnr zdw!TqOSL7rDMgukh7Y=RNyRs_KgETYT!}7;T^gw)ryf0e3=8JWL67mN=+?K7_M7Z) zvadBMvj2%vPVJjKE^A)np4j9(m;C6UslJC*7gWQxr<ejo8BQ>vsHu~QF!mwJa;qy^ zjJenHBf$_XTAt=E^+Z|FWZl!P53<Nf61^V1qVV0^9mv>Oi6@?)tlDhNFUCOlz#JBi zeC8I|l6D+@2E-ypa$oQs`T`J4<S1WabQf$ZMA?42Mito++MCu39(Sjkk3jS7VaD4n z%U7U;0dZ1?aAqz|88aH)`}L#N-lOqeu0-sxaDP~W&G_WqjkxumVa%*b0po-_(l=*e z;zix?;ah3A@zyPP`{jOYwx-$gWVY6r@f0qY`8;oBJ}npS2<aB=N@V##Tg0Ww%gY1R zWcdDY=NBl<uf`uAAA^zOIuY%uhRKjTkbgV_2Y2p5S#b$`Oupz96b+w@5Cl|*tNS^} zMy)Xv<3{(z?c-MC*)QsdO`nYaKD&a5Q63mLGEUXi$A`-}H(Wy*{)3^=A_pq-D^Nsa zySS-@D&|@i8LJ0zYLC~~wHB$2@et>rfX)E3Hf0%Q$Xk<-`icgOx%3i5=0sus*WVzF z=uq)p=oZC2Q{1F-m^`o7m{FK`&DHQ~gfRZTP@y+YQ=pvwH~k!!dKY?1aWvU(v!jGa zPsM~+mv}<|1b>7i1uM5|{Tw}}&^gv}t@`cd)s2gu-ev8j%|<(0fC4hC-hOcdhK-A7 z;-47eYB%7n>*gSJS_1P51mltmmf{~XI%Cbe0^Ix9a10)ipa}!|s^C~aigpz-2-$|3 zk{X23pVU3J9t*zw7}?u*5wT$q;clscXDOh7u_G-FJ;tTt!W(aZH?gQ)aYHXFsm6c) zwu)JqnY^dYj-6>Xyff!YB1746_jPlLXmJ96_{T^L7#5@A2Xt+g&`*Sm8e0vbCqyBr zv!?xSfuYdikI?&FH2Swsn1>J3E}|<GP2xXNa#!b}^gt;(G7p9~V|<*ctY3WuHSF)v zW0P>?fDImQUetzrps<93_t7eN9j}FDI1x{}hj85gK*{M$O>wEZh#T7R?=oSx+R^b! zjvLNPHU8L2#apU%tQ|3cF4em~voWgUwQGy1P0uIlp(w=6L(Gjzos7MEab*8~6l7(K zMhzhr3p#b}iq1WIxU6FAXHwHKk+iGr+`mc>mr|f8MqODQ0y>)L_7seuj)BS||FfTA zTzFn7U<elHm7K=SgaSHaC!ZS_P$dc6X1=!x8`d1h&9@K7l>_JE(~TFP-@rI*T6YvT z4PS(3XN|zL>B(}5R+=o%)kW1P+G<0|p<*oA_#r%lYjDBT3t{16tZO4A$xu#iF6MqV z3tff|!L*xhA`GW%SuS+C3L?<zmHP-|S%#rQl9cv(QrIl~{=FWUbYU-AkLp!Gw-8cO z8))uh)r?!oUX;bmqxFcH5KWh}fbT8Z4$8a4o6cNk9xYb1@TE&s(;~Fgi&!|^X~_{k z-OO*Z5S$!DCf%YwdzzO306+jqL_t*l-0JVOwe`r%C`DcllON_4(lk*=YhOM#yjqUz za(CSGrv!}d5rL>-5eTAHPah|(Uh#HNfy$gp9RBzSEMp@PF;H_+YzNIYns_D(G}*Tb zwilu>t&l(@o`|ALs(+NG+-N#K>zL0%zKexIFHL{fkF~q^CUlgx*OoCC%)wHW?k`1k zKH*^F{1C!iSb=dSco57)ioWKnFUCu&QowM9YE|;P=EHSnUlvFeOw2{%*ntZC;m_l- z_uvs+c~`s=Dx~$MKp*Tz4U%Y$kkYuQI+T3!U4pu!$3)>!?l#o=6k*zh*CH}90(QHW zbWg66XT}jDeA-o4V&-eFBWb_@bnDmGrBt`lMvVz}!|2rR(ri>#*fD+JY&<%vFD{<W z_Og8T!Dg&kk%8adGZH=e3WKa&=S#o*sIgH@N|=M3<++HN7z0zZ1Sz`|nG_AgJx|6? zTnD<@ou=qxRDP1cc2O@2a#l#MU=;5xfhj%!0r94j<MA~2X!b*n%~dk({{0Wj;8x*- z9BUEgrCYIe(-pYyPhs%z_Cy7d)XLLKh-YbN#+$uQPI-VpW&2uu)p(Jg8rQE=&NIHc zJ_VZgef~x(ikPFxn`>?~*Id7dlNNMYdj#HEiYN0_NYTFw#B9^GN&VQ4UK9<*Yr(OU z)iXCwF@v(j?Wv}KXpuyZj2>@6AT1Q)<|{?^L~$pRHQy-CL4lKL1f8QkMlD>30@?zp zEfNnrF$%FAgXj<Gfx3n|l$O|0R8WN(Pp(lJyc4@=8sbwIRsFj_rtUkki2>mQ5EW%n zLWL~C`k#<Z1dS-dKlT|uoXKw1XwZ%>L{DjoETAF?=rJuD)*M69XkR5%WMvg$!`dSl zK(wIS?jDI3o>+tI%wmlKjzw3PXW8h$eJyuc4yu?tL}$WXj8tla)`A>=ddotRLg3-= zfr3pMT%n9V+3Z@SIQq<wu*+|Lc`4pnd?9{yUw8QO-)C>{CK?oT!36rjm%@bzMiM_G zMGq=&l{&YFhdPFXKwH|aHOjx+-=b-w_4TSfkilr<Nebvfh4}mDtjtlC3g0MS#4^6E zsZgO`q3>IAo&4$F_21j$yAlv&GvfaSx=u^B>|=9}!@Aar6E!E4D_O#{c!c%{)r1O} zhqCT*Pc({iQNR!^oU1=vwl?Cb^zXl}LD$3%v`&QLg}<!B^{KOA3h~6Gsl8RZ&&I&j z*HIYBEnrMVaHDpoDY0}0P%@Q9&_ybb!Em}3sOfpG@L8?B^1?E3SUi69>p@t*;wbL9 zW*!!Ny9*z@vK{fALNI(n9RBy_Zgrk8n|aygL=b9#iRNR=7>ja=%As%}fp9wg)X6UN zqQ#i}ga(nEGX5kZYbarkt8%JQe1KK}meyv}rEluEvA+4zYCLiCHf-5`1qP0a#uZU> zkd;-&`Vk$CT00Nsk1<N~Q$RACOA*FLn3`ZF^ptY8^-G;#ytpO>gt_G$t*@rb;?gWy zDr^Wy3_wgO0U?NVDC0tYKV6aIXZr1Q=ey!Decs4i5(`D?ky0F+cN{s3a!At#g!K<Y z{DpBar-U=NmBXb~rl64Y^kj8KGPS8T8Lm)ms=L~5Wxc#S$=H@*&8j`P>9@o1?u(o7 z+>6C{W9~(a5pY+Gk%n|}<~C}XhJSn|ku&tg>dbd*+rP*@#r=wKsXiDmG#=m1*@pfD zI^fSwjZ$b^Pd~f_t3J)gtBWqc?(KPu$=Hjd>9u(5#j%9HbVpF9K%5vu?HUD(?4>z~ zn-mMLKqiSh%O=>iDfZ*F*Ee`!6iW}3F|Mab`9=E?3%#}JGUtE0@bcaJu!6ZPq6j;i z+{c2Xq-gqPyQ7}kJ=tI-*`!gLmje1Ys4lEV#nB4*#?TFlnb4)^<EMFRVBFoQDBxgl z>nK!YFU>~To-%|E425NIBz^xmA00x6Q`OnYDkJlzfl#sK1ly2c_sWb4T8%vsHh{3& zL@;d3maPjDQvV-k1Dvdt?W;P5imQF~cdN8k7+?_^{z#^G-?kF``Hx>>^Og*3*fyO$ z@M3`wQ}EeW3J(uY_y>oeq@+ZdjZbZezTwvriq@K+kEkvkRY2~k=eg2%UAT}o4H?=E z)h8-2^X;uDApGSQuV!P$cVp3eKr|MAdw}s4b8*>kx~Qz(x*Jt+*B~a*qgz=4gSshP z)K%6g=16GSo{KQ(<^kbK16j1grr-=)n`!JL{g|f-6=I1Kw}Boh7DSp&sAh(B$+qsU z3l?XeO6|F8&s*CCLR3&`B%`lla@zNlId61rQ#W!jy5hEzvmys&d&^)RWkw7WlZsHG z)=^_F{;*4%%oSP3%8!*Ji<Ta-P*mquBZScM@zdg984`JVq2h;kG>$kK1q_RYlXZwI zR!10MDdtEM9)Dq?^7{`A_C-*TFOUAXP@yj7JqC<R#mbLoqFeWFFq_Ta3*IJ2tP;d! zXXjwoj?I{M!<{^&$<2Cjm%5LOy@)_2a)8<5k4K-Kh~*0p;DU?4z?Ka+B4tn<-hVBP zXjw)0YUZyIpP-@E$Thk!Cou(r+?CH{gd$wTjE_-?^>jha#mM!On<GK(Aw7d(+rdEY zIDRIKtMY(pB}PFq?Mjh;5r1r{i}azmX-Q%zP&w*B>xEI8kpd*M2$(e#<jM|~Lwx81 z804=?D~*e0Y^`zYvrs@^9~%g)khLNkrMpUD9%)vV3UwLhX=^JZ&QkqW`AMgl2;#CQ z3!<7>D9R335W&$6f!zYNY}wq)^wRVi{h0CY{1otboW=R8Y1GgaD8PML_<saZsF0*y z!Vv$I#aCZkBAEnl&-Qd2*trAI@$nQ)f|v-+3!cnuEBDFE%ftM6a}Z?jfR5gRkmSyc z-QLvZY9?t}&|I+yO@&6D)LL7}5GQr^$G8bSF!TL1EdDA3fBVl=qzs8c#^F5Ds00Bf zfB5)Pqb7_gFBVrHH!nAoFxiluLG=NQ!4TYao$jtkv$M5HwVT41SAZAFkCrQVLlY6A zJcMSg$S|BbU&JR_w55WWBJ~RIts$$Ot!>6{zmNi)Yo)YS6m2X*RH{WK^8AH&G%jyr z6wv3P2o>41RFv*4Mfk9AL=AO7RO=gE8*5jyRhRV8NFo~*P(fJlLb{n+*A+0YQY``* zj}kFB0^vl4lr^_$`6>KC)ANnb&OreqkA-veL%XVlwlJStlXZs9mY0t&Uw;FIhmW8y z^Hqd|N`kx-urfj3wiRoTJaG(CFPM(1gSFI#*x(=UkMMrs%*g4`EUFdm+@9T*&aPu{ zwkKmK(pDY8ougM_aluva@^!;s?_Y>=D^y16$OK>9_t;1#jbkz|5iZD}N-5Y<04s6V zWucE2bG~T6U7TKL>RWxh6%vbHHo(9xqzSR;Mc{G@nmGDJOMI9G&j3%vPl-QMbByEX zi2@C^4LCaIn3^{U7bn2eA#932lnD4nad8Sb=A8)ixodJ^+g6OQfnkUqNu)ia|Iga_ zRrys4@Su$GYxT^f6qp#uoI$||i1BA~JIA^zcQEL9RywwY9}SB|Tj(VhtK-kvfOXA< zUw{d{C{P4gBCv1gE*#ji2gi;bgPX4(Qm?xjBc@D+DLjAzg&#Z#(^;~+6y+J^N~0^V ze=27I>G^Yno3_vmvL*|yB-6Q@e?5kdi^X$~uEaC1?8E~Pq~QL?N8!N!Y?N1)qf1wd zqKnQH2^6hx&BZ&4QCn4u00yNB%&*R5w}tkfuR1CuC=%13X8D?mTF7ST?(42{U8rZY z^5!SVMAFY9Sd{E7g<mw?awOrQQJR$k<;TlWKx>70aJZTu`rP<AYg)5b)VTTir$7^- zB9Hk?iqne`+9w3jBcjy4;;MVfxe`*J6Z&;E#Wk>QEkNO>LR22Fq=noUmXQ{OF_ywN zoO?1ATa5~?R$f>2Ivel5i~>fC#V@0uadit*Ko%{@QkpVq6#6n>g$^m};o-prO7k(7 zW8(fKrd5GfwWe87u)cu4oMnjOg62<mAz=hgT>x9SDb4aYwMdgjow|nO!DmJyH>U_W z`CQcV?#0ZH*I>!~y$A^MMwPuvJ-?o~>mHtxtVx4CG!HN{Kf(!xh~!Q!l4V$=^JwXt zg<@y8scdi#M#4;sS5<Bminh}N#<3%=WBPArxVps-*V*e-khvH8)F{nN0nV$UbQ}CE zKFVECo~B}4c)X;T*?|4M;LQMfSy%X9DPDDJU^CauxP5a|z_Auc{vYcmt5UuMB@_HY zgbFHOq{DUPS94dzY41-O=?V|2V=Waj#e0iUyt@Pql@0JVds84cBZ%GuI?#mLgJl*u ztmaPJHlt$aL;;NxJEu#GlMM>A9tC9amc>YlJB0~PiLv0HE>y^GdT^_p<4YXz*a<N( z4>K$CYv#Nx6l^O*U6t!1ASoJ2<5_%teYn6==<;a5ZNE#!mW{{oz;AY7$e<qBu{9G5 z=ImGW(&$QNhzoq=kO-xT6U#;Z1|}zBaJS-;G_q?qZV#^x;lew_ThT;(s`J-bQCCr? z(W5nK;OGm%eQzMvyLX@=STt96IF=ifAFn_)^P8AD1;8iNM+MKzyeP6(;olFZG3jTe zs)EcV0Z1^IrRM69R=BUM1uV9`HrQyXFm*I3ONG1Sj*+?8&P`ToPbQ6I?AMmpqG+cL z$L1VI0bOf7iI5dB+>H3C@d`dx7b=9Fa=zMI<cm>Uh60Ag!ezR${WL@^MrUYl<8O|Q zKo))Ri;w7UR$4|Y8ReXXxu`f+!GJOIN!J#&Ih^69ZSb(({E$!+uDYoYZn@wajJhfW zBSt0Sh9iTJon>SGi`f{82>8uE!Q;&6%#72zN!l{ZqAVA-Z8oKmiyRb5Mu~)QF*^Nd z*E$Fp$h;q6{p~2AU{SQQh=NAsk6zlGC+Oo`Ac93p=oM%z<;_KJoo6iGN2qJM!v%MC zB*d%d;NYAH3G;{Dvlv;KHX`2zqO7<U`MG8AB-5GDDcI0tH&@5B@O{hZQY-OR0tgZ{ z!qS+h!eERw0YMZlWyi`;yt4$7mEM;wPmv=d5XAY?7?j=K_Ky}Gqt5a;V>fb^o-pds zJQQ#+hN4js7naxwu`nf+D1Xna1=%Xga%hjxlUa*hXyFr0t1v}VE>A+w0TC!IEki<l z6n3X)<JK|Dk-p_h#KZ^VuXn6qehV**7~K^ogkHQ*Vo{<;MJa1V(WW9=hujdsdo)@u z9CU4-PIIoi%GeP`UUC*xW>pe`wpfiN5jI*)&?5cCl2lt-2ho~JJdsh(KLzx@7ii3? ztSb01cFUKsS{Vmyc<YtbNJ@^zwKosMC0F&tUw^j>!!8cS(E}Cu<b4~yTQM0OIuoYY zC~cPlj{Tszpc;8=^QokAWA2|QcoX=d)#iR{Q}dKPQUci}TXylD5@pfz4Dv+eh)4t_ z2GNqLp**R1B%4P&+BaIen_Bbv#%|P_)Hj~pViXX@P!>0V43*dm2_Unk6;K0RfQrmY zScXR`w<%fpbgg}hRj!#HBtnHQTqJY|=TpM5=$n0b_U<%%x#tojCNT+IEnu&+<GmT_ zhzJi+$J{&|P_w#l!I%p%X^Sthxu3b+Z4Ua_@nkmG2)c=7FV0p>hNqt={GwVRE4O1~ zudk_x1V(z(-OrtUW|Z?!0g0iIQhJ0)Hq5RZMkR!lgf1bt_=>*x^3!zOee-7s^bEzG z4L1CS00}qT7>DOx@`A}Ed3ub}W+~v9XTmsJHx@7<Vl6CVER3;no*1i^rmZe$%X0-G zt(=yB+b$cbva1v_ogidMwrnMMIV#umnwGAw@x0cdfPo6tI&?Rl+6oj<?jw%PLQJzk zode<K?S^tDyOJO?H>SG~lnM!2c9q3YgbOKpdyZ!m<A!_sqD%L1RB)qw?B4GX819BC zmnP$#|88bryc@bEMQH9Dq?HI0{zQkWrXP86dI>yC9$N6ScH5J$BEd+}AE<qEnOA-6 z$hh%h%thJ$GE@~*Dfc?b)ZOCROZKa{K^4(e&5w!Nf@nQ4eEQGtaMR~Zbx}2}>kBy# z{9sNI0bPc<8)6fJF=l)>gaiekyw--?+zJGRd7{q%3qnH!m6cBy?9Xq_jJvf61srQ< z0TGP^T`;&;h*GRHE^&_ETcj?n{Se2#DdWC@2{{EF&ANf0AR7ynA}*wN2qKw_BDia? zQr@ZRHo2y?t8YBNWhr0?7A;F}<7ur;0ev&jnP#H=_*i^VOD0<w=b8#?fQhi<WpX6y zaV(ClUF~1`e8m{^CLI&Q5E|}>LTe=+zI{GAbT{Gmk4!{zABmx8z{k(*#}&6GsmDpv zg=6+$jEZRHE3+$6x~CK#0TeDG9GZI3=n73`w{6^U)rGG2BJCtmd?;AB(bv9|%!XJE z0*G>=nEI9+E38XRc?}9T6e1)kSfN!pl<TfX_f~3=K4*%VfUt~x9X*~dFbtB{1&*A| zGCXqEw`e%gfIIFVgVB>Zp|q+P@BiZfhE1f)r3$Dw(1Kd2`+m`5^*$~=Tng*j0{F-I zE7wqQg=&|=qO5&H#3~cG<t>G3-I17zkiHZsXvGcb76P&8>U2}YmurmDE-7$cT%o=< znA#;7jYhXQ3OJZVskh{7@H6|OXopS3UQ}gPA#6aHmdRLO99xoUY(`@03G~vpWT8j@ z2;Bbr;Y8JP!>(;PxO@6q%-?l!<I^=cL31JV2_d@H1u@84kcGTuc`61YwEIc_@$!-q z)Ya4=*lbqX+{T-@+6TvY2u%t>E!~icm>|nL#EY72hl^NCj1xy4YD;UC_RuSg{@(g{ z*2k->(Y>YH<LJjax<5(2f`G2FM$iR@Mdn34fx+H<c{PTl#$e)w-D#ZwHf`C1p2?kX z>8wHUArg*uuF)Zi5N+v&eJ?_#z@-*!E`$deX3NmX#)P4+u0K&-b;>$c$!yuS-NmTL zs8B9YLEVE8(jyqYoD&j%r+7dvLb@>Cmb%-h>-nd^c@ZpRFv-BueAC_4!h8O!YTVJF zz<H*CW6zhdEAA{21BnvFET9EMj<T+{qAI5f=KkSaJn1XVb6r7zmaN=TMvd=`u@k#0 zq^I;Pndm!U9@ej(gh)#eUVLgT3UlnZ@$O!9nbO>%r0u?8zKCJ)d)C4%<SfWl8|{&t z9Z36bG3*uPaF-pI5Wrpf^v2)`;}K@DXdUKiDn4O62q$VL(wIOis`wMeI7sYHi!u1D zgWXm`w*&V^>zcxlR~696ZoK{i3OLpV$$e8pI-7@@RgioGGt~=}td(w02Y1`>;9n*Y z_&^gPVE4x-?`*+yuU+^H9&B9N>J*T*o^fRoS7Eo>m8Hrj+(%g}U442r?r}2y)f!`0 z(6MEQ%9O>zkM4k>Ol)UjGF3g<Y<(T`MoEm9W6rdC{}~l%bqf6Ah`%}p@W>)7!i5A7 zy18+C6Gp-)1_jy(1(cS>F=R~?EaE1|A+%qJf}}k@HxniMixmQoE<m@DW}UT~o}Q9W z5Xi|ap-{09JGW0mP@o^e%rj6?QH8M=#p0eT7UI~EqLcR_jU;<{?D$x?n;Y=yvoB)t zyC0xKlm)|?4SnQ@;pmf`gkxK`;jKSEhNFA;YS+4&icc615k?6FAh46N*;)mLmO+fF zI5y>zgggJAM2TZBv@_nrOS!6@eDL{yR(rjp1gF}F$L}BK2UE03kwJ#C^nC;wD>JjQ zzxw=26f*`xiaE*zUmq=>h_MGMlC-@S$39@&Q;d>bCCYLwZayww*lwj5cC6KQCVb4_ zn2)1h9YYDxo_ra{6*Vpj@!T85!Y@KaUUnG@@++8NRTC-<w$J59$xm+JFOIR;l9q{I z_4pcIaTSzkskI9a;y`PseuzJr?8b>sM*&^i!Ub0HUIfGk5dFiK0lL*F+G11w?1FUB z7<=J#4fwega>MPnbwO-wI3B*|TikVb50vJ+;ifwWAhLrO)-1@ukTG#eaiBEf+;}{E z-LP)McgWA%gG;7giQawsaNdU_jDIn)u?+t1j=K7KESWVM$wLOgKTu#<Nmv&WFmz$m z-P;5HM2V?nX6~{BWy&qehbT?Wt-TYd*@8`l3VkK0YmgeVY6$D&_~#A><2sI25`lCq zIaI2Gc0+mxBQPPrp)k|jJ;Z7;a7YKtoxKNtx@H3^-K@Cd*6n!u-$N9Hvyf4Z5rdv> zhXRg%SJ1Mn^A#vU)NqTMTl$!2JB>MqnmXDo<5f`RM5xMV`^%41sMr$opm0Rc)l)3@ zRh6~)_KRJx!~|gL`W(Fd-!ydY8Ub%AS$up?iLp3`Dj27_76pE>#p09KcHqjt$095w z2p11pif5jjhuq9^SK=XDtH+JjoSg!~n8`xRVxndyZc;45hlHd2a5-IovQfNGvtFnZ z<UGf@>U$e}2@LXK60a~EK4^tcbr5d3b2L&fj>BK?S%QUg_aU(tlYy~~<@D#*8KvWg z4`J(f-(d2zOA#F%jjF0D)YjIjKfB!yA0HnK9X1T%k!Gx0xJaXl3kemC-ORBfnrX?* z9i1Aba!KT_%A=5>Wnezj*pO?)rRJ9u2^z00sa4PRpm3v#AyoVllAgR)`jxIQB@Ej3 zA|`(jliaDoE|X!Ln^lJYJiQcO&fdtxl3noS&Pf<JJQmycU&8kUb*esBPTth`<kS?9 z>Bzp7O!oP7g%xX6IGJg&N-FD98<<;CPgU2`5c&tln=U_Wn{w&gUWm;3nG9C9D(Kkg zNzu&v6ocSICV7+$<1EA}K<<%h)Yuyk*D)9~-rJ6kUf;~)ZGb?hp^I^-c_{FU1Pd{P z9XgVai!UC8i>LR&k-F<~V1pfBf4NoF_Nz~KV%faITFsjd<mPFMaaV%^=Z6B0HBuI1 zffuzf%W~XQYVB$M%~_g<tR-1YOvVHdavuMFuxOv3TC~Vr#QZI#Q<rdrbUcBj3-@B^ z$WG`nC<G-r!1yUiSTuhZem!M2P2aRivh3No8D0DKgT)f5gb87y^gnlZcXdpJi(dWu zVc*7040L6rtBV$Cx6n{vien~5)4j}{c`kBLT}bzsCSya!NJD*t8Y^;*XuPFVSJlC8 zKX>|wW>J_QafOm`*_OwkGYt)WK9o~AX3w=FpmPANLSCAi2>YzKxDvNt^ew#HeBoyI zW0JfLSibNO#*FDn&26O756;w?=TaAKIx1~_<*`bHGarK~)}M<zr?E(rPF`gG>uYcW z*V`H@cxtMv6`+PxU9D3*N7|#-3P+Rbxc(IFEW+`5nJTA~8{L{LV<QneITj%T9n085 zQG#%VQ55j!et5&3{qe=eJ7A+V|Cz^zqYl+nCTZci@(80C6!=-e+j;=o+T};^_3Bbw zwrW58g1XSZ)DQNWS|$dfD*#JSs3-jWHEmjhV^HfGszG{#0?tkWZ84TbnaEM`CiqN? zCvsFFY}<=boqr7GL1t=mf*KjSwy*)6N^=Q9micT7^1^TLABz8dl7`nG-+>4IH4txn zH2`1F+K%V%--7EO>4c~LxE#0K*AInx*))a6a5M2mwY?f1WTu-IeG?0!3zR2+D=sKN zcvO_^Xc{q_p6mjjg@(eA3IuQL1<}Zyn}tlGXT?((6*MjRP0rDS9ZM+`6c!a@Cy}`3 zesUOYwVwFztf}zQEe}6|CU%=$1rvMe8MkFacYgloC$y|}Pf>kIH?vX(ZHwz)2$9c( zv^se=uQ`sXw?yK$JBF)@UUFRz+%)<t^zOe8Bgb^t`kI2CQQ8m%9Gxrfj73|DVCrCE zB18f)NNEFIsS6dNz&Uo{0M^f6h}^@6(NI^9zz8!^QpX@EB?W;YTC50HB9-j1G@}f~ zM0%>ureI+4VYciD1Tr6vDAS~<`|@1j*&6DB%;SX+@Q;|7P~;cbuy?mR?tf$)M*Dq? zD{nlGlz|<1uDaBCY3CI9S;IgV;5TnRf`2^H6M<%TT;FjKEYa_x@90oWzjh!F?YB}| z28N940DDz~8k~ku(auL(8*IKl)`hbWCeAX{LS&?9xcg8uy)Xy4t8-CPT60pXC7V<m zD>5%-6Op8Ok32CIuYNTRDFZrT*2kOi&OdkIqg9vT);q@Hy5IK315*|;kX<zI_3)v2 z+>?L}XVOwNG&E3XA!3N<|Fd@$fKeS=`y=k|ZX|>N0fM``7AVw9X=zKTy|&cde%eRd zS9x`LRa&gL1$TD{0YVbuo@`wI@67InK%o>5umf4Sdu8sqGiQ!|$6&uWNzChvK>wh- zN+2a6Y?#pNH`o_7x*D8VoQg`;N0HxhGfS~^YcdtL%@`dy1N}pnVr^_9zM6Fq&ALin znjxI&A6!dzsmQBPFqmf6hI%X4fdKRmZ|$1xrYyO<dKrsB79T5y<gQpcvt|jmX=|4~ z!``+t{#@2HX>K0282@r43U$Tm`}H7*>N{$DJxKvRU@114y*Wz(TDZlys=}a3n>@Kw z1N}grBZi!^;$kfP@h5!u%(Jjz$2)iJf*!qlAv`z;E5H68KfUvR$V@)LSTCS}U?i!r zMD~l#KrP3yM^|_F^=CMHa~GvlBqn4bc0oLEm<k2YUq$EZf;oFJI(Qj;{b%6N!8F`^ ze`id4bQg*WEAj5~Hh69PW@M(8pqe3s#%83z1uGWHEyjTye;^0zX6N9MXF6llt?jV5 zXf*!s)h4jBre)XK1e-S=!P42sk(XV9Y40q<k~#6%ye1ifA|*{@;$jOr_4(>N5V_vj z{;tl|jaRuYDIn5?foD2|Iv`-MKi%cdC?Il(AV=l1a<^*Nseq?>8EU8qxOiA&%jP5a z{i9>}X8s8H2kR|e>FIfh?B|St$Veo_XW-Wvhme(B4s+JQ5S@Y3N;R2VW>hBf^9xwT zoE6TUo%KK5;IE2?^iJ3iG02aoTGjbg3^6!?>arS4|8Wmu+Al@?j$(YUtP9qqj)awo zHJ*R81;#(vNm=?Xgb$Weu`pv5bj@QSR!C#JGz#dSAB%t`7Sp0zOIS_4w_@$`1gwia zrcNHyAqduH)|hzP?^w0?C{`^wim#uILvVnP;o`>UaA{BB+8muvM7Of^a(Hy|fHnD_ z;`P_TqtwLs)pO@z-||@8KIvif>eUO)n>9mJR8zEV+ZN*=pA2_rC(Qi#GaO!Y09mVZ zP@Yr4dv!y=aIRy~uBs@kz{X0;NG(I(p^K28TT0X?9VU*Qf$ba8upn(1)~xJ?vCWsk z!`lwyUu%Njf8K;{-6P@M+zfa3{{zS4^~iV^!ppAB6E|M<N>bp0QK2-q{>Jt3c=wAV zm^7>gS3yZDWq~<YFAym4Pn3Lh#(k4S4;#PB?zJ5O+SX8qI^9v0%nQ#2rpWx87l= zEWBJ0HK9iTeg&f?3<`zBLCdwy{Hvo-CEJY6NP)9bz%Vp~X-dMZBzIf7S*m&$MTd(} zMXQ%sI3+Gsn(TKyjVCo9*l&4vcCo=N_cnzSYgb5HJa<3FkKTq^t45&C#1RAU-U6E% z8w?rfqv}^w(~DtFG%YbaNPb0bPA*o*E<@{neGy2MsGFq;?Sw~4^>!;CE9CFUM=>pV zF++p!#yf4$vwvfFdO2b7pZoFW*T?YQcZ1-3vhbRCj?T~jYM3~gw>u9O&gO8Aq|zqi z(#W<hZHS44MjBSLE=l^z3^;__!#ToH$(M%@WaGW(HewBx3m11wjJ>ZFtSrp2Y~f*S zS(k#x-fpXswq?F+o{dZEjPdBJK>>|F>lhN0#e`+ar;)lv;$c*+fzK)N21`y`;-N!W z@#D`Jdg~Yj2M42uh<pO3QO(dO*~iMt3IRbu*td5(N;9j_v|DTCrEmzQr<Chh^6`Ad z!>uU82~GS1onTs-i~Tz@(Y<#B{+zLwVNO2i(mNRb0d9y(N+b$YEr#6E9QXBIhatBG z;nsUwpwIAN_{p)V>_7u2-qjdjH}N$tILAVpbP*B0_;gxxJU?kEEX(ZS8)1tNKR=4t zwF8imk&7HAetLSk;PdyF;+JoF;r0nJ*s(DcuRXhj2s4B<E6^c!-cfi5*rHRnFgQ3_ zDWOCdvj`{BRKcywH9ie1wk-UmIvomWO)bo0RZUt{on2kv=t4Ij1tN{v+{9zTc<bk( zfQ%uLIAkt3gg7V*wIGQU9xPO~FFZPXz#+(93Fz0`cvAPq`?q({R~eT6|MAlfynN4o z#Al7;b}RfqgF<7_b#8eGDJhOvx|)hQYB6a-s1mHQva+#a<q~==995w_A?Ni*hw}`9 zzx`Rpg(w$xzIN~%?u!$%Q&74!53Yj)m}dbjnR@`w-Mt+fQf^bQjm1QFDl967qoWP% zh|*<XRe!yPLHMmC#(XW63l$43Ly_0g`0`&5GH&@t6fmqa1w?|Xt*ljHP?E?hn|lq2 zK#zWnsYo2e4_|G?!9Qy8&e9mX{y~2@IO-K|xrR1({?YY}J6%%>X#A*>p*&fvL1WGw zGmp0J^sq9B!!XaTX$LRh;)%zO!P(IfK|w)Ep^$qMg+ptiWLQ{Opk>P##2rtBWrPVV z9VHKzVNMmmx1Vi*myZ)3{#O_65R4v6K%SPfaQpbSc=nBMxU<y?jJm4@9G$Fj|Kn{C z-FhLiG6M0#vfeOb=@u(1b1Ec;yp)q?F@CsNDR99a3ypcYGPbl`$6#u-VF-w_f?J>o z9(^JjZ3(yc`TLtNVzM_pnK(4#j~$rwWH;E^Tj0R%45YFk*T}KWG5y;;uyU@3PFIGn z-`<Zl-Mtl)E-kKx1ueenItFHcwud8=M@&p2<DIW*Or(~Y{L%dfF!!skvFh6&kaj2z zhc;}$x|y?(S5(9h5Ra1yPUkzP@%NjY0@}FaFf?JVfgN$~t!T}ZYTfyJ3z%O-?*;2# zm=W9idTg3-TTxz()k_ob$?tt(%L01OOq|C=Zwq|>JCluV>(J}=wX9H^h9leZQEo$o zqmsi|GHWJmP5sazYB=++oFH`_I8$Z1-ZV}4mk9ilylv@ih0L|gIn!0*NO~?_d1NC# z_^BuBdzfL(sss4<OB-?TBL^{dqz}A&?f+g0BMr+k%Tdg59lKyVmD3?a)%fyFL*OfL zqY`JIu_go7KGv#=x(JyI=j_4Mm)64B-5djkHO25TtSUdu8MCJE#jsJ^F{qCld;{p& z;Dj+ggDdb*uEZ(Hv-R^6<Xzb)ODls{cTZ&?x?Y~6^i!0Fn#xM-TDJ}^cJ|6EA>{hY zI3&-IeN9YFv2*hlM7C=OJ10kaG-#aQ+U4wQjbUTf<Dmy4l;KFu?e1lVw2U02W@OSc z5sf1m3C!cDCeoibTwLtYp$*Xzy&MtM(u-bgYvcI8665qbKIcVCJu>*@&y~fYI(dek zBta!{psmd8(4j+PY$RHgm!li4a{8*JyZ0ubW2a!0(%o#<$QrGh`yw#Z86Q6IJK_$c z!NbE5|NUqcTDJ~{m9-gKw)KU;o5^*w$s(Txp+Ji8)o=CpslQ!WmPPyEaa&ic#Gg~& zN6*o>U|_#~FlWM~s3)bRrP#Q74ZeNx72NsK^9X9v<g`;5zZfZSP725{l!b~`YI}Mq zM3Btcor?k@M~RZ*PUI-ru9^(C(}%5odjovc6n23TeB#CKRFvxQ)(cAz#PE<OpYMrs z#-MN50!(?RH--&ugtWtl@YSbDINI16Prvsfyj%j2x2Z@8YNkwdxAh|rxQ_1say$~v zbKwwZkC4#;*!II=^n7DIVwzfG*^0gRa!5WNe9#AvywnO)zw&^qn|_>}E1X79ibO%H z5qMgTBQu6B7~5r_0M`aCFep4qZw1G-C~OkWxNKe=-nw-sUjMTlK7M&SLq-ZwR#S+u zP#?VX`cOPDIg6-gV)>PJolI<fXK(IT!=oyQ%SXY!0=TqxhCSz~G)>O9ZV1G6M?$+r zzL7Dk-p}Z~@C=cU&SbyBXJmWJHvV|^8G0Z7yA991-CJ!FI^6eIYrOShEXp6QWgb`u zJlJgs`VWnS6QQ*S4Ubk=(dXP=hjcrGM#is~mjV~9SjfPWKQ&-w(5i9WNI@n(`bs;x z-b|U`nTNX`ZmD`KNj<aPNX7JnF?jR&r98HdiJ$pM$;yIzcomvN`eNIrL_G8I9!$Ee z1m>m|nD=WuUi_dN?CtgLW`+wgey|o(9$o-T`rpRf6NRW&-pUQ9^+n$6w>nmIY1GIB zsdND@_~=uNxPO95Y7z}z;tyqiM+XNCy=5d;F!^cTSKr{_ci&cFQT5Mb{A{GaIVoTm zaxxdBSej1{Z+b9nRoreWk)s?VOW@wxO%+g6=QxwYpYy~G;0MFKrKc8Q`LtXtE*M2G zV<qnFJ`+LFwyH*jM{q}U9yAaw`ebr}ibdn*(VSOA(dueJ^pSL=ug-u^Z*SOm6R(6c zI&+RTfV;e6T@d9$7GGP!IJyn;K<FD*7}nGq{RTJ3voCuxKg~*kU0g^9s%g9t8k*ba z*|!J|#&(euP;-&W8WZe`SUKICc`Hr~Z>+4S!>r%-;G<PN(Y{L%ic5<LQnDFef7lU^ z4cd&iek;eQaWU$mH`|!LNJ=-1!wkGvkiT-a<-#Vw2Cgk#^?Kp;z?a!aWnfTp4s-n& z%I)gug~JC{DBgNDA`7yUz<#GDC&Ss#7j}*;ddem|M9FQ`OGe)wgWFoo#i%=@(Kymg z{VvtD5Byn<v=fDBMmg*IErS&-x%?<&UWj%sBZxYI@#Utaz^OpIizI;zP7%^2VMhj; zjCWBgq;>BXh8R{Pl~!I^j`8m_BSMT7rvCQ??w;5l&%fIf?b-yR!*D0~1UO**`h$4y z<rsYS>j+Go(i+RY$y4W(<A3;kJG}}CI6^e4^$XGw$mF{3zutu2)-&+on`@AfUSOCg zGT`-1F?{x|n(o3)OP8QUmyU>xY@#MbCAUeElO#CFXLECNbnemxCj7pWu0?73$)i7) z@zqFy^HD$v%!c77xflL}{g?!5hr<0tjv_ixF~JR}=c+|a!ep+8Cif(Qu|KO1f3>R* zQWA^s)P(sMIw24remk5YJgqUYODy*6Ou_P{aj=rYVdMd|G^$(Bo9W%dOBHgH5R-Bu zNY$s&^>F`}qmL?Aj3<g}AAs!##$jaN2qdmg!+)o3#gy?&v0?QQwZNbET81H0Q%;vH zIk%ZXu{iH>#vRT{0gcD&l60udtyBrTCaku+CN>U5C(M*mL1bmDnV(KDf&r}K(iVRn z8h{_4PK1u}`Nnn~Q9$O8@FpTt=4{U)Y&JuF+PTAw(GhAKG-Umpeb@RZJXHE##v+_q z>vLclZ^e9vJR~uxTI3knPmHG8y9KwEvsm8FO<NGsqB+B$oK6~5HLpdpmY%rp&2T*R z-~yy16(BpS2yZ^U5_h~Eh#*FlNDFW3!Sm~7JL!Y)d1EtD;A|AQWX0laCp~kU4DiTi z-gxk-_DoQ;#rL1J#@mxO;-l9#;oeE>(Y;3`a#)<^mzT0}&!pDs@LEnBKNlja5OHet zvUrT2v>8pBdk{?}4ilbjrV@>&{$~_E-qe*zj8!<apV{JUN|9EdPQ6rcQXSc{15Ki$ zR1%XY6rv32Cx>1!k_0Db7iahd1mHMxFO1Ddfs3Mm#%#F|SUOwMd*Q=Mrk)I6sb%4` z6l875Qkdx~+14N^UetB2;z8QtBk{CmgqB@0vw+pPLm0CQwCNat=l|Cdx5Ug=1u>_7 z+Ye=oRe$f5RXBPeONBs5;iamgYNW5tKt(=<F^xu7@gA<}rFg%>1Ld}@85Z~Zo_O5$ z+7_&Q`xv?m^uW|NH(|rd1l7f}@vk%cUPF%sku6Snz-~5ro=K34{m%KPJW8CKy5l-k zo5q<1@uX#E7GwGbD$9%aVLtOLY^-cp0544ab|Nt!T_-xi&W>5Dsu>@Efe;P%O?a)$ zLmEjk$%BiJmoNm%g*i%kXjx_OXsEq6^j-QWo;6*v4jCIVkUaYYD~0Ewai`{J*}FHE z&7F(miHS7wnkpnHN!S*IzQV#nES|rB1w38Rrdv04m-X*g`#kaSPUzOh4_$-iV~pn# zgtu_OeGhhJLc2k!X{huu_N&2B;G#pJF62&R;L5@(i>7!s?s=#^I`zz;kYYj?b18E! zsu6!Q2_aFoaB{X&r<VOaW2~@iOA4m+UypseZh?cHEjDjC4rd#C?CzX~=(awH>EN&a zlqHx)YC@1US>$9AK2=*;#-wly{Q`d$Ra^EKg+oFUipon>AG8<g_gN0nem1@vDezYc z7#1-RT%04F7zSjGoL#x9vTGTYXxCOQ*H!L?%n$Kgh}CM&rha&+RV=>V+ZU0|z2NF$ zg9ly>$K4Y;qr9vdPv1WuUVion4AY~8IE6c@O0QWPa#XUUPfr#Y<lMiRn#M!+>`KGY zz2!KtYBWCRxdl;vu6X*<&R8-pfpCr?B#A+aI16z!o+hY8rj|5_++;L73rQ|%lL{%J zv{{l>g5rb{xV3XVS#@~u$QIbs$`jvA+k;Pj*^dWr55=*=`3P?8jE^5UgjZ(7D1<MW z6SC$QTLY(n;SuHS%Tsw3PR*R))}jHT7|HaPR)0wGb45WVJ>EJhz{RM@s6ZnccpRHL z61~hGHtsg)*K!P9n7|+3euwCe9pO&k91~i8bh;uO+`R{Oo}L)<_+zkVO(A9EIEz4I zOMsmxp6i2K@5)o232z@q<rzKK{`!s@UyT&_D+R8KVj-86MN!7GT3{(tghzU*-aPY0 z55Cu8`=%3k>6`Wn5lViL{fi39Fuu!TOr6<<Xaqi3xi}t2megR<G+#`6eI0C_ObDSC zK=*Vz96EFYYZs;9p{F~ZOzhScVmV)3ov8|Y(trMIoVPZ4Dyk}BQp2Qf!mz<$kkaO$ z+DB%5{Yft;G&Umz&O!lEDvI*UQC3z(00Vn?cV(UdD>I85K5cnAoLiKu_+CkFR_)v} z<yp>t^*4zCC4!VF^B=71hG*}MMXT<vnEPQGzFglM#jG+tVdyOM80wFQpX#hSC`!E; z6I`0Qpfax#g>gkF@Mgj!tGxb=41M))?kZi;f}sRu1$BsM?}w4U<YLiRyHQJ+*?<;Y z#HF2!w;0BTgmKhSu`stcCT{BqcJZ&WK1fK3?kK$#RMH#?Odw6xC`Wo5TQ~>cqaOnB z+`HZI;}<*d_}%lBr#o`61EQJ-s(rP!L47g4G)M|)YkTpDV&v|~gN?5ZK{ec!*YA8h zNRE|Bc}1TyFL5$&qTc+kC2CVyW*Lf(=}>Z_1U02KFn2PCTSqs-oZHi5X|9*4c(jGJ zH3r>v2b#8RgKe=Zu<vlZ3U{(~ut#Usw`kF^1FY@r=&@m5DWf&c*5qEzEE*voSW~$4 z#^CyL)Y*<RZZlHgObRsOV3L`0RhwK<yX?8FJx2~^BRq;F>i^pb;~(yz`uEASSlC!N zV8WyAP*GmRYLKbeMi0iMr@A5GNDi&1^Qes6MM&EM>|mwH;p3Xal5n5qoGkOc{SsB# zg-n_pq!Lc$gxZ9WctoibTvk?w*|TTDy|xLFsd~dYz)B_ITDe*2VM7g*UiwZ}b8aEv zs#4Q<v4%>4>Z&?CJ87QsguOX!D6K_|e`RMY^2(`P<Wit1hLx8kJQ(MzP?PwZ8e3O% z6}xn5X>}9QPZVLpsw70W@rEm{W)F^;i_zmFF!t`&$jmGx>Y6!R+-&rc8t<u!ibrZ} z8Y*dh^X}zMBo;1oGM5_o{a!l7PLhDQ@pYK??oy=2*Pzd^AdI+e4Q?eI?@Pb-L0DH` zdQ8n|W;u%$Tm}wVjzFTOEKSve1km$iY!^uZ13jgJ`SIV5D`X(g_8xj+lcyAP!^NyO zax5+n14o4;tg$x=a?4arm)0G8(6L*nQnJsreqCg*jE7xr3K*VqC2MD;EJ;<uxo>|T z1tg)EaXmr)%uAofn+)eUbDxX;&hv>fA+0|DlQ75Xl4_J@l%j|VZ)r*yjR<D2_O((T zZaY6a1#f=xy82V7bJai3$=~aJIPHGwZLj}Plg$)a$d_*d)?EMgjh`<Q1+Ix=@ppYM zDlEt0xHK%Ee}qX^CV1i9zO)iSG5D~^pNZ7b75xXbAUc6QiAXzFSxGJ4dT9Y1UCr_7 z%jx*vvW^P&cEjpp=-k&IImro#TQw8?hYUt|c(_W~7KK8ZC>7GyuUn6U`}bk&15;p9 zXbIi1B8G6(!kS?$wt+UP(y|2$%a~c$yY}T~II1Vcm+P4VT6n~=1#yJun}f-BM&h+; z{TVW(FFGgpR!7ToK8tbX?aWhgv#xDj;KcZDm5_TKjH&vIl*F7rGG+nppVE@GZGvbF z*o19gmBFTQ9iEuhTGh7E_7@ABEbwNwX7C%}t3pq-->&Cp{!jWYi?%G(lJvW7bsXWf z9axyk1?Bs7SU2k^lH5y(BX7a_2+h&BiRbA`mtjmO0f~x*wHFiPyXw8Z|K!@nea=n+ z$@?jy7tMc|pQ>X}Lo2Z<>sPE;d>GFR+K6X<4#Ur{olt0pQ@&`0maRevfv!*XJ)a<V z_A45<UNs6B=8P!gX{*vHq?f9C6;4g`c{G354~-|4QOGB#q=LWyyZ!&sZ_*#F)l+Dy z>4~T;s-QAa#+n+XC`l?+ARpGOe`6P93tN9X=0}_=#HgLZ_->>?L#4n47s$KPH&Q|< z%Xc2$ipJ5-nEYH93jKPwop>GEwY0~NuN=ebt@{`|SWNSs8H?K4Fw~+E5|8ELf1mHd zwwXm(cyKscwGCuGMjR$Swgw?g%HF(M2U{0&S_-$(Dpdyux(ID;_4PHfSt)qa#*Nsq zc0I;DKLr8J!>B~C3T~)9tUN50g|%>h5sH&alqI32l5meCgb0Bu3Aa8`49u+lCgbOu zhXR^Xahy=gZ#=mQZ@k+c$q7a1GdO~alPPkuOR#rG8vH|?VNFFt-11e$tVF+?82LH0 zPEoM5U=>(tnjqQ`sV72}w)k#dmCU$aH+1XO1ow}ejShsC{ny7G(Y~Jt{`1yGbm<iY z8(Zc#F^oz=cp6cUmn!8lRcvX2`^y|^h&Oy0`Xow)JX1@e4vFGmLPG4@mxQG&4&c+z z6EKI#!XpOyplhFS#yE?y;|z--kFR1rTG5drRdmqKU(dfTL)UJ~Q;6p*bA2X}plsn1 z<)X%+7@GvxW81bP7&S2xLr1p4;5!>5tgSU(>Anjtk!FaDW*7~dQL$*i!^W@<3xZn4 zhD_$r6vMTRD_o-qEi3JB)@hBe73P=W#YxN2yp0=+z+C`Twwzm9J>}u#ciF~2@k*4? zD^YN`0Qr0JR8dt4$uqMtRk;)H9eB;AQeVT4g-FQ@c+QjIQtD*J7b684E(P?a`UccL z7lu@Pv4~ZDSrJmd5bI~Dy@M4-jBSCw6abw-V!zOmlX5ZZyLkNi$x^)e-(C=m{r8^O z0{?JFM2xjYaF`qN2yHp@>!Vn=Y7Dyc2*=K?NqF`48a(<|F#h;$E8W;B@Nq4tg|He{ zcGa+Pa>0WiybG_u5G4Rf%&isUY~{}})R7B%6^ascYEjSK$|PJWGnTBhY%kSseQZ== zG(|=OGZE$_&A9j|GTam|^2{YPqKd`jK74mIZl4&1uDye>a)H1A>b+d=zq}T{p;qY7 zEl9<{N|>E*A72y%6e5?n>Z!CwJGXFBf{rN#P}O888OBvZBBVC9dkzXyxlwa|--YHe zp1A9NedwZ_hb{aXTOlbSpP^E=N^lbziD#xfr-BP=zCzA&Zb}Pc1HH$~O>g08(iY7* zgvqz8LZ>c1xN}Mb?w{NRv7aACn^1;BQQ@ed@j~)3w5Eg#4uU#b)a$AlK6XZdP;Ecs z`}rxL36A*`>KfItuqETw8<C&aG9So{MN^L-&BeGow&K(GTA_2#U<#F1M1+dK!qQNs zOv@Ftv48%n8TYvA6sX5bq*!Uu!6G<CJHb6hU!OsH{^#nKEZs6sb8~h0_?JvP@<I(N zh{j~8MJmYo4NZkJsr`9?#-p`&Z66v`#Hdo9Q?9%c66PfNW_4UABxlOf%>phhUFeyx zr5s^F6eb!|wR7;?nnIz%ueA#s-;ES#z!Ye3iiOb1!jQ~bp8;r--PX<$kx{ICI?V^A zCA2uD6=VMF1NdZaKX`gNV|eGEAmLPL={ZPRX^QCC0gC+2uGZLftQ>9ojl|rA)!4Hm z2iA>U(Yn12y%)A{3krm_qdv4iEU036)?&#;$q?vJ_ZUmKv6xOtdMSz%8UM)2%2^w- z)uJRWOS>StESa2ZMuAWkD%IF=;X}rSNKcNN;dSZ4eK@eJ1poOq2Fb~pOo%g~#oiR_ zR#4IXJ_B<SZ`BVCM&ilnkn!abMGpqu-Pzl6kh3KhrKx4`?BoGaJY<p4#$rRHpBMq8 z$$X4xX0QnI`-(;JICemX$XC2jLd9IH@D@Zf5=0Y8o)qZW9Nw>$o2ByJ)SvI_gg5X@ zUS>I_{m}s<$Fx>=SzK6!sBUif<^3J_=$55;<BbUX>*ekzy+Rr(B%xEn=w!?o+eJ{o zAOuzwRVgdBr6-L;^qQ$=(E5D+oUvqXf5a|7fDvIc@a%W3(TX)lv_h>HaaH3XSCIn3 zQ$+|bI8uPz&AG6Pu!Cm@54~qpQv%M>ACWVJ*9x4oBcmGb9&C?@sNb<~=dCOf=k*sp ztb~0{2_Qe0`B_s{qZEb;Di7r>#3%K8%CgE?)tv?HDBGAZl+KF0UZ8Fzq)~YMnO<Ar zg~Iq{NUCd`qrb+@MhY}U3N%Q?LQ^8@3*(xl&oJLO*`UHJf*ZTwwf6^{r0s;KqOfCK zCNc{$Pz;@_TOmI#UmS<WUUNqV(G?Pt^6~S$F$fBC#@=1&`1ebj;oy{yw0-4x_4}@f zj`2~JXJIbV>I*!gFxdG~iQu0~vdUykB;z7W85dc=a4Ac73)ltnce*ew7}lafKh#;| zMEuj3khabEenU_|8zV`_b6LfG4eP3m;bOs%pMr7(gxesMVRTRQS%uHn_l1YIElPDY zu(8)$q_txuG|I1^FR{tFAkur0^k*_$iM8(1%tfEDr%jB8Mlr*<?%6jI&)vBacJ8&v zPA|tdj~>O_bGxcq06)K+jHtoZ82?Bcg*qbcea{Z=D&InsDZjqHhI{1A-2`)F<Q-AW z8_UPOJ;~U(<~aWS_)%De0C$e`!kfW9*t0u{W2A~>L<(M=df<X$B+%ORdE=>Hji1g( z0Zr&EWT8h<E@XZ+qLL<aP`r&o>w^dA%?`4}tz$Z(bXX}9ws+&5z6UTpZvf%YtxsDI z&UZcI?^l-s8ZRxP;V*qz2CM^_i`31F0zG-{`PN_lE^>zC*hsTtsB@1&0mv&VAxfA& z$3ZCooPX7X)mDktOt!A2cd@FZ3RN@?R2EdBg5KEjOhUr)T!P|c?r5&O5f-e=W$kJO zD|*1p4dJfmqK<KXpNlfa?bibZ8eH=Bg%V8|i7W;pi^!y}qE#T}L>^vwa19DGtI+E% zS3<Eh!Xr-{LuUGISUCF-g}frXG_{ZNb`0|U6(gqv<0U4EuU-+4|2(rBGk1(ZW~vTb zHzpw>(vvZ@L<?cln3lW?ld5lJkfcg+b?O+)sUtj`Fa`@(y^>+$Wuq*zCI-u{I+;2l zUv2^+>g4_2z#r5Y<g?7l(W%O%En51k0&=^yALp1mOe?b~?3~T;<O@A9f95_cWGGBK zq5zE=8>1GjGsjdZy<$@LqJ;SRc42m`d*P}t=F~vP=L_v$002M$Nkl<ZjW##ZQwtHh z=nw^BGek!RW5zE#kaVO3Q{L``L;EtZW^oGM`m{e4ep7lls);F|2i@L$I1*RhtAi&k zg}gz|ts8Y-Xyc@UvEQ#vUWxzv_87kWusOY~B{W2s;IRk#;KYh#Od7Eg&n)VKZUe(q zh>q|Y;VC6)B}iME4yR_0ddSQhcurmJThqp<7=%)mr7D3iXk^f7C_CSMwgo>-PC)no zJN&*R1HaF0i%y-IA}c!|frR0-v@*i{Uhd~|xz49OU#X%lbHtoEm13w71E2PrTuc91 zKAl>8wcjfU*0OT>K^!@ripa14^zI#r=|647nuRCufA92RNOT1%sX)|}=+T>GEY%d( zsB_ACZ|Q2uKfV*`O_T={hBKOy*GUw^Y3sidA&lsf@)V5i#-l)kOWwXv63b$%{Rwk0 zWthd`L+Lmamx0e`jv%yb9SXB*@#S~j>Dsizf2OX%xO-c{FVG2FHzZ=sFBu46A-+vp z4kJIc4#$t>p>uz4JbV8#3WMhO<HtRyuB$|I*3ppbNUMK6sSqwX9!`uWmwXJn06REF zIx09wffbd)OOo_0#cir7Oh^cWDPuL&xeR<w`mLQ;eK5XUcN8!T4cORN66wd0N~c~r z76o<eJ&WOEZ;Kb-?L&du1m%QE?mHwLMR`^D^N+n~+aXBhD9D(QIB!|+ELg0_o>iJn ztXK=<P?1trWxIGaG=)|ZQX2xXRNomw;NWOQkDni!HuFK;!3@m)f`ap(!{OoSfX3mT z`1yye=+r$3W~SzN<AufWWd4$`mm>-e6e>krLJn0k%#DU{Z<n(V+BlKhYt`8kFTWH+ zdW5j>nk#<!Y#;iJY62%OD;zzXjgkr-+IFTJgTNKakimmh<=w<$m{D=FC$BNK3!{LH zQ>k2>$9e|t^qARrOH!`zbTGFfk3!^j{Iqd2hTj>1ad!n^VCPi?AFxAPqMb=S78RCq zgO1G$qhtfyn-5VvTPhW)OVVNKVS#}D{wiWXQySzs%V#}AuoT2)jsy8^Bf6G_r`A;i zv!-vy(*GPsn=l_t9=`<+rUmHe>5t#viDRYRQutZeqAb6H$^vsRJ+0sn=D?(I7kG4b zhYJhtIW~5Hy+1);80sWE+?-wy@s>!W!^w4C7`gnHL1!FG4QwD8yK^}x&>%yhF80RM zMCHM{{2JVLe=|6+Za}Yr;r!E|F)O_Uy$3c%t9CwWLp5U|eVW_g%?}6Bi=o30pKZtR zdqWukWs2Woa}fV=Z`^I!0{{1FEXLg(g9Ce0v2tD#T6ggvgl4cpcd4Ws^otMI;{Hip zSZUaq3`B%Nb67AT)tRt%<+<e$x2zPn$={uiT-GnJ_O)ixx4jCd5+y?=YZ-33A;;1i zx@dem4+TyxAY3%eR06IgUi^1gWMvd%Y`297jc~*8d&1DZn?Jt)?@k3jYGTGkuDTIy z4TaCt0!Ss)t(6;vA6sOzs9x5_Z07Zp!i`CYO2OdyWj}4)pQl?_{AU$PLgk{CeG(xo z{%{6vc_0GTHhL?sPFI29w?=X?2cEiro<duR=}du*ammH0CHlF+1=-w=`|I_xE8gd8 zbpZ*taC5gmd3jfNJBGiRVds_vbn6`srxw;oUYm)M<3+HEbT}!12(K_<;&v^K6vlP| z6i~_o>Bi!Mg@=o1_%T;W+v*G=aZ<>uEU$*0gssT<V+#xSz>;-+uz717dJo{Z<=7Sk zLSt)S6i~c^1{kihX`)nkTEM@bpMtd3c(KMOg;$m_-)r;cMEv>75yb5&z~CW17}hC_ zkn}}(>%sl_^3xcU(ksy>+7>5{l+glUf>&mD!i$e=L!XCRp$Ut78ZP;_gizO1d+5hZ zL*dhZ+iByEMhaX83S9Rdi?h)nG{Ti`Yq!&cE}7(Zv>>!<>&L}GJO*IAtSz>0PlB$D z=pbg*IB~QDqwj1%i-svWwXw&*-Yd|%j|;}$)rPL{LNtk*i-#W#!o1((Xi>~!k)&Xx zo+yByk2|7T>BFUjd1y>btPz6OZ|iHzN|_F{a9G0B#zY}P6znZPAw#Ar3t8fu;U99G zpa5yi$FSC&?e>h@8aM^y8EVhb(a8q4jc$UbEj;nwtE<qtR{$nG-=0Eu8rpYWg8S|Z zM883ic;opcnEOjSi$c=s#bZQ(5kW>Qq>`s&A4nmTW&nXCEQ)8PU@HO&I9W2>O5($n zT+|?_qTRQ=+uK=V`loR)btpzW3toz#77|K2yu2C%Mu*_Gd)q2FX%mJDm{7saXGoNw z9jRi0H-Y?il27o-<*>fT^0oW%`5Q-Z<WMT!{2~!gO^Ae3jVUaNXK!v$&gxinQFokB z+oZ1}QSglI0w};SRhC7-jkVcuZSMv<)(22?ht_Hx`LKnhIsW|dFt%?!PB>tK3zXGj z#?N~Z6yl1g7Cwrv>#f}va82W`SDpfVu7#{Qm9ad71xM>C6~aFaA%wz@$_Ou<5TA*u zudc<~r{mGRvm4qnQUAAT@i=rMgP<_pNGTyQUmm?47L|B$S}(5IrpRo>km}))h-~iy z-!MnQhU*nip7K-^O+#9}ZHgDlPO`1ucD0}J#Yll0m;w^fe8V+au<I|{a)UC-Wf4)r z8Yj8@?#db%uYb}9d0ACh@LM8!4Dm$?k!?PCe>HBoy(PBn8G?z!R-?SK5_UwR;?eN; zbH`gB4q*;PIr5mG_0zYTQCe0mmp~CwZn78ytr!Zw%k|_%ybvZtgOG*Hv%M$$`uoCf zxF6l9cB&Xo%7Rpt#G0`&6FOR5q@d4fOiNC#&Ltm=?RuerJb$VD-n6A2_r7bdK<uN} zD<iE)DGx(C+2OtiyJ79Bc)ayN3I>b{!kZ6mMC_6S>K2s{V;HM~CgMRPCI7*GDr6{q zMFy)_r=pCgBPYj-jFEaxn+Nqasc~Qb`<3{tJQuuq@ytB`aR=sD6vLW}@rlGjqOAOZ zd0!`@gvsl(f8T+Hv-YE+qMDG-4sdSnL;wWV^PrHe+8OtACHi`;&Z+SyLA9GbeJ38g zV+O-38eztk{uuLc2;vS6N3T1hP(qJq;o-uQ-4w-3{sf=j*e-wq2Ei_uH3Q7)$<}Is zsL(^6X7~0~{Pxv8y#LK070wqJG7qDDXRDmO{)3vS3mKJ)3!wLv-nAZY$Ul^ijFn8( zW_4=6zIsoETwat4Nw6+XE=Dd<Ade6X<F@Ges3FkG8^89%;M<yG*fXv0>+!KTe1iLV z)nV+TP4NEo{dnwOJ>VVSh>u_01#cofN^94}zh3gb+H~qg#+PA?o!V&pWTe2&NrCG= zdHYfxn<yD-u^^MGrX|pf3wsrz94Fs72cb<J(JDFwUwyV7Prcj??(PmKDk{a84vX;2 zhf%oq!H!5cl7k03FT$LJ(ahIK!q4CBR24c04vEC*yITIGYYN7W%;%)y!5GhKy59v= zsxZC?WvmQpD#>0%3ldCgiJ=xaIxVJNosfN1$R;PH$-i92xS;`2K$Hs+B4wOOKF;<n ziC8f+9WQ*i4ja~tMpHs32fF=&RTL;&xAVt}C5LhUz!iAvt=8z+%NH>n{q=Vv3#nn@ z62&wBU_SD8<*R%h*Ve8owpW87mFr2<#+mwZ#)~G{p0W9|eyx9vY+|=RGI{}Sn;ec~ z2MciMU=|bCTOu!~6y5p-At2NV6?ql<s@%*U@bB+;L!(5AKr7y+^(zx_d++u5VNrXA zR|fK#(Gx{wR6+QXKc%u_uoU<5qe5xRdN~rxld?PoRk~_~j${6XvHfETlu}_$rf})V z#CPw`gbw9AkvUSv%Ew=QxCux172uQUL)7{5Si_*Ix*AT74l3kTrj(K8{$o;K(H*tt zCN&^(_OPZD^JVNp?NlXt$!CytR^VOBxvmy71X;)M(GsFIg>~@4n!QQ*_TLBa)A~_x z_OwNvcr@7CwXsLBYULq3_GCYN&d}!M)NB;y)SySNFx>xm2O>pDPKLgCqH!Et(Z_2% z`3*pU8!{B?pFS5^R5T`KVhBBvO5Z*Ehaxe)5Ic7!vvPL_gf?-(gue4IaY}psMWb); zr5HTW7kRoIlv7v?pnG&wpJn*=kHNTeLK}Sg#&%Sr5|Pb4v3pwzUF?#|!jKk*e5h-Z z@d-N<g+UM_tO!Bp#IOd3FcxZJh>Hjf<!NQeXF{tmMuD?4wKk(5!eivmz}ZwjhOkA0 z!1+(_+qmD=qkudr?Rm-b_Vss0r{4Yu_N#-huM4I=zX6k;4n_YVO;Jt=!;juthwei? z(X_b_t5$D-Uyvi=S)EYLMNN%Wc3sAdt&c60PAimUlu^ORBU(wN0t`^q;CWHGW<_Z! zQW6rdf9qBx95_fXfezNJHg9P~+;sk~jr}V{4Y`JXF>Zuo@mXlb3bq4BL}B4?$1r4E zV+<G(!8{8udT%V%5SG3Ra!Mh`y&08jYj;9>@}EY(E7i|ya$fa}pseHndab3(vw3Lv z0?eGZ4Zr?}&x+43rgMO*!d}c|ZBZsAoKoh0(Q$e;iALkdLW7cTVQlB2fI%tGrq#Nt zs2c7Zhmr@U_$TLNiLR1f!^3#*%VY4euSQs;E1X^I=%uz+p?X((o}8!m#-Hm^KzkN4 z{?pfHC|==2Pp2DE^J)dCC9Xi#0?MVbadbx(Ol?h&OQhHZhZ7Lh!w(THeUXrpiTESA z3}t4KQl%_3f)i`$yf<+thTR*1LAOLAG|UqtZf%Ah{lg5&-i%llCB!)0>OBqPM<WH! zPJx?Ru@I7ILW@|Wl=p($L=kA!-d81hd;2+1AuvN$egVFkx*aAq<*+EPAW}#$e*0!G z?tZix{`~0(lf?tkXHXNBtXq&@hTM{DgcGiAWaHnle_sya>XNvaRl`5XMd4)2S|?2y zRYsalLEpOh*ek1~Bz;R&Z^6H=F0MvVoQ@$P1t=%Vl+q)3j0l$^WGIR3<jM5sG`==m z3dq7J3S!HcKs63@Wret9Y#U`2o&LWaIL@$_w>}zz$QEumawJW`)JC`UR%27Yu$@u} z1V&M6rqmIEs^oYna(Cx(AvJ@gqa_j!AH*LYe~RsM>H4RTT9TcO{cG1@?VodDPUXUf z1tCqCYosmISBhfNXSs%#j|1z*xDsNv0zZGhor`u0R^yfs11b*OM_aL_@@%QphGDpZ zgsYfQp}x|6yi)i6cjuLPFZU&h?Se-1+~mdRKFkX)R&IFW)lQf<^8g%)uoc?e9aRh! z(;d>m%z=fqSWHVNH5dv-v2aj)fF#uOfxo-p&H9^xHUb5nL*NV>A1k;;>x($bxT_>> z3-Ze`XlOIsJ}wx0_8v#y0UP0NUrgoRQ{`O<ag73H!x%uaUJKe{+R8M-yH~)Og+;AA zEKzW%ki}}Vn1fc#Ibi}@8tE3X9!Yp#Z?x?dg1!S6<Ks`_FnMwm+I0xTfT7DVlA+Ju ze)>G6O=}X7l9!8-V_PV1p*y`9b`A!Fx`uJ<#$Mb|6p(QJ({D(VMNi*)eSW*?6$|aL zow;y`HA^16{0YKMyUu}J<ZV$|U5>jSY7Z+SJ>E8c2d4gCC#+h21g+Z!5Hiw5o$j-# zn;6y-z*y)iy!K@xewx)Cjic?+qwf;jdv^$}VH5^=V#*duvH5K3MhqJpOLQEf{?PJl zYhwdfR)e)A6ru=Mwn0qhr8T#VMaS|P{!)@sf(j-ZiiJkdWhCiaolLtKc{ePm^*0$m zUwaDFXC6{TzC}w`d}meeJv&k{X6PFHGItQ%xTtO0n1naR@4>%+*4JBEFn5=#zvAKP zq{8j=Oo`8*3Z{L4y~?|g3a`4uIvhK;AM<|s4;lxDqwmnch-%&pjUyshLAEK}eY_F- z^RK9;P!$x>M1|vAX~A9YS-VC~Rw?%EIzb5D)>M}CgS7tK6Jbvj>H>zPG@_$VfDh`^ z?AkxU%hv;WhH`I<X2wzRvc=Oc^}(#~4r0{(;e?enLuOVU5$yaGCcdD7iE!)`?u4=& zR`X4-fL)NS9zjdGB+SIv{z3uG<CVKV52Ytc2{G$I(1TNT4J2v%cAv#au1i2<Qy+{N z)e84d2*$A^*$4@9C*X$^M!P}1W`Cj9wb*b<z*0lFJ~J?MM?X*0V?g22LS(GXQXzph z3=^)QVwg{4r;6+ff<AEFB=oCGOM09b0nmn_g@^W}Lu$tK=<JN5JtY`&X9zvO&In>Y z#c$v5Lib(~*u3!w9_zmmA5HHAFJF7*$<ij&S-NvAh-AFx4MG8J9hC7UyubdR+~TR< zuig!7vV{JMm&b^5L&42n&PO2vj<#0I!!~1mjF@)8h(WNrOlc|O!IQ=?l))N}={gsv z+AyL~ZQGKB{X4T+fUhrp{c$7a&u*&_MT+Te4}}|#6=6!><QfIEzQhqBXw|a*3RnBH zrw^dUaR&yD4#Hg%I>DAjb|gf^*2|U^n4Oud%i0)B@YWqGMlq9OP3=r!Bk;8pE^T~m zPC?fiI<E$*+=+HG+Qp6U*PH_KY)llu2p>*eBg|RUhsvodaw&X2*=HrbUfCUKL>=qY zViw+czcq4Gim-9*ay<8HKQ%7uc#QUZ1vScswN%n~q+rGeKcHRz?&#E^qw)sS*6PEk z?CtH*yje3^!X5F;C!fJDEDTLrx4J5H(3AsrFFQQ{MqeuH!bH!`#H<Ob;27noa!3Rr zN|cKmv1vj}DEY_C=l5gclhx=w&I2z@jD@ba43nShq;A`sUK{5Y&PZR923?8{maGC? zS64_xxk^a!O(i@VC-`56h?*NDas|<Vq|l_~t7%Ew+IVzwwZ?B*{jqZ4QQQ)}2>tH2 z!(I1wRG<pxT1ge{LH*?l#tj!s0eLpk^n%r?v((?NEtNw4IvUZkcjhS1y{Qeo^;|nk z)62NMmWDT5I7JYx$-|0;==35@4Yyknfw4$ejvt;{ho56dtFXb>A76!U=8R^@ay!J% zNXF2P-?Qe97v{zfK}chFJwN5Sjm=1b%S{1|AIW+1v$C1fwHL)jI(kJd5f~m0-;fYB zuIul2DSeRh$TZU;akO*EHjSm7y4K|#^G1(_M(w|O7v?7qqppqu8uydW77UM)Vqo%s zBAa<4FJFg0=j}i;Vej7lcmQlzMR(w^t$6?a_DcDPJCuTh+f#7+#8%4UEQ!4Gz}K%l zhUK#p@xt4E5E|}=v6Gszz5yX|8<``bnI26@iv^bCT(P!E;WT@?dM#Mpvzl@Fl8+(S z>_xnG4P&uY0X~M?NOGuJK!q_I@;Z$2$KO2YtNfE*xX}yOUJQd1j~9m+e*9_+T*E9B z9P6`_7hrE(HJ*4n3S-AdWB&h+!`8WxQb2?$D5fFLS3Yxm96G#}Nr!1nI_$+UYJqAd zpz529;aV!MHE?opK((1EAz%-pSqCO2ij;qqn)KJ!?v%y&%*$$wPQtu&%q@_L_EPgj z1c@81>7PH~7vO~4f&%>S(_IJ(^hQOU4g-fqF}^!g-Lmi;3mO<C->Q-r{8m&fN|~5y zW@n~q;RsXG&UK^F=u+=T=u&vB5QX~+;n~#_R^*wgaX3j%6LoGlI9Q=sOK;rqXau%z zNWnc1ZNk0x=snAN6u}#B>|H8dFXeb`j?1&DC4Z_cFGo#Pm0DO$&CJwTR)y<i&hx+I z7iAJLQDC+q28l|&2T443jtm*~Y|We*u8Ueds6T(*kJs<n2%iWG^zPpjf6m+u3u|D= z@Rs=ggWa%kuY`}UlPXZ@7i@<qZ})(khd>V-JyVzRT#d(`n*!R{lCd89#|$j^@@o{N z(omF@g{;IxY+bq>32|`<2yYB)yGtL7BBRRu($)p}&Y7c^6Nm*Xd{3JEUPIubveHTv z6qG91ZCiWhJYFKpGb$E(`kwby2(3M+Qx8keh$JbyO~(NA8_*nsMnn?E))oi$r6Rzk zhTo$o6ak+zKcn{;FNR13@}Ua4g#Mu~TDTjZ{E!0cIzn=?4wi+f6?_SECzk8vgaU<` zZAO7n2ByC01yMo2ka%lHRxY-o3)q4Xic*!gm>Bnh79^+`B1lRehQ6p4J0XMR)M?Ip z&&D0DJO$32z@h+FbKx93rYSO5wR`v8MEo*+IR5;7AGU5vBJ;^th4lhMocVn9V^Vsl zjmfq1=EL2|8BxumP)V3%6BE61sf}M5N1|}la18EPvkvVC4Wu_tkIR1L?^b^Wjme(v zdg?xSU!oj~P$vSRght6&S7Y}Q8Q$5hd9Am}vuW2U2;q?)6mV=<Xww0i*#$U}l&gy9 z2~@9AtZ0nQ-<eOuI2+};6h%@Jx}|B)|61QmgSoz8Zd5Z2CYyyN%~<r#y`7sr^iPZ| zB>Li+@%X<fYvCV3K5B2rf~o}3aI!|n_SWdxH&Ou)NEocKT}BF!{|N1CY2ZkpVPoTx zrC2>{4tA_ug%k={W)_43c5sBbrQY};#<I+HSqh|rXzwcL%7N9b-P^jsA>0|$XYOP{ z+eRvfD=WPifBd)$$(%PmNBiN<A?tC=9sUeK4r6uurFim*X6VmIfN`DH&_Gv=p`%)$ zMQa~bGwAPl(q+7R<Ed^!3TR_Xl#01ue}m)OcQAjqKiYNdfYvcF2yYSrzrX;bbNp{! zz8v9<BCxpx<w7qPu>3(`IWp6Au%`iBlQ;G2gkJWP&k_py)7M+@)w753;S*bsR+vqL zR%3!z=#A;z<)WIAYhD!D^atr5lK!&@L8e^zWe`b=XySuMrgWxI4;1Ul@cKuIXc7^i zeiLm`j!jM|#83Z9!KU?t(Z0JEK6q>o26@fEhSdqQ;?&{G_qSoo#$)Q3x>|X{dZj{b z;ETEzpP&d`F3niHr?(IMhxoz06GN`pkBrz%!YiIY+M0CaQy{I#XE8qgX|z)q-y0YO zB(b|=&mg$Ev8W=^k`8PrR$))CP3w<{rf!I6?TEHS3zAlU{HYCmsbEbNODoHh<51hK zy~{D8*qYHYSjWX(6b#i2<5t`6O1_`1-)Fm?Af;J)St6gn1!A>4O));(0oUm^;T>{s zzO=x;{oG1C)M*_mX-$t^a2!+aj-|Vt^OntyAz>EI7RqxWp>twLkUvFFJ=?iXDR2e4 zZCDT`H>EVWl%Zr!`b2lRl;O9o{R1(6N)z1IeghuAZw}_o-ih~K*Z?;d=PS@5<0%yd z$bZU8O0eX&>G<izR|v75hM1PoXy3LC%uCDg%ZsmK&evZd>u4&nH|HW_btc!XN)-<5 z){cd52m8abqX(=#tkrr;28JhwEhpcr!@5;Rao^-cc=)L<7(ON%Km6Gdj}KptuqJN! z`r{UO?dc`-R5)X6#;ut6XgAdzG2}@5r#wmRY<w|N;36ond3h|3Z{1F1_jcxUHH9TZ zpv3qmV=o{e0E34QgA0}Fg+KgA<D!21nU|@*Olz{{Y5Q<E*BRFI_ygH^<DP8_`Ak9s z3vw#4I`$A=dww2PE<30WTlnW*tY?Vr8`C@B*<V^>+P5i0psYOYlo#{G=&`sMs-5K+ zjWKEOQk~P<R&NOwtJnP#LWl_EO6#!ba(b)H&K<|FYjr-J{jeK?Lp(6}_HgvS*N0X! zTZNcYYhHwv^H0Ff-vuu}y%3gk=Q=P>UbIPp#nrAQ156mCB=VZ`nl?=Owq*?r+dvyM zVs1z=7uJ$vC44VIDdYDg+(!006RJ|qL)GQv!h4q6JohGT#B-7u+oe%J37R~Gppv+< zlI&CCVv(4W$V5dmOnkfxYy~P)WYM#bI)2wK)Kyd>EHso1!XR|k>yA8QQ7W<+<C&0n z44sDz=68d~;%q0ZckEUB$orChD&#YME{ZGSIA$U@6P#5$%Y9$9zFmO}Y4iBoPqreU zi3R>UXM_TO=+-j=i)J4}bSD?My4o|;jb5DU8Wb@>-<;@Qg5xg<tCE974senmT!AiL zm!}k-m_bNVp^qDDy=XNK#QRfLT7`W((&6svjDInIDUeq1*<U20V_z?f7#ppSf#kU~ zlqqptK7cbit=1P3blLB}W8dPX7<2!9Xi4j=x3@PuJw4%1t8J53t+0RlRvg}V0M0eO zd=8dGKeLBtXAczuoI@+D1?PcgfsJb6izUnU^VwFT|KKRBnwpIE!#&~S=Y-%;5A583 z3>mcAj=H^-g0=PwbfyKDh>JvylBUhQGrDU0YNWtrp@25#3$t^uoXT|j&K(iLT#9mf zFZBFH?>&%^Wr-wkc6Y-zqBnYl1as|YtsN=}r^e{%JYL&g#c~8i1r-WmP>FAbuwfTB z8#L?e#NxD8*s|d`p1glPEX^$02Y>V#xEgoe7J`JNWCF+pp+mO-G!FM-XjT&CO(%H! zI$jd*Gb$FBW%&H{Br>3+$-|d$6i2$)^&w6AWSYwIS}d4(5X<M}phXuuI5GCuj<Icm zpYG&L4T7zr6qqo68+OLVW6XU`={5_-tIsUN<`t<puq%g^L7B8>*jKKhl5`E$PZ7#2 z90^BC1w-I#&0RzoWh`9^lfM}*RY1J&D#ovy5K&5M2b`9V!TtWJf*}K6O&(rfMxyjV zq+V^%s~a)CTo46>cJe1fD59wc`i}{Li=z{I4hXGR`StA5wS*Y#T(OeSu3@kxtgPfz zXic7zj1zg^&c)l-(F@Hxb)bSt*jeuMPoKGVzAO2zjUNkoZ3KR{G__1A%w`m*)p#R) zZm7lwuz77VLL*(!B+5(OT2^Ku?tExFo_Hcs@ddeM34bajDqJ0Nv}!AART!it=eayU zjb=9#T`uNk^n+O$QUpa$3gS5gJE#Jc;t|ZvF2yTPF2(NMCouKlop1@NMYkSJF!c5a zdISCI37r>napN&(qks~S$a@a&+KqM7XJFWEW0jRw)(hcL)l`^e{Ms`l&Cfp&Teio- z+1n3M1E~-+CFDC{nX}SMaCfhnXxhe!;lj?!5^Kh_s|j-vd$XwMBM(KP^B502)p;Gp zJwuBy*S4S#7i?USjNXI8R1TvlpTB0fG0vB>Jt^b1%SHk6Au(=<AtNg-6`L9I*NdUY z<_tYPa}ATRSW{cW+BtSOyl)@Ddb|+OIIP}&dZt!fT!GE&j$`BMWR7nW8lde6mtBHq z9$bu8ZK;S`GS6~P7N-BO6WKXMICwAxIYbZ)X=0DZpWlrC{W=uUF@CUbRE#>aGE|nD zA^B(#I`;~q2e1*1jg&376ryXN;7cNdQL(rr@}2u&87TD`mo|_@IFKd}(a+x%&AVD- z&8m1jK4K3lkPRmm%k%KGM;Z};zW?u5tk`@EUwq#m!*6Ylq@*l7@aR_j>*;2&cQRuk zz8&b&FF+|I$;b1koX|sI$lXXjo{znI6OffzM4`k14)kI;FbP&rQ7qjoX!)!~8HF{e zT3Sp&MO<Z#*e6jiR1%!@Qzi!g)xQtbdBcmkCoUH9yK=vAk&xO5>bp_7s86bk`mBu= zQ8@jB#PT3<$LA0r^*_LsmcO0rH=r;-9|3`ZuwpSh#gce3$)zyk^En(Jk2Q-HqjlgQ z*qS*rhrvvbreb*JV)bY!O~$iW=LLQi)LlhUKG%3`D1E-rerUYGhja76_8XB`lf{_u z5EK>_qiwV`y7deftxqj7;%O|Wb*h{>RJAlt2qK)d8x0ZX8W$JZnHyn$!}!hGoTbo& zBpzRRb%ZBzJS|(ihl{cqUVeKJ3aWAu7~q9BCago>aRIQg(T5S<Xk+Y}KUTwfusJps zW(@P_*rm(KFc6`&MsHCxY;3I=+Ej%+<fB=)7{#}w(55{-xOT)dd12sYjC(RlfuP8J zc@p@-qN0ev70DPgt}PB_Cn6y}2kpBAs-*1h{X><9L!&|c>uKAJ??wt-HVTN`WZ*Tz zZ=~h2j?}@`YtXt=M_AG`A>&*d^V)UAuqS&TVeXNGpD%(55ijTAf3@$i_-uIl%z&Rq z3BiGYeuI{ya~o$>YiGjbjVPgl5Z>Gsk4>7xJdScqexU=>Sr2L#q3rvz#@cUFQ*qB@ z!8CGH7IZPiub;;urfmpb=CR!edmu3(4<9_R7j^bk7%{Gy)~$<rH!2nvMYan+=*&SR zXOI~Bh^C(C+C2h8@A84Jv<L;6HAqV>M9Vhw@W6ynv~TAKYYQtxMSJ6qpLgQcQ2|5? zYKNvRyfO33I5g|Zniy3U7!y1PwQhx|uA*BkzZ#DWT}*VDN^Dx1h1ex=D$dl^U0-la zDt+7gGgOKm4@-9<2bI>S3Z{jJidcN80@dZ!iV=%-_-y^w1R?n?^%yHVZ{Lo+n>HhU z&t7C_q^sC{CpUrCHB2x~LRMRhFBd}rJ#!HTbB?3=gFF{ORq+iC!J0X9k(!!9$VJvb zq4HNlS8HxgE_UzSjnxb1qyIe<>5^?h4EQ`%7e$1285fEXDox}Z*SQ9pE$l5&LAYie zQDLmTt%#6B3;hk$^mD9^Fu*qGOFm%jP>T}kQOA$wQpxASQqzdFO-?CPru14!SYu5^ z4d<_w3K2WoJiUQP^p9^v<4q;0CCJ;I54#XsI7jMpCbUI<+vej+7|l+pL}pGN#!qUG z^*autO$Tp=p|U^`n~|RXnB?c(Q9Tp#pvtl`?4)qz<Ke+j3!k$dr+AnNp`VC&vU|&R zg<5At&#P$8@=QV)-n3=kG2|B%@p%QP95G4W4y9Lq;?NzqZDJ@ob`3_t(Hw>o`m5vQ zLdNGQR4}$HLV+{aORmu}-lWc$^0p^2i8?M7hYoI{T-R0^`_86VNZ$mZaoeUX@Mp~_ z|FBS{fJi8P4MEdBoVpTEJ{g8dPxNBlv>@CvT)eA{HmG8VY*`NHiW7#8ZjO!1PGA^O z?Zh}GYyZ-v`_Z>wGt^mSP#HI6eI5@^CljJfZpUkHHpj3#g7M#%_ah^t5cf=>0Xrpw zMM5K$k@;fjr3Zb!*rScdT@nR^iHcj+f-bGF2zT`-mdB*TToy<hK%}W4ESYxzzkM8! ze#0X0*pvhoZW^MlS<En|6PpOdX=jUdE04e|pav#2EU3rt3;sBSX1(q4#s`DwNdSKM zd^6sDAr{}w9H$U4s_C5&7qTRMD`ct0j(V(j!ddDPbjV(xjeL7naAx&yJ1QXdL3YaG zr`@bf8u3u9Td)8dD8Sk<Mn8bUEa5q@XUkTs=K|7h=n!-rJczD2qXkf-+oimd<mYYL z;_1iyit%r}idD1cVA0%pv`{#y5U64uYakG6_SPxSqDh+=WzChUwfTDskVWJbNpy8> z?gDF1eJGx~;F(PGyuG;0I~cAnHC##(OH?Aa<Pd2>;$^<tH9Q>`B2L!eP&X8_s`e9i zEW*mTLOe92F+SVB56^wk0X>P<Av{BBSJ-%4qlgB9N>(411a3jm(z~Xv;Vm?POKWpL z(1avh%#plJ+WwvC@ba@)7VM6l8e>f40_;X9w~rz0at1#6R)<&KkxKB!cBLqw);rE; zQ3NP?{q1EMQ6N%_mtilp=5XxGTKU4e-4Px&9UZ!cFswtbpa=+sJUv<Oy#4KQ&%_Rf zOZ@$H{<agwAB_|^lLE3HX-yki+B>%k{C`<S8A{Sh6mnlRd3lYF-U%wpj*k8~dh`fl zV%jLhL*_&MCLvNODJdwguHqQ>Q$K0^tcv+`RTXu}&#pwi6i8(rrK5u#ycptSN!~y4 zvG(}iXM4F1(4|)(vU79Q@l9HIpe)aX-iTau>l=hGpNmJYzRd^@V~1Hkwr5@j^E#Xy zFmggU`j3dfy4Yi=F0`V7lIzJOG^1j13B<e9LuJrN%%_Ym8Ds)WE87BtiGIO9X)R*{ zk#lP~j>Kj1TcUDT1&4ZK<A!+HgjK=X%mG_>tjGKR>w}Ixg5(fFj1}SbfpHjjcM{sN z`e>g)k%)^=Bhzh!g$wo|<w!Xme7Zda8M!dEWJ1^4P?(fICBO-i3N3!c6y(YoE|tAC zn-G`83}+D{YZf7rYTv8p{E018D*6%0Ae5T57P~K@P)USMn<v$s>GJM3ZmhZ|)iLAC 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�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 %v|)$0qy+9Y#aeITS@h z%mGO;AU5%~wgvI+U*u!kwz!7^VO~BKk!M{+&nHorZ&8sPhQmR*EcrqGIeR~iGe$J| zUj#_)tjs!#hTx@#qQ^iLatq1*uPb>SdF_14-Z`P0I@0n>+lS*UPg%KPIaa;<cdP&$ z4uM1EGNszT9t+96(&ofmadl5M{D&!Lv)nC`%(Qu)x^qJ22>Sb4wqn)f+1=9zo<R;c zOPc_${Udo@AHjG5BmPvEB$a<N?o+A0Iu44b6OOU{5~^I|$!mZo0536i7|J0waGuFF zDhkd;zOCow;cdy^tB8+%L)>cSbV<&ni0AQIJ8FiQPw#%d5Ev+Q$KEyuCh1P9HUz7O zShUeB|0QwNH}$Uni9U`p`#sQx0WdjqKn>7e0Z~|hO;Na3S?l^cNulPGuxGYscbYGG zSSA3{;?Tsbw0w{xI4-PE8y!7SO1D)O7E*P}$vcGgnUuc>+;gtD5)RUeC?-YFxqTdN zv}Zrrv`H+_*}3pH0D*uJ_g#h6U;vcaz_0W-W?J*RR_z`t`wbfr2t?YCCM|mkUaaNH zE<#<~BW@YnsP3RP_`Er`<5anD)D3-v91_wa-(^n;(kzG)XV&ZSz-=|TpyXw}?a$o( z%Dfxz`ath5Y)t`TY#I67g2KcWvQIp`mUdcZ)+01}iH|gBk0dU+BlWEbeSH>~z2#gc z<}jVg@%2vJr>z*p_o;X_;hJ?i0;MKBUY7TE4tL@DROs5NxCPvbgba{^XE|{;r3Uaw z6TqHAHE=lF-JY>07D%mg9Hu&-(q;T^@%3-(ThBAG4rU>c<>o;f-AeUEPIWhVyt^|S z^Uv&N{eV`EyN(WKg?iIvre8JgY)+aq9pP)$?`$62QR6?I41^cJrfE#?6uqlU^%zf{ z4j8#}-Y;i6_;!OAXf2n&i{=qjZ|KCE@>RQhA~Rzn$C7QPdQ!mnJ+5q?^q1snQpH4a z5=!f2H#W!hUeeuj&vgVl_deNvG~~KgbR6waAg4io{pjmJ6w)EDl8yS<t20N$#ML-| z+!pb1i~^reJcaxgm@$%3VtWwEqIUK;ceq0_fKS<=Jvu<5qJ}bKRAr`+zpYnkmEjVc znO$W18)C)l?sh|~sr$?H+^)^N)#9zWfQW)f1He;azX09>&*cVn1K>>uZ=DpI#%H)O z3hV0Hv-BF8w$1$I?-(`+z;5i1DI^9+oE_#}E7X~BAn&h3-1u;fS3cS0>;l67=whaM zx|kt<84=?JXmwdhN!su)!2XgJhH(zS*>>tbblv~^@j2uWqJid^3jSqj$pAdY7cKOw Iba8S21JU5GVE_OC literal 0 HcmV?d00001 diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 114c39cca8..ae58d2d7bb 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -292,7 +292,7 @@ class UnitTable { void print_base_units(std::stringstream& base_units_details); /// Get base unit name based on the ID number of the dimension - std::string get_base_unit_name(int id){ + std::string get_base_unit_name(int id) { return base_units_names[id]; } }; From f4a7012dd29e5f36f631b1c80dff96f2d159cbab Mon Sep 17 00:00:00 2001 From: Tristan Carel <tristan.carel@gmail.com> Date: Mon, 6 May 2019 10:41:52 +0200 Subject: [PATCH 205/871] Fix double free when creating AST with Python bindings (BlueBrain/nmodl#179) * Fix double-free of ast nodes in python bindings Fix construction of ast based on python object. Example: >>> s = ast.String("Foo") >>> n = ast.Name(s) The nmodl::ast::String instance is shared by the `s' python object and the nmodl::ast::Name instance. This patch ensures that the nmodl::ast::Name constructor is given the nmodl::ast::String shared pointer, not the raw pointer that basically would make Name also owner of the pointer, and lead to a double free. Fixes BlueBrain/nmodl#161 * mark nmodl::ast constructors with one argument `explicit`. * Add unit tests to construct NMODL ast from python bindings Change-Id: Ie86a1eca52e9a9e705457d5ecdffd3a558092ab0 NMODL Repo SHA: BlueBrain/nmodl@d15627ade3d91b294a06b586db29484ba662d266 --- src/nmodl/language/nodes.py | 22 +++++++++++++++++-- src/nmodl/language/templates/pybind/pyast.cpp | 2 +- test/nmodl/transpiler/pybind/test_ast.py | 16 ++++++++++++-- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index c86eebeda3..34cc0786da 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -180,6 +180,24 @@ def get_typename(self): return type_name + def get_shared_typename(self): + """returns the shared pointer type of the node for declaration + + When node is of base type then it is returned as it is. Depending + on pointer or list, appropriate suffix is added. Some of the examples + of typename are Expression, ExpressionVector, + std::shared_ptr<Expression> etc. + """ + + type_name = self.class_name + + if self.is_vector: + type_name += "Vector" + elif not self.is_base_type_node and not self.is_ptr_excluded_node: + type_name = "std::shared_ptr<" + type_name + ">" + + return type_name + @property def member_typename(self): """returns type when used as a member of the class""" @@ -375,7 +393,7 @@ def is_base_class_number_node(self): def ctor_declaration(self): args = [f'{c.get_typename()} {c.varname}' for c in self.children] - return f"{self.class_name}({', '.join(args)});" + return f"explicit {self.class_name}({', '.join(args)});" def ctor_definition(self): args = [f'{c.get_typename()} {c.varname}' for c in self.children] @@ -388,7 +406,7 @@ def ctor_definition(self): def ctor_shrptr_declaration(self): args = [f'{c.member_typename} {c.varname}' for c in self.children] - return f"{self.class_name}({', '.join(args)});" + return f"explicit {self.class_name}({', '.join(args)});" def ctor_shrptr_definition(self): args = [f'{c.member_typename} {c.varname}' for c in self.children] diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 15a6c5c5fb..7d15b7022e 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -27,7 +27,7 @@ {%- endmacro -%} {% macro args(children) %} -{% for c in children %} {{ c.get_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} +{% for c in children %} {{ c.get_shared_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} {%- endmacro -%} // clang-format on diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/pybind/test_ast.py index f487394c0b..fdb3470188 100644 --- a/test/nmodl/transpiler/pybind/test_ast.py +++ b/test/nmodl/transpiler/pybind/test_ast.py @@ -5,8 +5,8 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** -import nmodl -from nmodl.dsl import ast, visitor +from nmodl.dsl import ast +import nmodl.dsl as nmodl import pytest class TestAst(object): @@ -14,3 +14,15 @@ def test_empty_program(self): pnode = ast.Program() assert str(pnode) == '{"Program":[]}' + def test_ast_construction(self): + string = ast.String("tau") + name = ast.Name(string) + assert nmodl.to_nmodl(name) == 'tau' + + int_macro = nmodl.ast.Integer(1, ast.Name(ast.String("x"))) + assert nmodl.to_nmodl(int_macro) == 'x' + + statements = [] + block = ast.StatementBlock(statements) + neuron_block = ast.NeuronBlock(block) + assert nmodl.to_nmodl(neuron_block) == 'NEURON {\n}' From 045183261eb3768df5743876bba325819009bcc8 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris <iomagkanaris@gmail.com> Date: Mon, 6 May 2019 16:43:09 +0200 Subject: [PATCH 206/871] Test CMakeLists.txt fixes (BlueBrain/nmodl#184) - Use catch_discover_tests only if cmake version is greater than 3.10, else use the add_test method to add the tests - Changed specific usage of "python3" command in Ode and Pybind tests. Instead PYTHON_EXECUTABLE variable is used NMODL Repo SHA: BlueBrain/nmodl@107b9e287b1ccaa574d0f90bc5b04f12edcd574e --- test/nmodl/transpiler/CMakeLists.txt | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index adbb967dfb..fb7272d772 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -61,7 +61,8 @@ target_link_libraries(testunitlexer lexer util) target_link_libraries(testunitparser lexer test_util config) # ============================================================================= -# Use catch_discover instead of add_test for granular test report +# Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, +# else use the normal add_test method # ============================================================================= foreach(test_name testmodtoken @@ -73,21 +74,30 @@ foreach(test_name testnewton testunitlexer testunitparser) - catch_discover_tests(${test_name} - TEST_PREFIX - "${test_name}/" - PROPERTIES - ENVIRONMENT - PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + + if(${CMAKE_VERSION} VERSION_GREATER "3.10") + catch_discover_tests(${test_name} + TEST_PREFIX + "${test_name}/" + PROPERTIES + ENVIRONMENT + PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + else() + add_test(NAME ${test_name} COMMAND ${test_name}) + if(${test_name} STREQUAL "testvisitor") + set_tests_properties(${test_name} + PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + endif() + endif() endforeach() # ============================================================================= # pybind11 tests # ============================================================================= add_test(NAME Ode - COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/ode) + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PROJECT_SOURCE_DIR}/test/ode) add_test(NAME Pybind - COMMAND python3 -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) foreach(test_name Ode Pybind) set_tests_properties(${test_name} PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) From ea0ee8147db3840ce49f412fa1a41e94ecf0a5ff Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 6 May 2019 17:51:40 +0200 Subject: [PATCH 207/871] Add option to embed nmodl form in when ast is converted to JSON (BlueBrain/nmodl#176) * Allow json printer to embed NMODL in the JSON ast Change-Id: Id3a38a67a104c85b1771638e31d6071e310483c7 * explicit about params in python test Change-Id: I410cd04f8a0c264638c158333858b95bc949259a NMODL Repo SHA: BlueBrain/nmodl@599c1aa319d8c0f496dba22f637a7328863a469c --- src/nmodl/language/templates/visitors/json_visitor.cpp | 7 +++++-- src/nmodl/language/templates/visitors/json_visitor.hpp | 10 ++++++++++ src/nmodl/printer/json_printer.cpp | 10 ++++++++++ src/nmodl/printer/json_printer.hpp | 1 + src/nmodl/pybind/pynmodl.cpp | 1 + src/nmodl/visitors/visitor_utils.cpp | 3 ++- src/nmodl/visitors/visitor_utils.hpp | 5 ++++- test/nmodl/transpiler/CMakeLists.txt | 2 +- test/nmodl/transpiler/pybind/test_visitor.py | 8 +++++++- 9 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 04f382e87e..722f96ce0b 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -6,7 +6,7 @@ *************************************************************************/ #include "visitors/json_visitor.hpp" - +#include "visitors/visitor_utils.hpp" namespace nmodl { namespace visitor { @@ -17,6 +17,9 @@ using namespace ast; void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { {% if node.has_children() %} printer->push_block(node->get_node_type_name()); + if (embed_nmodl) { + printer->add_block_property("nmodl", to_nmodl(node)); + } node->visit_children(this); {% if node.is_data_type_node %} {% if node.is_integer_node %} @@ -44,4 +47,4 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* {% endfor %} } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index 5653e649ac..89b9086f76 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -32,8 +32,12 @@ namespace visitor { */ class JSONVisitor: public AstVisitor { private: + /// json printer std::unique_ptr<printer::JSONPrinter> printer; + /// true if nmodl corresponding to ast node should be added to json + bool embed_nmodl = false; + public: JSONVisitor() : printer(new printer::JSONPrinter()) {} @@ -47,9 +51,15 @@ class JSONVisitor: public AstVisitor { void flush() { printer->flush(); } + void compact_json(bool flag) { printer->compact_json(flag); } + + void add_nmodl(bool flag) { + embed_nmodl = flag; + } + void expand_keys(bool flag) { printer->expand_keys(flag); } diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index c338907074..3a3ea53585 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include "printer/json_printer.hpp" +#include "utils/logger.hpp" namespace nmodl { @@ -40,6 +41,15 @@ void JSONPrinter::add_node(std::string value, const std::string& key) { block->front().push_back(j); } +/// Add property to the block which is added last +void JSONPrinter::add_block_property(std::string name, const std::string& value) { + if (block == nullptr) { + logger->warn("JSONPrinter : can't add property without block"); + return; + } + (*block)[name] = value; +} + /// Add new json object (typically start of new block) /// name here is type of new block encountered void JSONPrinter::push_block(const std::string& value, const std::string& key) { diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 2680bc0374..1cb8a9a148 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -85,6 +85,7 @@ class JSONPrinter { void push_block(const std::string& value, const std::string& key = "name"); void add_node(std::string value, const std::string& key = "name"); + void add_block_property(std::string name, const std::string& value); void pop_block(); void flush(); diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index fd06f319eb..7bda1dbb16 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -170,6 +170,7 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { "node"_a, "compact"_a = false, "expand"_a = false, + "add_nmodl"_a = false, nmodl::docstring::to_json); init_visitor_module(m_nmodl); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 2f67da90a2..7a364424d8 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -170,10 +170,11 @@ std::string to_nmodl(ast::Ast* node, const std::set<ast::AstNodeType>& exclude_t } -std::string to_json(ast::Ast* node, bool compact, bool expand) { +std::string to_json(ast::Ast* node, bool compact, bool expand, bool add_nmodl) { std::stringstream stream; visitor::JSONVisitor v(stream); v.compact_json(compact); + v.add_nmodl(add_nmodl); v.expand_keys(expand); node->accept(&v); v.flush(); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index ccd0518bb8..fc1003ed87 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -71,7 +71,10 @@ std::string to_nmodl(ast::Ast* node, const std::set<ast::AstNodeType>& exclude_t /// Given AST node, return the JSON string representation -std::string to_json(ast::Ast* node, bool compact = false, bool expand = false); +std::string to_json(ast::Ast* node, + bool compact = false, + bool expand = false, + bool add_nmodl = false); } // namespace nmodl diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index fb7272d772..c32866551a 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -55,7 +55,7 @@ target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) target_link_libraries(testparser lexer test_util util) target_link_libraries(testvisitor visitor symtab lexer util test_util printer) -target_link_libraries(testprinter printer) +target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) target_link_libraries(testunitparser lexer test_util config) diff --git a/test/nmodl/transpiler/pybind/test_visitor.py b/test/nmodl/transpiler/pybind/test_visitor.py index cfa5b4ee98..4b8858083f 100644 --- a/test/nmodl/transpiler/pybind/test_visitor.py +++ b/test/nmodl/transpiler/pybind/test_visitor.py @@ -35,12 +35,18 @@ def test_json_visitor(ch_ast): assert prime_json == '{"PrimeName":[{"String":[{"name":"m"}]},{"Integer":[{"name":"1"}]}]}' # test json with expanded keys - result_json = nmodl.dsl.to_json(primes[0], True, True) + result_json = nmodl.dsl.to_json(primes[0], compact=True, expand=True) expected_json = ('{"children":[{"children":[{"name":"m"}],' '"name":"String"},{"children":[{"name":"1"}],' '"name":"Integer"}],"name":"PrimeName"}') assert result_json == expected_json + # test json with nmodl embedded + result_json = nmodl.dsl.to_json(primes[0], compact=True, expand=True, add_nmodl=True) + expected_json = ('{"children":[{"children":[{"name":"m"}],"name":"String","nmodl":"m"},' + '{"children":[{"name":"1"}],"name":"Integer","nmodl":"1"}],' + '"name":"PrimeName","nmodl":"m\'"}') + assert result_json == expected_json def test_custom_visitor(ch_ast): From 09d8720b9dc830e3d05527aeb18d5d3fcaa95067 Mon Sep 17 00:00:00 2001 From: Omar Awile <omar.awile@epfl.ch> Date: Mon, 6 May 2019 22:29:09 +0200 Subject: [PATCH 208/871] Various fixes in documentation and build system (BlueBrain/nmodl#175) - Dropping breahte and exhale in favor of a simpler organization of sphinx+doxygen documentation. - Fix minimum version to sympy to 1.3 needed for ODE solver - Removed exhale and breathe and fixed the way doc is generated NMODL Repo SHA: BlueBrain/nmodl@37c4ed4c7a13a0359a2076d45f60a7772237854b --- INSTALL.md | 3 +++ docs/nmodl/transpiler/conf.py | 25 ------------------------- docs/nmodl/transpiler/index.rst | 1 - docs/nmodl/transpiler/modules.rst | 2 +- docs/nmodl/transpiler/nmodl.rst | 4 ++-- setup.py | 27 ++++++++++++++++++++------- 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 182dd7f000..c3320ab191 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -112,6 +112,9 @@ NMODL is now setup correctly! #### Generating Documentation +In order to build the documentation you must have additionally `pandoc` installed. Use your +system's package manager to do this (e.g. `apt install pandoc`). + Once you have installed NMODL and setup the correct PYTHONPATH, you can build the documentation locally from the docs folder as: ``` diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index f21207012e..a3d9284c07 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -35,8 +35,6 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "breathe", - "exhale", "m2r", "nbsphinx", "sphinx.ext.autodoc", @@ -66,29 +64,6 @@ nbsphinx_execute = "always" nbsphinx_kernel_name = "python3" -# Setup the breathe extension -breathe_projects = {"NMODL C++ library": "./doxyoutput/xml"} -breathe_default_project = "NMODL C++ library" - -# Setup the exhale extension -exhale_args = { - # These arguments are required - "containmentFolder": "./cpp_api", - "rootFileName": "library_root.rst", - "rootFileTitle": "C++ API", - "doxygenStripFromPath": "..", - # Suggested optional arguments - "createTreeView": True, - # TIP: if using the sphinx-bootstrap-theme, you need - "treeViewIsBootstrap": True, - "exhaleExecutesDoxygen": True, - # TODO: change to ../include? - "exhaleDoxygenStdin": """ - INPUT = ../src - FILE_PATTERNS = *.c *.h *.cpp *.hpp *.ipp - """, -} - # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 9f9937a22e..c06def12d1 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -37,7 +37,6 @@ About NMODL :caption: Reference: modules.rst - cpp_api/library_root Indices and tables ================== diff --git a/docs/nmodl/transpiler/modules.rst b/docs/nmodl/transpiler/modules.rst index d464425955..c9301b3e77 100644 --- a/docs/nmodl/transpiler/modules.rst +++ b/docs/nmodl/transpiler/modules.rst @@ -1,5 +1,5 @@ Python package -===== +============== .. toctree:: :maxdepth: 4 diff --git a/docs/nmodl/transpiler/nmodl.rst b/docs/nmodl/transpiler/nmodl.rst index 69781d6e0e..a085d15568 100644 --- a/docs/nmodl/transpiler/nmodl.rst +++ b/docs/nmodl/transpiler/nmodl.rst @@ -1,5 +1,5 @@ Module contents -================ +=============== .. automodule:: nmodl :members: @@ -9,7 +9,7 @@ Module contents Submodules -============= +========== nmodl.ast module ---------------- diff --git a/setup.py b/setup.py index bf17ad38cd..7c1125e465 100644 --- a/setup.py +++ b/setup.py @@ -15,9 +15,9 @@ import sysconfig from distutils.version import LooseVersion +from distutils.cmd import Command from setuptools import Extension, setup from setuptools.command.build_ext import build_ext -from setuptools.command.install import install from setuptools.command.test import test @@ -41,6 +41,19 @@ def get_sphinx_command(): return BuildDoc +class InstallDoc(Command): + description = 'Install Sphinx documentation' + user_options = [] + def initialize_options(self): + pass + def finalize_options(self): + pass + + def run(self): + self.run_command("test") + self.run_command("doctest") + self.run_command("buildhtml") + class CMakeExtension(Extension): def __init__(self, name, sourcedir=""): Extension.__init__(self, name, sources=[]) @@ -60,8 +73,8 @@ def run(self): cmake_version = LooseVersion( re.search(r"version\s*([\d.]+)", out.decode()).group(1) ) - if cmake_version < "3.1.0": - raise RuntimeError("CMake >= 3.1.0 is required") + if cmake_version < "3.3.0": + raise RuntimeError("CMake >= 3.3.0 is required") for ext in self.extensions: self.build_extension(ext) @@ -128,10 +141,9 @@ def run(self): "test", ] ) - subprocess.check_call([sys.executable, __file__, "doctest"]) -install_requirements = ["jinja2>=2.9", "PyYAML>=3.13", "sympy>=1.2"] +install_requirements = ["jinja2>=2.9", "PyYAML>=3.13", "sympy>=1.3"] setup( name="NMODL", @@ -145,11 +157,12 @@ def run(self): cmdclass=lazy_dict( build_ext=CMakeBuild, test=NMODLTest, - install_doc=get_sphinx_command, + install_doc=InstallDoc, doctest=get_sphinx_command, + buildhtml=get_sphinx_command, ), zip_safe=False, - setup_requires=["nbsphinx", "m2r", "exhale", "sphinx-rtd-theme", "sphinx<2"] + setup_requires=["nbsphinx", "m2r", "sphinx-rtd-theme", "sphinx>=2.0"] + install_requirements, install_requires=install_requirements, tests_require=["pytest>=3.7.2"], From 09db98528c9a29727de1c3ba63253abc4cabfe9f Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 6 May 2019 22:39:18 +0200 Subject: [PATCH 209/871] Adapt sympy tests to be compatible with sympy version 1.4 (BlueBrain/nmodl#181) - sympy produces different result expressions between different versions (mathematically equivalent though) - add result for version 1.4 and accept any of the matching solution - more robust solution will be provided by BlueBrain/nmodl#180 NMODL Repo SHA: BlueBrain/nmodl@8f724510b7b071b93064e5af2fc4a40e7aef3b82 --- .../nmodl/transpiler/visitor/sympy_solver.cpp | 76 +++++++++++++++++-- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/visitor/sympy_solver.cpp index 104533d2fb..070f9fa0d6 100644 --- a/test/nmodl/transpiler/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/visitor/sympy_solver.cpp @@ -298,7 +298,10 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", REQUIRE(result[0] == "z' = a/z+b/z/z"); REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); REQUIRE(result[2] == "x = a*dt+x"); - REQUIRE(result[3] == "y' = c3*y*y*y"); + /// sympy 1.4 able to solve ode but not older versions + bool last_result = (result[3] == "y' = c3*y*y*y" || + result[3] == "y = sqrt(-pow(y, 2)/(2*c3*dt*pow(y, 2)-1))"); + REQUIRE(last_result); } } GIVEN("Derivative block with cnexp solver method, AST after SympySolver pass") { @@ -948,16 +951,26 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { ~ a + x/b + z - y = 0.842*b*b ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c })"; - std::string expected_text = R"( + std::string expected_text_sympy_13 = R"( LINEAR lin { x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)+(1*b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(1*b+4*c)-4*c*(5.343*a+b*(-1*a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) y = (1*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-1*pow(a, 2)*pow(b, 2)*(1*b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) z = pow(a, 2)*b*(c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-(1*b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) })"; + std::string expected_text_sympy_14 = R"( + LINEAR lin { + x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(4*c-1.3)+(b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(b+4*c)-4*c*(5.343*a+b*(-a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + y = (pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(4*c-1.3)-pow(a, 2)*pow(b, 2)*(b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + z = pow(a, 2)*b*(c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(4*c-1.3)-(b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + })"; + THEN("solve analytically") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + bool result_match = (reindent_text(result[0]) == + reindent_text(expected_text_sympy_13) || + reindent_text(result[0]) == reindent_text(expected_text_sympy_14)); + REQUIRE(result_match); } } GIVEN("array state-var numeric LINEAR solve block") { @@ -1265,7 +1278,7 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s NONLINEAR nonlin { ~ x = 5 })"; - std::string expected_text = R"( + std::string expected_text_sympy_13 = R"( NONLINEAR nonlin { EIGEN_NEWTON_SOLVE[1]{ }{ @@ -1279,10 +1292,28 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s }{ } })"; + std::string expected_text_sympy_14 = R"( + NONLINEAR nonlin { + EIGEN_NEWTON_SOLVE[1]{ + }{ + }{ + X[0] = x + }{ + F[0] = 5-X[0] + J[0] = -1 + }{ + x = X[0] + }{ + } + })"; + THEN("return F & J for newton solver") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + bool result_match = (reindent_text(result[0]) == + reindent_text(expected_text_sympy_13) || + reindent_text(result[0]) == reindent_text(expected_text_sympy_14)); + REQUIRE(result_match); } } GIVEN("array state-var numeric NONLINEAR solve block") { @@ -1295,7 +1326,7 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s ~ s[1] = 3 ~ s[2] + s[1] = s[0] })"; - std::string expected_text = R"( + std::string expected_text_sympy_13 = R"( NONLINEAR nonlin { EIGEN_NEWTON_SOLVE[3]{ }{ @@ -1323,10 +1354,41 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s }{ } })"; + std::string expected_text_sympy_14 = R"( + NONLINEAR nonlin { + EIGEN_NEWTON_SOLVE[3]{ + }{ + }{ + X[0] = s[0] + X[1] = s[1] + X[2] = s[2] + }{ + F[0] = 1-X[0] + F[1] = 3-X[1] + F[2] = X[0]-X[1]-X[2] + J[0] = -1 + J[3] = 0 + J[6] = 0 + J[1] = 0 + J[4] = -1 + J[7] = 0 + J[2] = 1 + J[5] = -1 + J[8] = -1 + }{ + s[0] = X[0] + s[1] = X[1] + s[2] = X[2] + }{ + } + })"; THEN("return F & J for newton solver") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + bool result_match = (reindent_text(result[0]) == + reindent_text(expected_text_sympy_13) || + reindent_text(result[0]) == reindent_text(expected_text_sympy_14)); + REQUIRE(result_match); } } } From 18785285eb2392141eb85505cca210662111fcfd Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 6 May 2019 23:21:40 +0200 Subject: [PATCH 210/871] Replace nmodl examples and remove old docs (BlueBrain/nmodl#177) (BlueBrain/nmodl#186) - old notes / docs NOTES.md and CodeGen.md removed - removed examples from test/input and added new mod files under share/example - install examples under share/example - add python api nmodl.example_dir (useful for tutorial) NMODL Repo SHA: BlueBrain/nmodl@ffb3b2beb9ecbb6143fa2eb34692eb9e5d4cd2b0 --- CodeGen.md | 1105 ------------------ NOTES.md | 56 - cmake/nmodl/CMakeLists.txt | 5 + share/example/exp2syn.mod | 82 ++ share/example/expsyn.mod | 42 + share/example/hh.mod | 116 ++ share/example/passive.mod | 24 + src/nmodl/config/config.cpp.in | 4 + src/nmodl/config/config.h | 20 + src/nmodl/parser/CMakeLists.txt | 1 - src/nmodl/pybind/CMakeLists.txt | 1 + src/nmodl/pybind/pynmodl.cpp | 8 + src/nmodl/utils/common_utils.hpp | 3 + test/nmodl/transpiler/input/channel.mod | 119 -- test/nmodl/transpiler/input/synapse.mod | 321 ----- test/nmodl/transpiler/pybind/test_visitor.py | 1 + 16 files changed, 306 insertions(+), 1602 deletions(-) delete mode 100644 CodeGen.md delete mode 100644 NOTES.md create mode 100644 share/example/exp2syn.mod create mode 100644 share/example/expsyn.mod create mode 100644 share/example/hh.mod create mode 100644 share/example/passive.mod delete mode 100644 test/nmodl/transpiler/input/channel.mod delete mode 100644 test/nmodl/transpiler/input/synapse.mod diff --git a/CodeGen.md b/CodeGen.md deleted file mode 100644 index 9a438f992a..0000000000 --- a/CodeGen.md +++ /dev/null @@ -1,1105 +0,0 @@ -#### Notes for Code Generation - -###### when vectorize is true and what is relation with threadsafe? - -nocpout.c@99 has following fragment: - -``` -#if VECTORIZE -int vectorize = 1; -/* -the idea is to put all variables into a vector of vectors so there -there is no static data. Every function has an implicit argument, underbar ix -which tells which set of data _p[ix][...] to use. There are going to have -to be limits on the kinds of scopmath functions called since many of them -need static data. We can have special versions of the most useful of these. -ie sparse.c. -Above is obsolete in detail , underbar ix is no longer used. -When vectorize = 1 then we believe the code is thread safe and most functions -pass around _p, _ppvar, _thread. When vectorize is 0 then we are definitely -not thread safe and _p and _ppvar are static. -*/ -``` - -So by default code is compiled with `-DVECTORIZE`. Then solvehandler in solve.c turn off vectorize in various cases: - -``` -void solvhandler() { - - case DERF: - if (method == SYM0) { - method = lookup("adrunge"); - } - - if (btype == BREAKPOINT && !steadystate) { - /* derivatives recalculated after while loop */ - if (strcmp(method - > name, "cnexp") != 0 && strcmp(method - > name, "derivimplicit") != 0) { - fprintf(stderr, "Notice: %s is not thread safe. Complain to Hines\n", method - > name); - vectorize = 0; - } - - case KINF: - if (method == SYM0) { - method = lookup("_advance"); - } - if (btype == BREAKPOINT && (method - > subtype & DERF)) {# - if VECTORIZE - fprintf(stderr, "Notice: KINETIC with is thread safe only with METHOD sparse. Complain to Hines\n"); - vectorize = 0;# - endif - - case NLINF: - #if VECTORIZE - fprintf(stderr, "Notice: NONLINEAR is not thread safe.\n"); - vectorize = 0;# - endif - if (method == SYM0) { - method = lookup("newton"); - } - - case LINF: - #if VECTORIZE - fprintf(stderr, "Notice: LINEAR is not thread safe.\n"); - vectorize = 0;# - endif - if (method == SYM0) { - method = lookup("simeq"); - } - - case DISCF: - #if VECTORIZE - fprintf(stderr, "Notice: DISCRETE is not thread safe.\n"); - vectorize = 0;# - endif - if (btype == BREAKPOINT) whileloop(qsol, (long) DISCRETE, 0); - Sprintf(buf, "0; %s += d%s; %s();\n", - indepsym - > name, indepsym - > name, fun - > name); - replacstr(qsol, buf); - break; - - # - if 1 /* really wanted? */ - case PROCED: - if (btype == BREAKPOINT) { - whileloop(qsol, (long) DERF, 0);# - if CVODE - if (cvodemethod_ == 1) { /*after_cvode*/ - cvode_interface(fun, listnum, 0); - } - if (cvodemethod_ == 2) { /*cvode_t*/ - cvode_interface(fun, listnum, 0); - insertstr(qsol, "if (!cvode_active_)"); - cvode_nrn_cur_solve_ = fun; - linsertstr(procfunc, "extern int cvode_active_;\n"); - } - if (cvodemethod_ == 3) { /*cvode_t_v*/ - cvode_interface(fun, listnum, 0); - insertstr(qsol, "if (!cvode_active_)"); - cvode_nrn_current_solve_ = fun; - linsertstr(procfunc, "extern int cvode_active_;\n"); - }# - endif - } - Sprintf(buf, " %s();\n", fun - > name); - replacstr(qsol, buf);# - if VECTORIZE - Sprintf(buf, "{ %s(_threadargs_); }\n", - fun - > name); - vectorize_substitute(qsol, buf);# - endif - break;# - endif - case PARF: - #if VECTORIZE - fprintf(stderr, "Notice: PARTIAL is not thread safe.\n"); - vectorize = 0;# - endif - if (btype == BREAKPOINT) whileloop(qsol, (long) DERF, 0); - solv_partial(qsol, fun); - break; - default: - diag("Illegal or unimplemented SOLVE type: ", fun - > name); - break; - } - #if CVODE - if (btype == BREAKPOINT) { - cvode_valid(); - } - #endif - /* add the error check */ - Insertstr(qsol, "error ="); - move(errstmt - > next, errstmt - > prev, qsol - > next); - #if VECTORIZE - if (errstmt - > next == errstmt - > prev) { - vectorize_substitute(qsol - > next, ""); - vectorize_substitute(qsol - > prev, ""); - } else { - fprintf(stderr, "Notice: SOLVE with ERROR is not thread safe.\n"); - vectorize = 0; - } - #endif - - } -``` - -So various blocks are not thread safe. Also, nocpcout.c has: - -``` - if (!mechname) { - sprintf(suffix,"_%s", modbase); - mechname = modbase; - } else if (strcmp(mechname, "nothing") == 0) { - vectorize = 0; - suffix[0] = '\0'; - mechname = modbase; - }else{ - sprintf(suffix, "_%s", mechname); - } - - ..... - - if (artificial_cell && vectorize && (thread_data_index || toplocal_)) { - fprintf(stderr, "Notice: ARTIFICIAL_CELL models that would require thread specific data are not thread safe.\n"); - vectorize = 0; - } -``` - -parsact.c has : - -``` -void vectorize_use_func(qname, qpar1, qexpr, qpar2, blocktype) -Item * qname, * qpar1, * qexpr, * qpar2; -int blocktype; { - Item * q; - if (SYM(qname) - > subtype & EXTDEF) { - if (strcmp(SYM(qname) - > name, "nrn_pointing") == 0) { - Insertstr(qpar1 - > next, "&"); - } else if (strcmp(SYM(qname) - > name, "state_discontinuity") == 0) {# - if CVODE - fprintf(stderr, "Notice: Use of state_discontinuity is not thread safe"); - vectorize = 0; - if (blocktype == NETRECEIVE) { - Fprintf(stderr, "Notice: Use of state_discontinuity in a NET_RECEIVE block is unnecessary and prevents use of this mechanism in a multithreaded simulation.\n"); - } -``` -`vectorize_use_func` function is called from parser for every function call. So `state_discontinuity` call is not thread safe. - -parse1.y has following in function call grammer block: - -``` -funccall: NAME '(' - if (SYM($1)->subtype & EXTDEF5) { - if (!assert_threadsafe) { -fprintf(stderr, "Notice: %s is not thread safe\n", SYM($1)->name); - vectorize = 0; - } - .... - | all VERBATIM - /* read everything and move as is to end of procfunc */ - {inblock(SYM($2)->name); replacstr($2, "\n/*parse1.y:163: VERBATIM*/\n"); - if (!assert_threadsafe && !saw_verbatim_) { - fprintf(stderr, "Notice: VERBATIM blocks are not thread safe\n"); - saw_verbatim_ = 1; - vectorize = 0; - } -``` -`EXTDEF5` are functions/methods defined in token_mapping. These methods are also not thread safe. Verbatim bocks are also not thrad-safe unless mod file is marked thread safe explicitly. - - - - -If following variables are encountered and if mod file is not marked THREADSAFE then vectorize becomes false. This is how it is done: - -``` - case EXTERNAL: - #if VECTORIZE - threadsafe("Use of EXTERNAL is not thread safe.");# - endif - for (q = q1 - > next; q != q2 - > next; q = q - > next) { - SYM(q) - > nrntype |= NRNEXTRN | NRNNOTP; - } - plist = (List * * ) 0; - break; - case POINTER: - threadsafe("Use of POINTER is not thread safe."); - plist = & nrnpointers; - for (q = q1 - > next; q != q2 - > next; q = q - > next) { - SYM(q) - > nrntype |= NRNNOTP | NRNPOINTER; - } - break; - case BBCOREPOINTER: - threadsafe("Use of BBCOREPOINTER is not thread safe."); - plist = & nrnpointers; - for (q = q1 - > next; q != q2 - > next; q = q - > next) { - SYM(q) - > nrntype |= NRNNOTP | NRNBBCOREPOINTER; - } - use_bbcorepointer = 1; - break; - } -``` -and threadsafe function call looks like below: - -``` -void threadsafe(char* s) { - if (!assert_threadsafe) { - fprintf(stderr, "Notice: %s\n", s); - vectorize = 0; - } -} -``` - -When THREADSAFE keyword is encountered in mod file, parser calls following function: - - -``` -void threadsafe_seen(Item* q1, Item* q2) { - Item* q; - assert_threadsafe = 1; - if (q2) { - for (q = q1->next; q != q2->next; q = q->next) { - SYM(q)->assigned_to_ = 2; - } - } -} -``` - -Some helper functions like: - -``` -void chk_thread_safe() { - Symbol* s; - int i; - Item* q; - SYMLISTITER { /* globals are now global with respect to C as well as hoc */ - s = SYM(q); - if (s->nrntype & (NRNGLOBAL) && s->assigned_to_ == 1) { - sprintf(buf, "Assignment to the GLOBAL variable, \"%s\", is not thread safe", s->name); - threadsafe(buf); - } - } -} -``` - - - -###### when _thread_mem init and cleanup callbacks generated? - -nocpout.c@625 has following conditions: - -``` - if (vectorize && toplocal_) { - int cnt; - cnt = 0; - ITERATE(q, toplocal_) { - if (SYM(q) - > assigned_to_ != 2) { - if (SYM(q) - > subtype & ARRAY) { - cnt += SYM(q) - > araydim; - } else { - ++cnt; - } - } - } - sprintf(buf, " _thread[%d]._pval = (double*)ecalloc(%d, sizeof(double));\n", thread_data_index, cnt); - lappendstr(thread_mem_init_list, buf); - sprintf(buf, " free((void*)(_thread[%d]._pval));\n", thread_data_index); - lappendstr(thread_cleanup_list, buf); - cnt = 0; - ITERATE(q, toplocal_) { - if (SYM(q) - > assigned_to_ != 2) { - if (SYM(q) - > subtype & ARRAY) { - sprintf(buf, "#define %s (_thread[%d]._pval + %d)\n", SYM(q) - > name, thread_data_index, cnt); - cnt += SYM(q) - > araydim; - } else { - sprintf(buf, "#define %s _thread[%d]._pval[%d]\n", SYM(q) - > name, thread_data_index, cnt); - ++cnt; - } - } else { /* stay file static */ - if (SYM(q) - > subtype & ARRAY) { - sprintf(buf, "static double %s[%d];\n", SYM(q) - > name, SYM(q) - > araydim); - } else { - sprintf(buf, "static double %s;\n", SYM(q) - > name); - } - } - lappendstr(defs_list, buf); - } - ++thread_data_index; - } -``` - -toplocal_ list non-empty when there are local variables in the top level global scopes. In that case we initialize `thread_mem_init_list` and `thread_cleanup_list.` Also in below context: - -``` - /* per thread global data */ - gind = 0; - if (vectorize) SYMLISTITER { - s = SYM(q); - if (s->nrntype & (NRNGLOBAL) && s->assigned_to_ == 1) { - if (s->subtype & ARRAY) { - gind += s->araydim; - }else{ - ++gind; - } - } - } - /* double scalars declared internally */ - Lappendstr(defs_list, "/* declare global and static user variables : nocpout.c 675 */\n"); - /* @todo: not being used by any model? */ - if (gind) { - sprintf(buf, "static int _thread1data_inuse = 0;\nstatic double _thread1data[%d];\n#define _gth %d\n", gind, thread_data_index); - Lappendstr(defs_list, buf); - sprintf(buf, " if (_thread1data_inuse) {_thread[_gth]._pval = (double*)ecalloc(%d, sizeof(double));\n }else{\n _thread[_gth]._pval = _thread1data; _thread1data_inuse = 1;\n }\n", gind); - lappendstr(thread_mem_init_list, buf); - lappendstr(thread_cleanup_list, " if (_thread[_gth]._pval == _thread1data) {\n _thread1data_inuse = 0;\n }else{\n free((void*)_thread[_gth]._pval);\n }\n"); - ++thread_data_index; - } -``` - -We loop over all global variables and see if any of the global variable is used as `assigned` i.e. shoul dbe written once. In that case those global variables get promited to thread variable. But note that this happens only in THREADSAFE marked mod file. - - -##### Confusions - - -###### Can ion variable appear in read as well as write list? - -``` -USEION ttx READ ttxo, ttxi WRITE ittx, ttxo VALENCE 1 -``` - -mod2c/neuron only consider ttxo in read list but not in write list. But note that `_ion_dittxdv` does appear and style variable. Check if above condition is semantically correct. - - -####### v as static when model is not thread safe - -Sometime I see `v` being declared as static variable: - -``` - static double delta_t = 0.01; - static double h0 = 0; - static double m0 = 0; - static double v = 0; -``` - - and that is used in kernels: - -``` - static double _nrn_current(double _v){double _current=0.;v=_v;{ { - gNaTs2_t = gNaTs2_tbar * m * m * m * h ; - ina = gNaTs2_t * ( v - ena ) + asecond [ 1 ] ; -``` - -Why this works? If model is marked threadsafe then v is passed as parameter to function calls where v is derived from: - - ``` - _v = _vec_v[_nd_idx]; - ``` -file:/Users/kumbhar/workarena/repos/bbp/mod2c_debug/src/mod2c_core/init.c -But in case of non-thread-safe models, v can be just static. This is done in nocpout.c@1994 where we see: - -``` -#if VECTORIZE - if (vectorize) { - s = ifnew_install("v"); - s->nrntype = NRNNOTP; /* this is a lie, it goes in at end specially */ - /* no it is not a lie. Use an optimization where voltage passed via arguments */ - }else -#endif - { - s = ifnew_install("v"); - s->nrntype |= NRNSTATIC | NRNNOTP; - } - s = ifnew_install("t"); - s->nrntype |= NRNEXTRN | NRNNOTP; - s = ifnew_install("dt"); - s->nrntype |= NRNEXTRN | NRNNOTP; - /*@todo: why this vectorize_substitute? */ - //printer->print_pragma_t_dt(); -``` - -So, declare v as static if model is not thread-safe. - - - -#### Order of variables in p array? - -This is driving me nuts! Initially I thought the order is dected by NEURON block order. But certainly it's not the case! -For example: - -- if we have RANGE variable declared in the NEURON block doesn't appear anywhere then it's not appear in translated c file! - -It looks like NEURON block is just declaration and doesn't decide the order. The variable must appear in definition blocks like parameter or assigned etc. For example, in above case if is mentioned in PARAMETER then only it appeared in the C file. - -``` -NEURON { - SUFFIX kca - RANGE gk, gbar, m_inf, tau_m,ik, PPP, QQQ - USEION ca READ cai - USEION k READ ek WRITE ik - GLOBAL beta, cac -} - -PARAMETER { - PPP -} - -``` - -`PPP` appeared first in p array. QQQ is not appeared anywhere then it's not used at all. Oh boy! if variable is mentioned as RANGE and used in one of the block, then it's not defined at all. It must be "defined" in parameter or assigned block. - -Consider below example: - - -``` - NEURON { - SUFFIX kca - USEION ca READ cai - } - - PARAMETER { - ek = -80 (mV) - cai - } -``` - -In this case ek comes first and then cai. This order is preserved across blocks i.e. : - -``` - NEURON { - SUFFIX kca - USEION ca READ cai - } - - ASSIGNED { - cai - - } - - PARAMETER { - ek = -80 (mV) - } - -``` - -Now cai becomes firsr variable. So definitions are important for order. - -Also, suppose STATE block appears before PARAMETER and ASSIGNED block. In this case, Dstate variables must come before other variables "in symorder" block. Here symorder means remaining variables that we print after range+state type. - -CONCLUSION: we can keep global order by excluding NEURON block definitions. - -Oh Wait: More caveats! For thread variables (global variables get converted to thread specific in threadsafe model), they don't appear in the order of their definition. This is because when we split out we iterate over symbol table in alphabetical order (?): - -``` - SYMLISTITER { /* globals are now global with respect to C as well as hoc */ - s = SYM(q); - if (s - > nrntype & (NRNGLOBAL)) { - if (vectorize && s - > assigned_to_ == 1) { - if (s - > subtype & ARRAY) { - sprintf(buf, "#define %s%s (_thread1data + %d)\n\ -#define %s (_thread[_gth]._pval + %d)\n", - s - > name, suffix, gind, s - > name, gind); - } else { - sprintf(buf, "#define %s%s _thread1data[%d]\n\ -#define %s _thread[_gth]._pval[%d]\n", - s - > name, suffix, gind, s - > name, gind); - } - q1 = Lappendstr(defs_list, buf); - q1 - > itemtype = VERBATIM; - if (s - > subtype & ARRAY) { - gind += s - > araydim; - } else { - ++gind; - } -``` - -Due to this thread variable declaration is not in the order of definitions. Here is mod file example: - -``` -NEURON { - THREADSAFE - SUFFIX NaTs2_t - GLOBAL hfirst, hlast, gsecond, gthird -} - -PARAMETER { - hlast - hfirst - glast - gsecond - gfirtst - gthird -} - -LOCAL abc, abd - -BREAKPOINT { - hlast = 1 - hfirst = 11 - gfirtst= 11 - glast = 1 - gthird = 11 - abc = 1 - abd = 2 -} -``` - -In this case, when global variables are encountered, they get inserted into symbol list with symbol[0] i.e. first character is used to select a bucket. Then subsequent symbols with same symbol[0] get prepended and hence the order becomes: - -``` -gfirst, glast, gthird, gsecond # note: symbol not found get prepended to the bucket -hfirst # this is separate bucket - -``` - -and hence generated order is: - - -``` - #define _zabc _thread[0]._pval[0] - #define _zabd _thread[0]._pval[1] - - /* declare global and static user variables */ - static int _thread1data_inuse = 0; -static double _thread1data[5]; -#define _gth 1 -#define gfirtst_NaTs2_t _thread1data[0] -#define gfirtst _thread[_gth]._pval[0] -#define glast_NaTs2_t _thread1data[1] -#define glast _thread[_gth]._pval[1] -#define gthird_NaTs2_t _thread1data[2] -#define gthird _thread[_gth]._pval[2] -#define gsecond gsecond_NaTs2_t - double gsecond = 0; - #pragma acc declare copyin (gsecond) -#define hlast_NaTs2_t _thread1data[3] -#define hlast _thread[_gth]._pval[3] -#define hfirst_NaTs2_t _thread1data[4] -#define hfirst _thread[_gth]._pval[4] -``` - -Note that top local thread variables are in order because they are simply appended from parser. - - -####### ppvar semantics - -PPVAR semantics are registred by following function: - -``` -void ppvar_semantics(int i, const char* name) { - Item* q; - nmp_add_ppvar_semantics(printer, name, i); - if (!ppvar_semantics_) { ppvar_semantics_ = newlist(); } - q = Lappendstr(ppvar_semantics_, name); - q->itemtype = (short)i; -} -``` - -and this is called by following functions: - -``` -void parout() { - ....funciton that emil all header part.... - - if (net_send_seen_) { - tqitem_index = ppvar_cnt; - ppvar_semantics(ppvar_cnt, "netsend"); - ppvar_cnt++; - } - if (watch_seen_) { - watch_index = ppvar_cnt; - for (i=0; i < watch_seen_ ; ++i) { - ppvar_semantics(i+ppvar_cnt, "watch"); - } - ppvar_cnt += watch_seen_; - sprintf(buf, "\n#define _watch_array _ppvar + %d", watch_index); - Lappendstr(defs_list, buf); - Lappendstr(defs_list, "\n"); - } - if (for_netcons_) { - sprintf(buf, "\n#define _fnc_index %d\n", ppvar_cnt); - Lappendstr(defs_list, buf); - ppvar_semantics(ppvar_cnt, "fornetcon"); - ppvar_cnt += 1; - } - if (cvode_emit) { - cvode_ieq_index = ppvar_cnt; - ppvar_semantics(ppvar_cnt, "cvodeieq"); - ppvar_cnt++; - } - - if (diamdec) { - ppvar_semantics(ioncount + pointercount, "diam"); - } - if (areadec) { - ppvar_semantics(ioncount + pointercount + diamdec, "area"); - } - -} - -/* note: _nt_data is only defined in nrn_init, nrn_cur and nrn_state where ions are used in the current mod files */ -static int iondef(p_pointercount) int * p_pointercount; { - int ioncount, it, need_style; - Item * q, * q1, * q2; - Symbol * sion; - char ionname[100]; - char pionname[100]; - - lappendstr(defs_list, "/*ion definitions in nocpout.c:2077 in iondef() */\n"); - - ioncount = 0; - if (point_process) { - ioncount = 2; - q = lappendstr(defs_list, "#define _nd_area _nt_data[_ppvar[0*_STRIDE]]\n"); - //printer->print_pragma_ion_var("_nd_area", 0, ION_REGULAR); - nmp_add_ion_variable(printer, "_nd_area", VAR_DOUBLE, 0, ION_REGULAR); - q - > itemtype = VERBATIM; - ppvar_semantics(0, "area"); - ppvar_semantics(1, "pntproc"); - } - ITERATE(q, useion) { - int dcurdef = 0; - if (!uip) { - uip = newlist(); - lappendstr(uip, "static void _update_ion_pointer(Datum* _ppvar) {\n"); - } - need_style = 0; - sion = SYM(q); - sprintf(ionname, "%s_ion", sion - > name); - nmp_add_ion_name(printer, sion - > name); - q = q - > next; - ITERATE(q1, LST(q)) { - SYM(q1) - > nrntype |= NRNIONFLAG; - Sprintf(buf, "#define _ion_%s _nt_data[_ppvar[%d*_STRIDE]]\n", - SYM(q1) - > name, ioncount); - q2 = lappendstr(defs_list, buf); - sprintf(pionname, "_ion_%s", SYM(q1) - > name); - //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); - q2 - > itemtype = VERBATIM; - sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, %d);\n", - sion - > name, ioncount, iontype(SYM(q1) - > name, sion - > name));# - if 0 /*BBCORE*/ - lappendstr(uip, buf);# - endif /*BBCORE*/ - SYM(q1) - > ioncount_ = ioncount; - ppvar_semantics(ioncount, ionname); - ioncount++; - } - q = q - > next; - ITERATE(q1, LST(q)) { - if (SYM(q1) - > nrntype & NRNIONFLAG) { - SYM(q1) - > nrntype &= ~NRNIONFLAG; - } else { - Sprintf(buf, "#define _ion_%s _nt_data[_ppvar[%d*_STRIDE]]\n", - SYM(q1) - > name, ioncount); - q2 = lappendstr(defs_list, buf); - sprintf(pionname, "_ion_%s", SYM(q1) - > name); - //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); - q2 - > itemtype = VERBATIM; - sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, %d);\n", - sion - > name, ioncount, iontype(SYM(q1) - > name, sion - > name));# - if 0 /*BBCORE*/ - lappendstr(uip, buf);# - endif /*BBCORE*/ - SYM(q1) - > ioncount_ = ioncount; - ppvar_semantics(ioncount, ionname); - ioncount++; - } - it = iontype(SYM(q1) - > name, sion - > name); - if (it == IONCUR) { - dcurdef = 1; - Sprintf(buf, "#define _ion_di%sdv\t_nt_data[_ppvar[%d*_STRIDE]]\n", sion - > name, ioncount); - q2 = lappendstr(defs_list, buf); - sprintf(pionname, "_ion_di%sdv", sion - > name); - //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); - q2 - > itemtype = VERBATIM; - sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, 4);\n", - sion - > name, ioncount);# - if 0 /*BBCORE*/ - lappendstr(uip, buf);# - endif /*BBCORE*/ - ppvar_semantics(ioncount, ionname); - ioncount++; - } - if (it == IONIN || it == IONOUT) { /* would have wrote_ion_conc */ - need_style = 1; - } - } - if (need_style) { - Sprintf(buf, "#define _style_%s\t_ppvar[%d]\n", sion - > name, ioncount); - q2 = lappendstr(defs_list, buf); - sprintf(pionname, "_style_%s", sion - > name); - //printer->print_pragma_ion_var(pionname, ioncount, ION_NEED_STYLE); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_NEED_STYLE); - q2 - > itemtype = VERBATIM; - sprintf(buf, "#%s", ionname); - ppvar_semantics(ioncount, buf); - ioncount++; - } - q = q - > next; - if (!dcurdef && ldifuslist) { - Sprintf(buf, "#define _ion_di%sdv\t_nt_data[_ppvar[%d*_STRIDE]]\n", sion - > name, ioncount); - q2 = lappendstr(defs_list, buf); - sprintf(pionname, "_ion_di%sdv", sion - > name); - //printer->print_pragma_ion_var(pionname, ioncount, ION_REGULAR); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount, ION_REGULAR); - q2 - > itemtype = VERBATIM; - sprintf(buf, " nrn_update_ion_pointer(_%s_sym, _ppvar, %d, 4);\n", - sion - > name, ioncount);# - if 0 /*BBCORE*/ - lappendstr(uip, buf);# - endif /*BBCORE*/ - ppvar_semantics(ioncount, ionname); - ioncount++; - } - } * p_pointercount = 0; - ITERATE(q, nrnpointers) { - sion = SYM(q); - if (sion - > nrntype & NRNPOINTER) { - Sprintf(buf, "#define %s _nt_data[_ppvar[%d*_STRIDE]]\n", - sion - > name, ioncount + * p_pointercount); - ppvar_semantics(ioncount + * p_pointercount, "pointer"); - sprintf(pionname, "%s", sion - > name); - //printer->print_pragma_ion_var(pionname, ioncount + *p_pointercount, ION_REGULAR); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount + * p_pointercount, ION_REGULAR); - } - /*@todo: forgot or doesnt matter ? */ - if (sion - > nrntype & NRNBBCOREPOINTER) { - Sprintf(buf, "#define _p_%s _nt->_vdata[_ppvar[%d*_STRIDE]]\n", - sion - > name, ioncount + * p_pointercount); - ppvar_semantics(ioncount + * p_pointercount, "bbcorepointer"); - sprintf(pionname, "_p_%s", sion - > name); - //printer->print_pragma_ion_var(pionname, ioncount + *p_pointercount, ION_BBCORE_PTR); - nmp_add_ion_variable(printer, pionname, VAR_DOUBLE, ioncount + * p_pointercount, ION_BBCORE_PTR); - } - sion - > used = ioncount + * p_pointercount; - q2 = lappendstr(defs_list, buf); - q2 - > itemtype = VERBATIM; - ( * p_pointercount) ++; - } - - if (diamdec) { /* must be last */ - Sprintf(buf, "#define diam *_ppvar[%d]._pval\n", ioncount + * p_pointercount); - //printer->print_error("diamdec ion declaration not possible!"); - - q2 = lappendstr(defs_list, buf); - q2 - > itemtype = VERBATIM; - } /* notice that ioncount is not incremented */ - if (areadec) { - /* must be last, if we add any more the administrative - procedures must be redone */ - Sprintf(buf, "#define area _nt->_data[_ppvar[%d*_STRIDE]]\n", ioncount + * p_pointercount + diamdec); - q2 = lappendstr(defs_list, buf); - //printer->print_pragma_ion_var("area", ioncount+ * p_pointercount + diamdec, ION_REGULAR); - nmp_add_ion_variable(printer, "area", VAR_DOUBLE, ioncount + * p_pointercount + diamdec, ION_REGULAR); - q2 - > itemtype = VERBATIM; - } /* notice that ioncount is not incremented */ - if (uip) { - lappendstr(uip, "}\n"); - } - return ioncount; -} -``` - - - -####### USEION statement ordering - -If we take following (fake) example: - -``` - USEION na READ ena, ina WRITE ina, nai -``` - - -ina appears on read as well as write list. Is that possible? Neuron generate code as: - -``` -#define _ion_ena _nt_data[_ppvar[0*_STRIDE]] -#define _ion_ina _nt_data[_ppvar[1*_STRIDE]] -#define _ion_nai _nt_data[_ppvar[2*_STRIDE]] -#define _ion_dinadv _nt_data[_ppvar[3*_STRIDE]] -``` - -So ina doesnt appear twice. After looking at the mod file, the ASSUMPTION is particular variable appears either in read list -or write list but not in both. If it appears in both list, then currently the order in symbol table is used and which case ina will appear later in write list. Hence the wrong indexes! - - -######### When write_conc gets written and different ionic type macros are defined? - -Here q->next->next is write_ion list where we search for intra/extra conc. - -``` - /* Models that write concentration need their INITIAL blocks called - before those that read the concentration or reversal potential. */ - i = 0; - ITERATE(q, useion) { - ITERATE(q1, LST(q->next->next)) { - int type; - type = iontype(SYM(q1)->name, SYM(q)->name); - if (type == IONIN || type == IONOUT) { - i += 1; - } - } - q = q->next->next->next; - } - - nmp_set_writes_conc(printer, i); - - if (i) { - Lappendstr(defs_list, "\tnrn_writes_conc(_mechtype, 0);\n"); - } -``` - -Note that IONIN or IONOUT is determined by: - -``` -static int iontype(s1, s2) /* returns index of variable in ion mechanism */ - char *s1, *s2; -{ - Sprintf(buf, "i%s", s2); - if (strcmp(buf, s1) == 0) { - return IONCUR; - } - Sprintf(buf, "e%s", s2); - if (strcmp(buf, s1) == 0) { - return IONEREV; - } - Sprintf(buf, "%si", s2); - if (strcmp(buf, s1) == 0) { - return IONIN; - } - Sprintf(buf, "%so", s2); - if (strcmp(buf, s1) == 0) { - return IONOUT; - } - Sprintf(buf, "%s is not a valid ionic variable for %s", s1, s2); - diag(buf, (char *)0); - return -1; -} -``` - -The loop that processes the use ion statements is following: - -``` -void nrn_use(q1, q2, q3, q4) - Item *q1, *q2, *q3, *q4; -{ - int used, i; - Item *q, *qr, *qw; - List *readlist, *writelist; - Symbol *ion; - - ion = SYM(q1); - /* is it already used */ - used = ion_declared(SYM(q1)); - if (used) { /* READ gets promoted to WRITE */ - diag("mergeing of neuron models not supported yet", (char *)0); - }else{ /* create all the ionic variables */ - Lappendsym(useion, ion); - readlist = newlist(); - writelist = newlist(); - qr = lappendsym(useion, SYM0); - qw = lappendsym(useion, SYM0); - if (q4) { - lappendstr(useion, STR(q4)); - }else{ - lappendstr(useion, "-10000."); - } - LST(qr) = readlist; - LST(qw) = writelist; - if (q2) { Item *qt = q2->next; - move(q1->next->next, q2, readlist); - if (q3) { - move(qt->next, q3, writelist); - } - }else if (q3) { - move(q1->next->next, q3, writelist); - } - ITERATE(q, readlist) { - i = iontype(SYM(q)->name, ion->name); - if (i == IONCUR) { - SYM(q)->nrntype |= NRNCURIN; - }else{ - SYM(q)->nrntype |= NRNPRANGEIN; - if (i == IONIN || i == IONOUT) { - SYM(q)->nrntype |= IONCONC; - } - } - } - ITERATE(q, writelist) { - i = iontype(SYM(q)->name, ion->name); - if (i == IONCUR) { - if (!currents) { - currents = newlist(); - } - Lappendsym(currents, SYM(q)); - SYM(q)->nrntype |= NRNCUROUT; - }else{ - SYM(q)->nrntype |= NRNPRANGEOUT; - if (i == IONIN || i == IONOUT) { - SYM(q)->nrntype |= IONCONC; - } - } - } - } -} -``` - - -######### Does derivX_advance (X is 1, 2) can appear multiple times? - -main() in modl.c calls solvhandler() which in turn calls solvhandler() in solve.c. There we loop over all solve blocks: - -``` - ITERATE(lq, solvq) { /* remember it goes by 3's */ - steadystate=0; - btype = lq->itemtype; - if (btype < 0) { - btype = lq->itemtype = -btype; - - case DERF: - if (method == SYM0) { - method = lookup("adrunge"); - } - if (btype == BREAKPOINT && !steadystate) { - /* derivatives recalculated after while loop */ - if (strcmp(method->name, "cnexp") != 0 && strcmp(method->name, "derivimplicit") != 0 && strcmp(method->name, "euler") != 0) { - fprintf(stderr, "Notice: %s is not thread safe. Complain to Hines\n", method->name); - vectorize = 0; - Sprintf(buf, " %s();\n", fun->name); - Insertstr(follow, buf); - } - /* envelope calls go after the while loop */ - sens_nonlin_out(follow, fun); - #if CVODE - cvode_interface(fun, listnum, numeqn); - #endif - } - if (btype == BREAKPOINT) whileloop(qsol, (long)DERF, steadystate); - solv_diffeq(qsol, fun, method, numeqn, listnum, steadystate, btype); -``` - -And solv_diffeq() is the one who print out derivimplicit related code: - -``` -if (deriv_imp_list) { /* make sure deriv block translation matches method */ - Item *q; int found=0; - ITERATE(q, deriv_imp_list) { - if (strcmp(STR(q), fun->name) == 0) { - found = 1; - } - } - if ((strcmp(method->name, Derivimplicit) == 0) ^ (found == 1)) { - diag("To use the derivimplicit method the SOLVE statement must\ - precede the DERIVATIVE block\n", - " and all SOLVEs using that block must use the derivimplicit method\n"); - } - derivimplic_listnum = listnum; - Sprintf(buf, "static int _deriv%d_advance = 1;\n", listnum); - q = linsertstr(procfunc, buf); - Sprintf(buf, "\n#define _deriv%d_advance _thread[%d]._i\n\ - #define _dith%d %d\n#define _newtonspace%d _thread[%d]._pvoid\nextern void* nrn_cons_newtonspace(int, int);\n\ -", listnum, thread_data_index, listnum, thread_data_index+1, listnum, thread_data_index+2); -``` - -So if there are multiple solve blocks appear in the MOD file then only this can appear twice. - - -######### where nrn_newton_thread(_newtonspac.. get printed? - -mixed_eqns is where the callbacks get printed: - -``` -Item *mixed_eqns(q2, q3, q4) /* name, '{', '}' */ - Item *q2, *q3, *q4; -{ - int counts; - Item *qret; - Item* q; - - if (!eqnq) { - return ITEM0; /* no nonlinear algebraic equations */ - } - /* makes use of old massagenonlin split into the guts and - the header stuff */ - numlist++; - counts = nonlin_common(q4, 0); - q = insertstr(q3, "{\n"); - sprintf(buf, "{ double* _savstate%d = (double*)_thread[_dith%d]._pval;\n\ - double* _dlist%d = (double*)(_thread[_dith%d]._pval) + (%d*_cntml_padded);\n", -numlist-1, numlist-1, -numlist, numlist-1, counts); - vectorize_substitute(q, buf); - - Sprintf(buf, "error = newton(%d,_slist%d, _p, _newton_%s%s, _dlist%d);\n", - counts, numlist, SYM(q2)->name, suffix, numlist); - qret = insertstr(q3, buf); - Sprintf(buf, - "#pragma acc routine(nrn_newton_thread) seq\n" -#if 0 - "_reset = nrn_newton_thread(_newtonspace%d, %d,_slist%d, _newton_%s%s, _dlist%d, _threadargs_);\n" -#else - "_reset = nrn_newton_thread(_newtonspace%d, %d,_slist%d, _derivimplicit_%s%s, _dlist%d, _threadargs_);\n" -#endif - , numlist-1, counts, numlist, SYM(q2)->name, suffix, numlist); - vectorize_substitute(qret, buf); - Insertstr(q3, "/*if(_reset) {abort_run(_reset);}*/ }\n"); - Sprintf(buf, - "extern int _newton_%s%s(_threadargsproto_);\n" - , SYM(q2)->name, suffix); - Linsertstr(procfunc, buf); - - Sprintf(buf, - "\n" - "/* _derivimplicit_ %s %s */\n" - "#ifndef INSIDE_NMODL\n" - "#define INSIDE_NMODL\n" - "#endif\n" - "#include \"_kinderiv.h\"\n" - , SYM(q2)->name, suffix); - Linsertstr(procfunc, buf); - - Sprintf(buf, "\n return _reset;\n}\n\nint _newton_%s%s (_threadargsproto_) { int _reset=0;\n", SYM(q2)->name, suffix); - Insertstr(q3, buf); - q = insertstr(q3, "{ int _counte = -1;\n"); - sprintf(buf, "{ double* _savstate%d = (double*)_thread[_dith%d]._pval;\n\ - double* _dlist%d = (double*)(_thread[_dith%d]._pval) + (%d*_cntml_padded);\n int _counte = -1;\n", -numlist-1, numlist-1, -numlist, numlist-1, counts); - vectorize_substitute(q, buf); - - Insertstr(q4, "\n }"); - return qret; -} -``` - - -##### Thread promoted variables may not appear in the thread in NOCMODL and code will be different ! - -See below example in old ProbAMPANMDA_EMS.mod: - -``` - -PARAMETER { - mggate -} - -BREAKPOINT { - - SOLVE state - mggate = 1 / (1 + exp(0.062 (/mV) * -(v)) * (mg / 3.57 (mM))) :mggate kinetics - Jahr & Stevens 1990 - g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA - g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics - g = g_AMPA + g_NMDA - i_AMPA = g_AMPA*(v-e) :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal - i_NMDA = g_NMDA*(v-e) :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal - i = i_AMPA + i_NMDA -} - -``` - -As mggate is parameter and being written, NEURON will promote this to thread variable. But, nocmodl localizer pass will convert the mggate to local variable and then mggate in global symbol table is not written and hence wont be promoted to Thread variable. - -It seems this is ok because we should use optimized mod file for NEURON as well. And in that case code generated by NEURON will be compatible with CoreNEURON. \ No newline at end of file diff --git a/NOTES.md b/NOTES.md deleted file mode 100644 index 861f76bb26..0000000000 --- a/NOTES.md +++ /dev/null @@ -1,56 +0,0 @@ -#### Generating bar charts from PerfStat visitor - -These are steps followed for generating bar plot for meeting on 18th Dec 2017. - -First, get mod files for BBP model (e.g. savestate branch of neurodamus). We are only interested in mod files that are used in simulation. Copy them to some temporary directory : - -``` -cd $HOME/workarena/repos/bbp/neurodamus/lib/modlib -git checkout sandbox/kumbhar/coreneuron_plasticity - -MOD_DIR=$HOME/workarena/repos/bbp/incubator/plots_dec_21_fs_meeting/bbp_mod - -mkdir -p $MOD_DIR -while read -u 10 file; do cp $file $MOD_DIR/; done 10<coreneuron_modlist.txt -``` - -The function/procedure inlining pass is not implemented into nmodl yet. Get inlined mod files for performance counting: - -``` -cd $MOD_DIR -mkdir inlined -for file in *.mod; do - $NMODL_DIR/bin/nmodl -f $file -a a -i -s s -n inlined/${file}; -done -``` - -Now we can run nmodl visitor executable: - -``` -mkdir -p $MOD_DIR/jsons -cd $MOD_DIR/jsons -export NMODL=~/workarena/repos/bbp/incubator/nmodl/cmake-build-debug - -for file in $MOD_DIR/inlined/*.mod.in; do - $NMODL/bin/nmodl_visitor --file $file; -done -``` - -We can generate JSON data required for plotting as: - -``` -cd $MOD_DIR/jsons -python ../../json_generator.py -``` - -Above script print JSON data required for plotting on stdout. We have to copy the output to: - -``` -../../html_plotting/data.js -``` - -We can now open the plot from `html_plotting/plots.html`. Material/Scripts for the plotting is stored in: - -``` -$HOME/workarena/repos/bbp/incubator/plots_dec_21_fs_meeting -``` diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 029d2faf34..4e98f9e9a4 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -160,3 +160,8 @@ set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ --show-possibly-lost=no") include(CTest) add_subdirectory(test) + +# ============================================================================= +# Install unit database, examples and utility scripts from share +# ============================================================================= +install(DIRECTORY share/ DESTINATION share) diff --git a/share/example/exp2syn.mod b/share/example/exp2syn.mod new file mode 100644 index 0000000000..2cb177eccd --- /dev/null +++ b/share/example/exp2syn.mod @@ -0,0 +1,82 @@ +COMMENT +Two state kinetic scheme synapse described by rise time tau1, and decay time +constant tau2. The normalized peak condunductance is 1. Decay time MUST be +greater than rise time. + +The solution of A->G->bath with rate constants 1/tau1 and 1/tau2 is + A = a*exp(-t/tau1) and + G = a*tau2/(tau2-tau1)*(-exp(-t/tau1) + exp(-t/tau2)) + where tau1 < tau2 + +If tau2-tau1 is very small compared to tau1, this is an alphasynapse with time +constant tau2. If tau1/tau2 is very small, this is single exponential decay +with time constant tau2. + +The factor is evaluated in the initial block such that an event of weight 1 +generates a peak conductance of 1. + +Because the solution is a sum of exponentials, the coupled equations can be +solved as a pair of independent equations by the more efficient cnexp method. +ENDCOMMENT + +NEURON { + POINT_PROCESS Exp2Syn + RANGE tau1, tau2, e, i + NONSPECIFIC_CURRENT i + RANGE g +} + +UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) +} + +PARAMETER { + tau1 = 0.1 (ms) <1e-9,1e9> + tau2 = 10 (ms) <1e-9,1e9> + e=0 (mV) +} + +ASSIGNED { + v (mV) + i (nA) + g (uS) + factor +} + +STATE { + A (uS) + B (uS) +} + +INITIAL { + LOCAL tp + if (tau1/tau2 > 0.9999) { + tau1 = 0.9999*tau2 + } + if (tau1/tau2 < 1e-9) { + tau1 = tau2*1e-9 + } + A = 0 + B = 0 + tp = (tau1*tau2)/(tau2 - tau1) * log(tau2/tau1) + factor = -exp(-tp/tau1) + exp(-tp/tau2) + factor = 1/factor +} + +BREAKPOINT { + SOLVE state METHOD cnexp + g = B - A + i = g*(v - e) +} + +DERIVATIVE state { + A' = -A/tau1 + B' = -B/tau2 +} + +NET_RECEIVE(weight (uS)) { + A = A + weight*factor + B = B + weight*factor +} diff --git a/share/example/expsyn.mod b/share/example/expsyn.mod new file mode 100644 index 0000000000..b6aa17a9a3 --- /dev/null +++ b/share/example/expsyn.mod @@ -0,0 +1,42 @@ +NEURON { + POINT_PROCESS ExpSyn + RANGE tau, e, i + NONSPECIFIC_CURRENT i +} + +UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) +} + +PARAMETER { + tau = 0.1 (ms) <1e-9,1e9> + e = 0 (mV) +} + +ASSIGNED { + v (mV) + i (nA) +} + +STATE { + g (uS) +} + +INITIAL { + g = 0 +} + +BREAKPOINT { + SOLVE state METHOD cnexp + i = g*(v - e) +} + +DERIVATIVE state { + g' = -g/tau +} + +NET_RECEIVE(weight (uS)) { + g = g + weight +} diff --git a/share/example/hh.mod b/share/example/hh.mod new file mode 100644 index 0000000000..cbe00e49e9 --- /dev/null +++ b/share/example/hh.mod @@ -0,0 +1,116 @@ +TITLE hh.mod squid sodium, potassium, and leak channels + +COMMENT +This is the original Hodgkin-Huxley treatment for the set of sodium, potassium, +and leakage channels found in the squid giant axon membrane. ("A quantitative +description of membrane current and its application conduction and excitation +in nerve" J.Physiol. (Lond.) 117:500-544 (1952).) Membrane voltage is in +absolute mV and has been reversed in polarity from the original HH convention +and shifted to reflect a resting potential of -65 mV. Remember to set +celsius=6.3 (or whatever) in your HOC file. See squid.hoc for an example of a +simulation using this model. SW Jaslove 6 March, 1992 +ENDCOMMENT + +UNITS { + (mA) = (milliamp) + (mV) = (millivolt) + (S) = (siemens) +} + +? interface +NEURON { + SUFFIX hh + USEION na READ ena WRITE ina + USEION k READ ek WRITE ik + NONSPECIFIC_CURRENT il + RANGE gnabar, gkbar, gl, el, gna, gk + RANGE minf, hinf, ninf, mtau, htau, ntau + THREADSAFE +} + +PARAMETER { + gnabar = .12 (S/cm2) <0,1e9> + gkbar = .036 (S/cm2) <0,1e9> + gl = .0003 (S/cm2) <0,1e9> + el = -54.3 (mV) +} + +STATE { + m h n +} + +ASSIGNED { + v (mV) + celsius (degC) + ena (mV) + ek (mV) + gna (S/cm2) + gk (S/cm2) + ina (mA/cm2) + ik (mA/cm2) + il (mA/cm2) + minf hinf ninf + mtau (ms) htau (ms) ntau (ms) +} + +? currents +BREAKPOINT { + SOLVE states METHOD cnexp + gna = gnabar*m*m*m*h + ina = gna*(v - ena) + gk = gkbar*n*n*n*n + ik = gk*(v - ek) + il = gl*(v - el) +} + +INITIAL { + rates(v) + m = minf + h = hinf + n = ninf +} + +? states +DERIVATIVE states { + rates(v) + m' = (minf-m)/mtau + h' = (hinf-h)/htau + n' = (ninf-n)/ntau +} + +? rates +PROCEDURE rates(v(mV)) { :Computes rate and other constants at current v. + :Call once from HOC to initialize inf at resting v. + LOCAL alpha, beta, sum, q10 + + UNITSOFF + q10 = 3^((celsius - 6.3)/10) + :"m" sodium activation system + alpha = .1 * vtrap(-(v+40),10) + beta = 4 * exp(-(v+65)/18) + sum = alpha + beta + mtau = 1/(q10*sum) + minf = alpha/sum + :"h" sodium inactivation system + alpha = .07 * exp(-(v+65)/20) + beta = 1 / (exp(-(v+35)/10) + 1) + sum = alpha + beta + htau = 1/(q10*sum) + hinf = alpha/sum + :"n" potassium activation system + alpha = .01*vtrap(-(v+55),10) + beta = .125*exp(-(v+65)/80) + sum = alpha + beta + ntau = 1/(q10*sum) + ninf = alpha/sum +} + +FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. + if (fabs(x/y) < 1e-6) { + vtrap = y*(1 - x/y/2) + }else{ + vtrap = x/(exp(x/y) - 1) + } +} + +UNITSON diff --git a/share/example/passive.mod b/share/example/passive.mod new file mode 100644 index 0000000000..bc323df2fc --- /dev/null +++ b/share/example/passive.mod @@ -0,0 +1,24 @@ +TITLE passive membrane channel + +UNITS { + (mV) = (millivolt) + (mA) = (milliamp) + (S) = (siemens) +} + +NEURON { + SUFFIX pas + NONSPECIFIC_CURRENT i + RANGE g, e +} + +PARAMETER { + g = .001 (S/cm2) <0,1e9> + e = -70 (mV) +} + +ASSIGNED {v (mV) i (mA/cm2)} + +BREAKPOINT { + i = g*(v - e) +} diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index c6158e1ddd..520169d4cb 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -22,3 +22,7 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; */ const std::vector<std::string> nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; + + /// Path of nmodl examples +const std::vector<std::string> nmodl::Config::NMODL_EXAMPLE_PATH = + {"@CMAKE_INSTALL_PREFIX@/share/example", "@PROJECT_SOURCE_DIR@/share/example"}; \ No newline at end of file diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index e6d4e9b9cb..6934550fd0 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -19,6 +19,8 @@ #include <string> #include <vector> +#include "utils/common_utils.hpp" + namespace nmodl { /** @@ -58,4 +60,22 @@ struct NrnUnitsLib { } }; +/** + * \brief Information about NMODL configurations (from share directory) + */ +struct Config { + /// directories with nmodl examples + static const std::vector<std::string> NMODL_EXAMPLE_PATH; + + /// Return path nmodl example directory + static std::string get_nmodl_example_dir() { + for (const auto& path: NMODL_EXAMPLE_PATH) { + if (nmodl::utils::is_dir_exist(path)) { + return path; + } + } + throw std::runtime_error("Could not found nmodl example directory"); + } +}; + } // namespace nmodl diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 7b9dc69ec8..a0c4ed2393 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -18,4 +18,3 @@ target_link_libraries(units_parser util visitor lexer) install(TARGETS nmodl_parser DESTINATION bin/parser) install(TARGETS c_parser DESTINATION bin/parser) install(TARGETS units_parser DESTINATION bin/parser) -install(FILES ../../share/nrnunits.lib DESTINATION share) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index a1ef02d0ca..a31f1b1d1f 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -43,6 +43,7 @@ add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} ${CMAKE_BINARY_DIR}/nmodl DEPENDS ${NMODL_PYTHON_FILES_IN} $<TARGET_FILE:_nmodl> COMMENT "-- COPYING NMODL PYTHON FILES --") + # ============================================================================= # Install python binding components # ============================================================================= diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 7bda1dbb16..4a48d7dcf4 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -36,6 +36,10 @@ namespace nmodl { /** \brief docstring of Python exposed API */ namespace docstring { +static const char* get_nmodl_example_dir = R"( + Get directory with NMODL examples +)"; + static const char* driver = R"( This is the NmodlDriver class documentation )"; @@ -144,6 +148,10 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; m_nmodl.attr("__version__") = nmodl::Version::NMODL_VERSION; + m_nmodl.def("example_dir", + &nmodl::Config::get_nmodl_example_dir, + nmodl::docstring::get_nmodl_example_dir); + py::class_<nmodl::PyNmodlDriver> nmodl_driver(m_nmodl, "NmodlDriver", nmodl::docstring::driver); nmodl_driver.def(py::init<>()) .def("parse_string", diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index f6ccf89c61..0c5fa5ba3a 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -48,6 +48,9 @@ T remove_extension(T const& filename) { /// Given directory path, create sub-directories bool make_path(const std::string& path); +/// Check if directory with given path exist +bool is_dir_exist(const std::string& path); + /** @} */ // end of utils } // namespace utils diff --git a/test/nmodl/transpiler/input/channel.mod b/test/nmodl/transpiler/input/channel.mod deleted file mode 100644 index 27ddc495a1..0000000000 --- a/test/nmodl/transpiler/input/channel.mod +++ /dev/null @@ -1,119 +0,0 @@ -:Reference :Colbert and Pan 2002 -:comment: took the NaTa and shifted both activation/inactivation by 6 mv - -NEURON { - SUFFIX NaTs2_t - USEION na READ ena WRITE ina - RANGE gNaTs2_tbar -} - -UNITS { - (S) = (siemens) - (mV) = (millivolt) - (mA) = (milliamp) -} - -PARAMETER { - gNaTs2_tbar = 0.00001 (S/cm2) -} - -ASSIGNED { - v (mV) - ena (mV) -} - -STATE { - m - h -} - -BREAKPOINT { - LOCAL gNaTs2_t, ina - CONDUCTANCE gNaTs2_t USEION na - SOLVE states METHOD cnexp - gNaTs2_t = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t*(v-ena) -} - -INITIAL{ - LOCAL mAlpha, mBeta, mInf, hAlpha, hBeta, hInf, lv - - lv = v - - if(lv == -32){ - lv = lv+0.0001 - } - - mAlpha = mAlphaf(lv) - mBeta = mBetaf(lv) - mInf = mAlpha/(mAlpha+mBeta) - m = mInf - - if(lv == -60){ - lv = lv+0.0001 - } - - hAlpha = hAlphaf(lv) - hBeta = hBetaf(lv) - hInf = hAlpha/(hAlpha+hBeta) - h = hInf - - v = lv -} - - - -DERIVATIVE states { - LOCAL mAlpha, mBeta, mInf, mTau, hAlpha, hBeta, hInf, hTau, lv, qt - - :qt = 2.3^((34-21)/10) - qt = 2.952882641412121 - - lv = v - - if(lv == -32){ - lv = lv+0.0001 - } - - mAlpha = mAlphaf(lv) - mBeta = mBetaf(lv) - mInf = mAlpha/(mAlpha+mBeta) - mTau = (1/(mAlpha+mBeta))/qt - m' = (mInf-m)/mTau - - if(lv == -60){ - lv = lv+0.0001 - } - - hAlpha = hAlphaf(lv) - hBeta = hBetaf(lv) - hInf = hAlpha/(hAlpha+hBeta) - hTau = (1/(hAlpha+hBeta))/qt - h' = (hInf-h)/hTau - - v = lv -} - -FUNCTION mAlphaf(v) { - LOCAL a, b - a = 2 + 3.4 - b = a * a - - if (a == 12) { - a = a + 0.1 - } - - mAlphaf = (0.182 * (v- -32))/(1-(exp(-(v- -32)/6))) -} - -FUNCTION mBetaf(v) { - mBetaf = (0.124 * (-v -32))/(1-(exp(-(-v -32)/6))) -} - -FUNCTION hAlphaf(v) { - hAlphaf = (-0.015 * (v- -60))/(1-(exp((v- -60)/6))) -} - -FUNCTION hBetaf(v) { - hBetaf = (-0.015 * (-v -60))/(1-(exp((-v -60)/6))) -} diff --git a/test/nmodl/transpiler/input/synapse.mod b/test/nmodl/transpiler/input/synapse.mod deleted file mode 100644 index ef40f2ccb5..0000000000 --- a/test/nmodl/transpiler/input/synapse.mod +++ /dev/null @@ -1,321 +0,0 @@ -COMMENT -/** - * @file ProbAMPANMDA_EMS.mod - * @brief - * @author king, muller, reimann, ramaswamy - * @date 2011-08-17 - * @remark Copyright © BBP/EPFL 2005-2011; All rights reserved. Do not distribute without further notice. - */ -ENDCOMMENT - -:TITLE Probabilistic AMPA and NMDA receptor with presynaptic short-term plasticity - - -COMMENT -AMPA and NMDA receptor conductance using a dual-exponential profile -presynaptic short-term plasticity as in Fuhrmann et al. 2002 - -_EMS (Eilif Michael Srikanth) -Modification of ProbAMPANMDA: 2-State model by Eilif Muller, Michael Reimann, Srikanth Ramaswamy, Blue Brain Project, August 2011 -This new model was motivated by the following constraints: - -1) No consumption on failure. -2) No release just after release until recovery. -3) Same ensemble averaged trace as deterministic/canonical Tsodyks-Markram - using same parameters determined from experiment. -4) Same quantal size as present production probabilistic model. - -To satisfy these constaints, the synapse is implemented as a -uni-vesicular (generalization to multi-vesicular should be -straight-forward) 2-state Markov process. The states are -{1=recovered, 0=unrecovered}. - -For a pre-synaptic spike or external spontaneous release trigger -event, the synapse will only release if it is in the recovered state, -and with probability u (which follows facilitation dynamics). If it -releases, it will transition to the unrecovered state. Recovery is as -a Poisson process with rate 1/Dep. - -This model satisys all of (1)-(4). - -ENDCOMMENT - -NEURON { - THREADSAFE - POINT_PROCESS ProbAMPANMDA_EMS - RANGE tau_r_AMPA, tau_d_AMPA, tau_r_NMDA, tau_d_NMDA - RANGE Use, u, Dep, Fac, u0, mg, Rstate, tsyn_fac - RANGE e, NMDA_ratio - RANGE A_AMPA_step, B_AMPA_step, A_NMDA_step, B_NMDA_step - NONSPECIFIC_CURRENT i - BBCOREPOINTER rng - RANGE synapseID, verboseLevel -} - -PARAMETER { - - - tau_r_AMPA = 0.2 (ms) : dual-exponential conductance profile - tau_d_AMPA = 1.7 (ms) : IMPORTANT: tau_r < tau_d - tau_r_NMDA = 0.29 (ms) : dual-exponential conductance profile - tau_d_NMDA = 43 (ms) : IMPORTANT: tau_r < tau_d - Use = 1.0 (1) : Utilization of synaptic efficacy (just initial values! Use, Dep and Fac are overwritten by BlueBuilder assigned values) - Dep = 100 (ms) : relaxation time constant from depression - Fac = 10 (ms) : relaxation time constant from facilitation - e = 0 (mV) : AMPA and NMDA reversal potential - mg = 1 (mM) : initial concentration of mg2+ - gmax = .001 (uS) : weight conversion factor (from nS to uS) - u0 = 0 :initial value of u, which is the running value of release probability - synapseID = 0 - verboseLevel = 0 - NMDA_ratio = 0.71 (1) : The ratio of NMDA to AMPA -} - -COMMENT -The Verbatim block is needed to generate random nos. from a uniform distribution between 0 and 1 -for comparison with Pr to decide whether to activate the synapse or not -ENDCOMMENT - -VERBATIM -#include "nrnran123.h" -ENDVERBATIM - - -ASSIGNED { - - v (mV) - i_AMPA (nA) - i_NMDA (nA) - g_NMDA (uS) - factor_AMPA - factor_NMDA - A_AMPA_step - B_AMPA_step - A_NMDA_step - B_NMDA_step - rng - mggate - - : Recording these three, you can observe full state of model - : tsyn_fac gives you presynaptic times, Rstate gives you - : state transitions, - : u gives you the "release probability" at transitions - : (attention: u is event based based, so only valid at incoming events) - Rstate (1) : recovered state {0=unrecovered, 1=recovered} - tsyn_fac (ms) : the time of the last spike - u (1) : running release probability - -} - -STATE { - - A_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_r_AMPA - B_AMPA : AMPA state variable to construct the dual-exponential profile - decays with conductance tau_d_AMPA - A_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_r_NMDA - B_NMDA : NMDA state variable to construct the dual-exponential profile - decays with conductance tau_d_NMDA -} - -INITIAL{ - - LOCAL tp_AMPA, tp_NMDA - - VERBATIM - if (_p_rng) - { - nrnran123_setseq((nrnran123_State*)_p_rng, 0, 0); - } - ENDVERBATIM - - Rstate=1 - tsyn_fac=0 - u=u0 - - A_AMPA = 0 - B_AMPA = 0 - - A_NMDA = 0 - B_NMDA = 0 - - tp_AMPA = (tau_r_AMPA*tau_d_AMPA)/(tau_d_AMPA-tau_r_AMPA)*log(tau_d_AMPA/tau_r_AMPA) :time to peak of the conductance - tp_NMDA = (tau_r_NMDA*tau_d_NMDA)/(tau_d_NMDA-tau_r_NMDA)*log(tau_d_NMDA/tau_r_NMDA) :time to peak of the conductance - - factor_AMPA = -exp(-tp_AMPA/tau_r_AMPA)+exp(-tp_AMPA/tau_d_AMPA) :AMPA Normalization factor - so that when t = tp_AMPA, gsyn = gpeak - factor_AMPA = 1/factor_AMPA - - factor_NMDA = -exp(-tp_NMDA/tau_r_NMDA)+exp(-tp_NMDA/tau_d_NMDA) :NMDA Normalization factor - so that when t = tp_NMDA, gsyn = gpeak - factor_NMDA = 1/factor_NMDA - - A_AMPA_step = exp(dt*(( - 1.0 ) / tau_r_AMPA)) - B_AMPA_step = exp(dt*(( - 1.0 ) / tau_d_AMPA)) - A_NMDA_step = exp(dt*(( - 1.0 ) / tau_r_NMDA)) - B_NMDA_step = exp(dt*(( - 1.0 ) / tau_d_NMDA)) -} - -BREAKPOINT { - - LOCAL mggate, g_AMPA, g_NMDA, g, i_AMPA, i_NMDA, vv, i, vve - CONDUCTANCE g_AMPA - CONDUCTANCE g_NMDA - SOLVE state - - vv = v - - :mggate = 1 / (1 + exp(0.062 (/mV) * -(vv)) * (mg / 3.57 (mM))) :mggate kinetics - Jahr & Stevens 1990 - mggate = 1 / (1 + exp(0.062 * -(vv)) * (mg / 3.57 )) :mggate kinetics - Jahr & Stevens 1990 - g_AMPA = gmax*(B_AMPA-A_AMPA) :compute time varying conductance as the difference of state variables B_AMPA and A_AMPA - g_NMDA = gmax*(B_NMDA-A_NMDA) * mggate :compute time varying conductance as the difference of state variables B_NMDA and A_NMDA and mggate kinetics - g = g_AMPA + g_NMDA - vve = (vv-e) - i_AMPA = g_AMPA*vve :compute the AMPA driving force based on the time varying conductance, membrane potential, and AMPA reversal - i_NMDA = g_NMDA*vve :compute the NMDA driving force based on the time varying conductance, membrane potential, and NMDA reversal - i = i_AMPA + i_NMDA -} - -PROCEDURE state() { - A_AMPA = A_AMPA*A_AMPA_step - B_AMPA = B_AMPA*B_AMPA_step - A_NMDA = A_NMDA*A_NMDA_step - B_NMDA = B_NMDA*B_NMDA_step -} - - -:NET_RECEIVE (weight,weight_AMPA, weight_NMDA, Psurv, tsyn (ms)){ -NET_RECEIVE (weight,weight_AMPA, weight_NMDA, Psurv, tsyn){ - LOCAL result - weight_AMPA = weight - weight_NMDA = weight * NMDA_ratio - : Locals: - : Psurv - survival probability of unrecovered state - : tsyn - time since last surival evaluation. - - INITIAL{ - tsyn=t - } - - : calc u at event- - if (Fac > 0) { - u = u*exp(-(t - tsyn_fac)/Fac) :update facilitation variable if Fac>0 Eq. 2 in Fuhrmann et al. - } else { - u = Use - } - if(Fac > 0){ - u = u + Use*(1-u) :update facilitation variable if Fac>0 Eq. 2 in Fuhrmann et al. - } - - : tsyn_fac knows about all spikes, not only those that released - : i.e. each spike can increase the u, regardless of recovered state. - tsyn_fac = t - - : recovery - - if (Rstate == 0) { - : probability of survival of unrecovered state based on Poisson recovery with rate 1/tau - Psurv = exp(-(t-tsyn)/Dep) - result = urand() - if (result>Psurv) { - Rstate = 1 : recover - - if( verboseLevel > 0 ) { - printf( "Recovered! %f at time %g: Psurv = %g, urand=%g\n", synapseID, t, Psurv, result ) - } - - } - else { - : survival must now be from this interval - tsyn = t - if( verboseLevel > 0 ) { - printf( "Failed to recover! %f at time %g: Psurv = %g, urand=%g\n", synapseID, t, Psurv, result ) - } - } - } - - if (Rstate == 1) { - result = urand() - if (result<u) { - : release! - tsyn = t - Rstate = 0 - A_AMPA = A_AMPA + weight_AMPA*factor_AMPA - B_AMPA = B_AMPA + weight_AMPA*factor_AMPA - A_NMDA = A_NMDA + weight_NMDA*factor_NMDA - B_NMDA = B_NMDA + weight_NMDA*factor_NMDA - - if( verboseLevel > 0 ) { - printf( "Release! %f at time %g: vals %g %g %g %g\n", synapseID, t, A_AMPA, weight_AMPA, factor_AMPA, weight ) - } - - } - else { - if( verboseLevel > 0 ) { - printf("Failure! %f at time %g: urand = %g\n", synapseID, t, result ) - } - - } - - } - -} - -PROCEDURE setRNG() { -VERBATIM - { -#if !NRNBBCORE - nrnran123_State** pv = (nrnran123_State**)(&_p_rng); - if (*pv) { - nrnran123_deletestream(*pv); - *pv = (nrnran123_State*)0; - } - if (ifarg(2)) { - *pv = nrnran123_newstream((uint32_t)*getarg(1), (uint32_t)*getarg(2)); - } -#endif - } -ENDVERBATIM -} - -FUNCTION urand() { -VERBATIM - double value; - if (_p_rng) { - /* - :Supports separate independent but reproducible streams for - : each instance. - */ - value = nrnran123_negexp((nrnran123_State*)_p_rng); - //printf("random stream for this simulation = %lf\n",value); - return value; - }else{ - value = 0.0; -// assert(0); - } - _lurand = value; -ENDVERBATIM -} - -VERBATIM -static void bbcore_write(double* x, int* d, int* xx, int* offset, _threadargsproto_) { - if (d) { - uint32_t* di = ((uint32_t*)d) + *offset; - nrnran123_State** pv = (nrnran123_State**)(&_p_rng); - nrnran123_getids(*pv, di, di+1); -//printf("ProbAMPANMDA_EMS bbcore_write %d %d\n", di[0], di[1]); - } - *offset += 2; -} -static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { - assert(!_p_rng); - uint32_t* di = ((uint32_t*)d) + *offset; - if (di[0] != 0 || di[1] != 0) - { - nrnran123_State** pv = (nrnran123_State**)(&_p_rng); - *pv = nrnran123_newstream(di[0], di[1]); - } -//printf("ProbAMPANMDA_EMS bbcore_read %d %d\n", di[0], di[1]); - *offset += 2; -} -ENDVERBATIM - - -FUNCTION toggleVerbose() { - verboseLevel = 1-verboseLevel -} diff --git a/test/nmodl/transpiler/pybind/test_visitor.py b/test/nmodl/transpiler/pybind/test_visitor.py index 4b8858083f..4ccdf38a77 100644 --- a/test/nmodl/transpiler/pybind/test_visitor.py +++ b/test/nmodl/transpiler/pybind/test_visitor.py @@ -48,6 +48,7 @@ def test_json_visitor(ch_ast): '"name":"PrimeName","nmodl":"m\'"}') assert result_json == expected_json + def test_custom_visitor(ch_ast): class StateVisitor(visitor.AstVisitor): From 0bae75bc03a416704eed35e5f869ff50c2b27ebd Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Mon, 6 May 2019 23:22:22 +0200 Subject: [PATCH 211/871] Use value initialization with std::array (compatibility with gcc < 6.0) (BlueBrain/nmodl#182) - initializer list is not compatible with older version of gcc - also see xelatihy/yocto-gl/issues/14 NMODL Repo SHA: BlueBrain/nmodl@e87c3b21286fa0d5808530a4202ab4199ad478ca --- src/nmodl/units/units.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index ae58d2d7bb..8625295121 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -78,7 +78,7 @@ class Unit { double unit_factor = 1.0; /// Array of MAX_DIMS size that keeps the Unit's dimensions - std::array<int, MAX_DIMS> unit_dimensions = {0}; + std::array<int, MAX_DIMS> unit_dimensions{{0}}; /// Name of the Unit std::string unit_name; From aac30af8d795487f5cd08c0a5a91b8464bcc31b7 Mon Sep 17 00:00:00 2001 From: Jorge Blanco Alonso <41900536+jorblancoa@users.noreply.github.com> Date: Tue, 7 May 2019 12:57:48 +0200 Subject: [PATCH 212/871] Add C++ API doxygen documentation (BlueBrain/nmodl#188) - Doxygen generated from sphinx - nbsphinx>=0.3.2 to avoid conflicts with sphinx>=2.0 NMODL Repo SHA: BlueBrain/nmodl@8f912e5d3e95d5fe08aa229eb513a54634fe23f7 --- docs/nmodl/transpiler/Doxyfile.in | 2 +- docs/nmodl/transpiler/conf.py | 7 +++++++ docs/nmodl/transpiler/doxygen.rst | 6 ++++++ docs/nmodl/transpiler/index.rst | 1 + setup.py | 2 +- 5 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 docs/nmodl/transpiler/doxygen.rst diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index 043d580910..6830bccff9 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -1112,7 +1112,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = html +HTML_OUTPUT = _static # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index a3d9284c07..403536277a 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -18,6 +18,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os +import subprocess import sys import textwrap @@ -25,6 +26,12 @@ import nmodl # isort:skip +# Run doxygen +subprocess.call('doxygen Doxyfile', shell=True) + +# Copy doxygen output to the sphinx output directory +html_static_path = ['_static'] + # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. diff --git a/docs/nmodl/transpiler/doxygen.rst b/docs/nmodl/transpiler/doxygen.rst new file mode 100644 index 0000000000..ac91f6127b --- /dev/null +++ b/docs/nmodl/transpiler/doxygen.rst @@ -0,0 +1,6 @@ +C++ API +=========== + +Link to doxygen `C++ API`_ + +.. _C++ API: _static/index.html diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index c06def12d1..7bd03dd1a6 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -37,6 +37,7 @@ About NMODL :caption: Reference: modules.rst + doxygen.rst Indices and tables ================== diff --git a/setup.py b/setup.py index 7c1125e465..78a8d35767 100644 --- a/setup.py +++ b/setup.py @@ -162,7 +162,7 @@ def run(self): buildhtml=get_sphinx_command, ), zip_safe=False, - setup_requires=["nbsphinx", "m2r", "sphinx-rtd-theme", "sphinx>=2.0"] + setup_requires=["nbsphinx>=0.3.2", "m2r", "sphinx-rtd-theme", "sphinx>=2.0"] + install_requirements, install_requires=install_requirements, tests_require=["pytest>=3.7.2"], From 71721cc01ff59ac1383b8ef1abbe9a97c4f03ee4 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 7 May 2019 16:58:20 +0200 Subject: [PATCH 213/871] Add travis and azure CI configurations (BlueBrain/nmodl#183) * On travis - build with ubuntu 16.04 with GCC 5.4 - build on mac os 10.13 with AppleClang 9.1 * On Azure - build with ubuntu 16.04 with GCC 8.1 - build on mac os 10.14 with AppleClang 10.2.1 * Address badges to README NMODL Repo SHA: BlueBrain/nmodl@2645f12d1f726c3ce4a299251c180115b9eaeccc --- .travis.yml | 89 +++++++++++++++++++++++++++++++++++++++++ README.md | 1 + src/nmodl/nmodl/LICENSE | 3 -- 3 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000000..c95a859785 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,89 @@ +#============================================================================= +# Travis NMODL settings +#============================================================================= + +#============================================================================= +# Environment +#============================================================================= +# Use new Travis infrastructure (Docker can't sudo yet) +sudo: false + +#============================================================================= +# Build matrix +#============================================================================= +matrix: + fast_finish: true + include: + - language: cpp + os: linux + dist: xenial + env: + - MATRIX_EVAL="CXX=g++" + - PYTHON_VERSION=3.6.7 + - language: cpp + os: osx + env: + - MATRIX_EVAL="CXX=c++" + +#============================================================================= +# Common Packages +#============================================================================= +addons: + # for Linux builds, we use APT + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - flex + - bison + - cmake + - python3-dev + - python3-pip + # for Mac builds, we use Homebrew + homebrew: + packages: + - flex + - bison + - cmake + - python@3 + +#============================================================================= +# Install dependencies / setup Spack +#============================================================================= +before_install: + # brew installed flex and bison is not in $PATH + - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; + else + pyenv global $PYTHON_VERSION; + ls /usr/bin/; + fi + - eval "${MATRIX_EVAL}" + +#============================================================================= +# Install NMODL dependencies +#============================================================================= +install: + - echo "------- Install Dependencies -------" + - pip3 install -U pip setuptools + - pip3 install Jinja2 PyYAML pytest sympy --user + +#============================================================================= +# Build, test and install +#============================================================================= +script: + - echo "------- Build, Test and Install -------" + - mkdir build && cd build + - cmake .. -DPYTHON_EXECUTABLE=`which python3` -DCMAKE_INSTALL_PREFIX=$HOME/nmodl + - make -j 2 + - make test + - make install + +#============================================================================= +# Notifications +#============================================================================= +notifications: + email: + recipients: pramod.s.kumbhar@gmail.com + on_success: change + on_failure: always diff --git a/README.md b/README.md index 89a66cea90..d87667df78 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ ## The NMODL Framework +[![Build Status](https://travis-ci.org/BlueBrain/nmodl.svg?branch=master)](https://travis-ci.org/BlueBrain/nmodl) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L**anguage ([NMODL](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html)). It is designed with modern compiler and code generation techniques to: diff --git a/src/nmodl/nmodl/LICENSE b/src/nmodl/nmodl/LICENSE index a79775abab..d39a0878fa 100644 --- a/src/nmodl/nmodl/LICENSE +++ b/src/nmodl/nmodl/LICENSE @@ -7,9 +7,6 @@ * dependencies. See file LGPL.txt for the full license. Examples and external * dependencies are either LGPL or BSD-licensed. * -* This version is not open sourced yet and provided for collaborators. This -* does not allow redistribution of the source without prior consent. - * THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL From 6df826b21854434253a1b169f6b8f1b7b0f44e2b Mon Sep 17 00:00:00 2001 From: Jorge Blanco Alonso <41900536+jorblancoa@users.noreply.github.com> Date: Tue, 7 May 2019 17:37:58 +0200 Subject: [PATCH 214/871] Improve documentation (BlueBrain/nmodl#189) - Remove some documentation generation warnings - Put doxygen documentation on a separate folder - Fix README.md link - Exclude notebook README from sphinx NMODL Repo SHA: BlueBrain/nmodl@f37828549d40c71720e4e22e25c3b5b2d18ffb23 --- README.md | 2 +- docs/nmodl/transpiler/Doxyfile.in | 4 ++-- docs/nmodl/transpiler/conf.py | 13 ++++++++----- docs/nmodl/transpiler/contents/visitors.rst | 12 ++++++------ docs/nmodl/transpiler/doxygen.rst | 2 +- nmodl/ode.py | 6 +++--- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index d87667df78..af03258919 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ PARAMETER { ### High Level Analysis and Code Generation -The NMODL Framework provides rich model introspection and analysis capabilities using [various visitors TODO](). Here is an example of theoretical performance characterisation of channels and synapses from rat neocortical column microcircuit [published in 2015](https://www.cell.com/abstract/S0092-8674(15)01191-5): +The NMODL Framework provides rich model introspection and analysis capabilities using [various visitors TODO](). Here is an example of theoretical performance characterisation of channels and synapses from rat neocortical column microcircuit [published in 2015](https://www.cell.com/abstract/S0092-8674%2815%2901191-5): ![alt text](docs/images/nmodl-perf-stats.png "Example of performance characterisation") diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index 6830bccff9..e6b63448f6 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -58,7 +58,7 @@ PROJECT_LOGO = logo.png # entered, it will be relative to the location where doxygen was started. If # left blank the current directory will be used. -OUTPUT_DIRECTORY = +OUTPUT_DIRECTORY = sphinx_doxygen # If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- # directories (in 2 levels) under the output directory of each output format and @@ -1112,7 +1112,7 @@ GENERATE_HTML = YES # The default directory is: html. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_OUTPUT = _static +HTML_OUTPUT = doxygen # The HTML_FILE_EXTENSION tag can be used to specify the file extension for each # generated HTML page (for example: .htm, .php, .asp). diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index 403536277a..823ecb4e98 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -29,9 +29,6 @@ # Run doxygen subprocess.call('doxygen Doxyfile', shell=True) -# Copy doxygen output to the sphinx output directory -html_static_path = ['_static'] - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -106,7 +103,7 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints"] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "**.ipynb_checkpoints", "**.md"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = "sphinx" @@ -131,7 +128,13 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["_static"] +#html_static_path = ["_static"] + +# A list of paths that contain extra files not directly related to the +# documentation, such as robots.txt or .htaccess. Relative paths are taken +# as relative to the configuration directory. They are copied to the output +# directory. They will overwrite any existing file of the same name. +html_extra_path = ["sphinx_doxygen"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index fe6d38fcf3..dfc7d1a2a4 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -1,11 +1,11 @@ Visitors -#### +######### One of the strengths of NMODL python interface is access to inbuilt Visitors. One can perform different queries and analysis on AST using different visitors. Let us start with examples of inbuilt visitors. Parsing Model and constructing AST -=========== +=================================== Once the NMODL is setup properly we should be able to import nmodl module and create the channel: @@ -63,11 +63,11 @@ If we simply print AST object, we can see JSON representation: Querying AST objects with Visitors -=========== +=================================== Lookup Visitor ------------ +--------------- As name suggest, lookup visitor allows to search different NMODL constructs in the AST. The `visitor` module provides access to inbuilt visitors. In order to use this visitor, we create an object of :class:`nmodl.visitor.AstLookupVisitor`: @@ -86,7 +86,7 @@ Assuming we have created :class:`nmodl.ast` object (as shown here), we can searc Symbol Table Visitor ----------- +-------------------- Symbol table visitor is used to find out all variables and their usage in mod file. To use this, just create a visitor object as: @@ -107,7 +107,7 @@ Now we can query for variables in the symbol table based on name of variable: Custom AST Visitor ----------- +------------------- If predefined visitors are limited, we can implement new visitor using :class:`nmodl.visitor.AstVisitor` interface. Let us say we want to implement a visitor that prints every floating point numbers in MOD file. Here is how it can be done: diff --git a/docs/nmodl/transpiler/doxygen.rst b/docs/nmodl/transpiler/doxygen.rst index ac91f6127b..19c77e0732 100644 --- a/docs/nmodl/transpiler/doxygen.rst +++ b/docs/nmodl/transpiler/doxygen.rst @@ -3,4 +3,4 @@ C++ API Link to doxygen `C++ API`_ -.. _C++ API: _static/index.html +.. _C++ API: doxygen/index.html diff --git a/nmodl/ode.py b/nmodl/ode.py index 431444afd6..7a3db43e1f 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -288,9 +288,9 @@ def integrate2c(diff_string, dt_var, vars, use_pade_approx=False): t_var: name of time variable t in NEURON dt_var: name of timestep variable dt in NEURON vars: set of variables used in expression, e.g. {"a", "b"} - use_pade_approx: if False: return exact solution - if True: return (1,1) Pade approx to solution - correct to second order in dt_var + use_pade_approx: if False, return exact solution + if True, return (1,1) Pade approx to solution + correct to second order in dt_var Returns: string containing analytic integral of derivative as C code From b91812994f6ab0c36d8e99af520062e8c033a4ea Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar <pramod.kumbhar@epfl.ch> Date: Tue, 7 May 2019 18:59:47 +0200 Subject: [PATCH 215/871] Added AST visualization functionality based on d3 (BlueBrain/nmodl#178) - nmodl.ast now has ast_view function that visualize in memory AST representation using d3.js based radial tree - necessary html, css and js files are added - get_nmodl_example_dir is removed and taken case by python modules Todo: utility tools should be outside the main repo (see BlueBrain/nmodl#190) NMODL Repo SHA: BlueBrain/nmodl@42058f55f7c4060bb1a2f716d2bbb006fa52a9a3 --- cmake/nmodl/CMakeLists.txt | 2 + nmodl/ast.py | 32 ++++ nmodl/config.py.in | 2 + nmodl/dsl.py | 12 ++ share/viz/css/tree.css | 51 ++++++ share/viz/index.html | 18 ++ share/viz/js/d3.min.js | 5 + share/viz/js/tree.js | 289 ++++++++++++++++++++++++++++++++ src/nmodl/config/config.cpp.in | 4 - src/nmodl/config/config.h | 18 -- src/nmodl/pybind/CMakeLists.txt | 4 +- src/nmodl/pybind/pynmodl.cpp | 8 - 12 files changed, 413 insertions(+), 32 deletions(-) create mode 100644 nmodl/config.py.in create mode 100644 share/viz/css/tree.css create mode 100644 share/viz/index.html create mode 100644 share/viz/js/d3.min.js create mode 100644 share/viz/js/tree.js diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4e98f9e9a4..54e1742787 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -110,6 +110,8 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) # generate file with version number from git and nrnunits.lib file path configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/config.cpp @ONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nmodl/config.py.in ${PROJECT_SOURCE_DIR}/nmodl/config.py + @ONLY) # generate Doxyfile with correct source paths configure_file(${PROJECT_SOURCE_DIR}/docs/Doxyfile.in ${PROJECT_SOURCE_DIR}/docs/Doxyfile) diff --git a/nmodl/ast.py b/nmodl/ast.py index 2002bd825f..811ad9675c 100644 --- a/nmodl/ast.py +++ b/nmodl/ast.py @@ -1 +1,33 @@ from ._nmodl.ast import * # noqa +from .config import * + +def ast_view(nmodl_ast): + """Visualize given NMODL AST by converting to JSON form""" + from ._nmodl import to_json + from distutils.dir_util import copy_tree + import getpass + import json + import os + import tempfile + import webbrowser + + installed_viz_tool = os.path.join(PROJECT_INSTALL_DIR, "share", "viz") + if os.path.exists(installed_viz_tool): + viz_tool_dir = installed_viz_tool + else: + viz_tool_dir = os.path.join(PROJECT_SOURCE_DIR, "share", "viz") + + work_dir = os.path.join(tempfile.gettempdir(), getpass.getuser(), "nmodl") + + # first copy necessary files to temp work directory + copy_tree(viz_tool_dir, work_dir) + + # prepare json data + with open(os.path.join(work_dir, 'ast.js'), 'w') as outfile: + json_data = json.loads(to_json(nmodl_ast, True, True, True)) + outfile.write('var astRoot = %s;' % json.dumps(json_data)) + + # open browser with ast + url = 'file://' + os.path.join(work_dir, "index.html") + webbrowser.open(url, new=1, autoraise=True) + diff --git a/nmodl/config.py.in b/nmodl/config.py.in new file mode 100644 index 0000000000..271d07d06c --- /dev/null +++ b/nmodl/config.py.in @@ -0,0 +1,2 @@ +PROJECT_SOURCE_DIR="@PROJECT_SOURCE_DIR@" +PROJECT_INSTALL_DIR="@CMAKE_INSTALL_PREFIX@" diff --git a/nmodl/dsl.py b/nmodl/dsl.py index e99f2af1d0..3e0414e612 100644 --- a/nmodl/dsl.py +++ b/nmodl/dsl.py @@ -1 +1,13 @@ from ._nmodl import * +from .config import * + + +def example_dir(): + import os + """Return directory containing NMODL examples""" + installed_example = os.path.join(PROJECT_INSTALL_DIR, "share", "example") + if os.path.exists(installed_example): + return installed_example + else: + return os.path.join(PROJECT_SOURCE_DIR, "share", "example") + diff --git a/share/viz/css/tree.css b/share/viz/css/tree.css new file mode 100644 index 0000000000..272ef1db6b --- /dev/null +++ b/share/viz/css/tree.css @@ -0,0 +1,51 @@ +#ast { + position: fixed; + left: 0px; + right: 0px; + top: 0px; + bottom: 0px; +} + +.node { + cursor: pointer; +} + +.node circle { + fill: #ffc8c8 !important; + stroke: #c57bc4; + stroke-width: 2.5px; +} + +.node text { + font-size: 12px !important; + font-family: sans-serif; + fill: #1f1f92; +} + +.link { + fill: none; + stroke: #DCA5DB; + stroke-width: 3px; +} + +.templink { + fill: none; + stroke: red; + stroke-width: 3px; +} + +.dots { + cursor: pointer; +} + +.tooltip { + background: #23298aeb; + white-space: pre; + padding: 10px; + border-radius: 3px; + color: white; + font-size: 13px; + font-family: "Monaco"; + position: "absolute"; + z-index: 10; +} diff --git a/share/viz/index.html b/share/viz/index.html new file mode 100644 index 0000000000..0ce10d41fb --- /dev/null +++ b/share/viz/index.html @@ -0,0 +1,18 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="UTF-8"> + <title>NMODL AST Visualization + + + + +
+ + + + + + + diff --git a/share/viz/js/d3.min.js b/share/viz/js/d3.min.js new file mode 100644 index 0000000000..34d5513ebf --- /dev/null +++ b/share/viz/js/d3.min.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:0/0}function r(n){return null===n?0/0:+n}function u(n){return!isNaN(n)}function i(n){return{left:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)<0?r=i+1:u=i}return r},right:function(t,e,r,u){for(arguments.length<3&&(r=0),arguments.length<4&&(u=t.length);u>r;){var i=r+u>>>1;n(t[i],e)>0?u=i:r=i+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function c(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function l(){this._=Object.create(null)}function s(n){return(n+="")===pa||n[0]===va?va+n:n}function f(n){return(n+="")[0]===va?n.slice(1):n}function h(n){return s(n)in this._}function g(n){return(n=s(n))in this._&&delete this._[n]}function p(){var n=[];for(var t in this._)n.push(f(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function m(){this._=Object.create(null)}function y(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=da.length;r>e;++e){var u=da[e]+t;if(u in n)return u}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,u=-1,i=r.length;++ue;e++)for(var u,i=n[e],o=0,a=i.length;a>o;o++)(u=i[o])&&t(u,o,e);return n}function Z(n){return ya(n,Sa),n}function V(n){var t,e;return function(r,u,i){var o,a=n[i].update,c=a.length;for(i!=e&&(e=i,t=0),u>=t&&(t=u+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var l=ka.get(n);return l&&(n=l,c=B),a?t?u:r:t?b:i}function $(n,t){return function(e){var r=ta.event;ta.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ta.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Aa,u="click"+r,i=ta.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ea&&(Ea="onselectstart"in e?!1:x(e.style,"userSelect")),Ea){var o=n(e).style,a=o[Ea];o[Ea]="none"}return function(n){if(i.on(r,null),Ea&&(o[Ea]=a),n){var t=function(){i.on(u,null)};i.on(u,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var u=r.createSVGPoint();if(0>Na){var i=t(n);if(i.scrollX||i.scrollY){r=ta.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Na=!(o.f||o.e),r.remove()}}return Na?(u.x=e.pageX,u.y=e.pageY):(u.x=e.clientX,u.y=e.clientY),u=u.matrixTransform(n.getScreenCTM().inverse()),[u.x,u.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ta.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nt(n){return n>1?0:-1>n?qa:Math.acos(n)}function tt(n){return n>1?Ra:-1>n?-Ra:Math.asin(n)}function et(n){return((n=Math.exp(n))-1/n)/2}function rt(n){return((n=Math.exp(n))+1/n)/2}function ut(n){return((n=Math.exp(2*n))-1)/(n+1)}function it(n){return(n=Math.sin(n/2))*n}function ot(){}function at(n,t,e){return this instanceof at?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof at?new at(n.h,n.s,n.l):bt(""+n,_t,at):new at(n,t,e)}function ct(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?i+(o-i)*n/60:180>n?o:240>n?i+(o-i)*(240-n)/60:i}function u(n){return Math.round(255*r(n))}var i,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,i=2*e-o,new mt(u(n+120),u(n),u(n-120))}function lt(n,t,e){return this instanceof lt?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof lt?new lt(n.h,n.c,n.l):n instanceof ft?gt(n.l,n.a,n.b):gt((n=wt((n=ta.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new lt(n,t,e)}function st(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new ft(e,Math.cos(n*=Da)*t,Math.sin(n)*t)}function ft(n,t,e){return this instanceof ft?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof ft?new ft(n.l,n.a,n.b):n instanceof lt?st(n.h,n.c,n.l):wt((n=mt(n)).r,n.g,n.b):new ft(n,t,e)}function ht(n,t,e){var r=(n+16)/116,u=r+t/500,i=r-e/200;return u=pt(u)*Xa,r=pt(r)*$a,i=pt(i)*Ba,new mt(dt(3.2404542*u-1.5371385*r-.4985314*i),dt(-.969266*u+1.8760108*r+.041556*i),dt(.0556434*u-.2040259*r+1.0572252*i))}function gt(n,t,e){return n>0?new lt(Math.atan2(e,t)*Pa,Math.sqrt(t*t+e*e),n):new lt(0/0,0/0,n)}function pt(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function vt(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function dt(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mt(n,t,e){return this instanceof mt?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mt?new mt(n.r,n.g,n.b):bt(""+n,mt,ct):new mt(n,t,e)}function yt(n){return new mt(n>>16,n>>8&255,255&n)}function Mt(n){return yt(n)+""}function xt(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function bt(n,t,e){var r,u,i,o=0,a=0,c=0;if(r=/([a-z]+)\((.*)\)/i.exec(n))switch(u=r[2].split(","),r[1]){case"hsl":return e(parseFloat(u[0]),parseFloat(u[1])/100,parseFloat(u[2])/100);case"rgb":return t(kt(u[0]),kt(u[1]),kt(u[2]))}return(i=Ga.get(n.toLowerCase()))?t(i.r,i.g,i.b):(null==n||"#"!==n.charAt(0)||isNaN(i=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&i)>>4,o=o>>4|o,a=240&i,a=a>>4|a,c=15&i,c=c<<4|c):7===n.length&&(o=(16711680&i)>>16,a=(65280&i)>>8,c=255&i)),t(o,a,c))}function _t(n,t,e){var r,u,i=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-i,c=(o+i)/2;return a?(u=.5>c?a/(o+i):a/(2-o-i),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=0/0,u=c>0&&1>c?0:r),new at(r,u,c)}function wt(n,t,e){n=St(n),t=St(t),e=St(e);var r=vt((.4124564*n+.3575761*t+.1804375*e)/Xa),u=vt((.2126729*n+.7151522*t+.072175*e)/$a),i=vt((.0193339*n+.119192*t+.9503041*e)/Ba);return ft(116*u-16,500*(r-u),200*(u-i))}function St(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function kt(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function Et(n){return"function"==typeof n?n:function(){return n}}function At(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Nt(t,e,n,r)}}function Nt(n,t,e,r){function u(){var n,t=c.status;if(!t&&zt(c)||t>=200&&300>t||304===t){try{n=e.call(i,c)}catch(r){return void o.error.call(i,r)}o.load.call(i,n)}else o.error.call(i,c)}var i={},o=ta.dispatch("beforesend","progress","load","error"),a={},c=new XMLHttpRequest,l=null;return!this.XDomainRequest||"withCredentials"in c||!/^(http(s)?:)?\/\//.test(n)||(c=new XDomainRequest),"onload"in c?c.onload=c.onerror=u:c.onreadystatechange=function(){c.readyState>3&&u()},c.onprogress=function(n){var t=ta.event;ta.event=n;try{o.progress.call(i,c)}finally{ta.event=t}},i.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",i)},i.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",i):t},i.responseType=function(n){return arguments.length?(l=n,i):l},i.response=function(n){return e=n,i},["get","post"].forEach(function(n){i[n]=function(){return i.send.apply(i,[n].concat(ra(arguments)))}}),i.send=function(e,r,u){if(2===arguments.length&&"function"==typeof r&&(u=r,r=null),c.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),c.setRequestHeader)for(var s in a)c.setRequestHeader(s,a[s]);return null!=t&&c.overrideMimeType&&c.overrideMimeType(t),null!=l&&(c.responseType=l),null!=u&&i.on("error",u).on("load",function(n){u(null,n)}),o.beforesend.call(i,c),c.send(null==r?null:r),i},i.abort=function(){return c.abort(),i},ta.rebind(i,o,"on"),null==r?i:i.get(Ct(r))}function Ct(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function zt(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qt(){var n=Lt(),t=Tt()-n;t>24?(isFinite(t)&&(clearTimeout(tc),tc=setTimeout(qt,t)),nc=0):(nc=1,rc(qt))}function Lt(){var n=Date.now();for(ec=Ka;ec;)n>=ec.t&&(ec.f=ec.c(n-ec.t)),ec=ec.n;return n}function Tt(){for(var n,t=Ka,e=1/0;t;)t.f?t=n?n.n=t.n:Ka=t.n:(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function Pt(n){var t=n.decimal,e=n.thousands,r=n.grouping,u=n.currency,i=r&&e?function(n,t){for(var u=n.length,i=[],o=0,a=r[0],c=0;u>0&&a>0&&(c+a+1>t&&(a=Math.max(1,t-c)),i.push(n.substring(u-=a,u+a)),!((c+=a+1)>t));)a=r[o=(o+1)%r.length];return i.reverse().join(e)}:y;return function(n){var e=ic.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",c=e[4]||"",l=e[5],s=+e[6],f=e[7],h=e[8],g=e[9],p=1,v="",d="",m=!1,y=!0;switch(h&&(h=+h.substring(1)),(l||"0"===r&&"="===o)&&(l=r="0",o="="),g){case"n":f=!0,g="g";break;case"%":p=100,d="%",g="f";break;case"p":p=100,d="%",g="r";break;case"b":case"o":case"x":case"X":"#"===c&&(v="0"+g.toLowerCase());case"c":y=!1;case"d":m=!0,h=0;break;case"s":p=-1,g="r"}"$"===c&&(v=u[0],d=u[1]),"r"!=g||h||(g="g"),null!=h&&("g"==g?h=Math.max(1,Math.min(21,h)):("e"==g||"f"==g)&&(h=Math.max(0,Math.min(20,h)))),g=oc.get(g)||Ut;var M=l&&f;return function(n){var e=d;if(m&&n%1)return"";var u=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>p){var c=ta.formatPrefix(n,h);n=c.scale(n),e=c.symbol+d}else n*=p;n=g(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=y?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!l&&f&&(x=i(x,1/0));var S=v.length+x.length+b.length+(M?0:u.length),k=s>S?new Array(S=s-S+1).join(r):"";return M&&(x=i(k+x,k.length?s-b.length:1/0)),u+=v,n=x+b,("<"===o?u+n+k:">"===o?k+u+n:"^"===o?k.substring(0,S>>=1)+u+n+k.substring(S):u+(M?n:k+n))+e}}}function Ut(n){return n+""}function jt(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function Ft(n,t,e){function r(t){var e=n(t),r=i(e,1);return r-t>t-e?e:r}function u(e){return t(e=n(new cc(e-1)),1),e}function i(n,e){return t(n=new cc(+n),e),n}function o(n,r,i){var o=u(n),a=[];if(i>1)for(;r>o;)e(o)%i||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{cc=jt;var r=new jt;return r._=n,o(r,t,e)}finally{cc=Date}}n.floor=n,n.round=r,n.ceil=u,n.offset=i,n.range=o;var c=n.utc=Ht(n);return c.floor=c,c.round=Ht(r),c.ceil=Ht(u),c.offset=Ht(i),c.range=a,n}function Ht(n){return function(t,e){try{cc=jt;var r=new jt;return r._=t,n(r,e)._}finally{cc=Date}}}function Ot(n){function t(n){function t(t){for(var e,u,i,o=[],a=-1,c=0;++aa;){if(r>=l)return-1;if(u=t.charCodeAt(a++),37===u){if(o=t.charAt(a++),i=C[o in sc?t.charAt(a++):o],!i||(r=i(n,e,r))<0)return-1}else if(u!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){E.lastIndex=0;var r=E.exec(t.slice(e));return r?(n.m=A.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,N.c.toString(),t,r)}function c(n,t,r){return e(n,N.x.toString(),t,r)}function l(n,t,r){return e(n,N.X.toString(),t,r)}function s(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var f=n.dateTime,h=n.date,g=n.time,p=n.periods,v=n.days,d=n.shortDays,m=n.months,y=n.shortMonths;t.utc=function(n){function e(n){try{cc=jt;var t=new cc;return t._=n,r(t)}finally{cc=Date}}var r=t(n);return e.parse=function(n){try{cc=jt;var t=r.parse(n);return t&&t._}finally{cc=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ae;var M=ta.map(),x=Yt(v),b=Zt(v),_=Yt(d),w=Zt(d),S=Yt(m),k=Zt(m),E=Yt(y),A=Zt(y);p.forEach(function(n,t){M.set(n.toLowerCase(),t)});var N={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return y[n.getMonth()]},B:function(n){return m[n.getMonth()]},c:t(f),d:function(n,t){return It(n.getDate(),t,2)},e:function(n,t){return It(n.getDate(),t,2)},H:function(n,t){return It(n.getHours(),t,2)},I:function(n,t){return It(n.getHours()%12||12,t,2)},j:function(n,t){return It(1+ac.dayOfYear(n),t,3)},L:function(n,t){return It(n.getMilliseconds(),t,3)},m:function(n,t){return It(n.getMonth()+1,t,2)},M:function(n,t){return It(n.getMinutes(),t,2)},p:function(n){return p[+(n.getHours()>=12)]},S:function(n,t){return It(n.getSeconds(),t,2)},U:function(n,t){return It(ac.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return It(ac.mondayOfYear(n),t,2)},x:t(h),X:t(g),y:function(n,t){return It(n.getFullYear()%100,t,2)},Y:function(n,t){return It(n.getFullYear()%1e4,t,4)},Z:ie,"%":function(){return"%"}},C={a:r,A:u,b:i,B:o,c:a,d:Qt,e:Qt,H:te,I:te,j:ne,L:ue,m:Kt,M:ee,p:s,S:re,U:Xt,w:Vt,W:$t,x:c,X:l,y:Wt,Y:Bt,Z:Jt,"%":oe};return t}function It(n,t,e){var r=0>n?"-":"",u=(r?-n:n)+"",i=u.length;return r+(e>i?new Array(e-i+1).join(t)+u:u)}function Yt(n){return new RegExp("^(?:"+n.map(ta.requote).join("|")+")","i")}function Zt(n){for(var t=new l,e=-1,r=n.length;++e68?1900:2e3)}function Kt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function Qt(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function ne(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function te(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function ee(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function re(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ue(n,t,e){fc.lastIndex=0;var r=fc.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function ie(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=ga(t)/60|0,u=ga(t)%60;return e+It(r,"0",2)+It(u,"0",2)}function oe(n,t,e){hc.lastIndex=0;var r=hc.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ae(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,c=Math.cos(t),l=Math.sin(t),s=i*l,f=u*c+s*Math.cos(a),h=s*o*Math.sin(a);yc.add(Math.atan2(h,f)),r=n,u=c,i=l}var t,e,r,u,i;Mc.point=function(o,a){Mc.point=n,r=(t=o)*Da,u=Math.cos(a=(e=a)*Da/2+qa/4),i=Math.sin(a)},Mc.lineEnd=function(){n(t,e)}}function pe(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function ve(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function de(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function me(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function ye(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function Me(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function xe(n){return[Math.atan2(n[1],n[0]),tt(n[2])]}function be(n,t){return ga(n[0]-t[0])a;++a)u.point((e=n[a])[0],e[1]);return void u.lineEnd()}var c=new qe(e,n,null,!0),l=new qe(e,null,c,!1);c.o=l,i.push(c),o.push(l),c=new qe(r,n,null,!1),l=new qe(r,null,c,!0),c.o=l,i.push(c),o.push(l)}}),o.sort(t),ze(i),ze(o),i.length){for(var a=0,c=e,l=o.length;l>a;++a)o[a].e=c=!c;for(var s,f,h=i[0];;){for(var g=h,p=!0;g.v;)if((g=g.n)===h)return;s=g.z,u.lineStart();do{if(g.v=g.o.v=!0,g.e){if(p)for(var a=0,l=s.length;l>a;++a)u.point((f=s[a])[0],f[1]);else r(g.x,g.n.x,1,u);g=g.n}else{if(p){s=g.p.z;for(var a=s.length-1;a>=0;--a)u.point((f=s[a])[0],f[1])}else r(g.x,g.p.x,-1,u);g=g.p}g=g.o,s=g.z,p=!p}while(!g.v);u.lineEnd()}}}function ze(n){if(t=n.length){for(var t,e,r=0,u=n[0];++r0){for(b||(i.polygonStart(),b=!0),i.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),g.push(e.filter(Te))}var g,p,v,d=t(i),m=u.invert(r[0],r[1]),y={point:o,lineStart:c,lineEnd:l,polygonStart:function(){y.point=s,y.lineStart=f,y.lineEnd=h,g=[],p=[]},polygonEnd:function(){y.point=o,y.lineStart=c,y.lineEnd=l,g=ta.merge(g);var n=Fe(m,p);g.length?(b||(i.polygonStart(),b=!0),Ce(g,De,n,e,i)):n&&(b||(i.polygonStart(),b=!0),i.lineStart(),e(null,null,1,i),i.lineEnd()),b&&(i.polygonEnd(),b=!1),g=p=null},sphere:function(){i.polygonStart(),i.lineStart(),e(null,null,1,i),i.lineEnd(),i.polygonEnd()}},M=Re(),x=t(M),b=!1;return y}}function Te(n){return n.length>1}function Re(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function De(n,t){return((n=n.x)[0]<0?n[1]-Ra-Ca:Ra-n[1])-((t=t.x)[0]<0?t[1]-Ra-Ca:Ra-t[1])}function Pe(n){var t,e=0/0,r=0/0,u=0/0;return{lineStart:function(){n.lineStart(),t=1},point:function(i,o){var a=i>0?qa:-qa,c=ga(i-e);ga(c-qa)0?Ra:-Ra),n.point(u,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(i,r),t=0):u!==a&&c>=qa&&(ga(e-u)Ca?Math.atan((Math.sin(t)*(i=Math.cos(r))*Math.sin(e)-Math.sin(r)*(u=Math.cos(t))*Math.sin(n))/(u*i*o)):(t+r)/2}function je(n,t,e,r){var u;if(null==n)u=e*Ra,r.point(-qa,u),r.point(0,u),r.point(qa,u),r.point(qa,0),r.point(qa,-u),r.point(0,-u),r.point(-qa,-u),r.point(-qa,0),r.point(-qa,u);else if(ga(n[0]-t[0])>Ca){var i=n[0]a;++a){var l=t[a],s=l.length;if(s)for(var f=l[0],h=f[0],g=f[1]/2+qa/4,p=Math.sin(g),v=Math.cos(g),d=1;;){d===s&&(d=0),n=l[d];var m=n[0],y=n[1]/2+qa/4,M=Math.sin(y),x=Math.cos(y),b=m-h,_=b>=0?1:-1,w=_*b,S=w>qa,k=p*M;if(yc.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),i+=S?b+_*La:b,S^h>=e^m>=e){var E=de(pe(f),pe(n));Me(E);var A=de(u,E);Me(A);var N=(S^b>=0?-1:1)*tt(A[2]);(r>N||r===N&&(E[0]||E[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=m,p=M,v=x,f=n}}return(-Ca>i||Ca>i&&0>yc)^1&o}function He(n){function t(n,t){return Math.cos(n)*Math.cos(t)>i}function e(n){var e,i,c,l,s;return{lineStart:function(){l=c=!1,s=1},point:function(f,h){var g,p=[f,h],v=t(f,h),d=o?v?0:u(f,h):v?u(f+(0>f?qa:-qa),h):0;if(!e&&(l=c=v)&&n.lineStart(),v!==c&&(g=r(e,p),(be(e,g)||be(p,g))&&(p[0]+=Ca,p[1]+=Ca,v=t(p[0],p[1]))),v!==c)s=0,v?(n.lineStart(),g=r(p,e),n.point(g[0],g[1])):(g=r(e,p),n.point(g[0],g[1]),n.lineEnd()),e=g;else if(a&&e&&o^v){var m;d&i||!(m=r(p,e,!0))||(s=0,o?(n.lineStart(),n.point(m[0][0],m[0][1]),n.point(m[1][0],m[1][1]),n.lineEnd()):(n.point(m[1][0],m[1][1]),n.lineEnd(),n.lineStart(),n.point(m[0][0],m[0][1])))}!v||e&&be(e,p)||n.point(p[0],p[1]),e=p,c=v,i=d},lineEnd:function(){c&&n.lineEnd(),e=null},clean:function(){return s|(l&&c)<<1}}}function r(n,t,e){var r=pe(n),u=pe(t),o=[1,0,0],a=de(r,u),c=ve(a,a),l=a[0],s=c-l*l;if(!s)return!e&&n;var f=i*c/s,h=-i*l/s,g=de(o,a),p=ye(o,f),v=ye(a,h);me(p,v);var d=g,m=ve(p,d),y=ve(d,d),M=m*m-y*(ve(p,p)-1);if(!(0>M)){var x=Math.sqrt(M),b=ye(d,(-m-x)/y);if(me(b,p),b=xe(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],E=t[1];w>S&&(_=w,w=S,S=_);var A=S-w,N=ga(A-qa)A;if(!N&&k>E&&(_=k,k=E,E=_),C?N?k+E>0^b[1]<(ga(b[0]-w)qa^(w<=b[0]&&b[0]<=S)){var z=ye(d,(-m+x)/y);return me(z,p),[b,xe(z)]}}}function u(t,e){var r=o?n:qa-n,u=0;return-r>t?u|=1:t>r&&(u|=2),-r>e?u|=4:e>r&&(u|=8),u}var i=Math.cos(n),o=i>0,a=ga(i)>Ca,c=gr(n,6*Da);return Le(t,e,c,o?[0,-n]:[-qa,n-qa])}function Oe(n,t,e,r){return function(u){var i,o=u.a,a=u.b,c=o.x,l=o.y,s=a.x,f=a.y,h=0,g=1,p=s-c,v=f-l;if(i=n-c,p||!(i>0)){if(i/=p,0>p){if(h>i)return;g>i&&(g=i)}else if(p>0){if(i>g)return;i>h&&(h=i)}if(i=e-c,p||!(0>i)){if(i/=p,0>p){if(i>g)return;i>h&&(h=i)}else if(p>0){if(h>i)return;g>i&&(g=i)}if(i=t-l,v||!(i>0)){if(i/=v,0>v){if(h>i)return;g>i&&(g=i)}else if(v>0){if(i>g)return;i>h&&(h=i)}if(i=r-l,v||!(0>i)){if(i/=v,0>v){if(i>g)return;i>h&&(h=i)}else if(v>0){if(h>i)return;g>i&&(g=i)}return h>0&&(u.a={x:c+h*p,y:l+h*v}),1>g&&(u.b={x:c+g*p,y:l+g*v}),u}}}}}}function Ie(n,t,e,r){function u(r,u){return ga(r[0]-n)0?0:3:ga(r[0]-e)0?2:1:ga(r[1]-t)0?1:0:u>0?3:2}function i(n,t){return o(n.x,t.x)}function o(n,t){var e=u(n,1),r=u(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function c(n){for(var t=0,e=d.length,r=n[1],u=0;e>u;++u)for(var i,o=1,a=d[u],c=a.length,l=a[0];c>o;++o)i=a[o],l[1]<=r?i[1]>r&&Q(l,i,n)>0&&++t:i[1]<=r&&Q(l,i,n)<0&&--t,l=i;return 0!==t}function l(i,a,c,l){var s=0,f=0;if(null==i||(s=u(i,c))!==(f=u(a,c))||o(i,a)<0^c>0){do l.point(0===s||3===s?n:e,s>1?r:t);while((s=(s+c+4)%4)!==f)}else l.point(a[0],a[1])}function s(u,i){return u>=n&&e>=u&&i>=t&&r>=i}function f(n,t){s(n,t)&&a.point(n,t)}function h(){C.point=p,d&&d.push(m=[]),S=!0,w=!1,b=_=0/0}function g(){v&&(p(y,M),x&&w&&A.rejoin(),v.push(A.buffer())),C.point=f,w&&a.lineEnd()}function p(n,t){n=Math.max(-Tc,Math.min(Tc,n)),t=Math.max(-Tc,Math.min(Tc,t));var e=s(n,t);if(d&&m.push([n,t]),S)y=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};N(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,m,y,M,x,b,_,w,S,k,E=a,A=Re(),N=Oe(n,t,e,r),C={point:f,lineStart:h,lineEnd:g,polygonStart:function(){a=A,v=[],d=[],k=!0},polygonEnd:function(){a=E,v=ta.merge(v);var t=c([n,r]),e=k&&t,u=v.length;(e||u)&&(a.polygonStart(),e&&(a.lineStart(),l(null,null,1,a),a.lineEnd()),u&&Ce(v,i,t,l,a),a.polygonEnd()),v=d=m=null}};return C}}function Ye(n){var t=0,e=qa/3,r=ir(n),u=r(t,e);return u.parallels=function(n){return arguments.length?r(t=n[0]*qa/180,e=n[1]*qa/180):[t/qa*180,e/qa*180]},u}function Ze(n,t){function e(n,t){var e=Math.sqrt(i-2*u*Math.sin(t))/u;return[e*Math.sin(n*=u),o-e*Math.cos(n)]}var r=Math.sin(n),u=(r+Math.sin(t))/2,i=1+r*(2*u-r),o=Math.sqrt(i)/u;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/u,tt((i-(n*n+e*e)*u*u)/(2*u))]},e}function Ve(){function n(n,t){Dc+=u*n-r*t,r=n,u=t}var t,e,r,u;Hc.point=function(i,o){Hc.point=n,t=r=i,e=u=o},Hc.lineEnd=function(){n(t,e)}}function Xe(n,t){Pc>n&&(Pc=n),n>jc&&(jc=n),Uc>t&&(Uc=t),t>Fc&&(Fc=t)}function $e(){function n(n,t){o.push("M",n,",",t,i)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function u(){o.push("Z")}var i=Be(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return i=Be(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Be(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function We(n,t){_c+=n,wc+=t,++Sc}function Je(){function n(n,r){var u=n-t,i=r-e,o=Math.sqrt(u*u+i*i);kc+=o*(t+n)/2,Ec+=o*(e+r)/2,Ac+=o,We(t=n,e=r)}var t,e;Ic.point=function(r,u){Ic.point=n,We(t=r,e=u)}}function Ge(){Ic.point=We}function Ke(){function n(n,t){var e=n-r,i=t-u,o=Math.sqrt(e*e+i*i);kc+=o*(r+n)/2,Ec+=o*(u+t)/2,Ac+=o,o=u*n-r*t,Nc+=o*(r+n),Cc+=o*(u+t),zc+=3*o,We(r=n,u=t)}var t,e,r,u;Ic.point=function(i,o){Ic.point=n,We(t=r=i,e=u=o)},Ic.lineEnd=function(){n(t,e)}}function Qe(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,La)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function u(){a.point=t}function i(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:u,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=u,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function nr(n){function t(n){return(a?r:e)(n)}function e(t){return rr(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=0/0,S.point=i,t.lineStart()}function i(e,r){var i=pe([e,r]),o=n(e,r);u(M,x,y,b,_,w,M=o[0],x=o[1],y=e,b=i[0],_=i[1],w=i[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function c(){r(),S.point=l,S.lineEnd=s}function l(n,t){i(f=n,h=t),g=M,p=x,v=b,d=_,m=w,S.point=i}function s(){u(M,x,y,b,_,w,g,p,f,v,d,m,a,t),S.lineEnd=o,o()}var f,h,g,p,v,d,m,y,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=c +},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function u(t,e,r,a,c,l,s,f,h,g,p,v,d,m){var y=s-t,M=f-e,x=y*y+M*M;if(x>4*i&&d--){var b=a+g,_=c+p,w=l+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),E=ga(ga(w)-1)i||ga((y*z+M*q)/x-.5)>.3||o>a*g+c*p+l*v)&&(u(t,e,r,a,c,l,N,C,E,b/=S,_/=S,w,d,m),m.point(N,C),u(N,C,E,b,_,w,s,f,h,g,p,v,d,m))}}var i=.5,o=Math.cos(30*Da),a=16;return t.precision=function(n){return arguments.length?(a=(i=n*n)>0&&16,t):Math.sqrt(i)},t}function tr(n){var t=nr(function(t,e){return n([t*Pa,e*Pa])});return function(n){return or(t(n))}}function er(n){this.stream=n}function rr(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function ur(n){return ir(function(){return n})()}function ir(n){function t(n){return n=a(n[0]*Da,n[1]*Da),[n[0]*h+c,l-n[1]*h]}function e(n){return n=a.invert((n[0]-c)/h,(l-n[1])/h),n&&[n[0]*Pa,n[1]*Pa]}function r(){a=Ae(o=lr(m,M,x),i);var n=i(v,d);return c=g-n[0]*h,l=p+n[1]*h,u()}function u(){return s&&(s.valid=!1,s=null),t}var i,o,a,c,l,s,f=nr(function(n,t){return n=i(n,t),[n[0]*h+c,l-n[1]*h]}),h=150,g=480,p=250,v=0,d=0,m=0,M=0,x=0,b=Lc,_=y,w=null,S=null;return t.stream=function(n){return s&&(s.valid=!1),s=or(b(o,f(_(n)))),s.valid=!0,s},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Lc):He((w=+n)*Da),u()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Ie(n[0][0],n[0][1],n[1][0],n[1][1]):y,u()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(g=+n[0],p=+n[1],r()):[g,p]},t.center=function(n){return arguments.length?(v=n[0]%360*Da,d=n[1]%360*Da,r()):[v*Pa,d*Pa]},t.rotate=function(n){return arguments.length?(m=n[0]%360*Da,M=n[1]%360*Da,x=n.length>2?n[2]%360*Da:0,r()):[m*Pa,M*Pa,x*Pa]},ta.rebind(t,f,"precision"),function(){return i=n.apply(this,arguments),t.invert=i.invert&&e,r()}}function or(n){return rr(n,function(t,e){n.point(t*Da,e*Da)})}function ar(n,t){return[n,t]}function cr(n,t){return[n>qa?n-La:-qa>n?n+La:n,t]}function lr(n,t,e){return n?t||e?Ae(fr(n),hr(t,e)):fr(n):t||e?hr(t,e):cr}function sr(n){return function(t,e){return t+=n,[t>qa?t-La:-qa>t?t+La:t,e]}}function fr(n){var t=sr(n);return t.invert=sr(-n),t}function hr(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*r+a*u;return[Math.atan2(c*i-s*o,a*r-l*u),tt(s*i+c*o)]}var r=Math.cos(n),u=Math.sin(n),i=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,c=Math.sin(n)*e,l=Math.sin(t),s=l*i-c*o;return[Math.atan2(c*i+l*o,a*r+s*u),tt(s*r-a*u)]},e}function gr(n,t){var e=Math.cos(n),r=Math.sin(n);return function(u,i,o,a){var c=o*t;null!=u?(u=pr(e,u),i=pr(e,i),(o>0?i>u:u>i)&&(u+=o*La)):(u=n+o*La,i=n-.5*c);for(var l,s=u;o>0?s>i:i>s;s-=c)a.point((l=xe([e,-r*Math.cos(s),-r*Math.sin(s)]))[0],l[1])}}function pr(n,t){var e=pe(t);e[0]-=n,Me(e);var r=nt(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Ca)%(2*Math.PI)}function vr(n,t,e){var r=ta.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function dr(n,t,e){var r=ta.range(n,t-Ca,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function mr(n){return n.source}function yr(n){return n.target}function Mr(n,t,e,r){var u=Math.cos(t),i=Math.sin(t),o=Math.cos(r),a=Math.sin(r),c=u*Math.cos(n),l=u*Math.sin(n),s=o*Math.cos(e),f=o*Math.sin(e),h=2*Math.asin(Math.sqrt(it(r-t)+u*o*it(e-n))),g=1/Math.sin(h),p=h?function(n){var t=Math.sin(n*=h)*g,e=Math.sin(h-n)*g,r=e*c+t*s,u=e*l+t*f,o=e*i+t*a;return[Math.atan2(u,r)*Pa,Math.atan2(o,Math.sqrt(r*r+u*u))*Pa]}:function(){return[n*Pa,t*Pa]};return p.distance=h,p}function xr(){function n(n,u){var i=Math.sin(u*=Da),o=Math.cos(u),a=ga((n*=Da)-t),c=Math.cos(a);Yc+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*i-e*o*c)*a),e*i+r*o*c),t=n,e=i,r=o}var t,e,r;Zc.point=function(u,i){t=u*Da,e=Math.sin(i*=Da),r=Math.cos(i),Zc.point=n},Zc.lineEnd=function(){Zc.point=Zc.lineEnd=b}}function br(n,t){function e(t,e){var r=Math.cos(t),u=Math.cos(e),i=n(r*u);return[i*u*Math.sin(t),i*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),u=t(r),i=Math.sin(u),o=Math.cos(u);return[Math.atan2(n*i,r*o),Math.asin(r&&e*i/r)]},e}function _r(n,t){function e(n,t){o>0?-Ra+Ca>t&&(t=-Ra+Ca):t>Ra-Ca&&(t=Ra-Ca);var e=o/Math.pow(u(t),i);return[e*Math.sin(i*n),o-e*Math.cos(i*n)]}var r=Math.cos(n),u=function(n){return Math.tan(qa/4+n/2)},i=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(u(t)/u(n)),o=r*Math.pow(u(n),i)/i;return i?(e.invert=function(n,t){var e=o-t,r=K(i)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/i,2*Math.atan(Math.pow(o/r,1/i))-Ra]},e):Sr}function wr(n,t){function e(n,t){var e=i-t;return[e*Math.sin(u*n),i-e*Math.cos(u*n)]}var r=Math.cos(n),u=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),i=r/u+n;return ga(u)u;u++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[u])<=0;)--r;e[r++]=u}return e.slice(0,r)}function zr(n,t){return n[0]-t[0]||n[1]-t[1]}function qr(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Lr(n,t,e,r){var u=n[0],i=e[0],o=t[0]-u,a=r[0]-i,c=n[1],l=e[1],s=t[1]-c,f=r[1]-l,h=(a*(c-l)-f*(u-i))/(f*o-a*s);return[u+h*o,c+h*s]}function Tr(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Rr(){tu(this),this.edge=this.site=this.circle=null}function Dr(n){var t=el.pop()||new Rr;return t.site=n,t}function Pr(n){Xr(n),Qc.remove(n),el.push(n),tu(n)}function Ur(n){var t=n.circle,e=t.x,r=t.cy,u={x:e,y:r},i=n.P,o=n.N,a=[n];Pr(n);for(var c=i;c.circle&&ga(e-c.circle.x)s;++s)l=a[s],c=a[s-1],Kr(l.edge,c.site,l.site,u);c=a[0],l=a[f-1],l.edge=Jr(c.site,l.site,null,u),Vr(c),Vr(l)}function jr(n){for(var t,e,r,u,i=n.x,o=n.y,a=Qc._;a;)if(r=Fr(a,o)-i,r>Ca)a=a.L;else{if(u=i-Hr(a,o),!(u>Ca)){r>-Ca?(t=a.P,e=a):u>-Ca?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var c=Dr(n);if(Qc.insert(t,c),t||e){if(t===e)return Xr(t),e=Dr(t.site),Qc.insert(c,e),c.edge=e.edge=Jr(t.site,c.site),Vr(t),void Vr(e);if(!e)return void(c.edge=Jr(t.site,c.site));Xr(t),Xr(e);var l=t.site,s=l.x,f=l.y,h=n.x-s,g=n.y-f,p=e.site,v=p.x-s,d=p.y-f,m=2*(h*d-g*v),y=h*h+g*g,M=v*v+d*d,x={x:(d*y-g*M)/m+s,y:(h*M-v*y)/m+f};Kr(e.edge,l,p,x),c.edge=Jr(l,n,null,x),e.edge=Jr(n,p,null,x),Vr(t),Vr(e)}}function Fr(n,t){var e=n.site,r=e.x,u=e.y,i=u-t;if(!i)return r;var o=n.P;if(!o)return-1/0;e=o.site;var a=e.x,c=e.y,l=c-t;if(!l)return a;var s=a-r,f=1/i-1/l,h=s/l;return f?(-h+Math.sqrt(h*h-2*f*(s*s/(-2*l)-c+l/2+u-i/2)))/f+r:(r+a)/2}function Hr(n,t){var e=n.N;if(e)return Fr(e,t);var r=n.site;return r.y===t?r.x:1/0}function Or(n){this.site=n,this.edges=[]}function Ir(n){for(var t,e,r,u,i,o,a,c,l,s,f=n[0][0],h=n[1][0],g=n[0][1],p=n[1][1],v=Kc,d=v.length;d--;)if(i=v[d],i&&i.prepare())for(a=i.edges,c=a.length,o=0;c>o;)s=a[o].end(),r=s.x,u=s.y,l=a[++o%c].start(),t=l.x,e=l.y,(ga(r-t)>Ca||ga(u-e)>Ca)&&(a.splice(o,0,new Qr(Gr(i.site,s,ga(r-f)Ca?{x:f,y:ga(t-f)Ca?{x:ga(e-p)Ca?{x:h,y:ga(t-h)Ca?{x:ga(e-g)=-za)){var g=c*c+l*l,p=s*s+f*f,v=(f*g-l*p)/h,d=(c*p-s*g)/h,f=d+a,m=rl.pop()||new Zr;m.arc=n,m.site=u,m.x=v+o,m.y=f+Math.sqrt(v*v+d*d),m.cy=f,n.circle=m;for(var y=null,M=tl._;M;)if(m.yd||d>=a)return;if(h>p){if(i){if(i.y>=l)return}else i={x:d,y:c};e={x:d,y:l}}else{if(i){if(i.yr||r>1)if(h>p){if(i){if(i.y>=l)return}else i={x:(c-u)/r,y:c};e={x:(l-u)/r,y:l}}else{if(i){if(i.yg){if(i){if(i.x>=a)return}else i={x:o,y:r*o+u};e={x:a,y:r*a+u}}else{if(i){if(i.xi||f>o||r>h||u>g)){if(p=n.point){var p,v=t-n.x,d=e-n.y,m=v*v+d*d;if(c>m){var y=Math.sqrt(c=m);r=t-y,u=e-y,i=t+y,o=e+y,a=p}}for(var M=n.nodes,x=.5*(s+h),b=.5*(f+g),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:l(n,s,f,x,b);break;case 1:l(n,x,f,h,b);break;case 2:l(n,s,b,x,g);break;case 3:l(n,x,b,h,g)}}}(n,r,u,i,o),a}function gu(n,t){n=ta.rgb(n),t=ta.rgb(t);var e=n.r,r=n.g,u=n.b,i=t.r-e,o=t.g-r,a=t.b-u;return function(n){return"#"+xt(Math.round(e+i*n))+xt(Math.round(r+o*n))+xt(Math.round(u+a*n))}}function pu(n,t){var e,r={},u={};for(e in n)e in t?r[e]=mu(n[e],t[e]):u[e]=n[e];for(e in t)e in n||(u[e]=t[e]);return function(n){for(e in r)u[e]=r[e](n);return u}}function vu(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function du(n,t){var e,r,u,i=il.lastIndex=ol.lastIndex=0,o=-1,a=[],c=[];for(n+="",t+="";(e=il.exec(n))&&(r=ol.exec(t));)(u=r.index)>i&&(u=t.slice(i,u),a[o]?a[o]+=u:a[++o]=u),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,c.push({i:o,x:vu(e,r)})),i=ol.lastIndex;return ir;++r)a[(e=c[r]).i]=e.x(n);return a.join("")})}function mu(n,t){for(var e,r=ta.interpolators.length;--r>=0&&!(e=ta.interpolators[r](n,t)););return e}function yu(n,t){var e,r=[],u=[],i=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(mu(n[e],t[e]));for(;i>e;++e)u[e]=n[e];for(;o>e;++e)u[e]=t[e];return function(n){for(e=0;a>e;++e)u[e]=r[e](n);return u}}function Mu(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function xu(n){return function(t){return 1-n(1-t)}}function bu(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function _u(n){return n*n}function wu(n){return n*n*n}function Su(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function ku(n){return function(t){return Math.pow(t,n)}}function Eu(n){return 1-Math.cos(n*Ra)}function Au(n){return Math.pow(2,10*(n-1))}function Nu(n){return 1-Math.sqrt(1-n*n)}function Cu(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/La*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*La/t)}}function zu(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function qu(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Lu(n,t){n=ta.hcl(n),t=ta.hcl(t);var e=n.h,r=n.c,u=n.l,i=t.h-e,o=t.c-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return st(e+i*n,r+o*n,u+a*n)+""}}function Tu(n,t){n=ta.hsl(n),t=ta.hsl(t);var e=n.h,r=n.s,u=n.l,i=t.h-e,o=t.s-r,a=t.l-u;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(i)?(i=0,e=isNaN(e)?t.h:e):i>180?i-=360:-180>i&&(i+=360),function(n){return ct(e+i*n,r+o*n,u+a*n)+""}}function Ru(n,t){n=ta.lab(n),t=ta.lab(t);var e=n.l,r=n.a,u=n.b,i=t.l-e,o=t.a-r,a=t.b-u;return function(n){return ht(e+i*n,r+o*n,u+a*n)+""}}function Du(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function Pu(n){var t=[n.a,n.b],e=[n.c,n.d],r=ju(t),u=Uu(t,e),i=ju(Fu(e,t,-u))||0;t[0]*e[1]180?s+=360:s-l>180&&(l+=360),u.push({i:r.push(r.pop()+"rotate(",null,")")-2,x:vu(l,s)})):s&&r.push(r.pop()+"rotate("+s+")"),f!=h?u.push({i:r.push(r.pop()+"skewX(",null,")")-2,x:vu(f,h)}):h&&r.push(r.pop()+"skewX("+h+")"),g[0]!=p[0]||g[1]!=p[1]?(e=r.push(r.pop()+"scale(",null,",",null,")"),u.push({i:e-4,x:vu(g[0],p[0])},{i:e-2,x:vu(g[1],p[1])})):(1!=p[0]||1!=p[1])&&r.push(r.pop()+"scale("+p+")"),e=u.length,function(n){for(var t,i=-1;++i=0;)e.push(u[r])}function Qu(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(i=n.children)&&(u=i.length))for(var u,i,o=-1;++oe;++e)(t=n[e][1])>u&&(r=e,u=t);return r}function si(n){return n.reduce(fi,0)}function fi(n,t){return n+t[1]}function hi(n,t){return gi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function gi(n,t){for(var e=-1,r=+n[0],u=(n[1]-r)/t,i=[];++e<=t;)i[e]=u*e+r;return i}function pi(n){return[ta.min(n),ta.max(n)]}function vi(n,t){return n.value-t.value}function di(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function mi(n,t){n._pack_next=t,t._pack_prev=n}function yi(n,t){var e=t.x-n.x,r=t.y-n.y,u=n.r+t.r;return.999*u*u>e*e+r*r}function Mi(n){function t(n){s=Math.min(n.x-n.r,s),f=Math.max(n.x+n.r,f),h=Math.min(n.y-n.r,h),g=Math.max(n.y+n.r,g)}if((e=n.children)&&(l=e.length)){var e,r,u,i,o,a,c,l,s=1/0,f=-1/0,h=1/0,g=-1/0;if(e.forEach(xi),r=e[0],r.x=-r.r,r.y=0,t(r),l>1&&(u=e[1],u.x=u.r,u.y=0,t(u),l>2))for(i=e[2],wi(r,u,i),t(i),di(r,i),r._pack_prev=i,di(i,u),u=r._pack_next,o=3;l>o;o++){wi(r,u,i=e[o]);var p=0,v=1,d=1;for(a=u._pack_next;a!==u;a=a._pack_next,v++)if(yi(a,i)){p=1;break}if(1==p)for(c=r._pack_prev;c!==a._pack_prev&&!yi(c,i);c=c._pack_prev,d++);p?(d>v||v==d&&u.ro;o++)i=e[o],i.x-=m,i.y-=y,M=Math.max(M,i.r+Math.sqrt(i.x*i.x+i.y*i.y));n.r=M,e.forEach(bi)}}function xi(n){n._pack_next=n._pack_prev=n}function bi(n){delete n._pack_next,delete n._pack_prev}function _i(n,t,e,r){var u=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,u)for(var i=-1,o=u.length;++i=0;)t=u[i],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Ci(n,t,e){return n.a.parent===t.parent?n.a:e}function zi(n){return 1+ta.max(n,function(n){return n.y})}function qi(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Li(n){var t=n.children;return t&&t.length?Li(t[0]):n}function Ti(n){var t,e=n.children;return e&&(t=e.length)?Ti(e[t-1]):n}function Ri(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Di(n,t){var e=n.x+t[3],r=n.y+t[0],u=n.dx-t[1]-t[3],i=n.dy-t[0]-t[2];return 0>u&&(e+=u/2,u=0),0>i&&(r+=i/2,i=0),{x:e,y:r,dx:u,dy:i}}function Pi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Ui(n){return n.rangeExtent?n.rangeExtent():Pi(n.range())}function ji(n,t,e,r){var u=e(n[0],n[1]),i=r(t[0],t[1]);return function(n){return i(u(n))}}function Fi(n,t){var e,r=0,u=n.length-1,i=n[r],o=n[u];return i>o&&(e=r,r=u,u=e,e=i,i=o,o=e),n[r]=t.floor(i),n[u]=t.ceil(o),n}function Hi(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:ml}function Oi(n,t,e,r){var u=[],i=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Oi:ji,c=r?Iu:Ou;return o=u(n,t,c,e),a=u(t,n,c,mu),i}function i(n){return o(n)}var o,a;return i.invert=function(n){return a(n)},i.domain=function(t){return arguments.length?(n=t.map(Number),u()):n},i.range=function(n){return arguments.length?(t=n,u()):t},i.rangeRound=function(n){return i.range(n).interpolate(Du)},i.clamp=function(n){return arguments.length?(r=n,u()):r},i.interpolate=function(n){return arguments.length?(e=n,u()):e},i.ticks=function(t){return Xi(n,t)},i.tickFormat=function(t,e){return $i(n,t,e)},i.nice=function(t){return Zi(n,t),u()},i.copy=function(){return Ii(n,t,e,r)},u()}function Yi(n,t){return ta.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Zi(n,t){return Fi(n,Hi(Vi(n,t)[2]))}function Vi(n,t){null==t&&(t=10);var e=Pi(n),r=e[1]-e[0],u=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),i=t/r*u;return.15>=i?u*=10:.35>=i?u*=5:.75>=i&&(u*=2),e[0]=Math.ceil(e[0]/u)*u,e[1]=Math.floor(e[1]/u)*u+.5*u,e[2]=u,e}function Xi(n,t){return ta.range.apply(ta,Vi(n,t))}function $i(n,t,e){var r=Vi(n,t);if(e){var u=ic.exec(e);if(u.shift(),"s"===u[8]){var i=ta.formatPrefix(Math.max(ga(r[0]),ga(r[1])));return u[7]||(u[7]="."+Bi(i.scale(r[2]))),u[8]="f",e=ta.format(u.join("")),function(n){return e(i.scale(n))+i.symbol}}u[7]||(u[7]="."+Wi(u[8],r)),e=u.join("")}else e=",."+Bi(r[2])+"f";return ta.format(e)}function Bi(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function Wi(n,t){var e=Bi(t[2]);return n in yl?Math.abs(e-Bi(Math.max(ga(t[0]),ga(t[1]))))+ +("e"!==n):e-2*("%"===n)}function Ji(n,t,e,r){function u(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function i(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(u(t))}return o.invert=function(t){return i(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(u)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(u)),o):t},o.nice=function(){var t=Fi(r.map(u),e?Math:xl);return n.domain(t),r=t.map(i),o},o.ticks=function(){var n=Pi(r),o=[],a=n[0],c=n[1],l=Math.floor(u(a)),s=Math.ceil(u(c)),f=t%1?2:t;if(isFinite(s-l)){if(e){for(;s>l;l++)for(var h=1;f>h;h++)o.push(i(l)*h);o.push(i(l))}else for(o.push(i(l));l++0;h--)o.push(i(l)*h);for(l=0;o[l]c;s--);o=o.slice(l,s)}return o},o.tickFormat=function(n,t){if(!arguments.length)return Ml;arguments.length<2?t=Ml:"function"!=typeof t&&(t=ta.format(t));var r,a=Math.max(.1,n/o.ticks().length),c=e?(r=1e-12,Math.ceil):(r=-1e-12,Math.floor);return function(n){return n/i(c(u(n)+r))<=a?t(n):""}},o.copy=function(){return Ji(n.copy(),t,e,r)},Yi(o,n)}function Gi(n,t,e){function r(t){return n(u(t))}var u=Ki(t),i=Ki(1/t);return r.invert=function(t){return i(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(u)),r):e},r.ticks=function(n){return Xi(e,n)},r.tickFormat=function(n,t){return $i(e,n,t)},r.nice=function(n){return r.domain(Zi(e,n))},r.exponent=function(o){return arguments.length?(u=Ki(t=o),i=Ki(1/t),n.domain(e.map(u)),r):t},r.copy=function(){return Gi(n.copy(),t,e)},Yi(r,n)}function Ki(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function Qi(n,t){function e(e){return i[((u.get(e)||("range"===t.t?u.set(e,n.push(e)):0/0))-1)%i.length]}function r(t,e){return ta.range(n.length).map(function(n){return t+e*n})}var u,i,o;return e.domain=function(r){if(!arguments.length)return n;n=[],u=new l;for(var i,o=-1,a=r.length;++oe?[0/0,0/0]:[e>0?a[e-1]:n[0],et?0/0:t/i+n,[t,t+1/i]},r.copy=function(){return to(n,t,e)},u()}function eo(n,t){function e(e){return e>=e?t[ta.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return eo(n,t)},e}function ro(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Xi(n,t)},t.tickFormat=function(t,e){return $i(n,t,e)},t.copy=function(){return ro(n)},t}function uo(){return 0}function io(n){return n.innerRadius}function oo(n){return n.outerRadius}function ao(n){return n.startAngle}function co(n){return n.endAngle}function lo(n){return n&&n.padAngle}function so(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function fo(n,t,e,r,u){var i=n[0]-t[0],o=n[1]-t[1],a=(u?r:-r)/Math.sqrt(i*i+o*o),c=a*o,l=-a*i,s=n[0]+c,f=n[1]+l,h=t[0]+c,g=t[1]+l,p=(s+h)/2,v=(f+g)/2,d=h-s,m=g-f,y=d*d+m*m,M=e-r,x=s*g-h*f,b=(0>m?-1:1)*Math.sqrt(M*M*y-x*x),_=(x*m-d*b)/y,w=(-x*d-m*b)/y,S=(x*m+d*b)/y,k=(-x*d+m*b)/y,E=_-p,A=w-v,N=S-p,C=k-v;return E*E+A*A>N*N+C*C&&(_=S,w=k),[[_-c,w-l],[_*e/M,w*e/M]]}function ho(n){function t(t){function o(){l.push("M",i(n(s),a))}for(var c,l=[],s=[],f=-1,h=t.length,g=Et(e),p=Et(r);++f1&&u.push("H",r[0]),u.join("")}function mo(n){for(var t=0,e=n.length,r=n[0],u=[r[0],",",r[1]];++t1){a=t[1],i=n[c],c++,r+="C"+(u[0]+o[0])+","+(u[1]+o[1])+","+(i[0]-a[0])+","+(i[1]-a[1])+","+i[0]+","+i[1];for(var l=2;l9&&(u=3*t/Math.sqrt(u),o[a]=u*e,o[a+1]=u*r));for(a=-1;++a<=c;)u=(n[Math.min(c,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),i.push([u||0,o[a]*u||0]);return i}function To(n){return n.length<3?go(n):n[0]+_o(n,Lo(n))}function Ro(n){for(var t,e,r,u=-1,i=n.length;++ur)return s();var u=i[i.active];u&&(--i.count,delete i[i.active],u.event&&u.event.interrupt.call(n,n.__data__,u.index)),i.active=r,o.event&&o.event.start.call(n,n.__data__,t),o.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&v.push(r)}),h=o.ease,f=o.duration,ta.timer(function(){return p.c=l(e||1)?Ne:l,1},0,a)}function l(e){if(i.active!==r)return 1;for(var u=e/f,a=h(u),c=v.length;c>0;)v[--c].call(n,a);return u>=1?(o.event&&o.event.end.call(n,n.__data__,t),s()):void 0}function s(){return--i.count?delete i[r]:delete n[e],1}var f,h,g=o.delay,p=ec,v=[];return p.t=g+a,u>=g?c(u-g):void(p.c=c)},0,a)}}function Bo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function Wo(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function Jo(n){return n.toISOString()}function Go(n,t,e){function r(t){return n(t)}function u(n,e){var r=n[1]-n[0],u=r/e,i=ta.bisect(Vl,u);return i==Vl.length?[t.year,Vi(n.map(function(n){return n/31536e6}),e)[2]]:i?t[u/Vl[i-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=Ko(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=Ko(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Pi(r.domain()),i=null==n?u(e,10):"number"==typeof n?u(e,n):!n.range&&[{range:n},t];return i&&(n=i[0],t=i[1]),n.range(e[0],Ko(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return Go(n.copy(),t,e)},Yi(r,n)}function Ko(n){return new Date(n)}function Qo(n){return JSON.parse(n.responseText)}function na(n){var t=ua.createRange();return t.selectNode(ua.body),t.createContextualFragment(n.responseText)}var ta={version:"3.5.5"},ea=[].slice,ra=function(n){return ea.call(n)},ua=this.document;if(ua)try{ra(ua.documentElement.childNodes)[0].nodeType}catch(ia){ra=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),ua)try{ua.createElement("DIV").style.setProperty("opacity",0,"")}catch(oa){var aa=this.Element.prototype,ca=aa.setAttribute,la=aa.setAttributeNS,sa=this.CSSStyleDeclaration.prototype,fa=sa.setProperty;aa.setAttribute=function(n,t){ca.call(this,n,t+"")},aa.setAttributeNS=function(n,t,e){la.call(this,n,t,e+"")},sa.setProperty=function(n,t,e){fa.call(this,n,t+"",e)}}ta.ascending=e,ta.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:0/0},ta.min=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ur&&(e=r)}else{for(;++u=r){e=r;break}for(;++ur&&(e=r)}return e},ta.max=function(n,t){var e,r,u=-1,i=n.length;if(1===arguments.length){for(;++u=r){e=r;break}for(;++ue&&(e=r)}else{for(;++u=r){e=r;break}for(;++ue&&(e=r)}return e},ta.extent=function(n,t){var e,r,u,i=-1,o=n.length;if(1===arguments.length){for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}else{for(;++i=r){e=u=r;break}for(;++ir&&(e=r),r>u&&(u=r))}return[e,u]},ta.sum=function(n,t){var e,r=0,i=n.length,o=-1;if(1===arguments.length)for(;++o1?c/(s-1):void 0},ta.deviation=function(){var n=ta.variance.apply(this,arguments);return n?Math.sqrt(n):n};var ha=i(e);ta.bisectLeft=ha.left,ta.bisect=ta.bisectRight=ha.right,ta.bisector=function(n){return i(1===n.length?function(t,r){return e(n(t),r)}:n)},ta.shuffle=function(n,t,e){(i=arguments.length)<3&&(e=n.length,2>i&&(t=0));for(var r,u,i=e-t;i;)u=Math.random()*i--|0,r=n[i+t],n[i+t]=n[u+t],n[u+t]=r;return n},ta.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ta.pairs=function(n){for(var t,e=0,r=n.length-1,u=n[0],i=new Array(0>r?0:r);r>e;)i[e]=[t=u,u=n[++e]];return i},ta.zip=function(){if(!(r=arguments.length))return[];for(var n=-1,t=ta.min(arguments,o),e=new Array(t);++n=0;)for(r=n[u],t=r.length;--t>=0;)e[--o]=r[t];return e};var ga=Math.abs;ta.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,u=[],i=a(ga(e)),o=-1;if(n*=i,t*=i,e*=i,0>e)for(;(r=n+e*++o)>t;)u.push(r/i);else for(;(r=n+e*++o)=i.length)return r?r.call(u,o):e?o.sort(e):o;for(var c,s,f,h,g=-1,p=o.length,v=i[a++],d=new l;++g=i.length)return n;var r=[],u=o[e++];return n.forEach(function(n,u){r.push({key:n,values:t(u,e)})}),u?r.sort(function(n,t){return u(n.key,t.key)}):r}var e,r,u={},i=[],o=[];return u.map=function(t,e){return n(e,t,0)},u.entries=function(e){return t(n(ta.map,e,0),0)},u.key=function(n){return i.push(n),u},u.sortKeys=function(n){return o[i.length-1]=n,u},u.sortValues=function(n){return e=n,u},u.rollup=function(n){return r=n,u},u},ta.set=function(n){var t=new m;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},c(m,{has:h,add:function(n){return this._[s(n+="")]=!0,n},remove:g,values:p,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,f(t))}}),ta.behavior={},ta.rebind=function(n,t){for(var e,r=1,u=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ta.event=null,ta.requote=function(n){return n.replace(ma,"\\$&")};var ma=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ya={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},Ma=function(n,t){return t.querySelector(n)},xa=function(n,t){return t.querySelectorAll(n)},ba=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(ba=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(Ma=function(n,t){return Sizzle(n,t)[0]||null},xa=Sizzle,ba=Sizzle.matchesSelector),ta.selection=function(){return ta.select(ua.documentElement)};var _a=ta.selection.prototype=[];_a.select=function(n){var t,e,r,u,i=[];n=N(n);for(var o=-1,a=this.length;++o=0&&(e=n.slice(0,t),n=n.slice(t+1)),wa.hasOwnProperty(e)?{space:wa[e],local:n}:n}},_a.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ta.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},_a.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,u=-1;if(t=e.classList){for(;++uu){if("string"!=typeof n){2>u&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>u){var i=this.node();return t(i).getComputedStyle(i,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},_a.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},_a.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},_a.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},_a.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},_a.insert=function(n,t){return n=j(n),t=N(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},_a.remove=function(){return this.each(F)},_a.data=function(n,t){function e(n,e){var r,u,i,o=n.length,f=e.length,h=Math.min(o,f),g=new Array(f),p=new Array(f),v=new Array(o);if(t){var d,m=new l,y=new Array(o);for(r=-1;++rr;++r)p[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}p.update=g,p.parentNode=g.parentNode=v.parentNode=n.parentNode,a.push(p),c.push(g),s.push(v)}var r,u,i=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++ii;i++){u.push(t=[]),t.parentNode=(e=this[i]).parentNode;for(var a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return A(u)},_a.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[u])&&(i&&i!==e.nextSibling&&i.parentNode.insertBefore(e,i),i=e);return this},_a.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,u=e.length;u>r;r++){var i=e[r];if(i)return i}return null},_a.size=function(){var n=0;return Y(this,function(){++n}),n};var Sa=[];ta.selection.enter=Z,ta.selection.enter.prototype=Sa,Sa.append=_a.append,Sa.empty=_a.empty,Sa.node=_a.node,Sa.call=_a.call,Sa.size=_a.size,Sa.select=function(n){for(var t,e,r,u,i,o=[],a=-1,c=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var ka=ta.map({mouseenter:"mouseover",mouseleave:"mouseout"});ua&&ka.forEach(function(n){"on"+n in ua&&ka.remove(n)});var Ea,Aa=0;ta.mouse=function(n){return J(n,k())};var Na=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ta.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,u=0,i=t.length;i>u;++u)if((r=t[u]).identifier===e)return J(n,r)},ta.behavior.drag=function(){function n(){this.on("mousedown.drag",i).on("touchstart.drag",o)}function e(n,t,e,i,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],p|=n|e,M=r,g({type:"drag",x:r[0]+l[0],y:r[1]+l[1],dx:n,dy:e}))}function c(){t(h,v)&&(m.on(i+d,null).on(o+d,null),y(p&&ta.event.target===f),g({type:"dragend"}))}var l,s=this,f=ta.event.target,h=s.parentNode,g=r.of(s,arguments),p=0,v=n(),d=".drag"+(null==v?"":"-"+v),m=ta.select(e(f)).on(i+d,a).on(o+d,c),y=W(f),M=t(h,v);u?(l=u.apply(s,arguments),l=[l.x-M[0],l.y-M[1]]):l=[0,0],g({type:"dragstart"})}}var r=E(n,"drag","dragstart","dragend"),u=null,i=e(b,ta.mouse,t,"mousemove","mouseup"),o=e(G,ta.touch,y,"touchmove","touchend");return n.origin=function(t){return arguments.length?(u=t,n):u},ta.rebind(n,r,"on")},ta.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?ra(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Ca=1e-6,za=Ca*Ca,qa=Math.PI,La=2*qa,Ta=La-Ca,Ra=qa/2,Da=qa/180,Pa=180/qa,Ua=Math.SQRT2,ja=2,Fa=4;ta.interpolateZoom=function(n,t){function e(n){var t=n*y;if(m){var e=rt(v),o=i/(ja*h)*(e*ut(Ua*t+v)-et(v));return[r+o*l,u+o*s,i*e/rt(Ua*t+v)]}return[r+n*l,u+n*s,i*Math.exp(Ua*t)]}var r=n[0],u=n[1],i=n[2],o=t[0],a=t[1],c=t[2],l=o-r,s=a-u,f=l*l+s*s,h=Math.sqrt(f),g=(c*c-i*i+Fa*f)/(2*i*ja*h),p=(c*c-i*i-Fa*f)/(2*c*ja*h),v=Math.log(Math.sqrt(g*g+1)-g),d=Math.log(Math.sqrt(p*p+1)-p),m=d-v,y=(m||Math.log(c/i))/Ua;return e.duration=1e3*y,e},ta.behavior.zoom=function(){function n(n){n.on(q,f).on(Oa+".zoom",g).on("dblclick.zoom",p).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function u(n){k.k=Math.max(N[0],Math.min(N[1],n))}function i(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},u(Math.pow(2,o)),i(d=e,r),t=ta.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function c(n){z++||n({type:"zoomstart"})}function l(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function s(n){--z||n({type:"zoomend"}),d=null}function f(){function n(){f=1,i(ta.mouse(u),g),l(a)}function r(){h.on(L,null).on(T,null),p(f&&ta.event.target===o),s(a)}var u=this,o=ta.event.target,a=D.of(u,arguments),f=0,h=ta.select(t(u)).on(L,n).on(T,r),g=e(ta.mouse(u)),p=W(u);Dl.call(u),c(a)}function h(){function n(){var n=ta.touches(p);return g=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ta.event.target;ta.select(t).on(x,r).on(b,a),_.push(t);for(var e=ta.event.changedTouches,u=0,i=e.length;i>u;++u)d[e[u].identifier]=null;var c=n(),l=Date.now();if(1===c.length){if(500>l-M){var s=c[0];o(p,s,d[s.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=l}else if(c.length>1){var s=c[0],f=c[1],h=s[0]-f[0],g=s[1]-f[1];m=h*h+g*g}}function r(){var n,t,e,r,o=ta.touches(p);Dl.call(p);for(var a=0,c=o.length;c>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var s=(s=e[0]-n[0])*s+(s=e[1]-n[1])*s,f=m&&Math.sqrt(s/m);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],u(f*g)}M=null,i(n,t),l(v)}function a(){if(ta.event.touches.length){for(var t=ta.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var u in d)return void n()}ta.selectAll(_).on(y,null),w.on(q,f).on(R,h),E(),s(v)}var g,p=this,v=D.of(p,arguments),d={},m=0,y=".zoom-"+ta.event.changedTouches[0].identifier,x="touchmove"+y,b="touchend"+y,_=[],w=ta.select(p),E=W(p);t(),c(v),w.on(q,null).on(R,t)}function g(){var n=D.of(this,arguments);y?clearTimeout(y):(v=e(d=m||ta.mouse(this)),Dl.call(this),c(n)),y=setTimeout(function(){y=null,s(n)},50),S(),u(Math.pow(2,.002*Ha())*k.k),i(d,v),l(n)}function p(){var n=ta.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ta.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,m,y,M,x,b,_,w,k={x:0,y:0,k:1},A=[960,500],N=Ia,C=250,z=0,q="mousedown.zoom",L="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=E(n,"zoomstart","zoom","zoomend");return Oa||(Oa="onwheel"in ua?(Ha=function(){return-ta.event.deltaY*(ta.event.deltaMode?120:1)},"wheel"):"onmousewheel"in ua?(Ha=function(){return ta.event.wheelDelta},"mousewheel"):(Ha=function(){return-ta.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Tl?ta.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},c(n)}).tween("zoom:zoom",function(){var e=A[0],r=A[1],u=d?d[0]:e/2,i=d?d[1]:r/2,o=ta.interpolateZoom([(u-k.x)/k.k,(i-k.y)/k.k,e/k.k],[(u-t.x)/t.k,(i-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:u-r[0]*a,y:i-r[1]*a,k:a},l(n)}}).each("interrupt.zoom",function(){s(n)}).each("end.zoom",function(){s(n)}):(this.__chart__=k,c(n),l(n),s(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:+t},a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(N=null==t?Ia:[+t[0],+t[1]],n):N},n.center=function(t){return arguments.length?(m=t&&[+t[0],+t[1]],n):m},n.size=function(t){return arguments.length?(A=t&&[+t[0],+t[1]],n):A},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ta.rebind(n,D,"on")};var Ha,Oa,Ia=[0,1/0];ta.color=ot,ot.prototype.toString=function(){return this.rgb()+""},ta.hsl=at;var Ya=at.prototype=new ot;Ya.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,this.l/n)},Ya.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new at(this.h,this.s,n*this.l)},Ya.rgb=function(){return ct(this.h,this.s,this.l)},ta.hcl=lt;var Za=lt.prototype=new ot;Za.brighter=function(n){return new lt(this.h,this.c,Math.min(100,this.l+Va*(arguments.length?n:1)))},Za.darker=function(n){return new lt(this.h,this.c,Math.max(0,this.l-Va*(arguments.length?n:1)))},Za.rgb=function(){return st(this.h,this.c,this.l).rgb()},ta.lab=ft;var Va=18,Xa=.95047,$a=1,Ba=1.08883,Wa=ft.prototype=new ot;Wa.brighter=function(n){return new ft(Math.min(100,this.l+Va*(arguments.length?n:1)),this.a,this.b)},Wa.darker=function(n){return new ft(Math.max(0,this.l-Va*(arguments.length?n:1)),this.a,this.b)},Wa.rgb=function(){return ht(this.l,this.a,this.b)},ta.rgb=mt;var Ja=mt.prototype=new ot;Ja.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,u=30;return t||e||r?(t&&u>t&&(t=u),e&&u>e&&(e=u),r&&u>r&&(r=u),new mt(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mt(u,u,u)},Ja.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mt(n*this.r,n*this.g,n*this.b)},Ja.hsl=function(){return _t(this.r,this.g,this.b)},Ja.toString=function(){return"#"+xt(this.r)+xt(this.g)+xt(this.b)};var Ga=ta.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});Ga.forEach(function(n,t){Ga.set(n,yt(t))}),ta.functor=Et,ta.xhr=At(y),ta.dsv=function(n,t){function e(n,e,i){arguments.length<3&&(i=e,e=null);var o=Nt(n,t,null==e?r:u(e),i);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:u(n)):e},o}function r(n){return e.parse(n.responseText)}function u(n){return function(t){return e.parse(t.responseText,n)}}function i(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),c=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var u=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(u(n),e)}:u})},e.parseRows=function(n,t){function e(){if(s>=l)return o;if(u)return u=!1,i;var t=s;if(34===n.charCodeAt(t)){for(var e=t;e++s;){var r=n.charCodeAt(s++),a=1;if(10===r)u=!0;else if(13===r)u=!0,10===n.charCodeAt(s)&&(++s,++a);else if(r!==c)continue;return n.slice(t,s-a)}return n.slice(t)}for(var r,u,i={},o={},a=[],l=n.length,s=0,f=0;(r=e())!==o;){for(var h=[];r!==i&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,f++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new m,u=[];return t.forEach(function(n){for(var t in n)r.has(t)||u.push(r.add(t))}),[u.map(o).join(n)].concat(t.map(function(t){return u.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(i).join("\n")},e},ta.csv=ta.dsv(",","text/csv"),ta.tsv=ta.dsv(" ","text/tab-separated-values");var Ka,Qa,nc,tc,ec,rc=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ta.timer=function(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var u=e+t,i={c:n,t:u,f:!1,n:null};Qa?Qa.n=i:Ka=i,Qa=i,nc||(tc=clearTimeout(tc),nc=1,rc(qt))},ta.timer.flush=function(){Lt(),Tt()},ta.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var uc=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Dt);ta.formatPrefix=function(n,t){var e=0;return n&&(0>n&&(n*=-1),t&&(n=ta.round(n,Rt(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),uc[8+e/3]};var ic=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,oc=ta.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ta.round(n,Rt(n,t))).toFixed(Math.max(0,Math.min(20,Rt(n*(1+1e-15),t))))}}),ac=ta.time={},cc=Date;jt.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){lc.setUTCDate.apply(this._,arguments)},setDay:function(){lc.setUTCDay.apply(this._,arguments)},setFullYear:function(){lc.setUTCFullYear.apply(this._,arguments)},setHours:function(){lc.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){lc.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){lc.setUTCMinutes.apply(this._,arguments)},setMonth:function(){lc.setUTCMonth.apply(this._,arguments)},setSeconds:function(){lc.setUTCSeconds.apply(this._,arguments)},setTime:function(){lc.setTime.apply(this._,arguments)}};var lc=Date.prototype;ac.year=Ft(function(n){return n=ac.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ac.years=ac.year.range,ac.years.utc=ac.year.utc.range,ac.day=Ft(function(n){var t=new cc(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ac.days=ac.day.range,ac.days.utc=ac.day.utc.range,ac.dayOfYear=function(n){var t=ac.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ac[n]=Ft(function(n){return(n=ac.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ac.year(n).getDay();return Math.floor((ac.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ac[n+"s"]=e.range,ac[n+"s"].utc=e.utc.range,ac[n+"OfYear"]=function(n){var e=ac.year(n).getDay();return Math.floor((ac.dayOfYear(n)+(e+t)%7)/7)}}),ac.week=ac.sunday,ac.weeks=ac.sunday.range,ac.weeks.utc=ac.sunday.utc.range,ac.weekOfYear=ac.sundayOfYear;var sc={"-":"",_:" ",0:"0"},fc=/^\s*\d+/,hc=/^%/;ta.locale=function(n){return{numberFormat:Pt(n),timeFormat:Ot(n)}};var gc=ta.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ta.format=gc.numberFormat,ta.geo={},ce.prototype={s:0,t:0,add:function(n){le(n,this.t,pc),le(pc.s,this.s,this),this.s?this.t+=pc.t:this.s=pc.t +},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var pc=new ce;ta.geo.stream=function(n,t){n&&vc.hasOwnProperty(n.type)?vc[n.type](n,t):se(n,t)};var vc={Feature:function(n,t){se(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,u=e.length;++rn?4*qa+n:n,Mc.lineStart=Mc.lineEnd=Mc.point=b}};ta.geo.bounds=function(){function n(n,t){M.push(x=[s=n,h=n]),f>t&&(f=t),t>g&&(g=t)}function t(t,e){var r=pe([t*Da,e*Da]);if(m){var u=de(m,r),i=[u[1],-u[0],0],o=de(i,u);Me(o),o=xe(o);var c=t-p,l=c>0?1:-1,v=o[0]*Pa*l,d=ga(c)>180;if(d^(v>l*p&&l*t>v)){var y=o[1]*Pa;y>g&&(g=y)}else if(v=(v+360)%360-180,d^(v>l*p&&l*t>v)){var y=-o[1]*Pa;f>y&&(f=y)}else f>e&&(f=e),e>g&&(g=e);d?p>t?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t):h>=s?(s>t&&(s=t),t>h&&(h=t)):t>p?a(s,t)>a(s,h)&&(h=t):a(t,h)>a(s,h)&&(s=t)}else n(t,e);m=r,p=t}function e(){b.point=t}function r(){x[0]=s,x[1]=h,b.point=n,m=null}function u(n,e){if(m){var r=n-p;y+=ga(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Mc.point(n,e),t(n,e)}function i(){Mc.lineStart()}function o(){u(v,d),Mc.lineEnd(),ga(y)>Ca&&(s=-(h=180)),x[0]=s,x[1]=h,m=null}function a(n,t){return(t-=n)<0?t+360:t}function c(n,t){return n[0]-t[0]}function l(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nyc?(s=-(h=180),f=-(g=90)):y>Ca?g=90:-Ca>y&&(f=-90),x[0]=s,x[1]=h}};return function(n){g=h=-(s=f=1/0),M=[],ta.geo.stream(n,b);var t=M.length;if(t){M.sort(c);for(var e,r=1,u=M[0],i=[u];t>r;++r)e=M[r],l(e[0],u)||l(e[1],u)?(a(u[0],e[1])>a(u[0],u[1])&&(u[1]=e[1]),a(e[0],u[1])>a(u[0],u[1])&&(u[0]=e[0])):i.push(u=e);for(var o,e,p=-1/0,t=i.length-1,r=0,u=i[t];t>=r;u=e,++r)e=i[r],(o=a(u[1],e[0]))>p&&(p=o,s=e[0],h=u[1])}return M=x=null,1/0===s||1/0===f?[[0/0,0/0],[0/0,0/0]]:[[s,f],[h,g]]}}(),ta.geo.centroid=function(n){xc=bc=_c=wc=Sc=kc=Ec=Ac=Nc=Cc=zc=0,ta.geo.stream(n,qc);var t=Nc,e=Cc,r=zc,u=t*t+e*e+r*r;return za>u&&(t=kc,e=Ec,r=Ac,Ca>bc&&(t=_c,e=wc,r=Sc),u=t*t+e*e+r*r,za>u)?[0/0,0/0]:[Math.atan2(e,t)*Pa,tt(r/Math.sqrt(u))*Pa]};var xc,bc,_c,wc,Sc,kc,Ec,Ac,Nc,Cc,zc,qc={sphere:b,point:_e,lineStart:Se,lineEnd:ke,polygonStart:function(){qc.lineStart=Ee},polygonEnd:function(){qc.lineStart=Se}},Lc=Le(Ne,Pe,je,[-qa,-qa/2]),Tc=1e9;ta.geo.clipExtent=function(){var n,t,e,r,u,i,o={stream:function(n){return u&&(u.valid=!1),u=i(n),u.valid=!0,u},extent:function(a){return arguments.length?(i=Ie(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),u&&(u.valid=!1,u=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ta.geo.conicEqualArea=function(){return Ye(Ze)}).raw=Ze,ta.geo.albers=function(){return ta.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ta.geo.albersUsa=function(){function n(n){var i=n[0],o=n[1];return t=null,e(i,o),t||(r(i,o),t)||u(i,o),t}var t,e,r,u,i=ta.geo.albers(),o=ta.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ta.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),c={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=i.scale(),e=i.translate(),r=(n[0]-e[0])/t,u=(n[1]-e[1])/t;return(u>=.12&&.234>u&&r>=-.425&&-.214>r?o:u>=.166&&.234>u&&r>=-.214&&-.115>r?a:i).invert(n)},n.stream=function(n){var t=i.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,u){t.point(n,u),e.point(n,u),r.point(n,u)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(i.precision(t),o.precision(t),a.precision(t),n):i.precision()},n.scale=function(t){return arguments.length?(i.scale(t),o.scale(.35*t),a.scale(t),n.translate(i.translate())):i.scale()},n.translate=function(t){if(!arguments.length)return i.translate();var l=i.scale(),s=+t[0],f=+t[1];return e=i.translate(t).clipExtent([[s-.455*l,f-.238*l],[s+.455*l,f+.238*l]]).stream(c).point,r=o.translate([s-.307*l,f+.201*l]).clipExtent([[s-.425*l+Ca,f+.12*l+Ca],[s-.214*l-Ca,f+.234*l-Ca]]).stream(c).point,u=a.translate([s-.205*l,f+.212*l]).clipExtent([[s-.214*l+Ca,f+.166*l+Ca],[s-.115*l-Ca,f+.234*l-Ca]]).stream(c).point,n},n.scale(1070)};var Rc,Dc,Pc,Uc,jc,Fc,Hc={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Dc=0,Hc.lineStart=Ve},polygonEnd:function(){Hc.lineStart=Hc.lineEnd=Hc.point=b,Rc+=ga(Dc/2)}},Oc={point:Xe,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Ic={point:We,lineStart:Je,lineEnd:Ge,polygonStart:function(){Ic.lineStart=Ke},polygonEnd:function(){Ic.point=We,Ic.lineStart=Je,Ic.lineEnd=Ge}};ta.geo.path=function(){function n(n){return n&&("function"==typeof a&&i.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=u(i)),ta.geo.stream(n,o)),i.result()}function t(){return o=null,n}var e,r,u,i,o,a=4.5;return n.area=function(n){return Rc=0,ta.geo.stream(n,u(Hc)),Rc},n.centroid=function(n){return _c=wc=Sc=kc=Ec=Ac=Nc=Cc=zc=0,ta.geo.stream(n,u(Ic)),zc?[Nc/zc,Cc/zc]:Ac?[kc/Ac,Ec/Ac]:Sc?[_c/Sc,wc/Sc]:[0/0,0/0]},n.bounds=function(n){return jc=Fc=-(Pc=Uc=1/0),ta.geo.stream(n,u(Oc)),[[Pc,Uc],[jc,Fc]]},n.projection=function(n){return arguments.length?(u=(e=n)?n.stream||tr(n):y,t()):e},n.context=function(n){return arguments.length?(i=null==(r=n)?new $e:new Qe(n),"function"!=typeof a&&i.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(i.pointRadius(+t),+t),n):a},n.projection(ta.geo.albersUsa()).context(null)},ta.geo.transform=function(n){return{stream:function(t){var e=new er(t);for(var r in n)e[r]=n[r];return e}}},er.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ta.geo.projection=ur,ta.geo.projectionMutator=ir,(ta.geo.equirectangular=function(){return ur(ar)}).raw=ar.invert=ar,ta.geo.rotation=function(n){function t(t){return t=n(t[0]*Da,t[1]*Da),t[0]*=Pa,t[1]*=Pa,t}return n=lr(n[0]%360*Da,n[1]*Da,n.length>2?n[2]*Da:0),t.invert=function(t){return t=n.invert(t[0]*Da,t[1]*Da),t[0]*=Pa,t[1]*=Pa,t},t},cr.invert=ar,ta.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=lr(-n[0]*Da,-n[1]*Da,0).invert,u=[];return e(null,null,1,{point:function(n,e){u.push(n=t(n,e)),n[0]*=Pa,n[1]*=Pa}}),{type:"Polygon",coordinates:[u]}}var t,e,r=[0,0],u=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=gr((t=+r)*Da,u*Da),n):t},n.precision=function(r){return arguments.length?(e=gr(t*Da,(u=+r)*Da),n):u},n.angle(90)},ta.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Da,u=n[1]*Da,i=t[1]*Da,o=Math.sin(r),a=Math.cos(r),c=Math.sin(u),l=Math.cos(u),s=Math.sin(i),f=Math.cos(i);return Math.atan2(Math.sqrt((e=f*o)*e+(e=l*s-c*f*a)*e),c*s+l*f*a)},ta.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ta.range(Math.ceil(i/d)*d,u,d).map(h).concat(ta.range(Math.ceil(l/m)*m,c,m).map(g)).concat(ta.range(Math.ceil(r/p)*p,e,p).filter(function(n){return ga(n%d)>Ca}).map(s)).concat(ta.range(Math.ceil(a/v)*v,o,v).filter(function(n){return ga(n%m)>Ca}).map(f))}var e,r,u,i,o,a,c,l,s,f,h,g,p=10,v=p,d=90,m=360,y=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(i).concat(g(c).slice(1),h(u).reverse().slice(1),g(l).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(i=+t[0][0],u=+t[1][0],l=+t[0][1],c=+t[1][1],i>u&&(t=i,i=u,u=t),l>c&&(t=l,l=c,c=t),n.precision(y)):[[i,l],[u,c]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(y)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],m=+t[1],n):[d,m]},n.minorStep=function(t){return arguments.length?(p=+t[0],v=+t[1],n):[p,v]},n.precision=function(t){return arguments.length?(y=+t,s=vr(a,o,90),f=dr(r,e,y),h=vr(l,c,90),g=dr(i,u,y),n):y},n.majorExtent([[-180,-90+Ca],[180,90-Ca]]).minorExtent([[-180,-80-Ca],[180,80+Ca]])},ta.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||u.apply(this,arguments)]}}var t,e,r=mr,u=yr;return n.distance=function(){return ta.geo.distance(t||r.apply(this,arguments),e||u.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(u=t,e="function"==typeof t?null:t,n):u},n.precision=function(){return arguments.length?n:0},n},ta.geo.interpolate=function(n,t){return Mr(n[0]*Da,n[1]*Da,t[0]*Da,t[1]*Da)},ta.geo.length=function(n){return Yc=0,ta.geo.stream(n,Zc),Yc};var Yc,Zc={sphere:b,point:b,lineStart:xr,lineEnd:b,polygonStart:b,polygonEnd:b},Vc=br(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ta.geo.azimuthalEqualArea=function(){return ur(Vc)}).raw=Vc;var Xc=br(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},y);(ta.geo.azimuthalEquidistant=function(){return ur(Xc)}).raw=Xc,(ta.geo.conicConformal=function(){return Ye(_r)}).raw=_r,(ta.geo.conicEquidistant=function(){return Ye(wr)}).raw=wr;var $c=br(function(n){return 1/n},Math.atan);(ta.geo.gnomonic=function(){return ur($c)}).raw=$c,Sr.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Ra]},(ta.geo.mercator=function(){return kr(Sr)}).raw=Sr;var Bc=br(function(){return 1},Math.asin);(ta.geo.orthographic=function(){return ur(Bc)}).raw=Bc;var Wc=br(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ta.geo.stereographic=function(){return ur(Wc)}).raw=Wc,Er.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Ra]},(ta.geo.transverseMercator=function(){var n=kr(Er),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Er,ta.geom={},ta.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,u=Et(e),i=Et(r),o=n.length,a=[],c=[];for(t=0;o>t;t++)a.push([+u.call(this,n[t],t),+i.call(this,n[t],t),t]);for(a.sort(zr),t=0;o>t;t++)c.push([a[t][0],-a[t][1]]);var l=Cr(a),s=Cr(c),f=s[0]===l[0],h=s[s.length-1]===l[l.length-1],g=[];for(t=l.length-1;t>=0;--t)g.push(n[a[l[t]][2]]);for(t=+f;t=r&&l.x<=i&&l.y>=u&&l.y<=o?[[r,o],[i,o],[i,u],[r,u]]:[];s.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(i(n,t)/Ca)*Ca,y:Math.round(o(n,t)/Ca)*Ca,i:t}})}var r=Ar,u=Nr,i=r,o=u,a=ul;return n?t(n):(t.links=function(n){return iu(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return iu(e(n)).cells.forEach(function(e,r){for(var u,i,o=e.site,a=e.edges.sort(Yr),c=-1,l=a.length,s=a[l-1].edge,f=s.l===o?s.r:s.l;++c=l,h=r>=s,g=h<<1|f;n.leaf=!1,n=n.nodes[g]||(n.nodes[g]=su()),f?u=l:a=l,h?o=s:c=s,i(n,t,e,r,u,o,a,c)}var s,f,h,g,p,v,d,m,y,M=Et(a),x=Et(c);if(null!=t)v=t,d=e,m=r,y=u;else if(m=y=-(v=d=1/0),f=[],h=[],p=n.length,o)for(g=0;p>g;++g)s=n[g],s.xm&&(m=s.x),s.y>y&&(y=s.y),f.push(s.x),h.push(s.y);else for(g=0;p>g;++g){var b=+M(s=n[g],g),_=+x(s,g);v>b&&(v=b),d>_&&(d=_),b>m&&(m=b),_>y&&(y=_),f.push(b),h.push(_)}var w=m-v,S=y-d;w>S?y=d+w:m=v+S;var k=su();if(k.add=function(n){i(k,n,+M(n,++g),+x(n,g),v,d,m,y)},k.visit=function(n){fu(n,k,v,d,m,y)},k.find=function(n){return hu(k,n[0],n[1],v,d,m,y)},g=-1,null==t){for(;++g=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=cl.get(e)||al,r=ll.get(r)||y,Mu(r(e.apply(null,ea.call(arguments,1))))},ta.interpolateHcl=Lu,ta.interpolateHsl=Tu,ta.interpolateLab=Ru,ta.interpolateRound=Du,ta.transform=function(n){var t=ua.createElementNS(ta.ns.prefix.svg,"g");return(ta.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new Pu(e?e.matrix:sl)})(n)},Pu.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var sl={a:1,b:0,c:0,d:1,e:0,f:0};ta.interpolateTransform=Hu,ta.layout={},ta.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/d){if(p>c){var l=t.charge/c;n.px-=i*l,n.py-=o*l}return!0}if(t.point&&c&&p>c){var l=t.pointCharge/c;n.px-=i*l,n.py-=o*l}}return!t.charge}}function t(n){n.px=ta.event.x,n.py=ta.event.y,a.resume()}var e,r,u,i,o,a={},c=ta.dispatch("start","tick","end"),l=[1,1],s=.9,f=fl,h=hl,g=-30,p=gl,v=.1,d=.64,m=[],M=[];return a.tick=function(){if((r*=.99)<.005)return c.end({type:"end",alpha:r=0}),!0;var t,e,a,f,h,p,d,y,x,b=m.length,_=M.length;for(e=0;_>e;++e)a=M[e],f=a.source,h=a.target,y=h.x-f.x,x=h.y-f.y,(p=y*y+x*x)&&(p=r*i[e]*((p=Math.sqrt(p))-u[e])/p,y*=p,x*=p,h.x-=y*(d=f.weight/(h.weight+f.weight)),h.y-=x*d,f.x+=y*(d=1-d),f.y+=x*d);if((d=r*v)&&(y=l[0]/2,x=l[1]/2,e=-1,d))for(;++e0?n:0:n>0&&(c.start({type:"start",alpha:r=n}),ta.timer(a.tick)),a):r},a.start=function(){function n(n,r){if(!e){for(e=new Array(c),a=0;c>a;++a)e[a]=[];for(a=0;s>a;++a){var u=M[a];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var i,o=e[t],a=-1,l=o.length;++at;++t)(r=m[t]).index=t,r.weight=0;for(t=0;s>t;++t)r=M[t],"number"==typeof r.source&&(r.source=m[r.source]),"number"==typeof r.target&&(r.target=m[r.target]),++r.source.weight,++r.target.weight;for(t=0;c>t;++t)r=m[t],isNaN(r.x)&&(r.x=n("x",p)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof f)for(t=0;s>t;++t)u[t]=+f.call(this,M[t],t);else for(t=0;s>t;++t)u[t]=f;if(i=[],"function"==typeof h)for(t=0;s>t;++t)i[t]=+h.call(this,M[t],t);else for(t=0;s>t;++t)i[t]=h;if(o=[],"function"==typeof g)for(t=0;c>t;++t)o[t]=+g.call(this,m[t],t);else for(t=0;c>t;++t)o[t]=g;return a.resume()},a.resume=function(){return a.alpha(.1)},a.stop=function(){return a.alpha(0)},a.drag=function(){return e||(e=ta.behavior.drag().origin(y).on("dragstart.force",Xu).on("drag.force",t).on("dragend.force",$u)),arguments.length?void this.on("mouseover.force",Bu).on("mouseout.force",Wu).call(e):e},ta.rebind(a,c,"on")};var fl=20,hl=1,gl=1/0;ta.layout.hierarchy=function(){function n(u){var i,o=[u],a=[];for(u.depth=0;null!=(i=o.pop());)if(a.push(i),(l=e.call(n,i,i.depth))&&(c=l.length)){for(var c,l,s;--c>=0;)o.push(s=l[c]),s.parent=i,s.depth=i.depth+1;r&&(i.value=0),i.children=l}else r&&(i.value=+r.call(n,i,i.depth)||0),delete i.children;return Qu(u,function(n){var e,u;t&&(e=n.children)&&e.sort(t),r&&(u=n.parent)&&(u.value+=n.value)}),a}var t=ei,e=ni,r=ti;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(Ku(t,function(n){n.children&&(n.value=0)}),Qu(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ta.layout.partition=function(){function n(t,e,r,u){var i=t.children;if(t.x=e,t.y=t.depth*u,t.dx=r,t.dy=u,i&&(o=i.length)){var o,a,c,l=-1;for(r=t.value?r/t.value:0;++lf?-1:1),p=(f-c*g)/ta.sum(l),v=ta.range(c),d=[];return null!=e&&v.sort(e===pl?function(n,t){return l[t]-l[n]}:function(n,t){return e(o[n],o[t])}),v.forEach(function(n){d[n]={data:o[n],value:a=l[n],startAngle:s,endAngle:s+=a*p+g,padAngle:h}}),d}var t=Number,e=pl,r=0,u=La,i=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(u=t,n):u},n.padAngle=function(t){return arguments.length?(i=t,n):i},n};var pl={};ta.layout.stack=function(){function n(a,c){if(!(h=a.length))return a;var l=a.map(function(e,r){return t.call(n,e,r)}),s=l.map(function(t){return t.map(function(t,e){return[i.call(n,t,e),o.call(n,t,e)]})}),f=e.call(n,s,c);l=ta.permute(l,f),s=ta.permute(s,f);var h,g,p,v,d=r.call(n,s,c),m=l[0].length;for(p=0;m>p;++p)for(u.call(n,l[0][p],v=d[p],s[0][p][1]),g=1;h>g;++g)u.call(n,l[g][p],v+=s[g-1][p][1],s[g][p][1]);return a}var t=y,e=ai,r=ci,u=oi,i=ui,o=ii;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:vl.get(t)||ai,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:dl.get(t)||ci,n):r},n.x=function(t){return arguments.length?(i=t,n):i},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(u=t,n):u},n};var vl=ta.map({"inside-out":function(n){var t,e,r=n.length,u=n.map(li),i=n.map(si),o=ta.range(r).sort(function(n,t){return u[n]-u[t]}),a=0,c=0,l=[],s=[];for(t=0;r>t;++t)e=o[t],c>a?(a+=i[e],l.push(e)):(c+=i[e],s.push(e));return s.reverse().concat(l)},reverse:function(n){return ta.range(n.length).reverse()},"default":ai}),dl=ta.map({silhouette:function(n){var t,e,r,u=n.length,i=n[0].length,o=[],a=0,c=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;i>e;++e)c[e]=(a-o[e])/2;return c},wiggle:function(n){var t,e,r,u,i,o,a,c,l,s=n.length,f=n[0],h=f.length,g=[];for(g[0]=c=l=0,e=1;h>e;++e){for(t=0,u=0;s>t;++t)u+=n[t][e][1];for(t=0,i=0,a=f[e][0]-f[e-1][0];s>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;i+=o*n[t][e][1]}g[e]=c-=u?i/u*a:0,l>c&&(l=c)}for(e=0;h>e;++e)g[e]-=l;return g},expand:function(n){var t,e,r,u=n.length,i=n[0].length,o=1/u,a=[];for(e=0;i>e;++e){for(t=0,r=0;u>t;t++)r+=n[t][e][1];if(r)for(t=0;u>t;t++)n[t][e][1]/=r;else for(t=0;u>t;t++)n[t][e][1]=o}for(e=0;i>e;++e)a[e]=0;return a},zero:ci});ta.layout.histogram=function(){function n(n,i){for(var o,a,c=[],l=n.map(e,this),s=r.call(this,l,i),f=u.call(this,s,l,i),i=-1,h=l.length,g=f.length-1,p=t?1:1/h;++i0)for(i=-1;++i=s[0]&&a<=s[1]&&(o=c[ta.bisect(f,a,1,g)-1],o.y+=p,o.push(n[i]));return c}var t=!0,e=Number,r=pi,u=hi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=Et(t),n):r},n.bins=function(t){return arguments.length?(u="number"==typeof t?function(n){return gi(n,t)}:Et(t),n):u},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ta.layout.pack=function(){function n(n,i){var o=e.call(this,n,i),a=o[0],c=u[0],l=u[1],s=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,Qu(a,function(n){n.r=+s(n.value)}),Qu(a,Mi),r){var f=r*(t?1:Math.max(2*a.r/c,2*a.r/l))/2;Qu(a,function(n){n.r+=f}),Qu(a,Mi),Qu(a,function(n){n.r-=f})}return _i(a,c/2,l/2,t?1:1/Math.max(2*a.r/c,2*a.r/l)),o}var t,e=ta.layout.hierarchy().sort(vi),r=0,u=[1,1];return n.size=function(t){return arguments.length?(u=t,n):u},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},Gu(n,e)},ta.layout.tree=function(){function n(n,u){var s=o.call(this,n,u),f=s[0],h=t(f);if(Qu(h,e),h.parent.m=-h.z,Ku(h,r),l)Ku(f,i);else{var g=f,p=f,v=f;Ku(f,function(n){n.xp.x&&(p=n),n.depth>v.depth&&(v=n)});var d=a(g,p)/2-g.x,m=c[0]/(p.x+a(p,g)/2+d),y=c[1]/(v.depth||1);Ku(f,function(n){n.x=(n.x+d)*m,n.y=n.depth*y})}return s}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var u,i=t.children,o=0,a=i.length;a>o;++o)r.push((i[o]=u={_:i[o],parent:t,children:(u=i[o].children)&&u.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=u);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Ni(n);var i=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-i):n.z=i}else r&&(n.z=r.z+a(n._,r._));n.parent.A=u(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function u(n,t,e){if(t){for(var r,u=n,i=n,o=t,c=u.parent.children[0],l=u.m,s=i.m,f=o.m,h=c.m;o=Ei(o),u=ki(u),o&&u;)c=ki(c),i=Ei(i),i.a=n,r=o.z+f-u.z-l+a(o._,u._),r>0&&(Ai(Ci(o,n,e),n,r),l+=r,s+=r),f+=o.m,l+=u.m,h+=c.m,s+=i.m;o&&!Ei(i)&&(i.t=o,i.m+=f-s),u&&!ki(c)&&(c.t=u,c.m+=l-h,e=n)}return e}function i(n){n.x*=c[0],n.y=n.depth*c[1]}var o=ta.layout.hierarchy().sort(null).value(null),a=Si,c=[1,1],l=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(l=null==(c=t)?i:null,n):l?null:c},n.nodeSize=function(t){return arguments.length?(l=null==(c=t)?null:i,n):l?c:null},Gu(n,o)},ta.layout.cluster=function(){function n(n,i){var o,a=t.call(this,n,i),c=a[0],l=0;Qu(c,function(n){var t=n.children;t&&t.length?(n.x=qi(t),n.y=zi(t)):(n.x=o?l+=e(n,o):0,n.y=0,o=n)});var s=Li(c),f=Ti(c),h=s.x-e(s,f)/2,g=f.x+e(f,s)/2;return Qu(c,u?function(n){n.x=(n.x-c.x)*r[0],n.y=(c.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(g-h)*r[0],n.y=(1-(c.y?n.y/c.y:1))*r[1]}),a}var t=ta.layout.hierarchy().sort(null).value(null),e=Si,r=[1,1],u=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(u=null==(r=t),n):u?null:r},n.nodeSize=function(t){return arguments.length?(u=null!=(r=t),n):u?r:null},Gu(n,t)},ta.layout.treemap=function(){function n(n,t){for(var e,r,u=-1,i=n.length;++ut?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var i=e.children;if(i&&i.length){var o,a,c,l=f(e),s=[],h=i.slice(),p=1/0,v="slice"===g?l.dx:"dice"===g?l.dy:"slice-dice"===g?1&e.depth?l.dy:l.dx:Math.min(l.dx,l.dy);for(n(h,l.dx*l.dy/e.value),s.area=0;(c=h.length)>0;)s.push(o=h[c-1]),s.area+=o.area,"squarify"!==g||(a=r(s,v))<=p?(h.pop(),p=a):(s.area-=s.pop().area,u(s,v,l,!1),v=Math.min(l.dx,l.dy),s.length=s.area=0,p=1/0);s.length&&(u(s,v,l,!0),s.length=s.area=0),i.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var i,o=f(t),a=r.slice(),c=[];for(n(a,o.dx*o.dy/t.value),c.area=0;i=a.pop();)c.push(i),c.area+=i.area,null!=i.z&&(u(c,i.z?o.dx:o.dy,o,!a.length),c.length=c.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,u=0,i=1/0,o=-1,a=n.length;++oe&&(i=e),e>u&&(u=e));return r*=r,t*=t,r?Math.max(t*u*p/r,r/(t*i*p)):1/0}function u(n,t,e,r){var u,i=-1,o=n.length,a=e.x,l=e.y,s=t?c(n.area/t):0;if(t==e.dx){for((r||s>e.dy)&&(s=e.dy);++ie.dx)&&(s=e.dx);++ie&&(t=1),1>e&&(n=0),function(){var e,r,u;do e=2*Math.random()-1,r=2*Math.random()-1,u=e*e+r*r;while(!u||u>1);return n+t*e*Math.sqrt(-2*Math.log(u)/u)}},logNormal:function(){var n=ta.random.normal.apply(ta,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ta.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ta.scale={};var ml={floor:y,ceil:y};ta.scale.linear=function(){return Ii([0,1],[0,1],mu,!1)};var yl={s:1,g:1,p:1,r:1,e:1};ta.scale.log=function(){return Ji(ta.scale.linear().domain([0,1]),10,!0,[1,10])};var Ml=ta.format(".0e"),xl={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ta.scale.pow=function(){return Gi(ta.scale.linear(),1,[0,1])},ta.scale.sqrt=function(){return ta.scale.pow().exponent(.5)},ta.scale.ordinal=function(){return Qi([],{t:"range",a:[[]]})},ta.scale.category10=function(){return ta.scale.ordinal().range(bl)},ta.scale.category20=function(){return ta.scale.ordinal().range(_l)},ta.scale.category20b=function(){return ta.scale.ordinal().range(wl)},ta.scale.category20c=function(){return ta.scale.ordinal().range(Sl)};var bl=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(Mt),_l=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(Mt),wl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(Mt),Sl=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(Mt);ta.scale.quantile=function(){return no([],[])},ta.scale.quantize=function(){return to(0,1,[0,1])},ta.scale.threshold=function(){return eo([.5],[0,1])},ta.scale.identity=function(){return ro([0,1])},ta.svg={},ta.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),l=Math.max(0,+r.apply(this,arguments)),s=o.apply(this,arguments)-Ra,f=a.apply(this,arguments)-Ra,h=Math.abs(f-s),g=s>f?0:1;if(n>l&&(p=l,l=n,n=p),h>=Ta)return t(l,g)+(n?t(n,1-g):"")+"Z";var p,v,d,m,y,M,x,b,_,w,S,k,E=0,A=0,N=[];if((m=(+c.apply(this,arguments)||0)/2)&&(d=i===kl?Math.sqrt(n*n+l*l):+i.apply(this,arguments),g||(A*=-1),l&&(A=tt(d/l*Math.sin(m))),n&&(E=tt(d/n*Math.sin(m)))),l){y=l*Math.cos(s+A),M=l*Math.sin(s+A),x=l*Math.cos(f-A),b=l*Math.sin(f-A);var C=Math.abs(f-s-2*A)<=qa?0:1;if(A&&so(y,M,x,b)===g^C){var z=(s+f)/2;y=l*Math.cos(z),M=l*Math.sin(z),x=b=null}}else y=M=0;if(n){_=n*Math.cos(f-E),w=n*Math.sin(f-E),S=n*Math.cos(s+E),k=n*Math.sin(s+E);var q=Math.abs(s-f+2*E)<=qa?0:1;if(E&&so(_,w,S,k)===1-g^q){var L=(s+f)/2;_=n*Math.cos(L),w=n*Math.sin(L),S=k=null}}else _=w=0;if((p=Math.min(Math.abs(l-n)/2,+u.apply(this,arguments)))>.001){v=l>n^g?0:1;var T=null==S?[_,w]:null==x?[y,M]:Lr([y,M],[S,k],[x,b],[_,w]),R=y-T[0],D=M-T[1],P=x-T[0],U=b-T[1],j=1/Math.sin(Math.acos((R*P+D*U)/(Math.sqrt(R*R+D*D)*Math.sqrt(P*P+U*U)))/2),F=Math.sqrt(T[0]*T[0]+T[1]*T[1]);if(null!=x){var H=Math.min(p,(l-F)/(j+1)),O=fo(null==S?[_,w]:[S,k],[y,M],l,H,g),I=fo([x,b],[_,w],l,H,g);p===H?N.push("M",O[0],"A",H,",",H," 0 0,",v," ",O[1],"A",l,",",l," 0 ",1-g^so(O[1][0],O[1][1],I[1][0],I[1][1]),",",g," ",I[1],"A",H,",",H," 0 0,",v," ",I[0]):N.push("M",O[0],"A",H,",",H," 0 1,",v," ",I[0])}else N.push("M",y,",",M);if(null!=S){var Y=Math.min(p,(n-F)/(j-1)),Z=fo([y,M],[S,k],n,-Y,g),V=fo([_,w],null==x?[y,M]:[x,b],n,-Y,g);p===Y?N.push("L",V[0],"A",Y,",",Y," 0 0,",v," ",V[1],"A",n,",",n," 0 ",g^so(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-g," ",Z[1],"A",Y,",",Y," 0 0,",v," ",Z[0]):N.push("L",V[0],"A",Y,",",Y," 0 0,",v," ",Z[0])}else N.push("L",_,",",w)}else N.push("M",y,",",M),null!=x&&N.push("A",l,",",l," 0 ",C,",",g," ",x,",",b),N.push("L",_,",",w),null!=S&&N.push("A",n,",",n," 0 ",q,",",1-g," ",S,",",k);return N.push("Z"),N.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=io,r=oo,u=uo,i=kl,o=ao,a=co,c=lo;return n.innerRadius=function(t){return arguments.length?(e=Et(t),n):e},n.outerRadius=function(t){return arguments.length?(r=Et(t),n):r},n.cornerRadius=function(t){return arguments.length?(u=Et(t),n):u},n.padRadius=function(t){return arguments.length?(i=t==kl?kl:Et(t),n):i},n.startAngle=function(t){return arguments.length?(o=Et(t),n):o},n.endAngle=function(t){return arguments.length?(a=Et(t),n):a},n.padAngle=function(t){return arguments.length?(c=Et(t),n):c},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Ra;return[Math.cos(t)*n,Math.sin(t)*n]},n};var kl="auto";ta.svg.line=function(){return ho(y)};var El=ta.map({linear:go,"linear-closed":po,step:vo,"step-before":mo,"step-after":yo,basis:So,"basis-open":ko,"basis-closed":Eo,bundle:Ao,cardinal:bo,"cardinal-open":Mo,"cardinal-closed":xo,monotone:To});El.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Al=[0,2/3,1/3,0],Nl=[0,1/3,2/3,0],Cl=[0,1/6,2/3,1/6];ta.svg.line.radial=function(){var n=ho(Ro);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},mo.reverse=yo,yo.reverse=mo,ta.svg.area=function(){return Do(y)},ta.svg.area.radial=function(){var n=Do(Ro);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ta.svg.chord=function(){function n(n,a){var c=t(this,i,n,a),l=t(this,o,n,a);return"M"+c.p0+r(c.r,c.p1,c.a1-c.a0)+(e(c,l)?u(c.r,c.p1,c.r,c.p0):u(c.r,c.p1,l.r,l.p0)+r(l.r,l.p1,l.a1-l.a0)+u(l.r,l.p1,c.r,c.p0))+"Z"}function t(n,t,e,r){var u=t.call(n,e,r),i=a.call(n,u,r),o=c.call(n,u,r)-Ra,s=l.call(n,u,r)-Ra;return{r:i,a0:o,a1:s,p0:[i*Math.cos(o),i*Math.sin(o)],p1:[i*Math.cos(s),i*Math.sin(s)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>qa)+",1 "+t}function u(n,t,e,r){return"Q 0,0 "+r}var i=mr,o=yr,a=Po,c=ao,l=co;return n.radius=function(t){return arguments.length?(a=Et(t),n):a},n.source=function(t){return arguments.length?(i=Et(t),n):i},n.target=function(t){return arguments.length?(o=Et(t),n):o},n.startAngle=function(t){return arguments.length?(c=Et(t),n):c},n.endAngle=function(t){return arguments.length?(l=Et(t),n):l},n},ta.svg.diagonal=function(){function n(n,u){var i=t.call(this,n,u),o=e.call(this,n,u),a=(i.y+o.y)/2,c=[i,{x:i.x,y:a},{x:o.x,y:a},o];return c=c.map(r),"M"+c[0]+"C"+c[1]+" "+c[2]+" "+c[3]}var t=mr,e=yr,r=Uo;return n.source=function(e){return arguments.length?(t=Et(e),n):t},n.target=function(t){return arguments.length?(e=Et(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ta.svg.diagonal.radial=function(){var n=ta.svg.diagonal(),t=Uo,e=n.projection;return n.projection=function(n){return arguments.length?e(jo(t=n)):t},n},ta.svg.symbol=function(){function n(n,r){return(zl.get(t.call(this,n,r))||Oo)(e.call(this,n,r))}var t=Ho,e=Fo;return n.type=function(e){return arguments.length?(t=Et(e),n):t},n.size=function(t){return arguments.length?(e=Et(t),n):e},n};var zl=ta.map({circle:Oo,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Ll)),e=t*Ll;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/ql),e=t*ql/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/ql),e=t*ql/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ta.svg.symbolTypes=zl.keys();var ql=Math.sqrt(3),Ll=Math.tan(30*Da);_a.transition=function(n){for(var t,e,r=Tl||++Ul,u=Xo(n),i=[],o=Rl||{time:Date.now(),ease:Su,delay:0,duration:250},a=-1,c=this.length;++ai;i++){u.push(t=[]);for(var e=this[i],a=0,c=e.length;c>a;a++)(r=e[a])&&n.call(r,r.__data__,a,i)&&t.push(r)}return Yo(u,this.namespace,this.id)},Pl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(u){u[r][e].tween.set(n,t)})},Pl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function u(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function i(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?Hu:mu,a=ta.ns.qualify(n);return Zo(this,"attr."+n,t,a.local?i:u)},Pl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(u));return r&&function(n){this.setAttribute(u,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(u.space,u.local));return r&&function(n){this.setAttributeNS(u.space,u.local,r(n))}}var u=ta.ns.qualify(n);return this.tween("attr."+n,u.local?r:e)},Pl.style=function(n,e,r){function u(){this.style.removeProperty(n)}function i(e){return null==e?u:(e+="",function(){var u,i=t(this).getComputedStyle(this,null).getPropertyValue(n);return i!==e&&(u=mu(i,e),function(t){this.style.setProperty(n,u(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Zo(this,"style."+n,e,i)},Pl.styleTween=function(n,e,r){function u(u,i){var o=e.call(this,u,i,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,u)},Pl.text=function(n){return Zo(this,"text",n,Vo)},Pl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Pl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ta.ease.apply(ta,arguments)),Y(this,function(r){r[e][t].ease=n}))},Pl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,u,i){r[e][t].delay=+n.call(r,r.__data__,u,i)}:(n=+n,function(r){r[e][t].delay=n}))},Pl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,u,i){r[e][t].duration=Math.max(1,n.call(r,r.__data__,u,i))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Pl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var u=Rl,i=Tl;try{Tl=e,Y(this,function(t,u,i){Rl=t[r][e],n.call(t,t.__data__,u,i)})}finally{Rl=u,Tl=i}}else Y(this,function(u){var i=u[r][e];(i.event||(i.event=ta.dispatch("start","end","interrupt"))).on(n,t)});return this},Pl.transition=function(){for(var n,t,e,r,u=this.id,i=++Ul,o=this.namespace,a=[],c=0,l=this.length;l>c;c++){a.push(n=[]);for(var t=this[c],s=0,f=t.length;f>s;s++)(e=t[s])&&(r=e[o][u],$o(e,s,o,i,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Yo(a,o,i)},ta.svg.axis=function(){function n(n){n.each(function(){var n,l=ta.select(this),s=this.__chart__||e,f=this.__chart__=e.copy(),h=null==c?f.ticks?f.ticks.apply(f,a):f.domain():c,g=null==t?f.tickFormat?f.tickFormat.apply(f,a):y:t,p=l.selectAll(".tick").data(h,f),v=p.enter().insert("g",".domain").attr("class","tick").style("opacity",Ca),d=ta.transition(p.exit()).style("opacity",Ca).remove(),m=ta.transition(p.order()).style("opacity",1),M=Math.max(u,0)+o,x=Ui(f),b=l.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ta.transition(b));v.append("line"),v.append("text");var w,S,k,E,A=v.select("line"),N=m.select("line"),C=p.select("text").text(g),z=v.select("text"),q=m.select("text"),L="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=Bo,w="x",k="y",S="x2",E="y2",C.attr("dy",0>L?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+L*i+"V0H"+x[1]+"V"+L*i)):(n=Wo,w="y",k="x",S="y2",E="x2",C.attr("dy",".32em").style("text-anchor",0>L?"end":"start"),_.attr("d","M"+L*i+","+x[0]+"H0V"+x[1]+"H"+L*i)),A.attr(E,L*u),z.attr(k,L*M),N.attr(S,0).attr(E,L*u),q.attr(w,0).attr(k,L*M),f.rangeBand){var T=f,R=T.rangeBand()/2;s=f=function(n){return T(n)+R}}else s.rangeBand?s=f:d.call(n,f,s);v.call(n,s,f),m.call(n,f,f)})}var t,e=ta.scale.linear(),r=jl,u=6,i=6,o=3,a=[10],c=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Fl?t+"":jl,n):r},n.ticks=function(){return arguments.length?(a=arguments,n):a},n.tickValues=function(t){return arguments.length?(c=t,n):c},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(u=+t,i=+arguments[e-1],n):u},n.innerTickSize=function(t){return arguments.length?(u=+t,n):u},n.outerTickSize=function(t){return arguments.length?(i=+t,n):i},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var jl="bottom",Fl={top:1,right:1,bottom:1,left:1};ta.svg.brush=function(){function n(t){t.each(function(){var t=ta.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",i).on("touchstart.brush",i),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,y);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return Hl[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var c,f=ta.transition(t),h=ta.transition(o);l&&(c=Ui(l),h.attr("x",c[0]).attr("width",c[1]-c[0]),r(f)),s&&(c=Ui(s),h.attr("y",c[0]).attr("height",c[1]-c[0]),u(f)),e(f)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+f[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",f[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",f[1]-f[0])}function u(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function i(){function i(){32==ta.event.keyCode&&(C||(M=null,q[0]-=f[1],q[1]-=h[1],C=2),S())}function v(){32==ta.event.keyCode&&2==C&&(q[0]+=f[1],q[1]+=h[1],C=0,S())}function d(){var n=ta.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ta.event.altKey?(M||(M=[(f[0]+f[1])/2,(h[0]+h[1])/2]),q[0]=f[+(n[0]s?(u=r,r=s):u=s),v[0]!=r||v[1]!=u?(e?a=null:o=null,v[0]=r,v[1]=u,!0):void 0}function y(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ta.select("body").style("cursor",null),L.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ta.select(ta.event.target),w=c.of(b,arguments),k=ta.select(b),E=_.datum(),A=!/^(n|s)$/.test(E)&&l,N=!/^(e|w)$/.test(E)&&s,C=_.classed("extent"),z=W(b),q=ta.mouse(b),L=ta.select(t(b)).on("keydown.brush",i).on("keyup.brush",v);if(ta.event.changedTouches?L.on("touchmove.brush",d).on("touchend.brush",y):L.on("mousemove.brush",d).on("mouseup.brush",y),k.interrupt().selectAll("*").interrupt(),C)q[0]=f[0]-q[0],q[1]=h[0]-q[1];else if(E){var T=+/w$/.test(E),R=+/^n/.test(E);x=[f[1-T]-q[0],h[1-R]-q[1]],q[0]=f[T],q[1]=h[R]}else ta.event.altKey&&(M=q.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ta.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,c=E(n,"brushstart","brush","brushend"),l=null,s=null,f=[0,0],h=[0,0],g=!0,p=!0,v=Ol[0];return n.event=function(n){n.each(function(){var n=c.of(this,arguments),t={x:f,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Tl?ta.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,f=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=yu(f,t.x),r=yu(h,t.y);return o=a=null,function(u){f=t.x=e(u),h=t.y=r(u),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(l=t,v=Ol[!l<<1|!s],n):l},n.y=function(t){return arguments.length?(s=t,v=Ol[!l<<1|!s],n):s},n.clamp=function(t){return arguments.length?(l&&s?(g=!!t[0],p=!!t[1]):l?g=!!t:s&&(p=!!t),n):l&&s?[g,p]:l?g:s?p:null},n.extent=function(t){var e,r,u,i,c;return arguments.length?(l&&(e=t[0],r=t[1],s&&(e=e[0],r=r[0]),o=[e,r],l.invert&&(e=l(e),r=l(r)),e>r&&(c=e,e=r,r=c),(e!=f[0]||r!=f[1])&&(f=[e,r])),s&&(u=t[0],i=t[1],l&&(u=u[1],i=i[1]),a=[u,i],s.invert&&(u=s(u),i=s(i)),u>i&&(c=u,u=i,i=c),(u!=h[0]||i!=h[1])&&(h=[u,i])),n):(l&&(o?(e=o[0],r=o[1]):(e=f[0],r=f[1],l.invert&&(e=l.invert(e),r=l.invert(r)),e>r&&(c=e,e=r,r=c))),s&&(a?(u=a[0],i=a[1]):(u=h[0],i=h[1],s.invert&&(u=s.invert(u),i=s.invert(i)),u>i&&(c=u,u=i,i=c))),l&&s?[[e,u],[r,i]]:l?[e,r]:s&&[u,i])},n.clear=function(){return n.empty()||(f=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!l&&f[0]==f[1]||!!s&&h[0]==h[1]},ta.rebind(n,c,"on")};var Hl={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Ol=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Il=ac.format=gc.timeFormat,Yl=Il.utc,Zl=Yl("%Y-%m-%dT%H:%M:%S.%LZ");Il.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?Jo:Zl,Jo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},Jo.toString=Zl.toString,ac.second=Ft(function(n){return new cc(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ac.seconds=ac.second.range,ac.seconds.utc=ac.second.utc.range,ac.minute=Ft(function(n){return new cc(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ac.minutes=ac.minute.range,ac.minutes.utc=ac.minute.utc.range,ac.hour=Ft(function(n){var t=n.getTimezoneOffset()/60;return new cc(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ac.hours=ac.hour.range,ac.hours.utc=ac.hour.utc.range,ac.month=Ft(function(n){return n=ac.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ac.months=ac.month.range,ac.months.utc=ac.month.utc.range;var Vl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Xl=[[ac.second,1],[ac.second,5],[ac.second,15],[ac.second,30],[ac.minute,1],[ac.minute,5],[ac.minute,15],[ac.minute,30],[ac.hour,1],[ac.hour,3],[ac.hour,6],[ac.hour,12],[ac.day,1],[ac.day,2],[ac.week,1],[ac.month,1],[ac.month,3],[ac.year,1]],$l=Il.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",Ne]]),Bl={range:function(n,t,e){return ta.range(Math.ceil(n/e)*e,+t,e).map(Ko)},floor:y,ceil:y};Xl.year=ac.year,ac.scale=function(){return Go(ta.scale.linear(),Xl,$l)};var Wl=Xl.map(function(n){return[n[0].utc,n[1]]}),Jl=Yl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",Ne]]);Wl.year=ac.year.utc,ac.scale.utc=function(){return Go(ta.scale.linear(),Wl,Jl)},ta.text=At(function(n){return n.responseText}),ta.json=function(n,t){return Nt(n,"application/json",Qo,t)},ta.html=function(n,t){return Nt(n,"text/html",na,t)},ta.xml=At(function(n){return n.responseXML}),"function"==typeof define&&define.amd?define(ta):"object"==typeof module&&module.exports&&(module.exports=ta),this.d3=ta}(); \ No newline at end of file diff --git a/share/viz/js/tree.js b/share/viz/js/tree.js new file mode 100644 index 0000000000..dbca057bc8 --- /dev/null +++ b/share/viz/js/tree.js @@ -0,0 +1,289 @@ +// main container +var astDiv = document.getElementById("ast"); + +// by default show 0 to 2 depth +var collapseDepth = 2; + +/// redraw AST when browser window resizes +function redrawAST() { + /// get container and compute visible size + var astDiv = document.getElementById("ast"); + var width = astDiv.clientWidth; + var height = astDiv.clientHeight; + + /// ast diameter is shortest of width and height + var diameter = width < height ? width : height; + + // define layout for tree + var tree = d3.layout.tree() + .size([360, diameter / 2 - 80]) + .separation(function(a, b) { + return (a.parent == b.parent ? 7 : 10) / a.depth; + }); + + /// angle for arcs + var diagonal = d3.svg.diagonal.radial() + .projection(function(d) { + return [d.y, d.x / 180 * Math.PI]; + }); + + /// d3 tree should start at the centre of browser + var centerX = width / 2; + var centerY = height / 2; + + // any previous svg container and tooltip show be removed + d3.select("svg").remove(); + d3.select("div.tooltip").remove(); + + /// select ast svg + var astSvg = d3.select(astDiv).append("svg") + .attr("width", width) + .attr("height", height) + .append("g") + .attr("class","treearea") + .attr("transform", "translate(" + centerX + "," + centerY + ")"); + + /// add div for tooltop + var tooltip = d3.select("body") + .append("div") + .attr('class', 'tooltip') + .style("position", "absolute") + .style("z-index", "10") + .style("opacity", 0) + .style('pointer-events', 'none') + + /// set position for tree + astRoot.x0 = height / 2; + astRoot.y0 = 0; + + /// first need to draw then collaprse to necessary depth and then redraw + drawTree(astRoot, astSvg, astRoot); + collapseToDepth(astRoot, collapseDepth); + drawTree(astRoot, astSvg, astRoot); + + /// draw main ast + function drawTree(source, destnode, astRoot) { + /// counter for ids + var idCounter = 0; + + /// animation duration + var duration = 200; + + /// compute the new tree layout + var nodes = tree.nodes(astRoot), + links = tree.links(nodes); + + /// normalize for fixed-depth + nodes.forEach(function(d) { + d.y = d.depth * 100; + }); + + /// update the nodes + var node = destnode.selectAll("g.node") + .data(nodes, function(d) { + return d.id || (d.id = ++idCounter); + }); + + /// enter any new nodes at the parent's previous position + var nodeEnter = node.enter().append("g") + .attr("class", "node"); + + /// draw little circle for each node with mouse events + nodeEnter.append("circle") + .attr("r", 1e-6) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }).on("mouseover", function(d) { + mouseover(d); + }) + .on("mousemove", function(d) { + mousemove(d) + }) + .on("mouseout", function() { + mouseout(); + }) + .on("click", function(d) { + click(d, destnode, astRoot); + }); + + /// add ast node type as title + nodeEnter.append("text") + .attr("x", 10) + .attr("dy", ".95em") + .attr("text-anchor", "start") + .text(function(d) { + return d.name; + }) + .style("fill-opacity", 1e-6); + + + // transformations nodes to their new position + + var nodeUpdate = node.transition() + .duration(duration) + .attr("transform", function(d) { + return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; + }) + + nodeUpdate.select("circle") + .attr("r", 6.0) + .style("fill", function(d) { + return d._children ? "lightsteelblue" : "#fff"; + }); + + nodeUpdate.select("text") + .style("fill-opacity", 1) + .attr("transform", function(d) { + return d.x < 180 ? "translate(0)" : "rotate(180)translate(-" + (d.name.length + 50) + ")"; + }); + + + /// transformations when nodes are made hidden + + var nodeExit = node.exit().transition() + .duration(duration) + .remove(); + + nodeExit.select("circle") + .attr("r", 1e-6); + + nodeExit.select("text") + .style("fill-opacity", 1e-6); + + /// update the link + var link = destnode.selectAll("path.link") + .data(links, function(d) { + return d.target.id; + }); + + /// enter any new links at the parent's previous position + link.enter().insert("path", "g") + .attr("class", "link") + .attr("d", function(d) { + var o = { + x: source.x0, + y: source.y0 + }; + return diagonal({ + source: o, + target: o + }); + }); + + /// transition links to their new position + link.transition() + .duration(duration) + .attr("d", diagonal); + + /// transition exiting nodes to the parent's new position + link.exit().transition() + .duration(duration) + .attr("d", function(d) { + var o = { + x: source.x, + y: source.y + }; + return diagonal({ + source: o, + target: o + }); + }) + .remove(); + + /// stash the old positions for transition + nodes.forEach(function(d) { + d.x0 = d.x; + d.y0 = d.y; + }); + + /// setup zoom listener + d3.select("svg") + .call(d3.behavior.zoom().scaleExtent([0.2, 5]).on("zoom", zoom)); + } + + /// make tooltip visible + function mouseover(d) { + if (d.nmodl) { + tooltip.text(d.nmodl); + } else { + tooltip.text(d.name); + } + d3.select('div.tooltip'). + transition() + .delay(100) + .duration(300) + .style("opacity", 100) + .style('pointer-events', 'none') + return tooltip; + } + + /// move tooltip with text as move moves + function mousemove(d) { + var height = Math.round(Number(tooltip.style('height').slice(0, -2))); + var top = (d3.event.pageY - (height / 2.0)) + "px"; + var left = (d3.event.pageX + 15) + "px"; + return tooltip.style("top", top).style("left", left); + } + + /// hide tooltip if mouse goes out + function mouseout() { + d3.select('div.tooltip'). + transition() + .delay(100) + .duration(300) + .style("opacity", 0) + .style('pointer-events', 'none'); + return tooltip; + } + + /// toggle children on click + function click(d, destnode, astRoot) { + if (d.children) { + d._children = d.children; + d.children = null; + } else { + d.children = d._children; + d._children = null; + } + drawTree(d, destnode, astRoot); + } + + /// collapse nodes + function collapse(d) { + if (d.children) { + d._children = d.children; + d._children.forEach(collapse); + d.children = null; + } + } + + /// collaprse ast to given depth + function collapseToDepth(node, depth) { + if (node.depth > depth) { + collapse(node); + } + if (node.depth <= depth) { + node.children = node.children || node._children; + } + if (!node.children) { + return; + } + node.children.forEach(function(n) { + collapseToDepth(n, depth); + }); + } + + /// zoom-in/out ast drawing area + function zoom() { + var scale = d3.event.scale; + d3.select(".treearea") + .attr("transform", "translate(" + centerX + "," + centerY + ") scale(" + scale + ")"); + } +} + + +/// draw for the first time +redrawAST(); + +/// Redraw based on the new size whenever the browser window is resized +window.addEventListener("resize", redrawAST); diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index 520169d4cb..c6158e1ddd 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -22,7 +22,3 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; */ const std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; - - /// Path of nmodl examples -const std::vector nmodl::Config::NMODL_EXAMPLE_PATH = - {"@CMAKE_INSTALL_PREFIX@/share/example", "@PROJECT_SOURCE_DIR@/share/example"}; \ No newline at end of file diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index 6934550fd0..486f70e03f 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -60,22 +60,4 @@ struct NrnUnitsLib { } }; -/** - * \brief Information about NMODL configurations (from share directory) - */ -struct Config { - /// directories with nmodl examples - static const std::vector NMODL_EXAMPLE_PATH; - - /// Return path nmodl example directory - static std::string get_nmodl_example_dir() { - for (const auto& path: NMODL_EXAMPLE_PATH) { - if (nmodl::utils::is_dir_exist(path)) { - return path; - } - } - throw std::runtime_error("Could not found nmodl example directory"); - } -}; - } // namespace nmodl diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index a31f1b1d1f..90495cb2e8 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,7 +3,7 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) -foreach(file ode.py dsl.py __init__.py) +foreach(file ast.py config.py dsl.py ode.py symtab.py visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() @@ -48,4 +48,4 @@ add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} # Install python binding components # ============================================================================= install(TARGETS _nmodl DESTINATION lib/python/nmodl) -install(DIRECTORY ${PROJECT_SOURCE_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") +install(DIRECTORY ${CMAKE_BINARY_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 4a48d7dcf4..7bda1dbb16 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -36,10 +36,6 @@ namespace nmodl { /** \brief docstring of Python exposed API */ namespace docstring { -static const char* get_nmodl_example_dir = R"( - Get directory with NMODL examples -)"; - static const char* driver = R"( This is the NmodlDriver class documentation )"; @@ -148,10 +144,6 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; m_nmodl.attr("__version__") = nmodl::Version::NMODL_VERSION; - m_nmodl.def("example_dir", - &nmodl::Config::get_nmodl_example_dir, - nmodl::docstring::get_nmodl_example_dir); - py::class_ nmodl_driver(m_nmodl, "NmodlDriver", nmodl::docstring::driver); nmodl_driver.def(py::init<>()) .def("parse_string", From 0c2af2cff20fe1f23e68a68624dda79194723fb1 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 8 May 2019 01:01:22 +0200 Subject: [PATCH 216/871] More documentation and included md files into sphinx doc (BlueBrain/nmodl#191) - Updated README with extra information about NMODL, eprint and benchmarks - Change version to 0.2 in CMakeLists.txt - Avoided import nmodl.dsl as nmodl in README - Renamed ast_view to view in nmodl.ast module NMODL Repo SHA: BlueBrain/nmodl@e732634dd614bfb46677765e5e8188c4b396b749 --- CONTRIBUTING.md | 55 ++++++- INSTALL.md | 151 +++++++++----------- README.md | 129 +++++++++++++---- cmake/nmodl/CMakeLists.txt | 2 +- docs/nmodl/transpiler/DoxygenLayout.xml | 2 +- docs/nmodl/transpiler/contents/visitors.rst | 14 +- docs/nmodl/transpiler/contributing.rst | 2 + docs/nmodl/transpiler/index.rst | 10 +- docs/nmodl/transpiler/install.rst | 3 + docs/nmodl/transpiler/notebooks.rst | 11 ++ nmodl/ast.py | 15 +- nmodl/dsl.py | 10 +- 12 files changed, 279 insertions(+), 125 deletions(-) create mode 100644 docs/nmodl/transpiler/contributing.rst create mode 100644 docs/nmodl/transpiler/install.rst create mode 100644 docs/nmodl/transpiler/notebooks.rst diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca3d6a08ec..17b7f7519a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,11 +1,12 @@ -# Contributing to the NMODL +# Contributing to the NMODL Framework -We would love for you to contribute to the NMODL and help make it better than it is today. As a +We would love for you to contribute to the NMODL Framework and help make it better than it is today. As a contributor, here are the guidelines we would like you to follow: - [Question or Problem?](#question) - [Issues and Bugs](#issue) - [Feature Requests](#feature) - [Submission Guidelines](#submit) + - [Development Conventions](#devconv) ## Got a Question? @@ -100,3 +101,53 @@ repository: ``` [github]: https://github.com/BlueBrain/nmodl + +## Development Conventions + +If you are developing NMODL, make sure to enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` +CMake variables to ensure that your contributions follow the coding conventions of this project: + +```cmake +cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON +``` + +The first variable provides the following additional targets to format +C, C++, and CMake files: + +``` +make clang-format cmake-format +``` + +The second option activates Git hooks that will discard commits that +do not comply with coding conventions of this project. These 2 CMake variables require additional utilities: + +* [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) +* [cmake-format](https://github.com/cheshirekow/cmake_format) +* [pre-commit](https://pre-commit.com/) + +clang-format can be installed on Linux thanks +to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a +[brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) +to install clang-format 7. _cmake-format_ and _pre-commit_ utilities can be installed with *pip*. + + +### Memory Leaks and clang-tidy + +If you want to test for memory leaks, do : + +``` +valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer +``` + +Or using CTest as: + +``` +ctest -T memcheck +``` + +If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: + +``` +cmake .. -DENABLE_CLANG_TIDY=ON +``` + diff --git a/INSTALL.md b/INSTALL.md index c3320ab191..1d5ad2a8ff 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,58 +1,72 @@ -### Getting Started +# Installing the NMODL Framework + +## Getting Started These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. -#### Cloning Source +## Cloning Source -This project uses git submodules which must be cloned along with the repository itself: +The NMODL Framework is maintained on github. The best way to get the sources is to simply clone the repository. -``` +**Note**: This project uses git submodules which must be cloned along with the repository itself: + +```sh git clone --recursive https://github.com/BlueBrain/nmodl.git +cd nmodl ``` -#### Prerequisites +## Prerequisites -To build the project from source, modern C++ compiler with c++11 support is necessary. Make sure you have following packages available: +To build the project from source, a modern C++ compiler with c++11 support is necessary. Make sure you have following packages available: - flex (>=2.6) - bison (>=3.0) -- CMake (>=3.1) +- CMake (>=3.3) - Python (>=3.6) -- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.2), textwrap +- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap -##### On OS X +### On OS X -Often we have older version of flex and bison. We can get recent version of dependencies via brew/macport and pip: +Typically the versions of bison and flex provided by the system are outdated and not compatible with our requirements. +To get recent version of all dependencies we recommend using [homebrew](https://brew.sh/): -``` +```sh brew install flex bison cmake python3 +``` + +The necessary Python packages can then easily be added using the pip3 command. + +```sh pip3 install Jinja2 PyYAML pytest sympy ``` Make sure to have latest flex/bison in $PATH : -``` +```sh export PATH=/usr/local/opt/flex:/usr/local/opt/bison:/usr/local/bin/:$PATH ``` -##### On Ubuntu +### On Ubuntu -On Ubuntu (>=16.04) flex/bison versions are recent enough. We can install Python3 and dependencies using: +On Ubuntu (>=16.04) flex/bison versions are recent enough and are installed along with the system toolchain: +```sh +apt-get install flex bison gcc python3 python3-pip ``` -apt-get install python3 python3-pip + +The Python dependencies are installed using: + +```sh pip3 install Jinja2 PyYAML pytest sympy ``` -> On Blue Brain B5 Supercomputer, use : module load cmake/3.12.0 bison/3.0.5 flex/2.6.3 gcc/6.4.0 python3-dev - -#### Build Project +## Build Project -##### Using CMake +### Using CMake Once all dependencies are in place, build project as: -``` +```sh mkdir -p nmodl/build cd nmodl/build cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl @@ -61,15 +75,37 @@ make -j && make install And set PYTHONPATH as: -``` +```sh export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH ``` -#### Testing Installed Module +#### Flex / Bison Paths + +If flex / bison are not in your default $PATH, you can provide the path to cmake as: + +``` +cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ + -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ + -DCMAKE_INSTALL_PREFIX=$HOME/nmodl +``` + +### Using Python setuptools -If you install NMODL using CMake, you can run tests from build directory as: +If you are mainly interested in the NMODL Framework parsing and analysis tools and wish to use them from Python, we +recommend building and installing using Python. +```sh +pip3 install --user . ``` + +This should build the NMODL framework and install it into your pip user `site-packages` folder such that it becomes +available as a Python module. + +## Testing the Installed Module + +If you have installed the NMODL Framework using CMake, you can now run tests from the build directory as: + +```bash $ make test Running tests... Test project /Users/kumbhar/workarena/repos/bbp/incubator/nocmodl/cmake-build-debug @@ -92,10 +128,9 @@ Test project /Users/kumbhar/workarena/repos/bbp/incubator/nocmodl/cmake-build-de ... ``` -We can use nmodl module from python as: +To test the NMODL Framework python bindings, you can try a minimal example in your Python 3 interpeter as follows: ```python -$ python3 >>> import nmodl.dsl as nmodl >>> driver = nmodl.NmodlDriver() >>> modast = driver.parse_string("NEURON { SUFFIX hh }") @@ -110,12 +145,13 @@ NEURON { NMODL is now setup correctly! -#### Generating Documentation +## Generating Documentation In order to build the documentation you must have additionally `pandoc` installed. Use your -system's package manager to do this (e.g. `apt install pandoc`). +system's package manager to do this (e.g. `sudo apt-get install pandoc`). -Once you have installed NMODL and setup the correct PYTHONPATH, you can build the documentation locally from the docs folder as: +Once you have installed NMODL and setup the correct `$PYTHONPATH`, you can build the documentation locally from the +docs folder as: ``` cd docs @@ -123,62 +159,9 @@ doxygen # for API documentation make html # for user documentation ``` +Alternatively, you can install the documentation using the Python setuptools script: -### Development Conventions - -If you are developing NMODL, make sure to enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` -CMake variables to ensure that your contributions follow the coding conventions of this project: - -```cmake -cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON +```sh +python3 setup.py buildhtml ``` -The first variable provides the following additional targets to format -C, C++, and CMake files: - -``` -make clang-format cmake-format -``` - -The second option activates Git hooks that will discard commits that -do not comply with coding conventions of this project. These 2 CMake variables require additional utilities: - -* [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) -* [cmake-format](https://github.com/cheshirekow/cmake_format) -* [pre-commit](https://pre-commit.com/) - -clang-format can be installed on Linux thanks -to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a -[brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) -to install clang-format 7. _cmake-format_ and _pre-commit_ utilities can be installed with *pip*. - - -##### Memory Leaks and clang-tidy - -If you want to test for memory leaks, do : - -``` -valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer -``` - -Or using CTest as: - -``` -ctest -T memcheck -``` - -If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: - -``` -cmake .. -DENABLE_CLANG_TIDY=ON -``` - -##### Flex / Bison Paths - -If flex / bison is not in default $PATH, you can provide path to cmake as: - -``` -cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ - -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ - -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -``` diff --git a/README.md b/README.md index af03258919..641bd150f2 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L* Simulators like [NEURON](https://www.neuron.yale.edu/neuron/) use NMODL as a domain specific language (DSL) to describe a wide range of membrane and intracellular submodels. Here is an example of exponential synapse specified in NMODL: -``` +```python NEURON { POINT_PROCESS ExpSyn RANGE tau, e, i @@ -52,22 +52,25 @@ NET_RECEIVE(weight (uS)) { ### Installation -See [INSTALL.md](INSTALL.md) for detailed instructions to build the NMODL from source. +See [INSTALL.md](https://github.com/BlueBrain/nmodl/blob/master/INSTALL.md) for detailed instructions to build the NMODL from source. ### Using the Python API -Once the NMODL Framework is installed, you can use the Python parsing API as: +Once the NMODL Framework is installed, you can use the Python parsing API to load NMOD file as: ```python -import nmodl.dsl as nmodl -driver = nmodl.NmodlDriver() -mod_ast = driver.parse_file("expsyn.mod") +from nmodl import dsl +import os + +expsyn = os.path.join(dsl.example_dir(), "expsyn.mod") +driver = dsl.NmodlDriver() +modast = driver.parse_file(expsyn) ``` -`parse_file()]` returns Abstract Syntax Tree ([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) representation of input NMODL file. One can look at the AST in JSON form as: +The `parse_file` API returns Abstract Syntax Tree ([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) representation of input NMODL file. One can look at the AST by converting to JSON form as: ```python ->>> print (nmodl.to_json(mod_ast)) +>>> print (dsl.to_json(modast)) { "Program": [ { @@ -85,16 +88,24 @@ mod_ast = driver.parse_file("expsyn.mod") } ... ``` -You can also use AST visualization API to look at the AST: +Every key in the JSON form represent a node in the AST. You can also use visualization API to look at the details of AST as: -![alt text](docs/images/nmodl.ast.png "AST representation of expsyn.mod") +``` +from nmodl import ast +ast.view(modast) +``` +which will open AST view in web browser: -The central *Program* node represents the whole MOD file and each of it's children represent the block in the input NMODL file i.e. **expsyn.mod**. Once the AST is built, one can use exisiting visitors to perform various analysis/optimisations or one can write his own custom visitor using Python Visitor API. See [Python API tutorial](docs/notebooks/nmodl-python-tutorial.ipynb) for details. +![ast_viz](https://user-images.githubusercontent.com/666852/57329449-12c9a400-7114-11e9-8da5-0042590044ec.gif "AST representation of expsyn.mod") -One can also transform AST back into NMODL form simply as : +The central *Program* node represents the whole MOD file and each of it's children represent the block in the input NMODL file. + +Once the AST is created, one can use exisiting visitors to perform various analysis/optimisations. One can also easily write his own custom visitor using Python Visitor API. See [Python API tutorial](docs/notebooks/nmodl-python-tutorial.ipynb) for details. + +NMODL Frameowrk also allows to transform AST representation back to NMODL form as: ```python ->>> print (nmodl.to_nmodl(mod_ast)) +>>> print (dsl.to_nmodl(modast)) NEURON { POINT_PROCESS ExpSyn RANGE tau, e, i @@ -116,25 +127,25 @@ PARAMETER { ### High Level Analysis and Code Generation -The NMODL Framework provides rich model introspection and analysis capabilities using [various visitors TODO](). Here is an example of theoretical performance characterisation of channels and synapses from rat neocortical column microcircuit [published in 2015](https://www.cell.com/abstract/S0092-8674%2815%2901191-5): +The NMODL Framework provides rich model introspection and analysis capabilities using [various visitors](https://bluebrain.github.io/nmodl/html/doxygen/group__visitor__classes.html). Here is an example of theoretical performance characterisation of channels and synapses from rat neocortical column microcircuit [published in 2015](https://www.cell.com/abstract/S0092-8674%2815%2901191-5): -![alt text](docs/images/nmodl-perf-stats.png "Example of performance characterisation") +![nmodl-perf-stats](https://user-images.githubusercontent.com/666852/57336711-2cc0b200-7127-11e9-8053-8f662e2ec191.png "Example of performance characterisation") To understand how you can write your own introspection and analysis tool, see [this tutorial](docs/notebooks/nmodl-python-tutorial.ipynb). -Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, [C++ TODO](), [OpenACC TODO](), [OpenMP TODO](), [CUDA TODO]() and [ISPC TODO]() backends are implemented and one can choose backends on command line as: +Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, C++, OpenACC, OpenMP, CUDA and ISPC backends are implemented and one can choose these backends on command line as: ``` $ nmodl expsyn.mod host --ispc acc --cuda sympy --analytic ``` -Here is an example of generated ISPC kernel for [DERIVATIVE](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html#derivative) block : +Here is an example of generated [ISPC](https://ispc.github.io/) kernel for DERIVATIVE block : ```c++ export void nrn_state_ExpSyn(uniform ExpSyn_Instance* uniform inst, uniform NrnThread* uniform nt ...) { uniform int nodecount = ml->nodecount; const int* uniform node_index = ml->nodeindices; - const double* uniform voltage = nt->_actual_v; + const double* uniform voltage = nt->actual_v; int uniform start = 0; int uniform end = nodecount; @@ -142,30 +153,96 @@ export void nrn_state_ExpSyn(uniform ExpSyn_Instance* uniform inst, uniform NrnT foreach (id = start ... end) { int node_id = node_index[id]; double v = voltage[node_id]; - inst->g[id] = inst->g[id] * vexp( -nt->_dt / inst->tau[id]); + inst->g[id] = inst->g[id] * vexp( -nt->dt / inst->tau[id]); } } ``` -To know more about code generation backends, [see here TODO](). +To know more about code generation backends, [see here](https://bluebrain.github.io/nmodl/html/doxygen/group__codegen__backends.html). NMODL Framework provides number of options (for code generation, optimization passes and ODE solver) which can be listed as: + +``` +$ nmodl -H +NMODL : Source-to-Source Code Generation Framework +Usage: /path/<>/nmodl [OPTIONS] file... [SUBCOMMAND] + +Positionals: + file TEXT:FILE ... REQUIRED One or more MOD files to process + +Options: + -h,--help Print this help message and exit + -H,--help-all Print this help message including all sub-commands + -v,--verbose Verbose logger output + -o,--output TEXT=. Directory for backend code output + --scratch TEXT=tmp Directory for intermediate code output + --units TEXT=/path/<>/nrnunits.lib + Directory of units lib file +Subcommands: +host + HOST/CPU code backends + Options: + --c C/C++ backend + --omp C/C++ backend with OpenMP + --ispc C/C++ backend with ISPC +acc + Accelerator code backends + Options: + --oacc C/C++ backend with OpenACC + --cuda C/C++ backend with CUDA +sympy + SymPy based analysis and optimizations + Options: + --analytic Solve ODEs using SymPy analytic integration + --pade Pade approximation in SymPy analytic integration + --cse CSE (Common Subexpression Elimination) in SymPy analytic integration + --conductance Add CONDUCTANCE keyword in BREAKPOINT +passes + Analyse/Optimization passes + Options: + --inline Perform inlining at NMODL level + --unroll Perform loop unroll at NMODL level + --const-folding Perform constant folding at NMODL level + --localize Convert RANGE variables to LOCAL + --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist + --local-rename Rename LOCAL variable if variable of same name exist in global scope + --verbatim-inline Inline even if verbatim block exist + --verbatim-rename Rename variables in verbatim block + --json-ast Write AST to JSON file + --nmodl-ast Write AST to NMODL file + --json-perf Write performance statistics to JSON file + --show-symtab Write symbol table to stdout +codegen + Code generation options + Options: + --layout TEXT:{aos,soa}=soa Memory layout for code generation + --datatype TEXT:{float,double}=soa Data type for floating point variables +``` ### Documentation We are working on user documentation, you can find current drafts of : -* User documentation on [Read the Docs TODO]() -* Developer / API documentation with [Doxygen TODO]() +* [User Documentation](https://bluebrain.github.io/nmodl/) +* [Developer / API Documentation](https://bluebrain.github.io/nmodl/html/doxygen/index.html) + + +### Citation + +If you would like to know more about the the NMODL Framework, see following paper: + +* Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform source-to-source compiler framework for the NEURON MODeling Language. In Eprint : arXiv:submit/2678839. ### Support / Contribuition -If you see any issue or need help, feel free to raise a ticket. If you would like to improve this framework, see open issues and [contribution guidelines](CONTRIBUTING.md). +If you see any issue, feel free to [raise a ticket](https://github.com/BlueBrain/nmodl/issues/new). If you would like to improve this framework, see [open issues](https://github.com/BlueBrain/nmodl/issues) and [contribution guidelines](CONTRIBUTING.md). -### Citation -If you are referencing NMODL Framework in a publication, please cite the following paper: +### Examples / Benchmarks + +The benchmarks used to test the performance and parsing capabilities of NMODL Framework are currently being migrated to GitHub. These benchmarks will be published soon in following repositories: -* Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform source-to-source compiler framework for the NEURON MODeling Language. In Eprint arXiv: TODO. +* [NMODL Benchmark](https://github.com/BlueBrain/nmodlbench) +* [NMODL Database](https://github.com/BlueBrain/nmodldb) ### Authors diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 54e1742787..8ee54585e1 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -12,7 +12,7 @@ project(NMODL CXX) # CMake common project settings # ============================================================================= set(PROJECT_VERSION_MAJOR 0) -set(PROJECT_VERSION_MINOR 1) +set(PROJECT_VERSION_MINOR 2) set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/docs/nmodl/transpiler/DoxygenLayout.xml b/docs/nmodl/transpiler/DoxygenLayout.xml index 00930c097f..7ad780b56b 100644 --- a/docs/nmodl/transpiler/DoxygenLayout.xml +++ b/docs/nmodl/transpiler/DoxygenLayout.xml @@ -3,7 +3,7 @@ - + diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index dfc7d1a2a4..a058297972 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -1,11 +1,11 @@ Visitors -######### +######## -One of the strengths of NMODL python interface is access to inbuilt Visitors. +One of the strengths of the NMODL python interface is access to inbuilt Visitors. One can perform different queries and analysis on AST using different visitors. Let us start with examples of inbuilt visitors. Parsing Model and constructing AST -=================================== +================================== Once the NMODL is setup properly we should be able to import nmodl module and create the channel: @@ -63,11 +63,11 @@ If we simply print AST object, we can see JSON representation: Querying AST objects with Visitors -=================================== +=========== Lookup Visitor ---------------- +----------- As name suggest, lookup visitor allows to search different NMODL constructs in the AST. The `visitor` module provides access to inbuilt visitors. In order to use this visitor, we create an object of :class:`nmodl.visitor.AstLookupVisitor`: @@ -86,7 +86,7 @@ Assuming we have created :class:`nmodl.ast` object (as shown here), we can searc Symbol Table Visitor --------------------- +---------- Symbol table visitor is used to find out all variables and their usage in mod file. To use this, just create a visitor object as: @@ -107,7 +107,7 @@ Now we can query for variables in the symbol table based on name of variable: Custom AST Visitor -------------------- +---------- If predefined visitors are limited, we can implement new visitor using :class:`nmodl.visitor.AstVisitor` interface. Let us say we want to implement a visitor that prints every floating point numbers in MOD file. Here is how it can be done: diff --git a/docs/nmodl/transpiler/contributing.rst b/docs/nmodl/transpiler/contributing.rst new file mode 100644 index 0000000000..0d3640164b --- /dev/null +++ b/docs/nmodl/transpiler/contributing.rst @@ -0,0 +1,2 @@ + +.. mdinclude:: ../CONTRIBUTING.md diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 7bd03dd1a6..691512cc16 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -10,7 +10,8 @@ About NMODL :maxdepth: 2 :caption: Introduction: - readme + readme.rst + install.rst .. toctree:: :maxdepth: 2 @@ -22,6 +23,7 @@ About NMODL :maxdepth: 3 :caption: Jupyter Notebooks: + notebooks.rst notebooks/nmodl-python-tutorial.ipynb notebooks/nmodl-odes-overview.ipynb notebooks/nmodl-kinetic-schemes.ipynb @@ -32,6 +34,12 @@ About NMODL notebooks/nmodl-nonlinear-solver.ipynb notebooks/nmodl-sympy-conductance.ipynb +.. toctree:: + :maxdepth: 2 + :caption: Contributing: + + contributing.rst + .. toctree:: :maxdepth: 2 :caption: Reference: diff --git a/docs/nmodl/transpiler/install.rst b/docs/nmodl/transpiler/install.rst new file mode 100644 index 0000000000..175b6101d3 --- /dev/null +++ b/docs/nmodl/transpiler/install.rst @@ -0,0 +1,3 @@ + +.. mdinclude:: ../INSTALL.md + diff --git a/docs/nmodl/transpiler/notebooks.rst b/docs/nmodl/transpiler/notebooks.rst new file mode 100644 index 0000000000..af42d04857 --- /dev/null +++ b/docs/nmodl/transpiler/notebooks.rst @@ -0,0 +1,11 @@ +The NMODL Jupyter notebooks +=========================== + +About the notebooks +------------------- + +To help you get started we have created several `Jupyter notebooks `_, which cover many aspects of using NMODL from Python +beginning from basic model parsing and AST visualization, continuing with using and writing your own AST visitors, and +finally understanding and using the NMODL ODE solvers. You can find the notebooks +`here `_ to run and test yourself. + diff --git a/nmodl/ast.py b/nmodl/ast.py index 811ad9675c..0e298a3058 100644 --- a/nmodl/ast.py +++ b/nmodl/ast.py @@ -1,8 +1,19 @@ from ._nmodl.ast import * # noqa from .config import * -def ast_view(nmodl_ast): - """Visualize given NMODL AST by converting to JSON form""" +def view(nmodl_ast): + """Visualize given NMODL AST in web browser + + In memory representation of AST can be converted to JSON + form and it can be visualized using AST viewer implemented + using d3.js. + + Args: + nmodl_ast: AST object of nmodl file or string + + Returns: + None + """ from ._nmodl import to_json from distutils.dir_util import copy_tree import getpass diff --git a/nmodl/dsl.py b/nmodl/dsl.py index 3e0414e612..e949194445 100644 --- a/nmodl/dsl.py +++ b/nmodl/dsl.py @@ -3,8 +3,16 @@ def example_dir(): + """Returns directory containing NMODL examples + + NMODL Framework is installed with few sample example of + channels. This method can be used to get the directory + containing all mod files. + + Returns: + Full path of directory containing sample mod files + """ import os - """Return directory containing NMODL examples""" installed_example = os.path.join(PROJECT_INSTALL_DIR, "share", "example") if os.path.exists(installed_example): return installed_example From b4e900a10674195f09d25ab1159f16062a467d23 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 13 May 2019 22:19:24 +0200 Subject: [PATCH 217/871] README update with citation (BlueBrain/nmodl#200) - add correct citation information from arXiv with link - minor update to INSTALL instructions NMODL Repo SHA: BlueBrain/nmodl@5edfeb640c5ef39397200095adfd3da5e64981cb --- INSTALL.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 1d5ad2a8ff..94f4c18481 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -162,6 +162,6 @@ make html # for user documentation Alternatively, you can install the documentation using the Python setuptools script: ```sh -python3 setup.py buildhtml +python3 setup.py install_doc ``` diff --git a/README.md b/README.md index 641bd150f2..65e7e0a2dd 100644 --- a/README.md +++ b/README.md @@ -229,7 +229,7 @@ We are working on user documentation, you can find current drafts of : If you would like to know more about the the NMODL Framework, see following paper: -* Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform source-to-source compiler framework for the NEURON MODeling Language. In Eprint : arXiv:submit/2678839. +* Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform source-to-source compiler framework for the NEURON MODeling Language. In Eprint : [arXiv:1905.02241](https://arxiv.org/pdf/1905.02241.pdf) ### Support / Contribuition From a47c8add188a24427a192a2d150687662b33dd6c Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Sat, 18 May 2019 11:06:24 +0200 Subject: [PATCH 218/871] Work around for ISPC atomics performance regression (BlueBrain/nmodl#205) - Replace the ispc atomic operations by explicit reduction loop with shadow updates - Since the ispc atomic operations seem to be incurring a big slowdown we change the code back to first writing the results into a shadow array, which is then looped over in a serial fashion. Partially addresses BlueBrain/nmodl#137 NMODL Repo SHA: BlueBrain/nmodl@b015d1e15947c45b5f49de854754199b33af71c1 --- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 67 +++++++++++----------- src/nmodl/codegen/codegen_ispc_visitor.hpp | 23 ++++++-- 3 files changed, 54 insertions(+), 38 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index eefbe5a879..cab76f5ff7 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1415,7 +1415,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print block / loop for statement requiring reduction * */ - void print_shadow_reduction_block_begin(); + virtual void print_shadow_reduction_block_begin(); /** diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index d8e94e462c..e3a2abffb9 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -154,36 +154,6 @@ std::string CodegenIspcVisitor::backend_name() { } -void CodegenIspcVisitor::print_atomic_op(const std::string& lhs, - const std::string& op, - const std::string& rhs) { - std::string function; - if (op == "+") { - function = "atomic_add_local"; - } else if (op == "-") { - function = "atomic_subtract_local"; - } else { - throw std::runtime_error("ISPC backend error : {} not supported"_format(op)); - } - printer->add_line("{}(&{}, {});"_format(function, lhs, rhs)); -} - - -void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_update() { - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - if (info.point_process) { - stringutils::remove_character(rhs_op, '='); - stringutils::remove_character(d_op, '='); - print_atomic_op("vec_rhs[node_id]", rhs_op, "rhs"); - print_atomic_op("vec_d[node_id]", d_op, "g"); - } else { - printer->add_line("vec_rhs[node_id] {} rhs;"_format(rhs_op)); - printer->add_line("vec_d[node_id] {} g;"_format(d_op)); - } -} - - void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { // no tiling for ispc backend but make sure variables are declared as uniform printer->add_line("int uniform start = 0;"); @@ -218,18 +188,49 @@ void CodegenIspcVisitor::print_get_memb_list() { } +void CodegenIspcVisitor::print_atomic_op(const std::string& lhs, + const std::string& op, + const std::string& rhs) { + std::string function; + if (op == "+") { + function = "atomic_add_local"; + } else if (op == "-") { + function = "atomic_subtract_local"; + } else { + throw std::runtime_error("ISPC backend error : {} not supported"_format(op)); + } + printer->add_line("{}(&{}, {});"_format(function, lhs, rhs)); +} + + void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_reduction() { - // do nothing + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + if (info.point_process) { + printer->start_block("if (programIndex == 0)"); + printer->add_line("uniform int node_id = node_index[id];"); + printer->add_line("vec_rhs[node_id] {} shadow_rhs[id];"_format(rhs_op)); + printer->add_line("vec_d[node_id] {} shadow_d[id];"_format(d_op)); + printer->end_block(1); + } +} + + +void CodegenIspcVisitor::print_shadow_reduction_block_begin() { + printer->start_block("for (uniform int id = start; id < end; id++) "); } void CodegenIspcVisitor::print_rhs_d_shadow_variables() { - // do nothing + if (info.point_process) { + printer->add_line("double* uniform shadow_rhs = nt->{};"_format(naming::NTHREAD_RHS_SHADOW)); + printer->add_line("double* uniform shadow_d = nt->{};"_format(naming::NTHREAD_D_SHADOW)); + } } bool CodegenIspcVisitor::nrn_cur_reduction_loop_required() { - return false; + return info.point_process; } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 3caf42c800..08e1edcf23 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -28,8 +28,20 @@ namespace codegen { * \brief %Visitor for printing C code with ISPC backend */ class CodegenIspcVisitor: public CodegenCVisitor { + + /** + * Prints an ISPC atomic operation + * + * \note This is currently not used because of performance issues on KNL. Instead reduction + * operations are serialized. + * + * \param lhs Reduction left-hand-side operand + * \param op Reducation operation. Currently only \c += and \c -= are supported + * \param rhs Reduction right-hand-side operand + */ void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); + /// ast nodes which are not compatible with ISPC target const std::vector incompatible_node_types{ ast::AstNodeType::VERBATIM, @@ -83,14 +95,17 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_backend_includes() override; - /// update to matrix elements with/without shadow vectors - void print_nrn_cur_matrix_shadow_update() override; - - /// reduction to matrix elements from shadow vectors void print_nrn_cur_matrix_shadow_reduction() override; + /** + * Print block / loop for statement requiring reduction + * + */ + void print_shadow_reduction_block_begin() override; + + /// setup method for setting matrix shadow vectors void print_rhs_d_shadow_variables() override; From 0c61fee314e8d340dbb00ab041b87bb75262658f Mon Sep 17 00:00:00 2001 From: Francesco Date: Wed, 29 May 2019 22:42:06 +0200 Subject: [PATCH 219/871] Fix mofidication of const ion variables (BlueBrain/nmodl#210) - read_ion_var should never be marked as const because they store copy of corresponding neuron variable value (in which case copy is required) Resolves BlueBrain/nmodl#209. NMODL Repo SHA: BlueBrain/nmodl@892b87acce2b197d929b3bcd7178a174d2488acb --- src/nmodl/codegen/codegen_c_visitor.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index ecc636846c..d27d3e9f56 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -729,9 +729,6 @@ bool CodegenCVisitor::is_constant_variable(std::string name) { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { - if (symbol->has_any_property(NmodlType::read_ion_var)) { - is_constant = true; - } if (symbol->has_any_property(NmodlType::param_assign) && symbol->get_write_count() == 0) { is_constant = true; } From 8af6c80eced84f813fd81065c09769ae8c6e5278 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Sun, 2 Jun 2019 20:49:47 +0200 Subject: [PATCH 220/871] OpenACC backend fixes (BlueBrain/nmodl#146) * Support net_receive on GPU * Fix two major issues : - kernels with eigen and newton solver now magically works with PGI 19.4. Big relief! - setup_global_variables is done in reg() function before ion types were set. * Use async on kernels launches similar to mod2c * Make openacc specific changes more generic - openacc backend overrides method to provide device pointer NMODL Repo SHA: BlueBrain/nmodl@9e04769140265383b6e001d52ee120666ab10acf --- src/nmodl/codegen/codegen_acc_visitor.cpp | 22 +++++++++-- src/nmodl/codegen/codegen_acc_visitor.hpp | 3 ++ src/nmodl/codegen/codegen_c_visitor.cpp | 46 +++++++++++++++++----- src/nmodl/codegen/codegen_c_visitor.hpp | 6 +++ src/nmodl/codegen/codegen_ispc_visitor.cpp | 3 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 1 - 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 2c841b0e97..02f3312773 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -32,15 +32,22 @@ namespace codegen { * */ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { - if (!info.artificial_cell) { - std::string present_clause = "present(node_index, data, voltage, indexes, thread"; + if (info.artificial_cell) { + return; + } + + std::string present_clause = "present(inst"; + if (type == BlockType::NetReceive) { + present_clause += ", nrb"; + } else { + present_clause += ", node_index, data, voltage, indexes, thread"; if (type == BlockType::Equation) { present_clause += ", vec_rhs, vec_d"; } - present_clause += ")"; - printer->add_line("#pragma acc parallel loop {}"_format(present_clause)); } + present_clause += ")"; + printer->add_line("#pragma acc parallel loop {} async(nt->stream_id)"_format(present_clause)); } @@ -152,5 +159,12 @@ void CodegenAccVisitor::print_global_variable_device_update_annotation() { } } +std::string CodegenAccVisitor::get_variable_device_pointer(std::string variable, std::string type) { + if (info.artificial_cell) { + return variable; + } + return "({}) acc_deviceptr({})"_format(type, variable); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index c5099a09cb..a22242e2ce 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -79,6 +79,9 @@ class CodegenAccVisitor: public CodegenCVisitor { /// update global variable from host to the device void print_global_variable_device_update_annotation() override; + std::string get_variable_device_pointer(std::string variable, std::string type) override; + + public: CodegenAccVisitor(std::string mod_file, std::string output_dir, diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index d27d3e9f56..cca2711608 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2537,9 +2537,6 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("/** register channel with the simulator */"); printer->start_block("void _{}_reg() "_format(info.mod_file)); - // allocate global variables - printer->add_line("setup_global_variables();"); - // type related information auto mech_type = get_variable_name("mech_type"); auto suffix = add_escape_quote(info.mod_suffix); @@ -2570,6 +2567,9 @@ void CodegenCVisitor::print_mechanism_register() { } printer->add_newline(); + // allocate global variables + printer->add_line("setup_global_variables();"); + /* * If threads are used then memory is allocated in setup_global_variables. * Register callbacks for thread allocation and cleanup. Note that thread_data_index @@ -2998,6 +2998,14 @@ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) return float_data_type(); } +/** + * \details For CPU/Host target there is no device pointer. In this case + * just use the host variable name directly. + */ +std::string CodegenCVisitor::get_variable_device_pointer(std::string variable, std::string /*type*/) { + return variable; +} + void CodegenCVisitor::print_instance_variable_setup() { if (range_variable_setup_required()) { @@ -3022,14 +3030,22 @@ void CodegenCVisitor::print_instance_variable_setup() { } printer->add_line("Datum* indexes = ml->pdata;"); + + std::string float_type = default_float_data_type(); + std::string int_type = default_int_data_type(); + std::string float_type_pointer = float_type + "*"; + std::string int_type_pointer = int_type + "*"; + int id = 0; std::vector variables_to_free; + for (auto& var: codegen_float_variables) { auto name = var->get_name(); - auto default_type = default_float_data_type(); auto range_var_type = get_range_var_float_type(var); - if (default_type == range_var_type) { - printer->add_line("inst->{} = ml->data+{}{};"_format(name, id, stride)); + if (float_type == range_var_type) { + auto variable = "ml->data+{}{}"_format(id, stride); + auto device_variable = get_variable_device_pointer(variable, float_type_pointer); + printer->add_line("inst->{} = {};"_format(name, device_variable)); } else { printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format( name, id, stride)); @@ -3040,12 +3056,20 @@ void CodegenCVisitor::print_instance_variable_setup() { for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); + std::string variable = name; + std::string type = ""; if (var.is_index || var.is_integer) { - printer->add_line("inst->{} = ml->pdata;"_format(name)); + variable = "ml->pdata"; + type = int_type_pointer; + } else if (var.is_vdata) { + variable = "nt->_vdata"; + type = "void**"; } else { - auto data = var.is_vdata ? "_vdata" : "_data"; - printer->add_line("inst->{} = nt->{};"_format(name, data)); + variable = "nt->_data"; + type = info.artificial_cell ? "void*" : float_type_pointer; } + auto device_variable = get_variable_device_pointer(variable, type); + printer->add_line("inst->{} = {};"_format(name, device_variable)); } printer->add_line("ml->instance = (void*) inst;"); @@ -3487,6 +3511,7 @@ void CodegenCVisitor::print_get_memb_list() { void CodegenCVisitor::print_net_receive_loop_begin() { printer->add_line("int count = nrb->_displ_cnt;"); + print_channel_iteration_block_parallel_hint(BlockType::NetReceive); printer->start_block("for (int i = 0; i < count; i++)"); } @@ -3507,6 +3532,8 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { auto net_receive = method_name("net_receive_kernel"); + print_kernel_data_present_annotation_block_begin(); + printer->add_line( "NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;"_format(ptr_type_qualifier())); if (need_mech_inst) { @@ -3535,6 +3562,7 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->add_line("nrb->_displ_cnt = 0;"); printer->add_line("nrb->_cnt = 0;"); + print_kernel_data_present_annotation_block_end(); printer->end_block(1); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index cab76f5ff7..119fc16888 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1585,6 +1585,12 @@ class CodegenCVisitor: public visitor::AstVisitor { virtual void print_wrapper_routines(); + /** + * Get device variable pointer for corresponding host variable + */ + virtual std::string get_variable_device_pointer(std::string variable, std::string type); + + CodegenCVisitor(std::string mod_filename, std::string output_dir, LayoutType layout, diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index e3a2abffb9..1564837f64 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -223,7 +223,8 @@ void CodegenIspcVisitor::print_shadow_reduction_block_begin() { void CodegenIspcVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { - printer->add_line("double* uniform shadow_rhs = nt->{};"_format(naming::NTHREAD_RHS_SHADOW)); + printer->add_line( + "double* uniform shadow_rhs = nt->{};"_format(naming::NTHREAD_RHS_SHADOW)); printer->add_line("double* uniform shadow_d = nt->{};"_format(naming::NTHREAD_D_SHADOW)); } } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 08e1edcf23..95f07985e2 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -28,7 +28,6 @@ namespace codegen { * \brief %Visitor for printing C code with ISPC backend */ class CodegenIspcVisitor: public CodegenCVisitor { - /** * Prints an ISPC atomic operation * From 290e13ff2f1647e5a92814ebfddd7842486e163c Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 18 Jun 2019 13:31:21 +0200 Subject: [PATCH 221/871] Codegen compatibility (BlueBrain/nmodl#196) * Added CodegenCompatibilityVisitor to check if there are blocks in the AST that are not compatible with the NMODL code generation and print related error message if there are NMODL Repo SHA: BlueBrain/nmodl@0e45509152ebc5b91a6f10a8cae52af167f28923 --- src/nmodl/ast/ast_common.hpp | 15 ++ src/nmodl/codegen/CMakeLists.txt | 2 + src/nmodl/codegen/codegen_c_visitor.cpp | 3 +- .../codegen/codegen_compatibility_visitor.cpp | 137 +++++++++++++ .../codegen/codegen_compatibility_visitor.hpp | 182 ++++++++++++++++++ src/nmodl/codegen/codegen_helper_visitor.cpp | 9 - src/nmodl/language/parser.py | 15 +- src/nmodl/language/templates/ast/ast.hpp | 17 ++ src/nmodl/language/templates/pybind/pyast.cpp | 16 +- src/nmodl/language/templates/pybind/pyast.hpp | 4 + src/nmodl/lexer/modtoken.cpp | 9 + src/nmodl/lexer/modtoken.hpp | 21 ++ src/nmodl/nmodl/main.cpp | 30 ++- src/nmodl/parser/nmodl.yy | 65 +++++-- test/nmodl/transpiler/CMakeLists.txt | 2 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 34 ++++ test/nmodl/transpiler/parser/parser.cpp | 41 +++- 17 files changed, 558 insertions(+), 44 deletions(-) create mode 100644 src/nmodl/codegen/codegen_compatibility_visitor.cpp create mode 100644 src/nmodl/codegen/codegen_compatibility_visitor.hpp diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 0fcdfc699a..0f06251556 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -179,6 +179,21 @@ struct Ast: public std::enable_shared_from_this { */ virtual std::string get_node_type_name() = 0; + /** + * \brief Return NMODL statement of ast node as std::string + * + * Every node is related to a special statement in the NMODL. This + * statement can be returned as a std::string for printing to + * text/json form. + * + * @return name of the statement as a string + * + * \sa Ast::get_nmodl_name + */ + virtual std::string get_nmodl_name() { + throw std::runtime_error("get_nmodl_name not implemented"); + } + /** * \brief Accept (or visit) the AST node using current visitor * diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 971c681f1e..2264fb7667 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -4,6 +4,8 @@ set(CODEGEN_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_compatibility_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_compatibility_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_cuda_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_cuda_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_omp_visitor.cpp diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index cca2711608..a3216a81a6 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3002,7 +3002,8 @@ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) * \details For CPU/Host target there is no device pointer. In this case * just use the host variable name directly. */ -std::string CodegenCVisitor::get_variable_device_pointer(std::string variable, std::string /*type*/) { +std::string CodegenCVisitor::get_variable_device_pointer(std::string variable, + std::string /*type*/) { return variable; } diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp new file mode 100644 index 0000000000..69da031e29 --- /dev/null +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -0,0 +1,137 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include +#include +#include +#include + +#include "codegen/codegen_compatibility_visitor.hpp" +#include "parser/c11_driver.hpp" +#include "utils/logger.hpp" +#include "visitors/lookup_visitor.hpp" + +using namespace fmt::literals; + +namespace nmodl { +namespace codegen { + +using visitor::AstLookupVisitor; + +std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( + ast::Ast* node, + std::shared_ptr& ast_node) { + auto solve_block_ast_node = std::dynamic_pointer_cast(ast_node); + std::stringstream unhandled_method_error_message; + auto method = solve_block_ast_node->get_method(); + if (method == nullptr) + return ""; + auto unhandled_solver_method = handled_solvers.find(method->get_node_name()) == + handled_solvers.end(); + if (unhandled_solver_method) { + unhandled_method_error_message + << "\"{}\" solving method used at [{}] not handled. Supported methods are cnexp, euler, derivimplicit and sparse\n"_format( + method->get_node_name(), method->get_token()->position()); + } + return unhandled_method_error_message.str(); +} + +std::string CodegenCompatibilityVisitor::return_error_global_var( + ast::Ast* node, + std::shared_ptr& ast_node) { + auto global_var = std::dynamic_pointer_cast(ast_node); + std::stringstream error_message_global_var; + if (node->get_symbol_table()->lookup(global_var->get_node_name())->get_write_count() > 0) { + error_message_global_var + << "\"{}\" variable found at [{}] should be defined as a RANGE variable instead of GLOBAL to enable backend transformations\n"_format( + global_var->get_node_name(), global_var->get_token()->position()); + } + return error_message_global_var.str(); +} + +std::string CodegenCompatibilityVisitor::return_error_pointer(ast::Ast* node, + std::shared_ptr& ast_node) { + auto pointer_var = std::dynamic_pointer_cast(ast_node); + return "\"{}\" POINTER found at [{}] should be defined as BBCOREPOINTER to use it in CoreNeuron\n"_format( + pointer_var->get_node_name(), pointer_var->get_token()->position()); +} + +std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( + ast::Ast* node, + std::shared_ptr& ast_node) { + std::stringstream error_message_no_bbcore_read_write; + auto verbatim_nodes = AstLookupVisitor().lookup(node, AstNodeType::VERBATIM); + auto found_bbcore_read = false; + auto found_bbcore_write = false; + for (const auto& it: verbatim_nodes) { + auto verbatim = std::dynamic_pointer_cast(it); + + auto verbatim_statement_string = verbatim->get_statement()->get_value(); + + // Parse verbatim block to find out if there is a token "bbcore_read" or + // "bbcore_write". If there is not, then the function is not defined or + // is commented. + parser::CDriver driver; + + driver.scan_string(verbatim_statement_string); + auto tokens = driver.all_tokens(); + + for (const auto& token: tokens) { + if (token == "bbcore_read") { + found_bbcore_read = true; + } + if (token == "bbcore_write") { + found_bbcore_write = true; + } + } + } + if (!found_bbcore_read) { + error_message_no_bbcore_read_write + << "\"bbcore_read\" function not defined in any VERBATIM block\n"; + } + if (!found_bbcore_write) { + error_message_no_bbcore_read_write + << "\"bbcore_write\" function not defined in any VERBATIM block\n"; + } + return error_message_no_bbcore_read_write.str(); +} + +/** + * \details Checks all the ast::AstNodeType that are not handled in NMODL code + * generation backend for CoreNEURON and prints related messages. If there is + * some kind of incompatibility return false. + */ + +bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast* node) { + std::vector unhandled_ast_types; + for (auto kv: unhandled_ast_types_func) { + unhandled_ast_types.push_back(kv.first); + } + unhandled_ast_nodes = AstLookupVisitor().lookup(node, unhandled_ast_types); + + std::stringstream ss; + for (auto it: unhandled_ast_nodes) { + auto node_type = it->get_node_type(); + ss << (this->*unhandled_ast_types_func[node_type])(node, it); + } + if (!ss.str().empty()) { + logger->error("Code incompatibility detected"); + logger->error("Cannot translate mod file to .cpp file"); + logger->error("Fix the following errors and try again"); + std::string line; + std::istringstream ss_stringstream(ss.str()); + while (std::getline(ss_stringstream, line)) { + if (!line.empty()) + logger->error("Code Incompatibility :: {}"_format(line)); + } + return true; + } + return false; +} + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp new file mode 100644 index 0000000000..f78edbcc3d --- /dev/null +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -0,0 +1,182 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::codegen::CodegenCompatibilityVisitor + */ + +#include +#include + +#include "ast/ast.hpp" +#include "codegen_naming.hpp" +#include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" +#include "visitors/ast_visitor.hpp" + +using namespace fmt::literals; + +namespace nmodl { +namespace codegen { + +using namespace ast; + +/** + * @addtogroup codegen_backends + * @{ + */ + +/** + * \class CodegenCompatibilityVisitor + * \brief %Visitor for printing compatibility issues of the mod file + */ +class CodegenCompatibilityVisitor: public visitor::AstVisitor { + /// Typedef for defining FunctionPointer that points to the + /// function needed to be called for every kind of error + typedef std::string (CodegenCompatibilityVisitor::*FunctionPointer)(ast::Ast* node, + std::shared_ptr&); + + /// Unordered_map to find the function needed to be called in + /// for every ast::AstNodeType that is unsupported + std::map unhandled_ast_types_func = { + {AstNodeType::MATCH_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::BEFORE_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::AFTER_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::TERMINAL_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::DISCRETE_BLOCK, + &CodegenCompatibilityVisitor::return_error_with_name}, + {AstNodeType::PARTIAL_BLOCK, + &CodegenCompatibilityVisitor::return_error_with_name}, + {AstNodeType::FUNCTION_TABLE_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::CONSTANT_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::CONSTRUCTOR_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::DESTRUCTOR_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::INDEPENDENT_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::SOLVE_BLOCK, + &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, + {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, + {AstNodeType::POINTER_VAR, &CodegenCompatibilityVisitor::return_error_pointer}, + {AstNodeType::BBCORE_POINTER_VAR, + &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}}; + + /// Set of handled solvers by the NMODL \c C++ code generator + const std::set handled_solvers{codegen::naming::CNEXP_METHOD, + codegen::naming::EULER_METHOD, + codegen::naming::DERIVIMPLICIT_METHOD, + codegen::naming::SPARSE_METHOD}; + + /// Vector that stores all the ast::Node that are unhandled + /// by the NMODL \c C++ code generator + std::vector> unhandled_ast_nodes; + + public: + /// \name Ctor & dtor + /// \{ + + /// Default CodegenCompatibilityVisitor constructor + CodegenCompatibilityVisitor() = default; + + /// \} + + /// Function that searches the ast::Ast for nodes that + /// are incompatible with NMODL \c C++ code generator + /// + /// \param node Ast + /// \return bool if there are unhandled nodes or not + bool find_unhandled_ast_nodes(Ast* node); + + /// Takes as parameter an std::shared_ptr, + /// searches if the method used for solving is supported + /// and if it is not it returns a relative error message + /// + /// \param node Not used by the function + /// \param ast_node Ast node which is checked + /// \return std::string error + std::string return_error_if_solve_method_is_unhandled(ast::Ast* node, + std::shared_ptr& ast_node); + + /// Takes as parameter an std::shared_ptr node + /// and returns a relative error with the name, the type + /// and the location of the unhandled statement + /// + /// \tparam T Type of node parameter in the ast::Ast + /// \param node Not used by the function + /// \param ast_node Ast node which is checked + /// \return std::string error + template + std::string return_error_with_name(ast::Ast* node, std::shared_ptr& ast_node) { + auto real_type_block = std::dynamic_pointer_cast(ast_node); + return "\"{}\" {}construct found at [{}] is not handled\n"_format( + ast_node->get_node_name(), + real_type_block->get_nmodl_name(), + real_type_block->get_token()->position()); + } + + /// Takes as parameter an std::shared_ptr node + /// and returns a relative error with the type and the + /// location of the unhandled statement + /// + /// \tparam T Type of node parameter in the ast::Ast + /// \param node Not used by the function + /// \param ast_node Ast node which is checked + /// \return std::string error + template + std::string return_error_without_name(ast::Ast* node, std::shared_ptr& ast_node) { + auto real_type_block = std::dynamic_pointer_cast(ast_node); + return "{}construct found at [{}] is not handled\n"_format( + real_type_block->get_nmodl_name(), real_type_block->get_token()->position()); + } + + /// Takes as parameter the ast::Ast to read the symbol table + /// and an std::shared_ptr node and returns relative + /// error if a variable that is writen in the mod file is + /// defined as GLOBAL instead of RANGE + /// + /// \param node Ast + /// \param ast_node Ast node which is checked + /// \return std::string error + std::string return_error_global_var(ast::Ast* node, std::shared_ptr& ast_node); + + /// Takes as parameter an std::shared_ptr node + /// and returns a relative error with the name and the + /// location of the pointer, as well as a suggestion to + /// define it as BBCOREPOINTER + /// + /// \param node Not used by the function + /// \param ast_node Ast node which is checked + /// \return std::string error + std::string return_error_pointer(ast::Ast* node, std::shared_ptr& ast_node); + + /// Takes as parameter the ast::Ast and checks if the + /// functions "bbcore_read" and "bbcore_write" are defined + /// in any of the ast::Ast VERBATIM blocks. The function is + /// called if there is a BBCORE_POINTER defined in the mod + /// file + /// + /// \param node Ast + /// \param ast_node Not used by the function + /// \return std::string error + std::string return_error_if_no_bbcore_read_write(ast::Ast* node, + std::shared_ptr& ast_node); +}; + +/** @} */ // end of codegen_backends + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index bdee6374de..83f0db3c9f 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -170,15 +170,6 @@ void CodegenHelperVisitor::find_non_range_variables() { } } - /** - * todo : move this to separate pass - */ - if (!variables.empty()) { - std::string message = - "Global variables are updated in compute blocks, convert them to range? : "; - throw std::runtime_error(message + variables); - } - /** * If parameter is not a range and used only as read variable then it becomes global * variable. To qualify it as thread variable it must be be written at least once and diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index e8312fbf2c..db6c560e05 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -28,16 +28,6 @@ def __init__(self, filename, debug=False): self.filename = filename self.debug = debug - @staticmethod - def is_token(name): - """check if the name (i.e. class) is a token type in lexer - - Lexims returned from Lexer have position and hence token object. - Return True if this node is returned by lexer otherwise False - """ - return (name in node_info.LEXER_DATA_TYPES or - name in node_info.SYMBOL_BLOCK_TYPES or - name in node_info.ADDITIONAL_TOKEN_BLOCKS) def parse_child_rule(self, child): """parse child specification and return argument as properties @@ -162,9 +152,8 @@ def parse_yaml_rules(self, nodelist, base_class=None): else: args.base_class = base_class if base_class else 'Ast' - # check if we need token for the node - # todo : we will have token for every node - args.has_token = LanguageParser.is_token(class_name) + # store token in every node + args.has_token = True # name of the node while printing back to NMODL args.nmodl_name = properties['nmodl'] if 'nmodl' in properties else None diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index bc7f64fd7c..d58a9c2f51 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -168,6 +168,23 @@ namespace ast { return "{{ node.class_name }}"; } + {% if node.nmodl_name %} + /** + * \brief Return NMODL statement of ast node as std::string + * + * Every node is related to a special statement in the NMODL. This + * statement can be returned as a std::string for printing to + * text/json form. + * + * @return name of the statement as a string i.e. "{{ node.nmodl_name }}" + * + * \sa Ast::get_nmodl_name + */ + {{ virtual(node) }} std::string get_nmodl_name() override { + return "{{ node.nmodl_name }}"; + } + {% endif %} + /** * \brief Get std::shared_ptr from `this` pointer of the current ast node */ diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 7d15b7022e..84cbf8767c 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -88,7 +88,7 @@ static const char* get_node_type_name_method = R"( )"; static const char* get_node_name_method = R"( - Return name of of the node + Return name of the node Some ast nodes have a member designated as node name. For example, in case of ast.FunctionCall, name of the function is returned as a node @@ -96,6 +96,16 @@ static const char* get_node_name_method = R"( ast node has name. )"; +static const char* get_nmodl_name_method = R"( + Return nmodl statement of the node + + Some ast nodes have a member designated as nmodl name. For example, + in case of "NEURON { }" the statement of NMODL which is stored as nmodl + name is "NEURON". This function is only implemented by node types that + have a nmodl statement. +)"; + + static const char* clone_method = R"( Create a copy of the AST node )"; @@ -189,6 +199,7 @@ void init_ast_module(py::module& m) { .def("get_node_type", &Ast::get_node_type, docstring::get_node_type_method) .def("get_node_type_name", &Ast::get_node_type_name, docstring::get_node_type_name_method) .def("get_node_name", &Ast::get_node_name, docstring::get_node_name_method) + .def("get_nmodl_name", &Ast::get_nmodl_name, docstring::get_nmodl_name_method) .def("get_token", &Ast::get_token, docstring::get_token_method) .def("get_symbol_table", &Ast::get_symbol_table, @@ -247,6 +258,9 @@ void init_ast_module(py::module& m) { .def("clone", &{{ node.class_name }}::clone, docstring::clone_method) .def("get_node_type", &{{ node.class_name }}::get_node_type, docstring::get_node_type_method) .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name, docstring::get_node_type_name_method) + {% if node.nmodl_name %} + .def("get_nmodl_name", &{{ node.class_name }}::get_nmodl_name, docstring::get_nmodl_name_method) + {% endif %} {% if node.is_data_type_node %} .def("eval", &{{ node.class_name }}::eval, docstring::eval_method) {% endif %} diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 32a03436d2..81342f05ae 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -79,6 +79,10 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(std::string, Ast, get_node_name, ); } + std::string get_nmodl_name() override { + PYBIND11_OVERLOAD(std::string, Ast, get_nmodl_name, ); + } + std::shared_ptr get_shared_ptr() override { PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_shared_ptr, ); } diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index c288001c0e..a06e997f64 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -9,6 +9,8 @@ namespace nmodl { +using LocationType = nmodl::parser::location; + std::string ModToken::position() const { std::stringstream ss; if (external) { @@ -26,4 +28,11 @@ std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { return stream << " type " << mt.token; } +ModToken operator+(ModToken adder1, ModToken adder2) { + LocationType sum_pos = adder1.pos + adder2.pos; + ModToken sum(adder1.name, adder1.token, sum_pos); + + return sum; +} + } // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index caa15c9eda..9959cbca8c 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -133,6 +133,27 @@ class ModToken { * \endcode */ friend std::ostream& operator<<(std::ostream& stream, const ModToken& mt); + + /** + * Overload \c + operator to create an object whose position starts + * from the start line and column of the first adder and finishes + * at the end line and column of the second adder + * + * + * For example: + * + * \code + * a at [118.9-14] + * b at [121.4-5] + * \endcode + * + * Output: + * + * \code + * (a + b) at [118.9-121.5] + * \endcode + */ + friend ModToken operator+(ModToken adder1, ModToken adder2); }; /** @} */ // end of token_modtoken diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index c8195b6fdb..fff548eacf 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -16,6 +16,7 @@ #include "ast/ast_decl.hpp" #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_compatibility_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_omp_visitor.hpp" @@ -116,6 +117,10 @@ int main(int argc, const char* argv[]) { /// true if verbatim blocks bool verbatim_rename(true); + /// true if code generation is forced to happed even if there + /// is any incompatibility + bool force_codegen(false); + /// directory where code will be generated std::string output_dir("."); @@ -158,6 +163,9 @@ int main(int argc, const char* argv[]) { app.add_option("--scratch", scratch_dir, "Directory for intermediate code output", true) ->ignore_case(); app.add_option("--units", units_dir, "Directory of units lib file", true)->ignore_case(); + app.add_flag("--force_codegen", + force_codegen, + "Force code generation even if there is any incompatibility"); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, "C/C++ backend")->ignore_case(); @@ -250,6 +258,22 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(ast.get()); } + { + // make sure to run perf visitor because code generator + // looks for read/write counts const/non-const declaration + PerfVisitor().visit_program(ast.get()); + } + + { + // Compatibility Checking + logger->info("Running code compatibility checker"); + // If there is an incompatible construct and code generation is not forced exit NMODL + if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(ast.get()) && + !force_codegen) { + return 1; + } + } + if (show_symtab) { logger->info("Printing symbol table"); std::stringstream stream; @@ -368,12 +392,6 @@ int main(int argc, const char* argv[]) { PerfVisitor(file).visit_program(ast.get()); } - { - // make sure to run perf visitor because code generator - // looks for read/write counts const/non-const declaration - PerfVisitor().visit_program(ast.get()); - } - { auto mem_layout = layout == "aos" ? codegen::LayoutType::aos : codegen::LayoutType::soa; diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 81b2038a63..cd57a6877b 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -743,6 +743,8 @@ double : REAL independent_block : INDEPENDENT "{" independent_block_body "}" { $$ = new ast::IndependentBlock($3); + ModToken block_token = $1 + $4; + $$->set_token(block_token); } ; @@ -950,11 +952,17 @@ procedure : initial_block } | BEFORE before_after_block { - $$ = new ast::BeforeBlock($2); + auto new_before_block = new ast::BeforeBlock($2); + ModToken block_token = $1+*($2->get_token()); + new_before_block->set_token(block_token); + $$ = new_before_block; } | AFTER before_after_block { - $$ = new ast::AfterBlock($2); + auto new_after_block = new ast::AfterBlock($2); + ModToken block_token = $1+*($2->get_token()); + new_after_block->set_token(block_token); + $$ = new_after_block; } ; @@ -969,6 +977,8 @@ initial_block : INITIAL1 statement_list "}" constructor_block : CONSTRUCTOR statement_list "}" { $$ = new ast::ConstructorBlock($2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } ; @@ -976,6 +986,8 @@ constructor_block : CONSTRUCTOR statement_list "}" destructor_block : DESTRUCTOR statement_list "}" { $$ = new ast::DestructorBlock($2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } ; @@ -1598,8 +1610,8 @@ non_linear_block : NONLINEAR NAME_PTR optional_solvefor statement_list "}" discrete_block : DISCRETE NAME_PTR statement_list "}" { $$ = new ast::DiscreteBlock($2, $3); - // todo Disabled symbol table, remove this - //$$->set_token($1); + ModToken block_token = $1 + $4; + $$->set_token(block_token); } ; @@ -1607,7 +1619,8 @@ discrete_block : DISCRETE NAME_PTR statement_list "}" partial_block : PARTIAL NAME_PTR statement_list "}" { $$ = new ast::PartialBlock($2, $3); - $$->set_token($1); + ModToken block_token = $1 + $4; + $$->set_token(block_token); } | PARTIAL error { @@ -1645,7 +1658,9 @@ first_last : FIRST function_table_block : FUNCTION_TABLE NAME_PTR "(" optional_argument_list ")" units { $$ = new ast::FunctionTableBlock($2, $4, $6); - $$->set_token($1); + // units don't have token, use ")" as end location + ModToken block_token = $1 + $5; + $$->set_token(block_token); } ; @@ -1710,15 +1725,18 @@ initial_statement : INITIAL1 statement_list "}" solve_block : SOLVE NAME_PTR if_solution_error { $$ = new ast::SolveBlock($2, NULL, NULL, $3); + $$->set_token(*($2->get_token())); } | SOLVE NAME_PTR USING METHOD if_solution_error { $$ = new ast::SolveBlock($2, $4.clone(), NULL, $5); + $$->set_token(*($2->get_token())); } | SOLVE NAME_PTR STEADYSTATE METHOD if_solution_error { $$ = new ast::SolveBlock($2, NULL, $4.clone(), $5); + $$->set_token(*($2->get_token())); } | SOLVE error { @@ -1776,6 +1794,8 @@ breakpoint_block : BREAKPOINT statement_list "}" terminal_block : TERMINAL statement_list "}" { $$ = new ast::TerminalBlock($2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } ; @@ -1783,18 +1803,26 @@ terminal_block : TERMINAL statement_list "}" before_after_block : BREAKPOINT statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_BREAKPOINT), $2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } | SOLVE statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_SOLVE), $2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } | INITIAL1 statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_INITIAL), $2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } | STEP statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_STEP), $2); + ModToken block_token = $1 + $3; + $$->set_token(block_token); } | error { @@ -2043,6 +2071,8 @@ queue_statement : PUTQ name match_block : MATCH "{" match_list "}" { $$ = new ast::MatchBlock($3); + ModToken block_token = $1 + $4; + $$->set_token(block_token); } ; @@ -2149,6 +2179,8 @@ factor_definition : NAME_PTR "=" double unit constant_block : CONSTANT "{" constant_statement "}" { $$ = new ast::ConstantBlock($3); + ModToken block_token = $1 + $4; + $$->set_token(block_token); } ; @@ -2215,8 +2247,11 @@ optional_dependent_var_list : neuron_block : NEURON OPEN_BRACE neuron_statement CLOSE_BRACE { auto block = new ast::StatementBlock($3); - block->set_token($2); + ModToken statement_block = $2 + $4; + block->set_token(statement_block); $$ = new ast::NeuronBlock(block); + ModToken neuron_block = $1 + statement_block; + $$->set_token(neuron_block); } ; @@ -2407,11 +2442,15 @@ range_var_list : NAME_PTR global_var_list: NAME_PTR { $$ = ast::GlobalVarVector(); - $$.emplace_back(new ast::GlobalVar($1)); + auto new_global_var = new ast::GlobalVar($1); + new_global_var->set_token(*($1->get_token())); + $$.emplace_back(new_global_var); } | global_var_list "," NAME_PTR { - $1.emplace_back(new ast::GlobalVar($3)); + auto new_global_var = new ast::GlobalVar($3); + new_global_var->set_token(*($3->get_token())); + $1.emplace_back(new_global_var); $$ = $1; } | error @@ -2424,11 +2463,15 @@ global_var_list: NAME_PTR pointer_var_list : NAME_PTR { $$ = ast::PointerVarVector(); - $$.emplace_back(new ast::PointerVar($1)); + auto new_pointer_var = new ast::PointerVar($1); + new_pointer_var->set_token(*($1->get_token())); + $$.emplace_back(new_pointer_var); } | pointer_var_list "," NAME_PTR { - $1.emplace_back(new ast::PointerVar($3)); + auto new_pointer_var = new ast::PointerVar($3); + new_pointer_var->set_token(*($3->get_token())); + $1.emplace_back(new_pointer_var); $$ = $1; } | error diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index c32866551a..9fe32cb024 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -53,7 +53,7 @@ add_executable(testunitparser units/parser.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) -target_link_libraries(testparser lexer test_util util) +target_link_libraries(testparser lexer util test_util visitor) target_link_libraries(testvisitor visitor symtab lexer util test_util printer) target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index 66da722853..b17aa075fd 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -7,12 +7,15 @@ #define CATCH_CONFIG_MAIN +#include #include #include "catch/catch.hpp" #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/lookup_visitor.hpp" /** @file @@ -25,6 +28,7 @@ using namespace nmodl; using nmodl::parser::NmodlDriver; using nmodl::parser::NmodlLexer; +using LocationType = nmodl::parser::location; template void symbol_type(const std::string& name, T& value) { @@ -68,4 +72,34 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { } } +TEST_CASE("Addition of two ModToken objects", "[token][modtoken]") { + SECTION("adding two random strings") { + ast::Name value; + { + std::stringstream ss; + + nmodl::parser::position adder1_begin(nullptr, 1, 1); + nmodl::parser::position adder1_end(nullptr, 1, 5); + LocationType adder1_location(adder1_begin, adder1_end); + ModToken adder1("text", 1, adder1_location); + + nmodl::parser::position adder2_begin(nullptr, 2, 1); + nmodl::parser::position adder2_end(nullptr, 2, 5); + LocationType adder2_location(adder2_begin, adder2_end); + ModToken adder2("text", 2, adder2_location); + + ss << adder1; + ss << " + "; + ss << adder2; + + ModToken sum = adder1 + adder2; + ss << " = " << sum; + REQUIRE(ss.str() == + " text at [1.1-4] type 1 + text at [2.1-4] type 2 = " + " text at [1.1-2.4] type 1"); + } + } +} + + /** @} */ // end of token_test diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index aa287b998f..9015b6145c 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -11,12 +11,15 @@ #include #include "catch/catch.hpp" +#include "lexer/modtoken.hpp" #include "parser/diffeq_driver.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/lookup_visitor.hpp" using namespace nmodl::test_utils; - +using nmodl::visitor::AstLookupVisitor; //============================================================================= // Parser tests //============================================================================= @@ -162,4 +165,38 @@ SCENARIO("Legacy differential equation solver from NEURON solve number of ODE ty counter++; } } -} \ No newline at end of file +} + + +//============================================================================= +// Check if parsed NEURON block has correct token information +//============================================================================= + +void parse_neuron_block_string(const std::string& name, nmodl::ModToken& value) { + nmodl::parser::NmodlDriver driver; + driver.parse_string(name); + + auto ast_program = driver.get_ast(); + std::vector> neuron_blocks = + AstLookupVisitor().lookup(ast_program->get_shared_ptr().get(), + nmodl::ast::AstNodeType::NEURON_BLOCK); + value = *(neuron_blocks[0]->get_token()); +} + +SCENARIO("Check if a NEURON block is parsed with correct location info in its token") { + GIVEN("A single NEURON block") { + nmodl::ModToken value; + + std::stringstream ss; + std::string neuron_block = R"( + NEURON { + SUFFIX it + USEION ca READ cai,cao WRITE ica + RANGE gcabar, m_inf, tau_m, alph1, alph2, KK, shift + } + )"; + parse_neuron_block_string(reindent_text(neuron_block), value); + ss << value; + REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 303"); + } +} From 5b9463fe3fb52def5db972bfdce7f4b45153b491 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 3 Jul 2019 11:28:12 +0200 Subject: [PATCH 222/871] Update homebrew to avoid brew install failure (BlueBrain/nmodl#225) - brew install was failing with default osx image (i.e. 10.13) - move to latest osx 10.14 to avoid brew update NMODL Repo SHA: BlueBrain/nmodl@87ffa8a7a10bb077c2db162a4c13c48ecea5ae99 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index c95a859785..189edd8b58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,6 +22,7 @@ matrix: - PYTHON_VERSION=3.6.7 - language: cpp os: osx + osx_image: xcode10.2 env: - MATRIX_EVAL="CXX=c++" From 4a3b9ca93d437fb8de55be59b064d6ebc2637be9 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 3 Jul 2019 13:01:28 +0200 Subject: [PATCH 223/871] Added list of supported NMODL language constructs (BlueBrain/nmodl#212) - Also, added missing solve methods NMODL Repo SHA: BlueBrain/nmodl@8b5bcbe83ae8f5a65df96a8913803b4149e4e5d1 --- docs/nmodl/transpiler/index.rst | 1 + docs/nmodl/transpiler/language.rst | 189 +++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 docs/nmodl/transpiler/language.rst diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 691512cc16..4a494781f7 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -12,6 +12,7 @@ About NMODL readme.rst install.rst + language.rst .. toctree:: :maxdepth: 2 diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst new file mode 100644 index 0000000000..358a29cd8e --- /dev/null +++ b/docs/nmodl/transpiler/language.rst @@ -0,0 +1,189 @@ +The NEURON MODeling language +============================ + +The NMODL Framework is able to parse all language features and constructs of the NMODL DSL. +The programmer is thus able to parse any mechanism file with the NMODL Framework and process +the AST data structures with a few notable exceptions (mainly VERBATIM C blocks). + +The Framework however still lacks code generation support for a some of the language constructs. +In the following table we summarize the various NMODL DSL constructs and their support in the +framework. Code generation information is related to CoreNEURON backend. + + ++------------------------+-------------------+-------------------+---------------------+ +| NMODL DSL construct | parsing supported | codegen supported | AST docs available | ++========================+===================+===================+=====================+ +| Blocks | ++========================+===================+===================+=====================+ +| PARAMETER | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| STEPPED | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| ASSIGNED | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| STATE | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| INITIAL | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| DERIVATIVE | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| LINEAR | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| FUNCTION | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| PROCEDURE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| NET_RECEIVE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| SOLVE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| BREAKPOINT | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| KINETIC | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| UNITS | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| NEURON | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ +| VERBATIM | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| CONSTANT | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| MATCH | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| BEFORE | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| AFTER | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| STEP | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| TERMINAL | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| DISCRETE | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| PARTIAL | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| FUNCTION_TABLE | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| CONSTRUCTOR | yes | no | yes | ++------------------------+-------------------+-------------------+---------------------+ +| DESTRUCTOR | yes | no | yes | ++------------------------+-------------------+-------------------+---------------------+ +| INDEPENDENT | yes | no | yes | ++------------------------+-------------------+-------------------+---------------------+ +| Control Flow | ++========================+===================+===================+=====================+ +| FORALL | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| WHILE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| IF | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| ELSE IF | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| ELSE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| Other | ++========================+===================+===================+=====================+ +| ~ | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| -> | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| FOR_NETCONS | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| LOCAL | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| TITLE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| DEFINE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| INCLUDE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| SWEEP | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| PLOT | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| CONDUCTANCE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| PROTECT | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| FROM | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| WATCH | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| MUTEXLOCK | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| MUTEXUNLOCK | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| RESET | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| SENS | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| CONSERVE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| COMPARTMENT | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| LONGITUDINAL_DIFFUSION | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| LAG | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| TABLE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| USEION | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| NONSPECIFIC_CURRENT | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| ELECTRODE_CURRENT | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| SECTION | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| RANGE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| GLOBAL | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| POINTER | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| BBCOREPOINTER | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| EXTERNAL | yes | no | no | ++------------------------+-------------------+-------------------+---------------------+ +| THREADSAFE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| COMMENT | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| SOLVE METHODs | ++========================+===================+===================+=====================+ +| cnexp | yes | yes | | ++------------------------+-------------------+-------------------+---------------------+ +| euler | yes | yes | | ++------------------------+-------------------+-------------------+---------------------+ +| derivimplicit | yes | yes | | ++------------------------+-------------------+-------------------+---------------------+ +| sparse | yes | yes | | ++------------------------+-------------------+-------------------+---------------------+ +| runge | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| after_cvode | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| adams | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| adeuler | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| heun | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| adrunge | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| gear | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| simplex | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| simeq | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| seidel | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| clsoda | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| cvode_t | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ +| cvode_v | yes | no | | ++------------------------+-------------------+-------------------+---------------------+ From e36d3664c12251b1dba831a5cf70f25061966745 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Fri, 5 Jul 2019 11:41:47 +0200 Subject: [PATCH 224/871] Added a nmodl dockerfile (BlueBrain/nmodl#221) - The generated image provides a Jupyter notebook environment with nmodl setup. - Added a brief subsection on testing NMODL from docker into the README.md NMODL Repo SHA: BlueBrain/nmodl@2016af4d07023d53fba86fa6d3439ce825a8e11d --- README.md | 51 +++++++++++++++++++++++++++++++++++- docker/docker-compose.yml | 22 ++++++++++++++++ docker/recipe/Dockerfile | 54 +++++++++++++++++++++++++++++++++++++++ docker/recipe/entrypoint | 41 +++++++++++++++++++++++++++++ setup.py | 2 +- 5 files changed, 168 insertions(+), 2 deletions(-) create mode 100644 docker/docker-compose.yml create mode 100644 docker/recipe/Dockerfile create mode 100755 docker/recipe/entrypoint diff --git a/README.md b/README.md index 65e7e0a2dd..cc01877e71 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,54 @@ NET_RECEIVE(weight (uS)) { See [INSTALL.md](https://github.com/BlueBrain/nmodl/blob/master/INSTALL.md) for detailed instructions to build the NMODL from source. +### Try NMODL with Docker + +To quickly test the NMODL Framework's analysis capabilities we provide a +[docker](https://www.docker.com) image, which includes the NMODL Framework python library and a +fully functional Jupyter notebook environment. After installing [docker](https://docs.docker.com/compose/install/) and [docker-compose](https://docs.docker.com/compose/install/) you can pull and run the NMODL image from your terminal. + +To try Python interface directly from CLI, you can run docker image as: + +``` +docker run -it --entrypoint=/bin/sh bluebrain/nmodl +``` + +And try NMODL Python API discussed later in this README as: + +``` +$ python3 +Python 3.6.8 (default, Apr 8 2019, 18:17:52) +>>> from nmodl import dsl +>>> import os +>>> expsyn = os.path.join(dsl.example_dir(), "expsyn.mod") +... +``` + +To try Jupyter notebooks you can download docker compose file and run it as: + +```sh +wget "https://raw.githubusercontent.com/BlueBrain/nmodl/master/docker/docker-compose.yml" +DUID=$(id -u) DGID=$(id -g) HOSTNAME=$(hostname) docker-compose up +``` + +If all goes well you should see at the end status messages similar to these: + +``` +[I 09:49:53.923 NotebookApp] The Jupyter Notebook is running at: +[I 09:49:53.923 NotebookApp] http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 +[I 09:49:53.923 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). + To access the notebook, open this file in a browser: + file:///root/.local/share/jupyter/runtime/nbserver-1-open.html + Or copy and paste one of these URLs: + http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 +``` + +Based on the example above you should then open your browser and navigate to the URL `http://127.0.0.1:8888/?token=a7902983bad430a11935`. + +You can open and run all example notebooks provided in the `examples` folder. You can also create +new notebooks in `my_notebooks`, which will be stored in a subfolder `notebooks` at your current +working directory. + ### Using the Python API Once the NMODL Framework is installed, you can use the Python parsing API to load NMOD file as: @@ -94,11 +142,12 @@ Every key in the JSON form represent a node in the AST. You can also use visuali from nmodl import ast ast.view(modast) ``` + which will open AST view in web browser: ![ast_viz](https://user-images.githubusercontent.com/666852/57329449-12c9a400-7114-11e9-8da5-0042590044ec.gif "AST representation of expsyn.mod") -The central *Program* node represents the whole MOD file and each of it's children represent the block in the input NMODL file. +The central *Program* node represents the whole MOD file and each of it's children represent the block in the input NMODL file. Note that this requires X-forwarding if you are using Docker image. Once the AST is created, one can use exisiting visitors to perform various analysis/optimisations. One can also easily write his own custom visitor using Python Visitor API. See [Python API tutorial](docs/notebooks/nmodl-python-tutorial.ipynb) for details. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000000..f28457f513 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,22 @@ +version: '3.7' +services: + notebook: + image: bluebrain/nmodl:latest + hostname: ${HOSTNAME} + ports: + - "8888:8888" + environment: + - USER_LOGIN=${USER} + - USER_ID=${DUID} + - GROUP_ID=${DGID} + volumes: + - $PWD/notebooks:/nmodl/notebooks/my_notebooks + command: + - jupyter + - notebook + - --port=8888 + - --no-browser + - --ip=0.0.0.0 + - --allow-root + - --notebook-dir=/nmodl/notebooks + diff --git a/docker/recipe/Dockerfile b/docker/recipe/Dockerfile new file mode 100644 index 0000000000..42b07d5fe4 --- /dev/null +++ b/docker/recipe/Dockerfile @@ -0,0 +1,54 @@ +FROM alpine:3.9 AS builder + +WORKDIR /nmodl/src + +RUN apk add --update build-base gcc g++ make cmake flex flex-dev bison git python3-dev + +RUN pip3 install --trusted-host pypi.python.org jinja2 pyyaml pytest sympy + + +ARG NMODL_VERSION=master + +RUN git clone --recursive https://github.com/BlueBrain/nmodl.git && \ + cd nmodl && \ + git checkout ${NMODL_VERSION} + +WORKDIR /nmodl/src/nmodl + +RUN python3 setup.py build + +FROM alpine:3.9 + + +RUN apk add --no-cache --update shadow python3 libgfortran libstdc++ openblas && \ + apk add --no-cache --update \ + --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing gosu && \ + apk add --no-cache --virtual build-dependencies \ + build-base linux-headers openblas-dev freetype-dev \ + pkgconfig gfortran python3-dev && \ + pip3 install --no-cache-dir --trusted-host pypi.python.org \ + jinja2 pyyaml pytest sympy numpy matplotlib jupyter && \ + apk del build-dependencies && \ + rm -rf /var/cache/apk/* + +WORKDIR /usr/lib/python3.6/site-packages/nmodl + +COPY --from=builder /nmodl/src/nmodl/build/lib.linux-x86_64-3.6/nmodl . +COPY --from=builder /nmodl/src/nmodl/share /nmodl/src/share + +RUN echo "PROJECT_SOURCE_DIR=\"/nmodl/src\"" > config.py && \ + echo "PROJECT_INSTALL_DIR=\"/usr/lib/python3.6/site-packages/nmodl\"" >> config.py + +ENV LANG en_US.utf8 +ENV SHELL=/bin/bash + +ADD entrypoint /usr/bin/ +ENTRYPOINT ["/usr/bin/entrypoint"] + +EXPOSE 8888 +WORKDIR /nmodl/notebooks + +COPY --from=builder /nmodl/src/nmodl/docs/notebooks ./examples + +CMD ["jupyter", "notebook", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/nmodl/notebooks"] + diff --git a/docker/recipe/entrypoint b/docker/recipe/entrypoint new file mode 100755 index 0000000000..61e8cf1a1d --- /dev/null +++ b/docker/recipe/entrypoint @@ -0,0 +1,41 @@ +#!/bin/sh -e + +# Create fake user and group with the same ids than +# the host user, and use its identity all along +# in the container so that all created files in +# mounted volumes belongs to the host user. +# +# https://stackoverflow.com/questions/41857462 + +if [ "x$GROUP_ID" = x -o "x$USER_ID" = x ] ;then + echo 'Error: $USER_ID and $GROUP_ID environment variable not set' >&2 + echo Abort >&2 + exit 1 +fi + +# Create fake user +CUR_GROUP=`grep ":${GROUP_ID}:" /etc/group | cut -d: -f1` +if [ "x$CUR_GROUP" != x ] ;then + groupmod --new-name dummy "$CUR_GROUP" +else + grep -q ^dummy: /etc/group || groupadd -g $GROUP_ID dummy +fi +grep -q ^dummy: /etc/passwd || useradd -m -u $USER_ID -g $GROUP_ID dummy -s /bin/bash + + +chown -R dummy:dummy /home/dummy +chown -R dummy:dummy /nmodl + +chmod -R "u=rwX,go=rX" "/nmodl" + +# Run the given command as root if bash or sh, +# the fake user otherwise. +case "$1" in + sh|bash) + exec $@ + ;; + *) + cd /nmodl/notebooks + gosu dummy "$@" + ;; +esac diff --git a/setup.py b/setup.py index 78a8d35767..f26b44ada6 100644 --- a/setup.py +++ b/setup.py @@ -147,7 +147,7 @@ def run(self): setup( name="NMODL", - version="0.1", + version="0.2", author="Blue Brain Project", author_email="bbp-ou-hpc@groupes.epfl.ch", description="NEURON Modelling Language Source-to-Source Compiler Framework", From 060a248c84c9f6a27b413ab9991aef786b48978a Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 9 Jul 2019 12:44:12 +0200 Subject: [PATCH 225/871] Fix codegen issues and update travis CI for CoreNEURON tests (BlueBrain/nmodl#228) - BlueBrain/nmodl#196 introduced regression in the sense that the perfvisitor was not running just before codegen. Because of this, read/write count was not updated resulting in const/non-const issues. - update travis configuration so that coreneuron is built and run on every PR to avoid issues like above fixes BlueBrain/nmodl#226 BlueBrain/nmodl#227 NMODL Repo SHA: BlueBrain/nmodl@8fa30723820216758c57e9ae1a2a1b36bee56986 --- .travis.yml | 14 ++++++++++++++ src/nmodl/nmodl/main.cpp | 14 ++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 189edd8b58..981b1bb462 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,7 @@ addons: packages: - flex - bison + - libboost-all-dev - cmake - python3-dev - python3-pip @@ -45,6 +46,7 @@ addons: packages: - flex - bison + - boost - cmake - python@3 @@ -80,6 +82,18 @@ script: - make test - make install +#============================================================================= +# Build CoreNEURON and run tests +#============================================================================= +after_success: + - echo "------- Build and Test CoreNEURON -------" + - cd $HOME + - git clone --recursive https://github.com/BlueBrain/CoreNeuron.git + - mkdir CoreNeuron/build && cd CoreNeuron/build + - cmake .. -DENABLE_MPI=OFF -DENABLE_NMODL=ON -DNMODL_ROOT=$HOME/nmodl -DNMODL_EXTRA_FLAGS="passes --verbatim-rename --inline sympy --analytic" + - make -j + - make test + #============================================================================= # Notifications #============================================================================= diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index fff548eacf..6374ff2259 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -258,15 +258,11 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(ast.get()); } - { - // make sure to run perf visitor because code generator - // looks for read/write counts const/non-const declaration - PerfVisitor().visit_program(ast.get()); - } - { // Compatibility Checking logger->info("Running code compatibility checker"); + // run perfvisitor to update read/wrie counts + PerfVisitor().visit_program(ast.get()); // If there is an incompatible construct and code generation is not forced exit NMODL if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(ast.get()) && !force_codegen) { @@ -392,6 +388,12 @@ int main(int argc, const char* argv[]) { PerfVisitor(file).visit_program(ast.get()); } + { + // make sure to run perf visitor because code generator + // looks for read/write counts const/non-const declaration + PerfVisitor().visit_program(ast.get()); + } + { auto mem_layout = layout == "aos" ? codegen::LayoutType::aos : codegen::LayoutType::soa; From 03a762a6fba564bf4e669d5b0f027838f5662224 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 9 Jul 2019 14:32:23 +0200 Subject: [PATCH 226/871] Fixes issue with Eigen solver related variables F, X, J, Jm clashing with code variable (BlueBrain/nmodl#223) - Changed name of N, X, F, J, Jm, Eigen::Matrix to "Variable_random_string" in sympy_solver_visitor - Made needed changes to filter the name of the variables coming from sympy solution - Single random suffix for every matrix is passed to the codegen_c_visitor using a Singleton pattern - SingletonRandomString class is a template based on the length of the random string - It has an std::map to keep all the random strings that need to be appended to the original string - Creation of new functions to avoid code repetition NMODL Repo SHA: BlueBrain/nmodl@6d4103cdc7e1aeb0a15f278d1532bdbba272dd88 --- src/nmodl/codegen/codegen_c_visitor.cpp | 53 ++++++++++---- src/nmodl/codegen/codegen_c_visitor.hpp | 7 ++ src/nmodl/utils/common_utils.cpp | 16 +++++ src/nmodl/utils/common_utils.hpp | 77 +++++++++++++++++++++ src/nmodl/visitors/sympy_solver_visitor.cpp | 70 ++++++++++++++++--- src/nmodl/visitors/sympy_solver_visitor.hpp | 17 +++++ 6 files changed, 218 insertions(+), 22 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index a3216a81a6..26503fc991 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1621,17 +1621,36 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = false; } +std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) { + auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); + std::string unique_name = original_name; + if (singleton_random_string_class->random_string_exists(original_name)) { + unique_name = original_name; + unique_name += "_" + singleton_random_string_class->get_random_string(original_name); + }; + return unique_name; +} void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) { // solution vector to store copy of state vars for Newton solver printer->add_newline(); + + // Check if there is a variable defined in the mod file as X, J, Jm or F and if yes + // try to use a different string for the matrices created by sympy in the form + // X_, J_, Jm_ and F_ + std::string X = find_var_unique_name("X"); + std::string J = find_var_unique_name("J"); + std::string Jm = find_var_unique_name("Jm"); + std::string F = find_var_unique_name("F"); + auto float_type = default_float_data_type(); int N = node->get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{}, {}, 1> X;"_format(float_type, N)); + printer->add_line("Eigen::Matrix<{}, {}, 1> {};"_format(float_type, N, X)); print_statement_block(node->get_setup_x_block().get(), false, false); - // functor that evaluates F(X) and J(X) for Newton solver + // functor that evaluates F(X) and J(X) for + // Newton solver printer->start_block("struct functor"); printer->add_line("NrnThread* nt;"); printer->add_line("{0}* inst;"_format(instance_struct())); @@ -1655,11 +1674,11 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->add_indent(); printer->add_text( - "void operator()(const Eigen::Matrix<{0}, {1}, 1>& X, Eigen::Matrix<{0}, {1}, " - "1>& F, " - "Eigen::Matrix<{0}, {1}, {1}>& Jm) const"_format(float_type, N)); + "void operator()(const Eigen::Matrix<{0}, {1}, 1>& {2}, Eigen::Matrix<{0}, {1}, " + "1>& {3}, " + "Eigen::Matrix<{0}, {1}, {1}>& {4}) const"_format(float_type, N, X, F, Jm)); printer->start_block(); - printer->add_line("{}* J = Jm.data();"_format(float_type)); + printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); print_statement_block(node->get_functor_block().get(), false, false); printer->end_block(2); @@ -1676,7 +1695,8 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->add_line("// call newton solver"); printer->add_line("functor newton_functor(nt, inst, id, pnodecount, v, indexes);"); printer->add_line("newton_functor.initialize();"); - printer->add_line("int newton_iterations = nmodl::newton::newton_solver(X, newton_functor);"); + printer->add_line( + "int newton_iterations = nmodl::newton::newton_solver({}, newton_functor);"_format(X)); // assign newton solver results in matrix X to state vars print_statement_block(node->get_update_states_block().get(), false, false); @@ -1685,19 +1705,28 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc void CodegenCVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) { printer->add_newline(); + + // Check if there is a variable defined in the mod file as X, J, Jm or F and if yes + // try to use a different string for the matrices created by sympy in the form + // X_, J_, Jm_ and F_ + std::string X = find_var_unique_name("X"); + std::string J = find_var_unique_name("J"); + std::string Jm = find_var_unique_name("Jm"); + std::string F = find_var_unique_name("F"); + std::string float_type = default_float_data_type(); int N = node->get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{0}, {1}, 1> X, F;"_format(float_type, N)); - printer->add_line("Eigen::Matrix<{0}, {1}, {1}> Jm;"_format(float_type, N)); - printer->add_line("{}* J = Jm.data();"_format(float_type)); + printer->add_line("Eigen::Matrix<{0}, {1}, 1> {2}, {3};"_format(float_type, N, X, F)); + printer->add_line("Eigen::Matrix<{0}, {1}, {1}> {2};"_format(float_type, N, Jm)); + printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); print_statement_block(node->get_variable_block().get(), false, false); print_statement_block(node->get_initialize_block().get(), false, false); print_statement_block(node->get_setup_x_block().get(), false, false); printer->add_newline(); printer->add_line( - "X = Eigen::PartialPivLU>>(Jm).solve(F);"_format( - float_type, N)); + "{0} = Eigen::PartialPivLU>>({3}).solve({4});"_format( + X, float_type, N, Jm, F)); print_statement_block(node->get_update_states_block().get(), false, false); print_statement_block(node->get_finalize_block().get(), false, false); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 119fc16888..e09d8071de 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1776,6 +1776,13 @@ class CodegenCVisitor: public visitor::AstVisitor { */ void set_codegen_global_variables(std::vector& global_vars); + /** + * Find unique variable name defined in nmodl::utils::SingletonRandomString by the + * nmodl::visitor::SympySolverVisitor + * @param original_name Original name of variable to change + * @return std::string Unique name produced as _ + */ + std::string find_var_unique_name(const std::string& original_name); virtual void visit_binary_expression(ast::BinaryExpression* node) override; virtual void visit_binary_operator(ast::BinaryOperator* node) override; diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 163b3e4cf2..6501ab7294 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -55,5 +56,20 @@ bool make_path(const std::string& path) { } } +std::string generate_random_string(const int len) { + std::string s(len, 0); + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + std::random_device dev; + std::mt19937 rng(dev()); + std::uniform_int_distribution dist(0, (sizeof(alphanum) - 1)); + for (int i = 0; i < len; ++i) { + s[i] = alphanum[dist(rng)]; + } + return s; +} + } // namespace utils } // namespace nmodl diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 0c5fa5ba3a..84e7372e7a 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -7,6 +7,9 @@ #pragma once +#include +#include + /** * * \dir @@ -51,6 +54,80 @@ bool make_path(const std::string& path); /// Check if directory with given path exist bool is_dir_exist(const std::string& path); +/// Generate random std::string of length len based on a +/// uniform distribution +std::string generate_random_string(int len); + +/** + * \class SingletonRandomString + * \brief Singleton class for random strings + * + * Singleton class for random strings that are appended to the + * Eigen matrices names that are used in the solutions of + * nmodl::visitor::SympySolverVisitor and need to be the same to + * be printed by the nmodl::codegen::CodegenCVisitor + */ +template +class SingletonRandomString { + public: + /// Delete public constructor needed for the singleton pattern to work + SingletonRandomString(SingletonRandomString const&) = delete; + /// Delete public "=" operator + SingletonRandomString& operator=(SingletonRandomString const&) = delete; + + /// Function to instantiate the SingletonRandomString class + static std::shared_ptr instance() { + static std::shared_ptr s{new SingletonRandomString}; + return s; + } + + /** Check if there is a random string assigned as suffix for the var_name variable + * + * @param var_name Variable name to check if exists in the std::map of random strings + * @return true if it exists, false if not + */ + bool random_string_exists(const std::string& var_name) const { + return (random_strings.find(var_name) != random_strings.end()); + } + + /** Get the random string of the var_name variable + * + * @param var_name Variable name for which to get the random string + * @return Random string assigned to var_name + */ + std::string get_random_string(const std::string& var_name) const { + return random_strings.at(var_name); + } + + /** If var_name has already got a random string assigned remove it from map + * and assign a new one, else simply insert a new random string for var_name + * + * @param var_name Variable name for which to reset the random string + * @return Random string assigned to var_name + */ + std::string reset_random_string(const std::string& var_name) { + if (random_string_exists(var_name)) { + random_strings.erase(var_name); + random_strings.insert({var_name, generate_random_string(SIZE)}); + } else { + random_strings.insert({var_name, generate_random_string(SIZE)}); + } + return random_strings[var_name]; + } + + private: + /// \name Ctor & dtor + /// \{ + + /// Constructor used by instance() + SingletonRandomString() {} + + /// \} + + /// std::map that keeps the random strings assigned to variables as suffix + std::map random_strings; +}; + /** @} */ // end of utils } // namespace utils diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index e9a7487542..91d6d155f9 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -129,10 +129,63 @@ static bool is_local_statement(std::shared_ptr statement) { return false; } +std::string& SympySolverVisitor::replaceAll(std::string& context, + const std::string& from, + const std::string& to) const { + std::size_t lookHere = 0; + std::size_t foundHere; + while ((foundHere = context.find(from, lookHere)) != std::string::npos) { + context.replace(foundHere, from.size(), to); + lookHere = foundHere + to.size(); + } + return context; +} + +std::vector SympySolverVisitor::filter_string_vector( + const std::vector& original_vector, + const std::string& original_string, + const std::string& substitution_string) const { + std::vector filtered_vector; + for (auto element: original_vector) { + std::string filtered_element = replaceAll(element, original_string, substitution_string); + filtered_vector.push_back(filtered_element); + } + return filtered_vector; +} + +std::string SympySolverVisitor::suffix_random_string(const std::string& original_string) const { + std::string new_string = original_string; + std::string random_string; + auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); + // Check if there is a variable defined in the mod file as original_string and if yes + // try to use a different string for the matrices created by sympy in the form + // _ + while (vars.find(new_string) != vars.end()) { + random_string = singleton_random_string_class->reset_random_string(original_string); + new_string = original_string; + new_string += "_" + random_string; + } + return new_string; +} + void SympySolverVisitor::construct_eigen_solver_block( const std::vector& pre_solve_statements, const std::vector& solutions, bool linear) { + // Provide random string to append to X, J, Jm and F matrices that + // are produced by sympy + std::string unique_X = suffix_random_string("X"); + std::string unique_J = suffix_random_string("J"); + std::string unique_Jm = suffix_random_string("Jm"); + std::string unique_F = suffix_random_string("F"); + + // filter solutions for matrices named "X", "J", "Jm" and "F" and change them to + // unique_X, unique_J, unique_Jm and unique_F respectively + auto solutions_filtered = filter_string_vector(solutions, "X[", unique_X + "["); + solutions_filtered = filter_string_vector(solutions_filtered, "J[", unique_J + "["); + solutions_filtered = filter_string_vector(solutions_filtered, "Jm[", unique_Jm + "["); + solutions_filtered = filter_string_vector(solutions_filtered, "F[", unique_F + "["); + // find out where to insert solution in statement block auto& statements = block_with_expression_statements->statements; auto it = get_solution_location_iterator(statements); @@ -146,18 +199,15 @@ void SympySolverVisitor::construct_eigen_solver_block( std::vector setup_x_eqs; std::vector update_state_eqs; for (int i = 0; i < state_vars.size(); i++) { - auto statement = state_vars[i] + " = X[" + std::to_string(i) + "]"; - auto rev_statement = "X[" + std::to_string(i) + "] = " + state_vars[i]; + auto statement = state_vars[i] + " = " + unique_X + "[" + std::to_string(i) + "]"; + auto rev_statement = unique_X + "[" + std::to_string(i) + "] = " + state_vars[i]; update_state_eqs.push_back(statement); setup_x_eqs.push_back(rev_statement); - logger->debug("SympySolverVisitor :: setup_x: {}", rev_statement); + logger->debug("SympySolverVisitor :: setup_", unique_X, ": {}", rev_statement); logger->debug("SympySolverVisitor :: update_state: {}", statement); } - // TODO: make unique name for Eigen vector if clashes - if (vars.find("X") != vars.end()) { - logger->error("SympySolverVisitor :: -> X conflicts with NMODL variable"); - } - for (const auto& sol: solutions) { + + for (const auto& sol: solutions_filtered) { logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); } // statements after last diff/linear/non-linear eq statement go into finalize_block @@ -186,7 +236,7 @@ void SympySolverVisitor::construct_eigen_solver_block( if (linear) { /// create eigen linear solver block - setup_x_eqs.insert(setup_x_eqs.end(), solutions.begin(), solutions.end()); + setup_x_eqs.insert(setup_x_eqs.end(), solutions_filtered.begin(), solutions_filtered.end()); auto setup_x_block = create_statement_block(setup_x_eqs); auto solver_block = std::make_shared(n_state_vars, variable_block, @@ -201,7 +251,7 @@ void SympySolverVisitor::construct_eigen_solver_block( } else { /// create eigen newton solver block auto setup_x_block = create_statement_block(setup_x_eqs); - auto functor_block = create_statement_block(solutions); + auto functor_block = create_statement_block(solutions_filtered); auto solver_block = std::make_shared(n_state_vars, variable_block, initialize_block, diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index a274b709d1..5b7e04e464 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -96,6 +96,23 @@ class SympySolverVisitor: public AstVisitor { return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } + /// Function used by SympySolverVisitor::filter_X to replace the name X in a std::string + /// to X_operator + std::string& replaceAll(std::string& context, + const std::string& from, + const std::string& to) const; + + /// Check original_vector for elements that contain a variable named original_string and + /// rename it to substitution_string + std::vector filter_string_vector(const std::vector& original_vector, + const std::string& original_string, + const std::string& substitution_string) const; + + /// Return a std::string in the form _, where + /// random_string is a string defined in the nmodl::utils::SingletonRandomString + /// for the original_string + std::string suffix_random_string(const std::string& original_string) const; + /// global variables std::set global_vars; From d18088a95449752bb71ce417032ef08de86d3534 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 10 Jul 2019 17:52:30 +0200 Subject: [PATCH 227/871] Changed --force_codegen flag to --force subcommand of codegen (BlueBrain/nmodl#233) NMODL Repo SHA: BlueBrain/nmodl@398436de26d1c5c95a935ffd3dc9b3c1af64f474 --- README.md | 1 + src/nmodl/nmodl/main.cpp | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cc01877e71..4f75090cd0 100644 --- a/README.md +++ b/README.md @@ -264,6 +264,7 @@ codegen Options: --layout TEXT:{aos,soa}=soa Memory layout for code generation --datatype TEXT:{float,double}=soa Data type for floating point variables + --force Force code generation even if there is any code incompatibility ``` ### Documentation diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 6374ff2259..1081b31b51 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -163,9 +163,6 @@ int main(int argc, const char* argv[]) { app.add_option("--scratch", scratch_dir, "Directory for intermediate code output", true) ->ignore_case(); app.add_option("--units", units_dir, "Directory of units lib file", true)->ignore_case(); - app.add_flag("--force_codegen", - force_codegen, - "Force code generation even if there is any incompatibility"); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, "C/C++ backend")->ignore_case(); @@ -200,6 +197,8 @@ int main(int argc, const char* argv[]) { auto codegen_opt = app.add_subcommand("codegen", "Code generation options")->ignore_case(); codegen_opt->add_option("--layout", layout, "Memory layout for code generation", true)->ignore_case()->check(CLI::IsMember({"aos", "soa"})); codegen_opt->add_option("--datatype", layout, "Data type for floating point variables", true)->ignore_case()->check(CLI::IsMember({"float", "double"})); + codegen_opt->add_flag("--force", force_codegen, "Force code generation even if there is any incompatibility"); + // clang-format on CLI11_PARSE(app, argc, argv); From 44ce2e18412889d7b4085d6576320a1cfbca3c12 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Thu, 11 Jul 2019 22:50:21 +0200 Subject: [PATCH 228/871] Fix issue with installing extra resources (BlueBrain/nmodl#232) * Make extra resources handling pythonic - Removed CMakeLists config.py generation - Replaced references to config paths and replace by `pkg_resources` - Added MANIFEST.in file telling distutils where to find the share directory for installation - Move `share` folder into nmodl directory - Update Dockerfile - Made sure nrnunits.lib file remains where it is expected - Update README.md fixes BlueBrain/nmodl#224 NMODL Repo SHA: BlueBrain/nmodl@3ffa518f80b5e2c44f1b209378d6cec61d2a370a --- README.md | 6 ++-- cmake/nmodl/CMakeLists.txt | 2 -- docker/recipe/Dockerfile | 4 --- nmodl/ast.py | 12 +++---- nmodl/config.py.in | 2 -- nmodl/dsl.py | 41 +++++++++++++++++------- {share => nmodl/ext}/example/exp2syn.mod | 0 {share => nmodl/ext}/example/expsyn.mod | 0 {share => nmodl/ext}/example/hh.mod | 0 {share => nmodl/ext}/example/passive.mod | 0 {share => nmodl/ext}/viz/css/tree.css | 0 {share => nmodl/ext}/viz/index.html | 0 {share => nmodl/ext}/viz/js/d3.min.js | 0 {share => nmodl/ext}/viz/js/tree.js | 0 setup.py | 1 + src/nmodl/pybind/CMakeLists.txt | 2 +- 16 files changed, 40 insertions(+), 30 deletions(-) delete mode 100644 nmodl/config.py.in rename {share => nmodl/ext}/example/exp2syn.mod (100%) rename {share => nmodl/ext}/example/expsyn.mod (100%) rename {share => nmodl/ext}/example/hh.mod (100%) rename {share => nmodl/ext}/example/passive.mod (100%) rename {share => nmodl/ext}/viz/css/tree.css (100%) rename {share => nmodl/ext}/viz/index.html (100%) rename {share => nmodl/ext}/viz/js/d3.min.js (100%) rename {share => nmodl/ext}/viz/js/tree.js (100%) diff --git a/README.md b/README.md index 4f75090cd0..c4edfdce98 100644 --- a/README.md +++ b/README.md @@ -108,11 +108,11 @@ Once the NMODL Framework is installed, you can use the Python parsing API to loa ```python from nmodl import dsl -import os -expsyn = os.path.join(dsl.example_dir(), "expsyn.mod") +examples = dsl.list_examples() +nmodl_string = dsl.load_example(examples[-1]) driver = dsl.NmodlDriver() -modast = driver.parse_file(expsyn) +modast = driver.parse_string(nmodl_string) ``` The `parse_file` API returns Abstract Syntax Tree ([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) representation of input NMODL file. One can look at the AST by converting to JSON form as: diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 8ee54585e1..dc6532c510 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -110,8 +110,6 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) # generate file with version number from git and nrnunits.lib file path configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in ${CMAKE_CURRENT_BINARY_DIR}/config.cpp @ONLY) -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/nmodl/config.py.in ${PROJECT_SOURCE_DIR}/nmodl/config.py - @ONLY) # generate Doxyfile with correct source paths configure_file(${PROJECT_SOURCE_DIR}/docs/Doxyfile.in ${PROJECT_SOURCE_DIR}/docs/Doxyfile) diff --git a/docker/recipe/Dockerfile b/docker/recipe/Dockerfile index 42b07d5fe4..c5660b54c2 100644 --- a/docker/recipe/Dockerfile +++ b/docker/recipe/Dockerfile @@ -34,10 +34,6 @@ RUN apk add --no-cache --update shadow python3 libgfortran libstdc++ openblas && WORKDIR /usr/lib/python3.6/site-packages/nmodl COPY --from=builder /nmodl/src/nmodl/build/lib.linux-x86_64-3.6/nmodl . -COPY --from=builder /nmodl/src/nmodl/share /nmodl/src/share - -RUN echo "PROJECT_SOURCE_DIR=\"/nmodl/src\"" > config.py && \ - echo "PROJECT_INSTALL_DIR=\"/usr/lib/python3.6/site-packages/nmodl\"" >> config.py ENV LANG en_US.utf8 ENV SHELL=/bin/bash diff --git a/nmodl/ast.py b/nmodl/ast.py index 0e298a3058..f16bbf2d92 100644 --- a/nmodl/ast.py +++ b/nmodl/ast.py @@ -1,5 +1,5 @@ from ._nmodl.ast import * # noqa -from .config import * +from pkg_resources import * def view(nmodl_ast): """Visualize given NMODL AST in web browser @@ -22,16 +22,16 @@ def view(nmodl_ast): import tempfile import webbrowser - installed_viz_tool = os.path.join(PROJECT_INSTALL_DIR, "share", "viz") - if os.path.exists(installed_viz_tool): - viz_tool_dir = installed_viz_tool + resource = "ext/viz" + if resource_exists(__name__, resource) and resource_isdir(__name__, resource): + installed_viz_tool = resource_filename(__name__, resource) else: - viz_tool_dir = os.path.join(PROJECT_SOURCE_DIR, "share", "viz") + raise FileNotFoundError("Could not find sample mod files") work_dir = os.path.join(tempfile.gettempdir(), getpass.getuser(), "nmodl") # first copy necessary files to temp work directory - copy_tree(viz_tool_dir, work_dir) + copy_tree(installed_viz_tool, work_dir) # prepare json data with open(os.path.join(work_dir, 'ast.js'), 'w') as outfile: diff --git a/nmodl/config.py.in b/nmodl/config.py.in deleted file mode 100644 index 271d07d06c..0000000000 --- a/nmodl/config.py.in +++ /dev/null @@ -1,2 +0,0 @@ -PROJECT_SOURCE_DIR="@PROJECT_SOURCE_DIR@" -PROJECT_INSTALL_DIR="@CMAKE_INSTALL_PREFIX@" diff --git a/nmodl/dsl.py b/nmodl/dsl.py index e949194445..746150fa85 100644 --- a/nmodl/dsl.py +++ b/nmodl/dsl.py @@ -1,21 +1,38 @@ +import os.path as osp +from pkg_resources import * + from ._nmodl import * -from .config import * +RESOURCE_DIR = "ext/example" -def example_dir(): - """Returns directory containing NMODL examples +def list_examples(): + """Returns a list of examples available - NMODL Framework is installed with few sample example of - channels. This method can be used to get the directory - containing all mod files. + The NMODL Framework provides a few examples for testing Returns: - Full path of directory containing sample mod files + List of available examples """ - import os - installed_example = os.path.join(PROJECT_INSTALL_DIR, "share", "example") - if os.path.exists(installed_example): - return installed_example + if resource_exists(__name__, RESOURCE_DIR) and resource_isdir(__name__, RESOURCE_DIR): + return resource_listdir(__name__, RESOURCE_DIR) else: - return os.path.join(PROJECT_SOURCE_DIR, "share", "example") + raise FileNotFoundError("Could not find sample directory") + + +def load_example(example): + """Load an example from the NMODL examples + + The NMODL Framework provides a few examples for testing. The list of examples can be requested + using `list_examples()`. This function then returns the NMODL code of the requested example + file. + Args: + example: Filename of an example as provided by `list_examples()` + Returns: + List of available examples + """ + resource = osp.join(RESOURCE_DIR, example) + if resource_exists(__name__, resource): + return resource_string(__name__, resource) + else: + raise FileNotFoundError("Could not find sample mod files") diff --git a/share/example/exp2syn.mod b/nmodl/ext/example/exp2syn.mod similarity index 100% rename from share/example/exp2syn.mod rename to nmodl/ext/example/exp2syn.mod diff --git a/share/example/expsyn.mod b/nmodl/ext/example/expsyn.mod similarity index 100% rename from share/example/expsyn.mod rename to nmodl/ext/example/expsyn.mod diff --git a/share/example/hh.mod b/nmodl/ext/example/hh.mod similarity index 100% rename from share/example/hh.mod rename to nmodl/ext/example/hh.mod diff --git a/share/example/passive.mod b/nmodl/ext/example/passive.mod similarity index 100% rename from share/example/passive.mod rename to nmodl/ext/example/passive.mod diff --git a/share/viz/css/tree.css b/nmodl/ext/viz/css/tree.css similarity index 100% rename from share/viz/css/tree.css rename to nmodl/ext/viz/css/tree.css diff --git a/share/viz/index.html b/nmodl/ext/viz/index.html similarity index 100% rename from share/viz/index.html rename to nmodl/ext/viz/index.html diff --git a/share/viz/js/d3.min.js b/nmodl/ext/viz/js/d3.min.js similarity index 100% rename from share/viz/js/d3.min.js rename to nmodl/ext/viz/js/d3.min.js diff --git a/share/viz/js/tree.js b/nmodl/ext/viz/js/tree.js similarity index 100% rename from share/viz/js/tree.js rename to nmodl/ext/viz/js/tree.js diff --git a/setup.py b/setup.py index f26b44ada6..f37a4ac9c4 100644 --- a/setup.py +++ b/setup.py @@ -153,6 +153,7 @@ def run(self): description="NEURON Modelling Language Source-to-Source Compiler Framework", long_description="", packages=["nmodl"], + include_package_data=True, ext_modules=[CMakeExtension("nmodl")], cmdclass=lazy_dict( build_ext=CMakeBuild, diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 90495cb2e8..254201cdfa 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,7 +3,7 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) -foreach(file ast.py config.py dsl.py ode.py symtab.py visitor.py __init__.py) +foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() From 67578745242681aee3b0132adf865516ce9a8378 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Mon, 29 Jul 2019 17:49:18 +0200 Subject: [PATCH 229/871] Automated documentation building with travis (BlueBrain/nmodl#234) * Add documentation building - also, in comments, deployment back to gh-pages - fixed conf.py script not to add the project root into python paths - added missing doxygen package on linux * Added PYTHONPATH into travis conf NMODL Repo SHA: BlueBrain/nmodl@af0b679cf08814b2988c59b95d6bdec96af75aa0 --- .travis.yml | 30 +++++++++++++++++++++++++++++- docs/nmodl/transpiler/conf.py | 2 -- docs/nmodl/transpiler/language.rst | 23 ++++++++++++----------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 981b1bb462..4ea4d88faf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -41,6 +41,8 @@ addons: - cmake - python3-dev - python3-pip + - doxygen + - pandoc # for Mac builds, we use Homebrew homebrew: packages: @@ -70,6 +72,9 @@ install: - echo "------- Install Dependencies -------" - pip3 install -U pip setuptools - pip3 install Jinja2 PyYAML pytest sympy --user + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + pip3 install -U --user sphinx nbsphinx>=0.3.2 m2r sphinx-rtd-theme jupyter; + fi #============================================================================= # Build, test and install @@ -83,9 +88,17 @@ script: - make install #============================================================================= -# Build CoreNEURON and run tests +# Build Documentation, CoreNEURON and run tests #============================================================================= after_success: + - export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH + - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then + echo "------- Build Documentation -------"; + cd $TRAVIS_BUILD_DIR/docs; + doxygen && make html; + rm -rf _build/doctrees && touch _build/.nojekyll; + echo "" > _build/index.html; + fi - echo "------- Build and Test CoreNEURON -------" - cd $HOME - git clone --recursive https://github.com/BlueBrain/CoreNeuron.git @@ -102,3 +115,18 @@ notifications: recipients: pramod.s.kumbhar@gmail.com on_success: change on_failure: always + + +#============================================================================= +# Documentation deployment +#============================================================================= +deploy: + provider: pages + skip_cleanup: true + github_token: $GITHUB_TOKEN + keep_history: false + local_dir: $TRAVIS_BUILD_DIR/docs/_build/ + target_branch: gh-pages + on: + branch: master + condition: $TRAVIS_OS_NAME = linux diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index 823ecb4e98..80522afaa5 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -22,8 +22,6 @@ import sys import textwrap -sys.path.insert(0, os.path.abspath("..")) - import nmodl # isort:skip # Run doxygen diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index 358a29cd8e..596ecb0892 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -14,7 +14,7 @@ framework. Code generation information is related to CoreNEURON backend. | NMODL DSL construct | parsing supported | codegen supported | AST docs available | +========================+===================+===================+=====================+ | Blocks | -+========================+===================+===================+=====================+ ++------------------------+-------------------+-------------------+---------------------+ | PARAMETER | yes | yes | yes | +------------------------+-------------------+-------------------+---------------------+ | STEPPED | yes | yes | yes | @@ -72,7 +72,7 @@ framework. Code generation information is related to CoreNEURON backend. | INDEPENDENT | yes | no | yes | +------------------------+-------------------+-------------------+---------------------+ | Control Flow | -+========================+===================+===================+=====================+ ++------------------------+-------------------+-------------------+---------------------+ | FORALL | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | WHILE | yes | yes | no | @@ -84,7 +84,7 @@ framework. Code generation information is related to CoreNEURON backend. | ELSE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | Other | -+========================+===================+===================+=====================+ ++------------------------+-------------------+-------------------+---------------------+ | ~ | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | -> | yes | yes | no | @@ -111,19 +111,19 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | WATCH | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| MUTEXLOCK | yes | no | no | +| MUTEXLOCK | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| MUTEXUNLOCK | yes | no | no | +| MUTEXUNLOCK | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| RESET | yes | no | no | +| RESET | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| SENS | yes | no | no | +| SENS | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | CONSERVE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | COMPARTMENT | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| LONGITUDINAL_DIFFUSION | yes | no | no | +| LONGITUDINAL_DIFFUSION | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | LAG | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ @@ -135,7 +135,7 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | ELECTRODE_CURRENT | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| SECTION | yes | no | no | +| SECTION | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | RANGE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ @@ -145,14 +145,14 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | BBCOREPOINTER | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| EXTERNAL | yes | no | no | +| EXTERNAL | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | THREADSAFE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | COMMENT | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | SOLVE METHODs | -+========================+===================+===================+=====================+ ++------------------------+-------------------+-------------------+---------------------+ | cnexp | yes | yes | | +------------------------+-------------------+-------------------+---------------------+ | euler | yes | yes | | @@ -187,3 +187,4 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | cvode_v | yes | no | | +------------------------+-------------------+-------------------+---------------------+ + From 2750511091b1130741db248e82641ea6f946307b Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 14 Aug 2019 17:11:22 +0200 Subject: [PATCH 230/871] Fixed small issue in code generation (BlueBrain/nmodl#236) the generated code was including _kinderiv.h as a system-dependent lib rather than as a local include. This broke compatibility with mod2c and coreneuron. NMODL Repo SHA: BlueBrain/nmodl@bfa7a6f3820d8b4cd56ad23ddbe376282e192455 --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 26503fc991..288ed6903c 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2297,7 +2297,7 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); - printer->add_line("#include <_kinderiv.h>"); + printer->add_line("#include \"_kinderiv.h\""); if (info.eigen_newton_solver_exist) { printer->add_line("#include "); } From 39c17bcbfe9948714eb12f7c6b1665b02407ecf7 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 28 Aug 2019 23:24:08 +0200 Subject: [PATCH 231/871] Add default value of options into help string (BlueBrain/nmodl#238) - added fmt string of argument default value. NMODL Repo SHA: BlueBrain/nmodl@f02224cfcf11c013b8bdd8e62dc6ecb444ef2d53 --- src/nmodl/nmodl/main.cpp | 97 +++++++++++++++++++++++++++++----------- 1 file changed, 71 insertions(+), 26 deletions(-) diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 1081b31b51..6e572c3564 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -103,7 +103,7 @@ int main(int argc, const char* argv[]) { bool nmodl_const_folding(false); /// true if range variables to be converted to local - bool localize(false); + bool nmodl_localize(false); /// true if localize variables even if verbatim block is used bool localize_verbatim(false); @@ -165,39 +165,84 @@ int main(int argc, const char* argv[]) { app.add_option("--units", units_dir, "Directory of units lib file", true)->ignore_case(); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); - host_opt->add_flag("--c", c_backend, "C/C++ backend")->ignore_case(); - host_opt->add_flag("--omp", omp_backend, "C/C++ backend with OpenMP")->ignore_case(); - host_opt->add_flag("--ispc", ispc_backend, "C/C++ backend with ISPC")->ignore_case(); + host_opt->add_flag("--c", c_backend, "C/C++ backend ({})"_format(c_backend))->ignore_case(); + host_opt->add_flag("--omp", omp_backend, "C/C++ backend with OpenMP ({})"_format(omp_backend)) + ->ignore_case(); + host_opt->add_flag("--ispc", ispc_backend, "C/C++ backend with ISPC ({})"_format(ispc_backend)) + ->ignore_case(); auto acc_opt = app.add_subcommand("acc", "Accelerator code backends")->ignore_case(); - acc_opt->add_flag("--oacc", oacc_backend, "C/C++ backend with OpenACC")->ignore_case(); - acc_opt->add_flag("--cuda", cuda_backend, "C/C++ backend with CUDA")->ignore_case(); + acc_opt + ->add_flag("--oacc", oacc_backend, "C/C++ backend with OpenACC ({})"_format(oacc_backend)) + ->ignore_case(); + acc_opt->add_flag("--cuda", cuda_backend, "C/C++ backend with CUDA ({})"_format(cuda_backend)) + ->ignore_case(); // clang-format off auto sympy_opt = app.add_subcommand("sympy", "SymPy based analysis and optimizations")->ignore_case(); - sympy_opt->add_flag("--analytic", sympy_analytic, "Solve ODEs using SymPy analytic integration")->ignore_case(); - sympy_opt->add_flag("--pade", sympy_pade, "Pade approximation in SymPy analytic integration")->ignore_case(); - sympy_opt->add_flag("--cse", sympy_cse, "CSE (Common Subexpression Elimination) in SymPy analytic integration")->ignore_case(); - sympy_opt->add_flag("--conductance", sympy_conductance, "Add CONDUCTANCE keyword in BREAKPOINT")->ignore_case(); + sympy_opt->add_flag("--analytic", + sympy_analytic, + "Solve ODEs using SymPy analytic integration ({})"_format(sympy_analytic))->ignore_case(); + sympy_opt->add_flag("--pade", + sympy_pade, + "Pade approximation in SymPy analytic integration ({})"_format(sympy_pade))->ignore_case(); + sympy_opt->add_flag("--cse", + sympy_cse, + "CSE (Common Subexpression Elimination) in SymPy analytic integration ({})"_format(sympy_cse))->ignore_case(); + sympy_opt->add_flag("--conductance", + sympy_conductance, + "Add CONDUCTANCE keyword in BREAKPOINT ({})"_format(sympy_conductance))->ignore_case(); auto passes_opt = app.add_subcommand("passes", "Analyse/Optimization passes")->ignore_case(); - passes_opt->add_flag("--inline", nmodl_inline, "Perform inlining at NMODL level")->ignore_case(); - passes_opt->add_flag("--unroll", nmodl_unroll, "Perform loop unroll at NMODL level")->ignore_case(); - passes_opt->add_flag("--const-folding", nmodl_const_folding, "Perform constant folding at NMODL level")->ignore_case(); - passes_opt->add_flag("--localize", localize, "Convert RANGE variables to LOCAL")->ignore_case(); - passes_opt->add_flag("--localize-verbatim", localize_verbatim, "Convert RANGE variables to LOCAL even if verbatim block exist")->ignore_case(); - passes_opt->add_flag("--local-rename", local_rename, "Rename LOCAL variable if variable of same name exist in global scope")->ignore_case(); - passes_opt->add_flag("--verbatim-inline", verbatim_inline, "Inline even if verbatim block exist")->ignore_case(); - passes_opt->add_flag("--verbatim-rename", verbatim_rename, "Rename variables in verbatim block")->ignore_case(); - passes_opt->add_flag("--json-ast", json_ast, "Write AST to JSON file")->ignore_case(); - passes_opt->add_flag("--nmodl-ast", nmodl_ast, "Write AST to NMODL file")->ignore_case(); - passes_opt->add_flag("--json-perf", json_perfstat, "Write performance statistics to JSON file")->ignore_case(); - passes_opt->add_flag("--show-symtab", show_symtab, "Write symbol table to stdout")->ignore_case(); + passes_opt->add_flag("--inline", + nmodl_inline, + "Perform inlining at NMODL level ({})"_format(nmodl_inline))->ignore_case(); + passes_opt->add_flag("--unroll", + nmodl_unroll, + "Perform loop unroll at NMODL level ({})"_format(nmodl_unroll))->ignore_case(); + passes_opt->add_flag("--const-folding", + nmodl_const_folding, + "Perform constant folding at NMODL level ({})"_format(nmodl_const_folding))->ignore_case(); + passes_opt->add_flag("--localize", + nmodl_localize, + "Convert RANGE variables to LOCAL ({})"_format(nmodl_localize))->ignore_case(); + passes_opt->add_flag("--localize-verbatim", + localize_verbatim, + "Convert RANGE variables to LOCAL even if verbatim block exist ({})"_format(localize_verbatim))->ignore_case(); + passes_opt->add_flag("--local-rename", + local_rename, + "Rename LOCAL variable if variable of same name exist in global scope ({})"_format(local_rename))->ignore_case(); + passes_opt->add_flag("--verbatim-inline", + verbatim_inline, + "Inline even if verbatim block exist ({})"_format(verbatim_inline))->ignore_case(); + passes_opt->add_flag("--verbatim-rename", + verbatim_rename, + "Rename variables in verbatim block ({})"_format(verbatim_rename))->ignore_case(); + passes_opt->add_flag("--json-ast", + json_ast, + "Write AST to JSON file ({})"_format(json_ast))->ignore_case(); + passes_opt->add_flag("--nmodl-ast", + nmodl_ast, + "Write AST to NMODL file ({})"_format(nmodl_ast))->ignore_case(); + passes_opt->add_flag("--json-perf", + json_perfstat, + "Write performance statistics to JSON file ({})"_format(json_perfstat))->ignore_case(); + passes_opt->add_flag("--show-symtab", + show_symtab, + "Write symbol table to stdout ({})"_format(show_symtab))->ignore_case(); auto codegen_opt = app.add_subcommand("codegen", "Code generation options")->ignore_case(); - codegen_opt->add_option("--layout", layout, "Memory layout for code generation", true)->ignore_case()->check(CLI::IsMember({"aos", "soa"})); - codegen_opt->add_option("--datatype", layout, "Data type for floating point variables", true)->ignore_case()->check(CLI::IsMember({"float", "double"})); - codegen_opt->add_flag("--force", force_codegen, "Force code generation even if there is any incompatibility"); + codegen_opt->add_option("--layout", + layout, + "Memory layout for code generation", + true)->ignore_case()->check(CLI::IsMember({"aos", "soa"})); + codegen_opt->add_option("--datatype", + layout, + "Data type for floating point variables", + true)->ignore_case()->check(CLI::IsMember({"float", "double"})); + codegen_opt->add_flag("--force", + force_codegen, + "Force code generation even if there is any incompatibility"); // clang-format on @@ -346,7 +391,7 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(ast.get(), filepath("local_rename")); } - if (localize) { + if (nmodl_localize) { // localize pass must follow rename pass to avoid conflict logger->info("Running localize visitor"); LocalizeVisitor(localize_verbatim).visit_program(ast.get()); From 1068de1f4bc018f5783a7502320cfbac909e9439 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 3 Sep 2019 09:44:54 +0200 Subject: [PATCH 232/871] Handle code generation for INITIAL block under NET_RECEIVE (BlueBrain/nmodl#240) - variables under INITIAL block were not correctly handle - bug fix: if all parameters were not used correctly, variable from weight index was not correctly used - added unit tests for VarUsageVisitor Co-Authored-By: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@f526c9b6ed91c5ec432b800848d4e5846d355952 --- src/nmodl/codegen/codegen_c_visitor.cpp | 60 ++++++++++--- test/nmodl/transpiler/CMakeLists.txt | 1 + test/nmodl/transpiler/visitor/var_usage.cpp | 95 +++++++++++++++++++++ 3 files changed, 142 insertions(+), 14 deletions(-) create mode 100644 test/nmodl/transpiler/visitor/var_usage.cpp diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 288ed6903c..9fbf648f53 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3372,7 +3372,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_ printer->add_line("int tid = pnt->_tid;"); printer->add_line("int id = pnt->_i_instance;"); printer->add_line("double v = 0;"); - if (info.artificial_cell) { + if (info.artificial_cell || node->is_initial_block()) { printer->add_line("NrnThread* nt = nrn_threads + tid;"); printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); } @@ -3393,14 +3393,14 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_ printer->add_newline(); for (auto& parameter: parameters) { auto name = parameter->get_node_name(); - VarUsageVisitor vu; - auto var_used = vu.variable_used(node, "(*" + name + ")"); + bool var_used = VarUsageVisitor().variable_used(node, "(*" + name + ")"); if (var_used) { - auto statement = "double* {} = weights + weight_index + {};"_format(name, i++); + auto statement = "double* {} = weights + weight_index + {};"_format(name, i); printer->add_line(statement); RenameVisitor vr(name, "*" + name); node->visit_children(&vr); } + i++; } } } @@ -3481,12 +3481,52 @@ void CodegenCVisitor::print_net_event_call(FunctionCall* node) { printer->add_text(")"); } +/** + * Rename arguments to NET_RECEIVE block with corresponding pointer variable + * + * Arguments to NET_RECEIVE block are packed and passed via weight vector. These + * variables need to be replaced with corresponding pointer variable. For example, + * if mod file is like + * + * \code{.mod} + * NET_RECEIVE (weight, R){ + * INITIAL { + * R=1 + * } + * } + * \endcode + * + * then generated code for initial block should be: + * + * \code{.cpp} + * double* R = weights + weight_index + 0; + * (*R) = 1.0; + * \endcode + * + * So, the `R` in AST needs to be renamed with `(*R)`. + */ +static void rename_net_receive_arguments(ast::NetReceiveBlock* net_receive_node, ast::Node* node) { + auto parameters = net_receive_node->get_parameters(); + for (auto& parameter: parameters) { + auto name = parameter->get_node_name(); + auto var_used = VarUsageVisitor().variable_used(node, name); + if (var_used) { + RenameVisitor vr(name, "(*" + name + ")"); + node->get_statement_block()->visit_children(&vr); + } + } +} + void CodegenCVisitor::print_net_init() { auto node = info.net_receive_initial_node; if (node == nullptr) { return; } + + // rename net_receive arguments used in the initial block of net_receive + rename_net_receive_arguments(info.net_receive_node, node); + codegen = true; auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); @@ -3633,16 +3673,8 @@ void CodegenCVisitor::print_net_receive_kernel() { printing_net_receive = true; auto node = info.net_receive_node; - // rename arguments if same name is used - auto parameters = node->get_parameters(); - for (auto& parameter: parameters) { - auto name = parameter->get_node_name(); - auto var_used = VarUsageVisitor().variable_used(node, name); - if (var_used) { - RenameVisitor vr(name, "(*" + name + ")"); - node->get_statement_block()->visit_children(&vr); - } - } + // rename net_receive arguments used in the block itself + rename_net_receive_arguments(info.net_receive_node, node); std::string name; auto params = ParamVector(); diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 9fe32cb024..f68b121d5c 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -44,6 +44,7 @@ add_executable(testvisitor visitor/sympy_conductance.cpp visitor/sympy_solver.cpp visitor/units.cpp + visitor/var_usage.cpp visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) diff --git a/test/nmodl/transpiler/visitor/var_usage.cpp b/test/nmodl/transpiler/visitor/var_usage.cpp new file mode 100644 index 0000000000..619957c907 --- /dev/null +++ b/test/nmodl/transpiler/visitor/var_usage.cpp @@ -0,0 +1,95 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "parser/nmodl_driver.hpp" +#include "test/utils/test_utils.hpp" +#include "visitors/var_usage_visitor.hpp" + +using namespace nmodl; +using namespace visitor; + +//============================================================================= +// Variable usage visitor tests +//============================================================================= + +bool run_var_usage_visitor(std::shared_ptr node, const std::string& variable) { + return VarUsageVisitor().variable_used(node.get(), variable); +} + +SCENARIO("Searching for variable name using VarUsageVisitor", "[visitor][var_usage]") { + auto to_ast = [](const std::string& text) { + parser::NmodlDriver driver; + return driver.parse_string(text); + }; + + GIVEN("A simple NMODL block") { + std::string nmodl_text = R"( + DERIVATIVE states { + tau = 11.1 + exp(tau) + { + h' = h + 2 + n + } + } + )"; + + auto ast = to_ast(nmodl_text); + auto node = ast->blocks[0]; + + WHEN("Looking for existing variable") { + THEN("Can find variables") { + REQUIRE(run_var_usage_visitor(node, "tau")); + REQUIRE(run_var_usage_visitor(node, "h")); + REQUIRE(run_var_usage_visitor(node, "n")); + } + } + + WHEN("Looking for missing variable") { + THEN("Can not find variable") { + REQUIRE_FALSE(run_var_usage_visitor(node, "tauu")); + REQUIRE_FALSE(run_var_usage_visitor(node, "my_var")); + } + } + } + + GIVEN("A nested NMODL block") { + std::string nmodl_text = R"( + NET_RECEIVE (weight,weight_AMPA, weight_NMDA, R){ + LOCAL result + weight_AMPA = weight + weight_NMDA = weight * NMDA_ratio + INITIAL { + R = 1 + u = u0 + { + tsyn = t + } + } + } + )"; + + auto ast = to_ast(nmodl_text); + auto node = ast->blocks[0]; + + WHEN("Looking for existing variable in outer block") { + THEN("Can find variables") { + REQUIRE(run_var_usage_visitor(node, "weight")); + REQUIRE(run_var_usage_visitor(node, "NMDA_ratio")); + } + } + + WHEN("Looking for existing variable in inner block") { + THEN("Can find variables") { + REQUIRE(run_var_usage_visitor(node, "R")); + REQUIRE(run_var_usage_visitor(node, "u0")); + REQUIRE(run_var_usage_visitor(node, "tsyn")); + } + } + } +} From 51738e4674dbd12fa4677dc280646ab068cc4cf6 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 6 Sep 2019 14:44:24 +0200 Subject: [PATCH 233/871] Support for NMODL ontology information in the AST (BlueBrain/nmodl#241) * Support for NMODL ontology information in the AST - Support in lexer, parser and AST - Added tests - Update documentation and test in Jupyter Python tutorial Todo: shift-reduce conflicts (as rules are copied from neuron, will be taken care by BlueBrain/nmodl#242) Fixes BlueBrain/nmodl#158 NMODL Repo SHA: BlueBrain/nmodl@ae69b6286a2793638f171b500f2f4a53bef47d6d --- README.md | 3 +- .../notebooks/nmodl-kinetic-schemes.ipynb | 2 +- .../notebooks/nmodl-linear-solver.ipynb | 2 +- .../notebooks/nmodl-nonlinear-solver.ipynb | 2 +- .../notebooks/nmodl-odes-overview.ipynb | 2 +- .../notebooks/nmodl-python-tutorial.ipynb | 139 ++++++++++++------ .../notebooks/nmodl-sympy-conductance.ipynb | 2 +- .../notebooks/nmodl-sympy-solver-cnexp.ipynb | 2 +- .../nmodl-sympy-solver-derivimplicit.ipynb | 2 +- .../notebooks/nmodl-sympy-solver-sparse.ipynb | 2 +- src/nmodl/ast/ast_common.hpp | 8 + src/nmodl/language/nmodl.yaml | 13 ++ src/nmodl/lexer/nmodl.ll | 30 +++- src/nmodl/lexer/nmodl_utils.cpp | 3 +- src/nmodl/parser/nmodl.yy | 65 +++++--- test/nmodl/transpiler/lexer/tokens.cpp | 1 + test/nmodl/transpiler/parser/parser.cpp | 17 +++ .../transpiler/utils/nmodl_constructs.cpp | 17 +++ 18 files changed, 235 insertions(+), 77 deletions(-) diff --git a/README.md b/README.md index c4edfdce98..7cefd643a1 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,8 @@ $ python3 Python 3.6.8 (default, Apr 8 2019, 18:17:52) >>> from nmodl import dsl >>> import os ->>> expsyn = os.path.join(dsl.example_dir(), "expsyn.mod") +>>> examples = dsl.list_examples() +>>> nmodl_string = dsl.load_example(examples[-1]) ... ``` diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb index 5f67bb7b4f..e5e27d51c9 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -450,7 +450,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb index a2cc24b0b2..85550df2c5 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb @@ -39,7 +39,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb index ccaf052521..aad7225f2e 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb @@ -63,7 +63,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb index 3885bdefee..a76fc3f861 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb @@ -96,7 +96,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index ef88e88315..a1d3bd2347 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -53,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -254,7 +254,7 @@ "" ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -265,7 +265,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -308,7 +308,7 @@ "" ] }, - "execution_count": 5, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -373,7 +373,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -389,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -448,7 +448,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -472,7 +472,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -493,7 +493,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -507,7 +507,7 @@ "" ] }, - "execution_count": 10, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -541,7 +541,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -559,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -589,7 +589,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -650,7 +650,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -694,7 +694,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -712,7 +712,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -770,7 +770,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -801,7 +801,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -830,7 +830,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "metadata": {}, "outputs": [ { @@ -861,7 +861,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -901,7 +901,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -958,7 +958,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ @@ -979,20 +979,20 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 22, "metadata": {}, "outputs": [], "source": [ - "from nmodl.dsl import ast\n", - "from nmodl.dsl import visitor\n", + "from nmodl.dsl import ast, visitor\n", + "\n", "\n", "class PyGenerator(visitor.AstVisitor):\n", " def __init__(self):\n", " visitor.AstVisitor.__init__(self)\n", - " self.pycode = ''\n", + " self.pycode = \"\"\n", " self.indent = 0\n", " self.func_name = \"\"\n", - " \n", + "\n", " def visit_function_block(self, node):\n", " params = []\n", " self.func_name = node.get_node_name()\n", @@ -1001,14 +1001,14 @@ " params_str = \", \".join(params)\n", " self.pycode += f\"def {node.get_node_name()}({params_str}):\\n\"\n", " node.visit_children(self)\n", - " \n", + "\n", " def visit_statement_block(self, node):\n", " self.indent += 1\n", " node.visit_children(self)\n", " self.indent -= 1\n", - " \n", + "\n", " def visit_expression_statement(self, node):\n", - " self.pycode += \" \"*4*self.indent\n", + " self.pycode += \" \" * 4 * self.indent\n", " expr = node.expression\n", " if type(expr) is ast.BinaryExpression and expr.op.eval() == \"=\":\n", " rhs = expr.rhs\n", @@ -1022,9 +1022,8 @@ " node.visit_children(self)\n", " self.pycode += \"\\n\"\n", "\n", - " \n", " def visit_if_statement(self, node):\n", - " self.pycode += \" \"*4*self.indent + \"if \"\n", + " self.pycode += \" \" * 4 * self.indent + \"if \"\n", " node.condition.accept(self)\n", " self.pycode += \":\\n\"\n", " node.get_statement_block().accept(self)\n", @@ -1034,10 +1033,9 @@ " node.elses.accept(self)\n", "\n", " def visit_else_statement(self, node):\n", - " self.pycode += \" \"*4*self.indent + \"else:\\n\"\n", + " self.pycode += \" \" * 4 * self.indent + \"else:\\n\"\n", " node.get_statement_block().accept(self)\n", - " \n", - " \n", + "\n", " def visit_binary_expression(self, node):\n", " lhs = node.lhs\n", " rhs = node.rhs\n", @@ -1052,20 +1050,20 @@ " lhs.accept(self)\n", " self.pycode += f\" {op} \"\n", " rhs.accept(self)\n", - " \n", + "\n", " def visit_var_name(self, node):\n", " self.pycode += node.name.get_node_name()\n", - " \n", + "\n", " def visit_integer(self, node):\n", " self.pycode += nmod.to_nmodl(node)\n", - " \n", + "\n", " def visit_double(self, node):\n", " self.pycode += nmodl.to_nmodl(node)" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 23, "metadata": {}, "outputs": [ { @@ -1087,6 +1085,63 @@ "print(pygen.pycode)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example of CURIE information parsing\n", + "\n", + "In this example we show how ontology information from NEURON block can be parsed." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " REPRESENTS NCIT:C17145 (NCIT:C17145)\n", + " REPRESENTS NCIT:C17008 (NCIT:C17008)\n", + " USEION na READ ena WRITE ina REPRESENTS CHEBI:29101 (CHEBI:29101)\n", + " USEION ca READ eca WRITE ina ()\n" + ] + } + ], + "source": [ + "import nmodl.dsl as nmodl\n", + "from nmodl.dsl import ast, visitor\n", + "\n", + "driver = nmodl.NmodlDriver()\n", + "mod_string = \"\"\"\n", + "NEURON {\n", + " SUFFIX hx\n", + " REPRESENTS NCIT:C17145 : sodium channel\n", + " REPRESENTS NCIT:C17008 : potassium channel\n", + " USEION na READ ena WRITE ina REPRESENTS CHEBI:29101\n", + " USEION ca READ eca WRITE ina\n", + " RANGE gnabar, gkbar, gl, el, gna, gk\n", + "}\n", + "\"\"\"\n", + "modast = driver.parse_string(mod_string)\n", + "\n", + "lookup_visitor = visitor.AstLookupVisitor()\n", + "\n", + "ont_statements = lookup_visitor.lookup(modast, ast.AstNodeType.ONTOLOGY_STATEMENT)\n", + "ions = lookup_visitor.lookup(modast, ast.AstNodeType.USEION)\n", + "\n", + "for statement in ont_statements:\n", + " print(\n", + " \" %s (%s)\" % (nmodl.to_nmodl(statement), nmodl.to_nmodl(statement.ontology_id))\n", + " )\n", + "\n", + "for ion in ions:\n", + " o_id = nmodl.to_nmodl(ion.ontology_id) if ion.ontology_id else \"\"\n", + " print(\" %s (%s)\" % (nmodl.to_nmodl(ion), o_id))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1111,7 +1166,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.7" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb index 367e043b19..6c3971c36f 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb @@ -456,7 +456,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb index 142e286f58..50e03d8eab 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb @@ -289,7 +289,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb index 1cacc49131..da1aa97846 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb @@ -235,7 +235,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb index 5c37d2242f..45f09003a1 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb @@ -235,7 +235,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.2" + "version": "3.7.4" } }, "nbformat": 4, diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 0f06251556..5d383b6bab 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -1488,6 +1488,14 @@ struct Ast: public std::enable_shared_from_this { virtual bool is_solution_expression() { return false; } + + /** + *\brief Check if the ast node is an instance of ast::OntologyStatement + * @return true if object of type ast::OntologyStatement + */ + virtual bool is_ontology_statement() { + return false; + } }; /** @} */ // end of ast_class diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index deebbcc0a7..cc17324a40 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1976,6 +1976,11 @@ brief: "(TODO)" type: Valence optional: true + - ontology_id: + brief: "Ontology to indicate the chemical ion" + type: String + optional: true + prefix: {value: " REPRESENTS "} brief: "Represents USEION statement in NMODL" - Nonspecific: @@ -2104,6 +2109,14 @@ type: String suffix: {value: "ENDCOMMENT"} + - OntologyStatement: + nmodl: "REPRESENTS " + members: + - ontology_id: + brief: "Ontology name" + type: String + brief: "Represents CURIE information in NMODL" + - Program: brief: "Represents top level AST node for whole NMODL input" members: diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 0f2530c311..82dd11c05e 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -41,6 +41,9 @@ D [0-9] E [Ee][-+]?{D}+ +/** regex for ontology id */ +O_ID [a-zA-Z][a-zA-Z0-9_]*:[a-zA-Z0-9_]* + /** we do use yymore feature in copy modes */ %option yymore @@ -96,6 +99,9 @@ E [Ee][-+]?{D}+ /* mode for TITLE and single line comment */ %x LINE_MODE +/* mode for ontology information */ +%x ONTOLOGY_MODE + /* enable use of start condition stacks */ %option stack @@ -131,6 +137,12 @@ ELSE { return token_symbol(yytext, loc); } +"REPRESENTS" { + /** start of ontology information */ + BEGIN(ONTOLOGY_MODE); + return token_symbol(yytext, loc, Token::REPRESENTS); + } + "VERBATIM" { /** start of verbatim block */ BEGIN(COPY_MODE); @@ -153,6 +165,12 @@ ELSE { return token_symbol(yytext, loc); } +\[{O_ID}\]|{O_ID} { + /** name of ontology */ + BEGIN(INITIAL); + return NmodlParser::make_ONTOLOGY_ID(yytext, loc); + } + [a-zA-Z][a-zA-Z0-9_]* { /** macro name (typically string) */ BEGIN(MACRO_VALUE_MODE); @@ -359,6 +377,7 @@ ELSE { return token_symbol(yytext, loc, Token::COMMA); } +[ \t] | [ \t] | [ \t] | [ \t] { @@ -476,6 +495,13 @@ ELSE { return NmodlParser::make_INVALID_TOKEN(loc); } +. | +\n { + /** if ontology id is not matched before end of line */ + auto msg = "Lexer Error : while parsing ontology information with REPRESENTS"; + throw std::runtime_error(msg); + } + \"[^\"\n]*$ { std::cout << "\n ERROR: Unterminated string (e.g. for printf) \n"; } @@ -515,7 +541,7 @@ void nmodl::parser::NmodlLexer::scan_unit() { loc.step(); std::string str; - /** Unit is a string until close parenthis */ + /** Unit is a string until close parenthesis */ while (1) { auto lastch = yyinput(); if(lastch == ')') { @@ -523,7 +549,7 @@ void nmodl::parser::NmodlLexer::scan_unit() { break; } else if ( lastch == '\n' || lastch == 0) { - std::cout << "ERROR: While parsing unit, closing parenthis not found"; + std::cout << "ERROR: While parsing unit, closing parenthesis not found"; break; } str += lastch; diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 643ce9a1d0..46535e2ccb 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -366,7 +366,8 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_COMMA(token, pos); case Token::PERIOD: return Parser::make_PERIOD(token, pos); - + case Token::REPRESENTS: + return Parser::make_REPRESENTS(token, pos); /** * we hit default case for missing token type. This is more likely * a bug in the implementation where we forgot to handle token type. diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index cd57a6877b..27eeb2f005 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -216,6 +216,9 @@ %token TILDE "~" %token PERIOD "." +%token REPRESENTS +%token ONTOLOGY_ID + %token END 0 "End of file" %token UNKNOWN %token INVALID_TOKEN @@ -315,6 +318,7 @@ %type neuron_statement %type read_ion_list %type write_ion_list +%type ontology %type nonspecific_var_list %type electrode_current_var_list %type section_var_list @@ -2315,20 +2319,25 @@ neuron_statement : $1.emplace_back(new ast::ThreadSafe($3)); $$ = $1; } + | neuron_statement REPRESENTS ONTOLOGY_ID + { + $1.emplace_back(new ast::OntologyStatement(new ast::String($3))); + $$ = $1; + } ; -use_ion_statement : USEION NAME_PTR READ read_ion_list valence +use_ion_statement : USEION NAME_PTR READ read_ion_list valence ontology { - $$ = new ast::Useion($2, $4, ast::WriteIonVarVector(), $5); + $$ = new ast::Useion($2, $4, ast::WriteIonVarVector(), $5, $6); } - | USEION NAME_PTR WRITE write_ion_list valence + | USEION NAME_PTR WRITE write_ion_list valence ontology { - $$ = new ast::Useion($2, ast::ReadIonVarVector(), $4, $5); + $$ = new ast::Useion($2, ast::ReadIonVarVector(), $4, $5, $6); } - | USEION NAME_PTR READ read_ion_list WRITE write_ion_list valence + | USEION NAME_PTR READ read_ion_list WRITE write_ion_list valence ontology { - $$ = new ast::Useion($2, $4, $6, $7); + $$ = new ast::Useion($2, $4, $6, $7, $8); } | USEION error { @@ -2371,6 +2380,33 @@ write_ion_list : NAME_PTR ; +valence : + { + $$ = nullptr; + } + | VALENCE double + { + $$ = new ast::Valence($1.clone(), $2); + } + | VALENCE "-" double + { + $3->negate(); + $$ = new ast::Valence($1.clone(), $3); + } + ; + + +ontology : + { + $$ = nullptr; + } + | REPRESENTS ONTOLOGY_ID + { + $$ = new ast::String($2); + } + ; + + nonspecific_var_list : NAME_PTR { $$ = ast::NonspecificCurVarVector(); @@ -2538,23 +2574,6 @@ threadsafe_var_list : NAME_PTR } ; - -valence : - { - $$ = nullptr; - } - | VALENCE double - { - $$ = new ast::Valence($1.clone(), $2); - } - | VALENCE "-" double - { - $3->negate(); - $$ = new ast::Valence($1.clone(), $3); - } - ; - - INTEGER_PTR : INTEGER { $$ = $1.clone(); diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index c1e6c6c854..f6e7709bf8 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -96,6 +96,7 @@ TEST_CASE("NMODL Lexer returning valid token types", "[Lexer]") { REQUIRE(token_type("WHILE") == Token::WHILE); REQUIRE(token_type("IF") == Token::IF); REQUIRE(token_type("ELSE") == Token::ELSE); + REQUIRE(token_type("REPRESENTS NCIT:C17145") == Token::REPRESENTS); } SECTION("Different number types") { diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 9015b6145c..0080178300 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -139,6 +139,23 @@ SCENARIO("NMODL parser running number of invalid NMODL constructs") { } } +SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { + GIVEN("A valid CURIE information statement") { + THEN("parser accepts without an error") { + REQUIRE(is_valid_construct("NEURON { REPRESENTS NCIT:C17008 }")); + REQUIRE(is_valid_construct("NEURON { REPRESENTS [NCIT:C17008] }")); + } + } + + GIVEN("Incomplete CURIE information statement") { + THEN("parser throws an error") { + REQUIRE_THROWS_WITH(is_valid_construct("NEURON { REPRESENTS }"), + Catch::Contains("Lexer Error")); + REQUIRE_THROWS_WITH(is_valid_construct("NEURON { REPRESENTS NCIT}"), + Catch::Contains("Lexer Error")); + } + } +} //============================================================================= // Differential Equation Parser tests diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 66ccd03535..9e95a139f6 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -168,8 +168,21 @@ std::map nmdol_invalid_constructs{ } )" } + }, + + { + "neuron_block_1", + { + "Invalid CURIE statement", + R"( + NEURON { + REPRESENTS xx + } + )" + } } + // clang-format on }; @@ -477,10 +490,14 @@ std::map nmodl_valid_constructs{ R"( NEURON { SUFFIX ProbAMPANMDA + REPRESENTS NCIT:C17145 + REPRESENTS [NCIT:C17145] USEION na READ ena USEION na READ ena, kna USEION na WRITE ena, kna VALENCE 2.1 USEION na READ ena WRITE ina + USEION k READ ek WRITE ik REPRESENTS CHEBI:29103 + USEION na READ ena WRITE ina VALENCE 3.3 REPRESENTS NCIT:C17 NONSPECIFIC_CURRENT i NONSPECIFIC_CURRENT i, j ELECTRODE_CURRENT i From 9f1065df57dcacb0af955b652bdfb10a1c836370 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 25 Sep 2019 09:59:48 +0200 Subject: [PATCH 234/871] Replace raw pointers by references (BlueBrain/nmodl#243) The aim is to remove raw pointers whenever they are not needed throughout the code. - updated signatures in AST for accept and visit_children to take references rather than raw pointers. - fixed all calls accordingly - replace shared_ptr constructor by make_shared NMODL Repo SHA: BlueBrain/nmodl@9202b1ef4edabd420c51210594dc68b71f525abe --- src/nmodl/ast/ast_common.hpp | 10 +-- src/nmodl/codegen/codegen_c_visitor.cpp | 82 +++++++++---------- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 26 +++--- src/nmodl/codegen/codegen_ispc_visitor.cpp | 4 +- src/nmodl/language/templates/ast/ast.cpp | 2 +- src/nmodl/language/templates/ast/ast.hpp | 8 +- src/nmodl/language/templates/pybind/pyast.cpp | 2 +- src/nmodl/language/templates/pybind/pyast.hpp | 4 +- .../templates/visitors/ast_visitor.cpp | 2 +- .../templates/visitors/json_visitor.cpp | 2 +- .../templates/visitors/lookup_visitor.cpp | 8 +- .../templates/visitors/nmodl_visitor.cpp | 2 +- .../visitors/constant_folder_visitor.cpp | 4 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 18 ++-- src/nmodl/visitors/inline_visitor.cpp | 14 ++-- src/nmodl/visitors/kinetic_block_visitor.cpp | 12 +-- .../visitors/local_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 6 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 6 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 24 +++--- src/nmodl/visitors/rename_visitor.cpp | 2 +- src/nmodl/visitors/solve_block_visitor.cpp | 6 +- .../visitors/sympy_conductance_visitor.cpp | 6 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 16 ++-- src/nmodl/visitors/symtab_visitor_helper.hpp | 4 +- src/nmodl/visitors/units_visitor.cpp | 2 +- src/nmodl/visitors/var_usage_visitor.cpp | 2 +- .../visitors/verbatim_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 4 +- test/nmodl/transpiler/visitor/lookup.cpp | 2 +- 32 files changed, 144 insertions(+), 144 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 5d383b6bab..65db7eeed5 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -208,13 +208,13 @@ struct Ast: public std::enable_shared_from_this { * to visit the node itself in the visitor. * * \code{.cpp} - * void IndexedName::accept(visitor::Visitor* v) override { - * v->visit_indexed_name(this); + * void IndexedName::accept(visitor::Visitor& v) override { + * v.visit_indexed_name(this); * } * \endcode * */ - virtual void accept(visitor::Visitor* v) = 0; + virtual void accept(visitor::Visitor& v) = 0; /** * \brief Visit children i.e. member of AST node using provided visitor @@ -228,13 +228,13 @@ struct Ast: public std::enable_shared_from_this { * ast::IndexedName node children are visited instead of node itself. * * \code{.cpp} - * void IndexedName::visit_children(visitor::Visitor* v) { + * void IndexedName::visit_children(visitor::Visitor& v) { * name->accept(v); * length->accept(v); * } * \endcode */ - virtual void visit_children(visitor::Visitor* v) = 0; + virtual void visit_children(visitor::Visitor& v) = 0; /** * \brief Create a copy of the current node diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 9fbf648f53..d9b4586c40 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -59,7 +59,7 @@ void CodegenCVisitor::visit_integer(Integer* node) { auto macro = node->get_macro(); auto value = node->get_value(); if (macro) { - macro->accept(this); + macro->accept(*this); } else { printer->add_text(std::to_string(value)); } @@ -96,7 +96,7 @@ void CodegenCVisitor::visit_name(Name* node) { if (!codegen) { return; } - node->visit_children(this); + node->visit_children(*this); } @@ -120,14 +120,14 @@ void CodegenCVisitor::visit_var_name(VarName* node) { auto name = node->get_name(); auto at_index = node->get_at(); auto index = node->get_index(); - name->accept(this); + name->accept(*this); if (at_index) { printer->add_text("@"); - at_index->accept(this); + at_index->accept(*this); } if (index) { printer->add_text("["); - index->accept(this); + index->accept(*this); printer->add_text("]"); } } @@ -137,9 +137,9 @@ void CodegenCVisitor::visit_indexed_name(IndexedName* node) { if (!codegen) { return; } - node->get_name()->accept(this); + node->get_name()->accept(*this); printer->add_text("["); - node->get_length()->accept(this); + node->get_length()->accept(*this); printer->add_text("]"); } @@ -159,13 +159,13 @@ void CodegenCVisitor::visit_if_statement(IfStatement* node) { return; } printer->add_text("if ("); - node->get_condition()->accept(this); + node->get_condition()->accept(*this); printer->add_text(") "); - node->get_statement_block()->accept(this); + node->get_statement_block()->accept(*this); print_vector_elements(node->get_elseifs(), ""); auto elses = node->get_elses(); if (elses) { - elses->accept(this); + elses->accept(*this); } } @@ -175,9 +175,9 @@ void CodegenCVisitor::visit_else_if_statement(ElseIfStatement* node) { return; } printer->add_text(" else if ("); - node->get_condition()->accept(this); + node->get_condition()->accept(*this); printer->add_text(") "); - node->get_statement_block()->accept(this); + node->get_statement_block()->accept(*this); } @@ -186,15 +186,15 @@ void CodegenCVisitor::visit_else_statement(ElseStatement* node) { return; } printer->add_text(" else "); - node->visit_children(this); + node->visit_children(*this); } void CodegenCVisitor::visit_while_statement(WhileStatement* node) { printer->add_text("while ("); - node->get_condition()->accept(this); + node->get_condition()->accept(*this); printer->add_text(") "); - node->get_statement_block()->accept(this); + node->get_statement_block()->accept(*this); } @@ -208,17 +208,17 @@ void CodegenCVisitor::visit_from_statement(ast::FromStatement* node) { auto inc = node->get_increment(); auto block = node->get_statement_block(); printer->add_text("for(int {}="_format(name)); - from->accept(this); + from->accept(*this); printer->add_text("; {}<="_format(name)); - to->accept(this); + to->accept(*this); if (inc) { printer->add_text("; {}+="_format(name)); - inc->accept(this); + inc->accept(*this); } else { printer->add_text("; {}++"_format(name)); } printer->add_text(")"); - block->accept(this); + block->accept(*this); } @@ -227,7 +227,7 @@ void CodegenCVisitor::visit_paren_expression(ParenExpression* node) { return; } printer->add_text("("); - node->get_expression()->accept(this); + node->get_expression()->accept(*this); printer->add_text(")"); } @@ -241,14 +241,14 @@ void CodegenCVisitor::visit_binary_expression(BinaryExpression* node) { auto rhs = node->get_rhs(); if (op == "^") { printer->add_text("pow("); - lhs->accept(this); + lhs->accept(*this); printer->add_text(", "); - rhs->accept(this); + rhs->accept(*this); printer->add_text(")"); } else { - lhs->accept(this); + lhs->accept(*this); printer->add_text(" " + op + " "); - rhs->accept(this); + rhs->accept(*this); } } @@ -276,7 +276,7 @@ void CodegenCVisitor::visit_unary_operator(UnaryOperator* node) { */ void CodegenCVisitor::visit_statement_block(StatementBlock* node) { if (!codegen) { - node->visit_children(this); + node->visit_children(*this); return; } print_statement_block(node); @@ -1263,7 +1263,7 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, if (!statement->is_verbatim()) { printer->add_indent(); } - statement->accept(this); + statement->accept(*this); if (need_semicolon(statement.get())) { printer->add_text(";"); } @@ -1327,7 +1327,7 @@ void CodegenCVisitor::print_top_verbatim_blocks() { for (const auto& block: info.top_blocks) { if (block->is_verbatim()) { printer->add_newline(2); - block->accept(this); + block->accept(*this); } } @@ -1351,12 +1351,12 @@ void CodegenCVisitor::rename_function_arguments() { RenameVisitor v(arg, "arg_" + arg); for (const auto& function: info.functions) { if (has_parameter_of_name(function, arg)) { - function->accept(&v); + function->accept(v); } } for (const auto& function: info.procedures) { if (has_parameter_of_name(function, arg)) { - function->accept(&v); + function->accept(v); } } } @@ -1387,7 +1387,7 @@ static TableStatement* get_table_statement(ast::Block* node) { // TableStatementVisitor v; AstLookupVisitor v(AstNodeType::TABLE_STATEMENT); - node->accept(&v); + node->accept(v); auto table_statements = v.get_nodes(); @@ -1443,13 +1443,13 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { printer->add_indent(); printer->add_text("{} = "_format(tmin_name)); - from->accept(this); + from->accept(*this); printer->add_text(";"); printer->add_newline(); printer->add_indent(); printer->add_text("double tmax = "); - to->accept(this); + to->accept(*this); printer->add_text(";"); printer->add_newline(); @@ -1615,7 +1615,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { // first rename return variable name auto block = node->get_statement_block().get(); RenameVisitor v(name, return_var); - block->accept(&v); + block->accept(v); print_function_or_procedure(node, name); codegen = false; @@ -3286,7 +3286,7 @@ void CodegenCVisitor::print_watch_activate() { printer->add_indent(); printer->add_text("{} = 2 + "_format(varname)); auto watch = statement->get_statements().front(); - watch->get_expression()->visit_children(this); + watch->get_expression()->visit_children(*this); printer->add_text(";"); printer->add_newline(); @@ -3324,7 +3324,7 @@ void CodegenCVisitor::print_watch_check() { // start block 2 printer->add_indent(); printer->add_text("if ("); - watch->get_expression()->accept(this); + watch->get_expression()->accept(*this); printer->add_text(") {"); printer->add_newline(); printer->increase_indent(); @@ -3339,7 +3339,7 @@ void CodegenCVisitor::print_watch_check() { auto t = get_variable_name("t"); printer->add_text( "ml->_net_send_buffer, 0, {}, 0, {}, {}+0.0, "_format(tqitem, point_process, t)); - watch->get_value()->accept(this); + watch->get_value()->accept(*this); printer->add_text(");"); printer->add_newline(); @@ -3398,7 +3398,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_ auto statement = "double* {} = weights + weight_index + {};"_format(name, i); printer->add_line(statement); RenameVisitor vr(name, "*" + name); - node->visit_children(&vr); + node->visit_children(vr); } i++; } @@ -3512,7 +3512,7 @@ static void rename_net_receive_arguments(ast::NetReceiveBlock* net_receive_node, auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); - node->get_statement_block()->visit_children(&vr); + node->get_statement_block()->visit_children(vr); } } } @@ -3705,7 +3705,7 @@ void CodegenCVisitor::print_net_receive_kernel() { } printer->add_line("{} = t;"_format(get_variable_name("tsave"))); printer->add_indent(); - node->get_statement_block()->accept(this); + node->get_statement_block()->accept(*this); printer->add_newline(); printer->end_block(); printer->add_newline(); @@ -3845,7 +3845,7 @@ void CodegenCVisitor::visit_solution_expression(SolutionExpression* node) { auto statement_block = dynamic_cast(block); print_statement_block(statement_block, false, false); } else { - block->accept(this); + block->accept(*this); } } @@ -3885,7 +3885,7 @@ void CodegenCVisitor::print_nrn_state() { } if (info.nrn_state_block) { - info.nrn_state_block->visit_children(this); + info.nrn_state_block->visit_children(*this); } if (info.currents.empty() && info.breakpoint_node != nullptr) { diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index e09d8071de..3a86b8d443 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1822,7 +1822,7 @@ void CodegenCVisitor::print_vector_elements(const std::vector& elements, const std::string& prefix) { for (auto iter = elements.begin(); iter != elements.end(); iter++) { printer->add_text(prefix); - (*iter)->accept(this); + (*iter)->accept(*this); if (!separator.empty() && !utils::is_last(iter, elements)) { printer->add_text(separator); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 83f0db3c9f..8893a05610 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -440,7 +440,7 @@ void CodegenHelperVisitor::visit_initial_block(InitialBlock* node) { } else { info.initial_node = node; } - node->visit_children(this); + node->visit_children(*this); } @@ -448,14 +448,14 @@ void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock* node) { under_net_receive_block = true; info.net_receive_node = node; info.num_net_receive_parameters = node->get_parameters().size(); - node->visit_children(this); + node->visit_children(*this); under_net_receive_block = false; } void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock* node) { under_derivative_block = true; - node->visit_children(this); + node->visit_children(*this); under_derivative_block = false; } @@ -468,20 +468,20 @@ void CodegenHelperVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallba void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { under_breakpoint_block = true; info.breakpoint_node = node; - node->visit_children(this); + node->visit_children(*this); under_breakpoint_block = false; } void CodegenHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock* node) { info.nrn_state_block = node; - node->visit_children(this); + node->visit_children(*this); } void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { info.procedures.push_back(node); - node->visit_children(this); + node->visit_children(*this); if (table_statement_used) { table_statement_used = false; info.functions_with_table.push_back(node); @@ -491,7 +491,7 @@ void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { info.functions.push_back(node); - node->visit_children(this); + node->visit_children(*this); if (table_statement_used) { table_statement_used = false; info.functions_with_table.push_back(node); @@ -547,7 +547,7 @@ void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { auto statements = node->get_statements(); for (auto& statement: statements) { - statement->accept(this); + statement->accept(*this); if (under_derivative_block && assign_lhs && (assign_lhs->is_name() || assign_lhs->is_var_name())) { auto name = assign_lhs->get_node_name(); @@ -577,8 +577,8 @@ void CodegenHelperVisitor::visit_binary_expression(BinaryExpression* node) { if (node->get_op().eval() == "=") { assign_lhs = node->get_lhs(); } - node->get_lhs()->accept(this); - node->get_rhs()->accept(this); + node->get_lhs()->accept(*this); + node->get_rhs()->accept(*this); } @@ -594,7 +594,7 @@ void CodegenHelperVisitor::visit_watch(ast::Watch* node) { void CodegenHelperVisitor::visit_watch_statement(ast::WatchStatement* node) { info.watch_statements.push_back(node); - node->visit_children(this); + node->visit_children(*this); } @@ -618,7 +618,7 @@ void CodegenHelperVisitor::visit_program(ast::Program* node) { info.top_verbatim_blocks.push_back(block.get()); } } - node->visit_children(this); + node->visit_children(*this); find_range_variables(); find_non_range_variables(); find_ion_variables(); @@ -627,7 +627,7 @@ void CodegenHelperVisitor::visit_program(ast::Program* node) { codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program* node) { - node->accept(this); + node->accept(*this); return info; } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 1564837f64..dc2217065f 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -57,9 +57,9 @@ void CodegenIspcVisitor::visit_var_name(ast::VarName* node) { return; } auto celsius_rename = RenameVisitor("celsius", "ispc_celsius"); - node->accept(&celsius_rename); + node->accept(celsius_rename); auto pi_rename = RenameVisitor("PI", "ISPC_PI"); - node->accept(&pi_rename); + node->accept(pi_rename); CodegenCVisitor::visit_var_name(node); } diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 10337ac505..7b4fc0759d 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -22,7 +22,7 @@ namespace ast { /// {{node.class_name}} member functions definition /// - void {{ node.class_name }}::visit_children(visitor::Visitor* v) { + void {{ node.class_name }}::visit_children(visitor::Visitor& v) { {% for child in node.non_base_members %} {% if child.is_vector %} /// visit each element of vector diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index d58a9c2f51..5c1e121ca4 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -267,7 +267,7 @@ namespace ast { /** * \brief Set token for the current ast node */ - void set_token(ModToken& tok) { token = std::shared_ptr(new ModToken(tok)); } + void set_token(ModToken& tok) { token = std::make_shared(tok); } {% endif %} {% if node.is_symtab_needed %} @@ -317,7 +317,7 @@ namespace ast { * * \sa Ast::visit_children for example. */ - {{ virtual(node) }} void visit_children (visitor::Visitor* v) override; + {{ virtual(node) }} void visit_children (visitor::Visitor& v) override; /** * \brief accept (or visit) the current AST node using provided visitor @@ -330,8 +330,8 @@ namespace ast { * * \sa Ast::accept for example. */ - {{ virtual(node) }} void accept(visitor::Visitor* v) override { - v->visit_{{ node.class_name | snake_case }}(this); + {{ virtual(node) }} void accept(visitor::Visitor& v) override { + v.visit_{{ node.class_name | snake_case }}(this); } /// \} diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 84cbf8767c..4384557d40 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -234,7 +234,7 @@ void init_ast_module(py::module& m) { std::stringstream ss; JSONVisitor v(ss); v.compact_json(true); - n.accept(&v); + n.accept(v); return ss.str(); }); diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 81342f05ae..e0fc91dc9c 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -46,7 +46,7 @@ using namespace ast; */ struct PyAst: public Ast { - void visit_children(visitor::Visitor* v) override { + void visit_children(visitor::Visitor& v) override { PYBIND11_OVERLOAD_PURE(void, /// Return type Ast, /// Parent class visit_children, /// Name of function in C++ (must match Python name) @@ -54,7 +54,7 @@ struct PyAst: public Ast { ); } - void accept(visitor::Visitor* v) override { + void accept(visitor::Visitor& v) override { PYBIND11_OVERLOAD_PURE(void, Ast, accept, v); } diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index 494e11d24b..85075a874c 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -15,7 +15,7 @@ using namespace ast; {% for node in nodes %} void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { - node->visit_children(this); + node->visit_children(*this); } {% endfor %} diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 722f96ce0b..aaa5445e6a 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -20,7 +20,7 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* if (embed_nmodl) { printer->add_block_property("nmodl", to_nmodl(node)); } - node->visit_children(this); + node->visit_children(*this); {% if node.is_data_type_node %} {% if node.is_integer_node %} if(!node->get_macro()) { diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index a101a2fe21..f99ce90119 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -20,7 +20,7 @@ void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name if(std::find(types.begin(), types.end(), type) != types.end()) { nodes.push_back(node->get_shared_ptr()); } - node->visit_children(this); + node->visit_children(*this); } {% endfor %} @@ -29,7 +29,7 @@ void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name std::vector> AstLookupVisitor::lookup(Ast* node, std::vector& _types) { nodes.clear(); types = _types; - node->accept(this); + node->accept(*this); return nodes; } @@ -37,13 +37,13 @@ std::vector> AstLookupVisitor::lookup(Ast* node, AstNo nodes.clear(); types.clear(); types.push_back(type); - node->accept(this); + node->accept(*this); return nodes; } std::vector> AstLookupVisitor::lookup(Ast* node) { nodes.clear(); - node->accept(this); + node->accept(*this); return nodes; } diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index e51e7a72df..14ea2a3e28 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -69,7 +69,7 @@ using namespace ast; printer->add_element(op); {% else %} {% call guard(child.prefix, child.suffix) %} - node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node else "." }}accept(this); + node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node else "." }}accept(*this); {% endcall %} {%- endif %} {%- endmacro -%} diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 6d2b96eed4..4afb925f21 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -76,7 +76,7 @@ static double compute(double lhs, ast::BinaryOp op, double rhs) { * parenthesis_exp => binary_expression => ... */ void ConstantFolderVisitor::visit_paren_expression(ast::ParenExpression* node) { - node->visit_children(this); + node->visit_children(*this); auto expr = node->get_expression(); if (expr->is_wrapped_expression()) { auto e = std::dynamic_pointer_cast(expr); @@ -105,7 +105,7 @@ void ConstantFolderVisitor::visit_paren_expression(ast::ParenExpression* node) { * } */ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* node) { - node->visit_children(this); + node->visit_children(*this); /// first expression which is wrapped auto expr = node->get_expression(); diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index f9d783d951..b5cbb98adf 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -183,7 +183,7 @@ DUState DUChain::eval() { void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node* node) { unsupported_node = true; - node->visit_children(this); + node->visit_children(*this); unsupported_node = false; } @@ -196,7 +196,7 @@ void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall* node) { std::string function_name = node->get_node_name(); auto symbol = global_symtab->lookup_in_scope(function_name); if (symbol == nullptr || symbol->is_external_variable()) { - node->visit_children(this); + node->visit_children(*this); } else { visit_unsupported_node(node); } @@ -209,7 +209,7 @@ void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock* node) { } symtab_stack.push(current_symtab); - node->visit_children(this); + node->visit_children(*this); symtab_stack.pop(); current_symtab = symtab_stack.top(); } @@ -218,11 +218,11 @@ void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock* node) { * and hence not necessary to keep track of assignment operator using stack. */ void DefUseAnalyzeVisitor::visit_binary_expression(ast::BinaryExpression* node) { - node->get_rhs()->visit_children(this); + node->get_rhs()->visit_children(*this); if (node->get_op().get_value() == ast::BOP_ASSIGN) { visiting_lhs = true; } - node->get_lhs()->visit_children(this); + node->get_lhs()->visit_children(*this); visiting_lhs = false; } @@ -237,10 +237,10 @@ void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement* node) { /// visiting if sub-block auto last_chain = current_chain; start_new_chain(DUState::IF); - node->get_condition()->accept(this); + node->get_condition()->accept(*this); auto block = node->get_statement_block(); if (block) { - block->accept(this); + block->accept(*this); } current_chain = last_chain; @@ -322,7 +322,7 @@ void DefUseAnalyzeVisitor::process_variable(const std::string& name, int index) void DefUseAnalyzeVisitor::visit_with_new_chain(ast::Node* node, DUState state) { auto last_chain = current_chain; start_new_chain(state); - node->visit_children(this); + node->visit_children(*this); current_chain = last_chain; } @@ -344,7 +344,7 @@ DUChain DefUseAnalyzeVisitor::analyze(ast::Ast* node, const std::string& name) { /// analyze given node symtab_stack.push(current_symtab); - node->visit_children(this); + node->visit_children(*this); symtab_stack.pop(); return usage; diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index be06914245..de438785ad 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -94,7 +94,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, /// variables in cloned block needs to be renamed RenameVisitor visitor(old_name, new_name); - inlined_block->visit_children(&visitor); + inlined_block->visit_children(visitor); auto lhs = new VarName(name->clone(), nullptr, nullptr); auto rhs = caller_expressions.at(counter)->clone(); @@ -159,7 +159,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, /// function definition has function name as return value. we have to rename /// it with new variable name RenameVisitor visitor(function_name, new_varname); - inlined_block->visit_children(&visitor); + inlined_block->visit_children(visitor); /// \todo Have to re-run symtab visitor pass to update symbol table inlined_block->set_symbol_table(nullptr); @@ -189,7 +189,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, void InlineVisitor::visit_function_call(FunctionCall* node) { /// argument can be function call itself - node->visit_children(this); + node->visit_children(*this); std::string function_name = node->get_name()->get_node_name(); auto symbol = program_symtab->lookup_in_scope(function_name); @@ -205,7 +205,7 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { } /// first inline called function - function_definition->visit_children(this); + function_definition->visit_children(*this); bool inlined = false; @@ -244,7 +244,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { for (auto& statement: statements) { caller_statement = statement; statement_stack.push(statement); - caller_statement->visit_children(this); + caller_statement->visit_children(*this); statement_stack.pop(); } @@ -293,7 +293,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { * also replaced with new variable node from the inlining result. */ void InlineVisitor::visit_wrapped_expression(WrappedExpression* node) { - node->visit_children(this); + node->visit_children(*this); auto e = node->get_expression(); if (e->is_function_call()) { auto expression = static_cast(e.get()); @@ -309,7 +309,7 @@ void InlineVisitor::visit_program(Program* node) { if (program_symtab == nullptr) { throw std::runtime_error("Program node doesn't have symbol table"); } - node->visit_children(this); + node->visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 0b08109469..25985b1442 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -115,7 +115,7 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { in_conserve_statement = true; // construct equation to replace ODE in conserve_equation_str - node->visit_children(this); + node->visit_children(*this); in_conserve_statement = false; conserve_equation_str = to_nmodl(node->get_expr().get()) + conserve_equation_str; @@ -273,7 +273,7 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) // add the corresponding integer to the new row in the matrix in_reaction_statement = true; in_reaction_statement_lhs = true; - node->visit_children(this); + node->visit_children(*this); in_reaction_statement = false; // generate fluxes @@ -334,13 +334,13 @@ void KineticBlockVisitor::visit_wrapped_expression(ast::WrappedExpression* node) node->set_expression(std::move(expr)); } } - node->visit_children(this); + node->visit_children(*this); } void KineticBlockVisitor::visit_statement_block(ast::StatementBlock* node) { auto prev_statement_block = current_statement_block; current_statement_block = node; - node->visit_children(this); + node->visit_children(*this); // remove processed statements from current statement block remove_statements_from_block(current_statement_block, statements_to_remove); current_statement_block = prev_statement_block; @@ -363,7 +363,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { i_statement = 0; // construct stoichiometric matrices and fluxes - node->visit_children(this); + node->visit_children(*this); // number of reaction statements int Ni = static_cast(rate_eqs.k_f.size()); @@ -461,7 +461,7 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { } // replace reaction statements within each kinetic block with equivalent ODEs - node->visit_children(this); + node->visit_children(*this); // change KINETIC blocks -> DERIVATIVE blocks auto blocks = node->get_blocks(); diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index dda91981f6..acf956ea38 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -34,7 +34,7 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { // first need to process all children : perform recursively from innermost block for (const auto& item: node->get_statements()) { - item->visit_children(this); + item->visit_children(*this); } /// go back to previous block in hierarchy diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index e61744e6a1..708cfbf2f8 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -55,7 +55,7 @@ class IndexRemover: public AstVisitor { } virtual void visit_binary_expression(ast::BinaryExpression* node) override { - node->visit_children(this); + node->visit_children(*this); if (under_indexed_name) { /// first recursively replaces childrens /// replace lhs & rhs if they have matching index variable @@ -68,7 +68,7 @@ class IndexRemover: public AstVisitor { virtual void visit_indexed_name(ast::IndexedName* node) override { under_indexed_name = true; - node->visit_children(this); + node->visit_children(*this); /// once all children are replaced, do the same for index auto length = replace_for_name(node->get_length()); node->set_length(std::move(length)); @@ -137,7 +137,7 @@ static std::shared_ptr unroll_for_loop( * Parse verbatim blocks and rename variable if it is used. */ void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { - node->visit_children(this); + node->visit_children(*this); for (auto iter = node->statements.begin(); iter != node->statements.end(); iter++) { if ((*iter)->is_from_statement()) { diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index cb85eef9d2..eb98f2aeae 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -30,14 +30,14 @@ void NeuronSolveVisitor::visit_solve_block(ast::SolveBlock* node) { void NeuronSolveVisitor::visit_derivative_block(ast::DerivativeBlock* node) { derivative_block_name = node->get_name()->get_node_name(); derivative_block = true; - node->visit_children(this); + node->visit_children(*this); derivative_block = false; } void NeuronSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { differential_equation = true; - node->visit_children(this); + node->visit_children(*this); differential_equation = false; } @@ -100,7 +100,7 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { void NeuronSolveVisitor::visit_program(ast::Program* node) { program_symtab = node->get_symbol_table(); - node->visit_children(this); + node->visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index 63acd8ebbc..af9aa795dc 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -33,7 +33,7 @@ void NmodlPrintVisitor::visit_element(const std::vector& elements, printer->add_indent(); } - (*iter)->accept(this); + (*iter)->accept(*this); /// print separator (e.g. comma, space) if (!separator.empty() && !utils::is_last(iter, elements)) { diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index e8555fbbdf..3d5be950a5 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -97,12 +97,12 @@ void PerfVisitor::visit_binary_expression(ast::BinaryExpression* node) { visiting_lhs_expression = true; } - node->get_lhs()->accept(this); + node->get_lhs()->accept(*this); /// lhs is done (rhs is read only) visiting_lhs_expression = false; - node->get_rhs()->accept(this); + node->get_rhs()->accept(*this); } /// add performance stats to json printer @@ -124,7 +124,7 @@ void PerfVisitor::add_perf_to_printer(PerfStat& perf) { void PerfVisitor::measure_performance(ast::Ast* node) { start_measurement = true; - node->visit_children(this); + node->visit_children(*this); PerfStat perf; while (!children_blocks_perf.empty()) { @@ -176,7 +176,7 @@ void PerfVisitor::visit_function_call(ast::FunctionCall* node) { } else if (name == "pow") { current_block_perf.n_pow++; } - node->visit_children(this); + node->visit_children(*this); auto symbol = current_symtab->lookup_in_scope(name); auto method_property = NmodlType::procedure_block | NmodlType::function_block; @@ -193,26 +193,26 @@ void PerfVisitor::visit_function_call(ast::FunctionCall* node) { /// every variable used is of type name, update counters void PerfVisitor::visit_name(ast::Name* node) { update_memory_ops(node->get_node_name()); - node->visit_children(this); + node->visit_children(*this); } /// prime name derived from identifier and hence need to be handled here void PerfVisitor::visit_prime_name(ast::PrimeName* node) { update_memory_ops(node->get_node_name()); - node->visit_children(this); + node->visit_children(*this); } void PerfVisitor::visit_if_statement(ast::IfStatement* node) { if (start_measurement) { current_block_perf.n_if++; - node->visit_children(this); + node->visit_children(*this); } } void PerfVisitor::visit_else_if_statement(ast::ElseIfStatement* node) { if (start_measurement) { current_block_perf.n_elif++; - node->visit_children(this); + node->visit_children(*this); } } @@ -318,7 +318,7 @@ void PerfVisitor::visit_program(ast::Program* node) { printer->push_block("BlockPerf"); } - node->visit_children(this); + node->visit_children(*this); std::string title = "Total Performance Statistics"; total_perf.title = title; total_perf.print(stream); @@ -353,7 +353,7 @@ void PerfVisitor::visit_statement_block(ast::StatementBlock* node) { /// new block perf starts from zero current_block_perf = PerfStat(); - node->visit_children(this); + node->visit_children(*this); /// add performance of all visited children total_perf = total_perf + current_block_perf; @@ -371,7 +371,7 @@ void PerfVisitor::visit_statement_block(ast::StatementBlock* node) { /// statement block (in theory) void PerfVisitor::visit_solve_block(ast::SolveBlock* node) { under_solve_block = true; - node->visit_children(this); + node->visit_children(*this); under_solve_block = false; } @@ -391,7 +391,7 @@ void PerfVisitor::visit_unary_expression(ast::UnaryExpression* node) { throw std::logic_error("Unary operator not handled in perf visitor"); } } - node->visit_children(this); + node->visit_children(*this); } /** Certain statements / symbols needs extra check while measuring diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 2cb62302e1..68a48085ed 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -27,7 +27,7 @@ void RenameVisitor::visit_name(ast::Name* node) { * by parser. To be safe we are only renaming prime variable. */ void RenameVisitor::visit_prime_name(ast::PrimeName* node) { - node->visit_children(this); + node->visit_children(*this); } /** diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 8dcc13894d..2b51bc2a5a 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -15,7 +15,7 @@ namespace visitor { void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { in_breakpoint_block = true; - node->visit_children(this); + node->visit_children(*this); in_breakpoint_block = false; } @@ -64,7 +64,7 @@ ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( * @param node Ast node for SOLVE statement in the mod file */ void SolveBlockVisitor::visit_expression_statement(ast::ExpressionStatement* node) { - node->visit_children(this); + node->visit_children(*this); if (node->get_expression()->is_solve_block()) { auto solve_block = dynamic_cast(node->get_expression().get()); auto sol_expr = create_solution_expression(solve_block); @@ -78,7 +78,7 @@ void SolveBlockVisitor::visit_expression_statement(ast::ExpressionStatement* nod void SolveBlockVisitor::visit_program(ast::Program* node) { symtab = node->get_symbol_table(); - node->visit_children(this); + node->visit_children(*this); /// add new node NrnState with solve blocks from breakpoint block if (!nrn_state_solve_statements.empty()) { auto nrn_state = new ast::NrnStateBlock(nrn_state_solve_statements); diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 66dff4fb63..5cd9ca47d5 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -41,7 +41,7 @@ using symtab::syminfo::NmodlType; */ static bool conductance_statement_possible(ast::BreakpointBlock* node) { AstLookupVisitor v({AstNodeType::IF_STATEMENT, AstNodeType::VERBATIM}); - node->accept(&v); + node->accept(v); return v.get_nodes().empty(); } @@ -216,7 +216,7 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) } // visit BREAKPOINT block statements under_breakpoint_block = true; - node->visit_children(this); + node->visit_children(*this); under_breakpoint_block = false; // lookup USEION and NONSPECIFIC statements from NEURON block @@ -249,7 +249,7 @@ void SympyConductanceVisitor::visit_program(ast::Program* node) { use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); nonspecific_nodes = ast_lookup_visitor.lookup(node, AstNodeType::NONSPECIFIC); - node->visit_children(this); + node->visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 91d6d155f9..65e94a728a 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -532,7 +532,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { // visit each differential equation: // - for CNEXP or EULER, each equation is independent & is replaced with its solution // - otherwise, each equation is added to eq_system - node->visit_children(this); + node->visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { // solve system of ODEs in eq_system @@ -596,7 +596,7 @@ void SympySolverVisitor::visit_lin_equation(ast::LinEquation* node) { last_expression_statement = current_expression_statement; logger->debug("SympySolverVisitor :: adding linear eq: {}", lin_eq); collect_state_vars = true; - node->visit_children(this); + node->visit_children(*this); collect_state_vars = false; } @@ -607,7 +607,7 @@ void SympySolverVisitor::visit_linear_block(ast::LinearBlock* node) { init_block_data(node); // collect linear equations - node->visit_children(this); + node->visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { solve_linear_system(); @@ -624,7 +624,7 @@ void SympySolverVisitor::visit_non_lin_equation(ast::NonLinEquation* node) { last_expression_statement = current_expression_statement; logger->debug("SympySolverVisitor :: adding non-linear eq: {}", non_lin_eq); collect_state_vars = true; - node->visit_children(this); + node->visit_children(*this); collect_state_vars = false; } @@ -635,7 +635,7 @@ void SympySolverVisitor::visit_non_linear_block(ast::NonLinearBlock* node) { init_block_data(node); // collect non-linear equations - node->visit_children(this); + node->visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { solve_non_linear_system(); @@ -645,14 +645,14 @@ void SympySolverVisitor::visit_non_linear_block(ast::NonLinearBlock* node) { void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement* node) { auto prev_expression_statement = current_expression_statement; current_expression_statement = node; - node->visit_children(this); + node->visit_children(*this); current_expression_statement = prev_expression_statement; } void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { auto prev_statement_block = current_statement_block; current_statement_block = node; - node->visit_children(this); + node->visit_children(*this); current_statement_block = prev_statement_block; } @@ -696,7 +696,7 @@ void SympySolverVisitor::visit_program(ast::Program* node) { } } - node->visit_children(this); + node->visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index dfb42638f8..7ea072a71a 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -127,7 +127,7 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { /// visit children, most likely variables are already /// leaf nodes, not necessary to visit - node->visit_children(this); + node->visit_children(*this); } @@ -187,7 +187,7 @@ void SymtabVisitor::setup_symbol_table(ast::Ast* node, const std::string& name, } /// look for all children blocks recursively - node->visit_children(this); + node->visit_children(*this); /// existing nmodl block modsymtab->leave_scope(); diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 575b4aa2f1..bef2aa7353 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -22,7 +22,7 @@ namespace visitor { void UnitsVisitor::visit_program(ast::Program* node) { units_driver.parse_file(units_dir); - node->visit_children(this); + node->visit_children(*this); } /** diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 5c6852705c..0d4f8a37cf 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -24,7 +24,7 @@ void VarUsageVisitor::visit_name(ast::Name* node) { bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { used = false; var_name = std::move(name); - node->visit_children(this); + node->visit_children(*this); return used; } diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 9a8882fe58..0775bf28d2 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -29,7 +29,7 @@ void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) // first need to process all children : perform recursively from innermost block for (const auto& item: node->get_statements()) { - item->accept(this); + item->accept(*this); } /// go back to previous block in hierarchy diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 7a364424d8..aa8184fe89 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -165,7 +165,7 @@ bool calls_function(ast::Ast* node, const std::string& name) { std::string to_nmodl(ast::Ast* node, const std::set& exclude_types) { std::stringstream stream; visitor::NmodlPrintVisitor v(stream, exclude_types); - node->accept(&v); + node->accept(v); return stream.str(); } @@ -176,7 +176,7 @@ std::string to_json(ast::Ast* node, bool compact, bool expand, bool add_nmodl) { v.compact_json(compact); v.add_nmodl(add_nmodl); v.expand_keys(expand); - node->accept(&v); + node->accept(v); v.flush(); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/lookup.cpp b/test/nmodl/transpiler/visitor/lookup.cpp index 359ffa6dc3..0900ca9ea0 100644 --- a/test/nmodl/transpiler/visitor/lookup.cpp +++ b/test/nmodl/transpiler/visitor/lookup.cpp @@ -65,7 +65,7 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") THEN("Can find NEURON block") { AstLookupVisitor v(AstNodeType::NEURON_BLOCK); - ast->accept(&v); + ast->accept(v); auto nodes = v.get_nodes(); REQUIRE(nodes.size() == 1); From cc38956e877d61fc53e337adfc2f686321d92454 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 28 Oct 2019 17:39:33 +0100 Subject: [PATCH 235/871] CMake changes to enable use of nmodl as submodule in CoreNEURON (BlueBrain/nmodl#245) - to enable use of NMODL inside coreneuron as submodule, avoid use of PROJECT_SOURCE_DIR directly - previously we were consistently using CMAKE_BINARY_DIR to have all output to main projects build directory - but in case of submodule it make sense to have nmodl specififc output into project instead of main project's bin and lib directory - only output main binary and libries into top level project NMODL Repo SHA: BlueBrain/nmodl@8c5f4a90edeb6ca47967c99fb32cfee9c17b3857 --- cmake/nmodl/CMakeLists.txt | 28 +++++++++++---- docs/nmodl/transpiler/Doxyfile.in | 6 ++-- src/nmodl/codegen/CMakeLists.txt | 4 +++ src/nmodl/config/config.cpp.in | 4 +-- src/nmodl/language/CMakeLists.txt | 14 ++++---- src/nmodl/lexer/CMakeLists.txt | 52 ++++++++++++++-------------- src/nmodl/pybind/CMakeLists.txt | 16 ++++----- src/nmodl/utils/CMakeLists.txt | 2 +- src/nmodl/visitors/CMakeLists.txt | 18 +++++----- test/nmodl/transpiler/CMakeLists.txt | 20 +++++------ 10 files changed, 91 insertions(+), 73 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index dc6532c510..762bdac239 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -19,6 +19,19 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +# ============================================================================= +# Settings to enable project as submodule +# ============================================================================= +set(NMODL_PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(NMODL_AS_SUBPROJECT OFF) +if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(NMODL_AS_SUBPROJECT ON) + # output targets into top level build directory + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +endif() + # ============================================================================= # Compile static libraries with hidden visibility # ============================================================================= @@ -65,7 +78,7 @@ add_custom_target(nb-format # ============================================================================= # Include cmake modules # ============================================================================= -list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) +list(APPEND CMAKE_MODULE_PATH ${NMODL_PROJECT_SOURCE_DIR}/cmake) include(Catch) include(ClangTidyHelper) include(CompilerHelper) @@ -86,10 +99,10 @@ find_python_module(sympy 1.2 REQUIRED) find_python_module(textwrap 0.9 REQUIRED) find_python_module(yaml 3.12 REQUIRED) -include_directories(${PROJECT_SOURCE_DIR} - ${PROJECT_SOURCE_DIR}/src +include_directories(${NMODL_PROJECT_SOURCE_DIR} + ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src - ${PROJECT_SOURCE_DIR}/ext) + ${NMODL_PROJECT_SOURCE_DIR}/ext) # ============================================================================= # Include pybind11 @@ -100,7 +113,7 @@ add_subdirectory(ext/pybind11) # ============================================================================= # Include path from external libraries # ============================================================================= -include_directories(${PROJECT_SOURCE_DIR}/ext/cli11/include) +include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/cli11/include) # ============================================================================= # Project version from git and project directories @@ -109,10 +122,11 @@ set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) # generate file with version number from git and nrnunits.lib file path configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in - ${CMAKE_CURRENT_BINARY_DIR}/config.cpp @ONLY) + ${PROJECT_BINARY_DIR}/src/config/config.cpp @ONLY) # generate Doxyfile with correct source paths -configure_file(${PROJECT_SOURCE_DIR}/docs/Doxyfile.in ${PROJECT_SOURCE_DIR}/docs/Doxyfile) +configure_file(${NMODL_PROJECT_SOURCE_DIR}/docs/Doxyfile.in + ${NMODL_PROJECT_SOURCE_DIR}/docs/Doxyfile) # ============================================================================= # list of autogenerated files diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index e6b63448f6..f1d13619a5 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -814,8 +814,8 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = @PROJECT_SOURCE_DIR@/src -INPUT += @PROJECT_SOURCE_DIR@/test +INPUT = @NMODL_PROJECT_SOURCE_DIR@/src +INPUT += @NMODL_PROJECT_SOURCE_DIR@/test INPUT += @PROJECT_BINARY_DIR@/src/ast INPUT += @PROJECT_BINARY_DIR@/src/visitors @@ -873,7 +873,7 @@ RECURSIVE = YES # Note that relative paths are relative to the directory from which doxygen is # run. -EXCLUDE = @PROJECT_SOURCE_DIR@/src/pybind/pybind_utils.hpp +EXCLUDE = @NMODL_PROJECT_SOURCE_DIR@/src/pybind/pybind_utils.hpp # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or # directories that are symbolic links (a Unix file system feature) are excluded diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 2264fb7667..8fc222fdac 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -27,6 +27,10 @@ add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) add_dependencies(codegen lexer util visitor) +# copy to build directory to make usable from build directory +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc + ${CMAKE_BINARY_DIR}/include/nmodl/fast_math.ispc COPYONLY) + # ============================================================================= # Install include files # ============================================================================= diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index c6158e1ddd..fc4039f608 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -17,8 +17,8 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; * \brief Path of nrnutils.lib file * * nrnunits.lib need to be loaded at runtime. Before project is - * installed it needs to be read from PROJECT_SOURCE_DIR and later + * installed it needs to be read from NMODL_PROJECT_SOURCE_DIR and later * from CMAKE_INSTALL_PREFIX. */ const std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = - {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; + {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@NMODL_PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 04855643a1..95144311fc 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -3,10 +3,10 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) -file(GLOB_RECURSE TEMPLATE_FILES "${PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") +file(GLOB_RECURSE TEMPLATE_FILES "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" + "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") -file(GLOB PYCODE "${PROJECT_SOURCE_DIR}/src/language/*.py") +file(GLOB PYCODE "${NMODL_PROJECT_SOURCE_DIR}/src/language/*.py") if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) set(CODE_GENERATOR_OPTS --clang-format=${ClangFormat_EXECUTABLE}) @@ -17,13 +17,13 @@ endif() add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} COMMAND ${PYTHON_EXECUTABLE} - ARGS ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" - WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src/language - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${PROJECT_SOURCE_DIR}/src/language/codegen.yaml + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml DEPENDS ${PYCODE} DEPENDS ${TEMPLATE_FILES} COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index abbbde4871..c9e797089f 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -15,26 +15,26 @@ set(BISON_GENERATED_SOURCE_FILES set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) -set(UNIT_SOURCE_FILES ${PROJECT_SOURCE_DIR}/src/units/units.hpp - ${PROJECT_SOURCE_DIR}/src/units/units.cpp) +set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) -set(NMODL_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp - ${PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) +set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) set(DIFFEQ_DRIVER_FILES - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) -set(C_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp - ${PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) +set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) -set(UNIT_DRIVER_FILES ${PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp - ${PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) +set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modl.h @@ -80,8 +80,8 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") # command to generate verbatim parser @@ -91,8 +91,8 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") # command to generate differential equation parser @@ -105,11 +105,11 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser. ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") # command to generate C (11) parser @@ -122,8 +122,8 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/c11.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/c11.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") # command to generate Units parser @@ -136,8 +136,8 @@ add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${PROJECT_SOURCE_DIR}/src/parser/unit.yy - DEPENDS ${PROJECT_SOURCE_DIR}/src/parser/unit.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") # command to generate nmodl lexer diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 254201cdfa..c1e2da91ec 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -4,7 +4,7 @@ set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) - list(APPEND NMODL_PYTHON_FILES_IN ${PROJECT_SOURCE_DIR}/nmodl/${file}) + list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() @@ -12,13 +12,13 @@ set(PYNMODL_SOURCES ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) set(PYNMODL_HEADERS ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp - ${PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp - ${PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) @@ -37,10 +37,10 @@ add_dependencies(_nmodl util_obj) add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/nmodl - ${CMAKE_BINARY_DIR}/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl + ${PROJECT_BINARY_DIR}/nmodl COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - ${CMAKE_BINARY_DIR}/nmodl + ${PROJECT_BINARY_DIR}/nmodl DEPENDS ${NMODL_PYTHON_FILES_IN} $ COMMENT "-- COPYING NMODL PYTHON FILES --") @@ -48,4 +48,4 @@ add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} # Install python binding components # ============================================================================= install(TARGETS _nmodl DESTINATION lib/python/nmodl) -install(DIRECTORY ${CMAKE_BINARY_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") +install(DIRECTORY ${PROJECT_BINARY_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 85500808ef..33c0e228da 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -11,7 +11,7 @@ set(UTIL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.hpp ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp - ${CMAKE_BINARY_DIR}/config.cpp) + ${PROJECT_BINARY_DIR}/src/config/config.cpp) # ============================================================================= # Symbol table library diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index d860287a2f..6b377fcd61 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -44,15 +44,15 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp) set(VISITOR_GENERATED_SOURCES - ${CMAKE_CURRENT_BINARY_DIR}/ast_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/json_visitor.hpp - ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/lookup_visitor.hpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_visitor.hpp - ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.cpp - ${CMAKE_CURRENT_BINARY_DIR}/symtab_visitor.hpp) + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp) set_source_files_properties(${VISITOR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index f68b121d5c..d232c065e3 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -1,9 +1,9 @@ -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/test) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -include_directories(${PROJECT_SOURCE_DIR}/ext/catch ${PROJECT_SOURCE_DIR}/test) -include_directories(${PROJECT_SOURCE_DIR}/src/solver) -include_directories(${PROJECT_SOURCE_DIR}/ext/eigen) +include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/catch ${NMODL_PROJECT_SOURCE_DIR}/test) +include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/solver) +include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= # Common input data library @@ -16,7 +16,7 @@ add_library(test_util # ============================================================================= # Common input data library # ============================================================================= -add_library(config STATIC ${PROJECT_BINARY_DIR}/config.cpp) +add_library(config STATIC ${PROJECT_BINARY_DIR}/src/config/config.cpp) # ============================================================================= # Test executables @@ -82,12 +82,12 @@ foreach(test_name "${test_name}/" PROPERTIES ENVIRONMENT - PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) else() add_test(NAME ${test_name} COMMAND ${test_name}) if(${test_name} STREQUAL "testvisitor") set_tests_properties(${test_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) endif() endif() endforeach() @@ -96,10 +96,10 @@ endforeach() # pybind11 tests # ============================================================================= add_test(NAME Ode - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PROJECT_SOURCE_DIR}/test/ode) + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) add_test(NAME Pybind - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PROJECT_SOURCE_DIR}/test/pybind) + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) foreach(test_name Ode Pybind) set_tests_properties(${test_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${CMAKE_BINARY_DIR}:$ENV{PYTHONPATH}) + PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) endforeach() From 0dd2d8ecdb776ec2c0084dddf389dd9b88921ac0 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 28 Oct 2019 17:40:09 +0100 Subject: [PATCH 236/871] Added fast_imem code blocks and small fixes (BlueBrain/nmodl#244) - Fixed info.electrode_current misspeling - Removed an additional space after for() keyword in some code blocks - Added fast_imem calculation for all the target backends NMODL Repo SHA: BlueBrain/nmodl@e78a2e21b7e8351850c03cc3e084eef90c3c2c83 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 14 +++++++ src/nmodl/codegen/codegen_acc_visitor.hpp | 2 + src/nmodl/codegen/codegen_c_visitor.cpp | 40 +++++++++++++++++++- src/nmodl/codegen/codegen_c_visitor.hpp | 11 ++++-- src/nmodl/codegen/codegen_cuda_visitor.cpp | 16 ++++++++ src/nmodl/codegen/codegen_cuda_visitor.hpp | 2 + src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- src/nmodl/codegen/codegen_info.hpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 11 +++++- src/nmodl/codegen/codegen_ispc_visitor.hpp | 6 +++ 10 files changed, 97 insertions(+), 9 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 02f3312773..37eb556d4d 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -120,6 +120,20 @@ void CodegenAccVisitor::print_nrn_cur_matrix_shadow_update() { printer->add_line("vec_d[node_id] {} g;"_format(d_op)); } +void CodegenAccVisitor::print_fast_imem_calculation() { + if (!info.electrode_current) { + return; + } + + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + printer->start_block("if (nt->nrn_fast_imem)"); + print_atomic_reduction_pragma(); + printer->add_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} rhs;"_format(rhs_op)); + print_atomic_reduction_pragma(); + printer->add_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;"_format(d_op)); + printer->end_block(1); +} void CodegenAccVisitor::print_nrn_cur_matrix_shadow_reduction() { // do nothing diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index a22242e2ce..7d1c7748c1 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -64,6 +64,8 @@ class CodegenAccVisitor: public CodegenCVisitor { /// reduction to matrix elements from shadow vectors void print_nrn_cur_matrix_shadow_reduction() override; + /// fast membrane current calculation + void print_fast_imem_calculation() override; /// setup method for setting matrix shadow vectors void print_rhs_d_shadow_variables() override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index d9b4586c40..5164593b87 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1057,7 +1057,7 @@ bool CodegenCVisitor::shadow_vector_setup_required() { */ void CodegenCVisitor::print_channel_iteration_block_begin(BlockType type) { print_channel_iteration_block_parallel_hint(type); - printer->start_block("for (int id = start; id < end; id++) "); + printer->start_block("for (int id = start; id < end; id++)"); } @@ -1125,7 +1125,7 @@ void CodegenCVisitor::print_atomic_reduction_pragma() { void CodegenCVisitor::print_shadow_reduction_block_begin() { - printer->start_block("for (int id = start; id < end; id++) "); + printer->start_block("for (int id = start; id < end; id++)"); } @@ -4033,6 +4033,38 @@ void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { } } +void CodegenCVisitor::print_fast_imem_calculation() { + if (!info.electrode_current) { + return; + } + std::string rhs, d; + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + if (channel_task_dependency_enabled()) { + rhs = get_variable_name("ml_rhs"); + d = get_variable_name("ml_d"); + } else if (info.point_process) { + rhs = "shadow_rhs[id]"; + d = "shadow_d[id]"; + } else { + rhs = "rhs"; + d = "g"; + } + + printer->start_block("if (nt->nrn_fast_imem)"); + if (nrn_cur_reduction_loop_required()) { + print_shadow_reduction_block_begin(); + printer->add_line("int node_id = node_index[id];"); + } + print_atomic_reduction_pragma(); + printer->add_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};"_format(rhs_op, rhs)); + print_atomic_reduction_pragma(); + printer->add_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};"_format(d_op, d)); + if (nrn_cur_reduction_loop_required()) { + print_shadow_reduction_block_end(); + } + printer->end_block(1); +} void CodegenCVisitor::print_nrn_cur() { if (!nrn_cur_required()) { @@ -4052,6 +4084,9 @@ void CodegenCVisitor::print_nrn_cur() { print_post_channel_iteration_common_code(); print_nrn_cur_kernel(info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); + if (!nrn_cur_reduction_loop_required()) { + print_fast_imem_calculation(); + } print_channel_iteration_block_end(); if (nrn_cur_reduction_loop_required()) { @@ -4059,6 +4094,7 @@ void CodegenCVisitor::print_nrn_cur() { print_nrn_cur_matrix_shadow_reduction(); print_shadow_reduction_statements(); print_shadow_reduction_block_end(); + print_fast_imem_calculation(); } print_channel_iteration_tiling_block_end(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 3a86b8d443..ab8f1ea177 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -315,7 +315,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Operator for rhs vector update (matrix update) */ std::string operator_for_rhs() { - return info.electorde_current ? "+=" : "-="; + return info.electrode_current ? "+=" : "-="; } @@ -323,7 +323,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Operator for diagonal vector update (matrix update) */ std::string operator_for_d() { - return info.electorde_current ? "-=" : "+="; + return info.electrode_current ? "-=" : "+="; } @@ -1422,7 +1422,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print end of block / loop for statement requiring reduction * */ - void print_shadow_reduction_block_end(); + virtual void print_shadow_reduction_block_end(); /** @@ -1711,6 +1711,11 @@ class CodegenCVisitor: public visitor::AstVisitor { */ void print_nrn_cur(); + /** + * Print fast membrane current calculation code + */ + virtual void print_fast_imem_calculation(); + /** * Print kernel for buffering net_receive events diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 24275e387a..e654e98666 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -100,6 +100,22 @@ void CodegenCudaVisitor::print_nrn_cur_matrix_shadow_update() { print_atomic_op("vec_d[node_id]", d_op, "g"); } +void CodegenCudaVisitor::print_fast_imem_calculation() { + if (!info.electrode_current) { + return; + } + + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + stringutils::remove_character(rhs_op, '='); + stringutils::remove_character(d_op, '='); + printer->start_block("if (nt->nrn_fast_imem)"); + print_atomic_reduction_pragma(); + print_atomic_op("nt->nrn_fast_imem->nrn_sav_rhs[node_id]", rhs_op, "rhs"); + print_atomic_reduction_pragma(); + print_atomic_op("nt->nrn_fast_imem->nrn_sav_d[node_id]", d_op, "g"); + printer->end_block(1); +} /* * Depending on the backend, print condition/loop for iterating over channels diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 36f6d92ada..bc00c9b509 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -47,6 +47,8 @@ class CodegenCudaVisitor: public CodegenCVisitor { /// update to matrix elements with/without shadow vectors void print_nrn_cur_matrix_shadow_update() override; + /// fast membrane current calculation + void print_fast_imem_calculation() override; /// reduction to matrix elements from shadow vectors void print_nrn_cur_matrix_shadow_reduction() override; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 8893a05610..4b0143a552 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -430,7 +430,7 @@ void CodegenHelperVisitor::visit_suffix(Suffix* node) { void CodegenHelperVisitor::visit_elctrode_current(ElctrodeCurrent* node) { - info.electorde_current = true; + info.electrode_current = true; } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index a6df78c8e3..a712e80249 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -160,7 +160,7 @@ struct CodegenInfo { bool artificial_cell = false; /// if electrode current specified - bool electorde_current = false; + bool electrode_current = false; /// if thread thread call back routines need to register bool thread_callback_register = false; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index dc2217065f..c3d2e0e1ed 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -207,19 +207,26 @@ void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_reduction() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); if (info.point_process) { - printer->start_block("if (programIndex == 0)"); printer->add_line("uniform int node_id = node_index[id];"); printer->add_line("vec_rhs[node_id] {} shadow_rhs[id];"_format(rhs_op)); printer->add_line("vec_d[node_id] {} shadow_d[id];"_format(d_op)); - printer->end_block(1); } } void CodegenIspcVisitor::print_shadow_reduction_block_begin() { printer->start_block("for (uniform int id = start; id < end; id++) "); + if (info.point_process) { + printer->start_block("if (programIndex == 0)"); + } } +void CodegenIspcVisitor::print_shadow_reduction_block_end() { + if (info.point_process) { + printer->end_block(1); + } + printer->end_block(1); +} void CodegenIspcVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 95f07985e2..fac3d75617 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -97,6 +97,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// reduction to matrix elements from shadow vectors void print_nrn_cur_matrix_shadow_reduction() override; + /// fast membrane current calculation /** * Print block / loop for statement requiring reduction @@ -104,6 +105,11 @@ class CodegenIspcVisitor: public CodegenCVisitor { */ void print_shadow_reduction_block_begin() override; + /** + * Print end of block / loop for statement requiring reduction + * + */ + void print_shadow_reduction_block_end() override; /// setup method for setting matrix shadow vectors void print_rhs_d_shadow_variables() override; From 1e6c7a858dc9195429d157ad76168fa3d46afdf0 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 28 Oct 2019 22:40:22 +0100 Subject: [PATCH 237/871] Disable tests if project is used as submodule (BlueBrain/nmodl#247) NMODL Repo SHA: BlueBrain/nmodl@86fc52d20aea385b64fb21be6444c6ff3ef293b0 --- cmake/nmodl/CMakeLists.txt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 762bdac239..5f450731cf 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -172,8 +172,11 @@ set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ --leak-check=full \ --track-origins=yes \ --show-possibly-lost=no") -include(CTest) -add_subdirectory(test) +# do not enable tests if nmodl is used as submodule +if(NOT NMODL_AS_SUBPROJECT) + include(CTest) + add_subdirectory(test) +endif() # ============================================================================= # Install unit database, examples and utility scripts from share From 3484920068f1ac74dd44c33a6311f80fefe65ef8 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Mon, 25 Nov 2019 05:34:15 -0500 Subject: [PATCH 238/871] Use the newly introduced function in coreneuron to set net_receive (BlueBrain/nmodl#248) This is necessary because we want to for now keep generated code free of the more complex changes of data encapsulation in coreneuron. * Use the newly introduced function in coreneuron to set net_receive : https://github.com/BlueBrain/CoreNeuron/pull/203 * initialize pointer with nullptr Co-Authored-By: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@675e1acdd9af7b99923ec5bafaa6879a69766ff8 --- src/nmodl/codegen/codegen_c_visitor.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 5164593b87..026638afb7 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2656,11 +2656,13 @@ void CodegenCVisitor::print_mechanism_register() { method_name("net_buf_receive"))); } if (info.num_net_receive_parameters != 0) { - printer->add_line("pnt_receive[mech_type] = {};"_format(method_name("net_receive"))); - printer->add_line("pnt_receive_size[mech_type] = num_net_receive_args();"); + auto net_recv_init_arg = "nullptr"; if (info.net_receive_initial_node != nullptr) { - printer->add_line("pnt_receive_init[mech_type] = net_init;"); + net_recv_init_arg = "net_init"; } + auto pnt_recline = "set_pnt_receive(mech_type, {}, {}, num_net_receive_args());"_format( + method_name("net_receive"), net_recv_init_arg); + printer->add_line(pnt_recline); } if (info.net_event_used || info.net_send_used) { printer->add_line("hoc_register_net_send_buffering(mech_type);"); From dced1869984defe4fdde464bb70285f1203b0412 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Fri, 24 Jan 2020 13:49:34 +0100 Subject: [PATCH 239/871] Changes tracking refactoring in coreneuron (BlueBrain/nmodl#252) * Updated include paths following the renames/moves in coreneuron * Bumped dependency versions and updated Cmake format to conform to updated `cmake-format` * Manually download a version of bison (3.4.2) against which we can build nmodl to work around BlueBrain/nmodl#251 NMODL Repo SHA: BlueBrain/nmodl@a7e2cc88fad9e0d6e3463fb266592a668bedff11 --- cmake/nmodl/CMakeLists.txt | 54 +++--- cmake/nmodl/Catch.cmake | 65 ++++--- cmake/nmodl/CatchAddTests.cmake | 48 ++--- cmake/nmodl/ClangTidyHelper.cmake | 23 +-- cmake/nmodl/FindPythonModule.cmake | 22 ++- cmake/nmodl/GitRevision.cmake | 24 ++- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 15 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 2 +- src/nmodl/language/CMakeLists.txt | 25 ++- src/nmodl/lexer/CMakeLists.txt | 198 ++++++++++----------- src/nmodl/nmodl/CMakeLists.txt | 9 +- src/nmodl/printer/CMakeLists.txt | 9 +- src/nmodl/pybind/CMakeLists.txt | 49 +++-- src/nmodl/solver/CMakeLists.txt | 9 +- src/nmodl/symtab/CMakeLists.txt | 6 +- src/nmodl/utils/string_utils.hpp | 10 +- test/nmodl/transpiler/CMakeLists.txt | 97 +++++----- 18 files changed, 324 insertions(+), 343 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 5f450731cf..bc0757404f 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -47,33 +47,34 @@ find_package(BISON 3.0 REQUIRED) # ============================================================================= # HPC Coding Conventions # ============================================================================= -set(NMODL_ClangFormat_EXCLUDES_RE ".*/ext/.*$$" - CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" - FORCE) -set(NMODL_CMakeFormat_EXCLUDES_RE ".*/ext/.*$$" - CACHE STRING "list of regular expressions to exclude CMake files from formatting" - FORCE) -set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen - CACHE STRING "list of CMake targets to build before formatting C++ code" - FORCE) +set(NMODL_ClangFormat_EXCLUDES_RE + ".*/ext/.*$$" + CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) +set(NMODL_CMakeFormat_EXCLUDES_RE + ".*/ext/.*$$" + CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) +set(NMODL_ClangFormat_DEPENDENCIES + pyastgen parser-gen + CACHE STRING "list of CMake targets to build before formatting C++ code" FORCE) add_subdirectory(cmake/hpc-coding-conventions/cpp) # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) # ============================================================================= -add_custom_target(nb-format - jupyter - nbconvert - --to - notebook - --execute - --inplace - --ExecutePreprocessor.timeout=360 - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" - && - clean_ipynb - --keep-output - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") +add_custom_target( + nb-format + jupyter + nbconvert + --to + notebook + --execute + --inplace + --ExecutePreprocessor.timeout=360 + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" + && + clean_ipynb + --keep-output + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") # ============================================================================= # Include cmake modules @@ -99,10 +100,8 @@ find_python_module(sympy 1.2 REQUIRED) find_python_module(textwrap 0.9 REQUIRED) find_python_module(yaml 3.12 REQUIRED) -include_directories(${NMODL_PROJECT_SOURCE_DIR} - ${NMODL_PROJECT_SOURCE_DIR}/src - ${PROJECT_BINARY_DIR}/src - ${NMODL_PROJECT_SOURCE_DIR}/ext) +include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src ${NMODL_PROJECT_SOURCE_DIR}/ext) # ============================================================================= # Include pybind11 @@ -168,7 +167,8 @@ add_subdirectory(src/solver) # Memory checker options and add tests # ============================================================================= find_program(MEMORYCHECK_COMMAND valgrind) -set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ +set(MEMORYCHECK_COMMAND_OPTIONS + "--trace-children=yes \ --leak-check=full \ --track-origins=yes \ --show-possibly-lost=no") diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake index d0b81ffe50..f33d7cdd24 100644 --- a/cmake/nmodl/Catch.cmake +++ b/cmake/nmodl/Catch.cmake @@ -94,11 +94,8 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. # ------------------------------------------------------------------------------ function(catch_discover_tests TARGET) - cmake_parse_arguments("" - "" - "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" - "TEST_SPEC;EXTRA_ARGS;PROPERTIES" - ${ARGN}) + cmake_parse_arguments("" "" "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" ${ARGN}) if(NOT _WORKING_DIRECTORY) set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") @@ -109,43 +106,45 @@ function(catch_discover_tests TARGET) # Generate a unique name based on the extra arguments string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") - string(SUBSTRING ${args_hash} - 0 - 7 - args_hash) + string(SUBSTRING ${args_hash} 0 7 args_hash) # Define rule to generate test list for aforementioned test executable set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") - get_property(crosscompiling_emulator TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR) - add_custom_command(TARGET - ${TARGET} - POST_BUILD - BYPRODUCTS - "${ctest_tests_file}" - COMMAND "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D - "TEST_EXECUTABLE=$" -D - "TEST_EXECUTOR=${crosscompiling_emulator}" -D - "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D "TEST_SPEC=${_TEST_SPEC}" - -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D "TEST_PROPERTIES=${_PROPERTIES}" - -D "TEST_PREFIX=${_TEST_PREFIX}" -D "TEST_SUFFIX=${_TEST_SUFFIX}" -D - "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" -P - "${_CATCH_DISCOVER_TESTS_SCRIPT}" - VERBATIM) - - file(WRITE "${ctest_include_file}" - "if(EXISTS \"${ctest_tests_file}\")\n" - " include(\"${ctest_tests_file}\")\n" - "else()\n" - " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" - "endif()\n") + get_property( + crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND + "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D "TEST_EXECUTABLE=$" -D + "TEST_EXECUTOR=${crosscompiling_emulator}" -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D + "TEST_SPEC=${_TEST_SPEC}" -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D + "TEST_PROPERTIES=${_PROPERTIES}" -D "TEST_PREFIX=${_TEST_PREFIX}" -D + "TEST_SUFFIX=${_TEST_SUFFIX}" -D "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" + -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM) + + file( + WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" " include(\"${ctest_tests_file}\")\n" "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" "endif()\n") if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") # Add discovered tests to directory TEST_INCLUDE_FILES - set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") + set_property( + DIRECTORY + APPEND + PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") else() # Add discovered tests as directory TEST_INCLUDE_FILE if possible - get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + get_property( + test_include_file_set + DIRECTORY + PROPERTY TEST_INCLUDE_FILE + SET) if(NOT ${test_include_file_set}) set_property(DIRECTORY PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}") else() diff --git a/cmake/nmodl/CatchAddTests.cmake b/cmake/nmodl/CatchAddTests.cmake index 2b4cd9b22f..79aa6d19fe 100644 --- a/cmake/nmodl/CatchAddTests.cmake +++ b/cmake/nmodl/CatchAddTests.cmake @@ -19,15 +19,14 @@ function(add_command NAME) set(_args "${_args} ${_arg}") endif() endforeach() - set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) + set(script + "${script}${NAME}(${_args})\n" + PARENT_SCOPE) endfunction() macro(_add_catch_test_labels LINE) # convert to list of tags - string(REPLACE "][" - "]\\;[" - tags - ${line}) + string(REPLACE "][" "]\\;[" tags ${line}) add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES LABELS "${tags}") endmacro() @@ -35,24 +34,13 @@ endmacro() macro(_add_catch_test LINE) set(test ${line}) # use escape commas to handle properly test cases with commans inside the name - string(REPLACE "," - "\\," - test_name - ${test}) + string(REPLACE "," "\\," test_name ${test}) # ...and add to script - add_command(add_test - "${prefix}${test}${suffix}" - ${TEST_EXECUTOR} - "${TEST_EXECUTABLE}" - "${test_name}" - ${extra_args}) + add_command(add_test "${prefix}${test}${suffix}" ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" + "${test_name}" ${extra_args}) - add_command(set_tests_properties - "${prefix}${test}${suffix}" - PROPERTIES - WORKING_DIRECTORY - "${TEST_WORKING_DIR}" - ${properties}) + add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES WORKING_DIRECTORY + "${TEST_WORKING_DIR}" ${properties}) list(APPEND tests "${prefix}${test}${suffix}") endmacro() @@ -60,9 +48,10 @@ endmacro() if(NOT EXISTS "${TEST_EXECUTABLE}") message(FATAL_ERROR "Specified test executable '${TEST_EXECUTABLE}' does not exist") endif() -execute_process(COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests - OUTPUT_VARIABLE output - RESULT_VARIABLE result) +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests + OUTPUT_VARIABLE output + RESULT_VARIABLE result) # Catch --list-test-names-only reports the number of tests, so 0 is... surprising if(${result} EQUAL 0) message(WARNING "Test executable '${TEST_EXECUTABLE}' contains no tests!\n") @@ -71,10 +60,7 @@ elseif(${result} LESS 0) " Result: ${result}\n" " Output: ${output}\n") endif() -string(REPLACE "\n" - ";" - output - "${output}") +string(REPLACE "\n" ";" output "${output}") set(test) set(tags_regex "(\\[([^\\[]*)\\])+$") @@ -83,11 +69,7 @@ foreach(line ${output}) # lines without leading whitespaces are catch output not tests if(${line} MATCHES "^[ \t]+") # strip leading spaces and tabs - string(REGEX - REPLACE "^[ \t]+" - "" - line - ${line}) + string(REGEX REPLACE "^[ \t]+" "" line ${line}) if(${line} MATCHES "${tags_regex}") _add_catch_test_labels(${line}) diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake index 3c4b892825..91c7389f30 100644 --- a/cmake/nmodl/ClangTidyHelper.cmake +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -1,21 +1,22 @@ if(CMAKE_VERSION VERSION_GREATER "3.5") - set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") + set(ENABLE_CLANG_TIDY + OFF + CACHE BOOL "Add clang-tidy automatically to builds") if(ENABLE_CLANG_TIDY) find_program(CLANG_TIDY_EXE NAMES "clang-tidy") if(CLANG_TIDY_EXE) message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set( - CLANG_TIDY_CHECKS - "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" - ) - set( - CMAKE_CXX_CLANG_TIDY - "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" - CACHE STRING "" - FORCE) + set(CLANG_TIDY_CHECKS + "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" + ) + set(CMAKE_CXX_CLANG_TIDY + "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" + CACHE STRING "" FORCE) else() message(AUTHOR_WARNING "clang-tidy not found!") - set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it + set(CMAKE_CXX_CLANG_TIDY + "" + CACHE STRING "" FORCE) # delete it endif() endif() endif() diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index 7ad83a9897..0af45ff948 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -37,14 +37,15 @@ macro(find_python_module module) OUTPUT_VARIABLE _${module}_location ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _${module}_status) - set(${module_upper}_LOCATION ${_${module}_location} + set(${module_upper}_LOCATION + ${_${module}_location} CACHE STRING "Location of Python module ${module}") # retrieve version - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import ${module}; print(${module}.__version__)" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_version - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" "-c" "import ${module}; print(${module}.__version__)" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_version + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) set(_${module_upper}_VERSION_MATCH TRUE) if(NOT _${module}_status) @@ -58,12 +59,9 @@ macro(find_python_module module) endif() endif() - find_package_handle_standard_args(${module} - REQUIRED_VARS - ${module_upper}_LOCATION - _${module_upper}_VERSION_MATCH - VERSION_VAR - ${module_upper}_VERSION_STRING) + find_package_handle_standard_args( + ${module} REQUIRED_VARS ${module_upper}_LOCATION _${module_upper}_VERSION_MATCH VERSION_VAR + ${module_upper}_VERSION_STRING) if(NOT ${module}_FIND_OPTIONAL AND NOT _${module_upper}_VERSION_MATCH) message(FATAL_ERROR "Missing python module ${module}") endif() diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index 2bbf2ce135..e1b190009d 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -6,23 +6,21 @@ find_package(Git) if(GIT_FOUND) # get last commit sha1 - execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_SHA1 - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # get last commit date - execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_DATE - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_DATE + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # remove extra double quotes - string(REGEX - REPLACE "\"" - "" - GIT_REVISION_DATE - "${GIT_REVISION_DATE}") + string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") else() diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 72226841ce..f0ca4d22ee 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 72226841ce176b11512468e407aff03c07e603ba +Subproject commit f0ca4d22ee081dca2c6d977ad4872b28742d56a9 diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 026638afb7..968396bd13 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2287,16 +2287,17 @@ void CodegenCVisitor::print_standard_includes() { void CodegenCVisitor::print_coreneuron_includes() { printer->add_newline(); - printer->add_line("#include "); + printer->add_line("#include "); printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include "); printer->add_line("#include \"_kinderiv.h\""); if (info.eigen_newton_solver_exist) { printer->add_line("#include "); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index c3d2e0e1ed..0dbab06773 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -143,7 +143,7 @@ std::string CodegenIspcVisitor::net_receive_buffering_declaration() { void CodegenIspcVisitor::print_backend_includes() { printer->add_line("#include \"nmodl/fast_math.ispc\""); - printer->add_line("#include \"coreneuron/nrnoc/nrnoc_ml.ispc\""); + printer->add_line("#include \"coreneuron/mechanism/nrnoc_ml.ispc\""); printer->add_newline(); printer->add_newline(); } diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 95144311fc..b0134cddaf 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -4,7 +4,7 @@ set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) file(GLOB_RECURSE TEMPLATE_FILES "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") + "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") file(GLOB PYCODE "${NMODL_PROJECT_SOURCE_DIR}/src/language/*.py") @@ -15,18 +15,17 @@ if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) endif(nmodl_ClangFormat_OPTIONS) endif() -add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} - COMMAND ${PYTHON_EXECUTABLE} - ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${CODE_GENERATOR_OPTS} - --base-dir ${PROJECT_BINARY_DIR}/src - --clang-format-opts="--style=file" - WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +add_custom_command( + OUTPUT ${AUTO_GENERATED_FILES} + COMMAND + ${PYTHON_EXECUTABLE} ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") unset(CODE_GENERATOR_OPTS) # ============================================================================= diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index c9e797089f..d9276a4020 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -16,10 +16,10 @@ set(BISON_GENERATED_SOURCE_FILES set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp @@ -29,12 +29,12 @@ set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modl.h @@ -62,134 +62,122 @@ set(LEXER_SOURCE_FILES # ============================================================================= # Directories for parsers (as they need to be in separate directories) # ============================================================================= -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl - ${PROJECT_BINARY_DIR}/src/parser/diffeq - ${PROJECT_BINARY_DIR}/src/parser/c - ${PROJECT_BINARY_DIR}/src/parser/unit) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR}/src/parser/diffeq + ${PROJECT_BINARY_DIR}/src/parser/c ${PROJECT_BINARY_DIR}/src/parser/unit) # ============================================================================= # Lexer & Parser commands # ============================================================================= # command to generate nmodl parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen - COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen + COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") # command to generate verbatim parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") # command to generate differential equation parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") # command to generate C (11) parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/location.hh - ${PROJECT_BINARY_DIR}/src/parser/c/position.hh - ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/c/location.hh + ${PROJECT_BINARY_DIR}/src/parser/c/position.hh + ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") # command to generate Units parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") # command to generate nmodl lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp - COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") # command to generate verbatim lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") # command to generate differential equation lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") # command to generate C (11) lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") # command to generate Units lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll - COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") # ============================================================================= # Libraries & executables # ============================================================================= -add_library(lexer_obj - OBJECT - ${LEXER_SOURCE_FILES} - ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES} - ${UNIT_SOURCE_FILES}) +add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} + ${AST_SOURCE_FILES} ${UNIT_SOURCE_FILES}) set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index b53dd30910..a83f15ef20 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -8,7 +8,14 @@ set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) # Add executables # ============================================================================= add_executable(nmodl ${NMODL_SOURCE_FILES}) -target_link_libraries(nmodl printer codegen visitor symtab util lexer) +target_link_libraries( + nmodl + printer + codegen + visitor + symtab + util + lexer) # ============================================================================= # Install executable diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 18aad38487..5a44153dab 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -2,12 +2,9 @@ # Printer sources # ============================================================================= set(PRINTER_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) # ============================================================================= # Printer library diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index c1e2da91ec..e36f65ff61 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,14 +3,20 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) -foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) +foreach( + file + ast.py + dsl.py + ode.py + symtab.py + visitor.py + __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() set(PYNMODL_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) @@ -22,30 +28,35 @@ set(PYNMODL_HEADERS ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) -pybind11_add_module(_nmodl - ${PYNMODL_HEADERS} - ${PYNMODL_SOURCES} - $ - $ - $ - $ - $) +pybind11_add_module( + _nmodl + ${PYNMODL_HEADERS} + ${PYNMODL_SOURCES} + $ + $ + $ + $ + $) add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) -add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl - ${PROJECT_BINARY_DIR}/nmodl - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - ${PROJECT_BINARY_DIR}/nmodl - DEPENDS ${NMODL_PYTHON_FILES_IN} $ - COMMENT "-- COPYING NMODL PYTHON FILES --") +add_custom_command( + OUTPUT ${NMODL_PYTHON_FILES_OUT} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl + ${PROJECT_BINARY_DIR}/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl + DEPENDS ${NMODL_PYTHON_FILES_IN} $ + COMMENT "-- COPYING NMODL PYTHON FILES --") # ============================================================================= # Install python binding components # ============================================================================= install(TARGETS _nmodl DESTINATION lib/python/nmodl) -install(DIRECTORY ${PROJECT_BINARY_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") +install( + DIRECTORY ${PROJECT_BINARY_DIR}/nmodl + DESTINATION lib/python/ + FILES_MATCHING + PATTERN "*.py") diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index c88c944de6..5fa56e3ec1 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -10,7 +10,8 @@ set(SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) # ============================================================================= # Install headers # ============================================================================= -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/newton - DESTINATION include - FILES_MATCHING - PATTERN "*.h*") +install( + DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/newton + DESTINATION include + FILES_MATCHING + PATTERN "*.h*") diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index dc9323953e..8487d51bec 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -2,13 +2,11 @@ # Visitor sources # ============================================================================= set(SYMTAB_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp) set(SYMTAB_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp) # ============================================================================= diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index adb67b2a52..4ea9825f9b 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -16,6 +16,8 @@ */ #include +#include +#include #include #include @@ -33,16 +35,14 @@ enum class text_alignment { left, right, center }; /// Trim from start static inline std::string& ltrim(std::string& s) { - s.erase(s.begin(), - std::find_if(s.begin(), s.end(), std::not1(std::ptr_fun(std::isspace)))); + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int c) { return !std::isspace(c); })); return s; } /// Trim from end static inline std::string& rtrim(std::string& s) { - s.erase( - std::find_if(s.rbegin(), s.rend(), std::not1(std::ptr_fun(std::isspace))).base(), - s.end()); + s.erase(std::find_if(s.rbegin(), s.rend(), [](int c) { return !std::isspace(c); }).base(), + s.end()); return s; } diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index d232c065e3..c20adec606 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -8,10 +8,8 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= # Common input data library # ============================================================================= -add_library(test_util - STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) +add_library(test_util STATIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) # ============================================================================= # Common input data library @@ -24,28 +22,29 @@ add_library(config STATIC ${PROJECT_BINARY_DIR}/src/config/config.cpp) add_executable(testmodtoken modtoken/modtoken.cpp) add_executable(testlexer lexer/tokens.cpp) add_executable(testparser parser/parser.cpp) -add_executable(testvisitor - visitor/main.cpp - visitor/constant_folder.cpp - visitor/defuse_analyze.cpp - visitor/inline.cpp - visitor/json.cpp - visitor/kinetic_block.cpp - visitor/localize.cpp - visitor/lookup.cpp - visitor/loop_unroll.cpp - visitor/misc.cpp - visitor/neuron_solve.cpp - visitor/nmodl.cpp - visitor/perf.cpp - visitor/rename.cpp - visitor/solve_block.cpp - visitor/steadystate.cpp - visitor/sympy_conductance.cpp - visitor/sympy_solver.cpp - visitor/units.cpp - visitor/var_usage.cpp - visitor/verbatim.cpp) +add_executable( + testvisitor + visitor/main.cpp + visitor/constant_folder.cpp + visitor/defuse_analyze.cpp + visitor/inline.cpp + visitor/json.cpp + visitor/kinetic_block.cpp + visitor/localize.cpp + visitor/lookup.cpp + visitor/loop_unroll.cpp + visitor/misc.cpp + visitor/neuron_solve.cpp + visitor/nmodl.cpp + visitor/perf.cpp + visitor/rename.cpp + visitor/solve_block.cpp + visitor/steadystate.cpp + visitor/sympy_conductance.cpp + visitor/sympy_solver.cpp + visitor/units.cpp + visitor/var_usage.cpp + visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) @@ -55,7 +54,14 @@ add_executable(testunitparser units/parser.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) target_link_libraries(testparser lexer util test_util visitor) -target_link_libraries(testvisitor visitor symtab lexer util test_util printer) +target_link_libraries( + testvisitor + visitor + symtab + lexer + util + test_util + printer) target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) @@ -65,23 +71,20 @@ target_link_libraries(testunitparser lexer test_util config) # Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, # else use the normal add_test method # ============================================================================= -foreach(test_name - testmodtoken - testlexer - testparser - testvisitor - testprinter - testsymtab - testnewton - testunitlexer - testunitparser) +foreach( + test_name + testmodtoken + testlexer + testparser + testvisitor + testprinter + testsymtab + testnewton + testunitlexer + testunitparser) if(${CMAKE_VERSION} VERSION_GREATER "3.10") - catch_discover_tests(${test_name} - TEST_PREFIX - "${test_name}/" - PROPERTIES - ENVIRONMENT + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) else() add_test(NAME ${test_name} COMMAND ${test_name}) @@ -95,11 +98,9 @@ endforeach() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME Ode - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) -add_test(NAME Pybind - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) +add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) +add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) foreach(test_name Ode Pybind) - set_tests_properties(${test_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) + set_tests_properties(${test_name} PROPERTIES ENVIRONMENT + PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) endforeach() From f68725c49687be0c80d6e6542e074730c88c556a Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Fri, 24 Jan 2020 16:32:02 +0100 Subject: [PATCH 240/871] Fixing bad submodule pulls in BlueBrain/nmodl#252 (BlueBrain/nmodl#254) In PR BlueBrain/nmodl#252 I mistakenly did a `pull --recurse-submodules` and commited the updated submodule into this repo pulling in changes from various submodules. Thiw as not intended and a mistake. Undoing it here. One small adjustment was to checkout a commit from hpc-coding-conventions that is one before the cmake-format version change but still much newer than the previously checked out version. NMODL Repo SHA: BlueBrain/nmodl@91c5905146e9146f85e8d201fc9b2ed1e6bd6795 --- cmake/nmodl/CMakeLists.txt | 54 ++++---- cmake/nmodl/Catch.cmake | 65 ++++----- cmake/nmodl/CatchAddTests.cmake | 48 +++++-- cmake/nmodl/ClangTidyHelper.cmake | 23 ++-- cmake/nmodl/FindPythonModule.cmake | 22 +-- cmake/nmodl/GitRevision.cmake | 24 ++-- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/language/CMakeLists.txt | 25 ++-- src/nmodl/lexer/CMakeLists.txt | 198 ++++++++++++++------------- src/nmodl/nmodl/CMakeLists.txt | 9 +- src/nmodl/printer/CMakeLists.txt | 9 +- src/nmodl/pybind/CMakeLists.txt | 49 +++---- src/nmodl/solver/CMakeLists.txt | 9 +- src/nmodl/symtab/CMakeLists.txt | 6 +- test/nmodl/transpiler/CMakeLists.txt | 97 +++++++------ 15 files changed, 330 insertions(+), 310 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index bc0757404f..5f450731cf 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -47,34 +47,33 @@ find_package(BISON 3.0 REQUIRED) # ============================================================================= # HPC Coding Conventions # ============================================================================= -set(NMODL_ClangFormat_EXCLUDES_RE - ".*/ext/.*$$" - CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) -set(NMODL_CMakeFormat_EXCLUDES_RE - ".*/ext/.*$$" - CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) -set(NMODL_ClangFormat_DEPENDENCIES - pyastgen parser-gen - CACHE STRING "list of CMake targets to build before formatting C++ code" FORCE) +set(NMODL_ClangFormat_EXCLUDES_RE ".*/ext/.*$$" + CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" + FORCE) +set(NMODL_CMakeFormat_EXCLUDES_RE ".*/ext/.*$$" + CACHE STRING "list of regular expressions to exclude CMake files from formatting" + FORCE) +set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen + CACHE STRING "list of CMake targets to build before formatting C++ code" + FORCE) add_subdirectory(cmake/hpc-coding-conventions/cpp) # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) # ============================================================================= -add_custom_target( - nb-format - jupyter - nbconvert - --to - notebook - --execute - --inplace - --ExecutePreprocessor.timeout=360 - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" - && - clean_ipynb - --keep-output - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") +add_custom_target(nb-format + jupyter + nbconvert + --to + notebook + --execute + --inplace + --ExecutePreprocessor.timeout=360 + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" + && + clean_ipynb + --keep-output + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") # ============================================================================= # Include cmake modules @@ -100,8 +99,10 @@ find_python_module(sympy 1.2 REQUIRED) find_python_module(textwrap 0.9 REQUIRED) find_python_module(yaml 3.12 REQUIRED) -include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src - ${PROJECT_BINARY_DIR}/src ${NMODL_PROJECT_SOURCE_DIR}/ext) +include_directories(${NMODL_PROJECT_SOURCE_DIR} + ${NMODL_PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src + ${NMODL_PROJECT_SOURCE_DIR}/ext) # ============================================================================= # Include pybind11 @@ -167,8 +168,7 @@ add_subdirectory(src/solver) # Memory checker options and add tests # ============================================================================= find_program(MEMORYCHECK_COMMAND valgrind) -set(MEMORYCHECK_COMMAND_OPTIONS - "--trace-children=yes \ +set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ --leak-check=full \ --track-origins=yes \ --show-possibly-lost=no") diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake index f33d7cdd24..d0b81ffe50 100644 --- a/cmake/nmodl/Catch.cmake +++ b/cmake/nmodl/Catch.cmake @@ -94,8 +94,11 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. # ------------------------------------------------------------------------------ function(catch_discover_tests TARGET) - cmake_parse_arguments("" "" "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" - "TEST_SPEC;EXTRA_ARGS;PROPERTIES" ${ARGN}) + cmake_parse_arguments("" + "" + "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" + ${ARGN}) if(NOT _WORKING_DIRECTORY) set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") @@ -106,45 +109,43 @@ function(catch_discover_tests TARGET) # Generate a unique name based on the extra arguments string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") - string(SUBSTRING ${args_hash} 0 7 args_hash) + string(SUBSTRING ${args_hash} + 0 + 7 + args_hash) # Define rule to generate test list for aforementioned test executable set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") - get_property( - crosscompiling_emulator - TARGET ${TARGET} - PROPERTY CROSSCOMPILING_EMULATOR) - add_custom_command( - TARGET ${TARGET} POST_BUILD - BYPRODUCTS "${ctest_tests_file}" - COMMAND - "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D "TEST_EXECUTABLE=$" -D - "TEST_EXECUTOR=${crosscompiling_emulator}" -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D - "TEST_SPEC=${_TEST_SPEC}" -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D - "TEST_PROPERTIES=${_PROPERTIES}" -D "TEST_PREFIX=${_TEST_PREFIX}" -D - "TEST_SUFFIX=${_TEST_SUFFIX}" -D "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" - -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" - VERBATIM) - - file( - WRITE "${ctest_include_file}" - "if(EXISTS \"${ctest_tests_file}\")\n" " include(\"${ctest_tests_file}\")\n" "else()\n" - " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" "endif()\n") + get_property(crosscompiling_emulator TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR) + add_custom_command(TARGET + ${TARGET} + POST_BUILD + BYPRODUCTS + "${ctest_tests_file}" + COMMAND "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D + "TEST_EXECUTABLE=$" -D + "TEST_EXECUTOR=${crosscompiling_emulator}" -D + "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D "TEST_SPEC=${_TEST_SPEC}" + -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D "TEST_PROPERTIES=${_PROPERTIES}" + -D "TEST_PREFIX=${_TEST_PREFIX}" -D "TEST_SUFFIX=${_TEST_SUFFIX}" -D + "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" -P + "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM) + + file(WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" + " include(\"${ctest_tests_file}\")\n" + "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" + "endif()\n") if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") # Add discovered tests to directory TEST_INCLUDE_FILES - set_property( - DIRECTORY - APPEND - PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") + set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") else() # Add discovered tests as directory TEST_INCLUDE_FILE if possible - get_property( - test_include_file_set - DIRECTORY - PROPERTY TEST_INCLUDE_FILE - SET) + get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) if(NOT ${test_include_file_set}) set_property(DIRECTORY PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}") else() diff --git a/cmake/nmodl/CatchAddTests.cmake b/cmake/nmodl/CatchAddTests.cmake index 79aa6d19fe..2b4cd9b22f 100644 --- a/cmake/nmodl/CatchAddTests.cmake +++ b/cmake/nmodl/CatchAddTests.cmake @@ -19,14 +19,15 @@ function(add_command NAME) set(_args "${_args} ${_arg}") endif() endforeach() - set(script - "${script}${NAME}(${_args})\n" - PARENT_SCOPE) + set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) endfunction() macro(_add_catch_test_labels LINE) # convert to list of tags - string(REPLACE "][" "]\\;[" tags ${line}) + string(REPLACE "][" + "]\\;[" + tags + ${line}) add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES LABELS "${tags}") endmacro() @@ -34,13 +35,24 @@ endmacro() macro(_add_catch_test LINE) set(test ${line}) # use escape commas to handle properly test cases with commans inside the name - string(REPLACE "," "\\," test_name ${test}) + string(REPLACE "," + "\\," + test_name + ${test}) # ...and add to script - add_command(add_test "${prefix}${test}${suffix}" ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" - "${test_name}" ${extra_args}) + add_command(add_test + "${prefix}${test}${suffix}" + ${TEST_EXECUTOR} + "${TEST_EXECUTABLE}" + "${test_name}" + ${extra_args}) - add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES WORKING_DIRECTORY - "${TEST_WORKING_DIR}" ${properties}) + add_command(set_tests_properties + "${prefix}${test}${suffix}" + PROPERTIES + WORKING_DIRECTORY + "${TEST_WORKING_DIR}" + ${properties}) list(APPEND tests "${prefix}${test}${suffix}") endmacro() @@ -48,10 +60,9 @@ endmacro() if(NOT EXISTS "${TEST_EXECUTABLE}") message(FATAL_ERROR "Specified test executable '${TEST_EXECUTABLE}' does not exist") endif() -execute_process( - COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests - OUTPUT_VARIABLE output - RESULT_VARIABLE result) +execute_process(COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests + OUTPUT_VARIABLE output + RESULT_VARIABLE result) # Catch --list-test-names-only reports the number of tests, so 0 is... surprising if(${result} EQUAL 0) message(WARNING "Test executable '${TEST_EXECUTABLE}' contains no tests!\n") @@ -60,7 +71,10 @@ elseif(${result} LESS 0) " Result: ${result}\n" " Output: ${output}\n") endif() -string(REPLACE "\n" ";" output "${output}") +string(REPLACE "\n" + ";" + output + "${output}") set(test) set(tags_regex "(\\[([^\\[]*)\\])+$") @@ -69,7 +83,11 @@ foreach(line ${output}) # lines without leading whitespaces are catch output not tests if(${line} MATCHES "^[ \t]+") # strip leading spaces and tabs - string(REGEX REPLACE "^[ \t]+" "" line ${line}) + string(REGEX + REPLACE "^[ \t]+" + "" + line + ${line}) if(${line} MATCHES "${tags_regex}") _add_catch_test_labels(${line}) diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake index 91c7389f30..3c4b892825 100644 --- a/cmake/nmodl/ClangTidyHelper.cmake +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -1,22 +1,21 @@ if(CMAKE_VERSION VERSION_GREATER "3.5") - set(ENABLE_CLANG_TIDY - OFF - CACHE BOOL "Add clang-tidy automatically to builds") + set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") if(ENABLE_CLANG_TIDY) find_program(CLANG_TIDY_EXE NAMES "clang-tidy") if(CLANG_TIDY_EXE) message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(CLANG_TIDY_CHECKS - "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" - ) - set(CMAKE_CXX_CLANG_TIDY - "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" - CACHE STRING "" FORCE) + set( + CLANG_TIDY_CHECKS + "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" + ) + set( + CMAKE_CXX_CLANG_TIDY + "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" + CACHE STRING "" + FORCE) else() message(AUTHOR_WARNING "clang-tidy not found!") - set(CMAKE_CXX_CLANG_TIDY - "" - CACHE STRING "" FORCE) # delete it + set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it endif() endif() endif() diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index 0af45ff948..7ad83a9897 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -37,15 +37,14 @@ macro(find_python_module module) OUTPUT_VARIABLE _${module}_location ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _${module}_status) - set(${module_upper}_LOCATION - ${_${module}_location} + set(${module_upper}_LOCATION ${_${module}_location} CACHE STRING "Location of Python module ${module}") # retrieve version - execute_process( - COMMAND "${PYTHON_EXECUTABLE}" "-c" "import ${module}; print(${module}.__version__)" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_version - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" + "import ${module}; print(${module}.__version__)" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_version + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) set(_${module_upper}_VERSION_MATCH TRUE) if(NOT _${module}_status) @@ -59,9 +58,12 @@ macro(find_python_module module) endif() endif() - find_package_handle_standard_args( - ${module} REQUIRED_VARS ${module_upper}_LOCATION _${module_upper}_VERSION_MATCH VERSION_VAR - ${module_upper}_VERSION_STRING) + find_package_handle_standard_args(${module} + REQUIRED_VARS + ${module_upper}_LOCATION + _${module_upper}_VERSION_MATCH + VERSION_VAR + ${module_upper}_VERSION_STRING) if(NOT ${module}_FIND_OPTIONAL AND NOT _${module_upper}_VERSION_MATCH) message(FATAL_ERROR "Missing python module ${module}") endif() diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index e1b190009d..2bbf2ce135 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -6,21 +6,23 @@ find_package(Git) if(GIT_FOUND) # get last commit sha1 - execute_process( - COMMAND ${GIT_EXECUTABLE} log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_SHA1 - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # get last commit date - execute_process( - COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_DATE - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_DATE + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # remove extra double quotes - string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") + string(REGEX + REPLACE "\"" + "" + GIT_REVISION_DATE + "${GIT_REVISION_DATE}") set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") else() diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index f0ca4d22ee..a1813c26dd 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit f0ca4d22ee081dca2c6d977ad4872b28742d56a9 +Subproject commit a1813c26dd75b8360b08893be5b2ecb485b816c4 diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index b0134cddaf..95144311fc 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -4,7 +4,7 @@ set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) file(GLOB_RECURSE TEMPLATE_FILES "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") + "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") file(GLOB PYCODE "${NMODL_PROJECT_SOURCE_DIR}/src/language/*.py") @@ -15,17 +15,18 @@ if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) endif(nmodl_ClangFormat_OPTIONS) endif() -add_custom_command( - OUTPUT ${AUTO_GENERATED_FILES} - COMMAND - ${PYTHON_EXECUTABLE} ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" - WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} + COMMAND ${PYTHON_EXECUTABLE} + ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} + --base-dir ${PROJECT_BINARY_DIR}/src + --clang-format-opts="--style=file" + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") unset(CODE_GENERATOR_OPTS) # ============================================================================= diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index d9276a4020..c9e797089f 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -16,10 +16,10 @@ set(BISON_GENERATED_SOURCE_FILES set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp @@ -29,12 +29,12 @@ set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modl.h @@ -62,122 +62,134 @@ set(LEXER_SOURCE_FILES # ============================================================================= # Directories for parsers (as they need to be in separate directories) # ============================================================================= -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR}/src/parser/diffeq - ${PROJECT_BINARY_DIR}/src/parser/c ${PROJECT_BINARY_DIR}/src/parser/unit) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl + ${PROJECT_BINARY_DIR}/src/parser/diffeq + ${PROJECT_BINARY_DIR}/src/parser/c + ${PROJECT_BINARY_DIR}/src/parser/unit) # ============================================================================= # Lexer & Parser commands # ============================================================================= # command to generate nmodl parser -add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen - COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen + COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") # command to generate verbatim parser -add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") # command to generate differential equation parser -add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") # command to generate C (11) parser -add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/location.hh - ${PROJECT_BINARY_DIR}/src/parser/c/position.hh - ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/c/location.hh + ${PROJECT_BINARY_DIR}/src/parser/c/position.hh + ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") # command to generate Units parser -add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") +add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh + COMMAND ${BISON_EXECUTABLE} + ARGS -d + -o + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") # command to generate nmodl lexer -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp - COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") # command to generate verbatim lexer -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") # command to generate differential equation lexer -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") # command to generate C (11) lexer -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") # command to generate Units lexer -add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll - COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") # ============================================================================= # Libraries & executables # ============================================================================= -add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES} ${UNIT_SOURCE_FILES}) +add_library(lexer_obj + OBJECT + ${LEXER_SOURCE_FILES} + ${BISON_GENERATED_SOURCE_FILES} + ${AST_SOURCE_FILES} + ${UNIT_SOURCE_FILES}) set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index a83f15ef20..b53dd30910 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -8,14 +8,7 @@ set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) # Add executables # ============================================================================= add_executable(nmodl ${NMODL_SOURCE_FILES}) -target_link_libraries( - nmodl - printer - codegen - visitor - symtab - util - lexer) +target_link_libraries(nmodl printer codegen visitor symtab util lexer) # ============================================================================= # Install executable diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 5a44153dab..18aad38487 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -2,9 +2,12 @@ # Printer sources # ============================================================================= set(PRINTER_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) # ============================================================================= # Printer library diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index e36f65ff61..c1e2da91ec 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,20 +3,14 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) -foreach( - file - ast.py - dsl.py - ode.py - symtab.py - visitor.py - __init__.py) +foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() set(PYNMODL_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) @@ -28,35 +22,30 @@ set(PYNMODL_HEADERS ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) -pybind11_add_module( - _nmodl - ${PYNMODL_HEADERS} - ${PYNMODL_SOURCES} - $ - $ - $ - $ - $) +pybind11_add_module(_nmodl + ${PYNMODL_HEADERS} + ${PYNMODL_SOURCES} + $ + $ + $ + $ + $) add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) -add_custom_command( - OUTPUT ${NMODL_PYTHON_FILES_OUT} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl - ${PROJECT_BINARY_DIR}/nmodl - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl - DEPENDS ${NMODL_PYTHON_FILES_IN} $ - COMMENT "-- COPYING NMODL PYTHON FILES --") +add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl + ${PROJECT_BINARY_DIR}/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ + ${PROJECT_BINARY_DIR}/nmodl + DEPENDS ${NMODL_PYTHON_FILES_IN} $ + COMMENT "-- COPYING NMODL PYTHON FILES --") # ============================================================================= # Install python binding components # ============================================================================= install(TARGETS _nmodl DESTINATION lib/python/nmodl) -install( - DIRECTORY ${PROJECT_BINARY_DIR}/nmodl - DESTINATION lib/python/ - FILES_MATCHING - PATTERN "*.py") +install(DIRECTORY ${PROJECT_BINARY_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index 5fa56e3ec1..c88c944de6 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -10,8 +10,7 @@ set(SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) # ============================================================================= # Install headers # ============================================================================= -install( - DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/newton - DESTINATION include - FILES_MATCHING - PATTERN "*.h*") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/newton + DESTINATION include + FILES_MATCHING + PATTERN "*.h*") diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index 8487d51bec..dc9323953e 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -2,11 +2,13 @@ # Visitor sources # ============================================================================= set(SYMTAB_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp) set(SYMTAB_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp) # ============================================================================= diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index c20adec606..d232c065e3 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -8,8 +8,10 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= # Common input data library # ============================================================================= -add_library(test_util STATIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) +add_library(test_util + STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) # ============================================================================= # Common input data library @@ -22,29 +24,28 @@ add_library(config STATIC ${PROJECT_BINARY_DIR}/src/config/config.cpp) add_executable(testmodtoken modtoken/modtoken.cpp) add_executable(testlexer lexer/tokens.cpp) add_executable(testparser parser/parser.cpp) -add_executable( - testvisitor - visitor/main.cpp - visitor/constant_folder.cpp - visitor/defuse_analyze.cpp - visitor/inline.cpp - visitor/json.cpp - visitor/kinetic_block.cpp - visitor/localize.cpp - visitor/lookup.cpp - visitor/loop_unroll.cpp - visitor/misc.cpp - visitor/neuron_solve.cpp - visitor/nmodl.cpp - visitor/perf.cpp - visitor/rename.cpp - visitor/solve_block.cpp - visitor/steadystate.cpp - visitor/sympy_conductance.cpp - visitor/sympy_solver.cpp - visitor/units.cpp - visitor/var_usage.cpp - visitor/verbatim.cpp) +add_executable(testvisitor + visitor/main.cpp + visitor/constant_folder.cpp + visitor/defuse_analyze.cpp + visitor/inline.cpp + visitor/json.cpp + visitor/kinetic_block.cpp + visitor/localize.cpp + visitor/lookup.cpp + visitor/loop_unroll.cpp + visitor/misc.cpp + visitor/neuron_solve.cpp + visitor/nmodl.cpp + visitor/perf.cpp + visitor/rename.cpp + visitor/solve_block.cpp + visitor/steadystate.cpp + visitor/sympy_conductance.cpp + visitor/sympy_solver.cpp + visitor/units.cpp + visitor/var_usage.cpp + visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) @@ -54,14 +55,7 @@ add_executable(testunitparser units/parser.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) target_link_libraries(testparser lexer util test_util visitor) -target_link_libraries( - testvisitor - visitor - symtab - lexer - util - test_util - printer) +target_link_libraries(testvisitor visitor symtab lexer util test_util printer) target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) @@ -71,20 +65,23 @@ target_link_libraries(testunitparser lexer test_util config) # Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, # else use the normal add_test method # ============================================================================= -foreach( - test_name - testmodtoken - testlexer - testparser - testvisitor - testprinter - testsymtab - testnewton - testunitlexer - testunitparser) +foreach(test_name + testmodtoken + testlexer + testparser + testvisitor + testprinter + testsymtab + testnewton + testunitlexer + testunitparser) if(${CMAKE_VERSION} VERSION_GREATER "3.10") - catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT + catch_discover_tests(${test_name} + TEST_PREFIX + "${test_name}/" + PROPERTIES + ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) else() add_test(NAME ${test_name} COMMAND ${test_name}) @@ -98,9 +95,11 @@ endforeach() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) -add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) +add_test(NAME Ode + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) +add_test(NAME Pybind + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) foreach(test_name Ode Pybind) - set_tests_properties(${test_name} PROPERTIES ENVIRONMENT - PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) + set_tests_properties(${test_name} + PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) endforeach() From 19d834ede75ae507fdfd44bbf73a48ec7b7a825f Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 24 Feb 2020 14:59:24 +0100 Subject: [PATCH 241/871] Directly construct *Visitors instead of constructing and assigning BlueBrain/nmodl#171 (BlueBrain/nmodl#256) * Directly construct *Visitors instead of constructing and assigning BlueBrain/nmodl#171 - i.e.: "auto lv = AstLookupVisitor(bla bla)" -> "AstLookupVisitor lv(bla bla)" - "make clang-format cmake-format" slightly changed 2 more files * Trying to solve the iOS Travis err: "brew: unknown command bundle" - "Brew bundle" has been deprecated for months and now it has been removed entirely. Here: https://bit.ly/2Vonh2O - they suggest to add an "update: true" line. Let's try it! fixes BlueBrain/nmodl#171 NMODL Repo SHA: BlueBrain/nmodl@c763cfadb71a736dafc00ceaa6ad2803b0cc9169 --- .travis.yml | 1 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 10 +++++----- src/nmodl/lexer/modl.h | 6 +++--- src/nmodl/visitors/sympy_solver_visitor.cpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/lexer/tokens.cpp | 4 +++- 6 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4ea4d88faf..25e17e6e5e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -51,6 +51,7 @@ addons: - boost - cmake - python@3 + update: true #============================================================================= # Install dependencies / setup Spack diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 0dbab06773..2c4decaa66 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -56,9 +56,9 @@ void CodegenIspcVisitor::visit_var_name(ast::VarName* node) { if (!codegen) { return; } - auto celsius_rename = RenameVisitor("celsius", "ispc_celsius"); + RenameVisitor celsius_rename("celsius", "ispc_celsius"); node->accept(celsius_rename); - auto pi_rename = RenameVisitor("PI", "ISPC_PI"); + RenameVisitor pi_rename("PI", "ISPC_PI"); node->accept(pi_rename); CodegenCVisitor::visit_var_name(node); } @@ -641,7 +641,7 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { } void CodegenIspcVisitor::determine_target() { - auto node_lv = AstLookupVisitor(incompatible_node_types); + AstLookupVisitor node_lv(incompatible_node_types); if (info.initial_node) { emit_fallback[BlockType::Initial] = @@ -679,7 +679,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { auto nameset = std::set(); auto populate_nameset = [&nameset](ast::Block* block) { - auto name_lv = AstLookupVisitor(ast::AstNodeType::NAME); + AstLookupVisitor name_lv(ast::AstNodeType::NAME); if (block) { auto names = name_lv.lookup(block); for (const auto& name: names) { @@ -691,7 +691,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { populate_nameset(info.nrn_state_block); populate_nameset(info.breakpoint_node); - auto node_lv = AstLookupVisitor(incompatible_node_types); + AstLookupVisitor node_lv(incompatible_node_types); auto target_procedures = std::vector(); for (auto it = info.procedures.begin(); it != info.procedures.end(); it++) { auto procname = (*it)->get_name()->get_node_name(); diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index 0e1de5cd68..bf3df86f18 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -20,7 +20,7 @@ */ /// bit masks for block types where integration method are used -#define DERF 01000 -#define KINF 02000 -#define LINF 0200000 +#define DERF 01000 +#define KINF 02000 +#define LINF 0200000 #define NLINF 04000 diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 65e94a728a..b26394972d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -44,7 +44,7 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { vars.insert(var_name); } } - auto lv = AstLookupVisitor(ast::AstNodeType::FUNCTION_CALL); + AstLookupVisitor lv(ast::AstNodeType::FUNCTION_CALL); for (const auto& call: lv.lookup(node->get_statement_block().get())) { function_calls.insert(call->get_node_name()); } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index aa8184fe89..676f73f100 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -150,7 +150,7 @@ std::set get_global_vars(Program* node) { bool calls_function(ast::Ast* node, const std::string& name) { - auto lv = AstLookupVisitor(ast::AstNodeType::FUNCTION_CALL); + AstLookupVisitor lv(ast::AstNodeType::FUNCTION_CALL); for (const auto& f: lv.lookup(node)) { if (std::dynamic_pointer_cast(f)->get_node_name() == name) { return true; diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index f6e7709bf8..e1079f6503 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -75,7 +75,9 @@ TokenType token_type(const std::string& name) { break; } - default: { auto value = sym.value.as(); } + default: { + auto value = sym.value.as(); + } } return token; From 929fbb933a5be0347e109bfd6218d0334dae4cf7 Mon Sep 17 00:00:00 2001 From: Pramod S Kumbhar Date: Fri, 6 Mar 2020 00:01:19 +0100 Subject: [PATCH 242/871] Fix link errors with fmt for non-Debug build type - spdlog also ships fmt which is older version - use newer fmt consistently through utils/logger.hpp - use fmt and spdlog as submodules - remove logger includes - include fmt when necessary - travis CI fixes : brew update required fixes BlueBrain/nmodl#264 BlueBrain/nmodl#265 BlueBrain/nmodl#266 BlueBrain/nmodl#267 BlueBrain/nmodl#268 NMODL Repo SHA: BlueBrain/nmodl@809d68de3f476db53569b277f553c62097fa25e5 --- .travis.yml | 9 +++++++-- cmake/nmodl/CMakeLists.txt | 12 ++++++++++-- src/nmodl/codegen/codegen_acc_visitor.cpp | 3 ++- src/nmodl/codegen/codegen_cuda_visitor.cpp | 2 -- src/nmodl/codegen/codegen_helper_visitor.cpp | 5 +++-- src/nmodl/codegen/codegen_ispc_visitor.cpp | 1 - src/nmodl/language/code_generator.py | 11 ++++++++--- src/nmodl/lexer/CMakeLists.txt | 1 + src/nmodl/lexer/main_c.cpp | 1 - src/nmodl/lexer/main_nmodl.cpp | 1 - src/nmodl/lexer/main_units.cpp | 1 - src/nmodl/nmodl/main.cpp | 1 - src/nmodl/parser/main_c.cpp | 1 - src/nmodl/parser/main_nmodl.cpp | 1 - src/nmodl/parser/main_units.cpp | 1 - src/nmodl/pybind/CMakeLists.txt | 5 +++++ src/nmodl/symtab/symbol.cpp | 3 ++- src/nmodl/visitors/kinetic_block_visitor.cpp | 1 - src/nmodl/visitors/main.cpp | 1 - src/nmodl/visitors/steadystate_visitor.cpp | 1 - 20 files changed, 38 insertions(+), 24 deletions(-) diff --git a/.travis.yml b/.travis.yml index 25e17e6e5e..48e59e16f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -47,19 +47,24 @@ addons: homebrew: packages: - flex - - bison - boost - cmake - python@3 - update: true + update: true #============================================================================= # Install dependencies / setup Spack #============================================================================= before_install: + # default bison in homebrew is now 3.5 but nmodl currently does not build with bison 3.5 # brew installed flex and bison is not in $PATH + # unlink python2 and use python3 as it's required for nmodl - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; + brew unlink python@2; + brew link --overwrite python; + curl -L "https://homebrew.bintray.com/bottles/bison-3.4.2.mojave.bottle.tar.gz" -o bison-3.4.2.mojave.bottle.tar.gz; + brew install file://$(pwd)/bison-3.4.2.mojave.bottle.tar.gz; else pyenv global $PYTHON_VERSION; ls /usr/bin/; diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 5f450731cf..2b61d9d13d 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -13,7 +13,6 @@ project(NMODL CXX) # ============================================================================= set(PROJECT_VERSION_MAJOR 0) set(PROJECT_VERSION_MINOR 2) -set(CMAKE_BUILD_TYPE Debug) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -102,7 +101,9 @@ find_python_module(yaml 3.12 REQUIRED) include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src - ${NMODL_PROJECT_SOURCE_DIR}/ext) + ${NMODL_PROJECT_SOURCE_DIR}/ext + ${NMODL_PROJECT_SOURCE_DIR}/ext/fmt/include + ${NMODL_PROJECT_SOURCE_DIR}/ext/spdlog/include) # ============================================================================= # Include pybind11 @@ -110,6 +111,13 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR} message(STATUS "INCLUDING PYBIND11") add_subdirectory(ext/pybind11) +# ============================================================================= +# Include fmt library +# ============================================================================= +message(STATUS "INCLUDING FMT") +add_subdirectory(ext/fmt EXCLUDE_FROM_ALL) +set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) + # ============================================================================= # Include path from external libraries # ============================================================================= diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 37eb556d4d..ee2a497584 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "fmt/format.h" + #include "codegen/codegen_acc_visitor.hpp" -#include using namespace fmt::literals; diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index e654e98666..a99b5d576c 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -5,8 +5,6 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - #include "codegen/codegen_cuda_visitor.hpp" #include "symtab/symbol_table.hpp" #include "utils/string_utils.hpp" diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 4b0143a552..fafff887f8 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -7,14 +7,15 @@ #include #include +#include + +#include "fmt/format.h" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" -#include -#include using namespace fmt::literals; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 2c4decaa66..9bd7578156 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -6,7 +6,6 @@ *************************************************************************/ #include -#include #include #include "codegen/codegen_ispc_visitor.hpp" diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 205dcd9199..aa12859823 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -48,10 +48,12 @@ destination_dir = Path(args.base_dir) or Path(__file__).resolve().parent.parent # templates will be created and clang-formated in tempfile. -# copy .clang-format file for correct formating +# create temp directory and copy .clang-format file for correct formating clang_format_file = Path(Path(__file__).resolve().parent.parent.parent / ".clang-format") +temp_dir = tempfile.mkdtemp() +os.chdir(temp_dir) + if clang_format_file.exists(): - temp_dir = tempfile.gettempdir() shutil.copy2(clang_format_file, temp_dir) env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates_dir)), @@ -72,7 +74,7 @@ if destination_file.exists(): # render template in temporary file and update target file # ONLY if different (to save a lot of build time) - fd, tmp_path = tempfile.mkstemp() + fd, tmp_path = tempfile.mkstemp(dir=temp_dir) os.write(fd, content.encode('utf-8')) os.close(fd) if clang_format: @@ -89,3 +91,6 @@ if updated_files: logging.info(' Updating out of date template files : %s', ' '.join(updated_files)) + +# remove temp directory +shutil.rmtree(temp_dir) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index c9e797089f..5bfcb2aacb 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -199,6 +199,7 @@ add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) add_executable(units_lexer main_units.cpp) +target_link_libraries(lexer fmt::fmt) target_link_libraries(nmodl_lexer lexer util) target_link_libraries(c_lexer lexer util) target_link_libraries(units_lexer lexer util) diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 809c5f21aa..18f64d467c 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -9,7 +9,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "config/config.h" #include "lexer/c11_lexer.hpp" diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 9f3cd7c985..2b2b71c66a 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -9,7 +9,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "ast/ast.hpp" #include "config/config.h" diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index a3aeee0c18..78e715a3df 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -8,7 +8,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "config/config.h" #include "lexer/unit_lexer.hpp" diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 6e572c3564..6241b1f187 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -10,7 +10,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "pybind11/embed.h" #include "ast/ast_decl.hpp" diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index 95eb41326d..b07835f9f1 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -8,7 +8,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "config/config.h" #include "parser/c11_driver.hpp" diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index d3b3a8298b..ac7b5ec2a1 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -7,7 +7,6 @@ #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "config/config.h" #include "parser/nmodl_driver.hpp" diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index ac9973e27f..cf8ddb7d49 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -8,7 +8,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "config/config.h" #include "parser/unit_driver.hpp" diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index c1e2da91ec..f783dadf58 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -22,7 +22,10 @@ set(PYNMODL_HEADERS ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) +# Note that LTO causes link time errors with GCC 8. To avoid this, we disable LTO for pybind using +# NO_EXTRAS. See #266. pybind11_add_module(_nmodl + NO_EXTRAS ${PYNMODL_HEADERS} ${PYNMODL_SOURCES} $ @@ -35,6 +38,8 @@ add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) +target_link_libraries(_nmodl PRIVATE fmt::fmt) + add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 415d570ff0..8f041a992d 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -5,9 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "symtab/symbol.hpp" #include "fmt/format.h" +#include "symtab/symbol.hpp" + using namespace fmt::literals; diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 25985b1442..3305c38d6d 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -7,7 +7,6 @@ #include -#include "fmt/format.h" #include "kinetic_block_visitor.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 25d0cc6378..e0db60a433 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -8,7 +8,6 @@ #include #include "CLI/CLI.hpp" -#include "fmt/format.h" #include "pybind11/embed.h" #include "config/config.h" diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 9b1216a66c..072202337a 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -7,7 +7,6 @@ #include -#include "fmt/format.h" #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" From db47b6fdb65c5bef3162034cf11838942f43defa Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 10 Mar 2020 14:26:39 +0100 Subject: [PATCH 243/871] Fix incompatibility with Bison >= 3.5 (BlueBrain/nmodl#270) - bison 3.5 released symbol_type::token () removed - we use c++ parsers with variant semantic values and symbol_type::token was used for unit testing as well as to decide if next token is EoF. - now we have to use symbol_type.type_get() and by_type(yytokentype) to compare the tokens - all comparisons with switch cases need to be converted to if-else because of non-constexpr by_type() function call (bison without C++17) - see bison bug report for details : https://lists.gnu.org/archive/html/bug-bison/2020-01/msg00001.html - remove older bison installation for CI fixes BlueBrain/nmodl#251 NMODL Repo SHA: BlueBrain/nmodl@383cf297ce845dcb6dd7859838bed37ad5e50412 --- .travis.yml | 6 +- src/nmodl/lexer/main_c.cpp | 6 +- src/nmodl/lexer/main_nmodl.cpp | 55 ++++------ src/nmodl/lexer/main_units.cpp | 5 +- src/nmodl/parser/c11_driver.cpp | 4 +- src/nmodl/parser/diffeq_context.cpp | 4 +- src/nmodl/parser/unit_driver.cpp | 4 +- src/nmodl/parser/verbatim.yy | 2 +- test/nmodl/transpiler/lexer/tokens.cpp | 139 +++++++++++++------------ test/nmodl/transpiler/units/lexer.cpp | 55 +++++----- 10 files changed, 140 insertions(+), 140 deletions(-) diff --git a/.travis.yml b/.travis.yml index 48e59e16f2..ed1adbcebb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,9 +46,10 @@ addons: # for Mac builds, we use Homebrew homebrew: packages: - - flex + - bison - boost - cmake + - flex - python@3 update: true @@ -56,15 +57,12 @@ addons: # Install dependencies / setup Spack #============================================================================= before_install: - # default bison in homebrew is now 3.5 but nmodl currently does not build with bison 3.5 # brew installed flex and bison is not in $PATH # unlink python2 and use python3 as it's required for nmodl - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; brew unlink python@2; brew link --overwrite python; - curl -L "https://homebrew.bintray.com/bottles/bison-3.4.2.mojave.bottle.tar.gz" -o bison-3.4.2.mojave.bottle.tar.gz; - brew install file://$(pwd)/bison-3.4.2.mojave.bottle.tar.gz; else pyenv global $PYTHON_VERSION; ls /usr/bin/; diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 18f64d467c..5b07de8ca1 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -25,7 +25,7 @@ using namespace fmt::literals; using namespace nmodl; - +using Token = parser::CParser::token; void scan_c_code(std::istream& in) { nmodl::parser::CDriver driver; @@ -34,8 +34,8 @@ void scan_c_code(std::istream& in) { /// parse C file and print token until EOF while (true) { auto sym = scanner.next_token(); - auto token = sym.token(); - if (token == nmodl::parser::CParser::token::END) { + auto token_type = sym.type_get(); + if (token_type == parser::CParser::by_type(Token::END).type_get()) { break; } std::cout << sym.value.as() << std::endl; diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 2b2b71c66a..04f6cce100 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -45,74 +45,65 @@ void tokenize(const std::string& mod_text) { /// lexer instance with stream to read-in tokens NmodlLexer scanner(driver, &in); + auto get_token_type = [](TokenType token) { + return parser::NmodlParser::by_type(token).type_get(); + }; + /// parse nmodl text and print token until EOF while (true) { SymbolType sym = scanner.next_token(); - TokenType token = sym.token(); + auto token_type = sym.type_get(); - if (token == Token::END) { + if (token_type == get_token_type(Token::END)) { break; } - /** Lexer returns different ast types base on token type. We + /** + * Lexer returns different ast types base on token type. We * retrieve token object from each instance and print it. - * Note that value is of ast type i.e. ast::Name* etc. */ - switch (token) { + * Note that value is of ast type i.e. ast::Name* etc. + */ /// token with name ast class - case Token::NAME: - case Token::METHOD: - case Token::SUFFIX: - case Token::VALENCE: - case Token::DEL: - case Token::DEL2: { + if (token_type == get_token_type(Token::NAME) || + token_type == get_token_type(Token::METHOD) || + token_type == get_token_type(Token::SUFFIX) || + token_type == get_token_type(Token::VALENCE) || + token_type == get_token_type(Token::DEL) || token_type == get_token_type(Token::DEL2)) { auto value = sym.value.as(); std::cout << *(value.get_token()) << std::endl; - break; } - /// token with prime ast class - case Token::PRIME: { + else if (token_type == get_token_type(Token::PRIME)) { auto value = sym.value.as(); std::cout << *(value.get_token()) << std::endl; - break; } - /// token with integer ast class - case Token::INTEGER: { + else if (token_type == get_token_type(Token::INTEGER)) { auto value = sym.value.as(); std::cout << *(value.get_token()) << std::endl; - break; } - /// token with double/float ast class - case Token::REAL: { + else if (token_type == get_token_type(Token::REAL)) { auto value = sym.value.as(); std::cout << *(value.get_token()) << std::endl; - break; } - /// token with string ast class - case Token::STRING: { + else if (token_type == get_token_type(Token::STRING)) { auto value = sym.value.as(); std::cout << *(value.get_token()) << std::endl; - break; } - /// token with string data type - case Token::VERBATIM: - case Token::BLOCK_COMMENT: - case Token::LINE_PART: { + else if (token_type == get_token_type(Token::VERBATIM) || + token_type == get_token_type(Token::BLOCK_COMMENT) || + token_type == get_token_type(Token::LINE_PART)) { auto str = sym.value.as(); std::cout << str << std::endl; - break; } - /// all remaining tokens has ModToken* as a vaue - default: { + else { auto token = sym.value.as(); std::cout << token << std::endl; } - } } } diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index 78e715a3df..e46b73b6ad 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -22,6 +22,7 @@ using namespace fmt::literals; using namespace nmodl; +using Token = parser::UnitParser::token; int main(int argc, const char* argv[]) { CLI::App app{"Unit-Lexer : Standalone Lexer for Units({})"_format(Version::to_string())}; @@ -42,8 +43,8 @@ int main(int argc, const char* argv[]) { /// parse Units file and print token until EOF while (true) { auto sym = scanner.next_token(); - auto token = sym.token(); - if (token == nmodl::parser::UnitParser::token::END) { + auto token_type = sym.type_get(); + if (token_type == parser::UnitParser::by_type(Token::END).type_get()) { break; } std::cout << sym.value.as() << std::endl; diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index 654d560d7b..2e25c9accb 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -69,8 +69,8 @@ void CDriver::scan_string(std::string& text) { this->parser = &parser; while (true) { auto sym = lexer->next_token(); - auto token = sym.token(); - if (token == CParser::token::END) { + auto token_type = sym.type_get(); + if (token_type == CParser::by_type(CParser::token::END).type_get()) { break; } } diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 743e8ae40e..735bdf0f30 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -77,8 +77,8 @@ std::string DiffEqContext::get_expr_for_nonlinear() { /// scan entire expression while (true) { auto sym = scanner.next_token(); - auto token = sym.token(); - if (token == DiffeqParser::token::END) { + auto token_type = sym.type_get(); + if (token_type == DiffeqParser::by_type(DiffeqParser::token::END).type_get()) { break; } /// extract value of the token and check if it is a token diff --git a/src/nmodl/parser/unit_driver.cpp b/src/nmodl/parser/unit_driver.cpp index fc4849ab75..459925d68f 100644 --- a/src/nmodl/parser/unit_driver.cpp +++ b/src/nmodl/parser/unit_driver.cpp @@ -64,8 +64,8 @@ void UnitDriver::scan_string(std::string& text) { this->parser = &parser; while (true) { auto sym = lexer->next_token(); - auto token = sym.token(); - if (token == UnitParser::token::END) { + auto token_type = sym.type_get(); + if (token_type == UnitParser::by_type(UnitParser::token::END).type_get()) { break; } } diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index feb58adeaa..27df1e835c 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -20,7 +20,7 @@ %} /** print out verbose error instead of just message 'syntax error' */ -%error-verbose +%define parse.error verbose /** make a reentrant parser */ %pure-parser diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index e1079f6503..2b23086e07 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -23,8 +23,9 @@ using Token = NmodlParser::token; using TokenType = NmodlParser::token_type; using SymbolType = NmodlParser::symbol_type; -/// just retrieve token type from lexer -TokenType token_type(const std::string& name) { + +/// retrieve token type from lexer and check if it's of given type +bool check_token_type(const std::string& name, TokenType type) { std::istringstream ss(name); std::istream& in = ss; @@ -32,104 +33,112 @@ TokenType token_type(const std::string& name) { NmodlLexer scanner(driver, &in); SymbolType sym = scanner.next_token(); - TokenType token = sym.token(); + int token_type = sym.type_get(); + + auto get_token_type = [](TokenType token) { + return parser::NmodlParser::by_type(token).type_get(); + }; - /** Lexer returns raw pointers for some AST types + /** + * Lexer returns raw pointers for some AST types * and we need to clean-up memory for those. - * Todo: add tests later for checking values */ - switch (token) { - case Token::NAME: - case Token::METHOD: - case Token::SUFFIX: - case Token::VALENCE: - case Token::DEL: - case Token::DEL2: { + * Todo: add tests later for checking values + */ + // clang-format off + if (token_type == get_token_type(Token::NAME) || + token_type == get_token_type(Token::METHOD) || + token_type == get_token_type(Token::SUFFIX) || + token_type == get_token_type(Token::VALENCE) || + token_type == get_token_type(Token::DEL) || + token_type == get_token_type(Token::DEL2)) { auto value = sym.value.as(); - break; + REQUIRE(value.get_node_name() != ""); } - - case Token::PRIME: { + // clang-format on + // prime variable + else if (token_type == get_token_type(Token::PRIME)) { auto value = sym.value.as(); - break; + REQUIRE(value.get_node_name() != ""); } - - case Token::INTEGER: { + // integer constant + else if (token_type == get_token_type(Token::INTEGER)) { auto value = sym.value.as(); - break; + REQUIRE(value.get_value() != 0); } - - case Token::REAL: { + // float constant + else if (token_type == get_token_type(Token::REAL)) { auto value = sym.value.as(); - break; + REQUIRE(value.get_value() != 0); } - - case Token::STRING: { + // const char* + else if (token_type == get_token_type(Token::STRING)) { auto value = sym.value.as(); - break; + REQUIRE(value.get_value() != ""); } - - case Token::VERBATIM: - case Token::BLOCK_COMMENT: - case Token::LINE_PART: { + // string block representation verbatim or block comment + else if (token_type == get_token_type(Token::VERBATIM) || + token_type == get_token_type(Token::BLOCK_COMMENT) || + token_type == get_token_type(Token::LINE_PART)) { auto value = sym.value.as(); - break; + REQUIRE(value != ""); } - - default: { + // rest of the tokens + else { auto value = sym.value.as(); - } + REQUIRE(value.text() != ""); } - return token; + return sym.type_get() == get_token_type(type); } TEST_CASE("NMODL Lexer returning valid token types", "[Lexer]") { SECTION("Some keywords") { - REQUIRE(token_type("VERBATIM Hello ENDVERBATIM") == Token::VERBATIM); - REQUIRE(token_type("INITIAL") == Token::INITIAL1); - REQUIRE(token_type("SOLVE") == Token::SOLVE); + REQUIRE(check_token_type("VERBATIM Hello ENDVERBATIM", Token::VERBATIM)); + REQUIRE(check_token_type("INITIAL", Token::INITIAL1)); + REQUIRE(check_token_type("SOLVE", Token::SOLVE)); } SECTION("NMODL language keywords and constructs") { - REQUIRE(token_type(" h' = (hInf-h)/hTau\n") == Token::PRIME); - REQUIRE(token_type("while") == Token::WHILE); - REQUIRE(token_type("if") == Token::IF); - REQUIRE(token_type("else") == Token::ELSE); - REQUIRE(token_type("WHILE") == Token::WHILE); - REQUIRE(token_type("IF") == Token::IF); - REQUIRE(token_type("ELSE") == Token::ELSE); - REQUIRE(token_type("REPRESENTS NCIT:C17145") == Token::REPRESENTS); + REQUIRE(check_token_type(" h' = (hInf-h)/hTau\n", Token::PRIME)); + REQUIRE(check_token_type("while", Token::WHILE)); + REQUIRE(check_token_type("if", Token::IF)); + REQUIRE(check_token_type("else", Token::ELSE)); + REQUIRE(check_token_type("WHILE", Token::WHILE)); + REQUIRE(check_token_type("IF", Token::IF)); + REQUIRE(check_token_type("ELSE", Token::ELSE)); + REQUIRE(check_token_type("REPRESENTS NCIT:C17145", Token::REPRESENTS)); } SECTION("Different number types") { - REQUIRE(token_type("123") == Token::INTEGER); - REQUIRE(token_type("123.32") == Token::REAL); - REQUIRE(token_type("1.32E+3") == Token::REAL); - REQUIRE(token_type("1.32e-3") == Token::REAL); - REQUIRE(token_type("32e-3") == Token::REAL); - REQUIRE(token_type("1e+23") == Token::REAL); - REQUIRE(token_type("1e-23") == Token::REAL); + REQUIRE(check_token_type("123", Token::INTEGER)); + REQUIRE(check_token_type("123.32", Token::REAL)); + REQUIRE(check_token_type("1.32E+3", Token::REAL)); + REQUIRE(check_token_type("1.32e-3", Token::REAL)); + REQUIRE(check_token_type("32e-3", Token::REAL)); + REQUIRE(check_token_type("1e+23", Token::REAL)); + REQUIRE(check_token_type("1e-23", Token::REAL)); + REQUIRE_FALSE(check_token_type("124.11", Token::INTEGER)); } SECTION("Name/Strings types") { - REQUIRE(token_type("neuron") == Token::NAME); - REQUIRE(token_type("\"Quoted String\"") == Token::STRING); + REQUIRE(check_token_type("neuron", Token::NAME)); + REQUIRE(check_token_type("\"Quoted String\"", Token::STRING)); } SECTION("Logical operator types") { - REQUIRE(token_type(">") == Token::GT); - REQUIRE(token_type(">=") == Token::GE); - REQUIRE(token_type("<") == Token::LT); - REQUIRE(token_type("==") == Token::EQ); - REQUIRE(token_type("!=") == Token::NE); - REQUIRE(token_type("<->") == Token::REACT1); - REQUIRE(token_type("~+") == Token::NONLIN1); + REQUIRE(check_token_type(">", Token::GT)); + REQUIRE(check_token_type(">=", Token::GE)); + REQUIRE(check_token_type("<", Token::LT)); + REQUIRE(check_token_type("==", Token::EQ)); + REQUIRE(check_token_type("!=", Token::NE)); + REQUIRE(check_token_type("<->", Token::REACT1)); + REQUIRE(check_token_type("~+", Token::NONLIN1)); } SECTION("Brace types") { - REQUIRE(token_type("{") == Token::OPEN_BRACE); - REQUIRE(token_type("}") == Token::CLOSE_BRACE); - REQUIRE(token_type("(") == Token::OPEN_PARENTHESIS); - REQUIRE(token_type(")") != Token::OPEN_PARENTHESIS); + REQUIRE(check_token_type("{", Token::OPEN_BRACE)); + REQUIRE(check_token_type("}", Token::CLOSE_BRACE)); + REQUIRE(check_token_type("(", Token::OPEN_PARENTHESIS)); + REQUIRE_FALSE(check_token_type(")", Token::OPEN_PARENTHESIS)); } } diff --git a/test/nmodl/transpiler/units/lexer.cpp b/test/nmodl/transpiler/units/lexer.cpp index cd96177525..9902231d6a 100644 --- a/test/nmodl/transpiler/units/lexer.cpp +++ b/test/nmodl/transpiler/units/lexer.cpp @@ -22,50 +22,51 @@ using Token = UnitParser::token; using TokenType = UnitParser::token_type; using SymbolType = UnitParser::symbol_type; -/// just retrieve token type from lexer -TokenType token_type(const std::string& name) { +/// retrieve token type from lexer and check if it's of given type +bool check_token_type(const std::string& name, TokenType type) { std::istringstream ss(name); std::istream& in = ss; UnitDriver driver; UnitLexer scanner(driver, &in); - return scanner.next_token().token(); + auto symbol = scanner.next_token(); + return symbol.type_get() == UnitParser::by_type(type).type_get(); } TEST_CASE("Unit Lexer tests for valid tokens", "[lexer][unit]") { SECTION("Tests for comments") { - REQUIRE(token_type("/ comment") == Token::COMMENT); - REQUIRE(token_type("/comment") == Token::COMMENT); + REQUIRE(check_token_type("/ comment", Token::COMMENT)); + REQUIRE(check_token_type("/comment", Token::COMMENT)); } SECTION("Tests for doubles") { - REQUIRE(token_type("27.52") == Token::DOUBLE); - REQUIRE(token_type("1.01325+5") == Token::DOUBLE); - REQUIRE(token_type("1") == Token::DOUBLE); - REQUIRE(token_type("-1.324e+10") == Token::DOUBLE); - REQUIRE(token_type("1-1") == Token::DOUBLE); - REQUIRE(token_type("1|100") == Token::FRACTION); - REQUIRE(token_type(".03") == Token::DOUBLE); - REQUIRE(token_type("12345e-2") == Token::DOUBLE); - REQUIRE(token_type("1|8.988e9") == Token::FRACTION); + REQUIRE(check_token_type("27.52", Token::DOUBLE)); + REQUIRE(check_token_type("1.01325+5", Token::DOUBLE)); + REQUIRE(check_token_type("1", Token::DOUBLE)); + REQUIRE(check_token_type("-1.324e+10", Token::DOUBLE)); + REQUIRE(check_token_type("1-1", Token::DOUBLE)); + REQUIRE(check_token_type("1|100", Token::FRACTION)); + REQUIRE(check_token_type(".03", Token::DOUBLE)); + REQUIRE(check_token_type("12345e-2", Token::DOUBLE)); + REQUIRE(check_token_type("1|8.988e9", Token::FRACTION)); } SECTION("Tests for units") { - REQUIRE(token_type("*a*") == Token::BASE_UNIT); - REQUIRE(token_type("*k*") == Token::INVALID_BASE_UNIT); - REQUIRE(token_type("planck") == Token::NEW_UNIT); - REQUIRE(token_type("mse-1") == Token::NEW_UNIT); - REQUIRE(token_type("mA/cm2") == Token::NEW_UNIT); - REQUIRE(token_type(" m2") == Token::UNIT_POWER); - REQUIRE(token_type(" m") == Token::UNIT); - REQUIRE(token_type(" m_2") == Token::UNIT); - REQUIRE(token_type(" m_unit2") == Token::UNIT); - REQUIRE(token_type("yotta-") == Token::PREFIX); + REQUIRE(check_token_type("*a*", Token::BASE_UNIT)); + REQUIRE(check_token_type("*k*", Token::INVALID_BASE_UNIT)); + REQUIRE(check_token_type("planck", Token::NEW_UNIT)); + REQUIRE(check_token_type("mse-1", Token::NEW_UNIT)); + REQUIRE(check_token_type("mA/cm2", Token::NEW_UNIT)); + REQUIRE(check_token_type(" m2", Token::UNIT_POWER)); + REQUIRE(check_token_type(" m", Token::UNIT)); + REQUIRE(check_token_type(" m_2", Token::UNIT)); + REQUIRE(check_token_type(" m_unit2", Token::UNIT)); + REQUIRE(check_token_type("yotta-", Token::PREFIX)); } SECTION("Tests for special characters") { - REQUIRE(token_type("-") == Token::UNIT_PROD); - REQUIRE(token_type(" / ") == Token::DIVISION); - REQUIRE(token_type("\n") == Token::NEWLINE); + REQUIRE(check_token_type("-", Token::UNIT_PROD)); + REQUIRE(check_token_type(" / ", Token::DIVISION)); + REQUIRE(check_token_type("\n", Token::NEWLINE)); } } From 7b23d8c40580eda1dcd45302acf9e8411f2b50c3 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 11 Mar 2020 14:10:28 +0100 Subject: [PATCH 244/871] Fixes for building with PGI compiler (BlueBrain/nmodl#274) - Add newline at the end of files to avoid PGI warnings - Fix PGI internal assertion external/nmodl/src/codegen/codegen_c_visitor.cpp", line 3969: internal error: assertion failed at: "../src/lower_init.c", line 7946 auto statement = ShadowUseStatement{lhs, "+=", rhs}; - Fixes for PGI build: set CMAKE_CXX11_STANDARD_COMPILE_OPTION to remove https://www.pgroup.com/userforum/viewtopic.php?f=4&t=7565&p=27308#p27308 - Use fork of spdlog because of fmtlib/fmtBlueBrain/nmodl#1581 - Add necessary compiler flags by default See BlueBrain/nmodl#271, it was merged into wrong branch. Fixes BlueBrain/nmodl#262 NMODL Repo SHA: BlueBrain/nmodl@7debe7095a0b5d082b7f42119e0be5075e576d1e --- cmake/nmodl/CMakeLists.txt | 10 ++++++++++ cmake/nmodl/CompilerHelper.cmake | 15 +++++++++++++++ src/nmodl/codegen/codegen_c_visitor.cpp | 4 ++-- .../codegen/codegen_compatibility_visitor.hpp | 1 + src/nmodl/codegen/codegen_cuda_visitor.hpp | 2 +- src/nmodl/codegen/codegen_omp_visitor.hpp | 2 +- src/nmodl/language/templates/ast/ast.cpp | 1 + src/nmodl/language/templates/ast/ast.hpp | 1 + src/nmodl/language/templates/ast/ast_decl.hpp | 1 + src/nmodl/language/templates/pybind/pyast.cpp | 1 + src/nmodl/language/templates/pybind/pyast.hpp | 1 + src/nmodl/language/templates/pybind/pysymtab.cpp | 1 + src/nmodl/language/templates/pybind/pyvisitor.cpp | 1 + src/nmodl/language/templates/pybind/pyvisitor.hpp | 1 + .../language/templates/visitors/ast_visitor.cpp | 3 ++- .../language/templates/visitors/ast_visitor.hpp | 3 ++- .../language/templates/visitors/json_visitor.cpp | 1 + .../language/templates/visitors/json_visitor.hpp | 3 ++- .../templates/visitors/lookup_visitor.cpp | 3 ++- .../templates/visitors/lookup_visitor.hpp | 3 ++- .../language/templates/visitors/nmodl_visitor.cpp | 3 ++- .../language/templates/visitors/nmodl_visitor.hpp | 3 ++- .../templates/visitors/symtab_visitor.cpp | 1 + .../templates/visitors/symtab_visitor.hpp | 1 + src/nmodl/language/templates/visitors/visitor.hpp | 3 ++- src/nmodl/lexer/modtoken.cpp | 2 +- src/nmodl/nmodl/nmodl.hpp | 2 +- src/nmodl/parser/verbatim_driver.hpp | 3 ++- src/nmodl/printer/json_printer.cpp | 2 +- src/nmodl/printer/nmodl_printer.cpp | 2 +- src/nmodl/utils/common_utils.hpp | 2 +- src/nmodl/utils/logger.cpp | 2 +- src/nmodl/utils/perf_stat.hpp | 2 +- src/nmodl/visitors/constant_folder_visitor.hpp | 2 +- src/nmodl/visitors/local_var_rename_visitor.hpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.hpp | 2 +- src/nmodl/visitors/neuron_solve_visitor.hpp | 2 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 3 ++- src/nmodl/visitors/rename_visitor.cpp | 2 +- src/nmodl/visitors/rename_visitor.hpp | 2 +- src/nmodl/visitors/sympy_conductance_visitor.hpp | 2 +- src/nmodl/visitors/var_usage_visitor.cpp | 2 +- src/nmodl/visitors/var_usage_visitor.hpp | 2 +- .../visitors/verbatim_var_rename_visitor.hpp | 2 +- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- test/nmodl/transpiler/utils/test_utils.hpp | 2 +- 47 files changed, 81 insertions(+), 34 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 2b61d9d13d..15ad015ba4 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -98,6 +98,16 @@ find_python_module(sympy 1.2 REQUIRED) find_python_module(textwrap 0.9 REQUIRED) find_python_module(yaml 3.12 REQUIRED) +# ============================================================================= +# Compiler specific flags for external submodules +# ============================================================================= +if(NMODL_PGI_COMPILER) + # PGI with llvm code generation doesn't have necessary assembly intrinsic headers + add_compile_definitions(EIGEN_DONT_VECTORIZE=1) + # nlohmann/json doesn't check for PGI compiler + add_compile_definitions(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK=1) +endif() + include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index ca40baf01b..74a457824a 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -5,3 +5,18 @@ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.9 (for c++11 support)") endif() endif() + +if(CMAKE_CXX_COMPILER_ID MATCHES "PGI") + set(NMODL_PGI_COMPILER TRUE) + + # CMake adds standard complaint PGI flag "-A" which breaks compilation of of spdlog and fmt + set(CMAKE_CXX11_STANDARD_COMPILE_OPTION --c++11) + set(CMAKE_CXX14_STANDARD_COMPILE_OPTION --c++14) + + # ~~~ + # PGI enables number of diagnostic messages by default classes which results into thousands of + # messages specifically for AST. Disable these verbose warnings for now. + # TODO : fix these warnings from template modification (#272) + # ~~~ + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --diag_suppress 1,82,111,115,177,186,611,997,1097,1625") +endif() diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 968396bd13..994bdfeb83 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3966,7 +3966,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { if (!conductance.ion.empty()) { auto lhs = "ion_di" + conductance.ion + "dv"; auto rhs = get_variable_name(conductance.variable); - auto statement = ShadowUseStatement{lhs, "+=", rhs}; + ShadowUseStatement statement{lhs, "+=", rhs}; auto text = process_shadow_update_statement(statement, BlockType::Equation); printer->add_line(text); } @@ -3995,7 +3995,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); rhs += "*1.e2/{}"_format(area); } - auto statement = ShadowUseStatement{lhs, "+=", rhs}; + ShadowUseStatement statement{lhs, "+=", rhs}; auto text = process_shadow_update_statement(statement, BlockType::Equation); printer->add_line(text); } diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index f78edbcc3d..fa89e869ef 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -17,6 +17,7 @@ #include "ast/ast.hpp" #include "codegen_naming.hpp" +#include "lexer/modtoken.hpp" #include "symtab/symbol_table.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index bc00c9b509..0558b39e27 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -118,4 +118,4 @@ class CodegenCudaVisitor: public CodegenCVisitor { /** @} */ // end of codegen_backends } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index 468010ce89..f75090914f 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -86,4 +86,4 @@ class CodegenOmpVisitor: public CodegenCVisitor { /** @} */ // end of codegen_backends } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 7b4fc0759d..be71f30b58 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -85,3 +85,4 @@ namespace ast { } // namespace ast } // namespace nmodl + diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 5c1e121ca4..0977a2a067 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -392,3 +392,4 @@ namespace ast { } // namespace ast } // namespace nmodl + diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index 5e5d205b9a..24ad0f4c61 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -60,3 +60,4 @@ using {{ node.class_name }}Vector = std::vector> AstLookupVisitor::lookup(Ast* node) { } } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl + diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index 564c8173dd..6c22e5f25b 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -70,4 +70,5 @@ class AstLookupVisitor: public Visitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl + diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 14ea2a3e28..90987f07b1 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -124,4 +124,5 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% endfor %} } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl + diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index b30b6e8564..0080e66831 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -71,4 +71,5 @@ class NmodlPrintVisitor: public Visitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl + diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp index 4daeddef3b..b5338e12c4 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.cpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.cpp @@ -40,3 +40,4 @@ void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { } // namespace visitor } // namespace nmodl + diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index 8793bdbfa7..6c6a4cc7f5 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -77,3 +77,4 @@ class SymtabVisitor: public AstVisitor { } // namespace visitor } // namespace nmodl + diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 789c2cf185..4b8873bed5 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -45,4 +45,5 @@ class Visitor { } // namespace visitor } // namespace nmodl -/** @} */ // end of visitor_classes \ No newline at end of file +/** @} */ // end of visitor_classes + diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index a06e997f64..8be6a38d6d 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -35,4 +35,4 @@ ModToken operator+(ModToken adder1, ModToken adder2) { return sum; } -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/nmodl/nmodl.hpp b/src/nmodl/nmodl/nmodl.hpp index 6d7ff2a38f..82abf643b6 100644 --- a/src/nmodl/nmodl/nmodl.hpp +++ b/src/nmodl/nmodl/nmodl.hpp @@ -16,4 +16,4 @@ #define EIGEN_DEFAULT_DENSE_INDEX_TYPE int #endif -#include "newton/newton.hpp" \ No newline at end of file +#include "newton/newton.hpp" diff --git a/src/nmodl/parser/verbatim_driver.hpp b/src/nmodl/parser/verbatim_driver.hpp index 03e328b033..7ff63e3bb3 100644 --- a/src/nmodl/parser/verbatim_driver.hpp +++ b/src/nmodl/parser/verbatim_driver.hpp @@ -52,4 +52,5 @@ class VerbatimDriver { } // namespace nmodl -int Verbatim_parse(nmodl::parser::VerbatimDriver*); \ No newline at end of file +int Verbatim_parse(nmodl::parser::VerbatimDriver*); + diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 3a3ea53585..82d857f70d 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -92,4 +92,4 @@ void JSONPrinter::flush() { } } // namespace printer -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index ad656462a9..b70eb51fba 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -52,4 +52,4 @@ void NMODLPrinter::pop_level() { } } // namespace printer -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 84e7372e7a..2761e07921 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -131,4 +131,4 @@ class SingletonRandomString { /** @} */ // end of utils } // namespace utils -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 4872f20da3..63e08f88cd 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -32,4 +32,4 @@ struct Logger { Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); logger_type logger = nmodl_logger.logger; -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index f24edd6f0b..42b6435dba 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -108,4 +108,4 @@ struct PerfStat { /** @} */ // end of utils } // namespace utils -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index 42a55ee9b1..f8f3a69edc 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -62,4 +62,4 @@ class ConstantFolderVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index bb9ae7dd3d..c870f73801 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -79,4 +79,4 @@ class LocalVarRenameVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 708cfbf2f8..fea7025d19 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -164,4 +164,4 @@ void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { } } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index 347a521387..c792c3c535 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -68,4 +68,4 @@ class LoopUnrollVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index 6b4bfe380c..99af768b31 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -71,4 +71,4 @@ class NeuronSolveVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index af9aa795dc..1c786099d8 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -66,4 +66,5 @@ void NmodlPrintVisitor::visit_element(const std::vector& elements, } } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl + diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 68a48085ed..40bc6b1c72 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -57,4 +57,4 @@ void RenameVisitor::visit_verbatim(ast::Verbatim* node) { } } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index ffe8ceeb47..0071e08c58 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -75,4 +75,4 @@ class RenameVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index 442d68ee1b..44ef95d63b 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -111,4 +111,4 @@ class SympyConductanceVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 0d4f8a37cf..553a6b52fd 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -29,4 +29,4 @@ bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { } } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index 50128bf709..a5574b0b32 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -50,4 +50,4 @@ class VarUsageVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index 62291ae111..217a9672b1 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -70,4 +70,4 @@ class VerbatimVarRenameVisitor: public AstVisitor { /** @} */ // end of visitor_classes } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index c81ce2c540..4204bf60db 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -28,4 +28,4 @@ void VerbatimVisitor::visit_verbatim(ast::Verbatim* node) { } } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/test/nmodl/transpiler/utils/test_utils.hpp b/test/nmodl/transpiler/utils/test_utils.hpp index 70a7647587..f35a72589d 100644 --- a/test/nmodl/transpiler/utils/test_utils.hpp +++ b/test/nmodl/transpiler/utils/test_utils.hpp @@ -13,4 +13,4 @@ namespace test_utils { std::string reindent_text(const std::string& text); } // namespace test_utils -} // namespace nmodl \ No newline at end of file +} // namespace nmodl From 2b595b3e2345a6fc52450b7abd90f1a5a871fd16 Mon Sep 17 00:00:00 2001 From: MikeG Date: Tue, 17 Mar 2020 07:23:06 -0400 Subject: [PATCH 245/871] Update fmt library include (BlueBrain/nmodl#273) * use * remove `ext/fmt/include` from global include list * add it to the objects that are using it NMODL Repo SHA: BlueBrain/nmodl@0812bfe37fb59a3512f8a3e84be4ee017217cf69 --- cmake/nmodl/CMakeLists.txt | 4 +--- src/nmodl/codegen/CMakeLists.txt | 1 + src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- src/nmodl/lexer/CMakeLists.txt | 2 +- src/nmodl/pybind/CMakeLists.txt | 2 +- src/nmodl/symtab/CMakeLists.txt | 1 + src/nmodl/symtab/symbol.cpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 15ad015ba4..32e69b26d0 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -112,7 +112,6 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src ${NMODL_PROJECT_SOURCE_DIR}/ext - ${NMODL_PROJECT_SOURCE_DIR}/ext/fmt/include ${NMODL_PROJECT_SOURCE_DIR}/ext/spdlog/include) # ============================================================================= @@ -125,8 +124,7 @@ add_subdirectory(ext/pybind11) # Include fmt library # ============================================================================= message(STATUS "INCLUDING FMT") -add_subdirectory(ext/fmt EXCLUDE_FROM_ALL) -set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) +add_subdirectory(${NMODL_PROJECT_SOURCE_DIR}/ext/fmt) # ============================================================================= # Include path from external libraries diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 8fc222fdac..f0e6f98c8a 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -24,6 +24,7 @@ set(CODEGEN_SOURCE_FILES # Codegen library and executable # ============================================================================= add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) +target_link_libraries(codegen fmt::fmt-header-only) add_dependencies(codegen lexer util visitor) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index ee2a497584..96215e71a2 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "fmt/format.h" +#include #include "codegen/codegen_acc_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index ab8f1ea177..62515f5af6 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -22,7 +22,7 @@ #include #include -#include "fmt/format.h" +#include #include "codegen/codegen_info.hpp" #include "codegen/codegen_naming.hpp" diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index fafff887f8..8f5c788062 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -9,7 +9,7 @@ #include #include -#include "fmt/format.h" +#include #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 5bfcb2aacb..00ee56af6f 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -199,7 +199,7 @@ add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) add_executable(units_lexer main_units.cpp) -target_link_libraries(lexer fmt::fmt) +target_link_libraries(lexer PRIVATE fmt::fmt-header-only) target_link_libraries(nmodl_lexer lexer util) target_link_libraries(c_lexer lexer util) target_link_libraries(units_lexer lexer util) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index f783dadf58..7db195ef1f 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -38,7 +38,7 @@ add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) -target_link_libraries(_nmodl PRIVATE fmt::fmt) +target_link_libraries(_nmodl PRIVATE fmt::fmt-header-only) add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index dc9323953e..42ba1896e3 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -16,6 +16,7 @@ set(SYMTAB_HEADERS # ============================================================================= add_library(symtab_obj OBJECT ${SYMTAB_SOURCES} ${SYMTAB_HEADERS}) set_property(TARGET symtab_obj PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(symtab_obj PRIVATE fmt::fmt-header-only) add_dependencies(symtab_obj lexer_obj) diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 8f041a992d..5a8ce46b21 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "fmt/format.h" +#include #include "symtab/symbol.hpp" From 9d4aa24fb71c35dcae85be2e89efbba5af9bf16c Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 20 Mar 2020 10:06:31 +0100 Subject: [PATCH 246/871] Fixes for integrating sympy based solvers with CoreNEURON (BlueBrain/nmodl#278) * Install missing Eigen headers required for newton solver * Prepare necessary components under CMAKE_BINARY_DIR * Install python files under nmodl from top level source/nmodl * Add dependency with python module NMODL Repo SHA: BlueBrain/nmodl@730c4e552ebe7cd64095138725e82ed5797a2ffc --- src/nmodl/nmodl/CMakeLists.txt | 5 +++++ src/nmodl/pybind/CMakeLists.txt | 12 ++++++++++-- src/nmodl/solver/CMakeLists.txt | 12 ++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index b53dd30910..57b3bd5486 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -10,6 +10,11 @@ set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) add_executable(nmodl ${NMODL_SOURCE_FILES}) target_link_libraries(nmodl printer codegen visitor symtab util lexer) +# ============================================================================= +# Add dependency with nmodl pytnon module (for consumer projects) +# ============================================================================= +add_dependencies(nmodl _nmodl) + # ============================================================================= # Install executable # ============================================================================= diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 7db195ef1f..a35a52ca2b 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,6 +3,9 @@ # ============================================================================= set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) +# build nmodl python module under lib/python/nmodl +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nmodl) + foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) @@ -49,8 +52,13 @@ add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} DEPENDS ${NMODL_PYTHON_FILES_IN} $ COMMENT "-- COPYING NMODL PYTHON FILES --") +# ============================================================================= +# Copy python binding components into build directory +# ============================================================================= +file(GLOB NMODL_PYTHON_HELPER_FILES "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") +file(COPY ${NMODL_PYTHON_HELPER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/lib/python/nmodl/) + # ============================================================================= # Install python binding components # ============================================================================= -install(TARGETS _nmodl DESTINATION lib/python/nmodl) -install(DIRECTORY ${PROJECT_BINARY_DIR}/nmodl DESTINATION lib/python/ FILES_MATCHING PATTERN "*.py") +install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION lib) diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index c88c944de6..2653e6e082 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -4,13 +4,13 @@ set(SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) # ============================================================================= -# Solver target for dependencies (only headers for now) +# Copy necessary headers to build directory # ============================================================================= +file(GLOB NMODL_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/newton/*.h*") +file(COPY ${NMODL_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/newton/) +file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY_DIR}/include/) # ============================================================================= -# Install headers +# Install solver headers and eigen from include # ============================================================================= -install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/newton - DESTINATION include - FILES_MATCHING - PATTERN "*.h*") +install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION include) From 69506cec31a9bae32f39037f4ac7a36b1315d89d Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Sat, 28 Mar 2020 06:58:28 +0100 Subject: [PATCH 247/871] Undid changes from PR BlueBrain/nmodl#273 (BlueBrain/nmodl#279) * Undid changes from PR BlueBrain/nmodl#273 Unfortunately these changes did not turn out to be compatible with cmake 3.10 and all attempted workarounds failed. In the interest of time I am mostly rolling back these changes for now. * Find precise cmake version on azure * Updated azure pipeline to force cmake 3.10 for testing Also, updated hpc-coding conventions and formatting of cmake files NMODL Repo SHA: BlueBrain/nmodl@6488adfa8a0a79fc265780ebc23a1d2470377965 --- cmake/nmodl/CMakeLists.txt | 60 ++++---- cmake/nmodl/Catch.cmake | 65 +++++---- cmake/nmodl/CatchAddTests.cmake | 48 ++----- cmake/nmodl/ClangTidyHelper.cmake | 23 +-- cmake/nmodl/FindPythonModule.cmake | 22 ++- cmake/nmodl/GitRevision.cmake | 24 ++-- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/codegen/CMakeLists.txt | 1 - src/nmodl/language/CMakeLists.txt | 25 ++-- src/nmodl/lexer/CMakeLists.txt | 200 +++++++++++++-------------- src/nmodl/nmodl/CMakeLists.txt | 9 +- src/nmodl/printer/CMakeLists.txt | 9 +- src/nmodl/pybind/CMakeLists.txt | 47 ++++--- src/nmodl/symtab/CMakeLists.txt | 8 +- test/nmodl/transpiler/CMakeLists.txt | 97 ++++++------- 15 files changed, 307 insertions(+), 333 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 32e69b26d0..46c9495a7a 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -46,33 +46,34 @@ find_package(BISON 3.0 REQUIRED) # ============================================================================= # HPC Coding Conventions # ============================================================================= -set(NMODL_ClangFormat_EXCLUDES_RE ".*/ext/.*$$" - CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" - FORCE) -set(NMODL_CMakeFormat_EXCLUDES_RE ".*/ext/.*$$" - CACHE STRING "list of regular expressions to exclude CMake files from formatting" - FORCE) -set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen - CACHE STRING "list of CMake targets to build before formatting C++ code" - FORCE) +set(NMODL_ClangFormat_EXCLUDES_RE + ".*/ext/.*$$" + CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) +set(NMODL_CMakeFormat_EXCLUDES_RE + ".*/ext/.*$$" + CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) +set(NMODL_ClangFormat_DEPENDENCIES + pyastgen parser-gen + CACHE STRING "list of CMake targets to build before formatting C++ code" FORCE) add_subdirectory(cmake/hpc-coding-conventions/cpp) # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) # ============================================================================= -add_custom_target(nb-format - jupyter - nbconvert - --to - notebook - --execute - --inplace - --ExecutePreprocessor.timeout=360 - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" - && - clean_ipynb - --keep-output - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") +add_custom_target( + nb-format + jupyter + nbconvert + --to + notebook + --execute + --inplace + --ExecutePreprocessor.timeout=360 + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" + && + clean_ipynb + --keep-output + "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") # ============================================================================= # Include cmake modules @@ -108,11 +109,10 @@ if(NMODL_PGI_COMPILER) add_compile_definitions(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK=1) endif() -include_directories(${NMODL_PROJECT_SOURCE_DIR} - ${NMODL_PROJECT_SOURCE_DIR}/src - ${PROJECT_BINARY_DIR}/src - ${NMODL_PROJECT_SOURCE_DIR}/ext - ${NMODL_PROJECT_SOURCE_DIR}/ext/spdlog/include) +include_directories( + ${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src + ${NMODL_PROJECT_SOURCE_DIR}/ext ${NMODL_PROJECT_SOURCE_DIR}/ext/fmt/include + ${NMODL_PROJECT_SOURCE_DIR}/ext/spdlog/include) # ============================================================================= # Include pybind11 @@ -124,7 +124,8 @@ add_subdirectory(ext/pybind11) # Include fmt library # ============================================================================= message(STATUS "INCLUDING FMT") -add_subdirectory(${NMODL_PROJECT_SOURCE_DIR}/ext/fmt) +add_subdirectory(${NMODL_PROJECT_SOURCE_DIR}/ext/fmt EXCLUDE_FROM_ALL) +set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) # ============================================================================= # Include path from external libraries @@ -184,7 +185,8 @@ add_subdirectory(src/solver) # Memory checker options and add tests # ============================================================================= find_program(MEMORYCHECK_COMMAND valgrind) -set(MEMORYCHECK_COMMAND_OPTIONS "--trace-children=yes \ +set(MEMORYCHECK_COMMAND_OPTIONS + "--trace-children=yes \ --leak-check=full \ --track-origins=yes \ --show-possibly-lost=no") diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake index d0b81ffe50..f33d7cdd24 100644 --- a/cmake/nmodl/Catch.cmake +++ b/cmake/nmodl/Catch.cmake @@ -94,11 +94,8 @@ same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. # ------------------------------------------------------------------------------ function(catch_discover_tests TARGET) - cmake_parse_arguments("" - "" - "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" - "TEST_SPEC;EXTRA_ARGS;PROPERTIES" - ${ARGN}) + cmake_parse_arguments("" "" "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" + "TEST_SPEC;EXTRA_ARGS;PROPERTIES" ${ARGN}) if(NOT _WORKING_DIRECTORY) set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") @@ -109,43 +106,45 @@ function(catch_discover_tests TARGET) # Generate a unique name based on the extra arguments string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") - string(SUBSTRING ${args_hash} - 0 - 7 - args_hash) + string(SUBSTRING ${args_hash} 0 7 args_hash) # Define rule to generate test list for aforementioned test executable set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") - get_property(crosscompiling_emulator TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR) - add_custom_command(TARGET - ${TARGET} - POST_BUILD - BYPRODUCTS - "${ctest_tests_file}" - COMMAND "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D - "TEST_EXECUTABLE=$" -D - "TEST_EXECUTOR=${crosscompiling_emulator}" -D - "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D "TEST_SPEC=${_TEST_SPEC}" - -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D "TEST_PROPERTIES=${_PROPERTIES}" - -D "TEST_PREFIX=${_TEST_PREFIX}" -D "TEST_SUFFIX=${_TEST_SUFFIX}" -D - "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" -P - "${_CATCH_DISCOVER_TESTS_SCRIPT}" - VERBATIM) - - file(WRITE "${ctest_include_file}" - "if(EXISTS \"${ctest_tests_file}\")\n" - " include(\"${ctest_tests_file}\")\n" - "else()\n" - " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" - "endif()\n") + get_property( + crosscompiling_emulator + TARGET ${TARGET} + PROPERTY CROSSCOMPILING_EMULATOR) + add_custom_command( + TARGET ${TARGET} POST_BUILD + BYPRODUCTS "${ctest_tests_file}" + COMMAND + "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D "TEST_EXECUTABLE=$" -D + "TEST_EXECUTOR=${crosscompiling_emulator}" -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D + "TEST_SPEC=${_TEST_SPEC}" -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D + "TEST_PROPERTIES=${_PROPERTIES}" -D "TEST_PREFIX=${_TEST_PREFIX}" -D + "TEST_SUFFIX=${_TEST_SUFFIX}" -D "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" + -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" + VERBATIM) + + file( + WRITE "${ctest_include_file}" + "if(EXISTS \"${ctest_tests_file}\")\n" " include(\"${ctest_tests_file}\")\n" "else()\n" + " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" "endif()\n") if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") # Add discovered tests to directory TEST_INCLUDE_FILES - set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") + set_property( + DIRECTORY + APPEND + PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") else() # Add discovered tests as directory TEST_INCLUDE_FILE if possible - get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET) + get_property( + test_include_file_set + DIRECTORY + PROPERTY TEST_INCLUDE_FILE + SET) if(NOT ${test_include_file_set}) set_property(DIRECTORY PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}") else() diff --git a/cmake/nmodl/CatchAddTests.cmake b/cmake/nmodl/CatchAddTests.cmake index 2b4cd9b22f..79aa6d19fe 100644 --- a/cmake/nmodl/CatchAddTests.cmake +++ b/cmake/nmodl/CatchAddTests.cmake @@ -19,15 +19,14 @@ function(add_command NAME) set(_args "${_args} ${_arg}") endif() endforeach() - set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE) + set(script + "${script}${NAME}(${_args})\n" + PARENT_SCOPE) endfunction() macro(_add_catch_test_labels LINE) # convert to list of tags - string(REPLACE "][" - "]\\;[" - tags - ${line}) + string(REPLACE "][" "]\\;[" tags ${line}) add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES LABELS "${tags}") endmacro() @@ -35,24 +34,13 @@ endmacro() macro(_add_catch_test LINE) set(test ${line}) # use escape commas to handle properly test cases with commans inside the name - string(REPLACE "," - "\\," - test_name - ${test}) + string(REPLACE "," "\\," test_name ${test}) # ...and add to script - add_command(add_test - "${prefix}${test}${suffix}" - ${TEST_EXECUTOR} - "${TEST_EXECUTABLE}" - "${test_name}" - ${extra_args}) + add_command(add_test "${prefix}${test}${suffix}" ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" + "${test_name}" ${extra_args}) - add_command(set_tests_properties - "${prefix}${test}${suffix}" - PROPERTIES - WORKING_DIRECTORY - "${TEST_WORKING_DIR}" - ${properties}) + add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES WORKING_DIRECTORY + "${TEST_WORKING_DIR}" ${properties}) list(APPEND tests "${prefix}${test}${suffix}") endmacro() @@ -60,9 +48,10 @@ endmacro() if(NOT EXISTS "${TEST_EXECUTABLE}") message(FATAL_ERROR "Specified test executable '${TEST_EXECUTABLE}' does not exist") endif() -execute_process(COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests - OUTPUT_VARIABLE output - RESULT_VARIABLE result) +execute_process( + COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests + OUTPUT_VARIABLE output + RESULT_VARIABLE result) # Catch --list-test-names-only reports the number of tests, so 0 is... surprising if(${result} EQUAL 0) message(WARNING "Test executable '${TEST_EXECUTABLE}' contains no tests!\n") @@ -71,10 +60,7 @@ elseif(${result} LESS 0) " Result: ${result}\n" " Output: ${output}\n") endif() -string(REPLACE "\n" - ";" - output - "${output}") +string(REPLACE "\n" ";" output "${output}") set(test) set(tags_regex "(\\[([^\\[]*)\\])+$") @@ -83,11 +69,7 @@ foreach(line ${output}) # lines without leading whitespaces are catch output not tests if(${line} MATCHES "^[ \t]+") # strip leading spaces and tabs - string(REGEX - REPLACE "^[ \t]+" - "" - line - ${line}) + string(REGEX REPLACE "^[ \t]+" "" line ${line}) if(${line} MATCHES "${tags_regex}") _add_catch_test_labels(${line}) diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake index 3c4b892825..91c7389f30 100644 --- a/cmake/nmodl/ClangTidyHelper.cmake +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -1,21 +1,22 @@ if(CMAKE_VERSION VERSION_GREATER "3.5") - set(ENABLE_CLANG_TIDY OFF CACHE BOOL "Add clang-tidy automatically to builds") + set(ENABLE_CLANG_TIDY + OFF + CACHE BOOL "Add clang-tidy automatically to builds") if(ENABLE_CLANG_TIDY) find_program(CLANG_TIDY_EXE NAMES "clang-tidy") if(CLANG_TIDY_EXE) message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set( - CLANG_TIDY_CHECKS - "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" - ) - set( - CMAKE_CXX_CLANG_TIDY - "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" - CACHE STRING "" - FORCE) + set(CLANG_TIDY_CHECKS + "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" + ) + set(CMAKE_CXX_CLANG_TIDY + "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" + CACHE STRING "" FORCE) else() message(AUTHOR_WARNING "clang-tidy not found!") - set(CMAKE_CXX_CLANG_TIDY "" CACHE STRING "" FORCE) # delete it + set(CMAKE_CXX_CLANG_TIDY + "" + CACHE STRING "" FORCE) # delete it endif() endif() endif() diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index 7ad83a9897..0af45ff948 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -37,14 +37,15 @@ macro(find_python_module module) OUTPUT_VARIABLE _${module}_location ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) if(NOT _${module}_status) - set(${module_upper}_LOCATION ${_${module}_location} + set(${module_upper}_LOCATION + ${_${module}_location} CACHE STRING "Location of Python module ${module}") # retrieve version - execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import ${module}; print(${module}.__version__)" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_version - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND "${PYTHON_EXECUTABLE}" "-c" "import ${module}; print(${module}.__version__)" + RESULT_VARIABLE _${module}_status + OUTPUT_VARIABLE _${module}_version + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) set(_${module_upper}_VERSION_MATCH TRUE) if(NOT _${module}_status) @@ -58,12 +59,9 @@ macro(find_python_module module) endif() endif() - find_package_handle_standard_args(${module} - REQUIRED_VARS - ${module_upper}_LOCATION - _${module_upper}_VERSION_MATCH - VERSION_VAR - ${module_upper}_VERSION_STRING) + find_package_handle_standard_args( + ${module} REQUIRED_VARS ${module_upper}_LOCATION _${module_upper}_VERSION_MATCH VERSION_VAR + ${module_upper}_VERSION_STRING) if(NOT ${module}_FIND_OPTIONAL AND NOT _${module_upper}_VERSION_MATCH) message(FATAL_ERROR "Missing python module ${module}") endif() diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index 2bbf2ce135..e1b190009d 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -6,23 +6,21 @@ find_package(Git) if(GIT_FOUND) # get last commit sha1 - execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_SHA1 - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_SHA1 + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # get last commit date - execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_DATE - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_REVISION_DATE + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # remove extra double quotes - string(REGEX - REPLACE "\"" - "" - GIT_REVISION_DATE - "${GIT_REVISION_DATE}") + string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") else() diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index a1813c26dd..0673881f7e 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit a1813c26dd75b8360b08893be5b2ecb485b816c4 +Subproject commit 0673881f7e7faa6e1d57baaef5a8b3316e7ecac0 diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index f0e6f98c8a..8fc222fdac 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -24,7 +24,6 @@ set(CODEGEN_SOURCE_FILES # Codegen library and executable # ============================================================================= add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) -target_link_libraries(codegen fmt::fmt-header-only) add_dependencies(codegen lexer util visitor) diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 95144311fc..b0134cddaf 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -4,7 +4,7 @@ set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) file(GLOB_RECURSE TEMPLATE_FILES "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") + "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") file(GLOB PYCODE "${NMODL_PROJECT_SOURCE_DIR}/src/language/*.py") @@ -15,18 +15,17 @@ if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) endif(nmodl_ClangFormat_OPTIONS) endif() -add_custom_command(OUTPUT ${AUTO_GENERATED_FILES} - COMMAND ${PYTHON_EXECUTABLE} - ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${CODE_GENERATOR_OPTS} - --base-dir ${PROJECT_BINARY_DIR}/src - --clang-format-opts="--style=file" - WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} - COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +add_custom_command( + OUTPUT ${AUTO_GENERATED_FILES} + COMMAND + ${PYTHON_EXECUTABLE} ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml + DEPENDS ${PYCODE} + DEPENDS ${TEMPLATE_FILES} + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") unset(CODE_GENERATOR_OPTS) # ============================================================================= diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 00ee56af6f..31edd62de7 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -16,10 +16,10 @@ set(BISON_GENERATED_SOURCE_FILES set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp @@ -29,12 +29,12 @@ set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/modl.h @@ -62,134 +62,122 @@ set(LEXER_SOURCE_FILES # ============================================================================= # Directories for parsers (as they need to be in separate directories) # ============================================================================= -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl - ${PROJECT_BINARY_DIR}/src/parser/diffeq - ${PROJECT_BINARY_DIR}/src/parser/c - ${PROJECT_BINARY_DIR}/src/parser/unit) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR}/src/parser/diffeq + ${PROJECT_BINARY_DIR}/src/parser/c ${PROJECT_BINARY_DIR}/src/parser/unit) # ============================================================================= # Lexer & Parser commands # ============================================================================= # command to generate nmodl parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen - COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh + ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen + COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") # command to generate verbatim parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") # command to generate differential equation parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh + ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") # command to generate C (11) parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/location.hh - ${PROJECT_BINARY_DIR}/src/parser/c/position.hh - ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/c/location.hh + ${PROJECT_BINARY_DIR}/src/parser/c/position.hh + ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") # command to generate Units parser -add_custom_command(OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh - COMMAND ${BISON_EXECUTABLE} - ARGS -d - -o - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") +add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh + ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh + COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") # command to generate nmodl lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp - COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp + COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") # command to generate verbatim lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l - COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") # command to generate differential equation lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll - COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") # command to generate C (11) lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll - COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") # command to generate Units lexer -add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll - COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp + ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp + COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") # ============================================================================= # Libraries & executables # ============================================================================= -add_library(lexer_obj - OBJECT - ${LEXER_SOURCE_FILES} - ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES} - ${UNIT_SOURCE_FILES}) +add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} + ${AST_SOURCE_FILES} ${UNIT_SOURCE_FILES}) set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) @@ -199,7 +187,7 @@ add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) add_executable(units_lexer main_units.cpp) -target_link_libraries(lexer PRIVATE fmt::fmt-header-only) +target_link_libraries(lexer fmt::fmt) target_link_libraries(nmodl_lexer lexer util) target_link_libraries(c_lexer lexer util) target_link_libraries(units_lexer lexer util) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 57b3bd5486..7ee64b9300 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -8,7 +8,14 @@ set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) # Add executables # ============================================================================= add_executable(nmodl ${NMODL_SOURCE_FILES}) -target_link_libraries(nmodl printer codegen visitor symtab util lexer) +target_link_libraries( + nmodl + printer + codegen + visitor + symtab + util + lexer) # ============================================================================= # Add dependency with nmodl pytnon module (for consumer projects) diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 18aad38487..5a44153dab 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -2,12 +2,9 @@ # Printer sources # ============================================================================= set(PRINTER_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) + ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) # ============================================================================= # Printer library diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index a35a52ca2b..72b3688353 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -6,14 +6,20 @@ set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) # build nmodl python module under lib/python/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nmodl) -foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) +foreach( + file + ast.py + dsl.py + ode.py + symtab.py + visitor.py + __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() set(PYNMODL_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) @@ -27,30 +33,31 @@ set(PYNMODL_HEADERS # Note that LTO causes link time errors with GCC 8. To avoid this, we disable LTO for pybind using # NO_EXTRAS. See #266. -pybind11_add_module(_nmodl - NO_EXTRAS - ${PYNMODL_HEADERS} - ${PYNMODL_SOURCES} - $ - $ - $ - $ - $) +pybind11_add_module( + _nmodl + NO_EXTRAS + ${PYNMODL_HEADERS} + ${PYNMODL_SOURCES} + $ + $ + $ + $ + $) add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) -target_link_libraries(_nmodl PRIVATE fmt::fmt-header-only) +target_link_libraries(_nmodl PRIVATE fmt::fmt) add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) -add_custom_command(OUTPUT ${NMODL_PYTHON_FILES_OUT} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl - ${PROJECT_BINARY_DIR}/nmodl - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ - ${PROJECT_BINARY_DIR}/nmodl - DEPENDS ${NMODL_PYTHON_FILES_IN} $ - COMMENT "-- COPYING NMODL PYTHON FILES --") +add_custom_command( + OUTPUT ${NMODL_PYTHON_FILES_OUT} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl + ${PROJECT_BINARY_DIR}/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl + DEPENDS ${NMODL_PYTHON_FILES_IN} $ + COMMENT "-- COPYING NMODL PYTHON FILES --") # ============================================================================= # Copy python binding components into build directory diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index 42ba1896e3..3d26aad7b3 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -2,13 +2,11 @@ # Visitor sources # ============================================================================= set(SYMTAB_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp) set(SYMTAB_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp) # ============================================================================= @@ -16,8 +14,6 @@ set(SYMTAB_HEADERS # ============================================================================= add_library(symtab_obj OBJECT ${SYMTAB_SOURCES} ${SYMTAB_HEADERS}) set_property(TARGET symtab_obj PROPERTY POSITION_INDEPENDENT_CODE ON) -target_link_libraries(symtab_obj PRIVATE fmt::fmt-header-only) - add_dependencies(symtab_obj lexer_obj) add_library(symtab STATIC $) diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index d232c065e3..c20adec606 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -8,10 +8,8 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= # Common input data library # ============================================================================= -add_library(test_util - STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) +add_library(test_util STATIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) # ============================================================================= # Common input data library @@ -24,28 +22,29 @@ add_library(config STATIC ${PROJECT_BINARY_DIR}/src/config/config.cpp) add_executable(testmodtoken modtoken/modtoken.cpp) add_executable(testlexer lexer/tokens.cpp) add_executable(testparser parser/parser.cpp) -add_executable(testvisitor - visitor/main.cpp - visitor/constant_folder.cpp - visitor/defuse_analyze.cpp - visitor/inline.cpp - visitor/json.cpp - visitor/kinetic_block.cpp - visitor/localize.cpp - visitor/lookup.cpp - visitor/loop_unroll.cpp - visitor/misc.cpp - visitor/neuron_solve.cpp - visitor/nmodl.cpp - visitor/perf.cpp - visitor/rename.cpp - visitor/solve_block.cpp - visitor/steadystate.cpp - visitor/sympy_conductance.cpp - visitor/sympy_solver.cpp - visitor/units.cpp - visitor/var_usage.cpp - visitor/verbatim.cpp) +add_executable( + testvisitor + visitor/main.cpp + visitor/constant_folder.cpp + visitor/defuse_analyze.cpp + visitor/inline.cpp + visitor/json.cpp + visitor/kinetic_block.cpp + visitor/localize.cpp + visitor/lookup.cpp + visitor/loop_unroll.cpp + visitor/misc.cpp + visitor/neuron_solve.cpp + visitor/nmodl.cpp + visitor/perf.cpp + visitor/rename.cpp + visitor/solve_block.cpp + visitor/steadystate.cpp + visitor/sympy_conductance.cpp + visitor/sympy_solver.cpp + visitor/units.cpp + visitor/var_usage.cpp + visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) @@ -55,7 +54,14 @@ add_executable(testunitparser units/parser.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) target_link_libraries(testparser lexer util test_util visitor) -target_link_libraries(testvisitor visitor symtab lexer util test_util printer) +target_link_libraries( + testvisitor + visitor + symtab + lexer + util + test_util + printer) target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) @@ -65,23 +71,20 @@ target_link_libraries(testunitparser lexer test_util config) # Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, # else use the normal add_test method # ============================================================================= -foreach(test_name - testmodtoken - testlexer - testparser - testvisitor - testprinter - testsymtab - testnewton - testunitlexer - testunitparser) +foreach( + test_name + testmodtoken + testlexer + testparser + testvisitor + testprinter + testsymtab + testnewton + testunitlexer + testunitparser) if(${CMAKE_VERSION} VERSION_GREATER "3.10") - catch_discover_tests(${test_name} - TEST_PREFIX - "${test_name}/" - PROPERTIES - ENVIRONMENT + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) else() add_test(NAME ${test_name} COMMAND ${test_name}) @@ -95,11 +98,9 @@ endforeach() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME Ode - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) -add_test(NAME Pybind - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) +add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) +add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) foreach(test_name Ode Pybind) - set_tests_properties(${test_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) + set_tests_properties(${test_name} PROPERTIES ENVIRONMENT + PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) endforeach() From b0be5bee8d58006ba8734f05cd93068152cac1da Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Thu, 2 Apr 2020 10:24:18 +0200 Subject: [PATCH 248/871] =?UTF-8?q?sanity:=20remove=20backticks,=20default?= =?UTF-8?q?=20to=20$(=E2=80=A6)=20(BlueBrain/nmodl#282)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * sanity: remove backticks, default to $(…) This is POSIX compatible, should work everywhere and is cleaner to read/modify since it is a proper delimiter and can be nested. * Exorcise Travis, too NMODL Repo SHA: BlueBrain/nmodl@5b00b4bb81f138db5e3a68354d88ffbfe68f5674 --- .travis.yml | 2 +- docker/recipe/entrypoint | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ed1adbcebb..cd4ea07be1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -86,7 +86,7 @@ install: script: - echo "------- Build, Test and Install -------" - mkdir build && cd build - - cmake .. -DPYTHON_EXECUTABLE=`which python3` -DCMAKE_INSTALL_PREFIX=$HOME/nmodl + - cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl - make -j 2 - make test - make install diff --git a/docker/recipe/entrypoint b/docker/recipe/entrypoint index 61e8cf1a1d..9082326f80 100755 --- a/docker/recipe/entrypoint +++ b/docker/recipe/entrypoint @@ -14,7 +14,7 @@ if [ "x$GROUP_ID" = x -o "x$USER_ID" = x ] ;then fi # Create fake user -CUR_GROUP=`grep ":${GROUP_ID}:" /etc/group | cut -d: -f1` +CUR_GROUP=$(grep ":${GROUP_ID}:" /etc/group | cut -d: -f1) if [ "x$CUR_GROUP" != x ] ;then groupmod --new-name dummy "$CUR_GROUP" else From eca03fd9158b6b7a0d3a86d6f2f814633779273d Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Thu, 2 Apr 2020 20:55:04 +0200 Subject: [PATCH 249/871] Remove the jinja macro "override(node)" (BlueBrain/nmodl#277) * Remove the jinja macro "override(node)" - override(node) is a macro to add override only where needed and copied from virtual(node). - However, override is required also for derived classes were virtual is not needed (we do not want a virtual table). It was used only for get_token and set_name, 2 methods that are in ast_commons and, thus, must be always overridden. - For these reasons override is actually superfluous and does not print "override" for LinearBlock where it should (because LinearBlock is a virtual class still). Removing it and hardcoding override in the 2 functions erases the problem and simplifies the code. NMODL Repo SHA: BlueBrain/nmodl@091f0bd047556c7065d6af0f3d5c6c26736ac264 --- src/nmodl/language/templates/ast/ast.hpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 0977a2a067..824056df1d 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -32,10 +32,7 @@ {% if node.is_abstract %} virtual {% endif %} {% endmacro %} -{# add override qualifier if node is not an abstract class #} -{% macro override(node) %} - {% if not node.is_abstract %} override {% endif %} -{% endmacro %} + namespace nmodl { @@ -202,7 +199,7 @@ namespace ast { * * @return pointer to token if exist otherwise nullptr */ - {{ virtual(node) }}ModToken* get_token(){{ override(node) }} { + {{ virtual(node) }}ModToken* get_token() override { return token.get(); } {% endif %} @@ -258,7 +255,7 @@ namespace ast { * * \sa Ast::get_node_type_name Ast::get_node_name */ - {{ virtual(node) }}void set_name(std::string name){{ override(node) }} { + {{ virtual(node) }}void set_name(std::string name) override { value->set(name); } {% endif %} @@ -392,4 +389,3 @@ namespace ast { } // namespace ast } // namespace nmodl - From be08bedc62087a6fc83ba91e099713abecab8f6f Mon Sep 17 00:00:00 2001 From: katta Date: Thu, 2 Apr 2020 14:19:22 +0200 Subject: [PATCH 250/871] Bugfix: KineticBlockVisitor generates CONSERVE statement in DERIVATIVE block and causes segfault - In KineticBlockVisitor, visit_kinetic_block sets up a vector that is in turn read by visit_conserve. In case the conserve statement is not inside a kinetic_block the vector is never set and a wild seg. fault appears. - Now the KineticBlockVisitor visits only the kinetic blocks. - Add warning about CONSERVE statements in DERIVATIVE blocks: The KineticBlockVisitor transforms Kinetic blocks into Derivative ones. Conserve statements are moved too. After, the sympySolverVisitor takes care of replacing them. The ast generated after the kineticBlockVisitor is not standard (since CONSERVE statements are not allowed in a Derivative block). If the user prints to nmodl this temporary ast, the nmodl file is non-standard. It works with nmodl but it does not work with mod2c. The warning enlights the user about this. fixes BlueBrain/nmodl#284 NMODL Repo SHA: BlueBrain/nmodl@523e3549fc38e343e8508f9537a636ad371c93b4 --- src/nmodl/nmodl/main.cpp | 11 +++++-- src/nmodl/visitors/kinetic_block_visitor.cpp | 30 +++++++++++++------- src/nmodl/visitors/kinetic_block_visitor.hpp | 6 ++++ 3 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 6241b1f187..0b3a5f7071 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -354,9 +354,16 @@ int main(int argc, const char* argv[]) { /// in global scope { logger->info("Running KINETIC block visitor"); - KineticBlockVisitor().visit_program(ast.get()); + auto kineticBlockVisitor = KineticBlockVisitor(); + kineticBlockVisitor.visit_program(ast.get()); SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("kinetic")); + const auto filename = filepath("kinetic"); + ast_to_nmodl(ast.get(), filename); + if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) { + logger->warn( + "{} presents non-standard CONSERVE statements in DERIVATIVE blocks. Use it only for debugging/developing"_format( + filename)); + } } { diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 3305c38d6d..2cd42a356a 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -12,6 +12,7 @@ #include "utils/logger.hpp" #include "utils/string_utils.hpp" #include "visitor_utils.hpp" +#include "visitors/lookup_visitor.hpp" namespace nmodl { @@ -101,6 +102,7 @@ std::shared_ptr create_expr(const std::string& str_expr) { } void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { + ++conserve_statement_count; // rewrite CONSERVE statement in form x = ... // where x was the last state var on LHS, and whose ODE should later be replaced with this // equation note: CONSERVE statement "implicitly takes into account COMPARTMENT factors on LHS" @@ -124,16 +126,20 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { ")"; } - auto lhs = create_expr(conserve_equation_statevar); - // set react (lhs) of CONSERVE to the state variable whose ODE should be replaced - node->set_react(std::move(lhs)); - // set expr (rhs) of CONSERVE to the equation that should replace the ODE - auto rhs = create_expr(conserve_equation_str); - // note: this is still a valid (and equivalent) CONSERVE statement. + // note: The following 4 lines result in a still valid (and equivalent) CONSERVE statement. // later this block will become a DERIVATIVE block where it is no longer valid - // to have a CONSERVE statement, but it is parsed without issues, and + // to have a CONSERVE statement. Parsing the equivalent nmodl in between the + // kinetic visitor and the sympysolvervisitor in presence of a conserve statement + // should result in an error since we do not want to add new functionalities to the language. // the SympySolver will use to it replace the ODE (to replicate what neuron does) - node->set_expr(std::move(rhs)); + auto statement = create_statement("CONSERVE " + conserve_equation_statevar + " = " + + conserve_equation_str); + auto expr = std::dynamic_pointer_cast(statement); + // set react (lhs) of CONSERVE to the state variable whose ODE should be replaced + node->set_react(std::move(expr->get_react())); + // set expr (rhs) of CONSERVE to the equation that should replace the ODE + node->set_expr(std::move(expr->get_expr())); + logger->debug("KineticBlockVisitor :: --> {}", to_nmodl(node)); } @@ -361,7 +367,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { additive_terms = std::vector(state_var_count); i_statement = 0; - // construct stoichiometric matrices and fluxes + // construct stochiometric matrices and fluxes node->visit_children(*this); // number of reaction statements @@ -424,6 +430,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { } void KineticBlockVisitor::visit_program(ast::Program* node) { + conserve_statement_count = 0; statements_to_remove.clear(); current_statement_block = nullptr; @@ -459,8 +466,11 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { } } + auto kineticBlockNodes = AstLookupVisitor().lookup(node, ast::AstNodeType::KINETIC_BLOCK); // replace reaction statements within each kinetic block with equivalent ODEs - node->visit_children(*this); + for (const auto& ii: kineticBlockNodes) { + ii->accept(*this); + } // change KINETIC blocks -> DERIVATIVE blocks auto blocks = node->get_blocks(); diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 6310c37849..bb7051420d 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -109,6 +109,9 @@ class KineticBlockVisitor: public AstVisitor { /// true if we are visiting a CONSERVE statement bool in_conserve_statement = false; + /// counts the number of CONSERVE statements in Kinetic blocks + int conserve_statement_count = 0; + /// conserve statement equation as string std::string conserve_equation_str; @@ -132,6 +135,9 @@ class KineticBlockVisitor: public AstVisitor { public: KineticBlockVisitor() = default; + inline int get_conserve_statement_count() const { + return conserve_statement_count; + } void visit_wrapped_expression(ast::WrappedExpression* node) override; void visit_reaction_operator(ast::ReactionOperator* node) override; From 2f22bbaccaade6df7fb2183dfcb44e29829a9248 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 3 Apr 2020 17:52:01 +0200 Subject: [PATCH 251/871] Bug fix for setup.py install (BlueBrain/nmodl#286) - python setup.py was installing nmodl module with all python files but not shared library - it was working fine in the past but somewhere distutils behaviour changed? Anyway, use build/nmodl directory where we built complete nmodl module and copy that to extension directory NMODL Repo SHA: BlueBrain/nmodl@93e4d71a8b21c96f7ef9e123cbef9ae28bef0bc1 --- setup.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/setup.py b/setup.py index f37a4ac9c4..e769a4609f 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ from distutils.version import LooseVersion from distutils.cmd import Command +from distutils.dir_util import copy_tree from setuptools import Extension, setup from setuptools.command.build_ext import build_ext from setuptools.command.test import test @@ -114,6 +115,9 @@ def build_extension(self, ext): ["cmake", "--build", "."] + build_args, cwd=self.build_temp ) + # copy nmodl module with shared library to extension directory + copy_tree(os.path.join(self.build_temp, 'nmodl'), extdir) + class NMODLTest(test): """Custom disutils command that acts like as a replacement for the "test" command. From baa00aeda392bc96af54cf7b5d3b3272fe155e7f Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Sat, 11 Apr 2020 09:59:01 +0200 Subject: [PATCH 252/871] Add parent pointer to children and make members private the AST(BlueBrain/nmodl#257) * Add parent pointer to nodes in the AST (issue BlueBrain/nmodl#65) - Expand node funtionality to navigate backwards among nodes. - add parent pointer to ast base class - add setters and getters - added set_parent_in_children() method to all parents. It is called on parent construction. - Add parent pointer to nodes (addNode and setters) BlueBrain/nmodl#65 - addNode now update the node parent (to Program*) - the setters now update the appropriate child/children parents - Create the check parent visitor to verify parents - The visitor goes down the tree keeping track of the parents and checking that they are all set-up correctly in the children. - Add test parents in NMODL constructs - Add a new scenario in ctest to test parent pointers in valid ast using CkParentVisitor. It descends in the tree, if parents do not match it emits runtime_errors failing the test. * Turn every child to private - Previously, some children (i.e. statement) were left public for easy access and manipulation by the visitors from the parent. Thus, the interface for handling children in a safe way (i.e. addNode) was only partially implemented. Now that there is a parent poiner, if the paradigm was left unchanged, every visitor would have the responsibility of updating children's parents. This is prone to errors. Thus, the interface was expanded (if not completed). - Add get__cr() to get a const ref of the child. This may replace completly get_ in the future. - Vector interface members that closely resemble std::vector - add is the corrispective of vector::emplace_back It does not have the same name due to legacy code. It can be changed with a little bit of refactoring - insert - erase - reset resets the shared_ptr - Mark clone() as const - Mark eval() as const - Adapt visitors to the new interface. They use only the new _cr getters - Add ckParent_visitor to most of the visitor tests (at the end, after the visitor passed) to check that parents are still up to date. * Add test namespace * Create the check parent visitor to verify parents - The visitor goes down the tree keeping track of the parents and checking that they are all set-up correctly in the children. * Add test parents in NMODL constructs * Removed the return-by-value getter of AST node children fixes BlueBrain/nmodl#65 NMODL Repo SHA: BlueBrain/nmodl@1b16939ee9ceeb4376ccfd22868744eda0effa2d --- cmake/nmodl/CMakeLists.txt | 4 +- src/nmodl/ast/ast_common.hpp | 63 ++++++- src/nmodl/language/nmodl.yaml | 9 +- src/nmodl/language/nodes.py | 169 ++++++++++++++++-- src/nmodl/language/templates/ast/ast.cpp | 37 ++++ src/nmodl/language/templates/ast/ast.hpp | 22 ++- src/nmodl/language/templates/pybind/pyast.hpp | 6 +- .../visitors/checkparent_visitor.cpp | 62 +++++++ .../visitors/checkparent_visitor.hpp | 87 +++++++++ .../templates/visitors/nmodl_visitor.cpp | 8 +- src/nmodl/parser/nmodl.yy | 24 +-- src/nmodl/visitors/CMakeLists.txt | 4 +- src/nmodl/visitors/inline_visitor.cpp | 36 ++-- src/nmodl/visitors/kinetic_block_visitor.cpp | 2 +- .../visitors/local_var_rename_visitor.cpp | 4 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 11 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 19 +- src/nmodl/visitors/solve_block_visitor.cpp | 2 +- src/nmodl/visitors/steadystate_visitor.cpp | 2 +- .../visitors/sympy_conductance_visitor.cpp | 4 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 22 +-- src/nmodl/visitors/sympy_solver_visitor.hpp | 3 +- src/nmodl/visitors/visitor_utils.cpp | 53 ++++-- src/nmodl/visitors/visitor_utils.hpp | 2 +- test/nmodl/transpiler/CMakeLists.txt | 9 +- test/nmodl/transpiler/parser/parser.cpp | 19 +- .../transpiler/utils/nmodl_constructs.cpp | 2 +- .../transpiler/utils/nmodl_constructs.hpp | 2 +- .../transpiler/visitor/constant_folder.cpp | 6 + .../transpiler/visitor/defuse_analyze.cpp | 6 + test/nmodl/transpiler/visitor/inline.cpp | 8 + test/nmodl/transpiler/visitor/json.cpp | 3 +- .../transpiler/visitor/kinetic_block.cpp | 6 + test/nmodl/transpiler/visitor/localize.cpp | 6 + test/nmodl/transpiler/visitor/loop_unroll.cpp | 6 + test/nmodl/transpiler/visitor/misc.cpp | 7 + .../nmodl/transpiler/visitor/neuron_solve.cpp | 7 + test/nmodl/transpiler/visitor/nmodl.cpp | 6 + test/nmodl/transpiler/visitor/rename.cpp | 6 + test/nmodl/transpiler/visitor/solve_block.cpp | 6 + test/nmodl/transpiler/visitor/steadystate.cpp | 6 + .../transpiler/visitor/sympy_conductance.cpp | 5 + .../nmodl/transpiler/visitor/sympy_solver.cpp | 5 + test/nmodl/transpiler/visitor/units.cpp | 5 + test/nmodl/transpiler/visitor/var_usage.cpp | 4 +- test/nmodl/transpiler/visitor/verbatim.cpp | 7 +- 46 files changed, 663 insertions(+), 129 deletions(-) create mode 100644 src/nmodl/language/templates/visitors/checkparent_visitor.cpp create mode 100644 src/nmodl/language/templates/visitors/checkparent_visitor.hpp diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 46c9495a7a..65f7fc4d06 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -167,7 +167,9 @@ set(AUTO_GENERATED_FILES ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp) + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.hpp) add_subdirectory(src/codegen) add_subdirectory(src/language) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 65db7eeed5..5dfc0e64d3 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -145,6 +145,31 @@ static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; * in the future. */ struct Ast: public std::enable_shared_from_this { + private: + /** + * \brief Generic pointer to the parent + * + * Children types can be known at compile time. Conversely, many parents + * can have the same children type. Thus, this is just a pointer to + * the base class. The pointer to the parent cannot have ownership + * (circular ownership problem). weak_ptr you say? Yes, however weak_ptr + * can be instantiated from shared_ptr (not this). Whys is this a problem? + * In bison things must be passed around as raw pointers (because it uses + * unions etc.) and there are cases where the shared_ptr to the parent + * was not yet created while the child is added (throwing a bad_weak_ptr + * exception). + * + * i.e. in bison the lines: + * + * ast::WatchStatement* a = new ast::WatchStatement(); + * yylhs.value.as< ast::WatchStatement* > ()->add_watch(a); + * + * would throw a bad_weak_ptr exception because when you call add_watch + * the shared_ptr_from_this to "a" is not yet created. + */ + Ast* parent = nullptr; + + public: /// \name Ctor & dtor /// \{ @@ -246,7 +271,7 @@ struct Ast: public std::enable_shared_from_this { * * @return pointer to the clone/copy of the current node */ - virtual Ast* clone() { + virtual Ast* clone() const { throw std::logic_error("clone not implemented"); } @@ -321,7 +346,7 @@ struct Ast: public std::enable_shared_from_this { * * \sa ast::StatementBlock */ - virtual std::shared_ptr get_statement_block() { + virtual const std::shared_ptr& get_statement_block() const { throw std::runtime_error("get_statement_block not implemented"); } @@ -1496,6 +1521,40 @@ struct Ast: public std::enable_shared_from_this { virtual bool is_ontology_statement() { return false; } + + /** + *\brief Parent getter + * + * returning a raw pointer may create less problems that the + * shared_from_this from the parent. + * + * \ref Check Ast::parent for more information + */ + virtual Ast* get_parent() const { + return parent; + } + + /** + *\brief Parent setter + * + * Usually, the parent parent pointer cannot be set in the constructor + * because children are generally build BEFORE the parent. Conversely, + * we set children parents directly in the parent constructor using + * set_parent_in_children() + * + * \ref Check Ast::parent for more information + */ + virtual void set_parent(Ast* p) { + parent = p; + } + + /** + *\brief Set this object as parent for all the children + * + * This should be called in every object (with children) constructor + * to set the parents. + */ + virtual void set_parent_in_children() {} }; /** @} */ // end of ast_class diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index cc17324a40..a03cf57bad 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -639,7 +639,6 @@ brief: "Vector of statements" type: Statement vector: true - public: true add: true brief: "Represents block encapsulating list of statements" description: | @@ -1198,15 +1197,12 @@ - lhs: brief: "LHS of the binary expression" type: Expression - public: true - op: brief: "Opearator" type: BinaryOperator - public: true - rhs: brief: "RHS of the binary expression" type: Expression - public: true brief: "Represents binary expression in the NMODL" description: | Any binary expression in the mod file is represented by this node type. @@ -1229,7 +1225,6 @@ - expression: brief: "Differential Expression" type: BinaryExpression - public: true - UnaryExpression: brief: "TODO" @@ -1396,7 +1391,7 @@ type: LocalVar vector: true separator: ", " - public: true + add: true - Model: brief: "TODO" @@ -2125,4 +2120,4 @@ type: Node vector: true add: true - public: true + # public: true diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 34cc0786da..45596d83bc 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -214,19 +214,88 @@ def member_typename(self): def get_add_methods(self): s = '' if self.add_method: + set_parent = "n->set_parent(this); " + if self.optional: + set_parent = f""" + if (n) {{ + n->set_parent(this); + }} + """ method = f""" /** * \\brief Add member to {self.varname} by raw pointer */ - void add{self.class_name}({self.class_name} *n) {{ - {self.varname}.emplace_back(n); + void emplace_back_{to_snake_case(self.class_name)}({self.class_name} *n) {{ + {self.varname}.emplace_back(n); + + // set parents + {set_parent} }} /** * \\brief Add member to {self.varname} by shared_ptr */ - void add{self.class_name}(std::shared_ptr<{self.class_name}> n) {{ - {self.varname}.push_back(n); + void emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n) {{ + {self.varname}.emplace_back(n); + + // set parents + {set_parent} + }} + + /** + * \\brief Erase member to {self.varname} + */ + {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first) {{ + return {self.varname}.erase(first); + }} + /** + * \\brief Erase members to {self.varname} + */ + {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last) {{ + return {self.varname}.erase(first, last); + }} + + /** + * \\brief Insert member to {self.varname} + */ + {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n) {{ + {set_parent} + + return {self.varname}.insert(position, n); + }} + /** + * \\brief Insert members to {self.varname} + */ + template + void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, InputIterator first, InputIterator last) {{ + + for (auto it = first; it != last; ++it) {{ + auto& n = *it; + //set parents + {set_parent} + }} + + {self.varname}.insert(position, first, last); + }} + + /** + * \\brief Reset member to {self.varname} + */ + void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n) {{ + //set parents + {set_parent} + + {self.varname}[position - {self.varname}.begin()].reset(n); + }} + + /** + * \\brief Reset member to {self.varname} + */ + void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n) {{ + //set parents + {set_parent} + + {self.varname}[position - {self.varname}.begin()] = n; }} """ s = textwrap.dedent(method) @@ -261,13 +330,17 @@ def get_getter_method(self, class_name): return_type = self.member_typename return f""" /** - * \\brief Getter for member variable \\ref {class_name}.{self.varname} + * \\brief Getter (const ref) for member variable \\ref {class_name}.{self.varname} */ - {return_type} {getter_method}(){getter_override}{{ + const {return_type}& {getter_method}() const {getter_override} {{ return {self.varname}; - }}""" + }} + """ + + + - def get_setter_method(self, class_name): + def get_setter_method_declaration(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename reference = "" if self.is_base_type_node else "&&" @@ -276,26 +349,86 @@ def get_setter_method(self, class_name): /** * \\brief Setter for member variable \\ref {class_name}.{self.varname} */ - void {setter_method}({setter_type} {self.varname}) {{ - this->{self.varname} = {self.varname}; - }} + void {setter_method}({setter_type} {self.varname}); """ else: return f""" /** * \\brief Setter for member variable \\ref {class_name}.{self.varname} (rvalue reference) */ - void {setter_method}({setter_type}&& {self.varname}) {{ - this->{self.varname} = {self.varname}; - }} - + void {setter_method}({setter_type}&& {self.varname}); + /** * \\brief Setter for member variable \\ref {class_name}.{self.varname} */ - void {setter_method}(const {setter_type}& {self.varname}) {{ + void {setter_method}(const {setter_type}& {self.varname}); + """ + + + + def get_setter_method_definition(self, class_name): + setter_method = "set_" + to_snake_case(self.varname) + setter_type = self.member_typename + reference = "" if self.is_base_type_node else "&&" + + + if self.is_base_type_node: + return f""" + void {class_name}::{setter_method}({setter_type} {self.varname}) {{ + // why don't we use a coding convention instead of this workaround for + // variable shadowing? this->{self.varname} = {self.varname}; }} """ + elif self.is_vector: + return f""" + void {class_name}::{setter_method}({setter_type}&& {self.varname}) {{ + this->{self.varname} = {self.varname}; + // set parents + for (auto& ii : {self.varname}) {{ + ii->set_parent(this); + }} + }} + + void {class_name}::{setter_method}(const {setter_type}& {self.varname}) {{ + this->{self.varname} = {self.varname}; + // set parents + for (auto& ii : {self.varname}) {{ + ii->set_parent(this); + }} + }} + """ + elif self.is_pointer_node or self.optional: + return f""" + void {class_name}::{setter_method}({setter_type}&& {self.varname}) {{ + this->{self.varname} = {self.varname}; + // set parents + if ({self.varname}) {{ + {self.varname}->set_parent(this); + }} + }} + + void {class_name}::{setter_method}(const {setter_type}& {self.varname}) {{ + this->{self.varname} = {self.varname}; + // set parents + if ({self.varname}) {{ + {self.varname}->set_parent(this); + }} + }} + """ + else: + return f""" + void {class_name}::{setter_method}({setter_type}&& {self.varname}) {{ + this->{self.varname} = {self.varname}; + }} + + void {class_name}::{setter_method}(const {setter_type}& {self.varname}) {{ + this->{self.varname} = {self.varname}; + }} + """ + + + def __repr__(self): return "ChildNode(class_name='{}', nmodl_name='{}')".format( @@ -400,7 +533,7 @@ def ctor_definition(self): initlist = [f'{c.varname}({c.varname})' for c in self.children] s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) - : {', '.join(initlist)} {{}} + : {', '.join(initlist)} {{ set_parent_in_children(); }} """ return textwrap.dedent(s) @@ -413,7 +546,7 @@ def ctor_shrptr_definition(self): initlist = [f'{c.varname}({c.varname})' for c in self.children] s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) - : {', '.join(initlist)} {{}} + : {', '.join(initlist)} {{ set_parent_in_children(); }} """ return textwrap.dedent(s) diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index be71f30b58..4f2702ffd6 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -71,16 +71,53 @@ namespace ast { this->{{ child.varname }} = obj.{{ child.varname }}; {% endif %} {% endfor %} + {% if node.has_token %} /// if there is a token, make copy if (obj.token) { this-> token = std::shared_ptr(obj.token->clone()); } {% endif %} + + /// set parents + set_parent_in_children(); } + /// set this parent in the children + void {{ node.class_name }}::set_parent_in_children() { + + {% for child in node.non_base_members %} + {% if child.is_vector %} + /// set parent for each element of the vector + for (auto& item : {{ child.varname }}) { + {% if child.optional %} + if (item) { + item->set_parent(this); + } + {% else %} + item->set_parent(this); + {% endif %} + + } + {% elif child.is_pointer_node or child.optional %} + /// optional member could be nullptr + if ({{ child.varname }}) { + {{ child.varname }}->set_parent(this); + } + {% else %} + {{ child.varname }}.set_parent(this); + {% endif %} + {% endfor %} + + } + + {% endif %} + {% for child in node.children %} + {{ child.get_setter_method_definition(node.class_name) }} + {% endfor %} + {% endfor %} } // namespace ast diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 824056df1d..f8c8883ce1 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -73,7 +73,6 @@ namespace ast { {% endif %} {% endfor %} - /// \name Ctor & dtor /// \{ @@ -97,7 +96,7 @@ namespace ast { /// \{ {% if node.is_base_block_node %} - virtual ArgumentVector get_parameters() { + virtual const ArgumentVector& get_parameters() const { throw std::runtime_error("get_parameters not implemented"); } {% endif %} @@ -128,7 +127,7 @@ namespace ast { * * @return pointer to the clone/copy of the current node */ - {{ virtual(node) }} {{ node.class_name }}* clone() override { + {{ virtual(node) }} {{ node.class_name }}* clone() const override { return new {{ node.class_name }}(*this); } @@ -237,6 +236,8 @@ namespace ast { {{ child.get_node_name_method() }} {{ child.get_getter_method(node.class_name) }} + + {% endfor %} /// \} @@ -295,7 +296,7 @@ namespace ast { {# doxygen for these methods is handled by nodes.py #} {% for child in node.children %} - {{ child.get_setter_method(node.class_name) }} + {{ child.get_setter_method_declaration(node.class_name) }} {% endfor %} /// \} @@ -362,7 +363,7 @@ namespace ast { * string representation when they are converted from AST back to * NMODL. This method is used to return corresponding string representation. */ - std::string eval() { return {{ + std::string eval() const { return {{ node.get_data_type_name() }}Names[value]; } {# but if basic data type then eval return their value #} @@ -376,11 +377,20 @@ namespace ast { * * \sa {{ node.class_name }}::set */ - {{ node.get_data_type_name() }} eval() { + {{ node.get_data_type_name() }} eval() const { return value; } {% endif %} {% endif %} + + {% if node.children %} + /** + * \brief Set parents in children + * + * Usually called in constructors + */ + virtual void set_parent_in_children() override; + {% endif %} }; {% endfor %} diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 7a30c2629c..453472fe33 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -59,7 +59,7 @@ struct PyAst: public Ast { } - Ast* clone() override { + Ast* clone() const override { PYBIND11_OVERLOAD(Ast*, Ast, clone, ); } @@ -95,8 +95,8 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(symtab::SymbolTable*, Ast, get_symbol_table, ); } - std::shared_ptr get_statement_block() override { - PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_statement_block, ); + const std::shared_ptr& get_statement_block() const override { + PYBIND11_OVERLOAD(const std::shared_ptr&, Ast, get_statement_block, ); } void set_symbol_table(symtab::SymbolTable* newsymtab) override { diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp new file mode 100644 index 0000000000..f12cdab5de --- /dev/null +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -0,0 +1,62 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include +#include + +#include "visitors/checkparent_visitor.hpp" + +namespace nmodl { +namespace visitor { +namespace test { + +using namespace fmt::literals; +using namespace ast; + +int CheckParentVisitor::check_ast(Ast* node) { + + parent = nullptr; + + node->accept(*this); + + return 0; +} + +void CheckParentVisitor::check_parent(ast::Ast* node) const { + if (!parent) { + if (is_root_with_null_parent && node->get_parent()) { + std::string node_type = (node == nullptr) ? "nullptr" : node->get_node_type_name(); + throw std::runtime_error("root->parent: {} is set when it should be nullptr"_format(node_type)); + } + } else { + if (parent != node->get_parent()) { + std::string parent_type = (parent == nullptr) ? "nullptr" : parent->get_node_type_name(); + std::string node_parent_type = (node->get_parent() == nullptr) ? std::string("nullptr") : node->get_parent()->get_node_type_name(); + throw std::runtime_error("parent: {} and child->parent: {} missmatch"_format(parent_type, node_parent_type)); + } + } +} + + +{% for node in nodes %} +void CheckParentVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { + + // Set this node as parent. and go down the tree + parent = node; + + // visit its children + node->visit_children(*this); + + // I am done with these children, I go up the tree. The parent of this node is the parent + parent = node->get_parent(); +} + +{% endfor %} + +} // namespace test +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp new file mode 100644 index 0000000000..338dc72240 --- /dev/null +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -0,0 +1,87 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * + * \dir + * \brief Auto generated visitors + * + * \file + * \brief \copybrief nmodl::visitor::CheckParentVisitor + */ + +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { +namespace test { + +/** + * @ingroup visitor_classes + * @{ + */ + +/** + * \class CheckParentVisitor + * \brief %Visitor for checking parents of ast nodes + * + * The visitor goes down the tree (parent -> children) marking down in + * parent who is the parent of the node he is visiting. + * Once check_parent(ast::Ast* node) verified that the current node has + * the correct parent, we set the current node as parent and go down + * the tree. Once all the children were checked we set the parent of the + * current node as parent (it was checked before) and return. + */ +class CheckParentVisitor : public AstVisitor { + private: + /** + * \brief Keeps track of the parents while going down the tree + */ + ast::Ast* parent = nullptr; + /** + * \brief Flag to activate the parent check on the root node + * + * This flag tells to the visitor to check (or not check) if the + * root node, from which we start the visit, is the root node and + * thus, it should have nullptr parent + */ + bool is_root_with_null_parent = false; + public: + + /** + * \brief Standard constructor + * + * If is_root_with_null_parent is set to true, also the initial + * node is checked to be sure that is really the root (parent = nullptr) + */ + CheckParentVisitor(const bool is_root_with_null_parent = true) {} + + /** + * \brief A small wrapper to have a nicer call in parser.cpp + */ + int check_ast(ast::Ast* node); + + /** + * \brief Check the parent, throw an error if not + */ + void check_parent(ast::Ast* node) const; + + {% for node in nodes %} + /** + * \brief Go through the tree while checking the parents + */ + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + {% endfor %} +}; + +/** @} */ // end of visitor_classes + +} // namespace test +} // namespace visitor +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 90987f07b1..f5796a86a0 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -63,13 +63,17 @@ using namespace ast; auto symbol = std::string(order, '\''); printer->add_element(symbol); {% elif node.class_name == node_info.BINARY_EXPRESSION_NODE and child.varname == node_info.BINARY_OPERATOR_NAME %} - std::string op = node->op.eval(); + std::string op = node->get_op().eval(); if(op == "=" || op == "&&" || op == "||" || op == "==") op = " " + op + " "; printer->add_element(op); {% else %} {% call guard(child.prefix, child.suffix) %} - node->get_{{ child.varname }}(){{ "->" if child.is_pointer_node else "." }}accept(*this); + {% if child.is_pointer_node %} + node->get_{{ child.varname }}()->accept(*this); + {% else %} + {{ child.class_name }}(node->get_{{ child.varname }}()).accept(*this); + {%- endif %} {% endcall %} {%- endif %} {%- endmacro -%} diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 27eeb2f005..701cae5ac2 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -443,22 +443,22 @@ all : { } | all model { - $1->addNode($2); + $1->emplace_back_node($2); $$ = $1; } | all local_statement { - $1->addNode($2); + $1->emplace_back_node($2); $$ = $1; } | all define { - $1->addNode($2); + $1->emplace_back_node($2); $$ = $1; } | all declare { - $1->addNode($2); + $1->emplace_back_node($2); $$ = $1; } | all MODEL_LEVEL INTEGER_PTR declare @@ -470,38 +470,38 @@ all : { } | all procedure { - $1->addNode($2); + $1->emplace_back_node($2); $$ = $1; } | all VERBATIM { auto text = parse_with_verbatim_parser($2); auto statement = new ast::Verbatim(new ast::String(text)); - $1->addNode(statement); + $1->emplace_back_node(statement); $$ = $1; } | all BLOCK_COMMENT { auto text = parse_with_verbatim_parser($2); auto statement = new ast::BlockComment(new ast::String(text)); - $1->addNode(statement); + $1->emplace_back_node(statement); $$ = $1; } | all LINE_COMMENT { auto statement = new ast::LineComment(new ast::String($2)); - $1->addNode(statement); + $1->emplace_back_node(statement); $$ = $1; } | all unit_state { - $1->addNode($2); + $1->emplace_back_node($2); $$ = $1; } | all INCLUDE1 STRING_PTR { auto statement = new ast::Include($3); - $1->addNode(statement); + $1->emplace_back_node(statement); $$ = $1; } ; @@ -1838,11 +1838,11 @@ before_after_block : BREAKPOINT statement_list "}" watch_statement : WATCH watch { $$ = new ast::WatchStatement(ast::WatchVector()); - $$->addWatch($2); + $$->emplace_back_watch($2); } | watch_statement "," watch { - $1->addWatch($3); $$ = $1; + $1->emplace_back_watch($3); $$ = $1; } | WATCH error { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 6b377fcd61..9acf1f4253 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -52,7 +52,9 @@ set(VISITOR_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp) + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.hpp) set_source_files_properties(${VISITOR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index de438785ad..413ea2161d 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -17,7 +17,7 @@ using namespace ast; bool InlineVisitor::can_inline_block(StatementBlock* block) { bool to_inline = true; const auto& statements = block->get_statements(); - for (auto statement: statements) { + for (const auto& statement: statements) { /// inlining is disabled if function/procedure contains table or lag statement if (statement->is_table_statement() || statement->is_lag_statement()) { to_inline = false; @@ -26,7 +26,7 @@ bool InlineVisitor::can_inline_block(StatementBlock* block) { // verbatim blocks with return statement are not safe to inline // especially for net_receive block if (statement->is_verbatim()) { - auto node = static_cast(statement.get()); + const auto node = static_cast(statement.get()); auto text = node->get_statement()->eval(); parser::CDriver driver; driver.scan_string(text); @@ -44,7 +44,7 @@ void InlineVisitor::add_return_variable(StatementBlock* block, std::string& varn auto rhs = new Integer(0, nullptr); auto expression = new BinaryExpression(lhs, BinaryOperator(BOP_ASSIGN), rhs); auto statement = std::make_shared(expression); - block->statements.push_back(statement); + block->emplace_back_statement(statement); } /** We can replace statement if the entire statement itself is a function call. @@ -81,7 +81,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, } size_t counter = 0; - auto& statements = inlined_block->statements; + const auto& statements = inlined_block->get_statements(); for (const auto& argument: callee_parameters) { auto name = argument->get_name()->clone(); @@ -102,7 +102,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, /// create assignment statement and insert after the local variables auto expression = new BinaryExpression(lhs, BinaryOperator(ast::BOP_ASSIGN), rhs); auto statement = std::make_shared(expression); - statements.insert(statements.begin() + counter + 1, statement); + inlined_block->insert_statement(statements.begin() + counter + 1, statement); counter++; } } @@ -145,12 +145,13 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, ModToken tok; name->set_token(tok); - auto local_variables = get_local_variables(caller); + const ast::StatementVector& statements = caller->get_statements(); + auto local_list_statement = get_local_list_statement(caller); /// each block should already have local statement - if (local_variables == nullptr) { + if (local_list_statement == nullptr) { throw std::logic_error("got local statement as nullptr"); } - local_variables->push_back(std::make_shared(name)); + local_list_statement->emplace_back_local_var(std::make_shared(name)); } /// get a copy of function/procedure body @@ -239,26 +240,27 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { */ add_local_statement(node); - auto& statements = node->statements; + const auto& statements = node->get_statements(); - for (auto& statement: statements) { + for (const auto& statement: statements) { caller_statement = statement; statement_stack.push(statement); caller_statement->visit_children(*this); statement_stack.pop(); } - /// if nothing was added into local statement, remove it - LocalVarVector* local_variables = get_local_variables(node); - if (local_variables->empty()) { - statements.erase(statements.begin()); + /// each block should already have local statement + auto local_list_statement = get_local_list_statement(node); + if (local_list_statement->get_variables().empty()) { + node->erase_statement(statements.begin()); } /// check if any statement is candidate for replacement due to inlining /// this is typicall case of procedure calls - for (auto& statement: statements) { + for (auto it = statements.begin(); it < statements.end(); ++it) { + const auto& statement = *it; if (replaced_statements.find(statement) != replaced_statements.end()) { - statement.reset(replaced_statements[statement]); + node->reset_statement(it, replaced_statements[statement]); } } @@ -266,7 +268,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { for (auto& element: inlined_statements) { auto it = std::find(statements.begin(), statements.end(), element.first); if (it != statements.end()) { - statements.insert(it, element.second.begin(), element.second.end()); + node->insert_statement(it, element.second.begin(), element.second.end()); element.second.clear(); } } diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 2cd42a356a..e51d3bc36c 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -422,7 +422,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { // add new statements for (const auto& ode: odes) { logger->debug("KineticBlockVisitor :: -> adding statement: {}", ode); - kinetic_statement_block->addStatement(create_statement(ode)); + kinetic_statement_block->emplace_back_statement(create_statement(ode)); } // store pointer to kinetic block diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index acf956ea38..bbc4d59b4e 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -46,7 +46,7 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { parent_symtab = symtab->get_parent_table(); } - auto variables = get_local_variables(node); + auto variables = get_local_list_statement(node); /// global blocks do not change (do no have parent symbol table) /// if no variables in the block then there is nothing to do @@ -56,7 +56,7 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { RenameVisitor rename_visitor; - for (const auto& var: *variables) { + for (const auto& var: variables->get_variables()) { std::string name = var->get_node_name(); auto s = parent_symtab->lookup_in_scope(name); /// if symbol is a variable name (avoid renaming use of units like mV) diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index fea7025d19..2080563207 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -122,8 +122,8 @@ static std::shared_ptr unroll_for_loop( auto new_block = node->get_statement_block()->clone(); IndexRemover(index_var, i).visit_statement_block(new_block); statements.insert(statements.end(), - new_block->statements.begin(), - new_block->statements.end()); + new_block->get_statements().begin(), + new_block->get_statements().end()); delete new_block; } @@ -139,7 +139,9 @@ static std::shared_ptr unroll_for_loop( void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { node->visit_children(*this); - for (auto iter = node->statements.begin(); iter != node->statements.end(); iter++) { + const auto& statements = node->get_statements(); + + for (auto iter = statements.begin(); iter != statements.end(); ++iter) { if ((*iter)->is_from_statement()) { auto statement = std::dynamic_pointer_cast((*iter)); @@ -154,7 +156,8 @@ void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { /// unroll loop, replace current statement on successfull unroll auto new_statement = unroll_for_loop(statement); if (new_statement != nullptr) { - (*iter) = new_statement; + node->reset_statement(iter, new_statement); + std::string before = to_nmodl(statement.get()); std::string after = to_nmodl(new_statement.get()); logger->debug("LoopUnrollVisitor : \n {} \n unrolled to \n {}", before, after); diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index eb98f2aeae..cbfa3d6497 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -43,9 +43,7 @@ void NeuronSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { - auto& lhs = node->lhs; - auto& rhs = node->rhs; - auto& op = node->op; + const auto& lhs = node->get_lhs(); /// we have to only solve odes under derivative block where lhs is variable if (!derivative_block || !differential_equation || !lhs->is_var_name()) { @@ -66,10 +64,10 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast( statement); - auto bin_expr = std::dynamic_pointer_cast( + const auto bin_expr = std::dynamic_pointer_cast( expr_statement->get_expression()); - lhs.reset(bin_expr->lhs->clone()); - rhs.reset(bin_expr->rhs->clone()); + node->set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); + node->set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); } else { logger->warn("NeuronSolveVisitor :: cnexp solver not possible for {}", to_nmodl(node)); @@ -78,14 +76,13 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { std::string solution = diffeq_driver.solve(equation, solve_method); auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast(statement); - auto bin_expr = std::dynamic_pointer_cast( + const auto bin_expr = std::dynamic_pointer_cast( expr_statement->get_expression()); - lhs.reset(bin_expr->lhs->clone()); - rhs.reset(bin_expr->rhs->clone()); + node->set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); + node->set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { auto varname = "D" + name->get_node_name(); - auto variable = new ast::Name(new ast::String(varname)); - lhs.reset(variable); + node->set_lhs(std::make_shared(new ast::String(varname))); if (program_symtab->lookup(varname) == nullptr) { auto symbol = std::make_shared(varname, ModToken()); symbol->set_original_name(name->get_node_name()); diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 2b51bc2a5a..de1f644022 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -82,7 +82,7 @@ void SolveBlockVisitor::visit_program(ast::Program* node) { /// add new node NrnState with solve blocks from breakpoint block if (!nrn_state_solve_statements.empty()) { auto nrn_state = new ast::NrnStateBlock(nrn_state_solve_statements); - node->addNode(nrn_state); + node->emplace_back_node(nrn_state); } } diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 072202337a..054f29db0c 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -119,7 +119,7 @@ void SteadystateVisitor::visit_program(ast::Program* node) { if (solve_block->get_steadystate()) { auto ss_block = create_steadystate_block(solve_block, deriv_blocks); if (ss_block != nullptr) { - node->addNode(ss_block); + node->emplace_back_node(ss_block); } } } diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 5cd9ca47d5..7db3bb4154 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -140,9 +140,9 @@ void SympyConductanceVisitor::visit_binary_expression(ast::BinaryExpression* nod return; } // only want binary expressions of form x = ... - if (node->lhs->is_var_name() && (node->op.get_value() == BinaryOp::BOP_ASSIGN)) { + if (node->get_lhs()->is_var_name() && (node->get_op().get_value() == BinaryOp::BOP_ASSIGN)) { auto lhs_str = - std::dynamic_pointer_cast(node->lhs)->get_name()->get_node_name(); + std::dynamic_pointer_cast(node->get_lhs())->get_name()->get_node_name(); binary_expr_index[lhs_str] = ordered_binary_exprs.size(); ordered_binary_exprs.push_back(to_nmodl_for_sympy(node)); ordered_binary_exprs_lhs.push_back(lhs_str); diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index b26394972d..f0e2d29df4 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -81,8 +81,8 @@ void SympySolverVisitor::check_expr_statements_in_same_block() { block_with_expression_statements = current_statement_block; } -ast::StatementVector::iterator SympySolverVisitor::get_solution_location_iterator( - ast::StatementVector& statements) { +ast::StatementVector::const_iterator SympySolverVisitor::get_solution_location_iterator( + const ast::StatementVector& statements) { // find out where to insert solutions in statement block // returns iterator pointing to the first element after the last (non)linear eq // so if there are no such elements, it returns statements.end() @@ -187,12 +187,12 @@ void SympySolverVisitor::construct_eigen_solver_block( solutions_filtered = filter_string_vector(solutions_filtered, "F[", unique_F + "["); // find out where to insert solution in statement block - auto& statements = block_with_expression_statements->statements; + const auto& statements = block_with_expression_statements->get_statements(); auto it = get_solution_location_iterator(statements); // insert pre-solve statements below last linear eq in block for (const auto& statement: pre_solve_statements) { logger->debug("SympySolverVisitor :: -> adding statement: {}", statement); - it = statements.insert(it, create_statement(statement)); + it = block_with_expression_statements->insert_statement(it, create_statement(statement)); ++it; } // make Eigen vector <-> state var assignments @@ -213,7 +213,8 @@ void SympySolverVisitor::construct_eigen_solver_block( // statements after last diff/linear/non-linear eq statement go into finalize_block ast::StatementVector finalize_statements{it, statements.end()}; // remove them from the statement block - statements.erase(it, statements.end()); + + block_with_expression_statements->erase_statement(it, statements.end()); // also remove diff/linear/non-linear eq statements from the statement block remove_statements_from_block(block_with_expression_statements, expression_statements); // move any local variable declarations into variable_block @@ -307,7 +308,7 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre return; } // find out where to insert solutions in statement block - auto& statements = block_with_expression_statements->statements; + const auto& statements = block_with_expression_statements->get_statements(); auto it = get_solution_location_iterator(statements); if (small_system) { // for small number of state vars, linear solver @@ -324,13 +325,14 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre // insert pre-solve statements below last linear eq in block for (const auto& statement: pre_solve_statements) { logger->debug("SympySolverVisitor :: -> adding statement: {}", statement); - it = statements.insert(it, create_statement(statement)); + it = block_with_expression_statements->insert_statement(it, + create_statement(statement)); ++it; } // then insert new solution statements for (const auto& sol: solutions) { logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); - it = statements.insert(it, create_statement(sol)); + it = block_with_expression_statements->insert_statement(it, create_statement(sol)); ++it; } /// remove original lineq statements from the block @@ -401,8 +403,8 @@ void SympySolverVisitor::visit_var_name(ast::VarName* node) { } void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { - auto& lhs = node->get_expression()->lhs; - auto& rhs = node->get_expression()->rhs; + const auto& lhs = node->get_expression()->get_lhs(); + const auto& rhs = node->get_expression()->get_rhs(); if (!lhs->is_var_name()) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a VariableName"); diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 5b7e04e464..b2d066191c 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -78,7 +78,8 @@ class SympySolverVisitor: public AstVisitor { void check_expr_statements_in_same_block(); /// return iterator pointing to where solution should be inserted in statement block - ast::StatementVector::iterator get_solution_location_iterator(ast::StatementVector& statements); + ast::StatementVector::const_iterator get_solution_location_iterator( + const ast::StatementVector& statements); /// construct solver block void construct_eigen_solver_block(const std::vector& pre_solve_statements, diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 676f73f100..e0cfda98e6 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -34,29 +34,38 @@ std::string get_new_name(const std::string& name, return (name + "_" + suffix + "_" + std::to_string(counter)); } -LocalVarVector* get_local_variables(const StatementBlock* node) { - for (const auto& statement: node->statements) { +std::shared_ptr get_local_list_statement(const StatementBlock* node) { + const auto& statements = node->get_statements(); + for (const auto& statement: statements) { if (statement->is_local_list_statement()) { - auto local_statement = std::static_pointer_cast(statement); - return &(local_statement->variables); + return std::static_pointer_cast(statement); } } return nullptr; } void add_local_statement(StatementBlock* node) { - auto variables = get_local_variables(node); + auto variables = get_local_list_statement(node); + const auto& statements = node->get_statements(); if (variables == nullptr) { auto statement = std::make_shared(LocalVarVector()); - node->statements.insert(node->statements.begin(), statement); + node->insert_statement(statements.begin(), statement); } } LocalVar* add_local_variable(StatementBlock* node, Identifier* varname) { add_local_statement(node); - auto local_variables = get_local_variables(node); + + const ast::StatementVector& statements = node->get_statements(); + + auto local_list_statement = get_local_list_statement(node); + /// each block should already have local statement + if (local_list_statement == nullptr) { + throw std::logic_error("no local statement"); + } auto var = std::make_shared(varname); - local_variables->push_back(var); + local_list_statement->emplace_back_local_var(var); + return var.get(); } @@ -84,7 +93,7 @@ std::shared_ptr create_statement(const std::string& code_statement) { nmodl::parser::NmodlDriver driver; auto nmodl_text = "PROCEDURE dummy() { " + code_statement + " }"; auto ast = driver.parse_string(nmodl_text); - auto procedure = std::dynamic_pointer_cast(ast->blocks[0]); + auto procedure = std::dynamic_pointer_cast(ast->get_blocks().front()); auto statement = std::shared_ptr( procedure->get_statement_block()->get_statements()[0]->clone()); return statement; @@ -106,7 +115,7 @@ std::shared_ptr create_statement_block( } nmodl_text += "}"; auto ast = driver.parse_string(nmodl_text); - auto procedure = std::dynamic_pointer_cast(ast->blocks[0]); + auto procedure = std::dynamic_pointer_cast(ast->get_blocks().front()); auto statement_block = std::shared_ptr( procedure->get_statement_block()->clone()); return statement_block; @@ -115,13 +124,23 @@ std::shared_ptr create_statement_block( void remove_statements_from_block(ast::StatementBlock* block, const std::set statements) { - auto& statement_vec = block->statements; - statement_vec.erase(std::remove_if(statement_vec.begin(), - statement_vec.end(), - [&statements](std::shared_ptr& s) { - return statements.find(s.get()) != statements.end(); - }), - statement_vec.end()); + const auto& statement_vec = block->get_statements(); + + // loosely following the cpp reference of remove_if + + auto first = statement_vec.begin(); + auto last = statement_vec.end(); + auto result = first; + + while (first != last) { + if (statements.find(first->get()) == statements.end()) { + block->reset_statement(result, *first); + ++result; + } + ++first; + } + + block->erase_statement(result, last); } diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index fc1003ed87..af65441321 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -29,7 +29,7 @@ std::string get_new_name(const std::string& name, /// Return pointer to local statement in the given block, otherwise nullptr -ast::LocalVarVector* get_local_variables(const ast::StatementBlock* node); +std::shared_ptr get_local_list_statement(const ast::StatementBlock* node); /// Add empty local statement to given block if already doesn't exist diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index c20adec606..0b400c0840 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -53,7 +53,14 @@ add_executable(testunitparser units/parser.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) -target_link_libraries(testparser lexer util test_util visitor) +target_link_libraries( + testparser + visitor + symtab + lexer + util + test_util + printer) target_link_libraries( testvisitor visitor diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 0080178300..38e95e0426 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -16,6 +16,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/lookup_visitor.hpp" using namespace nmodl::test_utils; @@ -129,7 +130,7 @@ SCENARIO("NMODL parser running number of valid NMODL constructs") { } SCENARIO("NMODL parser running number of invalid NMODL constructs") { - for (const auto& construct: nmdol_invalid_constructs) { + for (const auto& construct: nmodl_invalid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { THEN("Parser throws an exception while parsing : " + test_case.input) { @@ -157,6 +158,22 @@ SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { } } + +SCENARIO("Check parents in valid NMODL constructs") { + nmodl::parser::NmodlDriver driver; + std::shared_ptr ast; + for (const auto& construct: nmodl_valid_constructs) { + // parse the string and get the ast + ast = driver.parse_string(construct.second.input); + GIVEN(construct.second.name) { + THEN("Check the parents in : " + construct.second.input) { + // check the parents + REQUIRE(!nmodl::visitor::test::CheckParentVisitor().check_ast(ast.get())); + } + } + } +} + //============================================================================= // Differential Equation Parser tests //============================================================================= diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/utils/nmodl_constructs.cpp index 9e95a139f6..bde2affd73 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.cpp @@ -60,7 +60,7 @@ namespace test_utils { * provided with the expected nmodl. */ -std::map nmdol_invalid_constructs{ +std::map nmodl_invalid_constructs{ // clang-format off { "title_1", diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.hpp b/test/nmodl/transpiler/utils/nmodl_constructs.hpp index 8e64f64b15..148960a3bc 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.hpp +++ b/test/nmodl/transpiler/utils/nmodl_constructs.hpp @@ -55,7 +55,7 @@ struct DiffEqTestCase { std::string method; }; -extern std::map nmdol_invalid_constructs; +extern std::map nmodl_invalid_constructs; extern std::map nmodl_valid_constructs; extern std::vector diff_eq_constructs; diff --git a/test/nmodl/transpiler/visitor/constant_folder.cpp b/test/nmodl/transpiler/visitor/constant_folder.cpp index ea837e5037..9e94828fc0 100644 --- a/test/nmodl/transpiler/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/visitor/constant_folder.cpp @@ -9,12 +9,14 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -32,6 +34,10 @@ std::string run_constant_folding_visitor(const std::string& text) { std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/visitor/defuse_analyze.cpp index 1b1294381f..db40fdd287 100644 --- a/test/nmodl/transpiler/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/visitor/defuse_analyze.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/lookup_visitor.hpp" @@ -17,6 +18,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -42,6 +44,10 @@ std::vector run_defuse_visitor(const std::string& text, const std::stri auto node = block.get(); chains.push_back(v.analyze(node, variable)); } + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return chains; } diff --git a/test/nmodl/transpiler/visitor/inline.cpp b/test/nmodl/transpiler/visitor/inline.cpp index 0d937cd000..490a220f6b 100644 --- a/test/nmodl/transpiler/visitor/inline.cpp +++ b/test/nmodl/transpiler/visitor/inline.cpp @@ -9,12 +9,15 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/symtab_visitor.hpp" + using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -31,6 +34,11 @@ std::string run_inline_visitor(const std::string& text) { InlineVisitor().visit_program(ast.get()); std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/json.cpp b/test/nmodl/transpiler/visitor/json.cpp index 0a1a2ed6f5..e9e2f98a0a 100644 --- a/test/nmodl/transpiler/visitor/json.cpp +++ b/test/nmodl/transpiler/visitor/json.cpp @@ -8,7 +8,6 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" #include "visitors/json_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -16,7 +15,6 @@ using json = nlohmann::json; using namespace nmodl; using namespace visitor; -using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -27,6 +25,7 @@ using nmodl::parser::NmodlDriver; std::string run_json_visitor(const std::string& text, bool compact = false) { NmodlDriver driver; auto ast = driver.parse_string(text); + return to_json(ast.get(), compact); } diff --git a/test/nmodl/transpiler/visitor/kinetic_block.cpp b/test/nmodl/transpiler/visitor/kinetic_block.cpp index 720aad371f..494e472fd9 100644 --- a/test/nmodl/transpiler/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/visitor/kinetic_block.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/lookup_visitor.hpp" @@ -17,6 +18,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -52,6 +54,10 @@ std::vector run_kinetic_block_visitor(const std::string& text) { results.push_back(to_nmodl(r.get())); } + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return results; } diff --git a/test/nmodl/transpiler/visitor/localize.cpp b/test/nmodl/transpiler/visitor/localize.cpp index 36b630f220..41f31ea2f9 100644 --- a/test/nmodl/transpiler/visitor/localize.cpp +++ b/test/nmodl/transpiler/visitor/localize.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -16,6 +17,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -33,6 +35,10 @@ std::string run_localize_visitor(const std::string& text) { std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/loop_unroll.cpp b/test/nmodl/transpiler/visitor/loop_unroll.cpp index 3697dc9c46..b9aa5ddf6a 100644 --- a/test/nmodl/transpiler/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/visitor/loop_unroll.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -17,6 +18,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -34,6 +36,10 @@ std::string run_loop_unroll_visitor(const std::string& text) { ConstantFolderVisitor().visit_program(ast.get()); LoopUnrollVisitor().visit_program(ast.get()); ConstantFolderVisitor().visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return to_nmodl(ast.get(), {AstNodeType::DEFINE}); } diff --git a/test/nmodl/transpiler/visitor/misc.cpp b/test/nmodl/transpiler/visitor/misc.cpp index c0b6553e6a..10cd7461ec 100644 --- a/test/nmodl/transpiler/visitor/misc.cpp +++ b/test/nmodl/transpiler/visitor/misc.cpp @@ -9,12 +9,14 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -31,14 +33,19 @@ void run_visitor_passes(const std::string& text) { SymtabVisitor v1; InlineVisitor v2; LocalizeVisitor v3; + CheckParentVisitor v4(true); v1.visit_program(ast.get()); v2.visit_program(ast.get()); v3.visit_program(ast.get()); + v4.visit_program(ast.get()); + v4.visit_program(ast.get()); v1.visit_program(ast.get()); v1.visit_program(ast.get()); + v4.visit_program(ast.get()); v2.visit_program(ast.get()); v3.visit_program(ast.get()); v2.visit_program(ast.get()); + v4.visit_program(ast.get()); } } diff --git a/test/nmodl/transpiler/visitor/neuron_solve.cpp b/test/nmodl/transpiler/visitor/neuron_solve.cpp index 997e1a9ebb..dfd408ba73 100644 --- a/test/nmodl/transpiler/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/visitor/neuron_solve.cpp @@ -9,12 +9,15 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/symtab_visitor.hpp" + using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -32,6 +35,10 @@ std::string run_cnexp_solve_visitor(const std::string& text) { NeuronSolveVisitor().visit_program(ast.get()); std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/nmodl.cpp b/test/nmodl/transpiler/visitor/nmodl.cpp index e5cde15f89..d24e3ced2f 100644 --- a/test/nmodl/transpiler/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/visitor/nmodl.cpp @@ -10,10 +10,12 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/nmodl_visitor.hpp" using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -29,6 +31,10 @@ std::string run_nmodl_visitor(const std::string& text) { std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/rename.cpp b/test/nmodl/transpiler/visitor/rename.cpp index b076806c76..20785dce6b 100644 --- a/test/nmodl/transpiler/visitor/rename.cpp +++ b/test/nmodl/transpiler/visitor/rename.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/rename_visitor.hpp" @@ -17,6 +18,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -35,6 +37,10 @@ std::string run_var_rename_visitor(const std::string& text, } std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/solve_block.cpp b/test/nmodl/transpiler/visitor/solve_block.cpp index 8ce6221867..723cb2be18 100644 --- a/test/nmodl/transpiler/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/visitor/solve_block.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/solve_block_visitor.hpp" @@ -16,6 +17,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -33,6 +35,10 @@ std::string run_solve_block_visitor(const std::string& text) { SolveBlockVisitor().visit_program(ast.get()); std::stringstream stream; NmodlPrintVisitor(stream).visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/steadystate.cpp b/test/nmodl/transpiler/visitor/steadystate.cpp index 361c5c696e..2d3f17138d 100644 --- a/test/nmodl/transpiler/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/visitor/steadystate.cpp @@ -16,10 +16,12 @@ //#include "visitors/nmodl_visitor.hpp" #include "visitors/steadystate_visitor.hpp" //#include "visitors/sympy_solver_visitor.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -61,6 +63,10 @@ std::vector run_steadystate_visitor( for (const auto& r: res) { results.push_back(to_nmodl(r.get())); } + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return results; } diff --git a/test/nmodl/transpiler/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/visitor/sympy_conductance.cpp index 534bc20f89..b469665c72 100644 --- a/test/nmodl/transpiler/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/visitor/sympy_conductance.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" @@ -18,6 +19,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -45,6 +47,9 @@ std::string run_sympy_conductance_visitor(const std::string& text) { // run SympyConductance on AST SympyConductanceVisitor().visit_program(ast.get()); + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; // return BREAKPOINT block as JSON string diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/visitor/sympy_solver.cpp index 070f9fa0d6..acadca6845 100644 --- a/test/nmodl/transpiler/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/visitor/sympy_solver.cpp @@ -9,6 +9,7 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" @@ -18,6 +19,7 @@ using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using ast::AstNodeType; @@ -51,6 +53,9 @@ std::vector run_sympy_solver_visitor( // run SympySolver on AST SympySolverVisitor(pade, cse).visit_program(ast.get()); + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; auto res = v_lookup.lookup(ast.get(), ret_nodetype); diff --git a/test/nmodl/transpiler/visitor/units.cpp b/test/nmodl/transpiler/visitor/units.cpp index c934f875dc..ded99209fb 100644 --- a/test/nmodl/transpiler/visitor/units.cpp +++ b/test/nmodl/transpiler/visitor/units.cpp @@ -13,12 +13,14 @@ #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" #include "utils/logger.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/units_visitor.hpp" using namespace nmodl; using namespace visitor; +using namespace test; using namespace test_utils; using nmodl::parser::NmodlDriver; @@ -101,6 +103,9 @@ std::string run_units_visitor(const std::string& text) { ss << "\n"; } + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return ss.str(); } diff --git a/test/nmodl/transpiler/visitor/var_usage.cpp b/test/nmodl/transpiler/visitor/var_usage.cpp index 619957c907..7f8146e2d9 100644 --- a/test/nmodl/transpiler/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/visitor/var_usage.cpp @@ -40,7 +40,7 @@ SCENARIO("Searching for variable name using VarUsageVisitor", "[visitor][var_usa )"; auto ast = to_ast(nmodl_text); - auto node = ast->blocks[0]; + auto node = ast->get_blocks().front(); WHEN("Looking for existing variable") { THEN("Can find variables") { @@ -75,7 +75,7 @@ SCENARIO("Searching for variable name using VarUsageVisitor", "[visitor][var_usa )"; auto ast = to_ast(nmodl_text); - auto node = ast->blocks[0]; + auto node = ast->get_blocks().front(); WHEN("Looking for existing variable in outer block") { THEN("Can find variables") { diff --git a/test/nmodl/transpiler/visitor/verbatim.cpp b/test/nmodl/transpiler/visitor/verbatim.cpp index 947dc6bf67..8b2c9646d7 100644 --- a/test/nmodl/transpiler/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/visitor/verbatim.cpp @@ -8,11 +8,12 @@ #include "catch/catch.hpp" #include "parser/nmodl_driver.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/verbatim_visitor.hpp" using namespace nmodl; using namespace visitor; - +using namespace test; using nmodl::parser::NmodlDriver; @@ -26,6 +27,10 @@ std::vector run_verbatim_visitor(const std::string& text) { VerbatimVisitor v; v.visit_program(ast.get()); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().visit_program(ast.get()); + return v.verbatim_blocks(); } From 0b5209c6cfcb062592dc240f036a387f4eed57ab Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 9 Apr 2020 11:42:59 +0200 Subject: [PATCH 253/871] Ast class is now generated with a Jinja template * Ast class: use C++ default destructor * node classes: * more `const` member functions * `get_token()` returned type is now constant * pass parameter by const reference * `is_foo` member methods are now marked with `const noexcept` specifiers * Code generator: generated files are marked read-only to prevent editing them by mistake * SymbolTable: mark some methods with const specifier. `print` methods are now const. * Cleaner `git status` * Move Ast member function definitions in ast.cpp * Add const specifier in classes defined in src/units/ / UnitTable.get_unit now throws * Previous implementation of UnitTable.get_unit inserted an entry in `table` if `unit_name` is not found. From the usage of this member function, it does seem to be the desired behavior. * code_generator: ensure destination has write permissions NMODL Repo SHA: BlueBrain/nmodl@54e41a8ca4af19fcdc1b5b873e36978d2f27bb59 --- src/nmodl/ast/ast_common.hpp | 1451 ----------------- src/nmodl/language/code_generator.py | 11 + src/nmodl/language/nodes.py | 13 +- src/nmodl/language/templates/ast/ast.cpp | 62 + src/nmodl/language/templates/ast/ast.hpp | 344 +++- src/nmodl/language/templates/ast/ast_decl.hpp | 4 + src/nmodl/language/templates/pybind/pyast.cpp | 4 + src/nmodl/language/templates/pybind/pyast.hpp | 22 +- .../language/templates/pybind/pysymtab.cpp | 4 + .../language/templates/pybind/pyvisitor.cpp | 4 + .../language/templates/pybind/pyvisitor.hpp | 4 + .../templates/visitors/ast_visitor.cpp | 4 + .../templates/visitors/ast_visitor.hpp | 4 + .../visitors/checkparent_visitor.cpp | 4 + .../visitors/checkparent_visitor.hpp | 4 + .../templates/visitors/json_visitor.cpp | 4 + .../templates/visitors/json_visitor.hpp | 4 + .../templates/visitors/lookup_visitor.cpp | 4 + .../templates/visitors/lookup_visitor.hpp | 4 + .../templates/visitors/nmodl_visitor.cpp | 4 + .../templates/visitors/nmodl_visitor.hpp | 4 + .../templates/visitors/symtab_visitor.cpp | 4 + .../templates/visitors/symtab_visitor.hpp | 4 + .../language/templates/visitors/visitor.hpp | 4 + src/nmodl/symtab/symbol_table.cpp | 6 +- src/nmodl/symtab/symbol_table.hpp | 8 +- src/nmodl/units/units.cpp | 61 +- src/nmodl/units/units.hpp | 55 +- src/nmodl/utils/table_data.cpp | 34 +- src/nmodl/utils/table_data.hpp | 4 +- src/nmodl/visitors/inline_visitor.hpp | 2 +- .../visitors/local_var_rename_visitor.hpp | 4 +- 32 files changed, 589 insertions(+), 1560 deletions(-) diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 5dfc0e64d3..eb854bb5c5 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -108,1456 +108,5 @@ static const std::string ReactionOpNames[] = {"<->", "<<", "->"}; /** @} */ // end of ast_prop - -/** - * \page ast_design Design of Abstract Syntax Tree (AST) - * - * This page describes the AST design aspects. - * - * \tableofcontents - * - * \section sec_1 AST Class Hierarchy - * This section describes the AST design - * - * \section sec_2 Block Scoped Nodes - * This section describes block scoped nodes. - * - * \section sec_3 Symbol Table Nodes - * This section describes nodes with symbol table. - */ - -/** - * @defgroup ast_class AST Classes - * @ingroup ast - * @brief Classes for implementing Abstract Syntax Tree (AST) - * @{ - */ - -/** - * \brief Base class for all Abstract Syntax Tree node types - * - * Every node in the Abstract Syntax Tree is inherited from base class - * ast::Ast. This class provides base properties and pure virtual - * functions that must be implemented by base classes. We inherit from - * std::enable_shared_from_this to get a `shared_ptr` from `this` pointer. - * - * \todo With the ast::Node as another top level node, this can be removed - * in the future. - */ -struct Ast: public std::enable_shared_from_this { - private: - /** - * \brief Generic pointer to the parent - * - * Children types can be known at compile time. Conversely, many parents - * can have the same children type. Thus, this is just a pointer to - * the base class. The pointer to the parent cannot have ownership - * (circular ownership problem). weak_ptr you say? Yes, however weak_ptr - * can be instantiated from shared_ptr (not this). Whys is this a problem? - * In bison things must be passed around as raw pointers (because it uses - * unions etc.) and there are cases where the shared_ptr to the parent - * was not yet created while the child is added (throwing a bad_weak_ptr - * exception). - * - * i.e. in bison the lines: - * - * ast::WatchStatement* a = new ast::WatchStatement(); - * yylhs.value.as< ast::WatchStatement* > ()->add_watch(a); - * - * would throw a bad_weak_ptr exception because when you call add_watch - * the shared_ptr_from_this to "a" is not yet created. - */ - Ast* parent = nullptr; - - public: - /// \name Ctor & dtor - /// \{ - - Ast() = default; - - virtual ~Ast() {} - - /// \} - - - /// \name Pure Virtual Functions - /// \{ - - /** - * \brief Return type (ast::AstNodeType) of AST node - * - * Every node in the ast has a type defined in ast::AstNodeType. - * This type is can be used to check/compare node types. - */ - virtual AstNodeType get_node_type() = 0; - - /** - * \brief Return type (ast::AstNodeType) of ast node as std::string - * - * Every node in the ast has a type defined in ast::AstNodeType. - * This type name can be returned as a std::string for printing - * ast to text/json form. - * - * @return name of the node type as a string - * - * \sa Ast::get_node_name - */ - virtual std::string get_node_type_name() = 0; - - /** - * \brief Return NMODL statement of ast node as std::string - * - * Every node is related to a special statement in the NMODL. This - * statement can be returned as a std::string for printing to - * text/json form. - * - * @return name of the statement as a string - * - * \sa Ast::get_nmodl_name - */ - virtual std::string get_nmodl_name() { - throw std::runtime_error("get_nmodl_name not implemented"); - } - - /** - * \brief Accept (or visit) the AST node using current visitor - * - * Instead of visiting children of AST node, like Ast::visit_children, - * accept allows to visit the current node itself using the concrete - * visitor provided. - * - * @param v Concrete visitor that will be used to recursively visit children - * - * \note Below is an example of `accept` method implementation which shows how - * visitor method corresponding to ast::IndexedName node is called allowing - * to visit the node itself in the visitor. - * - * \code{.cpp} - * void IndexedName::accept(visitor::Visitor& v) override { - * v.visit_indexed_name(this); - * } - * \endcode - * - */ - virtual void accept(visitor::Visitor& v) = 0; - - /** - * \brief Visit children i.e. member of AST node using provided visitor - * - * Different nodes in the AST have different members (i.e. children). This method - * recursively visits children using provided concrete visitor. - * - * @param v Concrete visitor that will be used to recursively visit node - * - * \note Below is an example of `visit_children` method implementation which shows - * ast::IndexedName node children are visited instead of node itself. - * - * \code{.cpp} - * void IndexedName::visit_children(visitor::Visitor& v) { - * name->accept(v); - * length->accept(v); - * } - * \endcode - */ - virtual void visit_children(visitor::Visitor& v) = 0; - - /** - * \brief Create a copy of the current node - * - * Recursively make a new copy/clone of the current node including - * all members and return a pointer to the node. This is used for - * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the - * ast. - * - * @return pointer to the clone/copy of the current node - */ - virtual Ast* clone() const { - throw std::logic_error("clone not implemented"); - } - - /// \} - - - /// \name Not implemented - /// \{ - - /** - * \brief Return name of of the node - * - * Some ast nodes have a member marked designated as node name. For example, - * in case of ast::FunctionCall, name of the function is returned as a node - * name. Note that this is different from ast node type name and not every - * ast node has name. - * - * @return name of the node as std::string - * - * \sa Ast::get_node_type_name - */ - virtual std::string get_node_name() { - throw std::logic_error("get_node_name() not implemented"); - } - - /** - * \brief Return associated token for the AST node - * - * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor - * can insert new nodes in the ast as a solution of ODEs. In this case, we return - * nullptr to store in the nmodl::symtab::SymbolTable. - * - * @return pointer to token if exist otherwise nullptr - */ - virtual ModToken* get_token() { - return nullptr; - } - - /** - * \brief Return associated symbol table for the AST node - * - * Certain ast nodes (e.g. inherited from ast::Block) have associated - * symbol table. These nodes have nmodl::symtab::SymbolTable as member - * and it can be accessed using this method. - * - * @return pointer to the symbol table - * - * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor - */ - virtual symtab::SymbolTable* get_symbol_table() { - throw std::runtime_error("get_symbol_table not implemented"); - } - - /** - * \brief Return associated statement block for the AST node - * - * Top level block nodes encloses all statements in the ast::StatementBlock. - * For example, ast::BreakpointBlock has all statements in curly brace (`{ }`) - * stored in ast::StatementBlock : - * - * \code - * BREAKPOINT { - * SOLVE states METHOD cnexp - * gNaTs2_t = gNaTs2_tbar*m*m*m*h - * ina = gNaTs2_t*(v-ena) - * } - * \endcode - * - * This method return enclosing statement block. - * - * @return pointer to the statement block if exist - * - * \sa ast::StatementBlock - */ - virtual const std::shared_ptr& get_statement_block() const { - throw std::runtime_error("get_statement_block not implemented"); - } - - /** - * \brief Set symbol table for the AST node - * - * Top level, block scoped nodes store symbol table in the ast node. - * nmodl::visitor::SymtabVisitor then used this method to setup symbol table - * for every node in the ast. - * - * \sa nmodl::visitor::SymtabVisitor - */ - virtual void set_symbol_table(symtab::SymbolTable* /*symtab*/) { - throw std::runtime_error("set_symbol_table not implemented"); - } - - /** - * \brief Set name for the AST node - * - * Some ast nodes have a member marked designated as node name (e.g. nodes - * derived from ast::Identifier). This method is used to set new name for those - * nodes. This useful for passes like nmodl::visitor::RenameVisitor. - * - * \sa Ast::get_node_type_name Ast::get_node_name - */ - virtual void set_name(std::string /*name*/) { - throw std::runtime_error("set_name not implemented"); - } - - /** - * \brief Negate the value of AST node - * - * Parser parse `-x` in two parts : `x` and then `-`. Once second token - * `-` is encountered, the corresponding value of ast node needs to be - * multiplied by `-1` for ast::Number node types or apply `!` operator - for the nodes of type ast::Boolean. - */ - virtual void negate() { - throw std::runtime_error("negate not implemented"); - } - /// \} - - /// get std::shared_ptr from `this` pointer of the AST node - virtual std::shared_ptr get_shared_ptr() { - return std::static_pointer_cast(shared_from_this()); - } - - /** - *\brief Check if the ast node is an instance of ast::Ast - * @return true if object of type ast::Ast - */ - virtual bool is_ast() { - return true; - } - - /** - *\brief Check if the ast node is an instance of ast::Node - * @return true if object of type ast::Node - */ - virtual bool is_node() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Statement - * @return true if object of type ast::Statement - */ - virtual bool is_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Expression - * @return true if object of type ast::Expression - */ - virtual bool is_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Block - * @return true if object of type ast::Block - */ - virtual bool is_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Identifier - * @return true if object of type ast::Identifier - */ - virtual bool is_identifier() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Number - * @return true if object of type ast::Number - */ - virtual bool is_number() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::String - * @return true if object of type ast::String - */ - virtual bool is_string() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Integer - * @return true if object of type ast::Integer - */ - virtual bool is_integer() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Float - * @return true if object of type ast::Float - */ - virtual bool is_float() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Double - * @return true if object of type ast::Double - */ - virtual bool is_double() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Boolean - * @return true if object of type ast::Boolean - */ - virtual bool is_boolean() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Name - * @return true if object of type ast::Name - */ - virtual bool is_name() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PrimeName - * @return true if object of type ast::PrimeName - */ - virtual bool is_prime_name() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::VarName - * @return true if object of type ast::VarName - */ - virtual bool is_var_name() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::IndexedName - * @return true if object of type ast::IndexedName - */ - virtual bool is_indexed_name() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Argument - * @return true if object of type ast::Argument - */ - virtual bool is_argument() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ReactVarName - * @return true if object of type ast::ReactVarName - */ - virtual bool is_react_var_name() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ReadIonVar - * @return true if object of type ast::ReadIonVar - */ - virtual bool is_read_ion_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::WriteIonVar - * @return true if object of type ast::WriteIonVar - */ - virtual bool is_write_ion_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NonspecificCurVar - * @return true if object of type ast::NonspecificCurVar - */ - virtual bool is_nonspecific_cur_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ElectrodeCurVar - * @return true if object of type ast::ElectrodeCurVar - */ - virtual bool is_electrode_cur_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::SectionVar - * @return true if object of type ast::SectionVar - */ - virtual bool is_section_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::RangeVar - * @return true if object of type ast::RangeVar - */ - virtual bool is_range_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::GlobalVar - * @return true if object of type ast::GlobalVar - */ - virtual bool is_global_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PointerVar - * @return true if object of type ast::PointerVar - */ - virtual bool is_pointer_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BbcorePointerVar - * @return true if object of type ast::BbcorePointerVar - */ - virtual bool is_bbcore_pointer_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ExternVar - * @return true if object of type ast::ExternVar - */ - virtual bool is_extern_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ThreadsafeVar - * @return true if object of type ast::ThreadsafeVar - */ - virtual bool is_threadsafe_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ParamBlock - * @return true if object of type ast::ParamBlock - */ - virtual bool is_param_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::StepBlock - * @return true if object of type ast::StepBlock - */ - virtual bool is_step_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::IndependentBlock - * @return true if object of type ast::IndependentBlock - */ - virtual bool is_independent_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::AssignedBlock - * @return true if object of type ast::AssignedBlock - */ - virtual bool is_assigned_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::StateBlock - * @return true if object of type ast::StateBlock - */ - virtual bool is_state_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PlotBlock - * @return true if object of type ast::PlotBlock - */ - virtual bool is_plot_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::InitialBlock - * @return true if object of type ast::InitialBlock - */ - virtual bool is_initial_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ConstructorBlock - * @return true if object of type ast::ConstructorBlock - */ - virtual bool is_constructor_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::DestructorBlock - * @return true if object of type ast::DestructorBlock - */ - virtual bool is_destructor_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::StatementBlock - * @return true if object of type ast::StatementBlock - */ - virtual bool is_statement_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::DerivativeBlock - * @return true if object of type ast::DerivativeBlock - */ - virtual bool is_derivative_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LinearBlock - * @return true if object of type ast::LinearBlock - */ - virtual bool is_linear_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NonLinearBlock - * @return true if object of type ast::NonLinearBlock - */ - virtual bool is_non_linear_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::DiscreteBlock - * @return true if object of type ast::DiscreteBlock - */ - virtual bool is_discrete_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PartialBlock - * @return true if object of type ast::PartialBlock - */ - virtual bool is_partial_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::FunctionTableBlock - * @return true if object of type ast::FunctionTableBlock - */ - virtual bool is_function_table_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::FunctionBlock - * @return true if object of type ast::FunctionBlock - */ - virtual bool is_function_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ProcedureBlock - * @return true if object of type ast::ProcedureBlock - */ - virtual bool is_procedure_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NetReceiveBlock - * @return true if object of type ast::NetReceiveBlock - */ - virtual bool is_net_receive_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::SolveBlock - * @return true if object of type ast::SolveBlock - */ - virtual bool is_solve_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BreakpointBlock - * @return true if object of type ast::BreakpointBlock - */ - virtual bool is_breakpoint_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::TerminalBlock - * @return true if object of type ast::TerminalBlock - */ - virtual bool is_terminal_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BeforeBlock - * @return true if object of type ast::BeforeBlock - */ - virtual bool is_before_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::AfterBlock - * @return true if object of type ast::AfterBlock - */ - virtual bool is_after_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BABlock - * @return true if object of type ast::BABlock - */ - virtual bool is_ba_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ForNetcon - * @return true if object of type ast::ForNetcon - */ - virtual bool is_for_netcon() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::KineticBlock - * @return true if object of type ast::KineticBlock - */ - virtual bool is_kinetic_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::MatchBlock - * @return true if object of type ast::MatchBlock - */ - virtual bool is_match_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::UnitBlock - * @return true if object of type ast::UnitBlock - */ - virtual bool is_unit_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ConstantBlock - * @return true if object of type ast::ConstantBlock - */ - virtual bool is_constant_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NeuronBlock - * @return true if object of type ast::NeuronBlock - */ - virtual bool is_neuron_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Unit - * @return true if object of type ast::Unit - */ - virtual bool is_unit() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::DoubleUnit - * @return true if object of type ast::DoubleUnit - */ - virtual bool is_double_unit() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LocalVar - * @return true if object of type ast::LocalVar - */ - virtual bool is_local_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Limits - * @return true if object of type ast::Limits - */ - virtual bool is_limits() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NumberRange - * @return true if object of type ast::NumberRange - */ - virtual bool is_number_range() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PlotVar - * @return true if object of type ast::PlotVar - */ - virtual bool is_plot_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ConstantVar - * @return true if object of type ast::ConstantVar - */ - virtual bool is_constant_var() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BinaryOperator - * @return true if object of type ast::BinaryOperator - */ - virtual bool is_binary_operator() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::UnaryOperator - * @return true if object of type ast::UnaryOperator - */ - virtual bool is_unary_operator() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ReactionOperator - * @return true if object of type ast::ReactionOperator - */ - virtual bool is_reaction_operator() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ParenExpression - * @return true if object of type ast::ParenExpression - */ - virtual bool is_paren_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BinaryExpression - * @return true if object of type ast::BinaryExpression - */ - virtual bool is_binary_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::DiffEqExpression - * @return true if object of type ast::DiffEqExpression - */ - virtual bool is_diff_eq_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::UnaryExpression - * @return true if object of type ast::UnaryExpression - */ - virtual bool is_unary_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NonLinEquation - * @return true if object of type ast::NonLinEquation - */ - virtual bool is_non_lin_equation() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LinEquation - * @return true if object of type ast::LinEquation - */ - virtual bool is_lin_equation() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::FunctionCall - * @return true if object of type ast::FunctionCall - */ - virtual bool is_function_call() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::FirstLastTypeIndex - * @return true if object of type ast::FirstLastTypeIndex - */ - virtual bool is_first_last_type_index() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Watch - * @return true if object of type ast::Watch - */ - virtual bool is_watch() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::QueueExpressionType - * @return true if object of type ast::QueueExpressionType - */ - virtual bool is_queue_expression_type() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Match - * @return true if object of type ast::Match - */ - virtual bool is_match() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BABlockType - * @return true if object of type ast::BABlockType - */ - virtual bool is_ba_block_type() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::UnitDef - * @return true if object of type ast::UnitDef - */ - virtual bool is_unit_def() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::FactorDef - * @return true if object of type ast::FactorDef - */ - virtual bool is_factor_def() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Valence - * @return true if object of type ast::Valence - */ - virtual bool is_valence() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::UnitState - * @return true if object of type ast::UnitState - */ - virtual bool is_unit_state() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LocalListStatement - * @return true if object of type ast::LocalListStatement - */ - virtual bool is_local_list_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Model - * @return true if object of type ast::Model - */ - virtual bool is_model() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Define - * @return true if object of type ast::Define - */ - virtual bool is_define() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Include - * @return true if object of type ast::Include - */ - virtual bool is_include() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ParamAssign - * @return true if object of type ast::ParamAssign - */ - virtual bool is_param_assign() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Stepped - * @return true if object of type ast::Stepped - */ - virtual bool is_stepped() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::IndependentDefinition - * @return true if object of type ast::IndependentDefinition - */ - virtual bool is_independent_definition() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::AssignedDefinition - * @return true if object of type ast::AssignedDefinition - */ - virtual bool is_assigned_definition() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PlotDeclaration - * @return true if object of type ast::PlotDeclaration - */ - virtual bool is_plot_declaration() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ConductanceHint - * @return true if object of type ast::ConductanceHint - */ - virtual bool is_conductance_hint() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ExpressionStatement - * @return true if object of type ast::ExpressionStatement - */ - virtual bool is_expression_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ProtectStatement - * @return true if object of type ast::ProtectStatement - */ - virtual bool is_protect_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::FromStatement - * @return true if object of type ast::FromStatement - */ - virtual bool is_from_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ForAllStatement - * @return true if object of type ast::ForAllStatement - */ - virtual bool is_for_all_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::WhileStatement - * @return true if object of type ast::WhileStatement - */ - virtual bool is_while_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::IfStatement - * @return true if object of type ast::IfStatement - */ - virtual bool is_if_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ElseIfStatement - * @return true if object of type ast::ElseIfStatement - */ - virtual bool is_else_if_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ElseStatement - * @return true if object of type ast::ElseStatement - */ - virtual bool is_else_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PartialEquation - * @return true if object of type ast::PartialEquation - */ - virtual bool is_partial_equation() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Ast - * @return true if object of type ast::Ast - */ - virtual bool is_partial_boundary() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::PartialBoundary - * @return true if object of type ast::PartialBoundary - */ - virtual bool is_watch_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::MutexLock - * @return true if object of type ast::MutexLock - */ - virtual bool is_mutex_lock() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::MutexUnlock - * @return true if object of type ast::MutexUnlock - */ - virtual bool is_mutex_unlock() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Reset - * @return true if object of type ast::Reset - */ - virtual bool is_reset() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Sens - * @return true if object of type ast::Sens - */ - virtual bool is_sens() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Conserve - * @return true if object of type ast::Conserve - */ - virtual bool is_conserve() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Compartment - * @return true if object of type ast::Compartment - */ - virtual bool is_compartment() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LonDifuse - * @return true if object of type ast::LonDifuse - */ - virtual bool is_lon_difuse() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ReactionStatement - * @return true if object of type ast::ReactionStatement - */ - virtual bool is_reaction_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LagStatement - * @return true if object of type ast::LagStatement - */ - virtual bool is_lag_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::QueueStatement - * @return true if object of type ast::QueueStatement - */ - virtual bool is_queue_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ConstantStatement - * @return true if object of type ast::ConstantStatement - */ - virtual bool is_constant_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::TableStatement - * @return true if object of type ast::TableStatement - */ - virtual bool is_table_statement() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Suffix - * @return true if object of type ast::Suffix - */ - virtual bool is_suffix() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Useion - * @return true if object of type ast::Useion - */ - virtual bool is_useion() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Nonspecific - * @return true if object of type ast::Nonspecific - */ - virtual bool is_nonspecific() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ElctrodeCurrent - * @return true if object of type ast::ElctrodeCurrent - */ - virtual bool is_elctrode_current() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Section - * @return true if object of type ast::Section - */ - virtual bool is_section() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Range - * @return true if object of type ast::Range - */ - virtual bool is_range() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Global - * @return true if object of type ast::Global - */ - virtual bool is_global() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Pointer - * @return true if object of type ast::Pointer - */ - virtual bool is_pointer() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BbcorePointer - * @return true if object of type ast::BbcorePointer - */ - virtual bool is_bbcore_pointer() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::External - * @return true if object of type ast::External - */ - virtual bool is_external() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::ThreadSafe - * @return true if object of type ast::ThreadSafe - */ - virtual bool is_thread_safe() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Verbatim - * @return true if object of type ast::Verbatim - */ - virtual bool is_verbatim() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::LineComment - * @return true if object of type ast::LineComment - */ - virtual bool is_line_comment() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::BlockComment - * @return true if object of type ast::BlockComment - */ - virtual bool is_block_comment() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::Program - * @return true if object of type ast::Program - */ - virtual bool is_program() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::NrnStateBlock - * @return true if object of type ast::NrnStateBlock - */ - virtual bool is_nrn_state_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::EigenNewtonSolverBlock - * @return true if object of type ast::EigenNewtonSolverBlock - */ - virtual bool is_eigen_newton_solver_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::EigenLinearSolverBlock - * @return true if object of type ast::EigenLinearSolverBlock - */ - virtual bool is_eigen_linear_solver_block() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::WrappedExpression - * @return true if object of type ast::WrappedExpression - */ - virtual bool is_wrapped_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::DerivimplicitCallback - * @return true if object of type ast::DerivimplicitCallback - */ - virtual bool is_derivimplicit_callback() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::SolutionExpression - * @return true if object of type ast::SolutionExpression - */ - virtual bool is_solution_expression() { - return false; - } - - /** - *\brief Check if the ast node is an instance of ast::OntologyStatement - * @return true if object of type ast::OntologyStatement - */ - virtual bool is_ontology_statement() { - return false; - } - - /** - *\brief Parent getter - * - * returning a raw pointer may create less problems that the - * shared_from_this from the parent. - * - * \ref Check Ast::parent for more information - */ - virtual Ast* get_parent() const { - return parent; - } - - /** - *\brief Parent setter - * - * Usually, the parent parent pointer cannot be set in the constructor - * because children are generally build BEFORE the parent. Conversely, - * we set children parents directly in the parent constructor using - * set_parent_in_children() - * - * \ref Check Ast::parent for more information - */ - virtual void set_parent(Ast* p) { - parent = p; - } - - /** - *\brief Set this object as parent for all the children - * - * This should be called in every object (with children) constructor - * to set the parents. - */ - virtual void set_parent_in_children() {} -}; - -/** @} */ // end of ast_class - } // namespace ast } // namespace nmodl diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index aa12859823..8153bd1e6d 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -11,8 +11,10 @@ import os from pathlib import Path, PurePath import shutil +import stat import subprocess import tempfile + import jinja2 from parser import LanguageParser @@ -80,6 +82,10 @@ if clang_format: subprocess.check_call(clang_format + ['-i', tmp_path]) if not filecmp.cmp(str(destination_file), tmp_path): + # ensure destination file has write permissions + mode = os.stat(destination_file).st_mode + rm_write_mask = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH + os.chmod(destination_file, mode | rm_write_mask) shutil.move(tmp_path, destination_file) updated_files.append(str(filepath.name)) else: @@ -88,6 +94,11 @@ updated_files.append(str(filepath.name)) if clang_format: subprocess.check_call(clang_format + ['-i', destination_file]) + # remove write permissions on the generated file + mode = os.stat(destination_file).st_mode + rm_write_mask = ~ (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + os.chmod(destination_file, mode & rm_write_mask) + if updated_files: logging.info(' Updating out of date template files : %s', ' '.join(updated_files)) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 45596d83bc..e3c84057d1 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -218,7 +218,7 @@ def get_add_methods(self): if self.optional: set_parent = f""" if (n) {{ - n->set_parent(this); + n->set_parent(this); }} """ method = f""" @@ -237,7 +237,6 @@ def get_add_methods(self): */ void emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n) {{ {self.varname}.emplace_back(n); - // set parents {set_parent} }} @@ -260,7 +259,7 @@ def get_add_methods(self): */ {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n) {{ {set_parent} - + return {self.varname}.insert(position, n); }} /** @@ -274,7 +273,7 @@ def get_add_methods(self): //set parents {set_parent} }} - + {self.varname}.insert(position, first, last); }} @@ -284,7 +283,7 @@ def get_add_methods(self): void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n) {{ //set parents {set_parent} - + {self.varname}[position - {self.varname}.begin()].reset(n); }} @@ -294,7 +293,7 @@ def get_add_methods(self): void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n) {{ //set parents {set_parent} - + {self.varname}[position - {self.varname}.begin()] = n; }} """ @@ -318,7 +317,7 @@ def get_node_name_method(self): * * \\sa Ast::get_node_type_name */ - virtual std::string get_node_name() override {{ + virtual std::string get_node_name() const override {{ return {self.varname}->{method_name}(); }}""" s = textwrap.dedent(method) diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 4f2702ffd6..d2f7591b87 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" @@ -16,6 +20,64 @@ namespace nmodl { namespace ast { +/// +/// Ast member function definition +/// + +Ast *Ast::clone() const { throw std::logic_error("clone not implemented"); } + +std::string Ast::get_node_name() const { + throw std::logic_error("get_node_name() not implemented"); +} + +const std::shared_ptr& Ast::get_statement_block() const { + throw std::runtime_error("get_statement_block not implemented"); +} + +const ModToken *Ast::get_token() const { return nullptr; } + +symtab::SymbolTable *Ast::get_symbol_table() { + throw std::runtime_error("get_symbol_table not implemented"); +} + +void Ast::set_symbol_table(symtab::SymbolTable * /*symtab*/) { + throw std::runtime_error("set_symbol_table not implemented"); +} + +void Ast::set_name(const std::string & /*name*/) { + throw std::runtime_error("set_name not implemented"); +} + +void Ast::negate() { throw std::runtime_error("negate not implemented"); } + +std::shared_ptr Ast::get_shared_ptr() { + return std::static_pointer_cast(shared_from_this()); +} + +std::shared_ptr Ast::get_shared_ptr() const { + return std::static_pointer_cast(shared_from_this()); +} + +bool Ast::is_ast() const noexcept { return true; } + +{% for node in nodes %} +bool Ast::is_{{ node.class_name | snake_case }} () const noexcept { return false; } + +{% endfor %} + +Ast* Ast::get_parent() const { + return parent; +} + +void Ast::set_parent(Ast* p) { + parent = p; +} + +void Ast::set_parent_in_children() { + throw std::runtime_error("set_parent_in_children not implemented"); +} + + {% for node in nodes %} /// diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index f8c8883ce1..e70d98b03c 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** @@ -38,6 +42,324 @@ namespace nmodl { namespace ast { +/** + * \page ast_design Design of Abstract Syntax Tree (AST) + * + * This page describes the AST design aspects. + * + * \tableofcontents + * + * \section sec_1 AST Class Hierarchy + * This section describes the AST design + * + * \section sec_2 Block Scoped Nodes + * This section describes block scoped nodes. + * + * \section sec_3 Symbol Table Nodes + * This section describes nodes with symbol table. + */ + +/** + * @defgroup ast_class AST Classes + * @ingroup ast + * @brief Classes for implementing Abstract Syntax Tree (AST) + * @{ + */ + +/** + * \brief Base class for all Abstract Syntax Tree node types + * + * Every node in the Abstract Syntax Tree is inherited from base class + * ast::Ast. This class provides base properties and pure virtual + * functions that must be implemented by base classes. We inherit from + * std::enable_shared_from_this to get a `shared_ptr` from `this` pointer. + * + * \todo With the ast::Node as another top level node, this can be removed + * in the future. + */ +struct Ast: public std::enable_shared_from_this { + private: + /** + * \brief Generic pointer to the parent + * + * Children types can be known at compile time. Conversely, many parents + * can have the same children type. Thus, this is just a pointer to + * the base class. The pointer to the parent cannot have ownership + * (circular ownership problem). weak_ptr you say? Yes, however weak_ptr + * can be instantiated from shared_ptr (not this). Whys is this a problem? + * In bison things must be passed around as raw pointers (because it uses + * unions etc.) and there are cases where the shared_ptr to the parent + * was not yet created while the child is added (throwing a bad_weak_ptr + * exception). + * + * i.e. in bison the lines: + * + * ast::WatchStatement* a = new ast::WatchStatement(); + * yylhs.value.as< ast::WatchStatement* > ()->add_watch(a); + * + * would throw a bad_weak_ptr exception because when you call add_watch + * the shared_ptr_from_this to "a" is not yet created. + */ + Ast* parent = nullptr; + + public: + + /// \name Ctor & dtor + /// \{ + + Ast() = default; + + virtual ~Ast() = default; + + /// \} + + + /// \name Pure Virtual Functions + /// \{ + + /** + * \brief Return type (ast::AstNodeType) of AST node + * + * Every node in the ast has a type defined in ast::AstNodeType. + * This type is can be used to check/compare node types. + */ + virtual AstNodeType get_node_type() const = 0; + + /** + * \brief Return type (ast::AstNodeType) of ast node as std::string + * + * Every node in the ast has a type defined in ast::AstNodeType. + * This type name can be returned as a std::string for printing + * ast to text/json form. + * + * @return name of the node type as a string + * + * \sa Ast::get_node_name + */ + virtual std::string get_node_type_name() const = 0; + + /** + * \brief Return NMODL statement of ast node as std::string + * + * Every node is related to a special statement in the NMODL. This + * statement can be returned as a std::string for printing to + * text/json form. + * + * @return name of the statement as a string + * + * \sa Ast::get_nmodl_name + */ + virtual std::string get_nmodl_name() const { + throw std::runtime_error("get_nmodl_name not implemented"); + } + + /** + * \brief Accept (or visit) the AST node using current visitor + * + * Instead of visiting children of AST node, like Ast::visit_children, + * accept allows to visit the current node itself using the concrete + * visitor provided. + * + * @param v Concrete visitor that will be used to recursively visit children + * + * \note Below is an example of `accept` method implementation which shows how + * visitor method corresponding to ast::IndexedName node is called allowing + * to visit the node itself in the visitor. + * + * \code{.cpp} + * void IndexedName::accept(visitor::Visitor& v) override { + * v.visit_indexed_name(this); + * } + * \endcode + * + */ + virtual void accept(visitor::Visitor& v) = 0; + + /** + * \brief Visit children i.e. member of AST node using provided visitor + * + * Different nodes in the AST have different members (i.e. children). This method + * recursively visits children using provided concrete visitor. + * + * @param v Concrete visitor that will be used to recursively visit node + * + * \note Below is an example of `visit_children` method implementation which shows + * ast::IndexedName node children are visited instead of node itself. + * + * \code{.cpp} + * void IndexedName::visit_children(visitor::Visitor& v) { + * name->accept(v); + * length->accept(v); + * } + * \endcode + */ + virtual void visit_children(visitor::Visitor& v) = 0; + + /** + * \brief Create a copy of the current node + * + * Recursively make a new copy/clone of the current node including + * all members and return a pointer to the node. This is used for + * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the + * ast. + * + * @return pointer to the clone/copy of the current node + */ + virtual Ast* clone() const; + + /// \} + + /// \name Not implemented + /// \{ + + /** + * \brief Return name of of the node + * + * Some ast nodes have a member marked designated as node name. For example, + * in case of ast::FunctionCall, name of the function is returned as a node + * name. Note that this is different from ast node type name and not every + * ast node has name. + * + * @return name of the node as std::string + * + * \sa Ast::get_node_type_name + */ + virtual std::string get_node_name() const; + + /** + * \brief Return associated token for the AST node + * + * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor + * can insert new nodes in the ast as a solution of ODEs. In this case, we return + * nullptr to store in the nmodl::symtab::SymbolTable. + * + * @return pointer to token if exist otherwise nullptr + */ + virtual const ModToken* get_token() const; + + /** + * \brief Return associated symbol table for the AST node + * + * Certain ast nodes (e.g. inherited from ast::Block) have associated + * symbol table. These nodes have nmodl::symtab::SymbolTable as member + * and it can be accessed using this method. + * + * @return pointer to the symbol table + * + * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor + */ + virtual symtab::SymbolTable* get_symbol_table(); + + /** + * \brief Return associated statement block for the AST node + * + * Top level block nodes encloses all statements in the ast::StatementBlock. + * For example, ast::BreakpointBlock has all statements in curly brace (`{ }`) + * stored in ast::StatementBlock : + * + * \code + * BREAKPOINT { + * SOLVE states METHOD cnexp + * gNaTs2_t = gNaTs2_tbar*m*m*m*h + * ina = gNaTs2_t*(v-ena) + * } + * \endcode + * + * This method return enclosing statement block. + * + * @return pointer to the statement block if exist + * + * \sa ast::StatementBlock + */ + virtual const std::shared_ptr& get_statement_block() const; + + /** + * \brief Set symbol table for the AST node + * + * Top level, block scoped nodes store symbol table in the ast node. + * nmodl::visitor::SymtabVisitor then used this method to setup symbol table + * for every node in the ast. + * + * \sa nmodl::visitor::SymtabVisitor + */ + virtual void set_symbol_table(symtab::SymbolTable* symtab); + + /** + * \brief Set name for the AST node + * + * Some ast nodes have a member marked designated as node name (e.g. nodes + * derived from ast::Identifier). This method is used to set new name for those + * nodes. This useful for passes like nmodl::visitor::RenameVisitor. + * + * \sa Ast::get_node_type_name Ast::get_node_name + */ + virtual void set_name(const std::string& name); + + /** + * \brief Negate the value of AST node + * + * Parser parse `-x` in two parts : `x` and then `-`. Once second token + * `-` is encountered, the corresponding value of ast node needs to be + * multiplied by `-1` for ast::Number node types or apply `!` operator + for the nodes of type ast::Boolean. + */ + virtual void negate(); + /// \} + + /// get std::shared_ptr from `this` pointer of the AST node + virtual std::shared_ptr get_shared_ptr(); + + /// get std::shared_ptr from `this` pointer of the AST node + virtual std::shared_ptr get_shared_ptr() const; + + /** + *\brief Check if the ast node is an instance of ast::Ast + * @return true if object of type ast::Ast + */ + virtual bool is_ast() const noexcept; + + {% for node in nodes %} + /** + * \brief Check if the ast node is an instance of ast::{{ node.class_name }} + * \return true if object of type ast::OntologyStatement + */ + virtual bool is_{{ node.class_name | snake_case }} () const noexcept; + + {% endfor %} + + /** + *\brief Parent getter + * + * returning a raw pointer may create less problems that the + * shared_from_this from the parent. + * + * \ref Check Ast::parent for more information + */ + virtual Ast* get_parent() const; + + /** + *\brief Parent setter + * + * Usually, the parent parent pointer cannot be set in the constructor + * because children are generally build BEFORE the parent. Conversely, + * we set children parents directly in the parent constructor using + * set_parent_in_children() + * + * \ref Check Ast::parent for more information + */ + virtual void set_parent(Ast* p); + + /** + *\brief Set this object as parent for all the children + * + * This should be called in every object (with children) constructor + * to set the parents. + */ + virtual void set_parent_in_children(); +}; + +/** @} */ // end of ast_class + /** * @addtogroup ast_class * @ingroup ast @@ -113,7 +435,7 @@ namespace ast { *\brief Check if the ast node is an instance of ast::{{ node.class_name }} * @return true as object is of type ast::{{ node.class_name }} */ - bool is_{{ node.class_name | snake_case }} () override { + bool is_{{ node.class_name | snake_case }} () const noexcept override { return true; } @@ -145,7 +467,7 @@ namespace ast { * * \sa Ast::get_node_type_name */ - {{ virtual(node) }} AstNodeType get_node_type() override { + {{ virtual(node) }} AstNodeType get_node_type() const noexcept override { return AstNodeType::{{ node.ast_enum_name }}; } @@ -160,7 +482,7 @@ namespace ast { * * \sa Ast::get_node_name */ - {{ virtual(node) }} std::string get_node_type_name() override { + {{ virtual(node) }} std::string get_node_type_name() const noexcept override { return "{{ node.class_name }}"; } @@ -176,7 +498,7 @@ namespace ast { * * \sa Ast::get_nmodl_name */ - {{ virtual(node) }} std::string get_nmodl_name() override { + {{ virtual(node) }} std::string get_nmodl_name() const noexcept override { return "{{ node.nmodl_name }}"; } {% endif %} @@ -188,6 +510,13 @@ namespace ast { return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); } + /** + * \brief Get std::shared_ptr from `this` pointer of the current ast node + */ + {{ virtual(node) }} std::shared_ptr get_shared_ptr() const override { + return std::static_pointer_cast(shared_from_this()); + } + {% if node.has_token %} /** * \brief Return associated token for the current ast node @@ -198,7 +527,7 @@ namespace ast { * * @return pointer to token if exist otherwise nullptr */ - {{ virtual(node) }}ModToken* get_token() override { + const {{ virtual(node) }}ModToken* get_token() const override { return token.get(); } {% endif %} @@ -218,6 +547,7 @@ namespace ast { symtab::SymbolTable* get_symbol_table() override { return symtab; } + {% endif %} {% if node.is_program_node %} @@ -256,7 +586,7 @@ namespace ast { * * \sa Ast::get_node_type_name Ast::get_node_name */ - {{ virtual(node) }}void set_name(std::string name) override { + {{ virtual(node) }}void set_name(const std::string& name) override { value->set(name); } {% endif %} @@ -265,7 +595,7 @@ namespace ast { /** * \brief Set token for the current ast node */ - void set_token(ModToken& tok) { token = std::make_shared(tok); } + void set_token(const ModToken& tok) { token = std::make_shared(tok); } {% endif %} {% if node.is_symtab_needed %} diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index 24ad0f4c61..4ced91b718 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once #include diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 526cae7722..947cf49338 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include #include diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 453472fe33..f275349a9a 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once #include @@ -63,7 +67,7 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(Ast*, Ast, clone, ); } - AstNodeType get_node_type() override { + AstNodeType get_node_type() const override { PYBIND11_OVERLOAD_PURE(AstNodeType, // Return type Ast, // Parent class get_node_type, // Name of function in C++ (must match Python name) @@ -71,15 +75,15 @@ struct PyAst: public Ast { ); } - std::string get_node_type_name() override { + std::string get_node_type_name() const override { PYBIND11_OVERLOAD_PURE(std::string, Ast, get_node_type_name, ); } - std::string get_node_name() override { + std::string get_node_name() const override { PYBIND11_OVERLOAD(std::string, Ast, get_node_name, ); } - std::string get_nmodl_name() override { + std::string get_nmodl_name() const override { PYBIND11_OVERLOAD(std::string, Ast, get_nmodl_name, ); } @@ -87,8 +91,8 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_shared_ptr, ); } - ModToken* get_token() override { - PYBIND11_OVERLOAD(ModToken*, Ast, get_token, ); + const ModToken* get_token() const override { + PYBIND11_OVERLOAD(const ModToken*, Ast, get_token, ); } symtab::SymbolTable* get_symbol_table() override { @@ -103,7 +107,7 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(void, Ast, set_symbol_table, newsymtab); } - void set_name(std::string name) override { + void set_name(const std::string& name) override { PYBIND11_OVERLOAD(void, Ast, set_name, name); } @@ -111,13 +115,13 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(void, Ast, negate, ); } - bool is_ast() override { + bool is_ast() const noexcept override { PYBIND11_OVERLOAD(bool, Ast, is_ast, ); } {% for node in nodes %} - bool is_{{node.class_name | snake_case}}() override { + bool is_{{node.class_name | snake_case}}() const noexcept override { PYBIND11_OVERLOAD(bool, Ast, is_{{node.class_name | snake_case}}, ); } diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 85259a7314..054eca56ae 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include #include #include diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index 540961759b..6fc42e4b8e 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include #include #include diff --git a/src/nmodl/language/templates/pybind/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp index baa683ad45..8d3cec4e74 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.hpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index b634272023..6e3e4c6f26 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index bbcc6ffe8a..5f11b00a41 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index f12cdab5de..364411b630 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include #include diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp index 338dc72240..83dfc68bb7 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 8c8f61f3a0..41e999361c 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include "visitors/json_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index 690cccc7d1..b9922c86e4 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index 87cc661536..4576e4fcd9 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index 6c22e5f25b..f828b38cb1 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index f5796a86a0..7d901960c0 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include "visitors/nmodl_visitor.hpp" #include "visitors/nmodl_visitor_helper.ipp" diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 0080e66831..15e4f5ff9b 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp index b5338e12c4..7fae12adee 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.cpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.cpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #include "symtab/symbol_table.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/symtab_visitor_helper.hpp" diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index 6c6a4cc7f5..ab5fadacb9 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once /** diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 4b8873bed5..e5dfd92cde 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -5,6 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + #pragma once diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 484bd1141d..53c7411c0c 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -91,7 +91,7 @@ void SymbolTable::insert_table(const std::string& name, std::shared_ptr> SymbolTable::get_variables_with_properties( NmodlType properties, - bool all) { + bool all) const { std::vector> variables; for (auto& symbol: table.symbols) { if (all) { @@ -121,7 +121,7 @@ std::vector> SymbolTable::get_variables(NmodlType with, std::vector> SymbolTable::get_variables_with_status(Status status, - bool all) { + bool all) const { std::vector> variables; for (auto& symbol: table.symbols) { if (all) { @@ -442,7 +442,7 @@ void ModelSymbolTable::set_mode(bool update_mode) { //============================================================================= -void SymbolTable::Table::print(std::stringstream& stream, std::string title, int indent) { +void SymbolTable::Table::print(std::ostream& stream, std::string title, int indent) const { using stringutils::text_alignment; using utils::TableData; if (!symbols.empty()) { diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index c684aca5aa..526181e48f 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -80,7 +80,7 @@ class SymbolTable { std::shared_ptr lookup(const std::string& name) const; /// pretty print - void print(std::stringstream& stream, std::string title, int indent); + void print(std::ostream& stream, std::string title, int indent) const; }; /// name of the block @@ -136,10 +136,10 @@ class SymbolTable { std::vector> get_variables_with_properties( syminfo::NmodlType properties, - bool all = false); + bool all = false) const; std::vector> get_variables_with_status(syminfo::Status status, - bool all = false); + bool all = false) const; /// \} @@ -179,7 +179,7 @@ class SymbolTable { } /// check if symbol with given name exist in the current table (but not in parents) - std::shared_ptr lookup(const std::string& name) { + std::shared_ptr lookup(const std::string& name) const { return table.lookup(name); } diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index a452213347..0f1d880948 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -26,7 +26,7 @@ namespace nmodl { namespace units { -Prefix::Prefix(std::string name, std::string factor) { +Prefix::Prefix(std::string name, const std::string& factor) { if (name.back() == '-') { name.pop_back(); } @@ -34,11 +34,11 @@ Prefix::Prefix(std::string name, std::string factor) { prefix_factor = std::stod(factor); } -void Unit::add_unit(std::string name) { +void Unit::add_unit(const std::string& name) { unit_name = name; } -void Unit::add_base_unit(std::string name) { +void Unit::add_base_unit(const std::string& name) { // name = "*[a-j]*" which is a base unit const auto dim_name = name[1]; const int dim_no = dim_name - 'a'; @@ -46,11 +46,11 @@ void Unit::add_base_unit(std::string name) { add_nominator_unit(name); } -void Unit::add_nominator_double(std::string double_string) { +void Unit::add_nominator_double(const std::string& double_string) { unit_factor = parse_double(double_string); } -void Unit::add_nominator_dims(std::array dimensions) { +void Unit::add_nominator_dims(const std::array& dimensions) { std::transform(unit_dimensions.begin(), unit_dimensions.end(), dimensions.begin(), @@ -58,7 +58,7 @@ void Unit::add_nominator_dims(std::array dimensions) { std::plus()); } -void Unit::add_denominator_dims(std::array dimensions) { +void Unit::add_denominator_dims(const std::array& dimensions) { std::transform(unit_dimensions.begin(), unit_dimensions.end(), dimensions.begin(), @@ -66,19 +66,19 @@ void Unit::add_denominator_dims(std::array dimensions) { std::minus()); } -void Unit::add_nominator_unit(std::string nom) { +void Unit::add_nominator_unit(const std::string& nom) { nominator.push_back(nom); } -void Unit::add_nominator_unit(const std::shared_ptr> nom) { +void Unit::add_nominator_unit(const std::shared_ptr>& nom) { nominator.insert(nominator.end(), nom->begin(), nom->end()); } -void Unit::add_denominator_unit(std::string denom) { +void Unit::add_denominator_unit(const std::string& denom) { denominator.push_back(denom); } -void Unit::add_denominator_unit(const std::shared_ptr> denom) { +void Unit::add_denominator_unit(const std::shared_ptr>& denom) { denominator.insert(denominator.end(), denom->begin(), denom->end()); } @@ -138,7 +138,7 @@ double Unit::parse_double(std::string double_string) { return static_cast(d_number * powl(10.0, d_magnitude) * sign); } -void UnitTable::calc_nominator_dims(std::shared_ptr unit, std::string nominator_name) { +void UnitTable::calc_nominator_dims(const std::shared_ptr& unit, std::string nominator_name) { double nominator_prefix_factor = 1.0; int nominator_power = 1; @@ -211,7 +211,8 @@ void UnitTable::calc_nominator_dims(std::shared_ptr unit, std::string nomi } } -void UnitTable::calc_denominator_dims(std::shared_ptr unit, std::string denominator_name) { +void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, + std::string denominator_name) { double denominator_prefix_factor = 1.0; int denominator_power = 1; @@ -283,7 +284,7 @@ void UnitTable::calc_denominator_dims(std::shared_ptr unit, std::string de } } -void UnitTable::insert(std::shared_ptr unit) { +void UnitTable::insert(const std::shared_ptr& unit) { // check if the unit is a base unit and // then add it to the base units vector auto unit_nominator = unit->get_nominator_unit(); @@ -321,63 +322,63 @@ void UnitTable::insert(std::shared_ptr unit) { } } -void UnitTable::insert_prefix(std::shared_ptr prfx) { +void UnitTable::insert_prefix(const std::shared_ptr& prfx) { prefixes.insert({prfx->get_name(), prfx->get_factor()}); } void UnitTable::print_units() const { for (const auto& it: table) { - std::cout << std::fixed << std::setprecision(8) << it.first << " " - << it.second->get_factor() << ":"; + std::cout << std::fixed << std::setprecision(8) << it.first << ' ' + << it.second->get_factor() << ':'; for (const auto& dims: it.second->get_dimensions()) { - std::cout << " " << dims; + std::cout << ' ' << dims; } - std::cout << "\n"; + std::cout << '\n'; } } void UnitTable::print_base_units() const { int first_print = 1; for (const auto& it: base_units_names) { - if (it != "") { + if (!it.empty()) { if (first_print) { first_print = 0; std::cout << it; } else { - std::cout << " " << it; + std::cout << ' ' << it; } } } - std::cout << "\n"; + std::cout << '\n'; } -void UnitTable::print_units_sorted(std::stringstream& units_details) { +void UnitTable::print_units_sorted(std::ostream& units_details) const { std::vector>> sorted_elements(table.begin(), table.end()); std::sort(sorted_elements.begin(), sorted_elements.end()); for (const auto& it: sorted_elements) { - units_details << std::fixed << std::setprecision(8) << it.first << " " - << it.second->get_factor() << ":"; + units_details << std::fixed << std::setprecision(8) << it.first << ' ' + << it.second->get_factor() << ':'; for (const auto& dims: it.second->get_dimensions()) { - units_details << " " << dims; + units_details << ' ' << dims; } - units_details << "\n"; + units_details << '\n'; } } -void UnitTable::print_base_units(std::stringstream& base_units_details) { +void UnitTable::print_base_units(std::ostream& base_units_details) const { int first_print = 1; for (const auto& it: base_units_names) { - if (it != "") { + if (!it.empty()) { if (first_print) { first_print = 0; base_units_details << it; } else { - base_units_details << " " << it; + base_units_details << ' ' << it; } } } - base_units_details << "\n"; + base_units_details << '\n'; } } // namespace units diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 8625295121..5086850281 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -101,7 +101,7 @@ class Unit { : unit_name(std::move(name)) {} /// Constructor that instantiates a Unit with its factor, dimensions and name - Unit(const double factor, std::array dimensions, std::string name) + Unit(const double factor, const std::array& dimensions, std::string name) : unit_factor(factor) , unit_dimensions(dimensions) , unit_name(std::move(name)) {} @@ -109,37 +109,37 @@ class Unit { /// \} /// Add unit name to the Unit - void add_unit(std::string name); + void add_unit(const std::string& name); /// If the Unit is a base unit the dimensions of the Unit should be calculated /// based on the name of the base unit (ex. m \*a\* => m has dimension 0) - void add_base_unit(std::string name); + void add_base_unit(const std::string& name); /// Takes as argument a double as string, parses it as double and stores it to /// the Unit factor - void add_nominator_double(std::string double_string); + void add_nominator_double(const std::string& double_string); /// Add the dimensions of a nominator of the unit to the dimensions of the Unit - void add_nominator_dims(std::array dimensions); + void add_nominator_dims(const std::array& dimensions); /// Subtract the dimensions of a nominator of the unit to the dimensions of the Unit - void add_denominator_dims(std::array dimensions); + void add_denominator_dims(const std::array& dimensions); /// Add a unit to the vector of nominator strings of the Unit, so it can be processed /// later - void add_nominator_unit(std::string nom); + void add_nominator_unit(const std::string& nom); /// Add a vector of units to the vector of nominator strings of the Unit, so they can /// be processed later - void add_nominator_unit(std::shared_ptr> nom); + void add_nominator_unit(const std::shared_ptr>& nom); /// Add a unit to the vector of denominator strings of the Unit, so it can be processed /// later - void add_denominator_unit(std::string denom); + void add_denominator_unit(const std::string& denom); /// Add a vector of units to the vector of denominator strings of the Unit, so they can /// be processed later - void add_denominator_unit(std::shared_ptr> denom); + void add_denominator_unit(const std::shared_ptr>& denom); /// Multiply Unit's factor with a double factor void mul_factor(double double_factor); @@ -157,12 +157,12 @@ class Unit { } /// Getter for the vector of denominators of the Unit - std::vector get_denominator_unit() const { + const std::vector& get_denominator_unit() const noexcept { return denominator; } /// Getter for the name of the Unit - std::string get_name() const { + const std::string& get_name() const noexcept { return unit_name; } @@ -172,7 +172,7 @@ class Unit { } /// Getter for the array of Unit's dimensions - std::array get_dimensions() const { + const std::array& get_dimensions() const noexcept { return unit_dimensions; } }; @@ -202,17 +202,17 @@ class Prefix { Prefix() = default; /// Constructor that instantiates a Prefix with its name and factor - Prefix(std::string name, std::string factor); + Prefix(std::string name, const std::string& factor); /// \} /// Getter for the name of the Prefix - std::string get_name() const { + const std::string& get_name() const noexcept { return prefix_name; } /// Getter for the factor of the Prefix - double get_factor() const { + double get_factor() const noexcept { return prefix_factor; } }; @@ -256,43 +256,44 @@ class UnitTable { /// Calculate unit's dimensions based on its nominator unit named nominator_name which is /// stored in the UnitTable's table - void calc_nominator_dims(std::shared_ptr unit, std::string nominator_name); + void calc_nominator_dims(const std::shared_ptr& unit, std::string nominator_name); /// Calculate unit's dimensions based on its denominator unit named denominator_name which is /// stored in the UnitTable's table - void calc_denominator_dims(std::shared_ptr unit, std::string denominator_name); + void calc_denominator_dims(const std::shared_ptr& unit, std::string denominator_name); /// Insert a unit to the UnitTable table and calculate its dimensions and factor based on the /// previously stored units in the UnitTable /// The unit can be a normal unit or unit based on just a base unit. In the latter case, the /// base unit is also added to the base_units_name array of UnitTable - void insert(std::shared_ptr unit); + void insert(const std::shared_ptr& unit); /// Insert a prefix to the prefixes of the UnitTable - void insert_prefix(std::shared_ptr prfx); + void insert_prefix(const std::shared_ptr& prfx); /// Get the unit_name of the UnitTable's table - std::shared_ptr get_unit(const std::string& unit_name) { - return table[unit_name]; + /// \throw std::out_of_range if \a unit_name is not found + const std::shared_ptr& get_unit(const std::string& unit_name) const { + return table.at(unit_name); } /// Print the details of the units that are stored in the UnitTable void print_units() const; /// Print the details of the units that are stored in the UnitTable - /// to the stringstream units_details in ascending order to be printed + /// to the output stream units_details in ascending order to be printed /// in tests in specific order - void print_units_sorted(std::stringstream& units_details); + void print_units_sorted(std::ostream& units_details) const; /// Print the base units that are stored in the UnitTable void print_base_units() const; /// Print the base units that are stored in the UnitTable to the - /// stringstream base_units_details - void print_base_units(std::stringstream& base_units_details); + /// output stream base_units_details + void print_base_units(std::ostream& base_units_details) const; /// Get base unit name based on the ID number of the dimension - std::string get_base_unit_name(int id) { + const std::string& get_base_unit_name(int id) const noexcept { return base_units_names[id]; } }; diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 98f6d70661..7bf9f4541a 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -11,7 +11,6 @@ #include "utils/string_utils.hpp" #include "utils/table_data.hpp" - namespace nmodl { namespace utils { @@ -29,7 +28,7 @@ namespace utils { * ---------------------------------------------------------------------------------------- */ -void TableData::print(std::stringstream& stream, int indent) { +void TableData::print(std::ostream& stream, int indent) const { const int PADDING = 1; /// not necessary to print empty table @@ -43,9 +42,11 @@ void TableData::print(std::stringstream& stream, int indent) { auto ncolumns = headers.size(); std::vector col_width(ncolumns); - /// alignment is optional, so fill remaining withh right alignment + /// alignment is optional, so fill remaining with right alignment + auto all_alignments = alignments; + all_alignments.reserve(ncolumns); for (unsigned i = alignments.size(); i < ncolumns; i++) { - alignments.push_back(stringutils::text_alignment::center); + all_alignments.push_back(stringutils::text_alignment::center); } /// calculate space required for each column @@ -90,32 +91,31 @@ void TableData::print(std::stringstream& stream, int indent) { /// title row if (!title.empty()) { - title = stringutils::align_text(title, row_width - 3, stringutils::text_alignment::center); - stream << "\n" << gutter << separator_line; - stream << "\n" << gutter << "|" << title << "|"; + auto fmt_title = + stringutils::align_text(title, row_width - 3, stringutils::text_alignment::center); + stream << '\n' << gutter << separator_line; + stream << '\n' << gutter << '|' << fmt_title << '|'; } /// header row - stream << "\n" << gutter << separator_line; - stream << "\n" << gutter << header.str(); - stream << "\n" << gutter << separator_line; + stream << '\n' << gutter << separator_line; + stream << '\n' << gutter << header.str(); + stream << '\n' << gutter << separator_line; /// data rows for (const auto& row: rows) { - stream << "\n" << gutter << "| "; + stream << '\n' << gutter << "| "; for (unsigned i = 0; i < row.size(); i++) { - stream << stringutils::align_text(row[i], col_width[i], alignments[i]) << " | "; + stream << stringutils::align_text(row[i], col_width[i], all_alignments[i]) << " | "; } } /// bottom separator line - stream << "\n" << gutter << separator_line << "\n"; + stream << '\n' << gutter << separator_line << '\n'; } -void TableData::print(int indent) { - std::stringstream ss; - print(ss, indent); - std::cout << ss.str(); +void TableData::print(int indent) const { + print(std::cout, indent); } } // namespace utils diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index 0c29ed3de4..feb6376f9f 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -48,9 +48,9 @@ struct TableData { /// alignment for every column of data rows std::vector alignments; - void print(int indent = 0); + void print(int indent = 0) const; - void print(std::stringstream& stream, int indent = 0); + void print(std::ostream& stream, int indent = 0) const; }; /** @} */ // end of utils diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 3e8dc39e11..1d2f06f55e 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -141,7 +141,7 @@ class InlineVisitor: public AstVisitor { std::shared_ptr caller_statement; /// symbol table for program node - symtab::SymbolTable* program_symtab = nullptr; + symtab::SymbolTable const* program_symtab = nullptr; /// statement blocks in call hierarchy std::stack statementblock_stack; diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index c870f73801..2e7b4382e6 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -63,10 +63,10 @@ namespace visitor { class LocalVarRenameVisitor: public AstVisitor { private: /// non-null symbol table in the scope hierarchy - symtab::SymbolTable* symtab = nullptr; + const symtab::SymbolTable* symtab = nullptr; /// symbol tables in case of nested blocks - std::stack symtab_stack; + std::stack symtab_stack; /// variables currently being renamed and their count std::map renamed_variables; From e8773dec2e9a63520f30f3b98de6719c134a3499 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Thu, 16 Apr 2020 15:19:46 +0200 Subject: [PATCH 254/871] Remove virtual from set_parent_for_children (BlueBrain/nmodl#296) * Remove virtual from set_parent_for_children (issue BlueBrain/nmodl#296) - set_parent_for_children is called only in the constructors. The virtual keyword unnecessary and generates only confusion. - Remove virtual from set_parent_for_children (issue BlueBrain/nmodl#295) - set_parent_in_children() visibility is now private Co-authored-by: Tristan Carel fixes BlueBrain/nmodl#295 NMODL Repo SHA: BlueBrain/nmodl@25e654cd0334e77ef7e15a600dbc564f58862e64 --- src/nmodl/language/templates/ast/ast.cpp | 4 ---- src/nmodl/language/templates/ast/ast.hpp | 17 ++++++----------- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index d2f7591b87..ab68e49b48 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -73,10 +73,6 @@ void Ast::set_parent(Ast* p) { parent = p; } -void Ast::set_parent_in_children() { - throw std::runtime_error("set_parent_in_children not implemented"); -} - {% for node in nodes %} diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index e70d98b03c..a8378df45a 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -348,14 +348,6 @@ struct Ast: public std::enable_shared_from_this { * \ref Check Ast::parent for more information */ virtual void set_parent(Ast* p); - - /** - *\brief Set this object as parent for all the children - * - * This should be called in every object (with children) constructor - * to set the parents. - */ - virtual void set_parent_in_children(); }; /** @} */ // end of ast_class @@ -714,12 +706,15 @@ struct Ast: public std::enable_shared_from_this { {% endif %} {% if node.children %} + private: /** - * \brief Set parents in children + *\brief Set this object as parent for all the children * - * Usually called in constructors + * This should be called in every object (with children) constructor + * to set parents. Since it is called only in the constructors it + * should not be virtual to avoid ambiguities (issue #295). */ - virtual void set_parent_in_children() override; + void set_parent_in_children(); {% endif %} }; From c6facb06612dac3e604972ff310d23e191ccab0f Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 23 Apr 2020 11:38:37 +0200 Subject: [PATCH 255/871] Visitors now takes C++ references of ast nodes in parameter, not pointers (BlueBrain/nmodl#297) * Visitors now takes C++ references of ast nodes in parameter, not pointers * Add instructions to run Python tests NMODL Repo SHA: BlueBrain/nmodl@b8ba4a0e844ba6a742b986caa49f492b6102b8a0 --- CONTRIBUTING.md | 17 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 7 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 19 +- src/nmodl/codegen/codegen_c_visitor.cpp | 383 +++++++++--------- src/nmodl/codegen/codegen_c_visitor.hpp | 238 +++++------ .../codegen/codegen_compatibility_visitor.cpp | 21 +- .../codegen/codegen_compatibility_visitor.hpp | 25 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 20 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 24 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 130 +++--- src/nmodl/codegen/codegen_helper_visitor.hpp | 60 +-- src/nmodl/codegen/codegen_info.cpp | 14 +- src/nmodl/codegen/codegen_info.hpp | 16 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 83 ++-- src/nmodl/codegen/codegen_ispc_visitor.hpp | 28 +- src/nmodl/codegen/codegen_omp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_omp_visitor.hpp | 2 +- src/nmodl/language/templates/ast/ast.cpp | 2 +- src/nmodl/language/templates/ast/ast.hpp | 6 +- src/nmodl/language/templates/pybind/pyast.hpp | 2 +- .../language/templates/pybind/pyvisitor.cpp | 12 +- .../language/templates/pybind/pyvisitor.hpp | 4 +- .../templates/visitors/ast_visitor.cpp | 4 +- .../templates/visitors/ast_visitor.hpp | 2 +- .../visitors/checkparent_visitor.cpp | 12 +- .../visitors/checkparent_visitor.hpp | 4 +- .../templates/visitors/json_visitor.cpp | 12 +- .../templates/visitors/json_visitor.hpp | 2 +- .../templates/visitors/lookup_visitor.cpp | 22 +- .../templates/visitors/lookup_visitor.hpp | 10 +- .../templates/visitors/nmodl_visitor.cpp | 30 +- .../templates/visitors/nmodl_visitor.hpp | 2 +- .../templates/visitors/symtab_visitor.cpp | 14 +- .../templates/visitors/symtab_visitor.hpp | 4 +- .../language/templates/visitors/visitor.hpp | 2 +- src/nmodl/nmodl/main.cpp | 109 +++-- src/nmodl/printer/code_printer.hpp | 2 +- src/nmodl/pybind/pynmodl.cpp | 12 +- src/nmodl/symtab/symbol_table.cpp | 2 +- src/nmodl/symtab/symbol_table.hpp | 6 +- .../visitors/constant_folder_visitor.cpp | 37 +- .../visitors/constant_folder_visitor.hpp | 4 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 38 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 64 +-- src/nmodl/visitors/inline_visitor.cpp | 66 +-- src/nmodl/visitors/inline_visitor.hpp | 12 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 100 ++--- src/nmodl/visitors/kinetic_block_visitor.hpp | 18 +- .../visitors/local_var_rename_visitor.cpp | 8 +- .../visitors/local_var_rename_visitor.hpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 16 +- src/nmodl/visitors/localize_visitor.hpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 43 +- src/nmodl/visitors/loop_unroll_visitor.hpp | 2 +- src/nmodl/visitors/main.cpp | 10 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 38 +- src/nmodl/visitors/neuron_solve_visitor.hpp | 10 +- src/nmodl/visitors/perf_visitor.cpp | 58 +-- src/nmodl/visitors/perf_visitor.hpp | 128 +++--- src/nmodl/visitors/rename_visitor.cpp | 14 +- src/nmodl/visitors/rename_visitor.hpp | 6 +- src/nmodl/visitors/solve_block_visitor.cpp | 38 +- src/nmodl/visitors/solve_block_visitor.hpp | 8 +- src/nmodl/visitors/steadystate_visitor.cpp | 10 +- src/nmodl/visitors/steadystate_visitor.hpp | 4 +- .../visitors/sympy_conductance_visitor.cpp | 38 +- .../visitors/sympy_conductance_visitor.hpp | 12 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 106 ++--- src/nmodl/visitors/sympy_solver_visitor.hpp | 26 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 8 +- src/nmodl/visitors/units_visitor.cpp | 46 +-- src/nmodl/visitors/units_visitor.hpp | 8 +- src/nmodl/visitors/var_usage_visitor.cpp | 8 +- src/nmodl/visitors/var_usage_visitor.hpp | 4 +- .../visitors/verbatim_var_rename_visitor.cpp | 17 +- .../visitors/verbatim_var_rename_visitor.hpp | 6 +- src/nmodl/visitors/verbatim_visitor.cpp | 8 +- src/nmodl/visitors/verbatim_visitor.hpp | 6 +- src/nmodl/visitors/visitor_utils.cpp | 42 +- src/nmodl/visitors/visitor_utils.hpp | 29 +- test/nmodl/transpiler/parser/parser.cpp | 7 +- .../transpiler/visitor/constant_folder.cpp | 10 +- .../transpiler/visitor/defuse_analyze.cpp | 10 +- test/nmodl/transpiler/visitor/inline.cpp | 10 +- test/nmodl/transpiler/visitor/json.cpp | 2 +- .../transpiler/visitor/kinetic_block.cpp | 25 +- test/nmodl/transpiler/visitor/localize.cpp | 36 +- test/nmodl/transpiler/visitor/lookup.cpp | 18 +- test/nmodl/transpiler/visitor/loop_unroll.cpp | 14 +- test/nmodl/transpiler/visitor/misc.cpp | 34 +- .../nmodl/transpiler/visitor/neuron_solve.cpp | 10 +- test/nmodl/transpiler/visitor/nmodl.cpp | 6 +- test/nmodl/transpiler/visitor/perf.cpp | 8 +- test/nmodl/transpiler/visitor/rename.cpp | 23 +- test/nmodl/transpiler/visitor/solve_block.cpp | 12 +- test/nmodl/transpiler/visitor/steadystate.cpp | 29 +- .../transpiler/visitor/sympy_conductance.cpp | 28 +- .../nmodl/transpiler/visitor/sympy_solver.cpp | 34 +- test/nmodl/transpiler/visitor/units.cpp | 32 +- test/nmodl/transpiler/visitor/var_usage.cpp | 5 +- test/nmodl/transpiler/visitor/verbatim.cpp | 6 +- 101 files changed, 1425 insertions(+), 1400 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 17b7f7519a..ca3af8d827 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,10 +126,21 @@ do not comply with coding conventions of this project. These 2 CMake variables r * [pre-commit](https://pre-commit.com/) clang-format can be installed on Linux thanks -to [LLVM apt page](http://apt.llvm.org/). On MacOS, there is a -[brew recipe](https://gist.github.com/ffeu/0460bb1349fa7e4ab4c459a6192cbb25) -to install clang-format 7. _cmake-format_ and _pre-commit_ utilities can be installed with *pip*. +to [LLVM apt page](http://apt.llvm.org/). On MacOS, you can simply install llvm with brew: +`brew install llvm`. +_cmake-format_ and _pre-commit_ utilities can be installed with *pip*. +### Validate the Python package + +You may run the Python test-suites if your contribution has an impact +on the Python API: + +1. setup a sandbox environment with either _virtualenv_, + _pyenv_, or _pipenv_. For instance with _virtualenv_: + `python -m venv .venv && source .venv/bin/activate` +1. build the Python package with the command: `python setup.py build` +1. install _pytest_ Python package: `pip install pytest` +1. execute the unit-tests: `pytest` ### Memory Leaks and clang-tidy diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 96215e71a2..9a16e631f3 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -66,12 +66,12 @@ void CodegenAccVisitor::print_backend_includes() { } -std::string CodegenAccVisitor::backend_name() { +std::string CodegenAccVisitor::backend_name() const { return "C-OpenAcc (api-compatibility)"; } -void CodegenAccVisitor::print_memory_allocation_routine() { +void CodegenAccVisitor::print_memory_allocation_routine() const { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); @@ -174,7 +174,8 @@ void CodegenAccVisitor::print_global_variable_device_update_annotation() { } } -std::string CodegenAccVisitor::get_variable_device_pointer(std::string variable, std::string type) { +std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& variable, + const std::string& type) const { if (info.artificial_cell) { return variable; } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 7d1c7748c1..20d692b262 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -30,7 +30,7 @@ namespace codegen { class CodegenAccVisitor: public CodegenCVisitor { protected: /// name of the code generation backend - std::string backend_name() override; + std::string backend_name() const override; /// common includes : standard c/c++, coreneuron and backend specific @@ -46,7 +46,7 @@ class CodegenAccVisitor: public CodegenCVisitor { /// memory allocation routine - void print_memory_allocation_routine() override; + void print_memory_allocation_routine() const override; /// annotations like "acc enter data present(...)" for main kernel @@ -81,20 +81,21 @@ class CodegenAccVisitor: public CodegenCVisitor { /// update global variable from host to the device void print_global_variable_device_update_annotation() override; - std::string get_variable_device_pointer(std::string variable, std::string type) override; + std::string get_variable_device_pointer(const std::string& variable, + const std::string& type) const override; public: - CodegenAccVisitor(std::string mod_file, - std::string output_dir, + CodegenAccVisitor(const std::string& mod_file, + const std::string& output_dir, LayoutType layout, - std::string float_type) + const std::string& float_type) : CodegenCVisitor(mod_file, output_dir, layout, float_type) {} - CodegenAccVisitor(std::string mod_file, - std::stringstream& stream, + CodegenAccVisitor(const std::string& mod_file, + std::ostream& stream, LayoutType layout, - std::string float_type) + const std::string& float_type) : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 994bdfeb83..a798474d49 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -40,11 +40,11 @@ using SymbolType = std::shared_ptr; /****************************************************************************************/ -void CodegenCVisitor::visit_string(String* node) { +void CodegenCVisitor::visit_string(String& node) { if (!codegen) { return; } - std::string name = node->eval(); + std::string name = node.eval(); if (enable_variable_name_lookup) { name = get_variable_name(name); } @@ -52,12 +52,12 @@ void CodegenCVisitor::visit_string(String* node) { } -void CodegenCVisitor::visit_integer(Integer* node) { +void CodegenCVisitor::visit_integer(Integer& node) { if (!codegen) { return; } - auto macro = node->get_macro(); - auto value = node->get_value(); + const auto& macro = node.get_macro(); + auto value = node.get_value(); if (macro) { macro->accept(*this); } else { @@ -66,46 +66,46 @@ void CodegenCVisitor::visit_integer(Integer* node) { } -void CodegenCVisitor::visit_float(Float* node) { +void CodegenCVisitor::visit_float(Float& node) { if (!codegen) { return; } - auto value = node->eval(); + auto value = node.eval(); printer->add_text(float_to_string(value)); } -void CodegenCVisitor::visit_double(Double* node) { +void CodegenCVisitor::visit_double(Double& node) { if (!codegen) { return; } - auto value = node->eval(); + auto value = node.eval(); printer->add_text(double_to_string(value)); } -void CodegenCVisitor::visit_boolean(Boolean* node) { +void CodegenCVisitor::visit_boolean(Boolean& node) { if (!codegen) { return; } - printer->add_text(std::to_string(static_cast(node->eval()))); + printer->add_text(std::to_string(static_cast(node.eval()))); } -void CodegenCVisitor::visit_name(Name* node) { +void CodegenCVisitor::visit_name(Name& node) { if (!codegen) { return; } - node->visit_children(*this); + node.visit_children(*this); } -void CodegenCVisitor::visit_unit(ast::Unit* node) { +void CodegenCVisitor::visit_unit(ast::Unit& node) { // do not print units } -void CodegenCVisitor::visit_prime_name(PrimeName* node) { +void CodegenCVisitor::visit_prime_name(PrimeName& node) { throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); } @@ -113,13 +113,13 @@ void CodegenCVisitor::visit_prime_name(PrimeName* node) { /** * \todo : Validate how @ is being handled in neuron implementation */ -void CodegenCVisitor::visit_var_name(VarName* node) { +void CodegenCVisitor::visit_var_name(VarName& node) { if (!codegen) { return; } - auto name = node->get_name(); - auto at_index = node->get_at(); - auto index = node->get_index(); + const auto& name = node.get_name(); + const auto& at_index = node.get_at(); + const auto& index = node.get_index(); name->accept(*this); if (at_index) { printer->add_text("@"); @@ -133,80 +133,80 @@ void CodegenCVisitor::visit_var_name(VarName* node) { } -void CodegenCVisitor::visit_indexed_name(IndexedName* node) { +void CodegenCVisitor::visit_indexed_name(IndexedName& node) { if (!codegen) { return; } - node->get_name()->accept(*this); + node.get_name()->accept(*this); printer->add_text("["); - node->get_length()->accept(*this); + node.get_length()->accept(*this); printer->add_text("]"); } -void CodegenCVisitor::visit_local_list_statement(LocalListStatement* node) { +void CodegenCVisitor::visit_local_list_statement(LocalListStatement& node) { if (!codegen) { return; } auto type = local_var_type() + " "; printer->add_text(type); - print_vector_elements(node->get_variables(), ", "); + print_vector_elements(node.get_variables(), ", "); } -void CodegenCVisitor::visit_if_statement(IfStatement* node) { +void CodegenCVisitor::visit_if_statement(IfStatement& node) { if (!codegen) { return; } printer->add_text("if ("); - node->get_condition()->accept(*this); + node.get_condition()->accept(*this); printer->add_text(") "); - node->get_statement_block()->accept(*this); - print_vector_elements(node->get_elseifs(), ""); - auto elses = node->get_elses(); + node.get_statement_block()->accept(*this); + print_vector_elements(node.get_elseifs(), ""); + const auto& elses = node.get_elses(); if (elses) { elses->accept(*this); } } -void CodegenCVisitor::visit_else_if_statement(ElseIfStatement* node) { +void CodegenCVisitor::visit_else_if_statement(ElseIfStatement& node) { if (!codegen) { return; } printer->add_text(" else if ("); - node->get_condition()->accept(*this); + node.get_condition()->accept(*this); printer->add_text(") "); - node->get_statement_block()->accept(*this); + node.get_statement_block()->accept(*this); } -void CodegenCVisitor::visit_else_statement(ElseStatement* node) { +void CodegenCVisitor::visit_else_statement(ElseStatement& node) { if (!codegen) { return; } printer->add_text(" else "); - node->visit_children(*this); + node.visit_children(*this); } -void CodegenCVisitor::visit_while_statement(WhileStatement* node) { +void CodegenCVisitor::visit_while_statement(WhileStatement& node) { printer->add_text("while ("); - node->get_condition()->accept(*this); + node.get_condition()->accept(*this); printer->add_text(") "); - node->get_statement_block()->accept(*this); + node.get_statement_block()->accept(*this); } -void CodegenCVisitor::visit_from_statement(ast::FromStatement* node) { +void CodegenCVisitor::visit_from_statement(ast::FromStatement& node) { if (!codegen) { return; } - auto name = node->get_node_name(); - auto from = node->get_from(); - auto to = node->get_to(); - auto inc = node->get_increment(); - auto block = node->get_statement_block(); + auto name = node.get_node_name(); + const auto& from = node.get_from(); + const auto& to = node.get_to(); + const auto& inc = node.get_increment(); + const auto& block = node.get_statement_block(); printer->add_text("for(int {}="_format(name)); from->accept(*this); printer->add_text("; {}<="_format(name)); @@ -222,23 +222,23 @@ void CodegenCVisitor::visit_from_statement(ast::FromStatement* node) { } -void CodegenCVisitor::visit_paren_expression(ParenExpression* node) { +void CodegenCVisitor::visit_paren_expression(ParenExpression& node) { if (!codegen) { return; } printer->add_text("("); - node->get_expression()->accept(*this); + node.get_expression()->accept(*this); printer->add_text(")"); } -void CodegenCVisitor::visit_binary_expression(BinaryExpression* node) { +void CodegenCVisitor::visit_binary_expression(BinaryExpression& node) { if (!codegen) { return; } - auto op = node->get_op().eval(); - auto lhs = node->get_lhs(); - auto rhs = node->get_rhs(); + auto op = node.get_op().eval(); + auto lhs = node.get_lhs(); + auto rhs = node.get_rhs(); if (op == "^") { printer->add_text("pow("); lhs->accept(*this); @@ -253,19 +253,19 @@ void CodegenCVisitor::visit_binary_expression(BinaryExpression* node) { } -void CodegenCVisitor::visit_binary_operator(BinaryOperator* node) { +void CodegenCVisitor::visit_binary_operator(BinaryOperator& node) { if (!codegen) { return; } - printer->add_text(node->eval()); + printer->add_text(node.eval()); } -void CodegenCVisitor::visit_unary_operator(UnaryOperator* node) { +void CodegenCVisitor::visit_unary_operator(UnaryOperator& node) { if (!codegen) { return; } - printer->add_text(" " + node->eval()); + printer->add_text(" " + node.eval()); } @@ -274,16 +274,16 @@ void CodegenCVisitor::visit_unary_operator(UnaryOperator* node) { * Sometime we want to analyse ast nodes even if code generation is * false. Hence we visit children even if code generation is false. */ -void CodegenCVisitor::visit_statement_block(StatementBlock* node) { +void CodegenCVisitor::visit_statement_block(StatementBlock& node) { if (!codegen) { - node->visit_children(*this); + node.visit_children(*this); return; } print_statement_block(node); } -void CodegenCVisitor::visit_function_call(FunctionCall* node) { +void CodegenCVisitor::visit_function_call(FunctionCall& node) { if (!codegen) { return; } @@ -291,11 +291,11 @@ void CodegenCVisitor::visit_function_call(FunctionCall* node) { } -void CodegenCVisitor::visit_verbatim(Verbatim* node) { +void CodegenCVisitor::visit_verbatim(Verbatim& node) { if (!codegen) { return; } - auto text = node->get_statement()->eval(); + auto text = node.get_statement()->eval(); auto result = process_verbatim_text(text); auto statements = stringutils::split_string(result, '\n'); @@ -319,19 +319,19 @@ void CodegenCVisitor::visit_verbatim(Verbatim* node) { * statement and hence we have to check inner expression. It's also true * for the initial block defined inside net receive block. */ -bool CodegenCVisitor::statement_to_skip(Statement* node) { +bool CodegenCVisitor::statement_to_skip(const Statement& node) const { // clang-format off - if (node->is_unit_state() - || node->is_line_comment() - || node->is_block_comment() - || node->is_solve_block() - || node->is_conductance_hint() - || node->is_table_statement()) { + if (node.is_unit_state() + || node.is_line_comment() + || node.is_block_comment() + || node.is_solve_block() + || node.is_conductance_hint() + || node.is_table_statement()) { return true; } // clang-format on - if (node->is_expression_statement()) { - auto expression = dynamic_cast(node)->get_expression(); + if (node.is_expression_statement()) { + auto expression = dynamic_cast(&node)->get_expression(); if (expression->is_solve_block()) { return true; } @@ -343,7 +343,7 @@ bool CodegenCVisitor::statement_to_skip(Statement* node) { } -bool CodegenCVisitor::net_send_buffer_required() { +bool CodegenCVisitor::net_send_buffer_required() const noexcept { if (net_receive_required() && !info.artificial_cell) { if (info.net_event_used || info.net_send_used || info.is_watch_used()) { return true; @@ -353,12 +353,12 @@ bool CodegenCVisitor::net_send_buffer_required() { } -bool CodegenCVisitor::net_receive_buffering_required() { +bool CodegenCVisitor::net_receive_buffering_required() const noexcept { return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; } -bool CodegenCVisitor::nrn_state_required() { +bool CodegenCVisitor::nrn_state_required() const noexcept { if (info.artificial_cell) { return false; } @@ -366,22 +366,22 @@ bool CodegenCVisitor::nrn_state_required() { } -bool CodegenCVisitor::nrn_cur_required() { +bool CodegenCVisitor::nrn_cur_required() const noexcept { return info.breakpoint_node != nullptr && !info.currents.empty(); } -bool CodegenCVisitor::net_receive_exist() { +bool CodegenCVisitor::net_receive_exist() const noexcept { return info.net_receive_node != nullptr; } -bool CodegenCVisitor::breakpoint_exist() { +bool CodegenCVisitor::breakpoint_exist() const noexcept { return info.breakpoint_node != nullptr; } -bool CodegenCVisitor::net_receive_required() { +bool CodegenCVisitor::net_receive_required() const noexcept { return net_receive_exist(); } @@ -390,12 +390,12 @@ bool CodegenCVisitor::net_receive_required() { * \details When floating point data type is not default (i.e. double) then we * have to copy old array to new type (for range variables). */ -bool CodegenCVisitor::range_variable_setup_required() { +bool CodegenCVisitor::range_variable_setup_required() const noexcept { return codegen::naming::DEFAULT_FLOAT_TYPE != float_data_type(); } -bool CodegenCVisitor::state_variable(std::string name) { +bool CodegenCVisitor::state_variable(const std::string& name) const { // clang-format off auto result = std::find_if(info.state_vars.begin(), info.state_vars.end(), @@ -408,7 +408,7 @@ bool CodegenCVisitor::state_variable(std::string name) { } -int CodegenCVisitor::position_of_float_var(const std::string& name) { +int CodegenCVisitor::position_of_float_var(const std::string& name) const { int index = 0; for (const auto& var: codegen_float_variables) { if (var->get_name() == name) { @@ -420,7 +420,7 @@ int CodegenCVisitor::position_of_float_var(const std::string& name) { } -int CodegenCVisitor::position_of_int_var(const std::string& name) { +int CodegenCVisitor::position_of_int_var(const std::string& name) const { int index = 0; for (const auto& var: codegen_int_variables) { if (var.symbol->get_name() == name) { @@ -440,7 +440,7 @@ int CodegenCVisitor::position_of_int_var(const std::string& name) { * to use std::to_string in else part? */ std::string CodegenCVisitor::double_to_string(double value) { - if (ceilf(value) == value) { + if (std::ceil(value) == value) { return "{:.1f}"_format(value); } return "{:.16g}"_format(value); @@ -448,7 +448,7 @@ std::string CodegenCVisitor::double_to_string(double value) { std::string CodegenCVisitor::float_to_string(float value) { - if (ceilf(value) == value) { + if (std::ceil(value) == value) { return "{:.1f}f"_format(value); } return "{:.16g}f"_format(value); @@ -461,7 +461,7 @@ std::string CodegenCVisitor::float_to_string(float value) { * block can appear as statement using expression statement which need to * be inspected. */ -bool CodegenCVisitor::need_semicolon(Statement* node) { +bool CodegenCVisitor::need_semicolon(Statement* node) const { // clang-format off if (node->is_if_statement() || node->is_else_if_statement() @@ -489,8 +489,8 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { // check if there is a function or procedure defined with given name -bool CodegenCVisitor::defined_method(const std::string& name) { - auto function = program_symtab->lookup(name); +bool CodegenCVisitor::defined_method(const std::string& name) const { + const auto& function = program_symtab->lookup(name); auto properties = NmodlType::function_block | NmodlType::procedure_block; return function && function->has_any_property(properties); } @@ -504,7 +504,7 @@ bool CodegenCVisitor::defined_method(const std::string& name) { * the variable is renamed. Note that we have to look into the symbol table * of statement block and not breakpoint. */ -std::string CodegenCVisitor::breakpoint_current(std::string current) { +std::string CodegenCVisitor::breakpoint_current(std::string current) const { auto breakpoint = info.breakpoint_node; if (breakpoint == nullptr) { return current; @@ -523,8 +523,8 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) { } -int CodegenCVisitor::float_variables_size() { - auto count_length = [](std::vector& variables) -> int { +int CodegenCVisitor::float_variables_size() const { + auto count_length = [](const std::vector& variables) -> int { int length = 0; for (const auto& variable: variables) { length += variable->get_length(); @@ -557,7 +557,7 @@ int CodegenCVisitor::float_variables_size() { } -int CodegenCVisitor::int_variables_size() { +int CodegenCVisitor::int_variables_size() const { int num_variables = 0; for (const auto& semantic: info.semantics) { num_variables += semantic.size; @@ -715,7 +715,7 @@ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { } -bool CodegenCVisitor::ion_variable_struct_required() { +bool CodegenCVisitor::ion_variable_struct_required() const { return optimize_ion_variable_copies() && info.ion_has_write_variable(); } @@ -725,7 +725,7 @@ bool CodegenCVisitor::ion_variable_struct_required() { * except in INITIAL block where they are set to 0. As initial block is/can be * executed on c/cpu backend, gpu/cuda backend can mark the parameter as constnat. */ -bool CodegenCVisitor::is_constant_variable(std::string name) { +bool CodegenCVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { @@ -1171,7 +1171,7 @@ void CodegenCVisitor::print_backend_includes() { } -std::string CodegenCVisitor::backend_name() { +std::string CodegenCVisitor::backend_name() const { return "C (api-compatibility)"; } @@ -1186,12 +1186,12 @@ bool CodegenCVisitor::channel_task_dependency_enabled() { } -bool CodegenCVisitor::optimize_ion_variable_copies() { +bool CodegenCVisitor::optimize_ion_variable_copies() const { return true; } -void CodegenCVisitor::print_memory_allocation_routine() { +void CodegenCVisitor::print_memory_allocation_routine() const { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); @@ -1208,7 +1208,7 @@ void CodegenCVisitor::print_memory_allocation_routine() { } -std::string CodegenCVisitor::compute_method_name(BlockType type) { +std::string CodegenCVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); } @@ -1241,22 +1241,22 @@ std::string CodegenCVisitor::k_const() { /****************************************************************************************/ -void CodegenCVisitor::visit_watch_statement(ast::WatchStatement* node) { +void CodegenCVisitor::visit_watch_statement(ast::WatchStatement& node) { printer->add_text( "nrn_watch_activate(inst, id, pnodecount, {})"_format(current_watch_statement++)); } -void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, +void CodegenCVisitor::print_statement_block(ast::StatementBlock& node, bool open_brace, bool close_brace) { if (open_brace) { printer->start_block(); } - auto statements = node->get_statements(); + auto statements = node.get_statements(); for (const auto& statement: statements) { - if (statement_to_skip(statement.get())) { + if (statement_to_skip(*statement)) { continue; } /// not necessary to add indent for verbatim block (pretty-printing) @@ -1276,8 +1276,8 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock* node, } -void CodegenCVisitor::print_function_call(FunctionCall* node) { - auto name = node->get_node_name(); +void CodegenCVisitor::print_function_call(FunctionCall& node) { + auto name = node.get_node_name(); auto function_name = name; if (defined_method(name)) { function_name = method_name(name); @@ -1298,7 +1298,7 @@ void CodegenCVisitor::print_function_call(FunctionCall* node) { return; } - auto arguments = node->get_arguments(); + auto arguments = node.get_arguments(); printer->add_text("{}("_format(function_name)); if (defined_method(name)) { @@ -1370,12 +1370,12 @@ void CodegenCVisitor::print_function_prototypes() { codegen = true; printer->add_newline(2); for (const auto& node: info.functions) { - print_function_declaration(node, node->get_node_name()); + print_function_declaration(*node, node->get_node_name()); printer->add_text(";"); printer->add_newline(); } for (const auto& node: info.procedures) { - print_function_declaration(node, node->get_node_name()); + print_function_declaration(*node, node->get_node_name()); printer->add_text(";"); printer->add_newline(); } @@ -1383,17 +1383,17 @@ void CodegenCVisitor::print_function_prototypes() { } -static TableStatement* get_table_statement(ast::Block* node) { +static TableStatement* get_table_statement(ast::Block& node) { // TableStatementVisitor v; AstLookupVisitor v(AstNodeType::TABLE_STATEMENT); - node->accept(v); + node.accept(v); auto table_statements = v.get_nodes(); if (table_statements.size() != 1) { auto message = - "One table statement expected in {} found {}"_format(node->get_node_name(), + "One table statement expected in {} found {}"_format(node.get_node_name(), table_statements.size()); throw std::runtime_error(message); } @@ -1401,13 +1401,13 @@ static TableStatement* get_table_statement(ast::Block* node) { } -void CodegenCVisitor::print_table_check_function(ast::Block* node) { +void CodegenCVisitor::print_table_check_function(Block& node) { auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); auto depend_variables = statement->get_depend_vars(); - auto from = statement->get_from(); - auto to = statement->get_to(); - auto name = node->get_node_name(); + const auto& from = statement->get_from(); + const auto& to = statement->get_to(); + auto name = node.get_node_name(); auto internal_params = internal_method_parameters(); auto with = statement->get_with()->eval(); auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); @@ -1483,8 +1483,8 @@ void CodegenCVisitor::print_table_check_function(ast::Block* node) { } -void CodegenCVisitor::print_table_replacement_function(ast::Block* node) { - auto name = node->get_node_name(); +void CodegenCVisitor::print_table_replacement_function(ast::Block& node) { + auto name = node.get_node_name(); auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); auto with = statement->get_with()->eval(); @@ -1570,29 +1570,29 @@ void CodegenCVisitor::print_check_table_thread_function() { } -void CodegenCVisitor::print_function_or_procedure(ast::Block* node, std::string& name) { +void CodegenCVisitor::print_function_or_procedure(ast::Block& node, const std::string& name) { printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); printer->start_block(); // function requires return variable declaration - if (node->is_function_block()) { + if (node.is_function_block()) { auto type = default_float_data_type(); printer->add_line("{} ret_{} = 0.0;"_format(type, name)); } else { printer->add_line("int ret_{} = 0;"_format(name)); } - print_statement_block(node->get_statement_block().get(), false, false); + print_statement_block(*node.get_statement_block(), false, false); printer->add_line("return ret_{};"_format(name)); printer->end_block(1); } -void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { +void CodegenCVisitor::print_procedure(ast::ProcedureBlock& node) { codegen = true; - auto name = node->get_node_name(); + auto name = node.get_node_name(); if (info.function_uses_table(name)) { auto new_name = "f_" + name; @@ -1607,13 +1607,13 @@ void CodegenCVisitor::print_procedure(ast::ProcedureBlock* node) { } -void CodegenCVisitor::print_function(ast::FunctionBlock* node) { +void CodegenCVisitor::print_function(ast::FunctionBlock& node) { codegen = true; - auto name = node->get_node_name(); + auto name = node.get_node_name(); auto return_var = "ret_" + name; // first rename return variable name - auto block = node->get_statement_block().get(); + auto block = node.get_statement_block().get(); RenameVisitor v(name, return_var); block->accept(v); @@ -1621,7 +1621,7 @@ void CodegenCVisitor::print_function(ast::FunctionBlock* node) { codegen = false; } -std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) { +std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) const { auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); std::string unique_name = original_name; if (singleton_random_string_class->random_string_exists(original_name)) { @@ -1631,7 +1631,7 @@ std::string CodegenCVisitor::find_var_unique_name(const std::string& original_na return unique_name; } -void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) { +void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) { // solution vector to store copy of state vars for Newton solver printer->add_newline(); @@ -1644,10 +1644,10 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc std::string F = find_var_unique_name("F"); auto float_type = default_float_data_type(); - int N = node->get_n_state_vars()->get_value(); + int N = node.get_n_state_vars()->get_value(); printer->add_line("Eigen::Matrix<{}, {}, 1> {};"_format(float_type, N, X)); - print_statement_block(node->get_setup_x_block().get(), false, false); + print_statement_block(*node.get_setup_x_block(), false, false); // functor that evaluates F(X) and J(X) for // Newton solver @@ -1661,11 +1661,11 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc print_ion_variable(); } - print_statement_block(node->get_variable_block().get(), false, false); + print_statement_block(*node.get_variable_block(), false, false); printer->add_newline(); printer->start_block("void initialize()"); - print_statement_block(node->get_initialize_block().get(), false, false); + print_statement_block(*node.get_initialize_block(), false, false); printer->end_block(2); printer->add_line( @@ -1679,12 +1679,12 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc "Eigen::Matrix<{0}, {1}, {1}>& {4}) const"_format(float_type, N, X, F, Jm)); printer->start_block(); printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); - print_statement_block(node->get_functor_block().get(), false, false); + print_statement_block(*node.get_functor_block(), false, false); printer->end_block(2); // assign newton solver results in matrix X to state vars printer->start_block("void finalize()"); - print_statement_block(node->get_finalize_block().get(), false, false); + print_statement_block(*node.get_finalize_block(), false, false); printer->end_block(1); printer->end_block(0); @@ -1699,11 +1699,11 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc "int newton_iterations = nmodl::newton::newton_solver({}, newton_functor);"_format(X)); // assign newton solver results in matrix X to state vars - print_statement_block(node->get_update_states_block().get(), false, false); + print_statement_block(*node.get_update_states_block(), false, false); printer->add_line("newton_functor.finalize();"); } -void CodegenCVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) { +void CodegenCVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) { printer->add_newline(); // Check if there is a variable defined in the mod file as X, J, Jm or F and if yes @@ -1714,21 +1714,21 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBloc std::string Jm = find_var_unique_name("Jm"); std::string F = find_var_unique_name("F"); - std::string float_type = default_float_data_type(); - int N = node->get_n_state_vars()->get_value(); + const std::string float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); printer->add_line("Eigen::Matrix<{0}, {1}, 1> {2}, {3};"_format(float_type, N, X, F)); printer->add_line("Eigen::Matrix<{0}, {1}, {1}> {2};"_format(float_type, N, Jm)); printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); - print_statement_block(node->get_variable_block().get(), false, false); - print_statement_block(node->get_initialize_block().get(), false, false); - print_statement_block(node->get_setup_x_block().get(), false, false); + print_statement_block(*node.get_variable_block(), false, false); + print_statement_block(*node.get_initialize_block(), false, false); + print_statement_block(*node.get_setup_x_block(), false, false); printer->add_newline(); printer->add_line( "{0} = Eigen::PartialPivLU>>({3}).solve({4});"_format( X, float_type, N, Jm, F)); - print_statement_block(node->get_update_states_block().get(), false, false); - print_statement_block(node->get_finalize_block().get(), false, false); + print_statement_block(*node.get_update_states_block(), false, false); + print_statement_block(*node.get_finalize_block(), false, false); } /****************************************************************************************/ @@ -1777,12 +1777,12 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { } -std::string CodegenCVisitor::external_method_arguments() { +std::string CodegenCVisitor::external_method_arguments() const { return "id, pnodecount, data, indexes, thread, nt, v"; } -std::string CodegenCVisitor::external_method_parameters(bool table) { +std::string CodegenCVisitor::external_method_parameters(bool table) const { if (table) { return "int id, int pnodecount, double* data, Datum* indexes, " "ThreadDatum* thread, NrnThread* nt, int tml_id"; @@ -1872,7 +1872,7 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { } -std::string CodegenCVisitor::register_mechanism_arguments() { +std::string CodegenCVisitor::register_mechanism_arguments() const { auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "NULL"; auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "NULL"; auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); @@ -1882,12 +1882,14 @@ std::string CodegenCVisitor::register_mechanism_arguments() { } -std::pair CodegenCVisitor::read_ion_variable_name(std::string name) { +std::pair CodegenCVisitor::read_ion_variable_name( + const std::string& name) const { return {name, "ion_" + name}; } -std::pair CodegenCVisitor::write_ion_variable_name(std::string name) { +std::pair CodegenCVisitor::write_ion_variable_name( + const std::string& name) const { return {"ion_" + name, name}; } @@ -2110,7 +2112,8 @@ void CodegenCVisitor::print_thread_getters() { /****************************************************************************************/ -std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_instance) { +std::string CodegenCVisitor::float_variable_name(const SymbolType& symbol, + bool use_instance) const { auto name = symbol->get_name(); auto dimension = symbol->get_length(); auto num_float = float_variables_size(); @@ -2134,9 +2137,9 @@ std::string CodegenCVisitor::float_variable_name(SymbolType& symbol, bool use_in } -std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, +std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, const std::string& name, - bool use_instance) { + bool use_instance) const { auto position = position_of_int_var(name); auto num_int = int_variables_size(); std::string offset; @@ -2166,17 +2169,17 @@ std::string CodegenCVisitor::int_variable_name(IndexVariableInfo& symbol, } -std::string CodegenCVisitor::global_variable_name(SymbolType& symbol) { +std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol) const { return "{}_global.{}"_format(info.mod_suffix, symbol->get_name()); } -std::string CodegenCVisitor::ion_shadow_variable_name(SymbolType& symbol) { +std::string CodegenCVisitor::ion_shadow_variable_name(const SymbolType& symbol) const { return "inst->{}[id]"_format(symbol->get_name()); } -std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name) { +std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name) const { std::string result(name); if (ion_variable_struct_required()) { if (info.is_ion_read_variable(name)) { @@ -2193,7 +2196,7 @@ std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name } -std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use_instance) { +std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use_instance) const { std::string varname = update_if_ion_variable_name(name); // clang-format off @@ -3034,8 +3037,8 @@ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) * \details For CPU/Host target there is no device pointer. In this case * just use the host variable name directly. */ -std::string CodegenCVisitor::get_variable_device_pointer(std::string variable, - std::string /*type*/) { +std::string CodegenCVisitor::get_variable_device_pointer(const std::string& variable, + const std::string& /*type*/) const { return variable; } @@ -3149,8 +3152,8 @@ void CodegenCVisitor::print_initial_block(InitialBlock* node) { // initial block if (node != nullptr) { - auto block = node->get_statement_block(); - print_statement_block(block.get(), false, false); + const auto& block = node->get_statement_block(); + print_statement_block(*block.get(), false, false); } // write ion statements @@ -3371,11 +3374,11 @@ void CodegenCVisitor::print_watch_check() { } -void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_inst) { +void CodegenCVisitor::print_net_receive_common_code(Block& node, bool need_mech_inst) { printer->add_line("int tid = pnt->_tid;"); printer->add_line("int id = pnt->_i_instance;"); printer->add_line("double v = 0;"); - if (info.artificial_cell || node->is_initial_block()) { + if (info.artificial_cell || node.is_initial_block()) { printer->add_line("NrnThread* nt = nrn_threads + tid;"); printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); } @@ -3401,7 +3404,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_ auto statement = "double* {} = weights + weight_index + {};"_format(name, i); printer->add_line(statement); RenameVisitor vr(name, "*" + name); - node->visit_children(vr); + node.visit_children(vr); } i++; } @@ -3409,8 +3412,8 @@ void CodegenCVisitor::print_net_receive_common_code(Block* node, bool need_mech_ } -void CodegenCVisitor::print_net_send_call(FunctionCall* node) { - auto arguments = node->get_arguments(); +void CodegenCVisitor::print_net_send_call(FunctionCall& node) { + auto arguments = node.get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; std::string pnt = "pnt"; @@ -3441,13 +3444,13 @@ void CodegenCVisitor::print_net_send_call(FunctionCall* node) { } -void CodegenCVisitor::print_net_move_call(FunctionCall* node) { +void CodegenCVisitor::print_net_move_call(FunctionCall& node) { if (!printing_net_receive) { std::cout << "Error : net_move only allowed in NET_RECEIVE block" << std::endl; abort(); } - auto arguments = node->get_arguments(); + auto arguments = node.get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "-1"; std::string pnt = "pnt"; @@ -3469,8 +3472,8 @@ void CodegenCVisitor::print_net_move_call(FunctionCall* node) { } -void CodegenCVisitor::print_net_event_call(FunctionCall* node) { - auto arguments = node->get_arguments(); +void CodegenCVisitor::print_net_event_call(FunctionCall& node) { + auto arguments = node.get_arguments(); if (info.artificial_cell) { printer->add_text("net_event(pnt, "); print_vector_elements(arguments, ", "); @@ -3508,14 +3511,14 @@ void CodegenCVisitor::print_net_event_call(FunctionCall* node) { * * So, the `R` in AST needs to be renamed with `(*R)`. */ -static void rename_net_receive_arguments(ast::NetReceiveBlock* net_receive_node, ast::Node* node) { - auto parameters = net_receive_node->get_parameters(); +static void rename_net_receive_arguments(ast::NetReceiveBlock& net_receive_node, ast::Node& node) { + auto parameters = net_receive_node.get_parameters(); for (auto& parameter: parameters) { auto name = parameter->get_node_name(); auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); - node->get_statement_block()->visit_children(vr); + node.get_statement_block()->visit_children(vr); } } } @@ -3528,7 +3531,7 @@ void CodegenCVisitor::print_net_init() { } // rename net_receive arguments used in the initial block of net_receive - rename_net_receive_arguments(info.net_receive_node, node); + rename_net_receive_arguments(*info.net_receive_node, *node); codegen = true; auto args = "Point_process* pnt, int weight_index, double flag"; @@ -3539,8 +3542,8 @@ void CodegenCVisitor::print_net_init() { if (block->get_statements().empty()) { printer->add_line("// do nothing"); } else { - print_net_receive_common_code(node); - print_statement_block(block, false, false); + print_net_receive_common_code(*node); + print_statement_block(*block, false, false); } printer->end_block(1); codegen = false; @@ -3677,7 +3680,7 @@ void CodegenCVisitor::print_net_receive_kernel() { auto node = info.net_receive_node; // rename net_receive arguments used in the block itself - rename_net_receive_arguments(info.net_receive_node, node); + rename_net_receive_arguments(*info.net_receive_node, *node); std::string name; auto params = ParamVector(); @@ -3702,7 +3705,7 @@ void CodegenCVisitor::print_net_receive_kernel() { printer->add_newline(2); printer->start_block("static inline void {}({}) "_format(name, get_parameter_str(params))); - print_net_receive_common_code(node, info.artificial_cell); + print_net_receive_common_code(*node, info.artificial_cell); if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); } @@ -3808,7 +3811,7 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); - print_statement_block(block->get_statement_block().get(), false, false); + print_statement_block(*block->get_statement_block(), false, false); printer->add_line("int counter = -1;"); printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); @@ -3823,7 +3826,7 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { // clang-format on } -void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback* node) { +void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& node) { if (!codegen) { return; } @@ -3833,7 +3836,7 @@ void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback* n int num = info.derivimplicit_list_num; auto slist = get_variable_name("slist{}"_format(num)); auto dlist = get_variable_name("dlist{}"_format(num)); - auto block_name = node->get_node_to_solve()->get_node_name(); + auto block_name = node.get_node_to_solve()->get_node_name(); auto args = "{}, {}, {}, _derivimplicit_{}_{}, {}" @@ -3842,11 +3845,11 @@ void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback* n printer->add_line(statement); } -void CodegenCVisitor::visit_solution_expression(SolutionExpression* node) { - auto block = node->get_node_to_solve().get(); +void CodegenCVisitor::visit_solution_expression(SolutionExpression& node) { + auto block = node.get_node_to_solve().get(); if (block->is_statement_block()) { auto statement_block = dynamic_cast(block); - print_statement_block(statement_block, false, false); + print_statement_block(*statement_block, false, false); } else { block->accept(*this); } @@ -3893,7 +3896,7 @@ void CodegenCVisitor::print_nrn_state() { if (info.currents.empty() && info.breakpoint_node != nullptr) { auto block = info.breakpoint_node->get_statement_block(); - print_statement_block(block.get(), false, false); + print_statement_block(*block, false, false); } auto write_statements = ion_write_statements(BlockType::State); @@ -3920,14 +3923,14 @@ void CodegenCVisitor::print_nrn_state() { /****************************************************************************************/ -void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { +void CodegenCVisitor::print_nrn_current(BreakpointBlock& node) { auto args = internal_method_parameters(); - auto block = node->get_statement_block().get(); + const auto& block = node.get_statement_block(); printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline double nrn_current({})"_format(get_parameter_str(args))); printer->add_line("double current = 0.0;"); - print_statement_block(block, false, false); + print_statement_block(*block, false, false); for (auto& current: info.currents) { auto name = get_variable_name(current); printer->add_line("current += {};"_format(name)); @@ -3937,9 +3940,9 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock* node) { } -void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock* node) { - auto block = node->get_statement_block(); - print_statement_block(block.get(), false, false); +void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock& node) { + const auto& block = node.get_statement_block(); + print_statement_block(*block, false, false); if (!info.currents.empty()) { std::string sum; for (const auto& current: info.currents) { @@ -4004,7 +4007,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { } -void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock* node) { +void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock& node) { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); if (ion_variable_struct_required()) { @@ -4076,7 +4079,7 @@ void CodegenCVisitor::print_nrn_cur() { codegen = true; if (info.conductances.empty()) { - print_nrn_current(info.breakpoint_node); + print_nrn_current(*info.breakpoint_node); } printer->add_newline(2); @@ -4085,7 +4088,7 @@ void CodegenCVisitor::print_nrn_cur() { print_channel_iteration_tiling_block_begin(BlockType::Equation); print_channel_iteration_block_begin(BlockType::Equation); print_post_channel_iteration_common_code(); - print_nrn_cur_kernel(info.breakpoint_node); + print_nrn_cur_kernel(*info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); if (!nrn_cur_reduction_loop_required()) { print_fast_imem_calculation(); @@ -4152,10 +4155,10 @@ void CodegenCVisitor::print_compute_functions() { print_top_verbatim_blocks(); print_function_prototypes(); for (const auto& procedure: info.procedures) { - print_procedure(procedure); + print_procedure(*procedure); } for (const auto& function: info.functions) { - print_function(function); + print_function(*function); } for (const auto& callback: info.derivimplicit_callbacks) { auto block = callback->get_node_to_solve().get(); @@ -4207,8 +4210,8 @@ void CodegenCVisitor::set_codegen_global_variables(std::vector& glob } -void CodegenCVisitor::setup(Program* node) { - program_symtab = node->get_symbol_table(); +void CodegenCVisitor::setup(Program& node) { + program_symtab = node.get_symbol_table(); CodegenHelperVisitor v; info = v.analyze(node); @@ -4227,7 +4230,7 @@ void CodegenCVisitor::setup(Program* node) { } -void CodegenCVisitor::visit_program(Program* node) { +void CodegenCVisitor::visit_program(Program& node) { setup(node); print_codegen_routines(); print_wrapper_routines(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 62515f5af6..f12f6cfaeb 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -18,7 +18,7 @@ #include #include #include -#include +#include #include #include @@ -117,7 +117,7 @@ struct IndexVariableInfo { /// if the variable is qualified as constant (this is property of IndexVariable) bool is_constant = false; - IndexVariableInfo(std::shared_ptr symbol, + IndexVariableInfo(const std::shared_ptr& symbol, bool is_vdata = false, bool is_index = false, bool is_integer = false) @@ -296,7 +296,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Return Nmodl language version * \return A version */ - std::string nmodl_version() { + std::string nmodl_version() const noexcept { return codegen::naming::NMODL_VERSION; } @@ -306,7 +306,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param text The string to be quoted * \return The same string with double-quotes pre- and postfixed */ - std::string add_escape_quote(const std::string& text) { + std::string add_escape_quote(const std::string& text) const { return "\"" + text + "\""; } @@ -314,7 +314,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Operator for rhs vector update (matrix update) */ - std::string operator_for_rhs() { + std::string operator_for_rhs() const noexcept { return info.electrode_current ? "+=" : "-="; } @@ -322,7 +322,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Operator for diagonal vector update (matrix update) */ - std::string operator_for_d() { + std::string operator_for_d() const noexcept { return info.electrode_current ? "-=" : "+="; } @@ -330,7 +330,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Data type for the local variables */ - std::string local_var_type() { + std::string local_var_type() const noexcept { return codegen::naming::DEFAULT_LOCAL_VAR_TYPE; } @@ -338,7 +338,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Default data type for floating point elements */ - std::string default_float_data_type() { + std::string default_float_data_type() const noexcept { return codegen::naming::DEFAULT_FLOAT_TYPE; } @@ -346,7 +346,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Data type for floating point elements specified on command line */ - std::string float_data_type() { + const std::string& float_data_type() const noexcept { return float_type; } @@ -354,7 +354,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Default data type for integer (offset) elements */ - std::string default_int_data_type() { + std::string default_int_data_type() const noexcept { return codegen::naming::DEFAULT_INTEGER_TYPE; } @@ -364,7 +364,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The function name to check * \return \c true if the function is net_send */ - bool is_net_send(const std::string& name) { + bool is_net_send(const std::string& name) const noexcept { return name == codegen::naming::NET_SEND_METHOD; } @@ -373,7 +373,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The function name to check * \return \c true if the function is net_move */ - bool is_net_move(const std::string& name) { + bool is_net_move(const std::string& name) const noexcept { return name == codegen::naming::NET_MOVE_METHOD; } @@ -382,7 +382,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The function name to check * \return \c true if the function is net_event */ - bool is_net_event(const std::string& name) { + bool is_net_event(const std::string& name) const noexcept { return name == codegen::naming::NET_EVENT_METHOD; } @@ -390,7 +390,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Name of structure that wraps range variables */ - std::string instance_struct() { + std::string instance_struct() const { return "{}_Instance"_format(info.mod_suffix); } @@ -398,7 +398,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Name of structure that wraps range variables */ - std::string global_struct() { + std::string global_struct() const { return "{}_Store"_format(info.mod_suffix); } @@ -408,9 +408,8 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of the function or procedure * \return The name of the function or procedure postfixed with the model name */ - std::string method_name(const std::string& name) { - auto suffix = info.mod_suffix; - return name + "_" + suffix; + std::string method_name(const std::string& name) const { + return name + "_" + info.mod_suffix; } @@ -419,7 +418,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of the variable * \return The name of the variable prefixed with \c shadow_ */ - std::string shadow_varname(const std::string& name) { + std::string shadow_varname(const std::string& name) const { return "shadow_" + name; } @@ -429,7 +428,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of the symbol * \return A symbol based on the given name */ - SymbolType make_symbol(std::string name) { + SymbolType make_symbol(const std::string& name) const { return std::make_shared(name, ModToken()); } @@ -439,56 +438,56 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The variable name * \return \c true if the variable is a state variable */ - bool state_variable(std::string name); + bool state_variable(const std::string& name) const; /** * Check if net receive/send buffering kernels required */ - bool net_receive_buffering_required(); + bool net_receive_buffering_required() const noexcept; /** * Check if nrn_state function is required */ - bool nrn_state_required(); + bool nrn_state_required() const noexcept; /** * Check if nrn_cur function is required */ - bool nrn_cur_required(); + bool nrn_cur_required() const noexcept; /** * Check if net_receive function is required */ - bool net_receive_required(); + bool net_receive_required() const noexcept; /** * Check if net_send_buffer is required */ - bool net_send_buffer_required(); + bool net_send_buffer_required() const noexcept; /** * Check if setup_range_variable function is required * \return */ - bool range_variable_setup_required(); + bool range_variable_setup_required() const noexcept; /** * Check if net_receive node exist */ - bool net_receive_exist(); + bool net_receive_exist() const noexcept; /** * Check if breakpoint node exist */ - bool breakpoint_exist(); + bool breakpoint_exist() const noexcept; /** @@ -496,7 +495,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of the method to check * \return \c true if the method is defined */ - bool defined_method(const std::string& name); + bool defined_method(const std::string& name) const; /** @@ -504,7 +503,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param node The AST Statement node to check * \return \c true if this Statement is to be skipped */ - bool statement_to_skip(ast::Statement* node); + bool statement_to_skip(const ast::Statement& node) const; /** @@ -512,13 +511,13 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param node The AST Statement node to check * \return \c true if this Statement requires a semicolon */ - bool need_semicolon(ast::Statement* node); + bool need_semicolon(ast::Statement* node) const; /** * Determine the number of threads to allocate */ - int num_thread_objects() { + int num_thread_objects() const noexcept { return info.vectorize ? (info.thread_data_index + 1) : 0; } @@ -526,13 +525,13 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Number of float variables in the model */ - int float_variables_size(); + int float_variables_size() const; /** * Number of integer variables in the model */ - int int_variables_size(); + int int_variables_size() const; /** @@ -540,7 +539,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of a float variable * \return The position index in the data array */ - int position_of_float_var(const std::string& name); + int position_of_float_var(const std::string& name) const; /** @@ -548,7 +547,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of an int variable * \return The position index in the data array */ - int position_of_int_var(const std::string& name); + int position_of_int_var(const std::string& name) const; /** @@ -556,13 +555,13 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The ion variable name * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena) */ - std::string update_if_ion_variable_name(const std::string& name); + std::string update_if_ion_variable_name(const std::string& name) const; /** * Name of the code generation backend */ - virtual std::string backend_name(); + virtual std::string backend_name() const; /** @@ -593,7 +592,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \return The backend code string representing the access to the given variable * symbol */ - std::string float_variable_name(SymbolType& symbol, bool use_instance); + std::string float_variable_name(const SymbolType& symbol, bool use_instance) const; /** @@ -609,9 +608,9 @@ class CodegenCVisitor: public visitor::AstVisitor { * \return The backend code string representing the access to the given variable * symbol */ - std::string int_variable_name(IndexVariableInfo& symbol, + std::string int_variable_name(const IndexVariableInfo& symbol, const std::string& name, - bool use_instance); + bool use_instance) const; /** @@ -619,7 +618,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param symbol The symbol of a variable for which we want to obtain its name * \return The C string representing the access to the global variable */ - std::string global_variable_name(SymbolType& symbol); + std::string global_variable_name(const SymbolType& symbol) const; /** @@ -627,7 +626,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param symbol The symbol of a variable for which we want to obtain its name * \return The C string representing the access to the shadow variable */ - std::string ion_shadow_variable_name(SymbolType& symbol); + std::string ion_shadow_variable_name(const SymbolType& symbol) const; /** @@ -638,7 +637,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \return The C string representing the access to the variable in the neuron thread * structure */ - std::string get_variable_name(const std::string& name, bool use_instance = true); + std::string get_variable_name(const std::string& name, bool use_instance = true) const; /** @@ -647,7 +646,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param current The variable name for the current used in the model * \return The name for the current to be printed in C */ - std::string breakpoint_current(std::string current); + std::string breakpoint_current(std::string current) const; /** @@ -715,7 +714,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param open_brace Print an opening brace if \c false * \param close_brace Print a closing brace if \c true */ - void print_statement_block(ast::StatementBlock* node, + void print_statement_block(ast::StatementBlock& node, bool open_brace = true, bool close_brace = true); @@ -724,7 +723,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Check if a structure for ion variables is required * \return \c true if a structure fot ion variables must be generated */ - bool ion_variable_struct_required(); + bool ion_variable_struct_required() const; /** @@ -781,7 +780,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The ion variable name * \return The ion read variable name */ - std::pair read_ion_variable_name(std::string name); + std::pair read_ion_variable_name(const std::string& name) const; /** @@ -789,7 +788,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The ion variable name * \return The ion write variable name */ - std::pair write_ion_variable_name(std::string name); + std::pair write_ion_variable_name(const std::string& name) const; /** @@ -822,7 +821,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - std::string external_method_arguments(); + std::string external_method_arguments() const; /** @@ -834,13 +833,13 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param table * \return A string representing the parameters of the function */ - std::string external_method_parameters(bool table = false); + std::string external_method_parameters(bool table = false) const; /** * Arguments for register_mech or point_register_mech function */ - std::string register_mechanism_arguments(); + std::string register_mechanism_arguments() const; /** @@ -867,7 +866,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Return the name of main compute kernels * \param type A block type */ - virtual std::string compute_method_name(BlockType type); + virtual std::string compute_method_name(BlockType type) const; /** @@ -953,7 +952,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Print memory allocation routine */ - virtual void print_memory_allocation_routine(); + virtual void print_memory_allocation_routine() const; /** @@ -1000,7 +999,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Check if ion variable are copies avoided */ - bool optimize_ion_variable_copies(); + bool optimize_ion_variable_copies() const; /** @@ -1014,7 +1013,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param name The name of variable * \return \c true if it is constant */ - virtual bool is_constant_variable(std::string name); + virtual bool is_constant_variable(const std::string& name) const; /** @@ -1189,28 +1188,28 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print call to internal or external function * \param node The AST node representing a function call */ - void print_function_call(ast::FunctionCall* node); + void print_function_call(ast::FunctionCall& node); /** * Print call to \c net\_send * \param node The AST node representing the function call */ - void print_net_send_call(ast::FunctionCall* node); + void print_net_send_call(ast::FunctionCall& node); /** * Print call to net\_move * \param node The AST node representing the function call */ - void print_net_move_call(ast::FunctionCall* node); + void print_net_move_call(ast::FunctionCall& node); /** * Print call to net\_event * \param node The AST node representing the function call */ - void print_net_event_call(ast::FunctionCall* node); + void print_net_event_call(ast::FunctionCall& node); /** @@ -1306,7 +1305,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param node the AST node representing the function or procedure in NMODL * \param name the name of the function or procedure */ - void print_function_or_procedure(ast::Block* node, std::string& name); + void print_function_or_procedure(ast::Block& node, const std::string& name); /** @@ -1354,7 +1353,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param need_mech_inst \c true if a local \c inst variable needs to be defined in generated * code */ - void print_net_receive_common_code(ast::Block* node, bool need_mech_inst = true); + void print_net_receive_common_code(ast::Block& node, bool need_mech_inst = true); /** @@ -1456,7 +1455,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print main body of nrn_cur function * \param node the AST node representing the NMODL breakpoint block */ - void print_nrn_cur_kernel(ast::BreakpointBlock* node); + void print_nrn_cur_kernel(ast::BreakpointBlock& node); /** @@ -1467,7 +1466,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * * \param node the AST node representing the NMODL breakpoint block */ - void print_nrn_cur_conductance_kernel(ast::BreakpointBlock* node); + void print_nrn_cur_conductance_kernel(ast::BreakpointBlock& node); /** @@ -1485,7 +1484,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified * \param node the AST node representing the NMODL breakpoint block */ - void print_nrn_current(ast::BreakpointBlock* node); + void print_nrn_current(ast::BreakpointBlock& node); /** @@ -1588,15 +1587,16 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Get device variable pointer for corresponding host variable */ - virtual std::string get_variable_device_pointer(std::string variable, std::string type); + virtual std::string get_variable_device_pointer(const std::string& variable, + const std::string& type) const; - CodegenCVisitor(std::string mod_filename, - std::string output_dir, + CodegenCVisitor(const std::string& mod_filename, + const std::string& output_dir, LayoutType layout, - std::string float_type, - std::string extension, - std::string wrapper_ext) + const std::string& float_type, + const std::string& extension, + const std::string& wrapper_ext) : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , wrapper_printer(new CodePrinter(output_dir + "/" + mod_filename + wrapper_ext)) , printer(target_printer) @@ -1624,11 +1624,11 @@ class CodegenCVisitor: public visitor::AstVisitor { * as-is in the target code. This defaults to \c double. * \param extension The file extension to use. This defaults to \c .cpp . */ - CodegenCVisitor(std::string mod_filename, - std::string output_dir, + CodegenCVisitor(const std::string& mod_filename, + const std::string& output_dir, LayoutType layout, - std::string float_type, - std::string extension = ".cpp") + const std::string& float_type, + const std::string& extension = ".cpp") : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , printer(target_printer) , mod_filename(mod_filename) @@ -1639,7 +1639,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \copybrief CodegenCVisitor(std::string, std::string, LayoutType, std::string, std::string) * * This constructor instantiates an NMODL C code generator and allows writing generated code - * into a \c std::stringstream. + * into an output stream. * * \note No code generation is performed at this stage. Since the code * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c @@ -1647,15 +1647,15 @@ class CodegenCVisitor: public visitor::AstVisitor { * * \param mod_filename The name of the model for which code should be generated. * It is used for constructing an output filename. - * \param stream The \c std::stringstream onto which to write the generated code + * \param stream The output stream onto which to write the generated code * \param layout The memory layout to be used for data structure generation. * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. */ - CodegenCVisitor(std::string mod_filename, - std::stringstream& stream, + CodegenCVisitor(const std::string& mod_filename, + std::ostream& stream, LayoutType layout, - std::string float_type) + const std::string& float_type) : target_printer(new CodePrinter(stream)) , printer(target_printer) , mod_filename(mod_filename) @@ -1743,28 +1743,28 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print \c check\_function() for functions or procedure using table * \param node The AST node representing a function or procedure block */ - void print_table_check_function(ast::Block* node); + void print_table_check_function(ast::Block& node); /** * Print replacement function for function or procedure using table * \param node The AST node representing a function or procedure block */ - void print_table_replacement_function(ast::Block* node); + void print_table_replacement_function(ast::Block& node); /** * Print NMODL function in target backend code * \param node */ - void print_function(ast::FunctionBlock* node); + void print_function(ast::FunctionBlock& node); /** * Print NMODL procedure in target backend code * \param node */ - virtual void print_procedure(ast::ProcedureBlock* node); + virtual void print_procedure(ast::ProcedureBlock& node); /** Setup the target backend code generator @@ -1772,7 +1772,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Typically called from within \c visit\_program but may be called from * specialized targets to setup this Code generator as fallback. */ - void setup(ast::Program* node); + void setup(ast::Program& node); /** @@ -1787,37 +1787,37 @@ class CodegenCVisitor: public visitor::AstVisitor { * @param original_name Original name of variable to change * @return std::string Unique name produced as _ */ - std::string find_var_unique_name(const std::string& original_name); - - virtual void visit_binary_expression(ast::BinaryExpression* node) override; - virtual void visit_binary_operator(ast::BinaryOperator* node) override; - virtual void visit_boolean(ast::Boolean* node) override; - virtual void visit_double(ast::Double* node) override; - virtual void visit_else_if_statement(ast::ElseIfStatement* node) override; - virtual void visit_else_statement(ast::ElseStatement* node) override; - virtual void visit_float(ast::Float* node) override; - virtual void visit_from_statement(ast::FromStatement* node) override; - virtual void visit_function_call(ast::FunctionCall* node) override; - virtual void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; - virtual void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) override; - virtual void visit_if_statement(ast::IfStatement* node) override; - virtual void visit_indexed_name(ast::IndexedName* node) override; - virtual void visit_integer(ast::Integer* node) override; - virtual void visit_local_list_statement(ast::LocalListStatement* node) override; - virtual void visit_name(ast::Name* node) override; - virtual void visit_paren_expression(ast::ParenExpression* node) override; - virtual void visit_prime_name(ast::PrimeName* node) override; - virtual void visit_program(ast::Program* node) override; - virtual void visit_statement_block(ast::StatementBlock* node) override; - virtual void visit_string(ast::String* node) override; - virtual void visit_solution_expression(ast::SolutionExpression* node) override; - virtual void visit_unary_operator(ast::UnaryOperator* node) override; - virtual void visit_unit(ast::Unit* node) override; - virtual void visit_var_name(ast::VarName* node) override; - virtual void visit_verbatim(ast::Verbatim* node) override; - virtual void visit_watch_statement(ast::WatchStatement* node) override; - virtual void visit_while_statement(ast::WhileStatement* node) override; - virtual void visit_derivimplicit_callback(ast::DerivimplicitCallback* node) override; + std::string find_var_unique_name(const std::string& original_name) const; + + void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_binary_operator(ast::BinaryOperator& node) override; + void visit_boolean(ast::Boolean& node) override; + void visit_double(ast::Double& node) override; + void visit_else_if_statement(ast::ElseIfStatement& node) override; + void visit_else_statement(ast::ElseStatement& node) override; + void visit_float(ast::Float& node) override; + void visit_from_statement(ast::FromStatement& node) override; + void visit_function_call(ast::FunctionCall& node) override; + void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) override; + void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) override; + void visit_if_statement(ast::IfStatement& node) override; + void visit_indexed_name(ast::IndexedName& node) override; + void visit_integer(ast::Integer& node) override; + void visit_local_list_statement(ast::LocalListStatement& node) override; + void visit_name(ast::Name& node) override; + void visit_paren_expression(ast::ParenExpression& node) override; + void visit_prime_name(ast::PrimeName& node) override; + void visit_program(ast::Program& node) override; + void visit_statement_block(ast::StatementBlock& node) override; + void visit_string(ast::String& node) override; + void visit_solution_expression(ast::SolutionExpression& node) override; + void visit_unary_operator(ast::UnaryOperator& node) override; + void visit_unit(ast::Unit& node) override; + void visit_var_name(ast::VarName& node) override; + void visit_verbatim(ast::Verbatim& node) override; + void visit_watch_statement(ast::WatchStatement& node) override; + void visit_while_statement(ast::WhileStatement& node) override; + void visit_derivimplicit_callback(ast::DerivimplicitCallback& node) override; }; @@ -1868,14 +1868,14 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin // internal and user provided arguments auto internal_params = internal_method_parameters(); - auto params = node->get_parameters(); + const auto& params = node.get_parameters(); for (const auto& param: params) { internal_params.emplace_back("", type, "", param.get()->get_node_name()); } // procedures have "int" return type by default std::string return_type = "int"; - if (node->is_function_block()) { + if (node.is_function_block()) { return_type = default_float_data_type(); } diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 69da031e29..68bcde6bcc 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -23,8 +23,8 @@ namespace codegen { using visitor::AstLookupVisitor; std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( - ast::Ast* node, - std::shared_ptr& ast_node) { + ast::Ast& node, + const std::shared_ptr& ast_node) { auto solve_block_ast_node = std::dynamic_pointer_cast(ast_node); std::stringstream unhandled_method_error_message; auto method = solve_block_ast_node->get_method(); @@ -41,11 +41,11 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl } std::string CodegenCompatibilityVisitor::return_error_global_var( - ast::Ast* node, - std::shared_ptr& ast_node) { + ast::Ast& node, + const std::shared_ptr& ast_node) { auto global_var = std::dynamic_pointer_cast(ast_node); std::stringstream error_message_global_var; - if (node->get_symbol_table()->lookup(global_var->get_node_name())->get_write_count() > 0) { + if (node.get_symbol_table()->lookup(global_var->get_node_name())->get_write_count() > 0) { error_message_global_var << "\"{}\" variable found at [{}] should be defined as a RANGE variable instead of GLOBAL to enable backend transformations\n"_format( global_var->get_node_name(), global_var->get_token()->position()); @@ -53,16 +53,17 @@ std::string CodegenCompatibilityVisitor::return_error_global_var( return error_message_global_var.str(); } -std::string CodegenCompatibilityVisitor::return_error_pointer(ast::Ast* node, - std::shared_ptr& ast_node) { +std::string CodegenCompatibilityVisitor::return_error_pointer( + ast::Ast& node, + const std::shared_ptr& ast_node) { auto pointer_var = std::dynamic_pointer_cast(ast_node); return "\"{}\" POINTER found at [{}] should be defined as BBCOREPOINTER to use it in CoreNeuron\n"_format( pointer_var->get_node_name(), pointer_var->get_token()->position()); } std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( - ast::Ast* node, - std::shared_ptr& ast_node) { + ast::Ast& node, + const std::shared_ptr& ast_node) { std::stringstream error_message_no_bbcore_read_write; auto verbatim_nodes = AstLookupVisitor().lookup(node, AstNodeType::VERBATIM); auto found_bbcore_read = false; @@ -106,7 +107,7 @@ std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( * some kind of incompatibility return false. */ -bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast* node) { +bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) { std::vector unhandled_ast_types; for (auto kv: unhandled_ast_types_func) { unhandled_ast_types.push_back(kv.first); diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index fa89e869ef..52398f007d 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -41,8 +41,9 @@ using namespace ast; class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// Typedef for defining FunctionPointer that points to the /// function needed to be called for every kind of error - typedef std::string (CodegenCompatibilityVisitor::*FunctionPointer)(ast::Ast* node, - std::shared_ptr&); + typedef std::string (CodegenCompatibilityVisitor::*FunctionPointer)( + ast::Ast& node, + const std::shared_ptr&); /// Unordered_map to find the function needed to be called in /// for every ast::AstNodeType that is unsupported @@ -100,7 +101,7 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// /// \param node Ast /// \return bool if there are unhandled nodes or not - bool find_unhandled_ast_nodes(Ast* node); + bool find_unhandled_ast_nodes(Ast& node); /// Takes as parameter an std::shared_ptr, /// searches if the method used for solving is supported @@ -109,8 +110,9 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param node Not used by the function /// \param ast_node Ast node which is checked /// \return std::string error - std::string return_error_if_solve_method_is_unhandled(ast::Ast* node, - std::shared_ptr& ast_node); + std::string return_error_if_solve_method_is_unhandled( + ast::Ast& node, + const std::shared_ptr& ast_node); /// Takes as parameter an std::shared_ptr node /// and returns a relative error with the name, the type @@ -121,7 +123,7 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param ast_node Ast node which is checked /// \return std::string error template - std::string return_error_with_name(ast::Ast* node, std::shared_ptr& ast_node) { + std::string return_error_with_name(ast::Ast& node, const std::shared_ptr& ast_node) { auto real_type_block = std::dynamic_pointer_cast(ast_node); return "\"{}\" {}construct found at [{}] is not handled\n"_format( ast_node->get_node_name(), @@ -138,7 +140,8 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param ast_node Ast node which is checked /// \return std::string error template - std::string return_error_without_name(ast::Ast* node, std::shared_ptr& ast_node) { + std::string return_error_without_name(ast::Ast& node, + const std::shared_ptr& ast_node) { auto real_type_block = std::dynamic_pointer_cast(ast_node); return "{}construct found at [{}] is not handled\n"_format( real_type_block->get_nmodl_name(), real_type_block->get_token()->position()); @@ -152,7 +155,7 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param node Ast /// \param ast_node Ast node which is checked /// \return std::string error - std::string return_error_global_var(ast::Ast* node, std::shared_ptr& ast_node); + std::string return_error_global_var(ast::Ast& node, const std::shared_ptr& ast_node); /// Takes as parameter an std::shared_ptr node /// and returns a relative error with the name and the @@ -162,7 +165,7 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param node Not used by the function /// \param ast_node Ast node which is checked /// \return std::string error - std::string return_error_pointer(ast::Ast* node, std::shared_ptr& ast_node); + std::string return_error_pointer(ast::Ast& node, const std::shared_ptr& ast_node); /// Takes as parameter the ast::Ast and checks if the /// functions "bbcore_read" and "bbcore_write" are defined @@ -173,8 +176,8 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param node Ast /// \param ast_node Not used by the function /// \return std::string error - std::string return_error_if_no_bbcore_read_write(ast::Ast* node, - std::shared_ptr& ast_node); + std::string return_error_if_no_bbcore_read_write(ast::Ast& node, + const std::shared_ptr& ast_node); }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index a99b5d576c..dd44ba1ff3 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -25,7 +25,7 @@ using symtab::syminfo::NmodlType; * backend can mark the parameter as constant even if they have * write count > 0 (typically due to initial block). */ -bool CodegenCudaVisitor::is_constant_variable(std::string name) { +bool CodegenCudaVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { @@ -40,7 +40,7 @@ bool CodegenCudaVisitor::is_constant_variable(std::string name) { } -std::string CodegenCudaVisitor::compute_method_name(BlockType type) { +std::string CodegenCudaVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name("nrn_init"); } @@ -56,7 +56,7 @@ std::string CodegenCudaVisitor::compute_method_name(BlockType type) { void CodegenCudaVisitor::print_atomic_op(const std::string& lhs, const std::string& op, - const std::string& rhs) { + const std::string& rhs) const { std::string function; if (op == "+") { function = "atomicAdd"; @@ -74,7 +74,7 @@ void CodegenCudaVisitor::print_backend_includes() { } -std::string CodegenCudaVisitor::backend_name() { +std::string CodegenCudaVisitor::backend_name() const { return "C-CUDA (api-compatibility)"; } @@ -165,11 +165,11 @@ void CodegenCudaVisitor::print_compute_functions() { print_function_prototypes(); for (const auto& procedure: info.procedures) { - print_procedure(procedure); + print_procedure(*procedure); } for (const auto& function: info.functions) { - print_function(function); + print_function(*function); } print_net_send_buffering(); @@ -180,13 +180,13 @@ void CodegenCudaVisitor::print_compute_functions() { } -void CodegenCudaVisitor::print_wrapper_routine(std::string wraper_function, BlockType type) { - auto args = "NrnThread* nt, Memb_list* ml, int type"; - wraper_function = method_name(wraper_function); +void CodegenCudaVisitor::print_wrapper_routine(std::string wrapper_function, BlockType type) { + static const auto args = "NrnThread* nt, Memb_list* ml, int type"; + wrapper_function = method_name(wrapper_function); auto compute_function = compute_method_name(type); printer->add_newline(2); - printer->start_block("void {}({})"_format(wraper_function, args)); + printer->start_block("void {}({})"_format(wrapper_function, args)); printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int nthread = 256;"); printer->add_line("int nblock = (nodecount+nthread-1)/nthread;"); diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 0558b39e27..5fb7f4856a 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -27,17 +27,19 @@ namespace codegen { * \brief %Visitor for printing CUDA backend */ class CodegenCudaVisitor: public CodegenCVisitor { - void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); + void print_atomic_op(const std::string& lhs, + const std::string& op, + const std::string& rhs) const; protected: /// name of the code generation backend - std::string backend_name() override; + std::string backend_name() const override; /// if variable is qualified as constant - bool is_constant_variable(std::string name) override; + bool is_constant_variable(const std::string& name) const override; /// return name of main compute kernels - std::string compute_method_name(BlockType type) override; + std::string compute_method_name(BlockType type) const override; /// common includes : standard c/c++, coreneuron and backend specific @@ -91,7 +93,7 @@ class CodegenCudaVisitor: public CodegenCVisitor { /// print wrapper function that calls cuda kernel - void print_wrapper_routine(std::string wraper_function, BlockType type); + void print_wrapper_routine(std::string wrapper_function, BlockType type); /// wrapper/caller routines for nrn_state and nrn_cur @@ -102,16 +104,16 @@ class CodegenCudaVisitor: public CodegenCVisitor { void print_codegen_routines() override; public: - CodegenCudaVisitor(std::string mod_file, - std::string output_dir, + CodegenCudaVisitor(const std::string& mod_file, + const std::string& output_dir, LayoutType layout, - std::string float_type) + const std::string& float_type) : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".cu") {} - CodegenCudaVisitor(std::string mod_file, - std::stringstream& stream, + CodegenCudaVisitor(const std::string& mod_file, + std::ostream& stream, LayoutType layout, - std::string float_type) + const std::string& float_type) : CodegenCVisitor(mod_file, stream, layout, float_type) {} }; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 8f5c788062..daedb39906 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -48,7 +48,7 @@ using symtab::syminfo::Status; * Note that variables in double array do not need this transformation * and it seems like they should just follow definition order. */ -void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& symbols) { +void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& symbols) const { /// first sort by global id to get in reverse order std::sort(symbols.begin(), symbols.end(), @@ -81,7 +81,7 @@ void CodegenHelperVisitor::find_ion_variables() { * For example, eca belongs to ca ion, nai belongs to na ion. * We just check if we exclude first/last char, if that is ion name. */ - auto ion_variable = [](std::string var, std::string ion) -> bool { + auto ion_variable = [](const std::string& var, const std::string& ion) -> bool { auto len = var.size() - 1; return (var.substr(1, len) == ion || var.substr(0, len) == ion); }; @@ -417,8 +417,8 @@ void CodegenHelperVisitor::find_table_variables() { } -void CodegenHelperVisitor::visit_suffix(Suffix* node) { - auto type = node->get_type()->get_node_name(); +void CodegenHelperVisitor::visit_suffix(Suffix& node) { + const auto& type = node.get_type()->get_node_name(); if (type == naming::POINT_PROCESS) { info.point_process = true; } @@ -426,90 +426,90 @@ void CodegenHelperVisitor::visit_suffix(Suffix* node) { info.artificial_cell = true; info.point_process = true; } - info.mod_suffix = node->get_node_name(); + info.mod_suffix = node.get_node_name(); } -void CodegenHelperVisitor::visit_elctrode_current(ElctrodeCurrent* node) { +void CodegenHelperVisitor::visit_elctrode_current(ElctrodeCurrent& node) { info.electrode_current = true; } -void CodegenHelperVisitor::visit_initial_block(InitialBlock* node) { +void CodegenHelperVisitor::visit_initial_block(InitialBlock& node) { if (under_net_receive_block) { - info.net_receive_initial_node = node; + info.net_receive_initial_node = &node; } else { - info.initial_node = node; + info.initial_node = &node; } - node->visit_children(*this); + node.visit_children(*this); } -void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock* node) { +void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock& node) { under_net_receive_block = true; - info.net_receive_node = node; - info.num_net_receive_parameters = node->get_parameters().size(); - node->visit_children(*this); + info.net_receive_node = &node; + info.num_net_receive_parameters = node.get_parameters().size(); + node.visit_children(*this); under_net_receive_block = false; } -void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock* node) { +void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock& node) { under_derivative_block = true; - node->visit_children(*this); + node.visit_children(*this); under_derivative_block = false; } -void CodegenHelperVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback* node) { +void CodegenHelperVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& node) { info.derivimplicit_used = true; - info.derivimplicit_callbacks.push_back(node); + info.derivimplicit_callbacks.push_back(&node); } -void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock* node) { +void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock& node) { under_breakpoint_block = true; - info.breakpoint_node = node; - node->visit_children(*this); + info.breakpoint_node = &node; + node.visit_children(*this); under_breakpoint_block = false; } -void CodegenHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock* node) { - info.nrn_state_block = node; - node->visit_children(*this); +void CodegenHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock& node) { + info.nrn_state_block = &node; + node.visit_children(*this); } -void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock* node) { - info.procedures.push_back(node); - node->visit_children(*this); +void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock& node) { + info.procedures.push_back(&node); + node.visit_children(*this); if (table_statement_used) { table_statement_used = false; - info.functions_with_table.push_back(node); + info.functions_with_table.push_back(&node); } } -void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock* node) { - info.functions.push_back(node); - node->visit_children(*this); +void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock& node) { + info.functions.push_back(&node); + node.visit_children(*this); if (table_statement_used) { table_statement_used = false; - info.functions_with_table.push_back(node); + info.functions_with_table.push_back(&node); } } -void CodegenHelperVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) { +void CodegenHelperVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) { info.eigen_newton_solver_exist = true; } -void CodegenHelperVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) { +void CodegenHelperVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) { info.eigen_linear_solver_exist = true; } -void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { - auto name = node->get_node_name(); +void CodegenHelperVisitor::visit_function_call(FunctionCall& node) { + auto name = node.get_node_name(); if (name == naming::NET_SEND_METHOD) { info.net_send_used = true; } @@ -519,9 +519,9 @@ void CodegenHelperVisitor::visit_function_call(FunctionCall* node) { } -void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { - auto ion = node->get_ion(); - auto variable = node->get_conductance(); +void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint& node) { + const auto& ion = node.get_ion(); + const auto& variable = node.get_conductance(); std::string ion_name; if (ion) { ion_name = ion->get_node_name(); @@ -545,8 +545,8 @@ void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint* node) { * is because prime_variables_by_order should contain state variable name and * not the one replaced by solver pass. */ -void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { - auto statements = node->get_statements(); +void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock& node) { + const auto& statements = node.get_statements(); for (auto& statement: statements) { statement->accept(*this); if (under_derivative_block && assign_lhs && @@ -569,57 +569,57 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock* node) { } } -void CodegenHelperVisitor::visit_factor_def(ast::FactorDef* node) { - info.factor_definitions.push_back(node); +void CodegenHelperVisitor::visit_factor_def(ast::FactorDef& node) { + info.factor_definitions.push_back(&node); } -void CodegenHelperVisitor::visit_binary_expression(BinaryExpression* node) { - if (node->get_op().eval() == "=") { - assign_lhs = node->get_lhs(); +void CodegenHelperVisitor::visit_binary_expression(BinaryExpression& node) { + if (node.get_op().eval() == "=") { + assign_lhs = node.get_lhs(); } - node->get_lhs()->accept(*this); - node->get_rhs()->accept(*this); + node.get_lhs()->accept(*this); + node.get_rhs()->accept(*this); } -void CodegenHelperVisitor::visit_bbcore_pointer(BbcorePointer* node) { +void CodegenHelperVisitor::visit_bbcore_pointer(BbcorePointer& node) { info.bbcore_pointer_used = true; } -void CodegenHelperVisitor::visit_watch(ast::Watch* node) { +void CodegenHelperVisitor::visit_watch(ast::Watch& node) { info.watch_count++; } -void CodegenHelperVisitor::visit_watch_statement(ast::WatchStatement* node) { - info.watch_statements.push_back(node); - node->visit_children(*this); +void CodegenHelperVisitor::visit_watch_statement(ast::WatchStatement& node) { + info.watch_statements.push_back(&node); + node.visit_children(*this); } -void CodegenHelperVisitor::visit_for_netcon(ast::ForNetcon* node) { +void CodegenHelperVisitor::visit_for_netcon(ast::ForNetcon& node) { info.for_netcon_used = true; } -void CodegenHelperVisitor::visit_table_statement(ast::TableStatement* node) { +void CodegenHelperVisitor::visit_table_statement(ast::TableStatement& node) { info.table_count++; table_statement_used = true; } -void CodegenHelperVisitor::visit_program(ast::Program* node) { - psymtab = node->get_symbol_table(); - auto blocks = node->get_blocks(); +void CodegenHelperVisitor::visit_program(ast::Program& node) { + psymtab = node.get_symbol_table(); + auto blocks = node.get_blocks(); for (auto& block: blocks) { info.top_blocks.push_back(block.get()); if (block->is_verbatim()) { info.top_verbatim_blocks.push_back(block.get()); } } - node->visit_children(*this); + node.visit_children(*this); find_range_variables(); find_non_range_variables(); find_ion_variables(); @@ -627,24 +627,24 @@ void CodegenHelperVisitor::visit_program(ast::Program* node) { } -codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program* node) { - node->accept(*this); +codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program& node) { + node.accept(*this); return info; } -void CodegenHelperVisitor::visit_linear_block(ast::LinearBlock* node) { +void CodegenHelperVisitor::visit_linear_block(ast::LinearBlock& node) { info.vectorize = false; } -void CodegenHelperVisitor::visit_non_linear_block(ast::NonLinearBlock* node) { +void CodegenHelperVisitor::visit_non_linear_block(ast::NonLinearBlock& node) { info.vectorize = false; } -void CodegenHelperVisitor::visit_discrete_block(ast::DiscreteBlock* node) { +void CodegenHelperVisitor::visit_discrete_block(ast::DiscreteBlock& node) { info.vectorize = false; } -void CodegenHelperVisitor::visit_partial_block(ast::PartialBlock* node) { +void CodegenHelperVisitor::visit_partial_block(ast::PartialBlock& node) { info.vectorize = false; } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 6dae6e4819..8eec7028ec 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -72,41 +72,41 @@ class CodegenHelperVisitor: public visitor::AstVisitor { void find_table_variables(); void find_range_variables(); void find_non_range_variables(); - void sort_with_mod2c_symbol_order(std::vector& symbols); + void sort_with_mod2c_symbol_order(std::vector& symbols) const; public: CodegenHelperVisitor() = default; /// run visitor and return information for code generation - codegen::CodegenInfo analyze(ast::Program* node); - - void visit_elctrode_current(ast::ElctrodeCurrent* node) override; - void visit_suffix(ast::Suffix* node) override; - void visit_function_call(ast::FunctionCall* node) override; - void visit_binary_expression(ast::BinaryExpression* node) override; - void visit_conductance_hint(ast::ConductanceHint* node) override; - void visit_procedure_block(ast::ProcedureBlock* node) override; - void visit_function_block(ast::FunctionBlock* node) override; - void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock* node) override; - void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock* node) override; - void visit_statement_block(ast::StatementBlock* node) override; - void visit_initial_block(ast::InitialBlock* node) override; - void visit_breakpoint_block(ast::BreakpointBlock* node) override; - void visit_derivative_block(ast::DerivativeBlock* node) override; - void visit_derivimplicit_callback(ast::DerivimplicitCallback* node) override; - void visit_net_receive_block(ast::NetReceiveBlock* node) override; - void visit_bbcore_pointer(ast::BbcorePointer* node) override; - void visit_watch(ast::Watch* node) override; - void visit_watch_statement(ast::WatchStatement* node) override; - void visit_for_netcon(ast::ForNetcon* node) override; - void visit_table_statement(ast::TableStatement* node) override; - void visit_program(ast::Program* node) override; - void visit_factor_def(ast::FactorDef* node) override; - void visit_nrn_state_block(ast::NrnStateBlock* node) override; - void visit_linear_block(ast::LinearBlock* node) override; - void visit_non_linear_block(ast::NonLinearBlock* node) override; - void visit_discrete_block(ast::DiscreteBlock* node) override; - void visit_partial_block(ast::PartialBlock* node) override; + codegen::CodegenInfo analyze(ast::Program& node); + + void visit_elctrode_current(ast::ElctrodeCurrent& node) override; + void visit_suffix(ast::Suffix& node) override; + void visit_function_call(ast::FunctionCall& node) override; + void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_conductance_hint(ast::ConductanceHint& node) override; + void visit_procedure_block(ast::ProcedureBlock& node) override; + void visit_function_block(ast::FunctionBlock& node) override; + void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) override; + void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) override; + void visit_statement_block(ast::StatementBlock& node) override; + void visit_initial_block(ast::InitialBlock& node) override; + void visit_breakpoint_block(ast::BreakpointBlock& node) override; + void visit_derivative_block(ast::DerivativeBlock& node) override; + void visit_derivimplicit_callback(ast::DerivimplicitCallback& node) override; + void visit_net_receive_block(ast::NetReceiveBlock& node) override; + void visit_bbcore_pointer(ast::BbcorePointer& node) override; + void visit_watch(ast::Watch& node) override; + void visit_watch_statement(ast::WatchStatement& node) override; + void visit_for_netcon(ast::ForNetcon& node) override; + void visit_table_statement(ast::TableStatement& node) override; + void visit_program(ast::Program& node) override; + void visit_factor_def(ast::FactorDef& node) override; + void visit_nrn_state_block(ast::NrnStateBlock& node) override; + void visit_linear_block(ast::LinearBlock& node) override; + void visit_non_linear_block(ast::NonLinearBlock& node) override; + void visit_discrete_block(ast::DiscreteBlock& node) override; + void visit_partial_block(ast::PartialBlock& node) override; }; /** @} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index fa360ad97d..972ee05c52 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -15,7 +15,7 @@ namespace codegen { using visitor::AstLookupVisitor; /// if any ion has write variable -bool CodegenInfo::ion_has_write_variable() { +bool CodegenInfo::ion_has_write_variable() const { for (const auto& ion: ions) { if (!ion.writes.empty()) { return true; @@ -26,7 +26,7 @@ bool CodegenInfo::ion_has_write_variable() { /// if given variable is ion write variable -bool CodegenInfo::is_ion_write_variable(const std::string& name) { +bool CodegenInfo::is_ion_write_variable(const std::string& name) const { for (const auto& ion: ions) { for (auto& var: ion.writes) { if (var == name) { @@ -39,7 +39,7 @@ bool CodegenInfo::is_ion_write_variable(const std::string& name) { /// if given variable is ion read variable -bool CodegenInfo::is_ion_read_variable(const std::string& name) { +bool CodegenInfo::is_ion_read_variable(const std::string& name) const { for (const auto& ion: ions) { for (auto& var: ion.reads) { if (var == name) { @@ -52,13 +52,13 @@ bool CodegenInfo::is_ion_read_variable(const std::string& name) { /// if either read or write variable -bool CodegenInfo::is_ion_variable(const std::string& name) { +bool CodegenInfo::is_ion_variable(const std::string& name) const { return is_ion_read_variable(name) || is_ion_write_variable(name); } /// if a current -bool CodegenInfo::is_current(const std::string& name) { +bool CodegenInfo::is_current(const std::string& name) const { for (auto& var: currents) { if (var == name) { return true; @@ -85,7 +85,7 @@ bool CodegenInfo::function_uses_table(std::string& name) const { * - if eigen solver block is used then coreneuron solver is not needed */ -bool CodegenInfo::derivimplicit_coreneuron_solver() { +bool CodegenInfo::derivimplicit_coreneuron_solver() const { return !derivimplicit_callbacks.empty(); } @@ -99,7 +99,7 @@ bool CodegenInfo::nrn_state_has_eigen_solver_block() const { return false; } return !AstLookupVisitor() - .lookup(nrn_state_block, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK) + .lookup(*nrn_state_block, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK) .empty(); } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index a712e80249..b2af5702a1 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -354,31 +354,31 @@ struct CodegenInfo { bool eigen_linear_solver_exist = false; /// if any ion has write variable - bool ion_has_write_variable(); + bool ion_has_write_variable() const; /// if given variable is ion write variable - bool is_ion_write_variable(const std::string& name); + bool is_ion_write_variable(const std::string& name) const; /// if given variable is ion read variable - bool is_ion_read_variable(const std::string& name); + bool is_ion_read_variable(const std::string& name) const; /// if either read or write variable - bool is_ion_variable(const std::string& name); + bool is_ion_variable(const std::string& name) const; /// if given variable is a current - bool is_current(const std::string& name); + bool is_current(const std::string& name) const; /// if watch statements are used - bool is_watch_used() const { + bool is_watch_used() const noexcept { return watch_count > 0; } - bool emit_table_thread() const { + bool emit_table_thread() const noexcept { return (table_count > 0 && vectorize == true); } /// if legacy derivimplicit solver from coreneuron to be used - bool derivimplicit_coreneuron_solver(); + bool derivimplicit_coreneuron_solver() const; bool function_uses_table(std::string& name) const; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 9bd7578156..58993baab2 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -33,15 +33,15 @@ using visitor::RenameVisitor; /* * Rename math functions for ISPC backend */ -void CodegenIspcVisitor::visit_function_call(ast::FunctionCall* node) { +void CodegenIspcVisitor::visit_function_call(ast::FunctionCall& node) { if (!codegen) { return; } - if (node->get_node_name() == "printf") { + if (node.get_node_name() == "printf") { logger->warn("Not emitted in ispc: {}"_format(to_nmodl(node))); return; } - auto fname = node->get_name().get(); + auto& fname = *node.get_name(); RenameVisitor("fabs", "abs").visit_name(fname); RenameVisitor("exp", "vexp").visit_name(fname); CodegenCVisitor::visit_function_call(node); @@ -51,14 +51,14 @@ void CodegenIspcVisitor::visit_function_call(ast::FunctionCall* node) { /* * Rename special global variables */ -void CodegenIspcVisitor::visit_var_name(ast::VarName* node) { +void CodegenIspcVisitor::visit_var_name(ast::VarName& node) { if (!codegen) { return; } RenameVisitor celsius_rename("celsius", "ispc_celsius"); - node->accept(celsius_rename); + node.accept(celsius_rename); RenameVisitor pi_rename("PI", "ISPC_PI"); - node->accept(pi_rename); + node.accept(pi_rename); CodegenCVisitor::visit_var_name(node); } @@ -109,7 +109,7 @@ std::string CodegenIspcVisitor::float_to_string(float value) { } -std::string CodegenIspcVisitor::compute_method_name(BlockType type) { +std::string CodegenIspcVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); } @@ -148,7 +148,7 @@ void CodegenIspcVisitor::print_backend_includes() { } -std::string CodegenIspcVisitor::backend_name() { +std::string CodegenIspcVisitor::backend_name() const { return "ispc (api-compatibility)"; } @@ -279,7 +279,7 @@ void CodegenIspcVisitor::print_backend_namespace_stop() { CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( - std::string arg_qualifier) { + const std::string& arg_qualifier) { auto params = ParamVector(); params.emplace_back(param_type_qualifier(), "{}*"_format(instance_struct()), @@ -292,9 +292,9 @@ CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( } -void CodegenIspcVisitor::print_procedure(ast::ProcedureBlock* node) { +void CodegenIspcVisitor::print_procedure(ast::ProcedureBlock& node) { codegen = true; - auto name = node->get_node_name(); + const auto& name = node.get_node_name(); print_function_or_procedure(node, name); codegen = false; } @@ -332,14 +332,14 @@ void CodegenIspcVisitor::print_compute_functions() { if (!program_symtab->lookup(function->get_node_name()) .get() ->has_all_status(Status::inlined)) { - print_function(function); + print_function(*function); } } for (const auto& procedure: info.procedures) { if (!program_symtab->lookup(procedure->get_node_name()) .get() ->has_all_status(Status::inlined)) { - print_procedure(procedure); + print_procedure(*procedure); } } if (!emit_fallback[BlockType::NetReceive]) { @@ -580,13 +580,14 @@ void CodegenIspcVisitor::print_wrapper_headers_include() { } -void CodegenIspcVisitor::print_wrapper_routine(std::string wraper_function, BlockType type) { - auto args = "NrnThread* nt, Memb_list* ml, int type"; - wraper_function = method_name(wraper_function); +void CodegenIspcVisitor::print_wrapper_routine(const std::string& wrapper_function, + BlockType type) { + static const auto args = "NrnThread* nt, Memb_list* ml, int type"; + const auto function_name = method_name(wrapper_function); auto compute_function = compute_method_name(type); printer->add_newline(2); - printer->start_block("void {}({})"_format(wraper_function, args)); + printer->start_block("void {}({})"_format(function_name, args)); printer->add_line("int nodecount = ml->nodecount;"); // clang-format off printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); @@ -644,22 +645,22 @@ void CodegenIspcVisitor::determine_target() { if (info.initial_node) { emit_fallback[BlockType::Initial] = - !node_lv.lookup(info.initial_node).empty() || - visitor::calls_function(info.initial_node, "net_send") || info.require_wrote_conc; + !node_lv.lookup(*info.initial_node).empty() || + visitor::calls_function(*info.initial_node, "net_send") || info.require_wrote_conc; } else { emit_fallback[BlockType::Initial] = info.net_receive_initial_node || info.require_wrote_conc; } if (info.net_receive_node) { - emit_fallback[BlockType::NetReceive] = !node_lv.lookup(info.net_receive_node).empty() || - visitor::calls_function(info.net_receive_node, + emit_fallback[BlockType::NetReceive] = !node_lv.lookup(*info.net_receive_node).empty() || + visitor::calls_function(*info.net_receive_node, "net_send"); } if (nrn_cur_required()) { if (info.breakpoint_node) { - emit_fallback[BlockType::Equation] = !node_lv.lookup(info.breakpoint_node).empty(); + emit_fallback[BlockType::Equation] = !node_lv.lookup(*info.breakpoint_node).empty(); } else { emit_fallback[BlockType::Equation] = false; } @@ -667,7 +668,7 @@ void CodegenIspcVisitor::determine_target() { if (nrn_state_required()) { if (info.nrn_state_block) { - emit_fallback[BlockType::State] = !node_lv.lookup(info.nrn_state_block).empty(); + emit_fallback[BlockType::State] = !node_lv.lookup(*info.nrn_state_block).empty(); } else { emit_fallback[BlockType::State] = false; } @@ -680,7 +681,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { auto populate_nameset = [&nameset](ast::Block* block) { AstLookupVisitor name_lv(ast::AstNodeType::NAME); if (block) { - auto names = name_lv.lookup(block); + const auto& names = name_lv.lookup(*block); for (const auto& name: names) { nameset.insert(name->get_node_name()); } @@ -692,22 +693,22 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { AstLookupVisitor node_lv(incompatible_node_types); auto target_procedures = std::vector(); - for (auto it = info.procedures.begin(); it != info.procedures.end(); it++) { - auto procname = (*it)->get_name()->get_node_name(); - if (nameset.find(procname) == nameset.end() || !node_lv.lookup(*it).empty()) { - wrapper_procedures.push_back(*it); + for (const auto& procedure: info.procedures) { + const auto& name = procedure->get_name()->get_node_name(); + if (nameset.find(name) == nameset.end() || !node_lv.lookup(*procedure).empty()) { + wrapper_procedures.push_back(procedure); } else { - target_procedures.push_back(*it); + target_procedures.push_back(procedure); } } info.procedures = target_procedures; auto target_functions = std::vector(); - for (auto it = info.functions.begin(); it != info.functions.end(); it++) { - auto procname = (*it)->get_name()->get_node_name(); - if (nameset.find(procname) == nameset.end() || !node_lv.lookup(*it).empty()) { - wrapper_functions.push_back(*it); + for (const auto& function: info.functions) { + const auto& name = function->get_name()->get_node_name(); + if (nameset.find(name) == nameset.end() || !node_lv.lookup(*function).empty()) { + wrapper_functions.push_back(function); } else { - target_functions.push_back(*it); + target_functions.push_back(function); } } info.functions = target_functions; @@ -741,7 +742,7 @@ void CodegenIspcVisitor::codegen_wrapper_routines() { } -void CodegenIspcVisitor::visit_program(ast::Program* node) { +void CodegenIspcVisitor::visit_program(ast::Program& node) { fallback_codegen.setup(node); CodegenCVisitor::visit_program(node); } @@ -791,17 +792,13 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { for (const auto& function: wrapper_functions) { - if (!program_symtab->lookup(function->get_node_name()) - .get() - ->has_all_status(Status::inlined)) { - fallback_codegen.print_function(function); + if (!program_symtab->lookup(function->get_node_name())->has_all_status(Status::inlined)) { + fallback_codegen.print_function(*function); } } for (const auto& procedure: wrapper_procedures) { - if (!program_symtab->lookup(procedure->get_node_name()) - .get() - ->has_all_status(Status::inlined)) { - fallback_codegen.print_procedure(procedure); + if (!program_symtab->lookup(procedure->get_node_name())->has_all_status(Status::inlined)) { + fallback_codegen.print_procedure(*procedure); } } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index fac3d75617..13d171ffe4 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -71,11 +71,11 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// name of the code generation backend - std::string backend_name() override; + std::string backend_name() const override; /// return name of main compute kernels - std::string compute_method_name(BlockType type) override; + std::string compute_method_name(BlockType type) const override; std::string net_receive_buffering_declaration() override; @@ -119,7 +119,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { bool nrn_cur_reduction_loop_required() override; - ParamVector get_global_function_parms(std::string arg_qualifier); + ParamVector get_global_function_parms(const std::string& arg_qualifier); void print_global_function_common_code(BlockType type) override; @@ -158,14 +158,14 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// nmodl procedure definition - void print_procedure(ast::ProcedureBlock* node) override; + void print_procedure(ast::ProcedureBlock& node) override; void print_backend_compute_routine_decl(); /// print wrapper function that calls ispc kernel - void print_wrapper_routine(std::string wraper_function, BlockType type); + void print_wrapper_routine(const std::string& wrapper_function, BlockType type); /// wrapper/caller routines for nrn_state and nrn_cur @@ -215,24 +215,24 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_codegen_wrapper_routines(); public: - CodegenIspcVisitor(std::string mod_file, - std::string output_dir, + CodegenIspcVisitor(const std::string& mod_file, + const std::string& output_dir, LayoutType layout, - std::string float_type) + const std::string& float_type) : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".ispc", ".cpp") , fallback_codegen(mod_file, layout, float_type, wrapper_printer) {} - CodegenIspcVisitor(std::string mod_file, - std::stringstream& stream, + CodegenIspcVisitor(const std::string& mod_file, + std::ostream& stream, LayoutType layout, - std::string float_type) + const std::string& float_type) : CodegenCVisitor(mod_file, stream, layout, float_type) , fallback_codegen(mod_file, layout, float_type, wrapper_printer) {} - void visit_function_call(ast::FunctionCall* node) override; - void visit_var_name(ast::VarName* node) override; - void visit_program(ast::Program* node) override; + void visit_function_call(ast::FunctionCall& node) override; + void visit_var_name(ast::VarName& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp index 6996b1f07b..ded4187128 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -87,7 +87,7 @@ void CodegenOmpVisitor::print_backend_includes() { } -std::string CodegenOmpVisitor::backend_name() { +std::string CodegenOmpVisitor::backend_name() const { return "C-OpenMP (api-compatibility)"; } diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index f75090914f..efc2e39daf 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -30,7 +30,7 @@ namespace codegen { class CodegenOmpVisitor: public CodegenCVisitor { protected: /// name of the code generation backend - std::string backend_name() override; + std::string backend_name() const override; /// common includes : standard c/c++, coreneuron and backend specific diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index ab68e49b48..d6d20a1bf4 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -36,7 +36,7 @@ const std::shared_ptr& Ast::get_statement_block() const { const ModToken *Ast::get_token() const { return nullptr; } -symtab::SymbolTable *Ast::get_symbol_table() { +symtab::SymbolTable *Ast::get_symbol_table() const { throw std::runtime_error("get_symbol_table not implemented"); } diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index a8378df45a..eeac94b0ad 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -248,7 +248,7 @@ struct Ast: public std::enable_shared_from_this { * * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor */ - virtual symtab::SymbolTable* get_symbol_table(); + virtual symtab::SymbolTable* get_symbol_table() const; /** * \brief Return associated statement block for the AST node @@ -536,7 +536,7 @@ struct Ast: public std::enable_shared_from_this { * * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor */ - symtab::SymbolTable* get_symbol_table() override { + symtab::SymbolTable* get_symbol_table() const override { return symtab; } @@ -651,7 +651,7 @@ struct Ast: public std::enable_shared_from_this { * \sa Ast::accept for example. */ {{ virtual(node) }} void accept(visitor::Visitor& v) override { - v.visit_{{ node.class_name | snake_case }}(this); + v.visit_{{ node.class_name | snake_case }}(*this); } /// \} diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index f275349a9a..e3e8380cc4 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -95,7 +95,7 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(const ModToken*, Ast, get_token, ); } - symtab::SymbolTable* get_symbol_table() override { + symtab::SymbolTable* get_symbol_table() const override { PYBIND11_OVERLOAD(symtab::SymbolTable*, Ast, get_symbol_table, ); } diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index 6fc42e4b8e..22eca9547a 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -100,13 +100,13 @@ namespace py = pybind11; {% for node in nodes %} -void PyVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) { +void PyVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) { PYBIND11_OVERLOAD_PURE(void, Visitor, visit_{{ node.class_name|snake_case }}, node); } {% endfor %} {% for node in nodes %} -void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) { +void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) { PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, node); } {% endfor %} @@ -137,7 +137,7 @@ class PyNmodlPrintVisitor: private VisitorOStreamResources, public NmodlPrintVis // clang-format off {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override { + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override { NmodlPrintVisitor::visit_{{ node.class_name|snake_case }}(node); flush(); } @@ -185,9 +185,9 @@ void init_visitor_module(py::module& m) { .def(py::init()) .def("get_nodes", &AstLookupVisitor::get_nodes) .def("clear", &AstLookupVisitor::clear) - .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast*)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast*, ast::AstNodeType)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast*, std::vector&)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast&)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast&, ast::AstNodeType)) &AstLookupVisitor::lookup) + .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast&, const std::vector&)) &AstLookupVisitor::lookup) {% for node in nodes %} .def("visit_{{ node.class_name | snake_case }}", &AstLookupVisitor::visit_{{ node.class_name | snake_case }}) {% if loop.last -%};{% endif %} diff --git a/src/nmodl/language/templates/pybind/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp index 8d3cec4e74..cfc63379fe 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.hpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.hpp @@ -39,7 +39,7 @@ class PyVisitor : public Visitor { using Visitor::Visitor; {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} }; @@ -57,7 +57,7 @@ class PyAstVisitor : public AstVisitor { using AstVisitor::AstVisitor; {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index 6e3e4c6f26..ec964f360e 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -18,8 +18,8 @@ namespace visitor { using namespace ast; {% for node in nodes %} -void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { - node->visit_children(*this); +void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { + node.visit_children(*this); } {% endfor %} diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index 5f11b00a41..b6e6477302 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -42,7 +42,7 @@ namespace visitor { class AstVisitor : public Visitor { public: {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index 364411b630..170bd98375 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -33,8 +33,8 @@ int CheckParentVisitor::check_ast(Ast* node) { void CheckParentVisitor::check_parent(ast::Ast* node) const { if (!parent) { if (is_root_with_null_parent && node->get_parent()) { - std::string node_type = (node == nullptr) ? "nullptr" : node->get_node_type_name(); - throw std::runtime_error("root->parent: {} is set when it should be nullptr"_format(node_type)); + const auto& parent_type = parent->get_node_type_name(); + throw std::runtime_error("root->parent: {} is set when it should be nullptr"_format(parent_type)); } } else { if (parent != node->get_parent()) { @@ -47,16 +47,16 @@ void CheckParentVisitor::check_parent(ast::Ast* node) const { {% for node in nodes %} -void CheckParentVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { +void CheckParentVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { // Set this node as parent. and go down the tree - parent = node; + parent = &node; // visit its children - node->visit_children(*this); + node.visit_children(*this); // I am done with these children, I go up the tree. The parent of this node is the parent - parent = node->get_parent(); + parent = node.get_parent(); } {% endfor %} diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp index 83dfc68bb7..76bf3c2f0e 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -80,7 +80,7 @@ class CheckParentVisitor : public AstVisitor { /** * \brief Go through the tree while checking the parents */ - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} }; @@ -88,4 +88,4 @@ class CheckParentVisitor : public AstVisitor { } // namespace test } // namespace visitor -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 41e999361c..8f3f0e1055 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -18,23 +18,23 @@ namespace visitor { using namespace ast; {% for node in nodes %} -void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { +void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { {% if node.has_children() %} - printer->push_block(node->get_node_type_name()); + printer->push_block(node.get_node_type_name()); if (embed_nmodl) { printer->add_block_property("nmodl", to_nmodl(node)); } - node->visit_children(*this); + node.visit_children(*this); {% if node.is_data_type_node %} {% if node.is_integer_node %} - if(!node->get_macro()) { + if(!node.get_macro()) { std::stringstream ss; - ss << node->eval(); + ss << node.eval(); printer->add_node(ss.str()); } {% else %} std::stringstream ss; - ss << node->eval(); + ss << node.eval(); printer->add_node(ss.str()); {% endif %} {% endif %} diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index b9922c86e4..255b7977fe 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -70,7 +70,7 @@ class JSONVisitor: public AstVisitor { // clang-format off {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} // clang-format on }; diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index 4576e4fcd9..c29c77eb36 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -19,35 +19,35 @@ namespace visitor { using namespace ast; {% for node in nodes %} -void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}* node) { - auto type = node->get_node_type(); - if(std::find(types.begin(), types.end(), type) != types.end()) { - nodes.push_back(node->get_shared_ptr()); +void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { + const auto type = node.get_node_type(); + if (std::find(types.begin(), types.end(), type) != types.end()) { + nodes.push_back(node.get_shared_ptr()); } - node->visit_children(*this); + node.visit_children(*this); } {% endfor %} -std::vector> AstLookupVisitor::lookup(Ast* node, std::vector& _types) { +std::vector> AstLookupVisitor::lookup(Ast& node, const std::vector& _types) { nodes.clear(); types = _types; - node->accept(*this); + node.accept(*this); return nodes; } -std::vector> AstLookupVisitor::lookup(Ast* node, AstNodeType type) { +std::vector> AstLookupVisitor::lookup(Ast& node, AstNodeType type) { nodes.clear(); types.clear(); types.push_back(type); - node->accept(*this); + node.accept(*this); return nodes; } -std::vector> AstLookupVisitor::lookup(Ast* node) { +std::vector> AstLookupVisitor::lookup(Ast& node) { nodes.clear(); - node->accept(*this); + node.accept(*this); return nodes; } diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index f828b38cb1..39ec15bd71 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -48,12 +48,12 @@ class AstLookupVisitor: public Visitor { AstLookupVisitor(const std::vector& types) : types(types) {} - std::vector> lookup(ast::Ast* node); + std::vector> lookup(ast::Ast& node); - std::vector> lookup(ast::Ast* node, ast::AstNodeType type); + std::vector> lookup(ast::Ast& node, ast::AstNodeType type); - std::vector> lookup(ast::Ast* node, - std::vector& types); + std::vector> lookup(ast::Ast& node, + const std::vector& types); const std::vector>& get_nodes() const noexcept { return nodes; @@ -66,7 +66,7 @@ class AstLookupVisitor: public Visitor { // clang-format off {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} // clang-format on }; diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 7d901960c0..580d1c1068 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -44,39 +44,39 @@ using namespace ast; {%- macro add_vector_child(node, child) -%} {% call guard(child.prefix, child.suffix) %} - visit_element(node->get_{{ child.varname }}(),"{{ child.separator }}",{{ is_program(node) }},{{ is_statement(node, child) }}); + visit_element(node.get_{{ child.varname }}(),"{{ child.separator }}",{{ is_program(node) }},{{ is_statement(node, child) }}); {% endcall %} {%- endmacro -%} {%- macro add_child(node, child) -%} {% if child.nmodl_name %} - if(node->get_{{ child.varname }}()->eval()) { + if(node.get_{{ child.varname }}()->eval()) { printer->add_element("{{ child.nmodl_name }}"); } {% elif child.is_vector %} {%- if child.prefix or child.suffix %} - if (!node->get_{{ child.varname }}().empty()) { + if (!node.get_{{ child.varname }}().empty()) { {{ add_vector_child(node, child)|trim }} } {%- else %} {{- add_vector_child(node, child) }} {%- endif %} {% elif node.is_prime_node and child.varname == node_info.ORDER_VAR_NAME %} - auto order = node->get_{{ child.varname }}()->eval(); - auto symbol = std::string(order, '\''); + auto order = node.get_{{ child.varname }}()->eval(); + const std::string symbol(order, '\''); printer->add_element(symbol); {% elif node.class_name == node_info.BINARY_EXPRESSION_NODE and child.varname == node_info.BINARY_OPERATOR_NAME %} - std::string op = node->get_op().eval(); + std::string op = node.get_op().eval(); if(op == "=" || op == "&&" || op == "||" || op == "==") op = " " + op + " "; printer->add_element(op); {% else %} {% call guard(child.prefix, child.suffix) %} {% if child.is_pointer_node %} - node->get_{{ child.varname }}()->accept(*this); + node.get_{{ child.varname }}()->accept(*this); {% else %} - {{ child.class_name }}(node->get_{{ child.varname }}()).accept(*this); + {{ child.class_name }}(node.get_{{ child.varname }}()).accept(*this); {%- endif %} {% endcall %} {%- endif %} @@ -84,8 +84,8 @@ using namespace ast; {%- for node in nodes %} -void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}* node) { - if (is_exclude_type(node->get_node_type())) { +void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}& node) { + if (is_exclude_type(node.get_node_type())) { return; } {{ add_element(node.nmodl_name) -}} @@ -95,17 +95,17 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% endif %} {% if node.is_data_type_node %} {% if node.is_integer_node %} - if(node->get_macro() == nullptr) { - printer->add_element(std::to_string(node->eval())); + if(node.get_macro() == nullptr) { + printer->add_element(std::to_string(node.eval())); } {% elif node.is_float_node or node.is_double_node %} std::stringstream ss; ss << std::setprecision(16); - ss << node->eval(); + ss << node.eval(); printer->add_element(ss.str()); {% else %} std::stringstream ss; - ss << node->eval(); + ss << node.eval(); printer->add_element(ss.str()); {% endif %} {% endif %} @@ -114,7 +114,7 @@ void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name {% if child.is_base_type_node %} {% else %} {% if child.optional or child.is_statement_block_node %} - if(node->get_{{ child.varname }}()) { + if(node.get_{{ child.varname }}()) { {{ add_child(node, child)|trim }} } {% else %} diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 15e4f5ff9b..9366e2bc6d 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -61,7 +61,7 @@ class NmodlPrintVisitor: public Visitor { // clang-format off {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} // clang-format on diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp index 7fae12adee..23b42343d0 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.cpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.cpp @@ -24,18 +24,18 @@ using symtab::syminfo::NmodlType; {% if node.is_symtab_method_required and not node.is_symbol_helper_node %} {% set typename = node.class_name|snake_case %} {% set propname = "NmodlType::" + typename %} -void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}* node) { +void SymtabVisitor::visit_{{ typename }}({{ node.class_name }}& node) { {% if node.is_symbol_var_node %} - setup_symbol(node, {{ propname }}); + setup_symbol(&node, {{ propname }}); {% elif node.is_program_node %} - setup_symbol_table_for_program_block(node); + setup_symbol_table_for_program_block(&node); {% elif node.is_global_block_node %} - setup_symbol_table_for_global_block(node); + setup_symbol_table_for_global_block(&node); {% elif node.is_symbol_block_node %} - add_model_symbol_with_property(node, {{ propname }}); - setup_symbol_table_for_scoped_block(node, node->get_node_name()); + add_model_symbol_with_property(&node, {{ propname }}); + setup_symbol_table_for_scoped_block(&node, node.get_node_name()); {% else %} - setup_symbol_table_for_scoped_block(node, node->get_node_type_name()); + setup_symbol_table_for_scoped_block(&node, node.get_node_type_name()); {% endif %} } diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index ab5fadacb9..dbab9b41da 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -52,7 +52,7 @@ class SymtabVisitor: public AstVisitor { : printer(new printer::JSONPrinter(os)) , update(update) {} - SymtabVisitor(std::string filename, bool update = false) + SymtabVisitor(const std::string& filename, bool update = false) : printer(new printer::JSONPrinter(filename)) , update(update) {} @@ -71,7 +71,7 @@ class SymtabVisitor: public AstVisitor { // clang-format off {% for node in nodes %} {% if node.is_symtab_method_required %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) override; + void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endif %} {% endfor %} // clang-format on diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index e5dfd92cde..c94f0f85ea 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -42,7 +42,7 @@ class Visitor { {% for node in nodes %} /// visit node of type ast::{{ node.class_name }} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}* node) = 0; + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) = 0; {% endfor %} }; diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 0b3a5f7071..1587166c20 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -264,7 +264,7 @@ int main(int argc, const char* argv[]) { } /// write ast to nmodl - auto ast_to_nmodl = [nmodl_ast](ast::Program* ast, const std::string& filepath) { + const auto ast_to_nmodl = [nmodl_ast](ast::Program& ast, const std::string& filepath) { if (nmodl_ast) { NmodlPrintVisitor(filepath).visit_program(ast); logger->info("AST to NMODL transformation written to {}", filepath); @@ -274,10 +274,10 @@ int main(int argc, const char* argv[]) { for (const auto& file: mod_files) { logger->info("Processing {}", file); - auto modfile = utils::remove_extension(utils::base_name(file)); + const auto modfile = utils::remove_extension(utils::base_name(file)); /// create file path for nmodl file - auto filepath = [scratch_dir, modfile](std::string suffix) { + auto filepath = [scratch_dir, modfile](const std::string& suffix) { static int count = 0; return "{}/{}.{}.{}.mod"_format(scratch_dir, modfile, std::to_string(count++), suffix); }; @@ -286,67 +286,64 @@ int main(int argc, const char* argv[]) { NmodlDriver driver; /// parse mod file and construct ast - auto ast = driver.parse_file(file); + const auto& ast = driver.parse_file(file); /// whether to update existing symbol table or create new /// one whenever we run symtab visitor. bool update_symtab = false; /// just visit the ast - AstVisitor().visit_program(ast.get()); + AstVisitor().visit_program(*ast); /// construct symbol table { logger->info("Running symtab visitor"); - SymtabVisitor(update_symtab).visit_program(ast.get()); + SymtabVisitor(update_symtab).visit_program(*ast); } { // Compatibility Checking logger->info("Running code compatibility checker"); // run perfvisitor to update read/wrie counts - PerfVisitor().visit_program(ast.get()); + PerfVisitor().visit_program(*ast); // If there is an incompatible construct and code generation is not forced exit NMODL - if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(ast.get()) && - !force_codegen) { + if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast) && !force_codegen) { return 1; } } if (show_symtab) { logger->info("Printing symbol table"); - std::stringstream stream; auto symtab = ast->get_model_symbol_table(); - symtab->print(stream); - std::cout << stream.str(); + symtab->print(std::cout); } - ast_to_nmodl(ast.get(), filepath("ast")); + ast_to_nmodl(*ast, filepath("ast")); if (json_ast) { logger->info("Writing AST into {}", file); auto file = scratch_dir + "/" + modfile + ".ast.json"; - JSONVisitor(file).visit_program(ast.get()); + JSONVisitor(file).visit_program(*ast); } if (verbatim_rename) { logger->info("Running verbatim rename visitor"); - VerbatimVarRenameVisitor().visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("verbatim_rename")); + VerbatimVarRenameVisitor().visit_program(*ast); + ast_to_nmodl(*ast, filepath("verbatim_rename")); } if (nmodl_const_folding) { logger->info("Running nmodl constant folding visitor"); - ConstantFolderVisitor().visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("constfold")); + ConstantFolderVisitor().visit_program(*ast); + ast_to_nmodl(*ast, filepath("constfold")); } if (nmodl_unroll) { logger->info("Running nmodl loop unroll visitor"); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("unroll")); - SymtabVisitor(update_symtab).visit_program(ast.get()); + LoopUnrollVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); + ast_to_nmodl(*ast, filepath("unroll")); + SymtabVisitor(update_symtab).visit_program(*ast); } /// note that we can not symtab visitor in update mode as we @@ -355,10 +352,10 @@ int main(int argc, const char* argv[]) { { logger->info("Running KINETIC block visitor"); auto kineticBlockVisitor = KineticBlockVisitor(); - kineticBlockVisitor.visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); + kineticBlockVisitor.visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); const auto filename = filepath("kinetic"); - ast_to_nmodl(ast.get(), filename); + ast_to_nmodl(*ast, filename); if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) { logger->warn( "{} presents non-standard CONSERVE statements in DERIVATIVE blocks. Use it only for debugging/developing"_format( @@ -368,15 +365,15 @@ int main(int argc, const char* argv[]) { { logger->info("Running STEADYSTATE visitor"); - SteadystateVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("steadystate")); + SteadystateVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("steadystate")); } /// Parsing units fron "nrnunits.lib" and mod files { logger->info("Parsing Units"); - UnitsVisitor(units_dir).visit_program(ast.get()); + UnitsVisitor(units_dir).visit_program(*ast); } /// once we start modifying (especially removing) older constructs @@ -386,62 +383,62 @@ int main(int argc, const char* argv[]) { if (nmodl_inline) { logger->info("Running nmodl inline visitor"); - InlineVisitor().visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("inline")); + InlineVisitor().visit_program(*ast); + ast_to_nmodl(*ast, filepath("inline")); } if (local_rename) { logger->info("Running local variable rename visitor"); - LocalVarRenameVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("local_rename")); + LocalVarRenameVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("local_rename")); } if (nmodl_localize) { // localize pass must follow rename pass to avoid conflict logger->info("Running localize visitor"); - LocalizeVisitor(localize_verbatim).visit_program(ast.get()); - LocalVarRenameVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("localize")); + LocalizeVisitor(localize_verbatim).visit_program(*ast); + LocalVarRenameVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("localize")); } if (sympy_conductance) { logger->info("Running sympy conductance visitor"); - SympyConductanceVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("sympy_conductance")); + SympyConductanceVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("sympy_conductance")); } if (sympy_analytic) { logger->info("Running sympy solve visitor"); - SympySolverVisitor(sympy_pade, sympy_cse).visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("sympy_solve")); + SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("sympy_solve")); } { logger->info("Running cnexp visitor"); - NeuronSolveVisitor().visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("cnexp")); + NeuronSolveVisitor().visit_program(*ast); + ast_to_nmodl(*ast, filepath("cnexp")); } { - SolveBlockVisitor().visit_program(ast.get()); - SymtabVisitor(update_symtab).visit_program(ast.get()); - ast_to_nmodl(ast.get(), filepath("solveblock")); + SolveBlockVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("solveblock")); } if (json_perfstat) { auto file = scratch_dir + "/" + modfile + ".perf.json"; logger->info("Writing performance statistics to {}", file); - PerfVisitor(file).visit_program(ast.get()); + PerfVisitor(file).visit_program(*ast); } { // make sure to run perf visitor because code generator // looks for read/write counts const/non-const declaration - PerfVisitor().visit_program(ast.get()); + PerfVisitor().visit_program(*ast); } { @@ -451,31 +448,31 @@ int main(int argc, const char* argv[]) { if (ispc_backend) { logger->info("Running ISPC backend code generator"); CodegenIspcVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); + visitor.visit_program(*ast); } else if (oacc_backend) { logger->info("Running OpenACC backend code generator"); CodegenAccVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); + visitor.visit_program(*ast); } else if (omp_backend) { logger->info("Running OpenMP backend code generator"); CodegenOmpVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); + visitor.visit_program(*ast); } else if (c_backend) { logger->info("Running C backend code generator"); CodegenCVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); + visitor.visit_program(*ast); } if (cuda_backend) { logger->info("Running CUDA backend code generator"); CodegenCudaVisitor visitor(modfile, output_dir, mem_layout, data_type); - visitor.visit_program(ast.get()); + visitor.visit_program(*ast); } } } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 33b1efd597..f21a050cf8 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -49,7 +49,7 @@ class CodePrinter { CodePrinter() : result(new std::ostream(std::cout.rdbuf())) {} - CodePrinter(std::stringstream& stream) + CodePrinter(std::ostream& stream) : result(new std::ostream(stream.rdbuf())) {} CodePrinter(const std::string& filename); diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 7bda1dbb16..9e496c5113 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -160,11 +160,13 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { nmodl::docstring::driver_parse_stream) .def("get_ast", &nmodl::PyNmodlDriver::get_ast, nmodl::docstring::driver_ast); - m_nmodl.def("to_nmodl", - nmodl::to_nmodl, - "node"_a, - "exclude_types"_a = std::set(), - nmodl::docstring::to_nmodl); + m_nmodl.def( + "to_nmodl", + static_cast&)>( + nmodl::to_nmodl), + "node"_a, + "exclude_types"_a = std::set(), + nmodl::docstring::to_nmodl); m_nmodl.def("to_json", nmodl::to_json, "node"_a, diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 53c7411c0c..4e0e7ce2f4 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -497,7 +497,7 @@ std::string SymbolTable::title() const { } -void SymbolTable::print(std::stringstream& ss, int level) { +void SymbolTable::print(std::ostream& ss, int level) const { table.print(ss, title(), level); /// when current symbol table is empty, the children diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 526181e48f..0029da120d 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -192,7 +192,7 @@ class SymbolTable { /// insert new symbol table as one of the children block void insert_table(const std::string& name, std::shared_ptr table); - void print(std::stringstream& ss, int level); + void print(std::ostream& ss, int level) const; std::string title() const; @@ -270,8 +270,8 @@ class ModelSymbolTable { void set_mode(bool update_mode); /// pretty print - void print(std::stringstream& ss) { - symtab->print(ss, 0); + void print(std::ostream& ostr) const { + symtab->print(ostr, 0); } }; diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 4afb925f21..36cc8ea253 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -15,7 +15,7 @@ namespace visitor { /// check if given expression is a number /// note that the DEFINE node is already expanded to integer -static bool is_number(const std::shared_ptr& node) { +static inline bool is_number(const std::shared_ptr& node) { return node->is_integer() || node->is_double() || node->is_float(); } @@ -24,18 +24,16 @@ static bool is_number(const std::shared_ptr& node) { static double get_value(const std::shared_ptr& node) { if (node->is_integer()) { return std::dynamic_pointer_cast(node)->eval(); - } - if (node->is_float()) { + } else if (node->is_float()) { return std::dynamic_pointer_cast(node)->eval(); - } - if (node->is_double()) { + } else if (node->is_double()) { return std::dynamic_pointer_cast(node)->eval(); } throw std::runtime_error("Invalid type passed to is_number()"); } /// operators that currently implemented -static bool supported_operator(ast::BinaryOp op) { +static inline bool supported_operator(ast::BinaryOp op) { return op == ast::BOP_ADDITION || op == ast::BOP_SUBTRACTION || op == ast::BOP_MULTIPLICATION || op == ast::BOP_DIVISION; } @@ -75,12 +73,12 @@ static double compute(double lhs, ast::BinaryOp op, double rhs) { * * parenthesis_exp => binary_expression => ... */ -void ConstantFolderVisitor::visit_paren_expression(ast::ParenExpression* node) { - node->visit_children(*this); - auto expr = node->get_expression(); +void ConstantFolderVisitor::visit_paren_expression(ast::ParenExpression& node) { + node.visit_children(*this); + auto expr = node.get_expression(); if (expr->is_wrapped_expression()) { auto e = std::dynamic_pointer_cast(expr); - node->set_expression(e->get_expression()); + node.set_expression(e->get_expression()); } } @@ -104,11 +102,12 @@ void ConstantFolderVisitor::visit_paren_expression(ast::ParenExpression* node) { * * } */ -void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* node) { - node->visit_children(*this); +void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression& node) { + node.visit_children(*this); + node.visit_children(*this); /// first expression which is wrapped - auto expr = node->get_expression(); + auto expr = node.get_expression(); /// if wrapped expression is parentheses bool is_parentheses = false; @@ -127,7 +126,7 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* nod /// wrapped expression might be parenthesis expression like (2) /// which we can simplify to 2 to help next evaluations if (is_parentheses) { - node->set_expression(std::move(expr)); + node.set_expression(std::move(expr)); } return; } @@ -158,21 +157,21 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression* nod return; } - std::string nmodl_before = to_nmodl(binary_expr.get()); + std::string nmodl_before = to_nmodl(binary_expr); /// compute the value of expression auto value = compute(get_value(lhs), op, get_value(rhs)); /// if both operands are not integers or floats, result is double if (lhs->is_integer() && rhs->is_integer()) { - node->set_expression(std::make_shared(int(value), nullptr)); + node.set_expression(std::make_shared(int(value), nullptr)); } else if (lhs->is_double() || rhs->is_double()) { - node->set_expression(std::make_shared(value)); + node.set_expression(std::make_shared(value)); } else { - node->set_expression(std::make_shared(value)); + node.set_expression(std::make_shared(value)); } - std::string nmodl_after = to_nmodl(node->get_expression().get()); + std::string nmodl_after = to_nmodl(node.get_expression()); logger->debug("ConstantFolderVisitor : expression {} folded to {}", nmodl_before, nmodl_after); } diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index f8f3a69edc..0404e54a6d 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -55,8 +55,8 @@ namespace visitor { class ConstantFolderVisitor: public AstVisitor { public: ConstantFolderVisitor() = default; - void visit_wrapped_expression(ast::WrappedExpression* node) override; - void visit_paren_expression(ast::ParenExpression* node) override; + void visit_wrapped_expression(ast::WrappedExpression& node) override; + void visit_paren_expression(ast::ParenExpression& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index b5cbb98adf..bdda17fb9f 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -192,24 +192,24 @@ void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node* node) { * there is no inlining happened. In this case we mark the call as * unsupported. */ -void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall* node) { - std::string function_name = node->get_node_name(); - auto symbol = global_symtab->lookup_in_scope(function_name); +void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall& node) { + const auto& function_name = node.get_node_name(); + const auto& symbol = global_symtab->lookup_in_scope(function_name); if (symbol == nullptr || symbol->is_external_variable()) { - node->visit_children(*this); + node.visit_children(*this); } else { - visit_unsupported_node(node); + visit_unsupported_node(&node); } } -void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock* node) { - auto symtab = node->get_symbol_table(); +void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock& node) { + const auto& symtab = node.get_symbol_table(); if (symtab != nullptr) { current_symtab = symtab; } symtab_stack.push(current_symtab); - node->visit_children(*this); + node.visit_children(*this); symtab_stack.pop(); current_symtab = symtab_stack.top(); } @@ -217,16 +217,16 @@ void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock* node) { /** Nmodl grammar doesn't allow assignment operator on rhs (e.g. a = b + (b=c) * and hence not necessary to keep track of assignment operator using stack. */ -void DefUseAnalyzeVisitor::visit_binary_expression(ast::BinaryExpression* node) { - node->get_rhs()->visit_children(*this); - if (node->get_op().get_value() == ast::BOP_ASSIGN) { +void DefUseAnalyzeVisitor::visit_binary_expression(ast::BinaryExpression& node) { + node.get_rhs()->visit_children(*this); + if (node.get_op().get_value() == ast::BOP_ASSIGN) { visiting_lhs = true; } - node->get_lhs()->visit_children(*this); + node.get_lhs()->visit_children(*this); visiting_lhs = false; } -void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement* node) { +void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement& node) { /// store previous chain auto previous_chain = current_chain; @@ -237,21 +237,21 @@ void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement* node) { /// visiting if sub-block auto last_chain = current_chain; start_new_chain(DUState::IF); - node->get_condition()->accept(*this); - auto block = node->get_statement_block(); + node.get_condition()->accept(*this); + const auto& block = node.get_statement_block(); if (block) { block->accept(*this); } current_chain = last_chain; /// visiting else if sub-blocks - for (const auto& item: node->get_elseifs()) { + for (const auto& item: node.get_elseifs()) { visit_with_new_chain(item.get(), DUState::ELSEIF); } /// visiting else sub-block - if (node->get_elses()) { - visit_with_new_chain(node->get_elses().get(), DUState::ELSE); + if (node.get_elses()) { + visit_with_new_chain(node.get_elses().get(), DUState::ELSE); } /// restore to previous chain @@ -264,7 +264,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement* node) { * \todo One simple way would be to look for p_name in the string * of verbatim block to find the variable usage. */ -void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim* node) { +void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim& node) { if (!ignore_verbatim) { current_chain->push_back(DUInstance(DUState::U)); } diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 1b809e6397..1337488956 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -84,7 +84,7 @@ class DUInstance { /// usage of variable in case of if like statements std::vector children; - DUInstance(DUState state) + explicit DUInstance(DUState state) : state(state) {} /// analyze all children and return "effective" usage @@ -113,8 +113,8 @@ class DUChain { std::vector chain; DUChain() = default; - DUChain(std::string name) - : name(name) {} + explicit DUChain(std::string name) + : name(std::move(name)) {} /// return "effective" usage of a variable DUState eval(); @@ -228,62 +228,62 @@ class DefUseAnalyzeVisitor: public AstVisitor { : global_symtab(symtab) , ignore_verbatim(ignore_verbatim) {} - virtual void visit_binary_expression(ast::BinaryExpression* node) override; - virtual void visit_if_statement(ast::IfStatement* node) override; - virtual void visit_function_call(ast::FunctionCall* node) override; - virtual void visit_statement_block(ast::StatementBlock* node) override; - virtual void visit_verbatim(ast::Verbatim* node) override; + void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_if_statement(ast::IfStatement& node) override; + void visit_function_call(ast::FunctionCall& node) override; + void visit_statement_block(ast::StatementBlock& node) override; + void visit_verbatim(ast::Verbatim& node) override; /// unsupported statements : we aren't sure how to handle this "yet" and /// hence variables used in any of the below statements are handled separately - virtual void visit_reaction_statement(ast::ReactionStatement* node) override { - visit_unsupported_node(node); + void visit_reaction_statement(ast::ReactionStatement& node) override { + visit_unsupported_node(&node); } - virtual void visit_non_lin_equation(ast::NonLinEquation* node) override { - visit_unsupported_node(node); + void visit_non_lin_equation(ast::NonLinEquation& node) override { + visit_unsupported_node(&node); } - virtual void visit_lin_equation(ast::LinEquation* node) override { - visit_unsupported_node(node); + void visit_lin_equation(ast::LinEquation& node) override { + visit_unsupported_node(&node); } - virtual void visit_partial_boundary(ast::PartialBoundary* node) override { - visit_unsupported_node(node); + void visit_partial_boundary(ast::PartialBoundary& node) override { + visit_unsupported_node(&node); } - virtual void visit_from_statement(ast::FromStatement* node) override { - visit_unsupported_node(node); + void visit_from_statement(ast::FromStatement& node) override { + visit_unsupported_node(&node); } - virtual void visit_conserve(ast::Conserve* node) override { - visit_unsupported_node(node); + void visit_conserve(ast::Conserve& node) override { + visit_unsupported_node(&node); } - virtual void visit_var_name(ast::VarName* node) override { - std::string variable = to_nmodl(node); + void visit_var_name(ast::VarName& node) override { + const std::string& variable = to_nmodl(node); process_variable(variable); } - virtual void visit_name(ast::Name* node) override { - std::string variable = to_nmodl(node); + void visit_name(ast::Name& node) override { + const std::string& variable = to_nmodl(node); process_variable(variable); } - virtual void visit_indexed_name(ast::IndexedName* node) override { - std::string name = node->get_node_name(); - auto length = node->get_length(); + void visit_indexed_name(ast::IndexedName& node) override { + const auto& name = node.get_node_name(); + const auto& length = node.get_length(); /// index should be an integer (e.g. after constant folding) /// if this is not the case and then we can't determine exact /// def-use chain if (!length->is_integer()) { /// check if variable name without index part is same - auto variable_name_prefix = variable_name.substr(0, variable_name.find("[")); + auto variable_name_prefix = variable_name.substr(0, variable_name.find('[')); if (name == variable_name_prefix) { update_defuse_chain(variable_name_prefix); - std::string text = to_nmodl(node); + const std::string& text = to_nmodl(node); nmodl::logger->info("index used to access variable is not known : {} ", text); } return; @@ -294,11 +294,11 @@ class DefUseAnalyzeVisitor: public AstVisitor { /// statements / nodes that should not be used for def-use chain analysis - virtual void visit_conductance_hint(ast::ConductanceHint* /*node*/) override {} + void visit_conductance_hint(ast::ConductanceHint& /*node*/) override {} - virtual void visit_local_list_statement(ast::LocalListStatement* /*node*/) override {} + void visit_local_list_statement(ast::LocalListStatement& /*node*/) override {} - virtual void visit_argument(ast::Argument* /*node*/) override {} + void visit_argument(ast::Argument& /*node*/) override {} /// compute def-use chain for a variable within the node DUChain analyze(ast::Ast* node, const std::string& name); diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 413ea2161d..a0b86e1e30 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -39,12 +39,12 @@ bool InlineVisitor::can_inline_block(StatementBlock* block) { return to_inline; } -void InlineVisitor::add_return_variable(StatementBlock* block, std::string& varname) { +void InlineVisitor::add_return_variable(StatementBlock& block, std::string& varname) { auto lhs = new Name(new String(varname)); auto rhs = new Integer(0, nullptr); auto expression = new BinaryExpression(lhs, BinaryOperator(BOP_ASSIGN), rhs); auto statement = std::make_shared(expression); - block->emplace_back_statement(statement); + block.emplace_back_statement(statement); } /** We can replace statement if the entire statement itself is a function call. @@ -72,7 +72,7 @@ bool InlineVisitor::can_replace_statement(const std::shared_ptr& stat return to_replace; } -void InlineVisitor::inline_arguments(StatementBlock* inlined_block, +void InlineVisitor::inline_arguments(StatementBlock& inlined_block, const ArgumentVector& callee_parameters, const ExpressionVector& caller_expressions) { /// nothing to inline if no arguments for function call @@ -81,7 +81,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, } size_t counter = 0; - const auto& statements = inlined_block->get_statements(); + const auto& statements = inlined_block.get_statements(); for (const auto& argument: callee_parameters) { auto name = argument->get_name()->clone(); @@ -94,7 +94,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, /// variables in cloned block needs to be renamed RenameVisitor visitor(old_name, new_name); - inlined_block->visit_children(visitor); + inlined_block.visit_children(visitor); auto lhs = new VarName(name->clone(), nullptr, nullptr); auto rhs = caller_expressions.at(counter)->clone(); @@ -102,7 +102,7 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, /// create assignment statement and insert after the local variables auto expression = new BinaryExpression(lhs, BinaryOperator(ast::BOP_ASSIGN), rhs); auto statement = std::make_shared(expression); - inlined_block->insert_statement(statements.begin() + counter + 1, statement); + inlined_block.insert_statement(statements.begin() + counter + 1, statement); counter++; } } @@ -117,11 +117,11 @@ void InlineVisitor::inline_arguments(StatementBlock* inlined_block, bool InlineVisitor::inline_function_call(ast::Block* callee, ast::FunctionCall* node, ast::StatementBlock* caller) { - std::string function_name = callee->get_node_name(); + const auto& function_name = callee->get_node_name(); /// do nothing if we can't inline given procedure/function if (!can_inline_block(callee->get_statement_block().get())) { - std::cerr << "Can not inline function call to " + function_name << std::endl; + std::cerr << "Can not inline function call to " + function_name << '\n'; return false; } @@ -129,9 +129,9 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, /// because in case of procedure inlining they can conflict with /// global variables used in procedure being inlined LocalVarRenameVisitor v; - v.visit_statement_block(caller); + v.visit_statement_block(*caller); - auto caller_arguments = node->get_arguments(); + const auto& caller_arguments = node->get_arguments(); std::string new_varname = get_new_name(function_name, "in", inlined_variables); /// check if caller statement could be replaced @@ -146,7 +146,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, name->set_token(tok); const ast::StatementVector& statements = caller->get_statements(); - auto local_list_statement = get_local_list_statement(caller); + auto local_list_statement = get_local_list_statement(*caller); /// each block should already have local statement if (local_list_statement == nullptr) { throw std::logic_error("got local statement as nullptr"); @@ -166,11 +166,11 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, inlined_block->set_symbol_table(nullptr); /// each argument is added as new assignment statement - inline_arguments(inlined_block, callee->get_parameters(), caller_arguments); + inline_arguments(*inlined_block, callee->get_parameters(), caller_arguments); /// to return value from procedure we have to add new variable if (callee->is_procedure_block() && to_replace == false) { - add_return_variable(inlined_block, new_varname); + add_return_variable(*inlined_block, new_varname); } auto statement = new ast::ExpressionStatement(inlined_block); @@ -188,11 +188,11 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, } -void InlineVisitor::visit_function_call(FunctionCall* node) { +void InlineVisitor::visit_function_call(FunctionCall& node) { /// argument can be function call itself - node->visit_children(*this); + node.visit_children(*this); - std::string function_name = node->get_name()->get_node_name(); + const auto& function_name = node.get_name()->get_node_name(); auto symbol = program_symtab->lookup_in_scope(function_name); /// nothing to do if called function is not defined or it's external @@ -212,10 +212,10 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { if (function_definition->is_procedure_block()) { auto proc = (ProcedureBlock*) function_definition; - inlined = inline_function_call(proc, node, caller_block); + inlined = inline_function_call(proc, &node, caller_block); } else if (function_definition->is_function_block()) { auto func = (FunctionBlock*) function_definition; - inlined = inline_function_call(func, node, caller_block); + inlined = inline_function_call(func, &node, caller_block); } if (inlined) { @@ -223,14 +223,14 @@ void InlineVisitor::visit_function_call(FunctionCall* node) { } } -void InlineVisitor::visit_statement_block(StatementBlock* node) { +void InlineVisitor::visit_statement_block(StatementBlock& node) { /** While inlining we have to add new statements before call site. * In order to return result we also have to add new local variable * to the caller block. Hence we have to keep track of caller block, * caller block's symbol table and caller statement. */ - caller_block = node; - statementblock_stack.push(node); + caller_block = &node; + statementblock_stack.push(&node); /** Add empty local statement at the begining of block if already doesn't exist. * Why? When we iterate over statements and inline function call, we have to add @@ -240,7 +240,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { */ add_local_statement(node); - const auto& statements = node->get_statements(); + const auto& statements = node.get_statements(); for (const auto& statement: statements) { caller_statement = statement; @@ -252,7 +252,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { /// each block should already have local statement auto local_list_statement = get_local_list_statement(node); if (local_list_statement->get_variables().empty()) { - node->erase_statement(statements.begin()); + node.erase_statement(statements.begin()); } /// check if any statement is candidate for replacement due to inlining @@ -260,7 +260,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { for (auto it = statements.begin(); it < statements.end(); ++it) { const auto& statement = *it; if (replaced_statements.find(statement) != replaced_statements.end()) { - node->reset_statement(it, replaced_statements[statement]); + node.reset_statement(it, replaced_statements[statement]); } } @@ -268,7 +268,7 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { for (auto& element: inlined_statements) { auto it = std::find(statements.begin(), statements.end(), element.first); if (it != statements.end()) { - node->insert_statement(it, element.second.begin(), element.second.end()); + node.insert_statement(it, element.second.begin(), element.second.end()); element.second.clear(); } } @@ -294,24 +294,24 @@ void InlineVisitor::visit_statement_block(StatementBlock* node) { * If a function call is replaced then the wrapped expression is * also replaced with new variable node from the inlining result. */ -void InlineVisitor::visit_wrapped_expression(WrappedExpression* node) { - node->visit_children(*this); - auto e = node->get_expression(); +void InlineVisitor::visit_wrapped_expression(WrappedExpression& node) { + node.visit_children(*this); + const auto& e = node.get_expression(); if (e->is_function_call()) { - auto expression = static_cast(e.get()); + auto expression = dynamic_cast(e.get()); if (replaced_fun_calls.find(expression) != replaced_fun_calls.end()) { auto var = replaced_fun_calls[expression]; - node->set_expression(std::make_shared(new String(var))); + node.set_expression(std::make_shared(new String(var))); } } } -void InlineVisitor::visit_program(Program* node) { - program_symtab = node->get_symbol_table(); +void InlineVisitor::visit_program(Program& node) { + program_symtab = node.get_symbol_table(); if (program_symtab == nullptr) { throw std::runtime_error("Program node doesn't have symbol table"); } - node->visit_children(*this); + node.visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 1d2f06f55e..ebeec6c30a 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -176,24 +176,24 @@ class InlineVisitor: public AstVisitor { ast::StatementBlock* caller); /// add assignement statements into given statement block to inline arguments - void inline_arguments(ast::StatementBlock* inlined_block, + void inline_arguments(ast::StatementBlock& inlined_block, const ast::ArgumentVector& callee_parameters, const ast::ExpressionVector& caller_expressions); /// add assignment statement at end of block (to use as a return statement /// in case of procedure blocks) - void add_return_variable(ast::StatementBlock* block, std::string& varname); + void add_return_variable(ast::StatementBlock& block, std::string& varname); public: InlineVisitor() = default; - virtual void visit_function_call(ast::FunctionCall* node) override; + void visit_function_call(ast::FunctionCall& node) override; - virtual void visit_statement_block(ast::StatementBlock* node) override; + void visit_statement_block(ast::StatementBlock& node) override; - virtual void visit_wrapped_expression(ast::WrappedExpression* node) override; + void visit_wrapped_expression(ast::WrappedExpression& node) override; - virtual void visit_program(ast::Program* node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index e51d3bc36c..4483fb5d4b 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -101,7 +101,7 @@ std::shared_ptr create_expr(const std::string& str_expr) { return std::dynamic_pointer_cast(expr)->get_rhs(); } -void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { +void KineticBlockVisitor::visit_conserve(ast::Conserve& node) { ++conserve_statement_count; // rewrite CONSERVE statement in form x = ... // where x was the last state var on LHS, and whose ODE should later be replaced with this @@ -116,10 +116,10 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { in_conserve_statement = true; // construct equation to replace ODE in conserve_equation_str - node->visit_children(*this); + node.visit_children(*this); in_conserve_statement = false; - conserve_equation_str = to_nmodl(node->get_expr().get()) + conserve_equation_str; + conserve_equation_str = to_nmodl(node.get_expr()) + conserve_equation_str; if (!conserve_equation_factor.empty()) { // divide by compartment factor of conserve_equation_statevar conserve_equation_str = "(" + conserve_equation_str + ")/(" + conserve_equation_factor + @@ -136,22 +136,22 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve* node) { conserve_equation_str); auto expr = std::dynamic_pointer_cast(statement); // set react (lhs) of CONSERVE to the state variable whose ODE should be replaced - node->set_react(std::move(expr->get_react())); + node.set_react(std::move(expr->get_react())); // set expr (rhs) of CONSERVE to the equation that should replace the ODE - node->set_expr(std::move(expr->get_expr())); + node.set_expr(std::move(expr->get_expr())); logger->debug("KineticBlockVisitor :: --> {}", to_nmodl(node)); } -void KineticBlockVisitor::visit_compartment(ast::Compartment* node) { +void KineticBlockVisitor::visit_compartment(ast::Compartment& node) { // COMPARTMENT block has an expression, and a list of state vars it applies to. // For each state var, the rhs of the differential eq should be divided by the expression. // Here we store the expressions in the compartment_factors vector - auto expr = node->get_expression(); - std::string expression = to_nmodl(expr.get()); + auto expr = node.get_expression(); + std::string expression = to_nmodl(expr); logger->debug("KineticBlockVisitor :: COMPARTMENT expr: {}", expression); - for (const auto name_ptr: node->get_names()) { - std::string var_name = name_ptr->get_node_name(); + for (const auto& name_ptr: node.get_names()) { + const auto& var_name = name_ptr->get_node_name(); const auto it = state_var_index.find(var_name); if (it != state_var_index.cend()) { int var_index = it->second; @@ -165,11 +165,11 @@ void KineticBlockVisitor::visit_compartment(ast::Compartment* node) { } // add COMPARTMENT state to list of statements to remove // since we don't want this statement to be present in the final DERIVATIVE block - statements_to_remove.insert(node); + statements_to_remove.insert(&node); } -void KineticBlockVisitor::visit_reaction_operator(ast::ReactionOperator* node) { - auto reaction_op = node->get_value(); +void KineticBlockVisitor::visit_reaction_operator(ast::ReactionOperator& node) { + auto reaction_op = node.get_value(); if (reaction_op == ast::ReactionOp::LTMINUSGT) { // <-> // reversible reaction @@ -178,12 +178,12 @@ void KineticBlockVisitor::visit_reaction_operator(ast::ReactionOperator* node) { } } -void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName* node) { +void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName& node) { // ReactVarName node contains a VarName and an Integer // the VarName is the state variable which we convert to an index // the Integer is the value to be added to the stoichiometric matrix at this index - auto varname = to_nmodl(node->get_name().get()); - int count = node->get_value() ? node->get_value()->eval() : 1; + auto varname = to_nmodl(node.get_name()); + int count = node.get_value() ? node.get_value()->eval() : 1; if (in_reaction_statement) { process_reac_var(varname, count); } else if (in_conserve_statement) { @@ -198,10 +198,10 @@ void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName* node) { } } -void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) { - statements_to_remove.insert(node); +void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement& node) { + statements_to_remove.insert(&node); - auto reaction_op = node->get_op().get_value(); + auto reaction_op = node.get_op().get_value(); // special case for << statements if (reaction_op == ast::ReactionOp::LTLT) { logger->debug("KineticBlockVisitor :: '<<' reaction statement: {}", to_nmodl(node)); @@ -211,7 +211,7 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) // So if x is a state var, then // ~ x << (a*b) // translates to the ODE contribution x' += a*b - auto lhs = node->get_reaction1(); + const auto& lhs = node.get_reaction1(); /// check if reaction statement is a single state variable bool single_state_var = true; @@ -225,16 +225,16 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) logger->warn( "KineticBlockVisitor :: LHS of \"<<\" reaction statement must be a single state " "var, but instead found {}: ignoring this statement", - to_nmodl(lhs.get())); + to_nmodl(lhs)); return; } - auto rhs = node->get_expression1(); - std::string varname = to_nmodl(lhs.get()); + const auto& rhs = node.get_expression1(); + std::string varname = to_nmodl(lhs); // get index of state var const auto it = state_var_index.find(varname); if (it != state_var_index.cend()) { int var_index = it->second; - std::string expr = to_nmodl(rhs.get()); + std::string expr = to_nmodl(rhs); if (!additive_terms[var_index].empty()) { additive_terms[var_index] += " + "; } @@ -248,18 +248,18 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) } // forwards reaction rate - auto kf = node->get_expression1(); + const auto& kf = node.get_expression1(); // backwards reaction rate - auto kb = node->get_expression2(); + const auto& kb = node.get_expression2(); // add reaction rates to vectors kf, kb - auto kf_str = to_nmodl(kf.get()); + auto kf_str = to_nmodl(kf); logger->debug("KineticBlockVisitor :: k_f[{}] = {}", i_statement, kf_str); rate_eqs.k_f.emplace_back(kf_str); if (kb) { // kf is always defined, but for statements with operator "->" kb is not - auto kb_str = to_nmodl(kb.get()); + auto kb_str = to_nmodl(kb); logger->debug("KineticBlockVisitor :: k_b[{}] = {}", i_statement, kb_str); rate_eqs.k_b.emplace_back(kb_str); } else { @@ -278,7 +278,7 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) // add the corresponding integer to the new row in the matrix in_reaction_statement = true; in_reaction_statement_lhs = true; - node->visit_children(*this); + node.visit_children(*this); in_reaction_statement = false; // generate fluxes @@ -322,36 +322,36 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement* node) ++i_statement; } -void KineticBlockVisitor::visit_wrapped_expression(ast::WrappedExpression* node) { +void KineticBlockVisitor::visit_wrapped_expression(ast::WrappedExpression& node) { // If a wrapped expression contains a variable with name "f_flux" or "b_flux", // this variable should be replaced by the expression for the corresponding flux // which depends on the previous reaction statement. The current expressions are // stored as strings in "modfile_fflux" and "modfile_bflux" - if (node->get_expression()->is_name()) { - auto var_name = std::dynamic_pointer_cast(node->get_expression()); + if (node.get_expression()->is_name()) { + auto var_name = std::dynamic_pointer_cast(node.get_expression()); if (var_name->get_node_name() == "f_flux") { auto expr = create_expr(modfile_fflux); - logger->debug("KineticBlockVisitor :: replacing f_flux with {}", to_nmodl(expr.get())); - node->set_expression(std::move(expr)); + logger->debug("KineticBlockVisitor :: replacing f_flux with {}", to_nmodl(expr)); + node.set_expression(std::move(expr)); } else if (var_name->get_node_name() == "b_flux") { auto expr = create_expr(modfile_bflux); - logger->debug("KineticBlockVisitor :: replacing b_flux with {}", to_nmodl(expr.get())); - node->set_expression(std::move(expr)); + logger->debug("KineticBlockVisitor :: replacing b_flux with {}", to_nmodl(expr)); + node.set_expression(std::move(expr)); } } - node->visit_children(*this); + node.visit_children(*this); } -void KineticBlockVisitor::visit_statement_block(ast::StatementBlock* node) { +void KineticBlockVisitor::visit_statement_block(ast::StatementBlock& node) { auto prev_statement_block = current_statement_block; - current_statement_block = node; - node->visit_children(*this); + current_statement_block = &node; + node.visit_children(*this); // remove processed statements from current statement block - remove_statements_from_block(current_statement_block, statements_to_remove); + remove_statements_from_block(*current_statement_block, statements_to_remove); current_statement_block = prev_statement_block; } -void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { +void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { rate_eqs.nu_L.clear(); rate_eqs.nu_R.clear(); rate_eqs.k_f.clear(); @@ -368,7 +368,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { i_statement = 0; // construct stochiometric matrices and fluxes - node->visit_children(*this); + node.visit_children(*this); // number of reaction statements int Ni = static_cast(rate_eqs.k_f.size()); @@ -416,9 +416,9 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { logger->debug("KineticBlockVisitor :: ode : {}", ode); } - auto kinetic_statement_block = node->get_statement_block(); + const auto& kinetic_statement_block = node.get_statement_block(); // remove any remaining kinetic statements - remove_statements_from_block(kinetic_statement_block.get(), statements_to_remove); + remove_statements_from_block(*kinetic_statement_block, statements_to_remove); // add new statements for (const auto& ode: odes) { logger->debug("KineticBlockVisitor :: -> adding statement: {}", ode); @@ -426,10 +426,10 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock* node) { } // store pointer to kinetic block - kinetic_blocks.push_back(node); + kinetic_blocks.push_back(&node); } -void KineticBlockVisitor::visit_program(ast::Program* node) { +void KineticBlockVisitor::visit_program(ast::Program& node) { conserve_statement_count = 0; statements_to_remove.clear(); current_statement_block = nullptr; @@ -439,7 +439,7 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { array_state_var_size.clear(); state_var.clear(); state_var_count = 0; - if (auto symtab = node->get_symbol_table()) { + if (auto symtab = node.get_symbol_table()) { auto statevars = symtab->get_variables_with_properties(NmodlType::state_var); for (const auto& v: statevars) { std::string var_name = v->get_name(); @@ -473,7 +473,7 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { } // change KINETIC blocks -> DERIVATIVE blocks - auto blocks = node->get_blocks(); + auto blocks = node.get_blocks(); for (auto* kinetic_block: kinetic_blocks) { for (auto it = blocks.begin(); it != blocks.end(); ++it) { if (it->get() == kinetic_block) { @@ -486,7 +486,7 @@ void KineticBlockVisitor::visit_program(ast::Program* node) { } } } - node->set_blocks(std::move(blocks)); + node.set_blocks(std::move(blocks)); } } // namespace visitor diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index bb7051420d..bd97e5b328 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -139,15 +139,15 @@ class KineticBlockVisitor: public AstVisitor { return conserve_statement_count; } - void visit_wrapped_expression(ast::WrappedExpression* node) override; - void visit_reaction_operator(ast::ReactionOperator* node) override; - void visit_react_var_name(ast::ReactVarName* node) override; - void visit_reaction_statement(ast::ReactionStatement* node) override; - void visit_conserve(ast::Conserve* node) override; - void visit_compartment(ast::Compartment* node) override; - void visit_statement_block(ast::StatementBlock* node) override; - void visit_kinetic_block(ast::KineticBlock* node) override; - void visit_program(ast::Program* node) override; + void visit_wrapped_expression(ast::WrappedExpression& node) override; + void visit_reaction_operator(ast::ReactionOperator& node) override; + void visit_react_var_name(ast::ReactVarName& node) override; + void visit_reaction_statement(ast::ReactionStatement& node) override; + void visit_conserve(ast::Conserve& node) override; + void visit_compartment(ast::Compartment& node) override; + void visit_statement_block(ast::StatementBlock& node) override; + void visit_kinetic_block(ast::KineticBlock& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index bbc4d59b4e..ef2d772961 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -16,13 +16,13 @@ namespace visitor { using symtab::SymbolTable; /// rename name conflicting variables in the statement block and it's all children -void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { +void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock& node) { /// nothing to do - if (node->get_statements().empty()) { + if (node.get_statements().empty()) { return; } - auto current_symtab = node->get_symbol_table(); + auto current_symtab = node.get_symbol_table(); if (current_symtab != nullptr) { symtab = current_symtab; } @@ -33,7 +33,7 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { symtab_stack.push(symtab); // first need to process all children : perform recursively from innermost block - for (const auto& item: node->get_statements()) { + for (const auto& item: node.get_statements()) { item->visit_children(*this); } diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index 2e7b4382e6..22cb62b73f 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -73,7 +73,7 @@ class LocalVarRenameVisitor: public AstVisitor { public: LocalVarRenameVisitor() = default; - virtual void visit_statement_block(ast::StatementBlock* node) override; + virtual void visit_statement_block(ast::StatementBlock& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 0750db8752..0d88dad18d 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -17,7 +17,7 @@ using symtab::Symbol; using symtab::syminfo::NmodlType; bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { - auto type = node->get_node_type(); + const auto& type = node->get_node_type(); /** * Blocks where we should compute def-use chains. We are excluding @@ -99,21 +99,21 @@ std::vector LocalizeVisitor::variables_to_optimize() { return result; } -void LocalizeVisitor::visit_program(ast::Program* node) { +void LocalizeVisitor::visit_program(ast::Program& node) { /// symtab visitor pass need to be run before - program_symtab = node->get_symbol_table(); + program_symtab = node.get_symbol_table(); if (program_symtab == nullptr) { logger->warn("LocalizeVisitor :: symbol table is not setup, returning"); return; } - auto variables = variables_to_optimize(); + const auto& variables = variables_to_optimize(); for (const auto& varname: variables) { - const auto& blocks = node->get_blocks(); + const auto& blocks = node.get_blocks(); std::map>> block_usage; /// compute def use chains - for (auto block: blocks) { + for (const auto& block: blocks) { if (node_for_def_use_analysis(block.get())) { DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); auto usages = v.analyze(block.get(), varname); @@ -138,11 +138,11 @@ void LocalizeVisitor::visit_program(ast::Program* node) { auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { - variable = add_local_variable(statement_block.get(), + variable = add_local_variable(*statement_block.get(), varname, symbol->get_length()); } else { - variable = add_local_variable(statement_block.get(), varname); + variable = add_local_variable(*statement_block.get(), varname); } /// mark variable as localized in global symbol table diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 1035a0974e..06d053ac69 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -106,7 +106,7 @@ class LocalizeVisitor: public AstVisitor { explicit LocalizeVisitor(bool ignore_verbatim) : ignore_verbatim(ignore_verbatim) {} - virtual void visit_program(ast::Program* node) override; + virtual void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 2080563207..4919c4ee22 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -40,11 +40,12 @@ class IndexRemover: public AstVisitor { public: IndexRemover(std::string index, int value) - : index(index) + : index(std::move(index)) , value(value) {} /// if expression we are visiting is `Name` then return new `Integer` node - std::shared_ptr replace_for_name(const std::shared_ptr node) { + std::shared_ptr replace_for_name( + const std::shared_ptr& node) { if (node->is_name()) { auto name = std::dynamic_pointer_cast(node); if (name->get_node_name() == index) { @@ -54,24 +55,24 @@ class IndexRemover: public AstVisitor { return node; } - virtual void visit_binary_expression(ast::BinaryExpression* node) override { - node->visit_children(*this); + void visit_binary_expression(ast::BinaryExpression& node) override { + node.visit_children(*this); if (under_indexed_name) { /// first recursively replaces childrens /// replace lhs & rhs if they have matching index variable - auto lhs = replace_for_name(node->get_lhs()); - auto rhs = replace_for_name(node->get_rhs()); - node->set_lhs(std::move(lhs)); - node->set_rhs(std::move(rhs)); + auto lhs = replace_for_name(node.get_lhs()); + auto rhs = replace_for_name(node.get_rhs()); + node.set_lhs(std::move(lhs)); + node.set_rhs(std::move(rhs)); } } - virtual void visit_indexed_name(ast::IndexedName* node) override { + virtual void visit_indexed_name(ast::IndexedName& node) override { under_indexed_name = true; - node->visit_children(*this); + node.visit_children(*this); /// once all children are replaced, do the same for index - auto length = replace_for_name(node->get_length()); - node->set_length(std::move(length)); + auto length = replace_for_name(node.get_length()); + node.set_length(std::move(length)); under_indexed_name = false; } }; @@ -94,7 +95,7 @@ static std::shared_ptr unwrap(const std::shared_ptr unroll_for_loop( - const std::shared_ptr node) { + const std::shared_ptr& node) { /// loop can be in the form of `FROM i=(0) TO (10)` /// so first unwrap all elements of the loop const auto from = unwrap(node->get_from()); @@ -120,7 +121,7 @@ static std::shared_ptr unroll_for_loop( for (int i = start; i <= end; i += step) { /// duplicate loop body and copy all statements to new vector auto new_block = node->get_statement_block()->clone(); - IndexRemover(index_var, i).visit_statement_block(new_block); + IndexRemover(index_var, i).visit_statement_block(*new_block); statements.insert(statements.end(), new_block->get_statements().begin(), new_block->get_statements().end()); @@ -136,17 +137,17 @@ static std::shared_ptr unroll_for_loop( /** * Parse verbatim blocks and rename variable if it is used. */ -void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { - node->visit_children(*this); +void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock& node) { + node.visit_children(*this); - const auto& statements = node->get_statements(); + const auto& statements = node.get_statements(); for (auto iter = statements.begin(); iter != statements.end(); ++iter) { if ((*iter)->is_from_statement()) { auto statement = std::dynamic_pointer_cast((*iter)); /// check if any verbatim block exists - auto verbatim_blocks = AstLookupVisitor().lookup(statement.get(), + auto verbatim_blocks = AstLookupVisitor().lookup(*statement, ast::AstNodeType::VERBATIM); if (!verbatim_blocks.empty()) { logger->debug("LoopUnrollVisitor : can not unroll because of verbatim block"); @@ -156,10 +157,10 @@ void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock* node) { /// unroll loop, replace current statement on successfull unroll auto new_statement = unroll_for_loop(statement); if (new_statement != nullptr) { - node->reset_statement(iter, new_statement); + node.reset_statement(iter, new_statement); - std::string before = to_nmodl(statement.get()); - std::string after = to_nmodl(new_statement.get()); + const auto& before = to_nmodl(statement); + const auto& after = to_nmodl(new_statement); logger->debug("LoopUnrollVisitor : \n {} \n unrolled to \n {}", before, after); } } diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index c792c3c535..3cd1ac69d0 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -62,7 +62,7 @@ class LoopUnrollVisitor: public AstVisitor { public: LoopUnrollVisitor() = default; - virtual void visit_statement_block(ast::StatementBlock* node) override; + void visit_statement_block(ast::StatementBlock& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index e0db60a433..5d69311431 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -92,20 +92,20 @@ int main(int argc, const char* argv[]) { for (const auto& filename: files) { logger->info("Processing {}", filename); - std::string mod_file = utils::remove_extension(utils::base_name(filename)); + const std::string mod_file(utils::remove_extension(utils::base_name(filename))); /// driver object that creates lexer and parser parser::NmodlDriver driver; /// shared_ptr to ast constructed from parsing nmodl file - auto ast = driver.parse_file(filename); + const auto& ast = driver.parse_file(filename); /// run all visitors and generate mod file after each run for (const auto& visitor: visitors) { logger->info("Running {}", visitor.description); - visitor.v->visit_program(ast.get()); - std::string file = mod_file + "." + visitor.id + ".mod"; - NmodlPrintVisitor(file).visit_program(ast.get()); + visitor.v->visit_program(*ast); + const std::string file = mod_file + "." + visitor.id + ".mod"; + NmodlPrintVisitor(file).visit_program(*ast); logger->info("NMODL visitor generated {}", file); } } diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index cbfa3d6497..d67380f875 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -19,38 +19,38 @@ namespace nmodl { namespace visitor { -void NeuronSolveVisitor::visit_solve_block(ast::SolveBlock* node) { - auto name = node->get_block_name()->get_node_name(); - auto method = node->get_method(); +void NeuronSolveVisitor::visit_solve_block(ast::SolveBlock& node) { + auto name = node.get_block_name()->get_node_name(); + const auto& method = node.get_method(); solve_method = method ? method->get_value()->eval() : ""; solve_blocks[name] = solve_method; } -void NeuronSolveVisitor::visit_derivative_block(ast::DerivativeBlock* node) { - derivative_block_name = node->get_name()->get_node_name(); +void NeuronSolveVisitor::visit_derivative_block(ast::DerivativeBlock& node) { + derivative_block_name = node.get_name()->get_node_name(); derivative_block = true; - node->visit_children(*this); + node.visit_children(*this); derivative_block = false; } -void NeuronSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { +void NeuronSolveVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { differential_equation = true; - node->visit_children(*this); + node.visit_children(*this); differential_equation = false; } -void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { - const auto& lhs = node->get_lhs(); +void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression& node) { + const auto& lhs = node.get_lhs(); /// we have to only solve odes under derivative block where lhs is variable if (!derivative_block || !differential_equation || !lhs->is_var_name()) { return; } - auto solve_method = solve_blocks[derivative_block_name]; + const auto& solve_method = solve_blocks[derivative_block_name]; auto name = std::dynamic_pointer_cast(lhs)->get_name(); if (name->is_prime_name()) { @@ -66,8 +66,8 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { statement); const auto bin_expr = std::dynamic_pointer_cast( expr_statement->get_expression()); - node->set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); - node->set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); + node.set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); + node.set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); } else { logger->warn("NeuronSolveVisitor :: cnexp solver not possible for {}", to_nmodl(node)); @@ -78,11 +78,11 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { auto expr_statement = std::dynamic_pointer_cast(statement); const auto bin_expr = std::dynamic_pointer_cast( expr_statement->get_expression()); - node->set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); - node->set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); + node.set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); + node.set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { auto varname = "D" + name->get_node_name(); - node->set_lhs(std::make_shared(new ast::String(varname))); + node.set_lhs(std::make_shared(new ast::String(varname))); if (program_symtab->lookup(varname) == nullptr) { auto symbol = std::make_shared(varname, ModToken()); symbol->set_original_name(name->get_node_name()); @@ -95,9 +95,9 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression* node) { } } -void NeuronSolveVisitor::visit_program(ast::Program* node) { - program_symtab = node->get_symbol_table(); - node->visit_children(*this); +void NeuronSolveVisitor::visit_program(ast::Program& node) { + program_symtab = node.get_symbol_table(); + node.visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index 99af768b31..f8c7367f93 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -61,11 +61,11 @@ class NeuronSolveVisitor: public AstVisitor { public: NeuronSolveVisitor() = default; - void visit_solve_block(ast::SolveBlock* node) override; - void visit_diff_eq_expression(ast::DiffEqExpression* node) override; - void visit_derivative_block(ast::DerivativeBlock* node) override; - void visit_binary_expression(ast::BinaryExpression* node) override; - void visit_program(ast::Program* node) override; + void visit_solve_block(ast::SolveBlock& node) override; + void visit_diff_eq_expression(ast::DiffEqExpression& node) override; + void visit_derivative_block(ast::DerivativeBlock& node) override; + void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 3d5be950a5..e2b68ab866 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -23,11 +23,11 @@ PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) {} /// count math operations from all binary expressions -void PerfVisitor::visit_binary_expression(ast::BinaryExpression* node) { +void PerfVisitor::visit_binary_expression(ast::BinaryExpression& node) { bool assign_op = false; if (start_measurement) { - auto value = node->get_op().get_value(); + auto value = node.get_op().get_value(); switch (value) { case ast::BOP_ADDITION: current_block_perf.n_add++; @@ -97,12 +97,12 @@ void PerfVisitor::visit_binary_expression(ast::BinaryExpression* node) { visiting_lhs_expression = true; } - node->get_lhs()->accept(*this); + node.get_lhs()->accept(*this); /// lhs is done (rhs is read only) visiting_lhs_expression = false; - node->get_rhs()->accept(*this); + node.get_rhs()->accept(*this); } /// add performance stats to json printer @@ -164,11 +164,11 @@ void PerfVisitor::measure_performance(ast::Ast* node) { } /// count function calls and "most useful" or "commonly used" math functions -void PerfVisitor::visit_function_call(ast::FunctionCall* node) { +void PerfVisitor::visit_function_call(ast::FunctionCall& node) { under_function_call = true; if (start_measurement) { - auto name = node->get_node_name(); + auto name = node.get_node_name(); if (name == "exp") { current_block_perf.n_exp++; } else if (name == "log") { @@ -176,7 +176,7 @@ void PerfVisitor::visit_function_call(ast::FunctionCall* node) { } else if (name == "pow") { current_block_perf.n_pow++; } - node->visit_children(*this); + node.visit_children(*this); auto symbol = current_symtab->lookup_in_scope(name); auto method_property = NmodlType::procedure_block | NmodlType::function_block; @@ -191,28 +191,28 @@ void PerfVisitor::visit_function_call(ast::FunctionCall* node) { } /// every variable used is of type name, update counters -void PerfVisitor::visit_name(ast::Name* node) { - update_memory_ops(node->get_node_name()); - node->visit_children(*this); +void PerfVisitor::visit_name(ast::Name& node) { + update_memory_ops(node.get_node_name()); + node.visit_children(*this); } /// prime name derived from identifier and hence need to be handled here -void PerfVisitor::visit_prime_name(ast::PrimeName* node) { - update_memory_ops(node->get_node_name()); - node->visit_children(*this); +void PerfVisitor::visit_prime_name(ast::PrimeName& node) { + update_memory_ops(node.get_node_name()); + node.visit_children(*this); } -void PerfVisitor::visit_if_statement(ast::IfStatement* node) { +void PerfVisitor::visit_if_statement(ast::IfStatement& node) { if (start_measurement) { current_block_perf.n_if++; - node->visit_children(*this); + node.visit_children(*this); } } -void PerfVisitor::visit_else_if_statement(ast::ElseIfStatement* node) { +void PerfVisitor::visit_else_if_statement(ast::ElseIfStatement& node) { if (start_measurement) { current_block_perf.n_elif++; - node->visit_children(*this); + node.visit_children(*this); } } @@ -313,12 +313,12 @@ void PerfVisitor::print_memory_usage() { } } -void PerfVisitor::visit_program(ast::Program* node) { +void PerfVisitor::visit_program(ast::Program& node) { if (printer) { printer->push_block("BlockPerf"); } - node->visit_children(*this); + node.visit_children(*this); std::string title = "Total Performance Statistics"; total_perf.title = title; total_perf.print(stream); @@ -330,7 +330,7 @@ void PerfVisitor::visit_program(ast::Program* node) { printer->pop_block(); } - current_symtab = node->get_symbol_table(); + current_symtab = node.get_symbol_table(); count_variables(); print_memory_usage(); } @@ -339,21 +339,21 @@ void PerfVisitor::visit_program(ast::Program* node) { * blocks like net receive has nested initial blocks. Hence need * to maintain separate stack. */ -void PerfVisitor::visit_statement_block(ast::StatementBlock* node) { +void PerfVisitor::visit_statement_block(ast::StatementBlock& node) { /// starting new block, store current state blocks_perf.push(current_block_perf); - current_symtab = node->get_symbol_table(); + current_symtab = node.get_symbol_table(); if (current_symtab == nullptr) { throw std::runtime_error("Perfvisitor : symbol table not setup for " + - node->get_node_type_name()); + node.get_node_type_name()); } /// new block perf starts from zero current_block_perf = PerfStat(); - node->visit_children(*this); + node.visit_children(*this); /// add performance of all visited children total_perf = total_perf + current_block_perf; @@ -369,15 +369,15 @@ void PerfVisitor::visit_statement_block(ast::StatementBlock* node) { /// and hence could/should not be skipped completely /// we can't ignore the block because it could have associated /// statement block (in theory) -void PerfVisitor::visit_solve_block(ast::SolveBlock* node) { +void PerfVisitor::visit_solve_block(ast::SolveBlock& node) { under_solve_block = true; - node->visit_children(*this); + node.visit_children(*this); under_solve_block = false; } -void PerfVisitor::visit_unary_expression(ast::UnaryExpression* node) { +void PerfVisitor::visit_unary_expression(ast::UnaryExpression& node) { if (start_measurement) { - auto value = node->get_op().get_value(); + auto value = node.get_op().get_value(); switch (value) { case ast::UOP_NEGATION: current_block_perf.n_neg++; @@ -391,7 +391,7 @@ void PerfVisitor::visit_unary_expression(ast::UnaryExpression* node) { throw std::logic_error("Unary operator not handled in perf visitor"); } } - node->visit_children(*this); + node.visit_children(*this); } /** Certain statements / symbols needs extra check while measuring diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 133e053bce..7370024878 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -155,153 +155,153 @@ class PerfVisitor: public AstVisitor { printer->compact_json(flag); } - utils::PerfStat get_total_perfstat() { + utils::PerfStat get_total_perfstat() const noexcept { return total_perf; } - int get_instance_variable_count() { + int get_instance_variable_count() const noexcept { return num_instance_variables; } - int get_const_instance_variable_count() { + int get_const_instance_variable_count() const noexcept { return num_constant_instance_variables; } - int get_const_global_variable_count() { + int get_const_global_variable_count() const noexcept { return num_constant_global_variables; } - int get_global_variable_count() { + int get_global_variable_count() const noexcept { return num_global_variables; } - int get_state_variable_count() { + int get_state_variable_count() const noexcept { return num_state_variables; } - void visit_binary_expression(ast::BinaryExpression* node) override; + void visit_binary_expression(ast::BinaryExpression& node) override; - void visit_function_call(ast::FunctionCall* node) override; + void visit_function_call(ast::FunctionCall& node) override; - void visit_name(ast::Name* node) override; + void visit_name(ast::Name& node) override; - void visit_prime_name(ast::PrimeName* node) override; + void visit_prime_name(ast::PrimeName& node) override; - void visit_solve_block(ast::SolveBlock* node) override; + void visit_solve_block(ast::SolveBlock& node) override; - void visit_statement_block(ast::StatementBlock* node) override; + void visit_statement_block(ast::StatementBlock& node) override; - void visit_unary_expression(ast::UnaryExpression* node) override; + void visit_unary_expression(ast::UnaryExpression& node) override; - void visit_if_statement(ast::IfStatement* node) override; + void visit_if_statement(ast::IfStatement& node) override; - void visit_else_if_statement(ast::ElseIfStatement* node) override; + void visit_else_if_statement(ast::ElseIfStatement& node) override; - void visit_program(ast::Program* node) override; + void visit_program(ast::Program& node) override; - void visit_plot_block(ast::PlotBlock* node) override { - measure_performance(node); + void visit_plot_block(ast::PlotBlock& node) override { + measure_performance(&node); } /// skip initial block under net_receive block - void visit_initial_block(ast::InitialBlock* node) override { + void visit_initial_block(ast::InitialBlock& node) override { if (!under_net_receive_block) { - measure_performance(node); + measure_performance(&node); } } - void visit_constructor_block(ast::ConstructorBlock* node) override { - measure_performance(node); + void visit_constructor_block(ast::ConstructorBlock& node) override { + measure_performance(&node); } - void visit_destructor_block(ast::DestructorBlock* node) override { - measure_performance(node); + void visit_destructor_block(ast::DestructorBlock& node) override { + measure_performance(&node); } - void visit_derivative_block(ast::DerivativeBlock* node) override { - measure_performance(node); + void visit_derivative_block(ast::DerivativeBlock& node) override { + measure_performance(&node); } - void visit_linear_block(ast::LinearBlock* node) override { - measure_performance(node); + void visit_linear_block(ast::LinearBlock& node) override { + measure_performance(&node); } - void visit_non_linear_block(ast::NonLinearBlock* node) override { - measure_performance(node); + void visit_non_linear_block(ast::NonLinearBlock& node) override { + measure_performance(&node); } - void visit_discrete_block(ast::DiscreteBlock* node) override { - measure_performance(node); + void visit_discrete_block(ast::DiscreteBlock& node) override { + measure_performance(&node); } - void visit_partial_block(ast::PartialBlock* node) override { - measure_performance(node); + void visit_partial_block(ast::PartialBlock& node) override { + measure_performance(&node); } - void visit_function_table_block(ast::FunctionTableBlock* node) override { - measure_performance(node); + void visit_function_table_block(ast::FunctionTableBlock& node) override { + measure_performance(&node); } - void visit_function_block(ast::FunctionBlock* node) override { - measure_performance(node); + void visit_function_block(ast::FunctionBlock& node) override { + measure_performance(&node); } - void visit_procedure_block(ast::ProcedureBlock* node) override { - measure_performance(node); + void visit_procedure_block(ast::ProcedureBlock& node) override { + measure_performance(&node); } - void visit_net_receive_block(ast::NetReceiveBlock* node) override { + void visit_net_receive_block(ast::NetReceiveBlock& node) override { under_net_receive_block = true; - measure_performance(node); + measure_performance(&node); under_net_receive_block = false; } - void visit_breakpoint_block(ast::BreakpointBlock* node) override { - measure_performance(node); + void visit_breakpoint_block(ast::BreakpointBlock& node) override { + measure_performance(&node); } - void visit_terminal_block(ast::TerminalBlock* node) override { - measure_performance(node); + void visit_terminal_block(ast::TerminalBlock& node) override { + measure_performance(&node); } - void visit_before_block(ast::BeforeBlock* node) override { - measure_performance(node); + void visit_before_block(ast::BeforeBlock& node) override { + measure_performance(&node); } - void visit_after_block(ast::AfterBlock* node) override { - measure_performance(node); + void visit_after_block(ast::AfterBlock& node) override { + measure_performance(&node); } - void visit_ba_block(ast::BABlock* node) override { - measure_performance(node); + void visit_ba_block(ast::BABlock& node) override { + measure_performance(&node); } - void visit_for_netcon(ast::ForNetcon* node) override { - measure_performance(node); + void visit_for_netcon(ast::ForNetcon& node) override { + measure_performance(&node); } - void visit_kinetic_block(ast::KineticBlock* node) override { - measure_performance(node); + void visit_kinetic_block(ast::KineticBlock& node) override { + measure_performance(&node); } - void visit_match_block(ast::MatchBlock* node) override { - measure_performance(node); + void visit_match_block(ast::MatchBlock& node) override { + measure_performance(&node); } /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations - void visit_conductance_hint(ast::ConductanceHint* /*node*/) override {} + void visit_conductance_hint(ast::ConductanceHint& /*node*/) override {} - void visit_local_list_statement(ast::LocalListStatement* /*node*/) override {} + void visit_local_list_statement(ast::LocalListStatement& /*node*/) override {} - void visit_suffix(ast::Suffix* /*node*/) override {} + void visit_suffix(ast::Suffix& /*node*/) override {} - void visit_useion(ast::Useion* /*node*/) override {} + void visit_useion(ast::Useion& /*node*/) override {} - void visit_valence(ast::Valence* /*node*/) override {} + void visit_valence(ast::Valence& /*node*/) override {} - void print(std::stringstream& ss) { + void print(std::ostream& ss) { ss << stream.str(); } }; diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 40bc6b1c72..0be19c5c62 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -13,10 +13,10 @@ namespace nmodl { namespace visitor { /// rename matching variable -void RenameVisitor::visit_name(ast::Name* node) { - std::string name = node->get_node_name(); +void RenameVisitor::visit_name(ast::Name& node) { + const auto& name = node.get_node_name(); if (name == var_name) { - auto value = node->get_value(); + auto& value = node.get_value(); value->set(new_var_name); } } @@ -26,19 +26,19 @@ void RenameVisitor::visit_name(ast::Name* node) { * macro. In practice this won't be an issue as we order is set * by parser. To be safe we are only renaming prime variable. */ -void RenameVisitor::visit_prime_name(ast::PrimeName* node) { - node->visit_children(*this); +void RenameVisitor::visit_prime_name(ast::PrimeName& node) { + node.visit_children(*this); } /** * Parse verbatim blocks and rename variable if it is used. */ -void RenameVisitor::visit_verbatim(ast::Verbatim* node) { +void RenameVisitor::visit_verbatim(ast::Verbatim& node) { if (!rename_verbatim) { return; } - auto statement = node->get_statement(); + const auto& statement = node.get_statement(); auto text = statement->eval(); parser::CDriver driver; diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index 0071e08c58..73784ef825 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -67,9 +67,9 @@ class RenameVisitor: public AstVisitor { rename_verbatim = state; } - virtual void visit_name(ast::Name* node) override; - virtual void visit_prime_name(ast::PrimeName* node) override; - virtual void visit_verbatim(ast::Verbatim* node) override; + void visit_name(ast::Name& node) override; + void visit_prime_name(ast::PrimeName& node) override; + void visit_verbatim(ast::Verbatim& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index de1f644022..f2d0486a3a 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -13,14 +13,14 @@ namespace nmodl { namespace visitor { -void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { +void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock& node) { in_breakpoint_block = true; - node->visit_children(*this); + node.visit_children(*this); in_breakpoint_block = false; } /// check if given node contains sympy solution -static bool has_sympy_solution(ast::Ast* node) { +static bool has_sympy_solution(ast::Ast& node) { return !AstLookupVisitor().lookup(node, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK).empty(); } @@ -35,54 +35,54 @@ static bool has_sympy_solution(ast::Ast* node) { * instead of just pointer. */ ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( - ast::SolveBlock* solve_block) { + ast::SolveBlock& solve_block) { /// find out the block that is going to solved - std::string block_name = solve_block->get_block_name()->get_node_name(); + std::string block_name = solve_block.get_block_name()->get_node_name(); auto solve_node_symbol = symtab->lookup(block_name); assert(solve_node_symbol != nullptr); auto node_to_solve = solve_node_symbol->get_node(); /// in case of derivimplicit method if neuron solver is used (i.e. not sympy) then /// the solution is not in place but we have to create a callback to newton solver - auto method = solve_block->get_method(); + const auto& method = solve_block.get_method(); std::string solve_method = method ? method->get_node_name() : ""; if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD && - !has_sympy_solution(node_to_solve)) { + !has_sympy_solution(*node_to_solve)) { /// typically derivimplicit is used for derivative block only assert(node_to_solve->get_node_type() == ast::AstNodeType::DERIVATIVE_BLOCK); auto derivative_block = dynamic_cast(node_to_solve); auto callback_expr = new ast::DerivimplicitCallback(derivative_block->clone()); - return new ast::SolutionExpression(solve_block->clone(), callback_expr); + return new ast::SolutionExpression(solve_block.clone(), callback_expr); } auto block_to_solve = node_to_solve->get_statement_block(); - return new ast::SolutionExpression(solve_block->clone(), block_to_solve->clone()); + return new ast::SolutionExpression(solve_block.clone(), block_to_solve->clone()); } /** * Replace solve blocks with solution expression * @param node Ast node for SOLVE statement in the mod file */ -void SolveBlockVisitor::visit_expression_statement(ast::ExpressionStatement* node) { - node->visit_children(*this); - if (node->get_expression()->is_solve_block()) { - auto solve_block = dynamic_cast(node->get_expression().get()); - auto sol_expr = create_solution_expression(solve_block); +void SolveBlockVisitor::visit_expression_statement(ast::ExpressionStatement& node) { + node.visit_children(*this); + if (node.get_expression()->is_solve_block()) { + auto solve_block = dynamic_cast(node.get_expression().get()); + auto sol_expr = create_solution_expression(*solve_block); if (in_breakpoint_block) { nrn_state_solve_statements.emplace_back(new ast::ExpressionStatement(sol_expr)); } else { - node->set_expression(std::shared_ptr(sol_expr)); + node.set_expression(std::shared_ptr(sol_expr)); } } } -void SolveBlockVisitor::visit_program(ast::Program* node) { - symtab = node->get_symbol_table(); - node->visit_children(*this); +void SolveBlockVisitor::visit_program(ast::Program& node) { + symtab = node.get_symbol_table(); + node.visit_children(*this); /// add new node NrnState with solve blocks from breakpoint block if (!nrn_state_solve_statements.empty()) { auto nrn_state = new ast::NrnStateBlock(nrn_state_solve_statements); - node->emplace_back_node(nrn_state); + node.emplace_back_node(nrn_state); } } diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp index 63951191c7..decca10379 100644 --- a/src/nmodl/visitors/solve_block_visitor.hpp +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -42,13 +42,13 @@ class SolveBlockVisitor: public AstVisitor { /// solve expression statements for NrnState block ast::StatementVector nrn_state_solve_statements; - ast::SolutionExpression* create_solution_expression(ast::SolveBlock* solve_block); + ast::SolutionExpression* create_solution_expression(ast::SolveBlock& solve_block); public: SolveBlockVisitor() = default; - void visit_breakpoint_block(ast::BreakpointBlock* node) override; - void visit_expression_statement(ast::ExpressionStatement* node) override; - void visit_program(ast::Program* node) override; + void visit_breakpoint_block(ast::BreakpointBlock& node) override; + void visit_expression_statement(ast::ExpressionStatement& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 054f29db0c..4a852d4473 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -24,7 +24,7 @@ using namespace fmt::literals; using symtab::syminfo::NmodlType; std::shared_ptr SteadystateVisitor::create_steadystate_block( - std::shared_ptr solve_block, + const std::shared_ptr& solve_block, const std::vector>& deriv_blocks) { // new block to be returned std::shared_ptr ss_block; @@ -76,7 +76,7 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo auto statement_block = ss_block->get_statement_block(); // declare tmp variable to save dt value (this will go into the LOCAL statement at the top // of the statement block) - add_local_variable(statement_block.get(), dt_tmp_var_name); + add_local_variable(*statement_block.get(), dt_tmp_var_name); // get a copy of existing statements auto statements = statement_block->get_statements(); // insert dt_save and dt_assign statements just below first LOCAL statement @@ -96,7 +96,7 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo // set name to point to new DERIVATIVE block solve_block->set_block_name(std::move(ss_name_clone)); // change from STEADYSTATE to METHOD - solve_block->set_method(std::move(solve_block->get_steadystate())); + solve_block->set_method(solve_block->get_steadystate()); solve_block->set_steadystate(nullptr); } else { logger->warn("SteadystateVisitor :: Could not find derivative block {} for STEADYSTATE", @@ -106,7 +106,7 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo return ss_block; } -void SteadystateVisitor::visit_program(ast::Program* node) { +void SteadystateVisitor::visit_program(ast::Program& node) { // get DERIVATIVE blocks const auto& deriv_blocks = AstLookupVisitor().lookup(node, ast::AstNodeType::DERIVATIVE_BLOCK); @@ -119,7 +119,7 @@ void SteadystateVisitor::visit_program(ast::Program* node) { if (solve_block->get_steadystate()) { auto ss_block = create_steadystate_block(solve_block, deriv_blocks); if (ss_block != nullptr) { - node->emplace_back_node(ss_block); + node.emplace_back_node(ss_block); } } } diff --git a/src/nmodl/visitors/steadystate_visitor.hpp b/src/nmodl/visitors/steadystate_visitor.hpp index 8d5a71257f..b467f1b393 100644 --- a/src/nmodl/visitors/steadystate_visitor.hpp +++ b/src/nmodl/visitors/steadystate_visitor.hpp @@ -56,7 +56,7 @@ class SteadystateVisitor: public AstVisitor { private: /// create new steady state derivative block for given solve block std::shared_ptr create_steadystate_block( - std::shared_ptr solve_block, + const std::shared_ptr& solve_block, const std::vector>& deriv_blocks); const double STEADYSTATE_SPARSE_DT = 1e9; @@ -66,7 +66,7 @@ class SteadystateVisitor: public AstVisitor { public: SteadystateVisitor() = default; - void visit_program(ast::Program* node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 7db3bb4154..dbd2ad9fc7 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -39,16 +39,16 @@ using symtab::syminfo::NmodlType; * @param node Ast node for breakpoint block * @return true if it is safe to insert conductance statements otherwise false */ -static bool conductance_statement_possible(ast::BreakpointBlock* node) { +static bool conductance_statement_possible(ast::BreakpointBlock& node) { AstLookupVisitor v({AstNodeType::IF_STATEMENT, AstNodeType::VERBATIM}); - node->accept(v); + node.accept(v); return v.get_nodes().empty(); } // Generate statement strings to be added to BREAKPOINT section std::vector SympyConductanceVisitor::generate_statement_strings( - ast::BreakpointBlock* node) { + ast::BreakpointBlock& node) { std::vector statements; // instead of passing all variables in the symbol table find out variables @@ -114,7 +114,7 @@ std::vector SympyConductanceVisitor::generate_statement_strings( } g_var = get_new_name("g", i_name_str, var_map); // declare it - add_local_variable(node->get_statement_block().get(), g_var); + add_local_variable(*node.get_statement_block(), g_var); // asign dIdV to it std::string statement_str = g_var; statement_str.append(" = ").append(dIdV); @@ -134,15 +134,15 @@ std::vector SympyConductanceVisitor::generate_statement_strings( return statements; } -void SympyConductanceVisitor::visit_binary_expression(ast::BinaryExpression* node) { +void SympyConductanceVisitor::visit_binary_expression(ast::BinaryExpression& node) { // only want binary expressions from breakpoint block if (!under_breakpoint_block) { return; } // only want binary expressions of form x = ... - if (node->get_lhs()->is_var_name() && (node->get_op().get_value() == BinaryOp::BOP_ASSIGN)) { + if (node.get_lhs()->is_var_name() && (node.get_op().get_value() == BinaryOp::BOP_ASSIGN)) { auto lhs_str = - std::dynamic_pointer_cast(node->get_lhs())->get_name()->get_node_name(); + std::dynamic_pointer_cast(node.get_lhs())->get_name()->get_node_name(); binary_expr_index[lhs_str] = ordered_binary_exprs.size(); ordered_binary_exprs.push_back(to_nmodl_for_sympy(node)); ordered_binary_exprs_lhs.push_back(lhs_str); @@ -169,9 +169,9 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { void SympyConductanceVisitor::lookup_useion_statements() { // add USEION statements to i_name map between write vars and names for (const auto& useion_ast: use_ion_nodes) { - auto ion = std::dynamic_pointer_cast(useion_ast).get(); - std::string ion_name = ion->get_node_name(); - logger->debug("SympyConductance :: Found USEION statement {}", to_nmodl_for_sympy(ion)); + const auto& ion = std::dynamic_pointer_cast(useion_ast); + const std::string& ion_name = ion->get_node_name(); + logger->debug("SympyConductance :: Found USEION statement {}", to_nmodl_for_sympy(*ion)); if (i_ignore.find(ion_name) != i_ignore.end()) { logger->debug("SympyConductance :: -> Ignoring ion current name: {}", ion_name); } else { @@ -187,12 +187,12 @@ void SympyConductanceVisitor::lookup_useion_statements() { } } -void SympyConductanceVisitor::visit_conductance_hint(ast::ConductanceHint* node) { +void SympyConductanceVisitor::visit_conductance_hint(ast::ConductanceHint& node) { // find existing CONDUCTANCE statements - do not want to alter them // so keep a set of ion names i_ignore that we should ignore later logger->debug("SympyConductance :: Found existing CONDUCTANCE statement: {}", to_nmodl_for_sympy(node)); - if (auto ion = node->get_ion()) { + if (const auto& ion = node.get_ion()) { logger->debug("SympyConductance :: -> Ignoring ion current name: {}", ion->get_node_name()); i_ignore.insert(ion->get_node_name()); } else { @@ -201,7 +201,7 @@ void SympyConductanceVisitor::visit_conductance_hint(ast::ConductanceHint* node) } }; -void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) { +void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock& node) { // return if it's not safe to insert conductance statements if (!conductance_statement_possible(node)) { logger->warn("SympyConductance :: Unsafe to insert CONDUCTANCE statement"); @@ -209,14 +209,14 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) } // add any breakpoint local variables to vars - if (auto* symtab = node->get_statement_block()->get_symbol_table()) { + if (auto* symtab = node.get_statement_block()->get_symbol_table()) { for (const auto& localvar: symtab->get_variables_with_properties(NmodlType::local_var)) { all_vars.insert(localvar->get_name()); } } // visit BREAKPOINT block statements under_breakpoint_block = true; - node->visit_children(*this); + node.visit_children(*this); under_breakpoint_block = false; // lookup USEION and NONSPECIFIC statements from NEURON block @@ -227,7 +227,7 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) auto new_statements = generate_statement_strings(node); if (!new_statements.empty()) { // get a copy of existing BREAKPOINT statements - auto brkpnt_statements = node->get_statement_block()->get_statements(); + auto brkpnt_statements = node.get_statement_block()->get_statements(); // insert new CONDUCTANCE statements at top of BREAKPOINT // or just below LOCAL statement if it exists auto insertion_point = brkpnt_statements.begin(); @@ -239,17 +239,17 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock* node) create_statement(statement_str)); } // replace old set of BREAKPOINT statements in AST with new one - node->get_statement_block()->set_statements(std::move(brkpnt_statements)); + node.get_statement_block()->set_statements(std::move(brkpnt_statements)); } } -void SympyConductanceVisitor::visit_program(ast::Program* node) { +void SympyConductanceVisitor::visit_program(ast::Program& node) { all_vars = get_global_vars(node); AstLookupVisitor ast_lookup_visitor; use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); nonspecific_nodes = ast_lookup_visitor.lookup(node, AstNodeType::NONSPECIFIC); - node->visit_children(*this); + node.visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index 44ef95d63b..8da985e783 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -92,20 +92,20 @@ class SympyConductanceVisitor: public AstVisitor { /// non specific currents std::vector> nonspecific_nodes; - std::vector generate_statement_strings(ast::BreakpointBlock* node); + std::vector generate_statement_strings(ast::BreakpointBlock& node); void lookup_useion_statements(); void lookup_nonspecific_statements(); - static std::string to_nmodl_for_sympy(ast::Ast* node) { + static std::string to_nmodl_for_sympy(ast::Ast& node) { return to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } public: SympyConductanceVisitor() = default; - void visit_binary_expression(ast::BinaryExpression* node) override; - void visit_breakpoint_block(ast::BreakpointBlock* node) override; - void visit_conductance_hint(ast::ConductanceHint* node) override; - void visit_program(ast::Program* node) override; + void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_breakpoint_block(ast::BreakpointBlock& node) override; + void visit_conductance_hint(ast::ConductanceHint& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index f0e2d29df4..e2ebfdeceb 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -45,7 +45,7 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { } } AstLookupVisitor lv(ast::AstNodeType::FUNCTION_CALL); - for (const auto& call: lv.lookup(node->get_statement_block().get())) { + for (const auto& call: lv.lookup(*node->get_statement_block())) { function_calls.insert(call->get_node_name()); } } @@ -59,13 +59,13 @@ void SympySolverVisitor::init_state_vars_vector() { } } -void SympySolverVisitor::replace_diffeq_expression(ast::DiffEqExpression* expr, +void SympySolverVisitor::replace_diffeq_expression(ast::DiffEqExpression& expr, const std::string& new_expr) { auto new_statement = create_statement(new_expr); auto new_expr_statement = std::dynamic_pointer_cast(new_statement); auto new_bin_expr = std::dynamic_pointer_cast( new_expr_statement->get_expression()); - expr->set_expression(std::move(new_bin_expr)); + expr.set_expression(std::move(new_bin_expr)); } void SympySolverVisitor::check_expr_statements_in_same_block() { @@ -92,14 +92,14 @@ ast::StatementVector::const_iterator SympySolverVisitor::get_solution_location_i (std::dynamic_pointer_cast(*it).get() != last_expression_statement)) { logger->debug("SympySolverVisitor :: {} != {}", - to_nmodl((*it).get()), - to_nmodl(last_expression_statement)); + to_nmodl(*it), + to_nmodl(*last_expression_statement)); ++it; } if (it != statements.end()) { logger->debug("SympySolverVisitor :: {} == {}", - to_nmodl(std::dynamic_pointer_cast(*it).get()), - to_nmodl(last_expression_statement)); + to_nmodl(std::dynamic_pointer_cast(*it)), + to_nmodl(*last_expression_statement)); ++it; } } @@ -216,7 +216,7 @@ void SympySolverVisitor::construct_eigen_solver_block( block_with_expression_statements->erase_statement(it, statements.end()); // also remove diff/linear/non-linear eq statements from the statement block - remove_statements_from_block(block_with_expression_statements, expression_statements); + remove_statements_from_block(*block_with_expression_statements, expression_statements); // move any local variable declarations into variable_block ast::StatementVector variable_statements; // remaining statements in block should go into initialize_block @@ -319,7 +319,7 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre for (const auto& new_local_var: new_local_vars) { logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", new_local_var); - add_local_variable(block_with_expression_statements, new_local_var); + add_local_variable(*block_with_expression_statements, new_local_var); } } // insert pre-solve statements below last linear eq in block @@ -336,7 +336,7 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre ++it; } /// remove original lineq statements from the block - remove_statements_from_block(block_with_expression_statements, expression_statements); + remove_statements_from_block(*block_with_expression_statements, expression_statements); } else { // otherwise it returns a linear matrix system to solve logger->debug("SympySolverVisitor :: Constructing linear newton solve block"); @@ -382,11 +382,11 @@ void SympySolverVisitor::solve_non_linear_system( construct_eigen_solver_block(pre_solve_statements, solutions, false); } -void SympySolverVisitor::visit_var_name(ast::VarName* node) { +void SympySolverVisitor::visit_var_name(ast::VarName& node) { if (collect_state_vars) { - std::string var_name = node->get_node_name(); - if (node->get_name()->is_indexed_name()) { - auto index_name = std::dynamic_pointer_cast(node->get_name()); + std::string var_name = node.get_node_name(); + if (node.get_name()->is_indexed_name()) { + auto index_name = std::dynamic_pointer_cast(node.get_name()); var_name += "[" + std::to_string( @@ -402,9 +402,9 @@ void SympySolverVisitor::visit_var_name(ast::VarName* node) { } } -void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { - const auto& lhs = node->get_expression()->get_lhs(); - const auto& rhs = node->get_expression()->get_rhs(); +void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { + const auto& lhs = node.get_expression()->get_lhs(); + const auto& rhs = node.get_expression()->get_rhs(); if (!lhs->is_var_name()) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a VariableName"); @@ -500,41 +500,41 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression* node) { } } -void SympySolverVisitor::visit_conserve(ast::Conserve* node) { +void SympySolverVisitor::visit_conserve(ast::Conserve& node) { // Replace ODE for state variable on LHS of CONSERVE statement with // algebraic expression on RHS (see p244 of NEURON book) logger->debug("SympySolverVisitor :: CONSERVE statement: {}", to_nmodl(node)); - expression_statements.insert(node); + expression_statements.insert(&node); std::string conserve_equation_statevar; - if (node->get_react()->is_react_var_name()) { - conserve_equation_statevar = node->get_react()->get_node_name(); + if (node.get_react()->is_react_var_name()) { + conserve_equation_statevar = node.get_react()->get_node_name(); } if (std::find(all_state_vars.cbegin(), all_state_vars.cend(), conserve_equation_statevar) == all_state_vars.cend()) { logger->error( "SympySolverVisitor :: Invalid CONSERVE statement for DERIVATIVE block, LHS should be " "a state variable, instead found: {}. Ignoring CONSERVE statement", - to_nmodl(node->get_react().get())); + to_nmodl(node.get_react())); return; } - auto conserve_equation_str = to_nmodl_for_sympy(node->get_expr().get()); + auto conserve_equation_str = to_nmodl_for_sympy(*node.get_expr()); logger->debug("SympySolverVisitor :: --> replace ODE for state var {} with equation {}", conserve_equation_statevar, conserve_equation_str); conserve_equation[conserve_equation_statevar] = conserve_equation_str; } -void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { +void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { /// clear information from previous block, get global vars + block local vars - init_block_data(node); + init_block_data(&node); // get user specified solve method for this block - solve_method = derivative_block_solve_method[node->get_node_name()]; + solve_method = derivative_block_solve_method[node.get_node_name()]; // visit each differential equation: // - for CNEXP or EULER, each equation is independent & is replaced with its solution // - otherwise, each equation is added to eq_system - node->visit_children(*this); + node.visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { // solve system of ODEs in eq_system @@ -568,7 +568,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { // check name is unique // declare old_x logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); - add_local_variable(block_with_expression_statements, old_x); + add_local_variable(*block_with_expression_statements, old_x); // assign old_x = x pre_solve_statements.push_back(old_x + " = " + x + x_array_index); // replace ODE with Euler equation @@ -588,77 +588,77 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock* node) { } } -void SympySolverVisitor::visit_lin_equation(ast::LinEquation* node) { +void SympySolverVisitor::visit_lin_equation(ast::LinEquation& node) { check_expr_statements_in_same_block(); - std::string lin_eq = to_nmodl_for_sympy(node->get_left_linxpression().get()); + std::string lin_eq = to_nmodl_for_sympy(*node.get_left_linxpression()); lin_eq += " = "; - lin_eq += to_nmodl_for_sympy(node->get_linxpression().get()); + lin_eq += to_nmodl_for_sympy(*node.get_linxpression()); eq_system.push_back(lin_eq); expression_statements.insert(current_expression_statement); last_expression_statement = current_expression_statement; logger->debug("SympySolverVisitor :: adding linear eq: {}", lin_eq); collect_state_vars = true; - node->visit_children(*this); + node.visit_children(*this); collect_state_vars = false; } -void SympySolverVisitor::visit_linear_block(ast::LinearBlock* node) { - logger->debug("SympySolverVisitor :: found LINEAR block: {}", node->get_node_name()); +void SympySolverVisitor::visit_linear_block(ast::LinearBlock& node) { + logger->debug("SympySolverVisitor :: found LINEAR block: {}", node.get_node_name()); /// clear information from previous block, get global vars + block local vars - init_block_data(node); + init_block_data(&node); // collect linear equations - node->visit_children(*this); + node.visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { solve_linear_system(); } } -void SympySolverVisitor::visit_non_lin_equation(ast::NonLinEquation* node) { +void SympySolverVisitor::visit_non_lin_equation(ast::NonLinEquation& node) { check_expr_statements_in_same_block(); - std::string non_lin_eq = to_nmodl_for_sympy(node->get_lhs().get()); + std::string non_lin_eq = to_nmodl_for_sympy(*node.get_lhs()); non_lin_eq += " = "; - non_lin_eq += to_nmodl_for_sympy(node->get_rhs().get()); + non_lin_eq += to_nmodl_for_sympy(*node.get_rhs()); eq_system.push_back(non_lin_eq); expression_statements.insert(current_expression_statement); last_expression_statement = current_expression_statement; logger->debug("SympySolverVisitor :: adding non-linear eq: {}", non_lin_eq); collect_state_vars = true; - node->visit_children(*this); + node.visit_children(*this); collect_state_vars = false; } -void SympySolverVisitor::visit_non_linear_block(ast::NonLinearBlock* node) { - logger->debug("SympySolverVisitor :: found NONLINEAR block: {}", node->get_node_name()); +void SympySolverVisitor::visit_non_linear_block(ast::NonLinearBlock& node) { + logger->debug("SympySolverVisitor :: found NONLINEAR block: {}", node.get_node_name()); /// clear information from previous block, get global vars + block local vars - init_block_data(node); + init_block_data(&node); // collect non-linear equations - node->visit_children(*this); + node.visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { solve_non_linear_system(); } } -void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement* node) { +void SympySolverVisitor::visit_expression_statement(ast::ExpressionStatement& node) { auto prev_expression_statement = current_expression_statement; - current_expression_statement = node; - node->visit_children(*this); + current_expression_statement = &node; + node.visit_children(*this); current_expression_statement = prev_expression_statement; } -void SympySolverVisitor::visit_statement_block(ast::StatementBlock* node) { +void SympySolverVisitor::visit_statement_block(ast::StatementBlock& node) { auto prev_statement_block = current_statement_block; - current_statement_block = node; - node->visit_children(*this); + current_statement_block = &node; + node.visit_children(*this); current_statement_block = prev_statement_block; } -void SympySolverVisitor::visit_program(ast::Program* node) { +void SympySolverVisitor::visit_program(ast::Program& node) { derivative_block_solve_method.clear(); global_vars = get_global_vars(node); @@ -683,7 +683,7 @@ void SympySolverVisitor::visit_program(ast::Program* node) { // get set of all state vars all_state_vars.clear(); - if (auto symtab = node->get_symbol_table()) { + if (auto symtab = node.get_symbol_table()) { auto statevars = symtab->get_variables_with_properties(NmodlType::state_var); for (const auto& v: statevars) { std::string var_name = v->get_name(); @@ -698,7 +698,7 @@ void SympySolverVisitor::visit_program(ast::Program* node) { } } - node->visit_children(*this); + node.visit_children(*this); } } // namespace visitor diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index b2d066191c..d26cc39c9d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -72,7 +72,7 @@ class SympySolverVisitor: public AstVisitor { void init_state_vars_vector(); /// replace binary expression with new expression provided as string - static void replace_diffeq_expression(ast::DiffEqExpression* expr, const std::string& new_expr); + static void replace_diffeq_expression(ast::DiffEqExpression& expr, const std::string& new_expr); /// raise error if kinetic/ode/(non)linear statements are spread over multiple blocks void check_expr_statements_in_same_block(); @@ -93,7 +93,7 @@ class SympySolverVisitor: public AstVisitor { void solve_non_linear_system(const std::vector& pre_solve_statements = {}); /// return NMODL string version of node, excluding any units - static std::string to_nmodl_for_sympy(ast::Ast* node) { + static std::string to_nmodl_for_sympy(ast::Ast& node) { return nmodl::to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } @@ -184,17 +184,17 @@ class SympySolverVisitor: public AstVisitor { , elimination(elimination) , SMALL_LINEAR_SYSTEM_MAX_STATES(SMALL_LINEAR_SYSTEM_MAX_STATES){}; - void visit_var_name(ast::VarName* node) override; - void visit_diff_eq_expression(ast::DiffEqExpression* node) override; - void visit_conserve(ast::Conserve* node) override; - void visit_derivative_block(ast::DerivativeBlock* node) override; - void visit_lin_equation(ast::LinEquation* node) override; - void visit_linear_block(ast::LinearBlock* node) override; - void visit_non_lin_equation(ast::NonLinEquation* node) override; - void visit_non_linear_block(ast::NonLinearBlock* node) override; - void visit_expression_statement(ast::ExpressionStatement* node) override; - void visit_statement_block(ast::StatementBlock* node) override; - void visit_program(ast::Program* node) override; + void visit_var_name(ast::VarName& node) override; + void visit_diff_eq_expression(ast::DiffEqExpression& node) override; + void visit_conserve(ast::Conserve& node) override; + void visit_derivative_block(ast::DerivativeBlock& node) override; + void visit_lin_equation(ast::LinEquation& node) override; + void visit_linear_block(ast::LinearBlock& node) override; + void visit_non_lin_equation(ast::NonLinEquation& node) override; + void visit_non_linear_block(ast::NonLinearBlock& node) override; + void visit_expression_statement(ast::ExpressionStatement& node) override; + void visit_statement_block(ast::StatementBlock& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 7ea072a71a..21e38b3a39 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -224,7 +224,7 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(ast::Node* node, const s * * @todo we assume table statement follows variable declaration */ -void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { +void SymtabVisitor::visit_table_statement(ast::TableStatement& node) { auto update_symbol = [this](const ast::NameVector& variables, NmodlType property, int num_values) { for (auto& var : variables) { auto name = var->get_node_name(); @@ -235,9 +235,9 @@ void SymtabVisitor::visit_table_statement(ast::TableStatement* node) { } } }; - int num_values = node->get_with()->eval() + 1; - update_symbol(node->get_table_vars(), NmodlType::table_statement_var, num_values); - update_symbol(node->get_depend_vars(), NmodlType::table_assigned_var, num_values); + int num_values = node.get_with()->eval() + 1; + update_symbol(node.get_table_vars(), NmodlType::table_statement_var, num_values); + update_symbol(node.get_depend_vars(), NmodlType::table_assigned_var, num_values); } } // namespace visitor diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index bef2aa7353..cd91b2c4df 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -20,9 +20,9 @@ namespace nmodl { namespace visitor { -void UnitsVisitor::visit_program(ast::Program* node) { +void UnitsVisitor::visit_program(ast::Program& node) { units_driver.parse_file(units_dir); - node->visit_children(*this); + node.visit_children(*this); } /** @@ -36,17 +36,17 @@ void UnitsVisitor::visit_program(ast::Program* node) { * unit parser which was used for parsing the \c nrnunits.lib file. * On \c nrnunits.lib constant "1" is defined as "fuzz", so it must be converted. */ -void UnitsVisitor::visit_unit_def(ast::UnitDef* node) { +void UnitsVisitor::visit_unit_def(ast::UnitDef& node) { std::stringstream ss; /* * In nrnunits.lib file "1" is defined as "fuzz", so there * must be a conversion to be able to parse "1" as unit */ - if (node->get_unit2()->get_node_name() == "1") { - ss << node->get_unit1()->get_node_name() << "\t"; + if (node.get_unit2()->get_node_name() == "1") { + ss << node.get_unit1()->get_node_name() << '\t'; ss << UNIT_FUZZ; } else { - ss << node->get_unit1()->get_node_name() << "\t" << node->get_unit2()->get_node_name(); + ss << node.get_unit1()->get_node_name() << '\t' << node.get_unit2()->get_node_name(); } // Parse the generated string for the defined unit using the units::UnitParser @@ -81,20 +81,20 @@ void UnitsVisitor::visit_unit_def(ast::UnitDef* node) { * unit parser used for parsing the \c nrnunits.lib file, which takes * care of all the units calculations. */ -void UnitsVisitor::visit_factor_def(ast::FactorDef* node) { +void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { std::stringstream ss; - auto node_has_value_defined_in_modfile = node->get_value() != nullptr; + auto node_has_value_defined_in_modfile = node.get_value() != nullptr; if (node_has_value_defined_in_modfile) { /* * In nrnunits.lib file "1" is defined as "fuzz", so * there must be a conversion to be able to parse "1" as unit */ - if (node->get_unit1()->get_node_name() == "1") { - ss << node->get_node_name() << "\t" << node->get_value()->eval() << " "; + if (node.get_unit1()->get_node_name() == "1") { + ss << node.get_node_name() << "\t" << node.get_value()->eval() << " "; ss << UNIT_FUZZ; } else { - ss << node->get_node_name() << "\t" << node->get_value()->eval() << " "; - ss << node->get_unit1()->get_node_name(); + ss << node.get_node_name() << "\t" << node.get_value()->eval() << " "; + ss << node.get_unit1()->get_node_name(); } // Parse the generated string for the defined unit using the units::UnitParser units_driver.parse_string(ss.str()); @@ -105,31 +105,31 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef* node) { * In nrnunits.lib file "1" is defined as "fuzz", so * there must be a conversion to be able to parse "1" as unit */ - if (node->get_unit1()->get_node_name() == "1") { + if (node.get_unit1()->get_node_name() == "1") { unit1_name = UNIT_FUZZ; } else { - unit1_name = node->get_unit1()->get_node_name(); + unit1_name = node.get_unit1()->get_node_name(); } - if (node->get_unit2()->get_node_name() == "1") { + if (node.get_unit2()->get_node_name() == "1") { unit2_name = UNIT_FUZZ; } else { - unit2_name = node->get_unit2()->get_node_name(); + unit2_name = node.get_unit2()->get_node_name(); } /* - * Create dummy unit "node->get_node_name()_unit1" and parse + * Create dummy unit "node.get_node_name()_unit1" and parse * it to calculate its factor */ - ss_unit1 << node->get_node_name() << "_unit1\t" << unit1_name; + ss_unit1 << node.get_node_name() << "_unit1\t" << unit1_name; units_driver.parse_string(ss_unit1.str()); /* - * Create dummy unit "node->get_node_name()_unit2" and parse + * Create dummy unit "node.get_node_name()_unit2" and parse * it to calculate its factor */ - ss_unit2 << node->get_node_name() << "_unit2\t" << unit2_name; + ss_unit2 << node.get_node_name() << "_unit2\t" << unit2_name; units_driver.parse_string(ss_unit2.str()); // Parse the generated string for the defined unit using the units::UnitParser - ss << node->get_node_name() << "\t" << unit1_name; + ss << node.get_node_name() << "\t" << unit1_name; units_driver.parse_string(ss.str()); /** @@ -145,12 +145,12 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef* node) { * units::UnitTable the factor of `FARADAY` will be `96485.30900000` */ - auto node_unit_name = node->get_node_name(); + auto node_unit_name = node.get_node_name(); auto unit1_factor = units_driver.table->get_unit(node_unit_name + "_unit1")->get_factor(); auto unit2_factor = units_driver.table->get_unit(node_unit_name + "_unit2")->get_factor(); auto unit_factor = unit1_factor / unit2_factor; auto double_value_ptr = std::make_shared(ast::Double(unit_factor)); - node->set_value(std::move(double_value_ptr)); + node.set_value(std::move(double_value_ptr)); } } diff --git a/src/nmodl/visitors/units_visitor.hpp b/src/nmodl/visitors/units_visitor.hpp index 942162adc5..68bb3de877 100644 --- a/src/nmodl/visitors/units_visitor.hpp +++ b/src/nmodl/visitors/units_visitor.hpp @@ -69,19 +69,19 @@ class UnitsVisitor: public AstVisitor { /// Function to visit all the ast::UnitDef nodes and parse the units defined as /// ast::UnitDef in the UNITS block of mod files - void visit_unit_def(ast::UnitDef* node) override; + void visit_unit_def(ast::UnitDef& node) override; /// Function to visit all the ast::FactorDef nodes and parse the units defined /// as ast::FactorDef in the UNITS block of mod files - void visit_factor_def(ast::FactorDef* node) override; + void visit_factor_def(ast::FactorDef& node) override; /// Override visit_program function to parse the \c nrnunits.lib unit file /// before starting visiting the AST to parse the units defined in mod files - void visit_program(ast::Program* node) override; + void visit_program(ast::Program& node) override; /// Get the parser::UnitDriver to be able to use it outside the visitor::UnitsVisitor /// scope keeping the same units::UnitTable - parser::UnitDriver get_unit_driver() { + parser::UnitDriver get_unit_driver() const noexcept { return units_driver; } }; diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 553a6b52fd..eadb8a7cb7 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -14,17 +14,17 @@ namespace nmodl { namespace visitor { /// rename matching variable -void VarUsageVisitor::visit_name(ast::Name* node) { - std::string name = node->get_node_name(); +void VarUsageVisitor::visit_name(ast::Name& node) { + const auto& name = node.get_node_name(); if (name == var_name) { used = true; } } -bool VarUsageVisitor::variable_used(ast::Node* node, std::string name) { +bool VarUsageVisitor::variable_used(ast::Node& node, std::string name) { used = false; var_name = std::move(name); - node->visit_children(*this); + node.visit_children(*this); return used; } diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index a5574b0b32..7d433f5fc5 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -42,9 +42,9 @@ class VarUsageVisitor: public AstVisitor { public: VarUsageVisitor() = default; - bool variable_used(ast::Node* node, std::string name); + bool variable_used(ast::Node& node, std::string name); - virtual void visit_name(ast::Name* node) override; + void visit_name(ast::Name& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 0775bf28d2..0138ec6ced 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -7,17 +7,18 @@ #include "visitors/verbatim_var_rename_visitor.hpp" #include "parser/c11_driver.hpp" +#include "src/utils/logger.hpp" namespace nmodl { namespace visitor { -void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) { - if (node->get_statements().empty()) { +void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock& node) { + if (node.get_statements().empty()) { return; } - auto current_symtab = node->get_symbol_table(); + auto current_symtab = node.get_symbol_table(); if (current_symtab != nullptr) { symtab = current_symtab; } @@ -28,7 +29,7 @@ void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) symtab_stack.push(symtab); // first need to process all children : perform recursively from innermost block - for (const auto& item: node->get_statements()) { + for (const auto& item: node.get_statements()) { item->accept(*this); } @@ -44,7 +45,7 @@ void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock* node) * defined in the nmodl blocks. If so, return "original" name of the * variable. */ -std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { +std::string VerbatimVarRenameVisitor::rename_variable(const std::string& name) { bool rename_plausible = false; auto new_name = name; if (name.find(LOCAL_PREFIX) == 0) { @@ -60,7 +61,7 @@ std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { if (symbol != nullptr) { return new_name; } - std::cerr << "Warning : could not find " << name << " definition in nmodl" << std::endl; + logger->warn("could not find {} definition in nmodl", name); } return name; } @@ -69,8 +70,8 @@ std::string VerbatimVarRenameVisitor::rename_variable(std::string name) { /** * Parse verbatim blocks and rename variables used */ -void VerbatimVarRenameVisitor::visit_verbatim(ast::Verbatim* node) { - auto statement = node->get_statement(); +void VerbatimVarRenameVisitor::visit_verbatim(ast::Verbatim& node) { + const auto& statement = node.get_statement(); auto text = statement->eval(); parser::CDriver driver; diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index 217a9672b1..37188cefa4 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -58,13 +58,13 @@ class VerbatimVarRenameVisitor: public AstVisitor { /// prefix used for range variables const std::string RANGE_PREFIX = "_p_"; - std::string rename_variable(std::string); + std::string rename_variable(const std::string& name); public: VerbatimVarRenameVisitor() = default; - virtual void visit_verbatim(ast::Verbatim* node) override; - virtual void visit_statement_block(ast::StatementBlock* node) override; + void visit_verbatim(ast::Verbatim& node) override; + void visit_statement_block(ast::StatementBlock& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 4204bf60db..5c7e103c55 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -12,16 +12,14 @@ namespace nmodl { namespace visitor { -void VerbatimVisitor::visit_verbatim(ast::Verbatim* node) { +void VerbatimVisitor::visit_verbatim(ast::Verbatim& node) { std::string block; - auto statement = node->get_statement(); + const auto& statement = node.get_statement(); if (statement) { block = statement->eval(); } if (!block.empty() && verbose) { - std::cout << "BLOCK START"; - std::cout << block; - std::cout << "\nBLOCK END \n\n"; + std::cout << "BLOCK START" << block << "\nBLOCK END \n\n"; } blocks.push_back(block); diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index c57527b1b5..a488bfc174 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -46,13 +46,13 @@ class VerbatimVisitor: public AstVisitor { public: VerbatimVisitor() = default; - VerbatimVisitor(bool flag) { + explicit VerbatimVisitor(bool flag) { verbose = flag; } - void visit_verbatim(ast::Verbatim* node) override; + void visit_verbatim(ast::Verbatim& node) override; - std::vector verbatim_blocks() { + const std::vector& verbatim_blocks() const noexcept { return blocks; } }; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index e0cfda98e6..33fd046686 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -34,8 +34,8 @@ std::string get_new_name(const std::string& name, return (name + "_" + suffix + "_" + std::to_string(counter)); } -std::shared_ptr get_local_list_statement(const StatementBlock* node) { - const auto& statements = node->get_statements(); +std::shared_ptr get_local_list_statement(const StatementBlock& node) { + const auto& statements = node.get_statements(); for (const auto& statement: statements) { if (statement->is_local_list_statement()) { return std::static_pointer_cast(statement); @@ -44,19 +44,19 @@ std::shared_ptr get_local_list_statement(const Statemen return nullptr; } -void add_local_statement(StatementBlock* node) { +void add_local_statement(StatementBlock& node) { auto variables = get_local_list_statement(node); - const auto& statements = node->get_statements(); + const auto& statements = node.get_statements(); if (variables == nullptr) { auto statement = std::make_shared(LocalVarVector()); - node->insert_statement(statements.begin(), statement); + node.insert_statement(statements.begin(), statement); } } -LocalVar* add_local_variable(StatementBlock* node, Identifier* varname) { +LocalVar* add_local_variable(StatementBlock& node, Identifier* varname) { add_local_statement(node); - const ast::StatementVector& statements = node->get_statements(); + const ast::StatementVector& statements = node.get_statements(); auto local_list_statement = get_local_list_statement(node); /// each block should already have local statement @@ -69,12 +69,12 @@ LocalVar* add_local_variable(StatementBlock* node, Identifier* varname) { return var.get(); } -LocalVar* add_local_variable(StatementBlock* node, const std::string& varname) { +LocalVar* add_local_variable(StatementBlock& node, const std::string& varname) { auto name = new Name(new String(varname)); return add_local_variable(node, name); } -LocalVar* add_local_variable(StatementBlock* node, const std::string& varname, int dim) { +LocalVar* add_local_variable(StatementBlock& node, const std::string& varname, int dim) { auto name = new IndexedName(new Name(new String(varname)), new Integer(dim, nullptr)); return add_local_variable(node, name); } @@ -122,9 +122,9 @@ std::shared_ptr create_statement_block( } -void remove_statements_from_block(ast::StatementBlock* block, - const std::set statements) { - const auto& statement_vec = block->get_statements(); +void remove_statements_from_block(ast::StatementBlock& block, + const std::set& statements) { + const auto& statement_vec = block.get_statements(); // loosely following the cpp reference of remove_if @@ -134,19 +134,19 @@ void remove_statements_from_block(ast::StatementBlock* block, while (first != last) { if (statements.find(first->get()) == statements.end()) { - block->reset_statement(result, *first); + block.reset_statement(result, *first); ++result; } ++first; } - block->erase_statement(result, last); + block.erase_statement(result, last); } -std::set get_global_vars(Program* node) { +std::set get_global_vars(const Program& node) { std::set vars; - if (auto* symtab = node->get_symbol_table()) { + if (auto* symtab = node.get_symbol_table()) { // NB: local_var included here as locals can be declared at global scope NmodlType property = NmodlType::global_var | NmodlType::local_var | NmodlType::range_var | NmodlType::param_assign | NmodlType::extern_var | @@ -168,7 +168,7 @@ std::set get_global_vars(Program* node) { } -bool calls_function(ast::Ast* node, const std::string& name) { +bool calls_function(ast::Ast& node, const std::string& name) { AstLookupVisitor lv(ast::AstNodeType::FUNCTION_CALL); for (const auto& f: lv.lookup(node)) { if (std::dynamic_pointer_cast(f)->get_node_name() == name) { @@ -181,21 +181,21 @@ bool calls_function(ast::Ast* node, const std::string& name) { } // namespace visitor -std::string to_nmodl(ast::Ast* node, const std::set& exclude_types) { +std::string to_nmodl(ast::Ast& node, const std::set& exclude_types) { std::stringstream stream; visitor::NmodlPrintVisitor v(stream, exclude_types); - node->accept(v); + node.accept(v); return stream.str(); } -std::string to_json(ast::Ast* node, bool compact, bool expand, bool add_nmodl) { +std::string to_json(ast::Ast& node, bool compact, bool expand, bool add_nmodl) { std::stringstream stream; visitor::JSONVisitor v(stream); v.compact_json(compact); v.add_nmodl(add_nmodl); v.expand_keys(expand); - node->accept(v); + node.accept(v); v.flush(); return stream.str(); } diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index af65441321..bb7ec50f4f 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -29,17 +29,17 @@ std::string get_new_name(const std::string& name, /// Return pointer to local statement in the given block, otherwise nullptr -std::shared_ptr get_local_list_statement(const ast::StatementBlock* node); +std::shared_ptr get_local_list_statement(const ast::StatementBlock& node); /// Add empty local statement to given block if already doesn't exist -void add_local_statement(ast::StatementBlock* node); +void add_local_statement(ast::StatementBlock& node); /// Add new local variable to the block -ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname); -ast::LocalVar* add_local_variable(ast::StatementBlock* node, ast::Identifier* varname); -ast::LocalVar* add_local_variable(ast::StatementBlock* node, const std::string& varname, int dim); +ast::LocalVar* add_local_variable(ast::StatementBlock& node, const std::string& varname); +ast::LocalVar* add_local_variable(ast::StatementBlock& node, ast::Identifier* varname); +ast::LocalVar* add_local_variable(ast::StatementBlock& node, const std::string& varname, int dim); /// Create ast statement node from given code in string format @@ -52,26 +52,33 @@ std::shared_ptr create_statement_block( /// Remove statements from given statement block if they exist -void remove_statements_from_block(ast::StatementBlock* block, - const std::set statements); +void remove_statements_from_block(ast::StatementBlock& block, + const std::set& statements); /// Return set of strings with the names of all global variables -std::set get_global_vars(ast::Program* node); +std::set get_global_vars(const ast::Program& node); /// Checks whether block contains a call to a perticular function -bool calls_function(ast::Ast* node, const std::string& name); +bool calls_function(ast::Ast& node, const std::string& name); } // namespace visitor /// Given AST node, return the NMODL string representation -std::string to_nmodl(ast::Ast* node, const std::set& exclude_types = {}); +std::string to_nmodl(ast::Ast& node, const std::set& exclude_types = {}); +/// Given a shared pointer to an AST node, return the NMODL string representation +template +typename std::enable_if::value, std::string>::type to_nmodl( + const std::shared_ptr& node, + const std::set& exclude_types = {}) { + return to_nmodl(*node, exclude_types); +} /// Given AST node, return the JSON string representation -std::string to_json(ast::Ast* node, +std::string to_json(ast::Ast& node, bool compact = false, bool expand = false, bool add_nmodl = false); diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index 38e95e0426..e0a6311a81 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -210,10 +210,9 @@ void parse_neuron_block_string(const std::string& name, nmodl::ModToken& value) nmodl::parser::NmodlDriver driver; driver.parse_string(name); - auto ast_program = driver.get_ast(); - std::vector> neuron_blocks = - AstLookupVisitor().lookup(ast_program->get_shared_ptr().get(), - nmodl::ast::AstNodeType::NEURON_BLOCK); + const auto& ast_program = driver.get_ast(); + const auto& neuron_blocks = AstLookupVisitor().lookup(*ast_program->get_shared_ptr(), + nmodl::ast::AstNodeType::NEURON_BLOCK); value = *(neuron_blocks[0]->get_token()); } diff --git a/test/nmodl/transpiler/visitor/constant_folder.cpp b/test/nmodl/transpiler/visitor/constant_folder.cpp index 9e94828fc0..2cd5e2cf5a 100644 --- a/test/nmodl/transpiler/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/visitor/constant_folder.cpp @@ -27,16 +27,16 @@ using nmodl::parser::NmodlDriver; std::string run_constant_folding_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/visitor/defuse_analyze.cpp index db40fdd287..f0ddb9b5d5 100644 --- a/test/nmodl/transpiler/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/visitor/defuse_analyze.cpp @@ -30,23 +30,23 @@ using nmodl::parser::NmodlDriver; std::vector run_defuse_visitor(const std::string& text, const std::string& variable) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - InlineVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); + InlineVisitor().visit_program(*ast); std::vector chains; DefUseAnalyzeVisitor v(ast->get_symbol_table()); /// analyse only derivative blocks in this test - auto blocks = AstLookupVisitor().lookup(ast.get(), AstNodeType::DERIVATIVE_BLOCK); + auto blocks = AstLookupVisitor().lookup(*ast, AstNodeType::DERIVATIVE_BLOCK); for (auto& block: blocks) { auto node = block.get(); chains.push_back(v.analyze(node, variable)); } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return chains; } diff --git a/test/nmodl/transpiler/visitor/inline.cpp b/test/nmodl/transpiler/visitor/inline.cpp index 490a220f6b..b031251d40 100644 --- a/test/nmodl/transpiler/visitor/inline.cpp +++ b/test/nmodl/transpiler/visitor/inline.cpp @@ -28,16 +28,16 @@ using nmodl::parser::NmodlDriver; std::string run_inline_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - InlineVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); + InlineVisitor().visit_program(*ast); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/json.cpp b/test/nmodl/transpiler/visitor/json.cpp index e9e2f98a0a..b60a30a3d3 100644 --- a/test/nmodl/transpiler/visitor/json.cpp +++ b/test/nmodl/transpiler/visitor/json.cpp @@ -26,7 +26,7 @@ std::string run_json_visitor(const std::string& text, bool compact = false) { NmodlDriver driver; auto ast = driver.parse_string(text); - return to_json(ast.get(), compact); + return to_json(*ast, compact); } TEST_CASE("Convert NMODL to AST to JSON form using JSONVisitor", "[visitor][json]") { diff --git a/test/nmodl/transpiler/visitor/kinetic_block.cpp b/test/nmodl/transpiler/visitor/kinetic_block.cpp index 494e472fd9..e1787e5e2c 100644 --- a/test/nmodl/transpiler/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/visitor/kinetic_block.cpp @@ -33,44 +33,45 @@ std::vector run_kinetic_block_visitor(const std::string& text) { // construct AST from text including KINETIC block(s) NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); // unroll loops and fold constants - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(*ast); + LoopUnrollVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); // run KineticBlock visitor on AST - KineticBlockVisitor().visit_program(ast.get()); + KineticBlockVisitor().visit_program(*ast); // run lookup visitor to extract DERIVATIVE block(s) from AST AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(ast.get(), AstNodeType::DERIVATIVE_BLOCK); + auto res = v_lookup.lookup(*ast, AstNodeType::DERIVATIVE_BLOCK); + results.reserve(res.size()); for (const auto& r: res) { - results.push_back(to_nmodl(r.get())); + results.push_back(to_nmodl(r)); } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return results; } SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][visitor]") { GIVEN("KINETIC block with << reaction statement, 1 state var") { - std::string input_nmodl_text = R"( + static const std::string input_nmodl_text = R"( STATE { x } KINETIC states { ~ x << (a*c/3.2) })"; - std::string output_nmodl_text = R"( + static const std::string output_nmodl_text = R"( DERIVATIVE states { x' = (a*c/3.2) })"; diff --git a/test/nmodl/transpiler/visitor/localize.cpp b/test/nmodl/transpiler/visitor/localize.cpp index 41f31ea2f9..8ba4f9911a 100644 --- a/test/nmodl/transpiler/visitor/localize.cpp +++ b/test/nmodl/transpiler/visitor/localize.cpp @@ -28,16 +28,16 @@ using nmodl::parser::NmodlDriver; std::string run_localize_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - InlineVisitor().visit_program(ast.get()); - LocalizeVisitor().visit_program(ast.get()); + const auto& ast = driver.parse_string(text); + SymtabVisitor().visit_program(*ast); + InlineVisitor().visit_program(*ast); + LocalizeVisitor().visit_program(*ast); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } @@ -45,7 +45,7 @@ std::string run_localize_visitor(const std::string& text) { SCENARIO("Localizer test with single global block", "[visitor][localizer]") { GIVEN("Single derivative block with variable definition") { - std::string nmodl_text = R"( + static const std::string nmodl_text = R"( NEURON { RANGE tau } @@ -56,7 +56,7 @@ SCENARIO("Localizer test with single global block", "[visitor][localizer]") { } )"; - std::string output_nmodl = R"( + static const std::string output_nmodl = R"( NEURON { RANGE tau } @@ -69,7 +69,7 @@ SCENARIO("Localizer test with single global block", "[visitor][localizer]") { )"; THEN("tau variable gets localized") { - std::string input = reindent_text(nmodl_text); + const std::string input = reindent_text(nmodl_text); auto expected_result = reindent_text(output_nmodl); auto result = run_localize_visitor(input); REQUIRE(result == expected_result); @@ -79,7 +79,7 @@ SCENARIO("Localizer test with single global block", "[visitor][localizer]") { SCENARIO("Localizer test with use of verbatim block", "[visitor][localizer]") { GIVEN("Verbatim block usage in one of the global block") { - std::string nmodl_text = R"( + static const std::string nmodl_text = R"( NEURON { RANGE tau } @@ -94,7 +94,7 @@ SCENARIO("Localizer test with use of verbatim block", "[visitor][localizer]") { } )"; - std::string output_nmodl = R"( + static const std::string output_nmodl = R"( NEURON { RANGE tau } @@ -110,7 +110,7 @@ SCENARIO("Localizer test with use of verbatim block", "[visitor][localizer]") { )"; THEN("Localization is disabled") { - std::string input = reindent_text(nmodl_text); + static const std::string input = reindent_text(nmodl_text); auto expected_result = reindent_text(output_nmodl); auto result = run_localize_visitor(input); REQUIRE(result == expected_result); @@ -121,7 +121,7 @@ SCENARIO("Localizer test with use of verbatim block", "[visitor][localizer]") { SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { GIVEN("Multiple global blocks with definition of variable") { - std::string nmodl_text = R"( + static const std::string nmodl_text = R"( NEURON { RANGE tau, beta } @@ -146,7 +146,7 @@ SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { } )"; - std::string output_nmodl = R"( + static const std::string output_nmodl = R"( NEURON { RANGE tau, beta } @@ -173,7 +173,7 @@ SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { )"; THEN("Localization across multiple blocks is done") { - std::string input = reindent_text(nmodl_text); + const std::string input = reindent_text(nmodl_text); auto expected_result = reindent_text(output_nmodl); auto result = run_localize_visitor(input); REQUIRE(result == expected_result); @@ -182,7 +182,7 @@ SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { GIVEN("Two global blocks with definition and use of the variable") { - std::string nmodl_text = R"( + static const std::string nmodl_text = R"( NEURON { RANGE tau } @@ -201,7 +201,7 @@ SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { } )"; - std::string output_nmodl = R"( + static const std::string output_nmodl = R"( NEURON { RANGE tau } @@ -220,7 +220,7 @@ SCENARIO("Localizer test with multiple global blocks", "[visitor][localizer]") { )"; THEN("Localization is not done due to use of variable") { - std::string input = reindent_text(nmodl_text); + const std::string input = reindent_text(nmodl_text); auto expected_result = reindent_text(output_nmodl); auto result = run_localize_visitor(input); REQUIRE(result == expected_result); diff --git a/test/nmodl/transpiler/visitor/lookup.cpp b/test/nmodl/transpiler/visitor/lookup.cpp index 0900ca9ea0..7bda85cb52 100644 --- a/test/nmodl/transpiler/visitor/lookup.cpp +++ b/test/nmodl/transpiler/visitor/lookup.cpp @@ -26,8 +26,8 @@ using symtab::syminfo::NmodlType; // Ast lookup visitor tests //============================================================================= -std::vector> run_lookup_visitor(ast::Program* node, - std::vector& types) { +std::vector> run_lookup_visitor(ast::Program& node, + const std::vector& types) { return AstLookupVisitor().lookup(node, types); } @@ -57,10 +57,10 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") WHEN("Looking for existing nodes") { THEN("Can find RANGE variables") { std::vector types{AstNodeType::RANGE_VAR}; - auto result = run_lookup_visitor(ast.get(), types); + auto result = run_lookup_visitor(*ast, types); REQUIRE(result.size() == 2); - REQUIRE(to_nmodl(result[0].get()) == "tau"); - REQUIRE(to_nmodl(result[1].get()) == "h"); + REQUIRE(to_nmodl(result[0]) == "tau"); + REQUIRE(to_nmodl(result[1]) == "h"); } THEN("Can find NEURON block") { @@ -73,7 +73,7 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") NEURON { RANGE tau, h })"; - auto result = reindent_text(to_nmodl(nodes[0].get())); + auto result = reindent_text(to_nmodl(nodes[0])); auto expected = reindent_text(neuron_block); REQUIRE(result == expected); } @@ -81,7 +81,7 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") THEN("Can find Binary Expressions and function call") { std::vector types{AstNodeType::BINARY_EXPRESSION, AstNodeType::FUNCTION_CALL}; - auto result = run_lookup_visitor(ast.get(), types); + auto result = run_lookup_visitor(*ast, types); REQUIRE(result.size() == 4); } } @@ -89,8 +89,8 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") WHEN("Looking for missing nodes") { THEN("Can not find BREAKPOINT block") { std::vector types{AstNodeType::BREAKPOINT_BLOCK}; - auto result = run_lookup_visitor(ast.get(), types); - REQUIRE(result.size() == 0); + auto result = run_lookup_visitor(*ast, types); + REQUIRE(result.empty()); } } } diff --git a/test/nmodl/transpiler/visitor/loop_unroll.cpp b/test/nmodl/transpiler/visitor/loop_unroll.cpp index b9aa5ddf6a..e97f27cd1a 100644 --- a/test/nmodl/transpiler/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/visitor/loop_unroll.cpp @@ -30,17 +30,17 @@ using nmodl::parser::NmodlDriver; std::string run_loop_unroll_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); + LoopUnrollVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); - return to_nmodl(ast.get(), {AstNodeType::DEFINE}); + return to_nmodl(ast, {AstNodeType::DEFINE}); } SCENARIO("Perform loop unrolling of FROM construct", "[visitor][unroll]") { diff --git a/test/nmodl/transpiler/visitor/misc.cpp b/test/nmodl/transpiler/visitor/misc.cpp index 10cd7461ec..56f05f0f30 100644 --- a/test/nmodl/transpiler/visitor/misc.cpp +++ b/test/nmodl/transpiler/visitor/misc.cpp @@ -28,24 +28,24 @@ using nmodl::parser::NmodlDriver; void run_visitor_passes(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); { SymtabVisitor v1; InlineVisitor v2; LocalizeVisitor v3; CheckParentVisitor v4(true); - v1.visit_program(ast.get()); - v2.visit_program(ast.get()); - v3.visit_program(ast.get()); - v4.visit_program(ast.get()); - v4.visit_program(ast.get()); - v1.visit_program(ast.get()); - v1.visit_program(ast.get()); - v4.visit_program(ast.get()); - v2.visit_program(ast.get()); - v3.visit_program(ast.get()); - v2.visit_program(ast.get()); - v4.visit_program(ast.get()); + v1.visit_program(*ast); + v2.visit_program(*ast); + v3.visit_program(*ast); + v4.visit_program(*ast); + v4.visit_program(*ast); + v1.visit_program(*ast); + v1.visit_program(*ast); + v4.visit_program(*ast); + v2.visit_program(*ast); + v3.visit_program(*ast); + v2.visit_program(*ast); + v4.visit_program(*ast); } } @@ -76,7 +76,7 @@ SCENARIO("Running visitor passes multiple times", "[visitor]") { SCENARIO("Sympy specific AST to NMODL conversion") { GIVEN("NMODL block with unit usage") { - std::string nmodl = R"( + static const std::string nmodl = R"( BREAKPOINT { Pf_NMDA = (1/1.38) * 120 (mM) * 0.6 VDCC = gca_bar_VDCC * 4(um2)*PI*3(1/um3) @@ -84,7 +84,7 @@ SCENARIO("Sympy specific AST to NMODL conversion") { } )"; - std::string expected = R"( + static const std::string expected = R"( BREAKPOINT { Pf_NMDA = (1/1.38)*120*0.6 VDCC = gca_bar_VDCC*4*PI*3 @@ -95,8 +95,8 @@ SCENARIO("Sympy specific AST to NMODL conversion") { THEN("to_nmodl can ignore all units") { auto input = reindent_text(nmodl); NmodlDriver driver; - auto ast = driver.parse_string(input); - auto result = to_nmodl(ast.get(), {AstNodeType::UNIT}); + const auto& ast = driver.parse_string(input); + const auto& result = to_nmodl(ast, {AstNodeType::UNIT}); REQUIRE(result == reindent_text(expected)); } } diff --git a/test/nmodl/transpiler/visitor/neuron_solve.cpp b/test/nmodl/transpiler/visitor/neuron_solve.cpp index dfd408ba73..37eab38c41 100644 --- a/test/nmodl/transpiler/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/visitor/neuron_solve.cpp @@ -29,15 +29,15 @@ using nmodl::parser::NmodlDriver; std::string run_cnexp_solve_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - NeuronSolveVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); + NeuronSolveVisitor().visit_program(*ast); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/nmodl.cpp b/test/nmodl/transpiler/visitor/nmodl.cpp index d24e3ced2f..57f9dbc52e 100644 --- a/test/nmodl/transpiler/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/visitor/nmodl.cpp @@ -27,13 +27,13 @@ using nmodl::parser::NmodlDriver; std::string run_nmodl_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/perf.cpp b/test/nmodl/transpiler/visitor/perf.cpp index 41f36a6ca7..d94aee0460 100644 --- a/test/nmodl/transpiler/visitor/perf.cpp +++ b/test/nmodl/transpiler/visitor/perf.cpp @@ -82,11 +82,11 @@ SCENARIO("Symbol table generation with Perf stat visitor", "[visitor][performanc )"; NmodlDriver driver; - auto ast = driver.parse_string(nmodl_text); + const auto& ast = driver.parse_string(nmodl_text); WHEN("Symbol table generator pass runs") { SymtabVisitor v; - v.visit_program(ast.get()); + v.visit_program(*ast); auto symtab = ast->get_model_symbol_table(); THEN("Can lookup for defined variables") { @@ -111,7 +111,7 @@ SCENARIO("Symbol table generation with Perf stat visitor", "[visitor][performanc WHEN("Perf visitor pass runs after symtab visitor") { PerfVisitor v; - v.visit_program(ast.get()); + v.visit_program(*ast); auto result = v.get_total_perfstat(); auto num_instance_var = v.get_instance_variable_count(); @@ -151,7 +151,7 @@ SCENARIO("Symbol table generation with Perf stat visitor", "[visitor][performanc WHEN("Perf visitor pass runs before symtab visitor") { PerfVisitor v; THEN("exception is thrown") { - REQUIRE_THROWS_WITH(v.visit_program(ast.get()), Catch::Contains("table not setup")); + REQUIRE_THROWS_WITH(v.visit_program(*ast), Catch::Contains("table not setup")); } } } diff --git a/test/nmodl/transpiler/visitor/rename.cpp b/test/nmodl/transpiler/visitor/rename.cpp index 20785dce6b..96abbb94ab 100644 --- a/test/nmodl/transpiler/visitor/rename.cpp +++ b/test/nmodl/transpiler/visitor/rename.cpp @@ -28,18 +28,19 @@ using nmodl::parser::NmodlDriver; // Variable rename tests //============================================================================= -std::string run_var_rename_visitor(const std::string& text, - std::vector> variables) { +static std::string run_var_rename_visitor( + const std::string& text, + const std::vector>& variables) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); for (const auto& variable: variables) { - RenameVisitor(variable.first, variable.second).visit_program(ast.get()); + RenameVisitor(variable.first, variable.second).visit_program(*ast); } std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } @@ -136,14 +137,14 @@ SCENARIO("Renaming any variable in mod file with RenameVisitor", "[visitor][rena std::string run_local_var_rename_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); - VerbatimVarRenameVisitor().visit_program(ast.get()); - LocalVarRenameVisitor().visit_program(ast.get()); + VerbatimVarRenameVisitor().visit_program(*ast); + LocalVarRenameVisitor().visit_program(*ast); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/solve_block.cpp b/test/nmodl/transpiler/visitor/solve_block.cpp index 723cb2be18..3b62d49eab 100644 --- a/test/nmodl/transpiler/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/visitor/solve_block.cpp @@ -29,15 +29,15 @@ using nmodl::parser::NmodlDriver; std::string run_solve_block_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); - SymtabVisitor().visit_program(ast.get()); - NeuronSolveVisitor().visit_program(ast.get()); - SolveBlockVisitor().visit_program(ast.get()); + const auto& ast = driver.parse_string(text); + SymtabVisitor().visit_program(*ast); + NeuronSolveVisitor().visit_program(*ast); + SolveBlockVisitor().visit_program(*ast); std::stringstream stream; - NmodlPrintVisitor(stream).visit_program(ast.get()); + NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/visitor/steadystate.cpp b/test/nmodl/transpiler/visitor/steadystate.cpp index 2d3f17138d..bc79bb468d 100644 --- a/test/nmodl/transpiler/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/visitor/steadystate.cpp @@ -34,38 +34,39 @@ using nmodl::parser::NmodlDriver; std::vector run_steadystate_visitor( const std::string& text, - std::vector ret_nodetypes = {AstNodeType::SOLVE_BLOCK, - AstNodeType::DERIVATIVE_BLOCK}) { + const std::vector& ret_nodetypes = {AstNodeType::SOLVE_BLOCK, + AstNodeType::DERIVATIVE_BLOCK}) { std::vector results; // construct AST from text NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); // unroll loops and fold constants - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(*ast); + LoopUnrollVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); // Run kinetic block visitor first, so any kinetic blocks // are converted into derivative blocks - KineticBlockVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); + KineticBlockVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); // run SteadystateVisitor on AST - SteadystateVisitor().visit_program(ast.get()); + SteadystateVisitor().visit_program(*ast); // run lookup visitor to extract results from AST - auto res = AstLookupVisitor().lookup(ast.get(), ret_nodetypes); + auto res = AstLookupVisitor().lookup(*ast, ret_nodetypes); + results.reserve(res.size()); for (const auto& r: res) { - results.push_back(to_nmodl(r.get())); + results.push_back(to_nmodl(r)); } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return results; } diff --git a/test/nmodl/transpiler/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/visitor/sympy_conductance.cpp index b469665c72..5bac487667 100644 --- a/test/nmodl/transpiler/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/visitor/sympy_conductance.cpp @@ -33,46 +33,44 @@ using nmodl::parser::NmodlDriver; std::string run_sympy_conductance_visitor(const std::string& text) { // construct AST from text NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); // construct symbol table from AST - SymtabVisitor(false).visit_program(ast.get()); + SymtabVisitor(false).visit_program(*ast); // run constant folding, inlining & local renaming first - ConstantFolderVisitor().visit_program(ast.get()); - InlineVisitor().visit_program(ast.get()); - LocalVarRenameVisitor().visit_program(ast.get()); - SymtabVisitor(true).visit_program(ast.get()); + ConstantFolderVisitor().visit_program(*ast); + InlineVisitor().visit_program(*ast); + LocalVarRenameVisitor().visit_program(*ast); + SymtabVisitor(true).visit_program(*ast); // run SympyConductance on AST - SympyConductanceVisitor().visit_program(ast.get()); + SympyConductanceVisitor().visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; // return BREAKPOINT block as JSON string - return reindent_text( - to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); + return reindent_text(to_nmodl(v_lookup.lookup(*ast, AstNodeType::BREAKPOINT_BLOCK)[0])); } std::string breakpoint_to_nmodl(const std::string& text) { // construct AST from text NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; // return BREAKPOINT block as JSON string - return reindent_text( - to_nmodl(v_lookup.lookup(ast.get(), AstNodeType::BREAKPOINT_BLOCK)[0].get())); + return reindent_text(to_nmodl(v_lookup.lookup(*ast, AstNodeType::BREAKPOINT_BLOCK)[0])); } -void run_sympy_conductance_passes(ast::Program* node) { +void run_sympy_conductance_passes(ast::Program& node) { // construct symbol table from AST SymtabVisitor v_symtab; v_symtab.visit_program(node); diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/visitor/sympy_solver.cpp index acadca6845..8335962357 100644 --- a/test/nmodl/transpiler/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/visitor/sympy_solver.cpp @@ -39,34 +39,34 @@ std::vector run_sympy_solver_visitor( // construct AST from text NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); // unroll loops and fold constants - ConstantFolderVisitor().visit_program(ast.get()); - LoopUnrollVisitor().visit_program(ast.get()); - ConstantFolderVisitor().visit_program(ast.get()); - SymtabVisitor().visit_program(ast.get()); + ConstantFolderVisitor().visit_program(*ast); + LoopUnrollVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); // run SympySolver on AST - SympySolverVisitor(pade, cse).visit_program(ast.get()); + SympySolverVisitor(pade, cse).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); // run lookup visitor to extract results from AST AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(ast.get(), ret_nodetype); + auto res = v_lookup.lookup(*ast, ret_nodetype); for (const auto& r: res) { - results.push_back(to_nmodl(r.get())); + results.push_back(to_nmodl(r)); } return results; } -void run_sympy_visitor_passes(ast::Program* node) { +void run_sympy_visitor_passes(ast::Program& node) { // construct symbol table from AST SymtabVisitor v_symtab; v_symtab.visit_program(node); @@ -83,7 +83,7 @@ void run_sympy_visitor_passes(ast::Program* node) { v_sympy2.visit_program(node); } -std::string ast_to_string(ast::Program* node) { +std::string ast_to_string(ast::Program& node) { std::stringstream stream; NmodlPrintVisitor(stream).visit_program(node); return stream.str(); @@ -323,16 +323,16 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", auto ast = driver.parse_string(nmodl_text); // construct symbol table from AST - SymtabVisitor().visit_program(ast.get()); + SymtabVisitor().visit_program(*ast); // run SympySolver on AST - SympySolverVisitor().visit_program(ast.get()); + SympySolverVisitor().visit_program(*ast); - std::string AST_string = ast_to_string(ast.get()); + std::string AST_string = ast_to_string(*ast); THEN("More SympySolver passes do nothing to the AST and don't throw") { - REQUIRE_NOTHROW(run_sympy_visitor_passes(ast.get())); - REQUIRE(AST_string == ast_to_string(ast.get())); + REQUIRE_NOTHROW(run_sympy_visitor_passes(*ast)); + REQUIRE(AST_string == ast_to_string(*ast)); } } } diff --git a/test/nmodl/transpiler/visitor/units.cpp b/test/nmodl/transpiler/visitor/units.cpp index ded99209fb..3bec45029d 100644 --- a/test/nmodl/transpiler/visitor/units.cpp +++ b/test/nmodl/transpiler/visitor/units.cpp @@ -32,13 +32,13 @@ using nmodl::parser::NmodlDriver; std::string run_units_visitor(const std::string& text) { NmodlDriver driver; driver.parse_string(text); - auto ast = driver.get_ast(); + const auto& ast = driver.get_ast(); // Parse nrnunits.lib file and the UNITS block of the mod file - std::string units_lib_path(NrnUnitsLib::get_path()); + const std::string units_lib_path(NrnUnitsLib::get_path()); UnitsVisitor units_visitor = UnitsVisitor(units_lib_path); - units_visitor.visit_program(ast.get()); + units_visitor.visit_program(*ast); // Keep the UnitTable created from parsing unit file and UNITS // block of the mod file @@ -50,14 +50,14 @@ std::string run_units_visitor(const std::string& text) { // Visit AST to find all the ast::UnitDef nodes to print their // unit names, factors and dimensions as they are calculated in // the units::UnitTable - auto unit_defs = AstLookupVisitor().lookup(ast.get(), ast::AstNodeType::UNIT_DEF); + auto unit_defs = AstLookupVisitor().lookup(*ast, ast::AstNodeType::UNIT_DEF); for (const auto& unit_def: unit_defs) { auto unit_name = unit_def->get_node_name(); unit_name.erase(remove_if(unit_name.begin(), unit_name.end(), isspace), unit_name.end()); auto unit = units_driver.table->get_unit(unit_name); - ss << std::fixed << std::setprecision(8) << unit->get_name() << " "; - ss << unit->get_factor() << ":"; + ss << std::fixed << std::setprecision(8) << unit->get_name() << ' '; + ss << unit->get_factor() << ':'; // Dimensions of the unit are printed to check that the units are successfully // parsed to the units::UnitTable int dimension_id = 0; @@ -65,7 +65,7 @@ std::string run_units_visitor(const std::string& text) { for (const auto& dimension: unit->get_dimensions()) { if (dimension != 0) { constant = false; - ss << " " << units_driver.table->get_base_unit_name(dimension_id); + ss << ' ' << units_driver.table->get_base_unit_name(dimension_id); ss << dimension; } dimension_id++; @@ -73,18 +73,18 @@ std::string run_units_visitor(const std::string& text) { if (constant) { ss << " constant"; } - ss << "\n"; + ss << '\n'; } // Visit AST to find all the ast::FactorDef nodes to print their // unit names, factors and dimensions as they are calculated to // be printed to the produced .cpp file - auto factor_defs = AstLookupVisitor().lookup(ast.get(), ast::AstNodeType::FACTOR_DEF); + auto factor_defs = AstLookupVisitor().lookup(*ast, ast::AstNodeType::FACTOR_DEF); for (const auto& factor_def: factor_defs) { auto unit = units_driver.table->get_unit(factor_def->get_node_name()); - ss << std::fixed << std::setprecision(8) << unit->get_name() << " "; + ss << std::fixed << std::setprecision(8) << unit->get_name() << ' '; auto factor_def_class = dynamic_cast(factor_def.get()); - ss << factor_def_class->get_value()->eval() << ":"; + ss << factor_def_class->get_value()->eval() << ':'; // Dimensions of the unit are printed to check that the units are successfully // parsed to the units::UnitTable int dimension_id = 0; @@ -100,11 +100,11 @@ std::string run_units_visitor(const std::string& text) { if (constant) { ss << " constant"; } - ss << "\n"; + ss << '\n'; } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return ss.str(); } @@ -112,7 +112,7 @@ std::string run_units_visitor(const std::string& text) { SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units]") { GIVEN("UNITS block with different cases of units definitions") { - std::string nmodl_text = R"( + static const std::string nmodl_text = R"( UNITS { (nA) = (nanoamp) (mA) = (milliamp) @@ -154,7 +154,7 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] } )"; - std::string output_nmodl = R"( + static const std::string output_nmodl = R"( nA 0.00000000: sec-1 coul1 mA 0.00100000: sec-1 coul1 mV 0.00100000: m2 kg1 sec-2 coul-1 @@ -195,7 +195,7 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] )"; THEN("Print the units that were added") { - std::string input = reindent_text(nmodl_text); + const std::string input(reindent_text(nmodl_text)); auto expected_result = reindent_text(output_nmodl); auto result = run_units_visitor(input); auto reindented_result = reindent_text(result); diff --git a/test/nmodl/transpiler/visitor/var_usage.cpp b/test/nmodl/transpiler/visitor/var_usage.cpp index 7f8146e2d9..8e56fddd1d 100644 --- a/test/nmodl/transpiler/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/visitor/var_usage.cpp @@ -18,8 +18,9 @@ using namespace visitor; // Variable usage visitor tests //============================================================================= -bool run_var_usage_visitor(std::shared_ptr node, const std::string& variable) { - return VarUsageVisitor().variable_used(node.get(), variable); +static bool run_var_usage_visitor(const std::shared_ptr& node, + const std::string& variable) { + return VarUsageVisitor().variable_used(*node, variable); } SCENARIO("Searching for variable name using VarUsageVisitor", "[visitor][var_usage]") { diff --git a/test/nmodl/transpiler/visitor/verbatim.cpp b/test/nmodl/transpiler/visitor/verbatim.cpp index 8b2c9646d7..1d36706150 100644 --- a/test/nmodl/transpiler/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/visitor/verbatim.cpp @@ -23,13 +23,13 @@ using nmodl::parser::NmodlDriver; std::vector run_verbatim_visitor(const std::string& text) { NmodlDriver driver; - auto ast = driver.parse_string(text); + const auto& ast = driver.parse_string(text); VerbatimVisitor v; - v.visit_program(ast.get()); + v.visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(ast.get()); + CheckParentVisitor().visit_program(*ast); return v.verbatim_blocks(); } From 84d4ac72ed9096bd60082a35c0f5cf0bcf0f7935 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 24 Apr 2020 14:32:01 +0200 Subject: [PATCH 256/871] Bugfixes for documentation build (issues BlueBrain/nmodl#305, 307) - m2r & sphinx>=3.0 are not compatible (more info in pr in progress in m2r: https://github.com/miyakogi/m2r/pull/55) - add path in conf.py to find the nmodl module - removed various doxygen warnings due to unacceptable keywords in configuration files - adjust checkparent_visitor sphinx autodoc /copybrief - reduce the maxium number of processors (freezed some workstations) - mistune downgrade due to conflicts with nbconvert Co-Authored-By: Omar Awile NMODL Repo SHA: BlueBrain/nmodl@622fe588028e2279c3814208a4547568512c3bec --- .travis.yml | 8 ++++- docs/nmodl/transpiler/DoxygenLayout.xml | 33 +------------------ docs/nmodl/transpiler/conf.py | 6 ++++ setup.py | 6 ++-- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/language/templates/ast/ast.hpp | 4 +-- .../visitors/checkparent_visitor.hpp | 2 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 2 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 2 +- 9 files changed, 23 insertions(+), 42 deletions(-) diff --git a/.travis.yml b/.travis.yml index cd4ea07be1..5f35e8fb78 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,8 @@ addons: - python3-pip - doxygen - pandoc + - texlive-base + - dvipng # for Mac builds, we use Homebrew homebrew: packages: @@ -73,11 +75,15 @@ before_install: # Install NMODL dependencies #============================================================================= install: + # there is a bug in m2r if sphinx>2.4.4. There is a pr in m2r to solve the + # issue here: https://github.com/miyakogi/m2r/pull/55 . When it is done + # I will remove the sphinx downgrade. Change setup.py requirements when it + # be the case, Katta - echo "------- Install Dependencies -------" - pip3 install -U pip setuptools - pip3 install Jinja2 PyYAML pytest sympy --user - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - pip3 install -U --user sphinx nbsphinx>=0.3.2 m2r sphinx-rtd-theme jupyter; + pip3 install -U --user sphinx==2.4.4 nbsphinx>=0.3.2 m2r sphinx-rtd-theme jupyter; fi #============================================================================= diff --git a/docs/nmodl/transpiler/DoxygenLayout.xml b/docs/nmodl/transpiler/DoxygenLayout.xml index 7ad780b56b..258ae9d3b0 100644 --- a/docs/nmodl/transpiler/DoxygenLayout.xml +++ b/docs/nmodl/transpiler/DoxygenLayout.xml @@ -1,5 +1,6 @@ + @@ -9,26 +10,12 @@ - - - - - - - - - - - - - - @@ -102,13 +89,8 @@ - - - - - @@ -117,8 +99,6 @@ - - @@ -135,16 +115,11 @@ - - - - - @@ -154,8 +129,6 @@ - - @@ -176,8 +149,6 @@ - - @@ -196,8 +167,6 @@ - - diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index 80522afaa5..e02ba1793d 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -17,11 +17,17 @@ # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # + + + import os import subprocess import sys import textwrap +# The project needs to be built before documentation in the usual build folder +sys.path.insert(0, os.path.abspath('../build/lib/python')) + import nmodl # isort:skip # Run doxygen diff --git a/setup.py b/setup.py index e769a4609f..bfae67e822 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,7 @@ def build_extension(self, ext): build_args += ["--", "/m"] else: cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg] - build_args += ["--", "-j{}".format(max(1, os.cpu_count() - 1))] + build_args += ["--", "-j{}".format(max(1, os.cpu_count() - 3))] env = os.environ.copy() env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format( @@ -147,7 +147,7 @@ def run(self): ) -install_requirements = ["jinja2>=2.9", "PyYAML>=3.13", "sympy>=1.3"] +install_requirements = ["jinja2>=2.9.3", "PyYAML>=3.13", "sympy>=1.3"] setup( name="NMODL", @@ -167,7 +167,7 @@ def run(self): buildhtml=get_sphinx_command, ), zip_safe=False, - setup_requires=["nbsphinx>=0.3.2", "m2r", "sphinx-rtd-theme", "sphinx>=2.0"] + setup_requires=["nbsphinx>=0.3.2", "mistune<2.0", "m2r", "sphinx-rtd-theme", "sphinx>=2.0", "sphinx<3.0"] + install_requirements, install_requires=install_requirements, tests_require=["pytest>=3.7.2"], diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index f12f6cfaeb..bba0df260d 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1785,7 +1785,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Find unique variable name defined in nmodl::utils::SingletonRandomString by the * nmodl::visitor::SympySolverVisitor * @param original_name Original name of variable to change - * @return std::string Unique name produced as _ + * @return std::string Unique name produced as [original_name]_[random_string] */ std::string find_var_unique_name(const std::string& original_name) const; diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index eeac94b0ad..cf033d18c7 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -333,7 +333,7 @@ struct Ast: public std::enable_shared_from_this { * returning a raw pointer may create less problems that the * shared_from_this from the parent. * - * \ref Check Ast::parent for more information + * Check \ref Ast::parent for more information */ virtual Ast* get_parent() const; @@ -345,7 +345,7 @@ struct Ast: public std::enable_shared_from_this { * we set children parents directly in the parent constructor using * set_parent_in_children() * - * \ref Check Ast::parent for more information + * Check \ref Ast::parent for more information */ virtual void set_parent(Ast* p); }; diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp index 76bf3c2f0e..57bfd65cf6 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -17,7 +17,7 @@ * \brief Auto generated visitors * * \file - * \brief \copybrief nmodl::visitor::CheckParentVisitor + * \brief \copybrief nmodl::visitor::test::CheckParentVisitor */ #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index e2ebfdeceb..13aa41d2ae 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -159,7 +159,7 @@ std::string SympySolverVisitor::suffix_random_string(const std::string& original auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); // Check if there is a variable defined in the mod file as original_string and if yes // try to use a different string for the matrices created by sympy in the form - // _ + // "original_string"_"random_string" while (vars.find(new_string) != vars.end()) { random_string = singleton_random_string_class->reset_random_string(original_string); new_string = original_string; diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index d26cc39c9d..fc0c0e994c 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -109,7 +109,7 @@ class SympySolverVisitor: public AstVisitor { const std::string& original_string, const std::string& substitution_string) const; - /// Return a std::string in the form _, where + /// Return a std::string in the form "original_string"_"random_string", where /// random_string is a string defined in the nmodl::utils::SingletonRandomString /// for the original_string std::string suffix_random_string(const std::string& original_string) const; From 3f092eba5d753a054721351f1dc81a57d65b8bd2 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 30 Apr 2020 22:12:49 +0200 Subject: [PATCH 257/871] Avoid including ast/ast.hpp in visitor class declarations. (BlueBrain/nmodl#310) * declarations only use Ast node forward declarations * do not inline virtual member functions and move the definitions in .cpp * Add const specifier to some member methods * Hide visit_* methods not supposed to be called directly Mark protected the visit_* member methods of the visitor classes providing a specific entry point. NMODL Repo SHA: BlueBrain/nmodl@584af62be9cd6f9017f63e2d9cb732e1da58d420 --- src/nmodl/codegen/codegen_c_visitor.cpp | 4 +- src/nmodl/language/templates/ast/ast_decl.hpp | 1 + .../templates/visitors/ast_visitor.cpp | 2 + .../templates/visitors/ast_visitor.hpp | 1 - .../visitors/checkparent_visitor.cpp | 4 +- .../templates/visitors/lookup_visitor.cpp | 5 +- .../templates/visitors/lookup_visitor.hpp | 1 - .../templates/visitors/nmodl_visitor.hpp | 2 +- .../language/templates/visitors/visitor.hpp | 1 + src/nmodl/printer/decl.hpp | 21 ++++ src/nmodl/printer/json_printer.hpp | 4 +- src/nmodl/symtab/decl.hpp | 20 ++++ src/nmodl/utils/perf_stat.cpp | 4 +- src/nmodl/utils/perf_stat.hpp | 7 +- .../visitors/constant_folder_visitor.hpp | 2 - src/nmodl/visitors/defuse_analyze_visitor.cpp | 97 +++++++++++++++--- src/nmodl/visitors/defuse_analyze_visitor.hpp | 92 ++++++----------- src/nmodl/visitors/inline_visitor.cpp | 11 ++- src/nmodl/visitors/inline_visitor.hpp | 9 +- src/nmodl/visitors/kinetic_block_visitor.hpp | 5 +- .../visitors/local_var_rename_visitor.hpp | 5 +- src/nmodl/visitors/localize_visitor.cpp | 26 ++--- src/nmodl/visitors/localize_visitor.hpp | 17 +--- src/nmodl/visitors/loop_unroll_visitor.hpp | 2 - src/nmodl/visitors/neuron_solve_visitor.hpp | 3 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 2 + src/nmodl/visitors/perf_visitor.cpp | 99 ++++++++++++++++++- src/nmodl/visitors/perf_visitor.hpp | 99 +++++-------------- src/nmodl/visitors/rename_visitor.cpp | 2 + src/nmodl/visitors/rename_visitor.hpp | 10 +- src/nmodl/visitors/solve_block_visitor.cpp | 2 + src/nmodl/visitors/solve_block_visitor.hpp | 3 +- src/nmodl/visitors/steadystate_visitor.hpp | 2 - .../visitors/sympy_conductance_visitor.cpp | 12 ++- .../visitors/sympy_conductance_visitor.hpp | 13 +-- src/nmodl/visitors/sympy_solver_visitor.cpp | 4 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 6 +- src/nmodl/visitors/units_visitor.hpp | 1 - src/nmodl/visitors/var_usage_visitor.cpp | 2 + src/nmodl/visitors/var_usage_visitor.hpp | 3 +- .../visitors/verbatim_var_rename_visitor.cpp | 2 + .../transpiler/visitor/defuse_analyze.cpp | 4 +- .../transpiler/visitor/kinetic_block.cpp | 1 + test/nmodl/transpiler/visitor/misc.cpp | 1 + test/nmodl/transpiler/visitor/steadystate.cpp | 5 +- .../transpiler/visitor/sympy_conductance.cpp | 1 + 46 files changed, 379 insertions(+), 241 deletions(-) create mode 100644 src/nmodl/printer/decl.hpp create mode 100644 src/nmodl/symtab/decl.hpp diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index a798474d49..8068b6f9f3 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3525,7 +3525,7 @@ static void rename_net_receive_arguments(ast::NetReceiveBlock& net_receive_node, void CodegenCVisitor::print_net_init() { - auto node = info.net_receive_initial_node; + const auto node = info.net_receive_initial_node; if (node == nullptr) { return; } @@ -3677,7 +3677,7 @@ void CodegenCVisitor::print_net_receive_kernel() { } codegen = true; printing_net_receive = true; - auto node = info.net_receive_node; + const auto node = info.net_receive_node; // rename net_receive arguments used in the block itself rename_net_receive_arguments(*info.net_receive_node, *node); diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index 4ced91b718..d8835db897 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -23,6 +23,7 @@ namespace ast { /// forward declaration of ast nodes +class Ast; {% for node in nodes %} class {{ node.class_name }}; {% endfor %} diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index ec964f360e..571511756d 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -11,6 +11,8 @@ #include "visitors/ast_visitor.hpp" +#include "ast/ast.hpp" + namespace nmodl { namespace visitor { diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index b6e6477302..369a7f31ae 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -20,7 +20,6 @@ * \brief \copybrief nmodl::visitor::AstVisitor */ -#include "ast/ast.hpp" #include "visitors/visitor.hpp" diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index 170bd98375..c65d326001 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -9,10 +9,12 @@ /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. /// +#include "visitors/checkparent_visitor.hpp" + #include #include -#include "visitors/checkparent_visitor.hpp" +#include "ast/ast.hpp" namespace nmodl { namespace visitor { diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index c29c77eb36..f95e3f42b7 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -9,9 +9,12 @@ /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. /// -#include #include "visitors/lookup_visitor.hpp" +#include + +#include "ast/ast.hpp" + namespace nmodl { namespace visitor { diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index 39ec15bd71..a299646d21 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -16,7 +16,6 @@ * \brief \copybrief nmodl::visitor::AstLookupVisitor */ -#include "ast/ast.hpp" #include "visitors/visitor.hpp" namespace nmodl { diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 9366e2bc6d..3af27a1886 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -18,7 +18,7 @@ #include -#include "ast/ast.hpp" +#include "visitors/visitor.hpp" #include "printer/nmodl_printer.hpp" namespace nmodl { diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index c94f0f85ea..553cefbb44 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -11,6 +11,7 @@ #pragma once +#include "ast/ast_decl.hpp" namespace nmodl { /// Implementation of different AST visitors diff --git a/src/nmodl/printer/decl.hpp b/src/nmodl/printer/decl.hpp new file mode 100644 index 0000000000..1c7b8f45aa --- /dev/null +++ b/src/nmodl/printer/decl.hpp @@ -0,0 +1,21 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief Forward references of symbols defined in namespace nmodl::printer + */ + +namespace nmodl { +namespace printer { + +class JSONPrinter; + +} +} // namespace nmodl diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 1cb8a9a148..ab237cd5ab 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -69,14 +69,14 @@ class JSONPrinter { const std::string child_key = "children"; public: - JSONPrinter(const std::string& filename); + explicit JSONPrinter(const std::string& filename); /// By default dump output to std::cout JSONPrinter() : result(new std::ostream(std::cout.rdbuf())) {} // Dump output to stringstream - JSONPrinter(std::ostream& os) + explicit JSONPrinter(std::ostream& os) : result(new std::ostream(os.rdbuf())) {} ~JSONPrinter() { diff --git a/src/nmodl/symtab/decl.hpp b/src/nmodl/symtab/decl.hpp new file mode 100644 index 0000000000..7e5a41e251 --- /dev/null +++ b/src/nmodl/symtab/decl.hpp @@ -0,0 +1,20 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/// \file +/// \brief Forward declarations of symbols in namespace nmodl::symtab + +namespace nmodl { +namespace symtab { + +class Symbol; +class SymbolTable; + +} // namespace symtab +} // namespace nmodl diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index 64247a42e0..201a3c85e1 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -73,13 +73,13 @@ void PerfStat::print(std::stringstream& stream) { table.print(stream); } -std::vector PerfStat::keys() { +std::vector PerfStat::keys() const { return {"+", "-", "x", "/", "exp", "log", "GM-R(T)", "GM-R(U)", "GM-W(T)", "GM-W(U)", "CM-R(T)", "CM-R(U)", "CM-W(T)", "CM-W(U)", "LM-R(T)", "LM-W(T)", "calls(ext)", "calls(int)", "compare", "unary", "conditional"}; } -std::vector PerfStat::values() { +std::vector PerfStat::values() const { std::vector row; int compares = n_gt + n_lt + n_ge + n_le + n_ne + n_ee; diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index 42b6435dba..9ba07dbd3b 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -13,7 +13,8 @@ */ #include - +#include +#include namespace nmodl { namespace utils { @@ -100,9 +101,9 @@ struct PerfStat { void print(std::stringstream& stream); - std::vector keys(); + std::vector keys() const; - std::vector values(); + std::vector values() const; }; /** @} */ // end of utils diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index 0404e54a6d..e58ab8d04b 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -19,8 +19,6 @@ #include #include -#include "ast/ast.hpp" -#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index bdda17fb9f..cf5c6c641d 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -5,10 +5,13 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "visitors/defuse_analyze_visitor.hpp" + #include #include -#include "visitors/defuse_analyze_visitor.hpp" +#include "ast/ast.hpp" +#include "utils/logger.hpp" namespace nmodl { namespace visitor { @@ -52,7 +55,7 @@ std::ostream& operator<<(std::ostream& os, DUState state) { } /// DUInstance to JSON string -void DUInstance::print(JSONPrinter& printer) { +void DUInstance::print(JSONPrinter& printer) const { if (children.empty()) { printer.add_node(to_string(state)); } else { @@ -65,7 +68,7 @@ void DUInstance::print(JSONPrinter& printer) { } /// DUChain to JSON string -std::string DUChain::to_string(bool compact) { +std::string DUChain::to_string(bool compact) const { std::stringstream stream; JSONPrinter printer(stream); printer.compact_json(compact); @@ -128,7 +131,6 @@ DUState DUInstance::sub_block_eval() { DUState DUInstance::conditional_block_eval() { DUState result = DUState::NONE; bool block_with_none = false; - bool block_non_def_cdef = false; for (auto& chain: children) { auto child_state = chain.eval(); @@ -181,9 +183,9 @@ DUState DUChain::eval() { return result; } -void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node* node) { +void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node& node) { unsupported_node = true; - node->visit_children(*this); + node.visit_children(*this); unsupported_node = false; } @@ -198,7 +200,7 @@ void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall& node) { if (symbol == nullptr || symbol->is_external_variable()) { node.visit_children(*this); } else { - visit_unsupported_node(&node); + visit_unsupported_node(node); } } @@ -246,12 +248,12 @@ void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement& node) { /// visiting else if sub-blocks for (const auto& item: node.get_elseifs()) { - visit_with_new_chain(item.get(), DUState::ELSEIF); + visit_with_new_chain(*item, DUState::ELSEIF); } /// visiting else sub-block if (node.get_elses()) { - visit_with_new_chain(node.get_elses().get(), DUState::ELSE); + visit_with_new_chain(*node.get_elses(), DUState::ELSE); } /// restore to previous chain @@ -270,6 +272,73 @@ void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim& node) { } } +/// unsupported statements : we aren't sure how to handle this "yet" and +/// hence variables used in any of the below statements are handled separately + +void DefUseAnalyzeVisitor::visit_reaction_statement(ast::ReactionStatement& node) { + visit_unsupported_node(node); +} + +void DefUseAnalyzeVisitor::visit_non_lin_equation(ast::NonLinEquation& node) { + visit_unsupported_node(node); +} + +void DefUseAnalyzeVisitor::visit_lin_equation(ast::LinEquation& node) { + visit_unsupported_node(node); +} + +void DefUseAnalyzeVisitor::visit_partial_boundary(ast::PartialBoundary& node) { + visit_unsupported_node(node); +} + +void DefUseAnalyzeVisitor::visit_from_statement(ast::FromStatement& node) { + visit_unsupported_node(node); +} + +void DefUseAnalyzeVisitor::visit_conserve(ast::Conserve& node) { + visit_unsupported_node(node); +} + +void DefUseAnalyzeVisitor::visit_var_name(ast::VarName& node) { + const std::string& variable = to_nmodl(node); + process_variable(variable); +} + +void DefUseAnalyzeVisitor::visit_name(ast::Name& node) { + const std::string& variable = to_nmodl(node); + process_variable(variable); +} + +void DefUseAnalyzeVisitor::visit_indexed_name(ast::IndexedName& node) { + const auto& name = node.get_node_name(); + const auto& length = node.get_length(); + + /// index should be an integer (e.g. after constant folding) + /// if this is not the case and then we can't determine exact + /// def-use chain + if (!length->is_integer()) { + /// check if variable name without index part is same + auto variable_name_prefix = variable_name.substr(0, variable_name.find('[')); + if (name == variable_name_prefix) { + update_defuse_chain(variable_name_prefix); + const std::string& text = to_nmodl(node); + nmodl::logger->info("index used to access variable is not known : {} ", text); + } + return; + } + auto index = std::dynamic_pointer_cast(length); + process_variable(name, index->eval()); +} + +/// statements / nodes that should not be used for def-use chain analysis + +void DefUseAnalyzeVisitor::visit_conductance_hint(ast::ConductanceHint& /*node*/) {} + +void DefUseAnalyzeVisitor::visit_local_list_statement(ast::LocalListStatement& /*node*/) {} + +void DefUseAnalyzeVisitor::visit_argument(ast::Argument& /*node*/) {} + + /** * Update the Def-Use chain for given variable * @@ -319,10 +388,10 @@ void DefUseAnalyzeVisitor::process_variable(const std::string& name, int index) } } -void DefUseAnalyzeVisitor::visit_with_new_chain(ast::Node* node, DUState state) { +void DefUseAnalyzeVisitor::visit_with_new_chain(ast::Node& node, DUState state) { auto last_chain = current_chain; start_new_chain(state); - node->visit_children(*this); + node.visit_children(*this); current_chain = last_chain; } @@ -331,7 +400,7 @@ void DefUseAnalyzeVisitor::start_new_chain(DUState state) { current_chain = ¤t_chain->back().children; } -DUChain DefUseAnalyzeVisitor::analyze(ast::Ast* node, const std::string& name) { +DUChain DefUseAnalyzeVisitor::analyze(ast::Ast& node, const std::string& name) { /// re-initialize state variable_name = name; visiting_lhs = false; @@ -339,12 +408,12 @@ DUChain DefUseAnalyzeVisitor::analyze(ast::Ast* node, const std::string& name) { unsupported_node = false; /// new chain - DUChain usage(node->get_node_type_name()); + DUChain usage(node.get_node_type_name()); current_chain = &usage.chain; /// analyze given node symtab_stack.push(current_symtab); - node->visit_children(*this); + node.visit_children(*this); symtab_stack.pop(); return usage; diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 1337488956..8e3e4a05d4 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -15,10 +15,7 @@ #include #include -#include "ast/ast.hpp" #include "printer/json_printer.hpp" -#include "symtab/symbol_table.hpp" -#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -96,7 +93,7 @@ class DUInstance { /// evaluate global usage i.e. with [D,U] states of children DUState conditional_block_eval(); - void print(printer::JSONPrinter& printer); + void print(printer::JSONPrinter& printer) const; }; @@ -120,7 +117,7 @@ class DUChain { DUState eval(); /// return json representation - std::string to_string(bool compact = true); + std::string to_string(bool compact = true) const; }; @@ -183,7 +180,7 @@ class DUChain { * in any of the if-elseif-else part then it is considered as "used". And * this is done recursively from innermost level to the top. */ -class DefUseAnalyzeVisitor: public AstVisitor { +class DefUseAnalyzeVisitor: protected AstVisitor { private: /// symbol table containing global variables symtab::SymbolTable* global_symtab = nullptr; @@ -214,8 +211,8 @@ class DefUseAnalyzeVisitor: public AstVisitor { void process_variable(const std::string& name, int index); void update_defuse_chain(const std::string& name); - void visit_unsupported_node(ast::Node* node); - void visit_with_new_chain(ast::Node* node, DUState state); + void visit_unsupported_node(ast::Node& node); + void visit_with_new_chain(ast::Node& node, DUState state); void start_new_chain(DUState state); public: @@ -234,74 +231,49 @@ class DefUseAnalyzeVisitor: public AstVisitor { void visit_statement_block(ast::StatementBlock& node) override; void visit_verbatim(ast::Verbatim& node) override; - /// unsupported statements : we aren't sure how to handle this "yet" and - /// hence variables used in any of the below statements are handled separately + /** + * /\name unsupported statements + * we aren't sure how to handle this "yet" and hence variables + * used in any of the below statements are handled separately + * \{ + */ - void visit_reaction_statement(ast::ReactionStatement& node) override { - visit_unsupported_node(&node); - } + void visit_reaction_statement(ast::ReactionStatement& node) override; - void visit_non_lin_equation(ast::NonLinEquation& node) override { - visit_unsupported_node(&node); - } + void visit_non_lin_equation(ast::NonLinEquation& node) override; - void visit_lin_equation(ast::LinEquation& node) override { - visit_unsupported_node(&node); - } + void visit_lin_equation(ast::LinEquation& node) override; - void visit_partial_boundary(ast::PartialBoundary& node) override { - visit_unsupported_node(&node); - } + void visit_partial_boundary(ast::PartialBoundary& node) override; - void visit_from_statement(ast::FromStatement& node) override { - visit_unsupported_node(&node); - } + void visit_from_statement(ast::FromStatement& node) override; - void visit_conserve(ast::Conserve& node) override { - visit_unsupported_node(&node); - } + void visit_conserve(ast::Conserve& node) override; - void visit_var_name(ast::VarName& node) override { - const std::string& variable = to_nmodl(node); - process_variable(variable); - } + void visit_var_name(ast::VarName& node) override; - void visit_name(ast::Name& node) override { - const std::string& variable = to_nmodl(node); - process_variable(variable); - } + void visit_name(ast::Name& node) override; - void visit_indexed_name(ast::IndexedName& node) override { - const auto& name = node.get_node_name(); - const auto& length = node.get_length(); + void visit_indexed_name(ast::IndexedName& node) override; - /// index should be an integer (e.g. after constant folding) - /// if this is not the case and then we can't determine exact - /// def-use chain - if (!length->is_integer()) { - /// check if variable name without index part is same - auto variable_name_prefix = variable_name.substr(0, variable_name.find('[')); - if (name == variable_name_prefix) { - update_defuse_chain(variable_name_prefix); - const std::string& text = to_nmodl(node); - nmodl::logger->info("index used to access variable is not known : {} ", text); - } - return; - } - auto index = std::dynamic_pointer_cast(length); - process_variable(name, index->eval()); - } + /** \} */ - /// statements / nodes that should not be used for def-use chain analysis + /** + * /\name statements + * nodes that should not be used for def-use chain analysis + * \{ + */ - void visit_conductance_hint(ast::ConductanceHint& /*node*/) override {} + void visit_conductance_hint(ast::ConductanceHint& node) override; - void visit_local_list_statement(ast::LocalListStatement& /*node*/) override {} + void visit_local_list_statement(ast::LocalListStatement& node) override; - void visit_argument(ast::Argument& /*node*/) override {} + void visit_argument(ast::Argument& node) override; + + /** \} */ /// compute def-use chain for a variable within the node - DUChain analyze(ast::Ast* node, const std::string& name); + DUChain analyze(ast::Ast& node, const std::string& name); }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index a0b86e1e30..2fa7ee9dad 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -6,7 +6,12 @@ *************************************************************************/ #include "visitors/inline_visitor.hpp" + +#include "ast/ast.hpp" #include "parser/c11_driver.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { @@ -14,9 +19,9 @@ namespace visitor { using namespace ast; -bool InlineVisitor::can_inline_block(StatementBlock* block) { +bool InlineVisitor::can_inline_block(StatementBlock& block) { bool to_inline = true; - const auto& statements = block->get_statements(); + const auto& statements = block.get_statements(); for (const auto& statement: statements) { /// inlining is disabled if function/procedure contains table or lag statement if (statement->is_table_statement() || statement->is_lag_statement()) { @@ -120,7 +125,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, const auto& function_name = callee->get_node_name(); /// do nothing if we can't inline given procedure/function - if (!can_inline_block(callee->get_statement_block().get())) { + if (!can_inline_block(*callee->get_statement_block())) { std::cerr << "Can not inline function call to " + function_name << '\n'; return false; } diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index ebeec6c30a..62cb067753 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -15,13 +15,8 @@ #include #include -#include "ast/ast.hpp" -#include "symtab/symbol_table.hpp" +#include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "visitors/rename_visitor.hpp" -#include "visitors/visitor_utils.hpp" - namespace nmodl { namespace visitor { @@ -164,7 +159,7 @@ class InlineVisitor: public AstVisitor { std::map inlined_variables; /// true if given statement block can be inlined - bool can_inline_block(ast::StatementBlock* block); + bool can_inline_block(ast::StatementBlock& block); /// true if statement can be replaced with inlined body /// this is possible for standalone function/procedure call as statement diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index bd97e5b328..95a8c6f999 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -12,15 +12,14 @@ * \brief \copybrief nmodl::visitor::KineticBlockVisitor */ -#include "ast/ast.hpp" -#include "visitors/ast_visitor.hpp" -#include "visitors/visitor_utils.hpp" #include #include #include #include #include +#include "visitors/ast_visitor.hpp" + namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index 22cb62b73f..a00e7643e8 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -15,8 +15,7 @@ #include #include -#include "ast/ast.hpp" -#include "symtab/symbol_table.hpp" +#include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" namespace nmodl { @@ -73,7 +72,7 @@ class LocalVarRenameVisitor: public AstVisitor { public: LocalVarRenameVisitor() = default; - virtual void visit_statement_block(ast::StatementBlock& node) override; + void visit_statement_block(ast::StatementBlock& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 0d88dad18d..f6498a25b7 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -5,10 +5,12 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "visitors/localize_visitor.hpp" + #include +#include "utils/logger.hpp" #include "visitors/defuse_analyze_visitor.hpp" -#include "visitors/localize_visitor.hpp" namespace nmodl { namespace visitor { @@ -16,8 +18,8 @@ namespace visitor { using symtab::Symbol; using symtab::syminfo::NmodlType; -bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { - const auto& type = node->get_node_type(); +bool LocalizeVisitor::node_for_def_use_analysis(const ast::Node& node) const { + const auto& type = node.get_node_type(); /** * Blocks where we should compute def-use chains. We are excluding @@ -54,9 +56,9 @@ bool LocalizeVisitor::node_for_def_use_analysis(ast::Node* node) { * Check if given node is a procedure block and if it is used * in the solve statement. */ -bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { - if (node->is_procedure_block()) { - auto symbol = program_symtab->lookup(node->get_node_name()); +bool LocalizeVisitor::is_solve_procedure(const ast::Node& node) const { + if (node.is_procedure_block()) { + const auto& symbol = program_symtab->lookup(node.get_node_name()); if (symbol && symbol->has_any_property(NmodlType::to_solve)) { return true; } @@ -64,7 +66,7 @@ bool LocalizeVisitor::is_solve_procedure(ast::Node* node) { return false; } -std::vector LocalizeVisitor::variables_to_optimize() { +std::vector LocalizeVisitor::variables_to_optimize() const { // clang-format off const NmodlType excluded_var_properties = NmodlType::extern_var | NmodlType::extern_neuron_variable @@ -88,10 +90,10 @@ std::vector LocalizeVisitor::variables_to_optimize() { * to avoid optimizations, we need to handle this case properly * \todo Instead of ast node, use symbol properties to check variable type */ - auto variables = program_symtab->get_variables_with_properties(global_var_properties); + const auto& variables = program_symtab->get_variables_with_properties(global_var_properties); std::vector result; - for (auto& variable: variables) { + for (const auto& variable: variables) { if (!variable->has_any_property(excluded_var_properties)) { result.push_back(variable->get_name()); } @@ -114,9 +116,9 @@ void LocalizeVisitor::visit_program(ast::Program& node) { /// compute def use chains for (const auto& block: blocks) { - if (node_for_def_use_analysis(block.get())) { + if (node_for_def_use_analysis(*block)) { DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); - auto usages = v.analyze(block.get(), varname); + auto usages = v.analyze(*block, varname); auto result = usages.eval(); block_usage[result].push_back(block); } @@ -133,7 +135,7 @@ void LocalizeVisitor::visit_program(ast::Program& node) { for (auto state: {DUState::D, DUState::CD}) { for (auto& block: block_usage[state]) { auto block_ptr = dynamic_cast(block.get()); - auto statement_block = block_ptr->get_statement_block(); + const auto& statement_block = block_ptr->get_statement_block(); ast::LocalVar* variable; auto symbol = program_symtab->lookup(varname); diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 06d053ac69..1a285a79a4 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -13,16 +13,9 @@ */ #include -#include -#include "ast/ast.hpp" -#include "printer/json_printer.hpp" -#include "symtab/symbol_table.hpp" -#include "utils/logger.hpp" +#include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/local_var_rename_visitor.hpp" -#include "visitors/rename_visitor.hpp" -#include "visitors/visitor_utils.hpp" namespace nmodl { @@ -94,11 +87,11 @@ class LocalizeVisitor: public AstVisitor { symtab::SymbolTable* program_symtab = nullptr; - std::vector variables_to_optimize(); + std::vector variables_to_optimize() const; - bool node_for_def_use_analysis(ast::Node* node); + bool node_for_def_use_analysis(const ast::Node& node) const; - bool is_solve_procedure(ast::Node* node); + bool is_solve_procedure(const ast::Node& node) const; public: LocalizeVisitor() = default; @@ -106,7 +99,7 @@ class LocalizeVisitor: public AstVisitor { explicit LocalizeVisitor(bool ignore_verbatim) : ignore_verbatim(ignore_verbatim) {} - virtual void visit_program(ast::Program& node) override; + void visit_program(ast::Program& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index 3cd1ac69d0..551715d224 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -14,8 +14,6 @@ #include -#include "ast/ast.hpp" -#include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" namespace nmodl { diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index f8c7367f93..0ba1a52c63 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -12,9 +12,10 @@ * \brief \copybrief nmodl::visitor::NeuronSolveVisitor */ +#include #include -#include "ast/ast.hpp" +#include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index 1c786099d8..22ce62985e 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -9,6 +9,8 @@ #include "visitors/nmodl_visitor.hpp" +#include "visitors/visitor_utils.hpp" + namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index e2b68ab866..4fc26f1897 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -5,9 +5,12 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "visitors/perf_visitor.hpp" + #include -#include "visitors/perf_visitor.hpp" +#include "ast/ast.hpp" +#include "printer/json_printer.hpp" namespace nmodl { @@ -22,6 +25,11 @@ using utils::PerfStat; PerfVisitor::PerfVisitor(const std::string& filename) : printer(new JSONPrinter(filename)) {} +void PerfVisitor::compact_json(bool flag) { + printer->compact_json(flag); +} + + /// count math operations from all binary expressions void PerfVisitor::visit_binary_expression(ast::BinaryExpression& node) { bool assign_op = false; @@ -335,6 +343,95 @@ void PerfVisitor::visit_program(ast::Program& node) { print_memory_usage(); } +void PerfVisitor::visit_plot_block(ast::PlotBlock& node) { + measure_performance(&node); +} + +/// skip initial block under net_receive block +void PerfVisitor::visit_initial_block(ast::InitialBlock& node) { + if (!under_net_receive_block) { + measure_performance(&node); + } +} + +void PerfVisitor::visit_constructor_block(ast::ConstructorBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_destructor_block(ast::DestructorBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_derivative_block(ast::DerivativeBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_linear_block(ast::LinearBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_non_linear_block(ast::NonLinearBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_discrete_block(ast::DiscreteBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_partial_block(ast::PartialBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_function_table_block(ast::FunctionTableBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_function_block(ast::FunctionBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_procedure_block(ast::ProcedureBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_net_receive_block(ast::NetReceiveBlock& node) { + under_net_receive_block = true; + measure_performance(&node); + under_net_receive_block = false; +} + +void PerfVisitor::visit_breakpoint_block(ast::BreakpointBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_terminal_block(ast::TerminalBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_before_block(ast::BeforeBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_after_block(ast::AfterBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_ba_block(ast::BABlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_for_netcon(ast::ForNetcon& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_kinetic_block(ast::KineticBlock& node) { + measure_performance(&node); +} + +void PerfVisitor::visit_match_block(ast::MatchBlock& node) { + measure_performance(&node); +} + /** Blocks like function can have multiple statement blocks and * blocks like net receive has nested initial blocks. Hence need * to maintain separate stack. diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 7370024878..cc32b1ec44 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -12,11 +12,12 @@ * \brief \copybrief nmodl::visitor::PerfVisitor */ +#include #include #include -#include "printer/json_printer.hpp" -#include "symtab/symbol_table.hpp" +#include "printer/decl.hpp" +#include "symtab/decl.hpp" #include "utils/perf_stat.hpp" #include "visitors/ast_visitor.hpp" @@ -151,9 +152,7 @@ class PerfVisitor: public AstVisitor { explicit PerfVisitor(const std::string& filename); - void compact_json(bool flag) { - printer->compact_json(flag); - } + void compact_json(bool flag); utils::PerfStat get_total_perfstat() const noexcept { return total_perf; @@ -199,94 +198,48 @@ class PerfVisitor: public AstVisitor { void visit_program(ast::Program& node) override; - void visit_plot_block(ast::PlotBlock& node) override { - measure_performance(&node); - } + void visit_plot_block(ast::PlotBlock& node) override; /// skip initial block under net_receive block - void visit_initial_block(ast::InitialBlock& node) override { - if (!under_net_receive_block) { - measure_performance(&node); - } - } + void visit_initial_block(ast::InitialBlock& node) override; - void visit_constructor_block(ast::ConstructorBlock& node) override { - measure_performance(&node); - } + void visit_constructor_block(ast::ConstructorBlock& node) override; - void visit_destructor_block(ast::DestructorBlock& node) override { - measure_performance(&node); - } + void visit_destructor_block(ast::DestructorBlock& node) override; - void visit_derivative_block(ast::DerivativeBlock& node) override { - measure_performance(&node); - } + void visit_derivative_block(ast::DerivativeBlock& node) override; - void visit_linear_block(ast::LinearBlock& node) override { - measure_performance(&node); - } + void visit_linear_block(ast::LinearBlock& node) override; - void visit_non_linear_block(ast::NonLinearBlock& node) override { - measure_performance(&node); - } + void visit_non_linear_block(ast::NonLinearBlock& node) override; - void visit_discrete_block(ast::DiscreteBlock& node) override { - measure_performance(&node); - } + void visit_discrete_block(ast::DiscreteBlock& node) override; - void visit_partial_block(ast::PartialBlock& node) override { - measure_performance(&node); - } + void visit_partial_block(ast::PartialBlock& node) override; - void visit_function_table_block(ast::FunctionTableBlock& node) override { - measure_performance(&node); - } + void visit_function_table_block(ast::FunctionTableBlock& node) override; - void visit_function_block(ast::FunctionBlock& node) override { - measure_performance(&node); - } + void visit_function_block(ast::FunctionBlock& node) override; - void visit_procedure_block(ast::ProcedureBlock& node) override { - measure_performance(&node); - } + void visit_procedure_block(ast::ProcedureBlock& node) override; - void visit_net_receive_block(ast::NetReceiveBlock& node) override { - under_net_receive_block = true; - measure_performance(&node); - under_net_receive_block = false; - } + void visit_net_receive_block(ast::NetReceiveBlock& node) override; - void visit_breakpoint_block(ast::BreakpointBlock& node) override { - measure_performance(&node); - } + void visit_breakpoint_block(ast::BreakpointBlock& node) override; - void visit_terminal_block(ast::TerminalBlock& node) override { - measure_performance(&node); - } + void visit_terminal_block(ast::TerminalBlock& node) override; - void visit_before_block(ast::BeforeBlock& node) override { - measure_performance(&node); - } + void visit_before_block(ast::BeforeBlock& node) override; - void visit_after_block(ast::AfterBlock& node) override { - measure_performance(&node); - } + void visit_after_block(ast::AfterBlock& node) override; - void visit_ba_block(ast::BABlock& node) override { - measure_performance(&node); - } + void visit_ba_block(ast::BABlock& node) override; - void visit_for_netcon(ast::ForNetcon& node) override { - measure_performance(&node); - } + void visit_for_netcon(ast::ForNetcon& node) override; - void visit_kinetic_block(ast::KineticBlock& node) override { - measure_performance(&node); - } + void visit_kinetic_block(ast::KineticBlock& node) override; - void visit_match_block(ast::MatchBlock& node) override { - measure_performance(&node); - } + void visit_match_block(ast::MatchBlock& node) override; /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations @@ -301,7 +254,7 @@ class PerfVisitor: public AstVisitor { void visit_valence(ast::Valence& /*node*/) override {} - void print(std::ostream& ss) { + void print(std::ostream& ss) const { ss << stream.str(); } }; diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 0be19c5c62..9c7ef23f9c 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "visitors/rename_visitor.hpp" + +#include "ast/ast.hpp" #include "parser/c11_driver.hpp" diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index 73784ef825..65c665da4f 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -14,8 +14,6 @@ #include -#include "ast/ast.hpp" -#include "symtab/symbol_table.hpp" #include "visitors/ast_visitor.hpp" @@ -55,12 +53,12 @@ class RenameVisitor: public AstVisitor { RenameVisitor() = default; RenameVisitor(std::string old_name, std::string new_name) - : var_name(old_name) - , new_var_name(new_name) {} + : var_name(std::move(old_name)) + , new_var_name(std::move(new_name)) {} void set(std::string old_name, std::string new_name) { - var_name = old_name; - new_var_name = new_name; + var_name = std::move(old_name); + new_var_name = std::move(new_name); } void enable_verbatim(bool state) { diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index f2d0486a3a..a7ae00bfb0 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "visitors/solve_block_visitor.hpp" + +#include "ast/ast.hpp" #include "codegen/codegen_naming.hpp" #include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp index decca10379..6897daa3e1 100644 --- a/src/nmodl/visitors/solve_block_visitor.hpp +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -12,8 +12,7 @@ * \brief \copybrief nmodl::visitor::SolveBlockVisitor */ -#include "ast/ast.hpp" -#include "symtab/symbol_table.hpp" +#include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" namespace nmodl { diff --git a/src/nmodl/visitors/steadystate_visitor.hpp b/src/nmodl/visitors/steadystate_visitor.hpp index b467f1b393..52d2616ec2 100644 --- a/src/nmodl/visitors/steadystate_visitor.hpp +++ b/src/nmodl/visitors/steadystate_visitor.hpp @@ -12,9 +12,7 @@ * \brief \copybrief nmodl::visitor::SteadystateVisitor */ -#include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/visitor_utils.hpp" namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index dbd2ad9fc7..c30b6eceea 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -5,13 +5,18 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "visitors/sympy_conductance_visitor.hpp" + #include #include +#include +#include + +#include "ast/ast.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/sympy_conductance_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -166,6 +171,11 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { } } +std::string SympyConductanceVisitor::to_nmodl_for_sympy(ast::Ast& node) { + return to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); +} + + void SympyConductanceVisitor::lookup_useion_statements() { // add USEION statements to i_name map between write vars and names for (const auto& useion_ast: use_ion_nodes) { diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index 8da985e783..73fe0eecf0 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -16,17 +16,10 @@ #include #include -#include -#include - -#include "ast/ast.hpp" -#include "symtab/symbol.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/lookup_visitor.hpp" -#include "visitors/visitor_utils.hpp" - namespace nmodl { + namespace visitor { /** @@ -96,9 +89,7 @@ class SympyConductanceVisitor: public AstVisitor { void lookup_useion_statements(); void lookup_nonspecific_statements(); - static std::string to_nmodl_for_sympy(ast::Ast& node) { - return to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); - } + static std::string to_nmodl_for_sympy(ast::Ast& node); public: SympyConductanceVisitor() = default; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 13aa41d2ae..902a34a637 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -7,6 +7,8 @@ #include +#include + #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" @@ -115,7 +117,7 @@ ast::StatementVector::const_iterator SympySolverVisitor::get_solution_location_i * expression statement and hence we try to look inside if it's really a * variable declaration. */ -static bool is_local_statement(std::shared_ptr statement) { +static bool is_local_statement(const std::shared_ptr& statement) { if (statement->is_local_list_statement()) { return true; } diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index fc0c0e994c..4f76985c45 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -177,9 +177,9 @@ class SympySolverVisitor: public AstVisitor { int SMALL_LINEAR_SYSTEM_MAX_STATES; public: - SympySolverVisitor(bool use_pade_approx = false, - bool elimination = true, - int SMALL_LINEAR_SYSTEM_MAX_STATES = 3) + explicit SympySolverVisitor(bool use_pade_approx = false, + bool elimination = true, + int SMALL_LINEAR_SYSTEM_MAX_STATES = 3) : use_pade_approx(use_pade_approx) , elimination(elimination) , SMALL_LINEAR_SYSTEM_MAX_STATES(SMALL_LINEAR_SYSTEM_MAX_STATES){}; diff --git a/src/nmodl/visitors/units_visitor.hpp b/src/nmodl/visitors/units_visitor.hpp index 68bb3de877..a2f9d7e7f3 100644 --- a/src/nmodl/visitors/units_visitor.hpp +++ b/src/nmodl/visitors/units_visitor.hpp @@ -16,7 +16,6 @@ #include #include -#include "ast/ast.hpp" #include "parser/unit_driver.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index eadb8a7cb7..6e3189738a 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -9,6 +9,8 @@ #include +#include "ast/ast.hpp" + namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index 7d433f5fc5..b706c5fcb7 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -14,7 +14,6 @@ #include -#include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -33,7 +32,7 @@ namespace visitor { * \todo Check if macro is considered as variable */ -class VarUsageVisitor: public AstVisitor { +class VarUsageVisitor: protected AstVisitor { private: /// variable to check usage std::string var_name; diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 0138ec6ced..9eb3890dde 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "visitors/verbatim_var_rename_visitor.hpp" + +#include "ast/ast.hpp" #include "parser/c11_driver.hpp" #include "src/utils/logger.hpp" diff --git a/test/nmodl/transpiler/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/visitor/defuse_analyze.cpp index f0ddb9b5d5..143b11afe4 100644 --- a/test/nmodl/transpiler/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/visitor/defuse_analyze.cpp @@ -40,9 +40,9 @@ std::vector run_defuse_visitor(const std::string& text, const std::stri /// analyse only derivative blocks in this test auto blocks = AstLookupVisitor().lookup(*ast, AstNodeType::DERIVATIVE_BLOCK); + chains.reserve(blocks.size()); for (auto& block: blocks) { - auto node = block.get(); - chains.push_back(v.analyze(node, variable)); + chains.push_back(v.analyze(*block, variable)); } // check that, after visitor rearrangement, parents are still up-to-date diff --git a/test/nmodl/transpiler/visitor/kinetic_block.cpp b/test/nmodl/transpiler/visitor/kinetic_block.cpp index e1787e5e2c..5799ab06b4 100644 --- a/test/nmodl/transpiler/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/visitor/kinetic_block.cpp @@ -15,6 +15,7 @@ #include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" using namespace nmodl; using namespace visitor; diff --git a/test/nmodl/transpiler/visitor/misc.cpp b/test/nmodl/transpiler/visitor/misc.cpp index 56f05f0f30..e58f1d1ffe 100644 --- a/test/nmodl/transpiler/visitor/misc.cpp +++ b/test/nmodl/transpiler/visitor/misc.cpp @@ -13,6 +13,7 @@ #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" using namespace nmodl; using namespace visitor; diff --git a/test/nmodl/transpiler/visitor/steadystate.cpp b/test/nmodl/transpiler/visitor/steadystate.cpp index bc79bb468d..55830644ac 100644 --- a/test/nmodl/transpiler/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/visitor/steadystate.cpp @@ -9,15 +9,14 @@ #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" -//#include "visitors/nmodl_visitor.hpp" #include "visitors/steadystate_visitor.hpp" -//#include "visitors/sympy_solver_visitor.hpp" -#include "visitors/checkparent_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" using namespace nmodl; using namespace visitor; diff --git a/test/nmodl/transpiler/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/visitor/sympy_conductance.cpp index 5bac487667..78369941dd 100644 --- a/test/nmodl/transpiler/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/visitor/sympy_conductance.cpp @@ -16,6 +16,7 @@ #include "visitors/lookup_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" using namespace nmodl; using namespace visitor; From c4e88d272c82fd53cbbcff284fed0d2d0b451528 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 4 May 2020 19:40:48 +0200 Subject: [PATCH 258/871] Rename context to eq_context for compatibility with Bison v3.6.0 (BlueBrain/nmodl#318) * Rename context to eq_context for compatibility with Bison v3.6.0 - upcoming bison release 3.6.0 will have new type called `class context` - this will conflict with the variable name used in differential equation parser and hence we rename it. * fix travis CI by using older bison version fixes BlueBrain/nmodl#314 NMODL Repo SHA: BlueBrain/nmodl@987dbc83b459b74a251db284a214025ca9465655 --- .travis.yml | 11 +++- src/nmodl/parser/diffeq.yy | 80 +++++++++++++++--------------- src/nmodl/parser/diffeq_driver.cpp | 8 +-- 3 files changed, 54 insertions(+), 45 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5f35e8fb78..b9c29344be 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,6 +26,14 @@ matrix: env: - MATRIX_EVAL="CXX=c++" +#============================================================================= +# Cache directories +#============================================================================= +cache: + directories: + - $HOME/.cache/pip + - $HOME/Library/Caches/Homebrew + #============================================================================= # Common Packages #============================================================================= @@ -48,7 +56,6 @@ addons: # for Mac builds, we use Homebrew homebrew: packages: - - bison - boost - cmake - flex @@ -61,7 +68,9 @@ addons: before_install: # brew installed flex and bison is not in $PATH # unlink python2 and use python3 as it's required for nmodl + # install older bison because of #314 - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then + brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/187c0d5/Formula/bison.rb; export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; brew unlink python@2; brew link --overwrite python; diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 0074fd24b6..2aa902d037 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -40,7 +40,7 @@ /** add extra arguments to yyparse() and yylexe() methods */ %parse-param {class DiffeqLexer& scanner} -%parse-param {diffeq::DiffEqContext& context} +%parse-param {diffeq::DiffEqContext& eq_context} %lex-param {diffeq::DiffEqScanner &scanner } /** use variant based implementation of semantic values */ @@ -115,7 +115,7 @@ */ top : expression { - context.solution.b = "-(" + context.solution.b + ")/(" + context.solution.a + ")"; + eq_context.solution.b = "-(" + eq_context.solution.b + ")/(" + eq_context.solution.a + ")"; } | error { throw std::runtime_error("Invalid differential equation"); @@ -131,63 +131,63 @@ expression : e { $$ = $1; } e : ATOM { - context.solution = Term($1, context.state_variable()); - $$ = context.solution; + eq_context.solution = Term($1, eq_context.state_variable()); + $$ = eq_context.solution; } | "(" e ")" { - context.solution = Term(); - context.solution.expr = "(" + $2.expr + ")"; + eq_context.solution = Term(); + eq_context.solution.expr = "(" + $2.expr + ")"; if($2.deriv_nonzero()) - context.solution.deriv = "(" + $2.deriv + ")"; + eq_context.solution.deriv = "(" + $2.deriv + ")"; if($2.a_nonzero()) - context.solution.a = "(" + $2.a + ")"; + eq_context.solution.a = "(" + $2.a + ")"; if($2.b_nonzero()) - context.solution.b = "(" + $2.b + ")"; + eq_context.solution.b = "(" + $2.b + ")"; - $$ = context.solution; + $$ = eq_context.solution; } | ATOM "(" arglist ")" { - context.solution = Term(); - context.solution.expr = $1 + "(" + $3.expr + ")"; - context.solution.b = $1 + "(" + $3.expr + ")"; - $$ = context.solution; + eq_context.solution = Term(); + eq_context.solution.expr = $1 + "(" + $3.expr + ")"; + eq_context.solution.b = $1 + "(" + $3.expr + ")"; + $$ = eq_context.solution; } | "-" e %prec UNARYMINUS { - context.solution = Term(); - context.solution.expr = "-" + $2.expr; + eq_context.solution = Term(); + eq_context.solution.expr = "-" + $2.expr; if($2.deriv_nonzero()) - context.solution.deriv = "-" + $2.deriv; + eq_context.solution.deriv = "-" + $2.deriv; if($2.a_nonzero()) - context.solution.a = "-" + $2.a; + eq_context.solution.a = "-" + $2.a; if($2.b_nonzero()) - context.solution.b = "-" +$2.b; + eq_context.solution.b = "-" +$2.b; - $$ = context.solution; + $$ = eq_context.solution; } | e "+" e { - context.solution = eval_derivative($1, $3, context.deriv_invalid, context.eqn_invalid); - $$ = context.solution; + eq_context.solution = eval_derivative($1, $3, eq_context.deriv_invalid, eq_context.eqn_invalid); + $$ = eq_context.solution; } | e "-" e { - context.solution = eval_derivative($1, $3, context.deriv_invalid, context.eqn_invalid); - $$ = context.solution; + eq_context.solution = eval_derivative($1, $3, eq_context.deriv_invalid, eq_context.eqn_invalid); + $$ = eq_context.solution; } | e "*" e { - context.solution = eval_derivative($1, $3, context.deriv_invalid, context.eqn_invalid); - $$ = context.solution; + eq_context.solution = eval_derivative($1, $3, eq_context.deriv_invalid, eq_context.eqn_invalid); + $$ = eq_context.solution; } | e "/" e { - context.solution = eval_derivative($1, $3, context.deriv_invalid, context.eqn_invalid); - $$ = context.solution; + eq_context.solution = eval_derivative($1, $3, eq_context.deriv_invalid, eq_context.eqn_invalid); + $$ = eq_context.solution; } ; @@ -199,29 +199,29 @@ arglist : /*nothing*/ { $$ = Term("", "0.0", "0.0", ""); } | arg { $$ = $1; } | arglist arg { - context.solution.expr = $1.expr + " " + $2.expr; - context.solution.b = $1.expr + " " + $2.expr; - $$ = context.solution; + eq_context.solution.expr = $1.expr + " " + $2.expr; + eq_context.solution.b = $1.expr + " " + $2.expr; + $$ = eq_context.solution; } | arglist "," arg { - context.solution.expr = $1.expr + "," + $3.expr; - context.solution.b = $1.expr + "," + $3.expr; - $$ = context.solution; + eq_context.solution.expr = $1.expr + "," + $3.expr; + eq_context.solution.b = $1.expr + "," + $3.expr; + $$ = eq_context.solution; } ; arg : e { - context.solution = Term(); - context.solution.expr = $1.expr; - context.solution.b = $1.expr; + eq_context.solution = Term(); + eq_context.solution.expr = $1.expr; + eq_context.solution.b = $1.expr; if($1.deriv_nonzero()) { - context.deriv_invalid = true; - context.eqn_invalid = true; + eq_context.deriv_invalid = true; + eq_context.eqn_invalid = true; } - $$ = context.solution; + $$ = eq_context.solution; } ; diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index e20a455d79..7ffb0332d1 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -48,14 +48,14 @@ std::string DiffeqDriver::solve_equation(std::string& state, bool& cnexp_possible, bool debug) { std::istringstream in(rhs); - diffeq::DiffEqContext context(state, order, rhs, method); + diffeq::DiffEqContext eq_context(state, order, rhs, method); DiffeqLexer scanner(&in); - DiffeqParser parser(scanner, context); + DiffeqParser parser(scanner, eq_context); parser.parse(); if (debug) { - context.print(); + eq_context.print(); } - return context.get_solution(cnexp_possible); + return eq_context.get_solution(cnexp_possible); } /// \todo Instead of using neuron like api, we need to refactor From 0b55f6335f9e0a95979489975a71c1062c64a9c2 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 6 May 2020 23:58:43 +0200 Subject: [PATCH 259/871] Support for building without libpython (BlueBrain/nmodl#316) * Support for building without libpython - add extra flag -DLINK_AGAINST_PYTHON=OFF to disable linking with libpython libary - update travis to use older bison on OSX - Support for NMODLHOME env variable to locate nrnunits.lib * Copy missing examples * Cache hombrew/pip directories fixes BlueBrain/nmodl#314 BlueBrain/nmodl#315 NMODL Repo SHA: BlueBrain/nmodl@f4f93a40f735b590c058f050e541c11db5e49cb8 --- cmake/nmodl/CMakeLists.txt | 1 + cmake/nmodl/PythonLinkHelper.cmake | 27 +++++++++++++++++++++++++++ src/nmodl/config/config.cpp.in | 2 +- src/nmodl/config/config.h | 10 +++++++++- src/nmodl/pybind/CMakeLists.txt | 3 ++- src/nmodl/visitors/CMakeLists.txt | 12 ++++++++++-- 6 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 cmake/nmodl/PythonLinkHelper.cmake diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 65f7fc4d06..b1ea339f0f 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -86,6 +86,7 @@ include(FindClangFormat) include(FindPythonModule) include(FlexHelper) include(GitRevision) +include(PythonLinkHelper) include(RpathHelper) # ============================================================================= diff --git a/cmake/nmodl/PythonLinkHelper.cmake b/cmake/nmodl/PythonLinkHelper.cmake new file mode 100644 index 0000000000..908e7ee9c6 --- /dev/null +++ b/cmake/nmodl/PythonLinkHelper.cmake @@ -0,0 +1,27 @@ +# ============================================================================= +# Support for building NMODL without python library +# ============================================================================= +# ~~~ +# When NMODL is built under environment (e.g. manylinux) without python-dev +# package i.e. libpython.so, we have to build extension and binaries by +# ignoring undefned symbols. The python library will be loaded later on target +# system. +# ~~~ + +# use same variable name as NEURON as it won't be user option +set(LINK_AGAINST_PYTHON + TRUE + CACHE BOOL "Disable linking to python library") + +mark_as_advanced(LINK_AGAINST_PYTHON) + +# Flags for ignoring undefined symbols for wheel +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(UNDEFINED_SYMBOLS_IGNORE_FLAG "-Wl,-undefined,dynamic_lookup") +else() + set(UNDEFINED_SYMBOLS_IGNORE_FLAG "-Wl,--unresolved-symbols=ignore-all") +endif() + +if(NOT LINK_AGAINST_PYTHON) + string(APPEND CMAKE_EXE_LINKER_FLAGS " ${UNDEFINED_SYMBOLS_IGNORE_FLAG}") +endif() diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index fc4039f608..b32d2bf1af 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -20,5 +20,5 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; * installed it needs to be read from NMODL_PROJECT_SOURCE_DIR and later * from CMAKE_INSTALL_PREFIX. */ -const std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = +std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@NMODL_PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index 486f70e03f..b6488830bb 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -15,6 +15,7 @@ * \brief Version information and units file path */ +#include #include #include #include @@ -44,12 +45,19 @@ struct Version { */ struct NrnUnitsLib { /// paths where nrnunits.lib can be found - static const std::vector NRNUNITSLIB_PATH; + static std::vector NRNUNITSLIB_PATH; /** * Return path of units database file */ static std::string get_path() { + // first look for NMODLHOME env variable + if (const char* nmodl_home = std::getenv("NMODLHOME")) { + auto path = std::string(nmodl_home) + "/share/nrnunits.lib"; + NRNUNITSLIB_PATH.emplace(NRNUNITSLIB_PATH.begin(), path); + } + + // check paths in order and return if found for (const auto& path: NRNUNITSLIB_PATH) { std::ifstream f(path.c_str()); if (f.good()) { diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 72b3688353..2bb3e2a7e9 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -60,10 +60,11 @@ add_custom_command( COMMENT "-- COPYING NMODL PYTHON FILES --") # ============================================================================= -# Copy python binding components into build directory +# Copy python binding components and examples into build directory # ============================================================================= file(GLOB NMODL_PYTHON_HELPER_FILES "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") file(COPY ${NMODL_PYTHON_HELPER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/lib/python/nmodl/) +file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/python/nmodl/) # ============================================================================= # Install python binding components diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 9acf1f4253..a69491bbd1 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -66,9 +66,17 @@ add_library(visitor_obj OBJECT ${VISITOR_SOURCES} ${VISITOR_GENERATED_SOURCES}) set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(visitor_obj lexer_obj) - add_library(visitor STATIC $) -target_link_libraries(visitor PRIVATE pybind11::embed) + +# ~~~ +# pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to +# libpython, we can use `pybind11::module` interface library from pybind11. +# ~~~ +if(NOT LINK_AGAINST_PYTHON) + target_link_libraries(visitor PRIVATE pybind11::module) +else() + target_link_libraries(visitor PRIVATE pybind11::embed) +endif() add_dependencies(visitor lexer util) From 16adde1b0aef2f26ac7ce88abd676aa68184ea9a Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 7 May 2020 00:01:06 +0200 Subject: [PATCH 260/871] Fix incompatibility issues with manylinux1 platform (BlueBrain/nmodl#320) * Fix incompatibility issues with manylinux1 platform - manylinux1 platform provides gcc 4.8.1 for wider compatibility - gcc 4.8.1 is first gnu compiler supporting `almost all` C++11 except that it lacks const_iterator overload for std::vector::erase and std::vector::insert - in this PR we introduce const_iter_cast thar turns const iterator into non-const-iterator with minimal changes to the code base - change the compatibility to gcc 4.8.2 for cmake as well as json library - change pybind11::literals::operator""_a to namespace pybind11::literals as documented in https://pybind11.readthedocs.io/en/stable/basics.html#keyword-arguments fixes BlueBrain/nmodl#319 Co-authored-by: Omar Awile NMODL Repo SHA: BlueBrain/nmodl@eb57b105a52f11fb5684fd5120c0f18408062e99 --- cmake/nmodl/CompilerHelper.cmake | 4 +-- src/nmodl/language/nodes.py | 21 +++++++++------ src/nmodl/language/templates/ast/ast.hpp | 3 +-- src/nmodl/language/templates/pybind/pyast.cpp | 2 +- .../language/templates/pybind/pysymtab.cpp | 2 +- .../language/templates/pybind/pyvisitor.cpp | 2 +- src/nmodl/pybind/pynmodl.cpp | 2 +- src/nmodl/utils/common_utils.hpp | 26 +++++++++++++++++++ src/nmodl/visitors/inline_visitor.cpp | 2 +- 9 files changed, 47 insertions(+), 17 deletions(-) diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 74a457824a..89c0222467 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -1,8 +1,8 @@ # minimal check for c++11 compliant gnu compiler if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if(NOT (GCC_VERSION VERSION_GREATER 4.9 OR GCC_VERSION VERSION_EQUAL 4.9)) - message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.9 (for c++11 support)") + if(NOT (GCC_VERSION VERSION_GREATER 4.8.2 OR GCC_VERSION VERSION_EQUAL 4.8.2)) + message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.8.2 (for C++11 support)") endif() endif() diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index e3c84057d1..381953ef66 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -245,13 +245,16 @@ def get_add_methods(self): * \\brief Erase member to {self.varname} */ {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first) {{ - return {self.varname}.erase(first); + auto first_it = const_iter_cast({self.varname}, first); + return {self.varname}.erase(first_it); }} /** * \\brief Erase members to {self.varname} */ {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last) {{ - return {self.varname}.erase(first, last); + auto first_it = const_iter_cast({self.varname}, first); + auto last_it = const_iter_cast({self.varname}, last); + return {self.varname}.erase(first_it, last_it); }} /** @@ -259,22 +262,24 @@ def get_add_methods(self): */ {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n) {{ {set_parent} - - return {self.varname}.insert(position, n); + auto pos_it = const_iter_cast({self.varname}, position); + return {self.varname}.insert(pos_it, n); }} /** * \\brief Insert members to {self.varname} */ - template - void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, InputIterator first, InputIterator last) {{ + template + void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, NodeType& to, InputIterator first, InputIterator last) {{ for (auto it = first; it != last; ++it) {{ auto& n = *it; //set parents {set_parent} }} - - {self.varname}.insert(position, first, last); + auto pos_it = const_iter_cast({self.varname}, position); + auto first_it = const_iter_cast(to, first); + auto last_it = const_iter_cast(to, last); + {self.varname}.insert(pos_it, first_it, last_it); }} /** diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index cf033d18c7..944885ad14 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -36,8 +36,7 @@ {% if node.is_abstract %} virtual {% endif %} {% endmacro %} - - +using nmodl::utils::const_iter_cast; namespace nmodl { namespace ast { diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 947cf49338..a89a11a986 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -165,7 +165,7 @@ static const char* eval_method = R"( namespace py = pybind11; using namespace nmodl::ast; using nmodl::visitor::JSONVisitor; -using pybind11::literals::operator""_a; +using namespace pybind11::literals; void init_ast_module(py::module& m) { diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 054eca56ae..48b7a038aa 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -62,7 +62,7 @@ static const char* symtabvisitor_class = R"( namespace py = pybind11; -using pybind11::literals::operator""_a; +using namespace pybind11::literals; using namespace nmodl; using namespace symtab; diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index 22eca9547a..166a951f79 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -85,7 +85,7 @@ static const char* sympy_solver_visitor_class = R"( } // namespace nmodl -using pybind11::literals::operator""_a; +using namespace pybind11::literals; namespace py = pybind11; diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 9e496c5113..9d069c1f52 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -28,7 +28,7 @@ namespace py = pybind11; -using pybind11::literals::operator""_a; +using namespace pybind11::literals; namespace nmodl { diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 2761e07921..5d08da91aa 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -48,6 +48,32 @@ T remove_extension(T const& filename) { return p > 0 && p != T::npos ? filename.substr(0, p) : filename; } +/** + * Return non-const iterator corresponding to the const_iterator in a vector + * + * Some old compilers like GCC v4.8.2 has C++11 support but missing erase and insert + * with const_iterator implementation. This is a workaround to handle build issues with + * such compilers especially on manylnux1 platform. + * + * See bug report : https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57158 + * + * \todo Remove this after move to manylinux2010 platform. + */ +#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 +template +typename std::vector::iterator +const_iter_cast(std::vector& v, typename std::vector::const_iterator iter) { + return v.begin() + (iter - v.cbegin()); +} +#else +template +typename std::vector::const_iterator +const_iter_cast(const std::vector& /*v*/, typename std::vector::const_iterator iter) { + return iter; +} +#endif + + /// Given directory path, create sub-directories bool make_path(const std::string& path); diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 2fa7ee9dad..b2b1b989ce 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -273,7 +273,7 @@ void InlineVisitor::visit_statement_block(StatementBlock& node) { for (auto& element: inlined_statements) { auto it = std::find(statements.begin(), statements.end(), element.first); if (it != statements.end()) { - node.insert_statement(it, element.second.begin(), element.second.end()); + node.insert_statement(it, element.second, element.second.begin(), element.second.end()); element.second.clear(); } } From 2d96bb34fcb39442eabc73a2d83092c47910e02a Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 7 May 2020 00:02:22 +0200 Subject: [PATCH 261/871] Support for building with Python 3.5 (BlueBrain/nmodl#321) * Support for building with Python 3.5 - NMODL can be installed with any Python 3 version except for the dependency on python interpreter v3.6 for AST classes generation. - In this PR, we find libraries and dependency modules for any python version but try to find python v3.6 for AST class generation. - This allows to build the wheel for Python 3.5 because newer python interpreter is also available on the system. NMODL Repo SHA: BlueBrain/nmodl@2342ae9c98157323d0e5ce4e52879554ad9a5753 --- cmake/nmodl/CMakeLists.txt | 45 ++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index b1ea339f0f..560b248c16 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -93,7 +93,7 @@ include(RpathHelper) # Find required python packages # ============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp 3.6 REQUIRED) +find_package(PythonInterp 3.5 REQUIRED) find_python_module(jinja2 2.9.3 REQUIRED) find_python_module(pytest 3.3.0 REQUIRED) find_python_module(sympy 1.2 REQUIRED) @@ -146,6 +146,34 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in configure_file(${NMODL_PROJECT_SOURCE_DIR}/docs/Doxyfile.in ${NMODL_PROJECT_SOURCE_DIR}/docs/Doxyfile) +# ============================================================================= +# Memory checker options and add tests +# ============================================================================= +find_program(MEMORYCHECK_COMMAND valgrind) +set(MEMORYCHECK_COMMAND_OPTIONS + "--trace-children=yes \ + --leak-check=full \ + --track-origins=yes \ + --show-possibly-lost=no") +# do not enable tests if nmodl is used as submodule +if(NOT NMODL_AS_SUBPROJECT) + include(CTest) + add_subdirectory(test) +endif() + +# ============================================================================= +# Check newer python for generating AST classes +# ============================================================================= +# ~~~ +# AST classes are generated by python scripts which require Python >= 3.6 +# In case of older python, try to find newer python interpreter +# ~~~ +if(PYTHON_VERSION_MINOR VERSION_LESS 6) + unset(PYTHONINTERP_FOUND CACHE) + unset(PYTHON_EXECUTABLE CACHE) + find_package(PythonInterp 3.6 REQUIRED) +endif() + # ============================================================================= # list of autogenerated files # ============================================================================= @@ -184,21 +212,6 @@ add_subdirectory(src/visitors) add_subdirectory(src/pybind) add_subdirectory(src/solver) -# ============================================================================= -# Memory checker options and add tests -# ============================================================================= -find_program(MEMORYCHECK_COMMAND valgrind) -set(MEMORYCHECK_COMMAND_OPTIONS - "--trace-children=yes \ - --leak-check=full \ - --track-origins=yes \ - --show-possibly-lost=no") -# do not enable tests if nmodl is used as submodule -if(NOT NMODL_AS_SUBPROJECT) - include(CTest) - add_subdirectory(test) -endif() - # ============================================================================= # Install unit database, examples and utility scripts from share # ============================================================================= From 8872ca1d2c253c5174983a79794a3b9a0bbaad00 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 15 May 2020 11:18:16 +0200 Subject: [PATCH 262/871] Consistently use spdlog (and internal fmt) (BlueBrain/nmodl#331) * Consistently use spdlog (and internal fmt) - spdlog ships it's internal fmt library - user header only target is not desirable at the moment due to link library required for some targets - we now udpated fmt and spdlog to have same fmt 6.2.0 - use spdlog consistently to avoid inclusion of incompatible headers fixes BlueBrain/nmodl#330 NMODL Repo SHA: BlueBrain/nmodl@c30ea06e3eea8f7e4055655e387c286ef75bb813 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 2 -- src/nmodl/codegen/codegen_c_visitor.hpp | 3 +-- src/nmodl/codegen/codegen_helper_visitor.cpp | 3 +-- .../templates/visitors/checkparent_visitor.cpp | 2 +- src/nmodl/symtab/symbol.cpp | 4 +--- src/nmodl/utils/common_utils.hpp | 13 +++++++------ 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 9a16e631f3..76d4577eab 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -5,8 +5,6 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - #include "codegen/codegen_acc_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index bba0df260d..f29f78308c 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -22,12 +22,11 @@ #include #include -#include - #include "codegen/codegen_info.hpp" #include "codegen/codegen_naming.hpp" #include "printer/code_printer.hpp" #include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index daedb39906..86ee2901c5 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -9,10 +9,9 @@ #include #include -#include - #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" +#include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index c65d326001..8dcb7cadce 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -12,9 +12,9 @@ #include "visitors/checkparent_visitor.hpp" #include -#include #include "ast/ast.hpp" +#include "utils/logger.hpp" namespace nmodl { namespace visitor { diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 5a8ce46b21..80fb1b6da3 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -5,10 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - #include "symtab/symbol.hpp" - +#include "utils/logger.hpp" using namespace fmt::literals; diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 5d08da91aa..fc39985204 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -60,15 +60,16 @@ T remove_extension(T const& filename) { * \todo Remove this after move to manylinux2010 platform. */ #if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 -template -typename std::vector::iterator -const_iter_cast(std::vector& v, typename std::vector::const_iterator iter) { +template +typename std::vector::iterator const_iter_cast(std::vector& v, + typename std::vector::const_iterator iter) { return v.begin() + (iter - v.cbegin()); } #else -template -typename std::vector::const_iterator -const_iter_cast(const std::vector& /*v*/, typename std::vector::const_iterator iter) { +template +typename std::vector::const_iterator const_iter_cast( + const std::vector& /*v*/, + typename std::vector::const_iterator iter) { return iter; } #endif From a79861f03b02a44caf88dd17deb55e0a9aa58b92 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Wed, 20 May 2020 02:00:40 +0200 Subject: [PATCH 263/871] Refactor code generator / Improve usage of AST classes (BlueBrain/nmodl#312) Refactor code generator / Improve usage of AST classes Compilation units now only include the AST classes they use. It essentially required the code generator to create one C++ header file per AST node. * It led to a refactoring of `src/language/code_generator.py`: * do not use global variables * split the procedural code into functions * use object oriented paradigms * do no change working directory during execution to ease file management * Improve dependency managements of Jinja tasks. A task is now considered out of date is the output file is missing or if one of its dependencies has changed: - the Jinja template(s) - the python files used by this program - the YAML files describing the NMODL language * Code genearator now create C++ headers in BUILD_DIR/ast/, one file per AST node * Code generator creates a new file: `src/language/code_generator.cmake` It defines a set CMake variables to be used elsewhere in the CMake project to manipulate inputs and outputs of the code generator. This file is updated by the code generator if a Jinja template is added or removed, or is a NMODL AST class is added for instance. The file is added to revision control on purpose to break the cyclic dependency but is not meant to be edited manually. * Some existing output files have been changed: - rename build_dir/src/ast/ast.hpp to build_dir/src/ast/all.hpp - build_dir/src/ast/ast.hpp now only provide nmodl::Ast class declaration * Ast nodes: move the definition `get_node_name` and `set_name methods` in compilation units to avoid providing child class definitions in header file. These methods were not inlined anyway because `virtual`. * Fix typos in nmodl.yaml Along the way: * More const& for parameters and return types * More `std::move` essentially in constructors * More `const noexcept` for member methods * Prefer references for Ast node parameters instead of pointers when possible * Hide some visitors `visit_*` methods that should not be called directly. * Use brackets to include catch2 header * Use `std::vector::reserve` when possible * Remove useless header inclusions * `CodegenCompatibilityVisitor`: - `unhandled_ast_types_func` member variable is now a *const static* std::map. Move definition in .cpp to avoid including headers in .hpp that are specific to the implementation - update visibility of some member methods to `private` * Fix out-of-date comment * Cleaner `git status` : copied few sections from: https://github.com/github/gitignore/blob/master/Python.gitignore * Use logging module in code generator / Fix clang-format ClangFormat is now properly executed on C++ files Level is: * warning by default * info if `-v` is passed in CLI * debug -f `-vv` is passed in CLI * Fix dependency issue in code generator * Compilation units do not import more than 3 individual ast headers They now include "ast/all.hpp" otherwise * Fix code_generator.py --verbosity option / black formatting NMODL Repo SHA: BlueBrain/nmodl@021b63fcf8b3f100eaa79007f357358864a27180 --- cmake/nmodl/CMakeLists.txt | 30 +- src/nmodl/codegen/codegen_c_visitor.cpp | 4 +- .../codegen/codegen_compatibility_visitor.cpp | 44 +- .../codegen/codegen_compatibility_visitor.hpp | 33 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 10 +- src/nmodl/codegen/codegen_info.cpp | 2 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 7 +- src/nmodl/language/CMakeLists.txt | 25 +- src/nmodl/language/code_generator.cmake | 222 +++++++++ src/nmodl/language/code_generator.py | 427 +++++++++++++++--- src/nmodl/language/nmodl.yaml | 4 +- src/nmodl/language/nodes.py | 66 ++- src/nmodl/language/templates/ast/all.hpp | 29 ++ src/nmodl/language/templates/ast/ast.cpp | 27 +- src/nmodl/language/templates/ast/ast.hpp | 380 ---------------- src/nmodl/language/templates/ast/ast_decl.hpp | 1 - src/nmodl/language/templates/ast/node.hpp | 33 ++ .../templates/ast/node_class.template | 374 +++++++++++++++ .../language/templates/code_generator.cmake | 37 ++ src/nmodl/language/templates/pybind/pyast.cpp | 3 +- src/nmodl/language/templates/pybind/pyast.hpp | 2 +- .../language/templates/pybind/pysymtab.cpp | 5 +- .../language/templates/pybind/pyvisitor.cpp | 4 +- .../templates/visitors/ast_visitor.cpp | 2 +- .../visitors/checkparent_visitor.cpp | 2 +- .../templates/visitors/json_visitor.cpp | 2 + .../templates/visitors/lookup_visitor.cpp | 2 +- .../templates/visitors/nmodl_visitor.cpp | 2 + .../templates/visitors/nmodl_visitor.hpp | 2 +- src/nmodl/lexer/CMakeLists.txt | 6 +- src/nmodl/lexer/modtoken.hpp | 2 +- src/nmodl/nmodl/main.cpp | 2 +- src/nmodl/parser/main_nmodl.cpp | 1 + src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/parser/nmodl_driver.hpp | 6 +- src/nmodl/pybind/CMakeLists.txt | 21 +- src/nmodl/pybind/pynmodl.cpp | 1 + src/nmodl/symtab/symbol.cpp | 4 +- src/nmodl/symtab/symbol.hpp | 90 ++-- src/nmodl/utils/logger.hpp | 4 +- src/nmodl/visitors/CMakeLists.txt | 17 +- .../visitors/constant_folder_visitor.cpp | 2 + src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 1 + src/nmodl/visitors/inline_visitor.cpp | 43 +- src/nmodl/visitors/inline_visitor.hpp | 13 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 4 +- .../visitors/local_var_rename_visitor.cpp | 4 + src/nmodl/visitors/localize_visitor.cpp | 2 + src/nmodl/visitors/loop_unroll_visitor.cpp | 2 + src/nmodl/visitors/main.cpp | 1 + src/nmodl/visitors/neuron_solve_visitor.cpp | 5 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 2 +- src/nmodl/visitors/rename_visitor.cpp | 2 +- src/nmodl/visitors/solve_block_visitor.cpp | 5 +- src/nmodl/visitors/steadystate_visitor.cpp | 8 +- .../visitors/sympy_conductance_visitor.cpp | 3 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 5 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 4 +- src/nmodl/visitors/units_visitor.cpp | 6 +- src/nmodl/visitors/var_usage_visitor.cpp | 2 +- src/nmodl/visitors/var_usage_visitor.hpp | 4 +- .../visitors/verbatim_var_rename_visitor.cpp | 4 +- src/nmodl/visitors/verbatim_visitor.cpp | 3 + src/nmodl/visitors/visitor_utils.cpp | 5 +- src/nmodl/visitors/visitor_utils.hpp | 2 +- test/nmodl/transpiler/lexer/tokens.cpp | 3 +- test/nmodl/transpiler/modtoken/modtoken.cpp | 3 +- test/nmodl/transpiler/newton/newton.cpp | 3 +- test/nmodl/transpiler/parser/parser.cpp | 4 +- test/nmodl/transpiler/printer/printer.cpp | 3 +- test/nmodl/transpiler/symtab/symbol_table.cpp | 5 +- test/nmodl/transpiler/units/lexer.cpp | 3 +- test/nmodl/transpiler/units/parser.cpp | 4 +- .../transpiler/visitor/constant_folder.cpp | 3 +- .../transpiler/visitor/defuse_analyze.cpp | 4 +- test/nmodl/transpiler/visitor/inline.cpp | 3 +- test/nmodl/transpiler/visitor/json.cpp | 3 +- .../transpiler/visitor/kinetic_block.cpp | 3 +- test/nmodl/transpiler/visitor/localize.cpp | 3 +- test/nmodl/transpiler/visitor/lookup.cpp | 4 +- test/nmodl/transpiler/visitor/loop_unroll.cpp | 3 +- test/nmodl/transpiler/visitor/main.cpp | 5 +- test/nmodl/transpiler/visitor/misc.cpp | 3 +- .../nmodl/transpiler/visitor/neuron_solve.cpp | 3 +- test/nmodl/transpiler/visitor/nmodl.cpp | 3 +- test/nmodl/transpiler/visitor/perf.cpp | 3 +- test/nmodl/transpiler/visitor/rename.cpp | 3 +- test/nmodl/transpiler/visitor/solve_block.cpp | 3 +- test/nmodl/transpiler/visitor/steadystate.cpp | 3 +- .../transpiler/visitor/sympy_conductance.cpp | 3 +- .../nmodl/transpiler/visitor/sympy_solver.cpp | 3 +- test/nmodl/transpiler/visitor/units.cpp | 8 +- test/nmodl/transpiler/visitor/var_usage.cpp | 3 +- test/nmodl/transpiler/visitor/verbatim.cpp | 3 +- 96 files changed, 1411 insertions(+), 762 deletions(-) create mode 100644 src/nmodl/language/code_generator.cmake create mode 100644 src/nmodl/language/templates/ast/all.hpp create mode 100644 src/nmodl/language/templates/ast/node.hpp create mode 100644 src/nmodl/language/templates/ast/node_class.template create mode 100644 src/nmodl/language/templates/code_generator.cmake diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 560b248c16..867af95525 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -50,7 +50,7 @@ set(NMODL_ClangFormat_EXCLUDES_RE ".*/ext/.*$$" CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) set(NMODL_CMakeFormat_EXCLUDES_RE - ".*/ext/.*$$" + ".*/ext/.*$$" ".*/src/language/templates/.*$$" CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen @@ -110,9 +110,10 @@ if(NMODL_PGI_COMPILER) add_compile_definitions(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK=1) endif() +include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src + ${PROJECT_BINARY_DIR}/src) include_directories( - ${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src - ${NMODL_PROJECT_SOURCE_DIR}/ext ${NMODL_PROJECT_SOURCE_DIR}/ext/fmt/include + SYSTEM ${NMODL_PROJECT_SOURCE_DIR}/ext ${NMODL_PROJECT_SOURCE_DIR}/ext/fmt/include ${NMODL_PROJECT_SOURCE_DIR}/ext/spdlog/include) # ============================================================================= @@ -177,28 +178,7 @@ endif() # ============================================================================= # list of autogenerated files # ============================================================================= -set(AUTO_GENERATED_FILES - ${PROJECT_BINARY_DIR}/src/ast/ast.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.hpp) +include(${PROJECT_SOURCE_DIR}/src/language/code_generator.cmake) add_subdirectory(src/codegen) add_subdirectory(src/language) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 8068b6f9f3..c3e4b177dd 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -5,11 +5,13 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "codegen/codegen_c_visitor.hpp" + #include #include #include -#include "codegen/codegen_c_visitor.hpp" +#include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "config/config.h" diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 68bcde6bcc..2db852ec80 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -5,12 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include -#include -#include -#include - #include "codegen/codegen_compatibility_visitor.hpp" + +#include "ast/all.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" @@ -22,6 +19,38 @@ namespace codegen { using visitor::AstLookupVisitor; +const std::map + CodegenCompatibilityVisitor::unhandled_ast_types_func( + {{AstNodeType::MATCH_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::BEFORE_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::AFTER_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::TERMINAL_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::DISCRETE_BLOCK, + &CodegenCompatibilityVisitor::return_error_with_name}, + {AstNodeType::PARTIAL_BLOCK, + &CodegenCompatibilityVisitor::return_error_with_name}, + {AstNodeType::FUNCTION_TABLE_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::CONSTANT_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::CONSTRUCTOR_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::DESTRUCTOR_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::INDEPENDENT_BLOCK, + &CodegenCompatibilityVisitor::return_error_without_name}, + {AstNodeType::SOLVE_BLOCK, + &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, + {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, + {AstNodeType::POINTER_VAR, &CodegenCompatibilityVisitor::return_error_pointer}, + {AstNodeType::BBCORE_POINTER_VAR, + &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}}); + + std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( ast::Ast& node, const std::shared_ptr& ast_node) { @@ -109,15 +138,16 @@ std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) { std::vector unhandled_ast_types; + unhandled_ast_types.reserve(unhandled_ast_types_func.size()); for (auto kv: unhandled_ast_types_func) { unhandled_ast_types.push_back(kv.first); } unhandled_ast_nodes = AstLookupVisitor().lookup(node, unhandled_ast_types); std::stringstream ss; - for (auto it: unhandled_ast_nodes) { + for (const auto& it: unhandled_ast_nodes) { auto node_type = it->get_node_type(); - ss << (this->*unhandled_ast_types_func[node_type])(node, it); + ss << (this->*unhandled_ast_types_func.find(node_type)->second)(node, it); } if (!ss.str().empty()) { logger->error("Code incompatibility detected"); diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 52398f007d..0752c4a111 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -45,37 +45,9 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { ast::Ast& node, const std::shared_ptr&); - /// Unordered_map to find the function needed to be called in + /// associated container to find the function needed to be called in /// for every ast::AstNodeType that is unsupported - std::map unhandled_ast_types_func = { - {AstNodeType::MATCH_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::BEFORE_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::AFTER_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::TERMINAL_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::DISCRETE_BLOCK, - &CodegenCompatibilityVisitor::return_error_with_name}, - {AstNodeType::PARTIAL_BLOCK, - &CodegenCompatibilityVisitor::return_error_with_name}, - {AstNodeType::FUNCTION_TABLE_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::CONSTANT_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::CONSTRUCTOR_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::DESTRUCTOR_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::INDEPENDENT_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::SOLVE_BLOCK, - &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, - {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, - {AstNodeType::POINTER_VAR, &CodegenCompatibilityVisitor::return_error_pointer}, - {AstNodeType::BBCORE_POINTER_VAR, - &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}}; + static const std::map unhandled_ast_types_func; /// Set of handled solvers by the NMODL \c C++ code generator const std::set handled_solvers{codegen::naming::CNEXP_METHOD, @@ -103,6 +75,7 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \return bool if there are unhandled nodes or not bool find_unhandled_ast_nodes(Ast& node); + private: /// Takes as parameter an std::shared_ptr, /// searches if the method used for solving is supported /// and if it is not it returns a relative error message diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 86ee2901c5..9eae86c9d9 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -5,18 +5,14 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "codegen/codegen_helper_visitor.hpp" + #include #include -#include -#include "codegen/codegen_helper_visitor.hpp" +#include "ast/all.hpp" #include "codegen/codegen_naming.hpp" -#include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/rename_visitor.hpp" - - -using namespace fmt::literals; namespace nmodl { diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 972ee05c52..b913f3cfea 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "codegen/codegen_info.hpp" + +#include "ast/all.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 58993baab2..4e99e69ac2 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -5,15 +5,16 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "codegen/codegen_ispc_visitor.hpp" + #include -#include -#include "codegen/codegen_ispc_visitor.hpp" +#include "ast/all.hpp" #include "codegen/codegen_naming.hpp" #include "symtab/symbol_table.hpp" #include "utils/logger.hpp" -#include "utils/string_utils.hpp" #include "visitors/lookup_visitor.hpp" +#include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" using namespace fmt::literals; diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index b0134cddaf..86fd69c105 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -1,30 +1,25 @@ # ============================================================================= # Command to generate AST/Visitor classes from language definition # ============================================================================= -set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) - -file(GLOB_RECURSE TEMPLATE_FILES "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.cpp" - "${NMODL_PROJECT_SOURCE_DIR}/src/language/templates/*.hpp") - -file(GLOB PYCODE "${NMODL_PROJECT_SOURCE_DIR}/src/language/*.py") +set_source_files_properties(${NMODL_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) - set(CODE_GENERATOR_OPTS --clang-format=${ClangFormat_EXECUTABLE}) + set(CODE_GENERATOR_OPTS -v --clang-format=${ClangFormat_EXECUTABLE}) if(nmodl_ClangFormat_OPTIONS) set(CODE_GENERATOR_OPTS ${CODE_GENERATOR_OPTS} --clang-format-opts ${nmodl_ClangFormat_OPTIONS}) endif(nmodl_ClangFormat_OPTIONS) endif() add_custom_command( - OUTPUT ${AUTO_GENERATED_FILES} + OUTPUT ${NMODL_GENERATED_SOURCES} COMMAND - ${PYTHON_EXECUTABLE} ARGS ${NMODL_PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" - WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}/src/language - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/nmodl.yaml - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/language/codegen.yaml - DEPENDS ${PYCODE} - DEPENDS ${TEMPLATE_FILES} + ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/code_generator.py ${CODE_GENERATOR_OPTS} + --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + DEPENDS ${CODE_GENERATOR_PY_FILES} + DEPENDS ${CODE_GENERATOR_YAML_FILES} + DEPENDS ${CODE_GENERATOR_JINJA_FILES} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/templates/code_generator.cmake COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") unset(CODE_GENERATOR_OPTS) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake new file mode 100644 index 0000000000..f4460597ec --- /dev/null +++ b/src/nmodl/language/code_generator.cmake @@ -0,0 +1,222 @@ +# +# THIS FILE IS GENERATED AND SHALL NOT BE EDITED. +# + +# cmake-format: off +set(CODE_GENERATOR_JINJA_FILES + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/all.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast_decl.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pysymtab.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyvisitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyvisitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/ast_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/ast_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/checkparent_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/checkparent_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/json_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/json_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/lookup_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/lookup_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/nmodl_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/nmodl_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/symtab_visitor.cpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/symtab_visitor.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/visitor.hpp +) + +set(CODE_GENERATOR_PY_FILES + ${PROJECT_SOURCE_DIR}/src/language/argument.py + ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${PROJECT_SOURCE_DIR}/src/language/node_info.py + ${PROJECT_SOURCE_DIR}/src/language/nodes.py + ${PROJECT_SOURCE_DIR}/src/language/parser.py + ${PROJECT_SOURCE_DIR}/src/language/utils.py +) + +set(CODE_GENERATOR_YAML_FILES + ${PROJECT_SOURCE_DIR}/src/language/codegen.yaml + ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml +) + +set(AST_GENERATED_SOURCES + ${PROJECT_BINARY_DIR}/src/ast/after_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/all.hpp + ${PROJECT_BINARY_DIR}/src/ast/argument.hpp + ${PROJECT_BINARY_DIR}/src/ast/assigned_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/assigned_definition.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast.cpp + ${PROJECT_BINARY_DIR}/src/ast/ast.hpp + ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp + ${PROJECT_BINARY_DIR}/src/ast/ba_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/ba_block_type.hpp + ${PROJECT_BINARY_DIR}/src/ast/bbcore_pointer.hpp + ${PROJECT_BINARY_DIR}/src/ast/bbcore_pointer_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/before_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/binary_expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/binary_operator.hpp + ${PROJECT_BINARY_DIR}/src/ast/block.hpp + ${PROJECT_BINARY_DIR}/src/ast/block_comment.hpp + ${PROJECT_BINARY_DIR}/src/ast/boolean.hpp + ${PROJECT_BINARY_DIR}/src/ast/breakpoint_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/compartment.hpp + ${PROJECT_BINARY_DIR}/src/ast/conductance_hint.hpp + ${PROJECT_BINARY_DIR}/src/ast/conserve.hpp + ${PROJECT_BINARY_DIR}/src/ast/constant_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/constant_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/constant_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/constructor_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/define.hpp + ${PROJECT_BINARY_DIR}/src/ast/derivative_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/derivimplicit_callback.hpp + ${PROJECT_BINARY_DIR}/src/ast/destructor_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/diff_eq_expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/discrete_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/double.hpp + ${PROJECT_BINARY_DIR}/src/ast/double_unit.hpp + ${PROJECT_BINARY_DIR}/src/ast/eigen_linear_solver_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/eigen_newton_solver_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/elctrode_current.hpp + ${PROJECT_BINARY_DIR}/src/ast/electrode_cur_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/else_if_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/else_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/expression_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/extern_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/external.hpp + ${PROJECT_BINARY_DIR}/src/ast/factor_def.hpp + ${PROJECT_BINARY_DIR}/src/ast/first_last_type_index.hpp + ${PROJECT_BINARY_DIR}/src/ast/float.hpp + ${PROJECT_BINARY_DIR}/src/ast/for_all_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/for_netcon.hpp + ${PROJECT_BINARY_DIR}/src/ast/from_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/function_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/function_call.hpp + ${PROJECT_BINARY_DIR}/src/ast/function_table_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/global.hpp + ${PROJECT_BINARY_DIR}/src/ast/global_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/identifier.hpp + ${PROJECT_BINARY_DIR}/src/ast/if_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/include.hpp + ${PROJECT_BINARY_DIR}/src/ast/independent_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/independent_definition.hpp + ${PROJECT_BINARY_DIR}/src/ast/indexed_name.hpp + ${PROJECT_BINARY_DIR}/src/ast/initial_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/integer.hpp + ${PROJECT_BINARY_DIR}/src/ast/kinetic_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/lag_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/limits.hpp + ${PROJECT_BINARY_DIR}/src/ast/lin_equation.hpp + ${PROJECT_BINARY_DIR}/src/ast/line_comment.hpp + ${PROJECT_BINARY_DIR}/src/ast/linear_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/local_list_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/local_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/lon_difuse.hpp + ${PROJECT_BINARY_DIR}/src/ast/match.hpp + ${PROJECT_BINARY_DIR}/src/ast/match_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/model.hpp + ${PROJECT_BINARY_DIR}/src/ast/mutex_lock.hpp + ${PROJECT_BINARY_DIR}/src/ast/mutex_unlock.hpp + ${PROJECT_BINARY_DIR}/src/ast/name.hpp + ${PROJECT_BINARY_DIR}/src/ast/net_receive_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/neuron_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/node.hpp + ${PROJECT_BINARY_DIR}/src/ast/non_lin_equation.hpp + ${PROJECT_BINARY_DIR}/src/ast/non_linear_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/nonspecific.hpp + ${PROJECT_BINARY_DIR}/src/ast/nonspecific_cur_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/nrn_state_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/number.hpp + ${PROJECT_BINARY_DIR}/src/ast/number_range.hpp + ${PROJECT_BINARY_DIR}/src/ast/ontology_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/param_assign.hpp + ${PROJECT_BINARY_DIR}/src/ast/param_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/paren_expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/partial_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/partial_boundary.hpp + ${PROJECT_BINARY_DIR}/src/ast/partial_equation.hpp + ${PROJECT_BINARY_DIR}/src/ast/plot_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/plot_declaration.hpp + ${PROJECT_BINARY_DIR}/src/ast/plot_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/pointer.hpp + ${PROJECT_BINARY_DIR}/src/ast/pointer_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/prime_name.hpp + ${PROJECT_BINARY_DIR}/src/ast/procedure_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/program.hpp + ${PROJECT_BINARY_DIR}/src/ast/protect_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/queue_expression_type.hpp + ${PROJECT_BINARY_DIR}/src/ast/queue_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/range.hpp + ${PROJECT_BINARY_DIR}/src/ast/range_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/react_var_name.hpp + ${PROJECT_BINARY_DIR}/src/ast/reaction_operator.hpp + ${PROJECT_BINARY_DIR}/src/ast/reaction_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/read_ion_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/reset.hpp + ${PROJECT_BINARY_DIR}/src/ast/section.hpp + ${PROJECT_BINARY_DIR}/src/ast/section_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/sens.hpp + ${PROJECT_BINARY_DIR}/src/ast/solution_expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/solve_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/state_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/statement_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/step_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/stepped.hpp + ${PROJECT_BINARY_DIR}/src/ast/string.hpp + ${PROJECT_BINARY_DIR}/src/ast/suffix.hpp + ${PROJECT_BINARY_DIR}/src/ast/table_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/terminal_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/thread_safe.hpp + ${PROJECT_BINARY_DIR}/src/ast/threadsafe_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/unary_expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/unary_operator.hpp + ${PROJECT_BINARY_DIR}/src/ast/unit.hpp + ${PROJECT_BINARY_DIR}/src/ast/unit_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/unit_def.hpp + ${PROJECT_BINARY_DIR}/src/ast/unit_state.hpp + ${PROJECT_BINARY_DIR}/src/ast/useion.hpp + ${PROJECT_BINARY_DIR}/src/ast/valence.hpp + ${PROJECT_BINARY_DIR}/src/ast/var_name.hpp + ${PROJECT_BINARY_DIR}/src/ast/verbatim.hpp + ${PROJECT_BINARY_DIR}/src/ast/watch.hpp + ${PROJECT_BINARY_DIR}/src/ast/watch_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/while_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/wrapped_expression.hpp + ${PROJECT_BINARY_DIR}/src/ast/write_ion_var.hpp +) + +set(PYBIND_GENERATED_SOURCES + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp +) + +set(VISITORS_GENERATED_SOURCES + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp + ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp + ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp +) + +set(NMODL_GENERATED_SOURCES + ${AST_GENERATED_SOURCES} + ${PYBIND_GENERATED_SOURCES} + ${VISITORS_GENERATED_SOURCES} +) +# cmake-format: on diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 8153bd1e6d..ae8848e2dd 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -6,7 +6,9 @@ # *********************************************************************** import argparse +import collections import filecmp +import itertools import logging import os from pathlib import Path, PurePath @@ -22,86 +24,367 @@ import utils -logging.basicConfig(level=logging.INFO, format='%(message)s') -parser = argparse.ArgumentParser() -parser.add_argument('--clang-format', help="Path to clang-format executable") -parser.add_argument('--clang-format-opts', help="clang-format options", nargs='+') -parser.add_argument('--base-dir') - -args = parser.parse_args() -clang_format = args.clang_format -if clang_format: - clang_format = [clang_format] - if args.clang_format_opts: - clang_format += args.clang_format_opts - -# parse NMODL and codegen definition files to get AST nodes -nmodl_nodes = LanguageParser("nmodl.yaml").parse_file() -codegen_nodes = LanguageParser("codegen.yaml").parse_file() - -# combine NMODL and codegen nodes for whole AST -nodes = nmodl_nodes -nodes.extend(x for x in codegen_nodes if x not in nodes) - -# directory containing all templates -templates_dir = Path(__file__).parent / 'templates' - -# destination directory to render templates -destination_dir = Path(args.base_dir) or Path(__file__).resolve().parent.parent - -# templates will be created and clang-formated in tempfile. -# create temp directory and copy .clang-format file for correct formating -clang_format_file = Path(Path(__file__).resolve().parent.parent.parent / ".clang-format") -temp_dir = tempfile.mkdtemp() -os.chdir(temp_dir) - -if clang_format_file.exists(): - shutil.copy2(clang_format_file, temp_dir) - -env = jinja2.Environment(loader=jinja2.FileSystemLoader(str(templates_dir)), - trim_blocks=True, - lstrip_blocks=True) -env.filters['snake_case'] = utils.to_snake_case - -updated_files = [] - -for path in templates_dir.iterdir(): - sub_dir = PurePath(path).name - (destination_dir / sub_dir).mkdir(parents=True, exist_ok=True) - for filepath in path.glob('*.[ch]pp'): - source_file = os.path.join(sub_dir, filepath.name) - destination_file = destination_dir / sub_dir / filepath.name - template = env.get_template(source_file) - content = template.render(nodes=nodes, node_info=node_info) - if destination_file.exists(): +LOGGING_FORMAT = "%(levelname)s:%(name)s: %(message)s" +LOGGER = logging.getLogger("NMODLCodeGen") + + +class CodeGenerator( + collections.namedtuple( + "Base", + [ + "base_dir", + "clang_format", + "jinja_env", + "jinja_templates_dir", + "modification_date", + "nodes", + "py_files", + "temp_dir", + "this_dir", + "yaml_files", + ], + ) +): + """Code generator application + + Attributes: + base_dir: output root directory where Jinja templates are rendered + clang_format: clang-format command line if C++ files have to be formatted, `None` otherwise + py_files: list of Path objects to the Python files used by this program + yaml_files: list of Path object to YAML files describing the NMODL language + modification_date: most recent modification date of the Python and YAML files in this directory + jinja_templates_dir: directory containing all Jinja templates + jinja_env: Jinja Environment object + this_dir: Path instance to the directory containing this file + temp_dir: path to the directory where to create temporary files + """ + + def __new__(cls, base_dir, clang_format=None): + this_dir = Path(__file__).parent.resolve() + jinja_templates_dir = this_dir / "templates" + py_files = [Path(p).relative_to(this_dir) for p in this_dir.glob("*.py")] + yaml_files = [Path(p).relative_to(this_dir) for p in this_dir.glob("*.yaml")] + self = super(CodeGenerator, cls).__new__( + cls, + base_dir=base_dir, + clang_format=clang_format, + this_dir=this_dir, + jinja_templates_dir=jinja_templates_dir, + jinja_env=jinja2.Environment( + loader=jinja2.FileSystemLoader(str(jinja_templates_dir)), + keep_trailing_newline=True, + trim_blocks=True, + lstrip_blocks=True, + ), + temp_dir=tempfile.mkdtemp(), + py_files=py_files, + yaml_files=yaml_files, + modification_date=max( + [os.path.getmtime(p) for p in itertools.chain(py_files, yaml_files)] + ), + nodes=LanguageParser(this_dir / "nmodl.yaml").parse_file(), + ) + + self.jinja_env.filters["snake_case"] = utils.to_snake_case + + # copy clang-format configuration file in the temporary directory is present + # in the repository root. + cf_config = Path(__file__).parent.parent.parent / ".clang-format" + if cf_config.exists(): + shutil.copy2(cf_config, self.temp_dir) + + # combine NMODL and codegen nodes for whole AST + codegen_nodes = LanguageParser(self.this_dir / "codegen.yaml").parse_file() + self.nodes.extend(x for x in codegen_nodes if x not in self.nodes) + + return self + + def jinja_template(self, path): + """Construct a Jinja template object + + Args: + path: Path object of a file inside the Jinja template directory + + Returns: + A jinja template object + """ + name = str(path.relative_to(self.jinja_templates_dir)) + return self.jinja_env.get_template(name) + + def _cmake_deps_task(self, tasks): + """"Construct the JinjaTask generating the CMake file exporting all dependencies + + Args: + tasks: list of JinjaTask objects + + Returns: + An instance of JinjaTask + """ + input = self.jinja_templates_dir / "code_generator.cmake" + output = self.this_dir / input.name + inputs = set() + outputs = dict() + for task in tasks: + inputs.add(task.input.relative_to(self.jinja_templates_dir)) + dir, name = task.output.relative_to(self.base_dir).parts + outputs.setdefault(dir, []).append(name) + + return JinjaTask( + app=self, + input=input, + output=output, + context=dict( + templates=inputs, + outputs=outputs, + py_files=self.py_files, + yaml_files=self.yaml_files, + ), + extradeps=None, + ) + + def workload(self): + """Compute the list of Jinja tasks to perform + + Returns: + A list of JinjaTask objects + """ + # special template "ast/node.hpp used to generate multiple .hpp files + node_hpp_tpl = self.jinja_templates_dir / "ast" / "node.hpp" + # special template only included by other templates + node_class_tpl = self.jinja_templates_dir / "ast" / "node_class.template" + # Jinja templates that should be ignored + ignored_templates = {node_class_tpl} + # Additional dependencies Path -> [Path, ...] + extradeps = collections.defaultdict( + list, + { + self.jinja_templates_dir / "ast" / "ast.hpp": [node_class_tpl], + node_hpp_tpl: [node_class_tpl], + }, + ) + + tasks = [] + for path in self.jinja_templates_dir.iterdir(): + sub_dir = PurePath(path).name + # create output directory if missing + (self.base_dir / sub_dir).mkdir(parents=True, exist_ok=True) + for filepath in path.glob("*.[ch]pp"): + if filepath in ignored_templates: + continue + if filepath == node_hpp_tpl: + # special treatment for this template. + # generate one C++ header per AST node type + for node in self.nodes: + task = JinjaTask( + app=self, + input=filepath, + output=self.base_dir / node.cpp_header, + context=dict(node=node), + extradeps=extradeps[filepath], + ) + tasks.append(task) + yield task + else: + task = JinjaTask( + app=self, + input=filepath, + output=self.base_dir / sub_dir / filepath.name, + context=dict(nodes=self.nodes, node_info=node_info), + extradeps=extradeps[filepath], + ) + tasks.append(task) + yield task + yield self._cmake_deps_task(tasks) + + +class JinjaTask( + collections.namedtuple( + "JinjaTask", ["app", "input", "output", "context", "extradeps"] + ) +): + """Generate a file with Jinja + + Attributes: + app: CodeGenerator object + input: Path to the template on the filesystem + output: Path to the destination file to create + context: dict containing the variables passed to Jinja renderer + """ + + def execute(self): + """"Perform the Jinja task + + Execute Jinja renderer if the output file is out-of-date. + + Returns: + True if the output file has been created or modified, False otherwise + """ + if self.out_of_date: + return self.render() + return False + + @property + def logger(self): + return LOGGER.getChild(str(self.output)) + + @property + def out_of_date(self): + """Check if the output file have to be generated + + Returns: + True if the output file is missing or if one of its dependencies has changed: + - the Jinja template + - the python files used by this program + - the YAML files describing the NMODL language + """ + if not self.output.exists(): + self.logger.debug("output does not exist") + return True + output_mdate = max(os.path.getctime(self.output), os.path.getmtime(self.output)) + if self.app.modification_date > output_mdate: + self.logger.debug("output is out-of-date") + return True + + deps = self.extradeps or [] + deps.append(self.input) + for dep in deps: + if os.path.getmtime(dep) > output_mdate: + return True + self.logger.debug("output is up-to-date") + + return False + + def format_output(self, file): + """Format a given file + + On applies to C++ files. Use ClangFormat if enabled + + Arguments: + file: path to the file to format. filename might be temporary + language: c++ or cmake + + """ + if self.language == "c++" and self.app.clang_format: + LOGGER.debug("Formatting C++ file %s", str(file)) + subprocess.check_call(self.app.clang_format + ["-i", str(file)]) + + @property + def language(self): + suffix = self.output.suffix + if self.output.name == "CMakeLists.txt": + return "cmake" + if suffix in [".hpp", ".cpp"]: + return "c++" + elif suffix == ".cmake": + return "cmake" + raise Exception("Unexpected output file extension: " + suffix) + + def render(self): + """Call Jinja renderer to create the output file and mark it read-only + + The output file is updated only if missing or if the new content + is different. + + Returns: + True if the output has been created or modified, False otherwise + """ + updated = False + content = self.app.jinja_template(self.input).render(**self.context) + if self.output.exists(): # render template in temporary file and update target file # ONLY if different (to save a lot of build time) - fd, tmp_path = tempfile.mkstemp(dir=temp_dir) - os.write(fd, content.encode('utf-8')) + fd, tmp_path = tempfile.mkstemp(dir=self.app.temp_dir) + os.write(fd, content.encode("utf-8")) os.close(fd) - if clang_format: - subprocess.check_call(clang_format + ['-i', tmp_path]) - if not filecmp.cmp(str(destination_file), tmp_path): + self.format_output(Path(tmp_path)) + if not filecmp.cmp(str(self.output), tmp_path, shallow=False): + self.logger.debug("previous output differs, updating it") # ensure destination file has write permissions - mode = os.stat(destination_file).st_mode + mode = self.output.stat().st_mode rm_write_mask = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH - os.chmod(destination_file, mode | rm_write_mask) - shutil.move(tmp_path, destination_file) - updated_files.append(str(filepath.name)) + self.output.chmod(mode | rm_write_mask) + shutil.copy2(tmp_path, self.output) + updated = True else: - with destination_file.open('w') as fd: + with self.output.open("w") as fd: fd.write(content) - updated_files.append(str(filepath.name)) - if clang_format: - subprocess.check_call(clang_format + ['-i', destination_file]) + self.format_output(self.output) + updated = True # remove write permissions on the generated file - mode = os.stat(destination_file).st_mode - rm_write_mask = ~ (stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) - os.chmod(destination_file, mode & rm_write_mask) + mode = self.output.stat().st_mode + rm_write_mask = ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) + self.output.chmod(mode & rm_write_mask) + return updated + + +def parse_args(args=None): + """Parse arguments in command line and post-process them + + Arguments: + args: arguments given in CLI + """ + parser = argparse.ArgumentParser() + parser.add_argument("--clang-format", help="Path to clang-format executable") + parser.add_argument("--clang-format-opts", help="clang-format options", nargs="+") + parser.add_argument("--base-dir", help="output root directory") + parser.add_argument( + "-v", "--verbosity", action="count", default=0, help="increase output verbosity" + ) + args = parser.parse_args(args=args) + + # construct clang-format command line to use, if provided + if args.clang_format: + args.clang_format = [args.clang_format] + if args.clang_format_opts: + args.clang_format += args.clang_format_opts + + # destination directory to render templates + args.base_dir = ( + Path(args.base_dir).resolve() + if args.base_dir + else Path(__file__).resolve().parent.parent + ) + return args + + +def configure_logger(verbosity): + """Prepare root logger + + Arguments: + verbosity: integer greater than 0 indicating the verbosity level + """ + level = logging.WARNING + if verbosity == 1: + level = logging.INFO + elif verbosity > 1: + level = logging.DEBUG + logging.basicConfig(level=level, format=LOGGING_FORMAT) + + +def main(args=None): + args = parse_args(args) + configure_logger(args.verbosity) + + codegen = CodeGenerator(clang_format=args.clang_format, base_dir=args.base_dir) + num_tasks = 0 + tasks_performed = [] + for task in codegen.workload(): + num_tasks += 1 + if task.execute(): + tasks_performed.append(task) + if tasks_performed: + LOGGER.info("Updated out-of-date files %i/%i", len(tasks_performed), num_tasks) + padding = max( + len(str(task.input.relative_to(codegen.this_dir))) + for task in tasks_performed + ) + for task in tasks_performed: + input = task.input.relative_to(codegen.this_dir) + LOGGER.debug(f" %-{padding}s -> %s", input, task.output) + else: + LOGGER.info("Nothing do to") -if updated_files: - logging.info(' Updating out of date template files : %s', ' '.join(updated_files)) -# remove temp directory -shutil.rmtree(temp_dir) +if __name__ == "__main__": + main() diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index a03cf57bad..cc127d63be 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1165,7 +1165,7 @@ - BinaryOperator: members: - value: - brief: "Opearator" + brief: "Operator" type: BinaryOp brief: "Operator used in ast::BinaryExpression" @@ -1198,7 +1198,7 @@ brief: "LHS of the binary expression" type: Expression - op: - brief: "Opearator" + brief: "Operator" type: BinaryOperator - rhs: brief: "RHS of the binary expression" diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 381953ef66..26424e8993 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -40,6 +40,22 @@ def get_data_type_name(self): """ return type name for the node """ return node_info.DATA_TYPES[self.class_name] + @property + def cpp_header(self): + """Path to C++ header file of this class relative to BUILD_DIR""" + return "ast/" + to_snake_case(self.class_name) + ".hpp" + + @property + def cpp_fence(self): + """Preprocessor macro to use to prevent symbol redefinition + + #ifndef {{ node.cpp_fence }} + #define {{ node.cpp_fence }} + // ... + # endif + """ + return "NMODL_AST_" + to_snake_case(self.class_name).upper() + '_HPP' + @property def is_statement_block_node(self): return self.class_name == node_info.STATEMENT_BLOCK_NODE @@ -305,24 +321,32 @@ def get_add_methods(self): s = textwrap.dedent(method) return s - def get_node_name_method(self): + def get_node_name_method_declaration(self): s = '' if self.get_node_name: # string node should be evaluated and hence eval() method - method_name = "eval" if self.is_string_node else "get_node_name" method = f""" - /** - * \\brief Return name of of the node - * - * Some ast nodes have a member marked designated as node name. For example, - * in case of this ast::{self.class_name} has {self.varname} designated as a - * node name. - * - * @return name of the node as std::string - * - * \\sa Ast::get_node_type_name - */ - virtual std::string get_node_name() const override {{ + /** + * \\brief Return name of the node + * + * Some ast nodes have a member marked designated as node name. For example, + * in case of this ast::{self.class_name} has {self.varname} designated as a + * node name. + * + * @return name of the node as std::string + * + * \\sa Ast::get_node_type_name + */ + std::string get_node_name() const override;""" + s = textwrap.dedent(method) + return s + + def get_node_name_method_definition(self, parent): + s = '' + if self.get_node_name: + # string node should be evaluated and hence eval() method + method_name = "eval" if self.is_string_node else "get_node_name" + method = f"""std::string {parent.class_name}::get_node_name() const {{ return {self.varname}->{method_name}(); }}""" s = textwrap.dedent(method) @@ -451,6 +475,20 @@ def __init__(self, args): self.url = args.url self.children = [] + def cpp_header_deps(self): + """Get list of C++ headers required by the C++ declaration of this class + + Returns: + string list of paths to C++ headers relative to BUILD_DIR + """ + dependent_classes = set() + if self.base_class: + dependent_classes.add(self.base_class) + for child in self.children: + if child.is_ptr_excluded_node or child.is_vector: + dependent_classes.add(child.class_name) + return ["ast/{}.hpp".format(to_snake_case(clazz)) for clazz in dependent_classes] + @property def ast_enum_name(self): return to_snake_case(self.class_name).upper() diff --git a/src/nmodl/language/templates/ast/all.hpp b/src/nmodl/language/templates/ast/all.hpp new file mode 100644 index 0000000000..dfbf02880b --- /dev/null +++ b/src/nmodl/language/templates/ast/all.hpp @@ -0,0 +1,29 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + +#pragma once + +/** + * \dir + * \brief Auto generated AST Implementations + * + * \file + * \brief Auto generated AST classes declaration + */ + +#include "ast/ast.hpp" + +{% for node in nodes %} +#ifndef {{ node.cpp_fence }} +#define {{ node.cpp_fence }} +{% include "ast/node_class.template" %} +#endif // !{{ node.cpp_fence }} +{% endfor %} diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index d6d20a1bf4..cc95aa313c 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -9,7 +9,7 @@ /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. /// -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "symtab/symbol_table.hpp" /** @@ -80,6 +80,10 @@ void Ast::set_parent(Ast* p) { /// {{node.class_name}} member functions definition /// + {% for child in node.children %} + {{ child.get_node_name_method_definition(node) }} + {% endfor %} + void {{ node.class_name }}::visit_children(visitor::Visitor& v) { {% for child in node.non_base_members %} {% if child.is_vector %} @@ -103,7 +107,11 @@ void Ast::set_parent(Ast* p) { {% endfor %} } - {% if node.children %} + void {{ node.class_name }}::accept(visitor::Visitor& v) { + v.visit_{{ node.class_name | snake_case }}(*this); + } + + {% if node.children %} {{ node.ctor_definition() }} @@ -141,6 +149,12 @@ void Ast::set_parent(Ast* p) { set_parent_in_children(); } + {% if node.is_name_node %} + void {{ node.class_name }}::set_name(const std::string& name) { + value->set(name); + } + {% endif %} + /// set this parent in the children void {{ node.class_name }}::set_parent_in_children() { @@ -169,14 +183,13 @@ void Ast::set_parent(Ast* p) { } + {% endif %} - {% endif %} - - {% for child in node.children %} + {% for child in node.children %} {{ child.get_setter_method_definition(node.class_name) }} - {% endfor %} + {% endfor %} - {% endfor %} + {% endfor %} } // namespace ast } // namespace nmodl diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 944885ad14..19b1436de3 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -19,8 +19,6 @@ * \brief Auto generated AST classes declaration */ -#include -#include #include #include @@ -30,12 +28,6 @@ #include "utils/common_utils.hpp" #include "visitors/visitor.hpp" - -{# add virtual qualifier if node is an abstract class #} -{% macro virtual(node) -%} - {% if node.is_abstract %} virtual {% endif %} -{% endmacro %} - using nmodl::utils::const_iter_cast; namespace nmodl { @@ -349,377 +341,5 @@ struct Ast: public std::enable_shared_from_this { virtual void set_parent(Ast* p); }; -/** @} */ // end of ast_class - - /** - * @addtogroup ast_class - * @ingroup ast - * @{ - */ - - {% for node in nodes %} - /** - * \brief {{ node.brief }} - * - * {{- node.get_description() -}} - */ - class {{ node.class_name }} : public {{ node.base_class }} { - {% if node.private_members() %} - private: - {% for member in node.private_members() %} - {{ '/// ' + member[3] }} - {% if member[2] is none %} - {{ member[0] }} {{ member[1] }}; - {% else %} - {{ member[0] }} {{ member[1] }} = {{ member[2] }}; - {% endif %} - {% endfor %} - - {% endif %} - public: - {% for member in node.public_members() %} - {{ '/// ' + member[3] }} - {% if member[2] is none %} - {{ member[0] }} {{ member[1] }}; - {% else %} - {{ member[0] }} {{ member[1] }} = {{ member[2] }}; - {% endif %} - {% endfor %} - - /// \name Ctor & dtor - /// \{ - - {% if node.children %} - {{ node.ctor_declaration() }} - {% if node.has_ptr_children() %} - {{ node.ctor_shrptr_declaration() }} - {% endif %} - {{ node.class_name }}(const {{ node.class_name }}& obj); - {% endif %} - - {% if node.requires_default_constructor %} - {{ node.class_name}}() = default; - {% endif %} - - virtual ~{{ node.class_name }}() {} - - /// \} - - /// \name Not implemented - /// \{ - - {% if node.is_base_block_node %} - virtual const ArgumentVector& get_parameters() const { - throw std::runtime_error("get_parameters not implemented"); - } - {% endif %} - - {% if node.is_number_node %} - {{ virtual(node) }}double to_double() { - throw std::runtime_error("to_double not implemented"); - } - {% endif %} - - /// \} - - /** - *\brief Check if the ast node is an instance of ast::{{ node.class_name }} - * @return true as object is of type ast::{{ node.class_name }} - */ - bool is_{{ node.class_name | snake_case }} () const noexcept override { - return true; - } - - /** - * \brief Return a copy of the current node - * - * Recursively make a new copy/clone of the current node including - * all members and return a pointer to the node. This is used for - * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the - * ast. - * - * @return pointer to the clone/copy of the current node - */ - {{ virtual(node) }} {{ node.class_name }}* clone() const override { - return new {{ node.class_name }}(*this); - } - - - /// \name Getters - /// \{ - - /** - * \brief Return type (ast::AstNodeType) of ast node - * - * Every node in the ast has a type defined in ast::AstNodeType and this - * function is used to retrieve the same. - * - * \return ast node type i.e. ast::AstNodeType::{{ node.ast_enum_name }} - * - * \sa Ast::get_node_type_name - */ - {{ virtual(node) }} AstNodeType get_node_type() const noexcept override { - return AstNodeType::{{ node.ast_enum_name }}; - } - - /** - * \brief Return type (ast::AstNodeType) of ast node as std::string - * - * Every node in the ast has a type defined in ast::AstNodeType. - * This type name can be returned as a std::string for printing - * node to text/json form. - * - * @return name of the node type as a string i.e. "{{ node.class_name }}" - * - * \sa Ast::get_node_name - */ - {{ virtual(node) }} std::string get_node_type_name() const noexcept override { - return "{{ node.class_name }}"; - } - - {% if node.nmodl_name %} - /** - * \brief Return NMODL statement of ast node as std::string - * - * Every node is related to a special statement in the NMODL. This - * statement can be returned as a std::string for printing to - * text/json form. - * - * @return name of the statement as a string i.e. "{{ node.nmodl_name }}" - * - * \sa Ast::get_nmodl_name - */ - {{ virtual(node) }} std::string get_nmodl_name() const noexcept override { - return "{{ node.nmodl_name }}"; - } - {% endif %} - - /** - * \brief Get std::shared_ptr from `this` pointer of the current ast node - */ - {{ virtual(node) }} std::shared_ptr get_shared_ptr() override { - return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); - } - - /** - * \brief Get std::shared_ptr from `this` pointer of the current ast node - */ - {{ virtual(node) }} std::shared_ptr get_shared_ptr() const override { - return std::static_pointer_cast(shared_from_this()); - } - - {% if node.has_token %} - /** - * \brief Return associated token for the current ast node - * - * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor - * can insert new nodes in the ast as a solution of ODEs. In this case, we return - * nullptr to store in the nmodl::symtab::SymbolTable. - * - * @return pointer to token if exist otherwise nullptr - */ - const {{ virtual(node) }}ModToken* get_token() const override { - return token.get(); - } - {% endif %} - - {% if node.is_symtab_needed %} - /** - * \brief Return associated symbol table for the current ast node - * - * Only certain ast nodes (e.g. inherited from ast::Block) have associated - * symbol table. These nodes have nmodl::symtab::SymbolTable as member - * and it can be accessed using this method. - * - * @return pointer to the symbol table - * - * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor - */ - symtab::SymbolTable* get_symbol_table() const override { - return symtab; - } - - {% endif %} - - {% if node.is_program_node %} - /** - * \brief Return global symbol table for the mod file - */ - symtab::ModelSymbolTable* get_model_symbol_table() { - return &model_symtab; - } - {% endif %} - - {# doxygen for these methods is handled by nodes.py #} - {% for child in node.children %} - {{ child.get_add_methods() }} - - {{ child.get_node_name_method() }} - - {{ child.get_getter_method(node.class_name) }} - - - {% endfor %} - - /// \} - - - /// \name Setters - /// \{ - - {% if node.is_name_node %} - /** - * \brief Set name for the current ast node - * - * Some ast nodes have a member marked designated as node name (e.g. nodes - * derived from ast::Identifier). This method is used to set new name for those - * nodes. This useful for passes like nmodl::visitor::RenameVisitor. - * - * \sa Ast::get_node_type_name Ast::get_node_name - */ - {{ virtual(node) }}void set_name(const std::string& name) override { - value->set(name); - } - {% endif %} - - {% if node.has_token %} - /** - * \brief Set token for the current ast node - */ - void set_token(const ModToken& tok) { token = std::make_shared(tok); } - {% endif %} - - {% if node.is_symtab_needed %} - /** - * \brief Set symbol table for the current ast node - * - * Top level, block scoped nodes store symbol table in the ast node. - * nmodl::visitor::SymtabVisitor then used this method to setup symbol table - * for every node in the ast. - * - * \sa nmodl::visitor::SymtabVisitor - */ - void set_symbol_table(symtab::SymbolTable* newsymtab) override { - symtab = newsymtab; - } - {% endif %} - - {# if node is base data type but not enum then add set method #} - {% if node.is_data_type_node and not node.is_enum_node %} - /** - * \brief Set new value to the current ast node - * \sa {{ node.class_name }}::eval - */ - void set({{ node.get_data_type_name() }} _value) { - value = _value; - } - {% endif %} - - {# doxygen for these methods is handled by nodes.py #} - {% for child in node.children %} - {{ child.get_setter_method_declaration(node.class_name) }} - {% endfor %} - - /// \} - - - /// \name Visitor - /// \{ - - /** - * \brief visit children i.e. member variables of current node using provided visitor - * - * Different nodes in the AST have different members (i.e. children). This method - * recursively visits children using provided visitor. - * - * @param v Concrete visitor that will be used to recursively visit children - * - * \sa Ast::visit_children for example. - */ - {{ virtual(node) }} void visit_children (visitor::Visitor& v) override; - - /** - * \brief accept (or visit) the current AST node using provided visitor - * - * Instead of visiting children of AST node, like Ast::visit_children, - * accept allows to visit the current node itself using provided concrete - * visitor. - * - * @param v Concrete visitor that will be used to recursively visit node - * - * \sa Ast::accept for example. - */ - {{ virtual(node) }} void accept(visitor::Visitor& v) override { - v.visit_{{ node.class_name | snake_case }}(*this); - } - - /// \} - - - {% if node.is_base_class_number_node %} - /** - * \brief Negate the value of current ast node - * - * Parser parse `-x` in two parts : `x` and then `-`. Once second token - * `-` is encountered, the corresponding value of ast node needs to be - * multiplied by `-1` for ast::Number node types. - */ - void negate() override { - value = {{ node.negation }}value; - } - - /** - * \brief Return value of the current ast node as double - */ - double to_double() override { return value; } - {% endif %} - - {% if node.is_data_type_node %} - {# if node is of enum type then return enum value #} - {% if node.is_enum_node %} - /** - * \brief Return enum value in string form - * - * Enum variables (e.g. ast::BinaryOp, ast::UnitStateType) have - * string representation when they are converted from AST back to - * NMODL. This method is used to return corresponding string representation. - */ - std::string eval() const { return {{ - node.get_data_type_name() }}Names[value]; - } - {# but if basic data type then eval return their value #} - {% else %} - /** - * \brief Return value of the ast node - * - * Base data type nodes like ast::Inetegr, ast::Double can be evaluated - * to their literal values. This method is used to access underlying - * literal value. - * - * \sa {{ node.class_name }}::set - */ - {{ node.get_data_type_name() }} eval() const { - return value; - } - {% endif %} - {% endif %} - - {% if node.children %} - private: - /** - *\brief Set this object as parent for all the children - * - * This should be called in every object (with children) constructor - * to set parents. Since it is called only in the constructors it - * should not be virtual to avoid ambiguities (issue #295). - */ - void set_parent_in_children(); - {% endif %} - }; - - {% endfor %} - - /** @} */ // end of ast_class - } // namespace ast } // namespace nmodl diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index d8835db897..cbca65e692 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -12,7 +12,6 @@ #pragma once #include -#include #include /// \file diff --git a/src/nmodl/language/templates/ast/node.hpp b/src/nmodl/language/templates/ast/node.hpp new file mode 100644 index 0000000000..fdbb2818e7 --- /dev/null +++ b/src/nmodl/language/templates/ast/node.hpp @@ -0,0 +1,33 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// + +#pragma once + +#ifndef {{ node.cpp_fence }} +# define {{ node.cpp_fence }} + +/** + * \dir + * \brief Auto generated AST Implementations + * + * \file + * \brief Auto generated AST classes declaration + */ + +#include "ast/ast_decl.hpp" +{% for header in node.cpp_header_deps() %} +#include "{{ header }}" +{% endfor %} + +{% include "ast/node_class.template" %} + +#endif // {{ node.cpp_fence }} + diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template new file mode 100644 index 0000000000..2a73258ab9 --- /dev/null +++ b/src/nmodl/language/templates/ast/node_class.template @@ -0,0 +1,374 @@ +{# + this Jinja template is not used to directly generate + a file but included by other templates. +#} + +{# add virtual qualifier if node is an abstract class #} +{% macro virtual(node) -%} + {% if node.is_abstract %} virtual {% endif %} +{% endmacro %} + +namespace nmodl { +namespace ast { + +/** + * @addtogroup ast_class + * @ingroup ast + * @{ + */ + +/** + * \brief {{ node.brief }} + * + * {{- node.get_description() -}} + */ +class {{ node.class_name }} : public {{ node.base_class }} { +{% if node.private_members() %} + private: + {% for member in node.private_members() %} + {{ '/// ' + member[3] }} + {% if member[2] is none %} + {{ member[0] }} {{ member[1] }}; + {% else %} + {{ member[0] }} {{ member[1] }} = {{ member[2] }}; + {% endif %} + {% endfor %} +{% endif %} + + public: + {% for member in node.public_members() %} + {{ '/// ' + member[3] }} + {% if member[2] is none %} + {{ member[0] }} {{ member[1] }}; + {% else %} + {{ member[0] }} {{ member[1] }} = {{ member[2] }}; + {% endif %} + {% endfor %} + + /// \name Ctor & dtor + /// \{ + + {% if node.children %} + {{ node.ctor_declaration() }} + {% if node.has_ptr_children() %} + {{ node.ctor_shrptr_declaration() }} + {% endif %} + {{ node.class_name }}(const {{ node.class_name }}& obj); + {% endif %} + + {% if node.requires_default_constructor %} + {{ node.class_name}}() = default; + {% endif %} + + virtual ~{{ node.class_name }}() = default; + + /// \} + + /// \name Not implemented + /// \{ + + {% if node.is_base_block_node %} + virtual const ArgumentVector& get_parameters() const { + throw std::runtime_error("get_parameters not implemented"); + } + {% endif %} + + {% if node.is_number_node %} + {{ virtual(node) }}double to_double() { + throw std::runtime_error("to_double not implemented"); + } + {% endif %} + + /// \} + + /** + * \brief Check if the ast node is an instance of ast::{{ node.class_name }} + * \return true as object is of type ast::{{ node.class_name }} + */ + bool is_{{ node.class_name | snake_case }} () const noexcept override { + return true; + } + + /** + * \brief Return a copy of the current node + * + * Recursively make a new copy/clone of the current node including + * all members and return a pointer to the node. This is used for + * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the + * ast. + * + * @return pointer to the clone/copy of the current node + */ + {{ virtual(node) }} {{ node.class_name }}* clone() const override { + return new {{ node.class_name }}(*this); + } + + /// \name Getters + /// \{ + + /** + * \brief Return type (ast::AstNodeType) of ast node + * + * Every node in the ast has a type defined in ast::AstNodeType and this + * function is used to retrieve the same. + * + * \return ast node type i.e. ast::AstNodeType::{{ node.ast_enum_name }} + * + * \sa Ast::get_node_type_name + */ + {{ virtual(node) }} AstNodeType get_node_type() const noexcept override { + return AstNodeType::{{ node.ast_enum_name }}; + } + + /** + * \brief Return type (ast::AstNodeType) of ast node as std::string + * + * Every node in the ast has a type defined in ast::AstNodeType. + * This type name can be returned as a std::string for printing + * node to text/json form. + * + * \return name of the node type as a string i.e. "{{ node.class_name }}" + * + * \sa Ast::get_node_name + */ + {{ virtual(node) }} std::string get_node_type_name() const noexcept override { + return "{{ node.class_name }}"; + } + + {% if node.nmodl_name %} + /** + * \brief Return NMODL statement of ast node as std::string + * + * Every node is related to a special statement in the NMODL. This + * statement can be returned as a std::string for printing to + * text/json form. + * + * \return name of the statement as a string i.e. "{{ node.nmodl_name }}" + * + * \sa Ast::get_nmodl_name + */ + {{ virtual(node) }} std::string get_nmodl_name() const noexcept override { + return "{{ node.nmodl_name }}"; + } + {% endif %} + + /** + * \brief Get std::shared_ptr from `this` pointer of the current ast node + */ + {{ virtual(node) }} std::shared_ptr get_shared_ptr() override { + return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); + } + + /** + * \brief Get std::shared_ptr from `this` pointer of the current ast node + */ + {{ virtual(node) }} std::shared_ptr get_shared_ptr() const override { + return std::static_pointer_cast(shared_from_this()); + } + + {% if node.has_token %} + /** + * \brief Return associated token for the current ast node + * + * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor + * can insert new nodes in the ast as a solution of ODEs. In this case, we return + * nullptr to store in the nmodl::symtab::SymbolTable. + * + * \return pointer to token if exist otherwise nullptr + */ + const {{ virtual(node) }}ModToken* get_token() const noexcept override { + return token.get(); + } + {% endif %} + + {% if node.is_symtab_needed %} + /** + * \brief Return associated symbol table for the current ast node + * + * Only certain ast nodes (e.g. inherited from ast::Block) have associated + * symbol table. These nodes have nmodl::symtab::SymbolTable as member + * and it can be accessed using this method. + * + * \return pointer to the symbol table + * + * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor + */ + symtab::SymbolTable* get_symbol_table() const override { + return symtab; + } + + {% endif %} + + {% if node.is_program_node %} + /** + * \brief Return global symbol table for the mod file + */ + symtab::ModelSymbolTable* get_model_symbol_table() { + return &model_symtab; + } + {% endif %} + + {# doxygen for these methods is handled by nodes.py #} + {% for child in node.children %} + {{ child.get_add_methods() }} + + {{ child.get_node_name_method_declaration() }} + + {{ child.get_getter_method(node.class_name) }} + + {% endfor %} + + /// \} + + /// \name Setters + /// \{ + + {% if node.is_name_node %} + /** + * \brief Set name for the current ast node + * + * Some ast nodes have a member marked designated as node name (e.g. nodes + * derived from ast::Identifier). This method is used to set new name for those + * nodes. This useful for passes like nmodl::visitor::RenameVisitor. + * + * \sa Ast::get_node_type_name Ast::get_node_name + */ + {{ virtual(node) }}void set_name(const std::string& name) override; + {% endif %} + + {% if node.has_token %} + /** + * \brief Set token for the current ast node + */ + void set_token(const ModToken& tok) { token = std::make_shared(tok); } + {% endif %} + + {% if node.is_symtab_needed %} + /** + * \brief Set symbol table for the current ast node + * + * Top level, block scoped nodes store symbol table in the ast node. + * nmodl::visitor::SymtabVisitor then used this method to setup symbol table + * for every node in the ast. + * + * \sa nmodl::visitor::SymtabVisitor + */ + void set_symbol_table(symtab::SymbolTable* newsymtab) override { + symtab = newsymtab; + } + {% endif %} + + {# if node is base data type but not enum then add set method #} + {% if node.is_data_type_node and not node.is_enum_node %} + /** + * \brief Set new value to the current ast node + * \sa {{ node.class_name }}::eval + */ + void set({{ node.get_data_type_name() }} _value) { + value = _value; + } + {% endif %} + + {# doxygen for these methods is handled by nodes.py #} + {% for child in node.children %} + {{ child.get_setter_method_declaration(node.class_name) }} + {% endfor %} + + /// \} + + /// \name Visitor + /// \{ + + /** + * \brief visit children i.e. member variables of current node using provided visitor + * + * Different nodes in the AST have different members (i.e. children). This method + * recursively visits children using provided visitor. + * + * \param v Concrete visitor that will be used to recursively visit children + * + * \sa Ast::visit_children for example. + */ + {{ virtual(node) }} void visit_children (visitor::Visitor& v) override; + + /** + * \brief accept (or visit) the current AST node using provided visitor + * + * Instead of visiting children of AST node, like Ast::visit_children, + * accept allows to visit the current node itself using provided concrete + * visitor. + * + * \param v Concrete visitor that will be used to recursively visit node + * + * \sa Ast::accept for example. + */ + {{ virtual(node) }} void accept(visitor::Visitor& v) override; + + /// \} + + {% if node.is_base_class_number_node %} + /** + * \brief Negate the value of current ast node + * + * Parser parse `-x` in two parts : `x` and then `-`. Once second token + * `-` is encountered, the corresponding value of ast node needs to be + * multiplied by `-1` for ast::Number node types. + */ + void negate() override { + value = {{ node.negation }}value; + } + + /** + * \brief Return value of the current ast node as double + */ + double to_double() override { return value; } + {% endif %} + + {% if node.is_data_type_node %} + {# if node is of enum type then return enum value #} + {% if node.is_enum_node %} + /** + * \brief Return enum value in string form + * + * Enum variables (e.g. ast::BinaryOp, ast::UnitStateType) have + * string representation when they are converted from AST back to + * NMODL. This method is used to return corresponding string representation. + */ + std::string eval() const { + return {{ node.get_data_type_name() }}Names[value]; + } + + {# but if basic data type then eval return their value #} + {% else %} + /** + * \brief Return value of the ast node + * + * Base data type nodes like ast::Inetegr, ast::Double can be evaluated + * to their literal values. This method is used to access underlying + * literal value. + * + * \sa {{ node.class_name }}::set + */ + {{ node.get_data_type_name() }} eval() const { + return value; + } + {% endif %} + {% endif %} + + {% if node.children %} + private: + /** + * \brief Set this object as parent for all the children + * + * This should be called in every object (with children) constructor + * to set parents. Since it is called only in the constructors it + * should not be virtual to avoid ambiguities (issue #295). + */ + void set_parent_in_children(); + {% endif %} +}; + +/** @} */ // end of ast_class + +} // namespace ast +} // namespace nmodl diff --git a/src/nmodl/language/templates/code_generator.cmake b/src/nmodl/language/templates/code_generator.cmake new file mode 100644 index 0000000000..892e3d96ca --- /dev/null +++ b/src/nmodl/language/templates/code_generator.cmake @@ -0,0 +1,37 @@ +# +# THIS FILE IS GENERATED AND SHALL NOT BE EDITED. +# + +# cmake-format: off +set(CODE_GENERATOR_JINJA_FILES +{% for template in templates | sort %} + ${PROJECT_SOURCE_DIR}/src/language/templates/{{ template }} +{% endfor %} +) + +set(CODE_GENERATOR_PY_FILES +{% for file in py_files | sort %} + ${PROJECT_SOURCE_DIR}/src/language/{{ file }} +{% endfor %} +) + +set(CODE_GENERATOR_YAML_FILES +{% for file in yaml_files | sort %} + ${PROJECT_SOURCE_DIR}/src/language/{{ file }} +{% endfor %} +) + +{% for dir, files in outputs.items() | sort(attribute="1") %} +set({{ dir | upper }}_GENERATED_SOURCES + {% for file in files | sort %} + ${PROJECT_BINARY_DIR}/src/{{ dir }}/{{ file }} + {% endfor %} +) + +{% endfor %} +set(NMODL_GENERATED_SOURCES +{% for dir in outputs.keys() | sort %} + ${{ '{' }}{{ dir | upper }}_GENERATED_SOURCES} +{% endfor %} +) +# cmake-format: on diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index a89a11a986..3d3f308a19 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -9,10 +9,11 @@ /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. /// +#include "pybind/pyast.hpp" + #include #include -#include "pybind/pyast.hpp" #include "visitors/json_visitor.hpp" diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index e3e8380cc4..0c6ae34320 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -14,7 +14,7 @@ #include #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "lexer/modtoken.hpp" #include "symtab/symbol_table.hpp" diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 48b7a038aa..14848e0ea4 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -9,16 +9,17 @@ /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. /// +#include "visitors/symtab_visitor.hpp" + #include #include #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "pybind/pybind_utils.hpp" #include "symtab/symbol.hpp" #include "symtab/symbol_properties.hpp" #include "symtab/symbol_table.hpp" -#include "visitors/symtab_visitor.hpp" #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index 166a951f79..fc97449151 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -9,13 +9,15 @@ /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. /// +#include "pybind/pyvisitor.hpp" + #include #include #include #include +#include "ast/all.hpp" #include "pybind/pybind_utils.hpp" -#include "pybind/pyvisitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index 571511756d..328c98516a 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -11,7 +11,7 @@ #include "visitors/ast_visitor.hpp" -#include "ast/ast.hpp" +#include "ast/all.hpp" namespace nmodl { diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index 8dcb7cadce..175b79e092 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -13,7 +13,7 @@ #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "utils/logger.hpp" namespace nmodl { diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 8f3f0e1055..215db9748a 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -10,6 +10,8 @@ /// #include "visitors/json_visitor.hpp" + +#include "ast/all.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index f95e3f42b7..3e907b8bf3 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -13,7 +13,7 @@ #include -#include "ast/ast.hpp" +#include "ast/all.hpp" namespace nmodl { diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 580d1c1068..740a1ef4fb 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -10,6 +10,8 @@ /// #include "visitors/nmodl_visitor.hpp" + +#include "ast/all.hpp" #include "visitors/nmodl_visitor_helper.ipp" diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 3af27a1886..a76e3b19f9 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -67,7 +67,7 @@ class NmodlPrintVisitor: public Visitor { template void visit_element(const std::vector& elements, - std::string separator, + const std::string& separator, bool program, bool statement); }; diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 31edd62de7..3466f10973 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -13,8 +13,6 @@ set(BISON_GENERATED_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp) -set(AST_SOURCE_FILES ${PROJECT_BINARY_DIR}/src/ast/ast.hpp ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) - set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.hpp ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) @@ -31,7 +29,7 @@ set(DIFFEQ_DRIVER_FILES set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) -set_source_files_properties(${AST_SOURCE_FILES} PROPERTIES GENERATED TRUE) +set_source_files_properties(${AST_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) @@ -177,7 +175,7 @@ add_custom_command( # ============================================================================= add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} - ${AST_SOURCE_FILES} ${UNIT_SOURCE_FILES}) + ${AST_GENERATED_SOURCES} ${UNIT_SOURCE_FILES}) set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 9959cbca8c..c01bd8dc03 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -75,7 +75,7 @@ class ModToken { , external(ext) {} ModToken(std::string name, int token, LocationType& pos) - : name(name) + : name(std::move(name)) , token(token) , pos(pos) {} diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 1587166c20..d82f45cc50 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -12,7 +12,7 @@ #include "CLI/CLI.hpp" #include "pybind11/embed.h" -#include "ast/ast_decl.hpp" +#include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_compatibility_visitor.hpp" diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index ac7b5ec2a1..38128c7586 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -8,6 +8,7 @@ #include "CLI/CLI.hpp" +#include "ast/program.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 701cae5ac2..e12954cac5 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -28,7 +28,7 @@ /** to include parser's header file, one has to include ast definitions */ %code requires { - #include "ast/ast.hpp" + #include "ast/all.hpp" } /** use C++ parser interface of bison */ diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 6111b5552c..26ba3e9974 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -109,17 +109,17 @@ class NmodlDriver { verbose = b; } - bool is_verbose() const { + bool is_verbose() const noexcept { return verbose; } /// return previously parsed AST otherwise nullptr - std::shared_ptr get_ast() const { + const std::shared_ptr& get_ast() const noexcept { return astRoot; } /// set new ast root - void set_ast(ast::Program* node) { + void set_ast(ast::Program* node) noexcept { astRoot.reset(node); } }; diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 2bb3e2a7e9..bf235663fa 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -1,7 +1,7 @@ # ============================================================================= # pybind targets # ============================================================================= -set_source_files_properties(${AUTO_GENERATED_FILES} PROPERTIES GENERATED TRUE) +set_source_files_properties(${PYBIND_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) # build nmodl python module under lib/python/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nmodl) @@ -18,26 +18,15 @@ foreach( list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() -set(PYNMODL_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp) - -set(PYNMODL_HEADERS - ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp) - # Note that LTO causes link time errors with GCC 8. To avoid this, we disable LTO for pybind using # NO_EXTRAS. See #266. pybind11_add_module( _nmodl NO_EXTRAS - ${PYNMODL_HEADERS} - ${PYNMODL_SOURCES} + ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp + ${PYBIND_GENERATED_SOURCES} $ $ $ diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 9d069c1f52..d2c19d8ed1 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -12,6 +12,7 @@ #include #include +#include "ast/program.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "pybind/pybind_utils.hpp" diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 80fb1b6da3..bf99495e93 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -17,7 +17,7 @@ using syminfo::NmodlType; using syminfo::Status; -bool Symbol::is_variable() { +bool Symbol::is_variable() const noexcept { // if symbol has one of the following property then it // is considered as variable in the NMODL // clang-format off @@ -40,7 +40,7 @@ bool Symbol::is_variable() { return has_any_property(var_properties); } -std::string Symbol::to_string() { +std::string Symbol::to_string() const { std::string s(name); if (properties != NmodlType::empty) { s += " [Properties : {}]"_format(syminfo::to_string(properties)); diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index da168ac2f3..b50c8a53a3 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -113,38 +113,38 @@ class Symbol { Symbol() = delete; Symbol(std::string name, ast::Ast* node) - : name(name) + : name(std::move(name)) , node(node) {} Symbol(std::string name, ModToken token) - : name(name) - , token(token) {} + : name(std::move(name)) + , token(std::move(token)) {} Symbol(std::string name, ast::Ast* node, ModToken token) - : name(name) + : name(std::move(name)) , node(node) - , token(token) {} + , token(std::move(token)) {} /// \} /// increment read count - void read() { + void read() noexcept { read_count++; } /// increment write count - void write() { + void write() noexcept { write_count++; } /// \name Setter /// \{ - void set_scope(std::string s) { + void set_scope(const std::string& s) { scope = s; } - void set_id(int i) { + void set_id(int i) noexcept { id = i; } @@ -155,7 +155,7 @@ class Symbol { * as we want to keep track of original name and not intermediate * renames */ - void set_name(std::string new_name) { + void set_name(const std::string& new_name) { if (renamed_from.empty()) { renamed_from = name; } @@ -168,13 +168,13 @@ class Symbol { * Prime variable will appear in different block and could have * multiple derivative orders. We have to store highest order. */ - void set_order(int new_order) { + void set_order(int new_order) noexcept { if (new_order > order) { order = new_order; } } - void set_definition_order(int order) { + void set_definition_order(int order) noexcept { definition_order = order; } @@ -182,16 +182,16 @@ class Symbol { value = std::make_shared(val); } - void set_as_array(int len) { + void set_as_array(int len) noexcept { array = true; length = len; } - void set_num_values(int n) { + void set_num_values(int n) noexcept { num_values = n; } - void set_original_name(std::string new_name) { + void set_original_name(const std::string& new_name) { renamed_from = new_name; } @@ -200,59 +200,59 @@ class Symbol { /// \name Getter /// \{ - int get_length() { + int get_length() const noexcept { return length; } - int get_num_values() { + int get_num_values() const noexcept { return num_values; } - std::string get_original_name() { + const std::string& get_original_name() const noexcept { return renamed_from; } - std::shared_ptr get_value() { + const std::shared_ptr& get_value() const noexcept { return value; } - std::string get_name() { + const std::string& get_name() const noexcept { return name; } - int get_id() { + int get_id() const noexcept { return id; } - std::string get_scope() { + const std::string& get_scope() const noexcept { return scope; } - syminfo::NmodlType get_properties() { + const syminfo::NmodlType& get_properties() const noexcept { return properties; } - syminfo::Status get_status() { + const syminfo::Status& get_status() const noexcept { return status; } - ast::Ast* get_node() { + ast::Ast* get_node() const noexcept { return node; } - ModToken get_token() { + ModToken get_token() const noexcept { return token; } - int get_read_count() const { + int get_read_count() const noexcept { return read_count; } - int get_write_count() const { + int get_write_count() const noexcept { return write_count; } - int get_definition_order() const { + int get_definition_order() const noexcept { return definition_order; } @@ -269,80 +269,80 @@ class Symbol { * * \sa nmodl::details::NEURON_VARIABLES */ - bool is_external_variable() { + bool is_external_variable() const noexcept { return (properties == syminfo::NmodlType::extern_neuron_variable || properties == syminfo::NmodlType::extern_method); } /// check if symbol has any of the given property - bool has_any_property(syminfo::NmodlType new_properties) { + bool has_any_property(syminfo::NmodlType new_properties) const noexcept { return static_cast(properties & new_properties); } /// check if symbol has all of the given properties - bool has_all_properties(syminfo::NmodlType new_properties) { + bool has_all_properties(syminfo::NmodlType new_properties) const noexcept { return ((properties & new_properties) == new_properties); } /// check if symbol has any of the status - bool has_any_status(syminfo::Status new_status) { + bool has_any_status(syminfo::Status new_status) const noexcept { return static_cast(status & new_status); } /// check if symbol has all of the status - bool has_all_status(syminfo::Status new_status) { + bool has_all_status(syminfo::Status new_status) const noexcept { return ((status & new_status) == new_status); } /// add new properties to symbol - void add_properties(syminfo::NmodlType new_properties) { + void add_properties(syminfo::NmodlType new_properties) noexcept { properties |= new_properties; } /// add new property to symbol - void add_property(syminfo::NmodlType property) { + void add_property(syminfo::NmodlType property) noexcept { properties |= property; } /// mark symbol as inlined (in case of procedure/function) - void mark_inlined() { + void mark_inlined() noexcept { status |= syminfo::Status::inlined; } /// mark symbol as newly created (in case of new variable) - void mark_created() { + void mark_created() noexcept { status |= syminfo::Status::created; } - void mark_renamed() { + void mark_renamed() noexcept { status |= syminfo::Status::renamed; } /// mark symbol as localized (e.g. from RANGE to LOCAL conversion) - void mark_localized() { + void mark_localized() noexcept { status |= syminfo::Status::localized; } - void mark_thread_safe() { + void mark_thread_safe() noexcept { status |= syminfo::Status::thread_safe; } /// mark symbol as newly created variable for the STATE variable /// this is used with legacy euler/derivimplicit solver where DState /// variables are created - void created_from_state() { + void created_from_state() noexcept { mark_created(); status |= syminfo::Status::from_state; } - bool is_array() { + bool is_array() const noexcept { return array; } /// check if symbol is a variable in nmodl - bool is_variable(); + bool is_variable() const noexcept; - std::string to_string(); + std::string to_string() const; }; /** @} */ // end of sym_tab diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index fa1535df7b..73513d6a8f 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -14,8 +14,8 @@ // clang-format off // disable clang-format to keep order of inclusion -#include "spdlog/spdlog.h" -#include "spdlog/sinks/stdout_color_sinks.h" +#include +#include // clang-format on namespace nmodl { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index a69491bbd1..ab19a22bb1 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -43,26 +43,13 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp) -set(VISITOR_GENERATED_SOURCES - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.hpp) - -set_source_files_properties(${VISITOR_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) +set_source_files_properties(${VISITORS_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) # ============================================================================= # Visitor library and executable # ============================================================================= include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -add_library(visitor_obj OBJECT ${VISITOR_SOURCES} ${VISITOR_GENERATED_SOURCES}) +add_library(visitor_obj OBJECT ${VISITOR_SOURCES} ${VISITORS_GENERATED_SOURCES}) set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(visitor_obj lexer_obj) diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 36cc8ea253..e5ea062190 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "visitors/constant_folder_visitor.hpp" + +#include "ast/all.hpp" #include "utils/logger.hpp" #include "visitors/visitor_utils.hpp" diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index cf5c6c641d..3b1895fdd7 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -10,7 +10,7 @@ #include #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "utils/logger.hpp" namespace nmodl { diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 8e3e4a05d4..4fff96f836 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -16,6 +16,7 @@ #include #include "printer/json_printer.hpp" +#include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index b2b1b989ce..942326bc4f 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -7,8 +7,9 @@ #include "visitors/inline_visitor.hpp" -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "parser/c11_driver.hpp" +#include "utils/logger.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -113,20 +114,14 @@ void InlineVisitor::inline_arguments(StatementBlock& inlined_block, } -/** - * Inline function/procedure call - * @param callee : ast node representing function/procedure definition being called - * @param node : function/procedure call node - * @param caller : statement block containing function call - */ -bool InlineVisitor::inline_function_call(ast::Block* callee, - ast::FunctionCall* node, - ast::StatementBlock* caller) { - const auto& function_name = callee->get_node_name(); +bool InlineVisitor::inline_function_call(ast::Block& callee, + ast::FunctionCall& node, + ast::StatementBlock& caller) { + const auto& function_name = callee.get_node_name(); /// do nothing if we can't inline given procedure/function - if (!can_inline_block(*callee->get_statement_block())) { - std::cerr << "Can not inline function call to " + function_name << '\n'; + if (!can_inline_block(*callee.get_statement_block())) { + logger->warn("Can not inline function call to {}", function_name); return false; } @@ -134,9 +129,9 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, /// because in case of procedure inlining they can conflict with /// global variables used in procedure being inlined LocalVarRenameVisitor v; - v.visit_statement_block(*caller); + v.visit_statement_block(caller); - const auto& caller_arguments = node->get_arguments(); + const auto& caller_arguments = node.get_arguments(); std::string new_varname = get_new_name(function_name, "in", inlined_variables); /// check if caller statement could be replaced @@ -144,14 +139,14 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, /// need to add local variable for function calls or for procedure call if it is part of /// expression (standalone procedure calls don't need return statement) - if (callee->is_function_block() || to_replace == false) { + if (callee.is_function_block() || to_replace == false) { /// create new variable which will be used for returning value from inlined block auto name = new ast::Name(new ast::String(new_varname)); ModToken tok; name->set_token(tok); - const ast::StatementVector& statements = caller->get_statements(); - auto local_list_statement = get_local_list_statement(*caller); + const ast::StatementVector& statements = caller.get_statements(); + auto local_list_statement = get_local_list_statement(caller); /// each block should already have local statement if (local_list_statement == nullptr) { throw std::logic_error("got local statement as nullptr"); @@ -160,7 +155,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, } /// get a copy of function/procedure body - auto inlined_block = callee->get_statement_block()->clone(); + auto inlined_block = callee.get_statement_block()->clone(); /// function definition has function name as return value. we have to rename /// it with new variable name @@ -171,10 +166,10 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, inlined_block->set_symbol_table(nullptr); /// each argument is added as new assignment statement - inline_arguments(*inlined_block, callee->get_parameters(), caller_arguments); + inline_arguments(*inlined_block, callee.get_parameters(), caller_arguments); /// to return value from procedure we have to add new variable - if (callee->is_procedure_block() && to_replace == false) { + if (callee.is_procedure_block() && to_replace == false) { add_return_variable(*inlined_block, new_varname); } @@ -188,7 +183,7 @@ bool InlineVisitor::inline_function_call(ast::Block* callee, } /// variable name which will replace the function call that we just inlined - replaced_fun_calls[node] = new_varname; + replaced_fun_calls[&node] = new_varname; return true; } @@ -217,10 +212,10 @@ void InlineVisitor::visit_function_call(FunctionCall& node) { if (function_definition->is_procedure_block()) { auto proc = (ProcedureBlock*) function_definition; - inlined = inline_function_call(proc, &node, caller_block); + inlined = inline_function_call(*proc, node, *caller_block); } else if (function_definition->is_function_block()) { auto func = (FunctionBlock*) function_definition; - inlined = inline_function_call(func, &node, caller_block); + inlined = inline_function_call(*func, node, *caller_block); } if (inlined) { diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 62cb067753..4043b75927 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -165,10 +165,15 @@ class InlineVisitor: public AstVisitor { /// this is possible for standalone function/procedure call as statement bool can_replace_statement(const std::shared_ptr& statement); - /// inline function/procedure into caller block - bool inline_function_call(ast::Block* callee, - ast::FunctionCall* node, - ast::StatementBlock* caller); + /** + * inline function/procedure into caller block + * @param callee : ast node representing function/procedure definition being called + * @param node : function/procedure call node + * @param caller : statement block containing function call + */ + bool inline_function_call(ast::Block& callee, + ast::FunctionCall& node, + ast::StatementBlock& caller); /// add assignement statements into given statement block to inline arguments void inline_arguments(ast::StatementBlock& inlined_block, diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 4483fb5d4b..006e09d1b8 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -5,9 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - #include "kinetic_block_visitor.hpp" + +#include "ast/all.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index ef2d772961..b7b078b6ab 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -6,6 +6,10 @@ *************************************************************************/ #include "visitors/local_var_rename_visitor.hpp" + +#include "ast/expression_statement.hpp" +#include "ast/local_list_statement.hpp" +#include "ast/statement_block.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index f6498a25b7..e2ca782b46 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -9,6 +9,8 @@ #include +#include "ast/all.hpp" +#include "symtab/symbol_properties.hpp" #include "utils/logger.hpp" #include "visitors/defuse_analyze_visitor.hpp" diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 4919c4ee22..f11d5e918c 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -6,6 +6,8 @@ *************************************************************************/ #include "visitors/loop_unroll_visitor.hpp" + +#include "ast/all.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 5d69311431..3db54e074d 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -10,6 +10,7 @@ #include "CLI/CLI.hpp" #include "pybind11/embed.h" +#include "ast/program.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index d67380f875..eab791ab8f 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -5,14 +5,13 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include "visitors/neuron_solve_visitor.hpp" +#include "ast/all.hpp" #include "codegen/codegen_naming.hpp" #include "parser/diffeq_driver.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitors/neuron_solve_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index 22ce62985e..d68c180366 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -26,7 +26,7 @@ namespace visitor { template void NmodlPrintVisitor::visit_element(const std::vector& elements, - std::string separator, + const std::string& separator, bool program, bool statement) { for (auto iter = elements.begin(); iter != elements.end(); iter++) { diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 4fc26f1897..77df1180ee 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -9,7 +9,7 @@ #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "printer/json_printer.hpp" diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 9c7ef23f9c..ab0d985f65 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -7,7 +7,7 @@ #include "visitors/rename_visitor.hpp" -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "parser/c11_driver.hpp" diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index a7ae00bfb0..69683b24bf 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -7,9 +7,10 @@ #include "visitors/solve_block_visitor.hpp" -#include "ast/ast.hpp" +#include + +#include "ast/all.hpp" #include "codegen/codegen_naming.hpp" -#include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" namespace nmodl { diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 4a852d4473..e910b6e3a3 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -5,18 +5,14 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - +#include "visitors/steadystate_visitor.hpp" +#include "ast/all.hpp" #include "codegen/codegen_naming.hpp" -#include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "utils/string_utils.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/steadystate_visitor.hpp" #include "visitors/visitor_utils.hpp" - namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index c30b6eceea..a1b4a1e7de 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -8,12 +8,11 @@ #include "visitors/sympy_conductance_visitor.hpp" #include -#include #include #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 902a34a637..2905cfc282 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -5,18 +5,19 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include "visitors/sympy_solver_visitor.hpp" #include +#include "ast/all.hpp" #include "codegen/codegen_naming.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/sympy_solver_visitor.hpp" #include "visitors/visitor_utils.hpp" + namespace py = pybind11; using namespace py::literals; diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 21e38b3a39..8484653b4d 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -82,8 +82,8 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { if (node->is_param_assign()) { auto parameter = dynamic_cast(node); - auto value = parameter->get_value(); - auto name = parameter->get_name(); + const auto& value = parameter->get_value(); + const auto& name = parameter->get_name(); if (value) { symbol->set_value(value->to_double()); } diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index cd91b2c4df..783239c9f2 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -5,12 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include -#include - -#include "ast/ast.hpp" #include "visitors/units_visitor.hpp" +#include "ast/all.hpp" + /** * \file * \brief AST Visitor to parse the ast::UnitDefs and ast::FactorDefs from the mod file diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 6e3189738a..4343b430d7 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -9,7 +9,7 @@ #include -#include "ast/ast.hpp" +#include "ast/name.hpp" namespace nmodl { diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index b706c5fcb7..7f59bc9501 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -38,12 +38,12 @@ class VarUsageVisitor: protected AstVisitor { std::string var_name; bool used = false; + void visit_name(ast::Name& node) override; + public: VarUsageVisitor() = default; bool variable_used(ast::Node& node, std::string name); - - void visit_name(ast::Name& node) override; }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 9eb3890dde..2be54672b2 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -7,7 +7,9 @@ #include "visitors/verbatim_var_rename_visitor.hpp" -#include "ast/ast.hpp" +#include "ast/statement_block.hpp" +#include "ast/string.hpp" +#include "ast/verbatim.hpp" #include "parser/c11_driver.hpp" #include "src/utils/logger.hpp" diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 5c7e103c55..a42a5de0b3 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -6,8 +6,11 @@ *************************************************************************/ #include "visitors/verbatim_visitor.hpp" + #include +#include "ast/string.hpp" +#include "ast/verbatim.hpp" namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 33fd046686..cf00a5c39c 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -5,13 +5,14 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "visitor_utils.hpp" + #include #include #include -#include "ast/ast.hpp" +#include "ast/all.hpp" #include "parser/nmodl_driver.hpp" -#include "visitor_utils.hpp" #include "visitors/json_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index bb7ec50f4f..82e71f12b6 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -16,7 +16,7 @@ #include #include -#include "ast/ast.hpp" +#include namespace nmodl { namespace visitor { diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/lexer/tokens.cpp index 2b23086e07..a197a97cb0 100644 --- a/test/nmodl/transpiler/lexer/tokens.cpp +++ b/test/nmodl/transpiler/lexer/tokens.cpp @@ -9,7 +9,8 @@ #include -#include "catch/catch.hpp" +#include + #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/modtoken/modtoken.cpp index b17aa075fd..4a1742d774 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/modtoken/modtoken.cpp @@ -10,7 +10,8 @@ #include #include -#include "catch/catch.hpp" +#include + #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/newton/newton.cpp b/test/nmodl/transpiler/newton/newton.cpp index c21db4e47f..6637afa408 100644 --- a/test/nmodl/transpiler/newton/newton.cpp +++ b/test/nmodl/transpiler/newton/newton.cpp @@ -9,7 +9,8 @@ #include -#include "catch/catch.hpp" +#include + #include "nmodl/nmodl.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/parser/parser.cpp index e0a6311a81..8aafa3c855 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/parser/parser.cpp @@ -10,7 +10,9 @@ #include #include -#include "catch/catch.hpp" +#include + +#include "ast/program.hpp" #include "lexer/modtoken.hpp" #include "parser/diffeq_driver.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/printer/printer.cpp index 72b88eeddc..a4e996baba 100644 --- a/test/nmodl/transpiler/printer/printer.cpp +++ b/test/nmodl/transpiler/printer/printer.cpp @@ -9,7 +9,8 @@ #include -#include "catch/catch.hpp" +#include + #include "printer/json_printer.hpp" using nmodl::printer::JSONPrinter; diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index aae02952d4..024a65659b 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -9,8 +9,9 @@ #include -#include "ast/ast.hpp" -#include "catch/catch.hpp" +#include + +#include "ast/program.hpp" #include "symtab/symbol.hpp" #include "symtab/symbol_table.hpp" diff --git a/test/nmodl/transpiler/units/lexer.cpp b/test/nmodl/transpiler/units/lexer.cpp index 9902231d6a..edc4a22b9c 100644 --- a/test/nmodl/transpiler/units/lexer.cpp +++ b/test/nmodl/transpiler/units/lexer.cpp @@ -9,7 +9,8 @@ #include -#include "catch/catch.hpp" +#include + #include "lexer/unit_lexer.hpp" #include "parser/unit_driver.hpp" diff --git a/test/nmodl/transpiler/units/parser.cpp b/test/nmodl/transpiler/units/parser.cpp index 08886af492..058aec9cec 100644 --- a/test/nmodl/transpiler/units/parser.cpp +++ b/test/nmodl/transpiler/units/parser.cpp @@ -7,11 +7,11 @@ #define CATCH_CONFIG_MAIN -#include #include #include -#include "catch/catch.hpp" +#include + #include "config/config.h" #include "parser/diffeq_driver.hpp" #include "parser/unit_driver.hpp" diff --git a/test/nmodl/transpiler/visitor/constant_folder.cpp b/test/nmodl/transpiler/visitor/constant_folder.cpp index 2cd5e2cf5a..216758dd8e 100644 --- a/test/nmodl/transpiler/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/visitor/constant_folder.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/visitor/defuse_analyze.cpp index 143b11afe4..133c347ac3 100644 --- a/test/nmodl/transpiler/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/visitor/defuse_analyze.cpp @@ -5,15 +5,15 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/visitor/inline.cpp b/test/nmodl/transpiler/visitor/inline.cpp index b031251d40..89e406b391 100644 --- a/test/nmodl/transpiler/visitor/inline.cpp +++ b/test/nmodl/transpiler/visitor/inline.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/json.cpp b/test/nmodl/transpiler/visitor/json.cpp index b60a30a3d3..1f205d87b7 100644 --- a/test/nmodl/transpiler/visitor/json.cpp +++ b/test/nmodl/transpiler/visitor/json.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "visitors/json_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/test/nmodl/transpiler/visitor/kinetic_block.cpp b/test/nmodl/transpiler/visitor/kinetic_block.cpp index 5799ab06b4..0aa92c8688 100644 --- a/test/nmodl/transpiler/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/visitor/kinetic_block.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/localize.cpp b/test/nmodl/transpiler/visitor/localize.cpp index 8ba4f9911a..af37d4985e 100644 --- a/test/nmodl/transpiler/visitor/localize.cpp +++ b/test/nmodl/transpiler/visitor/localize.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/lookup.cpp b/test/nmodl/transpiler/visitor/lookup.cpp index 7bda85cb52..73f42f4b5b 100644 --- a/test/nmodl/transpiler/visitor/lookup.cpp +++ b/test/nmodl/transpiler/visitor/lookup.cpp @@ -5,12 +5,12 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" #include "visitors/visitor_utils.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/visitor/loop_unroll.cpp b/test/nmodl/transpiler/visitor/loop_unroll.cpp index e97f27cd1a..1f33246c3b 100644 --- a/test/nmodl/transpiler/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/visitor/loop_unroll.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/main.cpp b/test/nmodl/transpiler/visitor/main.cpp index c4095d9ceb..79bdd89433 100644 --- a/test/nmodl/transpiler/visitor/main.cpp +++ b/test/nmodl/transpiler/visitor/main.cpp @@ -7,10 +7,11 @@ #define CATCH_CONFIG_RUNNER -#include "catch/catch.hpp" -#include "utils/logger.hpp" +#include #include +#include "utils/logger.hpp" + using namespace nmodl; int main(int argc, char* argv[]) { diff --git a/test/nmodl/transpiler/visitor/misc.cpp b/test/nmodl/transpiler/visitor/misc.cpp index e58f1d1ffe..951df8bab4 100644 --- a/test/nmodl/transpiler/visitor/misc.cpp +++ b/test/nmodl/transpiler/visitor/misc.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/neuron_solve.cpp b/test/nmodl/transpiler/visitor/neuron_solve.cpp index 37eab38c41..2bb689abc6 100644 --- a/test/nmodl/transpiler/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/visitor/neuron_solve.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/nmodl.cpp b/test/nmodl/transpiler/visitor/nmodl.cpp index 57f9dbc52e..58ed6b248e 100644 --- a/test/nmodl/transpiler/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/visitor/nmodl.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" diff --git a/test/nmodl/transpiler/visitor/perf.cpp b/test/nmodl/transpiler/visitor/perf.cpp index d94aee0460..0f704ae1d8 100644 --- a/test/nmodl/transpiler/visitor/perf.cpp +++ b/test/nmodl/transpiler/visitor/perf.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/rename.cpp b/test/nmodl/transpiler/visitor/rename.cpp index 96abbb94ab..b55e9ff3d5 100644 --- a/test/nmodl/transpiler/visitor/rename.cpp +++ b/test/nmodl/transpiler/visitor/rename.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/solve_block.cpp b/test/nmodl/transpiler/visitor/solve_block.cpp index 3b62d49eab..616d4b821b 100644 --- a/test/nmodl/transpiler/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/visitor/solve_block.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/steadystate.cpp b/test/nmodl/transpiler/visitor/steadystate.cpp index 55830644ac..61b6e8108d 100644 --- a/test/nmodl/transpiler/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/visitor/steadystate.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/visitor/sympy_conductance.cpp index 78369941dd..7dc1812f8d 100644 --- a/test/nmodl/transpiler/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/visitor/sympy_conductance.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/visitor/sympy_solver.cpp index 8335962357..2b8ea31bc2 100644 --- a/test/nmodl/transpiler/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/visitor/sympy_solver.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/units.cpp b/test/nmodl/transpiler/visitor/units.cpp index 3bec45029d..da72c7376d 100644 --- a/test/nmodl/transpiler/visitor/units.cpp +++ b/test/nmodl/transpiler/visitor/units.cpp @@ -5,17 +5,17 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include -#include "ast/ast.hpp" +#include "ast/double.hpp" +#include "ast/factor_def.hpp" +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "src/config/config.h" #include "test/utils/nmodl_constructs.hpp" #include "test/utils/test_utils.hpp" -#include "utils/logger.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/lookup_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" #include "visitors/units_visitor.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/visitor/var_usage.cpp b/test/nmodl/transpiler/visitor/var_usage.cpp index 8e56fddd1d..67f34edd4f 100644 --- a/test/nmodl/transpiler/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/visitor/var_usage.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/utils/test_utils.hpp" #include "visitors/var_usage_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/verbatim.cpp b/test/nmodl/transpiler/visitor/verbatim.cpp index 1d36706150..24d39a9e02 100644 --- a/test/nmodl/transpiler/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/visitor/verbatim.cpp @@ -5,8 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/verbatim_visitor.hpp" From 44bc474271dd76966734f49102fad230f2eef3bc Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 22 May 2020 10:31:40 +0200 Subject: [PATCH 264/871] Remove INDEPENDENT from compatibility_visitor checks (issue BlueBrain/nmodl#303) (BlueBrain/nmodl#304) Remove INDEPENDENT from compatibility_visitor checks * Independent should not be supported by NMODL as stated in: https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html * Add doxygen explanation on ignored independent_block NMODL Repo SHA: BlueBrain/nmodl@75c8ed326201413757bf1ea64b0c3010aa2ff62b --- src/nmodl/codegen/codegen_compatibility_visitor.cpp | 2 -- src/nmodl/codegen/codegen_compatibility_visitor.hpp | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 2db852ec80..8eb580cd8b 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -41,8 +41,6 @@ const std::map &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::DESTRUCTOR_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::INDEPENDENT_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::SOLVE_BLOCK, &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 0752c4a111..5008ee19ad 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -37,6 +37,9 @@ using namespace ast; /** * \class CodegenCompatibilityVisitor * \brief %Visitor for printing compatibility issues of the mod file + * + * INDEPENDENT_BLOCK is ignored (no error raised) as stated in: + * https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html */ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// Typedef for defining FunctionPointer that points to the From e52bd99035bd07b8162a0afcfb7dbaa484ab3f1b Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 26 May 2020 22:31:11 +0200 Subject: [PATCH 265/871] Buil system improvements and introducing scikit-build * Build docs from setup.py and cleanup old code * Update travis to up-to-date toolchains * Fixes to travis configuration * Doc build with "python setup.py build_ext --inplace docs" * Add MPI installaiton for coreneuron in travis * use travis_terminate Co-authored-by: katta Co-authored-by: Pramod S Kumbhar Co-authored-by: Omar Awile NMODL Repo SHA: BlueBrain/nmodl@7c2c6903a829489148b414a5121c06dc4c338e06 --- .travis.yml | 43 +++--- docs/nmodl/transpiler/Makefile | 24 ---- docs/nmodl/transpiler/conf.py | 4 +- docs/nmodl/transpiler/contents/visitors.rst | 8 +- setup.py | 147 ++++---------------- src/nmodl/codegen/codegen_c_visitor.hpp | 6 +- src/nmodl/pybind/CMakeLists.txt | 2 +- test/nmodl/transpiler/CMakeLists.txt | 8 +- 8 files changed, 70 insertions(+), 172 deletions(-) delete mode 100644 docs/nmodl/transpiler/Makefile diff --git a/.travis.yml b/.travis.yml index b9c29344be..e314e2467a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,13 +16,13 @@ matrix: include: - language: cpp os: linux - dist: xenial + dist: bionic env: - MATRIX_EVAL="CXX=g++" - - PYTHON_VERSION=3.6.7 + - PYTHON_VERSION=3.8 - language: cpp os: osx - osx_image: xcode10.2 + osx_image: xcode11.3 env: - MATRIX_EVAL="CXX=c++" @@ -45,25 +45,29 @@ addons: packages: - flex - bison - - libboost-all-dev - cmake - - python3-dev - - python3-pip + - dvipng - doxygen + - libboost-all-dev + - libfl-dev + - libopenmpi-dev + - openmpi-bin - pandoc + - python3-dev + - python3-pip - texlive-base - - dvipng # for Mac builds, we use Homebrew homebrew: packages: - boost - cmake - flex + - openmpi - python@3 update: true #============================================================================= -# Install dependencies / setup Spack +# Install dependencies #============================================================================= before_install: # brew installed flex and bison is not in $PATH @@ -76,7 +80,6 @@ before_install: brew link --overwrite python; else pyenv global $PYTHON_VERSION; - ls /usr/bin/; fi - eval "${MATRIX_EVAL}" @@ -89,35 +92,37 @@ install: # I will remove the sphinx downgrade. Change setup.py requirements when it # be the case, Katta - echo "------- Install Dependencies -------" - - pip3 install -U pip setuptools - - pip3 install Jinja2 PyYAML pytest sympy --user - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - pip3 install -U --user sphinx==2.4.4 nbsphinx>=0.3.2 m2r sphinx-rtd-theme jupyter; - fi + - pip3 install -U pip setuptools scikit-build Jinja2 PyYAML pytest "sympy<1.6" #============================================================================= # Build, test and install #============================================================================= script: - echo "------- Build, Test and Install -------" - - mkdir build && cd build + - mkdir build && pushd build - cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl - - make -j 2 - - make test + - make -j 2; + if [ $? -ne 0 ]; then + make VERBOSE=1; + fi + - env CTEST_OUTPUT_ON_FAILURE=1 make test - make install + - popd #============================================================================= # Build Documentation, CoreNEURON and run tests #============================================================================= after_success: - - export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH + # Scikit-build does not support build_ext --inplace in custom commands. + # More info at: https://github.com/scikit-build/scikit-build/issues/489 - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "------- Build Documentation -------"; + python3 setup.py build_ext --inplace docs -j 2 -G "Unix Makefiles" || travis_terminate 1; cd $TRAVIS_BUILD_DIR/docs; - doxygen && make html; rm -rf _build/doctrees && touch _build/.nojekyll; echo "" > _build/index.html; fi + - echo "------- Build and Test CoreNEURON -------" - cd $HOME - git clone --recursive https://github.com/BlueBrain/CoreNeuron.git diff --git a/docs/nmodl/transpiler/Makefile b/docs/nmodl/transpiler/Makefile deleted file mode 100644 index dd895fa70b..0000000000 --- a/docs/nmodl/transpiler/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = nmodl -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -clean: - $(RM) -f doxyoutput/ cpp_api/ - @$(SPHINXBUILD) -M clean "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index e02ba1793d..536b87abc4 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -26,10 +26,12 @@ import textwrap # The project needs to be built before documentation in the usual build folder -sys.path.insert(0, os.path.abspath('../build/lib/python')) +sys.path.insert(0, os.path.abspath('..')) import nmodl # isort:skip +os.environ['PYTHONPATH'] = ':'.join(sys.path) + # Run doxygen subprocess.call('doxygen Doxyfile', shell=True) diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index a058297972..e97bb5e49b 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -63,11 +63,11 @@ If we simply print AST object, we can see JSON representation: Querying AST objects with Visitors -=========== +================================== Lookup Visitor ------------ +-------------- As name suggest, lookup visitor allows to search different NMODL constructs in the AST. The `visitor` module provides access to inbuilt visitors. In order to use this visitor, we create an object of :class:`nmodl.visitor.AstLookupVisitor`: @@ -86,7 +86,7 @@ Assuming we have created :class:`nmodl.ast` object (as shown here), we can searc Symbol Table Visitor ----------- +-------------------- Symbol table visitor is used to find out all variables and their usage in mod file. To use this, just create a visitor object as: @@ -107,7 +107,7 @@ Now we can query for variables in the symbol table based on name of variable: Custom AST Visitor ----------- +------------------ If predefined visitors are limited, we can implement new visitor using :class:`nmodl.visitor.AstVisitor` interface. Let us say we want to implement a visitor that prints every floating point numbers in MOD file. Here is how it can be done: diff --git a/setup.py b/setup.py index bfae67e822..ad82f1c138 100644 --- a/setup.py +++ b/setup.py @@ -7,19 +7,14 @@ import inspect import os -import os.path as osp -import platform -import re import subprocess import sys -import sysconfig -from distutils.version import LooseVersion -from distutils.cmd import Command -from distutils.dir_util import copy_tree -from setuptools import Extension, setup -from setuptools.command.build_ext import build_ext -from setuptools.command.test import test +import subprocess +import os + +from setuptools import Command +from skbuild import setup class lazy_dict(dict): @@ -42,112 +37,23 @@ def get_sphinx_command(): return BuildDoc -class InstallDoc(Command): - description = 'Install Sphinx documentation' +class Docs(Command): + description = "Generate & optionally upload documentation to docs server" user_options = [] - def initialize_options(self): - pass - def finalize_options(self): - pass + finalize_options = lambda self: None + initialize_options = lambda self: None + - def run(self): - self.run_command("test") + def run(self, *args, **kwargs): self.run_command("doctest") self.run_command("buildhtml") -class CMakeExtension(Extension): - def __init__(self, name, sourcedir=""): - Extension.__init__(self, name, sources=[]) - self.sourcedir = osp.abspath(sourcedir) - - -class CMakeBuild(build_ext): - def run(self): - try: - out = subprocess.check_output(["cmake", "--version"]) - except OSError: - raise RuntimeError( - "CMake must be installed to build the following extensions: " - + ", ".join(e.name for e in self.extensions) - ) - - cmake_version = LooseVersion( - re.search(r"version\s*([\d.]+)", out.decode()).group(1) - ) - if cmake_version < "3.3.0": - raise RuntimeError("CMake >= 3.3.0 is required") - - for ext in self.extensions: - self.build_extension(ext) - - def build_extension(self, ext): - extdir = osp.abspath(osp.dirname(self.get_ext_fullpath(ext.name))) - extdir = osp.join(extdir, ext.name) - cmake_args = [ - "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=" + extdir, - "-DPYTHON_EXECUTABLE=" + sys.executable, - ] - - cfg = "Debug" if self.debug else "Release" - build_args = ["--config", cfg] - - if platform.system() == "Windows": - cmake_args += [ - "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}".format(cfg.upper(), extdir) - ] - if sys.maxsize > 2 ** 32: - cmake_args += ["-A", "x64"] - build_args += ["--", "/m"] - else: - cmake_args += ["-DCMAKE_BUILD_TYPE=" + cfg] - build_args += ["--", "-j{}".format(max(1, os.cpu_count() - 3))] - - env = os.environ.copy() - env["CXXFLAGS"] = '{} -DVERSION_INFO=\\"{}\\"'.format( - env.get("CXXFLAGS", ""), self.distribution.get_version() - ) - if not osp.exists(self.build_temp): - os.makedirs(self.build_temp) - subprocess.check_call( - ["cmake", ext.sourcedir] + cmake_args, cwd=self.build_temp, env=env - ) - subprocess.check_call( - ["cmake", "--build", "."] + build_args, cwd=self.build_temp - ) - - # copy nmodl module with shared library to extension directory - copy_tree(os.path.join(self.build_temp, 'nmodl'), extdir) - -class NMODLTest(test): - """Custom disutils command that acts like as a replacement - for the "test" command. - - It first executes the standard "test" command, then runs the - C++ tests and finally runs the "doctest" to also validate - code snippets in the sphinx documentation. - """ - - def distutils_dir_name(self, dname): - """Returns the name of a distutils build directory""" - dir_name = "{dirname}.{platform}-{version[0]}.{version[1]}" - return dir_name.format( - dirname=dname, platform=sysconfig.get_platform(), version=sys.version_info - ) - - def run(self): - super().run() - subprocess.check_call( - [ - "cmake", - "--build", - os.path.join("build", self.distutils_dir_name("temp")), - "--target", - "test", - ] - ) -install_requirements = ["jinja2>=2.9.3", "PyYAML>=3.13", "sympy>=1.3"] +install_requirements = [ + "PyYAML>=3.13", + "sympy>=1.3,<1.6", +] setup( name="NMODL", @@ -158,17 +64,24 @@ def run(self): long_description="", packages=["nmodl"], include_package_data=True, - ext_modules=[CMakeExtension("nmodl")], + cmake_minimum_required_version="3.3.0", + cmake_args=["-DPYTHON_EXECUTABLE=" + sys.executable], cmdclass=lazy_dict( - build_ext=CMakeBuild, - test=NMODLTest, - install_doc=InstallDoc, - doctest=get_sphinx_command, - buildhtml=get_sphinx_command, + docs=Docs, doctest=get_sphinx_command, buildhtml=get_sphinx_command, ), zip_safe=False, - setup_requires=["nbsphinx>=0.3.2", "mistune<2.0", "m2r", "sphinx-rtd-theme", "sphinx>=2.0", "sphinx<3.0"] + setup_requires=[ + "jinja2>=2.9.3", + "jupyter", + "m2r", + "mistune<2", # prevents a version conflict with nbconvert + "nbconvert<6.0", # prevents issues with nbsphinx + "nbsphinx>=0.3.2", + "pytest>=3.7.2", + "sphinx-rtd-theme", + "sphinx>=2.0", + "sphinx<3.0", # prevents issue with m2r where m2r uses an old API no more supported with sphinx>=3.0 + ] + install_requirements, install_requires=install_requirements, - tests_require=["pytest>=3.7.2"], ) diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index f29f78308c..62232412f9 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1606,7 +1606,7 @@ class CodegenCVisitor: public visitor::AstVisitor { public: /** - * Constructs the C code generator visitor + * \brief Constructs the C code generator visitor * * This constructor instantiates an NMODL C code generator and allows writing generated code * directly to a file in \c [output_dir]/[mod_filename].[extension]. @@ -1635,7 +1635,7 @@ class CodegenCVisitor: public visitor::AstVisitor { , float_type(float_type) {} /** - * \copybrief CodegenCVisitor(std::string, std::string, LayoutType, std::string, std::string) + * \copybrief nmodl::codegen::CodegenCVisitor * * This constructor instantiates an NMODL C code generator and allows writing generated code * into an output stream. @@ -1663,7 +1663,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** - * \copybrief CodegenCVisitor(std::string, std::string, LayoutType, std::string, std::string) + * \copybrief nmodl::codegen::CodegenCVisitor * * This constructor instantiates an NMODL C code generator and allows writing generated code * using an nmodl::printer::CodePrinter defined elsewhere. diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index bf235663fa..b7c25f7bbb 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -58,4 +58,4 @@ file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/ # ============================================================================= # Install python binding components # ============================================================================= -install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION lib) +install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nmodl/ DESTINATION nmodl) diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 0b400c0840..14592cb4e3 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -105,9 +105,11 @@ endforeach() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) -add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) -foreach(test_name Ode Pybind) +add_test(NAME "pytest/ode" COMMAND ${PYTHON_EXECUTABLE} -m pytest + ${NMODL_PROJECT_SOURCE_DIR}/test/ode) +add_test(NAME "pytest/pybind" COMMAND ${PYTHON_EXECUTABLE} -m pytest + ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) +foreach(test_name "pytest/ode" "pytest/pybind") set_tests_properties(${test_name} PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) endforeach() From e37e0fce96068829299a4e3953d7ef8b5ca80d98 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 29 May 2020 14:45:08 +0200 Subject: [PATCH 266/871] Change map -> unordered map (BlueBrain/nmodl#343) There is no need to keep the items in order. Unordered maps may perform better. NMODL Repo SHA: BlueBrain/nmodl@bb7d9b3abfb3490a6c3fbd6de2df427d5c2f507f --- src/nmodl/visitors/kinetic_block_visitor.hpp | 12 ++++++------ src/nmodl/visitors/sympy_solver_visitor.hpp | 5 +++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 95a8c6f999..8eddcbe8fa 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -12,10 +12,10 @@ * \brief \copybrief nmodl::visitor::KineticBlockVisitor */ -#include #include #include #include +#include #include #include "visitors/ast_visitor.hpp" @@ -92,12 +92,12 @@ class KineticBlockVisitor: public AstVisitor { /// state variables vector std::vector state_var; - /// map from state variable to corresponding index - std::map state_var_index; + /// unordered_map from state variable to corresponding index + std::unordered_map state_var_index; - /// map from array state variable to its size (for summing over each element of any array state - /// vars in a CONSERVE statement) - std::map array_state_var_size; + /// unordered_map from array state variable to its size (for summing over each element of any + /// array state vars in a CONSERVE statement) + std::unordered_map array_state_var_size; /// true if we are visiting a reaction statement bool in_reaction_statement = false; diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 4f76985c45..c862bc0300 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "ast/ast.hpp" @@ -124,7 +125,7 @@ class SympySolverVisitor: public AstVisitor { std::set function_calls; /// map between derivative block names and associated solver method - std::map derivative_block_solve_method{}; + std::unordered_map derivative_block_solve_method{}; /// expression statements appearing in the block /// (these can be of type DiffEqExpression, LinEquation or NonLinEquation) @@ -165,7 +166,7 @@ class SympySolverVisitor: public AstVisitor { /// map from state vars to the algebraic equation from CONSERVE statement that should replace /// their ODE, if any - std::map conserve_equation; + std::unordered_map conserve_equation; /// optionally replace cnexp solution with (1,1) pade approx bool use_pade_approx; From 97b508ea3400da2f0346a16a9418d387087be4e8 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 2 Jun 2020 13:45:00 +0200 Subject: [PATCH 267/871] Add erase method for non-consecutive nodes in vectors of node pointers Every node variable that is a vector of pointers to other nodes has a method to erase non-consecutive elements passed as elements of an unordered_set (generated by jinja2 and python). This effectively generalizes the procedure implemented in erase_statement_block making the old routine (erase_statement_block) obsolete. Thus, it can replace erase_statement_block and can be tested by the same unit tests that were used for testing erase_statement_block. It can be used to simplify the PR BlueBrain/nmodl#230 and addresses BlueBrain/nmodl#340 NMODL Repo SHA: BlueBrain/nmodl@7e0b1672bac284f9908e4dfc90da4295990c4cdd --- src/nmodl/language/code_generator.cmake | 16 ++++++------ src/nmodl/language/nodes.py | 26 ++++++++++++++++++++ src/nmodl/language/templates/ast/ast.hpp | 1 + src/nmodl/visitors/kinetic_block_visitor.cpp | 4 +-- src/nmodl/visitors/kinetic_block_visitor.hpp | 4 +-- src/nmodl/visitors/sympy_solver_visitor.cpp | 4 +-- src/nmodl/visitors/sympy_solver_visitor.hpp | 4 +-- src/nmodl/visitors/visitor_utils.cpp | 23 ----------------- 8 files changed, 43 insertions(+), 39 deletions(-) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index f4460597ec..2af9375925 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -190,14 +190,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/write_ion_var.hpp ) -set(PYBIND_GENERATED_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp -) - set(VISITORS_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp @@ -214,6 +206,14 @@ set(VISITORS_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp ) +set(PYBIND_GENERATED_SOURCES + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp +) + set(NMODL_GENERATED_SOURCES ${AST_GENERATED_SOURCES} ${PYBIND_GENERATED_SOURCES} diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 26424e8993..db5b6e2f72 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -272,6 +272,32 @@ def get_add_methods(self): auto last_it = const_iter_cast({self.varname}, last); return {self.varname}.erase(first_it, last_it); }} + /** + * \\brief Erase non-consecutive members to {self.varname} + * + * loosely following the cpp reference of remove_if + */ + size_t erase_{to_snake_case(self.class_name)}(std::unordered_set<{self.class_name}*>& to_be_erased) {{ + auto first = {self.varname}.begin(); + auto last = {self.varname}.end(); + auto result = first; + + while (first != last) {{ + // automatically erase dangling pointers from the uset while + // looking for them to erase them in the vector + if (to_be_erased.erase(first->get()) == 0) {{ + reset_{to_snake_case(self.class_name)}(result, *first); + ++result; + }} + ++first; + }} + + size_t out = last - result; + erase_{to_snake_case(self.class_name)}(result, last); + + return out; + }} + /** * \\brief Insert member to {self.varname} diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 19b1436de3..d50899c532 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -21,6 +21,7 @@ #include #include +#include #include "ast/ast_decl.hpp" #include "ast/ast_common.hpp" diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 006e09d1b8..6a5335cd01 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -347,7 +347,7 @@ void KineticBlockVisitor::visit_statement_block(ast::StatementBlock& node) { current_statement_block = &node; node.visit_children(*this); // remove processed statements from current statement block - remove_statements_from_block(*current_statement_block, statements_to_remove); + current_statement_block->erase_statement(statements_to_remove); current_statement_block = prev_statement_block; } @@ -418,7 +418,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { const auto& kinetic_statement_block = node.get_statement_block(); // remove any remaining kinetic statements - remove_statements_from_block(*kinetic_statement_block, statements_to_remove); + kinetic_statement_block->erase_statement(statements_to_remove); // add new statements for (const auto& ode: odes) { logger->debug("KineticBlockVisitor :: -> adding statement: {}", ode); diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 8eddcbe8fa..0a67071c1f 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -13,9 +13,9 @@ */ #include -#include #include #include +#include #include #include "visitors/ast_visitor.hpp" @@ -127,7 +127,7 @@ class KineticBlockVisitor: public AstVisitor { std::vector kinetic_blocks; /// statements to remove from block - std::set statements_to_remove; + std::unordered_set statements_to_remove; /// current statement block being visited ast::StatementBlock* current_statement_block = nullptr; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 2905cfc282..d0eb3539dd 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -219,7 +219,7 @@ void SympySolverVisitor::construct_eigen_solver_block( block_with_expression_statements->erase_statement(it, statements.end()); // also remove diff/linear/non-linear eq statements from the statement block - remove_statements_from_block(*block_with_expression_statements, expression_statements); + block_with_expression_statements->erase_statement(expression_statements); // move any local variable declarations into variable_block ast::StatementVector variable_statements; // remaining statements in block should go into initialize_block @@ -339,7 +339,7 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre ++it; } /// remove original lineq statements from the block - remove_statements_from_block(*block_with_expression_statements, expression_statements); + block_with_expression_statements->erase_statement(expression_statements); } else { // otherwise it returns a linear matrix system to solve logger->debug("SympySolverVisitor :: Constructing linear newton solve block"); diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index c862bc0300..5ec466ac43 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -14,8 +14,8 @@ #include #include -#include #include +#include #include #include "ast/ast.hpp" @@ -129,7 +129,7 @@ class SympySolverVisitor: public AstVisitor { /// expression statements appearing in the block /// (these can be of type DiffEqExpression, LinEquation or NonLinEquation) - std::set expression_statements; + std::unordered_set expression_statements; /// current expression statement being visited (to track ODEs / (non)lineqs) ast::ExpressionStatement* current_expression_statement; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index cf00a5c39c..0fecda7d12 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -122,29 +122,6 @@ std::shared_ptr create_statement_block( return statement_block; } - -void remove_statements_from_block(ast::StatementBlock& block, - const std::set& statements) { - const auto& statement_vec = block.get_statements(); - - // loosely following the cpp reference of remove_if - - auto first = statement_vec.begin(); - auto last = statement_vec.end(); - auto result = first; - - while (first != last) { - if (statements.find(first->get()) == statements.end()) { - block.reset_statement(result, *first); - ++result; - } - ++first; - } - - block.erase_statement(result, last); -} - - std::set get_global_vars(const Program& node) { std::set vars; if (auto* symtab = node.get_symbol_table()) { From 12d2005f0923c99b59e0b3532836168a35eafd83 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 2 Jun 2020 14:11:59 +0200 Subject: [PATCH 268/871] Move the pybind11 embedded python into a dynamically loadable plugin (BlueBrain/nmodl#325) - Created a wrapper library that handles all the access to libpython*.so through a singleton class - Added code to dlopen/dlsym the wrapper library after loading libpython - Redirected access to pybind11/libpython through the wrapper specifically in the sympy visitor. - Try to load wrapper_api before dlopen in case it's already linked - Added a new env variables `NMODL_WARPLIB`, `NMODL_PYLIB` to give loading mechanism hints on where to find the libraries. - Updated cmake files accordingly - When initializing the embedded python, we update `sys.path` with `PYTHONPATH` - Updated documentation & fixed macro variable naming also: - Update pybind11 to 2.5.0 release, which fixes a number of issues and improves support for newer python versions Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@c9d7b9918c0d32f84e7016361c80feaa529d5067 --- .travis.yml | 9 +- INSTALL.md | 42 ++-- cmake/nmodl/PythonLinkHelper.cmake | 3 + src/nmodl/nmodl/CMakeLists.txt | 5 +- src/nmodl/nmodl/main.cpp | 8 +- src/nmodl/pybind/CMakeLists.txt | 28 ++- src/nmodl/pybind/pyembed.cpp | 85 +++++++ src/nmodl/pybind/pyembed.hpp | 185 +++++++++++++++ src/nmodl/pybind/wrapper.cpp | 221 ++++++++++++++++++ src/nmodl/visitors/CMakeLists.txt | 17 +- src/nmodl/visitors/main.cpp | 6 +- .../visitors/sympy_conductance_visitor.cpp | 36 +-- src/nmodl/visitors/sympy_solver_visitor.cpp | 122 +++------- test/nmodl/transpiler/CMakeLists.txt | 42 ++-- test/nmodl/transpiler/visitor/main.cpp | 5 +- 15 files changed, 654 insertions(+), 160 deletions(-) create mode 100644 src/nmodl/pybind/pyembed.cpp create mode 100644 src/nmodl/pybind/pyembed.hpp create mode 100644 src/nmodl/pybind/wrapper.cpp diff --git a/.travis.yml b/.travis.yml index e314e2467a..2e716a121a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -103,7 +103,7 @@ script: - cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl - make -j 2; if [ $? -ne 0 ]; then - make VERBOSE=1; + make VERBOSE=1; fi - env CTEST_OUTPUT_ON_FAILURE=1 make test - make install @@ -118,11 +118,10 @@ after_success: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then echo "------- Build Documentation -------"; python3 setup.py build_ext --inplace docs -j 2 -G "Unix Makefiles" || travis_terminate 1; - cd $TRAVIS_BUILD_DIR/docs; - rm -rf _build/doctrees && touch _build/.nojekyll; - echo "" > _build/index.html; + cd $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx; + rm -rf doctrees && touch html/.nojekyll; + echo "" > html/index.html; fi - - echo "------- Build and Test CoreNEURON -------" - cd $HOME - git clone --recursive https://github.com/BlueBrain/CoreNeuron.git diff --git a/INSTALL.md b/INSTALL.md index 94f4c18481..b2cae306b8 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -83,7 +83,7 @@ export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH If flex / bison are not in your default $PATH, you can provide the path to cmake as: -``` +```sh cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ -DCMAKE_INSTALL_PREFIX=$HOME/nmodl @@ -101,6 +101,32 @@ pip3 install --user . This should build the NMODL framework and install it into your pip user `site-packages` folder such that it becomes available as a Python module. +### When building without linking against libpython + +NMODL uses an embedded python to symbolically evaluate differential equations. For this to work we would usually link +against libpython, which is automatically taken care of by pybind11. In some cases, for instance when building a +python wheel, we cannot link against libpython, because we cannot know where it will be at runtime. Instead, we load +the python library (along with a wrapper library that manages calls to embedded python) at runtime. +To disable linking against python and enabling dynamic loading of libpython at runtime we need to configure the build +with the cmake option `-DLINK_AGAINST_PYTHON=False`. + +In order for NMODL binaries to know where to find libpython and our own libpywrapper two environment variables need to +be present: + +* `NMODL_PYLIB`: This variable should point to the libpython shared-object (or dylib) file. On macos this could be +for example: +````sh +export NMODL_PYLIB=/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/Python +```` +* 'NMODL_WRAPLIB': This variable should point to the `libpywrapper.so` built as part of NMODL, for example: +```sh +export NMODL_WRAPLIB=/opt/nmodl/lib/python/nmodl/libpywrapper.dylib +``` + +**Note**: In order for all unit tests to function correctly when building without linking against libpython we must +set `NMODL_PYLIB` before running cmake! + + ## Testing the Installed Module If you have installed the NMODL Framework using CMake, you can now run tests from the build directory as: @@ -150,18 +176,8 @@ NMODL is now setup correctly! In order to build the documentation you must have additionally `pandoc` installed. Use your system's package manager to do this (e.g. `sudo apt-get install pandoc`). -Once you have installed NMODL and setup the correct `$PYTHONPATH`, you can build the documentation locally from the -docs folder as: - -``` -cd docs -doxygen # for API documentation -make html # for user documentation -``` - -Alternatively, you can install the documentation using the Python setuptools script: +You can build the entire documentation simply by using sphinx from `setup.py`: ```sh -python3 setup.py install_doc +python3 setup.py build_ext --inplace docs -G "Unix Makefiles" ``` - diff --git a/cmake/nmodl/PythonLinkHelper.cmake b/cmake/nmodl/PythonLinkHelper.cmake index 908e7ee9c6..fb65c546b4 100644 --- a/cmake/nmodl/PythonLinkHelper.cmake +++ b/cmake/nmodl/PythonLinkHelper.cmake @@ -24,4 +24,7 @@ endif() if(NOT LINK_AGAINST_PYTHON) string(APPEND CMAKE_EXE_LINKER_FLAGS " ${UNDEFINED_SYMBOLS_IGNORE_FLAG}") + set(NMODL_WRAPPER_LIBS pyembed dl) +else() + set(NMODL_WRAPPER_LIBS pyembed pywrapper dl) endif() diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 7ee64b9300..596c2f8fab 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -15,12 +15,13 @@ target_link_libraries( visitor symtab util - lexer) + lexer + ${NMODL_WRAPPER_LIBS}) # ============================================================================= # Add dependency with nmodl pytnon module (for consumer projects) # ============================================================================= -add_dependencies(nmodl _nmodl) +add_dependencies(nmodl _nmodl pywrapper) # ============================================================================= # Install executable diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index d82f45cc50..f009db0a3b 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -10,7 +10,6 @@ #include #include "CLI/CLI.hpp" -#include "pybind11/embed.h" #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" @@ -22,6 +21,7 @@ #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "parser/unit_driver.hpp" +#include "pybind/pyembed.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" @@ -256,7 +256,9 @@ int main(int argc, const char* argv[]) { utils::make_path(scratch_dir); if (sympy_opt) { - pybind11::initialize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() + .api() + ->initialize_interpreter(); } if (verbose) { @@ -478,6 +480,6 @@ int main(int argc, const char* argv[]) { } if (sympy_opt) { - pybind11::finalize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); } } diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index b7c25f7bbb..c2adeb06ac 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -18,6 +18,30 @@ foreach( list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) endforeach() +add_library(pyembed ${CMAKE_CURRENT_SOURCE_DIR}/pyembed.cpp) +set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) + +if(NOT LINK_AGAINST_PYTHON) + add_library(pywrapper SHARED ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) +else() + add_library(pywrapper ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) + set_property(TARGET pywrapper PROPERTY POSITION_INDEPENDENT_CODE ON) + target_compile_definitions(pyembed PRIVATE NMODL_STATIC_PYWRAPPER=1) +endif() + +target_include_directories(pyembed PRIVATE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) +target_include_directories(pywrapper PRIVATE ${pybind11_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) + +# ~~~ +# pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to +# libpython, we can use `module` interface library from pybind11. +# ~~~ +if(NOT LINK_AGAINST_PYTHON) + target_link_libraries(pywrapper PRIVATE pybind11::module) +else() + target_link_libraries(pywrapper PRIVATE pybind11::embed) +endif() + # Note that LTO causes link time errors with GCC 8. To avoid this, we disable LTO for pybind using # NO_EXTRAS. See #266. pybind11_add_module( @@ -37,13 +61,13 @@ add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) -target_link_libraries(_nmodl PRIVATE fmt::fmt) +target_link_libraries(_nmodl PRIVATE fmt::fmt pyembed pywrapper) add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) add_custom_command( OUTPUT ${NMODL_PYTHON_FILES_OUT} COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl - ${PROJECT_BINARY_DIR}/nmodl + ${PROJECT_BINARY_DIR}/lib/python/nmodl COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl DEPENDS ${NMODL_PYTHON_FILES_IN} $ COMMENT "-- COPYING NMODL PYTHON FILES --") diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp new file mode 100644 index 0000000000..921912e322 --- /dev/null +++ b/src/nmodl/pybind/pyembed.cpp @@ -0,0 +1,85 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include +#include + +#include "pybind/pyembed.hpp" +#include "utils/logger.hpp" + +namespace nmodl { + +namespace pybind_wrappers { + +bool EmbeddedPythonLoader::have_wrappers() { +#if defined(NMODL_STATIC_PYWRAPPER) + static auto wrapper_api = nmodl::pybind_wrappers::init_pybind_wrap_api(); + wrappers = &wrapper_api; + return true; +#else + wrappers = static_cast(dlsym(RTLD_DEFAULT, "nmodl_wrapper_api")); + return wrappers != nullptr; +#endif +} + +void EmbeddedPythonLoader::load_libraries() { + const auto pylib_env = std::getenv("NMODL_PYLIB"); + if (!pylib_env) { + logger->critical("NMODL_PYLIB environment variable must be set to load embedded python"); + throw std::runtime_error("NMODL_PYLIB not set"); + } + const auto dlopen_opts = RTLD_NOW | RTLD_GLOBAL; + dlerror(); // reset old error conditions + pylib_handle = dlopen(pylib_env, dlopen_opts); + if (!pylib_handle) { + const auto errstr = dlerror(); + logger->critical("Tried but failed to load {}", pylib_env); + logger->critical(errstr); + throw std::runtime_error("Failed to dlopen"); + } + const auto pybind_wraplib_env = std::getenv("NMODL_WRAPLIB"); + if (!pybind_wraplib_env) { + logger->critical( + "NMODL_WRAPLIB environment variable must be set to load the pybind wrapper library"); + throw std::runtime_error("NMODL_WRAPLIB not set"); + } + pybind_wrapper_handle = dlopen(pybind_wraplib_env, dlopen_opts); + if (!pybind_wrapper_handle) { + const auto errstr = dlerror(); + logger->critical("Tried but failed to load {}", pybind_wraplib_env); + logger->critical(errstr); + throw std::runtime_error("Failed to dlopen"); + } +} + +void EmbeddedPythonLoader::populate_symbols() { + wrappers = static_cast(dlsym(pybind_wrapper_handle, "nmodl_wrapper_api")); + if (!wrappers) { + const auto errstr = dlerror(); + logger->critical("Tried but failed to load pybind wrapper symbols"); + logger->critical(errstr); + throw std::runtime_error("Failed to dlsym"); + } +} + +void EmbeddedPythonLoader::unload() { + if (pybind_wrapper_handle) { + dlclose(pybind_wrapper_handle); + } + if (pylib_handle) { + dlclose(pylib_handle); + } +} + +const pybind_wrap_api* EmbeddedPythonLoader::api() { + return wrappers; +} + + +} // namespace pybind_wrappers + +} // namespace nmodl diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp new file mode 100644 index 0000000000..a903e6a332 --- /dev/null +++ b/src/nmodl/pybind/pyembed.hpp @@ -0,0 +1,185 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include +#include +#include +#include + +#include "pybind11/embed.h" + +namespace nmodl { +namespace pybind_wrappers { + + +struct PythonExecutor { + virtual ~PythonExecutor() {} + + virtual void operator()() = 0; +}; + + +struct SolveLinearSystemExecutor: public PythonExecutor { + // input + std::vector eq_system; + std::vector state_vars; + std::set vars; + bool small_system; + bool elimination; + std::set function_calls; + // output + // returns a vector of solutions, i.e. new statements to add to block: + std::vector solutions; + // and a vector of new local variables that need to be declared in the block: + std::vector new_local_vars; + // may also return a python exception message: + std::string exception_message; + + // executor function + virtual void operator()() override; +}; + + +struct SolveNonLinearSystemExecutor: public PythonExecutor { + // input + std::vector eq_system; + std::vector state_vars; + std::set vars; + std::set function_calls; + // output + // returns a vector of solutions, i.e. new statements to add to block: + std::vector solutions; + // may also return a python exception message: + std::string exception_message; + + // executor function + virtual void operator()() override; +}; + + +struct DiffeqSolverExecutor: public PythonExecutor { + // input + std::string node_as_nmodl; + std::string dt_var; + std::set vars; + bool use_pade_approx; + std::set function_calls; + std::string method; + // output + // returns solution, i.e. new statement to add to block: + std::string solution; + // may also return a python exception message: + std::string exception_message; + + // executor function + virtual void operator()() override; +}; + + +struct AnalyticDiffExecutor: public PythonExecutor { + // input + std::vector expressions; + std::set used_names_in_block; + // output + // returns solution, i.e. new statement to add to block: + std::string solution; + // may also return a python exception message: + std::string exception_message; + + // executor function + virtual void operator()() override; +}; + + +SolveLinearSystemExecutor* create_sls_executor_func(); +SolveNonLinearSystemExecutor* create_nsls_executor_func(); +DiffeqSolverExecutor* create_des_executor_func(); +AnalyticDiffExecutor* create_ads_executor_func(); +void destroy_sls_executor_func(SolveLinearSystemExecutor* exec); +void destroy_nsls_executor_func(SolveNonLinearSystemExecutor* exec); +void destroy_des_executor_func(DiffeqSolverExecutor* exec); +void destroy_ads_executor_func(AnalyticDiffExecutor* exec); + +void initialize_interpreter_func(); +void finalize_interpreter_func(); + +struct pybind_wrap_api { + decltype(&initialize_interpreter_func) initialize_interpreter; + decltype(&finalize_interpreter_func) finalize_interpreter; + decltype(&create_sls_executor_func) create_sls_executor; + decltype(&create_nsls_executor_func) create_nsls_executor; + decltype(&create_des_executor_func) create_des_executor; + decltype(&create_ads_executor_func) create_ads_executor; + decltype(&destroy_sls_executor_func) destroy_sls_executor; + decltype(&destroy_nsls_executor_func) destroy_nsls_executor; + decltype(&destroy_des_executor_func) destroy_des_executor; + decltype(&destroy_ads_executor_func) destroy_ads_executor; +}; + + +/** + * A singleton class handling access to the pybind_wrap_api struct + * + * This class manages the runtime loading of the libpython so/dylib file and the python binding + * wrapper library and provides access to the API wrapper struct that can be used to access the + * pybind11 embedded python functionality. + */ +class EmbeddedPythonLoader { + public: + /** + * Construct (if not already done) and get the only instance of this class + * + * @return the EmbeddedPythonLoader singleton instance + */ + static EmbeddedPythonLoader& get_instance() { + static EmbeddedPythonLoader instance; + + return instance; + } + + EmbeddedPythonLoader(const EmbeddedPythonLoader&) = delete; + void operator=(const EmbeddedPythonLoader&) = delete; + + + /** + * Get a pointer to the pybind_wrap_api struct + * + * Get access to the container struct for the pointers to the functions in the wrapper library. + * @return a pybind_wrap_api pointer + */ + const pybind_wrap_api* api(); + + ~EmbeddedPythonLoader() { + unload(); + } + + private: + pybind_wrap_api* wrappers = nullptr; + + void* pylib_handle = nullptr; + void* pybind_wrapper_handle = nullptr; + + bool have_wrappers(); + void load_libraries(); + void populate_symbols(); + void unload(); + + EmbeddedPythonLoader() { + if (!have_wrappers()) { + load_libraries(); + populate_symbols(); + } + } +}; + + +pybind_wrap_api init_pybind_wrap_api() noexcept; + +} // namespace pybind_wrappers +} // namespace nmodl diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp new file mode 100644 index 0000000000..1ceb1c111b --- /dev/null +++ b/src/nmodl/pybind/wrapper.cpp @@ -0,0 +1,221 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + + +#include +#include + +#include "pybind11/embed.h" +#include "pybind11/stl.h" + +#include "codegen/codegen_naming.hpp" +#include "pybind/pyembed.hpp" + +namespace py = pybind11; +using namespace py::literals; + +namespace nmodl { +namespace pybind_wrappers { + +void SolveLinearSystemExecutor::operator()() { + const auto locals = py::dict("eq_strings"_a = eq_system, + "state_vars"_a = state_vars, + "vars"_a = vars, + "small_system"_a = small_system, + "do_cse"_a = elimination, + "function_calls"_a = function_calls); + py::exec(R"( + from nmodl.ode import solve_lin_system + exception_message = "" + try: + solutions, new_local_vars = solve_lin_system(eq_strings, + state_vars, + vars, + function_calls, + small_system, + do_cse) + except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) + )", + py::globals(), + locals); + // returns a vector of solutions, i.e. new statements to add to block: + solutions = locals["solutions"].cast>(); + // and a vector of new local variables that need to be declared in the block: + new_local_vars = locals["new_local_vars"].cast>(); + // may also return a python exception message: + exception_message = locals["exception_message"].cast(); +} + + +void SolveNonLinearSystemExecutor::operator()() { + const auto locals = py::dict("equation_strings"_a = eq_system, + "state_vars"_a = state_vars, + "vars"_a = vars, + "function_calls"_a = function_calls); + py::exec(R"( + from nmodl.ode import solve_non_lin_system + exception_message = "" + try: + solutions = solve_non_lin_system(equation_strings, + state_vars, + vars, + function_calls) + except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) + )", + py::globals(), + locals); + // returns a vector of solutions, i.e. new statements to add to block: + solutions = locals["solutions"].cast>(); + // may also return a python exception message: + exception_message = locals["exception_message"].cast(); +} + +void DiffeqSolverExecutor::operator()() { + const auto locals = py::dict("equation_string"_a = node_as_nmodl, + "dt_var"_a = dt_var, + "vars"_a = vars, + "use_pade_approx"_a = use_pade_approx, + "function_calls"_a = function_calls); + + if (method == codegen::naming::EULER_METHOD) { + // replace x' = f(x) differential equation + // with forwards Euler timestep: + // x = x + f(x) * dt + py::exec(R"( + from nmodl.ode import forwards_euler2c + exception_message = "" + try: + solution = forwards_euler2c(equation_string, dt_var, vars, function_calls) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), + locals); + } else if (method == codegen::naming::CNEXP_METHOD) { + // replace x' = f(x) differential equation + // with analytic solution for x(t+dt) in terms of x(t) + // x = ... + py::exec(R"( + from nmodl.ode import integrate2c + exception_message = "" + try: + solution = integrate2c(equation_string, dt_var, vars, + use_pade_approx) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), + locals); + } else { + // nothing to do, but the caller should know. + return; + } + solution = locals["solution"].cast(); + exception_message = locals["exception_message"].cast(); +} + +void AnalyticDiffExecutor::operator()() { + auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block); + py::exec(R"( + from nmodl.ode import differentiate2c + exception_message = "" + try: + rhs = expressions[-1].split("=", 1)[1] + solution = differentiate2c(rhs, + "v", + vars, + expressions[:-1] + ) + except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) + )", + py::globals(), + locals); + solution = locals["solution"].cast(); + exception_message = locals["exception_message"].cast(); +} + +SolveLinearSystemExecutor* create_sls_executor_func() { + return new SolveLinearSystemExecutor(); +} + +SolveNonLinearSystemExecutor* create_nsls_executor_func() { + return new SolveNonLinearSystemExecutor(); +} + +DiffeqSolverExecutor* create_des_executor_func() { + return new DiffeqSolverExecutor(); +} + +AnalyticDiffExecutor* create_ads_executor_func() { + return new AnalyticDiffExecutor(); +} + +void destroy_sls_executor_func(SolveLinearSystemExecutor* exec) { + delete exec; +} + +void destroy_nsls_executor_func(SolveNonLinearSystemExecutor* exec) { + delete exec; +} + +void destroy_des_executor_func(DiffeqSolverExecutor* exec) { + delete exec; +} + +void destroy_ads_executor_func(AnalyticDiffExecutor* exec) { + delete exec; +} + +void initialize_interpreter_func() { + pybind11::initialize_interpreter(true); + const auto python_path_cstr = std::getenv("PYTHONPATH"); + if (python_path_cstr) { + pybind11::module::import("sys").attr("path").cast().insert( + 0, python_path_cstr); + } +} + +void finalize_interpreter_func() { + pybind11::finalize_interpreter(); +} + +pybind_wrap_api init_pybind_wrap_api() noexcept { + return { + &nmodl::pybind_wrappers::initialize_interpreter_func, + &nmodl::pybind_wrappers::finalize_interpreter_func, + &nmodl::pybind_wrappers::create_sls_executor_func, + &nmodl::pybind_wrappers::create_nsls_executor_func, + &nmodl::pybind_wrappers::create_des_executor_func, + &nmodl::pybind_wrappers::create_ads_executor_func, + &nmodl::pybind_wrappers::destroy_sls_executor_func, + &nmodl::pybind_wrappers::destroy_nsls_executor_func, + &nmodl::pybind_wrappers::destroy_des_executor_func, + &nmodl::pybind_wrappers::destroy_ads_executor_func, + }; +} + +} // namespace pybind_wrappers +} // namespace nmodl + + +__attribute__((visibility("default"))) nmodl::pybind_wrappers::pybind_wrap_api nmodl_wrapper_api = + nmodl::pybind_wrappers::init_pybind_wrap_api(); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index ab19a22bb1..e117fcdf8c 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -55,6 +55,10 @@ set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_dependencies(visitor_obj lexer_obj) add_library(visitor STATIC $) +add_dependencies(visitor lexer util pywrapper) + +add_executable(nmodl_visitor main.cpp) + # ~~~ # pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to # libpython, we can use `pybind11::module` interface library from pybind11. @@ -64,11 +68,14 @@ if(NOT LINK_AGAINST_PYTHON) else() target_link_libraries(visitor PRIVATE pybind11::embed) endif() - -add_dependencies(visitor lexer util) - -add_executable(nmodl_visitor main.cpp) -target_link_libraries(nmodl_visitor printer visitor symtab util lexer) +target_link_libraries( + nmodl_visitor + printer + visitor + symtab + util + lexer + ${NMODL_WRAPPER_LIBS}) # ============================================================================= # Install executable diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 3db54e074d..23e06cc4e2 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -8,11 +8,11 @@ #include #include "CLI/CLI.hpp" -#include "pybind11/embed.h" #include "ast/program.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" +#include "pybind/pyembed.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" @@ -88,7 +88,7 @@ int main(int argc, const char* argv[]) { {std::make_shared(NrnUnitsLib::get_path()), "units", "UnitsVisitor"}, }; - pybind11::initialize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); for (const auto& filename: files) { logger->info("Processing {}", filename); @@ -111,7 +111,7 @@ int main(int argc, const char* argv[]) { } } - pybind11::finalize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); return 0; } diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index a1b4a1e7de..9d860df095 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -9,18 +9,14 @@ #include -#include -#include - #include "ast/all.hpp" +#include "pybind/pyembed.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" - -namespace py = pybind11; -using namespace py::literals; +namespace pywrap = nmodl::pybind_wrappers; namespace nmodl { namespace visitor { @@ -79,26 +75,14 @@ std::vector SympyConductanceVisitor::generate_statement_strings( ordered_binary_exprs.begin() + binary_expr_index[lhs_str] + 1); // differentiate dI/dV - auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block); - py::exec(R"( - from nmodl.ode import differentiate2c - exception_message = "" - try: - rhs = expressions[-1].split("=", 1)[1] - solution = differentiate2c(rhs, - "v", - vars, - expressions[:-1] - ) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), - locals); - auto dIdV = locals["solution"].cast(); - auto exception_message = locals["exception_message"].cast(); + auto analytic_diff = + pywrap::EmbeddedPythonLoader::get_instance().api()->create_ads_executor(); + analytic_diff->expressions = expressions; + analytic_diff->used_names_in_block = used_names_in_block; + (*analytic_diff)(); + auto dIdV = analytic_diff->solution; + auto exception_message = analytic_diff->exception_message; + pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_ads_executor(analytic_diff); if (!exception_message.empty()) { logger->warn("SympyConductance :: python exception: {}", exception_message); } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index d0eb3539dd..2861a9d0bf 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -7,10 +7,10 @@ #include "visitors/sympy_solver_visitor.hpp" -#include #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" +#include "pybind/pyembed.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" @@ -18,8 +18,7 @@ #include "visitors/visitor_utils.hpp" -namespace py = pybind11; -using namespace py::literals; +namespace pywrap = nmodl::pybind_wrappers; namespace nmodl { namespace visitor { @@ -275,36 +274,21 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre init_state_vars_vector(); // call sympy linear solver bool small_system = (eq_system.size() <= SMALL_LINEAR_SYSTEM_MAX_STATES); - auto locals = py::dict("eq_strings"_a = eq_system, - "state_vars"_a = state_vars, - "vars"_a = vars, - "small_system"_a = small_system, - "do_cse"_a = elimination, - "function_calls"_a = function_calls); - py::exec(R"( - from nmodl.ode import solve_lin_system - exception_message = "" - try: - solutions, new_local_vars = solve_lin_system(eq_strings, - state_vars, - vars, - function_calls, - small_system, - do_cse) - except Exception as e: - # if we fail, fail silently and return empty string - solutions = [""] - new_local_vars = [""] - exception_message = str(e) - )", - py::globals(), - locals); + auto solver = pywrap::EmbeddedPythonLoader::get_instance().api()->create_sls_executor(); + solver->eq_system = eq_system; + solver->state_vars = state_vars; + solver->vars = vars; + solver->small_system = small_system; + solver->elimination = elimination; + solver->function_calls = function_calls; + (*solver)(); // returns a vector of solutions, i.e. new statements to add to block: - auto solutions = locals["solutions"].cast>(); + auto solutions = solver->solutions; // and a vector of new local variables that need to be declared in the block: - auto new_local_vars = locals["new_local_vars"].cast>(); + auto new_local_vars = solver->new_local_vars; // may also return a python exception message: - auto exception_message = locals["exception_message"].cast(); + auto exception_message = solver->exception_message; + pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_sls_executor(solver); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: solve_lin_system python exception: " + exception_message); @@ -352,30 +336,18 @@ void SympySolverVisitor::solve_non_linear_system( // construct ordered vector of state vars used in non-linear system init_state_vars_vector(); // call sympy non-linear solver - auto locals = py::dict("equation_strings"_a = eq_system, - "state_vars"_a = state_vars, - "vars"_a = vars, - "function_calls"_a = function_calls); - py::exec(R"( - from nmodl.ode import solve_non_lin_system - exception_message = "" - try: - solutions = solve_non_lin_system(equation_strings, - state_vars, - vars, - function_calls) - except Exception as e: - # if we fail, fail silently and return empty string - solutions = [""] - new_local_vars = [""] - exception_message = str(e) - )", - py::globals(), - locals); + + auto solver = pywrap::EmbeddedPythonLoader::get_instance().api()->create_nsls_executor(); + solver->eq_system = eq_system; + solver->state_vars = state_vars; + solver->vars = vars; + solver->function_calls = function_calls; + (*solver)(); // returns a vector of solutions, i.e. new statements to add to block: - auto solutions = locals["solutions"].cast>(); + auto solutions = solver->solutions; // may also return a python exception message: - auto exception_message = locals["exception_message"].cast(); + auto exception_message = solver->exception_message; + pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_nsls_executor(solver); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: solve_non_lin_system python exception: " + exception_message); @@ -424,47 +396,24 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { check_expr_statements_in_same_block(); const auto node_as_nmodl = to_nmodl_for_sympy(node); - const auto locals = py::dict("equation_string"_a = node_as_nmodl, - "dt_var"_a = codegen::naming::NTHREAD_DT_VARIABLE, - "vars"_a = vars, - "use_pade_approx"_a = use_pade_approx, - "function_calls"_a = function_calls); - + auto diffeq_solver = pywrap::EmbeddedPythonLoader::get_instance().api()->create_des_executor(); + diffeq_solver->node_as_nmodl = node_as_nmodl; + diffeq_solver->dt_var = codegen::naming::NTHREAD_DT_VARIABLE; + diffeq_solver->vars = vars; + diffeq_solver->use_pade_approx = use_pade_approx; + diffeq_solver->function_calls = function_calls; + diffeq_solver->method = solve_method; + (*diffeq_solver)(); if (solve_method == codegen::naming::EULER_METHOD) { - logger->debug("SympySolverVisitor :: EULER - solving: {}", node_as_nmodl); // replace x' = f(x) differential equation // with forwards Euler timestep: // x = x + f(x) * dt - py::exec(R"( - from nmodl.ode import forwards_euler2c - exception_message = "" - try: - solution = forwards_euler2c(equation_string, dt_var, vars, function_calls) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), - locals); + logger->debug("SympySolverVisitor :: EULER - solving: {}", node_as_nmodl); } else if (solve_method == codegen::naming::CNEXP_METHOD) { // replace x' = f(x) differential equation // with analytic solution for x(t+dt) in terms of x(t) // x = ... logger->debug("SympySolverVisitor :: CNEXP - solving: {}", node_as_nmodl); - py::exec(R"( - from nmodl.ode import integrate2c - exception_message = "" - try: - solution = integrate2c(equation_string, dt_var, vars, - use_pade_approx) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), - locals); } else { // for other solver methods: just collect the ODEs & return std::string eq_str = to_nmodl_for_sympy(node); @@ -487,10 +436,11 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { } // replace ODE with solution in AST - auto solution = locals["solution"].cast(); + auto solution = diffeq_solver->solution; logger->debug("SympySolverVisitor :: -> solution: {}", solution); - auto exception_message = locals["exception_message"].cast(); + auto exception_message = diffeq_solver->exception_message; + pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_des_executor(diffeq_solver); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: python exception: " + exception_message); return; diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 14592cb4e3..69f9da0f38 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -60,7 +60,8 @@ target_link_libraries( lexer util test_util - printer) + printer + ${NMODL_WRAPPER_LIBS}) target_link_libraries( testvisitor visitor @@ -68,7 +69,8 @@ target_link_libraries( lexer util test_util - printer) + printer + ${NMODL_WRAPPER_LIBS}) target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) @@ -78,6 +80,18 @@ target_link_libraries(testunitparser lexer test_util config) # Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, # else use the normal add_test method # ============================================================================= +set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib/python:$ENV{PYTHONPATH}") +if(NOT LINK_AGAINST_PYTHON) + list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") + if(CMAKE_SYSTEM_NAME MATCHES "Linux") + list(APPEND testvisitor_env + "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/python/nmodl/libpywrapper.so") + elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") + list(APPEND testvisitor_env + "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/python/nmodl/libpywrapper.dylib") + endif() +endif() + foreach( test_name testmodtoken @@ -91,13 +105,16 @@ foreach( testunitparser) if(${CMAKE_VERSION} VERSION_GREATER "3.10") - catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT - PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) + if(${test_name} STREQUAL "testvisitor") + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT + "${testvisitor_env}") + else() + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/") + endif() else() add_test(NAME ${test_name} COMMAND ${test_name}) if(${test_name} STREQUAL "testvisitor") - set_tests_properties(${test_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) + set_tests_properties(${test_name} PROPERTIES ENVIRONMENT "${testvisitor_env}") endif() endif() endforeach() @@ -105,11 +122,10 @@ endforeach() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME "pytest/ode" COMMAND ${PYTHON_EXECUTABLE} -m pytest - ${NMODL_PROJECT_SOURCE_DIR}/test/ode) -add_test(NAME "pytest/pybind" COMMAND ${PYTHON_EXECUTABLE} -m pytest - ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) -foreach(test_name "pytest/ode" "pytest/pybind") - set_tests_properties(${test_name} PROPERTIES ENVIRONMENT - PYTHONPATH=${PROJECT_BINARY_DIR}:$ENV{PYTHONPATH}) +add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) +add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) +foreach(test_name Ode Pybind) + set_tests_properties( + ${test_name} PROPERTIES ENVIRONMENT + PYTHONPATH=${PROJECT_BINARY_DIR}/lib/python:$ENV{PYTHONPATH}) endforeach() diff --git a/test/nmodl/transpiler/visitor/main.cpp b/test/nmodl/transpiler/visitor/main.cpp index 79bdd89433..39b207cb3f 100644 --- a/test/nmodl/transpiler/visitor/main.cpp +++ b/test/nmodl/transpiler/visitor/main.cpp @@ -8,18 +8,19 @@ #define CATCH_CONFIG_RUNNER #include -#include +#include "pybind/pyembed.hpp" #include "utils/logger.hpp" using namespace nmodl; int main(int argc, char* argv[]) { // initialize python interpreter once for entire catch executable - pybind11::scoped_interpreter guard{}; + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); // enable verbose logger output logger->set_level(spdlog::level::debug); // run all catch tests int result = Catch::Session().run(argc, argv); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); return result; } From f7a3e5b26f800d310dad4e8057c3c3513ae654c7 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 3 Jun 2020 16:27:31 +0200 Subject: [PATCH 269/871] Change GLOBAL to RANGE variables if they are written (BlueBrain/nmodl#230) * Added visitor that checks if a global variable is written and turns it into range * Added GlobalToRangeVisitor in main function - Visit ast with GlobalToRangeVisitor after the last symtab creation in main and after running PerfVisitor to calculate the write_count - Modified SymTab visitor, so that when symtab is updated and the visitor encounters a global_var, it checks if the property of the variable is range_var in the symtab and it keeps it that way if it is * Added test for GlobalToRange Visitor * Added option for global to range pass * Fix coreneuron cmake flags * Moved GlobalToRangeVisitor before CodegenCompatibilityVisitor * Visit only NeuronBlocks in GlobalToRangeVisitor * Added test for remove_property in symtab * Added -Dnmodl_PYTHONPATH CoreNeuron cmake flag otherwise it doesn't find nmodl Co-authored-by: Ioannis Magkanaris Co-authored-by: Pramod S Kumbhar Co-authored-by: Omar Awile Co-authored-by: Alessandro Cattabiani NMODL Repo SHA: BlueBrain/nmodl@393a6d97d731f1d1e415876dbd66bc59d982b968 --- .travis.yml | 5 +- src/nmodl/language/nmodl.yaml | 1 + src/nmodl/nmodl/main.cpp | 20 ++++ src/nmodl/symtab/symbol.hpp | 5 + src/nmodl/symtab/symbol_properties.hpp | 12 +++ src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/global_var_visitor.cpp | 69 ++++++++++++++ src/nmodl/visitors/global_var_visitor.hpp | 91 +++++++++++++++++++ test/nmodl/transpiler/CMakeLists.txt | 1 + test/nmodl/transpiler/symtab/symbol_table.cpp | 16 ++++ .../transpiler/visitor/global_to_range.cpp | 77 ++++++++++++++++ 11 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 src/nmodl/visitors/global_var_visitor.cpp create mode 100644 src/nmodl/visitors/global_var_visitor.hpp create mode 100644 test/nmodl/transpiler/visitor/global_to_range.cpp diff --git a/.travis.yml b/.travis.yml index 2e716a121a..5427906cf4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -78,6 +78,7 @@ before_install: export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; brew unlink python@2; brew link --overwrite python; + export SDKROOT="$(xcrun --show-sdk-path)"; else pyenv global $PYTHON_VERSION; fi @@ -126,8 +127,8 @@ after_success: - cd $HOME - git clone --recursive https://github.com/BlueBrain/CoreNeuron.git - mkdir CoreNeuron/build && cd CoreNeuron/build - - cmake .. -DENABLE_MPI=OFF -DENABLE_NMODL=ON -DNMODL_ROOT=$HOME/nmodl -DNMODL_EXTRA_FLAGS="passes --verbatim-rename --inline sympy --analytic" - - make -j + - cmake .. -DCORENRN_ENABLE_MPI=OFF -DCORENRN_ENABLE_NMODL=ON -DCORENRN_NMODL_DIR=$HOME/nmodl -Dnmodl_PYTHONPATH=$HOME/nmodl -DCORENRN_NMODL_FLAGS="sympy --analytic" + - make -j 2 - make test #============================================================================= diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index cc127d63be..dbb6f3b321 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -2026,6 +2026,7 @@ brief: "Vector of global variables" type: GlobalVar vector: true + add: true separator: ", " brief: "Represents GLOBAL statement in NMODL" diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index f009db0a3b..f660519184 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -26,6 +26,7 @@ #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" +#include "visitors/global_var_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" @@ -104,6 +105,9 @@ int main(int argc, const char* argv[]) { /// true if range variables to be converted to local bool nmodl_localize(false); + /// true if global variables to be converted to range + bool nmodl_global_to_range(false); + /// true if localize variables even if verbatim block is used bool localize_verbatim(false); @@ -205,6 +209,9 @@ int main(int argc, const char* argv[]) { passes_opt->add_flag("--localize", nmodl_localize, "Convert RANGE variables to LOCAL ({})"_format(nmodl_localize))->ignore_case(); + passes_opt->add_flag("--global-to-range", + nmodl_global_to_range, + "Convert GLOBAL variables to RANGE ({})"_format(nmodl_global_to_range))->ignore_case(); passes_opt->add_flag("--localize-verbatim", localize_verbatim, "Convert RANGE variables to LOCAL even if verbatim block exist ({})"_format(localize_verbatim))->ignore_case(); @@ -303,6 +310,19 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(*ast); } + /// GLOBAL to RANGE rename visitor + if (nmodl_global_to_range) { + // make sure to run perf visitor because code generator + // looks for read/write counts const/non-const declaration + PerfVisitor().visit_program(*ast); + // make sure to run the GlobalToRange visitor after all the + // reinitializations of Symtab + logger->info("Running GlobalToRange visitor"); + GlobalToRangeVisitor(ast).visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("globaltorange")); + } + { // Compatibility Checking logger->info("Running code compatibility checker"); diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index b50c8a53a3..6684febca9 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -304,6 +304,11 @@ class Symbol { properties |= property; } + /// remove property from symbol + void remove_property(syminfo::NmodlType property) { + properties &= ~property; + } + /// mark symbol as inlined (in case of procedure/function) void mark_inlined() noexcept { status |= syminfo::Status::inlined; diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 46893a816b..39492b0981 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -223,6 +223,12 @@ enum class NmodlType : enum_type { define = 1L << 34 }; +template +inline T operator~(T arg) { + using utype = enum_type; + return static_cast(~static_cast(arg)); +} + template inline T operator|(T lhs, T rhs) { using utype = enum_type; @@ -241,6 +247,12 @@ inline T& operator|=(T& lhs, T rhs) { return lhs; } +template +inline T& operator&=(T& lhs, T rhs) { + lhs = lhs & rhs; + return lhs; +} + /// check if any property is set inline bool has_property(const NmodlType& obj, NmodlType property) { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index e117fcdf8c..85671a692c 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -8,6 +8,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.hpp diff --git a/src/nmodl/visitors/global_var_visitor.cpp b/src/nmodl/visitors/global_var_visitor.cpp new file mode 100644 index 0000000000..323fc6c43c --- /dev/null +++ b/src/nmodl/visitors/global_var_visitor.cpp @@ -0,0 +1,69 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include +#include +#include + +#include "ast/global.hpp" +#include "ast/neuron_block.hpp" +#include "ast/program.hpp" +#include "ast/range.hpp" +#include "ast/statement_block.hpp" +#include "visitors/global_var_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +void GlobalToRangeVisitor::visit_neuron_block(ast::NeuronBlock& node) { + ast::RangeVarVector range_variables; + std::unordered_set global_variables_to_remove; + std::unordered_set global_statements_to_remove; + + auto& statement_block = node.get_statement_block(); + auto& statements = (*statement_block).get_statements(); + const auto& symbol_table = ast->get_symbol_table(); + + for (auto& statement: statements) { + /// only process global statements + if (statement->is_global()) { + const auto& global_variables = + std::static_pointer_cast(statement)->get_variables(); + global_variables_to_remove.clear(); + for (auto& global_variable: global_variables) { + auto variable_name = global_variable->get_node_name(); + /// check if global variable is being updated in the mod file + if (symbol_table->lookup(variable_name)->get_write_count() > 0) { + range_variables.emplace_back(new ast::RangeVar(global_variable->get_name())); + global_variables_to_remove.emplace(global_variable.get()); + } + } + + /// remove offending global variables + std::static_pointer_cast(statement)->erase_global_var( + global_variables_to_remove); + + /// add empty global statements to global_statements_to_remove + if (global_variables.empty()) { + global_statements_to_remove.emplace(statement.get()); + } + } + } + + /// remove offending global statements if empty + statement_block->erase_statement(global_statements_to_remove); + + /// insert new range variables replacing global ones + if (!range_variables.empty()) { + auto range_statement = new ast::Range(range_variables); + (*statement_block).emplace_back_statement(range_statement); + } +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/global_var_visitor.hpp b/src/nmodl/visitors/global_var_visitor.hpp new file mode 100644 index 0000000000..746ba98d47 --- /dev/null +++ b/src/nmodl/visitors/global_var_visitor.hpp @@ -0,0 +1,91 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::GlobalToRangeVisitor + */ + +#include +#include +#include +#include + +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + +/** + * \class GlobalToRangeVisitor + * \brief Visitor to convert GLOBAL variables to RANGE variables + * + * Some of the existing mod files have GLOBAL variables that are updated in BREAKPOINT + * or DERIVATIVE blocks. These variables have a single copy and works well when they + * are read only. If such variables that are written as well, they result into race condition. + * For example, + * + * \code{.mod} + * NEURON { + * SUFFIX test + * GLOBAL x + * } + * + * ASSIGNED { + * x + * } + * + * BREAKPOINT { + * x = 1 + * } + * \endcode + * + * In above example, x will be simultaneously updated in case of vectorization. In NEURON, + * such race condition is avoided by promoting these variables to thread variable (i.e. separate + * copy per thread). In case of CoreNEURON, this is not sufficient because of vectorisation or + * GPU execution. To address this issue, this visitor converts GLOBAL variables to RANGE variables + * when they are assigned / updated in compute functions. + */ + +class GlobalToRangeVisitor: public AstVisitor { + private: + /// ast::Ast* node + std::shared_ptr ast; + + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor + GlobalToRangeVisitor() = delete; + + /// Constructor that takes as parameter the AST + explicit GlobalToRangeVisitor(std::shared_ptr node) + : ast(std::move(node)) {} + + /// \} + + /// Visit ast::NeuronBlock nodes to check if there is any GLOBAL + /// variables defined in them that are written in any part of the code. + /// This is checked by reading the write_count member of the variable in + /// the symtab::SymbolTable. If it's written it removes the variable from + /// the ast::Global node and adds it to the ast::Range node of the + /// ast::NeuronBlock + void visit_neuron_block(ast::NeuronBlock& node) override; +}; + +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/CMakeLists.txt index 69f9da0f38..a9b5bda02e 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/CMakeLists.txt @@ -27,6 +27,7 @@ add_executable( visitor/main.cpp visitor/constant_folder.cpp visitor/defuse_analyze.cpp + visitor/global_to_range.cpp visitor/inline.cpp visitor/json.cpp visitor/kinetic_block.cpp diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/symtab/symbol_table.cpp index 024a65659b..52ec261fa0 100644 --- a/test/nmodl/transpiler/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/symtab/symbol_table.cpp @@ -128,6 +128,22 @@ SCENARIO("Multiple properties can be added to Symbol") { REQUIRE(symbol.has_all_properties(property) == false); } } + WHEN("remove properties from symbol") { + symbol.add_property(property1); + symbol.add_property(property2); + THEN("remove property that exists") { + REQUIRE(symbol.has_any_property(property2) == true); + symbol.remove_property(property2); + REQUIRE(symbol.has_any_property(property2) == false); + } + THEN("remove property that doesn't exist") { + REQUIRE(symbol.has_any_property(property3) == false); + symbol.remove_property(property3); + REQUIRE(symbol.has_any_property(property3) == false); + auto properties = property1 | property2; + REQUIRE(symbol.has_all_properties(properties) == true); + } + } WHEN("combined properties") { NmodlType property = NmodlType::factor_def | NmodlType::global_var; THEN("symbol has union of all properties") { diff --git a/test/nmodl/transpiler/visitor/global_to_range.cpp b/test/nmodl/transpiler/visitor/global_to_range.cpp new file mode 100644 index 0000000000..9d4c360a8d --- /dev/null +++ b/test/nmodl/transpiler/visitor/global_to_range.cpp @@ -0,0 +1,77 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/utils/nmodl_constructs.hpp" +#include "visitors/global_var_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; +using symtab::syminfo::NmodlType; + +//============================================================================= +// GlobalToRange visitor tests +//============================================================================= + +std::shared_ptr run_global_to_var_visitor(const std::string& text) { + std::map rval; + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(*ast); + PerfVisitor().visit_program(*ast); + GlobalToRangeVisitor(ast).visit_program(*ast); + SymtabVisitor().visit_program(*ast); + return ast; +} + +SCENARIO("GLOBAL to RANGE variable transformer", "[visitor][globaltorange]") { + GIVEN("mod file with GLOBAL variables that are written") { + std::string input_nmodl = R"( + NEURON { + SUFFIX test + RANGE a, b + GLOBAL x, y + } + ASSIGNED { + x + } + BREAKPOINT { + x = y + } + )"; + auto ast = run_global_to_var_visitor(input_nmodl); + auto symtab = ast->get_symbol_table(); + THEN("GLOBAL variables that are written are turned to RANGE") { + /// check for all RANGE variables : old ones + newly converted ones + auto vars = symtab->get_variables_with_properties(NmodlType::range_var); + REQUIRE(vars.size() == 3); + + /// x should be converted from GLOBAL to RANGE + auto x = symtab->lookup("x"); + REQUIRE(x != nullptr); + REQUIRE(x->has_any_property(NmodlType::range_var) == true); + REQUIRE(x->has_any_property(NmodlType::global_var) == false); + } + THEN("GLOBAL variables that are read only remain GLOBAL") { + auto vars = symtab->get_variables_with_properties(NmodlType::global_var); + REQUIRE(vars.size() == 1); + REQUIRE(vars[0]->get_name() == "y"); + } + } +} From 58b62590cc056d83a35a2f0f413be836c708ac06 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 3 Jun 2020 16:29:45 +0200 Subject: [PATCH 270/871] Fixup/docs deploy (BlueBrain/nmodl#344) * Fixing issue with documentation deployment NMODL Repo SHA: BlueBrain/nmodl@bdffb0cf73121efea8b1432b9c413a111ebdc514 --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5427906cf4..4e5e42d98b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -120,8 +120,8 @@ after_success: echo "------- Build Documentation -------"; python3 setup.py build_ext --inplace docs -j 2 -G "Unix Makefiles" || travis_terminate 1; cd $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx; - rm -rf doctrees && touch html/.nojekyll; - echo "" > html/index.html; + rm -rf doctest doctrees && touch .nojekyll; + echo "" > index.html; fi - echo "------- Build and Test CoreNEURON -------" - cd $HOME @@ -149,7 +149,7 @@ deploy: skip_cleanup: true github_token: $GITHUB_TOKEN keep_history: false - local_dir: $TRAVIS_BUILD_DIR/docs/_build/ + local_dir: $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx/ target_branch: gh-pages on: branch: master From df2825dac6c93771e2aecedcfab04a8a5d43f2e1 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Mon, 8 Jun 2020 00:09:39 +0200 Subject: [PATCH 271/871] Fix code generator dependencies (BlueBrain/nmodl#345) Unit ast headers and `all.hpp` are now rebuilt when template `node_class.template` is modified. It involves 2 fixes: * It is the Jinja template `all.hpp` that includes `node_class.template`, not `ast.hpp` * Jinja tasks extra dependencies are now declared at CMake level NMODL Repo SHA: BlueBrain/nmodl@a864a7a5eac50849387031114ea73030bb0030e7 --- src/nmodl/language/code_generator.cmake | 1 + src/nmodl/language/code_generator.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 2af9375925..c59e6af7c4 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -9,6 +9,7 @@ set(CODE_GENERATOR_JINJA_FILES ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast_decl.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node_class.template ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.cpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pysymtab.cpp diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index ae8848e2dd..cb2e3017f4 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -126,6 +126,8 @@ def _cmake_deps_task(self, tasks): outputs = dict() for task in tasks: inputs.add(task.input.relative_to(self.jinja_templates_dir)) + for dep in task.extradeps: + inputs.add(dep.relative_to(self.jinja_templates_dir)) dir, name = task.output.relative_to(self.base_dir).parts outputs.setdefault(dir, []).append(name) @@ -158,7 +160,7 @@ def workload(self): extradeps = collections.defaultdict( list, { - self.jinja_templates_dir / "ast" / "ast.hpp": [node_class_tpl], + self.jinja_templates_dir / "ast" / "all.hpp": [node_class_tpl], node_hpp_tpl: [node_class_tpl], }, ) From c3cb65b63ca9d4ff07c8299dbd86f1f04634e5e1 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Wed, 17 Jun 2020 11:34:51 +0200 Subject: [PATCH 272/871] Minor improvements of Ast node classes (BlueBrain/nmodl#349) * Node constructors: use `const&` for not integral types * Remove empty Doxygen groups, for instance ```c++ /// \name Setters /// \{ /// \} ``` * Node `get_xxx` methods: * mark them `noexcept` * return by value instead of reference for integral types, for instance: ```diff /** - * \brief Getter (const ref) for member variable \ref Integer.value + * \brief Getter for member variable \ref Integer.value */ - const int& get_value() const { + int get_value() const noexcept { return value; } ``` NMODL Repo SHA: BlueBrain/nmodl@0f618a30d3b65bbb5be099ed1952060073ba2f5e --- src/nmodl/language/node_info.py | 27 ++++++------ src/nmodl/language/nodes.py | 43 ++++++++++++++++--- .../templates/ast/node_class.template | 8 +++- src/nmodl/lexer/nmodl.ll | 2 +- 4 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 112fbbd848..335cacf67b 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -18,19 +18,20 @@ """ # yapf: disable -BASE_TYPES = {"short", - "int", - "float", - "double", - "std::string", - "BinaryOp", - "UnaryOp", - "ReactionOp", - "FirstLastType", - "QueueType", - "BAType", - "UnitStateType", - } +INTEGRAL_TYPES = {"short", + "int", + "float", + "double", + "BinaryOp", + "UnaryOp", + "ReactionOp", + "FirstLastType", + "QueueType", + "BAType", + "UnitStateType", + } + +BASE_TYPES = {"std::string" } | INTEGRAL_TYPES # base types which are enums ENUM_BASE_TYPES = {"BinaryOp", diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index db5b6e2f72..af960d3c83 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -115,6 +115,10 @@ def is_symbol_block_node(self): def is_base_type_node(self): return self.class_name in node_info.BASE_TYPES + @property + def is_integral_type_node(self): + return self.class_name in node_info.INTEGRAL_TYPES + @property def is_string_node(self): return self.class_name == node_info.STRING_NODE @@ -196,6 +200,13 @@ def get_typename(self): return type_name + def get_rvalue_typename(self): + """returns rvalue reference type of the node""" + typename = self.get_typename() + if self.is_base_type_node and not self.is_integral_type_node or self.is_ptr_excluded_node: + return "const " + typename + "&" + return typename + def get_shared_typename(self): """returns the shared pointer type of the node for declaration @@ -227,6 +238,14 @@ def member_typename(self): return type_name + @property + def member_rvalue_typename(self): + """returns rvalue reference type when used as returned or parameter type""" + typename = self.member_typename + if not self.is_integral_type_node: + return "const " + typename + "&" + return typename + def get_add_methods(self): s = '' if self.add_method: @@ -381,12 +400,12 @@ def get_node_name_method_definition(self, parent): def get_getter_method(self, class_name): getter_method = self.getter_method if self.getter_method else "get_" + to_snake_case(self.varname) getter_override = " override" if self.getter_override else "" - return_type = self.member_typename + return_type = self.member_rvalue_typename return f""" /** - * \\brief Getter (const ref) for member variable \\ref {class_name}.{self.varname} + * \\brief Getter for member variable \\ref {class_name}.{self.varname} */ - const {return_type}& {getter_method}() const {getter_override} {{ + {return_type} {getter_method}() const noexcept {getter_override} {{ return {self.varname}; }} """ @@ -546,6 +565,16 @@ def has_parent_block_node(self): """ return self.base_class == node_info.BASE_BLOCK + @property + def has_setters(self): + """returns True if the class has at least one setter member method""" + return any([ + self.is_name_node, + self.has_token, + self.is_symtab_needed, + self.is_data_type_node and not self.is_enum_node + ]) + @property def is_base_block_node(self): """ @@ -593,11 +622,11 @@ def is_base_class_number_node(self): return self.base_class == node_info.NUMBER_NODE def ctor_declaration(self): - args = [f'{c.get_typename()} {c.varname}' for c in self.children] + args = [f'{c.get_rvalue_typename()} {c.varname}' for c in self.children] return f"explicit {self.class_name}({', '.join(args)});" def ctor_definition(self): - args = [f'{c.get_typename()} {c.varname}' for c in self.children] + args = [f'{c.get_rvalue_typename()} {c.varname}' for c in self.children] initlist = [f'{c.varname}({c.varname})' for c in self.children] s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) @@ -606,11 +635,11 @@ def ctor_definition(self): return textwrap.dedent(s) def ctor_shrptr_declaration(self): - args = [f'{c.member_typename} {c.varname}' for c in self.children] + args = [f'{c.member_rvalue_typename} {c.varname}' for c in self.children] return f"explicit {self.class_name}({', '.join(args)});" def ctor_shrptr_definition(self): - args = [f'{c.member_typename} {c.varname}' for c in self.children] + args = [f'{c.member_rvalue_typename} {c.varname}' for c in self.children] initlist = [f'{c.varname}({c.varname})' for c in self.children] s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index 2a73258ab9..e4a70a4f46 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -64,8 +64,10 @@ class {{ node.class_name }} : public {{ node.base_class }} { /// \} + {% if node.is_base_block_node or node.is_number_node %} /// \name Not implemented /// \{ + {% endif %} {% if node.is_base_block_node %} virtual const ArgumentVector& get_parameters() const { @@ -79,7 +81,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { } {% endif %} - /// \} + {{ "/// \}" if node.is_base_block_node or node.is_number_node else "" }} /** * \brief Check if the ast node is an instance of ast::{{ node.class_name }} @@ -220,8 +222,10 @@ class {{ node.class_name }} : public {{ node.base_class }} { /// \} + {% if node.has_setters %} /// \name Setters /// \{ + {% endif %} {% if node.is_name_node %} /** @@ -274,7 +278,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { {{ child.get_setter_method_declaration(node.class_name) }} {% endfor %} - /// \} + {{ "/// \}" if node.has_setters else "" }} /// \name Visitor /// \{ diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 82dd11c05e..84c96d121a 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -231,7 +231,7 @@ ELSE { * as integer with token as it's name. Otherwise return it as * regular name token. */ else if (driver.is_defined_var(yytext)) { - auto value = driver.get_defined_var_value(yytext); + const auto& value = driver.get_defined_var_value(yytext); return integer_symbol(value, loc, yytext); } else { return name_symbol(yytext, loc); From 86e4f5f9392de35b122c2f65577a4c25be47f832 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Thu, 25 Jun 2020 12:01:23 +0200 Subject: [PATCH 273/871] Allow the same variable in read and write in USEION (BlueBrain/nmodl#346) This Fixes BlueBrain/nmodl#215 * Refactor test -> unit/test * Add integration tests with coreneuron to azure pipeline * Change ion_read_statements to support same name read/write variables * Change emplace -> insert as it is safer as Omar suggested NMODL Repo SHA: BlueBrain/nmodl@75ab684ab8959cdbbaa5212771015c01d95b44e1 --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 29 ++++++++--- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- .../transpiler/integration/mod/cabpump.mod | 50 +++++++++++++++++++ .../transpiler/{ => unit}/CMakeLists.txt | 8 +-- .../transpiler/{ => unit}/lexer/tokens.cpp | 0 .../{ => unit}/modtoken/modtoken.cpp | 2 +- .../transpiler/{ => unit}/newton/newton.cpp | 0 .../transpiler/{ => unit}/ode/__init__.py | 0 .../transpiler/{ => unit}/ode/test_ode.py | 0 .../transpiler/{ => unit}/parser/parser.cpp | 4 +- .../transpiler/{ => unit}/printer/printer.cpp | 0 .../transpiler/{ => unit}/pybind/__init__.py | 0 .../transpiler/{ => unit}/pybind/conftest.py | 0 .../transpiler/{ => unit}/pybind/test_ast.py | 0 .../{ => unit}/pybind/test_symtab.py | 0 .../{ => unit}/pybind/test_visitor.py | 0 .../{ => unit}/symtab/symbol_table.cpp | 0 .../transpiler/{ => unit}/units/lexer.cpp | 0 .../transpiler/{ => unit}/units/parser.cpp | 2 +- .../{ => unit}/utils/nmodl_constructs.cpp | 2 +- .../{ => unit}/utils/nmodl_constructs.hpp | 0 .../{ => unit}/utils/test_utils.cpp | 0 .../{ => unit}/utils/test_utils.hpp | 0 .../{ => unit}/visitor/constant_folder.cpp | 2 +- .../{ => unit}/visitor/defuse_analyze.cpp | 2 +- .../{ => unit}/visitor/global_to_range.cpp | 2 +- .../transpiler/{ => unit}/visitor/inline.cpp | 2 +- .../transpiler/{ => unit}/visitor/json.cpp | 0 .../{ => unit}/visitor/kinetic_block.cpp | 2 +- .../{ => unit}/visitor/localize.cpp | 2 +- .../transpiler/{ => unit}/visitor/lookup.cpp | 2 +- .../{ => unit}/visitor/loop_unroll.cpp | 2 +- .../transpiler/{ => unit}/visitor/main.cpp | 0 .../transpiler/{ => unit}/visitor/misc.cpp | 2 +- .../{ => unit}/visitor/neuron_solve.cpp | 2 +- .../transpiler/{ => unit}/visitor/nmodl.cpp | 4 +- .../transpiler/{ => unit}/visitor/perf.cpp | 0 .../transpiler/{ => unit}/visitor/rename.cpp | 2 +- .../{ => unit}/visitor/solve_block.cpp | 2 +- .../{ => unit}/visitor/steadystate.cpp | 2 +- .../{ => unit}/visitor/sympy_conductance.cpp | 2 +- .../{ => unit}/visitor/sympy_solver.cpp | 2 +- .../transpiler/{ => unit}/visitor/units.cpp | 4 +- .../{ => unit}/visitor/var_usage.cpp | 2 +- .../{ => unit}/visitor/verbatim.cpp | 0 46 files changed, 103 insertions(+), 38 deletions(-) create mode 100644 test/nmodl/transpiler/integration/mod/cabpump.mod rename test/nmodl/transpiler/{ => unit}/CMakeLists.txt (93%) rename test/nmodl/transpiler/{ => unit}/lexer/tokens.cpp (100%) rename test/nmodl/transpiler/{ => unit}/modtoken/modtoken.cpp (98%) rename test/nmodl/transpiler/{ => unit}/newton/newton.cpp (100%) rename test/nmodl/transpiler/{ => unit}/ode/__init__.py (100%) rename test/nmodl/transpiler/{ => unit}/ode/test_ode.py (100%) rename test/nmodl/transpiler/{ => unit}/parser/parser.cpp (98%) rename test/nmodl/transpiler/{ => unit}/printer/printer.cpp (100%) rename test/nmodl/transpiler/{ => unit}/pybind/__init__.py (100%) rename test/nmodl/transpiler/{ => unit}/pybind/conftest.py (100%) rename test/nmodl/transpiler/{ => unit}/pybind/test_ast.py (100%) rename test/nmodl/transpiler/{ => unit}/pybind/test_symtab.py (100%) rename test/nmodl/transpiler/{ => unit}/pybind/test_visitor.py (100%) rename test/nmodl/transpiler/{ => unit}/symtab/symbol_table.cpp (100%) rename test/nmodl/transpiler/{ => unit}/units/lexer.cpp (100%) rename test/nmodl/transpiler/{ => unit}/units/parser.cpp (99%) rename test/nmodl/transpiler/{ => unit}/utils/nmodl_constructs.cpp (99%) rename test/nmodl/transpiler/{ => unit}/utils/nmodl_constructs.hpp (100%) rename test/nmodl/transpiler/{ => unit}/utils/test_utils.cpp (100%) rename test/nmodl/transpiler/{ => unit}/utils/test_utils.hpp (100%) rename test/nmodl/transpiler/{ => unit}/visitor/constant_folder.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/defuse_analyze.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/global_to_range.cpp (98%) rename test/nmodl/transpiler/{ => unit}/visitor/inline.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/json.cpp (100%) rename test/nmodl/transpiler/{ => unit}/visitor/kinetic_block.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/localize.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/lookup.cpp (98%) rename test/nmodl/transpiler/{ => unit}/visitor/loop_unroll.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/main.cpp (100%) rename test/nmodl/transpiler/{ => unit}/visitor/misc.cpp (98%) rename test/nmodl/transpiler/{ => unit}/visitor/neuron_solve.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/nmodl.cpp (95%) rename test/nmodl/transpiler/{ => unit}/visitor/perf.cpp (100%) rename test/nmodl/transpiler/{ => unit}/visitor/rename.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/solve_block.cpp (98%) rename test/nmodl/transpiler/{ => unit}/visitor/steadystate.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/sympy_conductance.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/sympy_solver.cpp (99%) rename test/nmodl/transpiler/{ => unit}/visitor/units.cpp (98%) rename test/nmodl/transpiler/{ => unit}/visitor/var_usage.cpp (98%) rename test/nmodl/transpiler/{ => unit}/visitor/verbatim.cpp (100%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 867af95525..efe1b6b14d 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -159,7 +159,7 @@ set(MEMORYCHECK_COMMAND_OPTIONS # do not enable tests if nmodl is used as submodule if(NOT NMODL_AS_SUBPROJECT) include(CTest) - add_subdirectory(test) + add_subdirectory(test/unit) endif() # ============================================================================= diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index c3e4b177dd..0d3afa68ff 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -583,19 +583,25 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { return ion_read_statements_optimized(type); } std::vector statements; + std::unordered_set ion_vars; // used to keep track of the variables to not + // have doubles between read/write. Same name + // variables are allowed for (const auto& ion: info.ions) { auto name = ion.name; for (const auto& var: ion.reads) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var) || + ion_vars.find(var) != ion_vars.end()) { continue; } auto variable_names = read_ion_variable_name(var); auto first = get_variable_name(variable_names.first); auto second = get_variable_name(variable_names.second); statements.push_back("{} = {};"_format(first, second)); + ion_vars.insert(var); } for (const auto& var: ion.writes) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var) || + ion_vars.find(var) != ion_vars.end()) { continue; } if (ion.is_ionic_conc(var)) { @@ -603,6 +609,7 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { auto first = get_variable_name(variables.first); auto second = get_variable_name(variables.second); statements.push_back("{} = {};"_format(first, second)); + ion_vars.insert(var); } } } @@ -875,12 +882,13 @@ std::vector CodegenCVisitor::get_int_variables() { for (const auto& ion: info.ions) { bool need_style = false; - for (const auto& var: ion.reads) { - variables.emplace_back(make_symbol("ion_" + var)); - variables.back().is_constant = true; - } + std::unordered_set ion_vars; // used to keep track of the variables to not + // have doubles between read/write. Same name + // variables are allowed for (const auto& var: ion.writes) { - variables.emplace_back(make_symbol("ion_" + var)); + const std::string name = "ion_" + var; + ion_vars.insert(name); + variables.emplace_back(make_symbol(name)); if (ion.is_ionic_current(var)) { variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); } @@ -888,6 +896,13 @@ std::vector CodegenCVisitor::get_int_variables() { need_style = true; } } + for (const auto& var: ion.reads) { + const std::string name = "ion_" + var; + if (ion_vars.find(name) == ion_vars.end()) { + variables.emplace_back(make_symbol(name)); + variables.back().is_constant = true; + } + } if (need_style) { variables.emplace_back(make_symbol("style_" + ion.name), false, true); variables.back().is_constant = true; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 9eae86c9d9..44a5edc63e 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -121,7 +121,7 @@ void CodegenHelperVisitor::find_ion_variables() { } } - /// check if worte_conc(...) will be needed + /// check if write_conc(...) will be needed for (const auto& ion: info.ions) { for (const auto& var: ion.writes) { if (!ion.is_ionic_current(var) && !ion.is_rev_potential(var)) { diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod new file mode 100644 index 0000000000..035641877d --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -0,0 +1,50 @@ +: simple first-order model of calcium dynamics + +NEURON { + SUFFIX cadyn + USEION ca READ cai,ica WRITE cai + RANGE ca + GLOBAL depth,cainf,taur + +} + +UNITS { + (molar) = (1/liter) + (mM) = (milli/liter) + (um) = (micron) + (mA) = (milliamp) + (msM) = (ms mM) + FARADAY = (faraday) (coul) +} + +PARAMETER { + depth = .1 (um) + taur = 200 (ms) : rate of calcium removal for stress conditions + cainf = 50e-6(mM) :changed oct2 + cai (mM) +} + +ASSIGNED { + ica (mA/cm2) + drive_channel (mM/ms) +} + +STATE { + ca (mM) +} + + +BREAKPOINT { + SOLVE state METHOD euler +} + +DERIVATIVE state { + + drive_channel = - (10000) * ica / (2 * FARADAY * depth) + if (drive_channel <= 0.) { drive_channel = 0. } : cannot pump inward + ca' = drive_channel/18 + (cainf -ca)/taur*11 + cai = ca +} +INITIAL { + ca = cainf +} diff --git a/test/nmodl/transpiler/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt similarity index 93% rename from test/nmodl/transpiler/CMakeLists.txt rename to test/nmodl/transpiler/unit/CMakeLists.txt index a9b5bda02e..b440e142e2 100644 --- a/test/nmodl/transpiler/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -8,8 +8,7 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= # Common input data library # ============================================================================= -add_library(test_util STATIC ${CMAKE_CURRENT_SOURCE_DIR}/utils/nmodl_constructs.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/utils/test_utils.cpp) +add_library(test_util STATIC utils/nmodl_constructs.cpp utils/test_utils.cpp) # ============================================================================= # Common input data library @@ -123,8 +122,9 @@ endforeach() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/ode) -add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/pybind) +add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/unit/ode) +add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest + ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) foreach(test_name Ode Pybind) set_tests_properties( ${test_name} PROPERTIES ENVIRONMENT diff --git a/test/nmodl/transpiler/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp similarity index 100% rename from test/nmodl/transpiler/lexer/tokens.cpp rename to test/nmodl/transpiler/unit/lexer/tokens.cpp diff --git a/test/nmodl/transpiler/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp similarity index 98% rename from test/nmodl/transpiler/modtoken/modtoken.cpp rename to test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 4a1742d774..983d6b868f 100644 --- a/test/nmodl/transpiler/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -15,7 +15,7 @@ #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/test/nmodl/transpiler/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp similarity index 100% rename from test/nmodl/transpiler/newton/newton.cpp rename to test/nmodl/transpiler/unit/newton/newton.cpp diff --git a/test/nmodl/transpiler/ode/__init__.py b/test/nmodl/transpiler/unit/ode/__init__.py similarity index 100% rename from test/nmodl/transpiler/ode/__init__.py rename to test/nmodl/transpiler/unit/ode/__init__.py diff --git a/test/nmodl/transpiler/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py similarity index 100% rename from test/nmodl/transpiler/ode/test_ode.py rename to test/nmodl/transpiler/unit/ode/test_ode.py diff --git a/test/nmodl/transpiler/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp similarity index 98% rename from test/nmodl/transpiler/parser/parser.cpp rename to test/nmodl/transpiler/unit/parser/parser.cpp index 8aafa3c855..66511bf752 100644 --- a/test/nmodl/transpiler/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -16,8 +16,8 @@ #include "lexer/modtoken.hpp" #include "parser/diffeq_driver.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/nmodl_constructs.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/nmodl_constructs.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/test/nmodl/transpiler/printer/printer.cpp b/test/nmodl/transpiler/unit/printer/printer.cpp similarity index 100% rename from test/nmodl/transpiler/printer/printer.cpp rename to test/nmodl/transpiler/unit/printer/printer.cpp diff --git a/test/nmodl/transpiler/pybind/__init__.py b/test/nmodl/transpiler/unit/pybind/__init__.py similarity index 100% rename from test/nmodl/transpiler/pybind/__init__.py rename to test/nmodl/transpiler/unit/pybind/__init__.py diff --git a/test/nmodl/transpiler/pybind/conftest.py b/test/nmodl/transpiler/unit/pybind/conftest.py similarity index 100% rename from test/nmodl/transpiler/pybind/conftest.py rename to test/nmodl/transpiler/unit/pybind/conftest.py diff --git a/test/nmodl/transpiler/pybind/test_ast.py b/test/nmodl/transpiler/unit/pybind/test_ast.py similarity index 100% rename from test/nmodl/transpiler/pybind/test_ast.py rename to test/nmodl/transpiler/unit/pybind/test_ast.py diff --git a/test/nmodl/transpiler/pybind/test_symtab.py b/test/nmodl/transpiler/unit/pybind/test_symtab.py similarity index 100% rename from test/nmodl/transpiler/pybind/test_symtab.py rename to test/nmodl/transpiler/unit/pybind/test_symtab.py diff --git a/test/nmodl/transpiler/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py similarity index 100% rename from test/nmodl/transpiler/pybind/test_visitor.py rename to test/nmodl/transpiler/unit/pybind/test_visitor.py diff --git a/test/nmodl/transpiler/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp similarity index 100% rename from test/nmodl/transpiler/symtab/symbol_table.cpp rename to test/nmodl/transpiler/unit/symtab/symbol_table.cpp diff --git a/test/nmodl/transpiler/units/lexer.cpp b/test/nmodl/transpiler/unit/units/lexer.cpp similarity index 100% rename from test/nmodl/transpiler/units/lexer.cpp rename to test/nmodl/transpiler/unit/units/lexer.cpp diff --git a/test/nmodl/transpiler/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp similarity index 99% rename from test/nmodl/transpiler/units/parser.cpp rename to test/nmodl/transpiler/unit/units/parser.cpp index 058aec9cec..af3be01a95 100644 --- a/test/nmodl/transpiler/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -15,7 +15,7 @@ #include "config/config.h" #include "parser/diffeq_driver.hpp" #include "parser/unit_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" //============================================================================= // Parser tests diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp similarity index 99% rename from test/nmodl/transpiler/utils/nmodl_constructs.cpp rename to test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index bde2affd73..c09b15af5a 100644 --- a/test/nmodl/transpiler/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "test/utils/nmodl_constructs.hpp" +#include "nmodl_constructs.hpp" namespace nmodl { /// custom type to represent nmodl construct for testing diff --git a/test/nmodl/transpiler/utils/nmodl_constructs.hpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp similarity index 100% rename from test/nmodl/transpiler/utils/nmodl_constructs.hpp rename to test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp diff --git a/test/nmodl/transpiler/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp similarity index 100% rename from test/nmodl/transpiler/utils/test_utils.cpp rename to test/nmodl/transpiler/unit/utils/test_utils.cpp diff --git a/test/nmodl/transpiler/utils/test_utils.hpp b/test/nmodl/transpiler/unit/utils/test_utils.hpp similarity index 100% rename from test/nmodl/transpiler/utils/test_utils.hpp rename to test/nmodl/transpiler/unit/utils/test_utils.hpp diff --git a/test/nmodl/transpiler/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/constant_folder.cpp rename to test/nmodl/transpiler/unit/visitor/constant_folder.cpp index 216758dd8e..afd6d5a108 100644 --- a/test/nmodl/transpiler/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/defuse_analyze.cpp rename to test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 133c347ac3..f3dfadde17 100644 --- a/test/nmodl/transpiler/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp similarity index 98% rename from test/nmodl/transpiler/visitor/global_to_range.cpp rename to test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 9d4c360a8d..169490667b 100644 --- a/test/nmodl/transpiler/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/nmodl_constructs.hpp" +#include "test/unit/utils/nmodl_constructs.hpp" #include "visitors/global_var_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/inline.cpp rename to test/nmodl/transpiler/unit/visitor/inline.cpp index 89e406b391..307e58fa9c 100644 --- a/test/nmodl/transpiler/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/json.cpp b/test/nmodl/transpiler/unit/visitor/json.cpp similarity index 100% rename from test/nmodl/transpiler/visitor/json.cpp rename to test/nmodl/transpiler/unit/visitor/json.cpp diff --git a/test/nmodl/transpiler/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/kinetic_block.cpp rename to test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 0aa92c8688..4a05b20ea8 100644 --- a/test/nmodl/transpiler/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/localize.cpp rename to test/nmodl/transpiler/unit/visitor/localize.cpp index af37d4985e..d7e460bc83 100644 --- a/test/nmodl/transpiler/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp similarity index 98% rename from test/nmodl/transpiler/visitor/lookup.cpp rename to test/nmodl/transpiler/unit/visitor/lookup.cpp index 73f42f4b5b..7ad31179eb 100644 --- a/test/nmodl/transpiler/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/test/nmodl/transpiler/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/loop_unroll.cpp rename to test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index 1f33246c3b..6baa4a2300 100644 --- a/test/nmodl/transpiler/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp similarity index 100% rename from test/nmodl/transpiler/visitor/main.cpp rename to test/nmodl/transpiler/unit/visitor/main.cpp diff --git a/test/nmodl/transpiler/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp similarity index 98% rename from test/nmodl/transpiler/visitor/misc.cpp rename to test/nmodl/transpiler/unit/visitor/misc.cpp index 951df8bab4..f3249f70fe 100644 --- a/test/nmodl/transpiler/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/neuron_solve.cpp rename to test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 2bb689abc6..ec2c807de7 100644 --- a/test/nmodl/transpiler/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp similarity index 95% rename from test/nmodl/transpiler/visitor/nmodl.cpp rename to test/nmodl/transpiler/unit/visitor/nmodl.cpp index 58ed6b248e..8366eaeb12 100644 --- a/test/nmodl/transpiler/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -9,8 +9,8 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/nmodl_constructs.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/nmodl_constructs.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/perf.cpp b/test/nmodl/transpiler/unit/visitor/perf.cpp similarity index 100% rename from test/nmodl/transpiler/visitor/perf.cpp rename to test/nmodl/transpiler/unit/visitor/perf.cpp diff --git a/test/nmodl/transpiler/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/rename.cpp rename to test/nmodl/transpiler/unit/visitor/rename.cpp index b55e9ff3d5..68206f4db7 100644 --- a/test/nmodl/transpiler/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp similarity index 98% rename from test/nmodl/transpiler/visitor/solve_block.cpp rename to test/nmodl/transpiler/unit/visitor/solve_block.cpp index 616d4b821b..f58285b5b0 100644 --- a/test/nmodl/transpiler/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/steadystate.cpp rename to test/nmodl/transpiler/unit/visitor/steadystate.cpp index 61b6e8108d..5d9f1afcf4 100644 --- a/test/nmodl/transpiler/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/sympy_conductance.cpp rename to test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 7dc1812f8d..2ab9c5250b 100644 --- a/test/nmodl/transpiler/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp similarity index 99% rename from test/nmodl/transpiler/visitor/sympy_solver.cpp rename to test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 2b8ea31bc2..0852fb035f 100644 --- a/test/nmodl/transpiler/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp similarity index 98% rename from test/nmodl/transpiler/visitor/units.cpp rename to test/nmodl/transpiler/unit/visitor/units.cpp index da72c7376d..2f1b03b31d 100644 --- a/test/nmodl/transpiler/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -12,8 +12,8 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "src/config/config.h" -#include "test/utils/nmodl_constructs.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/nmodl_constructs.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/units_visitor.hpp" diff --git a/test/nmodl/transpiler/visitor/var_usage.cpp b/test/nmodl/transpiler/unit/visitor/var_usage.cpp similarity index 98% rename from test/nmodl/transpiler/visitor/var_usage.cpp rename to test/nmodl/transpiler/unit/visitor/var_usage.cpp index 67f34edd4f..69e1a59348 100644 --- a/test/nmodl/transpiler/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/unit/visitor/var_usage.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/utils/test_utils.hpp" +#include "test/unit/utils/test_utils.hpp" #include "visitors/var_usage_visitor.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/visitor/verbatim.cpp b/test/nmodl/transpiler/unit/visitor/verbatim.cpp similarity index 100% rename from test/nmodl/transpiler/visitor/verbatim.cpp rename to test/nmodl/transpiler/unit/visitor/verbatim.cpp From e0f8be236d34e5fae6c276ad997f0a1493a6e31b Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Thu, 25 Jun 2020 13:44:48 +0200 Subject: [PATCH 274/871] Fix declaration of dt_saved_value in ISPC backend (BlueBrain/nmodl#353) * Override visit_local_list_statement in CodegenIspcVisitor to declare dt_* as uniform This fixes BlueBrain/nmodl#329 NMODL Repo SHA: BlueBrain/nmodl@0dd00bcccaee45ef85f522c6e795901978676b1d --- src/nmodl/codegen/codegen_ispc_visitor.cpp | 44 ++++++++++++++++++++++ src/nmodl/codegen/codegen_ispc_visitor.hpp | 1 + 2 files changed, 45 insertions(+) diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 4e99e69ac2..8aa551962f 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -63,6 +63,50 @@ void CodegenIspcVisitor::visit_var_name(ast::VarName& node) { CodegenCVisitor::visit_var_name(node); } +void CodegenIspcVisitor::visit_local_list_statement(ast::LocalListStatement& node) { + if (!codegen) { + return; + } + /// Correct indentation + printer->add_newline(); + printer->add_indent(); + + /// Name of the variable _dt given by + /// nmodl::visitor::SteadystateVisitor::create_steadystate_block() + const std::string steadystate_dt_variable_name = "dt_saved_value"; + auto type = CodegenCVisitor::local_var_type() + " "; + printer->add_text(type); + auto local_variables = node.get_variables(); + bool dt_saved_value_exists = false; + + /// Remove dt_saved_value from local_variables if it exists + local_variables.erase(std::remove_if(local_variables.begin(), + local_variables.end(), + [&](const std::shared_ptr& local_variable) { + if (local_variable->get_node_name() == + steadystate_dt_variable_name) { + dt_saved_value_exists = true; + return true; + } else { + return false; + } + }), + local_variables.end()); + + /// Print the local_variables like normally + CodegenCVisitor::print_vector_elements(local_variables, ", "); + + /// Print dt_saved_value as uniform + if (dt_saved_value_exists) { + printer->add_text(";"); + /// Correct indentation + printer->add_newline(); + printer->add_indent(); + + type = CodegenCVisitor::local_var_type() + " uniform "; + printer->add_text(type + steadystate_dt_variable_name); + } +} /****************************************************************************************/ /* Routines must be overloaded in backend */ diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 13d171ffe4..7326746a16 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -233,6 +233,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { void visit_function_call(ast::FunctionCall& node) override; void visit_var_name(ast::VarName& node) override; void visit_program(ast::Program& node) override; + void visit_local_list_statement(ast::LocalListStatement& node) override; }; /** @} */ // end of codegen_backends From 9d75e573291a8ad493fcbf1b6f51c3c35e821f92 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 30 Jun 2020 12:33:22 +0300 Subject: [PATCH 275/871] Transform top level LOCAL variables to ASSIGNED (BlueBrain/nmodl#347) * Added visitor that checks if a global variable is written and turns it into range * Added GlobalToRangeVisitor in main function - Visit ast with GlobalToRangeVisitor after the last symtab creation in main and after running PerfVisitor to calculate the write_count * Added test for GlobalToRange Visitor * Added a lot of const, comments and print ast_to_nmodl after pass * Changes in GlobalToRange visitor according to katta/erase_bag_of_nodes_from_vectors * Ast generated code: fix template members / move definitions in compilation unit (BlueBrain/nmodl#351) * In `ast/all.hpp` the definition of template member functions are now after the definition of all Ast classes. * The definition of some member methods has been moved in `ast/ast.cpp` compilation units instead of inlining them. Co-authored-by: Ioannis Magkanaris Co-authored-by: Pramod S Kumbhar Co-authored-by: Omar Awile Co-authored-by: Tristan Carel NMODL Repo SHA: BlueBrain/nmodl@11eae0855d8d455d8247c0b86c0b52be0022fee5 --- src/nmodl/language/code_generator.cmake | 17 ++- src/nmodl/language/code_generator.py | 16 +- src/nmodl/language/nmodl.yaml | 1 + src/nmodl/language/nodes.py | 138 ++++++++++++++---- src/nmodl/language/templates/ast/all.hpp | 17 +++ src/nmodl/language/templates/ast/ast.cpp | 2 + .../templates/ast/node_class.template | 6 +- .../ast/node_class_inline_definition.template | 8 + src/nmodl/nmodl/main.cpp | 14 +- src/nmodl/visitors/CMakeLists.txt | 2 + .../visitors/local_to_assigned_visitor.cpp | 85 +++++++++++ .../visitors/local_to_assigned_visitor.hpp | 83 +++++++++++ test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../unit/visitor/local_to_assigned.cpp | 87 +++++++++++ 14 files changed, 432 insertions(+), 45 deletions(-) create mode 100644 src/nmodl/language/templates/ast/node_class_inline_definition.template create mode 100644 src/nmodl/visitors/local_to_assigned_visitor.cpp create mode 100644 src/nmodl/visitors/local_to_assigned_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index c59e6af7c4..9169f894f2 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -10,6 +10,7 @@ set(CODE_GENERATOR_JINJA_FILES ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast_decl.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node_class.template + ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node_class_inline_definition.template ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.cpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pysymtab.cpp @@ -191,6 +192,14 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/write_ion_var.hpp ) +set(PYBIND_GENERATED_SOURCES + ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp +) + set(VISITORS_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp @@ -207,14 +216,6 @@ set(VISITORS_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp ) -set(PYBIND_GENERATED_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp -) - set(NMODL_GENERATED_SOURCES ${AST_GENERATED_SOURCES} ${PYBIND_GENERATED_SOURCES} diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index cb2e3017f4..6b240de49f 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -154,16 +154,24 @@ def workload(self): node_hpp_tpl = self.jinja_templates_dir / "ast" / "node.hpp" # special template only included by other templates node_class_tpl = self.jinja_templates_dir / "ast" / "node_class.template" + node_class_inline_def_tpl = self.jinja_templates_dir / "ast" / "node_class_inline_definition.template" # Jinja templates that should be ignored ignored_templates = {node_class_tpl} # Additional dependencies Path -> [Path, ...] extradeps = collections.defaultdict( list, { - self.jinja_templates_dir / "ast" / "all.hpp": [node_class_tpl], - node_hpp_tpl: [node_class_tpl], + self.jinja_templates_dir / "ast" / "all.hpp": [node_class_tpl, node_class_inline_def_tpl], + node_hpp_tpl: [node_class_tpl, node_class_inline_def_tpl], }, ) + # Additional Jinja context set when rendering the template + extracontext = collections.defaultdict( + dict, + { + self.jinja_templates_dir / "ast" / "all.hpp": dict(render_ast_all=True) + } + ) tasks = [] for path in self.jinja_templates_dir.iterdir(): @@ -181,7 +189,7 @@ def workload(self): app=self, input=filepath, output=self.base_dir / node.cpp_header, - context=dict(node=node), + context=dict(node=node, **extracontext[filepath]), extradeps=extradeps[filepath], ) tasks.append(task) @@ -191,7 +199,7 @@ def workload(self): app=self, input=filepath, output=self.base_dir / sub_dir / filepath.name, - context=dict(nodes=self.nodes, node_info=node_info), + context=dict(nodes=self.nodes, node_info=node_info, **extracontext[filepath]), extradeps=extradeps[filepath], ) tasks.append(task) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index dbb6f3b321..3313b3f236 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -507,6 +507,7 @@ brief: "Vector of assigned variables" type: AssignedDefinition vector: true + add: true brief: "Represents a `ASSIGNED` block in the NMODL" description: | The `ASSIGNED` block is used for declaring two kinds of variables : diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index af960d3c83..09627c4d29 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -167,6 +167,15 @@ def requires_default_constructor(self): self.is_program_node or self.is_ptr_excluded_node) + @property + def has_template_methods(self): + """returns True if the corresponding C++ class has at least + one template member method, False otherwise""" + for child in self.children: + if child.add_method: + return True + return False + class ChildNode(BaseNode): """represent member variable for a Node""" @@ -246,7 +255,62 @@ def member_rvalue_typename(self): return "const " + typename + "&" return typename - def get_add_methods(self): + def get_add_methods_declaration(self): + s = '' + if self.add_method: + method = f""" + /** + * \\brief Add member to {self.varname} by raw pointer + */ + void emplace_back_{to_snake_case(self.class_name)}({self.class_name} *n); + + /** + * \\brief Add member to {self.varname} by shared_ptr + */ + void emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n); + + /** + * \\brief Erase member to {self.varname} + */ + {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first); + + /** + * \\brief Erase members to {self.varname} + */ + {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last); + + /** + * \\brief Erase non-consecutive members to {self.varname} + * + * loosely following the cpp reference of remove_if + */ + size_t erase_{to_snake_case(self.class_name)}(std::unordered_set<{self.class_name}*>& to_be_erased); + + /** + * \\brief Insert member to {self.varname} + */ + {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n); + + /** + * \\brief Insert members to {self.varname} + */ + template + void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, NodeType& to, InputIterator first, InputIterator last); + + /** + * \\brief Reset member to {self.varname} + */ + void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n); + + /** + * \\brief Reset member to {self.varname} + */ + void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n); + """ + s = textwrap.dedent(method) + return s + + def get_add_methods_definition(self, parent): s = '' if self.add_method: set_parent = "n->set_parent(this); " @@ -260,7 +324,7 @@ def get_add_methods(self): /** * \\brief Add member to {self.varname} by raw pointer */ - void emplace_back_{to_snake_case(self.class_name)}({self.class_name} *n) {{ + void {parent.class_name}::emplace_back_{to_snake_case(self.class_name)}({self.class_name} *n) {{ {self.varname}.emplace_back(n); // set parents @@ -270,7 +334,7 @@ def get_add_methods(self): /** * \\brief Add member to {self.varname} by shared_ptr */ - void emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n) {{ + void {parent.class_name}::emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n) {{ {self.varname}.emplace_back(n); // set parents {set_parent} @@ -279,28 +343,28 @@ def get_add_methods(self): /** * \\brief Erase member to {self.varname} */ - {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first) {{ + {self.class_name}Vector::const_iterator {parent.class_name}::erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first) {{ auto first_it = const_iter_cast({self.varname}, first); return {self.varname}.erase(first_it); }} /** * \\brief Erase members to {self.varname} */ - {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last) {{ + {self.class_name}Vector::const_iterator {parent.class_name}::erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last) {{ auto first_it = const_iter_cast({self.varname}, first); auto last_it = const_iter_cast({self.varname}, last); return {self.varname}.erase(first_it, last_it); }} /** * \\brief Erase non-consecutive members to {self.varname} - * + * * loosely following the cpp reference of remove_if */ - size_t erase_{to_snake_case(self.class_name)}(std::unordered_set<{self.class_name}*>& to_be_erased) {{ + size_t {parent.class_name}::erase_{to_snake_case(self.class_name)}(std::unordered_set<{self.class_name}*>& to_be_erased) {{ auto first = {self.varname}.begin(); auto last = {self.varname}.end(); auto result = first; - + while (first != last) {{ // automatically erase dangling pointers from the uset while // looking for them to erase them in the vector @@ -310,43 +374,26 @@ def get_add_methods(self): }} ++first; }} - + size_t out = last - result; erase_{to_snake_case(self.class_name)}(result, last); - + return out; }} - /** * \\brief Insert member to {self.varname} */ - {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n) {{ + {self.class_name}Vector::const_iterator {parent.class_name}::insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n) {{ {set_parent} auto pos_it = const_iter_cast({self.varname}, position); return {self.varname}.insert(pos_it, n); }} - /** - * \\brief Insert members to {self.varname} - */ - template - void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, NodeType& to, InputIterator first, InputIterator last) {{ - - for (auto it = first; it != last; ++it) {{ - auto& n = *it; - //set parents - {set_parent} - }} - auto pos_it = const_iter_cast({self.varname}, position); - auto first_it = const_iter_cast(to, first); - auto last_it = const_iter_cast(to, last); - {self.varname}.insert(pos_it, first_it, last_it); - }} /** * \\brief Reset member to {self.varname} */ - void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n) {{ + void {parent.class_name}::reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n) {{ //set parents {set_parent} @@ -356,7 +403,7 @@ def get_add_methods(self): /** * \\brief Reset member to {self.varname} */ - void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n) {{ + void {parent.class_name}::reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n) {{ //set parents {set_parent} @@ -366,6 +413,37 @@ def get_add_methods(self): s = textwrap.dedent(method) return s + def get_add_methods_inline_definition(self, parent): + s = '' + if self.add_method: + set_parent = "n->set_parent(this); " + if self.optional: + set_parent = f""" + if (n) {{ + n->set_parent(this); + }} + """ + method = f""" + /** + * \\brief Insert members to {self.varname} + */ + template + void {parent.class_name}::insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, NodeType& to, InputIterator first, InputIterator last) {{ + + for (auto it = first; it != last; ++it) {{ + auto& n = *it; + //set parents + {set_parent} + }} + auto pos_it = const_iter_cast({self.varname}, position); + auto first_it = const_iter_cast(to, first); + auto last_it = const_iter_cast(to, last); + {self.varname}.insert(pos_it, first_it, last_it); + }} + """ + s = textwrap.dedent(method) + return s + def get_node_name_method_declaration(self): s = '' if self.get_node_name: diff --git a/src/nmodl/language/templates/ast/all.hpp b/src/nmodl/language/templates/ast/all.hpp index dfbf02880b..23689bab97 100644 --- a/src/nmodl/language/templates/ast/all.hpp +++ b/src/nmodl/language/templates/ast/all.hpp @@ -24,6 +24,23 @@ {% for node in nodes %} #ifndef {{ node.cpp_fence }} #define {{ node.cpp_fence }} +{% if node.has_template_methods %} +#define {{ node.cpp_fence }}_INLINE_DEFINITION_REQUIRED +{% endif %} {% include "ast/node_class.template" %} #endif // !{{ node.cpp_fence }} {% endfor %} + +{# add inline definitions of template member methods #} +namespace nmodl { +namespace ast { +{% for node in nodes %} +{%- if node.has_template_methods %} +#ifdef {{ node.cpp_fence }}_INLINE_DEFINITION_REQUIRED + {% include "ast/node_class_inline_definition.template" %} +#endif // !{{ node.cpp_fence }}_INLINE_DEFINITION_REQUIRED + +{% endif %} +{%- endfor %} +} // namespace ast +} // namespace nmodl diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index cc95aa313c..2b33ff9bbb 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -81,6 +81,8 @@ void Ast::set_parent(Ast* p) { /// {% for child in node.children %} + {{ child.get_add_methods_definition(node) }} + {{ child.get_node_name_method_definition(node) }} {% endfor %} diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index e4a70a4f46..c7145dee10 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -212,7 +212,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { {# doxygen for these methods is handled by nodes.py #} {% for child in node.children %} - {{ child.get_add_methods() }} + {{ child.get_add_methods_declaration() }} {{ child.get_node_name_method_declaration() }} @@ -374,5 +374,9 @@ class {{ node.class_name }} : public {{ node.base_class }} { /** @} */ // end of ast_class +{% if render_ast_all is not defined %} +{% include "ast/node_class_inline_definition.template" %} +{% endif %} + } // namespace ast } // namespace nmodl diff --git a/src/nmodl/language/templates/ast/node_class_inline_definition.template b/src/nmodl/language/templates/ast/node_class_inline_definition.template new file mode 100644 index 0000000000..2e78a6bf94 --- /dev/null +++ b/src/nmodl/language/templates/ast/node_class_inline_definition.template @@ -0,0 +1,8 @@ +{# + this Jinja template is not used to directly generate + a file but included by other templates. +#} +{# doxygen for these methods is handled by nodes.py #} +{% for child in node.children %} + {{ child.get_add_methods_inline_definition(node) }} +{% endfor %} diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index f660519184..c6bf21e3f5 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -30,6 +30,7 @@ #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" +#include "visitors/local_to_assigned_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" @@ -320,13 +321,22 @@ int main(int argc, const char* argv[]) { logger->info("Running GlobalToRange visitor"); GlobalToRangeVisitor(ast).visit_program(*ast); SymtabVisitor(update_symtab).visit_program(*ast); - ast_to_nmodl(*ast, filepath("globaltorange")); + ast_to_nmodl(*ast, filepath("global_to_range")); + } + + /// LOCAL to ASSIGNED visitor + { + logger->info("Running LOCAL to ASSIGNED visitor"); + PerfVisitor().visit_program(*ast); + LocalToAssignedVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("local_to_assigned")); } { // Compatibility Checking logger->info("Running code compatibility checker"); - // run perfvisitor to update read/wrie counts + // run perfvisitor to update read/write counts PerfVisitor().visit_program(*ast); // If there is an incompatible construct and code generation is not forced exit NMODL if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast) && !force_codegen) { diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 85671a692c..2146356b05 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -14,6 +14,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_to_assigned_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/local_to_assigned_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp diff --git a/src/nmodl/visitors/local_to_assigned_visitor.cpp b/src/nmodl/visitors/local_to_assigned_visitor.cpp new file mode 100644 index 0000000000..538f3046c0 --- /dev/null +++ b/src/nmodl/visitors/local_to_assigned_visitor.cpp @@ -0,0 +1,85 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include +#include +#include + +#include "ast/assigned_block.hpp" +#include "ast/local_list_statement.hpp" +#include "ast/program.hpp" +#include "visitors/local_to_assigned_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +void LocalToAssignedVisitor::visit_program(ast::Program& node) { + ast::AssignedDefinitionVector assigned_variables; + std::unordered_set local_variables_to_remove; + std::unordered_set local_nodes_to_remove; + std::shared_ptr assigned_block; + + const auto& top_level_nodes = node.get_blocks(); + const auto& symbol_table = node.get_symbol_table(); + + for (auto& top_level_node: top_level_nodes) { + /// save pointer to assigned block to add new variables (only one block) + if (top_level_node->is_assigned_block()) { + assigned_block = std::static_pointer_cast(top_level_node); + } + + /// only process local_list statements otherwise continue + if (!top_level_node->is_local_list_statement()) { + continue; + } + + const auto& local_list_statement = std::static_pointer_cast( + top_level_node); + + for (const auto& local_variable: local_list_statement->get_variables()) { + auto variable_name = local_variable->get_node_name(); + /// check if local variable is being updated in the mod file + if (symbol_table->lookup(variable_name)->get_write_count() > 0) { + assigned_variables.emplace_back( + std::make_shared(local_variable->get_name(), + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr)); + local_variables_to_remove.insert(local_variable.get()); + } + } + + /// remove local variables being converted to assigned + local_list_statement->erase_local_var(local_variables_to_remove); + + /// if all local variables are converted to assigned, keep track + /// for removal + if (local_list_statement->get_variables().empty()) { + local_nodes_to_remove.insert(top_level_node.get()); + } + } + + /// remove empty local statements if empty + node.erase_node(local_nodes_to_remove); + + /// if no assigned block found add one to the node otherwise emplace back new assigned variables + if (assigned_block == nullptr) { + assigned_block = std::make_shared(assigned_variables); + node.emplace_back_node(std::static_pointer_cast(assigned_block)); + } else { + for (auto& assigned_variable: assigned_variables) { + assigned_block->emplace_back_assigned_definition(assigned_variable); + } + } +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/local_to_assigned_visitor.hpp b/src/nmodl/visitors/local_to_assigned_visitor.hpp new file mode 100644 index 0000000000..3da902b1a1 --- /dev/null +++ b/src/nmodl/visitors/local_to_assigned_visitor.hpp @@ -0,0 +1,83 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::LocalToAssignedVisitor + */ + +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + +/** + * \class LocalToAssignedVisitor + * \brief Visitor to convert top level LOCAL variables to ASSIGNED variables + * + * Some of the existing mod file include declaration of LOCAL variables in + * the top level of the mod file. Those variables are normally written in + * the INITIAL block which is executed potentially by multiple threads. + * This results into race condition in case of CoreNEURON. To avoid this, + * such variables are converted to ASSIGNED variables which by default are + * handled as RANGE. + * + * For example: + * + * \code{.mod} + * NEURON { + * SUFFIX test + * RANGE x + * } + * + * LOCAL qt + * + * INITIAL { + * qt = 10.0 + * } + * + * PROCEDURE rates(v (mV)) { + * x = qt + 12.2 + * } + * \endcode + * + * In the above example, `qt` is used as temporary variable to pass value from + * INITIAL block to PROCEDURE. This works fine in case of serial execution but + * in parallel execution we end up in race condition. To avoid this, we convert + * qt to ASSIGNED variable. + * + * \todo + * - Variables like qt are often constant. As long as INITIAL block is executed + * serially or qt is updated in atomic way then we don't have a problem. + */ + +class LocalToAssignedVisitor: public AstVisitor { + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor + LocalToAssignedVisitor() = default; + + /// \} + + /// Visit ast::Program node to transform top level LOCAL variables + /// to ASSIGNED if they are written in the mod file + void visit_program(ast::Program& node) override; +}; + +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index b440e142e2..93c8bd3a27 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -31,6 +31,7 @@ add_executable( visitor/json.cpp visitor/kinetic_block.cpp visitor/localize.cpp + visitor/local_to_assigned.cpp visitor/lookup.cpp visitor/loop_unroll.cpp visitor/misc.cpp diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp new file mode 100644 index 0000000000..b734fdd136 --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -0,0 +1,87 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/nmodl_constructs.hpp" +#include "visitors/local_to_assigned_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; +using symtab::syminfo::NmodlType; + +//============================================================================= +// GlobalToRange visitor tests +//============================================================================= + +std::shared_ptr run_local_to_assigned_visitor(const std::string& text) { + std::map rval; + NmodlDriver driver; + auto ast = driver.parse_string(text); + + SymtabVisitor().visit_program(*ast); + PerfVisitor().visit_program(*ast); + LocalToAssignedVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); + return ast; +} + +SCENARIO("LOCAL to ASSIGNED variable transformer", "[visitor][localtoassigned]") { + GIVEN("mod file with LOCAL variables that are written") { + std::string input_nmodl = R"( + NEURON { + SUFFIX test + } + + LOCAL x, y, z + + INITIAL { + x = 1 + } + + BREAKPOINT { + z = 2 + } + )"; + + auto ast = run_local_to_assigned_visitor(input_nmodl); + auto symtab = ast->get_symbol_table(); + + THEN("LOCAL variables that are written are turned to ASSIGNED") { + /// check for all ASSIGNED variables : old ones + newly converted ones + auto vars = symtab->get_variables_with_properties(NmodlType::assigned_definition); + REQUIRE(vars.size() == 2); + + /// x and z should be converted from LOCAL to ASSIGNED + auto x = symtab->lookup("x"); + REQUIRE(x != nullptr); + REQUIRE(x->has_any_property(NmodlType::assigned_definition) == true); + REQUIRE(x->has_any_property(NmodlType::local_var) == false); + + auto z = symtab->lookup("z"); + REQUIRE(z != nullptr); + REQUIRE(z->has_any_property(NmodlType::assigned_definition) == true); + REQUIRE(z->has_any_property(NmodlType::local_var) == false); + } + + THEN("LOCAL variables that are read only remain LOCAL") { + auto vars = symtab->get_variables_with_properties(NmodlType::local_var); + REQUIRE(vars.size() == 1); + REQUIRE(vars[0]->get_name() == "y"); + } + } +} From 541a2636dbcdce6c1806113a004c372ab058eebc Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 30 Jun 2020 19:03:15 +0200 Subject: [PATCH 276/871] Fix useion variable reordering (issue BlueBrain/nmodl#355) (BlueBrain/nmodl#356) NMODL Repo SHA: BlueBrain/nmodl@3759c09f5577414994b268913f3e30419f5b4742 --- src/nmodl/codegen/codegen_c_visitor.cpp | 39 ++++++++++++------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 0d3afa68ff..f048109841 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -583,25 +583,19 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { return ion_read_statements_optimized(type); } std::vector statements; - std::unordered_set ion_vars; // used to keep track of the variables to not - // have doubles between read/write. Same name - // variables are allowed for (const auto& ion: info.ions) { auto name = ion.name; for (const auto& var: ion.reads) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var) || - ion_vars.find(var) != ion_vars.end()) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } auto variable_names = read_ion_variable_name(var); auto first = get_variable_name(variable_names.first); auto second = get_variable_name(variable_names.second); statements.push_back("{} = {};"_format(first, second)); - ion_vars.insert(var); } for (const auto& var: ion.writes) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var) || - ion_vars.find(var) != ion_vars.end()) { + if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { continue; } if (ion.is_ionic_conc(var)) { @@ -609,7 +603,6 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { auto first = get_variable_name(variables.first); auto second = get_variable_name(variables.second); statements.push_back("{} = {};"_format(first, second)); - ion_vars.insert(var); } } } @@ -882,13 +875,24 @@ std::vector CodegenCVisitor::get_int_variables() { for (const auto& ion: info.ions) { bool need_style = false; - std::unordered_set ion_vars; // used to keep track of the variables to not - // have doubles between read/write. Same name - // variables are allowed - for (const auto& var: ion.writes) { + std::unordered_map ion_vars; // used to keep track of the variables to + // not have doubles between read/write. Same + // name variables are allowed + for (const auto& var: ion.reads) { const std::string name = "ion_" + var; - ion_vars.insert(name); variables.emplace_back(make_symbol(name)); + variables.back().is_constant = true; + ion_vars[name] = variables.size() - 1; + } + for (const auto& var: ion.writes) { + const std::string name = "ion_" + var; + + const auto ion_vars_it = ion_vars.find(name); + if (ion_vars_it != ion_vars.end()) { + variables[ion_vars_it->second].is_constant = false; + } else { + variables.emplace_back(make_symbol("ion_" + var)); + } if (ion.is_ionic_current(var)) { variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); } @@ -896,13 +900,6 @@ std::vector CodegenCVisitor::get_int_variables() { need_style = true; } } - for (const auto& var: ion.reads) { - const std::string name = "ion_" + var; - if (ion_vars.find(name) == ion_vars.end()) { - variables.emplace_back(make_symbol(name)); - variables.back().is_constant = true; - } - } if (need_style) { variables.emplace_back(make_symbol("style_" + ion.name), false, true); variables.back().is_constant = true; From 5d4c7e56706b7704ed981e0bc71be9ee0471a0e1 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 1 Jul 2020 02:41:17 +0200 Subject: [PATCH 277/871] Add shim for python wheels (BlueBrain/nmodl#352) - Add support for shim for wheel - Remove _skbuild after wheel building to ensure install from pristine - Add Azure CI support - Move libs with CMAKE_SHARED_LIBRARY_SUFFIX for simplicity and generality - Add support for mac and libpywrapper.dylib - Travis, docs and the new path for _nmodl not recognized by inplace - Update azure-pipelines.yml - pywheel shim is working - add shim scripts - skbuild already copies an nmodl folder and installs it in the correct spot. Add wrapper and nmodl to .data in that folder - Add sourcetrail files to gitignore - Python wheel preparation - shuffling of libpywrapper.so and _nmodl*.so to prepare for the shim - skbuild already creates an nmodl folder in cmake-install. We now operate on this premise - Add versioning and nightly tag for wheel - detect version number based on commits from last tag - link to pywrapper if not a wheel Co-authored-by: Omar Awile Co-authored-by: Alessandro Cattabiani Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@806e08c68a72d7a503beda8f83dc728fc0e41b60 --- INSTALL.md | 4 +- pywheel/shim/_binwrapper.py | 36 +++ pywheel/shim/find_libpython.py | 364 ++++++++++++++++++++++ pywheel/shim/nmodl | 1 + setup.py | 62 +++- src/nmodl/pybind/CMakeLists.txt | 30 +- test/nmodl/transpiler/unit/CMakeLists.txt | 19 +- 7 files changed, 493 insertions(+), 23 deletions(-) create mode 100755 pywheel/shim/_binwrapper.py create mode 100644 pywheel/shim/find_libpython.py create mode 120000 pywheel/shim/nmodl diff --git a/INSTALL.md b/INSTALL.md index b2cae306b8..7e79b9bf7c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -76,7 +76,7 @@ make -j && make install And set PYTHONPATH as: ```sh -export PYTHONPATH=$HOME/nmodl/lib/python:$PYTHONPATH +export PYTHONPATH=$HOME/nmodl/lib:$PYTHONPATH ``` #### Flex / Bison Paths @@ -120,7 +120,7 @@ export NMODL_PYLIB=/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Ve ```` * 'NMODL_WRAPLIB': This variable should point to the `libpywrapper.so` built as part of NMODL, for example: ```sh -export NMODL_WRAPLIB=/opt/nmodl/lib/python/nmodl/libpywrapper.dylib +export NMODL_WRAPLIB=/opt/nmodl/lib/libpywrapper.so ``` **Note**: In order for all unit tests to function correctly when building without linking against libpython we must diff --git a/pywheel/shim/_binwrapper.py b/pywheel/shim/_binwrapper.py new file mode 100755 index 0000000000..114861dcc5 --- /dev/null +++ b/pywheel/shim/_binwrapper.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +A generic wrapper to access nmodl binaries from a python installation +Please create a softlink with the binary name to be called. +""" + +import os +import sys +import stat +from pkg_resources import working_set +from find_libpython import find_libpython + +def _config_exe(exe_name): + """Sets the environment to run the real executable (returned)""" + + package_name = 'nmodl' + + assert package_name in working_set.by_key, "NMODL package not found! Verify PYTHONPATH" + NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, 'nmodl') + NMODL_PREFIX_DATA = os.path.join(NMODL_PREFIX, '.data') + if sys.platform == "darwin" : + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.dylib') + else: + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.so') + + # find libpython*.so in the system + os.environ["NMODL_PYLIB"] = find_libpython() + + return os.path.join(NMODL_PREFIX_DATA, exe_name) + +if __name__ == '__main__': + """Set the pointed file as executable""" + exe = _config_exe(os.path.basename(sys.argv[0])) + st = os.stat(exe) + os.chmod(exe, st.st_mode | stat.S_IEXEC) + os.execv(exe, sys.argv) diff --git a/pywheel/shim/find_libpython.py b/pywheel/shim/find_libpython.py new file mode 100644 index 0000000000..3ff09e9cf1 --- /dev/null +++ b/pywheel/shim/find_libpython.py @@ -0,0 +1,364 @@ +#!/usr/bin/env python + +""" +Locate libpython associated with this Python executable. +""" + +# License +# +# Copyright 2018, Takafumi Arakaki +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +from __future__ import print_function, absolute_import + +from logging import getLogger +import ctypes.util +import functools +import os +import sys +import sysconfig + +logger = getLogger("find_libpython") + +is_windows = os.name == "nt" +is_apple = sys.platform == "darwin" + +SHLIB_SUFFIX = sysconfig.get_config_var("SHLIB_SUFFIX") +if SHLIB_SUFFIX is None: + if is_windows: + SHLIB_SUFFIX = ".dll" + else: + SHLIB_SUFFIX = ".so" +if is_apple: + # sysconfig.get_config_var("SHLIB_SUFFIX") can be ".so" in macOS. + # Let's not use the value from sysconfig. + SHLIB_SUFFIX = ".dylib" + + +def linked_libpython(): + """ + Find the linked libpython using dladdr (in *nix). + Calling this in Windows always return `None` at the moment. + Returns + ------- + path : str or None + A path to linked libpython. Return `None` if statically linked. + """ + if is_windows: + return None + return _linked_libpython_unix() + + +class Dl_info(ctypes.Structure): + _fields_ = [ + ("dli_fname", ctypes.c_char_p), + ("dli_fbase", ctypes.c_void_p), + ("dli_sname", ctypes.c_char_p), + ("dli_saddr", ctypes.c_void_p), + ] + + +def _linked_libpython_unix(): + libdl = ctypes.CDLL(ctypes.util.find_library("dl")) + libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)] + libdl.dladdr.restype = ctypes.c_int + + dlinfo = Dl_info() + retcode = libdl.dladdr( + ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), + ctypes.pointer(dlinfo)) + if retcode == 0: # means error + return None + path = os.path.realpath(dlinfo.dli_fname.decode()) + if path == os.path.realpath(sys.executable): + return None + return path + + +def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows): + """ + Convert a file basename `name` to a library name (no "lib" and ".so" etc.) + >>> library_name("libpython3.7m.so") # doctest: +SKIP + 'python3.7m' + >>> library_name("libpython3.7m.so", suffix=".so", is_windows=False) + 'python3.7m' + >>> library_name("libpython3.7m.dylib", suffix=".dylib", is_windows=False) + 'python3.7m' + >>> library_name("python37.dll", suffix=".dll", is_windows=True) + 'python37' + """ + if not is_windows and name.startswith("lib"): + name = name[len("lib"):] + if suffix and name.endswith(suffix): + name = name[:-len(suffix)] + return name + + +def append_truthy(list, item): + if item: + list.append(item) + + +def uniquifying(items): + """ + Yield items while excluding the duplicates and preserving the order. + >>> list(uniquifying([1, 2, 1, 2, 3])) + [1, 2, 3] + """ + seen = set() + for x in items: + if x not in seen: + yield x + seen.add(x) + + +def uniquified(func): + """ Wrap iterator returned from `func` by `uniquifying`. """ + @functools.wraps(func) + def wrapper(*args, **kwds): + return uniquifying(func(*args, **kwds)) + return wrapper + + +@uniquified +def candidate_names(suffix=SHLIB_SUFFIX): + """ + Iterate over candidate file names of libpython. + Yields + ------ + name : str + Candidate name libpython. + """ + LDLIBRARY = sysconfig.get_config_var("LDLIBRARY") + if LDLIBRARY: + yield LDLIBRARY + + LIBRARY = sysconfig.get_config_var("LIBRARY") + if LIBRARY: + yield os.path.splitext(LIBRARY)[0] + suffix + + dlprefix = "" if is_windows else "lib" + sysdata = dict( + v=sys.version_info, + # VERSION is X.Y in Linux/macOS and XY in Windows: + VERSION=(sysconfig.get_config_var("VERSION") or + "{v.major}.{v.minor}".format(v=sys.version_info)), + ABIFLAGS=(sysconfig.get_config_var("ABIFLAGS") or + sysconfig.get_config_var("abiflags") or ""), + ) + + for stem in [ + "python{VERSION}{ABIFLAGS}".format(**sysdata), + "python{VERSION}".format(**sysdata), + "python{v.major}".format(**sysdata), + "python", + ]: + yield dlprefix + stem + suffix + + + +@uniquified +def candidate_paths(suffix=SHLIB_SUFFIX): + """ + Iterate over candidate paths of libpython. + Yields + ------ + path : str or None + Candidate path to libpython. The path may not be a fullpath + and may not exist. + """ + + yield linked_libpython() + + # List candidates for directories in which libpython may exist + lib_dirs = [] + append_truthy(lib_dirs, sysconfig.get_config_var('LIBPL')) + append_truthy(lib_dirs, sysconfig.get_config_var('srcdir')) + append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR")) + + # LIBPL seems to be the right config_var to use. It is the one + # used in python-config when shared library is not enabled: + # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57 + # + # But we try other places just in case. + + if is_windows: + lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) + else: + lib_dirs.append(os.path.join( + os.path.dirname(os.path.dirname(sys.executable)), + "lib")) + + # For macOS: + append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX")) + + lib_dirs.append(sys.exec_prefix) + lib_dirs.append(os.path.join(sys.exec_prefix, "lib")) + + lib_basenames = list(candidate_names(suffix=suffix)) + + for directory in lib_dirs: + for basename in lib_basenames: + yield os.path.join(directory, basename) + + # In macOS and Windows, ctypes.util.find_library returns a full path: + for basename in lib_basenames: + yield ctypes.util.find_library(library_name(basename)) + +# Possibly useful links: +# * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist +# * https://github.com/Valloric/ycmd/issues/518 +# * https://github.com/Valloric/ycmd/pull/519 + + +def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple): + """ + Normalize shared library `path` to a real path. + If `path` is not a full path, `None` is returned. If `path` does + not exists, append `SHLIB_SUFFIX` and check if it exists. + Finally, the path is canonicalized by following the symlinks. + Parameters + ---------- + path : str ot None + A candidate path to a shared library. + """ + if not path: + return None + if not os.path.isabs(path): + return None + if os.path.exists(path): + return os.path.realpath(path) + if os.path.exists(path + suffix): + return os.path.realpath(path + suffix) + if is_apple: + return normalize_path(_remove_suffix_apple(path), + suffix=".so", is_apple=False) + return None + + +def _remove_suffix_apple(path): + """ + Strip off .so or .dylib. + >>> _remove_suffix_apple("libpython.so") + 'libpython' + >>> _remove_suffix_apple("libpython.dylib") + 'libpython' + >>> _remove_suffix_apple("libpython3.7") + 'libpython3.7' + """ + if path.endswith(".dylib"): + return path[:-len(".dylib")] + if path.endswith(".so"): + return path[:-len(".so")] + return path + + +@uniquified +def finding_libpython(): + """ + Iterate over existing libpython paths. + The first item is likely to be the best one. + Yields + ------ + path : str + Existing path to a libpython. + """ + logger.debug("is_windows = %s", is_windows) + logger.debug("is_apple = %s", is_apple) + for path in candidate_paths(): + logger.debug("Candidate: %s", path) + normalized = normalize_path(path) + if normalized: + logger.debug("Found: %s", normalized) + yield normalized + else: + logger.debug("Not found.") + + +def find_libpython(): + """ + Return a path (`str`) to libpython or `None` if not found. + Parameters + ---------- + path : str or None + Existing path to the (supposedly) correct libpython. + """ + for path in finding_libpython(): + return os.path.realpath(path) + + +def print_all(items): + for x in items: + print(x) + + +def cli_find_libpython(cli_op, verbose): + import logging + # Importing `logging` module here so that using `logging.debug` + # instead of `logger.debug` outside of this function becomes an + # error. + + if verbose: + logging.basicConfig( + format="%(levelname)s %(message)s", + level=logging.DEBUG) + + if cli_op == "list-all": + print_all(finding_libpython()) + elif cli_op == "candidate-names": + print_all(candidate_names()) + elif cli_op == "candidate-paths": + print_all(p for p in candidate_paths() if p and os.path.isabs(p)) + else: + path = find_libpython() + if path is None: + return 1 + print(path, end="") + + +def main(args=None): + import argparse + parser = argparse.ArgumentParser( + description=__doc__) + parser.add_argument( + "--verbose", "-v", action="store_true", + help="Print debugging information.") + + group = parser.add_mutually_exclusive_group() + group.add_argument( + "--list-all", + action="store_const", dest="cli_op", const="list-all", + help="Print list of all paths found.") + group.add_argument( + "--candidate-names", + action="store_const", dest="cli_op", const="candidate-names", + help="Print list of candidate names of libpython.") + group.add_argument( + "--candidate-paths", + action="store_const", dest="cli_op", const="candidate-paths", + help="Print list of candidate paths of libpython.") + + ns = parser.parse_args(args) + parser.exit(cli_find_libpython(**vars(ns))) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pywheel/shim/nmodl b/pywheel/shim/nmodl new file mode 120000 index 0000000000..cf9ff4fba4 --- /dev/null +++ b/pywheel/shim/nmodl @@ -0,0 +1 @@ +_binwrapper.py \ No newline at end of file diff --git a/setup.py b/setup.py index ad82f1c138..ba0577cad0 100644 --- a/setup.py +++ b/setup.py @@ -17,6 +17,15 @@ from skbuild import setup +# Main source of the version. Dont rename, used by Cmake +try: + v = subprocess.run(['git', 'describe', '--tags'], + stdout=subprocess.PIPE).stdout.strip().decode() + __version__ = v[:v.rfind("-")].replace('-', '.') if "-" in v else v +except Exception as e: + raise RuntimeError("Could not get version from Git repo") from e + + class lazy_dict(dict): """When the value associated to a key is a function, then returns the function call instead of the function. @@ -49,23 +58,70 @@ def run(self, *args, **kwargs): self.run_command("buildhtml") +""" +A generic wrapper to access nmodl binaries from a python installation +Please create a softlink with the binary name to be called. +""" +import os +import stat +import sys +from pkg_resources import working_set +from pywheel.shim.find_libpython import find_libpython + + +def _config_exe(exe_name): + """Sets the environment to run the real executable (returned)""" + + package_name = 'nmodl' + + assert package_name in working_set.by_key, "NMODL package not found! Verify PYTHONPATH" + NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, 'nmodl') + NMODL_PREFIX_DATA = os.path.join(NMODL_PREFIX, '.data') + if sys.platform == "darwin" : + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.dylib') + else: + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.so') + + # find libpython*.so in the system + os.environ["NMODL_PYLIB"] = find_libpython() + + return os.path.join(NMODL_PREFIX_DATA, exe_name) + install_requirements = [ "PyYAML>=3.13", "sympy>=1.3,<1.6", ] + +cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable] +if "bdist_wheel" in sys.argv: + cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE") + + +def exclude_nmodl(cmake_manifest): + return list(filter(lambda name: not name.endswith('bin/nmodl'), cmake_manifest)) + + +# For CI, we want to build separate wheel +package_name = 'NMODL' +if "NMODL_NIGHTLY_TAG" in os.environ: + package_name += os.environ['NMODL_NIGHTLY_TAG'] + + setup( - name="NMODL", - version="0.2", + name=package_name, + version=__version__, author="Blue Brain Project", author_email="bbp-ou-hpc@groupes.epfl.ch", description="NEURON Modelling Language Source-to-Source Compiler Framework", long_description="", packages=["nmodl"], + scripts=["pywheel/shim/nmodl", "pywheel/shim/find_libpython.py"], + cmake_process_manifest_hook=exclude_nmodl, include_package_data=True, cmake_minimum_required_version="3.3.0", - cmake_args=["-DPYTHON_EXECUTABLE=" + sys.executable], + cmake_args=cmake_args, cmdclass=lazy_dict( docs=Docs, doctest=get_sphinx_command, buildhtml=get_sphinx_command, ), diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index c2adeb06ac..6ae469fae9 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,8 +3,8 @@ # ============================================================================= set_source_files_properties(${PYBIND_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) -# build nmodl python module under lib/python/nmodl -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nmodl) +# build nmodl python module under lib/nmodl +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl) foreach( file @@ -23,6 +23,7 @@ set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) if(NOT LINK_AGAINST_PYTHON) add_library(pywrapper SHARED ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) + set_target_properties(pywrapper PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) else() add_library(pywrapper ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) set_property(TARGET pywrapper PROPERTY POSITION_INDEPENDENT_CODE ON) @@ -61,13 +62,18 @@ add_dependencies(_nmodl pyastgen) add_dependencies(_nmodl lexer_obj) add_dependencies(_nmodl util_obj) -target_link_libraries(_nmodl PRIVATE fmt::fmt pyembed pywrapper) +target_link_libraries(_nmodl PRIVATE fmt::fmt pyembed) + +# in case of wheel, python module shouldn't link to wrapper library +if(LINK_AGAINST_PYTHON) + target_link_libraries(_nmodl PRIVATE pywrapper) +endif() add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) add_custom_command( OUTPUT ${NMODL_PYTHON_FILES_OUT} COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl - ${PROJECT_BINARY_DIR}/lib/python/nmodl + ${PROJECT_BINARY_DIR}/lib/nmodl COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl DEPENDS ${NMODL_PYTHON_FILES_IN} $ COMMENT "-- COPYING NMODL PYTHON FILES --") @@ -76,10 +82,20 @@ add_custom_command( # Copy python binding components and examples into build directory # ============================================================================= file(GLOB NMODL_PYTHON_HELPER_FILES "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") -file(COPY ${NMODL_PYTHON_HELPER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/lib/python/nmodl/) -file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/python/nmodl/) +file(COPY ${NMODL_PYTHON_HELPER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) +file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) # ============================================================================= # Install python binding components # ============================================================================= -install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/python/nmodl/ DESTINATION nmodl) +# scikit already installs the package in /nmodl. If we add it another time things are installed +# twice with the wheel and in weird places. Let's just move the .so libs +if(NOT LINK_AGAINST_PYTHON) + install(TARGETS _nmodl DESTINATION nmodl/) + install(TARGETS pywrapper DESTINATION nmodl/.data/) + install(FILES ${CMAKE_BINARY_DIR}/bin/nmodl DESTINATION nmodl/.data/) +elseif(SKBUILD) # skbuild needs the installation dir to be in nmodl to do the correct inplace + install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl DESTINATION .) +else() + install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION lib) +endif() diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 93c8bd3a27..0583fad3c5 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -81,16 +81,14 @@ target_link_libraries(testunitparser lexer test_util config) # Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, # else use the normal add_test method # ============================================================================= -set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib/python:$ENV{PYTHONPATH}") +set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") if(NOT LINK_AGAINST_PYTHON) list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") - if(CMAKE_SYSTEM_NAME MATCHES "Linux") - list(APPEND testvisitor_env - "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/python/nmodl/libpywrapper.so") - elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") - list(APPEND testvisitor_env - "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/python/nmodl/libpywrapper.dylib") - endif() + list( + APPEND + testvisitor_env + "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/nmodl/libpywrapper${CMAKE_SHARED_LIBRARY_SUFFIX}") + endif() foreach( @@ -127,7 +125,6 @@ add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_ add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) foreach(test_name Ode Pybind) - set_tests_properties( - ${test_name} PROPERTIES ENVIRONMENT - PYTHONPATH=${PROJECT_BINARY_DIR}/lib/python:$ENV{PYTHONPATH}) + set_tests_properties(${test_name} + PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) endforeach() From 27d16be580366bda95151984ce6205f3eaf74d81 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Mon, 13 Jul 2020 14:41:23 +0200 Subject: [PATCH 278/871] Fix sorting of dict to have consistent order in Jinja rendering (BlueBrain/nmodl#361) Fixes BlueBrain/nmodl#350 NMODL Repo SHA: BlueBrain/nmodl@e8b63ba9f275c084efac8b32853bcf998cc8984a --- src/nmodl/language/templates/code_generator.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/language/templates/code_generator.cmake b/src/nmodl/language/templates/code_generator.cmake index 892e3d96ca..4e462fe48d 100644 --- a/src/nmodl/language/templates/code_generator.cmake +++ b/src/nmodl/language/templates/code_generator.cmake @@ -21,7 +21,7 @@ set(CODE_GENERATOR_YAML_FILES {% endfor %} ) -{% for dir, files in outputs.items() | sort(attribute="1") %} +{% for dir, files in outputs | dictsort %} set({{ dir | upper }}_GENERATED_SOURCES {% for file in files | sort %} ${PROJECT_BINARY_DIR}/src/{{ dir }}/{{ file }} From 3b13dc8f323565ac4437405837c0aa94201c16f7 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 13 Jul 2020 15:45:58 +0300 Subject: [PATCH 279/871] Rename variables that match with ISPC double constants namings (BlueBrain/nmodl#348) * Rename variables that match double constant naming of ISPC compiler * Moved suffix_random_string to visitor_utils to reuse it in IspcRenameVisitor * Create random suffixes for new variables if the name is already taken * Added unit test and removed small thing in GlobalToRange test * Added warning when renaming variables * Fixes issue with appending different random suffixes for one variable * Added test for VERBATIM block renaming * Fixed calling RenameVisitor from IspcRenameVisitor * Added CI test for ISPC compiler * Added simple mod file for testing IspcRenameVisitor * Removed building CoreNEURON with NMODL in travis CI * Added make test in CoreNEURON installations Co-authored-by: Pramod Kumbhar Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@9e1388a5d0c50fe7d1422583b03ef254d2f7481d --- .travis.yml | 9 +- src/nmodl/nmodl/main.cpp | 9 ++ src/nmodl/visitors/ispc_rename_visitor.hpp | 90 ++++++++++++++++++ src/nmodl/visitors/rename_visitor.cpp | 46 ++++++++- src/nmodl/visitors/rename_visitor.hpp | 48 +++++++++- src/nmodl/visitors/sympy_solver_visitor.cpp | 24 +---- src/nmodl/visitors/sympy_solver_visitor.hpp | 5 - src/nmodl/visitors/visitor_utils.cpp | 16 ++++ src/nmodl/visitors/visitor_utils.hpp | 7 ++ .../integration/mod/ispc_rename.mod | 22 +++++ test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../unit/visitor/global_to_range.cpp | 1 - .../transpiler/unit/visitor/ispc_rename.cpp | 95 +++++++++++++++++++ 13 files changed, 329 insertions(+), 44 deletions(-) create mode 100644 src/nmodl/visitors/ispc_rename_visitor.hpp create mode 100644 test/nmodl/transpiler/integration/mod/ispc_rename.mod create mode 100644 test/nmodl/transpiler/unit/visitor/ispc_rename.cpp diff --git a/.travis.yml b/.travis.yml index 4e5e42d98b..6f11199499 100644 --- a/.travis.yml +++ b/.travis.yml @@ -77,7 +77,7 @@ before_install: brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/187c0d5/Formula/bison.rb; export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; brew unlink python@2; - brew link --overwrite python; + brew link --overwrite python@3.8; export SDKROOT="$(xcrun --show-sdk-path)"; else pyenv global $PYTHON_VERSION; @@ -123,13 +123,6 @@ after_success: rm -rf doctest doctrees && touch .nojekyll; echo "" > index.html; fi - - echo "------- Build and Test CoreNEURON -------" - - cd $HOME - - git clone --recursive https://github.com/BlueBrain/CoreNeuron.git - - mkdir CoreNeuron/build && cd CoreNeuron/build - - cmake .. -DCORENRN_ENABLE_MPI=OFF -DCORENRN_ENABLE_NMODL=ON -DCORENRN_NMODL_DIR=$HOME/nmodl -Dnmodl_PYTHONPATH=$HOME/nmodl -DCORENRN_NMODL_FLAGS="sympy --analytic" - - make -j 2 - - make test #============================================================================= # Notifications diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index c6bf21e3f5..c1b1957ae7 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -28,6 +28,7 @@ #include "visitors/constant_folder_visitor.hpp" #include "visitors/global_var_visitor.hpp" #include "visitors/inline_visitor.hpp" +#include "visitors/ispc_rename_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_to_assigned_visitor.hpp" @@ -311,6 +312,14 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(*ast); } + /// Rename variables that match ISPC compiler double constants + if (ispc_backend) { + logger->info("Running ISPC variables rename visitor"); + IspcRenameVisitor(ast).visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("ispc_double_rename")); + } + /// GLOBAL to RANGE rename visitor if (nmodl_global_to_range) { // make sure to run perf visitor because code generator diff --git a/src/nmodl/visitors/ispc_rename_visitor.hpp b/src/nmodl/visitors/ispc_rename_visitor.hpp new file mode 100644 index 0000000000..4ac4973da1 --- /dev/null +++ b/src/nmodl/visitors/ispc_rename_visitor.hpp @@ -0,0 +1,90 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::IspcRenameVisitor + */ + +#include + +#include "visitors/ast_visitor.hpp" +#include "visitors/rename_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + +/** + * \class IspcRenameVisitor + * \brief Rename variable names to match with ISPC backend requirement + * + * Wrapper class of RenameVisitor to rename the + * variables of the mod file that match the representation + * of double constants by the ISPC compiler. If those + * variables are not renamed to avoid matching the + * representation of the double constants of the ISPC + * compiler, then there are compilation issues. + * Here are some examples of double constants that + * ISPC compiler parses: 3.14d, 31.4d-1, 1.d, 1.0d, + * 1d-2. A regex which matches these variables is: + * ([0-9\.]*d[\-0-9]+)|([0-9\.]+d[\-0-9]*) + * + * Example mod file: + * \code{.mod} + * NEURON { + * SUFFIX test_ispc_rename + * RANGE d1, d2, d3, var_d3, d4 + * } + * ASSIGNED { + * d1 + * d2 + * d3 + * var_d3 + * d4 + * } + * INITIAL { + * d1 = 1 + * d2 = 2 + * d3 = 3 + * var_d3 = 3 + * } + * PROCEDURE func () { + * VERBATIM + * d4 = 4; + * ENDVERBATIM + * } + * \endcode + * Variables d1, d2 and d4 match the double constant + * presentation of variables by ISPC and should be + * renamed to var_d1, var_d2 and var_d4 to avoid + * compilation errors. + * var_d3 can stay the same because it doesn't match + * the regex. + * In case var_d1 already exists as variable then d1 + * is renamed to var_d1_. + */ +class IspcRenameVisitor: public RenameVisitor { + public: + /// Default constructor + IspcRenameVisitor() = delete; + + /// Constructor that takes as parameter the AST and calls the RenameVisitor + explicit IspcRenameVisitor(std::shared_ptr ast) + : RenameVisitor(ast, "([0-9\\.]*d[\\-0-9]+)|([0-9\\.]+d[\\-0-9]*)", "var_", true, true) {} +}; + +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index ab0d985f65..46524d9530 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -9,17 +9,47 @@ #include "ast/all.hpp" #include "parser/c11_driver.hpp" +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { namespace visitor { +std::string RenameVisitor::new_name_generator(const std::string old_name) { + std::string new_name; + if (add_random_suffix) { + if (renamed_variables.find(old_name) != renamed_variables.end()) { + new_name = renamed_variables[old_name]; + } else { + const auto& vars = get_global_vars(*ast); + if (add_prefix) { + new_name = suffix_random_string(vars, new_var_name_prefix + old_name); + } else { + new_name = suffix_random_string(vars, new_var_name); + } + renamed_variables[old_name] = new_name; + } + } else { + if (add_prefix) { + new_name = new_var_name_prefix + old_name; + } else { + new_name = new_var_name; + } + } + return new_name; +} + /// rename matching variable void RenameVisitor::visit_name(ast::Name& node) { const auto& name = node.get_node_name(); - if (name == var_name) { - auto& value = node.get_value(); - value->set(new_var_name); + if (std::regex_match(name, var_name_regex)) { + std::string new_name = new_name_generator(name); + node.get_value()->set(new_name); + logger->warn("RenameVisitor :: Renaming variable {} in {} to {}", + name, + node.get_token()->position(), + new_name); } } @@ -49,8 +79,14 @@ void RenameVisitor::visit_verbatim(ast::Verbatim& node) { std::string result; for (auto& token: tokens) { - if (token == var_name) { - result += new_var_name; + if (std::regex_match(token, var_name_regex)) { + /// Check if variable is already renamed and use the same naming otherwise add the + /// new_name to the renamed_variables map + std::string new_name = new_name_generator(token); + result += new_name; + logger->warn("RenameVisitor :: Renaming variable {} in VERBATIM block to {}", + token, + new_name); } else { result += token; } diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index 65c665da4f..393d2123bf 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -12,7 +12,9 @@ * \brief \copybrief nmodl::visitor::RenameVisitor */ +#include #include +#include #include "visitors/ast_visitor.hpp" @@ -40,24 +42,60 @@ namespace visitor { */ class RenameVisitor: public AstVisitor { private: - /// variable to rename - std::string var_name; + /// ast::Ast* node + std::shared_ptr ast; + + /// regex for searching which variables to replace + std::regex var_name_regex; /// new name std::string new_var_name; - // rename verbatim blocks as well + /// variable prefix + std::string new_var_name_prefix; + + /// Map that keeps the renamed variables to keep the same random suffix when a variable is + /// renamed accross the whole file + std::unordered_map renamed_variables; + + /// add prefix to variable name + bool add_prefix = false; + + /// add random suffix + bool add_random_suffix = false; + + /// rename verbatim blocks as well bool rename_verbatim = true; public: RenameVisitor() = default; RenameVisitor(std::string old_name, std::string new_name) - : var_name(std::move(old_name)) + : var_name_regex(std::move(old_name)) , new_var_name(std::move(new_name)) {} + RenameVisitor(std::shared_ptr ast, + std::string old_name, + std::string new_var_name_or_prefix, + bool add_prefix, + bool add_random_suffix) + : ast(std::move(ast)) + , var_name_regex(std::move(old_name)) + , add_prefix(std::move(add_prefix)) + , add_random_suffix(std::move(add_random_suffix)) { + if (add_prefix) { + new_var_name_prefix = std::move(new_var_name_or_prefix); + } else { + new_var_name = std::move(new_var_name_or_prefix); + } + } + + /// Check if variable is already renamed and use the same naming otherwise add the new_name + /// to the renamed_variables map + std::string new_name_generator(const std::string old_name); + void set(std::string old_name, std::string new_name) { - var_name = std::move(old_name); + var_name_regex = std::move(old_name); new_var_name = std::move(new_name); } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 2861a9d0bf..7a5630031b 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -13,7 +13,6 @@ #include "pybind/pyembed.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "utils/string_utils.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -155,31 +154,16 @@ std::vector SympySolverVisitor::filter_string_vector( return filtered_vector; } -std::string SympySolverVisitor::suffix_random_string(const std::string& original_string) const { - std::string new_string = original_string; - std::string random_string; - auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); - // Check if there is a variable defined in the mod file as original_string and if yes - // try to use a different string for the matrices created by sympy in the form - // "original_string"_"random_string" - while (vars.find(new_string) != vars.end()) { - random_string = singleton_random_string_class->reset_random_string(original_string); - new_string = original_string; - new_string += "_" + random_string; - } - return new_string; -} - void SympySolverVisitor::construct_eigen_solver_block( const std::vector& pre_solve_statements, const std::vector& solutions, bool linear) { // Provide random string to append to X, J, Jm and F matrices that // are produced by sympy - std::string unique_X = suffix_random_string("X"); - std::string unique_J = suffix_random_string("J"); - std::string unique_Jm = suffix_random_string("Jm"); - std::string unique_F = suffix_random_string("F"); + std::string unique_X = suffix_random_string(vars, "X"); + std::string unique_J = suffix_random_string(vars, "J"); + std::string unique_Jm = suffix_random_string(vars, "Jm"); + std::string unique_F = suffix_random_string(vars, "F"); // filter solutions for matrices named "X", "J", "Jm" and "F" and change them to // unique_X, unique_J, unique_Jm and unique_F respectively diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 5ec466ac43..1ce1455567 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -110,11 +110,6 @@ class SympySolverVisitor: public AstVisitor { const std::string& original_string, const std::string& substitution_string) const; - /// Return a std::string in the form "original_string"_"random_string", where - /// random_string is a string defined in the nmodl::utils::SingletonRandomString - /// for the original_string - std::string suffix_random_string(const std::string& original_string) const; - /// global variables std::set global_vars; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 0fecda7d12..8108b2d42a 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -13,6 +13,7 @@ #include "ast/all.hpp" #include "parser/nmodl_driver.hpp" +#include "utils/string_utils.hpp" #include "visitors/json_visitor.hpp" #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -24,6 +25,21 @@ namespace visitor { using namespace ast; using symtab::syminfo::NmodlType; +std::string suffix_random_string(const std::set& vars, + const std::string& original_string) { + std::string new_string = original_string; + std::string random_string; + auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); + // Check if there is a variable defined in the mod file as original_string and if yes + // try to use a different string in the form "original_string"_"random_string" + while (vars.find(new_string) != vars.end()) { + random_string = singleton_random_string_class->reset_random_string(original_string); + new_string = original_string; + new_string += "_" + random_string; + } + return new_string; +} + std::string get_new_name(const std::string& name, const std::string& suffix, std::map& variables) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 82e71f12b6..920e62a19a 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -21,6 +21,13 @@ namespace nmodl { namespace visitor { +/// Return a std::string in the form "original_string"_"random_string", where +/// random_string is a string defined in the nmodl::utils::SingletonRandomString +/// for the original_string. Vars is a const ref to std::set which +/// holds the names that need to be checked for uniqueness +std::string suffix_random_string(const std::set& vars, + const std::string& original_string); + /// Return new name variable by appending `_suffix_COUNT` where `COUNT` is /// number of times the given variable is already used. std::string get_new_name(const std::string& name, diff --git a/test/nmodl/transpiler/integration/mod/ispc_rename.mod b/test/nmodl/transpiler/integration/mod/ispc_rename.mod new file mode 100644 index 0000000000..4abf5f2d6e --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/ispc_rename.mod @@ -0,0 +1,22 @@ +NEURON { + SUFFIX test_ispc_rename + RANGE d1, d2, d3, var_d3, d4 +} +ASSIGNED { + d1 + d2 + d3 + var_d3 + d4 +} +INITIAL { + d1 = 1 + d2 = 2 + d3 = 3 + var_d3 = 3 +} +PROCEDURE func () { +VERBATIM + d4 = 4; +ENDVERBATIM +} diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 0583fad3c5..e34e0b598a 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -28,6 +28,7 @@ add_executable( visitor/defuse_analyze.cpp visitor/global_to_range.cpp visitor/inline.cpp + visitor/ispc_rename.cpp visitor/json.cpp visitor/kinetic_block.cpp visitor/localize.cpp diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 169490667b..014e48a765 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -29,7 +29,6 @@ using symtab::syminfo::NmodlType; //============================================================================= std::shared_ptr run_global_to_var_visitor(const std::string& text) { - std::map rval; NmodlDriver driver; auto ast = driver.parse_string(text); diff --git a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp new file mode 100644 index 0000000000..33e0e159d3 --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp @@ -0,0 +1,95 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "catch/catch.hpp" + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/nmodl_constructs.hpp" +#include "visitors/ispc_rename_visitor.hpp" +#include "visitors/lookup_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/verbatim_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::parser::NmodlDriver; +using symtab::syminfo::NmodlType; + +//============================================================================= +// IspcRename visitor tests +//============================================================================= + +std::shared_ptr run_ispc_rename_visitor(const std::string& text) { + NmodlDriver driver; + auto ast = driver.parse_string(text); + + IspcRenameVisitor(ast).visit_program(*ast); + SymtabVisitor().visit_program(*ast); + return ast; +} + +std::vector run_verbatim_visitor(const std::shared_ptr& ast) { + NmodlDriver driver; + + VerbatimVisitor v; + v.visit_program(*ast); + + return v.verbatim_blocks(); +} + +SCENARIO("Rename variables that ISPC parses as double constants", "[visitor][ispcrename]") { + GIVEN("mod file with variables with names that resemble for ISPC double constants") { + std::string input_nmodl = R"( + NEURON { + SUFFIX test_ispc_rename + RANGE d1, d2, var_d3, d4 + } + ASSIGNED { + d1 + d2 + var_d3 + d4 + } + INITIAL { + d1 = 1 + d2 = 2 + var_d3 = 3 + } + PROCEDURE func () { + VERBATIM d4 = 4; ENDVERBATIM + } + )"; + auto ast = run_ispc_rename_visitor(input_nmodl); + auto verbatim_blocks = run_verbatim_visitor(ast); + auto symtab = ast->get_symbol_table(); + THEN( + "Variables that match the constant double presentation in ISPC are renamed to " + "var_") { + /// check if var_d1 and var_d2 exist and replaced d1 and d2 + auto var_d1 = symtab->lookup("var_d1"); + REQUIRE(var_d1 != nullptr); + auto d1 = symtab->lookup("d1"); + REQUIRE(d1 == nullptr); + auto var_d2 = symtab->lookup("var_d2"); + REQUIRE(var_d2 != nullptr); + auto d2 = symtab->lookup("d2"); + REQUIRE(d2 == nullptr); + /// Check if VERBATIM block variable is renamed + REQUIRE(verbatim_blocks.size() == 1); + REQUIRE(verbatim_blocks.front() == " var_d4 = 4; "); + } + THEN("Variables that don't match the constant double presentation in ISPC stay the same") { + /// check if var_d3 exists + auto var_d3 = symtab->lookup("var_d3"); + REQUIRE(var_d3 != nullptr); + } + } +} From 06ce2c258f363ffe4c31f5d76a50bfd45d313a4f Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 20 Jul 2020 21:53:00 +0200 Subject: [PATCH 280/871] Fix: notebooks without latex formulae (issue BlueBrain/nmodl#365) (BlueBrain/nmodl#366) * wrong latex package installed texlive-base -> texlive-latex-recommended + texlive-latex-extra * Fix indentation issues with rendering Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@2969adbf3302a58520e6f55dafcb1a4ccdf2df0a --- .travis.yml | 3 +- cmake/nmodl/CMakeLists.txt | 1 - .../notebooks/nmodl-kinetic-schemes.ipynb | 6 +- .../notebooks/nmodl-linear-solver.ipynb | 2 +- .../notebooks/nmodl-nonlinear-solver.ipynb | 2 +- .../notebooks/nmodl-odes-overview.ipynb | 23 +- .../notebooks/nmodl-python-tutorial.ipynb | 442 ++---------------- .../notebooks/nmodl-sympy-conductance.ipynb | 2 +- .../notebooks/nmodl-sympy-solver-cnexp.ipynb | 12 +- .../nmodl-sympy-solver-derivimplicit.ipynb | 6 +- .../notebooks/nmodl-sympy-solver-sparse.ipynb | 6 +- 11 files changed, 90 insertions(+), 415 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6f11199499..8ead1f765b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,7 +55,8 @@ addons: - pandoc - python3-dev - python3-pip - - texlive-base + - texlive-latex-recommended + - texlive-latex-extra # for Mac builds, we use Homebrew homebrew: packages: diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index efe1b6b14d..d21560fd83 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -72,7 +72,6 @@ add_custom_target( "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" && clean_ipynb - --keep-output "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") # ============================================================================= diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb index e5e27d51c9..67cedb55a8 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -11,6 +11,7 @@ "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", "\n", "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "\n", "***" ] }, @@ -37,6 +38,7 @@ "\n", "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", "$$r_i = k_i \\prod_j Y_j^{\\nu_{ij}^R}$$\n", + "\n", "***" ] }, @@ -95,6 +97,7 @@ "```\n", "~ x + 2y + ... <-> (a, 0)\n", "```\n", + "\n", "***" ] }, @@ -109,6 +112,7 @@ "The `KineticBlockVisitor` substitutes the current expression for these fluxes for these variables within the `KINETIC` block.\n", "\n", "If these variables are referenced before a reaction statement then they are assumed to be zero.\n", + "\n", "***\n" ] }, @@ -450,7 +454,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb index 85550df2c5..6a8d2ba377 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb @@ -39,7 +39,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb index aad7225f2e..a650095b6f 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-nonlinear-solver.ipynb @@ -63,7 +63,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb index a76fc3f861..14ba6d4a99 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-odes-overview.ipynb @@ -10,7 +10,9 @@ " - the different ways a user can specify the equations that define the system they want to simulate in the MOD file\n", " - how these equations can be related to each other\n", " - how these equations are solved in NMODL\n", + "\n", "***\n", + "\n", "The user can specify information about the system in a variety of ways:\n", " - A high level way to describe a system is to specify a Mass Action Kinetic scheme of reaction equations in a `KINETIC` block\n", " - Alternatively a system of ODEs can be specified in a `DERIVATIVE` block (any kinetic scheme can also be written as a system of ODEs)\n", @@ -21,13 +23,18 @@ " - `DERIVATIVE` blocks of ODEs are translated to `(NON)LINEAR` blocks of algebraic equations using a numerical integration scheme\n", "\n", "After these transformations we are left with only `LINEAR`/`NONLINEAR` blocks that are then solved numerically (by Gaussian Elimination, LU factorization or Newton iteration)\n", - " ***\n", - "`KINETIC` block\n", + "\n", + "***\n", + "\n", + "#### `KINETIC` block\n", + "\n", " - Mass Action kinetics: a set of reaction equations with associated reaction rates\n", " - converted to a `DERIVATIVE` blocking containing an equivalent system of ODEs using the law of Mass Action\n", " - see the [nmodl-kinetic-schemes](nmodl-kinetic-schemes.ipynb) notebook for more details\n", + "\n", "***\n", - "`DERIVATIVE` block\n", + "\n", + "#### `DERIVATIVE` block\n", " - system of ODEs & associated solve method: `cnexp`, `sparse`, `derivimplicit` or `euler`\n", " - `cnexp`\n", " - applicable if ODEs are linear & independent\n", @@ -53,16 +60,20 @@ " - numerically unstable\n", " - $\\mathcal{O}(\\Delta t)$ integration error\n", " - not recommended due to instability of scheme\n", + "\n", "***\n", - "`LINEAR` block\n", + "\n", + "#### `LINEAR` block\n", " - system of linear algebraic equations\n", " - for small systems ($N<=3$)\n", " - solve at compile time by Gaussian elimination\n", " - for larger systems\n", " - solve at run-time by LU factorization with partial pivoting\n", " - see the [nmodl-linear-solver](nmodl-linear-solver.ipynb) notebook for more details \n", + "\n", "***\n", - "`NONLINEAR` block\n", + "\n", + "#### `NONLINEAR` block\n", " - system of non-linear algebraic equations\n", " - solve by Newton iteration\n", " - construct $F(X)$, with Jacobian $J(X)=\\frac{\\partial F_i}{\\partial X_j}$\n", @@ -96,7 +107,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index a1d3bd2347..1ae6802f0c 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -4,362 +4,42 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# NMODL Python Interface Tutorial\n", - "\n", - "## Visualization Library Setup" + "# NMODL Python Interface Tutorial\n" ] }, { - "cell_type": "code", - "execution_count": 1, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import json\n", + "## Introduction\n", "\n", - "from IPython.display import HTML, Javascript, display" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "require.config({\n", - " paths: {\n", - " d3: \"https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min\"\n", - " }\n", - "});\n" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "%%javascript\n", - "require.config({\n", - " paths: {\n", - " d3: \"https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min\"\n", - " }\n", - "});" + "NMODL is a code generation framework for NEURON Modeling Language. It is primarily designed to support optimised code generation backends for modern architectures including CPUs and GPUs. It provides high level Python interface that can be used for model introspection as well as performing various analysis on underlying model.\n", + "\n", + "This tutorial provides introduction to Python API with examples." ] }, { - "cell_type": "code", - "execution_count": 3, + "cell_type": "markdown", "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "define('draw_tree', ['d3'], function(d3) {\n", - "\n", - " /// draw d3 tree to given div container\n", - " function draw_ast(container, jsontree) {\n", - "\n", - " // container margin\n", - " var margin = {\n", - " top: 20,\n", - " right: 20,\n", - " bottom: 20,\n", - " left: 20\n", - " };\n", - "\n", - " /// diameter equal to container width (e.g. notebook cell)\n", - " var diameter = d3.select(container).node().getBoundingClientRect().width;\n", - "\n", - " /// width / ehight for the box\n", - " var width = diameter;\n", - " var height = diameter;\n", - "\n", - " /// animation duration\n", - " var duration = 200;\n", - "\n", - " /// unique ids for nodes\n", - " var id = 0;\n", - "\n", - " /// define tree layout\n", - " // see: https://d3-wiki.readthedocs.io/zh_CN/master/Tree-Layout/\n", - " var tree = d3.layout.tree()\n", - " .size([360, diameter / 2 - 80])\n", - " .separation(function(a, b) {\n", - " return (a.parent == b.parent ? 1 : 10) / a.depth;\n", - " });\n", - " var diagonal = d3.svg.diagonal.radial()\n", - " .projection(function(d) {\n", - " return [d.y, d.x / 180 * Math.PI];\n", - " });\n", - "\n", - " /// create svg element inside container\n", - " var tree_svg = d3.select(container).append(\"svg\")\n", - " .attr(\"width\", width)\n", - " .attr(\"height\", height)\n", - " .append(\"g\")\n", - " .attr(\"transform\", \"translate(\" + diameter / 2 + \",\" + diameter / 2 + \")\");\n", - "\n", - " /// start drawing tree\n", - " jsontree.x0 = width / 2;\n", - " jsontree.y0 = height / 2;\n", - " update(jsontree, tree_svg, jsontree);\n", - "\n", - " /// toggle children state on click\n", - " function click(d, destnode, jsonroot) {\n", - " if (d.children) {\n", - " d._children = d.children;\n", - " d.children = null;\n", - " } else {\n", - " d.children = d._children;\n", - " d._children = null;\n", - " }\n", - " update(d, destnode, jsonroot);\n", - " }\n", - "\n", - " /// collapse nodes on click\n", - " function collapse(d) {\n", - " if (d.children) {\n", - " d._children = d.children;\n", - " d._children.forEach(collapse);\n", - " d.children = null;\n", - " }\n", - " }\n", - "\n", - " function update(source, destnode, jsonroot) {\n", - " /// compute the new tree layout\n", - " var nodes = tree.nodes(jsonroot);\n", - " var links = tree.links(nodes);\n", - "\n", - " /// normalize for fixed-depth\n", - " nodes.forEach(function(d) {\n", - " d.y = d.depth * 80;\n", - " });\n", - "\n", - " /// update the nodes\n", - " var node = destnode.selectAll(\"g.node\")\n", - " .data(nodes, function(d) {\n", - " return d.id || (d.id = ++id);\n", - " });\n", - "\n", - " /// enter any new nodes at the parent's previous position\n", - " var nodeEnter = node.enter().append(\"g\")\n", - " .attr(\"class\", \"node\")\n", - " .on(\"click\", function(d) {\n", - " click(d, destnode, jsonroot);\n", - " });\n", - "\n", - " nodeEnter.append(\"circle\")\n", - " .attr(\"r\", 1e-6)\n", - " .style(\"fill\", function(d) {\n", - " return d._children ? \"lightsteelblue\" : \"#fff\";\n", - " });\n", - "\n", - " nodeEnter.append(\"text\")\n", - " .attr(\"x\", 10)\n", - " .attr(\"dy\", \".55em\")\n", - " .attr(\"text-anchor\", \"start\")\n", - " .text(function(d) {\n", - " return d.name;\n", - " })\n", - " .style(\"fill-opacity\", 1e-6);\n", - "\n", - " /// transition nodes to their new position\n", - " var nodeUpdate = node.transition()\n", - " .duration(duration)\n", - " .attr(\"transform\", function(d) {\n", - " return \"rotate(\" + (d.x - 90) + \")translate(\" + d.y + \")\";\n", - " })\n", - "\n", - " nodeUpdate.select(\"circle\")\n", - " .attr(\"r\", 4.5)\n", - " .style(\"fill\", function(d) {\n", - " return d._children ? \"lightsteelblue\" : \"#fff\";\n", - " });\n", - "\n", - " nodeUpdate.select(\"text\")\n", - " .style(\"fill-opacity\", 1)\n", - " .attr(\"transform\", function(d) {\n", - " return d.x < 180 ? \"translate(0)\" : \"rotate(180)translate(-\" + (d.name.length + 50) + \")\";\n", - " });\n", - "\n", - " /// appropriate transform\n", - " var nodeExit = node.exit().transition()\n", - " .duration(duration)\n", - " .remove();\n", - "\n", - " nodeExit.select(\"circle\")\n", - " .attr(\"r\", 1e-6);\n", - "\n", - " nodeExit.select(\"text\")\n", - " .style(\"fill-opacity\", 1e-6);\n", - "\n", - " /// update the links\n", - " var link = destnode.selectAll(\"path.link\")\n", - " .data(links, function(d) {\n", - " return d.target.id;\n", - " });\n", - "\n", - " /// enter any new links at the parent's previous position\n", - " link.enter().insert(\"path\", \"g\")\n", - " .attr(\"class\", \"link\")\n", - " .attr(\"d\", function(d) {\n", - " var o = {\n", - " x: source.x0,\n", - " y: source.y0\n", - " };\n", - " return diagonal({\n", - " source: o,\n", - " target: o\n", - " });\n", - " });\n", - "\n", - " /// transition links to their new position\n", - " link.transition()\n", - " .duration(duration)\n", - " .attr(\"d\", diagonal);\n", - "\n", - " /// transition exiting nodes to the parent's new position\n", - " link.exit().transition()\n", - " .duration(duration)\n", - " .attr(\"d\", function(d) {\n", - " var o = {\n", - " x: source.x,\n", - " y: source.y\n", - " };\n", - " return diagonal({\n", - " source: o,\n", - " target: o\n", - " });\n", - " })\n", - " .remove();\n", - "\n", - " /// stash the old positions for transition\n", - " nodes.forEach(function(d) {\n", - " d.x0 = d.x;\n", - " d.y0 = d.y;\n", - " });\n", - " }\n", - "\n", - " }\n", - "\n", - " return draw_ast;\n", - "});" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], "source": [ - "Javascript(filename=\"tree.js\")" + "To get started, let's install nmodl via Python wheel as:" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "\n", - "\n" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ - "HTML(\n", - " \"\"\"\n", - "\n", - "\"\"\"\n", - ")" + "%%capture\n", + "! pip install nmodl" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Introduction\n", - "\n", - "NMODL is a code generation framework for NEURON Modeling Language. It is primarily designed to support optimised code generation backends for morphologically detailed neuron simulators. It provides high level Python interface that can be used for model introspection as well as performing various analysis on underlying model.\n", - "\n", - "This tutorial provides introduction to python API with examples." + "> Note : Python wheel is only available for Linux and Mac OS. Windows version will be available in the future. Today you can use Windows Subsystem for Linux. " ] }, { @@ -373,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -384,12 +64,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "If you see any issues, check the [installation section](#installation). Lets take an example of a channelCaDynamics :" + "If you see any issues, check the [installation section](#installation). Lets take an example of a channel `CaDynamics` :" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -443,12 +123,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Now we can parse any valid NMODL constructs using parsing interface. First, we have to create nmodl parser object using `nmodl::NmodlDriver` and then we can use `parse_string` method :" + "Now we can parse any valid NMODL constructs using NMODL's parsing interface. First, we have to create nmodl parser object using `nmodl::NmodlDriver` and then we can use `parse_string` method :" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -460,7 +140,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `parse_string` method will throw an exception with parsing error if input is invalid. Otherwise it returns [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) object." + "The `parse_string` method will throw an exception if input is invalid. Otherwise it returns [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) object." ] }, { @@ -472,53 +152,19 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "{\"Program\":[{\"NeuronBlock\":[{\"StatementBlock\":[{\"Suffix\":[{\"Name\":[{\"String\":[{\"name\":\"SUFFIX\"}]}]},\n" + "{\"Program\":[{\"NeuronBlock\":[{\"StatementBlock\":[{\"Suffix\":[{\"Name\":[{\"String\":[{\"name\":\"SUFFIX\"}]}]},{\"Name\":[{\"String\":[{\"name\":\"CaDynamics\"}]}]}]},{\"Useion\":[{\"Name\":[{\"String\":[{\"name\":\"ca\"}]}]},{\"R\n" ] } ], "source": [ - "print(\"%.100s\" % modast) # only first 100 characters\n", - "import json\n", - "\n", - "json_data = json.loads(nmodl.to_json(modast, True))\n", - "json_data_expand = json.loads(nmodl.to_json(modast, True, True))" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "application/javascript": [ - "(function(element){\n", - " require(['draw_tree'], function(draw) { draw(element.get(0), {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"SUFFIX\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"CaDynamics\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"Suffix\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ca\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"ReadIonVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"WriteIonVar\"}], \"name\": \"Useion\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"RangeVar\"}], \"name\": \"Range\"}], \"name\": \"StatementBlock\"}], \"name\": \"NeuronBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"mV\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millivolt\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mA\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"milliamp\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"faraday\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"coulombs\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"FactorDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"molar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"1/liter\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"millimolar\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}, {\"children\": [{\"children\": [{\"name\": \"micron\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"UnitDef\"}], \"name\": \"UnitBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.05\"}], \"name\": \"Double\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"80\"}], \"name\": \"Integer\"}, {\"children\": [{\"children\": [{\"name\": \"ms\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.1\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"um\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"name\": \"0.0001\"}], \"name\": \"Double\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"ParamAssign\"}], \"name\": \"ParamBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mA/cm2\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"AssignedDefinition\"}], \"name\": \"AssignedBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"InitialBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"mM\"}], \"name\": \"String\"}], \"name\": \"Unit\"}], \"name\": \"AssignedDefinition\"}], \"name\": \"StateBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"name\": \"cnexp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"SolveBlock\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"BreakpointBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"states\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}, {\"children\": [{\"name\": \"1\"}], \"name\": \"Integer\"}], \"name\": \"PrimeName\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"-\"}], \"name\": \"UnaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"10000\"}], \"name\": \"Double\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"UnaryExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"ica\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"2\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"FARADAY\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"*\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"depth\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"cai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"-\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"minCai\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"ParenExpression\"}], \"name\": \"WrappedExpression\"}, {\"children\": [{\"name\": \"/\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"decay\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"DiffEqExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"DerivativeBlock\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"temp\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"LocalVar\"}], \"name\": \"LocalListStatement\"}, {\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"children\": [{\"name\": \"foo\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}, {\"children\": [{\"name\": \"=\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"1\"}], \"name\": \"Double\"}, {\"children\": [{\"name\": \"+\"}], \"name\": \"BinaryOperator\"}, {\"children\": [{\"children\": [{\"children\": [{\"name\": \"gamma\"}], \"name\": \"String\"}], \"name\": \"Name\"}], \"name\": \"VarName\"}], \"name\": \"BinaryExpression\"}], \"name\": \"WrappedExpression\"}], \"name\": \"BinaryExpression\"}], \"name\": \"ExpressionStatement\"}], \"name\": \"StatementBlock\"}], \"name\": \"FunctionBlock\"}], \"name\": \"Program\"}) });\n", - " })(element);" - ], - "text/plain": [ - "" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "Javascript(\n", - " \"\"\"(function(element){\n", - " require(['draw_tree'], function(draw) { draw(element.get(0), %s) });\n", - " })(element);\"\"\"\n", - " % json.dumps(json_data_expand)\n", - ")" + "print(\"%.200s\" % modast) # only first 200 characters" ] }, { @@ -541,11 +187,11 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ - "from nmodl.dsl import ast, visitor\n", + "from nmodl import ast, visitor\n", "\n", "lookup_visitor = visitor.AstLookupVisitor()" ] @@ -559,7 +205,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -582,14 +228,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We have used `to_nmodl` helper function to print AST object in NMODL format. Note that the output of `to_nmodl` should be same as input except for comments (?, :, COMMENT). There are very few edge cases where the NMODL output could slightly differ and this is considered as bug. This is being addressed by testing entire [ModelDB](https://senselab.med.yale.edu/modeldb/) database.\n", + "We have used `to_nmodl` helper function to convert AST object back to NMODL language. Note that the output of `to_nmodl` should be same as input except for comments (?, :, COMMENT). There are very few edge cases where the NMODL output could slightly differ and this is considered as bug. This is being addressed by testing entire [ModelDB](https://senselab.med.yale.edu/modeldb/) database.\n", "\n", - "Using AstLookupVisitor we can introspect NMODL constructs at any level of details. Below are some examples to find out different constructs in the example mod file:" + "Using AstLookupVisitor we can introspect NMODL constructs at any level of details. Below are some examples to find out different constructs in the example mod file. All different kind of NMODL constructs are [listeed here](https://bluebrain.github.io/nmodl/html/doxygen/group__ast__type.html)." ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -650,7 +296,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 9, "metadata": {}, "outputs": [ { @@ -668,7 +314,7 @@ "# expression statements include assignments\n", "new_lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.EXPRESSION_STATEMENT)\n", "\n", - "# using accept method of node\n", + "# using accept method of node we can visit it\n", "function.accept(new_lookup_visitor)\n", "statements = new_lookup_visitor.get_nodes()\n", "\n", @@ -694,11 +340,11 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ - "from nmodl.dsl import symtab\n", + "from nmodl import symtab\n", "\n", "symv = symtab.SymtabVisitor()" ] @@ -712,7 +358,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 11, "metadata": {}, "outputs": [ { @@ -770,7 +416,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -801,7 +447,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -830,7 +476,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 14, "metadata": {}, "outputs": [ { @@ -861,7 +507,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -878,7 +524,7 @@ } ], "source": [ - "from nmodl.dsl import ast, visitor\n", + "from nmodl import ast, visitor\n", "\n", "\n", "class DoubleVisitor(visitor.AstVisitor):\n", @@ -901,7 +547,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -958,7 +604,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 17, "metadata": {}, "outputs": [], "source": [ @@ -979,7 +625,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 18, "metadata": {}, "outputs": [], "source": [ @@ -1063,7 +709,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 19, "metadata": {}, "outputs": [ { @@ -1096,7 +742,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 20, "metadata": {}, "outputs": [ { @@ -1166,7 +812,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb index 6c3971c36f..5fc867a29b 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-conductance.ipynb @@ -456,7 +456,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb index 50e03d8eab..0fe84e88f4 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb @@ -11,7 +11,9 @@ "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", "\n", "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "\n", "***\n", + "\n", "#### Implementation\n", "The `SympySolverVisitor` for solver method `cnexp` does the following:\n", "\n", @@ -26,7 +28,9 @@ " * where $g$ is some function that depends on the value of $m$ at time t, the timestep $dt$, and the other variables ($\\dots$).\n", " * Replace ODE with analytic solution as C code using [sympy.printing.ccode](https://docs.sympy.org/latest/_modules/sympy/printing/ccode.html)\n", " * If we fail to find a solution then do nothing - so currently NMODL reverts to using the legacy CNEXP solver routine (same as mod2c or nocmodl)\n", + "\n", "***\n", + "\n", "#### Pade approximant\n", "There is an option `use_pade_approx` which if enabled does the following extra step:\n", "\n", @@ -38,7 +42,9 @@ " * Return this approximate solution (correct to second order in $dt$) as C code\n", "\n", "(Replacing the exponential with a Pade aproximant here was suggested in sec 5.2 of (https://www.eccomas2016.org/proceedings/pdf/7366.pdf) - since the overall numerical integration scheme in NEURON is only correct to first or second order in $dt$, it is valid to expand the analytic solution here to the same order and so avoid evaluating the exponential function)\n", + "\n", "***\n", + "\n", "#### Implementation Tests\n", "The unit tests may be helpful to understand what these functions are doing\n", " - `SympySolverVisitor` tests are located in [test/visitor/sympy_solver.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/sympy_solver.cpp), and tests involving `cnexp` have the tag \"`[cnexp]`\"" @@ -247,8 +253,8 @@ "name": "stdout", "output_type": "stream", "text": [ - "exact solution:\t m' = m^3\n", - "pade approx:\t m' = m^3\n" + "exact solution:\t m = sqrt(-pow(m, 2)/(2*dt*pow(m, 2)-1))\n", + "pade approx:\t m = pow(m, 2)*(dt*pow(m, 2)-2)*pow(pow(m, 2), -0.5)/(3*dt*pow(m, 2)-2)\n" ] } ], @@ -289,7 +295,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb index da1aa97846..a703a66098 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-derivimplicit.ipynb @@ -11,13 +11,17 @@ "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", "\n", "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "\n", "***\n", + "\n", "#### Implementation\n", "The `SympySolverVisitor` for solver method `derivimplicit` does the following:\n", "\n", "* Construct the implicit Euler equations to convert the sysytem of ODEs to a `NONLINEAR` block\n", "* This `NONLINEAR` block is then solved as described in [nmodl-nonlinear-solver](nmodl-nonlinear-solver.ipynb)\n", + "\n", "***\n", + "\n", "#### Implementation Tests\n", "The unit tests may be helpful to understand what these functions are doing\n", " - `SympySolverVisitor` tests are located in [test/visitor/sympy_solver.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/sympy_solver.cpp), and tests involving the `derivimplicit` solver method have the tag \"`[derivimplicit]`\"" @@ -235,7 +239,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb index 45f09003a1..12e3883963 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-sparse.ipynb @@ -11,13 +11,17 @@ "For a higher level overview of the approach to solving ODEs in NMODL, please see the [nmodl-odes-overview](nmodl-odes-overview.ipynb) notebook. \n", "\n", "For a more general tutorial on using the NMODL python interface, please see the [tutorial notebook](nmodl-python-tutorial.ipynb).\n", + "\n", "***\n", + "\n", "#### Implementation\n", "The `SympySolverVisitor` for solver method `sparse` does the following:\n", "\n", "* Construct the implicit Euler equations to convert the sysytem of ODEs to a `LINEAR` block\n", "* This `LINEAR` block is then solved as described in [nmodl-linear-solver](nmodl-linear-solver.ipynb)\n", + "\n", "***\n", + "\n", "#### Implementation Tests\n", "The unit tests may be helpful to understand what these functions are doing\n", " - `SympySolverVisitor` tests are located in [test/visitor/sympy_solver.cpp](https://github.com/BlueBrain/nmodl/blob/master/test/visitor/sympy_solver.cpp), and tests using the `sparse` solver method have the tag \"`[sparse]`\"" @@ -235,7 +239,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.7.7" } }, "nbformat": 4, From abc2198294b864452e334e13e47bba6dfbf34ed8 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 28 Jul 2020 10:40:40 +0300 Subject: [PATCH 281/871] Fix printing token in ISPC rename when there is none (BlueBrain/nmodl#364) * Fix printing token in ispc rename when there is none * Enabled selecting if random strings will include numbers or not - Fixes issue with ISPC compiler matching variables names with numbes as constants * Renamed UseRandomNumbers to UseNumbersInString NMODL Repo SHA: BlueBrain/nmodl@3c79b9ca808cf4e03e4d32500f8479e3899286cd --- src/nmodl/codegen/codegen_c_visitor.cpp | 5 ++++- src/nmodl/utils/common_utils.cpp | 10 ++++++++-- src/nmodl/utils/common_utils.hpp | 16 ++++++++++++---- src/nmodl/visitors/rename_visitor.cpp | 17 +++++++++++------ src/nmodl/visitors/sympy_solver_visitor.cpp | 2 ++ src/nmodl/visitors/visitor_utils.cpp | 12 ++++++++++-- src/nmodl/visitors/visitor_utils.hpp | 15 ++++++++++++++- 7 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index f048109841..71f2a8936d 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -37,6 +37,8 @@ using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; using SymbolType = std::shared_ptr; +using nmodl::utils::UseNumbersInString; + /****************************************************************************************/ /* Overloaded visitor routines */ /****************************************************************************************/ @@ -1636,7 +1638,8 @@ void CodegenCVisitor::print_function(ast::FunctionBlock& node) { } std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) const { - auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); + auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance( + UseNumbersInString::WithNumbers); std::string unique_name = original_name; if (singleton_random_string_class->random_string_exists(original_name)) { unique_name = original_name; diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 6501ab7294..0f256c3cc9 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -12,6 +12,7 @@ #include #include +#include "common_utils.hpp" namespace nmodl { namespace utils { @@ -56,7 +57,7 @@ bool make_path(const std::string& path) { } } -std::string generate_random_string(const int len) { +std::string generate_random_string(const int len, UseNumbersInString use_numbers) { std::string s(len, 0); static const char alphanum[] = "0123456789" @@ -64,7 +65,12 @@ std::string generate_random_string(const int len) { "abcdefghijklmnopqrstuvwxyz"; std::random_device dev; std::mt19937 rng(dev()); - std::uniform_int_distribution dist(0, (sizeof(alphanum) - 1)); + std::uniform_int_distribution dist; + if (use_numbers) { + dist = std::uniform_int_distribution(0, (sizeof(alphanum) - 1)); + } else { + dist = std::uniform_int_distribution(10, (sizeof(alphanum) - 1)); + } for (int i = 0; i < len; ++i) { s[i] = alphanum[dist(rng)]; } diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index fc39985204..f3874c9d52 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -81,9 +81,13 @@ bool make_path(const std::string& path); /// Check if directory with given path exist bool is_dir_exist(const std::string& path); +/// Enum to wrap bool variable to select if random string +/// should have numbers or not +enum UseNumbersInString : bool { WithNumbers = true, WithoutNumbers = false }; + /// Generate random std::string of length len based on a /// uniform distribution -std::string generate_random_string(int len); +std::string generate_random_string(int len, UseNumbersInString use_numbers); /** * \class SingletonRandomString @@ -103,8 +107,9 @@ class SingletonRandomString { SingletonRandomString& operator=(SingletonRandomString const&) = delete; /// Function to instantiate the SingletonRandomString class - static std::shared_ptr instance() { + static std::shared_ptr instance(const UseNumbersInString use_numbers_) { static std::shared_ptr s{new SingletonRandomString}; + s->use_numbers = use_numbers_; return s; } @@ -135,9 +140,9 @@ class SingletonRandomString { std::string reset_random_string(const std::string& var_name) { if (random_string_exists(var_name)) { random_strings.erase(var_name); - random_strings.insert({var_name, generate_random_string(SIZE)}); + random_strings.insert({var_name, generate_random_string(SIZE, use_numbers)}); } else { - random_strings.insert({var_name, generate_random_string(SIZE)}); + random_strings.insert({var_name, generate_random_string(SIZE, use_numbers)}); } return random_strings[var_name]; } @@ -153,6 +158,9 @@ class SingletonRandomString { /// std::map that keeps the random strings assigned to variables as suffix std::map random_strings; + + /// bool to control if random string will include numbers or not + UseNumbersInString use_numbers = WithNumbers; }; /** @} */ // end of utils diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 46524d9530..dcaf026043 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -16,6 +16,8 @@ namespace nmodl { namespace visitor { +using nmodl::utils::UseNumbersInString; + std::string RenameVisitor::new_name_generator(const std::string old_name) { std::string new_name; if (add_random_suffix) { @@ -24,9 +26,12 @@ std::string RenameVisitor::new_name_generator(const std::string old_name) { } else { const auto& vars = get_global_vars(*ast); if (add_prefix) { - new_name = suffix_random_string(vars, new_var_name_prefix + old_name); + new_name = suffix_random_string(vars, + new_var_name_prefix + old_name, + UseNumbersInString::WithoutNumbers); } else { - new_name = suffix_random_string(vars, new_var_name); + new_name = + suffix_random_string(vars, new_var_name, UseNumbersInString::WithoutNumbers); } renamed_variables[old_name] = new_name; } @@ -46,10 +51,10 @@ void RenameVisitor::visit_name(ast::Name& node) { if (std::regex_match(name, var_name_regex)) { std::string new_name = new_name_generator(name); node.get_value()->set(new_name); - logger->warn("RenameVisitor :: Renaming variable {} in {} to {}", - name, - node.get_token()->position(), - new_name); + std::string token_string = node.get_token() != nullptr + ? " at " + node.get_token()->position() + : ""; + logger->warn("RenameVisitor :: Renaming variable {}{} to {}", name, token_string, new_name); } } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 7a5630031b..cf8f014776 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -24,6 +24,8 @@ namespace visitor { using symtab::syminfo::NmodlType; +using nmodl::utils::UseNumbersInString; + void SympySolverVisitor::init_block_data(ast::Node* node) { // clear any previous data expression_statements.clear(); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 8108b2d42a..cb1c0af09e 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -25,11 +25,14 @@ namespace visitor { using namespace ast; using symtab::syminfo::NmodlType; +using nmodl::utils::UseNumbersInString; + std::string suffix_random_string(const std::set& vars, - const std::string& original_string) { + const std::string& original_string, + const UseNumbersInString use_num) { std::string new_string = original_string; std::string random_string; - auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); + auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(use_num); // Check if there is a variable defined in the mod file as original_string and if yes // try to use a different string in the form "original_string"_"random_string" while (vars.find(new_string) != vars.end()) { @@ -40,6 +43,11 @@ std::string suffix_random_string(const std::set& vars, return new_string; } +std::string suffix_random_string(const std::set& vars, + const std::string& original_string) { + return suffix_random_string(vars, original_string, UseNumbersInString::WithNumbers); +} + std::string get_new_name(const std::string& name, const std::string& suffix, std::map& variables) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 920e62a19a..c01b560d90 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -17,14 +17,27 @@ #include #include +#include namespace nmodl { namespace visitor { +using nmodl::utils::UseNumbersInString; + +/// Return a std::string in the form "original_string"_"random_string", where +/// random_string is a string defined in the nmodl::utils::SingletonRandomString +/// for the original_string. Vars is a const ref to std::set which +/// holds the names that need to be checked for uniqueness. Choose if the +/// "random_string" will include numbers using "use_num" +std::string suffix_random_string(const std::set& vars, + const std::string& original_string, + const UseNumbersInString use_num); + /// Return a std::string in the form "original_string"_"random_string", where /// random_string is a string defined in the nmodl::utils::SingletonRandomString /// for the original_string. Vars is a const ref to std::set which -/// holds the names that need to be checked for uniqueness +/// holds the names that need to be checked for uniqueness. Adds numbers in the +/// "random_string" std::string suffix_random_string(const std::set& vars, const std::string& original_string); From 0186405ab05f8e0af9eef30170bb275d921e3441 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 4 Aug 2020 10:46:16 +0200 Subject: [PATCH 282/871] Reorder pywheels (BlueBrain/nmodl#381) * Reorder folders for pywheels (this also fixes issue BlueBrain/nmodl#377) During a NOT LINK_AGAINST_PYTHON build (for pywheels) the stuff inside lib/nmodl becomes root (in the build and then, through skbuild, in site-packages after the wheel is installed) and everything else (that was outside) goes inside site-packages/.data. **Notes:** - libpywrapper goes inside .data/lib - nmodl/nmodl/ext is copied with the nmodl/nmodl folder by skbuild. No easy way to not have it also in site-packages/nmodl/ext without removing it from its current location (nmodl/nmodl/ext) in the source files - nrnunits was not found runtime (issue BlueBrain/nmodl#377). In config.h there was already the logic to find it runtime and fallback to hardcoded paths in case it was not found. Shim modified accordingly. * workaround for PYTHONPATH in mac CI Co-authored-by: Pramod S Kumbhar Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@1c3be4805df9b4149b6983f6f9e2c16637ecf8aa --- cmake/nmodl/CMakeLists.txt | 9 +++- pywheel/shim/_binwrapper.py | 32 +++++++++---- pywheel/shim/find_libpython.py | 77 ++++++++++++++++++------------- setup.py | 7 --- src/nmodl/codegen/CMakeLists.txt | 3 +- src/nmodl/lexer/CMakeLists.txt | 8 ++-- src/nmodl/nmodl/CMakeLists.txt | 4 +- src/nmodl/parser/CMakeLists.txt | 6 +-- src/nmodl/pybind/CMakeLists.txt | 3 +- src/nmodl/solver/CMakeLists.txt | 2 +- src/nmodl/visitors/CMakeLists.txt | 2 +- 11 files changed, 91 insertions(+), 62 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index d21560fd83..e641871758 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -88,6 +88,13 @@ include(GitRevision) include(PythonLinkHelper) include(RpathHelper) +# ============================================================================= +# Adjust install prefix for wheel +# ============================================================================= +if(NOT LINK_AGAINST_PYTHON) + set(NMODL_INSTALL_DIR_SUFFIX "nmodl/.data/") +endif() + # ============================================================================= # Find required python packages # ============================================================================= @@ -194,4 +201,4 @@ add_subdirectory(src/solver) # ============================================================================= # Install unit database, examples and utility scripts from share # ============================================================================= -install(DIRECTORY share/ DESTINATION share) +install(DIRECTORY share/ DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share) diff --git a/pywheel/shim/_binwrapper.py b/pywheel/shim/_binwrapper.py index 114861dcc5..445cc59bbc 100755 --- a/pywheel/shim/_binwrapper.py +++ b/pywheel/shim/_binwrapper.py @@ -10,25 +10,37 @@ from pkg_resources import working_set from find_libpython import find_libpython + def _config_exe(exe_name): """Sets the environment to run the real executable (returned)""" - package_name = 'nmodl' + package_name = "nmodl" + + assert ( + package_name in working_set.by_key + ), "NMODL package not found! Verify PYTHONPATH" + + NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, "nmodl") + NMODL_HOME = os.path.join(NMODL_PREFIX, ".data") + NMODL_BIN = os.path.join(NMODL_HOME, "bin") + NMODL_LIB = os.path.join(NMODL_HOME, "lib") - assert package_name in working_set.by_key, "NMODL package not found! Verify PYTHONPATH" - NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, 'nmodl') - NMODL_PREFIX_DATA = os.path.join(NMODL_PREFIX, '.data') - if sys.platform == "darwin" : - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.dylib') + # add pywrapper path to environment + if sys.platform == "darwin": + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_LIB, "libpywrapper.dylib") else: - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.so') + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_LIB, "libpywrapper.so") - # find libpython*.so in the system + # add libpython*.so path to environment os.environ["NMODL_PYLIB"] = find_libpython() - return os.path.join(NMODL_PREFIX_DATA, exe_name) + # add nmodl home to environment (i.e. necessary for nrnunits.lib) + os.environ["NMODLHOME"] = NMODL_HOME + + return os.path.join(NMODL_BIN, exe_name) + -if __name__ == '__main__': +if __name__ == "__main__": """Set the pointed file as executable""" exe = _config_exe(os.path.basename(sys.argv[0])) st = os.stat(exe) diff --git a/pywheel/shim/find_libpython.py b/pywheel/shim/find_libpython.py index 3ff09e9cf1..abab278ae3 100644 --- a/pywheel/shim/find_libpython.py +++ b/pywheel/shim/find_libpython.py @@ -84,7 +84,8 @@ def _linked_libpython_unix(): dlinfo = Dl_info() retcode = libdl.dladdr( ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), - ctypes.pointer(dlinfo)) + ctypes.pointer(dlinfo), + ) if retcode == 0: # means error return None path = os.path.realpath(dlinfo.dli_fname.decode()) @@ -106,9 +107,9 @@ def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows): 'python37' """ if not is_windows and name.startswith("lib"): - name = name[len("lib"):] + name = name[len("lib") :] if suffix and name.endswith(suffix): - name = name[:-len(suffix)] + name = name[: -len(suffix)] return name @@ -132,9 +133,11 @@ def uniquifying(items): def uniquified(func): """ Wrap iterator returned from `func` by `uniquifying`. """ + @functools.wraps(func) def wrapper(*args, **kwds): return uniquifying(func(*args, **kwds)) + return wrapper @@ -159,10 +162,15 @@ def candidate_names(suffix=SHLIB_SUFFIX): sysdata = dict( v=sys.version_info, # VERSION is X.Y in Linux/macOS and XY in Windows: - VERSION=(sysconfig.get_config_var("VERSION") or - "{v.major}.{v.minor}".format(v=sys.version_info)), - ABIFLAGS=(sysconfig.get_config_var("ABIFLAGS") or - sysconfig.get_config_var("abiflags") or ""), + VERSION=( + sysconfig.get_config_var("VERSION") + or "{v.major}.{v.minor}".format(v=sys.version_info) + ), + ABIFLAGS=( + sysconfig.get_config_var("ABIFLAGS") + or sysconfig.get_config_var("abiflags") + or "" + ), ) for stem in [ @@ -174,7 +182,6 @@ def candidate_names(suffix=SHLIB_SUFFIX): yield dlprefix + stem + suffix - @uniquified def candidate_paths(suffix=SHLIB_SUFFIX): """ @@ -190,8 +197,8 @@ def candidate_paths(suffix=SHLIB_SUFFIX): # List candidates for directories in which libpython may exist lib_dirs = [] - append_truthy(lib_dirs, sysconfig.get_config_var('LIBPL')) - append_truthy(lib_dirs, sysconfig.get_config_var('srcdir')) + append_truthy(lib_dirs, sysconfig.get_config_var("LIBPL")) + append_truthy(lib_dirs, sysconfig.get_config_var("srcdir")) append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR")) # LIBPL seems to be the right config_var to use. It is the one @@ -203,9 +210,9 @@ def candidate_paths(suffix=SHLIB_SUFFIX): if is_windows: lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) else: - lib_dirs.append(os.path.join( - os.path.dirname(os.path.dirname(sys.executable)), - "lib")) + lib_dirs.append( + os.path.join(os.path.dirname(os.path.dirname(sys.executable)), "lib") + ) # For macOS: append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX")) @@ -223,6 +230,7 @@ def candidate_paths(suffix=SHLIB_SUFFIX): for basename in lib_basenames: yield ctypes.util.find_library(library_name(basename)) + # Possibly useful links: # * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist # * https://github.com/Valloric/ycmd/issues/518 @@ -249,8 +257,7 @@ def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple): if os.path.exists(path + suffix): return os.path.realpath(path + suffix) if is_apple: - return normalize_path(_remove_suffix_apple(path), - suffix=".so", is_apple=False) + return normalize_path(_remove_suffix_apple(path), suffix=".so", is_apple=False) return None @@ -265,9 +272,9 @@ def _remove_suffix_apple(path): 'libpython3.7' """ if path.endswith(".dylib"): - return path[:-len(".dylib")] + return path[: -len(".dylib")] if path.endswith(".so"): - return path[:-len(".so")] + return path[: -len(".so")] return path @@ -312,14 +319,13 @@ def print_all(items): def cli_find_libpython(cli_op, verbose): import logging + # Importing `logging` module here so that using `logging.debug` # instead of `logger.debug` outside of this function becomes an # error. if verbose: - logging.basicConfig( - format="%(levelname)s %(message)s", - level=logging.DEBUG) + logging.basicConfig(format="%(levelname)s %(message)s", level=logging.DEBUG) if cli_op == "list-all": print_all(finding_libpython()) @@ -336,29 +342,38 @@ def cli_find_libpython(cli_op, verbose): def main(args=None): import argparse - parser = argparse.ArgumentParser( - description=__doc__) + + parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( - "--verbose", "-v", action="store_true", - help="Print debugging information.") + "--verbose", "-v", action="store_true", help="Print debugging information." + ) group = parser.add_mutually_exclusive_group() group.add_argument( "--list-all", - action="store_const", dest="cli_op", const="list-all", - help="Print list of all paths found.") + action="store_const", + dest="cli_op", + const="list-all", + help="Print list of all paths found.", + ) group.add_argument( "--candidate-names", - action="store_const", dest="cli_op", const="candidate-names", - help="Print list of candidate names of libpython.") + action="store_const", + dest="cli_op", + const="candidate-names", + help="Print list of candidate names of libpython.", + ) group.add_argument( "--candidate-paths", - action="store_const", dest="cli_op", const="candidate-paths", - help="Print list of candidate paths of libpython.") + action="store_const", + dest="cli_op", + const="candidate-paths", + help="Print list of candidate paths of libpython.", + ) ns = parser.parse_args(args) parser.exit(cli_find_libpython(**vars(ns))) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/setup.py b/setup.py index ba0577cad0..fde44cb1ef 100644 --- a/setup.py +++ b/setup.py @@ -98,17 +98,11 @@ def _config_exe(exe_name): if "bdist_wheel" in sys.argv: cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE") - -def exclude_nmodl(cmake_manifest): - return list(filter(lambda name: not name.endswith('bin/nmodl'), cmake_manifest)) - - # For CI, we want to build separate wheel package_name = 'NMODL' if "NMODL_NIGHTLY_TAG" in os.environ: package_name += os.environ['NMODL_NIGHTLY_TAG'] - setup( name=package_name, version=__version__, @@ -118,7 +112,6 @@ def exclude_nmodl(cmake_manifest): long_description="", packages=["nmodl"], scripts=["pywheel/shim/nmodl", "pywheel/shim/find_libpython.py"], - cmake_process_manifest_hook=exclude_nmodl, include_package_data=True, cmake_minimum_required_version="3.3.0", cmake_args=cmake_args, diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 8fc222fdac..c7124a75d3 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -34,4 +34,5 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc # ============================================================================= # Install include files # ============================================================================= -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc DESTINATION include/nmodl) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include/nmodl) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 3466f10973..34168e57ac 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -189,10 +189,12 @@ target_link_libraries(lexer fmt::fmt) target_link_libraries(nmodl_lexer lexer util) target_link_libraries(c_lexer lexer util) target_link_libraries(units_lexer lexer util) + # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_lexer DESTINATION bin/lexer) -install(TARGETS c_lexer DESTINATION bin/lexer) -install(TARGETS units_lexer DESTINATION bin/lexer) +install(TARGETS nmodl_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer) +install(TARGETS c_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer) +install(TARGETS units_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer) + add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 596c2f8fab..4e84a27aba 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -26,5 +26,5 @@ add_dependencies(nmodl _nmodl pywrapper) # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl DESTINATION bin) -install(FILES nmodl.hpp DESTINATION include) +install(TARGETS nmodl DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin) +install(FILES nmodl.hpp DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include) diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index a0c4ed2393..c8308ef4c5 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -15,6 +15,6 @@ target_link_libraries(units_parser util visitor lexer) # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_parser DESTINATION bin/parser) -install(TARGETS c_parser DESTINATION bin/parser) -install(TARGETS units_parser DESTINATION bin/parser) +install(TARGETS nmodl_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser) +install(TARGETS c_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser) +install(TARGETS units_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 6ae469fae9..97a8f6998c 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -92,8 +92,7 @@ file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/ # twice with the wheel and in weird places. Let's just move the .so libs if(NOT LINK_AGAINST_PYTHON) install(TARGETS _nmodl DESTINATION nmodl/) - install(TARGETS pywrapper DESTINATION nmodl/.data/) - install(FILES ${CMAKE_BINARY_DIR}/bin/nmodl DESTINATION nmodl/.data/) + install(TARGETS pywrapper DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib) elseif(SKBUILD) # skbuild needs the installation dir to be in nmodl to do the correct inplace install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl DESTINATION .) else() diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index 2653e6e082..e627a86851 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -13,4 +13,4 @@ file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY # ============================================================================= # Install solver headers and eigen from include # ============================================================================= -install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION include) +install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include) diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 2146356b05..069ac0c988 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -84,4 +84,4 @@ target_link_libraries( # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_visitor DESTINATION bin/visitor) +install(TARGETS nmodl_visitor DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor) From fbf1ef840bceb0d6d817040e0f422f7de0b9b698 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 5 Aug 2020 10:29:57 +0200 Subject: [PATCH 283/871] Fixes small imprecisions in the jupyter notebooks (issue BlueBrain/nmodl#368) (BlueBrain/nmodl#373) Fix small imprecisions in jupyter notebooks - .gitignore ignores jupyter folder .ipynb_checkpoints - kinetic schemes now correctly displays the law of mass action - m' = m^3 in cnexp now is correctly computed. Added an additional example to show what happens if the equation is not handled. This fixes issue BlueBrain/nmodl#368 - black-formatted setup.py NMODL Repo SHA: BlueBrain/nmodl@124e9164f404e421fa7b235e49ee5fbb56d974d8 --- .../notebooks/nmodl-kinetic-schemes.ipynb | 15 +++-- .../notebooks/nmodl-sympy-solver-cnexp.ipynb | 38 +++++++++++-- setup.py | 56 +++++++++---------- 3 files changed, 70 insertions(+), 39 deletions(-) diff --git a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb index 67cedb55a8..280a056658 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-kinetic-schemes.ipynb @@ -28,8 +28,13 @@ "\n", "where\n", "- $k_i$ is the rate coefficient\n", - "- $\\nu_{ij}^L$, $\\nu_{ij}^R$ are stoichiometric coefficients - must be positive integers (including zero)\n", - "***\n", + "- $\\nu_{ij}^L$, $\\nu_{ij}^R$ are stoichiometric coefficients - must be positive integers (including zero)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "#### Law of Mass Action\n", "\n", "- This allows us to convert these reaction equations to a set of ODEs\n", @@ -37,9 +42,7 @@ "$$\\frac{dY_j}{dt} = \\sum_i \\Delta \\nu_{ij} r_i$$\n", "\n", "where $\\Delta \\nu_{ij} = \\nu_{ij}^R - \\nu_{ij}^L$, and\n", - "$$r_i = k_i \\prod_j Y_j^{\\nu_{ij}^R}$$\n", - "\n", - "***" + "$$r_i = k_i \\prod_j Y_j^{\\nu_{ij}^R}$$" ] }, { @@ -454,7 +457,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.7" + "version": "3.8.2" } }, "nbformat": 4, diff --git a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb index 0fe84e88f4..02761b3c25 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-sympy-solver-cnexp.ipynb @@ -241,7 +241,7 @@ "metadata": {}, "source": [ "##### Ex. 5\n", - "Single nonlinear ODE: unsupported, so does not modify DERIVATIVE block, leaves it to a later visitor pass to deal with" + "Single nonlinear ODE with analytic solution" ] }, { @@ -271,12 +271,40 @@ "print(\"pade approx:\\t\", run_sympy_solver(ex5, pade=True)[0])" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "##### Ex. 6\n", + "Single nonlinear ODE (more complicated and not handled yet): unsupported, so does not modify DERIVATIVE block, leaves it to a later visitor pass to deal with" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "exact solution:\t m' = exp(m)^2\n", + "pade approx:\t m' = exp(m)^2\n" + ] + } + ], + "source": [ + "ex6 = \"\"\"\n", + "BREAKPOINT {\n", + " SOLVE states METHOD cnexp\n", + "}\n", + "DERIVATIVE states {\n", + " m' = exp(m)^2\n", + "}\n", + "\"\"\"\n", + "print(\"exact solution:\\t\", run_sympy_solver(ex6, pade=False)[0])\n", + "print(\"pade approx:\\t\", run_sympy_solver(ex6, pade=True)[0])" + ] } ], "metadata": { @@ -295,7 +323,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.7" + "version": "3.8.2" } }, "nbformat": 4, diff --git a/setup.py b/setup.py index fde44cb1ef..bf4951ff4b 100644 --- a/setup.py +++ b/setup.py @@ -10,18 +10,26 @@ import subprocess import sys -import subprocess -import os - from setuptools import Command from skbuild import setup +""" +A generic wrapper to access nmodl binaries from a python installation +Please create a softlink with the binary name to be called. +""" +import stat +from pkg_resources import working_set +from pywheel.shim.find_libpython import find_libpython + # Main source of the version. Dont rename, used by Cmake try: - v = subprocess.run(['git', 'describe', '--tags'], - stdout=subprocess.PIPE).stdout.strip().decode() - __version__ = v[:v.rfind("-")].replace('-', '.') if "-" in v else v + v = ( + subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE) + .stdout.strip() + .decode() + ) + __version__ = v[: v.rfind("-")].replace("-", ".") if "-" in v else v except Exception as e: raise RuntimeError("Could not get version from Git repo") from e @@ -52,35 +60,27 @@ class Docs(Command): finalize_options = lambda self: None initialize_options = lambda self: None - def run(self, *args, **kwargs): self.run_command("doctest") self.run_command("buildhtml") -""" -A generic wrapper to access nmodl binaries from a python installation -Please create a softlink with the binary name to be called. -""" -import os -import stat -import sys -from pkg_resources import working_set -from pywheel.shim.find_libpython import find_libpython - - def _config_exe(exe_name): """Sets the environment to run the real executable (returned)""" - package_name = 'nmodl' - - assert package_name in working_set.by_key, "NMODL package not found! Verify PYTHONPATH" - NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, 'nmodl') - NMODL_PREFIX_DATA = os.path.join(NMODL_PREFIX, '.data') - if sys.platform == "darwin" : - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.dylib') + package_name = "nmodl" + + assert ( + package_name in working_set.by_key + ), "NMODL package not found! Verify PYTHONPATH" + NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, "nmodl") + NMODL_PREFIX_DATA = os.path.join(NMODL_PREFIX, ".data") + if sys.platform == "darwin": + os.environ["NMODL_WRAPLIB"] = os.path.join( + NMODL_PREFIX_DATA, "libpywrapper.dylib" + ) else: - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, 'libpywrapper.so') + os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, "libpywrapper.so") # find libpython*.so in the system os.environ["NMODL_PYLIB"] = find_libpython() @@ -99,9 +99,9 @@ def _config_exe(exe_name): cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE") # For CI, we want to build separate wheel -package_name = 'NMODL' +package_name = "NMODL" if "NMODL_NIGHTLY_TAG" in os.environ: - package_name += os.environ['NMODL_NIGHTLY_TAG'] + package_name += os.environ["NMODL_NIGHTLY_TAG"] setup( name=package_name, From 53f845f83f029a282ea5abaec336cbc1d03861ca Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Wed, 5 Aug 2020 15:32:16 +0200 Subject: [PATCH 284/871] Fix maximum value returned by uniform distribution The previous implementation assumed that the following code didn't assert. ```c++ const char foo[] = "foo"; assert(sizeof (foo) == 3); ``` But actually the `assert` fails because `sizeof (foo) == 4` since it takes the trailing \0 into account. Fixes BlueBrain/nmodl#386 NMODL Repo SHA: BlueBrain/nmodl@91be8efb1c30af4e576c42428510eac99b2c2d1f --- src/nmodl/utils/common_utils.cpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 0f256c3cc9..fbeed5a579 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -65,12 +65,8 @@ std::string generate_random_string(const int len, UseNumbersInString use_numbers "abcdefghijklmnopqrstuvwxyz"; std::random_device dev; std::mt19937 rng(dev()); - std::uniform_int_distribution dist; - if (use_numbers) { - dist = std::uniform_int_distribution(0, (sizeof(alphanum) - 1)); - } else { - dist = std::uniform_int_distribution(10, (sizeof(alphanum) - 1)); - } + std::uniform_int_distribution dist(use_numbers ? 0 : 10, + sizeof(alphanum) - 2); for (int i = 0; i < len; ++i) { s[i] = alphanum[dist(rng)]; } From 9c2b50bae3901fcac2d2b5be616bfeb90a2ef2d4 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Wed, 5 Aug 2020 15:36:07 +0200 Subject: [PATCH 285/871] `SingletonRandomString` class is now reentrant the `instance` static method could swith the `use_numbers` member variable of the singleton which could alter other part of the code that kept the shared pointer. Now the `use_numbers` parameter is a parameter of `reset_random_string` member function. This change doesn't fix an actual bug but prevent future one. NMODL Repo SHA: BlueBrain/nmodl@92941092855c0deeca1fdb8d12bf5968d10da8a5 --- src/nmodl/codegen/codegen_c_visitor.cpp | 7 +++---- src/nmodl/utils/common_utils.hpp | 15 ++++++--------- src/nmodl/visitors/visitor_utils.cpp | 4 ++-- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 71f2a8936d..8c7d577d00 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1638,12 +1638,11 @@ void CodegenCVisitor::print_function(ast::FunctionBlock& node) { } std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) const { - auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance( - UseNumbersInString::WithNumbers); + auto& singleton_random_string_class = utils::SingletonRandomString<4>::instance(); std::string unique_name = original_name; - if (singleton_random_string_class->random_string_exists(original_name)) { + if (singleton_random_string_class.random_string_exists(original_name)) { unique_name = original_name; - unique_name += "_" + singleton_random_string_class->get_random_string(original_name); + unique_name += "_" + singleton_random_string_class.get_random_string(original_name); }; return unique_name; } diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index f3874c9d52..3d02e7b4c0 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -107,10 +107,9 @@ class SingletonRandomString { SingletonRandomString& operator=(SingletonRandomString const&) = delete; /// Function to instantiate the SingletonRandomString class - static std::shared_ptr instance(const UseNumbersInString use_numbers_) { - static std::shared_ptr s{new SingletonRandomString}; - s->use_numbers = use_numbers_; - return s; + static SingletonRandomString& instance() { + static SingletonRandomString srs; + return srs; } /** Check if there is a random string assigned as suffix for the var_name variable @@ -135,9 +134,10 @@ class SingletonRandomString { * and assign a new one, else simply insert a new random string for var_name * * @param var_name Variable name for which to reset the random string + * @param use_numbers control whether random string can include numeric characters or not * @return Random string assigned to var_name */ - std::string reset_random_string(const std::string& var_name) { + std::string reset_random_string(const std::string& var_name, UseNumbersInString use_numbers) { if (random_string_exists(var_name)) { random_strings.erase(var_name); random_strings.insert({var_name, generate_random_string(SIZE, use_numbers)}); @@ -152,15 +152,12 @@ class SingletonRandomString { /// \{ /// Constructor used by instance() - SingletonRandomString() {} + SingletonRandomString() = default; /// \} /// std::map that keeps the random strings assigned to variables as suffix std::map random_strings; - - /// bool to control if random string will include numbers or not - UseNumbersInString use_numbers = WithNumbers; }; /** @} */ // end of utils diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index cb1c0af09e..fb14866617 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -32,11 +32,11 @@ std::string suffix_random_string(const std::set& vars, const UseNumbersInString use_num) { std::string new_string = original_string; std::string random_string; - auto singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(use_num); + auto& singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); // Check if there is a variable defined in the mod file as original_string and if yes // try to use a different string in the form "original_string"_"random_string" while (vars.find(new_string) != vars.end()) { - random_string = singleton_random_string_class->reset_random_string(original_string); + random_string = singleton_random_string_class.reset_random_string(original_string, use_num); new_string = original_string; new_string += "_" + random_string; } From cb51e24c902c8a5c0f82050313868bea315d4a2c Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Tue, 4 Aug 2020 19:07:04 +0200 Subject: [PATCH 286/871] All LALR1 parsers share the same location class. diffeq, c11, and unit Bison parsers now reuses the `nmodl::parser::location` class generated by the nmodl Bison parser. Fixes BlueBrain/nmodl#380 NMODL Repo SHA: BlueBrain/nmodl@68ce4767fb167b959e9fa5a81938b2e57bcd7a01 --- src/nmodl/parser/c11.yy | 4 ++++ src/nmodl/parser/diffeq.yy | 6 +++--- src/nmodl/parser/unit.yy | 7 ++++--- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/nmodl/parser/c11.yy b/src/nmodl/parser/c11.yy index d85aec397c..d89e1ad241 100644 --- a/src/nmodl/parser/c11.yy +++ b/src/nmodl/parser/c11.yy @@ -15,6 +15,7 @@ #include #include "parser/c11_driver.hpp" + #include "parser/nmodl/location.hh" } /** use C++ parser interface of bison */ @@ -53,6 +54,9 @@ /** keep track of the current position within the input */ %locations +/** specify own location class */ +%define api.location.type {nmodl::parser::location} + %token IDENTIFIER %token I_CONSTANT %token F_CONSTANT diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 2aa902d037..fd8b88c1c5 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -21,6 +21,7 @@ #include "parser/diffeq_context.hpp" #include "parser/diffeq_driver.hpp" #include "parser/diffeq_helper.hpp" + #include "parser/nmodl/location.hh" } /** use C++ parser interface of bison */ @@ -35,9 +36,6 @@ /** enable tracing parser for debugging */ %define parse.trace -/** enable location tracking */ -%locations - /** add extra arguments to yyparse() and yylexe() methods */ %parse-param {class DiffeqLexer& scanner} %parse-param {diffeq::DiffEqContext& eq_context} @@ -61,6 +59,8 @@ /** keep track of the current position within the input */ %locations +/** specify own location class */ +%define api.location.type {nmodl::parser::location} %token ATOM %token NEWLINE diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index f304d6a186..290c1f76f2 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -10,9 +10,7 @@ %code requires { - #include - #include - + #include "parser/nmodl/location.hh" #include "parser/unit_driver.hpp" #include "units/units.hpp" } @@ -53,6 +51,9 @@ /** keep track of the current position within the input */ %locations +/** specify own location class */ +%define api.location.type {nmodl::parser::location} + %token END 0 "end of file" %token INVALID_TOKEN From 422e368b35564c18c66dbbad7aea6d07e0a501b2 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Tue, 4 Aug 2020 19:30:15 +0200 Subject: [PATCH 287/871] Declare in CMake all C++ files created by nmodl.yy NMODL Repo SHA: BlueBrain/nmodl@f6db2a72054c312b7318ecbdc9f31d38780967d6 --- src/nmodl/lexer/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 34168e57ac..1c88d996ad 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -2,8 +2,10 @@ # Various project components and their source files # ============================================================================= set(BISON_GENERATED_SOURCE_FILES + ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp + ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp From bf44cb41d03a15e8ab03c101a59563129e657df1 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Fri, 7 Aug 2020 11:03:33 +0200 Subject: [PATCH 288/871] Introducing ConstVisitor (BlueBrain/nmodl#362) * New `nmodl::visitor::ConstVisitor` class that provide a way to create visitors to traverse an AST without modifying it. * Provide Python bindings for such visitor * new helper function `collect_nodes` to replace direct usage of `AstLookupVisitor` API of AstLookupVisitor is error prone as the user can easily manipulate a reference to a destructed object: ``` const auto& verbatim_nodes = AstLookupVisitor().lookup(ast, {VERBATIM}) ``` * Prefer \ to @ for doxygen comment to be compliant with coding conventions * `CheckParentVisitor` API now uses const references to Ast node, not pointers. * `JSONVisitor`: new `write` member function helper. * `RenameVisitor`: prefer ostringstream to string for string concatenation * constant visitors: * CheckParentVisitor * ConstAstLookupVisitor (new class) * DefUseAnalyzeVisitor * JSONVisitor * LocalizeVisitor * NmodlPrintVisitor * PerfVisitor * RenameVisitor * VarUsageVisitor * VerbatimVisitor * `get_new_name` helper function: only look once in the map instead of at least 2, 3 at worse. * CDriver: more const, more const reference, less IO flush, use unique_ptr instead of raw pointers * prefer `std::map::emplace` to `std::map::insert` * remove some explicit calls to "delete" as well as useless heap allocations NMODL Repo SHA: BlueBrain/nmodl@f90f671e03fd537540de359e52e21852a335f2b1 --- src/nmodl/codegen/codegen_c_visitor.cpp | 7 +- .../codegen/codegen_compatibility_visitor.cpp | 9 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 1 - src/nmodl/codegen/codegen_info.cpp | 8 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 26 ++-- src/nmodl/language/templates/ast/ast.cpp | 27 ++++ src/nmodl/language/templates/ast/ast.hpp | 30 +++-- .../templates/ast/node_class.template | 19 ++- src/nmodl/language/templates/pybind/pyast.cpp | 10 +- src/nmodl/language/templates/pybind/pyast.hpp | 26 ++-- .../language/templates/pybind/pyvisitor.cpp | 49 +++++-- .../language/templates/pybind/pyvisitor.hpp | 35 +++++ .../templates/visitors/ast_visitor.cpp | 5 + .../templates/visitors/ast_visitor.hpp | 22 +-- .../visitors/checkparent_visitor.cpp | 18 +-- .../visitors/checkparent_visitor.hpp | 39 +++--- .../templates/visitors/json_visitor.cpp | 2 +- .../templates/visitors/json_visitor.hpp | 24 +++- .../templates/visitors/lookup_visitor.cpp | 33 +++-- .../templates/visitors/lookup_visitor.hpp | 49 +++++-- .../templates/visitors/nmodl_visitor.cpp | 2 +- .../templates/visitors/nmodl_visitor.hpp | 10 +- .../language/templates/visitors/visitor.hpp | 47 ++++--- src/nmodl/nmodl/main.cpp | 2 +- src/nmodl/parser/c11_driver.cpp | 33 +++-- src/nmodl/parser/c11_driver.hpp | 32 ++--- src/nmodl/parser/nmodl.yy | 5 +- src/nmodl/parser/nmodl_driver.cpp | 4 +- src/nmodl/pybind/pynmodl.cpp | 14 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 76 +++++------ src/nmodl/visitors/defuse_analyze_visitor.hpp | 58 ++++---- src/nmodl/visitors/global_var_visitor.cpp | 2 +- src/nmodl/visitors/global_var_visitor.hpp | 10 +- src/nmodl/visitors/inline_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.hpp | 12 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 3 +- src/nmodl/visitors/kinetic_block_visitor.hpp | 6 +- .../visitors/local_to_assigned_visitor.cpp | 4 +- .../visitors/local_to_assigned_visitor.hpp | 2 +- .../visitors/local_var_rename_visitor.cpp | 2 +- .../visitors/local_var_rename_visitor.hpp | 6 +- src/nmodl/visitors/localize_visitor.cpp | 26 ++-- src/nmodl/visitors/localize_visitor.hpp | 10 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 34 +++-- src/nmodl/visitors/loop_unroll_visitor.hpp | 6 +- src/nmodl/visitors/main.cpp | 50 ++++--- src/nmodl/visitors/neuron_solve_visitor.hpp | 8 +- src/nmodl/visitors/perf_visitor.cpp | 126 +++++++++--------- src/nmodl/visitors/perf_visitor.hpp | 90 ++++++------- src/nmodl/visitors/rename_visitor.cpp | 21 ++- src/nmodl/visitors/rename_visitor.hpp | 18 +-- src/nmodl/visitors/solve_block_visitor.cpp | 14 +- src/nmodl/visitors/solve_block_visitor.hpp | 6 +- src/nmodl/visitors/steadystate_visitor.cpp | 5 +- .../visitors/sympy_conductance_visitor.cpp | 27 ++-- .../visitors/sympy_conductance_visitor.hpp | 6 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 14 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 1 - src/nmodl/visitors/units_visitor.cpp | 8 +- src/nmodl/visitors/units_visitor.hpp | 8 +- src/nmodl/visitors/var_usage_visitor.cpp | 4 +- src/nmodl/visitors/var_usage_visitor.hpp | 12 +- .../visitors/verbatim_var_rename_visitor.cpp | 10 +- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_visitor.hpp | 8 +- src/nmodl/visitors/visitor_utils.cpp | 45 ++++--- src/nmodl/visitors/visitor_utils.hpp | 17 ++- .../transpiler/unit/modtoken/modtoken.cpp | 1 - test/nmodl/transpiler/unit/parser/parser.cpp | 12 +- .../transpiler/unit/pybind/test_visitor.py | 18 ++- .../unit/visitor/constant_folder.cpp | 2 +- .../unit/visitor/defuse_analyze.cpp | 9 +- .../unit/visitor/global_to_range.cpp | 1 - test/nmodl/transpiler/unit/visitor/inline.cpp | 2 +- .../transpiler/unit/visitor/kinetic_block.cpp | 12 +- .../unit/visitor/local_to_assigned.cpp | 1 - .../transpiler/unit/visitor/localize.cpp | 2 +- test/nmodl/transpiler/unit/visitor/lookup.cpp | 28 ++-- .../transpiler/unit/visitor/loop_unroll.cpp | 2 +- test/nmodl/transpiler/unit/visitor/misc.cpp | 8 +- .../transpiler/unit/visitor/neuron_solve.cpp | 2 +- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 6 +- test/nmodl/transpiler/unit/visitor/rename.cpp | 2 +- .../transpiler/unit/visitor/solve_block.cpp | 2 +- .../transpiler/unit/visitor/steadystate.cpp | 5 +- .../unit/visitor/sympy_conductance.cpp | 9 +- .../transpiler/unit/visitor/sympy_solver.cpp | 9 +- test/nmodl/transpiler/unit/visitor/units.cpp | 18 ++- .../transpiler/unit/visitor/verbatim.cpp | 4 +- 89 files changed, 856 insertions(+), 646 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 8c7d577d00..617bc81422 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -18,7 +18,6 @@ #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -30,7 +29,6 @@ namespace codegen { using namespace ast; -using visitor::AstLookupVisitor; using visitor::RenameVisitor; using visitor::VarUsageVisitor; @@ -1402,10 +1400,7 @@ void CodegenCVisitor::print_function_prototypes() { static TableStatement* get_table_statement(ast::Block& node) { // TableStatementVisitor v; - AstLookupVisitor v(AstNodeType::TABLE_STATEMENT); - node.accept(v); - - auto table_statements = v.get_nodes(); + const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); if (table_statements.size() != 1) { auto message = diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 8eb580cd8b..2bc8d742ac 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -10,15 +10,14 @@ #include "ast/all.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" -#include "visitors/lookup_visitor.hpp" +#include "visitors/visitor_utils.hpp" + using namespace fmt::literals; namespace nmodl { namespace codegen { -using visitor::AstLookupVisitor; - const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( {{AstNodeType::MATCH_BLOCK, @@ -92,7 +91,7 @@ std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( ast::Ast& node, const std::shared_ptr& ast_node) { std::stringstream error_message_no_bbcore_read_write; - auto verbatim_nodes = AstLookupVisitor().lookup(node, AstNodeType::VERBATIM); + const auto& verbatim_nodes = collect_nodes(node, {AstNodeType::VERBATIM}); auto found_bbcore_read = false; auto found_bbcore_write = false; for (const auto& it: verbatim_nodes) { @@ -140,7 +139,7 @@ bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) { for (auto kv: unhandled_ast_types_func) { unhandled_ast_types.push_back(kv.first); } - unhandled_ast_nodes = AstLookupVisitor().lookup(node, unhandled_ast_types); + unhandled_ast_nodes = collect_nodes(node, unhandled_ast_types); std::stringstream ss; for (const auto& it: unhandled_ast_nodes) { diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 44a5edc63e..040001cdbc 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -12,7 +12,6 @@ #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" -#include "visitors/lookup_visitor.hpp" namespace nmodl { diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index b913f3cfea..0ab1b0b169 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -8,14 +8,12 @@ #include "codegen/codegen_info.hpp" #include "ast/all.hpp" -#include "visitors/lookup_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { namespace codegen { -using visitor::AstLookupVisitor; - /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() const { for (const auto& ion: ions) { @@ -100,9 +98,7 @@ bool CodegenInfo::nrn_state_has_eigen_solver_block() const { if (nrn_state_block == nullptr) { return false; } - return !AstLookupVisitor() - .lookup(*nrn_state_block, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK) - .empty(); + return !collect_nodes(*nrn_state_block, {ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK}).empty(); } } // namespace codegen diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 8aa551962f..303e13d74d 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -13,7 +13,6 @@ #include "codegen/codegen_naming.hpp" #include "symtab/symbol_table.hpp" #include "utils/logger.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -24,7 +23,6 @@ namespace codegen { using symtab::syminfo::Status; -using visitor::AstLookupVisitor; using visitor::RenameVisitor; /****************************************************************************************/ @@ -686,11 +684,13 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { } void CodegenIspcVisitor::determine_target() { - AstLookupVisitor node_lv(incompatible_node_types); + const auto& has_incompatible_nodes = [this](const ast::Ast& node) { + return !collect_nodes(node, incompatible_node_types).empty(); + }; if (info.initial_node) { emit_fallback[BlockType::Initial] = - !node_lv.lookup(*info.initial_node).empty() || + has_incompatible_nodes(*info.initial_node) || visitor::calls_function(*info.initial_node, "net_send") || info.require_wrote_conc; } else { emit_fallback[BlockType::Initial] = info.net_receive_initial_node || @@ -698,14 +698,14 @@ void CodegenIspcVisitor::determine_target() { } if (info.net_receive_node) { - emit_fallback[BlockType::NetReceive] = !node_lv.lookup(*info.net_receive_node).empty() || + emit_fallback[BlockType::NetReceive] = has_incompatible_nodes(*info.net_receive_node) || visitor::calls_function(*info.net_receive_node, "net_send"); } if (nrn_cur_required()) { if (info.breakpoint_node) { - emit_fallback[BlockType::Equation] = !node_lv.lookup(*info.breakpoint_node).empty(); + emit_fallback[BlockType::Equation] = has_incompatible_nodes(*info.breakpoint_node); } else { emit_fallback[BlockType::Equation] = false; } @@ -713,7 +713,7 @@ void CodegenIspcVisitor::determine_target() { if (nrn_state_required()) { if (info.nrn_state_block) { - emit_fallback[BlockType::State] = !node_lv.lookup(*info.nrn_state_block).empty(); + emit_fallback[BlockType::State] = has_incompatible_nodes(*info.nrn_state_block); } else { emit_fallback[BlockType::State] = false; } @@ -724,9 +724,8 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { auto nameset = std::set(); auto populate_nameset = [&nameset](ast::Block* block) { - AstLookupVisitor name_lv(ast::AstNodeType::NAME); if (block) { - const auto& names = name_lv.lookup(*block); + const auto& names = collect_nodes(*block, {ast::AstNodeType::NAME}); for (const auto& name: names) { nameset.insert(name->get_node_name()); } @@ -736,11 +735,14 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { populate_nameset(info.nrn_state_block); populate_nameset(info.breakpoint_node); - AstLookupVisitor node_lv(incompatible_node_types); + const auto& has_incompatible_nodes = [this](const ast::Ast& node) { + return !collect_nodes(node, incompatible_node_types).empty(); + }; + auto target_procedures = std::vector(); for (const auto& procedure: info.procedures) { const auto& name = procedure->get_name()->get_node_name(); - if (nameset.find(name) == nameset.end() || !node_lv.lookup(*procedure).empty()) { + if (nameset.find(name) == nameset.end() || has_incompatible_nodes(*procedure)) { wrapper_procedures.push_back(procedure); } else { target_procedures.push_back(procedure); @@ -750,7 +752,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { auto target_functions = std::vector(); for (const auto& function: info.functions) { const auto& name = function->get_name()->get_node_name(); - if (nameset.find(name) == nameset.end() || !node_lv.lookup(*function).empty()) { + if (nameset.find(name) == nameset.end() || has_incompatible_nodes(*function)) { wrapper_functions.push_back(function); } else { target_functions.push_back(function); diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 2b33ff9bbb..a72e3ff106 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -109,10 +109,37 @@ void Ast::set_parent(Ast* p) { {% endfor %} } + void {{ node.class_name }}::visit_children(visitor::ConstVisitor& v) const { + {% for child in node.non_base_members %} + {% if child.is_vector %} + /// visit each element of vector + for (auto& item : this->{{ child.varname }}) { + item->accept(v); + } + {% elif child.optional %} + /// optional member could be nullptr + if (this->{{ child.varname }}) { + this->{{ child.varname }}->accept(v); + } + {% elif child.is_pointer_node %} + /// use -> for pointer member + {{ child.varname }}->accept(v); + {% else %} + /// use . for object member + {{ child.varname }}.accept(v); + {% endif %} + (void)v; + {% endfor %} + } + void {{ node.class_name }}::accept(visitor::Visitor& v) { v.visit_{{ node.class_name | snake_case }}(*this); } + void {{ node.class_name }}::accept(visitor::ConstVisitor& v) const { + v.visit_{{ node.class_name | snake_case }}(*this); + } + {% if node.children %} {{ node.ctor_definition() }} diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index d50899c532..ee28092379 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -137,7 +137,7 @@ struct Ast: public std::enable_shared_from_this { * statement can be returned as a std::string for printing to * text/json form. * - * @return name of the statement as a string + * \return name of the statement as a string * * \sa Ast::get_nmodl_name */ @@ -152,7 +152,7 @@ struct Ast: public std::enable_shared_from_this { * accept allows to visit the current node itself using the concrete * visitor provided. * - * @param v Concrete visitor that will be used to recursively visit children + * \param v Concrete visitor that will be used to recursively visit children * * \note Below is an example of `accept` method implementation which shows how * visitor method corresponding to ast::IndexedName node is called allowing @@ -167,13 +167,20 @@ struct Ast: public std::enable_shared_from_this { */ virtual void accept(visitor::Visitor& v) = 0; + /** + * \brief Accept (or visit) the AST node using a given visitor + * \param v constant visitor visiting the AST node + * \ref accept(visitor::Visitor&); + */ + virtual void accept(visitor::ConstVisitor& v) const = 0; + /** * \brief Visit children i.e. member of AST node using provided visitor * * Different nodes in the AST have different members (i.e. children). This method * recursively visits children using provided concrete visitor. * - * @param v Concrete visitor that will be used to recursively visit node + * \param v Concrete visitor that will be used to recursively visit node * * \note Below is an example of `visit_children` method implementation which shows * ast::IndexedName node children are visited instead of node itself. @@ -187,6 +194,11 @@ struct Ast: public std::enable_shared_from_this { */ virtual void visit_children(visitor::Visitor& v) = 0; + /** + * \copydoc visit_children(visitor::Visitor& v) + */ + virtual void visit_children(visitor::ConstVisitor& v) const = 0; + /** * \brief Create a copy of the current node * @@ -195,7 +207,7 @@ struct Ast: public std::enable_shared_from_this { * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the * ast. * - * @return pointer to the clone/copy of the current node + * \return pointer to the clone/copy of the current node */ virtual Ast* clone() const; @@ -212,7 +224,7 @@ struct Ast: public std::enable_shared_from_this { * name. Note that this is different from ast node type name and not every * ast node has name. * - * @return name of the node as std::string + * \return name of the node as std::string * * \sa Ast::get_node_type_name */ @@ -225,7 +237,7 @@ struct Ast: public std::enable_shared_from_this { * can insert new nodes in the ast as a solution of ODEs. In this case, we return * nullptr to store in the nmodl::symtab::SymbolTable. * - * @return pointer to token if exist otherwise nullptr + * \return pointer to token if exist otherwise nullptr */ virtual const ModToken* get_token() const; @@ -236,7 +248,7 @@ struct Ast: public std::enable_shared_from_this { * symbol table. These nodes have nmodl::symtab::SymbolTable as member * and it can be accessed using this method. * - * @return pointer to the symbol table + * \return pointer to the symbol table * * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor */ @@ -259,7 +271,7 @@ struct Ast: public std::enable_shared_from_this { * * This method return enclosing statement block. * - * @return pointer to the statement block if exist + * \return pointer to the statement block if exist * * \sa ast::StatementBlock */ @@ -306,7 +318,7 @@ struct Ast: public std::enable_shared_from_this { /** *\brief Check if the ast node is an instance of ast::Ast - * @return true if object of type ast::Ast + * \return true if object of type ast::Ast */ virtual bool is_ast() const noexcept; diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index c7145dee10..4585bd7a7e 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -293,7 +293,19 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::visit_children for example. */ - {{ virtual(node) }} void visit_children (visitor::Visitor& v) override; + {{ virtual(node) }} void visit_children(visitor::Visitor& v) override; + + /** + * \brief visit children i.e. member variables of current node using provided visitor + * + * Different nodes in the AST have different members (i.e. children). This method + * recursively visits children using provided visitor. + * + * \param v Concrete constant visitor that will be used to recursively visit children + * + * \sa Ast::visit_children for example. + */ + {{ virtual(node) }} void visit_children(visitor::ConstVisitor& v) const override; /** * \brief accept (or visit) the current AST node using provided visitor @@ -308,6 +320,11 @@ class {{ node.class_name }} : public {{ node.base_class }} { */ {{ virtual(node) }} void accept(visitor::Visitor& v) override; + /** + * \copydoc accept(visitor::Visitor&) + */ + {{ virtual(node) }} void accept(visitor::ConstVisitor& v) const override; + /// \} {% if node.is_base_class_number_node %} diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 3d3f308a19..b81d8ff6a2 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -199,8 +199,9 @@ void init_ast_module(py::module& m) { py::class_> ast_(m_ast, "Ast", docstring::ast_class); ast_.def(py::init<>()) - .def("visit_children", &Ast::visit_children, "v"_a, docstring::visit_children_method) - .def("accept", &Ast::accept, "v"_a, docstring::accept_method) + .def("visit_children", static_cast(&Ast::visit_children), "v"_a, docstring::visit_children_method) + .def("accept", static_cast(&Ast::accept), "v"_a, docstring::accept_method) + .def("accept", static_cast(&Ast::accept), "v"_a, docstring::accept_method) .def("get_node_type", &Ast::get_node_type, docstring::get_node_type_method) .def("get_node_type_name", &Ast::get_node_type_name, docstring::get_node_type_name_method) .def("get_node_name", &Ast::get_node_name, docstring::get_node_name_method) @@ -258,8 +259,9 @@ void init_ast_module(py::module& m) { {% endif %} {% endfor %} - {{ var(node) }}.def("visit_children", &{{ node.class_name }}::visit_children, docstring::visit_children_method) - .def("accept", &{{ node.class_name }}::accept, docstring::accept_method) + {{ var(node) }}.def("visit_children", static_cast(&{{ node.class_name }}::visit_children), docstring::visit_children_method) + .def("accept", static_cast(&{{ node.class_name }}::accept), docstring::accept_method) + .def("accept", static_cast(&{{ node.class_name }}::accept), docstring::accept_method) .def("clone", &{{ node.class_name }}::clone, docstring::clone_method) .def("get_node_type", &{{ node.class_name }}::get_node_type, docstring::get_node_type_method) .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name, docstring::get_node_type_name_method) diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 0c6ae34320..0e3910c140 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -28,16 +28,16 @@ using namespace ast; */ /** - * @defgroup nmodl_python Python Interface - * @brief Python Bindings Implementation + * \defgroup nmodl_python Python Interface + * \brief Python Bindings Implementation */ /** * - * @defgroup ast_python AST Python Interface - * @ingroup nmodl_python - * @brief Ast classes for Python bindings - * @{ + * \defgroup ast_python AST Python Interface + * \ingroup nmodl_python + * \brief Ast classes for Python bindings + * \{ */ /** @@ -58,10 +58,21 @@ struct PyAst: public Ast { ); } + void visit_children(visitor::ConstVisitor& v) const override { + PYBIND11_OVERLOAD_PURE(void, /// Return type + Ast, /// Parent class + visit_children, /// Name of function in C++ (must match Python name) + v /// Argument(s) + ); + } + void accept(visitor::Visitor& v) override { PYBIND11_OVERLOAD_PURE(void, Ast, accept, v); } + void accept(visitor::ConstVisitor& v) const override { + PYBIND11_OVERLOAD_PURE(void, Ast, accept, v); + } Ast* clone() const override { PYBIND11_OVERLOAD(Ast*, Ast, clone, ); @@ -128,5 +139,4 @@ struct PyAst: public Ast { {% endfor %} }; -/** @} */ // end of ast_python - +/** \} */ // end of ast_python diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index fc97449151..d131843784 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -112,6 +112,18 @@ void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_nam PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, node); } {% endfor %} + +{% for node in nodes %} +void PyConstVisitor::visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) { +PYBIND11_OVERLOAD_PURE(void, ConstVisitor, visit_{{ node.class_name|snake_case }}, node); +} +{% endfor %} + +{% for node in nodes %} +void PyConstAstVisitor::visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) { +PYBIND11_OVERLOAD(void, ConstAstVisitor, visit_{{ node.class_name|snake_case }}, node); +} +{% endfor %} // clang-format on @@ -139,7 +151,7 @@ class PyNmodlPrintVisitor: private VisitorOStreamResources, public NmodlPrintVis // clang-format off {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override { + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override { NmodlPrintVisitor::visit_{{ node.class_name|snake_case }}(node); flush(); } @@ -159,17 +171,34 @@ void init_visitor_module(py::module& m) { {% if loop.last -%};{% endif %} {% endfor %} // clang-format on + py::class_ const_visitor(m_visitor, "ConstVisitor", docstring::visitor_class); + const_visitor.def(py::init<>()) + // clang-format off + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &ConstVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} // clang-format on + + py::class_ + const_ast_visitor(m_visitor, "ConstAstVisitor", docstring::ast_visitor_class); + const_ast_visitor.def(py::init<>()) + // clang-format off + {% for node in nodes %} + .def("visit_{{ node.class_name | snake_case }}", &ConstAstVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} + {% endfor %} // clang-format on + py::class_ - ast_visitor(m_visitor, "AstVisitor", docstring::ast_visitor_class); + ast_visitor(m_visitor, "AstVisitor", docstring::ast_visitor_class); ast_visitor.def(py::init<>()) // clang-format off {% for node in nodes %} - .def("visit_{{ node.class_name | snake_case }}", &AstVisitor::visit_{{ node.class_name | snake_case }}) - {% if loop.last -%};{% endif %} + .def("visit_{{ node.class_name | snake_case }}", &AstVisitor::visit_{{ node.class_name | snake_case }}) + {% if loop.last -%};{% endif %} {% endfor %} // clang-format on - py::class_ + py::class_ nmodl_visitor(m_visitor, "NmodlPrintVisitor", docstring::nmodl_print_visitor_class); nmodl_visitor.def(py::init()); nmodl_visitor.def(py::init()); @@ -187,13 +216,9 @@ void init_visitor_module(py::module& m) { .def(py::init()) .def("get_nodes", &AstLookupVisitor::get_nodes) .def("clear", &AstLookupVisitor::clear) - .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast&)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast&, ast::AstNodeType)) &AstLookupVisitor::lookup) - .def("lookup", (std::vector> (AstLookupVisitor::*)(ast::Ast&, const std::vector&)) &AstLookupVisitor::lookup) - {% for node in nodes %} - .def("visit_{{ node.class_name | snake_case }}", &AstLookupVisitor::visit_{{ node.class_name | snake_case }}) - {% if loop.last -%};{% endif %} - {% endfor %} + .def("lookup", static_cast>& (AstLookupVisitor::*)(ast::Ast&)>(&AstLookupVisitor::lookup)) + .def("lookup", static_cast>& (AstLookupVisitor::*)(ast::Ast&, ast::AstNodeType)>(&AstLookupVisitor::lookup)) + .def("lookup", static_cast>& (AstLookupVisitor::*)(ast::Ast&, const std::vector&)>(&AstLookupVisitor::lookup)); py::class_ constant_folder_visitor(m_visitor, "ConstantFolderVisitor", docstring::constant_folder_visitor_class); constant_folder_visitor.def(py::init<>()) diff --git a/src/nmodl/language/templates/pybind/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp index cfc63379fe..22a4fc4a3d 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.hpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.hpp @@ -61,3 +61,38 @@ class PyAstVisitor : public AstVisitor { {% endfor %} }; +/** + * \brief Class mirroring nmodl::visitor::ConstVisitor for Python bindings + * + * \details \copydetails nmodl::visitor::ConstVisitor + * + * This class is used to interface nmodl::visitor::ConstVisitor with the Python + * world using `pybind11`. + */ +class PyConstVisitor : public ConstVisitor { +public: + using ConstVisitor::ConstVisitor; + + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; + {% endfor %} +}; + + +/** + * \brief Class mirroring nmodl::visitor::ConstAstVisitor for Python bindings + * + * \details \copydetails nmodl::visitor::ConstAstVisitor + * + * This class is used to interface nmodl::visitor::ConstAstVisitor with the Python + * world using `pybind11`. + */ +class PyConstAstVisitor : public ConstAstVisitor { +public: + using ConstAstVisitor::ConstAstVisitor; + + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; + {% endfor %} +}; + diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index 328c98516a..4e4f019b60 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -23,7 +23,12 @@ using namespace ast; void AstVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { node.visit_children(*this); } +{% endfor %} +{% for node in nodes %} +void ConstAstVisitor::visit_{{ node.class_name|snake_case }}(const {{ node.class_name }}& node) { + node.visit_children(*this); +} {% endfor %} } // namespace visitor diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index 369a7f31ae..324a0a0dd4 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -27,25 +27,31 @@ namespace nmodl { namespace visitor { /** - * @ingroup visitor_classes - * @{ + * \ingroup visitor_classes + * \{ */ /** * \brief Concrete visitor for all AST classes - * - * This class defines interface for all concrete visitors implementation. - * Note that this class only provides interface that could be implemented - * by concrete visitors like ast::AstVisitor. */ -class AstVisitor : public Visitor { +class AstVisitor: public Visitor { public: {% for node in nodes %} void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; {% endfor %} }; -/** @} */ // end of visitor_classes +/** + * \brief Concrete constant visitor for all AST classes + */ +class ConstAstVisitor: public ConstVisitor { + public: + {% for node in nodes %} + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; + {% endfor %} +}; + +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index 175b79e092..df39f122c7 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -23,25 +23,25 @@ namespace test { using namespace fmt::literals; using namespace ast; -int CheckParentVisitor::check_ast(Ast* node) { +int CheckParentVisitor::check_ast(const Ast& node) { parent = nullptr; - node->accept(*this); + node.accept(*this); return 0; } -void CheckParentVisitor::check_parent(ast::Ast* node) const { +void CheckParentVisitor::check_parent(const ast::Ast& node) const { if (!parent) { - if (is_root_with_null_parent && node->get_parent()) { + if (is_root_with_null_parent && node.get_parent()) { const auto& parent_type = parent->get_node_type_name(); throw std::runtime_error("root->parent: {} is set when it should be nullptr"_format(parent_type)); } } else { - if (parent != node->get_parent()) { - std::string parent_type = (parent == nullptr) ? "nullptr" : parent->get_node_type_name(); - std::string node_parent_type = (node->get_parent() == nullptr) ? std::string("nullptr") : node->get_parent()->get_node_type_name(); + if (parent != node.get_parent()) { + const std::string parent_type = (parent == nullptr) ? "nullptr" : parent->get_node_type_name(); + const std::string node_parent_type = (node.get_parent() == nullptr) ? "nullptr" : node.get_parent()->get_node_type_name(); throw std::runtime_error("parent: {} and child->parent: {} missmatch"_format(parent_type, node_parent_type)); } } @@ -49,7 +49,9 @@ void CheckParentVisitor::check_parent(ast::Ast* node) const { {% for node in nodes %} -void CheckParentVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { +void CheckParentVisitor::visit_{{ node.class_name|snake_case }}(const {{ node.class_name }}& node) { + // check the node + check_parent(node); // Set this node as parent. and go down the tree parent = &node; diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp index 57bfd65cf6..d4f57fb9b9 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -42,12 +42,12 @@ namespace test { * the tree. Once all the children were checked we set the parent of the * current node as parent (it was checked before) and return. */ -class CheckParentVisitor : public AstVisitor { +class CheckParentVisitor : public ConstAstVisitor { private: /** * \brief Keeps track of the parents while going down the tree */ - ast::Ast* parent = nullptr; + const ast::Ast* parent = nullptr; /** * \brief Flag to activate the parent check on the root node * @@ -55,32 +55,37 @@ class CheckParentVisitor : public AstVisitor { * root node, from which we start the visit, is the root node and * thus, it should have nullptr parent */ - bool is_root_with_null_parent = false; - public: + const bool is_root_with_null_parent = false; /** - * \brief Standard constructor - * - * If is_root_with_null_parent is set to true, also the initial - * node is checked to be sure that is really the root (parent = nullptr) - */ - CheckParentVisitor(const bool is_root_with_null_parent = true) {} + * \brief Check the parent, throw an error if not + */ + void check_parent(const ast::Ast& node) const; + + public: /** - * \brief A small wrapper to have a nicer call in parser.cpp - */ - int check_ast(ast::Ast* node); + * \brief Standard constructor + * + * If \a is_root_with_null_parent is set to true, also the initial + * node is checked to be sure that is really the root (parent == nullptr) + */ + CheckParentVisitor(bool is_root_with_null_parent = true) + : is_root_with_null_parent(is_root_with_null_parent) {} /** - * \brief Check the parent, throw an error if not - */ - void check_parent(ast::Ast* node) const; + * \brief A small wrapper to have a nicer call in parser.cpp + * \return 0 + */ + int check_ast(const ast::Ast& node); + + protected: {% for node in nodes %} /** * \brief Go through the tree while checking the parents */ - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; {% endfor %} }; diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 215db9748a..87a372f965 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -20,7 +20,7 @@ namespace visitor { using namespace ast; {% for node in nodes %} -void JSONVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { +void JSONVisitor::visit_{{ node.class_name|snake_case }}(const {{ node.class_name }}& node) { {% if node.has_children() %} printer->push_block(node.get_node_type_name()); if (embed_nmodl) { diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index 255b7977fe..92a9a2a32f 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -34,7 +34,7 @@ namespace visitor { * Convert AST into JSON form form using AST visitor. This is used * for debugging or visualization purpose. */ -class JSONVisitor: public AstVisitor { +class JSONVisitor: public ConstAstVisitor { private: /// json printer std::unique_ptr printer; @@ -49,28 +49,38 @@ class JSONVisitor: public AstVisitor { JSONVisitor(std::string filename) : printer(new printer::JSONPrinter(filename)) {} - JSONVisitor(std::stringstream& ss) + JSONVisitor(std::ostream& ss) : printer(new printer::JSONPrinter(ss)) {} - void flush() { + JSONVisitor& write(const ast::Program& program) { + visit_program(program); + return *this; + } + + JSONVisitor& flush() { printer->flush(); + return *this; } - void compact_json(bool flag) { + JSONVisitor& compact_json(bool flag) { printer->compact_json(flag); + return *this; } - void add_nmodl(bool flag) { + JSONVisitor& add_nmodl(bool flag) { embed_nmodl = flag; + return *this; } - void expand_keys(bool flag) { + JSONVisitor& expand_keys(bool flag) { printer->expand_keys(flag); + return *this; } + protected: // clang-format off {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; {% endfor %} // clang-format on }; diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index 3e907b8bf3..89ea781ded 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -22,38 +22,47 @@ namespace visitor { using namespace ast; {% for node in nodes %} -void AstLookupVisitor::visit_{{ node.class_name|snake_case }}({{ node.class_name }}& node) { +template +void MetaAstLookupVisitor::visit_{{ node.class_name|snake_case }}(typename visit_arg_trait<{{ node.class_name }}>::type& node) { const auto type = node.get_node_type(); if (std::find(types.begin(), types.end(), type) != types.end()) { nodes.push_back(node.get_shared_ptr()); } node.visit_children(*this); } - {% endfor %} - -std::vector> AstLookupVisitor::lookup(Ast& node, const std::vector& _types) { - nodes.clear(); - types = _types; +template +const typename MetaAstLookupVisitor::nodes_t& +MetaAstLookupVisitor::lookup(typename MetaAstLookupVisitor::ast_t& node, + const std::vector& t_types) { + clear(); + this->types = t_types; node.accept(*this); return nodes; } -std::vector> AstLookupVisitor::lookup(Ast& node, AstNodeType type) { - nodes.clear(); - types.clear(); - types.push_back(type); +template +const typename MetaAstLookupVisitor::nodes_t& +MetaAstLookupVisitor::lookup(typename MetaAstLookupVisitor::ast_t& node, + AstNodeType type) { + clear(); + this->types.push_back(type); node.accept(*this); return nodes; } -std::vector> AstLookupVisitor::lookup(Ast& node) { +template +const typename MetaAstLookupVisitor::nodes_t& +MetaAstLookupVisitor::lookup(typename MetaAstLookupVisitor::ast_t& node) { nodes.clear(); node.accept(*this); return nodes; } +// explicit template instantiation definitions +template class MetaAstLookupVisitor; +template class MetaAstLookupVisitor; + } // namespace visitor } // namespace nmodl - diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index a299646d21..fb7a6ebcbf 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -22,39 +22,52 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** * \class AstLookupVisitor * \brief %Visitor to find AST nodes based on their types */ -class AstLookupVisitor: public Visitor { +template +class MetaAstLookupVisitor: public DefaultVisitor { + static const bool is_const_visitor = std::is_same::value; + + template + struct identity { + using type = T; + }; + + template + using visit_arg_trait = + typename std::conditional, identity>::type; + using ast_t = typename visit_arg_trait::type; + using nodes_t = std::vector>; + private: /// node types to search in the ast std::vector types; /// matching nodes found in the ast - std::vector> nodes; + std::vector> nodes; public: - AstLookupVisitor() = default; + MetaAstLookupVisitor() = default; - AstLookupVisitor(ast::AstNodeType type) + MetaAstLookupVisitor(ast::AstNodeType type) : types{type} {} - AstLookupVisitor(const std::vector& types) + MetaAstLookupVisitor(const std::vector& types) : types(types) {} - std::vector> lookup(ast::Ast& node); + const nodes_t& lookup(ast_t& node); - std::vector> lookup(ast::Ast& node, ast::AstNodeType type); + const nodes_t& lookup(ast_t& node, ast::AstNodeType type); - std::vector> lookup(ast::Ast& node, - const std::vector& types); + const nodes_t& lookup(ast_t& node, const std::vector& t_types); - const std::vector>& get_nodes() const noexcept { + const nodes_t& get_nodes() const noexcept { return nodes; } @@ -63,14 +76,22 @@ class AstLookupVisitor: public Visitor { nodes.clear(); } + protected: // clang-format off {% for node in nodes %} - void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; + void visit_{{ node.class_name|snake_case }}(typename visit_arg_trait::type& node) override; {% endfor %} // clang-format on }; -/** @} */ // end of visitor_classes +using AstLookupVisitor = MetaAstLookupVisitor; +using ConstAstLookupVisitor = MetaAstLookupVisitor; + +// explicit template instantiation declarations +extern template class MetaAstLookupVisitor; +extern template class MetaAstLookupVisitor; + +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 740a1ef4fb..2b9ef3ede5 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -86,7 +86,7 @@ using namespace ast; {%- for node in nodes %} -void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}({{ node.class_name }}& node) { +void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}(const {{ node.class_name }}& node) { if (is_exclude_type(node.get_node_type())) { return; } diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index a76e3b19f9..854d76183f 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -25,15 +25,15 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** * \class NmodlPrintVisitor * \brief %Visitor for printing AST back to NMODL */ -class NmodlPrintVisitor: public Visitor { +class NmodlPrintVisitor: public ConstVisitor { private: std::unique_ptr printer; @@ -61,7 +61,7 @@ class NmodlPrintVisitor: public Visitor { // clang-format off {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) override; + virtual void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; {% endfor %} // clang-format on @@ -72,7 +72,7 @@ class NmodlPrintVisitor: public Visitor { bool statement); }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 553cefbb44..01bbe3bd1e 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -18,13 +18,13 @@ namespace nmodl { namespace visitor { /** - * @defgroup visitor Visitor Implementation - * @brief All visitors related implementation details + * \defgroup visitor Visitor Implementation + * \brief All visitors related implementation details * - * @defgroup visitor_classes Visitors - * @ingroup visitor - * @brief Different visitors implemented in NMODL - * @{ + * \defgroup visitor_classes Visitors + * \ingroup visitor + * \brief Different visitors implemented in NMODL + * \{ */ /** @@ -32,23 +32,40 @@ namespace visitor { * * This class defines interface for all concrete visitors implementation. * Note that this class only provides interface that could be implemented - * by oncrete visitors like ast::AstVisitor. + * by concrete visitors like ast::AstVisitor. * * \sa ast::AstVisitor */ class Visitor { + public: + virtual ~Visitor() = default; - public: - virtual ~Visitor() = default; + {% for node in nodes %} + /// visit node of type ast::{{ node.class_name }} + virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) = 0; + {% endfor %} +}; + +/** + * \brief Abstract base class for all constant visitors implementation + * + * This class defines interface for all concrete constant visitors implementation. + * Note that this class only provides interface that could be implemented + * by concrete visitors like ast::ConstAstVisitor. + * + * \sa ast::ConstAstVisitor + */ +class ConstVisitor { + public: + virtual ~ConstVisitor() = default; - {% for node in nodes %} - /// visit node of type ast::{{ node.class_name }} - virtual void visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) = 0; - {% endfor %} + {% for node in nodes %} + /// visit node of type ast::{{ node.class_name }} + virtual void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) = 0; + {% endfor %} }; } // namespace visitor } // namespace nmodl -/** @} */ // end of visitor_classes - +/** \} */ // end of visitor_classes diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index c1b1957ae7..4807320c00 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -364,7 +364,7 @@ int main(int argc, const char* argv[]) { if (json_ast) { logger->info("Writing AST into {}", file); auto file = scratch_dir + "/" + modfile + ".ast.json"; - JSONVisitor(file).visit_program(*ast); + JSONVisitor(file).write(*ast); } if (verbatim_rename) { diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index 2e25c9accb..e9a1a82e8c 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -14,21 +14,22 @@ namespace nmodl { namespace parser { +CDriver::CDriver() = default; + CDriver::CDriver(bool strace, bool ptrace) : trace_scanner(strace) , trace_parser(ptrace) {} +CDriver::~CDriver() = default; + /// parse c file provided as istream bool CDriver::parse_stream(std::istream& in) { - CLexer scanner(*this, &in); - CParser parser(scanner, *this); - - this->lexer = &scanner; - this->parser = &parser; + lexer.reset(new CLexer(*this, &in)); + parser.reset(new CParser(*lexer, *this)); - scanner.set_debug(trace_scanner); - parser.set_debug_level(trace_parser); - return (parser.parse() == 0); + lexer->set_debug(trace_scanner); + parser->set_debug_level(trace_parser); + return parser->parse() == 0; } //// parse c file @@ -48,8 +49,8 @@ bool CDriver::parse_string(const std::string& input) { return parse_stream(iss); } -void CDriver::error(const std::string& m) { - std::cerr << m << std::endl; +void CDriver::error(const std::string& m) const { + std::cerr << m << '\n'; } void CDriver::add_token(const std::string& text) { @@ -57,16 +58,14 @@ void CDriver::add_token(const std::string& text) { // here we will query and look into symbol table or register callback } -void CDriver::error(const std::string& m, const location& l) { - std::cerr << l << " : " << m << std::endl; +void CDriver::error(const std::string& m, const location& l) const { + std::cerr << l << " : " << m << '\n'; } -void CDriver::scan_string(std::string& text) { +void CDriver::scan_string(const std::string& text) { std::istringstream in(text); - CLexer scanner(*this, &in); - CParser parser(scanner, *this); - this->lexer = &scanner; - this->parser = &parser; + lexer.reset(new CLexer(*this, &in)); + parser.reset(new CParser(*lexer, *this)); while (true) { auto sym = lexer->next_token(); auto token_type = sym.type_get(); diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 07eb485d4e..9de478e54e 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -25,8 +26,8 @@ class CParser; class location; /** - * @addtogroup parser - * @{ + * \addtogroup parser + * \{ */ /** @@ -51,10 +52,10 @@ class CDriver { bool trace_parser = false; /// pointer to the lexer instance being used - CLexer* lexer = nullptr; + std::unique_ptr lexer; /// pointer to the parser instance being used - CParser* parser = nullptr; + std::unique_ptr parser; /// print messages from lexer/parser bool verbose = false; @@ -63,41 +64,42 @@ class CDriver { /// file or input stream name (used by scanner for position), see todo std::string streamname; - CDriver() = default; + CDriver(); CDriver(bool strace, bool ptrace); + ~CDriver(); - void error(const std::string& m); + void error(const std::string& m) const; bool parse_stream(std::istream& in); bool parse_string(const std::string& input); bool parse_file(const std::string& filename); - void scan_string(std::string& text); + void scan_string(const std::string& text); void add_token(const std::string&); - void error(const std::string& m, const location& l); + void error(const std::string& m, const location& l) const; - void set_verbose(bool b) { + void set_verbose(bool b) noexcept { verbose = b; } - bool is_verbose() const { + bool is_verbose() const noexcept { return verbose; } - bool is_typedef(std::string type) const { + bool is_typedef(const std::string& type) const noexcept { return typedefs.find(type) != typedefs.end(); } - bool is_enum_constant(std::string constant) const { + bool is_enum_constant(const std::string& constant) const noexcept { return std::find(enum_constants.begin(), enum_constants.end(), constant) != enum_constants.end(); } - std::vector all_tokens() const { + const std::vector& all_tokens() const noexcept { return tokens; } - bool has_token(std::string token) { + bool has_token(const std::string& token) const noexcept { if (std::find(tokens.begin(), tokens.end(), token) != tokens.end()) { return true; } @@ -105,7 +107,7 @@ class CDriver { } }; -/** @} */ // end of parser +/** \} */ // end of parser } // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index e12954cac5..7cf74354a9 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -2610,14 +2610,13 @@ threadsafe_var_list : NAME_PTR * "empty" Verbatim parser which scan and return same string. */ std::string parse_with_verbatim_parser(std::string str) { - auto is = new std::istringstream(str.c_str()); + std::istringstream is(str.c_str()); - VerbatimDriver extcontext(is); + VerbatimDriver extcontext(&is); Verbatim_parse(&extcontext); std::string ss(*(extcontext.result)); - delete is; return ss; } diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index cf1c457348..99219b5ca2 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -54,11 +54,11 @@ std::shared_ptr NmodlDriver::parse_string(const std::string& input } void NmodlDriver::error(const std::string& m, const class location& l) { - std::cerr << l << " : " << m << std::endl; + std::cerr << l << " : " << m << '\n'; } void NmodlDriver::error(const std::string& m) { - std::cerr << m << std::endl; + std::cerr << m << '\n'; } /// add macro definition and it's value (DEFINE keyword of nmodl) diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index d2c19d8ed1..9aee8393b6 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -161,13 +161,13 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { nmodl::docstring::driver_parse_stream) .def("get_ast", &nmodl::PyNmodlDriver::get_ast, nmodl::docstring::driver_ast); - m_nmodl.def( - "to_nmodl", - static_cast&)>( - nmodl::to_nmodl), - "node"_a, - "exclude_types"_a = std::set(), - nmodl::docstring::to_nmodl); + m_nmodl.def("to_nmodl", + static_cast&)>( + nmodl::to_nmodl), + "node"_a, + "exclude_types"_a = std::set(), + nmodl::docstring::to_nmodl); m_nmodl.def("to_json", nmodl::to_json, "node"_a, diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 3b1895fdd7..4f4b60e1a0 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -50,8 +50,7 @@ std::string to_string(DUState state) { } std::ostream& operator<<(std::ostream& os, DUState state) { - os << to_string(state); - return os; + return os << to_string(state); } /// DUInstance to JSON string @@ -60,7 +59,7 @@ void DUInstance::print(JSONPrinter& printer) const { printer.add_node(to_string(state)); } else { printer.push_block(to_string(state)); - for (auto& inst: children) { + for (const auto& inst: children) { inst.print(printer); } printer.pop_block(); @@ -69,12 +68,12 @@ void DUInstance::print(JSONPrinter& printer) const { /// DUChain to JSON string std::string DUChain::to_string(bool compact) const { - std::stringstream stream; + std::ostringstream stream; JSONPrinter printer(stream); printer.compact_json(compact); printer.push_block(name); - for (auto& instance: chain) { + for (const auto& instance: chain) { instance.print(printer); } printer.pop_block(); @@ -87,10 +86,10 @@ std::string DUChain::to_string(bool compact) const { * As these are innermost blocks, we have to just check first use * of variable in this block and that's the result of this block. */ -DUState DUInstance::sub_block_eval() { +DUState DUInstance::sub_block_eval() const { DUState result = DUState::NONE; - for (auto& chain: children) { - auto child_state = chain.eval(); + for (const auto& chain: children) { + const auto& child_state = chain.eval(); if (child_state == DUState::U || child_state == DUState::D) { result = child_state; break; @@ -128,11 +127,11 @@ DUState DUInstance::sub_block_eval() { * block encountered, this means every block has either "D" or "CD". In * this case we can say that entire block effectively has "D". */ -DUState DUInstance::conditional_block_eval() { +DUState DUInstance::conditional_block_eval() const { DUState result = DUState::NONE; bool block_with_none = false; - for (auto& chain: children) { + for (const auto& chain: children) { auto child_state = chain.eval(); if (child_state == DUState::U) { result = child_state; @@ -156,7 +155,7 @@ DUState DUInstance::conditional_block_eval() { * Note that we are interested in "global" variable usage * and hence we consider only [U,D] states and not [LU, LD] */ -DUState DUInstance::eval() { +DUState DUInstance::eval() const { auto result = state; if (state == DUState::IF || state == DUState::ELSEIF || state == DUState::ELSE) { result = sub_block_eval(); @@ -168,7 +167,7 @@ DUState DUInstance::eval() { /// first usage of a variable in a block decides whether it's definition /// or usage. Note that if-else blocks already evaluated. -DUState DUChain::eval() { +DUState DUChain::eval() const { auto result = DUState::NONE; for (auto& inst: chain) { auto re = inst.eval(); @@ -183,7 +182,7 @@ DUState DUChain::eval() { return result; } -void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node& node) { +void DefUseAnalyzeVisitor::visit_unsupported_node(const ast::Node& node) { unsupported_node = true; node.visit_children(*this); unsupported_node = false; @@ -194,7 +193,7 @@ void DefUseAnalyzeVisitor::visit_unsupported_node(ast::Node& node) { * there is no inlining happened. In this case we mark the call as * unsupported. */ -void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall& node) { +void DefUseAnalyzeVisitor::visit_function_call(const ast::FunctionCall& node) { const auto& function_name = node.get_node_name(); const auto& symbol = global_symtab->lookup_in_scope(function_name); if (symbol == nullptr || symbol->is_external_variable()) { @@ -204,7 +203,7 @@ void DefUseAnalyzeVisitor::visit_function_call(ast::FunctionCall& node) { } } -void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock& node) { +void DefUseAnalyzeVisitor::visit_statement_block(const ast::StatementBlock& node) { const auto& symtab = node.get_symbol_table(); if (symtab != nullptr) { current_symtab = symtab; @@ -219,7 +218,7 @@ void DefUseAnalyzeVisitor::visit_statement_block(ast::StatementBlock& node) { /** Nmodl grammar doesn't allow assignment operator on rhs (e.g. a = b + (b=c) * and hence not necessary to keep track of assignment operator using stack. */ -void DefUseAnalyzeVisitor::visit_binary_expression(ast::BinaryExpression& node) { +void DefUseAnalyzeVisitor::visit_binary_expression(const ast::BinaryExpression& node) { node.get_rhs()->visit_children(*this); if (node.get_op().get_value() == ast::BOP_ASSIGN) { visiting_lhs = true; @@ -228,7 +227,7 @@ void DefUseAnalyzeVisitor::visit_binary_expression(ast::BinaryExpression& node) visiting_lhs = false; } -void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement& node) { +void DefUseAnalyzeVisitor::visit_if_statement(const ast::IfStatement& node) { /// store previous chain auto previous_chain = current_chain; @@ -266,7 +265,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(ast::IfStatement& node) { * \todo One simple way would be to look for p_name in the string * of verbatim block to find the variable usage. */ -void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim& node) { +void DefUseAnalyzeVisitor::visit_verbatim(const ast::Verbatim& node) { if (!ignore_verbatim) { current_chain->push_back(DUInstance(DUState::U)); } @@ -275,41 +274,41 @@ void DefUseAnalyzeVisitor::visit_verbatim(ast::Verbatim& node) { /// unsupported statements : we aren't sure how to handle this "yet" and /// hence variables used in any of the below statements are handled separately -void DefUseAnalyzeVisitor::visit_reaction_statement(ast::ReactionStatement& node) { +void DefUseAnalyzeVisitor::visit_reaction_statement(const ast::ReactionStatement& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_non_lin_equation(ast::NonLinEquation& node) { +void DefUseAnalyzeVisitor::visit_non_lin_equation(const ast::NonLinEquation& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_lin_equation(ast::LinEquation& node) { +void DefUseAnalyzeVisitor::visit_lin_equation(const ast::LinEquation& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_partial_boundary(ast::PartialBoundary& node) { +void DefUseAnalyzeVisitor::visit_partial_boundary(const ast::PartialBoundary& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_from_statement(ast::FromStatement& node) { +void DefUseAnalyzeVisitor::visit_from_statement(const ast::FromStatement& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_conserve(ast::Conserve& node) { +void DefUseAnalyzeVisitor::visit_conserve(const ast::Conserve& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_var_name(ast::VarName& node) { +void DefUseAnalyzeVisitor::visit_var_name(const ast::VarName& node) { const std::string& variable = to_nmodl(node); process_variable(variable); } -void DefUseAnalyzeVisitor::visit_name(ast::Name& node) { +void DefUseAnalyzeVisitor::visit_name(const ast::Name& node) { const std::string& variable = to_nmodl(node); process_variable(variable); } -void DefUseAnalyzeVisitor::visit_indexed_name(ast::IndexedName& node) { +void DefUseAnalyzeVisitor::visit_indexed_name(const ast::IndexedName& node) { const auto& name = node.get_node_name(); const auto& length = node.get_length(); @@ -332,11 +331,11 @@ void DefUseAnalyzeVisitor::visit_indexed_name(ast::IndexedName& node) { /// statements / nodes that should not be used for def-use chain analysis -void DefUseAnalyzeVisitor::visit_conductance_hint(ast::ConductanceHint& /*node*/) {} +void DefUseAnalyzeVisitor::visit_conductance_hint(const ast::ConductanceHint& /*node*/) {} -void DefUseAnalyzeVisitor::visit_local_list_statement(ast::LocalListStatement& /*node*/) {} +void DefUseAnalyzeVisitor::visit_local_list_statement(const ast::LocalListStatement& /*node*/) {} -void DefUseAnalyzeVisitor::visit_argument(ast::Argument& /*node*/) {} +void DefUseAnalyzeVisitor::visit_argument(const ast::Argument& /*node*/) {} /** @@ -352,11 +351,11 @@ void DefUseAnalyzeVisitor::visit_argument(ast::Argument& /*node*/) {} * whereas the one on rhs are marked as "usages". */ void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { - auto symbol = current_symtab->lookup_in_scope(name); + const auto& symbol = current_symtab->lookup_in_scope(name); assert(symbol != nullptr); // variable properties that make variable local - auto properties = NmodlType::local_var | NmodlType::argument; - auto is_local = symbol->has_any_property(properties); + const auto properties = NmodlType::local_var | NmodlType::argument; + const auto is_local = symbol->has_any_property(properties); if (unsupported_node) { current_chain->push_back(DUInstance(DUState::U)); @@ -382,14 +381,15 @@ void DefUseAnalyzeVisitor::process_variable(const std::string& name) { } void DefUseAnalyzeVisitor::process_variable(const std::string& name, int index) { - std::string fullname = name + "[" + std::to_string(index) + "]"; - if (fullname == variable_name) { + std::ostringstream fullname; + fullname << name << '[' << std::to_string(index) + ']'; + if (fullname.str() == variable_name) { update_defuse_chain(name); } } -void DefUseAnalyzeVisitor::visit_with_new_chain(ast::Node& node, DUState state) { - auto last_chain = current_chain; +void DefUseAnalyzeVisitor::visit_with_new_chain(const ast::Node& node, DUState state) { + const auto last_chain = current_chain; start_new_chain(state); node.visit_children(*this); current_chain = last_chain; @@ -400,7 +400,7 @@ void DefUseAnalyzeVisitor::start_new_chain(DUState state) { current_chain = ¤t_chain->back().children; } -DUChain DefUseAnalyzeVisitor::analyze(ast::Ast& node, const std::string& name) { +DUChain DefUseAnalyzeVisitor::analyze(const ast::Ast& node, const std::string& name) { /// re-initialize state variable_name = name; visiting_lhs = false; diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 4fff96f836..af881aeb75 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -86,13 +86,13 @@ class DUInstance { : state(state) {} /// analyze all children and return "effective" usage - DUState eval(); + DUState eval() const; /// if, elseif and else evaluation - DUState sub_block_eval(); + DUState sub_block_eval() const; /// evaluate global usage i.e. with [D,U] states of children - DUState conditional_block_eval(); + DUState conditional_block_eval() const; void print(printer::JSONPrinter& printer) const; }; @@ -115,7 +115,7 @@ class DUChain { : name(std::move(name)) {} /// return "effective" usage of a variable - DUState eval(); + DUState eval() const; /// return json representation std::string to_string(bool compact = true) const; @@ -181,7 +181,7 @@ class DUChain { * in any of the if-elseif-else part then it is considered as "used". And * this is done recursively from innermost level to the top. */ -class DefUseAnalyzeVisitor: protected AstVisitor { +class DefUseAnalyzeVisitor: protected ConstAstVisitor { private: /// symbol table containing global variables symtab::SymbolTable* global_symtab = nullptr; @@ -212,25 +212,25 @@ class DefUseAnalyzeVisitor: protected AstVisitor { void process_variable(const std::string& name, int index); void update_defuse_chain(const std::string& name); - void visit_unsupported_node(ast::Node& node); - void visit_with_new_chain(ast::Node& node, DUState state); + void visit_unsupported_node(const ast::Node& node); + void visit_with_new_chain(const ast::Node& node, DUState state); void start_new_chain(DUState state); public: DefUseAnalyzeVisitor() = delete; - explicit DefUseAnalyzeVisitor(symtab::SymbolTable* symtab) - : global_symtab(symtab) {} + explicit DefUseAnalyzeVisitor(symtab::SymbolTable& symtab) + : global_symtab(&symtab) {} - DefUseAnalyzeVisitor(symtab::SymbolTable* symtab, bool ignore_verbatim) - : global_symtab(symtab) + DefUseAnalyzeVisitor(symtab::SymbolTable& symtab, bool ignore_verbatim) + : global_symtab(&symtab) , ignore_verbatim(ignore_verbatim) {} - void visit_binary_expression(ast::BinaryExpression& node) override; - void visit_if_statement(ast::IfStatement& node) override; - void visit_function_call(ast::FunctionCall& node) override; - void visit_statement_block(ast::StatementBlock& node) override; - void visit_verbatim(ast::Verbatim& node) override; + void visit_binary_expression(const ast::BinaryExpression& node) override; + void visit_if_statement(const ast::IfStatement& node) override; + void visit_function_call(const ast::FunctionCall& node) override; + void visit_statement_block(const ast::StatementBlock& node) override; + void visit_verbatim(const ast::Verbatim& node) override; /** * /\name unsupported statements @@ -239,23 +239,23 @@ class DefUseAnalyzeVisitor: protected AstVisitor { * \{ */ - void visit_reaction_statement(ast::ReactionStatement& node) override; + void visit_reaction_statement(const ast::ReactionStatement& node) override; - void visit_non_lin_equation(ast::NonLinEquation& node) override; + void visit_non_lin_equation(const ast::NonLinEquation& node) override; - void visit_lin_equation(ast::LinEquation& node) override; + void visit_lin_equation(const ast::LinEquation& node) override; - void visit_partial_boundary(ast::PartialBoundary& node) override; + void visit_partial_boundary(const ast::PartialBoundary& node) override; - void visit_from_statement(ast::FromStatement& node) override; + void visit_from_statement(const ast::FromStatement& node) override; - void visit_conserve(ast::Conserve& node) override; + void visit_conserve(const ast::Conserve& node) override; - void visit_var_name(ast::VarName& node) override; + void visit_var_name(const ast::VarName& node) override; - void visit_name(ast::Name& node) override; + void visit_name(const ast::Name& node) override; - void visit_indexed_name(ast::IndexedName& node) override; + void visit_indexed_name(const ast::IndexedName& node) override; /** \} */ @@ -265,16 +265,16 @@ class DefUseAnalyzeVisitor: protected AstVisitor { * \{ */ - void visit_conductance_hint(ast::ConductanceHint& node) override; + void visit_conductance_hint(const ast::ConductanceHint& node) override; - void visit_local_list_statement(ast::LocalListStatement& node) override; + void visit_local_list_statement(const ast::LocalListStatement& node) override; - void visit_argument(ast::Argument& node) override; + void visit_argument(const ast::Argument& node) override; /** \} */ /// compute def-use chain for a variable within the node - DUChain analyze(ast::Ast& node, const std::string& name); + DUChain analyze(const ast::Ast& node, const std::string& name); }; /** @} */ // end of visitor_classes diff --git a/src/nmodl/visitors/global_var_visitor.cpp b/src/nmodl/visitors/global_var_visitor.cpp index 323fc6c43c..7ce72436e1 100644 --- a/src/nmodl/visitors/global_var_visitor.cpp +++ b/src/nmodl/visitors/global_var_visitor.cpp @@ -61,7 +61,7 @@ void GlobalToRangeVisitor::visit_neuron_block(ast::NeuronBlock& node) { /// insert new range variables replacing global ones if (!range_variables.empty()) { auto range_statement = new ast::Range(range_variables); - (*statement_block).emplace_back_statement(range_statement); + statement_block->emplace_back_statement(range_statement); } } diff --git a/src/nmodl/visitors/global_var_visitor.hpp b/src/nmodl/visitors/global_var_visitor.hpp index 746ba98d47..4774958fbf 100644 --- a/src/nmodl/visitors/global_var_visitor.hpp +++ b/src/nmodl/visitors/global_var_visitor.hpp @@ -23,8 +23,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -33,7 +33,7 @@ namespace visitor { * * Some of the existing mod files have GLOBAL variables that are updated in BREAKPOINT * or DERIVATIVE blocks. These variables have a single copy and works well when they - * are read only. If such variables that are written as well, they result into race condition. + * are read only. If such variables are written as well, they result into race condition. * For example, * * \code{.mod} @@ -51,7 +51,7 @@ namespace visitor { * } * \endcode * - * In above example, x will be simultaneously updated in case of vectorization. In NEURON, + * In above example, \a x will be simultaneously updated in case of vectorization. In NEURON, * such race condition is avoided by promoting these variables to thread variable (i.e. separate * copy per thread). In case of CoreNEURON, this is not sufficient because of vectorisation or * GPU execution. To address this issue, this visitor converts GLOBAL variables to RANGE variables @@ -85,7 +85,7 @@ class GlobalToRangeVisitor: public AstVisitor { void visit_neuron_block(ast::NeuronBlock& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 942326bc4f..dec1cf4ef4 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -20,7 +20,7 @@ namespace visitor { using namespace ast; -bool InlineVisitor::can_inline_block(StatementBlock& block) { +bool InlineVisitor::can_inline_block(const StatementBlock& block) const { bool to_inline = true; const auto& statements = block.get_statements(); for (const auto& statement: statements) { diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 4043b75927..5a215acc31 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -22,8 +22,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -54,7 +54,7 @@ namespace visitor { * replacing tau and beta with local variables. Many mod files from BlueBrain * and other open source projects could be hugely benefited by inlining pass. * The goal of this pass is to implement procedure and function inlining in - * the nmodl programs. After inlining we should be asble to translate AST back + * the nmodl programs. After inlining we should be able to translate AST back * to "transformed" nmodl program which can be compiled and run by NEURON or * CoreNEURON simulator. * @@ -159,7 +159,7 @@ class InlineVisitor: public AstVisitor { std::map inlined_variables; /// true if given statement block can be inlined - bool can_inline_block(ast::StatementBlock& block); + bool can_inline_block(const ast::StatementBlock& block) const; /// true if statement can be replaced with inlined body /// this is possible for standalone function/procedure call as statement @@ -175,7 +175,7 @@ class InlineVisitor: public AstVisitor { ast::FunctionCall& node, ast::StatementBlock& caller); - /// add assignement statements into given statement block to inline arguments + /// add assignment statements into given statement block to inline arguments void inline_arguments(ast::StatementBlock& inlined_block, const ast::ArgumentVector& callee_parameters, const ast::ExpressionVector& caller_expressions); @@ -196,7 +196,7 @@ class InlineVisitor: public AstVisitor { void visit_program(ast::Program& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 6a5335cd01..10a403d85a 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -12,7 +12,6 @@ #include "utils/logger.hpp" #include "utils/string_utils.hpp" #include "visitor_utils.hpp" -#include "visitors/lookup_visitor.hpp" namespace nmodl { @@ -466,7 +465,7 @@ void KineticBlockVisitor::visit_program(ast::Program& node) { } } - auto kineticBlockNodes = AstLookupVisitor().lookup(node, ast::AstNodeType::KINETIC_BLOCK); + const auto& kineticBlockNodes = collect_nodes(node, {ast::AstNodeType::KINETIC_BLOCK}); // replace reaction statements within each kinetic block with equivalent ODEs for (const auto& ii: kineticBlockNodes) { ii->accept(*this); diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 0a67071c1f..f744e640c1 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -24,8 +24,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -149,7 +149,7 @@ class KineticBlockVisitor: public AstVisitor { void visit_program(ast::Program& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/local_to_assigned_visitor.cpp b/src/nmodl/visitors/local_to_assigned_visitor.cpp index 538f3046c0..b52b6c3ce8 100644 --- a/src/nmodl/visitors/local_to_assigned_visitor.cpp +++ b/src/nmodl/visitors/local_to_assigned_visitor.cpp @@ -42,7 +42,7 @@ void LocalToAssignedVisitor::visit_program(ast::Program& node) { top_level_node); for (const auto& local_variable: local_list_statement->get_variables()) { - auto variable_name = local_variable->get_node_name(); + const auto& variable_name = local_variable->get_node_name(); /// check if local variable is being updated in the mod file if (symbol_table->lookup(variable_name)->get_write_count() > 0) { assigned_variables.emplace_back( @@ -75,7 +75,7 @@ void LocalToAssignedVisitor::visit_program(ast::Program& node) { assigned_block = std::make_shared(assigned_variables); node.emplace_back_node(std::static_pointer_cast(assigned_block)); } else { - for (auto& assigned_variable: assigned_variables) { + for (const auto& assigned_variable: assigned_variables) { assigned_block->emplace_back_assigned_definition(assigned_variable); } } diff --git a/src/nmodl/visitors/local_to_assigned_visitor.hpp b/src/nmodl/visitors/local_to_assigned_visitor.hpp index 3da902b1a1..cad8e07fe1 100644 --- a/src/nmodl/visitors/local_to_assigned_visitor.hpp +++ b/src/nmodl/visitors/local_to_assigned_visitor.hpp @@ -77,7 +77,7 @@ class LocalToAssignedVisitor: public AstVisitor { void visit_program(ast::Program& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index b7b078b6ab..b81bc7d571 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -50,7 +50,7 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock& node) { parent_symtab = symtab->get_parent_table(); } - auto variables = get_local_list_statement(node); + const auto& variables = get_local_list_statement(node); /// global blocks do not change (do no have parent symbol table) /// if no variables in the block then there is nothing to do diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index a00e7643e8..fb92f4ac7e 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -22,8 +22,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -75,7 +75,7 @@ class LocalVarRenameVisitor: public AstVisitor { void visit_statement_block(ast::StatementBlock& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index e2ca782b46..197d5c9784 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -51,7 +51,7 @@ bool LocalizeVisitor::node_for_def_use_analysis(const ast::Node& node) const { auto it = std::find(blocks_to_analyze.begin(), blocks_to_analyze.end(), type); auto node_to_analyze = !(it == blocks_to_analyze.end()); auto solve_procedure = is_solve_procedure(node); - return (node_to_analyze || solve_procedure); + return node_to_analyze || solve_procedure; } /* @@ -69,6 +69,7 @@ bool LocalizeVisitor::is_solve_procedure(const ast::Node& node) const { } std::vector LocalizeVisitor::variables_to_optimize() const { + std::vector result; // clang-format off const NmodlType excluded_var_properties = NmodlType::extern_var | NmodlType::extern_neuron_variable @@ -81,12 +82,11 @@ std::vector LocalizeVisitor::variables_to_optimize() const { | NmodlType::electrode_cur_var | NmodlType::section_var; - NmodlType global_var_properties = NmodlType::range_var - | NmodlType::assigned_definition - | NmodlType::param_assign; + const NmodlType global_var_properties = NmodlType::range_var + | NmodlType::assigned_definition + | NmodlType::param_assign; // clang-format on - /** * \todo Voltage v can be global variable as well as external. In order * to avoid optimizations, we need to handle this case properly @@ -94,7 +94,6 @@ std::vector LocalizeVisitor::variables_to_optimize() const { */ const auto& variables = program_symtab->get_variables_with_properties(global_var_properties); - std::vector result; for (const auto& variable: variables) { if (!variable->has_any_property(excluded_var_properties)) { result.push_back(variable->get_name()); @@ -103,7 +102,7 @@ std::vector LocalizeVisitor::variables_to_optimize() const { return result; } -void LocalizeVisitor::visit_program(ast::Program& node) { +void LocalizeVisitor::visit_program(const ast::Program& node) { /// symtab visitor pass need to be run before program_symtab = node.get_symbol_table(); if (program_symtab == nullptr) { @@ -119,8 +118,8 @@ void LocalizeVisitor::visit_program(ast::Program& node) { /// compute def use chains for (const auto& block: blocks) { if (node_for_def_use_analysis(*block)) { - DefUseAnalyzeVisitor v(program_symtab, ignore_verbatim); - auto usages = v.analyze(*block, varname); + DefUseAnalyzeVisitor v(*program_symtab, ignore_verbatim); + const auto& usages = v.analyze(*block, varname); auto result = usages.eval(); block_usage[result].push_back(block); } @@ -142,18 +141,17 @@ void LocalizeVisitor::visit_program(ast::Program& node) { auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { - variable = add_local_variable(*statement_block.get(), - varname, - symbol->get_length()); + variable = + add_local_variable(*statement_block, varname, symbol->get_length()); } else { - variable = add_local_variable(*statement_block.get(), varname); + variable = add_local_variable(*statement_block, varname); } /// mark variable as localized in global symbol table symbol->mark_localized(); /// insert new symbol in the symbol table of current block - auto symtab = statement_block->get_symbol_table(); + const auto& symtab = statement_block->get_symbol_table(); auto new_symbol = std::make_shared(varname, variable); new_symbol->add_property(NmodlType::local_var); new_symbol->mark_created(); diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 1a285a79a4..2fbb86005f 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -22,8 +22,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -80,7 +80,7 @@ namespace visitor { * variables. We need to have dead-code removal pass to eliminate unused procedures/ * functions before localizer pass. */ -class LocalizeVisitor: public AstVisitor { +class LocalizeVisitor: public ConstAstVisitor { private: /// ignore verbatim blocks while localizing bool ignore_verbatim = false; @@ -99,10 +99,10 @@ class LocalizeVisitor: public AstVisitor { explicit LocalizeVisitor(bool ignore_verbatim) : ignore_verbatim(ignore_verbatim) {} - void visit_program(ast::Program& node) override; + void visit_program(const ast::Program& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index f11d5e918c..7dd6f89a4e 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -10,7 +10,6 @@ #include "ast/all.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -47,7 +46,7 @@ class IndexRemover: public AstVisitor { /// if expression we are visiting is `Name` then return new `Integer` node std::shared_ptr replace_for_name( - const std::shared_ptr& node) { + const std::shared_ptr& node) const { if (node->is_name()) { auto name = std::dynamic_pointer_cast(node); if (name->get_node_name() == index) { @@ -60,10 +59,10 @@ class IndexRemover: public AstVisitor { void visit_binary_expression(ast::BinaryExpression& node) override { node.visit_children(*this); if (under_indexed_name) { - /// first recursively replaces childrens + /// first recursively replaces children /// replace lhs & rhs if they have matching index variable - auto lhs = replace_for_name(node.get_lhs()); - auto rhs = replace_for_name(node.get_rhs()); + const auto& lhs = replace_for_name(node.get_lhs()); + const auto& rhs = replace_for_name(node.get_rhs()); node.set_lhs(std::move(lhs)); node.set_rhs(std::move(rhs)); } @@ -73,7 +72,7 @@ class IndexRemover: public AstVisitor { under_indexed_name = true; node.visit_children(*this); /// once all children are replaced, do the same for index - auto length = replace_for_name(node.get_length()); + const auto& length = replace_for_name(node.get_length()); node.set_length(std::move(length)); under_indexed_name = false; } @@ -83,7 +82,7 @@ class IndexRemover: public AstVisitor { /// return underlying expression wrapped by WrappedExpression static std::shared_ptr unwrap(const std::shared_ptr& expr) { if (expr && expr->is_wrapped_expression()) { - auto e = std::dynamic_pointer_cast(expr); + const auto& e = std::dynamic_pointer_cast(expr); return e->get_expression(); } return expr; @@ -93,16 +92,16 @@ static std::shared_ptr unwrap(const std::shared_ptr unroll_for_loop( const std::shared_ptr& node) { /// loop can be in the form of `FROM i=(0) TO (10)` /// so first unwrap all elements of the loop - const auto from = unwrap(node->get_from()); - const auto to = unwrap(node->get_to()); - const auto increment = unwrap(node->get_increment()); + const auto& from = unwrap(node->get_from()); + const auto& to = unwrap(node->get_to()); + const auto& increment = unwrap(node->get_increment()); /// we can unroll if iteration space of the loop is known /// after constant folding start, end and increment must be known @@ -122,12 +121,12 @@ static std::shared_ptr unroll_for_loop( std::string index_var = node->get_node_name(); for (int i = start; i <= end; i += step) { /// duplicate loop body and copy all statements to new vector - auto new_block = node->get_statement_block()->clone(); + const auto new_block = std::unique_ptr( + node->get_statement_block()->clone()); IndexRemover(index_var, i).visit_statement_block(*new_block); statements.insert(statements.end(), new_block->get_statements().begin(), new_block->get_statements().end()); - delete new_block; } /// create new statement representing unrolled loop @@ -146,18 +145,17 @@ void LoopUnrollVisitor::visit_statement_block(ast::StatementBlock& node) { for (auto iter = statements.begin(); iter != statements.end(); ++iter) { if ((*iter)->is_from_statement()) { - auto statement = std::dynamic_pointer_cast((*iter)); + const auto& statement = std::dynamic_pointer_cast((*iter)); /// check if any verbatim block exists - auto verbatim_blocks = AstLookupVisitor().lookup(*statement, - ast::AstNodeType::VERBATIM); + const auto& verbatim_blocks = collect_nodes(*statement, {ast::AstNodeType::VERBATIM}); if (!verbatim_blocks.empty()) { logger->debug("LoopUnrollVisitor : can not unroll because of verbatim block"); continue; } /// unroll loop, replace current statement on successfull unroll - auto new_statement = unroll_for_loop(statement); + const auto& new_statement = unroll_for_loop(statement); if (new_statement != nullptr) { node.reset_statement(iter, new_statement); diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index 551715d224..9ee87792d1 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -20,8 +20,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -63,7 +63,7 @@ class LoopUnrollVisitor: public AstVisitor { void visit_statement_block(ast::StatementBlock& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 23e06cc4e2..a6c989f5de 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -15,6 +15,7 @@ #include "pybind/pyembed.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" +#include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/json_visitor.hpp" @@ -41,6 +42,26 @@ using namespace fmt::literals; * \brief Standalone program demonstrating usage of different visitors and driver classes. **/ +template +struct ClassInfo { + std::shared_ptr v; + std::string id; + std::string description; +}; +using VisitorInfo = ClassInfo; +using ConstVisitorInfo = ClassInfo; + +template +void visit_program(const std::string& mod_file, + const ClassInfo& visitor, + ast::Program& ast) { + logger->info("Running {}", visitor.description); + visitor.v->visit_program(ast); + const std::string file = "{}.{}.mod"_format(mod_file, visitor.id); + NmodlPrintVisitor(file).visit_program(ast); + logger->info("NMODL visitor generated {}", file); +}; + int main(int argc, const char* argv[]) { CLI::App app{ "NMODL Visitor : Runs standalone visitor classes({})"_format(Version::to_string())}; @@ -59,17 +80,9 @@ int main(int argc, const char* argv[]) { logger->set_level(spdlog::level::debug); } - struct VisitorInfo { - std::shared_ptr v; - std::string id; - std::string description; - }; - - std::vector visitors = { + const std::vector visitors = { {std::make_shared(), "astvis", "AstVisitor"}, {std::make_shared(), "symtab", "SymtabVisitor"}, - {std::make_shared(), "json", "JSONVisitor"}, - {std::make_shared(), "verbatim", "VerbatimVisitor"}, {std::make_shared(), "verbatim-rename", "VerbatimVarRenameVisitor"}, @@ -83,11 +96,17 @@ int main(int argc, const char* argv[]) { "SympyConductanceVisitor"}, {std::make_shared(), "sympy-solve", "SympySolverVisitor"}, {std::make_shared(), "neuron-solve", "NeuronSolveVisitor"}, - {std::make_shared(), "localize", "LocalizeVisitor"}, - {std::make_shared(), "perf", "PerfVisitor"}, {std::make_shared(NrnUnitsLib::get_path()), "units", "UnitsVisitor"}, }; + const std::vector const_visitors = { + {std::make_shared(), "json", "JSONVisitor"}, + {std::make_shared(), "check-parent", "CheckParentVisitor"}, + {std::make_shared(), "perf", "PerfVisitor"}, + {std::make_shared(), "localize", "LocalizeVisitor"}, + {std::make_shared(), "verbatim", "VerbatimVisitor"}, + }; + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); for (const auto& filename: files) { @@ -103,11 +122,10 @@ int main(int argc, const char* argv[]) { /// run all visitors and generate mod file after each run for (const auto& visitor: visitors) { - logger->info("Running {}", visitor.description); - visitor.v->visit_program(*ast); - const std::string file = mod_file + "." + visitor.id + ".mod"; - NmodlPrintVisitor(file).visit_program(*ast); - logger->info("NMODL visitor generated {}", file); + visit_program(mod_file, visitor, *ast); + } + for (const auto& visitor: const_visitors) { + visit_program(mod_file, visitor, *ast); } } diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index 0ba1a52c63..f2603da17b 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -23,9 +23,9 @@ namespace nmodl { namespace visitor { /** - * @addtogroup solver - * @addtogroup visitor_classes - * @{ + * \addtogroup solver + * \addtogroup visitor_classes + * \{ */ /** @@ -69,7 +69,7 @@ class NeuronSolveVisitor: public AstVisitor { void visit_program(ast::Program& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 77df1180ee..202c302df7 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -31,7 +31,7 @@ void PerfVisitor::compact_json(bool flag) { /// count math operations from all binary expressions -void PerfVisitor::visit_binary_expression(ast::BinaryExpression& node) { +void PerfVisitor::visit_binary_expression(const ast::BinaryExpression& node) { bool assign_op = false; if (start_measurement) { @@ -114,9 +114,9 @@ void PerfVisitor::visit_binary_expression(ast::BinaryExpression& node) { } /// add performance stats to json printer -void PerfVisitor::add_perf_to_printer(PerfStat& perf) { - auto keys = perf.keys(); - auto values = perf.values(); +void PerfVisitor::add_perf_to_printer(const PerfStat& perf) const { + const auto& keys = perf.keys(); + const auto& values = perf.values(); assert(keys.size() == values.size()); for (size_t i = 0; i < keys.size(); i++) { @@ -129,10 +129,10 @@ void PerfVisitor::add_perf_to_printer(PerfStat& perf) { * all children visited, we get total performance by summing * perfstat of all children. */ -void PerfVisitor::measure_performance(ast::Ast* node) { +void PerfVisitor::measure_performance(const ast::Ast& node) { start_measurement = true; - node->visit_children(*this); + node.visit_children(*this); PerfStat perf; while (!children_blocks_perf.empty()) { @@ -140,15 +140,15 @@ void PerfVisitor::measure_performance(ast::Ast* node) { children_blocks_perf.pop(); } - auto symtab = node->get_symbol_table(); + auto symtab = node.get_symbol_table(); if (symtab == nullptr) { throw std::runtime_error("Perfvisitor : symbol table not setup for " + - node->get_node_type_name()); + node.get_node_type_name()); } auto name = symtab->name(); - if (node->is_derivative_block()) { - name = node->get_node_type_name(); + if (node.is_derivative_block()) { + name = node.get_node_type_name(); } if (printer) { @@ -172,7 +172,7 @@ void PerfVisitor::measure_performance(ast::Ast* node) { } /// count function calls and "most useful" or "commonly used" math functions -void PerfVisitor::visit_function_call(ast::FunctionCall& node) { +void PerfVisitor::visit_function_call(const ast::FunctionCall& node) { under_function_call = true; if (start_measurement) { @@ -199,25 +199,25 @@ void PerfVisitor::visit_function_call(ast::FunctionCall& node) { } /// every variable used is of type name, update counters -void PerfVisitor::visit_name(ast::Name& node) { +void PerfVisitor::visit_name(const ast::Name& node) { update_memory_ops(node.get_node_name()); node.visit_children(*this); } /// prime name derived from identifier and hence need to be handled here -void PerfVisitor::visit_prime_name(ast::PrimeName& node) { +void PerfVisitor::visit_prime_name(const ast::PrimeName& node) { update_memory_ops(node.get_node_name()); node.visit_children(*this); } -void PerfVisitor::visit_if_statement(ast::IfStatement& node) { +void PerfVisitor::visit_if_statement(const ast::IfStatement& node) { if (start_measurement) { current_block_perf.n_if++; node.visit_children(*this); } } -void PerfVisitor::visit_else_if_statement(ast::ElseIfStatement& node) { +void PerfVisitor::visit_else_if_statement(const ast::ElseIfStatement& node) { if (start_measurement) { current_block_perf.n_elif++; node.visit_children(*this); @@ -321,7 +321,7 @@ void PerfVisitor::print_memory_usage() { } } -void PerfVisitor::visit_program(ast::Program& node) { +void PerfVisitor::visit_program(const ast::Program& node) { if (printer) { printer->push_block("BlockPerf"); } @@ -343,100 +343,100 @@ void PerfVisitor::visit_program(ast::Program& node) { print_memory_usage(); } -void PerfVisitor::visit_plot_block(ast::PlotBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_plot_block(const ast::PlotBlock& node) { + measure_performance(node); } /// skip initial block under net_receive block -void PerfVisitor::visit_initial_block(ast::InitialBlock& node) { +void PerfVisitor::visit_initial_block(const ast::InitialBlock& node) { if (!under_net_receive_block) { - measure_performance(&node); + measure_performance(node); } } -void PerfVisitor::visit_constructor_block(ast::ConstructorBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_constructor_block(const ast::ConstructorBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_destructor_block(ast::DestructorBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_destructor_block(const ast::DestructorBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_derivative_block(ast::DerivativeBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_derivative_block(const ast::DerivativeBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_linear_block(ast::LinearBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_linear_block(const ast::LinearBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_non_linear_block(ast::NonLinearBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_non_linear_block(const ast::NonLinearBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_discrete_block(ast::DiscreteBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_discrete_block(const ast::DiscreteBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_partial_block(ast::PartialBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_partial_block(const ast::PartialBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_function_table_block(ast::FunctionTableBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_function_table_block(const ast::FunctionTableBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_function_block(ast::FunctionBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_function_block(const ast::FunctionBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_procedure_block(ast::ProcedureBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_net_receive_block(ast::NetReceiveBlock& node) { +void PerfVisitor::visit_net_receive_block(const ast::NetReceiveBlock& node) { under_net_receive_block = true; - measure_performance(&node); + measure_performance(node); under_net_receive_block = false; } -void PerfVisitor::visit_breakpoint_block(ast::BreakpointBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_breakpoint_block(const ast::BreakpointBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_terminal_block(ast::TerminalBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_terminal_block(const ast::TerminalBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_before_block(ast::BeforeBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_before_block(const ast::BeforeBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_after_block(ast::AfterBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_after_block(const ast::AfterBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_ba_block(ast::BABlock& node) { - measure_performance(&node); +void PerfVisitor::visit_ba_block(const ast::BABlock& node) { + measure_performance(node); } -void PerfVisitor::visit_for_netcon(ast::ForNetcon& node) { - measure_performance(&node); +void PerfVisitor::visit_for_netcon(const ast::ForNetcon& node) { + measure_performance(node); } -void PerfVisitor::visit_kinetic_block(ast::KineticBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_kinetic_block(const ast::KineticBlock& node) { + measure_performance(node); } -void PerfVisitor::visit_match_block(ast::MatchBlock& node) { - measure_performance(&node); +void PerfVisitor::visit_match_block(const ast::MatchBlock& node) { + measure_performance(node); } /** Blocks like function can have multiple statement blocks and * blocks like net receive has nested initial blocks. Hence need * to maintain separate stack. */ -void PerfVisitor::visit_statement_block(ast::StatementBlock& node) { +void PerfVisitor::visit_statement_block(const ast::StatementBlock& node) { /// starting new block, store current state blocks_perf.push(current_block_perf); @@ -466,13 +466,13 @@ void PerfVisitor::visit_statement_block(ast::StatementBlock& node) { /// and hence could/should not be skipped completely /// we can't ignore the block because it could have associated /// statement block (in theory) -void PerfVisitor::visit_solve_block(ast::SolveBlock& node) { +void PerfVisitor::visit_solve_block(const ast::SolveBlock& node) { under_solve_block = true; node.visit_children(*this); under_solve_block = false; } -void PerfVisitor::visit_unary_expression(ast::UnaryExpression& node) { +void PerfVisitor::visit_unary_expression(const ast::UnaryExpression& node) { if (start_measurement) { auto value = node.get_op().get_value(); switch (value) { @@ -513,7 +513,7 @@ bool PerfVisitor::symbol_to_skip(const std::shared_ptr& symbol) { return skip; } -bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) { +bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) const { bool is_local = false; /// in the function when we write to function variable then consider it as local variable auto properties = NmodlType::local_var | NmodlType::argument | NmodlType::function_block; @@ -523,7 +523,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) { return is_local; } -bool PerfVisitor::is_constant_variable(const std::shared_ptr& symbol) { +bool PerfVisitor::is_constant_variable(const std::shared_ptr& symbol) const { bool is_constant = false; auto properties = NmodlType::param_assign; if (symbol->has_any_property(properties)) { diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index cc32b1ec44..e55347f306 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -26,8 +26,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -55,7 +55,7 @@ namespace visitor { * start_measurement, there should be "empty" ast visitor from * which PerfVisitor should be inherited. */ -class PerfVisitor: public AstVisitor { +class PerfVisitor: public ConstAstVisitor { private: /// symbol table of current block being visited symtab::SymbolTable* current_symtab = nullptr; @@ -135,17 +135,17 @@ class PerfVisitor: public AstVisitor { bool symbol_to_skip(const std::shared_ptr& symbol); - bool is_local_variable(const std::shared_ptr& symbol); + bool is_local_variable(const std::shared_ptr& symbol) const; - bool is_constant_variable(const std::shared_ptr& symbol); + bool is_constant_variable(const std::shared_ptr& symbol) const; void count_variables(); - void measure_performance(ast::Ast* node); + void measure_performance(const ast::Ast& node); void print_memory_usage(); - void add_perf_to_printer(utils::PerfStat& perf); + void add_perf_to_printer(const utils::PerfStat& perf) const; public: PerfVisitor() = default; @@ -154,7 +154,7 @@ class PerfVisitor: public AstVisitor { void compact_json(bool flag); - utils::PerfStat get_total_perfstat() const noexcept { + const utils::PerfStat& get_total_perfstat() const noexcept { return total_perf; } @@ -178,88 +178,88 @@ class PerfVisitor: public AstVisitor { return num_state_variables; } - void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_binary_expression(const ast::BinaryExpression& node) override; - void visit_function_call(ast::FunctionCall& node) override; + void visit_function_call(const ast::FunctionCall& node) override; - void visit_name(ast::Name& node) override; + void visit_name(const ast::Name& node) override; - void visit_prime_name(ast::PrimeName& node) override; + void visit_prime_name(const ast::PrimeName& node) override; - void visit_solve_block(ast::SolveBlock& node) override; + void visit_solve_block(const ast::SolveBlock& node) override; - void visit_statement_block(ast::StatementBlock& node) override; + void visit_statement_block(const ast::StatementBlock& node) override; - void visit_unary_expression(ast::UnaryExpression& node) override; + void visit_unary_expression(const ast::UnaryExpression& node) override; - void visit_if_statement(ast::IfStatement& node) override; + void visit_if_statement(const ast::IfStatement& node) override; - void visit_else_if_statement(ast::ElseIfStatement& node) override; + void visit_else_if_statement(const ast::ElseIfStatement& node) override; - void visit_program(ast::Program& node) override; + void visit_program(const ast::Program& node) override; - void visit_plot_block(ast::PlotBlock& node) override; + void visit_plot_block(const ast::PlotBlock& node) override; /// skip initial block under net_receive block - void visit_initial_block(ast::InitialBlock& node) override; + void visit_initial_block(const ast::InitialBlock& node) override; - void visit_constructor_block(ast::ConstructorBlock& node) override; + void visit_constructor_block(const ast::ConstructorBlock& node) override; - void visit_destructor_block(ast::DestructorBlock& node) override; + void visit_destructor_block(const ast::DestructorBlock& node) override; - void visit_derivative_block(ast::DerivativeBlock& node) override; + void visit_derivative_block(const ast::DerivativeBlock& node) override; - void visit_linear_block(ast::LinearBlock& node) override; + void visit_linear_block(const ast::LinearBlock& node) override; - void visit_non_linear_block(ast::NonLinearBlock& node) override; + void visit_non_linear_block(const ast::NonLinearBlock& node) override; - void visit_discrete_block(ast::DiscreteBlock& node) override; + void visit_discrete_block(const ast::DiscreteBlock& node) override; - void visit_partial_block(ast::PartialBlock& node) override; + void visit_partial_block(const ast::PartialBlock& node) override; - void visit_function_table_block(ast::FunctionTableBlock& node) override; + void visit_function_table_block(const ast::FunctionTableBlock& node) override; - void visit_function_block(ast::FunctionBlock& node) override; + void visit_function_block(const ast::FunctionBlock& node) override; - void visit_procedure_block(ast::ProcedureBlock& node) override; + void visit_procedure_block(const ast::ProcedureBlock& node) override; - void visit_net_receive_block(ast::NetReceiveBlock& node) override; + void visit_net_receive_block(const ast::NetReceiveBlock& node) override; - void visit_breakpoint_block(ast::BreakpointBlock& node) override; + void visit_breakpoint_block(const ast::BreakpointBlock& node) override; - void visit_terminal_block(ast::TerminalBlock& node) override; + void visit_terminal_block(const ast::TerminalBlock& node) override; - void visit_before_block(ast::BeforeBlock& node) override; + void visit_before_block(const ast::BeforeBlock& node) override; - void visit_after_block(ast::AfterBlock& node) override; + void visit_after_block(const ast::AfterBlock& node) override; - void visit_ba_block(ast::BABlock& node) override; + void visit_ba_block(const ast::BABlock& node) override; - void visit_for_netcon(ast::ForNetcon& node) override; + void visit_for_netcon(const ast::ForNetcon& node) override; - void visit_kinetic_block(ast::KineticBlock& node) override; + void visit_kinetic_block(const ast::KineticBlock& node) override; - void visit_match_block(ast::MatchBlock& node) override; + void visit_match_block(const ast::MatchBlock& node) override; /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations - void visit_conductance_hint(ast::ConductanceHint& /*node*/) override {} + void visit_conductance_hint(const ast::ConductanceHint& /*node*/) override {} - void visit_local_list_statement(ast::LocalListStatement& /*node*/) override {} + void visit_local_list_statement(const ast::LocalListStatement& /*node*/) override {} - void visit_suffix(ast::Suffix& /*node*/) override {} + void visit_suffix(const ast::Suffix& /*node*/) override {} - void visit_useion(ast::Useion& /*node*/) override {} + void visit_useion(const ast::Useion& /*node*/) override {} - void visit_valence(ast::Valence& /*node*/) override {} + void visit_valence(const ast::Valence& /*node*/) override {} void print(std::ostream& ss) const { ss << stream.str(); } }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index dcaf026043..d48e48d982 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -16,9 +16,8 @@ namespace nmodl { namespace visitor { -using nmodl::utils::UseNumbersInString; -std::string RenameVisitor::new_name_generator(const std::string old_name) { +std::string RenameVisitor::new_name_generator(const std::string& old_name) { std::string new_name; if (add_random_suffix) { if (renamed_variables.find(old_name) != renamed_variables.end()) { @@ -46,7 +45,7 @@ std::string RenameVisitor::new_name_generator(const std::string old_name) { } /// rename matching variable -void RenameVisitor::visit_name(ast::Name& node) { +void RenameVisitor::visit_name(const ast::Name& node) { const auto& name = node.get_node_name(); if (std::regex_match(name, var_name_regex)) { std::string new_name = new_name_generator(name); @@ -63,40 +62,40 @@ void RenameVisitor::visit_name(ast::Name& node) { * macro. In practice this won't be an issue as we order is set * by parser. To be safe we are only renaming prime variable. */ -void RenameVisitor::visit_prime_name(ast::PrimeName& node) { +void RenameVisitor::visit_prime_name(const ast::PrimeName& node) { node.visit_children(*this); } /** * Parse verbatim blocks and rename variable if it is used. */ -void RenameVisitor::visit_verbatim(ast::Verbatim& node) { +void RenameVisitor::visit_verbatim(const ast::Verbatim& node) { if (!rename_verbatim) { return; } const auto& statement = node.get_statement(); - auto text = statement->eval(); + const auto& text = statement->eval(); parser::CDriver driver; driver.scan_string(text); auto tokens = driver.all_tokens(); - std::string result; + std::ostringstream result; for (auto& token: tokens) { if (std::regex_match(token, var_name_regex)) { /// Check if variable is already renamed and use the same naming otherwise add the /// new_name to the renamed_variables map - std::string new_name = new_name_generator(token); - result += new_name; + const std::string& new_name = new_name_generator(token); + result << new_name; logger->warn("RenameVisitor :: Renaming variable {} in VERBATIM block to {}", token, new_name); } else { - result += token; + result << token; } } - statement->set(result); + statement->set(result.str()); } } // namespace visitor diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index 393d2123bf..6979593749 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -23,8 +23,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -40,7 +40,7 @@ namespace visitor { * * \todo Add log/warning messages. */ -class RenameVisitor: public AstVisitor { +class RenameVisitor: public ConstAstVisitor { private: /// ast::Ast* node std::shared_ptr ast; @@ -92,23 +92,23 @@ class RenameVisitor: public AstVisitor { /// Check if variable is already renamed and use the same naming otherwise add the new_name /// to the renamed_variables map - std::string new_name_generator(const std::string old_name); + std::string new_name_generator(const std::string& old_name); void set(std::string old_name, std::string new_name) { var_name_regex = std::move(old_name); new_var_name = std::move(new_name); } - void enable_verbatim(bool state) { + void enable_verbatim(bool state) noexcept { rename_verbatim = state; } - void visit_name(ast::Name& node) override; - void visit_prime_name(ast::PrimeName& node) override; - void visit_verbatim(ast::Verbatim& node) override; + void visit_name(const ast::Name& node) override; + void visit_prime_name(const ast::PrimeName& node) override; + void visit_verbatim(const ast::Verbatim& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 69683b24bf..370ed713e8 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -11,7 +11,7 @@ #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" -#include "visitors/lookup_visitor.hpp" +#include "visitor_utils.hpp" namespace nmodl { namespace visitor { @@ -23,14 +23,14 @@ void SolveBlockVisitor::visit_breakpoint_block(ast::BreakpointBlock& node) { } /// check if given node contains sympy solution -static bool has_sympy_solution(ast::Ast& node) { - return !AstLookupVisitor().lookup(node, ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK).empty(); +static bool has_sympy_solution(const ast::Ast& node) { + return !collect_nodes(node, {ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK}).empty(); } /** * Create solution expression node that will be used for solve block - * @param solve_block solve block used to describe node to solve and method - * @return solution expression that will be used to replace the solve block + * \param solve_block solve block used to describe node to solve and method + * \return solution expression that will be used to replace the solve block * * Depending on the solver used, solve block is converted to solve expression statement * that will be used to replace solve block. Note that the blocks are clones instead of @@ -40,8 +40,8 @@ static bool has_sympy_solution(ast::Ast& node) { ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( ast::SolveBlock& solve_block) { /// find out the block that is going to solved - std::string block_name = solve_block.get_block_name()->get_node_name(); - auto solve_node_symbol = symtab->lookup(block_name); + const auto& block_name = solve_block.get_block_name()->get_node_name(); + const auto& solve_node_symbol = symtab->lookup(block_name); assert(solve_node_symbol != nullptr); auto node_to_solve = solve_node_symbol->get_node(); diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp index 6897daa3e1..4a677b398b 100644 --- a/src/nmodl/visitors/solve_block_visitor.hpp +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -19,8 +19,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -50,7 +50,7 @@ class SolveBlockVisitor: public AstVisitor { void visit_program(ast::Program& node) override; }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index e910b6e3a3..53bc5bcf4b 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -10,7 +10,6 @@ #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" #include "utils/logger.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { @@ -104,10 +103,10 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo void SteadystateVisitor::visit_program(ast::Program& node) { // get DERIVATIVE blocks - const auto& deriv_blocks = AstLookupVisitor().lookup(node, ast::AstNodeType::DERIVATIVE_BLOCK); + const auto& deriv_blocks = collect_nodes(node, {ast::AstNodeType::DERIVATIVE_BLOCK}); // get list of STEADYSTATE solve statements with names & methods - const auto& solve_block_nodes = AstLookupVisitor().lookup(node, ast::AstNodeType::SOLVE_BLOCK); + const auto& solve_block_nodes = collect_nodes(node, {ast::AstNodeType::SOLVE_BLOCK}); // create new DERIVATIVE blocks for the STEADYSTATE solves for (const auto& solve_block_ptr: solve_block_nodes) { diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 9d860df095..d883e37a3c 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -13,7 +13,6 @@ #include "pybind/pyembed.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace pywrap = nmodl::pybind_wrappers; @@ -39,10 +38,8 @@ using symtab::syminfo::NmodlType; * @param node Ast node for breakpoint block * @return true if it is safe to insert conductance statements otherwise false */ -static bool conductance_statement_possible(ast::BreakpointBlock& node) { - AstLookupVisitor v({AstNodeType::IF_STATEMENT, AstNodeType::VERBATIM}); - node.accept(v); - return v.get_nodes().empty(); +static bool conductance_statement_possible(const ast::BreakpointBlock& node) { + return collect_nodes(node, {AstNodeType::IF_STATEMENT, AstNodeType::VERBATIM}).empty(); } @@ -54,9 +51,9 @@ std::vector SympyConductanceVisitor::generate_statement_strings( // instead of passing all variables in the symbol table find out variables // that are used in the current block and then pass to sympy // name could be parameter or unit so check if it exist in symbol table - auto names_in_block = AstLookupVisitor().lookup(node, AstNodeType::NAME); + const auto& names_in_block = collect_nodes(node, {AstNodeType::NAME}); string_set used_names_in_block; - for (auto& name: names_in_block) { + for (const auto& name: names_in_block) { auto varname = name->get_node_name(); if (all_vars.find(varname) != all_vars.end()) { used_names_in_block.insert(varname); @@ -144,8 +141,10 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { for (const auto& ns_curr_ast: nonspecific_nodes) { logger->debug("SympyConductance :: Found NONSPECIFIC_CURRENT statement"); for (const auto& write_name: - std::dynamic_pointer_cast(ns_curr_ast).get()->get_currents()) { - std::string curr_write = write_name->get_node_name(); + std::dynamic_pointer_cast(ns_curr_ast) + .get() + ->get_currents()) { + const std::string& curr_write = write_name->get_node_name(); logger->debug("SympyConductance :: -> Adding non-specific current write name: {}", curr_write); i_name[curr_write] = ""; @@ -154,7 +153,7 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { } } -std::string SympyConductanceVisitor::to_nmodl_for_sympy(ast::Ast& node) { +std::string SympyConductanceVisitor::to_nmodl_for_sympy(const ast::Ast& node) { return to_nmodl(node, {ast::AstNodeType::UNIT, ast::AstNodeType::UNIT_DEF}); } @@ -162,7 +161,7 @@ std::string SympyConductanceVisitor::to_nmodl_for_sympy(ast::Ast& node) { void SympyConductanceVisitor::lookup_useion_statements() { // add USEION statements to i_name map between write vars and names for (const auto& useion_ast: use_ion_nodes) { - const auto& ion = std::dynamic_pointer_cast(useion_ast); + const auto& ion = std::dynamic_pointer_cast(useion_ast); const std::string& ion_name = ion->get_node_name(); logger->debug("SympyConductance :: Found USEION statement {}", to_nmodl_for_sympy(*ion)); if (i_ignore.find(ion_name) != i_ignore.end()) { @@ -238,9 +237,9 @@ void SympyConductanceVisitor::visit_breakpoint_block(ast::BreakpointBlock& node) void SympyConductanceVisitor::visit_program(ast::Program& node) { all_vars = get_global_vars(node); - AstLookupVisitor ast_lookup_visitor; - use_ion_nodes = ast_lookup_visitor.lookup(node, AstNodeType::USEION); - nonspecific_nodes = ast_lookup_visitor.lookup(node, AstNodeType::NONSPECIFIC); + const auto& program = node; + use_ion_nodes = collect_nodes(program, {AstNodeType::USEION}); + nonspecific_nodes = collect_nodes(program, {AstNodeType::NONSPECIFIC}); node.visit_children(*this); } diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index 73fe0eecf0..fb71adbf29 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -80,16 +80,16 @@ class SympyConductanceVisitor: public AstVisitor { std::map binary_expr_index; /// use ion ast nodes - std::vector> use_ion_nodes; + std::vector> use_ion_nodes; /// non specific currents - std::vector> nonspecific_nodes; + std::vector> nonspecific_nodes; std::vector generate_statement_strings(ast::BreakpointBlock& node); void lookup_useion_statements(); void lookup_nonspecific_statements(); - static std::string to_nmodl_for_sympy(ast::Ast& node); + static std::string to_nmodl_for_sympy(const ast::Ast& node); public: SympyConductanceVisitor() = default; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index cf8f014776..d75c82b6f5 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -13,7 +13,7 @@ #include "pybind/pyembed.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" -#include "visitors/lookup_visitor.hpp" +#include "utils/string_utils.hpp" #include "visitors/visitor_utils.hpp" @@ -47,8 +47,9 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { vars.insert(var_name); } } - AstLookupVisitor lv(ast::AstNodeType::FUNCTION_CALL); - for (const auto& call: lv.lookup(*node->get_statement_block())) { + const auto& fcall_nodes = collect_nodes(*node->get_statement_block(), + {ast::AstNodeType::FUNCTION_CALL}); + for (const auto& call: fcall_nodes) { function_calls.insert(call->get_node_name()); } } @@ -603,11 +604,10 @@ void SympySolverVisitor::visit_program(ast::Program& node) { global_vars = get_global_vars(node); // get list of solve statements with names & methods - AstLookupVisitor ast_lookup_visitor; - auto solve_block_nodes = ast_lookup_visitor.lookup(node, ast::AstNodeType::SOLVE_BLOCK); + const auto& solve_block_nodes = collect_nodes(node, {ast::AstNodeType::SOLVE_BLOCK}); for (const auto& block: solve_block_nodes) { - if (auto block_ptr = std::dynamic_pointer_cast(block)) { - const auto block_name = block_ptr->get_block_name()->get_value()->eval(); + if (auto block_ptr = std::dynamic_pointer_cast(block)) { + const auto& block_name = block_ptr->get_block_name()->get_value()->eval(); if (block_ptr->get_method()) { // Note: solve method name is an optional parameter // LINEAR and NONLINEAR blocks do not have solve method specified diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 1ce1455567..eb02987539 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -21,7 +21,6 @@ #include "ast/ast.hpp" #include "symtab/symbol.hpp" #include "visitors/ast_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 783239c9f2..42f4f418aa 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -35,7 +35,7 @@ void UnitsVisitor::visit_program(ast::Program& node) { * On \c nrnunits.lib constant "1" is defined as "fuzz", so it must be converted. */ void UnitsVisitor::visit_unit_def(ast::UnitDef& node) { - std::stringstream ss; + std::ostringstream ss; /* * In nrnunits.lib file "1" is defined as "fuzz", so there * must be a conversion to be able to parse "1" as unit @@ -80,8 +80,8 @@ void UnitsVisitor::visit_unit_def(ast::UnitDef& node) { * care of all the units calculations. */ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { - std::stringstream ss; - auto node_has_value_defined_in_modfile = node.get_value() != nullptr; + std::ostringstream ss; + const auto node_has_value_defined_in_modfile = node.get_value() != nullptr; if (node_has_value_defined_in_modfile) { /* * In nrnunits.lib file "1" is defined as "fuzz", so @@ -97,7 +97,7 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { // Parse the generated string for the defined unit using the units::UnitParser units_driver.parse_string(ss.str()); } else { - std::stringstream ss_unit1, ss_unit2; + std::ostringstream ss_unit1, ss_unit2; std::string unit1_name, unit2_name; /* * In nrnunits.lib file "1" is defined as "fuzz", so diff --git a/src/nmodl/visitors/units_visitor.hpp b/src/nmodl/visitors/units_visitor.hpp index a2f9d7e7f3..ea8157f667 100644 --- a/src/nmodl/visitors/units_visitor.hpp +++ b/src/nmodl/visitors/units_visitor.hpp @@ -24,8 +24,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -80,12 +80,12 @@ class UnitsVisitor: public AstVisitor { /// Get the parser::UnitDriver to be able to use it outside the visitor::UnitsVisitor /// scope keeping the same units::UnitTable - parser::UnitDriver get_unit_driver() const noexcept { + const parser::UnitDriver& get_unit_driver() const noexcept { return units_driver; } }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 4343b430d7..684628db0d 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -16,14 +16,14 @@ namespace nmodl { namespace visitor { /// rename matching variable -void VarUsageVisitor::visit_name(ast::Name& node) { +void VarUsageVisitor::visit_name(const ast::Name& node) { const auto& name = node.get_node_name(); if (name == var_name) { used = true; } } -bool VarUsageVisitor::variable_used(ast::Node& node, std::string name) { +bool VarUsageVisitor::variable_used(const ast::Node& node, std::string name) { used = false; var_name = std::move(name); node.visit_children(*this); diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index 7f59bc9501..23bd0efc04 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -21,8 +21,8 @@ namespace nmodl { namespace visitor { /** - * @addtogroup visitor_classes - * @{ + * \addtogroup visitor_classes + * \{ */ /** @@ -32,21 +32,21 @@ namespace visitor { * \todo Check if macro is considered as variable */ -class VarUsageVisitor: protected AstVisitor { +class VarUsageVisitor: protected ConstAstVisitor { private: /// variable to check usage std::string var_name; bool used = false; - void visit_name(ast::Name& node) override; + void visit_name(const ast::Name& node) override; public: VarUsageVisitor() = default; - bool variable_used(ast::Node& node, std::string name); + bool variable_used(const ast::Node& node, std::string name); }; -/** @} */ // end of visitor_classes +/** \} */ // end of visitor_classes } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 2be54672b2..298c5529c3 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -76,17 +76,17 @@ std::string VerbatimVarRenameVisitor::rename_variable(const std::string& name) { */ void VerbatimVarRenameVisitor::visit_verbatim(ast::Verbatim& node) { const auto& statement = node.get_statement(); - auto text = statement->eval(); + const auto& text = statement->eval(); parser::CDriver driver; driver.scan_string(text); - auto tokens = driver.all_tokens(); + const auto& tokens = driver.all_tokens(); - std::string result; + std::ostringstream oss; for (const auto& token: tokens) { - result += rename_variable(token); + oss << rename_variable(token); } - statement->set(result); + statement->set(oss.str()); } } // namespace visitor diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index a42a5de0b3..c038ca3a4c 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -15,7 +15,7 @@ namespace nmodl { namespace visitor { -void VerbatimVisitor::visit_verbatim(ast::Verbatim& node) { +void VerbatimVisitor::visit_verbatim(const ast::Verbatim& node) { std::string block; const auto& statement = node.get_statement(); if (statement) { diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index a488bfc174..70d91856b5 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -35,7 +35,7 @@ namespace visitor { * generating report of all verbatim blocks from all mod files * in ModelDB. */ -class VerbatimVisitor: public AstVisitor { +class VerbatimVisitor: public ConstAstVisitor { private: /// flag to enable/disable printing blocks as we visit them bool verbose = false; @@ -46,11 +46,11 @@ class VerbatimVisitor: public AstVisitor { public: VerbatimVisitor() = default; - explicit VerbatimVisitor(bool flag) { - verbose = flag; + explicit VerbatimVisitor(bool verbose) { + this->verbose = verbose; } - void visit_verbatim(ast::Verbatim& node) override; + void visit_verbatim(const ast::Verbatim& node) override; const std::vector& verbatim_blocks() const noexcept { return blocks; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index fb14866617..84a5e2e938 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -51,12 +51,13 @@ std::string suffix_random_string(const std::set& vars, std::string get_new_name(const std::string& name, const std::string& suffix, std::map& variables) { - int counter = 0; - if (variables.find(name) != variables.end()) { - counter = variables[name]; - } - variables[name] = counter + 1; - return (name + "_" + suffix + "_" + std::to_string(counter)); + auto it = variables.emplace(name, 0); + auto counter = it.first->second; + ++it.first->second; + + std::ostringstream oss; + oss << name << '_' << suffix << '_' << counter; + return oss.str(); } std::shared_ptr get_local_list_statement(const StatementBlock& node) { @@ -170,20 +171,32 @@ std::set get_global_vars(const Program& node) { } -bool calls_function(ast::Ast& node, const std::string& name) { - AstLookupVisitor lv(ast::AstNodeType::FUNCTION_CALL); - for (const auto& f: lv.lookup(node)) { - if (std::dynamic_pointer_cast(f)->get_node_name() == name) { - return true; - } - } - return false; +bool calls_function(const ast::Ast& node, const std::string& name) { + const auto& function_calls = collect_nodes(node, {ast::AstNodeType::FUNCTION_CALL}); + return std::any_of( + function_calls.begin(), + function_calls.end(), + [&name](const std::shared_ptr& f) { + return std::dynamic_pointer_cast(f)->get_node_name() == name; + }); } } // namespace visitor +std::vector> collect_nodes( + const ast::Ast& node, + const std::vector& types) { + visitor::ConstAstLookupVisitor visitor; + return visitor.lookup(node, types); +} + +std::vector> collect_nodes(ast::Ast& node, + const std::vector& types) { + visitor::AstLookupVisitor visitor; + return visitor.lookup(node, types); +} -std::string to_nmodl(ast::Ast& node, const std::set& exclude_types) { +std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types) { std::stringstream stream; visitor::NmodlPrintVisitor v(stream, exclude_types); node.accept(v); @@ -191,7 +204,7 @@ std::string to_nmodl(ast::Ast& node, const std::set& exclude_t } -std::string to_json(ast::Ast& node, bool compact, bool expand, bool add_nmodl) { +std::string to_json(const ast::Ast& node, bool compact, bool expand, bool add_nmodl) { std::stringstream stream; visitor::JSONVisitor v(stream); v.compact_json(compact); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index c01b560d90..3582271ac2 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -80,14 +80,23 @@ void remove_statements_from_block(ast::StatementBlock& block, std::set get_global_vars(const ast::Program& node); -/// Checks whether block contains a call to a perticular function -bool calls_function(ast::Ast& node, const std::string& name); +/// Checks whether block contains a call to a particular function +bool calls_function(const ast::Ast& node, const std::string& name); } // namespace visitor +/// traverse \a node recursively and collect nodes of given \a types +std::vector> collect_nodes( + const ast::Ast& node, + const std::vector& types = {}); + +/// traverse \a node recursively and collect nodes of given \a types +std::vector> collect_nodes( + ast::Ast& node, + const std::vector& types = {}); /// Given AST node, return the NMODL string representation -std::string to_nmodl(ast::Ast& node, const std::set& exclude_types = {}); +std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types = {}); /// Given a shared pointer to an AST node, return the NMODL string representation template @@ -98,7 +107,7 @@ typename std::enable_if::value, std::string>::type } /// Given AST node, return the JSON string representation -std::string to_json(ast::Ast& node, +std::string to_json(const ast::Ast& node, bool compact = false, bool expand = false, bool add_nmodl = false); diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 983d6b868f..f166a1e3be 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -16,7 +16,6 @@ #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" -#include "visitors/lookup_visitor.hpp" /** @file diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 66511bf752..33b2fa2102 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -19,10 +19,10 @@ #include "test/unit/utils/nmodl_constructs.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" -#include "visitors/lookup_visitor.hpp" +#include "visitors/visitor_utils.hpp" + using namespace nmodl::test_utils; -using nmodl::visitor::AstLookupVisitor; //============================================================================= // Parser tests //============================================================================= @@ -170,7 +170,7 @@ SCENARIO("Check parents in valid NMODL constructs") { GIVEN(construct.second.name) { THEN("Check the parents in : " + construct.second.input) { // check the parents - REQUIRE(!nmodl::visitor::test::CheckParentVisitor().check_ast(ast.get())); + REQUIRE(!nmodl::visitor::test::CheckParentVisitor().check_ast(*ast)); } } } @@ -213,9 +213,9 @@ void parse_neuron_block_string(const std::string& name, nmodl::ModToken& value) driver.parse_string(name); const auto& ast_program = driver.get_ast(); - const auto& neuron_blocks = AstLookupVisitor().lookup(*ast_program->get_shared_ptr(), - nmodl::ast::AstNodeType::NEURON_BLOCK); - value = *(neuron_blocks[0]->get_token()); + const auto& neuron_blocks = nmodl::collect_nodes(*ast_program->get_shared_ptr(), + {nmodl::ast::AstNodeType::NEURON_BLOCK}); + value = *(neuron_blocks.front()->get_token()); } SCENARIO("Check if a NEURON block is parsed with correct location info in its token") { diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index 4ccdf38a77..ad130766b0 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -17,17 +17,27 @@ def test_lookup_visitor(ch_ast): assert eq_str == "m' = mInf-m" +def test_lookup_visitor_any_node(): + """Ensure the AstLookupVisitor.lookup methods accept any node""" + lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.INTEGER) + int42 = ast.Integer(42, None) + + eqs = lookup_visitor.lookup(int42) + assert len(eqs) == 1 + + eqs = lookup_visitor.lookup(int42, ast.AstNodeType.DOUBLE) + assert len(eqs) == 0 + + def test_lookup_visitor_constructor(ch_ast): lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.DIFF_EQ_EXPRESSION) - ch_ast.accept(lookup_visitor) - eqs = lookup_visitor.get_nodes() + eqs = lookup_visitor.lookup(ch_ast) eq_str = nmodl.dsl.to_nmodl(eqs[0]) def test_json_visitor(ch_ast): lookup_visitor = visitor.AstLookupVisitor(ast.AstNodeType.PRIME_NAME) - ch_ast.accept(lookup_visitor) - primes = lookup_visitor.get_nodes() + primes = lookup_visitor.lookup(ch_ast) # test compact json prime_str = nmodl.dsl.to_nmodl(primes[0]) diff --git a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp index afd6d5a108..4f6c26d5aa 100644 --- a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -37,7 +37,7 @@ std::string run_constant_folding_visitor(const std::string& text) { NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index f3dfadde17..85143b869e 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -13,7 +13,6 @@ #include "visitors/checkparent_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; @@ -36,17 +35,17 @@ std::vector run_defuse_visitor(const std::string& text, const std::stri InlineVisitor().visit_program(*ast); std::vector chains; - DefUseAnalyzeVisitor v(ast->get_symbol_table()); + DefUseAnalyzeVisitor v(*ast->get_symbol_table()); /// analyse only derivative blocks in this test - auto blocks = AstLookupVisitor().lookup(*ast, AstNodeType::DERIVATIVE_BLOCK); + const auto& blocks = nmodl::collect_nodes(*ast, {AstNodeType::DERIVATIVE_BLOCK}); chains.reserve(blocks.size()); - for (auto& block: blocks) { + for (const auto& block: blocks) { chains.push_back(v.analyze(*block, variable)); } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return chains; } diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 014e48a765..8fbc4a3199 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -11,7 +11,6 @@ #include "parser/nmodl_driver.hpp" #include "test/unit/utils/nmodl_constructs.hpp" #include "visitors/global_var_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 307e58fa9c..9cf6e7f3a2 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -38,7 +38,7 @@ std::string run_inline_visitor(const std::string& text) { // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 4a05b20ea8..8d164ec5db 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -13,7 +13,6 @@ #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -50,16 +49,15 @@ std::vector run_kinetic_block_visitor(const std::string& text) { KineticBlockVisitor().visit_program(*ast); // run lookup visitor to extract DERIVATIVE block(s) from AST - AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(*ast, AstNodeType::DERIVATIVE_BLOCK); - results.reserve(res.size()); - for (const auto& r: res) { - results.push_back(to_nmodl(r)); + const auto& blocks = collect_nodes(*ast, {AstNodeType::DERIVATIVE_BLOCK}); + results.reserve(blocks.size()); + for (const auto& block: blocks) { + results.push_back(to_nmodl(block)); } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return results; } diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index b734fdd136..3c3c8c77ed 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -11,7 +11,6 @@ #include "parser/nmodl_driver.hpp" #include "test/unit/utils/nmodl_constructs.hpp" #include "visitors/local_to_assigned_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp index d7e460bc83..db2b89b909 100644 --- a/test/nmodl/transpiler/unit/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -38,7 +38,7 @@ std::string run_localize_visitor(const std::string& text) { NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index 7ad31179eb..e80820842c 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -10,7 +10,6 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/visitor_utils.hpp" using namespace nmodl; @@ -26,11 +25,6 @@ using symtab::syminfo::NmodlType; // Ast lookup visitor tests //============================================================================= -std::vector> run_lookup_visitor(ast::Program& node, - const std::vector& types) { - return AstLookupVisitor().lookup(node, types); -} - SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") { auto to_ast = [](const std::string& text) { NmodlDriver driver; @@ -56,40 +50,36 @@ SCENARIO("Searching for ast nodes using AstLookupVisitor", "[visitor][lookup]") WHEN("Looking for existing nodes") { THEN("Can find RANGE variables") { - std::vector types{AstNodeType::RANGE_VAR}; - auto result = run_lookup_visitor(*ast, types); + const auto& result = collect_nodes(*ast, {AstNodeType::RANGE_VAR}); REQUIRE(result.size() == 2); REQUIRE(to_nmodl(result[0]) == "tau"); REQUIRE(to_nmodl(result[1]) == "h"); } THEN("Can find NEURON block") { - AstLookupVisitor v(AstNodeType::NEURON_BLOCK); - ast->accept(v); - auto nodes = v.get_nodes(); + const auto& nodes = collect_nodes(*ast, {AstNodeType::NEURON_BLOCK}); REQUIRE(nodes.size() == 1); - std::string neuron_block = R"( + const std::string neuron_block = R"( NEURON { RANGE tau, h })"; - auto result = reindent_text(to_nmodl(nodes[0])); - auto expected = reindent_text(neuron_block); + const auto& result = reindent_text(to_nmodl(nodes[0])); + const auto& expected = reindent_text(neuron_block); REQUIRE(result == expected); } THEN("Can find Binary Expressions and function call") { - std::vector types{AstNodeType::BINARY_EXPRESSION, - AstNodeType::FUNCTION_CALL}; - auto result = run_lookup_visitor(*ast, types); + const auto& result = + collect_nodes(*ast, + {AstNodeType::BINARY_EXPRESSION, AstNodeType::FUNCTION_CALL}); REQUIRE(result.size() == 4); } } WHEN("Looking for missing nodes") { THEN("Can not find BREAKPOINT block") { - std::vector types{AstNodeType::BREAKPOINT_BLOCK}; - auto result = run_lookup_visitor(*ast, types); + const auto& result = collect_nodes(*ast, {AstNodeType::BREAKPOINT_BLOCK}); REQUIRE(result.empty()); } } diff --git a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index 6baa4a2300..20a5d359cb 100644 --- a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -39,7 +39,7 @@ std::string run_loop_unroll_visitor(const std::string& text) { ConstantFolderVisitor().visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return to_nmodl(ast, {AstNodeType::DEFINE}); } diff --git a/test/nmodl/transpiler/unit/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp index f3249f70fe..36d4ad5856 100644 --- a/test/nmodl/transpiler/unit/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -39,15 +39,15 @@ void run_visitor_passes(const std::string& text) { v1.visit_program(*ast); v2.visit_program(*ast); v3.visit_program(*ast); - v4.visit_program(*ast); - v4.visit_program(*ast); + v4.check_ast(*ast); + v4.check_ast(*ast); v1.visit_program(*ast); v1.visit_program(*ast); - v4.visit_program(*ast); + v4.check_ast(*ast); v2.visit_program(*ast); v3.visit_program(*ast); v2.visit_program(*ast); - v4.visit_program(*ast); + v4.check_ast(*ast); } } diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index ec2c807de7..bd7eed2c30 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -38,7 +38,7 @@ std::string run_cnexp_solve_visitor(const std::string& text) { NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 8366eaeb12..9dda94663d 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -34,7 +34,7 @@ std::string run_nmodl_visitor(const std::string& text) { NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } @@ -42,8 +42,8 @@ std::string run_nmodl_visitor(const std::string& text) { SCENARIO("Convert AST back to NMODL form", "[visitor][nmodl]") { for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; - std::string input_nmodl_text = reindent_text(test_case.input); - std::string output_nmodl_text = reindent_text(test_case.output); + const std::string& input_nmodl_text = reindent_text(test_case.input); + const std::string& output_nmodl_text = reindent_text(test_case.output); GIVEN(test_case.name) { THEN("Visitor successfully returns : " + input_nmodl_text) { auto result = run_nmodl_visitor(input_nmodl_text); diff --git a/test/nmodl/transpiler/unit/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp index 68206f4db7..352038ad0f 100644 --- a/test/nmodl/transpiler/unit/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -41,7 +41,7 @@ static std::string run_var_rename_visitor( NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index f58285b5b0..ff594b44b3 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -38,7 +38,7 @@ std::string run_solve_block_visitor(const std::string& text) { NmodlPrintVisitor(stream).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return stream.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index 5d9f1afcf4..2398a7023d 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -13,7 +13,6 @@ #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/steadystate_visitor.hpp" #include "visitors/symtab_visitor.hpp" @@ -59,14 +58,14 @@ std::vector run_steadystate_visitor( SteadystateVisitor().visit_program(*ast); // run lookup visitor to extract results from AST - auto res = AstLookupVisitor().lookup(*ast, ret_nodetypes); + const auto& res = collect_nodes(*ast, ret_nodetypes); results.reserve(res.size()); for (const auto& r: res) { results.push_back(to_nmodl(r)); } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return results; } diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 2ab9c5250b..c416c66249 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -14,7 +14,6 @@ #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" #include "visitors/symtab_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -50,12 +49,11 @@ std::string run_sympy_conductance_visitor(const std::string& text) { SympyConductanceVisitor().visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); // run lookup visitor to extract results from AST - AstLookupVisitor v_lookup; // return BREAKPOINT block as JSON string - return reindent_text(to_nmodl(v_lookup.lookup(*ast, AstNodeType::BREAKPOINT_BLOCK)[0])); + return reindent_text(to_nmodl(collect_nodes(*ast, {AstNodeType::BREAKPOINT_BLOCK}).front())); } std::string breakpoint_to_nmodl(const std::string& text) { @@ -67,9 +65,8 @@ std::string breakpoint_to_nmodl(const std::string& text) { SymtabVisitor().visit_program(*ast); // run lookup visitor to extract results from AST - AstLookupVisitor v_lookup; // return BREAKPOINT block as JSON string - return reindent_text(to_nmodl(v_lookup.lookup(*ast, AstNodeType::BREAKPOINT_BLOCK)[0])); + return reindent_text(to_nmodl(collect_nodes(*ast, {AstNodeType::BREAKPOINT_BLOCK}).front())); } void run_sympy_conductance_passes(ast::Program& node) { diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 0852fb035f..cc983d7335 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -12,7 +12,6 @@ #include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" @@ -55,13 +54,11 @@ std::vector run_sympy_solver_visitor( SympySolverVisitor(pade, cse).visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); // run lookup visitor to extract results from AST - AstLookupVisitor v_lookup; - auto res = v_lookup.lookup(*ast, ret_nodetype); - for (const auto& r: res) { - results.push_back(to_nmodl(r)); + for (const auto& eq: collect_nodes(*ast, {ret_nodetype})) { + results.push_back(to_nmodl(eq)); } return results; diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 2f1b03b31d..345ddb0a5e 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -15,7 +15,6 @@ #include "test/unit/utils/nmodl_constructs.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" -#include "visitors/lookup_visitor.hpp" #include "visitors/units_visitor.hpp" using namespace nmodl; @@ -50,14 +49,14 @@ std::string run_units_visitor(const std::string& text) { // Visit AST to find all the ast::UnitDef nodes to print their // unit names, factors and dimensions as they are calculated in // the units::UnitTable - auto unit_defs = AstLookupVisitor().lookup(*ast, ast::AstNodeType::UNIT_DEF); + const auto& unit_defs = collect_nodes(*ast, {ast::AstNodeType::UNIT_DEF}); for (const auto& unit_def: unit_defs) { auto unit_name = unit_def->get_node_name(); unit_name.erase(remove_if(unit_name.begin(), unit_name.end(), isspace), unit_name.end()); auto unit = units_driver.table->get_unit(unit_name); - ss << std::fixed << std::setprecision(8) << unit->get_name() << ' '; - ss << unit->get_factor() << ':'; + ss << std::fixed << std::setprecision(8) << unit->get_name() << ' ' << unit->get_factor() + << ':'; // Dimensions of the unit are printed to check that the units are successfully // parsed to the units::UnitTable int dimension_id = 0; @@ -65,8 +64,7 @@ std::string run_units_visitor(const std::string& text) { for (const auto& dimension: unit->get_dimensions()) { if (dimension != 0) { constant = false; - ss << ' ' << units_driver.table->get_base_unit_name(dimension_id); - ss << dimension; + ss << ' ' << units_driver.table->get_base_unit_name(dimension_id) << dimension; } dimension_id++; } @@ -79,11 +77,11 @@ std::string run_units_visitor(const std::string& text) { // Visit AST to find all the ast::FactorDef nodes to print their // unit names, factors and dimensions as they are calculated to // be printed to the produced .cpp file - auto factor_defs = AstLookupVisitor().lookup(*ast, ast::AstNodeType::FACTOR_DEF); + const auto& factor_defs = collect_nodes(*ast, {ast::AstNodeType::FACTOR_DEF}); for (const auto& factor_def: factor_defs) { auto unit = units_driver.table->get_unit(factor_def->get_node_name()); ss << std::fixed << std::setprecision(8) << unit->get_name() << ' '; - auto factor_def_class = dynamic_cast(factor_def.get()); + auto factor_def_class = std::dynamic_pointer_cast(factor_def); ss << factor_def_class->get_value()->eval() << ':'; // Dimensions of the unit are printed to check that the units are successfully // parsed to the units::UnitTable @@ -92,7 +90,7 @@ std::string run_units_visitor(const std::string& text) { for (const auto& dimension: unit->get_dimensions()) { if (dimension != 0) { constant = false; - ss << " " << units_driver.table->get_base_unit_name(dimension_id); + ss << ' ' << units_driver.table->get_base_unit_name(dimension_id); ss << dimension; } dimension_id++; @@ -104,7 +102,7 @@ std::string run_units_visitor(const std::string& text) { } // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return ss.str(); } diff --git a/test/nmodl/transpiler/unit/visitor/verbatim.cpp b/test/nmodl/transpiler/unit/visitor/verbatim.cpp index 24d39a9e02..6389a48828 100644 --- a/test/nmodl/transpiler/unit/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/unit/visitor/verbatim.cpp @@ -30,14 +30,14 @@ std::vector run_verbatim_visitor(const std::string& text) { v.visit_program(*ast); // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().visit_program(*ast); + CheckParentVisitor().check_ast(*ast); return v.verbatim_blocks(); } TEST_CASE("Parse VERBATIM block using Verbatim Visitor") { SECTION("Single Block") { - std::string text = "VERBATIM int a; ENDVERBATIM"; + const std::string text = "VERBATIM int a; ENDVERBATIM"; auto blocks = run_verbatim_visitor(text); REQUIRE(blocks.size() == 1); From 1e7dc4f174585e65174bf7bcb8372b46622dda8b Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Thu, 13 Aug 2020 06:20:55 +0200 Subject: [PATCH 289/871] Set CodegenCVisitor::optimize_ion_variable_copies by a CLI flag (BlueBrain/nmodl#389) * Set CodegenCVisitor::optimize_ion_variable_copies as CLI flag * CodegenCVisitor::optimize_ion_variable_copies was hard-coded as true. Now it can be set by a CLI flag Fix issue BlueBrain/nmodl#388 NMODL Repo SHA: BlueBrain/nmodl@f438807733f083502245de98bb21c0c923554609 --- src/nmodl/codegen/codegen_acc_visitor.hpp | 10 +++++---- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- src/nmodl/codegen/codegen_c_visitor.hpp | 25 ++++++++++++++++------ src/nmodl/codegen/codegen_cuda_visitor.hpp | 11 ++++++---- src/nmodl/codegen/codegen_ispc_visitor.hpp | 20 +++++++++++------ src/nmodl/codegen/codegen_omp_visitor.hpp | 10 +++++---- src/nmodl/nmodl/main.cpp | 23 ++++++++++++++------ 7 files changed, 70 insertions(+), 31 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 20d692b262..9b95a36fed 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -89,14 +89,16 @@ class CodegenAccVisitor: public CodegenCVisitor { CodegenAccVisitor(const std::string& mod_file, const std::string& output_dir, LayoutType layout, - const std::string& float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type) {} + const std::string& float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies) {} CodegenAccVisitor(const std::string& mod_file, std::ostream& stream, LayoutType layout, - const std::string& float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) {} + const std::string& float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 617bc81422..f9b91f57b4 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1201,7 +1201,7 @@ bool CodegenCVisitor::channel_task_dependency_enabled() { bool CodegenCVisitor::optimize_ion_variable_copies() const { - return true; + return optimize_ionvar_copies; } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 62232412f9..a1153a754d 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -204,6 +204,11 @@ class CodegenCVisitor: public visitor::AstVisitor { */ bool codegen = false; + /** + * Flag to indicate if visitor should avoid ion variable copies + */ + bool optimize_ionvar_copies = true; + /** * Variable name should be converted to instance name (but not for function arguments) */ @@ -996,7 +1001,7 @@ class CodegenCVisitor: public visitor::AstVisitor { /** - * Check if ion variable are copies avoided + * Check if ion variable copies should be avoided */ bool optimize_ion_variable_copies() const; @@ -1594,6 +1599,7 @@ class CodegenCVisitor: public visitor::AstVisitor { const std::string& output_dir, LayoutType layout, const std::string& float_type, + const bool optimize_ionvar_copies, const std::string& extension, const std::string& wrapper_ext) : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) @@ -1601,7 +1607,8 @@ class CodegenCVisitor: public visitor::AstVisitor { , printer(target_printer) , mod_filename(mod_filename) , layout(layout) - , float_type(float_type) {} + , float_type(float_type) + , optimize_ionvar_copies(optimize_ionvar_copies) {} public: @@ -1627,12 +1634,14 @@ class CodegenCVisitor: public visitor::AstVisitor { const std::string& output_dir, LayoutType layout, const std::string& float_type, + const bool optimize_ionvar_copies, const std::string& extension = ".cpp") : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , printer(target_printer) , mod_filename(mod_filename) , layout(layout) - , float_type(float_type) {} + , float_type(float_type) + , optimize_ionvar_copies(optimize_ionvar_copies) {} /** * \copybrief nmodl::codegen::CodegenCVisitor @@ -1654,12 +1663,14 @@ class CodegenCVisitor: public visitor::AstVisitor { CodegenCVisitor(const std::string& mod_filename, std::ostream& stream, LayoutType layout, - const std::string& float_type) + const std::string& float_type, + const bool optimize_ionvar_copies) : target_printer(new CodePrinter(stream)) , printer(target_printer) , mod_filename(mod_filename) , layout(layout) - , float_type(float_type) {} + , float_type(float_type) + , optimize_ionvar_copies(optimize_ionvar_copies) {} /** @@ -1683,12 +1694,14 @@ class CodegenCVisitor: public visitor::AstVisitor { CodegenCVisitor(std::string mod_filename, LayoutType layout, std::string float_type, + const bool optimize_ionvar_copies, std::shared_ptr& target_printer) : target_printer(target_printer) , printer(target_printer) , mod_filename(mod_filename) , layout(layout) - , float_type(float_type) {} + , float_type(float_type) + , optimize_ionvar_copies(optimize_ionvar_copies) {} /** diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 5fb7f4856a..b7d309a6b3 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -107,14 +107,17 @@ class CodegenCudaVisitor: public CodegenCVisitor { CodegenCudaVisitor(const std::string& mod_file, const std::string& output_dir, LayoutType layout, - const std::string& float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".cu") {} + const std::string& float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies, ".cu") { + } CodegenCudaVisitor(const std::string& mod_file, std::ostream& stream, LayoutType layout, - const std::string& float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) {} + const std::string& float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 7326746a16..32be6ad704 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -218,17 +218,25 @@ class CodegenIspcVisitor: public CodegenCVisitor { CodegenIspcVisitor(const std::string& mod_file, const std::string& output_dir, LayoutType layout, - const std::string& float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, ".ispc", ".cpp") - , fallback_codegen(mod_file, layout, float_type, wrapper_printer) {} + const std::string& float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, + output_dir, + layout, + float_type, + optimize_ionvar_copies, + ".ispc", + ".cpp") + , fallback_codegen(mod_file, layout, float_type, optimize_ionvar_copies, wrapper_printer) {} CodegenIspcVisitor(const std::string& mod_file, std::ostream& stream, LayoutType layout, - const std::string& float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) - , fallback_codegen(mod_file, layout, float_type, wrapper_printer) {} + const std::string& float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) + , fallback_codegen(mod_file, layout, float_type, optimize_ionvar_copies, wrapper_printer) {} void visit_function_call(ast::FunctionCall& node) override; void visit_var_name(ast::VarName& node) override; diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index efc2e39daf..8bb09e3a87 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -73,14 +73,16 @@ class CodegenOmpVisitor: public CodegenCVisitor { CodegenOmpVisitor(std::string mod_file, std::string output_dir, LayoutType layout, - std::string float_type) - : CodegenCVisitor(mod_file, output_dir, layout, float_type) {} + std::string float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies) {} CodegenOmpVisitor(std::string mod_file, std::stringstream& stream, LayoutType layout, - std::string float_type) - : CodegenCVisitor(mod_file, stream, layout, float_type) {} + std::string float_type, + const bool optimize_ionvar_copies) + : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 4807320c00..fd47b29664 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -122,10 +122,13 @@ int main(int argc, const char* argv[]) { /// true if verbatim blocks bool verbatim_rename(true); - /// true if code generation is forced to happed even if there + /// true if code generation is forced to happen even if there /// is any incompatibility bool force_codegen(false); + /// true if ion variable copies should be avoided + bool optimize_ionvar_copies_codegen(true); + /// directory where code will be generated std::string output_dir("."); @@ -251,6 +254,9 @@ int main(int argc, const char* argv[]) { codegen_opt->add_flag("--force", force_codegen, "Force code generation even if there is any incompatibility"); + codegen_opt->add_flag("--opt-ionvar-copy", + optimize_ionvar_copies_codegen, + "Optimize copies of ion variables ({})"_format(optimize_ionvar_copies_codegen))->ignore_case(); // clang-format on @@ -488,31 +494,36 @@ int main(int argc, const char* argv[]) { if (ispc_backend) { logger->info("Running ISPC backend code generator"); - CodegenIspcVisitor visitor(modfile, output_dir, mem_layout, data_type); + CodegenIspcVisitor visitor( + modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } else if (oacc_backend) { logger->info("Running OpenACC backend code generator"); - CodegenAccVisitor visitor(modfile, output_dir, mem_layout, data_type); + CodegenAccVisitor visitor( + modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } else if (omp_backend) { logger->info("Running OpenMP backend code generator"); - CodegenOmpVisitor visitor(modfile, output_dir, mem_layout, data_type); + CodegenOmpVisitor visitor( + modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } else if (c_backend) { logger->info("Running C backend code generator"); - CodegenCVisitor visitor(modfile, output_dir, mem_layout, data_type); + CodegenCVisitor visitor( + modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } if (cuda_backend) { logger->info("Running CUDA backend code generator"); - CodegenCudaVisitor visitor(modfile, output_dir, mem_layout, data_type); + CodegenCudaVisitor visitor( + modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } } From e15877f8112770b0cf293b4ecb6305b3bf80ab37 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 14 Aug 2020 13:27:40 +0200 Subject: [PATCH 290/871] default codegen --opt-ionvar-copy=FALSE (BlueBrain/nmodl#393) Fix issue BlueBrain/nmodl#392 NMODL Repo SHA: BlueBrain/nmodl@e110eb232b41c8267bb2afbfc9fc5d3c07dbfae3 --- src/nmodl/nmodl/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index fd47b29664..bfb822eeb3 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -127,7 +127,7 @@ int main(int argc, const char* argv[]) { bool force_codegen(false); /// true if ion variable copies should be avoided - bool optimize_ionvar_copies_codegen(true); + bool optimize_ionvar_copies_codegen(false); /// directory where code will be generated std::string output_dir("."); From b22bb133e71ccac15cab457b5099cd3f0508f787 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 24 Aug 2020 13:30:41 +0200 Subject: [PATCH 291/871] Various fixes with OpenACC backend for watch and derivimplicit (BlueBrain/nmodl#382) * Various fixes with OpenACC backend for watch & derivimplicit - artificial cells shouldn't use cuda memory allocation routines (but on cpu) - artificial cells shouldn't be compiled for GPU - use backend specific abort routine as abort is not supported on gpus - transfer routine to copy newtonspace on gpu * Azure CI changes - use python 3.7 - use delocate on OS X instead of auditwheel * Add test for watch statement fixes BlueBrain/nmodl#379, fixes BlueBrain/nmodl#115, fixes BlueBrain/nmodl#375 NMODL Repo SHA: BlueBrain/nmodl@964fbde69bd48d042066126495d05a44be085e33 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 56 +++++++++- src/nmodl/codegen/codegen_acc_visitor.hpp | 7 ++ src/nmodl/codegen/codegen_c_visitor.cpp | 101 ++++++++++++++---- src/nmodl/codegen/codegen_c_visitor.hpp | 18 ++++ src/nmodl/codegen/codegen_info.cpp | 22 ++++ src/nmodl/codegen/codegen_info.hpp | 3 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 1 + .../transpiler/integration/mod/watch_test.mod | 57 ++++++++++ 8 files changed, 244 insertions(+), 21 deletions(-) create mode 100644 test/nmodl/transpiler/integration/mod/watch_test.mod diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 76d4577eab..76c53584fc 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -58,9 +58,18 @@ void CodegenAccVisitor::print_atomic_reduction_pragma() { void CodegenAccVisitor::print_backend_includes() { - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); + /** + * Artificial cells are executed on CPU. As Random123 is allocated on GPU by default, + * we have to disable GPU allocations using `DISABLE_OPENACC` macro. + */ + if (info.artificial_cell) { + printer->add_line("#undef DISABLE_OPENACC"); + printer->add_line("#define DISABLE_OPENACC"); + } else { + printer->add_line("#include "); + printer->add_line("#include "); + printer->add_line("#include "); + } } @@ -70,6 +79,11 @@ std::string CodegenAccVisitor::backend_name() const { void CodegenAccVisitor::print_memory_allocation_routine() const { + // memory for artificial cells should be allocated on CPU + if (info.artificial_cell) { + CodegenCVisitor::print_memory_allocation_routine(); + return; + } printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); @@ -85,6 +99,23 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { printer->add_line("}"); } +/** + * OpenACC kernels running on GPU doesn't support `abort()`. CUDA/OpenACC supports + * `assert()` in device kernel that can be used for similar purpose. Also, `printf` + * is supported on device. + * + * @todo : we need to implement proper error handling mechanism to propogate errors + * from GPU to CPU. For example, error code can be returned like original + * neuron implementation. For now we use `assert(0==1)` pattern which is + * used for OpenACC/CUDA. + */ +void CodegenAccVisitor::print_abort_routine() const { + printer->add_newline(2); + printer->add_line("static inline void coreneuron_abort() {"); + printer->add_line(" printf(\"Error : Issue while running OpenACC kernel \\n\");"); + printer->add_line(" assert(0==1);"); + printer->add_line("}"); +} /** * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded @@ -166,12 +197,14 @@ void CodegenAccVisitor::print_global_variable_device_create_annotation() { } } + void CodegenAccVisitor::print_global_variable_device_update_annotation() { if (!info.artificial_cell) { printer->add_line("#pragma acc update device ({}_global)"_format(info.mod_suffix)); } } + std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& variable, const std::string& type) const { if (info.artificial_cell) { @@ -180,5 +213,22 @@ std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& va return "({}) acc_deviceptr({})"_format(type, variable); } + +void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { + int list_num = info.derivimplicit_list_num; + printer->add_line("if (nt->compute_gpu) {"); + printer->add_line(" auto device_vec = static_cast(acc_copyin(vec, vec_size));"); + printer->add_line(" auto device_ns = static_cast(acc_deviceptr(ns));"); + printer->add_line(" auto device_thread = static_cast(acc_deviceptr(thread));"); + printer->add_line( + " acc_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns, sizeof(void*));"_format( + info.thread_data_index - 1)); + printer->add_line( + " acc_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec, sizeof(double*));"_format( + list_num)); + printer->add_line("}"); +} + + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 9b95a36fed..18869bfed0 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -49,6 +49,10 @@ class CodegenAccVisitor: public CodegenCVisitor { void print_memory_allocation_routine() const override; + /// abort routine + void print_abort_routine() const override; + + /// annotations like "acc enter data present(...)" for main kernel void print_kernel_data_present_annotation_block_begin() override; @@ -81,6 +85,9 @@ class CodegenAccVisitor: public CodegenCVisitor { /// update global variable from host to the device void print_global_variable_device_update_annotation() override; + /// transfer newtonspace structure to device + void print_newtonspace_transfer_to_device() const override; + std::string get_variable_device_pointer(const std::string& variable, const std::string& type) const override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index f9b91f57b4..d75511f820 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1222,6 +1222,14 @@ void CodegenCVisitor::print_memory_allocation_routine() const { } +void CodegenCVisitor::print_abort_routine() const { + printer->add_newline(2); + printer->add_line("static inline void coreneuron_abort() {"); + printer->add_line(" abort();"); + printer->add_line("}"); +} + + std::string CodegenCVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); @@ -1256,8 +1264,8 @@ std::string CodegenCVisitor::k_const() { void CodegenCVisitor::visit_watch_statement(ast::WatchStatement& node) { - printer->add_text( - "nrn_watch_activate(inst, id, pnodecount, {})"_format(current_watch_statement++)); + printer->add_text("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)"_format( + current_watch_statement++)); } @@ -1488,7 +1496,7 @@ void CodegenCVisitor::print_table_check_function(Block& node) { printer->add_line("save_{} = {};"_format(name, instance_name)); } } - printer->end_block(); + printer->end_block(1); } printer->end_block(1); } @@ -1563,7 +1571,6 @@ void CodegenCVisitor::print_check_table_thread_function() { printer->add_line(" setup_instance(nt, ml);"); printer->add_line(" {0}* inst = ({0}*) ml->instance;"_format(instance_struct())); printer->add_line(" double v = 0;"); - printer->add_line(" IonCurVar ionvar;"); for (const auto& function: info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); @@ -1601,7 +1608,7 @@ void CodegenCVisitor::print_function_or_procedure(ast::Block& node, const std::s } -void CodegenCVisitor::print_procedure(ast::ProcedureBlock& node) { +void CodegenCVisitor::print_function_procedure_helper(ast::Block& node) { codegen = true; auto name = node.get_node_name(); @@ -1618,20 +1625,31 @@ void CodegenCVisitor::print_procedure(ast::ProcedureBlock& node) { } +void CodegenCVisitor::print_procedure(ast::ProcedureBlock& node) { + print_function_procedure_helper(node); +} + + void CodegenCVisitor::print_function(ast::FunctionBlock& node) { - codegen = true; auto name = node.get_node_name(); - auto return_var = "ret_" + name; + + // name of return variable + std::string return_var; + if (info.function_uses_table(name)) { + return_var = "ret_f_" + name; + } else { + return_var = "ret_" + name; + } // first rename return variable name auto block = node.get_statement_block().get(); RenameVisitor v(name, return_var); block->accept(v); - print_function_or_procedure(node, name); - codegen = false; + print_function_procedure_helper(node); } + std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) const { auto& singleton_random_string_class = utils::SingletonRandomString<4>::instance(); std::string unique_name = original_name; @@ -2655,6 +2673,11 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("hoc_register_dparam_semantics({});"_format(args)); } + if (info.is_watch_used()) { + auto watch_fun = compute_method_name(BlockType::Watch); + printer->add_line("hoc_register_watch_check({}, mech_type);"_format(watch_fun)); + } + if (info.write_concentration) { printer->add_line("nrn_writes_conc(mech_type, 0);"); } @@ -3180,6 +3203,11 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { std::string method = compute_method_name(type); auto args = "NrnThread* nt, Memb_list* ml, int type"; + // watch statement function doesn't have type argument + if (type == BlockType::Watch) { + args = "NrnThread* nt, Memb_list* ml"; + } + print_global_method_annotation(); printer->start_block("void {}({})"_format(method, args)); print_kernel_data_present_annotation_block_begin(); @@ -3222,9 +3250,15 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { int list_num = info.derivimplicit_list_num; // clang-format off printer->add_line("*deriv{}_advance(thread) = 0;"_format(list_num)); - printer->add_line("if (*newtonspace{}(thread) == NULL) {}"_format(list_num, "{")); - printer->add_line(" *newtonspace{}(thread) = nrn_cons_newtonspace({}, pnodecount);"_format(list_num, nequation)); - printer->add_line(" thread[dith{}()].pval = makevector(2*{}*pnodecount*sizeof(double));"_format(list_num, nequation)); + printer->add_line("auto ns = newtonspace{}(thread);"_format(list_num)); + printer->add_line("auto& th = thread[dith{}()];"_format(list_num)); + + printer->add_line("if (*ns == nullptr) {"); + printer->add_line(" int vec_size = 2*{}*pnodecount*sizeof(double);"_format(nequation)); + printer->add_line(" double* vec = makevector(vec_size);"_format(nequation)); + printer->add_line(" th.pval = vec;"_format(list_num)); + printer->add_line(" *ns = nrn_cons_newtonspace({}, pnodecount);"_format(nequation)); + print_newtonspace_transfer_to_device(); printer->add_line("}"); // clang-format on } @@ -3281,14 +3315,16 @@ void CodegenCVisitor::print_watch_activate() { auto inst = "{}* inst"_format(instance_struct()); printer->start_block( - "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id) "_format(inst)); + "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, double v, bool &watch_remove) "_format( + inst)); // initialize all variables only during first watch statement - printer->add_line("if (watch_id == 0) {"); + printer->add_line("if (watch_remove == false) {"); for (int i = 0; i < info.watch_count; i++) { auto name = get_variable_name("watch{}"_format(i + 1)); printer->add_line(" {} = 0;"_format(name)); } + printer->add_line(" watch_remove = true;"); printer->add_line("}"); /** @@ -3301,10 +3337,10 @@ void CodegenCVisitor::print_watch_activate() { auto varname = get_variable_name("watch{}"_format(i + 1)); printer->add_indent(); - printer->add_text("{} = 2 + "_format(varname)); + printer->add_text("{} = 2 + ("_format(varname)); auto watch = statement->get_statements().front(); watch->get_expression()->visit_children(*this); - printer->add_text(";"); + printer->add_text(");"); printer->add_newline(); printer->end_block(1); @@ -3330,6 +3366,11 @@ void CodegenCVisitor::print_watch_check() { print_channel_iteration_block_begin(BlockType::Watch); print_post_channel_iteration_common_code(); + if (info.is_voltage_used_by_watch_statements()) { + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + } + for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; auto watch = statement->get_statements().front(); @@ -3380,6 +3421,8 @@ void CodegenCVisitor::print_watch_check() { print_channel_iteration_block_end(); print_send_event_move(); + print_channel_iteration_tiling_block_end(); + print_kernel_data_present_annotation_block_end(); printer->end_block(1); codegen = false; } @@ -3670,7 +3713,7 @@ void CodegenCVisitor::print_net_send_buffering() { printer->add_line("nsb->_cnt++;"); printer->add_line("if(nsb->_cnt >= nsb->_size) {"); printer->add_line(" printf({}, nsb->_cnt);"_format(error)); - printer->add_line(" abort();"); + printer->add_line(" coreneuron_abort();"); printer->add_line("}"); printer->add_line("nsb->_sendtype[i] = type;"); printer->add_line("nsb->_vdata_index[i] = vdata_index;"); @@ -3720,7 +3763,20 @@ void CodegenCVisitor::print_net_receive_kernel() { if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); } + + // set voltage variable if it is used in the block (e.g. for WATCH statement) + auto v_used = VarUsageVisitor().variable_used(*node->get_statement_block(), "v"); + if (v_used) { + printer->add_line("int node_id = ml->nodeindices[id];"); + printer->add_line("v = nt->_actual_v[node_id];"); + } + printer->add_line("{} = t;"_format(get_variable_name("tsave"))); + + if (info.is_watch_used()) { + printer->add_line("bool watch_remove = false;"); + } + printer->add_indent(); node->get_statement_block()->accept(*this); printer->add_newline(); @@ -3822,11 +3878,13 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); + codegen = true; print_statement_block(*block->get_statement_block(), false, false); + codegen = false; printer->add_line("int counter = -1;"); printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); - printer->add_line(" dlist{0}[(++counter){1}] = data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/dt;"_format(list_num + 1, stride, list_num)); + printer->add_line(" dlist{0}[(++counter){1}] = data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;"_format(list_num + 1, stride, list_num)); printer->add_line(" }"); printer->add_line(" else {"); printer->add_line(" dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];"_format(list_num + 1, stride, list_num)); @@ -3837,6 +3895,12 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { // clang-format on } + +void CodegenCVisitor::print_newtonspace_transfer_to_device() const { + // nothing to do on cpu +} + + void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& node) { if (!codegen) { return; @@ -4199,6 +4263,7 @@ void CodegenCVisitor::print_codegen_routines() { print_global_variables_for_hoc(); print_common_getters(); print_memory_allocation_routine(); + print_abort_routine(); print_thread_memory_callbacks(); print_global_variable_setup(); print_instance_variable_setup(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index a1153a754d..cd49429fea 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -959,6 +959,12 @@ class CodegenCVisitor: public visitor::AstVisitor { virtual void print_memory_allocation_routine() const; + /** + * Print backend specific abort routine + */ + virtual void print_abort_routine() const; + + /** * Print standard C/C++ includes */ @@ -1312,6 +1318,12 @@ class CodegenCVisitor: public visitor::AstVisitor { void print_function_or_procedure(ast::Block& node, const std::string& name); + /** + * Common helper function to help printing function or procedure blocks + * \param node the AST node representing the function or procedure in NMODL + */ + void print_function_procedure_helper(ast::Block& node); + /** * Print thread related memory allocation and deallocation callbacks */ @@ -1414,6 +1426,12 @@ class CodegenCVisitor: public visitor::AstVisitor { void print_derivimplicit_kernel(ast::Block* block); + /** + * Print code block to transfer newtonspace structure to device + */ + virtual void print_newtonspace_transfer_to_device() const; + + /** * Print block / loop for statement requiring reduction * diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 0ab1b0b169..cbf4a0dcf3 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -8,12 +8,15 @@ #include "codegen/codegen_info.hpp" #include "ast/all.hpp" +#include "visitors/var_usage_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { namespace codegen { +using visitor::VarUsageVisitor; + /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() const { for (const auto& ion: ions) { @@ -101,5 +104,24 @@ bool CodegenInfo::nrn_state_has_eigen_solver_block() const { return !collect_nodes(*nrn_state_block, {ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK}).empty(); } +/** + * Check if WatchStatement uses voltage variable v + * + * Watch statement has condition expression which could use voltage + * variable `v`. To avoid memory access into voltage array we check + * if `v` is used and then print necessary code. + * + * @return true if voltage variable b is used otherwise false + */ +bool CodegenInfo::is_voltage_used_by_watch_statements() const { + for (const auto& statement: watch_statements) { + auto v_used = VarUsageVisitor().variable_used(*statement, "v"); + if (v_used) { + return true; + } + } + return false; +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index b2af5702a1..ef818f7417 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -385,6 +385,9 @@ struct CodegenInfo { /// true if EigenNewtonSolver is used in nrn_state block bool nrn_state_has_eigen_solver_block() const; + /// true if WatchStatement uses voltage v variable + bool is_voltage_used_by_watch_statements() const; + /// if we need a call back to wrote_conc in neuron/coreneuron bool require_wrote_conc = false; }; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 303e13d74d..1fbb374e7b 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -828,6 +828,7 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { print_memory_allocation_routine(); print_thread_memory_callbacks(); + print_abort_routine(); print_global_variable_setup(); /* this is a godawful mess.. the global variables have to be copied over into the fallback * such that they are available to the fallback generator. diff --git a/test/nmodl/transpiler/integration/mod/watch_test.mod b/test/nmodl/transpiler/integration/mod/watch_test.mod new file mode 100644 index 0000000000..0eb08d3e1b --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/watch_test.mod @@ -0,0 +1,57 @@ +NEURON { + POINT_PROCESS watchtest + NONSPECIFIC_CURRENT i + GLOBAL ena, ek, erev, gna, gk, gpas + RANGE e, g +} + +UNITS { + (mV) = (millivolt) + (nA) = (nanoamp) + (umho) = (micromho) +} + +PARAMETER { + ena = 50 (mV) + ek = -80 (mV) + erev = -65 (mV) + gna = 0.1 (umho) + gk = 0.03 (umho) + gpas = 0.0001 (umho) +} + +ASSIGNED { + v (mV) + i (nA) + e (mV) + g (umho) +} + +DEFINE init 1 +DEFINE rise 2 +DEFINE fall 3 +DEFINE off 4 + +INITIAL { + g = gpas + e = erev + net_send(0, init) +} + +BREAKPOINT { + i = g*(v - e) +} + +NET_RECEIVE(w) { + if (flag == init) { + WATCH (v > -55) rise + }else if (flag == rise) { + g = gna + e = ena + WATCH (v > 10) fall + }else if (flag == fall) { + g = gk + e = ek + WATCH (v < -70) off + } +} From bc54ab94423d833a3f0718be12cd6458e1effdce Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 27 Aug 2020 11:36:29 +0200 Subject: [PATCH 292/871] Fix issue with const-ness of ion variables (BlueBrain/nmodl#395) * Fix issue with const-ness of ion variables - per mechanism ion variables needs to be updated from values calculated by neuron/coreneuron (in eion.cpp) - with the changes in BlueBrain/nmodl#389 and BlueBrain/nmodl#393, read variables can not be declared as constant - in this PR we avoid const declaration for ION variables fixes BlueBrain/nmodl#394 NMODL Repo SHA: BlueBrain/nmodl@9c95e966fd37f9220ed5dec87be70903bf2dd731 --- src/nmodl/codegen/codegen_c_visitor.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index d75511f820..b7c87e61e9 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -731,7 +731,11 @@ bool CodegenCVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { - if (symbol->has_any_property(NmodlType::param_assign) && symbol->get_write_count() == 0) { + // per mechanism ion variables needs to be updated from neuron/coreneuron values + if (info.is_ion_variable(name)) { + is_constant = false; + } else if (symbol->has_any_property(NmodlType::param_assign) && + symbol->get_write_count() == 0) { is_constant = true; } } From 79e725b1b59108f68f93c78dd47ab4c041ce0a17 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 31 Aug 2020 15:45:32 +0200 Subject: [PATCH 293/871] fixup! typo in pybind translation (BlueBrain/nmodl#397) NMODL Repo SHA: BlueBrain/nmodl@861e5d9ad9e09e1367b8dc87cd770e0f6aba6740 --- src/nmodl/language/templates/pybind/pysymtab.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 14848e0ea4..e051a0a180 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -161,7 +161,7 @@ void init_symtab_module(py::module& m) { .value("extern_method", syminfo::NmodlType::extern_method) .value("extern_neuron_variable", syminfo::NmodlType::extern_neuron_variable) .value("extern_var", syminfo::NmodlType::extern_var) - .value("vactor_def", syminfo::NmodlType::factor_def) + .value("vector_def", syminfo::NmodlType::factor_def) .value("function_block", syminfo::NmodlType::function_block) .value("function_table_block", syminfo::NmodlType::function_table_block) .value("global_var", syminfo::NmodlType::global_var) @@ -169,7 +169,7 @@ void init_symtab_module(py::module& m) { .value("linear_block", syminfo::NmodlType::linear_block) .value("local_var", syminfo::NmodlType::local_var) .value("man_linear_block", syminfo::NmodlType::non_linear_block) - .value("nonspecifig_cur_var", syminfo::NmodlType::nonspecific_cur_var) + .value("nonspecific_cur_var", syminfo::NmodlType::nonspecific_cur_var) .value("param_assign", syminfo::NmodlType::param_assign) .value("partial_block", syminfo::NmodlType::partial_block) .value("pointer_var", syminfo::NmodlType::pointer_var) From 0649198fd33bdf1609b644e6c747ca086240ef8e Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 21 Sep 2020 20:20:14 +0200 Subject: [PATCH 294/871] fix typos : ElctrodeCurrent to ElectrodeCurrent (BlueBrain/nmodl#398) * fixup : ElctrodeCurrent -> ElectrodeCurrent Useful since ast_decl.hpp enum is exposed to the python API * fix bison install for travis Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@7755d9c10e039f8556086d53b31d4378d7bd7090 --- .travis.yml | 2 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 2 +- src/nmodl/language/code_generator.cmake | 2 +- src/nmodl/language/nmodl.yaml | 2 +- src/nmodl/parser/nmodl.yy | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8ead1f765b..7da6963e5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,6 +60,7 @@ addons: # for Mac builds, we use Homebrew homebrew: packages: + - bison - boost - cmake - flex @@ -75,7 +76,6 @@ before_install: # unlink python2 and use python3 as it's required for nmodl # install older bison because of #314 - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/187c0d5/Formula/bison.rb; export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; brew unlink python@2; brew link --overwrite python@3.8; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 040001cdbc..8bde54ce89 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -424,7 +424,7 @@ void CodegenHelperVisitor::visit_suffix(Suffix& node) { } -void CodegenHelperVisitor::visit_elctrode_current(ElctrodeCurrent& node) { +void CodegenHelperVisitor::visit_electrode_current(ElectrodeCurrent& node) { info.electrode_current = true; } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 8eec7028ec..3ea11f2fd6 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -80,7 +80,7 @@ class CodegenHelperVisitor: public visitor::AstVisitor { /// run visitor and return information for code generation codegen::CodegenInfo analyze(ast::Program& node); - void visit_elctrode_current(ast::ElctrodeCurrent& node) override; + void visit_electrode_current(ast::ElectrodeCurrent& node) override; void visit_suffix(ast::Suffix& node) override; void visit_function_call(ast::FunctionCall& node) override; void visit_binary_expression(ast::BinaryExpression& node) override; diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 9169f894f2..400b969a23 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -82,8 +82,8 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/double_unit.hpp ${PROJECT_BINARY_DIR}/src/ast/eigen_linear_solver_block.hpp ${PROJECT_BINARY_DIR}/src/ast/eigen_newton_solver_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/elctrode_current.hpp ${PROJECT_BINARY_DIR}/src/ast/electrode_cur_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/electrode_current.hpp ${PROJECT_BINARY_DIR}/src/ast/else_if_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/else_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/expression.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 3313b3f236..8c8e5cd083 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1989,7 +1989,7 @@ separator: ", " brief: "Represents NONSPECIFIC_CURRENT variables statement in NMODL" - - ElctrodeCurrent: + - ElectrodeCurrent: nmodl: "ELECTRODE_CURRENT " members: - currents: diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 7cf74354a9..01e7e5d8f5 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -2281,7 +2281,7 @@ neuron_statement : } | neuron_statement ELECTRODE_CURRENT electrode_current_var_list { - $1.emplace_back(new ast::ElctrodeCurrent($3)); + $1.emplace_back(new ast::ElectrodeCurrent($3)); $$ = $1; } | neuron_statement SECTION section_var_list From 975ee6038483c72bf821b09850a49b116f3da74a Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 21 Sep 2020 20:22:32 +0200 Subject: [PATCH 295/871] Add symtab get_variables and get_variables_with_properties defaults. Fix BlueBrain/nmodl#403 (BlueBrain/nmodl#400) * Add symtab get_variables and get_variables_with_properties defaults - these 2 functions can be called without properties as inputs to collect all the symbols - use syminfo::NmodlType::empty property fixes BlueBrain/nmodl#399 NMODL Repo SHA: BlueBrain/nmodl@2a27da6c8e7bf657943d38b3809e5352edd2c5e2 --- .travis.yml | 1 + .../language/templates/pybind/pysymtab.cpp | 5 ++++- src/nmodl/symtab/symbol_table.hpp | 21 +++++++++++++++++-- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7da6963e5d..7313981c9f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,6 +64,7 @@ addons: - boost - cmake - flex + - bison - openmpi - python@3 update: true diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index e051a0a180..a2caf35782 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -214,7 +214,10 @@ void init_symtab_module(py::module& m) { symbol_table.def("name", &SymbolTable::name) .def("title", &SymbolTable::title) .def("is_method_defined", &SymbolTable::is_method_defined) - .def("get_variables", &SymbolTable::get_variables) + .def("get_variables", + &SymbolTable::get_variables, + py::arg("with") = syminfo::NmodlType::empty, + py::arg("without") = syminfo::NmodlType::empty) .def("get_variables_with_properties", &SymbolTable::get_variables_with_properties, py::arg("properties"), diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 0029da120d..ec6ed4b3a4 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -131,9 +131,26 @@ class SymbolTable { return parent ? parent->name() : "None"; } - std::vector> get_variables(syminfo::NmodlType with, - syminfo::NmodlType without); + /** + * get variables + * + * \param with variables with properties. 0 matches everything + * \param without variables without properties. 0 matches nothing + * + * The two different behaviors for 0 depend on the fact that we get + * get variables with ALL the with properties and without ANY of the + * without properties + */ + std::vector> get_variables( + syminfo::NmodlType with = syminfo::NmodlType::empty, + syminfo::NmodlType without = syminfo::NmodlType::empty); + /** + * get variables with properties + * + * \param properties variables with properties. -1 matches everything + * \param all all/any + */ std::vector> get_variables_with_properties( syminfo::NmodlType properties, bool all = false) const; From d4251cbcaeffb5798935326e787597c989dde1ab Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 23 Sep 2020 17:32:18 +0200 Subject: [PATCH 296/871] Fixes issue with net_send_buffering declared and defined after net_init (BlueBrain/nmodl#405) NMODL Repo SHA: BlueBrain/nmodl@40bf20b0a3e3e641011867051e9a6c2866f5aa64 --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index b7c87e61e9..3aa8bfc01e 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -4243,8 +4243,8 @@ void CodegenCVisitor::print_compute_functions() { auto block = callback->get_node_to_solve().get(); print_derivimplicit_kernel(block); } - print_net_init(); print_net_send_buffering(); + print_net_init(); print_watch_activate(); print_watch_check(); print_net_receive_kernel(); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 1fbb374e7b..1d7ee0aa21 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -852,8 +852,8 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { print_check_table_thread_function(); - print_net_init(); print_net_send_buffering(); + print_net_init(); print_watch_activate(); fallback_codegen.print_watch_check(); // requires C style variable declarations and loops From c74d4e03986103b16ec731af10152af83fb4a5d1 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 5 Oct 2020 13:07:26 +0200 Subject: [PATCH 297/871] Handle after_cvode solver and at_time declaration (BlueBrain/nmodl#406) - Change `after_cvode` solver to `cnexp` - Add `nt` variable in `at_time` function NMODL Repo SHA: BlueBrain/nmodl@528043dde011fac17e0e1d7222a9cf5371c0fcab --- .travis.yml | 1 - src/nmodl/codegen/codegen_c_visitor.cpp | 4 + .../codegen/codegen_compatibility_visitor.hpp | 3 +- src/nmodl/codegen/codegen_naming.hpp | 3 + src/nmodl/lexer/token_mapping.cpp | 244 +++++++++--------- src/nmodl/lexer/token_mapping.hpp | 6 + src/nmodl/nmodl/main.cpp | 8 + src/nmodl/visitors/CMakeLists.txt | 2 + .../visitors/after_cvode_to_cnexp_visitor.cpp | 32 +++ .../visitors/after_cvode_to_cnexp_visitor.hpp | 54 ++++ test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../unit/visitor/after_cvode_to_cnexp.cpp | 66 +++++ 12 files changed, 306 insertions(+), 118 deletions(-) create mode 100644 src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp create mode 100644 src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp diff --git a/.travis.yml b/.travis.yml index 7313981c9f..ef86233fb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,6 @@ addons: # for Mac builds, we use Homebrew homebrew: packages: - - bison - boost - cmake - flex diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 3aa8bfc01e..dbb8495c90 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -15,6 +15,7 @@ #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "config/config.h" +#include "lexer/token_mapping.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" @@ -1332,6 +1333,9 @@ void CodegenCVisitor::print_function_call(FunctionCall& node) { if (!arguments.empty()) { printer->add_text(", "); } + } else if (nmodl::details::needs_neuron_thread_first_arg(function_name) && + arguments.front()->get_node_name() != "nt") { + arguments.insert(arguments.begin(), std::make_shared("nt")); } print_vector_elements(arguments, ", "); diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 5008ee19ad..bbc7c32e67 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -56,7 +56,8 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { const std::set handled_solvers{codegen::naming::CNEXP_METHOD, codegen::naming::EULER_METHOD, codegen::naming::DERIVIMPLICIT_METHOD, - codegen::naming::SPARSE_METHOD}; + codegen::naming::SPARSE_METHOD, + codegen::naming::AFTER_CVODE_METHOD}; /// Vector that stores all the ast::Node that are unhandled /// by the NMODL \c C++ code generator diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index f76a48ae05..7d2aabe7bb 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -29,6 +29,9 @@ const std::string EULER_METHOD("euler"); /// cnexp method in nmodl const std::string CNEXP_METHOD("cnexp"); +/// cvode method in nmodl +const std::string AFTER_CVODE_METHOD("after_cvode"); + /// sparse method in nmodl const std::string SPARSE_METHOD("sparse"); diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index b3908ea1e9..fdcfaccf4b 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -11,6 +11,7 @@ #include "ast/ast.hpp" #include "lexer/modl.h" +#include "lexer/token_mapping.hpp" #include "parser/nmodl/nmodl_parser.hpp" namespace nmodl { @@ -33,97 +34,98 @@ namespace details { * in multiple context and hence we are keeping original names. * Once we finish code generation part then we change this. */ -static std::map keywords = {{"VERBATIM", Token::VERBATIM}, - {"COMMENT", Token::BLOCK_COMMENT}, - {"TITLE", Token::MODEL}, - {"CONSTANT", Token::CONSTANT}, - {"PARAMETER", Token::PARAMETER}, - {"INDEPENDENT", Token::INDEPENDENT}, - {"ASSIGNED", Token::ASSIGNED}, - {"INITIAL", Token::INITIAL1}, - {"TERMINAL", Token::TERMINAL}, - {"DERIVATIVE", Token::DERIVATIVE}, - {"EQUATION", Token::BREAKPOINT}, - {"BREAKPOINT", Token::BREAKPOINT}, - {"CONDUCTANCE", Token::CONDUCTANCE}, - {"SOLVE", Token::SOLVE}, - {"STATE", Token::STATE}, - {"STEPPED", Token::STEPPED}, - {"LINEAR", Token::LINEAR}, - {"NONLINEAR", Token::NONLINEAR}, - {"DISCRETE", Token::DISCRETE}, - {"FUNCTION", Token::FUNCTION1}, - {"FUNCTION_TABLE", Token::FUNCTION_TABLE}, - {"PROCEDURE", Token::PROCEDURE}, - {"PARTIAL", Token::PARTIAL}, - {"DEL2", Token::DEL2}, - {"DEL", Token::DEL}, - {"LOCAL", Token::LOCAL}, - {"METHOD", Token::USING}, - {"STEADYSTATE", Token::STEADYSTATE}, - {"SENS", Token::SENS}, - {"STEP", Token::STEP}, - {"WITH", Token::WITH}, - {"FROM", Token::FROM}, - {"FORALL", Token::FORALL1}, - {"TO", Token::TO}, - {"BY", Token::BY}, - {"if", Token::IF}, - {"else", Token::ELSE}, - {"while", Token::WHILE}, - {"START", Token::START1}, - {"DEFINE", Token::DEFINE1}, - {"KINETIC", Token::KINETIC}, - {"CONSERVE", Token::CONSERVE}, - {"PLOT", Token::PLOT}, - {"VS", Token::VS}, - {"LAG", Token::LAG}, - {"RESET", Token::RESET}, - {"MATCH", Token::MATCH}, - {"MODEL_LEVEL", Token::MODEL_LEVEL}, - {"SWEEP", Token::SWEEP}, - {"FIRST", Token::FIRST}, - {"LAST", Token::LAST}, - {"COMPARTMENT", Token::COMPARTMENT}, - {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, - {"PUTQ", Token::PUTQ}, - {"GETQ", Token::GETQ}, - {"IFERROR", Token::IFERROR}, - {"SOLVEFOR", Token::SOLVEFOR}, - {"UNITS", Token::UNITS}, - {"UNITSON", Token::UNITSON}, - {"UNITSOFF", Token::UNITSOFF}, - {"TABLE", Token::TABLE}, - {"DEPEND", Token::DEPEND}, - {"NEURON", Token::NEURON}, - {"SUFFIX", Token::SUFFIX}, - {"POINT_PROCESS", Token::SUFFIX}, - {"ARTIFICIAL_CELL", Token::SUFFIX}, - {"NONSPECIFIC_CURRENT", Token::NONSPECIFIC}, - {"ELECTRODE_CURRENT", Token::ELECTRODE_CURRENT}, - {"SECTION", Token::SECTION}, - {"RANGE", Token::RANGE}, - {"USEION", Token::USEION}, - {"READ", Token::READ}, - {"WRITE", Token::WRITE}, - {"VALENCE", Token::VALENCE}, - {"CHARGE", Token::VALENCE}, - {"GLOBAL", Token::GLOBAL}, - {"POINTER", Token::POINTER}, - {"BBCOREPOINTER", Token::BBCOREPOINTER}, - {"EXTERNAL", Token::EXTERNAL}, - {"INCLUDE", Token::INCLUDE1}, - {"CONSTRUCTOR", Token::CONSTRUCTOR}, - {"DESTRUCTOR", Token::DESTRUCTOR}, - {"NET_RECEIVE", Token::NETRECEIVE}, - {"BEFORE", Token::BEFORE}, - {"AFTER", Token::AFTER}, - {"WATCH", Token::WATCH}, - {"FOR_NETCONS", Token::FOR_NETCONS}, - {"THREADSAFE", Token::THREADSAFE}, - {"PROTECT", Token::PROTECT}, - {"MUTEXLOCK", Token::NRNMUTEXLOCK}, - {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; +const static std::map keywords = { + {"VERBATIM", Token::VERBATIM}, + {"COMMENT", Token::BLOCK_COMMENT}, + {"TITLE", Token::MODEL}, + {"CONSTANT", Token::CONSTANT}, + {"PARAMETER", Token::PARAMETER}, + {"INDEPENDENT", Token::INDEPENDENT}, + {"ASSIGNED", Token::ASSIGNED}, + {"INITIAL", Token::INITIAL1}, + {"TERMINAL", Token::TERMINAL}, + {"DERIVATIVE", Token::DERIVATIVE}, + {"EQUATION", Token::BREAKPOINT}, + {"BREAKPOINT", Token::BREAKPOINT}, + {"CONDUCTANCE", Token::CONDUCTANCE}, + {"SOLVE", Token::SOLVE}, + {"STATE", Token::STATE}, + {"STEPPED", Token::STEPPED}, + {"LINEAR", Token::LINEAR}, + {"NONLINEAR", Token::NONLINEAR}, + {"DISCRETE", Token::DISCRETE}, + {"FUNCTION", Token::FUNCTION1}, + {"FUNCTION_TABLE", Token::FUNCTION_TABLE}, + {"PROCEDURE", Token::PROCEDURE}, + {"PARTIAL", Token::PARTIAL}, + {"DEL2", Token::DEL2}, + {"DEL", Token::DEL}, + {"LOCAL", Token::LOCAL}, + {"METHOD", Token::USING}, + {"STEADYSTATE", Token::STEADYSTATE}, + {"SENS", Token::SENS}, + {"STEP", Token::STEP}, + {"WITH", Token::WITH}, + {"FROM", Token::FROM}, + {"FORALL", Token::FORALL1}, + {"TO", Token::TO}, + {"BY", Token::BY}, + {"if", Token::IF}, + {"else", Token::ELSE}, + {"while", Token::WHILE}, + {"START", Token::START1}, + {"DEFINE", Token::DEFINE1}, + {"KINETIC", Token::KINETIC}, + {"CONSERVE", Token::CONSERVE}, + {"PLOT", Token::PLOT}, + {"VS", Token::VS}, + {"LAG", Token::LAG}, + {"RESET", Token::RESET}, + {"MATCH", Token::MATCH}, + {"MODEL_LEVEL", Token::MODEL_LEVEL}, + {"SWEEP", Token::SWEEP}, + {"FIRST", Token::FIRST}, + {"LAST", Token::LAST}, + {"COMPARTMENT", Token::COMPARTMENT}, + {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, + {"PUTQ", Token::PUTQ}, + {"GETQ", Token::GETQ}, + {"IFERROR", Token::IFERROR}, + {"SOLVEFOR", Token::SOLVEFOR}, + {"UNITS", Token::UNITS}, + {"UNITSON", Token::UNITSON}, + {"UNITSOFF", Token::UNITSOFF}, + {"TABLE", Token::TABLE}, + {"DEPEND", Token::DEPEND}, + {"NEURON", Token::NEURON}, + {"SUFFIX", Token::SUFFIX}, + {"POINT_PROCESS", Token::SUFFIX}, + {"ARTIFICIAL_CELL", Token::SUFFIX}, + {"NONSPECIFIC_CURRENT", Token::NONSPECIFIC}, + {"ELECTRODE_CURRENT", Token::ELECTRODE_CURRENT}, + {"SECTION", Token::SECTION}, + {"RANGE", Token::RANGE}, + {"USEION", Token::USEION}, + {"READ", Token::READ}, + {"WRITE", Token::WRITE}, + {"VALENCE", Token::VALENCE}, + {"CHARGE", Token::VALENCE}, + {"GLOBAL", Token::GLOBAL}, + {"POINTER", Token::POINTER}, + {"BBCOREPOINTER", Token::BBCOREPOINTER}, + {"EXTERNAL", Token::EXTERNAL}, + {"INCLUDE", Token::INCLUDE1}, + {"CONSTRUCTOR", Token::CONSTRUCTOR}, + {"DESTRUCTOR", Token::DESTRUCTOR}, + {"NET_RECEIVE", Token::NETRECEIVE}, + {"BEFORE", Token::BEFORE}, + {"AFTER", Token::AFTER}, + {"WATCH", Token::WATCH}, + {"FOR_NETCONS", Token::FOR_NETCONS}, + {"THREADSAFE", Token::THREADSAFE}, + {"PROTECT", Token::PROTECT}, + {"MUTEXLOCK", Token::NRNMUTEXLOCK}, + {"MUTEXUNLOCK", Token::NRNMUTEXUNLOCK}}; /** @@ -155,25 +157,25 @@ struct MethodInfo { * * \todo MethodInfo::subtype should be changed from integer flag to proper type */ -static std::map methods = {{"adams", MethodInfo(DERF | KINF, 0)}, - {"runge", MethodInfo(DERF | KINF, 0)}, - {"euler", MethodInfo(DERF | KINF, 0)}, - {"adeuler", MethodInfo(DERF | KINF, 1)}, - {"heun", MethodInfo(DERF | KINF, 0)}, - {"adrunge", MethodInfo(DERF | KINF, 1)}, - {"gear", MethodInfo(DERF | KINF, 1)}, - {"newton", MethodInfo(NLINF, 0)}, - {"simplex", MethodInfo(NLINF, 0)}, - {"simeq", MethodInfo(LINF, 0)}, - {"seidel", MethodInfo(LINF, 0)}, - {"_advance", MethodInfo(KINF, 0)}, - {"sparse", MethodInfo(KINF, 0)}, - {"derivimplicit", MethodInfo(DERF, 0)}, - {"cnexp", MethodInfo(DERF, 0)}, - {"clsoda", MethodInfo(DERF | KINF, 1)}, - {"after_cvode", MethodInfo(0, 0)}, - {"cvode_t", MethodInfo(0, 0)}, - {"cvode_t_v", MethodInfo(0, 0)}}; +const static std::map methods = {{"adams", MethodInfo(DERF | KINF, 0)}, + {"runge", MethodInfo(DERF | KINF, 0)}, + {"euler", MethodInfo(DERF | KINF, 0)}, + {"adeuler", MethodInfo(DERF | KINF, 1)}, + {"heun", MethodInfo(DERF | KINF, 0)}, + {"adrunge", MethodInfo(DERF | KINF, 1)}, + {"gear", MethodInfo(DERF | KINF, 1)}, + {"newton", MethodInfo(NLINF, 0)}, + {"simplex", MethodInfo(NLINF, 0)}, + {"simeq", MethodInfo(LINF, 0)}, + {"seidel", MethodInfo(LINF, 0)}, + {"_advance", MethodInfo(KINF, 0)}, + {"sparse", MethodInfo(KINF, 0)}, + {"derivimplicit", MethodInfo(DERF, 0)}, + {"cnexp", MethodInfo(DERF, 0)}, + {"clsoda", MethodInfo(DERF | KINF, 1)}, + {"after_cvode", MethodInfo(0, 0)}, + {"cvode_t", MethodInfo(0, 0)}, + {"cvode_t_v", MethodInfo(0, 0)}}; /** @@ -192,13 +194,11 @@ static std::map methods = {{"adams", MethodInfo(DERF | * - DefinitionType::EXT_4 : functions that need a first arg of \c NrnThread* * - DefinitionType::EXT_5 : external definition names that are not \c threadsafe * - * \todo These types were implemented for easy migration from old implementation. - * As first draft of code generation is complete, this can be now refactored. */ - enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; -static std::map extern_definitions = { + +const static std::map extern_definitions = { {"first_time", DefinitionType::EXT_DOUBLE}, {"error", DefinitionType::EXT_DOUBLE}, {"f_flux", DefinitionType::EXT_DOUBLE}, @@ -265,7 +265,6 @@ static std::map extern_definitions = { {"net_move", DefinitionType::EXT_DOUBLE}, {"net_event", DefinitionType::EXT_DOUBLE}, {"nrn_random_play", DefinitionType::EXT_DOUBLE}, - {"at_time", DefinitionType::EXT_DOUBLE}, {"nrn_ghk", DefinitionType::EXT_DOUBLE}, {"romberg", DefinitionType::EXT_2}, {"legendre", DefinitionType::EXT_2}, @@ -303,6 +302,19 @@ static std::map extern_definitions = { {"nrn_random_play", DefinitionType::EXT_5}}; +/** + * Checks if \c token is one of the functions coming from NEURON/CoreNEURON and needs + * passing NrnThread* as first argument (typical name of variable \c nt) + * + * @param token Name of function + * @return True or false depending if the function needs NrnThread* argument + */ +bool needs_neuron_thread_first_arg(const std::string& token) { + auto extern_def = extern_definitions.find(token); + return extern_def != extern_definitions.end() && extern_def->second == DefinitionType::EXT_4; +} + + /** * Variables from NEURON that are directly used in NMODL * @@ -315,7 +327,7 @@ static std::vector NEURON_VARIABLES = {"t", "dt", "celsius", "v", " /// Return token type for the keyword TokenType keyword_type(const std::string& name) { - return keywords[name]; + return keywords.at(name); } } // namespace details diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index f8e3f2efab..364467818c 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -25,4 +25,10 @@ parser::NmodlParser::token_type token_type(const std::string& name); std::vector get_external_variables(); std::vector get_external_functions(); +namespace details { + +bool needs_neuron_thread_first_arg(const std::string& token); + +} // namespace details + } // namespace nmodl diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index bfb822eeb3..c9d64b92f2 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -24,6 +24,7 @@ #include "pybind/pyembed.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" +#include "visitors/after_cvode_to_cnexp_visitor.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/global_var_visitor.hpp" @@ -318,6 +319,13 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(*ast); } + /// use cnexp instead of after_cvode solve method + { + logger->info("Running CVode to cnexp visitor"); + AfterCVodeToCnexpVisitor().visit_program(*ast); + ast_to_nmodl(*ast, filepath("after_cvode_to_cnexp")); + } + /// Rename variables that match ISPC compiler double constants if (ispc_backend) { logger->info("Running ISPC variables rename visitor"); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 069ac0c988..e069df9747 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -6,6 +6,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/neuron_solve_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/after_cvode_to_cnexp_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/after_cvode_to_cnexp_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.cpp diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp new file mode 100644 index 0000000000..a6e38cfc5d --- /dev/null +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp @@ -0,0 +1,32 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "visitors/after_cvode_to_cnexp_visitor.hpp" + +#include "ast/name.hpp" +#include "ast/solve_block.hpp" +#include "ast/string.hpp" +#include "codegen/codegen_naming.hpp" +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +void AfterCVodeToCnexpVisitor::visit_solve_block(ast::SolveBlock& node) { + const auto& method = node.get_method(); + if (method != nullptr && method->get_node_name() == codegen::naming::AFTER_CVODE_METHOD) { + logger->warn("CVode solver of {} in {} replaced with cnexp solver", + node.get_block_name()->get_node_name(), + method->get_token()->position()); + node.set_method(std::make_shared( + std::make_shared(codegen::naming::CNEXP_METHOD))); + } +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp new file mode 100644 index 0000000000..b6ee74aa75 --- /dev/null +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp @@ -0,0 +1,54 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::AfterCVodeToCnexpVisitor + */ + +#include +#include +#include +#include + +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * \addtogroup visitor_classes + * \{ + */ + +/** + * \class AfterCVodeToCnexpVisitor + * \brief Visitor to change usage of after_cvode solver to cnexp + * + * `CVODE` is not supported in CoreNEURON. If MOD file has `after_cvode` solver then + * we can treat that as `cnexp`. In order to re-use existing passes, in this visitor we + * replace `after_cvode` with `cnexp`. + */ + +class AfterCVodeToCnexpVisitor: public AstVisitor { + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor + AfterCVodeToCnexpVisitor() = default; + + /// \} + void visit_solve_block(ast::SolveBlock& node) override; +}; + +/** \} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index e34e0b598a..66666770bf 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -24,6 +24,7 @@ add_executable(testparser parser/parser.cpp) add_executable( testvisitor visitor/main.cpp + visitor/after_cvode_to_cnexp.cpp visitor/constant_folder.cpp visitor/defuse_analyze.cpp visitor/global_to_range.cpp diff --git a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp new file mode 100644 index 0000000000..05c9e2d24a --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp @@ -0,0 +1,66 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/after_cvode_to_cnexp_visitor.hpp" +#include "visitors/checkparent_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" + + +using namespace nmodl; +using namespace visitor; +using namespace test; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// AfterCVodeToCnexp visitor tests +//============================================================================= + +std::string run_after_cvode_to_cnexp_visitor(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + + SymtabVisitor().visit_program(*ast); + AfterCVodeToCnexpVisitor().visit_program(*ast); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().check_ast(*ast); + + return to_nmodl(ast); +} + + +SCENARIO("AfterCVodeToCnexpVisitor changes after_cvode solver method to cnexp") { + GIVEN("Breakpoint block with after_cvode method") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE states METHOD after_cvode + } + )"; + + std::string output_nmodl = R"( + BREAKPOINT { + SOLVE states METHOD cnexp + } + )"; + + THEN("AfterCVodeToCnexp visitor replaces after_cvode solver with cnexp") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_after_cvode_to_cnexp_visitor(input); + REQUIRE(result == expected_result); + } + } +} From c226f3c276163cf22c60fffdfe4f88509139cd93 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Mon, 5 Oct 2020 22:27:32 +0200 Subject: [PATCH 298/871] Grow NetSendBuffer_t dynamically when necessary (BlueBrain/nmodl#407) The `NetSendBuffer_t` struct in coreneuron has been updated to support dynamic resizing of its internal buffers. It offers now the `grow()` function will double the buffers' size. Until now, the `net_send` buffer was allocated statically at intialization and could not be grown. When many `net_send` events are requested before they have been processed, this fixed buffer size has lead to coreneuron either crashing due to buffer overflows (when used with mod2c) or terminating with an error message (when used with nmodl). We can now call the `grow()` function to dynamically enlarge the buffers when we run out of space. NMODL Repo SHA: BlueBrain/nmodl@b4ccdfef16ef2ca9112fd728efbcb49fa95baf12 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 7 +++++++ src/nmodl/codegen/codegen_acc_visitor.hpp | 3 +++ src/nmodl/codegen/codegen_c_visitor.cpp | 9 ++++++--- src/nmodl/codegen/codegen_c_visitor.hpp | 7 +++++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 76c53584fc..43828dcf8b 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -117,6 +117,13 @@ void CodegenAccVisitor::print_abort_routine() const { printer->add_line("}"); } +void CodegenAccVisitor::print_net_send_buffering_grow() { + auto error = add_escape_quote("Error : netsend buffer size (%d) exceeded\\n"); + + printer->add_line("printf({}, nsb->_cnt);"_format(error)); + printer->add_line("coreneuron_abort();"); +} + /** * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded * to accelerator. In this case, at very top level, we print pragma diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 18869bfed0..cdeb7cc339 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -92,6 +92,9 @@ class CodegenAccVisitor: public CodegenCVisitor { const std::string& type) const override; + void print_net_send_buffering_grow() override; + + public: CodegenAccVisitor(const std::string& mod_file, const std::string& output_dir, diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index dbb8495c90..51f57993e4 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3704,13 +3704,15 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->end_block(1); } +void CodegenCVisitor::print_net_send_buffering_grow() { + printer->add_line("nsb->grow();"); +} void CodegenCVisitor::print_net_send_buffering() { if (!net_send_buffer_required()) { return; } - auto error = add_escape_quote("Error : netsend buffer size (%d) exceeded\\n"); printer->add_newline(2); print_device_method_annotation(); auto args = @@ -3720,8 +3722,9 @@ void CodegenCVisitor::print_net_send_buffering() { printer->add_line("int i = nsb->_cnt;"); printer->add_line("nsb->_cnt++;"); printer->add_line("if(nsb->_cnt >= nsb->_size) {"); - printer->add_line(" printf({}, nsb->_cnt);"_format(error)); - printer->add_line(" coreneuron_abort();"); + printer->increase_indent(); + print_net_send_buffering_grow(); + printer->decrease_indent(); printer->add_line("}"); printer->add_line("nsb->_sendtype[i] = type;"); printer->add_line("nsb->_vdata_index[i] = vdata_index;"); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index cd49429fea..4c74570a7b 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1372,6 +1372,13 @@ class CodegenCVisitor: public visitor::AstVisitor { void print_net_receive_common_code(ast::Block& node, bool need_mech_inst = true); + /** + * Print statement that grows NetSendBuffering_t structure if needed. + * This function should be overridden for backends that cannot dynamically reallocate the buffer + */ + virtual void print_net_send_buffering_grow(); + + /** * Print kernel for buffering net_send events * From 6c45157325c16b699b87989c6c4af0954c659f59 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 27 Oct 2020 15:10:46 +0100 Subject: [PATCH 299/871] Suppress warning for v argument shadowing (BlueBrain/nmodl#367) (BlueBrain/nmodl#409) NMODL Repo SHA: BlueBrain/nmodl@be675091c088135cf4f92dcf5996787f3cb4febc --- src/nmodl/symtab/symbol_table.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 4e0e7ce2f4..45db2e0632 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -329,11 +329,20 @@ std::shared_ptr ModelSymbolTable::insert(const std::shared_ptr& * within the same scope. Otherwise, there is variable with same name * in parent scopes and it will shadow the definition. In this case just * emit the warning and insert the symbol. + * + * Note: + * We suppress the warning for the voltage since in most cases it is extern + * as well as argument and it is fine like that. */ if (search_symbol->get_scope() == current_symtab->name()) { emit_message(symbol, search_symbol, true); } else { - emit_message(symbol, search_symbol, false); + /** + * Suppress warning for voltage since it is often extern and argument. + */ + if (symbol->get_name() != "v") { + emit_message(symbol, search_symbol, false); + } current_symtab->insert(symbol); } return symbol; From caf8c6a80df37035ab0d69946ab8e71ca297db59 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 2 Nov 2020 14:35:27 +0100 Subject: [PATCH 300/871] Add support for for_netcon (BlueBrain/nmodl#410) * Add support for for_netcon (issue BlueBrain/nmodl#390) For_netcon applies the operations of the block to the weights of all the netcons. Since all the weights are on the same vector, weights, this means appling the same operations to a mask of values with the appropriate offset. The mask is the arguments of the for_netcon block. Note: the local variables are renamed to the correct position in the corenrn vectors (no new variables added). Some of these local variables may have been already substituted with special characters that break rename_visitor (i.e. (*w) ). Thus, we need to sanitize first the regex names that we are going to substitute. - Tested with the test in /nrn/test/coreneuron/test_fornetcon.py SoA and AoS * Fix for AoS Tested with /nrn/test/coreneuron/test_fornetcon.py with aos flags (for both nmodl and coreneuron) TODO: CI testing * Update src/codegen/codegen_c_visitor.cpp Co-authored-by: Pramod Kumbhar * Update src/codegen/codegen_c_visitor.cpp Co-authored-by: Pramod Kumbhar * squash! simplify printing in for_netcon loop add for_netcon_start add for_netcon_end * Avoid semicolon for FOR_NETCON node type Avoid extra newline - for netcon is visited while printing the statement - instead of newline, continue writing text as indentation etc are already setup Co-authored-by: Pramod Kumbhar Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@fee633581c206f9c137c5e2eca2d053e7c761d11 --- src/nmodl/codegen/codegen_c_visitor.cpp | 54 ++++++++++++++++++++++++- src/nmodl/codegen/codegen_c_visitor.hpp | 1 + src/nmodl/visitors/rename_visitor.hpp | 2 +- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 51f57993e4..670ddf9d6f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" @@ -42,6 +43,7 @@ using nmodl::utils::UseNumbersInString; /* Overloaded visitor routines */ /****************************************************************************************/ +const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; void CodegenCVisitor::visit_string(String& node) { if (!codegen) { @@ -482,7 +484,8 @@ bool CodegenCVisitor::need_semicolon(Statement* node) const { if (expression->is_statement_block() || expression->is_eigen_newton_solver_block() || expression->is_eigen_linear_solver_block() - || expression->is_solution_expression()) { + || expression->is_solution_expression() + || expression->is_for_netcon()) { return false; } } @@ -2710,6 +2713,15 @@ void CodegenCVisitor::print_mechanism_register() { method_name("net_receive"), net_recv_init_arg); printer->add_line(pnt_recline); } + if (info.for_netcon_used) { + // index where information about FOR_NETCON is stored in the integer array + const auto index = + std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { + return a.name == naming::FOR_NETCON_SEMANTIC; + })->index; + printer->add_line("add_nrn_fornetcons(mech_type, {});"_format(index)); + } + if (info.net_event_used || info.net_send_used) { printer->add_line("hoc_register_net_send_buffering(mech_type);"); } @@ -3736,6 +3748,46 @@ void CodegenCVisitor::print_net_send_buffering() { } +void CodegenCVisitor::visit_for_netcon(ast::ForNetcon& node) { + // For_netcon should take the same arguments as net_receive and apply the operations + // in the block to the weights of the netcons. Since all the weights are on the same vector, + // weights, we have a mask of operations that we apply iteratively, advancing the offset + // to the next netcon. + const auto& args = node.get_parameters(); + RenameVisitor v; + auto& statement_block = node.get_statement_block(); + for (size_t i_arg = 0; i_arg < args.size(); ++i_arg) { + // sanitize node_name since we want to substitute names like (*w) as they are + auto old_name = + std::regex_replace(args[i_arg]->get_node_name(), regex_special_chars, R"(\$&)"); + auto new_name = "weights[{} + nt->_fornetcon_weight_perm[i]]"_format(i_arg); + v.set(old_name, new_name); + statement_block->accept(v); + } + + const auto index = + std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { + return a.name == naming::FOR_NETCON_SEMANTIC; + })->index; + const auto num_int = int_variables_size(); + + std::string offset = (layout == LayoutType::soa) ? "{}*pnodecount + id"_format(index) + : "{} + id*{}"_format(index, num_int); + printer->add_text("const size_t offset = {};"_format(offset)); + printer->add_newline(); + printer->add_line( + "const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]];"); + printer->add_line( + "const size_t for_netcon_end = nt->_fornetcon_perm_indices[indexes[offset] + 1];"); + + printer->add_line("for (auto i = for_netcon_start; i < for_netcon_end; ++i) {"); + printer->increase_indent(); + print_statement_block(*statement_block, false, false); + printer->decrease_indent(); + + printer->add_line("}"); +} + void CodegenCVisitor::print_net_receive_kernel() { if (!net_receive_required()) { return; diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 4c74570a7b..b935976a43 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1855,6 +1855,7 @@ class CodegenCVisitor: public visitor::AstVisitor { void visit_watch_statement(ast::WatchStatement& node) override; void visit_while_statement(ast::WhileStatement& node) override; void visit_derivimplicit_callback(ast::DerivimplicitCallback& node) override; + void visit_for_netcon(ast::ForNetcon& node) override; }; diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index 6979593749..ce14105bae 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -55,7 +55,7 @@ class RenameVisitor: public ConstAstVisitor { std::string new_var_name_prefix; /// Map that keeps the renamed variables to keep the same random suffix when a variable is - /// renamed accross the whole file + /// renamed across the whole file std::unordered_map renamed_variables; /// add prefix to variable name From 75fb0caf6e945f119b18b128f1b65300581eb784 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 9 Nov 2020 09:20:12 +0100 Subject: [PATCH 301/871] Update nrnunits.lib with new values and CMake option to turn on/off legacy units (BlueBrain/nmodl#411) * Update nrnunits.lib with new values of 2019 nist constants * New CMake option `NMODL_ENABLE_LEGACY_UNITS` * Use CURRENT_BINARY_DIR everywhere * Install() take files from BINARY_DIR and not SRC_DIR Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@7f646c4382c29df7e87a11b889ad9376965ff980 --- cmake/nmodl/CMakeLists.txt | 20 ++++++++++++-- share/{nmodl/nrnunits.lib => nrnunits.lib.in} | 27 ++++++++++--------- src/nmodl/config/config.cpp.in | 2 +- test/nmodl/transpiler/unit/units/parser.cpp | 2 +- test/nmodl/transpiler/unit/visitor/units.cpp | 10 +++---- 5 files changed, 40 insertions(+), 21 deletions(-) rename share/{nmodl/nrnunits.lib => nrnunits.lib.in} (95%) mode change 100644 => 100755 diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e641871758..9691c6c2d3 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -18,10 +18,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +option(NMODL_ENABLE_LEGACY_UNITS "Use original faraday, R, etc. instead of 2019 nist constants" OFF) + # ============================================================================= # Settings to enable project as submodule # ============================================================================= set(NMODL_PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(NMODL_PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) set(NMODL_AS_SUBPROJECT OFF) if(NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) set(NMODL_AS_SUBPROJECT ON) @@ -199,6 +202,19 @@ add_subdirectory(src/pybind) add_subdirectory(src/solver) # ============================================================================= -# Install unit database, examples and utility scripts from share +# Prepare units database file from nrnunits.lib.in +# ============================================================================= +if(NMODL_ENABLE_LEGACY_UNITS) + set(LegacyY "") + set(LegacyN "/") +else() + set(LegacyY "/") + set(LegacyN "") +endif() +configure_file(share/nrnunits.lib.in ${CMAKE_CURRENT_BINARY_DIR}/share/nrnunits.lib @ONLY) + +# ============================================================================= +# Install unit database to share # ============================================================================= -install(DIRECTORY share/ DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nrnunits.lib + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share) diff --git a/share/nmodl/nrnunits.lib b/share/nrnunits.lib.in old mode 100644 new mode 100755 similarity index 95% rename from share/nmodl/nrnunits.lib rename to share/nrnunits.lib.in index b8c5fdb15b..8af235ee58 --- a/share/nmodl/nrnunits.lib +++ b/share/nrnunits.lib.in @@ -63,19 +63,20 @@ demi- .5 / constants fuzz 1 +two 2 pi 3.14159265358979323846 c 2.99792458+8 m/sec fuzz g 9.80665 m/sec2 au 1.49597871+11 m fuzz -mole 6.022169+23 fuzz +@LegacyY@mole 6.022169+23 fuzz +@LegacyN@mole 6.02214076+23 fuzz mol 1 / mol is explicitly defined as a constant / with value 1 to avoid "undefined unit" / error with mod files that don't define / it themselves -/mole 6.022140857+23 fuzz -e 1.6021917-19 coul fuzz -/e 1.6021766208-19 coul fuzz +@LegacyY@e 1.6021917-19 coul fuzz +@LegacyN@e 1.602176634-19 coul fuzz energy c2 force g mercury 1.33322+5 kg/m2-sec2 @@ -398,8 +399,8 @@ ev e-volt / faraday 9.652000+04 coul / faraday from host: physics.nist.gov / path: /PhysRefData/fundconst/html/keywords.html -faraday 9.6485309+4 coul -/faraday 96485.33289 coul +@LegacyY@faraday 9.6485309+4 coul +@LegacyN@faraday e-mole fathom 6 ft fermi 1-15 m fifth 4|5 qt @@ -435,7 +436,8 @@ hyl gm force sec2/m hz /sec imaginarycubicfoot 1.4 ft3 jeroboam 4|5 gal -boltzmann 1.38064852-23 joule/K +@LegacyY@boltzmann 1.38064852-23 joule/K +@LegacyN@boltzmann 1.380649-23 joule/K k boltzmann karat 1|24 kcal kilocal @@ -504,7 +506,8 @@ quarter 9 in quartersection 1|4 mi2 quintal 100 kg quire 25 -gasconstant 8.3144598 joule/K +@LegacyY@gasconstant 8.3144598 joule/K +@LegacyN@gasconstant k-mole R gasconstant rad 100 erg/gm ream 500 @@ -578,10 +581,10 @@ tex .001 gram / m englishell 45 inch scottishell 37.2 inch flemishell 27 inch -planck 6.626-34 joule-sec -/planck 6.626070040-34 joule-sec -hbar 1.055-34 joule-sec -/hbar 1.054571800-34 joule-sec +@LegacyY@planck 6.626-34 joule-sec +@LegacyN@planck 6.62607015-34 joule-sec +@LegacyY@hbar 1.055-34 joule-sec +@LegacyN@hbar planck/two-pi electronmass 9.1095-31 kg protonmass 1.6726-27 kg neutronmass 1.6606-27 kg diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index b32d2bf1af..f6b77a2420 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -21,4 +21,4 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; * from CMAKE_INSTALL_PREFIX. */ std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = - {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@NMODL_PROJECT_SOURCE_DIR@/share/nrnunits.lib"}; + {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@NMODL_PROJECT_BINARY_DIR@/share/nrnunits.lib"}; diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index af3be01a95..aef788e695 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -182,7 +182,7 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars REQUIRE(is_substring(parsed_units, "dummy3 0.02500000: -2 0 0 0 0 0 0 0 0 0")); REQUIRE(is_substring(parsed_units, "dummy4 -0.02500000: -2 0 0 0 0 0 0 0 0 0")); REQUIRE(is_substring(parsed_units, "dummy5 0.02500000: 0 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "R 8.31449872: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE(is_substring(parsed_units, "R 8.31446262: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE(is_substring(parsed_units, "R1 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE(is_substring(parsed_units, "R2 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE(is_substring(parsed_units, "m kg sec coul candela dollar bit erlang K")); diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 345ddb0a5e..8cfe92b850 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -175,13 +175,13 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] um3 0.00100000: m3 molar1 1000.00000000: m-3 degK 1.00000000: K1 - FARADAY1 96485.30900000: coul1 - FARADAY2 96.48530900: coul1 - FARADAY3 9.64853090: coul1 + FARADAY1 96485.33212331: coul1 + FARADAY2 96.48533212: coul1 + FARADAY3 9.64853321: coul1 PI 3.14159265: constant - R1 8.31449872: m2 kg1 sec-2 K-1 + R1 8.31446262: m2 kg1 sec-2 K-1 R2 8.31400000: m2 kg1 sec-2 K-1 - R3 8314.49871704: m2 kg1 sec-2 K-1 + R3 8314.46261815: m2 kg1 sec-2 K-1 R4 8.31400000: m2 kg1 sec-2 K-1 R5 8.31450000: m2 kg1 sec-2 K-1 dummy1 123.45000000: m1 sec-2 From 5cbdf14d90a8b89f818ac4765f70e7df5d7552b4 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Tue, 10 Nov 2020 15:17:13 +0100 Subject: [PATCH 302/871] Add support for INCLUDE NMODL directive (BlueBrain/nmodl#372) Parse files instructed by the NMODL INCLUDE directive * handle the INCLUDE NMODL directive * parser now follows the included files and store the result AST in the `nmodl::ast::Include` AST node. * update NmodlVisitor generatede class so that it ignores the `nodes` member of the `nmodl::ast:Include` AST node. * NmodlDriver: * new method `parse_include` in charge of parsing * move reporting of parsing error in `NmodlDriver` class. * remove useless member variables `lexer` and `parser` to make it completely reentrant. * all `parse_*` methods now raise exceptions upon error. * Improve performance of methods: * less lookup in datastructure: * `NmodlDriver::get_defined_var_value` * `SingletonRandomString::get_random_string` * return `const&`: * `SingletonRandomString::get_random_string` * Fix logging of created JSON file fixes BlueBrain/nmodl#300 Co-authored-by: Ioannis Magkanaris Co-authored-by: Alessandro Cattabiani NMODL Repo SHA: BlueBrain/nmodl@ca0374ed2dd6e971a1f9e27917e2c47a5827e659 --- src/nmodl/language/nmodl.yaml | 8 +- src/nmodl/language/node_info.py | 15 +-- src/nmodl/language/nodes.py | 3 - .../templates/visitors/json_visitor.cpp | 4 +- .../templates/visitors/nmodl_visitor.cpp | 4 +- src/nmodl/nmodl/main.cpp | 4 +- src/nmodl/parser/nmodl.yy | 7 +- src/nmodl/parser/nmodl_driver.cpp | 116 ++++++++++++++---- src/nmodl/parser/nmodl_driver.hpp | 77 ++++++++---- src/nmodl/pybind/pynmodl.cpp | 11 +- src/nmodl/utils/CMakeLists.txt | 20 +-- src/nmodl/utils/common_utils.cpp | 46 ++++++- src/nmodl/utils/common_utils.hpp | 60 +++++++-- src/nmodl/utils/file_library.cpp | 70 +++++++++++ src/nmodl/utils/file_library.hpp | 71 +++++++++++ .../transpiler/integration/mod/cabpump.mod | 12 +- .../transpiler/integration/mod/var_init.inc | 10 ++ test/nmodl/transpiler/unit/parser/parser.cpp | 28 ++++- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 2 + 19 files changed, 469 insertions(+), 99 deletions(-) create mode 100644 src/nmodl/utils/file_library.cpp create mode 100644 src/nmodl/utils/file_library.hpp create mode 100644 test/nmodl/transpiler/integration/mod/var_init.inc diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 8c8e5cd083..47f4b3183f 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1416,12 +1416,16 @@ brief: "Represents a `DEFINE` statement in NMODL" - Include: - brief: "TODO" + brief: "Represents an `INCLUDE` statement in NMODL" nmodl: "INCLUDE " members: - filename: - brief: "TODO" + brief: "path to the file to include" type: String + - blocks: + brief: "AST of the included file" + type: Node + vector: true - ParamAssign: brief: "TODO" diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 335cacf67b..10a0bcae24 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -153,18 +153,19 @@ # these node names are explicitly added because they are used in ast/visitor # printer classes. In order to avoid hardcoding in the printer functions, they # are defined here. -PROGRAM_BLOCK = "Program" BASE_BLOCK = "Block" -PRIME_NAME_NODE = "PrimeName" -STRING_NODE = "String" -NUMBER_NODE = "Number" BINARY_EXPRESSION_NODE = "BinaryExpression" -NAME_NODE = "Name" BOOLEAN_NODE = "Boolean" -INTEGER_NODE = "Integer" -FLOAT_NODE = "Float" DOUBLE_NODE = "Double" +FLOAT_NODE = "Float" +INCLUDE_NODE = "Include" +INTEGER_NODE = "Integer" +NAME_NODE = "Name" +NUMBER_NODE = "Number" +PRIME_NAME_NODE = "PrimeName" +PROGRAM_BLOCK = "Program" STATEMENT_BLOCK_NODE = "StatementBlock" +STRING_NODE = "String" UNIT_BLOCK = "UnitBlock" # name of variable in prime node which represent order of derivative diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 09627c4d29..a539b55647 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -488,9 +488,6 @@ def get_getter_method(self, class_name): }} """ - - - def get_setter_method_declaration(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 87a372f965..e96bcbf10c 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -42,7 +42,9 @@ void JSONVisitor::visit_{{ node.class_name|snake_case }}(const {{ node.class_nam {% endif %} printer->pop_block(); {% if node.is_program_node %} - flush(); + if (node.get_parent() == nullptr) { + flush(); + } {% endif %} {% else %} (void)node; diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 2b9ef3ede5..a69c3b0b26 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -52,7 +52,9 @@ using namespace ast; {%- macro add_child(node, child) -%} - {% if child.nmodl_name %} + {% if node.class_name == node_info.INCLUDE_NODE and child.varname == "blocks" %} + {# ignore the "blocks" field of Include node #} + {% elif child.nmodl_name %} if(node.get_{{ child.varname }}()->eval()) { printer->add_element("{{ child.nmodl_name }}"); } diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index c9d64b92f2..0a53028489 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -5,7 +5,6 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include #include #include @@ -20,7 +19,6 @@ #include "codegen/codegen_omp_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" -#include "parser/unit_driver.hpp" #include "pybind/pyembed.hpp" #include "utils/common_utils.hpp" #include "utils/logger.hpp" @@ -376,8 +374,8 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("ast")); if (json_ast) { - logger->info("Writing AST into {}", file); auto file = scratch_dir + "/" + modfile + ".ast.json"; + logger->info("Writing AST into {}", file); JSONVisitor(file).write(*ast); } diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 01e7e5d8f5..d3ec9df238 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -500,8 +500,7 @@ all : { } | all INCLUDE1 STRING_PTR { - auto statement = new ast::Include($3); - $1->emplace_back_node(statement); + $1->emplace_back_node(driver.parse_include(driver.check_include_argument(scanner.loc, $3->get_value()), scanner.loc)); $$ = $1; } ; @@ -2626,7 +2625,5 @@ std::string parse_with_verbatim_parser(std::string str) { */ void NmodlParser::error(const location &loc , const std::string &msg) { - std::stringstream ss; - ss << "NMODL Parser Error : " << msg << " [Location : " << loc << "]"; - throw std::runtime_error(ss.str()); + driver.parse_error(loc, msg); } diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 99219b5ca2..13af0e37a6 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -24,60 +24,132 @@ std::shared_ptr NmodlDriver::parse_stream(std::istream& in) { NmodlLexer scanner(*this, &in); NmodlParser parser(scanner, *this); - this->lexer = &scanner; - this->parser = &parser; - scanner.set_debug(trace_scanner); parser.set_debug_level(trace_parser); parser.parse(); return astRoot; } -//// parse nmodl file -std::shared_ptr NmodlDriver::parse_file(const std::string& filename) { +std::shared_ptr NmodlDriver::parse_file(const std::string& filename, + const location* loc) { std::ifstream in(filename.c_str()); - stream_name = filename; - if (!in.good()) { - logger->error("Can not open file : {}", filename); - return nullptr; + std::ostringstream oss; + if (loc == nullptr) { + oss << "NMODL Parser Error : "; + } + oss << "can not open file : " << filename; + if (loc != nullptr) { + parse_error(*loc, oss.str()); + } else { + throw std::runtime_error(oss.str()); + } + } + + auto current_stream_name = stream_name; + stream_name = filename; + auto absolute_path = utils::cwd() + utils::pathsep + filename; + { + const auto last_slash = filename.find_last_of(utils::pathsep); + if (utils::file_is_abs(filename)) { + const auto path_prefix = filename.substr(0, last_slash + 1); + library.push_current_directory(path_prefix); + absolute_path = filename; + } else if (last_slash == std::string::npos) { + library.push_current_directory(utils::cwd()); + } else { + const auto path_prefix = filename.substr(0, last_slash + 1); + const auto path = utils::cwd() + utils::pathsep + path_prefix; + library.push_current_directory(path); + } } + + open_files.emplace(absolute_path, loc); parse_stream(in); + open_files.erase(absolute_path); + library.pop_current_directory(); + stream_name = current_stream_name; + return astRoot; } -/// parser nmodl provided as string (used for testing) std::shared_ptr NmodlDriver::parse_string(const std::string& input) { std::istringstream iss(input); parse_stream(iss); return astRoot; } -void NmodlDriver::error(const std::string& m, const class location& l) { - std::cerr << l << " : " << m << '\n'; -} +std::shared_ptr NmodlDriver::parse_include(const std::string& name, + const location& loc) { + if (name.empty()) { + parse_error(loc, "empty filename"); + } + + // Try to find directory containing the file to import + const auto directory_path = library.find_file(name); + + // Complete path of file (directory + filename). + std::string absolute_path = name; -void NmodlDriver::error(const std::string& m) { - std::cerr << m << '\n'; + if (!directory_path.empty()) { + absolute_path = directory_path + std::string(1, utils::pathsep) + name; + } + + // Detect recursive inclusion. + auto already_included = open_files.find(absolute_path); + if (already_included != open_files.end()) { + std::ostringstream oss; + oss << name << ": recursive inclusion.\n"; + if (already_included->second != nullptr) { + oss << *already_included->second << ": initial inclusion was here."; + } + parse_error(loc, oss.str()); + } + + std::shared_ptr program; + program.swap(astRoot); + + parse_file(absolute_path, &loc); + + program.swap(astRoot); + auto filename_node = std::shared_ptr( + new ast::String(std::string(1, '"') + name + std::string(1, '"'))); + return std::shared_ptr(new ast::Include(filename_node, program->get_blocks())); } -/// add macro definition and it's value (DEFINE keyword of nmodl) void NmodlDriver::add_defined_var(const std::string& name, int value) { defined_var[name] = value; } -/// check if particular text is defined as macro -bool NmodlDriver::is_defined_var(const std::string& name) { +bool NmodlDriver::is_defined_var(const std::string& name) const { return !(defined_var.find(name) == defined_var.end()); } -/// return variable's value defined as macro (always an integer) -int NmodlDriver::get_defined_var_value(const std::string& name) { - if (is_defined_var(name)) { - return defined_var[name]; +int NmodlDriver::get_defined_var_value(const std::string& name) const { + const auto var_it = defined_var.find(name); + if (var_it != defined_var.end()) { + return var_it->second; } throw std::runtime_error("Trying to get undefined macro / define :" + name); } +void NmodlDriver::parse_error(const location& location, const std::string& message) { + std::ostringstream oss; + oss << "NMODL Parser Error : " << message << " [Location : " << location << ']'; + throw std::runtime_error(oss.str()); +} + +std::string NmodlDriver::check_include_argument(const location& location, + const std::string& filename) { + if (filename.empty()) { + parse_error(location, "empty filename in INCLUDE directive"); + } else if (filename.front() != '"' && filename.back() != '"') { + parse_error(location, "filename may start and end with \" character"); + } else if (filename.size() == 3) { + parse_error(location, "filename is empty"); + } + return filename.substr(1, filename.size() - 2); +} + } // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 26ba3e9974..45916e9252 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -12,10 +12,11 @@ * \brief Parser implementations */ -#include #include +#include #include "ast/ast.hpp" +#include "utils/file_library.hpp" /// encapsulates everything related to NMODL code generation framework @@ -24,23 +25,15 @@ namespace nmodl { namespace parser { /** - * @defgroup parser Parser Implementation - * @brief All parser and driver classes implementation + * \defgroup parser Parser Implementation + * \brief All parser and driver classes implementation * * * - * @addtogroup parser - * @{ + * \addtogroup parser + * \{ */ - -/// flex generated scanner class (extends base lexer class of flex) -class NmodlLexer; - -/// parser class generated by bison -class NmodlParser; - - /** * \class NmodlDriver * \brief Class that binds all pieces together for parsing nmodl file @@ -67,7 +60,7 @@ class NmodlParser; class NmodlDriver { private: /// all macro defined in the mod file - std::map defined_var; + std::unordered_map defined_var; /// enable debug output in the flex scanner bool trace_scanner = false; @@ -75,18 +68,19 @@ class NmodlDriver { /// enable debug output in the bison parser bool trace_parser = false; - /// pointer to the lexer instance being used - NmodlLexer* lexer = nullptr; - - /// pointer to the parser instance being used - NmodlParser* parser = nullptr; - /// print messages from lexer/parser bool verbose = false; /// root of the ast std::shared_ptr astRoot = nullptr; + /// The file library for IMPORT directives + FileLibrary library{FileLibrary::default_instance()}; + + /// The list of open files, and the location of the request. + /// \a nullptr is pushed as location for the top NMODL file + std::unordered_map open_files; + public: /// file or input stream name (used by scanner for position), see todo std::string stream_name; @@ -94,16 +88,30 @@ class NmodlDriver { NmodlDriver() = default; NmodlDriver(bool strace, bool ptrace); - void error(const std::string& m, const class location& l); - void error(const std::string& m); - + /// add macro definition and it's value (DEFINE keyword of nmodl) void add_defined_var(const std::string& name, int value); - bool is_defined_var(const std::string& name); - int get_defined_var_value(const std::string& name); + + /// check if particular text is defined as macro + bool is_defined_var(const std::string& name) const; + + /// return variable's value defined as macro (always an integer) + int get_defined_var_value(const std::string& name) const; std::shared_ptr parse_stream(std::istream& in); + + /// parser nmodl provided as string (used for testing) std::shared_ptr parse_string(const std::string& input); - std::shared_ptr parse_file(const std::string& filename); + + /** + * \brief parse NMODL file + * \param filename path to the file to parse + * \param loc optional location when \a filename is dictated + * by an `INCLUDE` NMODL directive. + */ + std::shared_ptr parse_file(const std::string& filename, + const location* loc = nullptr); + //// parse file specified in nmodl include directive + std::shared_ptr parse_include(const std::string& filename, const location& loc); void set_verbose(bool b) { verbose = b; @@ -122,9 +130,24 @@ class NmodlDriver { void set_ast(ast::Program* node) noexcept { astRoot.reset(node); } + + /** + * Emit a parsing error + * \throw std::runtime_error + */ + void parse_error(const location& location, const std::string& message); + + /** + * Ensure \a file argument given to the INCLUDE directive is valid: + * - between double-quotes + * - not empty "" + * + * \return unquoted string + */ + std::string check_include_argument(const location& location, const std::string& filename); }; -/** @} */ // end of parser +/** \} */ // end of parser } // namespace parser } // namespace nmodl diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 9aee8393b6..20a2df7b19 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -151,10 +151,13 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { &nmodl::PyNmodlDriver::parse_string, "input"_a, nmodl::docstring::driver_parse_string) - .def("parse_file", - &nmodl::PyNmodlDriver::parse_file, - "filename"_a, - nmodl::docstring::driver_parse_file) + .def( + "parse_file", + [](nmodl::PyNmodlDriver& driver, const std::string& file) { + return driver.parse_file(file, nullptr); + }, + "filename"_a, + nmodl::docstring::driver_parse_file) .def("parse_stream", &nmodl::PyNmodlDriver::parse_stream, "in"_a, diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 33c0e228da..aaa406f9ea 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -2,15 +2,17 @@ # Utility sources # ============================================================================= set(UTIL_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/string_utils.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/common_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_stat.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/table_data.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/table_data.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/logger.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/logger.cpp + common_utils.cpp + common_utils.hpp + file_library.cpp + file_library.hpp + logger.cpp + logger.hpp + perf_stat.cpp + perf_stat.hpp + string_utils.hpp + table_data.cpp + table_data.hpp ${PROJECT_BINARY_DIR}/src/config/config.cpp) # ============================================================================= diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index fbeed5a579..5c3010990c 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -5,14 +5,20 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "common_utils.hpp" + +#include #include +#include #include #include #include #include #include -#include "common_utils.hpp" +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +#define IS_WINDOWS +#endif namespace nmodl { namespace utils { @@ -25,6 +31,27 @@ bool is_dir_exist(const std::string& path) { return (info.st_mode & S_IFDIR) != 0; } +bool file_exists(const std::string& path) { + struct stat info; + return stat(path.c_str(), &info) == 0; +} + +bool file_is_abs(const std::string& path) { +#ifdef IS_WINDOWS + return path.find(":\\") != std::string::npos; +#else + return path.find(pathsep) == 0; +#endif +} + +std::string cwd() { + char cwd[MAXPATHLEN + 1]; + + if (nullptr == getcwd(cwd, MAXPATHLEN + 1)) { + throw std::runtime_error("working directory name too long"); + } + return {cwd}; +} bool make_path(const std::string& path) { mode_t mode = 0755; int ret = mkdir(path.c_str(), mode); @@ -57,6 +84,23 @@ bool make_path(const std::string& path) { } } +TempFile::TempFile(std::string path) + : path_(std::move(path)) { + std::ofstream output(path_); +} + +TempFile::TempFile(std::string path, const std::string& content) + : path_(std::move(path)) { + std::ofstream output(path_); + output << content; +} + +TempFile::~TempFile() { + if (remove(path_.c_str()) != 0) { + perror("Cannot delete temporary file"); + } +} + std::string generate_random_string(const int len, UseNumbersInString use_numbers) { std::string s(len, 0); static const char alphanum[] = diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 3d02e7b4c0..d8341a8508 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -9,6 +9,11 @@ #include #include +#include +#include +#include +#include + /** * @@ -74,13 +79,49 @@ typename std::vector::const_iterator const_iter_cast( } #endif +#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) +/// The character used by the operating system to separate pathname components +static constexpr char pathsep{'\\'}; +/// The character conventionally used by the operating system to separate search path components +static constexpr char envpathsep{';'}; +/// Maximum size of a directory path +static constexpr int max_path_len{_MAX_DIR}; +#else +/// The character used by the operating system to separate pathname components +static constexpr char pathsep{'/'}; +/// The character conventionally used by the operating system to separate search path components +static constexpr char envpathsep{':'}; +/// Maximum size of a directory path +static constexpr int max_path_len{MAXPATHLEN}; +#endif /// Given directory path, create sub-directories bool make_path(const std::string& path); -/// Check if directory with given path exist +/// Check if directory with given path exists bool is_dir_exist(const std::string& path); +/// Check if specified file path exists +bool file_exists(const std::string& path); + +/// Check if specified file path is absolute +bool file_is_abs(const std::string& path); + +/// get current working directory +std::string cwd(); + +/** + * \brief Create an empty file which is then removed when the C++ object is destructed + */ +struct TempFile { + explicit TempFile(std::string path); + TempFile(std::string path, const std::string& content); + ~TempFile(); + + private: + std::string path_; +}; + /// Enum to wrap bool variable to select if random string /// should have numbers or not enum UseNumbersInString : bool { WithNumbers = true, WithoutNumbers = false }; @@ -126,7 +167,7 @@ class SingletonRandomString { * @param var_name Variable name for which to get the random string * @return Random string assigned to var_name */ - std::string get_random_string(const std::string& var_name) const { + const std::string& get_random_string(const std::string& var_name) const { return random_strings.at(var_name); } @@ -137,14 +178,15 @@ class SingletonRandomString { * @param use_numbers control whether random string can include numeric characters or not * @return Random string assigned to var_name */ - std::string reset_random_string(const std::string& var_name, UseNumbersInString use_numbers) { - if (random_string_exists(var_name)) { - random_strings.erase(var_name); - random_strings.insert({var_name, generate_random_string(SIZE, use_numbers)}); - } else { - random_strings.insert({var_name, generate_random_string(SIZE, use_numbers)}); + const std::string& reset_random_string(const std::string& var_name, + UseNumbersInString use_numbers) { + const auto new_string = generate_random_string(SIZE, use_numbers); + auto status = random_strings.emplace(var_name, new_string); + if (!status.second) { + // insertion didn't take place since element was already there. + status.first->second = new_string; } - return random_strings[var_name]; + return status.first->second; } private: diff --git a/src/nmodl/utils/file_library.cpp b/src/nmodl/utils/file_library.cpp new file mode 100644 index 0000000000..2a2e4d4827 --- /dev/null +++ b/src/nmodl/utils/file_library.cpp @@ -0,0 +1,70 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "file_library.hpp" + +#include +#include +#include + +#include "utils/common_utils.hpp" +#include "utils/string_utils.hpp" + +namespace nmodl { + + +FileLibrary FileLibrary::default_instance() { + FileLibrary library; + library.append_env_var("NMODL_PATH"); + library.push_cwd(); + return library; +} + +void FileLibrary::append_dir(const std::string& path) { + paths_.insert(paths_.begin(), path); +} + +void FileLibrary::append_env_var(const std::string& env_var) { + const auto value = getenv(env_var.c_str()); + if (value != nullptr) { + for (const auto& path: stringutils::split_string(value, utils::envpathsep)) { + if (!path.empty()) { + append_dir(path); + } + } + } +} + +void FileLibrary::push_current_directory(const std::string& path) { + paths_.push_back(path); +} + +void FileLibrary::push_cwd() { + push_current_directory(utils::cwd()); +} + +void FileLibrary::pop_current_directory() { + assert(!paths_.empty()); + paths_.erase(--paths_.end()); +} + +std::string FileLibrary::find_file(const std::string& file) { + if (utils::file_is_abs(file)) { + if (utils::file_exists(file)) { + return ""; + } + } + for (auto paths_it = paths_.rbegin(); paths_it != paths_.rend(); ++paths_it) { + auto file_abs = *paths_it + utils::pathsep + file; + if (utils::file_exists(file_abs)) { + return *paths_it; + } + } + return ""; +} + +} // namespace nmodl diff --git a/src/nmodl/utils/file_library.hpp b/src/nmodl/utils/file_library.hpp new file mode 100644 index 0000000000..51b78e8cf7 --- /dev/null +++ b/src/nmodl/utils/file_library.hpp @@ -0,0 +1,71 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include +#include + +/** + * + * \dir + * \brief Utility classes and function + * + * \file + * \brief Manage search path. + */ + +namespace nmodl { + +/** + * \brief Manage search path. + * + * Store search path used for handling paths when processing include NMODL directive + */ +class FileLibrary { + public: + /// An empty library + FileLibrary() = default; + /** + * Initialize the library with the following path: + * - current working directory + * - paths in the NMODL_PATH environment variable + */ + static FileLibrary default_instance(); + + /** + * \name Managing inclusion paths. + * \{ + */ + void append_dir(const std::string& path); + void append_env_var(const std::string& env_var); + /** \} */ + + /** + * \name current directory + * \{ + */ + void push_current_directory(const std::string& path); + void pop_current_directory(); + /** \} */ + + /** + * \brief Search a file. + * Determine real path of \a file + * \return Directory containing \a file, or "" if not found. + */ + std::string find_file(const std::string& file); + + private: + /// push the working directory in the directories stack + void push_cwd(); + + /// inclusion path list + std::vector paths_; +}; + +} // namespace nmodl diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 035641877d..57f035bb72 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -4,7 +4,8 @@ NEURON { SUFFIX cadyn USEION ca READ cai,ica WRITE cai RANGE ca - GLOBAL depth,cainf,taur + GLOBAL depth,cainf,taur + RANGE var } @@ -27,6 +28,7 @@ PARAMETER { ASSIGNED { ica (mA/cm2) drive_channel (mM/ms) + var (mV) } STATE { @@ -38,13 +40,17 @@ BREAKPOINT { SOLVE state METHOD euler } -DERIVATIVE state { +INCLUDE "var_init.inc" + +DERIVATIVE state { drive_channel = - (10000) * ica / (2 * FARADAY * depth) if (drive_channel <= 0.) { drive_channel = 0. } : cannot pump inward ca' = drive_channel/18 + (cainf -ca)/taur*11 cai = ca } + INITIAL { - ca = cainf + var_init(var) + ca = cainf } diff --git a/test/nmodl/transpiler/integration/mod/var_init.inc b/test/nmodl/transpiler/integration/mod/var_init.inc new file mode 100644 index 0000000000..a708ba9b24 --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/var_init.inc @@ -0,0 +1,10 @@ +COMMENT + Example file to test INCLUDE keyword + in NMODL. This file is included in + cabpump.mod and takes care of initializing + the dummy variable named "var" +ENDCOMMENT + +FUNCTION var_init(var(mV)) (mV) { + var = 1 +} diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 33b2fa2102..f8b7bb632a 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -18,6 +18,7 @@ #include "parser/nmodl_driver.hpp" #include "test/unit/utils/nmodl_constructs.hpp" #include "test/unit/utils/test_utils.hpp" +#include "utils/common_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -121,6 +122,7 @@ SCENARIO("NMODL parser accepts empty unit specification") { } SCENARIO("NMODL parser running number of valid NMODL constructs") { + nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { @@ -142,6 +144,28 @@ SCENARIO("NMODL parser running number of invalid NMODL constructs") { } } +//============================================================================= +// Ensure that the parser can handle invalid INCLUDE constructs +//============================================================================= + +SCENARIO("Check that the parser doesn't crash when passing invalid INCLUDE constructs") { + GIVEN("An empty filename") { + REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"\""), Catch::Contains("empty filename")); + } + + GIVEN("An missing included file") { + REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"unknown.file\""), + Catch::Contains("can not open file : unknown.file")); + } + + GIVEN("An invalid included file") { + nmodl::utils::TempFile included("included.file", + nmodl_invalid_constructs.at("title_1").input); + REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"included.file\""), + Catch::Contains("unexpected End of file")); + } +} + SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { GIVEN("A valid CURIE information statement") { THEN("parser accepts without an error") { @@ -163,10 +187,10 @@ SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { SCENARIO("Check parents in valid NMODL constructs") { nmodl::parser::NmodlDriver driver; - std::shared_ptr ast; + nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { // parse the string and get the ast - ast = driver.parse_string(construct.second.input); + const auto ast = driver.parse_string(construct.second.input); GIVEN(construct.second.name) { THEN("Check the parents in : " + construct.second.input) { // check the parents diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 9dda94663d..2215ca3cd3 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -11,6 +11,7 @@ #include "parser/nmodl_driver.hpp" #include "test/unit/utils/nmodl_constructs.hpp" #include "test/unit/utils/test_utils.hpp" +#include "utils/common_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -40,6 +41,7 @@ std::string run_nmodl_visitor(const std::string& text) { } SCENARIO("Convert AST back to NMODL form", "[visitor][nmodl]") { + nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; const std::string& input_nmodl_text = reindent_text(test_case.input); From 158c400a52cba436512c29549520e7f0e81e2c34 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 10 Nov 2020 20:20:47 +0100 Subject: [PATCH 303/871] Print variable with different precision depending of LEGACY_UNITS (BlueBrain/nmodl#421) * Print variable with different precision depending of LEGACY_UNITS * Use more format instead of stringstream in codegen. NMODL Repo SHA: BlueBrain/nmodl@b9050fa77b66d4c1265cdb99e404affb87dbc4fe --- cmake/nmodl/CMakeLists.txt | 3 +++ src/nmodl/codegen/codegen_c_visitor.cpp | 26 ++++++++++++++----------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9691c6c2d3..8d0fa34f93 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -19,6 +19,9 @@ set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) option(NMODL_ENABLE_LEGACY_UNITS "Use original faraday, R, etc. instead of 2019 nist constants" OFF) +if(NMODL_ENABLE_LEGACY_UNITS) + add_definitions(-DUSE_LEGACY_UNITS) +endif() # ============================================================================= # Settings to enable project as submodule diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 670ddf9d6f..2d02dd9561 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -986,17 +986,17 @@ std::vector CodegenCVisitor::get_shadow_variables() { /****************************************************************************************/ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { - std::stringstream param_ss; + std::string param{}; for (auto iter = params.begin(); iter != params.end(); iter++) { - param_ss << "{}{} {}{}"_format(std::get<0>(*iter), - std::get<1>(*iter), - std::get<2>(*iter), - std::get<3>(*iter)); + param += "{}{} {}{}"_format(std::get<0>(*iter), + std::get<1>(*iter), + std::get<2>(*iter), + std::get<3>(*iter)); if (!utils::is_last(iter, params)) { - param_ss << ", "; + param += ", "; } } - return param_ss.str(); + return param; } @@ -1994,10 +1994,14 @@ void CodegenCVisitor::print_nmodl_constants() { printer->add_newline(2); printer->add_line("/** constants used in nmodl */"); for (const auto& it: info.factor_definitions) { - std::stringstream ss; - ss << "static const double " << it->get_node_name() << " = "; - ss << it->get_value()->get_value() << ";"; - printer->add_line(ss.str()); +#ifdef USE_LEGACY_UNITS + std::string format_string = "static const double {} = {:g};"; +#else + std::string format_string = "static const double {} = {:.18g};"; +#endif + printer->add_line(fmt::format(format_string.c_str(), + it->get_node_name(), + it->get_value()->get_value())); } } } From 5eeb63becac3ecd471d8d34ea2f562825cb23603 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 13 Nov 2020 10:22:42 +0100 Subject: [PATCH 304/871] In CMAKE use VERSION of project command (BlueBrain/nmodl#425) NMODL Repo SHA: BlueBrain/nmodl@f03e0df8f1263037e7f6c97838a132df9cd95411 --- cmake/nmodl/CMakeLists.txt | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 8d0fa34f93..0c9ea6fbf5 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -6,13 +6,14 @@ # ============================================================================= cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR) -project(NMODL CXX) +project( + NMODL + VERSION 0.2 + LANGUAGES CXX) # ============================================================================= # CMake common project settings # ============================================================================= -set(PROJECT_VERSION_MAJOR 0) -set(PROJECT_VERSION_MINOR 2) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -146,11 +147,6 @@ set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) # ============================================================================= include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/cli11/include) -# ============================================================================= -# Project version from git and project directories -# ============================================================================= -set(PROJECT_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}) - # generate file with version number from git and nrnunits.lib file path configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in ${PROJECT_BINARY_DIR}/src/config/config.cpp @ONLY) From a53dddf26916628d10e8e27c3534363d7b0fe980 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 13 Nov 2020 18:15:04 +0100 Subject: [PATCH 305/871] fixup! ispc visitor rounding small doubles (BlueBrain/nmodl#423) * ISPC visitor rounds doubles with too little precision (fmt default) * We need more precision because, in some mod files (i.e. TaDa_t or TXXDynamicSwitch) there are thresholds at 1e-12 fixes BlueBrain/nmodl#422 NMODL Repo SHA: BlueBrain/nmodl@02a35bfc05337b201120390aaea6fbf383ca5615 --- src/nmodl/codegen/codegen_ispc_visitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 1d7ee0aa21..d1f6ab8167 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -115,7 +115,7 @@ std::string CodegenIspcVisitor::double_to_string(double value) { return "{:.1f}d"_format(value); } if ((value <= 1.0) && (value >= -1.0)) { - return "{:f}d"_format(value); + return "{:.16f}d"_format(value); } else { auto e = std::log10(std::abs(value)); if (e < 0.0) { @@ -136,7 +136,7 @@ std::string CodegenIspcVisitor::float_to_string(float value) { return "{:.1f}"_format(value); } if ((value <= 1.0f) && (value >= -1.0f)) { - return "{:f}"_format(value); + return "{:.6f}"_format(value); } else { auto e = std::log10(std::abs(value)); if (e < 0.0f) { From 5622cd7ea3f15687bfa5386e2558a37cad7100c1 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 13 Nov 2020 18:47:02 +0100 Subject: [PATCH 306/871] Add NMODL_ENABLE_PYTHON_BINDINGS option to disable python bindings (BlueBrain/nmodl#428) * Add NMODL_ENABLE_PYTHON_BINDINGS option to disable python bindings - NMODL_ENABLE_PYTHON_BINDINGS=ON by default - if python bindings are not enabled, show warning at the import - do not add dependency with _nmodl target - use imp module to test existance of package * Address review comments and fix build error - fix typo - avoid oputput expression with add_custom_command - change one of the CI with -DNMODL_ENABLE_PYTHON_BINDINGS=OFF - add Pybind test if NMODL_ENABLE_PYTHON_BINDINGS=ON NMODL Repo SHA: BlueBrain/nmodl@c28fffa18103b7c74f8b0e8d6699509500a5946e --- cmake/nmodl/CMakeLists.txt | 4 ++ nmodl/__init__.py | 12 +++- src/nmodl/nmodl/CMakeLists.txt | 6 +- src/nmodl/pybind/CMakeLists.txt | 67 ++++++++++++++--------- test/nmodl/transpiler/unit/CMakeLists.txt | 15 +++-- 5 files changed, 67 insertions(+), 37 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 0c9ea6fbf5..9e7de7272c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -19,6 +19,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +# ============================================================================= +# Build options for NMODL +# ============================================================================= +option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) option(NMODL_ENABLE_LEGACY_UNITS "Use original faraday, R, etc. instead of 2019 nist constants" OFF) if(NMODL_ENABLE_LEGACY_UNITS) add_definitions(-DUSE_LEGACY_UNITS) diff --git a/nmodl/__init__.py b/nmodl/__init__.py index 705805af7c..b502d8e32f 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -1,4 +1,10 @@ -from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa -from ._nmodl import __version__ +import imp +try: + # check if nmodl binds are built before importing + imp.find_module('_nmodl') + from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa + from ._nmodl import __version__ + __all__ = ["NmodlDriver", "to_json", "to_nmodl"] +except ImportError: + print("[NMODL] [warning] :: Python bindings are not available") -__all__ = ["NmodlDriver", "to_json", "to_nmodl"] diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/nmodl/CMakeLists.txt index 4e84a27aba..2fb9dca3f9 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/nmodl/CMakeLists.txt @@ -21,7 +21,11 @@ target_link_libraries( # ============================================================================= # Add dependency with nmodl pytnon module (for consumer projects) # ============================================================================= -add_dependencies(nmodl _nmodl pywrapper) +add_dependencies(nmodl pywrapper) + +if(NMODL_ENABLE_PYTHON_BINDINGS) + add_dependencies(nmodl _nmodl) +endif() # ============================================================================= # Install executable diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 97a8f6998c..f4cc8dc43d 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -43,30 +43,38 @@ else() target_link_libraries(pywrapper PRIVATE pybind11::embed) endif() -# Note that LTO causes link time errors with GCC 8. To avoid this, we disable LTO for pybind using -# NO_EXTRAS. See #266. -pybind11_add_module( - _nmodl - NO_EXTRAS - ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp - ${PYBIND_GENERATED_SOURCES} - $ - $ - $ - $ - $) +# avoid _nmodl target if python bindings are disabled +if(NMODL_ENABLE_PYTHON_BINDINGS) + # ~~~ + # Note that LTO causes link time errors with GCC 8. To avoid this, we disable LTO + # for pybind using NO_EXTRAS. See #266. + # ~~~ + pybind11_add_module( + _nmodl + NO_EXTRAS + ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp + ${PYBIND_GENERATED_SOURCES} + $ + $ + $ + $ + $) -add_dependencies(_nmodl pyastgen) -add_dependencies(_nmodl lexer_obj) -add_dependencies(_nmodl util_obj) + add_dependencies(_nmodl pyastgen lexer_obj util_obj) + target_link_libraries(_nmodl PRIVATE fmt::fmt pyembed) -target_link_libraries(_nmodl PRIVATE fmt::fmt pyembed) + # in case of wheel, python module shouldn't link to wrapper library + if(LINK_AGAINST_PYTHON) + target_link_libraries(_nmodl PRIVATE pywrapper) + endif() -# in case of wheel, python module shouldn't link to wrapper library -if(LINK_AGAINST_PYTHON) - target_link_libraries(_nmodl PRIVATE pywrapper) + add_custom_command( + OUTPUT ${PROJECT_BINARY_DIR}/lib/nmodl + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl + DEPENDS ${NMODL_PYTHON_FILES_IN} _nmodl + COMMENT "-- COPYING NMODL PYTHON MODULE --") endif() add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) @@ -74,8 +82,7 @@ add_custom_command( OUTPUT ${NMODL_PYTHON_FILES_OUT} COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl ${PROJECT_BINARY_DIR}/lib/nmodl - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl - DEPENDS ${NMODL_PYTHON_FILES_IN} $ + DEPENDS ${NMODL_PYTHON_FILES_IN} COMMENT "-- COPYING NMODL PYTHON FILES --") # ============================================================================= @@ -88,12 +95,18 @@ file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/ # ============================================================================= # Install python binding components # ============================================================================= -# scikit already installs the package in /nmodl. If we add it another time things are installed -# twice with the wheel and in weird places. Let's just move the .so libs +# ~~~ +# scikit already installs the package in /nmodl. If we add it another time +# things are installed twice with the wheel and in weird places. Let's just +# move the .so libs +# ~~~ if(NOT LINK_AGAINST_PYTHON) - install(TARGETS _nmodl DESTINATION nmodl/) install(TARGETS pywrapper DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib) -elseif(SKBUILD) # skbuild needs the installation dir to be in nmodl to do the correct inplace + if(NMODL_ENABLE_PYTHON_BINDINGS) + install(TARGETS _nmodl DESTINATION nmodl/) + endif() +elseif(SKBUILD) + # skbuild needs the installation dir to be in nmodl to do the correct inplace install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl DESTINATION .) else() install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION lib) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 66666770bf..4973ef2c38 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -124,9 +124,12 @@ endforeach() # pybind11 tests # ============================================================================= add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/unit/ode) -add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest - ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) -foreach(test_name Ode Pybind) - set_tests_properties(${test_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) -endforeach() +set_tests_properties(Ode PROPERTIES ENVIRONMENT + PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) + +if(NMODL_ENABLE_PYTHON_BINDINGS) + add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest + ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) + set_tests_properties(Pybind PROPERTIES ENVIRONMENT + PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) +endif() From 58d648d6da840caee0e6a735d3447db8a6f142b9 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 18 Nov 2020 14:22:49 +0100 Subject: [PATCH 307/871] fallback to c generator when ispc keyword in mod file vars (BlueBrain/nmodl#426) * emit_fallback: map -> vector * Remove print_mechanism_global_var_structure overload in ispc * Refactoring ispc print wrappers * rename codegen_wrapper_routines as it is confusing with print_wrapper_routines and print_wrapper_routine * proper overload of print_wrapper_routines from codegenCvisitor instead of ad-hoc functions * Do not print structs in ispc if no blocks are compatible * Standardize print_mechanism_global_var_structure * Add some small helper functions * Complete fallback if ispc keywards in mod file * Add complete fallback warning * Add integration test * Add full list of ispc-specific keywords as incompatible var names * reduced ican.mod complexity * Simplified complete C fallback in case of ispc keywords: now we revert to base codegen in visit_program * Refactoring of function calls in visit_program * More consistent names * fixup! Remove imp.find_module for doc building NMODL Repo SHA: BlueBrain/nmodl@a216b0b10f8c955e6bc1a7d5b4101c526901cab5 --- nmodl/__init__.py | 5 +- src/nmodl/codegen/codegen_c_visitor.cpp | 71 ++-- src/nmodl/codegen/codegen_c_visitor.hpp | 32 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 3 +- src/nmodl/codegen/codegen_info.cpp | 12 - src/nmodl/codegen/codegen_info.hpp | 7 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 325 +++++++----------- src/nmodl/codegen/codegen_ispc_visitor.hpp | 47 +-- .../nmodl/transpiler/integration/mod/ican.mod | 12 + 9 files changed, 237 insertions(+), 277 deletions(-) create mode 100644 test/nmodl/transpiler/integration/mod/ican.mod diff --git a/nmodl/__init__.py b/nmodl/__init__.py index b502d8e32f..d0dfe8d4ad 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -1,10 +1,7 @@ -import imp try: - # check if nmodl binds are built before importing - imp.find_module('_nmodl') + # Try importing but catch exception in case bindings are not available from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa from ._nmodl import __version__ __all__ = ["NmodlDriver", "to_json", "to_nmodl"] except ImportError: print("[NMODL] [warning] :: Python bindings are not available") - diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 2d02dd9561..d8586f9539 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1260,6 +1260,14 @@ std::string CodegenCVisitor::ptr_type_qualifier() { return "__restrict__ "; } +/// Useful in ispc so that variables in the global struct get "uniform " +std::string CodegenCVisitor::global_var_struct_type_qualifier() { + return ""; +} + +void CodegenCVisitor::print_global_var_struct_decl() { + printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); +} std::string CodegenCVisitor::k_const() { return "const "; @@ -2108,7 +2116,7 @@ void CodegenCVisitor::print_namespace_stop() { */ void CodegenCVisitor::print_thread_getters() { - if (info.vectorize && info.derivimplicit_coreneuron_solver()) { + if (info.vectorize && info.derivimplicit_used()) { int tid = info.derivimplicit_var_thread_id; int list = info.derivimplicit_list_num; @@ -2370,7 +2378,9 @@ void CodegenCVisitor::print_coreneuron_includes() { * Note that static variables are already initialized to 0. We do the * same for some variables to keep same code as neuron. */ -void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { +void CodegenCVisitor::print_mechanism_global_var_structure() { + const auto qualifier = global_var_struct_type_qualifier(); + auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); @@ -2380,13 +2390,13 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { if (!info.ions.empty()) { for (const auto& ion: info.ions) { auto name = "{}_type"_format(ion.name); - printer->add_line("int {};"_format(name)); + printer->add_line("{}int {};"_format(qualifier, name)); codegen_global_variables.push_back(make_symbol(name)); } } if (info.point_process) { - printer->add_line("int point_type;"); + printer->add_line("{}int point_type;"_format(qualifier)); codegen_global_variables.push_back(make_symbol("point_type")); } @@ -2395,7 +2405,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(qualifier, float_type, name)); codegen_global_variables.push_back(make_symbol(name)); } } @@ -2411,27 +2421,28 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line("{} {}[{}];"_format(float_type, name, length)); + printer->add_line("{}{} {}[{}];"_format(qualifier, float_type, name, length)); } else { - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(qualifier, float_type, name)); } codegen_global_variables.push_back(var); } } if (!info.thread_variables.empty()) { - printer->add_line("int thread_data_in_use;"); - printer->add_line("{} thread_data[{}];"_format(float_type, info.thread_var_data_size)); + printer->add_line("{}int thread_data_in_use;"_format(qualifier)); + printer->add_line( + "{}{} thread_data[{}];"_format(qualifier, float_type, info.thread_var_data_size)); codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); symbol->set_as_array(info.thread_var_data_size); codegen_global_variables.push_back(symbol); } - printer->add_line("int reset;"); + printer->add_line("{}int reset;"_format(qualifier)); codegen_global_variables.push_back(make_symbol("reset")); - printer->add_line("int mech_type;"); + printer->add_line("{}int mech_type;"_format(qualifier)); codegen_global_variables.push_back(make_symbol("mech_type")); auto& globals = info.global_variables; @@ -2442,9 +2453,9 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line("{} {}[{}];"_format(float_type, name, length)); + printer->add_line("{}{} {}[{}];"_format(qualifier, float_type, name, length)); } else { - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(qualifier, float_type, name)); } codegen_global_variables.push_back(var); } @@ -2454,43 +2465,43 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { for (const auto& var: constants) { auto name = var->get_name(); auto value_ptr = var->get_value(); - printer->add_line("{} {};"_format(float_type, name)); + printer->add_line("{}{} {};"_format(qualifier, float_type, name)); codegen_global_variables.push_back(var); } } if (info.primes_size != 0) { - printer->add_line("int* slist1;"); - printer->add_line("int* dlist1;"); + printer->add_line("int* {}slist1;"_format(qualifier)); + printer->add_line("int* {}dlist1;"_format(qualifier)); codegen_global_variables.push_back(make_symbol("slist1")); codegen_global_variables.push_back(make_symbol("dlist1")); - if (info.derivimplicit_coreneuron_solver()) { - printer->add_line("int* slist2;"); + if (info.derivimplicit_used()) { + printer->add_line("int* {}slist2;"_format(qualifier)); codegen_global_variables.push_back(make_symbol("slist2")); } } if (info.table_count > 0) { - printer->add_line("double usetable;"); + printer->add_line("{}double usetable;"_format(qualifier)); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); for (const auto& block: info.functions_with_table) { auto name = block->get_node_name(); - printer->add_line("{} tmin_{};"_format(float_type, name)); - printer->add_line("{} mfac_{};"_format(float_type, name)); + printer->add_line("{}{} tmin_{};"_format(qualifier, float_type, name)); + printer->add_line("{}{} mfac_{};"_format(qualifier, float_type, name)); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); } for (const auto& variable: info.table_statement_variables) { auto name = "t_" + variable->get_name(); - printer->add_line("{}* {};"_format(float_type, name)); + printer->add_line("{}* {}{};"_format(float_type, qualifier, name)); codegen_global_variables.push_back(make_symbol(name)); } } if (info.vectorize) { - printer->add_line("ThreadDatum* {}ext_call_thread;"_format(ptr_type_qualifier())); + printer->add_line("ThreadDatum* {}ext_call_thread;"_format(qualifier)); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } @@ -2499,7 +2510,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool wrapper) { printer->add_newline(1); printer->add_line("/** holds object of global variable */"); - printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + print_global_var_struct_decl(); // create copy on the device print_global_variable_device_create_annotation(); @@ -2746,7 +2757,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->add_line("/** thread memory allocation callback */"); printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); - if (info.vectorize && info.derivimplicit_coreneuron_solver()) { + if (info.vectorize && info.derivimplicit_used()) { printer->add_line("thread[dith{}()].pval = NULL;"_format(info.derivimplicit_list_num)); } if (info.vectorize && (info.top_local_thread_size != 0)) { @@ -2775,7 +2786,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); // clang-format off - if (info.vectorize && info.derivimplicit_coreneuron_solver()) { + if (info.vectorize && info.derivimplicit_used()) { int n = info.derivimplicit_list_num; printer->add_line("free(thread[dith{}()].pval);"_format(n)); printer->add_line("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));"_format(n)); @@ -2929,7 +2940,7 @@ void CodegenCVisitor::print_global_variable_setup() { } // additional list for derivimplicit method - if (info.derivimplicit_coreneuron_solver()) { + if (info.derivimplicit_used()) { auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); auto slist2 = get_variable_name("slist2"); auto nprimes = info.primes_size; @@ -3268,7 +3279,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { printer->add_line("/** initialize channel */"); print_global_function_common_code(BlockType::Initial); - if (info.derivimplicit_coreneuron_solver()) { + if (info.derivimplicit_used()) { printer->add_newline(); int nequation = info.num_equations; int list_num = info.derivimplicit_list_num; @@ -3306,7 +3317,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_channel_iteration_tiling_block_end(); printer->end_block(1); - if (info.derivimplicit_coreneuron_solver()) { + if (info.derivimplicit_used()) { printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); } print_kernel_data_present_annotation_block_end(); @@ -4287,7 +4298,7 @@ void CodegenCVisitor::print_common_getters() { void CodegenCVisitor::print_data_structures() { - print_mechanism_global_var_structure(false); + print_mechanism_global_var_structure(); print_mechanism_range_var_structure(); print_ion_var_structure(); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index b935976a43..28c8945343 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -50,8 +50,10 @@ namespace codegen { * \enum BlockType * \brief Helper to represent various block types * + * Note: do not assign integers to these enums + * */ -enum class BlockType { +enum BlockType { /// initial block Initial, @@ -68,7 +70,10 @@ enum class BlockType { Watch, /// net_receive block - NetReceive + NetReceive, + + /// fake ending block type for loops on the enums. Keep it at the end + BlockTypeEnd }; @@ -882,6 +887,24 @@ class CodegenCVisitor: public visitor::AstVisitor { */ virtual std::string ptr_type_qualifier(); + /** + * The used global type qualifier + * + * For C code generation this is empty + * \return "" + * + * For ispc + * \return "uniform " + */ + virtual std::string global_var_struct_type_qualifier(); + + /** + * Instantiate global var instance + * + * For C code generation this is empty + * \return "" + */ + virtual void print_global_var_struct_decl(); /** * The used parameter type qualifier @@ -1035,9 +1058,8 @@ class CodegenCVisitor: public visitor::AstVisitor { /** * Print the structure that wraps all global variables used in the NMODL - * \param wrapper */ - virtual void print_mechanism_global_var_structure(bool wrapper); + void print_mechanism_global_var_structure(); /** @@ -1590,7 +1612,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print all classes * */ - virtual void print_data_structures(); + void print_data_structures(); /** diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 8bde54ce89..712874739e 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -219,7 +219,7 @@ void CodegenHelperVisitor::find_non_range_variables() { * slist and dlist represent the offsets for prime variables used. For * euler or derivimplicit methods its always first number. */ - if (info.derivimplicit_used) { + if (info.derivimplicit_used()) { info.derivimplicit_var_thread_id = 0; info.thread_data_index = 3; info.derivimplicit_list_num = 1; @@ -455,7 +455,6 @@ void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock& node) { } void CodegenHelperVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& node) { - info.derivimplicit_used = true; info.derivimplicit_callbacks.push_back(&node); } diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index cbf4a0dcf3..6e24b6da22 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -80,18 +80,6 @@ bool CodegenInfo::function_uses_table(std::string& name) const { return false; } -/** - * Check if coreneuron internal derivimplicit solver needs to be used - * - * - if derivimplicit method is not used or solve block is empty - * then there is nothing to do - * - if eigen solver block is used then coreneuron solver is not needed - */ - -bool CodegenInfo::derivimplicit_coreneuron_solver() const { - return !derivimplicit_callbacks.empty(); -} - /** * Check if NrnState node in the AST has EigenSolverBlock node * diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index ef818f7417..640cd2d661 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -335,9 +335,6 @@ struct CodegenInfo { /// non specific and ionic currents std::vector currents; - /// if mod file used dervimplicit method - bool derivimplicit_used = false; - /// all top level global blocks std::vector top_blocks; @@ -378,7 +375,9 @@ struct CodegenInfo { } /// if legacy derivimplicit solver from coreneuron to be used - bool derivimplicit_coreneuron_solver() const; + inline bool derivimplicit_used() const { + return !derivimplicit_callbacks.empty(); + } bool function_uses_table(std::string& name) const; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index d1f6ab8167..5b437952ea 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -25,6 +25,19 @@ using symtab::syminfo::Status; using visitor::RenameVisitor; +const std::vector CodegenIspcVisitor::incompatible_node_types = { + ast::AstNodeType::VERBATIM, + ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK, + ast::AstNodeType::EIGEN_LINEAR_SOLVER_BLOCK, + ast::AstNodeType::WATCH_STATEMENT, + ast::AstNodeType::TABLE_STATEMENT}; + +const std::unordered_set CodegenIspcVisitor::incompatible_var_names = { + "cdo", "cfor", "cif", "cwhile", "foreach", "foreach_active", + "foreach_tiled", "foreach_unique", "in", "noinline", "__vectorcall", "int8", + "int16", "int32", "int64", "launch", "print", "soa", + "sync", "task", "varying"}; + /****************************************************************************************/ /* Overloaded visitor methods */ /****************************************************************************************/ @@ -292,6 +305,23 @@ std::string CodegenIspcVisitor::ptr_type_qualifier() { } } +std::string CodegenIspcVisitor::global_var_struct_type_qualifier() { + if (wrapper_codegen) { + return CodegenCVisitor::global_var_struct_type_qualifier(); + } else { + return "uniform "; // @note: extra space needed to separate qualifier from var name. + } +} + +void CodegenIspcVisitor::print_global_var_struct_decl() { + if (wrapper_codegen) { + printer->start_block("extern \"C\""); + printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + printer->end_block(2); + } else { + printer->add_line("extern {} {}_global;"_format(global_struct(), info.mod_suffix)); + } +} std::string CodegenIspcVisitor::param_type_qualifier() { if (wrapper_codegen) { @@ -401,168 +431,6 @@ void CodegenIspcVisitor::print_compute_functions() { } -// @todo : use base visitor function with provision to override specific qualifiers -void CodegenIspcVisitor::print_mechanism_global_var_structure(bool wrapper) { - std::string decorator = ""; - if (!wrapper) { - decorator = "uniform "; - } - - auto float_type = default_float_data_type(); - printer->add_newline(2); - printer->add_line("/** all global variables */"); - printer->add_line("struct {} {}"_format(global_struct(), "{")); - printer->increase_indent(); - - if (!info.ions.empty()) { - for (const auto& ion: info.ions) { - auto name = "{}_type"_format(ion.name); - printer->add_line("{}int {};"_format(decorator, name)); - codegen_global_variables.push_back(make_symbol(name)); - } - } - - if (info.point_process) { - printer->add_line("{}int point_type;"_format(decorator)); - codegen_global_variables.push_back(make_symbol("point_type")); - } - - if (!info.state_vars.empty()) { - for (const auto& var: info.state_vars) { - auto name = var->get_name() + "0"; - auto symbol = program_symtab->lookup(name); - if (symbol == nullptr) { - printer->add_line("{}{} {};"_format(decorator, float_type, name)); - codegen_global_variables.push_back(make_symbol(name)); - } - } - } - - if (!info.vectorize) { - printer->add_line("{}{} v;"_format(decorator, float_type)); - codegen_global_variables.push_back(make_symbol("v")); - } - - auto& top_locals = info.top_local_variables; - if (!info.vectorize && !top_locals.empty()) { - for (const auto& var: top_locals) { - auto name = var->get_name(); - auto length = var->get_length(); - if (var->is_array()) { - printer->add_line("{}{} {}[{}];"_format(decorator, float_type, name, length)); - } else { - printer->add_line("{}{} {};"_format(decorator, float_type, name)); - } - codegen_global_variables.push_back(var); - } - } - - if (!info.thread_variables.empty()) { - printer->add_line("{}int thread_data_in_use;"_format(decorator)); - printer->add_line( - "{}{} thread_data[{}];"_format(decorator, float_type, info.thread_var_data_size)); - codegen_global_variables.push_back(make_symbol("thread_data_in_use")); - auto symbol = make_symbol("thread_data"); - symbol->set_as_array(info.thread_var_data_size); - codegen_global_variables.push_back(symbol); - } - - printer->add_line("{}int reset;"_format(decorator)); - codegen_global_variables.push_back(make_symbol("reset")); - - printer->add_line("{}int mech_type;"_format(decorator)); - codegen_global_variables.push_back(make_symbol("mech_type")); - - auto& globals = info.global_variables; - auto& constants = info.constant_variables; - - if (!globals.empty()) { - for (const auto& var: globals) { - auto name = var->get_name(); - auto length = var->get_length(); - if (var->is_array()) { - printer->add_line("{}{} {}[{}];"_format(decorator, float_type, name, length)); - } else { - printer->add_line("{}{} {};"_format(decorator, float_type, name)); - } - codegen_global_variables.push_back(var); - } - } - - if (!constants.empty()) { - for (const auto& var: constants) { - auto name = var->get_name(); - auto value_ptr = var->get_value(); - printer->add_line("{}{} {};"_format(decorator, float_type, name)); - codegen_global_variables.push_back(var); - } - } - - if (info.primes_size != 0) { - printer->add_line("int* {}slist1;"_format(decorator)); - printer->add_line("int* {}dlist1;"_format(decorator)); - codegen_global_variables.push_back(make_symbol("slist1")); - codegen_global_variables.push_back(make_symbol("dlist1")); - if (info.derivimplicit_used) { - printer->add_line("int* {}slist2;"_format(decorator)); - codegen_global_variables.push_back(make_symbol("slist2")); - } - } - - if (info.table_count > 0) { - printer->add_line("{}double usetable;"_format(decorator)); - codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); - - for (const auto& block: info.functions_with_table) { - auto name = block->get_node_name(); - printer->add_line("{}{} tmin_{};"_format(decorator, float_type, name)); - printer->add_line("{}{} mfac_{};"_format(decorator, float_type, name)); - codegen_global_variables.push_back(make_symbol("tmin_" + name)); - codegen_global_variables.push_back(make_symbol("mfac_" + name)); - } - - for (const auto& variable: info.table_statement_variables) { - auto name = "t_" + variable->get_name(); - printer->add_line("{}* {}{};"_format(float_type, decorator, name)); - codegen_global_variables.push_back(make_symbol(name)); - } - } - - if (info.vectorize) { - printer->add_line("ThreadDatum* {}ext_call_thread;"_format(decorator)); - codegen_global_variables.push_back(make_symbol("ext_call_thread")); - } - - printer->decrease_indent(); - printer->add_line("};"); - - printer->add_newline(1); - if (wrapper) { - printer->add_line("/** holds object of global variable */"); - printer->start_block("extern \"C\""); - printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); - printer->end_block(2); - } else { - printer->add_line("/** holds object of global variable */"); - printer->add_line("extern {} {}_global;"_format(global_struct(), info.mod_suffix)); - } -} - - -void CodegenIspcVisitor::print_data_structures() { - print_mechanism_global_var_structure(false); - print_mechanism_range_var_structure(); - print_ion_var_structure(); -} - - -void CodegenIspcVisitor::print_wrapper_data_structures() { - print_mechanism_global_var_structure(true); - print_mechanism_range_var_structure(); - print_ion_var_structure(); -} - - void CodegenIspcVisitor::print_ispc_globals() { printer->start_block("extern \"C\""); printer->add_line("extern double ispc_celsius;"); @@ -683,43 +551,94 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { } } -void CodegenIspcVisitor::determine_target() { +bool CodegenIspcVisitor::check_incompatibilities() { const auto& has_incompatible_nodes = [this](const ast::Ast& node) { return !collect_nodes(node, incompatible_node_types).empty(); }; + const auto get_name_from_symbol_type_vector = [](const SymbolType& var) -> const std::string& { + return var->get_name(); + }; + + // instance vars + if (check_incompatible_var_name(codegen_float_variables, + get_name_from_symbol_type_vector)) { + return true; + } + + if (check_incompatible_var_name(codegen_shadow_variables, + get_name_from_symbol_type_vector)) { + return true; + } + if (check_incompatible_var_name( + codegen_int_variables, [](const IndexVariableInfo& var) -> const std::string& { + return var.symbol->get_name(); + })) { + return true; + } + + + if (check_incompatible_var_name(info.currents, + [](const std::string& var) -> const std::string& { + return var; + })) { + return true; + } + + + // global vars + // info.top_local_variables is not checked because it should be addressed by the + // renameIspcVisitor + if (check_incompatible_var_name(info.global_variables, + get_name_from_symbol_type_vector)) { + return true; + } + + + if (check_incompatible_var_name(info.constant_variables, + get_name_from_symbol_type_vector)) { + return true; + } + + // ion vars + for (const auto& ion: info.ions) { + if (check_incompatible_var_name( + ion.writes, [](const std::string& var) -> const std::string& { return var; })) { + return true; + } + } + + + emit_fallback = std::vector(BlockType::BlockTypeEnd, false); + if (info.initial_node) { emit_fallback[BlockType::Initial] = - has_incompatible_nodes(*info.initial_node) || + emit_fallback[BlockType::Initial] || has_incompatible_nodes(*info.initial_node) || visitor::calls_function(*info.initial_node, "net_send") || info.require_wrote_conc; } else { - emit_fallback[BlockType::Initial] = info.net_receive_initial_node || + emit_fallback[BlockType::Initial] = emit_fallback[BlockType::Initial] || + info.net_receive_initial_node || info.require_wrote_conc; } - if (info.net_receive_node) { - emit_fallback[BlockType::NetReceive] = has_incompatible_nodes(*info.net_receive_node) || - visitor::calls_function(*info.net_receive_node, - "net_send"); - } + emit_fallback[BlockType::NetReceive] = + emit_fallback[BlockType::NetReceive] || + (info.net_receive_node && (has_incompatible_nodes(*info.net_receive_node) || + visitor::calls_function(*info.net_receive_node, "net_send"))); - if (nrn_cur_required()) { - if (info.breakpoint_node) { - emit_fallback[BlockType::Equation] = has_incompatible_nodes(*info.breakpoint_node); - } else { - emit_fallback[BlockType::Equation] = false; - } - } + emit_fallback[BlockType::Equation] = emit_fallback[BlockType::Equation] || + (nrn_cur_required() && info.breakpoint_node && + has_incompatible_nodes(*info.breakpoint_node)); - if (nrn_state_required()) { - if (info.nrn_state_block) { - emit_fallback[BlockType::State] = has_incompatible_nodes(*info.nrn_state_block); - } else { - emit_fallback[BlockType::State] = false; - } - } + emit_fallback[BlockType::State] = emit_fallback[BlockType::State] || + (nrn_state_required() && info.nrn_state_block && + has_incompatible_nodes(*info.nrn_state_block)); + + + return false; } + void CodegenIspcVisitor::move_procs_to_wrapper() { auto nameset = std::set(); @@ -761,7 +680,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { info.functions = target_functions; } -void CodegenIspcVisitor::codegen_wrapper_routines() { +void CodegenIspcVisitor::print_block_wrappers_initial_equation_state() { if (emit_fallback[BlockType::Initial]) { logger->warn("Falling back to C backend for emitting Initial block"); fallback_codegen.print_nrn_init(); @@ -790,29 +709,37 @@ void CodegenIspcVisitor::codegen_wrapper_routines() { void CodegenIspcVisitor::visit_program(ast::Program& node) { - fallback_codegen.setup(node); - CodegenCVisitor::visit_program(node); + setup(node); + + // we need setup to check incompatibilities + if (check_incompatibilities()) { + logger->warn( + "ISPC reserved keyword used as variable name in mod file. Using C++ backend as " + "fallback"); + print_backend_info(); + print_headers_include(); + fallback_codegen.visit_program(node); + } else { + fallback_codegen.setup(node); + // we do not want to call setup twice + print_codegen_routines(); + print_wrapper_routines(); + } } void CodegenIspcVisitor::print_codegen_routines() { codegen = true; - determine_target(); move_procs_to_wrapper(); print_backend_info(); print_headers_include(); print_nmodl_constants(); - print_data_structures(); - print_compute_functions(); - - // now print the ispc wrapper code - print_codegen_wrapper_routines(); } -void CodegenIspcVisitor::print_codegen_wrapper_routines() { +void CodegenIspcVisitor::print_wrapper_routines() { printer = wrapper_printer; wrapper_codegen = true; print_backend_info(); @@ -822,7 +749,7 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { CodegenCVisitor::print_nmodl_constants(); print_mechanism_info(); - print_wrapper_data_structures(); + print_data_structures(); print_global_variables_for_hoc(); print_common_getters(); @@ -858,7 +785,10 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { fallback_codegen.print_watch_check(); // requires C style variable declarations and loops if (emit_fallback[BlockType::NetReceive]) { - logger->warn("Found VERBATIM code in net_receive block, falling back to C backend"); + logger->warn( + "Found VERBATIM code or ISPC keyword in NET_RECEIVE block, using C++ backend as " + "fallback" + "backend"); fallback_codegen.print_net_receive_kernel(); fallback_codegen.print_net_receive_buffering(); } @@ -870,7 +800,8 @@ void CodegenIspcVisitor::print_codegen_wrapper_routines() { if (!emit_fallback[BlockType::NetReceive]) { print_net_receive_buffering_wrapper(); } - codegen_wrapper_routines(); + + print_block_wrappers_initial_equation_state(); print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 32be6ad704..5ff8bf0649 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -42,12 +42,9 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// ast nodes which are not compatible with ISPC target - const std::vector incompatible_node_types{ - ast::AstNodeType::VERBATIM, - ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK, - ast::AstNodeType::EIGEN_LINEAR_SOLVER_BLOCK, - ast::AstNodeType::WATCH_STATEMENT, - ast::AstNodeType::TABLE_STATEMENT}; + static const std::vector incompatible_node_types; + + static const std::unordered_set incompatible_var_names; /// flag to indicate if visitor should print the the wrapper code bool wrapper_codegen = false; @@ -56,7 +53,8 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// supported CodegenCVisitor fallback_codegen; - std::map emit_fallback; + std::vector emit_fallback = + std::vector(static_cast(BlockType::BlockTypeEnd), false); std::vector wrapper_procedures; std::vector wrapper_functions; @@ -83,6 +81,9 @@ class CodegenIspcVisitor: public CodegenCVisitor { std::string ptr_type_qualifier() override; + std::string global_var_struct_type_qualifier() override; + + void print_global_var_struct_decl() override; std::string param_type_qualifier() override; @@ -168,18 +169,8 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_wrapper_routine(const std::string& wrapper_function, BlockType type); - /// wrapper/caller routines for nrn_state and nrn_cur - void codegen_wrapper_routines(); - - - /// structure that wraps all global variables in the mod file - void print_mechanism_global_var_structure(bool wrapper) override; - - - void print_data_structures() override; - - - void print_wrapper_data_structures(); + /// print initial equation and state wrapper + void print_block_wrappers_initial_equation_state(); void print_ispc_globals(); @@ -201,8 +192,19 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// find out for main compute routines whether they are suitable to be emitted in ISPC backend - void determine_target(); - + bool check_incompatibilities(); + + /// check incompatible name var + template + bool check_incompatible_var_name(const std::vector& vec, + const std::string& get_name(const T&)) { + for (const auto& var: vec) { + if (incompatible_var_names.count(get_name(var))) { + return true; + } + } + return false; + } /// move procedures and functions unused by compute kernels into the wrapper void move_procs_to_wrapper(); @@ -211,8 +213,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// entry point to code generation void print_codegen_routines() override; - - void print_codegen_wrapper_routines(); + void print_wrapper_routines() override; public: CodegenIspcVisitor(const std::string& mod_file, diff --git a/test/nmodl/transpiler/integration/mod/ican.mod b/test/nmodl/transpiler/integration/mod/ican.mod new file mode 100644 index 0000000000..0cbbbb4a3a --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/ican.mod @@ -0,0 +1,12 @@ +: simple first-order model of calcium dynamics + +NEURON { +SUFFIX ican +USEION n READ ni, in WRITE ni +RANGE n +} + +ASSIGNED { +in (mA/cm2) +} + From 6f7610ffb9ea0ab6872c107698c96086cedc7735 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 18 Nov 2020 19:29:57 +0100 Subject: [PATCH 308/871] Don't install pycache files and optional executables (BlueBrain/nmodl#430) Optional executable are installed only if compiled in debug NMODL Repo SHA: BlueBrain/nmodl@6d39a657b8908933875f2f09b7dede3a9748757b --- src/nmodl/lexer/CMakeLists.txt | 6 +++--- src/nmodl/parser/CMakeLists.txt | 6 +++--- src/nmodl/pybind/CMakeLists.txt | 10 ++++++++-- src/nmodl/visitors/CMakeLists.txt | 3 ++- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 1c88d996ad..483860bd6b 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -195,8 +195,8 @@ target_link_libraries(units_lexer lexer util) # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer) -install(TARGETS c_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer) -install(TARGETS units_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer) +install(TARGETS nmodl_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) +install(TARGETS c_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) +install(TARGETS units_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index c8308ef4c5..9ed586043a 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -15,6 +15,6 @@ target_link_libraries(units_parser util visitor lexer) # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser) -install(TARGETS c_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser) -install(TARGETS units_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser) +install(TARGETS nmodl_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) +install(TARGETS c_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) +install(TARGETS units_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index f4cc8dc43d..4eaa8efefa 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -107,7 +107,13 @@ if(NOT LINK_AGAINST_PYTHON) endif() elseif(SKBUILD) # skbuild needs the installation dir to be in nmodl to do the correct inplace - install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl DESTINATION .) + install( + DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl + DESTINATION . + PATTERN "__pycache__" EXCLUDE) else() - install(DIRECTORY ${CMAKE_BINARY_DIR}/lib/ DESTINATION lib) + install( + DIRECTORY ${CMAKE_BINARY_DIR}/lib/ + DESTINATION lib + PATTERN "__pycache__" EXCLUDE) endif() diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index e069df9747..33dc0f61b8 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -86,4 +86,5 @@ target_link_libraries( # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_visitor DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor) +install(TARGETS nmodl_visitor DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor CONFIGURATIONS + Debug) From 8e391b4d3c3afc52561bf1e5c3281303b4f50cc2 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 19 Nov 2020 23:14:03 +0100 Subject: [PATCH 309/871] Put nrnunits.lib into share/nmodl instead of share (BlueBrain/nmodl#429) NMODL Repo SHA: BlueBrain/nmodl@8adf896310e6a80a2c10a14153f2a48cad6e6fb8 --- cmake/nmodl/CMakeLists.txt | 6 +++--- src/nmodl/config/config.cpp.in | 2 +- src/nmodl/config/config.h | 2 +- src/nmodl/lexer/unit.ll | 2 +- src/nmodl/parser/unit.yy | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9e7de7272c..2378f62568 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -214,10 +214,10 @@ else() set(LegacyY "/") set(LegacyN "") endif() -configure_file(share/nrnunits.lib.in ${CMAKE_CURRENT_BINARY_DIR}/share/nrnunits.lib @ONLY) +configure_file(share/nrnunits.lib.in ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib @ONLY) # ============================================================================= # Install unit database to share # ============================================================================= -install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nrnunits.lib - DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share/nmodl) diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index f6b77a2420..52ad0e2913 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -21,4 +21,4 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; * from CMAKE_INSTALL_PREFIX. */ std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = - {"@CMAKE_INSTALL_PREFIX@/share/nrnunits.lib", "@NMODL_PROJECT_BINARY_DIR@/share/nrnunits.lib"}; + {"@CMAKE_INSTALL_PREFIX@/share/nmodl/nrnunits.lib", "@NMODL_PROJECT_BINARY_DIR@/share/nmodl/nrnunits.lib"}; diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index b6488830bb..13ec4b548a 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -53,7 +53,7 @@ struct NrnUnitsLib { static std::string get_path() { // first look for NMODLHOME env variable if (const char* nmodl_home = std::getenv("NMODLHOME")) { - auto path = std::string(nmodl_home) + "/share/nrnunits.lib"; + auto path = std::string(nmodl_home) + "/share/nmodl/nrnunits.lib"; NRNUNITSLIB_PATH.emplace(NRNUNITSLIB_PATH.begin(), path); } diff --git a/src/nmodl/lexer/unit.ll b/src/nmodl/lexer/unit.ll index cc149c6ca9..1665082048 100755 --- a/src/nmodl/lexer/unit.ll +++ b/src/nmodl/lexer/unit.ll @@ -3,7 +3,7 @@ * @brief Flex lexer implementation reading Units of Neuron like MOD2C * * @brief Parsing unit definition file for use in NMODL - * https://github.com/BlueBrain/mod2c/blob/master/share/nrnunits.lib + * https://github.com/BlueBrain/nmodl/blob/master/share/nrnunits.lib.in *****************************************************************************/ %{ diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index 290c1f76f2..5544217c54 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -5,7 +5,7 @@ * Parsing Unit definition file for use in NMODL. * * The parser is designed to parse the nrnunits.lib file which is also used by MOD2C - * https://github.com/BlueBrain/mod2c/blob/master/share/nrnunits.lib + * https://github.com/BlueBrain/nmodl/blob/master/share/nrnunits.lib.in *****************************************************************************/ %code requires From 87c9fc3e7826ca723c134384f94a93deebad9efd Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 24 Nov 2020 12:02:43 +0100 Subject: [PATCH 310/871] Build our own cmake 3.3.2 in CI (BlueBrain/nmodl#435) * Use binary cmake release v 3.3.2 - with homebrew one has to go with whatever version they choose, which is not testing our minimum version and can cause unrelated failures. NMODL Repo SHA: BlueBrain/nmodl@3194e5ae51c1974c95f4418b2a7775cd5cdc7666 --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index ef86233fb5..111c137db1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -45,7 +45,6 @@ addons: packages: - flex - bison - - cmake - dvipng - doxygen - libboost-all-dev @@ -61,7 +60,6 @@ addons: homebrew: packages: - boost - - cmake - flex - bison - openmpi @@ -80,9 +78,14 @@ before_install: brew unlink python@2; brew link --overwrite python@3.8; export SDKROOT="$(xcrun --show-sdk-path)"; + curl -L -o cmake-3.3.2.tar.gz https://github.com/Kitware/CMake/releases/download/v3.3.2/cmake-3.3.2-Darwin-x86_64.tar.gz; + mkdir cmake && tar -xzf cmake-3.3.2.tar.gz -C cmake --strip-components=3; else pyenv global $PYTHON_VERSION; + curl -L -o cmake-3.3.2.tar.gz https://github.com/Kitware/CMake/releases/download/v3.3.2/cmake-3.3.2-Linux-x86_64.tar.gz; + mkdir cmake && tar -xzf cmake-3.3.2.tar.gz -C cmake --strip-components=1; fi + - export PATH=$(pwd)/cmake/bin:$PATH - eval "${MATRIX_EVAL}" #============================================================================= From 3d1b48250680dc09a5fed21ad181df8bffff8e4f Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 24 Nov 2020 22:26:58 +0100 Subject: [PATCH 311/871] Fix units constant in ISPC backend (BlueBrain/nmodl#434) * fix-up ispc units constants : issue BlueBrain/nmodl#433 - units constants were hard-coded so no custom constants, no unit changes, no double_to_string handling - I leave to double_to_string to handle how to chop doubles * fix indentation issue in travis.yaml fixes BlueBrain/nmodl#433 Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@0b2073326308211af3017ffc3f1486eab33692af --- .travis.yml | 6 +++--- src/nmodl/codegen/codegen_ispc_visitor.cpp | 17 +++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 111c137db1..8d0de6eb05 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,13 +79,13 @@ before_install: brew link --overwrite python@3.8; export SDKROOT="$(xcrun --show-sdk-path)"; curl -L -o cmake-3.3.2.tar.gz https://github.com/Kitware/CMake/releases/download/v3.3.2/cmake-3.3.2-Darwin-x86_64.tar.gz; - mkdir cmake && tar -xzf cmake-3.3.2.tar.gz -C cmake --strip-components=3; + mkdir -p $HOME/cmake && tar -xzf cmake-3.3.2.tar.gz -C $HOME/cmake --strip-components=3; else pyenv global $PYTHON_VERSION; curl -L -o cmake-3.3.2.tar.gz https://github.com/Kitware/CMake/releases/download/v3.3.2/cmake-3.3.2-Linux-x86_64.tar.gz; - mkdir cmake && tar -xzf cmake-3.3.2.tar.gz -C cmake --strip-components=1; + mkdir -p $HOME/cmake && tar -xzf cmake-3.3.2.tar.gz -C $HOME/cmake --strip-components=1; fi - - export PATH=$(pwd)/cmake/bin:$PATH + - export PATH=$HOME/cmake/bin:$PATH - eval "${MATRIX_EVAL}" #============================================================================= diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 5b437952ea..7011f470f2 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -477,12 +477,17 @@ void CodegenIspcVisitor::print_headers_include() { } void CodegenIspcVisitor::print_nmodl_constants() { - printer->add_newline(2); - printer->add_line("/** constants used in nmodl. */"); - // we use here macros to work around ispc's PI being declared in global namespace - printer->add_line("static const uniform double FARADAY = 96485.3d;"); - printer->add_line("static const uniform double ISPC_PI = 3.14159d;"); - printer->add_line("static const uniform double R = 8.3145d;"); + if (!info.factor_definitions.empty()) { + printer->add_newline(2); + printer->add_line("/** constants used in nmodl */"); + for (auto& it: info.factor_definitions) { + const std::string name = it->get_node_name() == "PI" ? "ISPC_PI" : it->get_node_name(); + std::string format_string = "static const uniform double {} = {};"; + printer->add_line(fmt::format(format_string.c_str(), + name, + double_to_string(it->get_value()->get_value()))); + } + } } void CodegenIspcVisitor::print_wrapper_headers_include() { From d8f5d86e1953107c4c0b4b86e9a3a443ab9ef6ce Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 26 Nov 2020 16:33:44 +0100 Subject: [PATCH 312/871] Fix FindPythonModule for cmake >= 3.19 (BlueBrain/nmodl#437) find_package_handle_standard_args is no more usable outside a find_package function Mimick it with basic if tests NMODL Repo SHA: BlueBrain/nmodl@3a29426b84525e155a35f32b9ae380b340e71481 --- cmake/nmodl/FindPythonModule.cmake | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index 0af45ff948..e2ac0649fc 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -59,11 +59,15 @@ macro(find_python_module module) endif() endif() - find_package_handle_standard_args( - ${module} REQUIRED_VARS ${module_upper}_LOCATION _${module_upper}_VERSION_MATCH VERSION_VAR - ${module_upper}_VERSION_STRING) - if(NOT ${module}_FIND_OPTIONAL AND NOT _${module_upper}_VERSION_MATCH) - message(FATAL_ERROR "Missing python module ${module}") + if(NOT ${module}_FIND_OPTIONAL) + if(NOT ${module_upper}_LOCATION) + message(FATAL_ERROR "Missing python module \"${module}\"") + elseif(NOT _${module_upper}_VERSION_MATCH) + message( + FATAL_ERROR + "Found module \"${module}\", but version mismatch. Asked for \"${${module}_FIND_VERSION}\" but found \"${${module_upper}_VERSION_STRING}\"" + ) + endif() endif() mark_as_advanced(${module_upper}_LOCATION) endif(NOT ${module_upper}_FOUND) From 244eb7e55de327dd936a7129d17a905921c6485d Mon Sep 17 00:00:00 2001 From: WeinaJi Date: Fri, 27 Nov 2020 23:17:20 +0100 Subject: [PATCH 313/871] Solve parser error on numbers with leading + (BlueBrain/nmodl#419) * numbers can have leading plus operator e.g. +52 * udpdate nmodl parser to accept `+ NUMBER` * add unit test for parser fixes BlueBrain/nmodl#52 NMODL Repo SHA: BlueBrain/nmodl@246ab9ad4e8556e04f13aacd63f8454fb6dbfef8 --- src/nmodl/parser/nmodl.yy | 4 ++++ .../unit/utils/nmodl_constructs.cpp | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index d3ec9df238..5d998819e4 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -706,6 +706,10 @@ number : NUMBER $2->negate(); $$ = $2; } + | "+" NUMBER + { + $$ = $2; + } ; diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index c09b15af5a..e57f63a77a 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -566,6 +566,27 @@ std::map nmodl_valid_constructs{ } }, + { + "constant_block_2", + { + "CONSTANT block with signed values", + R"( + CONSTANT { + xx = +1 + yy2 = -1.1 + ee = 1e-06 (gg) + } + )", + R"( + CONSTANT { + xx = 1 + yy2 = -1.1 + ee = 1e-06 (gg) + } + )" + } + }, + { "plot_declare_1", { From 6ef50bc94854e20970ccdd889e7ed31e3c68e78e Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Sun, 29 Nov 2020 11:46:05 +0100 Subject: [PATCH 314/871] Print build status after cmake configure stage and update INSTALL.md (BlueBrain/nmodl#440) * Print build status after cmake configure stage - print table with different build options, flags and paths used that can be helpful for debugging - fix git revision date for older git version * fix build error when CMAKE_BUILD_TYPE is not specified * fix brew install paths in INSTALL.md NMODL Repo SHA: BlueBrain/nmodl@350263c12c9cef25025e8c5f6403be8dfce597bf --- INSTALL.md | 2 +- cmake/nmodl/CMakeLists.txt | 46 +++++++++++++++++++++++++++++++++++ cmake/nmodl/GitRevision.cmake | 6 ++--- 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 7e79b9bf7c..e987a9d8c5 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -43,7 +43,7 @@ pip3 install Jinja2 PyYAML pytest sympy Make sure to have latest flex/bison in $PATH : ```sh -export PATH=/usr/local/opt/flex:/usr/local/opt/bison:/usr/local/bin/:$PATH +export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:/usr/local/bin/:$PATH ``` ### On Ubuntu diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 2378f62568..1f515a036e 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -221,3 +221,49 @@ configure_file(share/nrnunits.lib.in ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrn # ============================================================================= install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share/nmodl) + +# to print compiler flags in the build status +if(CMAKE_BUILD_TYPE) + string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_UPPER) + set(COMPILER_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_TYPE_UPPER}}") +else() + set(COMPILER_FLAGS "${CMAKE_CXX_FLAGS}") +endif() + +# ============================================================================= +# Build status +# ============================================================================= +message(STATUS "") +message(STATUS "Configured NMODL ${PROJECT_VERSION} (${GIT_REVISION})") +message(STATUS "") +string(TOLOWER "${CMAKE_GENERATOR}" cmake_generator_tolower) +if(cmake_generator_tolower MATCHES "makefile") + message(STATUS "Some things you can do now:") + message(STATUS "--------------------+--------------------------------------------------------") + message(STATUS "Command | Description") + message(STATUS "--------------------+--------------------------------------------------------") + message(STATUS "make | Build the project") + message(STATUS "make test | Run unit tests") + message(STATUS "make install | Will install NMODL to: ${CMAKE_INSTALL_PREFIX}") + message(STATUS "--------------------+--------------------------------------------------------") + message(STATUS " Build option | Status") + message(STATUS "--------------------+--------------------------------------------------------") + message(STATUS "CXX COMPILER | ${CMAKE_CXX_COMPILER}") + message(STATUS "COMPILE FLAGS | ${COMPILER_FLAGS}") + message(STATUS "Build Type | ${CMAKE_BUILD_TYPE}") + message(STATUS "Legacy Units | ${NMODL_ENABLE_LEGACY_UNITS}") + message(STATUS "Python Bindings | ${NMODL_ENABLE_PYTHON_BINDINGS}") + message(STATUS "Flex | ${FLEX_EXECUTABLE}") + message(STATUS "Bison | ${BISON_EXECUTABLE}") + message(STATUS "Python | ${PYTHON_EXECUTABLE}") + if(NMODL_CLANG_FORMAT) + message(STATUS "Clang Format | ${ClangFormat_EXECUTABLE}") + endif() + if(NMODL_CMAKE_FORMAT) + message(STATUS "Cmake Format | ${CMakeFormat_EXECUTABLE}") + endif() + message(STATUS "--------------+--------------------------------------------------------------") + message(STATUS " See documentation : https://github.com/BlueBrain/nmodl/") + message(STATUS "--------------+--------------------------------------------------------------") +endif() +message(STATUS "") diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index e1b190009d..3c7bc594fa 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -8,14 +8,14 @@ if(GIT_FOUND) # get last commit sha1 execute_process( COMMAND ${GIT_EXECUTABLE} log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} OUTPUT_VARIABLE GIT_REVISION_SHA1 ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # get last commit date execute_process( - COMMAND ${GIT_EXECUTABLE} log -1 --format=%ad --date=format:"%d-%m-%Y %H:%M" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${GIT_EXECUTABLE} show -s --format=%ci + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} OUTPUT_VARIABLE GIT_REVISION_DATE ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) From 97ae6bcc2d1f2b62f67d93e3b0ba12eb378ecbc1 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Thu, 3 Dec 2020 20:57:12 +0100 Subject: [PATCH 315/871] Automated python wheel building (BlueBrain/nmodl#420) Following the work done in NEURON, this adds the necessary scripts for building linux and osx python wheels for nmodl. * Build scripts for automated wheel building * When nrnunits.lib is not found we print all the directories that have been searched. * Added a test script for wheels * Support nightly package in shim * set PYTHONPATH in shim for embedded python Thanks to @ferdonline @pramodk and @cattabiani for reviews and discussions This closes BlueBrain/nmodl#414 BlueBrain/nmodl#417 NMODL Repo SHA: BlueBrain/nmodl@4bbd25d15ac17188297322829f6bfdd63942e4e0 --- pywheel/shim/_binwrapper.py | 7 +++++++ src/nmodl/config/config.h | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pywheel/shim/_binwrapper.py b/pywheel/shim/_binwrapper.py index 445cc59bbc..fe2a920e06 100755 --- a/pywheel/shim/_binwrapper.py +++ b/pywheel/shim/_binwrapper.py @@ -16,6 +16,10 @@ def _config_exe(exe_name): package_name = "nmodl" + if package_name not in working_set.by_key: + print ("INFO : Using nmodl-nightly Package (Developer Version)") + package_name = 'nmodl-nightly' + assert ( package_name in working_set.by_key ), "NMODL package not found! Verify PYTHONPATH" @@ -37,6 +41,9 @@ def _config_exe(exe_name): # add nmodl home to environment (i.e. necessary for nrnunits.lib) os.environ["NMODLHOME"] = NMODL_HOME + # set PYTHONPATH for embedded python to properly find the nmodl module + os.environ["PYTHONPATH"] = working_set.by_key[package_name].location + ':' + os.environ.get("PYTHONPATH", "") + return os.path.join(NMODL_BIN, exe_name) diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index 13ec4b548a..b6567e503e 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -64,7 +65,12 @@ struct NrnUnitsLib { return path; } } - throw std::runtime_error("Could not found nrnunits.lib"); + std::ostringstream err_msg; + err_msg << "Could not find nrnunits.lib in any of:\n"; + for (const auto& path: NRNUNITSLIB_PATH) { + err_msg << path << "\n"; + } + throw std::runtime_error(err_msg.str()); } }; From da8cceb80079f4163d20b834f9ad73e6a5f70563 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 4 Dec 2020 10:54:56 +0100 Subject: [PATCH 316/871] Avoid rebuilding of parser targets (BlueBrain/nmodl#439) * In BlueBrain/nmodl#385 we used common position and location classes from nmodl parser * But output of CMake targets were not updated resulting in regenerating parser files every time fixes BlueBrain/nmodl#413 NMODL Repo SHA: BlueBrain/nmodl@ac46928a78cba40fa97900835814cfc765b143ff --- src/nmodl/lexer/CMakeLists.txt | 6 ------ src/nmodl/pybind/CMakeLists.txt | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 483860bd6b..c42b5b41a7 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -93,8 +93,6 @@ add_custom_command( add_custom_command( OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/location.hh - ${PROJECT_BINARY_DIR}/src/parser/diffeq/position.hh ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy @@ -108,8 +106,6 @@ add_custom_command( add_custom_command( OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/location.hh - ${PROJECT_BINARY_DIR}/src/parser/c/position.hh ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy @@ -120,8 +116,6 @@ add_custom_command( add_custom_command( OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/location.hh - ${PROJECT_BINARY_DIR}/src/parser/unit/position.hh ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 4eaa8efefa..50b8339879 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -15,7 +15,7 @@ foreach( visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) - list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/nmodl/${file}) + list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/lib/nmodl/${file}) endforeach() add_library(pyembed ${CMAKE_CURRENT_SOURCE_DIR}/pyembed.cpp) From 95e856f7c1b40e872394b05aa8ab7fe49bc43238 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Sat, 5 Dec 2020 10:00:29 +0100 Subject: [PATCH 317/871] Store double and float as std::string in the AST (BlueBrain/nmodl#431) * Store double and float as std::string in the AST - when double/float values are parsed, stored in AST and printed back in the MOD file or C++ code, it could result into slightly different floating point value than the mod file - to avoid this and keep compatibility with neuron, we can store float/double as original parsed token string * Add stringutils::to_string for precision flexibility * Fix various tests - tests that were converting 1.0 to 1 or 1e-1 to 0.1 are now correctly printing double values - if double is used in ODE statement that processed by sympy then they are often transformed due to use of %.16g in to_string - FIX ME: some changes in units are still not clear and need to be verified * Code generator now print double units as string - Units visitor takes care of storing values with legacy or modern units values * Fix units constant and ispc codegen visitor * double_to_string now takes care of printing values with .1f or directly pritning string. * Updated doc with some clarification to ISPC discrepancies * NOTE : ISPC backend also works fine for current use cases. But corner cases can be checked as part of BlueBrain/nmodl#442. fixes BlueBrain/nmodl#144 NMODL Repo SHA: BlueBrain/nmodl@31d7c4f4513ed85d2c2da61623454f3368ab6570 --- docs/nmodl/transpiler/contents/visitors.rst | 6 +- src/nmodl/codegen/codegen_c_visitor.cpp | 38 ++-- src/nmodl/codegen/codegen_c_visitor.hpp | 4 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 20 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 4 +- src/nmodl/language/nmodl.yaml | 4 +- src/nmodl/language/node_info.py | 4 +- .../templates/ast/node_class.template | 14 +- src/nmodl/lexer/nmodl.ll | 2 +- src/nmodl/lexer/nmodl_utils.cpp | 4 +- src/nmodl/lexer/nmodl_utils.hpp | 2 +- src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/utils/string_utils.hpp | 21 +++ .../visitors/constant_folder_visitor.cpp | 8 +- src/nmodl/visitors/units_visitor.cpp | 9 +- test/nmodl/transpiler/unit/lexer/tokens.cpp | 2 +- .../unit/utils/nmodl_constructs.cpp | 16 +- .../transpiler/unit/visitor/neuron_solve.cpp | 4 +- .../transpiler/unit/visitor/solve_block.cpp | 12 +- .../transpiler/unit/visitor/steadystate.cpp | 4 +- .../unit/visitor/sympy_conductance.cpp | 8 +- .../transpiler/unit/visitor/sympy_solver.cpp | 174 +++++++++--------- test/nmodl/transpiler/unit/visitor/units.cpp | 30 +-- 23 files changed, 209 insertions(+), 183 deletions(-) diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index e97bb5e49b..6f945d55ac 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -119,8 +119,8 @@ If predefined visitors are limited, we can implement new visitor using :class:`n >>> modast.accept(d_visitor) 0.05 0.1 - 0.0001 - 10000.0 - 2.0 + 1e-4 + 10000 + 2 1.0 diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index d8586f9539..e7958551ae 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -75,8 +75,7 @@ void CodegenCVisitor::visit_float(Float& node) { if (!codegen) { return; } - auto value = node.eval(); - printer->add_text(float_to_string(value)); + printer->add_text(float_to_string(node.get_value())); } @@ -84,8 +83,7 @@ void CodegenCVisitor::visit_double(Double& node) { if (!codegen) { return; } - auto value = node.eval(); - printer->add_text(double_to_string(value)); + printer->add_text(double_to_string(node.get_value())); } @@ -438,25 +436,27 @@ int CodegenCVisitor::position_of_int_var(const std::string& name) const { /** - * \details We can directly use to_string method but if user specify 7.0 then it gets - * printed as 7 (as integer). To avoid this, we use below wrapper. But note - * that there are still issues. For example, if 1.1 is not exactly represented - * in floating point, then it gets printed as 1.0999999999999. May be better - * to use std::to_string in else part? + * \details We can directly print value but if user specify value as integer then + * then it gets printed as an integer. To avoid this, we use below wrapper. + * If user has provided integer then it gets printed as 1.0 (similar to mod2c + * and neuron where ".0" is appended). Otherwise we print double variables as + * they are represented in the mod file by user. */ -std::string CodegenCVisitor::double_to_string(double value) { +std::string CodegenCVisitor::double_to_string(const std::string& s_value) { + double value = std::stod(s_value); if (std::ceil(value) == value) { return "{:.1f}"_format(value); } - return "{:.16g}"_format(value); + return s_value; } -std::string CodegenCVisitor::float_to_string(float value) { +std::string CodegenCVisitor::float_to_string(const std::string& s_value) { + float value = std::stof(s_value); if (std::ceil(value) == value) { - return "{:.1f}f"_format(value); + return "{:.1f}"_format(value); } - return "{:.16g}f"_format(value); + return s_value; } @@ -2002,14 +2002,8 @@ void CodegenCVisitor::print_nmodl_constants() { printer->add_newline(2); printer->add_line("/** constants used in nmodl */"); for (const auto& it: info.factor_definitions) { -#ifdef USE_LEGACY_UNITS - std::string format_string = "static const double {} = {:g};"; -#else - std::string format_string = "static const double {} = {:.18g};"; -#endif - printer->add_line(fmt::format(format_string.c_str(), - it->get_node_name(), - it->get_value()->get_value())); + printer->add_line("static const double {} = {};"_format(it->get_node_name(), + it->get_value()->get_value())); } } } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 28c8945343..3c8cd98cec 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -578,7 +578,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param value The number to convert * \return Its string representation */ - virtual std::string double_to_string(double value); + virtual std::string double_to_string(const std::string& value); /** @@ -586,7 +586,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param value The number to convert * \return Its string representation */ - virtual std::string float_to_string(float value); + virtual std::string float_to_string(const std::string& value); /** diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 7011f470f2..6d42162727 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -123,7 +123,16 @@ void CodegenIspcVisitor::visit_local_list_statement(ast::LocalListStatement& nod /* Routines must be overloaded in backend */ /****************************************************************************************/ -std::string CodegenIspcVisitor::double_to_string(double value) { +/** + * \todo : In ISPC we have to explicitly append `d` to a floating point number + * otherwise it is treated as float. A value stored in the AST can be in + * scientific notation and hence we can't just append `d` to the string. + * Hence, we have to print number with .16f and then append `d`. But note + * that this will result into discrepancy between C++ backend and ISPC + * backend when floating point number is not exactly represented with .16f. + */ +std::string CodegenIspcVisitor::double_to_string(const std::string& s_value) { + double value = std::stod(s_value); if (std::ceil(value) == value) { return "{:.1f}d"_format(value); } @@ -144,7 +153,8 @@ std::string CodegenIspcVisitor::double_to_string(double value) { } -std::string CodegenIspcVisitor::float_to_string(float value) { +std::string CodegenIspcVisitor::float_to_string(const std::string& s_value) { + float value = std::stof(s_value); if (std::ceil(value) == value) { return "{:.1f}"_format(value); } @@ -482,10 +492,8 @@ void CodegenIspcVisitor::print_nmodl_constants() { printer->add_line("/** constants used in nmodl */"); for (auto& it: info.factor_definitions) { const std::string name = it->get_node_name() == "PI" ? "ISPC_PI" : it->get_node_name(); - std::string format_string = "static const uniform double {} = {};"; - printer->add_line(fmt::format(format_string.c_str(), - name, - double_to_string(it->get_value()->get_value()))); + const std::string value = it->get_value()->get_value(); + printer->add_line("static const uniform double {} = {};"_format(name, value)); } } } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 5ff8bf0649..8fb6b95eb2 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -61,11 +61,11 @@ class CodegenIspcVisitor: public CodegenCVisitor { protected: /// doubles are differently represented in ispc than in C - std::string double_to_string(double value) override; + std::string double_to_string(const std::string& value) override; /// floats are differently represented in ispc than in C - std::string float_to_string(float value) override; + std::string float_to_string(const std::string& value) override; /// name of the code generation backend diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 47f4b3183f..0724f81e29 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -164,7 +164,7 @@ members: - value: brief: "Value of float" - type: float + type: std::string brief: "Represents a float variable" description: | Single precision float value in the mod file can be represented by ast::Float. @@ -178,7 +178,7 @@ members: - value: brief: "Value of double" - type: double + type: std::string brief: "Represents a double variable" description: | %Double precision float value in the mod file is represented by ast::Double. diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 10a0bcae24..f4fb599347 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -46,8 +46,8 @@ # data types and then their return types DATA_TYPES = {"Boolean": "bool", "Integer": "int", - "Float": "float", - "Double": "double", + "Float": "std::string", + "Double": "std::string", "String": "std::string", "BinaryOperator": "BinaryOp", "UnaryOperator": "UnaryOp", diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index 4585bd7a7e..8dc981c08f 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -336,13 +336,23 @@ class {{ node.class_name }} : public {{ node.base_class }} { * multiplied by `-1` for ast::Number node types. */ void negate() override { - value = {{ node.negation }}value; + {% if node.is_float_node or node.is_double_node %} + value = value.insert (0, 1, '{{ node.negation }}'); + {% else %} + value = {{ node.negation }}value; + {% endif %} } /** * \brief Return value of the current ast node as double */ - double to_double() override { return value; } + double to_double() override { + {% if node.is_float_node or node.is_double_node %} + return std::stod(value); + {% else %} + return value; + {% endif %} + } {% endif %} {% if node.is_data_type_node %} diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 84c96d121a..3c881a3004 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -246,7 +246,7 @@ ELSE { {D}+"."{D}*({E})? | {D}*"."{D}+({E})? | {D}+{E} { - return double_symbol(atof(yytext), loc); + return double_symbol(yytext, loc); } \"[^\"]*\" { diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 46535e2ccb..ac645f6956 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -25,8 +25,8 @@ using Parser = parser::NmodlParser; * @param pos Position of value in the mod file * @return Symbol for double value */ -SymbolType double_symbol(double value, PositionType& pos) { - ModToken token(std::to_string(value), Token::REAL, pos); +SymbolType double_symbol(const std::string& value, PositionType& pos) { + ModToken token(value, Token::REAL, pos); ast::Double float_value(value); float_value.set_token(token); return Parser::make_REAL(float_value, pos); diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index 51510f7dcf..fc5699439c 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -27,7 +27,7 @@ using SymbolType = parser::NmodlParser::symbol_type; using Token = parser::NmodlParser::token; using TokenType = parser::NmodlParser::token_type; -SymbolType double_symbol(double value, PositionType& pos); +SymbolType double_symbol(const std::string& value, PositionType& pos); SymbolType integer_symbol(int value, PositionType& pos, const char* text = nullptr); SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType type = Token::NAME); SymbolType prime_symbol(std::string text, PositionType& pos); diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 5d998819e4..9702c33509 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -741,7 +741,7 @@ double : REAL } | integer { - $$ = new ast::Double(double($1->eval())); + $$ = new ast::Double(std::to_string(($1->eval()))); delete($1); } ; diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 4ea9825f9b..6e0331860b 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -17,10 +17,13 @@ #include #include +#include #include #include #include +#include + namespace nmodl { /// string utility functions namespace stringutils { @@ -124,6 +127,24 @@ static inline std::string tolower(std::string text) { return text; } +/** + * Convert double value to string without trailing zeros + * + * When we use std::to_string with double value 1 then it gets + * printed as `1.000000`. This is not convenient for testing + * and testing/validation. To avoid this issue, we use to_string + * for integer values and stringstream for the rest. + */ +static inline std::string to_string(double value, const std::string& format_spec = "{:.16g}") { + // double containing integer value + if (std::ceil(value) == value) { + return std::to_string(static_cast(value)); + } + + // actual float value + return fmt::format(format_spec, value); +} + /** @} */ // end of utils } // namespace stringutils diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index e5ea062190..3a66c731fc 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -27,9 +27,9 @@ static double get_value(const std::shared_ptr& node) { if (node->is_integer()) { return std::dynamic_pointer_cast(node)->eval(); } else if (node->is_float()) { - return std::dynamic_pointer_cast(node)->eval(); + return std::dynamic_pointer_cast(node)->to_double(); } else if (node->is_double()) { - return std::dynamic_pointer_cast(node)->eval(); + return std::dynamic_pointer_cast(node)->to_double(); } throw std::runtime_error("Invalid type passed to is_number()"); } @@ -168,9 +168,9 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression& nod if (lhs->is_integer() && rhs->is_integer()) { node.set_expression(std::make_shared(int(value), nullptr)); } else if (lhs->is_double() || rhs->is_double()) { - node.set_expression(std::make_shared(value)); + node.set_expression(std::make_shared(stringutils::to_string(value))); } else { - node.set_expression(std::make_shared(value)); + node.set_expression(std::make_shared(stringutils::to_string(value))); } std::string nmodl_after = to_nmodl(node.get_expression()); diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 42f4f418aa..5373230317 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include "visitors/units_visitor.hpp" +#include "utils/string_utils.hpp" #include "ast/all.hpp" @@ -146,7 +147,13 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { auto node_unit_name = node.get_node_name(); auto unit1_factor = units_driver.table->get_unit(node_unit_name + "_unit1")->get_factor(); auto unit2_factor = units_driver.table->get_unit(node_unit_name + "_unit2")->get_factor(); - auto unit_factor = unit1_factor / unit2_factor; + +#ifdef USE_LEGACY_UNITS + auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:g}"); +#else + auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:.18g}"); +#endif + auto double_value_ptr = std::make_shared(ast::Double(unit_factor)); node.set_value(std::move(double_value_ptr)); } diff --git a/test/nmodl/transpiler/unit/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp index a197a97cb0..c306941b60 100644 --- a/test/nmodl/transpiler/unit/lexer/tokens.cpp +++ b/test/nmodl/transpiler/unit/lexer/tokens.cpp @@ -69,7 +69,7 @@ bool check_token_type(const std::string& name, TokenType type) { // float constant else if (token_type == get_token_type(Token::REAL)) { auto value = sym.value.as(); - REQUIRE(value.get_value() != 0); + REQUIRE(value.to_double() != 0); } // const char* else if (token_type == get_token_type(Token::STRING)) { diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index e57f63a77a..4d2e27dac5 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -411,7 +411,7 @@ std::map nmodl_valid_constructs{ )", R"( PARAMETER { - tau_r_AMPA = 10 + tau_r_AMPA = 10.0 tau_d_AMPA = 10.1 (mV) tau_r_NMDA = 10 (mV) <1,2> tau_d_NMDA = 10 (mV) <1.1,2.2> @@ -954,12 +954,6 @@ std::map nmodl_valid_constructs{ CONSERVE C+o = 1 CONSERVE pump+pumpca = TotalPump*parea*(1e+10) } - )", - R"( - KINETIC ihkin { - CONSERVE C+o = 1 - CONSERVE pump+pumpca = TotalPump*parea*(10000000000) - } )" } }, @@ -975,14 +969,6 @@ std::map nmodl_valid_constructs{ COMPARTMENT (1e+10)*area1 {pump pumpca} COMPARTMENT i, diam*diam*vol[i]*1(um) {ca CaBuffer Buffer} } - )", - R"( - KINETIC ihkin { - COMPARTMENT voli {cai} - COMPARTMENT diam*diam*PI/4 {qk} - COMPARTMENT (10000000000)*area1 {pump pumpca} - COMPARTMENT i, diam*diam*vol[i]*1(um) {ca CaBuffer Buffer} - } )" } }, diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index bd7eed2c30..3e1c7d361f 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -64,8 +64,8 @@ SCENARIO("NeuronSolveVisitor visitor solves different ODE types") { } DERIVATIVE states { - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) - h = h+(1-exp(dt*((((-1)))/hTau)))*(-(((hInf))/hTau)/((((-1)))/hTau)-h) + m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m) + h = h+(1.0-exp(dt*((((-1.0)))/hTau)))*(-(((hInf))/hTau)/((((-1.0)))/hTau)-h) m = m+h } )"; diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index ff594b44b3..862b1c4fbf 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -62,11 +62,11 @@ TEST_CASE("Solve ODEs using legacy NeuronSolveVisitor", "[visitor][solver]") { } DERIVATIVE states { - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m) } NRN_STATE SOLVE states METHOD cnexp{ - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m) } )"; @@ -100,18 +100,18 @@ TEST_CASE("Solve ODEs using legacy NeuronSolveVisitor", "[visitor][solver]") { } DERIVATIVE state1 { - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m) } DERIVATIVE state2 { - h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) + h = h+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-h) } NRN_STATE SOLVE state1 METHOD cnexp{ - m = m+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-m) + m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m) } SOLVE state2 METHOD cnexp{ - h = h+(1-exp(dt*((((-1)))/mTau)))*(-(((mInf))/mTau)/((((-1)))/mTau)-h) + h = h+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-h) } )"; diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index 2398a7023d..f6374a0147 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -152,7 +152,7 @@ SCENARIO("Solving ODEs with STEADYSTATE solve method", "[visitor][steadystate]") DERIVATIVE states0 { Z'[0] = Z[1]-Z[2] Z'[1] = Z[0]+2*Z[2] - Z'[2] = Z[0]*Z[0]-3.1 + Z'[2] = Z[0]*Z[0]-3.10 })"; std::string expected_text2 = R"( DERIVATIVE states1 { @@ -165,7 +165,7 @@ SCENARIO("Solving ODEs with STEADYSTATE solve method", "[visitor][steadystate]") dt = 1e-09 Z'[0] = Z[1]-Z[2] Z'[1] = Z[0]+2*Z[2] - Z'[2] = Z[0]*Z[0]-3.1 + Z'[2] = Z[0]*Z[0]-3.10 dt = dt_saved_value })"; std::string expected_text4 = R"( diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index c416c66249..19f19ca2d6 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -903,16 +903,16 @@ SCENARIO("Addition of CONDUCTANCE using SympyConductance visitor", "[visitor][so LOCAL Eca_syn, mggate, i_AMPA, gmax_NMDA, i_NMDA, Pf_NMDA, gca_bar_abs_VDCC, gca_VDCC, nernst_in_0, g__0 CONDUCTANCE g__0 g__0 = (0.001*gmax_NMDA*mg*scale_mg*slope_mg*(A_NMDA-B_NMDA)*(E_NMDA-v)*exp(slope_mg*v)-0.001*gmax_NMDA*scale_mg*(A_NMDA-B_NMDA)*(mg+scale_mg*exp(slope_mg*v))*exp(slope_mg*v)+(g_AMPA+gca_VDCC)*pow(mg+scale_mg*exp(slope_mg*v), 2))/pow(mg+scale_mg*exp(slope_mg*v), 2) - g_AMPA = 0.001*gmax_AMPA*(B_AMPA-A_AMPA) + g_AMPA = 1e-3*gmax_AMPA*(B_AMPA-A_AMPA) i_AMPA = g_AMPA*(v-E_AMPA) gmax_NMDA = gmax_AMPA*NMDA_ratio mggate = 1/(1+exp(slope_mg*-v)*(mg/scale_mg)) - g_NMDA = 0.001*gmax_NMDA*mggate*(B_NMDA-A_NMDA) + g_NMDA = 1e-3*gmax_NMDA*mggate*(B_NMDA-A_NMDA) i_NMDA = g_NMDA*(v-E_NMDA) Pf_NMDA = (4*cao_CR)/(4*cao_CR+0.7246376811594204*120(mM))*0.6 - ica_NMDA = Pf_NMDA*g_NMDA*(v-40) + ica_NMDA = Pf_NMDA*g_NMDA*(v-40.0) gca_bar_abs_VDCC = gca_bar_VDCC*4(um2)*PI*(3(1/um3)/4*volume_CR*1/PI)^0.6666666666666666 - gca_VDCC = 0.001*gca_bar_abs_VDCC*m_VDCC*m_VDCC*h_VDCC + gca_VDCC = 1e-3*gca_bar_abs_VDCC*m_VDCC*m_VDCC*h_VDCC { LOCAL ci_in_0, co_in_0, z_in_0 ci_in_0 = cai_CR diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index cc983d7335..0e965fd065 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -208,7 +208,7 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); REQUIRE(result[0] == "m = mInf-(-m+mInf)*exp(-dt/mTau)"); - REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); + REQUIRE(result[1] == "h = -h/(c2*dt*h-1.0)"); } } GIVEN("Derivative block including array of 2 state vars, solver method cnexp") { @@ -228,7 +228,7 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 2); REQUIRE(result[0] == "X[0] = mInf-(mInf-X[0])*exp(-dt/mTau)"); - REQUIRE(result[1] == "X[1] = -X[1]/(c2*dt*X[1]-1)"); + REQUIRE(result[1] == "X[1] = -X[1]/(c2*dt*X[1]-1.0)"); } } GIVEN("Derivative block including loop over array vars, solver method cnexp") { @@ -299,11 +299,11 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 4); REQUIRE(result[0] == "z' = a/z+b/z/z"); - REQUIRE(result[1] == "h = -h/(c2*dt*h-1)"); + REQUIRE(result[1] == "h = -h/(c2*dt*h-1.0)"); REQUIRE(result[2] == "x = a*dt+x"); /// sympy 1.4 able to solve ode but not older versions bool last_result = (result[3] == "y' = c3*y*y*y" || - result[3] == "y = sqrt(-pow(y, 2)/(2*c3*dt*pow(y, 2)-1))"); + result[3] == "y = sqrt(-pow(y, 2)/(2.0*c3*dt*pow(y, 2)-1.0))"); REQUIRE(last_result); } } @@ -400,9 +400,9 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_x = x old_y = y old_z = z - x = (-a*dt*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(b*dt*h+old_x)*(2*a*pow(dt, 3)-d*dt+1))/(2*a*pow(dt, 3)-d*dt+1) - y = (-2*a*pow(dt, 2)*(dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)-old_z)+(2*a*pow(dt, 3)-d*dt+1)*(c*dt+2*dt*(b*dt*h+old_x)+old_y))/(2*a*pow(dt, 3)-d*dt+1) - z = (-dt*(c*dt+2*dt*(b*dt*h+old_x)+old_y)+old_z)/(2*a*pow(dt, 3)-d*dt+1) + x = (-a*dt*(dt*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y)-old_z)+(b*dt*h+old_x)*(2.0*a*pow(dt, 3)-d*dt+1.0))/(2.0*a*pow(dt, 3)-d*dt+1.0) + y = (-2.0*a*pow(dt, 2)*(dt*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y)-old_z)+(2.0*a*pow(dt, 3)-d*dt+1.0)*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y))/(2.0*a*pow(dt, 3)-d*dt+1.0) + z = (-dt*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y)+old_z)/(2.0*a*pow(dt, 3)-d*dt+1.0) })"; std::string expected_cse_result = R"( DERIVATIVE states { @@ -410,11 +410,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_x = x old_y = y old_z = z - tmp0 = 2*a - tmp1 = -d*dt+pow(dt, 3)*tmp0+1 - tmp2 = 1/tmp1 + tmp0 = 2.0*a + tmp1 = -d*dt+pow(dt, 3)*tmp0+1.0 + tmp2 = 1.0/tmp1 tmp3 = b*dt*h+old_x - tmp4 = c*dt+2*dt*tmp3+old_y + tmp4 = c*dt+2.0*dt*tmp3+old_y tmp5 = dt*tmp4-old_z x = -tmp2*(a*dt*tmp5-tmp1*tmp3) y = -tmp2*(pow(dt, 2)*tmp0*tmp5-tmp1*tmp4) @@ -448,8 +448,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", LOCAL old_mc, old_m old_mc = mc old_m = m - mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) - m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) + mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1.0) + m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1.0) })"; THEN("Construct & solve linear system") { CAPTURE(nmodl_text); @@ -476,8 +476,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", DERIVATIVE scheme1 { LOCAL old_mc old_mc = mc - mc = (b*dt+old_mc)/(a*dt+b*dt+1) - m = (a*dt-old_mc+1)/(a*dt+b*dt+1) + mc = (b*dt+old_mc)/(a*dt+b*dt+1.0) + m = (a*dt-old_mc+1.0)/(a*dt+b*dt+1.0) })"; THEN("Construct & solve linear system, replace ODE for m with rhs of CONSERVE statement") { CAPTURE(nmodl_text); @@ -507,8 +507,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", LOCAL old_mc, old_m old_mc = mc old_m = m - mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1) - m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1) + mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1.0) + m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1.0) })"; THEN("Construct & solve linear system, ignore invalid CONSERVE statement") { CAPTURE(nmodl_text); @@ -553,34 +553,34 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", X[4] = p1 F[0] = -old_c1 F[1] = -old_o1 - F[2] = -1 + F[2] = -1.0 F[3] = -old_p0 - F[4] = -1 - J[0] = -alpha*dt-1 + F[4] = -1.0 + J[0] = -alpha*dt-1.0 J[5] = beta*dt J[10] = 0 J[15] = 0 J[20] = 0 J[1] = alpha*dt - J[6] = -beta*dt-dt*k3p-1 + J[6] = -beta*dt-dt*k3p-1.0 J[11] = dt*k4 J[16] = 0 J[21] = 0 - J[2] = -1 - J[7] = -1 - J[12] = -1 + J[2] = -1.0 + J[7] = -1.0 + J[12] = -1.0 J[17] = 0 J[22] = 0 J[3] = 0 J[8] = 0 J[13] = 0 - J[18] = -dt*k1ca-1 + J[18] = -dt*k1ca-1.0 J[23] = dt*k2 J[4] = 0 J[9] = 0 J[14] = 0 - J[19] = -1 - J[24] = -1 + J[19] = -1.0 + J[24] = -1.0 }{ c1 = X[0] o1 = X[1] @@ -619,7 +619,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", DERIVATIVE scheme1 { LOCAL old_W_0 old_W_0 = W[0] - W[0] = (3*dt*A[1]+old_W_0)/(dt*A[0]-dt*B[0]+1) + W[0] = (3.0*dt*A[1]+old_W_0)/(dt*A[0]-dt*B[0]+1.0) })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); @@ -650,8 +650,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", LOCAL old_M_0, old_M_1 old_M_0 = M[0] old_M_1 = M[1] - M[0] = (dt*old_M_0*B[1]+dt*old_M_1*B[0]+old_M_0)/(pow(dt, 2)*A[0]*B[1]-pow(dt, 2)*A[1]*B[0]+dt*A[0]+dt*B[1]+1) - M[1] = -(dt*old_M_0*A[1]+old_M_1*(dt*A[0]+1))/(pow(dt, 2)*A[1]*B[0]-(dt*A[0]+1)*(dt*B[1]+1)) + M[0] = (dt*old_M_0*B[1]+dt*old_M_1*B[0]+old_M_0)/(pow(dt, 2)*A[0]*B[1]-pow(dt, 2)*A[1]*B[0]+dt*A[0]+dt*B[1]+1.0) + M[1] = -(dt*old_M_0*A[1]+old_M_1*(dt*A[0]+1.0))/(pow(dt, 2)*A[1]*B[0]-(dt*A[0]+1.0)*(dt*B[1]+1.0)) })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); @@ -685,8 +685,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ X[0] = W[0] }{ - F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3*dt*A[1]+old_W_0 - J[0] = -dt*A[0]+dt*B[0]-1 + F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3.0*dt*A[1]+old_W_0 + J[0] = -dt*A[0]+dt*B[0]-1.0 }{ W[0] = X[0] }{ @@ -729,13 +729,13 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", X[1] = h X[2] = n }{ - F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]-3*X[1]*dt+old_m))/mtau + F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]-3.0*X[1]*dt+old_m))/mtau F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau F[2] = (-X[2]*dt+dt*ninf+ntau*(-X[2]+old_n))/ntau J[0] = -(dt+mtau)/mtau - J[3] = -3*dt + J[3] = -3.0*dt J[6] = 0 - J[1] = 2*X[0]*dt + J[1] = 2.0*X[0]*dt J[4] = -(dt+htau)/htau J[7] = 0 J[2] = 0 @@ -791,7 +791,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau J[0] = -(dt+mtau)/mtau J[2] = 0 - J[1] = 2*X[0]*dt + J[1] = 2.0*X[0]*dt J[3] = -(dt+htau)/htau }{ m = X[0] @@ -812,7 +812,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ F[0] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau F[1] = (-X[0]*dt+dt*minf+mtau*(-X[0]+X[1]*dt+old_m))/mtau - J[0] = 2*X[0]*dt + J[0] = 2.0*X[0]*dt J[2] = -(dt+htau)/htau J[1] = -(dt+mtau)/mtau J[3] = dt @@ -848,7 +848,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { })"; std::string expected_text = R"( LINEAR lin { - x = 5 + x = 5.0 })"; THEN("solve analytically") { auto result = @@ -956,15 +956,15 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { })"; std::string expected_text_sympy_13 = R"( LINEAR lin { - x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)+(1*b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(1*b+4*c)-4*c*(5.343*a+b*(-1*a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - y = (1*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-1*pow(a, 2)*pow(b, 2)*(1*b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(1*b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - z = pow(a, 2)*b*(c*(5.343*a+b*(-1*a+0.842*pow(b, 2)))*(4*c-1.3)-(1*b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + x = (4.0*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)+(1.0*b+4.0*c)*(5.343*a*c+1.43543))-(5.343*a*(1.0*b+4.0*c)-4.0*c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/((1.0*b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) + y = (1.0*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-1.0*pow(a, 2)*pow(b, 2)*(1.0*b+4.0*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/(c*(1.0*b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) + z = pow(a, 2)*b*(c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-(1.0*b+4.0*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) })"; std::string expected_text_sympy_14 = R"( LINEAR lin { - x = (4*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(4*c-1.3)+(b+4*c)*(5.343*a*c+1.43543))-(5.343*a*(b+4*c)-4*c*(5.343*a+b*(-a+0.842*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/((b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - y = (pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(4*c-1.3)-pow(a, 2)*pow(b, 2)*(b+4*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c))/(c*(b+4*c)*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) - z = pow(a, 2)*b*(c*(5.343*a+b*(-a+0.842*pow(b, 2)))*(4*c-1.3)-(b+4*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4*c-1.3)+0.1*b+0.4*c)) + x = (4.0*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)+(b+4.0*c)*(5.343*a*c+1.43543))-(5.343*a*(b+4.0*c)-4.0*c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/((b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) + y = (pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-pow(a, 2)*pow(b, 2)*(b+4.0*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/(c*(b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) + z = pow(a, 2)*b*(c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-(b+4.0*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) })"; THEN("solve analytically") { @@ -988,9 +988,9 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { })"; std::string expected_text = R"( LINEAR lin { - s[0] = 1 - s[1] = 3 - s[2] = -2 + s[0] = 1.0 + s[1] = 3.0 + s[2] = -2.0 })"; THEN("solve analytically") { auto result = @@ -1020,24 +1020,24 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { X[3] = z F[0] = 0 F[1] = 5.343*a - F[2] = a-0.842*pow(b, 2) + F[2] = a-0.84199999999999997*pow(b, 2) F[3] = -1.43543/c - J[0] = -1 + J[0] = -1.0 J[4] = 0 - J[8] = -2 + J[8] = -2.0 J[12] = -0.3125 J[1] = 0 - J[5] = -1 - J[9] = -4*c + J[5] = -1.0 + J[9] = -4.0*c J[13] = 0 J[2] = 0 J[6] = -1/b - J[10] = 1 - J[14] = -1 + J[10] = 1.0 + J[14] = -1.0 J[3] = 0 - J[7] = -1 + J[7] = -1.0 J[11] = -1.3 - J[15] = 0.1/(pow(a, 2)*b) + J[15] = 0.10000000000000001/(pow(a, 2)*b) }{ w = X[0] x = X[1] @@ -1099,7 +1099,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { F[8] = 0 F[9] = 0 F[10] = 0 - F[11] = -1 + F[11] = -1.0 J[0] = f01+fi1 J[12] = -b01 J[24] = 0 @@ -1232,18 +1232,18 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { J[118] = b14+bi5+f1n J[130] = -b1n J[142] = 0 - J[11] = -1 - J[23] = -1 - J[35] = -1 - J[47] = -1 - J[59] = -1 - J[71] = -1 - J[83] = -1 - J[95] = -1 - J[107] = -1 - J[119] = -1 - J[131] = -1 - J[143] = -1 + J[11] = -1.0 + J[23] = -1.0 + J[35] = -1.0 + J[47] = -1.0 + J[59] = -1.0 + J[71] = -1.0 + J[83] = -1.0 + J[95] = -1.0 + J[107] = -1.0 + J[119] = -1.0 + J[131] = -1.0 + J[143] = -1.0 }{ C1 = X[0] C2 = X[1] @@ -1288,8 +1288,8 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s }{ X[0] = x }{ - F[0] = -X[0]+5 - J[0] = -1 + F[0] = -X[0]+5.0 + J[0] = -1.0 }{ x = X[0] }{ @@ -1302,8 +1302,8 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s }{ X[0] = x }{ - F[0] = 5-X[0] - J[0] = -1 + F[0] = 5.0-X[0] + J[0] = -1.0 }{ x = X[0] }{ @@ -1338,18 +1338,18 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s X[1] = s[1] X[2] = s[2] }{ - F[0] = -X[0]+1 - F[1] = -X[1]+3 + F[0] = -X[0]+1.0 + F[1] = -X[1]+3.0 F[2] = X[0]-X[1]-X[2] - J[0] = -1 + J[0] = -1.0 J[3] = 0 J[6] = 0 J[1] = 0 - J[4] = -1 + J[4] = -1.0 J[7] = 0 - J[2] = 1 - J[5] = -1 - J[8] = -1 + J[2] = 1.0 + J[5] = -1.0 + J[8] = -1.0 }{ s[0] = X[0] s[1] = X[1] @@ -1366,18 +1366,18 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s X[1] = s[1] X[2] = s[2] }{ - F[0] = 1-X[0] - F[1] = 3-X[1] + F[0] = 1.0-X[0] + F[1] = 3.0-X[1] F[2] = X[0]-X[1]-X[2] - J[0] = -1 + J[0] = -1.0 J[3] = 0 J[6] = 0 J[1] = 0 - J[4] = -1 + J[4] = -1.0 J[7] = 0 - J[2] = 1 - J[5] = -1 - J[8] = -1 + J[2] = 1.0 + J[5] = -1.0 + J[8] = -1.0 }{ s[0] = X[0] s[1] = X[1] diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 8cfe92b850..048dbdabcd 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -175,21 +175,21 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] um3 0.00100000: m3 molar1 1000.00000000: m-3 degK 1.00000000: K1 - FARADAY1 96485.33212331: coul1 - FARADAY2 96.48533212: coul1 - FARADAY3 9.64853321: coul1 - PI 3.14159265: constant - R1 8.31446262: m2 kg1 sec-2 K-1 - R2 8.31400000: m2 kg1 sec-2 K-1 - R3 8314.46261815: m2 kg1 sec-2 K-1 - R4 8.31400000: m2 kg1 sec-2 K-1 - R5 8.31450000: m2 kg1 sec-2 K-1 - dummy1 123.45000000: m1 sec-2 - dummy2 123450.00000000: m1 sec-2 - dummy3 123.45000000: m1 sec-2 - KTOMV 0.08530000: m2 kg1 sec-2 coul-1 K-1 - B 0.26000000: m-1 coul-1 - TEMP 25.00000000: K1 + FARADAY1 96485.3321233100141: coul1 + FARADAY2 96.4853321233100161: coul1 + FARADAY3 9.64853321233100125: coul1 + PI 3.14159265358979312: constant + R1 8.3144626181532395: m2 kg1 sec-2 K-1 + R2 8.314: m2 kg1 sec-2 K-1 + R3 8314.46261815323851: m2 kg1 sec-2 K-1 + R4 8.314: m2 kg1 sec-2 K-1 + R5 8.314500000000001: m2 kg1 sec-2 K-1 + dummy1 123.45: m1 sec-2 + dummy2 123.45e3: m1 sec-2 + dummy3 12345e-2: m1 sec-2 + KTOMV 0.0853: m2 kg1 sec-2 coul-1 K-1 + B 0.26: m-1 coul-1 + TEMP 25: K1 )"; THEN("Print the units that were added") { From 43a26228cbe154d09578db2be6993079f0b12978 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Sat, 5 Dec 2020 11:33:40 +0100 Subject: [PATCH 318/871] Add compare_blocks for robust sympy visitor comparisons (BlueBrain/nmodl#441) * Add compare_blocks for robust sympy visitor comparisons - No more problems with new sympy versions that rearrange the results producing equivalent but string-different results! Now we have compare_blocks: it lets sympy itself do the heavylifting to compare equivalent systems of equations. Noteworthy: * ctest-checked with sympy=1.4.*, 1.5*, 1.6*, and 1.7.* * Remove sympy<1.6 * Increase azure time limit to 45 mins NMODL Repo SHA: BlueBrain/nmodl@5cb6699aa4b720d8edfc4e3729b23298c8fe1f8c --- .travis.yml | 2 +- setup.py | 2 +- .../transpiler/unit/visitor/sympy_solver.cpp | 313 +++++++++++++----- 3 files changed, 230 insertions(+), 87 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8d0de6eb05..eeaeacf79f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -97,7 +97,7 @@ install: # I will remove the sphinx downgrade. Change setup.py requirements when it # be the case, Katta - echo "------- Install Dependencies -------" - - pip3 install -U pip setuptools scikit-build Jinja2 PyYAML pytest "sympy<1.6" + - pip3 install -U pip setuptools scikit-build Jinja2 PyYAML pytest sympy #============================================================================= # Build, test and install diff --git a/setup.py b/setup.py index bf4951ff4b..a870695244 100644 --- a/setup.py +++ b/setup.py @@ -90,7 +90,7 @@ def _config_exe(exe_name): install_requirements = [ "PyYAML>=3.13", - "sympy>=1.3,<1.6", + "sympy>=1.3", ] diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 0e965fd065..3fb0752f33 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -64,6 +64,130 @@ std::vector run_sympy_solver_visitor( return results; } + +/** + * \brief Compare nmodl blocks that contain systems of equations (i.e. derivative, linear, etc.) + * + * This is basically and advanced string == string comparison where we detect the (various) systems + * of equations and check if they are equivalent. Implemented mostly in python since we need a call + * to sympy to simplify the equations. + * + * - compare_systems_of_eq The core of the code. \p result_dict and \p expected_dict are + * dictionaries that represent the systems of equations in this way: + * + * a = b*x + c -> result_dict['a'] = 'b*x + c' + * + * where the variable \p a become a key \p k of the dictionary. + * + * In there we go over all the equations in \p result_dict and \p expected_dict and check that + * result_dict[k] - expected_dict[k] simplifies to 0. + * + * - sanitize is to transform the equations in something treatable by sympy (i.e. pow(dt, 3) -> + * dt**3 + * - reduce back-substitution of the temporary variables + * + * \p require_fail requires that the equations are different. Used only for unit-test this function + */ +void compare_blocks(const std::string& result, + const std::string& expected, + const bool require_fail = false) { + using namespace pybind11::literals; + + auto locals = + pybind11::dict("result"_a = result, "expected"_a = expected, "is_equal"_a = false); + pybind11::exec(R"( + # Comments are in the doxygen for better highlighting + def compare_blocks(result, expected): + + def sanitize(s): + import re + d = {'\[(\d+)\]':'_\\1', 'pow\((\w+), ?(\d+)\)':'\\1**\\2'} + out = s + for key, val in d.items(): + out = re.sub(key, val, out) + return out + + def compare_systems_of_eq(result_dict, expected_dict): + from sympy.parsing.sympy_parser import parse_expr + try: + for k, v in result_dict.items(): + if parse_expr(f'simplify(({v})-({expected_dict[k]}))'): + return False + except KeyError: + return False + + result_dict.clear() + expected_dict.clear() + return True + + def reduce(s): + i = 0 + d = {} + + sout = "" + # split of sout and a dict with the tmp variables + for line in s.split('\n'): + line_split = line.lstrip().split('=') + if len(line_split) == 2 and line_split[0] == f'tmp{i} ': + # back-substitution of tmp variables in tmp variables + for k, v in d.items(): + line_split[1] = line_split[1].replace(k, f'({v})') + d[f'tmp{i}'] = line_split[1] + i += 1 + elif 'LOCAL' in line: + sout += line.split('tmp0')[0] + '\n' + else: + sout += line + '\n' + + # Back-substitution of the tmps + # so that we do not replace tmp11 with (tmp1)1 + for j in range(i-1, -1, -1): + k = f'tmp{j}' + sout = sout.replace(k, f'({d[k]})') + + return sout + + result = reduce(sanitize(result)).split('\n') + expected = reduce(sanitize(expected)).split('\n') + + if len(result) != len(expected): + return False + + result_dict = {} + expected_dict = {} + for token1, token2 in zip(result, expected): + if token1 == token2: + if not compare_systems_of_eq(result_dict, expected_dict): + return False + continue + + eq1 = token1.split('=') + eq2 = token2.split('=') + if len(eq1) == 2 and len(eq2) == 2: + result_dict[eq1[0]] = eq1[1] + expected_dict[eq2[0]] = eq2[1] + continue + + return False + return compare_systems_of_eq(result_dict, expected_dict) + + is_equal = compare_blocks(result, expected))", + pybind11::globals(), + locals); + + // Error log + if (require_fail == locals["is_equal"].cast()) { + if (require_fail) { + REQUIRE(result != expected); + } else { + REQUIRE(result == expected); + } + } else { // so that we signal to ctest that an assert was performed + REQUIRE(true); + } +} + + void run_sympy_visitor_passes(ast::Program& node) { // construct symbol table from AST SymtabVisitor v_symtab; @@ -81,12 +205,86 @@ void run_sympy_visitor_passes(ast::Program& node) { v_sympy2.visit_program(node); } + std::string ast_to_string(ast::Program& node) { std::stringstream stream; NmodlPrintVisitor(stream).visit_program(node); return stream.str(); } +SCENARIO("Check compare_blocks in sympy unit tests", "[visitor][sympy]") { + GIVEN("Empty strings") { + THEN("Strings are equal") { + compare_blocks("", ""); + } + } + GIVEN("Equivalent equation") { + THEN("Strings are equal") { + compare_blocks("a = 3*b + c", "a = 2*b + b + c"); + } + } + GIVEN("Equivalent systems of equations") { + std::string result = R"( + x = 3*b + c + y = 2*a + b)"; + std::string expected = R"( + x = b+2*b + c + y = 2*a + 2*b-b)"; + THEN("Systems of equations are equal") { + compare_blocks(result, expected); + } + } + GIVEN("Equivalent systems of equations with brackets") { + std::string result = R"( + DERIVATIVE { + A[0] = 3*b + c + y = pow(a, 3) + b + })"; + std::string expected = R"( + DERIVATIVE { + tmp0 = a + c + tmp1 = tmp0 - a + A[0] = b+2*b + tmp1 + y = pow(a, 2)*a + 2*b-b + })"; + THEN("Blocks are equal") { + compare_blocks(result, expected); + } + } + GIVEN("Different systems of equations (additional space)") { + std::string result = R"( + DERIVATIVE { + x = 3*b + c + y = 2*a + b + })"; + std::string expected = R"( + DERIVATIVE { + x = b+2*b + c + y = 2*a + 2*b-b + })"; + THEN("Blocks are different") { + compare_blocks(result, expected, true); + } + } + GIVEN("Different systems of equations") { + std::string result = R"( + DERIVATIVE { + tmp0 = a - c + tmp1 = tmp0 - a + x = 3*b + tmp1 + y = 2*a + b + })"; + std::string expected = R"( + DERIVATIVE { + x = b+2*b + c + y = 2*a + 2*b-b + })"; + THEN("Blocks are different") { + compare_blocks(result, expected, true); + } + } +} + SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", "[visitor][sympy][cnexp][euler]") { GIVEN("Derivative block without ODE, solver method cnexp") { @@ -375,7 +573,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } @@ -426,8 +624,9 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); auto result_cse = run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); - REQUIRE(result_cse[0] == reindent_text(expected_cse_result)); + + compare_blocks(result[0], reindent_text(expected_result)); + compare_blocks(result_cse[0], reindent_text(expected_cse_result)); } } GIVEN("Derivative block including ODES with sparse method (from nmodl paper)") { @@ -455,7 +654,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Derivative block with ODES with sparse method, CONSERVE statement of form m = ...") { @@ -483,7 +682,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN( @@ -514,7 +713,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Derivative block with ODES with sparse method, two CONSERVE statements") { @@ -596,7 +795,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Derivative block including ODES with sparse method - single var in array") { @@ -625,7 +824,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Derivative block including ODES with sparse method - array vars") { @@ -657,7 +856,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Derivative block including ODES with derivimplicit method - single var in array") { @@ -696,7 +895,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Derivative block including ODES with derivimplicit method") { @@ -752,7 +951,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", CAPTURE(nmodl_text); auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - REQUIRE(result[0] == reindent_text(expected_result)); + compare_blocks(result[0], reindent_text(expected_result)); } } GIVEN("Multiple derivative blocks each with derivimplicit method") { @@ -950,30 +1149,22 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { x y z } LINEAR lin { - ~ x + 4*c*y = -5.343*a - ~ a + x/b + z - y = 0.842*b*b - ~ x + 1.3*y - 0.1*z/(a*a*b) = 1.43543/c - })"; - std::string expected_text_sympy_13 = R"( - LINEAR lin { - x = (4.0*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)+(1.0*b+4.0*c)*(5.343*a*c+1.43543))-(5.343*a*(1.0*b+4.0*c)-4.0*c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/((1.0*b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) - y = (1.0*pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-1.0*pow(a, 2)*pow(b, 2)*(1.0*b+4.0*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/(c*(1.0*b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) - z = pow(a, 2)*b*(c*(5.343*a+b*(-1.0*a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-(1.0*b+4.0*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) - })"; - std::string expected_text_sympy_14 = R"( - LINEAR lin { - x = (4.0*pow(a, 2)*pow(b, 2)*(-c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)+(b+4.0*c)*(5.343*a*c+1.43543))-(5.343*a*(b+4.0*c)-4.0*c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2))))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/((b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) - y = (pow(a, 2)*pow(b, 2)*c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-pow(a, 2)*pow(b, 2)*(b+4.0*c)*(5.343*a*c+1.43543)-c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c))/(c*(b+4.0*c)*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) - z = pow(a, 2)*b*(c*(5.343*a+b*(-a+0.84199999999999997*pow(b, 2)))*(4.0*c-1.3)-(b+4.0*c)*(5.343*a*c+1.43543))/(c*(pow(a, 2)*pow(b, 2)*(4.0*c-1.3)+0.10000000000000001*b+0.40000000000000002*c)) + ~ x + 4*c*y = -6*a + ~ a + x/b + z - y = 1*b*b + ~ 10*x + 13*y - z/(a*a*b) = 14/c })"; + std::string expected_text = R"( + LINEAR lin { + x = 2*b*(39*pow(a, 3)*b+28*pow(a, 2)*b-2*a*c-3*a+2*pow(b, 2)*c)/(40*pow(a, 2)*pow(b, 2)*c-13*pow(a, 2)*pow(b, 2)+b+4*c) + y = (-60*pow(a, 3)*pow(b, 2)*c-14*pow(a, 2)*pow(b, 2)+a*b*c-6*a*c-pow(b, 3)*c)/(c*(40*pow(a, 2)*pow(b, 2)*c-13*pow(a, 2)*pow(b, 2)+b+4*c)) + z = pow(a, 2)*b*(-40*a*b*pow(c, 2)-47*a*b*c-78*a*c+40*pow(b, 3)*pow(c, 2)-13*pow(b, 3)*c-14*b-56*c)/(c*(40*pow(a, 2)*pow(b, 2)*c-13*pow(a, 2)*pow(b,2)+b+4*c)) + })"; THEN("solve analytically") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - bool result_match = (reindent_text(result[0]) == - reindent_text(expected_text_sympy_13) || - reindent_text(result[0]) == reindent_text(expected_text_sympy_14)); - REQUIRE(result_match); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } GIVEN("array state-var numeric LINEAR solve block") { @@ -995,7 +1186,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { THEN("solve analytically") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } GIVEN("4 state-var LINEAR solve block") { @@ -1049,7 +1240,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { THEN("return matrix system to solve") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } GIVEN("12 state-var LINEAR solve block") { @@ -1263,7 +1454,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { THEN("return matrix system to be solved") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } } @@ -1281,21 +1472,7 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s NONLINEAR nonlin { ~ x = 5 })"; - std::string expected_text_sympy_13 = R"( - NONLINEAR nonlin { - EIGEN_NEWTON_SOLVE[1]{ - }{ - }{ - X[0] = x - }{ - F[0] = -X[0]+5.0 - J[0] = -1.0 - }{ - x = X[0] - }{ - } - })"; - std::string expected_text_sympy_14 = R"( + std::string expected_text = R"( NONLINEAR nonlin { EIGEN_NEWTON_SOLVE[1]{ }{ @@ -1313,10 +1490,7 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s THEN("return F & J for newton solver") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); - bool result_match = (reindent_text(result[0]) == - reindent_text(expected_text_sympy_13) || - reindent_text(result[0]) == reindent_text(expected_text_sympy_14)); - REQUIRE(result_match); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } GIVEN("array state-var numeric NONLINEAR solve block") { @@ -1329,35 +1503,7 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s ~ s[1] = 3 ~ s[2] + s[1] = s[0] })"; - std::string expected_text_sympy_13 = R"( - NONLINEAR nonlin { - EIGEN_NEWTON_SOLVE[3]{ - }{ - }{ - X[0] = s[0] - X[1] = s[1] - X[2] = s[2] - }{ - F[0] = -X[0]+1.0 - F[1] = -X[1]+3.0 - F[2] = X[0]-X[1]-X[2] - J[0] = -1.0 - J[3] = 0 - J[6] = 0 - J[1] = 0 - J[4] = -1.0 - J[7] = 0 - J[2] = 1.0 - J[5] = -1.0 - J[8] = -1.0 - }{ - s[0] = X[0] - s[1] = X[1] - s[2] = X[2] - }{ - } - })"; - std::string expected_text_sympy_14 = R"( + std::string expected_text = R"( NONLINEAR nonlin { EIGEN_NEWTON_SOLVE[3]{ }{ @@ -1388,10 +1534,7 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s THEN("return F & J for newton solver") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::NON_LINEAR_BLOCK); - bool result_match = (reindent_text(result[0]) == - reindent_text(expected_text_sympy_13) || - reindent_text(result[0]) == reindent_text(expected_text_sympy_14)); - REQUIRE(result_match); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } } From 8ac7f68c585221e6b0fb5338674e37fb8ca86863 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Sat, 5 Dec 2020 13:23:14 +0100 Subject: [PATCH 319/871] Use cmake helper to initialise submodules if not cloned with --recursive (BlueBrain/nmodl#438) * Added cmake/ExternalProjectHelper.cmake to auto-initialise submodules * Check and initialize all submodules before building * Don't include fmt in all and set -fPIC * Add cli11 in the same include_directories fixes BlueBrain/nmodl#88 Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@46f691ee9af8fb10d6606945476c66842c5d004d --- cmake/nmodl/CMakeLists.txt | 72 +++++++++++++------------ cmake/nmodl/ExternalProjectHelper.cmake | 38 +++++++++++++ 2 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 cmake/nmodl/ExternalProjectHelper.cmake diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 1f515a036e..e8a8e7b617 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -54,6 +54,38 @@ message(STATUS "CHECKING FOR FLEX/BISON") find_package(FLEX 2.6 REQUIRED) find_package(BISON 3.0 REQUIRED) +# ============================================================================= +# Include cmake modules +# ============================================================================= +list(APPEND CMAKE_MODULE_PATH ${NMODL_PROJECT_SOURCE_DIR}/cmake) +include(Catch) +include(ClangTidyHelper) +include(CompilerHelper) +include(FindPythonModule) +include(FlexHelper) +include(GitRevision) +include(PythonLinkHelper) +include(RpathHelper) +include(ExternalProjectHelper) + +# ============================================================================= +# Initialize external libraries as submodule +# ============================================================================= +set(THIRD_PARTY_DIRECTORY "${PROJECT_SOURCE_DIR}/ext") +add_external_project(fmt OFF) +add_external_project(spdlog OFF) +add_external_project(pybind11) +add_external_project(cli11 OFF) +add_external_project(eigen OFF) + +add_subdirectory(${THIRD_PARTY_DIRECTORY}/fmt EXCLUDE_FROM_ALL) +set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) + +include_directories( + SYSTEM ${THIRD_PARTY_DIRECTORY} ${THIRD_PARTY_DIRECTORY}/catch/include + ${THIRD_PARTY_DIRECTORY}/fmt/include ${THIRD_PARTY_DIRECTORY}/spdlog/include + ${THIRD_PARTY_DIRECTORY}/cli11/include) + # ============================================================================= # HPC Coding Conventions # ============================================================================= @@ -66,7 +98,12 @@ set(NMODL_CMakeFormat_EXCLUDES_RE set(NMODL_ClangFormat_DEPENDENCIES pyastgen parser-gen CACHE STRING "list of CMake targets to build before formatting C++ code" FORCE) + +# initialize submodule of coding conventions under cmake +set(THIRD_PARTY_DIRECTORY "${PROJECT_SOURCE_DIR}/cmake") +add_external_project(hpc-coding-conventions OFF) add_subdirectory(cmake/hpc-coding-conventions/cpp) +include(FindClangFormat) # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) @@ -85,20 +122,6 @@ add_custom_target( clean_ipynb "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") -# ============================================================================= -# Include cmake modules -# ============================================================================= -list(APPEND CMAKE_MODULE_PATH ${NMODL_PROJECT_SOURCE_DIR}/cmake) -include(Catch) -include(ClangTidyHelper) -include(CompilerHelper) -include(FindClangFormat) -include(FindPythonModule) -include(FlexHelper) -include(GitRevision) -include(PythonLinkHelper) -include(RpathHelper) - # ============================================================================= # Adjust install prefix for wheel # ============================================================================= @@ -129,27 +152,6 @@ endif() include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src ${PROJECT_BINARY_DIR}/src) -include_directories( - SYSTEM ${NMODL_PROJECT_SOURCE_DIR}/ext ${NMODL_PROJECT_SOURCE_DIR}/ext/fmt/include - ${NMODL_PROJECT_SOURCE_DIR}/ext/spdlog/include) - -# ============================================================================= -# Include pybind11 -# ============================================================================= -message(STATUS "INCLUDING PYBIND11") -add_subdirectory(ext/pybind11) - -# ============================================================================= -# Include fmt library -# ============================================================================= -message(STATUS "INCLUDING FMT") -add_subdirectory(${NMODL_PROJECT_SOURCE_DIR}/ext/fmt EXCLUDE_FROM_ALL) -set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) - -# ============================================================================= -# Include path from external libraries -# ============================================================================= -include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/cli11/include) # generate file with version number from git and nrnunits.lib file path configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in diff --git a/cmake/nmodl/ExternalProjectHelper.cmake b/cmake/nmodl/ExternalProjectHelper.cmake new file mode 100644 index 0000000000..087974d9cd --- /dev/null +++ b/cmake/nmodl/ExternalProjectHelper.cmake @@ -0,0 +1,38 @@ +find_package(Git QUIET) + +set(THIRD_PARTY_DIRECTORY + "${NMODL_PROJECT_SOURCE_DIR}/3rdparty" + CACHE PATH "The path were all the 3rd party projects can be found") + +# initialize submodule with given path +function(initialize_submodule path) + if(NOT ${GIT_FOUND}) + message( + FATAL_ERROR "git not found and ${path} sub-module not cloned (use git clone --recursive)") + endif() + message(STATUS "Sub-module : missing ${path}: running git submodule update --init --recursive") + execute_process( + COMMAND + git submodule update --init --recursive -- ${path} + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}) +endfunction() + +# check for external project and initialize submodule if it is missing +function(add_external_project name) + find_path( + ${name}_PATH + NAMES CMakeLists.txt + PATHS "${THIRD_PARTY_DIRECTORY}/${name}") + if(NOT EXISTS ${${name}_PATH}) + initialize_submodule("${THIRD_PARTY_DIRECTORY}/${name}") + else() + message(STATUS "Sub-project : using ${name} from \"${THIRD_PARTY_DIRECTORY}/${name}\"") + endif() + if(${ARGC} GREATER 1) + if(${ARGV2}) + add_subdirectory("${THIRD_PARTY_DIRECTORY}/${name}") + endif() + else() + add_subdirectory("${THIRD_PARTY_DIRECTORY}/${name}") + endif() +endfunction() From 095335280738598f85efb6f757b8a4c465cc21c5 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Sat, 5 Dec 2020 19:17:33 +0100 Subject: [PATCH 320/871] Code generator visitors are now ConstAstVisitors ... and more! (BlueBrain/nmodl#427) * Code generator visitors now extends `ConstAstVisitor` * Remove a couple of objects copies * Prefer ostringstream than string concatenation * Remove const specifier from lvalue parameter of function declarations * Add static specifier to some symbols not meant to be exported in compilation unit * Prefer std::move in constructors * Include system headers with <>, not "" (e.g. for CLI11) NMODL Repo SHA: BlueBrain/nmodl@6c85112faa07e5a7dba252715534eb4d1051960f --- src/nmodl/codegen/codegen_acc_visitor.hpp | 4 +- src/nmodl/codegen/codegen_c_visitor.cpp | 108 +++++++++--------- src/nmodl/codegen/codegen_c_visitor.hpp | 106 ++++++++--------- src/nmodl/codegen/codegen_helper_visitor.cpp | 58 +++++----- src/nmodl/codegen/codegen_helper_visitor.hpp | 60 +++++----- src/nmodl/codegen/codegen_info.hpp | 34 +++--- src/nmodl/codegen/codegen_ispc_visitor.cpp | 16 +-- src/nmodl/codegen/codegen_ispc_visitor.hpp | 14 +-- src/nmodl/lexer/main_c.cpp | 3 +- src/nmodl/lexer/main_nmodl.cpp | 2 +- src/nmodl/nmodl/main.cpp | 2 +- src/nmodl/parser/diffeq_context.cpp | 20 ++-- src/nmodl/parser/diffeq_context.hpp | 46 ++++---- src/nmodl/parser/diffeq_helper.hpp | 21 ++-- src/nmodl/parser/main_c.cpp | 4 +- src/nmodl/parser/main_nmodl.cpp | 2 +- src/nmodl/parser/main_units.cpp | 2 +- src/nmodl/units/units.hpp | 4 +- .../visitors/after_cvode_to_cnexp_visitor.cpp | 1 - .../visitors/constant_folder_visitor.cpp | 6 +- src/nmodl/visitors/main.cpp | 4 +- 21 files changed, 256 insertions(+), 261 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index cdeb7cc339..775746f99d 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -100,14 +100,14 @@ class CodegenAccVisitor: public CodegenCVisitor { const std::string& output_dir, LayoutType layout, const std::string& float_type, - const bool optimize_ionvar_copies) + bool optimize_ionvar_copies) : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies) {} CodegenAccVisitor(const std::string& mod_file, std::ostream& stream, LayoutType layout, const std::string& float_type, - const bool optimize_ionvar_copies) + bool optimize_ionvar_copies) : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} }; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index e7958551ae..914199545e 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -45,7 +45,7 @@ using nmodl::utils::UseNumbersInString; const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; -void CodegenCVisitor::visit_string(String& node) { +void CodegenCVisitor::visit_string(const String& node) { if (!codegen) { return; } @@ -57,12 +57,12 @@ void CodegenCVisitor::visit_string(String& node) { } -void CodegenCVisitor::visit_integer(Integer& node) { +void CodegenCVisitor::visit_integer(const Integer& node) { if (!codegen) { return; } const auto& macro = node.get_macro(); - auto value = node.get_value(); + const auto& value = node.get_value(); if (macro) { macro->accept(*this); } else { @@ -71,7 +71,7 @@ void CodegenCVisitor::visit_integer(Integer& node) { } -void CodegenCVisitor::visit_float(Float& node) { +void CodegenCVisitor::visit_float(const Float& node) { if (!codegen) { return; } @@ -79,7 +79,7 @@ void CodegenCVisitor::visit_float(Float& node) { } -void CodegenCVisitor::visit_double(Double& node) { +void CodegenCVisitor::visit_double(const Double& node) { if (!codegen) { return; } @@ -87,7 +87,7 @@ void CodegenCVisitor::visit_double(Double& node) { } -void CodegenCVisitor::visit_boolean(Boolean& node) { +void CodegenCVisitor::visit_boolean(const Boolean& node) { if (!codegen) { return; } @@ -95,7 +95,7 @@ void CodegenCVisitor::visit_boolean(Boolean& node) { } -void CodegenCVisitor::visit_name(Name& node) { +void CodegenCVisitor::visit_name(const Name& node) { if (!codegen) { return; } @@ -103,12 +103,12 @@ void CodegenCVisitor::visit_name(Name& node) { } -void CodegenCVisitor::visit_unit(ast::Unit& node) { +void CodegenCVisitor::visit_unit(const ast::Unit& node) { // do not print units } -void CodegenCVisitor::visit_prime_name(PrimeName& node) { +void CodegenCVisitor::visit_prime_name(const PrimeName& node) { throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); } @@ -116,7 +116,7 @@ void CodegenCVisitor::visit_prime_name(PrimeName& node) { /** * \todo : Validate how @ is being handled in neuron implementation */ -void CodegenCVisitor::visit_var_name(VarName& node) { +void CodegenCVisitor::visit_var_name(const VarName& node) { if (!codegen) { return; } @@ -136,7 +136,7 @@ void CodegenCVisitor::visit_var_name(VarName& node) { } -void CodegenCVisitor::visit_indexed_name(IndexedName& node) { +void CodegenCVisitor::visit_indexed_name(const IndexedName& node) { if (!codegen) { return; } @@ -147,7 +147,7 @@ void CodegenCVisitor::visit_indexed_name(IndexedName& node) { } -void CodegenCVisitor::visit_local_list_statement(LocalListStatement& node) { +void CodegenCVisitor::visit_local_list_statement(const LocalListStatement& node) { if (!codegen) { return; } @@ -157,7 +157,7 @@ void CodegenCVisitor::visit_local_list_statement(LocalListStatement& node) { } -void CodegenCVisitor::visit_if_statement(IfStatement& node) { +void CodegenCVisitor::visit_if_statement(const IfStatement& node) { if (!codegen) { return; } @@ -173,7 +173,7 @@ void CodegenCVisitor::visit_if_statement(IfStatement& node) { } -void CodegenCVisitor::visit_else_if_statement(ElseIfStatement& node) { +void CodegenCVisitor::visit_else_if_statement(const ElseIfStatement& node) { if (!codegen) { return; } @@ -184,7 +184,7 @@ void CodegenCVisitor::visit_else_if_statement(ElseIfStatement& node) { } -void CodegenCVisitor::visit_else_statement(ElseStatement& node) { +void CodegenCVisitor::visit_else_statement(const ElseStatement& node) { if (!codegen) { return; } @@ -193,7 +193,7 @@ void CodegenCVisitor::visit_else_statement(ElseStatement& node) { } -void CodegenCVisitor::visit_while_statement(WhileStatement& node) { +void CodegenCVisitor::visit_while_statement(const WhileStatement& node) { printer->add_text("while ("); node.get_condition()->accept(*this); printer->add_text(") "); @@ -201,7 +201,7 @@ void CodegenCVisitor::visit_while_statement(WhileStatement& node) { } -void CodegenCVisitor::visit_from_statement(ast::FromStatement& node) { +void CodegenCVisitor::visit_from_statement(const ast::FromStatement& node) { if (!codegen) { return; } @@ -225,7 +225,7 @@ void CodegenCVisitor::visit_from_statement(ast::FromStatement& node) { } -void CodegenCVisitor::visit_paren_expression(ParenExpression& node) { +void CodegenCVisitor::visit_paren_expression(const ParenExpression& node) { if (!codegen) { return; } @@ -235,13 +235,13 @@ void CodegenCVisitor::visit_paren_expression(ParenExpression& node) { } -void CodegenCVisitor::visit_binary_expression(BinaryExpression& node) { +void CodegenCVisitor::visit_binary_expression(const BinaryExpression& node) { if (!codegen) { return; } auto op = node.get_op().eval(); - auto lhs = node.get_lhs(); - auto rhs = node.get_rhs(); + const auto& lhs = node.get_lhs(); + const auto& rhs = node.get_rhs(); if (op == "^") { printer->add_text("pow("); lhs->accept(*this); @@ -256,7 +256,7 @@ void CodegenCVisitor::visit_binary_expression(BinaryExpression& node) { } -void CodegenCVisitor::visit_binary_operator(BinaryOperator& node) { +void CodegenCVisitor::visit_binary_operator(const BinaryOperator& node) { if (!codegen) { return; } @@ -264,7 +264,7 @@ void CodegenCVisitor::visit_binary_operator(BinaryOperator& node) { } -void CodegenCVisitor::visit_unary_operator(UnaryOperator& node) { +void CodegenCVisitor::visit_unary_operator(const UnaryOperator& node) { if (!codegen) { return; } @@ -277,7 +277,7 @@ void CodegenCVisitor::visit_unary_operator(UnaryOperator& node) { * Sometime we want to analyse ast nodes even if code generation is * false. Hence we visit children even if code generation is false. */ -void CodegenCVisitor::visit_statement_block(StatementBlock& node) { +void CodegenCVisitor::visit_statement_block(const StatementBlock& node) { if (!codegen) { node.visit_children(*this); return; @@ -286,7 +286,7 @@ void CodegenCVisitor::visit_statement_block(StatementBlock& node) { } -void CodegenCVisitor::visit_function_call(FunctionCall& node) { +void CodegenCVisitor::visit_function_call(const FunctionCall& node) { if (!codegen) { return; } @@ -294,7 +294,7 @@ void CodegenCVisitor::visit_function_call(FunctionCall& node) { } -void CodegenCVisitor::visit_verbatim(Verbatim& node) { +void CodegenCVisitor::visit_verbatim(const Verbatim& node) { if (!codegen) { return; } @@ -1279,13 +1279,13 @@ std::string CodegenCVisitor::k_const() { /****************************************************************************************/ -void CodegenCVisitor::visit_watch_statement(ast::WatchStatement& node) { +void CodegenCVisitor::visit_watch_statement(const ast::WatchStatement& node) { printer->add_text("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)"_format( current_watch_statement++)); } -void CodegenCVisitor::print_statement_block(ast::StatementBlock& node, +void CodegenCVisitor::print_statement_block(const ast::StatementBlock& node, bool open_brace, bool close_brace) { if (open_brace) { @@ -1314,7 +1314,7 @@ void CodegenCVisitor::print_statement_block(ast::StatementBlock& node, } -void CodegenCVisitor::print_function_call(FunctionCall& node) { +void CodegenCVisitor::print_function_call(const FunctionCall& node) { auto name = node.get_node_name(); auto function_name = name; if (defined_method(name)) { @@ -1424,7 +1424,7 @@ void CodegenCVisitor::print_function_prototypes() { } -static TableStatement* get_table_statement(ast::Block& node) { +static const TableStatement* get_table_statement(const ast::Block& node) { // TableStatementVisitor v; const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); @@ -1435,11 +1435,11 @@ static TableStatement* get_table_statement(ast::Block& node) { table_statements.size()); throw std::runtime_error(message); } - return dynamic_cast(table_statements.front().get()); + return dynamic_cast(table_statements.front().get()); } -void CodegenCVisitor::print_table_check_function(Block& node) { +void CodegenCVisitor::print_table_check_function(const Block& node) { auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); auto depend_variables = statement->get_depend_vars(); @@ -1521,7 +1521,7 @@ void CodegenCVisitor::print_table_check_function(Block& node) { } -void CodegenCVisitor::print_table_replacement_function(ast::Block& node) { +void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { auto name = node.get_node_name(); auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); @@ -1607,7 +1607,7 @@ void CodegenCVisitor::print_check_table_thread_function() { } -void CodegenCVisitor::print_function_or_procedure(ast::Block& node, const std::string& name) { +void CodegenCVisitor::print_function_or_procedure(const ast::Block& node, const std::string& name) { printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); @@ -1627,7 +1627,7 @@ void CodegenCVisitor::print_function_or_procedure(ast::Block& node, const std::s } -void CodegenCVisitor::print_function_procedure_helper(ast::Block& node) { +void CodegenCVisitor::print_function_procedure_helper(const ast::Block& node) { codegen = true; auto name = node.get_node_name(); @@ -1644,12 +1644,12 @@ void CodegenCVisitor::print_function_procedure_helper(ast::Block& node) { } -void CodegenCVisitor::print_procedure(ast::ProcedureBlock& node) { +void CodegenCVisitor::print_procedure(const ast::ProcedureBlock& node) { print_function_procedure_helper(node); } -void CodegenCVisitor::print_function(ast::FunctionBlock& node) { +void CodegenCVisitor::print_function(const ast::FunctionBlock& node) { auto name = node.get_node_name(); // name of return variable @@ -1679,7 +1679,7 @@ std::string CodegenCVisitor::find_var_unique_name(const std::string& original_na return unique_name; } -void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) { +void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { // solution vector to store copy of state vars for Newton solver printer->add_newline(); @@ -1751,7 +1751,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBloc printer->add_line("newton_functor.finalize();"); } -void CodegenCVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) { +void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) { printer->add_newline(); // Check if there is a variable defined in the mod file as X, J, Jm or F and if yes @@ -3187,7 +3187,7 @@ void CodegenCVisitor::print_instance_variable_setup() { } -void CodegenCVisitor::print_initial_block(InitialBlock* node) { +void CodegenCVisitor::print_initial_block(const InitialBlock* node) { if (info.artificial_cell) { printer->add_line("double v = 0.0;"); } else { @@ -3457,7 +3457,7 @@ void CodegenCVisitor::print_watch_check() { } -void CodegenCVisitor::print_net_receive_common_code(Block& node, bool need_mech_inst) { +void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need_mech_inst) { printer->add_line("int tid = pnt->_tid;"); printer->add_line("int id = pnt->_i_instance;"); printer->add_line("double v = 0;"); @@ -3495,7 +3495,7 @@ void CodegenCVisitor::print_net_receive_common_code(Block& node, bool need_mech_ } -void CodegenCVisitor::print_net_send_call(FunctionCall& node) { +void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { auto arguments = node.get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; @@ -3527,7 +3527,7 @@ void CodegenCVisitor::print_net_send_call(FunctionCall& node) { } -void CodegenCVisitor::print_net_move_call(FunctionCall& node) { +void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { if (!printing_net_receive) { std::cout << "Error : net_move only allowed in NET_RECEIVE block" << std::endl; abort(); @@ -3555,8 +3555,8 @@ void CodegenCVisitor::print_net_move_call(FunctionCall& node) { } -void CodegenCVisitor::print_net_event_call(FunctionCall& node) { - auto arguments = node.get_arguments(); +void CodegenCVisitor::print_net_event_call(const FunctionCall& node) { + const auto& arguments = node.get_arguments(); if (info.artificial_cell) { printer->add_text("net_event(pnt, "); print_vector_elements(arguments, ", "); @@ -3594,7 +3594,7 @@ void CodegenCVisitor::print_net_event_call(FunctionCall& node) { * * So, the `R` in AST needs to be renamed with `(*R)`. */ -static void rename_net_receive_arguments(ast::NetReceiveBlock& net_receive_node, ast::Node& node) { +static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive_node, const ast::Node& node) { auto parameters = net_receive_node.get_parameters(); for (auto& parameter: parameters) { auto name = parameter->get_node_name(); @@ -3757,7 +3757,7 @@ void CodegenCVisitor::print_net_send_buffering() { } -void CodegenCVisitor::visit_for_netcon(ast::ForNetcon& node) { +void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { // For_netcon should take the same arguments as net_receive and apply the operations // in the block to the weights of the netcons. Since all the weights are on the same vector, // weights, we have a mask of operations that we apply iteratively, advancing the offset @@ -3973,7 +3973,7 @@ void CodegenCVisitor::print_newtonspace_transfer_to_device() const { } -void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& node) { +void CodegenCVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { if (!codegen) { return; } @@ -3992,7 +3992,7 @@ void CodegenCVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& n printer->add_line(statement); } -void CodegenCVisitor::visit_solution_expression(SolutionExpression& node) { +void CodegenCVisitor::visit_solution_expression(const SolutionExpression& node) { auto block = node.get_node_to_solve().get(); if (block->is_statement_block()) { auto statement_block = dynamic_cast(block); @@ -4070,7 +4070,7 @@ void CodegenCVisitor::print_nrn_state() { /****************************************************************************************/ -void CodegenCVisitor::print_nrn_current(BreakpointBlock& node) { +void CodegenCVisitor::print_nrn_current(const BreakpointBlock& node) { auto args = internal_method_parameters(); const auto& block = node.get_statement_block(); printer->add_newline(2); @@ -4087,7 +4087,7 @@ void CodegenCVisitor::print_nrn_current(BreakpointBlock& node) { } -void CodegenCVisitor::print_nrn_cur_conductance_kernel(BreakpointBlock& node) { +void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { const auto& block = node.get_statement_block(); print_statement_block(*block, false, false); if (!info.currents.empty()) { @@ -4154,7 +4154,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { } -void CodegenCVisitor::print_nrn_cur_kernel(BreakpointBlock& node) { +void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); if (ion_variable_struct_required()) { @@ -4358,7 +4358,7 @@ void CodegenCVisitor::set_codegen_global_variables(std::vector& glob } -void CodegenCVisitor::setup(Program& node) { +void CodegenCVisitor::setup(const Program& node) { program_symtab = node.get_symbol_table(); CodegenHelperVisitor v; @@ -4378,7 +4378,7 @@ void CodegenCVisitor::setup(Program& node) { } -void CodegenCVisitor::visit_program(Program& node) { +void CodegenCVisitor::visit_program(const Program& node) { setup(node); print_codegen_routines(); print_wrapper_routines(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 3c8cd98cec..fc980e87da 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -104,7 +104,7 @@ enum class MemberType { */ struct IndexVariableInfo { /// symbol for the variable - std::shared_ptr symbol; + const std::shared_ptr symbol; /// if variable reside in vdata field of NrnThread /// typically true for bbcore pointer @@ -121,11 +121,11 @@ struct IndexVariableInfo { /// if the variable is qualified as constant (this is property of IndexVariable) bool is_constant = false; - IndexVariableInfo(const std::shared_ptr& symbol, + IndexVariableInfo(std::shared_ptr symbol, bool is_vdata = false, bool is_index = false, bool is_integer = false) - : symbol(symbol) + : symbol(std::move(symbol)) , is_vdata(is_vdata) , is_index(is_index) , is_integer(is_integer) {} @@ -185,7 +185,7 @@ using printer::CodePrinter; * error checking. For example, see netstim.mod where we * have removed return from verbatim block. */ -class CodegenCVisitor: public visitor::AstVisitor { +class CodegenCVisitor: public visitor::ConstAstVisitor { protected: using SymbolType = std::shared_ptr; @@ -723,7 +723,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param open_brace Print an opening brace if \c false * \param close_brace Print a closing brace if \c true */ - void print_statement_block(ast::StatementBlock& node, + void print_statement_block(const ast::StatementBlock& node, bool open_brace = true, bool close_brace = true); @@ -1220,28 +1220,28 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print call to internal or external function * \param node The AST node representing a function call */ - void print_function_call(ast::FunctionCall& node); + void print_function_call(const ast::FunctionCall& node); /** * Print call to \c net\_send * \param node The AST node representing the function call */ - void print_net_send_call(ast::FunctionCall& node); + void print_net_send_call(const ast::FunctionCall& node); /** * Print call to net\_move * \param node The AST node representing the function call */ - void print_net_move_call(ast::FunctionCall& node); + void print_net_move_call(const ast::FunctionCall& node); /** * Print call to net\_event * \param node The AST node representing the function call */ - void print_net_event_call(ast::FunctionCall& node); + void print_net_event_call(const ast::FunctionCall& node); /** @@ -1337,14 +1337,14 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param node the AST node representing the function or procedure in NMODL * \param name the name of the function or procedure */ - void print_function_or_procedure(ast::Block& node, const std::string& name); + void print_function_or_procedure(const ast::Block& node, const std::string& name); /** * Common helper function to help printing function or procedure blocks * \param node the AST node representing the function or procedure in NMODL */ - void print_function_procedure_helper(ast::Block& node); + void print_function_procedure_helper(const ast::Block& node); /** * Print thread related memory allocation and deallocation callbacks @@ -1375,7 +1375,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * * \param node The AST Node representing a NMODL initial block */ - void print_initial_block(ast::InitialBlock* node); + void print_initial_block(const ast::InitialBlock* node); /** @@ -1391,7 +1391,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \param need_mech_inst \c true if a local \c inst variable needs to be defined in generated * code */ - void print_net_receive_common_code(ast::Block& node, bool need_mech_inst = true); + void print_net_receive_common_code(const ast::Block& node, bool need_mech_inst = true); /** @@ -1506,7 +1506,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print main body of nrn_cur function * \param node the AST node representing the NMODL breakpoint block */ - void print_nrn_cur_kernel(ast::BreakpointBlock& node); + void print_nrn_cur_kernel(const ast::BreakpointBlock& node); /** @@ -1517,7 +1517,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * * \param node the AST node representing the NMODL breakpoint block */ - void print_nrn_cur_conductance_kernel(ast::BreakpointBlock& node); + void print_nrn_cur_conductance_kernel(const ast::BreakpointBlock& node); /** @@ -1535,7 +1535,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified * \param node the AST node representing the NMODL breakpoint block */ - void print_nrn_current(ast::BreakpointBlock& node); + void print_nrn_current(const ast::BreakpointBlock& node); /** @@ -1680,14 +1680,14 @@ class CodegenCVisitor: public visitor::AstVisitor { CodegenCVisitor(const std::string& mod_filename, const std::string& output_dir, LayoutType layout, - const std::string& float_type, + std::string float_type, const bool optimize_ionvar_copies, const std::string& extension = ".cpp") : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , printer(target_printer) , mod_filename(mod_filename) , layout(layout) - , float_type(float_type) + , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} /** @@ -1802,28 +1802,28 @@ class CodegenCVisitor: public visitor::AstVisitor { * Print \c check\_function() for functions or procedure using table * \param node The AST node representing a function or procedure block */ - void print_table_check_function(ast::Block& node); + void print_table_check_function(const ast::Block& node); /** * Print replacement function for function or procedure using table * \param node The AST node representing a function or procedure block */ - void print_table_replacement_function(ast::Block& node); + void print_table_replacement_function(const ast::Block& node); /** * Print NMODL function in target backend code * \param node */ - void print_function(ast::FunctionBlock& node); + void print_function(const ast::FunctionBlock& node); /** * Print NMODL procedure in target backend code * \param node */ - virtual void print_procedure(ast::ProcedureBlock& node); + virtual void print_procedure(const ast::ProcedureBlock& node); /** Setup the target backend code generator @@ -1831,7 +1831,7 @@ class CodegenCVisitor: public visitor::AstVisitor { * Typically called from within \c visit\_program but may be called from * specialized targets to setup this Code generator as fallback. */ - void setup(ast::Program& node); + void setup(const ast::Program& node); /** @@ -1848,36 +1848,36 @@ class CodegenCVisitor: public visitor::AstVisitor { */ std::string find_var_unique_name(const std::string& original_name) const; - void visit_binary_expression(ast::BinaryExpression& node) override; - void visit_binary_operator(ast::BinaryOperator& node) override; - void visit_boolean(ast::Boolean& node) override; - void visit_double(ast::Double& node) override; - void visit_else_if_statement(ast::ElseIfStatement& node) override; - void visit_else_statement(ast::ElseStatement& node) override; - void visit_float(ast::Float& node) override; - void visit_from_statement(ast::FromStatement& node) override; - void visit_function_call(ast::FunctionCall& node) override; - void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) override; - void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) override; - void visit_if_statement(ast::IfStatement& node) override; - void visit_indexed_name(ast::IndexedName& node) override; - void visit_integer(ast::Integer& node) override; - void visit_local_list_statement(ast::LocalListStatement& node) override; - void visit_name(ast::Name& node) override; - void visit_paren_expression(ast::ParenExpression& node) override; - void visit_prime_name(ast::PrimeName& node) override; - void visit_program(ast::Program& node) override; - void visit_statement_block(ast::StatementBlock& node) override; - void visit_string(ast::String& node) override; - void visit_solution_expression(ast::SolutionExpression& node) override; - void visit_unary_operator(ast::UnaryOperator& node) override; - void visit_unit(ast::Unit& node) override; - void visit_var_name(ast::VarName& node) override; - void visit_verbatim(ast::Verbatim& node) override; - void visit_watch_statement(ast::WatchStatement& node) override; - void visit_while_statement(ast::WhileStatement& node) override; - void visit_derivimplicit_callback(ast::DerivimplicitCallback& node) override; - void visit_for_netcon(ast::ForNetcon& node) override; + void visit_binary_expression(const ast::BinaryExpression& node) override; + void visit_binary_operator(const ast::BinaryOperator& node) override; + void visit_boolean(const ast::Boolean& node) override; + void visit_double(const ast::Double& node) override; + void visit_else_if_statement(const ast::ElseIfStatement& node) override; + void visit_else_statement(const ast::ElseStatement& node) override; + void visit_float(const ast::Float& node) override; + void visit_from_statement(const ast::FromStatement& node) override; + void visit_function_call(const ast::FunctionCall& node) override; + void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; + void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; + void visit_if_statement(const ast::IfStatement& node) override; + void visit_indexed_name(const ast::IndexedName& node) override; + void visit_integer(const ast::Integer& node) override; + void visit_local_list_statement(const ast::LocalListStatement& node) override; + void visit_name(const ast::Name& node) override; + void visit_paren_expression(const ast::ParenExpression& node) override; + void visit_prime_name(const ast::PrimeName& node) override; + void visit_program(const ast::Program& node) override; + void visit_statement_block(const ast::StatementBlock& node) override; + void visit_string(const ast::String& node) override; + void visit_solution_expression(const ast::SolutionExpression& node) override; + void visit_unary_operator(const ast::UnaryOperator& node) override; + void visit_unit(const ast::Unit& node) override; + void visit_var_name(const ast::VarName& node) override; + void visit_verbatim(const ast::Verbatim& node) override; + void visit_watch_statement(const ast::WatchStatement& node) override; + void visit_while_statement(const ast::WhileStatement& node) override; + void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; + void visit_for_netcon(const ast::ForNetcon& node) override; }; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 712874739e..b4ffe91a59 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -411,7 +411,7 @@ void CodegenHelperVisitor::find_table_variables() { } -void CodegenHelperVisitor::visit_suffix(Suffix& node) { +void CodegenHelperVisitor::visit_suffix(const Suffix& node) { const auto& type = node.get_type()->get_node_name(); if (type == naming::POINT_PROCESS) { info.point_process = true; @@ -424,12 +424,12 @@ void CodegenHelperVisitor::visit_suffix(Suffix& node) { } -void CodegenHelperVisitor::visit_electrode_current(ElectrodeCurrent& node) { +void CodegenHelperVisitor::visit_electrode_current(const ElectrodeCurrent& node) { info.electrode_current = true; } -void CodegenHelperVisitor::visit_initial_block(InitialBlock& node) { +void CodegenHelperVisitor::visit_initial_block(const InitialBlock& node) { if (under_net_receive_block) { info.net_receive_initial_node = &node; } else { @@ -439,7 +439,7 @@ void CodegenHelperVisitor::visit_initial_block(InitialBlock& node) { } -void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock& node) { +void CodegenHelperVisitor::visit_net_receive_block(const NetReceiveBlock& node) { under_net_receive_block = true; info.net_receive_node = &node; info.num_net_receive_parameters = node.get_parameters().size(); @@ -448,18 +448,18 @@ void CodegenHelperVisitor::visit_net_receive_block(NetReceiveBlock& node) { } -void CodegenHelperVisitor::visit_derivative_block(DerivativeBlock& node) { +void CodegenHelperVisitor::visit_derivative_block(const DerivativeBlock& node) { under_derivative_block = true; node.visit_children(*this); under_derivative_block = false; } -void CodegenHelperVisitor::visit_derivimplicit_callback(ast::DerivimplicitCallback& node) { +void CodegenHelperVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { info.derivimplicit_callbacks.push_back(&node); } -void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock& node) { +void CodegenHelperVisitor::visit_breakpoint_block(const BreakpointBlock& node) { under_breakpoint_block = true; info.breakpoint_node = &node; node.visit_children(*this); @@ -467,13 +467,13 @@ void CodegenHelperVisitor::visit_breakpoint_block(BreakpointBlock& node) { } -void CodegenHelperVisitor::visit_nrn_state_block(ast::NrnStateBlock& node) { +void CodegenHelperVisitor::visit_nrn_state_block(const ast::NrnStateBlock& node) { info.nrn_state_block = &node; node.visit_children(*this); } -void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock& node) { +void CodegenHelperVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { info.procedures.push_back(&node); node.visit_children(*this); if (table_statement_used) { @@ -483,7 +483,7 @@ void CodegenHelperVisitor::visit_procedure_block(ast::ProcedureBlock& node) { } -void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock& node) { +void CodegenHelperVisitor::visit_function_block(const ast::FunctionBlock& node) { info.functions.push_back(&node); node.visit_children(*this); if (table_statement_used) { @@ -493,15 +493,17 @@ void CodegenHelperVisitor::visit_function_block(ast::FunctionBlock& node) { } -void CodegenHelperVisitor::visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) { +void CodegenHelperVisitor::visit_eigen_newton_solver_block( + const ast::EigenNewtonSolverBlock& node) { info.eigen_newton_solver_exist = true; } -void CodegenHelperVisitor::visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) { +void CodegenHelperVisitor::visit_eigen_linear_solver_block( + const ast::EigenLinearSolverBlock& node) { info.eigen_linear_solver_exist = true; } -void CodegenHelperVisitor::visit_function_call(FunctionCall& node) { +void CodegenHelperVisitor::visit_function_call(const FunctionCall& node) { auto name = node.get_node_name(); if (name == naming::NET_SEND_METHOD) { info.net_send_used = true; @@ -512,7 +514,7 @@ void CodegenHelperVisitor::visit_function_call(FunctionCall& node) { } -void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint& node) { +void CodegenHelperVisitor::visit_conductance_hint(const ConductanceHint& node) { const auto& ion = node.get_ion(); const auto& variable = node.get_conductance(); std::string ion_name; @@ -538,7 +540,7 @@ void CodegenHelperVisitor::visit_conductance_hint(ConductanceHint& node) { * is because prime_variables_by_order should contain state variable name and * not the one replaced by solver pass. */ -void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock& node) { +void CodegenHelperVisitor::visit_statement_block(const ast::StatementBlock& node) { const auto& statements = node.get_statements(); for (auto& statement: statements) { statement->accept(*this); @@ -562,12 +564,12 @@ void CodegenHelperVisitor::visit_statement_block(ast::StatementBlock& node) { } } -void CodegenHelperVisitor::visit_factor_def(ast::FactorDef& node) { +void CodegenHelperVisitor::visit_factor_def(const ast::FactorDef& node) { info.factor_definitions.push_back(&node); } -void CodegenHelperVisitor::visit_binary_expression(BinaryExpression& node) { +void CodegenHelperVisitor::visit_binary_expression(const BinaryExpression& node) { if (node.get_op().eval() == "=") { assign_lhs = node.get_lhs(); } @@ -576,34 +578,34 @@ void CodegenHelperVisitor::visit_binary_expression(BinaryExpression& node) { } -void CodegenHelperVisitor::visit_bbcore_pointer(BbcorePointer& node) { +void CodegenHelperVisitor::visit_bbcore_pointer(const BbcorePointer& node) { info.bbcore_pointer_used = true; } -void CodegenHelperVisitor::visit_watch(ast::Watch& node) { +void CodegenHelperVisitor::visit_watch(const ast::Watch& node) { info.watch_count++; } -void CodegenHelperVisitor::visit_watch_statement(ast::WatchStatement& node) { +void CodegenHelperVisitor::visit_watch_statement(const ast::WatchStatement& node) { info.watch_statements.push_back(&node); node.visit_children(*this); } -void CodegenHelperVisitor::visit_for_netcon(ast::ForNetcon& node) { +void CodegenHelperVisitor::visit_for_netcon(const ast::ForNetcon& node) { info.for_netcon_used = true; } -void CodegenHelperVisitor::visit_table_statement(ast::TableStatement& node) { +void CodegenHelperVisitor::visit_table_statement(const ast::TableStatement& node) { info.table_count++; table_statement_used = true; } -void CodegenHelperVisitor::visit_program(ast::Program& node) { +void CodegenHelperVisitor::visit_program(const ast::Program& node) { psymtab = node.get_symbol_table(); auto blocks = node.get_blocks(); for (auto& block: blocks) { @@ -620,24 +622,24 @@ void CodegenHelperVisitor::visit_program(ast::Program& node) { } -codegen::CodegenInfo CodegenHelperVisitor::analyze(ast::Program& node) { +codegen::CodegenInfo CodegenHelperVisitor::analyze(const ast::Program& node) { node.accept(*this); return info; } -void CodegenHelperVisitor::visit_linear_block(ast::LinearBlock& node) { +void CodegenHelperVisitor::visit_linear_block(const ast::LinearBlock& node) { info.vectorize = false; } -void CodegenHelperVisitor::visit_non_linear_block(ast::NonLinearBlock& node) { +void CodegenHelperVisitor::visit_non_linear_block(const ast::NonLinearBlock& node) { info.vectorize = false; } -void CodegenHelperVisitor::visit_discrete_block(ast::DiscreteBlock& node) { +void CodegenHelperVisitor::visit_discrete_block(const ast::DiscreteBlock& node) { info.vectorize = false; } -void CodegenHelperVisitor::visit_partial_block(ast::PartialBlock& node) { +void CodegenHelperVisitor::visit_partial_block(const ast::PartialBlock& node) { info.vectorize = false; } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 3ea11f2fd6..964de79cd9 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -44,7 +44,7 @@ namespace codegen { * - POINTER rng and if it's also assigned rng[4] then it is printed as one value. * Need to check what is correct value. */ -class CodegenHelperVisitor: public visitor::AstVisitor { +class CodegenHelperVisitor: public visitor::ConstAstVisitor { using SymbolType = std::shared_ptr; /// holds all codegen related information @@ -78,35 +78,35 @@ class CodegenHelperVisitor: public visitor::AstVisitor { CodegenHelperVisitor() = default; /// run visitor and return information for code generation - codegen::CodegenInfo analyze(ast::Program& node); - - void visit_electrode_current(ast::ElectrodeCurrent& node) override; - void visit_suffix(ast::Suffix& node) override; - void visit_function_call(ast::FunctionCall& node) override; - void visit_binary_expression(ast::BinaryExpression& node) override; - void visit_conductance_hint(ast::ConductanceHint& node) override; - void visit_procedure_block(ast::ProcedureBlock& node) override; - void visit_function_block(ast::FunctionBlock& node) override; - void visit_eigen_newton_solver_block(ast::EigenNewtonSolverBlock& node) override; - void visit_eigen_linear_solver_block(ast::EigenLinearSolverBlock& node) override; - void visit_statement_block(ast::StatementBlock& node) override; - void visit_initial_block(ast::InitialBlock& node) override; - void visit_breakpoint_block(ast::BreakpointBlock& node) override; - void visit_derivative_block(ast::DerivativeBlock& node) override; - void visit_derivimplicit_callback(ast::DerivimplicitCallback& node) override; - void visit_net_receive_block(ast::NetReceiveBlock& node) override; - void visit_bbcore_pointer(ast::BbcorePointer& node) override; - void visit_watch(ast::Watch& node) override; - void visit_watch_statement(ast::WatchStatement& node) override; - void visit_for_netcon(ast::ForNetcon& node) override; - void visit_table_statement(ast::TableStatement& node) override; - void visit_program(ast::Program& node) override; - void visit_factor_def(ast::FactorDef& node) override; - void visit_nrn_state_block(ast::NrnStateBlock& node) override; - void visit_linear_block(ast::LinearBlock& node) override; - void visit_non_linear_block(ast::NonLinearBlock& node) override; - void visit_discrete_block(ast::DiscreteBlock& node) override; - void visit_partial_block(ast::PartialBlock& node) override; + codegen::CodegenInfo analyze(const ast::Program& node); + + void visit_electrode_current(const ast::ElectrodeCurrent& node) override; + void visit_suffix(const ast::Suffix& node) override; + void visit_function_call(const ast::FunctionCall& node) override; + void visit_binary_expression(const ast::BinaryExpression& node) override; + void visit_conductance_hint(const ast::ConductanceHint& node) override; + void visit_procedure_block(const ast::ProcedureBlock& node) override; + void visit_function_block(const ast::FunctionBlock& node) override; + void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; + void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; + void visit_statement_block(const ast::StatementBlock& node) override; + void visit_initial_block(const ast::InitialBlock& node) override; + void visit_breakpoint_block(const ast::BreakpointBlock& node) override; + void visit_derivative_block(const ast::DerivativeBlock& node) override; + void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; + void visit_net_receive_block(const ast::NetReceiveBlock& node) override; + void visit_bbcore_pointer(const ast::BbcorePointer& node) override; + void visit_watch(const ast::Watch& node) override; + void visit_watch_statement(const ast::WatchStatement& node) override; + void visit_for_netcon(const ast::ForNetcon& node) override; + void visit_table_statement(const ast::TableStatement& node) override; + void visit_program(const ast::Program& node) override; + void visit_factor_def(const ast::FactorDef& node) override; + void visit_nrn_state_block(const ast::NrnStateBlock& node) override; + void visit_linear_block(const ast::LinearBlock& node) override; + void visit_non_linear_block(const ast::NonLinearBlock& node) override; + void visit_discrete_block(const ast::DiscreteBlock& node) override; + void visit_partial_block(const ast::PartialBlock& node) override; }; /** @} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 640cd2d661..9ca9d96dc1 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -58,7 +58,7 @@ struct Ion { Ion() = delete; Ion(std::string name) - : name(name) {} + : name(std::move(name)) {} /** * Check if variable name is a ionic current @@ -67,7 +67,7 @@ struct Ion { * If it is read variable then also get NRNCURIN flag. * If it is write variables then also get NRNCUROUT flag. */ - bool is_ionic_current(std::string text) const { + bool is_ionic_current(const std::string& text) const { return text == ("i" + name); } @@ -76,7 +76,7 @@ struct Ion { * * This is equivalent of IONIN flag in mod2c. */ - bool is_intra_cell_conc(std::string text) const { + bool is_intra_cell_conc(const std::string& text) const { return text == (name + "i"); } @@ -85,7 +85,7 @@ struct Ion { * * This is equivalent of IONOUT flag in mod2c. */ - bool is_extra_cell_conc(std::string text) const { + bool is_extra_cell_conc(const std::string& text) const { return text == (name + "o"); } @@ -94,12 +94,12 @@ struct Ion { * * This is equivalent of IONEREV flag in mod2c. */ - bool is_rev_potential(std::string text) const { + bool is_rev_potential(const std::string& text) const { return text == ("e" + name); } /// check if it is either internal or external concentration - bool is_ionic_conc(std::string text) const { + bool is_ionic_conc(const std::string& text) const { return is_intra_cell_conc(text) || is_extra_cell_conc(text); } }; @@ -241,34 +241,34 @@ struct CodegenInfo { int num_equations = 0; /// derivative block - ast::BreakpointBlock* breakpoint_node = nullptr; + const ast::BreakpointBlock* breakpoint_node = nullptr; /// nrn_state block - ast::NrnStateBlock* nrn_state_block = nullptr; + const ast::NrnStateBlock* nrn_state_block = nullptr; /// net receive block for point process - ast::NetReceiveBlock* net_receive_node = nullptr; + const ast::NetReceiveBlock* net_receive_node = nullptr; /// number of arguments to net_receive block int num_net_receive_parameters = 0; /// initial block within net receive block - ast::InitialBlock* net_receive_initial_node = nullptr; + const ast::InitialBlock* net_receive_initial_node = nullptr; /// initial block - ast::InitialBlock* initial_node = nullptr; + const ast::InitialBlock* initial_node = nullptr; /// all procedures defined in the mod file - std::vector procedures; + std::vector procedures; /// derivimplicit callbacks need to be emited - std::vector derivimplicit_callbacks; + std::vector derivimplicit_callbacks; /// all functions defined in the mod file - std::vector functions; + std::vector functions; /// all factors defined in the mod file - std::vector factor_definitions; + std::vector factor_definitions; /// ions used in the mod file std::vector ions; @@ -324,7 +324,7 @@ struct CodegenInfo { std::vector table_assigned_variables; /// function or procedures with table statement - std::vector functions_with_table; + std::vector functions_with_table; /// represent conductance statements used in mod file std::vector conductances; @@ -342,7 +342,7 @@ struct CodegenInfo { std::vector top_verbatim_blocks; /// all watch statements - std::vector watch_statements; + std::vector watch_statements; /// true if eigen newton solver is used bool eigen_newton_solver_exist = false; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 6d42162727..ca517aabd3 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -45,7 +45,7 @@ const std::unordered_set CodegenIspcVisitor::incompatible_var_names /* * Rename math functions for ISPC backend */ -void CodegenIspcVisitor::visit_function_call(ast::FunctionCall& node) { +void CodegenIspcVisitor::visit_function_call(const ast::FunctionCall& node) { if (!codegen) { return; } @@ -63,7 +63,7 @@ void CodegenIspcVisitor::visit_function_call(ast::FunctionCall& node) { /* * Rename special global variables */ -void CodegenIspcVisitor::visit_var_name(ast::VarName& node) { +void CodegenIspcVisitor::visit_var_name(const ast::VarName& node) { if (!codegen) { return; } @@ -74,7 +74,7 @@ void CodegenIspcVisitor::visit_var_name(ast::VarName& node) { CodegenCVisitor::visit_var_name(node); } -void CodegenIspcVisitor::visit_local_list_statement(ast::LocalListStatement& node) { +void CodegenIspcVisitor::visit_local_list_statement(const ast::LocalListStatement& node) { if (!codegen) { return; } @@ -375,7 +375,7 @@ CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( } -void CodegenIspcVisitor::print_procedure(ast::ProcedureBlock& node) { +void CodegenIspcVisitor::print_procedure(const ast::ProcedureBlock& node) { codegen = true; const auto& name = node.get_node_name(); print_function_or_procedure(node, name); @@ -655,7 +655,7 @@ bool CodegenIspcVisitor::check_incompatibilities() { void CodegenIspcVisitor::move_procs_to_wrapper() { auto nameset = std::set(); - auto populate_nameset = [&nameset](ast::Block* block) { + auto populate_nameset = [&nameset](const ast::Block* block) { if (block) { const auto& names = collect_nodes(*block, {ast::AstNodeType::NAME}); for (const auto& name: names) { @@ -671,7 +671,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { return !collect_nodes(node, incompatible_node_types).empty(); }; - auto target_procedures = std::vector(); + auto target_procedures = std::vector(); for (const auto& procedure: info.procedures) { const auto& name = procedure->get_name()->get_node_name(); if (nameset.find(name) == nameset.end() || has_incompatible_nodes(*procedure)) { @@ -681,7 +681,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { } } info.procedures = target_procedures; - auto target_functions = std::vector(); + auto target_functions = std::vector(); for (const auto& function: info.functions) { const auto& name = function->get_name()->get_node_name(); if (nameset.find(name) == nameset.end() || has_incompatible_nodes(*function)) { @@ -721,7 +721,7 @@ void CodegenIspcVisitor::print_block_wrappers_initial_equation_state() { } -void CodegenIspcVisitor::visit_program(ast::Program& node) { +void CodegenIspcVisitor::visit_program(const ast::Program& node) { setup(node); // we need setup to check incompatibilities diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 8fb6b95eb2..6d562ef1e0 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -56,8 +56,8 @@ class CodegenIspcVisitor: public CodegenCVisitor { std::vector emit_fallback = std::vector(static_cast(BlockType::BlockTypeEnd), false); - std::vector wrapper_procedures; - std::vector wrapper_functions; + std::vector wrapper_procedures; + std::vector wrapper_functions; protected: /// doubles are differently represented in ispc than in C @@ -159,7 +159,7 @@ class CodegenIspcVisitor: public CodegenCVisitor { /// nmodl procedure definition - void print_procedure(ast::ProcedureBlock& node) override; + void print_procedure(const ast::ProcedureBlock& node) override; void print_backend_compute_routine_decl(); @@ -239,10 +239,10 @@ class CodegenIspcVisitor: public CodegenCVisitor { : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) , fallback_codegen(mod_file, layout, float_type, optimize_ionvar_copies, wrapper_printer) {} - void visit_function_call(ast::FunctionCall& node) override; - void visit_var_name(ast::VarName& node) override; - void visit_program(ast::Program& node) override; - void visit_local_list_statement(ast::LocalListStatement& node) override; + void visit_function_call(const ast::FunctionCall& node) override; + void visit_var_name(const ast::VarName& node) override; + void visit_program(const ast::Program& node) override; + void visit_local_list_statement(const ast::LocalListStatement& node) override; }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 5b07de8ca1..31b9a58445 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -5,10 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include #include -#include "CLI/CLI.hpp" +#include #include "config/config.h" #include "lexer/c11_lexer.hpp" diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 04f6cce100..2752f3e6f7 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -8,7 +8,7 @@ #include #include -#include "CLI/CLI.hpp" +#include #include "ast/ast.hpp" #include "config/config.h" diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 0a53028489..0aca55e7de 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -8,7 +8,7 @@ #include #include -#include "CLI/CLI.hpp" +#include #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 735bdf0f30..b6a862d142 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -26,13 +26,13 @@ Term::Term(const std::string& expr, const std::string& state) } -void Term::print() { +void Term::print() const { std::cout << "Term [expr, deriv, a, b] : "; std::cout << expr << ", " << deriv << ", " << a << ", " << b << std::endl; } -void DiffEqContext::print() { +void DiffEqContext::print() const { std::cout << "-----------------DiffEq Context----------------" << std::endl; std::cout << "deriv_invalid = " << deriv_invalid << std::endl; std::cout << "eqn_invalid = " << eqn_invalid << std::endl; @@ -44,7 +44,7 @@ void DiffEqContext::print() { } -std::string DiffEqContext::cvode_deriv() { +std::string DiffEqContext::cvode_deriv() const { std::string result; if (!deriv_invalid) { result = solution.deriv; @@ -53,7 +53,7 @@ std::string DiffEqContext::cvode_deriv() { } -std::string DiffEqContext::cvode_eqnrhs() { +std::string DiffEqContext::cvode_eqnrhs() const { std::string result; if (!eqn_invalid) { result = solution.b; @@ -67,7 +67,7 @@ std::string DiffEqContext::cvode_eqnrhs() { * expression but with replacing every state variable with (state+0.001). In order * to do this we scan original expression and build new by replacing only state variable. */ -std::string DiffEqContext::get_expr_for_nonlinear() { +std::string DiffEqContext::get_expr_for_nonlinear() const { std::string expression; /// build lexer instance @@ -96,7 +96,7 @@ std::string DiffEqContext::get_expr_for_nonlinear() { /** * Return solution for non-cnexp method and when equation is linear */ -std::string DiffEqContext::get_cvode_linear_diffeq() { +std::string DiffEqContext::get_cvode_linear_diffeq() const { auto result = "D" + state + " = " + "D" + state + "/(1.0-dt*(" + solution.deriv + "))"; return result; } @@ -105,7 +105,7 @@ std::string DiffEqContext::get_cvode_linear_diffeq() { /** * Return solution for non-cnexp method and when equation is non-linear. */ -std::string DiffEqContext::get_cvode_nonlinear_diffeq() { +std::string DiffEqContext::get_cvode_nonlinear_diffeq() const { std::string expr = get_expr_for_nonlinear(); std::string sol = "D" + state + " = " + "D" + state + "/(1.0-dt*("; sol += "((" + expr + ")-(" + solution.expr + "))/0.001))"; @@ -116,7 +116,7 @@ std::string DiffEqContext::get_cvode_nonlinear_diffeq() { /** * Return solution for cnexp method */ -std::string DiffEqContext::get_cnexp_solution() { +std::string DiffEqContext::get_cnexp_solution() const { auto a = cvode_deriv(); auto b = cvode_eqnrhs(); /** @@ -139,7 +139,7 @@ std::string DiffEqContext::get_cnexp_solution() { /** * Return solution for euler method */ -std::string DiffEqContext::get_euler_solution() { +std::string DiffEqContext::get_euler_solution() const { return state + " = " + state + "+dt*(" + rhs + ")"; } @@ -147,7 +147,7 @@ std::string DiffEqContext::get_euler_solution() { /** * Return solution for non-cnexp method */ -std::string DiffEqContext::get_non_cnexp_solution() { +std::string DiffEqContext::get_non_cnexp_solution() const { std::string result; if (!deriv_invalid) { result = get_cvode_linear_diffeq(); diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 3a4cbde0dc..fec8ac9f62 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -8,6 +8,7 @@ #pragma once #include +#include namespace nmodl { namespace parser { @@ -38,26 +39,26 @@ struct Term { Term(const std::string& expr, const std::string& state); Term(std::string expr, std::string deriv, std::string a, std::string b) - : expr(expr) - , deriv(deriv) - , a(a) - , b(b) {} + : expr(std::move(expr)) + , deriv(std::move(deriv)) + , a(std::move(a)) + , b(std::move(b)) {} /// helper routines used in parser - bool deriv_nonzero() { + bool deriv_nonzero() const { return deriv != "0.0"; } - bool a_nonzero() { + bool a_nonzero() const { return a != "0.0"; } - bool b_nonzero() { + bool b_nonzero() const { return b != "0.0"; } - void print(); + void print() const; }; @@ -80,25 +81,22 @@ class DiffEqContext { /// order of the diff equation int order = 0; - /// if equation is non-linear then expression to use during code generation - std::string expr_for_nonlinear; - /// return solution for cnexp method - std::string get_cnexp_solution(); + std::string get_cnexp_solution() const; /// return solution for euler method - std::string get_euler_solution(); + std::string get_euler_solution() const; /// return solution for non-cnexp method - std::string get_non_cnexp_solution(); + std::string get_non_cnexp_solution() const; /// for non-cnexp methods : return the solution based on if equation is linear or not - std::string get_cvode_linear_diffeq(); - std::string get_cvode_nonlinear_diffeq(); + std::string get_cvode_linear_diffeq() const; + std::string get_cvode_nonlinear_diffeq() const; /// \todo Methods inherited neuron implementation - std::string cvode_deriv(); - std::string cvode_eqnrhs(); + std::string cvode_deriv() const; + std::string cvode_eqnrhs() const; public: /// "final" solution of the equation @@ -113,13 +111,13 @@ class DiffEqContext { DiffEqContext() = default; DiffEqContext(std::string state, int order, std::string rhs, std::string method) - : state(state) + : state(std::move(state)) , order(order) - , rhs(rhs) - , method(method) {} + , rhs(std::move(rhs)) + , method(std::move(method)) {} /// return the state variable - std::string state_variable() const { + const std::string& state_variable() const { return state; } @@ -127,10 +125,10 @@ class DiffEqContext { std::string get_solution(bool& cnexp_possible); /// return expression with Dstate added - std::string get_expr_for_nonlinear(); + std::string get_expr_for_nonlinear() const; /// print the context (for debugging) - void print(); + void print() const; }; } // namespace diffeq diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index a2a6fabf52..feffb904b2 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -29,13 +29,16 @@ namespace diffeq { enum class MathOp { add = 1, sub, mul, div }; template -inline Term eval_derivative(Term& first, Term& second, bool& deriv_invalid, bool& eqn_invalid); +inline Term eval_derivative(const Term& first, + const Term& second, + bool& deriv_invalid, + bool& eqn_invalid); /// implement (f(x) + g(x))' = f'(x) + g'(x) template <> -inline Term eval_derivative(Term& first, - Term& second, +inline Term eval_derivative(const Term& first, + const Term& second, bool& deriv_invalid, bool& eqn_invalid) { Term solution; @@ -71,8 +74,8 @@ inline Term eval_derivative(Term& first, /// implement (f(x) - g(x))' = f'(x) - g'(x) template <> -inline Term eval_derivative(Term& first, - Term& second, +inline Term eval_derivative(const Term& first, + const Term& second, bool& deriv_invalid, bool& eqn_invalid) { Term solution; @@ -108,8 +111,8 @@ inline Term eval_derivative(Term& first, /// implement (f(x) * g(x))' = f'(x)g(x) + f(x)g'(x) template <> -inline Term eval_derivative(Term& first, - Term& second, +inline Term eval_derivative(const Term& first, + const Term& second, bool& deriv_invalid, bool& eqn_invalid) { Term solution; @@ -146,8 +149,8 @@ inline Term eval_derivative(Term& first, * and this needs to be discussed with Michael. */ template <> -inline Term eval_derivative(Term& first, - Term& second, +inline Term eval_derivative(const Term& first, + const Term& second, bool& deriv_invalid, bool& eqn_invalid) { Term solution; diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index b07835f9f1..b8c33ba751 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -5,9 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - -#include "CLI/CLI.hpp" +#include #include "config/config.h" #include "parser/c11_driver.hpp" diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 38128c7586..4a485ce9fa 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -6,7 +6,7 @@ *************************************************************************/ -#include "CLI/CLI.hpp" +#include #include "ast/program.hpp" #include "config/config.h" diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index cf8ddb7d49..39bf319463 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -7,7 +7,7 @@ #include -#include "CLI/CLI.hpp" +#include #include "config/config.h" #include "parser/unit_driver.hpp" diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 5086850281..93db989792 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -1,5 +1,3 @@ -#include - /************************************************************************* * Copyright (C) 2018-2019 Blue Brain Project * @@ -152,7 +150,7 @@ class Unit { double parse_double(std::string double_string); /// Getter for the vector of nominators of the Unit - std::vector get_nominator_unit() const { + const std::vector& get_nominator_unit() const noexcept { return nominator; } diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp index a6e38cfc5d..4eee6692dd 100644 --- a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp @@ -12,7 +12,6 @@ #include "ast/string.hpp" #include "codegen/codegen_naming.hpp" #include "utils/logger.hpp" -#include "visitors/visitor_utils.hpp" namespace nmodl { namespace visitor { diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 3a66c731fc..5823f93ac6 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -159,21 +159,21 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression& nod return; } - std::string nmodl_before = to_nmodl(binary_expr); + const std::string nmodl_before = to_nmodl(binary_expr); /// compute the value of expression auto value = compute(get_value(lhs), op, get_value(rhs)); /// if both operands are not integers or floats, result is double if (lhs->is_integer() && rhs->is_integer()) { - node.set_expression(std::make_shared(int(value), nullptr)); + node.set_expression(std::make_shared(static_cast(value), nullptr)); } else if (lhs->is_double() || rhs->is_double()) { node.set_expression(std::make_shared(stringutils::to_string(value))); } else { node.set_expression(std::make_shared(stringutils::to_string(value))); } - std::string nmodl_after = to_nmodl(node.get_expression()); + const std::string nmodl_after = to_nmodl(node.get_expression()); logger->debug("ConstantFolderVisitor : expression {} folded to {}", nmodl_before, nmodl_after); } diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index a6c989f5de..3a3d8daf9a 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -5,9 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include - -#include "CLI/CLI.hpp" +#include #include "ast/program.hpp" #include "config/config.h" From d5128d6cea14c869fde458a5683310026974b686 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 10 Dec 2020 15:24:37 +0100 Subject: [PATCH 321/871] Fix range/state/ion variables order in codegen backend (BlueBrain/nmodl#443) - range variabes needs to be printed in the order as it's done in neuron/mod2c implementation - existing implementation had bugs when ion variables are state variables. (CaDynamics_E2.mod cal_mig.mod) - this PR reviews implementation in mod2c and documents are variable order is calculated in original implementation and fixes variable ordering issue. - add missing ion read/write variables missing with assigned variable list - Add unit tests for code helper visitor, specifically for range variables order fixes BlueBrain/nmodl#436 NMODL Repo SHA: BlueBrain/nmodl@23e5249780607789eccf79460a3f27497be35878 --- src/nmodl/codegen/codegen_c_visitor.cpp | 18 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 175 +++++++++++------- src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.cpp | 22 ++- src/nmodl/codegen/codegen_info.hpp | 14 +- test/nmodl/transpiler/unit/CMakeLists.txt | 12 ++ .../unit/codegen/codegen_helper.cpp | 172 +++++++++++++++++ 7 files changed, 333 insertions(+), 81 deletions(-) create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_helper.cpp diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 914199545e..5712b233f0 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -540,12 +540,11 @@ int CodegenCVisitor::float_variables_size() const { int float_size = count_length(info.range_parameter_vars); float_size += count_length(info.range_assigned_vars); - float_size += count_length(info.state_vars); + float_size += count_length(info.range_state_vars); float_size += count_length(info.assigned_vars); - /// for state variables we add Dstate variables + /// all state variables for which we add Dstate variables float_size += info.state_vars.size(); - float_size += info.ion_state_vars.size(); /// for v_unused variable if (info.vectorize) { @@ -821,7 +820,6 @@ std::vector CodegenCVisitor::get_float_variables() { auto assigned = info.assigned_vars; auto states = info.state_vars; - states.insert(states.end(), info.ion_state_vars.begin(), info.ion_state_vars.end()); // each state variable has corresponding Dstate variable for (auto& variable: states) { @@ -836,7 +834,7 @@ std::vector CodegenCVisitor::get_float_variables() { variables.insert(variables.end(), info.range_assigned_vars.begin(), info.range_assigned_vars.end()); - variables.insert(variables.end(), info.state_vars.begin(), info.state_vars.end()); + variables.insert(variables.end(), info.range_state_vars.begin(), info.range_state_vars.end()); variables.insert(variables.end(), assigned.begin(), assigned.end()); if (info.vectorize) { @@ -2535,7 +2533,7 @@ void CodegenCVisitor::print_mechanism_info() { printer->add_line("0,"); variable_printer(info.range_assigned_vars); printer->add_line("0,"); - variable_printer(info.state_vars); + variable_printer(info.range_state_vars); printer->add_line("0,"); variable_printer(info.pointer_variables); printer->add_line("0"); @@ -3208,9 +3206,11 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { // initialize state variables (excluding ion state) for (auto& var: info.state_vars) { auto name = var->get_name(); - auto lhs = get_variable_name(name); - auto rhs = get_variable_name(name + "0"); - printer->add_line("{} = {};"_format(lhs, rhs)); + if (!info.is_ionic_conc(name)) { + auto lhs = get_variable_name(name); + auto rhs = get_variable_name(name + "0"); + printer->add_line("{} = {};"_format(lhs, rhs)); + } } // initial block diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index b4ffe91a59..ec44afb06a 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -276,12 +276,54 @@ void CodegenHelperVisitor::find_non_range_variables() { /** * Find range variables i.e. ones that are belong to per instance allocation * - * In order to be compatible with NEURON, we need to print range variables in - * certain order. For example, range variables which are parameters comes first. - * Also, there is difference between declaration order vs. definition order. For - * example, POINTER variable in NEURON block is just declaration and doesn't - * determine the order in which they will get printed. Below we query symbol table - * and order all instance variables into certain order. + * In order to be compatible with NEURON, we need to print all variables in + * exact order as NEURON/MOD2C implementation. This is important because memory + * for all variables is allocated in single 1-D array with certain offset + * for each variable. The order of variables determine the offset and hence + * they must be in same order as NEURON. + * + * Here is how order is determined into NEURON/MOD2C implementation: + * + * First, following three lists are created + * - variables with parameter and range property (List 1) + * - variables with state and range property (List 2) + * - variables with assigned and range property (List 3) + * + * Once created, we remove some variables due to the following criteria: + * - In NEURON/MOD2C implementation, we remove variables with NRNPRANGEIN + * or NRNPRANGEOUT type + * - So who has NRNPRANGEIN and NRNPRANGEOUT type? these are USEION read + * or write variables that are not ionic currents. + * - This is the reason for mod files CaDynamics_E2.mod or cal_mig.mod, ica variable + * is printed earlier in the list but other variables like cai, cao don't appear + * in same order. + * + * Finally we create 4th list: + * - variables with assigned property and not in the previous 3 lists + * + * We now print the variables in following order: + * + * - List 1 i.e. range + parameter variables are printed first + * - List 3 i.e. range + assigned variables are printed next + * - List 2 i.e. range + state variables are printed next + * - List 4 i.e. assigned and ion variables not present in the previous 3 lists + * + * NOTE: + * - State variables also have the property `assigned_definition` but these variables + * are not from ASSIGNED block. + * - Variable can not be range as well as state, it's redeclaration error + * - Variable can be parameter as well as range. Without range, parameter + * is considered as global variable i.e. one value for all instances. + * - If a variable is only defined as RANGE and not in assigned or parameter + * or state block then it's not printed. + * - Note that a variable property is different than the variable type. For example, + * if variable has range property, it doesn't mean the variable is declared as RANGE. + * Other variables like STATE and ASSIGNED block variables also get range property + * without being explicitly declared as RANGE in the mod file. + * - Also, there is difference between declaration order vs. definition order. For + * example, POINTER variable in NEURON block is just declaration and doesn't + * determine the order in which they will get printed. Below we query symbol table + * and order all instance variables into certain order. */ void CodegenHelperVisitor::find_range_variables() { /// comparator to decide the order based on definition @@ -289,6 +331,34 @@ void CodegenHelperVisitor::find_range_variables() { return first->get_definition_order() < second->get_definition_order(); }; + /// from symbols vector `vars`, remove all ion variables which are not ionic currents + auto remove_non_ioncur_vars = [](SymbolVectorType& vars, const CodegenInfo& info) -> void { + vars.erase(std::remove_if(vars.begin(), + vars.end(), + [&](SymbolType& s) { + return info.is_ion_variable(s->get_name()) && + !info.is_ionic_current(s->get_name()); + }), + vars.end()); + }; + + /// if `secondary` vector contains any symbol that exist in the `primary` then remove it + auto remove_var_exist = [](SymbolVectorType& primary, SymbolVectorType& secondary) -> void { + secondary.erase(std::remove_if(secondary.begin(), + secondary.end(), + [&primary](const SymbolType& tosearch) { + return std::find_if(primary.begin(), + primary.end(), + // compare by symbol name + [&tosearch]( + const SymbolType& symbol) { + return tosearch->get_name() == + symbol->get_name(); + }) != primary.end(); + }), + secondary.end()); + }; + /** * First come parameters which are range variables. */ @@ -299,8 +369,10 @@ void CodegenHelperVisitor::find_range_variables() { | NmodlType::pointer_var | NmodlType::bbcore_pointer_var | NmodlType::state_var; + // clang-format on info.range_parameter_vars = psymtab->get_variables(with, without); + remove_non_ioncur_vars(info.range_parameter_vars, info); std::sort(info.range_parameter_vars.begin(), info.range_parameter_vars.end(), comparator); /** @@ -314,92 +386,59 @@ void CodegenHelperVisitor::find_range_variables() { | NmodlType::bbcore_pointer_var | NmodlType::state_var | NmodlType::param_assign; + // clang-format on info.range_assigned_vars = psymtab->get_variables(with, without); + remove_non_ioncur_vars(info.range_assigned_vars, info); std::sort(info.range_assigned_vars.begin(), info.range_assigned_vars.end(), comparator); + /** * Third come state variables. All state variables are kind of range by default. - * Note that some mod files like CaDynamics_E2.mod use cai as state variable - * and those are not considered as range+state variables while printing instance - * variables. Such read/write ion variables are assigned variables and hence they - * will be printed at laster stage. - * \todo Need to validate with more models and mod2c details. + * Note that some mod files like CaDynamics_E2.mod use cai as state variable which + * appear in USEION read/write list. These variables are not considered in this + * variables because non ionic-current variables are removed and printed later. */ // clang-format off with = NmodlType::state_var; without = NmodlType::global_var | NmodlType::pointer_var - | NmodlType::bbcore_pointer_var - | NmodlType::read_ion_var - | NmodlType::write_ion_var; + | NmodlType::bbcore_pointer_var; + // clang-format on info.state_vars = psymtab->get_variables(with, without); std::sort(info.state_vars.begin(), info.state_vars.end(), comparator); - /** - * Remaining variables are: - * - all assigned variables without range - * - read ion variables which appear in parameter or assigned block - * - state variables which are not range but with ion variable of read/write type - */ - - /** - * first get assigned definition without read ion variables - */ - // clang-format off - with = NmodlType::assigned_definition; - without = NmodlType::global_var - | NmodlType::pointer_var - | NmodlType::bbcore_pointer_var - | NmodlType::state_var - | NmodlType::range_var - | NmodlType::extern_neuron_variable - | NmodlType::read_ion_var; - // clang-format on - info.assigned_vars = psymtab->get_variables(with, without); + /// range_state_vars is copy of state variables but without non ionic current variables + info.range_state_vars = info.state_vars; + remove_non_ioncur_vars(info.range_state_vars, info); - /** - * Now just use read-ion variables because every read-ion variable - * must be part of either assigned or parameter block. Otherwise code is not - * compiled anyway. - */ - // clang-format off - with = NmodlType::read_ion_var; - without = NmodlType::global_var - | NmodlType::pointer_var - | NmodlType::bbcore_pointer_var - | NmodlType::state_var - | NmodlType::range_var - | NmodlType::extern_neuron_variable; - // clang-format on - auto variables = psymtab->get_variables(with, without); - info.assigned_vars.insert(info.assigned_vars.end(), variables.begin(), variables.end()); + /// Remaining variables are assigned and ion variables which are not in the previous 3 lists - /* - * We want to have state variables which are read or write ion variables. - * This needs to be separated from other state variables because mod2c - * treat them separately for ordering. - */ // clang-format off - with = NmodlType::state_var; + with = NmodlType::assigned_definition + | NmodlType::read_ion_var + | NmodlType::write_ion_var; without = NmodlType::global_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var - | NmodlType::range_var | NmodlType::extern_neuron_variable; // clang-format on - variables = psymtab->get_variables(with, without); - for (auto& variable: variables) { - // clang-format off - auto properties = NmodlType::read_ion_var - | NmodlType::write_ion_var; - // clang-format on - if (variable->has_any_property(properties)) { - info.ion_state_vars.push_back(variable); + const auto& variables = psymtab->get_variables_with_properties(with, false); + for (const auto& variable: variables) { + if (!variable->has_any_property(without)) { info.assigned_vars.push_back(variable); } } + + /// make sure that variables already present in previous lists + /// are removed to avoid any duplication + remove_var_exist(info.range_parameter_vars, info.assigned_vars); + remove_var_exist(info.range_assigned_vars, info.assigned_vars); + remove_var_exist(info.range_state_vars, info.assigned_vars); + + /// sort variables with their definition order + std::sort(info.assigned_vars.begin(), info.assigned_vars.end(), comparator); } @@ -615,14 +654,14 @@ void CodegenHelperVisitor::visit_program(const ast::Program& node) { } } node.visit_children(*this); + find_ion_variables(); // Keep this before find_*_range_variables() find_range_variables(); find_non_range_variables(); - find_ion_variables(); find_table_variables(); } -codegen::CodegenInfo CodegenHelperVisitor::analyze(const ast::Program& node) { +CodegenInfo CodegenHelperVisitor::analyze(const ast::Program& node) { node.accept(*this); return info; } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 964de79cd9..c37c9745e0 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -46,6 +46,7 @@ namespace codegen { */ class CodegenHelperVisitor: public visitor::ConstAstVisitor { using SymbolType = std::shared_ptr; + using SymbolVectorType = std::vector>; /// holds all codegen related information codegen::CodegenInfo info; diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 6e24b6da22..8f6bd448f8 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -60,7 +60,7 @@ bool CodegenInfo::is_ion_variable(const std::string& name) const { } -/// if a current +/// if a current (ionic or non-specific) bool CodegenInfo::is_current(const std::string& name) const { for (auto& var: currents) { if (var == name) { @@ -70,6 +70,26 @@ bool CodegenInfo::is_current(const std::string& name) const { return false; } +/// true is a given variable name if a ionic current +/// (i.e. currents excluding non-specific current) +bool CodegenInfo::is_ionic_current(const std::string& name) const { + for (const auto& ion: ions) { + if (ion.is_ionic_current(name) == true) { + return true; + } + } + return false; +} + +/// true if given variable name is a ionic concentration +bool CodegenInfo::is_ionic_conc(const std::string& name) const { + for (const auto& ion: ions) { + if (ion.is_ionic_conc(name) == true) { + return true; + } + } + return false; +} bool CodegenInfo::function_uses_table(std::string& name) const { for (auto& function: functions_with_table) { diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 9ca9d96dc1..176c3a2968 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -284,11 +284,13 @@ struct CodegenInfo { /// reamining assigned variables std::vector assigned_vars; - /// state variables + /// all state variables std::vector state_vars; - /// ion variables which are also state variables - std::vector ion_state_vars; + /// state variables excluding such useion read/write variables + /// that are not ionic currents. In neuron/mod2c these are stored + /// in the list "rangestate". + std::vector range_state_vars; /// local variables in the global scope std::vector top_local_variables; @@ -365,6 +367,12 @@ struct CodegenInfo { /// if given variable is a current bool is_current(const std::string& name) const; + /// if given variable is a ionic current + bool is_ionic_current(const std::string& name) const; + + /// if given variable is a ionic concentration + bool is_ionic_conc(const std::string& name) const; + /// if watch statements are used bool is_watch_used() const noexcept { return watch_count > 0; diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 4973ef2c38..a206a1e6aa 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -53,6 +53,7 @@ add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) +add_executable(testcodegen codegen/codegen_helper.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) @@ -74,6 +75,16 @@ target_link_libraries( test_util printer ${NMODL_WRAPPER_LIBS}) +target_link_libraries( + testcodegen + codegen + visitor + symtab + lexer + util + test_util + printer + ${NMODL_WRAPPER_LIBS}) target_link_libraries(testprinter printer util) target_link_libraries(testsymtab symtab lexer util) target_link_libraries(testunitlexer lexer util) @@ -99,6 +110,7 @@ foreach( testlexer testparser testvisitor + testcodegen testprinter testsymtab testnewton diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp new file mode 100644 index 0000000000..1265df8e6b --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -0,0 +1,172 @@ +/************************************************************************* + * Copyright (C) 2019-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_MAIN + +#include + +#include "ast/program.hpp" +#include "codegen/codegen_helper_visitor.hpp" +#include "parser/nmodl_driver.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace codegen; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Helper for codege related visitor +//============================================================================= +std::string run_inline_visitor(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + + /// construct symbol table and run codegen helper visitor + SymtabVisitor().visit_program(*ast); + CodegenHelperVisitor v; + + /// symbols/variables are collected in info object + const auto& info = v.analyze(*ast); + + /// semicolon separated list of variables + std::string variables; + + /// range variables in order of code generation + for (const auto& var: info.range_parameter_vars) { + variables += var->get_name() + ";"; + } + for (const auto& var: info.range_assigned_vars) { + variables += var->get_name() + ";"; + } + for (const auto& var: info.range_state_vars) { + variables += var->get_name() + ";"; + } + for (const auto& var: info.assigned_vars) { + variables += var->get_name() + ";"; + } + + return variables; +} + +SCENARIO("unusual / failing mod files", "[codegen][var_order]") { + GIVEN("cal_mig.mod : USEION variables declared as RANGE") { + std::string nmodl_text = R"( + PARAMETER { + gcalbar=.003 (mho/cm2) + ki=.001 (mM) + cai = 50.e-6 (mM) + cao = 2 (mM) + q10 = 5 + USEGHK=1 + } + NEURON { + SUFFIX cal + USEION ca READ cai,cao WRITE ica + RANGE gcalbar, cai, ica, gcal, ggk + RANGE minf, tau + GLOBAL USEGHK + } + STATE { + m + } + ASSIGNED { + ica (mA/cm2) + gcal (mho/cm2) + minf + tau (ms) + ggk + } + DERIVATIVE state { + rate(v) + m' = (minf - m)/tau + } + )"; + + THEN("ionic current variable declared as RANGE appears first") { + std::string expected = "gcalbar;ica;gcal;minf;tau;ggk;m;cai;cao;"; + auto result = run_inline_visitor(nmodl_text); + REQUIRE(result == expected); + } + } + + GIVEN("CaDynamics_E2.mod : USEION variables declared as STATE variable") { + std::string nmodl_text = R"( + NEURON { + SUFFIX CaDynamics_E2 + USEION ca READ ica WRITE cai + RANGE decay, gamma, minCai, depth + } + + PARAMETER { + gamma = 0.05 : percent of free calcium (not buffered) + decay = 80 (ms) : rate of removal of calcium + depth = 0.1 (um) : depth of shell + minCai = 1e-4 (mM) + } + + ASSIGNED {ica (mA/cm2)} + + STATE { + cai (mM) + } + + DERIVATIVE states { + cai' = -(10000)*(ica*gamma/(2*FARADAY*depth)) - (cai - minCai)/decay + } + )"; + + THEN("ion state variable is ordered after parameter and assigned ionic current") { + std::string expected = "gamma;decay;depth;minCai;ica;cai;"; + auto result = run_inline_visitor(nmodl_text); + REQUIRE(result == expected); + } + } + + GIVEN("cadyn.mod : same USEION variables used for read as well as write") { + std::string nmodl_text = R"( + NEURON { + SUFFIX cadyn + USEION ca READ cai,ica WRITE cai + RANGE ca + GLOBAL depth,cainf,taur + } + + PARAMETER { + depth = .1 (um) + taur = 200 (ms) : rate of calcium removal + cainf = 50e-6(mM) :changed oct2 + cai (mM) + } + + ASSIGNED { + ica (mA/cm2) + drive_channel (mM/ms) + } + + STATE { + ca (mM) + } + + BREAKPOINT { + SOLVE state METHOD euler + } + + DERIVATIVE state { + ca' = drive_channel/18 + (cainf -ca)/taur*11 + cai = ca + } + )"; + + THEN("ion variables are ordered correctly") { + std::string expected = "ca;cai;ica;drive_channel;"; + auto result = run_inline_visitor(nmodl_text); + REQUIRE(result == expected); + } + } +} From 8331e2d79b5cc9bbfe968f38ab207143c390197e Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 10 Dec 2020 23:44:30 +0100 Subject: [PATCH 322/871] Bug fix when state variable is an array (BlueBrain/nmodl#447) - for each state variable, Dstate variable is allocated in the array - when calculating vector size, we have to allocate Dstate variable as vector and not scalar variable - bug fix when variable is present in ion read as well as write list - bug fix for di_ion_dv variable - it should come after read/write but before style ion variable fixes BlueBrain/nmodl#446 NMODL Repo SHA: BlueBrain/nmodl@85aeac62ef7a1e748f0d3e60510ded545b2b1cec --- src/nmodl/codegen/codegen_c_visitor.cpp | 32 ++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 5712b233f0..8f3b4cdc18 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -544,7 +544,7 @@ int CodegenCVisitor::float_variables_size() const { float_size += count_length(info.assigned_vars); /// all state variables for which we add Dstate variables - float_size += info.state_vars.size(); + float_size += count_length(info.state_vars); /// for v_unused variable if (info.vectorize) { @@ -758,11 +758,14 @@ void CodegenCVisitor::update_index_semantics() { info.semantics.emplace_back(index++, naming::POINT_PROCESS_SEMANTIC, 1); } for (const auto& ion: info.ions) { - for (auto& var: ion.reads) { + for (const auto& var: ion.reads) { info.semantics.emplace_back(index++, ion.name + "_ion", 1); } - for (auto& var: ion.writes) { - info.semantics.emplace_back(index++, ion.name + "_ion", 1); + for (const auto& var: ion.writes) { + /// add if variable is not present in the read list + if (std::find(ion.reads.begin(), ion.reads.end(), var) == ion.reads.end()) { + info.semantics.emplace_back(index++, ion.name + "_ion", 1); + } if (ion.is_ionic_current(var)) { info.semantics.emplace_back(index++, ion.name + "_ion", 1); } @@ -822,10 +825,13 @@ std::vector CodegenCVisitor::get_float_variables() { auto states = info.state_vars; // each state variable has corresponding Dstate variable - for (auto& variable: states) { - auto name = "D" + variable->get_name(); + for (auto& state: states) { + auto name = "D" + state->get_name(); auto symbol = make_symbol(name); - symbol->set_definition_order(variable->get_definition_order()); + if (state->is_array()) { + symbol->set_as_array(state->get_length()); + } + symbol->set_definition_order(state->get_definition_order()); assigned.push_back(symbol); } std::sort(assigned.begin(), assigned.end(), comparator); @@ -890,6 +896,10 @@ std::vector CodegenCVisitor::get_int_variables() { variables.back().is_constant = true; ion_vars[name] = variables.size() - 1; } + + /// symbol for di_ion_dv var + std::shared_ptr ion_di_dv_var = nullptr; + for (const auto& var: ion.writes) { const std::string name = "ion_" + var; @@ -900,12 +910,18 @@ std::vector CodegenCVisitor::get_int_variables() { variables.emplace_back(make_symbol("ion_" + var)); } if (ion.is_ionic_current(var)) { - variables.emplace_back(make_symbol("ion_di" + ion.name + "dv")); + ion_di_dv_var = make_symbol("ion_di" + ion.name + "dv"); } if (ion.is_intra_cell_conc(var) || ion.is_extra_cell_conc(var)) { need_style = true; } } + + /// insert after read/write variables but before style ion variable + if (ion_di_dv_var != nullptr) { + variables.emplace_back(ion_di_dv_var); + } + if (need_style) { variables.emplace_back(make_symbol("style_" + ion.name), false, true); variables.back().is_constant = true; From fd17cb956440e02440f43be57bb541b73735f962 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Tue, 15 Dec 2020 10:24:58 +0100 Subject: [PATCH 323/871] Bump pybind11 -> 2.6.1 and cmake -> 3.8 (BlueBrain/nmodl#455) NMODL Repo SHA: BlueBrain/nmodl@1089bc1509e7ae8cc9a76e0d527c3f738014c47b --- .travis.yml | 8 ++++---- cmake/nmodl/CMakeLists.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index eeaeacf79f..03a0186c2d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -78,12 +78,12 @@ before_install: brew unlink python@2; brew link --overwrite python@3.8; export SDKROOT="$(xcrun --show-sdk-path)"; - curl -L -o cmake-3.3.2.tar.gz https://github.com/Kitware/CMake/releases/download/v3.3.2/cmake-3.3.2-Darwin-x86_64.tar.gz; - mkdir -p $HOME/cmake && tar -xzf cmake-3.3.2.tar.gz -C $HOME/cmake --strip-components=3; + curl -L -o cmake-3.8.0.tar.gz https://github.com/Kitware/CMake/releases/download/v3.8.0/cmake-3.8.0-Darwin-x86_64.tar.gz; + mkdir -p $HOME/cmake && tar -xzf cmake-3.8.0.tar.gz -C $HOME/cmake --strip-components=3; else pyenv global $PYTHON_VERSION; - curl -L -o cmake-3.3.2.tar.gz https://github.com/Kitware/CMake/releases/download/v3.3.2/cmake-3.3.2-Linux-x86_64.tar.gz; - mkdir -p $HOME/cmake && tar -xzf cmake-3.3.2.tar.gz -C $HOME/cmake --strip-components=1; + curl -L -o cmake-3.8.0.tar.gz https://github.com/Kitware/CMake/releases/download/v3.8.0/cmake-3.8.0-Linux-x86_64.tar.gz; + mkdir -p $HOME/cmake && tar -xzf cmake-3.8.0.tar.gz -C $HOME/cmake --strip-components=1; fi - export PATH=$HOME/cmake/bin:$PATH - eval "${MATRIX_EVAL}" diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e8a8e7b617..ca6a73c060 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -5,7 +5,7 @@ # See top-level LICENSE file for details. # ============================================================================= -cmake_minimum_required(VERSION 3.3.0 FATAL_ERROR) +cmake_minimum_required(VERSION 3.8 FATAL_ERROR) project( NMODL VERSION 0.2 From 9873ac9e06111e21ac96f709196d6a09e885182d Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 16 Dec 2020 13:36:14 +0100 Subject: [PATCH 324/871] Add CLI flag only-check-compatibility (BlueBrain/nmodl#459) * Add CLI flag only-check-compatibility We return right after checking compatibility without generating code Useful for having coreneuron switch to mod2c in case code is not compatible - Update readme NMODL Repo SHA: BlueBrain/nmodl@372c2f978b4ef94a65dd4e29aa75844e4d38f80e --- README.md | 1 + src/nmodl/nmodl/main.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/README.md b/README.md index 7cefd643a1..9da8003ae1 100644 --- a/README.md +++ b/README.md @@ -266,6 +266,7 @@ codegen --layout TEXT:{aos,soa}=soa Memory layout for code generation --datatype TEXT:{float,double}=soa Data type for floating point variables --force Force code generation even if there is any code incompatibility + --only-check-compatibility Check compatibility and return without generating code ``` ### Documentation diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 0aca55e7de..13ec7a8af4 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -125,6 +125,9 @@ int main(int argc, const char* argv[]) { /// is any incompatibility bool force_codegen(false); + /// true if we want to only check compatibility without generating code + bool only_check_compatibility(false); + /// true if ion variable copies should be avoided bool optimize_ionvar_copies_codegen(false); @@ -253,6 +256,9 @@ int main(int argc, const char* argv[]) { codegen_opt->add_flag("--force", force_codegen, "Force code generation even if there is any incompatibility"); + codegen_opt->add_flag("--only-check-compatibility", + only_check_compatibility, + "Check compatibility and return without generating code"); codegen_opt->add_flag("--opt-ionvar-copy", optimize_ionvar_copies_codegen, "Optimize copies of ion variables ({})"_format(optimize_ionvar_copies_codegen))->ignore_case(); @@ -359,6 +365,12 @@ int main(int argc, const char* argv[]) { logger->info("Running code compatibility checker"); // run perfvisitor to update read/write counts PerfVisitor().visit_program(*ast); + + // If we want to just check compatibility we return the result + if (only_check_compatibility) { + return CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast); + } + // If there is an incompatible construct and code generation is not forced exit NMODL if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast) && !force_codegen) { return 1; From 73e4d8c429afb254fe4b895b8a7eb7defa87e855 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 16 Dec 2020 15:53:40 +0200 Subject: [PATCH 325/871] Fix issue with double constants in ISPC generated files (BlueBrain/nmodl#450) * Print double constants to ISPC file with similar representation as C codegen * Added unit tests for ISPC codegen * Small refactoring for double_to_string and float_to_string * Updated README.md nmodl help output Co-authored-by: Alessandro Cattabiani NMODL Repo SHA: BlueBrain/nmodl@f71a2f704a267a025f56b61f21575ff2fe722331 --- README.md | 53 ++--- src/nmodl/codegen/CMakeLists.txt | 4 +- src/nmodl/codegen/codegen_c_visitor.cpp | 29 ++- src/nmodl/codegen/codegen_c_visitor.hpp | 25 ++- src/nmodl/codegen/codegen_ispc_visitor.cpp | 63 ++---- src/nmodl/codegen/codegen_ispc_visitor.hpp | 35 +++- src/nmodl/codegen/codegen_utils.cpp | 91 +++++++++ src/nmodl/codegen/codegen_utils.hpp | 53 +++++ src/nmodl/printer/code_printer.hpp | 4 +- test/nmodl/transpiler/unit/CMakeLists.txt | 5 +- .../unit/codegen/codegen_helper.cpp | 2 - .../transpiler/unit/codegen/codegen_ispc.cpp | 190 ++++++++++++++++++ .../transpiler/unit/codegen/codegen_utils.cpp | 161 +++++++++++++++ test/nmodl/transpiler/unit/codegen/main.cpp | 26 +++ 14 files changed, 633 insertions(+), 108 deletions(-) create mode 100644 src/nmodl/codegen/codegen_utils.cpp create mode 100644 src/nmodl/codegen/codegen_utils.hpp create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_utils.cpp create mode 100644 test/nmodl/transpiler/unit/codegen/main.cpp diff --git a/README.md b/README.md index 9da8003ae1..92a7eff387 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ To know more about code generation backends, [see here](https://bluebrain.github ``` $ nmodl -H -NMODL : Source-to-Source Code Generation Framework +NMODL : Source-to-Source Code Generation Framework [version] Usage: /path/<>/nmodl [OPTIONS] file... [SUBCOMMAND] Positionals: @@ -226,47 +226,54 @@ Options: --scratch TEXT=tmp Directory for intermediate code output --units TEXT=/path/<>/nrnunits.lib Directory of units lib file + Subcommands: host HOST/CPU code backends Options: - --c C/C++ backend - --omp C/C++ backend with OpenMP - --ispc C/C++ backend with ISPC + --c C/C++ backend (true) + --omp C/C++ backend with OpenMP (false) + --ispc C/C++ backend with ISPC (false) + acc Accelerator code backends Options: - --oacc C/C++ backend with OpenACC - --cuda C/C++ backend with CUDA + --oacc C/C++ backend with OpenACC (false) + --cuda C/C++ backend with CUDA (false) + sympy SymPy based analysis and optimizations Options: - --analytic Solve ODEs using SymPy analytic integration - --pade Pade approximation in SymPy analytic integration - --cse CSE (Common Subexpression Elimination) in SymPy analytic integration - --conductance Add CONDUCTANCE keyword in BREAKPOINT + --analytic Solve ODEs using SymPy analytic integration (false) + --pade Pade approximation in SymPy analytic integration (false) + --cse CSE (Common Subexpression Elimination) in SymPy analytic integration (false) + --conductance Add CONDUCTANCE keyword in BREAKPOINT (false) + passes Analyse/Optimization passes Options: - --inline Perform inlining at NMODL level - --unroll Perform loop unroll at NMODL level - --const-folding Perform constant folding at NMODL level - --localize Convert RANGE variables to LOCAL - --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist - --local-rename Rename LOCAL variable if variable of same name exist in global scope - --verbatim-inline Inline even if verbatim block exist - --verbatim-rename Rename variables in verbatim block - --json-ast Write AST to JSON file - --nmodl-ast Write AST to NMODL file - --json-perf Write performance statistics to JSON file - --show-symtab Write symbol table to stdout + --inline Perform inlining at NMODL level (false) + --unroll Perform loop unroll at NMODL level (false) + --const-folding Perform constant folding at NMODL level (false) + --localize Convert RANGE variables to LOCAL (false) + --global-to-range Convert GLOBAL variables to RANGE (false) + --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist (false) + --local-rename Rename LOCAL variable if variable of same name exist in global scope (false) + --verbatim-inline Inline even if verbatim block exist (false) + --verbatim-rename Rename variables in verbatim block (true) + --json-ast Write AST to JSON file (false) + --nmodl-ast Write AST to NMODL file (false) + --json-perf Write performance statistics to JSON file (false) + --show-symtab Write symbol table to stdout (false) + codegen Code generation options Options: --layout TEXT:{aos,soa}=soa Memory layout for code generation --datatype TEXT:{float,double}=soa Data type for floating point variables - --force Force code generation even if there is any code incompatibility + --force Force code generation even if there is any incompatibility --only-check-compatibility Check compatibility and return without generating code + --opt-ionvar-copy Optimize copies of ion variables (false) ``` ### Documentation diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index c7124a75d3..046d3fa214 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -18,7 +18,9 @@ set(CODEGEN_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_ispc_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/codegen_ispc_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp) + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_utils.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/codegen_utils.hpp) # ============================================================================= # Codegen library and executable diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 8f3b4cdc18..76975e9195 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -15,6 +15,7 @@ #include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" +#include "codegen/codegen_utils.hpp" #include "config/config.h" #include "lexer/token_mapping.hpp" #include "parser/c11_driver.hpp" @@ -38,6 +39,7 @@ using symtab::syminfo::NmodlType; using SymbolType = std::shared_ptr; using nmodl::utils::UseNumbersInString; +namespace codegen_utils = nmodl::codegen::utils; /****************************************************************************************/ /* Overloaded visitor routines */ @@ -75,7 +77,7 @@ void CodegenCVisitor::visit_float(const Float& node) { if (!codegen) { return; } - printer->add_text(float_to_string(node.get_value())); + printer->add_text(format_float_string(node.get_value())); } @@ -83,7 +85,7 @@ void CodegenCVisitor::visit_double(const Double& node) { if (!codegen) { return; } - printer->add_text(double_to_string(node.get_value())); + printer->add_text(format_double_string(node.get_value())); } @@ -440,23 +442,16 @@ int CodegenCVisitor::position_of_int_var(const std::string& name) const { * then it gets printed as an integer. To avoid this, we use below wrapper. * If user has provided integer then it gets printed as 1.0 (similar to mod2c * and neuron where ".0" is appended). Otherwise we print double variables as - * they are represented in the mod file by user. + * they are represented in the mod file by user. If the value is in scientific + * representation (1e+20, 1E-15) then keep it as it is. */ -std::string CodegenCVisitor::double_to_string(const std::string& s_value) { - double value = std::stod(s_value); - if (std::ceil(value) == value) { - return "{:.1f}"_format(value); - } - return s_value; +std::string CodegenCVisitor::format_double_string(const std::string& s_value) { + return codegen_utils::format_double_string(s_value); } -std::string CodegenCVisitor::float_to_string(const std::string& s_value) { - float value = std::stof(s_value); - if (std::ceil(value) == value) { - return "{:.1f}"_format(value); - } - return s_value; +std::string CodegenCVisitor::format_float_string(const std::string& s_value) { + return codegen_utils::format_float_string(s_value); } @@ -1006,7 +1001,7 @@ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { std::get<1>(*iter), std::get<2>(*iter), std::get<3>(*iter)); - if (!utils::is_last(iter, params)) { + if (!nmodl::utils::is_last(iter, params)) { param += ", "; } } @@ -1684,7 +1679,7 @@ void CodegenCVisitor::print_function(const ast::FunctionBlock& node) { std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) const { - auto& singleton_random_string_class = utils::SingletonRandomString<4>::instance(); + auto& singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); std::string unique_name = original_name; if (singleton_random_string_class.random_string_exists(original_name)) { unique_name = original_name; diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index fc980e87da..2cffb37cc9 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -575,18 +575,18 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Convert a given \c double value to its string representation - * \param value The number to convert + * \param value The number to convert given as string as it is parsed by the modfile * \return Its string representation */ - virtual std::string double_to_string(const std::string& value); + virtual std::string format_double_string(const std::string& value); /** * Convert a given \c float value to its string representation - * \param value The number to convert + * \param value The number to convert given as string as it is parsed by the modfile * \return Its string representation */ - virtual std::string float_to_string(const std::string& value); + virtual std::string format_float_string(const std::string& value); /** @@ -1657,6 +1657,21 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { , float_type(float_type) , optimize_ionvar_copies(optimize_ionvar_copies) {} + CodegenCVisitor(const std::string& mod_filename, + std::ostream& stream, + LayoutType layout, + const std::string& float_type, + const bool optimize_ionvar_copies, + const std::string& extension, + const std::string& wrapper_ext) + : target_printer(new CodePrinter(stream)) + , wrapper_printer(new CodePrinter(stream)) + , printer(target_printer) + , mod_filename(mod_filename) + , layout(layout) + , float_type(float_type) + , optimize_ionvar_copies(optimize_ionvar_copies) {} + public: /** @@ -1888,7 +1903,7 @@ void CodegenCVisitor::print_vector_elements(const std::vector& elements, for (auto iter = elements.begin(); iter != elements.end(); iter++) { printer->add_text(prefix); (*iter)->accept(*this); - if (!separator.empty() && !utils::is_last(iter, elements)) { + if (!separator.empty() && !nmodl::utils::is_last(iter, elements)) { printer->add_text(separator); } } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index ca517aabd3..b2822f1078 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -11,6 +11,7 @@ #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" +#include "codegen/codegen_utils.hpp" #include "symtab/symbol_table.hpp" #include "utils/logger.hpp" #include "visitors/rename_visitor.hpp" @@ -124,54 +125,26 @@ void CodegenIspcVisitor::visit_local_list_statement(const ast::LocalListStatemen /****************************************************************************************/ /** - * \todo : In ISPC we have to explicitly append `d` to a floating point number - * otherwise it is treated as float. A value stored in the AST can be in - * scientific notation and hence we can't just append `d` to the string. - * Hence, we have to print number with .16f and then append `d`. But note - * that this will result into discrepancy between C++ backend and ISPC - * backend when floating point number is not exactly represented with .16f. + * In ISPC we have to explicitly append `d` to a floating point number + * otherwise it is treated as float. A value stored in the AST can be in + * scientific notation and hence we can't just append `d` to the string. + * Hence, we have to transform the value into the ISPC compliant format by + * replacing `e` and `E` with `d` to keep the same representation of the + * number as in the cpp backend. */ -std::string CodegenIspcVisitor::double_to_string(const std::string& s_value) { - double value = std::stod(s_value); - if (std::ceil(value) == value) { - return "{:.1f}d"_format(value); - } - if ((value <= 1.0) && (value >= -1.0)) { - return "{:.16f}d"_format(value); - } else { - auto e = std::log10(std::abs(value)); - if (e < 0.0) { - e = std::ceil(-e); - auto m = std::pow(10, e); - return "{:f}d-{:d}"_format(value * m, static_cast(e)); - } else { - e = std::floor(e); - auto m = std::pow(10, e); - return "{:f}d{:d}"_format(value / m, static_cast(e)); - } - } +std::string CodegenIspcVisitor::format_double_string(const std::string& s_value) { + return utils::format_double_string(s_value); } -std::string CodegenIspcVisitor::float_to_string(const std::string& s_value) { - float value = std::stof(s_value); - if (std::ceil(value) == value) { - return "{:.1f}"_format(value); - } - if ((value <= 1.0f) && (value >= -1.0f)) { - return "{:.6f}"_format(value); - } else { - auto e = std::log10(std::abs(value)); - if (e < 0.0f) { - e = std::ceil(-e); - auto m = std::pow(10, e); - return "{:f}e-{:d}"_format(value * m, static_cast(e)); - } else { - e = std::floor(e); - auto m = std::pow(10, e); - return "{:f}e{:d}"_format(value / m, static_cast(e)); - } - } +/** + * For float variables we don't have to do the conversion with changing `e` and + * `E` with `f`, since the scientific notation numbers are already parsed as + * floats by ISPC. Instead we need to take care of only appending `f` to the + * end of floating point numbers, which is optional on ISPC. + */ +std::string CodegenIspcVisitor::format_float_string(const std::string& s_value) { + return utils::format_float_string(s_value); } @@ -492,7 +465,7 @@ void CodegenIspcVisitor::print_nmodl_constants() { printer->add_line("/** constants used in nmodl */"); for (auto& it: info.factor_definitions) { const std::string name = it->get_node_name() == "PI" ? "ISPC_PI" : it->get_node_name(); - const std::string value = it->get_value()->get_value(); + const std::string value = format_double_string(it->get_value()->get_value()); printer->add_line("static const uniform double {} = {};"_format(name, value)); } } diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 6d562ef1e0..589dcb9614 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -60,12 +60,20 @@ class CodegenIspcVisitor: public CodegenCVisitor { std::vector wrapper_functions; protected: - /// doubles are differently represented in ispc than in C - std::string double_to_string(const std::string& value) override; + /** + * Convert a given \c double value to its string representation + * \param value The number to convert given as string as it parsed by the modfile + * \return Its string representation in ISPC compliant format + */ + std::string format_double_string(const std::string& value) override; - /// floats are differently represented in ispc than in C - std::string float_to_string(const std::string& value) override; + /** + * Convert a given \c float value to its string representation + * \param value The number to convert given as string as it parsed by the modfile + * \return Its string representation in ISPC compliant format + */ + std::string format_float_string(const std::string& value) override; /// name of the code generation backend @@ -151,12 +159,6 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_wrapper_headers_include(); - void print_nmodl_constants() override; - - - /// all compute functions for every backend - void print_compute_functions() override; - /// nmodl procedure definition void print_procedure(const ast::ProcedureBlock& node) override; @@ -236,13 +238,24 @@ class CodegenIspcVisitor: public CodegenCVisitor { LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) + : CodegenCVisitor(mod_file, + stream, + layout, + float_type, + optimize_ionvar_copies, + ".ispc", + ".cpp") , fallback_codegen(mod_file, layout, float_type, optimize_ionvar_copies, wrapper_printer) {} void visit_function_call(const ast::FunctionCall& node) override; void visit_var_name(const ast::VarName& node) override; void visit_program(const ast::Program& node) override; void visit_local_list_statement(const ast::LocalListStatement& node) override; + + /// all compute functions for every backend + void print_compute_functions() override; + + void print_nmodl_constants() override; }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp new file mode 100644 index 0000000000..cc11494cae --- /dev/null +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -0,0 +1,91 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "codegen/codegen_utils.hpp" + +#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_ispc_visitor.hpp" + +namespace nmodl { +namespace codegen { +namespace utils { +/** + * \details We can directly print value but if user specify value as integer then + * then it gets printed as an integer. To avoid this, we use below wrapper. + * If user has provided integer then it gets printed as 1.0 (similar to mod2c + * and neuron where ".0" is appended). Otherwise we print double variables as + * they are represented in the mod file by user. If the value is in scientific + * representation (1e+20, 1E-15) then keep it as it is. + */ +template <> +std::string format_double_string(const std::string& s_value) { + double value = std::stod(s_value); + if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { + return "{:.1f}"_format(value); + } + return s_value; +} + + +/** + * \details In ISPC we have to explicitly append `d` to a floating point number + * otherwise it is treated as float. A value stored in the AST can be in + * scientific notation and hence we can't just append `d` to the string. + * Hence, we have to transform the value into the ISPC compliant format by + * replacing `e` and `E` with `d` to keep the same representation of the + * number as in the cpp backend. + */ +template <> +std::string format_double_string(const std::string& s_value) { + std::string return_string = s_value; + if (s_value.find_first_of("eE") != std::string::npos) { + std::replace(return_string.begin(), return_string.end(), 'E', 'd'); + std::replace(return_string.begin(), return_string.end(), 'e', 'd'); + } else if (s_value.find('.') == std::string::npos) { + return_string += ".0d"; + } else if (s_value.front() == '.') { + return_string = '0' + return_string + 'd'; + } else { + return_string += 'd'; + } + return return_string; +} + + +template <> +std::string format_float_string(const std::string& s_value) { + float value = std::stof(s_value); + if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { + return "{:.1f}"_format(value); + } + return s_value; +} + + +/** + * \details For float variables we don't have to do the conversion with changing `e` and + * `E` with `f`, since the scientific notation numbers are already parsed as + * floats by ISPC. Instead we need to take care of only appending `f` to the + * end of floating point numbers, which is optional on ISPC. + */ +template <> +std::string format_float_string(const std::string& s_value) { + std::string return_string = s_value; + if (s_value.find_first_of("Ee.") == std::string::npos) { + return_string += ".0f"; + } else if (s_value.front() == '.' && s_value.find_first_of("Ee") == std::string::npos) { + return_string = '0' + return_string + 'f'; + } else if (s_value.find_first_of("Ee") == std::string::npos) { + return_string += 'f'; + } + return return_string; +} + + +} // namespace utils +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_utils.hpp b/src/nmodl/codegen/codegen_utils.hpp new file mode 100644 index 0000000000..4178edb9a3 --- /dev/null +++ b/src/nmodl/codegen/codegen_utils.hpp @@ -0,0 +1,53 @@ +/************************************************************************* + * Copyright (C) 2018-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief Implement utility functions for codegen visitors + * + */ + +#include + +namespace nmodl { +namespace codegen { +namespace utils { + +/** + * Handles the double constants format being printed in the generated code. + * + * It takes care of printing the values with the correct floating point precision + * for each backend, similar to mod2c and Neuron. + * This function can be called using as template `CodegenCVisitor` or + * `CodegenIspcVisitor`. + * + * \param s_value The double constant as string + * \return The proper string to be printed in the generated file. + */ +template +std::string format_double_string(const std::string& s_value); + + +/** + * Handles the float constants format being printed in the generated code. + * + * It takes care of printing the values with the correct floating point precision + * for each backend, similar to mod2c and Neuron. + * This function can be called using as template `CodegenCVisitor` or + * `CodegenIspcVisitor`. + * + * \param s_value The double constant as string + * \return The proper string to be printed in the generated file. + */ +template +std::string format_float_string(const std::string& s_value); + +} // namespace utils +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index f21a050cf8..b0d6d5d1fd 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -47,10 +47,10 @@ class CodePrinter { public: CodePrinter() - : result(new std::ostream(std::cout.rdbuf())) {} + : result(std::make_shared(std::cout.rdbuf())) {} CodePrinter(std::ostream& stream) - : result(new std::ostream(stream.rdbuf())) {} + : result(std::make_shared(stream.rdbuf())) {} CodePrinter(const std::string& filename); diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index a206a1e6aa..54db4eb799 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -53,7 +53,8 @@ add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) -add_executable(testcodegen codegen/codegen_helper.cpp) +add_executable(testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp + codegen/codegen_utils.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) @@ -106,11 +107,11 @@ endif() foreach( test_name + testcodegen testmodtoken testlexer testparser testvisitor - testcodegen testprinter testsymtab testnewton diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 1265df8e6b..f23a6f29e7 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -5,8 +5,6 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include #include "ast/program.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp new file mode 100644 index 0000000000..9c0418f5b7 --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp @@ -0,0 +1,190 @@ +/************************************************************************* + * Copyright (C) 2019-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "codegen/codegen_ispc_visitor.hpp" +#include "config/config.h" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/units_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace codegen; +using namespace test_utils; + +using nmodl::NrnUnitsLib; +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Helper for codegen related visitor +//============================================================================= +class IspcCodegenTestHelper { + /** + * Shared pointer to the CodegenIspcVisitor that is used throughout the test + */ + std::shared_ptr codegen_ispc_visitor; + + /** + * Driver to parse the nmodl text + */ + NmodlDriver driver; + + /** + * Shared pointer of AST generated by the parsed NMODL text + */ + std::shared_ptr ast; + + /** + * String buffer which saves the output after every call to CodegenIspcVisitor + */ + std::stringbuf strbuf; + + public: + /** + * \brief Constructs the helper class for the unit tests of CodegenIspcVisitor + * + * Takes as argument the nmodl text and sets up the CodegenIspcVisitor. + * + * \param nmodl_text NMODL text to be parsed and printed + * + */ + IspcCodegenTestHelper(const std::string& nmodl_text) { + ast = driver.parse_string(nmodl_text); + + /// directory where units lib file is located + std::string units_dir(NrnUnitsLib::get_path()); + /// parse units of text + UnitsVisitor(units_dir).visit_program(*ast); + + /// construct symbol table + SymtabVisitor().visit_program(*ast); + + /// initialize CodegenIspcVisitor + std::ostream oss(&strbuf); + codegen_ispc_visitor = std::make_shared( + "unit_test", oss, codegen::LayoutType::soa, "double", false); + codegen_ispc_visitor->setup(*ast); + } + + /** + * Get CodegenIspcVisitor to call its functions + */ + std::shared_ptr get_visitor() { + return codegen_ispc_visitor; + } + + /** + * Returns the existing string and resets the buffer for next call + */ + std::string get_string() { + std::string return_string = strbuf.str(); + strbuf.str(""); + return return_string; + } +}; + +std::string print_ispc_nmodl_constants(IspcCodegenTestHelper& ispc_codegen_test_helper) { + /// print nmodl constants + ispc_codegen_test_helper.get_visitor()->print_nmodl_constants(); + + return ispc_codegen_test_helper.get_string(); +} + +std::string print_ispc_compute_functions(IspcCodegenTestHelper& ispc_codegen_test_helper) { + /// print compute functions + ispc_codegen_test_helper.get_visitor()->print_compute_functions(); + + return ispc_codegen_test_helper.get_string(); +} + + +SCENARIO("ISPC codegen", "[codegen][ispc]") { + GIVEN("Simple mod file") { + std::string nmodl_text = R"( + TITLE UnitTest + NEURON { + SUFFIX unit_test + RANGE a, b + } + ASSIGNED { + a + b + } + UNITS { + FARADAY = (faraday) (coulomb) + } + INITIAL { + a = 0.0 + b = 0.0 + } + BREAKPOINT { + a = b + FARADAY + } + )"; + + std::string nmodl_constants_declaration = R"( + /** constants used in nmodl */ + static const uniform double FARADAY = 96485.3321233100141d; + )"; + + std::string nrn_init_state_block = R"( + /** initialize channel */ + export void nrn_init_unit_test(uniform unit_test_Instance* uniform inst, uniform NrnThread* uniform nt, uniform Memb_list* uniform ml, uniform int type) { + uniform int nodecount = ml->nodecount; + uniform int pnodecount = ml->_nodecount_padded; + const int* uniform node_index = ml->nodeindices; + double* uniform data = ml->data; + const double* uniform voltage = nt->_actual_v; + Datum* uniform indexes = ml->pdata; + ThreadDatum* uniform thread = ml->_thread; + + int uniform start = 0; + int uniform end = nodecount; + foreach (id = start ... end) { + int node_id = node_index[id]; + double v = voltage[node_id]; + inst->a[id] = 0.0d; + inst->b[id] = 0.0d; + } + } + + + /** update state */ + export void nrn_state_unit_test(uniform unit_test_Instance* uniform inst, uniform NrnThread* uniform nt, uniform Memb_list* uniform ml, uniform int type) { + uniform int nodecount = ml->nodecount; + uniform int pnodecount = ml->_nodecount_padded; + const int* uniform node_index = ml->nodeindices; + double* uniform data = ml->data; + const double* uniform voltage = nt->_actual_v; + Datum* uniform indexes = ml->pdata; + ThreadDatum* uniform thread = ml->_thread; + + int uniform start = 0; + int uniform end = nodecount; + foreach (id = start ... end) { + int node_id = node_index[id]; + double v = voltage[node_id]; + inst->a[id] = inst->b[id] + FARADAY; + } + } + )"; + + IspcCodegenTestHelper ispc_codegen_test_helper(nmodl_text); + THEN("Check that the nmodl constants and computer functions are printed correctly") { + auto nmodl_constants_result = reindent_text( + print_ispc_nmodl_constants(ispc_codegen_test_helper)); + REQUIRE(nmodl_constants_result == reindent_text(nmodl_constants_declaration)); + auto nmodl_init_cur_state = reindent_text( + print_ispc_compute_functions(ispc_codegen_test_helper)); + REQUIRE(nmodl_init_cur_state == reindent_text(nrn_init_state_block)); + } + } +} diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp new file mode 100644 index 0000000000..ef1901b6f7 --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -0,0 +1,161 @@ +/************************************************************************* + * Copyright (C) 2019-2020 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_ispc_visitor.hpp" +#include "codegen/codegen_utils.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace codegen; + +using input_result_map = std::unordered_map; + +SCENARIO("C codegen utility functions", "[codegen][util][c]") { + GIVEN("Double constant as string") { + std::string double_constant = "0.012345678901234567"; + + THEN("Codegen C Visitor prints double with same precision") { + auto nmodl_constant_result = codegen::utils::format_double_string( + double_constant); + REQUIRE(nmodl_constant_result == double_constant); + } + } + + GIVEN("Integer constant as string") { + std::string double_constant = "1"; + + std::string codegen_output = "1.0"; + + THEN("Codegen C Visitor prints integer as double number") { + auto nmodl_constant_result = codegen::utils::format_double_string( + double_constant); + REQUIRE(nmodl_constant_result == codegen_output); + } + } + + GIVEN("Double constants in scientific notation as strings") { + input_result_map tests({{"1e+18", "1e+18"}, {"1e-18", "1e-18"}, {"1E18", "1E18"}}); + + THEN("Codegen C Visitor prints doubles with scientific notation") { + for (const auto& test: tests) { + REQUIRE(codegen::utils::format_double_string(test.first) == + test.second); + } + } + } + + GIVEN("Float constant as string") { + std::string float_constant = "0.01234567"; + + THEN("Codegen C Visitor prints float with same precision") { + auto nmodl_constant_result = codegen::utils::format_float_string( + float_constant); + REQUIRE(nmodl_constant_result == float_constant); + } + } + + GIVEN("Float constant as string") { + std::string float_constant = "1"; + + std::string codegen_output = "1.0"; + + THEN("Codegen C Visitor prints integer as double number") { + auto nmodl_constant_result = codegen::utils::format_float_string( + float_constant); + REQUIRE(nmodl_constant_result == codegen_output); + } + } + + GIVEN("Float constants in scientific notation as strings") { + input_result_map tests({{"1e+18", "1e+18"}, {"1e-18", "1e-18"}, {"1E18", "1E18"}}); + + THEN("Codegen C Visitor prints doubles with scientific notation") { + for (const auto& test: tests) { + REQUIRE(codegen::utils::format_float_string(test.first) == + test.second); + } + } + } +} + +SCENARIO("ISPC codegen utility functions", "[codegen][util][ispc]") { + GIVEN("Double constant as string") { + input_result_map tests({{"0.012345678901234567", "0.012345678901234567d"}, + {".012345678901234567", "0.012345678901234567d"}, + {"123.", "123.d"}}); + + THEN("Codegen ISPC Visitor prints double with same precision") { + for (const auto& test: tests) { + REQUIRE(codegen::utils::format_double_string(test.first) == + test.second); + } + } + } + + GIVEN("Integer double constant as string") { + std::string double_constant = "1"; + + std::string codegen_output = "1.0d"; + + THEN("Codegen ISPC Visitor prints integer as double number") { + auto nmodl_constant_result = codegen::utils::format_double_string( + double_constant); + REQUIRE(nmodl_constant_result == codegen_output); + } + } + + GIVEN("Double constants in scientific notation as strings") { + input_result_map tests( + {{"1e+18", "1d+18"}, {"1e-18", "1d-18"}, {".123e18", ".123d18"}, {"1E18", "1d18"}}); + + THEN("Codegen ISPC Visitor prints doubles with scientific notation") { + for (const auto& test: tests) { + REQUIRE(codegen::utils::format_double_string(test.first) == + test.second); + } + } + } + + GIVEN("Float constant as string") { + input_result_map tests( + {{"0.01234567", "0.01234567f"}, {".01234567", "0.01234567f"}, {"123.", "123.f"}}); + + THEN("Codegen ISPC Visitor prints float with same precision") { + for (const auto& test: tests) { + REQUIRE(codegen::utils::format_float_string(test.first) == + test.second); + } + } + } + + GIVEN("Integer float constant as string") { + std::string float_constant = "1"; + + std::string codegen_output = "1.0f"; + + THEN("Codegen ISPC Visitor prints integer as double number") { + auto nmodl_constant_result = codegen::utils::format_float_string( + float_constant); + REQUIRE(nmodl_constant_result == codegen_output); + } + } + + GIVEN("Float constants in scientific notation as strings") { + input_result_map tests( + {{"1e+18", "1e+18"}, {"1e-18", "1e-18"}, {".123e18", ".123e18"}, {"1E18", "1E18"}}); + + THEN("Codegen ISPC Visitor prints doubles with scientific notation") { + for (const auto& test: tests) { + REQUIRE(codegen::utils::format_float_string(test.first) == + test.second); + } + } + } +} diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp new file mode 100644 index 0000000000..39b207cb3f --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -0,0 +1,26 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_RUNNER + +#include + +#include "pybind/pyembed.hpp" +#include "utils/logger.hpp" + +using namespace nmodl; + +int main(int argc, char* argv[]) { + // initialize python interpreter once for entire catch executable + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); + // enable verbose logger output + logger->set_level(spdlog::level::debug); + // run all catch tests + int result = Catch::Session().run(argc, argv); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); + return result; +} From 1440ef68f035f97af332e65ff47f25bff2e9eeff Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Sun, 20 Dec 2020 16:08:18 +0100 Subject: [PATCH 326/871] Azure CI and git submodule improvements (BlueBrain/nmodl#466) * Disable extra nmodl targets if used as sub-module - nmodl has multiple utility lexer/parsers - when nmodl is used as submodule in neuron or coreneuron then disable these targets * Avoid azure trigger for all branches - when PR is created, there are 2 builds running for same update : one on branch and one on PR * On Azure, build without submodules when ubuntu * Avoid recursive cloning of each submodule - we are not dependent on submodules of submodules NMODL Repo SHA: BlueBrain/nmodl@ab27595d047ef8e43519a5a6058d19f1a9bc45ef --- cmake/nmodl/ExternalProjectHelper.cmake | 8 +++---- src/nmodl/lexer/CMakeLists.txt | 24 +++++++++++--------- src/nmodl/parser/CMakeLists.txt | 24 ++++++++++++-------- src/nmodl/visitors/CMakeLists.txt | 29 +++++++++++++++---------- 4 files changed, 49 insertions(+), 36 deletions(-) diff --git a/cmake/nmodl/ExternalProjectHelper.cmake b/cmake/nmodl/ExternalProjectHelper.cmake index 087974d9cd..6064008346 100644 --- a/cmake/nmodl/ExternalProjectHelper.cmake +++ b/cmake/nmodl/ExternalProjectHelper.cmake @@ -10,11 +10,9 @@ function(initialize_submodule path) message( FATAL_ERROR "git not found and ${path} sub-module not cloned (use git clone --recursive)") endif() - message(STATUS "Sub-module : missing ${path}: running git submodule update --init --recursive") - execute_process( - COMMAND - git submodule update --init --recursive -- ${path} - WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}) + message(STATUS "Sub-module : missing ${path}: running git submodule update --init") + execute_process(COMMAND git submodule update --init -- ${path} + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}) endfunction() # check for external project and initialize submodule if it is missing diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index c42b5b41a7..927864edcd 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -176,21 +176,25 @@ add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILE set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) add_library(lexer STATIC $) +target_link_libraries(lexer fmt::fmt) -add_executable(nmodl_lexer main_nmodl.cpp) -add_executable(c_lexer main_c.cpp) -add_executable(units_lexer main_units.cpp) +if(NOT NMODL_AS_SUBPROJECT) + add_executable(nmodl_lexer main_nmodl.cpp) + add_executable(c_lexer main_c.cpp) + add_executable(units_lexer main_units.cpp) -target_link_libraries(lexer fmt::fmt) -target_link_libraries(nmodl_lexer lexer util) -target_link_libraries(c_lexer lexer util) -target_link_libraries(units_lexer lexer util) + target_link_libraries(nmodl_lexer lexer util) + target_link_libraries(c_lexer lexer util) + target_link_libraries(units_lexer lexer util) +endif() # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) -install(TARGETS c_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) -install(TARGETS units_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) +if(NOT NMODL_AS_SUBPROJECT) + install(TARGETS nmodl_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) + install(TARGETS c_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) + install(TARGETS units_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) +endif() add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 9ed586043a..749b534c7e 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -4,17 +4,23 @@ # lexer library links with all parser related files so no need to have parser as a separate library -add_executable(nmodl_parser main_nmodl.cpp) -add_executable(c_parser main_c.cpp) -add_executable(units_parser main_units.cpp) +if(NOT NMODL_AS_SUBPROJECT) + add_executable(nmodl_parser main_nmodl.cpp) + add_executable(c_parser main_c.cpp) + add_executable(units_parser main_units.cpp) -target_link_libraries(nmodl_parser lexer util) -target_link_libraries(c_parser lexer util) -target_link_libraries(units_parser util visitor lexer) + target_link_libraries(nmodl_parser lexer util) + target_link_libraries(c_parser lexer util) + target_link_libraries(units_parser util visitor lexer) +endif() # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) -install(TARGETS c_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) -install(TARGETS units_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) +if(NOT NMODL_AS_SUBPROJECT) + install(TARGETS nmodl_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS + Debug) + install(TARGETS c_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) + install(TARGETS units_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS + Debug) +endif() diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 33dc0f61b8..cd13353a41 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -63,8 +63,6 @@ add_library(visitor STATIC $) add_dependencies(visitor lexer util pywrapper) -add_executable(nmodl_visitor main.cpp) - # ~~~ # pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to # libpython, we can use `pybind11::module` interface library from pybind11. @@ -74,17 +72,24 @@ if(NOT LINK_AGAINST_PYTHON) else() target_link_libraries(visitor PRIVATE pybind11::embed) endif() -target_link_libraries( - nmodl_visitor - printer - visitor - symtab - util - lexer - ${NMODL_WRAPPER_LIBS}) + +if(NOT NMODL_AS_SUBPROJECT) + add_executable(nmodl_visitor main.cpp) + + target_link_libraries( + nmodl_visitor + printer + visitor + symtab + util + lexer + ${NMODL_WRAPPER_LIBS}) +endif() # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl_visitor DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor CONFIGURATIONS - Debug) +if(NOT NMODL_AS_SUBPROJECT) + install(TARGETS nmodl_visitor DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor CONFIGURATIONS + Debug) +endif() From 59d18175661d7cbfca32fc062683cb28cdb77933 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 5 Jan 2021 00:21:29 +0100 Subject: [PATCH 327/871] Add --verbose with different severity levels (BlueBrain/nmodl#460) * Currently --verbose is just boolean flag to turn on / off logs * Convert boolean flag with textual options: trace, debug, info, warning, error, critical, off * Update README file NMODL Repo SHA: BlueBrain/nmodl@83386cf0991ead7ebe879b48e9f4049bc5e5de9f --- README.md | 2 +- src/nmodl/nmodl/main.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 92a7eff387..4318cc616a 100644 --- a/README.md +++ b/README.md @@ -221,7 +221,7 @@ Positionals: Options: -h,--help Print this help message and exit -H,--help-all Print this help message including all sub-commands - -v,--verbose Verbose logger output + --verbose=info Verbose logger output (trace, debug, info, warning, error, critical, off) -o,--output TEXT=. Directory for backend code output --scratch TEXT=tmp Directory for intermediate code output --units TEXT=/path/<>/nrnunits.lib diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 13ec7a8af4..0fe75550ba 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -65,7 +65,7 @@ int main(int argc, const char* argv[]) { std::vector mod_files; /// true if debug logger statements should be shown - bool verbose(false); + std::string verbose("info"); /// true if serial c code to be generated bool c_backend(true); @@ -161,7 +161,9 @@ int main(int argc, const char* argv[]) { app.get_formatter()->column_width(40); app.set_help_all_flag("-H,--help-all", "Print this help message including all sub-commands"); - app.add_flag("-v,--verbose", verbose, "Verbose logger output")->ignore_case(); + app.add_option("--verbose", verbose, "Verbosity of logger output", true) + ->ignore_case() + ->check(CLI::IsMember({"trace", "debug", "info", "warning", "error", "critical", "off"})); app.add_option("file", mod_files, "One or more MOD files to process") ->ignore_case() @@ -281,9 +283,7 @@ int main(int argc, const char* argv[]) { ->initialize_interpreter(); } - if (verbose) { - logger->set_level(spdlog::level::debug); - } + logger->set_level(spdlog::level::from_str(verbose)); /// write ast to nmodl const auto ast_to_nmodl = [nmodl_ast](ast::Program& ast, const std::string& filepath) { From aa323384a33df8362c245bd35fc9b0fa86f2646e Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Thu, 7 Jan 2021 14:13:31 +0100 Subject: [PATCH 328/871] Implement exprelr and vexpm1 (BlueBrain/nmodl#481) * Move exp(g)-1 out of expv to be reused * Add expm1 Single and double precision * Add exprelr Single and double precision * Add fast_math for non-ispc compilers too * Add unit tests * Rename uint322sp -> int322sp in fast_math * Update src/codegen/fast_math.hpp * Address Omar's suggestions Co-authored-by: Omar Awile NMODL Repo SHA: BlueBrain/nmodl@d583ae80943b576f2498d1736dc7b0712c26fa0d --- src/nmodl/codegen/CMakeLists.txt | 2 + src/nmodl/codegen/codegen_c_visitor.cpp | 1 + src/nmodl/codegen/fast_math.hpp | 222 ++++++++++++++++++ src/nmodl/codegen/fast_math.ispc | 115 +++++++-- test/nmodl/transpiler/unit/CMakeLists.txt | 2 + .../transpiler/unit/fast_math/fast_math.cpp | 119 ++++++++++ 6 files changed, 439 insertions(+), 22 deletions(-) create mode 100644 src/nmodl/codegen/fast_math.hpp create mode 100644 test/nmodl/transpiler/unit/fast_math/fast_math.cpp diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 046d3fa214..32ad4e1303 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -32,6 +32,8 @@ add_dependencies(codegen lexer util visitor) # copy to build directory to make usable from build directory configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc ${CMAKE_BINARY_DIR}/include/nmodl/fast_math.ispc COPYONLY) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.hpp + ${CMAKE_BINARY_DIR}/include/nmodl/fast_math.hpp COPYONLY) # ============================================================================= # Install include files diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 76975e9195..c7d5e32fc3 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2337,6 +2337,7 @@ void CodegenCVisitor::print_backend_info() { void CodegenCVisitor::print_standard_includes() { printer->add_newline(); printer->add_line("#include "); + printer->add_line("#include \"nmodl/fast_math.hpp\" // extend math with some useful functions"); printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp new file mode 100644 index 0000000000..2934548890 --- /dev/null +++ b/src/nmodl/codegen/fast_math.hpp @@ -0,0 +1,222 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + * + * Note: fast_math.ispc translated into .hpp syntax. More information in + * fast_math.ispc + *************************************************************************/ + +#pragma once + +#include + +/** + * \file + * \brief Implementation of different math functions + */ + +static inline double uint642dp(uint64_t ll) { + return *((double*) (&ll)); +} + +static inline uint64_t dp2uint64(double x) { + return *((uint64_t*) (&x)); +} + +static inline float int322sp(int32_t x) { + return *((float*) (&x)); +} + +static inline unsigned int sp2uint32(float x) { + return *((uint32_t*) (&x)); +} + +static inline double dpfloor(const double x) { + int32_t ret = x; + ret -= (sp2uint32(x) >> 31); + return ret; +} + +static inline float spfloor(const float x) { + int32_t ret = x; + ret -= (sp2uint32(x) >> 31); + return ret; +} + +static inline float f_inf() { + uint32_t v = 0x7F800000; + return *((float*) (&v)); +} + +static inline double inf() { + uint64_t v = 0x7FF0000000000000; + return *((double*) (&v)); +} + + +static const double EXP_LIMIT = 708.0; + +static const double C1 = 6.93145751953125E-1; +static const double C2 = 1.42860682030941723212E-6; + +static const double PX1exp = 1.26177193074810590878E-4; +static const double PX2exp = 3.02994407707441961300E-2; +static const double PX3exp = 9.99999999999999999910E-1; +static const double QX1exp = 3.00198505138664455042E-6; +static const double QX2exp = 2.52448340349684104192E-3; +static const double QX3exp = 2.27265548208155028766E-1; +static const double QX4exp = 2.00000000000000000009; + +static const double LOG2E = 1.4426950408889634073599; // 1/ln(2) + +static const float MAXLOGF = 88.72283905206835f; +static const float MINLOGF = -88.f; + +static const float C1F = 0.693359375f; +static const float C2F = -2.12194440e-4f; + +static const float PX1expf = 1.9875691500E-4f; +static const float PX2expf = 1.3981999507E-3f; +static const float PX3expf = 8.3334519073E-3f; +static const float PX4expf = 4.1665795894E-2f; +static const float PX5expf = 1.6666665459E-1f; +static const float PX6expf = 5.0000001201E-1f; + +static const float LOG2EF = 1.44269504088896341f; // 1/ln(2) + +static inline double egm1(double x, double px) { + x -= px * C1; + x -= px * C2; + + const double xx = x * x; + + px = PX1exp; + px *= xx; + px += PX2exp; + px *= xx; + px += PX3exp; + px *= x; + + double qx = QX1exp; + qx *= xx; + qx += QX2exp; + qx *= xx; + qx += QX3exp; + qx *= xx; + qx += QX4exp; + + return 2.0 * px / (qx - px); +} + +/// double precision exp function +static inline double vexp(double initial_x) { + double x = initial_x; + double px = dpfloor(LOG2E * x + 0.5); + const int32_t n = px; + + x = 1.0 + egm1(x, px); + x *= uint642dp((((uint64_t) n) + 1023) << 52); + + if (initial_x > EXP_LIMIT) + x = inf(); + if (initial_x < -EXP_LIMIT) + x = 0.0; + + return x; +} + +/// double precision exp(x) - 1 function, used for small x. +static inline double vexpm1(double initial_x) { + double x = initial_x; + double px = dpfloor(LOG2E * x + 0.5); + const int32_t n = px; + + const uint64_t twopnm1 = (((uint64_t)(n - 1)) + 1023) << 52; + x = 2 * (uint642dp(twopnm1) * egm1(x, px) + uint642dp(twopnm1)) - 1; + + if (initial_x > EXP_LIMIT) + x = inf(); + if (initial_x < -EXP_LIMIT) + x = -1.0; + + return x; +} + + +/// double precision exprelr function () +static inline double exprelr(double initial_x) { + if (1.0 + initial_x == 1.0) { + return 1.0; + } + + return initial_x / vexpm1(initial_x); +} + +static inline float egm1(float x) { + float z = x * PX1expf; + z += PX2expf; + z *= x; + z += PX3expf; + z *= x; + z += PX4expf; + z *= x; + z += PX5expf; + z *= x; + z += PX6expf; + z *= x * x; + z += x; + + return z; +} + +/// single precision exp function +static inline float vexp(float initial_x) { + float x = initial_x; + float z = spfloor(LOG2EF * x + 0.5f); + + x -= z * C1F; + x -= z * C2F; + const int32_t n = z; + + z = 1.0f + egm1(x); + + z *= int322sp((n + 0x7f) << 23); + + if (initial_x > MAXLOGF) + z = f_inf(); + if (initial_x < MINLOGF) + z = 0.f; + + return z; +} + +/// single precision exp function +static inline float vexpm1(float initial_x) { + float x = initial_x; + float z = spfloor(LOG2EF * x + 0.5f); + + x -= z * C1F; + x -= z * C2F; + const int32_t n = z; + + const int32_t twopnm1 = ((n - 1) + 0x7f) << 23; + x = 2 * (int322sp(twopnm1) * egm1(x) + int322sp(twopnm1)) - 1; + + if (initial_x > MAXLOGF) + x = f_inf(); + if (initial_x < MINLOGF) + x = -1.0f; + + return x; +} + +/// single precision exprelr function +static inline float exprelr(float initial_x) { + if (1.0 + initial_x == 1.0) { + return 1.0; + } + + return initial_x / vexpm1(initial_x); +} diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc index 1bd20577e9..4f8de0a9ac 100644 --- a/src/nmodl/codegen/fast_math.ispc +++ b/src/nmodl/codegen/fast_math.ispc @@ -7,6 +7,7 @@ * Note that the fast ISPC exponentials Based on VDT implementation of * D. Piparo et al. See https://github.com/dpiparo/vdt for additional * license information. + * exprelr and expm1 are based on https://arbor.readthedocs.io/en/latest/internals/simd_api.html#implementation-of-vector-transcendental-functions *************************************************************************/ /** @@ -22,7 +23,7 @@ static inline unsigned int64 dp2uint64(double x) { return *((varying unsigned int64*) (&x)); } -static inline float uint322sp(int32 x) { +static inline float int322sp(int32 x) { return *((varying float*) (&x)); } @@ -63,7 +64,7 @@ static const uniform double QX2exp = 2.52448340349684104192d-3; static const uniform double QX3exp = 2.27265548208155028766d-1; static const uniform double QX4exp = 2.00000000000000000009d; -static const uniform double LOG2E = 1.4426950408889634073599d; +static const uniform double LOG2E = 1.4426950408889634073599d; // 1/ln(2) static const uniform float MAXLOGF = 88.72283905206835f; static const uniform float MINLOGF = -88.f; @@ -78,13 +79,9 @@ static const uniform float PX4expf = 4.1665795894E-2f; static const uniform float PX5expf = 1.6666665459E-1f; static const uniform float PX6expf = 5.0000001201E-1f; -static const uniform float LOG2EF = 1.44269504088896341f; +static const uniform float LOG2EF = 1.44269504088896341f; // 1/ln(2) -/// double precision exp function -static inline double vexp(double initial_x) { - double x = initial_x; - double px = dpfloor(LOG2E * x + 0.5d); - const int32 n = px; +static inline double egm1(double x, double px) { x -= px * 6.93145751953125d-1; x -= px * 1.42860682030941723212d-6; @@ -106,8 +103,16 @@ static inline double vexp(double initial_x) { qx *= xx; qx += QX4exp; - x = px / (qx - px); - x = 1.0d + 2.0d * x; + return 2.0d*px / (qx - px); +} + +/// double precision exp function +static inline double vexp(double initial_x) { + double x = initial_x; + double px = dpfloor(LOG2E * x + 0.5d); + const int32 n = px; + + x = 1.0d + egm1(x, px); x *= uint642dp((((unsigned int64) n) + 1023) << 52); if (initial_x > EXP_LIMIT) @@ -118,17 +123,38 @@ static inline double vexp(double initial_x) { return x; } -/// single prevision exp function -static inline float vexp(float initial_x) { - float x = initial_x; - float z = spfloor(LOG2EF * x + 0.5f); - x -= z * C1F; - x -= z * C2F; - const int32 n = z; - const float x2 = x * x; +/// double precision exp function +static inline double vexpm1(double initial_x) { + double x = initial_x; + double px = dpfloor(LOG2E * x + 0.5d); + const int32 n = px; + + const unsigned int64 twopnm1 = (((unsigned int64) (n-1) ) + 1023) << 52; + x = 2*(uint642dp(twopnm1)*egm1(x, px) + uint642dp(twopnm1)) - 1.0d; + + if (initial_x > EXP_LIMIT) + x = inf(); + if (initial_x < -EXP_LIMIT) + x = -1.0d; + + return x; +} + + +/// double precision exprelr function () +static inline double exprelr(double initial_x) { + if (1.0d+initial_x == 1.0d) { + return 1.0d; + } + + return initial_x/vexpm1(initial_x); +} + + +static inline float egm1(float x) { - z = x * PX1expf; + float z = x * PX1expf; z += PX2expf; z *= x; z += PX3expf; @@ -138,10 +164,24 @@ static inline float vexp(float initial_x) { z += PX5expf; z *= x; z += PX6expf; - z *= x2; - z += x + 1.0f; + z *= x*x; + z += x; - z *= uint322sp((n + 0x7f) << 23); + return z; +} + +/// single precision exp function +static inline float vexp(float initial_x) { + float x = initial_x; + float z = spfloor(LOG2EF * x + 0.5f); + + x -= z * C1F; + x -= z * C2F; + const int32 n = z; + + z = 1.0f + egm1(x); + + z *= int322sp((n + 0x7f) << 23); if (initial_x > MAXLOGF) z = f_inf(); @@ -150,3 +190,34 @@ static inline float vexp(float initial_x) { return z; } + +/// single precision exp function +static inline float vexpm1(float initial_x) { + float x = initial_x; + float z = spfloor(LOG2EF * x + 0.5f); + + x -= z * C1F; + x -= z * C2F; + const int32 n = z; + + const int32 twopnm1 = ((n-1) + 0x7f) << 23; + x = 2*(int322sp(twopnm1)*egm1(x) + int322sp(twopnm1)) - 1.0f; + + if (initial_x > MAXLOGF) + x = f_inf(); + if (initial_x < MINLOGF) + x = -1.0f; + + return x; +} + +/// single precision exprelr function +static inline float exprelr(float initial_x) { + if (1.0f+initial_x == 1.0f) { + return 1.0f; + } + + return initial_x/vexpm1(initial_x); +} + + diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 54db4eb799..529374a44b 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -51,6 +51,7 @@ add_executable( add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) +add_executable(testfast_math fast_math/fast_math.cpp ${SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) add_executable(testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp @@ -115,6 +116,7 @@ foreach( testprinter testsymtab testnewton + testfast_math testunitlexer testunitparser) diff --git a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp new file mode 100644 index 0000000000..858786aabf --- /dev/null +++ b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp @@ -0,0 +1,119 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_MAIN + +#include "codegen/fast_math.hpp" +#include + +#include + +float check_over_span_float(float f1(float), + float f2(float), + const float low_limit, + const float span, + const size_t npoints) { + float x = 0.0; + float val = f1(x) - f2(x); + float err = val * val; // 0.0 is an edge case + + + for (size_t i = 0; i < npoints; ++i) { + x = low_limit + span * i / npoints; + val = f1(x) - f2(x); + err += val * val; + } + err /= npoints + 1; + return err; +} + +double check_over_span_double(double f1(double), + double f2(double), + const double low_limit, + const double span, + const size_t npoints) { + double x = 0.0; + double val = f1(x) - f2(x); + double err = val * val; // 0.0 is an edge case + + for (size_t i = 0; i < npoints; ++i) { + x = low_limit + span * i / npoints; + val = f1(x) - f2(x); + err += val * val; + } + err /= npoints + 1; + return err; +} + +SCENARIO("Check fast_math") { + constexpr double low_limit = -5.0; + constexpr double span = 10.0; + constexpr size_t npoints = 1000; + constexpr double err_threshold = 1e-10; + GIVEN("vexp (double)") { + const double err = check_over_span_double(std::exp, vexp, low_limit, span, npoints); + + THEN("error inside threshold") { + REQUIRE(err < err_threshold); + } + } + GIVEN("vexp (float)") { + const double err = check_over_span_float(std::exp, vexp, low_limit, span, npoints); + + THEN("error inside threshold") { + REQUIRE(err < err_threshold); + } + } + GIVEN("expm1 (double)") { + const double err = + check_over_span_double([](const double x) -> double { return std::exp(x) - 1; }, + vexpm1, + low_limit, + span, + npoints); + + THEN("error inside threshold") { + REQUIRE(err < err_threshold); + } + } + GIVEN("expm1 (float)") { + const double err = + check_over_span_float([](const float x) -> float { return std::exp(x) - 1; }, + vexpm1, + low_limit, + span, + npoints); + + THEN("error inside threshold") { + REQUIRE(err < err_threshold); + } + } + GIVEN("exprelr (double)") { + const double err = check_over_span_double( + [](const double x) -> double { return (1.0 + x == 1.0) ? 1.0 : x / (std::exp(x) - 1); }, + exprelr, + low_limit, + span, + npoints); + + THEN("error inside threshold") { + REQUIRE(err < err_threshold); + } + } + GIVEN("exprelr (float)") { + const float err = check_over_span_float( + [](const float x) -> float { return (1.0 + x == 1.0) ? 1.0 : x / (std::exp(x) - 1); }, + exprelr, + low_limit, + span, + npoints); + + THEN("error inside threshold") { + REQUIRE(err < err_threshold); + } + } +} From df2416219c6f3e892db762ea5ecce97a27dbdfff Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 13 Jan 2021 23:16:27 +0100 Subject: [PATCH 329/871] Fix deprecation warning for sympy 1.7 (BlueBrain/nmodl#486) * Deprecation warning for sympy.printing.c vs sympy.printing.ccode * Remove limit on sympy <1.6 in azure CI NMODL Repo SHA: BlueBrain/nmodl@93b74cadc2f5bd7c360329713ecb25aa95940fbd --- nmodl/ode.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index 7a3db43e1f..b9db294c1f 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -12,11 +12,15 @@ # import known_functions through low-level mechanism because the ccode # module is overwritten in sympy and contents of that submodule cannot be # accessed through regular imports -known_functions = import_module('sympy.printing.ccode').known_functions_C99 +major, minor = (int(v) for v in sp.__version__.split(".")[:2]) +if major >= 1 and minor >= 7: + known_functions = import_module('sympy.printing.c').known_functions_C99 +else: + known_functions = import_module('sympy.printing.ccode').known_functions_C99 known_functions.pop('Abs') known_functions['abs'] = 'fabs' -major, minor = (int(v) for v in sp.__version__.split(".")[:2]) + if not ((major >= 1) and (minor >= 2)): raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") From 26ad1c7ec9accac427a8c42c64d70aac8eb19669 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 25 Jan 2021 13:58:37 +0100 Subject: [PATCH 330/871] Fixup! typo "nothing do to" -> "nothing to do" (BlueBrain/nmodl#498) NMODL Repo SHA: BlueBrain/nmodl@fff91848904cc1e53fe22b65aa710719963bfab4 --- src/nmodl/language/code_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 6b240de49f..d02c66f81e 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -393,7 +393,7 @@ def main(args=None): input = task.input.relative_to(codegen.this_dir) LOGGER.debug(f" %-{padding}s -> %s", input, task.output) else: - LOGGER.info("Nothing do to") + LOGGER.info("Nothing to do") if __name__ == "__main__": From 7de1b74571704dcc5caea208ebe9d0d687fa1ce9 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 26 Jan 2021 17:18:57 +0100 Subject: [PATCH 331/871] Updated HPC coding convention to latest (BlueBrain/nmodl#503) NMODL Repo SHA: BlueBrain/nmodl@45f8dd6855e0269142c55d8fbb7325f65629f36b --- cmake/nmodl/hpc-coding-conventions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 0673881f7e..3a4e543672 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 0673881f7e7faa6e1d57baaef5a8b3316e7ecac0 +Subproject commit 3a4e543672a98240a946f2bd2560e203baa670ef From 77e0208abba9e352dc32df5ed37514b4c66da3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20S=C4=83vulescu?= Date: Wed, 27 Jan 2021 00:43:41 +0100 Subject: [PATCH 332/871] code coverage -> codecov.io (BlueBrain/nmodl#504) Add code coverage with gcov/lcov and reporting on codecov.io NMODL Repo SHA: BlueBrain/nmodl@4e2fa666809324dc0747c62bd47287a9322bbf25 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4318cc616a..5f8602f618 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## The NMODL Framework -[![Build Status](https://travis-ci.org/BlueBrain/nmodl.svg?branch=master)](https://travis-ci.org/BlueBrain/nmodl) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) +[![Build Status](https://travis-ci.org/BlueBrain/nmodl.svg?branch=master)](https://travis-ci.org/BlueBrain/nmodl) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB)](https://codecov.io/gh/BlueBrain/nmodl) The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L**anguage ([NMODL](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html)). It is designed with modern compiler and code generation techniques to: From f1c5475e19c00ad69060957b5287fc72bf00eb10 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Sat, 30 Jan 2021 13:33:47 +0100 Subject: [PATCH 333/871] Fix typo in setup.py / more constexpr NMODL Repo SHA: BlueBrain/nmodl@b3f40a6cb62d34a5b302f288fb9ee3585d28729d --- setup.py | 2 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 14 ++-- src/nmodl/codegen/codegen_c_visitor.cpp | 12 +-- src/nmodl/codegen/codegen_naming.hpp | 84 +++++++++---------- src/nmodl/parser/diffeq_context.cpp | 4 +- src/nmodl/solver/newton/newton.hpp | 8 +- src/nmodl/units/units.hpp | 4 +- .../visitors/constant_folder_visitor.cpp | 4 +- src/nmodl/visitors/steadystate_visitor.cpp | 5 +- .../transpiler/integration/mod/cabpump.mod | 4 +- 10 files changed, 72 insertions(+), 69 deletions(-) diff --git a/setup.py b/setup.py index a870695244..1f95253e94 100644 --- a/setup.py +++ b/setup.py @@ -108,7 +108,7 @@ def _config_exe(exe_name): version=__version__, author="Blue Brain Project", author_email="bbp-ou-hpc@groupes.epfl.ch", - description="NEURON Modelling Language Source-to-Source Compiler Framework", + description="NEURON Modeling Language Source-to-Source Compiler Framework", long_description="", packages=["nmodl"], scripts=["pywheel/shim/nmodl", "pywheel/shim/find_libpython.py"], diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 43828dcf8b..273b137e96 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -35,18 +35,20 @@ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType ty return; } - std::string present_clause = "present(inst"; + std::ostringstream present_clause; + present_clause << "present(inst"; if (type == BlockType::NetReceive) { - present_clause += ", nrb"; + present_clause << ", nrb"; } else { - present_clause += ", node_index, data, voltage, indexes, thread"; + present_clause << ", node_index, data, voltage, indexes, thread"; if (type == BlockType::Equation) { - present_clause += ", vec_rhs, vec_d"; + present_clause << ", vec_rhs, vec_d"; } } - present_clause += ")"; - printer->add_line("#pragma acc parallel loop {} async(nt->stream_id)"_format(present_clause)); + present_clause << ')'; + printer->add_line( + "#pragma acc parallel loop {} async(nt->stream_id)"_format(present_clause.str())); } diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index c7d5e32fc3..cf74d91ffa 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -45,7 +45,7 @@ namespace codegen_utils = nmodl::codegen::utils; /* Overloaded visitor routines */ /****************************************************************************************/ -const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; +static const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; void CodegenCVisitor::visit_string(const String& node) { if (!codegen) { @@ -1917,7 +1917,7 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { } auto name = process_verbatim_token(token); - if (token == ("_" + naming::TQITEM_VARIABLE)) { + if (token == (std::string("_") + naming::TQITEM_VARIABLE)) { name = "&" + name; } if (token == "_STRIDE") { @@ -2296,13 +2296,13 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use } if (varname == naming::NTHREAD_DT_VARIABLE) { - return "nt->_" + naming::NTHREAD_DT_VARIABLE; + return std::string("nt->_") + naming::NTHREAD_DT_VARIABLE; } // t in net_receive method is an argument to function and hence it should // ne used instead of nt->_t which is current time of thread if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { - return "nt->_" + naming::NTHREAD_T_VARIABLE; + return std::string("nt->_") + naming::NTHREAD_T_VARIABLE; } // otherwise return original name @@ -2653,8 +2653,8 @@ void CodegenCVisitor::print_mechanism_register() { // types for ion for (const auto& ion: info.ions) { - auto type = get_variable_name(ion.name + "_type"); - auto name = add_escape_quote(ion.name + "_ion"); + const auto& type = get_variable_name(ion.name + "_type"); + const auto& name = add_escape_quote(ion.name + "_ion"); printer->add_line(type + " = nrn_get_mechtype(" + name + ");"); } printer->add_newline(); diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 7d2aabe7bb..83b79fa82a 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -18,130 +18,130 @@ namespace naming { /// nmodl language version /// @todo : should be moved from codegen to global scope -const std::string NMODL_VERSION("6.2.0"); +static constexpr char NMODL_VERSION[] = "6.2.0"; /// derivimplicit method in nmodl -const std::string DERIVIMPLICIT_METHOD("derivimplicit"); +static constexpr char DERIVIMPLICIT_METHOD[] = "derivimplicit"; /// euler method in nmodl -const std::string EULER_METHOD("euler"); +static constexpr char EULER_METHOD[] = "euler"; /// cnexp method in nmodl -const std::string CNEXP_METHOD("cnexp"); +static constexpr char CNEXP_METHOD[] = "cnexp"; /// cvode method in nmodl -const std::string AFTER_CVODE_METHOD("after_cvode"); +static constexpr char AFTER_CVODE_METHOD[] = "after_cvode"; /// sparse method in nmodl -const std::string SPARSE_METHOD("sparse"); +static constexpr char SPARSE_METHOD[] = "sparse"; /// net_event function call in nmodl -const std::string NET_EVENT_METHOD("net_event"); +static constexpr char NET_EVENT_METHOD[] = "net_event"; /// net_move function call in nmodl -const std::string NET_MOVE_METHOD("net_move"); +static constexpr char NET_MOVE_METHOD[] = "net_move"; /// net_send function call in nmodl -const std::string NET_SEND_METHOD("net_send"); +static constexpr char NET_SEND_METHOD[] = "net_send"; /// artificial cell keyword in nmodl -const std::string ARTIFICIAL_CELL("ARTIFICIAL_CELL"); +static constexpr char ARTIFICIAL_CELL[] = "ARTIFICIAL_CELL"; /// point process keyword in nmodl -const std::string POINT_PROCESS("POINT_PROCESS"); +static constexpr char POINT_PROCESS[] = "POINT_PROCESS"; /// inbuilt neuron variable for diameter of the compartment -const std::string DIAM_VARIABLE("diam"); +static constexpr char DIAM_VARIABLE[] = "diam"; /// inbuilt neuron variable for area of the compartment -const std::string NODE_AREA_VARIABLE("node_area"); +static constexpr char NODE_AREA_VARIABLE[] = "node_area"; /// similar to node_area but user can explicitly declare it as area -const std::string AREA_VARIABLE("area"); +static constexpr char AREA_VARIABLE[] = "area"; /// inbuilt neuron variable for point process -const std::string POINT_PROCESS_VARIABLE("point_process"); +static constexpr char POINT_PROCESS_VARIABLE[] = "point_process"; /// inbuilt neuron variable for tqitem process -const std::string TQITEM_VARIABLE("tqitem"); +static constexpr char TQITEM_VARIABLE[] = "tqitem"; /// range variable for conductance -const std::string CONDUCTANCE_VARIABLE("_g"); +static constexpr char CONDUCTANCE_VARIABLE[] = "_g"; /// global variable to indicate if table is used -const std::string USE_TABLE_VARIABLE("usetable"); +static constexpr char USE_TABLE_VARIABLE[] = "usetable"; /// range variable when conductance is not used (for vectorized model) -const std::string CONDUCTANCE_UNUSED_VARIABLE("g_unused"); +static constexpr char CONDUCTANCE_UNUSED_VARIABLE[] = "g_unused"; /// range variable for voltage when unused (for vectorized model) -const std::string VOLTAGE_UNUSED_VARIABLE("v_unused"); +static constexpr char VOLTAGE_UNUSED_VARIABLE[] = "v_unused"; /// variable t indicating last execution time of net receive block -const std::string T_SAVE_VARIABLE("tsave"); +static constexpr char T_SAVE_VARIABLE[] = "tsave"; /// shadow rhs variable in neuron thread structure -const std::string NTHREAD_RHS_SHADOW("_shadow_rhs"); +static constexpr char NTHREAD_RHS_SHADOW[] = "_shadow_rhs"; /// shadow d variable in neuron thread structure -const std::string NTHREAD_D_SHADOW("_shadow_d"); +static constexpr char NTHREAD_D_SHADOW[] = "_shadow_d"; /// t variable in neuron thread structure -const std::string NTHREAD_T_VARIABLE("t"); +static constexpr char NTHREAD_T_VARIABLE[] = "t"; /// dt variable in neuron thread structure -const std::string NTHREAD_DT_VARIABLE("dt"); +static constexpr char NTHREAD_DT_VARIABLE[] = "dt"; /// default float variable type -const std::string DEFAULT_FLOAT_TYPE("double"); +static constexpr char DEFAULT_FLOAT_TYPE[] = "double"; /// default local variable type -const std::string DEFAULT_LOCAL_VAR_TYPE("double"); +static constexpr char DEFAULT_LOCAL_VAR_TYPE[] = "double"; /// default integer variable type -const std::string DEFAULT_INTEGER_TYPE("int"); +static constexpr char DEFAULT_INTEGER_TYPE[] = "int"; /// semantic type for area variable -const std::string AREA_SEMANTIC("area"); +static constexpr char AREA_SEMANTIC[] = "area"; /// semantic type for point process variable -const std::string POINT_PROCESS_SEMANTIC("pntproc"); +static constexpr char POINT_PROCESS_SEMANTIC[] = "pntproc"; /// semantic type for pointer variable -const std::string POINTER_SEMANTIC("pointer"); +static constexpr char POINTER_SEMANTIC[] = "pointer"; /// semantic type for core pointer variable -const std::string CORE_POINTER_SEMANTIC("bbcorepointer"); +static constexpr char CORE_POINTER_SEMANTIC[] = "bbcorepointer"; /// semantic type for net send call -const std::string NET_SEND_SEMANTIC("netsend"); +static constexpr char NET_SEND_SEMANTIC[] = "netsend"; /// semantic type for watch statement -const std::string WATCH_SEMANTIC("watch"); +static constexpr char WATCH_SEMANTIC[] = "watch"; /// semantic type for for_netcon statement -const std::string FOR_NETCON_SEMANTIC("fornetcon"); +static constexpr char FOR_NETCON_SEMANTIC[] = "fornetcon"; /// nrn_init method in generated code -const std::string NRN_INIT_METHOD("nrn_init"); +static constexpr char NRN_INIT_METHOD[] = "nrn_init"; /// nrn_alloc method in generated code -const std::string NRN_ALLOC_METHOD("nrn_alloc"); +static constexpr char NRN_ALLOC_METHOD[] = "nrn_alloc"; /// nrn_state method in generated code -const std::string NRN_STATE_METHOD("nrn_state"); +static constexpr char NRN_STATE_METHOD[] = "nrn_state"; /// nrn_cur method in generated code -const std::string NRN_CUR_METHOD("nrn_cur"); +static constexpr char NRN_CUR_METHOD[] = "nrn_cur"; /// nrn_watch_check method in generated c file -const std::string NRN_WATCH_CHECK_METHOD("nrn_watch_check"); +static constexpr char NRN_WATCH_CHECK_METHOD[] = "nrn_watch_check"; /// verbatim name of the variable for nrn thread arguments -const std::string THREAD_ARGS("_threadargs_"); +static constexpr char THREAD_ARGS[] = "_threadargs_"; /// verbatim name of the variable for nrn thread arguments in prototype -const std::string THREAD_ARGS_PROTO("_threadargsproto_"); +static constexpr char THREAD_ARGS_PROTO[] = "_threadargsproto_"; /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index b6a862d142..505a50443a 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -27,8 +27,8 @@ Term::Term(const std::string& expr, const std::string& state) void Term::print() const { - std::cout << "Term [expr, deriv, a, b] : "; - std::cout << expr << ", " << deriv << ", " << a << ", " << b << std::endl; + std::cout << "Term [expr, deriv, a, b] : " << expr << ", " << deriv << ", " << a << ", " << b + << '\n'; } diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 50ad84570c..b8b04d3394 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -32,8 +32,8 @@ namespace newton { * @{ */ -constexpr int MAX_ITER = 1e3; -constexpr double EPS = 1e-12; +static constexpr int MAX_ITER = 1e3; +static constexpr double EPS = 1e-12; /** * \brief Newton method with user-provided Jacobian @@ -77,8 +77,8 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, return -1; } -constexpr double SQUARE_ROOT_ULP = 1e-7; -constexpr double CUBIC_ROOT_ULP = 1e-5; +static constexpr double SQUARE_ROOT_ULP = 1e-7; +static constexpr double CUBIC_ROOT_ULP = 1e-5; /** * \brief Newton method without user-provided Jacobian diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 93db989792..233604a4b6 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -39,7 +39,7 @@ namespace units { */ /// Maximum number of dimensions of units (maximum number of base units) -static const int MAX_DIMS = 10; +static constexpr int MAX_DIMS = 10; /** * \class Unit @@ -197,7 +197,7 @@ class Prefix { /// \{ /// Default constructor for Prefix - Prefix() = default; + Prefix() = delete; /// Constructor that instantiates a Prefix with its name and factor Prefix(std::string name, const std::string& factor); diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 5823f93ac6..6b2f21a945 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -159,7 +159,7 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression& nod return; } - const std::string nmodl_before = to_nmodl(binary_expr); + const std::string& nmodl_before = to_nmodl(binary_expr); /// compute the value of expression auto value = compute(get_value(lhs), op, get_value(rhs)); @@ -173,7 +173,7 @@ void ConstantFolderVisitor::visit_wrapped_expression(ast::WrappedExpression& nod node.set_expression(std::make_shared(stringutils::to_string(value))); } - const std::string nmodl_after = to_nmodl(node.get_expression()); + const std::string& nmodl_after = to_nmodl(node.get_expression()); logger->debug("ConstantFolderVisitor : expression {} folded to {}", nmodl_before, nmodl_after); } diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 53bc5bcf4b..7721a09a20 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -55,9 +55,10 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo // create statements to alter value of dt within DERIVATIVE block // TODO: make sure dt_tmp_var_name variable name does not clash - std::string dt_tmp_var_name = codegen::naming::NTHREAD_DT_VARIABLE + "_saved_value"; + std::string dt_tmp_var_name = std::string(codegen::naming::NTHREAD_DT_VARIABLE) + + "_saved_value"; std::string dt_save = dt_tmp_var_name + " = " + codegen::naming::NTHREAD_DT_VARIABLE; - std::string dt_assign = codegen::naming::NTHREAD_DT_VARIABLE + " = "; + std::string dt_assign = std::string(codegen::naming::NTHREAD_DT_VARIABLE) + " = "; std::string dt_restore = dt_assign + dt_tmp_var_name; if (steadystate_method == codegen::naming::SPARSE_METHOD) { dt_assign += "{:.16g}"_format(STEADYSTATE_SPARSE_DT); diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 57f035bb72..134f830af2 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -4,8 +4,8 @@ NEURON { SUFFIX cadyn USEION ca READ cai,ica WRITE cai RANGE ca - GLOBAL depth,cainf,taur - RANGE var + GLOBAL depth,cainf,taur + RANGE var } From 8374b5662ebff47d6c4b0137f20f15649ec62193 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 19 Feb 2021 11:52:59 +0100 Subject: [PATCH 334/871] sympy replace inplace (BlueBrain/nmodl#495) * Add sympy_replace_solution_visitor The visitor addresses diff_equations, linEquation, nonLinEquation. It receives the ast from sympy_solver_visitor. From python it receives: - the pre_solve_statements - the tmp_statements (only if --cse is selected) - the solution_statements In possibly two runs it tries to replace the solutions in place updating the tmp_statements when needed. In case of big systems the Newton method is employed and a system J x = f must be repeatedly solved. In this case the visitor must provide the J matrix and f vector. It still tries to replace the solutions line by line: 1 row of the J matrix and one element of the f vector. More details in the visitor doxygen NMODL Repo SHA: BlueBrain/nmodl@f48a3b8e83ea427ab991205e68b72a5558586022 --- nmodl/ode.py | 71 ++- src/nmodl/visitors/CMakeLists.txt | 2 + .../sympy_replace_solutions_visitor.cpp | 460 ++++++++++++++++ .../sympy_replace_solutions_visitor.hpp | 502 ++++++++++++++++++ src/nmodl/visitors/sympy_solver_visitor.cpp | 149 +++--- src/nmodl/visitors/visitor_utils.cpp | 36 ++ src/nmodl/visitors/visitor_utils.hpp | 14 + .../transpiler/unit/visitor/sympy_solver.cpp | 425 +++++++++++++-- 8 files changed, 1562 insertions(+), 97 deletions(-) create mode 100644 src/nmodl/visitors/sympy_replace_solutions_visitor.cpp create mode 100644 src/nmodl/visitors/sympy_replace_solutions_visitor.hpp diff --git a/nmodl/ode.py b/nmodl/ode.py index b9db294c1f..0cf7cebee0 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -158,6 +158,60 @@ def _sympify_eqs(eq_strings, state_vars, vars): ] return eqs, sympy_state_vars, sympy_vars +def _interweave_eqs(F, J): + """Interweave F and J equations so that they are printed in code + rowwise from the equation J x = F. For example: + + F = [F_0, + F_1, + F_2] + + (Jmat is not the actual J in the argument, it is here to the sake of + clarity) + Jmat = [J_0, J_3, J_6 + J_1, J_4, J_7 + J_2, J_5, J_8] + (J is the actual input with the following ordering) + J = [J_0, + J_3, + J_6, + J_1, + J_4, + J_7, + J_2, + J_5, + J_8] + + What we want is: + code = [F_0, + J_0, + J_3, + J_6, + F_1, + J_1, + J_4, + J_7, + F_2, + J_2, + J_5, + J_8] + + Args: + F: F vector + J: J matrix represented as a vector (rowwise) + + Returns: + code: F and J interweaved in one vector + """ + code = [] + n = len(F) + for i, expr in enumerate(F): + code.append(expr) + for j in range(i * n, (i+1) * n): + code.append(J[j]) + + return code + def solve_lin_system(eq_strings, vars, constants, function_calls, small_system=False, do_cse=False): """Solve linear system of equations, return solution as C code. @@ -216,13 +270,17 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, small_system=F matJ, vecF = sp.linear_eq_to_matrix(eqs, state_vars) # construct vector F + vecFcode = [] for i, expr in enumerate(vecF): - code.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf())}") + vecFcode.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf())}") # construct matrix J + vecJcode = [] for i, expr in enumerate(matJ): # todo: fix indexing to be ascending order flat_index = matJ.rows * (i % matJ.rows) + (i // matJ.rows) - code.append(f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}") + vecJcode.append(f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}") + # interweave + code = _interweave_eqs(vecFcode, vecJcode) return code, new_local_vars @@ -252,15 +310,18 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): X_vec_map = {x: sp.symbols(f"X[{i}]") for i, x in enumerate(state_vars)} - code = [] + vecFcode = [] for i, eq in enumerate(eqs): - code.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf())}") + vecFcode.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf())}") + vecJcode = [] for i, jac in enumerate(jacobian): # todo: fix indexing to be ascending order flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) - code.append( + vecJcode.append( f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}" ) + # interweave + code = _interweave_eqs(vecFcode, vecJcode) return code diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index cd13353a41..c437114e48 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -37,6 +37,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_replace_solutions_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/sympy_replace_solutions_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp ${CMAKE_CURRENT_SOURCE_DIR}/units_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/units_visitor.hpp diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp new file mode 100644 index 0000000000..5c78403336 --- /dev/null +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -0,0 +1,460 @@ +/************************************************************************* + * Copyright (C) 2018-2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "visitors/sympy_replace_solutions_visitor.hpp" +#include "visitors/lookup_visitor.hpp" + +#include "ast/all.hpp" +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +using namespace fmt::literals; + +/** + * \details SympyReplaceSolutionsVisitor tells us that a new equation appear and, depending where + * it is located, it can determine if it is part of the main system of equations or is something + * else. Every time we are out of the system and we print a new equation that is in the system + * we update the counter. \ref in_system follows, with lag, \ref is_in_system and every time + * they are false and true respectively we detect a switch. + * + * \param is_in_system is a bool provided from outside that tells us if a new equation is indeed + * part of the main system of equations + */ +void SympyReplaceSolutionsVisitor::InterleavesCounter::new_equation(const bool is_in_system) { + n_interleaves += (!in_system && is_in_system); // count an interleave only if in_system == + // false and is_in_system == true + in_system = is_in_system; // update in_system +} + +SympyReplaceSolutionsVisitor::SympyReplaceSolutionsVisitor( + const std::vector& pre_solve_statements, + const std::vector& solutions, + const std::unordered_set& to_be_removed, + const ReplacePolicy policy, + const size_t n_next_equations) + : pre_solve_statements(pre_solve_statements.begin(), pre_solve_statements.end(), 2) + , to_be_removed(&to_be_removed) + , policy(policy) + , n_next_equations(n_next_equations) + , replaced_statements_range(-1, -1) { + const auto ss_tmp_delimeter = + std::find_if(solutions.begin(), solutions.end(), [](const std::string& statement) { + return statement.substr(0, 3) != "tmp"; + }); + tmp_statements = StatementDispenser(solutions.begin(), ss_tmp_delimeter, -1); + solution_statements = StatementDispenser(ss_tmp_delimeter, solutions.end(), -1); + + replacements.clear(); + is_top_level_statement_block = true; +} + + +void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& node) { + const bool current_is_top_level_statement_block = + is_top_level_statement_block; // we mark it down since we are going to change it for + // visiting the children + is_top_level_statement_block = false; + + if (current_is_top_level_statement_block) { + logger->debug("SympyReplaceSolutionsVisitor :: visit statements. Matching policy: {}", + (policy == ReplacePolicy::VALUE ? "VALUE" : "GREEDY")); + interleaves_counter = InterleavesCounter(); + node.visit_children(*this); + + if (!solution_statements.tags.empty() && policy == ReplacePolicy::VALUE) { + logger->debug( + "SympyReplaceSolutionsVisitor :: not all solutions were replaced. Policy: GREEDY"); + interleaves_counter = InterleavesCounter(); + policy = ReplacePolicy::GREEDY; + node.visit_children(*this); + + if (interleaves_counter.n() > 0) { + logger->warn( + "SympyReplaceSolutionsVisitor :: Found ambiguous system of equations " + "interleaved with {} assignment statements. I do not know what equations go " + "before and what " + "equations go after the assignment statements. Either put all the equations " + "that need to be solved " + "in the form: x = f(...) and with distinct variable assignments or do not " + "interleave the system with assignments.", + interleaves_counter.n()); + } + } + } else { + node.visit_children(*this); + } + + const auto& old_statements = node.get_statements(); + + ast::StatementVector new_statements; + new_statements.reserve(2 * old_statements.size()); + for (const auto& old_statement: old_statements) { + const auto& replacement_ptr = replacements.find(old_statement); + if (replacement_ptr != replacements.end()) { + if (replaced_statements_range.first == -1) { + replaced_statements_range.first = new_statements.size(); + } + + new_statements.insert(new_statements.end(), + replacement_ptr->second.begin(), + replacement_ptr->second.end()); + + replaced_statements_range.second = new_statements.size(); + + logger->debug("SympyReplaceSolutionsVisitor :: erasing {}", to_nmodl(old_statement)); + for (const auto& replacement: replacement_ptr->second) { + logger->debug("SympyReplaceSolutionsVisitor :: adding {}", to_nmodl(replacement)); + } + } else if (to_be_removed == nullptr || + to_be_removed->find(&(*old_statement)) == to_be_removed->end()) { + logger->debug("SympyReplaceSolutionsVisitor :: found {}, nothing to do", + to_nmodl(old_statement)); + new_statements.emplace_back(std::move(old_statement)); + } else { + logger->debug("SympyReplaceSolutionsVisitor :: erasing {}", to_nmodl(old_statement)); + } + } + + if (current_is_top_level_statement_block) { + if (!solution_statements.tags.empty()) { + std::ostringstream ss; + for (const auto ii: solution_statements.tags) { + ss << to_nmodl(solution_statements.statements[ii]) << '\n'; + } + throw std::runtime_error( + "Not all solutions were replaced! Sympy returned {} equations but I could not find " + "a place " + "for all of them. In particular, the following equations remain to be replaced " + "somewhere:\n{}This is " + "probably a bug and I invite you to report it to a developer. Possible causes:\n" + " - I did not do a GREEDY pass and some solutions could not be replaced by VALUE\n " + "sympy " + "returned more equations than what we expected\n - There is a bug in the GREEDY " + "pass\n - some " + "solutions were replaced but not untagged"_format( + solution_statements.statements.size(), ss.str())); + } + + if (replaced_statements_range.first == -1) { + replaced_statements_range.first = new_statements.size(); + } + if (replaced_statements_range.second == -1) { + replaced_statements_range.second = new_statements.size(); + } + } + + node.set_statements(std::move(new_statements)); +} + +void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( + const ast::Node& node, + const std::shared_ptr& get_lhs(const ast::Node& node), + const std::shared_ptr& get_rhs(const ast::Node& node)) { + interleaves_counter.new_equation(true); + + const auto& statement = std::static_pointer_cast( + node.get_parent()->get_shared_ptr()); + + // do not visit if already marked + if (replacements.find(statement) != replacements.end()) { + return; + } + + + switch (policy) { + case ReplacePolicy::VALUE: { + const auto dependencies = statement_dependencies(get_lhs(node), get_rhs(node)); + const auto& key = dependencies.first; + const auto& vars = dependencies.second; + + if (solution_statements.is_var_assigned_here(key)) { + logger->debug("SympyReplaceSolutionsVisitor :: marking for replacement {}", + to_nmodl(statement)); + + ast::StatementVector new_statements; + + pre_solve_statements.emplace_back_all_tagged_statements(new_statements); + tmp_statements.emplace_back_all_tagged_statements(new_statements); + solution_statements.try_emplace_back_tagged_statement(new_statements, key); + + replacements.emplace(statement, new_statements); + } + break; + } + case ReplacePolicy::GREEDY: { + logger->debug("SympyReplaceSolutionsVisitor :: marking for replacement {}", + to_nmodl(statement)); + + ast::StatementVector new_statements; + + pre_solve_statements.emplace_back_all_tagged_statements(new_statements); + tmp_statements.emplace_back_all_tagged_statements(new_statements); + solution_statements.emplace_back_next_tagged_statements(new_statements, n_next_equations); + + replacements.emplace(statement, new_statements); + break; + } + } +} + + +void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { + logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); + auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { + return static_cast(node).get_expression()->get_lhs(); + }; + + auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { + return static_cast(node).get_expression()->get_rhs(); + }; + + try_replace_tagged_statement(node, get_lhs, get_rhs); +} + +void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { + logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); + auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { + return static_cast(node).get_left_linxpression(); + }; + + auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { + return static_cast(node).get_left_linxpression(); + }; + + try_replace_tagged_statement(node, get_lhs, get_rhs); +} + + +void SympyReplaceSolutionsVisitor::visit_non_lin_equation(ast::NonLinEquation& node) { + logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); + auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { + return static_cast(node).get_lhs(); + }; + + auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { + return static_cast(node).get_rhs(); + }; + + try_replace_tagged_statement(node, get_lhs, get_rhs); +} + + +void SympyReplaceSolutionsVisitor::visit_binary_expression(ast::BinaryExpression& node) { + logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); + if (node.get_op().get_value() == ast::BinaryOp::BOP_ASSIGN && node.get_lhs()->is_var_name()) { + interleaves_counter.new_equation(false); + + const auto& var = + std::static_pointer_cast(node.get_lhs())->get_name()->get_node_name(); + pre_solve_statements.tag_dependant_statements(var); + tmp_statements.tag_dependant_statements(var); + } +} + + +SympyReplaceSolutionsVisitor::StatementDispenser::StatementDispenser( + const std::vector::const_iterator& statements_str_beg, + const std::vector::const_iterator& statements_str_end, + const int error_on_n_flushes) + : statements(create_statements(statements_str_beg, statements_str_end)) + , error_on_n_flushes(error_on_n_flushes) { + tag_all_statements(); + build_maps(); +} + + +/** + * \details CHere we construct a map variable -> affected equations. In other words this map tells + * me what equations need to be updated when I change a particular variable. To do that we build a a + * graph of dependencies var -> vars and in the mean time we reduce it to the root variables. This + * is ensured by the fact that the tmp variables are sorted so that the next tmp variable may depend + * on the previous one. Since it is a relation of equivalence (if an equation depends on a variable, + * it needs to be updated if the variable changes), we build the two maps at the same time. + * + * An example: + * + * \code{.mod} + * tmp0 = x + a + * tmp1 = tmp0 + b + * tmp2 = y + * \endcode + * + * dependency_map should be (the order of the equation is unimportant since we are building + * a map): + * + * - tmp0 : x, a + * - tmp1 : x, a, b + * - tmp2 : y + * + * and the var2statement map should be (the order of the following equations is unimportant + * since we are building a map. The number represents the index of the original equations): + * + * - x : 0, 1 + * - y : 2 + * - a : 0, 1 + * - b : 1 + * + */ +void SympyReplaceSolutionsVisitor::StatementDispenser::build_maps() { + for (size_t ii = 0; ii < statements.size(); ++ii) { + const auto& statement = statements[ii]; + + if (statement->is_expression_statement()) { + const auto& e_statement = + std::static_pointer_cast(statement)->get_expression(); + if (e_statement->is_binary_expression()) { + const auto& bin_exp = std::static_pointer_cast(e_statement); + const auto& dependencies = statement_dependencies(bin_exp->get_lhs(), + bin_exp->get_rhs()); + + const auto& key = dependencies.first; + const auto& vars = dependencies.second; + if (!key.empty()) { + var2statement.emplace(key, ii); + for (const auto& var: vars) { + const auto& var_already_inserted = dependency_map.find(var); + if (var_already_inserted != dependency_map.end()) { + dependency_map[key].insert(var_already_inserted->second.begin(), + var_already_inserted->second.end()); + for (const auto& root_var: var_already_inserted->second) { + var2dependants[root_var].insert(ii); + } + } else { + dependency_map[key].insert(var); + var2dependants[var].insert(ii); + } + } + } + } + } + } + + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: var2dependants map"); + for (const auto& entry: var2dependants) { + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: var `{}` used in:", + entry.first); + for (const auto ii: entry.second) { + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: -> {}", + to_nmodl(statements[ii])); + } + } + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: var2statement map"); + for (const auto& entry: var2statement) { + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: var `{}` defined in:", + entry.first); + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: -> {}", + to_nmodl(statements[entry.second])); + } +} + +bool SympyReplaceSolutionsVisitor::StatementDispenser::try_emplace_back_tagged_statement( + ast::StatementVector& new_statements, + const std::string& var) { + auto ptr = var2statement.find(var); + bool emplaced = false; + if (ptr != var2statement.end()) { + const auto ii = ptr->second; + const auto tag_ptr = tags.find(ii); + if (tag_ptr != tags.end()) { + new_statements.emplace_back(statements[ii]->clone()); + tags.erase(tag_ptr); + emplaced = true; + + logger->debug( + "SympyReplaceSolutionsVisitor::StatementDispenser :: adding to replacement rule {}", + to_nmodl(statements[ii])); + } else { + logger->error( + "SympyReplaceSolutionsVisitor::StatementDispenser :: tried adding to replacement " + "rule {} but statement is not " + "tagged", + to_nmodl(statements[ii])); + } + } + return emplaced; +} + +size_t SympyReplaceSolutionsVisitor::StatementDispenser::emplace_back_next_tagged_statements( + ast::StatementVector& new_statements, + const size_t n_next_statements) { + size_t counter = 0; + for (size_t next_statement_ii = 0; + next_statement_ii < statements.size() && counter < n_next_statements; + ++next_statement_ii) { + const auto tag_ptr = tags.find(next_statement_ii); + if (tag_ptr != tags.end()) { + logger->debug( + "SympyReplaceSolutionsVisitor::StatementDispenser :: adding to replacement rule {}", + to_nmodl(statements[next_statement_ii])); + new_statements.emplace_back(statements[next_statement_ii]->clone()); + tags.erase(tag_ptr); + ++counter; + } + } + return counter; +} + +size_t SympyReplaceSolutionsVisitor::StatementDispenser::emplace_back_all_tagged_statements( + ast::StatementVector& new_statements) { + for (const auto ii: tags) { + new_statements.emplace_back(statements[ii]->clone()); + logger->debug( + "SympyReplaceSolutionsVisitor::StatementDispenser :: adding to replacement rule {}", + to_nmodl(statements[ii])); + } + + n_flushes += (!tags.empty()); + if (error_on_n_flushes > 0 && n_flushes >= error_on_n_flushes) { + throw std::runtime_error( + "SympyReplaceSolutionsVisitor::StatementDispenser :: State variable assignment(s) " + "interleaved in system " + "of " + "equations/differential equations. It is not allowed due to possible numerical " + "instability and undefined " + "behavior. Erase the assignment statement(s) or move them before/after the" + " set of equations/differential equations."); + } + + const auto n_replacements = tags.size(); + + tags.clear(); + + return n_replacements; +} + +size_t SympyReplaceSolutionsVisitor::StatementDispenser::tag_dependant_statements( + const std::string& var) { + auto ptr = var2dependants.find(var); + size_t n = 0; + if (ptr != var2dependants.end()) { + for (const auto ii: ptr->second) { + const auto pos = tags.insert(ii); + if (pos.second) { + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: tagging {}", + to_nmodl(statements[ii])); + } + ++n; + } + } + return n; +} + +void SympyReplaceSolutionsVisitor::StatementDispenser::tag_all_statements() { + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: tagging all statements"); + for (size_t i = 0; i < statements.size(); ++i) { + tags.insert(i); + logger->debug("SympyReplaceSolutionsVisitor::StatementDispenser :: tagging {}", + to_nmodl(statements[i])); + } +} + + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp new file mode 100644 index 0000000000..793020e5a6 --- /dev/null +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -0,0 +1,502 @@ +/************************************************************************* + * Copyright (C) 2018-2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::SympyReplaceSolutionsVisitor + */ + +#include "visitors/ast_visitor.hpp" + +#include +#include +#include +#include +#include +#include + +namespace nmodl { +namespace visitor { + +/** + * @addtogroup visitor_classes + * @{ + */ + + +/** + * \class SympyReplaceSolutionsVisitor + * \brief Replace statements in \p node with pre_solve_statements, tmp_statements, and solutions + * + * The goal is to replace statements with \ref solutions in place. In this way we can allow (to + * some extent) the use of control flow blocks and assignments. \ref pre_solve_statements are added + * in front of the replaced statements in case their variable needs updating. \ref + StatementDispenser + * keeps track of what needs updating. Let's start with some nomenclature: + * + * - statement: a line in the .mod file. It can be a diff_eq_expression, binary_expression, or + * linEquation + * - old_Statement: line in the staementBlock that must be replaced with the solution + * - solution_statements/ new_statement: a nmodl-statement (always binary expression) provided by + sympy that + * assigns a variable + * - pre_solve_Statements: statements that update the variables (i.e. x = old_x) + * - tmp_statements: assignment of temporary variables in the solution generated by sympy in case + * --cse. (i.e. \f tmp = f + * (...) \f + * + * We employ a multi-step approach: + * + * - try to replace the old_statements (not binary_expressions) and in "assignment form: \f x = + * f(...) \f" with the corresponding solution matching by variable (i.e. x in \f x = f(...) \f) + * - try to replace the old_Statements with a greedy approach. When we find a + * diff_eq_expression/linEquation that needs replacing we take the next solution that was not yet + * used + * - add all the remaining solutions at the end + * + * Let's finish with an example (that are usually better than blabbling around). + * + * Imagine we have this derivative block in the ast (before SympyReplaceSolutionsVisitor passes): + * + * \code{.mod} + * DERIVATIVE d { + * LOCAL a, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7 + * b = 1 + * x' = x + y + a + b + * if ( x == 0) { + * a = a + 1 + * # x = x + 1 // this would be an error. Explained later + * } + * y' = x + y + a + * z' = y + a + * x = x + 1 + * } + * \endcode + * + * where SympySolverVisitor already added variables in the LOCAL declaration. + * + * Sympy solver visitor also provides: + * + * - pre-solve statements: + * + * \code{.mod} + * old_x = x + * old_y = y + * old_z = z + * \endcode + * + * - tmp statements: + * + * \code{.mod} + * tmp0 = 2.0*dt + * tmp1 = 1.0/(tmp0-1.0) + * tmp2 = pow(dt, 2) + * tmp3 = b*tmp2 + * tmp4 = dt*old_x + * tmp5 = a*dt + * tmp6 = dt*old_y + * tmp7 = tmp5+tmp6 + * \endcode + * + * - solutions: + * + * \code{.mod} + * x = -tmp1*(b*dt+old_x-tmp3-tmp4+tmp7) + * y = -tmp1*(old_y+tmp3+tmp4+tmp5-tmp6) + * z = -tmp1*(-a*tmp2+b*pow(dt, 3)+old_x*tmp2-old_y*tmp2-old_z*tmp0+old_z+tmp7) + * \endcode + * + * SympySolveVisitor works in this way: + * + * \code{.mod} + * DERIVATIVE d { // nothing to do + * + * LOCAL a, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7 // nothing to do + * + * b = 1 // initial statement, nothing to do + * + * x' = x + y + a + b -> old_x = x // before printing this solution let's + * old_y = y // flush the pre solve statements + * old_z = z // mark down that we did this + * + * tmp0 = 2.0*dt // we also flush the tmp statements + * tmp1 = 1.0/(tmp0-1.0) + * tmp2 = pow(dt, 2) + * tmp3 = b*tmp2 + * tmp4 = dt*old_x + * tmp5 = a*dt + * tmp6 = dt*old_y + * tmp7 = tmp5+tmp6 + * + * x = -tmp1*(b*dt+old_x-tmp3-tmp4+tmp7) // finally, the solution + * + * if ( x == 0) { // nothing to do + * a = a + 1 // mark down the tmp statements and pre solve statements + * // that contain 'a' in the rhs as in need for an update + * + * // x = x + 1 // the same as before but for 'x'. In particular a pre + * // solve statement is marked for updating. This will + * // produce an error later in the code + * } // nothing to do + * + * y' = x + y + a -> // old_x = x // here, if 'x = x + 1' were not commented, the + * // code would try to print this line an throw an + * // error since the pre solve statements were already + * // printed once + * tmp5 = a*dt // flush the tmp statements that need updating. In + * tmp7 = tmp5+tmp6 // our example, all the tmp statements that + * // directly or indirectly depend on a + * // for performance, we print only the ones that need updating + * + * z' = y + a -> z = -tmp1*(-a*tmp2+b*pow(dt, 3)+old_x*tmp2-old_y*tmp2-old_z*tmp0+old_z+tmp7) + * // nothing is marked for updating (among pre solve statements and tmp + * // statements): just print the solution + * + * x = x + 1 // nothing to do + * } // nothing to do + * \endcode + * + * Last notes: + * + * For linEquations or NonLinEquations association of the solution with a particular statement could + * be impossible. For example \f ~ x + y = 0 \f does not have a simple variable in the lhs. Thus, an + * association with a particular solution statement is not possible. Thus we do 2 runs where we + * first match everything we can by value and then we associate everything we can in a greedy way. + * + * For large system of equations the code sets up the J matrix and F vector to be sent to eigen for + * the Newton method which will solve a bunch of J x = F for each time step). In this case it is + always safe to + * replace greedy because sympy does not sort the equations in the matrix/vector. In addition, cse + is disabled by + * default. Thus, there is a 1:1 correspondence of an equation of the original mod file and a row of + the matrix and + * an element of F. So if we have: + * + * \code{.mod} + * LINEAR lin { + * ~ x = ... + * a = a + 1 + * ~ y = ... + * ~ z = ... + * ~ w = ... + * } + * \endcode + * + * We get the vector F and matrix J + * + * \code + * F = [0, J = [0, 4, 8, 12, + * 1, 1, 5, 9, 13, + * 2, 2, 6, 10, 14, + * 3] 3, 7, 11, 15] + * \endcode + * + * Where the numbers indicate their column-wise index. The solution replacement becomes: + * + * \code + * ~ x = ... -> F[0] = ... + * J[0] = ... + * J[4] = ... + * J[8] = ... + * J[12] = ... + * a = a + 1 + * ~ y = ... -> ... + * \endcode + * + */ +class SympyReplaceSolutionsVisitor: public AstVisitor { + public: + enum class ReplacePolicy { + VALUE = 0, //!< Replace statements matching by lhs varName + GREEDY = 1, //!< Replace statements greedily + }; + /// Empty ctor + SympyReplaceSolutionsVisitor() = delete; + + /// Default constructor + SympyReplaceSolutionsVisitor(const std::vector& pre_solve_statements, + const std::vector& solutions, + const std::unordered_set& to_be_removed, + const ReplacePolicy policy, + size_t n_next_equations); + + /// idx (in the new statementVector) of the first statement that was added. -1 if nothing was + /// added + inline int replaced_statements_begin() const { + return replaced_statements_range.first; + } + /// idx (in the new statementVector) of the last statement that was added. -1 if nothing was + /// added + inline int replaced_statements_end() const { + return replaced_statements_range.second; + } + + void visit_statement_block(ast::StatementBlock& node) override; + void visit_diff_eq_expression(ast::DiffEqExpression& node) override; + void visit_lin_equation(ast::LinEquation& node) override; + void visit_non_lin_equation(ast::NonLinEquation& node) override; + void visit_binary_expression(ast::BinaryExpression& node) override; + + + private: + /** \brief Try to replace a statement + * + * \param node it can be Diff_Eq_Expression/LinEquation/NonLinEquation + * \param get_lhs method with witch we may get the lhs (in case we need it) + * \param get_rhs method with witch we may get the rhs (in case we need it) + */ + void try_replace_tagged_statement( + const ast::Node& node, + const std::shared_ptr& get_lhs(const ast::Node& node), + const std::shared_ptr& get_rhs(const ast::Node& node)); + + /** + * \struct InterleavesCounter + * \brief Count interleaves of assignment statement inside the system of equations + * + * Example: + * + * \code + * \\ not in the system, n = 0, is_in_system_ = false + * ~ x + y = 0 \\ system, in_system_ switch false -> true, n = 1 + * ~ y = a + 1 \\ system, no switch, nothing to do + * a = ... \\ no system, in_system_ switch true -> false, nothing to do + * ~ z = x + y + z \\ system, in_system_ switch false -> true, n = 2 + * \endcode + * + * Number of interleaves: n-1 = 1 + */ + struct InterleavesCounter { + /// Count interleaves defined as a switch false -> true for \ref in_system_ + void new_equation(const bool is_in_system); + + /// Number of interleaves. We need to remove the first activation of the switch except if + /// there were no switches + inline size_t n() const { + return n_interleaves == 0 ? 0 : n_interleaves - 1; + } + + private: + /** + * \brief Number of interleaves of assignment statements in between equations of the system + * of equations + * + * This is equivalent to the number of switches false -> true of \ref in_system_ minus the + * very first one (if the system exists). + */ + size_t n_interleaves = 0; + + /// Bool that keeps track if just wrote an equation of the system of equations (true) or not + /// (false) + bool in_system = false; + }; + + + /** + * \struct StatementDispenser + * \brief Sorts and maps statements to variables keeping track of what needs updating + * + * This is a multi-purpose object that: + * + * - keeps track of what was already updated + * - decides what statements need updating in case there was a variable assignment (i.e. \f a = + * 3 \f) + * - builds the statements from a vector of strings + * + */ + struct StatementDispenser { + /// Empty ctor + StatementDispenser() = default; + + /// Standard ctor + StatementDispenser(const std::vector::const_iterator& statements_str_beg, + const std::vector::const_iterator& statements_str_end, + const int error_on_n_flushes); + + /// Construct the maps \ref var2dependants_, \ref var2statement_ and \ref dependency_map_ + /// for easy access and classification of the statements + void build_maps(); + + /// Check if one of the statements assigns this variable (i.e. \f x' = f(x, y, x) \f) and is + /// still tagged + inline bool is_var_assigned_here(const std::string& var) const { + const auto it = var2statement.find(var); + return it != var2statement.end() && tags.find(it->second) != tags.end(); + } + + /** + * \brief Look for \p var in \ref var2statement_ and emplace back that statement in \p + * new_statements + * + * If there is no \p var key in \ref var2statement_, return false + */ + bool try_emplace_back_tagged_statement(ast::StatementVector& new_statements, + const std::string& var); + + + /// Emplace back the next \p n_next_statements solutions in \ref statements that is marked + /// for updating in \ref tags_ + size_t emplace_back_next_tagged_statements(ast::StatementVector& new_statements, + const size_t n_next_statements); + + /// Emplace back all the statements that are marked for updating in \ref tags_ + size_t emplace_back_all_tagged_statements(ast::StatementVector& new_statements); + + /** + * \brief Tag all the statements that depend on \p var for updating + * + * This is necessary when an assignment has invalidated this variable + */ + size_t tag_dependant_statements(const std::string& var); + + /// Mark that all the statements need updating (probably unused) + void tag_all_statements(); + + /** + * \brief x (key) : f(a, b, c, ...) (values) + * + * Given a certain variable (map key) we get all the (root) variables on which this variable + * depends on (values) + * + * For example, imagine we have these assignments: + * + * \code + * tmp = b + * x = a + tmp + exp(a) + * \endcode + * + * \ref dependency_map_ is: + * + * - tmp : b + * - x : a, b + * + */ + std::unordered_map> dependency_map; + + /** + * \brief a (key) : f(..., a, ...), g(..., a, ...), h(..., a, ...), ... (values) + * + * This the "reverse" of \ref dependency_map_. Given a certain variable it provides + * the statements that depend on it. It is a set because we want to print them in + * order and we do not want duplicates. The value is the index in \ref statements_ or \ref + * tags_ + * + * For example: + * + * \code + * tmp = b // statement 0 + * x = a + tmp + exp(a) // statement 1 + * \endcode + * + * \ref var2dependants_ is: + * + * - a : 1 + * - b : 0, 1 + * + */ + std::unordered_map> var2dependants; + + /** + * \brief a (key) : a = f(...) (value) + * + * Given a certain variable we get the statement where that variable is defined + * + * For example: + * + * \code{.mod} + * tmp = b // statement 0 + * x = a + tmp + exp(a) // statement 1 + * \endcode + * + * \ref var2dependants_ is: + * + * - tmp : 0 + * - x : 1 + * + */ + std::unordered_map var2statement; + + /// Vector of statements + std::vector> statements; + + /** + * \brief Keeps track of what statements need updating + * + * The elements of this set are the indexes of the \ref statements_ vector that need + * updating. It is a set because we need to be able to easily find them by value and we need + * them ordered to pick "the next one" + */ + std::set tags; + + /** + * \brief Max number of times a statement was printed using an \ref + * emplace_all_back_statement command + * + * This is useful to check if, during updates, a variable was assigned. + * + * For example: + * + * \code{.mod} + * x' = a + * x = a + 1 + * y' = b + * \endcode + * + * In this sequence of statements \f x \f was assigned within variable updates. This + * sequence of statements could lead to instability/wrong results for derivimplicit methods. + * Better to prevent this entirely. It can still be assigned at the end/beginning + */ + size_t n_flushes = 0; + + /// Emit error when \ref n_flushes_ reaches this number. -1 disables the error entirely + int error_on_n_flushes; + }; + + /// Update state variable statements (i.e. \f old_x = x \f) + StatementDispenser pre_solve_statements; + + /// tmp statements that appear with --cse (i.e. \f tmp0 = a \f) + StatementDispenser tmp_statements; + + /// solutions that we want to replace + StatementDispenser solution_statements; + + /** + * \brief Replacements found by the visitor + * + * The keys are the old_statements that need replacing with the new ones (the + * value). Since there are \ref pre_solve_statements_ and \ref tmp_statements; it is in general + * a replacement of 1 : n statements + */ + std::unordered_map, ast::StatementVector> replacements; + + /// Used to notify to visit_statement_block was called by the user (or another visitor) or + /// re-called in a nested block + bool is_top_level_statement_block = true; + + /// Replacement policy used by the various visitors + ReplacePolicy policy; + + /// Number of solutions that match each old_statement with the greedy policy + size_t n_next_equations; + + /// group of old statements that need replacing + const std::unordered_set* to_be_removed; + + /// counts how many times the solution statements are interleaved with assignment expressions + InterleavesCounter interleaves_counter; + + /// {begin index, end index} of the added statements. -1 means that it is invalid + std::pair replaced_statements_range = {-1, -1}; +}; + +/** @} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index d75c82b6f5..6f579ecd5b 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -6,7 +6,7 @@ *************************************************************************/ #include "visitors/sympy_solver_visitor.hpp" - +#include "visitors/sympy_replace_solutions_visitor.hpp" #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" @@ -111,7 +111,7 @@ ast::StatementVector::const_iterator SympySolverVisitor::get_solution_location_i } /** - * Check if provided statemenet is local variable declaration statement + * Check if provided statement is local variable declaration statement * @param statement AST node representing statement in the MOD file * @return True if statement is local variable declaration else False * @@ -175,59 +175,95 @@ void SympySolverVisitor::construct_eigen_solver_block( solutions_filtered = filter_string_vector(solutions_filtered, "Jm[", unique_Jm + "["); solutions_filtered = filter_string_vector(solutions_filtered, "F[", unique_F + "["); - // find out where to insert solution in statement block - const auto& statements = block_with_expression_statements->get_statements(); - auto it = get_solution_location_iterator(statements); - // insert pre-solve statements below last linear eq in block - for (const auto& statement: pre_solve_statements) { - logger->debug("SympySolverVisitor :: -> adding statement: {}", statement); - it = block_with_expression_statements->insert_statement(it, create_statement(statement)); - ++it; + for (const auto& sol: solutions_filtered) { + logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); } - // make Eigen vector <-> state var assignments - std::vector setup_x_eqs; - std::vector update_state_eqs; + + std::vector pre_solve_statements_and_setup_x_eqs(pre_solve_statements); + std::vector update_statements; for (int i = 0; i < state_vars.size(); i++) { - auto statement = state_vars[i] + " = " + unique_X + "[" + std::to_string(i) + "]"; - auto rev_statement = unique_X + "[" + std::to_string(i) + "] = " + state_vars[i]; - update_state_eqs.push_back(statement); - setup_x_eqs.push_back(rev_statement); - logger->debug("SympySolverVisitor :: setup_", unique_X, ": {}", rev_statement); - logger->debug("SympySolverVisitor :: update_state: {}", statement); - } + auto update_state = state_vars[i] + " = " + unique_X + "[" + std::to_string(i) + "]"; + auto setup_x = unique_X + "[" + std::to_string(i) + "] = " + state_vars[i]; - for (const auto& sol: solutions_filtered) { - logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); + pre_solve_statements_and_setup_x_eqs.push_back(setup_x); + update_statements.push_back(update_state); + logger->debug("SympySolverVisitor :: setup_", unique_X, ": {}", setup_x); + logger->debug("SympySolverVisitor :: update_state: {}", update_state); } - // statements after last diff/linear/non-linear eq statement go into finalize_block - ast::StatementVector finalize_statements{it, statements.end()}; - // remove them from the statement block - - block_with_expression_statements->erase_statement(it, statements.end()); - // also remove diff/linear/non-linear eq statements from the statement block - block_with_expression_statements->erase_statement(expression_statements); - // move any local variable declarations into variable_block - ast::StatementVector variable_statements; - // remaining statements in block should go into initialize_block - ast::StatementVector initialize_statements; - for (auto s: statements) { + + visitor::SympyReplaceSolutionsVisitor solution_replacer( + pre_solve_statements_and_setup_x_eqs, + solutions_filtered, + expression_statements, + visitor::SympyReplaceSolutionsVisitor::ReplacePolicy::GREEDY, + state_vars.size() + 1); + solution_replacer.visit_statement_block(*block_with_expression_statements); + + // split in the various blocks for eigen + auto n_state_vars = std::make_shared(state_vars.size(), nullptr); + + const auto& statements = block_with_expression_statements->get_statements(); + + ast::StatementVector variable_statements; // LOCAL // + ast::StatementVector initialize_statements; // pre_solve_statements // + ast::StatementVector setup_x_statements; // old_x = x, X[0] = x // + ast::StatementVector functor_statements; // J[0]_row * X = F[0], additional assignments during + // computation // + ast::StatementVector finalize_statements; // assignments at the end // + const size_t sr_begin = solution_replacer.replaced_statements_begin(); + const size_t sr_end = solution_replacer.replaced_statements_end(); + + // initialize and edge case where the system of equations is empty + for (size_t idx = 0; idx < statements.size(); ++idx) { + auto& s = statements[idx]; if (is_local_statement(s)) { variable_statements.push_back(s); - } else { + } else if (sr_begin == statements.size()) { + initialize_statements.push_back(s); + } else if (idx < sr_begin) { initialize_statements.push_back(s); } } - // make statement blocks - auto n_state_vars = std::make_shared(state_vars.size(), nullptr); + + if (sr_begin != statements.size()) { + initialize_statements.insert(initialize_statements.end(), + statements.begin() + sr_begin, + statements.begin() + sr_begin + pre_solve_statements.size()); + setup_x_statements = + ast::StatementVector(statements.begin() + sr_begin + pre_solve_statements.size(), + statements.begin() + sr_begin + pre_solve_statements.size() + + state_vars.size()); + functor_statements = ast::StatementVector(statements.begin() + sr_begin + + pre_solve_statements.size() + + state_vars.size(), + statements.begin() + sr_end); + finalize_statements = ast::StatementVector(statements.begin() + sr_end, statements.end()); + } + + const size_t total_statements_size = variable_statements.size() + initialize_statements.size() + + setup_x_statements.size() + functor_statements.size() + + finalize_statements.size(); + if (statements.size() != total_statements_size) { + logger->error( + "SympySolverVisitor :: statement number missmatch ({} =/= {}) during splitting before " + "creation of " + "eigen " + "solver block.", + statements.size(), + total_statements_size); + return; + } + auto variable_block = std::make_shared(std::move(variable_statements)); auto initialize_block = std::make_shared(std::move(initialize_statements)); - auto update_state_block = create_statement_block(update_state_eqs); + auto update_state_block = create_statement_block(update_statements); auto finalize_block = std::make_shared(std::move(finalize_statements)); - if (linear) { - /// create eigen linear solver block - setup_x_eqs.insert(setup_x_eqs.end(), solutions_filtered.begin(), solutions_filtered.end()); - auto setup_x_block = create_statement_block(setup_x_eqs); + /// functor and initialize block converge in the same block + setup_x_statements.insert(setup_x_statements.end(), + functor_statements.begin(), + functor_statements.end()); + auto setup_x_block = std::make_shared(std::move(setup_x_statements)); auto solver_block = std::make_shared(n_state_vars, variable_block, initialize_block, @@ -240,8 +276,8 @@ void SympySolverVisitor::construct_eigen_solver_block( block_with_expression_statements->set_statements(std::move(solver_block_statements)); } else { /// create eigen newton solver block - auto setup_x_block = create_statement_block(setup_x_eqs); - auto functor_block = create_statement_block(solutions_filtered); + auto setup_x_block = std::make_shared(std::move(setup_x_statements)); + auto functor_block = std::make_shared(std::move(functor_statements)); auto solver_block = std::make_shared(n_state_vars, variable_block, initialize_block, @@ -256,6 +292,7 @@ void SympySolverVisitor::construct_eigen_solver_block( } } + void SympySolverVisitor::solve_linear_system(const std::vector& pre_solve_statements) { // construct ordered vector of state vars used in linear system init_state_vars_vector(); @@ -296,21 +333,13 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre add_local_variable(*block_with_expression_statements, new_local_var); } } - // insert pre-solve statements below last linear eq in block - for (const auto& statement: pre_solve_statements) { - logger->debug("SympySolverVisitor :: -> adding statement: {}", statement); - it = block_with_expression_statements->insert_statement(it, - create_statement(statement)); - ++it; - } - // then insert new solution statements - for (const auto& sol: solutions) { - logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); - it = block_with_expression_statements->insert_statement(it, create_statement(sol)); - ++it; - } - /// remove original lineq statements from the block - block_with_expression_statements->erase_statement(expression_statements); + visitor::SympyReplaceSolutionsVisitor solution_replacer( + pre_solve_statements, + solutions, + expression_statements, + visitor::SympyReplaceSolutionsVisitor::ReplacePolicy::VALUE, + 1); + solution_replacer.visit_statement_block(*block_with_expression_statements); } else { // otherwise it returns a linear matrix system to solve logger->debug("SympySolverVisitor :: Constructing linear newton solve block"); @@ -505,7 +534,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { // no CONSERVE equation, construct Euler equation auto dxdt = stringutils::trim(split_eq[1]); auto old_x = "old_" + x + x_array_index_i; // TODO: do this properly, - // check name is unique + // check name is unique // declare old_x logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); add_local_variable(*block_with_expression_statements, old_x); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 84a5e2e938..262347622a 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -125,6 +125,18 @@ std::shared_ptr create_statement(const std::string& code_statement) { return statement; } +std::vector> create_statements( + const std::vector::const_iterator& code_statements_beg, + const std::vector::const_iterator& code_statements_end) { + std::vector> statements; + statements.reserve(code_statements_end - code_statements_beg); + std::transform(code_statements_beg, + code_statements_end, + std::back_inserter(statements), + [](const std::string& s) { return create_statement(s); }); + return statements; +} + /** * Convert given code statement (in string format) to corresponding ast node * @@ -215,4 +227,28 @@ std::string to_json(const ast::Ast& node, bool compact, bool expand, bool add_nm return stream.str(); } +std::pair> statement_dependencies( + const std::shared_ptr& lhs, + const std::shared_ptr& rhs) { + std::string key; + std::unordered_set out; + + if (!lhs->is_var_name()) { + return {key, out}; + } + + key = to_nmodl(lhs); + key.erase(std::remove(key.begin(), key.end(), '\''), + key.end()); // we want to match derivatives and variables + visitor::AstLookupVisitor lookup_visitor; + lookup_visitor.lookup(*rhs, ast::AstNodeType::VAR_NAME); + auto rhs_nodes = lookup_visitor.get_nodes(); + std::for_each(rhs_nodes.begin(), + rhs_nodes.end(), + [&out](const std::shared_ptr& node) { out.emplace(to_nmodl(node)); }); + + + return {key, out}; +} + } // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 3582271ac2..76566b6e87 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -65,6 +66,11 @@ ast::LocalVar* add_local_variable(ast::StatementBlock& node, const std::string& /// Create ast statement node from given code in string format std::shared_ptr create_statement(const std::string& code_statement); +/// Same as for create_statement but for vectors of strings +std::vector> create_statements( + const std::vector::const_iterator& code_statements_beg, + const std::vector::const_iterator& code_statements_end); + /// Create ast statement block node from given code in string format std::shared_ptr create_statement_block( @@ -112,5 +118,13 @@ std::string to_json(const ast::Ast& node, bool expand = false, bool add_nmodl = false); +/// If \p lhs and \p rhs combined represent an assignment (we assume to have an "=" in between them) +/// we extract the variables on which the assigned variable depends on. We provide the input with +/// lhs and rhs because there are a few nodes that have this similar structure but slightly +/// different naming (binaryExpression, diff_eq_expression, linExpression) +std::pair> statement_dependencies( + const std::shared_ptr& lhs, + const std::shared_ptr& rhs); + } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 3fb0752f33..d9df335573 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -17,6 +17,7 @@ #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" + using namespace nmodl; using namespace visitor; using namespace test; @@ -101,7 +102,7 @@ void compare_blocks(const std::string& result, def sanitize(s): import re - d = {'\[(\d+)\]':'_\\1', 'pow\((\w+), ?(\d+)\)':'\\1**\\2'} + d = {'\[(\d+)\]':'_\\1', 'pow\((\w+), ?(\d+)\)':'\\1**\\2', 'beta': 'beta_var', 'gamma': 'gamma_var'} out = s for key, val in d.items(): out = re.sub(key, val, out) @@ -121,19 +122,24 @@ void compare_blocks(const std::string& result, return True def reduce(s): - i = 0 + max_tmp = -1 d = {} sout = "" # split of sout and a dict with the tmp variables for line in s.split('\n'): line_split = line.lstrip().split('=') - if len(line_split) == 2 and line_split[0] == f'tmp{i} ': + + if len(line_split) == 2 and line_split[0].startswith('tmp'): # back-substitution of tmp variables in tmp variables + tmp_var = line_split[0].strip() + if tmp_var in d: + continue + + max_tmp = max(max_tmp, int(tmp_var[3:])) for k, v in d.items(): line_split[1] = line_split[1].replace(k, f'({v})') - d[f'tmp{i}'] = line_split[1] - i += 1 + d[tmp_var] = line_split[1] elif 'LOCAL' in line: sout += line.split('tmp0')[0] + '\n' else: @@ -141,7 +147,7 @@ void compare_blocks(const std::string& result, # Back-substitution of the tmps # so that we do not replace tmp11 with (tmp1)1 - for j in range(i-1, -1, -1): + for j in range(max_tmp, -1, -1): k = f'tmp{j}' sout = sout.replace(k, f'({d[k]})') @@ -535,6 +541,37 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", "[visitor][sympy][derivimplicit]") { + GIVEN("Derivative block with derivimplicit solver method. Check avoided name-clash") { + std::string nmodl_text = R"( + UNITS { + F = (faraday) (coulombs) + } + STATE { + x + } + BREAKPOINT { + SOLVE integrate METHOD derivimplicit + } + DERIVATIVE integrate { + x' = x + 1 + } + )"; + THEN("SympySolver correctly renames F vector") { + const std::string probable_explaination = + "Sympy_visitor left the standard F name for the F_vector. Name " + "clash with F faraday."; + CAPTURE(nmodl_text); + CAPTURE(probable_explaination); + + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + + REQUIRE(result.size() == 1); + REQUIRE(result[0].find("F[") == std::string::npos); + } + } + + GIVEN("Derivative block with derivimplicit solver method and conditional block") { std::string nmodl_text = R"( STATE { @@ -577,6 +614,216 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", } } + GIVEN("Derivative block, sparse, print in order") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b + y' = a + x' = b + })"; + std::string expected_result = R"( + DERIVATIVE states { + LOCAL a, b, old_y, old_x + old_y = y + old_x = x + y = a*dt+old_y + x = b*dt+old_x + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN("Derivative block, sparse, print in order, vectors") { + std::string nmodl_text = R"( + STATE { + M[2] + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b + M'[1] = a + M'[0] = b + })"; + std::string expected_result = R"( + DERIVATIVE states { + LOCAL a, b, old_M_1, old_M_0 + old_M_1 = M[1] + old_M_0 = M[0] + M[1] = a*dt+old_M_1 + M[0] = b*dt+old_M_0 + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN("Derivative block, sparse, derivatives mixed with local variable reassignment") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b + x' = a + b = b + 1 + y' = b + })"; + std::string expected_result = R"( + DERIVATIVE states { + LOCAL a, b, old_x, old_y + old_x = x + old_y = y + x = a*dt+old_x + b = b+1 + y = b*dt+old_y + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN( + "Throw exception during derivative variable reassignment interleaved in the differential " + "equation set") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b + x' = a + x = x + 1 + y' = b + x + })"; + + THEN( + "Throw an error because state variable assignments are not allowed inside the system " + "of differential " + "equations") { + REQUIRE_THROWS_WITH( + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK), + Catch::Matchers::Contains("State variable assignment(s) interleaved in system of " + "equations/differential equations") && + Catch::Matchers::StartsWith("SympyReplaceSolutionsVisitor")); + } + } + GIVEN("Derivative block in control flow block") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b + if (a == 1) { + x' = a + y' = b + } + })"; + std::string expected_result = R"( + DERIVATIVE states { + LOCAL a, b + IF (a == 1) { + LOCAL old_x, old_y + old_x = x + old_y = y + x = a*dt+old_x + y = b*dt+old_y + } + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN( + "Derivative block, sparse, coupled derivatives mixed with reassignment and control flow " + "block") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL a, b + x' = a * y+b + if (b == 1) { + a = a + 1 + } + y' = x + a*y + })"; + std::string expected_result = R"( + DERIVATIVE states { + LOCAL a, b, old_x, old_y + old_x = x + old_y = y + x = (a*b*pow(dt, 2)+a*dt*old_x-a*dt*old_y-b*dt-old_x)/(a*pow(dt, 2)+a*dt-1.0) + IF (b == 1) { + a = a+1 + } + y = (-b*pow(dt, 2)-dt*old_x-old_y)/(a*pow(dt, 2)+a*dt-1.0) + })"; + std::string expected_result_cse = R"( + DERIVATIVE states { + LOCAL a, b, old_x, old_y, tmp0, tmp1, tmp2, tmp3 + old_x = x + old_y = y + tmp0 = a*dt + tmp1 = pow(dt, 2) + tmp2 = a*tmp1 + tmp3 = 1.0/(tmp0+tmp2-1.0) + x = -tmp3*(b*dt-b*tmp2-old_x*tmp0+old_x+old_y*tmp0) + IF (b == 1) { + a = a+1 + } + tmp0 = a*dt + tmp2 = a*tmp1 + tmp3 = 1.0/(tmp0+tmp2-1.0) + y = -tmp3*(b*tmp1+dt*old_x+old_y) + })"; + + THEN("Construct & solve linear system for backwards Euler") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); + auto result_cse = + run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::DERIVATIVE_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + compare_blocks(reindent_text(result_cse[0]), reindent_text(expected_result_cse)); + } + } + GIVEN("Derivative block of coupled & linear ODES, solver method sparse") { std::string nmodl_text = R"( STATE { @@ -1025,8 +1272,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); CAPTURE(nmodl_text); - REQUIRE(result[0] == reindent_text(expected_result_0)); - REQUIRE(result[1] == reindent_text(expected_result_1)); + compare_blocks(result[0], reindent_text(expected_result_0)); + compare_blocks(result[1], reindent_text(expected_result_1)); } } } @@ -1093,54 +1340,168 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } - GIVEN("2 state-var LINEAR solve block, post-solve statements") { + GIVEN("Linear block, print in order") { std::string nmodl_text = R"( STATE { x y } LINEAR lin { - ~ x + 4*y = 5*a - ~ x - y = 0 - x = x + 2 - y = y - a + ~ y = x + 1 + ~ x = 2 })"; - std::string expected_text = R"( + std::string expected_result = R"( LINEAR lin { - x = a + y = 3.0 + x = 2.0 + })"; + + THEN("Construct & solve linear system") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN("Linear block, print in order, vectors") { + std::string nmodl_text = R"( + STATE { + M[2] + } + LINEAR lin { + ~ M[1] = M[0] + 1 + ~ M[0] = 2 + })"; + std::string expected_result = R"( + LINEAR lin { + M[1] = 3.0 + M[0] = 2.0 + })"; + + THEN("Construct & solve linear system") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN("Linear block, greedy replacement, interleaved") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + LOCAL a + a = 0 + ~ x + y = 1 + a = 1 + ~ y - x = 3 + a = 2 + })"; + std::string expected_result = R"( + LINEAR lin { + LOCAL a + a = 0 + x = -1.0 + a = 1 + y = 2.0 + a = 2 + })"; + + THEN("Construct & solve linear system") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN("Linear block, by value replacement, interleaved") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + LOCAL a + a = 0 + ~ x = y + a + a = 1 + ~ y = a + a = 2 + })"; + std::string expected_result = R"( + LINEAR lin { + LOCAL a + a = 0 + x = 2.0*a + a = 1 y = a - x = x+2 - y = y-a + a = 2 })"; - THEN("solve analytically, insert in correct location") { - CAPTURE(reindent_text(nmodl_text)); + + THEN("Construct & solve linear system") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); } } - GIVEN("2 state-var LINEAR solve block, mixed & post-solve statements") { + GIVEN("Linear block in control flow block") { std::string nmodl_text = R"( STATE { x y } LINEAR lin { - ~ x + 4*y = 5*a - a2 = 3*b - ~ x - y = 0 - y = y - a + LOCAL a + if (a == 1) { + ~ x = y + a + ~ y = a + } })"; - std::string expected_text = R"( + std::string expected_result = R"( LINEAR lin { - a2 = 3*b - x = a + LOCAL a + IF (a == 1) { + x = 2.0*a + y = a + } + })"; + + THEN("Construct & solve linear system") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); + } + } + GIVEN("Linear block, linear equations mixed with control flow blocks and reassignments") { + std::string nmodl_text = R"( + STATE { + x y + } + LINEAR lin { + LOCAL a + ~ x = y + a + if (a == 1) { + a = a + 1 + x = a + 1 + } + ~ y = a + })"; + std::string expected_result = R"( + LINEAR lin { + LOCAL a + x = 2.0*a + IF (a == 1) { + a = a+1 + x = a+1 + } y = a - y = y-a })"; - THEN("solve analytically, insert in correct location") { - CAPTURE(reindent_text(nmodl_text)); + + THEN("Construct & solve linear system") { auto result = run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + + compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); } } GIVEN("3 state-var LINEAR solve block") { From 283e8dee843d3c5c316caf977a91bede6c4dabd1 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 24 Feb 2021 09:59:46 +0100 Subject: [PATCH 335/871] Fixup! ctest fail when looking for F[ (BlueBrain/nmodl#520) The CI fails if it finds F[. However, the replaced var F_****[ can still present a trailing F[ making the CI fail even if the var was correctly replaced NMODL Repo SHA: BlueBrain/nmodl@651f48e700cc975f827c0b9484eff4e7c57b5f2c --- test/nmodl/transpiler/unit/visitor/sympy_solver.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index d9df335573..e4b7691df9 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -567,7 +567,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); REQUIRE(result.size() == 1); - REQUIRE(result[0].find("F[") == std::string::npos); + REQUIRE(result[0].find("F_") != std::string::npos); } } From 742534fb6fe5897e6de6c0481118e60daeacf743 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 25 Feb 2021 08:23:25 +0100 Subject: [PATCH 336/871] BB5 CI: build and test only intel compiler / bump cmake-format to 0.6.13 (BlueBrain/nmodl#517) * GitLab CI/CD: bump cmake-format to 0.6.13 Use same version than BlueBrain/spack. * GitLab CI/CD: only build with intel compiler on BB5 * Bump number of concurrent builds from 2 to 6 NMODL Repo SHA: BlueBrain/nmodl@791b8a1a970aeb0bf8902f812e6d367d020b4300 --- cmake/nmodl/Catch.cmake | 3 ++- src/nmodl/lexer/CMakeLists.txt | 15 ++++++++++++--- src/nmodl/parser/CMakeLists.txt | 17 ++++++++++++----- src/nmodl/pybind/CMakeLists.txt | 9 +-------- src/nmodl/visitors/CMakeLists.txt | 6 ++++-- test/nmodl/transpiler/unit/CMakeLists.txt | 6 ++---- 6 files changed, 33 insertions(+), 23 deletions(-) diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake index f33d7cdd24..c64bf9a395 100644 --- a/cmake/nmodl/Catch.cmake +++ b/cmake/nmodl/Catch.cmake @@ -116,7 +116,8 @@ function(catch_discover_tests TARGET) TARGET ${TARGET} PROPERTY CROSSCOMPILING_EMULATOR) add_custom_command( - TARGET ${TARGET} POST_BUILD + TARGET ${TARGET} + POST_BUILD BYPRODUCTS "${ctest_tests_file}" COMMAND "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D "TEST_EXECUTABLE=$" -D diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 927864edcd..a83b7819cb 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -192,9 +192,18 @@ endif() # Install executable # ============================================================================= if(NOT NMODL_AS_SUBPROJECT) - install(TARGETS nmodl_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) - install(TARGETS c_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) - install(TARGETS units_lexer DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer CONFIGURATIONS Debug) + install( + TARGETS nmodl_lexer + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer + CONFIGURATIONS Debug) + install( + TARGETS c_lexer + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer + CONFIGURATIONS Debug) + install( + TARGETS units_lexer + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/lexer + CONFIGURATIONS Debug) endif() add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index 749b534c7e..c1ad510df8 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -18,9 +18,16 @@ endif() # Install executable # ============================================================================= if(NOT NMODL_AS_SUBPROJECT) - install(TARGETS nmodl_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS - Debug) - install(TARGETS c_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS Debug) - install(TARGETS units_parser DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser CONFIGURATIONS - Debug) + install( + TARGETS nmodl_parser + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser + CONFIGURATIONS Debug) + install( + TARGETS c_parser + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser + CONFIGURATIONS Debug) + install( + TARGETS units_parser + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/parser + CONFIGURATIONS Debug) endif() diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 50b8339879..de7798009b 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -6,14 +6,7 @@ set_source_files_properties(${PYBIND_GENERATED_SOURCES} PROPERTIES GENERATED TRU # build nmodl python module under lib/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl) -foreach( - file - ast.py - dsl.py - ode.py - symtab.py - visitor.py - __init__.py) +foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/lib/nmodl/${file}) endforeach() diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index c437114e48..5df4e28970 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -92,6 +92,8 @@ endif() # Install executable # ============================================================================= if(NOT NMODL_AS_SUBPROJECT) - install(TARGETS nmodl_visitor DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor CONFIGURATIONS - Debug) + install( + TARGETS nmodl_visitor + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin/visitor + CONFIGURATIONS Debug) endif() diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 529374a44b..e4969b39a3 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -99,10 +99,8 @@ target_link_libraries(testunitparser lexer test_util config) set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") if(NOT LINK_AGAINST_PYTHON) list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") - list( - APPEND - testvisitor_env - "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/nmodl/libpywrapper${CMAKE_SHARED_LIBRARY_SUFFIX}") + list(APPEND testvisitor_env + "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/nmodl/libpywrapper${CMAKE_SHARED_LIBRARY_SUFFIX}") endif() From 14d6dd0822bb07806e71522e50f6c8f841bde2e8 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Mon, 1 Mar 2021 15:19:41 +0100 Subject: [PATCH 337/871] sympy var clashes (BlueBrain/nmodl#518) - Remove second version of "suffix_random_string" with default param - old_x with unique prefix to avoid clashes - suffix_random_string: check if new_string is prefix of an existing one - remove _make_unique_prefix and its tests as it is useless - tmp_* is now the default to better distinguish the iterator - Make tmp_* variables unique We leverage the fact that suffix_random_string now returns a prefix that is unique to create various unique strings in ode.py appending the usual iterator. The tmp_unique_prefix is provided to solve_lin_system. Only in case do_cse is True we use the tmp_unique_prefix to generate the temp variables. NMODL Repo SHA: BlueBrain/nmodl@ce1def6de19ff3a3cf36ec8fabd1f9a7eb2449c3 --- nmodl/ode.py | 40 +----- src/nmodl/pybind/pyembed.hpp | 3 +- src/nmodl/pybind/wrapper.cpp | 4 +- .../sympy_replace_solutions_visitor.cpp | 15 ++- .../sympy_replace_solutions_visitor.hpp | 3 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 15 ++- src/nmodl/visitors/visitor_utils.cpp | 12 +- src/nmodl/visitors/visitor_utils.hpp | 18 +-- test/nmodl/transpiler/unit/ode/test_ode.py | 15 +-- .../transpiler/unit/visitor/sympy_solver.cpp | 123 +++++++++++++----- 10 files changed, 138 insertions(+), 110 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index 0cf7cebee0..cdbeca0458 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -31,39 +31,6 @@ def _get_custom_functions(fcts): custom_functions[f] = f return custom_functions -def _make_unique_prefix(vars, default_prefix="tmp"): - """Generate a unique prefix - - Generates a prefix that doesn't match the first part - of any string in vars. - - Starting point is the supplied default_prefix, if - this doesn't match the first part of any string in vars - it is returned. - - Otherwise, underscores are appended until it doesn't match, - then this string is returned. - - Args: - vars: list of strings that the prefix should not match - default_prefix: desired prefix - - Returns: - the prefix as a string - """ - prefix = default_prefix - while True: - for v in vars: - # if v is long enough to match prefix - # and first part of it matches prefix - if (len(v) >= len(prefix)) and (v[: len(prefix)] == prefix): - # append undescore to prefix, try again - prefix += "_" - break - else: - # for loop ended without finding possible clash - return prefix - def _var_to_sympy(var_str): """Return sympy variable from string representing variable @@ -213,7 +180,7 @@ def _interweave_eqs(F, J): return code -def solve_lin_system(eq_strings, vars, constants, function_calls, small_system=False, do_cse=False): +def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_prefix, small_system=False, do_cse=False): """Solve linear system of equations, return solution as C code. If system is small (small_system=True, typically N<=3): @@ -230,6 +197,8 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, small_system=F vars: list of variables to solve for, e.g. ["x", "y"] constants: set of any other symbolic expressions used, e.g. {"a", "b"} function_calls: set of function calls used in the ODE + tmp_unique_prefix: is a unique prefix on which new variables can be easily created + by appending strings. It is usually of the form "tmp" small_system: if True, solve analytically by gaussian elimination otherwise return matrix system to be solved do_cse: if True, do Common Subexpression Elimination @@ -250,8 +219,7 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, small_system=F solution_vector = sp.linsolve(eqs, state_vars).args[0] if do_cse: # generate prefix for new local vars that avoids clashes - prefix = _make_unique_prefix(vars) - my_symbols = sp.utilities.iterables.numbered_symbols(prefix=prefix) + my_symbols = sp.utilities.iterables.numbered_symbols(prefix=tmp_unique_prefix + '_') sub_exprs, simplified_solution_vector = sp.cse( solution_vector, symbols=my_symbols, diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp index a903e6a332..cfd78e73f9 100644 --- a/src/nmodl/pybind/pyembed.hpp +++ b/src/nmodl/pybind/pyembed.hpp @@ -32,6 +32,8 @@ struct SolveLinearSystemExecutor: public PythonExecutor { std::set vars; bool small_system; bool elimination; + // This is used only if elimination is true. It gives the root for the tmp variables + std::string tmp_unique_prefix; std::set function_calls; // output // returns a vector of solutions, i.e. new statements to add to block: @@ -40,7 +42,6 @@ struct SolveLinearSystemExecutor: public PythonExecutor { std::vector new_local_vars; // may also return a python exception message: std::string exception_message; - // executor function virtual void operator()() override; }; diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 1ceb1c111b..3a97410a70 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -27,7 +27,8 @@ void SolveLinearSystemExecutor::operator()() { "vars"_a = vars, "small_system"_a = small_system, "do_cse"_a = elimination, - "function_calls"_a = function_calls); + "function_calls"_a = function_calls, + "tmp_unique_prefix"_a = tmp_unique_prefix); py::exec(R"( from nmodl.ode import solve_lin_system exception_message = "" @@ -36,6 +37,7 @@ void SolveLinearSystemExecutor::operator()() { state_vars, vars, function_calls, + tmp_unique_prefix, small_system, do_cse) except Exception as e: diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index 5c78403336..d4b120ec16 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -38,16 +38,23 @@ SympyReplaceSolutionsVisitor::SympyReplaceSolutionsVisitor( const std::vector& solutions, const std::unordered_set& to_be_removed, const ReplacePolicy policy, - const size_t n_next_equations) + const size_t n_next_equations, + const std::string& tmp_unique_prefix) : pre_solve_statements(pre_solve_statements.begin(), pre_solve_statements.end(), 2) , to_be_removed(&to_be_removed) , policy(policy) , n_next_equations(n_next_equations) , replaced_statements_range(-1, -1) { + // if tmp_unique_prefix we do not expect tmp_statements const auto ss_tmp_delimeter = - std::find_if(solutions.begin(), solutions.end(), [](const std::string& statement) { - return statement.substr(0, 3) != "tmp"; - }); + tmp_unique_prefix.empty() + ? solutions.begin() + : std::find_if(solutions.begin(), + solutions.end(), + [&tmp_unique_prefix](const std::string& statement) { + return statement.substr(0, tmp_unique_prefix.size()) != + tmp_unique_prefix; + }); tmp_statements = StatementDispenser(solutions.begin(), ss_tmp_delimeter, -1); solution_statements = StatementDispenser(ss_tmp_delimeter, solutions.end(), -1); diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp index 793020e5a6..6bf5a928b4 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -224,7 +224,8 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { const std::vector& solutions, const std::unordered_set& to_be_removed, const ReplacePolicy policy, - size_t n_next_equations); + size_t n_next_equations, + const std::string& tmp_unique_prefix); /// idx (in the new statementVector) of the first statement that was added. -1 if nothing was /// added diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 6f579ecd5b..3605d68a5d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -196,7 +196,8 @@ void SympySolverVisitor::construct_eigen_solver_block( solutions_filtered, expression_statements, visitor::SympyReplaceSolutionsVisitor::ReplacePolicy::GREEDY, - state_vars.size() + 1); + state_vars.size() + 1, + ""); solution_replacer.visit_statement_block(*block_with_expression_statements); // split in the various blocks for eigen @@ -304,6 +305,9 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre solver->vars = vars; solver->small_system = small_system; solver->elimination = elimination; + // this is necessary after we destroy the solver + const auto tmp_unique_prefix = suffix_random_string(vars, "tmp"); + solver->tmp_unique_prefix = tmp_unique_prefix; solver->function_calls = function_calls; (*solver)(); // returns a vector of solutions, i.e. new statements to add to block: @@ -312,6 +316,7 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre auto new_local_vars = solver->new_local_vars; // may also return a python exception message: auto exception_message = solver->exception_message; + // destroy solver pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_sls_executor(solver); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: solve_lin_system python exception: " + @@ -338,7 +343,8 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre solutions, expression_statements, visitor::SympyReplaceSolutionsVisitor::ReplacePolicy::VALUE, - 1); + 1, + tmp_unique_prefix); solution_replacer.visit_statement_block(*block_with_expression_statements); } else { // otherwise it returns a linear matrix system to solve @@ -533,8 +539,9 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { } else { // no CONSERVE equation, construct Euler equation auto dxdt = stringutils::trim(split_eq[1]); - auto old_x = "old_" + x + x_array_index_i; // TODO: do this properly, - // check name is unique + + + const auto old_x = suffix_random_string(vars, "old_" + x + x_array_index_i); // declare old_x logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); add_local_variable(*block_with_expression_statements, old_x); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 262347622a..ea25252959 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -33,9 +33,12 @@ std::string suffix_random_string(const std::set& vars, std::string new_string = original_string; std::string random_string; auto& singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); - // Check if there is a variable defined in the mod file as original_string and if yes + // Check if there is a variable defined in the mod file as original_string or that + // has original_string as prefix and, if yes, // try to use a different string in the form "original_string"_"random_string" - while (vars.find(new_string) != vars.end()) { + + const auto it = vars.lower_bound(new_string); + while ((it != vars.end()) && new_string == it->substr(0, new_string.size())) { random_string = singleton_random_string_class.reset_random_string(original_string, use_num); new_string = original_string; new_string += "_" + random_string; @@ -43,11 +46,6 @@ std::string suffix_random_string(const std::set& vars, return new_string; } -std::string suffix_random_string(const std::set& vars, - const std::string& original_string) { - return suffix_random_string(vars, original_string, UseNumbersInString::WithNumbers); -} - std::string get_new_name(const std::string& name, const std::string& suffix, std::map& variables) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 76566b6e87..1b44906d24 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -30,17 +30,13 @@ using nmodl::utils::UseNumbersInString; /// for the original_string. Vars is a const ref to std::set which /// holds the names that need to be checked for uniqueness. Choose if the /// "random_string" will include numbers using "use_num" -std::string suffix_random_string(const std::set& vars, - const std::string& original_string, - const UseNumbersInString use_num); - -/// Return a std::string in the form "original_string"_"random_string", where -/// random_string is a string defined in the nmodl::utils::SingletonRandomString -/// for the original_string. Vars is a const ref to std::set which -/// holds the names that need to be checked for uniqueness. Adds numbers in the -/// "random_string" -std::string suffix_random_string(const std::set& vars, - const std::string& original_string); +/// We make sure that the new string does not match any string in vars AND is not +/// a prefix for any string in vars. In this way, appending to the result +/// will always create new unique strings +std::string suffix_random_string( + const std::set& vars, + const std::string& original_string, + const UseNumbersInString use_num = nmodl::utils::UseNumbersInString::WithNumbers); /// Return new name variable by appending `_suffix_COUNT` where `COUNT` is /// number of times the given variable is already used. diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index 50b57c755a..b90ce186a7 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -5,7 +5,7 @@ # Lesser General Public License. See top-level LICENSE file for details. # *********************************************************************** -from nmodl.ode import _make_unique_prefix, differentiate2c, integrate2c +from nmodl.ode import differentiate2c, integrate2c import sympy as sp @@ -40,19 +40,6 @@ def _equivalent( return True -def test_make_unique_prefix(): - - assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "z") == "z" - assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "a") == "a_" - assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "az") == "az" - assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "cc") == "cc_" - assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "ccc") == "ccc_" - assert _make_unique_prefix(["a", "b", "ccc", "tmp"], "tmp") == "tmp_" - assert _make_unique_prefix(["a", "b", "tmp_", "tmp"], "tmp") == "tmp__" - assert _make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmp") == "tmp_" - assert _make_unique_prefix(["a", "tmp2", "ccc", "x"], "tmpvar") == "tmpvar" - - def test_differentiate2c(): # simple examples, no prev_expressions diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index e4b7691df9..f8b9fa067a 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -65,6 +65,22 @@ std::vector run_sympy_solver_visitor( return results; } +// check if in a list of vars (like LOCAL) there are duplicates +bool is_unique_vars(std::string result) { + result.erase(std::remove(result.begin(), result.end(), ','), result.end()); + std::stringstream ss(result); + std::string token; + + std::unordered_set old_vars; + + while (getline(ss, token, ' ')) { + if (!old_vars.insert(token).second) { + return false; + } + } + return true; +} + /** * \brief Compare nmodl blocks that contain systems of equations (i.e. derivative, linear, etc.) @@ -88,6 +104,8 @@ std::vector run_sympy_solver_visitor( * - reduce back-substitution of the temporary variables * * \p require_fail requires that the equations are different. Used only for unit-test this function + * + * \warning do not use this method when there are tmp variables not in the form: tmp_ */ void compare_blocks(const std::string& result, const std::string& expected, @@ -130,25 +148,25 @@ void compare_blocks(const std::string& result, for line in s.split('\n'): line_split = line.lstrip().split('=') - if len(line_split) == 2 and line_split[0].startswith('tmp'): + if len(line_split) == 2 and line_split[0].startswith('tmp_'): # back-substitution of tmp variables in tmp variables tmp_var = line_split[0].strip() if tmp_var in d: continue - max_tmp = max(max_tmp, int(tmp_var[3:])) + max_tmp = max(max_tmp, int(tmp_var[4:])) for k, v in d.items(): line_split[1] = line_split[1].replace(k, f'({v})') d[tmp_var] = line_split[1] elif 'LOCAL' in line: - sout += line.split('tmp0')[0] + '\n' + sout += line.split('tmp_0')[0] + '\n' else: sout += line + '\n' # Back-substitution of the tmps - # so that we do not replace tmp11 with (tmp1)1 + # so that we do not replace tmp_11 with (tmp_1)1 for j in range(max_tmp, -1, -1): - k = f'tmp{j}' + k = f'tmp_{j}' sout = sout.replace(k, f'({d[k]})') return sout @@ -248,9 +266,9 @@ SCENARIO("Check compare_blocks in sympy unit tests", "[visitor][sympy]") { })"; std::string expected = R"( DERIVATIVE { - tmp0 = a + c - tmp1 = tmp0 - a - A[0] = b+2*b + tmp1 + tmp_0 = a + c + tmp_1 = tmp_0 - a + A[0] = b+2*b + tmp_1 y = pow(a, 2)*a + 2*b-b })"; THEN("Blocks are equal") { @@ -275,9 +293,9 @@ SCENARIO("Check compare_blocks in sympy unit tests", "[visitor][sympy]") { GIVEN("Different systems of equations") { std::string result = R"( DERIVATIVE { - tmp0 = a - c - tmp1 = tmp0 - a - x = 3*b + tmp1 + tmp_0 = a - c + tmp_1 = tmp_0 - a + x = 3*b + tmp_1 y = 2*a + b })"; std::string expected = R"( @@ -291,6 +309,49 @@ SCENARIO("Check compare_blocks in sympy unit tests", "[visitor][sympy]") { } } +SCENARIO("Check local vars name-clash prevention", "[visitor][sympy]") { + GIVEN("LOCAL tmp") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL tmp, b + x' = tmp + b + y' = tmp + b + })"; + THEN("There are no duplicate vars in LOCAL") { + auto result = + run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::LOCAL_LIST_STATEMENT); + REQUIRE(!result.empty()); + REQUIRE(is_unique_vars(result[0])); + } + } + GIVEN("LOCAL tmp_0") { + std::string nmodl_text = R"( + STATE { + x y + } + BREAKPOINT { + SOLVE states METHOD sparse + } + DERIVATIVE states { + LOCAL tmp_0, b + x' = tmp_0 + b + y' = tmp_0 + b + })"; + THEN("There are no duplicate vars in LOCAL") { + auto result = + run_sympy_solver_visitor(nmodl_text, true, true, AstNodeType::LOCAL_LIST_STATEMENT); + REQUIRE(!result.empty()); + REQUIRE(is_unique_vars(result[0])); + } + } +} + SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", "[visitor][sympy][cnexp][euler]") { GIVEN("Derivative block without ODE, solver method cnexp") { @@ -796,21 +857,21 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_result_cse = R"( DERIVATIVE states { - LOCAL a, b, old_x, old_y, tmp0, tmp1, tmp2, tmp3 + LOCAL a, b, old_x, old_y, tmp_0, tmp_1, tmp_2, tmp_3 old_x = x old_y = y - tmp0 = a*dt - tmp1 = pow(dt, 2) - tmp2 = a*tmp1 - tmp3 = 1.0/(tmp0+tmp2-1.0) - x = -tmp3*(b*dt-b*tmp2-old_x*tmp0+old_x+old_y*tmp0) + tmp_0 = a*dt + tmp_1 = pow(dt, 2) + tmp_2 = a*tmp_1 + tmp_3 = 1.0/(tmp_0+tmp_2-1.0) + x = -tmp_3*(b*dt-b*tmp_2-old_x*tmp_0+old_x+old_y*tmp_0) IF (b == 1) { a = a+1 } - tmp0 = a*dt - tmp2 = a*tmp1 - tmp3 = 1.0/(tmp0+tmp2-1.0) - y = -tmp3*(b*tmp1+dt*old_x+old_y) + tmp_0 = a*dt + tmp_2 = a*tmp_1 + tmp_3 = 1.0/(tmp_0+tmp_2-1.0) + y = -tmp_3*(b*tmp_1+dt*old_x+old_y) })"; THEN("Construct & solve linear system for backwards Euler") { @@ -851,19 +912,19 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_cse_result = R"( DERIVATIVE states { - LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp0, tmp1, tmp2, tmp3, tmp4, tmp5 + LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp_0, tmp_1, tmp_2, tmp_3, tmp_4, tmp_5 old_x = x old_y = y old_z = z - tmp0 = 2.0*a - tmp1 = -d*dt+pow(dt, 3)*tmp0+1.0 - tmp2 = 1.0/tmp1 - tmp3 = b*dt*h+old_x - tmp4 = c*dt+2.0*dt*tmp3+old_y - tmp5 = dt*tmp4-old_z - x = -tmp2*(a*dt*tmp5-tmp1*tmp3) - y = -tmp2*(pow(dt, 2)*tmp0*tmp5-tmp1*tmp4) - z = -tmp2*tmp5 + tmp_0 = 2.0*a + tmp_1 = -d*dt+pow(dt, 3)*tmp_0+1.0 + tmp_2 = 1.0/tmp_1 + tmp_3 = b*dt*h+old_x + tmp_4 = c*dt+2.0*dt*tmp_3+old_y + tmp_5 = dt*tmp_4-old_z + x = -tmp_2*(a*dt*tmp_5-tmp_1*tmp_3) + y = -tmp_2*(pow(dt, 2)*tmp_0*tmp_5-tmp_1*tmp_4) + z = -tmp_2*tmp_5 })"; THEN("Construct & solve linear system for backwards Euler") { From 25a57a457e2e04fb05d5bb9cd7039f5b6b8d9a68 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Sun, 7 Mar 2021 10:06:19 +0100 Subject: [PATCH 338/871] Fix build issue on Apple M1 with AppleClang (BlueBrain/nmodl#528) * Currently catch2 is header directly added in the project - Before switching to submodule (in separate PR), copying minimal fix from the upstream: https://github.com/catchorg/Catch2/pull/1971/ * Update INSTALL.md for Apple M1 fixes BlueBrain/nmodl#527 NMODL Repo SHA: BlueBrain/nmodl@c18a4eddf5526f3858c7c252628f1718aecea1a3 --- INSTALL.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index e987a9d8c5..f384a6d1d1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -46,6 +46,12 @@ Make sure to have latest flex/bison in $PATH : export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:/usr/local/bin/:$PATH ``` +On Apple M1, corresponding brew paths are under `/opt/homebrew/opt/`: + +```sh +export PATH=/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin:$PATH +``` + ### On Ubuntu On Ubuntu (>=16.04) flex/bison versions are recent enough and are installed along with the system toolchain: From 76bd1ac3dcc1fdbfd5089ea8e9e63109497dbde9 Mon Sep 17 00:00:00 2001 From: Giacomo Castiglioni Date: Fri, 12 Mar 2021 21:29:59 +0100 Subject: [PATCH 339/871] Improved vexp (and friends) test (BlueBrain/nmodl#522) * Modified vexp test to have tigheter tolerance (4ULP) on wider range of inputs * fixed typo double-->float * Fix clang-format with v11.0 * fixed test normalization for small numbers, made cancellation trick robust * added short explanation of why reordering cannot happen NMODL Repo SHA: BlueBrain/nmodl@d535ebc3fb4e8ff53ce2072d3fc24ab1b6fe33b8 --- src/nmodl/codegen/fast_math.hpp | 137 +++++++++--------- .../transpiler/unit/fast_math/fast_math.cpp | 120 +++++++-------- 2 files changed, 118 insertions(+), 139 deletions(-) diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index 2934548890..6546058794 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -10,7 +10,8 @@ #pragma once -#include +#include +#include /** * \file @@ -33,18 +34,6 @@ static inline unsigned int sp2uint32(float x) { return *((uint32_t*) (&x)); } -static inline double dpfloor(const double x) { - int32_t ret = x; - ret -= (sp2uint32(x) >> 31); - return ret; -} - -static inline float spfloor(const float x) { - int32_t ret = x; - ret -= (sp2uint32(x) >> 31); - return ret; -} - static inline float f_inf() { uint32_t v = 0x7F800000; return *((float*) (&v)); @@ -56,55 +45,60 @@ static inline double inf() { } -static const double EXP_LIMIT = 708.0; +static constexpr double EXP_LIMIT = 708.0; -static const double C1 = 6.93145751953125E-1; -static const double C2 = 1.42860682030941723212E-6; +static constexpr double C1 = 6.93145751953125E-1; +static constexpr double C2 = 1.42860682030941723212E-6; -static const double PX1exp = 1.26177193074810590878E-4; -static const double PX2exp = 3.02994407707441961300E-2; -static const double PX3exp = 9.99999999999999999910E-1; -static const double QX1exp = 3.00198505138664455042E-6; -static const double QX2exp = 2.52448340349684104192E-3; -static const double QX3exp = 2.27265548208155028766E-1; -static const double QX4exp = 2.00000000000000000009; +static constexpr double PX1exp = 1.26177193074810590878E-4; +static constexpr double PX2exp = 3.02994407707441961300E-2; +static constexpr double PX3exp = 9.99999999999999999910E-1; +static constexpr double QX1exp = 3.00198505138664455042E-6; +static constexpr double QX2exp = 2.52448340349684104192E-3; +static constexpr double QX3exp = 2.27265548208155028766E-1; +static constexpr double QX4exp = 2.00000000000000000009; -static const double LOG2E = 1.4426950408889634073599; // 1/ln(2) +static constexpr double LOG2E = 1.4426950408889634073599; // 1/ln(2) -static const float MAXLOGF = 88.72283905206835f; -static const float MINLOGF = -88.f; +static constexpr float MAXLOGF = 88.72283905206835f; +static constexpr float MINLOGF = -88.f; -static const float C1F = 0.693359375f; -static const float C2F = -2.12194440e-4f; +static constexpr float C1F = 0.693359375f; +static constexpr float C2F = -2.12194440e-4f; -static const float PX1expf = 1.9875691500E-4f; -static const float PX2expf = 1.3981999507E-3f; -static const float PX3expf = 8.3334519073E-3f; -static const float PX4expf = 4.1665795894E-2f; -static const float PX5expf = 1.6666665459E-1f; -static const float PX6expf = 5.0000001201E-1f; +static constexpr float PX1expf = 1.9875691500E-4f; +static constexpr float PX2expf = 1.3981999507E-3f; +static constexpr float PX3expf = 8.3334519073E-3f; +static constexpr float PX4expf = 4.1665795894E-2f; +static constexpr float PX5expf = 1.6666665459E-1f; +static constexpr float PX6expf = 5.0000001201E-1f; -static const float LOG2EF = 1.44269504088896341f; // 1/ln(2) +static constexpr float LOG2EF = 1.44269504088896341f; // 1/ln(2) -static inline double egm1(double x, double px) { - x -= px * C1; - x -= px * C2; +static inline double egm1(double x, double n) { + // this cannot be reordered for the double-double trick to work + // i.e., it cannot be re-written as g = x - n * (C1+C2) + // the loss of accuracy comes from the different magnitudes of ln(2) and n + // max(|n|) ~ 2^9 + // ln(2) ~ 2^-1 + volatile double g = x - n * C1; + g -= n * C2; - const double xx = x * x; + const double gg = g * g; - px = PX1exp; - px *= xx; + double px = PX1exp; + px *= gg; px += PX2exp; - px *= xx; + px *= gg; px += PX3exp; - px *= x; + px *= g; double qx = QX1exp; - qx *= xx; + qx *= gg; qx += QX2exp; - qx *= xx; + qx *= gg; qx += QX3exp; - qx *= xx; + qx *= gg; qx += QX4exp; return 2.0 * px / (qx - px); @@ -113,7 +107,7 @@ static inline double egm1(double x, double px) { /// double precision exp function static inline double vexp(double initial_x) { double x = initial_x; - double px = dpfloor(LOG2E * x + 0.5); + double px = std::floor(LOG2E * x + 0.5); const int32_t n = px; x = 1.0 + egm1(x, px); @@ -130,7 +124,7 @@ static inline double vexp(double initial_x) { /// double precision exp(x) - 1 function, used for small x. static inline double vexpm1(double initial_x) { double x = initial_x; - double px = dpfloor(LOG2E * x + 0.5); + double px = std::floor(LOG2E * x + 0.5); const int32_t n = px; const uint64_t twopnm1 = (((uint64_t)(n - 1)) + 1023) << 52; @@ -172,51 +166,52 @@ static inline float egm1(float x) { } /// single precision exp function -static inline float vexp(float initial_x) { - float x = initial_x; - float z = spfloor(LOG2EF * x + 0.5f); +static inline float vexp(float x) { + float z = std::floor(LOG2EF * x + 0.5f); - x -= z * C1F; - x -= z * C2F; + // this cannot be reordered for the double-double trick to work + float volatile g = x - z * C1F; + g -= z * C2F; const int32_t n = z; - z = 1.0f + egm1(x); + z = 1.0f + egm1(g); z *= int322sp((n + 0x7f) << 23); - if (initial_x > MAXLOGF) + if (x > MAXLOGF) z = f_inf(); - if (initial_x < MINLOGF) + if (x < MINLOGF) z = 0.f; return z; } /// single precision exp function -static inline float vexpm1(float initial_x) { - float x = initial_x; - float z = spfloor(LOG2EF * x + 0.5f); +static inline float vexpm1(float x) { + float z = std::floor(LOG2EF * x + 0.5f); + + // this cannot be reordered for the double-double trick to work + volatile float g = x - z * C1F; + g -= z * C2F; - x -= z * C1F; - x -= z * C2F; const int32_t n = z; const int32_t twopnm1 = ((n - 1) + 0x7f) << 23; - x = 2 * (int322sp(twopnm1) * egm1(x) + int322sp(twopnm1)) - 1; + float ret = 2 * (int322sp(twopnm1) * egm1(g) + int322sp(twopnm1)) - 1; - if (initial_x > MAXLOGF) - x = f_inf(); - if (initial_x < MINLOGF) - x = -1.0f; + if (x > MAXLOGF) + ret = f_inf(); + if (x < MINLOGF) + ret = -1.0f; - return x; + return ret; } /// single precision exprelr function -static inline float exprelr(float initial_x) { - if (1.0 + initial_x == 1.0) { +static inline float exprelr(float x) { + if (1.0 + x == 1.0) { return 1.0; } - return initial_x / vexpm1(initial_x); -} + return x / vexpm1(x); +} \ No newline at end of file diff --git a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp index 858786aabf..e0d0f9a32c 100644 --- a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp +++ b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp @@ -8,112 +8,96 @@ #define CATCH_CONFIG_MAIN #include "codegen/fast_math.hpp" -#include #include -float check_over_span_float(float f1(float), - float f2(float), - const float low_limit, - const float span, - const size_t npoints) { - float x = 0.0; - float val = f1(x) - f2(x); - float err = val * val; // 0.0 is an edge case +template ::value>::type> +bool check_over_span(T f_ref(T), + T f_test(T), + const T low_limit, + const T high_limit, + const size_t npoints) { + constexpr uint nULP = 4; + constexpr T eps = std::numeric_limits::epsilon(); + constexpr T one_o_eps = 1.0 / std::numeric_limits::epsilon(); + T low = std::numeric_limits::min() * one_o_eps * 1e2; - for (size_t i = 0; i < npoints; ++i) { - x = low_limit + span * i / npoints; - val = f1(x) - f2(x); - err += val * val; - } - err /= npoints + 1; - return err; -} - -double check_over_span_double(double f1(double), - double f2(double), - const double low_limit, - const double span, - const size_t npoints) { - double x = 0.0; - double val = f1(x) - f2(x); - double err = val * val; // 0.0 is an edge case + T range = high_limit - low_limit; + bool ret = true; for (size_t i = 0; i < npoints; ++i) { - x = low_limit + span * i / npoints; - val = f1(x) - f2(x); - err += val * val; + T x = low_limit + range * i / npoints; + T ref = f_ref(x); + T test = f_test(x); + T diff = std::abs(ref - test); + T max = std::max(std::abs(ref), std::abs(test)); + T tol = max * nULP; + // normalize based on range + if (tol > low) { + tol *= eps; + } else { + diff *= one_o_eps; + } + if (diff > tol && diff != 0.0) { + ret = false; + } } - err /= npoints + 1; - return err; + return ret; } +template ::value>::type> +T exprelr_ref(const T x) { + return (1.0 + x == 1.0) ? 1.0 : x / (std::exp(x) - 1.0); +}; + SCENARIO("Check fast_math") { - constexpr double low_limit = -5.0; - constexpr double span = 10.0; - constexpr size_t npoints = 1000; - constexpr double err_threshold = 1e-10; + constexpr double low_limit = -708.0; + constexpr double high_limit = 708.0; + constexpr float low_limit_f = -87.0f; + constexpr float high_limit_f = 88.0f; + constexpr size_t npoints = 2000; + GIVEN("vexp (double)") { - const double err = check_over_span_double(std::exp, vexp, low_limit, span, npoints); + auto test = check_over_span(std::exp, vexp, low_limit, high_limit, npoints); THEN("error inside threshold") { - REQUIRE(err < err_threshold); + REQUIRE(test); } } GIVEN("vexp (float)") { - const double err = check_over_span_float(std::exp, vexp, low_limit, span, npoints); + auto test = check_over_span(std::exp, vexp, low_limit_f, high_limit_f, npoints); THEN("error inside threshold") { - REQUIRE(err < err_threshold); + REQUIRE(test); } } GIVEN("expm1 (double)") { - const double err = - check_over_span_double([](const double x) -> double { return std::exp(x) - 1; }, - vexpm1, - low_limit, - span, - npoints); + auto test = check_over_span(std::expm1, vexpm1, low_limit, high_limit, npoints); THEN("error inside threshold") { - REQUIRE(err < err_threshold); + REQUIRE(test); } } GIVEN("expm1 (float)") { - const double err = - check_over_span_float([](const float x) -> float { return std::exp(x) - 1; }, - vexpm1, - low_limit, - span, - npoints); + auto test = check_over_span(std::expm1, vexpm1, low_limit_f, high_limit_f, npoints); THEN("error inside threshold") { - REQUIRE(err < err_threshold); + REQUIRE(test); } } GIVEN("exprelr (double)") { - const double err = check_over_span_double( - [](const double x) -> double { return (1.0 + x == 1.0) ? 1.0 : x / (std::exp(x) - 1); }, - exprelr, - low_limit, - span, - npoints); + auto test = check_over_span(exprelr_ref, exprelr, low_limit, high_limit, npoints); THEN("error inside threshold") { - REQUIRE(err < err_threshold); + REQUIRE(test); } } GIVEN("exprelr (float)") { - const float err = check_over_span_float( - [](const float x) -> float { return (1.0 + x == 1.0) ? 1.0 : x / (std::exp(x) - 1); }, - exprelr, - low_limit, - span, - npoints); + auto test = check_over_span(exprelr_ref, exprelr, low_limit_f, high_limit_f, npoints); THEN("error inside threshold") { - REQUIRE(err < err_threshold); + REQUIRE(test); } } -} +} \ No newline at end of file From a66c6069e6e0045b75b3b07c2430007c439f19ce Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Fri, 12 Mar 2021 23:56:47 +0100 Subject: [PATCH 340/871] Defuse with pointers to the statement in a variable is used/defined (BlueBrain/nmodl#514) * Add pointer to the expression statement where a variable is used/defined * Add/improve unit tests + expr_statement -> bin_expression - Add unit tests to verify if IF(tau == 0) was handled - Add unit tests for defuse with pointers - DUInstance returns a binary_expression to take into account variable usage in IF statements (i.e.: IF (tau==0) ) NMODL Repo SHA: BlueBrain/nmodl@45e2b02855e75700bc8ed4da6c5c1bba43022d16 --- src/nmodl/visitors/defuse_analyze_visitor.cpp | 28 +++-- src/nmodl/visitors/defuse_analyze_visitor.hpp | 24 +++- .../unit/visitor/defuse_analyze.cpp | 118 ++++++++++++++++++ 3 files changed, 160 insertions(+), 10 deletions(-) diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 4f4b60e1a0..678603cd4d 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -219,12 +219,23 @@ void DefUseAnalyzeVisitor::visit_statement_block(const ast::StatementBlock& node * and hence not necessary to keep track of assignment operator using stack. */ void DefUseAnalyzeVisitor::visit_binary_expression(const ast::BinaryExpression& node) { + // only the outermost binary expression is important + const bool is_outer_binary_expression = !current_binary_expression; + if (is_outer_binary_expression) { + current_binary_expression = std::static_pointer_cast( + node.get_shared_ptr()); + } + node.get_rhs()->visit_children(*this); if (node.get_op().get_value() == ast::BOP_ASSIGN) { visiting_lhs = true; } node.get_lhs()->visit_children(*this); visiting_lhs = false; + + if (is_outer_binary_expression) { + current_binary_expression = nullptr; + } } void DefUseAnalyzeVisitor::visit_if_statement(const ast::IfStatement& node) { @@ -232,7 +243,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(const ast::IfStatement& node) { auto previous_chain = current_chain; /// starting new if block - previous_chain->push_back(DUInstance(DUState::CONDITIONAL_BLOCK)); + previous_chain->push_back(DUInstance(DUState::CONDITIONAL_BLOCK, current_binary_expression)); current_chain = &(previous_chain->back().children); /// visiting if sub-block @@ -267,10 +278,11 @@ void DefUseAnalyzeVisitor::visit_if_statement(const ast::IfStatement& node) { */ void DefUseAnalyzeVisitor::visit_verbatim(const ast::Verbatim& node) { if (!ignore_verbatim) { - current_chain->push_back(DUInstance(DUState::U)); + current_chain->push_back(DUInstance(DUState::U, current_binary_expression)); } } + /// unsupported statements : we aren't sure how to handle this "yet" and /// hence variables used in any of the below statements are handled separately @@ -358,18 +370,18 @@ void DefUseAnalyzeVisitor::update_defuse_chain(const std::string& name) { const auto is_local = symbol->has_any_property(properties); if (unsupported_node) { - current_chain->push_back(DUInstance(DUState::U)); + current_chain->push_back(DUInstance(DUState::U, current_binary_expression)); } else if (visiting_lhs) { if (is_local) { - current_chain->push_back(DUInstance(DUState::LD)); + current_chain->push_back(DUInstance(DUState::LD, current_binary_expression)); } else { - current_chain->push_back(DUInstance(DUState::D)); + current_chain->push_back(DUInstance(DUState::D, current_binary_expression)); } } else { if (is_local) { - current_chain->push_back(DUInstance(DUState::LU)); + current_chain->push_back(DUInstance(DUState::LU, current_binary_expression)); } else { - current_chain->push_back(DUInstance(DUState::U)); + current_chain->push_back(DUInstance(DUState::U, current_binary_expression)); } } } @@ -396,7 +408,7 @@ void DefUseAnalyzeVisitor::visit_with_new_chain(const ast::Node& node, DUState s } void DefUseAnalyzeVisitor::start_new_chain(DUState state) { - current_chain->push_back(DUInstance(state)); + current_chain->push_back(DUInstance(state, current_binary_expression)); current_chain = ¤t_chain->back().children; } diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index af881aeb75..71f9257fc5 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -82,8 +82,10 @@ class DUInstance { /// usage of variable in case of if like statements std::vector children; - explicit DUInstance(DUState state) - : state(state) {} + explicit DUInstance(DUState state, + const std::shared_ptr binary_expression) + : state(state) + , binary_expression(binary_expression) {} /// analyze all children and return "effective" usage DUState eval() const; @@ -95,6 +97,22 @@ class DUInstance { DUState conditional_block_eval() const; void print(printer::JSONPrinter& printer) const; + + /** \brief binary expression in which the variable is used/defined + * + * We use the binary expression because it is used in both: + * + * - x = ... // expression statement, binary expression + * - IF (x == 0) // not an expression statement, binary expression + * + * We also want the outermost binary expression. Thus, we do not keep track + * of the interior ones. For example: + * + * \f tau = tau + 1 \f + * + * we want to return the full statement, not only \f tau + 1 \f + */ + std::shared_ptr binary_expression; }; @@ -208,6 +226,8 @@ class DefUseAnalyzeVisitor: protected ConstAstVisitor { /// starting visiting lhs of assignment statement bool visiting_lhs = false; + std::shared_ptr current_binary_expression = nullptr; + void process_variable(const std::string& name); void process_variable(const std::string& name, int index); diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 85143b869e..f7b4f42edc 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -7,6 +7,7 @@ #include +#include "ast/binary_expression.hpp" #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" @@ -69,8 +70,13 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { THEN("Def-Use chains for individual usage is printed") { std::string input = reindent_text(nmodl_text); auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string(true) == expected_text); REQUIRE(chains[0].eval() == DUState::D); + + REQUIRE(to_nmodl(*chains[0].chain[0].binary_expression) == "tau = 1"); + REQUIRE(to_nmodl(*chains[0].chain[1].binary_expression) == "tau = 1+tau"); + REQUIRE(to_nmodl(*chains[0].chain[2].binary_expression) == "tau = 1+tau"); } } @@ -95,6 +101,10 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { auto chains = run_defuse_visitor(input, "tau"); REQUIRE(chains[0].to_string(true) == expected_text); REQUIRE(chains[0].eval() == DUState::U); + + REQUIRE(chains[0].chain[0].binary_expression == nullptr); // verbatim begin + REQUIRE(to_nmodl(*chains[0].chain[1].binary_expression) == "tau = 1"); + REQUIRE(chains[0].chain[2].binary_expression == nullptr); // verbatim end } } @@ -132,19 +142,89 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { auto o0 = run_defuse_visitor(input, "o[0]"); REQUIRE(m0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"},{"name":"U"}]})"); + REQUIRE(to_nmodl(*m0[0].chain[0].binary_expression) == "m[0] = m[1]"); + REQUIRE(to_nmodl(*m0[0].chain[1].binary_expression) == "h[1] = m[0]+h[0]"); REQUIRE(m1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"}]})"); + REQUIRE(to_nmodl(*m1[0].chain[0].binary_expression) == "m[0] = m[1]"); REQUIRE(h1[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); + REQUIRE(to_nmodl(*h1[0].chain[0].binary_expression) == "h[1] = m[0]+h[0]"); REQUIRE(tau0[0].to_string() == R"({"DerivativeBlock":[{"name":"LD"}]})"); + REQUIRE(to_nmodl(*tau0[0].chain[0].binary_expression) == "tau[0] = 1"); REQUIRE(tau1[0].to_string() == R"({"DerivativeBlock":[{"name":"LU"}]})"); + REQUIRE(to_nmodl(*tau1[0].chain[0].binary_expression) == + "tau[2] = 1+tau[1]+tau[2]"); REQUIRE(tau2[0].to_string() == R"({"DerivativeBlock":[{"name":"LU"},{"name":"LD"}]})"); + REQUIRE(to_nmodl(*tau2[0].chain[0].binary_expression) == + "tau[2] = 1+tau[1]+tau[2]"); + REQUIRE(to_nmodl(*tau2[0].chain[1].binary_expression) == + "tau[2] = 1+tau[1]+tau[2]"); REQUIRE(n0[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); + REQUIRE(to_nmodl(*n0[0].chain[0].binary_expression) == "n[i+1] = 1+n[i]"); + REQUIRE(to_nmodl(*n0[0].chain[1].binary_expression) == "n[i+1] = 1+n[i]"); REQUIRE(n1[0].to_string() == R"({"DerivativeBlock":[{"name":"U"},{"name":"D"}]})"); + REQUIRE(to_nmodl(*n1[0].chain[0].binary_expression) == "n[i+1] = 1+n[i]"); + REQUIRE(to_nmodl(*n1[0].chain[1].binary_expression) == "n[i+1] = 1+n[i]"); REQUIRE(o0[0].to_string() == R"({"DerivativeBlock":[{"name":"D"}]})"); + REQUIRE(to_nmodl(*o0[0].chain[0].binary_expression) == "o[i] = 1"); } } } + GIVEN("global variable used in if statement (lhs)") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + IF (tau == 0) { + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"U"}]}]}]})"; + + THEN("tau is used") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + REQUIRE(chains[0].chain[0].binary_expression == nullptr); + REQUIRE(chains[0].chain[0].children[0].binary_expression == nullptr); + REQUIRE(to_nmodl(*chains[0].chain[0].children[0].children[0].binary_expression) == + "tau == 0"); + } + } + + GIVEN("global variable used in if statement (rhs)") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + IF (0 == tau) { + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"U"}]}]}]})"; + + THEN("tau is used") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + REQUIRE(chains[0].chain[0].binary_expression == nullptr); + REQUIRE(chains[0].chain[0].children[0].binary_expression == nullptr); + REQUIRE(to_nmodl(*chains[0].chain[0].children[0].children[0].binary_expression) == + "0 == tau"); + } + } + GIVEN("global variable definition in else block") { std::string nmodl_text = R"( NEURON { @@ -172,6 +252,44 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { } } + GIVEN("global variable use in if statement + definition and use in if and else blocks") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + IF (tau == 0) { + tau = 1 + tau + } ELSE { + tau = 2 + tau + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"U"},{"name":"U"},{"name":"D"}]},{"ELSE":[{"name":"U"},{"name":"D"}]}]}]})"; + + THEN("tau is used and then used in its definitions") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].chain[0].binary_expression == nullptr); // CONDITIONAL_BLOCK + REQUIRE(chains[0].chain[0].children[0].binary_expression == nullptr); // IF + REQUIRE(to_nmodl(*chains[0].chain[0].children[0].children[0].binary_expression) == + "tau == 0"); + REQUIRE(to_nmodl(*chains[0].chain[0].children[0].children[1].binary_expression) == + "tau = 1+tau"); + REQUIRE(to_nmodl(*chains[0].chain[0].children[0].children[2].binary_expression) == + "tau = 1+tau"); + REQUIRE(chains[0].chain[0].children[1].binary_expression == nullptr); // ELSE + REQUIRE(to_nmodl(*chains[0].chain[0].children[1].children[0].binary_expression) == + "tau = 2+tau"); + REQUIRE(to_nmodl(*chains[0].chain[0].children[1].children[1].binary_expression) == + "tau = 2+tau"); + } + } + GIVEN("global variable usage in else block") { std::string nmodl_text = R"( NEURON { From 0c6a2b4faf95987ee26eef00da33137ff6fb50ba Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 16 Mar 2021 17:10:08 +0100 Subject: [PATCH 341/871] Add log10 function to ISPC (BlueBrain/nmodl#553) * Added log10 function in ISPC fast math * Added test for new log10 functions NMODL Repo SHA: BlueBrain/nmodl@66acd4675d7275ebda5251af8db043b8d5f16f2f --- src/nmodl/codegen/fast_math.hpp | 21 +++++++++++++++- src/nmodl/codegen/fast_math.ispc | 11 ++++++++ .../transpiler/unit/fast_math/fast_math.cpp | 25 ++++++++++++++++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index 6546058794..cda26aba10 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -18,6 +18,9 @@ * \brief Implementation of different math functions */ +namespace nmodl { +namespace fast_math { + static inline double uint642dp(uint64_t ll) { return *((double*) (&ll)); } @@ -75,6 +78,9 @@ static constexpr float PX6expf = 5.0000001201E-1f; static constexpr float LOG2EF = 1.44269504088896341f; // 1/ln(2) +static constexpr double LOG10E = 2.302585092994046; // log(10) +static constexpr float LOG10F = 2.302585f; // log(10) + static inline double egm1(double x, double n) { // this cannot be reordered for the double-double trick to work // i.e., it cannot be re-written as g = x - n * (C1+C2) @@ -214,4 +220,17 @@ static inline float exprelr(float x) { } return x / vexpm1(x); -} \ No newline at end of file +} + +/// double precision log10 function +static inline double log10(double f) { + return std::log(f) / LOG10E; +} + +/// single precision log10 function +static inline float log10(float f) { + return std::log(f) / LOG10F; +} + +} // namespace fast_math +} // namespace nmodl diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc index 4f8de0a9ac..1aa3e66952 100644 --- a/src/nmodl/codegen/fast_math.ispc +++ b/src/nmodl/codegen/fast_math.ispc @@ -81,6 +81,9 @@ static const uniform float PX6expf = 5.0000001201E-1f; static const uniform float LOG2EF = 1.44269504088896341f; // 1/ln(2) +static const uniform double LOG10E = 2.302585092994046d; // log(10) double +static const uniform float LOG10F = 2.3025851f; // log(10) float + static inline double egm1(double x, double px) { x -= px * 6.93145751953125d-1; @@ -220,4 +223,12 @@ static inline float exprelr(float initial_x) { return initial_x/vexpm1(initial_x); } +/// double precision log10 function +static inline double log10(double f) { + return log(f)/LOG10E; +} +/// single precision log10 function +static inline float log10(float f) { + return log(f)/LOG10F; +} diff --git a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp index e0d0f9a32c..668675261b 100644 --- a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp +++ b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp @@ -11,6 +11,8 @@ #include +namespace nmodl { +namespace fast_math { template ::value>::type> bool check_over_span(T f_ref(T), @@ -57,6 +59,10 @@ SCENARIO("Check fast_math") { constexpr float low_limit_f = -87.0f; constexpr float high_limit_f = 88.0f; constexpr size_t npoints = 2000; + constexpr double min_double = std::numeric_limits::min(); + constexpr double max_double = std::numeric_limits::max(); + constexpr double min_float = std::numeric_limits::min(); + constexpr double max_float = std::numeric_limits::max(); GIVEN("vexp (double)") { auto test = check_over_span(std::exp, vexp, low_limit, high_limit, npoints); @@ -100,4 +106,21 @@ SCENARIO("Check fast_math") { REQUIRE(test); } } -} \ No newline at end of file + GIVEN("log10 (double)") { + auto test = check_over_span(std::log10, log10, min_double, max_double, npoints); + + THEN("error inside threshold") { + REQUIRE(test); + } + } + GIVEN("log10 (float)") { + auto test = check_over_span(std::log10, log10, min_float, max_float, npoints); + + THEN("error inside threshold") { + REQUIRE(test); + } + } +} + +} // namespace fast_math +} // namespace nmodl From fa940c5dc5ab54fa26275ae38039cbce41a7c12d Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Thu, 18 Mar 2021 23:30:53 +0100 Subject: [PATCH 342/871] Improved log10 by using multiplication (BlueBrain/nmodl#555) NMODL Repo SHA: BlueBrain/nmodl@03408acb1f3b5d21067b57e48cba02a652d75ae6 --- src/nmodl/codegen/fast_math.hpp | 8 ++++---- src/nmodl/codegen/fast_math.ispc | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index cda26aba10..957ba83658 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -78,8 +78,8 @@ static constexpr float PX6expf = 5.0000001201E-1f; static constexpr float LOG2EF = 1.44269504088896341f; // 1/ln(2) -static constexpr double LOG10E = 2.302585092994046; // log(10) -static constexpr float LOG10F = 2.302585f; // log(10) +static constexpr double LOG10E = 0.4342944819032518; // 1/log(10) +static constexpr float LOG10F = 0.4342945f; // 1/log(10) static inline double egm1(double x, double n) { // this cannot be reordered for the double-double trick to work @@ -224,12 +224,12 @@ static inline float exprelr(float x) { /// double precision log10 function static inline double log10(double f) { - return std::log(f) / LOG10E; + return std::log(f) * LOG10E; } /// single precision log10 function static inline float log10(float f) { - return std::log(f) / LOG10F; + return std::log(f) * LOG10F; } } // namespace fast_math diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc index 1aa3e66952..0d0ba97617 100644 --- a/src/nmodl/codegen/fast_math.ispc +++ b/src/nmodl/codegen/fast_math.ispc @@ -81,8 +81,8 @@ static const uniform float PX6expf = 5.0000001201E-1f; static const uniform float LOG2EF = 1.44269504088896341f; // 1/ln(2) -static const uniform double LOG10E = 2.302585092994046d; // log(10) double -static const uniform float LOG10F = 2.3025851f; // log(10) float +static const uniform double LOG10E = 0.4342944819032518d; // 1/log(10) double +static const uniform float LOG10F = 0.4342945f; // 1/log(10) float static inline double egm1(double x, double px) { @@ -225,10 +225,10 @@ static inline float exprelr(float initial_x) { /// double precision log10 function static inline double log10(double f) { - return log(f)/LOG10E; + return log(f) * LOG10E; } /// single precision log10 function static inline float log10(float f) { - return log(f)/LOG10F; + return log(f) * LOG10F; } From eb2f01e0dce7d2c7f7a6069ccf2810179b4413af Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 25 Mar 2021 11:10:14 +0100 Subject: [PATCH 343/871] Add flexibility when using or not legacy units (BlueBrain/nmodl#559) NMODL Repo SHA: BlueBrain/nmodl@bde46c4c8900833115c5e59b75e4c5c9f52171b7 --- src/nmodl/codegen/codegen_c_visitor.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index cf74d91ffa..acf769bf9c 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2011,8 +2011,14 @@ void CodegenCVisitor::print_nmodl_constants() { printer->add_newline(2); printer->add_line("/** constants used in nmodl */"); for (const auto& it: info.factor_definitions) { - printer->add_line("static const double {} = {};"_format(it->get_node_name(), - it->get_value()->get_value())); +#ifdef USE_LEGACY_UNITS + const std::string format_string = "static const double {} = {:g};"; +#else + const std::string format_string = "static const double {} = {:.18g};"; +#endif + printer->add_line(fmt::format(format_string, + it->get_node_name(), + stod(it->get_value()->get_value()))); } } } From 025d20ad56aa47d94f6881855fa8f444a756264e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 25 Mar 2021 11:41:53 +0100 Subject: [PATCH 344/871] Travis has been replaced by github actions (BlueBrain/nmodl#560) NMODL Repo SHA: BlueBrain/nmodl@41a3d604443a8e734cf603bc681c1bda6b6e732d --- .travis.yml | 153 ---------------------------------------------------- 1 file changed, 153 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 03a0186c2d..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,153 +0,0 @@ -#============================================================================= -# Travis NMODL settings -#============================================================================= - -#============================================================================= -# Environment -#============================================================================= -# Use new Travis infrastructure (Docker can't sudo yet) -sudo: false - -#============================================================================= -# Build matrix -#============================================================================= -matrix: - fast_finish: true - include: - - language: cpp - os: linux - dist: bionic - env: - - MATRIX_EVAL="CXX=g++" - - PYTHON_VERSION=3.8 - - language: cpp - os: osx - osx_image: xcode11.3 - env: - - MATRIX_EVAL="CXX=c++" - -#============================================================================= -# Cache directories -#============================================================================= -cache: - directories: - - $HOME/.cache/pip - - $HOME/Library/Caches/Homebrew - -#============================================================================= -# Common Packages -#============================================================================= -addons: - # for Linux builds, we use APT - apt: - sources: - - ubuntu-toolchain-r-test - packages: - - flex - - bison - - dvipng - - doxygen - - libboost-all-dev - - libfl-dev - - libopenmpi-dev - - openmpi-bin - - pandoc - - python3-dev - - python3-pip - - texlive-latex-recommended - - texlive-latex-extra - # for Mac builds, we use Homebrew - homebrew: - packages: - - boost - - flex - - bison - - openmpi - - python@3 - update: true - -#============================================================================= -# Install dependencies -#============================================================================= -before_install: - # brew installed flex and bison is not in $PATH - # unlink python2 and use python3 as it's required for nmodl - # install older bison because of #314 - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then - export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:$PATH; - brew unlink python@2; - brew link --overwrite python@3.8; - export SDKROOT="$(xcrun --show-sdk-path)"; - curl -L -o cmake-3.8.0.tar.gz https://github.com/Kitware/CMake/releases/download/v3.8.0/cmake-3.8.0-Darwin-x86_64.tar.gz; - mkdir -p $HOME/cmake && tar -xzf cmake-3.8.0.tar.gz -C $HOME/cmake --strip-components=3; - else - pyenv global $PYTHON_VERSION; - curl -L -o cmake-3.8.0.tar.gz https://github.com/Kitware/CMake/releases/download/v3.8.0/cmake-3.8.0-Linux-x86_64.tar.gz; - mkdir -p $HOME/cmake && tar -xzf cmake-3.8.0.tar.gz -C $HOME/cmake --strip-components=1; - fi - - export PATH=$HOME/cmake/bin:$PATH - - eval "${MATRIX_EVAL}" - -#============================================================================= -# Install NMODL dependencies -#============================================================================= -install: - # there is a bug in m2r if sphinx>2.4.4. There is a pr in m2r to solve the - # issue here: https://github.com/miyakogi/m2r/pull/55 . When it is done - # I will remove the sphinx downgrade. Change setup.py requirements when it - # be the case, Katta - - echo "------- Install Dependencies -------" - - pip3 install -U pip setuptools scikit-build Jinja2 PyYAML pytest sympy - -#============================================================================= -# Build, test and install -#============================================================================= -script: - - echo "------- Build, Test and Install -------" - - mkdir build && pushd build - - cmake .. -DPYTHON_EXECUTABLE=$(which python3) -DCMAKE_INSTALL_PREFIX=$HOME/nmodl - - make -j 2; - if [ $? -ne 0 ]; then - make VERBOSE=1; - fi - - env CTEST_OUTPUT_ON_FAILURE=1 make test - - make install - - popd - -#============================================================================= -# Build Documentation, CoreNEURON and run tests -#============================================================================= -after_success: - # Scikit-build does not support build_ext --inplace in custom commands. - # More info at: https://github.com/scikit-build/scikit-build/issues/489 - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - echo "------- Build Documentation -------"; - python3 setup.py build_ext --inplace docs -j 2 -G "Unix Makefiles" || travis_terminate 1; - cd $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx; - rm -rf doctest doctrees && touch .nojekyll; - echo "" > index.html; - fi - -#============================================================================= -# Notifications -#============================================================================= -notifications: - email: - recipients: pramod.s.kumbhar@gmail.com - on_success: change - on_failure: always - - -#============================================================================= -# Documentation deployment -#============================================================================= -deploy: - provider: pages - skip_cleanup: true - github_token: $GITHUB_TOKEN - keep_history: false - local_dir: $TRAVIS_BUILD_DIR/_skbuild/linux-x86_64-3.8/setuptools/sphinx/ - target_branch: gh-pages - on: - branch: master - condition: $TRAVIS_OS_NAME = linux From dd34498dd0923942a923a0bda2f213f924e07de1 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 30 Mar 2021 14:58:21 +0200 Subject: [PATCH 345/871] Fix ConstantBlock check in CodegenCompatibilityVisitor (BlueBrain/nmodl#562) * codegen backends were already supporting CONSTANT block * only CodegenCompatibilityVisitor was missing this fact * remove check in CodegenCompatibilityVisitor fixes BlueBrain/nmodl#323 NMODL Repo SHA: BlueBrain/nmodl@1fffd29acc64d82c957432cc50e4d203dbcfc9c4 --- src/nmodl/codegen/codegen_c_visitor.cpp | 4 ++-- src/nmodl/codegen/codegen_compatibility_visitor.cpp | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index acf769bf9c..c079d01df4 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -723,7 +723,7 @@ bool CodegenCVisitor::ion_variable_struct_required() const { /** * \details This can be override in the backend. For example, parameters can be constant * except in INITIAL block where they are set to 0. As initial block is/can be - * executed on c/cpu backend, gpu/cuda backend can mark the parameter as constnat. + * executed on c/cpu backend, gpu/cuda backend can mark the parameter as constant. */ bool CodegenCVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); @@ -2009,7 +2009,7 @@ std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& void CodegenCVisitor::print_nmodl_constants() { if (!info.factor_definitions.empty()) { printer->add_newline(2); - printer->add_line("/** constants used in nmodl */"); + printer->add_line("/** constants used in nmodl from UNITS */"); for (const auto& it: info.factor_definitions) { #ifdef USE_LEGACY_UNITS const std::string format_string = "static const double {} = {:g};"; diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 2bc8d742ac..488ce187b3 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -34,8 +34,6 @@ const std::map &CodegenCompatibilityVisitor::return_error_with_name}, {AstNodeType::FUNCTION_TABLE_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::CONSTANT_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::CONSTRUCTOR_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::DESTRUCTOR_BLOCK, From 5c42435d5a2b6116a1fd635926c210daa924da90 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 7 Apr 2021 01:52:12 +0200 Subject: [PATCH 346/871] Fix ci for old pip version (BlueBrain/nmodl#569) sphinx ask for docutils > 0.12 sphinx-rtd-theme for docutils < 0.17 if sphinx-rtd-theme is installed first it seems that sphinx install a upper version of docutils. With newer version of python or pip (I don't know), it takes care of it, but not on ubuntu 18.04 and/or macos NMODL Repo SHA: BlueBrain/nmodl@f88e2f54b7b699936e04775e0182e205f2bae525 --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1f95253e94..914648c53e 100644 --- a/setup.py +++ b/setup.py @@ -127,9 +127,9 @@ def _config_exe(exe_name): "nbconvert<6.0", # prevents issues with nbsphinx "nbsphinx>=0.3.2", "pytest>=3.7.2", - "sphinx-rtd-theme", "sphinx>=2.0", "sphinx<3.0", # prevents issue with m2r where m2r uses an old API no more supported with sphinx>=3.0 + "sphinx-rtd-theme", ] + install_requirements, install_requires=install_requirements, From 1c1b8b4b5d36d51ac33b3be8a9c11d79c0adfd9c Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 7 Apr 2021 11:04:38 +0200 Subject: [PATCH 347/871] Remove support of AoS layout (BlueBrain/nmodl#565) Consider that we always use SoA. NMODL currently has support for AoS layout. This was added for compatibility and testing with MOD2C and CoreNEURON. We remove AoS support due to following reasons: In practice, we never use AoS layout for CPU vectorisation or GPUs This part remains untested because of the same reason Simplify the code What have been done: Remove CLI option in nmodl for layout Remove all logic in src/codegen where we we are using LayoutType::aos. Fix BlueBrain/nmodl#563 NMODL Repo SHA: BlueBrain/nmodl@520f60e4bcd5ef3ffbbdf5b281f63aa41cea2d99 --- src/nmodl/codegen/codegen_acc_visitor.hpp | 6 +- src/nmodl/codegen/codegen_c_visitor.cpp | 77 +++++-------------- src/nmodl/codegen/codegen_c_visitor.hpp | 45 ----------- src/nmodl/codegen/codegen_cuda_visitor.hpp | 7 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 22 +----- src/nmodl/codegen/codegen_omp_visitor.hpp | 6 +- src/nmodl/nmodl/main.cpp | 42 +++++----- .../transpiler/unit/codegen/codegen_ispc.cpp | 4 +- 8 files changed, 51 insertions(+), 158 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 775746f99d..2c367ab6ed 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -98,17 +98,15 @@ class CodegenAccVisitor: public CodegenCVisitor { public: CodegenAccVisitor(const std::string& mod_file, const std::string& output_dir, - LayoutType layout, const std::string& float_type, bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies) {} + : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} CodegenAccVisitor(const std::string& mod_file, std::ostream& stream, - LayoutType layout, const std::string& float_type, bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} + : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index c079d01df4..0d1169e174 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1921,7 +1921,7 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { name = "&" + name; } if (token == "_STRIDE") { - name = (layout == LayoutType::soa) ? "pnodecount+id" : "1"; + name = "pnodecount+id"; } result += name; } @@ -2024,18 +2024,6 @@ void CodegenCVisitor::print_nmodl_constants() { } -void CodegenCVisitor::print_memory_layout_getter() { - printer->add_newline(2); - printer->add_line("static inline int get_memory_layout() {"); - if (layout == LayoutType::aos) { - printer->add_line(" return 1; //aos"); - } else { - printer->add_line(" return 0; //soa"); - } - printer->add_line("}"); -} - - void CodegenCVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); print_device_method_annotation(); @@ -2093,14 +2081,6 @@ void CodegenCVisitor::print_memb_list_getter() { } -void CodegenCVisitor::print_post_channel_iteration_common_code() { - if (layout == LayoutType::aos) { - printer->add_line("data = ml->data + id*{};"_format(float_variables_size())); - printer->add_line("indexes = ml->pdata + id*{};"_format(int_variables_size())); - } -} - - void CodegenCVisitor::print_namespace_start() { printer->add_newline(2); printer->start_block("namespace coreneuron"); @@ -2182,18 +2162,14 @@ std::string CodegenCVisitor::float_variable_name(const SymbolType& symbol, // clang-format off if (symbol->is_array()) { if (use_instance) { - auto stride = (layout == LayoutType::soa) ? dimension : num_float; - return "(inst->{}+id*{})"_format(name, stride); + return "(inst->{}+id*{})"_format(name, dimension); } - auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id*{}"_format(position, dimension) : "{}"_format(position); - return "(data+{})"_format(stride); + return "(data + {}*pnodecount + id*{})"_format(position, dimension); } if (use_instance) { - auto stride = (layout == LayoutType::soa) ? "id" : "id*{}"_format(num_float); - return "inst->{}[{}]"_format(name, stride); + return "inst->{}[id]"_format(name); } - auto stride = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); - return "data[{}]"_format(stride); + return "data[{}*pnodecount + id]"_format(position); // clang-format on } @@ -2203,29 +2179,24 @@ std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, bool use_instance) const { auto position = position_of_int_var(name); auto num_int = int_variables_size(); - std::string offset; // clang-format off if (symbol.is_index) { - offset = std::to_string(position); if (use_instance) { - return "inst->{}[{}]"_format(name, offset); + return "inst->{}[{}]"_format(name, position); } - return "indexes[{}]"_format(offset); + return "indexes[{}]"_format(position); } if (symbol.is_integer) { if (use_instance) { - offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id*{}+{}"_format(num_int, position); - return "inst->{}[{}]"_format(name, offset); + return "inst->{}[{}*pnodecount+id]"_format(name, position); } - offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "id"; - return "indexes[{}]"_format(offset); + return "indexes[{}*pnodecount+id]"_format(position); } - offset = (layout == LayoutType::soa) ? "{}*pnodecount+id"_format(position) : "{}"_format(position); if (use_instance) { - return "inst->{}[indexes[{}]]"_format(name, offset); + return "inst->{}[indexes[{}*pnodecount + id]]"_format(name, position); } auto data = symbol.is_vdata ? "_vdata" : "_data"; - return "nt->{}[indexes[{}]]"_format(data, offset); + return "nt->{}[indexes[{}]]"_format(data, position); // clang-format on } @@ -2622,8 +2593,6 @@ void CodegenCVisitor::print_global_variables_for_hoc() { * - If nrn_get_mechtype is < -1 means that mechanism is not used in the * context of neuron execution and hence could be ignored in coreneuron * execution. - * - Each mechanism could have different layout and hence we register the - * layout with the simulator. In practice all mechanisms have same layout. * - Ions are internally defined and their types can be queried similar to * other mechanisms. * - hoc_register_var may not be needed in the context of coreneuron @@ -2646,7 +2615,7 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("}"); printer->add_newline(); - printer->add_line("_nrn_layout_reg(mech_type, get_memory_layout());"); + printer->add_line("_nrn_layout_reg(mech_type, 0);"); // 0 for SoA // register mechanism auto args = register_mechanism_arguments(); @@ -2999,7 +2968,7 @@ void CodegenCVisitor::print_global_variable_setup() { value = *value_ptr; } /// use %g to be same as nocmodl in neuron - printer->add_line("{} = {};"_format(name, "{:g}"_format(value))); + printer->add_line("{} = {:g};"_format(name, value)); } } @@ -3012,7 +2981,7 @@ void CodegenCVisitor::print_global_variable_setup() { value = *value_ptr; } /// use %g to be same as nocmodl in neuron - printer->add_line("{} = {};"_format(name, "{:g}"_format(value))); + printer->add_line("{} = {:g};"_format(name, value)); } if (info.table_count > 0) { @@ -3139,10 +3108,8 @@ void CodegenCVisitor::print_instance_variable_setup() { } std::string stride; - if (layout == LayoutType::soa) { - printer->add_line("int pnodecount = ml->_nodecount_padded;"); - stride = "*pnodecount"; - } + printer->add_line("int pnodecount = ml->_nodecount_padded;"); + stride = "*pnodecount"; printer->add_line("Datum* indexes = ml->pdata;"); @@ -3317,8 +3284,6 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_channel_iteration_tiling_block_begin(BlockType::Initial); print_channel_iteration_block_begin(BlockType::Initial); - print_post_channel_iteration_common_code(); - if (info.net_receive_node != nullptr) { printer->add_line("{} = -1e20;"_format(get_variable_name("tsave"))); } @@ -3411,7 +3376,6 @@ void CodegenCVisitor::print_watch_check() { print_global_function_common_code(BlockType::Watch); print_channel_iteration_tiling_block_begin(BlockType::Watch); print_channel_iteration_block_begin(BlockType::Watch); - print_post_channel_iteration_common_code(); if (info.is_voltage_used_by_watch_statements()) { printer->add_line("int node_id = node_index[id];"); @@ -3798,9 +3762,7 @@ void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { })->index; const auto num_int = int_variables_size(); - std::string offset = (layout == LayoutType::soa) ? "{}*pnodecount + id"_format(index) - : "{} + id*{}"_format(index, num_int); - printer->add_text("const size_t offset = {};"_format(offset)); + printer->add_text("const size_t offset = {}*pnodecount + id;"_format(index)); printer->add_newline(); printer->add_line( "const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]];"); @@ -3925,7 +3887,7 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { auto list_num = info.derivimplicit_list_num; auto block_name = block->get_node_name(); auto primes_size = info.primes_size; - auto stride = (layout == LayoutType::aos) ? "" : "*pnodecount+id"; + auto stride = "*pnodecount+id"; printer->add_newline(2); @@ -4037,7 +3999,6 @@ void CodegenCVisitor::print_nrn_state() { print_global_function_common_code(BlockType::State); print_channel_iteration_tiling_block_begin(BlockType::State); print_channel_iteration_block_begin(BlockType::State); - print_post_channel_iteration_common_code(); printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); @@ -4252,7 +4213,6 @@ void CodegenCVisitor::print_nrn_cur() { print_global_function_common_code(BlockType::Equation); print_channel_iteration_tiling_block_begin(BlockType::Equation); print_channel_iteration_block_begin(BlockType::Equation); - print_post_channel_iteration_common_code(); print_nrn_cur_kernel(*info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); if (!nrn_cur_reduction_loop_required()) { @@ -4300,7 +4260,6 @@ void CodegenCVisitor::print_namespace_end() { void CodegenCVisitor::print_common_getters() { print_first_pointer_var_index_getter(); - print_memory_layout_getter(); print_net_receive_arg_size_getter(); print_thread_getters(); print_num_variable_getter(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 2cffb37cc9..5432ff1302 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -132,20 +132,6 @@ struct IndexVariableInfo { }; -/** - * \enum LayoutType - * \brief Represents memory layout to use for code generation - * - */ -enum class LayoutType { - /// array of structure - aos, - - /// structure of array - soa -}; - - /** * \class ShadowUseStatement * \brief Represents ion write statement during code generation @@ -270,11 +256,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ std::string float_type = codegen::naming::DEFAULT_FLOAT_TYPE; - /** - * Memory layout for code generation - */ - LayoutType layout; - /** * All ast information for code generation */ @@ -1121,13 +1102,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_thread_getters(); - /** - * Print the getter method for memory layout - * - */ - void print_memory_layout_getter(); - - /** * Print the getter method for index position of first pointer variable * @@ -1314,12 +1288,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_channel_iteration_block_end(); - /** - * Print common code post channel instance iteration - */ - void print_post_channel_iteration_common_code(); - - /** * Print function and procedures prototype declaration */ @@ -1644,7 +1612,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { CodegenCVisitor(const std::string& mod_filename, const std::string& output_dir, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies, const std::string& extension, @@ -1653,13 +1620,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { , wrapper_printer(new CodePrinter(output_dir + "/" + mod_filename + wrapper_ext)) , printer(target_printer) , mod_filename(mod_filename) - , layout(layout) , float_type(float_type) , optimize_ionvar_copies(optimize_ionvar_copies) {} CodegenCVisitor(const std::string& mod_filename, std::ostream& stream, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies, const std::string& extension, @@ -1668,7 +1633,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { , wrapper_printer(new CodePrinter(stream)) , printer(target_printer) , mod_filename(mod_filename) - , layout(layout) , float_type(float_type) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1687,21 +1651,18 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param mod_filename The name of the model for which code should be generated. * It is used for constructing an output filename. * \param output_dir The directory where target C file should be generated. - * \param layout The memory layout to be used for data structure generation. * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. * \param extension The file extension to use. This defaults to \c .cpp . */ CodegenCVisitor(const std::string& mod_filename, const std::string& output_dir, - LayoutType layout, std::string float_type, const bool optimize_ionvar_copies, const std::string& extension = ".cpp") : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , printer(target_printer) , mod_filename(mod_filename) - , layout(layout) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1718,19 +1679,16 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param mod_filename The name of the model for which code should be generated. * It is used for constructing an output filename. * \param stream The output stream onto which to write the generated code - * \param layout The memory layout to be used for data structure generation. * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. */ CodegenCVisitor(const std::string& mod_filename, std::ostream& stream, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies) : target_printer(new CodePrinter(stream)) , printer(target_printer) , mod_filename(mod_filename) - , layout(layout) , float_type(float_type) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1747,21 +1705,18 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * * \param mod_filename The name of the model for which code should be generated. * It is used for constructing an output filename. - * \param layout The memory layout to be used for data structure generation. * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. * \param target_printer A printer defined outside this visitor to be used for the code * generation */ CodegenCVisitor(std::string mod_filename, - LayoutType layout, std::string float_type, const bool optimize_ionvar_copies, std::shared_ptr& target_printer) : target_printer(target_printer) , printer(target_printer) , mod_filename(mod_filename) - , layout(layout) , float_type(float_type) , optimize_ionvar_copies(optimize_ionvar_copies) {} diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index b7d309a6b3..2578747b89 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -106,18 +106,15 @@ class CodegenCudaVisitor: public CodegenCVisitor { public: CodegenCudaVisitor(const std::string& mod_file, const std::string& output_dir, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies, ".cu") { - } + : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies, ".cu") {} CodegenCudaVisitor(const std::string& mod_file, std::ostream& stream, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} + : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 589dcb9614..90ae56c5fe 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -220,32 +220,18 @@ class CodegenIspcVisitor: public CodegenCVisitor { public: CodegenIspcVisitor(const std::string& mod_file, const std::string& output_dir, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, - output_dir, - layout, - float_type, - optimize_ionvar_copies, - ".ispc", - ".cpp") - , fallback_codegen(mod_file, layout, float_type, optimize_ionvar_copies, wrapper_printer) {} + : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies, ".ispc", ".cpp") + , fallback_codegen(mod_file, float_type, optimize_ionvar_copies, wrapper_printer) {} CodegenIspcVisitor(const std::string& mod_file, std::ostream& stream, - LayoutType layout, const std::string& float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, - stream, - layout, - float_type, - optimize_ionvar_copies, - ".ispc", - ".cpp") - , fallback_codegen(mod_file, layout, float_type, optimize_ionvar_copies, wrapper_printer) {} + : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies, ".ispc", ".cpp") + , fallback_codegen(mod_file, float_type, optimize_ionvar_copies, wrapper_printer) {} void visit_function_call(const ast::FunctionCall& node) override; void visit_var_name(const ast::VarName& node) override; diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index 8bb09e3a87..23a3976d2e 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -72,17 +72,15 @@ class CodegenOmpVisitor: public CodegenCVisitor { public: CodegenOmpVisitor(std::string mod_file, std::string output_dir, - LayoutType layout, std::string float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, layout, float_type, optimize_ionvar_copies) {} + : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} CodegenOmpVisitor(std::string mod_file, std::stringstream& stream, - LayoutType layout, std::string float_type, const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, layout, float_type, optimize_ionvar_copies) {} + : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/nmodl/main.cpp index 0fe75550ba..310dd125ea 100644 --- a/src/nmodl/nmodl/main.cpp +++ b/src/nmodl/nmodl/main.cpp @@ -152,9 +152,6 @@ int main(int argc, const char* argv[]) { /// true if symbol table should be printed bool show_symtab(false); - /// memory layout for code generation - std::string layout("soa"); - /// floating point data type std::string data_type("double"); @@ -247,12 +244,8 @@ int main(int argc, const char* argv[]) { "Write symbol table to stdout ({})"_format(show_symtab))->ignore_case(); auto codegen_opt = app.add_subcommand("codegen", "Code generation options")->ignore_case(); - codegen_opt->add_option("--layout", - layout, - "Memory layout for code generation", - true)->ignore_case()->check(CLI::IsMember({"aos", "soa"})); codegen_opt->add_option("--datatype", - layout, + data_type, "Data type for floating point variables", true)->ignore_case()->check(CLI::IsMember({"float", "double"})); codegen_opt->add_flag("--force", @@ -507,41 +500,48 @@ int main(int argc, const char* argv[]) { } { - auto mem_layout = layout == "aos" ? codegen::LayoutType::aos : codegen::LayoutType::soa; - - if (ispc_backend) { logger->info("Running ISPC backend code generator"); - CodegenIspcVisitor visitor( - modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); + CodegenIspcVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } else if (oacc_backend) { logger->info("Running OpenACC backend code generator"); - CodegenAccVisitor visitor( - modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); + CodegenAccVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } else if (omp_backend) { logger->info("Running OpenMP backend code generator"); - CodegenOmpVisitor visitor( - modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); + CodegenOmpVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } else if (c_backend) { logger->info("Running C backend code generator"); - CodegenCVisitor visitor( - modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); + CodegenCVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } if (cuda_backend) { logger->info("Running CUDA backend code generator"); - CodegenCudaVisitor visitor( - modfile, output_dir, mem_layout, data_type, optimize_ionvar_copies_codegen); + CodegenCudaVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp index 9c0418f5b7..45a1dc0c5e 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp @@ -69,8 +69,8 @@ class IspcCodegenTestHelper { /// initialize CodegenIspcVisitor std::ostream oss(&strbuf); - codegen_ispc_visitor = std::make_shared( - "unit_test", oss, codegen::LayoutType::soa, "double", false); + codegen_ispc_visitor = + std::make_shared("unit_test", oss, "double", false); codegen_ispc_visitor->setup(*ast); } From 19a8a0b26ed5c9d3b6524c5f8e906d59c3108e5d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 7 Apr 2021 21:24:27 +0200 Subject: [PATCH 348/871] Fix documentation to reduce warnings (BlueBrain/nmodl#571) Fix version of jupyter-client, should be removed when the problem with the version 1.6.2 is fixed upstream. https://github.com/jupyter/jupyter_client/issues/637 NMODL Repo SHA: BlueBrain/nmodl@c9fead60e6b71079116b3d976430f152ee5fd0b8 --- docs/nmodl/transpiler/Doxyfile.in | 262 ++++++++++++------ setup.py | 1 + .../language/templates/visitors/visitor.hpp | 4 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 4 +- .../sympy_replace_solutions_visitor.cpp | 2 +- .../sympy_replace_solutions_visitor.hpp | 69 +++-- 6 files changed, 211 insertions(+), 131 deletions(-) diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index f1d13619a5..8a9ec48c67 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -1,4 +1,4 @@ -# Doxyfile 1.8.15 +# Doxyfile 1.9.1 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project. @@ -197,6 +197,16 @@ SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + # If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first # line (until the first dot) of a Qt-style comment as the brief description. If # set to NO, the Qt-style will behave just like regular Qt-style comments (thus @@ -217,6 +227,14 @@ QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + # If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the # documentation from any documented member that it re-implements. # The default value is: YES. @@ -253,12 +271,6 @@ TAB_SIZE = 4 ALIASES = -# This tag can be used to specify a number of word-keyword mappings (TCL only). -# A mapping has the form "name=value". For example adding "class=itcl::class" -# will allow you to use the command class in the itcl::class meaning. - -TCL_SUBST = - # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources # only. Doxygen will then generate output that is more tailored for C. For # instance, some of the names that are used will be different. The list of all @@ -299,22 +311,25 @@ OPTIMIZE_OUTPUT_SLICE = NO # parses. With this tag you can assign which parser to use for a given # extension. Doxygen has a built-in mapping, but you can override or extend it # using this tag. The format is ext=language, where ext is a file extension, and -# language is one of the parsers supported by doxygen: IDL, Java, Javascript, -# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, # Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: # FortranFree, unknown formatted Fortran: Fortran. In the later case the parser # tries to guess whether the code is fixed or free formatted code, this is the -# default for Fortran type files), VHDL, tcl. For instance to make doxygen treat -# .inc files as Fortran files (default is PHP), and .f files as C (default is -# Fortran), use: inc=Fortran f=C. +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. # # Note: For files without extension you can use no_extension as a placeholder. # # Note that for custom extensions you also need to set FILE_PATTERNS otherwise -# the files are not read by doxygen. +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. -EXTENSION_MAPPING = .yaml=Python -EXTENSION_MAPPING += .ispc=C +EXTENSION_MAPPING = .yaml=Python \ + .ispc=C # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -330,7 +345,7 @@ MARKDOWN_SUPPORT = YES # to that level are automatically included in the table of contents, even if # they do not have an id attribute. # Note: This feature currently applies only to Markdown headings. -# Minimum value: 0, maximum value: 99, default value: 0. +# Minimum value: 0, maximum value: 99, default value: 5. # This tag requires that the tag MARKDOWN_SUPPORT is set to YES. TOC_INCLUDE_HEADINGS = 0 @@ -446,6 +461,19 @@ TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- @@ -466,6 +494,12 @@ EXTRACT_ALL = YES EXTRACT_PRIVATE = YES +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + # If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal # scope will be included in the documentation. # The default value is: NO. @@ -503,6 +537,13 @@ EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + # If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all # undocumented members inside documented classes or files. If set to NO these # members will be included in the various overviews, but no documentation @@ -520,8 +561,8 @@ HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend -# (class|struct|union) declarations. If set to NO, these declarations will be -# included in the documentation. +# declarations. If set to NO, these declarations will be included in the +# documentation. # The default value is: NO. HIDE_FRIEND_COMPOUNDS = NO @@ -540,11 +581,18 @@ HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file -# names in lower-case letters. If set to YES, upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# and Mac users are advised to set this option to NO. +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. # The default value is: system dependent. CASE_SENSE_NAMES = NO @@ -783,7 +831,10 @@ WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO # If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when -# a warning is encountered. +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. # The default value is: NO. WARN_AS_ERROR = NO @@ -814,16 +865,17 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = @NMODL_PROJECT_SOURCE_DIR@/src -INPUT += @NMODL_PROJECT_SOURCE_DIR@/test -INPUT += @PROJECT_BINARY_DIR@/src/ast -INPUT += @PROJECT_BINARY_DIR@/src/visitors +INPUT = @NMODL_PROJECT_SOURCE_DIR@/src \ + @NMODL_PROJECT_SOURCE_DIR@/test \ + @PROJECT_BINARY_DIR@/src/ast \ + @PROJECT_BINARY_DIR@/src/visitors \ + ../README.md # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses # libiconv (or the iconv built into libc) for the transcoding. See the libiconv -# documentation (see: https://www.gnu.org/software/libiconv/) for the list of -# possible encodings. +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. # The default value is: UTF-8. INPUT_ENCODING = UTF-8 @@ -836,11 +888,15 @@ INPUT_ENCODING = UTF-8 # need to set EXTENSION_MAPPING for the extension otherwise the files are not # read by doxygen. # +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# # If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, # *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, # *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, -# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, -# *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice. +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. FILE_PATTERNS = *.c \ *.cc \ @@ -858,7 +914,7 @@ FILE_PATTERNS = *.c \ *.md \ *.mm \ *.dox \ - *.yaml \ + *.yaml # The RECURSIVE tag can be used to specify whether or not subdirectories should # be searched for input files as well. @@ -982,7 +1038,6 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -INPUT += ../README.md USE_MDFILE_AS_MAINPAGE = ../README.md #--------------------------------------------------------------------------- @@ -1082,13 +1137,6 @@ VERBATIM_HEADERS = YES ALPHABETICAL_INDEX = YES -# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in -# which the alphabetical index list will be split. -# Minimum value: 1, maximum value: 20, default value: 5. -# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. - -COLS_IN_ALPHA_INDEX = 5 - # In case all classes in a project start with a common prefix, all classes will # be put under the same header in the alphabetical index. The IGNORE_PREFIX tag # can be used to specify a prefix (or a list of prefixes) that should be ignored @@ -1227,9 +1275,9 @@ HTML_TIMESTAMP = NO # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that -# are dynamically created via Javascript. If disabled, the navigation index will +# are dynamically created via JavaScript. If disabled, the navigation index will # consists of multiple levels of tabs that are statically embedded in every HTML -# page. Disable this option to support browsers that do not have Javascript, +# page. Disable this option to support browsers that do not have JavaScript, # like the Qt help browser. # The default value is: YES. # This tag requires that the tag GENERATE_HTML is set to YES. @@ -1259,10 +1307,11 @@ HTML_INDEX_NUM_ENTRIES = 100 # If the GENERATE_DOCSET tag is set to YES, additional index files will be # generated that can be used as input for Apple's Xcode 3 integrated development -# environment (see: https://developer.apple.com/xcode/), introduced with OSX -# 10.5 (Leopard). To create a documentation set, doxygen will generate a -# Makefile in the HTML output directory. Running make will produce the docset in -# that directory and running make install will install the docset in +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at # startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy # genXcode/_index.html for more information. @@ -1304,8 +1353,8 @@ DOCSET_PUBLISHER_NAME = Publisher # If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three # additional HTML index files: index.hhp, index.hhc, and index.hhk. The # index.hhp is a project file that can be read by Microsoft's HTML Help Workshop -# (see: https://www.microsoft.com/en-us/download/details.aspx?id=21138) on -# Windows. +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. # # The HTML Help Workshop contains a compiler that can convert all HTML output # generated by doxygen into a single compiled HTML file (.chm). Compiled HTML @@ -1335,7 +1384,7 @@ CHM_FILE = HHC_LOCATION = # The GENERATE_CHI flag controls if a separate .chi index file is generated -# (YES) or that it should be included in the master .chm file (NO). +# (YES) or that it should be included in the main .chm file (NO). # The default value is: NO. # This tag requires that the tag GENERATE_HTMLHELP is set to YES. @@ -1380,7 +1429,8 @@ QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help # Project output. For more information please see Qt Help Project / Namespace -# (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). # The default value is: org.doxygen.Project. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1388,8 +1438,8 @@ QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt # Help Project output. For more information please see Qt Help Project / Virtual -# Folders (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual- -# folders). +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). # The default value is: doc. # This tag requires that the tag GENERATE_QHP is set to YES. @@ -1397,30 +1447,30 @@ QHP_VIRTUAL_FOLDER = doc # If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom # filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_NAME = # The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the # custom filter to add. For more information please see Qt Help Project / Custom -# Filters (see: http://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom- -# filters). +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_CUST_FILTER_ATTRS = # The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this # project's filter section matches. Qt Help Project / Filter Attributes (see: -# http://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). # This tag requires that the tag GENERATE_QHP is set to YES. QHP_SECT_FILTER_ATTRS = -# The QHG_LOCATION tag can be used to specify the location of Qt's -# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the -# generated .qhp file. +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. # This tag requires that the tag GENERATE_QHP is set to YES. QHG_LOCATION = @@ -1497,6 +1547,17 @@ TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + # Use this tag to change the font size of LaTeX formulas included as images in # the HTML documentation. When you change the font size after a successful # doxygen run you need to manually remove any form_*.png images from the HTML @@ -1517,8 +1578,14 @@ FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + # Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see -# https://www.mathjax.org) which uses client side Javascript for the rendering +# https://www.mathjax.org) which uses client side JavaScript for the rendering # instead of using pre-rendered bitmaps. Use this if you do not have LaTeX # installed or if you want to formulas look prettier in the HTML output. When # enabled you may also need to install MathJax separately and configure the path @@ -1530,7 +1597,7 @@ USE_MATHJAX = YES # When MathJax is enabled you can set the default output format to be used for # the MathJax output. See the MathJax site (see: -# http://docs.mathjax.org/en/latest/output.html) for more details. +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. # Possible values are: HTML-CSS (which is slower, but has the best # compatibility), NativeMML (i.e. MathML) and SVG. # The default value is: HTML-CSS. @@ -1546,7 +1613,7 @@ MATHJAX_FORMAT = HTML-CSS # Content Delivery Network so you can quickly see the result without installing # MathJax. However, it is strongly recommended to install a local copy of # MathJax from https://www.mathjax.org before deployment. -# The default value is: https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. # This tag requires that the tag USE_MATHJAX is set to YES. MATHJAX_RELPATH = https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/ @@ -1560,7 +1627,8 @@ MATHJAX_EXTENSIONS = # The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces # of code that will be used on startup of the MathJax code. See the MathJax site -# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an # example see the documentation. # This tag requires that the tag USE_MATHJAX is set to YES. @@ -1588,7 +1656,7 @@ MATHJAX_CODEFILE = SEARCHENGINE = YES # When the SERVER_BASED_SEARCH tag is enabled the search engine will be -# implemented using a web server instead of a web client using Javascript. There +# implemented using a web server instead of a web client using JavaScript. There # are two flavors of web server based searching depending on the EXTERNAL_SEARCH # setting. When disabled, doxygen will generate a PHP script for searching and # an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing @@ -1607,7 +1675,8 @@ SERVER_BASED_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). +# Xapian (see: +# https://xapian.org/). # # See the section "External Indexing and Searching" for details. # The default value is: NO. @@ -1620,8 +1689,9 @@ EXTERNAL_SEARCH = NO # # Doxygen ships with an example indexer (doxyindexer) and search engine # (doxysearch.cgi) which are based on the open source search engine library -# Xapian (see: https://xapian.org/). See the section "External Indexing and -# Searching" for details. +# Xapian (see: +# https://xapian.org/). See the section "External Indexing and Searching" for +# details. # This tag requires that the tag SEARCHENGINE is set to YES. SEARCHENGINE_URL = @@ -1785,9 +1855,11 @@ LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES -# If the USE_PDFLATEX tag is set to YES, doxygen will use pdflatex to generate -# the PDF file directly from the LaTeX files. Set this option to YES, to get a -# higher quality PDF documentation. +# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as +# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX +# files. Set this option to YES, to get a higher quality PDF documentation. +# +# See also section LATEX_CMD_NAME for selecting the engine. # The default value is: YES. # This tag requires that the tag GENERATE_LATEX is set to YES. @@ -2160,7 +2232,6 @@ SKIP_FUNCTION_MACROS = YES # run, you must also specify the path to the tagfile here. TAGFILES = -# TAGFILES += "cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/" # When a file name is specified after GENERATE_TAGFILE, doxygen will create a # tag file that is based on the input files it reads. See section "Linking to @@ -2189,12 +2260,6 @@ EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of 'which perl'). -# The default file (with absolute path) is: /usr/bin/perl. - -PERL_PATH = /usr/bin/perl - #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- @@ -2208,15 +2273,6 @@ PERL_PATH = /usr/bin/perl CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see: -# http://www.mcternan.me.uk/mscgen/)) to produce the chart and insert it in the -# documentation. The MSCGEN_PATH tag allows you to specify the directory where -# the mscgen tool resides. If left empty the tool is assumed to be found in the -# default search path. - -MSCGEN_PATH = - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. @@ -2314,10 +2370,32 @@ UML_LOOK = NO # but if the number exceeds 15, the total amount of fields shown is limited to # 10. # Minimum value: 0, maximum value: 100, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. +# This tag requires that the tag UML_LOOK is set to YES. UML_LIMIT_NUM_FIELDS = 10 +# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and +# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS +# tag is set to YES, doxygen will add type and arguments for attributes and +# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen +# will not generate fields with class member information in the UML graphs. The +# class diagrams will look similar to the default class diagrams but using UML +# notation for the relationships. +# Possible values are: NO, YES and NONE. +# The default value is: NO. +# This tag requires that the tag UML_LOOK is set to YES. + +DOT_UML_DETAILS = NO + +# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters +# to display on a single line. If the actual line length exceeds this threshold +# significantly it will wrapped across multiple lines. Some heuristics are apply +# to avoid ugly line breaks. +# Minimum value: 0, maximum value: 1000, default value: 17. +# This tag requires that the tag HAVE_DOT is set to YES. + +DOT_WRAP_THRESHOLD = 17 + # If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and # collaboration graphs will show the relations between templates and their # instances. @@ -2507,9 +2585,11 @@ DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate dot +# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate # files that are used to generate the various graphs. +# +# Note: This setting is not only used for dot files but also for msc and +# plantuml temporary files. # The default value is: YES. -# This tag requires that the tag HAVE_DOT is set to YES. DOT_CLEANUP = YES diff --git a/setup.py b/setup.py index 914648c53e..1e1d13f0e4 100644 --- a/setup.py +++ b/setup.py @@ -121,6 +121,7 @@ def _config_exe(exe_name): zip_safe=False, setup_requires=[ "jinja2>=2.9.3", + "jupyter-client==6.1.12", "jupyter", "m2r", "mistune<2", # prevents a version conflict with nbconvert diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 01bbe3bd1e..20c6037be6 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -65,7 +65,7 @@ class ConstVisitor { {% endfor %} }; +/** \} */ // end of visitor_classes + } // namespace visitor } // namespace nmodl - -/** \} */ // end of visitor_classes diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 71f9257fc5..9508078203 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -108,9 +108,9 @@ class DUInstance { * We also want the outermost binary expression. Thus, we do not keep track * of the interior ones. For example: * - * \f tau = tau + 1 \f + * \f$ tau = tau + 1 \f$ * - * we want to return the full statement, not only \f tau + 1 \f + * we want to return the full statement, not only \f$ tau + 1 \f$ */ std::shared_ptr binary_expression; }; diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index d4b120ec16..2c8aab1c35 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -21,7 +21,7 @@ using namespace fmt::literals; * \details SympyReplaceSolutionsVisitor tells us that a new equation appear and, depending where * it is located, it can determine if it is part of the main system of equations or is something * else. Every time we are out of the system and we print a new equation that is in the system - * we update the counter. \ref in_system follows, with lag, \ref is_in_system and every time + * we update the counter. \ref in_system follows, with lag, \param is_in_system and every time * they are false and true respectively we detect a switch. * * \param is_in_system is a bool provided from outside that tells us if a new equation is indeed diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp index 6bf5a928b4..fc4c821e26 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -34,7 +34,7 @@ namespace visitor { * \class SympyReplaceSolutionsVisitor * \brief Replace statements in \p node with pre_solve_statements, tmp_statements, and solutions * - * The goal is to replace statements with \ref solutions in place. In this way we can allow (to + * The goal is to replace statements with solutions in place. In this way we can allow (to * some extent) the use of control flow blocks and assignments. \ref pre_solve_statements are added * in front of the replaced statements in case their variable needs updating. \ref StatementDispenser @@ -48,13 +48,12 @@ namespace visitor { * assigns a variable * - pre_solve_Statements: statements that update the variables (i.e. x = old_x) * - tmp_statements: assignment of temporary variables in the solution generated by sympy in case - * --cse. (i.e. \f tmp = f - * (...) \f + * --cse. (i.e. \f$ tmp = f (...) \f$ * * We employ a multi-step approach: * - * - try to replace the old_statements (not binary_expressions) and in "assignment form: \f x = - * f(...) \f" with the corresponding solution matching by variable (i.e. x in \f x = f(...) \f) + * - try to replace the old_statements (not binary_expressions) and in "assignment form: \f$ x = + * f(...) \f$" with the corresponding solution matching by variable (i.e. x in \f$ x = f(...) \f$) * - try to replace the old_Statements with a greedy approach. When we find a * diff_eq_expression/linEquation that needs replacing we take the next solution that was not yet * used @@ -165,8 +164,8 @@ namespace visitor { * Last notes: * * For linEquations or NonLinEquations association of the solution with a particular statement could - * be impossible. For example \f ~ x + y = 0 \f does not have a simple variable in the lhs. Thus, an - * association with a particular solution statement is not possible. Thus we do 2 runs where we + * be impossible. For example \f$ ~ x + y = 0 \f$ does not have a simple variable in the lhs. Thus, + * an association with a particular solution statement is not possible. Thus we do 2 runs where we * first match everything we can by value and then we associate everything we can in a greedy way. * * For large system of equations the code sets up the J matrix and F vector to be sent to eigen for @@ -264,17 +263,17 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * Example: * * \code - * \\ not in the system, n = 0, is_in_system_ = false - * ~ x + y = 0 \\ system, in_system_ switch false -> true, n = 1 + * \\ not in the system, n = 0, is_in_system = false + * ~ x + y = 0 \\ system, in_system switch false -> true, n = 1 * ~ y = a + 1 \\ system, no switch, nothing to do - * a = ... \\ no system, in_system_ switch true -> false, nothing to do - * ~ z = x + y + z \\ system, in_system_ switch false -> true, n = 2 + * a = ... \\ no system, in_system switch true -> false, nothing to do + * ~ z = x + y + z \\ system, in_system switch false -> true, n = 2 * \endcode * * Number of interleaves: n-1 = 1 */ struct InterleavesCounter { - /// Count interleaves defined as a switch false -> true for \ref in_system_ + /// Count interleaves defined as a switch false -> true for \ref in_system void new_equation(const bool is_in_system); /// Number of interleaves. We need to remove the first activation of the switch except if @@ -288,7 +287,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * \brief Number of interleaves of assignment statements in between equations of the system * of equations * - * This is equivalent to the number of switches false -> true of \ref in_system_ minus the + * This is equivalent to the number of switches false -> true of \ref in_system minus the * very first one (if the system exists). */ size_t n_interleaves = 0; @@ -306,8 +305,8 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * This is a multi-purpose object that: * * - keeps track of what was already updated - * - decides what statements need updating in case there was a variable assignment (i.e. \f a = - * 3 \f) + * - decides what statements need updating in case there was a variable assignment (i.e. \f$ a = + * 3 \f$) * - builds the statements from a vector of strings * */ @@ -320,33 +319,33 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { const std::vector::const_iterator& statements_str_end, const int error_on_n_flushes); - /// Construct the maps \ref var2dependants_, \ref var2statement_ and \ref dependency_map_ + /// Construct the maps \ref var2dependants, \ref var2statement and \ref dependency_map /// for easy access and classification of the statements void build_maps(); - /// Check if one of the statements assigns this variable (i.e. \f x' = f(x, y, x) \f) and is - /// still tagged + /// Check if one of the statements assigns this variable (i.e. \f$ x' = f(x, y, x) \f$) and + /// is still tagged inline bool is_var_assigned_here(const std::string& var) const { const auto it = var2statement.find(var); return it != var2statement.end() && tags.find(it->second) != tags.end(); } /** - * \brief Look for \p var in \ref var2statement_ and emplace back that statement in \p + * \brief Look for \p var in \ref var2statement and emplace back that statement in \p * new_statements * - * If there is no \p var key in \ref var2statement_, return false + * If there is no \p var key in \ref var2statement, return false */ bool try_emplace_back_tagged_statement(ast::StatementVector& new_statements, const std::string& var); /// Emplace back the next \p n_next_statements solutions in \ref statements that is marked - /// for updating in \ref tags_ + /// for updating in \ref tags size_t emplace_back_next_tagged_statements(ast::StatementVector& new_statements, const size_t n_next_statements); - /// Emplace back all the statements that are marked for updating in \ref tags_ + /// Emplace back all the statements that are marked for updating in \ref tags size_t emplace_back_all_tagged_statements(ast::StatementVector& new_statements); /** @@ -372,7 +371,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * x = a + tmp + exp(a) * \endcode * - * \ref dependency_map_ is: + * \ref dependency_map is: * * - tmp : b * - x : a, b @@ -383,10 +382,10 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { /** * \brief a (key) : f(..., a, ...), g(..., a, ...), h(..., a, ...), ... (values) * - * This the "reverse" of \ref dependency_map_. Given a certain variable it provides + * This the "reverse" of \ref dependency_map. Given a certain variable it provides * the statements that depend on it. It is a set because we want to print them in - * order and we do not want duplicates. The value is the index in \ref statements_ or \ref - * tags_ + * order and we do not want duplicates. The value is the index in \ref statements or \ref + * tags * * For example: * @@ -395,7 +394,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * x = a + tmp + exp(a) // statement 1 * \endcode * - * \ref var2dependants_ is: + * \ref var2dependants is: * * - a : 1 * - b : 0, 1 @@ -415,7 +414,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * x = a + tmp + exp(a) // statement 1 * \endcode * - * \ref var2dependants_ is: + * \ref var2dependants is: * * - tmp : 0 * - x : 1 @@ -429,7 +428,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { /** * \brief Keeps track of what statements need updating * - * The elements of this set are the indexes of the \ref statements_ vector that need + * The elements of this set are the indexes of the \ref statements vector that need * updating. It is a set because we need to be able to easily find them by value and we need * them ordered to pick "the next one" */ @@ -437,7 +436,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { /** * \brief Max number of times a statement was printed using an \ref - * emplace_all_back_statement command + * emplace_back_all_tagged_statements command * * This is useful to check if, during updates, a variable was assigned. * @@ -449,20 +448,20 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * y' = b * \endcode * - * In this sequence of statements \f x \f was assigned within variable updates. This + * In this sequence of statements \f$ x \f$ was assigned within variable updates. This * sequence of statements could lead to instability/wrong results for derivimplicit methods. * Better to prevent this entirely. It can still be assigned at the end/beginning */ size_t n_flushes = 0; - /// Emit error when \ref n_flushes_ reaches this number. -1 disables the error entirely + /// Emit error when \ref n_flushes reaches this number. -1 disables the error entirely int error_on_n_flushes; }; - /// Update state variable statements (i.e. \f old_x = x \f) + /// Update state variable statements (i.e. \f$old_x = x \f$) StatementDispenser pre_solve_statements; - /// tmp statements that appear with --cse (i.e. \f tmp0 = a \f) + /// tmp statements that appear with --cse (i.e. \f$tmp0 = a \f$) StatementDispenser tmp_statements; /// solutions that we want to replace @@ -472,7 +471,7 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * \brief Replacements found by the visitor * * The keys are the old_statements that need replacing with the new ones (the - * value). Since there are \ref pre_solve_statements_ and \ref tmp_statements; it is in general + * value). Since there are \ref pre_solve_statements and \ref tmp_statements; it is in general * a replacement of 1 : n statements */ std::unordered_map, ast::StatementVector> replacements; From 2ab6e946c4ccf53f99b96fdcc4ed481b8770f79d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 8 Apr 2021 09:52:34 +0200 Subject: [PATCH 349/871] Move main.cpp at the root of src (BlueBrain/nmodl#566) For an easier understanding of sources put main.cpp at the root of src and create there a CMakeLists.txt delegate to build all the sources. NMODL Repo SHA: BlueBrain/nmodl@8ef48ec75cc7a525dc880e7e345fc0e8f05c9c9e --- cmake/nmodl/CMakeLists.txt | 12 +----------- src/nmodl/{nmodl => }/CMakeLists.txt | 11 +++++++++++ src/nmodl/{nmodl => }/main.cpp | 0 src/nmodl/{nmodl => }/nmodl.hpp | 0 test/nmodl/transpiler/unit/newton/newton.cpp | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) rename src/nmodl/{nmodl => }/CMakeLists.txt (83%) rename src/nmodl/{nmodl => }/main.cpp (100%) rename src/nmodl/{nmodl => }/nmodl.hpp (100%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index ca6a73c060..df16e23768 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -194,17 +194,7 @@ endif() # ============================================================================= include(${PROJECT_SOURCE_DIR}/src/language/code_generator.cmake) -add_subdirectory(src/codegen) -add_subdirectory(src/language) -add_subdirectory(src/lexer) -add_subdirectory(src/nmodl) -add_subdirectory(src/parser) -add_subdirectory(src/printer) -add_subdirectory(src/symtab) -add_subdirectory(src/utils) -add_subdirectory(src/visitors) -add_subdirectory(src/pybind) -add_subdirectory(src/solver) +add_subdirectory(src) # ============================================================================= # Prepare units database file from nrnunits.lib.in diff --git a/src/nmodl/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt similarity index 83% rename from src/nmodl/nmodl/CMakeLists.txt rename to src/nmodl/CMakeLists.txt index 2fb9dca3f9..7b5e67a66a 100644 --- a/src/nmodl/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -1,3 +1,14 @@ +add_subdirectory(codegen) +add_subdirectory(language) +add_subdirectory(lexer) +add_subdirectory(parser) +add_subdirectory(printer) +add_subdirectory(symtab) +add_subdirectory(utils) +add_subdirectory(visitors) +add_subdirectory(pybind) +add_subdirectory(solver) + # ============================================================================= # NMODL sources # ============================================================================= diff --git a/src/nmodl/nmodl/main.cpp b/src/nmodl/main.cpp similarity index 100% rename from src/nmodl/nmodl/main.cpp rename to src/nmodl/main.cpp diff --git a/src/nmodl/nmodl/nmodl.hpp b/src/nmodl/nmodl.hpp similarity index 100% rename from src/nmodl/nmodl/nmodl.hpp rename to src/nmodl/nmodl.hpp diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 6637afa408..1817427d06 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -11,7 +11,7 @@ #include -#include "nmodl/nmodl.hpp" +#include "nmodl.hpp" using namespace nmodl; From 9934c81d6a684c1fbb4b8fb7a16b82ef5d4f2cca Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 9 Apr 2021 14:28:16 +0200 Subject: [PATCH 350/871] Use ubuntu 18.04 as 16.04 is end of life (BlueBrain/nmodl#575) * The only difference is that headers of package flex are no more inside the main package, so we install libfl-dev (means libflex-dev). NMODL Repo SHA: BlueBrain/nmodl@1af582783075e397bbf5f61f2b359c7d947c72b2 --- INSTALL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index f384a6d1d1..335651c86c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -54,7 +54,7 @@ export PATH=/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin:$PATH ### On Ubuntu -On Ubuntu (>=16.04) flex/bison versions are recent enough and are installed along with the system toolchain: +On Ubuntu (>=18.04) flex/bison versions are recent enough and are installed along with the system toolchain: ```sh apt-get install flex bison gcc python3 python3-pip From eb5aefa11cc2763b1254067105fc54c2cd196859 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 9 Apr 2021 16:47:54 +0200 Subject: [PATCH 351/871] jupyter-client is already a depends of jupyter (BlueBrain/nmodl#576) * We no more need to fix the version because the wrong package (6.1.13) has been yanked in PyPi * https://github.com/jupyter/jupyter_client/issues/637 NMODL Repo SHA: BlueBrain/nmodl@cc2e41c1d624b0c2dfe436de3eca4cd4faed2179 --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 1e1d13f0e4..914648c53e 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,6 @@ def _config_exe(exe_name): zip_safe=False, setup_requires=[ "jinja2>=2.9.3", - "jupyter-client==6.1.12", "jupyter", "m2r", "mistune<2", # prevents a version conflict with nbconvert From f15af852303d802b706fd2b5c8e7c95ac0271ce9 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 9 Apr 2021 21:04:13 +0200 Subject: [PATCH 352/871] Automatically enable sympy_analytics pass if there is a sparse solver (BlueBrain/nmodl#578) * Those kind of solvers always need a sympy pass because they contains ODE. * Enable it for user if he didn't ask for. NMODL Repo SHA: BlueBrain/nmodl@07c7267998e2066f744885e43b88a595aa5f5591 --- src/nmodl/main.cpp | 6 +++++- src/nmodl/visitors/visitor_utils.cpp | 11 +++++++++++ src/nmodl/visitors/visitor_utils.hpp | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 310dd125ea..60e933f052 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -468,7 +468,11 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("sympy_conductance")); } - if (sympy_analytic) { + if (sympy_analytic || sparse_solver_exists(*ast)) { + if (!sympy_analytic) { + logger->info( + "Automatically enable sympy_analytic because it exists solver of type sparse"); + } logger->info("Running sympy solve visitor"); SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast); SymtabVisitor(update_symtab).visit_program(*ast); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index ea25252959..0000bcbec3 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -206,6 +206,17 @@ std::vector> collect_nodes(ast::Ast& node, return visitor.lookup(node, types); } +bool sparse_solver_exists(const ast::Ast& node) { + const auto solve_blocks = collect_nodes(node, {ast::AstNodeType::SOLVE_BLOCK}); + for (const auto& solve_block: solve_blocks) { + const auto& method = dynamic_cast(solve_block.get())->get_method(); + if (method && method->get_node_name() == "sparse") { + return true; + } + } + return false; +} + std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types) { std::stringstream stream; visitor::NmodlPrintVisitor v(stream, exclude_types); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 1b44906d24..01de313375 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -97,6 +97,8 @@ std::vector> collect_nodes( ast::Ast& node, const std::vector& types = {}); +bool sparse_solver_exists(const ast::Ast& node); + /// Given AST node, return the NMODL string representation std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types = {}); From 7f66cb42f8601129eb945fbab9e5c9ffd1143802 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 20 Apr 2021 18:33:18 +0300 Subject: [PATCH 353/871] Fixed bug with replacing external function when in-lining (BlueBrain/nmodl#598) - Avoid replacing external function by wrong in-lined function - Return value of in-lined function is passed as a variable in external function NMODL Repo SHA: BlueBrain/nmodl@cb3c0bfa8d176f53bdac94d6cb4c6c41a20b1055 --- src/nmodl/visitors/inline_visitor.cpp | 7 +++- test/nmodl/transpiler/unit/CMakeLists.txt | 1 + test/nmodl/transpiler/unit/visitor/inline.cpp | 34 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index dec1cf4ef4..cb723f0f1c 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -72,7 +72,12 @@ bool InlineVisitor::can_replace_statement(const std::shared_ptr& stat if (e->is_wrapped_expression()) { auto wrapped_expression = static_cast(e.get()); if (wrapped_expression->get_expression()->is_function_call()) { - to_replace = true; + // if caller is external function (i.e. neuron function) don't replace it + const auto& function_call = std::static_pointer_cast( + wrapped_expression->get_expression()); + const auto& function_name = function_call->get_node_name(); + const auto& symbol = program_symtab->lookup_in_scope(function_name); + to_replace = !symbol->is_external_variable(); } } return to_replace; diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index e4969b39a3..04e33614cd 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -3,6 +3,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/catch ${NMODL_PROJECT_SOURCE_DIR}/test) include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/solver) +include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/utils) include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # ============================================================================= diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 9cf6e7f3a2..de578edada 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -63,6 +63,40 @@ SCENARIO("Inlining of external procedure calls", "[visitor][inline]") { } } +SCENARIO("Inlining of function call as argument in external function", "[visitor][inline]") { + GIVEN("An external function calling a function") { + std::string input_nmodl = R"( + FUNCTION rates_1() { + rates_1 = 1 + } + + FUNCTION rates_2() { + net_send(rates_1(), 0) + } + )"; + + std::string output_nmodl = R"( + FUNCTION rates_1() { + rates_1 = 1 + } + + FUNCTION rates_2() { + LOCAL rates_1_in_0 + { + rates_1_in_0 = 1 + } + net_send(rates_1_in_0, 0) + } + )"; + THEN("External function doesn't get inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(result == expected_result); + } + } +} + SCENARIO("Inlining of simple, one level procedure call", "[visitor][inline]") { GIVEN("A procedure calling another procedure") { std::string input_nmodl = R"( From 4c21b1afafa2532016421906e469c9022bec4aa3 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 20 Apr 2021 19:55:05 +0200 Subject: [PATCH 354/871] Fix use of artcell_net_move to match the signature of CoreNeuron and nrn (BlueBrain/nmodl#601) * Fix use of artcell_net_move to match the signature of CoreNeuron and nrn * Catched by computing netstim.mod NMODL Repo SHA: BlueBrain/nmodl@641ee616f92e1d551bff1b7d341210527a065bba --- src/nmodl/codegen/codegen_c_visitor.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 0d1169e174..ae95d9759a 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3523,17 +3523,18 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text("artcell_net_move(&{}, {}, {}, nt->_t+"_format(tqitem, weight_index, pnt)); + printer->add_text("artcell_net_move(&{}, {}, nt->_t+"_format(tqitem, pnt)); + print_vector_elements(arguments, ", "); + printer->add_text(")"); } else { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, {}+"_format(tqitem, weight_index, point_process, t)); + print_vector_elements(arguments, ", "); + printer->add_text(", 0.0"); + printer->add_text(")"); } - // clang-format off - print_vector_elements(arguments, ", "); - printer->add_text(", 0.0"); - printer->add_text(")"); } From b6ccee1360421839b6a5e601dbf8a979c600c1db Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 20 Apr 2021 20:18:52 +0200 Subject: [PATCH 355/871] Fix expand of indexes (BlueBrain/nmodl#600) * This bug has been introduced by 520f60e4bcd5e * By simplifying code we forget to expand resolution of indexes NMODL Repo SHA: BlueBrain/nmodl@dcaf303566bf4a183cf667e31a45a680c7a006fe --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index ae95d9759a..7420093f33 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2196,7 +2196,7 @@ std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, return "inst->{}[indexes[{}*pnodecount + id]]"_format(name, position); } auto data = symbol.is_vdata ? "_vdata" : "_data"; - return "nt->{}[indexes[{}]]"_format(data, position); + return "nt->{}[indexes[{}*pnodecount + id]]"_format(data, position); // clang-format on } From b9e663dea68c64b0c8772c72815ff0bba7777b97 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 28 Apr 2021 10:38:29 +0200 Subject: [PATCH 356/871] Fixes to upload wheels to pypi.org from Azure CI (BlueBrain/nmodl#618) * Azure CI can be launched manually to upload wheels to nmodl-nightly or nmodl projects on pypi.org * Open Azure Web UI and set following variables: - `ReleaseWheelBuild = True` to build the wheels without nightly tag i.e. release wheel - `UploadWheel = False` to disable uploading wheels to pypi.org. This is useful for testing where wheels are built but they are not uploaded - `NMODL_WHEEL_VERSION = x.y` to override the version number created by `git describe --tags` command. This is helpful for testing/development purpose as pypi.org doesn't allow to upload same version wheels. NMODL Repo SHA: BlueBrain/nmodl@cb168c1522f2cbee18f09a7b3ace470f5e1d0781 --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 914648c53e..ec560c6c1e 100644 --- a/setup.py +++ b/setup.py @@ -30,6 +30,9 @@ .decode() ) __version__ = v[: v.rfind("-")].replace("-", ".") if "-" in v else v + # allow to override version during development/testing + if "NMODL_WHEEL_VERSION" in os.environ: + __version__ = os.environ['NMODL_WHEEL_VERSION'] except Exception as e: raise RuntimeError("Could not get version from Git repo") from e From 4d6dc1781add9fe98dd1516e4808f8c3a1572975 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 3 May 2021 22:12:24 +0200 Subject: [PATCH 357/871] Fix conversion to int if the double is bigger than INT_MAX (BlueBrain/nmodl#624) NMODL Repo SHA: BlueBrain/nmodl@ab3d86d8719f877aaf8262e458c8bc0c6fca2b9f --- src/nmodl/utils/string_utils.hpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 6e0331860b..8d81b05b58 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -137,8 +137,10 @@ static inline std::string tolower(std::string text) { */ static inline std::string to_string(double value, const std::string& format_spec = "{:.16g}") { // double containing integer value - if (std::ceil(value) == value) { - return std::to_string(static_cast(value)); + if (std::ceil(value) == value && + value < static_cast(std::numeric_limits::max()) && + value > static_cast(std::numeric_limits::min())) { + return std::to_string(static_cast(value)); } // actual float value From 3ad13f8126c2aded71f01c0bad832f8ccd6a8669 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 7 May 2021 00:19:51 +0200 Subject: [PATCH 358/871] Handle destructor block (BlueBrain/nmodl#625) * This block was still not handled add support for it NMODL Repo SHA: BlueBrain/nmodl@4318e2f9ddc3a7d61e20bdb1aee2e211fc0d67d4 --- docs/nmodl/transpiler/language.rst | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 22 +++++++++++++++++-- src/nmodl/codegen/codegen_c_visitor.hpp | 10 +++++++++ .../codegen/codegen_compatibility_visitor.cpp | 2 -- src/nmodl/codegen/codegen_helper_visitor.cpp | 6 +++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 +++ src/nmodl/codegen/codegen_naming.hpp | 3 +++ .../transpiler/integration/mod/cabpump.mod | 6 +++++ 9 files changed, 50 insertions(+), 5 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index 596ecb0892..7b48c80622 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -67,7 +67,7 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | CONSTRUCTOR | yes | no | yes | +------------------------+-------------------+-------------------+---------------------+ -| DESTRUCTOR | yes | no | yes | +| DESTRUCTOR | yes | yes | yes | +------------------------+-------------------+-------------------+---------------------+ | INDEPENDENT | yes | no | yes | +------------------------+-------------------+-------------------+---------------------+ diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 7420093f33..63de87807f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1251,6 +1251,9 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); } + if (type == BlockType::Destructor) { + return method_name(naming::NRN_DESTRUCTOR_METHOD); + } if (type == BlockType::State) { return method_name(naming::NRN_STATE_METHOD); } @@ -2621,7 +2624,10 @@ void CodegenCVisitor::print_mechanism_register() { auto args = register_mechanism_arguments(); auto nobjects = num_thread_objects(); if (info.point_process) { - printer->add_line("point_register_mech({}, NULL, NULL, {});"_format(args, nobjects)); + printer->add_line("point_register_mech({}, NULL, {}, {});"_format( + args, + info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) : "NULL", + nobjects)); } else { printer->add_line("register_mech({}, {});"_format(args, nobjects)); } @@ -3305,9 +3311,20 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { } +void CodegenCVisitor::print_nrn_destructor() { + printer->add_newline(2); + print_global_function_common_code(BlockType::Destructor); + if (info.destructor_node != nullptr) { + const auto& block = info.destructor_node->get_statement_block(); + print_statement_block(*block.get(), false, false); + } + printer->end_block(1); +} + + void CodegenCVisitor::print_nrn_alloc() { printer->add_newline(2); - auto method = method_name("nrn_alloc"); + auto method = method_name(naming::NRN_ALLOC_METHOD); printer->start_block("static void {}(double* data, Datum* indexes, int type) "_format(method)); printer->add_line("// do nothing"); printer->end_block(1); @@ -4318,6 +4335,7 @@ void CodegenCVisitor::print_codegen_routines() { print_global_variable_setup(); print_instance_variable_setup(); print_nrn_alloc(); + print_nrn_destructor(); print_compute_functions(); print_check_table_thread_function(); print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 5432ff1302..87dad2d3ef 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -57,6 +57,9 @@ enum BlockType { /// initial block Initial, + /// destructor block + Destructor, + /// breakpoint block Equation, @@ -1520,6 +1523,13 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_nrn_cur_matrix_shadow_reduction(); + /** + * Print nrn_destructor function definition + * + */ + void print_nrn_destructor(); + + /** * Print nrn_alloc function definition * diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 488ce187b3..6c442f3a6f 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -36,8 +36,6 @@ const std::map &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::CONSTRUCTOR_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::DESTRUCTOR_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::SOLVE_BLOCK, &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index ec44afb06a..38e5c3c1e0 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -478,6 +478,12 @@ void CodegenHelperVisitor::visit_initial_block(const InitialBlock& node) { } +void CodegenHelperVisitor::visit_destructor_block(const DestructorBlock& node) { + info.destructor_node = &node; + node.visit_children(*this); +} + + void CodegenHelperVisitor::visit_net_receive_block(const NetReceiveBlock& node) { under_net_receive_block = true; info.net_receive_node = &node; diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index c37c9745e0..4f32d1cef8 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -92,6 +92,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; void visit_statement_block(const ast::StatementBlock& node) override; void visit_initial_block(const ast::InitialBlock& node) override; + void visit_destructor_block(const ast::DestructorBlock& node) override; void visit_breakpoint_block(const ast::BreakpointBlock& node) override; void visit_derivative_block(const ast::DerivativeBlock& node) override; void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 176c3a2968..2df99d7c1c 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -258,6 +258,9 @@ struct CodegenInfo { /// initial block const ast::InitialBlock* initial_node = nullptr; + /// destructor block only for point process + const ast::DestructorBlock* destructor_node = nullptr; + /// all procedures defined in the mod file std::vector procedures; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 83b79fa82a..6d8875a000 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -125,6 +125,9 @@ static constexpr char FOR_NETCON_SEMANTIC[] = "fornetcon"; /// nrn_init method in generated code static constexpr char NRN_INIT_METHOD[] = "nrn_init"; +/// nrn_destructor method in generated code +static constexpr char NRN_DESTRUCTOR_METHOD[] = "nrn_destructor"; + /// nrn_alloc method in generated code static constexpr char NRN_ALLOC_METHOD[] = "nrn_alloc"; diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 134f830af2..4888689e72 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -18,6 +18,12 @@ UNITS { FARADAY = (faraday) (coul) } +DESTRUCTOR { +VERBATIM +// Nothing only to verify that it is well handled +ENDVERBATIM +} + PARAMETER { depth = .1 (um) taur = 200 (ms) : rate of calcium removal for stress conditions From 3cf6e97063197395637be19ad7a0ab417eb69106 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 18 May 2021 11:01:47 +0200 Subject: [PATCH 359/871] Print last line in parser error (BlueBrain/nmodl#646) Print last line in parser error - Store current line being lexed in nmodl_lexer - in parse_error print last line (hopefully parser/lexer stay on same line) - Additionally, use location to give pretty-print precise column location of error Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@ffe3e6b88f3c598ce21e9f0b2ee2f5cfdb7297af --- src/nmodl/lexer/nmodl.ll | 5 +++++ src/nmodl/lexer/nmodl_lexer.hpp | 5 +++++ src/nmodl/parser/nmodl.yy | 2 +- src/nmodl/parser/nmodl_driver.cpp | 13 ++++++++++++- src/nmodl/parser/nmodl_driver.hpp | 10 ++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 3c881a3004..1c45635fea 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -399,6 +399,7 @@ ELSE { /** First we read entire line and print to stdout. This is useful * for using lexer program. */ std::string str(yytext); + cur_line = str; stringutils::trim(str); if (driver.is_verbose()) { @@ -574,3 +575,7 @@ nmodl::ast::String* nmodl::parser::NmodlLexer::get_unit() { last_unit = nullptr; return result; } + +std::string nmodl::parser::NmodlLexer::get_curr_line() const { + return cur_line; +} diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 2c795c7b99..56759c2cf3 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -80,6 +80,8 @@ class NmodlLexer: public NmodlFlexLexer { */ int lexical_context = 0; + std::string cur_line; + public: /// location of the parsed token location loc; @@ -144,6 +146,9 @@ class NmodlLexer: public NmodlFlexLexer { /// Return last scanned unit as ast::String ast::String* get_unit(); + /// Return current line as string + std::string get_curr_line() const; + /// Enable debug output (via yyout) if compiled into the scanner. void set_debug(bool b); }; diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 9702c33509..c11364ee9c 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -2629,5 +2629,5 @@ std::string parse_with_verbatim_parser(std::string str) { */ void NmodlParser::error(const location &loc , const std::string &msg) { - driver.parse_error(loc, msg); + driver.parse_error(scanner, loc, msg); } diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 13af0e37a6..4bf28cc316 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -135,7 +135,18 @@ int NmodlDriver::get_defined_var_value(const std::string& name) const { void NmodlDriver::parse_error(const location& location, const std::string& message) { std::ostringstream oss; - oss << "NMODL Parser Error : " << message << " [Location : " << location << ']'; + oss << "NMODL Parser Error : " << message << " [Location : " << location << "]"; + throw std::runtime_error(oss.str()); +} + +void NmodlDriver::parse_error(const NmodlLexer& scanner, + const location& location, + const std::string& message) { + std::ostringstream oss; + oss << "NMODL Parser Error : " << message << " [Location : " << location << "]"; + oss << scanner.get_curr_line() << '\n'; + oss << std::string(location.begin.column - 1, '-'); + oss << "^\n"; throw std::runtime_error(oss.str()); } diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 45916e9252..5e33d5594c 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -16,6 +16,8 @@ #include #include "ast/ast.hpp" +#include "lexer/nmodl_lexer.hpp" +#include "parser/nmodl_driver.hpp" #include "utils/file_library.hpp" @@ -137,6 +139,14 @@ class NmodlDriver { */ void parse_error(const location& location, const std::string& message); + /** + * Emit a parsing error. Takes additionally a Lexer instance to print code context + * \throw std::runtime_error + */ + void parse_error(const NmodlLexer& scanner, + const location& location, + const std::string& message); + /** * Ensure \a file argument given to the INCLUDE directive is valid: * - between double-quotes From f02f8fda30d1d55ee18c2e7aa89920cc6b2958e5 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 20 May 2021 10:55:46 +0200 Subject: [PATCH 360/871] Do not add data presence pragmas in destructors. (BlueBrain/nmodl#651) Without this change then when the OpenACC backend is used a block was opened with `#pragma acc data present ...`, but the closing brace was not printed. NMODL Repo SHA: BlueBrain/nmodl@7c9e05913756e79bf0e00799c052dbb94cfeb54e --- src/nmodl/codegen/codegen_c_visitor.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 63de87807f..2883280992 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3230,7 +3230,11 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { print_global_method_annotation(); printer->start_block("void {}({})"_format(method, args)); - print_kernel_data_present_annotation_block_begin(); + if (type != BlockType::Destructor) { + // We do not (currently) support DESTRUCTOR (and, eventually, + // CONSTRUCTOR) blocks running anything on the GPU. + print_kernel_data_present_annotation_block_begin(); + } printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); printer->add_line( From 4941db780c6df469568c8b4d3f2ba040102f7ab7 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 20 May 2021 14:35:06 +0200 Subject: [PATCH 361/871] Support CMake v3.20+; fix for nmodl-as-a-submodule (BlueBrain/nmodl#660) * In CMake v3.20+ then the NVHPC compilers are identified as NVHPC, not PGI. * Also make sure that NMODL sources its own CompilerHelper.cmake, previously in the case that NMODL is built as a submodule of CoreNEURON and CoreNEURON is built as a submodule of NEURON then NMODL would source the CompilerHelper.cmake from NEURON. NMODL Repo SHA: BlueBrain/nmodl@2586c2e66ffd4d8e67244f8447ef56c9e79575a9 --- cmake/nmodl/CMakeLists.txt | 2 +- cmake/nmodl/CompilerHelper.cmake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index df16e23768..dec163d284 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -60,7 +60,7 @@ find_package(BISON 3.0 REQUIRED) list(APPEND CMAKE_MODULE_PATH ${NMODL_PROJECT_SOURCE_DIR}/cmake) include(Catch) include(ClangTidyHelper) -include(CompilerHelper) +include(${NMODL_PROJECT_SOURCE_DIR}/cmake/CompilerHelper.cmake) include(FindPythonModule) include(FlexHelper) include(GitRevision) diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 89c0222467..d9546fd48a 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -6,7 +6,7 @@ if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") endif() endif() -if(CMAKE_CXX_COMPILER_ID MATCHES "PGI") +if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") set(NMODL_PGI_COMPILER TRUE) # CMake adds standard complaint PGI flag "-A" which breaks compilation of of spdlog and fmt From fa37a18ac5ba6f70e4d0684f984e97c7e6070c99 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 25 May 2021 21:39:38 +0200 Subject: [PATCH 362/871] Add constructor (BlueBrain/nmodl#627) * Disable openacc pragmas for constructor too * Create dtor for ispc too * Force use of C function of print_global_function_common_code NMODL Repo SHA: BlueBrain/nmodl@b6de274d3abd3441be64cd5454474691b777fe76 --- src/nmodl/codegen/codegen_c_visitor.cpp | 28 ++++++++++++++++--- src/nmodl/codegen/codegen_c_visitor.hpp | 10 +++++++ .../codegen/codegen_compatibility_visitor.cpp | 2 -- src/nmodl/codegen/codegen_helper_visitor.cpp | 6 ++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 ++ src/nmodl/codegen/codegen_ispc_visitor.cpp | 14 ++++++++++ src/nmodl/codegen/codegen_naming.hpp | 3 ++ .../transpiler/integration/mod/cabpump.mod | 6 ++++ 9 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 2883280992..ee223216ca 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1251,6 +1251,9 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); } + if (type == BlockType::Constructor) { + return method_name(naming::NRN_CONSTRUCTOR_METHOD); + } if (type == BlockType::Destructor) { return method_name(naming::NRN_DESTRUCTOR_METHOD); } @@ -2624,12 +2627,17 @@ void CodegenCVisitor::print_mechanism_register() { auto args = register_mechanism_arguments(); auto nobjects = num_thread_objects(); if (info.point_process) { - printer->add_line("point_register_mech({}, NULL, {}, {});"_format( + printer->add_line("point_register_mech({}, {}, {}, {});"_format( args, + info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) : "NULL", info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) : "NULL", nobjects)); } else { printer->add_line("register_mech({}, {});"_format(args, nobjects)); + if (info.constructor_node) { + printer->add_line( + "register_constructor({});"_format(method_name(naming::NRN_CONSTRUCTOR_METHOD))); + } } // types for ion @@ -3230,9 +3238,9 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { print_global_method_annotation(); printer->start_block("void {}({})"_format(method, args)); - if (type != BlockType::Destructor) { - // We do not (currently) support DESTRUCTOR (and, eventually, - // CONSTRUCTOR) blocks running anything on the GPU. + if (type != BlockType::Destructor || type != BlockType::Constructor) { + // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks + // running anything on the GPU. print_kernel_data_present_annotation_block_begin(); } printer->add_line("int nodecount = ml->nodecount;"); @@ -3315,6 +3323,17 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { } +void CodegenCVisitor::print_nrn_constructor() { + printer->add_newline(2); + print_global_function_common_code(BlockType::Constructor); + if (info.constructor_node != nullptr) { + const auto& block = info.constructor_node->get_statement_block(); + print_statement_block(*block.get(), false, false); + } + printer->end_block(1); +} + + void CodegenCVisitor::print_nrn_destructor() { printer->add_newline(2); print_global_function_common_code(BlockType::Destructor); @@ -4339,6 +4358,7 @@ void CodegenCVisitor::print_codegen_routines() { print_global_variable_setup(); print_instance_variable_setup(); print_nrn_alloc(); + print_nrn_constructor(); print_nrn_destructor(); print_compute_functions(); print_check_table_thread_function(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 87dad2d3ef..d3416edabc 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -57,6 +57,9 @@ enum BlockType { /// initial block Initial, + /// constructor block + Constructor, + /// destructor block Destructor, @@ -1523,6 +1526,13 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_nrn_cur_matrix_shadow_reduction(); + /** + * Print nrn_constructor function definition + * + */ + void print_nrn_constructor(); + + /** * Print nrn_destructor function definition * diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 6c442f3a6f..f2cecb668d 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -34,8 +34,6 @@ const std::map &CodegenCompatibilityVisitor::return_error_with_name}, {AstNodeType::FUNCTION_TABLE_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::CONSTRUCTOR_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::SOLVE_BLOCK, &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 38e5c3c1e0..e9474b2b14 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -478,6 +478,12 @@ void CodegenHelperVisitor::visit_initial_block(const InitialBlock& node) { } +void CodegenHelperVisitor::visit_constructor_block(const ConstructorBlock& node) { + info.constructor_node = &node; + node.visit_children(*this); +} + + void CodegenHelperVisitor::visit_destructor_block(const DestructorBlock& node) { info.destructor_node = &node; node.visit_children(*this); diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 4f32d1cef8..d05b683bed 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -92,6 +92,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; void visit_statement_block(const ast::StatementBlock& node) override; void visit_initial_block(const ast::InitialBlock& node) override; + void visit_constructor_block(const ast::ConstructorBlock& node) override; void visit_destructor_block(const ast::DestructorBlock& node) override; void visit_breakpoint_block(const ast::BreakpointBlock& node) override; void visit_derivative_block(const ast::DerivativeBlock& node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 2df99d7c1c..f64cc817d4 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -258,6 +258,9 @@ struct CodegenInfo { /// initial block const ast::InitialBlock* initial_node = nullptr; + /// constructor block + const ast::ConstructorBlock* constructor_node = nullptr; + /// destructor block only for point process const ast::DestructorBlock* destructor_node = nullptr; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index b2822f1078..3bb35b83af 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -152,6 +152,12 @@ std::string CodegenIspcVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); } + if (type == BlockType::Constructor) { + return method_name(naming::NRN_CONSTRUCTOR_METHOD); + } + if (type == BlockType::Destructor) { + return method_name(naming::NRN_DESTRUCTOR_METHOD); + } if (type == BlockType::State) { return method_name(naming::NRN_STATE_METHOD); } @@ -357,6 +363,11 @@ void CodegenIspcVisitor::print_procedure(const ast::ProcedureBlock& node) { void CodegenIspcVisitor::print_global_function_common_code(BlockType type) { + // If we are printing the cpp file, we have to use the c version of this function + if (wrapper_codegen) { + return CodegenCVisitor::print_global_function_common_code(type); + } + std::string method = compute_method_name(type); auto params = get_global_function_parms(ptr_type_qualifier()); @@ -789,6 +800,9 @@ void CodegenIspcVisitor::print_wrapper_routines() { print_block_wrappers_initial_equation_state(); + print_nrn_constructor(); + print_nrn_destructor(); + print_mechanism_register(); print_namespace_end(); diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 6d8875a000..02050c3f43 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -125,6 +125,9 @@ static constexpr char FOR_NETCON_SEMANTIC[] = "fornetcon"; /// nrn_init method in generated code static constexpr char NRN_INIT_METHOD[] = "nrn_init"; +/// nrn_constructor method in generated code +static constexpr char NRN_CONSTRUCTOR_METHOD[] = "nrn_constructor"; + /// nrn_destructor method in generated code static constexpr char NRN_DESTRUCTOR_METHOD[] = "nrn_destructor"; diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 4888689e72..205d5b4a18 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -18,6 +18,12 @@ UNITS { FARADAY = (faraday) (coul) } +CONSTRUCTOR { +VERBATIM +// Nothing only to verify that it is well handled +ENDVERBATIM +} + DESTRUCTOR { VERBATIM // Nothing only to verify that it is well handled From e853c0b37dd67e7cdc0cfc558bb6765f77eb52a2 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 1 Jun 2021 10:21:41 +0200 Subject: [PATCH 363/871] Fix OpenACC backend codegen for constructor and destructor (BlueBrain/nmodl#677) NMODL Repo SHA: BlueBrain/nmodl@b783974c2cb9e221e9de32c7164016e712d94744 --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index ee223216ca..058343f74f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3238,7 +3238,7 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { print_global_method_annotation(); printer->start_block("void {}({})"_format(method, args)); - if (type != BlockType::Destructor || type != BlockType::Constructor) { + if (type != BlockType::Destructor && type != BlockType::Constructor) { // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks // running anything on the GPU. print_kernel_data_present_annotation_block_begin(); From 457504011cab7e93f8e802ca6631eafc0ae79fd7 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Wed, 2 Jun 2021 11:19:54 +0200 Subject: [PATCH 364/871] Fix execution of OpenACC code with GPU disabled. (BlueBrain/nmodl#681) NMODL Repo SHA: BlueBrain/nmodl@6300b47f9484836aafeaf964326daf8bf36fc63a --- src/nmodl/codegen/codegen_acc_visitor.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 273b137e96..e9b28f01b2 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -48,7 +48,8 @@ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType ty } present_clause << ')'; printer->add_line( - "#pragma acc parallel loop {} async(nt->stream_id)"_format(present_clause.str())); + "#pragma acc parallel loop {} async(nt->stream_id) if(nt->compute_gpu)"_format( + present_clause.str())); } @@ -143,7 +144,8 @@ void CodegenAccVisitor::print_net_send_buffering_grow() { void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { if (!info.artificial_cell) { auto global_variable = "{}_global"_format(info.mod_suffix); - printer->add_line("#pragma acc data present(nt, ml, {})"_format(global_variable)); + printer->add_line( + "#pragma acc data present(nt, ml, {}) if(nt->compute_gpu)"_format(global_variable)); printer->add_line("{"); printer->increase_indent(); } @@ -219,7 +221,9 @@ std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& va if (info.artificial_cell) { return variable; } - return "({}) acc_deviceptr({})"_format(type, variable); + return "reinterpret_cast<{}>( nt->compute_gpu ? acc_deviceptr({}) : {} )"_format(type, + variable, + variable); } From 7be2af5409a5b4befa89c9878d9244a8b984451b Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 22 Jun 2021 12:28:23 +0200 Subject: [PATCH 365/871] Add cii badge into README.md (BlueBrain/nmodl#693) NMODL Repo SHA: BlueBrain/nmodl@189e35c81201b49d5cfc99256a19b7e78364a3e8 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f8602f618..29ede53a39 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## The NMODL Framework -[![Build Status](https://travis-ci.org/BlueBrain/nmodl.svg?branch=master)](https://travis-ci.org/BlueBrain/nmodl) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB)](https://codecov.io/gh/BlueBrain/nmodl) +[![Build Status](https://travis-ci.org/BlueBrain/nmodl.svg?branch=master)](https://travis-ci.org/BlueBrain/nmodl) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB)](https://codecov.io/gh/BlueBrain/nmodl) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4467/badge)](https://bestpractices.coreinfrastructure.org/projects/4467) The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L**anguage ([NMODL](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html)). It is designed with modern compiler and code generation techniques to: From d01534020e8c2ab6fc271d70d2236a7f5a807848 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 29 Jun 2021 09:30:21 +0200 Subject: [PATCH 366/871] Fix the issue with ION ordering (BlueBrain/nmodl#697) * NMODL was ordering ION variables based on symbol table i.e. the order in which variables appear in the MOD file * MOD2C orders ION variables based on the order of USEION statements in MOD file. * If other blocks like PARAMETER appear first in MOD file with ION variables, the NMODL will end up with different order than MOD2C. * In order to avoid this issue, use USEION statement from AST. * Add test for codegen C visitor fixes BlueBrain/nmodl#150 NMODL Repo SHA: BlueBrain/nmodl@a094a8fc0a936e3a007636cb6bec0141018bc7c4 --- src/nmodl/codegen/codegen_c_visitor.hpp | 23 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 44 ++-- src/nmodl/codegen/codegen_helper_visitor.hpp | 2 +- test/nmodl/transpiler/unit/CMakeLists.txt | 2 +- .../unit/codegen/codegen_c_visitor.cpp | 224 ++++++++++++++++++ .../unit/codegen/codegen_helper.cpp | 10 +- 6 files changed, 268 insertions(+), 37 deletions(-) create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index d3416edabc..0d0cc6ca87 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1049,12 +1049,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_mechanism_global_var_structure(); - /** - * Print the structure that wraps all range and int variables required for the NMODL - */ - void print_mechanism_range_var_structure(); - - /** * Print structure of ion variables used for local copies */ @@ -1087,13 +1081,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_setup_range_variable(); - /** - * Print the function that initialize instance structure - * - */ - void print_instance_variable_setup(); - - /** * Print byte arrays that register scalar and vector variables for hoc interface * @@ -1838,6 +1825,16 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ std::string find_var_unique_name(const std::string& original_name) const; + /** + * Print the structure that wraps all range and int variables required for the NMODL + */ + void print_mechanism_range_var_structure(); + + /** + * Print the function that initialize instance structure + */ + void print_instance_variable_setup(); + void visit_binary_expression(const ast::BinaryExpression& node) override; void visit_binary_operator(const ast::BinaryOperator& node) override; void visit_boolean(const ast::Boolean& node) override; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index e9474b2b14..83e441105d 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -12,6 +12,7 @@ #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { @@ -62,13 +63,25 @@ void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& /** * Find all ions used in mod file */ -void CodegenHelperVisitor::find_ion_variables() { - /// name of the ions used - auto ion_vars = psymtab->get_variables_with_properties(NmodlType::useion); - /// read variables from all ions - auto read_ion_vars = psymtab->get_variables_with_properties(NmodlType::read_ion_var); - /// write variables from all ions - auto write_ion_vars = psymtab->get_variables_with_properties(NmodlType::write_ion_var); +void CodegenHelperVisitor::find_ion_variables(const ast::Program& node) { + // collect all use ion statements + const auto& ion_nodes = collect_nodes(node, {AstNodeType::USEION}); + + // ion names, read ion variables and write ion variables + std::vector ion_vars; + std::vector read_ion_vars; + std::vector write_ion_vars; + + for (const auto& ion_node: ion_nodes) { + const auto& ion = std::dynamic_pointer_cast(ion_node); + ion_vars.push_back(ion->get_node_name()); + for (const auto& var: ion->get_readlist()) { + read_ion_vars.push_back(var->get_node_name()); + } + for (const auto& var: ion->get_writelist()) { + write_ion_vars.push_back(var->get_node_name()); + } + } /** * Check if given variable belongs to given ion. @@ -81,20 +94,17 @@ void CodegenHelperVisitor::find_ion_variables() { }; /// iterate over all ion types and construct the Ion objects - for (auto& ion_var: ion_vars) { - auto ion_name = ion_var->get_name(); + for (auto& ion_name: ion_vars) { Ion ion(ion_name); for (auto& read_var: read_ion_vars) { - auto var = read_var->get_name(); - if (ion_variable(var, ion_name)) { - ion.reads.push_back(var); + if (ion_variable(read_var, ion_name)) { + ion.reads.push_back(read_var); } } for (auto& write_var: write_ion_vars) { - auto varname = write_var->get_name(); - if (ion_variable(varname, ion_name)) { - ion.writes.push_back(varname); - if (ion.is_intra_cell_conc(varname) || ion.is_extra_cell_conc(varname)) { + if (ion_variable(write_var, ion_name)) { + ion.writes.push_back(write_var); + if (ion.is_intra_cell_conc(write_var) || ion.is_extra_cell_conc(write_var)) { ion.need_style = true; info.write_concentration = true; } @@ -666,7 +676,7 @@ void CodegenHelperVisitor::visit_program(const ast::Program& node) { } } node.visit_children(*this); - find_ion_variables(); // Keep this before find_*_range_variables() + find_ion_variables(node); // Keep this before find_*_range_variables() find_range_variables(); find_non_range_variables(); find_table_variables(); diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index d05b683bed..d2224a7bd0 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -69,7 +69,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { /// lhs of assignment in derivative block std::shared_ptr assign_lhs; - void find_ion_variables(); + void find_ion_variables(const ast::Program& node); void find_table_variables(); void find_range_variables(); void find_non_range_variables(); diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 04e33614cd..12b7ac17bb 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -56,7 +56,7 @@ add_executable(testfast_math fast_math/fast_math.cpp ${SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) add_executable(testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp - codegen/codegen_utils.cpp) + codegen/codegen_utils.cpp codegen/codegen_c_visitor.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp new file mode 100644 index 0000000000..ff8011e0be --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -0,0 +1,224 @@ +/************************************************************************* + * Copyright (C) 2019-2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "codegen/codegen_c_visitor.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace codegen; + +using nmodl::parser::NmodlDriver; +using nmodl::test_utils::reindent_text; + +/// Helper for creating C codegen visitor +std::shared_ptr create_c_visitor(const std::string& text, std::stringstream& ss) { + /// parse mod file and create AST + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + + /// construct symbol table + SymtabVisitor().visit_program(*ast); + + /// create C code generation visitor + auto cv = std::make_shared("temp.mod", ss, "double", false); + cv->setup(*ast); + return cv; +} + +/// print instance structure for testing purpose +std::string get_instance_var_setup_function(std::string& nmodl_text) { + std::stringstream ss; + auto cvisitor = create_c_visitor(nmodl_text, ss); + cvisitor->print_instance_variable_setup(); + return reindent_text(ss.str()); +} + +SCENARIO("Check instance variable definition order", "[codegen][var_order]") { + GIVEN("cal_mig.mod: USEION variables declared as RANGE") { + // In the below mod file, the ion variables cai and cao are also + // declared as RANGE variables. The ordering issue was fixed in #443. + std::string nmodl_text = R"( + PARAMETER { + gcalbar=.003 (mho/cm2) + ki=.001 (mM) + cai = 50.e-6 (mM) + cao = 2 (mM) + } + NEURON { + SUFFIX cal + USEION ca READ cai,cao WRITE ica + RANGE gcalbar, cai, ica, gcal, ggk + RANGE minf, tau + } + STATE { + m + } + ASSIGNED { + ica (mA/cm2) + gcal (mho/cm2) + minf + tau (ms) + ggk + } + )"; + + THEN("ionic current variable declared as RANGE appears first") { + std::string generated_code = R"( + static inline void setup_instance(NrnThread* nt, Memb_list* ml) { + cal_Instance* inst = (cal_Instance*) mem_alloc(1, sizeof(cal_Instance)); + int pnodecount = ml->_nodecount_padded; + Datum* indexes = ml->pdata; + inst->gcalbar = ml->data+0*pnodecount; + inst->ica = ml->data+1*pnodecount; + inst->gcal = ml->data+2*pnodecount; + inst->minf = ml->data+3*pnodecount; + inst->tau = ml->data+4*pnodecount; + inst->ggk = ml->data+5*pnodecount; + inst->m = ml->data+6*pnodecount; + inst->cai = ml->data+7*pnodecount; + inst->cao = ml->data+8*pnodecount; + inst->Dm = ml->data+9*pnodecount; + inst->v_unused = ml->data+10*pnodecount; + inst->ion_cai = nt->_data; + inst->ion_cao = nt->_data; + inst->ion_ica = nt->_data; + inst->ion_dicadv = nt->_data; + ml->instance = (void*) inst; + } + )"; + auto expected = reindent_text(generated_code); + auto result = get_instance_var_setup_function(nmodl_text); + REQUIRE(result.find(expected) != std::string::npos); + } + } + + // In the below mod file, the `cao` is defined first in the PARAMETER + // block but it appears after cai in the USEION statement. As per NEURON + // implementation, variables should appear in the order of USEION + // statements i.e. ion_cai should come before ion_cao. This was a bug + // and it has been fixed in #697. + GIVEN("LcaMig.mod: mod file from reduced_dentate model") { + std::string nmodl_text = R"( + PARAMETER { + ki = .001(mM) + cao(mM) + tfa = 1 + } + NEURON { + SUFFIX lca + USEION ca READ cai, cao VALENCE 2 + RANGE cai, ilca, elca + } + STATE { + m + } + )"; + + THEN("Ion variables are defined in the order of USEION") { + std::string generated_code = R"( + static inline void setup_instance(NrnThread* nt, Memb_list* ml) { + lca_Instance* inst = (lca_Instance*) mem_alloc(1, sizeof(lca_Instance)); + int pnodecount = ml->_nodecount_padded; + Datum* indexes = ml->pdata; + inst->m = ml->data+0*pnodecount; + inst->cai = ml->data+1*pnodecount; + inst->cao = ml->data+2*pnodecount; + inst->Dm = ml->data+3*pnodecount; + inst->v_unused = ml->data+4*pnodecount; + inst->ion_cai = nt->_data; + inst->ion_cao = nt->_data; + ml->instance = (void*) inst; + } + )"; + + auto expected = reindent_text(generated_code); + auto result = get_instance_var_setup_function(nmodl_text); + REQUIRE(result.find(expected) != std::string::npos); + } + } + + // In the below mod file, ion variables ncai and lcai are declared + // as state variables as well as range variables. The issue about + // this mod file ordering was fixed in #443. + GIVEN("ccanl.mod: mod file from reduced_dentate model") { + std::string nmodl_text = R"( + NEURON { + SUFFIX ccanl + USEION nca READ ncai, inca, enca WRITE enca, ncai VALENCE 2 + USEION lca READ lcai, ilca, elca WRITE elca, lcai VALENCE 2 + RANGE caiinf, catau, cai, ncai, lcai, eca, elca, enca + } + UNITS { + FARADAY = 96520(coul) + R = 8.3134(joule / degC) + } + PARAMETER { + depth = 200(nm): assume volume = area * depth + catau = 9(ms) + caiinf = 50.e-6(mM) + cao = 2(mM) + } + ASSIGNED { + celsius(degC) + ica(mA / cm2) + inca(mA / cm2) + ilca(mA / cm2) + cai(mM) + enca(mV) + elca(mV) + eca(mV) + } + STATE { + ncai(mM) + lcai(mM) + } + )"; + + THEN("Ion variables are defined in the order of USEION") { + std::string generated_code = R"( + static inline void setup_instance(NrnThread* nt, Memb_list* ml) { + ccanl_Instance* inst = (ccanl_Instance*) mem_alloc(1, sizeof(ccanl_Instance)); + int pnodecount = ml->_nodecount_padded; + Datum* indexes = ml->pdata; + inst->catau = ml->data+0*pnodecount; + inst->caiinf = ml->data+1*pnodecount; + inst->cai = ml->data+2*pnodecount; + inst->eca = ml->data+3*pnodecount; + inst->ica = ml->data+4*pnodecount; + inst->inca = ml->data+5*pnodecount; + inst->ilca = ml->data+6*pnodecount; + inst->enca = ml->data+7*pnodecount; + inst->elca = ml->data+8*pnodecount; + inst->ncai = ml->data+9*pnodecount; + inst->Dncai = ml->data+10*pnodecount; + inst->lcai = ml->data+11*pnodecount; + inst->Dlcai = ml->data+12*pnodecount; + inst->v_unused = ml->data+13*pnodecount; + inst->ion_ncai = nt->_data; + inst->ion_inca = nt->_data; + inst->ion_enca = nt->_data; + inst->style_nca = ml->pdata; + inst->ion_lcai = nt->_data; + inst->ion_ilca = nt->_data; + inst->ion_elca = nt->_data; + inst->style_lca = ml->pdata; + ml->instance = (void*) inst; + } + )"; + + auto expected = reindent_text(generated_code); + auto result = get_instance_var_setup_function(nmodl_text); + REQUIRE(result.find(expected) != std::string::npos); + } + } +} diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index f23a6f29e7..5e5189d88e 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -19,9 +19,9 @@ using namespace codegen; using nmodl::parser::NmodlDriver; //============================================================================= -// Helper for codege related visitor +// Helper for codegen related visitor //============================================================================= -std::string run_inline_visitor(const std::string& text) { +std::string run_codegen_helper_visitor(const std::string& text) { NmodlDriver driver; const auto& ast = driver.parse_string(text); @@ -88,7 +88,7 @@ SCENARIO("unusual / failing mod files", "[codegen][var_order]") { THEN("ionic current variable declared as RANGE appears first") { std::string expected = "gcalbar;ica;gcal;minf;tau;ggk;m;cai;cao;"; - auto result = run_inline_visitor(nmodl_text); + auto result = run_codegen_helper_visitor(nmodl_text); REQUIRE(result == expected); } } @@ -121,7 +121,7 @@ SCENARIO("unusual / failing mod files", "[codegen][var_order]") { THEN("ion state variable is ordered after parameter and assigned ionic current") { std::string expected = "gamma;decay;depth;minCai;ica;cai;"; - auto result = run_inline_visitor(nmodl_text); + auto result = run_codegen_helper_visitor(nmodl_text); REQUIRE(result == expected); } } @@ -163,7 +163,7 @@ SCENARIO("unusual / failing mod files", "[codegen][var_order]") { THEN("ion variables are ordered correctly") { std::string expected = "ca;cai;ica;drive_channel;"; - auto result = run_inline_visitor(nmodl_text); + auto result = run_codegen_helper_visitor(nmodl_text); REQUIRE(result == expected); } } From 91356c939e3cad4670a8c5d650636f99baa9d4bd Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 29 Jun 2021 15:55:21 +0200 Subject: [PATCH 367/871] Set the project version using git describe (BlueBrain/nmodl#695) We extend GitRevision.cmake a little to look up the last git tag. We then call `project()` again in the main CMakeLists.txt to set the version. This fixes BlueBrain/nmodl#694 NMODL Repo SHA: BlueBrain/nmodl@436428974b3cde871763846b37be326372984830 --- cmake/nmodl/CMakeLists.txt | 14 ++++++++++---- cmake/nmodl/GitRevision.cmake | 8 ++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index dec163d284..9bc3897887 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -6,10 +6,8 @@ # ============================================================================= cmake_minimum_required(VERSION 3.8 FATAL_ERROR) -project( - NMODL - VERSION 0.2 - LANGUAGES CXX) + +project(NMODL LANGUAGES CXX) # ============================================================================= # CMake common project settings @@ -68,6 +66,14 @@ include(PythonLinkHelper) include(RpathHelper) include(ExternalProjectHelper) +# ============================================================================= +# Set the project version now using git +# ============================================================================= +project( + NMODL + VERSION ${GIT_LAST_TAG} + LANGUAGES CXX) + # ============================================================================= # Initialize external libraries as submodule # ============================================================================= diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index 3c7bc594fa..b6d95e937a 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -23,8 +23,16 @@ if(GIT_FOUND) string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") + # get the last version tag from git + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --tags + WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} + OUTPUT_VARIABLE GIT_LAST_TAG + ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + else() set(GIT_REVISION "unknown") + set(GIT_LAST_TAG "unknown") endif() From 6f3321181288f1427c92b359b3a2cfe81cfe6855 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Wed, 14 Jul 2021 15:02:13 +0200 Subject: [PATCH 368/871] Fix repr() and str() for AST python wrappers (BlueBrain/nmodl#700) The python bindings for AST classes were fixed to now properly print a compact json representation of the node (and its children) when the repr is called (implicitly or explictely). Additionally I added a `__str__` to each AST node binding that will be used for string conversions (as in print). This function returns the NMODL representation of the node (and its children). NMODL Repo SHA: BlueBrain/nmodl@8c739f3d5ec6b7a21649aedf4d899e32c8c5b541 --- docs/nmodl/transpiler/contents/visitors.rst | 15 ++++++++++++--- src/nmodl/language/templates/pybind/pyast.cpp | 11 +++++++++++ test/nmodl/transpiler/unit/pybind/test_ast.py | 12 +++++++++++- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/docs/nmodl/transpiler/contents/visitors.rst b/docs/nmodl/transpiler/contents/visitors.rst index 6f945d55ac..f714b56e60 100644 --- a/docs/nmodl/transpiler/contents/visitors.rst +++ b/docs/nmodl/transpiler/contents/visitors.rst @@ -54,11 +54,20 @@ First, we have to create nmodl parser object using :class:`nmodl.NmodlDriver` an >>> modast = driver.parse_string(channel) The :func:`nmodl.NmodlDriver.parse_string` method will throw an exception with parsing error if the input is invalid. -Otherwise it return :class:`nmodl.ast.AST` object. +Otherwise it returns :class:`nmodl.ast.AST` object. -If we simply print AST object, we can see JSON representation: +If we simply print the AST object, we can see the NMODL code: - >>> print ('%.100s' % modast) # only first 100 characters + >>> print ('%.103s' % modast) # only first 103 characters + NEURON { + SUFFIX CaDynamics + USEION ca READ ica WRITE cai + RANGE decay, gamma, minCai, depth + } + +If we would like to see the AST tree, we can simply print the python representation `repr` of the AST object: + + >>> print ('%.100s' % repr(modast)) # only first 100 characters {"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]}, diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index b81d8ff6a2..fb682c7d39 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -15,6 +15,7 @@ #include #include "visitors/json_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" /** @@ -166,6 +167,7 @@ static const char* eval_method = R"( namespace py = pybind11; using namespace nmodl::ast; using nmodl::visitor::JSONVisitor; +using nmodl::visitor::NmodlPrintVisitor; using namespace pybind11::literals; @@ -241,6 +243,15 @@ void init_ast_module(py::module& m) { JSONVisitor v(ss); v.compact_json(true); n.accept(v); + v.flush(); + return ss.str(); + }); + + {{var(node)}} + .def("__str__", []({{node.class_name}} & n) { + std::stringstream ss; + NmodlPrintVisitor v(ss); + n.accept(v); return ss.str(); }); diff --git a/test/nmodl/transpiler/unit/pybind/test_ast.py b/test/nmodl/transpiler/unit/pybind/test_ast.py index fdb3470188..b1a935d1ce 100644 --- a/test/nmodl/transpiler/unit/pybind/test_ast.py +++ b/test/nmodl/transpiler/unit/pybind/test_ast.py @@ -12,7 +12,7 @@ class TestAst(object): def test_empty_program(self): pnode = ast.Program() - assert str(pnode) == '{"Program":[]}' + assert str(pnode) == '' def test_ast_construction(self): string = ast.String("tau") @@ -26,3 +26,13 @@ def test_ast_construction(self): block = ast.StatementBlock(statements) neuron_block = ast.NeuronBlock(block) assert nmodl.to_nmodl(neuron_block) == 'NEURON {\n}' + + def test_ast_node_repr(self): + string = ast.String("tau") + name = ast.Name(string) + assert repr(name) == nmodl.to_json(name, compact=True) + + def test_ast_node_str(self): + string = ast.String("tau") + name = ast.Name(string) + assert str(name) == nmodl.to_nmodl(name) From 77dcba02d4089953a5b590eb64e6531b39b1e3d0 Mon Sep 17 00:00:00 2001 From: Christos Kotsalos Date: Thu, 15 Jul 2021 14:14:04 +0300 Subject: [PATCH 369/871] Add translation of mod files as tests with make test (BlueBrain/nmodl#673) * Updated hpc-coding-conventions to master Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@fe8a1f657578dd675429de4df5274a24d90d4f9f --- cmake/nmodl/CMakeLists.txt | 1 + cmake/nmodl/hpc-coding-conventions | 2 +- test/nmodl/transpiler/integration/CMakeLists.txt | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 test/nmodl/transpiler/integration/CMakeLists.txt diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9bc3897887..a161d27847 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -180,6 +180,7 @@ set(MEMORYCHECK_COMMAND_OPTIONS if(NOT NMODL_AS_SUBPROJECT) include(CTest) add_subdirectory(test/unit) + add_subdirectory(test/integration) endif() # ============================================================================= diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 3a4e543672..fae561508d 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 3a4e543672a98240a946f2bd2560e203baa670ef +Subproject commit fae561508d4a6b02d1848384707bcc3dc81d826d diff --git a/test/nmodl/transpiler/integration/CMakeLists.txt b/test/nmodl/transpiler/integration/CMakeLists.txt new file mode 100644 index 0000000000..3207e49e3a --- /dev/null +++ b/test/nmodl/transpiler/integration/CMakeLists.txt @@ -0,0 +1,8 @@ +# ============================================================================= +# translation of mod files +# ============================================================================= +file(GLOB modfiles "${NMODL_PROJECT_SOURCE_DIR}/test/integration/mod/*.mod") +foreach(modfile ${modfiles}) + get_filename_component(modfile_name "${modfile}" NAME) + add_test(NAME ${modfile_name} COMMAND ${PROJECT_BINARY_DIR}/bin/nmodl ${modfile}) +endforeach() From b0a7b2fedccff3a69ed8d155031d69e53b49a10d Mon Sep 17 00:00:00 2001 From: Fernando Pereira Date: Wed, 21 Jul 2021 08:14:46 +0100 Subject: [PATCH 370/871] Fixing pyvisitor template so that no node copies are done (BlueBrain/nmodl#708) * Added failing test for AST modification from python along with some debug prints in the AstVisitor template * Fixing pyvisitor template so that no node copies are done Co-authored-by: Omar Awile NMODL Repo SHA: BlueBrain/nmodl@758bb7b654b0242466e21968754357995ee7a5c3 --- .../language/templates/pybind/pyvisitor.cpp | 8 ++--- .../transpiler/unit/pybind/test_visitor.py | 29 +++++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index d131843784..faadd8a2fe 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -103,25 +103,25 @@ namespace py = pybind11; {% for node in nodes %} void PyVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) { - PYBIND11_OVERLOAD_PURE(void, Visitor, visit_{{ node.class_name|snake_case }}, node); + PYBIND11_OVERLOAD_PURE(void, Visitor, visit_{{ node.class_name|snake_case }}, std::ref(node)); } {% endfor %} {% for node in nodes %} void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) { - PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, node); + PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, std::ref(node)); } {% endfor %} {% for node in nodes %} void PyConstVisitor::visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) { -PYBIND11_OVERLOAD_PURE(void, ConstVisitor, visit_{{ node.class_name|snake_case }}, node); + PYBIND11_OVERLOAD_PURE(void, ConstVisitor, visit_{{ node.class_name|snake_case }}, std::cref(node)); } {% endfor %} {% for node in nodes %} void PyConstAstVisitor::visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) { -PYBIND11_OVERLOAD(void, ConstAstVisitor, visit_{{ node.class_name|snake_case }}, node); + PYBIND11_OVERLOAD(void, ConstAstVisitor, visit_{{ node.class_name|snake_case }}, std::cref(node)); } {% endfor %} // clang-format on diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index ad130766b0..e40072450e 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -82,3 +82,32 @@ def visit_name(self, node): assert len(myvisitor.states) is 2 assert myvisitor.states[0] == "m" assert myvisitor.states[1] == "h" + + +def test_modify_ast(): + one_var = """NEURON { + SUFFIX test + RANGE x +} + """ + class ModifyVisitor(visitor.AstVisitor): + def __init__(self, old_name, new_name): + visitor.AstVisitor.__init__(self) + self.old_name = old_name + self.new_name = new_name + + def visit_range_var(self, node): + if nmodl.to_nmodl(node.name) == self.old_name: + node.name.value = ast.String(self.new_name) + node.visit_children(self) + + driver = nmodl.NmodlDriver() + modast = driver.parse_string(one_var) + mod_visitor = ModifyVisitor("x", "y") + mod_visitor.visit_program(modast) + one_var_after = """NEURON { + SUFFIX test + RANGE y +} +""" + assert str(modast) == one_var_after From b591c0136e48408810435bf21c5a28320f92138c Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 21 Jul 2021 11:51:58 +0200 Subject: [PATCH 371/871] Fixes for legacy derivimplicit solver on the GPU (BlueBrain/nmodl#709) - NewtonSpace** was copied to GPU instead of NewtonSpace* - MechInstance variable pointer from the device was not setup in the membrane list structure - In case of derivimplicit solver, deriv1_advance flag was not updated on the GPU device fixes BlueBrain/nmodl#707 NMODL Repo SHA: BlueBrain/nmodl@82415f4148baa52318d28bc0fccd24b20256dcc4 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 14 +++++++++++++- src/nmodl/codegen/codegen_acc_visitor.hpp | 6 ++++++ src/nmodl/codegen/codegen_c_visitor.cpp | 15 +++++++++++++-- src/nmodl/codegen/codegen_c_visitor.hpp | 11 +++++++++++ 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index e9b28f01b2..d775b7ea2a 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -231,7 +231,7 @@ void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { int list_num = info.derivimplicit_list_num; printer->add_line("if (nt->compute_gpu) {"); printer->add_line(" auto device_vec = static_cast(acc_copyin(vec, vec_size));"); - printer->add_line(" auto device_ns = static_cast(acc_deviceptr(ns));"); + printer->add_line(" auto device_ns = static_cast(acc_deviceptr(*ns));"); printer->add_line(" auto device_thread = static_cast(acc_deviceptr(thread));"); printer->add_line( " acc_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns, sizeof(void*));"_format( @@ -243,5 +243,17 @@ void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { } +void CodegenAccVisitor::print_instance_variable_transfer_to_device() const { + printer->add_line("if (nt->compute_gpu) {"); + printer->add_line(" auto dml = (Memb_list*) acc_deviceptr(ml);"); + printer->add_line(" acc_memcpy_to_device(&(dml->instance), &inst, sizeof(void*));"); + printer->add_line("}"); +} + + +void CodegenAccVisitor::print_deriv_advance_flag_transfer_to_device() const { + printer->add_line("#pragma acc update device (deriv_advance_flag) if (nt->compute_gpu)"); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 2c367ab6ed..82373eb4ce 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -88,6 +88,12 @@ class CodegenAccVisitor: public CodegenCVisitor { /// transfer newtonspace structure to device void print_newtonspace_transfer_to_device() const override; + // update instance variable object pointer on the gpu device + void print_instance_variable_transfer_to_device() const override; + + // update derivimplicit advance flag on the gpu device + void print_deriv_advance_flag_transfer_to_device() const override; + std::string get_variable_device_pointer(const std::string& variable, const std::string& type) const override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 058343f74f..81b5630cf2 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1030,6 +1030,13 @@ void CodegenCVisitor::print_channel_iteration_tiling_block_end() { // backend specific, do nothing } +void CodegenCVisitor::print_instance_variable_transfer_to_device() const { + // backend specific, do nothing +} + +void CodegenCVisitor::print_deriv_advance_flag_transfer_to_device() const { + // backend specific, do nothing +} /** * \details Each kernel such as \c nrn\_init, \c nrn\_state and \c nrn\_cur could be offloaded @@ -3169,6 +3176,7 @@ void CodegenCVisitor::print_instance_variable_setup() { } printer->add_line("ml->instance = (void*) inst;"); + print_instance_variable_transfer_to_device(); printer->end_block(3); printer->add_line("/** cleanup mechanism instance variables */"); @@ -3281,7 +3289,9 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { int nequation = info.num_equations; int list_num = info.derivimplicit_list_num; // clang-format off - printer->add_line("*deriv{}_advance(thread) = 0;"_format(list_num)); + printer->add_line("int& deriv_advance_flag = *deriv{}_advance(thread);"_format(list_num)); + printer->add_line("deriv_advance_flag = 0;"); + print_deriv_advance_flag_transfer_to_device(); printer->add_line("auto ns = newtonspace{}(thread);"_format(list_num)); printer->add_line("auto& th = thread[dith{}()];"_format(list_num)); @@ -3313,7 +3323,8 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { printer->end_block(1); if (info.derivimplicit_used()) { - printer->add_line("*deriv{}_advance(thread) = 1;"_format(info.derivimplicit_list_num)); + printer->add_line("deriv_advance_flag = 1;"); + print_deriv_advance_flag_transfer_to_device(); } print_kernel_data_present_annotation_block_end(); if (skip_init_check) { diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 0d0cc6ca87..15f08be693 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1081,6 +1081,17 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_setup_range_variable(); + /** + * Print the code to copy instance variable to device + */ + virtual void print_instance_variable_transfer_to_device() const; + + /** + * Print the code to copy derivative advance flag to device + */ + virtual void print_deriv_advance_flag_transfer_to_device() const; + + /** * Print byte arrays that register scalar and vector variables for hoc interface * From 5fdcf0947f3fcdc2488fe1327ce46f3b7900b5f4 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 23 Jul 2021 18:31:10 +0200 Subject: [PATCH 372/871] Fixed for watch statements codegen, global and NetSendBuffer_t variables update on CPU/GPU (BlueBrain/nmodl#710) * fixed for watch statements, global variable on GPU * fixed watch statement code generation described in BlueBrain/nmodl#679 * fixed global variable update to gpu descrbed in BlueBrain/nmodl#678 * fixed atomic capture for net_send buffer * fixed for watch test on GPU - Initial block was missing net_send_buffering related codegen when net_send was used - fix indentation with watch if-else statements - net_send_buffer updates from host & device missing - net_receieve_buffer code was out of place * Incorporate BlueBrain/mod2c/pull/63 * Fix CVF CI: INITIAL block shouldn't be executed on ISPC backend when mod file uses net_send/net_event method. This is because they contain non-compatible code for event buffering used for GPU. * Fix the weight_index argument for net_send_buffering() generated for net_send() call in INITIAL block. - See analysis in https://github.com/BlueBrain/nmodl/issues/680 Closes BlueBrain/nmodl#679, closes BlueBrain/nmodl#678, closes BlueBrain/nmodl#675, closes BlueBrain/nmodl#680 NMODL Repo SHA: BlueBrain/nmodl@6678d8e895ca0b808f43c6f0183d6fac12986e95 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 21 +++++++ src/nmodl/codegen/codegen_acc_visitor.hpp | 12 ++++ src/nmodl/codegen/codegen_c_visitor.cpp | 64 ++++++++++++++++------ src/nmodl/codegen/codegen_c_visitor.hpp | 24 ++++++++ src/nmodl/codegen/codegen_ispc_visitor.cpp | 3 +- 5 files changed, 105 insertions(+), 19 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index d775b7ea2a..9ed2e805d6 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -255,5 +255,26 @@ void CodegenAccVisitor::print_deriv_advance_flag_transfer_to_device() const { printer->add_line("#pragma acc update device (deriv_advance_flag) if (nt->compute_gpu)"); } + +void CodegenAccVisitor::print_device_atomic_capture_annotation() const { + printer->add_line("#pragma acc atomic capture"); +} + + +void CodegenAccVisitor::print_device_stream_wait() const { + printer->add_line("#pragma acc wait(nt->stream_id)"); +} + + +void CodegenAccVisitor::print_net_send_buf_count_update_to_host() const { + print_device_stream_wait(); + printer->add_line("#pragma acc update self(nsb->_cnt) if(nt->compute_gpu)"); +} + + +void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { + printer->add_line("#pragma acc update device(nsb->_cnt) if (nt->compute_gpu)"); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 82373eb4ce..4810464626 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -94,6 +94,18 @@ class CodegenAccVisitor: public CodegenCVisitor { // update derivimplicit advance flag on the gpu device void print_deriv_advance_flag_transfer_to_device() const override; + // update NetSendBuffer_t count from device to host + void print_net_send_buf_count_update_to_host() const override; + + // update NetSendBuffer_t count from host to device + virtual void print_net_send_buf_count_update_to_device() const override; + + // synchronise/wait on stream specific to NrnThread + virtual void print_device_stream_wait() const override; + + // print atomic capture pragma + void print_device_atomic_capture_annotation() const override; + std::string get_variable_device_pointer(const std::string& variable, const std::string& type) const override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 81b5630cf2..d9ba1d89a3 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1038,6 +1038,22 @@ void CodegenCVisitor::print_deriv_advance_flag_transfer_to_device() const { // backend specific, do nothing } +void CodegenCVisitor::print_device_atomic_capture_annotation() const { + // backend specific, do nothing +} + +void CodegenCVisitor::print_net_send_buf_count_update_to_host() const { + // backend specific, do nothing +} + +void CodegenCVisitor::print_net_send_buf_count_update_to_device() const { + // backend specific, do nothing +} + +void CodegenCVisitor::print_device_stream_wait() const { + // backend specific, do nothing +} + /** * \details Each kernel such as \c nrn\_init, \c nrn\_state and \c nrn\_cur could be offloaded * to accelerator. In this case, at very top level, we print pragma @@ -3305,6 +3321,9 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { // clang-format on } + // update global variable as those might be updated via python/hoc API + print_global_variable_device_update_annotation(); + if (skip_init_check) { printer->start_block("if (_nrn_skip_initmodel == 0)"); } @@ -3326,6 +3345,11 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { printer->add_line("deriv_advance_flag = 1;"); print_deriv_advance_flag_transfer_to_device(); } + + if (info.net_send_used && !info.artificial_cell) { + print_send_event_move(); + } + print_kernel_data_present_annotation_block_end(); if (skip_init_check) { printer->end_block(1); @@ -3433,13 +3457,16 @@ void CodegenCVisitor::print_watch_check() { printer->add_line("double v = voltage[node_id];"); } + // flat to make sure only one WATCH statement can be triggered at a time + printer->add_line("bool watch_untriggered = true;"); + for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; auto watch = statement->get_statements().front(); auto varname = get_variable_name("watch{}"_format(i + 1)); // start block 1 - printer->start_block("if ({}&2)"_format(varname)); + printer->start_block("if ({}&2 && watch_untriggered)"_format(varname)); // start block 2 printer->add_indent(); @@ -3452,6 +3479,8 @@ void CodegenCVisitor::print_watch_check() { // start block 3 printer->start_block("if (({}&1) == 0)"_format(varname)); + printer->add_line("watch_untriggered = false;"); + auto tqitem = get_variable_name("tqitem"); auto point_process = get_variable_name("point_process"); printer->add_indent(); @@ -3462,21 +3491,18 @@ void CodegenCVisitor::print_watch_check() { watch->get_value()->accept(*this); printer->add_text(");"); printer->add_newline(); + printer->end_block(1); printer->add_line("{} = 3;"_format(varname)); - printer->end_block(1); // end block 3 // start block 3 - printer->start_block("else"_format()); + printer->decrease_indent(); + printer->start_block("} else"); printer->add_line("{} = 2;"_format(varname)); printer->end_block(1); // end block 3 - printer->decrease_indent(); - printer->add_line("}"); - // end block 2 - printer->end_block(1); // end block 1 } @@ -3534,10 +3560,9 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { std::string weight_index = "weight_index"; std::string pnt = "pnt"; - // for non-net-receieve functions there is no weight index argument - // and artificial cell is in vdata which is void** + // for non-net_receieve functions i.e. initial block, the weight_index argument is 0. if (!printing_net_receive) { - weight_index = "-1"; + weight_index = "0"; auto var = get_variable_name("point_process"); if (info.artificial_cell) { pnt = "(Point_process*)" + var; @@ -3670,7 +3695,8 @@ void CodegenCVisitor::print_net_init() { void CodegenCVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); - /// \todo Update net send buffer on host + print_net_send_buf_count_update_to_host(); + printer->add_line("update_net_send_buffer_on_host(nt, nsb);"); printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); printer->add_line(" int type = nsb->_sendtype[i];"); printer->add_line(" int tid = nt->id;"); @@ -3684,7 +3710,7 @@ void CodegenCVisitor::print_send_event_move() { // clang-format on printer->add_line("}"); printer->add_line("nsb->_cnt = 0;"); - /// \todo Update net send buffer count on device + print_net_send_buf_count_update_to_device(); } @@ -3747,14 +3773,15 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->end_block(1); print_net_receive_loop_end(); + print_device_stream_wait(); + printer->add_line("nrb->_displ_cnt = 0;"); + printer->add_line("nrb->_cnt = 0;"); + if (info.net_send_used || info.net_event_used) { print_send_event_move(); } printer->add_newline(); - printer->add_line("nrb->_displ_cnt = 0;"); - printer->add_line("nrb->_cnt = 0;"); - print_kernel_data_present_annotation_block_end(); printer->end_block(1); } @@ -3774,9 +3801,10 @@ void CodegenCVisitor::print_net_send_buffering() { "NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; printer->start_block("static inline void net_send_buffering({}) "_format(args)); - printer->add_line("int i = nsb->_cnt;"); - printer->add_line("nsb->_cnt++;"); - printer->add_line("if(nsb->_cnt >= nsb->_size) {"); + printer->add_line("int i = 0;"); + print_device_atomic_capture_annotation(); + printer->add_line("i = nsb->_cnt++;"); + printer->add_line("if(i >= nsb->_size) {"); printer->increase_indent(); print_net_send_buffering_grow(); printer->decrease_indent(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 15f08be693..14d12925a1 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1086,12 +1086,31 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_instance_variable_transfer_to_device() const; + /** * Print the code to copy derivative advance flag to device */ virtual void print_deriv_advance_flag_transfer_to_device() const; + /** + * Print the code to update NetSendBuffer_t count from device to host + */ + virtual void print_net_send_buf_count_update_to_host() const; + + + /** + * Print the code to update NetSendBuffer_t count from host to device + */ + virtual void print_net_send_buf_count_update_to_device() const; + + + /** + * Print the code to synchronise/wait on stream specific to NrnThread + */ + virtual void print_device_stream_wait() const; + + /** * Print byte arrays that register scalar and vector variables for hoc interface * @@ -1433,6 +1452,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_newtonspace_transfer_to_device() const; + /** + * Print pragma annotation for increase and capture of variable in automatic way + */ + virtual void print_device_atomic_capture_annotation() const; + /** * Print block / loop for statement requiring reduction * diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 3bb35b83af..b7e2910443 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -611,7 +611,8 @@ bool CodegenIspcVisitor::check_incompatibilities() { if (info.initial_node) { emit_fallback[BlockType::Initial] = emit_fallback[BlockType::Initial] || has_incompatible_nodes(*info.initial_node) || - visitor::calls_function(*info.initial_node, "net_send") || info.require_wrote_conc; + visitor::calls_function(*info.initial_node, "net_send") || info.require_wrote_conc || + info.net_send_used || info.net_event_used; } else { emit_fallback[BlockType::Initial] = emit_fallback[BlockType::Initial] || info.net_receive_initial_node || From b4502701c06d3d3b7bd3d72c98d465065d31a2b1 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 28 Jul 2021 12:24:31 +0200 Subject: [PATCH 373/871] Add PRCELLSTATE mechanism to nmodl (BlueBrain/nmodl#712) PRCELLSTATE mechanism is a feature helpful to debug code. Once nmodl is compiled you can use PRCELLSTATE by setting the variable "NRN_PRCELLSTATE" to 1. Closes BlueBrain/nmodl#701. NMODL Repo SHA: BlueBrain/nmodl@d7b82bb1fae0fdc6b9e8da6908731af447e5e659 --- src/nmodl/codegen/codegen_c_visitor.cpp | 43 +++++++++++++++---- src/nmodl/codegen/codegen_c_visitor.hpp | 17 ++++++++ src/nmodl/codegen/codegen_naming.hpp | 2 +- .../transpiler/unit/codegen/codegen_ispc.cpp | 6 +++ 4 files changed, 58 insertions(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index d9ba1d89a3..1cf118037b 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2527,6 +2527,13 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { } +void CodegenCVisitor::print_prcellstate_macros() const { + printer->add_line("#ifndef NRN_PRCELLSTATE"); + printer->add_line("#define NRN_PRCELLSTATE 0"); + printer->add_line("#endif"); +} + + void CodegenCVisitor::print_mechanism_info() { auto variable_printer = [&](std::vector& variables) { for (const auto& v: variables) { @@ -3214,6 +3221,7 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { } else { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); + print_v_unused(); } if (ion_variable_struct_required()) { @@ -3455,6 +3463,7 @@ void CodegenCVisitor::print_watch_check() { if (info.is_voltage_used_by_watch_statements()) { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); + print_v_unused(); } // flat to make sure only one WATCH statement can be triggered at a time @@ -4082,6 +4091,7 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); + print_v_unused(); /** * \todo Eigen solver node also emits IonCurVar variable in the functor @@ -4160,17 +4170,17 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no } printer->add_line("double rhs = {};"_format(sum)); } - if (!info.conductances.empty()) { - std::string sum; - for (const auto& conductance: info.conductances) { - auto var = breakpoint_current(conductance.variable); - sum += get_variable_name(var); - if (&conductance != &info.conductances.back()) { - sum += "+"; - } + + std::string sum; + for (const auto& conductance: info.conductances) { + auto var = breakpoint_current(conductance.variable); + sum += get_variable_name(var); + if (&conductance != &info.conductances.back()) { + sum += "+"; } - printer->add_line("double g = {};"_format(sum)); } + printer->add_line("double g = {};"_format(sum)); + for (const auto& conductance: info.conductances) { if (!conductance.ion.empty()) { auto lhs = "ion_di" + conductance.ion + "dv"; @@ -4216,6 +4226,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); + print_v_unused(); if (ion_variable_struct_required()) { print_ion_variable(); } @@ -4243,6 +4254,8 @@ void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { printer->add_line("g = g*mfactor;"); printer->add_line("rhs = rhs*mfactor;"); } + + print_g_unused(); } void CodegenCVisitor::print_fast_imem_calculation() { @@ -4354,6 +4367,17 @@ void CodegenCVisitor::print_data_structures() { print_ion_var_structure(); } +void CodegenCVisitor::print_v_unused() const { + printer->add_line("#if NRN_PRCELLSTATE"); + printer->add_line("inst->v_unused[id] = v;"); + printer->add_line("#endif"); +} + +void CodegenCVisitor::print_g_unused() const { + printer->add_line("#if NRN_PRCELLSTATE"); + printer->add_line("inst->g_unused[id] = g;"); + printer->add_line("#endif"); +} void CodegenCVisitor::print_compute_functions() { print_top_verbatim_blocks(); @@ -4387,6 +4411,7 @@ void CodegenCVisitor::print_codegen_routines() { print_headers_include(); print_namespace_begin(); print_nmodl_constants(); + print_prcellstate_macros(); print_mechanism_info(); print_data_structures(); print_global_variables_for_hoc(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 14d12925a1..524740a7bb 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1036,6 +1036,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual bool is_constant_variable(const std::string& name) const; + /** + * Print declaration of macro NRN_PRCELLSTATE for debugging + */ + void print_prcellstate_macros() const; + /** * Print backend code for byte array that has mechanism information (to be registered * with coreneuron) @@ -1625,6 +1630,18 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_data_structures(); + /** + * Set v_unused (voltage) for NRN_PRCELLSTATE feature + */ + void print_v_unused() const; + + + /** + * Set g_unused (conductance) for NRN_PRCELLSTATE feature + */ + void print_g_unused() const; + + /** * Print all compute functions for every backend * diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 02050c3f43..dffb3e5136 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -66,7 +66,7 @@ static constexpr char POINT_PROCESS_VARIABLE[] = "point_process"; static constexpr char TQITEM_VARIABLE[] = "tqitem"; /// range variable for conductance -static constexpr char CONDUCTANCE_VARIABLE[] = "_g"; +static constexpr char CONDUCTANCE_VARIABLE[] = "g"; /// global variable to indicate if table is used static constexpr char USE_TABLE_VARIABLE[] = "usetable"; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp index 45a1dc0c5e..41905c0567 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp @@ -151,6 +151,9 @@ SCENARIO("ISPC codegen", "[codegen][ispc]") { foreach (id = start ... end) { int node_id = node_index[id]; double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif inst->a[id] = 0.0d; inst->b[id] = 0.0d; } @@ -172,6 +175,9 @@ SCENARIO("ISPC codegen", "[codegen][ispc]") { foreach (id = start ... end) { int node_id = node_index[id]; double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif inst->a[id] = inst->b[id] + FARADAY; } } From 6452d1400ad1378ec963a204fa02ddf7daade7c8 Mon Sep 17 00:00:00 2001 From: nrnhines Date: Mon, 9 Aug 2021 01:24:20 -0400 Subject: [PATCH 374/871] fix bad translation of netmove(expr) (BlueBrain/nmodl#714) NMODL Repo SHA: BlueBrain/nmodl@d38bb5a2dfef3cb18b5cafa90e5b9aa679afef92 --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 1cf118037b..a773a66b48 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3615,7 +3615,7 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, {}+"_format(tqitem, weight_index, point_process, t)); + printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, "_format(tqitem, weight_index, point_process)); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); printer->add_text(")"); From b936766e6f8c9db0aa1f40f43b88b602bd8e9702 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Mon, 16 Aug 2021 11:50:34 +0200 Subject: [PATCH 375/871] Fix version when NMODL is submodule. (BlueBrain/nmodl#715) * Previously then when NMODL is built as a submodule of CoreNEURON the GitRevision.cmake helper from CoreNEURON was included. This makes things more explicit. * Also print summary at the end of CMake execution for all build system generators, not just Unix Makefiles. NMODL Repo SHA: BlueBrain/nmodl@9d937e26fa0a1f4a1eccb29b7b13935ab2cf2ef2 --- cmake/nmodl/CMakeLists.txt | 89 ++++++++++++++++++---------------- cmake/nmodl/GitRevision.cmake | 14 +++--- src/nmodl/config/config.cpp.in | 2 +- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index a161d27847..d916c41122 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -53,25 +53,24 @@ find_package(FLEX 2.6 REQUIRED) find_package(BISON 3.0 REQUIRED) # ============================================================================= -# Include cmake modules -# ============================================================================= -list(APPEND CMAKE_MODULE_PATH ${NMODL_PROJECT_SOURCE_DIR}/cmake) -include(Catch) -include(ClangTidyHelper) -include(${NMODL_PROJECT_SOURCE_DIR}/cmake/CompilerHelper.cmake) -include(FindPythonModule) -include(FlexHelper) -include(GitRevision) -include(PythonLinkHelper) -include(RpathHelper) -include(ExternalProjectHelper) +# Include cmake modules. Filenames ensure we always pick up NMODL's versions. +# ============================================================================= +include(cmake/Catch.cmake) +include(cmake/ClangTidyHelper.cmake) +include(cmake/CompilerHelper.cmake) +include(cmake/FindPythonModule.cmake) +include(cmake/FlexHelper.cmake) +include(cmake/GitRevision.cmake) +include(cmake/PythonLinkHelper.cmake) +include(cmake/RpathHelper.cmake) +include(cmake/ExternalProjectHelper.cmake) # ============================================================================= # Set the project version now using git # ============================================================================= project( NMODL - VERSION ${GIT_LAST_TAG} + VERSION ${NMODL_GIT_LAST_TAG} LANGUAGES CXX) # ============================================================================= @@ -233,36 +232,40 @@ endif() # Build status # ============================================================================= message(STATUS "") -message(STATUS "Configured NMODL ${PROJECT_VERSION} (${GIT_REVISION})") +message(STATUS "Configured NMODL ${PROJECT_VERSION} (${NMODL_GIT_REVISION})") message(STATUS "") -string(TOLOWER "${CMAKE_GENERATOR}" cmake_generator_tolower) -if(cmake_generator_tolower MATCHES "makefile") - message(STATUS "Some things you can do now:") - message(STATUS "--------------------+--------------------------------------------------------") - message(STATUS "Command | Description") - message(STATUS "--------------------+--------------------------------------------------------") - message(STATUS "make | Build the project") - message(STATUS "make test | Run unit tests") - message(STATUS "make install | Will install NMODL to: ${CMAKE_INSTALL_PREFIX}") - message(STATUS "--------------------+--------------------------------------------------------") - message(STATUS " Build option | Status") - message(STATUS "--------------------+--------------------------------------------------------") - message(STATUS "CXX COMPILER | ${CMAKE_CXX_COMPILER}") - message(STATUS "COMPILE FLAGS | ${COMPILER_FLAGS}") - message(STATUS "Build Type | ${CMAKE_BUILD_TYPE}") - message(STATUS "Legacy Units | ${NMODL_ENABLE_LEGACY_UNITS}") - message(STATUS "Python Bindings | ${NMODL_ENABLE_PYTHON_BINDINGS}") - message(STATUS "Flex | ${FLEX_EXECUTABLE}") - message(STATUS "Bison | ${BISON_EXECUTABLE}") - message(STATUS "Python | ${PYTHON_EXECUTABLE}") - if(NMODL_CLANG_FORMAT) - message(STATUS "Clang Format | ${ClangFormat_EXECUTABLE}") - endif() - if(NMODL_CMAKE_FORMAT) - message(STATUS "Cmake Format | ${CMakeFormat_EXECUTABLE}") - endif() - message(STATUS "--------------+--------------------------------------------------------------") - message(STATUS " See documentation : https://github.com/BlueBrain/nmodl/") - message(STATUS "--------------+--------------------------------------------------------------") +message(STATUS "You can now build NMODL using:") +if(${CMAKE_VERSION} VERSION_LESS "3.12") + message(STATUS " cmake --build . [--target TARGET] -- -j 8") +else() + message(STATUS " cmake --build . --parallel 8 [--target TARGET]") +endif() +message(STATUS "You might want to adjust the number of parallel build jobs for your system.") +message(STATUS "Some non-default targets you might want to build:") +message(STATUS "Some things you can do now:") +message(STATUS "--------------------+--------------------------------------------------------") +message(STATUS " Target | Description") +message(STATUS "--------------------+--------------------------------------------------------") +message(STATUS "test | Run unit tests") +message(STATUS "install | Will install NMODL to: ${CMAKE_INSTALL_PREFIX}") +message(STATUS "--------------------+--------------------------------------------------------") +message(STATUS " Build option | Status") +message(STATUS "--------------------+--------------------------------------------------------") +message(STATUS "CXX COMPILER | ${CMAKE_CXX_COMPILER}") +message(STATUS "COMPILE FLAGS | ${COMPILER_FLAGS}") +message(STATUS "Build Type | ${CMAKE_BUILD_TYPE}") +message(STATUS "Legacy Units | ${NMODL_ENABLE_LEGACY_UNITS}") +message(STATUS "Python Bindings | ${NMODL_ENABLE_PYTHON_BINDINGS}") +message(STATUS "Flex | ${FLEX_EXECUTABLE}") +message(STATUS "Bison | ${BISON_EXECUTABLE}") +message(STATUS "Python | ${PYTHON_EXECUTABLE}") +if(NMODL_CLANG_FORMAT) + message(STATUS "Clang Format | ${ClangFormat_EXECUTABLE}") +endif() +if(NMODL_CMAKE_FORMAT) + message(STATUS "Cmake Format | ${CMakeFormat_EXECUTABLE}") endif() +message(STATUS "--------------+--------------------------------------------------------------") +message(STATUS " See documentation : https://github.com/BlueBrain/nmodl/") +message(STATUS "--------------+--------------------------------------------------------------") message(STATUS "") diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index b6d95e937a..2455d47094 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -9,30 +9,30 @@ if(GIT_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} log -1 --format=%h WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_SHA1 + OUTPUT_VARIABLE NMODL_GIT_REVISION_SHA1 ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # get last commit date execute_process( COMMAND ${GIT_EXECUTABLE} show -s --format=%ci WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_REVISION_DATE + OUTPUT_VARIABLE NMODL_GIT_REVISION_DATE ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) # remove extra double quotes - string(REGEX REPLACE "\"" "" GIT_REVISION_DATE "${GIT_REVISION_DATE}") - set(GIT_REVISION "${GIT_REVISION_SHA1} ${GIT_REVISION_DATE}") + string(REGEX REPLACE "\"" "" NMODL_GIT_REVISION_DATE "${NMODL_GIT_REVISION_DATE}") + set(NMODL_GIT_REVISION "${NMODL_GIT_REVISION_SHA1} ${NMODL_GIT_REVISION_DATE}") # get the last version tag from git execute_process( COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --tags WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} - OUTPUT_VARIABLE GIT_LAST_TAG + OUTPUT_VARIABLE NMODL_GIT_LAST_TAG ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) else() - set(GIT_REVISION "unknown") - set(GIT_LAST_TAG "unknown") + set(NMODL_GIT_REVISION "unknown") + set(NMODL_GIT_LAST_TAG "unknown") endif() diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index 52ad0e2913..53cd519996 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -8,7 +8,7 @@ #include "config/config.h" /// Git version of the project -const std::string nmodl::Version::GIT_REVISION = "@GIT_REVISION@"; +const std::string nmodl::Version::GIT_REVISION = "@NMODL_GIT_REVISION@"; /// NMODL version const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; From 072c1ae17525ae276c39b189de8537f580a32434 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Mon, 16 Aug 2021 13:59:17 +0200 Subject: [PATCH 376/871] Suppress spammy warnings in NVHPC 20.7+ (BlueBrain/nmodl#716) Make sure these suppressions do not apply to, for example, CoreNEURON if NMODL is being built as a submodule. NMODL Repo SHA: BlueBrain/nmodl@b955149675e4aaef68deae093aa586cf4ccd3ea4 --- cmake/nmodl/CMakeLists.txt | 3 +++ cmake/nmodl/CompilerHelper.cmake | 23 ++++++++++++++++++++++- src/nmodl/lexer/CMakeLists.txt | 6 ++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index d916c41122..d8a83147c7 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -65,6 +65,9 @@ include(cmake/PythonLinkHelper.cmake) include(cmake/RpathHelper.cmake) include(cmake/ExternalProjectHelper.cmake) +# This should apply to all NMODL targets but should not leak out when NMODL is built as a submodule. +add_compile_options(${NMODL_COMPILER_WARNING_SUPPRESSIONS}) + # ============================================================================= # Set the project version now using git # ============================================================================= diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index d9546fd48a..98a54c8dde 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -18,5 +18,26 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") # messages specifically for AST. Disable these verbose warnings for now. # TODO : fix these warnings from template modification (#272) # ~~~ - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --diag_suppress 1,82,111,115,177,186,611,997,1097,1625") + if(${CMAKE_CXX_COMPILER_VERSION} VERSION_LESS 20.7) + set(NMODL_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=1,82,111,115,177,186,611,997,1097,1625) + else() + # https://forums.developer.nvidia.com/t/many-all-diagnostic-numbers-increased-by-1-from-previous-values/146268/3 + # changed the numbering scheme in newer versions. The following list is from a clean start 13 + # August 2021. It would clearly be nicer to apply these suppressions only to relevant files. + # Examples of the suppressed warnings are given below. + # ~~~ + # "ext/spdlog/include/spdlog/fmt/fmt.h", warning #1-D: last line of file ends without a newline + # "ext/fmt/include/fmt/format.h", warning #111-D: statement is unreachable + # "ext/json/json.hpp", warning #186-D: pointless comparison of unsigned integer with zero + # "src/ast/all.hpp", warning #998-D: function "..." is hidden by "..." -- virtual function override intended? + # "ext/spdlog/include/spdlog/fmt/bundled/format.h", warning #1098-D: unknown attribute "fallthrough" + # "ext/pybind11/include/pybind11/detail/common.h", warning #1626-D: routine is both "inline" and "noinline" + # ~~~ + # The following warnings do not seem to be suppressible with --diag_suppress: + # ~~~ + # "src/codegen/codegen_cuda_visitor.cpp", NVC++-W-0277-Cannot inline function - data type mismatch + # "nvc++IkWUbMugiSgNH.s: Warning: stand-alone `data16' prefix + # ~~~ + set(NMODL_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=1,111,186,998,1098,1626) + endif() endif() diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index a83b7819cb..008ea5f0e5 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -59,6 +59,12 @@ set(LEXER_SOURCE_FILES ${C_DRIVER_FILES} ${UNIT_DRIVER_FILES}) +if(NMODL_PGI_COMPILER) + # "verbatim_lexer.cpp", warning #550-D: variable "..." was set but never used + set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp + PROPERTIES COMPILE_FLAGS "--diag_suppress 550") +endif() + # ============================================================================= # Directories for parsers (as they need to be in separate directories) # ============================================================================= From 4c400631d417725adf7c136c1c48581d01785bb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20S=C4=83vulescu?= Date: Wed, 18 Aug 2021 23:47:28 +0200 Subject: [PATCH 377/871] Bump CMake min version to 3.15 (BlueBrain/nmodl#718) NMODL Repo SHA: BlueBrain/nmodl@97776aec19706973973b55194fd494a877086919 --- cmake/nmodl/CMakeLists.txt | 8 ++------ setup.py | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index d8a83147c7..3c96960e6c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -5,7 +5,7 @@ # See top-level LICENSE file for details. # ============================================================================= -cmake_minimum_required(VERSION 3.8 FATAL_ERROR) +cmake_minimum_required(VERSION 3.15 FATAL_ERROR) project(NMODL LANGUAGES CXX) @@ -238,11 +238,7 @@ message(STATUS "") message(STATUS "Configured NMODL ${PROJECT_VERSION} (${NMODL_GIT_REVISION})") message(STATUS "") message(STATUS "You can now build NMODL using:") -if(${CMAKE_VERSION} VERSION_LESS "3.12") - message(STATUS " cmake --build . [--target TARGET] -- -j 8") -else() - message(STATUS " cmake --build . --parallel 8 [--target TARGET]") -endif() +message(STATUS " cmake --build . --parallel 8 [--target TARGET]") message(STATUS "You might want to adjust the number of parallel build jobs for your system.") message(STATUS "Some non-default targets you might want to build:") message(STATUS "Some things you can do now:") diff --git a/setup.py b/setup.py index ec560c6c1e..cc7ea3d4fb 100644 --- a/setup.py +++ b/setup.py @@ -116,7 +116,7 @@ def _config_exe(exe_name): packages=["nmodl"], scripts=["pywheel/shim/nmodl", "pywheel/shim/find_libpython.py"], include_package_data=True, - cmake_minimum_required_version="3.3.0", + cmake_minimum_required_version="3.15.0", cmake_args=cmake_args, cmdclass=lazy_dict( docs=Docs, doctest=get_sphinx_command, buildhtml=get_sphinx_command, From f4c10ec66d60b597da88230cc03747cdb264fa92 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Fri, 20 Aug 2021 07:49:57 +0200 Subject: [PATCH 378/871] Use jupyter-client<7 due to issue with nbconvert (BlueBrain/nmodl#722) try suggestion in https://github.com/jupyter/jupyter_client/issues/637#issuecomment-902149763 NMODL Repo SHA: BlueBrain/nmodl@097856b81903cb6963f35dcaef7ce95cca6daf59 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index cc7ea3d4fb..b15e188fe0 100644 --- a/setup.py +++ b/setup.py @@ -124,6 +124,7 @@ def _config_exe(exe_name): zip_safe=False, setup_requires=[ "jinja2>=2.9.3", + "jupyter-client<7", # try and work around: TypeError in notebooks/nmodl-kinetic-schemes.ipynb: 'coroutine' object is not subscriptable "jupyter", "m2r", "mistune<2", # prevents a version conflict with nbconvert From 68f7cb5a17812990cf32a2039a2d2d71e1dfa19d Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Fri, 20 Aug 2021 08:50:50 +0200 Subject: [PATCH 379/871] Add nmodl_ prefix to find_python_module. (BlueBrain/nmodl#721) This should make things more robust against divergence between different projects' definitions of `find_python_module`, in case many git modules/subprojects are being configured in a single CMake run. NMODL Repo SHA: BlueBrain/nmodl@e4a690d025cb5f99b400f932eb08f3be6ee769d0 --- cmake/nmodl/CMakeLists.txt | 10 +++++----- cmake/nmodl/FindPythonModule.cmake | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 3c96960e6c..d09ee7054c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -142,11 +142,11 @@ endif() # ============================================================================= message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.5 REQUIRED) -find_python_module(jinja2 2.9.3 REQUIRED) -find_python_module(pytest 3.3.0 REQUIRED) -find_python_module(sympy 1.2 REQUIRED) -find_python_module(textwrap 0.9 REQUIRED) -find_python_module(yaml 3.12 REQUIRED) +nmodl_find_python_module(jinja2 2.9.3 REQUIRED) +nmodl_find_python_module(pytest 3.3.0 REQUIRED) +nmodl_find_python_module(sympy 1.2 REQUIRED) +nmodl_find_python_module(textwrap 0.9 REQUIRED) +nmodl_find_python_module(yaml 3.12 REQUIRED) # ============================================================================= # Compiler specific flags for external submodules diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake index e2ac0649fc..6fdbfd88d2 100644 --- a/cmake/nmodl/FindPythonModule.cmake +++ b/cmake/nmodl/FindPythonModule.cmake @@ -1,6 +1,6 @@ # * Macro to find a python module # -# Usage: find_python_module (module [VERSION] [REQUIRED]) +# Usage: nmodl_find_python_module (module [VERSION] [REQUIRED]) # # Copyright 2005-2018 Airbus-EDF-IMACS-Phimeca # @@ -9,7 +9,7 @@ # # https://github.com/openturns/otsubsetinverse/blob/master/cmake/FindPythonModule.cmake -macro(find_python_module module) +macro(nmodl_find_python_module module) string(TOUPPER ${module} module_upper) if(NOT ${module_upper}_FOUND) @@ -71,4 +71,4 @@ macro(find_python_module module) endif() mark_as_advanced(${module_upper}_LOCATION) endif(NOT ${module_upper}_FOUND) -endmacro(find_python_module) +endmacro(nmodl_find_python_module) From 1f2261144d9a4af6499f66cdaf496e293131fc1e Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 20 Aug 2021 08:55:24 +0200 Subject: [PATCH 380/871] Avoid use of assert in the OpenACC kernel (BlueBrain/nmodl#720) * Use of assert with atomic capture result into wrong results * See details in https://github.com/BlueBrain/mod2c/pull/68 NMODL Repo SHA: BlueBrain/nmodl@cf56a24192b084400f96055060c8e53d0f9495b0 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 5 +---- src/nmodl/codegen/codegen_c_visitor.cpp | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 9ed2e805d6..c5c4ef2f05 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -121,10 +121,7 @@ void CodegenAccVisitor::print_abort_routine() const { } void CodegenAccVisitor::print_net_send_buffering_grow() { - auto error = add_escape_quote("Error : netsend buffer size (%d) exceeded\\n"); - - printer->add_line("printf({}, nsb->_cnt);"_format(error)); - printer->add_line("coreneuron_abort();"); + // can not grow buffer during gpu execution } /** diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index a773a66b48..651e2e631f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3796,7 +3796,9 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { } void CodegenCVisitor::print_net_send_buffering_grow() { - printer->add_line("nsb->grow();"); + printer->add_line("if(i >= nsb->_size) {"); + printer->add_line(" nsb->grow();"); + printer->add_line("}"); } void CodegenCVisitor::print_net_send_buffering() { @@ -3813,17 +3815,15 @@ void CodegenCVisitor::print_net_send_buffering() { printer->add_line("int i = 0;"); print_device_atomic_capture_annotation(); printer->add_line("i = nsb->_cnt++;"); - printer->add_line("if(i >= nsb->_size) {"); - printer->increase_indent(); print_net_send_buffering_grow(); - printer->decrease_indent(); + printer->add_line("if(i < nsb->_size) {"); + printer->add_line(" nsb->_sendtype[i] = type;"); + printer->add_line(" nsb->_vdata_index[i] = vdata_index;"); + printer->add_line(" nsb->_weight_index[i] = weight_index;"); + printer->add_line(" nsb->_pnt_index[i] = point_index;"); + printer->add_line(" nsb->_nsb_t[i] = t;"); + printer->add_line(" nsb->_nsb_flag[i] = flag;"); printer->add_line("}"); - printer->add_line("nsb->_sendtype[i] = type;"); - printer->add_line("nsb->_vdata_index[i] = vdata_index;"); - printer->add_line("nsb->_weight_index[i] = weight_index;"); - printer->add_line("nsb->_pnt_index[i] = point_index;"); - printer->add_line("nsb->_nsb_t[i] = t;"); - printer->add_line("nsb->_nsb_flag[i] = flag;"); printer->end_block(1); } From ecfba32d6a4c62d9640162581be46f5e55570f75 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 26 Aug 2021 21:13:04 +0200 Subject: [PATCH 381/871] Fix warnings and improve ccache performance. (BlueBrain/nmodl#723) * Tweak NVHPC warning suppressions. This makes them work properly in standalone builds, when NMODL is not a submodule of CoreNEURON. * Add const version of PyAst::get_shared_ptr. Fixes NVHPC compiler warning: warning BlueBrain/nmodl#612-D: overloaded virtual function "nmodl::ast::Ast::get_shared_ptr" is only partially overridden in class "PyAst". * Remove unused variable. Fixes compiler warning. * Tweak code so clang-format-{11,12} agree. Otherwise clang-format-12 added whitespace that clang-format-11 did not. * Call flex and bison with relative paths. This avoids #line directives and asserts containing the absolute path of the build directory, which should in turn improve ccache performance in the CI. * Fix comment and add note about ccache misses. NMODL Repo SHA: BlueBrain/nmodl@e98f2819eb695d4c308ae3260598687440438275 --- cmake/nmodl/CMakeLists.txt | 1 - cmake/nmodl/CompilerHelper.cmake | 5 + src/nmodl/codegen/fast_math.hpp | 2 +- src/nmodl/config/config.cpp.in | 5 +- src/nmodl/language/templates/pybind/pyast.hpp | 4 + src/nmodl/lexer/CMakeLists.txt | 121 +++++++++++------- .../sympy_replace_solutions_visitor.cpp | 1 - test/nmodl/transpiler/unit/CMakeLists.txt | 2 + 8 files changed, 87 insertions(+), 54 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index d09ee7054c..d73bcdec3f 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -241,7 +241,6 @@ message(STATUS "You can now build NMODL using:") message(STATUS " cmake --build . --parallel 8 [--target TARGET]") message(STATUS "You might want to adjust the number of parallel build jobs for your system.") message(STATUS "Some non-default targets you might want to build:") -message(STATUS "Some things you can do now:") message(STATUS "--------------------+--------------------------------------------------------") message(STATUS " Target | Description") message(STATUS "--------------------+--------------------------------------------------------") diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 98a54c8dde..8f2efccb1e 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -39,5 +39,10 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") # "nvc++IkWUbMugiSgNH.s: Warning: stand-alone `data16' prefix # ~~~ set(NMODL_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=1,111,186,998,1098,1626) + # There are a few more warnings produced by the unit test infrastructure. + # ~~~ + # "test/unit/visitor/constant_folder.cpp", warning #177-D: variable "..." was declared but never referenced + # ~~~ + set(NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=177) endif() endif() diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index 957ba83658..6981051ede 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -133,7 +133,7 @@ static inline double vexpm1(double initial_x) { double px = std::floor(LOG2E * x + 0.5); const int32_t n = px; - const uint64_t twopnm1 = (((uint64_t)(n - 1)) + 1023) << 52; + const uint64_t twopnm1 = (static_cast(n - 1) + 1023) << 52; x = 2 * (uint642dp(twopnm1) * egm1(x, px) + uint642dp(twopnm1)) - 1; if (initial_x > EXP_LIMIT) diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index 53cd519996..4af2841d38 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -17,8 +17,9 @@ const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; * \brief Path of nrnutils.lib file * * nrnunits.lib need to be loaded at runtime. Before project is - * installed it needs to be read from NMODL_PROJECT_SOURCE_DIR and later - * from CMAKE_INSTALL_PREFIX. + * installed it needs to be read from NMODL_PROJECT_BINARY_DIR and later + * from CMAKE_INSTALL_PREFIX. Note that this use of NMODL_PROJECT_BINARY_DIR + * will cause ccache misses when the build prefix is changed. */ std::vector nmodl::NrnUnitsLib::NRNUNITSLIB_PATH = {"@CMAKE_INSTALL_PREFIX@/share/nmodl/nrnunits.lib", "@NMODL_PROJECT_BINARY_DIR@/share/nmodl/nrnunits.lib"}; diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 0e3910c140..ee0c6699fe 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -102,6 +102,10 @@ struct PyAst: public Ast { PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_shared_ptr, ); } + std::shared_ptr get_shared_ptr() const override { + PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_shared_ptr, ); + } + const ModToken* get_token() const override { PYBIND11_OVERLOAD(const ModToken*, Ast, get_token, ); } diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 008ea5f0e5..a051eca375 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -74,101 +74,124 @@ file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR} # ============================================================================= # Lexer & Parser commands # ============================================================================= -# command to generate nmodl parser +# Command to generate nmodl parser Construct a relative path to avoid baking any absolute paths into +# the output .cpp and .hpp files (via __FILE__ and so on), which cause ccache misses when the build +# prefix changes (e.g. GitLab CI). +set(NMODL_PARSER_SOURCE_DIR "${NMODL_PROJECT_SOURCE_DIR}/src/parser") +set(NMODL_PARSER_BINARY_DIR "${NMODL_PROJECT_BINARY_DIR}/src/parser") +file(RELATIVE_PATH NMODL_YY_FROM_PARSER_BINARY_DIR "${NMODL_PARSER_BINARY_DIR}" + "${NMODL_PARSER_SOURCE_DIR}/nmodl.yy") add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl.yy pyastgen + OUTPUT "${NMODL_PARSER_BINARY_DIR}/nmodl/location.hh" + "${NMODL_PARSER_BINARY_DIR}/nmodl/nmodl_parser.cpp" + "${NMODL_PARSER_BINARY_DIR}/nmodl/nmodl_parser.hpp" + "${NMODL_PARSER_BINARY_DIR}/nmodl/position.hh" + "${NMODL_PARSER_BINARY_DIR}/nmodl/stack.hh" + WORKING_DIRECTORY "${NMODL_PARSER_BINARY_DIR}" + COMMAND ${BISON_EXECUTABLE} ARGS -d -o nmodl/nmodl_parser.cpp "${NMODL_YY_FROM_PARSER_BINARY_DIR}" + DEPENDS "${NMODL_PARSER_SOURCE_DIR}/nmodl.yy" pyastgen COMMENT "-- NMODL : GENERATING NMODL_CORE PARSER WITH BISON! --") -# command to generate verbatim parser +# Command to generate verbatim parser. See comment above about absolute paths. +file(RELATIVE_PATH VERBATIM_YY_FROM_PARSER_BINARY_DIR "${NMODL_PARSER_BINARY_DIR}" + "${NMODL_PARSER_SOURCE_DIR}/verbatim.yy") add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/verbatim.yy + OUTPUT "${NMODL_PARSER_BINARY_DIR}/verbatim_parser.cpp" + "${NMODL_PARSER_BINARY_DIR}/verbatim_parser.hpp" + WORKING_DIRECTORY "${NMODL_PARSER_BINARY_DIR}" + COMMAND ${BISON_EXECUTABLE} ARGS -d -o verbatim_parser.cpp "${VERBATIM_YY_FROM_PARSER_BINARY_DIR}" + DEPENDS "${NMODL_PARSER_SOURCE_DIR}/verbatim.yy" COMMENT "-- NMODL : GENERATING VERBATIM PARSER WITH BISON! --") -# command to generate differential equation parser +# Command to generate differential equation parser. See comment above about absolute paths. +file(RELATIVE_PATH DIFFEQ_YY_FROM_PARSER_BINARY_DIR "${NMODL_PARSER_BINARY_DIR}" + "${NMODL_PARSER_SOURCE_DIR}/diffeq.yy") add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq.yy - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp + OUTPUT "${NMODL_PARSER_BINARY_DIR}/diffeq/diffeq_parser.cpp" + "${NMODL_PARSER_BINARY_DIR}/diffeq/diffeq_parser.hpp" + "${NMODL_PARSER_BINARY_DIR}/diffeq/stack.hh" + WORKING_DIRECTORY "${NMODL_PARSER_BINARY_DIR}" + COMMAND ${BISON_EXECUTABLE} ARGS -d -o diffeq/diffeq_parser.cpp + "${DIFFEQ_YY_FROM_PARSER_BINARY_DIR}" + DEPENDS "${NMODL_PARSER_SOURCE_DIR}/diffeq.yy" "${NMODL_PARSER_SOURCE_DIR}/diffeq_context.hpp" + "${NMODL_PARSER_SOURCE_DIR}/diffeq_context.cpp" + "${NMODL_PARSER_SOURCE_DIR}/diffeq_helper.hpp" COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION PARSER WITH BISON! --") -# command to generate C (11) parser +# Command to generate C (11) parser. See comment above about absolute paths. +file(RELATIVE_PATH C11_YY_FROM_PARSER_BINARY_DIR "${NMODL_PARSER_BINARY_DIR}" + "${NMODL_PARSER_SOURCE_DIR}/c11.yy") add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11.yy + OUTPUT "${NMODL_PARSER_BINARY_DIR}/c/c11_parser.cpp" "${NMODL_PARSER_BINARY_DIR}/c/c11_parser.hpp" + "${NMODL_PARSER_BINARY_DIR}/c/stack.hh" + WORKING_DIRECTORY "${NMODL_PARSER_BINARY_DIR}" + COMMAND ${BISON_EXECUTABLE} ARGS -d -o c/c11_parser.cpp "${C11_YY_FROM_PARSER_BINARY_DIR}" + DEPENDS "${NMODL_PARSER_SOURCE_DIR}/c11.yy" COMMENT "-- NMODL : GENERATING C (11) PARSER WITH BISON! --") -# command to generate Units parser +# Command to generate Units parser. See comment above about absolute paths. +file(RELATIVE_PATH UNIT_YY_FROM_PARSER_BINARY_DIR "${NMODL_PARSER_BINARY_DIR}" + "${NMODL_PARSER_SOURCE_DIR}/unit.yy") add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/stack.hh - COMMAND ${BISON_EXECUTABLE} ARGS -d -o ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy - DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit.yy + OUTPUT "${NMODL_PARSER_BINARY_DIR}/unit/unit_parser.cpp" + "${NMODL_PARSER_BINARY_DIR}/unit/unit_parser.hpp" + "${NMODL_PARSER_BINARY_DIR}/unit/stack.hh" + WORKING_DIRECTORY "${NMODL_PARSER_BINARY_DIR}" + COMMAND ${BISON_EXECUTABLE} ARGS -d -o unit/unit_parser.cpp "${UNIT_YY_FROM_PARSER_BINARY_DIR}" + DEPENDS "${NMODL_PARSER_SOURCE_DIR}/unit.yy" COMMENT "-- NMODL : GENERATING UNIT PARSER WITH BISON! --") -# command to generate nmodl lexer +# Command to generate nmodl lexer. See comment above about absolute paths. +file(RELATIVE_PATH NMODL_LL_FROM_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll + COMMAND ${FLEX_EXECUTABLE} ARGS "${NMODL_LL_FROM_BINARY_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/nmodl.ll ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp COMMENT "-- NMODL : GENERATING NMODL LEXER WITH FLEX! --") -# command to generate verbatim lexer +# Command to generate verbatim lexer. See comment above about absolute paths. +file(RELATIVE_PATH VERBATIM_LL_FROM_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l + COMMAND ${FLEX_EXECUTABLE} ARGS "${VERBATIM_LL_FROM_BINARY_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/verbatim.l COMMENT "-- NMODL : GENERATING VERBATIM LEXER WITH FLEX! --") -# command to generate differential equation lexer +# Command to generate differential equation lexer. See comment above about absolute paths. +file(RELATIVE_PATH DIFFEQ_LL_FROM_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll + COMMAND ${FLEX_EXECUTABLE} ARGS "${DIFFEQ_LL_FROM_BINARY_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/diffeq.ll COMMENT "-- NMODL : GENERATING DIFFERENTIAL EQUATION LEXER WITH FLEX! --") -# command to generate C (11) lexer +# Command to generate C (11) lexer. See comment above about absolute paths. +file(RELATIVE_PATH C11_LL_FROM_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/c11.ll") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll + COMMAND ${FLEX_EXECUTABLE} ARGS "${C11_LL_FROM_BINARY_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/c11.ll COMMENT "-- NMODL : GENERATING C(11) LEXER WITH FLEX! --") -# command to generate Units lexer +# Command to generate Units lexer. See comment above about absolute paths. +file(RELATIVE_PATH UNIT_LL_FROM_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" + "${CMAKE_CURRENT_SOURCE_DIR}/unit.ll") add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp - COMMAND ${FLEX_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll + COMMAND ${FLEX_EXECUTABLE} ARGS "${UNIT_LL_FROM_BINARY_DIR}" WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/unit.ll COMMENT "-- NMODL : GENERATING UNIT LEXER WITH FLEX! --") diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index 2c8aab1c35..e57be6de87 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -179,7 +179,6 @@ void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( case ReplacePolicy::VALUE: { const auto dependencies = statement_dependencies(get_lhs(node), get_rhs(node)); const auto& key = dependencies.first; - const auto& vars = dependencies.second; if (solution_statements.is_var_assigned_here(key)) { logger->debug("SympyReplaceSolutionsVisitor :: marking for replacement {}", diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 12b7ac17bb..bd16ae2fc8 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -1,5 +1,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) +add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) + include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/catch ${NMODL_PROJECT_SOURCE_DIR}/test) include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/solver) From 880171bbc46f49403871085913e1229202e778fa Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Fri, 27 Aug 2021 11:58:39 +0300 Subject: [PATCH 382/871] Fix Newton solver code generation (BlueBrain/nmodl#696) * Fixes compilation issue when the operator() was declared as const while it changed some of values outside of its scope in the sympy solver * Use DefUseAnalyzeVisitor to find out whether the operator() is Using or Defining any of the variables outside its scope * Update DefUseAnalyzeVisitor to be able to process LOCAL variables as well, given precedence to GLOBAL/RANGE variables, when they have the same name * Added related unit tests * Fix codegen issue in CONSTRUCTOR and DESTRUCTOR blocks code when compiled for CoreNEURON Fixes BlueBrain/nmodl#691 FIxes BlueBrain/nmodl#692 NMODL Repo SHA: BlueBrain/nmodl@1321c7b78b40331605caaa6a3d8b4bf6417214a8 --- src/nmodl/codegen/codegen_c_visitor.cpp | 74 +++++++++- .../templates/visitors/symtab_visitor.hpp | 6 +- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 45 ++++-- src/nmodl/visitors/defuse_analyze_visitor.hpp | 29 +++- test/nmodl/transpiler/unit/parser/parser.cpp | 2 +- .../unit/visitor/defuse_analyze.cpp | 131 +++++++++++++++++- 7 files changed, 262 insertions(+), 27 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 651e2e631f..e10bf10a7b 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -21,7 +21,9 @@ #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" +#include "visitors/defuse_analyze_visitor.hpp" #include "visitors/rename_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #include "visitors/var_usage_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -32,7 +34,11 @@ namespace codegen { using namespace ast; +using visitor::DefUseAnalyzeVisitor; +using visitor::DUChain; +using visitor::DUState; using visitor::RenameVisitor; +using visitor::SymtabVisitor; using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; @@ -1717,6 +1723,54 @@ std::string CodegenCVisitor::find_var_unique_name(const std::string& original_na return unique_name; } +/** + * @brief Checks whether the functor_block generated by sympy solver modifies any variable outside + * its scope. If it does then return false, so that the operator() of the struct functor of the + * Eigen Newton solver doesn't have const qualifier. + * + * @param variable_block Statement Block of the variables declarations used in the functor struct of + * the solver + * @param functor_block Actual code being printed in the operator() of the functor struct of the + * solver + * @return True if operator() is const else False + */ +bool is_functor_const(const ast::StatementBlock& variable_block, + const ast::StatementBlock& functor_block) { + // Save DUChain for every variable in variable_block + std::unordered_map chains; + + // Create complete_block with both variable declarations (done in variable_block) and solver + // part (done in functor_block) to be able to run the SymtabVisitor and DefUseAnalyzeVisitor + // then and get the proper DUChains for the variables defined in the variable_block + ast::StatementBlock complete_block(functor_block); + // Typically variable_block has only one statement, a statement containing the declaration + // of the local variables + for (const auto& statement: variable_block.get_statements()) { + complete_block.insert_statement(complete_block.get_statements().begin(), statement); + } + + // Create Symbol Table for complete_block + auto model_symbol_table = std::make_shared(); + SymtabVisitor(model_symbol_table.get()).visit_statement_block(complete_block); + // Initialize DefUseAnalyzeVisitor to generate the DUChains for the variables defined in the + // variable_block + DefUseAnalyzeVisitor v(*complete_block.get_symbol_table()); + + // Check the DUChains for all the variables in the variable_block + // If variable is defined in complete_block don't add const quilifier in operator() + auto is_functor_const = true; + const auto& variables = collect_nodes(variable_block, {ast::AstNodeType::LOCAL_VAR}); + for (const auto& variable: variables) { + const auto& chain = v.analyze(complete_block, variable->get_node_name()); + is_functor_const = !(chain.eval() == DUState::D || chain.eval() == DUState::LD || + chain.eval() == DUState::CD); + if (!is_functor_const) + break; + } + + return is_functor_const; +} + void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { // solution vector to store copy of state vars for Newton solver printer->add_newline(); @@ -1759,13 +1813,23 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv instance_struct(), "{}")); printer->add_indent(); + + const auto& variable_block = *node.get_variable_block(); + const auto& functor_block = *node.get_functor_block(); + printer->add_text( "void operator()(const Eigen::Matrix<{0}, {1}, 1>& {2}, Eigen::Matrix<{0}, {1}, " "1>& {3}, " - "Eigen::Matrix<{0}, {1}, {1}>& {4}) const"_format(float_type, N, X, F, Jm)); + "Eigen::Matrix<{0}, {1}, {1}>& {4}) {5}"_format( + float_type, + N, + X, + F, + Jm, + is_functor_const(variable_block, functor_block) ? "const " : "")); printer->start_block(); printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); - print_statement_block(*node.get_functor_block(), false, false); + print_statement_block(functor_block, false, false); printer->end_block(2); // assign newton solver results in matrix X to state vars @@ -3274,6 +3338,10 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type) { // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks // running anything on the GPU. print_kernel_data_present_annotation_block_begin(); + } else { + /// TODO: Remove this when the code generation is propery done + /// Related to https://github.com/BlueBrain/nmodl/issues/692 + printer->add_line("#ifndef CORENEURON_BUILD"); } printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); @@ -3373,6 +3441,7 @@ void CodegenCVisitor::print_nrn_constructor() { const auto& block = info.constructor_node->get_statement_block(); print_statement_block(*block.get(), false, false); } + printer->add_line("#endif"); printer->end_block(1); } @@ -3384,6 +3453,7 @@ void CodegenCVisitor::print_nrn_destructor() { const auto& block = info.destructor_node->get_statement_block(); print_statement_block(*block.get(), false, false); } + printer->add_line("#endif"); printer->end_block(1); } diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index dbab9b41da..22aba173cc 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -36,7 +36,7 @@ namespace visitor { */ class SymtabVisitor: public AstVisitor { private: - symtab::ModelSymbolTable* modsymtab; + symtab::ModelSymbolTable* modsymtab = nullptr; std::unique_ptr printer; std::set block_to_solve; @@ -48,6 +48,10 @@ class SymtabVisitor: public AstVisitor { : printer(new printer::JSONPrinter()) , update(update) {} + SymtabVisitor(symtab::ModelSymbolTable* _modsymtab, bool update = false) + : modsymtab(_modsymtab) + , update(update) {} + SymtabVisitor(std::ostream& os, bool update = false) : printer(new printer::JSONPrinter(os)) , update(update) {} diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index ec6ed4b3a4..543cbd48bc 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -238,7 +238,7 @@ class SymbolTable { */ class ModelSymbolTable { /// symbol table for mod file (always top level symbol table) - std::shared_ptr symtab = nullptr; + std::shared_ptr symtab; /// current symbol table being constructed SymbolTable* current_symtab = nullptr; diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 678603cd4d..44837be5b7 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -86,11 +86,14 @@ std::string DUChain::to_string(bool compact) const { * As these are innermost blocks, we have to just check first use * of variable in this block and that's the result of this block. */ -DUState DUInstance::sub_block_eval() const { +DUState DUInstance::sub_block_eval(DUVariableType variable_type = DUVariableType::Global) const { DUState result = DUState::NONE; for (const auto& chain: children) { - const auto& child_state = chain.eval(); - if (child_state == DUState::U || child_state == DUState::D) { + const auto& child_state = chain.eval(variable_type); + if ((variable_type == DUVariableType::Global && + (child_state == DUState::U || child_state == DUState::D)) || + (variable_type == DUVariableType::Local && + (child_state == DUState::LU || child_state == DUState::LD))) { result = child_state; break; } @@ -127,23 +130,27 @@ DUState DUInstance::sub_block_eval() const { * block encountered, this means every block has either "D" or "CD". In * this case we can say that entire block effectively has "D". */ -DUState DUInstance::conditional_block_eval() const { +DUState DUInstance::conditional_block_eval( + DUVariableType variable_type = DUVariableType::Global) const { DUState result = DUState::NONE; bool block_with_none = false; for (const auto& chain: children) { - auto child_state = chain.eval(); - if (child_state == DUState::U) { + auto child_state = chain.eval(variable_type); + if ((variable_type == DUVariableType::Global && child_state == DUState::U) || + (variable_type == DUVariableType::Local && child_state == DUState::LU)) { result = child_state; break; } if (child_state == DUState::NONE) { block_with_none = true; } - if (child_state == DUState::D || child_state == DUState::CD) { + if ((variable_type == DUVariableType::Global && child_state == DUState::D) || + (variable_type == DUVariableType::Local && child_state == DUState::LD) || + child_state == DUState::CD) { result = DUState::CD; if (chain.state == DUState::ELSE && !block_with_none) { - result = DUState::D; + result = child_state; break; } } @@ -155,12 +162,12 @@ DUState DUInstance::conditional_block_eval() const { * Note that we are interested in "global" variable usage * and hence we consider only [U,D] states and not [LU, LD] */ -DUState DUInstance::eval() const { +DUState DUInstance::eval(DUVariableType variable_type = DUVariableType::Global) const { auto result = state; if (state == DUState::IF || state == DUState::ELSEIF || state == DUState::ELSE) { - result = sub_block_eval(); + result = sub_block_eval(variable_type); } else if (state == DUState::CONDITIONAL_BLOCK) { - result = conditional_block_eval(); + result = conditional_block_eval(variable_type); } return result; } @@ -170,8 +177,9 @@ DUState DUInstance::eval() const { DUState DUChain::eval() const { auto result = DUState::NONE; for (auto& inst: chain) { - auto re = inst.eval(); - if (re == DUState::U || re == DUState::D) { + auto re = inst.eval(variable_type); + if ((variable_type == DUVariableType::Global && (re == DUState::U || re == DUState::D)) || + (variable_type == DUVariableType::Local && (re == DUState::LU || re == DUState::LD))) { result = re; break; } @@ -418,9 +426,18 @@ DUChain DefUseAnalyzeVisitor::analyze(const ast::Ast& node, const std::string& n visiting_lhs = false; current_symtab = global_symtab; unsupported_node = false; + auto global_symbol = global_symtab->lookup_in_scope(variable_name); + // If global_symbol exists in the global_symtab then search for a global variable. Otherwise the + // variable can only be local if it exists + auto global_symbol_properties = NmodlType::global_var | NmodlType::range_var; + if (global_symbol != nullptr && global_symbol->has_any_property(global_symbol_properties)) { + variable_type = DUVariableType::Global; + } else { + variable_type = DUVariableType::Local; + } /// new chain - DUChain usage(node.get_node_type_name()); + DUChain usage(node.get_node_type_name(), variable_type); current_chain = &usage.chain; /// analyze given node diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 9508078203..1bd9bf8d03 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -30,11 +30,11 @@ enum class DUState { U, /// global variable is defined D, - /// global variable is conditionally defined + /// global or local variable is conditionally defined CD, /// local variable is used LU, - /// local variable is used + /// local variable is defined LD, /// state not known UNKNOWN, @@ -50,6 +50,14 @@ enum class DUState { NONE }; +/** + * Variable type processed by DefUseAnalyzeVisitor + * + * DUVariableType::Local means that we are looking for LD, LU and CD DUStates, while Global means we + * are looking for U, D and CD DUStates. + */ +enum class DUVariableType { Local, Global }; + std::ostream& operator<<(std::ostream& os, DUState state); /** @@ -88,13 +96,13 @@ class DUInstance { , binary_expression(binary_expression) {} /// analyze all children and return "effective" usage - DUState eval() const; + DUState eval(DUVariableType variable_type) const; /// if, elseif and else evaluation - DUState sub_block_eval() const; + DUState sub_block_eval(DUVariableType variable_type) const; /// evaluate global usage i.e. with [D,U] states of children - DUState conditional_block_eval() const; + DUState conditional_block_eval(DUVariableType variable_type) const; void print(printer::JSONPrinter& printer) const; @@ -125,12 +133,16 @@ class DUChain { /// name of the node std::string name; + /// type of variable + DUVariableType variable_type; + /// def-use chain for a variable std::vector chain; DUChain() = default; - explicit DUChain(std::string name) - : name(std::move(name)) {} + DUChain(std::string name, DUVariableType type) + : name(std::move(name)) + , variable_type(type) {} /// return "effective" usage of a variable DUState eval() const; @@ -217,6 +229,9 @@ class DefUseAnalyzeVisitor: protected ConstAstVisitor { /// variable for which to construct def-use chain std::string variable_name; + /// variable type (Local or Global) + DUVariableType variable_type; + /// indicate that there is unsupported construct encountered bool unsupported_node = false; diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index f8b7bb632a..b654129c4c 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -210,7 +210,7 @@ std::string solve_construct(const std::string& equation, std::string method) { return solution; } -SCENARIO("Legacy differential equation solver from NEURON solve number of ODE types") { +SCENARIO("Legacy differential equation solver") { GIVEN("A differential equation") { int counter = 0; for (const auto& test_case: diff_eq_constructs) { diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index f7b4f42edc..2667a5ced0 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -244,7 +244,7 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { std::string expected_text = R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]},{"ELSE":[{"name":"D"}]}]}]})"; - THEN("Def-Use chains should return NONE") { + THEN("Def-Use chains should return CD") { std::string input = reindent_text(nmodl_text); auto chains = run_defuse_visitor(input, "tau"); REQUIRE(chains[0].to_string() == expected_text); @@ -315,6 +315,135 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { } } + GIVEN("local variable usage in else block") { + std::string nmodl_text = R"( + NEURON { + RANGE tau + } + + DERIVATIVE states { + IF (tau == 1) { + } ELSE { + LOCAL tau + tau = 1 + tau + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"U"}]},{"ELSE":[{"name":"LU"},{"name":"LD"}]}]}]})"; + + THEN("Def-Use chains should return USE because global variables have precedence in eval") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::U); + } + } + + GIVEN("local variable conditional definition") { + std::string nmodl_text = R"( + NEURON { + RANGE beta + } + + DERIVATIVE states { + LOCAL tau + IF (beta == 0) { + tau = 1 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]}]}]})"; + + THEN("Def-Use chains should return USE because global variables have precedence in eval") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "tau"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::CD); + } + } + + GIVEN("local and range variables usage and definitions") { + std::string nmodl_text = R"( + NEURON { + RANGE beta + } + + DERIVATIVE states { + LOCAL tau + IF (beta == 0) { + tau = 1 + } ELSE { + beta = 0 + } + } + )"; + + std::string expected_text = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"name":"IF"},{"name":"ELSE"}]}]})"; + + THEN( + "Def-Use chains should return NONE because the variable we look for is not one of " + "them") { + std::string input = reindent_text(nmodl_text); + auto chains = run_defuse_visitor(input, "alpha"); + REQUIRE(chains[0].to_string() == expected_text); + REQUIRE(chains[0].eval() == DUState::NONE); + } + } + + GIVEN("Simple check of local and global variables") { + std::string nmodl_text = R"( + NEURON { + GLOBAL x + } + + DERIVATIVE states { + LOCAL a, b + a = 1 + IF (x == 1) { + LOCAL c + c = 1 + } + } + )"; + + std::string expected_text_x = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"U"}]}]}]})"; + std::string expected_text_a = + R"({"DerivativeBlock":[{"name":"LD"},{"CONDITIONAL_BLOCK":[{"name":"IF"}]}]})"; + std::string expected_text_b = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"name":"IF"}]}]})"; + std::string expected_text_c = + R"({"DerivativeBlock":[{"CONDITIONAL_BLOCK":[{"IF":[{"name":"LD"}]}]}]})"; + + THEN("local and global variables are correctly analyzed") { + std::string input = reindent_text(nmodl_text); + auto chains_x = run_defuse_visitor(input, "x"); + // Global variable "x" should be U as it's only used in the IF-ELSE statement + REQUIRE(chains_x[0].to_string() == expected_text_x); + REQUIRE(chains_x[0].eval() == DUState::U); + + auto chains_a = run_defuse_visitor(input, "a"); + // Local variable "a" should be LD as it's local and defined + REQUIRE(chains_a[0].to_string() == expected_text_a); + REQUIRE(chains_a[0].eval() == DUState::LD); + + auto chains_b = run_defuse_visitor(input, "b"); + // Local variable "b" should be NONE as it's not used + REQUIRE(chains_b[0].to_string() == expected_text_b); + REQUIRE(chains_b[0].eval() == DUState::NONE); + + auto chains_c = run_defuse_visitor(input, "c"); + // Local variable "c" should be CD as it's conditionally defined + REQUIRE(chains_c[0].to_string() == expected_text_c); + REQUIRE(chains_c[0].eval() == DUState::CD); + } + } + GIVEN("global variable definition in if-else block") { std::string nmodl_text = R"( NEURON { From 09dbdfe0fd7e9c19157a62486c7d1166c641649a Mon Sep 17 00:00:00 2001 From: Christos Kotsalos Date: Mon, 6 Sep 2021 17:27:24 +0200 Subject: [PATCH 383/871] Fix compatibility issues between Eigen and GPUs (OpenACC/CUDA) (BlueBrain/nmodl#728) * Two factors contribute to the above solution: - New eigen branch (version 3.5 and above). Currently we are using a mirrored version of Eigen in BlueBrain organisation https://github.com/BlueBrain/eigen/releases/tag/v3.5-alpha - An API that makes possible to call any Eigen `__device__` function from within OpenACC regions. * More details: Eigen-3.5+ provides better GPU support; however, some functions cannot be called directly from within OpenACC regions. Therefore, we need to wrap them in a special API (decorate them with `__device__` & `acc routine` tokens), which allows us to eventually call them from OpenACC. Calling these functions from CUDA kernels presents no issue. * From BlueBrain/nmodl#726: Avoid use `[]` operator with eigen Matrix objects. This results into runtime error with OpenACC and PGI compiler. * Note that this should works in combination with BlueBrain/CoreNeuron/pull/624 fixes BlueBrain/nmodl#311 fixes BlueBrain/nmodl#135 NMODL Repo SHA: BlueBrain/nmodl@5f4462a85a8fe97f10da4d0885d80b8423396083 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 24 +++++++ src/nmodl/codegen/codegen_acc_visitor.hpp | 7 ++ src/nmodl/codegen/codegen_c_visitor.cpp | 68 +++++++++++++------ src/nmodl/codegen/codegen_c_visitor.hpp | 6 ++ src/nmodl/solver/CMakeLists.txt | 13 ++-- src/nmodl/solver/newton/newton.hpp | 17 ++++- .../solver/partial_piv_lu/partial_piv_lu.cu | 36 ++++++++++ .../solver/partial_piv_lu/partial_piv_lu.h | 28 ++++++++ test/nmodl/transpiler/unit/CMakeLists.txt | 4 +- 9 files changed, 176 insertions(+), 27 deletions(-) create mode 100644 src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu create mode 100644 src/nmodl/solver/partial_piv_lu/partial_piv_lu.h diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index c5c4ef2f05..431aa607d2 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -7,6 +7,9 @@ #include "codegen/codegen_acc_visitor.hpp" +#include "ast/eigen_linear_solver_block.hpp" +#include "ast/integer.hpp" + using namespace fmt::literals; @@ -73,6 +76,15 @@ void CodegenAccVisitor::print_backend_includes() { printer->add_line("#include "); printer->add_line("#include "); } + + if (info.eigen_linear_solver_exist && std::accumulate(info.state_vars.begin(), + info.state_vars.end(), + 0, + [](int l, const SymbolType& variable) { + return l += variable->get_length(); + }) > 4) { + printer->add_line("#include "); + } } @@ -124,6 +136,18 @@ void CodegenAccVisitor::print_net_send_buffering_grow() { // can not grow buffer during gpu execution } +void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, + int N, + const std::string& Xm, + const std::string& Jm, + const std::string& Fm) { + if (N <= 4) { + printer->add_line("{0} = {1}.inverse()*{2};"_format(Xm, Jm, Fm)); + } else { + printer->add_line("{0} = partialPivLu<{1}>({2}, {3});"_format(Xm, N, Jm, Fm)); + } +} + /** * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded * to accelerator. In this case, at very top level, we print pragma diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 4810464626..4a2b74345b 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -113,6 +113,13 @@ class CodegenAccVisitor: public CodegenCVisitor { void print_net_send_buffering_grow() override; + void print_eigen_linear_solver(const std::string& float_type, + int N, + const std::string& Xm, + const std::string& Jm, + const std::string& Fm) override; + + public: CodegenAccVisitor(const std::string& mod_file, const std::string& output_dir, diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index e10bf10a7b..ad486d3aa6 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -531,21 +531,27 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) const { int CodegenCVisitor::float_variables_size() const { - auto count_length = [](const std::vector& variables) -> int { - int length = 0; - for (const auto& variable: variables) { - length += variable->get_length(); - } - return length; + auto count_length = [](int l, const SymbolType& variable) { + return l += variable->get_length(); }; - int float_size = count_length(info.range_parameter_vars); - float_size += count_length(info.range_assigned_vars); - float_size += count_length(info.range_state_vars); - float_size += count_length(info.assigned_vars); + int float_size = std::accumulate(info.range_parameter_vars.begin(), + info.range_parameter_vars.end(), + 0, + count_length); + float_size += std::accumulate(info.range_assigned_vars.begin(), + info.range_assigned_vars.end(), + 0, + count_length); + float_size += std::accumulate(info.range_state_vars.begin(), + info.range_state_vars.end(), + 0, + count_length); + float_size += + std::accumulate(info.assigned_vars.begin(), info.assigned_vars.end(), 0, count_length); /// all state variables for which we add Dstate variables - float_size += count_length(info.state_vars); + float_size += std::accumulate(info.state_vars.begin(), info.state_vars.end(), 0, count_length); /// for v_unused variable if (info.vectorize) { @@ -1779,13 +1785,16 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv // try to use a different string for the matrices created by sympy in the form // X_, J_, Jm_ and F_ std::string X = find_var_unique_name("X"); + std::string Xm = find_var_unique_name("Xm"); std::string J = find_var_unique_name("J"); std::string Jm = find_var_unique_name("Jm"); std::string F = find_var_unique_name("F"); + std::string Fm = find_var_unique_name("Fm"); auto float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{}, {}, 1> {};"_format(float_type, N, X)); + printer->add_line("Eigen::Matrix<{}, {}, 1> {};"_format(float_type, N, Xm)); + printer->add_line("{}* {} = {}.data();"_format(float_type, X, Xm)); print_statement_block(*node.get_setup_x_block(), false, false); @@ -1823,12 +1832,14 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv "Eigen::Matrix<{0}, {1}, {1}>& {4}) {5}"_format( float_type, N, - X, - F, + Xm, + Fm, Jm, is_functor_const(variable_block, functor_block) ? "const " : "")); printer->start_block(); + printer->add_line("const {}* {} = {}.data();"_format(float_type, X, Xm)); printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); + printer->add_line("{}* {} = {}.data();"_format(float_type, F, Fm)); print_statement_block(functor_block, false, false); printer->end_block(2); @@ -1846,7 +1857,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->add_line("functor newton_functor(nt, inst, id, pnodecount, v, indexes);"); printer->add_line("newton_functor.initialize();"); printer->add_line( - "int newton_iterations = nmodl::newton::newton_solver({}, newton_functor);"_format(X)); + "int newton_iterations = nmodl::newton::newton_solver({}, newton_functor);"_format(Xm)); // assign newton solver results in matrix X to state vars print_statement_block(*node.get_update_states_block(), false, false); @@ -1860,27 +1871,46 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv // try to use a different string for the matrices created by sympy in the form // X_, J_, Jm_ and F_ std::string X = find_var_unique_name("X"); + std::string Xm = find_var_unique_name("Xm"); std::string J = find_var_unique_name("J"); std::string Jm = find_var_unique_name("Jm"); std::string F = find_var_unique_name("F"); + std::string Fm = find_var_unique_name("Fm"); const std::string float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{0}, {1}, 1> {2}, {3};"_format(float_type, N, X, F)); + printer->add_line("Eigen::Matrix<{0}, {1}, 1> {2}, {3};"_format(float_type, N, Xm, Fm)); printer->add_line("Eigen::Matrix<{0}, {1}, {1}> {2};"_format(float_type, N, Jm)); + printer->add_line("{}* {} = {}.data();"_format(float_type, X, Xm)); printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); + printer->add_line("{}* {} = {}.data();"_format(float_type, F, Fm)); print_statement_block(*node.get_variable_block(), false, false); print_statement_block(*node.get_initialize_block(), false, false); print_statement_block(*node.get_setup_x_block(), false, false); printer->add_newline(); - printer->add_line( - "{0} = Eigen::PartialPivLU>>({3}).solve({4});"_format( - X, float_type, N, Jm, F)); + print_eigen_linear_solver(float_type, N, Xm, Jm, Fm); + printer->add_newline(); + print_statement_block(*node.get_update_states_block(), false, false); print_statement_block(*node.get_finalize_block(), false, false); } +void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, + int N, + const std::string& Xm, + const std::string& Jm, + const std::string& Fm) { + if (N <= 4) { + // Faster compared to LU, given the template specialization in Eigen. + printer->add_line("{0} = {1}.inverse()*{2};"_format(Xm, Jm, Fm)); + } else { + printer->add_line( + "{0} = Eigen::PartialPivLU>>({3}).solve({4});"_format( + Xm, float_type, N, Jm, Fm)); + } +} + /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 524740a7bb..ce98ff0879 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -1898,6 +1899,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void visit_function_call(const ast::FunctionCall& node) override; void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; + virtual void print_eigen_linear_solver(const std::string& float_type, + int N, + const std::string& Xm, + const std::string& Jm, + const std::string& Fm); void visit_if_statement(const ast::IfStatement& node) override; void visit_indexed_name(const ast::IndexedName& node) override; void visit_integer(const ast::Integer& node) override; diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index e627a86851..a2369739af 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -1,13 +1,18 @@ # ============================================================================= # Solver sources # ============================================================================= -set(SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) +set(NEWTON_SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) # ============================================================================= -# Copy necessary headers to build directory +# Copy necessary files to build directory # ============================================================================= -file(GLOB NMODL_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/newton/*.h*") -file(COPY ${NMODL_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/newton/) +# Newton +file(GLOB NMODL_NEWTON_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/newton/*.h*") +file(COPY ${NMODL_NEWTON_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/newton/) +# partial_piv_lu +file(GLOB NMODL_PARTIAL_PIV_LU_API_FILES "${CMAKE_CURRENT_SOURCE_DIR}/partial_piv_lu/*") +file(COPY ${NMODL_PARTIAL_PIV_LU_API_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/partial_piv_lu/) +# Eigen file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY_DIR}/include/) # ============================================================================= diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index b8b04d3394..274307dd33 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -15,6 +15,10 @@ * \brief Implementation of Newton method for solving system of non-linear equations */ +#if defined(_OPENACC) && !defined(DISABLE_OPENACC) +#include "partial_piv_lu/partial_piv_lu.h" +#endif + #include namespace nmodl { @@ -69,9 +73,13 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, // we have converged: return iteration count return iter; } +#if defined(_OPENACC) && !defined(DISABLE_OPENACC) + X -= partialPivLu(J, F); +#else // update X use in-place LU decomposition of J with partial pivoting // (suitable for any N, but less efficient than .inverse() for N <=4) X -= Eigen::PartialPivLU>>(J).solve(F); +#endif } // If we fail to converge after max_iter iterations, return -1 return -1; @@ -142,10 +150,13 @@ EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix& // restore X X[i] += dX; } - // update X - // use in-place LU decomposition of J with partial pivoting +#if defined(_OPENACC) && !defined(DISABLE_OPENACC) + X -= partialPivLu(J, F); +#else + // update X use in-place LU decomposition of J with partial pivoting // (suitable for any N, but less efficient than .inverse() for N <=4) X -= Eigen::PartialPivLU>>(J).solve(F); +#endif } // If we fail to converge after max_iter iterations, return -1 return -1; @@ -171,6 +182,8 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, if (error < eps) { return iter; } + // The inverse can be called from within OpenACC regions without any issue, as opposed to + // Eigen::PartialPivLU. X -= J.inverse() * F; } return -1; diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu new file mode 100644 index 0000000000..27dd634cd3 --- /dev/null +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu @@ -0,0 +1,36 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "partial_piv_lu/partial_piv_lu.h" + +template +EIGEN_DEVICE_FUNC +VecType partialPivLu(const MatType& A, const VecType& b) +{ + return A.partialPivLu().solve(b); +} + +// Explicit Template Instantiation +template EIGEN_DEVICE_FUNC VecType<1> partialPivLu<1>(const MatType<1>& A, const VecType<1>& b); +template EIGEN_DEVICE_FUNC VecType<2> partialPivLu<2>(const MatType<2>& A, const VecType<2>& b); +template EIGEN_DEVICE_FUNC VecType<3> partialPivLu<3>(const MatType<3>& A, const VecType<3>& b); +template EIGEN_DEVICE_FUNC VecType<4> partialPivLu<4>(const MatType<4>& A, const VecType<4>& b); +template EIGEN_DEVICE_FUNC VecType<5> partialPivLu<5>(const MatType<5>& A, const VecType<5>& b); +template EIGEN_DEVICE_FUNC VecType<6> partialPivLu<6>(const MatType<6>& A, const VecType<6>& b); +template EIGEN_DEVICE_FUNC VecType<7> partialPivLu<7>(const MatType<7>& A, const VecType<7>& b); +template EIGEN_DEVICE_FUNC VecType<8> partialPivLu<8>(const MatType<8>& A, const VecType<8>& b); +template EIGEN_DEVICE_FUNC VecType<9> partialPivLu<9>(const MatType<9>& A, const VecType<9>& b); +template EIGEN_DEVICE_FUNC VecType<10> partialPivLu<10>(const MatType<10>& A, const VecType<10>& b); +template EIGEN_DEVICE_FUNC VecType<11> partialPivLu<11>(const MatType<11>& A, const VecType<11>& b); +template EIGEN_DEVICE_FUNC VecType<12> partialPivLu<12>(const MatType<12>& A, const VecType<12>& b); +template EIGEN_DEVICE_FUNC VecType<13> partialPivLu<13>(const MatType<13>& A, const VecType<13>& b); +template EIGEN_DEVICE_FUNC VecType<14> partialPivLu<14>(const MatType<14>& A, const VecType<14>& b); +template EIGEN_DEVICE_FUNC VecType<15> partialPivLu<15>(const MatType<15>& A, const VecType<15>& b); +template EIGEN_DEVICE_FUNC VecType<16> partialPivLu<16>(const MatType<16>& A, const VecType<16>& b); + +// Currently there is an issue in Eigen (GPU-branch) for matrices 17x17 and above. +// ToDo: Check in a future release if this issue is resolved! diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h new file mode 100644 index 0000000000..6454056c67 --- /dev/null +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h @@ -0,0 +1,28 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +#include "Eigen/Dense" +#include "Eigen/LU" + +template +using MatType = Eigen::Matrix; + +template +using VecType = Eigen::Matrix; + +// Eigen-3.5+ provides better GPU support. However, some functions cannot be called directly +// from within an OpenACC region. Therefore, we need to wrap them in a special API (decorate +// them with __device__ & acc routine tokens), which allows us to eventually call them from OpenACC. +// Calling these functions from CUDA kernels presents no issue ... + +#if defined(_OPENACC) && !defined(DISABLE_OPENACC) +#pragma acc routine seq +#endif +template +VecType partialPivLu(const MatType&, const VecType&); diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index bd16ae2fc8..fc17d9e386 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -53,8 +53,8 @@ add_executable( visitor/verbatim.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) -add_executable(testnewton newton/newton.cpp ${SOLVER_SOURCE_FILES}) -add_executable(testfast_math fast_math/fast_math.cpp ${SOLVER_SOURCE_FILES}) +add_executable(testnewton newton/newton.cpp ${NEWTON_SOLVER_SOURCE_FILES}) +add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) add_executable(testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp From 9635571580cf58e28e7d495e0acf7a0b5ddcf155 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Mon, 6 Sep 2021 19:06:34 +0200 Subject: [PATCH 384/871] Fix race condition issue with STEADYSTATE code generation on GPU backends (BlueBrain/nmodl#730) * update of dt was done inside parallel/SIMD for loop * this leads to race condition and random failures on GPU * handle update of dt in code generation backend: pull dt update outside loop. * CUDA backend doesn't handle these updates yet. This should be handled in separate PR. fixes BlueBrain/nmodl#729 NMODL Repo SHA: BlueBrain/nmodl@5598d7cafe81512b22d627eda5f99a89c80ac217 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 5 +++ src/nmodl/codegen/codegen_acc_visitor.hpp | 3 ++ src/nmodl/codegen/codegen_c_visitor.cpp | 22 +++++++++++++ src/nmodl/codegen/codegen_c_visitor.hpp | 5 +++ src/nmodl/codegen/codegen_cuda_visitor.cpp | 6 ++++ src/nmodl/codegen/codegen_cuda_visitor.hpp | 2 ++ src/nmodl/codegen/codegen_helper_visitor.cpp | 6 ++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 4 +++ src/nmodl/language/code_generator.cmake | 1 + src/nmodl/language/codegen.yaml | 10 ++++++ src/nmodl/visitors/steadystate_visitor.cpp | 32 ++++++------------- .../transpiler/unit/visitor/steadystate.cpp | 12 ------- 13 files changed, 74 insertions(+), 35 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 431aa607d2..722b037c05 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -297,5 +297,10 @@ void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { printer->add_line("#pragma acc update device(nsb->_cnt) if (nt->compute_gpu)"); } +void CodegenAccVisitor::print_dt_update_to_device() const { + printer->add_line("#pragma acc update device({}) if (nt->compute_gpu)"_format( + get_variable_name(naming::NTHREAD_DT_VARIABLE))); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 4a2b74345b..5edeaa2a6c 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -100,6 +100,9 @@ class CodegenAccVisitor: public CodegenCVisitor { // update NetSendBuffer_t count from host to device virtual void print_net_send_buf_count_update_to_device() const override; + // update dt from host to device + virtual void print_dt_update_to_device() const override; + // synchronise/wait on stream specific to NrnThread virtual void print_device_stream_wait() const override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index ad486d3aa6..f6d2caea82 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -318,6 +318,9 @@ void CodegenCVisitor::visit_verbatim(const Verbatim& node) { } } +void CodegenCVisitor::visit_update_dt(const ast::UpdateDt& node) { + // dt change statement should be pulled outside already +} /****************************************************************************************/ /* Common helper routines */ @@ -1062,6 +1065,10 @@ void CodegenCVisitor::print_net_send_buf_count_update_to_device() const { // backend specific, do nothing } +void CodegenCVisitor::print_dt_update_to_device() const { + // backend specific, do nothing +} + void CodegenCVisitor::print_device_stream_wait() const { // backend specific, do nothing } @@ -3434,6 +3441,14 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { printer->start_block("if (_nrn_skip_initmodel == 0)"); } + if (!info.changed_dt.empty()) { + printer->add_line( + "double _save_prev_dt = {};"_format(get_variable_name(naming::NTHREAD_DT_VARIABLE))); + printer->add_line( + "{} = {};"_format(get_variable_name(naming::NTHREAD_DT_VARIABLE), info.changed_dt)); + print_dt_update_to_device(); + } + print_channel_iteration_tiling_block_begin(BlockType::Initial); print_channel_iteration_block_begin(BlockType::Initial); @@ -3445,6 +3460,13 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_channel_iteration_block_end(); print_shadow_reduction_statements(); print_channel_iteration_tiling_block_end(); + + if (!info.changed_dt.empty()) { + printer->add_line( + "{} = _save_prev_dt;"_format(get_variable_name(naming::NTHREAD_DT_VARIABLE))); + print_dt_update_to_device(); + } + printer->end_block(1); if (info.derivimplicit_used()) { diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index ce98ff0879..775eb3ee4f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1110,6 +1110,10 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_net_send_buf_count_update_to_device() const; + /** + * Print the code to update dt from host to device + */ + virtual void print_dt_update_to_device() const; /** * Print the code to synchronise/wait on stream specific to NrnThread @@ -1923,6 +1927,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void visit_while_statement(const ast::WhileStatement& node) override; void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; + void visit_update_dt(const ast::UpdateDt& node) override; }; diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index dd44ba1ff3..199d9bdd66 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -20,6 +20,12 @@ using symtab::syminfo::NmodlType; /* Routines must be overloaded in backend */ /****************************************************************************************/ +// TODO: Update of dt and other variables on device via CUDA backend is not handled +// yet. We need to review device code generation in OpenACC backend. +void CodegenCudaVisitor::print_dt_update_to_device() const { + throw std::runtime_error("CUDA backend doesn't handled dt update on GPU"); +} + /** * As initial block is/can be executed on c/cpu backend, gpu/cuda * backend can mark the parameter as constant even if they have diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index 2578747b89..d1ec9d8974 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -95,6 +95,8 @@ class CodegenCudaVisitor: public CodegenCVisitor { /// print wrapper function that calls cuda kernel void print_wrapper_routine(std::string wrapper_function, BlockType type); + // update dt from host to device + void print_dt_update_to_device() const override; /// wrapper/caller routines for nrn_state and nrn_cur void codegen_wrapper_routines(); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 83e441105d..1bc11d85f4 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -557,11 +557,13 @@ void CodegenHelperVisitor::visit_function_block(const ast::FunctionBlock& node) void CodegenHelperVisitor::visit_eigen_newton_solver_block( const ast::EigenNewtonSolverBlock& node) { info.eigen_newton_solver_exist = true; + node.visit_children(*this); } void CodegenHelperVisitor::visit_eigen_linear_solver_block( const ast::EigenLinearSolverBlock& node) { info.eigen_linear_solver_exist = true; + node.visit_children(*this); } void CodegenHelperVisitor::visit_function_call(const FunctionCall& node) { @@ -704,5 +706,9 @@ void CodegenHelperVisitor::visit_partial_block(const ast::PartialBlock& node) { info.vectorize = false; } +void CodegenHelperVisitor::visit_update_dt(const ast::UpdateDt& node) { + info.changed_dt = node.get_value()->eval(); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index d2224a7bd0..9ad6bd69d1 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -110,6 +110,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_non_linear_block(const ast::NonLinearBlock& node) override; void visit_discrete_block(const ast::DiscreteBlock& node) override; void visit_partial_block(const ast::PartialBlock& node) override; + void visit_update_dt(const ast::UpdateDt& node) override; }; /** @} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index f64cc817d4..bb84127486 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -311,6 +311,10 @@ struct CodegenInfo { /// note that if tqitem doesn't exist then the default value should be 0 int tqitem_index = 0; + // updated dt to use with steadystate solver (in initial block) + // empty string means no change in dt + std::string changed_dt; + /// global variables std::vector global_variables; diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 400b969a23..08091bf9c4 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -181,6 +181,7 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/unit_block.hpp ${PROJECT_BINARY_DIR}/src/ast/unit_def.hpp ${PROJECT_BINARY_DIR}/src/ast/unit_state.hpp + ${PROJECT_BINARY_DIR}/src/ast/update_dt.hpp ${PROJECT_BINARY_DIR}/src/ast/useion.hpp ${PROJECT_BINARY_DIR}/src/ast/valence.hpp ${PROJECT_BINARY_DIR}/src/ast/var_name.hpp diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 63762a9be0..a3156b4f3c 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -111,3 +111,13 @@ brief: "Block to be solved (callback node or solution node itself)" type: Expression - Statement: + brief: "Statement base class" + children: + - UpdateDt: + nmodl: "dt" + members: + - value: + brief: "Value of new timestep" + type: Double + prefix: {value: " = "} + brief: "Statement to indicate a change in timestep in a given block" \ No newline at end of file diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 7721a09a20..9d8f859954 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -53,38 +53,24 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo logger->debug("SteadystateVisitor :: -> adding new DERIVATIVE block: {}", ss_block->get_node_name()); - // create statements to alter value of dt within DERIVATIVE block - // TODO: make sure dt_tmp_var_name variable name does not clash - std::string dt_tmp_var_name = std::string(codegen::naming::NTHREAD_DT_VARIABLE) + - "_saved_value"; - std::string dt_save = dt_tmp_var_name + " = " + codegen::naming::NTHREAD_DT_VARIABLE; - std::string dt_assign = std::string(codegen::naming::NTHREAD_DT_VARIABLE) + " = "; - std::string dt_restore = dt_assign + dt_tmp_var_name; + std::string new_dt; if (steadystate_method == codegen::naming::SPARSE_METHOD) { - dt_assign += "{:.16g}"_format(STEADYSTATE_SPARSE_DT); + new_dt = "{:.16g}"_format(STEADYSTATE_SPARSE_DT); } else if (steadystate_method == codegen::naming::DERIVIMPLICIT_METHOD) { - dt_assign += "{:.16g}"_format(STEADYSTATE_DERIVIMPLICIT_DT); + new_dt += "{:.16g}"_format(STEADYSTATE_DERIVIMPLICIT_DT); } else { logger->warn("SteadystateVisitor :: solve method {} not supported for STEADYSTATE", steadystate_method); return nullptr; } + auto statement_block = ss_block->get_statement_block(); - // declare tmp variable to save dt value (this will go into the LOCAL statement at the top - // of the statement block) - add_local_variable(*statement_block.get(), dt_tmp_var_name); - // get a copy of existing statements auto statements = statement_block->get_statements(); - // insert dt_save and dt_assign statements just below first LOCAL statement - auto insertion_point = statements.begin(); - while ((*insertion_point)->is_local_list_statement()) { - ++insertion_point; - } - insertion_point = statements.insert(insertion_point, create_statement(dt_save)); - ++insertion_point; - statements.insert(insertion_point, create_statement(dt_assign)); - // insert dt_restore statement at the end - statements.push_back(create_statement(dt_restore)); + + // add statement for changing the timestep + auto update_dt_statement = std::make_shared(new ast::Double(new_dt)); + statements.insert(statements.begin(), update_dt_statement); + // replace old set of statements in AST with new one statement_block->set_statements(std::move(statements)); diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index f6374a0147..9eaec01221 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -86,11 +86,8 @@ SCENARIO("Solving ODEs with STEADYSTATE solve method", "[visitor][steadystate]") })"; std::string expected_text2 = R"( DERIVATIVE states_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt dt = 1000000000 m' = m+h - dt = dt_saved_value })"; THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { auto result = run_steadystate_visitor(nmodl_text); @@ -115,11 +112,8 @@ SCENARIO("Solving ODEs with STEADYSTATE solve method", "[visitor][steadystate]") })"; std::string expected_text2 = R"( DERIVATIVE states_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt dt = 1e-09 m' = m+h - dt = dt_saved_value })"; THEN("Construct DERIVATIVE block with steadystate solution & update SOLVE statement") { auto result = run_steadystate_visitor(nmodl_text); @@ -160,21 +154,15 @@ SCENARIO("Solving ODEs with STEADYSTATE solve method", "[visitor][steadystate]") })"; std::string expected_text3 = R"( DERIVATIVE states0_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt dt = 1e-09 Z'[0] = Z[1]-Z[2] Z'[1] = Z[0]+2*Z[2] Z'[2] = Z[0]*Z[0]-3.10 - dt = dt_saved_value })"; std::string expected_text4 = R"( DERIVATIVE states1_steadystate { - LOCAL dt_saved_value - dt_saved_value = dt dt = 1000000000 x' = x+c - dt = dt_saved_value })"; THEN("Construct DERIVATIVE blocks with steadystate solution & update SOLVE statements") { auto result = run_steadystate_visitor(nmodl_text); From 5dde7d4c2e957b27458f8546f9af4a7bd7491a6a Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 16 Sep 2021 10:44:11 +0200 Subject: [PATCH 385/871] Fix NMODL compilation when clang-format was found. (BlueBrain/nmodl#733) Without this fix then building NMODL-as-a-submodule-of-CoreNEURON-as-a-submodule-of-NEURON would not work if code formatting was enabled in NEURON following the recipe of https://github.com/neuronsimulator/nrn/pull/1460. This was apparently because NMODL was formatting NMODL generated code using a clang-format configuration file from NEURON, which uses C++03 compatibility. This breaks NMODL code compilation by adding a space before `_format` in `"..{}.."_format("bar")`, which is not valid. Also change the handling of the --clang-format-opts option so that the ${NMODL_ClangFormat_OPTIONS} option should actually work. Previously the CMake code would generate something like: --clang-format-opts A B --clang-format-opts="--style=file" where the `A B` part would then be ignored. Update hpc-coding-conventions submodule commit. NMODL Repo SHA: BlueBrain/nmodl@61928b15207a033688ce20a67b0c0470079136dc --- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/language/CMakeLists.txt | 17 ++++++++++------- src/nmodl/language/code_generator.py | 4 +++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index fae561508d..5dd9d83089 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit fae561508d4a6b02d1848384707bcc3dc81d826d +Subproject commit 5dd9d8308937f9e9be7093cd579e4301dc43131c diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 86fd69c105..06522f522f 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -3,18 +3,21 @@ # ============================================================================= set_source_files_properties(${NMODL_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) -if(ClangFormat_FOUND AND (NOT ClangFormat_VERSION_MAJOR LESS 4)) +# clang-format is handled by the HPC coding conventions scripts, which also handle generating the +# .clang-format configuration file. It's important that we only try to format code if formatting was +# enabled and NMODL's .clang-format exists, otherwise clang-format will search too far up the +# directory tree and find the wrong configuration file. This can break compilation. +if(NMODL_CLANG_FORMAT OR NMODL_FORMATTING) set(CODE_GENERATOR_OPTS -v --clang-format=${ClangFormat_EXECUTABLE}) - if(nmodl_ClangFormat_OPTIONS) - set(CODE_GENERATOR_OPTS ${CODE_GENERATOR_OPTS} --clang-format-opts ${nmodl_ClangFormat_OPTIONS}) - endif(nmodl_ClangFormat_OPTIONS) + foreach(clang_format_opt ${NMODL_ClangFormat_OPTIONS} --style=file) + list(APPEND CODE_GENERATOR_OPTS --clang-format-opts=${clang_format_opt}) + endforeach() endif() add_custom_command( OUTPUT ${NMODL_GENERATED_SOURCES} - COMMAND - ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/code_generator.py ${CODE_GENERATOR_OPTS} - --base-dir ${PROJECT_BINARY_DIR}/src --clang-format-opts="--style=file" + COMMAND ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/code_generator.py + ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${CODE_GENERATOR_PY_FILES} DEPENDS ${CODE_GENERATOR_YAML_FILES} diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index d02c66f81e..e03f8ce637 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -335,7 +335,9 @@ def parse_args(args=None): """ parser = argparse.ArgumentParser() parser.add_argument("--clang-format", help="Path to clang-format executable") - parser.add_argument("--clang-format-opts", help="clang-format options", nargs="+") + parser.add_argument( + "--clang-format-opts", help="clang-format options", action="append" + ) parser.add_argument("--base-dir", help="output root directory") parser.add_argument( "-v", "--verbosity", action="count", default=0, help="increase output verbosity" From 177310806ea29d07e48c8d79432d7b7fef33c07d Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 16 Sep 2021 18:09:27 +0200 Subject: [PATCH 386/871] Run formatting checks in GitHub Actions CI. (BlueBrain/nmodl#735) Also use ninja and ccache. Add better failure of the version number detection from git history in shallow clones. Slim down apt/brew install commands. Test on newer Ubuntu and macOS images in addition. NMODL Repo SHA: BlueBrain/nmodl@8fe9c6c84bf4f9f59efaefa09b0834683755a5ac --- cmake/nmodl/GitRevision.cmake | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/cmake/nmodl/GitRevision.cmake b/cmake/nmodl/GitRevision.cmake index 2455d47094..579c57ef48 100644 --- a/cmake/nmodl/GitRevision.cmake +++ b/cmake/nmodl/GitRevision.cmake @@ -10,14 +10,22 @@ if(GIT_FOUND) COMMAND ${GIT_EXECUTABLE} log -1 --format=%h WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} OUTPUT_VARIABLE NMODL_GIT_REVISION_SHA1 + RESULT_VARIABLE NMODL_GIT_STATUS ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT NMODL_GIT_STATUS EQUAL 0) + set(NMODL_GIT_REVISION_SHA1 "git-error") + endif() # get last commit date execute_process( COMMAND ${GIT_EXECUTABLE} show -s --format=%ci WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} OUTPUT_VARIABLE NMODL_GIT_REVISION_DATE + RESULT_VARIABLE NMODL_GIT_STATUS ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT NMODL_GIT_STATUS EQUAL 0) + set(NMODL_GIT_REVISION_SHA1 "git-error") + endif() # remove extra double quotes string(REGEX REPLACE "\"" "" NMODL_GIT_REVISION_DATE "${NMODL_GIT_REVISION_DATE}") @@ -28,11 +36,14 @@ if(GIT_FOUND) COMMAND ${GIT_EXECUTABLE} describe --abbrev=0 --tags WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR} OUTPUT_VARIABLE NMODL_GIT_LAST_TAG + RESULT_VARIABLE NMODL_GIT_STATUS ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - + if(NOT NMODL_GIT_STATUS EQUAL 0) + # Must be a valid version from CMake's perspective. + set(NMODL_GIT_LAST_TAG "0.0") + endif() else() - set(NMODL_GIT_REVISION "unknown") - set(NMODL_GIT_LAST_TAG "unknown") - + # Must be a valid version from CMake's perspective. + set(NMODL_GIT_LAST_TAG "0.0") endif() From ca2d531fbded0a8a3f633f105650e0fc0aeedd7d Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Fri, 17 Sep 2021 13:35:53 +0200 Subject: [PATCH 387/871] Require C++14 (BlueBrain/nmodl#739) * Drop old GCC workaround. * Minimum supported CMake is v3.15. * Fix warning suppression with NVHPC compiler. NMODL Repo SHA: BlueBrain/nmodl@1845c2746faaedaad533de069417bdce084a1bac --- INSTALL.md | 4 +-- cmake/nmodl/CMakeLists.txt | 8 +++-- cmake/nmodl/Catch.cmake | 25 +++------------ cmake/nmodl/ClangTidyHelper.cmake | 38 +++++++++++------------ cmake/nmodl/CompilerHelper.cmake | 9 ------ src/nmodl/language/nodes.py | 15 +++------ src/nmodl/language/templates/ast/ast.hpp | 2 -- src/nmodl/utils/common_utils.hpp | 26 ---------------- test/nmodl/transpiler/unit/CMakeLists.txt | 15 +++------ 9 files changed, 39 insertions(+), 103 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 335651c86c..8ce1c43d5f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -17,11 +17,11 @@ cd nmodl ## Prerequisites -To build the project from source, a modern C++ compiler with c++11 support is necessary. Make sure you have following packages available: +To build the project from source, a modern C++ compiler with C++14 support is necessary. Make sure you have following packages available: - flex (>=2.6) - bison (>=3.0) -- CMake (>=3.3) +- CMake (>=3.15) - Python (>=3.6) - Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index d73bcdec3f..9f49c0b49b 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -12,7 +12,7 @@ project(NMODL LANGUAGES CXX) # ============================================================================= # CMake common project settings # ============================================================================= -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) @@ -88,7 +88,11 @@ add_external_project(eigen OFF) add_subdirectory(${THIRD_PARTY_DIRECTORY}/fmt EXCLUDE_FROM_ALL) set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) - +if(NMODL_PGI_COMPILER) + # "format.cc", warning #128-D: loop is not reachable + set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ext/fmt/src/format.cc + PROPERTIES COMPILE_FLAGS "--diag_suppress=128") +endif() include_directories( SYSTEM ${THIRD_PARTY_DIRECTORY} ${THIRD_PARTY_DIRECTORY}/catch/include ${THIRD_PARTY_DIRECTORY}/fmt/include ${THIRD_PARTY_DIRECTORY}/spdlog/include diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake index c64bf9a395..36ce2786b8 100644 --- a/cmake/nmodl/Catch.cmake +++ b/cmake/nmodl/Catch.cmake @@ -133,26 +133,11 @@ function(catch_discover_tests TARGET) "if(EXISTS \"${ctest_tests_file}\")\n" " include(\"${ctest_tests_file}\")\n" "else()\n" " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" "endif()\n") - if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0") - # Add discovered tests to directory TEST_INCLUDE_FILES - set_property( - DIRECTORY - APPEND - PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") - else() - # Add discovered tests as directory TEST_INCLUDE_FILE if possible - get_property( - test_include_file_set - DIRECTORY - PROPERTY TEST_INCLUDE_FILE - SET) - if(NOT ${test_include_file_set}) - set_property(DIRECTORY PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}") - else() - message(FATAL_ERROR "Cannot set more than one TEST_INCLUDE_FILE") - endif() - endif() - + # Add discovered tests to directory TEST_INCLUDE_FILES + set_property( + DIRECTORY + APPEND + PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") endfunction() # diff --git a/cmake/nmodl/ClangTidyHelper.cmake b/cmake/nmodl/ClangTidyHelper.cmake index 91c7389f30..1b1974a1e5 100644 --- a/cmake/nmodl/ClangTidyHelper.cmake +++ b/cmake/nmodl/ClangTidyHelper.cmake @@ -1,22 +1,20 @@ -if(CMAKE_VERSION VERSION_GREATER "3.5") - set(ENABLE_CLANG_TIDY - OFF - CACHE BOOL "Add clang-tidy automatically to builds") - if(ENABLE_CLANG_TIDY) - find_program(CLANG_TIDY_EXE NAMES "clang-tidy") - if(CLANG_TIDY_EXE) - message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") - set(CLANG_TIDY_CHECKS - "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" - ) - set(CMAKE_CXX_CLANG_TIDY - "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" - CACHE STRING "" FORCE) - else() - message(AUTHOR_WARNING "clang-tidy not found!") - set(CMAKE_CXX_CLANG_TIDY - "" - CACHE STRING "" FORCE) # delete it - endif() +set(ENABLE_CLANG_TIDY + OFF + CACHE BOOL "Add clang-tidy automatically to builds") +if(ENABLE_CLANG_TIDY) + find_program(CLANG_TIDY_EXE NAMES "clang-tidy") + if(CLANG_TIDY_EXE) + message(STATUS "clang-tidy found: ${CLANG_TIDY_EXE}") + set(CLANG_TIDY_CHECKS + "-*,modernize-*,readability-*,performance-*,cppcoreguidelines-*,clang-analyzer-core*,google-*" + ) + set(CMAKE_CXX_CLANG_TIDY + "${CLANG_TIDY_EXE};-checks=${CLANG_TIDY_CHECKS};-fix;-header-filter='${CMAKE_SOURCE_DIR}/*'" + CACHE STRING "" FORCE) + else() + message(AUTHOR_WARNING "clang-tidy not found!") + set(CMAKE_CXX_CLANG_TIDY + "" + CACHE STRING "" FORCE) # delete it endif() endif() diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 8f2efccb1e..0e8960cfd8 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -1,16 +1,7 @@ -# minimal check for c++11 compliant gnu compiler -if("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") - execute_process(COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) - if(NOT (GCC_VERSION VERSION_GREATER 4.8.2 OR GCC_VERSION VERSION_EQUAL 4.8.2)) - message(FATAL_ERROR "${PROJECT_NAME} requires g++ >= 4.8.2 (for C++11 support)") - endif() -endif() - if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") set(NMODL_PGI_COMPILER TRUE) # CMake adds standard complaint PGI flag "-A" which breaks compilation of of spdlog and fmt - set(CMAKE_CXX11_STANDARD_COMPILE_OPTION --c++11) set(CMAKE_CXX14_STANDARD_COMPILE_OPTION --c++14) # ~~~ diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index a539b55647..489db03e19 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -344,16 +344,13 @@ def get_add_methods_definition(self, parent): * \\brief Erase member to {self.varname} */ {self.class_name}Vector::const_iterator {parent.class_name}::erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first) {{ - auto first_it = const_iter_cast({self.varname}, first); - return {self.varname}.erase(first_it); + return {self.varname}.erase(first); }} /** * \\brief Erase members to {self.varname} */ {self.class_name}Vector::const_iterator {parent.class_name}::erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last) {{ - auto first_it = const_iter_cast({self.varname}, first); - auto last_it = const_iter_cast({self.varname}, last); - return {self.varname}.erase(first_it, last_it); + return {self.varname}.erase(first, last); }} /** * \\brief Erase non-consecutive members to {self.varname} @@ -386,8 +383,7 @@ def get_add_methods_definition(self, parent): */ {self.class_name}Vector::const_iterator {parent.class_name}::insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n) {{ {set_parent} - auto pos_it = const_iter_cast({self.varname}, position); - return {self.varname}.insert(pos_it, n); + return {self.varname}.insert(position, n); }} /** @@ -435,10 +431,7 @@ def get_add_methods_inline_definition(self, parent): //set parents {set_parent} }} - auto pos_it = const_iter_cast({self.varname}, position); - auto first_it = const_iter_cast(to, first); - auto last_it = const_iter_cast(to, last); - {self.varname}.insert(pos_it, first_it, last_it); + {self.varname}.insert(position, first, last); }} """ s = textwrap.dedent(method) diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index ee28092379..4820773421 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -29,8 +29,6 @@ #include "utils/common_utils.hpp" #include "visitors/visitor.hpp" -using nmodl::utils::const_iter_cast; - namespace nmodl { namespace ast { diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index d8341a8508..b418e85fb9 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -53,32 +53,6 @@ T remove_extension(T const& filename) { return p > 0 && p != T::npos ? filename.substr(0, p) : filename; } -/** - * Return non-const iterator corresponding to the const_iterator in a vector - * - * Some old compilers like GCC v4.8.2 has C++11 support but missing erase and insert - * with const_iterator implementation. This is a workaround to handle build issues with - * such compilers especially on manylnux1 platform. - * - * See bug report : https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57158 - * - * \todo Remove this after move to manylinux2010 platform. - */ -#if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40900 -template -typename std::vector::iterator const_iter_cast(std::vector& v, - typename std::vector::const_iterator iter) { - return v.begin() + (iter - v.cbegin()); -} -#else -template -typename std::vector::const_iterator const_iter_cast( - const std::vector& /*v*/, - typename std::vector::const_iterator iter) { - return iter; -} -#endif - #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) /// The character used by the operating system to separate pathname components static constexpr char pathsep{'\\'}; diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index fc17d9e386..cf9cc16b51 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -121,18 +121,11 @@ foreach( testunitlexer testunitparser) - if(${CMAKE_VERSION} VERSION_GREATER "3.10") - if(${test_name} STREQUAL "testvisitor") - catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT - "${testvisitor_env}") - else() - catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/") - endif() + if(${test_name} STREQUAL "testvisitor") + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT + "${testvisitor_env}") else() - add_test(NAME ${test_name} COMMAND ${test_name}) - if(${test_name} STREQUAL "testvisitor") - set_tests_properties(${test_name} PROPERTIES ENVIRONMENT "${testvisitor_env}") - endif() + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/") endif() endforeach() From ee05b0e0c9f04ce79dbb82276a0dca79ff24688f Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 20 Sep 2021 18:02:40 +0200 Subject: [PATCH 388/871] Added CLI option for transforming top LEVEL local variables to RANGE (BlueBrain/nmodl#738) * By default the option is disabled so that NMODL and nocmodl/mod2c generate code with the same instance variables instead of LOCAL variables being transformed to ASSIGNED only in NMODL case Co-authored-by: Olli Lupton NMODL Repo SHA: BlueBrain/nmodl@c094f31eba8b3b9a54e1b659389a3a6bd5947312 --- src/nmodl/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 60e933f052..c1d74468ab 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -109,6 +109,9 @@ int main(int argc, const char* argv[]) { /// true if global variables to be converted to range bool nmodl_global_to_range(false); + /// true if top level local variables to be converted to range + bool nmodl_local_to_range(false); + /// true if localize variables even if verbatim block is used bool localize_verbatim(false); @@ -218,6 +221,9 @@ int main(int argc, const char* argv[]) { passes_opt->add_flag("--global-to-range", nmodl_global_to_range, "Convert GLOBAL variables to RANGE ({})"_format(nmodl_global_to_range))->ignore_case(); + passes_opt->add_flag("--local-to-range", + nmodl_local_to_range, + "Convert top level LOCAL variables to RANGE ({})"_format(nmodl_local_to_range))->ignore_case(); passes_opt->add_flag("--localize-verbatim", localize_verbatim, "Convert RANGE variables to LOCAL even if verbatim block exist ({})"_format(localize_verbatim))->ignore_case(); @@ -345,7 +351,7 @@ int main(int argc, const char* argv[]) { } /// LOCAL to ASSIGNED visitor - { + if (nmodl_local_to_range) { logger->info("Running LOCAL to ASSIGNED visitor"); PerfVisitor().visit_program(*ast); LocalToAssignedVisitor().visit_program(*ast); From 9fa347183b7fe280bfb52f6d5955c941e05ab358 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 29 Sep 2021 18:16:30 +0200 Subject: [PATCH 389/871] Bug fix for net_init code generation on GPUs with OpenACC backend (BlueBrain/nmodl#741) - Bug fix for net_init code generation on GPUs with OpenACC backend * net_init was being executed on CPU but it was dereferencing gpu pointers on CPU via instance structure * Currently there is no support for getting device function pointers * As net_init is called from cpu side, a simple approach is to launch serial kernel for each invocation of net_init. * This is bit inefficient as large number of kernels could be launched. But, as this happens only during initialialization, we can use this fix for time being. * Note that mod2c "get away" by launching kernels on CPU because the net_init kernel is just handling spike events and those are handled on CPU anyway. - Don't update net send buffer on host if it's null (BlueBrain/nmodl#742) * Fix for fi.mod and ampanmda.mod from olfactory * Remove call to update_net_send_buffer_on_host * Add function print_net_send_buf_update_to_host() * Remove extra space after if - Improve ccache handling in Github CI * disable ccache direct mode in 4.4 and 4.4.1. fixes BlueBrain/nmodl#740 Co-authored-by: Ioannis Magkanaris Co-authored-by: Olli Lupton NMODL Repo SHA: BlueBrain/nmodl@b0fe32791c3ac3949f14ad70eeeaf32cf9bc431e --- src/nmodl/codegen/codegen_acc_visitor.cpp | 34 ++++++++++++++++++++++- src/nmodl/codegen/codegen_acc_visitor.hpp | 11 ++++++++ src/nmodl/codegen/codegen_c_visitor.cpp | 28 +++++++++++++++++-- src/nmodl/codegen/codegen_c_visitor.hpp | 17 ++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 722b037c05..e85175ee8b 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -172,6 +172,29 @@ void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { } } +/** + * `INITIAL` block from `NET_RECEIVE` generates `net_init` function. The `net_init` + * function pointer is registered with the coreneuron and called from the CPU. + * As the data is on GPU, we need to launch `net_init` on the GPU. + * + * \todo: With the current code structure for NMODL and MOD2C, we use `serial` + * construct to launch serial kernels. This is during initialization + * but still inefficient. This should be improved when we drop MOD2C. + */ +void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_begin() { + if (!info.artificial_cell) { + printer->add_line("#pragma acc serial present(inst, indexes, weights) if(nt->compute_gpu)"); + printer->add_line("{"); + printer->increase_indent(); + } +} + +void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_end() { + if (!info.artificial_cell) { + printer->add_line("}"); + printer->decrease_indent(); + } +} void CodegenAccVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); @@ -288,15 +311,24 @@ void CodegenAccVisitor::print_device_stream_wait() const { void CodegenAccVisitor::print_net_send_buf_count_update_to_host() const { - print_device_stream_wait(); printer->add_line("#pragma acc update self(nsb->_cnt) if(nt->compute_gpu)"); } +void CodegenAccVisitor::print_net_send_buf_update_to_host() const { + print_device_stream_wait(); + printer->start_block("if (nsb)"); + print_net_send_buf_count_update_to_host(); + printer->add_line("update_net_send_buffer_on_host(nt, nsb);"); + printer->end_block(1); +} + + void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { printer->add_line("#pragma acc update device(nsb->_cnt) if (nt->compute_gpu)"); } + void CodegenAccVisitor::print_dt_update_to_device() const { printer->add_line("#pragma acc update device({}) if (nt->compute_gpu)"_format( get_variable_name(naming::NTHREAD_DT_VARIABLE))); diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 5edeaa2a6c..7073a89f83 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -61,6 +61,14 @@ class CodegenAccVisitor: public CodegenCVisitor { void print_kernel_data_present_annotation_block_end() override; + /// start of annotation "acc kernels" for net_init kernel + void print_net_init_acc_serial_annotation_block_begin() override; + + + /// end of annotation "acc kernels" for net_init kernel + void print_net_init_acc_serial_annotation_block_end() override; + + /// update to matrix elements with/without shadow vectors void print_nrn_cur_matrix_shadow_update() override; @@ -97,6 +105,9 @@ class CodegenAccVisitor: public CodegenCVisitor { // update NetSendBuffer_t count from device to host void print_net_send_buf_count_update_to_host() const override; + // update NetSendBuffer_t from device to host + void print_net_send_buf_update_to_host() const override; + // update NetSendBuffer_t count from host to device virtual void print_net_send_buf_count_update_to_device() const override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index f6d2caea82..81a14fad77 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1061,6 +1061,10 @@ void CodegenCVisitor::print_net_send_buf_count_update_to_host() const { // backend specific, do nothing } +void CodegenCVisitor::print_net_send_buf_update_to_host() const { + // backend specific, do nothing +} + void CodegenCVisitor::print_net_send_buf_count_update_to_device() const { // backend specific, do nothing } @@ -1096,6 +1100,13 @@ void CodegenCVisitor::print_kernel_data_present_annotation_block_end() { // backend specific, do nothing } +void CodegenCVisitor::print_net_init_acc_serial_annotation_block_begin() { + // backend specific, do nothing +} + +void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { + // backend specific, do nothing +} /** * \details Depending programming model and compiler, we print compiler hint @@ -3655,6 +3666,10 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need printer->add_line("NrnThread* nt = nrn_threads + tid;"); printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); } + if (node.is_initial_block()) { + print_kernel_data_present_annotation_block_begin(); + } + printer->add_line("{}int nodecount = ml->nodecount;"_format(param_type_qualifier())); printer->add_line("{}int pnodecount = ml->_nodecount_padded;"_format(param_type_qualifier())); printer->add_line("double* data = ml->data;"); @@ -3665,6 +3680,10 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); } + if (node.is_initial_block()) { + print_net_init_acc_serial_annotation_block_begin(); + } + // rename variables but need to see if they are actually used auto parameters = info.net_receive_node->get_parameters(); if (!parameters.empty()) { @@ -3817,6 +3836,12 @@ void CodegenCVisitor::print_net_init() { } else { print_net_receive_common_code(*node); print_statement_block(*block, false, false); + if (node->is_initial_block()) { + print_net_init_acc_serial_annotation_block_end(); + print_kernel_data_present_annotation_block_end(); + printer->add_line("auto& nsb = ml->_net_send_buffer;"); + print_net_send_buf_update_to_host(); + } } printer->end_block(1); codegen = false; @@ -3826,8 +3851,7 @@ void CodegenCVisitor::print_net_init() { void CodegenCVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); - print_net_send_buf_count_update_to_host(); - printer->add_line("update_net_send_buffer_on_host(nt, nsb);"); + print_net_send_buf_update_to_host(); printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); printer->add_line(" int type = nsb->_sendtype[i];"); printer->add_line(" int tid = nt->id;"); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 775eb3ee4f..285b3ee5db 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1104,6 +1104,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_net_send_buf_count_update_to_host() const; + /** + * Print the code to update NetSendBuffer_t from device to host + */ + virtual void print_net_send_buf_update_to_host() const; + /** * Print the code to update NetSendBuffer_t count from host to device @@ -1308,6 +1313,18 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_kernel_data_present_annotation_block_end(); + /** + * Print accelerator kernels begin annotation for net_init kernel + */ + virtual void print_net_init_acc_serial_annotation_block_begin(); + + + /** + * Print accelerator kernels end annotation for net_init kernel + */ + virtual void print_net_init_acc_serial_annotation_block_end(); + + /** * Print backend specific channel instance iteration block start * \param type The block type in which we currently are From 74878f6c4bda159c4d7d6f543c51c24c43ecb41e Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Mon, 11 Oct 2021 11:48:09 +0200 Subject: [PATCH 390/871] Ban sympy 1.9 from CI. (BlueBrain/nmodl#748) * Use GITHUB_PATH in CI config * Also require sympy<1.9 in setup.py NMODL Repo SHA: BlueBrain/nmodl@067abc42b55238b1cd517235288fd10f885aeddf --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b15e188fe0..0b54e37cc4 100644 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ def _config_exe(exe_name): install_requirements = [ "PyYAML>=3.13", - "sympy>=1.3", + "sympy>=1.3,<1.9", ] From 6d5088c84db3784088895b8b9cb5ac804c563422 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 11 Oct 2021 16:37:50 +0200 Subject: [PATCH 391/871] Support python >= 3.6 (BlueBrain/nmodl#750) NMODL Repo SHA: BlueBrain/nmodl@dbd1dd1f362867d7bb3fbfbef9bcd66a76066f53 --- CONTRIBUTING.md | 2 +- cmake/nmodl/CMakeLists.txt | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ca3af8d827..873a95847c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -156,7 +156,7 @@ Or using CTest as: ctest -T memcheck ``` -If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.5` and use following cmake option: +If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.15` and use following cmake option: ``` cmake .. -DENABLE_CLANG_TIDY=ON diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9f49c0b49b..0fc8dcf9cd 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -145,7 +145,7 @@ endif() # Find required python packages # ============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp 3.5 REQUIRED) +find_package(PythonInterp 3.6 REQUIRED) nmodl_find_python_module(jinja2 2.9.3 REQUIRED) nmodl_find_python_module(pytest 3.3.0 REQUIRED) nmodl_find_python_module(sympy 1.2 REQUIRED) @@ -189,19 +189,6 @@ if(NOT NMODL_AS_SUBPROJECT) add_subdirectory(test/integration) endif() -# ============================================================================= -# Check newer python for generating AST classes -# ============================================================================= -# ~~~ -# AST classes are generated by python scripts which require Python >= 3.6 -# In case of older python, try to find newer python interpreter -# ~~~ -if(PYTHON_VERSION_MINOR VERSION_LESS 6) - unset(PYTHONINTERP_FOUND CACHE) - unset(PYTHON_EXECUTABLE CACHE) - find_package(PythonInterp 3.6 REQUIRED) -endif() - # ============================================================================= # list of autogenerated files # ============================================================================= From c4833975748419cc2ad63e82637d94612aa5fcd5 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 14 Oct 2021 16:11:14 +0200 Subject: [PATCH 392/871] Enable MPI for having tests of nrn (BlueBrain/nmodl#753) NMODL Repo SHA: BlueBrain/nmodl@00e399e1096dbf92cd71f77c5a11905e559e5a74 --- cmake/nmodl/hpc-coding-conventions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 5dd9d83089..7bca42c14a 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 5dd9d8308937f9e9be7093cd579e4301dc43131c +Subproject commit 7bca42c14a93e2eb2858ad4e90514d629aa3df5b From ea3de3c9f9aa7d5d09c58be5e9389c622ce4fc2e Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Fri, 15 Oct 2021 13:21:46 +0200 Subject: [PATCH 393/871] Add sanitizer builds to CI (BlueBrain/nmodl#751) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now {Address,Leak,UndefinedBehaviour}Sanitizer from LLVM 12 are run in the GitHub Actions CI after the regular CI builds have passed. Also: - Add problem matchers for gcc/clang, asan and ubsan. - Add CMake option for extra compile flags that do not apply to external software built inside the NMODL tree. - Enable + fix some extra compiler warnings. - Remove strict aliasing undefined behaviour. - Fix memory leak (DiffeqSolverExecutor). - Use PYTHONMALLOC=malloc when running tests. - Disable leak detection during the build. - Add LeakSanitizer suppressions for bison and pybind11 related code. Co-authored-by: Omar Awile Co-authored-by: Alexandru Săvulescu NMODL Repo SHA: BlueBrain/nmodl@5d144470583a2b9545a711d0a99cb37d595d9f43 --- cmake/nmodl/CMakeLists.txt | 6 ++- src/nmodl/CMakeLists.txt | 6 +++ src/nmodl/codegen/codegen_ispc_visitor.cpp | 6 +-- src/nmodl/codegen/fast_math.hpp | 45 ++++++++++++++----- src/nmodl/language/templates/ast/ast_decl.hpp | 4 +- src/nmodl/lexer/unit_lexer.hpp | 17 +++---- src/nmodl/parser/diffeq_context.hpp | 8 +--- src/nmodl/pybind/CMakeLists.txt | 13 ++++++ src/nmodl/symtab/symbol.hpp | 4 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 11 +++-- .../transpiler/integration/CMakeLists.txt | 6 +++ test/nmodl/transpiler/unit/CMakeLists.txt | 5 +++ 12 files changed, 91 insertions(+), 40 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 0fc8dcf9cd..ef79c5c719 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2021 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. # See top-level LICENSE file for details. @@ -25,6 +25,10 @@ option(NMODL_ENABLE_LEGACY_UNITS "Use original faraday, R, etc. instead of 2019 if(NMODL_ENABLE_LEGACY_UNITS) add_definitions(-DUSE_LEGACY_UNITS) endif() +set(NMODL_EXTRA_CXX_FLAGS + "" + CACHE STRING "Add extra compile flags for NMODL sources") +separate_arguments(NMODL_EXTRA_CXX_FLAGS) # ============================================================================= # Settings to enable project as submodule diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 7b5e67a66a..29e1e205a2 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -1,3 +1,9 @@ +# ============================================================================= +# Add extra compile flags to NMODL sources +# ============================================================================= +add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) +add_link_options(${NMODL_EXTRA_CXX_FLAGS}) + add_subdirectory(codegen) add_subdirectory(language) add_subdirectory(lexer) diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index b7e2910443..e828df7bc8 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -549,7 +549,7 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { } bool CodegenIspcVisitor::check_incompatibilities() { - const auto& has_incompatible_nodes = [this](const ast::Ast& node) { + const auto& has_incompatible_nodes = [](const ast::Ast& node) { return !collect_nodes(node, incompatible_node_types).empty(); }; @@ -652,7 +652,7 @@ void CodegenIspcVisitor::move_procs_to_wrapper() { populate_nameset(info.nrn_state_block); populate_nameset(info.breakpoint_node); - const auto& has_incompatible_nodes = [this](const ast::Ast& node) { + const auto& has_incompatible_nodes = [](const ast::Ast& node) { return !collect_nodes(node, incompatible_node_types).empty(); }; diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index 6981051ede..6f3cd48cae 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -12,6 +12,7 @@ #include #include +#include /** * \file @@ -21,30 +22,54 @@ namespace nmodl { namespace fast_math { -static inline double uint642dp(uint64_t ll) { - return *((double*) (&ll)); +static inline double uint642dp(uint64_t x) { + static_assert(sizeof(double) == sizeof(uint64_t), + "nmodl::fast_math::uint642dp requires sizeof(double) == sizeof(uint64_t)"); + double v; + std::memcpy(&v, &x, sizeof(double)); + return v; } static inline uint64_t dp2uint64(double x) { - return *((uint64_t*) (&x)); + static_assert(sizeof(double) == sizeof(uint64_t), + "nmodl::fast_math::dp2uint64 requires sizeof(double) == sizeof(uint64_t)"); + uint64_t v; + std::memcpy(&v, &x, sizeof(uint64_t)); + return v; } static inline float int322sp(int32_t x) { - return *((float*) (&x)); + static_assert(sizeof(float) == sizeof(int32_t), + "nmodl::fast_math::int322sp requires sizeof(float) == sizeof(int32_t)"); + float v; + std::memcpy(&v, &x, sizeof(float)); + return v; } static inline unsigned int sp2uint32(float x) { - return *((uint32_t*) (&x)); + static_assert(sizeof(float) == sizeof(unsigned int), + "nmodl::fast_math::sp2uint32 requires sizeof(float) == sizeof(unsigned int)"); + unsigned int v; + std::memcpy(&v, &x, sizeof(unsigned int)); + return v; } static inline float f_inf() { - uint32_t v = 0x7F800000; - return *((float*) (&v)); + static_assert(sizeof(float) == sizeof(uint32_t), + "nmodl::fast_math::f_inf requires sizeof(float) == sizeof(uint32_t)"); + float v; + uint32_t int_val{0x7F800000}; + std::memcpy(&v, &int_val, sizeof(float)); + return v; } static inline double inf() { - uint64_t v = 0x7FF0000000000000; - return *((double*) (&v)); + static_assert(sizeof(double) == sizeof(uint64_t), + "nmodl::fast_math::inf requires sizeof(double) == sizeof(uint64_t)"); + double v; + uint64_t int_val{0x7FF0000000000000}; + std::memcpy(&v, &int_val, sizeof(double)); + return v; } diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index cbca65e692..184bfc3315 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -22,7 +22,7 @@ namespace ast { /// forward declaration of ast nodes -class Ast; +struct Ast; {% for node in nodes %} class {{ node.class_name }}; {% endfor %} diff --git a/src/nmodl/lexer/unit_lexer.hpp b/src/nmodl/lexer/unit_lexer.hpp index 5a846ff936..234874f394 100644 --- a/src/nmodl/lexer/unit_lexer.hpp +++ b/src/nmodl/lexer/unit_lexer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -47,14 +47,6 @@ namespace parser { * because the yylex() defined in UnitFlexLexer has no parameters. */ class UnitLexer: public UnitFlexLexer { - /** - * \brief Reference to driver object where this lexer resides - * - * Currently driver object is not used within lexer but could be - * used for accessing previous units if needed. - */ - UnitDriver& driver; - public: /// location of the parsed token location loc; @@ -69,9 +61,10 @@ class UnitLexer: public UnitFlexLexer { * @param in Input stream from where tokens will be read * @param out Output stream where output will be sent */ - explicit UnitLexer(UnitDriver& driver, std::istream* in = nullptr, std::ostream* out = nullptr) - : UnitFlexLexer(in, out) - , driver(driver) {} + explicit UnitLexer(UnitDriver& /* driver */, + std::istream* in = nullptr, + std::ostream* out = nullptr) + : UnitFlexLexer(in, out) {} ~UnitLexer() override = default; diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index fec8ac9f62..56e45942e3 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -78,9 +78,6 @@ class DiffEqContext { /// rhs of equation std::string rhs; - /// order of the diff equation - int order = 0; - /// return solution for cnexp method std::string get_cnexp_solution() const; @@ -110,9 +107,8 @@ class DiffEqContext { DiffEqContext() = default; - DiffEqContext(std::string state, int order, std::string rhs, std::string method) + DiffEqContext(std::string state, int /* order */, std::string rhs, std::string method) : state(std::move(state)) - , order(order) , rhs(std::move(rhs)) , method(std::move(method)) {} diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index de7798009b..e24861af62 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -3,6 +3,19 @@ # ============================================================================= set_source_files_properties(${PYBIND_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) +# Set -fno-var-tracking-assignments on pyast.cpp with GCC to avoid a warning + double compilation +if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") + foreach(pybind_file "${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp") + get_source_file_property(pybind_file_compile_options "${pybind_file}" COMPILE_OPTIONS) + if("${pybind_file_compile_options}" STREQUAL "NOTFOUND") + set(pybind_file_compile_options) + endif() + list(APPEND pybind_file_compile_options "-fno-var-tracking-assignments") + set_source_files_properties("${pybind_file}" PROPERTIES COMPILE_OPTIONS + "${pybind_file_compile_options}") + endforeach() +endif() + # build nmodl python module under lib/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl) diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 6684febca9..a5502ca778 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -22,7 +22,7 @@ namespace nmodl { namespace ast { -class Ast; +struct Ast; } /// %Symbol table related implementations diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 3605d68a5d..9a14274e37 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -325,7 +325,6 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre } // find out where to insert solutions in statement block const auto& statements = block_with_expression_statements->get_statements(); - auto it = get_solution_location_iterator(statements); if (small_system) { // for small number of state vars, linear solver // directly returns solution by solving symbolically at compile time @@ -418,7 +417,12 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { check_expr_statements_in_same_block(); const auto node_as_nmodl = to_nmodl_for_sympy(node); - auto diffeq_solver = pywrap::EmbeddedPythonLoader::get_instance().api()->create_des_executor(); + const auto deleter = [](nmodl::pybind_wrappers::DiffeqSolverExecutor* ptr) { + pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_des_executor(ptr); + }; + std::unique_ptr diffeq_solver{ + pywrap::EmbeddedPythonLoader::get_instance().api()->create_des_executor(), deleter}; + diffeq_solver->node_as_nmodl = node_as_nmodl; diffeq_solver->dt_var = codegen::naming::NTHREAD_DT_VARIABLE; diffeq_solver->vars = vars; @@ -462,7 +466,6 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { logger->debug("SympySolverVisitor :: -> solution: {}", solution); auto exception_message = diffeq_solver->exception_message; - pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_des_executor(diffeq_solver); if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: python exception: " + exception_message); return; diff --git a/test/nmodl/transpiler/integration/CMakeLists.txt b/test/nmodl/transpiler/integration/CMakeLists.txt index 3207e49e3a..3343748dcf 100644 --- a/test/nmodl/transpiler/integration/CMakeLists.txt +++ b/test/nmodl/transpiler/integration/CMakeLists.txt @@ -1,3 +1,9 @@ +# ============================================================================= +# Add extra compile flags to NMODL test sources +# ============================================================================= +add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) +add_link_options(${NMODL_EXTRA_CXX_FLAGS}) + # ============================================================================= # translation of mod files # ============================================================================= diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index cf9cc16b51..e7b02eefb2 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -1,5 +1,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) +# ============================================================================= +# Add extra compile flags to NMODL test sources +# ============================================================================= +add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) +add_link_options(${NMODL_EXTRA_CXX_FLAGS}) add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) From 64f5c3585f7bdefcb3675417ff31e59b1c306d7d Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Fri, 22 Oct 2021 14:38:53 +0200 Subject: [PATCH 394/871] Fix -Wunused-variable warnings. (BlueBrain/nmodl#757) Remove -Wno-unused-variable so the CI will catch regressions. NMODL Repo SHA: BlueBrain/nmodl@529de8657512d1d36eaf4c6ece83c288e81d2971 --- src/nmodl/codegen/codegen_c_visitor.cpp | 5 +---- src/nmodl/visitors/inline_visitor.cpp | 1 - src/nmodl/visitors/sympy_conductance_visitor.cpp | 1 - src/nmodl/visitors/sympy_solver_visitor.cpp | 2 -- src/nmodl/visitors/visitor_utils.cpp | 2 -- 5 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 81a14fad77..443fd31728 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -768,7 +768,7 @@ void CodegenCVisitor::update_index_semantics() { info.semantics.emplace_back(index++, naming::POINT_PROCESS_SEMANTIC, 1); } for (const auto& ion: info.ions) { - for (const auto& var: ion.reads) { + for (auto i = 0; i < ion.reads.size(); ++i) { info.semantics.emplace_back(index++, ion.name + "_ion", 1); } for (const auto& var: ion.writes) { @@ -2298,7 +2298,6 @@ std::string CodegenCVisitor::float_variable_name(const SymbolType& symbol, bool use_instance) const { auto name = symbol->get_name(); auto dimension = symbol->get_length(); - auto num_float = float_variables_size(); auto position = position_of_float_var(name); // clang-format off if (symbol->is_array()) { @@ -2319,7 +2318,6 @@ std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, const std::string& name, bool use_instance) const { auto position = position_of_int_var(name); - auto num_int = int_variables_size(); // clang-format off if (symbol.is_index) { if (use_instance) { @@ -3995,7 +3993,6 @@ void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { return a.name == naming::FOR_NETCON_SEMANTIC; })->index; - const auto num_int = int_variables_size(); printer->add_text("const size_t offset = {}*pnodecount + id;"_format(index)); printer->add_newline(); diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index cb723f0f1c..7d82ce57bc 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -150,7 +150,6 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, ModToken tok; name->set_token(tok); - const ast::StatementVector& statements = caller.get_statements(); auto local_list_statement = get_local_list_statement(caller); /// each block should already have local statement if (local_list_statement == nullptr) { diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index d883e37a3c..2b4b6250a7 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -65,7 +65,6 @@ std::vector SympyConductanceVisitor::generate_statement_strings( // look for a current name that matches lhs of expr (current write name) auto it = i_name.find(lhs_str); if (it != i_name.end()) { - const auto& equation_string = ordered_binary_exprs[binary_expr_index[lhs_str]]; std::string i_name_str = it->second; // SymPy needs the current expression & all previous expressions std::vector expressions(ordered_binary_exprs.begin(), diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 9a14274e37..6e5c254f21 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -324,7 +324,6 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre return; } // find out where to insert solutions in statement block - const auto& statements = block_with_expression_statements->get_statements(); if (small_system) { // for small number of state vars, linear solver // directly returns solution by solving symbolically at compile time @@ -400,7 +399,6 @@ void SympySolverVisitor::visit_var_name(ast::VarName& node) { void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { const auto& lhs = node.get_expression()->get_lhs(); - const auto& rhs = node.get_expression()->get_rhs(); if (!lhs->is_var_name()) { logger->warn("SympySolverVisitor :: LHS of differential equation is not a VariableName"); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 0000bcbec3..c5f33a251d 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -80,8 +80,6 @@ void add_local_statement(StatementBlock& node) { LocalVar* add_local_variable(StatementBlock& node, Identifier* varname) { add_local_statement(node); - const ast::StatementVector& statements = node.get_statements(); - auto local_list_statement = get_local_list_statement(node); /// each block should already have local statement if (local_list_statement == nullptr) { From 9654f6fbbe7e44efa45ab78db704419c2824358a Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Wed, 27 Oct 2021 17:17:09 +0200 Subject: [PATCH 395/871] Fix codegen with multiple DERIVATIVE blocks. (BlueBrain/nmodl#762) Add an exception that would have caught the issue earlier, and a unit test. NMODL Repo SHA: BlueBrain/nmodl@85dec36180cc8d012db3392c06c065d39de79960 --- src/nmodl/codegen/codegen_c_visitor.cpp | 5 +++ src/nmodl/codegen/codegen_helper_visitor.cpp | 17 ++++++- .../unit/codegen/codegen_helper.cpp | 44 +++++++++++++++++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 443fd31728..ce4a500a2b 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3055,6 +3055,11 @@ void CodegenCVisitor::print_global_variable_setup() { // offsets for state variables if (info.primes_size != 0) { + if (info.primes_size != info.prime_variables_by_order.size()) { + throw std::runtime_error{ + "primes_size = {} differs from prime_variables_by_order.size() = {}, this should not happen."_format( + info.primes_size, info.prime_variables_by_order.size())}; + } auto slist1 = get_variable_name("slist1"); auto dlist1 = get_variable_name("dlist1"); auto n = info.primes_size; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 1bc11d85f4..5d7b28578a 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -602,6 +602,12 @@ void CodegenHelperVisitor::visit_conductance_hint(const ConductanceHint& node) { * Dstate then we have to lookup state to find out corresponding symbol. This * is because prime_variables_by_order should contain state variable name and * not the one replaced by solver pass. + * + * \todo AST can have duplicate DERIVATIVE blocks if a mod file uses SOLVE + * statements in its INITIAL block (e.g. in case of kinetic schemes using + * `STEADYSTATE sparse` solver). Such duplicated DERIVATIVE blocks could + * be removed by `SolveBlockVisitor`, or we have to avoid visiting them + * here. See e.g. SH_na8st.mod test and original reduced_dentate .mod. */ void CodegenHelperVisitor::visit_statement_block(const ast::StatementBlock& node) { const auto& statements = node.get_statements(); @@ -618,8 +624,15 @@ void CodegenHelperVisitor::visit_statement_block(const ast::StatementBlock& node if (from_state) { symbol = psymtab->lookup(name.substr(1, name.size())); } - info.prime_variables_by_order.push_back(symbol); - info.num_equations++; + // See the \todo note above. + if (std::find_if(info.prime_variables_by_order.begin(), + info.prime_variables_by_order.end(), + [&](auto const& sym) { + return sym->get_name() == symbol->get_name(); + }) == info.prime_variables_by_order.end()) { + info.prime_variables_by_order.push_back(symbol); + info.num_equations++; + } } } } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 5e5189d88e..838a661297 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -10,6 +10,10 @@ #include "ast/program.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" +#include "visitors/kinetic_block_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" +#include "visitors/steadystate_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; @@ -168,3 +172,43 @@ SCENARIO("unusual / failing mod files", "[codegen][var_order]") { } } } + +SCENARIO("Check global variable setup", "[codegen][global_variables]") { + GIVEN("SH_na8st.mod: modfile from reduced_dentate model") { + std::string const nmodl_text{R"( + NEURON { + SUFFIX na8st + } + STATE { c1 c2 } + BREAKPOINT { + SOLVE kin METHOD derivimplicit + } + INITIAL { + SOLVE kin STEADYSTATE derivimplicit + } + KINETIC kin { + ~ c1 <-> c2 (a1, b1) + } + )"}; + NmodlDriver driver; + const auto ast = driver.parse_string(nmodl_text); + + /// construct symbol table and run codegen helper visitor + SymtabVisitor{}.visit_program(*ast); + KineticBlockVisitor{}.visit_program(*ast); + SymtabVisitor{}.visit_program(*ast); + SteadystateVisitor{}.visit_program(*ast); + SymtabVisitor{}.visit_program(*ast); + NeuronSolveVisitor{}.visit_program(*ast); + SolveBlockVisitor{}.visit_program(*ast); + SymtabVisitor{true}.visit_program(*ast); + + CodegenHelperVisitor v; + const auto info = v.analyze(*ast); + // See https://github.com/BlueBrain/nmodl/issues/736 + THEN("Checking that primes_size and prime_variables_by_order have the expected size") { + REQUIRE(info.primes_size == 2); + REQUIRE(info.prime_variables_by_order.size() == 2); + } + } +} From 6ad3e70d9a8767b676597103fb138c191a4c76a0 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 29 Oct 2021 10:48:45 +0200 Subject: [PATCH 396/871] Rename ION variable from verbatim block (BlueBrain/nmodl#761) * Verbatim block can refer to ION variables like _ion_cai * Such variables need to be renamed as we don't have same macros as MOD2C or NOCMODL * We updated symtab visitor in order to populate all possible ION variables in the symbol table * VerbatimVarRename visitor updated to rename _ion_cai to ion_cai * Add new symbol property type codegen_var * Remove hardcoded ion_ prefix and put that under naming namespace. * Integration test added Fixes BlueBrain/nmodl#756 Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@db45ceafd58ee0bcebd1fa63bb4b39350d473fc3 --- src/nmodl/codegen/codegen_c_visitor.cpp | 26 ++++++++++--------- src/nmodl/codegen/codegen_info.hpp | 7 +++++ src/nmodl/codegen/codegen_naming.hpp | 4 +++ src/nmodl/symtab/symbol_properties.cpp | 4 +++ src/nmodl/symtab/symbol_properties.hpp | 5 +++- src/nmodl/visitors/symtab_visitor_helper.hpp | 16 ++++++++++++ .../visitors/verbatim_var_rename_visitor.cpp | 4 +++ .../visitors/verbatim_var_rename_visitor.hpp | 3 +++ .../transpiler/integration/mod/cabpump.mod | 4 ++- 9 files changed, 59 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index ce4a500a2b..a72cd2ab32 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -901,7 +901,7 @@ std::vector CodegenCVisitor::get_int_variables() { // not have doubles between read/write. Same // name variables are allowed for (const auto& var: ion.reads) { - const std::string name = "ion_" + var; + const std::string name = naming::ION_VARNAME_PREFIX + var; variables.emplace_back(make_symbol(name)); variables.back().is_constant = true; ion_vars[name] = variables.size() - 1; @@ -911,16 +911,17 @@ std::vector CodegenCVisitor::get_int_variables() { std::shared_ptr ion_di_dv_var = nullptr; for (const auto& var: ion.writes) { - const std::string name = "ion_" + var; + const std::string name = naming::ION_VARNAME_PREFIX + var; const auto ion_vars_it = ion_vars.find(name); if (ion_vars_it != ion_vars.end()) { variables[ion_vars_it->second].is_constant = false; } else { - variables.emplace_back(make_symbol("ion_" + var)); + variables.emplace_back(make_symbol(naming::ION_VARNAME_PREFIX + var)); } if (ion.is_ionic_current(var)) { - ion_di_dv_var = make_symbol("ion_di" + ion.name + "dv"); + ion_di_dv_var = make_symbol(std::string(naming::ION_VARNAME_PREFIX) + "di" + + ion.name + "dv"); } if (ion.is_intra_cell_conc(var) || ion.is_extra_cell_conc(var)) { need_style = true; @@ -993,9 +994,10 @@ std::vector CodegenCVisitor::get_shadow_variables() { std::vector variables; for (const auto& ion: info.ions) { for (const auto& var: ion.writes) { - variables.push_back({make_symbol(shadow_varname("ion_" + var))}); + variables.push_back({make_symbol(shadow_varname(naming::ION_VARNAME_PREFIX + var))}); if (ion.is_ionic_current(var)) { - variables.push_back({make_symbol(shadow_varname("ion_di" + ion.name + "dv"))}); + variables.push_back({make_symbol(shadow_varname( + std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"))}); } } } @@ -2082,20 +2084,20 @@ std::string CodegenCVisitor::register_mechanism_arguments() const { std::pair CodegenCVisitor::read_ion_variable_name( const std::string& name) const { - return {name, "ion_" + name}; + return {name, naming::ION_VARNAME_PREFIX + name}; } std::pair CodegenCVisitor::write_ion_variable_name( const std::string& name) const { - return {"ion_" + name, name}; + return {naming::ION_VARNAME_PREFIX + name, name}; } std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, const std::string& concentration, int index) { - auto conc_var_name = get_variable_name("ion_" + concentration); + auto conc_var_name = get_variable_name(naming::ION_VARNAME_PREFIX + concentration); auto style_var_name = get_variable_name("style_" + ion_name); return "nrn_wrote_conc({}_type," " &({})," @@ -2354,7 +2356,7 @@ std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name std::string result(name); if (ion_variable_struct_required()) { if (info.is_ion_read_variable(name)) { - result = "ion_" + name; + result = naming::ION_VARNAME_PREFIX + name; } if (info.is_ion_write_variable(name)) { result = "ionvar." + name; @@ -4331,7 +4333,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no for (const auto& conductance: info.conductances) { if (!conductance.ion.empty()) { - auto lhs = "ion_di" + conductance.ion + "dv"; + auto lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + conductance.ion + "dv"; auto rhs = get_variable_name(conductance.variable); ShadowUseStatement statement{lhs, "+=", rhs}; auto text = process_shadow_update_statement(statement, BlockType::Equation); @@ -4356,7 +4358,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { - auto lhs = "ion_di" + ion.name + "dv"; + auto lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; auto rhs = "(di{}-{})/0.001"_format(ion.name, get_variable_name(var)); if (info.point_process) { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index bb84127486..2d89580578 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -102,6 +102,13 @@ struct Ion { bool is_ionic_conc(const std::string& text) const { return is_intra_cell_conc(text) || is_extra_cell_conc(text); } + + /// for a given ion, return different variable names/properties + /// like internal/external concentration, reversial potential, + /// ionic current etc. + static std::vector get_possible_variables(const std::string& ion_name) { + return {"i" + ion_name, ion_name + "i", ion_name + "o", "e" + ion_name}; + } }; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index dffb3e5136..73c09df055 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -149,6 +149,10 @@ static constexpr char THREAD_ARGS[] = "_threadargs_"; /// verbatim name of the variable for nrn thread arguments in prototype static constexpr char THREAD_ARGS_PROTO[] = "_threadargsproto_"; +/// prefix for ion variable +static constexpr char ION_VARNAME_PREFIX[] = "ion_"; + + /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends // clang-format off diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index fb6c4e92ea..695d4decf3 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -162,6 +162,10 @@ std::vector to_string_vector(const NmodlType& obj) { properties.emplace_back("define"); } + if (has_property(obj, NmodlType::codegen_var)) { + properties.emplace_back("codegen_var"); + } + return properties; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 39492b0981..43422030e8 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -220,7 +220,10 @@ enum class NmodlType : enum_type { discrete_block = 1L << 33, /// Define variable / macro - define = 1L << 34 + define = 1L << 34, + + /// Codegen specific variable + codegen_var = 1L << 35 }; template diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 8484653b4d..f7b073f2dd 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -9,6 +9,8 @@ #include +#include "codegen/codegen_info.hpp" +#include "codegen/codegen_naming.hpp" #include "lexer/token_mapping.hpp" #include "visitors/symtab_visitor.hpp" @@ -125,6 +127,20 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { symbol->set_value(define->get_value()->to_double()); } + // for a given USEION statement, add all possible ion variables + // these variables can be used within VERBATIM block and hence + // needs to be populated in the symbol table + if (node->is_useion()) { + auto use_ion = dynamic_cast(node); + auto name = use_ion->get_name()->get_node_name(); + for (const auto& variable: codegen::Ion::get_possible_variables(name)) { + std::string ion_variable(codegen::naming::ION_VARNAME_PREFIX + variable); + auto symbol = std::make_shared(ion_variable, nullptr, ModToken()); + symbol->add_property(NmodlType::codegen_var); + modsymtab->insert(symbol); + } + } + /// visit children, most likely variables are already /// leaf nodes, not necessary to visit node->visit_children(*this); diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 298c5529c3..1282f56486 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -60,6 +60,10 @@ std::string VerbatimVarRenameVisitor::rename_variable(const std::string& name) { new_name.erase(0, 3); rename_plausible = true; } + if (name.find(ION_PREFIX) == 0) { + new_name.erase(0, 1); + rename_plausible = true; + } if (rename_plausible) { auto symbol = symtab->lookup_in_scope(new_name); if (symbol != nullptr) { diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index 37188cefa4..e12eac5409 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -58,6 +58,9 @@ class VerbatimVarRenameVisitor: public AstVisitor { /// prefix used for range variables const std::string RANGE_PREFIX = "_p_"; + /// prefix used for range variables + const std::string ION_PREFIX = "_ion_"; + std::string rename_variable(const std::string& name); public: diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 205d5b4a18..5fd624fd2e 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -55,7 +55,9 @@ BREAKPOINT { INCLUDE "var_init.inc" DERIVATIVE state { - + VERBATIM + cai = 2 * _ion_cai; + ENDVERBATIM drive_channel = - (10000) * ica / (2 * FARADAY * depth) if (drive_channel <= 0.) { drive_channel = 0. } : cannot pump inward ca' = drive_channel/18 + (cainf -ca)/taur*11 From 2e84e58b62ca84fc962e491fd46d9ab41243d176 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 29 Oct 2021 15:52:24 +0200 Subject: [PATCH 397/871] Update bison files with last updates (BlueBrain/nmodl#755) NMODL Repo SHA: BlueBrain/nmodl@dba59038a22c1262e0d5265e33948c7b78504259 --- src/nmodl/parser/verbatim.yy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index 27df1e835c..bf1df1d81e 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -23,7 +23,7 @@ %define parse.error verbose /** make a reentrant parser */ -%pure-parser +%define api.pure /** parser prefix */ %name-prefix "Verbatim_" From d023ebfd401d3e3c2d6152e49516cfbbdc51693a Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 5 Nov 2021 10:22:01 +0100 Subject: [PATCH 398/871] Add a Semantic Analysis Visitor for check semantic (BlueBrain/nmodl#769) First checker: procedure and function having table stmt should have exactly one argument. NMODL Repo SHA: BlueBrain/nmodl@5dff44317cfe1010e65d7bad66d7d1db379938c9 --- src/nmodl/main.cpp | 7 ++ src/nmodl/visitors/CMakeLists.txt | 2 + .../visitors/semantic_analysis_visitor.cpp | 32 +++++++++ .../visitors/semantic_analysis_visitor.hpp | 55 ++++++++++++++++ test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../unit/visitor/semantic_analysis.cpp | 66 +++++++++++++++++++ 6 files changed, 163 insertions(+) create mode 100644 src/nmodl/visitors/semantic_analysis_visitor.cpp create mode 100644 src/nmodl/visitors/semantic_analysis_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index c1d74468ab..2bcb3ebfcf 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -37,6 +37,7 @@ #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" +#include "visitors/semantic_analysis_visitor.hpp" #include "visitors/solve_block_visitor.hpp" #include "visitors/steadystate_visitor.hpp" #include "visitors/sympy_conductance_visitor.hpp" @@ -316,6 +317,12 @@ int main(int argc, const char* argv[]) { /// just visit the ast AstVisitor().visit_program(*ast); + /// Check some rules that ast should follow + { + logger->info("Running semantic analysis visitor"); + SemanticAnalysisVisitor().visit_program(*ast); + } + /// construct symbol table { logger->info("Running symtab visitor"); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 5df4e28970..9da6e048c0 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -31,6 +31,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/semantic_analysis_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/semantic_analysis_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/steadystate_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/steadystate_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.hpp diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp new file mode 100644 index 0000000000..2e2528f7c2 --- /dev/null +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -0,0 +1,32 @@ +#include "visitors/semantic_analysis_visitor.hpp" +#include "ast/function_block.hpp" +#include "ast/procedure_block.hpp" +#include "ast/table_statement.hpp" + +namespace nmodl { +namespace visitor { + +void SemanticAnalysisVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { + in_procedure_function = true; + one_arg_in_procedure_function = node.get_parameters().size() == 1; + node.visit_children(*this); + in_procedure_function = false; +} + +void SemanticAnalysisVisitor::visit_function_block(const ast::FunctionBlock& node) { + in_procedure_function = true; + one_arg_in_procedure_function = node.get_parameters().size() == 1; + node.visit_children(*this); + in_procedure_function = false; +} + +void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement&) { + if (in_procedure_function && !one_arg_in_procedure_function) { + throw std::runtime_error( + "The procedure or function containing the TABLE statement should contains exactly one " + "argument."); + } +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp new file mode 100644 index 0000000000..b20c049fa5 --- /dev/null +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -0,0 +1,55 @@ +/************************************************************************* + * Copyright (C) 2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::SemanticAnalysisVisitor + */ + +/** + * \addtogroup visitor_classes + * \{ + */ + +/** + * \class SemanticAnalysisVisitor + * \brief %Visitor to check some semantic rules on the ast + * + * Current checks: + * + * - Check that a function or a procedure containing a TABLE statement contains only one argument + * (mandatory in mod2c). + */ +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +class SemanticAnalysisVisitor: public ConstAstVisitor { + private: + /// true if the procedure or the function contains only one argument + bool one_arg_in_procedure_function = false; + /// true if we are in a procedure or a function block + bool in_procedure_function = false; + + public: + SemanticAnalysisVisitor() = default; + + void visit_procedure_block(const ast::ProcedureBlock& node) override; + + void visit_function_block(const ast::FunctionBlock& node) override; + + void visit_table_statement(const ast::TableStatement& node) override; +}; + +/** \} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index e7b02eefb2..9f8d9e999a 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -49,6 +49,7 @@ add_executable( visitor/nmodl.cpp visitor/perf.cpp visitor/rename.cpp + visitor/semantic_analysis.cpp visitor/solve_block.cpp visitor/steadystate.cpp visitor/sympy_conductance.cpp diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp new file mode 100644 index 0000000000..9f7829894e --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -0,0 +1,66 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/semantic_analysis_visitor.hpp" + + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Procedure/Function inlining tests +//============================================================================= + +void run_semantic_analysis_visitor(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + SemanticAnalysisVisitor().visit_program(*ast); +} + +SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") { + GIVEN("Procedure with more than one argument") { + std::string nmodl_text = R"( + PROCEDURE rates_1(a, b) { + TABLE ainf FROM 0 TO 1 WITH 1 + ainf = 1 + } + )"; + THEN("throw") { + REQUIRE_THROWS(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("Procedure with exactly one argument") { + std::string nmodl_text = R"( + PROCEDURE rates_1(a) { + TABLE ainf FROM 0 TO 1 WITH 1 + ainf = 1 + } + )"; + THEN("no throw") { + REQUIRE_NOTHROW(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("Procedure with less than one argument") { + std::string nmodl_text = R"( + PROCEDURE rates_1() { + TABLE ainf FROM 0 TO 1 WITH 1 + ainf = 1 + } + )"; + THEN("throw") { + REQUIRE_THROWS(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From 6d3f15e663be6d4fb625fe452a746e89355a0a8c Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 9 Nov 2021 13:26:16 +0100 Subject: [PATCH 399/871] =?UTF-8?q?Replace=20placeholder=20arg=5Fv=20with?= =?UTF-8?q?=20the=20correct=20name=20in=20function=20containin=E2=80=A6=20?= =?UTF-8?q?(BlueBrain/nmodl#768)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last argument is often called `v` and is renamed to `arg_v`. This fix let user choose a different name. Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@55e4a6f10c7843bdc1e8a2cb9d1193d2a3590a56 --- src/nmodl/codegen/codegen_c_visitor.cpp | 11 +++++++--- .../transpiler/integration/mod/cabpump.mod | 21 +++++++------------ .../transpiler/integration/mod/watch_test.mod | 12 +++++++++++ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index a72cd2ab32..05a11123c5 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1606,12 +1606,17 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { print_function_declaration(node, name); printer->start_block(); { - printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); - printer->add_line(" {}({}, arg_v);"_format(function_name, internal_method_arguments())); + const auto& params = node.get_parameters(); + printer->add_line("if ( {} == 0) {{"_format(use_table_var)); + printer->add_line(" {}({}, {});"_format(function_name, + internal_method_arguments(), + params[0].get()->get_node_name())); printer->add_line(" return 0;"); printer->add_line("}"); - printer->add_line("double xi = {} * (arg_v - {});"_format(mfac_name, tmin_name)); + printer->add_line("double xi = {} * ({} - {});"_format(mfac_name, + params[0].get()->get_node_name(), + tmin_name)); printer->add_line("if (isnan(xi)) {"); for (const auto& var: table_variables) { auto name = get_variable_name(var->get_node_name()); diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 5fd624fd2e..d7c763b26c 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -6,7 +6,7 @@ NEURON { RANGE ca GLOBAL depth,cainf,taur RANGE var - + RANGE ainf } UNITS { @@ -18,18 +18,6 @@ UNITS { FARADAY = (faraday) (coul) } -CONSTRUCTOR { -VERBATIM -// Nothing only to verify that it is well handled -ENDVERBATIM -} - -DESTRUCTOR { -VERBATIM -// Nothing only to verify that it is well handled -ENDVERBATIM -} - PARAMETER { depth = .1 (um) taur = 200 (ms) : rate of calcium removal for stress conditions @@ -41,6 +29,7 @@ ASSIGNED { ica (mA/cm2) drive_channel (mM/ms) var (mV) + ainf } STATE { @@ -64,6 +53,12 @@ DERIVATIVE state { cai = ca } +: to test code generation for TABLE statement +PROCEDURE test_table(br) { + TABLE ainf FROM 0 TO 1 WITH 1 + ainf = 1 +} + INITIAL { var_init(var) ca = cainf diff --git a/test/nmodl/transpiler/integration/mod/watch_test.mod b/test/nmodl/transpiler/integration/mod/watch_test.mod index 0eb08d3e1b..0a923d402b 100644 --- a/test/nmodl/transpiler/integration/mod/watch_test.mod +++ b/test/nmodl/transpiler/integration/mod/watch_test.mod @@ -27,6 +27,18 @@ ASSIGNED { g (umho) } +CONSTRUCTOR { + VERBATIM + // only to verify that it is well handled + ENDVERBATIM +} + +DESTRUCTOR { + VERBATIM + // only to verify that it is well handled + ENDVERBATIM +} + DEFINE init 1 DEFINE rise 2 DEFINE fall 3 From 8229b3f436750d469aa1f9f6c3d3c25ce33bfc6a Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 16 Nov 2021 12:01:51 +0100 Subject: [PATCH 400/871] Replace macro name by value (BlueBrain/nmodl#766) macro is created by DEFINE statements, and always got a direct value DEFINE NAME_PTR INTEGER_PTR is the only accepted way Add test case in cabpump.mod NMODL Repo SHA: BlueBrain/nmodl@a164c86418cca893599dff0ed3b3f7f405c289e1 --- src/nmodl/codegen/codegen_c_visitor.cpp | 7 +------ test/nmodl/transpiler/integration/mod/cabpump.mod | 6 +++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 05a11123c5..223e09c72f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -69,13 +69,8 @@ void CodegenCVisitor::visit_integer(const Integer& node) { if (!codegen) { return; } - const auto& macro = node.get_macro(); const auto& value = node.get_value(); - if (macro) { - macro->accept(*this); - } else { - printer->add_text(std::to_string(value)); - } + printer->add_text(std::to_string(value)); } diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index d7c763b26c..a1c6b361a2 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -1,5 +1,7 @@ : simple first-order model of calcium dynamics +DEFINE FOO 1 + NEURON { SUFFIX cadyn USEION ca READ cai,ica WRITE cai @@ -51,11 +53,13 @@ DERIVATIVE state { if (drive_channel <= 0.) { drive_channel = 0. } : cannot pump inward ca' = drive_channel/18 + (cainf -ca)/taur*11 cai = ca + + if (FOO == 0) { } } : to test code generation for TABLE statement PROCEDURE test_table(br) { - TABLE ainf FROM 0 TO 1 WITH 1 + TABLE ainf FROM 0 TO FOO WITH 1 ainf = 1 } From bcba141d18bce477d4b77b1bba61c5bc0f3ca504 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Wed, 17 Nov 2021 13:41:04 +0100 Subject: [PATCH 401/871] Reorganise code generation for Python bindings, improve build speed (BlueBrain/nmodl#773) * Make all.hpp forward to other headers. * Avoid including spdlog in utils/string_utils.hpp. * pybind11: remove explicit dependencies. * pybind11: split generated code across more files. NMODL Repo SHA: BlueBrain/nmodl@ee17a9cf06fd9b2a2a5dd5755afae7e3406bfd42 --- src/nmodl/language/code_generator.cmake | 4 +- src/nmodl/language/code_generator.py | 45 ++- src/nmodl/language/nodes.py | 13 +- src/nmodl/language/templates/ast/all.hpp | 24 +- src/nmodl/language/templates/ast/node.hpp | 8 +- .../templates/ast/node_class.template | 6 +- .../ast/node_class_inline_definition.template | 8 - src/nmodl/language/templates/pybind/pyast.cpp | 256 +++--------------- .../language/templates/pybind/pynode.cpp | 89 ++++++ src/nmodl/pybind/docstrings.hpp | 167 ++++++++++++ src/nmodl/utils/CMakeLists.txt | 1 + src/nmodl/utils/string_utils.cpp | 30 ++ src/nmodl/utils/string_utils.hpp | 16 +- 13 files changed, 366 insertions(+), 301 deletions(-) delete mode 100644 src/nmodl/language/templates/ast/node_class_inline_definition.template create mode 100644 src/nmodl/language/templates/pybind/pynode.cpp create mode 100644 src/nmodl/pybind/docstrings.hpp create mode 100644 src/nmodl/utils/string_utils.cpp diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 08091bf9c4..783acd8d5b 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -10,9 +10,9 @@ set(CODE_GENERATOR_JINJA_FILES ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast_decl.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node.hpp ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node_class.template - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node_class_inline_definition.template ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.cpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.hpp + ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pynode.cpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pysymtab.cpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyvisitor.cpp ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyvisitor.hpp @@ -196,6 +196,8 @@ set(AST_GENERATED_SOURCES set(PYBIND_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp + ${PROJECT_BINARY_DIR}/src/pybind/pynode_0.cpp + ${PROJECT_BINARY_DIR}/src/pybind/pynode_1.cpp ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index e03f8ce637..df700b9098 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2021 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. @@ -10,6 +10,7 @@ import filecmp import itertools import logging +import math import os from pathlib import Path, PurePath import shutil @@ -152,25 +153,24 @@ def workload(self): """ # special template "ast/node.hpp used to generate multiple .hpp files node_hpp_tpl = self.jinja_templates_dir / "ast" / "node.hpp" + pyast_cpp_tpl = self.jinja_templates_dir / "pybind" / "pyast.cpp" + pynode_cpp_tpl = self.jinja_templates_dir / "pybind" / "pynode.cpp" # special template only included by other templates node_class_tpl = self.jinja_templates_dir / "ast" / "node_class.template" - node_class_inline_def_tpl = self.jinja_templates_dir / "ast" / "node_class_inline_definition.template" - # Jinja templates that should be ignored - ignored_templates = {node_class_tpl} # Additional dependencies Path -> [Path, ...] - extradeps = collections.defaultdict( - list, - { - self.jinja_templates_dir / "ast" / "all.hpp": [node_class_tpl, node_class_inline_def_tpl], - node_hpp_tpl: [node_class_tpl, node_class_inline_def_tpl], - }, - ) + extradeps = collections.defaultdict(list, {node_hpp_tpl: [node_class_tpl]}) # Additional Jinja context set when rendering the template + num_pybind_files = 2 extracontext = collections.defaultdict( dict, { - self.jinja_templates_dir / "ast" / "all.hpp": dict(render_ast_all=True) - } + pyast_cpp_tpl: { + "setup_pybind_methods": [ + "init_pybind_classes_{}".format(x) + for x in range(num_pybind_files) + ] + } + }, ) tasks = [] @@ -179,8 +179,6 @@ def workload(self): # create output directory if missing (self.base_dir / sub_dir).mkdir(parents=True, exist_ok=True) for filepath in path.glob("*.[ch]pp"): - if filepath in ignored_templates: - continue if filepath == node_hpp_tpl: # special treatment for this template. # generate one C++ header per AST node type @@ -194,6 +192,23 @@ def workload(self): ) tasks.append(task) yield task + elif filepath == pynode_cpp_tpl: + chunk_length = math.ceil(len(self.nodes) / num_pybind_files) + for chunk_k in range(num_pybind_files): + task = JinjaTask( + app=self, + input=filepath, + output=self.base_dir / sub_dir / "pynode_{}.cpp".format(chunk_k), + context=dict( + nodes=self.nodes[ + chunk_k * chunk_length : (chunk_k + 1) * chunk_length + ], + setup_pybind_method="init_pybind_classes_{}".format(chunk_k), + ), + extradeps=extradeps[filepath], + ) + tasks.append(task) + yield task else: task = JinjaTask( app=self, diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 489db03e19..77735b470f 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2021 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. @@ -45,17 +45,6 @@ def cpp_header(self): """Path to C++ header file of this class relative to BUILD_DIR""" return "ast/" + to_snake_case(self.class_name) + ".hpp" - @property - def cpp_fence(self): - """Preprocessor macro to use to prevent symbol redefinition - - #ifndef {{ node.cpp_fence }} - #define {{ node.cpp_fence }} - // ... - # endif - """ - return "NMODL_AST_" + to_snake_case(self.class_name).upper() + '_HPP' - @property def is_statement_block_node(self): return self.class_name == node_info.STATEMENT_BLOCK_NODE diff --git a/src/nmodl/language/templates/ast/all.hpp b/src/nmodl/language/templates/ast/all.hpp index 23689bab97..c25c79f3dc 100644 --- a/src/nmodl/language/templates/ast/all.hpp +++ b/src/nmodl/language/templates/ast/all.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -22,25 +22,5 @@ #include "ast/ast.hpp" {% for node in nodes %} -#ifndef {{ node.cpp_fence }} -#define {{ node.cpp_fence }} -{% if node.has_template_methods %} -#define {{ node.cpp_fence }}_INLINE_DEFINITION_REQUIRED -{% endif %} -{% include "ast/node_class.template" %} -#endif // !{{ node.cpp_fence }} +#include "{{node.cpp_header}}" {% endfor %} - -{# add inline definitions of template member methods #} -namespace nmodl { -namespace ast { -{% for node in nodes %} -{%- if node.has_template_methods %} -#ifdef {{ node.cpp_fence }}_INLINE_DEFINITION_REQUIRED - {% include "ast/node_class_inline_definition.template" %} -#endif // !{{ node.cpp_fence }}_INLINE_DEFINITION_REQUIRED - -{% endif %} -{%- endfor %} -} // namespace ast -} // namespace nmodl diff --git a/src/nmodl/language/templates/ast/node.hpp b/src/nmodl/language/templates/ast/node.hpp index fdbb2818e7..8f138c84d4 100644 --- a/src/nmodl/language/templates/ast/node.hpp +++ b/src/nmodl/language/templates/ast/node.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -11,9 +11,6 @@ #pragma once -#ifndef {{ node.cpp_fence }} -# define {{ node.cpp_fence }} - /** * \dir * \brief Auto generated AST Implementations @@ -28,6 +25,3 @@ {% endfor %} {% include "ast/node_class.template" %} - -#endif // {{ node.cpp_fence }} - diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index 8dc981c08f..66e6fd7e93 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -401,9 +401,9 @@ class {{ node.class_name }} : public {{ node.base_class }} { /** @} */ // end of ast_class -{% if render_ast_all is not defined %} -{% include "ast/node_class_inline_definition.template" %} -{% endif %} +{% for child in node.children %} + {{ child.get_add_methods_inline_definition(node) }} +{% endfor %} } // namespace ast } // namespace nmodl diff --git a/src/nmodl/language/templates/ast/node_class_inline_definition.template b/src/nmodl/language/templates/ast/node_class_inline_definition.template deleted file mode 100644 index 2e78a6bf94..0000000000 --- a/src/nmodl/language/templates/ast/node_class_inline_definition.template +++ /dev/null @@ -1,8 +0,0 @@ -{# - this Jinja template is not used to directly generate - a file but included by other templates. -#} -{# doxygen for these methods is handled by nodes.py #} -{% for child in node.children %} - {{ child.get_add_methods_inline_definition(node) }} -{% endfor %} diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index fb682c7d39..b1b37950ff 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -10,14 +10,11 @@ /// #include "pybind/pyast.hpp" +#include "pybind/docstrings.hpp" #include #include -#include "visitors/json_visitor.hpp" -#include "visitors/nmodl_visitor.hpp" - - /** * \file * \brief All AST classes for Python bindings @@ -26,155 +23,25 @@ #pragma clang diagnostic push #pragma ide diagnostic ignored "OCDFAInspection" - -// clang-format off -{% macro var(node) -%} -{{ node.class_name | snake_case }}_ -{%- endmacro -%} - -{% macro args(children) %} -{% for c in children %} {{ c.get_shared_typename() }} {%- if not loop.last %}, {% endif %} {% endfor %} -{%- endmacro -%} -// clang-format on - namespace nmodl { -namespace docstring { - -static const char* binary_op_enum = R"( - Enum type for binary operators in NMODL - - NMODL support different binary operators and this - type is used to store their value in the AST. See - nmodl::ast::Ast for details. -)"; - -static const char* ast_nodetype_enum = R"( - Enum type for every AST node type - - Every node in the ast has associated type represented by - this enum class. See nmodl::ast::AstNodeType for details. - -)"; - -static const char* ast_class = R"( - Base class for all Abstract Syntax Tree node types - - Every node in the Abstract Syntax Tree is inherited from base class - Ast. This class provides base properties and functions that are implemented - by base classes. -)"; - -static const char* accept_method = R"( - Accept (or visit) the current AST node using current visitor - - Instead of visiting children of AST node, like Ast::visit_children, - accept allows to visit the current node itself using the concrete - visitor provided. - - Args: - v (Visitor): Concrete visitor that will be used to recursively visit node -)"; - -static const char* visit_children_method = R"( - Visit children i.e. member of current AST node using provided visitor - - Different nodes in the AST have different members (i.e. children). This method - recursively visits children using provided concrete visitor. - - Args: - v (Visitor): Concrete visitor that will be used to recursively visit node -)"; - -static const char* get_node_type_method = R"( - Return type (ast.AstNodeType) of the ast node -)"; - -static const char* get_node_type_name_method = R"( - Return type (ast.AstNodeType) of the ast node as string -)"; - -static const char* get_node_name_method = R"( - Return name of the node - - Some ast nodes have a member designated as node name. For example, - in case of ast.FunctionCall, name of the function is returned as a node - name. Note that this is different from ast node type name and not every - ast node has name. -)"; - -static const char* get_nmodl_name_method = R"( - Return nmodl statement of the node - - Some ast nodes have a member designated as nmodl name. For example, - in case of "NEURON { }" the statement of NMODL which is stored as nmodl - name is "NEURON". This function is only implemented by node types that - have a nmodl statement. -)"; - - -static const char* clone_method = R"( - Create a copy of the AST node -)"; - -static const char* get_token_method = R"( - Return associated token for the AST node -)"; - -static const char* get_symbol_table_method = R"( - Return associated symbol table for the AST node - - Certain ast nodes (e.g. inherited from ast.Block) have associated - symbol table. These nodes have nmodl.symtab.SymbolTable as member - and it can be accessed using this method. -)"; - -static const char* get_statement_block_method = R"( - Return associated statement block for the AST node - - Top level block nodes encloses all statements in the ast::StatementBlock. - For example, ast.BreakpointBlock has all statements in curly brace (`{ }`) - stored in ast.StatementBlock : - - BREAKPOINT { - SOLVE states METHOD cnexp - gNaTs2_t = gNaTs2_tbar*m*m*m*h - ina = gNaTs2_t*(v-ena) - } - - This method return enclosing statement block. -)"; - -static const char* negate_method = R"( - Negate the value of AST node -)"; - -static const char* set_name_method = R"( - Set name for the AST node -)"; - -static const char* is_ast_method = R"( - Check if current node is of type ast.Ast -)"; - -static const char* eval_method = R"( - Return value of the ast node -)"; - -} // namespace docstring +namespace ast { +namespace pybind { +{% for setup_pybind_method in setup_pybind_methods %} +void {{setup_pybind_method}}(pybind11::module&); +{% endfor %} +} // namespace pybind +} // namespace ast } // namespace nmodl - namespace py = pybind11; using namespace nmodl::ast; -using nmodl::visitor::JSONVisitor; -using nmodl::visitor::NmodlPrintVisitor; using namespace pybind11::literals; void init_ast_module(py::module& m) { py::module m_ast = m.def_submodule("ast", "Abstract Syntax Tree (AST) related implementations"); - py::enum_(m_ast, "BinaryOp", docstring::binary_op_enum) + py::enum_(m_ast, "BinaryOp", docstring::binary_op_enum()) .value("BOP_ADDITION", BinaryOp::BOP_ADDITION) .value("BOP_SUBTRACTION", BinaryOp::BOP_SUBTRACTION) .value("BOP_MULTIPLICATION", BinaryOp::BOP_MULTIPLICATION) @@ -191,7 +58,7 @@ void init_ast_module(py::module& m) { .value("BOP_EXACT_EQUAL", BinaryOp::BOP_EXACT_EQUAL) .export_values(); - py::enum_(m_ast, "AstNodeType", docstring::ast_nodetype_enum) + py::enum_(m_ast, "AstNodeType", docstring::ast_nodetype_enum()) // clang-format off {% for node in nodes %} .value("{{ node.class_name|snake_case|upper }}", AstNodeType::{{ node.class_name|snake_case|upper }}, "AST node of type ast.{{ node.class_name}}") @@ -199,94 +66,45 @@ void init_ast_module(py::module& m) { .export_values(); // clang-format on - py::class_> ast_(m_ast, "Ast", docstring::ast_class); + py::class_> ast_(m_ast, "Ast", docstring::ast_class()); ast_.def(py::init<>()) - .def("visit_children", static_cast(&Ast::visit_children), "v"_a, docstring::visit_children_method) - .def("accept", static_cast(&Ast::accept), "v"_a, docstring::accept_method) - .def("accept", static_cast(&Ast::accept), "v"_a, docstring::accept_method) - .def("get_node_type", &Ast::get_node_type, docstring::get_node_type_method) - .def("get_node_type_name", &Ast::get_node_type_name, docstring::get_node_type_name_method) - .def("get_node_name", &Ast::get_node_name, docstring::get_node_name_method) - .def("get_nmodl_name", &Ast::get_nmodl_name, docstring::get_nmodl_name_method) - .def("get_token", &Ast::get_token, docstring::get_token_method) + .def("visit_children", + static_cast(&Ast::visit_children), + "v"_a, + docstring::visit_children_method()) + .def("accept", + static_cast(&Ast::accept), + "v"_a, + docstring::accept_method()) + .def("accept", + static_cast(&Ast::accept), + "v"_a, + docstring::accept_method()) + .def("get_node_type", &Ast::get_node_type, docstring::get_node_type_method()) + .def("get_node_type_name", &Ast::get_node_type_name, docstring::get_node_type_name_method()) + .def("get_node_name", &Ast::get_node_name, docstring::get_node_name_method()) + .def("get_nmodl_name", &Ast::get_nmodl_name, docstring::get_nmodl_name_method()) + .def("get_token", &Ast::get_token, docstring::get_token_method()) .def("get_symbol_table", &Ast::get_symbol_table, py::return_value_policy::reference, - docstring::get_symbol_table_method) + docstring::get_symbol_table_method()) .def("get_statement_block", &Ast::get_statement_block, - docstring::get_statement_block_method) - .def("clone", &Ast::clone, docstring::clone_method) - .def("negate", &Ast::negate, docstring::negate_method) - .def("set_name", &Ast::set_name, docstring::set_name_method) - .def("is_ast", &Ast::is_ast, docstring::is_ast_method) + docstring::get_statement_block_method()) + .def("clone", &Ast::clone, docstring::clone_method()) + .def("negate", &Ast::negate, docstring::negate_method()) + .def("set_name", &Ast::set_name, docstring::set_name_method()) + .def("is_ast", &Ast::is_ast, docstring::is_ast_method()) // clang-format off {% for node in nodes %} .def("is_{{ node.class_name | snake_case }}", &Ast::is_{{ node.class_name | snake_case }}, "Check if node is of type ast.{{ node.class_name}}") {% if loop.last -%};{% endif %} {% endfor %} - {% for node in nodes %} - py::class_<{{ node.class_name }}, std::shared_ptr<{{ node.class_name }}>> {{ var(node) }}(m_ast, "{{ node.class_name }}", {{ node.base_class | snake_case }}_); - {{ var(node) }}.doc() = "{{ node.brief }}"; - {% if node.children %} - {{ var(node) }}.def(py::init<{{ args(node.children) }}>()); - {% endif %} - {% if node.is_program_node or node.is_ptr_excluded_node %} - {{ var(node) }}.def(py::init<>()); - {% endif %} - // clang-format on - - {{var(node)}} - .def("__repr__", []({{node.class_name}} & n) { - std::stringstream ss; - JSONVisitor v(ss); - v.compact_json(true); - n.accept(v); - v.flush(); - return ss.str(); - }); - - {{var(node)}} - .def("__str__", []({{node.class_name}} & n) { - std::stringstream ss; - NmodlPrintVisitor v(ss); - n.accept(v); - return ss.str(); - }); - - // clang-format off - {% for member in node.public_members() %} - {{ var(node) }}.def_readwrite("{{ member[1] }}", &{{ node.class_name }}::{{ member[1] }}); + {% for setup_pybind_method in setup_pybind_methods %} + nmodl::ast::pybind::{{setup_pybind_method}}(m_ast); {% endfor %} - - {% for member in node.properties() %} - {% if member[2] == True %} - {{ var(node) }}.def_property("{{ member[1] }}", &{{ node.class_name }}::get_{{ member[1] }}, - &{{ node.class_name }}::set_{{ member[1] }}); - {% else %} - {{ var(node) }}.def_property("{{ member[1] }}", &{{ node.class_name }}::get_{{ member[1] }}, - static_cast(&{{ node.class_name }}::set_{{ member[1] }})); - {% endif %} - {% endfor %} - - {{ var(node) }}.def("visit_children", static_cast(&{{ node.class_name }}::visit_children), docstring::visit_children_method) - .def("accept", static_cast(&{{ node.class_name }}::accept), docstring::accept_method) - .def("accept", static_cast(&{{ node.class_name }}::accept), docstring::accept_method) - .def("clone", &{{ node.class_name }}::clone, docstring::clone_method) - .def("get_node_type", &{{ node.class_name }}::get_node_type, docstring::get_node_type_method) - .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name, docstring::get_node_type_name_method) - {% if node.nmodl_name %} - .def("get_nmodl_name", &{{ node.class_name }}::get_nmodl_name, docstring::get_nmodl_name_method) - {% endif %} - {% if node.is_data_type_node %} - .def("eval", &{{ node.class_name }}::eval, docstring::eval_method) - {% endif %} - .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}, "Check if node is of type ast.{{ node.class_name}}"); - - {% endfor %} - // clang-format on } #pragma clang diagnostic pop - diff --git a/src/nmodl/language/templates/pybind/pynode.cpp b/src/nmodl/language/templates/pybind/pynode.cpp new file mode 100644 index 0000000000..18926f3b2d --- /dev/null +++ b/src/nmodl/language/templates/pybind/pynode.cpp @@ -0,0 +1,89 @@ +/************************************************************************* + * Copyright (C) 2018-2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +/// +/// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. +/// +#include "ast/all.hpp" +#include "pybind/docstrings.hpp" +#include "visitors/json_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" + +#include +#include + +// clang-format off +{% macro args(children) %} +{% for c in children %}{{ c.get_shared_typename() }}{%- if not loop.last %}, {% endif %}{% endfor %} +{%- endmacro -%} +// clang-format on + +namespace nmodl { +namespace ast { +namespace pybind { + +void {{setup_pybind_method}}(pybind11::module& m_ast) { + {% for node in nodes %} + { + pybind11::class_<{{ node.class_name }}, {{node.base_class}}, std::shared_ptr<{{ node.class_name }}>> tmp{m_ast, "{{ node.class_name }}"}; + tmp.doc() = "{{ node.brief }}"; + {% if node.children %} + tmp.def(pybind11::init<{{ args(node.children) }}>()); + {% endif %} + {% if node.is_program_node or node.is_ptr_excluded_node %} + tmp.def(pybind11::init<>()); + {% endif %} + + tmp.def("__repr__", []({{node.class_name}} & n) { + std::stringstream ss; + nmodl::visitor::JSONVisitor v(ss); + v.compact_json(true); + n.accept(v); + v.flush(); + return ss.str(); + }); + tmp.def("__str__", []({{node.class_name}} & n) { + std::stringstream ss; + nmodl::visitor::NmodlPrintVisitor v(ss); + n.accept(v); + return ss.str(); + }); + + // clang-format off + {% for member in node.public_members() %} + tmp.def_readwrite("{{ member[1] }}", &{{ node.class_name }}::{{ member[1] }}); + {% endfor %} + + {% for member in node.properties() %} + {% if member[2] == True %} + tmp.def_property("{{ member[1] }}", &{{ node.class_name }}::get_{{ member[1] }}, &{{ node.class_name }}::set_{{ member[1] }}); + {% else %} + tmp.def_property("{{ member[1] }}", &{{ node.class_name }}::get_{{ member[1] }}, static_cast(&{{ node.class_name }}::set_{{ member[1] }})); + {% endif %} + {% endfor %} + + tmp.def("visit_children", static_cast(&{{ node.class_name }}::visit_children), docstring::visit_children_method()) + .def("accept", static_cast(&{{ node.class_name }}::accept), docstring::accept_method()) + .def("accept", static_cast(&{{ node.class_name }}::accept), docstring::accept_method()) + .def("clone", &{{ node.class_name }}::clone, docstring::clone_method()) + .def("get_node_type", &{{ node.class_name }}::get_node_type, docstring::get_node_type_method()) + .def("get_node_type_name", &{{ node.class_name }}::get_node_type_name, docstring::get_node_type_name_method()) + {% if node.nmodl_name %} + .def("get_nmodl_name", &{{ node.class_name }}::get_nmodl_name, docstring::get_nmodl_name_method()) + {% endif %} + {% if node.is_data_type_node %} + .def("eval", &{{ node.class_name }}::eval, docstring::eval_method()) + {% endif %} + .def("is_{{ node.class_name | snake_case }}", &{{ node.class_name }}::is_{{ node.class_name | snake_case }}, "Check if node is of type ast.{{ node.class_name}}"); + + // clang-format on + } + {% endfor %} +} +} // namespace pybind +} // namespace ast +} // namespace nmodl diff --git a/src/nmodl/pybind/docstrings.hpp b/src/nmodl/pybind/docstrings.hpp new file mode 100644 index 0000000000..279b148ab2 --- /dev/null +++ b/src/nmodl/pybind/docstrings.hpp @@ -0,0 +1,167 @@ +/************************************************************************* + * Copyright (C) 2018-2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#pragma once + +namespace nmodl { +namespace docstring { + +constexpr const char* binary_op_enum() { + return R"( + Enum type for binary operators in NMODL + + NMODL support different binary operators and this + type is used to store their value in the AST. See + nmodl::ast::Ast for details. +)"; +} + +constexpr const char* ast_nodetype_enum() { + return R"( + Enum type for every AST node type + + Every node in the ast has associated type represented by + this enum class. See nmodl::ast::AstNodeType for details. + +)"; +} + +constexpr const char* ast_class() { + return R"( + Base class for all Abstract Syntax Tree node types + + Every node in the Abstract Syntax Tree is inherited from base class + Ast. This class provides base properties and functions that are implemented + by base classes. +)"; +} + +constexpr const char* accept_method() { + return R"( + Accept (or visit) the current AST node using current visitor + + Instead of visiting children of AST node, like Ast::visit_children, + accept allows to visit the current node itself using the concrete + visitor provided. + + Args: + v (Visitor): Concrete visitor that will be used to recursively visit node +)"; +} + +constexpr const char* visit_children_method() { + return R"( + Visit children i.e. member of current AST node using provided visitor + + Different nodes in the AST have different members (i.e. children). This method + recursively visits children using provided concrete visitor. + + Args: + v (Visitor): Concrete visitor that will be used to recursively visit node +)"; +} + +constexpr const char* get_node_type_method() { + return R"( + Return type (ast.AstNodeType) of the ast node +)"; +} + +constexpr const char* get_node_type_name_method() { + return R"( + Return type (ast.AstNodeType) of the ast node as string +)"; +} + +constexpr const char* get_node_name_method() { + return R"( + Return name of the node + + Some ast nodes have a member designated as node name. For example, + in case of ast.FunctionCall, name of the function is returned as a node + name. Note that this is different from ast node type name and not every + ast node has name. +)"; +} + +constexpr const char* get_nmodl_name_method() { + return R"( + Return nmodl statement of the node + + Some ast nodes have a member designated as nmodl name. For example, + in case of "NEURON { }" the statement of NMODL which is stored as nmodl + name is "NEURON". This function is only implemented by node types that + have a nmodl statement. +)"; +} + + +constexpr const char* clone_method() { + return R"( + Create a copy of the AST node +)"; +} + +constexpr const char* get_token_method() { + return R"( + Return associated token for the AST node +)"; +} + +constexpr const char* get_symbol_table_method() { + return R"( + Return associated symbol table for the AST node + + Certain ast nodes (e.g. inherited from ast.Block) have associated + symbol table. These nodes have nmodl.symtab.SymbolTable as member + and it can be accessed using this method. +)"; +} + +constexpr const char* get_statement_block_method() { + return R"( + Return associated statement block for the AST node + + Top level block nodes encloses all statements in the ast::StatementBlock. + For example, ast.BreakpointBlock has all statements in curly brace (`{ }`) + stored in ast.StatementBlock : + + BREAKPOINT { + SOLVE states METHOD cnexp + gNaTs2_t = gNaTs2_tbar*m*m*m*h + ina = gNaTs2_t*(v-ena) + } + + This method return enclosing statement block. +)"; +} + +constexpr const char* negate_method() { + return R"( + Negate the value of AST node +)"; +} + +constexpr const char* set_name_method() { + return R"( + Set name for the AST node +)"; +} + +constexpr const char* is_ast_method() { + return R"( + Check if current node is of type ast.Ast +)"; +} + +constexpr const char* eval_method() { + return R"( + Return value of the ast node +)"; +} + +} // namespace docstring +} // namespace nmodl diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index aaa406f9ea..a8a2d4dc53 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -10,6 +10,7 @@ set(UTIL_SOURCE_FILES logger.hpp perf_stat.cpp perf_stat.hpp + string_utils.cpp string_utils.hpp table_data.cpp table_data.hpp diff --git a/src/nmodl/utils/string_utils.cpp b/src/nmodl/utils/string_utils.cpp new file mode 100644 index 0000000000..32406985ba --- /dev/null +++ b/src/nmodl/utils/string_utils.cpp @@ -0,0 +1,30 @@ +/************************************************************************* + * Copyright (C) 2018-2021 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#include "utils/string_utils.hpp" + +#include + +#include +#include + +namespace nmodl { +namespace stringutils { + +std::string to_string(double value, const std::string& format_spec) { + // double containing integer value + if (std::ceil(value) == value && + value < static_cast(std::numeric_limits::max()) && + value > static_cast(std::numeric_limits::min())) { + return std::to_string(static_cast(value)); + } + + // actual float value + return fmt::format(format_spec, value); +} + +} // namespace stringutils +} // namespace nmodl diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 8d81b05b58..5255a0e0e8 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -22,8 +22,6 @@ #include #include -#include - namespace nmodl { /// string utility functions namespace stringutils { @@ -135,17 +133,7 @@ static inline std::string tolower(std::string text) { * and testing/validation. To avoid this issue, we use to_string * for integer values and stringstream for the rest. */ -static inline std::string to_string(double value, const std::string& format_spec = "{:.16g}") { - // double containing integer value - if (std::ceil(value) == value && - value < static_cast(std::numeric_limits::max()) && - value > static_cast(std::numeric_limits::min())) { - return std::to_string(static_cast(value)); - } - - // actual float value - return fmt::format(format_spec, value); -} +std::string to_string(double value, const std::string& format_spec = "{:.16g}"); /** @} */ // end of utils From 50cae01ae94e83034e11320d5d542f4d7a47719f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 19 Nov 2021 10:29:33 +0100 Subject: [PATCH 402/871] Cast index of array to int (BlueBrain/nmodl#778) Sometimes variables are double, so cast them to int, to have valid c. Fix BlueBrain/nmodl#776 NMODL Repo SHA: BlueBrain/nmodl@9a7fab07f2b7461d465bc17b8d9151252e4b7779 --- src/nmodl/codegen/codegen_c_visitor.cpp | 4 ++++ src/nmodl/language/nmodl.yaml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 223e09c72f..2e268ff34f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -133,7 +133,9 @@ void CodegenCVisitor::visit_var_name(const VarName& node) { } if (index) { printer->add_text("["); + printer->add_text("static_cast("); index->accept(*this); + printer->add_text(")"); printer->add_text("]"); } } @@ -145,7 +147,9 @@ void CodegenCVisitor::visit_indexed_name(const IndexedName& node) { } node.get_name()->accept(*this); printer->add_text("["); + printer->add_text("static_cast("); node.get_length()->accept(*this); + printer->add_text(")"); printer->add_text("]"); } diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 0724f81e29..a71358f701 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -262,7 +262,7 @@ type: Identifier node_name: true - length: - brief: "legth of an array or index position" + brief: "length of an array or index position" type: Expression prefix: {value: "["} suffix: {value: "]"} @@ -271,7 +271,7 @@ If variable is declared as an array or when array element is accessed, it is stored in the ast as ast::IndexedName. For example, in below NMODL, construct `m[4]` is stored as ast::IndexedName with `m` as ast::IndexedName::name - and `4` as ast::IndexedName::legth. + and `4` as ast::IndexedName::length. \code STATE { From f85d1493e43b7c296e51e1c2c8bf2f07759a71df Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Nov 2021 18:02:43 +0100 Subject: [PATCH 403/871] Enable local rename by default as it is in mod2c (BlueBrain/nmodl#779) It has been tested that it works too for variable inside VERBATIM block Fix BlueBrain/nmodl#777 NMODL Repo SHA: BlueBrain/nmodl@8543cc710370c0520abd97def1bd420b1cc6b023 --- src/nmodl/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 2bcb3ebfcf..a8f9e804cb 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -117,7 +117,7 @@ int main(int argc, const char* argv[]) { bool localize_verbatim(false); /// true if local variables to be renamed - bool local_rename(false); + bool local_rename(true); /// true if inline even if verbatim block exist bool verbatim_inline(false); From 44217578f7b78a6219f52c69af4bdd1275c8374e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 26 Nov 2021 08:55:32 +0100 Subject: [PATCH 404/871] Add semantic analysis visitor (BlueBrain/nmodl#772) * Add new visitor to run semantic analysis related checks - Use log system instead of only throw - Add a new check: destructor blocks only in point process type mod file * Always run Semantic analysis * Added tests NMODL Repo SHA: BlueBrain/nmodl@4a67187535eb6d79d7ef74095dacd6434948a43d --- src/nmodl/main.cpp | 4 +- .../visitors/semantic_analysis_visitor.cpp | 44 +++++++++++++- .../visitors/semantic_analysis_visitor.hpp | 21 +++++-- .../unit/visitor/semantic_analysis.cpp | 57 ++++++++++++++++--- 4 files changed, 110 insertions(+), 16 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index a8f9e804cb..b36835144f 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -320,7 +320,9 @@ int main(int argc, const char* argv[]) { /// Check some rules that ast should follow { logger->info("Running semantic analysis visitor"); - SemanticAnalysisVisitor().visit_program(*ast); + if (SemanticAnalysisVisitor().check(*ast)) { + return 1; + } } /// construct symbol table diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 2e2528f7c2..eecfb74262 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -1,31 +1,69 @@ #include "visitors/semantic_analysis_visitor.hpp" #include "ast/function_block.hpp" #include "ast/procedure_block.hpp" +#include "ast/program.hpp" +#include "ast/suffix.hpp" #include "ast/table_statement.hpp" +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { namespace visitor { +bool SemanticAnalysisVisitor::check(const ast::Program& node) { + check_fail = false; + + /// <-- This code is for check 2 + const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX}); + if (!suffix_node.empty()) { + const auto& suffix = std::dynamic_pointer_cast(suffix_node[0]); + const auto& type = suffix->get_type()->get_node_name(); + is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL"); + } + /// --> + + visit_program(node); + return check_fail; +} + void SemanticAnalysisVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { + /// <-- This code is for check 1 in_procedure_function = true; one_arg_in_procedure_function = node.get_parameters().size() == 1; node.visit_children(*this); in_procedure_function = false; + /// --> } void SemanticAnalysisVisitor::visit_function_block(const ast::FunctionBlock& node) { + /// <-- This code is for check 1 in_procedure_function = true; one_arg_in_procedure_function = node.get_parameters().size() == 1; node.visit_children(*this); in_procedure_function = false; + /// --> } void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement&) { + /// <-- This code is for check 1 if (in_procedure_function && !one_arg_in_procedure_function) { - throw std::runtime_error( - "The procedure or function containing the TABLE statement should contains exactly one " - "argument."); + logger->critical( + "SemanticAnalysisVisitor :: The procedure or function containing the TABLE statement " + "should contains exactly one argument."); + check_fail = true; + } + /// --> +} + +void SemanticAnalysisVisitor::visit_destructor_block(const ast::DestructorBlock& node) { + /// <-- This code is for check 2 + if (!is_point_process) { + logger->warn( + "SemanticAnalysisVisitor :: This mod file is not point process but contains a " + "destructor."); + check_fail = true; } + /// --> } } // namespace visitor diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index b20c049fa5..729b8f3842 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -23,8 +23,9 @@ * * Current checks: * - * - Check that a function or a procedure containing a TABLE statement contains only one argument + * 1. Check that a function or a procedure containing a TABLE statement contains only one argument * (mandatory in mod2c). + * 2. Check that destructor blocks are only inside mod file that are point_process */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -34,19 +35,31 @@ namespace visitor { class SemanticAnalysisVisitor: public ConstAstVisitor { private: + bool check_fail = false; + /// true if the procedure or the function contains only one argument bool one_arg_in_procedure_function = false; /// true if we are in a procedure or a function block bool in_procedure_function = false; + /// true if the mod file is of type point process + bool is_point_process = false; - public: - SemanticAnalysisVisitor() = default; - + /// Store if we are in a procedure and if the arity of this is 1 void visit_procedure_block(const ast::ProcedureBlock& node) override; + /// Store if we are in a function and if the arity of this is 1 void visit_function_block(const ast::FunctionBlock& node) override; + /// Visit a table statement and check that the arity of the block were 1 void visit_table_statement(const ast::TableStatement& node) override; + + /// Visit destructor and check that the file is of type POINT_PROCESS or ARTIFICIAL_CELL + void visit_destructor_block(const ast::DestructorBlock& node) override; + + public: + SemanticAnalysisVisitor() = default; + + bool check(const ast::Program& node); }; /** \} */ // end of visitor_classes diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 9f7829894e..ca2f22044f 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -23,10 +23,10 @@ using nmodl::parser::NmodlDriver; // Procedure/Function inlining tests //============================================================================= -void run_semantic_analysis_visitor(const std::string& text) { +bool run_semantic_analysis_visitor(const std::string& text) { NmodlDriver driver; const auto& ast = driver.parse_string(text); - SemanticAnalysisVisitor().visit_program(*ast); + return SemanticAnalysisVisitor{}.check(*ast); } SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") { @@ -37,8 +37,8 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") { ainf = 1 } )"; - THEN("throw") { - REQUIRE_THROWS(run_semantic_analysis_visitor(nmodl_text)); + THEN("fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); } } GIVEN("Procedure with exactly one argument") { @@ -48,8 +48,8 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") { ainf = 1 } )"; - THEN("no throw") { - REQUIRE_NOTHROW(run_semantic_analysis_visitor(nmodl_text)); + THEN("pass") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); } } GIVEN("Procedure with less than one argument") { @@ -59,8 +59,49 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") { ainf = 1 } )"; - THEN("throw") { - REQUIRE_THROWS(run_semantic_analysis_visitor(nmodl_text)); + THEN("fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} + +SCENARIO("Destructor block", "[visitor][semantic_analysis]") { + GIVEN("A point-process mod file, with a destructor") { + std::string nmodl_text = R"( + DESTRUCTOR { : Destructor is before + } + + NEURON { + POINT_PROCESS test + } + )"; + THEN("pass") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("A artifial-cell mod file, with a destructor") { + std::string nmodl_text = R"( + NEURON { + ARTIFICIAL_CELL test + } + + DESTRUCTOR { + } + )"; + THEN("pass") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("A non point-process mod file, with a destructor") { + std::string nmodl_text = R"( + NEURON { + } + + DESTRUCTOR { + } + )"; + THEN("fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); } } } From 8f909a2f4b3b871f5842ed9121d1631efc754cd4 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 21 Dec 2021 16:53:50 +0100 Subject: [PATCH 405/871] Forbid PARAMETER if it is written and not a writable variable (BlueBrain/nmodl#775) NMODL Repo SHA: BlueBrain/nmodl@93322ba28622fe7f240036b6fb845c20df5775ef --- .../codegen/codegen_compatibility_visitor.cpp | 17 +++++++++++++++++ .../codegen/codegen_compatibility_visitor.hpp | 2 ++ src/nmodl/symtab/symbol.hpp | 8 ++++++++ 3 files changed, 27 insertions(+) diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index f2cecb668d..ef9b92d8df 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -18,6 +18,8 @@ using namespace fmt::literals; namespace nmodl { namespace codegen { +using symtab::syminfo::NmodlType; + const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( {{AstNodeType::MATCH_BLOCK, @@ -37,6 +39,7 @@ const std::map {AstNodeType::SOLVE_BLOCK, &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, + {AstNodeType::PARAM_ASSIGN, &CodegenCompatibilityVisitor::return_error_param_var}, {AstNodeType::POINTER_VAR, &CodegenCompatibilityVisitor::return_error_pointer}, {AstNodeType::BBCORE_POINTER_VAR, &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}}); @@ -73,6 +76,20 @@ std::string CodegenCompatibilityVisitor::return_error_global_var( return error_message_global_var.str(); } +std::string CodegenCompatibilityVisitor::return_error_param_var( + ast::Ast& node, + const std::shared_ptr& ast_node) { + auto param_assign = std::dynamic_pointer_cast(ast_node); + std::stringstream error_message_global_var; + auto symbol = node.get_symbol_table()->lookup(param_assign->get_node_name()); + if (!symbol->is_writable() && symbol->get_write_count() > 0) { + error_message_global_var + << "\"{}\" variable found at [{}] should be writable if it needs to be written\n"_format( + symbol->get_name(), symbol->get_token().position()); + } + return error_message_global_var.str(); +} + std::string CodegenCompatibilityVisitor::return_error_pointer( ast::Ast& node, const std::shared_ptr& ast_node) { diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index bbc7c32e67..a19940620c 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -134,6 +134,8 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \return std::string error std::string return_error_global_var(ast::Ast& node, const std::shared_ptr& ast_node); + std::string return_error_param_var(ast::Ast& node, const std::shared_ptr& ast_node); + /// Takes as parameter an std::shared_ptr node /// and returns a relative error with the name and the /// location of the pointer, as well as a suggestion to diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index a5502ca778..395ed38159 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -348,6 +348,14 @@ class Symbol { bool is_variable() const noexcept; std::string to_string() const; + + bool is_writable() const noexcept { + return has_any_property(syminfo::NmodlType::range_var) || + has_any_property(syminfo::NmodlType::write_ion_var) || + has_any_property(syminfo::NmodlType::assigned_definition) || + has_any_property(syminfo::NmodlType::state_var) || + has_any_property(syminfo::NmodlType::read_ion_var); + } }; /** @} */ // end of sym_tab From 819e41f28a2876e3433b311b2570211590843998 Mon Sep 17 00:00:00 2001 From: WeinaJi Date: Tue, 21 Dec 2021 17:39:43 +0100 Subject: [PATCH 406/871] Return var name with index (BlueBrain/nmodl#758) * Add function in visitor_utils to return node name with index for IndexedName node * Add a visitor to visit IndexedName node and get the dependencies of DiffEqExpression * Add unit test and IndexedNameVistor Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@885e4a02b6e8fa575a6f5228d3d3626fe90561f4 --- src/nmodl/main.cpp | 1 + src/nmodl/visitors/CMakeLists.txt | 2 + src/nmodl/visitors/indexedname_visitor.cpp | 38 +++++++++ src/nmodl/visitors/indexedname_visitor.hpp | 68 ++++++++++++++++ src/nmodl/visitors/visitor_utils.cpp | 22 ++++- src/nmodl/visitors/visitor_utils.hpp | 5 ++ test/nmodl/transpiler/unit/CMakeLists.txt | 3 +- .../transpiler/unit/visitor/node_index.cpp | 80 +++++++++++++++++++ 8 files changed, 215 insertions(+), 4 deletions(-) create mode 100644 src/nmodl/visitors/indexedname_visitor.cpp create mode 100644 src/nmodl/visitors/indexedname_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/node_index.cpp diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index b36835144f..0e27e6a349 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -26,6 +26,7 @@ #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/global_var_visitor.hpp" +#include "visitors/indexedname_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/ispc_rename_visitor.hpp" #include "visitors/json_visitor.hpp" diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 9da6e048c0..edc5d525c6 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -12,6 +12,8 @@ set(VISITOR_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/indexedname_visitor.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/indexedname_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.hpp diff --git a/src/nmodl/visitors/indexedname_visitor.cpp b/src/nmodl/visitors/indexedname_visitor.cpp new file mode 100644 index 0000000000..8878e49bc8 --- /dev/null +++ b/src/nmodl/visitors/indexedname_visitor.cpp @@ -0,0 +1,38 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include "visitors/indexedname_visitor.hpp" +#include "ast/binary_expression.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +void IndexedNameVisitor::visit_indexed_name(ast::IndexedName& node) { + indexed_name = nmodl::get_indexed_name(node); +} + +void IndexedNameVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { + node.visit_children(*this); + const auto& bin_exp = std::static_pointer_cast(node.get_expression()); + auto lhs = bin_exp->get_lhs(); + auto rhs = bin_exp->get_rhs(); + dependencies = nmodl::statement_dependencies(lhs, rhs); +} + +void IndexedNameVisitor::visit_program(ast::Program& node) { + node.visit_children(*this); +} +std::pair> IndexedNameVisitor::get_dependencies() { + return dependencies; +} +std::string IndexedNameVisitor::get_indexed_name() { + return indexed_name; +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/indexedname_visitor.hpp b/src/nmodl/visitors/indexedname_visitor.hpp new file mode 100644 index 0000000000..8efec7bfcd --- /dev/null +++ b/src/nmodl/visitors/indexedname_visitor.hpp @@ -0,0 +1,68 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::IndexedNameVisitor + */ + +#include +#include + +#include "ast/diff_eq_expression.hpp" +#include "ast/indexed_name.hpp" +#include "ast/program.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * \addtogroup visitor_classes + * \{ + */ + +/** + * \class IndexedNameVisitor + * \brief Get node name with indexed for the IndexedName node and + * the dependencies of DiffEqExpression node + */ +class IndexedNameVisitor: public AstVisitor { + private: + std::string indexed_name; + std::pair> dependencies; + + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor + IndexedNameVisitor() = default; + + /// \} + + /// Get node name with index for the IndexedName node + void visit_indexed_name(ast::IndexedName& node) override; + + /// Get dependencies for the DiffEqExpression node + void visit_diff_eq_expression(ast::DiffEqExpression& node) override; + + void visit_program(ast::Program& node) override; + + /// get the attribute indexed_name + std::string get_indexed_name(); + + /// get the attribute dependencies + std::pair> get_dependencies(); +}; + +/** \} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index c5f33a251d..903fa6abef 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -18,6 +18,7 @@ #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" +#include namespace nmodl { namespace visitor { @@ -244,9 +245,9 @@ std::pair> statement_dependencies( return {key, out}; } - key = to_nmodl(lhs); - key.erase(std::remove(key.begin(), key.end(), '\''), - key.end()); // we want to match derivatives and variables + const auto& lhs_var_name = std::dynamic_pointer_cast(lhs); + key = get_full_var_name(*lhs_var_name); + visitor::AstLookupVisitor lookup_visitor; lookup_visitor.lookup(*rhs, ast::AstNodeType::VAR_NAME); auto rhs_nodes = lookup_visitor.get_nodes(); @@ -258,4 +259,19 @@ std::pair> statement_dependencies( return {key, out}; } +std::string get_indexed_name(const ast::IndexedName& node) { + return fmt::format("{}[{}]", node.get_node_name(), to_nmodl(node.get_length())); +} + +std::string get_full_var_name(const ast::VarName& node) { + std::string full_var_name; + if (node.get_name()->is_indexed_name()) { + auto index_name_node = std::dynamic_pointer_cast(node.get_name()); + full_var_name = get_indexed_name(*index_name_node); + } else { + full_var_name = node.get_node_name(); + } + return full_var_name; +} + } // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 01de313375..f190bf9e93 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -124,5 +124,10 @@ std::pair> statement_dependencies( const std::shared_ptr& lhs, const std::shared_ptr& rhs); +/// Given a Indexed node, return the name with index +std::string get_indexed_name(const ast::IndexedName& node); + +/// Given a VarName node, return the full var name including index +std::string get_full_var_name(const ast::VarName& node); } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 9f8d9e999a..457f35a288 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -56,7 +56,8 @@ add_executable( visitor/sympy_solver.cpp visitor/units.cpp visitor/var_usage.cpp - visitor/verbatim.cpp) + visitor/verbatim.cpp + visitor/node_index.cpp) add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${NEWTON_SOLVER_SOURCE_FILES}) diff --git a/test/nmodl/transpiler/unit/visitor/node_index.cpp b/test/nmodl/transpiler/unit/visitor/node_index.cpp new file mode 100644 index 0000000000..6112a0404c --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/node_index.cpp @@ -0,0 +1,80 @@ +/************************************************************************* + * Copyright (C) 2018-2019 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/indexedname_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace nmodl; +using namespace visitor; + +//============================================================================= +// Get the indexed node name and the dependencies of differential equations +//============================================================================= +std::pair>> +get_indexedname_dependencies(ast::Program& node) { + IndexedNameVisitor testvisitor; + testvisitor.visit_program(node); + return std::make_pair(testvisitor.get_indexed_name(), testvisitor.get_dependencies()); +} + +SCENARIO("Get node name with index TestVisitor", "[visitor][node_index]") { + auto to_ast = [](const std::string& text) { + parser::NmodlDriver driver; + return driver.parse_string(text); + }; + + GIVEN("A simple NMODL block") { + std::string nmodl_text_a = R"( + STATE { + m[1] + } + BREAKPOINT { + SOLVE states METHOD euler + } + DERIVATIVE states { + m'[0] = mInf/mTau + } + )"; + std::string nmodl_text_b = R"( + BREAKPOINT { + SOLVE states STEADYSTATE sparse + } + DERIVATIVE states { + m' = m + h + } + )"; + + WHEN("Get node name with index") { + THEN("Get node name with index") { + auto ast = to_ast(nmodl_text_a); + std::unordered_set vars{"mInf", "mTau"}; + std::string var("m[0]"); + auto expect = std::make_pair(var, vars); + auto result_name = get_indexedname_dependencies(*ast).first; + auto result_dependencies = get_indexedname_dependencies(*ast).second; + REQUIRE(result_name == var); + REQUIRE(result_dependencies.first == expect.first); + REQUIRE(result_dependencies.second == expect.second); + } + THEN("Get dependencies") { + auto ast = to_ast(nmodl_text_b); + std::unordered_set vars{"m", "h"}; + std::string var("m"); + auto expect = std::make_pair(var, vars); + auto result_name = get_indexedname_dependencies(*ast).first; + auto result_dependencies = get_indexedname_dependencies(*ast).second; + REQUIRE(result_dependencies.first == expect.first); + REQUIRE(result_dependencies.second == expect.second); + } + } + } +} From 3f74c5e10d651160e6b6e65e00bbdb8534d05ffb Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Wed, 22 Dec 2021 17:59:56 +0100 Subject: [PATCH 407/871] Integrate changes from NERSC GPU hackathon. (BlueBrain/nmodl#783) * Tweak NVHPC warning suppressions. * Emit nrn_pragma_{acc,omp}(...) macros. (BlueBrain/nmodl#780) * Use cnrn_target_ wrappers instead of acc_ API. * GPU code generation improvements (BlueBrain/nmodl#782) * Fix NVHPC + OpenMP ~ OpenACC compilation (BlueBrain/nmodl#784) * Add EIGEN_DEVICE_FUNC to header to fix a compilation warning. * Fudge partialPivLu for NVHPC + OpenMP without OpenACC. * Transfer ml only if cell is not artificial. (BlueBrain/nmodl#785) * Update Eigen to include OpenMP fixes. (BlueBrain/nmodl#787, BlueBrain/nmodl#789) Co-authored-by: Nicolas Cornu Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@46f8baf2bbeaa0d21559d6306ec37b94c601f1ee --- cmake/nmodl/CMakeLists.txt | 5 -- cmake/nmodl/CompilerHelper.cmake | 7 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 73 ++++++++++++------- src/nmodl/codegen/codegen_acc_visitor.hpp | 3 +- src/nmodl/codegen/codegen_c_visitor.cpp | 24 +++--- src/nmodl/codegen/codegen_c_visitor.hpp | 14 +++- src/nmodl/solver/newton/newton.hpp | 6 +- .../solver/partial_piv_lu/partial_piv_lu.cu | 22 +++++- .../solver/partial_piv_lu/partial_piv_lu.h | 44 +++++++++-- .../unit/codegen/codegen_c_visitor.cpp | 6 +- 10 files changed, 143 insertions(+), 61 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index ef79c5c719..88b6cb07f8 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -92,11 +92,6 @@ add_external_project(eigen OFF) add_subdirectory(${THIRD_PARTY_DIRECTORY}/fmt EXCLUDE_FROM_ALL) set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) -if(NMODL_PGI_COMPILER) - # "format.cc", warning #128-D: loop is not reachable - set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/ext/fmt/src/format.cc - PROPERTIES COMPILE_FLAGS "--diag_suppress=128") -endif() include_directories( SYSTEM ${THIRD_PARTY_DIRECTORY} ${THIRD_PARTY_DIRECTORY}/catch/include ${THIRD_PARTY_DIRECTORY}/fmt/include ${THIRD_PARTY_DIRECTORY}/spdlog/include diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 0e8960cfd8..86d31762e7 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -19,17 +19,22 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") # ~~~ # "ext/spdlog/include/spdlog/fmt/fmt.h", warning #1-D: last line of file ends without a newline # "ext/fmt/include/fmt/format.h", warning #111-D: statement is unreachable + # "ext/fmt/include/fmt/format.h", warning #128-D: loop is not reachable + # "ext/spdlog/include/spdlog/fmt/bundled/format.h", warning #185-D: dynamic initialization in unreachable code # "ext/json/json.hpp", warning #186-D: pointless comparison of unsigned integer with zero # "src/ast/all.hpp", warning #998-D: function "..." is hidden by "..." -- virtual function override intended? # "ext/spdlog/include/spdlog/fmt/bundled/format.h", warning #1098-D: unknown attribute "fallthrough" # "ext/pybind11/include/pybind11/detail/common.h", warning #1626-D: routine is both "inline" and "noinline" + # "ext/spdlog/include/spdlog/fmt/bundled/core.h", warning #1676-D: unrecognized GCC pragma # ~~~ # The following warnings do not seem to be suppressible with --diag_suppress: # ~~~ # "src/codegen/codegen_cuda_visitor.cpp", NVC++-W-0277-Cannot inline function - data type mismatch # "nvc++IkWUbMugiSgNH.s: Warning: stand-alone `data16' prefix # ~~~ - set(NMODL_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=1,111,186,998,1098,1626) + # The situation may be better once https://github.com/fmtlib/fmt/pull/2582 is included in a + # release. + set(NMODL_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=1,111,128,185,186,998,1098,1626,1676) # There are a few more warnings produced by the unit test infrastructure. # ~~~ # "test/unit/visitor/constant_folder.cpp", warning #177-D: variable "..." was declared but never referenced diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index e85175ee8b..a12843bc0c 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -51,14 +51,18 @@ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType ty } present_clause << ')'; printer->add_line( - "#pragma acc parallel loop {} async(nt->stream_id) if(nt->compute_gpu)"_format( + "nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))"_format( present_clause.str())); + printer->add_line( + "nrn_pragma_omp(target teams distribute parallel for is_device_ptr(inst) " + "if(nt->compute_gpu))"); } void CodegenAccVisitor::print_atomic_reduction_pragma() { if (!info.artificial_cell) { - printer->add_line("#pragma acc atomic update"); + printer->add_line("nrn_pragma_acc(atomic update)"); + printer->add_line("nrn_pragma_omp(atomic update)"); } } @@ -72,9 +76,8 @@ void CodegenAccVisitor::print_backend_includes() { printer->add_line("#undef DISABLE_OPENACC"); printer->add_line("#define DISABLE_OPENACC"); } else { - printer->add_line("#include "); + printer->add_line("#include "); printer->add_line("#include "); - printer->add_line("#include "); } if (info.eigen_linear_solver_exist && std::accumulate(info.state_vars.begin(), @@ -144,7 +147,7 @@ void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, if (N <= 4) { printer->add_line("{0} = {1}.inverse()*{2};"_format(Xm, Jm, Fm)); } else { - printer->add_line("{0} = partialPivLu<{1}>({2}, {3});"_format(Xm, N, Jm, Fm)); + printer->add_line("{0} = partialPivLu{1}({2}, {3});"_format(Xm, N, Jm, Fm)); } } @@ -166,7 +169,7 @@ void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { if (!info.artificial_cell) { auto global_variable = "{}_global"_format(info.mod_suffix); printer->add_line( - "#pragma acc data present(nt, ml, {}) if(nt->compute_gpu)"_format(global_variable)); + "nrn_pragma_acc(data present(nt, ml, {}) if(nt->compute_gpu))"_format(global_variable)); printer->add_line("{"); printer->increase_indent(); } @@ -246,16 +249,23 @@ bool CodegenAccVisitor::nrn_cur_reduction_loop_required() { } -void CodegenAccVisitor::print_global_variable_device_create_annotation() { +void CodegenAccVisitor::print_global_variable_device_create_annotation_pre() { if (!info.artificial_cell) { - printer->add_line("#pragma acc declare create ({}_global)"_format(info.mod_suffix)); + printer->add_line("nrn_pragma_omp(declare target)"); } } +void CodegenAccVisitor::print_global_variable_device_create_annotation_post() { + if (!info.artificial_cell) { + printer->add_line("nrn_pragma_acc(declare create ({}_global))"_format(info.mod_suffix)); + printer->add_line("nrn_pragma_omp(end declare target)"); + } +} void CodegenAccVisitor::print_global_variable_device_update_annotation() { if (!info.artificial_cell) { - printer->add_line("#pragma acc update device ({}_global)"_format(info.mod_suffix)); + printer->add_line("nrn_pragma_acc(update device ({}_global))"_format(info.mod_suffix)); + printer->add_line("nrn_pragma_omp(target update to({}_global))"_format(info.mod_suffix)); } } @@ -265,53 +275,59 @@ std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& va if (info.artificial_cell) { return variable; } - return "reinterpret_cast<{}>( nt->compute_gpu ? acc_deviceptr({}) : {} )"_format(type, - variable, - variable); + return "nt->compute_gpu ? cnrn_target_deviceptr({}) : {}"_format(variable, variable); } void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { int list_num = info.derivimplicit_list_num; - printer->add_line("if (nt->compute_gpu) {"); - printer->add_line(" auto device_vec = static_cast(acc_copyin(vec, vec_size));"); - printer->add_line(" auto device_ns = static_cast(acc_deviceptr(*ns));"); - printer->add_line(" auto device_thread = static_cast(acc_deviceptr(thread));"); + printer->start_block("if(nt->compute_gpu)"); + printer->add_line("double* device_vec = cnrn_target_copyin(vec, vec_size / sizeof(double));"); + printer->add_line("void* device_ns = cnrn_target_deviceptr(*ns);"); + printer->add_line("ThreadDatum* device_thread = cnrn_target_deviceptr(thread);"); printer->add_line( - " acc_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns, sizeof(void*));"_format( + "cnrn_target_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns);"_format( info.thread_data_index - 1)); printer->add_line( - " acc_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec, sizeof(double*));"_format( + "cnrn_target_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec);"_format( list_num)); - printer->add_line("}"); + printer->end_block(1); } void CodegenAccVisitor::print_instance_variable_transfer_to_device() const { - printer->add_line("if (nt->compute_gpu) {"); - printer->add_line(" auto dml = (Memb_list*) acc_deviceptr(ml);"); - printer->add_line(" acc_memcpy_to_device(&(dml->instance), &inst, sizeof(void*));"); - printer->add_line("}"); + if (!info.artificial_cell) { + printer->start_block("if(nt->compute_gpu)"); + printer->add_line("Memb_list* dml = cnrn_target_deviceptr(ml);"); + printer->add_line("cnrn_target_memcpy_to_device(&(dml->instance), &(ml->instance));"); + printer->end_block(1); + } } void CodegenAccVisitor::print_deriv_advance_flag_transfer_to_device() const { - printer->add_line("#pragma acc update device (deriv_advance_flag) if (nt->compute_gpu)"); + printer->add_line("nrn_pragma_acc(update device (deriv_advance_flag) if(nt->compute_gpu))"); + printer->add_line("nrn_pragma_omp(target update to(deriv_advance_flag) if(nt->compute_gpu))"); } void CodegenAccVisitor::print_device_atomic_capture_annotation() const { - printer->add_line("#pragma acc atomic capture"); + printer->add_line("nrn_pragma_acc(atomic capture)"); + printer->add_line("nrn_pragma_omp(atomic capture)"); } void CodegenAccVisitor::print_device_stream_wait() const { - printer->add_line("#pragma acc wait(nt->stream_id)"); + printer->start_block("if(nt->compute_gpu)"); + printer->add_line("nrn_pragma_acc(wait(nt->stream_id))"); + printer->add_line("nrn_pragma_omp(taskwait)"); + printer->end_block(1); } void CodegenAccVisitor::print_net_send_buf_count_update_to_host() const { - printer->add_line("#pragma acc update self(nsb->_cnt) if(nt->compute_gpu)"); + printer->add_line("nrn_pragma_acc(update self(nsb->_cnt) if(nt->compute_gpu))"); + printer->add_line("nrn_pragma_omp(target update from(nsb->_cnt) if(nt->compute_gpu))"); } @@ -325,7 +341,8 @@ void CodegenAccVisitor::print_net_send_buf_update_to_host() const { void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { - printer->add_line("#pragma acc update device(nsb->_cnt) if (nt->compute_gpu)"); + printer->add_line("nrn_pragma_acc(update device(nsb->_cnt) if(nt->compute_gpu))"); + printer->add_line("nrn_pragma_omp(target update to(nsb->_cnt) if(nt->compute_gpu))"); } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 7073a89f83..ed37c7fd52 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -88,7 +88,8 @@ class CodegenAccVisitor: public CodegenCVisitor { /// create global variable on the device - void print_global_variable_device_create_annotation() override; + void print_global_variable_device_create_annotation_pre() override; + void print_global_variable_device_create_annotation_post() override; /// update global variable from host to the device void print_global_variable_device_update_annotation() override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 2e268ff34f..58c7dd16bf 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2636,10 +2636,11 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { printer->add_newline(1); printer->add_line("/** holds object of global variable */"); + print_global_variable_device_create_annotation_pre(); print_global_var_struct_decl(); // create copy on the device - print_global_variable_device_create_annotation(); + print_global_variable_device_create_annotation_post(); } @@ -3037,7 +3038,11 @@ void CodegenCVisitor::print_ion_variable() { } -void CodegenCVisitor::print_global_variable_device_create_annotation() { +void CodegenCVisitor::print_global_variable_device_create_annotation_pre() { + // nothing for cpu +} + +void CodegenCVisitor::print_global_variable_device_create_annotation_post() { // nothing for cpu } @@ -3319,7 +3324,7 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("inst->{} = {};"_format(name, device_variable)); } - printer->add_line("ml->instance = (void*) inst;"); + printer->add_line("ml->instance = inst;"); print_instance_variable_transfer_to_device(); printer->end_block(3); @@ -3443,14 +3448,13 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_deriv_advance_flag_transfer_to_device(); printer->add_line("auto ns = newtonspace{}(thread);"_format(list_num)); printer->add_line("auto& th = thread[dith{}()];"_format(list_num)); - - printer->add_line("if (*ns == nullptr) {"); - printer->add_line(" int vec_size = 2*{}*pnodecount*sizeof(double);"_format(nequation)); - printer->add_line(" double* vec = makevector(vec_size);"_format(nequation)); - printer->add_line(" th.pval = vec;"_format(list_num)); - printer->add_line(" *ns = nrn_cons_newtonspace({}, pnodecount);"_format(nequation)); + printer->start_block("if (*ns == nullptr)"); + printer->add_line("int vec_size = 2*{}*pnodecount*sizeof(double);"_format(nequation)); + printer->add_line("double* vec = makevector(vec_size);"_format(nequation)); + printer->add_line("th.pval = vec;"_format(list_num)); + printer->add_line("*ns = nrn_cons_newtonspace({}, pnodecount);"_format(nequation)); print_newtonspace_transfer_to_device(); - printer->add_line("}"); + printer->end_block(1); // clang-format on } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 285b3ee5db..ef97c151e2 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1183,12 +1183,22 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** - * Print the pragma annotation to create global variables on the device + * Print the pragma annotation needed before a global variable that must be + * created on the device. This always comes before a matching call to + * print_global_variable_device_create_annotation_post. * * \note This is not used for the C backend */ - virtual void print_global_variable_device_create_annotation(); + virtual void print_global_variable_device_create_annotation_pre(); + /** + * Print the pragma annotation needed after a global variables that must be + * created on the device. This always comes after a matching call to + * print_global_variable_device_create_annotation_pre. + * + * \note This is not used for the C backend + */ + virtual void print_global_variable_device_create_annotation_post(); /** * Print the pragma annotation to update global variables from host to the device diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 274307dd33..ec90fc441a 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -15,7 +15,7 @@ * \brief Implementation of Newton method for solving system of non-linear equations */ -#if defined(_OPENACC) && !defined(DISABLE_OPENACC) +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) #include "partial_piv_lu/partial_piv_lu.h" #endif @@ -73,7 +73,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, // we have converged: return iteration count return iter; } -#if defined(_OPENACC) && !defined(DISABLE_OPENACC) +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) X -= partialPivLu(J, F); #else // update X use in-place LU decomposition of J with partial pivoting @@ -150,7 +150,7 @@ EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix& // restore X X[i] += dX; } -#if defined(_OPENACC) && !defined(DISABLE_OPENACC) +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) X -= partialPivLu(J, F); #else // update X use in-place LU decomposition of J with partial pivoting diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu index 27dd634cd3..c043a710e4 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu @@ -1,14 +1,13 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ - #include "partial_piv_lu/partial_piv_lu.h" template -EIGEN_DEVICE_FUNC +EIGEN_DEVICE_FUNC VecType partialPivLu(const MatType& A, const VecType& b) { return A.partialPivLu().solve(b); @@ -32,5 +31,22 @@ template EIGEN_DEVICE_FUNC VecType<14> partialPivLu<14>(const MatType<14>& A, co template EIGEN_DEVICE_FUNC VecType<15> partialPivLu<15>(const MatType<15>& A, const VecType<15>& b); template EIGEN_DEVICE_FUNC VecType<16> partialPivLu<16>(const MatType<16>& A, const VecType<16>& b); +EIGEN_DEVICE_FUNC VecType<1> partialPivLu1(const MatType<1>& A, const VecType<1>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<2> partialPivLu2(const MatType<2>& A, const VecType<2>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<3> partialPivLu3(const MatType<3>& A, const VecType<3>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<4> partialPivLu4(const MatType<4>& A, const VecType<4>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<5> partialPivLu5(const MatType<5>& A, const VecType<5>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<6> partialPivLu6(const MatType<6>& A, const VecType<6>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<7> partialPivLu7(const MatType<7>& A, const VecType<7>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<8> partialPivLu8(const MatType<8>& A, const VecType<8>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<9> partialPivLu9(const MatType<9>& A, const VecType<9>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<10> partialPivLu10(const MatType<10>& A, const VecType<10>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<11> partialPivLu11(const MatType<11>& A, const VecType<11>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<12> partialPivLu12(const MatType<12>& A, const VecType<12>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<13> partialPivLu13(const MatType<13>& A, const VecType<13>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<14> partialPivLu14(const MatType<14>& A, const VecType<14>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<15> partialPivLu15(const MatType<15>& A, const VecType<15>& b) { return A.partialPivLu().solve(b); } +EIGEN_DEVICE_FUNC VecType<16> partialPivLu16(const MatType<16>& A, const VecType<16>& b) { return A.partialPivLu().solve(b); } + // Currently there is an issue in Eigen (GPU-branch) for matrices 17x17 and above. // ToDo: Check in a future release if this issue is resolved! diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h index 6454056c67..1af9d5ad4d 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2021 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. @@ -7,6 +7,8 @@ #pragma once +#include "coreneuron/utils/offload.hpp" + #include "Eigen/Dense" #include "Eigen/LU" @@ -21,8 +23,40 @@ using VecType = Eigen::Matrix; // them with __device__ & acc routine tokens), which allows us to eventually call them from OpenACC. // Calling these functions from CUDA kernels presents no issue ... -#if defined(_OPENACC) && !defined(DISABLE_OPENACC) -#pragma acc routine seq -#endif +nrn_pragma_omp(declare target) +nrn_pragma_acc(routine seq) template -VecType partialPivLu(const MatType&, const VecType&); +EIGEN_DEVICE_FUNC VecType partialPivLu(const MatType&, const VecType&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<1> partialPivLu1(const MatType<1>&, const VecType<1>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<2> partialPivLu2(const MatType<2>&, const VecType<2>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<3> partialPivLu3(const MatType<3>&, const VecType<3>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<4> partialPivLu4(const MatType<4>&, const VecType<4>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<5> partialPivLu5(const MatType<5>&, const VecType<5>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<6> partialPivLu6(const MatType<6>&, const VecType<6>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<7> partialPivLu7(const MatType<7>&, const VecType<7>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<8> partialPivLu8(const MatType<8>&, const VecType<8>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<9> partialPivLu9(const MatType<9>&, const VecType<9>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<10> partialPivLu10(const MatType<10>&, const VecType<10>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<11> partialPivLu11(const MatType<11>&, const VecType<11>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<12> partialPivLu12(const MatType<12>&, const VecType<12>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<13> partialPivLu13(const MatType<13>&, const VecType<13>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<14> partialPivLu14(const MatType<14>&, const VecType<14>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<15> partialPivLu15(const MatType<15>&, const VecType<15>&); +nrn_pragma_acc(routine seq) +EIGEN_DEVICE_FUNC VecType<16> partialPivLu16(const MatType<16>&, const VecType<16>&); +nrn_pragma_omp(end declare target) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index ff8011e0be..e9ff1c484d 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -93,7 +93,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_cao = nt->_data; inst->ion_ica = nt->_data; inst->ion_dicadv = nt->_data; - ml->instance = (void*) inst; + ml->instance = inst; } )"; auto expected = reindent_text(generated_code); @@ -137,7 +137,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->v_unused = ml->data+4*pnodecount; inst->ion_cai = nt->_data; inst->ion_cao = nt->_data; - ml->instance = (void*) inst; + ml->instance = inst; } )"; @@ -212,7 +212,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_ilca = nt->_data; inst->ion_elca = nt->_data; inst->style_lca = ml->pdata; - ml->instance = (void*) inst; + ml->instance = inst; } )"; From 71a46690a9be37a29e64fe2fba4ef06ef77e0726 Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 23 Dec 2021 05:58:52 +0100 Subject: [PATCH 408/871] Add acknoledgments to README.md and include AUTHORS.txt (BlueBrain/nmodl#790) NMODL Repo SHA: BlueBrain/nmodl@d2e7295a7e9cce5321c696446201b77a274346ba --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 29ede53a39..970a78b50f 100644 --- a/README.md +++ b/README.md @@ -303,10 +303,9 @@ The benchmarks used to test the performance and parsing capabilities of NMODL Fr * [NMODL Benchmark](https://github.com/BlueBrain/nmodlbench) * [NMODL Database](https://github.com/BlueBrain/nmodldb) -### Authors -See [contributors](https://github.com/BlueBrain/nmodl/graphs/contributors). +## Funding & Acknowledgment -### Funding +The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government's ETH Board of the Swiss Federal Institutes of Technology. In addition, the development was supported by funding from the National Institutes of Health (NIH) under the Grant Number R01NS11613 (Yale University) and the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 785907 (Human Brain Project SGA2). -This work has been funded by the EPFL Blue Brain Project (funded by the Swiss ETH board), NIH grant number R01NS11613 (Yale University) and partially funded by the European Union's Horizon 2020 Framework Programme for Research and Innovation under Grant Agreement number 785907 (Human Brain Project SGA2). +Copyright © 2017-2021 Blue Brain Project/EPFL From e3c56adf8c46686ea83b7647d6808ce38481ff8d Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 23 Dec 2021 15:47:17 +0100 Subject: [PATCH 409/871] Bug fix: Parameter updated in VERBATIM block shouldn't be const (BlueBrain/nmodl#791) * if PARAMETER variable of RANGE type is updated in VERBATIM block then it's write_count could be 0 even though it's updated in VERBATIM block - such variable can not be declared as `const` instance variable - one such example is https://github.com/nrnhines/tqperf/blob/master/mod/invlfire.mod * Codegen helper visitor now keep track of all symbols that are used in all verbatim blocks and the codegen visitor check this list before deciding if variable can be const or not. - note that variable could be read-only in verbatim block but currently we don't have robut C code analysis capability and it's not worth (yet) * this issue was encountered in https://github.com/BlueBrain/CoreNeuron/pull/701 * test update: keep ast node, do not return codegen_c_visitor instance with local AST NMODL Repo SHA: BlueBrain/nmodl@3e960d7d9e6db1e4f74a1c7fb6b773a6a3cd593c --- src/nmodl/codegen/codegen_c_visitor.cpp | 8 ++- src/nmodl/codegen/codegen_helper_visitor.cpp | 20 +++++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 4 ++ .../unit/codegen/codegen_c_visitor.cpp | 57 +++++++++++++++++-- 5 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 58c7dd16bf..fd3986c120 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -746,8 +746,12 @@ bool CodegenCVisitor::is_constant_variable(const std::string& name) const { // per mechanism ion variables needs to be updated from neuron/coreneuron values if (info.is_ion_variable(name)) { is_constant = false; - } else if (symbol->has_any_property(NmodlType::param_assign) && - symbol->get_write_count() == 0) { + } + // for parameter variable to be const, make sure it's write count is 0 + // and it's not used in the verbatim block + else if (symbol->has_any_property(NmodlType::param_assign) && + info.variables_in_verbatim.find(name) == info.variables_in_verbatim.end() && + symbol->get_write_count() == 0) { is_constant = true; } } diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 5d7b28578a..49aea037b0 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -12,6 +12,7 @@ #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" +#include "parser/c11_driver.hpp" #include "visitors/visitor_utils.hpp" @@ -723,5 +724,24 @@ void CodegenHelperVisitor::visit_update_dt(const ast::UpdateDt& node) { info.changed_dt = node.get_value()->eval(); } +/// visit verbatim block and find all symbols used +void CodegenHelperVisitor::visit_verbatim(const Verbatim& node) { + const auto& text = node.get_statement()->eval(); + // use C parser to get all tokens + parser::CDriver driver; + driver.scan_string(text); + const auto& tokens = driver.all_tokens(); + + // check if the token exist in the symbol table + for (auto& token: tokens) { + if (info.variables_in_verbatim.find(token) == info.variables_in_verbatim.end()) { + auto symbol = psymtab->lookup(token); + if (symbol != nullptr) { + info.variables_in_verbatim.insert(token); + } + } + } +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 9ad6bd69d1..a2fe68d5cd 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -111,6 +111,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_discrete_block(const ast::DiscreteBlock& node) override; void visit_partial_block(const ast::PartialBlock& node) override; void visit_update_dt(const ast::UpdateDt& node) override; + void visit_verbatim(const ast::Verbatim& node) override; }; /** @} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 2d89580578..978ad11efd 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -13,6 +13,7 @@ */ #include +#include #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" @@ -363,6 +364,9 @@ struct CodegenInfo { /// all watch statements std::vector watch_statements; + /// all variables/symbols used in the verbatim block + std::unordered_set variables_in_verbatim; + /// true if eigen newton solver is used bool eigen_newton_solver_exist = false; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index e9ff1c484d..310525c961 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -9,6 +9,7 @@ #include "ast/program.hpp" #include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/symtab_visitor.hpp" @@ -21,11 +22,9 @@ using nmodl::parser::NmodlDriver; using nmodl::test_utils::reindent_text; /// Helper for creating C codegen visitor -std::shared_ptr create_c_visitor(const std::string& text, std::stringstream& ss) { - /// parse mod file and create AST - NmodlDriver driver; - const auto& ast = driver.parse_string(text); - +std::shared_ptr create_c_visitor(const std::shared_ptr& ast, + const std::string& text, + std::stringstream& ss) { /// construct symbol table SymtabVisitor().visit_program(*ast); @@ -37,8 +36,9 @@ std::shared_ptr create_c_visitor(const std::string& text, std:: /// print instance structure for testing purpose std::string get_instance_var_setup_function(std::string& nmodl_text) { + const auto& ast = NmodlDriver().parse_string(nmodl_text); std::stringstream ss; - auto cvisitor = create_c_visitor(nmodl_text, ss); + auto cvisitor = create_c_visitor(ast, nmodl_text, ss); cvisitor->print_instance_variable_setup(); return reindent_text(ss.str()); } @@ -222,3 +222,48 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { } } } + +SCENARIO("Check parameter constness with VERBATIM block", + "[codegen][verbatim_variable_constness]") { + GIVEN("A mod file containing parameter range variables that are updated in VERBATIM block") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX IntervalFire + RANGE invl, burst_start + } + PARAMETER { + invl = 10 (ms) <1e-9,1e9> + burst_start = 0 (ms) + } + INITIAL { + LOCAL temp + : as invl is used in verbatim, it shouldn't be treated as const + VERBATIM + invl = 11 + ENDVERBATIM + : burst_start time is read-only and hence can be const + temp = burst_start + } + )"; + + THEN("Variable used in VERBATIM shouldn't be marked as const") { + std::stringstream ss; + + /// parse mod file & print mechanism structure + const auto& ast = NmodlDriver().parse_string(nmodl_text); + auto cvisitor = create_c_visitor(ast, nmodl_text, ss); + cvisitor->print_mechanism_range_var_structure(); + + std::string expected_code = R"( + /** all mechanism instance variables */ + struct IntervalFire_Instance { + double* __restrict__ invl; + const double* __restrict__ burst_start; + double* __restrict__ v_unused; + }; + )"; + + REQUIRE(reindent_text(ss.str()) == reindent_text(expected_code)); + } + } +} From f63e458b2c313840fbfe454cd0aa00a99879b537 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 6 Jan 2022 10:44:19 +0100 Subject: [PATCH 410/871] Sort generated #includes. (BlueBrain/nmodl#792) This improves reproducibility and ccache performance. NMODL Repo SHA: BlueBrain/nmodl@f6260ccfc6cd4422da14c42d18701210bf20b742 --- src/nmodl/language/nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 77735b470f..bbbd983b01 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -589,7 +589,7 @@ def cpp_header_deps(self): for child in self.children: if child.is_ptr_excluded_node or child.is_vector: dependent_classes.add(child.class_name) - return ["ast/{}.hpp".format(to_snake_case(clazz)) for clazz in dependent_classes] + return sorted(["ast/{}.hpp".format(to_snake_case(clazz)) for clazz in dependent_classes]) @property def ast_enum_name(self): From 8a5a134e7349466668f36fdc8b35005519fdd5a2 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 10 Jan 2022 17:11:30 +0200 Subject: [PATCH 411/871] Fix nrn_state_required (BlueBrain/nmodl#797) * Nrn state required if state block exists or breakpoint exists similar to mod2c NMODL Repo SHA: BlueBrain/nmodl@9e0a6f260ac2e6fad068a39ea3bdf7aa7a6f4ee0 --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index fd3986c120..a6ce6c7b38 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -375,7 +375,7 @@ bool CodegenCVisitor::nrn_state_required() const noexcept { if (info.artificial_cell) { return false; } - return info.nrn_state_block != nullptr || info.currents.empty(); + return info.nrn_state_block != nullptr || breakpoint_exist(); } From 703184dbfef3126182337ab9b5fc05f0676f4626 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Thu, 13 Jan 2022 10:44:52 +0100 Subject: [PATCH 412/871] Add a python binding for the parent pointer (BlueBrain/nmodl#798) * The getter and setter methods are bound to a pybind11 property. * Add unit tests for parent property NMODL Repo SHA: BlueBrain/nmodl@f3b50fd5736e70ced73b020500791f7339076df7 --- src/nmodl/language/templates/pybind/pyast.cpp | 2 +- src/nmodl/language/templates/pybind/pyast.hpp | 8 ++++++++ src/nmodl/pybind/docstrings.hpp | 6 ++++++ test/nmodl/transpiler/unit/pybind/test_ast.py | 13 +++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index b1b37950ff..6fddda64c5 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -99,8 +99,8 @@ void init_ast_module(py::module& m) { // clang-format off {% for node in nodes %} .def("is_{{ node.class_name | snake_case }}", &Ast::is_{{ node.class_name | snake_case }}, "Check if node is of type ast.{{ node.class_name}}") - {% if loop.last -%};{% endif %} {% endfor %} + .def_property("parent", &Ast::get_parent, &Ast::set_parent, docstring::parent_property()); {% for setup_pybind_method in setup_pybind_methods %} nmodl::ast::pybind::{{setup_pybind_method}}(m_ast); diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index ee0c6699fe..b161f82fab 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -141,6 +141,14 @@ struct PyAst: public Ast { } {% endfor %} + + Ast* get_parent() const override { + PYBIND11_OVERLOAD(Ast*, Ast, get_parent, ); + } + + void set_parent(Ast* p) override { + PYBIND11_OVERLOAD(void, Ast, set_parent, p); + } }; /** \} */ // end of ast_python diff --git a/src/nmodl/pybind/docstrings.hpp b/src/nmodl/pybind/docstrings.hpp index 279b148ab2..1ff5c982f5 100644 --- a/src/nmodl/pybind/docstrings.hpp +++ b/src/nmodl/pybind/docstrings.hpp @@ -163,5 +163,11 @@ constexpr const char* eval_method() { )"; } +constexpr const char* parent_property() { + return R"( + Get or set the parent of this node +)"; +} + } // namespace docstring } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/pybind/test_ast.py b/test/nmodl/transpiler/unit/pybind/test_ast.py index b1a935d1ce..b9348b7298 100644 --- a/test/nmodl/transpiler/unit/pybind/test_ast.py +++ b/test/nmodl/transpiler/unit/pybind/test_ast.py @@ -27,6 +27,19 @@ def test_ast_construction(self): neuron_block = ast.NeuronBlock(block) assert nmodl.to_nmodl(neuron_block) == 'NEURON {\n}' + def test_get_parent(self): + x_name = ast.Name(ast.String("x")) + int_macro = nmodl.ast.Integer(1, x_name) + assert x_name.parent == int_macro # getting the parent + + def test_set_parent(self): + x_name = ast.Name(ast.String("x")) + y_name = ast.Name(ast.String("y")) + int_macro = nmodl.ast.Integer(1, x_name) + y_name.parent = int_macro # setting the parent + int_macro.macro = y_name + assert nmodl.to_nmodl(int_macro) == 'y' + def test_ast_node_repr(self): string = ast.String("tau") name = ast.Name(string) From f47e87c109444441c3325f7b4e813454f5bb6e9e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 19 Jan 2022 11:01:51 +0100 Subject: [PATCH 413/871] Fixing \r as return char inside comments (BlueBrain/nmodl#800) * Fixing \r as return char inside comments If a single line comment in a mod file is end by only \r (not \r\n or \n), currently it breaks because until the next \r\n or \n everything is inside the comment NMODL Repo SHA: BlueBrain/nmodl@d8f159cb2da5b0e79125a1329c7ed443b141aa54 --- src/nmodl/lexer/nmodl.ll | 4 ++-- test/nmodl/transpiler/integration/mod/cabpump.mod | 6 ++++-- test/nmodl/transpiler/unit/parser/parser.cpp | 11 +++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 1c45635fea..4fdcffd166 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -418,8 +418,8 @@ ELSE { loc.step(); } -:.* | -\?.* { +:[^\r\n]* | +\?[^\r\n]* { /** Todo : add grammar support for inline vs single-line comments auto str = std::string(yytext); return NmodlParser::make_LINE_COMMENT(str, loc); diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index a1c6b361a2..a01e1b9607 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -40,8 +40,7 @@ STATE { BREAKPOINT { - SOLVE state METHOD euler -} + SOLVE state METHOD euler : this comment is terminated by a \r only, and it should not break the parser } INCLUDE "var_init.inc" @@ -63,6 +62,9 @@ PROCEDURE test_table(br) { ainf = 1 } +: to test \r only as newline PROCEDURE test_r() { +} : add something that will breaks the parser + INITIAL { var_init(var) ca = cainf diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index b654129c4c..8439528520 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -34,6 +34,17 @@ bool is_valid_construct(const std::string& construct) { } +SCENARIO("NMODL can accept \\r as return char for one line comment", "[parser]") { + GIVEN("A comment defined with \\r as return char") { + WHEN("parsing") { + THEN("success") { + REQUIRE(is_valid_construct(R"(: see you next line PROCEDURE foo() { + })")); + } + } + } +} + SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { GIVEN("A valid macro definition") { WHEN("DEFINE NSTEP 6") { From efc18a88a5e43248721979f0b7f7fdbbe7075a08 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 19 Jan 2022 22:47:27 +0530 Subject: [PATCH 414/871] Support for BEFORE/AFTER blocks in code generation (BlueBrain/nmodl#799) * Support for BEFORE/AFTER blocks in code generation - separate functions are generated for each before/after block just like other global functions init/state/current - functions are then registed to coreneuron using api * Fix ion variables manipulation same as mod2c - Note that ion statements for equation block are added. * Use ubnuntu-20.04 for docs building ci NMODL Repo SHA: BlueBrain/nmodl@f636d1d6526a8a5baeb835bd839dab98451e0cc5 --- src/nmodl/codegen/codegen_c_visitor.cpp | 120 +++++++++++++++++- src/nmodl/codegen/codegen_c_visitor.hpp | 15 ++- .../codegen/codegen_compatibility_visitor.cpp | 4 - src/nmodl/codegen/codegen_helper_visitor.cpp | 8 ++ src/nmodl/codegen/codegen_helper_visitor.hpp | 2 + src/nmodl/codegen/codegen_info.hpp | 3 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 3 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 3 +- 8 files changed, 147 insertions(+), 11 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index a6ce6c7b38..b7bfc5e1ee 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2124,7 +2124,7 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, * case we first update current mechanism's shadow vector and then add statement * to queue that will be used in reduction queue. */ -std::string CodegenCVisitor::process_shadow_update_statement(ShadowUseStatement& statement, +std::string CodegenCVisitor::process_shadow_update_statement(const ShadowUseStatement& statement, BlockType type) { // when there is no operator or rhs then that statement doesn't need shadow update if (statement.op.empty() && statement.rhs.empty()) { @@ -2737,6 +2737,45 @@ void CodegenCVisitor::print_global_variables_for_hoc() { printer->add_line("};"); } +/** + * Return registration type for a given BEFORE/AFTER block + * /param block A BEFORE/AFTER block being registered + * + * Depending on a block type i.e. BEFORE or AFTER and also type + * of it's associated block i.e. BREAKPOINT, INITIAL, SOLVE and + * STEP, the registration type (as an integer) is calculated. + * These values are then interpreted by CoreNEURON internally. + */ +static size_t get_register_type_for_ba_block(const ast::Block* block) { + size_t register_type = 0; + BAType ba_type; + /// before block have value 10 and after block 20 + if (block->is_before_block()) { + register_type = 10; + ba_type = + dynamic_cast(block)->get_bablock()->get_type()->get_value(); + } else { + register_type = 20; + ba_type = + dynamic_cast(block)->get_bablock()->get_type()->get_value(); + } + + /// associated blocks have different values (1 to 4) based on type. + /// These values are based on neuron/coreneuron implementation details. + if (ba_type == BATYPE_BREAKPOINT) { + register_type += 1; + } else if (ba_type == BATYPE_SOLVE) { + register_type += 2; + } else if (ba_type == BATYPE_INITIAL) { + register_type += 3; + } else if (ba_type == BATYPE_STEP) { + register_type += 4; + } else { + throw std::runtime_error("Unhandled Before/After type encountered during code generation"); + } + return register_type; +} + /** * \details Every mod file has register function to connect with the simulator. @@ -2885,6 +2924,15 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("hoc_register_net_send_buffering(mech_type);"); } + /// register all before/after blocks + for (size_t i = 0; i < info.before_after_blocks.size(); i++) { + // register type and associated function name for the block + const auto& block = info.before_after_blocks[i]; + size_t register_type = get_register_type_for_ba_block(block); + std::string function_name = method_name("nrn_before_after_{}"_format(i)); + printer->add_line("hoc_reg_ba(mech_type, {}, {});"_format(function_name, register_type)); + } + // register variables for hoc printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); printer->end_block(1); @@ -3389,8 +3437,14 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { } -void CodegenCVisitor::print_global_function_common_code(BlockType type) { - std::string method = compute_method_name(type); +void CodegenCVisitor::print_global_function_common_code(BlockType type, + const std::string& function_name) { + std::string method; + if (function_name.empty()) { + method = compute_method_name(type); + } else { + method = function_name; + } auto args = "NrnThread* nt, Memb_list* ml, int type"; // watch statement function doesn't have type argument @@ -3513,6 +3567,63 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { codegen = false; } +void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t block_id) { + codegen = true; + + std::string ba_type; + std::shared_ptr ba_block; + + if (node->is_before_block()) { + ba_block = dynamic_cast(node)->get_bablock(); + ba_type = "BEFORE"; + } else { + ba_block = dynamic_cast(node)->get_bablock(); + ba_type = "AFTER"; + } + + std::string ba_block_type = ba_block->get_type()->eval(); + + /// name of the before/after function + std::string function_name = method_name("nrn_before_after_{}"_format(block_id)); + + /// print common function code like init/state/current + printer->add_newline(2); + printer->add_line("/** {} of block type {} # {} */"_format(ba_type, ba_block_type, block_id)); + print_global_function_common_code(BlockType::BeforeAfter, function_name); + + print_channel_iteration_tiling_block_begin(BlockType::BeforeAfter); + print_channel_iteration_block_begin(BlockType::BeforeAfter); + + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + print_v_unused(); + + // read ion statements + const auto& read_statements = ion_read_statements(BlockType::Equation); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + + /// print main body + printer->add_indent(); + print_statement_block(*ba_block->get_statement_block()); + printer->add_newline(); + + // write ion statements + const auto& write_statements = ion_write_statements(BlockType::Equation); + for (auto& statement: write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + + /// loop end including data annotation block + print_channel_iteration_block_end(); + print_channel_iteration_tiling_block_end(); + printer->end_block(1); + print_kernel_data_present_annotation_block_end(); + + codegen = false; +} void CodegenCVisitor::print_nrn_constructor() { printer->add_newline(2); @@ -4550,6 +4661,9 @@ void CodegenCVisitor::print_compute_functions() { for (const auto& function: info.functions) { print_function(*function); } + for (size_t i = 0; i < info.before_after_blocks.size(); i++) { + print_before_after_block(info.before_after_blocks[i], i); + } for (const auto& callback: info.derivimplicit_callbacks) { auto block = callback->get_node_to_solve().get(); print_derivimplicit_kernel(block); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index ef97c151e2..f9353bbc4a 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -79,6 +79,9 @@ enum BlockType { /// net_receive block NetReceive, + /// before / after block + BeforeAfter, + /// fake ending block type for loops on the enums. Keep it at the end BlockTypeEnd }; @@ -1532,7 +1535,8 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param type The target backend code block type * \return The generated target backend code */ - std::string process_shadow_update_statement(ShadowUseStatement& statement, BlockType type); + std::string process_shadow_update_statement(const ShadowUseStatement& statement, + BlockType type); /** @@ -1610,7 +1614,8 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * Print common code for global functions like nrn_init, nrn_cur and nrn_state * \param type The target backend code block type */ - virtual void print_global_function_common_code(BlockType type); + virtual void print_global_function_common_code(BlockType type, + const std::string& function_name = ""); /** @@ -1886,6 +1891,12 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_procedure(const ast::ProcedureBlock& node); + /** + * Print NMODL before / after block in target backend code + * @param node AST node of type before/after type being printed + * @param block_id Index of the before/after block + */ + virtual void print_before_after_block(const ast::Block* node, size_t block_id); /** Setup the target backend code generator * diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index ef9b92d8df..7d10358083 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -24,10 +24,6 @@ const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( {{AstNodeType::MATCH_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::BEFORE_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::AFTER_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::TERMINAL_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::DISCRETE_BLOCK, diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 49aea037b0..236ff79a83 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -743,5 +743,13 @@ void CodegenHelperVisitor::visit_verbatim(const Verbatim& node) { } } +void CodegenHelperVisitor::visit_before_block(const ast::BeforeBlock& node) { + info.before_after_blocks.push_back(&node); +} + +void CodegenHelperVisitor::visit_after_block(const ast::AfterBlock& node) { + info.before_after_blocks.push_back(&node); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index a2fe68d5cd..11008668b5 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -112,6 +112,8 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_partial_block(const ast::PartialBlock& node) override; void visit_update_dt(const ast::UpdateDt& node) override; void visit_verbatim(const ast::Verbatim& node) override; + void visit_before_block(const ast::BeforeBlock& node) override; + void visit_after_block(const ast::AfterBlock& node) override; }; /** @} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 978ad11efd..805470dad6 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -364,6 +364,9 @@ struct CodegenInfo { /// all watch statements std::vector watch_statements; + /// all before after blocks + std::vector before_after_blocks; + /// all variables/symbols used in the verbatim block std::unordered_set variables_in_verbatim; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index e828df7bc8..dca97d426c 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -362,7 +362,8 @@ void CodegenIspcVisitor::print_procedure(const ast::ProcedureBlock& node) { } -void CodegenIspcVisitor::print_global_function_common_code(BlockType type) { +void CodegenIspcVisitor::print_global_function_common_code(BlockType type, + const std::string& function_name) { // If we are printing the cpp file, we have to use the c version of this function if (wrapper_codegen) { return CodegenCVisitor::print_global_function_common_code(type); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index 90ae56c5fe..e14b05e000 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -131,7 +131,8 @@ class CodegenIspcVisitor: public CodegenCVisitor { ParamVector get_global_function_parms(const std::string& arg_qualifier); - void print_global_function_common_code(BlockType type) override; + void print_global_function_common_code(BlockType type, + const std::string& function_name = "") override; /// backend specific channel instance iteration block start From 550f7bf4357b2121d2e2b4938d9fd75c16616bce Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 27 Jan 2022 18:08:19 +0100 Subject: [PATCH 415/871] Use non linear newton solver for sparse method (BlueBrain/nmodl#804) * Use non linear newton solver for sparse method It is possible to give non linear system to sparse solver. The sparse method was mistaken using linear solver, now use deriv implicit. Fix BlueBrain/nmodl#801 NMODL Repo SHA: BlueBrain/nmodl@2833671832fd5f062439a7b633910fa499a5c428 --- src/nmodl/visitors/sympy_solver_visitor.cpp | 5 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 14 +- .../transpiler/unit/visitor/sympy_solver.cpp | 372 +++++++++++++----- 3 files changed, 287 insertions(+), 104 deletions(-) diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 6e5c254f21..8c1e09b331 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -555,9 +555,8 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { } } - if (solve_method == codegen::naming::SPARSE_METHOD) { - solve_linear_system(pre_solve_statements); - } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { + if (solve_method == codegen::naming::SPARSE_METHOD || + solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { solve_non_linear_system(pre_solve_statements); } else { logger->error("SympySolverVisitor :: Solve method {} not supported", solve_method); diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index eb02987539..c45b9ef193 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -42,15 +42,7 @@ namespace visitor { * For `DERIVATIVE` block, solver method `euler`: * - replace each ODE with forwards Euler timestep * - * For `DERIVATIVE` block, solver method `sparse`: - * - construct backwards Euler timestep linear system - * - for small systems: solves resulting linear algebraic equation by - * Gaussian elimination, replaces differential equations - * with explicit solution of backwards Euler equations - * - for large systems, returns matrix and vector of linear system - * to be solved by e.g. LU factorization - * - * For `DERIVATIVE` block, solver method `derivimplicit`: + * For `DERIVATIVE` block, solver method `sparse` and `derivimplicit`: * - construct backwards Euler timestep non-linear system * - return function F and its Jacobian J to be solved by newton solver * @@ -86,10 +78,10 @@ class SympySolverVisitor: public AstVisitor { const std::vector& solutions, bool linear); - /// solve linear system (for "sparse" and "LINEAR") + /// solve linear system (for "LINEAR") void solve_linear_system(const std::vector& pre_solve_statements = {}); - /// solve non-linear system (for "derivimplicit" and "NONLINEAR") + /// solve non-linear system (for "derivimplicit", "sparse" and "NONLINEAR") void solve_non_linear_system(const std::vector& pre_solve_statements = {}); /// return NMODL string version of node, excluding any units diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index f8b9fa067a..80a5d29e84 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -690,11 +690,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_result = R"( DERIVATIVE states { - LOCAL a, b, old_y, old_x - old_y = y - old_x = x - y = a*dt+old_y - x = b*dt+old_x + EIGEN_NEWTON_SOLVE[2]{ + LOCAL a, b, old_y, old_x + }{ + old_y = y + old_x = x + }{ + X[0] = x + X[1] = y + }{ + F[0] = -X[1]+a*dt+old_y + J[0] = 0 + J[2] = -1.0 + F[1] = -X[0]+b*dt+old_x + J[1] = -1.0 + J[3] = 0 + }{ + x = X[0] + y = X[1] + }{ + } })"; THEN("Construct & solve linear system for backwards Euler") { @@ -719,11 +734,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_result = R"( DERIVATIVE states { - LOCAL a, b, old_M_1, old_M_0 - old_M_1 = M[1] - old_M_0 = M[0] - M[1] = a*dt+old_M_1 - M[0] = b*dt+old_M_0 + EIGEN_NEWTON_SOLVE[2]{ + LOCAL a, b, old_M_1, old_M_0 + }{ + old_M_1 = M[1] + old_M_0 = M[0] + }{ + X[0] = M[0] + X[1] = M[1] + }{ + F[0] = -X[1]+a*dt+old_M_1 + J[0] = 0 + J[2] = -1.0 + F[1] = -X[0]+b*dt+old_M_0 + J[1] = -1.0 + J[3] = 0 + }{ + M[0] = X[0] + M[1] = X[1] + }{ + } })"; THEN("Construct & solve linear system for backwards Euler") { @@ -749,12 +779,27 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_result = R"( DERIVATIVE states { - LOCAL a, b, old_x, old_y - old_x = x - old_y = y - x = a*dt+old_x - b = b+1 - y = b*dt+old_y + EIGEN_NEWTON_SOLVE[2]{ + LOCAL a, b, old_x, old_y + }{ + old_x = x + old_y = y + }{ + X[0] = x + X[1] = y + }{ + F[0] = -X[0]+a*dt+old_x + J[0] = -1.0 + J[2] = 0 + b = b+1 + F[1] = -X[1]+b*dt+old_y + J[1] = 0 + J[3] = -1.0 + }{ + x = X[0] + y = X[1] + }{ + } })"; THEN("Construct & solve linear system for backwards Euler") { @@ -811,11 +856,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", DERIVATIVE states { LOCAL a, b IF (a == 1) { - LOCAL old_x, old_y - old_x = x - old_y = y - x = a*dt+old_x - y = b*dt+old_y + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_x, old_y + }{ + old_x = x + old_y = y + }{ + X[0] = x + X[1] = y + }{ + F[0] = -X[0]+a*dt+old_x + J[0] = -1.0 + J[2] = 0 + F[1] = -X[1]+b*dt+old_y + J[1] = 0 + J[3] = -1.0 + }{ + x = X[0] + y = X[1] + }{ + } } })"; @@ -846,32 +906,55 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_result = R"( DERIVATIVE states { - LOCAL a, b, old_x, old_y - old_x = x - old_y = y - x = (a*b*pow(dt, 2)+a*dt*old_x-a*dt*old_y-b*dt-old_x)/(a*pow(dt, 2)+a*dt-1.0) - IF (b == 1) { - a = a+1 + EIGEN_NEWTON_SOLVE[2]{ + LOCAL a, b, old_x, old_y + }{ + old_x = x + old_y = y + }{ + X[0] = x + X[1] = y + }{ + F[0] = -X[0]+X[1]*a*dt+b*dt+old_x + J[0] = -1.0 + J[2] = a*dt + IF (b == 1) { + a = a+1 + } + F[1] = X[0]*dt+X[1]*a*dt-X[1]+old_y + J[1] = dt + J[3] = a*dt-1.0 + }{ + x = X[0] + y = X[1] + }{ } - y = (-b*pow(dt, 2)-dt*old_x-old_y)/(a*pow(dt, 2)+a*dt-1.0) })"; std::string expected_result_cse = R"( DERIVATIVE states { - LOCAL a, b, old_x, old_y, tmp_0, tmp_1, tmp_2, tmp_3 - old_x = x - old_y = y - tmp_0 = a*dt - tmp_1 = pow(dt, 2) - tmp_2 = a*tmp_1 - tmp_3 = 1.0/(tmp_0+tmp_2-1.0) - x = -tmp_3*(b*dt-b*tmp_2-old_x*tmp_0+old_x+old_y*tmp_0) - IF (b == 1) { - a = a+1 + EIGEN_NEWTON_SOLVE[2]{ + LOCAL a, b, old_x, old_y + }{ + old_x = x + old_y = y + }{ + X[0] = x + X[1] = y + }{ + F[0] = -X[0]+X[1]*a*dt+b*dt+old_x + J[0] = -1.0 + J[2] = a*dt + IF (b == 1) { + a = a+1 + } + F[1] = X[0]*dt+X[1]*a*dt-X[1]+old_y + J[1] = dt + J[3] = a*dt-1.0 + }{ + x = X[0] + y = X[1] + }{ } - tmp_0 = a*dt - tmp_2 = a*tmp_1 - tmp_3 = 1.0/(tmp_0+tmp_2-1.0) - y = -tmp_3*(b*tmp_1+dt*old_x+old_y) })"; THEN("Construct & solve linear system for backwards Euler") { @@ -902,29 +985,67 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", )"; std::string expected_result = R"( DERIVATIVE states { - LOCAL a, b, c, d, h, old_x, old_y, old_z - old_x = x - old_y = y - old_z = z - x = (-a*dt*(dt*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y)-old_z)+(b*dt*h+old_x)*(2.0*a*pow(dt, 3)-d*dt+1.0))/(2.0*a*pow(dt, 3)-d*dt+1.0) - y = (-2.0*a*pow(dt, 2)*(dt*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y)-old_z)+(2.0*a*pow(dt, 3)-d*dt+1.0)*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y))/(2.0*a*pow(dt, 3)-d*dt+1.0) - z = (-dt*(c*dt+2.0*dt*(b*dt*h+old_x)+old_y)+old_z)/(2.0*a*pow(dt, 3)-d*dt+1.0) + EIGEN_NEWTON_SOLVE[3]{ + LOCAL a, b, c, d, h, old_x, old_y, old_z + }{ + old_x = x + old_y = y + old_z = z + }{ + X[0] = x + X[1] = y + X[2] = z + }{ + F[0] = -X[0]+X[2]*a*dt+b*dt*h+old_x + J[0] = -1.0 + J[3] = 0 + J[6] = a*dt + F[1] = 2.0*X[0]*dt-X[1]+c*dt+old_y + J[1] = 2.0*dt + J[4] = -1.0 + J[7] = 0 + F[2] = -X[1]*dt+X[2]*d*dt-X[2]+old_z + J[2] = 0 + J[5] = -dt + J[8] = d*dt-1.0 + }{ + x = X[0] + y = X[1] + z = X[2] + }{ + } })"; std::string expected_cse_result = R"( DERIVATIVE states { - LOCAL a, b, c, d, h, old_x, old_y, old_z, tmp_0, tmp_1, tmp_2, tmp_3, tmp_4, tmp_5 - old_x = x - old_y = y - old_z = z - tmp_0 = 2.0*a - tmp_1 = -d*dt+pow(dt, 3)*tmp_0+1.0 - tmp_2 = 1.0/tmp_1 - tmp_3 = b*dt*h+old_x - tmp_4 = c*dt+2.0*dt*tmp_3+old_y - tmp_5 = dt*tmp_4-old_z - x = -tmp_2*(a*dt*tmp_5-tmp_1*tmp_3) - y = -tmp_2*(pow(dt, 2)*tmp_0*tmp_5-tmp_1*tmp_4) - z = -tmp_2*tmp_5 + EIGEN_NEWTON_SOLVE[3]{ + LOCAL a, b, c, d, h, old_x, old_y, old_z + }{ + old_x = x + old_y = y + old_z = z + }{ + X[0] = x + X[1] = y + X[2] = z + }{ + F[0] = -X[0]+X[2]*a*dt+b*dt*h+old_x + J[0] = -1.0 + J[3] = 0 + J[6] = a*dt + F[1] = 2.0*X[0]*dt-X[1]+c*dt+old_y + J[1] = 2.0*dt + J[4] = -1.0 + J[7] = 0 + F[2] = -X[1]*dt+X[2]*d*dt-X[2]+old_z + J[2] = 0 + J[5] = -dt + J[8] = d*dt-1.0 + }{ + x = X[0] + y = X[1] + z = X[2] + }{ + } })"; THEN("Construct & solve linear system for backwards Euler") { @@ -952,11 +1073,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", )"; std::string expected_result = R"( DERIVATIVE scheme1 { - LOCAL old_mc, old_m - old_mc = mc - old_m = m - mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1.0) - m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1.0) + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_mc, old_m + }{ + old_mc = mc + old_m = m + }{ + X[0] = mc + X[1] = m + }{ + F[0] = -X[0]*a*dt-X[0]+X[1]*b*dt+old_mc + J[0] = -a*dt-1.0 + J[2] = b*dt + F[1] = X[0]*a*dt-X[1]*b*dt-X[1]+old_m + J[1] = a*dt + J[3] = -b*dt-1.0 + }{ + mc = X[0] + m = X[1] + }{ + } })"; THEN("Construct & solve linear system") { CAPTURE(nmodl_text); @@ -981,10 +1117,25 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", )"; std::string expected_result = R"( DERIVATIVE scheme1 { - LOCAL old_mc - old_mc = mc - mc = (b*dt+old_mc)/(a*dt+b*dt+1.0) - m = (a*dt-old_mc+1.0)/(a*dt+b*dt+1.0) + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_mc + }{ + old_mc = mc + }{ + X[0] = mc + X[1] = m + }{ + F[0] = -X[0]*a*dt-X[0]+X[1]*b*dt+old_mc + J[0] = -a*dt-1.0 + J[2] = b*dt + F[1] = -X[0]-X[1]+1.0 + J[1] = -1.0 + J[3] = -1.0 + }{ + mc = X[0] + m = X[1] + }{ + } })"; THEN("Construct & solve linear system, replace ODE for m with rhs of CONSERVE statement") { CAPTURE(nmodl_text); @@ -1011,11 +1162,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", )"; std::string expected_result = R"( DERIVATIVE scheme1 { - LOCAL old_mc, old_m - old_mc = mc - old_m = m - mc = (b*dt*old_m+b*dt*old_mc+old_mc)/(a*dt+b*dt+1.0) - m = (a*dt*old_m+a*dt*old_mc+old_m)/(a*dt+b*dt+1.0) + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_mc, old_m + }{ + old_mc = mc + old_m = m + }{ + X[0] = mc + X[1] = m + }{ + F[0] = -X[0]*a*dt-X[0]+X[1]*b*dt+old_mc + J[0] = -a*dt-1.0 + J[2] = b*dt + F[1] = X[0]*a*dt-X[1]*b*dt-X[1]+old_m + J[1] = a*dt + J[3] = -b*dt-1.0 + }{ + mc = X[0] + m = X[1] + }{ + } })"; THEN("Construct & solve linear system, ignore invalid CONSERVE statement") { CAPTURE(nmodl_text); @@ -1045,7 +1211,7 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", })"; std::string expected_result = R"( DERIVATIVE ihkin { - EIGEN_LINEAR_SOLVE[5]{ + EIGEN_NEWTON_SOLVE[5]{ LOCAL alpha, beta, k3p, k4, k1ca, k2, old_c1, old_o1, old_p0 }{ evaluate_fct(v, cai) @@ -1058,31 +1224,32 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", X[2] = o2 X[3] = p0 X[4] = p1 - F[0] = -old_c1 - F[1] = -old_o1 - F[2] = -1.0 - F[3] = -old_p0 - F[4] = -1.0 + }{ + F[0] = -X[0]*alpha*dt-X[0]+X[1]*beta*dt+old_c1 J[0] = -alpha*dt-1.0 J[5] = beta*dt J[10] = 0 J[15] = 0 J[20] = 0 + F[1] = X[0]*alpha*dt-X[1]*beta*dt-X[1]*dt*k3p-X[1]+X[2]*dt*k4+old_o1 J[1] = alpha*dt J[6] = -beta*dt-dt*k3p-1.0 J[11] = dt*k4 J[16] = 0 J[21] = 0 + F[2] = -X[0]-X[1]-X[2]+1.0 J[2] = -1.0 J[7] = -1.0 J[12] = -1.0 J[17] = 0 J[22] = 0 + F[3] = -X[3]*dt*k1ca-X[3]+X[4]*dt*k2+old_p0 J[3] = 0 J[8] = 0 J[13] = 0 J[18] = -dt*k1ca-1.0 J[23] = dt*k2 + F[4] = -X[3]-X[4]+1.0 J[4] = 0 J[9] = 0 J[14] = 0 @@ -1124,9 +1291,19 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", )"; std::string expected_result = R"( DERIVATIVE scheme1 { - LOCAL old_W_0 - old_W_0 = W[0] - W[0] = (3.0*dt*A[1]+old_W_0)/(dt*A[0]-dt*B[0]+1.0) + EIGEN_NEWTON_SOLVE[1]{ + LOCAL old_W_0 + }{ + old_W_0 = W[0] + }{ + X[0] = W[0] + }{ + F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3.0*dt*A[1]+old_W_0 + J[0] = -dt*A[0]+dt*B[0]-1.0 + }{ + W[0] = X[0] + }{ + } })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); @@ -1154,11 +1331,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", )"; std::string expected_result = R"( DERIVATIVE scheme1 { - LOCAL old_M_0, old_M_1 - old_M_0 = M[0] - old_M_1 = M[1] - M[0] = (dt*old_M_0*B[1]+dt*old_M_1*B[0]+old_M_0)/(pow(dt, 2)*A[0]*B[1]-pow(dt, 2)*A[1]*B[0]+dt*A[0]+dt*B[1]+1.0) - M[1] = -(dt*old_M_0*A[1]+old_M_1*(dt*A[0]+1.0))/(pow(dt, 2)*A[1]*B[0]-(dt*A[0]+1.0)*(dt*B[1]+1.0)) + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_M_0, old_M_1 + }{ + old_M_0 = M[0] + old_M_1 = M[1] + }{ + X[0] = M[0] + X[1] = M[1] + }{ + F[0] = -X[0]*dt*A[0]-X[0]+X[1]*dt*B[0]+old_M_0 + J[0] = -dt*A[0]-1.0 + J[2] = dt*B[0] + F[1] = X[0]*dt*A[1]-X[1]*dt*B[1]-X[1]+old_M_1 + J[1] = dt*A[1] + J[3] = -dt*B[1]-1.0 + }{ + M[0] = X[0] + M[1] = X[1] + }{ + } })"; THEN("Construct & solver linear system") { CAPTURE(nmodl_text); From 42e50f5f72388290ebd99312bd723ba8984fd61f Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Fri, 28 Jan 2022 17:48:09 +0100 Subject: [PATCH 416/871] partialPivLu: rework OpenMP workaround (BlueBrain/nmodl#805) The explicitly named partialPivLuN(...) functions still seem to be required, but by providing specialisations of partialPivLu(...) in the header then we seem to be able to revert one change to the code generation and allow nmodl::newton::newton_solver(...) to be used in code compiled with OpenMP. This includes the reduced dentate model after the changes made in BlueBrain/nmodl#804. See also: CoreNEURONBlueBrain/nmodl#767. NMODL Repo SHA: BlueBrain/nmodl@6d53d072c73cbc3a74dbf55bf844e6ccc32be1c5 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- .../solver/partial_piv_lu/partial_piv_lu.cu | 86 ++++++++++--------- .../solver/partial_piv_lu/partial_piv_lu.h | 74 ++++++++-------- 3 files changed, 87 insertions(+), 75 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index a12843bc0c..93b04ef480 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -147,7 +147,7 @@ void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, if (N <= 4) { printer->add_line("{0} = {1}.inverse()*{2};"_format(Xm, Jm, Fm)); } else { - printer->add_line("{0} = partialPivLu{1}({2}, {3});"_format(Xm, N, Jm, Fm)); + printer->add_line("{0} = partialPivLu<{1}>({2}, {3});"_format(Xm, N, Jm, Fm)); } } diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu index c043a710e4..ad9cd05e68 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu @@ -6,47 +6,55 @@ *************************************************************************/ #include "partial_piv_lu/partial_piv_lu.h" -template -EIGEN_DEVICE_FUNC -VecType partialPivLu(const MatType& A, const VecType& b) -{ +// See explanation in partial_piv_lu.h +EIGEN_DEVICE_FUNC VecType<1> partialPivLu1(const MatType<1>& A, const VecType<1>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<2> partialPivLu2(const MatType<2>& A, const VecType<2>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<3> partialPivLu3(const MatType<3>& A, const VecType<3>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<4> partialPivLu4(const MatType<4>& A, const VecType<4>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<5> partialPivLu5(const MatType<5>& A, const VecType<5>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<6> partialPivLu6(const MatType<6>& A, const VecType<6>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<7> partialPivLu7(const MatType<7>& A, const VecType<7>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<8> partialPivLu8(const MatType<8>& A, const VecType<8>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<9> partialPivLu9(const MatType<9>& A, const VecType<9>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<10> partialPivLu10(const MatType<10>& A, const VecType<10>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<11> partialPivLu11(const MatType<11>& A, const VecType<11>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<12> partialPivLu12(const MatType<12>& A, const VecType<12>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<13> partialPivLu13(const MatType<13>& A, const VecType<13>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<14> partialPivLu14(const MatType<14>& A, const VecType<14>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<15> partialPivLu15(const MatType<15>& A, const VecType<15>& b) { + return A.partialPivLu().solve(b); +} +EIGEN_DEVICE_FUNC VecType<16> partialPivLu16(const MatType<16>& A, const VecType<16>& b) { return A.partialPivLu().solve(b); } - -// Explicit Template Instantiation -template EIGEN_DEVICE_FUNC VecType<1> partialPivLu<1>(const MatType<1>& A, const VecType<1>& b); -template EIGEN_DEVICE_FUNC VecType<2> partialPivLu<2>(const MatType<2>& A, const VecType<2>& b); -template EIGEN_DEVICE_FUNC VecType<3> partialPivLu<3>(const MatType<3>& A, const VecType<3>& b); -template EIGEN_DEVICE_FUNC VecType<4> partialPivLu<4>(const MatType<4>& A, const VecType<4>& b); -template EIGEN_DEVICE_FUNC VecType<5> partialPivLu<5>(const MatType<5>& A, const VecType<5>& b); -template EIGEN_DEVICE_FUNC VecType<6> partialPivLu<6>(const MatType<6>& A, const VecType<6>& b); -template EIGEN_DEVICE_FUNC VecType<7> partialPivLu<7>(const MatType<7>& A, const VecType<7>& b); -template EIGEN_DEVICE_FUNC VecType<8> partialPivLu<8>(const MatType<8>& A, const VecType<8>& b); -template EIGEN_DEVICE_FUNC VecType<9> partialPivLu<9>(const MatType<9>& A, const VecType<9>& b); -template EIGEN_DEVICE_FUNC VecType<10> partialPivLu<10>(const MatType<10>& A, const VecType<10>& b); -template EIGEN_DEVICE_FUNC VecType<11> partialPivLu<11>(const MatType<11>& A, const VecType<11>& b); -template EIGEN_DEVICE_FUNC VecType<12> partialPivLu<12>(const MatType<12>& A, const VecType<12>& b); -template EIGEN_DEVICE_FUNC VecType<13> partialPivLu<13>(const MatType<13>& A, const VecType<13>& b); -template EIGEN_DEVICE_FUNC VecType<14> partialPivLu<14>(const MatType<14>& A, const VecType<14>& b); -template EIGEN_DEVICE_FUNC VecType<15> partialPivLu<15>(const MatType<15>& A, const VecType<15>& b); -template EIGEN_DEVICE_FUNC VecType<16> partialPivLu<16>(const MatType<16>& A, const VecType<16>& b); - -EIGEN_DEVICE_FUNC VecType<1> partialPivLu1(const MatType<1>& A, const VecType<1>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<2> partialPivLu2(const MatType<2>& A, const VecType<2>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<3> partialPivLu3(const MatType<3>& A, const VecType<3>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<4> partialPivLu4(const MatType<4>& A, const VecType<4>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<5> partialPivLu5(const MatType<5>& A, const VecType<5>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<6> partialPivLu6(const MatType<6>& A, const VecType<6>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<7> partialPivLu7(const MatType<7>& A, const VecType<7>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<8> partialPivLu8(const MatType<8>& A, const VecType<8>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<9> partialPivLu9(const MatType<9>& A, const VecType<9>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<10> partialPivLu10(const MatType<10>& A, const VecType<10>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<11> partialPivLu11(const MatType<11>& A, const VecType<11>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<12> partialPivLu12(const MatType<12>& A, const VecType<12>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<13> partialPivLu13(const MatType<13>& A, const VecType<13>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<14> partialPivLu14(const MatType<14>& A, const VecType<14>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<15> partialPivLu15(const MatType<15>& A, const VecType<15>& b) { return A.partialPivLu().solve(b); } -EIGEN_DEVICE_FUNC VecType<16> partialPivLu16(const MatType<16>& A, const VecType<16>& b) { return A.partialPivLu().solve(b); } // Currently there is an issue in Eigen (GPU-branch) for matrices 17x17 and above. // ToDo: Check in a future release if this issue is resolved! diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h index 1af9d5ad4d..84cd26904c 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h @@ -12,10 +12,10 @@ #include "Eigen/Dense" #include "Eigen/LU" -template +template using MatType = Eigen::Matrix; -template +template using VecType = Eigen::Matrix; // Eigen-3.5+ provides better GPU support. However, some functions cannot be called directly @@ -23,40 +23,44 @@ using VecType = Eigen::Matrix; // them with __device__ & acc routine tokens), which allows us to eventually call them from OpenACC. // Calling these functions from CUDA kernels presents no issue ... +// We want to declare a function template that is callable from OpenMP and +// OpenACC code, but whose instantiations are compiled by CUDA. This is to avoid +// Eigen internals having to be digested by OpenACC/OpenMP compilers. The +// problem is that it is apparently not sufficient to declare a template in a OpenMP +// declare target region and have the OpenMP compiler assume that device +// versions of instantations of it will be available. The convoluted approach +// here has two ingredients: +// - partialPivLu(...), a function template that has explicit +// instantiations visible in this header file that call: +// - partialPivLuN(...), functions that are declared in this header but defined +// in the CUDA file partial_piv_lu.cu. nrn_pragma_omp(declare target) nrn_pragma_acc(routine seq) -template +template EIGEN_DEVICE_FUNC VecType partialPivLu(const MatType&, const VecType&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<1> partialPivLu1(const MatType<1>&, const VecType<1>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<2> partialPivLu2(const MatType<2>&, const VecType<2>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<3> partialPivLu3(const MatType<3>&, const VecType<3>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<4> partialPivLu4(const MatType<4>&, const VecType<4>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<5> partialPivLu5(const MatType<5>&, const VecType<5>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<6> partialPivLu6(const MatType<6>&, const VecType<6>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<7> partialPivLu7(const MatType<7>&, const VecType<7>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<8> partialPivLu8(const MatType<8>&, const VecType<8>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<9> partialPivLu9(const MatType<9>&, const VecType<9>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<10> partialPivLu10(const MatType<10>&, const VecType<10>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<11> partialPivLu11(const MatType<11>&, const VecType<11>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<12> partialPivLu12(const MatType<12>&, const VecType<12>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<13> partialPivLu13(const MatType<13>&, const VecType<13>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<14> partialPivLu14(const MatType<14>&, const VecType<14>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<15> partialPivLu15(const MatType<15>&, const VecType<15>&); -nrn_pragma_acc(routine seq) -EIGEN_DEVICE_FUNC VecType<16> partialPivLu16(const MatType<16>&, const VecType<16>&); +#define InstantiatePartialPivLu(N) \ + nrn_pragma_acc(routine seq) \ + EIGEN_DEVICE_FUNC VecType partialPivLu##N(const MatType&, const VecType&); \ + nrn_pragma_acc(routine seq) \ + template <> \ + EIGEN_DEVICE_FUNC inline VecType partialPivLu(const MatType& A, const VecType& b) { \ + return partialPivLu##N(A, b); \ + } +InstantiatePartialPivLu(1) +InstantiatePartialPivLu(2) +InstantiatePartialPivLu(3) +InstantiatePartialPivLu(4) +InstantiatePartialPivLu(5) +InstantiatePartialPivLu(6) +InstantiatePartialPivLu(7) +InstantiatePartialPivLu(8) +InstantiatePartialPivLu(9) +InstantiatePartialPivLu(10) +InstantiatePartialPivLu(11) +InstantiatePartialPivLu(12) +InstantiatePartialPivLu(13) +InstantiatePartialPivLu(14) +InstantiatePartialPivLu(15) +InstantiatePartialPivLu(16) +#undef InstantiatePartialPivLu nrn_pragma_omp(end declare target) From 5fc9c783fca761ce0ff561c8288f64881bc990cd Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 17 Feb 2022 19:28:21 +0100 Subject: [PATCH 417/871] Use myst_parser instead of m2r (BlueBrain/nmodl#808) m2r seems outdated with sphinx > 3.0 myst_parser is the supported markdown ext in sphinx https://www.sphinx-doc.org/en/master/usage/markdown.html NMODL Repo SHA: BlueBrain/nmodl@9de6a5d59b2861402d50ef27e8fba4a39abbbf70 --- docs/nmodl/transpiler/conf.py | 2 +- docs/nmodl/transpiler/contributing.rst | 3 ++- docs/nmodl/transpiler/install.rst | 3 ++- docs/nmodl/transpiler/readme.rst | 3 ++- setup.py | 5 ++--- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index 536b87abc4..8bfe193232 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -45,7 +45,7 @@ # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - "m2r", + "myst_parser", "nbsphinx", "sphinx.ext.autodoc", "sphinx.ext.coverage", diff --git a/docs/nmodl/transpiler/contributing.rst b/docs/nmodl/transpiler/contributing.rst index 0d3640164b..3dbffa8ee7 100644 --- a/docs/nmodl/transpiler/contributing.rst +++ b/docs/nmodl/transpiler/contributing.rst @@ -1,2 +1,3 @@ -.. mdinclude:: ../CONTRIBUTING.md +.. include:: ../CONTRIBUTING.md + :parser: myst_parser.sphinx_ diff --git a/docs/nmodl/transpiler/install.rst b/docs/nmodl/transpiler/install.rst index 175b6101d3..71f9383876 100644 --- a/docs/nmodl/transpiler/install.rst +++ b/docs/nmodl/transpiler/install.rst @@ -1,3 +1,4 @@ -.. mdinclude:: ../INSTALL.md +.. include:: ../INSTALL.md + :parser: myst_parser.sphinx_ diff --git a/docs/nmodl/transpiler/readme.rst b/docs/nmodl/transpiler/readme.rst index 312057d434..6a9465b82b 100644 --- a/docs/nmodl/transpiler/readme.rst +++ b/docs/nmodl/transpiler/readme.rst @@ -1,5 +1,6 @@ Introduction ============ -.. mdinclude:: ../README.md +.. include:: ../README.md + :parser: myst_parser.sphinx_ diff --git a/setup.py b/setup.py index 0b54e37cc4..a99c01a276 100644 --- a/setup.py +++ b/setup.py @@ -126,13 +126,12 @@ def _config_exe(exe_name): "jinja2>=2.9.3", "jupyter-client<7", # try and work around: TypeError in notebooks/nmodl-kinetic-schemes.ipynb: 'coroutine' object is not subscriptable "jupyter", - "m2r", + "myst_parser", "mistune<2", # prevents a version conflict with nbconvert "nbconvert<6.0", # prevents issues with nbsphinx "nbsphinx>=0.3.2", "pytest>=3.7.2", - "sphinx>=2.0", - "sphinx<3.0", # prevents issue with m2r where m2r uses an old API no more supported with sphinx>=3.0 + "sphinx", "sphinx-rtd-theme", ] + install_requirements, From 0303463a727d78bd4710e61930d6b3104f0659ad Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 10 Mar 2022 11:01:25 +0100 Subject: [PATCH 418/871] Fixup various CI plans. (BlueBrain/nmodl#812) * Small fixes for Azure/GitHub CIs. * Follow hpc/gitlab-pipelines changes. * Drop :imported-members: from documentation. NMODL Repo SHA: BlueBrain/nmodl@d8166392d370c9cd1b78a0a3bfa2dacda5e40596 --- docs/nmodl/transpiler/nmodl.rst | 8 +------- setup.py | 13 ++++++++++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/nmodl/transpiler/nmodl.rst b/docs/nmodl/transpiler/nmodl.rst index a085d15568..b7142ff4ab 100644 --- a/docs/nmodl/transpiler/nmodl.rst +++ b/docs/nmodl/transpiler/nmodl.rst @@ -3,7 +3,6 @@ Module contents .. automodule:: nmodl :members: - :imported-members: :undoc-members: :show-inheritance: @@ -16,7 +15,6 @@ nmodl.ast module .. automodule:: nmodl.ast :members: - :imported-members: :no-undoc-members: :show-inheritance: @@ -25,7 +23,6 @@ nmodl.dsl module .. automodule:: nmodl.dsl :members: - :imported-members: :undoc-members: :show-inheritance: @@ -34,7 +31,6 @@ nmodl.ode module .. automodule:: nmodl.ode :members: - :imported-members: :undoc-members: :show-inheritance: @@ -43,7 +39,6 @@ nmodl.symtab module .. automodule:: nmodl.symtab :members: - :imported-members: :undoc-members: :show-inheritance: @@ -52,6 +47,5 @@ nmodl.visitor module .. automodule:: nmodl.visitor :members: - :imported-members: :undoc-members: - :show-inheritance: \ No newline at end of file + :show-inheritance: diff --git a/setup.py b/setup.py index a99c01a276..e8825204f6 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. @@ -52,6 +52,13 @@ def __getitem__(self, item): def get_sphinx_command(): """Lazy load of Sphinx distutils command class """ + # If nbconvert is installed to .eggs on the fly when running setup.py then + # templates from it will not be found. This is a workaround. + if 'JUPYTER_PATH' not in os.environ: + import nbconvert + os.environ['JUPYTER_PATH'] = os.path.realpath(os.path.join(os.path.dirname(nbconvert.__file__), '..', 'share', 'jupyter')) + print("Setting JUPYTER_PATH={}".format(os.environ['JUPYTER_PATH'])) + from sphinx.setup_command import BuildDoc return BuildDoc @@ -124,11 +131,11 @@ def _config_exe(exe_name): zip_safe=False, setup_requires=[ "jinja2>=2.9.3", - "jupyter-client<7", # try and work around: TypeError in notebooks/nmodl-kinetic-schemes.ipynb: 'coroutine' object is not subscriptable + "jupyter-client", "jupyter", "myst_parser", "mistune<2", # prevents a version conflict with nbconvert - "nbconvert<6.0", # prevents issues with nbsphinx + "nbconvert", "nbsphinx>=0.3.2", "pytest>=3.7.2", "sphinx", From 579288913a08ea285057fce600d330b299c087c9 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 15 Mar 2022 12:10:22 +0100 Subject: [PATCH 419/871] Removed POINTER compatibility error (BlueBrain/nmodl#823) NMODL Repo SHA: BlueBrain/nmodl@dd9788e0ae8f7a780d95903304e3873b324a98ae --- src/nmodl/codegen/codegen_compatibility_visitor.cpp | 9 --------- src/nmodl/codegen/codegen_compatibility_visitor.hpp | 10 ---------- 2 files changed, 19 deletions(-) diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 7d10358083..33863f1a02 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -36,7 +36,6 @@ const std::map &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, {AstNodeType::PARAM_ASSIGN, &CodegenCompatibilityVisitor::return_error_param_var}, - {AstNodeType::POINTER_VAR, &CodegenCompatibilityVisitor::return_error_pointer}, {AstNodeType::BBCORE_POINTER_VAR, &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}}); @@ -86,14 +85,6 @@ std::string CodegenCompatibilityVisitor::return_error_param_var( return error_message_global_var.str(); } -std::string CodegenCompatibilityVisitor::return_error_pointer( - ast::Ast& node, - const std::shared_ptr& ast_node) { - auto pointer_var = std::dynamic_pointer_cast(ast_node); - return "\"{}\" POINTER found at [{}] should be defined as BBCOREPOINTER to use it in CoreNeuron\n"_format( - pointer_var->get_node_name(), pointer_var->get_token()->position()); -} - std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( ast::Ast& node, const std::shared_ptr& ast_node) { diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index a19940620c..b2f205aa4d 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -136,16 +136,6 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { std::string return_error_param_var(ast::Ast& node, const std::shared_ptr& ast_node); - /// Takes as parameter an std::shared_ptr node - /// and returns a relative error with the name and the - /// location of the pointer, as well as a suggestion to - /// define it as BBCOREPOINTER - /// - /// \param node Not used by the function - /// \param ast_node Ast node which is checked - /// \return std::string error - std::string return_error_pointer(ast::Ast& node, const std::shared_ptr& ast_node); - /// Takes as parameter the ast::Ast and checks if the /// functions "bbcore_read" and "bbcore_write" are defined /// in any of the ast::Ast VERBATIM blocks. The function is From 6ea13b5847c8b0ef04fc1204a8e8be3d74e254c2 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 15 Mar 2022 15:46:04 +0100 Subject: [PATCH 420/871] Call get_node_name only on nodes that get it (BlueBrain/nmodl#806) * Handle the fact that nodes can be without arguments NMODL Repo SHA: BlueBrain/nmodl@0411cc345eda4ed5ded396c86d305a2415e08df6 --- src/nmodl/codegen/codegen_c_visitor.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index b7bfc5e1ee..b49d0f070c 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1412,13 +1412,25 @@ void CodegenCVisitor::print_function_call(const FunctionCall& node) { auto arguments = node.get_arguments(); printer->add_text("{}("_format(function_name)); + bool possible_nt_variable_types{false}; + if (!defined_method(name)) { + possible_nt_variable_types = arguments.front()->get_node_type() == AstNodeType::NAME && + arguments.front()->get_node_type() == AstNodeType::STRING && + arguments.front()->get_node_type() == + AstNodeType::CONSTANT_VAR && + arguments.front()->get_node_type() == AstNodeType::VAR_NAME && + arguments.front()->get_node_type() == AstNodeType::LOCAL_VAR; + } if (defined_method(name)) { printer->add_text(internal_method_arguments()); if (!arguments.empty()) { printer->add_text(", "); } } else if (nmodl::details::needs_neuron_thread_first_arg(function_name) && - arguments.front()->get_node_name() != "nt") { + (!possible_nt_variable_types || arguments.front()->get_node_name() != "nt")) { + // If the first argument is not `nt` we should add it. + // We compare with type because expression `a+b` is not `nt` but don't have `get_node_name` + // implemented arguments.insert(arguments.begin(), std::make_shared("nt")); } From d891050d5f94363c17f78aaecf7aa1de4f42c9df Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 16 Mar 2022 16:29:10 +0100 Subject: [PATCH 421/871] Fixed suffix_random_string and added explanations (BlueBrain/nmodl#824) * Use only one random suffix for each original variable name in sympy solver visitor * Fixes bug with adding random suffix for unneeded variables NMODL Repo SHA: BlueBrain/nmodl@94ae8f5f18ebdd6fa2428824788a97fceb538752 --- src/nmodl/visitors/visitor_utils.cpp | 30 ++++++++++++++++++---------- src/nmodl/visitors/visitor_utils.hpp | 25 +++++++++++++++-------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 903fa6abef..de8663e07b 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -31,18 +31,28 @@ using nmodl::utils::UseNumbersInString; std::string suffix_random_string(const std::set& vars, const std::string& original_string, const UseNumbersInString use_num) { + // If the "original_string" is not in the set of the variables to check then + // return the "original_string" without suffix + if (vars.find(original_string) == vars.end()) { + return original_string; + } std::string new_string = original_string; - std::string random_string; auto& singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); - // Check if there is a variable defined in the mod file as original_string or that - // has original_string as prefix and, if yes, - // try to use a different string in the form "original_string"_"random_string" - - const auto it = vars.lower_bound(new_string); - while ((it != vars.end()) && new_string == it->substr(0, new_string.size())) { - random_string = singleton_random_string_class.reset_random_string(original_string, use_num); - new_string = original_string; - new_string += "_" + random_string; + // Check if there is a variable defined in the mod file and, if yes, try to use + // a different string in the form "original_string"_"random_string" + // If there is already a "random_string" assigned to the "originl_string" return it + if (singleton_random_string_class.random_string_exists(original_string)) { + const auto random_suffix = "_" + + singleton_random_string_class.get_random_string(original_string); + new_string = original_string + random_suffix; + } else { + // Check if the "random_string" already exists in the set of variables and if it does try + // to find another random string to add as suffix + while (vars.find(new_string) != vars.end()) { + const auto random_suffix = + "_" + singleton_random_string_class.reset_random_string(original_string, use_num); + new_string = original_string + random_suffix; + } } return new_string; } diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index f190bf9e93..8ef8419351 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -25,14 +25,23 @@ namespace visitor { using nmodl::utils::UseNumbersInString; -/// Return a std::string in the form "original_string"_"random_string", where -/// random_string is a string defined in the nmodl::utils::SingletonRandomString -/// for the original_string. Vars is a const ref to std::set which -/// holds the names that need to be checked for uniqueness. Choose if the -/// "random_string" will include numbers using "use_num" -/// We make sure that the new string does not match any string in vars AND is not -/// a prefix for any string in vars. In this way, appending to the result -/// will always create new unique strings +/** + * \brief Return the "original_string" with a random suffix if "original_string" exists in "vars" + * + * Return a std::string in the form "original_string"_"random_string", where + * random_string is a string defined in the nmodl::utils::SingletonRandomString + * for the original_string. Vars is a const ref to std::set which + * holds the names that need to be checked for uniqueness. Choose if the + * "random_string" will include numbers using "use_num" + * + * \param vars a const ref to std::set which holds the names of the variables we should + * check for uniqueness. Normally this is a vector of all the variables defined in the + * mod file. + * \param original_string the original string to be suffixed with a random string + * \param use_num a UseNumbersInString enum value to choose if the random string will include + * numbers + * \return std::string the new string with the proper suffix if needed + */ std::string suffix_random_string( const std::set& vars, const std::string& original_string, From 096b8d560fcdee892df1396d1a62ed9a77470249 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Thu, 17 Mar 2022 00:01:59 +0100 Subject: [PATCH 422/871] Get rid of one unnecessary shared_ptr (BlueBrain/nmodl#826) The field ast in GlobalToRangeVisitor doesn't need to be a shared pointer, so we store it as a reference that is initialized at construction time. NMODL Repo SHA: BlueBrain/nmodl@c65b86a16d019ef8723d617e3043502e239674c2 --- src/nmodl/main.cpp | 2 +- src/nmodl/visitors/global_var_visitor.cpp | 2 +- src/nmodl/visitors/global_var_visitor.hpp | 6 +++--- test/nmodl/transpiler/unit/visitor/global_to_range.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 0e27e6a349..c7af4ca14e 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -355,7 +355,7 @@ int main(int argc, const char* argv[]) { // make sure to run the GlobalToRange visitor after all the // reinitializations of Symtab logger->info("Running GlobalToRange visitor"); - GlobalToRangeVisitor(ast).visit_program(*ast); + GlobalToRangeVisitor(*ast).visit_program(*ast); SymtabVisitor(update_symtab).visit_program(*ast); ast_to_nmodl(*ast, filepath("global_to_range")); } diff --git a/src/nmodl/visitors/global_var_visitor.cpp b/src/nmodl/visitors/global_var_visitor.cpp index 7ce72436e1..3488fdd6d0 100644 --- a/src/nmodl/visitors/global_var_visitor.cpp +++ b/src/nmodl/visitors/global_var_visitor.cpp @@ -27,7 +27,7 @@ void GlobalToRangeVisitor::visit_neuron_block(ast::NeuronBlock& node) { auto& statement_block = node.get_statement_block(); auto& statements = (*statement_block).get_statements(); - const auto& symbol_table = ast->get_symbol_table(); + const auto& symbol_table = ast.get_symbol_table(); for (auto& statement: statements) { /// only process global statements diff --git a/src/nmodl/visitors/global_var_visitor.hpp b/src/nmodl/visitors/global_var_visitor.hpp index 4774958fbf..c16b99decd 100644 --- a/src/nmodl/visitors/global_var_visitor.hpp +++ b/src/nmodl/visitors/global_var_visitor.hpp @@ -61,7 +61,7 @@ namespace visitor { class GlobalToRangeVisitor: public AstVisitor { private: /// ast::Ast* node - std::shared_ptr ast; + const ast::Program& ast; public: /// \name Ctor & dtor @@ -71,8 +71,8 @@ class GlobalToRangeVisitor: public AstVisitor { GlobalToRangeVisitor() = delete; /// Constructor that takes as parameter the AST - explicit GlobalToRangeVisitor(std::shared_ptr node) - : ast(std::move(node)) {} + explicit GlobalToRangeVisitor(const ast::Program& node) + : ast(node) {} /// \} diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 8fbc4a3199..6b77c47a9d 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -33,7 +33,7 @@ std::shared_ptr run_global_to_var_visitor(const std::string& text) SymtabVisitor().visit_program(*ast); PerfVisitor().visit_program(*ast); - GlobalToRangeVisitor(ast).visit_program(*ast); + GlobalToRangeVisitor(*ast).visit_program(*ast); SymtabVisitor().visit_program(*ast); return ast; } From 56e9c1a4c63027e91d784c85fa16a082f919992f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 18 Mar 2022 11:12:31 +0100 Subject: [PATCH 423/871] Stop using suffix and use protected names (BlueBrain/nmodl#825) * Use protected variables starting with nmodl_ NMODL Repo SHA: BlueBrain/nmodl@779564325ad14f37f01c23087161e5617c7c6649 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 11 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 6 +- src/nmodl/codegen/codegen_c_visitor.cpp | 81 +- src/nmodl/codegen/codegen_c_visitor.hpp | 6 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 23 +- .../transpiler/unit/visitor/sympy_solver.cpp | 967 +++++++++--------- 6 files changed, 502 insertions(+), 592 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 93b04ef480..d13df4e82f 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -139,15 +139,12 @@ void CodegenAccVisitor::print_net_send_buffering_grow() { // can not grow buffer during gpu execution } -void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, - int N, - const std::string& Xm, - const std::string& Jm, - const std::string& Fm) { +void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { if (N <= 4) { - printer->add_line("{0} = {1}.inverse()*{2};"_format(Xm, Jm, Fm)); + printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { - printer->add_line("{0} = partialPivLu<{1}>({2}, {3});"_format(Xm, N, Jm, Fm)); + printer->add_line( + "nmodl_eigen_xm = partialPivLu<{}>nmodl_eigen_jm, nmodl_eigen_fm);"_format(N)); } } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index ed37c7fd52..26f5ecba10 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -128,11 +128,7 @@ class CodegenAccVisitor: public CodegenCVisitor { void print_net_send_buffering_grow() override; - void print_eigen_linear_solver(const std::string& float_type, - int N, - const std::string& Xm, - const std::string& Jm, - const std::string& Fm) override; + void print_eigen_linear_solver(const std::string& float_type, int N) override; public: diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index b49d0f070c..af785e3f03 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1758,17 +1758,6 @@ void CodegenCVisitor::print_function(const ast::FunctionBlock& node) { print_function_procedure_helper(node); } - -std::string CodegenCVisitor::find_var_unique_name(const std::string& original_name) const { - auto& singleton_random_string_class = nmodl::utils::SingletonRandomString<4>::instance(); - std::string unique_name = original_name; - if (singleton_random_string_class.random_string_exists(original_name)) { - unique_name = original_name; - unique_name += "_" + singleton_random_string_class.get_random_string(original_name); - }; - return unique_name; -} - /** * @brief Checks whether the functor_block generated by sympy solver modifies any variable outside * its scope. If it does then return false, so that the operator() of the struct functor of the @@ -1821,20 +1810,10 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv // solution vector to store copy of state vars for Newton solver printer->add_newline(); - // Check if there is a variable defined in the mod file as X, J, Jm or F and if yes - // try to use a different string for the matrices created by sympy in the form - // X_, J_, Jm_ and F_ - std::string X = find_var_unique_name("X"); - std::string Xm = find_var_unique_name("Xm"); - std::string J = find_var_unique_name("J"); - std::string Jm = find_var_unique_name("Jm"); - std::string F = find_var_unique_name("F"); - std::string Fm = find_var_unique_name("Fm"); - auto float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{}, {}, 1> {};"_format(float_type, N, Xm)); - printer->add_line("{}* {} = {}.data();"_format(float_type, X, Xm)); + printer->add_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;"_format(float_type, N)); + printer->add_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();"_format(float_type)); print_statement_block(*node.get_setup_x_block(), false, false); @@ -1867,19 +1846,14 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv const auto& functor_block = *node.get_functor_block(); printer->add_text( - "void operator()(const Eigen::Matrix<{0}, {1}, 1>& {2}, Eigen::Matrix<{0}, {1}, " - "1>& {3}, " - "Eigen::Matrix<{0}, {1}, {1}>& {4}) {5}"_format( - float_type, - N, - Xm, - Fm, - Jm, - is_functor_const(variable_block, functor_block) ? "const " : "")); + "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " + "1>& nmodl_eigen_fm, " + "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}"_format( + float_type, N, is_functor_const(variable_block, functor_block) ? "const " : "")); printer->start_block(); - printer->add_line("const {}* {} = {}.data();"_format(float_type, X, Xm)); - printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); - printer->add_line("{}* {} = {}.data();"_format(float_type, F, Fm)); + printer->add_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();"_format(float_type)); + printer->add_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();"_format(float_type)); + printer->add_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();"_format(float_type)); print_statement_block(functor_block, false, false); printer->end_block(2); @@ -1897,7 +1871,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->add_line("functor newton_functor(nt, inst, id, pnodecount, v, indexes);"); printer->add_line("newton_functor.initialize();"); printer->add_line( - "int newton_iterations = nmodl::newton::newton_solver({}, newton_functor);"_format(Xm)); + "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); // assign newton solver results in matrix X to state vars print_statement_block(*node.get_update_states_block(), false, false); @@ -1907,47 +1881,34 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) { printer->add_newline(); - // Check if there is a variable defined in the mod file as X, J, Jm or F and if yes - // try to use a different string for the matrices created by sympy in the form - // X_, J_, Jm_ and F_ - std::string X = find_var_unique_name("X"); - std::string Xm = find_var_unique_name("Xm"); - std::string J = find_var_unique_name("J"); - std::string Jm = find_var_unique_name("Jm"); - std::string F = find_var_unique_name("F"); - std::string Fm = find_var_unique_name("Fm"); - const std::string float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{0}, {1}, 1> {2}, {3};"_format(float_type, N, Xm, Fm)); - printer->add_line("Eigen::Matrix<{0}, {1}, {1}> {2};"_format(float_type, N, Jm)); - printer->add_line("{}* {} = {}.data();"_format(float_type, X, Xm)); - printer->add_line("{}* {} = {}.data();"_format(float_type, J, Jm)); - printer->add_line("{}* {} = {}.data();"_format(float_type, F, Fm)); + printer->add_line( + "Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;"_format(float_type, N)); + printer->add_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;"_format(float_type, N)); + printer->add_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();"_format(float_type)); + printer->add_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();"_format(float_type)); + printer->add_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();"_format(float_type)); print_statement_block(*node.get_variable_block(), false, false); print_statement_block(*node.get_initialize_block(), false, false); print_statement_block(*node.get_setup_x_block(), false, false); printer->add_newline(); - print_eigen_linear_solver(float_type, N, Xm, Jm, Fm); + print_eigen_linear_solver(float_type, N); printer->add_newline(); print_statement_block(*node.get_update_states_block(), false, false); print_statement_block(*node.get_finalize_block(), false, false); } -void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, - int N, - const std::string& Xm, - const std::string& Jm, - const std::string& Fm) { +void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { if (N <= 4) { // Faster compared to LU, given the template specialization in Eigen. - printer->add_line("{0} = {1}.inverse()*{2};"_format(Xm, Jm, Fm)); + printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { printer->add_line( - "{0} = Eigen::PartialPivLU>>({3}).solve({4});"_format( - Xm, float_type, N, Jm, Fm)); + "nmodl_eigen_xm = Eigen::PartialPivLU>>(nmodl_eigen_jm).solve(nmodl_eigen_fm);"_format( + float_type, N)); } } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index f9353bbc4a..8062a45420 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1941,11 +1941,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void visit_function_call(const ast::FunctionCall& node) override; void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; - virtual void print_eigen_linear_solver(const std::string& float_type, - int N, - const std::string& Xm, - const std::string& Jm, - const std::string& Fm); + virtual void print_eigen_linear_solver(const std::string& float_type, int N); void visit_if_statement(const ast::IfStatement& node) override; void visit_indexed_name(const ast::IndexedName& node) override; void visit_integer(const ast::Integer& node) override; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 8c1e09b331..58683d601a 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -161,19 +161,10 @@ void SympySolverVisitor::construct_eigen_solver_block( const std::vector& pre_solve_statements, const std::vector& solutions, bool linear) { - // Provide random string to append to X, J, Jm and F matrices that - // are produced by sympy - std::string unique_X = suffix_random_string(vars, "X"); - std::string unique_J = suffix_random_string(vars, "J"); - std::string unique_Jm = suffix_random_string(vars, "Jm"); - std::string unique_F = suffix_random_string(vars, "F"); - - // filter solutions for matrices named "X", "J", "Jm" and "F" and change them to - // unique_X, unique_J, unique_Jm and unique_F respectively - auto solutions_filtered = filter_string_vector(solutions, "X[", unique_X + "["); - solutions_filtered = filter_string_vector(solutions_filtered, "J[", unique_J + "["); - solutions_filtered = filter_string_vector(solutions_filtered, "Jm[", unique_Jm + "["); - solutions_filtered = filter_string_vector(solutions_filtered, "F[", unique_F + "["); + auto solutions_filtered = filter_string_vector(solutions, "X[", "nmodl_eigen_x["); + solutions_filtered = filter_string_vector(solutions_filtered, "J[", "nmodl_eigen_j["); + solutions_filtered = filter_string_vector(solutions_filtered, "Jm[", "nmodl_eigen_jm["); + solutions_filtered = filter_string_vector(solutions_filtered, "F[", "nmodl_eigen_f["); for (const auto& sol: solutions_filtered) { logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); @@ -182,12 +173,12 @@ void SympySolverVisitor::construct_eigen_solver_block( std::vector pre_solve_statements_and_setup_x_eqs(pre_solve_statements); std::vector update_statements; for (int i = 0; i < state_vars.size(); i++) { - auto update_state = state_vars[i] + " = " + unique_X + "[" + std::to_string(i) + "]"; - auto setup_x = unique_X + "[" + std::to_string(i) + "] = " + state_vars[i]; + auto update_state = state_vars[i] + " = nmodl_eigen_x[" + std::to_string(i) + "]"; + auto setup_x = "nmodl_eigen_x[" + std::to_string(i) + "] = " + state_vars[i]; pre_solve_statements_and_setup_x_eqs.push_back(setup_x); update_statements.push_back(update_state); - logger->debug("SympySolverVisitor :: setup_", unique_X, ": {}", setup_x); + logger->debug("SympySolverVisitor :: setup_x_eigen: {}", setup_x); logger->debug("SympySolverVisitor :: update_state: {}", update_state); } diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 80a5d29e84..5bdfd25478 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -602,37 +602,6 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", "[visitor][sympy][derivimplicit]") { - GIVEN("Derivative block with derivimplicit solver method. Check avoided name-clash") { - std::string nmodl_text = R"( - UNITS { - F = (faraday) (coulombs) - } - STATE { - x - } - BREAKPOINT { - SOLVE integrate METHOD derivimplicit - } - DERIVATIVE integrate { - x' = x + 1 - } - )"; - THEN("SympySolver correctly renames F vector") { - const std::string probable_explaination = - "Sympy_visitor left the standard F name for the F_vector. Name " - "clash with F faraday."; - CAPTURE(nmodl_text); - CAPTURE(probable_explaination); - - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK); - - REQUIRE(result.size() == 1); - REQUIRE(result[0].find("F_") != std::string::npos); - } - } - - GIVEN("Derivative block with derivimplicit solver method and conditional block") { std::string nmodl_text = R"( STATE { @@ -658,12 +627,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", } old_m = m }{ - X[0] = m + nmodl_eigen_x[0] = m }{ - F[0] = (-X[0]*dt+dt*mInf+mTau*(-X[0]+old_m))/mTau - J[0] = -(dt+mTau)/mTau + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*mInf+mTau*(-nmodl_eigen_x[0]+old_m))/mTau + nmodl_eigen_j[0] = -(dt+mTau)/mTau }{ - m = X[0] + m = nmodl_eigen_x[0] }{ } })"; @@ -696,18 +665,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_y = y old_x = x }{ - X[0] = x - X[1] = y + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y }{ - F[0] = -X[1]+a*dt+old_y - J[0] = 0 - J[2] = -1.0 - F[1] = -X[0]+b*dt+old_x - J[1] = -1.0 - J[3] = 0 + nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_y + nmodl_eigen_j[0] = 0 + nmodl_eigen_j[2] = -1.0 + nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_x + nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[3] = 0 }{ - x = X[0] - y = X[1] + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] }{ } })"; @@ -740,18 +709,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_M_1 = M[1] old_M_0 = M[0] }{ - X[0] = M[0] - X[1] = M[1] + nmodl_eigen_x[0] = M[0] + nmodl_eigen_x[1] = M[1] }{ - F[0] = -X[1]+a*dt+old_M_1 - J[0] = 0 - J[2] = -1.0 - F[1] = -X[0]+b*dt+old_M_0 - J[1] = -1.0 - J[3] = 0 + nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_M_1 + nmodl_eigen_j[0] = 0 + nmodl_eigen_j[2] = -1.0 + nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_M_0 + nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[3] = 0 }{ - M[0] = X[0] - M[1] = X[1] + M[0] = nmodl_eigen_x[0] + M[1] = nmodl_eigen_x[1] }{ } })"; @@ -785,19 +754,19 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_x = x old_y = y }{ - X[0] = x - X[1] = y + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y }{ - F[0] = -X[0]+a*dt+old_x - J[0] = -1.0 - J[2] = 0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[2] = 0 b = b+1 - F[1] = -X[1]+b*dt+old_y - J[1] = 0 - J[3] = -1.0 + nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y + nmodl_eigen_j[1] = 0 + nmodl_eigen_j[3] = -1.0 }{ - x = X[0] - y = X[1] + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] }{ } })"; @@ -862,18 +831,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_x = x old_y = y }{ - X[0] = x - X[1] = y + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y }{ - F[0] = -X[0]+a*dt+old_x - J[0] = -1.0 - J[2] = 0 - F[1] = -X[1]+b*dt+old_y - J[1] = 0 - J[3] = -1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[2] = 0 + nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y + nmodl_eigen_j[1] = 0 + nmodl_eigen_j[3] = -1.0 }{ - x = X[0] - y = X[1] + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] }{ } } @@ -912,21 +881,21 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_x = x old_y = y }{ - X[0] = x - X[1] = y + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y }{ - F[0] = -X[0]+X[1]*a*dt+b*dt+old_x - J[0] = -1.0 - J[2] = a*dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[2] = a*dt IF (b == 1) { a = a+1 } - F[1] = X[0]*dt+X[1]*a*dt-X[1]+old_y - J[1] = dt - J[3] = a*dt-1.0 + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y + nmodl_eigen_j[1] = dt + nmodl_eigen_j[3] = a*dt-1.0 }{ - x = X[0] - y = X[1] + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] }{ } })"; @@ -938,21 +907,21 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_x = x old_y = y }{ - X[0] = x - X[1] = y + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y }{ - F[0] = -X[0]+X[1]*a*dt+b*dt+old_x - J[0] = -1.0 - J[2] = a*dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[2] = a*dt IF (b == 1) { a = a+1 } - F[1] = X[0]*dt+X[1]*a*dt-X[1]+old_y - J[1] = dt - J[3] = a*dt-1.0 + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y + nmodl_eigen_j[1] = dt + nmodl_eigen_j[3] = a*dt-1.0 }{ - x = X[0] - y = X[1] + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] }{ } })"; @@ -992,26 +961,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_y = y old_z = z }{ - X[0] = x - X[1] = y - X[2] = z - }{ - F[0] = -X[0]+X[2]*a*dt+b*dt*h+old_x - J[0] = -1.0 - J[3] = 0 - J[6] = a*dt - F[1] = 2.0*X[0]*dt-X[1]+c*dt+old_y - J[1] = 2.0*dt - J[4] = -1.0 - J[7] = 0 - F[2] = -X[1]*dt+X[2]*d*dt-X[2]+old_z - J[2] = 0 - J[5] = -dt - J[8] = d*dt-1.0 - }{ - x = X[0] - y = X[1] - z = X[2] + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y + nmodl_eigen_x[2] = z + }{ + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[3] = 0 + nmodl_eigen_j[6] = a*dt + nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y + nmodl_eigen_j[1] = 2.0*dt + nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[7] = 0 + nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z + nmodl_eigen_j[2] = 0 + nmodl_eigen_j[5] = -dt + nmodl_eigen_j[8] = d*dt-1.0 + }{ + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] + z = nmodl_eigen_x[2] }{ } })"; @@ -1024,26 +993,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_y = y old_z = z }{ - X[0] = x - X[1] = y - X[2] = z - }{ - F[0] = -X[0]+X[2]*a*dt+b*dt*h+old_x - J[0] = -1.0 - J[3] = 0 - J[6] = a*dt - F[1] = 2.0*X[0]*dt-X[1]+c*dt+old_y - J[1] = 2.0*dt - J[4] = -1.0 - J[7] = 0 - F[2] = -X[1]*dt+X[2]*d*dt-X[2]+old_z - J[2] = 0 - J[5] = -dt - J[8] = d*dt-1.0 - }{ - x = X[0] - y = X[1] - z = X[2] + nmodl_eigen_x[0] = x + nmodl_eigen_x[1] = y + nmodl_eigen_x[2] = z + }{ + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[3] = 0 + nmodl_eigen_j[6] = a*dt + nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y + nmodl_eigen_j[1] = 2.0*dt + nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[7] = 0 + nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z + nmodl_eigen_j[2] = 0 + nmodl_eigen_j[5] = -dt + nmodl_eigen_j[8] = d*dt-1.0 + }{ + x = nmodl_eigen_x[0] + y = nmodl_eigen_x[1] + z = nmodl_eigen_x[2] }{ } })"; @@ -1079,18 +1048,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_mc = mc old_m = m }{ - X[0] = mc - X[1] = m + nmodl_eigen_x[0] = mc + nmodl_eigen_x[1] = m }{ - F[0] = -X[0]*a*dt-X[0]+X[1]*b*dt+old_mc - J[0] = -a*dt-1.0 - J[2] = b*dt - F[1] = X[0]*a*dt-X[1]*b*dt-X[1]+old_m - J[1] = a*dt - J[3] = -b*dt-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc + nmodl_eigen_j[0] = -a*dt-1.0 + nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m + nmodl_eigen_j[1] = a*dt + nmodl_eigen_j[3] = -b*dt-1.0 }{ - mc = X[0] - m = X[1] + mc = nmodl_eigen_x[0] + m = nmodl_eigen_x[1] }{ } })"; @@ -1122,18 +1091,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ old_mc = mc }{ - X[0] = mc - X[1] = m + nmodl_eigen_x[0] = mc + nmodl_eigen_x[1] = m }{ - F[0] = -X[0]*a*dt-X[0]+X[1]*b*dt+old_mc - J[0] = -a*dt-1.0 - J[2] = b*dt - F[1] = -X[0]-X[1]+1.0 - J[1] = -1.0 - J[3] = -1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc + nmodl_eigen_j[0] = -a*dt-1.0 + nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[1] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]+1.0 + nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[3] = -1.0 }{ - mc = X[0] - m = X[1] + mc = nmodl_eigen_x[0] + m = nmodl_eigen_x[1] }{ } })"; @@ -1168,18 +1137,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_mc = mc old_m = m }{ - X[0] = mc - X[1] = m + nmodl_eigen_x[0] = mc + nmodl_eigen_x[1] = m }{ - F[0] = -X[0]*a*dt-X[0]+X[1]*b*dt+old_mc - J[0] = -a*dt-1.0 - J[2] = b*dt - F[1] = X[0]*a*dt-X[1]*b*dt-X[1]+old_m - J[1] = a*dt - J[3] = -b*dt-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc + nmodl_eigen_j[0] = -a*dt-1.0 + nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m + nmodl_eigen_j[1] = a*dt + nmodl_eigen_j[3] = -b*dt-1.0 }{ - mc = X[0] - m = X[1] + mc = nmodl_eigen_x[0] + m = nmodl_eigen_x[1] }{ } })"; @@ -1219,48 +1188,48 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_o1 = o1 old_p0 = p0 }{ - X[0] = c1 - X[1] = o1 - X[2] = o2 - X[3] = p0 - X[4] = p1 - }{ - F[0] = -X[0]*alpha*dt-X[0]+X[1]*beta*dt+old_c1 - J[0] = -alpha*dt-1.0 - J[5] = beta*dt - J[10] = 0 - J[15] = 0 - J[20] = 0 - F[1] = X[0]*alpha*dt-X[1]*beta*dt-X[1]*dt*k3p-X[1]+X[2]*dt*k4+old_o1 - J[1] = alpha*dt - J[6] = -beta*dt-dt*k3p-1.0 - J[11] = dt*k4 - J[16] = 0 - J[21] = 0 - F[2] = -X[0]-X[1]-X[2]+1.0 - J[2] = -1.0 - J[7] = -1.0 - J[12] = -1.0 - J[17] = 0 - J[22] = 0 - F[3] = -X[3]*dt*k1ca-X[3]+X[4]*dt*k2+old_p0 - J[3] = 0 - J[8] = 0 - J[13] = 0 - J[18] = -dt*k1ca-1.0 - J[23] = dt*k2 - F[4] = -X[3]-X[4]+1.0 - J[4] = 0 - J[9] = 0 - J[14] = 0 - J[19] = -1.0 - J[24] = -1.0 - }{ - c1 = X[0] - o1 = X[1] - o2 = X[2] - p0 = X[3] - p1 = X[4] + nmodl_eigen_x[0] = c1 + nmodl_eigen_x[1] = o1 + nmodl_eigen_x[2] = o2 + nmodl_eigen_x[3] = p0 + nmodl_eigen_x[4] = p1 + }{ + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*beta*dt+old_c1 + nmodl_eigen_j[0] = -alpha*dt-1.0 + nmodl_eigen_j[5] = beta*dt + nmodl_eigen_j[10] = 0 + nmodl_eigen_j[15] = 0 + nmodl_eigen_j[20] = 0 + nmodl_eigen_f[1] = nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[1]*beta*dt-nmodl_eigen_x[1]*dt*k3p-nmodl_eigen_x[1]+nmodl_eigen_x[2]*dt*k4+old_o1 + nmodl_eigen_j[1] = alpha*dt + nmodl_eigen_j[6] = -beta*dt-dt*k3p-1.0 + nmodl_eigen_j[11] = dt*k4 + nmodl_eigen_j[16] = 0 + nmodl_eigen_j[21] = 0 + nmodl_eigen_f[2] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]-nmodl_eigen_x[2]+1.0 + nmodl_eigen_j[2] = -1.0 + nmodl_eigen_j[7] = -1.0 + nmodl_eigen_j[12] = -1.0 + nmodl_eigen_j[17] = 0 + nmodl_eigen_j[22] = 0 + nmodl_eigen_f[3] = -nmodl_eigen_x[3]*dt*k1ca-nmodl_eigen_x[3]+nmodl_eigen_x[4]*dt*k2+old_p0 + nmodl_eigen_j[3] = 0 + nmodl_eigen_j[8] = 0 + nmodl_eigen_j[13] = 0 + nmodl_eigen_j[18] = -dt*k1ca-1.0 + nmodl_eigen_j[23] = dt*k2 + nmodl_eigen_f[4] = -nmodl_eigen_x[3]-nmodl_eigen_x[4]+1.0 + nmodl_eigen_j[4] = 0 + nmodl_eigen_j[9] = 0 + nmodl_eigen_j[14] = 0 + nmodl_eigen_j[19] = -1.0 + nmodl_eigen_j[24] = -1.0 + }{ + c1 = nmodl_eigen_x[0] + o1 = nmodl_eigen_x[1] + o2 = nmodl_eigen_x[2] + p0 = nmodl_eigen_x[3] + p1 = nmodl_eigen_x[4] }{ } })"; @@ -1296,12 +1265,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ old_W_0 = W[0] }{ - X[0] = W[0] + nmodl_eigen_x[0] = W[0] }{ - F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3.0*dt*A[1]+old_W_0 - J[0] = -dt*A[0]+dt*B[0]-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 + nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 }{ - W[0] = X[0] + W[0] = nmodl_eigen_x[0] }{ } })"; @@ -1337,18 +1306,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_M_0 = M[0] old_M_1 = M[1] }{ - X[0] = M[0] - X[1] = M[1] + nmodl_eigen_x[0] = M[0] + nmodl_eigen_x[1] = M[1] }{ - F[0] = -X[0]*dt*A[0]-X[0]+X[1]*dt*B[0]+old_M_0 - J[0] = -dt*A[0]-1.0 - J[2] = dt*B[0] - F[1] = X[0]*dt*A[1]-X[1]*dt*B[1]-X[1]+old_M_1 - J[1] = dt*A[1] - J[3] = -dt*B[1]-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*B[0]+old_M_0 + nmodl_eigen_j[0] = -dt*A[0]-1.0 + nmodl_eigen_j[2] = dt*B[0] + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*A[1]-nmodl_eigen_x[1]*dt*B[1]-nmodl_eigen_x[1]+old_M_1 + nmodl_eigen_j[1] = dt*A[1] + nmodl_eigen_j[3] = -dt*B[1]-1.0 }{ - M[0] = X[0] - M[1] = X[1] + M[0] = nmodl_eigen_x[0] + M[1] = nmodl_eigen_x[1] }{ } })"; @@ -1382,12 +1351,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ old_W_0 = W[0] }{ - X[0] = W[0] + nmodl_eigen_x[0] = W[0] }{ - F[0] = -X[0]*dt*A[0]+X[0]*dt*B[0]-X[0]+3.0*dt*A[1]+old_W_0 - J[0] = -dt*A[0]+dt*B[0]-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 + nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 }{ - W[0] = X[0] + W[0] = nmodl_eigen_x[0] }{ } })"; @@ -1424,26 +1393,26 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_h = h old_n = n }{ - X[0] = m - X[1] = h - X[2] = n - }{ - F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]-3.0*X[1]*dt+old_m))/mtau - F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau - F[2] = (-X[2]*dt+dt*ninf+ntau*(-X[2]+old_n))/ntau - J[0] = -(dt+mtau)/mtau - J[3] = -3.0*dt - J[6] = 0 - J[1] = 2.0*X[0]*dt - J[4] = -(dt+htau)/htau - J[7] = 0 - J[2] = 0 - J[5] = 0 - J[8] = -(dt+ntau)/ntau - }{ - m = X[0] - h = X[1] - n = X[2] + nmodl_eigen_x[0] = m + nmodl_eigen_x[1] = h + nmodl_eigen_x[2] = n + }{ + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]-3.0*nmodl_eigen_x[1]*dt+old_m))/mtau + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau + nmodl_eigen_f[2] = (-nmodl_eigen_x[2]*dt+dt*ninf+ntau*(-nmodl_eigen_x[2]+old_n))/ntau + nmodl_eigen_j[0] = -(dt+mtau)/mtau + nmodl_eigen_j[3] = -3.0*dt + nmodl_eigen_j[6] = 0 + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt + nmodl_eigen_j[4] = -(dt+htau)/htau + nmodl_eigen_j[7] = 0 + nmodl_eigen_j[2] = 0 + nmodl_eigen_j[5] = 0 + nmodl_eigen_j[8] = -(dt+ntau)/ntau + }{ + m = nmodl_eigen_x[0] + h = nmodl_eigen_x[1] + n = nmodl_eigen_x[2] }{ } })"; @@ -1483,18 +1452,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_m = m old_h = h }{ - X[0] = m - X[1] = h + nmodl_eigen_x[0] = m + nmodl_eigen_x[1] = h }{ - F[0] = (-X[0]*dt+dt*minf+mtau*(-X[0]+old_m))/mtau - F[1] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau - J[0] = -(dt+mtau)/mtau - J[2] = 0 - J[1] = 2.0*X[0]*dt - J[3] = -(dt+htau)/htau + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+old_m))/mtau + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau + nmodl_eigen_j[0] = -(dt+mtau)/mtau + nmodl_eigen_j[2] = 0 + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt + nmodl_eigen_j[3] = -(dt+htau)/htau }{ - m = X[0] - h = X[1] + m = nmodl_eigen_x[0] + h = nmodl_eigen_x[1] }{ } })"; @@ -1506,18 +1475,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", old_h = h old_m = m }{ - X[0] = m - X[1] = h + nmodl_eigen_x[0] = m + nmodl_eigen_x[1] = h }{ - F[0] = (-X[1]*dt+dt*hinf+htau*(pow(X[0], 2)*dt-X[1]+old_h))/htau - F[1] = (-X[0]*dt+dt*minf+mtau*(-X[0]+X[1]*dt+old_m))/mtau - J[0] = 2.0*X[0]*dt - J[2] = -(dt+htau)/htau - J[1] = -(dt+mtau)/mtau - J[3] = dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau + nmodl_eigen_f[1] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt+old_m))/mtau + nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0]*dt + nmodl_eigen_j[2] = -(dt+htau)/htau + nmodl_eigen_j[1] = -(dt+mtau)/mtau + nmodl_eigen_j[3] = dt }{ - m = X[0] - h = X[1] + m = nmodl_eigen_x[0] + h = nmodl_eigen_x[1] }{ } })"; @@ -1819,35 +1788,35 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { EIGEN_LINEAR_SOLVE[4]{ }{ }{ - X[0] = w - X[1] = x - X[2] = y - X[3] = z - F[0] = 0 - F[1] = 5.343*a - F[2] = a-0.84199999999999997*pow(b, 2) - F[3] = -1.43543/c - J[0] = -1.0 - J[4] = 0 - J[8] = -2.0 - J[12] = -0.3125 - J[1] = 0 - J[5] = -1.0 - J[9] = -4.0*c - J[13] = 0 - J[2] = 0 - J[6] = -1/b - J[10] = 1.0 - J[14] = -1.0 - J[3] = 0 - J[7] = -1.0 - J[11] = -1.3 - J[15] = 0.10000000000000001/(pow(a, 2)*b) - }{ - w = X[0] - x = X[1] - y = X[2] - z = X[3] + nmodl_eigen_x[0] = w + nmodl_eigen_x[1] = x + nmodl_eigen_x[2] = y + nmodl_eigen_x[3] = z + nmodl_eigen_f[0] = 0 + nmodl_eigen_f[1] = 5.343*a + nmodl_eigen_f[2] = a-0.84199999999999997*pow(b, 2) + nmodl_eigen_f[3] = -1.43543/c + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[4] = 0 + nmodl_eigen_j[8] = -2.0 + nmodl_eigen_j[12] = -0.3125 + nmodl_eigen_j[1] = 0 + nmodl_eigen_j[5] = -1.0 + nmodl_eigen_j[9] = -4.0*c + nmodl_eigen_j[13] = 0 + nmodl_eigen_j[2] = 0 + nmodl_eigen_j[6] = -1/b + nmodl_eigen_j[10] = 1.0 + nmodl_eigen_j[14] = -1.0 + nmodl_eigen_j[3] = 0 + nmodl_eigen_j[7] = -1.0 + nmodl_eigen_j[11] = -1.3 + nmodl_eigen_j[15] = 0.10000000000000001/(pow(a, 2)*b) + }{ + w = nmodl_eigen_x[0] + x = nmodl_eigen_x[1] + y = nmodl_eigen_x[2] + z = nmodl_eigen_x[3] }{ } })"; @@ -1881,187 +1850,187 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { EIGEN_LINEAR_SOLVE[12]{ }{ }{ - X[0] = C1 - X[1] = C2 - X[2] = C3 - X[3] = C4 - X[4] = C5 - X[5] = I1 - X[6] = I2 - X[7] = I3 - X[8] = I4 - X[9] = I5 - X[10] = I6 - X[11] = O - F[0] = 0 - F[1] = 0 - F[2] = 0 - F[3] = 0 - F[4] = 0 - F[5] = 0 - F[6] = 0 - F[7] = 0 - F[8] = 0 - F[9] = 0 - F[10] = 0 - F[11] = -1.0 - J[0] = f01+fi1 - J[12] = -b01 - J[24] = 0 - J[36] = 0 - J[48] = 0 - J[60] = -bi1 - J[72] = 0 - J[84] = 0 - J[96] = 0 - J[108] = 0 - J[120] = 0 - J[132] = 0 - J[1] = -f01 - J[13] = b01+f02+fi2 - J[25] = -b02 - J[37] = 0 - J[49] = 0 - J[61] = 0 - J[73] = -bi2 - J[85] = 0 - J[97] = 0 - J[109] = 0 - J[121] = 0 - J[133] = 0 - J[2] = 0 - J[14] = -f02 - J[26] = b02+f03+fi3 - J[38] = -b03 - J[50] = 0 - J[62] = 0 - J[74] = 0 - J[86] = -bi3 - J[98] = 0 - J[110] = 0 - J[122] = 0 - J[134] = 0 - J[3] = 0 - J[15] = 0 - J[27] = -f03 - J[39] = b03+f04+fi4 - J[51] = -b04 - J[63] = 0 - J[75] = 0 - J[87] = 0 - J[99] = -bi4 - J[111] = 0 - J[123] = 0 - J[135] = 0 - J[4] = 0 - J[16] = 0 - J[28] = 0 - J[40] = -f04 - J[52] = b04+f0O+fi5 - J[64] = 0 - J[76] = 0 - J[88] = 0 - J[100] = 0 - J[112] = -bi5 - J[124] = 0 - J[136] = -b0O - J[5] = 0 - J[17] = 0 - J[29] = 0 - J[41] = 0 - J[53] = -f0O - J[65] = 0 - J[77] = 0 - J[89] = 0 - J[101] = 0 - J[113] = 0 - J[125] = -bin - J[137] = b0O+fin - J[6] = -fi1 - J[18] = 0 - J[30] = 0 - J[42] = 0 - J[54] = 0 - J[66] = bi1+f11 - J[78] = -b11 - J[90] = 0 - J[102] = 0 - J[114] = 0 - J[126] = 0 - J[138] = 0 - J[7] = 0 - J[19] = -fi2 - J[31] = 0 - J[43] = 0 - J[55] = 0 - J[67] = -f11 - J[79] = b11+bi2+f12 - J[91] = -b12 - J[103] = 0 - J[115] = 0 - J[127] = 0 - J[139] = 0 - J[8] = 0 - J[20] = 0 - J[32] = -fi3 - J[44] = 0 - J[56] = 0 - J[68] = 0 - J[80] = -f12 - J[92] = b12+bi3+f13 - J[104] = -bi3 - J[116] = 0 - J[128] = 0 - J[140] = 0 - J[9] = 0 - J[21] = 0 - J[33] = 0 - J[45] = -fi4 - J[57] = 0 - J[69] = 0 - J[81] = 0 - J[93] = -f13 - J[105] = b13+bi4+f14 - J[117] = -b14 - J[129] = 0 - J[141] = 0 - J[10] = 0 - J[22] = 0 - J[34] = 0 - J[46] = 0 - J[58] = -fi5 - J[70] = 0 - J[82] = 0 - J[94] = 0 - J[106] = -f14 - J[118] = b14+bi5+f1n - J[130] = -b1n - J[142] = 0 - J[11] = -1.0 - J[23] = -1.0 - J[35] = -1.0 - J[47] = -1.0 - J[59] = -1.0 - J[71] = -1.0 - J[83] = -1.0 - J[95] = -1.0 - J[107] = -1.0 - J[119] = -1.0 - J[131] = -1.0 - J[143] = -1.0 - }{ - C1 = X[0] - C2 = X[1] - C3 = X[2] - C4 = X[3] - C5 = X[4] - I1 = X[5] - I2 = X[6] - I3 = X[7] - I4 = X[8] - I5 = X[9] - I6 = X[10] - O = X[11] + nmodl_eigen_x[0] = C1 + nmodl_eigen_x[1] = C2 + nmodl_eigen_x[2] = C3 + nmodl_eigen_x[3] = C4 + nmodl_eigen_x[4] = C5 + nmodl_eigen_x[5] = I1 + nmodl_eigen_x[6] = I2 + nmodl_eigen_x[7] = I3 + nmodl_eigen_x[8] = I4 + nmodl_eigen_x[9] = I5 + nmodl_eigen_x[10] = I6 + nmodl_eigen_x[11] = O + nmodl_eigen_f[0] = 0 + nmodl_eigen_f[1] = 0 + nmodl_eigen_f[2] = 0 + nmodl_eigen_f[3] = 0 + nmodl_eigen_f[4] = 0 + nmodl_eigen_f[5] = 0 + nmodl_eigen_f[6] = 0 + nmodl_eigen_f[7] = 0 + nmodl_eigen_f[8] = 0 + nmodl_eigen_f[9] = 0 + nmodl_eigen_f[10] = 0 + nmodl_eigen_f[11] = -1.0 + nmodl_eigen_j[0] = f01+fi1 + nmodl_eigen_j[12] = -b01 + nmodl_eigen_j[24] = 0 + nmodl_eigen_j[36] = 0 + nmodl_eigen_j[48] = 0 + nmodl_eigen_j[60] = -bi1 + nmodl_eigen_j[72] = 0 + nmodl_eigen_j[84] = 0 + nmodl_eigen_j[96] = 0 + nmodl_eigen_j[108] = 0 + nmodl_eigen_j[120] = 0 + nmodl_eigen_j[132] = 0 + nmodl_eigen_j[1] = -f01 + nmodl_eigen_j[13] = b01+f02+fi2 + nmodl_eigen_j[25] = -b02 + nmodl_eigen_j[37] = 0 + nmodl_eigen_j[49] = 0 + nmodl_eigen_j[61] = 0 + nmodl_eigen_j[73] = -bi2 + nmodl_eigen_j[85] = 0 + nmodl_eigen_j[97] = 0 + nmodl_eigen_j[109] = 0 + nmodl_eigen_j[121] = 0 + nmodl_eigen_j[133] = 0 + nmodl_eigen_j[2] = 0 + nmodl_eigen_j[14] = -f02 + nmodl_eigen_j[26] = b02+f03+fi3 + nmodl_eigen_j[38] = -b03 + nmodl_eigen_j[50] = 0 + nmodl_eigen_j[62] = 0 + nmodl_eigen_j[74] = 0 + nmodl_eigen_j[86] = -bi3 + nmodl_eigen_j[98] = 0 + nmodl_eigen_j[110] = 0 + nmodl_eigen_j[122] = 0 + nmodl_eigen_j[134] = 0 + nmodl_eigen_j[3] = 0 + nmodl_eigen_j[15] = 0 + nmodl_eigen_j[27] = -f03 + nmodl_eigen_j[39] = b03+f04+fi4 + nmodl_eigen_j[51] = -b04 + nmodl_eigen_j[63] = 0 + nmodl_eigen_j[75] = 0 + nmodl_eigen_j[87] = 0 + nmodl_eigen_j[99] = -bi4 + nmodl_eigen_j[111] = 0 + nmodl_eigen_j[123] = 0 + nmodl_eigen_j[135] = 0 + nmodl_eigen_j[4] = 0 + nmodl_eigen_j[16] = 0 + nmodl_eigen_j[28] = 0 + nmodl_eigen_j[40] = -f04 + nmodl_eigen_j[52] = b04+f0O+fi5 + nmodl_eigen_j[64] = 0 + nmodl_eigen_j[76] = 0 + nmodl_eigen_j[88] = 0 + nmodl_eigen_j[100] = 0 + nmodl_eigen_j[112] = -bi5 + nmodl_eigen_j[124] = 0 + nmodl_eigen_j[136] = -b0O + nmodl_eigen_j[5] = 0 + nmodl_eigen_j[17] = 0 + nmodl_eigen_j[29] = 0 + nmodl_eigen_j[41] = 0 + nmodl_eigen_j[53] = -f0O + nmodl_eigen_j[65] = 0 + nmodl_eigen_j[77] = 0 + nmodl_eigen_j[89] = 0 + nmodl_eigen_j[101] = 0 + nmodl_eigen_j[113] = 0 + nmodl_eigen_j[125] = -bin + nmodl_eigen_j[137] = b0O+fin + nmodl_eigen_j[6] = -fi1 + nmodl_eigen_j[18] = 0 + nmodl_eigen_j[30] = 0 + nmodl_eigen_j[42] = 0 + nmodl_eigen_j[54] = 0 + nmodl_eigen_j[66] = bi1+f11 + nmodl_eigen_j[78] = -b11 + nmodl_eigen_j[90] = 0 + nmodl_eigen_j[102] = 0 + nmodl_eigen_j[114] = 0 + nmodl_eigen_j[126] = 0 + nmodl_eigen_j[138] = 0 + nmodl_eigen_j[7] = 0 + nmodl_eigen_j[19] = -fi2 + nmodl_eigen_j[31] = 0 + nmodl_eigen_j[43] = 0 + nmodl_eigen_j[55] = 0 + nmodl_eigen_j[67] = -f11 + nmodl_eigen_j[79] = b11+bi2+f12 + nmodl_eigen_j[91] = -b12 + nmodl_eigen_j[103] = 0 + nmodl_eigen_j[115] = 0 + nmodl_eigen_j[127] = 0 + nmodl_eigen_j[139] = 0 + nmodl_eigen_j[8] = 0 + nmodl_eigen_j[20] = 0 + nmodl_eigen_j[32] = -fi3 + nmodl_eigen_j[44] = 0 + nmodl_eigen_j[56] = 0 + nmodl_eigen_j[68] = 0 + nmodl_eigen_j[80] = -f12 + nmodl_eigen_j[92] = b12+bi3+f13 + nmodl_eigen_j[104] = -bi3 + nmodl_eigen_j[116] = 0 + nmodl_eigen_j[128] = 0 + nmodl_eigen_j[140] = 0 + nmodl_eigen_j[9] = 0 + nmodl_eigen_j[21] = 0 + nmodl_eigen_j[33] = 0 + nmodl_eigen_j[45] = -fi4 + nmodl_eigen_j[57] = 0 + nmodl_eigen_j[69] = 0 + nmodl_eigen_j[81] = 0 + nmodl_eigen_j[93] = -f13 + nmodl_eigen_j[105] = b13+bi4+f14 + nmodl_eigen_j[117] = -b14 + nmodl_eigen_j[129] = 0 + nmodl_eigen_j[141] = 0 + nmodl_eigen_j[10] = 0 + nmodl_eigen_j[22] = 0 + nmodl_eigen_j[34] = 0 + nmodl_eigen_j[46] = 0 + nmodl_eigen_j[58] = -fi5 + nmodl_eigen_j[70] = 0 + nmodl_eigen_j[82] = 0 + nmodl_eigen_j[94] = 0 + nmodl_eigen_j[106] = -f14 + nmodl_eigen_j[118] = b14+bi5+f1n + nmodl_eigen_j[130] = -b1n + nmodl_eigen_j[142] = 0 + nmodl_eigen_j[11] = -1.0 + nmodl_eigen_j[23] = -1.0 + nmodl_eigen_j[35] = -1.0 + nmodl_eigen_j[47] = -1.0 + nmodl_eigen_j[59] = -1.0 + nmodl_eigen_j[71] = -1.0 + nmodl_eigen_j[83] = -1.0 + nmodl_eigen_j[95] = -1.0 + nmodl_eigen_j[107] = -1.0 + nmodl_eigen_j[119] = -1.0 + nmodl_eigen_j[131] = -1.0 + nmodl_eigen_j[143] = -1.0 + }{ + C1 = nmodl_eigen_x[0] + C2 = nmodl_eigen_x[1] + C3 = nmodl_eigen_x[2] + C4 = nmodl_eigen_x[3] + C5 = nmodl_eigen_x[4] + I1 = nmodl_eigen_x[5] + I2 = nmodl_eigen_x[6] + I3 = nmodl_eigen_x[7] + I4 = nmodl_eigen_x[8] + I5 = nmodl_eigen_x[9] + I6 = nmodl_eigen_x[10] + O = nmodl_eigen_x[11] }{ } })"; @@ -2091,12 +2060,12 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s EIGEN_NEWTON_SOLVE[1]{ }{ }{ - X[0] = x + nmodl_eigen_x[0] = x }{ - F[0] = 5.0-X[0] - J[0] = -1.0 + nmodl_eigen_f[0] = 5.0-nmodl_eigen_x[0] + nmodl_eigen_j[0] = -1.0 }{ - x = X[0] + x = nmodl_eigen_x[0] }{ } })"; @@ -2122,26 +2091,26 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s EIGEN_NEWTON_SOLVE[3]{ }{ }{ - X[0] = s[0] - X[1] = s[1] - X[2] = s[2] - }{ - F[0] = 1.0-X[0] - F[1] = 3.0-X[1] - F[2] = X[0]-X[1]-X[2] - J[0] = -1.0 - J[3] = 0 - J[6] = 0 - J[1] = 0 - J[4] = -1.0 - J[7] = 0 - J[2] = 1.0 - J[5] = -1.0 - J[8] = -1.0 - }{ - s[0] = X[0] - s[1] = X[1] - s[2] = X[2] + nmodl_eigen_x[0] = s[0] + nmodl_eigen_x[1] = s[1] + nmodl_eigen_x[2] = s[2] + }{ + nmodl_eigen_f[0] = 1.0-nmodl_eigen_x[0] + nmodl_eigen_f[1] = 3.0-nmodl_eigen_x[1] + nmodl_eigen_f[2] = nmodl_eigen_x[0]-nmodl_eigen_x[1]-nmodl_eigen_x[2] + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[3] = 0 + nmodl_eigen_j[6] = 0 + nmodl_eigen_j[1] = 0 + nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[7] = 0 + nmodl_eigen_j[2] = 1.0 + nmodl_eigen_j[5] = -1.0 + nmodl_eigen_j[8] = -1.0 + }{ + s[0] = nmodl_eigen_x[0] + s[1] = nmodl_eigen_x[1] + s[2] = nmodl_eigen_x[2] }{ } })"; From 3d01caf2a925a9eec4e2e98452c777c7bc33eb71 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 18 Mar 2022 13:27:55 +0100 Subject: [PATCH 424/871] Line can end with only \r (BlueBrain/nmodl#828) NMODL Repo SHA: BlueBrain/nmodl@d5bd5614e575255861f7ff13a0683187551c55ed --- src/nmodl/lexer/nmodl.ll | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 4fdcffd166..a8284a510b 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -475,7 +475,8 @@ ELSE { } \n | -\r\n { +\r\n | +\r { /** For title return string without new line character */ loc.lines(1); std::string str(yytext); From 2a0bca8252046e7997a5b31b6a5d7c9069a7748d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 22 Mar 2022 06:44:34 +0100 Subject: [PATCH 425/871] Accept array variables in diffeq lexer (BlueBrain/nmodl#830) * Differential Equation lexer was accepting only scalar variables * Resulting in flex scanner jammed error for ODEs like m' = (inf[0] - m)/tau[0] https://senselab.med.yale.edu/modeldb/showmodel.cshtml?model=266806&file=/golgi_cell_2020/morphology_1/mod_files/Cav2_3.mod#tabs-2 * Update lexer rule to accept variable name with an index NMODL Repo SHA: BlueBrain/nmodl@2109df6abeaa4816dc4c54c1d65caba3d993a2f1 --- src/nmodl/lexer/diffeq.ll | 4 +++- test/nmodl/transpiler/unit/visitor/neuron_solve.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index 0635ecab12..041e827301 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -27,6 +27,7 @@ D [0-9] E [Ee][-+]?{D}+ +N [a-zA-Z][a-zA-Z0-9_]* /** if want to use yymore feature in copy modes */ %option yymore @@ -93,7 +94,8 @@ E [Ee][-+]?{D}+ {D}+"."{D}*({E})? | {D}*"."{D}+({E})? | {D}+{E} | -[a-zA-Z][a-zA-Z0-9_]* { return DiffeqParser::make_ATOM(yytext, loc); } +{N}\[(({D}+)|({N}))\] | +{N} { return DiffeqParser::make_ATOM(yytext, loc); } "\n" { return DiffeqParser::make_NEWLINE(yytext, loc); } diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 3e1c7d361f..58f819288d 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -52,7 +52,7 @@ SCENARIO("NeuronSolveVisitor visitor solves different ODE types") { } DERIVATIVE states { - m' = (mInf-m)/mTau + m' = (mInf-m)/mTau[0] h' = (hInf-h)/hTau m = m + h } @@ -64,7 +64,7 @@ SCENARIO("NeuronSolveVisitor visitor solves different ODE types") { } DERIVATIVE states { - m = m+(1.0-exp(dt*((((-1.0)))/mTau)))*(-(((mInf))/mTau)/((((-1.0)))/mTau)-m) + m = m+(1.0-exp(dt*((((-1.0)))/mTau[0])))*(-(((mInf))/mTau[0])/((((-1.0)))/mTau[0])-m) h = h+(1.0-exp(dt*((((-1.0)))/hTau)))*(-(((hInf))/hTau)/((((-1.0)))/hTau)-h) m = m+h } From 1d41c0d08bee659451dce103492ae29d015bbc4e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 4 Apr 2022 17:40:05 +0200 Subject: [PATCH 426/871] Update hpc-coding-convention (BlueBrain/nmodl#836) NMODL Repo SHA: BlueBrain/nmodl@1f059561d65fc6a1e0117b4d594e37e7ee5d557b --- cmake/nmodl/CMakeLists.txt | 7 ++---- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/parser/verbatim_driver.hpp | 2 -- src/nmodl/visitors/nmodl_visitor_helper.ipp | 1 - src/nmodl/visitors/symtab_visitor_helper.hpp | 23 ++++++++++---------- 5 files changed, 15 insertions(+), 20 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 88b6cb07f8..f439530e78 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -101,14 +101,11 @@ include_directories( # HPC Coding Conventions # ============================================================================= set(NMODL_ClangFormat_EXCLUDES_RE - ".*/ext/.*$$" + "ext/.*$$" "src/language/templates/.*$$" CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) set(NMODL_CMakeFormat_EXCLUDES_RE - ".*/ext/.*$$" ".*/src/language/templates/.*$$" + "ext/.*$$" "src/language/templates/.*$$" CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) -set(NMODL_ClangFormat_DEPENDENCIES - pyastgen parser-gen - CACHE STRING "list of CMake targets to build before formatting C++ code" FORCE) # initialize submodule of coding conventions under cmake set(THIRD_PARTY_DIRECTORY "${PROJECT_SOURCE_DIR}/cmake") diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 7bca42c14a..7eaad9d932 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 7bca42c14a93e2eb2858ad4e90514d629aa3df5b +Subproject commit 7eaad9d932f1fdcd7421d943cbf7bc5fcd6c5165 diff --git a/src/nmodl/parser/verbatim_driver.hpp b/src/nmodl/parser/verbatim_driver.hpp index 7ff63e3bb3..ea8c3e050e 100644 --- a/src/nmodl/parser/verbatim_driver.hpp +++ b/src/nmodl/parser/verbatim_driver.hpp @@ -23,7 +23,6 @@ namespace parser { * \brief Class that binds lexer and parser together for parsing VERBATIM block */ class VerbatimDriver { - protected: void init_scanner(); void destroy_scanner(); @@ -53,4 +52,3 @@ class VerbatimDriver { int Verbatim_parse(nmodl::parser::VerbatimDriver*); - diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index d68c180366..8ec90eb6e1 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -69,4 +69,3 @@ void NmodlPrintVisitor::visit_element(const std::vector& elements, } // namespace visitor } // namespace nmodl - diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index f7b073f2dd..35e7aa831a 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -164,13 +164,13 @@ void SymtabVisitor::add_model_symbol_with_property(ast::Node* node, NmodlType pr static void add_external_symbols(symtab::ModelSymbolTable* symtab) { ModToken tok(true); auto variables = nmodl::get_external_variables(); - for (auto variable : variables) { + for (auto variable: variables) { auto symbol = std::make_shared(variable, nullptr, tok); symbol->add_property(NmodlType::extern_neuron_variable); symtab->insert(symbol); } auto methods = nmodl::get_external_functions(); - for (auto method : methods) { + for (auto method: methods) { auto symbol = std::make_shared(method, nullptr, tok); symbol->add_property(NmodlType::extern_method); symtab->insert(symbol); @@ -241,16 +241,17 @@ void SymtabVisitor::setup_symbol_table_for_scoped_block(ast::Node* node, const s * @todo we assume table statement follows variable declaration */ void SymtabVisitor::visit_table_statement(ast::TableStatement& node) { - auto update_symbol = [this](const ast::NameVector& variables, NmodlType property, int num_values) { - for (auto& var : variables) { - auto name = var->get_node_name(); - auto symbol = modsymtab->lookup(name); - if (symbol) { - symbol->add_property(property); - symbol->set_num_values(num_values); + auto update_symbol = + [this](const ast::NameVector& variables, NmodlType property, int num_values) { + for (auto& var: variables) { + auto name = var->get_node_name(); + auto symbol = modsymtab->lookup(name); + if (symbol) { + symbol->add_property(property); + symbol->set_num_values(num_values); + } } - } - }; + }; int num_values = node.get_with()->eval() + 1; update_symbol(node.get_table_vars(), NmodlType::table_statement_var, num_values); update_symbol(node.get_depend_vars(), NmodlType::table_assigned_var, num_values); From 28f0f57d7490bca0d3653329235aa1f308c5d54e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 8 Apr 2022 17:54:33 +0200 Subject: [PATCH 427/871] Upgrade all dependencies (BlueBrain/nmodl#842) NMODL Repo SHA: BlueBrain/nmodl@cde5dbf02fe93bb1b0b8cc9b5a6ebe0747ab8720 --- src/nmodl/main.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index c7af4ca14e..4da9e23a67 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -163,7 +163,8 @@ int main(int argc, const char* argv[]) { app.get_formatter()->column_width(40); app.set_help_all_flag("-H,--help-all", "Print this help message including all sub-commands"); - app.add_option("--verbose", verbose, "Verbosity of logger output", true) + app.add_option("--verbose", verbose, "Verbosity of logger output") + ->capture_default_str() ->ignore_case() ->check(CLI::IsMember({"trace", "debug", "info", "warning", "error", "critical", "off"})); @@ -172,11 +173,13 @@ int main(int argc, const char* argv[]) { ->required() ->check(CLI::ExistingFile); - app.add_option("-o,--output", output_dir, "Directory for backend code output", true) + app.add_option("-o,--output", output_dir, "Directory for backend code output") + ->capture_default_str() ->ignore_case(); - app.add_option("--scratch", scratch_dir, "Directory for intermediate code output", true) + app.add_option("--scratch", scratch_dir, "Directory for intermediate code output") + ->capture_default_str() ->ignore_case(); - app.add_option("--units", units_dir, "Directory of units lib file", true)->ignore_case(); + app.add_option("--units", units_dir, "Directory of units lib file")->capture_default_str()->ignore_case(); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, "C/C++ backend ({})"_format(c_backend))->ignore_case(); @@ -254,8 +257,7 @@ int main(int argc, const char* argv[]) { auto codegen_opt = app.add_subcommand("codegen", "Code generation options")->ignore_case(); codegen_opt->add_option("--datatype", data_type, - "Data type for floating point variables", - true)->ignore_case()->check(CLI::IsMember({"float", "double"})); + "Data type for floating point variables")->capture_default_str()->ignore_case()->check(CLI::IsMember({"float", "double"})); codegen_opt->add_flag("--force", force_codegen, "Force code generation even if there is any incompatibility"); From 415968daed77b505645446de90646806a2de532d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 28 Apr 2022 16:27:46 +0200 Subject: [PATCH 428/871] Change _format() by fmt::format (BlueBrain/nmodl#849) _format() is now deprecated with fmt NMODL Repo SHA: BlueBrain/nmodl@467330db62d557bd15435fc456360e9265b25a8e --- src/nmodl/codegen/codegen_acc_visitor.cpp | 47 +- src/nmodl/codegen/codegen_c_visitor.cpp | 708 +++++++++--------- src/nmodl/codegen/codegen_c_visitor.hpp | 11 +- .../codegen/codegen_compatibility_visitor.cpp | 27 +- .../codegen/codegen_compatibility_visitor.hpp | 15 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 10 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 77 +- src/nmodl/codegen/codegen_omp_visitor.cpp | 4 +- src/nmodl/codegen/codegen_utils.cpp | 4 +- .../visitors/checkparent_visitor.cpp | 8 +- src/nmodl/lexer/main_c.cpp | 3 +- src/nmodl/lexer/main_nmodl.cpp | 4 +- src/nmodl/lexer/main_units.cpp | 3 +- src/nmodl/main.cpp | 75 +- src/nmodl/parser/main_c.cpp | 3 +- src/nmodl/parser/main_nmodl.cpp | 4 +- src/nmodl/parser/main_units.cpp | 4 +- src/nmodl/symtab/symbol.cpp | 6 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 13 +- src/nmodl/visitors/main.cpp | 5 +- src/nmodl/visitors/steadystate_visitor.cpp | 5 +- .../sympy_replace_solutions_visitor.cpp | 9 +- 22 files changed, 545 insertions(+), 500 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index d13df4e82f..40907b96b0 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -11,9 +11,6 @@ #include "ast/integer.hpp" -using namespace fmt::literals; - - namespace nmodl { namespace codegen { @@ -51,8 +48,8 @@ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType ty } present_clause << ')'; printer->add_line( - "nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))"_format( - present_clause.str())); + fmt::format("nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", + present_clause.str())); printer->add_line( "nrn_pragma_omp(target teams distribute parallel for is_device_ptr(inst) " "if(nt->compute_gpu))"); @@ -104,7 +101,7 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { } printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); + printer->add_line(fmt::format("static inline void* mem_alloc({}) {}", args, "{")); printer->add_line(" void* ptr;"); printer->add_line(" cudaMallocManaged(&ptr, num*size);"); printer->add_line(" cudaMemset(ptr, 0, num*size);"); @@ -144,7 +141,7 @@ void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { printer->add_line( - "nmodl_eigen_xm = partialPivLu<{}>nmodl_eigen_jm, nmodl_eigen_fm);"_format(N)); + fmt::format("nmodl_eigen_xm = partialPivLu<{}>nmodl_eigen_jm, nmodl_eigen_fm);", N)); } } @@ -164,9 +161,10 @@ void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { if (!info.artificial_cell) { - auto global_variable = "{}_global"_format(info.mod_suffix); + auto global_variable = fmt::format("{}_global", info.mod_suffix); printer->add_line( - "nrn_pragma_acc(data present(nt, ml, {}) if(nt->compute_gpu))"_format(global_variable)); + fmt::format("nrn_pragma_acc(data present(nt, ml, {}) if(nt->compute_gpu))", + global_variable)); printer->add_line("{"); printer->increase_indent(); } @@ -200,9 +198,9 @@ void CodegenAccVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); print_atomic_reduction_pragma(); - printer->add_line("vec_rhs[node_id] {} rhs;"_format(rhs_op)); + printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); print_atomic_reduction_pragma(); - printer->add_line("vec_d[node_id] {} g;"_format(d_op)); + printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); } void CodegenAccVisitor::print_fast_imem_calculation() { @@ -214,9 +212,9 @@ void CodegenAccVisitor::print_fast_imem_calculation() { auto d_op = operator_for_d(); printer->start_block("if (nt->nrn_fast_imem)"); print_atomic_reduction_pragma(); - printer->add_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} rhs;"_format(rhs_op)); + printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} rhs;", rhs_op)); print_atomic_reduction_pragma(); - printer->add_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;"_format(d_op)); + printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;", d_op)); printer->end_block(1); } @@ -254,15 +252,18 @@ void CodegenAccVisitor::print_global_variable_device_create_annotation_pre() { void CodegenAccVisitor::print_global_variable_device_create_annotation_post() { if (!info.artificial_cell) { - printer->add_line("nrn_pragma_acc(declare create ({}_global))"_format(info.mod_suffix)); + printer->add_line( + fmt::format("nrn_pragma_acc(declare create ({}_global))", info.mod_suffix)); printer->add_line("nrn_pragma_omp(end declare target)"); } } void CodegenAccVisitor::print_global_variable_device_update_annotation() { if (!info.artificial_cell) { - printer->add_line("nrn_pragma_acc(update device ({}_global))"_format(info.mod_suffix)); - printer->add_line("nrn_pragma_omp(target update to({}_global))"_format(info.mod_suffix)); + printer->add_line( + fmt::format("nrn_pragma_acc(update device ({}_global))", info.mod_suffix)); + printer->add_line( + fmt::format("nrn_pragma_omp(target update to({}_global))", info.mod_suffix)); } } @@ -272,7 +273,7 @@ std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& va if (info.artificial_cell) { return variable; } - return "nt->compute_gpu ? cnrn_target_deviceptr({}) : {}"_format(variable, variable); + return fmt::format("nt->compute_gpu ? cnrn_target_deviceptr({}) : {}", variable, variable); } @@ -283,11 +284,11 @@ void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { printer->add_line("void* device_ns = cnrn_target_deviceptr(*ns);"); printer->add_line("ThreadDatum* device_thread = cnrn_target_deviceptr(thread);"); printer->add_line( - "cnrn_target_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns);"_format( - info.thread_data_index - 1)); + fmt::format("cnrn_target_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns);", + info.thread_data_index - 1)); printer->add_line( - "cnrn_target_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec);"_format( - list_num)); + fmt::format("cnrn_target_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec);", + list_num)); printer->end_block(1); } @@ -344,8 +345,8 @@ void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { void CodegenAccVisitor::print_dt_update_to_device() const { - printer->add_line("#pragma acc update device({}) if (nt->compute_gpu)"_format( - get_variable_name(naming::NTHREAD_DT_VARIABLE))); + printer->add_line(fmt::format("#pragma acc update device({}) if (nt->compute_gpu)", + get_variable_name(naming::NTHREAD_DT_VARIABLE))); } } // namespace codegen diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index af785e3f03..b859e5199d 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -27,8 +27,6 @@ #include "visitors/var_usage_visitor.hpp" #include "visitors/visitor_utils.hpp" -using namespace fmt::literals; - namespace nmodl { namespace codegen { @@ -217,15 +215,15 @@ void CodegenCVisitor::visit_from_statement(const ast::FromStatement& node) { const auto& to = node.get_to(); const auto& inc = node.get_increment(); const auto& block = node.get_statement_block(); - printer->add_text("for(int {}="_format(name)); + printer->add_text(fmt::format("for(int {}=", name)); from->accept(*this); - printer->add_text("; {}<="_format(name)); + printer->add_text(fmt::format("; {}<=", name)); to->accept(*this); if (inc) { - printer->add_text("; {}+="_format(name)); + printer->add_text(fmt::format("; {}+=", name)); inc->accept(*this); } else { - printer->add_text("; {}++"_format(name)); + printer->add_text(fmt::format("; {}++", name)); } printer->add_text(")"); block->accept(*this); @@ -604,7 +602,7 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { auto variable_names = read_ion_variable_name(var); auto first = get_variable_name(variable_names.first); auto second = get_variable_name(variable_names.second); - statements.push_back("{} = {};"_format(first, second)); + statements.push_back(fmt::format("{} = {};", first, second)); } for (const auto& var: ion.writes) { if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { @@ -614,7 +612,7 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { auto variables = read_ion_variable_name(var); auto first = get_variable_name(variables.first); auto second = get_variable_name(variables.second); - statements.push_back("{} = {};"_format(first, second)); + statements.push_back(fmt::format("{} = {};", first, second)); } } } @@ -633,7 +631,7 @@ std::vector CodegenCVisitor::ion_read_statements_optimized(BlockTyp auto variables = read_ion_variable_name(var); auto first = "ionvar." + variables.first; auto second = get_variable_name(variables.second); - statements.push_back("{} = {};"_format(first, second)); + statements.push_back(fmt::format("{} = {};", first, second)); } } } @@ -656,7 +654,7 @@ std::vector CodegenCVisitor::ion_write_statements(BlockType auto rhs = get_variable_name(current); if (info.point_process) { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - rhs += "*(1.e2/{})"_format(area); + rhs += fmt::format("*(1.e2/{})", area); } statements.push_back(ShadowUseStatement{lhs, op, rhs}); } @@ -679,10 +677,10 @@ std::vector CodegenCVisitor::ion_write_statements(BlockType index = 2; } else { /// \todo Unhandled case in neuron implementation - throw std::logic_error("codegen error for {} ion"_format(ion.name)); + throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); } - auto ion_type_name = "{}_type"_format(ion.name); - auto lhs = "int {}"_format(ion_type_name); + auto ion_type_name = fmt::format("{}_type", ion.name); + auto lhs = fmt::format("int {}", ion_type_name); auto op = "="; auto rhs = get_variable_name(ion_type_name); statements.push_back(ShadowUseStatement{lhs, op, rhs}); @@ -784,7 +782,7 @@ void CodegenCVisitor::update_index_semantics() { } } if (ion.need_style) { - info.semantics.emplace_back(index++, "#{}_ion"_format(ion.name), 1); + info.semantics.emplace_back(index++, fmt::format("#{}_ion", ion.name), 1); } } for (auto& var: info.pointer_variables) { @@ -978,7 +976,7 @@ std::vector CodegenCVisitor::get_int_variables() { */ if (!info.watch_statements.empty()) { for (int i = 0; i < info.watch_statements.size() + 1; i++) { - variables.emplace_back(make_symbol("watch{}"_format(i)), false, false, true); + variables.emplace_back(make_symbol(fmt::format("watch{}", i)), false, false, true); } } return variables; @@ -1017,10 +1015,11 @@ std::vector CodegenCVisitor::get_shadow_variables() { std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { std::string param{}; for (auto iter = params.begin(); iter != params.end(); iter++) { - param += "{}{} {}{}"_format(std::get<0>(*iter), - std::get<1>(*iter), - std::get<2>(*iter), - std::get<3>(*iter)); + param += fmt::format("{}{} {}{}", + std::get<0>(*iter), + std::get<1>(*iter), + std::get<2>(*iter), + std::get<3>(*iter)); if (!nmodl::utils::is_last(iter, params)) { param += ", "; } @@ -1157,8 +1156,8 @@ void CodegenCVisitor::print_channel_iteration_block_end() { void CodegenCVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { - printer->add_line("double* shadow_rhs = nt->{};"_format(naming::NTHREAD_RHS_SHADOW)); - printer->add_line("double* shadow_d = nt->{};"_format(naming::NTHREAD_D_SHADOW)); + printer->add_line(fmt::format("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW)); + printer->add_line(fmt::format("double* shadow_d = nt->{};", naming::NTHREAD_D_SHADOW)); } } @@ -1167,8 +1166,8 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { if (channel_task_dependency_enabled()) { auto rhs = get_variable_name("ml_rhs"); auto d = get_variable_name("ml_d"); - printer->add_line("{} = rhs;"_format(rhs)); - printer->add_line("{} = g;"_format(d)); + printer->add_line(fmt::format("{} = rhs;", rhs)); + printer->add_line(fmt::format("{} = g;", d)); } else { if (info.point_process) { printer->add_line("shadow_rhs[id] = rhs;"); @@ -1177,9 +1176,9 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); print_atomic_reduction_pragma(); - printer->add_line("vec_rhs[node_id] {} rhs;"_format(rhs_op)); + printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); print_atomic_reduction_pragma(); - printer->add_line("vec_d[node_id] {} g;"_format(d_op)); + printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); } } } @@ -1193,16 +1192,16 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { auto d = get_variable_name("ml_d"); printer->add_line("int node_id = node_index[id];"); print_atomic_reduction_pragma(); - printer->add_line("vec_rhs[node_id] {} {};"_format(rhs_op, rhs)); + printer->add_line(fmt::format("vec_rhs[node_id] {} {};", rhs_op, rhs)); print_atomic_reduction_pragma(); - printer->add_line("vec_d[node_id] {} {};"_format(d_op, d)); + printer->add_line(fmt::format("vec_d[node_id] {} {};", d_op, d)); } else { if (info.point_process) { printer->add_line("int node_id = node_index[id];"); print_atomic_reduction_pragma(); - printer->add_line("vec_rhs[node_id] {} shadow_rhs[id];"_format(rhs_op)); + printer->add_line(fmt::format("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op)); print_atomic_reduction_pragma(); - printer->add_line("vec_d[node_id] {} shadow_d[id];"_format(d_op)); + printer->add_line(fmt::format("vec_d[node_id] {} shadow_d[id];", d_op)); } } } @@ -1223,7 +1222,7 @@ void CodegenCVisitor::print_shadow_reduction_statements() { print_atomic_reduction_pragma(); auto lhs = get_variable_name(statement.lhs); auto rhs = get_variable_name(shadow_varname(statement.lhs)); - auto text = "{} {} {};"_format(lhs, statement.op, rhs); + auto text = fmt::format("{} {} {};", lhs, statement.op, rhs); printer->add_line(text); } shadow_statements.clear(); @@ -1283,7 +1282,7 @@ bool CodegenCVisitor::optimize_ion_variable_copies() const { void CodegenCVisitor::print_memory_allocation_routine() const { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->add_line("static inline void* mem_alloc({}) {}"_format(args, "{")); + printer->add_line(fmt::format("static inline void* mem_alloc({}) {}", args, "{")); printer->add_line(" void* ptr;"); printer->add_line(" posix_memalign(&ptr, alignment, num*size);"); printer->add_line(" memset(ptr, 0, size);"); @@ -1339,7 +1338,7 @@ std::string CodegenCVisitor::global_var_struct_type_qualifier() { } void CodegenCVisitor::print_global_var_struct_decl() { - printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + printer->add_line(fmt::format("{} {}_global;", global_struct(), info.mod_suffix)); } std::string CodegenCVisitor::k_const() { @@ -1353,8 +1352,8 @@ std::string CodegenCVisitor::k_const() { void CodegenCVisitor::visit_watch_statement(const ast::WatchStatement& node) { - printer->add_text("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)"_format( - current_watch_statement++)); + printer->add_text(fmt::format("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)", + current_watch_statement++)); } @@ -1410,7 +1409,7 @@ void CodegenCVisitor::print_function_call(const FunctionCall& node) { } auto arguments = node.get_arguments(); - printer->add_text("{}("_format(function_name)); + printer->add_text(fmt::format("{}(", function_name)); bool possible_nt_variable_types{false}; if (!defined_method(name)) { @@ -1515,9 +1514,9 @@ static const TableStatement* get_table_statement(const ast::Block& node) { const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); if (table_statements.size() != 1) { - auto message = - "One table statement expected in {} found {}"_format(node.get_node_name(), - table_statements.size()); + auto message = fmt::format("One table statement expected in {} found {}", + node.get_node_name(), + table_statements.size()); throw std::runtime_error(message); } return dynamic_cast(table_statements.front().get()); @@ -1541,21 +1540,22 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->add_newline(2); print_device_method_annotation(); printer->start_block( - "void check_{}({})"_format(method_name(name), get_parameter_str(internal_params))); + fmt::format("void check_{}({})", method_name(name), get_parameter_str(internal_params))); { - printer->add_line("if ( {} == 0) {}"_format(use_table_var, "{")); + printer->add_line(fmt::format("if ( {} == 0) {}", use_table_var, "{")); printer->add_line(" return;"); printer->add_line("}"); printer->add_line("static bool make_table = true;"); for (const auto& variable: depend_variables) { - printer->add_line("static {} save_{};"_format(float_type, variable->get_node_name())); + printer->add_line( + fmt::format("static {} save_{};", float_type, variable->get_node_name())); } for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); - printer->add_line("if (save_{} != {}) {}"_format(name, instance_name, "{")); + printer->add_line(fmt::format("if (save_{} != {}) {}", name, instance_name, "{")); printer->add_line(" make_table = true;"); printer->add_line("}"); } @@ -1565,7 +1565,7 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->add_line("make_table = false;"); printer->add_indent(); - printer->add_text("{} = "_format(tmin_name)); + printer->add_text(fmt::format("{} = ", tmin_name)); from->accept(*this); printer->add_text(";"); printer->add_newline(); @@ -1577,27 +1577,27 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->add_newline(); - printer->add_line("double dx = (tmax-{})/{}.0;"_format(tmin_name, with)); - printer->add_line("{} = 1.0/dx;"_format(mfac_name)); + printer->add_line(fmt::format("double dx = (tmax-{})/{}.0;", tmin_name, with)); + printer->add_line(fmt::format("{} = 1.0/dx;", mfac_name)); printer->add_line("int i = 0;"); printer->add_line("double x = 0;"); - printer->add_line( - "for(i = 0, x = {}; i < {}; x += dx, i++) {}"_format(tmin_name, with + 1, "{")); + printer->add_line(fmt::format( + "for(i = 0, x = {}; i < {}; x += dx, i++) {}", tmin_name, with + 1, "{")); auto function = method_name("f_" + name); - printer->add_line(" {}({}, x);"_format(function, internal_method_arguments())); + printer->add_line(fmt::format(" {}({}, x);", function, internal_method_arguments())); for (const auto& variable: table_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); - printer->add_line(" {}[i] = {};"_format(table_name, instance_name)); + printer->add_line(fmt::format(" {}[i] = {};", table_name, instance_name)); } printer->add_line("}"); for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); - printer->add_line("save_{} = {};"_format(name, instance_name)); + printer->add_line(fmt::format("save_{} = {};", name, instance_name)); } } printer->end_block(1); @@ -1622,31 +1622,31 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { printer->start_block(); { const auto& params = node.get_parameters(); - printer->add_line("if ( {} == 0) {{"_format(use_table_var)); - printer->add_line(" {}({}, {});"_format(function_name, - internal_method_arguments(), - params[0].get()->get_node_name())); + printer->add_line(fmt::format("if ( {} == 0) {{", use_table_var)); + printer->add_line(fmt::format(" {}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name())); printer->add_line(" return 0;"); printer->add_line("}"); - printer->add_line("double xi = {} * ({} - {});"_format(mfac_name, - params[0].get()->get_node_name(), - tmin_name)); + printer->add_line(fmt::format( + "double xi = {} * ({} - {});", mfac_name, params[0].get()->get_node_name(), tmin_name)); printer->add_line("if (isnan(xi)) {"); for (const auto& var: table_variables) { auto name = get_variable_name(var->get_node_name()); - printer->add_line(" {} = xi;"_format(name)); + printer->add_line(fmt::format(" {} = xi;", name)); } printer->add_line(" return 0;"); printer->add_line("}"); - printer->add_line("if (xi <= 0.0 || xi >= {}) {}"_format(with, "{")); - printer->add_line(" int index = (xi <= 0.0) ? 0 : {};"_format(with)); + printer->add_line(fmt::format("if (xi <= 0.0 || xi >= {}) {}", with, "{")); + printer->add_line(fmt::format(" int index = (xi <= 0.0) ? 0 : {};", with)); for (const auto& variable: table_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); - printer->add_line(" {} = {}[index];"_format(instance_name, table_name)); + printer->add_line(fmt::format(" {} = {}[index];", instance_name, table_name)); } printer->add_line(" return 0;"); printer->add_line("}"); @@ -1657,7 +1657,7 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { auto instance_name = get_variable_name(var->get_node_name()); auto table_name = get_variable_name("t_" + var->get_node_name()); printer->add_line( - "{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);"_format(instance_name, table_name)); + fmt::format("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", instance_name, table_name)); } printer->add_line("return 0;"); @@ -1675,16 +1675,16 @@ void CodegenCVisitor::print_check_table_thread_function() { auto name = method_name("check_table_thread"); auto parameters = external_method_parameters(true); - printer->add_line("static void {} ({}) {}"_format(name, parameters, "{")); + printer->add_line(fmt::format("static void {} ({}) {}", name, parameters, "{")); printer->add_line(" Memb_list* ml = nt->_ml_list[tml_id];"); printer->add_line(" setup_instance(nt, ml);"); - printer->add_line(" {0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + printer->add_line(fmt::format(" {0}* inst = ({0}*) ml->instance;", instance_struct())); printer->add_line(" double v = 0;"); for (const auto& function: info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); - printer->add_line(" {}({});"_format(name, arguments)); + printer->add_line(fmt::format(" {}({});", name, arguments)); } /** @@ -1706,13 +1706,13 @@ void CodegenCVisitor::print_function_or_procedure(const ast::Block& node, const // function requires return variable declaration if (node.is_function_block()) { auto type = default_float_data_type(); - printer->add_line("{} ret_{} = 0.0;"_format(type, name)); + printer->add_line(fmt::format("{} ret_{} = 0.0;", type, name)); } else { - printer->add_line("int ret_{} = 0;"_format(name)); + printer->add_line(fmt::format("int ret_{} = 0;", name)); } print_statement_block(*node.get_statement_block(), false, false); - printer->add_line("return ret_{};"_format(name)); + printer->add_line(fmt::format("return ret_{};", name)); printer->end_block(1); } @@ -1812,8 +1812,8 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv auto float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;"_format(float_type, N)); - printer->add_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();"_format(float_type)); + printer->add_line(fmt::format("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N)); + printer->add_line(fmt::format("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type)); print_statement_block(*node.get_setup_x_block(), false, false); @@ -1821,7 +1821,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv // Newton solver printer->start_block("struct functor"); printer->add_line("NrnThread* nt;"); - printer->add_line("{0}* inst;"_format(instance_struct())); + printer->add_line(fmt::format("{0}* inst;", instance_struct())); printer->add_line("int id, pnodecount;"); printer->add_line("double v;"); printer->add_line("Datum* indexes;"); @@ -1836,24 +1836,28 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv print_statement_block(*node.get_initialize_block(), false, false); printer->end_block(2); - printer->add_line( - "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes) : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes) {}"_format( - instance_struct(), "{}")); + printer->add_line(fmt::format( + "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes) : " + "nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes) {}", + instance_struct(), + "{}")); printer->add_indent(); const auto& variable_block = *node.get_variable_block(); const auto& functor_block = *node.get_functor_block(); - printer->add_text( + printer->add_text(fmt::format( "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " "1>& nmodl_eigen_fm, " - "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}"_format( - float_type, N, is_functor_const(variable_block, functor_block) ? "const " : "")); + "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", + float_type, + N, + is_functor_const(variable_block, functor_block) ? "const " : "")); printer->start_block(); - printer->add_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();"_format(float_type)); - printer->add_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();"_format(float_type)); - printer->add_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();"_format(float_type)); + printer->add_line(fmt::format("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type)); + printer->add_line(fmt::format("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type)); + printer->add_line(fmt::format("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type)); print_statement_block(functor_block, false, false); printer->end_block(2); @@ -1884,11 +1888,11 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv const std::string float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); printer->add_line( - "Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;"_format(float_type, N)); - printer->add_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;"_format(float_type, N)); - printer->add_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();"_format(float_type)); - printer->add_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();"_format(float_type)); - printer->add_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();"_format(float_type)); + fmt::format("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N)); + printer->add_line(fmt::format("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N)); + printer->add_line(fmt::format("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type)); + printer->add_line(fmt::format("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type)); + printer->add_line(fmt::format("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type)); print_statement_block(*node.get_variable_block(), false, false); print_statement_block(*node.get_initialize_block(), false, false); print_statement_block(*node.get_setup_x_block(), false, false); @@ -1907,8 +1911,10 @@ void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, i printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { printer->add_line( - "nmodl_eigen_xm = Eigen::PartialPivLU>>(nmodl_eigen_jm).solve(nmodl_eigen_fm);"_format( - float_type, N)); + fmt::format("nmodl_eigen_xm = Eigen::PartialPivLU>>(nmodl_eigen_jm).solve(nmodl_eigen_fm);", + float_type, + N)); } } @@ -1943,7 +1949,7 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { params.emplace_back("", "int", "", "id"); params.emplace_back(param_type_qualifier(), "int", "", "pnodecount"); params.emplace_back(param_type_qualifier(), - "{}*"_format(instance_struct()), + fmt::format("{}*", instance_struct()), param_ptr_qualifier(), "inst"); if (ion_variable_struct_required()) { @@ -2058,8 +2064,11 @@ std::string CodegenCVisitor::register_mechanism_arguments() const { auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "NULL"; auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); auto nrn_init = method_name(naming::NRN_INIT_METHOD); - return "mechanism, {}, {}, NULL, {}, {}, first_pointer_var_index()" - ""_format(nrn_alloc, nrn_cur, nrn_state, nrn_init); + return fmt::format("mechanism, {}, {}, NULL, {}, {}, first_pointer_var_index()", + nrn_alloc, + nrn_cur, + nrn_state, + nrn_init); } @@ -2080,14 +2089,19 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, int index) { auto conc_var_name = get_variable_name(naming::ION_VARNAME_PREFIX + concentration); auto style_var_name = get_variable_name("style_" + ion_name); - return "nrn_wrote_conc({}_type," - " &({})," - " {}," - " {}," - " nrn_ion_global_map," - " celsius," - " nt->_ml_list[{}_type]->_nodecount_padded)" - ""_format(ion_name, conc_var_name, index, style_var_name, ion_name); + return fmt::format( + "nrn_wrote_conc({}_type," + " &({})," + " {}," + " {}," + " nrn_ion_global_map," + " celsius," + " nt->_ml_list[{}_type]->_nodecount_padded)", + ion_name, + conc_var_name, + index, + style_var_name, + ion_name); } @@ -2110,13 +2124,13 @@ std::string CodegenCVisitor::process_shadow_update_statement(const ShadowUseStat shadow_statements.push_back(statement); auto lhs = get_variable_name(shadow_varname(statement.lhs)); auto rhs = statement.rhs; - auto text = "{} = {};"_format(lhs, rhs); + auto text = fmt::format("{} = {};", lhs, rhs); return text; } // return regular statement auto lhs = get_variable_name(statement.lhs); - auto text = "{} {} {};"_format(lhs, statement.op, statement.rhs); + auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs); return text; } @@ -2152,7 +2166,7 @@ void CodegenCVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); print_device_method_annotation(); printer->add_line("static inline int first_pointer_var_index() {"); - printer->add_line(" return {};"_format(info.first_pointer_var_index)); + printer->add_line(fmt::format(" return {};", info.first_pointer_var_index)); printer->add_line("}"); } @@ -2161,13 +2175,13 @@ void CodegenCVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); printer->add_line("static inline int float_variables_size() {"); - printer->add_line(" return {};"_format(float_variables_size())); + printer->add_line(fmt::format(" return {};", float_variables_size())); printer->add_line("}"); printer->add_newline(2); print_device_method_annotation(); printer->add_line("static inline int int_variables_size() {"); - printer->add_line(" return {};"_format(int_variables_size())); + printer->add_line(fmt::format(" return {};", int_variables_size())); printer->add_line("}"); } @@ -2179,7 +2193,7 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { printer->add_newline(2); print_device_method_annotation(); printer->add_line("static inline int num_net_receive_args() {"); - printer->add_line(" return {};"_format(info.num_net_receive_parameters)); + printer->add_line(fmt::format(" return {};", info.num_net_receive_parameters)); printer->add_line("}"); } @@ -2188,7 +2202,7 @@ void CodegenCVisitor::print_mech_type_getter() { printer->add_newline(2); print_device_method_annotation(); printer->add_line("static inline int get_mech_type() {"); - printer->add_line(" return {};"_format(get_variable_name("mech_type"))); + printer->add_line(fmt::format(" return {};", get_variable_name("mech_type"))); printer->add_line("}"); } @@ -2238,18 +2252,18 @@ void CodegenCVisitor::print_thread_getters() { printer->add_line("/** thread specific helper routines for derivimplicit */"); printer->add_newline(1); - printer->add_line("static inline int* deriv{}_advance(ThreadDatum* thread) {}"_format(list, "{")); - printer->add_line(" return &(thread[{}].i);"_format(tid)); + printer->add_line(fmt::format("static inline int* deriv{}_advance(ThreadDatum* thread) {}", list, "{")); + printer->add_line(fmt::format(" return &(thread[{}].i);", tid)); printer->add_line("}"); printer->add_newline(1); - printer->add_line("static inline int dith{}() {}"_format(list, "{")); - printer->add_line(" return {};"_format(tid+1)); + printer->add_line(fmt::format("static inline int dith{}() {}", list, "{")); + printer->add_line(fmt::format(" return {};", tid+1)); printer->add_line("}"); printer->add_newline(1); - printer->add_line("static inline void** newtonspace{}(ThreadDatum* thread) {}"_format(list, "{")); - printer->add_line(" return &(thread[{}]._pvoid);"_format(tid+2)); + printer->add_line(fmt::format("static inline void** newtonspace{}(ThreadDatum* thread) {}", list, "{")); + printer->add_line(fmt::format(" return &(thread[{}]._pvoid);", tid+2)); printer->add_line("}"); } @@ -2257,7 +2271,7 @@ void CodegenCVisitor::print_thread_getters() { printer->add_newline(2); printer->add_line("/** tid for thread variables */"); printer->add_line("static inline int thread_var_tid() {"); - printer->add_line(" return {};"_format(info.thread_var_thread_id)); + printer->add_line(fmt::format(" return {};", info.thread_var_thread_id)); printer->add_line("}"); } @@ -2265,7 +2279,7 @@ void CodegenCVisitor::print_thread_getters() { printer->add_newline(2); printer->add_line("/** tid for top local tread variables */"); printer->add_line("static inline int top_local_var_tid() {"); - printer->add_line(" return {};"_format(info.top_local_thread_id)); + printer->add_line(fmt::format(" return {};", info.top_local_thread_id)); printer->add_line("}"); } // clang-format on @@ -2285,14 +2299,14 @@ std::string CodegenCVisitor::float_variable_name(const SymbolType& symbol, // clang-format off if (symbol->is_array()) { if (use_instance) { - return "(inst->{}+id*{})"_format(name, dimension); + return fmt::format("(inst->{}+id*{})", name, dimension); } - return "(data + {}*pnodecount + id*{})"_format(position, dimension); + return fmt::format("(data + {}*pnodecount + id*{})", position, dimension); } if (use_instance) { - return "inst->{}[id]"_format(name); + return fmt::format("inst->{}[id]", name); } - return "data[{}*pnodecount + id]"_format(position); + return fmt::format("data[{}*pnodecount + id]", position); // clang-format on } @@ -2304,32 +2318,32 @@ std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, // clang-format off if (symbol.is_index) { if (use_instance) { - return "inst->{}[{}]"_format(name, position); + return fmt::format("inst->{}[{}]", name, position); } - return "indexes[{}]"_format(position); + return fmt::format("indexes[{}]", position); } if (symbol.is_integer) { if (use_instance) { - return "inst->{}[{}*pnodecount+id]"_format(name, position); + return fmt::format("inst->{}[{}*pnodecount+id]", name, position); } - return "indexes[{}*pnodecount+id]"_format(position); + return fmt::format("indexes[{}*pnodecount+id]", position); } if (use_instance) { - return "inst->{}[indexes[{}*pnodecount + id]]"_format(name, position); + return fmt::format("inst->{}[indexes[{}*pnodecount + id]]", name, position); } auto data = symbol.is_vdata ? "_vdata" : "_data"; - return "nt->{}[indexes[{}*pnodecount + id]]"_format(data, position); + return fmt::format("nt->{}[indexes[{}*pnodecount + id]]", data, position); // clang-format on } std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol) const { - return "{}_global.{}"_format(info.mod_suffix, symbol->get_name()); + return fmt::format("{}_global.{}", info.mod_suffix, symbol->get_name()); } std::string CodegenCVisitor::ion_shadow_variable_name(const SymbolType& symbol) const { - return "inst->{}[id]"_format(symbol->get_name()); + return fmt::format("inst->{}[id]", symbol->get_name()); } @@ -2421,14 +2435,14 @@ void CodegenCVisitor::print_backend_info() { auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; printer->add_line("/*********************************************************"); - printer->add_line("Model Name : {}"_format(info.mod_suffix)); - printer->add_line("Filename : {}"_format(info.mod_file + ".mod")); - printer->add_line("NMODL Version : {}"_format(nmodl_version())); - printer->add_line("Vectorized : {}"_format(info.vectorize)); - printer->add_line("Threadsafe : {}"_format(info.thread_safe)); - printer->add_line("Created : {}"_format(stringutils::trim(date))); - printer->add_line("Backend : {}"_format(backend_name())); - printer->add_line("NMODL Compiler : {}"_format(version)); + printer->add_line(fmt::format("Model Name : {}", info.mod_suffix)); + printer->add_line(fmt::format("Filename : {}", info.mod_file + ".mod")); + printer->add_line(fmt::format("NMODL Version : {}", nmodl_version())); + printer->add_line(fmt::format("Vectorized : {}", info.vectorize)); + printer->add_line(fmt::format("Threadsafe : {}", info.thread_safe)); + printer->add_line(fmt::format("Created : {}", stringutils::trim(date))); + printer->add_line(fmt::format("Backend : {}", backend_name())); + printer->add_line(fmt::format("NMODL Compiler : {}", version)); printer->add_line("*********************************************************/"); } @@ -2487,19 +2501,19 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); - printer->add_line("struct {} {}"_format(global_struct(), "{")); + printer->add_line(fmt::format("struct {} {}", global_struct(), "{")); printer->increase_indent(); if (!info.ions.empty()) { for (const auto& ion: info.ions) { - auto name = "{}_type"_format(ion.name); - printer->add_line("{}int {};"_format(qualifier, name)); + auto name = fmt::format("{}_type", ion.name); + printer->add_line(fmt::format("{}int {};", qualifier, name)); codegen_global_variables.push_back(make_symbol(name)); } } if (info.point_process) { - printer->add_line("{}int point_type;"_format(qualifier)); + printer->add_line(fmt::format("{}int point_type;", qualifier)); codegen_global_variables.push_back(make_symbol("point_type")); } @@ -2508,7 +2522,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { - printer->add_line("{}{} {};"_format(qualifier, float_type, name)); + printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); codegen_global_variables.push_back(make_symbol(name)); } } @@ -2524,28 +2538,28 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line("{}{} {}[{}];"_format(qualifier, float_type, name, length)); + printer->add_line(fmt::format("{}{} {}[{}];", qualifier, float_type, name, length)); } else { - printer->add_line("{}{} {};"_format(qualifier, float_type, name)); + printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); } codegen_global_variables.push_back(var); } } if (!info.thread_variables.empty()) { - printer->add_line("{}int thread_data_in_use;"_format(qualifier)); + printer->add_line(fmt::format("{}int thread_data_in_use;", qualifier)); printer->add_line( - "{}{} thread_data[{}];"_format(qualifier, float_type, info.thread_var_data_size)); + fmt::format("{}{} thread_data[{}];", qualifier, float_type, info.thread_var_data_size)); codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); symbol->set_as_array(info.thread_var_data_size); codegen_global_variables.push_back(symbol); } - printer->add_line("{}int reset;"_format(qualifier)); + printer->add_line(fmt::format("{}int reset;", qualifier)); codegen_global_variables.push_back(make_symbol("reset")); - printer->add_line("{}int mech_type;"_format(qualifier)); + printer->add_line(fmt::format("{}int mech_type;", qualifier)); codegen_global_variables.push_back(make_symbol("mech_type")); auto& globals = info.global_variables; @@ -2556,9 +2570,9 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line("{}{} {}[{}];"_format(qualifier, float_type, name, length)); + printer->add_line(fmt::format("{}{} {}[{}];", qualifier, float_type, name, length)); } else { - printer->add_line("{}{} {};"_format(qualifier, float_type, name)); + printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); } codegen_global_variables.push_back(var); } @@ -2568,43 +2582,43 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { for (const auto& var: constants) { auto name = var->get_name(); auto value_ptr = var->get_value(); - printer->add_line("{}{} {};"_format(qualifier, float_type, name)); + printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); codegen_global_variables.push_back(var); } } if (info.primes_size != 0) { - printer->add_line("int* {}slist1;"_format(qualifier)); - printer->add_line("int* {}dlist1;"_format(qualifier)); + printer->add_line(fmt::format("int* {}slist1;", qualifier)); + printer->add_line(fmt::format("int* {}dlist1;", qualifier)); codegen_global_variables.push_back(make_symbol("slist1")); codegen_global_variables.push_back(make_symbol("dlist1")); if (info.derivimplicit_used()) { - printer->add_line("int* {}slist2;"_format(qualifier)); + printer->add_line(fmt::format("int* {}slist2;", qualifier)); codegen_global_variables.push_back(make_symbol("slist2")); } } if (info.table_count > 0) { - printer->add_line("{}double usetable;"_format(qualifier)); + printer->add_line(fmt::format("{}double usetable;", qualifier)); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); for (const auto& block: info.functions_with_table) { auto name = block->get_node_name(); - printer->add_line("{}{} tmin_{};"_format(qualifier, float_type, name)); - printer->add_line("{}{} mfac_{};"_format(qualifier, float_type, name)); + printer->add_line(fmt::format("{}{} tmin_{};", qualifier, float_type, name)); + printer->add_line(fmt::format("{}{} mfac_{};", qualifier, float_type, name)); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); } for (const auto& variable: info.table_statement_variables) { auto name = "t_" + variable->get_name(); - printer->add_line("{}* {}{};"_format(float_type, qualifier, name)); + printer->add_line(fmt::format("{}* {}{};", float_type, qualifier, name)); codegen_global_variables.push_back(make_symbol(name)); } } if (info.vectorize) { - printer->add_line("ThreadDatum* {}ext_call_thread;"_format(qualifier)); + printer->add_line(fmt::format("ThreadDatum* {}ext_call_thread;", qualifier)); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } @@ -2636,7 +2650,7 @@ void CodegenCVisitor::print_mechanism_info() { name += "_" + info.mod_suffix; } if (v->is_array()) { - name += "[{}]"_format(v->get_length()); + name += fmt::format("[{}]", v->get_length()); } printer->add_line(add_escape_quote(name) + ","); } @@ -2674,9 +2688,9 @@ void CodegenCVisitor::print_global_variables_for_hoc() { auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); auto length = variable->get_length(); if (if_vector) { - printer->add_line("{}, {}, {},"_format(ename, name, length)); + printer->add_line(fmt::format("{}, {}, {},", ename, name, length)); } else { - printer->add_line("{}, &{},"_format(ename, name)); + printer->add_line(fmt::format("{}, &{},", ename, name)); } } } @@ -2771,14 +2785,14 @@ static size_t get_register_type_for_ba_block(const ast::Block* block) { void CodegenCVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); - printer->start_block("void _{}_reg() "_format(info.mod_file)); + printer->start_block(fmt::format("void _{}_reg() ", info.mod_file)); // type related information auto mech_type = get_variable_name("mech_type"); auto suffix = add_escape_quote(info.mod_suffix); printer->add_newline(); - printer->add_line("int mech_type = nrn_get_mechtype({});"_format(suffix)); - printer->add_line("{} = mech_type;"_format(mech_type)); + printer->add_line(fmt::format("int mech_type = nrn_get_mechtype({});", suffix)); + printer->add_line(fmt::format("{} = mech_type;", mech_type)); printer->add_line("if (mech_type == -1) {"); printer->add_line(" return;"); printer->add_line("}"); @@ -2790,16 +2804,18 @@ void CodegenCVisitor::print_mechanism_register() { auto args = register_mechanism_arguments(); auto nobjects = num_thread_objects(); if (info.point_process) { - printer->add_line("point_register_mech({}, {}, {}, {});"_format( - args, - info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) : "NULL", - info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) : "NULL", - nobjects)); + printer->add_line( + fmt::format("point_register_mech({}, {}, {}, {});", + args, + info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) + : "NULL", + info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) : "NULL", + nobjects)); } else { - printer->add_line("register_mech({}, {});"_format(args, nobjects)); + printer->add_line(fmt::format("register_mech({}, {});", args, nobjects)); if (info.constructor_node) { - printer->add_line( - "register_constructor({});"_format(method_name(naming::NRN_CONSTRUCTOR_METHOD))); + printer->add_line(fmt::format("register_constructor({});", + method_name(naming::NRN_CONSTRUCTOR_METHOD))); } } @@ -2821,11 +2837,11 @@ void CodegenCVisitor::print_mechanism_register() { */ if (info.vectorize && (info.thread_data_index != 0)) { auto name = get_variable_name("ext_call_thread"); - printer->add_line("thread_mem_init({});"_format(name)); + printer->add_line(fmt::format("thread_mem_init({});", name)); } if (!info.thread_variables.empty()) { - printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); + printer->add_line(fmt::format("{} = 0;", get_variable_name("thread_data_in_use"))); } if (info.thread_callback_register) { @@ -2835,7 +2851,7 @@ void CodegenCVisitor::print_mechanism_register() { if (info.emit_table_thread()) { auto name = method_name("check_table_thread"); - printer->add_line("_nrn_thread_table_reg(mech_type, {});"_format(name)); + printer->add_line(fmt::format("_nrn_thread_table_reg(mech_type, {});", name)); } // register read/write callbacks for pointers @@ -2851,13 +2867,14 @@ void CodegenCVisitor::print_mechanism_register() { // register semantics for index variables for (auto& semantic: info.semantics) { - auto args = "mech_type, {}, {}"_format(semantic.index, add_escape_quote(semantic.name)); - printer->add_line("hoc_register_dparam_semantics({});"_format(args)); + auto args = + fmt::format("mech_type, {}, {}", semantic.index, add_escape_quote(semantic.name)); + printer->add_line(fmt::format("hoc_register_dparam_semantics({});", args)); } if (info.is_watch_used()) { auto watch_fun = compute_method_name(BlockType::Watch); - printer->add_line("hoc_register_watch_check({}, mech_type);"_format(watch_fun)); + printer->add_line(fmt::format("hoc_register_watch_check({}, mech_type);", watch_fun)); } if (info.write_concentration) { @@ -2869,19 +2886,21 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("add_nrn_has_net_event(mech_type);"); } if (info.artificial_cell) { - printer->add_line("add_nrn_artcell(mech_type, {});"_format(info.tqitem_index)); + printer->add_line(fmt::format("add_nrn_artcell(mech_type, {});", info.tqitem_index)); } if (net_receive_buffering_required()) { - printer->add_line("hoc_register_net_receive_buffering({}, mech_type);"_format( - method_name("net_buf_receive"))); + printer->add_line(fmt::format("hoc_register_net_receive_buffering({}, mech_type);", + method_name("net_buf_receive"))); } if (info.num_net_receive_parameters != 0) { auto net_recv_init_arg = "nullptr"; if (info.net_receive_initial_node != nullptr) { net_recv_init_arg = "net_init"; } - auto pnt_recline = "set_pnt_receive(mech_type, {}, {}, num_net_receive_args());"_format( - method_name("net_receive"), net_recv_init_arg); + auto pnt_recline = + fmt::format("set_pnt_receive(mech_type, {}, {}, num_net_receive_args());", + method_name("net_receive"), + net_recv_init_arg); printer->add_line(pnt_recline); } if (info.for_netcon_used) { @@ -2890,7 +2909,7 @@ void CodegenCVisitor::print_mechanism_register() { std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { return a.name == naming::FOR_NETCON_SEMANTIC; })->index; - printer->add_line("add_nrn_fornetcons(mech_type, {});"_format(index)); + printer->add_line(fmt::format("add_nrn_fornetcons(mech_type, {});", index)); } if (info.net_event_used || info.net_send_used) { @@ -2902,8 +2921,9 @@ void CodegenCVisitor::print_mechanism_register() { // register type and associated function name for the block const auto& block = info.before_after_blocks[i]; size_t register_type = get_register_type_for_ba_block(block); - std::string function_name = method_name("nrn_before_after_{}"_format(i)); - printer->add_line("hoc_reg_ba(mech_type, {}, {});"_format(function_name, register_type)); + std::string function_name = method_name(fmt::format("nrn_before_after_{}", i)); + printer->add_line( + fmt::format("hoc_reg_ba(mech_type, {}, {});", function_name, register_type)); } // register variables for hoc @@ -2923,24 +2943,25 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); if (info.vectorize && info.derivimplicit_used()) { - printer->add_line("thread[dith{}()].pval = NULL;"_format(info.derivimplicit_list_num)); + printer->add_line( + fmt::format("thread[dith{}()].pval = NULL;", info.derivimplicit_list_num)); } if (info.vectorize && (info.top_local_thread_size != 0)) { auto length = info.top_local_thread_size; - auto allocation = "(double*)mem_alloc({}, sizeof(double))"_format(length); - auto line = "thread[top_local_var_tid()].pval = {};"_format(allocation); + auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); + auto line = fmt::format("thread[top_local_var_tid()].pval = {};", allocation); printer->add_line(line); } if (info.thread_var_data_size != 0) { auto length = info.thread_var_data_size; auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); - auto allocation = "(double*)mem_alloc({}, sizeof(double))"_format(length); - printer->add_line("if ({}) {}"_format(thread_data_in_use, "{")); - printer->add_line(" thread[thread_var_tid()].pval = {};"_format(allocation)); + auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); + printer->add_line(fmt::format("if ({}) {}", thread_data_in_use, "{")); + printer->add_line(fmt::format(" thread[thread_var_tid()].pval = {};", allocation)); printer->add_line("} else {"); - printer->add_line(" thread[thread_var_tid()].pval = {};"_format(thread_data)); - printer->add_line(" {} = 1;"_format(thread_data_in_use)); + printer->add_line(fmt::format(" thread[thread_var_tid()].pval = {};", thread_data)); + printer->add_line(fmt::format(" {} = 1;", thread_data_in_use)); printer->add_line("}"); } printer->end_block(3); @@ -2953,8 +2974,8 @@ void CodegenCVisitor::print_thread_memory_callbacks() { // clang-format off if (info.vectorize && info.derivimplicit_used()) { int n = info.derivimplicit_list_num; - printer->add_line("free(thread[dith{}()].pval);"_format(n)); - printer->add_line("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));"_format(n)); + printer->add_line(fmt::format("free(thread[dith{}()].pval);", n)); + printer->add_line(fmt::format("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));", n)); } // clang-format on @@ -2965,8 +2986,9 @@ void CodegenCVisitor::print_thread_memory_callbacks() { if (info.thread_var_data_size != 0) { auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); - printer->add_line("if (thread[thread_var_tid()].pval == {}) {}"_format(thread_data, "{")); - printer->add_line(" {} = 0;"_format(thread_data_in_use)); + printer->add_line( + fmt::format("if (thread[thread_var_tid()].pval == {}) {}", thread_data, "{")); + printer->add_line(fmt::format(" {} = 0;", thread_data_in_use)); printer->add_line("} else {"); printer->add_line(" free(thread[thread_var_tid()].pval);"); printer->add_line("}"); @@ -2980,29 +3002,30 @@ void CodegenCVisitor::print_mechanism_range_var_structure() { auto int_type = default_int_data_type(); printer->add_newline(2); printer->add_line("/** all mechanism instance variables */"); - printer->start_block("struct {} "_format(instance_struct())); + printer->start_block(fmt::format("struct {} ", instance_struct())); for (auto& var: codegen_float_variables) { auto name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? k_const() : ""; - printer->add_line("{}{}* {}{};"_format(qualifier, type, ptr_type_qualifier(), name)); + printer->add_line(fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); } for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? k_const() : ""; printer->add_line( - "{}{}* {}{};"_format(qualifier, int_type, ptr_type_qualifier(), name)); + fmt::format("{}{}* {}{};", qualifier, int_type, ptr_type_qualifier(), name)); } else { auto qualifier = var.is_constant ? k_const() : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->add_line("{}{}* {}{};"_format(qualifier, type, ptr_type_qualifier(), name)); + printer->add_line( + fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); } } if (channel_task_dependency_enabled()) { for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); - printer->add_line("{}* {}{};"_format(float_type, ptr_type_qualifier(), name)); + printer->add_line(fmt::format("{}* {}{};", float_type, ptr_type_qualifier(), name)); } } printer->end_block(); @@ -3024,13 +3047,13 @@ void CodegenCVisitor::print_ion_var_structure() { for (auto& ion: info.ions) { for (auto& var: ion.writes) { - printer->add_line("{} {};"_format(float_type, var)); + printer->add_line(fmt::format("{} {};", float_type, var)); members.push_back(var); } } for (auto& var: info.currents) { if (!info.is_ion_variable(var)) { - printer->add_line("{} {};"_format(float_type, var)); + printer->add_line(fmt::format("{} {};", float_type, var)); members.push_back(var); } } @@ -3048,7 +3071,7 @@ void CodegenCVisitor::print_ion_var_constructor(const std::vector& printer->add_newline(); printer->add_line("IonCurVar() : ", 0); for (int i = 0; i < members.size(); i++) { - printer->add_text("{}(0)"_format(members[i])); + printer->add_text(fmt::format("{}(0)", members[i])); if (i + 1 < members.size()) { printer->add_text(", "); } @@ -3093,22 +3116,25 @@ void CodegenCVisitor::print_global_variable_setup() { if (info.primes_size != 0) { if (info.primes_size != info.prime_variables_by_order.size()) { throw std::runtime_error{ - "primes_size = {} differs from prime_variables_by_order.size() = {}, this should not happen."_format( - info.primes_size, info.prime_variables_by_order.size())}; + fmt::format("primes_size = {} differs from prime_variables_by_order.size() = {}, " + "this should not happen.", + info.primes_size, + info.prime_variables_by_order.size())}; } auto slist1 = get_variable_name("slist1"); auto dlist1 = get_variable_name("dlist1"); auto n = info.primes_size; - printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(slist1, n)); - printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(dlist1, n)); + printer->add_line(fmt::format("{} = (int*) mem_alloc({}, sizeof(int));", slist1, n)); + printer->add_line(fmt::format("{} = (int*) mem_alloc({}, sizeof(int));", dlist1, n)); allocated_variables.push_back(slist1); allocated_variables.push_back(dlist1); int id = 0; for (auto& prime: info.prime_variables_by_order) { auto name = prime->get_name(); - printer->add_line("{}[{}] = {};"_format(slist1, id, position_of_float_var(name))); - printer->add_line("{}[{}] = {};"_format(dlist1, id, position_of_float_var("D" + name))); + printer->add_line(fmt::format("{}[{}] = {};", slist1, id, position_of_float_var(name))); + printer->add_line( + fmt::format("{}[{}] = {};", dlist1, id, position_of_float_var("D" + name))); id++; } } @@ -3118,11 +3144,11 @@ void CodegenCVisitor::print_global_variable_setup() { auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); auto slist2 = get_variable_name("slist2"); auto nprimes = info.primes_size; - printer->add_line("{} = (int*) mem_alloc({}, sizeof(int));"_format(slist2, nprimes)); + printer->add_line(fmt::format("{} = (int*) mem_alloc({}, sizeof(int));", slist2, nprimes)); int id = 0; for (auto& variable: primes) { auto name = variable->get_name(); - printer->add_line("{}[{}] = {};"_format(slist2, id, position_of_float_var(name))); + printer->add_line(fmt::format("{}[{}] = {};", slist2, id, position_of_float_var(name))); id++; } allocated_variables.push_back(slist2); @@ -3131,9 +3157,9 @@ void CodegenCVisitor::print_global_variable_setup() { // memory for thread member if (info.vectorize && (info.thread_data_index != 0)) { auto n = info.thread_data_index; - auto alloc = "(ThreadDatum*) mem_alloc({}, sizeof(ThreadDatum))"_format(n); + auto alloc = fmt::format("(ThreadDatum*) mem_alloc({}, sizeof(ThreadDatum))", n); auto name = get_variable_name("ext_call_thread"); - printer->add_line("{} = {};"_format(name, alloc)); + printer->add_line(fmt::format("{} = {};", name, alloc)); allocated_variables.push_back(name); } @@ -3143,14 +3169,14 @@ void CodegenCVisitor::print_global_variable_setup() { auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { auto global_name = get_variable_name(name); - printer->add_line("{} = 0.0;"_format(global_name)); + printer->add_line(fmt::format("{} = 0.0;", global_name)); } } // note : v is not needed in global structure for nmodl even if vectorize is false if (!info.thread_variables.empty()) { - printer->add_line("{} = 0;"_format(get_variable_name("thread_data_in_use"))); + printer->add_line(fmt::format("{} = 0;", get_variable_name("thread_data_in_use"))); } // initialize global variables @@ -3163,7 +3189,7 @@ void CodegenCVisitor::print_global_variable_setup() { value = *value_ptr; } /// use %g to be same as nocmodl in neuron - printer->add_line("{} = {:g};"_format(name, value)); + printer->add_line(fmt::format("{} = {:g};", name, value)); } } @@ -3176,18 +3202,18 @@ void CodegenCVisitor::print_global_variable_setup() { value = *value_ptr; } /// use %g to be same as nocmodl in neuron - printer->add_line("{} = {:g};"_format(name, value)); + printer->add_line(fmt::format("{} = {:g};", name, value)); } if (info.table_count > 0) { auto name = get_variable_name(naming::USE_TABLE_VARIABLE); - printer->add_line("{} = 1;"_format(name)); + printer->add_line(fmt::format("{} = 1;", name)); for (auto& variable: info.table_statement_variables) { auto name = get_variable_name("t_" + variable->get_name()); int num_values = variable->get_num_values(); printer->add_line( - "{} = (double*) mem_alloc({}, sizeof(double));"_format(name, num_values)); + fmt::format("{} = (double*) mem_alloc({}, sizeof(double));", name, num_values)); } } @@ -3204,7 +3230,7 @@ void CodegenCVisitor::print_global_variable_setup() { printer->add_line("// do nothing"); } else { for (auto& var: allocated_variables) { - printer->add_line("mem_free({});"_format(var)); + printer->add_line(fmt::format("mem_free({});", var)); } } printer->end_block(1); @@ -3214,26 +3240,26 @@ void CodegenCVisitor::print_global_variable_setup() { void CodegenCVisitor::print_shadow_vector_setup() { printer->add_newline(2); printer->add_line("/** allocate and initialize shadow vector */"); - auto args = "{}* inst, Memb_list* ml"_format(instance_struct()); - printer->start_block("static inline void setup_shadow_vectors({}) "_format(args)); + auto args = fmt::format("{}* inst, Memb_list* ml", instance_struct()); + printer->start_block(fmt::format("static inline void setup_shadow_vectors({}) ", args)); if (channel_task_dependency_enabled()) { printer->add_line("int nodecount = ml->nodecount;"); for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); auto type = default_float_data_type(); - auto allocation = "({0}*) mem_alloc(nodecount, sizeof({0}))"_format(type); - printer->add_line("inst->{0} = {1};"_format(name, allocation)); + auto allocation = fmt::format("({0}*) mem_alloc(nodecount, sizeof({0}))", type); + printer->add_line(fmt::format("inst->{0} = {1};", name, allocation)); } } printer->end_block(3); printer->add_line("/** free shadow vector */"); - args = "{}* inst"_format(instance_struct()); - printer->start_block("static inline void free_shadow_vectors({}) "_format(args)); + args = fmt::format("{}* inst", instance_struct()); + printer->start_block(fmt::format("static inline void free_shadow_vectors({}) ", args)); if (channel_task_dependency_enabled()) { for (auto& var: codegen_shadow_variables) { auto name = var->get_name(); - printer->add_line("mem_free(inst->{});"_format(name)); + printer->add_line(fmt::format("mem_free(inst->{});", name)); } } printer->end_block(1); @@ -3245,8 +3271,8 @@ void CodegenCVisitor::print_setup_range_variable() { printer->add_newline(2); printer->add_line("/** allocate and setup array for range variable */"); printer->start_block( - "static inline {}* setup_range_variable(double* variable, int n) "_format(type)); - printer->add_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));"_format(type)); + fmt::format("static inline {}* setup_range_variable(double* variable, int n) ", type)); + printer->add_line(fmt::format("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));", type)); printer->add_line("for(size_t i = 0; i < n; i++) {"); printer->add_line(" data[i] = variable[i];"); printer->add_line("}"); @@ -3297,7 +3323,8 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_newline(2); printer->add_line("/** initialize mechanism instance variables */"); printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml) "); - printer->add_line("{0}* inst = ({0}*) mem_alloc(1, sizeof({0}));"_format(instance_struct())); + printer->add_line( + fmt::format("{0}* inst = ({0}*) mem_alloc(1, sizeof({0}));", instance_struct())); if (channel_task_dependency_enabled() && !codegen_shadow_variables.empty()) { printer->add_line("setup_shadow_vectors(inst, ml);"); } @@ -3320,12 +3347,12 @@ void CodegenCVisitor::print_instance_variable_setup() { auto name = var->get_name(); auto range_var_type = get_range_var_float_type(var); if (float_type == range_var_type) { - auto variable = "ml->data+{}{}"_format(id, stride); + auto variable = fmt::format("ml->data+{}{}", id, stride); auto device_variable = get_variable_device_pointer(variable, float_type_pointer); - printer->add_line("inst->{} = {};"_format(name, device_variable)); + printer->add_line(fmt::format("inst->{} = {};", name, device_variable)); } else { - printer->add_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);"_format( - name, id, stride)); + printer->add_line(fmt::format( + "inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);", name, id, stride)); variables_to_free.push_back(name); } id += var->get_length(); @@ -3346,7 +3373,7 @@ void CodegenCVisitor::print_instance_variable_setup() { type = info.artificial_cell ? "void*" : float_type_pointer; } auto device_variable = get_variable_device_pointer(variable, type); - printer->add_line("inst->{} = {};"_format(name, device_variable)); + printer->add_line(fmt::format("inst->{} = {};", name, device_variable)); } printer->add_line("ml->instance = inst;"); @@ -3355,10 +3382,10 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("/** cleanup mechanism instance variables */"); printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); - printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + printer->add_line(fmt::format("{0}* inst = ({0}*) ml->instance;", instance_struct())); if (range_variable_setup_required()) { for (auto& var: variables_to_free) { - printer->add_line("mem_free((void*)inst->{});"_format(var)); + printer->add_line(fmt::format("mem_free((void*)inst->{});", var)); } } printer->add_line("mem_free((void*)inst);"); @@ -3391,7 +3418,7 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { if (!info.is_ionic_conc(name)) { auto lhs = get_variable_name(name); auto rhs = get_variable_name(name + "0"); - printer->add_line("{} = {};"_format(lhs, rhs)); + printer->add_line(fmt::format("{} = {};", lhs, rhs)); } } @@ -3426,7 +3453,7 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, } print_global_method_annotation(); - printer->start_block("void {}({})"_format(method, args)); + printer->start_block(fmt::format("void {}({})", method, args)); if (type != BlockType::Destructor && type != BlockType::Constructor) { // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks // running anything on the GPU. @@ -3439,25 +3466,26 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); printer->add_line( - "{}int* {}node_index = ml->nodeindices;"_format(k_const(), ptr_type_qualifier())); - printer->add_line("double* {}data = ml->data;"_format(ptr_type_qualifier())); + fmt::format("{}int* {}node_index = ml->nodeindices;", k_const(), ptr_type_qualifier())); + printer->add_line(fmt::format("double* {}data = ml->data;", ptr_type_qualifier())); printer->add_line( - "{}double* {}voltage = nt->_actual_v;"_format(k_const(), ptr_type_qualifier())); + fmt::format("{}double* {}voltage = nt->_actual_v;", k_const(), ptr_type_qualifier())); if (type == BlockType::Equation) { - printer->add_line("double* {} vec_rhs = nt->_actual_rhs;"_format(ptr_type_qualifier())); - printer->add_line("double* {} vec_d = nt->_actual_d;"_format(ptr_type_qualifier())); + printer->add_line( + fmt::format("double* {} vec_rhs = nt->_actual_rhs;", ptr_type_qualifier())); + printer->add_line(fmt::format("double* {} vec_d = nt->_actual_d;", ptr_type_qualifier())); print_rhs_d_shadow_variables(); } - printer->add_line("Datum* {}indexes = ml->pdata;"_format(ptr_type_qualifier())); - printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(ptr_type_qualifier())); + printer->add_line(fmt::format("Datum* {}indexes = ml->pdata;", ptr_type_qualifier())); + printer->add_line(fmt::format("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier())); if (type == BlockType::Initial) { printer->add_newline(); printer->add_line("setup_instance(nt, ml);"); } // clang-format off - printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); + printer->add_line(fmt::format("{0}* {1}inst = ({0}*) ml->instance;", instance_struct(), ptr_type_qualifier())); // clang-format on printer->add_newline(1); } @@ -3474,16 +3502,16 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { int nequation = info.num_equations; int list_num = info.derivimplicit_list_num; // clang-format off - printer->add_line("int& deriv_advance_flag = *deriv{}_advance(thread);"_format(list_num)); + printer->add_line(fmt::format("int& deriv_advance_flag = *deriv{}_advance(thread);", list_num)); printer->add_line("deriv_advance_flag = 0;"); print_deriv_advance_flag_transfer_to_device(); - printer->add_line("auto ns = newtonspace{}(thread);"_format(list_num)); - printer->add_line("auto& th = thread[dith{}()];"_format(list_num)); + printer->add_line(fmt::format("auto ns = newtonspace{}(thread);", list_num)); + printer->add_line(fmt::format("auto& th = thread[dith{}()];", list_num)); printer->start_block("if (*ns == nullptr)"); - printer->add_line("int vec_size = 2*{}*pnodecount*sizeof(double);"_format(nequation)); - printer->add_line("double* vec = makevector(vec_size);"_format(nequation)); - printer->add_line("th.pval = vec;"_format(list_num)); - printer->add_line("*ns = nrn_cons_newtonspace({}, pnodecount);"_format(nequation)); + printer->add_line(fmt::format("int vec_size = 2*{}*pnodecount*sizeof(double);", nequation)); + printer->add_line(fmt::format("double* vec = makevector(vec_size);", nequation)); + printer->add_line(fmt::format("th.pval = vec;", list_num)); + printer->add_line(fmt::format("*ns = nrn_cons_newtonspace({}, pnodecount);", nequation)); print_newtonspace_transfer_to_device(); printer->end_block(1); // clang-format on @@ -3497,10 +3525,11 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { } if (!info.changed_dt.empty()) { - printer->add_line( - "double _save_prev_dt = {};"_format(get_variable_name(naming::NTHREAD_DT_VARIABLE))); - printer->add_line( - "{} = {};"_format(get_variable_name(naming::NTHREAD_DT_VARIABLE), info.changed_dt)); + printer->add_line(fmt::format("double _save_prev_dt = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE))); + printer->add_line(fmt::format("{} = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE), + info.changed_dt)); print_dt_update_to_device(); } @@ -3508,7 +3537,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_channel_iteration_block_begin(BlockType::Initial); if (info.net_receive_node != nullptr) { - printer->add_line("{} = -1e20;"_format(get_variable_name("tsave"))); + printer->add_line(fmt::format("{} = -1e20;", get_variable_name("tsave"))); } print_initial_block(info.initial_node); @@ -3518,7 +3547,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { if (!info.changed_dt.empty()) { printer->add_line( - "{} = _save_prev_dt;"_format(get_variable_name(naming::NTHREAD_DT_VARIABLE))); + fmt::format("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE))); print_dt_update_to_device(); } @@ -3557,11 +3586,12 @@ void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t bl std::string ba_block_type = ba_block->get_type()->eval(); /// name of the before/after function - std::string function_name = method_name("nrn_before_after_{}"_format(block_id)); + std::string function_name = method_name(fmt::format("nrn_before_after_{}", block_id)); /// print common function code like init/state/current printer->add_newline(2); - printer->add_line("/** {} of block type {} # {} */"_format(ba_type, ba_block_type, block_id)); + printer->add_line( + fmt::format("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id)); print_global_function_common_code(BlockType::BeforeAfter, function_name); print_channel_iteration_tiling_block_begin(BlockType::BeforeAfter); @@ -3625,7 +3655,8 @@ void CodegenCVisitor::print_nrn_destructor() { void CodegenCVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); - printer->start_block("static void {}(double* data, Datum* indexes, int type) "_format(method)); + printer->start_block( + fmt::format("static void {}(double* data, Datum* indexes, int type) ", method)); printer->add_line("// do nothing"); printer->end_block(1); } @@ -3641,17 +3672,18 @@ void CodegenCVisitor::print_watch_activate() { } codegen = true; printer->add_newline(2); - auto inst = "{}* inst"_format(instance_struct()); + auto inst = fmt::format("{}* inst", instance_struct()); printer->start_block( - "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, double v, bool &watch_remove) "_format( - inst)); + fmt::format("static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, " + "double v, bool &watch_remove) ", + inst)); // initialize all variables only during first watch statement printer->add_line("if (watch_remove == false) {"); for (int i = 0; i < info.watch_count; i++) { - auto name = get_variable_name("watch{}"_format(i + 1)); - printer->add_line(" {} = 0;"_format(name)); + auto name = get_variable_name(fmt::format("watch{}", i + 1)); + printer->add_line(fmt::format(" {} = 0;", name)); } printer->add_line(" watch_remove = true;"); printer->add_line("}"); @@ -3662,11 +3694,11 @@ void CodegenCVisitor::print_watch_activate() { */ for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; - printer->start_block("if (watch_id == {})"_format(i)); + printer->start_block(fmt::format("if (watch_id == {})", i)); - auto varname = get_variable_name("watch{}"_format(i + 1)); + auto varname = get_variable_name(fmt::format("watch{}", i + 1)); printer->add_indent(); - printer->add_text("{} = 2 + ("_format(varname)); + printer->add_text(fmt::format("{} = 2 + (", varname)); auto watch = statement->get_statements().front(); watch->get_expression()->visit_children(*this); printer->add_text(");"); @@ -3706,10 +3738,10 @@ void CodegenCVisitor::print_watch_check() { for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; auto watch = statement->get_statements().front(); - auto varname = get_variable_name("watch{}"_format(i + 1)); + auto varname = get_variable_name(fmt::format("watch{}", i + 1)); // start block 1 - printer->start_block("if ({}&2 && watch_untriggered)"_format(varname)); + printer->start_block(fmt::format("if ({}&2 && watch_untriggered)", varname)); // start block 2 printer->add_indent(); @@ -3720,7 +3752,7 @@ void CodegenCVisitor::print_watch_check() { printer->increase_indent(); // start block 3 - printer->start_block("if (({}&1) == 0)"_format(varname)); + printer->start_block(fmt::format("if (({}&1) == 0)", varname)); printer->add_line("watch_untriggered = false;"); @@ -3730,19 +3762,19 @@ void CodegenCVisitor::print_watch_check() { printer->add_text("net_send_buffering("); auto t = get_variable_name("t"); printer->add_text( - "ml->_net_send_buffer, 0, {}, 0, {}, {}+0.0, "_format(tqitem, point_process, t)); + fmt::format("ml->_net_send_buffer, 0, {}, 0, {}, {}+0.0, ", tqitem, point_process, t)); watch->get_value()->accept(*this); printer->add_text(");"); printer->add_newline(); printer->end_block(1); - printer->add_line("{} = 3;"_format(varname)); + printer->add_line(fmt::format("{} = 3;", varname)); // end block 3 // start block 3 printer->decrease_indent(); printer->start_block("} else"); - printer->add_line("{} = 2;"_format(varname)); + printer->add_line(fmt::format("{} = 2;", varname)); printer->end_block(1); // end block 3 @@ -3771,14 +3803,15 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need print_kernel_data_present_annotation_block_begin(); } - printer->add_line("{}int nodecount = ml->nodecount;"_format(param_type_qualifier())); - printer->add_line("{}int pnodecount = ml->_nodecount_padded;"_format(param_type_qualifier())); + printer->add_line(fmt::format("{}int nodecount = ml->nodecount;", param_type_qualifier())); + printer->add_line( + fmt::format("{}int pnodecount = ml->_nodecount_padded;", param_type_qualifier())); printer->add_line("double* data = ml->data;"); printer->add_line("double* weights = nt->weights;"); printer->add_line("Datum* indexes = ml->pdata;"); printer->add_line("ThreadDatum* thread = ml->_thread;"); if (need_mech_inst) { - printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + printer->add_line(fmt::format("{0}* inst = ({0}*) ml->instance;", instance_struct())); } if (node.is_initial_block()) { @@ -3794,7 +3827,7 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need auto name = parameter->get_node_name(); bool var_used = VarUsageVisitor().variable_used(node, "(*" + name + ")"); if (var_used) { - auto statement = "double* {} = weights + weight_index + {};"_format(name, i); + auto statement = fmt::format("double* {} = weights + weight_index + {};", name, i); printer->add_line(statement); RenameVisitor vr(name, "*" + name); node.visit_children(vr); @@ -3823,12 +3856,12 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text("artcell_net_send(&{}, {}, {}, nt->_t+"_format(tqitem, weight_index, pnt)); + printer->add_text(fmt::format("artcell_net_send(&{}, {}, {}, nt->_t+", tqitem, weight_index, pnt)); } else { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text("ml->_net_send_buffer, 0, {}, {}, {}, {}+"_format(tqitem, weight_index, point_process, t)); + printer->add_text(fmt::format("ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t)); } // clang-format off print_vector_elements(arguments, ", "); @@ -3850,14 +3883,14 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text("artcell_net_move(&{}, {}, nt->_t+"_format(tqitem, pnt)); + printer->add_text(fmt::format("artcell_net_move(&{}, {}, nt->_t+", tqitem, pnt)); print_vector_elements(arguments, ", "); printer->add_text(")"); } else { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text("ml->_net_send_buffer, 2, {}, {}, {}, "_format(tqitem, weight_index, point_process)); + printer->add_text(fmt::format("ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process)); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); printer->add_text(")"); @@ -3873,7 +3906,7 @@ void CodegenCVisitor::print_net_event_call(const FunctionCall& node) { } else { auto point_process = get_variable_name("point_process"); printer->add_text("net_send_buffering("); - printer->add_text("ml->_net_send_buffer, 1, -1, -1, {}, "_format(point_process)); + printer->add_text(fmt::format("ml->_net_send_buffer, 1, -1, -1, {}, ", point_process)); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); } @@ -3930,7 +3963,7 @@ void CodegenCVisitor::print_net_init() { auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); - printer->start_block("static void net_init({}) "_format(args)); + printer->start_block(fmt::format("static void net_init({}) ", args)); auto block = node->get_statement_block().get(); if (block->get_statements().empty()) { printer->add_line("// do nothing"); @@ -3971,7 +4004,7 @@ void CodegenCVisitor::print_send_event_move() { std::string CodegenCVisitor::net_receive_buffering_declaration() { - return "void {}(NrnThread* nt)"_format(method_name("net_buf_receive")); + return fmt::format("void {}(NrnThread* nt)", method_name("net_buf_receive")); } @@ -4010,9 +4043,9 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { print_kernel_data_present_annotation_block_begin(); printer->add_line( - "NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;"_format(ptr_type_qualifier())); + fmt::format("NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;", ptr_type_qualifier())); if (need_mech_inst) { - printer->add_line("{0}* inst = ({0}*) ml->instance;"_format(instance_struct())); + printer->add_line(fmt::format("{0}* inst = ({0}*) ml->instance;", instance_struct())); } print_net_receive_loop_begin(); printer->add_line("int start = nrb->_displ[i];"); @@ -4025,7 +4058,7 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->add_line("double flag = nrb->_nrb_flag[index];"); printer->add_line("Point_process* point_process = nt->pntprocs + offset;"); printer->add_line( - "{}(t, point_process, inst, nt, ml, weight_index, flag);"_format(net_receive)); + fmt::format("{}(t, point_process, inst, nt, ml, weight_index, flag);", net_receive)); printer->end_block(1); print_net_receive_loop_end(); @@ -4058,7 +4091,7 @@ void CodegenCVisitor::print_net_send_buffering() { auto args = "NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; - printer->start_block("static inline void net_send_buffering({}) "_format(args)); + printer->start_block(fmt::format("static inline void net_send_buffering({}) ", args)); printer->add_line("int i = 0;"); print_device_atomic_capture_annotation(); printer->add_line("i = nsb->_cnt++;"); @@ -4087,7 +4120,7 @@ void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { // sanitize node_name since we want to substitute names like (*w) as they are auto old_name = std::regex_replace(args[i_arg]->get_node_name(), regex_special_chars, R"(\$&)"); - auto new_name = "weights[{} + nt->_fornetcon_weight_perm[i]]"_format(i_arg); + auto new_name = fmt::format("weights[{} + nt->_fornetcon_weight_perm[i]]", i_arg); v.set(old_name, new_name); statement_block->accept(v); } @@ -4097,7 +4130,7 @@ void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { return a.name == naming::FOR_NETCON_SEMANTIC; })->index; - printer->add_text("const size_t offset = {}*pnodecount + id;"_format(index)); + printer->add_text(fmt::format("const size_t offset = {}*pnodecount + id;", index)); printer->add_newline(); printer->add_line( "const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]];"); @@ -4130,7 +4163,7 @@ void CodegenCVisitor::print_net_receive_kernel() { params.emplace_back("", "double", "", "t"); params.emplace_back("", "Point_process*", "", "pnt"); params.emplace_back(param_type_qualifier(), - "{}*"_format(instance_struct()), + fmt::format("{}*", instance_struct()), param_ptr_qualifier(), "inst"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); @@ -4145,7 +4178,8 @@ void CodegenCVisitor::print_net_receive_kernel() { } printer->add_newline(2); - printer->start_block("static inline void {}({}) "_format(name, get_parameter_str(params))); + printer->start_block( + fmt::format("static inline void {}({}) ", name, get_parameter_str(params))); print_net_receive_common_code(*node, info.artificial_cell); if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); @@ -4158,7 +4192,7 @@ void CodegenCVisitor::print_net_receive_kernel() { printer->add_line("v = nt->_actual_v[node_id];"); } - printer->add_line("{} = t;"_format(get_variable_name("tsave"))); + printer->add_line(fmt::format("{} = t;", get_variable_name("tsave"))); if (info.is_watch_used()) { printer->add_line("bool watch_remove = false;"); @@ -4188,7 +4222,7 @@ void CodegenCVisitor::print_net_receive() { params.emplace_back("", "int", "", "weight_index"); params.emplace_back("", "double", "", "flag"); printer->add_newline(2); - printer->start_block("static void {}({}) "_format(name, get_parameter_str(params))); + printer->start_block(fmt::format("static void {}({}) ", name, get_parameter_str(params))); printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); @@ -4227,24 +4261,24 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->add_newline(2); // clang-format off - printer->start_block("int {}_{}({})"_format(block_name, suffix, ext_params)); - auto instance = "{0}* inst = ({0}*)get_memb_list(nt)->instance;"_format(instance_struct()); - auto slist1 = "int* slist{} = {};"_format(list_num, get_variable_name("slist{}"_format(list_num))); - auto slist2 = "int* slist{} = {};"_format(list_num+1, get_variable_name("slist{}"_format(list_num+1))); - auto dlist1 = "int* dlist{} = {};"_format(list_num, get_variable_name("dlist{}"_format(list_num))); - auto dlist2 = "double* dlist{} = (double*) thread[dith{}()].pval + ({}*pnodecount);"_format(list_num + 1, list_num, info.primes_size); + printer->start_block(fmt::format("int {}_{}({})", block_name, suffix, ext_params)); + auto instance = fmt::format("{0}* inst = ({0}*)get_memb_list(nt)->instance;", instance_struct()); + auto slist1 = fmt::format("int* slist{} = {};", list_num, get_variable_name(fmt::format("slist{}", list_num))); + auto slist2 = fmt::format("int* slist{} = {};", list_num+1, get_variable_name(fmt::format("slist{}", list_num+1))); + auto dlist1 = fmt::format("int* dlist{} = {};", list_num, get_variable_name(fmt::format("dlist{}", list_num))); + auto dlist2 = fmt::format("double* dlist{} = (double*) thread[dith{}()].pval + ({}*pnodecount);", list_num + 1, list_num, info.primes_size); printer->add_line(instance); - printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); + printer->add_line(fmt::format("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num)); printer->add_line(slist1); printer->add_line(slist2); printer->add_line(dlist2); - printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); - printer->add_line(" savstate{}[i{}] = data[slist{}[i]{}];"_format(list_num, stride, list_num, stride)); + printer->add_line(fmt::format("for (int i=0; i<{}; i++) {}", info.num_primes, "{")); + printer->add_line(fmt::format(" savstate{}[i{}] = data[slist{}[i]{}];", list_num, stride, list_num, stride)); printer->add_line("}"); - auto argument = "{}, slist{}, _derivimplicit_{}_{}, dlist{}, {}"_format(primes_size, list_num+1, block_name, suffix, list_num + 1, ext_args); - printer->add_line("int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {});"_format(list_num, argument)); + auto argument = fmt::format("{}, slist{}, _derivimplicit_{}_{}, dlist{}, {}", primes_size, list_num+1, block_name, suffix, list_num + 1, ext_args); + printer->add_line(fmt::format("int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {});", list_num, argument)); printer->add_line("return reset;"); printer->end_block(3); @@ -4253,15 +4287,15 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { * comment marker in the generated cpp file for kinderiv.py to * process it and generate correct _kinderiv.h */ - printer->add_line("/* _derivimplicit_ {} _{} */"_format(block_name, info.mod_suffix)); + printer->add_line(fmt::format("/* _derivimplicit_ {} _{} */", block_name, info.mod_suffix)); printer->add_newline(1); - printer->start_block("int _newton_{}_{}({}) "_format(block_name, info.mod_suffix, external_method_parameters())); + printer->start_block(fmt::format("int _newton_{}_{}({}) ", block_name, info.mod_suffix, external_method_parameters())); printer->add_line(instance); if (ion_variable_struct_required()) { print_ion_variable(); } - printer->add_line("double* savstate{} = (double*) thread[dith{}()].pval;"_format(list_num, list_num)); + printer->add_line(fmt::format("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num)); printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); @@ -4269,12 +4303,12 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { print_statement_block(*block->get_statement_block(), false, false); codegen = false; printer->add_line("int counter = -1;"); - printer->add_line("for (int i=0; i<{}; i++) {}"_format(info.num_primes, "{")); - printer->add_line(" if (*deriv{}_advance(thread)) {}"_format(list_num, "{")); - printer->add_line(" dlist{0}[(++counter){1}] = data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;"_format(list_num + 1, stride, list_num)); + printer->add_line(fmt::format("for (int i=0; i<{}; i++) {}", info.num_primes, "{")); + printer->add_line(fmt::format(" if (*deriv{}_advance(thread)) {}", list_num, "{")); + printer->add_line(fmt::format(" dlist{0}[(++counter){1}] = data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;", list_num + 1, stride, list_num)); printer->add_line(" }"); printer->add_line(" else {"); - printer->add_line(" dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];"_format(list_num + 1, stride, list_num)); + printer->add_line(fmt::format(" dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];", list_num + 1, stride, list_num)); printer->add_line(" }"); printer->add_line("}"); printer->add_line("return 0;"); @@ -4296,14 +4330,18 @@ void CodegenCVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallb auto num_primes = info.num_primes; auto suffix = info.mod_suffix; int num = info.derivimplicit_list_num; - auto slist = get_variable_name("slist{}"_format(num)); - auto dlist = get_variable_name("dlist{}"_format(num)); + auto slist = get_variable_name(fmt::format("slist{}", num)); + auto dlist = get_variable_name(fmt::format("dlist{}", num)); auto block_name = node.get_node_to_solve()->get_node_name(); - auto args = - "{}, {}, {}, _derivimplicit_{}_{}, {}" - ""_format(num_primes, slist, dlist, block_name, suffix, thread_args); - auto statement = "derivimplicit_thread({});"_format(args); + auto args = fmt::format("{}, {}, {}, _derivimplicit_{}_{}, {}", + num_primes, + slist, + dlist, + block_name, + suffix, + thread_args); + auto statement = fmt::format("derivimplicit_thread({});", args); printer->add_line(statement); } @@ -4390,12 +4428,13 @@ void CodegenCVisitor::print_nrn_current(const BreakpointBlock& node) { const auto& block = node.get_statement_block(); printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline double nrn_current({})"_format(get_parameter_str(args))); + printer->start_block( + fmt::format("static inline double nrn_current({})", get_parameter_str(args))); printer->add_line("double current = 0.0;"); print_statement_block(*block, false, false); for (auto& current: info.currents) { auto name = get_variable_name(current); - printer->add_line("current += {};"_format(name)); + printer->add_line(fmt::format("current += {};", name)); } printer->add_line("return current;"); printer->end_block(1); @@ -4414,7 +4453,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no sum += "+"; } } - printer->add_line("double rhs = {};"_format(sum)); + printer->add_line(fmt::format("double rhs = {};", sum)); } std::string sum; @@ -4425,7 +4464,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no sum += "+"; } } - printer->add_line("double g = {};"_format(sum)); + printer->add_line(fmt::format("double g = {};", sum)); for (const auto& conductance: info.conductances) { if (!conductance.ion.empty()) { @@ -4440,25 +4479,26 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { - printer->add_line("double g = nrn_current({}+0.001);"_format(internal_method_arguments())); + printer->add_line( + fmt::format("double g = nrn_current({}+0.001);", internal_method_arguments())); for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { auto name = get_variable_name(var); - printer->add_line("double di{} = {};"_format(ion.name, name)); + printer->add_line(fmt::format("double di{} = {};", ion.name, name)); } } } - printer->add_line("double rhs = nrn_current({});"_format(internal_method_arguments())); + printer->add_line(fmt::format("double rhs = nrn_current({});", internal_method_arguments())); printer->add_line("g = (g-rhs)/0.001;"); for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { auto lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; - auto rhs = "(di{}-{})/0.001"_format(ion.name, get_variable_name(var)); + auto rhs = fmt::format("(di{}-{})/0.001", ion.name, get_variable_name(var)); if (info.point_process) { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - rhs += "*1.e2/{}"_format(area); + rhs += fmt::format("*1.e2/{}", area); } ShadowUseStatement statement{lhs, "+=", rhs}; auto text = process_shadow_update_statement(statement, BlockType::Equation); @@ -4496,7 +4536,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { if (info.point_process) { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - printer->add_line("double mfactor = 1.e2/{};"_format(area)); + printer->add_line(fmt::format("double mfactor = 1.e2/{};", area)); printer->add_line("g = g*mfactor;"); printer->add_line("rhs = rhs*mfactor;"); } @@ -4528,9 +4568,9 @@ void CodegenCVisitor::print_fast_imem_calculation() { printer->add_line("int node_id = node_index[id];"); } print_atomic_reduction_pragma(); - printer->add_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};"_format(rhs_op, rhs)); + printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs)); print_atomic_reduction_pragma(); - printer->add_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};"_format(d_op, d)); + printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d)); if (nrn_cur_reduction_loop_required()) { print_shadow_reduction_block_end(); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 8062a45420..fd051d50a1 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -31,8 +31,6 @@ #include "visitors/ast_visitor.hpp" -using namespace fmt::literals; - namespace nmodl { /// encapsulates code generation backend implementations namespace codegen { @@ -391,7 +389,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * Name of structure that wraps range variables */ std::string instance_struct() const { - return "{}_Instance"_format(info.mod_suffix); + return fmt::format("{}_Instance", info.mod_suffix); } @@ -399,7 +397,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * Name of structure that wraps range variables */ std::string global_struct() const { - return "{}_Store"_format(info.mod_suffix); + return fmt::format("{}_Store", info.mod_suffix); } @@ -2025,9 +2023,8 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin print_device_method_annotation(); printer->add_indent(); - printer->add_text("inline {} {}({})"_format(return_type, - method_name(name), - get_parameter_str(internal_params))); + printer->add_text(fmt::format( + "inline {} {}({})", return_type, method_name(name), get_parameter_str(internal_params))); enable_variable_name_lookup = true; } diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 33863f1a02..a67e003d48 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -13,8 +13,6 @@ #include "visitors/visitor_utils.hpp" -using namespace fmt::literals; - namespace nmodl { namespace codegen { @@ -51,9 +49,11 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl auto unhandled_solver_method = handled_solvers.find(method->get_node_name()) == handled_solvers.end(); if (unhandled_solver_method) { - unhandled_method_error_message - << "\"{}\" solving method used at [{}] not handled. Supported methods are cnexp, euler, derivimplicit and sparse\n"_format( - method->get_node_name(), method->get_token()->position()); + unhandled_method_error_message << fmt::format( + "\"{}\" solving method used at [{}] not handled. Supported methods are cnexp, euler, " + "derivimplicit and sparse\n", + method->get_node_name(), + method->get_token()->position()); } return unhandled_method_error_message.str(); } @@ -64,9 +64,11 @@ std::string CodegenCompatibilityVisitor::return_error_global_var( auto global_var = std::dynamic_pointer_cast(ast_node); std::stringstream error_message_global_var; if (node.get_symbol_table()->lookup(global_var->get_node_name())->get_write_count() > 0) { - error_message_global_var - << "\"{}\" variable found at [{}] should be defined as a RANGE variable instead of GLOBAL to enable backend transformations\n"_format( - global_var->get_node_name(), global_var->get_token()->position()); + error_message_global_var << fmt::format( + "\"{}\" variable found at [{}] should be defined as a RANGE variable instead of GLOBAL " + "to enable backend transformations\n", + global_var->get_node_name(), + global_var->get_token()->position()); } return error_message_global_var.str(); } @@ -78,9 +80,10 @@ std::string CodegenCompatibilityVisitor::return_error_param_var( std::stringstream error_message_global_var; auto symbol = node.get_symbol_table()->lookup(param_assign->get_node_name()); if (!symbol->is_writable() && symbol->get_write_count() > 0) { - error_message_global_var - << "\"{}\" variable found at [{}] should be writable if it needs to be written\n"_format( - symbol->get_name(), symbol->get_token().position()); + error_message_global_var << fmt::format( + "\"{}\" variable found at [{}] should be writable if it needs to be written\n", + symbol->get_name(), + symbol->get_token().position()); } return error_message_global_var.str(); } @@ -152,7 +155,7 @@ bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) { std::istringstream ss_stringstream(ss.str()); while (std::getline(ss_stringstream, line)) { if (!line.empty()) - logger->error("Code Incompatibility :: {}"_format(line)); + logger->error(fmt::format("Code Incompatibility :: {}", line)); } return true; } diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index b2f205aa4d..7f46b66755 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -22,8 +22,6 @@ #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" -using namespace fmt::literals; - namespace nmodl { namespace codegen { @@ -102,10 +100,10 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { template std::string return_error_with_name(ast::Ast& node, const std::shared_ptr& ast_node) { auto real_type_block = std::dynamic_pointer_cast(ast_node); - return "\"{}\" {}construct found at [{}] is not handled\n"_format( - ast_node->get_node_name(), - real_type_block->get_nmodl_name(), - real_type_block->get_token()->position()); + return fmt::format("\"{}\" {}construct found at [{}] is not handled\n", + ast_node->get_node_name(), + real_type_block->get_nmodl_name(), + real_type_block->get_token()->position()); } /// Takes as parameter an std::shared_ptr node @@ -120,8 +118,9 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { std::string return_error_without_name(ast::Ast& node, const std::shared_ptr& ast_node) { auto real_type_block = std::dynamic_pointer_cast(ast_node); - return "{}construct found at [{}] is not handled\n"_format( - real_type_block->get_nmodl_name(), real_type_block->get_token()->position()); + return fmt::format("{}construct found at [{}] is not handled\n", + real_type_block->get_nmodl_name(), + real_type_block->get_token()->position()); } /// Takes as parameter the ast::Ast to read the symbol table diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 199d9bdd66..f76db272a3 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -9,8 +9,6 @@ #include "symtab/symbol_table.hpp" #include "utils/string_utils.hpp" -using namespace fmt::literals; - namespace nmodl { namespace codegen { @@ -69,9 +67,9 @@ void CodegenCudaVisitor::print_atomic_op(const std::string& lhs, } else if (op == "-") { function = "atomicSub"; } else { - throw std::runtime_error("CUDA backend error : {} not supported"_format(op)); + throw std::runtime_error(fmt::format("CUDA backend error : {} not supported", op)); } - printer->add_line("{}(&{}, {});"_format(function, lhs, rhs)); + printer->add_line(fmt::format("{}(&{}, {});", function, lhs, rhs)); } @@ -192,11 +190,11 @@ void CodegenCudaVisitor::print_wrapper_routine(std::string wrapper_function, Blo auto compute_function = compute_method_name(type); printer->add_newline(2); - printer->start_block("void {}({})"_format(wrapper_function, args)); + printer->start_block(fmt::format("void {}({})", wrapper_function, args)); printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int nthread = 256;"); printer->add_line("int nblock = (nodecount+nthread-1)/nthread;"); - printer->add_line("{}<<>>(nt, ml, type);"_format(compute_function)); + printer->add_line(fmt::format("{}<<>>(nt, ml, type);", compute_function)); printer->add_line("cudaDeviceSynchronize();"); printer->end_block(); printer->add_newline(); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index dca97d426c..6cdcfb30b2 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -17,8 +17,6 @@ #include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" -using namespace fmt::literals; - namespace nmodl { namespace codegen { @@ -51,7 +49,7 @@ void CodegenIspcVisitor::visit_function_call(const ast::FunctionCall& node) { return; } if (node.get_node_name() == "printf") { - logger->warn("Not emitted in ispc: {}"_format(to_nmodl(node))); + logger->warn(fmt::format("Not emitted in ispc: {}", to_nmodl(node))); return; } auto& fname = *node.get_name(); @@ -174,14 +172,15 @@ std::string CodegenIspcVisitor::compute_method_name(BlockType type) const { std::string CodegenIspcVisitor::net_receive_buffering_declaration() { auto params = ParamVector(); params.emplace_back(param_type_qualifier(), - "{}*"_format(instance_struct()), + fmt::format("{}*", instance_struct()), param_ptr_qualifier(), "inst"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); - return "export void {}({})"_format(method_name("ispc_net_buf_receive"), - get_parameter_str(params)); + return fmt::format("export void {}({})", + method_name("ispc_net_buf_receive"), + get_parameter_str(params)); } @@ -241,9 +240,9 @@ void CodegenIspcVisitor::print_atomic_op(const std::string& lhs, } else if (op == "-") { function = "atomic_subtract_local"; } else { - throw std::runtime_error("ISPC backend error : {} not supported"_format(op)); + throw std::runtime_error(fmt::format("ISPC backend error : {} not supported", op)); } - printer->add_line("{}(&{}, {});"_format(function, lhs, rhs)); + printer->add_line(fmt::format("{}(&{}, {});", function, lhs, rhs)); } @@ -252,8 +251,8 @@ void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_reduction() { auto d_op = operator_for_d(); if (info.point_process) { printer->add_line("uniform int node_id = node_index[id];"); - printer->add_line("vec_rhs[node_id] {} shadow_rhs[id];"_format(rhs_op)); - printer->add_line("vec_d[node_id] {} shadow_d[id];"_format(d_op)); + printer->add_line(fmt::format("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op)); + printer->add_line(fmt::format("vec_d[node_id] {} shadow_d[id];", d_op)); } } @@ -275,8 +274,9 @@ void CodegenIspcVisitor::print_shadow_reduction_block_end() { void CodegenIspcVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { printer->add_line( - "double* uniform shadow_rhs = nt->{};"_format(naming::NTHREAD_RHS_SHADOW)); - printer->add_line("double* uniform shadow_d = nt->{};"_format(naming::NTHREAD_D_SHADOW)); + fmt::format("double* uniform shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW)); + printer->add_line( + fmt::format("double* uniform shadow_d = nt->{};", naming::NTHREAD_D_SHADOW)); } } @@ -305,10 +305,10 @@ std::string CodegenIspcVisitor::global_var_struct_type_qualifier() { void CodegenIspcVisitor::print_global_var_struct_decl() { if (wrapper_codegen) { printer->start_block("extern \"C\""); - printer->add_line("{} {}_global;"_format(global_struct(), info.mod_suffix)); + printer->add_line(fmt::format("{} {}_global;", global_struct(), info.mod_suffix)); printer->end_block(2); } else { - printer->add_line("extern {} {}_global;"_format(global_struct(), info.mod_suffix)); + printer->add_line(fmt::format("extern {} {}_global;", global_struct(), info.mod_suffix)); } } @@ -344,7 +344,7 @@ CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( const std::string& arg_qualifier) { auto params = ParamVector(); params.emplace_back(param_type_qualifier(), - "{}*"_format(instance_struct()), + fmt::format("{}*", instance_struct()), param_ptr_qualifier(), "inst"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); @@ -373,24 +373,25 @@ void CodegenIspcVisitor::print_global_function_common_code(BlockType type, auto params = get_global_function_parms(ptr_type_qualifier()); print_global_method_annotation(); - printer->start_block("export void {}({})"_format(method, get_parameter_str(params))); + printer->start_block(fmt::format("export void {}({})", method, get_parameter_str(params))); print_kernel_data_present_annotation_block_begin(); printer->add_line("uniform int nodecount = ml->nodecount;"); printer->add_line("uniform int pnodecount = ml->_nodecount_padded;"); printer->add_line( - "{}int* {}node_index = ml->nodeindices;"_format(k_const(), ptr_type_qualifier())); - printer->add_line("double* {}data = ml->data;"_format(ptr_type_qualifier())); + fmt::format("{}int* {}node_index = ml->nodeindices;", k_const(), ptr_type_qualifier())); + printer->add_line(fmt::format("double* {}data = ml->data;", ptr_type_qualifier())); printer->add_line( - "{}double* {}voltage = nt->_actual_v;"_format(k_const(), ptr_type_qualifier())); + fmt::format("{}double* {}voltage = nt->_actual_v;", k_const(), ptr_type_qualifier())); if (type == BlockType::Equation) { - printer->add_line("double* {}vec_rhs = nt->_actual_rhs;"_format(ptr_type_qualifier())); - printer->add_line("double* {}vec_d = nt->_actual_d;"_format(ptr_type_qualifier())); + printer->add_line( + fmt::format("double* {}vec_rhs = nt->_actual_rhs;", ptr_type_qualifier())); + printer->add_line(fmt::format("double* {}vec_d = nt->_actual_d;", ptr_type_qualifier())); print_rhs_d_shadow_variables(); } - printer->add_line("Datum* {}indexes = ml->pdata;"_format(ptr_type_qualifier())); - printer->add_line("ThreadDatum* {}thread = ml->_thread;"_format(ptr_type_qualifier())); + printer->add_line(fmt::format("Datum* {}indexes = ml->pdata;", ptr_type_qualifier())); + printer->add_line(fmt::format("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier())); printer->add_newline(1); } @@ -453,15 +454,16 @@ void CodegenIspcVisitor::print_net_receive_buffering_wrapper() { return; } printer->add_newline(2); - printer->start_block("void {}(NrnThread* nt)"_format(method_name("net_buf_receive"))); + printer->start_block(fmt::format("void {}(NrnThread* nt)", method_name("net_buf_receive"))); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->start_block("if (ml == NULL)"); printer->add_line("return;"); printer->end_block(1); - printer->add_line( - "{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); + printer->add_line(fmt::format("{0}* {1}inst = ({0}*) ml->instance;", + instance_struct(), + ptr_type_qualifier())); - printer->add_line("{}(inst, nt, ml);"_format(method_name("ispc_net_buf_receive"))); + printer->add_line(fmt::format("{}(inst, nt, ml);", method_name("ispc_net_buf_receive"))); printer->end_block(1); } @@ -478,7 +480,7 @@ void CodegenIspcVisitor::print_nmodl_constants() { for (auto& it: info.factor_definitions) { const std::string name = it->get_node_name() == "PI" ? "ISPC_PI" : it->get_node_name(); const std::string value = format_double_string(it->get_value()->get_value()); - printer->add_line("static const uniform double {} = {};"_format(name, value)); + printer->add_line(fmt::format("static const uniform double {} = {};", name, value)); } } } @@ -496,10 +498,10 @@ void CodegenIspcVisitor::print_wrapper_routine(const std::string& wrapper_functi auto compute_function = compute_method_name(type); printer->add_newline(2); - printer->start_block("void {}({})"_format(function_name, args)); + printer->start_block(fmt::format("void {}({})", function_name, args)); printer->add_line("int nodecount = ml->nodecount;"); // clang-format off - printer->add_line("{0}* {1}inst = ({0}*) ml->instance;"_format(instance_struct(), ptr_type_qualifier())); + printer->add_line(fmt::format("{0}* {1}inst = ({0}*) ml->instance;", instance_struct(), ptr_type_qualifier())); // clang-format on if (type == BlockType::Initial) { @@ -513,7 +515,7 @@ void CodegenIspcVisitor::print_wrapper_routine(const std::string& wrapper_functi printer->add_newline(); } - printer->add_line("{}(inst, nt, ml, type);"_format(compute_function)); + printer->add_line(fmt::format("{}(inst, nt, ml, type);", compute_function)); printer->end_block(); printer->add_newline(); } @@ -524,28 +526,29 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { auto compute_function = compute_method_name(BlockType::Initial); if (!emit_fallback[BlockType::Initial]) { printer->add_line( - "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + fmt::format("extern \"C\" void {}({});", compute_function, get_parameter_str(params))); } if (nrn_cur_required() && !emit_fallback[BlockType::Equation]) { compute_function = compute_method_name(BlockType::Equation); printer->add_line( - "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + fmt::format("extern \"C\" void {}({});", compute_function, get_parameter_str(params))); } if (nrn_state_required() && !emit_fallback[BlockType::State]) { compute_function = compute_method_name(BlockType::State); printer->add_line( - "extern \"C\" void {}({});"_format(compute_function, get_parameter_str(params))); + fmt::format("extern \"C\" void {}({});", compute_function, get_parameter_str(params))); } if (net_receive_required()) { auto net_recv_params = ParamVector(); - net_recv_params.emplace_back("", "{}*"_format(instance_struct()), "", "inst"); + net_recv_params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); net_recv_params.emplace_back("", "NrnThread*", "", "nt"); net_recv_params.emplace_back("", "Memb_list*", "", "ml"); - printer->add_line("extern \"C\" void {}({});"_format(method_name("ispc_net_buf_receive"), - get_parameter_str(net_recv_params))); + printer->add_line(fmt::format("extern \"C\" void {}({});", + method_name("ispc_net_buf_receive"), + get_parameter_str(net_recv_params))); } } diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp index ded4187128..e9996f307b 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -8,8 +8,6 @@ #include "codegen/codegen_omp_visitor.hpp" -using namespace fmt::literals; - namespace nmodl { namespace codegen { @@ -26,7 +24,7 @@ void CodegenOmpVisitor::print_channel_iteration_task_begin(BlockType type) { } else { vars = "start, end, node_index, indexes, voltage, inst, thread, nt"; } - printer->add_line("#pragma omp task default(shared) firstprivate({})"_format(vars)); + printer->add_line(fmt::format("#pragma omp task default(shared) firstprivate({})", vars)); printer->add_line("{"); printer->increase_indent(); } diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index cc11494cae..f1c21593b1 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -25,7 +25,7 @@ template <> std::string format_double_string(const std::string& s_value) { double value = std::stod(s_value); if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { - return "{:.1f}"_format(value); + return fmt::format("{:.1f}", value); } return s_value; } @@ -60,7 +60,7 @@ template <> std::string format_float_string(const std::string& s_value) { float value = std::stof(s_value); if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { - return "{:.1f}"_format(value); + return fmt::format("{:.1f}", value); } return s_value; } diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index df39f122c7..8dc7c92828 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -20,7 +20,6 @@ namespace nmodl { namespace visitor { namespace test { -using namespace fmt::literals; using namespace ast; int CheckParentVisitor::check_ast(const Ast& node) { @@ -36,13 +35,16 @@ void CheckParentVisitor::check_parent(const ast::Ast& node) const { if (!parent) { if (is_root_with_null_parent && node.get_parent()) { const auto& parent_type = parent->get_node_type_name(); - throw std::runtime_error("root->parent: {} is set when it should be nullptr"_format(parent_type)); + throw std::runtime_error( + fmt::format("root->parent: {} is set when it should be nullptr", parent_type)); } } else { if (parent != node.get_parent()) { const std::string parent_type = (parent == nullptr) ? "nullptr" : parent->get_node_type_name(); const std::string node_parent_type = (node.get_parent() == nullptr) ? "nullptr" : node.get_parent()->get_node_type_name(); - throw std::runtime_error("parent: {} and child->parent: {} missmatch"_format(parent_type, node_parent_type)); + throw std::runtime_error(fmt::format("parent: {} and child->parent: {} missmatch", + parent_type, + node_parent_type)); } } } diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 31b9a58445..2aa0dc82be 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -22,7 +22,6 @@ * to scan arbitrary C code. */ -using namespace fmt::literals; using namespace nmodl; using Token = parser::CParser::token; @@ -43,7 +42,7 @@ void scan_c_code(std::istream& in) { int main(int argc, const char* argv[]) { - CLI::App app{"C-Lexer : Standalone Lexer for C Code({})"_format(Version::to_string())}; + CLI::App app{fmt::format("C-Lexer : Standalone Lexer for C Code({})", Version::to_string())}; std::vector c_files; std::vector c_codes; diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 2752f3e6f7..23f99fbc99 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -26,7 +26,6 @@ * it's value and location. */ -using namespace fmt::literals; using namespace nmodl; using parser::NmodlDriver; @@ -109,7 +108,8 @@ void tokenize(const std::string& mod_text) { int main(int argc, const char* argv[]) { - CLI::App app{"NMODL-Lexer : Standalone Lexer for NMODL Code({})"_format(Version::to_string())}; + CLI::App app{ + fmt::format("NMODL-Lexer : Standalone Lexer for NMODL Code({})", Version::to_string())}; std::vector mod_files; std::vector mod_texts; diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index e46b73b6ad..6f9b9ada89 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -20,12 +20,11 @@ * demonstrate use of UnitLexer and UnitDriver classes. */ -using namespace fmt::literals; using namespace nmodl; using Token = parser::UnitParser::token; int main(int argc, const char* argv[]) { - CLI::App app{"Unit-Lexer : Standalone Lexer for Units({})"_format(Version::to_string())}; + CLI::App app{fmt::format("Unit-Lexer : Standalone Lexer for Units({})", Version::to_string())}; std::vector files; app.add_option("file", files, "One or more units files to process") diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 4da9e23a67..5ed951d758 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -53,15 +53,14 @@ * \brief Main NMODL code generation program */ -using namespace fmt::literals; using namespace nmodl; using namespace codegen; using namespace visitor; using nmodl::parser::NmodlDriver; int main(int argc, const char* argv[]) { - CLI::App app{ - "NMODL : Source-to-Source Code Generation Framework [{}]"_format(Version::to_string())}; + CLI::App app{fmt::format("NMODL : Source-to-Source Code Generation Framework [{}]", + Version::to_string())}; /// list of mod files to process std::vector mod_files; @@ -179,80 +178,92 @@ int main(int argc, const char* argv[]) { app.add_option("--scratch", scratch_dir, "Directory for intermediate code output") ->capture_default_str() ->ignore_case(); - app.add_option("--units", units_dir, "Directory of units lib file")->capture_default_str()->ignore_case(); + app.add_option("--units", units_dir, "Directory of units lib file") + ->capture_default_str() + ->ignore_case(); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); - host_opt->add_flag("--c", c_backend, "C/C++ backend ({})"_format(c_backend))->ignore_case(); - host_opt->add_flag("--omp", omp_backend, "C/C++ backend with OpenMP ({})"_format(omp_backend)) + host_opt->add_flag("--c", c_backend, fmt::format("C/C++ backend ({})", c_backend)) ->ignore_case(); - host_opt->add_flag("--ispc", ispc_backend, "C/C++ backend with ISPC ({})"_format(ispc_backend)) + host_opt + ->add_flag("--omp", omp_backend, fmt::format("C/C++ backend with OpenMP ({})", omp_backend)) + ->ignore_case(); + host_opt + ->add_flag("--ispc", + ispc_backend, + fmt::format("C/C++ backend with ISPC ({})", ispc_backend)) ->ignore_case(); auto acc_opt = app.add_subcommand("acc", "Accelerator code backends")->ignore_case(); acc_opt - ->add_flag("--oacc", oacc_backend, "C/C++ backend with OpenACC ({})"_format(oacc_backend)) + ->add_flag("--oacc", + oacc_backend, + fmt::format("C/C++ backend with OpenACC ({})", oacc_backend)) ->ignore_case(); - acc_opt->add_flag("--cuda", cuda_backend, "C/C++ backend with CUDA ({})"_format(cuda_backend)) + acc_opt + ->add_flag("--cuda", + cuda_backend, + fmt::format("C/C++ backend with CUDA ({})", cuda_backend)) ->ignore_case(); // clang-format off auto sympy_opt = app.add_subcommand("sympy", "SymPy based analysis and optimizations")->ignore_case(); sympy_opt->add_flag("--analytic", sympy_analytic, - "Solve ODEs using SymPy analytic integration ({})"_format(sympy_analytic))->ignore_case(); + fmt::format("Solve ODEs using SymPy analytic integration ({})", sympy_analytic))->ignore_case(); sympy_opt->add_flag("--pade", sympy_pade, - "Pade approximation in SymPy analytic integration ({})"_format(sympy_pade))->ignore_case(); + fmt::format("Pade approximation in SymPy analytic integration ({})", sympy_pade))->ignore_case(); sympy_opt->add_flag("--cse", sympy_cse, - "CSE (Common Subexpression Elimination) in SymPy analytic integration ({})"_format(sympy_cse))->ignore_case(); + fmt::format("CSE (Common Subexpression Elimination) in SymPy analytic integration ({})", sympy_cse))->ignore_case(); sympy_opt->add_flag("--conductance", sympy_conductance, - "Add CONDUCTANCE keyword in BREAKPOINT ({})"_format(sympy_conductance))->ignore_case(); + fmt::format("Add CONDUCTANCE keyword in BREAKPOINT ({})", sympy_conductance))->ignore_case(); auto passes_opt = app.add_subcommand("passes", "Analyse/Optimization passes")->ignore_case(); passes_opt->add_flag("--inline", nmodl_inline, - "Perform inlining at NMODL level ({})"_format(nmodl_inline))->ignore_case(); + fmt::format("Perform inlining at NMODL level ({})", nmodl_inline))->ignore_case(); passes_opt->add_flag("--unroll", nmodl_unroll, - "Perform loop unroll at NMODL level ({})"_format(nmodl_unroll))->ignore_case(); + fmt::format("Perform loop unroll at NMODL level ({})", nmodl_unroll))->ignore_case(); passes_opt->add_flag("--const-folding", nmodl_const_folding, - "Perform constant folding at NMODL level ({})"_format(nmodl_const_folding))->ignore_case(); + fmt::format("Perform constant folding at NMODL level ({})", nmodl_const_folding))->ignore_case(); passes_opt->add_flag("--localize", nmodl_localize, - "Convert RANGE variables to LOCAL ({})"_format(nmodl_localize))->ignore_case(); + fmt::format("Convert RANGE variables to LOCAL ({})", nmodl_localize))->ignore_case(); passes_opt->add_flag("--global-to-range", nmodl_global_to_range, - "Convert GLOBAL variables to RANGE ({})"_format(nmodl_global_to_range))->ignore_case(); + fmt::format("Convert GLOBAL variables to RANGE ({})", nmodl_global_to_range))->ignore_case(); passes_opt->add_flag("--local-to-range", nmodl_local_to_range, - "Convert top level LOCAL variables to RANGE ({})"_format(nmodl_local_to_range))->ignore_case(); + fmt::format("Convert top level LOCAL variables to RANGE ({})", nmodl_local_to_range))->ignore_case(); passes_opt->add_flag("--localize-verbatim", localize_verbatim, - "Convert RANGE variables to LOCAL even if verbatim block exist ({})"_format(localize_verbatim))->ignore_case(); + fmt::format("Convert RANGE variables to LOCAL even if verbatim block exist ({})", localize_verbatim))->ignore_case(); passes_opt->add_flag("--local-rename", local_rename, - "Rename LOCAL variable if variable of same name exist in global scope ({})"_format(local_rename))->ignore_case(); + fmt::format("Rename LOCAL variable if variable of same name exist in global scope ({})", local_rename))->ignore_case(); passes_opt->add_flag("--verbatim-inline", verbatim_inline, - "Inline even if verbatim block exist ({})"_format(verbatim_inline))->ignore_case(); + fmt::format("Inline even if verbatim block exist ({})", verbatim_inline))->ignore_case(); passes_opt->add_flag("--verbatim-rename", verbatim_rename, - "Rename variables in verbatim block ({})"_format(verbatim_rename))->ignore_case(); + fmt::format("Rename variables in verbatim block ({})", verbatim_rename))->ignore_case(); passes_opt->add_flag("--json-ast", json_ast, - "Write AST to JSON file ({})"_format(json_ast))->ignore_case(); + fmt::format("Write AST to JSON file ({})", json_ast))->ignore_case(); passes_opt->add_flag("--nmodl-ast", nmodl_ast, - "Write AST to NMODL file ({})"_format(nmodl_ast))->ignore_case(); + fmt::format("Write AST to NMODL file ({})", nmodl_ast))->ignore_case(); passes_opt->add_flag("--json-perf", json_perfstat, - "Write performance statistics to JSON file ({})"_format(json_perfstat))->ignore_case(); + fmt::format("Write performance statistics to JSON file ({})", json_perfstat))->ignore_case(); passes_opt->add_flag("--show-symtab", show_symtab, - "Write symbol table to stdout ({})"_format(show_symtab))->ignore_case(); + fmt::format("Write symbol table to stdout ({})", show_symtab))->ignore_case(); auto codegen_opt = app.add_subcommand("codegen", "Code generation options")->ignore_case(); codegen_opt->add_option("--datatype", @@ -266,7 +277,7 @@ int main(int argc, const char* argv[]) { "Check compatibility and return without generating code"); codegen_opt->add_flag("--opt-ionvar-copy", optimize_ionvar_copies_codegen, - "Optimize copies of ion variables ({})"_format(optimize_ionvar_copies_codegen))->ignore_case(); + fmt::format("Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case(); // clang-format on @@ -304,7 +315,8 @@ int main(int argc, const char* argv[]) { /// create file path for nmodl file auto filepath = [scratch_dir, modfile](const std::string& suffix) { static int count = 0; - return "{}/{}.{}.{}.mod"_format(scratch_dir, modfile, std::to_string(count++), suffix); + return fmt::format( + "{}/{}.{}.{}.mod", scratch_dir, modfile, std::to_string(count++), suffix); }; /// driver object creates lexer and parser, just call parser method @@ -434,8 +446,9 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filename); if (nmodl_ast && kineticBlockVisitor.get_conserve_statement_count()) { logger->warn( - "{} presents non-standard CONSERVE statements in DERIVATIVE blocks. Use it only for debugging/developing"_format( - filename)); + fmt::format("{} presents non-standard CONSERVE statements in DERIVATIVE " + "blocks. Use it only for debugging/developing", + filename)); } } diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index b8c33ba751..ccfd8b2d2d 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -17,11 +17,10 @@ * usage of parser and driver class. */ -using namespace fmt::literals; using namespace nmodl; int main(int argc, const char* argv[]) { - CLI::App app{"C-Parser : Standalone Parser for C Code({})"_format(Version::to_string())}; + CLI::App app{fmt::format("C-Parser : Standalone Parser for C Code({})", Version::to_string())}; std::vector files; app.add_option("file", files, "One or more C files to process") diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 4a485ce9fa..6b4f66def4 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -18,11 +18,11 @@ * basic usage of parser and driver classes. */ -using namespace fmt::literals; using namespace nmodl; int main(int argc, const char* argv[]) { - CLI::App app{"NMODL-Parser : Standalone Parser for NMODL({})"_format(Version::to_string())}; + CLI::App app{ + fmt::format("NMODL-Parser : Standalone Parser for NMODL({})", Version::to_string())}; std::vector mod_files; std::vector mod_texts; diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index 39bf319463..3ba6f5a40f 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -20,12 +20,12 @@ * */ -using namespace fmt::literals; using namespace nmodl; int main(int argc, const char* argv[]) { - CLI::App app{"Unit-Parser : Standalone Parser for Units({})"_format(Version::to_string())}; + CLI::App app{ + fmt::format("Unit-Parser : Standalone Parser for Units({})", Version::to_string())}; std::vector units_files; units_files.push_back(NrnUnitsLib::get_path()); diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index bf99495e93..70d692c883 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -8,8 +8,6 @@ #include "symtab/symbol.hpp" #include "utils/logger.hpp" -using namespace fmt::literals; - namespace nmodl { namespace symtab { @@ -43,10 +41,10 @@ bool Symbol::is_variable() const noexcept { std::string Symbol::to_string() const { std::string s(name); if (properties != NmodlType::empty) { - s += " [Properties : {}]"_format(syminfo::to_string(properties)); + s += fmt::format(" [Properties : {}]", syminfo::to_string(properties)); } if (status != Status::empty) { - s += " [Status : {}]"_format(syminfo::to_string(status)); + s += fmt::format(" [Status : {}]", syminfo::to_string(status)); } return s; } diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 10a403d85a..80b574c9ac 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -18,7 +18,6 @@ namespace nmodl { namespace visitor { using symtab::syminfo::NmodlType; -using namespace fmt::literals; void KineticBlockVisitor::process_reac_var(const std::string& varname, int count) { // lookup index of state var @@ -238,7 +237,7 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement& node) additive_terms[var_index] += " + "; } // add to additive terms for this state var - additive_terms[var_index] += "({})"_format(expr); + additive_terms[var_index] += fmt::format("({})", expr); logger->debug("KineticBlockVisitor :: '<<' reaction statement: {}' += {}", varname, expr); @@ -387,17 +386,17 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { ode_rhs += " + "; } if (bflux[i].empty()) { - ode_rhs += "({}*({}))"_format(delta_nu, fflux[i]); + ode_rhs += fmt::format("({}*({}))", delta_nu, fflux[i]); } else if (fflux[i].empty()) { - ode_rhs += "({}*(-{}))"_format(delta_nu, bflux[i]); + ode_rhs += fmt::format("({}*(-{}))", delta_nu, bflux[i]); } else { - ode_rhs += "({}*({}-{}))"_format(delta_nu, fflux[i], bflux[i]); + ode_rhs += fmt::format("({}*({}-{}))", delta_nu, fflux[i], bflux[i]); } } } // divide by COMPARTMENT factor if present if (!compartment_factors[j].empty()) { - ode_rhs = "({})/({})"_format(ode_rhs, compartment_factors[j]); + ode_rhs = fmt::format("({})/({})", ode_rhs, compartment_factors[j]); } // if rhs of ODE is not empty, add to list of ODEs if (!ode_rhs.empty()) { @@ -407,7 +406,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { if (state_var_split.size() > 1) { index_str = "[" + state_var_split[1]; } - odes.push_back("{}'{} = {}"_format(var_str, index_str, ode_rhs)); + odes.push_back(fmt::format("{}'{} = {}", var_str, index_str, ode_rhs)); } } diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 3a3d8daf9a..aa1445ef46 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -33,7 +33,6 @@ using namespace nmodl; using namespace visitor; -using namespace fmt::literals; /** * \file @@ -55,14 +54,14 @@ void visit_program(const std::string& mod_file, ast::Program& ast) { logger->info("Running {}", visitor.description); visitor.v->visit_program(ast); - const std::string file = "{}.{}.mod"_format(mod_file, visitor.id); + const std::string file = fmt::format("{}.{}.mod", mod_file, visitor.id); NmodlPrintVisitor(file).visit_program(ast); logger->info("NMODL visitor generated {}", file); }; int main(int argc, const char* argv[]) { CLI::App app{ - "NMODL Visitor : Runs standalone visitor classes({})"_format(Version::to_string())}; + fmt::format("NMODL Visitor : Runs standalone visitor classes({})", Version::to_string())}; bool verbose = false; std::vector files; diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 9d8f859954..a2670d2e76 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -15,7 +15,6 @@ namespace nmodl { namespace visitor { -using namespace fmt::literals; using symtab::syminfo::NmodlType; std::shared_ptr SteadystateVisitor::create_steadystate_block( @@ -55,9 +54,9 @@ std::shared_ptr SteadystateVisitor::create_steadystate_blo std::string new_dt; if (steadystate_method == codegen::naming::SPARSE_METHOD) { - new_dt = "{:.16g}"_format(STEADYSTATE_SPARSE_DT); + new_dt = fmt::format("{:.16g}", STEADYSTATE_SPARSE_DT); } else if (steadystate_method == codegen::naming::DERIVIMPLICIT_METHOD) { - new_dt += "{:.16g}"_format(STEADYSTATE_DERIVIMPLICIT_DT); + new_dt += fmt::format("{:.16g}", STEADYSTATE_DERIVIMPLICIT_DT); } else { logger->warn("SteadystateVisitor :: solve method {} not supported for STEADYSTATE", steadystate_method); diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index e57be6de87..156508bc03 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -15,8 +15,6 @@ namespace nmodl { namespace visitor { -using namespace fmt::literals; - /** * \details SympyReplaceSolutionsVisitor tells us that a new equation appear and, depending where * it is located, it can determine if it is part of the main system of equations or is something @@ -135,7 +133,7 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no for (const auto ii: solution_statements.tags) { ss << to_nmodl(solution_statements.statements[ii]) << '\n'; } - throw std::runtime_error( + throw std::runtime_error(fmt::format( "Not all solutions were replaced! Sympy returned {} equations but I could not find " "a place " "for all of them. In particular, the following equations remain to be replaced " @@ -145,8 +143,9 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no "sympy " "returned more equations than what we expected\n - There is a bug in the GREEDY " "pass\n - some " - "solutions were replaced but not untagged"_format( - solution_statements.statements.size(), ss.str())); + "solutions were replaced but not untagged", + solution_statements.statements.size(), + ss.str())); } if (replaced_statements_range.first == -1) { From 712df0a6fe97823388cbebc4dc0c62bb4eb9599b Mon Sep 17 00:00:00 2001 From: bbpgithubaudit <86652185+bbpgithubaudit@users.noreply.github.com> Date: Fri, 29 Apr 2022 10:36:10 +0200 Subject: [PATCH 429/871] Updating copyright year 2022 (BlueBrain/nmodl#847) Co-authored-by: adietz NMODL Repo SHA: BlueBrain/nmodl@4445f0d14aac15eb47716d5ee9b2c52e27602501 --- README.md | 2 +- cmake/nmodl/CMakeLists.txt | 2 +- cmake/nmodl/RpathHelper.cmake | 2 +- nmodl/ode.py | 2 +- src/nmodl/ast/ast_common.hpp | 2 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_compatibility_visitor.cpp | 2 +- src/nmodl/codegen/codegen_compatibility_visitor.hpp | 2 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cuda_visitor.hpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 2 +- src/nmodl/codegen/codegen_info.cpp | 2 +- src/nmodl/codegen/codegen_info.hpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 2 +- src/nmodl/codegen/codegen_naming.hpp | 2 +- src/nmodl/codegen/codegen_omp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_omp_visitor.hpp | 2 +- src/nmodl/codegen/codegen_utils.cpp | 2 +- src/nmodl/codegen/codegen_utils.hpp | 2 +- src/nmodl/codegen/fast_math.hpp | 2 +- src/nmodl/codegen/fast_math.ispc | 2 +- src/nmodl/config/config.cpp.in | 2 +- src/nmodl/config/config.h | 2 +- src/nmodl/language/argument.py | 2 +- src/nmodl/language/code_generator.py | 2 +- src/nmodl/language/codegen.yaml | 2 +- src/nmodl/language/nmodl.yaml | 2 +- src/nmodl/language/node_info.py | 2 +- src/nmodl/language/nodes.py | 2 +- src/nmodl/language/parser.py | 2 +- src/nmodl/language/templates/ast/all.hpp | 2 +- src/nmodl/language/templates/ast/ast.cpp | 2 +- src/nmodl/language/templates/ast/ast.hpp | 2 +- src/nmodl/language/templates/ast/ast_decl.hpp | 2 +- src/nmodl/language/templates/ast/node.hpp | 2 +- src/nmodl/language/templates/pybind/pyast.cpp | 2 +- src/nmodl/language/templates/pybind/pyast.hpp | 2 +- src/nmodl/language/templates/pybind/pynode.cpp | 2 +- src/nmodl/language/templates/pybind/pysymtab.cpp | 2 +- src/nmodl/language/templates/pybind/pyvisitor.cpp | 2 +- src/nmodl/language/templates/pybind/pyvisitor.hpp | 2 +- src/nmodl/language/templates/visitors/ast_visitor.cpp | 2 +- src/nmodl/language/templates/visitors/ast_visitor.hpp | 2 +- src/nmodl/language/templates/visitors/checkparent_visitor.cpp | 2 +- src/nmodl/language/templates/visitors/checkparent_visitor.hpp | 2 +- src/nmodl/language/templates/visitors/json_visitor.cpp | 2 +- src/nmodl/language/templates/visitors/json_visitor.hpp | 2 +- src/nmodl/language/templates/visitors/lookup_visitor.cpp | 2 +- src/nmodl/language/templates/visitors/lookup_visitor.hpp | 2 +- src/nmodl/language/templates/visitors/nmodl_visitor.cpp | 2 +- src/nmodl/language/templates/visitors/nmodl_visitor.hpp | 2 +- src/nmodl/language/templates/visitors/symtab_visitor.cpp | 2 +- src/nmodl/language/templates/visitors/symtab_visitor.hpp | 2 +- src/nmodl/language/templates/visitors/visitor.hpp | 2 +- src/nmodl/language/utils.py | 2 +- src/nmodl/lexer/c11.ll | 2 +- src/nmodl/lexer/c11_lexer.hpp | 2 +- src/nmodl/lexer/diffeq.ll | 4 ++-- src/nmodl/lexer/diffeq_lexer.hpp | 2 +- src/nmodl/lexer/main_c.cpp | 2 +- src/nmodl/lexer/main_nmodl.cpp | 2 +- src/nmodl/lexer/main_units.cpp | 2 +- src/nmodl/lexer/modl.h | 2 +- src/nmodl/lexer/modtoken.cpp | 2 +- src/nmodl/lexer/modtoken.hpp | 2 +- src/nmodl/lexer/nmodl.ll | 4 ++-- src/nmodl/lexer/nmodl_lexer.hpp | 2 +- src/nmodl/lexer/nmodl_utils.cpp | 2 +- src/nmodl/lexer/nmodl_utils.hpp | 2 +- src/nmodl/lexer/token_mapping.cpp | 2 +- src/nmodl/lexer/token_mapping.hpp | 2 +- src/nmodl/lexer/unit_lexer.hpp | 2 +- src/nmodl/lexer/verbatim.l | 2 +- src/nmodl/main.cpp | 2 +- src/nmodl/nmodl.hpp | 2 +- src/nmodl/nmodl/LICENSE | 2 +- src/nmodl/parser/c11_driver.cpp | 2 +- src/nmodl/parser/c11_driver.hpp | 2 +- src/nmodl/parser/diffeq.yy | 4 ++-- src/nmodl/parser/diffeq_context.cpp | 2 +- src/nmodl/parser/diffeq_context.hpp | 2 +- src/nmodl/parser/diffeq_driver.cpp | 2 +- src/nmodl/parser/diffeq_driver.hpp | 2 +- src/nmodl/parser/diffeq_helper.hpp | 2 +- src/nmodl/parser/main_c.cpp | 2 +- src/nmodl/parser/main_nmodl.cpp | 2 +- src/nmodl/parser/main_units.cpp | 2 +- src/nmodl/parser/nmodl.yy | 4 ++-- src/nmodl/parser/nmodl_driver.cpp | 2 +- src/nmodl/parser/nmodl_driver.hpp | 2 +- src/nmodl/parser/unit_driver.cpp | 2 +- src/nmodl/parser/unit_driver.hpp | 2 +- src/nmodl/parser/verbatim.yy | 2 +- src/nmodl/parser/verbatim_driver.hpp | 2 +- src/nmodl/printer/code_printer.cpp | 2 +- src/nmodl/printer/code_printer.hpp | 2 +- src/nmodl/printer/decl.hpp | 2 +- src/nmodl/printer/json_printer.cpp | 2 +- src/nmodl/printer/json_printer.hpp | 2 +- src/nmodl/printer/nmodl_printer.cpp | 2 +- src/nmodl/printer/nmodl_printer.hpp | 2 +- src/nmodl/pybind/docstrings.hpp | 2 +- src/nmodl/pybind/pybind_utils.hpp | 2 +- src/nmodl/pybind/pyembed.cpp | 2 +- src/nmodl/pybind/pyembed.hpp | 2 +- src/nmodl/pybind/pynmodl.cpp | 2 +- src/nmodl/pybind/wrapper.cpp | 2 +- src/nmodl/solver/newton/newton.hpp | 2 +- src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu | 2 +- src/nmodl/solver/partial_piv_lu/partial_piv_lu.h | 2 +- src/nmodl/symtab/decl.hpp | 2 +- src/nmodl/symtab/symbol.cpp | 2 +- src/nmodl/symtab/symbol.hpp | 2 +- src/nmodl/symtab/symbol_properties.cpp | 2 +- src/nmodl/symtab/symbol_properties.hpp | 2 +- src/nmodl/symtab/symbol_table.cpp | 2 +- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/units/units.cpp | 2 +- src/nmodl/units/units.hpp | 2 +- src/nmodl/utils/common_utils.cpp | 2 +- src/nmodl/utils/common_utils.hpp | 2 +- src/nmodl/utils/file_library.cpp | 2 +- src/nmodl/utils/file_library.hpp | 2 +- src/nmodl/utils/logger.cpp | 2 +- src/nmodl/utils/logger.hpp | 2 +- src/nmodl/utils/perf_stat.cpp | 2 +- src/nmodl/utils/perf_stat.hpp | 2 +- src/nmodl/utils/string_utils.cpp | 2 +- src/nmodl/utils/string_utils.hpp | 2 +- src/nmodl/utils/table_data.cpp | 2 +- src/nmodl/utils/table_data.hpp | 2 +- src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp | 2 +- src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp | 2 +- src/nmodl/visitors/constant_folder_visitor.cpp | 2 +- src/nmodl/visitors/constant_folder_visitor.hpp | 2 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 2 +- src/nmodl/visitors/global_var_visitor.cpp | 2 +- src/nmodl/visitors/global_var_visitor.hpp | 2 +- src/nmodl/visitors/indexedname_visitor.cpp | 2 +- src/nmodl/visitors/indexedname_visitor.hpp | 2 +- src/nmodl/visitors/inline_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.hpp | 2 +- src/nmodl/visitors/ispc_rename_visitor.hpp | 2 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 2 +- src/nmodl/visitors/kinetic_block_visitor.hpp | 2 +- src/nmodl/visitors/local_to_assigned_visitor.cpp | 2 +- src/nmodl/visitors/local_to_assigned_visitor.hpp | 2 +- src/nmodl/visitors/local_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/local_var_rename_visitor.hpp | 2 +- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/localize_visitor.hpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.hpp | 2 +- src/nmodl/visitors/main.cpp | 2 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 2 +- src/nmodl/visitors/neuron_solve_visitor.hpp | 2 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 2 +- src/nmodl/visitors/perf_visitor.hpp | 2 +- src/nmodl/visitors/rename_visitor.cpp | 2 +- src/nmodl/visitors/rename_visitor.hpp | 2 +- src/nmodl/visitors/semantic_analysis_visitor.hpp | 2 +- src/nmodl/visitors/solve_block_visitor.cpp | 2 +- src/nmodl/visitors/solve_block_visitor.hpp | 2 +- src/nmodl/visitors/steadystate_visitor.cpp | 2 +- src/nmodl/visitors/steadystate_visitor.hpp | 2 +- src/nmodl/visitors/sympy_conductance_visitor.cpp | 2 +- src/nmodl/visitors/sympy_conductance_visitor.hpp | 2 +- src/nmodl/visitors/sympy_replace_solutions_visitor.cpp | 2 +- src/nmodl/visitors/sympy_replace_solutions_visitor.hpp | 2 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 2 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 2 +- src/nmodl/visitors/units_visitor.cpp | 2 +- src/nmodl/visitors/units_visitor.hpp | 2 +- src/nmodl/visitors/var_usage_visitor.cpp | 2 +- src/nmodl/visitors/var_usage_visitor.hpp | 2 +- src/nmodl/visitors/verbatim_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_var_rename_visitor.hpp | 2 +- src/nmodl/visitors/verbatim_visitor.cpp | 2 +- src/nmodl/visitors/verbatim_visitor.hpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- src/nmodl/visitors/visitor_utils.hpp | 2 +- test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp | 2 +- test/nmodl/transpiler/unit/codegen/codegen_helper.cpp | 2 +- test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp | 2 +- test/nmodl/transpiler/unit/codegen/codegen_utils.cpp | 2 +- test/nmodl/transpiler/unit/codegen/main.cpp | 2 +- test/nmodl/transpiler/unit/fast_math/fast_math.cpp | 2 +- test/nmodl/transpiler/unit/lexer/tokens.cpp | 2 +- test/nmodl/transpiler/unit/modtoken/modtoken.cpp | 2 +- test/nmodl/transpiler/unit/newton/newton.cpp | 2 +- test/nmodl/transpiler/unit/ode/test_ode.py | 2 +- test/nmodl/transpiler/unit/parser/parser.cpp | 2 +- test/nmodl/transpiler/unit/printer/printer.cpp | 2 +- test/nmodl/transpiler/unit/pybind/conftest.py | 2 +- test/nmodl/transpiler/unit/pybind/test_ast.py | 2 +- test/nmodl/transpiler/unit/pybind/test_symtab.py | 2 +- test/nmodl/transpiler/unit/pybind/test_visitor.py | 2 +- test/nmodl/transpiler/unit/symtab/symbol_table.cpp | 2 +- test/nmodl/transpiler/unit/units/lexer.cpp | 2 +- test/nmodl/transpiler/unit/units/parser.cpp | 2 +- test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp | 2 +- test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp | 2 +- test/nmodl/transpiler/unit/utils/test_utils.cpp | 2 +- test/nmodl/transpiler/unit/utils/test_utils.hpp | 2 +- test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp | 2 +- test/nmodl/transpiler/unit/visitor/constant_folder.cpp | 2 +- test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp | 2 +- test/nmodl/transpiler/unit/visitor/global_to_range.cpp | 2 +- test/nmodl/transpiler/unit/visitor/inline.cpp | 2 +- test/nmodl/transpiler/unit/visitor/ispc_rename.cpp | 2 +- test/nmodl/transpiler/unit/visitor/json.cpp | 2 +- test/nmodl/transpiler/unit/visitor/kinetic_block.cpp | 2 +- test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp | 2 +- test/nmodl/transpiler/unit/visitor/localize.cpp | 2 +- test/nmodl/transpiler/unit/visitor/lookup.cpp | 2 +- test/nmodl/transpiler/unit/visitor/loop_unroll.cpp | 2 +- test/nmodl/transpiler/unit/visitor/main.cpp | 2 +- test/nmodl/transpiler/unit/visitor/misc.cpp | 2 +- test/nmodl/transpiler/unit/visitor/neuron_solve.cpp | 2 +- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 2 +- test/nmodl/transpiler/unit/visitor/node_index.cpp | 2 +- test/nmodl/transpiler/unit/visitor/perf.cpp | 2 +- test/nmodl/transpiler/unit/visitor/rename.cpp | 2 +- test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp | 2 +- test/nmodl/transpiler/unit/visitor/solve_block.cpp | 2 +- test/nmodl/transpiler/unit/visitor/steadystate.cpp | 2 +- test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp | 2 +- test/nmodl/transpiler/unit/visitor/sympy_solver.cpp | 2 +- test/nmodl/transpiler/unit/visitor/units.cpp | 2 +- test/nmodl/transpiler/unit/visitor/var_usage.cpp | 2 +- test/nmodl/transpiler/unit/visitor/verbatim.cpp | 2 +- 239 files changed, 243 insertions(+), 243 deletions(-) diff --git a/README.md b/README.md index 970a78b50f..a1b29e4dc8 100644 --- a/README.md +++ b/README.md @@ -308,4 +308,4 @@ The benchmarks used to test the performance and parsing capabilities of NMODL Fr The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government's ETH Board of the Swiss Federal Institutes of Technology. In addition, the development was supported by funding from the National Institutes of Health (NIH) under the Grant Number R01NS11613 (Yale University) and the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 785907 (Human Brain Project SGA2). -Copyright © 2017-2021 Blue Brain Project/EPFL +Copyright © 2017-2022 Blue Brain Project/EPFL diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index f439530e78..065055bf52 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (C) 2018-2021 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. # See top-level LICENSE file for details. diff --git a/cmake/nmodl/RpathHelper.cmake b/cmake/nmodl/RpathHelper.cmake index 761441af6f..a008744a1a 100644 --- a/cmake/nmodl/RpathHelper.cmake +++ b/cmake/nmodl/RpathHelper.cmake @@ -1,5 +1,5 @@ # ============================================================================= -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. # See top-level LICENSE file for details. diff --git a/nmodl/ode.py b/nmodl/ode.py index cdbeca0458..372012777d 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index eb854bb5c5..f4b176e0cd 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 40907b96b0..f5b097ef2b 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 26f5ecba10..9446960189 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index b859e5199d..f5d1f279cb 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index fd051d50a1..c72ec88e46 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index a67e003d48..09a151aec8 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 7f46b66755..d24e60b977 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index f76db272a3..6daa06fefe 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp index d1ec9d8974..6b2c3fc202 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 236ff79a83..313a44c809 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 11008668b5..1abd7bdd42 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 8f6bd448f8..d2c433f649 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 805470dad6..f1830d0de2 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 6cdcfb30b2..87fb9dcd97 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index e14b05e000..d4dc677627 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 73c09df055..199d922534 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp index e9996f307b..2efa82f019 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ b/src/nmodl/codegen/codegen_omp_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp index 23a3976d2e..70f6c02f6b 100644 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ b/src/nmodl/codegen/codegen_omp_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index f1c21593b1..02f9d09b1f 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/codegen_utils.hpp b/src/nmodl/codegen/codegen_utils.hpp index 4178edb9a3..e88335619c 100644 --- a/src/nmodl/codegen/codegen_utils.hpp +++ b/src/nmodl/codegen/codegen_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index 6f3cd48cae..a6ba51fef4 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc index 0d0ba97617..ffc3a09555 100644 --- a/src/nmodl/codegen/fast_math.ispc +++ b/src/nmodl/codegen/fast_math.ispc @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index 4af2841d38..d962184f73 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index b6567e503e..513bb64041 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 6a57682fae..18b26ab779 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index df700b9098..aac339d691 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2021 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index a3156b4f3c..5511d14478 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -1,5 +1,5 @@ # ********************************************************************* -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index a71358f701..0653449cf7 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1,5 +1,5 @@ # ********************************************************************* -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index f4fb599347..7dbccc2e6c 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index bbbd983b01..88ad1bb000 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2021 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index db6c560e05..b7626e82ea 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/ast/all.hpp b/src/nmodl/language/templates/ast/all.hpp index c25c79f3dc..6033652490 100644 --- a/src/nmodl/language/templates/ast/all.hpp +++ b/src/nmodl/language/templates/ast/all.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index a72e3ff106..51a2139e81 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 4820773421..b1cc75f6b0 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index 184bfc3315..a6bdae69a1 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/ast/node.hpp b/src/nmodl/language/templates/ast/node.hpp index 8f138c84d4..277d03277a 100644 --- a/src/nmodl/language/templates/ast/node.hpp +++ b/src/nmodl/language/templates/ast/node.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 6fddda64c5..95601bed5c 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index b161f82fab..1b68a0a2da 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/pybind/pynode.cpp b/src/nmodl/language/templates/pybind/pynode.cpp index 18926f3b2d..a2069b30e9 100644 --- a/src/nmodl/language/templates/pybind/pynode.cpp +++ b/src/nmodl/language/templates/pybind/pynode.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index a2caf35782..104e132203 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index faadd8a2fe..aacbb14d7e 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/pybind/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp index 22a4fc4a3d..0d3832fa75 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.hpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index 4e4f019b60..bcac22a0ce 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index 324a0a0dd4..483fca2ad9 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index 8dc7c92828..32c205ead9 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp index d4f57fb9b9..a849d299ca 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index e96bcbf10c..11bd9cf556 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index 92a9a2a32f..9216156b3c 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index 89ea781ded..2bc3a877ab 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index fb7a6ebcbf..a2f959315a 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index a69c3b0b26..9c60bf8f87 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 854d76183f..e7d66b5333 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp index 23b42343d0..bf97de0e60 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.cpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index 22aba173cc..7d5b06fe6d 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 20c6037be6..0e5e0b233c 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index 87b90d72ab..e5ab9671e1 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index a894fd0fc1..eec87885c1 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -1,5 +1,5 @@ /********************************************************************************** - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 8f383c17ab..680d3f17d4 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index 041e827301..a5f19d0314 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -1,6 +1,6 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * Copyright (C) 2018-2019 Michael Hines + * Copyright (C) 2018-2022 Blue Brain Project + * Copyright (C) 2018-2022 Michael Hines * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp index 2d3d76b794..2d78e73948 100644 --- a/src/nmodl/lexer/diffeq_lexer.hpp +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 2aa0dc82be..ad5a57ffe3 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 23f99fbc99..aab31e23db 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index 6f9b9ada89..976d125edb 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index bf3df86f18..6f3e514ca1 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 8be6a38d6d..2b4506356e 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index c01bd8dc03..ad052edf44 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index a8284a510b..dedef56bdb 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -1,6 +1,6 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * Copyright (C) 2018-2019 Michael Hines + * Copyright (C) 2018-2022 Blue Brain Project + * Copyright (C) 2018-2022 Michael Hines * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 56759c2cf3..1ec5052a40 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index ac645f6956..2866c312be 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index fc5699439c..ae1ae2b01f 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index fdcfaccf4b..682d5f7f6c 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 364467818c..6f1fb3255f 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/unit_lexer.hpp b/src/nmodl/lexer/unit_lexer.hpp index 234874f394..eab16d53c2 100644 --- a/src/nmodl/lexer/unit_lexer.hpp +++ b/src/nmodl/lexer/unit_lexer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 7e2b414d68..315877a37b 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 5ed951d758..a10818c6c4 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/nmodl.hpp b/src/nmodl/nmodl.hpp index 82abf643b6..de18ef1b71 100644 --- a/src/nmodl/nmodl.hpp +++ b/src/nmodl/nmodl.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/nmodl/LICENSE b/src/nmodl/nmodl/LICENSE index d39a0878fa..8cff9eceff 100644 --- a/src/nmodl/nmodl/LICENSE +++ b/src/nmodl/nmodl/LICENSE @@ -1,7 +1,7 @@ ********************************************************************************* * NMODL - NEURON Modeling Language Code Generation Framework * -* Copyright (c) 2019, Blue Brain Project, EPFL. +* Copyright (c) 2019-2022, Blue Brain Project, EPFL. * * NMODL is licensed under the LGPL, unless noted otherwise, e.g., for external * dependencies. See file LGPL.txt for the full license. Examples and external diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index e9a1a82e8c..08f74ae980 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 9de478e54e..30f24238cc 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index fd8b88c1c5..0291047ed2 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -1,6 +1,6 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * Copyright (C) 2018-2019 Michael Hines + * Copyright (C) 2018-2022 Blue Brain Project + * Copyright (C) 2018-2022 Michael Hines * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 505a50443a..7aa4478795 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 56e45942e3..caec0a5ad5 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 7ffb0332d1..6c2d80abc6 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index dc7c89dfc6..31bb8cf13b 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index feffb904b2..0ae5a2f31e 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index ccfd8b2d2d..6dd2154c36 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 6b4f66def4..87b1ac7c4c 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index 3ba6f5a40f..47052223ff 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index c11364ee9c..5867f396d2 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1,6 +1,6 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project - * Copyright (C) 2018-2019 Michael Hines + * Copyright (C) 2018-2022 Blue Brain Project + * Copyright (C) 2018-2022 Michael Hines * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 4bf28cc316..8972471404 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 5e33d5594c..869b4a177e 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/unit_driver.cpp b/src/nmodl/parser/unit_driver.cpp index 459925d68f..768b104d40 100644 --- a/src/nmodl/parser/unit_driver.cpp +++ b/src/nmodl/parser/unit_driver.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/unit_driver.hpp b/src/nmodl/parser/unit_driver.hpp index 4c6703b21d..74f4a63166 100644 --- a/src/nmodl/parser/unit_driver.hpp +++ b/src/nmodl/parser/unit_driver.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index bf1df1d81e..fdfdcd0778 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/parser/verbatim_driver.hpp b/src/nmodl/parser/verbatim_driver.hpp index ea8c3e050e..3a5013533d 100644 --- a/src/nmodl/parser/verbatim_driver.hpp +++ b/src/nmodl/parser/verbatim_driver.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index a754ccff30..2042df94f8 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index b0d6d5d1fd..be5e7a9d3b 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/decl.hpp b/src/nmodl/printer/decl.hpp index 1c7b8f45aa..f83a87d150 100644 --- a/src/nmodl/printer/decl.hpp +++ b/src/nmodl/printer/decl.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 82d857f70d..d3abfe5c7c 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index ab237cd5ab..dab26faf1a 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index b70eb51fba..356f9c5e4b 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp index 1cbfc5c7d1..d333b9bd2a 100644 --- a/src/nmodl/printer/nmodl_printer.hpp +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/pybind/docstrings.hpp b/src/nmodl/pybind/docstrings.hpp index 1ff5c982f5..c2b554df0f 100644 --- a/src/nmodl/pybind/docstrings.hpp +++ b/src/nmodl/pybind/docstrings.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/pybind/pybind_utils.hpp b/src/nmodl/pybind/pybind_utils.hpp index 855ebe9cc2..8d02060273 100644 --- a/src/nmodl/pybind/pybind_utils.hpp +++ b/src/nmodl/pybind/pybind_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index 921912e322..b882cc4ad4 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp index cfd78e73f9..c4fee5dc95 100644 --- a/src/nmodl/pybind/pyembed.hpp +++ b/src/nmodl/pybind/pyembed.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 20a2df7b19..ebc72f2e18 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 3a97410a70..c9cc9060e8 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index ec90fc441a..76168caa99 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu index ad9cd05e68..6faf0e6b64 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h index 84cd26904c..e70d4b4432 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/decl.hpp b/src/nmodl/symtab/decl.hpp index 7e5a41e251..2894baea55 100644 --- a/src/nmodl/symtab/decl.hpp +++ b/src/nmodl/symtab/decl.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 70d692c883..edd9688c5a 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 395ed38159..e6c8dddfb1 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 695d4decf3..a02ea88cee 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 43422030e8..2824f5b43e 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 45db2e0632..8a041c6cc5 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 543cbd48bc..efb5311c6b 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 0f1d880948..c50b59975b 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 233604a4b6..3b96b04c26 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 5c3010990c..94339f7e08 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index b418e85fb9..650cdab0cb 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/file_library.cpp b/src/nmodl/utils/file_library.cpp index 2a2e4d4827..f058b85119 100644 --- a/src/nmodl/utils/file_library.cpp +++ b/src/nmodl/utils/file_library.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/file_library.hpp b/src/nmodl/utils/file_library.hpp index 51b78e8cf7..17fbeb3fa1 100644 --- a/src/nmodl/utils/file_library.hpp +++ b/src/nmodl/utils/file_library.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 63e08f88cd..e8fadae56e 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index 73513d6a8f..b12e2f4d5c 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index 201a3c85e1..df068cbeba 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index 9ba07dbd3b..b17d791c90 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/string_utils.cpp b/src/nmodl/utils/string_utils.cpp index 32406985ba..072c81a28a 100644 --- a/src/nmodl/utils/string_utils.cpp +++ b/src/nmodl/utils/string_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 5255a0e0e8..72358d1a05 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 7bf9f4541a..478ee25f5f 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index feb6376f9f..c689474b16 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp index 4eee6692dd..7d0db8ca49 100644 --- a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp index b6ee74aa75..e8aa847a77 100644 --- a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index 6b2f21a945..d2d7f0f9ce 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index e58ab8d04b..77bcf44d9e 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 44837be5b7..ae6ced6791 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 1bd9bf8d03..9c307fcf0e 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/global_var_visitor.cpp b/src/nmodl/visitors/global_var_visitor.cpp index 3488fdd6d0..979bcf0947 100644 --- a/src/nmodl/visitors/global_var_visitor.cpp +++ b/src/nmodl/visitors/global_var_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/global_var_visitor.hpp b/src/nmodl/visitors/global_var_visitor.hpp index c16b99decd..354702830a 100644 --- a/src/nmodl/visitors/global_var_visitor.hpp +++ b/src/nmodl/visitors/global_var_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/indexedname_visitor.cpp b/src/nmodl/visitors/indexedname_visitor.cpp index 8878e49bc8..1ad216b85c 100644 --- a/src/nmodl/visitors/indexedname_visitor.cpp +++ b/src/nmodl/visitors/indexedname_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/indexedname_visitor.hpp b/src/nmodl/visitors/indexedname_visitor.hpp index 8efec7bfcd..e5eea8ad32 100644 --- a/src/nmodl/visitors/indexedname_visitor.hpp +++ b/src/nmodl/visitors/indexedname_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 7d82ce57bc..3b6230a149 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 5a215acc31..a6156632f5 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/ispc_rename_visitor.hpp b/src/nmodl/visitors/ispc_rename_visitor.hpp index 4ac4973da1..929b1eac4f 100644 --- a/src/nmodl/visitors/ispc_rename_visitor.hpp +++ b/src/nmodl/visitors/ispc_rename_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 80b574c9ac..23f561bff4 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index f744e640c1..a13bd1ddd3 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/local_to_assigned_visitor.cpp b/src/nmodl/visitors/local_to_assigned_visitor.cpp index b52b6c3ce8..4596c3a3d9 100644 --- a/src/nmodl/visitors/local_to_assigned_visitor.cpp +++ b/src/nmodl/visitors/local_to_assigned_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/local_to_assigned_visitor.hpp b/src/nmodl/visitors/local_to_assigned_visitor.hpp index cad8e07fe1..3ffc76f4e5 100644 --- a/src/nmodl/visitors/local_to_assigned_visitor.hpp +++ b/src/nmodl/visitors/local_to_assigned_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index b81bc7d571..04778af171 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index fb92f4ac7e..e6c2a38c4a 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 197d5c9784..d2e191238d 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 2fbb86005f..e8af4878ab 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 7dd6f89a4e..b29a42bdf7 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index 9ee87792d1..681e2ea28f 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index aa1445ef46..8ca9795b68 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index eab791ab8f..f10b0d5666 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index f2603da17b..1d8410af0b 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index 8ec90eb6e1..4faa2ca25b 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 202c302df7..bb5ce0dafe 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index e55347f306..0757532020 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index d48e48d982..0771aa7abc 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index ce14105bae..b40276ab41 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 729b8f3842..54574f584b 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2021 Blue Brain Project + * Copyright (C) 2021-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 370ed713e8..0de5a1ecaf 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp index 4a677b398b..8ef7a46200 100644 --- a/src/nmodl/visitors/solve_block_visitor.hpp +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index a2670d2e76..99b658c775 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/steadystate_visitor.hpp b/src/nmodl/visitors/steadystate_visitor.hpp index 52d2616ec2..b3276045ef 100644 --- a/src/nmodl/visitors/steadystate_visitor.hpp +++ b/src/nmodl/visitors/steadystate_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 2b4b6250a7..53039b243f 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index fb71adbf29..8c8de7d67e 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index 156508bc03..9194be64bf 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp index fc4c821e26..b810f0d1b4 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 58683d601a..8ba318636b 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2021 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index c45b9ef193..f3a3099d0d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 35e7aa831a..11e0596472 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 5373230317..9d149197ed 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/units_visitor.hpp b/src/nmodl/visitors/units_visitor.hpp index ea8157f667..7d03e2e672 100644 --- a/src/nmodl/visitors/units_visitor.hpp +++ b/src/nmodl/visitors/units_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 684628db0d..5e6aaedb2e 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index 23bd0efc04..ff552ae20f 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 1282f56486..2a44c094e2 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index e12eac5409..f918be61a4 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index c038ca3a4c..25df00c480 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index 70d91856b5..eb33c5f46c 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index de8663e07b..22de3cd57b 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 8ef8419351..850d00e176 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index 310525c961..00869c8f04 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2019-2021 Blue Brain Project + * Copyright (C) 2019-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 838a661297..b4ac5d650b 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2019-2020 Blue Brain Project + * Copyright (C) 2019-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp index 41905c0567..ea456a999d 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2019-2020 Blue Brain Project + * Copyright (C) 2019-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index ef1901b6f7..17f2d0f4b7 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2019-2020 Blue Brain Project + * Copyright (C) 2019-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp index 39b207cb3f..9e1bc03860 100644 --- a/test/nmodl/transpiler/unit/codegen/main.cpp +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp index 668675261b..e156074d4d 100644 --- a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp +++ b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp index c306941b60..7a41d6781a 100644 --- a/test/nmodl/transpiler/unit/lexer/tokens.cpp +++ b/test/nmodl/transpiler/unit/lexer/tokens.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index f166a1e3be..61a164e1b3 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 1817427d06..626d9906e2 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index b90ce186a7..9ef2f45a1f 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 8439528520..d8444dceda 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/printer/printer.cpp b/test/nmodl/transpiler/unit/printer/printer.cpp index a4e996baba..383e0c1a7e 100644 --- a/test/nmodl/transpiler/unit/printer/printer.cpp +++ b/test/nmodl/transpiler/unit/printer/printer.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/pybind/conftest.py b/test/nmodl/transpiler/unit/pybind/conftest.py index 9aee8aa936..3ace8bf6d3 100644 --- a/test/nmodl/transpiler/unit/pybind/conftest.py +++ b/test/nmodl/transpiler/unit/pybind/conftest.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/pybind/test_ast.py b/test/nmodl/transpiler/unit/pybind/test_ast.py index b9348b7298..6f657fff88 100644 --- a/test/nmodl/transpiler/unit/pybind/test_ast.py +++ b/test/nmodl/transpiler/unit/pybind/test_ast.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/pybind/test_symtab.py b/test/nmodl/transpiler/unit/pybind/test_symtab.py index 836ed76d69..2493ed4b90 100644 --- a/test/nmodl/transpiler/unit/pybind/test_symtab.py +++ b/test/nmodl/transpiler/unit/pybind/test_symtab.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index e40072450e..72c49bf430 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -1,5 +1,5 @@ # *********************************************************************** -# Copyright (C) 2018-2019 Blue Brain Project +# Copyright (C) 2018-2022 Blue Brain Project # # This file is part of NMODL distributed under the terms of the GNU # Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index 52ec261fa0..2e87604c68 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/units/lexer.cpp b/test/nmodl/transpiler/unit/units/lexer.cpp index edc4a22b9c..bfc5386964 100644 --- a/test/nmodl/transpiler/unit/units/lexer.cpp +++ b/test/nmodl/transpiler/unit/units/lexer.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index aef788e695..7db2635247 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 4d2e27dac5..89b013048e 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp index 148960a3bc..24a4c5cc10 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index 6dcadb303e..3dd8cf27fe 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/utils/test_utils.hpp b/test/nmodl/transpiler/unit/utils/test_utils.hpp index f35a72589d..75f3828b43 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.hpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.hpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp index 05c9e2d24a..95b6887979 100644 --- a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp +++ b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp index 4f6c26d5aa..db0a232c3a 100644 --- a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 2667a5ced0..9471cc5fdf 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 6b77c47a9d..3beb4a6d3c 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index de578edada..15972cf809 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp index 33e0e159d3..29f521ab4a 100644 --- a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2020 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/json.cpp b/test/nmodl/transpiler/unit/visitor/json.cpp index 1f205d87b7..395a029f40 100644 --- a/test/nmodl/transpiler/unit/visitor/json.cpp +++ b/test/nmodl/transpiler/unit/visitor/json.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 8d164ec5db..eafd25c2b9 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index 3c3c8c77ed..f1c637a5e0 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp index db2b89b909..31821318d6 100644 --- a/test/nmodl/transpiler/unit/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index e80820842c..26e12d6452 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index 20a5d359cb..107ec820fa 100644 --- a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp index 39b207cb3f..9e1bc03860 100644 --- a/test/nmodl/transpiler/unit/visitor/main.cpp +++ b/test/nmodl/transpiler/unit/visitor/main.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp index 36d4ad5856..74446baf4e 100644 --- a/test/nmodl/transpiler/unit/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 58f819288d..524b630538 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 2215ca3cd3..1e98d68d19 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/node_index.cpp b/test/nmodl/transpiler/unit/visitor/node_index.cpp index 6112a0404c..0d6ad82fcc 100644 --- a/test/nmodl/transpiler/unit/visitor/node_index.cpp +++ b/test/nmodl/transpiler/unit/visitor/node_index.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/perf.cpp b/test/nmodl/transpiler/unit/visitor/perf.cpp index 0f704ae1d8..9dc70b4b98 100644 --- a/test/nmodl/transpiler/unit/visitor/perf.cpp +++ b/test/nmodl/transpiler/unit/visitor/perf.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp index 352038ad0f..0208076ad3 100644 --- a/test/nmodl/transpiler/unit/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index ca2f22044f..7644bb6cf1 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index 862b1c4fbf..2602c08e5c 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index 9eaec01221..ef833e6f7b 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 19f19ca2d6..20da87af81 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 5bdfd25478..94ce8e10e4 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 048dbdabcd..8a1b19f1ce 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/var_usage.cpp b/test/nmodl/transpiler/unit/visitor/var_usage.cpp index 69e1a59348..0ddb39c854 100644 --- a/test/nmodl/transpiler/unit/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/unit/visitor/var_usage.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. diff --git a/test/nmodl/transpiler/unit/visitor/verbatim.cpp b/test/nmodl/transpiler/unit/visitor/verbatim.cpp index 6389a48828..2ebc01ac8a 100644 --- a/test/nmodl/transpiler/unit/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/unit/visitor/verbatim.cpp @@ -1,5 +1,5 @@ /************************************************************************* - * Copyright (C) 2018-2019 Blue Brain Project + * Copyright (C) 2018-2022 Blue Brain Project * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. From fee21debd407bb1676f4b842c24f6ac01e9420fe Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 12 May 2022 18:19:53 +0200 Subject: [PATCH 430/871] Code generation changes for "inline" scopmath solvers. (BlueBrain/nmodl#859) * Use named structs instead of functions, pass instances to solvers. * Goes with BlueBrain/CoreNeuronBlueBrain/nmodl#809. * Add fmt_line, fmt_start_block and restart_block methods to CodePrinter. * gitlab-ci: support CVF_BRANCH variable. NMODL Repo SHA: BlueBrain/nmodl@a237926e6c8bbe7b6cdb802eec60ba80f2413ea4 --- src/nmodl/codegen/codegen_c_visitor.cpp | 128 ++++++++++++------------ src/nmodl/printer/code_printer.cpp | 8 ++ src/nmodl/printer/code_printer.hpp | 20 +++- 3 files changed, 92 insertions(+), 64 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index f5d1f279cb..362795374d 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2459,7 +2459,6 @@ void CodegenCVisitor::print_standard_includes() { void CodegenCVisitor::print_coreneuron_includes() { printer->add_newline(); - printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); @@ -2469,8 +2468,7 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include \"_kinderiv.h\""); + printer->add_line("#include "); if (info.eigen_newton_solver_exist) { printer->add_line("#include "); } @@ -4260,42 +4258,30 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->add_newline(2); - // clang-format off - printer->start_block(fmt::format("int {}_{}({})", block_name, suffix, ext_params)); - auto instance = fmt::format("{0}* inst = ({0}*)get_memb_list(nt)->instance;", instance_struct()); - auto slist1 = fmt::format("int* slist{} = {};", list_num, get_variable_name(fmt::format("slist{}", list_num))); - auto slist2 = fmt::format("int* slist{} = {};", list_num+1, get_variable_name(fmt::format("slist{}", list_num+1))); - auto dlist1 = fmt::format("int* dlist{} = {};", list_num, get_variable_name(fmt::format("dlist{}", list_num))); - auto dlist2 = fmt::format("double* dlist{} = (double*) thread[dith{}()].pval + ({}*pnodecount);", list_num + 1, list_num, info.primes_size); - - printer->add_line(instance); - printer->add_line(fmt::format("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num)); - printer->add_line(slist1); - printer->add_line(slist2); - printer->add_line(dlist2); - printer->add_line(fmt::format("for (int i=0; i<{}; i++) {}", info.num_primes, "{")); - printer->add_line(fmt::format(" savstate{}[i{}] = data[slist{}[i]{}];", list_num, stride, list_num, stride)); - printer->add_line("}"); - - auto argument = fmt::format("{}, slist{}, _derivimplicit_{}_{}, dlist{}, {}", primes_size, list_num+1, block_name, suffix, list_num + 1, ext_args); - printer->add_line(fmt::format("int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {});", list_num, argument)); - printer->add_line("return reset;"); - printer->end_block(3); - - /** - * \todo To be backward compatible with mod2c we have to generate below - * comment marker in the generated cpp file for kinderiv.py to - * process it and generate correct _kinderiv.h - */ - printer->add_line(fmt::format("/* _derivimplicit_ {} _{} */", block_name, info.mod_suffix)); - printer->add_newline(1); - - printer->start_block(fmt::format("int _newton_{}_{}({}) ", block_name, info.mod_suffix, external_method_parameters())); + printer->start_block("namespace"); + printer->fmt_start_block("struct _newton_{}_{}", block_name, info.mod_suffix); + printer->fmt_start_block("int operator()({}) const", external_method_parameters()); + auto const instance = fmt::format("{0}* inst = ({0}*)get_memb_list(nt)->instance;", + instance_struct()); + auto const slist1 = fmt::format("int* slist{} = {};", + list_num, + get_variable_name(fmt::format("slist{}", list_num))); + auto const slist2 = fmt::format("int* slist{} = {};", + list_num + 1, + get_variable_name(fmt::format("slist{}", list_num + 1))); + auto const dlist1 = fmt::format("int* dlist{} = {};", + list_num, + get_variable_name(fmt::format("dlist{}", list_num))); + auto const dlist2 = + fmt::format("double* dlist{} = (double*) thread[dith{}()].pval + ({}*pnodecount);", + list_num + 1, + list_num, + info.primes_size); printer->add_line(instance); if (ion_variable_struct_required()) { print_ion_variable(); } - printer->add_line(fmt::format("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num)); + printer->fmt_line("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num); printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); @@ -4303,17 +4289,48 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { print_statement_block(*block->get_statement_block(), false, false); codegen = false; printer->add_line("int counter = -1;"); - printer->add_line(fmt::format("for (int i=0; i<{}; i++) {}", info.num_primes, "{")); - printer->add_line(fmt::format(" if (*deriv{}_advance(thread)) {}", list_num, "{")); - printer->add_line(fmt::format(" dlist{0}[(++counter){1}] = data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;", list_num + 1, stride, list_num)); - printer->add_line(" }"); - printer->add_line(" else {"); - printer->add_line(fmt::format(" dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];", list_num + 1, stride, list_num)); - printer->add_line(" }"); - printer->add_line("}"); + printer->fmt_start_block("for (int i=0; i<{}; i++)", info.num_primes); + printer->fmt_start_block("if (*deriv{}_advance(thread))", list_num); + printer->fmt_line( + "dlist{0}[(++counter){1}] = " + "data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;", + list_num + 1, + stride, + list_num); + printer->restart_block("else"); + printer->fmt_line("dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];", + list_num + 1, + stride, + list_num); + printer->end_block(1); + printer->end_block(1); printer->add_line("return 0;"); - printer->end_block(); - // clang-format on + printer->end_block(1); // operator() + printer->end_block(); // struct + printer->add_text(";"); + printer->add_newline(); + printer->end_block(2); // namespace + printer->fmt_start_block("int {}_{}({})", block_name, suffix, ext_params); + printer->add_line(instance); + printer->fmt_line("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num); + printer->add_line(slist1); + printer->add_line(slist2); + printer->add_line(dlist2); + printer->fmt_start_block("for (int i=0; i<{}; i++)", info.num_primes); + printer->fmt_line("savstate{}[i{}] = data[slist{}[i]{}];", list_num, stride, list_num, stride); + printer->end_block(1); + printer->fmt_line( + "int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {}, " + "slist{}, _newton_{}_{}{{}}, dlist{}, {});", + list_num, + primes_size, + list_num + 1, + block_name, + suffix, + list_num + 1, + ext_args); + printer->add_line("return reset;"); + printer->end_block(3); } @@ -4326,23 +4343,10 @@ void CodegenCVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallb if (!codegen) { return; } - auto thread_args = external_method_arguments(); - auto num_primes = info.num_primes; - auto suffix = info.mod_suffix; - int num = info.derivimplicit_list_num; - auto slist = get_variable_name(fmt::format("slist{}", num)); - auto dlist = get_variable_name(fmt::format("dlist{}", num)); - auto block_name = node.get_node_to_solve()->get_node_name(); - - auto args = fmt::format("{}, {}, {}, _derivimplicit_{}_{}, {}", - num_primes, - slist, - dlist, - block_name, - suffix, - thread_args); - auto statement = fmt::format("derivimplicit_thread({});", args); - printer->add_line(statement); + printer->fmt_line("{}_{}({});", + node.get_node_to_solve()->get_node_name(), + info.mod_suffix, + external_method_arguments()); } void CodegenCVisitor::visit_solution_expression(const SolutionExpression& node) { diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 2042df94f8..f7f6eb4580 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -40,6 +40,14 @@ void CodePrinter::start_block(std::string&& text) { indent_level++; } +void CodePrinter::restart_block(std::string const& expression) { + --indent_level; + add_indent(); + *result << "} " << expression << " {"; + add_newline(); + ++indent_level; +} + void CodePrinter::add_indent() { *result << std::string(indent_level * NUM_SPACES, ' '); } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index be5e7a9d3b..62810b3828 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -14,6 +14,7 @@ * \file * \brief \copybrief nmodl::printer::CodePrinter */ +#include // want fmt but #include #include @@ -61,15 +62,30 @@ class CodePrinter { /// print whitespaces for indentation void add_indent(); - /// start a block scope (i.e. start with "{") + /// start a block scope without indentation (i.e. "{\n") void start_block(); - void start_block(std::string&&); + /// start a block scope with an expression (i.e. "[indent][expression] {\n") + void start_block(std::string&& expression); + + /// end a block and immediately start a new one (i.e. "[indent-1]} [expression] {\n") + void restart_block(std::string const& expression); void add_text(const std::string&); void add_line(const std::string&, int num_new_lines = 1); + /// fmt_line(x, y, z) is just shorthand for add_line(fmt::format(x, y, z)) + template + void fmt_line(Args&&... args) { + add_line(fmt::format(std::forward(args)...)); + } + + template + void fmt_start_block(Args&&... args) { + start_block(fmt::format(std::forward(args)...)); + } + void add_multi_line(const std::string&); void add_newline(int n = 1); From ee9d146b98767255a12d72fd6b33a33a1efcf503 Mon Sep 17 00:00:00 2001 From: Alessandro Cattabiani Date: Wed, 18 May 2022 10:24:21 +0200 Subject: [PATCH 431/871] fixup! CI errors with sympy 1.9 and 1.10 (BlueBrain/nmodl#870) * fixup! CI errors with sympy 1.9 and 1.10 - bin is a function in python: renamed variable in test to not get this error - sympy now can solve z'=a/z+b/z/z. It is getting hard to find simple equations that it cannot solve. Warning: this is a downgrade of the testing capabilities. I could not find an ode with simple functions (no sin, exp, log etc. Only simple multiplications and additions) that cannot be solved by sympy. Best case the test hangs waiting for sympy to solve a very long equation. For this reason, with this PR, the fact that the code should return the equation untouched if it cannot be processed is untested * fixup! clang-format * remove limit for sympy 1.9 Co-authored-by: Alessandro Cattabiani NMODL Repo SHA: BlueBrain/nmodl@21a32bc263f01ff4fc0eb3c9dcc4fb0deb5cfcbe --- setup.py | 2 +- .../transpiler/unit/visitor/sympy_solver.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/setup.py b/setup.py index e8825204f6..ef64dbcecd 100644 --- a/setup.py +++ b/setup.py @@ -100,7 +100,7 @@ def _config_exe(exe_name): install_requirements = [ "PyYAML>=3.13", - "sympy>=1.3,<1.9", + "sympy>=1.3", ] diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 94ce8e10e4..917eb1edee 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -563,13 +563,15 @@ SCENARIO("Solve ODEs with cnexp or euler method using SympySolverVisitor", THEN("Integrate equations analytically where possible, otherwise leave untouched") { auto result = run_sympy_solver_visitor(nmodl_text); REQUIRE(result.size() == 4); - REQUIRE(result[0] == "z' = a/z+b/z/z"); + /// sympy 1.9 able to solve ode but not older versions + REQUIRE((result[0] == "z' = a/z+b/z/z" || + result[0] == + "z = (0.5*pow(a, 2)*pow(z, 2)-a*b*z+pow(b, 2)*log(a*z+b))/pow(a, 3)")); REQUIRE(result[1] == "h = -h/(c2*dt*h-1.0)"); REQUIRE(result[2] == "x = a*dt+x"); /// sympy 1.4 able to solve ode but not older versions - bool last_result = (result[3] == "y' = c3*y*y*y" || - result[3] == "y = sqrt(-pow(y, 2)/(2.0*c3*dt*pow(y, 2)-1.0))"); - REQUIRE(last_result); + REQUIRE((result[3] == "y' = c3*y*y*y" || + result[3] == "y = sqrt(-pow(y, 2)/(2.0*c3*dt*pow(y, 2)-1.0))")); } } GIVEN("Derivative block with cnexp solver method, AST after SympySolver pass") { @@ -1837,7 +1839,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { ~ C2*f02 + I3*bi3 + C4*b03 - C3*(b02+fi3+f03) = 0 ~ C3*f03 + I4*bi4 + C5*b04 - C4*(b03+fi4+f04) = 0 ~ C4*f04 + I5*bi5 + O*b0O - C5*(b04+fi5+f0O) = 0 - ~ C5*f0O + I6*bin - O*(b0O+fin) = 0 + ~ C5*f0O + I6*bin0 - O*(b0O+fin0) = 0 ~ C1*fi1 + I2*b11 - I1*( bi1+f11) = 0 ~ I1*f11 + C2*fi2 + I3*b12 - I2*(b11+bi2+f12) = 0 ~ I2*f12 + C3*fi3 + I4*bi3 - I3*(b12+bi3+f13) = 0 @@ -1944,8 +1946,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { nmodl_eigen_j[89] = 0 nmodl_eigen_j[101] = 0 nmodl_eigen_j[113] = 0 - nmodl_eigen_j[125] = -bin - nmodl_eigen_j[137] = b0O+fin + nmodl_eigen_j[125] = -bin0 + nmodl_eigen_j[137] = b0O+fin0 nmodl_eigen_j[6] = -fi1 nmodl_eigen_j[18] = 0 nmodl_eigen_j[30] = 0 From 54cb7040bb50c4d36c1d41ce84735fc1fe4e87d7 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Mon, 30 May 2022 16:28:14 +0200 Subject: [PATCH 432/871] Ban sphinx 5.0.0 (BlueBrain/nmodl#877) This is not supported in other documentation dependencies. NMODL Repo SHA: BlueBrain/nmodl@5675849012843499fd08d43eab818a45284d16ac --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index ef64dbcecd..38a54f5204 100644 --- a/setup.py +++ b/setup.py @@ -138,7 +138,7 @@ def _config_exe(exe_name): "nbconvert", "nbsphinx>=0.3.2", "pytest>=3.7.2", - "sphinx", + "sphinx<5", # myst_parser requires <5 and pip falls over "sphinx-rtd-theme", ] + install_requirements, From 3deddcea4a07bd96284182fe2b45f49981aa8a8d Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Tue, 31 May 2022 10:24:56 +0200 Subject: [PATCH 433/871] Submodule and CMake cleanup (BlueBrain/nmodl#875) Submodules: * Use hpc-coding-conventions helper to make them optional. * catch2: use submodule, do not commit code to repo. * fmt: use submodule or external, not spdlog copy. * nlohmann-json: use submodule, do not commit code to repo. * spdlog: do not use bundled copy of fmt. * Use #include <...> for external dependency headers. CMake: * cli11: use CMake target * use hpc-coding-conventions helpers, drop local copies * drop indirection of OBJECT libraries underneath STATIC ones * stop adding header files to libraries (this was probably harmless) Misc: * Drop upper limits on sympy version (BlueBrain/nmodl#870) NMODL Repo SHA: BlueBrain/nmodl@d63a061ee01b1fd6b14971644bb7fa3efeee20b0 --- cmake/nmodl/CMakeLists.txt | 64 +++++--- cmake/nmodl/Catch.cmake | 145 ------------------ cmake/nmodl/CatchAddTests.cmake | 86 ----------- cmake/nmodl/ExternalProjectHelper.cmake | 11 -- cmake/nmodl/FindPythonModule.cmake | 74 --------- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/CMakeLists.txt | 4 +- src/nmodl/codegen/CMakeLists.txt | 40 ++--- src/nmodl/lexer/CMakeLists.txt | 60 +++----- src/nmodl/lexer/main_units.cpp | 9 +- src/nmodl/parser/CMakeLists.txt | 6 +- src/nmodl/printer/CMakeLists.txt | 16 +- src/nmodl/printer/code_printer.hpp | 2 +- src/nmodl/printer/json_printer.hpp | 5 +- src/nmodl/pybind/CMakeLists.txt | 20 +-- src/nmodl/pybind/pyembed.hpp | 5 +- src/nmodl/pybind/pynmodl.cpp | 14 +- src/nmodl/pybind/wrapper.cpp | 10 +- .../solver/partial_piv_lu/partial_piv_lu.h | 4 +- src/nmodl/symtab/CMakeLists.txt | 20 +-- src/nmodl/utils/CMakeLists.txt | 38 ++--- src/nmodl/utils/string_utils.cpp | 2 +- src/nmodl/visitors/CMakeLists.txt | 93 ++++------- test/nmodl/transpiler/unit/CMakeLists.txt | 3 +- .../unit/codegen/codegen_c_visitor.cpp | 2 +- .../unit/codegen/codegen_helper.cpp | 2 +- .../transpiler/unit/codegen/codegen_ispc.cpp | 2 +- .../transpiler/unit/codegen/codegen_utils.cpp | 2 +- test/nmodl/transpiler/unit/codegen/main.cpp | 2 +- .../transpiler/unit/fast_math/fast_math.cpp | 2 +- test/nmodl/transpiler/unit/lexer/tokens.cpp | 2 +- .../transpiler/unit/modtoken/modtoken.cpp | 2 +- test/nmodl/transpiler/unit/newton/newton.cpp | 6 +- test/nmodl/transpiler/unit/parser/parser.cpp | 5 +- .../nmodl/transpiler/unit/printer/printer.cpp | 2 +- .../transpiler/unit/symtab/symbol_table.cpp | 2 +- test/nmodl/transpiler/unit/units/lexer.cpp | 2 +- test/nmodl/transpiler/unit/units/parser.cpp | 2 +- .../unit/visitor/after_cvode_to_cnexp.cpp | 2 +- .../unit/visitor/constant_folder.cpp | 2 +- .../unit/visitor/defuse_analyze.cpp | 2 +- .../unit/visitor/global_to_range.cpp | 2 +- test/nmodl/transpiler/unit/visitor/inline.cpp | 2 +- .../transpiler/unit/visitor/ispc_rename.cpp | 2 +- test/nmodl/transpiler/unit/visitor/json.cpp | 2 +- .../transpiler/unit/visitor/kinetic_block.cpp | 2 +- .../unit/visitor/local_to_assigned.cpp | 2 +- .../transpiler/unit/visitor/localize.cpp | 2 +- test/nmodl/transpiler/unit/visitor/lookup.cpp | 2 +- .../transpiler/unit/visitor/loop_unroll.cpp | 2 +- test/nmodl/transpiler/unit/visitor/main.cpp | 2 +- test/nmodl/transpiler/unit/visitor/misc.cpp | 2 +- .../transpiler/unit/visitor/neuron_solve.cpp | 2 +- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 2 +- .../transpiler/unit/visitor/node_index.cpp | 2 +- test/nmodl/transpiler/unit/visitor/perf.cpp | 2 +- test/nmodl/transpiler/unit/visitor/rename.cpp | 2 +- .../unit/visitor/semantic_analysis.cpp | 2 +- .../transpiler/unit/visitor/solve_block.cpp | 2 +- .../transpiler/unit/visitor/steadystate.cpp | 2 +- .../unit/visitor/sympy_conductance.cpp | 2 +- .../transpiler/unit/visitor/sympy_solver.cpp | 2 +- test/nmodl/transpiler/unit/visitor/units.cpp | 2 +- .../transpiler/unit/visitor/var_usage.cpp | 2 +- .../transpiler/unit/visitor/verbatim.cpp | 2 +- 65 files changed, 205 insertions(+), 617 deletions(-) delete mode 100644 cmake/nmodl/Catch.cmake delete mode 100644 cmake/nmodl/CatchAddTests.cmake delete mode 100644 cmake/nmodl/FindPythonModule.cmake diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 065055bf52..327287ca74 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -59,10 +59,8 @@ find_package(BISON 3.0 REQUIRED) # ============================================================================= # Include cmake modules. Filenames ensure we always pick up NMODL's versions. # ============================================================================= -include(cmake/Catch.cmake) include(cmake/ClangTidyHelper.cmake) include(cmake/CompilerHelper.cmake) -include(cmake/FindPythonModule.cmake) include(cmake/FlexHelper.cmake) include(cmake/GitRevision.cmake) include(cmake/PythonLinkHelper.cmake) @@ -80,23 +78,6 @@ project( VERSION ${NMODL_GIT_LAST_TAG} LANGUAGES CXX) -# ============================================================================= -# Initialize external libraries as submodule -# ============================================================================= -set(THIRD_PARTY_DIRECTORY "${PROJECT_SOURCE_DIR}/ext") -add_external_project(fmt OFF) -add_external_project(spdlog OFF) -add_external_project(pybind11) -add_external_project(cli11 OFF) -add_external_project(eigen OFF) - -add_subdirectory(${THIRD_PARTY_DIRECTORY}/fmt EXCLUDE_FROM_ALL) -set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) -include_directories( - SYSTEM ${THIRD_PARTY_DIRECTORY} ${THIRD_PARTY_DIRECTORY}/catch/include - ${THIRD_PARTY_DIRECTORY}/fmt/include ${THIRD_PARTY_DIRECTORY}/spdlog/include - ${THIRD_PARTY_DIRECTORY}/cli11/include) - # ============================================================================= # HPC Coding Conventions # ============================================================================= @@ -111,7 +92,39 @@ set(NMODL_CMakeFormat_EXCLUDES_RE set(THIRD_PARTY_DIRECTORY "${PROJECT_SOURCE_DIR}/cmake") add_external_project(hpc-coding-conventions OFF) add_subdirectory(cmake/hpc-coding-conventions/cpp) -include(FindClangFormat) +include(cmake/hpc-coding-conventions/cpp/cmake/FindClangFormat.cmake) + +# ============================================================================= +# Initialize external libraries as submodule +# ============================================================================= +set(NMODL_3RDPARTY_DIR "${PROJECT_SOURCE_DIR}/ext") +include(cmake/hpc-coding-conventions/cpp/cmake/3rdparty.cmake) +cpp_cc_git_submodule(catch2 BUILD PACKAGE Catch2 REQUIRED) +if(NMODL_3RDPARTY_USE_CATCH2) + # If we're using the submodule then make sure the Catch.cmake helper can be found. In newer + # versions of Catch2, and with hpc-coding-conventions#130, this should just work... + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ext/catch2/contrib") +endif() +include(Catch) +cpp_cc_git_submodule(cli11 BUILD PACKAGE CLI11 REQUIRED) +# For the moment do not try and use an external Eigen. +cpp_cc_git_submodule(eigen) +cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED) +# If we're building from the submodule, make sure we pass -fPIC so that we can link the code into a +# shared library later. +if(NMODL_3RDPARTY_USE_FMT) + set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() +cpp_cc_git_submodule(json BUILD PACKAGE nlohmann_json REQUIRED) +cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED) +# Tell spdlog not to use its bundled fmt, it should either use the fmt submodule or a truly external +# installation for consistency. This line should be harmless if we use an external spdlog. +set(SPDLOG_FMT_EXTERNAL ON) +cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) +if(NMODL_3RDPARTY_USE_SPDLOG) + # See above, same logic as fmt + set_property(TARGET spdlog PROPERTY POSITION_INDEPENDENT_CODE ON) +endif() # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) @@ -142,11 +155,12 @@ endif() # ============================================================================= message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.6 REQUIRED) -nmodl_find_python_module(jinja2 2.9.3 REQUIRED) -nmodl_find_python_module(pytest 3.3.0 REQUIRED) -nmodl_find_python_module(sympy 1.2 REQUIRED) -nmodl_find_python_module(textwrap 0.9 REQUIRED) -nmodl_find_python_module(yaml 3.12 REQUIRED) +include(cmake/hpc-coding-conventions/cpp/cmake/bbp-find-python-module.cmake) +cpp_cc_find_python_module(jinja2 2.9.3 REQUIRED) +cpp_cc_find_python_module(pytest 3.3.0 REQUIRED) +cpp_cc_find_python_module(sympy 1.3 REQUIRED) +cpp_cc_find_python_module(textwrap 0.9 REQUIRED) +cpp_cc_find_python_module(yaml 3.12 REQUIRED) # ============================================================================= # Compiler specific flags for external submodules diff --git a/cmake/nmodl/Catch.cmake b/cmake/nmodl/Catch.cmake deleted file mode 100644 index 36ce2786b8..0000000000 --- a/cmake/nmodl/Catch.cmake +++ /dev/null @@ -1,145 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or -# https://cmake.org/licensing for details. - -#[=======================================================================[.rst: -Catch ------ - -This module defines a function to help use the Catch test framework. - -The :command:`catch_discover_tests` discovers tests by asking the compiled test -executable to enumerate its tests. This does not require CMake to be re-run -when tests change. However, it may not work in a cross-compiling environment, -and setting test properties is less convenient. - -This command is intended to replace use of :command:`add_test` to register -tests, and will create a separate CTest test for each Catch test case. Note -that this is in some cases less efficient, as common set-up and tear-down logic -cannot be shared by multiple test cases executing in the same instance. -However, it provides more fine-grained pass/fail information to CTest, which is -usually considered as more beneficial. By default, the CTest test name is the -same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``. - -.. command:: catch_discover_tests - - Automatically add tests with CTest by querying the compiled test executable - for available tests:: - - catch_discover_tests(target - [TEST_SPEC arg1...] - [EXTRA_ARGS arg1...] - [WORKING_DIRECTORY dir] - [TEST_PREFIX prefix] - [TEST_SUFFIX suffix] - [PROPERTIES name1 value1...] - [TEST_LIST var] - ) - - ``catch_discover_tests`` sets up a post-build command on the test executable - that generates the list of tests by parsing the output from running the test - with the ``--list-test-names-only`` argument. This ensures that the full - list of tests is obtained. Since test discovery occurs at build time, it is - not necessary to re-run CMake when the list of tests changes. - However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set - in order to function in a cross-compiling environment. - - Additionally, setting properties on tests is somewhat less convenient, since - the tests are not available at CMake time. Additional test properties may be - assigned to the set of tests as a whole using the ``PROPERTIES`` option. If - more fine-grained test control is needed, custom content may be provided - through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES` - directory property. The set of discovered tests is made accessible to such a - script via the ``_TESTS`` variable. - - The options are: - - ``target`` - Specifies the Catch executable, which must be a known CMake executable - target. CMake will substitute the location of the built executable when - running the test. - - ``TEST_SPEC arg1...`` - Specifies test cases, wildcarded test cases, tags and tag expressions to - pass to the Catch executable with the ``--list-test-names-only`` argument. - - ``EXTRA_ARGS arg1...`` - Any extra arguments to pass on the command line to each test case. - - ``WORKING_DIRECTORY dir`` - Specifies the directory in which to run the discovered test cases. If this - option is not provided, the current binary directory is used. - - ``TEST_PREFIX prefix`` - Specifies a ``prefix`` to be prepended to the name of each discovered test - case. This can be useful when the same test executable is being used in - multiple calls to ``catch_discover_tests()`` but with different - ``TEST_SPEC`` or ``EXTRA_ARGS``. - - ``TEST_SUFFIX suffix`` - Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of - every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may - be specified. - - ``PROPERTIES name1 value1...`` - Specifies additional properties to be set on all tests discovered by this - invocation of ``catch_discover_tests``. - - ``TEST_LIST var`` - Make the list of tests available in the variable ``var``, rather than the - default ``_TESTS``. This can be useful when the same test - executable is being used in multiple calls to ``catch_discover_tests()``. - Note that this variable is only available in CTest. - -#]=======================================================================] - -# ------------------------------------------------------------------------------ -function(catch_discover_tests TARGET) - cmake_parse_arguments("" "" "TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST" - "TEST_SPEC;EXTRA_ARGS;PROPERTIES" ${ARGN}) - - if(NOT _WORKING_DIRECTORY) - set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") - endif() - if(NOT _TEST_LIST) - set(_TEST_LIST ${TARGET}_TESTS) - endif() - - # Generate a unique name based on the extra arguments - string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS}") - string(SUBSTRING ${args_hash} 0 7 args_hash) - - # Define rule to generate test list for aforementioned test executable - set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake") - set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake") - get_property( - crosscompiling_emulator - TARGET ${TARGET} - PROPERTY CROSSCOMPILING_EMULATOR) - add_custom_command( - TARGET ${TARGET} - POST_BUILD - BYPRODUCTS "${ctest_tests_file}" - COMMAND - "${CMAKE_COMMAND}" -D "TEST_TARGET=${TARGET}" -D "TEST_EXECUTABLE=$" -D - "TEST_EXECUTOR=${crosscompiling_emulator}" -D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}" -D - "TEST_SPEC=${_TEST_SPEC}" -D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}" -D - "TEST_PROPERTIES=${_PROPERTIES}" -D "TEST_PREFIX=${_TEST_PREFIX}" -D - "TEST_SUFFIX=${_TEST_SUFFIX}" -D "TEST_LIST=${_TEST_LIST}" -D "CTEST_FILE=${ctest_tests_file}" - -P "${_CATCH_DISCOVER_TESTS_SCRIPT}" - VERBATIM) - - file( - WRITE "${ctest_include_file}" - "if(EXISTS \"${ctest_tests_file}\")\n" " include(\"${ctest_tests_file}\")\n" "else()\n" - " add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n" "endif()\n") - - # Add discovered tests to directory TEST_INCLUDE_FILES - set_property( - DIRECTORY - APPEND - PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}") -endfunction() - -# - -set(_CATCH_DISCOVER_TESTS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake) diff --git a/cmake/nmodl/CatchAddTests.cmake b/cmake/nmodl/CatchAddTests.cmake deleted file mode 100644 index 79aa6d19fe..0000000000 --- a/cmake/nmodl/CatchAddTests.cmake +++ /dev/null @@ -1,86 +0,0 @@ -# Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or -# https://cmake.org/licensing for details. - -set(prefix "${TEST_PREFIX}") -set(suffix "${TEST_SUFFIX}") -set(spec ${TEST_SPEC}) -set(extra_args ${TEST_EXTRA_ARGS}) -set(properties ${TEST_PROPERTIES}) -set(script) -set(suite) -set(tests) - -function(add_command NAME) - set(_args "") - foreach(_arg ${ARGN}) - if(_arg MATCHES "[^-./:a-zA-Z0-9_]") - set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument - else() - set(_args "${_args} ${_arg}") - endif() - endforeach() - set(script - "${script}${NAME}(${_args})\n" - PARENT_SCOPE) -endfunction() - -macro(_add_catch_test_labels LINE) - # convert to list of tags - string(REPLACE "][" "]\\;[" tags ${line}) - - add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES LABELS "${tags}") -endmacro() - -macro(_add_catch_test LINE) - set(test ${line}) - # use escape commas to handle properly test cases with commans inside the name - string(REPLACE "," "\\," test_name ${test}) - # ...and add to script - add_command(add_test "${prefix}${test}${suffix}" ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" - "${test_name}" ${extra_args}) - - add_command(set_tests_properties "${prefix}${test}${suffix}" PROPERTIES WORKING_DIRECTORY - "${TEST_WORKING_DIR}" ${properties}) - list(APPEND tests "${prefix}${test}${suffix}") -endmacro() - -# Run test executable to get list of available tests -if(NOT EXISTS "${TEST_EXECUTABLE}") - message(FATAL_ERROR "Specified test executable '${TEST_EXECUTABLE}' does not exist") -endif() -execute_process( - COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-tests - OUTPUT_VARIABLE output - RESULT_VARIABLE result) -# Catch --list-test-names-only reports the number of tests, so 0 is... surprising -if(${result} EQUAL 0) - message(WARNING "Test executable '${TEST_EXECUTABLE}' contains no tests!\n") -elseif(${result} LESS 0) - message(FATAL_ERROR "Error running test executable '${TEST_EXECUTABLE}':\n" - " Result: ${result}\n" " Output: ${output}\n") -endif() - -string(REPLACE "\n" ";" output "${output}") -set(test) -set(tags_regex "(\\[([^\\[]*)\\])+$") - -# Parse output -foreach(line ${output}) - # lines without leading whitespaces are catch output not tests - if(${line} MATCHES "^[ \t]+") - # strip leading spaces and tabs - string(REGEX REPLACE "^[ \t]+" "" line ${line}) - - if(${line} MATCHES "${tags_regex}") - _add_catch_test_labels(${line}) - else() - _add_catch_test(${line}) - endif() - endif() -endforeach() - -# Create a list of all discovered tests, which users may use to e.g. set properties on the tests -add_command(set ${TEST_LIST} ${tests}) - -# Write CTest script -file(WRITE "${CTEST_FILE}" "${script}") diff --git a/cmake/nmodl/ExternalProjectHelper.cmake b/cmake/nmodl/ExternalProjectHelper.cmake index 6064008346..8c1d72a996 100644 --- a/cmake/nmodl/ExternalProjectHelper.cmake +++ b/cmake/nmodl/ExternalProjectHelper.cmake @@ -1,9 +1,5 @@ find_package(Git QUIET) -set(THIRD_PARTY_DIRECTORY - "${NMODL_PROJECT_SOURCE_DIR}/3rdparty" - CACHE PATH "The path were all the 3rd party projects can be found") - # initialize submodule with given path function(initialize_submodule path) if(NOT ${GIT_FOUND}) @@ -26,11 +22,4 @@ function(add_external_project name) else() message(STATUS "Sub-project : using ${name} from \"${THIRD_PARTY_DIRECTORY}/${name}\"") endif() - if(${ARGC} GREATER 1) - if(${ARGV2}) - add_subdirectory("${THIRD_PARTY_DIRECTORY}/${name}") - endif() - else() - add_subdirectory("${THIRD_PARTY_DIRECTORY}/${name}") - endif() endfunction() diff --git a/cmake/nmodl/FindPythonModule.cmake b/cmake/nmodl/FindPythonModule.cmake deleted file mode 100644 index 6fdbfd88d2..0000000000 --- a/cmake/nmodl/FindPythonModule.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# * Macro to find a python module -# -# Usage: nmodl_find_python_module (module [VERSION] [REQUIRED]) -# -# Copyright 2005-2018 Airbus-EDF-IMACS-Phimeca -# -# Distributed under the OSI-approved BSD License (the "License"); see accompanying file -# Copyright.txt for details in: -# -# https://github.com/openturns/otsubsetinverse/blob/master/cmake/FindPythonModule.cmake - -macro(nmodl_find_python_module module) - - string(TOUPPER ${module} module_upper) - if(NOT ${module_upper}_FOUND) - - # parse arguments - set(${module}_FIND_OPTIONAL TRUE) - if(${ARGC} EQUAL 2) - if(${ARGV1} MATCHES REQUIRED) - set(${module}_FIND_OPTIONAL FALSE) - else() - set(${module}_FIND_VERSION ${ARGV1}) - endif() - elseif(${ARGC} EQUAL 3) - if(${ARGV2} MATCHES REQUIRED) - set(${module}_FIND_OPTIONAL FALSE) - endif() - set(${module}_FIND_VERSION ${ARGV1}) - endif() - - # A module's location is usually a directory, but for binary modules it's a .so file. - execute_process( - COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import re, ${module}; print(re.compile('/__init__.py.*').sub('',${module}.__file__))" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_location - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT _${module}_status) - set(${module_upper}_LOCATION - ${_${module}_location} - CACHE STRING "Location of Python module ${module}") - # retrieve version - execute_process( - COMMAND "${PYTHON_EXECUTABLE}" "-c" "import ${module}; print(${module}.__version__)" - RESULT_VARIABLE _${module}_status - OUTPUT_VARIABLE _${module}_version - ERROR_QUIET OUTPUT_STRIP_TRAILING_WHITESPACE) - - set(_${module_upper}_VERSION_MATCH TRUE) - if(NOT _${module}_status) - set(${module_upper}_VERSION_STRING ${_${module}_version}) - if(${module}_FIND_VERSION) - if(${module}_FIND_VERSION VERSION_GREATER ${module_upper}_VERSION_STRING) - set(_${module_upper}_VERSION_MATCH FALSE) - endif() - endif() - mark_as_advanced(${module_upper}_VERSION_STRING) - endif() - endif() - - if(NOT ${module}_FIND_OPTIONAL) - if(NOT ${module_upper}_LOCATION) - message(FATAL_ERROR "Missing python module \"${module}\"") - elseif(NOT _${module_upper}_VERSION_MATCH) - message( - FATAL_ERROR - "Found module \"${module}\", but version mismatch. Asked for \"${${module}_FIND_VERSION}\" but found \"${${module_upper}_VERSION_STRING}\"" - ) - endif() - endif() - mark_as_advanced(${module_upper}_LOCATION) - endif(NOT ${module_upper}_FOUND) -endmacro(nmodl_find_python_module) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 7eaad9d932..086e9156ab 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 7eaad9d932f1fdcd7421d943cbf7bc5fcd6c5165 +Subproject commit 086e9156abceeca26cbb2bf31663229968da1671 diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 29e1e205a2..06993fa2d1 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -19,14 +19,14 @@ add_subdirectory(solver) # NMODL sources # ============================================================================= include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -set(NMODL_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) # ============================================================================= # Add executables # ============================================================================= -add_executable(nmodl ${NMODL_SOURCE_FILES}) +add_executable(nmodl main.cpp) target_link_libraries( nmodl + CLI11::CLI11 printer codegen visitor diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 32ad4e1303..23e2eca632 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -1,33 +1,19 @@ # ============================================================================= -# Codegen sources +# Codegen library # ============================================================================= -set(CODEGEN_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_acc_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_compatibility_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_compatibility_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_cuda_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_cuda_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_omp_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_omp_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_c_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_c_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_helper_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_helper_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_info.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_ispc_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_ispc_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_naming.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/codegen_utils.hpp) - -# ============================================================================= -# Codegen library and executable -# ============================================================================= -add_library(codegen STATIC ${CODEGEN_SOURCE_FILES}) - +add_library( + codegen STATIC + codegen_acc_visitor.cpp + codegen_c_visitor.cpp + codegen_compatibility_visitor.cpp + codegen_cuda_visitor.cpp + codegen_helper_visitor.cpp + codegen_info.cpp + codegen_ispc_visitor.cpp + codegen_omp_visitor.cpp + codegen_utils.cpp) add_dependencies(codegen lexer util visitor) +target_link_libraries(codegen PRIVATE util) # copy to build directory to make usable from build directory configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index a051eca375..93cab280b2 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -2,58 +2,42 @@ # Various project components and their source files # ============================================================================= set(BISON_GENERATED_SOURCE_FILES - ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp + ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp) +set(BISON_GENERATED_HEADER_FILES + ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp) -set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) +set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) -set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) +set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) -set(DIFFEQ_DRIVER_FILES - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_helper.hpp) +set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp + ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp) -set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) +set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) set_source_files_properties(${AST_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) -set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) +set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) set(LEXER_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/modl.h - ${CMAKE_CURRENT_SOURCE_DIR}/token_mapping.hpp ${CMAKE_CURRENT_SOURCE_DIR}/token_mapping.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.hpp ${CMAKE_CURRENT_SOURCE_DIR}/modtoken.cpp ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/nmodl_base_lexer.hpp ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/verbatim_lexer.hpp ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/diffeq_base_lexer.hpp ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/c11_base_lexer.hpp ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.cpp - ${CMAKE_CURRENT_BINARY_DIR}/unit_base_lexer.hpp ${NMODL_DRIVER_FILES} ${DIFFEQ_DRIVER_FILES} ${C_DRIVER_FILES} @@ -199,22 +183,19 @@ add_custom_command( # Libraries & executables # ============================================================================= -add_library(lexer_obj OBJECT ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} - ${AST_GENERATED_SOURCES} ${UNIT_SOURCE_FILES}) - -set_property(TARGET lexer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) - -add_library(lexer STATIC $) -target_link_libraries(lexer fmt::fmt) +add_library(lexer STATIC ${LEXER_SOURCE_FILES} ${BISON_GENERATED_SOURCE_FILES} + ${AST_GENERATED_SOURCES} ${UNIT_SOURCE_FILES}) +set_property(TARGET lexer PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(lexer PRIVATE util) if(NOT NMODL_AS_SUBPROJECT) add_executable(nmodl_lexer main_nmodl.cpp) add_executable(c_lexer main_c.cpp) add_executable(units_lexer main_units.cpp) - target_link_libraries(nmodl_lexer lexer util) - target_link_libraries(c_lexer lexer util) - target_link_libraries(units_lexer lexer util) + target_link_libraries(nmodl_lexer CLI11::CLI11 lexer util) + target_link_libraries(c_lexer CLI11::CLI11 lexer util) + target_link_libraries(units_lexer CLI11::CLI11 lexer util) endif() # ============================================================================= @@ -235,4 +216,5 @@ if(NOT NMODL_AS_SUBPROJECT) CONFIGURATIONS Debug) endif() -add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES}) +add_custom_target(parser-gen DEPENDS ${BISON_GENERATED_SOURCE_FILES} + ${BISON_GENERATED_HEADER_FILES}) diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index 976d125edb..dd194fee4c 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -4,16 +4,15 @@ * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ - -#include - -#include "CLI/CLI.hpp" - #include "config/config.h" #include "lexer/unit_lexer.hpp" #include "parser/unit_driver.hpp" #include "utils/logger.hpp" +#include + +#include + /** * \file * Example of standalone lexer program for Units that diff --git a/src/nmodl/parser/CMakeLists.txt b/src/nmodl/parser/CMakeLists.txt index c1ad510df8..835b206a43 100644 --- a/src/nmodl/parser/CMakeLists.txt +++ b/src/nmodl/parser/CMakeLists.txt @@ -9,9 +9,9 @@ if(NOT NMODL_AS_SUBPROJECT) add_executable(c_parser main_c.cpp) add_executable(units_parser main_units.cpp) - target_link_libraries(nmodl_parser lexer util) - target_link_libraries(c_parser lexer util) - target_link_libraries(units_parser util visitor lexer) + target_link_libraries(nmodl_parser CLI11::CLI11 lexer util) + target_link_libraries(c_parser CLI11::CLI11 lexer util) + target_link_libraries(units_parser CLI11::CLI11 util visitor lexer) endif() # ============================================================================= diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 5a44153dab..8a3e02da23 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -1,16 +1,6 @@ -# ============================================================================= -# Printer sources -# ============================================================================= -set(PRINTER_SOURCE_FILES - ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/json_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_printer.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.hpp ${CMAKE_CURRENT_SOURCE_DIR}/code_printer.cpp) - # ============================================================================= # Printer library # ============================================================================= - -add_library(printer_obj OBJECT ${PRINTER_SOURCE_FILES}) -set_property(TARGET printer_obj PROPERTY POSITION_INDEPENDENT_CODE ON) - -add_library(printer STATIC $) +add_library(printer OBJECT code_printer.cpp json_printer.cpp nmodl_printer.cpp) +set_property(TARGET printer PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(printer PRIVATE util) diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 62810b3828..695a85bb68 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -14,7 +14,7 @@ * \file * \brief \copybrief nmodl::printer::CodePrinter */ -#include // want fmt but +#include #include #include diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index dab26faf1a..471fd53bed 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -12,13 +12,12 @@ * \brief \copybrief nmodl::printer::JSONPrinter */ +#include + #include #include #include -#include "json/json.hpp" - - namespace nmodl { namespace printer { diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index e24861af62..a78174cefd 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -24,8 +24,9 @@ foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/lib/nmodl/${file}) endforeach() -add_library(pyembed ${CMAKE_CURRENT_SOURCE_DIR}/pyembed.cpp) +add_library(pyembed pyembed.cpp) set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(pyembed PRIVATE util) if(NOT LINK_AGAINST_PYTHON) add_library(pywrapper SHARED ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) @@ -56,20 +57,11 @@ if(NMODL_ENABLE_PYTHON_BINDINGS) # for pybind using NO_EXTRAS. See #266. # ~~~ pybind11_add_module( - _nmodl - NO_EXTRAS - ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp + _nmodl NO_EXTRAS ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp - ${PYBIND_GENERATED_SOURCES} - $ - $ - $ - $ - $) - - add_dependencies(_nmodl pyastgen lexer_obj util_obj) - target_link_libraries(_nmodl PRIVATE fmt::fmt pyembed) + ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp ${PYBIND_GENERATED_SOURCES}) + add_dependencies(_nmodl lexer pyastgen util) + target_link_libraries(_nmodl PRIVATE printer symtab visitor pyembed) # in case of wheel, python module shouldn't link to wrapper library if(LINK_AGAINST_PYTHON) diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp index c4fee5dc95..80068610ad 100644 --- a/src/nmodl/pybind/pyembed.hpp +++ b/src/nmodl/pybind/pyembed.hpp @@ -4,16 +4,15 @@ * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ - #pragma once +#include + #include #include #include #include -#include "pybind11/embed.h" - namespace nmodl { namespace pybind_wrappers { diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index ebc72f2e18..dfbcf3068d 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -4,20 +4,18 @@ * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ - -#include -#include - -#include -#include -#include - #include "ast/program.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "pybind/pybind_utils.hpp" #include "visitors/visitor_utils.hpp" +#include +#include +#include + +#include +#include /** * \dir diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index c9cc9060e8..e3cbc88c75 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -4,17 +4,15 @@ * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "codegen/codegen_naming.hpp" +#include "pybind/pyembed.hpp" +#include +#include #include #include -#include "pybind11/embed.h" -#include "pybind11/stl.h" - -#include "codegen/codegen_naming.hpp" -#include "pybind/pyembed.hpp" - namespace py = pybind11; using namespace py::literals; diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h index e70d4b4432..918febe896 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h @@ -9,8 +9,8 @@ #include "coreneuron/utils/offload.hpp" -#include "Eigen/Dense" -#include "Eigen/LU" +#include +#include template using MatType = Eigen::Matrix; diff --git a/src/nmodl/symtab/CMakeLists.txt b/src/nmodl/symtab/CMakeLists.txt index 3d26aad7b3..8203e86dc3 100644 --- a/src/nmodl/symtab/CMakeLists.txt +++ b/src/nmodl/symtab/CMakeLists.txt @@ -1,21 +1,7 @@ -# ============================================================================= -# Visitor sources -# ============================================================================= -set(SYMTAB_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.cpp) - -set(SYMTAB_HEADERS - ${CMAKE_CURRENT_SOURCE_DIR}/symbol.hpp ${CMAKE_CURRENT_SOURCE_DIR}/symbol_properties.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/symbol_table.hpp) - # ============================================================================= # Symbol table library # ============================================================================= -add_library(symtab_obj OBJECT ${SYMTAB_SOURCES} ${SYMTAB_HEADERS}) -set_property(TARGET symtab_obj PROPERTY POSITION_INDEPENDENT_CODE ON) -add_dependencies(symtab_obj lexer_obj) - -add_library(symtab STATIC $) - +add_library(symtab STATIC symbol.cpp symbol_properties.cpp symbol_table.cpp) +set_property(TARGET symtab PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(symtab PRIVATE util) add_dependencies(symtab lexer) diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index a8a2d4dc53..cab0dbfbf4 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -1,26 +1,14 @@ # ============================================================================= -# Utility sources -# ============================================================================= -set(UTIL_SOURCE_FILES - common_utils.cpp - common_utils.hpp - file_library.cpp - file_library.hpp - logger.cpp - logger.hpp - perf_stat.cpp - perf_stat.hpp - string_utils.cpp - string_utils.hpp - table_data.cpp - table_data.hpp - ${PROJECT_BINARY_DIR}/src/config/config.cpp) - -# ============================================================================= -# Symbol table library -# ============================================================================= - -add_library(util_obj OBJECT ${UTIL_SOURCE_FILES}) -set_property(TARGET util_obj PROPERTY POSITION_INDEPENDENT_CODE ON) - -add_library(util STATIC $) +# Utility library +# ============================================================================= +add_library( + util STATIC + common_utils.cpp + file_library.cpp + logger.cpp + perf_stat.cpp + string_utils.cpp + table_data.cpp + ${PROJECT_BINARY_DIR}/src/config/config.cpp) +set_property(TARGET util PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(util PUBLIC fmt::fmt nlohmann_json::nlohmann_json spdlog::spdlog_header_only) diff --git a/src/nmodl/utils/string_utils.cpp b/src/nmodl/utils/string_utils.cpp index 072c81a28a..ae468af790 100644 --- a/src/nmodl/utils/string_utils.cpp +++ b/src/nmodl/utils/string_utils.cpp @@ -6,7 +6,7 @@ *************************************************************************/ #include "utils/string_utils.hpp" -#include +#include #include #include diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index edc5d525c6..c5e4121adc 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -1,73 +1,43 @@ # ============================================================================= # Visitor sources # ============================================================================= -set(VISITOR_SOURCES - ${CMAKE_CURRENT_SOURCE_DIR}/neuron_solve_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/neuron_solve_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/constant_folder_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/after_cvode_to_cnexp_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/after_cvode_to_cnexp_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/defuse_analyze_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/global_var_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/indexedname_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/indexedname_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/inline_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/kinetic_block_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_to_assigned_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_to_assigned_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/local_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/localize_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/loop_unroll_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/loop_unroll_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/nmodl_visitor_helper.ipp - ${CMAKE_CURRENT_SOURCE_DIR}/solve_block_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/solve_block_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/perf_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/semantic_analysis_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/semantic_analysis_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/steadystate_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/steadystate_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_conductance_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_solver_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_replace_solutions_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/sympy_replace_solutions_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/symtab_visitor_helper.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/units_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/units_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/var_usage_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_var_rename_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/verbatim_visitor.hpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/visitor_utils.hpp) - set_source_files_properties(${VISITORS_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) # ============================================================================= # Visitor library and executable # ============================================================================= include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -add_library(visitor_obj OBJECT ${VISITOR_SOURCES} ${VISITORS_GENERATED_SOURCES}) -set_property(TARGET visitor_obj PROPERTY POSITION_INDEPENDENT_CODE ON) - -add_dependencies(visitor_obj lexer_obj) -add_library(visitor STATIC $) - -add_dependencies(visitor lexer util pywrapper) +add_library( + visitor STATIC + after_cvode_to_cnexp_visitor.cpp + constant_folder_visitor.cpp + defuse_analyze_visitor.cpp + global_var_visitor.cpp + indexedname_visitor.cpp + inline_visitor.cpp + kinetic_block_visitor.cpp + local_to_assigned_visitor.cpp + local_var_rename_visitor.cpp + localize_visitor.cpp + loop_unroll_visitor.cpp + neuron_solve_visitor.cpp + perf_visitor.cpp + rename_visitor.cpp + semantic_analysis_visitor.cpp + solve_block_visitor.cpp + steadystate_visitor.cpp + sympy_conductance_visitor.cpp + sympy_replace_solutions_visitor.cpp + sympy_solver_visitor.cpp + units_visitor.cpp + var_usage_visitor.cpp + verbatim_var_rename_visitor.cpp + verbatim_visitor.cpp + visitor_utils.cpp + ${VISITORS_GENERATED_SOURCES}) +set_property(TARGET visitor PROPERTY POSITION_INDEPENDENT_CODE ON) +add_dependencies(visitor pywrapper) +target_link_libraries(visitor PUBLIC lexer util) # ~~~ # pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to @@ -84,6 +54,7 @@ if(NOT NMODL_AS_SUBPROJECT) target_link_libraries( nmodl_visitor + CLI11::CLI11 printer visitor symtab diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 457f35a288..77bab79e98 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -8,7 +8,7 @@ add_link_options(${NMODL_EXTRA_CXX_FLAGS}) add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/catch ${NMODL_PROJECT_SOURCE_DIR}/test) +include_directories(${NMODL_PROJECT_SOURCE_DIR}/test) include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/solver) include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/utils) include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) @@ -128,6 +128,7 @@ foreach( testunitlexer testunitparser) + target_link_libraries(${test_name} Catch2::Catch2) if(${test_name} STREQUAL "testvisitor") catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT "${testvisitor_env}") diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index 00869c8f04..6c3f3b1959 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "codegen/codegen_c_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index b4ac5d650b..9aa296efcd 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "codegen/codegen_helper_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp index ea456a999d..e2e63432bb 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "codegen/codegen_ispc_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index 17f2d0f4b7..ee1c1899bf 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_ispc_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp index 9e1bc03860..9c75cacfd2 100644 --- a/test/nmodl/transpiler/unit/codegen/main.cpp +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -7,7 +7,7 @@ #define CATCH_CONFIG_RUNNER -#include +#include #include "pybind/pyembed.hpp" #include "utils/logger.hpp" diff --git a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp index e156074d4d..606ebbcc7c 100644 --- a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp +++ b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp @@ -9,7 +9,7 @@ #include "codegen/fast_math.hpp" -#include +#include namespace nmodl { namespace fast_math { diff --git a/test/nmodl/transpiler/unit/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp index 7a41d6781a..738fb00c76 100644 --- a/test/nmodl/transpiler/unit/lexer/tokens.cpp +++ b/test/nmodl/transpiler/unit/lexer/tokens.cpp @@ -9,7 +9,7 @@ #include -#include +#include #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 61a164e1b3..7656219477 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 626d9906e2..07a8a60924 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -7,11 +7,11 @@ #define CATCH_CONFIG_MAIN -#include +#include "nmodl.hpp" -#include +#include -#include "nmodl.hpp" +#include using namespace nmodl; diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index d8444dceda..f0f3fec8c4 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include "ast/program.hpp" #include "lexer/modtoken.hpp" @@ -38,7 +38,8 @@ SCENARIO("NMODL can accept \\r as return char for one line comment", "[parser]") GIVEN("A comment defined with \\r as return char") { WHEN("parsing") { THEN("success") { - REQUIRE(is_valid_construct(R"(: see you next line PROCEDURE foo() { + REQUIRE(is_valid_construct(R"(: see you next line +PROCEDURE foo() { })")); } } diff --git a/test/nmodl/transpiler/unit/printer/printer.cpp b/test/nmodl/transpiler/unit/printer/printer.cpp index 383e0c1a7e..f419bc4acc 100644 --- a/test/nmodl/transpiler/unit/printer/printer.cpp +++ b/test/nmodl/transpiler/unit/printer/printer.cpp @@ -9,7 +9,7 @@ #include -#include +#include #include "printer/json_printer.hpp" diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index 2e87604c68..707ea8775b 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -9,7 +9,7 @@ #include -#include +#include #include "ast/program.hpp" #include "symtab/symbol.hpp" diff --git a/test/nmodl/transpiler/unit/units/lexer.cpp b/test/nmodl/transpiler/unit/units/lexer.cpp index bfc5386964..d51e0e478a 100644 --- a/test/nmodl/transpiler/unit/units/lexer.cpp +++ b/test/nmodl/transpiler/unit/units/lexer.cpp @@ -9,7 +9,7 @@ #include -#include +#include #include "lexer/unit_lexer.hpp" #include "parser/unit_driver.hpp" diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index 7db2635247..ea7ea31b61 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include "config/config.h" #include "parser/diffeq_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp index 95b6887979..9b5a5da7ad 100644 --- a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp +++ b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp index db0a232c3a..c69c152790 100644 --- a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 9471cc5fdf..3e31361d72 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/binary_expression.hpp" #include "ast/program.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 3beb4a6d3c..861899773f 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 15972cf809..2eb82fd930 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp index 29f521ab4a..c75ce6d110 100644 --- a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/json.cpp b/test/nmodl/transpiler/unit/visitor/json.cpp index 395a029f40..003f10fd43 100644 --- a/test/nmodl/transpiler/unit/visitor/json.cpp +++ b/test/nmodl/transpiler/unit/visitor/json.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index eafd25c2b9..ef9a8f509d 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index f1c637a5e0..17e4c45124 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "catch/catch.hpp" +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp index 31821318d6..5e97d89272 100644 --- a/test/nmodl/transpiler/unit/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index 26e12d6452..84e3146f06 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index 107ec820fa..60af4bf57a 100644 --- a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp index 9e1bc03860..9c75cacfd2 100644 --- a/test/nmodl/transpiler/unit/visitor/main.cpp +++ b/test/nmodl/transpiler/unit/visitor/main.cpp @@ -7,7 +7,7 @@ #define CATCH_CONFIG_RUNNER -#include +#include #include "pybind/pyembed.hpp" #include "utils/logger.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp index 74446baf4e..8146e7f378 100644 --- a/test/nmodl/transpiler/unit/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 524b630538..83b5a648d5 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 1e98d68d19..b2879f3d7c 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/node_index.cpp b/test/nmodl/transpiler/unit/visitor/node_index.cpp index 0d6ad82fcc..2053757153 100644 --- a/test/nmodl/transpiler/unit/visitor/node_index.cpp +++ b/test/nmodl/transpiler/unit/visitor/node_index.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/perf.cpp b/test/nmodl/transpiler/unit/visitor/perf.cpp index 9dc70b4b98..aada3bf24f 100644 --- a/test/nmodl/transpiler/unit/visitor/perf.cpp +++ b/test/nmodl/transpiler/unit/visitor/perf.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp index 0208076ad3..b0e47f7028 100644 --- a/test/nmodl/transpiler/unit/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 7644bb6cf1..710cf35224 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index 2602c08e5c..3dc19cfc7d 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index ef833e6f7b..416e143645 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 20da87af81..1e6d232d95 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 917eb1edee..776297b9de 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 8a1b19f1ce..b1bc9d4c1e 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/double.hpp" #include "ast/factor_def.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/var_usage.cpp b/test/nmodl/transpiler/unit/visitor/var_usage.cpp index 0ddb39c854..787a2c464d 100644 --- a/test/nmodl/transpiler/unit/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/unit/visitor/var_usage.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/verbatim.cpp b/test/nmodl/transpiler/unit/visitor/verbatim.cpp index 2ebc01ac8a..d2c68f602f 100644 --- a/test/nmodl/transpiler/unit/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/unit/visitor/verbatim.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" From f481b1e5f005de4bb8e4872d7aff09d64c316234 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 2 Jun 2022 15:42:25 +0200 Subject: [PATCH 434/871] Update GitHub Actions CI, minor CMake fixes. (BlueBrain/nmodl#879) - Only pin major versions of actions, update some too. - Drop sympy version constraints. - Coverage from a Debug, not Release, build. - Prefer ubuntu-20.04 to ubuntu-18.04, add ubuntu-22.04 too. - Drop add_external_project helper that was only used to bootstrap hpc-coding-conventions. - Do not add a CLI11 submodule if the relevant targets already exist, this fixes some combinations of NMODL-as-a-submodule build. - Fix NMODL_3RDPARTY_DIR to avoid the absolute path being prepended twice. NMODL Repo SHA: BlueBrain/nmodl@f3a9bd2ca835af2db81ff0b2b12462e30b7291ae --- cmake/nmodl/CMakeLists.txt | 16 +++++++++++----- cmake/nmodl/ExternalProjectHelper.cmake | 13 ------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 327287ca74..5d65fcd326 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -89,15 +89,17 @@ set(NMODL_CMakeFormat_EXCLUDES_RE CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) # initialize submodule of coding conventions under cmake -set(THIRD_PARTY_DIRECTORY "${PROJECT_SOURCE_DIR}/cmake") -add_external_project(hpc-coding-conventions OFF) +if(NOT EXISTS "${PROJECT_SOURCE_DIR}/cmake/hpc-coding-conventions/cpp/CMakeLists.txt") + initialize_submodule("${PROJECT_SOURCE_DIR}/cmake/hpc-coding-conventions") +endif() +set(CODING_CONV_PREFIX NMODL) add_subdirectory(cmake/hpc-coding-conventions/cpp) include(cmake/hpc-coding-conventions/cpp/cmake/FindClangFormat.cmake) # ============================================================================= -# Initialize external libraries as submodule +# Initialize external libraries as submodules # ============================================================================= -set(NMODL_3RDPARTY_DIR "${PROJECT_SOURCE_DIR}/ext") +set(NMODL_3RDPARTY_DIR ext) include(cmake/hpc-coding-conventions/cpp/cmake/3rdparty.cmake) cpp_cc_git_submodule(catch2 BUILD PACKAGE Catch2 REQUIRED) if(NMODL_3RDPARTY_USE_CATCH2) @@ -106,7 +108,11 @@ if(NMODL_3RDPARTY_USE_CATCH2) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ext/catch2/contrib") endif() include(Catch) -cpp_cc_git_submodule(cli11 BUILD PACKAGE CLI11 REQUIRED) +# If we're being built as a submodule of CoreNEURON then CoreNEURON may have already found/loaded a +# CLI11 submodule. +if(NOT TARGET CLI11::CLI11) + cpp_cc_git_submodule(cli11 BUILD PACKAGE CLI11 REQUIRED) +endif() # For the moment do not try and use an external Eigen. cpp_cc_git_submodule(eigen) cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED) diff --git a/cmake/nmodl/ExternalProjectHelper.cmake b/cmake/nmodl/ExternalProjectHelper.cmake index 8c1d72a996..32007e0f20 100644 --- a/cmake/nmodl/ExternalProjectHelper.cmake +++ b/cmake/nmodl/ExternalProjectHelper.cmake @@ -10,16 +10,3 @@ function(initialize_submodule path) execute_process(COMMAND git submodule update --init -- ${path} WORKING_DIRECTORY ${NMODL_PROJECT_SOURCE_DIR}) endfunction() - -# check for external project and initialize submodule if it is missing -function(add_external_project name) - find_path( - ${name}_PATH - NAMES CMakeLists.txt - PATHS "${THIRD_PARTY_DIRECTORY}/${name}") - if(NOT EXISTS ${${name}_PATH}) - initialize_submodule("${THIRD_PARTY_DIRECTORY}/${name}") - else() - message(STATUS "Sub-project : using ${name} from \"${THIRD_PARTY_DIRECTORY}/${name}\"") - endif() -endfunction() From de25a22879be37a623a64eebb754b7f5b16137de Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Tue, 7 Jun 2022 15:51:39 +0200 Subject: [PATCH 435/871] Configure sanitizers in CMake (BlueBrain/nmodl#878) * Use sanitizer helpers from hpc-coding-conventions. * Integrate sanitizer-specific configuration into CMake configuration so less magic needs to be done externally. * Add more UBSan checks + exclusions. * Make sanitizer GitHub Actions configuration simpler. NMODL Repo SHA: BlueBrain/nmodl@0e5bf0de8563a2527fbe181aacd61c8ebbae4edd --- cmake/nmodl/CMakeLists.txt | 7 ++++++ cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/CMakeLists.txt | 1 + .../transpiler/integration/CMakeLists.txt | 3 ++- test/nmodl/transpiler/unit/CMakeLists.txt | 23 +++++++++++-------- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 5d65fcd326..bb23da06d8 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -96,6 +96,12 @@ set(CODING_CONV_PREFIX NMODL) add_subdirectory(cmake/hpc-coding-conventions/cpp) include(cmake/hpc-coding-conventions/cpp/cmake/FindClangFormat.cmake) +# ============================================================================= +# Enable sanitizer support if the NMODL_SANITIZERS variable is set +# ============================================================================= +include(cmake/hpc-coding-conventions/cpp/cmake/sanitizers.cmake) +list(APPEND NMODL_EXTRA_CXX_FLAGS ${NMODL_SANITIZER_COMPILER_FLAGS}) + # ============================================================================= # Initialize external libraries as submodules # ============================================================================= @@ -237,6 +243,7 @@ if(CMAKE_BUILD_TYPE) else() set(COMPILER_FLAGS "${CMAKE_CXX_FLAGS}") endif() +string(JOIN " " COMPILER_FLAGS "${COMPILER_FLAGS}" ${NMODL_EXTRA_CXX_FLAGS}) # ============================================================================= # Build status diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 086e9156ab..5d4bcd2d41 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 086e9156abceeca26cbb2bf31663229968da1671 +Subproject commit 5d4bcd2d410e67bdc1d23d3280c08ee5c9df943b diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 06993fa2d1..7a81895ff3 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -34,6 +34,7 @@ target_link_libraries( util lexer ${NMODL_WRAPPER_LIBS}) +cpp_cc_configure_sanitizers(TARGET nmodl) # ============================================================================= # Add dependency with nmodl pytnon module (for consumer projects) diff --git a/test/nmodl/transpiler/integration/CMakeLists.txt b/test/nmodl/transpiler/integration/CMakeLists.txt index 3343748dcf..82c72a6a40 100644 --- a/test/nmodl/transpiler/integration/CMakeLists.txt +++ b/test/nmodl/transpiler/integration/CMakeLists.txt @@ -10,5 +10,6 @@ add_link_options(${NMODL_EXTRA_CXX_FLAGS}) file(GLOB modfiles "${NMODL_PROJECT_SOURCE_DIR}/test/integration/mod/*.mod") foreach(modfile ${modfiles}) get_filename_component(modfile_name "${modfile}" NAME) - add_test(NAME ${modfile_name} COMMAND ${PROJECT_BINARY_DIR}/bin/nmodl ${modfile}) + add_test(NAME ${modfile_name} COMMAND ${CMAKE_BINARY_DIR}/bin/nmodl ${modfile}) + cpp_cc_configure_sanitizers(TEST ${modfile_name}) endforeach() diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 77bab79e98..cbd83fe1bf 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -103,15 +103,14 @@ target_link_libraries(testunitlexer lexer util) target_link_libraries(testunitparser lexer test_util config) # ============================================================================= -# Use catch_discover instead of add_test for granular test report if CMAKE ver is greater than 3.9, -# else use the normal add_test method +# Use catch_discover instead of add_test for granular test result reporting. # ============================================================================= +set(test_env ${NMODL_SANITIZER_ENABLE_ENVIRONMENT}) set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") if(NOT LINK_AGAINST_PYTHON) list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") list(APPEND testvisitor_env "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/nmodl/libpywrapper${CMAKE_SHARED_LIBRARY_SUFFIX}") - endif() foreach( @@ -129,12 +128,17 @@ foreach( testunitparser) target_link_libraries(${test_name} Catch2::Catch2) - if(${test_name} STREQUAL "testvisitor") - catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT - "${testvisitor_env}") - else() - catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/") - endif() + cpp_cc_configure_sanitizers(TARGET ${test_name}) + set(env ${test_env}) + list(APPEND env ${${test_name}_env}) + # See https://github.com/catchorg/Catch2/issues/2424 + string(REPLACE ";" ";ENVIRONMENT;" _environment_vars_list "${env}") + # Catch2 doesn't set these environment variables when running the executable to discover tests + # (https://github.com/catchorg/Catch2/issues/1810). This means that in builds that enable it, we + # see errors from the LeakSanitizer during the build. These seem to be harmless because Catch2 + # does not seem to check the exit code. + catch_discover_tests(${test_name} TEST_PREFIX "${test_name}/" PROPERTIES ENVIRONMENT + "${_environment_vars_list}") endforeach() # ============================================================================= @@ -149,4 +153,5 @@ if(NMODL_ENABLE_PYTHON_BINDINGS) ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) set_tests_properties(Pybind PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) + cpp_cc_configure_sanitizers(TEST Pybind PRELOAD) endif() From 6afb87f1a188cebbea15058db96e40bb7d8796b9 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 9 Jun 2022 14:23:53 +0200 Subject: [PATCH 436/871] Remove OMP backend (BlueBrain/nmodl#858) NMODL Repo SHA: BlueBrain/nmodl@cd5728ec5505144c99812fa743f743329746df1b --- README.md | 1 - src/nmodl/codegen/CMakeLists.txt | 1 - src/nmodl/codegen/codegen_c_visitor.cpp | 123 +++------------------- src/nmodl/codegen/codegen_c_visitor.hpp | 47 --------- src/nmodl/codegen/codegen_omp_visitor.cpp | 103 ------------------ src/nmodl/codegen/codegen_omp_visitor.hpp | 89 ---------------- src/nmodl/main.cpp | 18 +--- 7 files changed, 15 insertions(+), 367 deletions(-) delete mode 100644 src/nmodl/codegen/codegen_omp_visitor.cpp delete mode 100644 src/nmodl/codegen/codegen_omp_visitor.hpp diff --git a/README.md b/README.md index a1b29e4dc8..667a4fc3fe 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,6 @@ host HOST/CPU code backends Options: --c C/C++ backend (true) - --omp C/C++ backend with OpenMP (false) --ispc C/C++ backend with ISPC (false) acc diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 23e2eca632..ea659ce1f3 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -10,7 +10,6 @@ add_library( codegen_helper_visitor.cpp codegen_info.cpp codegen_ispc_visitor.cpp - codegen_omp_visitor.cpp codegen_utils.cpp) add_dependencies(codegen lexer util visitor) target_link_libraries(codegen PRIVATE util) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 362795374d..5bce056009 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1028,16 +1028,6 @@ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { } -void CodegenCVisitor::print_channel_iteration_task_begin(BlockType type) { - // backend specific, do nothing -} - - -void CodegenCVisitor::print_channel_iteration_task_end() { - // backend specific, do nothing -} - - void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { // no tiling for cpu backend, just get loop bounds printer->add_line("int start = 0;"); @@ -1130,12 +1120,7 @@ void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType type bool CodegenCVisitor::nrn_cur_reduction_loop_required() { - return channel_task_dependency_enabled() || info.point_process; -} - - -bool CodegenCVisitor::shadow_vector_setup_required() { - return (channel_task_dependency_enabled() && !codegen_shadow_variables.empty()); + return info.point_process; } @@ -1163,23 +1148,16 @@ void CodegenCVisitor::print_rhs_d_shadow_variables() { void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { - if (channel_task_dependency_enabled()) { - auto rhs = get_variable_name("ml_rhs"); - auto d = get_variable_name("ml_d"); - printer->add_line(fmt::format("{} = rhs;", rhs)); - printer->add_line(fmt::format("{} = g;", d)); + if (info.point_process) { + printer->add_line("shadow_rhs[id] = rhs;"); + printer->add_line("shadow_d[id] = g;"); } else { - if (info.point_process) { - printer->add_line("shadow_rhs[id] = rhs;"); - printer->add_line("shadow_d[id] = g;"); - } else { - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); - print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); - } + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + print_atomic_reduction_pragma(); + printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); + print_atomic_reduction_pragma(); + printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); } } @@ -1187,22 +1165,12 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); - if (channel_task_dependency_enabled()) { - auto rhs = get_variable_name("ml_rhs"); - auto d = get_variable_name("ml_d"); + if (info.point_process) { printer->add_line("int node_id = node_index[id];"); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_rhs[node_id] {} {};", rhs_op, rhs)); + printer->add_line(fmt::format("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op)); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_d[node_id] {} {};", d_op, d)); - } else { - if (info.point_process) { - printer->add_line("int node_id = node_index[id];"); - print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op)); - print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_d[node_id] {} shadow_d[id];", d_op)); - } + printer->add_line(fmt::format("vec_d[node_id] {} shadow_d[id];", d_op)); } } @@ -1264,16 +1232,6 @@ std::string CodegenCVisitor::backend_name() const { } -bool CodegenCVisitor::block_require_shadow_update(BlockType type) { - return false; -} - - -bool CodegenCVisitor::channel_task_dependency_enabled() { - return false; -} - - bool CodegenCVisitor::optimize_ion_variable_copies() const { return optimize_ionvar_copies; } @@ -2119,15 +2077,6 @@ std::string CodegenCVisitor::process_shadow_update_statement(const ShadowUseStat return text; } - // blocks like initial doesn't use shadow update (e.g. due to wrote_conc call) - if (block_require_shadow_update(type)) { - shadow_statements.push_back(statement); - auto lhs = get_variable_name(shadow_varname(statement.lhs)); - auto rhs = statement.rhs; - auto text = fmt::format("{} = {};", lhs, rhs); - return text; - } - // return regular statement auto lhs = get_variable_name(statement.lhs); auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs); @@ -3020,12 +2969,6 @@ void CodegenCVisitor::print_mechanism_range_var_structure() { fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); } } - if (channel_task_dependency_enabled()) { - for (auto& var: codegen_shadow_variables) { - auto name = var->get_name(); - printer->add_line(fmt::format("{}* {}{};", float_type, ptr_type_qualifier(), name)); - } - } printer->end_block(); printer->add_text(";"); printer->add_newline(); @@ -3235,35 +3178,6 @@ void CodegenCVisitor::print_global_variable_setup() { } -void CodegenCVisitor::print_shadow_vector_setup() { - printer->add_newline(2); - printer->add_line("/** allocate and initialize shadow vector */"); - auto args = fmt::format("{}* inst, Memb_list* ml", instance_struct()); - printer->start_block(fmt::format("static inline void setup_shadow_vectors({}) ", args)); - if (channel_task_dependency_enabled()) { - printer->add_line("int nodecount = ml->nodecount;"); - for (auto& var: codegen_shadow_variables) { - auto name = var->get_name(); - auto type = default_float_data_type(); - auto allocation = fmt::format("({0}*) mem_alloc(nodecount, sizeof({0}))", type); - printer->add_line(fmt::format("inst->{0} = {1};", name, allocation)); - } - } - printer->end_block(3); - - printer->add_line("/** free shadow vector */"); - args = fmt::format("{}* inst", instance_struct()); - printer->start_block(fmt::format("static inline void free_shadow_vectors({}) ", args)); - if (channel_task_dependency_enabled()) { - for (auto& var: codegen_shadow_variables) { - auto name = var->get_name(); - printer->add_line(fmt::format("mem_free(inst->{});", name)); - } - } - printer->end_block(1); -} - - void CodegenCVisitor::print_setup_range_variable() { auto type = float_data_type(); printer->add_newline(2); @@ -3315,17 +3229,11 @@ void CodegenCVisitor::print_instance_variable_setup() { print_setup_range_variable(); } - if (shadow_vector_setup_required()) { - print_shadow_vector_setup(); - } printer->add_newline(2); printer->add_line("/** initialize mechanism instance variables */"); printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml) "); printer->add_line( fmt::format("{0}* inst = ({0}*) mem_alloc(1, sizeof({0}));", instance_struct())); - if (channel_task_dependency_enabled() && !codegen_shadow_variables.empty()) { - printer->add_line("setup_shadow_vectors(inst, ml);"); - } std::string stride; printer->add_line("int pnodecount = ml->_nodecount_padded;"); @@ -4555,10 +4463,7 @@ void CodegenCVisitor::print_fast_imem_calculation() { std::string rhs, d; auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); - if (channel_task_dependency_enabled()) { - rhs = get_variable_name("ml_rhs"); - d = get_variable_name("ml_d"); - } else if (info.point_process) { + if (info.point_process) { rhs = "shadow_rhs[id]"; d = "shadow_d[id]"; } else { diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index c72ec88e46..5f884dbb19 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -995,29 +995,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_backend_includes(); - /** - * Determine whether use of shadow updates at channel level required - * - * - * \param type The backend block type - * \return \c true if shadow updates are needed - */ - virtual bool block_require_shadow_update(BlockType type); - - - /** - * Determine whether this backend is performing channel execution with dependency - * \return \c true if task dependency is enabled - */ - virtual bool channel_task_dependency_enabled(); - - - /** - * Check if \c shadow\_vector\_setup function is required - */ - bool shadow_vector_setup_required(); - - /** * Check if ion variable copies should be avoided */ @@ -1209,13 +1186,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_global_variable_device_update_annotation(); - /** - * Print the setup method for allocation of shadow vectors - * - */ - void print_shadow_vector_setup(); - - /** * Print the setup method for setting matrix shadow vectors * @@ -1267,23 +1237,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_net_event_call(const ast::FunctionCall& node); - /** - * Print channel iterations from which tasks are created - * - * \note This is not used for the C backend - * \param type - */ - virtual void print_channel_iteration_task_begin(BlockType type); - - - /** - * Print end of channel iteration for task - * - * \note This is not used for the C backend - */ - virtual void print_channel_iteration_task_end(); - - /** * Print block start for tiling on channel iteration */ diff --git a/src/nmodl/codegen/codegen_omp_visitor.cpp b/src/nmodl/codegen/codegen_omp_visitor.cpp deleted file mode 100644 index 2efa82f019..0000000000 --- a/src/nmodl/codegen/codegen_omp_visitor.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#include "codegen/codegen_omp_visitor.hpp" - - -namespace nmodl { -namespace codegen { - - -/****************************************************************************************/ -/* Routines must be overloaded in backend */ -/****************************************************************************************/ - - -void CodegenOmpVisitor::print_channel_iteration_task_begin(BlockType type) { - std::string vars; - if (type == BlockType::Equation) { - vars = "start, end, node_index, indexes, voltage, vec_rhs, vec_d, inst, thread, nt"; - } else { - vars = "start, end, node_index, indexes, voltage, inst, thread, nt"; - } - printer->add_line(fmt::format("#pragma omp task default(shared) firstprivate({})", vars)); - printer->add_line("{"); - printer->increase_indent(); -} - - -void CodegenOmpVisitor::print_channel_iteration_task_end() { - printer->decrease_indent(); - printer->add_line("}"); -} - - -/* - * Depending on the backend, print loop for tiling channel iterations - */ -void CodegenOmpVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { - printer->add_line("const int TILE = 3;"); - printer->start_block("for (int block = 0; block < nodecount;) "); - printer->add_line("int start = block;"); - printer->add_line("block = (block+TILE) < nodecount ? (block+TILE) : nodecount;"); - printer->add_line("int end = block;"); - print_channel_iteration_task_begin(type); -} - - -/** - * End of tiled channel iteration block - */ -void CodegenOmpVisitor::print_channel_iteration_tiling_block_end() { - print_channel_iteration_task_end(); - printer->end_block(); - printer->add_newline(); -} - - -/** - * Depending programming model and compiler, we print compiler hint - * for parallelization. For example: - * - * #pragma ivdep - * for(int id=0; idadd_line("#pragma omp simd"); -} - - -void CodegenOmpVisitor::print_atomic_reduction_pragma() { - printer->add_line("#pragma omp atomic update"); -} - - -void CodegenOmpVisitor::print_backend_includes() { - printer->add_line("#include "); -} - - -std::string CodegenOmpVisitor::backend_name() const { - return "C-OpenMP (api-compatibility)"; -} - - -bool CodegenOmpVisitor::channel_task_dependency_enabled() { - return true; -} - - -bool CodegenOmpVisitor::block_require_shadow_update(BlockType type) { - return !(!channel_task_dependency_enabled() || type == BlockType::Initial); -} - -} // namespace codegen -} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_omp_visitor.hpp b/src/nmodl/codegen/codegen_omp_visitor.hpp deleted file mode 100644 index 70f6c02f6b..0000000000 --- a/src/nmodl/codegen/codegen_omp_visitor.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -/** - * \file - * \brief \copybrief nmodl::codegen::CodegenOmpVisitor - */ - -#include "codegen/codegen_c_visitor.hpp" - - -namespace nmodl { -namespace codegen { - -/** - * @addtogroup codegen_backends - * @{ - */ - -/** - * \class CodegenOmpVisitor - * \brief %Visitor for printing C code with OpenMP backend - */ -class CodegenOmpVisitor: public CodegenCVisitor { - protected: - /// name of the code generation backend - std::string backend_name() const override; - - - /// common includes : standard c/c++, coreneuron and backend specific - void print_backend_includes() override; - - - /// channel execution with dependency (backend specific) - bool channel_task_dependency_enabled() override; - - - /// channel iterations from which task can be created - void print_channel_iteration_task_begin(BlockType type) override; - - - /// end of task for channel iteration - void print_channel_iteration_task_end() override; - - - /// backend specific block start for tiling on channel iteration - void print_channel_iteration_tiling_block_begin(BlockType type) override; - - - /// backend specific block end for tiling on channel iteration - void print_channel_iteration_tiling_block_end() override; - - - /// ivdep like annotation for channel iterations - void print_channel_iteration_block_parallel_hint(BlockType type) override; - - - /// atomic update pragma for reduction statements - void print_atomic_reduction_pragma() override; - - - /// use of shadow updates at channel level required - bool block_require_shadow_update(BlockType type) override; - - - public: - CodegenOmpVisitor(std::string mod_file, - std::string output_dir, - std::string float_type, - const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} - - CodegenOmpVisitor(std::string mod_file, - std::stringstream& stream, - std::string float_type, - const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} -}; - -/** @} */ // end of codegen_backends - -} // namespace codegen -} // namespace nmodl diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index a10818c6c4..4feb59edd4 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -16,7 +16,6 @@ #include "codegen/codegen_compatibility_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_ispc_visitor.hpp" -#include "codegen/codegen_omp_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "pybind/pyembed.hpp" @@ -71,9 +70,6 @@ int main(int argc, const char* argv[]) { /// true if serial c code to be generated bool c_backend(true); - /// true if c code with openmp to be generated - bool omp_backend(false); - /// true if ispc code to be generated bool ispc_backend(false); @@ -185,9 +181,6 @@ int main(int argc, const char* argv[]) { auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, fmt::format("C/C++ backend ({})", c_backend)) ->ignore_case(); - host_opt - ->add_flag("--omp", omp_backend, fmt::format("C/C++ backend with OpenMP ({})", omp_backend)) - ->ignore_case(); host_opt ->add_flag("--ispc", ispc_backend, @@ -284,7 +277,7 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); // if any of the other backends is used we force the C backend to be off. - if (omp_backend || ispc_backend) { + if (ispc_backend) { c_backend = false; } @@ -553,15 +546,6 @@ int main(int argc, const char* argv[]) { visitor.visit_program(*ast); } - else if (omp_backend) { - logger->info("Running OpenMP backend code generator"); - CodegenOmpVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); - visitor.visit_program(*ast); - } - else if (c_backend) { logger->info("Running C backend code generator"); CodegenCVisitor visitor(modfile, From f9f539b87062d4ba706afc68e558f20ebae2b69a Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 15 Jun 2022 11:10:43 +0200 Subject: [PATCH 437/871] Avoid using raw pointers in InlineVisitor (BlueBrain/nmodl#845) Co-authored-by: Ioannis Magkanaris Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@eeb3d9252fd082ba82945aaa4bdcf44c65fbbbd3 --- src/nmodl/visitors/inline_visitor.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 3b6230a149..4a48949a31 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -159,7 +159,8 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, } /// get a copy of function/procedure body - auto inlined_block = callee.get_statement_block()->clone(); + auto inlined_block = std::unique_ptr( + callee.get_statement_block()->clone()); /// function definition has function name as return value. we have to rename /// it with new variable name @@ -177,7 +178,7 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, add_return_variable(*inlined_block, new_varname); } - auto statement = new ast::ExpressionStatement(inlined_block); + auto statement = new ast::ExpressionStatement(std::move(inlined_block)); if (to_replace) { replaced_statements[caller_statement] = statement; From 721a40ff68ded45d822bd9f0ac59797191750e88 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 16 Jun 2022 13:04:03 +0200 Subject: [PATCH 438/871] Check that token is not nullptr (BlueBrain/nmodl#884) NMODL Repo SHA: BlueBrain/nmodl@769fc0593900ae671fcab0813d011756af858fa6 --- src/nmodl/visitors/symtab_visitor_helper.hpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 11e0596472..477c8e453b 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -148,9 +148,13 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { void SymtabVisitor::add_model_symbol_with_property(ast::Node* node, NmodlType property) { - auto token = node->get_token(); auto name = node->get_node_name(); - auto symbol = std::make_shared(name, node, *token); + std::shared_ptr symbol; + if (node->get_token()) { // token can be nullptr + symbol = std::make_shared(name, node, *node->get_token()); + } else { + symbol = std::make_shared(name, node, ModToken{}); + } symbol->add_property(property); if (block_to_solve.find(name) != block_to_solve.cend()) { From 0824ce7c0105efe402d62ef15a2fcdbaa978c856 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 17 Jun 2022 14:20:34 +0200 Subject: [PATCH 439/871] Remove k_const (BlueBrain/nmodl#882) NMODL Repo SHA: BlueBrain/nmodl@426b508730185f8d3475c63b9d5a1aba02be9848 --- src/nmodl/codegen/codegen_c_visitor.cpp | 17 ++++++----------- src/nmodl/codegen/codegen_c_visitor.hpp | 7 ------- src/nmodl/codegen/codegen_ispc_visitor.cpp | 4 ++-- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 5bce056009..5ea7040b34 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1299,11 +1299,6 @@ void CodegenCVisitor::print_global_var_struct_decl() { printer->add_line(fmt::format("{} {}_global;", global_struct(), info.mod_suffix)); } -std::string CodegenCVisitor::k_const() { - return "const "; -} - - /****************************************************************************************/ /* printing routines for code generation */ /****************************************************************************************/ @@ -1914,7 +1909,7 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { params.emplace_back("", "IonCurVar&", "", "ionvar"); } params.emplace_back("", "double*", "", "data"); - params.emplace_back(k_const(), "Datum*", "", "indexes"); + params.emplace_back("const ", "Datum*", "", "indexes"); params.emplace_back(param_type_qualifier(), "ThreadDatum*", "", "thread"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); params.emplace_back("", "double", "", "v"); @@ -2953,17 +2948,17 @@ void CodegenCVisitor::print_mechanism_range_var_structure() { for (auto& var: codegen_float_variables) { auto name = var->get_name(); auto type = get_range_var_float_type(var); - auto qualifier = is_constant_variable(name) ? k_const() : ""; + auto qualifier = is_constant_variable(name) ? "const " : ""; printer->add_line(fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); } for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { - auto qualifier = var.is_constant ? k_const() : ""; + auto qualifier = var.is_constant ? "const " : ""; printer->add_line( fmt::format("{}{}* {}{};", qualifier, int_type, ptr_type_qualifier(), name)); } else { - auto qualifier = var.is_constant ? k_const() : ""; + auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); printer->add_line( fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); @@ -3372,10 +3367,10 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); printer->add_line( - fmt::format("{}int* {}node_index = ml->nodeindices;", k_const(), ptr_type_qualifier())); + fmt::format("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier())); printer->add_line(fmt::format("double* {}data = ml->data;", ptr_type_qualifier())); printer->add_line( - fmt::format("{}double* {}voltage = nt->_actual_v;", k_const(), ptr_type_qualifier())); + fmt::format("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier())); if (type == BlockType::Equation) { printer->add_line( diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 5f884dbb19..b17bf217a2 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -909,13 +909,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual std::string param_ptr_qualifier(); - /** - * Returns the \c const keyword - * \return \c const - */ - virtual std::string k_const(); - - /** * Prints the start of the \c coreneuron namespace */ diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 87fb9dcd97..814b9a934f 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -379,10 +379,10 @@ void CodegenIspcVisitor::print_global_function_common_code(BlockType type, printer->add_line("uniform int nodecount = ml->nodecount;"); printer->add_line("uniform int pnodecount = ml->_nodecount_padded;"); printer->add_line( - fmt::format("{}int* {}node_index = ml->nodeindices;", k_const(), ptr_type_qualifier())); + fmt::format("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier())); printer->add_line(fmt::format("double* {}data = ml->data;", ptr_type_qualifier())); printer->add_line( - fmt::format("{}double* {}voltage = nt->_actual_v;", k_const(), ptr_type_qualifier())); + fmt::format("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier())); if (type == BlockType::Equation) { printer->add_line( From 20c5abbf11743c6476da109697847662f546369a Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 22 Jun 2022 18:30:19 +0200 Subject: [PATCH 440/871] Fix for WATCH statement weight vector index for net_send (copy from BlueBrain/mod2c/pull/82) (BlueBrain/nmodl#886) - See https://github.com/BlueBrain/mod2c/pull/82 NMODL Repo SHA: BlueBrain/nmodl@d38eb74b71728e9ab3e9dc43c986d662faa3ff9e --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 5ea7040b34..3e17c6f14e 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3663,7 +3663,7 @@ void CodegenCVisitor::print_watch_check() { printer->add_text("net_send_buffering("); auto t = get_variable_name("t"); printer->add_text( - fmt::format("ml->_net_send_buffer, 0, {}, 0, {}, {}+0.0, ", tqitem, point_process, t)); + fmt::format("ml->_net_send_buffer, 0, {}, -1, {}, {}+0.0, ", tqitem, point_process, t)); watch->get_value()->accept(*this); printer->add_text(");"); printer->add_newline(); From a637f422cf1968dc68e4a30592d3511aa99d0774 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 23 Jun 2022 15:07:36 +0200 Subject: [PATCH 441/871] Minor changes to improve compiler vectorisation (BlueBrain/nmodl#887) * Add #pragma omp simd along with ivdep * Rename nrn_current() to nrn_current_mod_suffix and avoid static function - With LLVM I see that `static inline` function inhibit compiler to vectorise BREAKPOINT / nrn_cur() functions - Haven't looked into details but wonder if linkgae change causes any change in behaviour NMODL Repo SHA: BlueBrain/nmodl@d8565e111bfe0af1d757728f5656c351fb365d63 --- src/nmodl/codegen/codegen_c_visitor.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 3e17c6f14e..743825edc2 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1116,6 +1116,7 @@ void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { */ void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { printer->add_line("#pragma ivdep"); + printer->add_line("#pragma omp simd"); } @@ -4336,7 +4337,7 @@ void CodegenCVisitor::print_nrn_current(const BreakpointBlock& node) { printer->add_newline(2); print_device_method_annotation(); printer->start_block( - fmt::format("static inline double nrn_current({})", get_parameter_str(args))); + fmt::format("inline double nrn_current_{}({})", info.mod_suffix, get_parameter_str(args))); printer->add_line("double current = 0.0;"); print_statement_block(*block, false, false); for (auto& current: info.currents) { @@ -4386,8 +4387,9 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { - printer->add_line( - fmt::format("double g = nrn_current({}+0.001);", internal_method_arguments())); + printer->add_line(fmt::format("double g = nrn_current_{}({}+0.001);", + info.mod_suffix, + internal_method_arguments())); for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { @@ -4396,7 +4398,9 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { } } } - printer->add_line(fmt::format("double rhs = nrn_current({});", internal_method_arguments())); + printer->add_line(fmt::format("double rhs = nrn_current_{}({});", + info.mod_suffix, + internal_method_arguments())); printer->add_line("g = (g-rhs)/0.001;"); for (auto& ion: info.ions) { for (auto& var: ion.writes) { From 6c67c8f4c1bd4c7375c4021dec8044aee9bf0453 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 23 Jun 2022 16:06:39 +0200 Subject: [PATCH 442/871] From pybind11 2.5.0 PYBIND11_OVERLOAD => PYBIND11_OVERRIDE (BlueBrain/nmodl#885) NMODL Repo SHA: BlueBrain/nmodl@e71b0ca47970d951901e72e550e7256d6d294b80 --- src/nmodl/language/templates/pybind/pyast.hpp | 42 +++++++++---------- .../language/templates/pybind/pyvisitor.cpp | 8 ++-- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index 1b68a0a2da..d54ccf95d9 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -51,7 +51,7 @@ using namespace ast; struct PyAst: public Ast { void visit_children(visitor::Visitor& v) override { - PYBIND11_OVERLOAD_PURE(void, /// Return type + PYBIND11_OVERRIDE_PURE(void, /// Return type Ast, /// Parent class visit_children, /// Name of function in C++ (must match Python name) v /// Argument(s) @@ -59,7 +59,7 @@ struct PyAst: public Ast { } void visit_children(visitor::ConstVisitor& v) const override { - PYBIND11_OVERLOAD_PURE(void, /// Return type + PYBIND11_OVERRIDE_PURE(void, /// Return type Ast, /// Parent class visit_children, /// Name of function in C++ (must match Python name) v /// Argument(s) @@ -67,19 +67,19 @@ struct PyAst: public Ast { } void accept(visitor::Visitor& v) override { - PYBIND11_OVERLOAD_PURE(void, Ast, accept, v); + PYBIND11_OVERRIDE_PURE(void, Ast, accept, v); } void accept(visitor::ConstVisitor& v) const override { - PYBIND11_OVERLOAD_PURE(void, Ast, accept, v); + PYBIND11_OVERRIDE_PURE(void, Ast, accept, v); } Ast* clone() const override { - PYBIND11_OVERLOAD(Ast*, Ast, clone, ); + PYBIND11_OVERRIDE(Ast*, Ast, clone, ); } AstNodeType get_node_type() const override { - PYBIND11_OVERLOAD_PURE(AstNodeType, // Return type + PYBIND11_OVERRIDE_PURE(AstNodeType, // Return type Ast, // Parent class get_node_type, // Name of function in C++ (must match Python name) // No argument (trailing ,) @@ -87,67 +87,67 @@ struct PyAst: public Ast { } std::string get_node_type_name() const override { - PYBIND11_OVERLOAD_PURE(std::string, Ast, get_node_type_name, ); + PYBIND11_OVERRIDE_PURE(std::string, Ast, get_node_type_name, ); } std::string get_node_name() const override { - PYBIND11_OVERLOAD(std::string, Ast, get_node_name, ); + PYBIND11_OVERRIDE(std::string, Ast, get_node_name, ); } std::string get_nmodl_name() const override { - PYBIND11_OVERLOAD(std::string, Ast, get_nmodl_name, ); + PYBIND11_OVERRIDE(std::string, Ast, get_nmodl_name, ); } std::shared_ptr get_shared_ptr() override { - PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_shared_ptr, ); + PYBIND11_OVERRIDE(std::shared_ptr, Ast, get_shared_ptr, ); } std::shared_ptr get_shared_ptr() const override { - PYBIND11_OVERLOAD(std::shared_ptr, Ast, get_shared_ptr, ); + PYBIND11_OVERRIDE(std::shared_ptr, Ast, get_shared_ptr, ); } const ModToken* get_token() const override { - PYBIND11_OVERLOAD(const ModToken*, Ast, get_token, ); + PYBIND11_OVERRIDE(const ModToken*, Ast, get_token, ); } symtab::SymbolTable* get_symbol_table() const override { - PYBIND11_OVERLOAD(symtab::SymbolTable*, Ast, get_symbol_table, ); + PYBIND11_OVERRIDE(symtab::SymbolTable*, Ast, get_symbol_table, ); } const std::shared_ptr& get_statement_block() const override { - PYBIND11_OVERLOAD(const std::shared_ptr&, Ast, get_statement_block, ); + PYBIND11_OVERRIDE(const std::shared_ptr&, Ast, get_statement_block, ); } void set_symbol_table(symtab::SymbolTable* newsymtab) override { - PYBIND11_OVERLOAD(void, Ast, set_symbol_table, newsymtab); + PYBIND11_OVERRIDE(void, Ast, set_symbol_table, newsymtab); } void set_name(const std::string& name) override { - PYBIND11_OVERLOAD(void, Ast, set_name, name); + PYBIND11_OVERRIDE(void, Ast, set_name, name); } void negate() override { - PYBIND11_OVERLOAD(void, Ast, negate, ); + PYBIND11_OVERRIDE(void, Ast, negate, ); } bool is_ast() const noexcept override { - PYBIND11_OVERLOAD(bool, Ast, is_ast, ); + PYBIND11_OVERRIDE(bool, Ast, is_ast, ); } {% for node in nodes %} bool is_{{node.class_name | snake_case}}() const noexcept override { - PYBIND11_OVERLOAD(bool, Ast, is_{{node.class_name | snake_case}}, ); + PYBIND11_OVERRIDE(bool, Ast, is_{{node.class_name | snake_case}}, ); } {% endfor %} Ast* get_parent() const override { - PYBIND11_OVERLOAD(Ast*, Ast, get_parent, ); + PYBIND11_OVERRIDE(Ast*, Ast, get_parent, ); } void set_parent(Ast* p) override { - PYBIND11_OVERLOAD(void, Ast, set_parent, p); + PYBIND11_OVERRIDE(void, Ast, set_parent, p); } }; diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index aacbb14d7e..115b6a7f15 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -103,25 +103,25 @@ namespace py = pybind11; {% for node in nodes %} void PyVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) { - PYBIND11_OVERLOAD_PURE(void, Visitor, visit_{{ node.class_name|snake_case }}, std::ref(node)); + PYBIND11_OVERRIDE_PURE(void, Visitor, visit_{{ node.class_name|snake_case }}, std::ref(node)); } {% endfor %} {% for node in nodes %} void PyAstVisitor::visit_{{ node.class_name|snake_case }}(ast::{{ node.class_name }}& node) { - PYBIND11_OVERLOAD(void, AstVisitor, visit_{{ node.class_name|snake_case }}, std::ref(node)); + PYBIND11_OVERRIDE(void, AstVisitor, visit_{{ node.class_name|snake_case }}, std::ref(node)); } {% endfor %} {% for node in nodes %} void PyConstVisitor::visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) { - PYBIND11_OVERLOAD_PURE(void, ConstVisitor, visit_{{ node.class_name|snake_case }}, std::cref(node)); + PYBIND11_OVERRIDE_PURE(void, ConstVisitor, visit_{{ node.class_name|snake_case }}, std::cref(node)); } {% endfor %} {% for node in nodes %} void PyConstAstVisitor::visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) { - PYBIND11_OVERLOAD(void, ConstAstVisitor, visit_{{ node.class_name|snake_case }}, std::cref(node)); + PYBIND11_OVERRIDE(void, ConstAstVisitor, visit_{{ node.class_name|snake_case }}, std::cref(node)); } {% endfor %} // clang-format on From 7d2f2d8ff78be2257e05836ff192181cbf049650 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Tue, 5 Jul 2022 10:14:53 +0200 Subject: [PATCH 443/871] Require C++17 (BlueBrain/nmodl#889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * pybind11: wrap PyNmodlDriver base class. * wheels: set MACOSX_DEPLOYMENT_TARGET=10.14. Co-authored-by: Alexandru Săvulescu NMODL Repo SHA: BlueBrain/nmodl@58456d1018a177691ce4d63ac2ef436d1e535000 --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/pybind/pynmodl.cpp | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index bb23da06d8..ec66dd2ae9 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -12,7 +12,7 @@ project(NMODL LANGUAGES CXX) # ============================================================================= # CMake common project settings # ============================================================================= -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index dfbcf3068d..f03fd38feb 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -25,11 +25,9 @@ * \brief Top level nmodl Python module implementation */ - namespace py = pybind11; using namespace pybind11::literals; - namespace nmodl { /** \brief docstring of Python exposed API */ @@ -110,7 +108,6 @@ static const char* to_json = R"( } // namespace docstring - /** * \class PyNmodlDriver * \brief Class to bridge C++ NmodlDriver with Python world using pybind11 @@ -138,12 +135,13 @@ void init_visitor_module(py::module& m); void init_ast_module(py::module& m); void init_symtab_module(py::module& m); - PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; m_nmodl.attr("__version__") = nmodl::Version::NMODL_VERSION; - py::class_ nmodl_driver(m_nmodl, "NmodlDriver", nmodl::docstring::driver); + py::class_(m_nmodl, "nmodl::parser::NmodlDriver"); + py::class_ nmodl_driver( + m_nmodl, "NmodlDriver", nmodl::docstring::driver); nmodl_driver.def(py::init<>()) .def("parse_string", &nmodl::PyNmodlDriver::parse_string, From 6187475ef88cb9015edd4c45b9bcf8e2c44c20d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20S=C4=83vulescu?= Date: Mon, 11 Jul 2022 15:30:34 +0200 Subject: [PATCH 444/871] wheels: drop py36 and add py310 support (BlueBrain/nmodl#890) * update macos Python installation logic for 310 * drop Python 3.6 support * drop unused docker image * auto-cancel PR builds * bump py versions * fix: use brew flex and bison for NEURON tests on macOS * clean NRN options Co-authored-by: Olli Lupton NMODL Repo SHA: BlueBrain/nmodl@f850b894fde9abf97877054322fc926bc5d341ef --- INSTALL.md | 2 +- cmake/nmodl/CMakeLists.txt | 2 +- docker/docker-compose.yml | 22 ----------------- docker/recipe/Dockerfile | 50 -------------------------------------- docker/recipe/entrypoint | 41 ------------------------------- 5 files changed, 2 insertions(+), 115 deletions(-) delete mode 100644 docker/docker-compose.yml delete mode 100644 docker/recipe/Dockerfile delete mode 100755 docker/recipe/entrypoint diff --git a/INSTALL.md b/INSTALL.md index 8ce1c43d5f..cf42c44ac9 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -22,7 +22,7 @@ To build the project from source, a modern C++ compiler with C++14 support is ne - flex (>=2.6) - bison (>=3.0) - CMake (>=3.15) -- Python (>=3.6) +- Python (>=3.7) - Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap ### On OS X diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index ec66dd2ae9..18079c8ca4 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -166,7 +166,7 @@ endif() # Find required python packages # ============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp 3.6 REQUIRED) +find_package(PythonInterp 3.7 REQUIRED) include(cmake/hpc-coding-conventions/cpp/cmake/bbp-find-python-module.cmake) cpp_cc_find_python_module(jinja2 2.9.3 REQUIRED) cpp_cc_find_python_module(pytest 3.3.0 REQUIRED) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml deleted file mode 100644 index f28457f513..0000000000 --- a/docker/docker-compose.yml +++ /dev/null @@ -1,22 +0,0 @@ -version: '3.7' -services: - notebook: - image: bluebrain/nmodl:latest - hostname: ${HOSTNAME} - ports: - - "8888:8888" - environment: - - USER_LOGIN=${USER} - - USER_ID=${DUID} - - GROUP_ID=${DGID} - volumes: - - $PWD/notebooks:/nmodl/notebooks/my_notebooks - command: - - jupyter - - notebook - - --port=8888 - - --no-browser - - --ip=0.0.0.0 - - --allow-root - - --notebook-dir=/nmodl/notebooks - diff --git a/docker/recipe/Dockerfile b/docker/recipe/Dockerfile deleted file mode 100644 index c5660b54c2..0000000000 --- a/docker/recipe/Dockerfile +++ /dev/null @@ -1,50 +0,0 @@ -FROM alpine:3.9 AS builder - -WORKDIR /nmodl/src - -RUN apk add --update build-base gcc g++ make cmake flex flex-dev bison git python3-dev - -RUN pip3 install --trusted-host pypi.python.org jinja2 pyyaml pytest sympy - - -ARG NMODL_VERSION=master - -RUN git clone --recursive https://github.com/BlueBrain/nmodl.git && \ - cd nmodl && \ - git checkout ${NMODL_VERSION} - -WORKDIR /nmodl/src/nmodl - -RUN python3 setup.py build - -FROM alpine:3.9 - - -RUN apk add --no-cache --update shadow python3 libgfortran libstdc++ openblas && \ - apk add --no-cache --update \ - --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing gosu && \ - apk add --no-cache --virtual build-dependencies \ - build-base linux-headers openblas-dev freetype-dev \ - pkgconfig gfortran python3-dev && \ - pip3 install --no-cache-dir --trusted-host pypi.python.org \ - jinja2 pyyaml pytest sympy numpy matplotlib jupyter && \ - apk del build-dependencies && \ - rm -rf /var/cache/apk/* - -WORKDIR /usr/lib/python3.6/site-packages/nmodl - -COPY --from=builder /nmodl/src/nmodl/build/lib.linux-x86_64-3.6/nmodl . - -ENV LANG en_US.utf8 -ENV SHELL=/bin/bash - -ADD entrypoint /usr/bin/ -ENTRYPOINT ["/usr/bin/entrypoint"] - -EXPOSE 8888 -WORKDIR /nmodl/notebooks - -COPY --from=builder /nmodl/src/nmodl/docs/notebooks ./examples - -CMD ["jupyter", "notebook", "--port=8888", "--no-browser", "--ip=0.0.0.0", "--allow-root", "--notebook-dir=/nmodl/notebooks"] - diff --git a/docker/recipe/entrypoint b/docker/recipe/entrypoint deleted file mode 100755 index 9082326f80..0000000000 --- a/docker/recipe/entrypoint +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh -e - -# Create fake user and group with the same ids than -# the host user, and use its identity all along -# in the container so that all created files in -# mounted volumes belongs to the host user. -# -# https://stackoverflow.com/questions/41857462 - -if [ "x$GROUP_ID" = x -o "x$USER_ID" = x ] ;then - echo 'Error: $USER_ID and $GROUP_ID environment variable not set' >&2 - echo Abort >&2 - exit 1 -fi - -# Create fake user -CUR_GROUP=$(grep ":${GROUP_ID}:" /etc/group | cut -d: -f1) -if [ "x$CUR_GROUP" != x ] ;then - groupmod --new-name dummy "$CUR_GROUP" -else - grep -q ^dummy: /etc/group || groupadd -g $GROUP_ID dummy -fi -grep -q ^dummy: /etc/passwd || useradd -m -u $USER_ID -g $GROUP_ID dummy -s /bin/bash - - -chown -R dummy:dummy /home/dummy -chown -R dummy:dummy /nmodl - -chmod -R "u=rwX,go=rX" "/nmodl" - -# Run the given command as root if bash or sh, -# the fake user otherwise. -case "$1" in - sh|bash) - exec $@ - ;; - *) - cd /nmodl/notebooks - gosu dummy "$@" - ;; -esac From 737bfcada6a22abbe1aab52e275d4d75cc156f68 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 13 Jul 2022 23:57:06 +0200 Subject: [PATCH 445/871] Fix GPU code reductions (BlueBrain/nmodl#896) * Only print atomic pragmas for updates of rhs and d vectors on GPU if a mechanism is POINT_PROCESS * Move if-statement for point_process outside of print_atomic_reduction_pragma NMODL Repo SHA: BlueBrain/nmodl@de0553a25eebb2fb0d54da48ca82708fd523c91d --- src/nmodl/codegen/codegen_acc_visitor.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index f5b097ef2b..04c305924e 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -57,10 +57,8 @@ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType ty void CodegenAccVisitor::print_atomic_reduction_pragma() { - if (!info.artificial_cell) { - printer->add_line("nrn_pragma_acc(atomic update)"); - printer->add_line("nrn_pragma_omp(atomic update)"); - } + printer->add_line("nrn_pragma_acc(atomic update)"); + printer->add_line("nrn_pragma_omp(atomic update)"); } @@ -197,9 +195,13 @@ void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_end() { void CodegenAccVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); - print_atomic_reduction_pragma(); + if (info.point_process) { + print_atomic_reduction_pragma(); + } printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); - print_atomic_reduction_pragma(); + if (info.point_process) { + print_atomic_reduction_pragma(); + } printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); } @@ -211,9 +213,13 @@ void CodegenAccVisitor::print_fast_imem_calculation() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); printer->start_block("if (nt->nrn_fast_imem)"); - print_atomic_reduction_pragma(); + if (info.point_process) { + print_atomic_reduction_pragma(); + } printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} rhs;", rhs_op)); - print_atomic_reduction_pragma(); + if (info.point_process) { + print_atomic_reduction_pragma(); + } printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;", d_op)); printer->end_block(1); } From 2398e248bbdfc695e9e991ea614f7f36b252107b Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Fri, 22 Jul 2022 15:17:35 +0200 Subject: [PATCH 446/871] Use new formatting utility (BlueBrain/nmodl#899) * Bump hpc-coding-conventions submodule * New way to format the code: - It is not required to run CMake anymore. - Just use `cmake/hpc-coding-conventions/bin/format` utility. NMODL Repo SHA: BlueBrain/nmodl@884497f6ec3da20059f794fb0c2dcaefa1b383e8 --- CONTRIBUTING.md | 10 +++++++--- cmake/nmodl/CMakeLists.txt | 14 -------------- cmake/nmodl/hpc-coding-conventions | 2 +- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 873a95847c..f31cebeb6a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,9 +47,13 @@ When you wish to contribute to the code base, please consider the following guid git checkout -b my-fix-branch master ``` * Create your patch, **including appropriate test cases**. -* Enable `NMODL_FORMATTING` and `NMODL_PRECOMMIT` CMake variables - to ensure that your change follows coding conventions of this project. - Please see [README.md](./README.md) for more information. +* Enable `NMODL_TEST_FORMATTING` CMake variable + to ensure that your change follows the coding conventions of this project when running the tests. + The formatting utility can also be used directly: + * to format CMake and C++ files: `cmake/hpc-coding-conventions/bin/format` + * to format only the C++ files: `cmake/hpc-coding-conventions/bin/format --lang c++` + * to format a subset of files or directories: `cmake/hpc-coding-conventions/bin/format src/codegen/ src/main.cpp` + * to check the formatting of CMake files: `cmake/hpc-coding-conventions/bin/format --dry-run --lang cmake` * Run the full test suite, and ensure that all tests pass. * Commit your changes using a descriptive commit message. diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 18079c8ca4..54bb3d0f75 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -81,20 +81,12 @@ project( # ============================================================================= # HPC Coding Conventions # ============================================================================= -set(NMODL_ClangFormat_EXCLUDES_RE - "ext/.*$$" "src/language/templates/.*$$" - CACHE STRING "list of regular expressions to exclude C/C++ files from formatting" FORCE) -set(NMODL_CMakeFormat_EXCLUDES_RE - "ext/.*$$" "src/language/templates/.*$$" - CACHE STRING "list of regular expressions to exclude CMake files from formatting" FORCE) - # initialize submodule of coding conventions under cmake if(NOT EXISTS "${PROJECT_SOURCE_DIR}/cmake/hpc-coding-conventions/cpp/CMakeLists.txt") initialize_submodule("${PROJECT_SOURCE_DIR}/cmake/hpc-coding-conventions") endif() set(CODING_CONV_PREFIX NMODL) add_subdirectory(cmake/hpc-coding-conventions/cpp) -include(cmake/hpc-coding-conventions/cpp/cmake/FindClangFormat.cmake) # ============================================================================= # Enable sanitizer support if the NMODL_SANITIZERS variable is set @@ -271,12 +263,6 @@ message(STATUS "Python Bindings | ${NMODL_ENABLE_PYTHON_BINDINGS}") message(STATUS "Flex | ${FLEX_EXECUTABLE}") message(STATUS "Bison | ${BISON_EXECUTABLE}") message(STATUS "Python | ${PYTHON_EXECUTABLE}") -if(NMODL_CLANG_FORMAT) - message(STATUS "Clang Format | ${ClangFormat_EXECUTABLE}") -endif() -if(NMODL_CMAKE_FORMAT) - message(STATUS "Cmake Format | ${CMakeFormat_EXECUTABLE}") -endif() message(STATUS "--------------+--------------------------------------------------------------") message(STATUS " See documentation : https://github.com/BlueBrain/nmodl/") message(STATUS "--------------+--------------------------------------------------------------") diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 5d4bcd2d41..93be2b58d6 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 5d4bcd2d410e67bdc1d23d3280c08ee5c9df943b +Subproject commit 93be2b58d67d70b73a600bcfaebd89e6d25daa2a From a55c23596ad593ab107b9cec2d7498aee7efe6b9 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 16 Aug 2022 14:36:15 +0200 Subject: [PATCH 447/871] Add token mapping for REPRESENTS (BlueBrain/nmodl#906) NMODL Repo SHA: BlueBrain/nmodl@c0033416788b440f25b13e11b3823e25458d369a --- src/nmodl/lexer/token_mapping.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 682d5f7f6c..d75b0009be 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -107,6 +107,7 @@ const static std::map keywords = { {"RANGE", Token::RANGE}, {"USEION", Token::USEION}, {"READ", Token::READ}, + {"REPRESENTS", Token::REPRESENTS}, {"WRITE", Token::WRITE}, {"VALENCE", Token::VALENCE}, {"CHARGE", Token::VALENCE}, From 8155ef05e6a4ba55f449705ecc3fe2751e12a7ea Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 18 Aug 2022 14:40:36 +0200 Subject: [PATCH 448/871] clang-tidy (BlueBrain/nmodl#902) * Run and fix a set of clang-tidy checks. * Bump submodule past hpc-coding-conventionsBlueBrain/nmodl#141. NMODL Repo SHA: BlueBrain/nmodl@60bf8c3d883601552b388d596908d59253fbbd17 --- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 4 +- src/nmodl/codegen/codegen_c_visitor.cpp | 69 +++++++++------- src/nmodl/codegen/codegen_c_visitor.hpp | 18 ++--- .../codegen/codegen_compatibility_visitor.cpp | 15 ++-- src/nmodl/codegen/codegen_cuda_visitor.cpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 25 +++--- src/nmodl/codegen/codegen_helper_visitor.hpp | 2 +- src/nmodl/codegen/codegen_info.cpp | 81 +++++++------------ src/nmodl/codegen/codegen_ispc_visitor.cpp | 16 ++-- .../templates/ast/node_class.template | 2 + src/nmodl/lexer/modtoken.cpp | 3 +- src/nmodl/lexer/modtoken.hpp | 2 +- src/nmodl/lexer/nmodl_utils.cpp | 3 +- src/nmodl/lexer/token_mapping.cpp | 3 +- src/nmodl/main.cpp | 12 ++- src/nmodl/parser/c11_driver.cpp | 4 +- src/nmodl/parser/c11_driver.hpp | 4 +- src/nmodl/parser/diffeq_driver.cpp | 8 +- src/nmodl/parser/diffeq_driver.hpp | 24 +++--- src/nmodl/parser/nmodl_driver.hpp | 11 +-- src/nmodl/parser/unit_driver.hpp | 4 +- src/nmodl/printer/code_printer.cpp | 4 +- src/nmodl/printer/json_printer.cpp | 9 +-- src/nmodl/printer/json_printer.hpp | 2 +- src/nmodl/pybind/pynmodl.cpp | 18 ++--- src/nmodl/pybind/wrapper.cpp | 1 + src/nmodl/symtab/symbol_properties.cpp | 1 + src/nmodl/symtab/symbol_table.cpp | 17 ++-- src/nmodl/symtab/symbol_table.hpp | 2 +- src/nmodl/units/units.cpp | 31 +++++-- src/nmodl/units/units.hpp | 2 +- src/nmodl/utils/common_utils.cpp | 25 +++--- src/nmodl/utils/logger.cpp | 2 + src/nmodl/utils/perf_stat.cpp | 4 +- src/nmodl/utils/perf_stat.hpp | 4 +- src/nmodl/utils/table_data.cpp | 34 +++++--- src/nmodl/visitors/defuse_analyze_visitor.cpp | 2 +- src/nmodl/visitors/inline_visitor.cpp | 25 +++--- src/nmodl/visitors/inline_visitor.hpp | 4 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 16 ++-- src/nmodl/visitors/localize_visitor.cpp | 2 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 8 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 6 +- src/nmodl/visitors/perf_visitor.cpp | 12 +-- src/nmodl/visitors/perf_visitor.hpp | 6 +- .../visitors/semantic_analysis_visitor.cpp | 4 +- src/nmodl/visitors/steadystate_visitor.cpp | 2 - .../visitors/sympy_conductance_visitor.cpp | 9 ++- .../sympy_replace_solutions_visitor.cpp | 31 +++---- src/nmodl/visitors/sympy_solver_visitor.cpp | 67 +++++++++------ src/nmodl/visitors/sympy_solver_visitor.hpp | 13 +-- src/nmodl/visitors/visitor_utils.cpp | 12 ++- .../unit/codegen/codegen_c_visitor.cpp | 2 +- test/nmodl/transpiler/unit/lexer/tokens.cpp | 10 +-- .../transpiler/unit/modtoken/modtoken.cpp | 2 + test/nmodl/transpiler/unit/newton/newton.cpp | 36 ++++----- test/nmodl/transpiler/unit/parser/parser.cpp | 3 +- .../transpiler/unit/symtab/symbol_table.cpp | 4 +- test/nmodl/transpiler/unit/units/parser.cpp | 2 +- .../unit/utils/nmodl_constructs.cpp | 6 +- .../unit/utils/nmodl_constructs.hpp | 6 +- .../transpiler/unit/utils/test_utils.cpp | 9 ++- .../unit/visitor/global_to_range.cpp | 1 - .../transpiler/unit/visitor/ispc_rename.cpp | 2 - .../unit/visitor/local_to_assigned.cpp | 1 - test/nmodl/transpiler/unit/visitor/lookup.cpp | 2 - test/nmodl/transpiler/unit/visitor/units.cpp | 10 ++- 68 files changed, 419 insertions(+), 366 deletions(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 93be2b58d6..be06dee210 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 93be2b58d67d70b73a600bcfaebd89e6d25daa2a +Subproject commit be06dee210c01a86b1510a6618d0df0a1c1b8bc1 diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 04c305924e..b7d56cc1ff 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -134,7 +134,7 @@ void CodegenAccVisitor::print_net_send_buffering_grow() { // can not grow buffer during gpu execution } -void CodegenAccVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { +void CodegenAccVisitor::print_eigen_linear_solver(const std::string& /* float_type */, int N) { if (N <= 4) { printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { @@ -275,7 +275,7 @@ void CodegenAccVisitor::print_global_variable_device_update_annotation() { std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& variable, - const std::string& type) const { + const std::string& /* type */) const { if (info.artificial_cell) { return variable; } diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 743825edc2..2ca65c2940 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -42,7 +42,6 @@ using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; using SymbolType = std::shared_ptr; -using nmodl::utils::UseNumbersInString; namespace codegen_utils = nmodl::codegen::utils; /****************************************************************************************/ @@ -109,7 +108,7 @@ void CodegenCVisitor::visit_unit(const ast::Unit& node) { } -void CodegenCVisitor::visit_prime_name(const PrimeName& node) { +void CodegenCVisitor::visit_prime_name(const PrimeName& /* node */) { throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); } @@ -330,7 +329,7 @@ void CodegenCVisitor::visit_update_dt(const ast::UpdateDt& node) { * statement and hence we have to check inner expression. It's also true * for the initial block defined inside net receive block. */ -bool CodegenCVisitor::statement_to_skip(const Statement& node) const { +bool CodegenCVisitor::statement_to_skip(const Statement& node) { // clang-format off if (node.is_unit_state() || node.is_line_comment() @@ -467,7 +466,7 @@ std::string CodegenCVisitor::format_float_string(const std::string& s_value) { * block can appear as statement using expression statement which need to * be inspected. */ -bool CodegenCVisitor::need_semicolon(Statement* node) const { +bool CodegenCVisitor::need_semicolon(Statement* node) { // clang-format off if (node->is_if_statement() || node->is_else_if_statement() @@ -638,7 +637,7 @@ std::vector CodegenCVisitor::ion_read_statements_optimized(BlockTyp return statements; } - +// NOLINTNEXTLINE(readability-function-cognitive-complexity) std::vector CodegenCVisitor::ion_write_statements(BlockType type) { std::vector statements; for (const auto& ion: info.ions) { @@ -760,6 +759,7 @@ bool CodegenCVisitor::is_constant_variable(const std::string& name) const { /** * \details Once variables are populated, update index semantics to register with coreneuron */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void CodegenCVisitor::update_index_semantics() { int index = 0; info.semantics.clear(); @@ -882,6 +882,7 @@ std::vector CodegenCVisitor::get_float_variables() { * - read ion variables are read only * - style_ionname is index / offset */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) std::vector CodegenCVisitor::get_int_variables() { std::vector variables; if (info.point_process) { @@ -905,7 +906,7 @@ std::vector CodegenCVisitor::get_int_variables() { const std::string name = naming::ION_VARNAME_PREFIX + var; variables.emplace_back(make_symbol(name)); variables.back().is_constant = true; - ion_vars[name] = variables.size() - 1; + ion_vars[name] = static_cast(variables.size() - 1); } /// symbol for di_ion_dv var @@ -966,7 +967,7 @@ std::vector CodegenCVisitor::get_int_variables() { variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), false, false, true); variables.back().is_constant = true; } - info.tqitem_index = variables.size() - 1; + info.tqitem_index = static_cast(variables.size() - 1); } /** @@ -1028,7 +1029,7 @@ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { } -void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { +void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType /* type */) { // no tiling for cpu backend, just get loop bounds printer->add_line("int start = 0;"); printer->add_line("int end = nodecount;"); @@ -1114,7 +1115,7 @@ void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { * for(int id = 0; id < nodecount; id++) { * \endcode */ -void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType type) { +void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */) { printer->add_line("#pragma ivdep"); printer->add_line("#pragma omp simd"); } @@ -1305,7 +1306,7 @@ void CodegenCVisitor::print_global_var_struct_decl() { /****************************************************************************************/ -void CodegenCVisitor::visit_watch_statement(const ast::WatchStatement& node) { +void CodegenCVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { printer->add_text(fmt::format("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)", current_watch_statement++)); } @@ -1753,8 +1754,9 @@ bool is_functor_const(const ast::StatementBlock& variable_block, const auto& chain = v.analyze(complete_block, variable->get_node_name()); is_functor_const = !(chain.eval() == DUState::D || chain.eval() == DUState::LD || chain.eval() == DUState::CD); - if (!is_functor_const) + if (!is_functor_const) { break; + } } return is_functor_const; @@ -1918,12 +1920,12 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { } -std::string CodegenCVisitor::external_method_arguments() const { +std::string CodegenCVisitor::external_method_arguments() { return "id, pnodecount, data, indexes, thread, nt, v"; } -std::string CodegenCVisitor::external_method_parameters(bool table) const { +std::string CodegenCVisitor::external_method_parameters(bool table) { if (table) { return "int id, int pnodecount, double* data, Datum* indexes, " "ThreadDatum* thread, NrnThread* nt, int tml_id"; @@ -1986,7 +1988,7 @@ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { * @todo : this is still ad-hoc and requires re-implementation to * handle it more elegantly. */ -std::string CodegenCVisitor::process_verbatim_text(std::string text) { +std::string CodegenCVisitor::process_verbatim_text(std::string const& text) { parser::CDriver driver; driver.scan_string(text); auto tokens = driver.all_tokens(); @@ -2002,7 +2004,7 @@ std::string CodegenCVisitor::process_verbatim_text(std::string text) { auto name = process_verbatim_token(token); if (token == (std::string("_") + naming::TQITEM_VARIABLE)) { - name = "&" + name; + name.insert(0, 1, '&'); } if (token == "_STRIDE") { name = "pnodecount+id"; @@ -2027,13 +2029,13 @@ std::string CodegenCVisitor::register_mechanism_arguments() const { std::pair CodegenCVisitor::read_ion_variable_name( - const std::string& name) const { + const std::string& name) { return {name, naming::ION_VARNAME_PREFIX + name}; } std::pair CodegenCVisitor::write_ion_variable_name( - const std::string& name) const { + const std::string& name) { return {naming::ION_VARNAME_PREFIX + name, name}; } @@ -2066,7 +2068,7 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, * to queue that will be used in reduction queue. */ std::string CodegenCVisitor::process_shadow_update_statement(const ShadowUseStatement& statement, - BlockType type) { + BlockType /* type */) { // when there is no operator or rhs then that statement doesn't need shadow update if (statement.op.empty() && statement.rhs.empty()) { auto text = statement.lhs + ";"; @@ -2287,7 +2289,7 @@ std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol) cons } -std::string CodegenCVisitor::ion_shadow_variable_name(const SymbolType& symbol) const { +std::string CodegenCVisitor::ion_shadow_variable_name(const SymbolType& symbol) { return fmt::format("inst->{}[id]", symbol->get_name()); } @@ -2374,7 +2376,7 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use void CodegenCVisitor::print_backend_info() { - time_t tr; + time_t tr{}; time(&tr); auto date = std::string(asctime(localtime(&tr))); auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; @@ -2438,6 +2440,7 @@ void CodegenCVisitor::print_coreneuron_includes() { * Note that static variables are already initialized to 0. We do the * same for some variables to keep same code as neuron. */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void CodegenCVisitor::print_mechanism_global_var_structure() { const auto qualifier = global_var_struct_type_qualifier(); @@ -2678,13 +2681,15 @@ void CodegenCVisitor::print_global_variables_for_hoc() { */ static size_t get_register_type_for_ba_block(const ast::Block* block) { size_t register_type = 0; - BAType ba_type; + BAType ba_type{}; /// before block have value 10 and after block 20 if (block->is_before_block()) { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) register_type = 10; ba_type = dynamic_cast(block)->get_bablock()->get_type()->get_value(); } else { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) register_type = 20; ba_type = dynamic_cast(block)->get_bablock()->get_type()->get_value(); @@ -2725,6 +2730,7 @@ static size_t get_register_type_for_ba_block(const ast::Block* block) { * - We assume net receive buffer is on. This is because generated code is * compatible for cpu as well as gpu target. */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void CodegenCVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); @@ -2764,9 +2770,12 @@ void CodegenCVisitor::print_mechanism_register() { // types for ion for (const auto& ion: info.ions) { - const auto& type = get_variable_name(ion.name + "_type"); + std::string line = get_variable_name(ion.name + "_type"); const auto& name = add_escape_quote(ion.name + "_ion"); - printer->add_line(type + " = nrn_get_mechtype(" + name + ");"); + line.append(" = nrn_get_mechtype("); + line.append(name); + line.append(");"); + printer->add_line(line); } printer->add_newline(); @@ -3036,7 +3045,7 @@ void CodegenCVisitor::print_global_variable_device_update_annotation() { // nothing for cpu } - +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void CodegenCVisitor::print_global_variable_setup() { std::vector allocated_variables; @@ -3263,7 +3272,7 @@ void CodegenCVisitor::print_instance_variable_setup() { for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); std::string variable = name; - std::string type = ""; + std::string type; if (var.is_index || var.is_integer) { variable = "ml->pdata"; type = int_type_pointer; @@ -3327,7 +3336,7 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { // initial block if (node != nullptr) { const auto& block = node->get_statement_block(); - print_statement_block(*block.get(), false, false); + print_statement_block(*block, false, false); } // write ion statements @@ -3535,7 +3544,7 @@ void CodegenCVisitor::print_nrn_constructor() { print_global_function_common_code(BlockType::Constructor); if (info.constructor_node != nullptr) { const auto& block = info.constructor_node->get_statement_block(); - print_statement_block(*block.get(), false, false); + print_statement_block(*block, false, false); } printer->add_line("#endif"); printer->end_block(1); @@ -3547,7 +3556,7 @@ void CodegenCVisitor::print_nrn_destructor() { print_global_function_common_code(BlockType::Destructor); if (info.destructor_node != nullptr) { const auto& block = info.destructor_node->get_statement_block(); - print_statement_block(*block.get(), false, false); + print_statement_block(*block, false, false); } printer->add_line("#endif"); printer->end_block(1); @@ -3741,7 +3750,7 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { - auto arguments = node.get_arguments(); + auto const& arguments = node.get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; std::string pnt = "pnt"; @@ -3777,7 +3786,7 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { abort(); } - auto arguments = node.get_arguments(); + auto const& arguments = node.get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "-1"; std::string pnt = "pnt"; diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index b17bf217a2..244497a727 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -501,7 +501,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param node The AST Statement node to check * \return \c true if this Statement is to be skipped */ - bool statement_to_skip(const ast::Statement& node) const; + static bool statement_to_skip(const ast::Statement& node); /** @@ -509,7 +509,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param node The AST Statement node to check * \return \c true if this Statement requires a semicolon */ - bool need_semicolon(ast::Statement* node) const; + static bool need_semicolon(ast::Statement* node); /** @@ -624,7 +624,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param symbol The symbol of a variable for which we want to obtain its name * \return The C string representing the access to the shadow variable */ - std::string ion_shadow_variable_name(const SymbolType& symbol) const; + static std::string ion_shadow_variable_name(const SymbolType& symbol); /** @@ -699,7 +699,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param params The parameters that should be concatenated into the function parameter * declaration \return The string representing the declaration of function parameters */ - std::string get_parameter_str(const ParamVector& params); + static std::string get_parameter_str(const ParamVector& params); /** @@ -729,7 +729,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param text The verbatim code to be processed * \return The code with all variables renamed as needed */ - std::string process_verbatim_text(std::string text); + std::string process_verbatim_text(std::string const& text); /** @@ -778,7 +778,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param name The ion variable name * \return The ion read variable name */ - std::pair read_ion_variable_name(const std::string& name) const; + static std::pair read_ion_variable_name(const std::string& name); /** @@ -786,7 +786,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param name The ion variable name * \return The ion write variable name */ - std::pair write_ion_variable_name(const std::string& name) const; + static std::pair write_ion_variable_name(const std::string& name); /** @@ -819,7 +819,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - std::string external_method_arguments() const; + static std::string external_method_arguments(); /** @@ -831,7 +831,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param table * \return A string representing the parameters of the function */ - std::string external_method_parameters(bool table = false) const; + static std::string external_method_parameters(bool table = false); /** diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 09a151aec8..b1242d6b71 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -16,8 +16,6 @@ namespace nmodl { namespace codegen { -using symtab::syminfo::NmodlType; - const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( {{AstNodeType::MATCH_BLOCK, @@ -39,13 +37,14 @@ const std::map std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( - ast::Ast& node, + ast::Ast& /* node */, const std::shared_ptr& ast_node) { auto solve_block_ast_node = std::dynamic_pointer_cast(ast_node); std::stringstream unhandled_method_error_message; auto method = solve_block_ast_node->get_method(); - if (method == nullptr) + if (!method) { return ""; + } auto unhandled_solver_method = handled_solvers.find(method->get_node_name()) == handled_solvers.end(); if (unhandled_solver_method) { @@ -58,6 +57,7 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl return unhandled_method_error_message.str(); } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_global_var( ast::Ast& node, const std::shared_ptr& ast_node) { @@ -73,6 +73,7 @@ std::string CodegenCompatibilityVisitor::return_error_global_var( return error_message_global_var.str(); } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_param_var( ast::Ast& node, const std::shared_ptr& ast_node) { @@ -88,9 +89,10 @@ std::string CodegenCompatibilityVisitor::return_error_param_var( return error_message_global_var.str(); } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( ast::Ast& node, - const std::shared_ptr& ast_node) { + const std::shared_ptr& /* ast_node */) { std::stringstream error_message_no_bbcore_read_write; const auto& verbatim_nodes = collect_nodes(node, {AstNodeType::VERBATIM}); auto found_bbcore_read = false; @@ -154,8 +156,9 @@ bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) { std::string line; std::istringstream ss_stringstream(ss.str()); while (std::getline(ss_stringstream, line)) { - if (!line.empty()) + if (!line.empty()) { logger->error(fmt::format("Code Incompatibility :: {}", line)); + } } return true; } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 6daa06fefe..48d1f6bade 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -125,7 +125,7 @@ void CodegenCudaVisitor::print_fast_imem_calculation() { * For GPU backend its thread id less than total channel instances. Below we * assume we launch 1-d grid. */ -void CodegenCudaVisitor::print_channel_iteration_block_begin(BlockType type) { +void CodegenCudaVisitor::print_channel_iteration_block_begin(BlockType /* type */) { printer->add_line("int id = blockIdx.x * blockDim.x + threadIdx.x;"); printer->start_block("if (id < end) "); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 313a44c809..e70b150727 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -44,7 +44,7 @@ using symtab::syminfo::Status; * Note that variables in double array do not need this transformation * and it seems like they should just follow definition order. */ -void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& symbols) const { +void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& symbols) { /// first sort by global id to get in reverse order std::sort(symbols.begin(), symbols.end(), @@ -64,6 +64,7 @@ void CodegenHelperVisitor::sort_with_mod2c_symbol_order(std::vector& /** * Find all ions used in mod file */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void CodegenHelperVisitor::find_ion_variables(const ast::Program& node) { // collect all use ion statements const auto& ion_nodes = collect_nodes(node, {AstNodeType::USEION}); @@ -256,7 +257,7 @@ void CodegenHelperVisitor::find_non_range_variables() { /// find number of prime variables and total size auto primes = psymtab->get_variables_with_properties(NmodlType::prime_name); - info.num_primes = primes.size(); + info.num_primes = static_cast(primes.size()); for (auto& variable: primes) { info.primes_size += variable->get_length(); } @@ -474,7 +475,7 @@ void CodegenHelperVisitor::visit_suffix(const Suffix& node) { } -void CodegenHelperVisitor::visit_electrode_current(const ElectrodeCurrent& node) { +void CodegenHelperVisitor::visit_electrode_current(const ElectrodeCurrent& /* node */) { info.electrode_current = true; } @@ -504,7 +505,7 @@ void CodegenHelperVisitor::visit_destructor_block(const DestructorBlock& node) { void CodegenHelperVisitor::visit_net_receive_block(const NetReceiveBlock& node) { under_net_receive_block = true; info.net_receive_node = &node; - info.num_net_receive_parameters = node.get_parameters().size(); + info.num_net_receive_parameters = static_cast(node.get_parameters().size()); node.visit_children(*this); under_net_receive_block = false; } @@ -655,12 +656,12 @@ void CodegenHelperVisitor::visit_binary_expression(const BinaryExpression& node) } -void CodegenHelperVisitor::visit_bbcore_pointer(const BbcorePointer& node) { +void CodegenHelperVisitor::visit_bbcore_pointer(const BbcorePointer& /* node */) { info.bbcore_pointer_used = true; } -void CodegenHelperVisitor::visit_watch(const ast::Watch& node) { +void CodegenHelperVisitor::visit_watch(const ast::Watch& /* node */) { info.watch_count++; } @@ -671,12 +672,12 @@ void CodegenHelperVisitor::visit_watch_statement(const ast::WatchStatement& node } -void CodegenHelperVisitor::visit_for_netcon(const ast::ForNetcon& node) { +void CodegenHelperVisitor::visit_for_netcon(const ast::ForNetcon& /* node */) { info.for_netcon_used = true; } -void CodegenHelperVisitor::visit_table_statement(const ast::TableStatement& node) { +void CodegenHelperVisitor::visit_table_statement(const ast::TableStatement& /* node */) { info.table_count++; table_statement_used = true; } @@ -704,19 +705,19 @@ CodegenInfo CodegenHelperVisitor::analyze(const ast::Program& node) { return info; } -void CodegenHelperVisitor::visit_linear_block(const ast::LinearBlock& node) { +void CodegenHelperVisitor::visit_linear_block(const ast::LinearBlock& /* node */) { info.vectorize = false; } -void CodegenHelperVisitor::visit_non_linear_block(const ast::NonLinearBlock& node) { +void CodegenHelperVisitor::visit_non_linear_block(const ast::NonLinearBlock& /* node */) { info.vectorize = false; } -void CodegenHelperVisitor::visit_discrete_block(const ast::DiscreteBlock& node) { +void CodegenHelperVisitor::visit_discrete_block(const ast::DiscreteBlock& /* node */) { info.vectorize = false; } -void CodegenHelperVisitor::visit_partial_block(const ast::PartialBlock& node) { +void CodegenHelperVisitor::visit_partial_block(const ast::PartialBlock& /* node */) { info.vectorize = false; } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 1abd7bdd42..c177a080ba 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -73,7 +73,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void find_table_variables(); void find_range_variables(); void find_non_range_variables(); - void sort_with_mod2c_symbol_order(std::vector& symbols) const; + static void sort_with_mod2c_symbol_order(std::vector& symbols); public: CodegenHelperVisitor() = default; diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index d2c433f649..fb96b26e59 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -19,38 +19,29 @@ using visitor::VarUsageVisitor; /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() const { - for (const auto& ion: ions) { - if (!ion.writes.empty()) { - return true; - } - } - return false; + return std::any_of(ions.begin(), ions.end(), [](auto const& ion) { + return !ion.writes.empty(); + }); } /// if given variable is ion write variable bool CodegenInfo::is_ion_write_variable(const std::string& name) const { - for (const auto& ion: ions) { - for (auto& var: ion.writes) { - if (var == name) { - return true; - } - } - } - return false; + return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { + return std::any_of(ion.writes.begin(), ion.writes.end(), [&name](auto const& var) { + return var == name; + }); + }); } /// if given variable is ion read variable bool CodegenInfo::is_ion_read_variable(const std::string& name) const { - for (const auto& ion: ions) { - for (auto& var: ion.reads) { - if (var == name) { - return true; - } - } - } - return false; + return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { + return std::any_of(ion.reads.begin(), ion.reads.end(), [&name](auto const& var) { + return var == name; + }); + }); } @@ -62,42 +53,30 @@ bool CodegenInfo::is_ion_variable(const std::string& name) const { /// if a current (ionic or non-specific) bool CodegenInfo::is_current(const std::string& name) const { - for (auto& var: currents) { - if (var == name) { - return true; - } - } - return false; + return std::any_of(currents.begin(), currents.end(), [&name](auto const& var) { + return var == name; + }); } /// true is a given variable name if a ionic current /// (i.e. currents excluding non-specific current) bool CodegenInfo::is_ionic_current(const std::string& name) const { - for (const auto& ion: ions) { - if (ion.is_ionic_current(name) == true) { - return true; - } - } - return false; + return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { + return ion.is_ionic_current(name); + }); } /// true if given variable name is a ionic concentration bool CodegenInfo::is_ionic_conc(const std::string& name) const { - for (const auto& ion: ions) { - if (ion.is_ionic_conc(name) == true) { - return true; - } - } - return false; + return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { + return ion.is_ionic_conc(name); + }); } bool CodegenInfo::function_uses_table(std::string& name) const { - for (auto& function: functions_with_table) { - if (name == function->get_node_name()) { - return true; - } - } - return false; + return std::any_of(functions_with_table.begin(), + functions_with_table.end(), + [&name](auto const& function) { return name == function->get_node_name(); }); } /** @@ -122,13 +101,9 @@ bool CodegenInfo::nrn_state_has_eigen_solver_block() const { * @return true if voltage variable b is used otherwise false */ bool CodegenInfo::is_voltage_used_by_watch_statements() const { - for (const auto& statement: watch_statements) { - auto v_used = VarUsageVisitor().variable_used(*statement, "v"); - if (v_used) { - return true; - } - } - return false; + return std::any_of(watch_statements.begin(), watch_statements.end(), [](auto const& statement) { + return VarUsageVisitor{}.variable_used(*statement, "v"); + }); } } // namespace codegen diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 814b9a934f..1d03a7261e 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -197,7 +197,7 @@ std::string CodegenIspcVisitor::backend_name() const { } -void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType type) { +void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType /* type */) { // no tiling for ispc backend but make sure variables are declared as uniform printer->add_line("int uniform start = 0;"); printer->add_line("int uniform end = nodecount;"); @@ -209,7 +209,7 @@ void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType ty * * Use ispc foreach loop */ -void CodegenIspcVisitor::print_channel_iteration_block_begin(BlockType type) { +void CodegenIspcVisitor::print_channel_iteration_block_begin(BlockType /* type */) { printer->start_block("foreach (id = start ... end)"); } @@ -341,7 +341,7 @@ void CodegenIspcVisitor::print_backend_namespace_stop() { CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( - const std::string& arg_qualifier) { + const std::string& /* arg_qualifier */) { auto params = ParamVector(); params.emplace_back(param_type_qualifier(), fmt::format("{}*", instance_struct()), @@ -366,7 +366,7 @@ void CodegenIspcVisitor::print_global_function_common_code(BlockType type, const std::string& function_name) { // If we are printing the cpp file, we have to use the c version of this function if (wrapper_codegen) { - return CodegenCVisitor::print_global_function_common_code(type); + return CodegenCVisitor::print_global_function_common_code(type, function_name); } std::string method = compute_method_name(type); @@ -398,16 +398,12 @@ void CodegenIspcVisitor::print_global_function_common_code(BlockType type, void CodegenIspcVisitor::print_compute_functions() { for (const auto& function: info.functions) { - if (!program_symtab->lookup(function->get_node_name()) - .get() - ->has_all_status(Status::inlined)) { + if (!program_symtab->lookup(function->get_node_name())->has_all_status(Status::inlined)) { print_function(*function); } } for (const auto& procedure: info.procedures) { - if (!program_symtab->lookup(procedure->get_node_name()) - .get() - ->has_all_status(Status::inlined)) { + if (!program_symtab->lookup(procedure->get_node_name())->has_all_status(Status::inlined)) { print_procedure(*procedure); } } diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index 66e6fd7e93..736601591d 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -101,9 +101,11 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * @return pointer to the clone/copy of the current node */ + // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) {{ virtual(node) }} {{ node.class_name }}* clone() const override { return new {{ node.class_name }}(*this); } + // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) /// \name Getters /// \{ diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 2b4506356e..2f6c045685 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -24,11 +24,12 @@ std::string ModToken::position() const { } std::ostream& operator<<(std::ostream& stream, const ModToken& mt) { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) stream << std::setw(15) << mt.name << " at [" << mt.position() << "]"; return stream << " type " << mt.token; } -ModToken operator+(ModToken adder1, ModToken adder2) { +ModToken operator+(ModToken const& adder1, ModToken const& adder2) { LocationType sum_pos = adder1.pos + adder2.pos; ModToken sum(adder1.name, adder1.token, sum_pos); diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index ad052edf44..41365473ea 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -153,7 +153,7 @@ class ModToken { * (a + b) at [118.9-121.5] * \endcode */ - friend ModToken operator+(ModToken adder1, ModToken adder2); + friend ModToken operator+(ModToken const& adder1, ModToken const& adder2); }; /** @} */ // end of token_modtoken diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 2866c312be..238476d767 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -96,7 +96,8 @@ SymbolType prime_symbol(std::string text, PositionType& pos) { stringutils::remove_character(text, '\''); auto prime_name = new ast::String(text); - auto prime_order = new ast::Integer(order, nullptr); + assert(order <= std::numeric_limits::max()); + auto prime_order = new ast::Integer(static_cast(order), nullptr); ast::PrimeName value(prime_name, prime_order); value.set_token(token); return Parser::make_PRIME(value, pos); diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index d75b0009be..38050b0c1f 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -323,7 +323,8 @@ bool needs_neuron_thread_first_arg(const std::string& token) { * The passes like scope checker needs to know if certain variable is * undefined and hence these needs to be inserted into symbol table */ -static std::vector NEURON_VARIABLES = {"t", "dt", "celsius", "v", "diam", "area"}; +static std::vector const NEURON_VARIABLES = + {"t", "dt", "celsius", "v", "diam", "area"}; /// Return token type for the keyword diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 4feb59edd4..cad0539b17 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -57,6 +57,7 @@ using namespace codegen; using namespace visitor; using nmodl::parser::NmodlDriver; +// NOLINTNEXTLINE(readability-function-cognitive-complexity) int main(int argc, const char* argv[]) { CLI::App app{fmt::format("NMODL : Source-to-Source Code Generation Framework [{}]", Version::to_string())}; @@ -155,6 +156,7 @@ int main(int argc, const char* argv[]) { /// floating point data type std::string data_type("double"); + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) app.get_formatter()->column_width(40); app.set_help_all_flag("-H,--help-all", "Print this help message including all sub-commands"); @@ -402,7 +404,10 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("ast")); if (json_ast) { - auto file = scratch_dir + "/" + modfile + ".ast.json"; + std::string file{scratch_dir}; + file += "/"; + file += modfile; + file += ".ast.json"; logger->info("Writing AST into {}", file); JSONVisitor(file).write(*ast); } @@ -516,7 +521,10 @@ int main(int argc, const char* argv[]) { } if (json_perfstat) { - auto file = scratch_dir + "/" + modfile + ".perf.json"; + std::string file{scratch_dir}; + file.append("/"); + file.append(modfile); + file.append(".perf.json"); logger->info("Writing performance statistics to {}", file); PerfVisitor(file).visit_program(*ast); } diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index 08f74ae980..e2bd9a3dc7 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -49,7 +49,7 @@ bool CDriver::parse_string(const std::string& input) { return parse_stream(iss); } -void CDriver::error(const std::string& m) const { +void CDriver::error(const std::string& m) { std::cerr << m << '\n'; } @@ -58,7 +58,7 @@ void CDriver::add_token(const std::string& text) { // here we will query and look into symbol table or register callback } -void CDriver::error(const std::string& m, const location& l) const { +void CDriver::error(const std::string& m, const location& l) { std::cerr << l << " : " << m << '\n'; } diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index 30f24238cc..eb02d17dbf 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -68,7 +68,7 @@ class CDriver { CDriver(bool strace, bool ptrace); ~CDriver(); - void error(const std::string& m) const; + static void error(const std::string& m); bool parse_stream(std::istream& in); bool parse_string(const std::string& input); @@ -76,7 +76,7 @@ class CDriver { void scan_string(const std::string& text); void add_token(const std::string&); - void error(const std::string& m, const location& l) const; + static void error(const std::string& m, const location& l); void set_verbose(bool b) noexcept { verbose = b; diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 6c2d80abc6..05c42b4b7a 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -24,7 +24,9 @@ void DiffeqDriver::parse_equation(const std::string& equation, rhs = stringutils::trim(parts[1]); /// expect prime on lhs, find order and remove quote - order = std::count(state.begin(), state.end(), '\''); + auto const wide_order = std::count(state.begin(), state.end(), '\''); + assert(wide_order >= 0 && wide_order <= std::numeric_limits::max()); + order = static_cast(wide_order); stringutils::remove_character(state, '\''); /// error if no prime in equation or not an assignment statement @@ -36,7 +38,7 @@ void DiffeqDriver::parse_equation(const std::string& equation, std::string DiffeqDriver::solve(const std::string& equation, std::string method, bool debug) { std::string state, rhs; int order = 0; - bool cnexp_possible; + bool cnexp_possible{}; parse_equation(equation, state, rhs, order); return solve_equation(state, order, rhs, method, cnexp_possible, debug); } @@ -62,7 +64,7 @@ std::string DiffeqDriver::solve_equation(std::string& state, bool DiffeqDriver::cnexp_possible(const std::string& equation, std::string& solution) { std::string state, rhs; int order = 0; - bool cnexp_possible; + bool cnexp_possible{}; std::string method = "cnexp"; parse_equation(equation, state, rhs, order); solution = solve_equation(state, order, rhs, method, cnexp_possible); diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index 31bb8cf13b..bb362963a3 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -35,27 +35,27 @@ class DiffeqParser; */ class DiffeqDriver { private: - std::string solve_equation(std::string& state, - int order, - std::string& rhs, - std::string& method, - bool& cnexp_possible, - bool debug = false); + static std::string solve_equation(std::string& state, + int order, + std::string& rhs, + std::string& method, + bool& cnexp_possible, + bool debug = false); /// parse given equation into lhs, rhs and find it's order and state variable - void parse_equation(const std::string& equation, - std::string& state, - std::string& rhs, - int& order); + static void parse_equation(const std::string& equation, + std::string& state, + std::string& rhs, + int& order); public: DiffeqDriver() = default; /// solve equation using provided method - std::string solve(const std::string& equation, std::string method, bool debug = false); + static std::string solve(const std::string& equation, std::string method, bool debug = false); /// check if given equation can be solved using cnexp method - bool cnexp_possible(const std::string& equation, std::string& solution); + static bool cnexp_possible(const std::string& equation, std::string& solution); }; /** @} */ // end of parser diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 869b4a177e..67ddefe744 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -137,15 +137,15 @@ class NmodlDriver { * Emit a parsing error * \throw std::runtime_error */ - void parse_error(const location& location, const std::string& message); + static void parse_error(const location& location, const std::string& message); /** * Emit a parsing error. Takes additionally a Lexer instance to print code context * \throw std::runtime_error */ - void parse_error(const NmodlLexer& scanner, - const location& location, - const std::string& message); + static void parse_error(const NmodlLexer& scanner, + const location& location, + const std::string& message); /** * Ensure \a file argument given to the INCLUDE directive is valid: @@ -154,7 +154,8 @@ class NmodlDriver { * * \return unquoted string */ - std::string check_include_argument(const location& location, const std::string& filename); + static std::string check_include_argument(const location& location, + const std::string& filename); }; /** \} */ // end of parser diff --git a/src/nmodl/parser/unit_driver.hpp b/src/nmodl/parser/unit_driver.hpp index 74f4a63166..feb3bba6ad 100644 --- a/src/nmodl/parser/unit_driver.hpp +++ b/src/nmodl/parser/unit_driver.hpp @@ -68,12 +68,12 @@ class UnitDriver { /// \} - void error(const std::string& m); + static void error(const std::string& m); bool parse_stream(std::istream& in); bool parse_string(const std::string& input); bool parse_file(const std::string& filename); void scan_string(std::string& text); - void error(const std::string& m, const location& l); + static void error(const std::string& m, const location& l); void set_verbose(bool b) { verbose = b; diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index f7f6eb4580..9d41b1eb97 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -33,9 +33,9 @@ void CodePrinter::start_block() { indent_level++; } -void CodePrinter::start_block(std::string&& text) { +void CodePrinter::start_block(std::string&& expression) { add_indent(); - *result << text << " {"; + *result << expression << " {"; add_newline(); indent_level++; } diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index d3abfe5c7c..e13a84e6c7 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -32,17 +32,16 @@ JSONPrinter::JSONPrinter(const std::string& filename) { /// Add node to json (typically basic type) void JSONPrinter::add_node(std::string value, const std::string& key) { if (!block) { - auto text = "Block not initialized (push_block missing?)"; - throw std::logic_error(text); + throw std::logic_error{"Block not initialized (push_block missing?)"}; } json j; - j[key] = value; - block->front().push_back(j); + j[key] = std::move(value); + block->front().push_back(std::move(j)); } /// Add property to the block which is added last -void JSONPrinter::add_block_property(std::string name, const std::string& value) { +void JSONPrinter::add_block_property(std::string const& name, const std::string& value) { if (block == nullptr) { logger->warn("JSONPrinter : can't add property without block"); return; diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 471fd53bed..e4fb4b5d65 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -84,7 +84,7 @@ class JSONPrinter { void push_block(const std::string& value, const std::string& key = "name"); void add_node(std::string value, const std::string& key = "name"); - void add_block_property(std::string name, const std::string& value); + void add_block_property(std::string const& name, const std::string& value); void pop_block(); void flush(); diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index f03fd38feb..259df6bb8b 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -33,18 +33,18 @@ namespace nmodl { /** \brief docstring of Python exposed API */ namespace docstring { -static const char* driver = R"( +static const char* const driver = R"( This is the NmodlDriver class documentation )"; -static const char* driver_ast = R"( +static const char* const driver_ast = R"( Get ast Returns: Instance of :py:class:`Program` )"; -static const char* driver_parse_string = R"( +static const char* const driver_parse_string = R"( Parse NMODL provided as a string Args: @@ -55,7 +55,7 @@ static const char* driver_parse_string = R"( >>> ast = driver.parse_string("DEFINE NSTEP 6") )"; -static const char* driver_parse_file = R"( +static const char* const driver_parse_file = R"( Parse NMODL provided as a file Args: @@ -65,7 +65,7 @@ static const char* driver_parse_file = R"( AST: ast root node if success, throws an exception otherwise )"; -static const char* driver_parse_stream = R"( +static const char* const driver_parse_stream = R"( Parse NMODL file provided as istream Args: @@ -75,7 +75,7 @@ static const char* driver_parse_stream = R"( AST: ast root node if success, throws an exception otherwise )"; -static const char* to_nmodl = R"( +static const char* const to_nmodl = R"( Given AST node, return the NMODL string representation Args: @@ -90,7 +90,7 @@ static const char* to_nmodl = R"( 'NEURON {\n}\n' )"; -static const char* to_json = R"( +static const char* const to_json = R"( Given AST node, return the JSON string representation Args: @@ -114,7 +114,7 @@ static const char* to_json = R"( */ class PyNmodlDriver: public nmodl::parser::NmodlDriver { public: - std::shared_ptr parse_stream(py::object object) { + std::shared_ptr parse_stream(py::object const& object) { py::object tiob = py::module::import("io").attr("TextIOBase"); if (py::isinstance(object, tiob)) { py::detail::pythonibuf buf(object); @@ -139,7 +139,7 @@ PYBIND11_MODULE(_nmodl, m_nmodl) { m_nmodl.doc() = "NMODL : Source-to-Source Code Generation Framework"; m_nmodl.attr("__version__") = nmodl::Version::NMODL_VERSION; - py::class_(m_nmodl, "nmodl::parser::NmodlDriver"); + py::class_ _{m_nmodl, "nmodl::parser::NmodlDriver"}; py::class_ nmodl_driver( m_nmodl, "NmodlDriver", nmodl::docstring::driver); nmodl_driver.def(py::init<>()) diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index e3cbc88c75..57f0f8c173 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -217,5 +217,6 @@ pybind_wrap_api init_pybind_wrap_api() noexcept { } // namespace nmodl +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) __attribute__((visibility("default"))) nmodl::pybind_wrappers::pybind_wrap_api nmodl_wrapper_api = nmodl::pybind_wrappers::init_pybind_wrap_api(); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index a02ea88cee..9677f6413c 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -17,6 +17,7 @@ namespace symtab { namespace syminfo { +// NOLINTNEXTLINE(readability-function-cognitive-complexity) std::vector to_string_vector(const NmodlType& obj) { std::vector properties; diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 8a041c6cc5..2a3f7a8a2b 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -20,7 +20,7 @@ using syminfo::NmodlType; using syminfo::Status; -int SymbolTable::Table::counter = 0; +int SymbolTable::Table::counter = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) /** * Insert symbol into current symbol table. There are certain @@ -46,13 +46,11 @@ std::shared_ptr SymbolTable::Table::lookup(const std::string& name) cons } -SymbolTable::SymbolTable(const SymbolTable& table) { - symtab_name = table.name(); - global = table.global_scope(); - node = nullptr; - parent = nullptr; -} - +SymbolTable::SymbolTable(const SymbolTable& table) + : symtab_name{table.name()} + , global{table.global_scope()} + , node{nullptr} + , parent{nullptr} {} bool SymbolTable::is_method_defined(const std::string& name) const { auto symbol = lookup_in_scope(name); @@ -108,7 +106,8 @@ std::vector> SymbolTable::get_variables_with_properties( } /// return all symbol which has all "with" properties and none of the "without" properties -std::vector> SymbolTable::get_variables(NmodlType with, NmodlType without) { +std::vector> SymbolTable::get_variables(NmodlType with, + NmodlType without) const { auto variables = get_variables_with_properties(with, true); decltype(variables) result; for (auto& variable: variables) { diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index efb5311c6b..cd52899f20 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -143,7 +143,7 @@ class SymbolTable { */ std::vector> get_variables( syminfo::NmodlType with = syminfo::NmodlType::empty, - syminfo::NmodlType without = syminfo::NmodlType::empty); + syminfo::NmodlType without = syminfo::NmodlType::empty) const; /** * get variables with properties diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index c50b59975b..03edb17b65 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,10 @@ * \brief Units processing while being processed from lexer and parser */ +namespace { +constexpr std::size_t output_precision{8}; +} + namespace nmodl { namespace units { @@ -42,6 +47,8 @@ void Unit::add_base_unit(const std::string& name) { // name = "*[a-j]*" which is a base unit const auto dim_name = name[1]; const int dim_no = dim_name - 'a'; + assert(dim_no >= 0 && dim_no < unit_dimensions.size()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) unit_dimensions[dim_no] = 1; add_nominator_unit(name); } @@ -87,7 +94,7 @@ void Unit::mul_factor(const double double_factor) { } void Unit::add_fraction(const std::string& fraction_string) { - double nom, denom; + double nom{}, denom{}; std::string nominator; std::string denominator; std::string::const_iterator it; @@ -105,8 +112,8 @@ void Unit::add_fraction(const std::string& fraction_string) { } double Unit::parse_double(std::string double_string) { - long double d_number; - double d_magnitude; + long double d_number{}; + double d_magnitude{}; std::string s_number; std::string s_magnitude; std::string::const_iterator it; @@ -135,9 +142,11 @@ double Unit::parse_double(std::string double_string) { } else { d_magnitude = std::stod(s_magnitude); } + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) return static_cast(d_number * powl(10.0, d_magnitude) * sign); } +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void UnitTable::calc_nominator_dims(const std::shared_ptr& unit, std::string nominator_name) { double nominator_prefix_factor = 1.0; int nominator_power = 1; @@ -163,7 +172,8 @@ void UnitTable::calc_nominator_dims(const std::shared_ptr& unit, std::stri changed_nominator_name = 1; nominator_prefix_factor *= it.second; nominator_name.erase(nominator_name.begin(), - nominator_name.begin() + it.first.size()); + nominator_name.begin() + + static_cast(it.first.size())); } } } @@ -211,6 +221,7 @@ void UnitTable::calc_nominator_dims(const std::shared_ptr& unit, std::stri } } +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, std::string denominator_name) { double denominator_prefix_factor = 1.0; @@ -238,7 +249,8 @@ void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, changed_denominator_name = 1; denominator_prefix_factor *= it.second; denominator_name.erase(denominator_name.begin(), - denominator_name.begin() + it.first.size()); + denominator_name.begin() + + static_cast(it.first.size())); } } } @@ -293,7 +305,10 @@ void UnitTable::insert(const std::shared_ptr& unit) { (unit_nominator.front().front() == '*' && unit_nominator.front().back() == '*'); if (only_base_unit_nominator) { // base_units_names[i] = "*i-th base unit*" (ex. base_units_names[0] = "*a*") - base_units_names[unit_nominator.front()[1] - 'a'] = unit->get_name(); + auto const index = unit_nominator.front()[1] - 'a'; + assert(index >= 0 && index < base_units_names.size()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) + base_units_names[index] = unit->get_name(); // if unit is found in table replace it auto find_unit_name = table.find(unit->get_name()); if (find_unit_name == table.end()) { @@ -328,7 +343,7 @@ void UnitTable::insert_prefix(const std::shared_ptr& prfx) { void UnitTable::print_units() const { for (const auto& it: table) { - std::cout << std::fixed << std::setprecision(8) << it.first << ' ' + std::cout << std::fixed << std::setprecision(output_precision) << it.first << ' ' << it.second->get_factor() << ':'; for (const auto& dims: it.second->get_dimensions()) { std::cout << ' ' << dims; @@ -357,7 +372,7 @@ void UnitTable::print_units_sorted(std::ostream& units_details) const { table.end()); std::sort(sorted_elements.begin(), sorted_elements.end()); for (const auto& it: sorted_elements) { - units_details << std::fixed << std::setprecision(8) << it.first << ' ' + units_details << std::fixed << std::setprecision(output_precision) << it.first << ' ' << it.second->get_factor() << ':'; for (const auto& dims: it.second->get_dimensions()) { units_details << ' ' << dims; diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 3b96b04c26..a94a93e845 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -147,7 +147,7 @@ class Unit { /// Parse a double number given as string. The double can be positive or negative and /// have all kinds of representations - double parse_double(std::string double_string); + static double parse_double(std::string double_string); /// Getter for the vector of nominators of the Unit const std::vector& get_nominator_unit() const noexcept { diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 94339f7e08..62db3c6e80 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -7,6 +7,7 @@ #include "common_utils.hpp" +#include #include #include #include @@ -14,6 +15,7 @@ #include #include #include +#include #include #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) @@ -32,7 +34,7 @@ bool is_dir_exist(const std::string& path) { } bool file_exists(const std::string& path) { - struct stat info; + struct stat info {}; return stat(path.c_str(), &info) == 0; } @@ -45,15 +47,14 @@ bool file_is_abs(const std::string& path) { } std::string cwd() { - char cwd[MAXPATHLEN + 1]; - - if (nullptr == getcwd(cwd, MAXPATHLEN + 1)) { + std::array cwd{}; + if (nullptr == getcwd(cwd.data(), MAXPATHLEN + 1)) { throw std::runtime_error("working directory name too long"); } - return {cwd}; + return {cwd.data()}; } bool make_path(const std::string& path) { - mode_t mode = 0755; + mode_t mode = 0755; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) int ret = mkdir(path.c_str(), mode); if (ret == 0) { return true; @@ -63,7 +64,7 @@ bool make_path(const std::string& path) { case ENOENT: // parent didn't exist, try to create it { - int pos = path.find_last_of('/'); + auto const pos = path.find_last_of('/'); if (pos == std::string::npos) { return false; } @@ -103,14 +104,16 @@ TempFile::~TempFile() { std::string generate_random_string(const int len, UseNumbersInString use_numbers) { std::string s(len, 0); - static const char alphanum[] = + constexpr std::size_t number_of_numbers{10}; + constexpr std::string_view alphanum{ "0123456789" "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz"; + "abcdefghijklmnopqrstuvwxyz"}; std::random_device dev; std::mt19937 rng(dev()); - std::uniform_int_distribution dist(use_numbers ? 0 : 10, - sizeof(alphanum) - 2); + std::uniform_int_distribution dist(use_numbers ? 0 + : number_of_numbers, + alphanum.size() - 1); for (int i = 0; i < len; ++i) { s[i] = alphanum[dist(rng)]; } diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index e8fadae56e..2087a670ed 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -29,7 +29,9 @@ struct Logger { } }; +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) Logger nmodl_logger("NMODL", "[%n] [%^%l%$] :: %v"); logger_type logger = nmodl_logger.logger; +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) } // namespace nmodl diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index df068cbeba..ac4ea7845d 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -63,7 +63,7 @@ PerfStat operator+(const PerfStat& first, const PerfStat& second) { return result; } -void PerfStat::print(std::stringstream& stream) { +void PerfStat::print(std::stringstream& stream) const { TableData table; table.headers = keys(); table.rows.push_back(values()); @@ -73,7 +73,7 @@ void PerfStat::print(std::stringstream& stream) { table.print(stream); } -std::vector PerfStat::keys() const { +std::vector PerfStat::keys() { return {"+", "-", "x", "/", "exp", "log", "GM-R(T)", "GM-R(U)", "GM-W(T)", "GM-W(U)", "CM-R(T)", "CM-R(U)", "CM-W(T)", "CM-W(U)", "LM-R(T)", "LM-W(T)", "calls(ext)", "calls(int)", "compare", "unary", "conditional"}; diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index b17d791c90..86cd423129 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -99,9 +99,9 @@ struct PerfStat { friend PerfStat operator+(const PerfStat& first, const PerfStat& second); - void print(std::stringstream& stream); + void print(std::stringstream& stream) const; - std::vector keys() const; + static std::vector keys(); std::vector values() const; }; diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 478ee25f5f..906309b5c2 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -5,6 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include #include #include @@ -29,7 +30,7 @@ namespace utils { */ void TableData::print(std::ostream& stream, int indent) const { - const int PADDING = 1; + constexpr std::size_t PADDING{1}; /// not necessary to print empty table if (rows.empty() || headers.empty()) { @@ -37,10 +38,11 @@ void TableData::print(std::ostream& stream, int indent) const { } /// based on indentation level, spaces to prefix - auto gutter = std::string(indent * 4, ' '); + assert(indent >= 0); + auto gutter = std::string(static_cast(indent) * 4, ' '); - auto ncolumns = headers.size(); - std::vector col_width(ncolumns); + auto const ncolumns = headers.size(); + std::vector col_width(ncolumns); /// alignment is optional, so fill remaining with right alignment auto all_alignments = alignments; @@ -50,7 +52,7 @@ void TableData::print(std::ostream& stream, int indent) const { } /// calculate space required for each column - unsigned row_width = 0; + std::size_t row_width{}; for (unsigned i = 0; i < headers.size(); i++) { col_width[i] = headers[i].length() + PADDING; row_width += col_width[i]; @@ -59,8 +61,8 @@ void TableData::print(std::ostream& stream, int indent) const { /// if title is larger than headers then every column /// width needs to be scaled if (title.length() > row_width) { - int extra_size = title.length() - row_width; - int column_pad = extra_size / ncolumns; + auto const extra_size = title.length() - row_width; + auto column_pad = extra_size / ncolumns; if ((extra_size % ncolumns) != 0) { column_pad++; } @@ -81,8 +83,10 @@ void TableData::print(std::ostream& stream, int indent) const { std::stringstream header; header << "| "; for (size_t i = 0; i < headers.size(); i++) { - auto text = - stringutils::align_text(headers[i], col_width[i], stringutils::text_alignment::center); + assert(col_width[i] <= std::numeric_limits::max()); + auto text = stringutils::align_text(headers[i], + static_cast(col_width[i]), + stringutils::text_alignment::center); header << text << " | "; } @@ -91,8 +95,10 @@ void TableData::print(std::ostream& stream, int indent) const { /// title row if (!title.empty()) { - auto fmt_title = - stringutils::align_text(title, row_width - 3, stringutils::text_alignment::center); + assert(row_width >= 3 && row_width - 3 <= std::numeric_limits::max()); + auto fmt_title = stringutils::align_text(title, + static_cast(row_width - 3), + stringutils::text_alignment::center); stream << '\n' << gutter << separator_line; stream << '\n' << gutter << '|' << fmt_title << '|'; } @@ -106,7 +112,11 @@ void TableData::print(std::ostream& stream, int indent) const { for (const auto& row: rows) { stream << '\n' << gutter << "| "; for (unsigned i = 0; i < row.size(); i++) { - stream << stringutils::align_text(row[i], col_width[i], all_alignments[i]) << " | "; + assert(col_width[i] <= std::numeric_limits::max()); + stream << stringutils::align_text(row[i], + static_cast(col_width[i]), + all_alignments[i]) + << " | "; } } diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index ae6ced6791..2358951726 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -284,7 +284,7 @@ void DefUseAnalyzeVisitor::visit_if_statement(const ast::IfStatement& node) { * \todo One simple way would be to look for p_name in the string * of verbatim block to find the variable usage. */ -void DefUseAnalyzeVisitor::visit_verbatim(const ast::Verbatim& node) { +void DefUseAnalyzeVisitor::visit_verbatim(const ast::Verbatim& /* node */) { if (!ignore_verbatim) { current_chain->push_back(DUInstance(DUState::U, current_binary_expression)); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 4a48949a31..b72c9cbdb9 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -20,7 +20,7 @@ namespace visitor { using namespace ast; -bool InlineVisitor::can_inline_block(const StatementBlock& block) const { +bool InlineVisitor::can_inline_block(const StatementBlock& block) { bool to_inline = true; const auto& statements = block.get_statements(); for (const auto& statement: statements) { @@ -32,7 +32,8 @@ bool InlineVisitor::can_inline_block(const StatementBlock& block) const { // verbatim blocks with return statement are not safe to inline // especially for net_receive block if (statement->is_verbatim()) { - const auto node = static_cast(statement.get()); + const auto node = dynamic_cast(statement.get()); + assert(node); auto text = node->get_statement()->eval(); parser::CDriver driver; driver.scan_string(text); @@ -67,10 +68,12 @@ bool InlineVisitor::can_replace_statement(const std::shared_ptr& stat } bool to_replace = false; - auto es = static_cast(statement.get()); + auto es = dynamic_cast(statement.get()); + assert(es); auto e = es->get_expression(); if (e->is_wrapped_expression()) { - auto wrapped_expression = static_cast(e.get()); + auto wrapped_expression = dynamic_cast(e.get()); + assert(wrapped_expression); if (wrapped_expression->get_expression()->is_function_call()) { // if caller is external function (i.e. neuron function) don't replace it const auto& function_call = std::static_pointer_cast( @@ -113,7 +116,9 @@ void InlineVisitor::inline_arguments(StatementBlock& inlined_block, /// create assignment statement and insert after the local variables auto expression = new BinaryExpression(lhs, BinaryOperator(ast::BOP_ASSIGN), rhs); auto statement = std::make_shared(expression); - inlined_block.insert_statement(statements.begin() + counter + 1, statement); + inlined_block.insert_statement(statements.begin() + + static_cast(counter + 1ul), + statement); counter++; } } @@ -144,7 +149,7 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, /// need to add local variable for function calls or for procedure call if it is part of /// expression (standalone procedure calls don't need return statement) - if (callee.is_function_block() || to_replace == false) { + if (callee.is_function_block() || !to_replace) { /// create new variable which will be used for returning value from inlined block auto name = new ast::Name(new ast::String(new_varname)); ModToken tok; @@ -174,7 +179,7 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, inline_arguments(*inlined_block, callee.get_parameters(), caller_arguments); /// to return value from procedure we have to add new variable - if (callee.is_procedure_block() && to_replace == false) { + if (callee.is_procedure_block() && !to_replace) { add_return_variable(*inlined_block, new_varname); } @@ -216,10 +221,12 @@ void InlineVisitor::visit_function_call(FunctionCall& node) { bool inlined = false; if (function_definition->is_procedure_block()) { - auto proc = (ProcedureBlock*) function_definition; + auto proc = dynamic_cast(function_definition); + assert(proc); inlined = inline_function_call(*proc, node, *caller_block); } else if (function_definition->is_function_block()) { - auto func = (FunctionBlock*) function_definition; + auto func = dynamic_cast(function_definition); + assert(func); inlined = inline_function_call(*func, node, *caller_block); } diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index a6156632f5..72b26abc0f 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -159,7 +159,7 @@ class InlineVisitor: public AstVisitor { std::map inlined_variables; /// true if given statement block can be inlined - bool can_inline_block(const ast::StatementBlock& block) const; + static bool can_inline_block(const ast::StatementBlock& block); /// true if statement can be replaced with inlined body /// this is possible for standalone function/procedure call as statement @@ -182,7 +182,7 @@ class InlineVisitor: public AstVisitor { /// add assignment statement at end of block (to use as a return statement /// in case of procedure blocks) - void add_return_variable(ast::StatementBlock& block, std::string& varname); + static void add_return_variable(ast::StatementBlock& block, std::string& varname); public: InlineVisitor() = default; diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 23f561bff4..867c47f3e3 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -62,7 +62,7 @@ void KineticBlockVisitor::process_reac_var(const std::string& varname, int count void KineticBlockVisitor::process_conserve_reac_var(const std::string& varname, int count) { // subtract previous term from both sides of equation - if (conserve_equation_statevar != "") { + if (!conserve_equation_statevar.empty()) { if (conserve_equation_factor.empty()) { conserve_equation_str += " - " + conserve_equation_statevar; @@ -134,9 +134,9 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve& node) { conserve_equation_str); auto expr = std::dynamic_pointer_cast(statement); // set react (lhs) of CONSERVE to the state variable whose ODE should be replaced - node.set_react(std::move(expr->get_react())); + node.set_react(expr->get_react()); // set expr (rhs) of CONSERVE to the equation that should replace the ODE - node.set_expr(std::move(expr->get_expr())); + node.set_expr(expr->get_expr()); logger->debug("KineticBlockVisitor :: --> {}", to_nmodl(node)); } @@ -176,7 +176,8 @@ void KineticBlockVisitor::visit_reaction_operator(ast::ReactionOperator& node) { } } -void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName& node) { +void KineticBlockVisitor::visit_react_var_name( + ast::ReactVarName& node) { // NOLINT(readability-function-cognitive-complexity) // ReactVarName node contains a VarName and an Integer // the VarName is the state variable which we convert to an index // the Integer is the value to be added to the stoichiometric matrix at this index @@ -196,6 +197,7 @@ void KineticBlockVisitor::visit_react_var_name(ast::ReactVarName& node) { } } +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement& node) { statements_to_remove.insert(&node); @@ -475,9 +477,9 @@ void KineticBlockVisitor::visit_program(ast::Program& node) { for (auto* kinetic_block: kinetic_blocks) { for (auto it = blocks.begin(); it != blocks.end(); ++it) { if (it->get() == kinetic_block) { - auto dblock = std::make_shared( - std::move(kinetic_block->get_name()), - std::move(kinetic_block->get_statement_block())); + auto dblock = + std::make_shared(kinetic_block->get_name(), + kinetic_block->get_statement_block()); ModToken tok{}; dblock->set_token(tok); *it = dblock; diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index d2e191238d..4698b1e91a 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -137,7 +137,7 @@ void LocalizeVisitor::visit_program(const ast::Program& node) { for (auto& block: block_usage[state]) { auto block_ptr = dynamic_cast(block.get()); const auto& statement_block = block_ptr->get_statement_block(); - ast::LocalVar* variable; + ast::LocalVar* variable{}; auto symbol = program_symtab->lookup(varname); if (symbol->is_array()) { diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index b29a42bdf7..705016a331 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -61,18 +61,18 @@ class IndexRemover: public AstVisitor { if (under_indexed_name) { /// first recursively replaces children /// replace lhs & rhs if they have matching index variable - const auto& lhs = replace_for_name(node.get_lhs()); - const auto& rhs = replace_for_name(node.get_rhs()); + auto lhs = replace_for_name(node.get_lhs()); + auto rhs = replace_for_name(node.get_rhs()); node.set_lhs(std::move(lhs)); node.set_rhs(std::move(rhs)); } } - virtual void visit_indexed_name(ast::IndexedName& node) override { + void visit_indexed_name(ast::IndexedName& node) override { under_indexed_name = true; node.visit_children(*this); /// once all children are replaced, do the same for index - const auto& length = replace_for_name(node.get_length()); + auto length = replace_for_name(node.get_length()); node.set_length(std::move(length)); under_indexed_name = false; } diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index f10b0d5666..a687cd9160 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -54,12 +54,10 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression& node) { if (name->is_prime_name()) { auto equation = to_nmodl(node); - parser::DiffeqDriver diffeq_driver; - if (solve_method == codegen::naming::CNEXP_METHOD) { std::string solution; /// check if ode can be solved with cnexp method - if (diffeq_driver.cnexp_possible(equation, solution)) { + if (parser::DiffeqDriver::cnexp_possible(equation, solution)) { auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast( statement); @@ -72,7 +70,7 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression& node) { to_nmodl(node)); } } else if (solve_method == codegen::naming::EULER_METHOD) { - std::string solution = diffeq_driver.solve(equation, solve_method); + std::string solution = parser::DiffeqDriver::solve(equation, solve_method); auto statement = create_statement(solution); auto expr_statement = std::dynamic_pointer_cast(statement); const auto bin_expr = std::dynamic_pointer_cast( diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index bb5ce0dafe..54c043402c 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -115,7 +115,7 @@ void PerfVisitor::visit_binary_expression(const ast::BinaryExpression& node) { /// add performance stats to json printer void PerfVisitor::add_perf_to_printer(const PerfStat& perf) const { - const auto& keys = perf.keys(); + const auto& keys = nmodl::utils::PerfStat::keys(); const auto& values = perf.values(); assert(keys.size() == values.size()); @@ -249,12 +249,12 @@ void PerfVisitor::count_variables() { /// state variables have state_var property property = NmodlType::state_var; variables = current_symtab->get_variables_with_properties(property); - num_state_variables = variables.size(); + num_state_variables = static_cast(variables.size()); /// pointer variables have pointer/bbcorepointer property = NmodlType::pointer_var | NmodlType::bbcore_pointer_var; variables = current_symtab->get_variables_with_properties(property); - num_pointer_variables = variables.size(); + num_pointer_variables = static_cast(variables.size()); /// number of global variables : parameters and pointers could appear also @@ -497,7 +497,7 @@ void PerfVisitor::visit_unary_expression(const ast::UnaryExpression& node) { * count for "exp" symbol. Same for solve statement where name will * be derivative block name and neuron solver method. */ -bool PerfVisitor::symbol_to_skip(const std::shared_ptr& symbol) { +bool PerfVisitor::symbol_to_skip(const std::shared_ptr& symbol) const { bool skip = false; auto is_method = symbol->has_any_property(NmodlType::extern_method | NmodlType::function_block); @@ -513,7 +513,7 @@ bool PerfVisitor::symbol_to_skip(const std::shared_ptr& symbol) { return skip; } -bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) const { +bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) { bool is_local = false; /// in the function when we write to function variable then consider it as local variable auto properties = NmodlType::local_var | NmodlType::argument | NmodlType::function_block; @@ -523,7 +523,7 @@ bool PerfVisitor::is_local_variable(const std::shared_ptr& symbol) const return is_local; } -bool PerfVisitor::is_constant_variable(const std::shared_ptr& symbol) const { +bool PerfVisitor::is_constant_variable(const std::shared_ptr& symbol) { bool is_constant = false; auto properties = NmodlType::param_assign; if (symbol->has_any_property(properties)) { diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 0757532020..188efe83b9 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -133,11 +133,11 @@ class PerfVisitor: public ConstAstVisitor { void update_memory_ops(const std::string& name); - bool symbol_to_skip(const std::shared_ptr& symbol); + bool symbol_to_skip(const std::shared_ptr& symbol) const; - bool is_local_variable(const std::shared_ptr& symbol) const; + static bool is_local_variable(const std::shared_ptr& symbol); - bool is_constant_variable(const std::shared_ptr& symbol) const; + static bool is_constant_variable(const std::shared_ptr& symbol); void count_variables(); diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index eecfb74262..bbb6cab0ee 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -44,7 +44,7 @@ void SemanticAnalysisVisitor::visit_function_block(const ast::FunctionBlock& nod /// --> } -void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement&) { +void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement& /* node */) { /// <-- This code is for check 1 if (in_procedure_function && !one_arg_in_procedure_function) { logger->critical( @@ -55,7 +55,7 @@ void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement&) /// --> } -void SemanticAnalysisVisitor::visit_destructor_block(const ast::DestructorBlock& node) { +void SemanticAnalysisVisitor::visit_destructor_block(const ast::DestructorBlock& /* node */) { /// <-- This code is for check 2 if (!is_point_process) { logger->warn( diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 99b658c775..4ad363529d 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -15,8 +15,6 @@ namespace nmodl { namespace visitor { -using symtab::syminfo::NmodlType; - std::shared_ptr SteadystateVisitor::create_steadystate_block( const std::shared_ptr& solve_block, const std::vector>& deriv_blocks) { diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 53039b243f..d319b1443e 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -44,6 +44,7 @@ static bool conductance_statement_possible(const ast::BreakpointBlock& node) { // Generate statement strings to be added to BREAKPOINT section +// NOLINTNEXTLINE(readability-function-cognitive-complexity) std::vector SympyConductanceVisitor::generate_statement_strings( ast::BreakpointBlock& node) { std::vector statements; @@ -69,7 +70,9 @@ std::vector SympyConductanceVisitor::generate_statement_strings( // SymPy needs the current expression & all previous expressions std::vector expressions(ordered_binary_exprs.begin(), ordered_binary_exprs.begin() + - binary_expr_index[lhs_str] + 1); + static_cast( + binary_expr_index[lhs_str]) + + 1); // differentiate dI/dV auto analytic_diff = pywrap::EmbeddedPythonLoader::get_instance().api()->create_ads_executor(); @@ -140,9 +143,7 @@ void SympyConductanceVisitor::lookup_nonspecific_statements() { for (const auto& ns_curr_ast: nonspecific_nodes) { logger->debug("SympyConductance :: Found NONSPECIFIC_CURRENT statement"); for (const auto& write_name: - std::dynamic_pointer_cast(ns_curr_ast) - .get() - ->get_currents()) { + std::dynamic_pointer_cast(ns_curr_ast)->get_currents()) { const std::string& curr_write = write_name->get_node_name(); logger->debug("SympyConductance :: -> Adding non-specific current write name: {}", curr_write); diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index 9194be64bf..aae5ba6f2e 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -39,6 +39,7 @@ SympyReplaceSolutionsVisitor::SympyReplaceSolutionsVisitor( const size_t n_next_equations, const std::string& tmp_unique_prefix) : pre_solve_statements(pre_solve_statements.begin(), pre_solve_statements.end(), 2) + , is_top_level_statement_block{true} , to_be_removed(&to_be_removed) , policy(policy) , n_next_equations(n_next_equations) @@ -57,10 +58,9 @@ SympyReplaceSolutionsVisitor::SympyReplaceSolutionsVisitor( solution_statements = StatementDispenser(ss_tmp_delimeter, solutions.end(), -1); replacements.clear(); - is_top_level_statement_block = true; } - +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& node) { const bool current_is_top_level_statement_block = is_top_level_statement_block; // we mark it down since we are going to change it for @@ -96,22 +96,22 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no node.visit_children(*this); } - const auto& old_statements = node.get_statements(); + auto const& old_statements = node.get_statements(); ast::StatementVector new_statements; new_statements.reserve(2 * old_statements.size()); - for (const auto& old_statement: old_statements) { + for (auto& old_statement: old_statements) { const auto& replacement_ptr = replacements.find(old_statement); if (replacement_ptr != replacements.end()) { if (replaced_statements_range.first == -1) { - replaced_statements_range.first = new_statements.size(); + replaced_statements_range.first = static_cast(new_statements.size()); } new_statements.insert(new_statements.end(), replacement_ptr->second.begin(), replacement_ptr->second.end()); - replaced_statements_range.second = new_statements.size(); + replaced_statements_range.second = static_cast(new_statements.size()); logger->debug("SympyReplaceSolutionsVisitor :: erasing {}", to_nmodl(old_statement)); for (const auto& replacement: replacement_ptr->second) { @@ -121,7 +121,7 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no to_be_removed->find(&(*old_statement)) == to_be_removed->end()) { logger->debug("SympyReplaceSolutionsVisitor :: found {}, nothing to do", to_nmodl(old_statement)); - new_statements.emplace_back(std::move(old_statement)); + new_statements.emplace_back(old_statement); } else { logger->debug("SympyReplaceSolutionsVisitor :: erasing {}", to_nmodl(old_statement)); } @@ -149,10 +149,10 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no } if (replaced_statements_range.first == -1) { - replaced_statements_range.first = new_statements.size(); + replaced_statements_range.first = static_cast(new_statements.size()); } if (replaced_statements_range.second == -1) { - replaced_statements_range.second = new_statements.size(); + replaced_statements_range.second = static_cast(new_statements.size()); } } @@ -213,11 +213,11 @@ void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { - return static_cast(node).get_expression()->get_lhs(); + return dynamic_cast(node).get_expression()->get_lhs(); }; auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { - return static_cast(node).get_expression()->get_rhs(); + return dynamic_cast(node).get_expression()->get_rhs(); }; try_replace_tagged_statement(node, get_lhs, get_rhs); @@ -226,11 +226,11 @@ void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpressio void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { - return static_cast(node).get_left_linxpression(); + return dynamic_cast(node).get_left_linxpression(); }; auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { - return static_cast(node).get_left_linxpression(); + return dynamic_cast(node).get_left_linxpression(); }; try_replace_tagged_statement(node, get_lhs, get_rhs); @@ -240,11 +240,11 @@ void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { void SympyReplaceSolutionsVisitor::visit_non_lin_equation(ast::NonLinEquation& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { - return static_cast(node).get_lhs(); + return dynamic_cast(node).get_lhs(); }; auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { - return static_cast(node).get_rhs(); + return dynamic_cast(node).get_rhs(); }; try_replace_tagged_statement(node, get_lhs, get_rhs); @@ -307,6 +307,7 @@ SympyReplaceSolutionsVisitor::StatementDispenser::StatementDispenser( * - b : 1 * */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) void SympyReplaceSolutionsVisitor::StatementDispenser::build_maps() { for (size_t ii = 0; ii < statements.size(); ++ii) { const auto& statement = statements[ii]; diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 8ba318636b..339f5c6535 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -24,8 +24,6 @@ namespace visitor { using symtab::syminfo::NmodlType; -using nmodl::utils::UseNumbersInString; - void SympySolverVisitor::init_block_data(ast::Node* node) { // clear any previous data expression_statements.clear(); @@ -135,9 +133,9 @@ static bool is_local_statement(const std::shared_ptr& statement) std::string& SympySolverVisitor::replaceAll(std::string& context, const std::string& from, - const std::string& to) const { + const std::string& to) { std::size_t lookHere = 0; - std::size_t foundHere; + std::size_t foundHere{}; while ((foundHere = context.find(from, lookHere)) != std::string::npos) { context.replace(foundHere, from.size(), to); lookHere = foundHere + to.size(); @@ -148,7 +146,7 @@ std::string& SympySolverVisitor::replaceAll(std::string& context, std::vector SympySolverVisitor::filter_string_vector( const std::vector& original_vector, const std::string& original_string, - const std::string& substitution_string) const { + const std::string& substitution_string) { std::vector filtered_vector; for (auto element: original_vector) { std::string filtered_element = replaceAll(element, original_string, substitution_string); @@ -202,17 +200,15 @@ void SympySolverVisitor::construct_eigen_solver_block( ast::StatementVector functor_statements; // J[0]_row * X = F[0], additional assignments during // computation // ast::StatementVector finalize_statements; // assignments at the end // - const size_t sr_begin = solution_replacer.replaced_statements_begin(); - const size_t sr_end = solution_replacer.replaced_statements_end(); + std::ptrdiff_t const sr_begin{solution_replacer.replaced_statements_begin()}; + std::ptrdiff_t const sr_end{solution_replacer.replaced_statements_end()}; // initialize and edge case where the system of equations is empty for (size_t idx = 0; idx < statements.size(); ++idx) { auto& s = statements[idx]; if (is_local_statement(s)) { variable_statements.push_back(s); - } else if (sr_begin == statements.size()) { - initialize_statements.push_back(s); - } else if (idx < sr_begin) { + } else if (sr_begin == statements.size() || idx < sr_begin) { initialize_statements.push_back(s); } } @@ -220,15 +216,17 @@ void SympySolverVisitor::construct_eigen_solver_block( if (sr_begin != statements.size()) { initialize_statements.insert(initialize_statements.end(), statements.begin() + sr_begin, - statements.begin() + sr_begin + pre_solve_statements.size()); - setup_x_statements = - ast::StatementVector(statements.begin() + sr_begin + pre_solve_statements.size(), - statements.begin() + sr_begin + pre_solve_statements.size() + - state_vars.size()); - functor_statements = ast::StatementVector(statements.begin() + sr_begin + - pre_solve_statements.size() + - state_vars.size(), - statements.begin() + sr_end); + statements.begin() + sr_begin + + static_cast(pre_solve_statements.size())); + setup_x_statements = ast::StatementVector( + statements.begin() + sr_begin + + static_cast(pre_solve_statements.size()), + statements.begin() + sr_begin + + static_cast(pre_solve_statements.size() + state_vars.size())); + functor_statements = ast::StatementVector( + statements.begin() + sr_begin + + static_cast(pre_solve_statements.size() + state_vars.size()), + statements.begin() + sr_end); finalize_statements = ast::StatementVector(statements.begin() + sr_end, statements.end()); } @@ -513,8 +511,8 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { auto split_eq = stringutils::split_string(eq, '='); auto x_prime_split = stringutils::split_string(split_eq[0], '\''); auto x = stringutils::trim(x_prime_split[0]); - std::string x_array_index = ""; - std::string x_array_index_i = ""; + std::string x_array_index; + std::string x_array_index_i; if (x_prime_split.size() > 1 && stringutils::trim(x_prime_split[1]).size() > 2) { x_array_index = stringutils::trim(x_prime_split[1]); x_array_index_i = "_" + x_array_index.substr(1, x_array_index.size() - 2); @@ -532,16 +530,33 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { // no CONSERVE equation, construct Euler equation auto dxdt = stringutils::trim(split_eq[1]); - - const auto old_x = suffix_random_string(vars, "old_" + x + x_array_index_i); + auto const old_x = [&]() { + std::string old_x_name{"old_"}; + old_x_name.append(x); + old_x_name.append(x_array_index_i); + return suffix_random_string(vars, old_x_name); + }(); // declare old_x logger->debug("SympySolverVisitor :: -> declaring new local variable: {}", old_x); add_local_variable(*block_with_expression_statements, old_x); // assign old_x = x - pre_solve_statements.push_back(old_x + " = " + x + x_array_index); + { + std::string expression{old_x}; + expression.append(" = "); + expression.append(x); + expression.append(x_array_index); + pre_solve_statements.push_back(std::move(expression)); + } // replace ODE with Euler equation - eq = x + x_array_index + " = " + old_x + " + " + - codegen::naming::NTHREAD_DT_VARIABLE + " * (" + dxdt + ")"; + eq = x; + eq.append(x_array_index); + eq.append(" = "); + eq.append(old_x); + eq.append(" + "); + eq.append(codegen::naming::NTHREAD_DT_VARIABLE); + eq.append(" * ("); + eq.append(dxdt); + eq.append(")"); logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); } } diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index f3a3099d0d..50abd0ff78 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -91,15 +91,16 @@ class SympySolverVisitor: public AstVisitor { /// Function used by SympySolverVisitor::filter_X to replace the name X in a std::string /// to X_operator - std::string& replaceAll(std::string& context, - const std::string& from, - const std::string& to) const; + static std::string& replaceAll(std::string& context, + const std::string& from, + const std::string& to); /// Check original_vector for elements that contain a variable named original_string and /// rename it to substitution_string - std::vector filter_string_vector(const std::vector& original_vector, - const std::string& original_string, - const std::string& substitution_string) const; + static std::vector filter_string_vector( + const std::vector& original_vector, + const std::string& original_string, + const std::string& substitution_string); /// global variables std::set global_vars; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 22de3cd57b..d51b887868 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -217,13 +217,11 @@ std::vector> collect_nodes(ast::Ast& node, bool sparse_solver_exists(const ast::Ast& node) { const auto solve_blocks = collect_nodes(node, {ast::AstNodeType::SOLVE_BLOCK}); - for (const auto& solve_block: solve_blocks) { - const auto& method = dynamic_cast(solve_block.get())->get_method(); - if (method && method->get_node_name() == "sparse") { - return true; - } - } - return false; + return std::any_of(solve_blocks.begin(), solve_blocks.end(), [](auto const& solve_block) { + assert(solve_block); + const auto& method = dynamic_cast(*solve_block).get_method(); + return method && method->get_node_name() == "sparse"; + }); } std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types) { diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index 6c3f3b1959..b7ca1eaa4d 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -23,7 +23,7 @@ using nmodl::test_utils::reindent_text; /// Helper for creating C codegen visitor std::shared_ptr create_c_visitor(const std::shared_ptr& ast, - const std::string& text, + const std::string& /* text */, std::stringstream& ss) { /// construct symbol table SymtabVisitor().visit_program(*ast); diff --git a/test/nmodl/transpiler/unit/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp index 738fb00c76..7747c8cc96 100644 --- a/test/nmodl/transpiler/unit/lexer/tokens.cpp +++ b/test/nmodl/transpiler/unit/lexer/tokens.cpp @@ -53,13 +53,13 @@ bool check_token_type(const std::string& name, TokenType type) { token_type == get_token_type(Token::DEL) || token_type == get_token_type(Token::DEL2)) { auto value = sym.value.as(); - REQUIRE(value.get_node_name() != ""); + REQUIRE(!value.get_node_name().empty()); } // clang-format on // prime variable else if (token_type == get_token_type(Token::PRIME)) { auto value = sym.value.as(); - REQUIRE(value.get_node_name() != ""); + REQUIRE(!value.get_node_name().empty()); } // integer constant else if (token_type == get_token_type(Token::INTEGER)) { @@ -74,19 +74,19 @@ bool check_token_type(const std::string& name, TokenType type) { // const char* else if (token_type == get_token_type(Token::STRING)) { auto value = sym.value.as(); - REQUIRE(value.get_value() != ""); + REQUIRE(!value.get_value().empty()); } // string block representation verbatim or block comment else if (token_type == get_token_type(Token::VERBATIM) || token_type == get_token_type(Token::BLOCK_COMMENT) || token_type == get_token_type(Token::LINE_PART)) { auto value = sym.value.as(); - REQUIRE(value != ""); + REQUIRE(!value.empty()); } // rest of the tokens else { auto value = sym.value.as(); - REQUIRE(value.text() != ""); + REQUIRE(!value.text().empty()); } return sym.type_get() == get_token_type(type); diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 7656219477..4e45103cbf 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -78,6 +78,7 @@ TEST_CASE("Addition of two ModToken objects", "[token][modtoken]") { { std::stringstream ss; + // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) nmodl::parser::position adder1_begin(nullptr, 1, 1); nmodl::parser::position adder1_end(nullptr, 1, 5); LocationType adder1_location(adder1_begin, adder1_end); @@ -85,6 +86,7 @@ TEST_CASE("Addition of two ModToken objects", "[token][modtoken]") { nmodl::parser::position adder2_begin(nullptr, 2, 1); nmodl::parser::position adder2_end(nullptr, 2, 5); + // NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) LocationType adder2_location(adder2_begin, adder2_end); ModToken adder2("text", 2, adder2_location); diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 07a8a60924..8035a4d060 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -17,6 +17,7 @@ using namespace nmodl; constexpr double max_error_norm = 1e-12; +// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numerical][solver]") { GIVEN("1 linear eq") { struct functor { @@ -115,18 +116,17 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer GIVEN("system of 4 non-linear eqs") { struct functor { - double _X0_old = 1.2345; - double _X1_old = 1.2345; - double _X2_old = 1.2345; - double _X3_old = 1.2345; + double X0_old = 1.2345; + double X1_old = 1.2345; + double X2_old = 1.2345; + double X3_old = 1.2345; double dt = 0.2; void operator()(const Eigen::Matrix& X, Eigen::Matrix& F) const { - F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - _X0_old + 2.0 * dt / X[1]); - F[1] = -(X[1] - _X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); - F[2] = -((X[2] * (X[2] - _X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / - X[2]); - F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - _X3_old + 6.0 * dt / X[2]); + F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - X0_old + 2.0 * dt / X[1]); + F[1] = -(X[1] - X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); + F[2] = -((X[2] * (X[2] - X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / X[2]); + F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - X3_old + 6.0 * dt / X[2]); } }; Eigen::Matrix X{0.21231, 0.4435, -0.11537, -0.8124312}; @@ -324,19 +324,18 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") GIVEN("system of 4 non-linear eqs") { struct functor { - double _X0_old = 1.2345; - double _X1_old = 1.2345; - double _X2_old = 1.2345; - double _X3_old = 1.2345; + double X0_old = 1.2345; + double X1_old = 1.2345; + double X2_old = 1.2345; + double X3_old = 1.2345; double dt = 0.2; void operator()(const Eigen::Matrix& X, Eigen::Matrix& F, Eigen::Matrix& J) const { - F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - _X0_old + 2.0 * dt / X[1]); - F[1] = -(X[1] - _X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); - F[2] = -((X[2] * (X[2] - _X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / - X[2]); - F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - _X3_old + 6.0 * dt / X[2]); + F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - X0_old + 2.0 * dt / X[1]); + F[1] = -(X[1] - X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); + F[2] = -((X[2] * (X[2] - X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / X[2]); + F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - X3_old + 6.0 * dt / X[2]); J(0, 0) = 3.0 * X[2] * dt - 1.0; J(0, 1) = 2.0 * dt / std::pow(X[1], 2); J(0, 2) = 3.0 * X[0] * dt; @@ -559,3 +558,4 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") } } } +// NOLINTEND(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index f0f3fec8c4..38881ac991 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -217,8 +217,7 @@ SCENARIO("Check parents in valid NMODL constructs") { //============================================================================= std::string solve_construct(const std::string& equation, std::string method) { - nmodl::parser::DiffeqDriver driver; - auto solution = driver.solve(equation, std::move(method)); + auto solution = nmodl::parser::DiffeqDriver::solve(equation, std::move(method)); return solution; } diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index 707ea8775b..1aa8d76164 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -40,7 +40,7 @@ SCENARIO("Symbol properties can be added and converted to string") { } } WHEN("adding another empty property") { - NmodlType result = prop1 | prop1; + NmodlType result = prop1 | prop1; // NOLINT(misc-redundant-expression) THEN("to_string still returns empty string") { REQUIRE(to_string(result).empty()); } @@ -75,7 +75,7 @@ SCENARIO("Symbol properties can be added and converted to string") { } WHEN("properties manipulated with bitwise operators") { THEN("& applied correctly") { - NmodlType result1 = prop2 & prop2; + NmodlType result1 = prop2 & prop2; // NOLINT(misc-redundant-expression) NmodlType result2 = prop1 & prop2; NmodlType result3 = prop1 & prop2 & prop3; REQUIRE(to_string(result1) == "local"); diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index ea7ea31b61..bfb4edb218 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -25,7 +25,7 @@ using namespace nmodl::test_utils; // Driver is defined as global to store all the units inserted to it and to be // able to define complex units based on base units -nmodl::parser::UnitDriver driver; +nmodl::parser::UnitDriver driver; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) bool is_valid_construct(const std::string& construct) { return driver.parse_string(construct); diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 89b013048e..5ada3fd366 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -60,7 +60,7 @@ namespace test_utils { * provided with the expected nmodl. */ -std::map nmodl_invalid_constructs{ +std::map const nmodl_invalid_constructs{ // clang-format off { "title_1", @@ -186,7 +186,7 @@ std::map nmodl_invalid_constructs{ // clang-format on }; -std::map nmodl_valid_constructs{ +std::map const nmodl_valid_constructs{ // clang-format off { "title_1", @@ -1480,7 +1480,7 @@ std::map nmodl_valid_constructs{ }; -std::vector diff_eq_constructs{ +std::vector const diff_eq_constructs{ // clang-format off /// differential equations from BlueBrain mod files including latest V6 branch diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp index 24a4c5cc10..de979535ba 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp @@ -55,9 +55,9 @@ struct DiffEqTestCase { std::string method; }; -extern std::map nmodl_invalid_constructs; -extern std::map nmodl_valid_constructs; -extern std::vector diff_eq_constructs; +extern std::map const nmodl_invalid_constructs; +extern std::map const nmodl_valid_constructs; +extern std::vector const diff_eq_constructs; } // namespace test_utils } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index 3dd8cf27fe..c1f88fccac 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -7,14 +7,17 @@ #include "utils/string_utils.hpp" +#include + namespace nmodl { namespace test_utils { int count_leading_spaces(std::string text) { - int length = text.size(); + auto const length = text.size(); nmodl::stringutils::ltrim(text); - int num_whitespaces = length - text.size(); - return num_whitespaces; + auto const num_whitespaces = length - text.size(); + assert(num_whitespaces <= std::numeric_limits::max()); + return static_cast(num_whitespaces); } /// check if string has only whitespaces diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 861899773f..b8ce0274d1 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -19,7 +19,6 @@ using namespace nmodl; using namespace visitor; using namespace test_utils; -using ast::AstNodeType; using nmodl::parser::NmodlDriver; using symtab::syminfo::NmodlType; diff --git a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp index c75ce6d110..f5edb9908a 100644 --- a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp @@ -19,9 +19,7 @@ using namespace nmodl; using namespace visitor; using namespace test_utils; -using ast::AstNodeType; using nmodl::parser::NmodlDriver; -using symtab::syminfo::NmodlType; //============================================================================= // IspcRename visitor tests diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index 17e4c45124..23fa6951de 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -19,7 +19,6 @@ using namespace nmodl; using namespace visitor; using namespace test_utils; -using ast::AstNodeType; using nmodl::parser::NmodlDriver; using symtab::syminfo::NmodlType; diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index 84e3146f06..de6829091d 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -18,8 +18,6 @@ using namespace test_utils; using ast::AstNodeType; using nmodl::parser::NmodlDriver; -using symtab::syminfo::NmodlType; - //============================================================================= // Ast lookup visitor tests diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index b1bc9d4c1e..353e7817ee 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -24,6 +24,10 @@ using namespace test_utils; using nmodl::parser::NmodlDriver; +namespace { +constexpr std::size_t output_precision{8}; +} + //============================================================================= // Unit visitor tests //============================================================================= @@ -55,8 +59,8 @@ std::string run_units_visitor(const std::string& text) { auto unit_name = unit_def->get_node_name(); unit_name.erase(remove_if(unit_name.begin(), unit_name.end(), isspace), unit_name.end()); auto unit = units_driver.table->get_unit(unit_name); - ss << std::fixed << std::setprecision(8) << unit->get_name() << ' ' << unit->get_factor() - << ':'; + ss << std::fixed << std::setprecision(output_precision) << unit->get_name() << ' ' + << unit->get_factor() << ':'; // Dimensions of the unit are printed to check that the units are successfully // parsed to the units::UnitTable int dimension_id = 0; @@ -80,7 +84,7 @@ std::string run_units_visitor(const std::string& text) { const auto& factor_defs = collect_nodes(*ast, {ast::AstNodeType::FACTOR_DEF}); for (const auto& factor_def: factor_defs) { auto unit = units_driver.table->get_unit(factor_def->get_node_name()); - ss << std::fixed << std::setprecision(8) << unit->get_name() << ' '; + ss << std::fixed << std::setprecision(output_precision) << unit->get_name() << ' '; auto factor_def_class = std::dynamic_pointer_cast(factor_def); ss << factor_def_class->get_value()->eval() << ':'; // Dimensions of the unit are printed to check that the units are successfully From 809d03e6f2568451e31ff26c7133551a4d6ff7db Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Aug 2022 09:00:24 +0200 Subject: [PATCH 449/871] Support for TABLE statements in FUNCTION blocks (BlueBrain/nmodl#913) * Add a check for table name list in TABLE statement In a function, the table statement is on the variable that got the name of the function. In procedure such mechanisms does not exists and so you should give at least one name in the list. mistune<3 See: https://github.com/jupyter/nbconvert/commit/e870d9a4a61432a65bee5466c5fa80c9ee28966e#diff-50c86b7ed8ac2cf95bd48334961bf0530cdc77b5a56f852c5c61b89d735fd711 NMODL Repo SHA: BlueBrain/nmodl@047811adbf975c8bd2b70958f356eff4abdd831e --- setup.py | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 201 ++++++++++-------- .../visitors/semantic_analysis_visitor.cpp | 24 ++- .../visitors/semantic_analysis_visitor.hpp | 7 +- .../transpiler/integration/mod/cabpump.mod | 7 +- 5 files changed, 147 insertions(+), 94 deletions(-) diff --git a/setup.py b/setup.py index 38a54f5204..5b853ee569 100644 --- a/setup.py +++ b/setup.py @@ -134,7 +134,7 @@ def _config_exe(exe_name): "jupyter-client", "jupyter", "myst_parser", - "mistune<2", # prevents a version conflict with nbconvert + "mistune<3", # prevents a version conflict with nbconvert "nbconvert", "nbsphinx>=0.3.2", "pytest>=3.7.2", diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 2ca65c2940..cf9d04ffce 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1497,22 +1497,21 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->start_block( fmt::format("void check_{}({})", method_name(name), get_parameter_str(internal_params))); { - printer->add_line(fmt::format("if ( {} == 0) {}", use_table_var, "{")); - printer->add_line(" return;"); - printer->add_line("}"); + printer->fmt_start_block("if ({} == 0)", use_table_var); + printer->add_line("return;"); + printer->end_block(1); printer->add_line("static bool make_table = true;"); for (const auto& variable: depend_variables) { - printer->add_line( - fmt::format("static {} save_{};", float_type, variable->get_node_name())); + printer->fmt_line("static {} save_{};", float_type, variable->get_node_name()); } for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); - printer->add_line(fmt::format("if (save_{} != {}) {}", name, instance_name, "{")); - printer->add_line(" make_table = true;"); - printer->add_line("}"); + printer->fmt_start_block("if (save_{} != {})", name, instance_name); + printer->add_line("make_table = true;"); + printer->end_block(1); } printer->start_block("if (make_table)"); @@ -1532,22 +1531,28 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->add_newline(); - printer->add_line(fmt::format("double dx = (tmax-{})/{}.0;", tmin_name, with)); - printer->add_line(fmt::format("{} = 1.0/dx;", mfac_name)); + printer->fmt_line("double dx = (tmax-{}) / {}.;", tmin_name, with); + printer->fmt_line("{} = 1./dx;", mfac_name); - printer->add_line("int i = 0;"); - printer->add_line("double x = 0;"); - printer->add_line(fmt::format( - "for(i = 0, x = {}; i < {}; x += dx, i++) {}", tmin_name, with + 1, "{")); + printer->fmt_line("double x = {};", tmin_name); + printer->fmt_start_block("for (size_t i = 0; i < {}; x += dx, i++)", with + 1); auto function = method_name("f_" + name); - printer->add_line(fmt::format(" {}({}, x);", function, internal_method_arguments())); - for (const auto& variable: table_variables) { - auto name = variable->get_node_name(); - auto instance_name = get_variable_name(name); + if (node.is_procedure_block()) { + printer->fmt_line("{}({}, x);", function, internal_method_arguments()); + for (const auto& variable: table_variables) { + auto name = variable->get_node_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("{}[i] = {};", table_name, instance_name); + } + } else { auto table_name = get_variable_name("t_" + name); - printer->add_line(fmt::format(" {}[i] = {};", table_name, instance_name)); + printer->fmt_line("{}[i] = {}({}, x);", + table_name, + function, + internal_method_arguments()); } - printer->add_line("}"); + printer->end_block(1); for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); @@ -1577,45 +1582,68 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { printer->start_block(); { const auto& params = node.get_parameters(); - printer->add_line(fmt::format("if ( {} == 0) {{", use_table_var)); - printer->add_line(fmt::format(" {}({}, {});", - function_name, - internal_method_arguments(), - params[0].get()->get_node_name())); - printer->add_line(" return 0;"); - printer->add_line("}"); + printer->fmt_start_block("if ({} == 0)", use_table_var); + if (node.is_procedure_block()) { + printer->fmt_line("{}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name()); + printer->add_line("return 0;"); + } else { + printer->fmt_line("return {}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name()); + } + printer->end_block(1); - printer->add_line(fmt::format( - "double xi = {} * ({} - {});", mfac_name, params[0].get()->get_node_name(), tmin_name)); - printer->add_line("if (isnan(xi)) {"); - for (const auto& var: table_variables) { - auto name = get_variable_name(var->get_node_name()); - printer->add_line(fmt::format(" {} = xi;", name)); + printer->fmt_line("double xi = {} * ({} - {});", + mfac_name, + params[0].get()->get_node_name(), + tmin_name); + printer->start_block("if (isnan(xi))"); + if (node.is_procedure_block()) { + for (const auto& var: table_variables) { + auto name = get_variable_name(var->get_node_name()); + printer->fmt_line("{} = xi;", name); + } + printer->add_line("return 0;"); + } else { + printer->add_line("return xi;"); } - printer->add_line(" return 0;"); - printer->add_line("}"); + printer->end_block(1); - printer->add_line(fmt::format("if (xi <= 0.0 || xi >= {}) {}", with, "{")); - printer->add_line(fmt::format(" int index = (xi <= 0.0) ? 0 : {};", with)); - for (const auto& variable: table_variables) { - auto name = variable->get_node_name(); - auto instance_name = get_variable_name(name); + printer->fmt_start_block("if (xi <= 0. || xi >= {}.)", with); + printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); + if (node.is_procedure_block()) { + for (const auto& variable: table_variables) { + auto name = variable->get_node_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("{} = {}[index];", instance_name, table_name); + } + printer->add_line("return 0;"); + } else { auto table_name = get_variable_name("t_" + name); - printer->add_line(fmt::format(" {} = {}[index];", instance_name, table_name)); + printer->fmt_line("return {}[index];", table_name); } - printer->add_line(" return 0;"); - printer->add_line("}"); + printer->end_block(1); printer->add_line("int i = int(xi);"); printer->add_line("double theta = xi - double(i);"); - for (const auto& var: table_variables) { - auto instance_name = get_variable_name(var->get_node_name()); - auto table_name = get_variable_name("t_" + var->get_node_name()); - printer->add_line( - fmt::format("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", instance_name, table_name)); + if (node.is_procedure_block()) { + for (const auto& var: table_variables) { + auto instance_name = get_variable_name(var->get_node_name()); + auto table_name = get_variable_name("t_" + var->get_node_name()); + printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", + instance_name, + table_name); + } + printer->add_line("return 0;"); + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("return {0}[i] + theta * ({0}[i+1] - {0}[i]);", table_name); } - - printer->add_line("return 0;"); } printer->end_block(1); } @@ -1630,16 +1658,16 @@ void CodegenCVisitor::print_check_table_thread_function() { auto name = method_name("check_table_thread"); auto parameters = external_method_parameters(true); - printer->add_line(fmt::format("static void {} ({}) {}", name, parameters, "{")); - printer->add_line(" Memb_list* ml = nt->_ml_list[tml_id];"); - printer->add_line(" setup_instance(nt, ml);"); - printer->add_line(fmt::format(" {0}* inst = ({0}*) ml->instance;", instance_struct())); - printer->add_line(" double v = 0;"); + printer->fmt_start_block("static void {} ({})", name, parameters); + printer->add_line("Memb_list* ml = nt->_ml_list[tml_id];"); + printer->add_line("setup_instance(nt, ml);"); + printer->fmt_line("{0}* inst = ({0}*) ml->instance;", instance_struct()); + printer->add_line("double v = 0;"); for (const auto& function: info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); - printer->add_line(fmt::format(" {}({});", name, arguments)); + printer->add_line(fmt::format("{}({});", name, arguments)); } /** @@ -1647,8 +1675,8 @@ void CodegenCVisitor::print_check_table_thread_function() { * after `finitialize`. If we cleaup the instance then it will result in segfault * but if we don't then there is memory leak */ - printer->add_line(" // cleanup_instance(ml);"); - printer->add_line("}"); + printer->add_line("// cleanup_instance(ml);"); + printer->end_block(1); } @@ -2447,19 +2475,18 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); - printer->add_line(fmt::format("struct {} {}", global_struct(), "{")); - printer->increase_indent(); + printer->fmt_start_block("struct {}", global_struct()); if (!info.ions.empty()) { for (const auto& ion: info.ions) { auto name = fmt::format("{}_type", ion.name); - printer->add_line(fmt::format("{}int {};", qualifier, name)); + printer->fmt_line("{}int {};", qualifier, name); codegen_global_variables.push_back(make_symbol(name)); } } if (info.point_process) { - printer->add_line(fmt::format("{}int point_type;", qualifier)); + printer->fmt_line("{}int point_type;", qualifier); codegen_global_variables.push_back(make_symbol("point_type")); } @@ -2468,7 +2495,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { - printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); + printer->fmt_line("{}{} {};", qualifier, float_type, name); codegen_global_variables.push_back(make_symbol(name)); } } @@ -2484,28 +2511,30 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line(fmt::format("{}{} {}[{}];", qualifier, float_type, name, length)); + printer->fmt_line("{}{} {}[{}];", qualifier, float_type, name, length); } else { - printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); + printer->fmt_line("{}{} {};", qualifier, float_type, name); } codegen_global_variables.push_back(var); } } if (!info.thread_variables.empty()) { - printer->add_line(fmt::format("{}int thread_data_in_use;", qualifier)); - printer->add_line( - fmt::format("{}{} thread_data[{}];", qualifier, float_type, info.thread_var_data_size)); + printer->fmt_line("{}int thread_data_in_use;", qualifier); + printer->fmt_line("{}{} thread_data[{}];", + qualifier, + float_type, + info.thread_var_data_size); codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); symbol->set_as_array(info.thread_var_data_size); codegen_global_variables.push_back(symbol); } - printer->add_line(fmt::format("{}int reset;", qualifier)); + printer->fmt_line("{}int reset;", qualifier); codegen_global_variables.push_back(make_symbol("reset")); - printer->add_line(fmt::format("{}int mech_type;", qualifier)); + printer->fmt_line("{}int mech_type;", qualifier); codegen_global_variables.push_back(make_symbol("mech_type")); auto& globals = info.global_variables; @@ -2516,9 +2545,9 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->add_line(fmt::format("{}{} {}[{}];", qualifier, float_type, name, length)); + printer->fmt_line("{}{} {}[{}];", qualifier, float_type, name, length); } else { - printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); + printer->fmt_line("{}{} {};", qualifier, float_type, name); } codegen_global_variables.push_back(var); } @@ -2528,50 +2557,54 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { for (const auto& var: constants) { auto name = var->get_name(); auto value_ptr = var->get_value(); - printer->add_line(fmt::format("{}{} {};", qualifier, float_type, name)); + printer->fmt_line("{}{} {};", qualifier, float_type, name); codegen_global_variables.push_back(var); } } if (info.primes_size != 0) { - printer->add_line(fmt::format("int* {}slist1;", qualifier)); - printer->add_line(fmt::format("int* {}dlist1;", qualifier)); + printer->fmt_line("int* {}slist1;", qualifier); + printer->fmt_line("int* {}dlist1;", qualifier); codegen_global_variables.push_back(make_symbol("slist1")); codegen_global_variables.push_back(make_symbol("dlist1")); if (info.derivimplicit_used()) { - printer->add_line(fmt::format("int* {}slist2;", qualifier)); + printer->fmt_line("int* {}slist2;", qualifier); codegen_global_variables.push_back(make_symbol("slist2")); } } if (info.table_count > 0) { - printer->add_line(fmt::format("{}double usetable;", qualifier)); + printer->fmt_line("{}double usetable;", qualifier); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); for (const auto& block: info.functions_with_table) { auto name = block->get_node_name(); - printer->add_line(fmt::format("{}{} tmin_{};", qualifier, float_type, name)); - printer->add_line(fmt::format("{}{} mfac_{};", qualifier, float_type, name)); + printer->fmt_line("{}{} tmin_{};", qualifier, float_type, name); + printer->fmt_line("{}{} mfac_{};", qualifier, float_type, name); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); + if (block->is_function_block()) { + printer->fmt_line("{}* {}t_{};", float_type, qualifier, name); + codegen_global_variables.push_back(make_symbol("t_" + name)); + } } for (const auto& variable: info.table_statement_variables) { auto name = "t_" + variable->get_name(); - printer->add_line(fmt::format("{}* {}{};", float_type, qualifier, name)); + printer->fmt_line("{}* {}{};", float_type, qualifier, name); codegen_global_variables.push_back(make_symbol(name)); } } if (info.vectorize) { - printer->add_line(fmt::format("ThreadDatum* {}ext_call_thread;", qualifier)); + printer->fmt_line("ThreadDatum* {}ext_call_thread;", qualifier); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } - printer->decrease_indent(); - printer->add_line("};"); + printer->end_block(0); + printer->add_text(";"); + printer->add_newline(2); - printer->add_newline(1); printer->add_line("/** holds object of global variable */"); print_global_variable_device_create_annotation_pre(); print_global_var_struct_decl(); diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index bbb6cab0ee..618200edc3 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -28,31 +28,43 @@ bool SemanticAnalysisVisitor::check(const ast::Program& node) { void SemanticAnalysisVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { /// <-- This code is for check 1 - in_procedure_function = true; + in_procedure = true; one_arg_in_procedure_function = node.get_parameters().size() == 1; node.visit_children(*this); - in_procedure_function = false; + in_procedure = false; /// --> } void SemanticAnalysisVisitor::visit_function_block(const ast::FunctionBlock& node) { /// <-- This code is for check 1 - in_procedure_function = true; + in_function = true; one_arg_in_procedure_function = node.get_parameters().size() == 1; node.visit_children(*this); - in_procedure_function = false; + in_function = false; /// --> } -void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement& /* node */) { +void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement& tableStmt) { /// <-- This code is for check 1 - if (in_procedure_function && !one_arg_in_procedure_function) { + if ((in_function || in_procedure) && !one_arg_in_procedure_function) { logger->critical( "SemanticAnalysisVisitor :: The procedure or function containing the TABLE statement " "should contains exactly one argument."); check_fail = true; } /// --> + /// <-- This code is for check 3 + const auto& table_vars = tableStmt.get_table_vars(); + if (in_function && !table_vars.empty()) { + logger->critical( + "SemanticAnalysisVisitor :: TABLE statement in FUNCTION cannot have a table name " + "list."); + } + if (in_procedure && table_vars.empty()) { + logger->critical( + "SemanticAnalysisVisitor :: TABLE statement in PROCEDURE must have a table name list."); + } + /// --> } void SemanticAnalysisVisitor::visit_destructor_block(const ast::DestructorBlock& /* node */) { diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 54574f584b..7d1bbb90e2 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -26,6 +26,7 @@ * 1. Check that a function or a procedure containing a TABLE statement contains only one argument * (mandatory in mod2c). * 2. Check that destructor blocks are only inside mod file that are point_process + * 3. A TABLE statement in functions cannot have name list, and should have one in procedures */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -39,8 +40,10 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// true if the procedure or the function contains only one argument bool one_arg_in_procedure_function = false; - /// true if we are in a procedure or a function block - bool in_procedure_function = false; + /// true if we are in a procedure block + bool in_procedure = false; + /// true if we are in a function block + bool in_function = false; /// true if the mod file is of type point process bool is_point_process = false; diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index a01e1b9607..1988445dd9 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -57,7 +57,12 @@ DERIVATIVE state { } : to test code generation for TABLE statement -PROCEDURE test_table(br) { +FUNCTION test_table_f(br) { + TABLE FROM 0 TO FOO WITH 1 + test_table_f = 1 +} + +PROCEDURE test_table_p(br) { TABLE ainf FROM 0 TO FOO WITH 1 ainf = 1 } From 29ad334ce75dfdba99049698d277b45cad6105c3 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 24 Aug 2022 09:42:50 +0200 Subject: [PATCH 450/871] Not prepend argument with t+ in net_move (BlueBrain/nmodl#915) in mod2c call from net_send are prepend with t+ but not net_move NMODL Repo SHA: BlueBrain/nmodl@d996a004b717af54d83996d36619698d613bd179 --- src/nmodl/codegen/codegen_c_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index cf9d04ffce..56f4d54a15 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -3827,7 +3827,7 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text(fmt::format("artcell_net_move(&{}, {}, nt->_t+", tqitem, pnt)); + printer->add_text(fmt::format("artcell_net_move(&{}, {}, ", tqitem, pnt)); print_vector_elements(arguments, ", "); printer->add_text(")"); } else { From 25d44e5b168583879ea67e960fa742158bc277a6 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Sun, 28 Aug 2022 13:51:41 +0200 Subject: [PATCH 451/871] Reorganise "global" variables to not be global (BlueBrain/nmodl#904) Various fixes to make GLOBAL variables in order to support shared library support for GPU/OpenACC build. * sync global variables like celsius from global to instance struct * partialPivLu: use nvc++ -cuda * instance struct no longer in unified memory * drop OpenMP async wait * fixes for ISPC, also drop ispc_celsius * cnrn_target_update_on_device * fix codegen with TABLE * fix unit tests * fmt: use upstream master with my nvhpc/22.3 + c++17 fix * global variables are always accessed via the instance struct in .ispc Related to https://github.com/BlueBrain/CoreNeuron/pull/795 NMODL Repo SHA: BlueBrain/nmodl@4f45a1c8a9b99c64127ea795eb12952e754b775c --- src/nmodl/codegen/codegen_acc_visitor.cpp | 86 +-- src/nmodl/codegen/codegen_acc_visitor.hpp | 17 +- src/nmodl/codegen/codegen_c_visitor.cpp | 713 +++++++++--------- src/nmodl/codegen/codegen_c_visitor.hpp | 82 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 14 + src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 + src/nmodl/codegen/codegen_ispc_visitor.cpp | 50 +- src/nmodl/codegen/codegen_ispc_visitor.hpp | 6 +- src/nmodl/codegen/codegen_naming.hpp | 12 + src/nmodl/lexer/token_mapping.cpp | 2 +- src/nmodl/main.cpp | 6 + src/nmodl/printer/code_printer.cpp | 6 + src/nmodl/printer/code_printer.hpp | 4 + .../solver/partial_piv_lu/partial_piv_lu.cpp | 17 + .../solver/partial_piv_lu/partial_piv_lu.cu | 60 -- .../solver/partial_piv_lu/partial_piv_lu.h | 89 ++- src/nmodl/visitors/CMakeLists.txt | 1 + .../visitors/implicit_argument_visitor.cpp | 56 ++ .../visitors/implicit_argument_visitor.hpp | 41 + test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../unit/codegen/codegen_c_visitor.cpp | 141 +++- .../unit/visitor/implicit_argument.cpp | 60 ++ 24 files changed, 839 insertions(+), 631 deletions(-) create mode 100644 src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp delete mode 100644 src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu create mode 100644 src/nmodl/visitors/implicit_argument_visitor.cpp create mode 100644 src/nmodl/visitors/implicit_argument_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/implicit_argument.cpp diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index b7d56cc1ff..e11cf0ea19 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -47,12 +47,9 @@ void CodegenAccVisitor::print_channel_iteration_block_parallel_hint(BlockType ty } } present_clause << ')'; - printer->add_line( - fmt::format("nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", - present_clause.str())); - printer->add_line( - "nrn_pragma_omp(target teams distribute parallel for is_device_ptr(inst) " - "if(nt->compute_gpu))"); + printer->fmt_line("nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", + present_clause.str()); + printer->add_line("nrn_pragma_omp(target teams distribute parallel for if(nt->compute_gpu))"); } @@ -159,10 +156,7 @@ void CodegenAccVisitor::print_eigen_linear_solver(const std::string& /* float_ty */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_begin() { if (!info.artificial_cell) { - auto global_variable = fmt::format("{}_global", info.mod_suffix); - printer->add_line( - fmt::format("nrn_pragma_acc(data present(nt, ml, {}) if(nt->compute_gpu))", - global_variable)); + printer->add_line("nrn_pragma_acc(data present(nt, ml) if(nt->compute_gpu))"); printer->add_line("{"); printer->increase_indent(); } @@ -187,8 +181,7 @@ void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_begin() { void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_end() { if (!info.artificial_cell) { - printer->add_line("}"); - printer->decrease_indent(); + printer->end_block(1); } } @@ -234,8 +227,7 @@ void CodegenAccVisitor::print_nrn_cur_matrix_shadow_reduction() { */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_end() { if (!info.artificial_cell) { - printer->decrease_indent(); - printer->add_line("}"); + printer->end_block(1); } } @@ -250,36 +242,13 @@ bool CodegenAccVisitor::nrn_cur_reduction_loop_required() { } -void CodegenAccVisitor::print_global_variable_device_create_annotation_pre() { - if (!info.artificial_cell) { - printer->add_line("nrn_pragma_omp(declare target)"); - } -} - -void CodegenAccVisitor::print_global_variable_device_create_annotation_post() { - if (!info.artificial_cell) { - printer->add_line( - fmt::format("nrn_pragma_acc(declare create ({}_global))", info.mod_suffix)); - printer->add_line("nrn_pragma_omp(end declare target)"); - } -} - void CodegenAccVisitor::print_global_variable_device_update_annotation() { if (!info.artificial_cell) { - printer->add_line( - fmt::format("nrn_pragma_acc(update device ({}_global))", info.mod_suffix)); - printer->add_line( - fmt::format("nrn_pragma_omp(target update to({}_global))", info.mod_suffix)); - } -} - - -std::string CodegenAccVisitor::get_variable_device_pointer(const std::string& variable, - const std::string& /* type */) const { - if (info.artificial_cell) { - return variable; + printer->start_block("if (nt->compute_gpu)"); + printer->fmt_line("nrn_pragma_acc(update device ({}))", global_struct_instance()); + printer->fmt_line("nrn_pragma_omp(target update to({}))", global_struct_instance()); + printer->end_block(1); } - return fmt::format("nt->compute_gpu ? cnrn_target_deviceptr({}) : {}", variable, variable); } @@ -299,16 +268,38 @@ void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { } -void CodegenAccVisitor::print_instance_variable_transfer_to_device() const { - if (!info.artificial_cell) { - printer->start_block("if(nt->compute_gpu)"); - printer->add_line("Memb_list* dml = cnrn_target_deviceptr(ml);"); - printer->add_line("cnrn_target_memcpy_to_device(&(dml->instance), &(ml->instance));"); - printer->end_block(1); +void CodegenAccVisitor::print_instance_variable_transfer_to_device( + std::vector const& ptr_members) const { + if (info.artificial_cell) { + return; } + printer->start_block("if (!nt->compute_gpu)"); + printer->add_line("return;"); + printer->end_block(1); + printer->fmt_line("auto tmp = *inst;"); + printer->add_line("auto* d_inst = cnrn_target_is_present(inst);"); + printer->start_block("if (!d_inst)"); + printer->add_line("d_inst = cnrn_target_copyin(inst);"); + printer->end_block(1); + for (auto const& ptr_mem: ptr_members) { + printer->fmt_line("tmp.{0} = cnrn_target_deviceptr(tmp.{0});", ptr_mem); + } + printer->add_line("cnrn_target_memcpy_to_device(d_inst, &tmp);"); + printer->add_line("auto* d_ml = cnrn_target_deviceptr(ml);"); + printer->add_line("void* d_inst_void = d_inst;"); + printer->add_line("cnrn_target_memcpy_to_device(&(d_ml->instance), &d_inst_void);"); } +void CodegenAccVisitor::print_instance_variable_deletion_from_device() const { + if (info.artificial_cell) { + return; + } + printer->start_block("if (cnrn_target_is_present(&inst))"); + printer->add_line("cnrn_target_delete(&inst);"); + printer->end_block(1); +} + void CodegenAccVisitor::print_deriv_advance_flag_transfer_to_device() const { printer->add_line("nrn_pragma_acc(update device (deriv_advance_flag) if(nt->compute_gpu))"); printer->add_line("nrn_pragma_omp(target update to(deriv_advance_flag) if(nt->compute_gpu))"); @@ -324,7 +315,6 @@ void CodegenAccVisitor::print_device_atomic_capture_annotation() const { void CodegenAccVisitor::print_device_stream_wait() const { printer->start_block("if(nt->compute_gpu)"); printer->add_line("nrn_pragma_acc(wait(nt->stream_id))"); - printer->add_line("nrn_pragma_omp(taskwait)"); printer->end_block(1); } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 9446960189..350fe9abbb 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -86,19 +86,18 @@ class CodegenAccVisitor: public CodegenCVisitor { /// if reduction block in nrn_cur required bool nrn_cur_reduction_loop_required() override; - - /// create global variable on the device - void print_global_variable_device_create_annotation_pre() override; - void print_global_variable_device_create_annotation_post() override; - /// update global variable from host to the device void print_global_variable_device_update_annotation() override; /// transfer newtonspace structure to device void print_newtonspace_transfer_to_device() const override; - // update instance variable object pointer on the gpu device - void print_instance_variable_transfer_to_device() const override; + /// copy the instance struct to the device + void print_instance_variable_transfer_to_device( + std::vector const& ptr_members) const override; + + /// delete the instance struct from the device + void print_instance_variable_deletion_from_device() const override; // update derivimplicit advance flag on the gpu device void print_deriv_advance_flag_transfer_to_device() const override; @@ -121,10 +120,6 @@ class CodegenAccVisitor: public CodegenCVisitor { // print atomic capture pragma void print_device_atomic_capture_annotation() const override; - std::string get_variable_device_pointer(const std::string& variable, - const std::string& type) const override; - - void print_net_send_buffering_grow() override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 56f4d54a15..cf7ab83c1a 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1040,10 +1040,18 @@ void CodegenCVisitor::print_channel_iteration_tiling_block_end() { // backend specific, do nothing } -void CodegenCVisitor::print_instance_variable_transfer_to_device() const { + +void CodegenCVisitor::print_instance_variable_transfer_to_device( + std::vector const& ptr_members) const { + // backend specific, do nothing +} + + +void CodegenCVisitor::print_instance_variable_deletion_from_device() const { // backend specific, do nothing } + void CodegenCVisitor::print_deriv_advance_flag_transfer_to_device() const { // backend specific, do nothing } @@ -1298,7 +1306,7 @@ std::string CodegenCVisitor::global_var_struct_type_qualifier() { } void CodegenCVisitor::print_global_var_struct_decl() { - printer->add_line(fmt::format("{} {}_global;", global_struct(), info.mod_suffix)); + printer->fmt_line("{} {};", global_struct(), global_struct_instance()); } /****************************************************************************************/ @@ -1366,26 +1374,11 @@ void CodegenCVisitor::print_function_call(const FunctionCall& node) { auto arguments = node.get_arguments(); printer->add_text(fmt::format("{}(", function_name)); - bool possible_nt_variable_types{false}; - if (!defined_method(name)) { - possible_nt_variable_types = arguments.front()->get_node_type() == AstNodeType::NAME && - arguments.front()->get_node_type() == AstNodeType::STRING && - arguments.front()->get_node_type() == - AstNodeType::CONSTANT_VAR && - arguments.front()->get_node_type() == AstNodeType::VAR_NAME && - arguments.front()->get_node_type() == AstNodeType::LOCAL_VAR; - } if (defined_method(name)) { printer->add_text(internal_method_arguments()); if (!arguments.empty()) { printer->add_text(", "); } - } else if (nmodl::details::needs_neuron_thread_first_arg(function_name) && - (!possible_nt_variable_types || arguments.front()->get_node_name() != "nt")) { - // If the first argument is not `nt` we should add it. - // We compare with type because expression `a+b` is not `nt` but don't have `get_node_name` - // implemented - arguments.insert(arguments.begin(), std::make_shared("nt")); } print_vector_elements(arguments, ", "); @@ -1494,8 +1487,9 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->add_newline(2); print_device_method_annotation(); - printer->start_block( - fmt::format("void check_{}({})", method_name(name), get_parameter_str(internal_params))); + printer->fmt_start_block("void check_{}({})", + method_name(name), + get_parameter_str(internal_params)); { printer->fmt_start_block("if ({} == 0)", use_table_var); printer->add_line("return;"); @@ -1535,7 +1529,7 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->fmt_line("{} = 1./dx;", mfac_name); printer->fmt_line("double x = {};", tmin_name); - printer->fmt_start_block("for (size_t i = 0; i < {}; x += dx, i++)", with + 1); + printer->fmt_start_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); auto function = method_name("f_" + name); if (node.is_procedure_block()) { printer->fmt_line("{}({}, x);", function, internal_method_arguments()); @@ -1557,7 +1551,7 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { for (const auto& variable: depend_variables) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); - printer->add_line(fmt::format("save_{} = {};", name, instance_name)); + printer->fmt_line("save_{} = {};", name, instance_name); } } printer->end_block(1); @@ -1659,23 +1653,16 @@ void CodegenCVisitor::print_check_table_thread_function() { auto parameters = external_method_parameters(true); printer->fmt_start_block("static void {} ({})", name, parameters); - printer->add_line("Memb_list* ml = nt->_ml_list[tml_id];"); printer->add_line("setup_instance(nt, ml);"); - printer->fmt_line("{0}* inst = ({0}*) ml->instance;", instance_struct()); + printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); printer->add_line("double v = 0;"); for (const auto& function: info.functions_with_table) { auto name = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); - printer->add_line(fmt::format("{}({});", name, arguments)); + printer->fmt_line("{}({});", name, arguments); } - /** - * \todo `check_table_thread` is called multiple times from coreneuron including - * after `finitialize`. If we cleaup the instance then it will result in segfault - * but if we don't then there is memory leak - */ - printer->add_line("// cleanup_instance(ml);"); printer->end_block(1); } @@ -1909,9 +1896,9 @@ void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, i std::string CodegenCVisitor::internal_method_arguments() { if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, ml, v"; } - return "id, pnodecount, inst, data, indexes, thread, nt, v"; + return "id, pnodecount, inst, data, indexes, thread, nt, ml, v"; } @@ -1943,31 +1930,32 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { params.emplace_back("const ", "Datum*", "", "indexes"); params.emplace_back(param_type_qualifier(), "ThreadDatum*", "", "thread"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); + params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); params.emplace_back("", "double", "", "v"); return params; } std::string CodegenCVisitor::external_method_arguments() { - return "id, pnodecount, data, indexes, thread, nt, v"; + return "id, pnodecount, data, indexes, thread, nt, ml, v"; } std::string CodegenCVisitor::external_method_parameters(bool table) { if (table) { return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, int tml_id"; + "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, int tml_id"; } return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, double v"; + "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v"; } std::string CodegenCVisitor::nrn_thread_arguments() { if (ion_variable_struct_required()) { - return "id, pnodecount, ionvar, data, indexes, thread, nt, v"; + return "id, pnodecount, ionvar, data, indexes, thread, nt, ml, v"; } - return "id, pnodecount, data, indexes, thread, nt, v"; + return "id, pnodecount, data, indexes, thread, nt, ml, v"; } @@ -1977,9 +1965,9 @@ std::string CodegenCVisitor::nrn_thread_arguments() { */ std::string CodegenCVisitor::nrn_thread_internal_arguments() { if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, ml, v"; } - return "id, pnodecount, inst, data, indexes, thread, nt, v"; + return "id, pnodecount, inst, data, indexes, thread, nt, ml, v"; } @@ -2044,15 +2032,19 @@ std::string CodegenCVisitor::process_verbatim_text(std::string const& text) { std::string CodegenCVisitor::register_mechanism_arguments() const { - auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "NULL"; - auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "NULL"; + auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "nullptr"; + auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr"; auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); auto nrn_init = method_name(naming::NRN_INIT_METHOD); - return fmt::format("mechanism, {}, {}, NULL, {}, {}, first_pointer_var_index()", + auto const nrn_private_constructor = method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD); + auto const nrn_private_destructor = method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD); + return fmt::format("mechanism, {}, {}, nullptr, {}, {}, {}, {}, first_pointer_var_index()", nrn_alloc, nrn_cur, nrn_state, - nrn_init); + nrn_init, + nrn_private_constructor, + nrn_private_destructor); } @@ -2079,12 +2071,13 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, " {}," " {}," " nrn_ion_global_map," - " celsius," + " {}," " nt->_ml_list[{}_type]->_nodecount_padded)", ion_name, conc_var_name, index, style_var_name, + get_variable_name(naming::CELSIUS_VARIABLE), ion_name); } @@ -2176,9 +2169,10 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { void CodegenCVisitor::print_mech_type_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int get_mech_type() {"); - printer->add_line(fmt::format(" return {};", get_variable_name("mech_type"))); - printer->add_line("}"); + printer->start_block("static inline int get_mech_type()"); + // false => get it from the host-only global struct, not the instance structure + printer->fmt_line("return {};", get_variable_name("mech_type", false)); + printer->end_block(1); } @@ -2312,8 +2306,13 @@ std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, } -std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol) const { - return fmt::format("{}_global.{}", info.mod_suffix, symbol->get_name()); +std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol, + bool use_instance) const { + if (use_instance) { + return fmt::format("inst->{}->{}", naming::INST_GLOBAL_MEMBER, symbol->get_name()); + } else { + return fmt::format("{}.{}", global_struct_instance(), symbol->get_name()); + } } @@ -2372,7 +2371,7 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use codegen_global_variables.end(), symbol_comparator); if (g != codegen_global_variables.end()) { - return global_variable_name(*g); + return global_variable_name(*g, use_instance); } // shadow variable @@ -2393,6 +2392,22 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use return std::string("nt->_") + naming::NTHREAD_T_VARIABLE; } + auto const iter = + std::find_if(info.neuron_global_variables.begin(), + info.neuron_global_variables.end(), + [&varname](auto const& entry) { return entry.first->get_name() == varname; }); + if (iter != info.neuron_global_variables.end()) { + std::string ret; + if (use_instance) { + ret = "*(inst->"; + } + ret.append(varname); + if (use_instance) { + ret.append(")"); + } + return ret; + } + // otherwise return original name return varname; } @@ -2469,7 +2484,8 @@ void CodegenCVisitor::print_coreneuron_includes() { * same for some variables to keep same code as neuron. */ // NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCVisitor::print_mechanism_global_var_structure() { +void CodegenCVisitor::print_mechanism_global_var_structure(bool print_initialisers) { + const auto value_initialise = print_initialisers ? "{}" : ""; const auto qualifier = global_var_struct_type_qualifier(); auto float_type = default_float_data_type(); @@ -2477,27 +2493,23 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { printer->add_line("/** all global variables */"); printer->fmt_start_block("struct {}", global_struct()); - if (!info.ions.empty()) { - for (const auto& ion: info.ions) { - auto name = fmt::format("{}_type", ion.name); - printer->fmt_line("{}int {};", qualifier, name); - codegen_global_variables.push_back(make_symbol(name)); - } + for (const auto& ion: info.ions) { + auto name = fmt::format("{}_type", ion.name); + printer->fmt_line("{}int {}{};", qualifier, name, value_initialise); + codegen_global_variables.push_back(make_symbol(name)); } if (info.point_process) { - printer->fmt_line("{}int point_type;", qualifier); + printer->fmt_line("{}int point_type{};", qualifier, value_initialise); codegen_global_variables.push_back(make_symbol("point_type")); } - if (!info.state_vars.empty()) { - for (const auto& var: info.state_vars) { - auto name = var->get_name() + "0"; - auto symbol = program_symtab->lookup(name); - if (symbol == nullptr) { - printer->fmt_line("{}{} {};", qualifier, float_type, name); - codegen_global_variables.push_back(make_symbol(name)); - } + for (const auto& var: info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + printer->fmt_line("{}{} {}{};", qualifier, float_type, name, value_initialise); + codegen_global_variables.push_back(make_symbol(name)); } } @@ -2511,17 +2523,24 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { auto name = var->get_name(); auto length = var->get_length(); if (var->is_array()) { - printer->fmt_line("{}{} {}[{}];", qualifier, float_type, name, length); + printer->fmt_line("{}{} {}[{}] /* TODO init top-local-array */;", + qualifier, + float_type, + name, + length); } else { - printer->fmt_line("{}{} {};", qualifier, float_type, name); + printer->fmt_line("{}{} {} /* TODO init top-local */;", + qualifier, + float_type, + name); } codegen_global_variables.push_back(var); } } if (!info.thread_variables.empty()) { - printer->fmt_line("{}int thread_data_in_use;", qualifier); - printer->fmt_line("{}{} thread_data[{}];", + printer->fmt_line("{}int thread_data_in_use{};", qualifier, value_initialise); + printer->fmt_line("{}{} thread_data[{}] /* TODO init thread_data */;", qualifier, float_type, info.thread_var_data_size); @@ -2531,56 +2550,97 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { codegen_global_variables.push_back(symbol); } - printer->fmt_line("{}int reset;", qualifier); + // TODO: remove this entirely? + printer->fmt_line("{}int reset{};", qualifier, value_initialise); codegen_global_variables.push_back(make_symbol("reset")); - printer->fmt_line("{}int mech_type;", qualifier); + printer->fmt_line("{}int mech_type{};", qualifier, value_initialise); codegen_global_variables.push_back(make_symbol("mech_type")); - auto& globals = info.global_variables; - auto& constants = info.constant_variables; - - if (!globals.empty()) { - for (const auto& var: globals) { - auto name = var->get_name(); - auto length = var->get_length(); - if (var->is_array()) { - printer->fmt_line("{}{} {}[{}];", qualifier, float_type, name, length); - } else { - printer->fmt_line("{}{} {};", qualifier, float_type, name); + for (const auto& var: info.global_variables) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->fmt_line( + "{}{} {}[{}] /* TODO init const-array */;", qualifier, float_type, name, length); + } else { + double value{}; + if (auto const& value_ptr = var->get_value()) { + value = *value_ptr; } - codegen_global_variables.push_back(var); + printer->fmt_line("{}{} {}{};", + qualifier, + float_type, + name, + print_initialisers ? fmt::format("{{{:g}}}", value) : std::string{}); } + codegen_global_variables.push_back(var); } - if (!constants.empty()) { - for (const auto& var: constants) { - auto name = var->get_name(); - auto value_ptr = var->get_value(); - printer->fmt_line("{}{} {};", qualifier, float_type, name); - codegen_global_variables.push_back(var); - } + for (const auto& var: info.constant_variables) { + auto const name = var->get_name(); + auto* const value_ptr = var->get_value().get(); + double const value{value_ptr ? *value_ptr : 0}; + printer->fmt_line("{}{} {}{};", + qualifier, + float_type, + name, + print_initialisers ? fmt::format("{{{:g}}}", value) : std::string{}); + codegen_global_variables.push_back(var); } if (info.primes_size != 0) { - printer->fmt_line("int* {}slist1;", qualifier); - printer->fmt_line("int* {}dlist1;", qualifier); + if (info.primes_size != info.prime_variables_by_order.size()) { + throw std::runtime_error{ + fmt::format("primes_size = {} differs from prime_variables_by_order.size() = {}, " + "this should not happen.", + info.primes_size, + info.prime_variables_by_order.size())}; + } + auto const initializer_list = [&](auto const& primes, const char* prefix) -> std::string { + if (!print_initialisers) { + return {}; + } + std::string list{"{"}; + for (auto iter = primes.begin(); iter != primes.end(); ++iter) { + auto const& prime = *iter; + list.append(std::to_string(position_of_float_var(prefix + prime->get_name()))); + if (std::next(iter) != primes.end()) { + list.append(", "); + } + } + list.append("}"); + return list; + }; + printer->fmt_line("{}int slist1[{}]{};", + qualifier, + info.primes_size, + initializer_list(info.prime_variables_by_order, "")); + printer->fmt_line("{}int dlist1[{}]{};", + qualifier, + info.primes_size, + initializer_list(info.prime_variables_by_order, "D")); codegen_global_variables.push_back(make_symbol("slist1")); codegen_global_variables.push_back(make_symbol("dlist1")); + // additional list for derivimplicit method if (info.derivimplicit_used()) { - printer->fmt_line("int* {}slist2;", qualifier); + auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); + printer->fmt_line("{}int slist2[{}]{};", + qualifier, + info.primes_size, + initializer_list(primes, "")); codegen_global_variables.push_back(make_symbol("slist2")); } } if (info.table_count > 0) { - printer->fmt_line("{}double usetable;", qualifier); + printer->fmt_line("{}double usetable{};", qualifier, print_initialisers ? "{1}" : ""); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); for (const auto& block: info.functions_with_table) { auto name = block->get_node_name(); - printer->fmt_line("{}{} tmin_{};", qualifier, float_type, name); - printer->fmt_line("{}{} mfac_{};", qualifier, float_type, name); + printer->fmt_line("{}{} tmin_{}{};", qualifier, float_type, name, value_initialise); + printer->fmt_line("{}{} mfac_{}{};", qualifier, float_type, name, value_initialise); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); if (block->is_function_block()) { @@ -2590,27 +2650,38 @@ void CodegenCVisitor::print_mechanism_global_var_structure() { } for (const auto& variable: info.table_statement_variables) { - auto name = "t_" + variable->get_name(); - printer->fmt_line("{}* {}{};", float_type, qualifier, name); + auto const name = "t_" + variable->get_name(); + auto const num_values = variable->get_num_values(); + printer->fmt_line( + "{}{} {}[{}]{};", qualifier, float_type, name, num_values, value_initialise); codegen_global_variables.push_back(make_symbol(name)); } } - if (info.vectorize) { - printer->fmt_line("ThreadDatum* {}ext_call_thread;", qualifier); + if (info.vectorize && info.thread_data_index) { + printer->fmt_line("{}ThreadDatum ext_call_thread[{}]{};", + qualifier, + info.thread_data_index, + value_initialise); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } - printer->end_block(0); - printer->add_text(";"); - printer->add_newline(2); + printer->end_block(";"); - printer->add_line("/** holds object of global variable */"); - print_global_variable_device_create_annotation_pre(); + print_global_var_struct_assertions(); print_global_var_struct_decl(); +} - // create copy on the device - print_global_variable_device_create_annotation_post(); +void CodegenCVisitor::print_global_var_struct_assertions() const { + // Assert some things that we assume when copying instances of this struct + // to the GPU and so on. + printer->fmt_line("static_assert(std::is_trivially_copy_constructible_v<{}>);", + global_struct()); + printer->fmt_line("static_assert(std::is_trivially_move_constructible_v<{}>);", + global_struct()); + printer->fmt_line("static_assert(std::is_trivially_copy_assignable_v<{}>);", global_struct()); + printer->fmt_line("static_assert(std::is_trivially_move_assignable_v<{}>);", global_struct()); + printer->fmt_line("static_assert(std::is_trivially_destructible_v<{}>);", global_struct()); } @@ -2663,13 +2734,15 @@ void CodegenCVisitor::print_global_variables_for_hoc() { [&](const std::vector& variables, bool if_array, bool if_vector) { for (const auto& variable: variables) { if (variable->is_array() == if_array) { - auto name = get_variable_name(variable->get_name()); + // false => do not use the instance struct, which is not + // defined in the global declaration that we are printing + auto name = get_variable_name(variable->get_name(), false); auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); auto length = variable->get_length(); if (if_vector) { - printer->add_line(fmt::format("{}, {}, {},", ename, name, length)); + printer->fmt_line("{{{}, {}, {}}},", ename, name, length); } else { - printer->add_line(fmt::format("{}, &{},", ename, name)); + printer->fmt_line("{{{}, &{}}},", ename, name); } } } @@ -2688,7 +2761,7 @@ void CodegenCVisitor::print_global_variables_for_hoc() { printer->increase_indent(); variable_printer(globals, false, false); variable_printer(thread_vars, false, false); - printer->add_line("0, 0"); + printer->add_line("{nullptr, nullptr}"); printer->decrease_indent(); printer->add_line("};"); @@ -2698,7 +2771,7 @@ void CodegenCVisitor::print_global_variables_for_hoc() { printer->increase_indent(); variable_printer(globals, true, true); variable_printer(thread_vars, true, true); - printer->add_line("0, 0, 0"); + printer->add_line("{nullptr, nullptr, 0}"); printer->decrease_indent(); printer->add_line("};"); } @@ -2751,9 +2824,7 @@ static size_t get_register_type_for_ba_block(const ast::Block* block) { * the simulator using suffix_reg() function. * * Here are details: - * - setup_global_variables function used to create vectors necessary for specific - * solvers like euler and derivimplicit. All global variables are initialized as well. - * We should exclude that callback based on the solver, watch statements. + * - We should exclude that callback based on the solver, watch statements. * - If nrn_get_mechtype is < -1 means that mechanism is not used in the * context of neuron execution and hence could be ignored in coreneuron * execution. @@ -2770,11 +2841,10 @@ void CodegenCVisitor::print_mechanism_register() { printer->start_block(fmt::format("void _{}_reg() ", info.mod_file)); // type related information - auto mech_type = get_variable_name("mech_type"); auto suffix = add_escape_quote(info.mod_suffix); printer->add_newline(); - printer->add_line(fmt::format("int mech_type = nrn_get_mechtype({});", suffix)); - printer->add_line(fmt::format("{} = mech_type;", mech_type)); + printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); + printer->fmt_line("{} = mech_type;", get_variable_name("mech_type", false)); printer->add_line("if (mech_type == -1) {"); printer->add_line(" return;"); printer->add_line("}"); @@ -2786,43 +2856,36 @@ void CodegenCVisitor::print_mechanism_register() { auto args = register_mechanism_arguments(); auto nobjects = num_thread_objects(); if (info.point_process) { - printer->add_line( - fmt::format("point_register_mech({}, {}, {}, {});", - args, - info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) - : "NULL", - info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) : "NULL", - nobjects)); + printer->fmt_line("point_register_mech({}, {}, {}, {});", + args, + info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) + : "nullptr", + info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) + : "nullptr", + nobjects); } else { - printer->add_line(fmt::format("register_mech({}, {});", args, nobjects)); + printer->fmt_line("register_mech({}, {});", args, nobjects); if (info.constructor_node) { - printer->add_line(fmt::format("register_constructor({});", - method_name(naming::NRN_CONSTRUCTOR_METHOD))); + printer->fmt_line("register_constructor({});", + method_name(naming::NRN_CONSTRUCTOR_METHOD)); } } // types for ion for (const auto& ion: info.ions) { - std::string line = get_variable_name(ion.name + "_type"); - const auto& name = add_escape_quote(ion.name + "_ion"); - line.append(" = nrn_get_mechtype("); - line.append(name); - line.append(");"); - printer->add_line(line); + printer->fmt_line("{} = nrn_get_mechtype({});", + get_variable_name(ion.name + "_type", false), + add_escape_quote(ion.name + "_ion")); } printer->add_newline(); - // allocate global variables - printer->add_line("setup_global_variables();"); - /* - * If threads are used then memory is allocated in setup_global_variables. * Register callbacks for thread allocation and cleanup. Note that thread_data_index * represent total number of thread used minus 1 (i.e. index of last thread). */ if (info.vectorize && (info.thread_data_index != 0)) { - auto name = get_variable_name("ext_call_thread"); - printer->add_line(fmt::format("thread_mem_init({});", name)); + // false to avoid getting the copy from the instance structure + printer->fmt_line("thread_mem_init({});", get_variable_name("ext_call_thread", false)); } if (!info.thread_variables.empty()) { @@ -2982,34 +3045,50 @@ void CodegenCVisitor::print_thread_memory_callbacks() { } -void CodegenCVisitor::print_mechanism_range_var_structure() { +void CodegenCVisitor::print_mechanism_range_var_structure(bool print_initialisers) { + auto const value_initialise = print_initialisers ? "{}" : ""; auto float_type = default_float_data_type(); auto int_type = default_int_data_type(); printer->add_newline(2); - printer->add_line("/** all mechanism instance variables */"); - printer->start_block(fmt::format("struct {} ", instance_struct())); + printer->add_line("/** all mechanism instance variables and global variables */"); + printer->fmt_start_block("struct {} ", instance_struct()); + + for (auto const& [var, type]: info.neuron_global_variables) { + auto const name = var->get_name(); + printer->fmt_line("{}* {}{}{};", + type, + ptr_type_qualifier(), + name, + print_initialisers ? fmt::format("{{&coreneuron::{}}}", name) + : std::string{}); + } for (auto& var: codegen_float_variables) { auto name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? "const " : ""; - printer->add_line(fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); + printer->fmt_line( + "{}{}* {}{}{};", qualifier, type, ptr_type_qualifier(), name, value_initialise); } for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? "const " : ""; - printer->add_line( - fmt::format("{}{}* {}{};", qualifier, int_type, ptr_type_qualifier(), name)); + printer->fmt_line( + "{}{}* {}{}{};", qualifier, int_type, ptr_type_qualifier(), name, value_initialise); } else { auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->add_line( - fmt::format("{}{}* {}{};", qualifier, type, ptr_type_qualifier(), name)); + printer->fmt_line( + "{}{}* {}{}{};", qualifier, type, ptr_type_qualifier(), name, value_initialise); } } - printer->end_block(); - printer->add_text(";"); - printer->add_newline(); + + printer->fmt_line("{}* {}{};", + global_struct(), + naming::INST_GLOBAL_MEMBER, + print_initialisers ? fmt::format("{{&{}}}", global_struct_instance()) + : std::string{}); + printer->end_block(";"); } @@ -3065,156 +3144,10 @@ void CodegenCVisitor::print_ion_variable() { } -void CodegenCVisitor::print_global_variable_device_create_annotation_pre() { - // nothing for cpu -} - -void CodegenCVisitor::print_global_variable_device_create_annotation_post() { - // nothing for cpu -} - - void CodegenCVisitor::print_global_variable_device_update_annotation() { // nothing for cpu } -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCVisitor::print_global_variable_setup() { - std::vector allocated_variables; - - printer->add_newline(2); - printer->add_line("/** initialize global variables */"); - printer->start_block("static inline void setup_global_variables() "); - - printer->add_line("static int setup_done = 0;"); - printer->add_line("if (setup_done) {"); - printer->add_line(" return;"); - printer->add_line("}"); - - // offsets for state variables - if (info.primes_size != 0) { - if (info.primes_size != info.prime_variables_by_order.size()) { - throw std::runtime_error{ - fmt::format("primes_size = {} differs from prime_variables_by_order.size() = {}, " - "this should not happen.", - info.primes_size, - info.prime_variables_by_order.size())}; - } - auto slist1 = get_variable_name("slist1"); - auto dlist1 = get_variable_name("dlist1"); - auto n = info.primes_size; - printer->add_line(fmt::format("{} = (int*) mem_alloc({}, sizeof(int));", slist1, n)); - printer->add_line(fmt::format("{} = (int*) mem_alloc({}, sizeof(int));", dlist1, n)); - allocated_variables.push_back(slist1); - allocated_variables.push_back(dlist1); - - int id = 0; - for (auto& prime: info.prime_variables_by_order) { - auto name = prime->get_name(); - printer->add_line(fmt::format("{}[{}] = {};", slist1, id, position_of_float_var(name))); - printer->add_line( - fmt::format("{}[{}] = {};", dlist1, id, position_of_float_var("D" + name))); - id++; - } - } - - // additional list for derivimplicit method - if (info.derivimplicit_used()) { - auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); - auto slist2 = get_variable_name("slist2"); - auto nprimes = info.primes_size; - printer->add_line(fmt::format("{} = (int*) mem_alloc({}, sizeof(int));", slist2, nprimes)); - int id = 0; - for (auto& variable: primes) { - auto name = variable->get_name(); - printer->add_line(fmt::format("{}[{}] = {};", slist2, id, position_of_float_var(name))); - id++; - } - allocated_variables.push_back(slist2); - } - - // memory for thread member - if (info.vectorize && (info.thread_data_index != 0)) { - auto n = info.thread_data_index; - auto alloc = fmt::format("(ThreadDatum*) mem_alloc({}, sizeof(ThreadDatum))", n); - auto name = get_variable_name("ext_call_thread"); - printer->add_line(fmt::format("{} = {};", name, alloc)); - allocated_variables.push_back(name); - } - - // initialize global variables - for (auto& var: info.state_vars) { - auto name = var->get_name() + "0"; - auto symbol = program_symtab->lookup(name); - if (symbol == nullptr) { - auto global_name = get_variable_name(name); - printer->add_line(fmt::format("{} = 0.0;", global_name)); - } - } - - // note : v is not needed in global structure for nmodl even if vectorize is false - - if (!info.thread_variables.empty()) { - printer->add_line(fmt::format("{} = 0;", get_variable_name("thread_data_in_use"))); - } - - // initialize global variables - for (auto& var: info.global_variables) { - if (!var->is_array()) { - auto name = get_variable_name(var->get_name()); - double value = 0; - auto value_ptr = var->get_value(); - if (value_ptr != nullptr) { - value = *value_ptr; - } - /// use %g to be same as nocmodl in neuron - printer->add_line(fmt::format("{} = {:g};", name, value)); - } - } - - // initialize constant variables - for (auto& var: info.constant_variables) { - auto name = get_variable_name(var->get_name()); - auto value_ptr = var->get_value(); - double value = 0; - if (value_ptr != nullptr) { - value = *value_ptr; - } - /// use %g to be same as nocmodl in neuron - printer->add_line(fmt::format("{} = {:g};", name, value)); - } - - if (info.table_count > 0) { - auto name = get_variable_name(naming::USE_TABLE_VARIABLE); - printer->add_line(fmt::format("{} = 1;", name)); - - for (auto& variable: info.table_statement_variables) { - auto name = get_variable_name("t_" + variable->get_name()); - int num_values = variable->get_num_values(); - printer->add_line( - fmt::format("{} = (double*) mem_alloc({}, sizeof(double));", name, num_values)); - } - } - - // update device copy - print_global_variable_device_update_annotation(); - - printer->add_newline(); - printer->add_line("setup_done = 1;"); - printer->end_block(3); - - printer->add_line("/** free global variables */"); - printer->start_block("static inline void free_global_variables() "); - if (allocated_variables.empty()) { - printer->add_line("// do nothing"); - } else { - for (auto& var: allocated_variables) { - printer->add_line(fmt::format("mem_free({});", var)); - } - } - printer->end_block(1); -} - void CodegenCVisitor::print_setup_range_variable() { auto type = float_data_type(); @@ -3252,26 +3185,61 @@ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) return float_data_type(); } -/** - * \details For CPU/Host target there is no device pointer. In this case - * just use the host variable name directly. - */ -std::string CodegenCVisitor::get_variable_device_pointer(const std::string& variable, - const std::string& /*type*/) const { - return variable; -} - void CodegenCVisitor::print_instance_variable_setup() { if (range_variable_setup_required()) { print_setup_range_variable(); } - printer->add_newline(2); + printer->add_newline(); + printer->add_line("// Allocate instance structure"); + printer->fmt_start_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", + method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD)); + printer->add_line("assert(!ml->instance);"); + printer->add_line("assert(!ml->global_variables);"); + printer->add_line("assert(ml->global_variables_size == 0);"); + printer->fmt_line("auto* const inst = new {}{{}};", instance_struct()); + printer->fmt_line("assert(inst->{} == &{});", + naming::INST_GLOBAL_MEMBER, + global_struct_instance()); + printer->add_line("ml->instance = inst;"); + printer->fmt_line("ml->global_variables = inst->{};", naming::INST_GLOBAL_MEMBER); + printer->fmt_line("ml->global_variables_size = sizeof({});", global_struct()); + printer->end_block(2); + + auto const cast_inst_and_assert_validity = [&]() { + printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); + printer->add_line("assert(inst);"); + printer->fmt_line("assert(inst->{});", naming::INST_GLOBAL_MEMBER); + printer->fmt_line("assert(inst->{} == &{});", + naming::INST_GLOBAL_MEMBER, + global_struct_instance()); + printer->fmt_line("assert(inst->{} == ml->global_variables);", naming::INST_GLOBAL_MEMBER); + printer->fmt_line("assert(ml->global_variables_size == sizeof({}));", global_struct()); + }; + + printer->fmt_line( + "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst);", + instance_struct()); + printer->fmt_line("static inline void delete_instance_from_device({}& inst);", + instance_struct()); + printer->add_newline(); + + printer->add_line("// Deallocate the instance structure"); + printer->fmt_start_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", + method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); + cast_inst_and_assert_validity(); + printer->add_line("delete_instance_from_device(*inst);"); + printer->add_line("delete inst;"); + printer->add_line("ml->instance = nullptr;"); + printer->add_line("ml->global_variables = nullptr;"); + printer->add_line("ml->global_variables_size = 0;"); + printer->end_block(2); + + printer->add_line("/** initialize mechanism instance variables */"); - printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml) "); - printer->add_line( - fmt::format("{0}* inst = ({0}*) mem_alloc(1, sizeof({0}));", instance_struct())); + printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml)"); + cast_inst_and_assert_validity(); std::string stride; printer->add_line("int pnodecount = ml->_nodecount_padded;"); @@ -3279,61 +3247,60 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->add_line("Datum* indexes = ml->pdata;"); - std::string float_type = default_float_data_type(); - std::string int_type = default_int_data_type(); - std::string float_type_pointer = float_type + "*"; - std::string int_type_pointer = int_type + "*"; + auto const float_type = default_float_data_type(); int id = 0; - std::vector variables_to_free; - + std::vector ptr_members{naming::INST_GLOBAL_MEMBER}; + for (auto const& [var, type]: info.neuron_global_variables) { + ptr_members.push_back(var->get_name()); + } + ptr_members.reserve(ptr_members.size() + codegen_float_variables.size() + + codegen_int_variables.size()); for (auto& var: codegen_float_variables) { auto name = var->get_name(); auto range_var_type = get_range_var_float_type(var); if (float_type == range_var_type) { - auto variable = fmt::format("ml->data+{}{}", id, stride); - auto device_variable = get_variable_device_pointer(variable, float_type_pointer); - printer->add_line(fmt::format("inst->{} = {};", name, device_variable)); + auto const variable = fmt::format("ml->data+{}{}", id, stride); + printer->fmt_line("inst->{} = {};", name, variable); } else { - printer->add_line(fmt::format( - "inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);", name, id, stride)); - variables_to_free.push_back(name); + // TODO what MOD file exercises this? + printer->fmt_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);", + name, + id, + stride); } + ptr_members.push_back(std::move(name)); id += var->get_length(); } for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); - std::string variable = name; - std::string type; - if (var.is_index || var.is_integer) { - variable = "ml->pdata"; - type = int_type_pointer; - } else if (var.is_vdata) { - variable = "nt->_vdata"; - type = "void**"; - } else { - variable = "nt->_data"; - type = info.artificial_cell ? "void*" : float_type_pointer; - } - auto device_variable = get_variable_device_pointer(variable, type); - printer->add_line(fmt::format("inst->{} = {};", name, device_variable)); + auto const variable = [&var]() { + if (var.is_index || var.is_integer) { + return "ml->pdata"; + } else if (var.is_vdata) { + return "nt->_vdata"; + } else { + return "nt->_data"; + } + }(); + printer->fmt_line("inst->{} = {};", name, variable); + ptr_members.push_back(std::move(name)); } + printer->add_line("copy_instance_to_device(nt, ml, inst);"); + printer->end_block(2); // setup_instance - printer->add_line("ml->instance = inst;"); - print_instance_variable_transfer_to_device(); - printer->end_block(3); + printer->add_line("// Set up the device-side copy of the instance structure"); + printer->fmt_start_block( + "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst)", + instance_struct()); + print_instance_variable_transfer_to_device(ptr_members); + printer->end_block(2); // copy_instance_to_device - printer->add_line("/** cleanup mechanism instance variables */"); - printer->start_block("static inline void cleanup_instance(Memb_list* ml) "); - printer->add_line(fmt::format("{0}* inst = ({0}*) ml->instance;", instance_struct())); - if (range_variable_setup_required()) { - for (auto& var: variables_to_free) { - printer->add_line(fmt::format("mem_free((void*)inst->{});", var)); - } - } - printer->add_line("mem_free((void*)inst);"); - printer->end_block(1); + printer->fmt_start_block("static inline void delete_instance_from_device({}& inst)", + instance_struct()); + print_instance_variable_deletion_from_device(); + printer->end_block(2); // delete_instance_from_device } @@ -3362,7 +3329,7 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { if (!info.is_ionic_conc(name)) { auto lhs = get_variable_name(name); auto rhs = get_variable_name(name + "0"); - printer->add_line(fmt::format("{} = {};", lhs, rhs)); + printer->fmt_line("{} = {};", lhs, rhs); } } @@ -3428,13 +3395,12 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, printer->add_newline(); printer->add_line("setup_instance(nt, ml);"); } - // clang-format off - printer->add_line(fmt::format("{0}* {1}inst = ({0}*) ml->instance;", instance_struct(), ptr_type_qualifier())); - // clang-format on + printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", + instance_struct(), + ptr_type_qualifier()); printer->add_newline(1); } - void CodegenCVisitor::print_nrn_init(bool skip_init_check) { codegen = true; printer->add_newline(2); @@ -3462,6 +3428,8 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { } // update global variable as those might be updated via python/hoc API + // NOTE: CoreNEURON has enough information to do this on its own, which + // would be neater. print_global_variable_device_update_annotation(); if (skip_init_check) { @@ -3755,7 +3723,7 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need printer->add_line("Datum* indexes = ml->pdata;"); printer->add_line("ThreadDatum* thread = ml->_thread;"); if (need_mech_inst) { - printer->add_line(fmt::format("{0}* inst = ({0}*) ml->instance;", instance_struct())); + printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); } if (node.is_initial_block()) { @@ -3989,7 +3957,7 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->add_line( fmt::format("NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;", ptr_type_qualifier())); if (need_mech_inst) { - printer->add_line(fmt::format("{0}* inst = ({0}*) ml->instance;", instance_struct())); + printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); } print_net_receive_loop_begin(); printer->add_line("int start = nrb->_displ[i];"); @@ -4207,27 +4175,29 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->start_block("namespace"); printer->fmt_start_block("struct _newton_{}_{}", block_name, info.mod_suffix); printer->fmt_start_block("int operator()({}) const", external_method_parameters()); - auto const instance = fmt::format("{0}* inst = ({0}*)get_memb_list(nt)->instance;", + auto const instance = fmt::format("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); - auto const slist1 = fmt::format("int* slist{} = {};", + auto const slist1 = fmt::format("auto const& slist{} = {};", list_num, get_variable_name(fmt::format("slist{}", list_num))); - auto const slist2 = fmt::format("int* slist{} = {};", + auto const slist2 = fmt::format("auto& slist{} = {};", list_num + 1, get_variable_name(fmt::format("slist{}", list_num + 1))); - auto const dlist1 = fmt::format("int* dlist{} = {};", + auto const dlist1 = fmt::format("auto const& dlist{} = {};", list_num, get_variable_name(fmt::format("dlist{}", list_num))); - auto const dlist2 = - fmt::format("double* dlist{} = (double*) thread[dith{}()].pval + ({}*pnodecount);", - list_num + 1, - list_num, - info.primes_size); + auto const dlist2 = fmt::format( + "double* dlist{} = static_cast(thread[dith{}()].pval) + ({}*pnodecount);", + list_num + 1, + list_num, + info.primes_size); printer->add_line(instance); if (ion_variable_struct_required()) { print_ion_variable(); } - printer->fmt_line("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num); + printer->fmt_line("double* savstate{} = static_cast(thread[dith{}()].pval);", + list_num, + list_num); printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); @@ -4597,9 +4567,9 @@ void CodegenCVisitor::print_common_getters() { } -void CodegenCVisitor::print_data_structures() { - print_mechanism_global_var_structure(); - print_mechanism_range_var_structure(); +void CodegenCVisitor::print_data_structures(bool print_initialisers) { + print_mechanism_global_var_structure(print_initialisers); + print_mechanism_range_var_structure(print_initialisers); print_ion_var_structure(); } @@ -4652,13 +4622,12 @@ void CodegenCVisitor::print_codegen_routines() { print_nmodl_constants(); print_prcellstate_macros(); print_mechanism_info(); - print_data_structures(); + print_data_structures(true); print_global_variables_for_hoc(); print_common_getters(); print_memory_allocation_routine(); print_abort_routine(); print_thread_memory_callbacks(); - print_global_variable_setup(); print_instance_variable_setup(); print_nrn_alloc(); print_nrn_constructor(); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 244497a727..8cd8188dcd 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "codegen/codegen_info.hpp" @@ -394,13 +395,21 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** - * Name of structure that wraps range variables + * Name of structure that wraps global variables */ std::string global_struct() const { return fmt::format("{}_Store", info.mod_suffix); } + /** + * Name of the (host-only) global instance of `global_struct` + */ + std::string global_struct_instance() const { + return info.mod_suffix + "_global"; + } + + /** * Constructs the name of a function or procedure * \param name The name of the function or procedure @@ -614,9 +623,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Determine the variable name for a global variable given its symbol * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via the (host-only) + * global variable or the instance-specific copy (also available on GPU). * \return The C string representing the access to the global variable */ - std::string global_variable_name(const SymbolType& symbol) const; + std::string global_variable_name(const SymbolType& symbol, bool use_instance = true) const; /** @@ -895,6 +906,13 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_global_var_struct_decl(); + /** + * Print static assertions about the global variable struct. + * + * For ISPC this has to be disabled. + */ + virtual void print_global_var_struct_assertions() const; + /** * The used parameter type qualifier * \return an empty string @@ -1022,8 +1040,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Print the structure that wraps all global variables used in the NMODL + * + * @param print_initialisers Whether to include default values in the struct + * definition (true: int foo{42}; false: int foo;) */ - void print_mechanism_global_var_structure(); + void print_mechanism_global_var_structure(bool print_initialisers); /** @@ -1059,9 +1080,19 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** - * Print the code to copy instance variable to device + * Print the code to copy instance struct members to the device, + * substituting host pointers for device ones. + * + * \param ptr_members Members to update. */ - virtual void print_instance_variable_transfer_to_device() const; + virtual void print_instance_variable_transfer_to_device( + std::vector const& ptr_members) const; + + + /** + * Print the code to delete the instance structure from the device. + */ + virtual void print_instance_variable_deletion_from_device() const; /** @@ -1146,31 +1177,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_mech_type_getter(); - /** - * Print the setup method that initializes all global variables - * - */ - void print_global_variable_setup(); - - - /** - * Print the pragma annotation needed before a global variable that must be - * created on the device. This always comes before a matching call to - * print_global_variable_device_create_annotation_post. - * - * \note This is not used for the C backend - */ - virtual void print_global_variable_device_create_annotation_pre(); - - /** - * Print the pragma annotation needed after a global variables that must be - * created on the device. This always comes after a matching call to - * print_global_variable_device_create_annotation_pre. - * - * \note This is not used for the C backend - */ - virtual void print_global_variable_device_create_annotation_post(); - /** * Print the pragma annotation to update global variables from host to the device * @@ -1606,9 +1612,9 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Print all classes - * + * @param print_initialisers Whether to include default values. */ - void print_data_structures(); + void print_data_structures(bool print_initialisers); /** @@ -1643,13 +1649,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_wrapper_routines(); - /** - * Get device variable pointer for corresponding host variable - */ - virtual std::string get_variable_device_pointer(const std::string& variable, - const std::string& type) const; - - CodegenCVisitor(const std::string& mod_filename, const std::string& output_dir, const std::string& float_type, @@ -1866,8 +1865,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Print the structure that wraps all range and int variables required for the NMODL + * + * @param print_initialisers Whether or not default values for variables + * be included in the struct declaration. */ - void print_mechanism_range_var_structure(); + void print_mechanism_range_var_structure(bool print_initialisers); /** * Print the function that initialize instance structure diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index 48d1f6bade..ae4d51fc6c 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -213,7 +213,7 @@ void CodegenCudaVisitor::print_codegen_routines() { print_headers_include(); print_namespace_begin(); - print_data_structures(); + print_data_structures(true); print_common_getters(); print_compute_functions(); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index e70b150727..45a0c5c53f 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -461,6 +461,19 @@ void CodegenHelperVisitor::find_table_variables() { info.table_assigned_variables = psymtab->get_variables_with_properties(property); } +void CodegenHelperVisitor::find_neuron_global_variables() { + // TODO: it would be nicer not to have this hardcoded list + using pair = std::pair; + for (auto [var, type]: {pair{naming::CELSIUS_VARIABLE, "double"}, + pair{"secondorder", "int"}, + pair{"pi", "double"}}) { + auto sym = psymtab->lookup(var); + if (sym && (sym->get_read_count() || sym->get_write_count())) { + info.neuron_global_variables.emplace_back(std::move(sym), type); + } + } +} + void CodegenHelperVisitor::visit_suffix(const Suffix& node) { const auto& type = node.get_type()->get_node_name(); @@ -697,6 +710,7 @@ void CodegenHelperVisitor::visit_program(const ast::Program& node) { find_range_variables(); find_non_range_variables(); find_table_variables(); + find_neuron_global_variables(); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index c177a080ba..614f93732e 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -73,6 +73,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void find_table_variables(); void find_range_variables(); void find_non_range_variables(); + void find_neuron_global_variables(); static void sort_with_mod2c_symbol_order(std::vector& symbols); public: diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index f1830d0de2..9ca2409dbe 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -343,6 +343,9 @@ struct CodegenInfo { std::vector table_statement_variables; std::vector table_assigned_variables; + /// [Core]NEURON global variables used (e.g. celsius) and their types + std::vector> neuron_global_variables; + /// function or procedures with table statement std::vector functions_with_table; diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 1d03a7261e..40b5607166 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -66,8 +66,6 @@ void CodegenIspcVisitor::visit_var_name(const ast::VarName& node) { if (!codegen) { return; } - RenameVisitor celsius_rename("celsius", "ispc_celsius"); - node.accept(celsius_rename); RenameVisitor pi_rename("PI", "ISPC_PI"); node.accept(pi_rename); CodegenCVisitor::visit_var_name(node); @@ -304,11 +302,14 @@ std::string CodegenIspcVisitor::global_var_struct_type_qualifier() { void CodegenIspcVisitor::print_global_var_struct_decl() { if (wrapper_codegen) { - printer->start_block("extern \"C\""); - printer->add_line(fmt::format("{} {}_global;", global_struct(), info.mod_suffix)); - printer->end_block(2); - } else { - printer->add_line(fmt::format("extern {} {}_global;", global_struct(), info.mod_suffix)); + CodegenCVisitor::print_global_var_struct_decl(); + } +} + +void CodegenIspcVisitor::print_global_var_struct_assertions() const { + // Print static_assert in .cpp but not .ispc + if (wrapper_codegen) { + CodegenCVisitor::print_global_var_struct_assertions(); } } @@ -423,13 +424,6 @@ void CodegenIspcVisitor::print_compute_functions() { } -void CodegenIspcVisitor::print_ispc_globals() { - printer->start_block("extern \"C\""); - printer->add_line("extern double ispc_celsius;"); - printer->end_block(); -} - - void CodegenIspcVisitor::print_ion_var_constructor(const std::vector& members) { /// no constructor for ispc } @@ -455,9 +449,9 @@ void CodegenIspcVisitor::print_net_receive_buffering_wrapper() { printer->start_block("if (ml == NULL)"); printer->add_line("return;"); printer->end_block(1); - printer->add_line(fmt::format("{0}* {1}inst = ({0}*) ml->instance;", - instance_struct(), - ptr_type_qualifier())); + printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", + instance_struct(), + ptr_type_qualifier()); printer->add_line(fmt::format("{}(inst, nt, ml);", method_name("ispc_net_buf_receive"))); @@ -496,24 +490,20 @@ void CodegenIspcVisitor::print_wrapper_routine(const std::string& wrapper_functi printer->add_newline(2); printer->start_block(fmt::format("void {}({})", function_name, args)); printer->add_line("int nodecount = ml->nodecount;"); - // clang-format off - printer->add_line(fmt::format("{0}* {1}inst = ({0}*) ml->instance;", instance_struct(), ptr_type_qualifier())); - // clang-format on + printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", + instance_struct(), + ptr_type_qualifier()); if (type == BlockType::Initial) { printer->add_newline(); printer->add_line("setup_instance(nt, ml);"); - printer->add_line("ispc_celsius = celsius;"); printer->add_newline(); printer->start_block("if (_nrn_skip_initmodel)"); printer->add_line("return;"); - printer->end_block(); - printer->add_newline(); + printer->end_block(1); } - - printer->add_line(fmt::format("{}(inst, nt, ml, type);", compute_function)); - printer->end_block(); - printer->add_newline(); + printer->fmt_line("{}(inst, nt, ml, type);", compute_function); + printer->end_block(1); } @@ -732,7 +722,7 @@ void CodegenIspcVisitor::print_codegen_routines() { print_backend_info(); print_headers_include(); print_nmodl_constants(); - print_data_structures(); + print_data_structures(false); print_compute_functions(); } @@ -742,19 +732,17 @@ void CodegenIspcVisitor::print_wrapper_routines() { wrapper_codegen = true; print_backend_info(); print_wrapper_headers_include(); - print_ispc_globals(); print_namespace_begin(); CodegenCVisitor::print_nmodl_constants(); print_mechanism_info(); - print_data_structures(); + print_data_structures(true); print_global_variables_for_hoc(); print_common_getters(); print_memory_allocation_routine(); print_thread_memory_callbacks(); print_abort_routine(); - print_global_variable_setup(); /* this is a godawful mess.. the global variables have to be copied over into the fallback * such that they are available to the fallback generator. */ diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp index d4dc677627..f97d0085c6 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.hpp @@ -91,7 +91,10 @@ class CodegenIspcVisitor: public CodegenCVisitor { std::string global_var_struct_type_qualifier() override; + void print_global_var_struct_decl() override; + void print_global_var_struct_assertions() const override; + std::string param_type_qualifier() override; @@ -176,9 +179,6 @@ class CodegenIspcVisitor: public CodegenCVisitor { void print_block_wrappers_initial_equation_state(); - void print_ispc_globals(); - - void print_get_memb_list() override; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 199d922534..9739285bc4 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -86,6 +86,12 @@ static constexpr char NTHREAD_RHS_SHADOW[] = "_shadow_rhs"; /// shadow d variable in neuron thread structure static constexpr char NTHREAD_D_SHADOW[] = "_shadow_d"; +/// global temperature variable +static constexpr char CELSIUS_VARIABLE[] = "celsius"; + +/// instance struct member pointing to the global variable structure +static constexpr char INST_GLOBAL_MEMBER[] = "global"; + /// t variable in neuron thread structure static constexpr char NTHREAD_T_VARIABLE[] = "t"; @@ -131,6 +137,12 @@ static constexpr char NRN_CONSTRUCTOR_METHOD[] = "nrn_constructor"; /// nrn_destructor method in generated code static constexpr char NRN_DESTRUCTOR_METHOD[] = "nrn_destructor"; +/// nrn_private_constructor method in generated code +inline constexpr char NRN_PRIVATE_CONSTRUCTOR_METHOD[] = "nrn_private_constructor"; + +/// nrn_private_destructor method in generated code +inline constexpr char NRN_PRIVATE_DESTRUCTOR_METHOD[] = "nrn_private_destructor"; + /// nrn_alloc method in generated code static constexpr char NRN_ALLOC_METHOD[] = "nrn_alloc"; diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 38050b0c1f..84a6bc2db5 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -324,7 +324,7 @@ bool needs_neuron_thread_first_arg(const std::string& token) { * undefined and hence these needs to be inserted into symbol table */ static std::vector const NEURON_VARIABLES = - {"t", "dt", "celsius", "v", "diam", "area"}; + {"t", "dt", "celsius", "v", "diam", "area", "pi", "secondorder"}; /// Return token type for the keyword diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index cad0539b17..fcd4791c23 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -25,6 +25,7 @@ #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/global_var_visitor.hpp" +#include "visitors/implicit_argument_visitor.hpp" #include "visitors/indexedname_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/ispc_rename_visitor.hpp" @@ -529,6 +530,11 @@ int main(int argc, const char* argv[]) { PerfVisitor(file).visit_program(*ast); } + // Add implicit arguments (like celsius, nt) to NEURON functions (like + // nrn_ghk, at_time) whose signatures we have to massage. + ImplicitArgumentVisitor{}.visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + { // make sure to run perf visitor because code generator // looks for read/write counts const/non-const declaration diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 9d41b1eb97..08fb13568e 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -82,5 +82,11 @@ void CodePrinter::end_block(int num_newlines) { add_newline(num_newlines); } +void CodePrinter::end_block(std::string_view suffix, std::size_t num_newlines) { + end_block(0); + *result << suffix; + add_newline(num_newlines); +} + } // namespace printer } // namespace nmodl diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 695a85bb68..565a56650e 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace nmodl { /// implementation of various printers @@ -101,6 +102,9 @@ class CodePrinter { /// end of current block scope (i.e. end with "}") void end_block(int num_newlines = 0); + /// end a block with `suffix` before the newline(s) (i.e. [indent]}[suffix]\n*num_newlines) + void end_block(std::string_view suffix, std::size_t num_newlines = 1); + int indent_spaces() { return NUM_SPACES * indent_level; } diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp new file mode 100644 index 0000000000..92dee20b88 --- /dev/null +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp @@ -0,0 +1,17 @@ +/************************************************************************* + * Copyright (C) 2018-2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#define NMODL_EIGEN_NO_OPENACC // disable OpenACC/OpenMP annotations +#define NMODL_LEAVE_PartialPivLuInstantiations_DEFINED // import PartialPivLuInstantiations +#include "partial_piv_lu/partial_piv_lu.h" + +// See explanation in partial_piv_lu.h +#define InstantiatePartialPivLu(N) \ + NMODL_EIGEN_ATTR VecType partialPivLu##N(const MatType& A, const VecType& b) { \ + return A.partialPivLu().solve(b); \ + } +PartialPivLuInstantiations +#undef InstantiatePartialPivLu diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu deleted file mode 100644 index 6faf0e6b64..0000000000 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cu +++ /dev/null @@ -1,60 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ -#include "partial_piv_lu/partial_piv_lu.h" - -// See explanation in partial_piv_lu.h -EIGEN_DEVICE_FUNC VecType<1> partialPivLu1(const MatType<1>& A, const VecType<1>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<2> partialPivLu2(const MatType<2>& A, const VecType<2>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<3> partialPivLu3(const MatType<3>& A, const VecType<3>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<4> partialPivLu4(const MatType<4>& A, const VecType<4>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<5> partialPivLu5(const MatType<5>& A, const VecType<5>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<6> partialPivLu6(const MatType<6>& A, const VecType<6>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<7> partialPivLu7(const MatType<7>& A, const VecType<7>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<8> partialPivLu8(const MatType<8>& A, const VecType<8>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<9> partialPivLu9(const MatType<9>& A, const VecType<9>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<10> partialPivLu10(const MatType<10>& A, const VecType<10>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<11> partialPivLu11(const MatType<11>& A, const VecType<11>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<12> partialPivLu12(const MatType<12>& A, const VecType<12>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<13> partialPivLu13(const MatType<13>& A, const VecType<13>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<14> partialPivLu14(const MatType<14>& A, const VecType<14>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<15> partialPivLu15(const MatType<15>& A, const VecType<15>& b) { - return A.partialPivLu().solve(b); -} -EIGEN_DEVICE_FUNC VecType<16> partialPivLu16(const MatType<16>& A, const VecType<16>& b) { - return A.partialPivLu().solve(b); -} - -// Currently there is an issue in Eigen (GPU-branch) for matrices 17x17 and above. -// ToDo: Check in a future release if this issue is resolved! diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h index 918febe896..69c1b603e0 100644 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h +++ b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h @@ -4,10 +4,11 @@ * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ - #pragma once +#ifndef NMODL_EIGEN_NO_OPENACC #include "coreneuron/utils/offload.hpp" +#endif #include #include @@ -18,49 +19,69 @@ using MatType = Eigen::Matrix; template using VecType = Eigen::Matrix; -// Eigen-3.5+ provides better GPU support. However, some functions cannot be called directly -// from within an OpenACC region. Therefore, we need to wrap them in a special API (decorate -// them with __device__ & acc routine tokens), which allows us to eventually call them from OpenACC. -// Calling these functions from CUDA kernels presents no issue ... - // We want to declare a function template that is callable from OpenMP and -// OpenACC code, but whose instantiations are compiled by CUDA. This is to avoid +// OpenACC code, but whose instantiations are compiled by nvc++ -cuda. This is to avoid // Eigen internals having to be digested by OpenACC/OpenMP compilers. The // problem is that it is apparently not sufficient to declare a template in a OpenMP // declare target region and have the OpenMP compiler assume that device // versions of instantations of it will be available. The convoluted approach // here has two ingredients: // - partialPivLu(...), a function template that has explicit -// instantiations visible in this header file that call: +// specialisations visible in this header file that call: // - partialPivLuN(...), functions that are declared in this header but defined -// in the CUDA file partial_piv_lu.cu. +// in the C++/CUDA file partial_piv_lu.cpp. +// +// It does not seem to work to generically define the template function +// partialPivLu in the .cpp file and then explicitly instantate it for 1..16 +// there; the relevant device functions are not generated. A possible workaround +// is to define the template + a dummy function that calls many different +// instantiations of the template in the .cpp file, but this seems more fragile +// than the present approach. +#ifdef NMODL_EIGEN_NO_OPENACC +#define NMODL_EIGEN_ATTR __host__ __device__ +#define NMODL_EIGEN_ROUTINE_SEQ +#else nrn_pragma_omp(declare target) -nrn_pragma_acc(routine seq) +#define NMODL_EIGEN_ATTR +#define NMODL_EIGEN_ROUTINE_SEQ nrn_pragma_acc(routine seq) +#endif +NMODL_EIGEN_ROUTINE_SEQ template -EIGEN_DEVICE_FUNC VecType partialPivLu(const MatType&, const VecType&); -#define InstantiatePartialPivLu(N) \ - nrn_pragma_acc(routine seq) \ - EIGEN_DEVICE_FUNC VecType partialPivLu##N(const MatType&, const VecType&); \ - nrn_pragma_acc(routine seq) \ - template <> \ - EIGEN_DEVICE_FUNC inline VecType partialPivLu(const MatType& A, const VecType& b) { \ - return partialPivLu##N(A, b); \ +NMODL_EIGEN_ATTR VecType partialPivLu(const MatType&, const VecType&); +#define InstantiatePartialPivLu(N) \ + NMODL_EIGEN_ROUTINE_SEQ \ + NMODL_EIGEN_ATTR VecType partialPivLu##N(const MatType&, const VecType&); \ + NMODL_EIGEN_ROUTINE_SEQ \ + template <> \ + NMODL_EIGEN_ATTR inline VecType partialPivLu(const MatType& A, const VecType& b) { \ + return partialPivLu##N(A, b); \ } -InstantiatePartialPivLu(1) -InstantiatePartialPivLu(2) -InstantiatePartialPivLu(3) -InstantiatePartialPivLu(4) -InstantiatePartialPivLu(5) -InstantiatePartialPivLu(6) -InstantiatePartialPivLu(7) -InstantiatePartialPivLu(8) -InstantiatePartialPivLu(9) -InstantiatePartialPivLu(10) -InstantiatePartialPivLu(11) -InstantiatePartialPivLu(12) -InstantiatePartialPivLu(13) -InstantiatePartialPivLu(14) -InstantiatePartialPivLu(15) -InstantiatePartialPivLu(16) +// This PartialPivLuInstantiations is used in partial_piv_lu.cpp with a +// different definition of the InstantiatePartialPivLu macro. As of 2022-08-18 there is still an +// issue with instantiating for matrices larger than 16x16. +#define PartialPivLuInstantiations \ + InstantiatePartialPivLu(1) \ + InstantiatePartialPivLu(2) \ + InstantiatePartialPivLu(3) \ + InstantiatePartialPivLu(4) \ + InstantiatePartialPivLu(5) \ + InstantiatePartialPivLu(6) \ + InstantiatePartialPivLu(7) \ + InstantiatePartialPivLu(8) \ + InstantiatePartialPivLu(9) \ + InstantiatePartialPivLu(10) \ + InstantiatePartialPivLu(11) \ + InstantiatePartialPivLu(12) \ + InstantiatePartialPivLu(13) \ + InstantiatePartialPivLu(14) \ + InstantiatePartialPivLu(15) \ + InstantiatePartialPivLu(16) +PartialPivLuInstantiations #undef InstantiatePartialPivLu +#ifndef NMODL_EIGEN_NO_OPENACC nrn_pragma_omp(end declare target) +#endif +// partial_piv_lu.cpp will request that this is left defined +#ifndef NMODL_LEAVE_PartialPivLuInstantiations_DEFINED +#undef PartialPivLuInstantiations +#endif diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index c5e4121adc..8871c9f80d 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -13,6 +13,7 @@ add_library( constant_folder_visitor.cpp defuse_analyze_visitor.cpp global_var_visitor.cpp + implicit_argument_visitor.cpp indexedname_visitor.cpp inline_visitor.cpp kinetic_block_visitor.cpp diff --git a/src/nmodl/visitors/implicit_argument_visitor.cpp b/src/nmodl/visitors/implicit_argument_visitor.cpp new file mode 100644 index 0000000000..572a72209d --- /dev/null +++ b/src/nmodl/visitors/implicit_argument_visitor.cpp @@ -0,0 +1,56 @@ +/************************************************************************* + * Copyright (C) 2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#include "visitors/implicit_argument_visitor.hpp" +#include "ast/ast_decl.hpp" +#include "ast/function_call.hpp" +#include "ast/string.hpp" +#include "codegen/codegen_naming.hpp" +#include "lexer/token_mapping.hpp" + +namespace nmodl { +namespace visitor { + +void ImplicitArgumentVisitor::visit_function_call(ast::FunctionCall& node) { + auto function_name = node.get_node_name(); + auto const& arguments = node.get_arguments(); + if (function_name == "nrn_ghk") { + // This function is traditionally used in MOD files with four arguments, but + // its value also depends on the global celsius variable so the real + // function in CoreNEURON has a 5th argument for that. + if (arguments.size() == 4) { + auto new_arguments = arguments; + new_arguments.insert(new_arguments.end(), + std::make_shared(std::make_shared( + codegen::naming::CELSIUS_VARIABLE))); + node.set_arguments(std::move(new_arguments)); + } + } else if (nmodl::details::needs_neuron_thread_first_arg(function_name)) { + // We need to insert `nt` as the first argument if it's not already + // there + auto const is_nt = [](auto const& arg) { + // Not all node types implement get_node_name(); for example if the + // first argument is `a+b` then calling `get_node_name` would throw + auto const node_type = arg.get_node_type(); + using ast::AstNodeType; + if (node_type == AstNodeType::NAME || node_type == AstNodeType::STRING || + node_type == AstNodeType::CONSTANT_VAR || node_type == AstNodeType::VAR_NAME || + node_type == AstNodeType::LOCAL_VAR) { + return arg.get_node_name() == "nt"; + } + return false; + }; + if (arguments.empty() || !is_nt(*arguments.front())) { + auto new_arguments = arguments; + new_arguments.insert(new_arguments.begin(), std::make_shared("nt")); + node.set_arguments(std::move(new_arguments)); + } + } + node.visit_children(*this); +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/implicit_argument_visitor.hpp b/src/nmodl/visitors/implicit_argument_visitor.hpp new file mode 100644 index 0000000000..46ec38978c --- /dev/null +++ b/src/nmodl/visitors/implicit_argument_visitor.hpp @@ -0,0 +1,41 @@ +/************************************************************************* + * Copyright (C) 2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::ImplicitArgumentVisitor + */ + +#include "visitors/ast_visitor.hpp" + + +namespace nmodl { +namespace visitor { + +/** + * \addtogroup visitor_classes + * \{ + */ + +/** + * \class ImplicitArgumentVisitor + * \brief %Visitor for adding implicit arguments to [Core]NEURON functions. + * + * This visitor is used to add implicit arguments to functions (nrn_ghk, ...) + * that have historially been used in MOD files with "fewer" arguments and + * relied on global state, but which now need "more" arguments to be passed so + * that they can be pure functions. + */ +struct ImplicitArgumentVisitor: public AstVisitor { + void visit_function_call(ast::FunctionCall& node) override; +}; + +/** \} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index cbd83fe1bf..110e3a6c7c 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable( visitor/constant_folder.cpp visitor/defuse_analyze.cpp visitor/global_to_range.cpp + visitor/implicit_argument.cpp visitor/inline.cpp visitor/ispc_rename.cpp visitor/json.cpp diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index b7ca1eaa4d..64fae2b914 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -12,8 +12,12 @@ #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" +#include "visitors/implicit_argument_visitor.hpp" +#include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" +using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 + using namespace nmodl; using namespace visitor; using namespace codegen; @@ -74,8 +78,13 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { THEN("ionic current variable declared as RANGE appears first") { std::string generated_code = R"( - static inline void setup_instance(NrnThread* nt, Memb_list* ml) { - cal_Instance* inst = (cal_Instance*) mem_alloc(1, sizeof(cal_Instance)); + static inline void setup_instance(NrnThread* nt, Memb_list* ml) { + auto* const inst = static_cast(ml->instance); + assert(inst); + assert(inst->global); + assert(inst->global == &cal_global); + assert(inst->global == ml->global_variables); + assert(ml->global_variables_size == sizeof(cal_Store)); int pnodecount = ml->_nodecount_padded; Datum* indexes = ml->pdata; inst->gcalbar = ml->data+0*pnodecount; @@ -93,12 +102,12 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_cao = nt->_data; inst->ion_ica = nt->_data; inst->ion_dicadv = nt->_data; - ml->instance = inst; + copy_instance_to_device(nt, ml, inst); } )"; - auto expected = reindent_text(generated_code); - auto result = get_instance_var_setup_function(nmodl_text); - REQUIRE(result.find(expected) != std::string::npos); + auto const expected = reindent_text(generated_code); + auto const result = get_instance_var_setup_function(nmodl_text); + REQUIRE_THAT(result, Contains(expected)); } } @@ -126,8 +135,13 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { THEN("Ion variables are defined in the order of USEION") { std::string generated_code = R"( - static inline void setup_instance(NrnThread* nt, Memb_list* ml) { - lca_Instance* inst = (lca_Instance*) mem_alloc(1, sizeof(lca_Instance)); + static inline void setup_instance(NrnThread* nt, Memb_list* ml) { + auto* const inst = static_cast(ml->instance); + assert(inst); + assert(inst->global); + assert(inst->global == &lca_global); + assert(inst->global == ml->global_variables); + assert(ml->global_variables_size == sizeof(lca_Store)); int pnodecount = ml->_nodecount_padded; Datum* indexes = ml->pdata; inst->m = ml->data+0*pnodecount; @@ -137,13 +151,13 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->v_unused = ml->data+4*pnodecount; inst->ion_cai = nt->_data; inst->ion_cao = nt->_data; - ml->instance = inst; + copy_instance_to_device(nt, ml, inst); } )"; - auto expected = reindent_text(generated_code); - auto result = get_instance_var_setup_function(nmodl_text); - REQUIRE(result.find(expected) != std::string::npos); + auto const expected = reindent_text(generated_code); + auto const result = get_instance_var_setup_function(nmodl_text); + REQUIRE_THAT(result, Contains(expected)); } } @@ -186,8 +200,13 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { THEN("Ion variables are defined in the order of USEION") { std::string generated_code = R"( - static inline void setup_instance(NrnThread* nt, Memb_list* ml) { - ccanl_Instance* inst = (ccanl_Instance*) mem_alloc(1, sizeof(ccanl_Instance)); + static inline void setup_instance(NrnThread* nt, Memb_list* ml) { + auto* const inst = static_cast(ml->instance); + assert(inst); + assert(inst->global); + assert(inst->global == &ccanl_global); + assert(inst->global == ml->global_variables); + assert(ml->global_variables_size == sizeof(ccanl_Store)); int pnodecount = ml->_nodecount_padded; Datum* indexes = ml->pdata; inst->catau = ml->data+0*pnodecount; @@ -212,17 +231,35 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_ilca = nt->_data; inst->ion_elca = nt->_data; inst->style_lca = ml->pdata; - ml->instance = inst; + copy_instance_to_device(nt, ml, inst); } )"; - auto expected = reindent_text(generated_code); - auto result = get_instance_var_setup_function(nmodl_text); - REQUIRE(result.find(expected) != std::string::npos); + auto const expected = reindent_text(generated_code); + auto const result = get_instance_var_setup_function(nmodl_text); + REQUIRE_THAT(result, Contains(expected)); } } } +std::string get_instance_structure(std::string nmodl_text) { + // parse mod file & print mechanism structure + auto const ast = NmodlDriver{}.parse_string(nmodl_text); + // add implicit arguments + ImplicitArgumentVisitor{}.visit_program(*ast); + // update the symbol table for PerfVisitor + SymtabVisitor{}.visit_program(*ast); + // we need the read/write counts so the codegen knows whether or not + // global variables are used + PerfVisitor{}.visit_program(*ast); + // setup codegen + std::stringstream ss{}; + CodegenCVisitor cv{"temp.mod", ss, "double", false}; + cv.setup(*ast); + cv.print_mechanism_range_var_structure(true); + return ss.str(); +} + SCENARIO("Check parameter constness with VERBATIM block", "[codegen][verbatim_variable_constness]") { GIVEN("A mod file containing parameter range variables that are updated in VERBATIM block") { @@ -247,23 +284,67 @@ SCENARIO("Check parameter constness with VERBATIM block", )"; THEN("Variable used in VERBATIM shouldn't be marked as const") { - std::stringstream ss; - - /// parse mod file & print mechanism structure - const auto& ast = NmodlDriver().parse_string(nmodl_text); - auto cvisitor = create_c_visitor(ast, nmodl_text, ss); - cvisitor->print_mechanism_range_var_structure(); - + auto const generated = get_instance_structure(nmodl_text); std::string expected_code = R"( - /** all mechanism instance variables */ + /** all mechanism instance variables and global variables */ struct IntervalFire_Instance { - double* __restrict__ invl; - const double* __restrict__ burst_start; - double* __restrict__ v_unused; + double* __restrict__ invl{}; + const double* __restrict__ burst_start{}; + double* __restrict__ v_unused{}; + IntervalFire_Store* global{&IntervalFire_global}; }; )"; + REQUIRE(reindent_text(generated) == reindent_text(expected_code)); + } + } +} - REQUIRE(reindent_text(ss.str()) == reindent_text(expected_code)); +SCENARIO("Check NEURON globals are added to the instance struct on demand", + "[codegen][global_variables]") { + GIVEN("A MOD file that uses global variables") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX GlobalTest + RANGE temperature + } + INITIAL { + temperature = celsius + secondorder + pi + } + )"; + THEN("The instance struct should contain these variables") { + auto const generated = get_instance_structure(nmodl_text); + REQUIRE_THAT(generated, Contains("double* __restrict__ celsius{&coreneuron::celsius}")); + REQUIRE_THAT(generated, Contains("double* __restrict__ pi{&coreneuron::pi}")); + REQUIRE_THAT(generated, + Contains("int* __restrict__ secondorder{&coreneuron::secondorder}")); + } + } + GIVEN("A MOD file that implicitly uses global variables") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX ImplicitTest + } + INITIAL { + LOCAL x + x = nrn_ghk(1, 2, 3, 4) + } + )"; + THEN("The instance struct should contain celsius for the implicit 5th argument") { + auto const generated = get_instance_structure(nmodl_text); + REQUIRE_THAT(generated, Contains("celsius")); + } + } + GIVEN("A MOD file that does not touch celsius, secondorder or pi") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX GlobalTest + } + )"; + THEN("The instance struct should not contain those variables") { + auto const generated = get_instance_structure(nmodl_text); + REQUIRE_THAT(generated, !Contains("celsius")); + REQUIRE_THAT(generated, !Contains("pi")); + REQUIRE_THAT(generated, !Contains("secondorder")); } } } diff --git a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp new file mode 100644 index 0000000000..8726d6f6d6 --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp @@ -0,0 +1,60 @@ +/************************************************************************* + * Copyright (C) 2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/implicit_argument_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +#include + +using namespace nmodl; +using nmodl::test_utils::reindent_text; + +using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 + +//============================================================================= +// Implicit visitor tests +//============================================================================= + +std::string generate_mod_after_implicit_argument_visitor(std::string const& text) { + parser::NmodlDriver driver{}; + auto const ast = driver.parse_string(text); + visitor::SymtabVisitor{}.visit_program(*ast); + visitor::ImplicitArgumentVisitor{}.visit_program(*ast); + return to_nmodl(*ast); +} + +SCENARIO("Check insertion of implicit arguments", "[codegen][implicit_arguments]") { + GIVEN("A mod file that calls functions that may need implicit arguments") { + auto const nmodl_text = R"( + NEURON { + SUFFIX ImplicitTest + } + INITIAL { + at_time(foo) + at_time(a+b) + at_time(nt, flop) + nrn_ghk(-50(mV), .001(mM), 10(mM), 2) + nrn_ghk(-50(mV), .001(mM), 10(mM), 2, -273.15) + } + )"; + auto const modified_nmodl = generate_mod_after_implicit_argument_visitor(nmodl_text); + THEN("at_time should have nt as its first argument") { + REQUIRE_THAT(modified_nmodl, Contains("at_time(nt, foo)")); + REQUIRE_THAT(modified_nmodl, Contains("at_time(nt, a+b)")); + REQUIRE_THAT(modified_nmodl, Contains("at_time(nt, flop)")); + } + THEN("nrn_ghk should have a temperature as its last argument") { + REQUIRE_THAT(modified_nmodl, + Contains("nrn_ghk(-50(mV), .001(mM), 10(mM), 2, celsius)")); + REQUIRE_THAT(modified_nmodl, + Contains("nrn_ghk(-50(mV), .001(mM), 10(mM), 2, -273.15)")); + } + } +} From 75f732e05eb0832dfbd1a1169ec69c11e9b94431 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Tue, 30 Aug 2022 11:44:33 +0200 Subject: [PATCH 452/871] Prefer printer->fmt_X(...) in code generation (BlueBrain/nmodl#917) NMODL Repo SHA: BlueBrain/nmodl@1d6a2ff18c4e551e5ca03a9784a2a801b92658ca --- src/nmodl/codegen/codegen_acc_visitor.cpp | 51 +-- src/nmodl/codegen/codegen_c_visitor.cpp | 493 ++++++++++----------- src/nmodl/codegen/codegen_c_visitor.hpp | 6 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 6 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 54 +-- src/nmodl/printer/code_printer.hpp | 7 + 6 files changed, 293 insertions(+), 324 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index e11cf0ea19..dc00dd48a7 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -96,17 +96,17 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { } printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->add_line(fmt::format("static inline void* mem_alloc({}) {}", args, "{")); - printer->add_line(" void* ptr;"); - printer->add_line(" cudaMallocManaged(&ptr, num*size);"); - printer->add_line(" cudaMemset(ptr, 0, num*size);"); - printer->add_line(" return ptr;"); - printer->add_line("}"); + printer->fmt_start_block("static inline void* mem_alloc({})", args); + printer->add_line("void* ptr;"); + printer->add_line("cudaMallocManaged(&ptr, num*size);"); + printer->add_line("cudaMemset(ptr, 0, num*size);"); + printer->add_line("return ptr;"); + printer->end_block(1); printer->add_newline(2); - printer->add_line("static inline void mem_free(void* ptr) {"); - printer->add_line(" cudaFree(ptr);"); - printer->add_line("}"); + printer->start_block("static inline void mem_free(void* ptr)"); + printer->add_line("cudaFree(ptr);"); + printer->end_block(1); } /** @@ -121,10 +121,10 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { */ void CodegenAccVisitor::print_abort_routine() const { printer->add_newline(2); - printer->add_line("static inline void coreneuron_abort() {"); - printer->add_line(" printf(\"Error : Issue while running OpenACC kernel \\n\");"); - printer->add_line(" assert(0==1);"); - printer->add_line("}"); + printer->start_block("static inline void coreneuron_abort()"); + printer->add_line("printf(\"Error : Issue while running OpenACC kernel \\n\");"); + printer->add_line("assert(0==1);"); + printer->end_block(1); } void CodegenAccVisitor::print_net_send_buffering_grow() { @@ -135,8 +135,7 @@ void CodegenAccVisitor::print_eigen_linear_solver(const std::string& /* float_ty if (N <= 4) { printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { - printer->add_line( - fmt::format("nmodl_eigen_xm = partialPivLu<{}>nmodl_eigen_jm, nmodl_eigen_fm);", N)); + printer->fmt_line("nmodl_eigen_xm = partialPivLu<{}>nmodl_eigen_jm, nmodl_eigen_fm);", N); } } @@ -191,11 +190,11 @@ void CodegenAccVisitor::print_nrn_cur_matrix_shadow_update() { if (info.point_process) { print_atomic_reduction_pragma(); } - printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); + printer->fmt_line("vec_rhs[node_id] {} rhs;", rhs_op); if (info.point_process) { print_atomic_reduction_pragma(); } - printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); + printer->fmt_line("vec_d[node_id] {} g;", d_op); } void CodegenAccVisitor::print_fast_imem_calculation() { @@ -209,11 +208,11 @@ void CodegenAccVisitor::print_fast_imem_calculation() { if (info.point_process) { print_atomic_reduction_pragma(); } - printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} rhs;", rhs_op)); + printer->fmt_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} rhs;", rhs_op); if (info.point_process) { print_atomic_reduction_pragma(); } - printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;", d_op)); + printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;", d_op); printer->end_block(1); } @@ -258,12 +257,10 @@ void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { printer->add_line("double* device_vec = cnrn_target_copyin(vec, vec_size / sizeof(double));"); printer->add_line("void* device_ns = cnrn_target_deviceptr(*ns);"); printer->add_line("ThreadDatum* device_thread = cnrn_target_deviceptr(thread);"); - printer->add_line( - fmt::format("cnrn_target_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns);", - info.thread_data_index - 1)); - printer->add_line( - fmt::format("cnrn_target_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec);", - list_num)); + printer->fmt_line("cnrn_target_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns);", + info.thread_data_index - 1); + printer->fmt_line("cnrn_target_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec);", + list_num); printer->end_block(1); } @@ -341,8 +338,8 @@ void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { void CodegenAccVisitor::print_dt_update_to_device() const { - printer->add_line(fmt::format("#pragma acc update device({}) if (nt->compute_gpu)", - get_variable_name(naming::NTHREAD_DT_VARIABLE))); + printer->fmt_line("#pragma acc update device({}) if (nt->compute_gpu)", + get_variable_name(naming::NTHREAD_DT_VARIABLE)); } } // namespace codegen diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index cf7ab83c1a..51e6602110 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -214,15 +214,15 @@ void CodegenCVisitor::visit_from_statement(const ast::FromStatement& node) { const auto& to = node.get_to(); const auto& inc = node.get_increment(); const auto& block = node.get_statement_block(); - printer->add_text(fmt::format("for(int {}=", name)); + printer->fmt_text("for(int {}=", name); from->accept(*this); - printer->add_text(fmt::format("; {}<=", name)); + printer->fmt_text("; {}<=", name); to->accept(*this); if (inc) { - printer->add_text(fmt::format("; {}+=", name)); + printer->fmt_text("; {}+=", name); inc->accept(*this); } else { - printer->add_text(fmt::format("; {}++", name)); + printer->fmt_text("; {}++", name); } printer->add_text(")"); block->accept(*this); @@ -1151,8 +1151,8 @@ void CodegenCVisitor::print_channel_iteration_block_end() { void CodegenCVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { - printer->add_line(fmt::format("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW)); - printer->add_line(fmt::format("double* shadow_d = nt->{};", naming::NTHREAD_D_SHADOW)); + printer->fmt_line("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); + printer->fmt_line("double* shadow_d = nt->{};", naming::NTHREAD_D_SHADOW); } } @@ -1165,9 +1165,9 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_rhs[node_id] {} rhs;", rhs_op)); + printer->fmt_line("vec_rhs[node_id] {} rhs;", rhs_op); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_d[node_id] {} g;", d_op)); + printer->fmt_line("vec_d[node_id] {} g;", d_op); } } @@ -1178,9 +1178,9 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { if (info.point_process) { printer->add_line("int node_id = node_index[id];"); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op)); + printer->fmt_line("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("vec_d[node_id] {} shadow_d[id];", d_op)); + printer->fmt_line("vec_d[node_id] {} shadow_d[id];", d_op); } } @@ -1200,8 +1200,7 @@ void CodegenCVisitor::print_shadow_reduction_statements() { print_atomic_reduction_pragma(); auto lhs = get_variable_name(statement.lhs); auto rhs = get_variable_name(shadow_varname(statement.lhs)); - auto text = fmt::format("{} {} {};", lhs, statement.op, rhs); - printer->add_line(text); + printer->fmt_line("{} {} {};", lhs, statement.op, rhs); } shadow_statements.clear(); } @@ -1250,25 +1249,25 @@ bool CodegenCVisitor::optimize_ion_variable_copies() const { void CodegenCVisitor::print_memory_allocation_routine() const { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->add_line(fmt::format("static inline void* mem_alloc({}) {}", args, "{")); - printer->add_line(" void* ptr;"); - printer->add_line(" posix_memalign(&ptr, alignment, num*size);"); - printer->add_line(" memset(ptr, 0, size);"); - printer->add_line(" return ptr;"); - printer->add_line("}"); + printer->fmt_start_block("static inline void* mem_alloc({})", args); + printer->add_line("void* ptr;"); + printer->add_line("posix_memalign(&ptr, alignment, num*size);"); + printer->add_line("memset(ptr, 0, size);"); + printer->add_line("return ptr;"); + printer->end_block(1); printer->add_newline(2); - printer->add_line("static inline void mem_free(void* ptr) {"); - printer->add_line(" free(ptr);"); - printer->add_line("}"); + printer->start_block("static inline void mem_free(void* ptr)"); + printer->add_line("free(ptr);"); + printer->end_block(1); } void CodegenCVisitor::print_abort_routine() const { printer->add_newline(2); - printer->add_line("static inline void coreneuron_abort() {"); - printer->add_line(" abort();"); - printer->add_line("}"); + printer->start_block("static inline void coreneuron_abort()"); + printer->add_line("abort();"); + printer->end_block(1); } @@ -1372,7 +1371,7 @@ void CodegenCVisitor::print_function_call(const FunctionCall& node) { } auto arguments = node.get_arguments(); - printer->add_text(fmt::format("{}(", function_name)); + printer->fmt_text("{}(", function_name); if (defined_method(name)) { printer->add_text(internal_method_arguments()); @@ -1513,7 +1512,7 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { printer->add_line("make_table = false;"); printer->add_indent(); - printer->add_text(fmt::format("{} = ", tmin_name)); + printer->fmt_text("{} = ", tmin_name); from->accept(*this); printer->add_text(";"); printer->add_newline(); @@ -1676,13 +1675,13 @@ void CodegenCVisitor::print_function_or_procedure(const ast::Block& node, const // function requires return variable declaration if (node.is_function_block()) { auto type = default_float_data_type(); - printer->add_line(fmt::format("{} ret_{} = 0.0;", type, name)); + printer->fmt_line("{} ret_{} = 0.0;", type, name); } else { - printer->add_line(fmt::format("int ret_{} = 0;", name)); + printer->fmt_line("int ret_{} = 0;", name); } print_statement_block(*node.get_statement_block(), false, false); - printer->add_line(fmt::format("return ret_{};", name)); + printer->fmt_line("return ret_{};", name); printer->end_block(1); } @@ -1783,8 +1782,8 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv auto float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line(fmt::format("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N)); - printer->add_line(fmt::format("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type)); + printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); print_statement_block(*node.get_setup_x_block(), false, false); @@ -1792,7 +1791,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv // Newton solver printer->start_block("struct functor"); printer->add_line("NrnThread* nt;"); - printer->add_line(fmt::format("{0}* inst;", instance_struct())); + printer->fmt_line("{0}* inst;", instance_struct()); printer->add_line("int id, pnodecount;"); printer->add_line("double v;"); printer->add_line("Datum* indexes;"); @@ -1807,28 +1806,28 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv print_statement_block(*node.get_initialize_block(), false, false); printer->end_block(2); - printer->add_line(fmt::format( + printer->fmt_line( "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes) : " - "nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes) {}", - instance_struct(), - "{}")); + "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}} " + "{{}}", + instance_struct()); printer->add_indent(); const auto& variable_block = *node.get_variable_block(); const auto& functor_block = *node.get_functor_block(); - printer->add_text(fmt::format( + printer->fmt_text( "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " "1>& nmodl_eigen_fm, " "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", float_type, N, - is_functor_const(variable_block, functor_block) ? "const " : "")); + is_functor_const(variable_block, functor_block) ? "const " : ""); printer->start_block(); - printer->add_line(fmt::format("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type)); - printer->add_line(fmt::format("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type)); - printer->add_line(fmt::format("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type)); + printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); print_statement_block(functor_block, false, false); printer->end_block(2); @@ -1837,9 +1836,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv print_statement_block(*node.get_finalize_block(), false, false); printer->end_block(1); - printer->end_block(0); - printer->add_text(";"); - printer->add_newline(); + printer->end_block(";"); // call newton solver with functor and X matrix that contains state vars printer->add_line("// call newton solver"); @@ -1858,12 +1855,11 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv const std::string float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->add_line( - fmt::format("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N)); - printer->add_line(fmt::format("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N)); - printer->add_line(fmt::format("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type)); - printer->add_line(fmt::format("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type)); - printer->add_line(fmt::format("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type)); + printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); + printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); print_statement_block(*node.get_variable_block(), false, false); print_statement_block(*node.get_initialize_block(), false, false); print_statement_block(*node.get_setup_x_block(), false, false); @@ -1881,11 +1877,11 @@ void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, i // Faster compared to LU, given the template specialization in Eigen. printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); } else { - printer->add_line( - fmt::format("nmodl_eigen_xm = Eigen::PartialPivLU>>(nmodl_eigen_jm).solve(nmodl_eigen_fm);", - float_type, - N)); + printer->fmt_line( + "nmodl_eigen_xm = Eigen::PartialPivLU>>(nmodl_eigen_jm).solve(nmodl_eigen_fm);", + float_type, + N); } } @@ -2122,9 +2118,9 @@ void CodegenCVisitor::print_nmodl_constants() { #else const std::string format_string = "static const double {} = {:.18g};"; #endif - printer->add_line(fmt::format(format_string, - it->get_node_name(), - stod(it->get_value()->get_value()))); + printer->fmt_line(format_string, + it->get_node_name(), + stod(it->get_value()->get_value())); } } } @@ -2133,24 +2129,24 @@ void CodegenCVisitor::print_nmodl_constants() { void CodegenCVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int first_pointer_var_index() {"); - printer->add_line(fmt::format(" return {};", info.first_pointer_var_index)); - printer->add_line("}"); + printer->start_block("static inline int first_pointer_var_index()"); + printer->fmt_line("return {};", info.first_pointer_var_index); + printer->end_block(1); } void CodegenCVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int float_variables_size() {"); - printer->add_line(fmt::format(" return {};", float_variables_size())); - printer->add_line("}"); + printer->start_block("static inline int float_variables_size()"); + printer->fmt_line("return {};", float_variables_size()); + printer->end_block(1); printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int int_variables_size() {"); - printer->add_line(fmt::format(" return {};", int_variables_size())); - printer->add_line("}"); + printer->start_block("static inline int int_variables_size()"); + printer->fmt_line("return {};", int_variables_size()); + printer->end_block(1); } @@ -2160,9 +2156,9 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { } printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline int num_net_receive_args() {"); - printer->add_line(fmt::format(" return {};", info.num_net_receive_parameters)); - printer->add_line("}"); + printer->start_block("static inline int num_net_receive_args()"); + printer->fmt_line("return {};", info.num_net_receive_parameters); + printer->end_block(1); } @@ -2179,12 +2175,12 @@ void CodegenCVisitor::print_mech_type_getter() { void CodegenCVisitor::print_memb_list_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->add_line("static inline Memb_list* get_memb_list(NrnThread* nt) {"); - printer->add_line(" if (nt->_ml_list == NULL) {"); - printer->add_line(" return NULL;"); - printer->add_line(" }"); - printer->add_line(" return nt->_ml_list[get_mech_type()];"); - printer->add_line("}"); + printer->start_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); + printer->start_block("if (!nt->_ml_list)"); + printer->add_line("return nullptr;"); + printer->end_block(1); + printer->add_line("return nt->_ml_list[get_mech_type()];"); + printer->end_block(1); } @@ -2221,35 +2217,33 @@ void CodegenCVisitor::print_thread_getters() { printer->add_line("/** thread specific helper routines for derivimplicit */"); printer->add_newline(1); - printer->add_line(fmt::format("static inline int* deriv{}_advance(ThreadDatum* thread) {}", list, "{")); - printer->add_line(fmt::format(" return &(thread[{}].i);", tid)); - printer->add_line("}"); + printer->fmt_start_block("static inline int* deriv{}_advance(ThreadDatum* thread)", list); + printer->fmt_line("return &(thread[{}].i);", tid); + printer->end_block(2); - printer->add_newline(1); - printer->add_line(fmt::format("static inline int dith{}() {}", list, "{")); - printer->add_line(fmt::format(" return {};", tid+1)); - printer->add_line("}"); + printer->fmt_start_block("static inline int dith{}()", list); + printer->fmt_line("return {};", tid+1); + printer->end_block(2); - printer->add_newline(1); - printer->add_line(fmt::format("static inline void** newtonspace{}(ThreadDatum* thread) {}", list, "{")); - printer->add_line(fmt::format(" return &(thread[{}]._pvoid);", tid+2)); - printer->add_line("}"); + printer->fmt_start_block("static inline void** newtonspace{}(ThreadDatum* thread)", list); + printer->fmt_line("return &(thread[{}]._pvoid);", tid+2); + printer->end_block(1); } if (info.vectorize && !info.thread_variables.empty()) { printer->add_newline(2); printer->add_line("/** tid for thread variables */"); - printer->add_line("static inline int thread_var_tid() {"); - printer->add_line(fmt::format(" return {};", info.thread_var_thread_id)); - printer->add_line("}"); + printer->start_block("static inline int thread_var_tid()"); + printer->fmt_line("return {};", info.thread_var_thread_id); + printer->end_block(1); } if (info.vectorize && !info.top_local_variables.empty()) { printer->add_newline(2); printer->add_line("/** tid for top local tread variables */"); - printer->add_line("static inline int top_local_var_tid() {"); - printer->add_line(fmt::format(" return {};", info.top_local_thread_id)); - printer->add_line("}"); + printer->start_block("static inline int top_local_var_tid()"); + printer->fmt_line("return {};", info.top_local_thread_id); + printer->end_block(1); } // clang-format on } @@ -2425,14 +2419,14 @@ void CodegenCVisitor::print_backend_info() { auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; printer->add_line("/*********************************************************"); - printer->add_line(fmt::format("Model Name : {}", info.mod_suffix)); - printer->add_line(fmt::format("Filename : {}", info.mod_file + ".mod")); - printer->add_line(fmt::format("NMODL Version : {}", nmodl_version())); - printer->add_line(fmt::format("Vectorized : {}", info.vectorize)); - printer->add_line(fmt::format("Threadsafe : {}", info.thread_safe)); - printer->add_line(fmt::format("Created : {}", stringutils::trim(date))); - printer->add_line(fmt::format("Backend : {}", backend_name())); - printer->add_line(fmt::format("NMODL Compiler : {}", version)); + printer->fmt_line("Model Name : {}", info.mod_suffix); + printer->fmt_line("Filename : {}", info.mod_file + ".mod"); + printer->fmt_line("NMODL Version : {}", nmodl_version()); + printer->fmt_line("Vectorized : {}", info.vectorize); + printer->fmt_line("Threadsafe : {}", info.thread_safe); + printer->fmt_line("Created : {}", stringutils::trim(date)); + printer->fmt_line("Backend : {}", backend_name()); + printer->fmt_line("NMODL Compiler : {}", version); printer->add_line("*********************************************************/"); } @@ -2838,16 +2832,16 @@ static size_t get_register_type_for_ba_block(const ast::Block* block) { void CodegenCVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); - printer->start_block(fmt::format("void _{}_reg() ", info.mod_file)); + printer->fmt_start_block("void _{}_reg() ", info.mod_file); // type related information auto suffix = add_escape_quote(info.mod_suffix); printer->add_newline(); printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); printer->fmt_line("{} = mech_type;", get_variable_name("mech_type", false)); - printer->add_line("if (mech_type == -1) {"); - printer->add_line(" return;"); - printer->add_line("}"); + printer->start_block("if (mech_type == -1)"); + printer->add_line("return;"); + printer->end_block(1); printer->add_newline(); printer->add_line("_nrn_layout_reg(mech_type, 0);"); // 0 for SoA @@ -2889,7 +2883,7 @@ void CodegenCVisitor::print_mechanism_register() { } if (!info.thread_variables.empty()) { - printer->add_line(fmt::format("{} = 0;", get_variable_name("thread_data_in_use"))); + printer->fmt_line("{} = 0;", get_variable_name("thread_data_in_use")); } if (info.thread_callback_register) { @@ -2899,7 +2893,7 @@ void CodegenCVisitor::print_mechanism_register() { if (info.emit_table_thread()) { auto name = method_name("check_table_thread"); - printer->add_line(fmt::format("_nrn_thread_table_reg(mech_type, {});", name)); + printer->fmt_line("_nrn_thread_table_reg(mech_type, {});", name); } // register read/write callbacks for pointers @@ -2917,12 +2911,12 @@ void CodegenCVisitor::print_mechanism_register() { for (auto& semantic: info.semantics) { auto args = fmt::format("mech_type, {}, {}", semantic.index, add_escape_quote(semantic.name)); - printer->add_line(fmt::format("hoc_register_dparam_semantics({});", args)); + printer->fmt_line("hoc_register_dparam_semantics({});", args); } if (info.is_watch_used()) { auto watch_fun = compute_method_name(BlockType::Watch); - printer->add_line(fmt::format("hoc_register_watch_check({}, mech_type);", watch_fun)); + printer->fmt_line("hoc_register_watch_check({}, mech_type);", watch_fun); } if (info.write_concentration) { @@ -2934,22 +2928,20 @@ void CodegenCVisitor::print_mechanism_register() { printer->add_line("add_nrn_has_net_event(mech_type);"); } if (info.artificial_cell) { - printer->add_line(fmt::format("add_nrn_artcell(mech_type, {});", info.tqitem_index)); + printer->fmt_line("add_nrn_artcell(mech_type, {});", info.tqitem_index); } if (net_receive_buffering_required()) { - printer->add_line(fmt::format("hoc_register_net_receive_buffering({}, mech_type);", - method_name("net_buf_receive"))); + printer->fmt_line("hoc_register_net_receive_buffering({}, mech_type);", + method_name("net_buf_receive")); } if (info.num_net_receive_parameters != 0) { auto net_recv_init_arg = "nullptr"; if (info.net_receive_initial_node != nullptr) { net_recv_init_arg = "net_init"; } - auto pnt_recline = - fmt::format("set_pnt_receive(mech_type, {}, {}, num_net_receive_args());", - method_name("net_receive"), - net_recv_init_arg); - printer->add_line(pnt_recline); + printer->fmt_line("set_pnt_receive(mech_type, {}, {}, num_net_receive_args());", + method_name("net_receive"), + net_recv_init_arg); } if (info.for_netcon_used) { // index where information about FOR_NETCON is stored in the integer array @@ -2957,7 +2949,7 @@ void CodegenCVisitor::print_mechanism_register() { std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { return a.name == naming::FOR_NETCON_SEMANTIC; })->index; - printer->add_line(fmt::format("add_nrn_fornetcons(mech_type, {});", index)); + printer->fmt_line("add_nrn_fornetcons(mech_type, {});", index); } if (info.net_event_used || info.net_send_used) { @@ -2970,8 +2962,7 @@ void CodegenCVisitor::print_mechanism_register() { const auto& block = info.before_after_blocks[i]; size_t register_type = get_register_type_for_ba_block(block); std::string function_name = method_name(fmt::format("nrn_before_after_{}", i)); - printer->add_line( - fmt::format("hoc_reg_ba(mech_type, {}, {});", function_name, register_type)); + printer->fmt_line("hoc_reg_ba(mech_type, {}, {});", function_name, register_type); } // register variables for hoc @@ -2991,26 +2982,24 @@ void CodegenCVisitor::print_thread_memory_callbacks() { printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); if (info.vectorize && info.derivimplicit_used()) { - printer->add_line( - fmt::format("thread[dith{}()].pval = NULL;", info.derivimplicit_list_num)); + printer->fmt_line("thread[dith{}()].pval = nullptr;", info.derivimplicit_list_num); } if (info.vectorize && (info.top_local_thread_size != 0)) { auto length = info.top_local_thread_size; auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); - auto line = fmt::format("thread[top_local_var_tid()].pval = {};", allocation); - printer->add_line(line); + printer->fmt_line("thread[top_local_var_tid()].pval = {};", allocation); } if (info.thread_var_data_size != 0) { auto length = info.thread_var_data_size; auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); - printer->add_line(fmt::format("if ({}) {}", thread_data_in_use, "{")); - printer->add_line(fmt::format(" thread[thread_var_tid()].pval = {};", allocation)); - printer->add_line("} else {"); - printer->add_line(fmt::format(" thread[thread_var_tid()].pval = {};", thread_data)); - printer->add_line(fmt::format(" {} = 1;", thread_data_in_use)); - printer->add_line("}"); + printer->fmt_start_block("if ({})", thread_data_in_use); + printer->fmt_line("thread[thread_var_tid()].pval = {};", allocation); + printer->restart_block("else"); + printer->fmt_line("thread[thread_var_tid()].pval = {};", thread_data); + printer->fmt_line("{} = 1;", thread_data_in_use); + printer->end_block(1); } printer->end_block(3); @@ -3022,8 +3011,8 @@ void CodegenCVisitor::print_thread_memory_callbacks() { // clang-format off if (info.vectorize && info.derivimplicit_used()) { int n = info.derivimplicit_list_num; - printer->add_line(fmt::format("free(thread[dith{}()].pval);", n)); - printer->add_line(fmt::format("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));", n)); + printer->fmt_line("free(thread[dith{}()].pval);", n); + printer->fmt_line("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));", n); } // clang-format on @@ -3034,12 +3023,11 @@ void CodegenCVisitor::print_thread_memory_callbacks() { if (info.thread_var_data_size != 0) { auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); - printer->add_line( - fmt::format("if (thread[thread_var_tid()].pval == {}) {}", thread_data, "{")); - printer->add_line(fmt::format(" {} = 0;", thread_data_in_use)); - printer->add_line("} else {"); - printer->add_line(" free(thread[thread_var_tid()].pval);"); - printer->add_line("}"); + printer->fmt_start_block("if (thread[thread_var_tid()].pval == {})", thread_data); + printer->fmt_line("{} = 0;", thread_data_in_use); + printer->restart_block("else"); + printer->add_line("free(thread[thread_var_tid()].pval);"); + printer->end_block(1); } printer->end_block(1); } @@ -3105,22 +3093,20 @@ void CodegenCVisitor::print_ion_var_structure() { for (auto& ion: info.ions) { for (auto& var: ion.writes) { - printer->add_line(fmt::format("{} {};", float_type, var)); + printer->fmt_line("{} {};", float_type, var); members.push_back(var); } } for (auto& var: info.currents) { if (!info.is_ion_variable(var)) { - printer->add_line(fmt::format("{} {};", float_type, var)); + printer->fmt_line("{} {};", float_type, var); members.push_back(var); } } print_ion_var_constructor(members); - printer->end_block(); - printer->add_text(";"); - printer->add_newline(); + printer->end_block(";"); } @@ -3129,7 +3115,7 @@ void CodegenCVisitor::print_ion_var_constructor(const std::vector& printer->add_newline(); printer->add_line("IonCurVar() : ", 0); for (int i = 0; i < members.size(); i++) { - printer->add_text(fmt::format("{}(0)", members[i])); + printer->fmt_text("{}(0)", members[i]); if (i + 1 < members.size()) { printer->add_text(", "); } @@ -3153,12 +3139,12 @@ void CodegenCVisitor::print_setup_range_variable() { auto type = float_data_type(); printer->add_newline(2); printer->add_line("/** allocate and setup array for range variable */"); - printer->start_block( - fmt::format("static inline {}* setup_range_variable(double* variable, int n) ", type)); - printer->add_line(fmt::format("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));", type)); - printer->add_line("for(size_t i = 0; i < n; i++) {"); - printer->add_line(" data[i] = variable[i];"); - printer->add_line("}"); + printer->fmt_start_block("static inline {}* setup_range_variable(double* variable, int n)", + type); + printer->fmt_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));", type); + printer->start_block("for(size_t i = 0; i < n; i++)"); + printer->add_line("data[i] = variable[i];"); + printer->end_block(1); printer->add_line("return data;"); printer->end_block(1); } @@ -3364,7 +3350,7 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, } print_global_method_annotation(); - printer->start_block(fmt::format("void {}({})", method, args)); + printer->fmt_start_block("void {}({})", method, args); if (type != BlockType::Destructor && type != BlockType::Constructor) { // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks // running anything on the GPU. @@ -3376,20 +3362,17 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, } printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); - printer->add_line( - fmt::format("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier())); - printer->add_line(fmt::format("double* {}data = ml->data;", ptr_type_qualifier())); - printer->add_line( - fmt::format("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier())); + printer->fmt_line("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier()); + printer->fmt_line("double* {}data = ml->data;", ptr_type_qualifier()); + printer->fmt_line("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier()); if (type == BlockType::Equation) { - printer->add_line( - fmt::format("double* {} vec_rhs = nt->_actual_rhs;", ptr_type_qualifier())); - printer->add_line(fmt::format("double* {} vec_d = nt->_actual_d;", ptr_type_qualifier())); + printer->fmt_line("double* {} vec_rhs = nt->_actual_rhs;", ptr_type_qualifier()); + printer->fmt_line("double* {} vec_d = nt->_actual_d;", ptr_type_qualifier()); print_rhs_d_shadow_variables(); } - printer->add_line(fmt::format("Datum* {}indexes = ml->pdata;", ptr_type_qualifier())); - printer->add_line(fmt::format("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier())); + printer->fmt_line("Datum* {}indexes = ml->pdata;", ptr_type_qualifier()); + printer->fmt_line("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier()); if (type == BlockType::Initial) { printer->add_newline(); @@ -3412,16 +3395,16 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { int nequation = info.num_equations; int list_num = info.derivimplicit_list_num; // clang-format off - printer->add_line(fmt::format("int& deriv_advance_flag = *deriv{}_advance(thread);", list_num)); + printer->fmt_line("int& deriv_advance_flag = *deriv{}_advance(thread);", list_num); printer->add_line("deriv_advance_flag = 0;"); print_deriv_advance_flag_transfer_to_device(); - printer->add_line(fmt::format("auto ns = newtonspace{}(thread);", list_num)); - printer->add_line(fmt::format("auto& th = thread[dith{}()];", list_num)); + printer->fmt_line("auto ns = newtonspace{}(thread);", list_num); + printer->fmt_line("auto& th = thread[dith{}()];", list_num); printer->start_block("if (*ns == nullptr)"); - printer->add_line(fmt::format("int vec_size = 2*{}*pnodecount*sizeof(double);", nequation)); - printer->add_line(fmt::format("double* vec = makevector(vec_size);", nequation)); - printer->add_line(fmt::format("th.pval = vec;", list_num)); - printer->add_line(fmt::format("*ns = nrn_cons_newtonspace({}, pnodecount);", nequation)); + printer->fmt_line("int vec_size = 2*{}*pnodecount*sizeof(double);", nequation); + printer->fmt_line("double* vec = makevector(vec_size);", nequation); + printer->fmt_line("th.pval = vec;", list_num); + printer->fmt_line("*ns = nrn_cons_newtonspace({}, pnodecount);", nequation); print_newtonspace_transfer_to_device(); printer->end_block(1); // clang-format on @@ -3437,11 +3420,11 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { } if (!info.changed_dt.empty()) { - printer->add_line(fmt::format("double _save_prev_dt = {};", - get_variable_name(naming::NTHREAD_DT_VARIABLE))); - printer->add_line(fmt::format("{} = {};", - get_variable_name(naming::NTHREAD_DT_VARIABLE), - info.changed_dt)); + printer->fmt_line("double _save_prev_dt = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE)); + printer->fmt_line("{} = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE), + info.changed_dt); print_dt_update_to_device(); } @@ -3449,7 +3432,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_channel_iteration_block_begin(BlockType::Initial); if (info.net_receive_node != nullptr) { - printer->add_line(fmt::format("{} = -1e20;", get_variable_name("tsave"))); + printer->fmt_line("{} = -1e20;", get_variable_name("tsave")); } print_initial_block(info.initial_node); @@ -3458,8 +3441,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_channel_iteration_tiling_block_end(); if (!info.changed_dt.empty()) { - printer->add_line( - fmt::format("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE))); + printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); print_dt_update_to_device(); } @@ -3502,8 +3484,7 @@ void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t bl /// print common function code like init/state/current printer->add_newline(2); - printer->add_line( - fmt::format("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id)); + printer->fmt_line("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id); print_global_function_common_code(BlockType::BeforeAfter, function_name); print_channel_iteration_tiling_block_begin(BlockType::BeforeAfter); @@ -3567,8 +3548,7 @@ void CodegenCVisitor::print_nrn_destructor() { void CodegenCVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); - printer->start_block( - fmt::format("static void {}(double* data, Datum* indexes, int type) ", method)); + printer->fmt_start_block("static void {}(double* data, Datum* indexes, int type) ", method); printer->add_line("// do nothing"); printer->end_block(1); } @@ -3586,19 +3566,19 @@ void CodegenCVisitor::print_watch_activate() { printer->add_newline(2); auto inst = fmt::format("{}* inst", instance_struct()); - printer->start_block( - fmt::format("static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, " - "double v, bool &watch_remove) ", - inst)); + printer->fmt_start_block( + "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, " + "double v, bool &watch_remove)", + inst); // initialize all variables only during first watch statement - printer->add_line("if (watch_remove == false) {"); + printer->start_block("if (watch_remove == false)"); for (int i = 0; i < info.watch_count; i++) { auto name = get_variable_name(fmt::format("watch{}", i + 1)); - printer->add_line(fmt::format(" {} = 0;", name)); + printer->fmt_line("{} = 0;", name); } - printer->add_line(" watch_remove = true;"); - printer->add_line("}"); + printer->add_line("watch_remove = true;"); + printer->end_block(1); /** * \todo Similar to neuron/coreneuron we are using @@ -3606,11 +3586,11 @@ void CodegenCVisitor::print_watch_activate() { */ for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; - printer->start_block(fmt::format("if (watch_id == {})", i)); + printer->fmt_start_block("if (watch_id == {})", i); auto varname = get_variable_name(fmt::format("watch{}", i + 1)); printer->add_indent(); - printer->add_text(fmt::format("{} = 2 + (", varname)); + printer->fmt_text("{} = 2 + (", varname); auto watch = statement->get_statements().front(); watch->get_expression()->visit_children(*this); printer->add_text(");"); @@ -3653,7 +3633,7 @@ void CodegenCVisitor::print_watch_check() { auto varname = get_variable_name(fmt::format("watch{}", i + 1)); // start block 1 - printer->start_block(fmt::format("if ({}&2 && watch_untriggered)", varname)); + printer->fmt_start_block("if ({}&2 && watch_untriggered)", varname); // start block 2 printer->add_indent(); @@ -3664,7 +3644,7 @@ void CodegenCVisitor::print_watch_check() { printer->increase_indent(); // start block 3 - printer->start_block(fmt::format("if (({}&1) == 0)", varname)); + printer->fmt_start_block("if (({}&1) == 0)", varname); printer->add_line("watch_untriggered = false;"); @@ -3680,13 +3660,13 @@ void CodegenCVisitor::print_watch_check() { printer->add_newline(); printer->end_block(1); - printer->add_line(fmt::format("{} = 3;", varname)); + printer->fmt_line("{} = 3;", varname); // end block 3 // start block 3 printer->decrease_indent(); printer->start_block("} else"); - printer->add_line(fmt::format("{} = 2;", varname)); + printer->fmt_line("{} = 2;", varname); printer->end_block(1); // end block 3 @@ -3715,9 +3695,8 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need print_kernel_data_present_annotation_block_begin(); } - printer->add_line(fmt::format("{}int nodecount = ml->nodecount;", param_type_qualifier())); - printer->add_line( - fmt::format("{}int pnodecount = ml->_nodecount_padded;", param_type_qualifier())); + printer->fmt_line("{}int nodecount = ml->nodecount;", param_type_qualifier()); + printer->fmt_line("{}int pnodecount = ml->_nodecount_padded;", param_type_qualifier()); printer->add_line("double* data = ml->data;"); printer->add_line("double* weights = nt->weights;"); printer->add_line("Datum* indexes = ml->pdata;"); @@ -3739,8 +3718,7 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need auto name = parameter->get_node_name(); bool var_used = VarUsageVisitor().variable_used(node, "(*" + name + ")"); if (var_used) { - auto statement = fmt::format("double* {} = weights + weight_index + {};", name, i); - printer->add_line(statement); + printer->fmt_line("double* {} = weights + weight_index + {};", name, i); RenameVisitor vr(name, "*" + name); node.visit_children(vr); } @@ -3768,12 +3746,12 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text(fmt::format("artcell_net_send(&{}, {}, {}, nt->_t+", tqitem, weight_index, pnt)); + printer->fmt_text("artcell_net_send(&{}, {}, {}, nt->_t+", tqitem, weight_index, pnt); } else { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text(fmt::format("ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t)); + printer->fmt_text("ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t); } // clang-format off print_vector_elements(arguments, ", "); @@ -3795,14 +3773,14 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { // artificial cells don't use spike buffering // clang-format off if (info.artificial_cell) { - printer->add_text(fmt::format("artcell_net_move(&{}, {}, ", tqitem, pnt)); + printer->fmt_text("artcell_net_move(&{}, {}, ", tqitem, pnt); print_vector_elements(arguments, ", "); printer->add_text(")"); } else { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->add_text(fmt::format("ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process)); + printer->fmt_text("ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); printer->add_text(")"); @@ -3818,7 +3796,7 @@ void CodegenCVisitor::print_net_event_call(const FunctionCall& node) { } else { auto point_process = get_variable_name("point_process"); printer->add_text("net_send_buffering("); - printer->add_text(fmt::format("ml->_net_send_buffer, 1, -1, -1, {}, ", point_process)); + printer->fmt_text("ml->_net_send_buffer, 1, -1, -1, {}, ", point_process); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); } @@ -3875,7 +3853,7 @@ void CodegenCVisitor::print_net_init() { auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); - printer->start_block(fmt::format("static void net_init({}) ", args)); + printer->fmt_start_block("static void net_init({}) ", args); auto block = node->get_statement_block().get(); if (block->get_statements().empty()) { printer->add_line("// do nothing"); @@ -3898,18 +3876,16 @@ void CodegenCVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); print_net_send_buf_update_to_host(); - printer->add_line("for (int i=0; i < nsb->_cnt; i++) {"); - printer->add_line(" int type = nsb->_sendtype[i];"); - printer->add_line(" int tid = nt->id;"); - printer->add_line(" double t = nsb->_nsb_t[i];"); - printer->add_line(" double flag = nsb->_nsb_flag[i];"); - printer->add_line(" int vdata_index = nsb->_vdata_index[i];"); - printer->add_line(" int weight_index = nsb->_weight_index[i];"); - printer->add_line(" int point_index = nsb->_pnt_index[i];"); - // clang-format off - printer->add_line(" net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag);"); - // clang-format on - printer->add_line("}"); + printer->start_block("for (int i=0; i < nsb->_cnt; i++)"); + printer->add_line("int type = nsb->_sendtype[i];"); + printer->add_line("int tid = nt->id;"); + printer->add_line("double t = nsb->_nsb_t[i];"); + printer->add_line("double flag = nsb->_nsb_flag[i];"); + printer->add_line("int vdata_index = nsb->_vdata_index[i];"); + printer->add_line("int weight_index = nsb->_weight_index[i];"); + printer->add_line("int point_index = nsb->_pnt_index[i];"); + printer->add_line("net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag);"); + printer->end_block(1); printer->add_line("nsb->_cnt = 0;"); print_net_send_buf_count_update_to_device(); } @@ -3922,10 +3898,9 @@ std::string CodegenCVisitor::net_receive_buffering_declaration() { void CodegenCVisitor::print_get_memb_list() { printer->add_line("Memb_list* ml = get_memb_list(nt);"); - printer->add_line("if (ml == NULL) {"); - printer->add_line(" return;"); - printer->add_line("}"); - printer->add_newline(); + printer->start_block("if (!ml)"); + printer->add_line("return;"); + printer->end_block(2); } @@ -3954,8 +3929,7 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { print_kernel_data_present_annotation_block_begin(); - printer->add_line( - fmt::format("NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;", ptr_type_qualifier())); + printer->fmt_line("NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;", ptr_type_qualifier()); if (need_mech_inst) { printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); } @@ -3969,8 +3943,7 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->add_line("int weight_index = nrb->_weight_index[index];"); printer->add_line("double flag = nrb->_nrb_flag[index];"); printer->add_line("Point_process* point_process = nt->pntprocs + offset;"); - printer->add_line( - fmt::format("{}(t, point_process, inst, nt, ml, weight_index, flag);", net_receive)); + printer->fmt_line("{}(t, point_process, inst, nt, ml, weight_index, flag);", net_receive); printer->end_block(1); print_net_receive_loop_end(); @@ -3988,9 +3961,9 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { } void CodegenCVisitor::print_net_send_buffering_grow() { - printer->add_line("if(i >= nsb->_size) {"); - printer->add_line(" nsb->grow();"); - printer->add_line("}"); + printer->start_block("if(i >= nsb->_size)"); + printer->add_line("nsb->grow();"); + printer->end_block(1); } void CodegenCVisitor::print_net_send_buffering() { @@ -4003,19 +3976,19 @@ void CodegenCVisitor::print_net_send_buffering() { auto args = "NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; - printer->start_block(fmt::format("static inline void net_send_buffering({}) ", args)); + printer->fmt_start_block("static inline void net_send_buffering({}) ", args); printer->add_line("int i = 0;"); print_device_atomic_capture_annotation(); printer->add_line("i = nsb->_cnt++;"); print_net_send_buffering_grow(); - printer->add_line("if(i < nsb->_size) {"); - printer->add_line(" nsb->_sendtype[i] = type;"); - printer->add_line(" nsb->_vdata_index[i] = vdata_index;"); - printer->add_line(" nsb->_weight_index[i] = weight_index;"); - printer->add_line(" nsb->_pnt_index[i] = point_index;"); - printer->add_line(" nsb->_nsb_t[i] = t;"); - printer->add_line(" nsb->_nsb_flag[i] = flag;"); - printer->add_line("}"); + printer->start_block("if(i < nsb->_size)"); + printer->add_line("nsb->_sendtype[i] = type;"); + printer->add_line("nsb->_vdata_index[i] = vdata_index;"); + printer->add_line("nsb->_weight_index[i] = weight_index;"); + printer->add_line("nsb->_pnt_index[i] = point_index;"); + printer->add_line("nsb->_nsb_t[i] = t;"); + printer->add_line("nsb->_nsb_flag[i] = flag;"); + printer->end_block(1); printer->end_block(1); } @@ -4042,7 +4015,7 @@ void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { return a.name == naming::FOR_NETCON_SEMANTIC; })->index; - printer->add_text(fmt::format("const size_t offset = {}*pnodecount + id;", index)); + printer->fmt_text("const size_t offset = {}*pnodecount + id;", index); printer->add_newline(); printer->add_line( "const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]];"); @@ -4090,8 +4063,7 @@ void CodegenCVisitor::print_net_receive_kernel() { } printer->add_newline(2); - printer->start_block( - fmt::format("static inline void {}({}) ", name, get_parameter_str(params))); + printer->fmt_start_block("static inline void {}({}) ", name, get_parameter_str(params)); print_net_receive_common_code(*node, info.artificial_cell); if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); @@ -4104,7 +4076,7 @@ void CodegenCVisitor::print_net_receive_kernel() { printer->add_line("v = nt->_actual_v[node_id];"); } - printer->add_line(fmt::format("{} = t;", get_variable_name("tsave"))); + printer->fmt_line("{} = t;", get_variable_name("tsave")); if (info.is_watch_used()) { printer->add_line("bool watch_remove = false;"); @@ -4134,13 +4106,13 @@ void CodegenCVisitor::print_net_receive() { params.emplace_back("", "int", "", "weight_index"); params.emplace_back("", "double", "", "flag"); printer->add_newline(2); - printer->start_block(fmt::format("static void {}({}) ", name, get_parameter_str(params))); + printer->fmt_start_block("static void {}({}) ", name, get_parameter_str(params)); printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); - printer->add_line("if (nrb->_cnt >= nrb->_size) {"); - printer->add_line(" realloc_net_receive_buffer(nt, ml);"); - printer->add_line("}"); + printer->start_block("if (nrb->_cnt >= nrb->_size)"); + printer->add_line("realloc_net_receive_buffer(nt, ml);"); + printer->end_block(1); printer->add_line("int id = nrb->_cnt;"); printer->add_line("nrb->_pnt_index[id] = pnt-nt->pntprocs;"); printer->add_line("nrb->_weight_index[id] = weight_index;"); @@ -4222,9 +4194,7 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { printer->end_block(1); printer->add_line("return 0;"); printer->end_block(1); // operator() - printer->end_block(); // struct - printer->add_text(";"); - printer->add_newline(); + printer->end_block(";"); // struct printer->end_block(2); // namespace printer->fmt_start_block("int {}_{}({})", block_name, suffix, ext_params); printer->add_line(instance); @@ -4348,13 +4318,14 @@ void CodegenCVisitor::print_nrn_current(const BreakpointBlock& node) { const auto& block = node.get_statement_block(); printer->add_newline(2); print_device_method_annotation(); - printer->start_block( - fmt::format("inline double nrn_current_{}({})", info.mod_suffix, get_parameter_str(args))); + printer->fmt_start_block("inline double nrn_current_{}({})", + info.mod_suffix, + get_parameter_str(args)); printer->add_line("double current = 0.0;"); print_statement_block(*block, false, false); for (auto& current: info.currents) { auto name = get_variable_name(current); - printer->add_line(fmt::format("current += {};", name)); + printer->fmt_line("current += {};", name); } printer->add_line("return current;"); printer->end_block(1); @@ -4373,7 +4344,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no sum += "+"; } } - printer->add_line(fmt::format("double rhs = {};", sum)); + printer->fmt_line("double rhs = {};", sum); } std::string sum; @@ -4384,7 +4355,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no sum += "+"; } } - printer->add_line(fmt::format("double g = {};", sum)); + printer->fmt_line("double g = {};", sum); for (const auto& conductance: info.conductances) { if (!conductance.ion.empty()) { @@ -4399,20 +4370,20 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { - printer->add_line(fmt::format("double g = nrn_current_{}({}+0.001);", + printer->fmt_line("double g = nrn_current_{}({}+0.001);", info.mod_suffix, - internal_method_arguments())); + internal_method_arguments()); for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { auto name = get_variable_name(var); - printer->add_line(fmt::format("double di{} = {};", ion.name, name)); + printer->fmt_line("double di{} = {};", ion.name, name); } } } - printer->add_line(fmt::format("double rhs = nrn_current_{}({});", + printer->fmt_line("double rhs = nrn_current_{}({});", info.mod_suffix, - internal_method_arguments())); + internal_method_arguments()); printer->add_line("g = (g-rhs)/0.001;"); for (auto& ion: info.ions) { for (auto& var: ion.writes) { @@ -4459,7 +4430,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { if (info.point_process) { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - printer->add_line(fmt::format("double mfactor = 1.e2/{};", area)); + printer->fmt_line("double mfactor = 1.e2/{};", area); printer->add_line("g = g*mfactor;"); printer->add_line("rhs = rhs*mfactor;"); } @@ -4488,9 +4459,9 @@ void CodegenCVisitor::print_fast_imem_calculation() { printer->add_line("int node_id = node_index[id];"); } print_atomic_reduction_pragma(); - printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs)); + printer->fmt_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs); print_atomic_reduction_pragma(); - printer->add_line(fmt::format("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d)); + printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d); if (nrn_cur_reduction_loop_required()) { print_shadow_reduction_block_end(); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 8cd8188dcd..9b19da396e 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1971,8 +1971,10 @@ void CodegenCVisitor::print_function_declaration(const T& node, const std::strin print_device_method_annotation(); printer->add_indent(); - printer->add_text(fmt::format( - "inline {} {}({})", return_type, method_name(name), get_parameter_str(internal_params))); + printer->fmt_text("inline {} {}({})", + return_type, + method_name(name), + get_parameter_str(internal_params)); enable_variable_name_lookup = true; } diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp index ae4d51fc6c..8f58f04917 100644 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ b/src/nmodl/codegen/codegen_cuda_visitor.cpp @@ -69,7 +69,7 @@ void CodegenCudaVisitor::print_atomic_op(const std::string& lhs, } else { throw std::runtime_error(fmt::format("CUDA backend error : {} not supported", op)); } - printer->add_line(fmt::format("{}(&{}, {});", function, lhs, rhs)); + printer->fmt_line("{}(&{}, {});", function, lhs, rhs); } @@ -190,11 +190,11 @@ void CodegenCudaVisitor::print_wrapper_routine(std::string wrapper_function, Blo auto compute_function = compute_method_name(type); printer->add_newline(2); - printer->start_block(fmt::format("void {}({})", wrapper_function, args)); + printer->fmt_start_block("void {}({})", wrapper_function, args); printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int nthread = 256;"); printer->add_line("int nblock = (nodecount+nthread-1)/nthread;"); - printer->add_line(fmt::format("{}<<>>(nt, ml, type);", compute_function)); + printer->fmt_line("{}<<>>(nt, ml, type);", compute_function); printer->add_line("cudaDeviceSynchronize();"); printer->end_block(); printer->add_newline(); diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp index 40b5607166..fa1621178c 100644 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ b/src/nmodl/codegen/codegen_ispc_visitor.cpp @@ -240,7 +240,7 @@ void CodegenIspcVisitor::print_atomic_op(const std::string& lhs, } else { throw std::runtime_error(fmt::format("ISPC backend error : {} not supported", op)); } - printer->add_line(fmt::format("{}(&{}, {});", function, lhs, rhs)); + printer->fmt_line("{}(&{}, {});", function, lhs, rhs); } @@ -249,8 +249,8 @@ void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_reduction() { auto d_op = operator_for_d(); if (info.point_process) { printer->add_line("uniform int node_id = node_index[id];"); - printer->add_line(fmt::format("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op)); - printer->add_line(fmt::format("vec_d[node_id] {} shadow_d[id];", d_op)); + printer->fmt_line("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op); + printer->fmt_line("vec_d[node_id] {} shadow_d[id];", d_op); } } @@ -271,10 +271,8 @@ void CodegenIspcVisitor::print_shadow_reduction_block_end() { void CodegenIspcVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { - printer->add_line( - fmt::format("double* uniform shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW)); - printer->add_line( - fmt::format("double* uniform shadow_d = nt->{};", naming::NTHREAD_D_SHADOW)); + printer->fmt_line("double* uniform shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); + printer->fmt_line("double* uniform shadow_d = nt->{};", naming::NTHREAD_D_SHADOW); } } @@ -374,25 +372,22 @@ void CodegenIspcVisitor::print_global_function_common_code(BlockType type, auto params = get_global_function_parms(ptr_type_qualifier()); print_global_method_annotation(); - printer->start_block(fmt::format("export void {}({})", method, get_parameter_str(params))); + printer->fmt_start_block("export void {}({})", method, get_parameter_str(params)); print_kernel_data_present_annotation_block_begin(); printer->add_line("uniform int nodecount = ml->nodecount;"); printer->add_line("uniform int pnodecount = ml->_nodecount_padded;"); - printer->add_line( - fmt::format("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier())); - printer->add_line(fmt::format("double* {}data = ml->data;", ptr_type_qualifier())); - printer->add_line( - fmt::format("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier())); + printer->fmt_line("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier()); + printer->fmt_line("double* {}data = ml->data;", ptr_type_qualifier()); + printer->fmt_line("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier()); if (type == BlockType::Equation) { - printer->add_line( - fmt::format("double* {}vec_rhs = nt->_actual_rhs;", ptr_type_qualifier())); - printer->add_line(fmt::format("double* {}vec_d = nt->_actual_d;", ptr_type_qualifier())); + printer->fmt_line("double* {}vec_rhs = nt->_actual_rhs;", ptr_type_qualifier()); + printer->fmt_line("double* {}vec_d = nt->_actual_d;", ptr_type_qualifier()); print_rhs_d_shadow_variables(); } - printer->add_line(fmt::format("Datum* {}indexes = ml->pdata;", ptr_type_qualifier())); - printer->add_line(fmt::format("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier())); + printer->fmt_line("Datum* {}indexes = ml->pdata;", ptr_type_qualifier()); + printer->fmt_line("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier()); printer->add_newline(1); } @@ -444,7 +439,7 @@ void CodegenIspcVisitor::print_net_receive_buffering_wrapper() { return; } printer->add_newline(2); - printer->start_block(fmt::format("void {}(NrnThread* nt)", method_name("net_buf_receive"))); + printer->fmt_start_block("void {}(NrnThread* nt)", method_name("net_buf_receive")); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->start_block("if (ml == NULL)"); printer->add_line("return;"); @@ -453,7 +448,7 @@ void CodegenIspcVisitor::print_net_receive_buffering_wrapper() { instance_struct(), ptr_type_qualifier()); - printer->add_line(fmt::format("{}(inst, nt, ml);", method_name("ispc_net_buf_receive"))); + printer->fmt_line("{}(inst, nt, ml);", method_name("ispc_net_buf_receive")); printer->end_block(1); } @@ -470,7 +465,7 @@ void CodegenIspcVisitor::print_nmodl_constants() { for (auto& it: info.factor_definitions) { const std::string name = it->get_node_name() == "PI" ? "ISPC_PI" : it->get_node_name(); const std::string value = format_double_string(it->get_value()->get_value()); - printer->add_line(fmt::format("static const uniform double {} = {};", name, value)); + printer->fmt_line("static const uniform double {} = {};", name, value); } } } @@ -488,7 +483,7 @@ void CodegenIspcVisitor::print_wrapper_routine(const std::string& wrapper_functi auto compute_function = compute_method_name(type); printer->add_newline(2); - printer->start_block(fmt::format("void {}({})", function_name, args)); + printer->fmt_start_block("void {}({})", function_name, args); printer->add_line("int nodecount = ml->nodecount;"); printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", instance_struct(), @@ -511,20 +506,17 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { auto params = get_global_function_parms(""); auto compute_function = compute_method_name(BlockType::Initial); if (!emit_fallback[BlockType::Initial]) { - printer->add_line( - fmt::format("extern \"C\" void {}({});", compute_function, get_parameter_str(params))); + printer->fmt_line("extern \"C\" void {}({});", compute_function, get_parameter_str(params)); } if (nrn_cur_required() && !emit_fallback[BlockType::Equation]) { compute_function = compute_method_name(BlockType::Equation); - printer->add_line( - fmt::format("extern \"C\" void {}({});", compute_function, get_parameter_str(params))); + printer->fmt_line("extern \"C\" void {}({});", compute_function, get_parameter_str(params)); } if (nrn_state_required() && !emit_fallback[BlockType::State]) { compute_function = compute_method_name(BlockType::State); - printer->add_line( - fmt::format("extern \"C\" void {}({});", compute_function, get_parameter_str(params))); + printer->fmt_line("extern \"C\" void {}({});", compute_function, get_parameter_str(params)); } if (net_receive_required()) { @@ -532,9 +524,9 @@ void CodegenIspcVisitor::print_backend_compute_routine_decl() { net_recv_params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); net_recv_params.emplace_back("", "NrnThread*", "", "nt"); net_recv_params.emplace_back("", "Memb_list*", "", "ml"); - printer->add_line(fmt::format("extern \"C\" void {}({});", - method_name("ispc_net_buf_receive"), - get_parameter_str(net_recv_params))); + printer->fmt_line("extern \"C\" void {}({});", + method_name("ispc_net_buf_receive"), + get_parameter_str(net_recv_params)); } } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 565a56650e..f6b703d3da 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -82,11 +82,18 @@ class CodePrinter { add_line(fmt::format(std::forward(args)...)); } + /// fmt_start_block(args...) is just shorthand for start_block(fmt::format(args...)) template void fmt_start_block(Args&&... args) { start_block(fmt::format(std::forward(args)...)); } + /// fmt_text(args...) is just shorthand for add_text(fmt::format(args...)) + template + void fmt_text(Args&&... args) { + add_text(fmt::format(std::forward(args)...)); + } + void add_multi_line(const std::string&); void add_newline(int n = 1); From 13888107a167de5dfbf74b0e7b9eef62434bc932 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 30 Aug 2022 13:55:41 +0300 Subject: [PATCH 453/871] Update fmt submodule (BlueBrain/nmodl#914) * Updated fmt submodule to tag 9.1.0 to include commit fbb568b which fixes issues with cxx 17 * Added warning message in CMake for external installations of `fmt` which might be problematic if compiled with NVHPC and Intel compilers (EDG frontend) and with C++17 standard enabled NMODL Repo SHA: BlueBrain/nmodl@76e8aa5460ffd8f49f827862d02e41aa3523fb30 --- cmake/nmodl/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 54bb3d0f75..e9c5942c33 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -119,6 +119,14 @@ cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED) if(NMODL_3RDPARTY_USE_FMT) set_property(TARGET fmt PROPERTY POSITION_INDEPENDENT_CODE ON) endif() +if(NOT NMODL_3RDPARTY_USE_FMT + AND ((NMODL_PGI_COMPILER AND CMAKE_CXX_COMPILER_VERSION LESS_EQUAL 22.3.0) + OR CMAKE_CXX_COMPILER_ID STREQUAL "Intel")) + message( + WARNING + "fmt might generate issues with NVHPC <=22.3 and Intel compiler when installed with C++11 or later standard enabled" + ) +endif() cpp_cc_git_submodule(json BUILD PACKAGE nlohmann_json REQUIRED) cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED) # Tell spdlog not to use its bundled fmt, it should either use the fmt submodule or a truly external From 29c68c7216805753dcb969ea25c1536cd261025e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 30 Aug 2022 13:51:41 +0200 Subject: [PATCH 454/871] Fix Table Statement for functions (BlueBrain/nmodl#916) With the last patch we were not allocating memory and so it gives a bug. Do it in a better way by putting the symbol inside the AST and running the symtab after Add a test for CodegenTransformVisitor NMODL Repo SHA: BlueBrain/nmodl@fd87897317caff66005717648d608e1f65f39311 --- src/nmodl/codegen/CMakeLists.txt | 1 + src/nmodl/codegen/codegen_c_visitor.cpp | 4 -- .../codegen/codegen_transform_visitor.cpp | 27 ++++++++ .../codegen/codegen_transform_visitor.hpp | 48 ++++++++++++++ src/nmodl/main.cpp | 7 +++ test/nmodl/transpiler/unit/CMakeLists.txt | 5 +- .../transpiler/unit/codegen/transform.cpp | 63 +++++++++++++++++++ 7 files changed, 149 insertions(+), 6 deletions(-) create mode 100644 src/nmodl/codegen/codegen_transform_visitor.cpp create mode 100644 src/nmodl/codegen/codegen_transform_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/codegen/transform.cpp diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index ea659ce1f3..d261fa7acf 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -4,6 +4,7 @@ add_library( codegen STATIC codegen_acc_visitor.cpp + codegen_transform_visitor.cpp codegen_c_visitor.cpp codegen_compatibility_visitor.cpp codegen_cuda_visitor.cpp diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 51e6602110..eb8e16b4ed 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -2637,10 +2637,6 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool print_initialise printer->fmt_line("{}{} mfac_{}{};", qualifier, float_type, name, value_initialise); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); - if (block->is_function_block()) { - printer->fmt_line("{}* {}t_{};", float_type, qualifier, name); - codegen_global_variables.push_back(make_symbol("t_" + name)); - } } for (const auto& variable: info.table_statement_variables) { diff --git a/src/nmodl/codegen/codegen_transform_visitor.cpp b/src/nmodl/codegen/codegen_transform_visitor.cpp new file mode 100644 index 0000000000..aed51cbd87 --- /dev/null +++ b/src/nmodl/codegen/codegen_transform_visitor.cpp @@ -0,0 +1,27 @@ +/************************************************************************* + * Copyright (C) 2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/ast_decl.hpp" +#include "ast/function_block.hpp" +#include "ast/string.hpp" +#include "ast/table_statement.hpp" +#include "codegen/codegen_transform_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +using namespace ast; + +void CodegenTransformVisitor::visit_function_block(FunctionBlock& node) { + auto table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); + for (auto t: table_statements) { + auto t_ = std::dynamic_pointer_cast(t); + t_->set_table_vars({std::make_shared(new String(node.get_node_name()))}); + } +} +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_transform_visitor.hpp b/src/nmodl/codegen/codegen_transform_visitor.hpp new file mode 100644 index 0000000000..08dd1142eb --- /dev/null +++ b/src/nmodl/codegen/codegen_transform_visitor.hpp @@ -0,0 +1,48 @@ +/************************************************************************* + * Copyright (C) 2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::CodegenTransformVisitor + */ + +#include "visitors/ast_visitor.hpp" + +namespace nmodl { + +/** + * @addtogroup codegen_details + * @{ + */ + +/** + * \class CodegenTransformVisitor + * \brief Visitor to make last transformation to AST before codegen + * + * Modifications made: + * - add an argument to the table if it is inside a function. In this case + * the argument is the name of the function. + */ + +class CodegenTransformVisitor: public visitor::AstVisitor { + public: + /// \name Ctor & dtor + /// \{ + + /// Default constructor + CodegenTransformVisitor() = default; + + /// \} + + void visit_function_block(ast::FunctionBlock& node) override; +}; + +/** \} */ // end of codegen_details + +} // namespace nmodl diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index fcd4791c23..5d32b51898 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -16,6 +16,7 @@ #include "codegen/codegen_compatibility_visitor.hpp" #include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_ispc_visitor.hpp" +#include "codegen/codegen_transform_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" #include "pybind/pyembed.hpp" @@ -541,6 +542,12 @@ int main(int argc, const char* argv[]) { PerfVisitor().visit_program(*ast); } + { + CodegenTransformVisitor{}.visit_program(*ast); + ast_to_nmodl(*ast, filepath("TransformVisitor")); + SymtabVisitor(update_symtab).visit_program(*ast); + } + { if (ispc_backend) { logger->info("Running ISPC backend code generator"); diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 110e3a6c7c..a2340b0414 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -65,8 +65,9 @@ add_executable(testnewton newton/newton.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) -add_executable(testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp - codegen/codegen_utils.cpp codegen/codegen_c_visitor.cpp) +add_executable( + testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp + codegen/codegen_utils.cpp codegen/codegen_c_visitor.cpp codegen/transform.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) diff --git a/test/nmodl/transpiler/unit/codegen/transform.cpp b/test/nmodl/transpiler/unit/codegen/transform.cpp new file mode 100644 index 0000000000..234eec6a68 --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/transform.cpp @@ -0,0 +1,63 @@ +/************************************************************************* + * Copyright (C) 2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "codegen/codegen_transform_visitor.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/nmodl_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +//============================================================================= +// Variable rename tests +//============================================================================= + +static std::string run_transform_visitor(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + CodegenTransformVisitor{}.visit_program(*ast); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(*ast); + + return stream.str(); +} + +SCENARIO("Adding a variable for a table inside a function", "[visitor][transform]") { + GIVEN("A mod file with a table inside a function") { + // sample nmodl text + std::string input_nmodl_text = R"( + FUNCTION mAlpha(v) { + TABLE FROM 0 TO 150 WITH 1 + mAlpha = 1 + } + )"; + + /// expected result after adding the variable + std::string output_nmodl_text = R"( + FUNCTION mAlpha(v) { + TABLE mAlpha FROM 0 TO 150 WITH 1 + mAlpha = 1 + } + )"; + + std::string input = reindent_text(input_nmodl_text); + std::string expected_output = reindent_text(output_nmodl_text); + + THEN("variable has been added") { + auto result = run_transform_visitor(input); + REQUIRE(result == expected_output); + } + } +} From c6e153e3b1b5037610b42fae349aabf7910c0510 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 31 Aug 2022 11:04:29 +0200 Subject: [PATCH 455/871] Clean extern_definitions (BlueBrain/nmodl#918) The multi-definition map was of course not working, hopefully we only use the EXT_4 type NMODL Repo SHA: BlueBrain/nmodl@aa5046ef5de18ee4cf6fe3d1c7b0674cc5244366 --- src/nmodl/lexer/token_mapping.cpp | 211 +++++++++++------------------- 1 file changed, 76 insertions(+), 135 deletions(-) diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 84a6bc2db5..5bfc9299b5 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -18,7 +18,6 @@ namespace nmodl { using Token = parser::NmodlParser::token; using TokenType = parser::NmodlParser::token_type; -using Parser = parser::NmodlParser; /// details of lexer tokens namespace details { @@ -178,130 +177,75 @@ const static std::map methods = {{"adams", MethodInfo(D {"cvode_t", MethodInfo(0, 0)}, {"cvode_t_v", MethodInfo(0, 0)}}; - -/** - * Definition type similar to old implementation - * - * In the original implementation of NMODL (mod2c, nocmodl) different vectors were - * created for \c extdef, \c extdef2, \c extdef3, \c extdef4 etc. We are changing - * those vectors with map. This will help us to search - * in single map and find it's type. The types are defined as follows: - * - * - DefinitionType::EXT_DOUBLE : external names that can be used as doubles - * without giving an error message - * - DefinitionType::EXT_2 : external function names that can be used with - * array and function name arguments - * - DefinitionType::EXT_3 : function names that get two reset arguments - * - DefinitionType::EXT_4 : functions that need a first arg of \c NrnThread* - * - DefinitionType::EXT_5 : external definition names that are not \c threadsafe - * - */ -enum class DefinitionType { EXT_DOUBLE, EXT_2, EXT_3, EXT_4, EXT_5 }; - - -const static std::map extern_definitions = { - {"first_time", DefinitionType::EXT_DOUBLE}, - {"error", DefinitionType::EXT_DOUBLE}, - {"f_flux", DefinitionType::EXT_DOUBLE}, - {"b_flux", DefinitionType::EXT_DOUBLE}, - {"fabs", DefinitionType::EXT_DOUBLE}, - {"sqrt", DefinitionType::EXT_DOUBLE}, - {"sin", DefinitionType::EXT_DOUBLE}, - {"cos", DefinitionType::EXT_DOUBLE}, - {"tan", DefinitionType::EXT_DOUBLE}, - {"acos", DefinitionType::EXT_DOUBLE}, - {"asin", DefinitionType::EXT_DOUBLE}, - {"atan", DefinitionType::EXT_DOUBLE}, - {"atan2", DefinitionType::EXT_DOUBLE}, - {"sinh", DefinitionType::EXT_DOUBLE}, - {"cosh", DefinitionType::EXT_DOUBLE}, - {"tanh", DefinitionType::EXT_DOUBLE}, - {"floor", DefinitionType::EXT_DOUBLE}, - {"ceil", DefinitionType::EXT_DOUBLE}, - {"fmod", DefinitionType::EXT_DOUBLE}, - {"log10", DefinitionType::EXT_DOUBLE}, - {"log", DefinitionType::EXT_DOUBLE}, - {"pow", DefinitionType::EXT_DOUBLE}, - {"printf", DefinitionType::EXT_DOUBLE}, - {"prterr", DefinitionType::EXT_DOUBLE}, - {"exp", DefinitionType::EXT_DOUBLE}, - {"threshold", DefinitionType::EXT_DOUBLE}, - {"force", DefinitionType::EXT_DOUBLE}, - {"deflate", DefinitionType::EXT_DOUBLE}, - {"expfit", DefinitionType::EXT_DOUBLE}, - {"derivs", DefinitionType::EXT_DOUBLE}, - {"spline", DefinitionType::EXT_DOUBLE}, - {"hyperbol", DefinitionType::EXT_DOUBLE}, - {"revhyperbol", DefinitionType::EXT_DOUBLE}, - {"sigmoid", DefinitionType::EXT_DOUBLE}, - {"revsigmoid", DefinitionType::EXT_DOUBLE}, - {"harmonic", DefinitionType::EXT_DOUBLE}, - {"squarewave", DefinitionType::EXT_DOUBLE}, - {"sawtooth", DefinitionType::EXT_DOUBLE}, - {"revsawtooth", DefinitionType::EXT_DOUBLE}, - {"ramp", DefinitionType::EXT_DOUBLE}, - {"pulse", DefinitionType::EXT_DOUBLE}, - {"perpulse", DefinitionType::EXT_DOUBLE}, - {"step", DefinitionType::EXT_DOUBLE}, - {"perstep", DefinitionType::EXT_DOUBLE}, - {"erf", DefinitionType::EXT_DOUBLE}, - {"exprand", DefinitionType::EXT_DOUBLE}, - {"factorial", DefinitionType::EXT_DOUBLE}, - {"gauss", DefinitionType::EXT_DOUBLE}, - {"normrand", DefinitionType::EXT_DOUBLE}, - {"poisrand", DefinitionType::EXT_DOUBLE}, - {"poisson", DefinitionType::EXT_DOUBLE}, - {"setseed", DefinitionType::EXT_DOUBLE}, - {"scop_random", DefinitionType::EXT_DOUBLE}, - {"boundary", DefinitionType::EXT_DOUBLE}, - {"romberg", DefinitionType::EXT_DOUBLE}, - {"legendre", DefinitionType::EXT_DOUBLE}, - {"invert", DefinitionType::EXT_DOUBLE}, - {"stepforce", DefinitionType::EXT_DOUBLE}, - {"schedule", DefinitionType::EXT_DOUBLE}, - {"set_seed", DefinitionType::EXT_DOUBLE}, - {"nrn_pointing", DefinitionType::EXT_DOUBLE}, - {"state_discontinuity", DefinitionType::EXT_DOUBLE}, - {"net_send", DefinitionType::EXT_DOUBLE}, - {"net_move", DefinitionType::EXT_DOUBLE}, - {"net_event", DefinitionType::EXT_DOUBLE}, - {"nrn_random_play", DefinitionType::EXT_DOUBLE}, - {"nrn_ghk", DefinitionType::EXT_DOUBLE}, - {"romberg", DefinitionType::EXT_2}, - {"legendre", DefinitionType::EXT_2}, - {"deflate", DefinitionType::EXT_2}, - {"threshold", DefinitionType::EXT_3}, - {"squarewave", DefinitionType::EXT_3}, - {"sawtooth", DefinitionType::EXT_3}, - {"revsawtooth", DefinitionType::EXT_3}, - {"ramp", DefinitionType::EXT_3}, - {"pulse", DefinitionType::EXT_3}, - {"perpulse", DefinitionType::EXT_3}, - {"step", DefinitionType::EXT_3}, - {"perstep", DefinitionType::EXT_3}, - {"stepforce", DefinitionType::EXT_3}, - {"schedule", DefinitionType::EXT_3}, - {"at_time", DefinitionType::EXT_4}, - {"force", DefinitionType::EXT_5}, - {"deflate", DefinitionType::EXT_5}, - {"expfit", DefinitionType::EXT_5}, - {"derivs", DefinitionType::EXT_5}, - {"spline", DefinitionType::EXT_5}, - {"exprand", DefinitionType::EXT_5}, - {"gauss", DefinitionType::EXT_5}, - {"normrand", DefinitionType::EXT_5}, - {"poisrand", DefinitionType::EXT_5}, - {"poisson", DefinitionType::EXT_5}, - {"setseed", DefinitionType::EXT_5}, - {"scop_random", DefinitionType::EXT_5}, - {"boundary", DefinitionType::EXT_5}, - {"romberg", DefinitionType::EXT_5}, - {"invert", DefinitionType::EXT_5}, - {"stepforce", DefinitionType::EXT_5}, - {"schedule", DefinitionType::EXT_5}, - {"set_seed", DefinitionType::EXT_5}, - {"nrn_random_play", DefinitionType::EXT_5}}; - +const static std::vector extern_definitions = {"acos", + "asin", + "at_time", + "atan", + "atan2", + "b_flux", + "boundary", + "ceil", + "cos", + "cosh", + "deflate", + "derivs", + "erf", + "error", + "exp", + "expfit", + "exprand", + "f_flux", + "fabs", + "factorial", + "first_time", + "floor", + "fmod", + "force", + "gauss", + "harmonic", + "hyperbol", + "invert", + "legendre", + "log", + "log10", + "net_event", + "net_move", + "net_send", + "normrand", + "nrn_ghk", + "nrn_pointing", + "nrn_random_play", + "perpulse", + "perstep", + "poisrand", + "poisson", + "pow", + "printf", + "prterr", + "pulse", + "ramp", + "revhyperbol", + "revsawtooth", + "revsigmoid", + "romberg", + "sawtooth", + "schedule", + "scop_random", + "set_seed", + "setseed", + "sigmoid", + "sin", + "sinh", + "spline", + "sqrt", + "squarewave", + "state_discontinuity", + "step", + "stepforce", + "tan", + "tanh", + "threshold"}; +const static std::vector need_nt = {"at_time"}; /** * Checks if \c token is one of the functions coming from NEURON/CoreNEURON and needs @@ -311,8 +255,7 @@ const static std::map extern_definitions = { * @return True or false depending if the function needs NrnThread* argument */ bool needs_neuron_thread_first_arg(const std::string& token) { - auto extern_def = extern_definitions.find(token); - return extern_def != extern_definitions.end() && extern_def->second == DefinitionType::EXT_4; + return std::find(need_nt.cbegin(), need_nt.cend(), token) != need_nt.cend(); } @@ -341,7 +284,7 @@ TokenType keyword_type(const std::string& name) { * @return true if name is a keyword */ bool is_keyword(const std::string& name) { - return (details::keywords.find(name) != details::keywords.end()); + return details::keywords.find(name) != details::keywords.end(); } @@ -376,9 +319,7 @@ TokenType token_type(const std::string& name) { * @return vector of NEURON variables */ std::vector get_external_variables() { - std::vector result; - result.insert(result.end(), details::NEURON_VARIABLES.begin(), details::NEURON_VARIABLES.end()); - return result; + return details::NEURON_VARIABLES; } @@ -388,13 +329,13 @@ std::vector get_external_variables() { */ std::vector get_external_functions() { std::vector result; - result.reserve(details::methods.size()); + result.reserve(details::methods.size() + details::extern_definitions.size()); for (auto& method: details::methods) { result.push_back(method.first); } - for (auto& definition: details::extern_definitions) { - result.push_back(definition.first); - } + result.insert(result.cend(), + details::extern_definitions.begin(), + details::extern_definitions.end()); return result; } From 88225671d0a47318d894645d9861c71ffba32d56 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Wed, 31 Aug 2022 13:10:09 +0200 Subject: [PATCH 456/871] Remove internal `ml` argument and move GPU code to Acc visitor (BlueBrain/nmodl#919) NMODL Repo SHA: BlueBrain/nmodl@60249f1f795b84a20fb0e9732374bdfec1f878e5 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 42 ++++++++++++++--- src/nmodl/codegen/codegen_acc_visitor.hpp | 15 ++++--- src/nmodl/codegen/codegen_c_visitor.cpp | 45 +++++-------------- src/nmodl/codegen/codegen_c_visitor.hpp | 34 +++++++++++--- .../unit/codegen/codegen_c_visitor.cpp | 3 -- 5 files changed, 83 insertions(+), 56 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index dc00dd48a7..0c8838475c 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -265,11 +265,26 @@ void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { } -void CodegenAccVisitor::print_instance_variable_transfer_to_device( - std::vector const& ptr_members) const { +void CodegenAccVisitor::print_instance_struct_transfer_routine_declarations() { if (info.artificial_cell) { return; } + printer->fmt_line( + "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst);", + instance_struct()); + printer->fmt_line("static inline void delete_instance_from_device({}* inst);", + instance_struct()); +} + + +void CodegenAccVisitor::print_instance_struct_transfer_routines( + std::vector const& ptr_members) { + if (info.artificial_cell) { + return; + } + printer->fmt_start_block( + "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst)", + instance_struct()); printer->start_block("if (!nt->compute_gpu)"); printer->add_line("return;"); printer->end_block(1); @@ -285,18 +300,33 @@ void CodegenAccVisitor::print_instance_variable_transfer_to_device( printer->add_line("auto* d_ml = cnrn_target_deviceptr(ml);"); printer->add_line("void* d_inst_void = d_inst;"); printer->add_line("cnrn_target_memcpy_to_device(&(d_ml->instance), &d_inst_void);"); + printer->end_block(2); // copy_instance_to_device + + printer->fmt_start_block("static inline void delete_instance_from_device({}* inst)", + instance_struct()); + printer->start_block("if (cnrn_target_is_present(inst))"); + printer->add_line("cnrn_target_delete(inst);"); + printer->end_block(1); + printer->end_block(2); // delete_instance_from_device } -void CodegenAccVisitor::print_instance_variable_deletion_from_device() const { +void CodegenAccVisitor::print_instance_struct_copy_to_device() { if (info.artificial_cell) { return; } - printer->start_block("if (cnrn_target_is_present(&inst))"); - printer->add_line("cnrn_target_delete(&inst);"); - printer->end_block(1); + printer->add_line("copy_instance_to_device(nt, ml, inst);"); +} + + +void CodegenAccVisitor::print_instance_struct_delete_from_device() { + if (info.artificial_cell) { + return; + } + printer->add_line("delete_instance_from_device(inst);"); } + void CodegenAccVisitor::print_deriv_advance_flag_transfer_to_device() const { printer->add_line("nrn_pragma_acc(update device (deriv_advance_flag) if(nt->compute_gpu))"); printer->add_line("nrn_pragma_omp(target update to(deriv_advance_flag) if(nt->compute_gpu))"); diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 350fe9abbb..456ef76c70 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -92,12 +92,17 @@ class CodegenAccVisitor: public CodegenCVisitor { /// transfer newtonspace structure to device void print_newtonspace_transfer_to_device() const override; - /// copy the instance struct to the device - void print_instance_variable_transfer_to_device( - std::vector const& ptr_members) const override; + /// declare helper functions for copying the instance struct to the device + void print_instance_struct_transfer_routine_declarations() override; - /// delete the instance struct from the device - void print_instance_variable_deletion_from_device() const override; + /// define helper functions for copying the instance struct to the device + void print_instance_struct_transfer_routines(std::vector const&) override; + + /// call helper function for copying the instance struct to the device + void print_instance_struct_copy_to_device() override; + + /// call helper function that deletes the instance struct from the device + void print_instance_struct_delete_from_device() override; // update derivimplicit advance flag on the gpu device void print_deriv_advance_flag_transfer_to_device() const override; diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index eb8e16b4ed..98bc738c0a 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1041,17 +1041,6 @@ void CodegenCVisitor::print_channel_iteration_tiling_block_end() { } -void CodegenCVisitor::print_instance_variable_transfer_to_device( - std::vector const& ptr_members) const { - // backend specific, do nothing -} - - -void CodegenCVisitor::print_instance_variable_deletion_from_device() const { - // backend specific, do nothing -} - - void CodegenCVisitor::print_deriv_advance_flag_transfer_to_device() const { // backend specific, do nothing } @@ -1892,9 +1881,9 @@ void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, i std::string CodegenCVisitor::internal_method_arguments() { if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, ml, v"; + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; } - return "id, pnodecount, inst, data, indexes, thread, nt, ml, v"; + return "id, pnodecount, inst, data, indexes, thread, nt, v"; } @@ -1926,7 +1915,6 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { params.emplace_back("const ", "Datum*", "", "indexes"); params.emplace_back(param_type_qualifier(), "ThreadDatum*", "", "thread"); params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); - params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); params.emplace_back("", "double", "", "v"); return params; } @@ -1961,9 +1949,9 @@ std::string CodegenCVisitor::nrn_thread_arguments() { */ std::string CodegenCVisitor::nrn_thread_internal_arguments() { if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, ml, v"; + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; } - return "id, pnodecount, inst, data, indexes, thread, nt, ml, v"; + return "id, pnodecount, inst, data, indexes, thread, nt, v"; } @@ -3200,18 +3188,15 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->fmt_line("assert(ml->global_variables_size == sizeof({}));", global_struct()); }; - printer->fmt_line( - "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst);", - instance_struct()); - printer->fmt_line("static inline void delete_instance_from_device({}& inst);", - instance_struct()); - printer->add_newline(); + // Must come before print_instance_struct_copy_to_device and + // print_instance_struct_delete_from_device + print_instance_struct_transfer_routine_declarations(); printer->add_line("// Deallocate the instance structure"); printer->fmt_start_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); cast_inst_and_assert_validity(); - printer->add_line("delete_instance_from_device(*inst);"); + print_instance_struct_delete_from_device(); printer->add_line("delete inst;"); printer->add_line("ml->instance = nullptr;"); printer->add_line("ml->global_variables = nullptr;"); @@ -3269,20 +3254,10 @@ void CodegenCVisitor::print_instance_variable_setup() { printer->fmt_line("inst->{} = {};", name, variable); ptr_members.push_back(std::move(name)); } - printer->add_line("copy_instance_to_device(nt, ml, inst);"); + print_instance_struct_copy_to_device(); printer->end_block(2); // setup_instance - printer->add_line("// Set up the device-side copy of the instance structure"); - printer->fmt_start_block( - "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst)", - instance_struct()); - print_instance_variable_transfer_to_device(ptr_members); - printer->end_block(2); // copy_instance_to_device - - printer->fmt_start_block("static inline void delete_instance_from_device({}& inst)", - instance_struct()); - print_instance_variable_deletion_from_device(); - printer->end_block(2); // delete_instance_from_device + print_instance_struct_transfer_routines(ptr_members); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 9b19da396e..7bb324e505 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1080,19 +1080,39 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** - * Print the code to copy instance struct members to the device, - * substituting host pointers for device ones. + * Print declarations of the functions used by \ref + * print_instance_struct_copy_to_device and \ref + * print_instance_struct_delete_from_device. + */ + virtual void print_instance_struct_transfer_routine_declarations() {} + + /** + * Print the definitions of the functions used by \ref + * print_instance_struct_copy_to_device and \ref + * print_instance_struct_delete_from_device. Declarations of these functions + * are printed by \ref print_instance_struct_transfer_routine_declarations. + * + * This updates the (pointer) member variables in the device copy of the + * instance struct to contain device pointers, which is why you must pass a + * list of names of those member variables. * - * \param ptr_members Members to update. + * \param ptr_members List of instance struct member names. */ - virtual void print_instance_variable_transfer_to_device( - std::vector const& ptr_members) const; + virtual void print_instance_struct_transfer_routines( + std::vector const& /* ptr_members */) {} /** - * Print the code to delete the instance structure from the device. + * Transfer the instance struct to the device. This calls a function + * declared by \ref print_instance_struct_transfer_routine_declarations. + */ + virtual void print_instance_struct_copy_to_device() {} + + /** + * Delete the instance struct from the device. This calls a function + * declared by \ref print_instance_struct_transfer_routine_declarations. */ - virtual void print_instance_variable_deletion_from_device() const; + virtual void print_instance_struct_delete_from_device() {} /** diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index 64fae2b914..66c1c14c80 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -102,7 +102,6 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_cao = nt->_data; inst->ion_ica = nt->_data; inst->ion_dicadv = nt->_data; - copy_instance_to_device(nt, ml, inst); } )"; auto const expected = reindent_text(generated_code); @@ -151,7 +150,6 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->v_unused = ml->data+4*pnodecount; inst->ion_cai = nt->_data; inst->ion_cao = nt->_data; - copy_instance_to_device(nt, ml, inst); } )"; @@ -231,7 +229,6 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_ilca = nt->_data; inst->ion_elca = nt->_data; inst->style_lca = ml->pdata; - copy_instance_to_device(nt, ml, inst); } )"; From 6233336d6f525bcdf9f321de9db044cfc94ecef0 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 5 Sep 2022 11:07:22 +0200 Subject: [PATCH 457/871] Throwing instead of an assert if we cannot find the block to solve (BlueBrain/nmodl#922) Otherwise it only segmentation fault in Release NMODL Repo SHA: BlueBrain/nmodl@4984c3112198bb7019f3de3c6a6b53c01dbe1fca --- src/nmodl/visitors/solve_block_visitor.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 0de5a1ecaf..d7ce65d9a2 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -8,6 +8,7 @@ #include "visitors/solve_block_visitor.hpp" #include +#include #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" @@ -42,7 +43,10 @@ ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( /// find out the block that is going to solved const auto& block_name = solve_block.get_block_name()->get_node_name(); const auto& solve_node_symbol = symtab->lookup(block_name); - assert(solve_node_symbol != nullptr); + if (solve_node_symbol == nullptr) { + throw std::runtime_error( + fmt::format("SolveBlockVisitor :: cannot find the block '{}' to solve it", block_name)); + } auto node_to_solve = solve_node_symbol->get_node(); /// in case of derivimplicit method if neuron solver is used (i.e. not sympy) then From 2f894c3cbf46002a058a4d68d8b8797f22876b16 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 7 Sep 2022 13:57:03 +0200 Subject: [PATCH 458/871] Code generation fixes for array variables in TABLE statement (BlueBrain/nmodl#924) * Table statements can have array variables. Until now only scalar variables were supported in code generation. * We can check symbol table to find out if the variable is an array and it's length. * Similar to mod2c implementation, generate code for array variable assignments: https://github.com/BlueBrain/mod2c/blob/469c74dc7d96bbc5a06a42696422154b4cd2ce28/src/mod2c_core/parsact.c#L942 * with this, `glia__dbbs_mod_collection__Cav2_3__0.mod` from BlueBrain/nmodl#888 compiles * Add test and fix the bug for array variables allocation and access NMODL Repo SHA: BlueBrain/nmodl@6a59caae7feb1cfaf9250b7fe417f18425f9b98d --- src/nmodl/codegen/codegen_c_visitor.cpp | 80 ++++++++++++++++--- src/nmodl/codegen/codegen_c_visitor.hpp | 6 ++ .../unit/codegen/codegen_c_visitor.cpp | 72 +++++++++++++++++ 3 files changed, 148 insertions(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 98bc738c0a..7063e4f7f3 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1459,6 +1459,20 @@ static const TableStatement* get_table_statement(const ast::Block& node) { } +std::tuple CodegenCVisitor::check_if_var_is_array(const std::string& name) { + auto symbol = program_symtab->lookup_in_scope(name); + if (!symbol) { + throw std::runtime_error( + fmt::format("CodegenCVisitor:: {} not found in symbol table!", name)); + } + if (symbol->is_array()) { + return {true, symbol->get_length()}; + } else { + return {false, 0}; + } +} + + void CodegenCVisitor::print_table_check_function(const Block& node) { auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); @@ -1525,7 +1539,15 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); - printer->fmt_line("{}[i] = {};", table_name, instance_name); + auto [is_array, array_length] = check_if_var_is_array(name); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line( + "{}[{}][i] = {}[{}];", table_name, j, instance_name, j); + } + } else { + printer->fmt_line("{}[i] = {};", table_name, instance_name); + } } } else { auto table_name = get_variable_name("t_" + name); @@ -1587,7 +1609,14 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { if (node.is_procedure_block()) { for (const auto& var: table_variables) { auto name = get_variable_name(var->get_node_name()); - printer->fmt_line("{} = xi;", name); + auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line("{}[{}] = xi;", name, j); + } + } else { + printer->fmt_line("{} = xi;", name); + } } printer->add_line("return 0;"); } else { @@ -1602,7 +1631,15 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { auto name = variable->get_node_name(); auto instance_name = get_variable_name(name); auto table_name = get_variable_name("t_" + name); - printer->fmt_line("{} = {}[index];", instance_name, table_name); + auto [is_array, array_length] = check_if_var_is_array(name); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line( + "{}[{}] = {}[{}][index];", instance_name, j, table_name, j); + } + } else { + printer->fmt_line("{} = {}[index];", instance_name, table_name); + } } printer->add_line("return 0;"); } else { @@ -1615,11 +1652,23 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { printer->add_line("double theta = xi - double(i);"); if (node.is_procedure_block()) { for (const auto& var: table_variables) { - auto instance_name = get_variable_name(var->get_node_name()); - auto table_name = get_variable_name("t_" + var->get_node_name()); - printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", - instance_name, - table_name); + auto name = var->get_node_name(); + auto instance_name = get_variable_name(name); + auto table_name = get_variable_name("t_" + name); + auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); + if (is_array) { + for (size_t j = 0; j < array_length; j++) { + printer->fmt_line( + "{0}[{1}] = {2}[{1}][i] + theta*({2}[{1}][i+1]-{2}[{1}][i]);", + instance_name, + j, + table_name); + } + } else { + printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", + instance_name, + table_name); + } } printer->add_line("return 0;"); } else { @@ -2630,8 +2679,19 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool print_initialise for (const auto& variable: info.table_statement_variables) { auto const name = "t_" + variable->get_name(); auto const num_values = variable->get_num_values(); - printer->fmt_line( - "{}{} {}[{}]{};", qualifier, float_type, name, num_values, value_initialise); + if (variable->is_array()) { + int array_len = variable->get_length(); + printer->fmt_line("{}{} {}[{}][{}]{};", + qualifier, + float_type, + name, + array_len, + num_values, + value_initialise); + } else { + printer->fmt_line( + "{}{} {}[{}]{};", qualifier, float_type, name, num_values, value_initialise); + } codegen_global_variables.push_back(make_symbol(name)); } } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 7bb324e505..3a4fc39c13 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1025,6 +1025,12 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual bool is_constant_variable(const std::string& name) const; + /** + * Check if the given name exist in the symbol + * \return \c return a tuple if variable + * is an array otherwise + */ + std::tuple check_if_var_is_array(const std::string& name); /** * Print declaration of macro NRN_PRCELLSTATE for debugging diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index 66c1c14c80..b38f79f424 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -13,7 +13,11 @@ #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/implicit_argument_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" #include "visitors/perf_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" +#include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 @@ -32,6 +36,11 @@ std::shared_ptr create_c_visitor(const std::shared_ptr("temp.mod", ss, "double", false); cv->setup(*ast); @@ -47,6 +56,15 @@ std::string get_instance_var_setup_function(std::string& nmodl_text) { return reindent_text(ss.str()); } +/// print entire code +std::string get_cpp_code(const std::string& nmodl_text) { + const auto& ast = NmodlDriver().parse_string(nmodl_text); + std::stringstream ss; + auto cvisitor = create_c_visitor(ast, nmodl_text, ss); + cvisitor->visit_program(*ast); + return reindent_text(ss.str()); +} + SCENARIO("Check instance variable definition order", "[codegen][var_order]") { GIVEN("cal_mig.mod: USEION variables declared as RANGE") { // In the below mod file, the ion variables cai and cao are also @@ -345,3 +363,57 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", } } } + +SCENARIO("Check code generation for TABLE statements", "[codegen][array_variables]") { + GIVEN("A MOD file that uses global and array variables in TABLE") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX glia_Cav2_3 + RANGE inf + GLOBAL tau + } + + STATE { m } + + PARAMETER { + tau = 1 + } + + ASSIGNED { + inf[2] + } + + BREAKPOINT { + SOLVE states METHOD cnexp + } + + DERIVATIVE states { + mhn(v) + m' = (inf[0] - m)/tau + } + + PROCEDURE mhn(v (mV)) { + TABLE inf, tau DEPEND celsius FROM -100 TO 100 WITH 200 + FROM i=0 TO 1 { + inf[i] = v + tau + } + } + )"; + THEN("Array and global variables should be correctly generated") { + auto const generated = get_cpp_code(nmodl_text); + REQUIRE_THAT(generated, Contains("double t_inf[2][201]{};")); + REQUIRE_THAT(generated, Contains("double t_tau[201]{};")); + + REQUIRE_THAT(generated, Contains("inst->global->t_inf[0][i] = (inst->inf+id*2)[0];")); + REQUIRE_THAT(generated, Contains("inst->global->t_inf[1][i] = (inst->inf+id*2)[1];")); + REQUIRE_THAT(generated, Contains("inst->global->t_tau[i] = inst->global->tau;")); + + REQUIRE_THAT(generated, + Contains("(inst->inf+id*2)[0] = inst->global->t_inf[0][index];")); + + REQUIRE_THAT(generated, Contains("(inst->inf+id*2)[0] = inst->global->t_inf[0][i]")); + REQUIRE_THAT(generated, Contains("(inst->inf+id*2)[1] = inst->global->t_inf[1][i]")); + REQUIRE_THAT(generated, Contains("inst->global->tau = inst->global->t_tau[i]")); + } + } +} From bf378ef1303d605acd456d637f1f9fe393a0b57f Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 7 Sep 2022 15:19:22 +0200 Subject: [PATCH 459/871] Codegen fixes for Eigen solvers with use of PROCEDURE using TABLE statement (BlueBrain/nmodl#925) * When we have TABLE statement used in a PROCEDURE and if such procedure is called from DERIVATIVE block then we end-up calling table statement related function from initialize() function: ```c++ struct functor { NrnThread* nt; glia__dbbs_mod_collection__Ca__granule_cell_Instance* inst; int id, pnodecount; double v; Datum* indexes; double old_s, old_u; void initialize() { rate_glia__dbbs_mod_collection__Ca__granule_cell(id, pnodecount, inst, data, indexes, thread, nt, v, v); ... ``` Here we are lacking `data` and `thread` variable as members in `struct functor`. * This happens with the `glia__dbbs_mod_collection__Ca__granule_cell.mod` in BlueBrain/nmodl#888 NMODL Repo SHA: BlueBrain/nmodl@c7813f8694161309179bbd49ae133c4ea487a268 --- src/nmodl/codegen/codegen_c_visitor.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 7063e4f7f3..b7828f0185 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1833,6 +1833,9 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->add_line("int id, pnodecount;"); printer->add_line("double v;"); printer->add_line("Datum* indexes;"); + printer->add_line("double* data;"); + printer->add_line("ThreadDatum* thread;"); + if (ion_variable_struct_required()) { print_ion_variable(); } @@ -1845,8 +1848,10 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->end_block(2); printer->fmt_line( - "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes) : " - "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}} " + "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes, " + "double* data, ThreadDatum* thread) : " + "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}}, " + "data{{data}}, thread{{thread}} " "{{}}", instance_struct()); @@ -1878,7 +1883,8 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv // call newton solver with functor and X matrix that contains state vars printer->add_line("// call newton solver"); - printer->add_line("functor newton_functor(nt, inst, id, pnodecount, v, indexes);"); + printer->add_line( + "functor newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);"); printer->add_line("newton_functor.initialize();"); printer->add_line( "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); From 3e27a1a09c01d64e5a98da31df35f8f22036d8a3 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 9 Sep 2022 12:36:19 +0200 Subject: [PATCH 460/871] Remove unused queue statement PUTQ/GETQ (BlueBrain/nmodl#907) * Modify tests since index are shifted NMODL Repo SHA: BlueBrain/nmodl@34fdcefb18a592ca8ffa2f3e224669d43a8a885a --- docs/nmodl/transpiler/test_status.txt | 2 -- src/nmodl/ast/ast_common.hpp | 6 ------ src/nmodl/language/code_generator.cmake | 2 -- src/nmodl/language/nmodl.yaml | 17 ----------------- src/nmodl/language/node_info.py | 3 --- src/nmodl/lexer/nmodl_utils.cpp | 4 ---- src/nmodl/lexer/token_mapping.cpp | 2 -- src/nmodl/parser/nmodl.yy | 18 ------------------ .../transpiler/unit/modtoken/modtoken.cpp | 6 +++--- test/nmodl/transpiler/unit/parser/parser.cpp | 2 +- .../transpiler/unit/utils/nmodl_constructs.cpp | 13 ------------- 11 files changed, 4 insertions(+), 71 deletions(-) diff --git a/docs/nmodl/transpiler/test_status.txt b/docs/nmodl/transpiler/test_status.txt index 78ff56381c..28a5c9840e 100644 --- a/docs/nmodl/transpiler/test_status.txt +++ b/docs/nmodl/transpiler/test_status.txt @@ -50,8 +50,6 @@ The following symbols are used in the document to descrive the status : |LOCAL | ✅ | | ~ | ✅ | |~+ | ❓ | -|PUTQ | ✅ | -|GETQ | ✅ | |TABLE | ✅ | |DEPEND | ✅ | |BREAKPOINT | ✅ | diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index f4b176e0cd..c2d3ffc7fa 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -82,12 +82,6 @@ typedef enum { PEQ_FIRST, PEQ_LAST } FirstLastType; /// string representation of ast::FirstLastType static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; -/// enum type for queue types -typedef enum { PUT_QUEUE, GET_QUEUE } QueueType; - -/// string representation of ast::QueueType -static const std::string QueueTypeNames[] = {"PUTQ", "GETQ"}; - /// enum type to distinguish BEFORE or AFTER blocks typedef enum { BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP } BAType; diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 783acd8d5b..36d10117e4 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -150,8 +150,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/procedure_block.hpp ${PROJECT_BINARY_DIR}/src/ast/program.hpp ${PROJECT_BINARY_DIR}/src/ast/protect_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/queue_expression_type.hpp - ${PROJECT_BINARY_DIR}/src/ast/queue_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/range.hpp ${PROJECT_BINARY_DIR}/src/ast/range_var.hpp ${PROJECT_BINARY_DIR}/src/ast/react_var_name.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 0653449cf7..9f94ada917 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1296,12 +1296,6 @@ type: Expression prefix: {value: " "} - - QueueExpressionType: - brief: "TODO" - members: - - value: - brief: "TODO" - type: QueueType - Match: brief: "TODO" members: @@ -1892,17 +1886,6 @@ } \endcode - - QueueStatement: - members: - - qtype: - brief: "queue type (put/get)" - type: QueueExpressionType - - name: - brief: "Name of the variable" - type: Identifier - prefix: {value: " "} - brief: "Represent queue statement in NMODL" - - ConstantStatement: members: - constant: diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 7dbccc2e6c..821d263287 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -26,7 +26,6 @@ "UnaryOp", "ReactionOp", "FirstLastType", - "QueueType", "BAType", "UnitStateType", } @@ -38,7 +37,6 @@ "UnaryOp", "ReactionOp", "FirstLastType", - "QueueType", "BAType", "UnitStateType", } @@ -54,7 +52,6 @@ "ReactionOperator": "ReactionOp", "UnitState": "UnitStateType", "BABlockType": "BAType", - "QueueExpressionType": "QueueType", "FirstLastTypeIndex": "FirstLastType", } diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 238476d767..e35c057513 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -195,8 +195,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_FUNCTION1(token, pos); case Token::FUNCTION_TABLE: return Parser::make_FUNCTION_TABLE(token, pos); - case Token::GETQ: - return Parser::make_GETQ(token, pos); case Token::GLOBAL: return Parser::make_GLOBAL(token, pos); case Token::IF: @@ -251,8 +249,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_PROCEDURE(token, pos); case Token::PROTECT: return Parser::make_PROTECT(token, pos); - case Token::PUTQ: - return Parser::make_PUTQ(token, pos); case Token::RANGE: return Parser::make_RANGE(token, pos); case Token::READ: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 5bfc9299b5..6a1ce6b7bb 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -87,8 +87,6 @@ const static std::map keywords = { {"LAST", Token::LAST}, {"COMPARTMENT", Token::COMPARTMENT}, {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, - {"PUTQ", Token::PUTQ}, - {"GETQ", Token::GETQ}, {"IFERROR", Token::IFERROR}, {"SOLVEFOR", Token::SOLVEFOR}, {"UNITS", Token::UNITS}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 5867f396d2..8b14f93e47 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -106,7 +106,6 @@ %token FROM %token FUNCTION1 %token FUNCTION_TABLE -%token GETQ %token GLOBAL %token IF %token IFERROR @@ -136,7 +135,6 @@ %token POINTER %token PROCEDURE %token PROTECT -%token PUTQ %token RANGE %token REACT1 %token REACTION @@ -259,7 +257,6 @@ %type local_var_list %type expression_list %type define -%type queue_statement %type assignment %type from_statement %type while_statement @@ -1150,10 +1147,6 @@ statement_type1 : from_statement { $$ = $1; } - | queue_statement - { - $$ = $1; - } | RESET { $$ = new ast::Reset(); @@ -2064,17 +2057,6 @@ lag_statement : LAG name BY NAME_PTR ; -queue_statement : PUTQ name - { - $$ = new ast::QueueStatement(new ast::QueueExpressionType(ast::PUT_QUEUE), $2); - } - | GETQ name - { - $$ = new ast::QueueStatement(new ast::QueueExpressionType(ast::GET_QUEUE), $2); - } - ; - - match_block : MATCH "{" match_list "}" { $$ = new ast::MatchBlock($3); diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 4e45103cbf..e961300a73 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -49,14 +49,14 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 357"); + REQUIRE(ss.str() == " text at [1.1-4] type 355"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 357"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 355"); } } @@ -66,7 +66,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 364"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 362"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 38881ac991..9b023ec978 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -267,6 +267,6 @@ SCENARIO("Check if a NEURON block is parsed with correct location info in its to )"; parse_neuron_block_string(reindent_text(neuron_block), value); ss << value; - REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 303"); + REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 302"); } } diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 5ada3fd366..c24cd5d249 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -1016,19 +1016,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "queue_statement_1", - { - "PUTQ and GETQ statement", - R"( - PROCEDURE lates() { - PUTQ one_name - GETQ another_name - } - )" - } - }, - { "reset_statement_1", { From 0c4aecefa1d65ba0ac73af64ed9dc4868afb9cf2 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 9 Sep 2022 13:32:21 +0200 Subject: [PATCH 461/871] Semantic analysis check for USEION variables in CONSTANT block (BlueBrain/nmodl#908) * Similar to https://github.com/neuronsimulator/nrn/pull/1955, add semantic analysis check to avoid declaring ion variables in CONSTANT {} block * Addresses first point in https://github.com/BlueBrain/nmodl/issues/888 MOD file is glia__dbbs_mod_collection__cdp5__CAM_GoC.mod. * Updated test NMODL Repo SHA: BlueBrain/nmodl@af7c7727a43e5cc76a621f4802ca4755832b15b0 --- src/nmodl/main.cpp | 12 +++++----- .../visitors/semantic_analysis_visitor.cpp | 23 +++++++++++++++++++ .../visitors/semantic_analysis_visitor.hpp | 5 ++-- .../unit/visitor/semantic_analysis.cpp | 17 ++++++++++++++ 4 files changed, 49 insertions(+), 8 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 5d32b51898..1e2a43871f 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -329,6 +329,12 @@ int main(int argc, const char* argv[]) { /// just visit the ast AstVisitor().visit_program(*ast); + /// construct symbol table + { + logger->info("Running symtab visitor"); + SymtabVisitor(update_symtab).visit_program(*ast); + } + /// Check some rules that ast should follow { logger->info("Running semantic analysis visitor"); @@ -337,12 +343,6 @@ int main(int argc, const char* argv[]) { } } - /// construct symbol table - { - logger->info("Running symtab visitor"); - SymtabVisitor(update_symtab).visit_program(*ast); - } - /// use cnexp instead of after_cvode solve method { logger->info("Running CVode to cnexp visitor"); diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 618200edc3..602b2eb502 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -4,6 +4,7 @@ #include "ast/program.hpp" #include "ast/suffix.hpp" #include "ast/table_statement.hpp" +#include "symtab/symbol_properties.hpp" #include "utils/logger.hpp" #include "visitors/visitor_utils.hpp" @@ -22,6 +23,28 @@ bool SemanticAnalysisVisitor::check(const ast::Program& node) { } /// --> + /// <-- This code is for check 4 + using namespace symtab::syminfo; + const auto& with_prop = NmodlType::read_ion_var | NmodlType::write_ion_var; + + const auto& sym_table = node.get_symbol_table(); + assert(sym_table != nullptr); + + // get all ion variables + const auto& ion_variables = sym_table->get_variables_with_properties(with_prop, false); + + /// make sure ion variables aren't redefined in a `CONSTANT` block. + for (const auto& var: ion_variables) { + if (var->has_any_property(NmodlType::constant_var)) { + logger->critical( + fmt::format("SemanticAnalysisVisitor :: ion variable {} from the USEION statement " + "can not be re-declared in a CONSTANT block", + var->get_name())); + check_fail = true; + } + } + /// --> + visit_program(node); return check_fail; } diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 7d1bbb90e2..35fef8d44b 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -25,8 +25,9 @@ * * 1. Check that a function or a procedure containing a TABLE statement contains only one argument * (mandatory in mod2c). - * 2. Check that destructor blocks are only inside mod file that are point_process - * 3. A TABLE statement in functions cannot have name list, and should have one in procedures + * 2. Check that destructor blocks are only inside mod file that are point_process. + * 3. A TABLE statement in functions cannot have name list, and should have one in procedures. + * 4. Check if ion variables from a `USEION` statement are not declared in `CONSTANT` block. */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 710cf35224..2e2314c1de 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -11,6 +11,7 @@ #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/semantic_analysis_visitor.hpp" +#include "visitors/symtab_visitor.hpp" using namespace nmodl; @@ -26,6 +27,7 @@ using nmodl::parser::NmodlDriver; bool run_semantic_analysis_visitor(const std::string& text) { NmodlDriver driver; const auto& ast = driver.parse_string(text); + SymtabVisitor().visit_program(*ast); return SemanticAnalysisVisitor{}.check(*ast); } @@ -105,3 +107,18 @@ SCENARIO("Destructor block", "[visitor][semantic_analysis]") { } } } + +SCENARIO("Ion variable in CONSTANT block", "[visitor][semantic_analysis]") { + GIVEN("A mod file with ion variable redeclared in a CONSTANT block") { + std::string nmodl_text = R"( + NEURON { + SUFFIX cdp4Nsp + USEION ca READ cao, cai, ica WRITE cai + } + CONSTANT { cao = 2 (mM) } + )"; + THEN("Semantic analysis fails") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From aff3828171bdd36a54c2c93a75417f3253685e05 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 13 Sep 2022 05:23:43 +0200 Subject: [PATCH 462/871] Fix duplicate declaration of conductance variable g (BlueBrain/nmodl#929) * when user defined g as a range variable in a non-threadsafe mod file, then instance structure was declaring g as a member variable twice * now, check if variable is already defined before adding new declaration * this is realted to BlueBrain/nmodl#888 with mod file glia__dbbs_mod_collection__Nav1_1__0.mod * print v_unused in case mod file is threadsafe NMODL Repo SHA: BlueBrain/nmodl@ea1acbd4ab8f10f1365ac9a7be582fc6b9b1f67c --- src/nmodl/codegen/codegen_c_visitor.cpp | 14 +++++++++- .../unit/codegen/codegen_c_visitor.cpp | 28 +++++++++++-------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index b7828f0185..4e800013a4 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -857,11 +857,20 @@ std::vector CodegenCVisitor::get_float_variables() { if (info.vectorize) { variables.push_back(make_symbol(naming::VOLTAGE_UNUSED_VARIABLE)); } + if (breakpoint_exist()) { std::string name = info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE : naming::CONDUCTANCE_VARIABLE; - variables.push_back(make_symbol(name)); + + // make sure conductance variable like `g` is not already defined + if (auto r = std::find_if(variables.cbegin(), + variables.cend(), + [&](const auto& s) { return name == s->get_name(); }); + r == variables.cend()) { + variables.push_back(make_symbol(name)); + } } + if (net_receive_exist()) { variables.push_back(make_symbol(naming::T_SAVE_VARIABLE)); } @@ -4582,6 +4591,9 @@ void CodegenCVisitor::print_data_structures(bool print_initialisers) { } void CodegenCVisitor::print_v_unused() const { + if (!info.vectorize) { + return; + } printer->add_line("#if NRN_PRCELLSTATE"); printer->add_line("inst->v_unused[id] = v;"); printer->add_line("#endif"); diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index b38f79f424..27565fb35f 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -180,13 +180,16 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { // In the below mod file, ion variables ncai and lcai are declared // as state variables as well as range variables. The issue about // this mod file ordering was fixed in #443. + // We also use example from #888 where mod file declared `g` as a + // conductance variable in a non-threadsafe mod file and resulting + // into duplicate definition of `g` in the instance structure. GIVEN("ccanl.mod: mod file from reduced_dentate model") { std::string nmodl_text = R"( NEURON { SUFFIX ccanl USEION nca READ ncai, inca, enca WRITE enca, ncai VALENCE 2 USEION lca READ lcai, ilca, elca WRITE elca, lcai VALENCE 2 - RANGE caiinf, catau, cai, ncai, lcai, eca, elca, enca + RANGE caiinf, catau, cai, ncai, lcai, eca, elca, enca, g } UNITS { FARADAY = 96520(coul) @@ -207,11 +210,14 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { enca(mV) elca(mV) eca(mV) + g(S/cm2) } STATE { ncai(mM) lcai(mM) } + BREAKPOINT {} + DISCRETE seq {} )"; THEN("Ion variables are defined in the order of USEION") { @@ -229,16 +235,16 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->caiinf = ml->data+1*pnodecount; inst->cai = ml->data+2*pnodecount; inst->eca = ml->data+3*pnodecount; - inst->ica = ml->data+4*pnodecount; - inst->inca = ml->data+5*pnodecount; - inst->ilca = ml->data+6*pnodecount; - inst->enca = ml->data+7*pnodecount; - inst->elca = ml->data+8*pnodecount; - inst->ncai = ml->data+9*pnodecount; - inst->Dncai = ml->data+10*pnodecount; - inst->lcai = ml->data+11*pnodecount; - inst->Dlcai = ml->data+12*pnodecount; - inst->v_unused = ml->data+13*pnodecount; + inst->g = ml->data+4*pnodecount; + inst->ica = ml->data+5*pnodecount; + inst->inca = ml->data+6*pnodecount; + inst->ilca = ml->data+7*pnodecount; + inst->enca = ml->data+8*pnodecount; + inst->elca = ml->data+9*pnodecount; + inst->ncai = ml->data+10*pnodecount; + inst->Dncai = ml->data+11*pnodecount; + inst->lcai = ml->data+12*pnodecount; + inst->Dlcai = ml->data+13*pnodecount; inst->ion_ncai = nt->_data; inst->ion_inca = nt->_data; inst->ion_enca = nt->_data; From b49f8809ceba859bb93ab5d5a9db65719b2fc5fa Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 26 Sep 2022 13:25:14 +0200 Subject: [PATCH 463/871] Store all associated nodes of a symbol in the symbol table (BlueBrain/nmodl#934) * Until now only first AST node for a given symbol was stored in the AST * Because of this, it was not possible to find/lookup all nodes associated with a given symbol * This PR now changes the symbol implementation to store all encountered AST nodes. * Add tests for symbol class NMODL Repo SHA: BlueBrain/nmodl@68e7086bfbda78993dbfc8ddbe7415cf96ce6e86 --- .../language/templates/pybind/pysymtab.cpp | 3 +- src/nmodl/symtab/symbol.cpp | 15 +++ src/nmodl/symtab/symbol.hpp | 35 +++--- src/nmodl/symtab/symbol_table.cpp | 30 +++--- src/nmodl/visitors/inline_visitor.cpp | 21 ++-- src/nmodl/visitors/solve_block_visitor.cpp | 2 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 6 +- .../transpiler/unit/symtab/symbol_table.cpp | 100 ++++++++++++++++-- 8 files changed, 159 insertions(+), 53 deletions(-) diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index 104e132203..a3d4e9b9dc 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -200,7 +200,8 @@ void init_symtab_module(py::module& m) { .def("get_id", &Symbol::get_id) .def("get_status", &Symbol::get_status) .def("get_properties", &Symbol::get_properties) - .def("get_node", &Symbol::get_node) + .def("get_node", [](const std::shared_ptr& s){ auto n = s->get_nodes(); return n.empty() ? nullptr : n.front(); }) + .def("get_nodes", &Symbol::get_nodes) .def("get_original_name", &Symbol::get_original_name) .def("get_name", &Symbol::get_name) .def("has_any_property", &Symbol::has_any_property) diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index edd9688c5a..852862fd89 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -7,6 +7,7 @@ #include "symtab/symbol.hpp" #include "utils/logger.hpp" +#include namespace nmodl { namespace symtab { @@ -49,5 +50,19 @@ std::string Symbol::to_string() const { return s; } +std::vector Symbol::get_nodes_by_type( + std::initializer_list l) const noexcept { + std::vector _nodes; + for (const auto& n: nodes) { + for (const auto& m: l) { + if (n->get_node_type() == m) { + _nodes.push_back(n); + break; + } + } + } + return _nodes; +} + } // namespace symtab } // namespace nmodl diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index e6c8dddfb1..3e7bf67afb 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -15,6 +15,7 @@ #include #include +#include "ast/ast_decl.hpp" #include "lexer/modtoken.hpp" #include "symtab/symbol_properties.hpp" @@ -61,14 +62,12 @@ class Symbol { /// unique id or index position when symbol is inserted into specific table int id = 0; - /// first AST node for which symbol is inserted - /// Variable can appear multiple times in the mod file. This node - /// represent the first occurance of the variable in the input. Currently - /// we don't track all AST nodes. - ast::Ast* node = nullptr; + /// All given AST nodes for this symbol. + /// Variable can appear multiple times in the mod file. + std::vector nodes{}; /// token associated with symbol (from node) - ModToken token; + ModToken token{}; /// properties of symbol as a result of usage across whole mod file syminfo::NmodlType properties{syminfo::NmodlType::empty}; @@ -112,9 +111,13 @@ class Symbol { Symbol() = delete; + Symbol(std::string name) + : name(std::move(name)) {} + Symbol(std::string name, ast::Ast* node) - : name(std::move(name)) - , node(node) {} + : name(std::move(name)) { + nodes.push_back(node); + } Symbol(std::string name, ModToken token) : name(std::move(name)) @@ -122,8 +125,9 @@ class Symbol { Symbol(std::string name, ast::Ast* node, ModToken token) : name(std::move(name)) - , node(node) - , token(std::move(token)) {} + , token(std::move(token)) { + nodes.push_back(node); + } /// \} @@ -236,10 +240,17 @@ class Symbol { return status; } - ast::Ast* get_node() const noexcept { - return node; + void add_node(ast::Ast* node) noexcept { + nodes.push_back(node); + } + + std::vector get_nodes() const noexcept { + return nodes; } + std::vector get_nodes_by_type( + std::initializer_list l) const noexcept; + ModToken get_token() const noexcept { return token; } diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 2a3f7a8a2b..6d3e02f49c 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -8,6 +8,7 @@ #include #include "ast/ast.hpp" +#include "ast/ast_decl.hpp" #include "symtab/symbol_table.hpp" #include "utils/logger.hpp" #include "utils/table_data.hpp" @@ -54,15 +55,12 @@ SymbolTable::SymbolTable(const SymbolTable& table) bool SymbolTable::is_method_defined(const std::string& name) const { auto symbol = lookup_in_scope(name); - if (symbol != nullptr) { - auto node = symbol->get_node(); - if (node != nullptr) { - if (node->is_procedure_block() || node->is_function_block()) { - return true; - } - } + if (symbol == nullptr) { + return false; } - return false; + auto nodes = symbol->get_nodes_by_type( + {AstNodeType::FUNCTION_BLOCK, AstNodeType::PROCEDURE_BLOCK}); + return !nodes.empty(); } @@ -202,12 +200,13 @@ std::shared_ptr ModelSymbolTable::lookup(const std::string& name) { void ModelSymbolTable::emit_message(const std::shared_ptr& first, const std::shared_ptr& second, bool redefinition) { - auto node = first->get_node(); + auto nodes = first->get_nodes(); std::string name = first->get_name(); auto properties = to_string(second->get_properties()); std::string type = "UNKNOWN"; - if (node != nullptr) { - type = node->get_node_type_name(); + if (!nodes.empty()) { + // Here we take the first one, because this is a redefinition + type = nodes.front()->get_node_type_name(); } if (redefinition) { @@ -318,6 +317,9 @@ std::shared_ptr ModelSymbolTable::insert(const std::shared_ptr& emit_message(symbol, search_symbol, true); } else { search_symbol->add_properties(symbol->get_properties()); + for (const auto& n: symbol->get_nodes()) { + search_symbol->add_node(n); + } } return search_symbol; } @@ -457,8 +459,9 @@ void SymbolTable::Table::print(std::ostream& stream, std::string title, int inde TableData table; table.title = std::move(title); table.headers = { - "NAME", "PROPERTIES", "STATUS", "LOCATION", "VALUE", "# READS", "# WRITES"}; + "NAME", "# NODES", "PROPERTIES", "STATUS", "LOCATION", "VALUE", "# READS", "# WRITES"}; table.alignments = {text_alignment::left, + text_alignment::left, text_alignment::left, text_alignment::right, text_alignment::right, @@ -482,13 +485,14 @@ void SymbolTable::Table::print(std::ostream& stream, std::string title, int inde auto properties = syminfo::to_string(symbol->get_properties()); auto status = syminfo::to_string(symbol->get_status()); auto reads = std::to_string(symbol->get_read_count()); + auto nodes = std::to_string(symbol->get_nodes().size()); std::string value; auto sym_value = symbol->get_value(); if (sym_value) { value = std::to_string(*sym_value); } auto writes = std::to_string(symbol->get_write_count()); - table.rows.push_back({name, properties, status, position, value, reads, writes}); + table.rows.push_back({name, nodes, properties, status, position, value, reads, writes}); } table.print(stream, indent); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index b72c9cbdb9..fb43e86271 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -8,6 +8,7 @@ #include "visitors/inline_visitor.hpp" #include "ast/all.hpp" +#include "ast/ast_decl.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" #include "visitors/local_var_rename_visitor.hpp" @@ -210,25 +211,21 @@ void InlineVisitor::visit_function_call(FunctionCall& node) { return; } - auto function_definition = symbol->get_node(); - if (function_definition == nullptr) { + auto nodes = symbol->get_nodes_by_type( + {AstNodeType::FUNCTION_BLOCK, AstNodeType::PROCEDURE_BLOCK}); + if (nodes.empty()) { throw std::runtime_error("symbol table doesn't have ast node for " + function_name); } + auto f_block = nodes.front(); /// first inline called function - function_definition->visit_children(*this); + f_block->visit_children(*this); bool inlined = false; - if (function_definition->is_procedure_block()) { - auto proc = dynamic_cast(function_definition); - assert(proc); - inlined = inline_function_call(*proc, node, *caller_block); - } else if (function_definition->is_function_block()) { - auto func = dynamic_cast(function_definition); - assert(func); - inlined = inline_function_call(*func, node, *caller_block); - } + auto block = dynamic_cast(f_block); + assert(block); + inlined = inline_function_call(*block, node, *caller_block); if (inlined) { symbol->mark_inlined(); diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index d7ce65d9a2..59e6b92d70 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -47,7 +47,7 @@ ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( throw std::runtime_error( fmt::format("SolveBlockVisitor :: cannot find the block '{}' to solve it", block_name)); } - auto node_to_solve = solve_node_symbol->get_node(); + auto node_to_solve = solve_node_symbol->get_nodes().front(); /// in case of derivimplicit method if neuron solver is used (i.e. not sympy) then /// the solution is not in place but we have to create a callback to newton solver diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 477c8e453b..63c9399a98 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -135,7 +135,7 @@ void SymtabVisitor::setup_symbol(ast::Node* node, NmodlType property) { auto name = use_ion->get_name()->get_node_name(); for (const auto& variable: codegen::Ion::get_possible_variables(name)) { std::string ion_variable(codegen::naming::ION_VARNAME_PREFIX + variable); - auto symbol = std::make_shared(ion_variable, nullptr, ModToken()); + auto symbol = std::make_shared(ion_variable); symbol->add_property(NmodlType::codegen_var); modsymtab->insert(symbol); } @@ -169,13 +169,13 @@ static void add_external_symbols(symtab::ModelSymbolTable* symtab) { ModToken tok(true); auto variables = nmodl::get_external_variables(); for (auto variable: variables) { - auto symbol = std::make_shared(variable, nullptr, tok); + auto symbol = std::make_shared(variable, tok); symbol->add_property(NmodlType::extern_neuron_variable); symtab->insert(symbol); } auto methods = nmodl::get_external_functions(); for (auto method: methods) { - auto symbol = std::make_shared(method, nullptr, tok); + auto symbol = std::make_shared(method, tok); symbol->add_property(NmodlType::extern_method); symtab->insert(symbol); } diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index 1aa8d76164..0524259f79 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -7,11 +7,14 @@ #define CATCH_CONFIG_MAIN +#include #include #include +#include "ast/float.hpp" #include "ast/program.hpp" +#include "ast/string.hpp" #include "symtab/symbol.hpp" #include "symtab/symbol_table.hpp" @@ -165,7 +168,7 @@ SCENARIO("Symbol table allows operations like insert, lookup") { GIVEN("A global SymbolTable") { auto program = std::make_shared(); auto table = std::make_shared("Na", program.get(), true); - auto symbol = std::make_shared("alpha", ModToken()); + auto symbol = std::make_shared("alpha"); WHEN("checked methods and member variables") { THEN("all members are initialized") { @@ -190,7 +193,7 @@ SCENARIO("Symbol table allows operations like insert, lookup") { } } WHEN("inserting another symbol") { - auto next_symbol = std::make_shared("beta", ModToken()); + auto next_symbol = std::make_shared("beta"); table->insert(next_symbol); THEN("symbol gets added and table size increases") { REQUIRE(table->symbol_count() == 2); @@ -204,7 +207,7 @@ SCENARIO("Symbol table allows operations like insert, lookup") { THEN("table doesn't have any global variables") { REQUIRE(variables.empty()); WHEN("added global symbol") { - auto next_symbol = std::make_shared("gamma", ModToken()); + auto next_symbol = std::make_shared("gamma"); next_symbol->add_property(NmodlType::assigned_definition); table->insert(next_symbol); auto variables = table->get_variables_with_properties( @@ -226,10 +229,10 @@ SCENARIO("Symbol table allows operations like insert, lookup") { } } WHEN("query for symbol with and without properties") { - auto symbol1 = std::make_shared("alpha", ModToken()); - auto symbol2 = std::make_shared("beta", ModToken()); - auto symbol3 = std::make_shared("gamma", ModToken()); - auto symbol4 = std::make_shared("delta", ModToken()); + auto symbol1 = std::make_shared("alpha"); + auto symbol2 = std::make_shared("beta"); + auto symbol3 = std::make_shared("gamma"); + auto symbol4 = std::make_shared("delta"); symbol1->add_property(NmodlType::range_var | NmodlType::param_assign); symbol2->add_property(NmodlType::range_var | NmodlType::param_assign | @@ -280,9 +283,9 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { ModelSymbolTable mod_symtab; auto program = std::make_shared(); - auto symbol1 = std::make_shared("alpha", ModToken()); - auto symbol2 = std::make_shared("alpha", ModToken()); - auto symbol3 = std::make_shared("alpha", ModToken()); + auto symbol1 = std::make_shared("alpha"); + auto symbol2 = std::make_shared("alpha"); + auto symbol3 = std::make_shared("alpha"); symbol1->add_property(NmodlType::param_assign); symbol2->add_property(NmodlType::range_var); @@ -303,7 +306,7 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { } WHEN("trying to insert without entering scope") { THEN("throws an exception") { - auto symbol = std::make_shared("alpha", ModToken()); + auto symbol = std::make_shared("alpha"); REQUIRE_THROWS_WITH(mod_symtab.insert(symbol), Catch::Contains("Can not insert")); } } @@ -346,3 +349,78 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { } } } + +//============================================================================= +// Symbol class tests +//============================================================================= + +SCENARIO("Symbol class allows manipulation") { + GIVEN("A symbol can have several nodes") { + auto st = std::make_shared("node1"); + auto fl = std::make_shared("1.1"); + Symbol symbol1("alpha"); + symbol1.add_node(st.get()); + symbol1.add_node(fl.get()); + + Symbol symbol2("beta"); + + WHEN("trying to get name") { + THEN("it works") { + REQUIRE(symbol1.get_name() == "alpha"); + REQUIRE(symbol2.get_name() == "beta"); + } + } + + WHEN("trying to get all nodes") { + THEN("it works") { + REQUIRE(symbol1.get_nodes().size() == 2); + REQUIRE(symbol2.get_nodes().empty()); + } + } + + WHEN("trying to get specific node") { + auto nodes = symbol1.get_nodes_by_type({ast::AstNodeType::STRING}); + + THEN("it works") { + REQUIRE(nodes.size() == 1); + REQUIRE(nodes.front()->is_string()); + REQUIRE(symbol2.get_nodes_by_type({ast::AstNodeType::STRING}).empty()); + } + } + WHEN("read and write counters works") { + symbol1.read(); + symbol1.read(); + symbol1.write(); + + THEN("it works") { + REQUIRE(symbol1.get_read_count() == 2); + REQUIRE(symbol1.get_write_count() == 1); + REQUIRE(symbol2.get_read_count() == 0); + REQUIRE(symbol2.get_write_count() == 0); + } + } + + WHEN("renaming a symbol") { + symbol2.set_name("gamma"); + THEN("get_name return the new name") { + REQUIRE(symbol2.get_name() == "gamma"); + REQUIRE(symbol2.get_original_name() == "beta"); + } + symbol2.set_original_name("gamma"); + THEN("get_original_name return the new name") { + REQUIRE(symbol2.get_original_name() == "gamma"); + } + } + + WHEN("set as array") { + symbol1.set_as_array(15); + THEN("recognized as an array") { + REQUIRE(symbol1.get_length() == 15); + REQUIRE(symbol1.is_array()); + + REQUIRE(symbol2.get_length() == 1); + REQUIRE(!symbol2.is_array()); + } + } + } +} From e6d4c95f1c462fe897d44b071b51ed8c4457afff Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 28 Sep 2022 09:13:05 +0200 Subject: [PATCH 464/871] Clean call of sympy (BlueBrain/nmodl#935) * Apply user_functions to vecFcode too When functions inside solve block are not inlined sympy cannot find them. The problem is already fixed for vecJcode, do the same. * Replace protected function names of sympy before sending eqs to it NMODL Repo SHA: BlueBrain/nmodl@09c615b0059d3035ef0712f5b4f27d080b469258 --- nmodl/ode.py | 41 ++++++- .../transpiler/unit/visitor/sympy_solver.cpp | 103 +++++++++++++++++- 2 files changed, 138 insertions(+), 6 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index 372012777d..283038f7ec 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -8,6 +8,7 @@ from importlib import import_module import sympy as sp +import re # import known_functions through low-level mechanism because the ccode # module is overwritten in sympy and contents of that submodule cannot be @@ -24,10 +25,31 @@ if not ((major >= 1) and (minor >= 2)): raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") +# Some functions are protected inside sympy, if user has declared such a function, it will fail +# because sympy will try to use its own internal one. +# Rename it before and after to a single name +forbidden_var = ["beta", "gamma", "uppergamma", "lowergamma", "polygamma", "loggamma", "digamma", "trigamma"] +def search_and_replace_protected_functions_to_sympy(eqs, function_calls): + for c in function_calls: + if c in forbidden_var: + r = re.compile(r"\b{}\b\s*\(".format(c)) + f = f"_sympy_{c}_fun(" + eqs = [re.sub(r, f, x) for x in eqs] + return eqs + +def search_and_replace_protected_functions_from_sympy(eqs, function_calls): + for c in function_calls: + if c in forbidden_var: + r = f"_sympy_{c}_fun" + eqs = [re.sub(r, f"{c}", x) for x in eqs] + return eqs + def _get_custom_functions(fcts): custom_functions = {} for f in fcts: - if not f in known_functions.keys(): + if f in forbidden_var: + custom_functions[f"_sympy_{f}_fun"] = f"_sympy_{f}_fun" + elif not f in known_functions.keys(): custom_functions[f] = f return custom_functions @@ -116,6 +138,7 @@ def _sympify_eqs(eq_strings, state_vars, vars): # parse state vars & eqs using above sympy objects sympy_state_vars = [] + for state_var in state_vars: sympy_state_vars.append(sp.sympify(state_var, locals=sympy_vars)) eqs = [ @@ -123,6 +146,7 @@ def _sympify_eqs(eq_strings, state_vars, vars): - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars)).expand() for eq in eq_strings ] + return eqs, sympy_state_vars, sympy_vars def _interweave_eqs(F, J): @@ -208,6 +232,8 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre vars: list of strings containing new local variables """ + eq_strings = search_and_replace_protected_functions_to_sympy(eq_strings, function_calls) + eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) custom_fcts = _get_custom_functions(function_calls) @@ -228,7 +254,7 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre ) for var, expr in sub_exprs: new_local_vars.append(sp.ccode(var)) - code.append(f"{var} = {sp.ccode(expr.evalf())}") + code.append(f"{var} = {sp.ccode(expr.evalf(), user_functions=custom_fcts)}") solution_vector = simplified_solution_vector[0] for var, expr in zip(state_vars, solution_vector): code.append(f"{sp.ccode(var)} = {sp.ccode(expr.evalf(), contract=False, user_functions=custom_fcts)}") @@ -240,7 +266,7 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre # construct vector F vecFcode = [] for i, expr in enumerate(vecF): - vecFcode.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf())}") + vecFcode.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}") # construct matrix J vecJcode = [] for i, expr in enumerate(matJ): @@ -250,6 +276,8 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre # interweave code = _interweave_eqs(vecFcode, vecJcode) + code = search_and_replace_protected_functions_from_sympy(code, function_calls) + return code, new_local_vars @@ -270,8 +298,9 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): List of strings containing assignment statements """ - eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) + eq_strings = search_and_replace_protected_functions_to_sympy(eq_strings, function_calls) + eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) custom_fcts = _get_custom_functions(function_calls) jacobian = sp.Matrix(eqs).jacobian(state_vars) @@ -280,7 +309,7 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): vecFcode = [] for i, eq in enumerate(eqs): - vecFcode.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf())}") + vecFcode.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}") vecJcode = [] for i, jac in enumerate(jacobian): # todo: fix indexing to be ascending order @@ -291,6 +320,8 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): # interweave code = _interweave_eqs(vecFcode, vecJcode) + code = search_and_replace_protected_functions_from_sympy(code, function_calls) + return code diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 776297b9de..2a0b3e1d7d 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -12,6 +12,7 @@ #include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" +#include "visitors/kinetic_block_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" @@ -35,7 +36,8 @@ std::vector run_sympy_solver_visitor( const std::string& text, bool pade = false, bool cse = false, - AstNodeType ret_nodetype = AstNodeType::DIFF_EQ_EXPRESSION) { + AstNodeType ret_nodetype = AstNodeType::DIFF_EQ_EXPRESSION, + bool kinetic = false) { std::vector results; // construct AST from text @@ -51,6 +53,10 @@ std::vector run_sympy_solver_visitor( ConstantFolderVisitor().visit_program(*ast); SymtabVisitor().visit_program(*ast); + if (kinetic) { + KineticBlockVisitor().visit_program(*ast); + } + // run SympySolver on AST SympySolverVisitor(pade, cse).visit_program(*ast); @@ -2123,3 +2129,98 @@ SCENARIO("Solve NONLINEAR block using SympySolver Visitor", "[visitor][solver][s } } } +SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sympy][kinetic]") { + GIVEN("KINETIC block with not inlined function should work") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE kstates METHOD sparse + } + STATE { + C1 + C2 + } + FUNCTION alfa(v(mV)) { + alfa = v + } + KINETIC kstates { + ~ C1 <-> C2 (alfa(v), alfa(v)) + })"; + std::string expected_text = R"( + DERIVATIVE kstates { + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_C1, old_C2 + }{ + old_C1 = C1 + old_C2 = C2 + }{ + nmodl_eigen_x[0] = C1 + nmodl_eigen_x[1] = C2 + }{ + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*alfa(v)-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*alfa(v)+old_C1 + nmodl_eigen_j[0] = -dt*alfa(v)-1.0 + nmodl_eigen_j[2] = dt*alfa(v) + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*alfa(v)-nmodl_eigen_x[1]*dt*alfa(v)-nmodl_eigen_x[1]+old_C2 + nmodl_eigen_j[1] = dt*alfa(v) + nmodl_eigen_j[3] = -dt*alfa(v)-1.0 + }{ + C1 = nmodl_eigen_x[0] + C2 = nmodl_eigen_x[1] + }{ + } + })"; + THEN("Run Kinetic and Sympy Visitor") { + std::vector result; + REQUIRE_NOTHROW(result = run_sympy_solver_visitor( + nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK, true)); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); + } + } + GIVEN("Protected names in Sympy are respected") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE kstates METHOD sparse + } + STATE { + C1 + C2 + } + FUNCTION beta(v(mV)) { + beta = v + } + FUNCTION lowergamma(v(mV)) { + lowergamma = v + } + KINETIC kstates { + ~ C1 <-> C2 (beta(v), lowergamma(v)) + })"; + std::string expected_text = R"( + DERIVATIVE kstates { + EIGEN_NEWTON_SOLVE[2]{ + LOCAL old_C1, old_C2 + }{ + old_C1 = C1 + old_C2 = C2 + }{ + nmodl_eigen_x[0] = C1 + nmodl_eigen_x[1] = C2 + }{ + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*beta(v)-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*lowergamma(v)+old_C1 + nmodl_eigen_j[0] = -dt*beta(v)-1.0 + nmodl_eigen_j[2] = dt*lowergamma(v) + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*beta(v)-nmodl_eigen_x[1]*dt*lowergamma(v)-nmodl_eigen_x[1]+old_C2 + nmodl_eigen_j[1] = dt*beta(v) + nmodl_eigen_j[3] = -dt*lowergamma(v)-1.0 + }{ + C1 = nmodl_eigen_x[0] + C2 = nmodl_eigen_x[1] + }{ + } + })"; + THEN("Run Kinetic and Sympy Visitor") { + std::vector result; + REQUIRE_NOTHROW(result = run_sympy_solver_visitor( + nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK, true)); + compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); + } + } +} From 14b1e4d4f58472f2ed79c447f8238fd98f5afb25 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 30 Sep 2022 00:14:13 +0200 Subject: [PATCH 465/871] Remove token that have never be implemented in nrn: STEPPED / TERMINAL / PLOT / SENS / SECTION (BlueBrain/nmodl#926) * Following constructs are no longer relevant in the NEURON context: STEPPED / TERMINAL / PLOT / SENS / SECTION * See discussion in neuronsimulator/nrnBlueBrain/nmodl#1974 NMODL Repo SHA: BlueBrain/nmodl@7b208330bb3dfef1342c9b81b7afb7b76a9251cf --- docs/nmodl/transpiler/language.rst | 10 -- docs/nmodl/transpiler/test_status.txt | 5 - .../codegen/codegen_compatibility_visitor.cpp | 2 - src/nmodl/language/code_generator.cmake | 9 - src/nmodl/language/nmodl.yaml | 118 ------------- src/nmodl/language/node_info.py | 2 - .../language/templates/pybind/pysymtab.cpp | 1 - src/nmodl/lexer/nmodl_utils.cpp | 10 -- src/nmodl/lexer/token_mapping.cpp | 5 - src/nmodl/parser/nmodl.yy | 163 ------------------ src/nmodl/symtab/symbol.cpp | 1 - src/nmodl/symtab/symbol_properties.cpp | 4 - src/nmodl/symtab/symbol_properties.hpp | 3 - src/nmodl/visitors/localize_visitor.cpp | 4 +- src/nmodl/visitors/perf_visitor.cpp | 8 - src/nmodl/visitors/perf_visitor.hpp | 4 - src/nmodl/visitors/visitor_utils.cpp | 5 +- .../transpiler/unit/modtoken/modtoken.cpp | 6 +- .../unit/utils/nmodl_constructs.cpp | 82 --------- 19 files changed, 6 insertions(+), 436 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index 7b48c80622..adef5fabb5 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -17,8 +17,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | PARAMETER | yes | yes | yes | +------------------------+-------------------+-------------------+---------------------+ -| STEPPED | yes | yes | yes | -+------------------------+-------------------+-------------------+---------------------+ | ASSIGNED | yes | yes | yes | +------------------------+-------------------+-------------------+---------------------+ | STATE | yes | yes | yes | @@ -57,8 +55,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | STEP | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| TERMINAL | yes | no | no | -+------------------------+-------------------+-------------------+---------------------+ | DISCRETE | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | PARTIAL | yes | no | no | @@ -101,8 +97,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | SWEEP | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| PLOT | yes | yes | no | -+------------------------+-------------------+-------------------+---------------------+ | CONDUCTANCE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | PROTECT | yes | yes | no | @@ -117,8 +111,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | RESET | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| SENS | yes | no | no | -+------------------------+-------------------+-------------------+---------------------+ | CONSERVE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | COMPARTMENT | yes | yes | no | @@ -135,8 +127,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | ELECTRODE_CURRENT | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| SECTION | yes | no | no | -+------------------------+-------------------+-------------------+---------------------+ | RANGE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | GLOBAL | yes | yes | no | diff --git a/docs/nmodl/transpiler/test_status.txt b/docs/nmodl/transpiler/test_status.txt index 28a5c9840e..e21c98e0ed 100644 --- a/docs/nmodl/transpiler/test_status.txt +++ b/docs/nmodl/transpiler/test_status.txt @@ -24,7 +24,6 @@ The following symbols are used in the document to descrive the status : |SOLVE | ✅ | |USING | ✅ | |WITH | ✅ | -|STEPPED | ✅ | |DISCRETE | ✅ | |FROM | ✅ | |FORALL | ✅ | @@ -35,7 +34,6 @@ The following symbols are used in the document to descrive the status : |ELSE | ✅ | |START | ✅ | |STEP | ✅ | -|SENS | ✅ | |SOLVEFOR | ✅ | |PROCEDURE | ✅ | |PARTIAL | ✅ | @@ -43,7 +41,6 @@ The following symbols are used in the document to descrive the status : |IFERROR | ✅ | |PARAMETER | ✅ | |EQUATION | ✅ | -|TERMINAL | ✅ | |LINEAR | ✅ | |NONLINEAR | ✅ | |FUNCTION | ✅ | @@ -67,7 +64,6 @@ The following symbols are used in the document to descrive the status : | != | ✅ | | ! | ✅ | | \>= | ✅ | -|PLOT | ✅ | |VS | ✅ | |LAG | ✅ | |RESET | ✅ | @@ -92,7 +88,6 @@ The following symbols are used in the document to descrive the status : |USEION | ✅ | |THREADSAFE | ✅ | |GLOBAL | ✅ | -|SECTION | ✅ | |RANGE | ✅ | |POINTER | ✅ | |BBCOREPOINTER | ✅ | diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index b1242d6b71..816cc98506 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -20,8 +20,6 @@ const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( {{AstNodeType::MATCH_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::TERMINAL_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::DISCRETE_BLOCK, &CodegenCompatibilityVisitor::return_error_with_name}, {AstNodeType::PARTIAL_BLOCK, diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 36d10117e4..4b36155a77 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -141,9 +141,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/partial_block.hpp ${PROJECT_BINARY_DIR}/src/ast/partial_boundary.hpp ${PROJECT_BINARY_DIR}/src/ast/partial_equation.hpp - ${PROJECT_BINARY_DIR}/src/ast/plot_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/plot_declaration.hpp - ${PROJECT_BINARY_DIR}/src/ast/plot_var.hpp ${PROJECT_BINARY_DIR}/src/ast/pointer.hpp ${PROJECT_BINARY_DIR}/src/ast/pointer_var.hpp ${PROJECT_BINARY_DIR}/src/ast/prime_name.hpp @@ -157,20 +154,14 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/reaction_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/read_ion_var.hpp ${PROJECT_BINARY_DIR}/src/ast/reset.hpp - ${PROJECT_BINARY_DIR}/src/ast/section.hpp - ${PROJECT_BINARY_DIR}/src/ast/section_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/sens.hpp ${PROJECT_BINARY_DIR}/src/ast/solution_expression.hpp ${PROJECT_BINARY_DIR}/src/ast/solve_block.hpp ${PROJECT_BINARY_DIR}/src/ast/state_block.hpp ${PROJECT_BINARY_DIR}/src/ast/statement.hpp ${PROJECT_BINARY_DIR}/src/ast/statement_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/step_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/stepped.hpp ${PROJECT_BINARY_DIR}/src/ast/string.hpp ${PROJECT_BINARY_DIR}/src/ast/suffix.hpp ${PROJECT_BINARY_DIR}/src/ast/table_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/terminal_block.hpp ${PROJECT_BINARY_DIR}/src/ast/thread_safe.hpp ${PROJECT_BINARY_DIR}/src/ast/threadsafe_var.hpp ${PROJECT_BINARY_DIR}/src/ast/unary_expression.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 9f94ada917..b089df1af3 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -372,14 +372,6 @@ type: Name node_name: true - - SectionVar: - brief: "TODO" - members: - - name: - brief: "TODO" - type: Name - node_name: true - - RangeVar: brief: "TODO" members: @@ -463,26 +455,6 @@ All parameters are stored in the ast::ParamBlock::statements as vector. - - StepBlock: - nmodl: "STEPPED " - members: - - statements: - brief: "Vector of statements" - type: Stepped - vector: true - brief: "Represents a `STEPPED` block in the NMODL" - description: | - `STEPPED` has following form in the NMODL specification : - - \code{.mod} - STEPPED { - name1 = number1 (mM) - name2 = number2, number3 - } - \endcode - - \todo Check ModelDB and other databse for example of channel. - - IndependentBlock: nmodl: "INDEPENDENT " members: @@ -549,22 +521,6 @@ Note that the state variable specification has form of ast::AssignedDefinition and hence can have associated unit specification. - - PlotBlock: - members: - - plot: - brief: "Vector of plot variables" - type: PlotDeclaration - brief: "Represents a `PLOT` statement in the NMODL" - description: | - `PLOT` construct doesn't have block scope but it's standalone, global scoped - statement of the form: - - \code{.mod} - PLOT name1, index_var2 VS name3 - \endcode - - \todo Check ModelDB and other databse for example of channel. - - InitialBlock: nmodl: "INITIAL " members: @@ -919,15 +875,6 @@ \sa ast::DerivativeBlock ast::InitialBlock - - TerminalBlock: - brief: "TODO" - nmodl: "TERMINAL " - members: - - statement_block: - brief: "Block with statements vector" - type: StatementBlock - getter: {override: true} - - BeforeBlock: nmodl: "BEFORE " members: @@ -1133,19 +1080,6 @@ type: Number suffix: {value: ">"} - - PlotVar: - brief: "TODO" - members: - - name: - brief: "TODO" - type: Identifier - - index: - brief: "TODO" - type: Integer - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - ConstantVar: members: - name: @@ -1444,24 +1378,6 @@ optional: true prefix: {value: " "} - - Stepped: - brief: "TODO" - members: - - name: - brief: "TODO" - type: Name - - values: - brief: "TODO" - type: Number - vector: true - prefix: {value: " = "} - separator: ", " - - unit: - brief: "TODO" - type: Unit - optional: true - prefix: {value: " "} - - IndependentDefinition: brief: "TODO" members: @@ -1536,20 +1452,6 @@ optional: true brief: "Represents a statement in `ASSIGNED` or `STATE` block" - - PlotDeclaration: - brief: "TODO" - nmodl: "PLOT " - members: - - variables: - brief: "TODO" - type: PlotVar - vector: true - separator: ", " - - name: - brief: "TODO" - type: PlotVar - prefix: {value: " VS "} - - ConductanceHint: nmodl: "CONDUCTANCE " members: @@ -1770,16 +1672,6 @@ nmodl: RESET brief: "Represent RESET statement in NMODL" - - Sens: - nmodl: "SENS " - members: - - variables: - brief: "TODO" - type: VarName - vector: true - separator: ", " - brief: "Represent SENS statement in NMODL" - - Conserve: nmodl: CONSERVE members: @@ -1986,16 +1878,6 @@ separator: ", " brief: "Represents ELECTRODE_CURRENT variables statement in NMODL" - - Section: - nmodl: "SECTION " - members: - - sections: - brief: "Vector of section variables" - type: SectionVar - vector: true - separator: ", " - brief: "Represents SECTION variables statement in NMODL" - - Range: nmodl: "RANGE " members: diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 821d263287..e27d1f29f3 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -68,7 +68,6 @@ "WriteIonVar", "NonspecificCurVar", "ElectrodeCurVar", - "SectionVar", "GlobalVar", "PointerVar", "BbcorePointerVar", @@ -114,7 +113,6 @@ "AssignedDefinition", "ParamAssign", "ConstantStatement", - "Stepped", } # data types which have token as an argument to the constructor diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index a3d4e9b9dc..c742bcee08 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -177,7 +177,6 @@ void init_symtab_module(py::module& m) { .value("procedure_block", syminfo::NmodlType::procedure_block) .value("range_var", syminfo::NmodlType::range_var) .value("read_ion_var", syminfo::NmodlType::read_ion_var) - .value("section_var", syminfo::NmodlType::section_var) .value("state_var", syminfo::NmodlType::state_var) .value("table_assigned_var", syminfo::NmodlType::table_assigned_var) .value("table_statement_var", syminfo::NmodlType::table_statement_var) diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index e35c057513..fb62638be5 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -241,8 +241,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_PARAMETER(token, pos); case Token::PARTIAL: return Parser::make_PARTIAL(token, pos); - case Token::PLOT: - return Parser::make_PLOT(token, pos); case Token::POINTER: return Parser::make_POINTER(token, pos); case Token::PROCEDURE: @@ -255,10 +253,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_READ(token, pos); case Token::RESET: return Parser::make_RESET(token, pos); - case Token::SECTION: - return Parser::make_SECTION(token, pos); - case Token::SENS: - return Parser::make_SENS(token, pos); case Token::SOLVE: return Parser::make_SOLVE(token, pos); case Token::SOLVEFOR: @@ -271,14 +265,10 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_STEADYSTATE(token, pos); case Token::STEP: return Parser::make_STEP(token, pos); - case Token::STEPPED: - return Parser::make_STEPPED(token, pos); case Token::SWEEP: return Parser::make_SWEEP(token, pos); case Token::TABLE: return Parser::make_TABLE(token, pos); - case Token::TERMINAL: - return Parser::make_TERMINAL(token, pos); case Token::THREADSAFE: return Parser::make_THREADSAFE(token, pos); case Token::TO: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 6a1ce6b7bb..6147be3ba4 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -42,14 +42,12 @@ const static std::map keywords = { {"INDEPENDENT", Token::INDEPENDENT}, {"ASSIGNED", Token::ASSIGNED}, {"INITIAL", Token::INITIAL1}, - {"TERMINAL", Token::TERMINAL}, {"DERIVATIVE", Token::DERIVATIVE}, {"EQUATION", Token::BREAKPOINT}, {"BREAKPOINT", Token::BREAKPOINT}, {"CONDUCTANCE", Token::CONDUCTANCE}, {"SOLVE", Token::SOLVE}, {"STATE", Token::STATE}, - {"STEPPED", Token::STEPPED}, {"LINEAR", Token::LINEAR}, {"NONLINEAR", Token::NONLINEAR}, {"DISCRETE", Token::DISCRETE}, @@ -62,7 +60,6 @@ const static std::map keywords = { {"LOCAL", Token::LOCAL}, {"METHOD", Token::USING}, {"STEADYSTATE", Token::STEADYSTATE}, - {"SENS", Token::SENS}, {"STEP", Token::STEP}, {"WITH", Token::WITH}, {"FROM", Token::FROM}, @@ -76,7 +73,6 @@ const static std::map keywords = { {"DEFINE", Token::DEFINE1}, {"KINETIC", Token::KINETIC}, {"CONSERVE", Token::CONSERVE}, - {"PLOT", Token::PLOT}, {"VS", Token::VS}, {"LAG", Token::LAG}, {"RESET", Token::RESET}, @@ -100,7 +96,6 @@ const static std::map keywords = { {"ARTIFICIAL_CELL", Token::SUFFIX}, {"NONSPECIFIC_CURRENT", Token::NONSPECIFIC}, {"ELECTRODE_CURRENT", Token::ELECTRODE_CURRENT}, - {"SECTION", Token::SECTION}, {"RANGE", Token::RANGE}, {"USEION", Token::USEION}, {"READ", Token::READ}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 8b14f93e47..de77e2b9b7 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -131,7 +131,6 @@ %token NRNMUTEXUNLOCK %token PARAMETER %token PARTIAL -%token PLOT %token POINTER %token PROCEDURE %token PROTECT @@ -140,18 +139,14 @@ %token REACTION %token READ %token RESET -%token SECTION -%token SENS %token SOLVE %token SOLVEFOR %token START1 %token STATE %token STEADYSTATE %token STEP -%token STEPPED %token SWEEP %token TABLE -%token TERMINAL %token THREADSAFE %token TO %token UNITS @@ -237,7 +232,6 @@ %type model %type units -%type optional_index %type unit %type procedure %type limits @@ -247,7 +241,6 @@ %type term %type left_linear_expression %type linear_expression -%type number_list %type expression %type watch_expression %type statement_type1 @@ -267,25 +260,19 @@ %type if_solution_error %type optional_increment %type optional_start -%type sens_list -%type sens %type lag_statement %type forall_statement %type parameter_assignment -%type stepped_statement %type independent_definition %type dependent_definition %type declare %type parameter_block_body %type independent_block_body %type dependent_block_body -%type step_block_body %type watch_statement %type watch_direction %type watch %type for_netcon -%type plot_declaration -%type plot_variable_list %type constant_statement %type match_list %type match @@ -318,7 +305,6 @@ %type ontology %type nonspecific_var_list %type electrode_current_var_list -%type section_var_list %type range_var_list %type global_var_list %type pointer_var_list @@ -355,8 +341,6 @@ %type procedure_block %type solve_block %type state_block -%type step_block -%type terminal_block %type unit_block %type INTEGER_PTR @@ -542,14 +526,6 @@ declare : parameter_block { $$ = $1; } - | step_block - { - $$ = $1; - } - | plot_declaration - { - $$ = new ast::PlotBlock($1); - } | neuron_block { $$ = $1; @@ -644,45 +620,6 @@ limits : { ; -step_block : STEPPED "{" step_block_body "}" - { - $$ = new ast::StepBlock($3); - } - ; - - -step_block_body : { - $$ = ast::SteppedVector(); - } - | step_block_body stepped_statement - { - $1.emplace_back($2); - $$ = $1; - } - ; - - -stepped_statement : NAME_PTR "=" number_list units - { - $$ = new ast::Stepped($1, $3, $4); - } - ; - - -number_list : number "," number - { - $$ = ast::NumberVector(); - $$.emplace_back($1); - $$.emplace_back($3); - } - | number_list "," number - { - $1.emplace_back($3); - $$ = $1; - } - ; - - name : Name { $$ = $1; @@ -857,43 +794,6 @@ state_block : STATE "{" dependent_block_body "}" ; -plot_declaration : PLOT plot_variable_list VS name optional_index - { - $$ = new ast::PlotDeclaration($2, new ast::PlotVar($4,$5)); - } - | PLOT error - { - error(scanner.loc, "plot_declaration"); - } - ; - - -plot_variable_list : name optional_index - { - $$ = ast::PlotVarVector(); - auto variable = new ast::PlotVar($1, $2); - $$.emplace_back(variable); - } - | plot_variable_list "," name optional_index - { - $$ = $1; - auto variable = new ast::PlotVar($3, $4); - $$.emplace_back(variable); - } - ; - - -optional_index : - { - $$ = nullptr; - } - | "[" INTEGER_PTR "]" - { - $$ = $2; - } - ; - - procedure : initial_block { $$ = $1; @@ -926,10 +826,6 @@ procedure : initial_block { $$ = $1; } - | terminal_block - { - $$ = $1; - } | discrete_block { $$ = $1; @@ -1127,10 +1023,6 @@ statement_type1 : from_statement { auto text = parse_with_verbatim_parser($1); $$ = new ast::BlockComment(new ast::String(text)); } - | sens - { - $$ = $1; - } | compartment { $$ = $1; @@ -1791,15 +1683,6 @@ breakpoint_block : BREAKPOINT statement_list "}" ; -terminal_block : TERMINAL statement_list "}" - { - $$ = new ast::TerminalBlock($2); - ModToken block_token = $1 + $3; - $$->set_token(block_token); - } - ; - - before_after_block : BREAKPOINT statement_list "}" { $$ = new ast::BABlock(new ast::BABlockType(ast::BATYPE_BREAKPOINT), $2); @@ -1923,30 +1806,6 @@ watch_expression : variable_name ; -sens : SENS sens_list - { - $$ = new ast::Sens($2); - } - | SENS error - { - error(scanner.loc, "sens"); - } - ; - - -sens_list : variable_name - { - $$ = ast::VarNameVector(); - $$.emplace_back($1); - } - | sens_list "," variable_name - { - $1.emplace_back($3); - $$ = $1; - } - ; - - conserve : CONSERVE react "=" expression { $$ = new ast::Conserve($2, $4); @@ -2269,11 +2128,6 @@ neuron_statement : $1.emplace_back(new ast::ElectrodeCurrent($3)); $$ = $1; } - | neuron_statement SECTION section_var_list - { - $1.emplace_back(new ast::Section($3)); - $$ = $1; - } | neuron_statement RANGE range_var_list { $1.emplace_back(new ast::Range($3)); @@ -2426,23 +2280,6 @@ electrode_current_var_list : NAME_PTR ; -section_var_list : NAME_PTR - { - $$ = ast::SectionVarVector(); - $$.emplace_back(new ast::SectionVar($1)); - } - | section_var_list "," NAME_PTR - { - $1.emplace_back(new ast::SectionVar($3)); - $$ = $1; - } - | error - { - error(scanner.loc, "section_var_list"); - } - ; - - range_var_list : NAME_PTR { $$ = ast::RangeVarVector(); diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index 852862fd89..bccda7259f 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -32,7 +32,6 @@ bool Symbol::is_variable() const noexcept { | NmodlType::write_ion_var | NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var - | NmodlType::section_var | NmodlType::argument | NmodlType::extern_neuron_variable; // clang-format on diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 9677f6413c..a2b3f8cd13 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -79,10 +79,6 @@ std::vector to_string_vector(const NmodlType& obj) { properties.emplace_back("electrode_cur"); } - if (has_property(obj, NmodlType::section_var)) { - properties.emplace_back("section"); - } - if (has_property(obj, NmodlType::argument)) { properties.emplace_back("argument"); } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 2824f5b43e..d15600a484 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -159,9 +159,6 @@ enum class NmodlType : enum_type { /// Electrode Current electrode_cur_var = 1L << 13, - /// Section Type - section_var = 1L << 14, - /// Argument Type argument = 1L << 15, diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 4698b1e91a..325ec86e32 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -41,7 +41,6 @@ bool LocalizeVisitor::node_for_def_use_analysis(const ast::Node& node) const { ast::AstNodeType::DISCRETE_BLOCK, ast::AstNodeType::PARTIAL_BLOCK, ast::AstNodeType::NET_RECEIVE_BLOCK, - ast::AstNodeType::TERMINAL_BLOCK, ast::AstNodeType::BA_BLOCK, ast::AstNodeType::FOR_NETCON, ast::AstNodeType::BEFORE_BLOCK, @@ -79,8 +78,7 @@ std::vector LocalizeVisitor::variables_to_optimize() const { | NmodlType::nonspecific_cur_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var - | NmodlType::electrode_cur_var - | NmodlType::section_var; + | NmodlType::electrode_cur_var; const NmodlType global_var_properties = NmodlType::range_var | NmodlType::assigned_definition diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 54c043402c..f06495c6a4 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -343,10 +343,6 @@ void PerfVisitor::visit_program(const ast::Program& node) { print_memory_usage(); } -void PerfVisitor::visit_plot_block(const ast::PlotBlock& node) { - measure_performance(node); -} - /// skip initial block under net_receive block void PerfVisitor::visit_initial_block(const ast::InitialBlock& node) { if (!under_net_receive_block) { @@ -404,10 +400,6 @@ void PerfVisitor::visit_breakpoint_block(const ast::BreakpointBlock& node) { measure_performance(node); } -void PerfVisitor::visit_terminal_block(const ast::TerminalBlock& node) { - measure_performance(node); -} - void PerfVisitor::visit_before_block(const ast::BeforeBlock& node) { measure_performance(node); } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 188efe83b9..47f4eaf289 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -198,8 +198,6 @@ class PerfVisitor: public ConstAstVisitor { void visit_program(const ast::Program& node) override; - void visit_plot_block(const ast::PlotBlock& node) override; - /// skip initial block under net_receive block void visit_initial_block(const ast::InitialBlock& node) override; @@ -227,8 +225,6 @@ class PerfVisitor: public ConstAstVisitor { void visit_breakpoint_block(const ast::BreakpointBlock& node) override; - void visit_terminal_block(const ast::TerminalBlock& node) override; - void visit_before_block(const ast::BeforeBlock& node) override; void visit_after_block(const ast::AfterBlock& node) override; diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index d51b887868..d7b00c997a 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -175,9 +175,8 @@ std::set get_global_vars(const Program& node) { NmodlType::prime_name | NmodlType::assigned_definition | NmodlType::read_ion_var | NmodlType::write_ion_var | NmodlType::nonspecific_cur_var | NmodlType::electrode_cur_var | - NmodlType::section_var | NmodlType::constant_var | - NmodlType::extern_neuron_variable | NmodlType::state_var | - NmodlType::factor_def; + NmodlType::constant_var | NmodlType::extern_neuron_variable | + NmodlType::state_var | NmodlType::factor_def; for (const auto& globalvar: symtab->get_variables_with_properties(property)) { std::string var_name = globalvar->get_name(); if (globalvar->is_array()) { diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index e961300a73..4f71f1fa24 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -49,14 +49,14 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 355"); + REQUIRE(ss.str() == " text at [1.1-4] type 350"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 355"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 350"); } } @@ -66,7 +66,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 362"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 357"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index c24cd5d249..26202027f9 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -439,20 +439,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "step_block_1", - { - "STEP block with all statement types", - R"( - STEPPED { - tau_r_AMPA = 1, -2 - tau_d_AMPA = 1.1, -2.1 - tau_r_NMDA = 1, 2.1, 3 (mV) - } - )" - } - }, - { "independent_block_1", { @@ -587,37 +573,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "plot_declare_1", - { - "PLOT declaration with single variables", - R"( - PLOT x VS y - )" - } - }, - - { - "plot_declare_2", - { - "PLOT declaration with multiple variables", - R"( - PLOT x, y, z VS a - )" - } - }, - - { - "plot_declare_3", - { - "PLOT declaration with indexed variables", - R"( - PLOT x[1], y[2], z VS a[1] - )" - } - }, - - { "statement_list_1", { @@ -933,18 +888,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "sens_1", - { - "SENS statement", - R"( - BREAKPOINT { - SENS a, b - } - )" - } - }, - { "conserve_1", { @@ -1226,20 +1169,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "function_call_1", - { - "FUNCTION call", - R"( - TERMINAL { - a = fun1() - b = fun2(a, 2) - fun3() - } - )" - } - }, - { "kinetic_block_1", { @@ -1442,17 +1371,6 @@ std::map const nmodl_valid_constructs{ )" } }, - { - "section_test", - { - "Section token test", - R"( - NEURON { - SECTION a, b - } - )" - } - }, { "empty_unit_declaration", { From f7c93eb3a29462072509d0fa966b24f45b727600 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 30 Sep 2022 06:46:00 +0200 Subject: [PATCH 466/871] Remove FIRST / LAST / PARTIAL / IFERROR (BlueBrain/nmodl#939) * Remove token PARTIAL / FIRST / LAST * nitpick: fix enum for symbol properties * Remove unnecessary IFERROR production in SolveBlock (BlueBrain/nmodl#940) See neuronsimulator/nrn/issues/1957 Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@25cd4707d2a4cf7f47febeb9ba321ff2dc30244c --- docs/nmodl/transpiler/language.rst | 2 - docs/nmodl/transpiler/test_status.txt | 4 - src/nmodl/ast/ast_common.hpp | 6 -- .../codegen/codegen_compatibility_visitor.cpp | 2 - src/nmodl/codegen/codegen_helper_visitor.cpp | 4 - src/nmodl/codegen/codegen_helper_visitor.hpp | 1 - src/nmodl/language/code_generator.cmake | 4 - src/nmodl/language/nmodl.yaml | 87 ------------------- src/nmodl/language/node_info.py | 4 - .../language/templates/pybind/pysymtab.cpp | 1 - src/nmodl/lexer/nmodl.ll | 5 -- src/nmodl/lexer/nmodl_lexer.hpp | 1 - src/nmodl/lexer/nmodl_utils.cpp | 10 --- src/nmodl/lexer/token_mapping.cpp | 4 - src/nmodl/parser/nmodl.yy | 78 ++--------------- src/nmodl/symtab/symbol_properties.cpp | 4 - src/nmodl/symtab/symbol_properties.hpp | 43 +++++---- src/nmodl/visitors/defuse_analyze_visitor.cpp | 4 - src/nmodl/visitors/defuse_analyze_visitor.hpp | 2 - src/nmodl/visitors/localize_visitor.cpp | 1 - src/nmodl/visitors/perf_visitor.cpp | 4 - src/nmodl/visitors/perf_visitor.hpp | 2 - .../transpiler/unit/modtoken/modtoken.cpp | 6 +- test/nmodl/transpiler/unit/parser/parser.cpp | 2 +- .../unit/utils/nmodl_constructs.cpp | 36 ++------ 25 files changed, 35 insertions(+), 282 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index adef5fabb5..b989189951 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -57,8 +57,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | DISCRETE | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| PARTIAL | yes | no | no | -+------------------------+-------------------+-------------------+---------------------+ | FUNCTION_TABLE | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | CONSTRUCTOR | yes | no | yes | diff --git a/docs/nmodl/transpiler/test_status.txt b/docs/nmodl/transpiler/test_status.txt index e21c98e0ed..a34534fa54 100644 --- a/docs/nmodl/transpiler/test_status.txt +++ b/docs/nmodl/transpiler/test_status.txt @@ -36,9 +36,7 @@ The following symbols are used in the document to descrive the status : |STEP | ✅ | |SOLVEFOR | ✅ | |PROCEDURE | ✅ | -|PARTIAL | ✅ | |DEFINE | ✅ | -|IFERROR | ✅ | |PARAMETER | ✅ | |EQUATION | ✅ | |LINEAR | ✅ | @@ -70,8 +68,6 @@ The following symbols are used in the document to descrive the status : |MATCH | ✅ | |MODEL_LEVEL | ✅ | |SWEEP | ✅ | -|FIRST | ✅ | -|LAST | ✅ | |KINETIC | ✅ | |CONSERVE | ✅ | |REACTION | ✅ | diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index c2d3ffc7fa..956dbc6c19 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -76,12 +76,6 @@ typedef enum { UOP_NOT, UOP_NEGATION } UnaryOp; /// string representation of ast::UnaryOp static const std::string UnaryOpNames[] = {"!", "-"}; -/// enum type for partial equation types -typedef enum { PEQ_FIRST, PEQ_LAST } FirstLastType; - -/// string representation of ast::FirstLastType -static const std::string FirstLastTypeNames[] = {"FIRST", "LAST"}; - /// enum type to distinguish BEFORE or AFTER blocks typedef enum { BATYPE_BREAKPOINT, BATYPE_SOLVE, BATYPE_INITIAL, BATYPE_STEP } BAType; diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 816cc98506..0f14466028 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -22,8 +22,6 @@ const std::map &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::DISCRETE_BLOCK, &CodegenCompatibilityVisitor::return_error_with_name}, - {AstNodeType::PARTIAL_BLOCK, - &CodegenCompatibilityVisitor::return_error_with_name}, {AstNodeType::FUNCTION_TABLE_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::SOLVE_BLOCK, diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 45a0c5c53f..4eda696b7f 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -731,10 +731,6 @@ void CodegenHelperVisitor::visit_discrete_block(const ast::DiscreteBlock& /* nod info.vectorize = false; } -void CodegenHelperVisitor::visit_partial_block(const ast::PartialBlock& /* node */) { - info.vectorize = false; -} - void CodegenHelperVisitor::visit_update_dt(const ast::UpdateDt& node) { info.changed_dt = node.get_value()->eval(); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 614f93732e..6d4a378a1d 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -110,7 +110,6 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_linear_block(const ast::LinearBlock& node) override; void visit_non_linear_block(const ast::NonLinearBlock& node) override; void visit_discrete_block(const ast::DiscreteBlock& node) override; - void visit_partial_block(const ast::PartialBlock& node) override; void visit_update_dt(const ast::UpdateDt& node) override; void visit_verbatim(const ast::Verbatim& node) override; void visit_before_block(const ast::BeforeBlock& node) override; diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 4b36155a77..cd5eba498e 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -91,7 +91,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/extern_var.hpp ${PROJECT_BINARY_DIR}/src/ast/external.hpp ${PROJECT_BINARY_DIR}/src/ast/factor_def.hpp - ${PROJECT_BINARY_DIR}/src/ast/first_last_type_index.hpp ${PROJECT_BINARY_DIR}/src/ast/float.hpp ${PROJECT_BINARY_DIR}/src/ast/for_all_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/for_netcon.hpp @@ -138,9 +137,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/param_assign.hpp ${PROJECT_BINARY_DIR}/src/ast/param_block.hpp ${PROJECT_BINARY_DIR}/src/ast/paren_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/partial_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/partial_boundary.hpp - ${PROJECT_BINARY_DIR}/src/ast/partial_equation.hpp ${PROJECT_BINARY_DIR}/src/ast/pointer.hpp ${PROJECT_BINARY_DIR}/src/ast/pointer_var.hpp ${PROJECT_BINARY_DIR}/src/ast/prime_name.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index b089df1af3..97b200ef8c 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -721,20 +721,6 @@ type: StatementBlock getter: {override: true} - - PartialBlock: - brief: "TODO" - nmodl: "PARTIAL " - members: - - name: - brief: "Name of the partial block" - type: Name - node_name: true - suffix: {value: " "} - - statement_block: - brief: "Block with statements vector" - type: StatementBlock - getter: {override: true} - - FunctionTableBlock: brief: "TODO" nmodl: "FUNCTION_TABLE " @@ -844,11 +830,6 @@ type: Name optional: true prefix: {value: " STEADYSTATE "} - - ifsolerr: - brief: "Block to be executed on error" - type: StatementBlock - optional: true - prefix: {value: " IFERROR "} - BreakpointBlock: nmodl: "BREAKPOINT " @@ -1210,13 +1191,6 @@ prefix: {value: "(", force: true} suffix: {value: ")", force: true} - - FirstLastTypeIndex: - brief: "TODO" - members: - - value: - brief: "TODO" - type: FirstLastType - - Watch: brief: "TODO" members: @@ -1588,67 +1562,6 @@ type: StatementBlock getter: {override: true} - - PartialEquation: - brief: "TODO" - members: - - prime: - brief: "TODO" - type: PrimeName - - name1: - brief: "TODO" - type: Name - - name2: - brief: "TODO" - type: Name - - name3: - brief: "TODO" - type: Name - - - PartialBoundary: - brief: "TODO" - nmodl: "~ " - members: - - del: - brief: "TODO" - type: Name - optional: true - suffix: {value: " "} - - name: - brief: "TODO" - type: Identifier - - index: - brief: "TODO" - type: FirstLastTypeIndex - optional: true - prefix: {value: "["} - suffix: {value: "]"} - - expression: - brief: "TODO" - type: Expression - optional: true - prefix: {value: " = "} - - name1: - brief: "TODO" - type: Name - optional: true - prefix: {value: " = "} - suffix: {value: "*"} - - del2: - brief: "TODO" - type: Name - optional: true - suffix: {value: "("} - - name2: - brief: "TODO" - type: Name - optional: true - suffix: {value: ")"} - - name3: - brief: "TODO" - type: Name - optional: true - prefix: {value: "+"} - - WatchStatement: nmodl: "WATCH " members: diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index e27d1f29f3..f305c021a7 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -25,7 +25,6 @@ "BinaryOp", "UnaryOp", "ReactionOp", - "FirstLastType", "BAType", "UnitStateType", } @@ -36,7 +35,6 @@ ENUM_BASE_TYPES = {"BinaryOp", "UnaryOp", "ReactionOp", - "FirstLastType", "BAType", "UnitStateType", } @@ -52,7 +50,6 @@ "ReactionOperator": "ReactionOp", "UnitState": "UnitStateType", "BABlockType": "BAType", - "FirstLastTypeIndex": "FirstLastType", } # nodes which will go into symbol table @@ -86,7 +83,6 @@ "LinearBlock", "NonLinearBlock", "DiscreteBlock", - "PartialBlock", "KineticBlock", "FunctionTableBlock" } diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index c742bcee08..cf070692d1 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -171,7 +171,6 @@ void init_symtab_module(py::module& m) { .value("man_linear_block", syminfo::NmodlType::non_linear_block) .value("nonspecific_cur_var", syminfo::NmodlType::nonspecific_cur_var) .value("param_assign", syminfo::NmodlType::param_assign) - .value("partial_block", syminfo::NmodlType::partial_block) .value("pointer_var", syminfo::NmodlType::pointer_var) .value("prime_name", syminfo::NmodlType::prime_name) .value("procedure_block", syminfo::NmodlType::procedure_block) diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index dedef56bdb..4b274375e3 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -210,7 +210,6 @@ ELSE { /** We have to store context for the reaction type */ case Token::NONLINEAR: case Token::LINEAR: - case Token::PARTIAL: case Token::KINETIC: lexical_context = type; break; @@ -308,10 +307,6 @@ ELSE { return token_symbol(yytext, loc, Token::LIN1); } - if (lexical_context == Token::PARTIAL) { - return token_symbol(yytext, loc, Token::TILDE); - } - if (lexical_context == Token::KINETIC) { return token_symbol(yytext, loc, Token::REACTION); } diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index 1ec5052a40..c7d31c1fe7 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -76,7 +76,6 @@ class NmodlLexer: public NmodlFlexLexer { * - NONLINEAR * - LINEAR * - KINETIC - * - PARTIAL */ int lexical_context = 0; diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index fb62638be5..9de4bd3ba8 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -183,8 +183,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_EQUATION(token, pos); case Token::EXTERNAL: return Parser::make_EXTERNAL(token, pos); - case Token::FIRST: - return Parser::make_FIRST(token, pos); case Token::FOR_NETCONS: return Parser::make_FOR_NETCONS(token, pos); case Token::FORALL1: @@ -199,8 +197,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_GLOBAL(token, pos); case Token::IF: return Parser::make_IF(token, pos); - case Token::IFERROR: - return Parser::make_IFERROR(token, pos); case Token::INCLUDE1: return Parser::make_INCLUDE1(token, pos); case Token::INDEPENDENT: @@ -211,8 +207,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_KINETIC(token, pos); case Token::LAG: return Parser::make_LAG(token, pos); - case Token::LAST: - return Parser::make_LAST(token, pos); case Token::LINEAR: return Parser::make_LINEAR(token, pos); case Token::LOCAL: @@ -239,8 +233,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_NRNMUTEXUNLOCK(token, pos); case Token::PARAMETER: return Parser::make_PARAMETER(token, pos); - case Token::PARTIAL: - return Parser::make_PARTIAL(token, pos); case Token::POINTER: return Parser::make_POINTER(token, pos); case Token::PROCEDURE: @@ -299,8 +291,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_NONLIN1(token, pos); case Token::LIN1: return Parser::make_LIN1(token, pos); - case Token::TILDE: - return Parser::make_TILDE(token, pos); case Token::REACTION: return Parser::make_REACTION(token, pos); case Token::GT: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 6147be3ba4..dcc44b5f72 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -54,7 +54,6 @@ const static std::map keywords = { {"FUNCTION", Token::FUNCTION1}, {"FUNCTION_TABLE", Token::FUNCTION_TABLE}, {"PROCEDURE", Token::PROCEDURE}, - {"PARTIAL", Token::PARTIAL}, {"DEL2", Token::DEL2}, {"DEL", Token::DEL}, {"LOCAL", Token::LOCAL}, @@ -79,11 +78,8 @@ const static std::map keywords = { {"MATCH", Token::MATCH}, {"MODEL_LEVEL", Token::MODEL_LEVEL}, {"SWEEP", Token::SWEEP}, - {"FIRST", Token::FIRST}, - {"LAST", Token::LAST}, {"COMPARTMENT", Token::COMPARTMENT}, {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, - {"IFERROR", Token::IFERROR}, {"SOLVEFOR", Token::SOLVEFOR}, {"UNITS", Token::UNITS}, {"UNITSON", Token::UNITSON}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index de77e2b9b7..4a7b402f91 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -100,7 +100,6 @@ %token ELSE %token EQUATION %token EXTERNAL -%token FIRST %token FORALL1 %token FOR_NETCONS %token FROM @@ -108,13 +107,11 @@ %token FUNCTION_TABLE %token GLOBAL %token IF -%token IFERROR %token INCLUDE1 %token INDEPENDENT %token INITIAL1 %token KINETIC %token LAG -%token LAST %token LIN1 %token LINEAR %token LOCAL @@ -130,7 +127,6 @@ %token NRNMUTEXLOCK %token NRNMUTEXUNLOCK %token PARAMETER -%token PARTIAL %token POINTER %token PROCEDURE %token PROTECT @@ -257,7 +253,6 @@ %type optional_else_if %type optional_else %type function_call -%type if_solution_error %type optional_increment %type optional_start %type lag_statement @@ -277,8 +272,6 @@ %type match_list %type match %type match_name -%type partial_equation -%type first_last %type reaction_statement %type conserve %type react @@ -337,7 +330,6 @@ %type neuron_block %type non_linear_block %type parameter_block -%type partial_block %type procedure_block %type solve_block %type state_block @@ -830,10 +822,6 @@ procedure : initial_block { $$ = $1; } - | partial_block - { - $$ = $1; - } | kinetic_block { $$ = $1; @@ -1047,10 +1035,6 @@ statement_type1 : from_statement { $$ = new ast::ExpressionStatement($1); } - | partial_equation - { - $$ = $1; - } | table_statement { $$ = $1; @@ -1508,45 +1492,6 @@ discrete_block : DISCRETE NAME_PTR statement_list "}" ; -partial_block : PARTIAL NAME_PTR statement_list "}" - { - $$ = new ast::PartialBlock($2, $3); - ModToken block_token = $1 + $4; - $$->set_token(block_token); - } - | PARTIAL error - { - error(scanner.loc, "partial_block"); - } - ; - - -partial_equation : "~" PRIME "=" NAME_PTR "*" DEL2 "(" NAME_PTR ")" "+" NAME_PTR - { - $$ = new ast::PartialBoundary(NULL, $2.clone(), NULL, NULL, $4, $6.clone(), $8, $11); - } - | "~" DEL NAME_PTR "[" first_last "]" "=" expression - { - $$ = new ast::PartialBoundary($2.clone(), $3, $5, $8, NULL, NULL, NULL, NULL); - } - | "~" NAME_PTR "[" first_last "]" "=" expression - { - $$ = new ast::PartialBoundary(NULL, $2, $4, $7, NULL, NULL, NULL, NULL); - } - ; - - -first_last : FIRST - { - $$ = new ast::FirstLastTypeIndex(ast::PEQ_FIRST); - } - | LAST - { - $$ = new ast::FirstLastTypeIndex(ast::PEQ_LAST); - } - ; - - function_table_block : FUNCTION_TABLE NAME_PTR "(" optional_argument_list ")" units { $$ = new ast::FunctionTableBlock($2, $4, $6); @@ -1614,20 +1559,20 @@ initial_statement : INITIAL1 statement_list "}" ; -solve_block : SOLVE NAME_PTR if_solution_error +solve_block : SOLVE NAME_PTR { - $$ = new ast::SolveBlock($2, NULL, NULL, $3); + $$ = new ast::SolveBlock($2, NULL, NULL); $$->set_token(*($2->get_token())); } - | SOLVE NAME_PTR USING METHOD if_solution_error + | SOLVE NAME_PTR USING METHOD { - $$ = new ast::SolveBlock($2, $4.clone(), NULL, $5); + $$ = new ast::SolveBlock($2, $4.clone(), NULL); $$->set_token(*($2->get_token())); } | - SOLVE NAME_PTR STEADYSTATE METHOD if_solution_error + SOLVE NAME_PTR STEADYSTATE METHOD { - $$ = new ast::SolveBlock($2, NULL, $4.clone(), $5); + $$ = new ast::SolveBlock($2, NULL, $4.clone()); $$->set_token(*($2->get_token())); } | SOLVE error @@ -1637,17 +1582,6 @@ solve_block : SOLVE NAME_PTR if_solution_error ; -if_solution_error : - { - $$ = nullptr; - } - | IFERROR statement_list "}" - { - $$ = $2; - } - ; - - optional_solvefor : { $$ = ast::NameVector(); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index a2b3f8cd13..0ef7717df9 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -115,10 +115,6 @@ std::vector to_string_vector(const NmodlType& obj) { properties.emplace_back("constant"); } - if (has_property(obj, NmodlType::partial_block)) { - properties.emplace_back("partial_block"); - } - if (has_property(obj, NmodlType::kinetic_block)) { properties.emplace_back("kinetic_block"); } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index d15600a484..0ca1b0ea1b 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -160,67 +160,64 @@ enum class NmodlType : enum_type { electrode_cur_var = 1L << 13, /// Argument Type - argument = 1L << 15, + argument = 1L << 14, /// Function Type - function_block = 1L << 16, + function_block = 1L << 15, /// Procedure Type - procedure_block = 1L << 17, + procedure_block = 1L << 16, /// Derivative Block - derivative_block = 1L << 18, + derivative_block = 1L << 17, /// Linear Block - linear_block = 1L << 19, + linear_block = 1L << 18, /// NonLinear Block - non_linear_block = 1L << 20, + non_linear_block = 1L << 19, /// constant variable - constant_var = 1L << 21, - - /// Partial Block - partial_block = 1L << 22, + constant_var = 1L << 20, /// Kinetic Block - kinetic_block = 1L << 23, + kinetic_block = 1L << 21, /// FunctionTable Block - function_table_block = 1L << 24, + function_table_block = 1L << 22, /// factor in unit block - factor_def = 1L << 25, + factor_def = 1L << 23, /// neuron variable accessible in mod file - extern_neuron_variable = 1L << 26, + extern_neuron_variable = 1L << 24, /// neuron solver methods and math functions - extern_method = 1L << 27, + extern_method = 1L << 25, /// state variable - state_var = 1L << 28, + state_var = 1L << 26, /// need to solve : used in solve statement - to_solve = 1L << 29, + to_solve = 1L << 27, /// ion type - useion = 1L << 30, + useion = 1L << 28, /// variable is used in table statement - table_statement_var = 1L << 31, + table_statement_var = 1L << 29, /// variable is used in table as assigned - table_assigned_var = 1L << 32, + table_assigned_var = 1L << 30, /// Discrete Block - discrete_block = 1L << 33, + discrete_block = 1L << 31, /// Define variable / macro - define = 1L << 34, + define = 1L << 32, /// Codegen specific variable - codegen_var = 1L << 35 + codegen_var = 1L << 33 }; template diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 2358951726..03109e07c7 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -306,10 +306,6 @@ void DefUseAnalyzeVisitor::visit_lin_equation(const ast::LinEquation& node) { visit_unsupported_node(node); } -void DefUseAnalyzeVisitor::visit_partial_boundary(const ast::PartialBoundary& node) { - visit_unsupported_node(node); -} - void DefUseAnalyzeVisitor::visit_from_statement(const ast::FromStatement& node) { visit_unsupported_node(node); } diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 9c307fcf0e..28f3a0a536 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -280,8 +280,6 @@ class DefUseAnalyzeVisitor: protected ConstAstVisitor { void visit_lin_equation(const ast::LinEquation& node) override; - void visit_partial_boundary(const ast::PartialBoundary& node) override; - void visit_from_statement(const ast::FromStatement& node) override; void visit_conserve(const ast::Conserve& node) override; diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 325ec86e32..47738bf291 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -39,7 +39,6 @@ bool LocalizeVisitor::node_for_def_use_analysis(const ast::Node& node) const { ast::AstNodeType::LINEAR_BLOCK, ast::AstNodeType::NON_LINEAR_BLOCK, ast::AstNodeType::DISCRETE_BLOCK, - ast::AstNodeType::PARTIAL_BLOCK, ast::AstNodeType::NET_RECEIVE_BLOCK, ast::AstNodeType::BA_BLOCK, ast::AstNodeType::FOR_NETCON, diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index f06495c6a4..e96972a7c8 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -374,10 +374,6 @@ void PerfVisitor::visit_discrete_block(const ast::DiscreteBlock& node) { measure_performance(node); } -void PerfVisitor::visit_partial_block(const ast::PartialBlock& node) { - measure_performance(node); -} - void PerfVisitor::visit_function_table_block(const ast::FunctionTableBlock& node) { measure_performance(node); } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 47f4eaf289..2fb9792034 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -213,8 +213,6 @@ class PerfVisitor: public ConstAstVisitor { void visit_discrete_block(const ast::DiscreteBlock& node) override; - void visit_partial_block(const ast::PartialBlock& node) override; - void visit_function_table_block(const ast::FunctionTableBlock& node) override; void visit_function_block(const ast::FunctionBlock& node) override; diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 4f71f1fa24..ed2a3b5ad6 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -49,14 +49,14 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 350"); + REQUIRE(ss.str() == " text at [1.1-4] type 346"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 350"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 346"); } } @@ -66,7 +66,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 357"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 353"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 9b023ec978..1a3c4a24dd 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -267,6 +267,6 @@ SCENARIO("Check if a NEURON block is parsed with correct location info in its to )"; parse_neuron_block_string(reindent_text(neuron_block), value); ss << value; - REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 302"); + REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 299"); } } diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 26202027f9..0a37847def 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -793,20 +793,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "solve_block_3", - { - "Solve statement with iferror block", - R"( - BREAKPOINT { - SOLVE states METHOD cnexp IFERROR { - a = 1 - } - } - )" - } - }, - { "solve_block_equation_1", { @@ -846,17 +832,19 @@ std::map const nmodl_valid_constructs{ { "solve_block_equation_3", { - "Solve statement with iferror block using EQUATION, generating BREAKPOINT", + "Solve statement using EQUATION, generating BREAKPOINT", R"( EQUATION { - SOLVE states METHOD cnexp IFERROR { + SOLVE states METHOD cnexp + { a = 1 } } )", R"( BREAKPOINT { - SOLVE states METHOD cnexp IFERROR { + SOLVE states METHOD cnexp + { a = 1 } } @@ -985,20 +973,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "partial_block_partial_equation_1", - { - "PARTIAL block and partial equation statements", - R"( - PARTIAL some_name { - ~ a' = a*DEL2(b)+c - ~ DEL abc[FIRST] = (a*b/c) - ~ abc[LAST] = (a*b/c) - } - )" - } - }, - { "linear_block_1", { From 7e19cafae53c2831f8bb0531a300c3cc8ddff41f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Sun, 2 Oct 2022 08:57:04 +0200 Subject: [PATCH 467/871] Remove MATCH, RESET, FORALL constructs (BlueBrain/nmodl#941) See neuronsimulator/nrnBlueBrain/nmodl#1957 NMODL Repo SHA: BlueBrain/nmodl@94ba2b29019c1e682f211e588e4b1815cb1538aa --- docs/nmodl/transpiler/language.rst | 6 -- docs/nmodl/transpiler/test_status.txt | 3 - src/nmodl/codegen/codegen_c_visitor.cpp | 1 - .../codegen/codegen_compatibility_visitor.cpp | 4 +- src/nmodl/language/code_generator.cmake | 4 - src/nmodl/language/nmodl.yaml | 39 --------- src/nmodl/lexer/nmodl_utils.cpp | 6 -- src/nmodl/lexer/token_mapping.cpp | 3 - src/nmodl/parser/nmodl.yy | 83 ------------------- .../visitors/local_var_rename_visitor.cpp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 4 - src/nmodl/visitors/perf_visitor.hpp | 2 - .../visitors/verbatim_var_rename_visitor.cpp | 2 +- .../transpiler/unit/modtoken/modtoken.cpp | 6 +- test/nmodl/transpiler/unit/parser/parser.cpp | 2 +- .../unit/utils/nmodl_constructs.cpp | 41 --------- 16 files changed, 7 insertions(+), 201 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index b989189951..fb93d78b9e 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -47,8 +47,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | CONSTANT | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| MATCH | yes | no | no | -+------------------------+-------------------+-------------------+---------------------+ | BEFORE | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | AFTER | yes | no | no | @@ -67,8 +65,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | Control Flow | +------------------------+-------------------+-------------------+---------------------+ -| FORALL | yes | yes | no | -+------------------------+-------------------+-------------------+---------------------+ | WHILE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | IF | yes | yes | no | @@ -107,8 +103,6 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | MUTEXUNLOCK | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| RESET | yes | no | no | -+------------------------+-------------------+-------------------+---------------------+ | CONSERVE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | COMPARTMENT | yes | yes | no | diff --git a/docs/nmodl/transpiler/test_status.txt b/docs/nmodl/transpiler/test_status.txt index a34534fa54..acc2216471 100644 --- a/docs/nmodl/transpiler/test_status.txt +++ b/docs/nmodl/transpiler/test_status.txt @@ -26,7 +26,6 @@ The following symbols are used in the document to descrive the status : |WITH | ✅ | |DISCRETE | ✅ | |FROM | ✅ | -|FORALL | ✅ | |TO | ✅ | |BY | ✅ | |WHILE | ✅ | @@ -64,8 +63,6 @@ The following symbols are used in the document to descrive the status : | \>= | ✅ | |VS | ✅ | |LAG | ✅ | -|RESET | ✅ | -|MATCH | ✅ | |MODEL_LEVEL | ✅ | |SWEEP | ✅ | |KINETIC | ✅ | diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 4e800013a4..62f522fd5e 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -473,7 +473,6 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { || node->is_else_statement() || node->is_from_statement() || node->is_verbatim() - || node->is_for_all_statement() || node->is_from_statement() || node->is_conductance_hint() || node->is_while_statement()) { diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 0f14466028..b7631c370b 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -18,9 +18,7 @@ namespace codegen { const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( - {{AstNodeType::MATCH_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, - {AstNodeType::DISCRETE_BLOCK, + {{AstNodeType::DISCRETE_BLOCK, &CodegenCompatibilityVisitor::return_error_with_name}, {AstNodeType::FUNCTION_TABLE_BLOCK, &CodegenCompatibilityVisitor::return_error_without_name}, diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index cd5eba498e..0efdbafe45 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -92,7 +92,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/external.hpp ${PROJECT_BINARY_DIR}/src/ast/factor_def.hpp ${PROJECT_BINARY_DIR}/src/ast/float.hpp - ${PROJECT_BINARY_DIR}/src/ast/for_all_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/for_netcon.hpp ${PROJECT_BINARY_DIR}/src/ast/from_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/function_block.hpp @@ -117,8 +116,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/local_list_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/local_var.hpp ${PROJECT_BINARY_DIR}/src/ast/lon_difuse.hpp - ${PROJECT_BINARY_DIR}/src/ast/match.hpp - ${PROJECT_BINARY_DIR}/src/ast/match_block.hpp ${PROJECT_BINARY_DIR}/src/ast/model.hpp ${PROJECT_BINARY_DIR}/src/ast/mutex_lock.hpp ${PROJECT_BINARY_DIR}/src/ast/mutex_unlock.hpp @@ -149,7 +146,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/reaction_operator.hpp ${PROJECT_BINARY_DIR}/src/ast/reaction_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/read_ion_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/reset.hpp ${PROJECT_BINARY_DIR}/src/ast/solution_expression.hpp ${PROJECT_BINARY_DIR}/src/ast/solve_block.hpp ${PROJECT_BINARY_DIR}/src/ast/state_block.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 97b200ef8c..7f4cc2fdd8 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -937,18 +937,6 @@ type: StatementBlock getter: {override: true} - - MatchBlock: - brief: "TODO" - nmodl: MATCH - members: - - matchs: - brief: "Vector of match statements" - type: Match - vector: true - separator: " " - prefix: {value: " { "} - suffix: {value: " }"} - - UnitBlock: brief: "TODO" nmodl: "UNITS " @@ -1204,16 +1192,6 @@ type: Expression prefix: {value: " "} - - Match: - brief: "TODO" - members: - - name: - brief: "TODO" - type: Identifier - - expression: - brief: "TODO" - type: Expression - optional: true - BABlockType: members: - value: @@ -1490,19 +1468,6 @@ type: StatementBlock getter: {override: true} - - ForAllStatement: - brief: "TODO" - nmodl: "FORALL " - members: - - name: - brief: "TODO" - type: Name - suffix: {value: " "} - - statement_block: - brief: "TODO" - type: StatementBlock - getter: {override: true} - - WhileStatement: brief: "TODO" nmodl: "WHILE " @@ -1581,10 +1546,6 @@ nmodl: MUTEXUNLOCK brief: "Represent MUTEXUNLOCK statement in NMODL" - - Reset: - nmodl: RESET - brief: "Represent RESET statement in NMODL" - - Conserve: nmodl: CONSERVE members: diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 9de4bd3ba8..57bdfa21f0 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -185,8 +185,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_EXTERNAL(token, pos); case Token::FOR_NETCONS: return Parser::make_FOR_NETCONS(token, pos); - case Token::FORALL1: - return Parser::make_FORALL1(token, pos); case Token::FROM: return Parser::make_FROM(token, pos); case Token::FUNCTION1: @@ -213,8 +211,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_LOCAL(token, pos); case Token::LONGDIFUS: return Parser::make_LONGDIFUS(token, pos); - case Token::MATCH: - return Parser::make_MATCH(token, pos); case Token::MODEL: return Parser::make_MODEL(token, pos); case Token::MODEL_LEVEL: @@ -243,8 +239,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_RANGE(token, pos); case Token::READ: return Parser::make_READ(token, pos); - case Token::RESET: - return Parser::make_RESET(token, pos); case Token::SOLVE: return Parser::make_SOLVE(token, pos); case Token::SOLVEFOR: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index dcc44b5f72..153b7b6b0f 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -62,7 +62,6 @@ const static std::map keywords = { {"STEP", Token::STEP}, {"WITH", Token::WITH}, {"FROM", Token::FROM}, - {"FORALL", Token::FORALL1}, {"TO", Token::TO}, {"BY", Token::BY}, {"if", Token::IF}, @@ -74,8 +73,6 @@ const static std::map keywords = { {"CONSERVE", Token::CONSERVE}, {"VS", Token::VS}, {"LAG", Token::LAG}, - {"RESET", Token::RESET}, - {"MATCH", Token::MATCH}, {"MODEL_LEVEL", Token::MODEL_LEVEL}, {"SWEEP", Token::SWEEP}, {"COMPARTMENT", Token::COMPARTMENT}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 4a7b402f91..a44a213a12 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -100,7 +100,6 @@ %token ELSE %token EQUATION %token EXTERNAL -%token FORALL1 %token FOR_NETCONS %token FROM %token FUNCTION1 @@ -116,7 +115,6 @@ %token LINEAR %token LOCAL %token LONGDIFUS -%token MATCH %token MODEL %token MODEL_LEVEL %token NETRECEIVE @@ -134,7 +132,6 @@ %token REACT1 %token REACTION %token READ -%token RESET %token SOLVE %token SOLVEFOR %token START1 @@ -256,7 +253,6 @@ %type optional_increment %type optional_start %type lag_statement -%type forall_statement %type parameter_assignment %type independent_definition %type dependent_definition @@ -269,9 +265,6 @@ %type watch %type for_netcon %type constant_statement -%type match_list -%type match -%type match_name %type reaction_statement %type conserve %type react @@ -325,7 +318,6 @@ %type initial_block %type kinetic_block %type linear_block -%type match_block %type net_receive_block %type neuron_block %type non_linear_block @@ -979,10 +971,6 @@ statement_type1 : from_statement { $$ = $1; } - | forall_statement - { - $$ = $1; - } | while_statement { $$ = $1; @@ -1027,14 +1015,6 @@ statement_type1 : from_statement { $$ = $1; } - | RESET - { - $$ = new ast::Reset(); - } - | match_block - { - $$ = new ast::ExpressionStatement($1); - } | table_statement { $$ = $1; @@ -1413,17 +1393,6 @@ optional_increment : ; -forall_statement : FORALL1 NAME_PTR statement_list "}" - { - $$ = new ast::ForAllStatement($2, $3); - } - | FORALL1 error - { - error(scanner.loc, "forall_statement"); - } - ; - - while_statement : WHILE "(" expression ")" statement_list "}" { $$ = new ast::WhileStatement($3, $5); @@ -1850,58 +1819,6 @@ lag_statement : LAG name BY NAME_PTR ; -match_block : MATCH "{" match_list "}" - { - $$ = new ast::MatchBlock($3); - ModToken block_token = $1 + $4; - $$->set_token(block_token); - } - ; - - -match_list : match - { - $$ = ast::MatchVector(); - $$.emplace_back($1); - } - | match_list match - { - $1.emplace_back($2); - $$ = $1; - } - ; - - -match : name - { - $$ = new ast::Match($1, NULL); - } - | match_name "(" expression ")" "=" expression - { - auto op = ast::BinaryOperator(ast::BOP_ASSIGN); - auto lhs = new ast::ParenExpression($3); - auto rhs = $6; - auto expression = new ast::BinaryExpression(lhs, op, rhs); - $$ = new ast::Match($1, expression); - } - | error - { - error(scanner.loc, "match "); - } - ; - - -match_name : name - { - $$ = $1; - } - | name "[" NAME_PTR "]" - { - $$ = new ast::IndexedName($1, $3); - } - ; - - unit_block : UNITS "{" unit_block_body "}" { $$ = new ast::UnitBlock($3); diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index 04778af171..aef8d0c089 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -31,7 +31,7 @@ void LocalVarRenameVisitor::visit_statement_block(ast::StatementBlock& node) { symtab = current_symtab; } - // Some statements like forall, from, while are of type expression statement type. + // Some statements like from, while are of type expression statement type. // These statements contain statement block but do not have symbol table. And hence // we push last non-null symbol table on the stack. symtab_stack.push(symtab); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index e96972a7c8..80111361b2 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -416,10 +416,6 @@ void PerfVisitor::visit_kinetic_block(const ast::KineticBlock& node) { measure_performance(node); } -void PerfVisitor::visit_match_block(const ast::MatchBlock& node) { - measure_performance(node); -} - /** Blocks like function can have multiple statement blocks and * blocks like net receive has nested initial blocks. Hence need * to maintain separate stack. diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 2fb9792034..b244589a7d 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -233,8 +233,6 @@ class PerfVisitor: public ConstAstVisitor { void visit_kinetic_block(const ast::KineticBlock& node) override; - void visit_match_block(const ast::MatchBlock& node) override; - /// certain constructs needs to be excluded from usage counting /// and hence need to provide empty implementations diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 2a44c094e2..f6b5b319d7 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -27,7 +27,7 @@ void VerbatimVarRenameVisitor::visit_statement_block(ast::StatementBlock& node) symtab = current_symtab; } - // some statements like forall, from, while are of type expression statement type. + // some statements like from, while are of type expression statement type. // These statements contain statement block but do not have symbol table. And hence // we push last non-null symbol table on the stack. symtab_stack.push(symtab); diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index ed2a3b5ad6..9eb4881edf 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -49,14 +49,14 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 346"); + REQUIRE(ss.str() == " text at [1.1-4] type 343"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 346"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 343"); } } @@ -66,7 +66,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 353"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 350"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 1a3c4a24dd..68b8216fd5 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -267,6 +267,6 @@ SCENARIO("Check if a NEURON block is parsed with correct location info in its to )"; parse_neuron_block_string(reindent_text(neuron_block), value); ss << value; - REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 299"); + REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 297"); } } diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 0a37847def..fed50cc0fb 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -626,21 +626,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "forall_statement_1", - { - "FORALL statement", - R"( - INITIAL { - FORALL some_name { - a = 1 - tau = 2.1 - } - } - )" - } - }, - { "while_statement_1", { @@ -947,32 +932,6 @@ std::map const nmodl_valid_constructs{ } }, - { - "reset_statement_1", - { - "RESET statement", - R"( - PROCEDURE lates() { - RESET - } - )" - } - }, - - { - "match_block_1", - { - "MATCH block", - R"( - PROCEDURE lates() { - MATCH { name1 } - MATCH { name1 name2 } - MATCH { name1[INDEX](expr1+expr2) = (expr3+expr4) } - } - )" - } - }, - { "linear_block_1", { From 80d3bf750c9af10b6ccfc9a3a9e86862a4e6c0a7 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Sun, 2 Oct 2022 10:37:51 +0200 Subject: [PATCH 468/871] Remove unnecessary NMODL constructs: MATCH, RESET, FORALL, MODEL_LEVEL, variables in THREADSAFE (BlueBrain/nmodl#942) Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@2dce6916ef75c391affad01c8a40c2b136cb8d21 --- docs/nmodl/transpiler/test_status.txt | 1 - src/nmodl/language/code_generator.cmake | 1 - src/nmodl/language/nmodl.yaml | 15 -------- src/nmodl/lexer/nmodl_utils.cpp | 2 - src/nmodl/lexer/token_mapping.cpp | 1 - src/nmodl/parser/nmodl.yy | 37 +------------------ .../transpiler/unit/modtoken/modtoken.cpp | 6 +-- test/nmodl/transpiler/unit/parser/parser.cpp | 2 +- .../unit/utils/nmodl_constructs.cpp | 19 +--------- 9 files changed, 7 insertions(+), 77 deletions(-) diff --git a/docs/nmodl/transpiler/test_status.txt b/docs/nmodl/transpiler/test_status.txt index acc2216471..89da4e595c 100644 --- a/docs/nmodl/transpiler/test_status.txt +++ b/docs/nmodl/transpiler/test_status.txt @@ -63,7 +63,6 @@ The following symbols are used in the document to descrive the status : | \>= | ✅ | |VS | ✅ | |LAG | ✅ | -|MODEL_LEVEL | ✅ | |SWEEP | ✅ | |KINETIC | ✅ | |CONSERVE | ✅ | diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 0efdbafe45..817f22c676 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -155,7 +155,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/suffix.hpp ${PROJECT_BINARY_DIR}/src/ast/table_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/thread_safe.hpp - ${PROJECT_BINARY_DIR}/src/ast/threadsafe_var.hpp ${PROJECT_BINARY_DIR}/src/ast/unary_expression.hpp ${PROJECT_BINARY_DIR}/src/ast/unary_operator.hpp ${PROJECT_BINARY_DIR}/src/ast/unit.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 7f4cc2fdd8..0024d4e436 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -414,14 +414,6 @@ type: Name node_name: true - - ThreadsafeVar: - brief: "TODO" - members: - - name: - brief: "TODO" - type: Name - node_name: true - - Block: brief: "Base class for all block scoped nodes" description: | @@ -1815,13 +1807,6 @@ - ThreadSafe: nmodl: THREADSAFE - members: - - variables: - brief: "Vector of thread safe variables" - type: ThreadsafeVar - vector: true - separator: ", " - prefix: {value: " "} brief: "Represents THREADSAFE statement in NMODL" - Verbatim: diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 57bdfa21f0..72695d9743 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -213,8 +213,6 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_LONGDIFUS(token, pos); case Token::MODEL: return Parser::make_MODEL(token, pos); - case Token::MODEL_LEVEL: - return Parser::make_MODEL_LEVEL(token, pos); case Token::NETRECEIVE: return Parser::make_NETRECEIVE(token, pos); case Token::NEURON: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 153b7b6b0f..0a8a25d459 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -73,7 +73,6 @@ const static std::map keywords = { {"CONSERVE", Token::CONSERVE}, {"VS", Token::VS}, {"LAG", Token::LAG}, - {"MODEL_LEVEL", Token::MODEL_LEVEL}, {"SWEEP", Token::SWEEP}, {"COMPARTMENT", Token::COMPARTMENT}, {"LONGITUDINAL_DIFFUSION", Token::LONGDIFUS}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index a44a213a12..669ba87e0c 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -116,7 +116,6 @@ %token LOCAL %token LONGDIFUS %token MODEL -%token MODEL_LEVEL %token NETRECEIVE %token NEURON %token NONLIN1 @@ -296,8 +295,6 @@ %type pointer_var_list %type bbcore_pointer_var_list %type external_var_list -%type optional_threadsafe_var_list -%type threadsafe_var_list %type valence %type initial_statement %type conductance @@ -426,13 +423,6 @@ all : { $1->emplace_back_node($2); $$ = $1; } - | all MODEL_LEVEL INTEGER_PTR declare - { - /** todo This is discussed with Michael Hines. Model level was inserted - * by merge program which is no longer exist. This was to avoid the name - * collision in case of include. Idea was to have some kind of namespace! - */ - } | all procedure { $1->emplace_back_node($2); @@ -2004,9 +1994,9 @@ neuron_statement : $1.emplace_back(new ast::External($3)); $$ = $1; } - | neuron_statement THREADSAFE optional_threadsafe_var_list + | neuron_statement THREADSAFE { - $1.emplace_back(new ast::ThreadSafe($3)); + $1.emplace_back(new ast::ThreadSafe()); $$ = $1; } | neuron_statement REPRESENTS ONTOLOGY_ID @@ -2224,29 +2214,6 @@ external_var_list : NAME_PTR ; -optional_threadsafe_var_list : - { - $$ = ast::ThreadsafeVarVector(); - } - | threadsafe_var_list - { - $$ = $1; - } - ; - - -threadsafe_var_list : NAME_PTR - { - $$ = ast::ThreadsafeVarVector(); - $$.emplace_back(new ast::ThreadsafeVar($1)); - } - | threadsafe_var_list "," NAME_PTR - { - $1.emplace_back(new ast::ThreadsafeVar($3)); - $$ = $1; - } - ; - INTEGER_PTR : INTEGER { $$ = $1.clone(); diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 9eb4881edf..0a11436add 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -49,14 +49,14 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 343"); + REQUIRE(ss.str() == " text at [1.1-4] type 342"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 343"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 342"); } } @@ -66,7 +66,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 350"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 349"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 68b8216fd5..0ab51a2bf7 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -267,6 +267,6 @@ SCENARIO("Check if a NEURON block is parsed with correct location info in its to )"; parse_neuron_block_string(reindent_text(neuron_block), value); ss << value; - REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 297"); + REQUIRE(ss.str() == " NEURON at [1.1-5.1] type 296"); } } diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index fed50cc0fb..3e4401c5c3 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -102,14 +102,6 @@ std::map const nmodl_invalid_constructs{ } }, - { - "model_level_1", - { - "Model level without any block", - "MODEL_LEVEL 2" - } - }, - { "verbatim_block_1", { @@ -278,15 +270,6 @@ std::map const nmodl_valid_constructs{ )" } }, -/** \todo : MODEL_LEVEL is not handled in parser as it's deprecated - { - "model_level_1", - { - "Model level followed by block", - "MODEL_LEVEL 2 NEURON {}" - } - }, -*/ { "verbatim_block_1", @@ -1128,7 +1111,7 @@ std::map const nmodl_valid_constructs{ R"( NEURON { THREADSAFE - THREADSAFE a, b + THREADSAFE } )" } From 8ff98ec21b03c87cd173538e6bb90cdee6d7eadb Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Fri, 7 Oct 2022 14:53:40 +0200 Subject: [PATCH 469/871] Updates on NMODL language table (BlueBrain/nmodl#944) * Added not mentioned language constructs and updated the ones that showed as implemented but didn't have any tests to non implemented to check them NMODL Repo SHA: BlueBrain/nmodl@179c9d88f2c1f42e9638783794e27cbe78f0269d --- docs/nmodl/transpiler/language.rst | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index fb93d78b9e..ff1b25d3a1 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -27,6 +27,8 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | LINEAR | yes | yes | yes | +------------------------+-------------------+-------------------+---------------------+ +| NONLINEAR | yes | yes | yes | ++------------------------+-------------------+-------------------+---------------------+ | FUNCTION | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | PROCEDURE | yes | yes | no | @@ -93,7 +95,7 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | CONDUCTANCE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ -| PROTECT | yes | yes | no | +| PROTECT | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | FROM | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ @@ -109,16 +111,28 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | LONGITUDINAL_DIFFUSION | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| LAG | yes | yes | no | +| LAG | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ | TABLE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | USEION | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ +| READ | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| WRITE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| VALENCE | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| REPRESENTS | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ | NONSPECIFIC_CURRENT | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | ELECTRODE_CURRENT | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ +| SUFFIX | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ +| POINT_PROCESS | yes | yes | no | ++------------------------+-------------------+-------------------+---------------------+ | RANGE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | GLOBAL | yes | yes | no | From e95d4fdc5753f4213b6a4caa7e48b7e51ecfba73 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 11 Oct 2022 14:42:05 +0200 Subject: [PATCH 470/871] Remove CUDA accelerator (BlueBrain/nmodl#962) NMODL Repo SHA: BlueBrain/nmodl@a1412c4e73ca1f99cc46aa6a5f0837c105e4fe7b --- README.md | 5 +- src/nmodl/codegen/CMakeLists.txt | 1 - src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_c_visitor.cpp | 5 +- src/nmodl/codegen/codegen_c_visitor.hpp | 2 +- src/nmodl/codegen/codegen_cuda_visitor.cpp | 227 --------------------- src/nmodl/codegen/codegen_cuda_visitor.hpp | 125 ------------ src/nmodl/main.cpp | 18 -- 8 files changed, 6 insertions(+), 379 deletions(-) delete mode 100644 src/nmodl/codegen/codegen_cuda_visitor.cpp delete mode 100644 src/nmodl/codegen/codegen_cuda_visitor.hpp diff --git a/README.md b/README.md index 667a4fc3fe..d3b34fcf41 100644 --- a/README.md +++ b/README.md @@ -183,10 +183,10 @@ The NMODL Framework provides rich model introspection and analysis capabilities To understand how you can write your own introspection and analysis tool, see [this tutorial](docs/notebooks/nmodl-python-tutorial.ipynb). -Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, C++, OpenACC, OpenMP, CUDA and ISPC backends are implemented and one can choose these backends on command line as: +Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, C++, OpenACC, OpenMP and ISPC backends are implemented and one can choose these backends on command line as: ``` -$ nmodl expsyn.mod host --ispc acc --cuda sympy --analytic +$ nmodl expsyn.mod host --ispc acc sympy --analytic ``` Here is an example of generated [ISPC](https://ispc.github.io/) kernel for DERIVATIVE block : @@ -238,7 +238,6 @@ acc Accelerator code backends Options: --oacc C/C++ backend with OpenACC (false) - --cuda C/C++ backend with CUDA (false) sympy SymPy based analysis and optimizations diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index d261fa7acf..9a1cf099d7 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -7,7 +7,6 @@ add_library( codegen_transform_visitor.cpp codegen_c_visitor.cpp codegen_compatibility_visitor.cpp - codegen_cuda_visitor.cpp codegen_helper_visitor.cpp codegen_info.cpp codegen_ispc_visitor.cpp diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 0c8838475c..651cfe6014 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -117,7 +117,7 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { * @todo : we need to implement proper error handling mechanism to propogate errors * from GPU to CPU. For example, error code can be returned like original * neuron implementation. For now we use `assert(0==1)` pattern which is - * used for OpenACC/CUDA. + * used for OpenACC. */ void CodegenAccVisitor::print_abort_routine() const { printer->add_newline(2); diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 62f522fd5e..e94d165d04 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -733,7 +733,7 @@ bool CodegenCVisitor::ion_variable_struct_required() const { /** * \details This can be override in the backend. For example, parameters can be constant * except in INITIAL block where they are set to 0. As initial block is/can be - * executed on c/cpu backend, gpu/cuda backend can mark the parameter as constant. + * executed on c/cpu backend, gpu backend can mark the parameter as constant. */ bool CodegenCVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); @@ -1132,8 +1132,7 @@ bool CodegenCVisitor::nrn_cur_reduction_loop_required() { /** - * \details For CPU backend we iterate over all node counts. For cuda we use thread - * index to check if block needs to be executed or not. + * \details For CPU backend we iterate over all node counts. */ void CodegenCVisitor::print_channel_iteration_block_begin(BlockType type) { print_channel_iteration_block_parallel_hint(type); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 3a4fc39c13..2f1c16eec7 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -271,7 +271,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { codegen::CodegenInfo info; /** - * Code printer object for target (C, CUDA, ispc, ...) + * Code printer object for target (C, ispc, ...) */ std::shared_ptr target_printer; diff --git a/src/nmodl/codegen/codegen_cuda_visitor.cpp b/src/nmodl/codegen/codegen_cuda_visitor.cpp deleted file mode 100644 index 8f58f04917..0000000000 --- a/src/nmodl/codegen/codegen_cuda_visitor.cpp +++ /dev/null @@ -1,227 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#include "codegen/codegen_cuda_visitor.hpp" -#include "symtab/symbol_table.hpp" -#include "utils/string_utils.hpp" - -namespace nmodl { -namespace codegen { - -using symtab::syminfo::NmodlType; - -/****************************************************************************************/ -/* Routines must be overloaded in backend */ -/****************************************************************************************/ - -// TODO: Update of dt and other variables on device via CUDA backend is not handled -// yet. We need to review device code generation in OpenACC backend. -void CodegenCudaVisitor::print_dt_update_to_device() const { - throw std::runtime_error("CUDA backend doesn't handled dt update on GPU"); -} - -/** - * As initial block is/can be executed on c/cpu backend, gpu/cuda - * backend can mark the parameter as constant even if they have - * write count > 0 (typically due to initial block). - */ -bool CodegenCudaVisitor::is_constant_variable(const std::string& name) const { - auto symbol = program_symtab->lookup_in_scope(name); - bool is_constant = false; - if (symbol != nullptr) { - if (symbol->has_any_property(NmodlType::read_ion_var)) { - is_constant = true; - } - if (symbol->has_any_property(NmodlType::param_assign)) { - is_constant = true; - } - } - return is_constant; -} - - -std::string CodegenCudaVisitor::compute_method_name(BlockType type) const { - if (type == BlockType::Initial) { - return method_name("nrn_init"); - } - if (type == BlockType::State) { - return method_name("cuda_nrn_state"); - } - if (type == BlockType::Equation) { - return method_name("cuda_nrn_cur"); - } - throw std::runtime_error("compute_method_name not implemented"); -} - - -void CodegenCudaVisitor::print_atomic_op(const std::string& lhs, - const std::string& op, - const std::string& rhs) const { - std::string function; - if (op == "+") { - function = "atomicAdd"; - } else if (op == "-") { - function = "atomicSub"; - } else { - throw std::runtime_error(fmt::format("CUDA backend error : {} not supported", op)); - } - printer->fmt_line("{}(&{}, {});", function, lhs, rhs); -} - - -void CodegenCudaVisitor::print_backend_includes() { - printer->add_line("#include "); -} - - -std::string CodegenCudaVisitor::backend_name() const { - return "C-CUDA (api-compatibility)"; -} - - -void CodegenCudaVisitor::print_global_method_annotation() { - printer->add_line("__global__"); -} - - -void CodegenCudaVisitor::print_device_method_annotation() { - printer->add_line("__device__"); -} - - -void CodegenCudaVisitor::print_nrn_cur_matrix_shadow_update() { - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - stringutils::remove_character(rhs_op, '='); - stringutils::remove_character(d_op, '='); - print_atomic_op("vec_rhs[node_id]", rhs_op, "rhs"); - print_atomic_op("vec_d[node_id]", d_op, "g"); -} - -void CodegenCudaVisitor::print_fast_imem_calculation() { - if (!info.electrode_current) { - return; - } - - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - stringutils::remove_character(rhs_op, '='); - stringutils::remove_character(d_op, '='); - printer->start_block("if (nt->nrn_fast_imem)"); - print_atomic_reduction_pragma(); - print_atomic_op("nt->nrn_fast_imem->nrn_sav_rhs[node_id]", rhs_op, "rhs"); - print_atomic_reduction_pragma(); - print_atomic_op("nt->nrn_fast_imem->nrn_sav_d[node_id]", d_op, "g"); - printer->end_block(1); -} - -/* - * Depending on the backend, print condition/loop for iterating over channels - * - * For GPU backend its thread id less than total channel instances. Below we - * assume we launch 1-d grid. - */ -void CodegenCudaVisitor::print_channel_iteration_block_begin(BlockType /* type */) { - printer->add_line("int id = blockIdx.x * blockDim.x + threadIdx.x;"); - printer->start_block("if (id < end) "); -} - - -void CodegenCudaVisitor::print_channel_iteration_block_end() { - printer->end_block(); - printer->add_newline(); -} - - -void CodegenCudaVisitor::print_nrn_cur_matrix_shadow_reduction() { - // do nothing -} - - -void CodegenCudaVisitor::print_rhs_d_shadow_variables() { - // do nothing -} - - -bool CodegenCudaVisitor::nrn_cur_reduction_loop_required() { - return false; -} - - -void CodegenCudaVisitor::print_backend_namespace_start() { - printer->add_newline(1); - printer->start_block("namespace cuda"); -} - - -void CodegenCudaVisitor::print_backend_namespace_stop() { - printer->end_block(); - printer->add_newline(); -} - - -void CodegenCudaVisitor::print_compute_functions() { - print_top_verbatim_blocks(); - print_function_prototypes(); - - for (const auto& procedure: info.procedures) { - print_procedure(*procedure); - } - - for (const auto& function: info.functions) { - print_function(*function); - } - - print_net_send_buffering(); - print_net_receive_kernel(); - print_net_receive_buffering(); - print_nrn_cur(); - print_nrn_state(); -} - - -void CodegenCudaVisitor::print_wrapper_routine(std::string wrapper_function, BlockType type) { - static const auto args = "NrnThread* nt, Memb_list* ml, int type"; - wrapper_function = method_name(wrapper_function); - auto compute_function = compute_method_name(type); - - printer->add_newline(2); - printer->fmt_start_block("void {}({})", wrapper_function, args); - printer->add_line("int nodecount = ml->nodecount;"); - printer->add_line("int nthread = 256;"); - printer->add_line("int nblock = (nodecount+nthread-1)/nthread;"); - printer->fmt_line("{}<<>>(nt, ml, type);", compute_function); - printer->add_line("cudaDeviceSynchronize();"); - printer->end_block(); - printer->add_newline(); -} - - -void CodegenCudaVisitor::codegen_wrapper_routines() { - print_wrapper_routine("nrn_cur", BlockType::Equation); - print_wrapper_routine("nrn_state", BlockType::State); -} - - -void CodegenCudaVisitor::print_codegen_routines() { - codegen = true; - print_backend_info(); - print_headers_include(); - print_namespace_begin(); - - print_data_structures(true); - print_common_getters(); - - print_compute_functions(); - - codegen_wrapper_routines(); - - print_namespace_end(); -} - -} // namespace codegen -} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cuda_visitor.hpp b/src/nmodl/codegen/codegen_cuda_visitor.hpp deleted file mode 100644 index 6b2c3fc202..0000000000 --- a/src/nmodl/codegen/codegen_cuda_visitor.hpp +++ /dev/null @@ -1,125 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -/** - * \file - * \brief \copybrief nmodl::codegen::CodegenCudaVisitor - */ - -#include "codegen/codegen_c_visitor.hpp" - -namespace nmodl { -namespace codegen { - -/** - * @addtogroup codegen_backends - * @{ - */ - -/** - * \class CodegenCudaVisitor - * \brief %Visitor for printing CUDA backend - */ -class CodegenCudaVisitor: public CodegenCVisitor { - void print_atomic_op(const std::string& lhs, - const std::string& op, - const std::string& rhs) const; - - protected: - /// name of the code generation backend - std::string backend_name() const override; - - /// if variable is qualified as constant - bool is_constant_variable(const std::string& name) const override; - - /// return name of main compute kernels - std::string compute_method_name(BlockType type) const override; - - - /// common includes : standard c/c++, coreneuron and backend specific - void print_backend_includes() override; - - - /// update to matrix elements with/without shadow vectors - void print_nrn_cur_matrix_shadow_update() override; - - /// fast membrane current calculation - void print_fast_imem_calculation() override; - - /// reduction to matrix elements from shadow vectors - void print_nrn_cur_matrix_shadow_reduction() override; - - - /// setup method for setting matrix shadow vectors - void print_rhs_d_shadow_variables() override; - - - /// if reduction block in nrn_cur required - bool nrn_cur_reduction_loop_required() override; - - - /// backend specific channel instance iteration block start - void print_channel_iteration_block_begin(BlockType type) override; - - - /// backend specific channel instance iteration block end - void print_channel_iteration_block_end() override; - - - /// start of backend namespace - void print_backend_namespace_start() override; - - - /// end of backend namespace - void print_backend_namespace_stop() override; - - - /// backend specific global method annotation - void print_global_method_annotation() override; - - - /// backend specific device method annotation - void print_device_method_annotation() override; - - - /// all compute functions for every backend - void print_compute_functions() override; - - - /// print wrapper function that calls cuda kernel - void print_wrapper_routine(std::string wrapper_function, BlockType type); - - // update dt from host to device - void print_dt_update_to_device() const override; - - /// wrapper/caller routines for nrn_state and nrn_cur - void codegen_wrapper_routines(); - - - /// entry point to code generation - void print_codegen_routines() override; - - public: - CodegenCudaVisitor(const std::string& mod_file, - const std::string& output_dir, - const std::string& float_type, - const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies, ".cu") {} - - CodegenCudaVisitor(const std::string& mod_file, - std::ostream& stream, - const std::string& float_type, - const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} -}; - -/** @} */ // end of codegen_backends - -} // namespace codegen -} // namespace nmodl diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 1e2a43871f..1fb706655e 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -14,7 +14,6 @@ #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_compatibility_visitor.hpp" -#include "codegen/codegen_cuda_visitor.hpp" #include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_transform_visitor.hpp" #include "config/config.h" @@ -79,9 +78,6 @@ int main(int argc, const char* argv[]) { /// true if c code with openacc to be generated bool oacc_backend(false); - /// true if cuda code to be generated - bool cuda_backend(false); - /// true if sympy should be used for solving ODEs analytically bool sympy_analytic(false); @@ -197,11 +193,6 @@ int main(int argc, const char* argv[]) { oacc_backend, fmt::format("C/C++ backend with OpenACC ({})", oacc_backend)) ->ignore_case(); - acc_opt - ->add_flag("--cuda", - cuda_backend, - fmt::format("C/C++ backend with CUDA ({})", cuda_backend)) - ->ignore_case(); // clang-format off auto sympy_opt = app.add_subcommand("sympy", "SymPy based analysis and optimizations")->ignore_case(); @@ -575,15 +566,6 @@ int main(int argc, const char* argv[]) { optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } - - if (cuda_backend) { - logger->info("Running CUDA backend code generator"); - CodegenCudaVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); - visitor.visit_program(*ast); - } } } From 8492ee181bd533e47440273bb77f6004a538fc47 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 14 Oct 2022 11:39:50 +0200 Subject: [PATCH 471/871] Remove ISPC codegen backend (BlueBrain/nmodl#963) NMODL Repo SHA: BlueBrain/nmodl@e185f61b0c26a33401c2c3be5300e72e6bb75b0f --- README.md | 24 +- docs/nmodl/transpiler/Doxyfile.in | 4 +- src/nmodl/codegen/CMakeLists.txt | 9 - src/nmodl/codegen/codegen_c_visitor.cpp | 84 +- src/nmodl/codegen/codegen_c_visitor.hpp | 44 +- src/nmodl/codegen/codegen_ispc_visitor.cpp | 793 ------------------ src/nmodl/codegen/codegen_ispc_visitor.hpp | 251 ------ src/nmodl/codegen/codegen_utils.cpp | 50 +- src/nmodl/codegen/codegen_utils.hpp | 6 +- src/nmodl/codegen/fast_math.hpp | 3 - src/nmodl/codegen/fast_math.ispc | 234 ------ src/nmodl/main.cpp | 34 +- src/nmodl/visitors/ispc_rename_visitor.hpp | 90 -- .../integration/mod/ispc_rename.mod | 22 - test/nmodl/transpiler/unit/CMakeLists.txt | 6 +- .../transpiler/unit/codegen/codegen_ispc.cpp | 196 ----- .../transpiler/unit/codegen/codegen_utils.cpp | 76 -- .../transpiler/unit/visitor/ispc_rename.cpp | 93 -- 18 files changed, 31 insertions(+), 1988 deletions(-) delete mode 100644 src/nmodl/codegen/codegen_ispc_visitor.cpp delete mode 100644 src/nmodl/codegen/codegen_ispc_visitor.hpp delete mode 100644 src/nmodl/codegen/fast_math.ispc delete mode 100644 src/nmodl/visitors/ispc_rename_visitor.hpp delete mode 100644 test/nmodl/transpiler/integration/mod/ispc_rename.mod delete mode 100644 test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp delete mode 100644 test/nmodl/transpiler/unit/visitor/ispc_rename.cpp diff --git a/README.md b/README.md index d3b34fcf41..2480de137d 100644 --- a/README.md +++ b/README.md @@ -183,29 +183,10 @@ The NMODL Framework provides rich model introspection and analysis capabilities To understand how you can write your own introspection and analysis tool, see [this tutorial](docs/notebooks/nmodl-python-tutorial.ipynb). -Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, C++, OpenACC, OpenMP and ISPC backends are implemented and one can choose these backends on command line as: +Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, C++, OpenACC and OpenMP backends are implemented and one can choose these backends on command line as: ``` -$ nmodl expsyn.mod host --ispc acc sympy --analytic -``` - -Here is an example of generated [ISPC](https://ispc.github.io/) kernel for DERIVATIVE block : - -```c++ -export void nrn_state_ExpSyn(uniform ExpSyn_Instance* uniform inst, uniform NrnThread* uniform nt ...) { - uniform int nodecount = ml->nodecount; - const int* uniform node_index = ml->nodeindices; - const double* uniform voltage = nt->actual_v; - - int uniform start = 0; - int uniform end = nodecount; - - foreach (id = start ... end) { - int node_id = node_index[id]; - double v = voltage[node_id]; - inst->g[id] = inst->g[id] * vexp( -nt->dt / inst->tau[id]); - } -} +$ nmodl expsyn.mod sympy --analytic ``` To know more about code generation backends, [see here](https://bluebrain.github.io/nmodl/html/doxygen/group__codegen__backends.html). NMODL Framework provides number of options (for code generation, optimization passes and ODE solver) which can be listed as: @@ -232,7 +213,6 @@ host HOST/CPU code backends Options: --c C/C++ backend (true) - --ispc C/C++ backend with ISPC (false) acc Accelerator code backends diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index 8a9ec48c67..96ffffcda3 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -328,8 +328,7 @@ OPTIMIZE_OUTPUT_SLICE = NO # # Note see also the list of default file extension mappings. -EXTENSION_MAPPING = .yaml=Python \ - .ispc=C +EXTENSION_MAPPING = .yaml=Python # If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments # according to the Markdown format, which allows for more readable @@ -904,7 +903,6 @@ FILE_PATTERNS = *.c \ *.cpp \ *.c++ \ *.ipp \ - *.ispc \ *.h \ *.hh \ *.hxx \ diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 9a1cf099d7..12ad6ceedd 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -9,19 +9,10 @@ add_library( codegen_compatibility_visitor.cpp codegen_helper_visitor.cpp codegen_info.cpp - codegen_ispc_visitor.cpp codegen_utils.cpp) add_dependencies(codegen lexer util visitor) target_link_libraries(codegen PRIVATE util) # copy to build directory to make usable from build directory -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc - ${CMAKE_BINARY_DIR}/include/nmodl/fast_math.ispc COPYONLY) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.hpp ${CMAKE_BINARY_DIR}/include/nmodl/fast_math.hpp COPYONLY) - -# ============================================================================= -# Install include files -# ============================================================================= -install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/fast_math.ispc - DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include/nmodl) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index e94d165d04..96e0a413d8 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1037,18 +1037,6 @@ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { } -void CodegenCVisitor::print_channel_iteration_tiling_block_begin(BlockType /* type */) { - // no tiling for cpu backend, just get loop bounds - printer->add_line("int start = 0;"); - printer->add_line("int end = nodecount;"); -} - - -void CodegenCVisitor::print_channel_iteration_tiling_block_end() { - // backend specific, do nothing -} - - void CodegenCVisitor::print_deriv_advance_flag_transfer_to_device() const { // backend specific, do nothing } @@ -1131,20 +1119,6 @@ bool CodegenCVisitor::nrn_cur_reduction_loop_required() { } -/** - * \details For CPU backend we iterate over all node counts. - */ -void CodegenCVisitor::print_channel_iteration_block_begin(BlockType type) { - print_channel_iteration_block_parallel_hint(type); - printer->start_block("for (int id = start; id < end; id++)"); -} - - -void CodegenCVisitor::print_channel_iteration_block_end() { - printer->end_block(1); -} - - void CodegenCVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { printer->fmt_line("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); @@ -1186,11 +1160,6 @@ void CodegenCVisitor::print_atomic_reduction_pragma() { } -void CodegenCVisitor::print_shadow_reduction_block_begin() { - printer->start_block("for (int id = start; id < end; id++)"); -} - - void CodegenCVisitor::print_shadow_reduction_statements() { for (const auto& statement: shadow_statements) { print_atomic_reduction_pragma(); @@ -1202,11 +1171,6 @@ void CodegenCVisitor::print_shadow_reduction_statements() { } -void CodegenCVisitor::print_shadow_reduction_block_end() { - printer->end_block(1); -} - - void CodegenCVisitor::print_device_method_annotation() { // backend specific, nothing for cpu } @@ -1295,7 +1259,6 @@ std::string CodegenCVisitor::ptr_type_qualifier() { return "__restrict__ "; } -/// Useful in ispc so that variables in the global struct get "uniform " std::string CodegenCVisitor::global_var_struct_type_qualifier() { return ""; } @@ -3472,17 +3435,16 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_dt_update_to_device(); } - print_channel_iteration_tiling_block_begin(BlockType::Initial); - print_channel_iteration_block_begin(BlockType::Initial); + print_channel_iteration_block_parallel_hint(BlockType::Initial); + printer->start_block("for (int id = 0; id < nodecount; id++)"); if (info.net_receive_node != nullptr) { printer->fmt_line("{} = -1e20;", get_variable_name("tsave")); } print_initial_block(info.initial_node); - print_channel_iteration_block_end(); + printer->end_block(1); print_shadow_reduction_statements(); - print_channel_iteration_tiling_block_end(); if (!info.changed_dt.empty()) { printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); @@ -3531,8 +3493,8 @@ void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t bl printer->fmt_line("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id); print_global_function_common_code(BlockType::BeforeAfter, function_name); - print_channel_iteration_tiling_block_begin(BlockType::BeforeAfter); - print_channel_iteration_block_begin(BlockType::BeforeAfter); + print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter); + printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); @@ -3557,8 +3519,7 @@ void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t bl } /// loop end including data annotation block - print_channel_iteration_block_end(); - print_channel_iteration_tiling_block_end(); + printer->end_block(1); printer->end_block(1); print_kernel_data_present_annotation_block_end(); @@ -3659,8 +3620,8 @@ void CodegenCVisitor::print_watch_check() { printer->add_newline(2); printer->add_line("/** routine to check watch activation */"); print_global_function_common_code(BlockType::Watch); - print_channel_iteration_tiling_block_begin(BlockType::Watch); - print_channel_iteration_block_begin(BlockType::Watch); + print_channel_iteration_block_parallel_hint(BlockType::Watch); + printer->start_block("for (int id = 0; id < nodecount; id++)"); if (info.is_voltage_used_by_watch_statements()) { printer->add_line("int node_id = node_index[id];"); @@ -3718,9 +3679,8 @@ void CodegenCVisitor::print_watch_check() { // end block 1 } - print_channel_iteration_block_end(); + printer->end_block(1); print_send_event_move(); - print_channel_iteration_tiling_block_end(); print_kernel_data_present_annotation_block_end(); printer->end_block(1); codegen = false; @@ -4304,8 +4264,8 @@ void CodegenCVisitor::print_nrn_state() { printer->add_newline(2); printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); - print_channel_iteration_tiling_block_begin(BlockType::State); - print_channel_iteration_block_begin(BlockType::State); + print_channel_iteration_block_parallel_hint(BlockType::State); + printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); @@ -4338,13 +4298,12 @@ void CodegenCVisitor::print_nrn_state() { auto text = process_shadow_update_statement(statement, BlockType::State); printer->add_line(text); } - print_channel_iteration_block_end(); + printer->end_block(1); if (!shadow_statements.empty()) { - print_shadow_reduction_block_begin(); + printer->start_block("for (int id = 0; id < nodecount; id++)"); print_shadow_reduction_statements(); - print_shadow_reduction_block_end(); + printer->end_block(1); } - print_channel_iteration_tiling_block_end(); print_kernel_data_present_annotation_block_end(); printer->end_block(1); @@ -4499,7 +4458,7 @@ void CodegenCVisitor::print_fast_imem_calculation() { printer->start_block("if (nt->nrn_fast_imem)"); if (nrn_cur_reduction_loop_required()) { - print_shadow_reduction_block_begin(); + printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); } print_atomic_reduction_pragma(); @@ -4507,7 +4466,7 @@ void CodegenCVisitor::print_fast_imem_calculation() { print_atomic_reduction_pragma(); printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d); if (nrn_cur_reduction_loop_required()) { - print_shadow_reduction_block_end(); + printer->end_block(1); } printer->end_block(1); } @@ -4525,24 +4484,23 @@ void CodegenCVisitor::print_nrn_cur() { printer->add_newline(2); printer->add_line("/** update current */"); print_global_function_common_code(BlockType::Equation); - print_channel_iteration_tiling_block_begin(BlockType::Equation); - print_channel_iteration_block_begin(BlockType::Equation); + print_channel_iteration_block_parallel_hint(BlockType::Equation); + printer->start_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_kernel(*info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); if (!nrn_cur_reduction_loop_required()) { print_fast_imem_calculation(); } - print_channel_iteration_block_end(); + printer->end_block(1); if (nrn_cur_reduction_loop_required()) { - print_shadow_reduction_block_begin(); + printer->start_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_matrix_shadow_reduction(); print_shadow_reduction_statements(); - print_shadow_reduction_block_end(); + printer->end_block(1); print_fast_imem_calculation(); } - print_channel_iteration_tiling_block_end(); print_kernel_data_present_annotation_block_end(); printer->end_block(1); codegen = false; diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 2f1c16eec7..9a9dd24a58 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -271,7 +271,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { codegen::CodegenInfo info; /** - * Code printer object for target (C, ispc, ...) + * Code printer object for target (C) */ std::shared_ptr target_printer; @@ -893,7 +893,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * For C code generation this is empty * \return "" * - * For ispc * \return "uniform " */ virtual std::string global_var_struct_type_qualifier(); @@ -908,8 +907,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Print static assertions about the global variable struct. - * - * For ISPC this has to be disabled. */ virtual void print_global_var_struct_assertions() const; @@ -1262,18 +1259,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_net_event_call(const ast::FunctionCall& node); - /** - * Print block start for tiling on channel iteration - */ - virtual void print_channel_iteration_tiling_block_begin(BlockType type); - - - /** - * Print block end for tiling on channel iteration - */ - virtual void print_channel_iteration_tiling_block_end(); - - /** * Print pragma annotations for channel iterations * @@ -1314,19 +1299,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_net_init_acc_serial_annotation_block_end(); - /** - * Print backend specific channel instance iteration block start - * \param type The block type in which we currently are - */ - virtual void print_channel_iteration_block_begin(BlockType type); - - - /** - * Print backend specific channel instance iteration block end - */ - virtual void print_channel_iteration_block_end(); - - /** * Print function and procedures prototype declaration */ @@ -1473,20 +1445,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_device_atomic_capture_annotation() const; - /** - * Print block / loop for statement requiring reduction - * - */ - virtual void print_shadow_reduction_block_begin(); - - - /** - * Print end of block / loop for statement requiring reduction - * - */ - virtual void print_shadow_reduction_block_end(); - - /** * Print atomic update pragma for reduction statements * diff --git a/src/nmodl/codegen/codegen_ispc_visitor.cpp b/src/nmodl/codegen/codegen_ispc_visitor.cpp deleted file mode 100644 index fa1621178c..0000000000 --- a/src/nmodl/codegen/codegen_ispc_visitor.cpp +++ /dev/null @@ -1,793 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#include "codegen/codegen_ispc_visitor.hpp" - -#include - -#include "ast/all.hpp" -#include "codegen/codegen_naming.hpp" -#include "codegen/codegen_utils.hpp" -#include "symtab/symbol_table.hpp" -#include "utils/logger.hpp" -#include "visitors/rename_visitor.hpp" -#include "visitors/visitor_utils.hpp" - -namespace nmodl { -namespace codegen { - -using symtab::syminfo::Status; - -using visitor::RenameVisitor; - -const std::vector CodegenIspcVisitor::incompatible_node_types = { - ast::AstNodeType::VERBATIM, - ast::AstNodeType::EIGEN_NEWTON_SOLVER_BLOCK, - ast::AstNodeType::EIGEN_LINEAR_SOLVER_BLOCK, - ast::AstNodeType::WATCH_STATEMENT, - ast::AstNodeType::TABLE_STATEMENT}; - -const std::unordered_set CodegenIspcVisitor::incompatible_var_names = { - "cdo", "cfor", "cif", "cwhile", "foreach", "foreach_active", - "foreach_tiled", "foreach_unique", "in", "noinline", "__vectorcall", "int8", - "int16", "int32", "int64", "launch", "print", "soa", - "sync", "task", "varying"}; - -/****************************************************************************************/ -/* Overloaded visitor methods */ -/****************************************************************************************/ - -/* - * Rename math functions for ISPC backend - */ -void CodegenIspcVisitor::visit_function_call(const ast::FunctionCall& node) { - if (!codegen) { - return; - } - if (node.get_node_name() == "printf") { - logger->warn(fmt::format("Not emitted in ispc: {}", to_nmodl(node))); - return; - } - auto& fname = *node.get_name(); - RenameVisitor("fabs", "abs").visit_name(fname); - RenameVisitor("exp", "vexp").visit_name(fname); - CodegenCVisitor::visit_function_call(node); -} - - -/* - * Rename special global variables - */ -void CodegenIspcVisitor::visit_var_name(const ast::VarName& node) { - if (!codegen) { - return; - } - RenameVisitor pi_rename("PI", "ISPC_PI"); - node.accept(pi_rename); - CodegenCVisitor::visit_var_name(node); -} - -void CodegenIspcVisitor::visit_local_list_statement(const ast::LocalListStatement& node) { - if (!codegen) { - return; - } - /// Correct indentation - printer->add_newline(); - printer->add_indent(); - - /// Name of the variable _dt given by - /// nmodl::visitor::SteadystateVisitor::create_steadystate_block() - const std::string steadystate_dt_variable_name = "dt_saved_value"; - auto type = CodegenCVisitor::local_var_type() + " "; - printer->add_text(type); - auto local_variables = node.get_variables(); - bool dt_saved_value_exists = false; - - /// Remove dt_saved_value from local_variables if it exists - local_variables.erase(std::remove_if(local_variables.begin(), - local_variables.end(), - [&](const std::shared_ptr& local_variable) { - if (local_variable->get_node_name() == - steadystate_dt_variable_name) { - dt_saved_value_exists = true; - return true; - } else { - return false; - } - }), - local_variables.end()); - - /// Print the local_variables like normally - CodegenCVisitor::print_vector_elements(local_variables, ", "); - - /// Print dt_saved_value as uniform - if (dt_saved_value_exists) { - printer->add_text(";"); - /// Correct indentation - printer->add_newline(); - printer->add_indent(); - - type = CodegenCVisitor::local_var_type() + " uniform "; - printer->add_text(type + steadystate_dt_variable_name); - } -} - -/****************************************************************************************/ -/* Routines must be overloaded in backend */ -/****************************************************************************************/ - -/** - * In ISPC we have to explicitly append `d` to a floating point number - * otherwise it is treated as float. A value stored in the AST can be in - * scientific notation and hence we can't just append `d` to the string. - * Hence, we have to transform the value into the ISPC compliant format by - * replacing `e` and `E` with `d` to keep the same representation of the - * number as in the cpp backend. - */ -std::string CodegenIspcVisitor::format_double_string(const std::string& s_value) { - return utils::format_double_string(s_value); -} - - -/** - * For float variables we don't have to do the conversion with changing `e` and - * `E` with `f`, since the scientific notation numbers are already parsed as - * floats by ISPC. Instead we need to take care of only appending `f` to the - * end of floating point numbers, which is optional on ISPC. - */ -std::string CodegenIspcVisitor::format_float_string(const std::string& s_value) { - return utils::format_float_string(s_value); -} - - -std::string CodegenIspcVisitor::compute_method_name(BlockType type) const { - if (type == BlockType::Initial) { - return method_name(naming::NRN_INIT_METHOD); - } - if (type == BlockType::Constructor) { - return method_name(naming::NRN_CONSTRUCTOR_METHOD); - } - if (type == BlockType::Destructor) { - return method_name(naming::NRN_DESTRUCTOR_METHOD); - } - if (type == BlockType::State) { - return method_name(naming::NRN_STATE_METHOD); - } - if (type == BlockType::Equation) { - return method_name(naming::NRN_CUR_METHOD); - } - if (type == BlockType::Watch) { - return method_name(naming::NRN_WATCH_CHECK_METHOD); - } - throw std::runtime_error("compute_method_name not implemented"); -} - - -std::string CodegenIspcVisitor::net_receive_buffering_declaration() { - auto params = ParamVector(); - params.emplace_back(param_type_qualifier(), - fmt::format("{}*", instance_struct()), - param_ptr_qualifier(), - "inst"); - params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); - params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); - - return fmt::format("export void {}({})", - method_name("ispc_net_buf_receive"), - get_parameter_str(params)); -} - - -void CodegenIspcVisitor::print_backend_includes() { - printer->add_line("#include \"nmodl/fast_math.ispc\""); - printer->add_line("#include \"coreneuron/mechanism/nrnoc_ml.ispc\""); - printer->add_newline(); - printer->add_newline(); -} - - -std::string CodegenIspcVisitor::backend_name() const { - return "ispc (api-compatibility)"; -} - - -void CodegenIspcVisitor::print_channel_iteration_tiling_block_begin(BlockType /* type */) { - // no tiling for ispc backend but make sure variables are declared as uniform - printer->add_line("int uniform start = 0;"); - printer->add_line("int uniform end = nodecount;"); -} - - -/* - * Depending on the backend, print condition/loop for iterating over channels - * - * Use ispc foreach loop - */ -void CodegenIspcVisitor::print_channel_iteration_block_begin(BlockType /* type */) { - printer->start_block("foreach (id = start ... end)"); -} - - -void CodegenIspcVisitor::print_channel_iteration_block_end() { - printer->end_block(); - printer->add_newline(); -} - - -void CodegenIspcVisitor::print_net_receive_loop_begin() { - printer->add_line("uniform int count = nrb->_displ_cnt;"); - printer->start_block("foreach (i = 0 ... count)"); -} - - -void CodegenIspcVisitor::print_get_memb_list() { - // do nothing -} - - -void CodegenIspcVisitor::print_atomic_op(const std::string& lhs, - const std::string& op, - const std::string& rhs) { - std::string function; - if (op == "+") { - function = "atomic_add_local"; - } else if (op == "-") { - function = "atomic_subtract_local"; - } else { - throw std::runtime_error(fmt::format("ISPC backend error : {} not supported", op)); - } - printer->fmt_line("{}(&{}, {});", function, lhs, rhs); -} - - -void CodegenIspcVisitor::print_nrn_cur_matrix_shadow_reduction() { - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - if (info.point_process) { - printer->add_line("uniform int node_id = node_index[id];"); - printer->fmt_line("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op); - printer->fmt_line("vec_d[node_id] {} shadow_d[id];", d_op); - } -} - - -void CodegenIspcVisitor::print_shadow_reduction_block_begin() { - printer->start_block("for (uniform int id = start; id < end; id++) "); - if (info.point_process) { - printer->start_block("if (programIndex == 0)"); - } -} - -void CodegenIspcVisitor::print_shadow_reduction_block_end() { - if (info.point_process) { - printer->end_block(1); - } - printer->end_block(1); -} - -void CodegenIspcVisitor::print_rhs_d_shadow_variables() { - if (info.point_process) { - printer->fmt_line("double* uniform shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); - printer->fmt_line("double* uniform shadow_d = nt->{};", naming::NTHREAD_D_SHADOW); - } -} - - -bool CodegenIspcVisitor::nrn_cur_reduction_loop_required() { - return info.point_process; -} - - -std::string CodegenIspcVisitor::ptr_type_qualifier() { - if (wrapper_codegen) { - return CodegenCVisitor::ptr_type_qualifier(); - } else { - return "uniform "; // @note: extra space needed to separate qualifier from var name. - } -} - -std::string CodegenIspcVisitor::global_var_struct_type_qualifier() { - if (wrapper_codegen) { - return CodegenCVisitor::global_var_struct_type_qualifier(); - } else { - return "uniform "; // @note: extra space needed to separate qualifier from var name. - } -} - -void CodegenIspcVisitor::print_global_var_struct_decl() { - if (wrapper_codegen) { - CodegenCVisitor::print_global_var_struct_decl(); - } -} - -void CodegenIspcVisitor::print_global_var_struct_assertions() const { - // Print static_assert in .cpp but not .ispc - if (wrapper_codegen) { - CodegenCVisitor::print_global_var_struct_assertions(); - } -} - -std::string CodegenIspcVisitor::param_type_qualifier() { - if (wrapper_codegen) { - return CodegenCVisitor::param_type_qualifier(); - } else { - return "uniform "; - } -} - - -std::string CodegenIspcVisitor::param_ptr_qualifier() { - if (wrapper_codegen) { - return CodegenCVisitor::param_ptr_qualifier(); - } else { - return "uniform "; - } -} - - -void CodegenIspcVisitor::print_backend_namespace_start() { - // no ispc namespace -} - - -void CodegenIspcVisitor::print_backend_namespace_stop() { - // no ispc namespace -} - - -CodegenIspcVisitor::ParamVector CodegenIspcVisitor::get_global_function_parms( - const std::string& /* arg_qualifier */) { - auto params = ParamVector(); - params.emplace_back(param_type_qualifier(), - fmt::format("{}*", instance_struct()), - param_ptr_qualifier(), - "inst"); - params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); - params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); - params.emplace_back(param_type_qualifier(), "int", "", "type"); - return params; -} - - -void CodegenIspcVisitor::print_procedure(const ast::ProcedureBlock& node) { - codegen = true; - const auto& name = node.get_node_name(); - print_function_or_procedure(node, name); - codegen = false; -} - - -void CodegenIspcVisitor::print_global_function_common_code(BlockType type, - const std::string& function_name) { - // If we are printing the cpp file, we have to use the c version of this function - if (wrapper_codegen) { - return CodegenCVisitor::print_global_function_common_code(type, function_name); - } - - std::string method = compute_method_name(type); - - auto params = get_global_function_parms(ptr_type_qualifier()); - print_global_method_annotation(); - printer->fmt_start_block("export void {}({})", method, get_parameter_str(params)); - - print_kernel_data_present_annotation_block_begin(); - printer->add_line("uniform int nodecount = ml->nodecount;"); - printer->add_line("uniform int pnodecount = ml->_nodecount_padded;"); - printer->fmt_line("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier()); - printer->fmt_line("double* {}data = ml->data;", ptr_type_qualifier()); - printer->fmt_line("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier()); - - if (type == BlockType::Equation) { - printer->fmt_line("double* {}vec_rhs = nt->_actual_rhs;", ptr_type_qualifier()); - printer->fmt_line("double* {}vec_d = nt->_actual_d;", ptr_type_qualifier()); - print_rhs_d_shadow_variables(); - } - printer->fmt_line("Datum* {}indexes = ml->pdata;", ptr_type_qualifier()); - printer->fmt_line("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier()); - printer->add_newline(1); -} - - -void CodegenIspcVisitor::print_compute_functions() { - for (const auto& function: info.functions) { - if (!program_symtab->lookup(function->get_node_name())->has_all_status(Status::inlined)) { - print_function(*function); - } - } - for (const auto& procedure: info.procedures) { - if (!program_symtab->lookup(procedure->get_node_name())->has_all_status(Status::inlined)) { - print_procedure(*procedure); - } - } - if (!emit_fallback[BlockType::NetReceive]) { - print_net_receive_kernel(); - print_net_receive_buffering(false); - } - if (!emit_fallback[BlockType::Initial]) { - print_nrn_init(false); - } - if (!emit_fallback[BlockType::Equation]) { - print_nrn_cur(); - } - if (!emit_fallback[BlockType::State]) { - print_nrn_state(); - } -} - - -void CodegenIspcVisitor::print_ion_var_constructor(const std::vector& members) { - /// no constructor for ispc -} - - -void CodegenIspcVisitor::print_ion_variable() { - /// c syntax to zero initialize struct - printer->add_line("IonCurVar ionvar = {0};"); -} - - -/****************************************************************************************/ -/* Main code printing entry points and wrappers */ -/****************************************************************************************/ - -void CodegenIspcVisitor::print_net_receive_buffering_wrapper() { - if (!net_receive_required() || info.artificial_cell) { - return; - } - printer->add_newline(2); - printer->fmt_start_block("void {}(NrnThread* nt)", method_name("net_buf_receive")); - printer->add_line("Memb_list* ml = get_memb_list(nt);"); - printer->start_block("if (ml == NULL)"); - printer->add_line("return;"); - printer->end_block(1); - printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", - instance_struct(), - ptr_type_qualifier()); - - printer->fmt_line("{}(inst, nt, ml);", method_name("ispc_net_buf_receive")); - - printer->end_block(1); -} - - -void CodegenIspcVisitor::print_headers_include() { - print_backend_includes(); -} - -void CodegenIspcVisitor::print_nmodl_constants() { - if (!info.factor_definitions.empty()) { - printer->add_newline(2); - printer->add_line("/** constants used in nmodl */"); - for (auto& it: info.factor_definitions) { - const std::string name = it->get_node_name() == "PI" ? "ISPC_PI" : it->get_node_name(); - const std::string value = format_double_string(it->get_value()->get_value()); - printer->fmt_line("static const uniform double {} = {};", name, value); - } - } -} - -void CodegenIspcVisitor::print_wrapper_headers_include() { - print_standard_includes(); - print_coreneuron_includes(); -} - - -void CodegenIspcVisitor::print_wrapper_routine(const std::string& wrapper_function, - BlockType type) { - static const auto args = "NrnThread* nt, Memb_list* ml, int type"; - const auto function_name = method_name(wrapper_function); - auto compute_function = compute_method_name(type); - - printer->add_newline(2); - printer->fmt_start_block("void {}({})", function_name, args); - printer->add_line("int nodecount = ml->nodecount;"); - printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", - instance_struct(), - ptr_type_qualifier()); - - if (type == BlockType::Initial) { - printer->add_newline(); - printer->add_line("setup_instance(nt, ml);"); - printer->add_newline(); - printer->start_block("if (_nrn_skip_initmodel)"); - printer->add_line("return;"); - printer->end_block(1); - } - printer->fmt_line("{}(inst, nt, ml, type);", compute_function); - printer->end_block(1); -} - - -void CodegenIspcVisitor::print_backend_compute_routine_decl() { - auto params = get_global_function_parms(""); - auto compute_function = compute_method_name(BlockType::Initial); - if (!emit_fallback[BlockType::Initial]) { - printer->fmt_line("extern \"C\" void {}({});", compute_function, get_parameter_str(params)); - } - - if (nrn_cur_required() && !emit_fallback[BlockType::Equation]) { - compute_function = compute_method_name(BlockType::Equation); - printer->fmt_line("extern \"C\" void {}({});", compute_function, get_parameter_str(params)); - } - - if (nrn_state_required() && !emit_fallback[BlockType::State]) { - compute_function = compute_method_name(BlockType::State); - printer->fmt_line("extern \"C\" void {}({});", compute_function, get_parameter_str(params)); - } - - if (net_receive_required()) { - auto net_recv_params = ParamVector(); - net_recv_params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); - net_recv_params.emplace_back("", "NrnThread*", "", "nt"); - net_recv_params.emplace_back("", "Memb_list*", "", "ml"); - printer->fmt_line("extern \"C\" void {}({});", - method_name("ispc_net_buf_receive"), - get_parameter_str(net_recv_params)); - } -} - -bool CodegenIspcVisitor::check_incompatibilities() { - const auto& has_incompatible_nodes = [](const ast::Ast& node) { - return !collect_nodes(node, incompatible_node_types).empty(); - }; - - const auto get_name_from_symbol_type_vector = [](const SymbolType& var) -> const std::string& { - return var->get_name(); - }; - - // instance vars - if (check_incompatible_var_name(codegen_float_variables, - get_name_from_symbol_type_vector)) { - return true; - } - - if (check_incompatible_var_name(codegen_shadow_variables, - get_name_from_symbol_type_vector)) { - return true; - } - if (check_incompatible_var_name( - codegen_int_variables, [](const IndexVariableInfo& var) -> const std::string& { - return var.symbol->get_name(); - })) { - return true; - } - - - if (check_incompatible_var_name(info.currents, - [](const std::string& var) -> const std::string& { - return var; - })) { - return true; - } - - - // global vars - // info.top_local_variables is not checked because it should be addressed by the - // renameIspcVisitor - if (check_incompatible_var_name(info.global_variables, - get_name_from_symbol_type_vector)) { - return true; - } - - - if (check_incompatible_var_name(info.constant_variables, - get_name_from_symbol_type_vector)) { - return true; - } - - // ion vars - for (const auto& ion: info.ions) { - if (check_incompatible_var_name( - ion.writes, [](const std::string& var) -> const std::string& { return var; })) { - return true; - } - } - - - emit_fallback = std::vector(BlockType::BlockTypeEnd, false); - - if (info.initial_node) { - emit_fallback[BlockType::Initial] = - emit_fallback[BlockType::Initial] || has_incompatible_nodes(*info.initial_node) || - visitor::calls_function(*info.initial_node, "net_send") || info.require_wrote_conc || - info.net_send_used || info.net_event_used; - } else { - emit_fallback[BlockType::Initial] = emit_fallback[BlockType::Initial] || - info.net_receive_initial_node || - info.require_wrote_conc; - } - - emit_fallback[BlockType::NetReceive] = - emit_fallback[BlockType::NetReceive] || - (info.net_receive_node && (has_incompatible_nodes(*info.net_receive_node) || - visitor::calls_function(*info.net_receive_node, "net_send"))); - - emit_fallback[BlockType::Equation] = emit_fallback[BlockType::Equation] || - (nrn_cur_required() && info.breakpoint_node && - has_incompatible_nodes(*info.breakpoint_node)); - - emit_fallback[BlockType::State] = emit_fallback[BlockType::State] || - (nrn_state_required() && info.nrn_state_block && - has_incompatible_nodes(*info.nrn_state_block)); - - - return false; -} - - -void CodegenIspcVisitor::move_procs_to_wrapper() { - auto nameset = std::set(); - - auto populate_nameset = [&nameset](const ast::Block* block) { - if (block) { - const auto& names = collect_nodes(*block, {ast::AstNodeType::NAME}); - for (const auto& name: names) { - nameset.insert(name->get_node_name()); - } - } - }; - populate_nameset(info.initial_node); - populate_nameset(info.nrn_state_block); - populate_nameset(info.breakpoint_node); - - const auto& has_incompatible_nodes = [](const ast::Ast& node) { - return !collect_nodes(node, incompatible_node_types).empty(); - }; - - auto target_procedures = std::vector(); - for (const auto& procedure: info.procedures) { - const auto& name = procedure->get_name()->get_node_name(); - if (nameset.find(name) == nameset.end() || has_incompatible_nodes(*procedure)) { - wrapper_procedures.push_back(procedure); - } else { - target_procedures.push_back(procedure); - } - } - info.procedures = target_procedures; - auto target_functions = std::vector(); - for (const auto& function: info.functions) { - const auto& name = function->get_name()->get_node_name(); - if (nameset.find(name) == nameset.end() || has_incompatible_nodes(*function)) { - wrapper_functions.push_back(function); - } else { - target_functions.push_back(function); - } - } - info.functions = target_functions; -} - -void CodegenIspcVisitor::print_block_wrappers_initial_equation_state() { - if (emit_fallback[BlockType::Initial]) { - logger->warn("Falling back to C backend for emitting Initial block"); - fallback_codegen.print_nrn_init(); - } else { - print_wrapper_routine(naming::NRN_INIT_METHOD, BlockType::Initial); - } - - if (nrn_cur_required()) { - if (emit_fallback[BlockType::Equation]) { - logger->warn("Falling back to C backend for emitting breakpoint block"); - fallback_codegen.print_nrn_cur(); - } else { - print_wrapper_routine(naming::NRN_CUR_METHOD, BlockType::Equation); - } - } - - if (nrn_state_required()) { - if (emit_fallback[BlockType::State]) { - logger->warn("Falling back to C backend for emitting state block"); - fallback_codegen.print_nrn_state(); - } else { - print_wrapper_routine(naming::NRN_STATE_METHOD, BlockType::State); - } - } -} - - -void CodegenIspcVisitor::visit_program(const ast::Program& node) { - setup(node); - - // we need setup to check incompatibilities - if (check_incompatibilities()) { - logger->warn( - "ISPC reserved keyword used as variable name in mod file. Using C++ backend as " - "fallback"); - print_backend_info(); - print_headers_include(); - fallback_codegen.visit_program(node); - } else { - fallback_codegen.setup(node); - // we do not want to call setup twice - print_codegen_routines(); - print_wrapper_routines(); - } -} - - -void CodegenIspcVisitor::print_codegen_routines() { - codegen = true; - move_procs_to_wrapper(); - print_backend_info(); - print_headers_include(); - print_nmodl_constants(); - print_data_structures(false); - print_compute_functions(); -} - - -void CodegenIspcVisitor::print_wrapper_routines() { - printer = wrapper_printer; - wrapper_codegen = true; - print_backend_info(); - print_wrapper_headers_include(); - print_namespace_begin(); - - CodegenCVisitor::print_nmodl_constants(); - print_mechanism_info(); - print_data_structures(true); - print_global_variables_for_hoc(); - print_common_getters(); - - print_memory_allocation_routine(); - print_thread_memory_callbacks(); - print_abort_routine(); - /* this is a godawful mess.. the global variables have to be copied over into the fallback - * such that they are available to the fallback generator. - */ - fallback_codegen.set_codegen_global_variables(codegen_global_variables); - print_instance_variable_setup(); - print_nrn_alloc(); - print_top_verbatim_blocks(); - - - for (const auto& function: wrapper_functions) { - if (!program_symtab->lookup(function->get_node_name())->has_all_status(Status::inlined)) { - fallback_codegen.print_function(*function); - } - } - for (const auto& procedure: wrapper_procedures) { - if (!program_symtab->lookup(procedure->get_node_name())->has_all_status(Status::inlined)) { - fallback_codegen.print_procedure(*procedure); - } - } - - print_check_table_thread_function(); - - print_net_send_buffering(); - print_net_init(); - print_watch_activate(); - fallback_codegen.print_watch_check(); // requires C style variable declarations and loops - - if (emit_fallback[BlockType::NetReceive]) { - logger->warn( - "Found VERBATIM code or ISPC keyword in NET_RECEIVE block, using C++ backend as " - "fallback" - "backend"); - fallback_codegen.print_net_receive_kernel(); - fallback_codegen.print_net_receive_buffering(); - } - - print_net_receive(); - - print_backend_compute_routine_decl(); - - if (!emit_fallback[BlockType::NetReceive]) { - print_net_receive_buffering_wrapper(); - } - - print_block_wrappers_initial_equation_state(); - - print_nrn_constructor(); - print_nrn_destructor(); - - print_mechanism_register(); - - print_namespace_end(); -} - -} // namespace codegen -} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_ispc_visitor.hpp b/src/nmodl/codegen/codegen_ispc_visitor.hpp deleted file mode 100644 index f97d0085c6..0000000000 --- a/src/nmodl/codegen/codegen_ispc_visitor.hpp +++ /dev/null @@ -1,251 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -/** - * \file - * \brief \copybrief nmodl::codegen::CodegenIspcVisitor - */ - -#include "codegen/codegen_c_visitor.hpp" - - -namespace nmodl { -namespace codegen { - -/** - * @addtogroup codegen_backends - * @{ - */ - -/** - * \class CodegenIspcVisitor - * \brief %Visitor for printing C code with ISPC backend - */ -class CodegenIspcVisitor: public CodegenCVisitor { - /** - * Prints an ISPC atomic operation - * - * \note This is currently not used because of performance issues on KNL. Instead reduction - * operations are serialized. - * - * \param lhs Reduction left-hand-side operand - * \param op Reducation operation. Currently only \c += and \c -= are supported - * \param rhs Reduction right-hand-side operand - */ - void print_atomic_op(const std::string& lhs, const std::string& op, const std::string& rhs); - - - /// ast nodes which are not compatible with ISPC target - static const std::vector incompatible_node_types; - - static const std::unordered_set incompatible_var_names; - - /// flag to indicate if visitor should print the the wrapper code - bool wrapper_codegen = false; - - /// fallback C code generator used to emit C code in the wrapper when emitting ISPC is not - /// supported - CodegenCVisitor fallback_codegen; - - std::vector emit_fallback = - std::vector(static_cast(BlockType::BlockTypeEnd), false); - - std::vector wrapper_procedures; - std::vector wrapper_functions; - - protected: - /** - * Convert a given \c double value to its string representation - * \param value The number to convert given as string as it parsed by the modfile - * \return Its string representation in ISPC compliant format - */ - std::string format_double_string(const std::string& value) override; - - - /** - * Convert a given \c float value to its string representation - * \param value The number to convert given as string as it parsed by the modfile - * \return Its string representation in ISPC compliant format - */ - std::string format_float_string(const std::string& value) override; - - - /// name of the code generation backend - std::string backend_name() const override; - - - /// return name of main compute kernels - std::string compute_method_name(BlockType type) const override; - - - std::string net_receive_buffering_declaration() override; - - - std::string ptr_type_qualifier() override; - - std::string global_var_struct_type_qualifier() override; - - - void print_global_var_struct_decl() override; - void print_global_var_struct_assertions() const override; - - - std::string param_type_qualifier() override; - - - std::string param_ptr_qualifier() override; - - - /// common includes : standard c/c++, coreneuron and backend specific - void print_backend_includes() override; - - - /// reduction to matrix elements from shadow vectors - void print_nrn_cur_matrix_shadow_reduction() override; - - /// fast membrane current calculation - - /** - * Print block / loop for statement requiring reduction - * - */ - void print_shadow_reduction_block_begin() override; - - /** - * Print end of block / loop for statement requiring reduction - * - */ - void print_shadow_reduction_block_end() override; - - /// setup method for setting matrix shadow vectors - void print_rhs_d_shadow_variables() override; - - - /// if reduction block in nrn_cur required - bool nrn_cur_reduction_loop_required() override; - - - ParamVector get_global_function_parms(const std::string& arg_qualifier); - - - void print_global_function_common_code(BlockType type, - const std::string& function_name = "") override; - - - /// backend specific channel instance iteration block start - void print_channel_iteration_block_begin(BlockType type) override; - - - /// backend specific channel iteration bounds - void print_channel_iteration_tiling_block_begin(BlockType type) override; - - - /// backend specific channel instance iteration block end - void print_channel_iteration_block_end() override; - - - /// start of backend namespace - void print_backend_namespace_start() override; - - - /// end of backend namespace - void print_backend_namespace_stop() override; - - - void print_headers_include() override; - - - void print_wrapper_headers_include(); - - - /// nmodl procedure definition - void print_procedure(const ast::ProcedureBlock& node) override; - - - void print_backend_compute_routine_decl(); - - - /// print wrapper function that calls ispc kernel - void print_wrapper_routine(const std::string& wrapper_function, BlockType type); - - - /// print initial equation and state wrapper - void print_block_wrappers_initial_equation_state(); - - - void print_get_memb_list() override; - - - void print_net_receive_loop_begin() override; - - - void print_net_receive_buffering_wrapper(); - - - void print_ion_var_constructor(const std::vector& members) override; - - - void print_ion_variable() override; - - - /// find out for main compute routines whether they are suitable to be emitted in ISPC backend - bool check_incompatibilities(); - - /// check incompatible name var - template - bool check_incompatible_var_name(const std::vector& vec, - const std::string& get_name(const T&)) { - for (const auto& var: vec) { - if (incompatible_var_names.count(get_name(var))) { - return true; - } - } - return false; - } - - /// move procedures and functions unused by compute kernels into the wrapper - void move_procs_to_wrapper(); - - - /// entry point to code generation - void print_codegen_routines() override; - - void print_wrapper_routines() override; - - public: - CodegenIspcVisitor(const std::string& mod_file, - const std::string& output_dir, - const std::string& float_type, - const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies, ".ispc", ".cpp") - , fallback_codegen(mod_file, float_type, optimize_ionvar_copies, wrapper_printer) {} - - - CodegenIspcVisitor(const std::string& mod_file, - std::ostream& stream, - const std::string& float_type, - const bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies, ".ispc", ".cpp") - , fallback_codegen(mod_file, float_type, optimize_ionvar_copies, wrapper_printer) {} - - void visit_function_call(const ast::FunctionCall& node) override; - void visit_var_name(const ast::VarName& node) override; - void visit_program(const ast::Program& node) override; - void visit_local_list_statement(const ast::LocalListStatement& node) override; - - /// all compute functions for every backend - void print_compute_functions() override; - - void print_nmodl_constants() override; -}; - -/** @} */ // end of codegen_backends - -} // namespace codegen -} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index 02f9d09b1f..41683258e5 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -8,14 +8,13 @@ #include "codegen/codegen_utils.hpp" #include "codegen/codegen_c_visitor.hpp" -#include "codegen/codegen_ispc_visitor.hpp" namespace nmodl { namespace codegen { namespace utils { /** * \details We can directly print value but if user specify value as integer then - * then it gets printed as an integer. To avoid this, we use below wrapper. + * then it gets printed as an integer. To avoid this, we use below wrappers. * If user has provided integer then it gets printed as 1.0 (similar to mod2c * and neuron where ".0" is appended). Otherwise we print double variables as * they are represented in the mod file by user. If the value is in scientific @@ -31,31 +30,6 @@ std::string format_double_string(const std::string& s_value) { } -/** - * \details In ISPC we have to explicitly append `d` to a floating point number - * otherwise it is treated as float. A value stored in the AST can be in - * scientific notation and hence we can't just append `d` to the string. - * Hence, we have to transform the value into the ISPC compliant format by - * replacing `e` and `E` with `d` to keep the same representation of the - * number as in the cpp backend. - */ -template <> -std::string format_double_string(const std::string& s_value) { - std::string return_string = s_value; - if (s_value.find_first_of("eE") != std::string::npos) { - std::replace(return_string.begin(), return_string.end(), 'E', 'd'); - std::replace(return_string.begin(), return_string.end(), 'e', 'd'); - } else if (s_value.find('.') == std::string::npos) { - return_string += ".0d"; - } else if (s_value.front() == '.') { - return_string = '0' + return_string + 'd'; - } else { - return_string += 'd'; - } - return return_string; -} - - template <> std::string format_float_string(const std::string& s_value) { float value = std::stof(s_value); @@ -64,28 +38,6 @@ std::string format_float_string(const std::string& s_value) { } return s_value; } - - -/** - * \details For float variables we don't have to do the conversion with changing `e` and - * `E` with `f`, since the scientific notation numbers are already parsed as - * floats by ISPC. Instead we need to take care of only appending `f` to the - * end of floating point numbers, which is optional on ISPC. - */ -template <> -std::string format_float_string(const std::string& s_value) { - std::string return_string = s_value; - if (s_value.find_first_of("Ee.") == std::string::npos) { - return_string += ".0f"; - } else if (s_value.front() == '.' && s_value.find_first_of("Ee") == std::string::npos) { - return_string = '0' + return_string + 'f'; - } else if (s_value.find_first_of("Ee") == std::string::npos) { - return_string += 'f'; - } - return return_string; -} - - } // namespace utils } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_utils.hpp b/src/nmodl/codegen/codegen_utils.hpp index e88335619c..8b5a01a6cf 100644 --- a/src/nmodl/codegen/codegen_utils.hpp +++ b/src/nmodl/codegen/codegen_utils.hpp @@ -24,8 +24,7 @@ namespace utils { * * It takes care of printing the values with the correct floating point precision * for each backend, similar to mod2c and Neuron. - * This function can be called using as template `CodegenCVisitor` or - * `CodegenIspcVisitor`. + * This function can be called using as template `CodegenCVisitor` * * \param s_value The double constant as string * \return The proper string to be printed in the generated file. @@ -39,8 +38,7 @@ std::string format_double_string(const std::string& s_value); * * It takes care of printing the values with the correct floating point precision * for each backend, similar to mod2c and Neuron. - * This function can be called using as template `CodegenCVisitor` or - * `CodegenIspcVisitor`. + * This function can be called using as template `CodegenCVisitor` * * \param s_value The double constant as string * \return The proper string to be printed in the generated file. diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp index a6ba51fef4..82f27c7d5e 100644 --- a/src/nmodl/codegen/fast_math.hpp +++ b/src/nmodl/codegen/fast_math.hpp @@ -3,9 +3,6 @@ * * This file is part of NMODL distributed under the terms of the GNU * Lesser General Public License. See top-level LICENSE file for details. - * - * Note: fast_math.ispc translated into .hpp syntax. More information in - * fast_math.ispc *************************************************************************/ #pragma once diff --git a/src/nmodl/codegen/fast_math.ispc b/src/nmodl/codegen/fast_math.ispc deleted file mode 100644 index ffc3a09555..0000000000 --- a/src/nmodl/codegen/fast_math.ispc +++ /dev/null @@ -1,234 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - * - * Note that the fast ISPC exponentials Based on VDT implementation of - * D. Piparo et al. See https://github.com/dpiparo/vdt for additional - * license information. - * exprelr and expm1 are based on https://arbor.readthedocs.io/en/latest/internals/simd_api.html#implementation-of-vector-transcendental-functions - *************************************************************************/ - -/** - * \file - * \brief Implementation of different math functions with ISPC - */ - -static inline double uint642dp(unsigned int64 ll) { - return *((varying double*) (&ll)); -} - -static inline unsigned int64 dp2uint64(double x) { - return *((varying unsigned int64*) (&x)); -} - -static inline float int322sp(int32 x) { - return *((varying float*) (&x)); -} - -static inline unsigned int sp2uint32(float x) { - return *((varying unsigned int32*) (&x)); -} - -static inline double dpfloor(const double x) { - int32 ret = x; - ret -= (sp2uint32(x) >> 31); - return ret; -} - -static inline float spfloor(const float x) { - int32 ret = x; - ret -= (sp2uint32(x) >> 31); - return ret; -} - -static inline uniform float f_inf() { - uniform unsigned int32 v = 0x7F800000; - return *((float*) (&v)); -} - -static inline uniform double inf() { - uniform unsigned int64 v = 0x7FF0000000000000; - return *((double*) (&v)); -} - - -static const uniform double EXP_LIMIT = 708.d; - -static const uniform double PX1exp = 1.26177193074810590878d-4; -static const uniform double PX2exp = 3.02994407707441961300d-2; -static const uniform double PX3exp = 9.99999999999999999910d-1; -static const uniform double QX1exp = 3.00198505138664455042d-6; -static const uniform double QX2exp = 2.52448340349684104192d-3; -static const uniform double QX3exp = 2.27265548208155028766d-1; -static const uniform double QX4exp = 2.00000000000000000009d; - -static const uniform double LOG2E = 1.4426950408889634073599d; // 1/ln(2) - -static const uniform float MAXLOGF = 88.72283905206835f; -static const uniform float MINLOGF = -88.f; - -static const uniform float C1F = 0.693359375f; -static const uniform float C2F = -2.12194440e-4f; - -static const uniform float PX1expf = 1.9875691500E-4f; -static const uniform float PX2expf = 1.3981999507E-3f; -static const uniform float PX3expf = 8.3334519073E-3f; -static const uniform float PX4expf = 4.1665795894E-2f; -static const uniform float PX5expf = 1.6666665459E-1f; -static const uniform float PX6expf = 5.0000001201E-1f; - -static const uniform float LOG2EF = 1.44269504088896341f; // 1/ln(2) - -static const uniform double LOG10E = 0.4342944819032518d; // 1/log(10) double -static const uniform float LOG10F = 0.4342945f; // 1/log(10) float - -static inline double egm1(double x, double px) { - - x -= px * 6.93145751953125d-1; - x -= px * 1.42860682030941723212d-6; - - const double xx = x * x; - - px = PX1exp; - px *= xx; - px += PX2exp; - px *= xx; - px += PX3exp; - px *= x; - - double qx = QX1exp; - qx *= xx; - qx += QX2exp; - qx *= xx; - qx += QX3exp; - qx *= xx; - qx += QX4exp; - - return 2.0d*px / (qx - px); -} - -/// double precision exp function -static inline double vexp(double initial_x) { - double x = initial_x; - double px = dpfloor(LOG2E * x + 0.5d); - const int32 n = px; - - x = 1.0d + egm1(x, px); - x *= uint642dp((((unsigned int64) n) + 1023) << 52); - - if (initial_x > EXP_LIMIT) - x = inf(); - if (initial_x < -EXP_LIMIT) - x = 0.d; - - return x; -} - - -/// double precision exp function -static inline double vexpm1(double initial_x) { - double x = initial_x; - double px = dpfloor(LOG2E * x + 0.5d); - const int32 n = px; - - const unsigned int64 twopnm1 = (((unsigned int64) (n-1) ) + 1023) << 52; - x = 2*(uint642dp(twopnm1)*egm1(x, px) + uint642dp(twopnm1)) - 1.0d; - - if (initial_x > EXP_LIMIT) - x = inf(); - if (initial_x < -EXP_LIMIT) - x = -1.0d; - - return x; -} - - -/// double precision exprelr function () -static inline double exprelr(double initial_x) { - if (1.0d+initial_x == 1.0d) { - return 1.0d; - } - - return initial_x/vexpm1(initial_x); -} - - -static inline float egm1(float x) { - - float z = x * PX1expf; - z += PX2expf; - z *= x; - z += PX3expf; - z *= x; - z += PX4expf; - z *= x; - z += PX5expf; - z *= x; - z += PX6expf; - z *= x*x; - z += x; - - return z; -} - -/// single precision exp function -static inline float vexp(float initial_x) { - float x = initial_x; - float z = spfloor(LOG2EF * x + 0.5f); - - x -= z * C1F; - x -= z * C2F; - const int32 n = z; - - z = 1.0f + egm1(x); - - z *= int322sp((n + 0x7f) << 23); - - if (initial_x > MAXLOGF) - z = f_inf(); - if (initial_x < MINLOGF) - z = 0.f; - - return z; -} - -/// single precision exp function -static inline float vexpm1(float initial_x) { - float x = initial_x; - float z = spfloor(LOG2EF * x + 0.5f); - - x -= z * C1F; - x -= z * C2F; - const int32 n = z; - - const int32 twopnm1 = ((n-1) + 0x7f) << 23; - x = 2*(int322sp(twopnm1)*egm1(x) + int322sp(twopnm1)) - 1.0f; - - if (initial_x > MAXLOGF) - x = f_inf(); - if (initial_x < MINLOGF) - x = -1.0f; - - return x; -} - -/// single precision exprelr function -static inline float exprelr(float initial_x) { - if (1.0f+initial_x == 1.0f) { - return 1.0f; - } - - return initial_x/vexpm1(initial_x); -} - -/// double precision log10 function -static inline double log10(double f) { - return log(f) * LOG10E; -} - -/// single precision log10 function -static inline float log10(float f) { - return log(f) * LOG10F; -} diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 1fb706655e..4971b02716 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -14,7 +14,6 @@ #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_compatibility_visitor.hpp" -#include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_transform_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" @@ -28,7 +27,6 @@ #include "visitors/implicit_argument_visitor.hpp" #include "visitors/indexedname_visitor.hpp" #include "visitors/inline_visitor.hpp" -#include "visitors/ispc_rename_visitor.hpp" #include "visitors/json_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/local_to_assigned_visitor.hpp" @@ -72,9 +70,6 @@ int main(int argc, const char* argv[]) { /// true if serial c code to be generated bool c_backend(true); - /// true if ispc code to be generated - bool ispc_backend(false); - /// true if c code with openacc to be generated bool oacc_backend(false); @@ -181,11 +176,6 @@ int main(int argc, const char* argv[]) { auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c", c_backend, fmt::format("C/C++ backend ({})", c_backend)) ->ignore_case(); - host_opt - ->add_flag("--ispc", - ispc_backend, - fmt::format("C/C++ backend with ISPC ({})", ispc_backend)) - ->ignore_case(); auto acc_opt = app.add_subcommand("acc", "Accelerator code backends")->ignore_case(); acc_opt @@ -271,11 +261,6 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); - // if any of the other backends is used we force the C backend to be off. - if (ispc_backend) { - c_backend = false; - } - utils::make_path(output_dir); utils::make_path(scratch_dir); @@ -341,14 +326,6 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("after_cvode_to_cnexp")); } - /// Rename variables that match ISPC compiler double constants - if (ispc_backend) { - logger->info("Running ISPC variables rename visitor"); - IspcRenameVisitor(ast).visit_program(*ast); - SymtabVisitor(update_symtab).visit_program(*ast); - ast_to_nmodl(*ast, filepath("ispc_double_rename")); - } - /// GLOBAL to RANGE rename visitor if (nmodl_global_to_range) { // make sure to run perf visitor because code generator @@ -540,16 +517,7 @@ int main(int argc, const char* argv[]) { } { - if (ispc_backend) { - logger->info("Running ISPC backend code generator"); - CodegenIspcVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); - visitor.visit_program(*ast); - } - - else if (oacc_backend) { + if (oacc_backend) { logger->info("Running OpenACC backend code generator"); CodegenAccVisitor visitor(modfile, output_dir, diff --git a/src/nmodl/visitors/ispc_rename_visitor.hpp b/src/nmodl/visitors/ispc_rename_visitor.hpp deleted file mode 100644 index 929b1eac4f..0000000000 --- a/src/nmodl/visitors/ispc_rename_visitor.hpp +++ /dev/null @@ -1,90 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -/** - * \file - * \brief \copybrief nmodl::visitor::IspcRenameVisitor - */ - -#include - -#include "visitors/ast_visitor.hpp" -#include "visitors/rename_visitor.hpp" - -namespace nmodl { -namespace visitor { - -/** - * @addtogroup visitor_classes - * @{ - */ - -/** - * \class IspcRenameVisitor - * \brief Rename variable names to match with ISPC backend requirement - * - * Wrapper class of RenameVisitor to rename the - * variables of the mod file that match the representation - * of double constants by the ISPC compiler. If those - * variables are not renamed to avoid matching the - * representation of the double constants of the ISPC - * compiler, then there are compilation issues. - * Here are some examples of double constants that - * ISPC compiler parses: 3.14d, 31.4d-1, 1.d, 1.0d, - * 1d-2. A regex which matches these variables is: - * ([0-9\.]*d[\-0-9]+)|([0-9\.]+d[\-0-9]*) - * - * Example mod file: - * \code{.mod} - * NEURON { - * SUFFIX test_ispc_rename - * RANGE d1, d2, d3, var_d3, d4 - * } - * ASSIGNED { - * d1 - * d2 - * d3 - * var_d3 - * d4 - * } - * INITIAL { - * d1 = 1 - * d2 = 2 - * d3 = 3 - * var_d3 = 3 - * } - * PROCEDURE func () { - * VERBATIM - * d4 = 4; - * ENDVERBATIM - * } - * \endcode - * Variables d1, d2 and d4 match the double constant - * presentation of variables by ISPC and should be - * renamed to var_d1, var_d2 and var_d4 to avoid - * compilation errors. - * var_d3 can stay the same because it doesn't match - * the regex. - * In case var_d1 already exists as variable then d1 - * is renamed to var_d1_. - */ -class IspcRenameVisitor: public RenameVisitor { - public: - /// Default constructor - IspcRenameVisitor() = delete; - - /// Constructor that takes as parameter the AST and calls the RenameVisitor - explicit IspcRenameVisitor(std::shared_ptr ast) - : RenameVisitor(ast, "([0-9\\.]*d[\\-0-9]+)|([0-9\\.]+d[\\-0-9]*)", "var_", true, true) {} -}; - -/** @} */ // end of visitor_classes - -} // namespace visitor -} // namespace nmodl diff --git a/test/nmodl/transpiler/integration/mod/ispc_rename.mod b/test/nmodl/transpiler/integration/mod/ispc_rename.mod deleted file mode 100644 index 4abf5f2d6e..0000000000 --- a/test/nmodl/transpiler/integration/mod/ispc_rename.mod +++ /dev/null @@ -1,22 +0,0 @@ -NEURON { - SUFFIX test_ispc_rename - RANGE d1, d2, d3, var_d3, d4 -} -ASSIGNED { - d1 - d2 - d3 - var_d3 - d4 -} -INITIAL { - d1 = 1 - d2 = 2 - d3 = 3 - var_d3 = 3 -} -PROCEDURE func () { -VERBATIM - d4 = 4; -ENDVERBATIM -} diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index a2340b0414..c535edcf8a 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -38,7 +38,6 @@ add_executable( visitor/global_to_range.cpp visitor/implicit_argument.cpp visitor/inline.cpp - visitor/ispc_rename.cpp visitor/json.cpp visitor/kinetic_block.cpp visitor/localize.cpp @@ -65,9 +64,8 @@ add_executable(testnewton newton/newton.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) -add_executable( - testcodegen codegen/main.cpp codegen/codegen_ispc.cpp codegen/codegen_helper.cpp - codegen/codegen_utils.cpp codegen/codegen_c_visitor.cpp codegen/transform.cpp) +add_executable(testcodegen codegen/main.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp + codegen/codegen_c_visitor.cpp codegen/transform.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp b/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp deleted file mode 100644 index e2e63432bb..0000000000 --- a/test/nmodl/transpiler/unit/codegen/codegen_ispc.cpp +++ /dev/null @@ -1,196 +0,0 @@ -/************************************************************************* - * Copyright (C) 2019-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#include - -#include "ast/program.hpp" -#include "codegen/codegen_ispc_visitor.hpp" -#include "config/config.h" -#include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" -#include "visitors/symtab_visitor.hpp" -#include "visitors/units_visitor.hpp" - -using namespace nmodl; -using namespace visitor; -using namespace codegen; -using namespace test_utils; - -using nmodl::NrnUnitsLib; -using nmodl::parser::NmodlDriver; - -//============================================================================= -// Helper for codegen related visitor -//============================================================================= -class IspcCodegenTestHelper { - /** - * Shared pointer to the CodegenIspcVisitor that is used throughout the test - */ - std::shared_ptr codegen_ispc_visitor; - - /** - * Driver to parse the nmodl text - */ - NmodlDriver driver; - - /** - * Shared pointer of AST generated by the parsed NMODL text - */ - std::shared_ptr ast; - - /** - * String buffer which saves the output after every call to CodegenIspcVisitor - */ - std::stringbuf strbuf; - - public: - /** - * \brief Constructs the helper class for the unit tests of CodegenIspcVisitor - * - * Takes as argument the nmodl text and sets up the CodegenIspcVisitor. - * - * \param nmodl_text NMODL text to be parsed and printed - * - */ - IspcCodegenTestHelper(const std::string& nmodl_text) { - ast = driver.parse_string(nmodl_text); - - /// directory where units lib file is located - std::string units_dir(NrnUnitsLib::get_path()); - /// parse units of text - UnitsVisitor(units_dir).visit_program(*ast); - - /// construct symbol table - SymtabVisitor().visit_program(*ast); - - /// initialize CodegenIspcVisitor - std::ostream oss(&strbuf); - codegen_ispc_visitor = - std::make_shared("unit_test", oss, "double", false); - codegen_ispc_visitor->setup(*ast); - } - - /** - * Get CodegenIspcVisitor to call its functions - */ - std::shared_ptr get_visitor() { - return codegen_ispc_visitor; - } - - /** - * Returns the existing string and resets the buffer for next call - */ - std::string get_string() { - std::string return_string = strbuf.str(); - strbuf.str(""); - return return_string; - } -}; - -std::string print_ispc_nmodl_constants(IspcCodegenTestHelper& ispc_codegen_test_helper) { - /// print nmodl constants - ispc_codegen_test_helper.get_visitor()->print_nmodl_constants(); - - return ispc_codegen_test_helper.get_string(); -} - -std::string print_ispc_compute_functions(IspcCodegenTestHelper& ispc_codegen_test_helper) { - /// print compute functions - ispc_codegen_test_helper.get_visitor()->print_compute_functions(); - - return ispc_codegen_test_helper.get_string(); -} - - -SCENARIO("ISPC codegen", "[codegen][ispc]") { - GIVEN("Simple mod file") { - std::string nmodl_text = R"( - TITLE UnitTest - NEURON { - SUFFIX unit_test - RANGE a, b - } - ASSIGNED { - a - b - } - UNITS { - FARADAY = (faraday) (coulomb) - } - INITIAL { - a = 0.0 - b = 0.0 - } - BREAKPOINT { - a = b + FARADAY - } - )"; - - std::string nmodl_constants_declaration = R"( - /** constants used in nmodl */ - static const uniform double FARADAY = 96485.3321233100141d; - )"; - - std::string nrn_init_state_block = R"( - /** initialize channel */ - export void nrn_init_unit_test(uniform unit_test_Instance* uniform inst, uniform NrnThread* uniform nt, uniform Memb_list* uniform ml, uniform int type) { - uniform int nodecount = ml->nodecount; - uniform int pnodecount = ml->_nodecount_padded; - const int* uniform node_index = ml->nodeindices; - double* uniform data = ml->data; - const double* uniform voltage = nt->_actual_v; - Datum* uniform indexes = ml->pdata; - ThreadDatum* uniform thread = ml->_thread; - - int uniform start = 0; - int uniform end = nodecount; - foreach (id = start ... end) { - int node_id = node_index[id]; - double v = voltage[node_id]; - #if NRN_PRCELLSTATE - inst->v_unused[id] = v; - #endif - inst->a[id] = 0.0d; - inst->b[id] = 0.0d; - } - } - - - /** update state */ - export void nrn_state_unit_test(uniform unit_test_Instance* uniform inst, uniform NrnThread* uniform nt, uniform Memb_list* uniform ml, uniform int type) { - uniform int nodecount = ml->nodecount; - uniform int pnodecount = ml->_nodecount_padded; - const int* uniform node_index = ml->nodeindices; - double* uniform data = ml->data; - const double* uniform voltage = nt->_actual_v; - Datum* uniform indexes = ml->pdata; - ThreadDatum* uniform thread = ml->_thread; - - int uniform start = 0; - int uniform end = nodecount; - foreach (id = start ... end) { - int node_id = node_index[id]; - double v = voltage[node_id]; - #if NRN_PRCELLSTATE - inst->v_unused[id] = v; - #endif - inst->a[id] = inst->b[id] + FARADAY; - } - } - )"; - - IspcCodegenTestHelper ispc_codegen_test_helper(nmodl_text); - THEN("Check that the nmodl constants and computer functions are printed correctly") { - auto nmodl_constants_result = reindent_text( - print_ispc_nmodl_constants(ispc_codegen_test_helper)); - REQUIRE(nmodl_constants_result == reindent_text(nmodl_constants_declaration)); - auto nmodl_init_cur_state = reindent_text( - print_ispc_compute_functions(ispc_codegen_test_helper)); - REQUIRE(nmodl_init_cur_state == reindent_text(nrn_init_state_block)); - } - } -} diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index ee1c1899bf..7ba1329b26 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -8,7 +8,6 @@ #include #include "codegen/codegen_c_visitor.hpp" -#include "codegen/codegen_ispc_visitor.hpp" #include "codegen/codegen_utils.hpp" using namespace nmodl; @@ -84,78 +83,3 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { } } } - -SCENARIO("ISPC codegen utility functions", "[codegen][util][ispc]") { - GIVEN("Double constant as string") { - input_result_map tests({{"0.012345678901234567", "0.012345678901234567d"}, - {".012345678901234567", "0.012345678901234567d"}, - {"123.", "123.d"}}); - - THEN("Codegen ISPC Visitor prints double with same precision") { - for (const auto& test: tests) { - REQUIRE(codegen::utils::format_double_string(test.first) == - test.second); - } - } - } - - GIVEN("Integer double constant as string") { - std::string double_constant = "1"; - - std::string codegen_output = "1.0d"; - - THEN("Codegen ISPC Visitor prints integer as double number") { - auto nmodl_constant_result = codegen::utils::format_double_string( - double_constant); - REQUIRE(nmodl_constant_result == codegen_output); - } - } - - GIVEN("Double constants in scientific notation as strings") { - input_result_map tests( - {{"1e+18", "1d+18"}, {"1e-18", "1d-18"}, {".123e18", ".123d18"}, {"1E18", "1d18"}}); - - THEN("Codegen ISPC Visitor prints doubles with scientific notation") { - for (const auto& test: tests) { - REQUIRE(codegen::utils::format_double_string(test.first) == - test.second); - } - } - } - - GIVEN("Float constant as string") { - input_result_map tests( - {{"0.01234567", "0.01234567f"}, {".01234567", "0.01234567f"}, {"123.", "123.f"}}); - - THEN("Codegen ISPC Visitor prints float with same precision") { - for (const auto& test: tests) { - REQUIRE(codegen::utils::format_float_string(test.first) == - test.second); - } - } - } - - GIVEN("Integer float constant as string") { - std::string float_constant = "1"; - - std::string codegen_output = "1.0f"; - - THEN("Codegen ISPC Visitor prints integer as double number") { - auto nmodl_constant_result = codegen::utils::format_float_string( - float_constant); - REQUIRE(nmodl_constant_result == codegen_output); - } - } - - GIVEN("Float constants in scientific notation as strings") { - input_result_map tests( - {{"1e+18", "1e+18"}, {"1e-18", "1e-18"}, {".123e18", ".123e18"}, {"1E18", "1E18"}}); - - THEN("Codegen ISPC Visitor prints doubles with scientific notation") { - for (const auto& test: tests) { - REQUIRE(codegen::utils::format_float_string(test.first) == - test.second); - } - } - } -} diff --git a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp b/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp deleted file mode 100644 index f5edb9908a..0000000000 --- a/test/nmodl/transpiler/unit/visitor/ispc_rename.cpp +++ /dev/null @@ -1,93 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#include - -#include "ast/program.hpp" -#include "parser/nmodl_driver.hpp" -#include "test/unit/utils/nmodl_constructs.hpp" -#include "visitors/ispc_rename_visitor.hpp" -#include "visitors/lookup_visitor.hpp" -#include "visitors/symtab_visitor.hpp" -#include "visitors/verbatim_visitor.hpp" - -using namespace nmodl; -using namespace visitor; -using namespace test_utils; - -using nmodl::parser::NmodlDriver; - -//============================================================================= -// IspcRename visitor tests -//============================================================================= - -std::shared_ptr run_ispc_rename_visitor(const std::string& text) { - NmodlDriver driver; - auto ast = driver.parse_string(text); - - IspcRenameVisitor(ast).visit_program(*ast); - SymtabVisitor().visit_program(*ast); - return ast; -} - -std::vector run_verbatim_visitor(const std::shared_ptr& ast) { - NmodlDriver driver; - - VerbatimVisitor v; - v.visit_program(*ast); - - return v.verbatim_blocks(); -} - -SCENARIO("Rename variables that ISPC parses as double constants", "[visitor][ispcrename]") { - GIVEN("mod file with variables with names that resemble for ISPC double constants") { - std::string input_nmodl = R"( - NEURON { - SUFFIX test_ispc_rename - RANGE d1, d2, var_d3, d4 - } - ASSIGNED { - d1 - d2 - var_d3 - d4 - } - INITIAL { - d1 = 1 - d2 = 2 - var_d3 = 3 - } - PROCEDURE func () { - VERBATIM d4 = 4; ENDVERBATIM - } - )"; - auto ast = run_ispc_rename_visitor(input_nmodl); - auto verbatim_blocks = run_verbatim_visitor(ast); - auto symtab = ast->get_symbol_table(); - THEN( - "Variables that match the constant double presentation in ISPC are renamed to " - "var_") { - /// check if var_d1 and var_d2 exist and replaced d1 and d2 - auto var_d1 = symtab->lookup("var_d1"); - REQUIRE(var_d1 != nullptr); - auto d1 = symtab->lookup("d1"); - REQUIRE(d1 == nullptr); - auto var_d2 = symtab->lookup("var_d2"); - REQUIRE(var_d2 != nullptr); - auto d2 = symtab->lookup("d2"); - REQUIRE(d2 == nullptr); - /// Check if VERBATIM block variable is renamed - REQUIRE(verbatim_blocks.size() == 1); - REQUIRE(verbatim_blocks.front() == " var_d4 = 4; "); - } - THEN("Variables that don't match the constant double presentation in ISPC stay the same") { - /// check if var_d3 exists - auto var_d3 = symtab->lookup("var_d3"); - REQUIRE(var_d3 != nullptr); - } - } -} From a0a89c4396ac0636b707a75e189cc92ebc4665de Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 20 Oct 2022 13:49:25 +0200 Subject: [PATCH 472/871] Remove useless ptr_type_qualifier() (BlueBrain/nmodl#965) NMODL Repo SHA: BlueBrain/nmodl@986d7324072a6b1d95b0e45bb0016a6f57ba480d --- src/nmodl/codegen/codegen_c_visitor.cpp | 37 +++++++------------ src/nmodl/codegen/codegen_c_visitor.hpp | 9 ----- .../unit/codegen/codegen_c_visitor.cpp | 13 +++---- 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 96e0a413d8..5802968a8e 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1254,11 +1254,6 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) const { } -// note extra empty space for pretty-printing if we skip the symbol -std::string CodegenCVisitor::ptr_type_qualifier() { - return "__restrict__ "; -} - std::string CodegenCVisitor::global_var_struct_type_qualifier() { return ""; } @@ -3063,9 +3058,8 @@ void CodegenCVisitor::print_mechanism_range_var_structure(bool print_initialiser for (auto const& [var, type]: info.neuron_global_variables) { auto const name = var->get_name(); - printer->fmt_line("{}* {}{}{};", + printer->fmt_line("{}* {}{};", type, - ptr_type_qualifier(), name, print_initialisers ? fmt::format("{{&coreneuron::{}}}", name) : std::string{}); @@ -3074,20 +3068,17 @@ void CodegenCVisitor::print_mechanism_range_var_structure(bool print_initialiser auto name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? "const " : ""; - printer->fmt_line( - "{}{}* {}{}{};", qualifier, type, ptr_type_qualifier(), name, value_initialise); + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialise); } for (auto& var: codegen_int_variables) { auto name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? "const " : ""; - printer->fmt_line( - "{}{}* {}{}{};", qualifier, int_type, ptr_type_qualifier(), name, value_initialise); + printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialise); } else { auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->fmt_line( - "{}{}* {}{}{};", qualifier, type, ptr_type_qualifier(), name, value_initialise); + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialise); } } @@ -3369,25 +3360,23 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, } printer->add_line("int nodecount = ml->nodecount;"); printer->add_line("int pnodecount = ml->_nodecount_padded;"); - printer->fmt_line("const int* {}node_index = ml->nodeindices;", ptr_type_qualifier()); - printer->fmt_line("double* {}data = ml->data;", ptr_type_qualifier()); - printer->fmt_line("const double* {}voltage = nt->_actual_v;", ptr_type_qualifier()); + printer->add_line("const int* node_index = ml->nodeindices;"); + printer->add_line("double* data = ml->data;"); + printer->add_line("const double* voltage = nt->_actual_v;"); if (type == BlockType::Equation) { - printer->fmt_line("double* {} vec_rhs = nt->_actual_rhs;", ptr_type_qualifier()); - printer->fmt_line("double* {} vec_d = nt->_actual_d;", ptr_type_qualifier()); + printer->add_line("double* vec_rhs = nt->_actual_rhs;"); + printer->add_line("double* vec_d = nt->_actual_d;"); print_rhs_d_shadow_variables(); } - printer->fmt_line("Datum* {}indexes = ml->pdata;", ptr_type_qualifier()); - printer->fmt_line("ThreadDatum* {}thread = ml->_thread;", ptr_type_qualifier()); + printer->add_line("Datum* indexes = ml->pdata;"); + printer->add_line("ThreadDatum* thread = ml->_thread;"); if (type == BlockType::Initial) { printer->add_newline(); printer->add_line("setup_instance(nt, ml);"); } - printer->fmt_line("auto* const {1}inst = static_cast<{0}*>(ml->instance);", - instance_struct(), - ptr_type_qualifier()); + printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); printer->add_newline(1); } @@ -3933,7 +3922,7 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { print_kernel_data_present_annotation_block_begin(); - printer->fmt_line("NetReceiveBuffer_t* {}nrb = ml->_net_receive_buffer;", ptr_type_qualifier()); + printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); if (need_mech_inst) { printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index 9a9dd24a58..c4b1bc1e3f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -878,15 +878,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual std::string compute_method_name(BlockType type) const; - /** - * The used pointer qualifier. - * - * For C code generation this is \c \_\_restrict\_\_ to ensure that the compiler is aware of - * the fact that we are working only with non-overlapping memory regions - * \return \c \_\_restrict\_\_ - */ - virtual std::string ptr_type_qualifier(); - /** * The used global type qualifier * diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp index 27565fb35f..563ae7a386 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp @@ -309,9 +309,9 @@ SCENARIO("Check parameter constness with VERBATIM block", std::string expected_code = R"( /** all mechanism instance variables and global variables */ struct IntervalFire_Instance { - double* __restrict__ invl{}; - const double* __restrict__ burst_start{}; - double* __restrict__ v_unused{}; + double* invl{}; + const double* burst_start{}; + double* v_unused{}; IntervalFire_Store* global{&IntervalFire_global}; }; )"; @@ -334,10 +334,9 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", )"; THEN("The instance struct should contain these variables") { auto const generated = get_instance_structure(nmodl_text); - REQUIRE_THAT(generated, Contains("double* __restrict__ celsius{&coreneuron::celsius}")); - REQUIRE_THAT(generated, Contains("double* __restrict__ pi{&coreneuron::pi}")); - REQUIRE_THAT(generated, - Contains("int* __restrict__ secondorder{&coreneuron::secondorder}")); + REQUIRE_THAT(generated, Contains("double* celsius{&coreneuron::celsius}")); + REQUIRE_THAT(generated, Contains("double* pi{&coreneuron::pi}")); + REQUIRE_THAT(generated, Contains("int* secondorder{&coreneuron::secondorder}")); } } GIVEN("A MOD file that implicitly uses global variables") { From ad3bf0bd80d94bd40071d2b0e5509c545b5ed2c5 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 20 Oct 2022 14:39:56 +0200 Subject: [PATCH 473/871] Independent is now useless, only check if variable is valid (BlueBrain/nmodl#966) * Independent is now use less, only check if variable is valid INDEPENDENT are now doing nothing in nrn, do the same here * Format * minor fix to make variable names readable (should be printed separately) * Add reminder about possible use case of nmodl-format Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@16a09e185d545f06350999a732ab1349ecdd3a8e --- src/nmodl/language/code_generator.cmake | 1 - src/nmodl/language/nmodl.yaml | 41 ++----------------- src/nmodl/language/node_info.py | 1 - .../templates/visitors/nmodl_visitor.hpp | 4 ++ src/nmodl/parser/nmodl.yy | 13 +++--- .../visitors/semantic_analysis_visitor.cpp | 15 +++++++ .../visitors/semantic_analysis_visitor.hpp | 4 ++ .../unit/utils/nmodl_constructs.cpp | 4 ++ .../unit/visitor/semantic_analysis.cpp | 24 +++++++++++ 9 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 817f22c676..9d9705edc1 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -103,7 +103,6 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/if_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/include.hpp ${PROJECT_BINARY_DIR}/src/ast/independent_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/independent_definition.hpp ${PROJECT_BINARY_DIR}/src/ast/indexed_name.hpp ${PROJECT_BINARY_DIR}/src/ast/initial_block.hpp ${PROJECT_BINARY_DIR}/src/ast/integer.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 0024d4e436..77607044f4 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -450,10 +450,11 @@ - IndependentBlock: nmodl: "INDEPENDENT " members: - - definitions: - brief: "TODO" - type: IndependentDefinition + - variables: + brief: "List of variable that should be independent" + type: Name vector: true + separator: " " brief: "Represents a `INDEPENDENT` block in the NMODL" description: | `INDEPENDENT` has following form in the NMODL specification : @@ -1322,40 +1323,6 @@ optional: true prefix: {value: " "} - - IndependentDefinition: - brief: "TODO" - members: - - sweep: - brief: "TODO" - type: Boolean - optional: true - nmodl: "SWEEP " - - name: - brief: "TODO" - type: Name - - from: - brief: "TODO" - type: Number - prefix: {value: " FROM "} - - to: - brief: "TODO" - type: Number - prefix: {value: " TO "} - - with: - brief: "TODO" - type: Integer - prefix: {value: " WITH "} - - start: - brief: "TODO" - type: Number - prefix: {value: " START "} - optional: true - - unit: - brief: "TODO" - type: Unit - optional: true - prefix: {value: " "} - - AssignedDefinition: members: - name: diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index f305c021a7..a491842357 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -105,7 +105,6 @@ # when translating back to nmodl, we need print each statement # to new line. Those nodes are are used from this list. STATEMENT_TYPES = {"Statement", - "IndependentDefinition", "AssignedDefinition", "ParamAssign", "ConstantStatement", diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index e7d66b5333..e1bdc56a4f 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -32,6 +32,10 @@ namespace visitor { /** * \class NmodlPrintVisitor * \brief %Visitor for printing AST back to NMODL + * \todo Note that AstNodeType::INDEPENDENT_BLOCK is now trimmed-down + * in the AST. So if we need to make provide something like + * `nmodl-format` then we should exclude this node type i.e. + * add that in the exclude_types. */ class NmodlPrintVisitor: public ConstVisitor { private: diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 669ba87e0c..344aa4ad63 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -253,11 +253,11 @@ %type optional_start %type lag_statement %type parameter_assignment -%type independent_definition +%type independent_definition %type dependent_definition %type declare %type parameter_block_body -%type independent_block_body +%type independent_block_body %type dependent_block_body %type watch_statement %type watch_direction @@ -655,18 +655,18 @@ double : REAL ; +/** We still parse INDEPENDENT block but we do nothing with it, except checking that the + variable is `t` later. */ independent_block : INDEPENDENT "{" independent_block_body "}" { $$ = new ast::IndependentBlock($3); - ModToken block_token = $1 + $4; - $$->set_token(block_token); } ; independent_block_body : { - $$ = ast::IndependentDefinitionVector(); + $$ = ast::NameVector(); } | independent_block_body independent_definition { @@ -676,7 +676,6 @@ independent_block_body : | independent_block_body SWEEP independent_definition { $1.emplace_back($3); - $3->set_sweep(std::make_shared(1)); $$ = $1; } ; @@ -684,7 +683,7 @@ independent_block_body : independent_definition : NAME_PTR FROM number TO number withby integer optional_start units { - $$ = new ast::IndependentDefinition(NULL, $1, $3, $5, $7, $8, $9); + $$ = $1; } | error { diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 602b2eb502..a2df30cec3 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -1,7 +1,9 @@ #include "visitors/semantic_analysis_visitor.hpp" #include "ast/function_block.hpp" +#include "ast/independent_block.hpp" #include "ast/procedure_block.hpp" #include "ast/program.hpp" +#include "ast/string.hpp" #include "ast/suffix.hpp" #include "ast/table_statement.hpp" #include "symtab/symbol_properties.hpp" @@ -101,5 +103,18 @@ void SemanticAnalysisVisitor::visit_destructor_block(const ast::DestructorBlock& /// --> } +void SemanticAnalysisVisitor::visit_independent_block(const ast::IndependentBlock& node) { + /// <-- This code is for check 5 + for (const auto& n: node.get_variables()) { + if (n->get_value()->get_value() != "t") { + logger->warn( + "SemanticAnalysisVisitor :: '{}' cannot be used as an independent variable, only " + "'t' is allowed.", + n->get_value()->get_value()); + } + } + /// --> +} + } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 35fef8d44b..f89cbd47a9 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -28,6 +28,7 @@ * 2. Check that destructor blocks are only inside mod file that are point_process. * 3. A TABLE statement in functions cannot have name list, and should have one in procedures. * 4. Check if ion variables from a `USEION` statement are not declared in `CONSTANT` block. + * 5. Check if an independent variable is not 't'. */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -60,6 +61,9 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// Visit destructor and check that the file is of type POINT_PROCESS or ARTIFICIAL_CELL void visit_destructor_block(const ast::DestructorBlock& node) override; + /// Visit independent block and check if one of the variable is not t + void visit_independent_block(const ast::IndependentBlock& node) override; + public: SemanticAnalysisVisitor() = default; diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 3e4401c5c3..1bad61e079 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -431,6 +431,10 @@ std::map const nmodl_valid_constructs{ t FROM 0 TO 1 WITH 1 (ms) SWEEP u FROM 0 TO 1 WITH 1 (ms) } + )", + R"( + INDEPENDENT { + t u} )" } }, diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 2e2314c1de..624c48b76d 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -122,3 +122,27 @@ SCENARIO("Ion variable in CONSTANT block", "[visitor][semantic_analysis]") { } } } + +SCENARIO("INDEPENDENT block", "[visitor][semantic_analysis]") { + GIVEN("A mod file with Independent block with only t") { + std::string nmodl_text = R"( + INDEPENDENT { + t FROM 0 TO 1 WITH 100 + } + )"; + THEN("Semantic analysis succeed") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("A mod file with Independent block with something else than t") { + std::string nmodl_text = R"( + INDEPENDENT { + t FROM 0 TO 1 WITH 100 + u FROM 0 TO 1 WITH 100 + } + )"; + THEN("Semantic analysis fails") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From 93b8cb224ccc6b1f0f32efc7bb5b0f4521d760e9 Mon Sep 17 00:00:00 2001 From: Christos Kotsalos Date: Mon, 24 Oct 2022 12:53:32 +0200 Subject: [PATCH 474/871] Removal of Eigen's PartialPivLU and replacement with Crout LU decomposition (BlueBrain/nmodl#964) NMODL Repo SHA: BlueBrain/nmodl@888f2879b9cd581d32034224219f816fdbe8d77a --- cmake/nmodl/CompilerHelper.cmake | 4 + .../notebooks/nmodl-linear-solver.ipynb | 25 +-- src/nmodl/codegen/codegen_acc_visitor.cpp | 38 ++-- src/nmodl/codegen/codegen_acc_visitor.hpp | 6 +- src/nmodl/codegen/codegen_c_visitor.cpp | 71 ++++++- src/nmodl/codegen/codegen_c_visitor.hpp | 3 +- src/nmodl/nmodl.hpp | 1 + src/nmodl/solver/CMakeLists.txt | 7 +- src/nmodl/solver/crout/crout.hpp | 176 ++++++++++++++++++ src/nmodl/solver/newton/newton.hpp | 50 +++-- .../solver/partial_piv_lu/partial_piv_lu.cpp | 17 -- .../solver/partial_piv_lu/partial_piv_lu.h | 87 --------- test/nmodl/transpiler/unit/CMakeLists.txt | 2 + test/nmodl/transpiler/unit/crout/crout.cpp | 134 +++++++++++++ 14 files changed, 438 insertions(+), 183 deletions(-) create mode 100644 src/nmodl/solver/crout/crout.hpp delete mode 100644 src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp delete mode 100644 src/nmodl/solver/partial_piv_lu/partial_piv_lu.h create mode 100644 test/nmodl/transpiler/unit/crout/crout.cpp diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 86d31762e7..27377956f2 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -4,6 +4,10 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") # CMake adds standard complaint PGI flag "-A" which breaks compilation of of spdlog and fmt set(CMAKE_CXX14_STANDARD_COMPILE_OPTION --c++14) + # Avoid errors related to "excessive recursion at instantiation of function ...", Eigen-related + # (in accelerated regions), e.g., transposeInPlace() + list(APPEND NMODL_EXTRA_CXX_FLAGS "-Wc,--pending_instantiations=0") + # ~~~ # PGI enables number of diagnostic messages by default classes which results into thousands of # messages specifically for AST. Disable these verbose warnings for now. diff --git a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb index 6a8d2ba377..afa680bab5 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-linear-solver.ipynb @@ -12,34 +12,13 @@ "\n", "If the system is sufficiently small (by default $N\\leq3$), then Gaussian elimination is used to directly construct the solution at compile time using SymPy to do the symbolic Gaussian elimination. Optionally Common Subexpression Elimination (CSE) can also be performed.\n", "\n", - "For larger matrices it may not be numerically safe to solve them at compile time by Gaussian elimination, so instead the matrix equation is constructed and then solved at run time by LU factorization with partial pivoting, using the `PartialPivLU()` function from the Eigen header-only matrix algebra library." + "For larger matrices it may not be numerically safe to solve them at compile time by Gaussian elimination, so instead the matrix equation is constructed and then solved at run time by LU factorization with partial pivoting (for more, see Crout solver in `src/solver/crout` and `test/unit/crout`)." ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.7.7" + "name": "python" } }, "nbformat": 4, diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 651cfe6014..bf196a63a1 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -30,7 +30,8 @@ namespace codegen { * for(int id=0; idfmt_line("nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", - present_clause.str()); - printer->add_line("nrn_pragma_omp(target teams distribute parallel for if(nt->compute_gpu))"); + if (error_checking) { + printer->fmt_line( + "nrn_pragma_acc(parallel loop {} reduction(+:solver_error) async(nt->stream_id) " + "if(nt->compute_gpu))", + present_clause.str()); + printer->add_line( + "nrn_pragma_omp(target teams distribute parallel for reduction(+:solver_error) " + "if(nt->compute_gpu))"); + } else { + printer->fmt_line( + "nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", + present_clause.str()); + printer->add_line( + "nrn_pragma_omp(target teams distribute parallel for if(nt->compute_gpu))"); + } } @@ -71,15 +84,6 @@ void CodegenAccVisitor::print_backend_includes() { printer->add_line("#include "); printer->add_line("#include "); } - - if (info.eigen_linear_solver_exist && std::accumulate(info.state_vars.begin(), - info.state_vars.end(), - 0, - [](int l, const SymbolType& variable) { - return l += variable->get_length(); - }) > 4) { - printer->add_line("#include "); - } } @@ -131,14 +135,6 @@ void CodegenAccVisitor::print_net_send_buffering_grow() { // can not grow buffer during gpu execution } -void CodegenAccVisitor::print_eigen_linear_solver(const std::string& /* float_type */, int N) { - if (N <= 4) { - printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); - } else { - printer->fmt_line("nmodl_eigen_xm = partialPivLu<{}>nmodl_eigen_jm, nmodl_eigen_fm);", N); - } -} - /** * Each kernel like nrn_init, nrn_state and nrn_cur could be offloaded * to accelerator. In this case, at very top level, we print pragma diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 456ef76c70..651534cb34 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -38,7 +38,8 @@ class CodegenAccVisitor: public CodegenCVisitor { /// ivdep like annotation for channel iterations - void print_channel_iteration_block_parallel_hint(BlockType type) override; + void print_channel_iteration_block_parallel_hint(BlockType type, + bool error_checking = false) override; /// atomic update pragma for reduction statements @@ -128,9 +129,6 @@ class CodegenAccVisitor: public CodegenCVisitor { void print_net_send_buffering_grow() override; - void print_eigen_linear_solver(const std::string& float_type, int N) override; - - public: CodegenAccVisitor(const std::string& mod_file, const std::string& output_dir, diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 5802968a8e..923db5e193 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1108,7 +1108,8 @@ void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { * for(int id = 0; id < nodecount; id++) { * \endcode */ -void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */) { +void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, + bool /* error_checking */) { printer->add_line("#pragma ivdep"); printer->add_line("#pragma omp simd"); } @@ -1853,6 +1854,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->add_line("newton_functor.initialize();"); printer->add_line( "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); + printer->add_line("if (newton_iterations < 0) solver_error += 1;"); // assign newton solver results in matrix X to state vars print_statement_block(*node.get_update_states_block(), false, false); @@ -1866,6 +1868,10 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv int N = node.get_n_state_vars()->get_value(); printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); + if (N <= 4) { + printer->add_line("bool invertible;"); + printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm_inv;", float_type, N); + } printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); @@ -1884,11 +1890,30 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { if (N <= 4) { // Faster compared to LU, given the template specialization in Eigen. - printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm.inverse()*nmodl_eigen_fm;"); + printer->add_line("nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible);"); + printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm;"); + printer->add_line("if(!invertible) solver_error += 1;"); } else { + // In Eigen the default storage order is ColMajor. + // Crout's implementation requires matrices stored in RowMajor order (C-style arrays). + // Therefore, the transposeInPlace is critical such that the data() method to give the rows + // instead of the columns. + printer->add_line("if (!nmodl_eigen_jm.IsRowMajor) nmodl_eigen_jm.transposeInPlace();"); + + // pivot vector + printer->fmt_line("Eigen::Matrix pivot;", N); + + // In-place LU-Decomposition (Crout Algo) : Jm is replaced by its LU-decomposition + printer->fmt_line( + "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data()) < 0) " + "solver_error += 1;", + float_type, + N); + + // Solve the linear system : Forward/Backward substitution part printer->fmt_line( - "nmodl_eigen_xm = Eigen::PartialPivLU>>(nmodl_eigen_jm).solve(nmodl_eigen_fm);", + "nmodl::crout::solveCrout<{0}>({1}, nmodl_eigen_jm.data(), nmodl_eigen_fm.data(), " + "nmodl_eigen_xm.data(), pivot.data());", float_type, N); } @@ -2465,7 +2490,17 @@ void CodegenCVisitor::print_coreneuron_includes() { printer->add_line("#include "); } if (info.eigen_linear_solver_exist) { - printer->add_line("#include "); + if (std::accumulate(info.state_vars.begin(), + info.state_vars.end(), + 0, + [](int l, const SymbolType& variable) { + return l += variable->get_length(); + }) > 4) { + printer->add_line("#include "); + } else { + printer->add_line("#include "); + printer->add_line("#include "); + } } } @@ -3424,7 +3459,12 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_dt_update_to_device(); } - print_channel_iteration_block_parallel_hint(BlockType::Initial); + if (info.eigen_newton_solver_exist) { + printer->add_line("int solver_error = 0;"); + print_channel_iteration_block_parallel_hint(BlockType::Initial, true); + } else { + print_channel_iteration_block_parallel_hint(BlockType::Initial); + } printer->start_block("for (int id = 0; id < nodecount; id++)"); if (info.net_receive_node != nullptr) { @@ -3435,6 +3475,10 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { printer->end_block(1); print_shadow_reduction_statements(); + if (info.eigen_newton_solver_exist) + printer->add_line( + "if (solver_error > 0) throw std::runtime_error(\"Newton solver did not converge!\");"); + if (!info.changed_dt.empty()) { printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); print_dt_update_to_device(); @@ -4253,9 +4297,14 @@ void CodegenCVisitor::print_nrn_state() { printer->add_newline(2); printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); - print_channel_iteration_block_parallel_hint(BlockType::State); + if (info.eigen_newton_solver_exist || info.eigen_linear_solver_exist) { + printer->add_line("int solver_error = 0;"); + print_channel_iteration_block_parallel_hint(BlockType::State, true); + } else { + print_channel_iteration_block_parallel_hint(BlockType::State); + } printer->start_block("for (int id = 0; id < nodecount; id++)"); - + printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); print_v_unused(); @@ -4295,6 +4344,12 @@ void CodegenCVisitor::print_nrn_state() { } print_kernel_data_present_annotation_block_end(); + + if (info.eigen_newton_solver_exist) + printer->add_line("if (solver_error > 0) throw std::runtime_error(\"Newton solver did not converge!\");"); + if (info.eigen_linear_solver_exist) + printer->add_line("if (solver_error > 0) throw std::runtime_error(\"Singular matrices (Crout/Inverse)!\");"); + printer->end_block(1); codegen = false; } diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index c4b1bc1e3f..cd68655ff6 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1263,7 +1263,8 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * * \param type The block type */ - virtual void print_channel_iteration_block_parallel_hint(BlockType type); + virtual void print_channel_iteration_block_parallel_hint(BlockType type, + bool error_checking = false); /** diff --git a/src/nmodl/nmodl.hpp b/src/nmodl/nmodl.hpp index de18ef1b71..a0b2271734 100644 --- a/src/nmodl/nmodl.hpp +++ b/src/nmodl/nmodl.hpp @@ -16,4 +16,5 @@ #define EIGEN_DEFAULT_DENSE_INDEX_TYPE int #endif +#include "crout/crout.hpp" #include "newton/newton.hpp" diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index a2369739af..c4b4370d2b 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -2,6 +2,7 @@ # Solver sources # ============================================================================= set(NEWTON_SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) +set(CROUT_SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp) # ============================================================================= # Copy necessary files to build directory @@ -9,9 +10,9 @@ set(NEWTON_SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) # Newton file(GLOB NMODL_NEWTON_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/newton/*.h*") file(COPY ${NMODL_NEWTON_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/newton/) -# partial_piv_lu -file(GLOB NMODL_PARTIAL_PIV_LU_API_FILES "${CMAKE_CURRENT_SOURCE_DIR}/partial_piv_lu/*") -file(COPY ${NMODL_PARTIAL_PIV_LU_API_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/partial_piv_lu/) +# Crout +file(GLOB NMODL_CROUT_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/crout/*.h*") +file(COPY ${NMODL_CROUT_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/crout/) # Eigen file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY_DIR}/include/) diff --git a/src/nmodl/solver/crout/crout.hpp b/src/nmodl/solver/crout/crout.hpp new file mode 100644 index 0000000000..580864cd86 --- /dev/null +++ b/src/nmodl/solver/crout/crout.hpp @@ -0,0 +1,176 @@ +/************************************************************************* + * Copyright (C) 2018-2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ +#pragma once + +/** + * \dir + * \brief Solver for a system of linear equations : Crout matrix decomposition + * + * \file + * \brief Implementation of Crout matrix decomposition (LU decomposition) followed by + * Forward/Backward substitution + */ + +#include +#include + +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) +#include "coreneuron/utils/offload.hpp" +#endif + +namespace nmodl { +namespace crout { + +/** + * \brief Crout matrix decomposition : in-place LU Decomposition of matrix A. + * + * LU decomposition function. + * Implementation details : (Legacy code) coreneuron/sim/scopmath/crout* + * + * Description: + * This routine uses Crout's method to decompose a row interchanged + * version of the n x n matrix A into a lower triangular matrix L and a + * unit upper triangular matrix U such that A = LU. + * The matrices L and U replace the matrix A so that the original matrix + * A is destroyed. + * Note! In Crout's method the diagonal elements of U are 1 and are not stored. + * Note! The determinant of A is the product of the diagonal elements of L. (det A = det L * det U + * = det L). The LU decomposition is convenient when one needs to solve the linear equation Ax = B + * for the vector x while the matrix A is fixed and the vector B is varied. The routine for solving + * the linear system Ax = B after performing the LU decomposition for A is solveCrout (see below). + * + * The Crout method with partial pivoting is: Determine the pivot row and + * interchange the current row with the pivot row, then assuming that + * row k is the current row, k = 0, ..., n - 1 evaluate in order the + * the following pair of expressions + * L[i][k] = (A[i][k] - (L[i][0]*U[0][k] + . + L[i][k-1]*U[k-1][k])) + * for i = k, ... , n-1, + * U[k][j] = A[k][j] - (L[k][0]*U[0][j] + ... + L[k][k-1]*U[k-1][j]) / L[k][k] + * for j = k+1, ... , n-1. + * The matrix U forms the upper triangular matrix, and the matrix L + * forms the lower triangular matrix. + * + * \param n The number of rows or columns of the matrix A + * \param A matrix of size nxn : in-place LU decomposition (C-style arrays : row-major order) + * \param pivot matrix of size n : The i-th element is the pivot row interchanged with row i + * + * @return 0 for SUCCESS || -1 for FAILURE (The matrix A is singular) + */ +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) +nrn_pragma_acc(routine seq) +nrn_pragma_omp(declare target) +#endif +template +EIGEN_DEVICE_FUNC inline int Crout(int n, T* A, int* pivot) { + int i, j, k; + T *p_k{}, *p_row{}, *p_col{}; + T max; + + // For each row and column, k = 0, ..., n-1, + for (k = 0, p_k = A; k < n; p_k += n, k++) { + // find the pivot row + pivot[k] = k; + max = std::fabs(*(p_k + k)); + for (j = k + 1, p_row = p_k + n; j < n; j++, p_row += n) { + if (max < std::fabs(*(p_row + k))) { + max = std::fabs(*(p_row + k)); + pivot[k] = j; + p_col = p_row; + } + } + + // and if the pivot row differs from the current row, then + // interchange the two rows. + if (pivot[k] != k) + for (j = 0; j < n; j++) { + max = *(p_k + j); + *(p_k + j) = *(p_col + j); + *(p_col + j) = max; + } + + // and if the matrix is singular, return error + if (*(p_k + k) == 0.0) + return -1; + + // otherwise find the upper triangular matrix elements for row k. + for (j = k + 1; j < n; j++) { + *(p_k + j) /= *(p_k + k); + } + + // update remaining matrix + for (i = k + 1, p_row = p_k + n; i < n; p_row += n, i++) + for (j = k + 1; j < n; j++) + *(p_row + j) -= *(p_row + k) * *(p_k + j); + } + return 0; +} +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) +nrn_pragma_omp(end declare target) +#endif + +/** + * \brief Crout matrix decomposition : Forward/Backward substitution. + * + * Forward/Backward substitution function. + * Implementation details : (Legacy code) coreneuron/sim/scopmath/crout* + * + * \param n The number of rows or columns of the matrix LU + * \param LU LU-factorized matrix (C-style arrays : row-major order) + * \param B rhs vector + * \param x solution of (LU)x=B linear system + * \param pivot matrix of size n : The i-th element is the pivot row interchanged with row i + * + * @return 0 for SUCCESS || -1 for FAILURE (The matrix A is singular) + */ +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) +nrn_pragma_acc(routine seq) +nrn_pragma_omp(declare target) +#endif +template +EIGEN_DEVICE_FUNC inline int solveCrout(int n, T* LU, T* B, T* x, int* pivot) { + int i, k; + T* p_k; + T dum; + + // Solve the linear equation Lx = B for x, where L is a lower + // triangular matrix. + for (k = 0, p_k = LU; k < n; p_k += n, k++) { + if (pivot[k] != k) { + dum = B[k]; + B[k] = B[pivot[k]]; + B[pivot[k]] = dum; + } + x[k] = B[k]; + for (i = 0; i < k; i++) + x[k] -= x[i] * *(p_k + i); + x[k] /= *(p_k + k); + } + + // Solve the linear equation Ux = y, where y is the solution + // obtained above of Lx = B and U is an upper triangular matrix. + // The diagonal part of the upper triangular part of the matrix is + // assumed to be 1.0. + for (k = n - 1, p_k = LU + n * (n - 1); k >= 0; k--, p_k -= n) { + if (pivot[k] != k) { + dum = B[k]; + B[k] = B[pivot[k]]; + B[pivot[k]] = dum; + } + for (i = k + 1; i < n; i++) + x[k] -= x[i] * *(p_k + i); + if (*(p_k + k) == 0.0) + return -1; + } + + return 0; +} +#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) +nrn_pragma_omp(end declare target) +#endif + +} // namespace crout +} // namespace nmodl diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 76168caa99..7150d544b8 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -15,10 +15,9 @@ * \brief Implementation of Newton method for solving system of non-linear equations */ -#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) -#include "partial_piv_lu/partial_piv_lu.h" -#endif +#include +#include #include namespace nmodl { @@ -73,13 +72,19 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, // we have converged: return iteration count return iter; } -#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) - X -= partialPivLu(J, F); -#else - // update X use in-place LU decomposition of J with partial pivoting - // (suitable for any N, but less efficient than .inverse() for N <=4) - X -= Eigen::PartialPivLU>>(J).solve(F); -#endif + // In Eigen the default storage order is ColMajor. + // Crout's implementation requires matrices stored in RowMajor order (C-style arrays). + // Therefore, the transposeInPlace is critical such that the data() method to give the rows + // instead of the columns. + if (!J.IsRowMajor) + J.transposeInPlace(); + Eigen::Matrix pivot; + // Check if J is singular + if (nmodl::crout::Crout(N, J.data(), pivot.data()) < 0) + return -1; + Eigen::Matrix X_solve; + nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); + X -= X_solve; } // If we fail to converge after max_iter iterations, return -1 return -1; @@ -150,13 +155,15 @@ EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix& // restore X X[i] += dX; } -#if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) - X -= partialPivLu(J, F); -#else - // update X use in-place LU decomposition of J with partial pivoting - // (suitable for any N, but less efficient than .inverse() for N <=4) - X -= Eigen::PartialPivLU>>(J).solve(F); -#endif + if (!J.IsRowMajor) + J.transposeInPlace(); + Eigen::Matrix pivot; + // Check if J is singular + if (nmodl::crout::Crout(N, J.data(), pivot.data()) < 0) + return -1; + Eigen::Matrix X_solve; + nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); + X -= X_solve; } // If we fail to converge after max_iter iterations, return -1 return -1; @@ -173,8 +180,9 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, FUNC functor, double eps, int max_iter) { + bool invertible; Eigen::Matrix F; - Eigen::Matrix J; + Eigen::Matrix J, J_inv; int iter = -1; while (++iter < max_iter) { functor(X, F, J); @@ -184,7 +192,11 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, } // The inverse can be called from within OpenACC regions without any issue, as opposed to // Eigen::PartialPivLU. - X -= J.inverse() * F; + J.computeInverseWithCheck(J_inv, invertible); + if (invertible) + X -= J_inv * F; + else + return -1; } return -1; } diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp deleted file mode 100644 index 92dee20b88..0000000000 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ -#define NMODL_EIGEN_NO_OPENACC // disable OpenACC/OpenMP annotations -#define NMODL_LEAVE_PartialPivLuInstantiations_DEFINED // import PartialPivLuInstantiations -#include "partial_piv_lu/partial_piv_lu.h" - -// See explanation in partial_piv_lu.h -#define InstantiatePartialPivLu(N) \ - NMODL_EIGEN_ATTR VecType partialPivLu##N(const MatType& A, const VecType& b) { \ - return A.partialPivLu().solve(b); \ - } -PartialPivLuInstantiations -#undef InstantiatePartialPivLu diff --git a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h b/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h deleted file mode 100644 index 69c1b603e0..0000000000 --- a/src/nmodl/solver/partial_piv_lu/partial_piv_lu.h +++ /dev/null @@ -1,87 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ -#pragma once - -#ifndef NMODL_EIGEN_NO_OPENACC -#include "coreneuron/utils/offload.hpp" -#endif - -#include -#include - -template -using MatType = Eigen::Matrix; - -template -using VecType = Eigen::Matrix; - -// We want to declare a function template that is callable from OpenMP and -// OpenACC code, but whose instantiations are compiled by nvc++ -cuda. This is to avoid -// Eigen internals having to be digested by OpenACC/OpenMP compilers. The -// problem is that it is apparently not sufficient to declare a template in a OpenMP -// declare target region and have the OpenMP compiler assume that device -// versions of instantations of it will be available. The convoluted approach -// here has two ingredients: -// - partialPivLu(...), a function template that has explicit -// specialisations visible in this header file that call: -// - partialPivLuN(...), functions that are declared in this header but defined -// in the C++/CUDA file partial_piv_lu.cpp. -// -// It does not seem to work to generically define the template function -// partialPivLu in the .cpp file and then explicitly instantate it for 1..16 -// there; the relevant device functions are not generated. A possible workaround -// is to define the template + a dummy function that calls many different -// instantiations of the template in the .cpp file, but this seems more fragile -// than the present approach. -#ifdef NMODL_EIGEN_NO_OPENACC -#define NMODL_EIGEN_ATTR __host__ __device__ -#define NMODL_EIGEN_ROUTINE_SEQ -#else -nrn_pragma_omp(declare target) -#define NMODL_EIGEN_ATTR -#define NMODL_EIGEN_ROUTINE_SEQ nrn_pragma_acc(routine seq) -#endif -NMODL_EIGEN_ROUTINE_SEQ -template -NMODL_EIGEN_ATTR VecType partialPivLu(const MatType&, const VecType&); -#define InstantiatePartialPivLu(N) \ - NMODL_EIGEN_ROUTINE_SEQ \ - NMODL_EIGEN_ATTR VecType partialPivLu##N(const MatType&, const VecType&); \ - NMODL_EIGEN_ROUTINE_SEQ \ - template <> \ - NMODL_EIGEN_ATTR inline VecType partialPivLu(const MatType& A, const VecType& b) { \ - return partialPivLu##N(A, b); \ - } -// This PartialPivLuInstantiations is used in partial_piv_lu.cpp with a -// different definition of the InstantiatePartialPivLu macro. As of 2022-08-18 there is still an -// issue with instantiating for matrices larger than 16x16. -#define PartialPivLuInstantiations \ - InstantiatePartialPivLu(1) \ - InstantiatePartialPivLu(2) \ - InstantiatePartialPivLu(3) \ - InstantiatePartialPivLu(4) \ - InstantiatePartialPivLu(5) \ - InstantiatePartialPivLu(6) \ - InstantiatePartialPivLu(7) \ - InstantiatePartialPivLu(8) \ - InstantiatePartialPivLu(9) \ - InstantiatePartialPivLu(10) \ - InstantiatePartialPivLu(11) \ - InstantiatePartialPivLu(12) \ - InstantiatePartialPivLu(13) \ - InstantiatePartialPivLu(14) \ - InstantiatePartialPivLu(15) \ - InstantiatePartialPivLu(16) -PartialPivLuInstantiations -#undef InstantiatePartialPivLu -#ifndef NMODL_EIGEN_NO_OPENACC -nrn_pragma_omp(end declare target) -#endif -// partial_piv_lu.cpp will request that this is left defined -#ifndef NMODL_LEAVE_PartialPivLuInstantiations_DEFINED -#undef PartialPivLuInstantiations -#endif diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index c535edcf8a..d9cb0e3880 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -61,6 +61,7 @@ add_executable( add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${NEWTON_SOLVER_SOURCE_FILES}) +add_executable(testcrout crout/crout.cpp ${CROUT_SOLVER_SOURCE_FILES}) add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) @@ -123,6 +124,7 @@ foreach( testprinter testsymtab testnewton + testcrout testfast_math testunitlexer testunitparser) diff --git a/test/nmodl/transpiler/unit/crout/crout.cpp b/test/nmodl/transpiler/unit/crout/crout.cpp new file mode 100644 index 0000000000..da833d6867 --- /dev/null +++ b/test/nmodl/transpiler/unit/crout/crout.cpp @@ -0,0 +1,134 @@ +/************************************************************************* + * Copyright (C) 2018-2022 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#define CATCH_CONFIG_MAIN + +#include "nmodl.hpp" + +#include + +#include +#include +#include + +#include "Eigen/Dense" +#include "Eigen/LU" + +using namespace nmodl; +using namespace Eigen; +using namespace std; + + +/// https://stackoverflow.com/questions/15051367/how-to-compare-vectors-approximately-in-eigen +template +bool allclose(const Eigen::DenseBase& a, + const Eigen::DenseBase& b, + const typename DerivedA::RealScalar& rtol = + Eigen::NumTraits::dummy_precision(), + const typename DerivedA::RealScalar& atol = + Eigen::NumTraits::epsilon()) { + return ((a.derived() - b.derived()).array().abs() <= (atol + rtol * b.derived().array().abs())) + .all(); +} + + +template +bool test_Crout_correctness(T rtol = 1e-8, T atol = 1e-8) { + std::random_device rd; // seeding + auto seed = rd(); + std::mt19937 mt(seed); + std::uniform_real_distribution nums(-1e3, 1e3); + + std::chrono::duration eigen_timing(std::chrono::duration::zero()); + std::chrono::duration crout_timing(std::chrono::duration::zero()); + + auto t1 = std::chrono::high_resolution_clock::now(); + auto t2 = std::chrono::high_resolution_clock::now(); + + for (int mat_size = 5; mat_size < 15; mat_size++) { + Matrix A_ColMajor(mat_size, + mat_size); // default in Eigen! + Matrix A_RowMajor(mat_size, mat_size); + Matrix b(mat_size); + + for (int repetitions = 0; repetitions < static_cast(1e3); ++repetitions) { + do { + // initialization + for (int r = 0; r < mat_size; r++) { + for (int c = 0; c < mat_size; c++) { + A_ColMajor(r, c) = nums(mt); + A_RowMajor(r, c) = A_ColMajor(r, c); + b(r) = nums(mt); + } + } + } while (!A_ColMajor.fullPivLu().isInvertible()); // Checking Invertibility + + t1 = std::chrono::high_resolution_clock::now(); + // Eigen (ColMajor) + Matrix eigen_x_ColMajor(mat_size); + eigen_x_ColMajor = A_ColMajor.partialPivLu().solve(b); + + // Eigen (RowMajor) + Matrix eigen_x_RowMajor(mat_size); + eigen_x_RowMajor = A_RowMajor.partialPivLu().solve(b); + t2 = std::chrono::high_resolution_clock::now(); + eigen_timing += (t2 - t1); + + if (!allclose(eigen_x_ColMajor, eigen_x_RowMajor, rtol, atol)) { + cerr << "eigen_x_ColMajor vs eigen_x_RowMajor (issue) / seed = " << seed << endl; + return false; + } + + t1 = std::chrono::high_resolution_clock::now(); + // Crout with A_ColMajor + Matrix crout_x_ColMajor(mat_size); + if (!A_ColMajor.IsRowMajor) + A_ColMajor.transposeInPlace(); + Matrix pivot(mat_size); + crout::Crout(mat_size, A_ColMajor.data(), pivot.data()); + crout::solveCrout( + mat_size, A_ColMajor.data(), b.data(), crout_x_ColMajor.data(), pivot.data()); + + // Crout with A_RowMajor + Matrix crout_x_RowMajor(mat_size); + crout::Crout(mat_size, A_RowMajor.data(), pivot.data()); + crout::solveCrout( + mat_size, A_RowMajor.data(), b.data(), crout_x_RowMajor.data(), pivot.data()); + t2 = std::chrono::high_resolution_clock::now(); + crout_timing += (t2 - t1); + + if (!allclose(eigen_x_ColMajor, crout_x_ColMajor, rtol, atol)) { + cerr << "eigen_x_ColMajor vs crout_x_ColMajor (issue) / seed = " << seed << endl; + return false; + } + + if (!allclose(eigen_x_RowMajor, crout_x_RowMajor, rtol, atol)) { + cerr << "eigen_x_RowMajor vs crout_x_RowMajor (issue) / seed = " << seed << endl; + return false; + } + } + } + + std::cout << "eigen_timing [ms] : " << eigen_timing.count() * 1e3 << std::endl; + std::cout << "crout_timing [ms] : " << crout_timing.count() * 1e3 << std::endl; + + return true; +} + + +SCENARIO("Compare Crout solver with Eigen") { + GIVEN("crout (double)") { + constexpr double rtol = 1e-8; + constexpr double atol = 1e-8; + + auto test = test_Crout_correctness(rtol, atol); + + THEN("run tests & compare") { + REQUIRE(test); + } + } +} From 57224be0c8ac4c11624ab9c7548dbced40d76831 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 25 Oct 2022 09:33:51 +0200 Subject: [PATCH 475/871] Remove useless solve's method (BlueBrain/nmodl#967) See https://github.com/neuronsimulator/nrn/issues/2018 NMODL Repo SHA: BlueBrain/nmodl@1f6d430f190300c0041d4ac46b5170cccdb65eab --- docs/nmodl/transpiler/language.rst | 16 ---------------- src/nmodl/lexer/token_mapping.cpp | 10 +--------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index ff1b25d3a1..e3de8af2b6 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -161,24 +161,8 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | after_cvode | yes | no | | +------------------------+-------------------+-------------------+---------------------+ -| adams | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ -| adeuler | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ -| heun | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ -| adrunge | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ -| gear | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ -| simplex | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ | simeq | yes | no | | +------------------------+-------------------+-------------------+---------------------+ -| seidel | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ -| clsoda | yes | no | | -+------------------------+-------------------+-------------------+---------------------+ | cvode_t | yes | no | | +------------------------+-------------------+-------------------+---------------------+ | cvode_v | yes | no | | diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 0a8a25d459..632a0e3727 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -142,22 +142,14 @@ struct MethodInfo { * * \todo MethodInfo::subtype should be changed from integer flag to proper type */ -const static std::map methods = {{"adams", MethodInfo(DERF | KINF, 0)}, - {"runge", MethodInfo(DERF | KINF, 0)}, +const static std::map methods = {{"runge", MethodInfo(DERF | KINF, 0)}, {"euler", MethodInfo(DERF | KINF, 0)}, - {"adeuler", MethodInfo(DERF | KINF, 1)}, - {"heun", MethodInfo(DERF | KINF, 0)}, - {"adrunge", MethodInfo(DERF | KINF, 1)}, - {"gear", MethodInfo(DERF | KINF, 1)}, {"newton", MethodInfo(NLINF, 0)}, - {"simplex", MethodInfo(NLINF, 0)}, {"simeq", MethodInfo(LINF, 0)}, - {"seidel", MethodInfo(LINF, 0)}, {"_advance", MethodInfo(KINF, 0)}, {"sparse", MethodInfo(KINF, 0)}, {"derivimplicit", MethodInfo(DERF, 0)}, {"cnexp", MethodInfo(DERF, 0)}, - {"clsoda", MethodInfo(DERF | KINF, 1)}, {"after_cvode", MethodInfo(0, 0)}, {"cvode_t", MethodInfo(0, 0)}, {"cvode_t_v", MethodInfo(0, 0)}}; From 2b6240a11193d187bc079d40f2f2150e0df6beaf Mon Sep 17 00:00:00 2001 From: Christos Kotsalos Date: Tue, 25 Oct 2022 10:42:42 +0200 Subject: [PATCH 476/871] Solve bug in Crout code generation (BlueBrain/nmodl#968) NMODL Repo SHA: BlueBrain/nmodl@d463fbaff173457570384b6de0b8729e713277ea --- src/nmodl/codegen/codegen_c_visitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 923db5e193..1e9c1c7862 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -4343,13 +4343,13 @@ void CodegenCVisitor::print_nrn_state() { printer->end_block(1); } - print_kernel_data_present_annotation_block_end(); - if (info.eigen_newton_solver_exist) printer->add_line("if (solver_error > 0) throw std::runtime_error(\"Newton solver did not converge!\");"); if (info.eigen_linear_solver_exist) printer->add_line("if (solver_error > 0) throw std::runtime_error(\"Singular matrices (Crout/Inverse)!\");"); + print_kernel_data_present_annotation_block_end(); + printer->end_block(1); codegen = false; } From 9a16acb4a3ccbcc287e9a9faa50ef582a2e7cd41 Mon Sep 17 00:00:00 2001 From: Christos Kotsalos Date: Wed, 26 Oct 2022 12:55:04 +0200 Subject: [PATCH 477/871] Crout solver now compiles with nvhpc (problem with return statement) (BlueBrain/nmodl#969) NMODL Repo SHA: BlueBrain/nmodl@24323db0290be0ed9d3886063e8b1ba4083beb79 --- src/nmodl/solver/crout/crout.hpp | 37 ++++++++++++++++++++------------ 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/nmodl/solver/crout/crout.hpp b/src/nmodl/solver/crout/crout.hpp index 580864cd86..2bd7d02e47 100644 --- a/src/nmodl/solver/crout/crout.hpp +++ b/src/nmodl/solver/crout/crout.hpp @@ -66,6 +66,10 @@ nrn_pragma_omp(declare target) #endif template EIGEN_DEVICE_FUNC inline int Crout(int n, T* A, int* pivot) { + // roundoff is the minimal value for a pivot element without its being considered too close to + // zero + double roundoff = 1.e-20; + int status = 0; int i, j, k; T *p_k{}, *p_row{}, *p_col{}; T max; @@ -93,20 +97,21 @@ EIGEN_DEVICE_FUNC inline int Crout(int n, T* A, int* pivot) { } // and if the matrix is singular, return error - if (*(p_k + k) == 0.0) - return -1; + if (std::fabs(*(p_k + k)) < roundoff) { + status = -1; + } else { + // otherwise find the upper triangular matrix elements for row k. + for (j = k + 1; j < n; j++) { + *(p_k + j) /= *(p_k + k); + } - // otherwise find the upper triangular matrix elements for row k. - for (j = k + 1; j < n; j++) { - *(p_k + j) /= *(p_k + k); + // update remaining matrix + for (i = k + 1, p_row = p_k + n; i < n; p_row += n, i++) + for (j = k + 1; j < n; j++) + *(p_row + j) -= *(p_row + k) * *(p_k + j); } - - // update remaining matrix - for (i = k + 1, p_row = p_k + n; i < n; p_row += n, i++) - for (j = k + 1; j < n; j++) - *(p_row + j) -= *(p_row + k) * *(p_k + j); } - return 0; + return status; } #if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) nrn_pragma_omp(end declare target) @@ -132,6 +137,10 @@ nrn_pragma_omp(declare target) #endif template EIGEN_DEVICE_FUNC inline int solveCrout(int n, T* LU, T* B, T* x, int* pivot) { + // roundoff is the minimal value for a pivot element without its being considered too close to + // zero + double roundoff = 1.e-20; + int status = 0; int i, k; T* p_k; T dum; @@ -162,11 +171,11 @@ EIGEN_DEVICE_FUNC inline int solveCrout(int n, T* LU, T* B, T* x, int* pivot) { } for (i = k + 1; i < n; i++) x[k] -= x[i] * *(p_k + i); - if (*(p_k + k) == 0.0) - return -1; + if (std::fabs(*(p_k + k)) < roundoff) + status = -1; } - return 0; + return status; } #if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) nrn_pragma_omp(end declare target) From c98e0a566b075f5c241bc01b3a54bce41545fe63 Mon Sep 17 00:00:00 2001 From: Christos Kotsalos Date: Fri, 28 Oct 2022 08:35:35 +0200 Subject: [PATCH 478/871] Use the legacy Crout solver : nrn / scopmath / crout.c (+solver error checking with assert) (BlueBrain/nmodl#970) NMODL Repo SHA: BlueBrain/nmodl@14359293a393415ca774bdcc8fce04f1402b4ac3 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 21 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 3 +- src/nmodl/codegen/codegen_c_visitor.cpp | 43 ++-- src/nmodl/codegen/codegen_c_visitor.hpp | 3 +- src/nmodl/solver/crout/crout.hpp | 242 +++++++++++---------- src/nmodl/solver/newton/newton.hpp | 6 +- test/nmodl/transpiler/unit/crout/crout.cpp | 5 +- 7 files changed, 155 insertions(+), 168 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index bf196a63a1..f85e7367c2 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -30,8 +30,7 @@ namespace codegen { * for(int id=0; idfmt_line( - "nrn_pragma_acc(parallel loop {} reduction(+:solver_error) async(nt->stream_id) " - "if(nt->compute_gpu))", - present_clause.str()); - printer->add_line( - "nrn_pragma_omp(target teams distribute parallel for reduction(+:solver_error) " - "if(nt->compute_gpu))"); - } else { - printer->fmt_line( - "nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", - present_clause.str()); - printer->add_line( - "nrn_pragma_omp(target teams distribute parallel for if(nt->compute_gpu))"); - } + printer->fmt_line("nrn_pragma_acc(parallel loop {} async(nt->stream_id) if(nt->compute_gpu))", + present_clause.str()); + printer->add_line("nrn_pragma_omp(target teams distribute parallel for if(nt->compute_gpu))"); } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 651534cb34..076bc966b9 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -38,8 +38,7 @@ class CodegenAccVisitor: public CodegenCVisitor { /// ivdep like annotation for channel iterations - void print_channel_iteration_block_parallel_hint(BlockType type, - bool error_checking = false) override; + void print_channel_iteration_block_parallel_hint(BlockType type) override; /// atomic update pragma for reduction statements diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_c_visitor.cpp index 1e9c1c7862..d3289c36b5 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_c_visitor.cpp @@ -1108,8 +1108,7 @@ void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { * for(int id = 0; id < nodecount; id++) { * \endcode */ -void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, - bool /* error_checking */) { +void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */) { printer->add_line("#pragma ivdep"); printer->add_line("#pragma omp simd"); } @@ -1854,7 +1853,8 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->add_line("newton_functor.initialize();"); printer->add_line( "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); - printer->add_line("if (newton_iterations < 0) solver_error += 1;"); + printer->add_line( + "if (newton_iterations < 0) assert(false && \"Newton solver did not converge!\");"); // assign newton solver results in matrix X to state vars print_statement_block(*node.get_update_states_block(), false, false); @@ -1868,10 +1868,8 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv int N = node.get_n_state_vars()->get_value(); printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); - if (N <= 4) { - printer->add_line("bool invertible;"); + if (N <= 4) printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm_inv;", float_type, N); - } printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); @@ -1890,9 +1888,12 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { if (N <= 4) { // Faster compared to LU, given the template specialization in Eigen. + printer->add_line("bool invertible;"); printer->add_line("nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible);"); printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm;"); - printer->add_line("if(!invertible) solver_error += 1;"); + printer->add_line( + "if (!invertible) assert(false && \"Singular or ill-conditioned matrix " + "(Eigen::inverse)!\");"); } else { // In Eigen the default storage order is ColMajor. // Crout's implementation requires matrices stored in RowMajor order (C-style arrays). @@ -1902,11 +1903,12 @@ void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, i // pivot vector printer->fmt_line("Eigen::Matrix pivot;", N); + printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> rowmax;", float_type, N); // In-place LU-Decomposition (Crout Algo) : Jm is replaced by its LU-decomposition printer->fmt_line( - "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data()) < 0) " - "solver_error += 1;", + "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data(), rowmax.data()) " + "< 0) assert(false && \"Singular or ill-conditioned matrix (nmodl::crout)!\");", float_type, N); @@ -3459,12 +3461,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_dt_update_to_device(); } - if (info.eigen_newton_solver_exist) { - printer->add_line("int solver_error = 0;"); - print_channel_iteration_block_parallel_hint(BlockType::Initial, true); - } else { - print_channel_iteration_block_parallel_hint(BlockType::Initial); - } + print_channel_iteration_block_parallel_hint(BlockType::Initial); printer->start_block("for (int id = 0; id < nodecount; id++)"); if (info.net_receive_node != nullptr) { @@ -3475,10 +3472,6 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { printer->end_block(1); print_shadow_reduction_statements(); - if (info.eigen_newton_solver_exist) - printer->add_line( - "if (solver_error > 0) throw std::runtime_error(\"Newton solver did not converge!\");"); - if (!info.changed_dt.empty()) { printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); print_dt_update_to_device(); @@ -4297,12 +4290,7 @@ void CodegenCVisitor::print_nrn_state() { printer->add_newline(2); printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); - if (info.eigen_newton_solver_exist || info.eigen_linear_solver_exist) { - printer->add_line("int solver_error = 0;"); - print_channel_iteration_block_parallel_hint(BlockType::State, true); - } else { - print_channel_iteration_block_parallel_hint(BlockType::State); - } + print_channel_iteration_block_parallel_hint(BlockType::State); printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); @@ -4343,11 +4331,6 @@ void CodegenCVisitor::print_nrn_state() { printer->end_block(1); } - if (info.eigen_newton_solver_exist) - printer->add_line("if (solver_error > 0) throw std::runtime_error(\"Newton solver did not converge!\");"); - if (info.eigen_linear_solver_exist) - printer->add_line("if (solver_error > 0) throw std::runtime_error(\"Singular matrices (Crout/Inverse)!\");"); - print_kernel_data_present_annotation_block_end(); printer->end_block(1); diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_c_visitor.hpp index cd68655ff6..c4b1bc1e3f 100644 --- a/src/nmodl/codegen/codegen_c_visitor.hpp +++ b/src/nmodl/codegen/codegen_c_visitor.hpp @@ -1263,8 +1263,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * * \param type The block type */ - virtual void print_channel_iteration_block_parallel_hint(BlockType type, - bool error_checking = false); + virtual void print_channel_iteration_block_parallel_hint(BlockType type); /** diff --git a/src/nmodl/solver/crout/crout.hpp b/src/nmodl/solver/crout/crout.hpp index 2bd7d02e47..c1bbb8d92a 100644 --- a/src/nmodl/solver/crout/crout.hpp +++ b/src/nmodl/solver/crout/crout.hpp @@ -12,7 +12,7 @@ * * \file * \brief Implementation of Crout matrix decomposition (LU decomposition) followed by - * Forward/Backward substitution + * Forward/Backward substitution: Implementation details : (Legacy code) nrn / scopmath / crout.c */ #include @@ -26,92 +26,97 @@ namespace nmodl { namespace crout { /** - * \brief Crout matrix decomposition : in-place LU Decomposition of matrix A. + * \brief Crout matrix decomposition : in-place LU Decomposition of matrix a. * - * LU decomposition function. - * Implementation details : (Legacy code) coreneuron/sim/scopmath/crout* + * Implementation details : (Legacy code) nrn / scopmath / crout.c * - * Description: - * This routine uses Crout's method to decompose a row interchanged - * version of the n x n matrix A into a lower triangular matrix L and a - * unit upper triangular matrix U such that A = LU. - * The matrices L and U replace the matrix A so that the original matrix - * A is destroyed. - * Note! In Crout's method the diagonal elements of U are 1 and are not stored. - * Note! The determinant of A is the product of the diagonal elements of L. (det A = det L * det U - * = det L). The LU decomposition is convenient when one needs to solve the linear equation Ax = B - * for the vector x while the matrix A is fixed and the vector B is varied. The routine for solving - * the linear system Ax = B after performing the LU decomposition for A is solveCrout (see below). - * - * The Crout method with partial pivoting is: Determine the pivot row and - * interchange the current row with the pivot row, then assuming that - * row k is the current row, k = 0, ..., n - 1 evaluate in order the - * the following pair of expressions - * L[i][k] = (A[i][k] - (L[i][0]*U[0][k] + . + L[i][k-1]*U[k-1][k])) - * for i = k, ... , n-1, - * U[k][j] = A[k][j] - (L[k][0]*U[0][j] + ... + L[k][k-1]*U[k-1][j]) / L[k][k] - * for j = k+1, ... , n-1. - * The matrix U forms the upper triangular matrix, and the matrix L - * forms the lower triangular matrix. - * - * \param n The number of rows or columns of the matrix A - * \param A matrix of size nxn : in-place LU decomposition (C-style arrays : row-major order) - * \param pivot matrix of size n : The i-th element is the pivot row interchanged with row i - * - * @return 0 for SUCCESS || -1 for FAILURE (The matrix A is singular) + * Returns: 0 if no error; -1 if matrix is singular or ill-conditioned */ #if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) nrn_pragma_acc(routine seq) nrn_pragma_omp(declare target) #endif template -EIGEN_DEVICE_FUNC inline int Crout(int n, T* A, int* pivot) { +EIGEN_DEVICE_FUNC inline int Crout(int n, T* a, int* perm, double* rowmax) { // roundoff is the minimal value for a pivot element without its being considered too close to // zero double roundoff = 1.e-20; - int status = 0; - int i, j, k; - T *p_k{}, *p_row{}, *p_col{}; - T max; - - // For each row and column, k = 0, ..., n-1, - for (k = 0, p_k = A; k < n; p_k += n, k++) { - // find the pivot row - pivot[k] = k; - max = std::fabs(*(p_k + k)); - for (j = k + 1, p_row = p_k + n; j < n; j++, p_row += n) { - if (max < std::fabs(*(p_row + k))) { - max = std::fabs(*(p_row + k)); - pivot[k] = j; - p_col = p_row; + int i, j, k, r, pivot, irow, save_i = 0, krow; + T sum, equil_1, equil_2; + + /* Initialize permutation and rowmax vectors */ + + for (i = 0; i < n; i++) { + perm[i] = i; + k = 0; + for (j = 1; j < n; j++) + if (std::fabs(a[i * n + j]) > std::fabs(a[i * n + k])) + k = j; + rowmax[i] = a[i * n + k]; + } + + /* Loop over rows and columns r */ + + for (r = 0; r < n; r++) { + /* + * Operate on rth column. This produces the lower triangular matrix + * of terms needed to transform the constant vector. + */ + + for (i = r; i < n; i++) { + sum = 0.0; + irow = perm[i]; + for (k = 0; k < r; k++) { + krow = perm[k]; + sum += a[irow * n + k] * a[krow * n + r]; } + a[irow * n + r] -= sum; } - // and if the pivot row differs from the current row, then - // interchange the two rows. - if (pivot[k] != k) - for (j = 0; j < n; j++) { - max = *(p_k + j); - *(p_k + j) = *(p_col + j); - *(p_col + j) = max; - } + /* Find row containing the pivot in the rth column */ - // and if the matrix is singular, return error - if (std::fabs(*(p_k + k)) < roundoff) { - status = -1; - } else { - // otherwise find the upper triangular matrix elements for row k. - for (j = k + 1; j < n; j++) { - *(p_k + j) /= *(p_k + k); + pivot = perm[r]; + equil_1 = std::fabs(a[pivot * n + r] / rowmax[pivot]); + for (i = r + 1; i < n; i++) { + irow = perm[i]; + equil_2 = std::fabs(a[irow * n + r] / rowmax[irow]); + if (equil_2 > equil_1) { + /* make irow the new pivot row */ + + pivot = irow; + save_i = i; + equil_1 = equil_2; } + } + + /* Interchange entries in permutation vector if necessary */ + + if (pivot != perm[r]) { + perm[save_i] = perm[r]; + perm[r] = pivot; + } + + /* Check that pivot element is not too small */ - // update remaining matrix - for (i = k + 1, p_row = p_k + n; i < n; p_row += n, i++) - for (j = k + 1; j < n; j++) - *(p_row + j) -= *(p_row + k) * *(p_k + j); + if (std::fabs(a[pivot * n + r]) < roundoff) + return -1; + + /* + * Operate on row in rth position. This produces the upper + * triangular matrix whose diagonal elements are assumed to be unity. + * This matrix is used in the back substitution algorithm. + */ + + for (j = r + 1; j < n; j++) { + sum = 0.0; + for (k = 0; k < r; k++) { + krow = perm[k]; + sum += a[pivot * n + k] * a[krow * n + j]; + } + a[pivot * n + j] = (a[pivot * n + j] - sum) / a[pivot * n + r]; } } - return status; + return 0; } #if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) nrn_pragma_omp(end declare target) @@ -120,66 +125,77 @@ nrn_pragma_omp(end declare target) /** * \brief Crout matrix decomposition : Forward/Backward substitution. * - * Forward/Backward substitution function. - * Implementation details : (Legacy code) coreneuron/sim/scopmath/crout* - * - * \param n The number of rows or columns of the matrix LU - * \param LU LU-factorized matrix (C-style arrays : row-major order) - * \param B rhs vector - * \param x solution of (LU)x=B linear system - * \param pivot matrix of size n : The i-th element is the pivot row interchanged with row i + * Implementation details : (Legacy code) nrn / scopmath / crout.c * - * @return 0 for SUCCESS || -1 for FAILURE (The matrix A is singular) + * Returns: no return variable */ +#define y_(arg) p[y[arg]] +#define b_(arg) b[arg] #if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) nrn_pragma_acc(routine seq) nrn_pragma_omp(declare target) #endif template -EIGEN_DEVICE_FUNC inline int solveCrout(int n, T* LU, T* B, T* x, int* pivot) { - // roundoff is the minimal value for a pivot element without its being considered too close to - // zero - double roundoff = 1.e-20; - int status = 0; - int i, k; - T* p_k; - T dum; - - // Solve the linear equation Lx = B for x, where L is a lower - // triangular matrix. - for (k = 0, p_k = LU; k < n; p_k += n, k++) { - if (pivot[k] != k) { - dum = B[k]; - B[k] = B[pivot[k]]; - B[pivot[k]] = dum; +EIGEN_DEVICE_FUNC inline int solveCrout(int n, T* a, T* b, T* p, int* perm, int* y = (int*) 0) { + int i, j, pivot; + T sum; + + /* Perform forward substitution with pivoting */ + if (y) { + for (i = 0; i < n; i++) { + pivot = perm[i]; + sum = 0.0; + for (j = 0; j < i; j++) + sum += a[pivot * n + j] * (y_(j)); + y_(i) = (b_(pivot) - sum) / a[pivot * n + i]; } - x[k] = B[k]; - for (i = 0; i < k; i++) - x[k] -= x[i] * *(p_k + i); - x[k] /= *(p_k + k); - } - // Solve the linear equation Ux = y, where y is the solution - // obtained above of Lx = B and U is an upper triangular matrix. - // The diagonal part of the upper triangular part of the matrix is - // assumed to be 1.0. - for (k = n - 1, p_k = LU + n * (n - 1); k >= 0; k--, p_k -= n) { - if (pivot[k] != k) { - dum = B[k]; - B[k] = B[pivot[k]]; - B[pivot[k]] = dum; + /* + * Note that the y vector is already in the correct order for back + * substitution. Perform back substitution, pivoting the matrix but not + * the y vector. There is no need to divide by the diagonal element as + * this is assumed to be unity. + */ + + for (i = n - 1; i >= 0; i--) { + pivot = perm[i]; + sum = 0.0; + for (j = i + 1; j < n; j++) + sum += a[pivot * n + j] * (y_(j)); + y_(i) -= sum; + } + } else { + for (i = 0; i < n; i++) { + pivot = perm[i]; + sum = 0.0; + for (j = 0; j < i; j++) + sum += a[pivot * n + j] * (p[j]); + p[i] = (b_(pivot) - sum) / a[pivot * n + i]; } - for (i = k + 1; i < n; i++) - x[k] -= x[i] * *(p_k + i); - if (std::fabs(*(p_k + k)) < roundoff) - status = -1; - } - return status; + /* + * Note that the y vector is already in the correct order for back + * substitution. Perform back substitution, pivoting the matrix but not + * the y vector. There is no need to divide by the diagonal element as + * this is assumed to be unity. + */ + + for (i = n - 1; i >= 0; i--) { + pivot = perm[i]; + sum = 0.0; + for (j = i + 1; j < n; j++) + sum += a[pivot * n + j] * (p[j]); + p[i] -= sum; + } + } + return 0; } #if defined(CORENEURON_ENABLE_GPU) && !defined(DISABLE_OPENACC) nrn_pragma_omp(end declare target) #endif +#undef y_ +#undef b_ + } // namespace crout } // namespace nmodl diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 7150d544b8..17d89a2cb9 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -79,8 +79,9 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, if (!J.IsRowMajor) J.transposeInPlace(); Eigen::Matrix pivot; + Eigen::Matrix rowmax; // Check if J is singular - if (nmodl::crout::Crout(N, J.data(), pivot.data()) < 0) + if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) return -1; Eigen::Matrix X_solve; nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); @@ -158,8 +159,9 @@ EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix& if (!J.IsRowMajor) J.transposeInPlace(); Eigen::Matrix pivot; + Eigen::Matrix rowmax; // Check if J is singular - if (nmodl::crout::Crout(N, J.data(), pivot.data()) < 0) + if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) return -1; Eigen::Matrix X_solve; nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); diff --git a/test/nmodl/transpiler/unit/crout/crout.cpp b/test/nmodl/transpiler/unit/crout/crout.cpp index da833d6867..e23a642be6 100644 --- a/test/nmodl/transpiler/unit/crout/crout.cpp +++ b/test/nmodl/transpiler/unit/crout/crout.cpp @@ -89,13 +89,14 @@ bool test_Crout_correctness(T rtol = 1e-8, T atol = 1e-8) { if (!A_ColMajor.IsRowMajor) A_ColMajor.transposeInPlace(); Matrix pivot(mat_size); - crout::Crout(mat_size, A_ColMajor.data(), pivot.data()); + Matrix rowmax(mat_size); + crout::Crout(mat_size, A_ColMajor.data(), pivot.data(), rowmax.data()); crout::solveCrout( mat_size, A_ColMajor.data(), b.data(), crout_x_ColMajor.data(), pivot.data()); // Crout with A_RowMajor Matrix crout_x_RowMajor(mat_size); - crout::Crout(mat_size, A_RowMajor.data(), pivot.data()); + crout::Crout(mat_size, A_RowMajor.data(), pivot.data(), rowmax.data()); crout::solveCrout( mat_size, A_RowMajor.data(), b.data(), crout_x_RowMajor.data(), pivot.data()); t2 = std::chrono::high_resolution_clock::now(); From 2113f91d9bf27c2cba5ddb734aa1aff45376e26f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 30 Nov 2022 23:20:19 +0100 Subject: [PATCH 479/871] Move codegen_c_visitor to codegen_cpp_visitor (BlueBrain/nmodl#977) NMODL Repo SHA: BlueBrain/nmodl@94cba1ce58b7c4e2f4ec40365719297ca68706b0 --- src/nmodl/codegen/CMakeLists.txt | 2 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 2 +- .../codegen/{codegen_c_visitor.cpp => codegen_cpp_visitor.cpp} | 2 +- .../codegen/{codegen_c_visitor.hpp => codegen_cpp_visitor.hpp} | 0 src/nmodl/codegen/codegen_utils.cpp | 2 +- src/nmodl/main.cpp | 2 +- test/nmodl/transpiler/unit/CMakeLists.txt | 2 +- .../codegen/{codegen_c_visitor.cpp => codegen_cpp_visitor.cpp} | 2 +- test/nmodl/transpiler/unit/codegen/codegen_utils.cpp | 2 +- 9 files changed, 8 insertions(+), 8 deletions(-) rename src/nmodl/codegen/{codegen_c_visitor.cpp => codegen_cpp_visitor.cpp} (99%) rename src/nmodl/codegen/{codegen_c_visitor.hpp => codegen_cpp_visitor.hpp} (100%) rename test/nmodl/transpiler/unit/codegen/{codegen_c_visitor.cpp => codegen_cpp_visitor.cpp} (99%) diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index 12ad6ceedd..cb48dd3034 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -5,7 +5,7 @@ add_library( codegen STATIC codegen_acc_visitor.cpp codegen_transform_visitor.cpp - codegen_c_visitor.cpp + codegen_cpp_visitor.cpp codegen_compatibility_visitor.cpp codegen_helper_visitor.cpp codegen_info.cpp diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 076bc966b9..0c06d7e3b4 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -12,7 +12,7 @@ * \brief \copybrief nmodl::codegen::CodegenAccVisitor */ -#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_cpp_visitor.hpp" namespace nmodl { diff --git a/src/nmodl/codegen/codegen_c_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp similarity index 99% rename from src/nmodl/codegen/codegen_c_visitor.cpp rename to src/nmodl/codegen/codegen_cpp_visitor.cpp index d3289c36b5..e38e5ff837 100644 --- a/src/nmodl/codegen/codegen_c_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include #include diff --git a/src/nmodl/codegen/codegen_c_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp similarity index 100% rename from src/nmodl/codegen/codegen_c_visitor.hpp rename to src/nmodl/codegen/codegen_cpp_visitor.hpp diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index 41683258e5..9f1fec3ea4 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -7,7 +7,7 @@ #include "codegen/codegen_utils.hpp" -#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_cpp_visitor.hpp" namespace nmodl { namespace codegen { diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 4971b02716..4e9ee3dd84 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -12,8 +12,8 @@ #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" -#include "codegen/codegen_c_visitor.hpp" #include "codegen/codegen_compatibility_visitor.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_transform_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index d9cb0e3880..f9c68c823b 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -66,7 +66,7 @@ add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILE add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) add_executable(testcodegen codegen/main.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp - codegen/codegen_c_visitor.cpp codegen/transform.cpp) + codegen/codegen_cpp_visitor.cpp codegen/transform.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp similarity index 99% rename from test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp rename to test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 563ae7a386..d41207e3b3 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_c_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -8,7 +8,7 @@ #include #include "ast/program.hpp" -#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index 7ba1329b26..6b57249261 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -7,7 +7,7 @@ #include -#include "codegen/codegen_c_visitor.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_utils.hpp" using namespace nmodl; From a25a8bbfa01dbf56e68c5b8a413440e2baa5f0ec Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 5 Dec 2022 14:19:34 +0100 Subject: [PATCH 480/871] Remove useless param_type/str_qualifier (BlueBrain/nmodl#978) Use a token for codecov to avoid one API call NMODL Repo SHA: BlueBrain/nmodl@7cf95511a804590681e1a5b9f9088b7860baca5d --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 35 +++++++---------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 14 --------- 2 files changed, 10 insertions(+), 39 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index e38e5ff837..34cc6d3324 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1934,34 +1934,21 @@ std::string CodegenCVisitor::internal_method_arguments() { } -std::string CodegenCVisitor::param_type_qualifier() { - return ""; -} - - -std::string CodegenCVisitor::param_ptr_qualifier() { - return ""; -} - - /** * @todo: figure out how to correctly handle qualifiers */ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { auto params = ParamVector(); params.emplace_back("", "int", "", "id"); - params.emplace_back(param_type_qualifier(), "int", "", "pnodecount"); - params.emplace_back(param_type_qualifier(), - fmt::format("{}*", instance_struct()), - param_ptr_qualifier(), - "inst"); + params.emplace_back("", "int", "", "pnodecount"); + params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); if (ion_variable_struct_required()) { params.emplace_back("", "IonCurVar&", "", "ionvar"); } params.emplace_back("", "double*", "", "data"); params.emplace_back("const ", "Datum*", "", "indexes"); - params.emplace_back(param_type_qualifier(), "ThreadDatum*", "", "thread"); - params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); + params.emplace_back("", "ThreadDatum*", "", "thread"); + params.emplace_back("", "NrnThread*", "", "nt"); params.emplace_back("", "double", "", "v"); return params; } @@ -3725,8 +3712,8 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need print_kernel_data_present_annotation_block_begin(); } - printer->fmt_line("{}int nodecount = ml->nodecount;", param_type_qualifier()); - printer->fmt_line("{}int pnodecount = ml->_nodecount_padded;", param_type_qualifier()); + printer->add_line("int nodecount = ml->nodecount;"); + printer->add_line("int pnodecount = ml->_nodecount_padded;"); printer->add_line("double* data = ml->data;"); printer->add_line("double* weights = nt->weights;"); printer->add_line("Datum* indexes = ml->pdata;"); @@ -4077,12 +4064,10 @@ void CodegenCVisitor::print_net_receive_kernel() { name = method_name("net_receive_kernel"); params.emplace_back("", "double", "", "t"); params.emplace_back("", "Point_process*", "", "pnt"); - params.emplace_back(param_type_qualifier(), - fmt::format("{}*", instance_struct()), - param_ptr_qualifier(), - "inst"); - params.emplace_back(param_type_qualifier(), "NrnThread*", param_ptr_qualifier(), "nt"); - params.emplace_back(param_type_qualifier(), "Memb_list*", param_ptr_qualifier(), "ml"); + params.emplace_back("", fmt::format("{}*", instance_struct()), + "", "inst"); + params.emplace_back("", "NrnThread*", "", "nt"); + params.emplace_back("", "Memb_list*", "", "ml"); params.emplace_back("", "int", "", "weight_index"); params.emplace_back("", "double", "", "flag"); } else { diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index c4b1bc1e3f..70b42a0175 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -901,20 +901,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ virtual void print_global_var_struct_assertions() const; - /** - * The used parameter type qualifier - * \return an empty string - */ - virtual std::string param_type_qualifier(); - - - /** - * The used parameter pointer type qualifier - * \return an empty string - */ - virtual std::string param_ptr_qualifier(); - - /** * Prints the start of the \c coreneuron namespace */ From 1c59c0e623359fda8c576a39365d11c64ec7e366 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 15 Dec 2022 14:18:54 +0100 Subject: [PATCH 481/871] Fix random bux in the inlining pass (BlueBrain/nmodl#980) * when we inline AST nodes, we keep the track of which nodes being replaced by using a map containing address of those nodes. * when inlining of a particular node is finished, we were not removing that node address from the map. * if we get a situation where AST node with same address is created (dynamic allocation) during inlining pass, it might end-up replacing wrong node just because it has the same address as previously inlined node. * to avoid this, make sure to clear map entry when inlining is done! Fixes BlueBrain/nmodl#809 NMODL Repo SHA: BlueBrain/nmodl@9602bddb83c55a0a3cb64661b156bb37270fdaaf --- src/nmodl/visitors/inline_visitor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index fb43e86271..2fa0acaf1c 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -308,9 +308,12 @@ void InlineVisitor::visit_wrapped_expression(WrappedExpression& node) { const auto& e = node.get_expression(); if (e->is_function_call()) { auto expression = dynamic_cast(e.get()); + // if node is inlined, replace it with corresponding variable name + // and remove entry from the bookkeeping map if (replaced_fun_calls.find(expression) != replaced_fun_calls.end()) { auto var = replaced_fun_calls[expression]; node.set_expression(std::make_shared(new String(var))); + replaced_fun_calls.erase(expression); } } } From dae8d71530dd8a43669fda2e72d2389c5b6781ba Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 19 Dec 2022 16:30:27 +0100 Subject: [PATCH 482/871] Add support for MUTEXLOCK/MUTEXUNLOCK/PROTECT (BlueBrain/nmodl#961) * Use OpenMP critical section instead of cpp mutex - for consistency with OpenMP backend, use openmp constructs to implement PROTECT and MUTEX*. As these constructs are used very sparingly, using critical section is fine here - add codegen test for the same Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@8b67dae84e17dbaec2c01b773446b265cffae656 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 29 +++++++++++++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 3 ++ src/nmodl/main.cpp | 2 +- .../visitors/semantic_analysis_visitor.cpp | 35 +++++++++++++++++++ .../visitors/semantic_analysis_visitor.hpp | 18 ++++++++-- .../unit/codegen/codegen_cpp_visitor.cpp | 31 ++++++++++++++++ 6 files changed, 112 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 34cc6d3324..a28a8b9742 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -318,6 +318,23 @@ void CodegenCVisitor::visit_update_dt(const ast::UpdateDt& node) { // dt change statement should be pulled outside already } +void CodegenCVisitor::visit_protect_statement(const ast::ProtectStatement& node) { + printer->fmt_start_block("#pragma omp critical {}", info.mod_suffix); + printer->add_indent(); + node.get_expression()->accept(*this); + printer->add_text(";"); + printer->add_newline(); + printer->end_block(1); +} + +void CodegenCVisitor::visit_mutex_lock(const ast::MutexLock& node) { + printer->fmt_start_block("#pragma omp critical {}", info.mod_suffix); +} + +void CodegenCVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { + printer->end_block(1); +} + /****************************************************************************************/ /* Common helper routines */ /****************************************************************************************/ @@ -475,7 +492,10 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { || node->is_verbatim() || node->is_from_statement() || node->is_conductance_hint() - || node->is_while_statement()) { + || node->is_while_statement() + || node->is_protect_statement() + || node->is_mutex_lock() + || node->is_mutex_unlock()) { return false; } if (node->is_expression_statement()) { @@ -1286,14 +1306,17 @@ void CodegenCVisitor::print_statement_block(const ast::StatementBlock& node, continue; } /// not necessary to add indent for verbatim block (pretty-printing) - if (!statement->is_verbatim()) { + if (!statement->is_verbatim() && !statement->is_mutex_lock() && + !statement->is_mutex_unlock() && !statement->is_protect_statement()) { printer->add_indent(); } statement->accept(*this); if (need_semicolon(statement.get())) { printer->add_text(";"); } - printer->add_newline(); + if (!statement->is_mutex_lock() && !statement->is_mutex_unlock()) { + printer->add_newline(); + } } if (close_brace) { diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 70b42a0175..0409115d5b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1869,6 +1869,9 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; void visit_update_dt(const ast::UpdateDt& node) override; + void visit_protect_statement(const ast::ProtectStatement& node) override; + void visit_mutex_lock(const ast::MutexLock& node) override; + void visit_mutex_unlock(const ast::MutexUnlock& node) override; }; diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 4e9ee3dd84..61ffefaf34 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -314,7 +314,7 @@ int main(int argc, const char* argv[]) { /// Check some rules that ast should follow { logger->info("Running semantic analysis visitor"); - if (SemanticAnalysisVisitor().check(*ast)) { + if (SemanticAnalysisVisitor(oacc_backend).check(*ast)) { return 1; } } diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index a2df30cec3..e290d052e0 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -116,5 +116,40 @@ void SemanticAnalysisVisitor::visit_independent_block(const ast::IndependentBloc /// --> } +void SemanticAnalysisVisitor::visit_protect_statement(const ast::ProtectStatement& /* node */) { + /// <-- This code is for check 6 + if (accel_backend) { + logger->error("PROTECT statement is not supported with GPU execution"); + } + if (in_mutex) { + logger->warn("SemanticAnalysisVisitor :: Find a PROTECT inside a already locked part."); + } + /// --> +} + +void SemanticAnalysisVisitor::visit_mutex_lock(const ast::MutexLock& /* node */) { + /// <-- This code is for check 6 + if (accel_backend) { + logger->error("MUTEXLOCK statement is not supported with GPU execution"); + } + if (in_mutex) { + logger->warn("SemanticAnalysisVisitor :: Found a MUTEXLOCK inside an already locked part."); + } + in_mutex = true; + /// --> +} + +void SemanticAnalysisVisitor::visit_mutex_unlock(const ast::MutexUnlock& /* node */) { + /// <-- This code is for check 6 + if (accel_backend) { + logger->error("MUTEXUNLOCK statement is not supported with GPU execution"); + } + if (!in_mutex) { + logger->warn("SemanticAnalysisVisitor :: Found a MUTEXUNLOCK outside a locked part."); + } + in_mutex = false; + /// --> +} + } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index f89cbd47a9..7fd821fb72 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -29,6 +29,7 @@ * 3. A TABLE statement in functions cannot have name list, and should have one in procedures. * 4. Check if ion variables from a `USEION` statement are not declared in `CONSTANT` block. * 5. Check if an independent variable is not 't'. + * 6. Check that mutex are not badly use */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -40,6 +41,8 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { private: bool check_fail = false; + /// true if accelerator backend is used for code generation + bool accel_backend = false; /// true if the procedure or the function contains only one argument bool one_arg_in_procedure_function = false; /// true if we are in a procedure block @@ -48,6 +51,8 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { bool in_function = false; /// true if the mod file is of type point process bool is_point_process = false; + /// true if we are inside a mutex locked part + bool in_mutex = false; /// Store if we are in a procedure and if the arity of this is 1 void visit_procedure_block(const ast::ProcedureBlock& node) override; @@ -64,9 +69,18 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// Visit independent block and check if one of the variable is not t void visit_independent_block(const ast::IndependentBlock& node) override; - public: - SemanticAnalysisVisitor() = default; + /// Look if protect is inside a locked block + void visit_protect_statement(const ast::ProtectStatement& node) override; + + /// Look if MUTEXLOCK is inside a locked block + void visit_mutex_lock(const ast::MutexLock& node) override; + /// Look if MUTEXUNLOCK is outside a locked block + void visit_mutex_unlock(const ast::MutexUnlock& node) override; + + public: + SemanticAnalysisVisitor(bool accel_backend = false) + : accel_backend(accel_backend) {} bool check(const ast::Program& node); }; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index d41207e3b3..4332f8c762 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -422,3 +422,34 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable } } } + +SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { + GIVEN("A mod file containing MUTEX & PROTECT") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX TEST + RANGE tmp + } + PARAMETER { + tmp = 10 + } + INITIAL { + MUTEXLOCK + tmp = 11 + MUTEXUNLOCK + PROTECT tmp = 12 + } + )"; + + THEN("Code with OpenMP critical sections is generated") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code = R"(#pragma omp critical TEST { + inst->tmp[id] = 11.0; + } + #pragma omp critical TEST { + inst->tmp[id] = 12.0; + })"; + REQUIRE_THAT(generated, Contains(expected_code)); + } + } +} From 66ce2a705f42e15bcda7b20725c028a1b033e74f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 21 Dec 2022 15:18:36 +0100 Subject: [PATCH 483/871] Add support for FUNCTION_TABLE (BlueBrain/nmodl#972) NMODL Repo SHA: BlueBrain/nmodl@df074d094d8df36f45afdea05f84ab3690d6b412 --- docs/nmodl/transpiler/language.rst | 2 +- .../codegen/codegen_compatibility_visitor.cpp | 2 -- src/nmodl/codegen/codegen_cpp_visitor.cpp | 35 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 7 ++++ src/nmodl/codegen/codegen_helper_visitor.cpp | 5 +++ src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 ++ .../visitors/semantic_analysis_visitor.cpp | 12 +++++++ .../visitors/semantic_analysis_visitor.hpp | 4 +++ .../unit/codegen/codegen_cpp_visitor.cpp | 19 ++++++++++ .../unit/visitor/semantic_analysis.cpp | 19 ++++++++++ 11 files changed, 106 insertions(+), 3 deletions(-) diff --git a/docs/nmodl/transpiler/language.rst b/docs/nmodl/transpiler/language.rst index e3de8af2b6..2122b2c215 100644 --- a/docs/nmodl/transpiler/language.rst +++ b/docs/nmodl/transpiler/language.rst @@ -57,7 +57,7 @@ framework. Code generation information is related to CoreNEURON backend. +------------------------+-------------------+-------------------+---------------------+ | DISCRETE | yes | no | no | +------------------------+-------------------+-------------------+---------------------+ -| FUNCTION_TABLE | yes | no | no | +| FUNCTION_TABLE | yes | yes | no | +------------------------+-------------------+-------------------+---------------------+ | CONSTRUCTOR | yes | no | yes | +------------------------+-------------------+-------------------+---------------------+ diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index b7631c370b..b38946de79 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -20,8 +20,6 @@ const std::map CodegenCompatibilityVisitor::unhandled_ast_types_func( {{AstNodeType::DISCRETE_BLOCK, &CodegenCompatibilityVisitor::return_error_with_name}, - {AstNodeType::FUNCTION_TABLE_BLOCK, - &CodegenCompatibilityVisitor::return_error_without_name}, {AstNodeType::SOLVE_BLOCK, &CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled}, {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index a28a8b9742..816e35ab20 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1753,6 +1753,33 @@ void CodegenCVisitor::print_function(const ast::FunctionBlock& node) { print_function_procedure_helper(node); } + +void CodegenCVisitor::print_function_tables(const ast::FunctionTableBlock& node) { + auto name = node.get_node_name(); + const auto& p = node.get_parameters(); + auto params = internal_method_parameters(); + for (const auto& i: p) { + params.emplace_back("", "double", "", i->get_node_name()); + } + printer->fmt_line("double {}({})", method_name(name), get_parameter_str(params)); + printer->start_block(); + printer->fmt_line("double _arg[{}];", p.size()); + for (size_t i = 0; i < p.size(); ++i) { + printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); + } + printer->fmt_line("return hoc_func_table({}, {}, _arg);", + get_variable_name(std::string("_ptable_" + name), true), + p.size()); + printer->end_block(1); + + printer->fmt_start_block("double table_{}()", method_name(name)); + printer->fmt_line("hoc_spec_table(&{}, {});", + get_variable_name(std::string("_ptable_" + name)), + p.size()); + printer->add_line("return 0.;"); + printer->end_block(1); +} + /** * @brief Checks whether the functor_block generated by sympy solver modifies any variable outside * its scope. If it does then return false, so that the operator() of the struct functor of the @@ -2714,6 +2741,11 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool print_initialise } } + for (const auto& f: info.function_tables) { + printer->fmt_line("void* _ptable_{}{{}};", f->get_node_name()); + codegen_global_variables.push_back(make_symbol("_ptable_" + f->get_node_name())); + } + if (info.vectorize && info.thread_data_index) { printer->fmt_line("{}ThreadDatum ext_call_thread[{}]{};", qualifier, @@ -4605,6 +4637,9 @@ void CodegenCVisitor::print_compute_functions() { for (const auto& function: info.functions) { print_function(*function); } + for (const auto& function: info.function_tables) { + print_function_tables(*function); + } for (size_t i = 0; i < info.before_after_blocks.size(); i++) { print_before_after_block(info.before_after_blocks[i], i); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 0409115d5b..1a102a054a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1789,6 +1789,13 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_function(const ast::FunctionBlock& node); + /** + * Print NMODL function_table in target backend code + * \param node + */ + void print_function_tables(const ast::FunctionTableBlock& node); + + /** * Print NMODL procedure in target backend code * \param node diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 4eda696b7f..6358993e30 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -569,6 +569,11 @@ void CodegenHelperVisitor::visit_function_block(const ast::FunctionBlock& node) } +void CodegenHelperVisitor::visit_function_table_block(const ast::FunctionTableBlock& node) { + info.function_tables.push_back(&node); +} + + void CodegenHelperVisitor::visit_eigen_newton_solver_block( const ast::EigenNewtonSolverBlock& node) { info.eigen_newton_solver_exist = true; diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 6d4a378a1d..1031535d53 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -89,6 +89,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_conductance_hint(const ast::ConductanceHint& node) override; void visit_procedure_block(const ast::ProcedureBlock& node) override; void visit_function_block(const ast::FunctionBlock& node) override; + void visit_function_table_block(const ast::FunctionTableBlock& node) override; void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; void visit_statement_block(const ast::StatementBlock& node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 9ca2409dbe..aac0f64cac 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -281,6 +281,9 @@ struct CodegenInfo { /// all functions defined in the mod file std::vector functions; + /// all functions tables defined in the mod file + std::vector function_tables; + /// all factors defined in the mod file std::vector factor_definitions; diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index e290d052e0..5464398790 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -1,5 +1,6 @@ #include "visitors/semantic_analysis_visitor.hpp" #include "ast/function_block.hpp" +#include "ast/function_table_block.hpp" #include "ast/independent_block.hpp" #include "ast/procedure_block.hpp" #include "ast/program.hpp" @@ -116,6 +117,17 @@ void SemanticAnalysisVisitor::visit_independent_block(const ast::IndependentBloc /// --> } +void SemanticAnalysisVisitor::visit_function_table_block(const ast::FunctionTableBlock& node) { + /// <-- This code is for check 7 + if (node.get_parameters().size() < 1) { + logger->critical( + "SemanticAnalysisVisitor :: Function table '{}' must have one or more arguments.", + node.get_node_name()); + check_fail = true; + } + /// --> +} + void SemanticAnalysisVisitor::visit_protect_statement(const ast::ProtectStatement& /* node */) { /// <-- This code is for check 6 if (accel_backend) { diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 7fd821fb72..bb7bdbf2dc 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -30,6 +30,7 @@ * 4. Check if ion variables from a `USEION` statement are not declared in `CONSTANT` block. * 5. Check if an independent variable is not 't'. * 6. Check that mutex are not badly use + * 7. Check than function table got at least one argument. */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -69,6 +70,9 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// Visit independent block and check if one of the variable is not t void visit_independent_block(const ast::IndependentBlock& node) override; + /// Visit function table to check that number of args > 0 + void visit_function_table_block(const ast::FunctionTableBlock& node) override; + /// Look if protect is inside a locked block void visit_protect_statement(const ast::ProtectStatement& node) override; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 4332f8c762..880519f39e 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -423,6 +423,25 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable } } +SCENARIO("Check code generation for FUNCTION_TABLE block", "[codegen][function_table]") { + GIVEN("A MOD file with Function table block") { + std::string const nmodl_text = R"( + NEURON { SUFFIX glia } + FUNCTION_TABLE ttt(l (mV)) + FUNCTION_TABLE uuu(l, k) + )"; + THEN("Code should be generated correctly") { + auto const generated = get_cpp_code(nmodl_text); + REQUIRE_THAT(generated, Contains("double ttt_glia(")); + REQUIRE_THAT(generated, Contains("double table_ttt_glia(")); + REQUIRE_THAT(generated, Contains("hoc_spec_table(&inst->global->_ptable_ttt, 1")); + REQUIRE_THAT(generated, Contains("double uuu_glia(")); + REQUIRE_THAT(generated, Contains("double table_uuu_glia(")); + REQUIRE_THAT(generated, Contains("hoc_func_table(inst->global->_ptable_uuu, 2")); + } + } +} + SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { GIVEN("A mod file containing MUTEX & PROTECT") { std::string const nmodl_text = R"( diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 624c48b76d..5dc5f1a44f 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -146,3 +146,22 @@ SCENARIO("INDEPENDENT block", "[visitor][semantic_analysis]") { } } } + +SCENARIO("FUNCTION_TABLE block", "[visitor][semantic_analysis]") { + GIVEN("A mod file with FUNCTION_TABLE without argument") { + std::string nmodl_text = R"( + FUNCTION_TABLE ttt() + )"; + THEN("Semantic analysis should fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("A mod file with FUNCTION_TABLE with at least one argument") { + std::string nmodl_text = R"( + FUNCTION_TABLE ttt(w (mV)) + )"; + THEN("Semantic analysis should success") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From 9127b9219ec0ff657c90096d79864ac8c2333520 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 5 Jan 2023 16:50:50 +0530 Subject: [PATCH 484/871] Add code generation test for CONSTANT block (BlueBrain/nmodl#981) * code generation is already implemented in https://github.com/BlueBrain/nmodl/blob/25cd4707d2a4cf7f47febeb9ba321ff2dc30244c/src/codegen/codegen_helper_visitor.cpp#L158 https://github.com/BlueBrain/nmodl/blob/9602bddb83c55a0a3cb64661b156bb37270fdaaf/src/codegen/codegen_cpp_visitor.cpp#L2606 * add test to make sure variables are added as part of mech_Store struct that contain all global variables fixes BlueBrain/nmodl#945 Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@04c4e3dc6a8c28bf95b376393501fbe8a98ef74f --- .../unit/codegen/codegen_cpp_visitor.cpp | 33 +++++++++++++++++++ .../transpiler/unit/utils/test_utils.cpp | 2 +- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 880519f39e..36750a91b1 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -423,6 +423,39 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable } } +SCENARIO("Check CONSTANT variables are added to global variable structure", + "[codegen][global_variables]") { + GIVEN("A MOD file that use CONSTANT variables") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX CONST + GLOBAL zGateS1 + } + PARAMETER { + zGateS1 = 1.2 (1) + } + CONSTANT { + e0 = 1.60217646e-19 (coulombs) + kB = 1.3806505e-23 (joule/kelvin) + q10Fluo = 1.67 (1) + } + )"; + THEN("The global struct should contain these variables") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code = R"( + struct CONST_Store { + int reset{}; + int mech_type{}; + double zGateS1{1.2}; + double e0{1.60218e-19}; + double kB{1.38065e-23}; + double q10Fluo{1.67}; + };)"; + REQUIRE_THAT(generated, Contains(reindent_text(stringutils::trim(expected_code)))); + } + } +} + SCENARIO("Check code generation for FUNCTION_TABLE block", "[codegen][function_table]") { GIVEN("A MOD file with Function table block") { std::string const nmodl_text = R"( diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index c1f88fccac..7f6e81841b 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -44,7 +44,7 @@ NEURON { } * i.e. we get first non-empty line and count number of leading whitespaces (X). - * Then for every sub-sequent line, we remove first X characters (assuing those + * Then for every sub-sequent line, we remove first X characters (assuming those * all are whitespaces). This is done because when ast is transformed back to * nmodl, the nmodl output is without "extra" whitespaces in the provided input. */ From 67520ba31ddcb98298ce25fa0ce458a5e2096a05 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 12 Jan 2023 22:56:02 +0100 Subject: [PATCH 485/871] Fix setup.py (BlueBrain/nmodl#990) Fix sphinxcontrib-applehelp NMODL Repo SHA: BlueBrain/nmodl@0847c805b71ce924623eabec83c37c40936c08fd --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5b853ee569..c956004c92 100644 --- a/setup.py +++ b/setup.py @@ -138,7 +138,8 @@ def _config_exe(exe_name): "nbconvert", "nbsphinx>=0.3.2", "pytest>=3.7.2", - "sphinx<5", # myst_parser requires <5 and pip falls over + "sphinxcontrib-applehelp<1.0.3", + "sphinx<6", "sphinx-rtd-theme", ] + install_requirements, From b83994767440aee4c24588ceb8e913a1becd3629 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 12 Jan 2023 23:40:29 +0100 Subject: [PATCH 486/871] Add tests for BEFORE/AFTER block (BlueBrain/nmodl#979) NMODL Repo SHA: BlueBrain/nmodl@ac272785dc444c8444b085d121f08b7575bb6647 --- src/nmodl/language/nmodl.yaml | 2 +- .../unit/codegen/codegen_cpp_visitor.cpp | 157 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 77607044f4..58a8b3bb49 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -834,7 +834,7 @@ brief: "Represents a `BREAKPOINT` block in NMODL" description: | The `BREAKPOINT` block is used to update current and conductance. - at each time step. Here is an example of `BEFORE` : + at each time step. Here is an example of `BREAKPOINT` : \code{.mod} BREAKPOINT { diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 36750a91b1..5182c38488 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -423,6 +423,163 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable } } +SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/after]") { + GIVEN("A mod file full of BEFORE/AFTER of all kinds") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX ba1 + } + BEFORE BREAKPOINT { + init_before_breakpoint() + inc = 0 + } + AFTER SOLVE { + init_after_solve() + inc = 0 + } + BEFORE INITIAL { + init_before_initial() + inc = 0 + } + AFTER INITIAL { + init_after_initial() + inc = 0 + } + BEFORE STEP { + init_before_step() + inc = 0 + } + )"; + THEN("They should be well registered") { + auto const generated = get_cpp_code(nmodl_text); + // 11: BEFORE BREAKPOINT + { + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, 11);")); + std::string generated_code = R"( + #pragma ivdep + #pragma omp simd + for (int id = 0; id < nodecount; id++) { + int node_id = node_index[id]; + double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + { + init_before_breakpoint(); + inc = 0.0; + } + })"; + auto const expected = generated_code; + REQUIRE_THAT(generated, Contains(expected)); + } + // 23: AFTER SOLVE + { + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, 22);")); + std::string generated_code = R"( + #pragma ivdep + #pragma omp simd + for (int id = 0; id < nodecount; id++) { + int node_id = node_index[id]; + double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + { + init_after_solve(); + inc = 0.0; + } + })"; + auto const expected = generated_code; + REQUIRE_THAT(generated, Contains(expected)); + } + // 11: BEFORE INITIAL + { + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, 13);")); + std::string generated_code = R"( + #pragma ivdep + #pragma omp simd + for (int id = 0; id < nodecount; id++) { + int node_id = node_index[id]; + double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + { + init_before_initial(); + inc = 0.0; + } + })"; + auto const expected = generated_code; + REQUIRE_THAT(generated, Contains(expected)); + } + // 21: AFTER INITIAL + { + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, 23);")); + std::string generated_code = R"( + #pragma ivdep + #pragma omp simd + for (int id = 0; id < nodecount; id++) { + int node_id = node_index[id]; + double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + { + init_after_initial(); + inc = 0.0; + } + })"; + auto const expected = generated_code; + REQUIRE_THAT(generated, Contains(expected)); + } + // 13: BEFORE STEP + { + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_4_ba1, 14);")); + std::string generated_code = R"( + #pragma ivdep + #pragma omp simd + for (int id = 0; id < nodecount; id++) { + int node_id = node_index[id]; + double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + { + init_before_step(); + inc = 0.0; + } + })"; + auto const expected = generated_code; + REQUIRE_THAT(generated, Contains(expected)); + } + } + } + + GIVEN("A mod file with several time same BEFORE or AFTER block") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX ba1 + } + BEFORE STEP {} + AFTER SOLVE {} + BEFORE STEP {} + AFTER SOLVE {} + )"; + THEN("They should be all registered") { + auto const generated = get_cpp_code(nmodl_text); + REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, 14);")); + REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, 22);")); + REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, 14);")); + REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, 22);")); + } + } +} + SCENARIO("Check CONSTANT variables are added to global variable structure", "[codegen][global_variables]") { GIVEN("A MOD file that use CONSTANT variables") { From 075b1978f50acd6bf358a02174a71ce11d7362ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20S=C4=83vulescu?= Date: Tue, 24 Jan 2023 20:01:49 +0100 Subject: [PATCH 487/871] setup.py: add long_description from README.md (BlueBrain/nmodl#993) * useful to display on PyPi NMODL Repo SHA: BlueBrain/nmodl@9618e27db905dc1769dfa951227cf1dcad17d824 --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c956004c92..60c3705c8c 100644 --- a/setup.py +++ b/setup.py @@ -113,13 +113,18 @@ def _config_exe(exe_name): if "NMODL_NIGHTLY_TAG" in os.environ: package_name += os.environ["NMODL_NIGHTLY_TAG"] +# Parse long description from README.md +with open('README.md', 'r', encoding='utf-8') as f: + long_description = f.read() + setup( name=package_name, version=__version__, author="Blue Brain Project", author_email="bbp-ou-hpc@groupes.epfl.ch", description="NEURON Modeling Language Source-to-Source Compiler Framework", - long_description="", + long_description=long_description, + long_description_content_type='text/markdown', packages=["nmodl"], scripts=["pywheel/shim/nmodl", "pywheel/shim/find_libpython.py"], include_package_data=True, From 88f8566d8d710f7b46ca755df8312acfb5c6ac77 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 2 Feb 2023 00:25:12 +0100 Subject: [PATCH 488/871] Improve / fix codegen support for PROTECT and MUTEX constructs (BlueBrain/nmodl#994) * Remove unnecessary code after OpenMP backend removal - BlueBrain/nmodl#858 has removed OpenMP backend with mechanism level async execution - shadow_statements vector is always empty now * Improve / fix codegen support for PROTECT and MUTEX constructs - typo/invalid syntax for mutex block from BlueBrain/nmodl#961 : name for critical section should be in ( ) - avoid printing `ivdep` as on CPU also we have to respect PROTECT and MUTEX statements. So now onwards, just use OpenMP semantics rather than non-standard pragma like ivdep. - protect statement now becomes semantically same as atomic statements. This adds some limitations but that's what allows us to rely on OpenMP / OpenACC for atomic udpates. - PROTECT can be now used on GPU as well - Update test * Update CI testing from Python 3.7 to 3.8 * pin sphinxcontrib-htmlhelp<=2.0.0 NMODL Repo SHA: BlueBrain/nmodl@519017ae2b6ece240799c05a23139169f82f12a0 --- setup.py | 1 + src/nmodl/codegen/codegen_acc_visitor.cpp | 4 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 3 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 81 ++++++++++--------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 10 +-- .../transpiler/integration/mod/cabpump.mod | 10 ++- .../unit/codegen/codegen_cpp_visitor.cpp | 47 +++++++---- 7 files changed, 90 insertions(+), 66 deletions(-) diff --git a/setup.py b/setup.py index 60c3705c8c..91e98938d0 100644 --- a/setup.py +++ b/setup.py @@ -144,6 +144,7 @@ def _config_exe(exe_name): "nbsphinx>=0.3.2", "pytest>=3.7.2", "sphinxcontrib-applehelp<1.0.3", + "sphinxcontrib-htmlhelp<=2.0.0", "sphinx<6", "sphinx-rtd-theme", ] diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index f85e7367c2..2a2c83dc13 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -9,6 +9,7 @@ #include "ast/eigen_linear_solver_block.hpp" #include "ast/integer.hpp" +#include "ast/protect_statement.hpp" namespace nmodl { @@ -30,7 +31,8 @@ namespace codegen { * for(int id=0; idfmt_start_block("#pragma omp critical {}", info.mod_suffix); + print_atomic_reduction_pragma(); printer->add_indent(); node.get_expression()->accept(*this); printer->add_text(";"); - printer->add_newline(); - printer->end_block(1); } void CodegenCVisitor::visit_mutex_lock(const ast::MutexLock& node) { - printer->fmt_start_block("#pragma omp critical {}", info.mod_suffix); + printer->fmt_line("#pragma omp critical ({})", info.mod_suffix); + printer->add_indent(); + printer->start_block(); } void CodegenCVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { @@ -1121,16 +1121,32 @@ void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { * for parallelization. For example: * * \code - * #pragma ivdep + * #pragma omp simd * for(int id = 0; id < nodecount; id++) { * * #pragma acc parallel loop * for(int id = 0; id < nodecount; id++) { * \endcode */ -void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */) { - printer->add_line("#pragma ivdep"); - printer->add_line("#pragma omp simd"); +void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, + const ast::Block* block) { + // ivdep allows SIMD parallelisation of a block/loop but doesn't provide + // a standard mechanism for atomics. Also, even with openmp 5.0, openmp + // atomics do not enable vectorisation under "omp simd" (gives compiler + // error with gcc < 9 if atomic and simd pragmas are nested). So, emit + // ivdep/simd pragma when no MUTEXLOCK/MUTEXUNLOCK/PROTECT statements + // are used in the given block. + std::vector> nodes; + if (block) { + nodes = collect_nodes(*block, + {ast::AstNodeType::PROTECT_STATEMENT, + ast::AstNodeType::MUTEX_LOCK, + ast::AstNodeType::MUTEX_UNLOCK}); + } + if (nodes.empty()) { + printer->add_line("#pragma ivdep"); + printer->add_line("#pragma omp simd"); + } } @@ -1154,9 +1170,7 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { } else { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); - print_atomic_reduction_pragma(); printer->fmt_line("vec_rhs[node_id] {} rhs;", rhs_op); - print_atomic_reduction_pragma(); printer->fmt_line("vec_d[node_id] {} g;", d_op); } } @@ -1167,27 +1181,19 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { auto d_op = operator_for_d(); if (info.point_process) { printer->add_line("int node_id = node_index[id];"); - print_atomic_reduction_pragma(); printer->fmt_line("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op); - print_atomic_reduction_pragma(); printer->fmt_line("vec_d[node_id] {} shadow_d[id];", d_op); } } +/** + * In the current implementation of CPU/CPP backend we need to emit atomic pragma + * only with PROTECT construct (atomic rduction requirement for other cases on CPU + * is handled via separate shadow vectors). + */ void CodegenCVisitor::print_atomic_reduction_pragma() { - // backend specific, do nothing -} - - -void CodegenCVisitor::print_shadow_reduction_statements() { - for (const auto& statement: shadow_statements) { - print_atomic_reduction_pragma(); - auto lhs = get_variable_name(statement.lhs); - auto rhs = get_variable_name(shadow_varname(statement.lhs)); - printer->fmt_line("{} {} {};", lhs, statement.op, rhs); - } - shadow_statements.clear(); + printer->add_line("#pragma omp atomic update"); } @@ -3503,7 +3509,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_dt_update_to_device(); } - print_channel_iteration_block_parallel_hint(BlockType::Initial); + print_channel_iteration_block_parallel_hint(BlockType::Initial, info.initial_node); printer->start_block("for (int id = 0; id < nodecount; id++)"); if (info.net_receive_node != nullptr) { @@ -3512,7 +3518,6 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { print_initial_block(info.initial_node); printer->end_block(1); - print_shadow_reduction_statements(); if (!info.changed_dt.empty()) { printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); @@ -3561,7 +3566,7 @@ void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t bl printer->fmt_line("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id); print_global_function_common_code(BlockType::BeforeAfter, function_name); - print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter); + print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter, node); printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); @@ -3688,7 +3693,13 @@ void CodegenCVisitor::print_watch_check() { printer->add_newline(2); printer->add_line("/** routine to check watch activation */"); print_global_function_common_code(BlockType::Watch); - print_channel_iteration_block_parallel_hint(BlockType::Watch); + + // WATCH statements appears in NET_RECEIVE block and while printing + // net_receive function we already check if it contains any MUTEX/PROTECT + // constructs. As WATCH is not a top level block but list of statements, + // we don't need to have ivdep pragma related check + print_channel_iteration_block_parallel_hint(BlockType::Watch, nullptr); + printer->start_block("for (int id = 0; id < nodecount; id++)"); if (info.is_voltage_used_by_watch_statements()) { @@ -3978,7 +3989,7 @@ void CodegenCVisitor::print_get_memb_list() { void CodegenCVisitor::print_net_receive_loop_begin() { printer->add_line("int count = nrb->_displ_cnt;"); - print_channel_iteration_block_parallel_hint(BlockType::NetReceive); + print_channel_iteration_block_parallel_hint(BlockType::NetReceive, info.net_receive_node); printer->start_block("for (int i = 0; i < count; i++)"); } @@ -4330,7 +4341,7 @@ void CodegenCVisitor::print_nrn_state() { printer->add_newline(2); printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); - print_channel_iteration_block_parallel_hint(BlockType::State); + print_channel_iteration_block_parallel_hint(BlockType::State, info.nrn_state_block); printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); @@ -4365,11 +4376,6 @@ void CodegenCVisitor::print_nrn_state() { printer->add_line(text); } printer->end_block(1); - if (!shadow_statements.empty()) { - printer->start_block("for (int id = 0; id < nodecount; id++)"); - print_shadow_reduction_statements(); - printer->end_block(1); - } print_kernel_data_present_annotation_block_end(); @@ -4528,9 +4534,7 @@ void CodegenCVisitor::print_fast_imem_calculation() { printer->start_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); } - print_atomic_reduction_pragma(); printer->fmt_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs); - print_atomic_reduction_pragma(); printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d); if (nrn_cur_reduction_loop_required()) { printer->end_block(1); @@ -4551,7 +4555,7 @@ void CodegenCVisitor::print_nrn_cur() { printer->add_newline(2); printer->add_line("/** update current */"); print_global_function_common_code(BlockType::Equation); - print_channel_iteration_block_parallel_hint(BlockType::Equation); + print_channel_iteration_block_parallel_hint(BlockType::Equation, info.breakpoint_node); printer->start_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_kernel(*info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); @@ -4563,7 +4567,6 @@ void CodegenCVisitor::print_nrn_cur() { if (nrn_cur_reduction_loop_required()) { printer->start_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_matrix_shadow_reduction(); - print_shadow_reduction_statements(); printer->end_block(1); print_fast_imem_calculation(); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 1a102a054a..1d0982fde3 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -285,12 +285,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ std::shared_ptr printer; - /** - * List of shadow statements in the current block - */ - std::vector shadow_statements; - - /** * Return Nmodl language version * \return A version @@ -1249,7 +1243,8 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * * \param type The block type */ - virtual void print_channel_iteration_block_parallel_hint(BlockType type); + virtual void print_channel_iteration_block_parallel_hint(BlockType type, + const ast::Block* block); /** @@ -1424,7 +1419,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** * Print atomic update pragma for reduction statements - * */ virtual void print_atomic_reduction_pragma(); diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 1988445dd9..33198b1145 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -9,6 +9,7 @@ NEURON { GLOBAL depth,cainf,taur RANGE var RANGE ainf + RANGE alpha } UNITS { @@ -25,6 +26,7 @@ PARAMETER { taur = 200 (ms) : rate of calcium removal for stress conditions cainf = 50e-6(mM) :changed oct2 cai (mM) + alpha = 1 } ASSIGNED { @@ -53,7 +55,11 @@ DERIVATIVE state { ca' = drive_channel/18 + (cainf -ca)/taur*11 cai = ca - if (FOO == 0) { } + if (FOO == 1) { + MUTEXLOCK + alpha = alpha + 1 + MUTEXUNLOCK + } } : to test code generation for TABLE statement @@ -72,5 +78,5 @@ PROCEDURE test_table_p(br) { INITIAL { var_init(var) - ca = cainf + PROTECT ca = ca + 1 } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 5182c38488..3d0d0ce53c 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -431,11 +431,13 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } BEFORE BREAKPOINT { init_before_breakpoint() - inc = 0 + PROTECT inc = inc + 1 } AFTER SOLVE { + MUTEXLOCK init_after_solve() inc = 0 + MUTEXUNLOCK } BEFORE INITIAL { init_before_initial() @@ -456,9 +458,9 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a { REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, 11);")); + // in case of PROTECT, there should not be simd or ivdep pragma std::string generated_code = R"( - #pragma ivdep - #pragma omp simd + for (int id = 0; id < nodecount; id++) { int node_id = node_index[id]; double v = voltage[node_id]; @@ -467,7 +469,8 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a #endif { init_before_breakpoint(); - inc = 0.0; + #pragma omp atomic update + inc = inc + 1.0; } })"; auto const expected = generated_code; @@ -477,9 +480,9 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a { REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, 22);")); + // in case of MUTEXLOCK/MUTEXUNLOCK, there should not be simd or ivdep pragma std::string generated_code = R"( - #pragma ivdep - #pragma omp simd + for (int id = 0; id < nodecount; id++) { int node_id = node_index[id]; double v = voltage[node_id]; @@ -487,8 +490,11 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a inst->v_unused[id] = v; #endif { - init_after_solve(); - inc = 0.0; + #pragma omp critical (ba1) + { + init_after_solve(); + inc = 0.0; + } } })"; auto const expected = generated_code; @@ -637,28 +643,39 @@ SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { std::string const nmodl_text = R"( NEURON { SUFFIX TEST - RANGE tmp + RANGE tmp, foo } PARAMETER { tmp = 10 + foo = 20 } INITIAL { MUTEXLOCK tmp = 11 MUTEXUNLOCK - PROTECT tmp = 12 + PROTECT tmp = tmp / 2.5 + } + PROCEDURE bar() { + PROTECT foo = foo - 21 } )"; THEN("Code with OpenMP critical sections is generated") { auto const generated = get_cpp_code(nmodl_text); - std::string expected_code = R"(#pragma omp critical TEST { + // critical section for the mutex block + std::string expected_code_initial = R"(#pragma omp critical (TEST) + { inst->tmp[id] = 11.0; } - #pragma omp critical TEST { - inst->tmp[id] = 12.0; - })"; - REQUIRE_THAT(generated, Contains(expected_code)); + #pragma omp atomic update + inst->tmp[id] = inst->tmp[id] / 2.5;)"; + + // atomic update for the PROTECT construct + std::string expected_code_proc = R"(#pragma omp atomic update + inst->foo[id] = inst->foo[id] - 21.0;)"; + + REQUIRE_THAT(generated, Contains(expected_code_initial)); + REQUIRE_THAT(generated, Contains(expected_code_proc)); } } } From 88ddbd7487f38771ea64924534043f5bce537ed0 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 7 Feb 2023 19:28:21 +0100 Subject: [PATCH 489/871] Remove shadow variables that were only used with OpenMP backend (BlueBrain/nmodl#1000) NMODL Repo SHA: BlueBrain/nmodl@e3f1efa6db32cd39ce7f0a9b5ad6af10ce0e14ea --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 39 ----------------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 30 ----------------- 2 files changed, 69 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 026fa6034d..3f0618994b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1012,31 +1012,6 @@ std::vector CodegenCVisitor::get_int_variables() { } -/** - * \details When we enable fine level parallelism at channel level, we have do updates - * to ion variables in atomic way. As cpus don't have atomic instructions in - * simd loop, we have to use shadow vectors for every ion variables. Here - * we return list of all such variables. - * - * \todo If conductances are specified, we don't need all below variables - */ -std::vector CodegenCVisitor::get_shadow_variables() { - std::vector variables; - for (const auto& ion: info.ions) { - for (const auto& var: ion.writes) { - variables.push_back({make_symbol(shadow_varname(naming::ION_VARNAME_PREFIX + var))}); - if (ion.is_ionic_current(var)) { - variables.push_back({make_symbol(shadow_varname( - std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"))}); - } - } - } - variables.push_back({make_symbol("ml_rhs")}); - variables.push_back({make_symbol("ml_d")}); - return variables; -} - - /****************************************************************************************/ /* Routines must be overloaded in backend */ /****************************************************************************************/ @@ -2388,11 +2363,6 @@ std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol, } -std::string CodegenCVisitor::ion_shadow_variable_name(const SymbolType& symbol) { - return fmt::format("inst->{}[id]", symbol->get_name()); -} - - std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name) const { std::string result(name); if (ion_variable_struct_required()) { @@ -2446,14 +2416,6 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use return global_variable_name(*g, use_instance); } - // shadow variable - auto s = std::find_if(codegen_shadow_variables.begin(), - codegen_shadow_variables.end(), - symbol_comparator); - if (s != codegen_shadow_variables.end()) { - return ion_shadow_variable_name(*s); - } - if (varname == naming::NTHREAD_DT_VARIABLE) { return std::string("nt->_") + naming::NTHREAD_DT_VARIABLE; } @@ -4712,7 +4674,6 @@ void CodegenCVisitor::setup(const Program& node) { codegen_float_variables = get_float_variables(); codegen_int_variables = get_int_variables(); - codegen_shadow_variables = get_shadow_variables(); update_index_semantics(); rename_function_arguments(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 1d0982fde3..4966e29979 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -235,11 +235,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ std::vector codegen_global_variables; - /** - * All ion variables that could be possibly written - */ - std::vector codegen_shadow_variables; - /** * \c true if currently net_receive block being printed */ @@ -414,16 +409,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { } - /** - * Constructs a shadow variable name - * \param name The name of the variable - * \return The name of the variable prefixed with \c shadow_ - */ - std::string shadow_varname(const std::string& name) const { - return "shadow_" + name; - } - - /** * Creates a temporary symbol * \param name The name of the symbol @@ -624,14 +609,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { std::string global_variable_name(const SymbolType& symbol, bool use_instance = true) const; - /** - * Determine the variable name for a shadow variable given its symbol - * \param symbol The symbol of a variable for which we want to obtain its name - * \return The C string representing the access to the shadow variable - */ - static std::string ion_shadow_variable_name(const SymbolType& symbol); - - /** * Determine variable name in the structure of mechanism properties * @@ -672,13 +649,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { std::vector get_int_variables(); - /** - * Determine all ion write variables that require shadow vectors during code generation - * \return A \c vector of ion variables - */ - std::vector get_shadow_variables(); - - /** * Print the items in a vector as a list * From f813b102e9e259f8eba6c4e25c367cfd900ac129 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 8 Feb 2023 11:22:17 +0100 Subject: [PATCH 490/871] Fix code generation for OpenMP GPU backend with NVHPC (BlueBrain/nmodl#998) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Solves the issues described in https://forums.developer.nvidia.com/t/issue-with-locally-defined-classes-in-openmp-offload-region-since-nvhpc-22-5/228735/7 by defining the functor structs in the beginning of the file * Each time there is an EigenNewtonSolverBlock there will be another functor struct defined. This is to make sure that the proper structs are always defined regardless of the order and where they are defined (nrn_init block, nrn_state or function) * Added integration and unit tests * In BB5 we have sympy 1.9 and in the other CIs there was 1.11. These two different versions generated different code and the new unit tests will fail in one or the other. To solve this issue I created a requirements.txt which is used to install all the python packages needed in all of the CIs. Currently I have pinned the sympy version the same as BB5. It can be updated once we update the version in BB5 as well --------- Co-authored-by: Alexandru Săvulescu NMODL Repo SHA: BlueBrain/nmodl@714b5a290743887cf1748c437afe183132945ab9 --- INSTALL.md | 4 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 46 ++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 16 ++ src/nmodl/codegen/codegen_helper_visitor.cpp | 7 + src/nmodl/codegen/codegen_info.hpp | 4 + .../integration/mod/test_functor.mod | 49 ++++ test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../transpiler/unit/visitor/sympy_solver.cpp | 212 ++++++++++++++++++ 8 files changed, 322 insertions(+), 17 deletions(-) create mode 100644 test/nmodl/transpiler/integration/mod/test_functor.mod diff --git a/INSTALL.md b/INSTALL.md index cf42c44ac9..1d83a37895 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -37,7 +37,7 @@ brew install flex bison cmake python3 The necessary Python packages can then easily be added using the pip3 command. ```sh -pip3 install Jinja2 PyYAML pytest sympy +pip3 install --user -r requirements.txt ``` Make sure to have latest flex/bison in $PATH : @@ -63,7 +63,7 @@ apt-get install flex bison gcc python3 python3-pip The Python dependencies are installed using: ```sh -pip3 install Jinja2 PyYAML pytest sympy +pip3 install --user -r requirements.txt ``` ## Build Project diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 3f0618994b..421853c0d4 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1810,25 +1810,19 @@ bool is_functor_const(const ast::StatementBlock& variable_block, return is_functor_const; } -void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { - // solution vector to store copy of state vars for Newton solver - printer->add_newline(); - +void CodegenCVisitor::print_functor_definition(const ast::EigenNewtonSolverBlock& node) { + // functor that evaluates F(X) and J(X) for + // Newton solver auto float_type = default_float_data_type(); int N = node.get_n_state_vars()->get_value(); - printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); - printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - print_statement_block(*node.get_setup_x_block(), false, false); - - // functor that evaluates F(X) and J(X) for - // Newton solver - printer->start_block("struct functor"); + const auto functor_name = info.functor_names[&node]; + printer->fmt_start_block("struct {0}", functor_name); printer->add_line("NrnThread* nt;"); printer->fmt_line("{0}* inst;", instance_struct()); printer->add_line("int id, pnodecount;"); printer->add_line("double v;"); - printer->add_line("Datum* indexes;"); + printer->add_line("const Datum* indexes;"); printer->add_line("double* data;"); printer->add_line("ThreadDatum* thread;"); @@ -1844,11 +1838,12 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->end_block(2); printer->fmt_line( - "functor(NrnThread* nt, {}* inst, int id, int pnodecount, double v, Datum* indexes, " + "{0}(NrnThread* nt, {1}* inst, int id, int pnodecount, double v, const Datum* indexes, " "double* data, ThreadDatum* thread) : " "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}}, " "data{{data}}, thread{{thread}} " "{{}}", + functor_name, instance_struct()); printer->add_indent(); @@ -1876,11 +1871,23 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->end_block(1); printer->end_block(";"); +} + +void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { + // solution vector to store copy of state vars for Newton solver + printer->add_newline(); + + auto float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + + print_statement_block(*node.get_setup_x_block(), false, false); // call newton solver with functor and X matrix that contains state vars printer->add_line("// call newton solver"); - printer->add_line( - "functor newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);"); + printer->fmt_line("{} newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);", + info.functor_names[&node]); printer->add_line("newton_functor.initialize();"); printer->add_line( "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); @@ -3585,6 +3592,14 @@ void CodegenCVisitor::print_nrn_destructor() { } +void CodegenCVisitor::print_functors_definitions() { + for (const auto& functor_name: info.functor_names) { + printer->add_newline(2); + print_functor_definition(*functor_name.first); + } +} + + void CodegenCVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); @@ -4643,6 +4658,7 @@ void CodegenCVisitor::print_codegen_routines() { print_nrn_alloc(); print_nrn_constructor(); print_nrn_destructor(); + print_functors_definitions(); print_compute_functions(); print_check_table_thread_function(); print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 4966e29979..a7e038579a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1808,6 +1808,22 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ void print_instance_variable_setup(); + /** + * Go through the map of \c EigenNewtonSolverBlock s and their corresponding functor names + * and print the functor definitions before the definitions of the functions of the generated + * file + * + */ + void print_functors_definitions(); + + /** + * @brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its + * functor + * + * @param node \c EigenNewtonSolverBlock for which to print the functor + */ + void print_functor_definition(const ast::EigenNewtonSolverBlock& node); + void visit_binary_expression(const ast::BinaryExpression& node) override; void visit_binary_operator(const ast::BinaryOperator& node) override; void visit_boolean(const ast::Boolean& node) override; diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 6358993e30..5473213b1b 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -577,6 +577,13 @@ void CodegenHelperVisitor::visit_function_table_block(const ast::FunctionTableBl void CodegenHelperVisitor::visit_eigen_newton_solver_block( const ast::EigenNewtonSolverBlock& node) { info.eigen_newton_solver_exist = true; + // Avoid extra declaration for `functor` corresponding to the DERIVATIVE block which is not + // printed to the generated CPP file + if (!under_derivative_block) { + const auto new_unique_functor_name = "functor_" + info.mod_suffix + "_" + + std::to_string(info.functor_names.size()); + info.functor_names[&node] = new_unique_functor_name; + } node.visit_children(*this); } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index aac0f64cac..3602fbfa83 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -13,6 +13,7 @@ */ #include +#include #include #include "ast/ast.hpp" @@ -376,6 +377,9 @@ struct CodegenInfo { /// all variables/symbols used in the verbatim block std::unordered_set variables_in_verbatim; + /// unique functor names for all the \c EigenNewtonSolverBlock s + std::unordered_map functor_names; + /// true if eigen newton solver is used bool eigen_newton_solver_exist = false; diff --git a/test/nmodl/transpiler/integration/mod/test_functor.mod b/test/nmodl/transpiler/integration/mod/test_functor.mod new file mode 100644 index 0000000000..d12ed43d38 --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/test_functor.mod @@ -0,0 +1,49 @@ +COMMENT +Test for derivimplicit solver with sympy and Eigen. Based on cacum.mod +ENDCOMMENT + +NEURON { + SUFFIX cacum + USEION ca READ ica WRITE cai + RANGE depth, tau, cai0 +} + +UNITS { + (mM) = (milli/liter) + (mA) = (milliamp) + F = (faraday) (coulombs) +} + +PARAMETER { + depth = 1 (nm) : assume volume = area*depth + tau = 10 (ms) + cai0 = 50e-6 (mM) : Requires explicit use in INITIAL + : block for it to take precedence over cai0_ca_ion + : Do not forget to initialize in hoc if different + : from this default. +} + +ASSIGNED { + ica (mA/cm2) +} + +STATE { + cai (mM) +} + +INITIAL { + cai = cai0 + extra_solve() +} + +BREAKPOINT { + SOLVE integrate METHOD derivimplicit +} + +PROCEDURE extra_solve() { + SOLVE integrate METHOD derivimplicit +} + +DERIVATIVE integrate { + cai' = -ica/depth/F/2 * (1e7) + (cai0 - cai)/tau +} diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index f9c68c823b..ce57584f97 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -87,6 +87,7 @@ target_link_libraries( util test_util printer + codegen ${NMODL_WRAPPER_LIBS}) target_link_libraries( testcodegen diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 2a0b3e1d7d..587fd69832 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -8,22 +8,31 @@ #include #include "ast/program.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" +#include "visitors/inline_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; +using namespace codegen; using namespace visitor; using namespace test; using namespace test_utils; +using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 + +using nmodl::test_utils::reindent_text; + using ast::AstNodeType; using nmodl::parser::NmodlDriver; @@ -2224,3 +2233,206 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym } } } + +/// Helper for creating C codegen visitor +std::shared_ptr create_c_visitor(const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss, + bool inline_visitor = true, + bool pade = false, + bool cse = false) { + /// construct symbol table + SymtabVisitor().visit_program(*ast); + + /// run all necessary pass + if (inline_visitor) { + InlineVisitor().visit_program(*ast); + } + // unroll loops and fold constants + ConstantFolderVisitor().visit_program(*ast); + LoopUnrollVisitor().visit_program(*ast); + ConstantFolderVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); + + // run SympySolver on AST + SympySolverVisitor(pade, cse).visit_program(*ast); + SymtabVisitor(true).visit_program(*ast); + + // Solve states + NeuronSolveVisitor().visit_program(*ast); + SolveBlockVisitor().visit_program(*ast); + + // Update symtab before CodegenCVisitor + SymtabVisitor(true).visit_program(*ast); + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().check_ast(*ast); + + /// create C code generation visitor + auto cv = std::make_shared("temp.mod", ss, "double", false); + cv->setup(*ast); + return cv; +} + +/// print entire code +std::string get_cpp_code(const std::string& nmodl_text) { + const auto& ast = NmodlDriver().parse_string(nmodl_text); + std::stringstream ss; + auto cvisitor = create_c_visitor(ast, nmodl_text, ss); + cvisitor->visit_program(*ast); + auto generated_string = ss.str(); + return reindent_text(generated_string); +} + +SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][derivimplicit]") { + GIVEN("A mod file containing two SOLVE statements") { + std::string const nmodl_text = R"( + NEURON { + SUFFIX cacum + USEION ca READ ica WRITE cai + RANGE depth, tau, cai0 + } + + UNITS { + (mM) = (milli/liter) + (mA) = (milliamp) + F = 96485.3 (coulombs) + } + + PARAMETER { + depth = 1 (nm) : assume volume = area*depth + tau = 10 (ms) + cai0 = 50e-6 (mM) : Requires explicit use in INITIAL + : block for it to take precedence over cai0_ca_ion + : Do not forget to initialize in hoc if different + : from this default. + } + + ASSIGNED { + ica (mA/cm2) + } + + STATE { + cai (mM) + } + + INITIAL { + cai = cai0 + extra_solve() + } + + BREAKPOINT { + SOLVE integrate METHOD derivimplicit + } + + DERIVATIVE integrate { + cai' = -ica/depth/F/2 * (1e7) + (cai0 - cai)/tau + } + + PROCEDURE extra_solve() { + SOLVE integrate + } + )"; + + THEN("Three different functor structs defined and used") { + auto const generated = get_cpp_code(nmodl_text); + + // Expected functor definitions + std::string expected_functor_cacum_0_definition = + R"(struct functor_cacum_0 { + NrnThread* nt; + cacum_Instance* inst; + int id, pnodecount; + double v; + const Datum* indexes; + double* data; + ThreadDatum* thread; + double old_cai; + + void initialize() { + old_cai = inst->cai[id]; + } + + functor_cacum_0(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) : nt{nt}, inst{inst}, id{id}, pnodecount{pnodecount}, v{v}, indexes{indexes}, data{data}, thread{thread} {} + void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { + const double* nmodl_eigen_x = nmodl_eigen_xm.data(); + double* nmodl_eigen_j = nmodl_eigen_jm.data(); + double* nmodl_eigen_f = nmodl_eigen_fm.data(); + nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); + nmodl_eigen_j[static_cast(0)] = -(nt->_dt + inst->tau[id]) / inst->tau[id]; + } + + void finalize() { + } + };)"; + std::string expected_functor_cacum_1_definition = + R"(struct functor_cacum_1 { + NrnThread* nt; + cacum_Instance* inst; + int id, pnodecount; + double v; + const Datum* indexes; + double* data; + ThreadDatum* thread; + double old_cai; + + void initialize() { + old_cai = inst->cai[id]; + } + + functor_cacum_1(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) : nt{nt}, inst{inst}, id{id}, pnodecount{pnodecount}, v{v}, indexes{indexes}, data{data}, thread{thread} {} + void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { + const double* nmodl_eigen_x = nmodl_eigen_xm.data(); + double* nmodl_eigen_j = nmodl_eigen_jm.data(); + double* nmodl_eigen_f = nmodl_eigen_fm.data(); + nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); + nmodl_eigen_j[static_cast(0)] = -(nt->_dt + inst->tau[id]) / inst->tau[id]; + } + + void finalize() { + } + };)"; + std::string expected_functor_cacum_2_definition = + R"(struct functor_cacum_2 { + NrnThread* nt; + cacum_Instance* inst; + int id, pnodecount; + double v; + const Datum* indexes; + double* data; + ThreadDatum* thread; + double old_cai; + + void initialize() { + old_cai = inst->cai[id]; + } + + functor_cacum_2(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) : nt{nt}, inst{inst}, id{id}, pnodecount{pnodecount}, v{v}, indexes{indexes}, data{data}, thread{thread} {} + void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { + const double* nmodl_eigen_x = nmodl_eigen_xm.data(); + double* nmodl_eigen_j = nmodl_eigen_jm.data(); + double* nmodl_eigen_f = nmodl_eigen_fm.data(); + nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); + nmodl_eigen_j[static_cast(0)] = -(nt->_dt + inst->tau[id]) / inst->tau[id]; + } + + void finalize() { + } + };)"; + // Expected functor usages + std::string expected_functor_cacum_0_usage = + R"(functor_cacum_0 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; + std::string expected_functor_cacum_1_usage = + R"(functor_cacum_1 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; + std::string expected_functor_cacum_2_usage = + R"(functor_cacum_2 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; + + REQUIRE_THAT(generated, Contains(expected_functor_cacum_0_definition)); + REQUIRE_THAT(generated, Contains(expected_functor_cacum_1_definition)); + REQUIRE_THAT(generated, Contains(expected_functor_cacum_2_definition)); + REQUIRE_THAT(generated, Contains(expected_functor_cacum_0_usage)); + REQUIRE_THAT(generated, Contains(expected_functor_cacum_1_usage)); + REQUIRE_THAT(generated, Contains(expected_functor_cacum_2_usage)); + } + } +} From 291ff4ecd565a75595a528f190a6021ca4ccd62b Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 13 Feb 2023 14:44:04 +0100 Subject: [PATCH 491/871] Increase CodegenCVisitor coverage (BlueBrain/nmodl#997) * Update actions/setup-python to v4, because v3 is deprecated in github ci * Add codegen test for net_send/net_move/net_event * Throw exception instead of abort if net_move is not called by NET_RECEIVE block * Add codegen test for derivimplicit solver without sympy * Add codegen test for INITIAL in NET_RECEIVE * Add codegen test for FOR_NETCONS * Add codegen test that throws if two table statement are declared in one procedure NMODL Repo SHA: BlueBrain/nmodl@0481f3012a47390a2f1e34d29c308a1040c99617 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 28 +- .../unit/codegen/codegen_cpp_visitor.cpp | 354 ++++++++++++++++++ 2 files changed, 367 insertions(+), 15 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 421853c0d4..a36c2ce04b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -214,17 +214,17 @@ void CodegenCVisitor::visit_from_statement(const ast::FromStatement& node) { const auto& to = node.get_to(); const auto& inc = node.get_increment(); const auto& block = node.get_statement_block(); - printer->fmt_text("for(int {}=", name); + printer->fmt_text("for (int {} = ", name); from->accept(*this); - printer->fmt_text("; {}<=", name); + printer->fmt_text("; {} <= ", name); to->accept(*this); if (inc) { - printer->fmt_text("; {}+=", name); + printer->fmt_text("; {} += ", name); inc->accept(*this); } else { printer->fmt_text("; {}++", name); } - printer->add_text(")"); + printer->add_text(") "); block->accept(*this); } @@ -2901,7 +2901,7 @@ static size_t get_register_type_for_ba_block(const ast::Block* block) { void CodegenCVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); - printer->fmt_start_block("void _{}_reg() ", info.mod_file); + printer->fmt_start_block("void _{}_reg()", info.mod_file); // type related information auto suffix = add_escape_quote(info.mod_suffix); @@ -3603,7 +3603,7 @@ void CodegenCVisitor::print_functors_definitions() { void CodegenCVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); - printer->fmt_start_block("static void {}(double* data, Datum* indexes, int type) ", method); + printer->fmt_start_block("static void {}(double* data, Datum* indexes, int type)", method); printer->add_line("// do nothing"); printer->end_block(1); } @@ -3821,8 +3821,7 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { if (!printing_net_receive) { - std::cout << "Error : net_move only allowed in NET_RECEIVE block" << std::endl; - abort(); + throw std::runtime_error("Error : net_move only allowed in NET_RECEIVE block"); } auto const& arguments = node.get_arguments(); @@ -3913,7 +3912,7 @@ void CodegenCVisitor::print_net_init() { auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); - printer->fmt_start_block("static void net_init({}) ", args); + printer->fmt_start_block("static void net_init({})", args); auto block = node->get_statement_block().get(); if (block->get_statements().empty()) { printer->add_line("// do nothing"); @@ -4015,13 +4014,12 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { print_send_event_move(); } - printer->add_newline(); print_kernel_data_present_annotation_block_end(); printer->end_block(1); } void CodegenCVisitor::print_net_send_buffering_grow() { - printer->start_block("if(i >= nsb->_size)"); + printer->start_block("if (i >= nsb->_size)"); printer->add_line("nsb->grow();"); printer->end_block(1); } @@ -4036,12 +4034,12 @@ void CodegenCVisitor::print_net_send_buffering() { auto args = "NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; - printer->fmt_start_block("static inline void net_send_buffering({}) ", args); + printer->fmt_start_block("static inline void net_send_buffering({})", args); printer->add_line("int i = 0;"); print_device_atomic_capture_annotation(); printer->add_line("i = nsb->_cnt++;"); print_net_send_buffering_grow(); - printer->start_block("if(i < nsb->_size)"); + printer->start_block("if (i < nsb->_size)"); printer->add_line("nsb->_sendtype[i] = type;"); printer->add_line("nsb->_vdata_index[i] = vdata_index;"); printer->add_line("nsb->_weight_index[i] = weight_index;"); @@ -4121,7 +4119,7 @@ void CodegenCVisitor::print_net_receive_kernel() { } printer->add_newline(2); - printer->fmt_start_block("static inline void {}({}) ", name, get_parameter_str(params)); + printer->fmt_start_block("static inline void {}({})", name, get_parameter_str(params)); print_net_receive_common_code(*node, info.artificial_cell); if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); @@ -4164,7 +4162,7 @@ void CodegenCVisitor::print_net_receive() { params.emplace_back("", "int", "", "weight_index"); params.emplace_back("", "double", "", "flag"); printer->add_newline(2); - printer->fmt_start_block("static void {}({}) ", name, get_parameter_str(params)); + printer->fmt_start_block("static void {}({})", name, get_parameter_str(params)); printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 3d0d0ce53c..b0895cd8b0 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -421,6 +421,24 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable REQUIRE_THAT(generated, Contains("inst->global->tau = inst->global->t_tau[i]")); } } + GIVEN("A MOD file with two table statements") { + std::string const nmodl_text = R"( + NEURON { + RANGE inf, tau + } + PROCEDURE foo(v) { + TABLE inf FROM 1 TO 3 WITH 100 + FROM i=0 TO 1 { + } + TABLE tau FROM 1 TO 3 WITH 100 + FROM i=0 TO 1 { + } + } + )"; + THEN("It should throw") { + REQUIRE_THROWS(get_cpp_code(nmodl_text)); + } + } } SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/after]") { @@ -638,6 +656,342 @@ SCENARIO("Check code generation for FUNCTION_TABLE block", "[codegen][function_t } } +SCENARIO("Check that loops are well generated", "[codegen][loops]") { + GIVEN("A mod file containing for/while/if/else/FROM") { + std::string const nmodl_text = R"( + PROCEDURE foo() { + LOCAL a, b + if (a == 1) { + b = 5 + } else if (a == 2) { + b = 6 + } else { + b = 7 ^ 2 + } + + while (b > 0) { + b = b - 1 + } + FROM a = 1 TO 10 BY 2 { + b = b + 1 + } + })"; + + THEN("Correct code is generated") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code = R"(double a, b; + if (a == 1.0) { + b = 5.0; + } else if (a == 2.0) { + b = 6.0; + } else { + b = pow(7.0, 2.0); + } + while (b > 0.0) { + b = b - 1.0; + } + for (int a = 1; a <= 10; a += 2) { + b = b + 1.0; + })"; + REQUIRE_THAT(generated, Contains(expected_code)); + } + } +} + + +SCENARIO("Check that top verbatim blocks are well generated", "[codegen][top verbatim block]") { + GIVEN("A mod file containing top verbatim block") { + std::string const nmodl_text = R"( + PROCEDURE foo(nt) { + } + VERBATIM + // This is a top verbatim block + double a = 2.; + // This procedure should be replaced + foo(_nt); + _tqitem; + _STRIDE; + ENDVERBATIM + )"; + + THEN("Correct code is generated") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code = R"(using namespace coreneuron; + + + double a = 2.; + foo_(nt); + &tqitem; + pnodecount+id;)"; + REQUIRE_THAT(generated, Contains(expected_code)); + } + } +} + + +SCENARIO("Check that codegen generate event functions well", "[codegen][net_events]") { + GIVEN("A mod file with events") { + std::string const nmodl_text = R"( + NET_RECEIVE(w) { + INITIAL {} + if (flag == 0) { + net_event(t) + net_move(t+1) + } else { + net_send(1, 1) + } + } + )"; + + THEN("Correct code is generated") { + auto const generated = get_cpp_code(nmodl_text); + std::string net_send_expected_code = + R"(static inline void net_send_buffering(NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { + int i = 0; + i = nsb->_cnt++; + if (i >= nsb->_size) { + nsb->grow(); + } + if (i < nsb->_size) { + nsb->_sendtype[i] = type; + nsb->_vdata_index[i] = vdata_index; + nsb->_weight_index[i] = weight_index; + nsb->_pnt_index[i] = point_index; + nsb->_nsb_t[i] = t; + nsb->_nsb_flag[i] = flag; + } + })"; + REQUIRE_THAT(generated, Contains(net_send_expected_code)); + std::string net_receive_kernel_expected_code = + R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { + int tid = pnt->_tid; + int id = pnt->_i_instance; + double v = 0; + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + double* data = ml->data; + double* weights = nt->weights; + Datum* indexes = ml->pdata; + ThreadDatum* thread = ml->_thread; + + inst->tsave[id] = t; + { + if (flag == 0.0) { + net_send_buffering(ml->_net_send_buffer, 1, -1, -1, point_process, t, 0.0); + net_send_buffering(ml->_net_send_buffer, 2, inst->tqitem[0*pnodecount+id], -1, point_process, t + 1.0, 0.0); + } else { + net_send_buffering(ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], weight_index, point_process, t+1.0, 1.0); + } + } + })"; + REQUIRE_THAT(generated, Contains(net_receive_kernel_expected_code)); + std::string net_receive_expected_code = + R"(static void net_receive_(Point_process* pnt, int weight_index, double flag) { + NrnThread* nt = nrn_threads + pnt->_tid; + Memb_list* ml = get_memb_list(nt); + NetReceiveBuffer_t* nrb = ml->_net_receive_buffer; + if (nrb->_cnt >= nrb->_size) { + realloc_net_receive_buffer(nt, ml); + } + int id = nrb->_cnt; + nrb->_pnt_index[id] = pnt-nt->pntprocs; + nrb->_weight_index[id] = weight_index; + nrb->_nrb_t[id] = nt->_t; + nrb->_nrb_flag[id] = flag; + nrb->_cnt++; + })"; + REQUIRE_THAT(generated, Contains(net_receive_expected_code)); + std::string net_buf_receive_expected_code = R"(void net_buf_receive_(NrnThread* nt) { + Memb_list* ml = get_memb_list(nt); + if (!ml) { + return; + } + + NetReceiveBuffer_t* nrb = ml->_net_receive_buffer; + auto* const inst = static_cast<_Instance*>(ml->instance); + int count = nrb->_displ_cnt; + #pragma ivdep + #pragma omp simd + for (int i = 0; i < count; i++) { + int start = nrb->_displ[i]; + int end = nrb->_displ[i+1]; + for (int j = start; j < end; j++) { + int index = nrb->_nrb_index[j]; + int offset = nrb->_pnt_index[index]; + double t = nrb->_nrb_t[index]; + int weight_index = nrb->_weight_index[index]; + double flag = nrb->_nrb_flag[index]; + Point_process* point_process = nt->pntprocs + offset; + net_receive_kernel_(t, point_process, inst, nt, ml, weight_index, flag); + } + } + nrb->_displ_cnt = 0; + nrb->_cnt = 0; + + NetSendBuffer_t* nsb = ml->_net_send_buffer; + for (int i=0; i < nsb->_cnt; i++) { + int type = nsb->_sendtype[i]; + int tid = nt->id; + double t = nsb->_nsb_t[i]; + double flag = nsb->_nsb_flag[i]; + int vdata_index = nsb->_vdata_index[i]; + int weight_index = nsb->_weight_index[i]; + int point_index = nsb->_pnt_index[i]; + net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag); + } + nsb->_cnt = 0; + })"; + REQUIRE_THAT(generated, Contains(net_buf_receive_expected_code)); + std::string net_init_expected_code = + R"(static void net_init(Point_process* pnt, int weight_index, double flag) { + // do nothing + })"; + REQUIRE_THAT(generated, Contains(net_init_expected_code)); + std::string set_pnt_receive_expected_code = + "set_pnt_receive(mech_type, net_receive_, net_init, num_net_receive_args());"; + REQUIRE_THAT(generated, Contains(set_pnt_receive_expected_code)); + } + } + GIVEN("A mod file with an INITIAL inside NET_RECEIVE") { + std::string const nmodl_text = R"( + NET_RECEIVE(w) { + INITIAL { + a = 1 + } + } + )"; + THEN("It should generate a net_init") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code = + R"(static void net_init(Point_process* pnt, int weight_index, double flag) { + int tid = pnt->_tid; + int id = pnt->_i_instance; + double v = 0; + NrnThread* nt = nrn_threads + tid; + Memb_list* ml = nt->_ml_list[pnt->_type]; + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + double* data = ml->data; + double* weights = nt->weights; + Datum* indexes = ml->pdata; + ThreadDatum* thread = ml->_thread; + auto* const inst = static_cast<_Instance*>(ml->instance); + + a = 1.0; + auto& nsb = ml->_net_send_buffer; + })"; + REQUIRE_THAT(generated, Contains(expected_code)); + } + } + GIVEN("A mod file with FOR_NETCONS") { + std::string const nmodl_text = R"( + NET_RECEIVE(w) { + FOR_NETCONS(v) { + b = 2 + } + } + )"; + THEN("New code is generated for for_netcons") { + auto const generated = get_cpp_code(nmodl_text); + std::string net_receive_kernel_expected_code = + R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { + int tid = pnt->_tid; + int id = pnt->_i_instance; + double v = 0; + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + double* data = ml->data; + double* weights = nt->weights; + Datum* indexes = ml->pdata; + ThreadDatum* thread = ml->_thread; + + int node_id = ml->nodeindices[id]; + v = nt->_actual_v[node_id]; + inst->tsave[id] = t; + { + const size_t offset = 0*pnodecount + id; + const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]]; + const size_t for_netcon_end = nt->_fornetcon_perm_indices[indexes[offset] + 1]; + for (auto i = for_netcon_start; i < for_netcon_end; ++i) { + b = 2.0; + } + + } + })"; + REQUIRE_THAT(generated, Contains(net_receive_kernel_expected_code)); + std::string registration_expected_code = "add_nrn_fornetcons(mech_type, 0);"; + REQUIRE_THAT(generated, Contains(registration_expected_code)); + } + } + GIVEN("A mod file with a net_move outside NET_RECEIVE") { + std::string const nmodl_text = R"( + PROCEDURE foo() { + net_move(t+1) + } + )"; + THEN("It should throw") { + REQUIRE_THROWS(get_cpp_code(nmodl_text)); + } + } +} + + +SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { + GIVEN("A mod file with derivimplicit") { + std::string const nmodl_text = R"( + STATE { + m + } + BREAKPOINT { + SOLVE state METHOD derivimplicit + } + DERIVATIVE state { + m' = 2 * m + } + )"; + THEN("Correct code is generated") { + auto const generated = get_cpp_code(nmodl_text); + std::string newton_state_expected_code = R"(namespace { + struct _newton_state_ { + int operator()(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) const { + auto* const inst = static_cast<_Instance*>(ml->instance); + double* savstate1 = static_cast(thread[dith1()].pval); + auto const& slist1 = inst->global->slist1; + auto const& dlist1 = inst->global->dlist1; + double* dlist2 = static_cast(thread[dith1()].pval) + (1*pnodecount); + inst->Dm[id] = 2.0 * inst->m[id]; + int counter = -1; + for (int i=0; i<1; i++) { + if (*deriv1_advance(thread)) { + dlist2[(++counter)*pnodecount+id] = data[dlist1[i]*pnodecount+id]-(data[slist1[i]*pnodecount+id]-savstate1[i*pnodecount+id])/nt->_dt; + } else { + dlist2[(++counter)*pnodecount+id] = data[slist1[i]*pnodecount+id]-savstate1[i*pnodecount+id]; + } + } + return 0; + } + }; + })"; + REQUIRE_THAT(generated, Contains(newton_state_expected_code)); + std::string state_expected_code = + R"(int state_(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) { + auto* const inst = static_cast<_Instance*>(ml->instance); + double* savstate1 = (double*) thread[dith1()].pval; + auto const& slist1 = inst->global->slist1; + auto& slist2 = inst->global->slist2; + double* dlist2 = static_cast(thread[dith1()].pval) + (1*pnodecount); + for (int i=0; i<1; i++) { + savstate1[i*pnodecount+id] = data[slist1[i]*pnodecount+id]; + } + int reset = nrn_newton_thread(static_cast(*newtonspace1(thread)), 1, slist2, _newton_state_{}, dlist2, id, pnodecount, data, indexes, thread, nt, ml, v); + return reset; + })"; + REQUIRE_THAT(generated, Contains(state_expected_code)); + } + } +} + + SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { GIVEN("A mod file containing MUTEX & PROTECT") { std::string const nmodl_text = R"( From afd4f51007d8f561313ee2dadf6e84562586835f Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 22 Feb 2023 10:56:04 +0100 Subject: [PATCH 492/871] Fixes issue with code generation of net_send_buffering() (BlueBrain/nmodl#1005) * If OpenMP GPU backend is enabled then `#pragma omp atomic capture` is not executed properly on CPU with NVHPC 23.1 (more details and tests in neuronsimulator/nrnBlueBrain/nmodl#2239) * Wrap the update of `nsb->_cnt` inside an `if-statement` based on `nt->compute_gpu` in OpenACC visitor * Use C++ `if-statement` instead of in OpenMP pragma in some cases * Added unit test for code generation of `net_send_buffering()` on code generated for GPU --------- Co-authored-by: Olli Lupton NMODL Repo SHA: BlueBrain/nmodl@c3b0736cbf4d6f7e4604c960334d07f2c7a23921 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 21 +++++-- src/nmodl/codegen/codegen_acc_visitor.hpp | 3 + src/nmodl/codegen/codegen_cpp_visitor.cpp | 21 ++++--- src/nmodl/codegen/codegen_cpp_visitor.hpp | 8 +++ .../unit/codegen/codegen_cpp_visitor.cpp | 63 ++++++++++++++++--- 5 files changed, 94 insertions(+), 22 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 2a2c83dc13..87b54e68d1 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -120,6 +120,15 @@ void CodegenAccVisitor::print_abort_routine() const { printer->end_block(1); } +void CodegenAccVisitor::print_net_send_buffering_cnt_update() const { + printer->fmt_start_block("if (nt->compute_gpu)"); + print_device_atomic_capture_annotation(); + printer->add_line("i = nsb->_cnt++;"); + printer->restart_block("else"); + printer->add_line("i = nsb->_cnt++;"); + printer->end_block(1); +} + void CodegenAccVisitor::print_net_send_buffering_grow() { // can not grow buffer during gpu execution } @@ -332,14 +341,14 @@ void CodegenAccVisitor::print_device_stream_wait() const { void CodegenAccVisitor::print_net_send_buf_count_update_to_host() const { - printer->add_line("nrn_pragma_acc(update self(nsb->_cnt) if(nt->compute_gpu))"); - printer->add_line("nrn_pragma_omp(target update from(nsb->_cnt) if(nt->compute_gpu))"); + printer->add_line("nrn_pragma_acc(update self(nsb->_cnt))"); + printer->add_line("nrn_pragma_omp(target update from(nsb->_cnt))"); } void CodegenAccVisitor::print_net_send_buf_update_to_host() const { print_device_stream_wait(); - printer->start_block("if (nsb)"); + printer->start_block("if (nsb && nt->compute_gpu)"); print_net_send_buf_count_update_to_host(); printer->add_line("update_net_send_buffer_on_host(nt, nsb);"); printer->end_block(1); @@ -347,8 +356,10 @@ void CodegenAccVisitor::print_net_send_buf_update_to_host() const { void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { - printer->add_line("nrn_pragma_acc(update device(nsb->_cnt) if(nt->compute_gpu))"); - printer->add_line("nrn_pragma_omp(target update to(nsb->_cnt) if(nt->compute_gpu))"); + printer->start_block("if (nt->compute_gpu)"); + printer->add_line("nrn_pragma_acc(update device(nsb->_cnt))"); + printer->add_line("nrn_pragma_omp(target update to(nsb->_cnt))"); + printer->end_block(1); } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 969f5efa7f..3bd221a870 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -126,6 +126,9 @@ class CodegenAccVisitor: public CodegenCVisitor { // print atomic capture pragma void print_device_atomic_capture_annotation() const override; + // print atomic update of NetSendBuffer_t cnt + void print_net_send_buffering_cnt_update() const override; + void print_net_send_buffering_grow() override; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index a36c2ce04b..844041280b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -3714,8 +3714,10 @@ void CodegenCVisitor::print_watch_check() { printer->add_indent(); printer->add_text("net_send_buffering("); auto t = get_variable_name("t"); - printer->add_text( - fmt::format("ml->_net_send_buffer, 0, {}, -1, {}, {}+0.0, ", tqitem, point_process, t)); + printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, -1, {}, {}+0.0, ", + tqitem, + point_process, + t); watch->get_value()->accept(*this); printer->add_text(");"); printer->add_newline(); @@ -3811,7 +3813,7 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->fmt_text("ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t); + printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t); } // clang-format off print_vector_elements(arguments, ", "); @@ -3839,7 +3841,7 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { auto point_process = get_variable_name("point_process"); std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); - printer->fmt_text("ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); + printer->fmt_text("nt, ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); printer->add_text(")"); @@ -3855,7 +3857,7 @@ void CodegenCVisitor::print_net_event_call(const FunctionCall& node) { } else { auto point_process = get_variable_name("point_process"); printer->add_text("net_send_buffering("); - printer->fmt_text("ml->_net_send_buffer, 1, -1, -1, {}, ", point_process); + printer->fmt_text("nt, ml->_net_send_buffer, 1, -1, -1, {}, ", point_process); print_vector_elements(arguments, ", "); printer->add_text(", 0.0"); } @@ -4018,6 +4020,10 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->end_block(1); } +void CodegenCVisitor::print_net_send_buffering_cnt_update() const { + printer->add_line("i = nsb->_cnt++;"); +} + void CodegenCVisitor::print_net_send_buffering_grow() { printer->start_block("if (i >= nsb->_size)"); printer->add_line("nsb->grow();"); @@ -4032,12 +4038,11 @@ void CodegenCVisitor::print_net_send_buffering() { printer->add_newline(2); print_device_method_annotation(); auto args = - "NetSendBuffer_t* nsb, int type, int vdata_index, " + "const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; printer->fmt_start_block("static inline void net_send_buffering({})", args); printer->add_line("int i = 0;"); - print_device_atomic_capture_annotation(); - printer->add_line("i = nsb->_cnt++;"); + print_net_send_buffering_cnt_update(); print_net_send_buffering_grow(); printer->start_block("if (i < nsb->_size)"); printer->add_line("nsb->_sendtype[i] = type;"); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index a7e038579a..a256f7acd9 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1315,6 +1315,14 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { void print_net_receive_common_code(const ast::Block& node, bool need_mech_inst = true); + /** + * Print the code related to the update of NetSendBuffer_t cnt. For GPU this needs to be done + * with atomic operation, on CPU it's not needed. + * + */ + virtual void print_net_send_buffering_cnt_update() const; + + /** * Print statement that grows NetSendBuffering_t structure if needed. * This function should be overridden for backends that cannot dynamically reallocate the buffer diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index b0895cd8b0..83cc4e3e62 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -8,6 +8,7 @@ #include #include "ast/program.hpp" +#include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" @@ -47,6 +48,24 @@ std::shared_ptr create_c_visitor(const std::shared_ptr create_acc_visitor(const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss) { + /// construct symbol table + SymtabVisitor().visit_program(*ast); + + /// run all necessary pass + InlineVisitor().visit_program(*ast); + NeuronSolveVisitor().visit_program(*ast); + SolveBlockVisitor().visit_program(*ast); + + /// create C code generation visitor + auto cv = std::make_shared("temp.mod", ss, "double", false); + cv->setup(*ast); + return cv; +} + /// print instance structure for testing purpose std::string get_instance_var_setup_function(std::string& nmodl_text) { const auto& ast = NmodlDriver().parse_string(nmodl_text); @@ -57,11 +76,16 @@ std::string get_instance_var_setup_function(std::string& nmodl_text) { } /// print entire code -std::string get_cpp_code(const std::string& nmodl_text) { +std::string get_cpp_code(const std::string& nmodl_text, const bool generate_gpu_code = false) { const auto& ast = NmodlDriver().parse_string(nmodl_text); std::stringstream ss; - auto cvisitor = create_c_visitor(ast, nmodl_text, ss); - cvisitor->visit_program(*ast); + if (generate_gpu_code) { + auto accvisitor = create_acc_visitor(ast, nmodl_text, ss); + accvisitor->visit_program(*ast); + } else { + auto cvisitor = create_c_visitor(ast, nmodl_text, ss); + cvisitor->visit_program(*ast); + } return reindent_text(ss.str()); } @@ -745,8 +769,8 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even THEN("Correct code is generated") { auto const generated = get_cpp_code(nmodl_text); - std::string net_send_expected_code = - R"(static inline void net_send_buffering(NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { + std::string cpu_net_send_expected_code = + R"(static inline void net_send_buffering(const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { int i = 0; i = nsb->_cnt++; if (i >= nsb->_size) { @@ -761,7 +785,28 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even nsb->_nsb_flag[i] = flag; } })"; - REQUIRE_THAT(generated, Contains(net_send_expected_code)); + REQUIRE_THAT(generated, Contains(cpu_net_send_expected_code)); + auto const gpu_generated = get_cpp_code(nmodl_text, true); + std::string gpu_net_send_expected_code = + R"(static inline void net_send_buffering(const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { + int i = 0; + if (nt->compute_gpu) { + nrn_pragma_acc(atomic capture) + nrn_pragma_omp(atomic capture) + i = nsb->_cnt++; + } else { + i = nsb->_cnt++; + } + if (i < nsb->_size) { + nsb->_sendtype[i] = type; + nsb->_vdata_index[i] = vdata_index; + nsb->_weight_index[i] = weight_index; + nsb->_pnt_index[i] = point_index; + nsb->_nsb_t[i] = t; + nsb->_nsb_flag[i] = flag; + } + })"; + REQUIRE_THAT(gpu_generated, Contains(gpu_net_send_expected_code)); std::string net_receive_kernel_expected_code = R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { int tid = pnt->_tid; @@ -777,10 +822,10 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even inst->tsave[id] = t; { if (flag == 0.0) { - net_send_buffering(ml->_net_send_buffer, 1, -1, -1, point_process, t, 0.0); - net_send_buffering(ml->_net_send_buffer, 2, inst->tqitem[0*pnodecount+id], -1, point_process, t + 1.0, 0.0); + net_send_buffering(nt, ml->_net_send_buffer, 1, -1, -1, point_process, t, 0.0); + net_send_buffering(nt, ml->_net_send_buffer, 2, inst->tqitem[0*pnodecount+id], -1, point_process, t + 1.0, 0.0); } else { - net_send_buffering(ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], weight_index, point_process, t+1.0, 1.0); + net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], weight_index, point_process, t+1.0, 1.0); } } })"; From 79aca4f59819d126500248201e1c2942497263e3 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 22 Feb 2023 12:47:41 +0100 Subject: [PATCH 493/871] Remove unused Ode block type (BlueBrain/nmodl#1003) NMODL Repo SHA: BlueBrain/nmodl@426d1252468d5cd619fc9991982b78dc02b267a8 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 25 ----------------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 11 ---------- 2 files changed, 36 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 844041280b..848725f577 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -422,19 +422,6 @@ bool CodegenCVisitor::range_variable_setup_required() const noexcept { } -bool CodegenCVisitor::state_variable(const std::string& name) const { - // clang-format off - auto result = std::find_if(info.state_vars.begin(), - info.state_vars.end(), - [&name](const SymbolType& sym) { - return name == sym->get_name(); - } - ); - // clang-format on - return result != info.state_vars.end(); -} - - int CodegenCVisitor::position_of_float_var(const std::string& name) const { int index = 0; for (const auto& var: codegen_float_variables) { @@ -602,9 +589,6 @@ int CodegenCVisitor::int_variables_size() const { * method return statements as vector. As different code backends could have * different variable names, we rely on backend-specific read_ion_variable_name * and write_ion_variable_name method which will be overloaded. - * - * \todo After looking into mod2c and neuron implementation, it seems like - * Ode block type is not used (?). Need to look into implementation details. */ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { if (optimize_ion_variable_copies()) { @@ -614,18 +598,12 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { for (const auto& ion: info.ions) { auto name = ion.name; for (const auto& var: ion.reads) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { - continue; - } auto variable_names = read_ion_variable_name(var); auto first = get_variable_name(variable_names.first); auto second = get_variable_name(variable_names.second); statements.push_back(fmt::format("{} = {};", first, second)); } for (const auto& var: ion.writes) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { - continue; - } if (ion.is_ionic_conc(var)) { auto variables = read_ion_variable_name(var); auto first = get_variable_name(variables.first); @@ -642,9 +620,6 @@ std::vector CodegenCVisitor::ion_read_statements_optimized(BlockTyp std::vector statements; for (const auto& ion: info.ions) { for (const auto& var: ion.writes) { - if (type == BlockType::Ode && ion.is_ionic_conc(var) && state_variable(var)) { - continue; - } if (ion.is_ionic_conc(var)) { auto variables = read_ion_variable_name(var); auto first = "ionvar." + variables.first; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index a256f7acd9..c87538bf5d 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -66,9 +66,6 @@ enum BlockType { /// breakpoint block Equation, - /// ode_* routines block (not used) - Ode, - /// derivative block State, @@ -419,14 +416,6 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { } - /** - * Checks if the given variable name belongs to a state variable - * \param name The variable name - * \return \c true if the variable is a state variable - */ - bool state_variable(const std::string& name) const; - - /** * Check if net receive/send buffering kernels required */ From 1ac2eba409dcb8d40a5f7cba8083129e745a7ea9 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 22 Feb 2023 12:47:55 +0100 Subject: [PATCH 494/871] Deprecate EXTERNAL variables (BlueBrain/nmodl#991) NMODL Repo SHA: BlueBrain/nmodl@9282b085e77e036b2ad641d35ab893d5a91588a4 --- .../codegen/codegen_compatibility_visitor.cpp | 5 +- .../codegen/codegen_compatibility_visitor.hpp | 15 +- src/nmodl/language/nmodl.yaml | 2 +- test/nmodl/transpiler/unit/CMakeLists.txt | 6 +- .../codegen/codegen_compatibility_visitor.cpp | 167 ++++++++++++++++++ 5 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index b38946de79..89076df697 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -25,7 +25,8 @@ const std::map {AstNodeType::GLOBAL_VAR, &CodegenCompatibilityVisitor::return_error_global_var}, {AstNodeType::PARAM_ASSIGN, &CodegenCompatibilityVisitor::return_error_param_var}, {AstNodeType::BBCORE_POINTER_VAR, - &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}}); + &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}, + {AstNodeType::EXTERNAL, &CodegenCompatibilityVisitor::return_error_with_name}}); std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( @@ -35,7 +36,7 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl std::stringstream unhandled_method_error_message; auto method = solve_block_ast_node->get_method(); if (!method) { - return ""; + return {}; } auto unhandled_solver_method = handled_solvers.find(method->get_node_name()) == handled_solvers.end(); diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index d24e60b977..4894205c2c 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -100,10 +100,17 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { template std::string return_error_with_name(ast::Ast& node, const std::shared_ptr& ast_node) { auto real_type_block = std::dynamic_pointer_cast(ast_node); - return fmt::format("\"{}\" {}construct found at [{}] is not handled\n", - ast_node->get_node_name(), - real_type_block->get_nmodl_name(), - real_type_block->get_token()->position()); + auto token = real_type_block->get_token(); + try { + return fmt::format("\"{}\" {}construct found at [{}] is not handled\n", + ast_node->get_node_name(), + real_type_block->get_nmodl_name(), + token ? token->position() : "unknown location"); + } catch (const std::logic_error&) { + return fmt::format("{}construct found at [{}] is not handled\n", + real_type_block->get_nmodl_name(), + token ? token->position() : "unknown location"); + } } /// Takes as parameter an std::shared_ptr node diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 58a8b3bb49..f648e43e17 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1770,7 +1770,7 @@ type: ExternVar vector: true separator: ", " - brief: "Represents EXTERNAL statement in NMODL" + brief: "This construct is deprecated and no longer supported in the NMODL" - ThreadSafe: nmodl: THREADSAFE diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index ce57584f97..ba3558e452 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -65,8 +65,10 @@ add_executable(testcrout crout/crout.cpp ${CROUT_SOLVER_SOURCE_FILES}) add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) -add_executable(testcodegen codegen/main.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp - codegen/codegen_cpp_visitor.cpp codegen/transform.cpp) +add_executable( + testcodegen + codegen/main.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp + codegen/codegen_cpp_visitor.cpp codegen/transform.cpp codegen/codegen_compatibility_visitor.cpp) target_link_libraries(testmodtoken lexer util) target_link_libraries(testlexer lexer util) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp new file mode 100644 index 0000000000..fc087cac1d --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp @@ -0,0 +1,167 @@ +/************************************************************************* + * Copyright (C) 2023 Blue Brain Project + * + * This file is part of NMODL distributed under the terms of the GNU + * Lesser General Public License. See top-level LICENSE file for details. + *************************************************************************/ + +#include + +#include "ast/program.hpp" +#include "codegen/codegen_compatibility_visitor.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/perf_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 + +using namespace nmodl; +using namespace visitor; +using namespace codegen; + +using nmodl::parser::NmodlDriver; + +/// Return true if it failed and false otherwise +bool runCompatibilityVisitor(const std::string& nmodl_text) { + const auto& ast = NmodlDriver().parse_string(nmodl_text); + SymtabVisitor().visit_program(*ast); + PerfVisitor().visit_program(*ast); + return CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast); +} + +SCENARIO("Uncompatible constructs should failed", "[codegen][compatibility_visitor]") { + GIVEN("A mod file containing an EXTERNAL construct") { + std::string const nmodl_text = R"( + NEURON { + EXTERNAL apc_metap + } + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } + GIVEN("A mod file containing a written GLOBAL var") { + std::string const nmodl_text = R"( + NEURON { + GLOBAL foo + } + + PROCEDURE bar() { + foo = 1 + } + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } + GIVEN("A mod file containing a written un-writtable var") { + std::string const nmodl_text = R"( + PARAMETER { + foo = 1 + } + + PROCEDURE bar() { + foo = 1 + } + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } + GIVEN("A mod file with BBCOREPOINTER without bbcore_read / bbcore_write") { + std::string const nmodl_text = R"( + NEURON { + BBCOREPOINTER rng + } + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } + GIVEN("A mod file with BBCOREPOINTER without bbcore_write") { + std::string const nmodl_text = R"( + NEURON { + BBCOREPOINTER rng + } + + VERBATIM + static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + } + ENDVERBATIM + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } + GIVEN("A mod file with BBCOREPOINTER without bbcore_read") { + std::string const nmodl_text = R"( + NEURON { + BBCOREPOINTER rng + } + + VERBATIM + static void bbcore_write(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + } + ENDVERBATIM + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } + GIVEN("A mod file with BBCOREPOINTER with bbcore_read / bbcore_write") { + std::string const nmodl_text = R"( + NEURON { + BBCOREPOINTER rng + } + + VERBATIM + static void bbcore_read(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + } + static void bbcore_write(double* x, int* d, int* xx, int* offset, _threadargsproto_) { + } + ENDVERBATIM + )"; + + THEN("should succeed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(!failed); + } + } + GIVEN("A mod file with a no SOLVE method") { + std::string const nmodl_text = R"( + BREAKPOINT { + SOLVE state + } + )"; + + THEN("should succeed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(!failed); + } + } + GIVEN("A mod file with a invalid SOLVE method") { + std::string const nmodl_text = R"( + BREAKPOINT { + SOLVE state METHOD runge + } + )"; + + THEN("should failed") { + bool failed = runCompatibilityVisitor(nmodl_text); + REQUIRE(failed); + } + } +} From d2c17c7dcd6a97bc0db81905221fd0903b000839 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Tue, 7 Mar 2023 14:07:50 +0100 Subject: [PATCH 495/871] sympy@1.11.1 yields (-A-B) not -(A+B) (BlueBrain/nmodl#1010) Also generously relax version constraints. NMODL Repo SHA: BlueBrain/nmodl@b2d5dfe80ea4e04d7c6f6f7e7cc126dfdcbdf9fe --- .../transpiler/unit/visitor/sympy_solver.cpp | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 587fd69832..6ad2a5c17b 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -2359,12 +2359,7 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri double* nmodl_eigen_j = nmodl_eigen_jm.data(); double* nmodl_eigen_f = nmodl_eigen_fm.data(); nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); - nmodl_eigen_j[static_cast(0)] = -(nt->_dt + inst->tau[id]) / inst->tau[id]; - } - - void finalize() { - } - };)"; + nmodl_eigen_j[static_cast(0)] =)"; std::string expected_functor_cacum_1_definition = R"(struct functor_cacum_1 { NrnThread* nt; @@ -2386,12 +2381,7 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri double* nmodl_eigen_j = nmodl_eigen_jm.data(); double* nmodl_eigen_f = nmodl_eigen_fm.data(); nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); - nmodl_eigen_j[static_cast(0)] = -(nt->_dt + inst->tau[id]) / inst->tau[id]; - } - - void finalize() { - } - };)"; + nmodl_eigen_j[static_cast(0)] =)"; std::string expected_functor_cacum_2_definition = R"(struct functor_cacum_2 { NrnThread* nt; @@ -2413,12 +2403,7 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri double* nmodl_eigen_j = nmodl_eigen_jm.data(); double* nmodl_eigen_f = nmodl_eigen_fm.data(); nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); - nmodl_eigen_j[static_cast(0)] = -(nt->_dt + inst->tau[id]) / inst->tau[id]; - } - - void finalize() { - } - };)"; + nmodl_eigen_j[static_cast(0)] =)"; // Expected functor usages std::string expected_functor_cacum_0_usage = R"(functor_cacum_0 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; From 62c0989221a061104b1bac787fb23f80c063a686 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 23 Mar 2023 11:36:36 +0100 Subject: [PATCH 496/871] BAType: use name instead of values (BlueBrain/nmodl#1006) * add before/after usage in cabpump.mod integration test NMODL Repo SHA: BlueBrain/nmodl@f4f722f72a24708f0a3f69951688302e38c191ff --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 18 ++++---- .../transpiler/integration/mod/cabpump.mod | 8 ++++ .../unit/codegen/codegen_cpp_visitor.cpp | 41 ++++++++++++------- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 848725f577..635c977576 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -2823,18 +2823,18 @@ void CodegenCVisitor::print_global_variables_for_hoc() { * STEP, the registration type (as an integer) is calculated. * These values are then interpreted by CoreNEURON internally. */ -static size_t get_register_type_for_ba_block(const ast::Block* block) { - size_t register_type = 0; +static std::string get_register_type_for_ba_block(const ast::Block* block) { + std::string register_type{}; BAType ba_type{}; /// before block have value 10 and after block 20 if (block->is_before_block()) { // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - register_type = 10; + register_type = "BAType::Before"; ba_type = dynamic_cast(block)->get_bablock()->get_type()->get_value(); } else { // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - register_type = 20; + register_type = "BAType::After"; ba_type = dynamic_cast(block)->get_bablock()->get_type()->get_value(); } @@ -2842,13 +2842,13 @@ static size_t get_register_type_for_ba_block(const ast::Block* block) { /// associated blocks have different values (1 to 4) based on type. /// These values are based on neuron/coreneuron implementation details. if (ba_type == BATYPE_BREAKPOINT) { - register_type += 1; + register_type += " + BAType::Breakpoint"; } else if (ba_type == BATYPE_SOLVE) { - register_type += 2; + register_type += " + BAType::Solve"; } else if (ba_type == BATYPE_INITIAL) { - register_type += 3; + register_type += " + BAType::Initial"; } else if (ba_type == BATYPE_STEP) { - register_type += 4; + register_type += " + BAType::Step"; } else { throw std::runtime_error("Unhandled Before/After type encountered during code generation"); } @@ -3004,7 +3004,7 @@ void CodegenCVisitor::print_mechanism_register() { for (size_t i = 0; i < info.before_after_blocks.size(); i++) { // register type and associated function name for the block const auto& block = info.before_after_blocks[i]; - size_t register_type = get_register_type_for_ba_block(block); + std::string register_type = get_register_type_for_ba_block(block); std::string function_name = method_name(fmt::format("nrn_before_after_{}", i)); printer->fmt_line("hoc_reg_ba(mech_type, {}, {});", function_name, register_type); } diff --git a/test/nmodl/transpiler/integration/mod/cabpump.mod b/test/nmodl/transpiler/integration/mod/cabpump.mod index 33198b1145..e2d7485418 100644 --- a/test/nmodl/transpiler/integration/mod/cabpump.mod +++ b/test/nmodl/transpiler/integration/mod/cabpump.mod @@ -76,7 +76,15 @@ PROCEDURE test_table_p(br) { : to test \r only as newline PROCEDURE test_r() { } : add something that will breaks the parser +BEFORE INITIAL { + ainf = 2 +} + INITIAL { var_init(var) PROTECT ca = ca + 1 } + +AFTER SOLVE { + ainf = 3 +} diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 83cc4e3e62..72cf1d8d38 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -496,10 +496,11 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a )"; THEN("They should be well registered") { auto const generated = get_cpp_code(nmodl_text); - // 11: BEFORE BREAKPOINT + // BEFORE BREAKPOINT { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, 11);")); + Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, " + "BAType::Before + BAType::Breakpoint);")); // in case of PROTECT, there should not be simd or ivdep pragma std::string generated_code = R"( @@ -518,10 +519,11 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a auto const expected = generated_code; REQUIRE_THAT(generated, Contains(expected)); } - // 23: AFTER SOLVE + // AFTER SOLVE { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, 22);")); + Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, BAType::After " + "+ BAType::Solve);")); // in case of MUTEXLOCK/MUTEXUNLOCK, there should not be simd or ivdep pragma std::string generated_code = R"( @@ -542,10 +544,11 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a auto const expected = generated_code; REQUIRE_THAT(generated, Contains(expected)); } - // 11: BEFORE INITIAL + // BEFORE INITIAL { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, 13);")); + Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, " + "BAType::Before + BAType::Initial);")); std::string generated_code = R"( #pragma ivdep #pragma omp simd @@ -563,10 +566,11 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a auto const expected = generated_code; REQUIRE_THAT(generated, Contains(expected)); } - // 21: AFTER INITIAL + // AFTER INITIAL { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, 23);")); + Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, BAType::After " + "+ BAType::Initial);")); std::string generated_code = R"( #pragma ivdep #pragma omp simd @@ -584,10 +588,11 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a auto const expected = generated_code; REQUIRE_THAT(generated, Contains(expected)); } - // 13: BEFORE STEP + // BEFORE STEP { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_4_ba1, 14);")); + Contains("hoc_reg_ba(mech_type, nrn_before_after_4_ba1, " + "BAType::Before + BAType::Step);")); std::string generated_code = R"( #pragma ivdep #pragma omp simd @@ -620,10 +625,18 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a )"; THEN("They should be all registered") { auto const generated = get_cpp_code(nmodl_text); - REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, 14);")); - REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, 22);")); - REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, 14);")); - REQUIRE_THAT(generated, Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, 22);")); + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, BAType::Before + " + "BAType::Step);")); + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, BAType::After + " + "BAType::Solve);")); + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, BAType::Before + " + "BAType::Step);")); + REQUIRE_THAT(generated, + Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, BAType::After + " + "BAType::Solve);")); } } } From 700e18eb2dae241acb007093c72ecb10948252fb Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 6 Apr 2023 21:58:19 +0200 Subject: [PATCH 497/871] Remove reduce/shift conflicts (BlueBrain/nmodl#1015) NMODL Repo SHA: BlueBrain/nmodl@8457f818531c24d392d14d0eb584bdbe894a1d0e --- src/nmodl/parser/diffeq.yy | 14 +++++++++----- src/nmodl/parser/unit.yy | 3 +-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index 0291047ed2..c75a916cbd 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -75,7 +75,7 @@ %type top -%type expression e arglist arg +%type expression e arglist arg arglist_opt /* operator associativity */ @@ -149,7 +149,7 @@ e : ATOM { $$ = eq_context.solution; } - | ATOM "(" arglist ")" { + | ATOM "(" arglist_opt ")" { eq_context.solution = Term(); eq_context.solution.expr = $1 + "(" + $3.expr + ")"; eq_context.solution.b = $1 + "(" + $3.expr + ")"; @@ -194,10 +194,14 @@ e : ATOM { -arglist : /*nothing*/ { $$ = Term("", "0.0", "0.0", ""); } +arglist_opt : /*nothing*/ { $$ = Term("", "0.0", "0.0", ""); } - | arg { $$ = $1; } + | arglist { $$ = $1; } + ; + + +arglist : arg { $$ = $1; } | arglist arg { eq_context.solution.expr = $1.expr + " " + $2.expr; eq_context.solution.b = $1.expr + " " + $2.expr; @@ -209,7 +213,7 @@ arglist : /*nothing*/ { $$ = Term("", "0.0", "0.0", ""); } eq_context.solution.b = $1.expr + "," + $3.expr; $$ = eq_context.solution; } - ; + ; diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index 5544217c54..fd1fbcb0b9 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -98,8 +98,7 @@ %% unit_table - : END - | table_insertion END + : table_insertion END table_insertion : { From 6640e417a51f676dfa7fd673e7f2448fdf8a5888 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Thu, 6 Apr 2023 22:02:38 +0200 Subject: [PATCH 498/871] Print renaming of variables only in debug verbosity (BlueBrain/nmodl#1023) NMODL Repo SHA: BlueBrain/nmodl@1c47e20d2467fc62fb1065df2dcff686f6e002a8 --- src/nmodl/visitors/rename_visitor.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 0771aa7abc..4d97785825 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -53,7 +53,10 @@ void RenameVisitor::visit_name(const ast::Name& node) { std::string token_string = node.get_token() != nullptr ? " at " + node.get_token()->position() : ""; - logger->warn("RenameVisitor :: Renaming variable {}{} to {}", name, token_string, new_name); + logger->debug("RenameVisitor :: Renaming variable {}{} to {}", + name, + token_string, + new_name); } } @@ -88,9 +91,9 @@ void RenameVisitor::visit_verbatim(const ast::Verbatim& node) { /// new_name to the renamed_variables map const std::string& new_name = new_name_generator(token); result << new_name; - logger->warn("RenameVisitor :: Renaming variable {} in VERBATIM block to {}", - token, - new_name); + logger->debug("RenameVisitor :: Renaming variable {} in VERBATIM block to {}", + token, + new_name); } else { result << token; } From 81fa13aaf9c3bab985f403ca2628b4323fc1eefe Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Wed, 12 Apr 2023 18:16:22 +0200 Subject: [PATCH 499/871] Bug fix in code generation in net_send() call in INITIAL block under NET_RECEIVE (BlueBrain/nmodl#1017) * Bug fix for net_send() code generation in INITIAL block of NET_RECEIVE - `net_init()` is generated when INITIAL block is used within NET_RECEIVE block in a synapse MOD file - when we emit call to `net_send_buffering()`, the `weight_index` is 0 or an argument `weight_index` itself depending on whether code being printed is for NET_RECEIVE block or top level INITIAL block. - current implementation wasn't considering a use case where INITIAL block can appear within NET_RECEIVE block. * Add unit tets for code generation with two cases NMODL Repo SHA: BlueBrain/nmodl@a0f77f440b76775419ac2c1aa2b8d56c4d4d2167 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 9 +++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 6 ++++ .../unit/codegen/codegen_cpp_visitor.cpp | 33 +++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 635c977576..9948a80cb0 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -3771,8 +3771,9 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { std::string weight_index = "weight_index"; std::string pnt = "pnt"; - // for non-net_receieve functions i.e. initial block, the weight_index argument is 0. - if (!printing_net_receive) { + // for functions not generated from NET_RECEIVE blocks (i.e. top level INITIAL block) + // the weight_index argument is 0. + if (!printing_net_receive && !printing_net_init) { weight_index = "0"; auto var = get_variable_name("point_process"); if (info.artificial_cell) { @@ -3797,7 +3798,7 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { - if (!printing_net_receive) { + if (!printing_net_receive && !printing_net_init) { throw std::runtime_error("Error : net_move only allowed in NET_RECEIVE block"); } @@ -3886,6 +3887,7 @@ void CodegenCVisitor::print_net_init() { rename_net_receive_arguments(*info.net_receive_node, *node); codegen = true; + printing_net_init = true; auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); @@ -3905,6 +3907,7 @@ void CodegenCVisitor::print_net_init() { } printer->end_block(1); codegen = false; + printing_net_init = false; } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index c87538bf5d..6a47849728 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -237,6 +237,12 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { */ bool printing_net_receive = false; + /** + * \c true if currently initial block of net_receive being printed + */ + bool printing_net_init = false; + + /** * \c true if currently printing top level verbatim blocks */ diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 72cf1d8d38..cfbb4e85e2 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -941,6 +941,39 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even REQUIRE_THAT(generated, Contains(expected_code)); } } + + GIVEN("A mod file with an INITIAL with net_send() inside NET_RECEIVE") { + std::string const nmodl_text = R"( + NET_RECEIVE(w) { + INITIAL { + net_send(5, 1) + } + } + )"; + THEN("It should generate a net_send_buffering with weight_index as parameter variable") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code( + "net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], " + "weight_index, point_process, nt->_t+5.0, 1.0);"); + REQUIRE_THAT(generated, Contains(expected_code)); + } + } + + GIVEN("A mod file with a top level INITIAL block with net_send()") { + std::string const nmodl_text = R"( + INITIAL { + net_send(5, 1) + } + )"; + THEN("It should generate a net_send_buffering with weight_index parameter as 0") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code( + "net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], 0, " + "point_process, nt->_t+5.0, 1.0);"); + REQUIRE_THAT(generated, Contains(expected_code)); + } + } + GIVEN("A mod file with FOR_NETCONS") { std::string const nmodl_text = R"( NET_RECEIVE(w) { From af1b2f09b0b9e6bb517b99ae4350dd9610cb1b07 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 12 Apr 2023 18:17:48 +0200 Subject: [PATCH 500/871] Fix parsing of double from the unit file (BlueBrain/nmodl#1022) * Avoid integer-overflow from fmt * Use Matchers to have better error in tests * Simplify FRACTION * Merge units_nom and units_denom Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@503e013e3744ea0d70bec1ecf6a4f9dcde2cc6c5 --- src/nmodl/lexer/unit.ll | 2 +- src/nmodl/parser/main_units.cpp | 1 + src/nmodl/parser/unit.yy | 40 ++----- src/nmodl/units/units.cpp | 119 +++++-------------- src/nmodl/units/units.hpp | 8 +- test/nmodl/transpiler/unit/units/lexer.cpp | 3 +- test/nmodl/transpiler/unit/units/parser.cpp | 56 ++++++--- test/nmodl/transpiler/unit/visitor/units.cpp | 4 + 8 files changed, 87 insertions(+), 146 deletions(-) diff --git a/src/nmodl/lexer/unit.ll b/src/nmodl/lexer/unit.ll index 1665082048..c2fca509dc 100755 --- a/src/nmodl/lexer/unit.ll +++ b/src/nmodl/lexer/unit.ll @@ -140,7 +140,7 @@ NEWUNIT ({CHAR}+{D}*)|({CHAR}+{D}*"_"{CHAR}+{D}*)|({CHAR}+{D}*"/"{CHAR}+{D}*)|({ return UnitParser::make_DOUBLE(yytext, loc); } -{DBL}"|"{DBL} { +"|" { return UnitParser::make_FRACTION(yytext, loc); } diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index 47052223ff..4f10512dfa 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -43,6 +43,7 @@ int main(int argc, const char* argv[]) { // just call parser method driver.parse_stream(file); + driver.table->print_units_sorted(std::cout); } return 0; diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index fd1fbcb0b9..49f7ebe4ab 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -70,8 +70,7 @@ %token UNIT_PROD %token DIVISION -%type >> units_nom -%type >> units_denom +%type >> units %type > nominator %type > item %type > prefix @@ -117,56 +116,39 @@ table_insertion } ; -units_nom +units : { $$ = std::make_shared>(); } - | UNIT units_nom { + | UNIT units { $2->push_back($1); $$ = $2; } - | UNIT_POWER units_nom { + | UNIT_POWER units { $2->push_back($1); $$ = $2; } - | UNIT_PROD units_nom { - $$ = $2; - } - ; - -units_denom - : { - $$ = std::make_shared>(); - } - | UNIT units_denom { - $2->push_back($1); - $$ = $2; - } - | UNIT_POWER units_denom { - $2->push_back($1); - $$ = $2; - } - | UNIT_PROD units_denom { + | UNIT_PROD units { $$ = $2; } ; nominator - : units_nom { + : units { auto newunit = std::make_shared(); newunit->add_nominator_unit($1); $$ = newunit; } - | DOUBLE units_nom { + | DOUBLE units { auto newunit = std::make_shared(); newunit->add_nominator_unit($2); newunit->add_nominator_double($1); $$ = newunit; } - | FRACTION units_nom { + | DOUBLE FRACTION DOUBLE units { auto newunit = std::make_shared(); - newunit->add_nominator_unit($2); - newunit->add_fraction($1); + newunit->add_nominator_unit($4); + newunit->add_fraction($1, $3); $$ = newunit; } ; @@ -195,7 +177,7 @@ item $2->add_unit($1); $$ = $2; } - | NEW_UNIT nominator DIVISION units_denom { + | NEW_UNIT nominator DIVISION units { $2->add_unit($1); $2->add_denominator_unit($4); $$ = $2; diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 03edb17b65..4d8e56a395 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -18,6 +18,7 @@ #include #include "units.hpp" +#include /** * \file @@ -93,57 +94,26 @@ void Unit::mul_factor(const double double_factor) { unit_factor *= double_factor; } -void Unit::add_fraction(const std::string& fraction_string) { - double nom{}, denom{}; - std::string nominator; - std::string denominator; - std::string::const_iterator it; - for (it = fraction_string.begin(); it != fraction_string.end() && *it != '|'; ++it) { - nominator.push_back(*it); - } - // pass "|" char - ++it; - for (auto itm = it; itm != fraction_string.end(); ++itm) { - denominator.push_back(*itm); - } - nom = parse_double(nominator); - denom = parse_double(denominator); +void Unit::add_fraction(const std::string& nominator, const std::string& denominator) { + double nom = parse_double(nominator); + double denom = parse_double(denominator); unit_factor = nom / denom; } +/** + * Double numbers in the \c nrnunits.lib file are defined in the form [.0-9]+[-+][0-9]+ + * To make sure they are parsed in the correct way and similarly to the NEURON parser + * we convert this string to a string compliant to scientific notation to be able to be parsed + * from std::stod(). To do that we have to add an `e` in case there is `-+` in the number + *string if it doesn't exist. + */ double Unit::parse_double(std::string double_string) { - long double d_number{}; - double d_magnitude{}; - std::string s_number; - std::string s_magnitude; - std::string::const_iterator it; - // if double is positive sign = 1 else sign = -1 - // to be able to be multiplied by the d_number - int sign = 1; - if (double_string.front() == '-') { - sign = -1; - double_string.erase(double_string.begin()); - } - // if *it reached an exponent related char, then the whole double number is read - for (it = double_string.begin(); - it != double_string.end() && *it != 'e' && *it != '+' && *it != '-'; - ++it) { - s_number.push_back(*it); - } - // then read the magnitude of the double number - for (auto itm = it; itm != double_string.end(); ++itm) { - if (*itm != 'e') { - s_magnitude.push_back(*itm); - } - } - d_number = std::stold(s_number); - if (s_magnitude.empty()) { - d_magnitude = 0.0; - } else { - d_magnitude = std::stod(s_magnitude); + auto pos = double_string.find_first_of("+-", 1); // start at 1 pos, because we don't care with + // the front sign + if (pos != std::string::npos && double_string.find_last_of("eE", pos) == std::string::npos) { + double_string.insert(pos, "e"); } - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - return static_cast(d_number * powl(10.0, d_magnitude) * sign); + return std::stod(double_string); } // NOLINTNEXTLINE(readability-function-cognitive-complexity) @@ -210,9 +180,8 @@ void UnitTable::calc_nominator_dims(const std::shared_ptr& unit, std::stri // if the nominator is still not found in the table then output error // else multiply its factor to the unit factor and calculate unit's dimensions if (nominator == table.end()) { - std::stringstream ss; - ss << "Unit " << nominator_name << " not defined!" << std::endl; - throw std::runtime_error(ss.str()); + std::string ss = fmt::format("Unit {} not defined!", nominator_name); + throw std::runtime_error(ss); } else { for (int i = 0; i < nominator_power; i++) { unit->mul_factor(nominator_prefix_factor * nominator->second->get_factor()); @@ -238,15 +207,15 @@ void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, // if the denominator_name is not in the table, check if there are any prefixes or power if (denominator == table.end()) { - int changed_denominator_name = 1; + bool changed_denominator_name = true; while (changed_denominator_name) { - changed_denominator_name = 0; + changed_denominator_name = false; for (const auto& it: prefixes) { auto res = std::mismatch(it.first.begin(), it.first.end(), denominator_name.begin()); if (res.first == it.first.end()) { - changed_denominator_name = 1; + changed_denominator_name = true; denominator_prefix_factor *= it.second; denominator_name.erase(denominator_name.begin(), denominator_name.begin() + @@ -285,9 +254,8 @@ void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, } if (denominator == table.end()) { - std::stringstream ss; - ss << "Unit " << denominator_name << " not defined!" << std::endl; - throw std::runtime_error(ss.str()); + std::string ss = fmt::format("Unit {} not defined!", denominator_name); + throw std::runtime_error(ss); } else { for (int i = 0; i < denominator_power; i++) { unit->mul_factor(1.0 / (denominator_prefix_factor * denominator->second->get_factor())); @@ -341,52 +309,25 @@ void UnitTable::insert_prefix(const std::shared_ptr& prfx) { prefixes.insert({prfx->get_name(), prfx->get_factor()}); } -void UnitTable::print_units() const { - for (const auto& it: table) { - std::cout << std::fixed << std::setprecision(output_precision) << it.first << ' ' - << it.second->get_factor() << ':'; - for (const auto& dims: it.second->get_dimensions()) { - std::cout << ' ' << dims; - } - std::cout << '\n'; - } -} - -void UnitTable::print_base_units() const { - int first_print = 1; - for (const auto& it: base_units_names) { - if (!it.empty()) { - if (first_print) { - first_print = 0; - std::cout << it; - } else { - std::cout << ' ' << it; - } - } - } - std::cout << '\n'; -} - void UnitTable::print_units_sorted(std::ostream& units_details) const { std::vector>> sorted_elements(table.begin(), table.end()); std::sort(sorted_elements.begin(), sorted_elements.end()); for (const auto& it: sorted_elements) { - units_details << std::fixed << std::setprecision(output_precision) << it.first << ' ' - << it.second->get_factor() << ':'; - for (const auto& dims: it.second->get_dimensions()) { - units_details << ' ' << dims; - } - units_details << '\n'; + units_details << fmt::format("{} {:.{}f}: {}\n", + it.first, + it.second->get_factor(), + output_precision, + fmt::join(it.second->get_dimensions(), " ")); } } void UnitTable::print_base_units(std::ostream& base_units_details) const { - int first_print = 1; + bool first_print = true; for (const auto& it: base_units_names) { if (!it.empty()) { if (first_print) { - first_print = 0; + first_print = false; base_units_details << it; } else { base_units_details << ' ' << it; diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index a94a93e845..0165e8b822 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -143,7 +143,7 @@ class Unit { void mul_factor(double double_factor); /// Parse a fraction given as string and store the result to the factor of the Unit - void add_fraction(const std::string& fraction_string); + void add_fraction(const std::string& nominator, const std::string& denominator); /// Parse a double number given as string. The double can be positive or negative and /// have all kinds of representations @@ -275,17 +275,11 @@ class UnitTable { return table.at(unit_name); } - /// Print the details of the units that are stored in the UnitTable - void print_units() const; - /// Print the details of the units that are stored in the UnitTable /// to the output stream units_details in ascending order to be printed /// in tests in specific order void print_units_sorted(std::ostream& units_details) const; - /// Print the base units that are stored in the UnitTable - void print_base_units() const; - /// Print the base units that are stored in the UnitTable to the /// output stream base_units_details void print_base_units(std::ostream& base_units_details) const; diff --git a/test/nmodl/transpiler/unit/units/lexer.cpp b/test/nmodl/transpiler/unit/units/lexer.cpp index d51e0e478a..0e8c88eb67 100644 --- a/test/nmodl/transpiler/unit/units/lexer.cpp +++ b/test/nmodl/transpiler/unit/units/lexer.cpp @@ -46,10 +46,9 @@ TEST_CASE("Unit Lexer tests for valid tokens", "[lexer][unit]") { REQUIRE(check_token_type("1", Token::DOUBLE)); REQUIRE(check_token_type("-1.324e+10", Token::DOUBLE)); REQUIRE(check_token_type("1-1", Token::DOUBLE)); - REQUIRE(check_token_type("1|100", Token::FRACTION)); + REQUIRE(check_token_type("|", Token::FRACTION)); REQUIRE(check_token_type(".03", Token::DOUBLE)); REQUIRE(check_token_type("12345e-2", Token::DOUBLE)); - REQUIRE(check_token_type("1|8.988e9", Token::FRACTION)); } SECTION("Tests for units") { diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index bfb4edb218..2133d147a7 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -22,6 +22,7 @@ //============================================================================= using namespace nmodl::test_utils; +using Catch::Matchers::Contains; // Driver is defined as global to store all the units inserted to it and to be // able to define complex units based on base units @@ -41,10 +42,6 @@ std::string parse_string(const std::string& unit_definition) { return ss.str(); } -bool is_substring(const std::string& str1, const std::string& str2) { - return str1.find(str2) != std::string::npos; -} - SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { GIVEN("A base unit") { WHEN("Base unit is *a*") { @@ -125,6 +122,16 @@ SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { REQUIRE(is_valid_construct("dipotre\t\t\t/m\n")); } } + WHEN("Nominator is unknown") { + THEN("it throws") { + REQUIRE_THROWS(parse_string("foo 1 pew/m\n")); + } + } + WHEN("Denominator is unknown") { + THEN("it throws") { + REQUIRE_THROWS(parse_string("foo 1 m/pew\n")); + } + } } GIVEN("A double number and some units") { WHEN("Double number is multiplied by a power of 10 with division of multiple units") { @@ -137,6 +144,19 @@ SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { REQUIRE(is_valid_construct("grade\t\t\t.9 degree\n")); } } + WHEN("A 's' is added") { + THEN("parser remove it to find the units") { + REQUIRE_NOTHROW(parse_string("pew 1 m\nfoo 2 pews\n")); + REQUIRE_NOTHROW(parse_string("pew 1 m\nfoo 2 /pews\n")); + } + } + WHEN("No unit but only a prefix factor") { + THEN("parser multiply the number by the factor") { + std::string parsed_unit{}; + REQUIRE_NOTHROW(parsed_unit = parse_string("pew 1 1/milli")); + REQUIRE_THAT(parsed_unit, Contains("pew 0.00100000: 0 0 0 0 0 0 0 0 0 0")); + } + } } GIVEN("A fraction and some units") { WHEN("Fraction is writen like 1|2") { @@ -172,20 +192,20 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars R2 8314 mV-coul/degC )"; std::string parsed_units = parse_string(reindent_text(units_definitions)); - REQUIRE(is_substring(parsed_units, "mV 0.00100000: 2 1 -2 -1 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "mM 1.00000000: -3 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "mA 0.00100000: 0 0 -1 1 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "KTOMV 0.00008530: 2 1 -2 -1 0 0 0 0 0 -1")); - REQUIRE(is_substring(parsed_units, "B 26.00000000: -1 0 0 -1 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "dummy1 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "dummy2 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "dummy3 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "dummy4 -0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "dummy5 0.02500000: 0 0 0 0 0 0 0 0 0 0")); - REQUIRE(is_substring(parsed_units, "R 8.31446262: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE(is_substring(parsed_units, "R1 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE(is_substring(parsed_units, "R2 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE(is_substring(parsed_units, "m kg sec coul candela dollar bit erlang K")); + REQUIRE_THAT(parsed_units, Contains("mV 0.00100000: 2 1 -2 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("mM 1.00000000: -3 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("mA 0.00100000: 0 0 -1 1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("KTOMV 0.00008530: 2 1 -2 -1 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("B 26.00000000: -1 0 0 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy1 0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy2 0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy3 0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy4 -0.02500000: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy5 0.02500000: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("R 8.31446262: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("R1 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("R2 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("m kg sec coul candela dollar bit erlang K")); } } } diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 353e7817ee..2165d3ae2c 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -153,6 +153,8 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] KTOMV = 0.0853 (mV/degC) B = 0.26 (mM-cm2/mA-ms) TEMP = 25 (degC) + toyfuzz = (1) (volt) + numbertwo = 2 (1) } )"; @@ -194,6 +196,8 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] KTOMV 0.0853: m2 kg1 sec-2 coul-1 K-1 B 0.26: m-1 coul-1 TEMP 25: K1 + toyfuzz 1: constant + numbertwo 2: constant )"; THEN("Print the units that were added") { From d25681a86772500b481af051fa55f222ddbe4d5f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 13 Apr 2023 12:45:52 +0200 Subject: [PATCH 501/871] Fix unit by using :a and printing string (BlueBrain/nmodl#1018) Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@4cb05c84f717ae3f2b3c6a3dd267e474680ec2ee --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 10 ++-------- src/nmodl/visitors/units_visitor.cpp | 2 +- test/nmodl/transpiler/unit/visitor/units.cpp | 12 ++++++------ 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 9948a80cb0..f51522c641 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -2148,14 +2148,8 @@ void CodegenCVisitor::print_nmodl_constants() { printer->add_newline(2); printer->add_line("/** constants used in nmodl from UNITS */"); for (const auto& it: info.factor_definitions) { -#ifdef USE_LEGACY_UNITS - const std::string format_string = "static const double {} = {:g};"; -#else - const std::string format_string = "static const double {} = {:.18g};"; -#endif - printer->fmt_line(format_string, - it->get_node_name(), - stod(it->get_value()->get_value())); + const std::string format_string = "static const double {} = {};"; + printer->fmt_line(format_string, it->get_node_name(), it->get_value()->get_value()); } } } diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 9d149197ed..cad73fd4ee 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -151,7 +151,7 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { #ifdef USE_LEGACY_UNITS auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:g}"); #else - auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:.18g}"); + auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:a}"); #endif auto double_value_ptr = std::make_shared(ast::Double(unit_factor)); diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 2165d3ae2c..95266d4973 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -181,13 +181,13 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] um3 0.00100000: m3 molar1 1000.00000000: m-3 degK 1.00000000: K1 - FARADAY1 96485.3321233100141: coul1 - FARADAY2 96.4853321233100161: coul1 - FARADAY3 9.64853321233100125: coul1 - PI 3.14159265358979312: constant - R1 8.3144626181532395: m2 kg1 sec-2 K-1 + FARADAY1 0x1.78e555060882cp+16: coul1 + FARADAY2 0x1.81f0fae775425p+6: coul1 + FARADAY3 0x1.34c0c8b92a9b7p+3: coul1 + PI 0x1.921fb54442d18p+1: constant + R1 0x1.0a1013e8990bep+3: m2 kg1 sec-2 K-1 R2 8.314: m2 kg1 sec-2 K-1 - R3 8314.46261815323851: m2 kg1 sec-2 K-1 + R3 0x1.03d3b37125759p+13: m2 kg1 sec-2 K-1 R4 8.314: m2 kg1 sec-2 K-1 R5 8.314500000000001: m2 kg1 sec-2 K-1 dummy1 123.45: m1 sec-2 From b32fbf080636a52183419839361fd1c209b7673d Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 13 Apr 2023 15:58:34 +0200 Subject: [PATCH 502/871] Add a test for covering inlining of VERBATIM block (BlueBrain/nmodl#1025) NMODL Repo SHA: BlueBrain/nmodl@0514fbe18a587ad0404f34c05bb74359498caa12 --- src/nmodl/parser/unit_driver.cpp | 29 --------- src/nmodl/parser/unit_driver.hpp | 9 --- test/nmodl/transpiler/unit/visitor/inline.cpp | 60 ++++++++++++++++++- 3 files changed, 59 insertions(+), 39 deletions(-) diff --git a/src/nmodl/parser/unit_driver.cpp b/src/nmodl/parser/unit_driver.cpp index 768b104d40..23d6a15340 100644 --- a/src/nmodl/parser/unit_driver.cpp +++ b/src/nmodl/parser/unit_driver.cpp @@ -14,10 +14,6 @@ namespace nmodl { namespace parser { -UnitDriver::UnitDriver(bool strace, bool ptrace) - : trace_scanner(strace) - , trace_parser(ptrace) {} - /// parse Units file provided as istream bool UnitDriver::parse_stream(std::istream& in) { UnitLexer scanner(*this, &in); @@ -26,8 +22,6 @@ bool UnitDriver::parse_stream(std::istream& in) { this->lexer = &scanner; this->parser = &parser; - scanner.set_debug(trace_scanner); - parser.set_debug_level(trace_parser); return (parser.parse() == 0); } @@ -48,28 +42,5 @@ bool UnitDriver::parse_string(const std::string& input) { return parse_stream(iss); } -void UnitDriver::error(const std::string& m) { - std::cerr << m << std::endl; -} - -void UnitDriver::error(const std::string& m, const location& l) { - std::cerr << l << " : " << m << std::endl; -} - -void UnitDriver::scan_string(std::string& text) { - std::istringstream in(text); - UnitLexer scanner(*this, &in); - UnitParser parser(scanner, *this); - this->lexer = &scanner; - this->parser = &parser; - while (true) { - auto sym = lexer->next_token(); - auto token_type = sym.type_get(); - if (token_type == UnitParser::by_type(UnitParser::token::END).type_get()) { - break; - } - } -} - } // namespace parser } // namespace nmodl diff --git a/src/nmodl/parser/unit_driver.hpp b/src/nmodl/parser/unit_driver.hpp index feb3bba6ad..4e9c265993 100644 --- a/src/nmodl/parser/unit_driver.hpp +++ b/src/nmodl/parser/unit_driver.hpp @@ -38,12 +38,6 @@ class location; */ class UnitDriver { private: - /// enable debug output in the flex scanner - bool trace_scanner = false; - - /// enable debug output in the bison parser - bool trace_parser = false; - /// pointer to the lexer instance being used UnitLexer* lexer = nullptr; @@ -68,12 +62,9 @@ class UnitDriver { /// \} - static void error(const std::string& m); bool parse_stream(std::istream& in); bool parse_string(const std::string& input); bool parse_file(const std::string& filename); - void scan_string(std::string& text); - static void error(const std::string& m, const location& l); void set_verbose(bool b) { verbose = b; diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 2eb82fd930..6905c3389c 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -21,6 +21,7 @@ using namespace visitor; using namespace test; using namespace test_utils; +using Catch::Matchers::Equals; using nmodl::parser::NmodlDriver; //============================================================================= @@ -55,7 +56,7 @@ SCENARIO("Inlining of external procedure calls", "[visitor][inline]") { } )"; - THEN("nothing gets inlinine") { + THEN("nothing gets inlined") { std::string input = reindent_text(nmodl_text); auto result = run_inline_visitor(input); REQUIRE(result == input); @@ -646,3 +647,60 @@ SCENARIO("Inlining pass handles local-global name conflict", "[visitor][inline]" } } } + +SCENARIO("Trying to inline a function with VERBATIM block") { + GIVEN("A VERBATIM block without a return inside") { + std::string input_nmodl = R"( + PROCEDURE verb_1() { + VERBATIM + pow(1,2); + ENDVERBATIM + } + + PROCEDURE verb_2() { + verb_1() + } + )"; + + std::string output_nmodl = R"( + PROCEDURE verb_1() { + VERBATIM + pow(1,2); + ENDVERBATIM + } + + PROCEDURE verb_2() { + { + VERBATIM + pow(1,2); + ENDVERBATIM + } + } + )"; + THEN("It gets inlined") { + std::string input = reindent_text(input_nmodl); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_visitor(input); + REQUIRE(expected_result == result); + } + } + GIVEN("A VERBATIM block with a return value") { + std::string nmodl_text = R"( + PROCEDURE verb_1() { + VERBATIM + return pow(1,2); + ENDVERBATIM + } + + PROCEDURE verb_2() { + verb_1() + } + )"; + + THEN("It is not inlined") { + std::string input = reindent_text(nmodl_text); + auto result = run_inline_visitor(input); + REQUIRE(result == input); + } + } +} From 3eb36807affc2fe7baa4beba5d89c5a819db3eb6 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 18 Apr 2023 17:36:27 +0200 Subject: [PATCH 503/871] Use c++17 filesystem (BlueBrain/nmodl#1027) * Use {:g} for printing the units for the tests of the unit parser Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@3b148fc1009012520f6732f4f6b48744efeda23e --- src/nmodl/main.cpp | 12 +-- src/nmodl/parser/nmodl_driver.cpp | 42 +++++----- src/nmodl/parser/nmodl_driver.hpp | 6 +- src/nmodl/units/units.cpp | 7 +- src/nmodl/utils/common_utils.cpp | 77 ------------------- src/nmodl/utils/common_utils.hpp | 48 ------------ src/nmodl/utils/file_library.cpp | 31 ++++---- src/nmodl/utils/file_library.hpp | 8 +- src/nmodl/visitors/main.cpp | 8 +- test/nmodl/transpiler/unit/CMakeLists.txt | 1 + test/nmodl/transpiler/unit/parser/parser.cpp | 9 +-- test/nmodl/transpiler/unit/units/parser.cpp | 28 +++---- .../transpiler/unit/utils/test_utils.cpp | 21 +++++ .../transpiler/unit/utils/test_utils.hpp | 14 ++++ test/nmodl/transpiler/unit/visitor/nmodl.cpp | 2 +- 15 files changed, 113 insertions(+), 201 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 61ffefaf34..1dbcc857f2 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -9,6 +9,7 @@ #include #include +#include #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" @@ -51,6 +52,7 @@ * \brief Main NMODL code generation program */ +namespace fs = std::filesystem; using namespace nmodl; using namespace codegen; using namespace visitor; @@ -62,7 +64,7 @@ int main(int argc, const char* argv[]) { Version::to_string())}; /// list of mod files to process - std::vector mod_files; + std::vector mod_files; /// true if debug logger statements should be shown std::string verbose("info"); @@ -261,8 +263,8 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); - utils::make_path(output_dir); - utils::make_path(scratch_dir); + fs::create_directories(output_dir); + fs::create_directories(scratch_dir); if (sympy_opt) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() @@ -281,9 +283,9 @@ int main(int argc, const char* argv[]) { }; for (const auto& file: mod_files) { - logger->info("Processing {}", file); + logger->info("Processing {}", file.string()); - const auto modfile = utils::remove_extension(utils::base_name(file)); + const auto modfile = file.stem().string(); /// create file path for nmodl file auto filepath = [scratch_dir, modfile](const std::string& suffix) { diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 8972471404..37b3979851 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -5,6 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include #include #include @@ -12,6 +13,8 @@ #include "parser/nmodl_driver.hpp" #include "utils/logger.hpp" +namespace fs = std::filesystem; + namespace nmodl { namespace parser { @@ -30,9 +33,9 @@ std::shared_ptr NmodlDriver::parse_stream(std::istream& in) { return astRoot; } -std::shared_ptr NmodlDriver::parse_file(const std::string& filename, +std::shared_ptr NmodlDriver::parse_file(const fs::path& filename, const location* loc) { - std::ifstream in(filename.c_str()); + std::ifstream in(filename); if (!in.good()) { std::ostringstream oss; if (loc == nullptr) { @@ -47,26 +50,25 @@ std::shared_ptr NmodlDriver::parse_file(const std::string& filenam } auto current_stream_name = stream_name; - stream_name = filename; - auto absolute_path = utils::cwd() + utils::pathsep + filename; + stream_name = filename.string(); + auto absolute_path = fs::absolute(filename); { - const auto last_slash = filename.find_last_of(utils::pathsep); - if (utils::file_is_abs(filename)) { - const auto path_prefix = filename.substr(0, last_slash + 1); + if (filename.is_absolute()) { + const auto path_prefix = filename.parent_path(); library.push_current_directory(path_prefix); - absolute_path = filename; - } else if (last_slash == std::string::npos) { - library.push_current_directory(utils::cwd()); + absolute_path = filename.string(); + } else if (!filename.has_parent_path()) { + library.push_current_directory(fs::current_path()); } else { - const auto path_prefix = filename.substr(0, last_slash + 1); - const auto path = utils::cwd() + utils::pathsep + path_prefix; + const auto path_prefix = filename.parent_path(); + const auto path = fs::absolute(path_prefix); library.push_current_directory(path); } } - open_files.emplace(absolute_path, loc); + open_files.emplace(absolute_path.string(), loc); parse_stream(in); - open_files.erase(absolute_path); + open_files.erase(absolute_path.string()); library.pop_current_directory(); stream_name = current_stream_name; @@ -79,24 +81,24 @@ std::shared_ptr NmodlDriver::parse_string(const std::string& input return astRoot; } -std::shared_ptr NmodlDriver::parse_include(const std::string& name, +std::shared_ptr NmodlDriver::parse_include(const fs::path& name, const location& loc) { if (name.empty()) { parse_error(loc, "empty filename"); } // Try to find directory containing the file to import - const auto directory_path = library.find_file(name); + const auto directory_path = fs::path{library.find_file(name)}; // Complete path of file (directory + filename). - std::string absolute_path = name; + auto absolute_path = name; if (!directory_path.empty()) { - absolute_path = directory_path + std::string(1, utils::pathsep) + name; + absolute_path = directory_path / name; } // Detect recursive inclusion. - auto already_included = open_files.find(absolute_path); + auto already_included = open_files.find(absolute_path.string()); if (already_included != open_files.end()) { std::ostringstream oss; oss << name << ": recursive inclusion.\n"; @@ -113,7 +115,7 @@ std::shared_ptr NmodlDriver::parse_include(const std::string& name program.swap(astRoot); auto filename_node = std::shared_ptr( - new ast::String(std::string(1, '"') + name + std::string(1, '"'))); + new ast::String(fmt::format("\"{}\"", name.string()))); return std::shared_ptr(new ast::Include(filename_node, program->get_blocks())); } diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 67ddefe744..6466d750f1 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -12,6 +12,7 @@ * \brief Parser implementations */ +#include #include #include @@ -110,10 +111,11 @@ class NmodlDriver { * \param loc optional location when \a filename is dictated * by an `INCLUDE` NMODL directive. */ - std::shared_ptr parse_file(const std::string& filename, + std::shared_ptr parse_file(const std::filesystem::path& filename, const location* loc = nullptr); //// parse file specified in nmodl include directive - std::shared_ptr parse_include(const std::string& filename, const location& loc); + std::shared_ptr parse_include(const std::filesystem::path& filename, + const location& loc); void set_verbose(bool b) { verbose = b; diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 4d8e56a395..2093e6f3a5 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -25,10 +25,6 @@ * \brief Units processing while being processed from lexer and parser */ -namespace { -constexpr std::size_t output_precision{8}; -} - namespace nmodl { namespace units { @@ -314,10 +310,9 @@ void UnitTable::print_units_sorted(std::ostream& units_details) const { table.end()); std::sort(sorted_elements.begin(), sorted_elements.end()); for (const auto& it: sorted_elements) { - units_details << fmt::format("{} {:.{}f}: {}\n", + units_details << fmt::format("{} {:g}: {}\n", it.first, it.second->get_factor(), - output_precision, fmt::join(it.second->get_dimensions(), " ")); } } diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 62db3c6e80..7601ada626 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -25,83 +25,6 @@ namespace nmodl { namespace utils { -bool is_dir_exist(const std::string& path) { - struct stat info {}; - if (stat(path.c_str(), &info) != 0) { - return false; - } - return (info.st_mode & S_IFDIR) != 0; -} - -bool file_exists(const std::string& path) { - struct stat info {}; - return stat(path.c_str(), &info) == 0; -} - -bool file_is_abs(const std::string& path) { -#ifdef IS_WINDOWS - return path.find(":\\") != std::string::npos; -#else - return path.find(pathsep) == 0; -#endif -} - -std::string cwd() { - std::array cwd{}; - if (nullptr == getcwd(cwd.data(), MAXPATHLEN + 1)) { - throw std::runtime_error("working directory name too long"); - } - return {cwd.data()}; -} -bool make_path(const std::string& path) { - mode_t mode = 0755; // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - int ret = mkdir(path.c_str(), mode); - if (ret == 0) { - return true; - } - - switch (errno) { - case ENOENT: - // parent didn't exist, try to create it - { - auto const pos = path.find_last_of('/'); - if (pos == std::string::npos) { - return false; - } - if (!make_path(path.substr(0, pos))) { - return false; - } - } - // now, try to create again - return 0 == mkdir(path.c_str(), mode); - - case EEXIST: - // done! - return is_dir_exist(path); - - default: - auto msg = "Can not create directory " + path; - throw std::runtime_error(msg); - } -} - -TempFile::TempFile(std::string path) - : path_(std::move(path)) { - std::ofstream output(path_); -} - -TempFile::TempFile(std::string path, const std::string& content) - : path_(std::move(path)) { - std::ofstream output(path_); - output << content; -} - -TempFile::~TempFile() { - if (remove(path_.c_str()) != 0) { - perror("Cannot delete temporary file"); - } -} - std::string generate_random_string(const int len, UseNumbersInString use_numbers) { std::string s(len, 0); constexpr std::size_t number_of_numbers{10}; diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 650cdab0cb..b78f4f8eb2 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -40,62 +40,14 @@ bool is_last(Iter iter, const Cont& cont) { return ((iter != cont.end()) && (next(iter) == cont.end())); } -/// Given full file path, returns only name of the file -template -T base_name(T const& path, T const& delims = "/\\") { - return path.substr(path.find_last_of(delims) + 1); -} - -/// Given the file name, returns name of the file without extension -template -T remove_extension(T const& filename) { - typename T::size_type const p(filename.find_last_of('.')); - return p > 0 && p != T::npos ? filename.substr(0, p) : filename; -} - #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) -/// The character used by the operating system to separate pathname components -static constexpr char pathsep{'\\'}; /// The character conventionally used by the operating system to separate search path components static constexpr char envpathsep{';'}; -/// Maximum size of a directory path -static constexpr int max_path_len{_MAX_DIR}; #else -/// The character used by the operating system to separate pathname components -static constexpr char pathsep{'/'}; /// The character conventionally used by the operating system to separate search path components static constexpr char envpathsep{':'}; -/// Maximum size of a directory path -static constexpr int max_path_len{MAXPATHLEN}; #endif -/// Given directory path, create sub-directories -bool make_path(const std::string& path); - -/// Check if directory with given path exists -bool is_dir_exist(const std::string& path); - -/// Check if specified file path exists -bool file_exists(const std::string& path); - -/// Check if specified file path is absolute -bool file_is_abs(const std::string& path); - -/// get current working directory -std::string cwd(); - -/** - * \brief Create an empty file which is then removed when the C++ object is destructed - */ -struct TempFile { - explicit TempFile(std::string path); - TempFile(std::string path, const std::string& content); - ~TempFile(); - - private: - std::string path_; -}; - /// Enum to wrap bool variable to select if random string /// should have numbers or not enum UseNumbersInString : bool { WithNumbers = true, WithoutNumbers = false }; diff --git a/src/nmodl/utils/file_library.cpp b/src/nmodl/utils/file_library.cpp index f058b85119..480a0ffbf7 100644 --- a/src/nmodl/utils/file_library.cpp +++ b/src/nmodl/utils/file_library.cpp @@ -8,12 +8,15 @@ #include "file_library.hpp" #include +#include #include #include #include "utils/common_utils.hpp" #include "utils/string_utils.hpp" +namespace fs = std::filesystem; + namespace nmodl { @@ -24,44 +27,40 @@ FileLibrary FileLibrary::default_instance() { return library; } -void FileLibrary::append_dir(const std::string& path) { - paths_.insert(paths_.begin(), path); -} - void FileLibrary::append_env_var(const std::string& env_var) { const auto value = getenv(env_var.c_str()); if (value != nullptr) { for (const auto& path: stringutils::split_string(value, utils::envpathsep)) { if (!path.empty()) { - append_dir(path); + paths_.insert(paths_.begin(), path); } } } } -void FileLibrary::push_current_directory(const std::string& path) { +void FileLibrary::push_current_directory(const fs::path& path) { paths_.push_back(path); } void FileLibrary::push_cwd() { - push_current_directory(utils::cwd()); + push_current_directory(fs::current_path()); } void FileLibrary::pop_current_directory() { assert(!paths_.empty()); - paths_.erase(--paths_.end()); + if (!paths_.empty()) { + paths_.pop_back(); + } } -std::string FileLibrary::find_file(const std::string& file) { - if (utils::file_is_abs(file)) { - if (utils::file_exists(file)) { - return ""; - } +std::string FileLibrary::find_file(const fs::path& file) { + if (file.is_absolute() && fs::exists(file)) { + return ""; } for (auto paths_it = paths_.rbegin(); paths_it != paths_.rend(); ++paths_it) { - auto file_abs = *paths_it + utils::pathsep + file; - if (utils::file_exists(file_abs)) { - return *paths_it; + auto file_abs = *paths_it / file; + if (fs::exists(file_abs)) { + return paths_it->string(); } } return ""; diff --git a/src/nmodl/utils/file_library.hpp b/src/nmodl/utils/file_library.hpp index 17fbeb3fa1..e587799647 100644 --- a/src/nmodl/utils/file_library.hpp +++ b/src/nmodl/utils/file_library.hpp @@ -7,6 +7,7 @@ #pragma once +#include #include #include @@ -41,7 +42,6 @@ class FileLibrary { * \name Managing inclusion paths. * \{ */ - void append_dir(const std::string& path); void append_env_var(const std::string& env_var); /** \} */ @@ -49,7 +49,7 @@ class FileLibrary { * \name current directory * \{ */ - void push_current_directory(const std::string& path); + void push_current_directory(const std::filesystem::path& path); void pop_current_directory(); /** \} */ @@ -58,14 +58,14 @@ class FileLibrary { * Determine real path of \a file * \return Directory containing \a file, or "" if not found. */ - std::string find_file(const std::string& file); + std::string find_file(const std::filesystem::path& file); private: /// push the working directory in the directories stack void push_cwd(); /// inclusion path list - std::vector paths_; + std::vector paths_; }; } // namespace nmodl diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 8ca9795b68..945f3883f5 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include +#include #include "ast/program.hpp" #include "config/config.h" @@ -33,6 +34,7 @@ using namespace nmodl; using namespace visitor; +namespace fs = std::filesystem; /** * \file @@ -64,7 +66,7 @@ int main(int argc, const char* argv[]) { fmt::format("NMODL Visitor : Runs standalone visitor classes({})", Version::to_string())}; bool verbose = false; - std::vector files; + std::vector files; app.add_flag("-v,--verbose", verbose, "Enable debug log level"); app.add_option("-f,--file,file", files, "One or more MOD files to process") @@ -107,9 +109,9 @@ int main(int argc, const char* argv[]) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); for (const auto& filename: files) { - logger->info("Processing {}", filename); + logger->info("Processing {}", filename.string()); - const std::string mod_file(utils::remove_extension(utils::base_name(filename))); + const std::string mod_file = filename.stem().string(); /// driver object that creates lexer and parser parser::NmodlDriver driver; diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index ba3558e452..c3ea1f8ebc 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -17,6 +17,7 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # Common input data library # ============================================================================= add_library(test_util STATIC utils/nmodl_constructs.cpp utils/test_utils.cpp) +target_link_libraries(test_util PUBLIC spdlog::spdlog_header_only) # ============================================================================= # Common input data library diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 0ab51a2bf7..42e9a7b511 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -134,7 +134,7 @@ SCENARIO("NMODL parser accepts empty unit specification") { } SCENARIO("NMODL parser running number of valid NMODL constructs") { - nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); + TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { @@ -167,12 +167,11 @@ SCENARIO("Check that the parser doesn't crash when passing invalid INCLUDE const GIVEN("An missing included file") { REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"unknown.file\""), - Catch::Contains("can not open file : unknown.file")); + Catch::Contains("can not open file : \"unknown.file\"")); } GIVEN("An invalid included file") { - nmodl::utils::TempFile included("included.file", - nmodl_invalid_constructs.at("title_1").input); + TempFile included("included.file", nmodl_invalid_constructs.at("title_1").input); REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"included.file\""), Catch::Contains("unexpected End of file")); } @@ -199,7 +198,7 @@ SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { SCENARIO("Check parents in valid NMODL constructs") { nmodl::parser::NmodlDriver driver; - nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); + TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { // parse the string and get the ast const auto ast = driver.parse_string(construct.second.input); diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index 2133d147a7..b3d9050af7 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -154,7 +154,7 @@ SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { THEN("parser multiply the number by the factor") { std::string parsed_unit{}; REQUIRE_NOTHROW(parsed_unit = parse_string("pew 1 1/milli")); - REQUIRE_THAT(parsed_unit, Contains("pew 0.00100000: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_unit, Contains("pew 0.001: 0 0 0 0 0 0 0 0 0 0")); } } } @@ -192,19 +192,19 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars R2 8314 mV-coul/degC )"; std::string parsed_units = parse_string(reindent_text(units_definitions)); - REQUIRE_THAT(parsed_units, Contains("mV 0.00100000: 2 1 -2 -1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("mM 1.00000000: -3 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("mA 0.00100000: 0 0 -1 1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("KTOMV 0.00008530: 2 1 -2 -1 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("B 26.00000000: -1 0 0 -1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy1 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy2 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy3 0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy4 -0.02500000: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy5 0.02500000: 0 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("R 8.31446262: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("R1 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("R2 8.31400000: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("mV 0.001: 2 1 -2 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("mM 1: -3 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("mA 0.001: 0 0 -1 1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("KTOMV 8.53e-05: 2 1 -2 -1 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("B 26: -1 0 0 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy1 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy2 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy3 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy4 -0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("dummy5 0.025: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, Contains("R 8.31446: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("R1 8.314: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("R2 8.314: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE_THAT(parsed_units, Contains("m kg sec coul candela dollar bit erlang K")); } } diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index 7f6e81841b..82e2a364b7 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -5,9 +5,15 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ +#include "test_utils.hpp" +#include "utils/logger.hpp" #include "utils/string_utils.hpp" #include +#include +#include + +namespace fs = std::filesystem; namespace nmodl { namespace test_utils { @@ -80,5 +86,20 @@ std::string reindent_text(const std::string& text) { return indented_text; } +TempFile::TempFile(fs::path path, const std::string& content) + : path_(std::move(path)) { + std::ofstream output(path_); + output << content; +} + +TempFile::~TempFile() { + try { + fs::remove(path_); + } catch (...) { + // TODO: remove .string() once spdlog use fmt 9.1.0 + logger->error("Cannot delete temporary file {}", path_.string()); + } +} + } // namespace test_utils } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/utils/test_utils.hpp b/test/nmodl/transpiler/unit/utils/test_utils.hpp index 75f3828b43..d2e0447b9e 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.hpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.hpp @@ -7,10 +7,24 @@ #pragma once +#include +#include + namespace nmodl { namespace test_utils { std::string reindent_text(const std::string& text); +/** + * \brief Create an empty file which is then removed when the C++ object is destructed + */ +struct TempFile { + TempFile(std::filesystem::path path, const std::string& content); + ~TempFile(); + + private: + std::filesystem::path path_; +}; + } // namespace test_utils } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index b2879f3d7c..263fae1f83 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -41,7 +41,7 @@ std::string run_nmodl_visitor(const std::string& text) { } SCENARIO("Convert AST back to NMODL form", "[visitor][nmodl]") { - nmodl::utils::TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); + TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; const std::string& input_nmodl_text = reindent_text(test_case.input); From ca2ac16fc95ebbfd8187486edee6c63f471b90c9 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 19 Apr 2023 10:16:53 +0300 Subject: [PATCH 504/871] Stop registering FactorDefs to the UnitTable (BlueBrain/nmodl#1030) * Stop parsing of FactorDefs by the UnitParser that registered them in the UnitTable to follow the same policy as NEURON * Throw error when trying to register unit that already exists in the UnitTable * Added tests for UNITS redefinition and correct registration of UnitDefs and FactorDefs * Use :g for printing factors of units in unit tests NMODL Repo SHA: BlueBrain/nmodl@5f864dc705fb241d5391d07e8ef7c5ec8dd0fcfb --- src/nmodl/parser/unit.yy | 7 +- src/nmodl/units/units.cpp | 33 ++- src/nmodl/visitors/units_visitor.cpp | 21 +- test/nmodl/transpiler/unit/units/parser.cpp | 4 +- test/nmodl/transpiler/unit/visitor/units.cpp | 229 +++++++++++-------- 5 files changed, 163 insertions(+), 131 deletions(-) diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index 49f7ebe4ab..34febc1bdf 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -104,7 +104,12 @@ table_insertion $$ = driver.table; } | table_insertion item { - $1->insert($2); + try { + $1->insert($2); + } + catch (std::runtime_error e) { + error(scanner.loc, e.what()); + } $$ = $1; } | table_insertion prefix { diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 2093e6f3a5..50cd2fe267 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -261,6 +261,20 @@ void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, } void UnitTable::insert(const std::shared_ptr& unit) { + // if the unit is already in the table throw error because + // redefinition of a unit is not allowed + if (table.find(unit->get_name()) != table.end()) { + std::stringstream ss_unit_string; + ss_unit_string << fmt::format("{:g} {}", + unit->get_factor(), + fmt::join(unit->get_nominator_unit(), "")); + if (!unit->get_denominator_unit().empty()) { + ss_unit_string << fmt::format("/{}", fmt::join(unit->get_denominator_unit(), "")); + } + throw std::runtime_error(fmt::format("Redefinition of units ({}) to {} is not allowed.", + unit->get_name(), + ss_unit_string.str())); + } // check if the unit is a base unit and // then add it to the base units vector auto unit_nominator = unit->get_nominator_unit(); @@ -273,14 +287,7 @@ void UnitTable::insert(const std::shared_ptr& unit) { assert(index >= 0 && index < base_units_names.size()); // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) base_units_names[index] = unit->get_name(); - // if unit is found in table replace it - auto find_unit_name = table.find(unit->get_name()); - if (find_unit_name == table.end()) { - table.insert({unit->get_name(), unit}); - } else { - table.erase(unit->get_name()); - table.insert({unit->get_name(), unit}); - } + table.insert({unit->get_name(), unit}); return; } // calculate unit's dimensions based on its nominator and denominator @@ -290,15 +297,7 @@ void UnitTable::insert(const std::shared_ptr& unit) { for (const auto& it: unit->get_denominator_unit()) { calc_denominator_dims(unit, it); } - // if unit is not in the table simply insert it, else replace with it with - // new definition - auto find_unit_name = table.find(unit->get_name()); - if (find_unit_name == table.end()) { - table.insert({unit->get_name(), unit}); - } else { - table.erase(unit->get_name()); - table.insert({unit->get_name(), unit}); - } + table.insert({unit->get_name(), unit}); } void UnitTable::insert_prefix(const std::shared_ptr& prfx) { diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index cad73fd4ee..01af6a0bfd 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -81,23 +81,8 @@ void UnitsVisitor::visit_unit_def(ast::UnitDef& node) { * care of all the units calculations. */ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { - std::ostringstream ss; const auto node_has_value_defined_in_modfile = node.get_value() != nullptr; - if (node_has_value_defined_in_modfile) { - /* - * In nrnunits.lib file "1" is defined as "fuzz", so - * there must be a conversion to be able to parse "1" as unit - */ - if (node.get_unit1()->get_node_name() == "1") { - ss << node.get_node_name() << "\t" << node.get_value()->eval() << " "; - ss << UNIT_FUZZ; - } else { - ss << node.get_node_name() << "\t" << node.get_value()->eval() << " "; - ss << node.get_unit1()->get_node_name(); - } - // Parse the generated string for the defined unit using the units::UnitParser - units_driver.parse_string(ss.str()); - } else { + if (!node_has_value_defined_in_modfile) { std::ostringstream ss_unit1, ss_unit2; std::string unit1_name, unit2_name; /* @@ -127,10 +112,6 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { ss_unit2 << node.get_node_name() << "_unit2\t" << unit2_name; units_driver.parse_string(ss_unit2.str()); - // Parse the generated string for the defined unit using the units::UnitParser - ss << node.get_node_name() << "\t" << unit1_name; - units_driver.parse_string(ss.str()); - /** * \note If the ast::FactorDef was made by using two units (second case), * the factors of both of them must be calculated based on the diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index b3d9050af7..183691ecca 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -187,7 +187,7 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars dummy3 25-3 / m2 dummy4 -0.025 /m2 dummy5 2.5 % - R k-mole + newR k-mole R1 8.314 volt-coul/degC R2 8314 mV-coul/degC )"; @@ -202,7 +202,7 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars REQUIRE_THAT(parsed_units, Contains("dummy3 0.025: -2 0 0 0 0 0 0 0 0 0")); REQUIRE_THAT(parsed_units, Contains("dummy4 -0.025: -2 0 0 0 0 0 0 0 0 0")); REQUIRE_THAT(parsed_units, Contains("dummy5 0.025: 0 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("R 8.31446: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, Contains("newR 8.31446: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE_THAT(parsed_units, Contains("R1 8.314: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE_THAT(parsed_units, Contains("R2 8.314: 2 1 -2 0 0 0 0 0 0 -1")); REQUIRE_THAT(parsed_units, Contains("m kg sec coul candela dollar bit erlang K")); diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 95266d4973..8543fc2823 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -6,6 +6,7 @@ *************************************************************************/ #include +#include #include "ast/double.hpp" #include "ast/factor_def.hpp" @@ -24,15 +25,12 @@ using namespace test_utils; using nmodl::parser::NmodlDriver; -namespace { -constexpr std::size_t output_precision{8}; -} - //============================================================================= // Unit visitor tests //============================================================================= -std::string run_units_visitor(const std::string& text) { +std::tuple, std::shared_ptr> run_units_visitor( + const std::string& text) { NmodlDriver driver; driver.parse_string(text); const auto& ast = driver.get_ast(); @@ -48,19 +46,40 @@ std::string run_units_visitor(const std::string& text) { parser::UnitDriver units_driver = units_visitor.get_unit_driver(); std::shared_ptr unit_table = units_driver.table; - std::stringstream ss; + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().check_ast(*ast); + + return {ast, unit_table}; +} + - // Visit AST to find all the ast::UnitDef nodes to print their - // unit names, factors and dimensions as they are calculated in - // the units::UnitTable - const auto& unit_defs = collect_nodes(*ast, {ast::AstNodeType::UNIT_DEF}); +/** + * @brief Returns all the \c UnitDef s of the \c ast::Program + * + * Visit AST to find all the ast::UnitDef nodes to print their + * unit names, factors and dimensions as they are calculated in + * the units::UnitTable + * The \c UnitDef s are printed in the format: + * \code + * : + * \endcode + * + * If the unit is constant then instead of the dimensions we print \c constant + * + * @arg ast \c ast::Program to look for \c UnitDef s + * @arg unit_table \c units::UnitTable to look for the definitions of the units + * + * @return std::string Unit definitions + */ +std::string get_unit_definitions(const ast::Program& ast, const units::UnitTable& unit_table) { + std::stringstream ss; + const auto& unit_defs = collect_nodes(ast, {ast::AstNodeType::UNIT_DEF}); for (const auto& unit_def: unit_defs) { auto unit_name = unit_def->get_node_name(); unit_name.erase(remove_if(unit_name.begin(), unit_name.end(), isspace), unit_name.end()); - auto unit = units_driver.table->get_unit(unit_name); - ss << std::fixed << std::setprecision(output_precision) << unit->get_name() << ' ' - << unit->get_factor() << ':'; + auto unit = unit_table.get_unit(unit_name); + ss << fmt::format("{} {:g}:", unit_name, unit->get_factor()); // Dimensions of the unit are printed to check that the units are successfully // parsed to the units::UnitTable int dimension_id = 0; @@ -68,7 +87,7 @@ std::string run_units_visitor(const std::string& text) { for (const auto& dimension: unit->get_dimensions()) { if (dimension != 0) { constant = false; - ss << ' ' << units_driver.table->get_base_unit_name(dimension_id) << dimension; + ss << ' ' << unit_table.get_base_unit_name(dimension_id) << dimension; } dimension_id++; } @@ -77,43 +96,38 @@ std::string run_units_visitor(const std::string& text) { } ss << '\n'; } + return ss.str(); +} - // Visit AST to find all the ast::FactorDef nodes to print their - // unit names, factors and dimensions as they are calculated to - // be printed to the produced .cpp file - const auto& factor_defs = collect_nodes(*ast, {ast::AstNodeType::FACTOR_DEF}); +/** + * @brief Returns all the \c FactorDef s of the \c ast::Program + * + * Visit AST to find all the ast::FactorDef nodes to print their + * unit names and factors as they are calculated to be printed + * to the generated .cpp file + * The \c FactorDef s are printed in the format: + * \code + * + * \endcode + * + * @arg ast \c ast::Program to look for \c FactorDef s + * + * @return std::string Factor definitions + */ +std::string get_factor_definitions(const ast::Program& ast) { + std::stringstream ss; + const auto& factor_defs = collect_nodes(ast, {ast::AstNodeType::FACTOR_DEF}); for (const auto& factor_def: factor_defs) { - auto unit = units_driver.table->get_unit(factor_def->get_node_name()); - ss << std::fixed << std::setprecision(output_precision) << unit->get_name() << ' '; - auto factor_def_class = std::dynamic_pointer_cast(factor_def); - ss << factor_def_class->get_value()->eval() << ':'; - // Dimensions of the unit are printed to check that the units are successfully - // parsed to the units::UnitTable - int dimension_id = 0; - auto constant = true; - for (const auto& dimension: unit->get_dimensions()) { - if (dimension != 0) { - constant = false; - ss << ' ' << units_driver.table->get_base_unit_name(dimension_id); - ss << dimension; - } - dimension_id++; - } - if (constant) { - ss << " constant"; - } - ss << '\n'; + const auto& factor_def_cast = std::dynamic_pointer_cast(factor_def); + ss << fmt::format("{} {}\n", + factor_def_cast->get_node_name(), + factor_def_cast->get_value()->get_value()); } - - // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().check_ast(*ast); - return ss.str(); } - SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units]") { - GIVEN("UNITS block with different cases of units definitions") { + GIVEN("UNITS block with different cases of UNITS definitions") { static const std::string nmodl_text = R"( UNITS { (nA) = (nanoamp) @@ -128,7 +142,7 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] (uM) = (micro/liter) (msM) = (ms mM) (fAm) = (femto amp meter) - (mol) = (1) + (newmol) = (1) (M) = (1/liter) (uM1) = (micro M) (mA/cm2) = (nanoamp/cm2) @@ -137,11 +151,11 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] (mse-1) = (1/millisec) (um3) = (liter/1e15) (molar1) = (/liter) - (degK) = (degC) + (newdegK) = (degC) FARADAY1 = (faraday) (coulomb) FARADAY2 = (faraday) (kilocoulombs) FARADAY3 = (faraday) (10000 coulomb) - PI = (pi) (1) + pi = (pi) (1) R1 = (k-mole) (joule/degC) R2 = 8.314 (volt-coul/degC) R3 = (mole k) (mV-coulomb/degC) @@ -155,57 +169,90 @@ SCENARIO("Parse UNITS block of mod files using Units Visitor", "[visitor][units] TEMP = 25 (degC) toyfuzz = (1) (volt) numbertwo = 2 (1) + oldJ = (R-mole) (1) : compute oldJ based on the value of R which is registered in the UnitTable + R = 8 (joule/degC) : define a new value for R that should be visible only in the mod file + J = (R-mole) (1) : recalculate J. It's value should be the same as oldJ because R shouldn't change in the UnitTable + (myR) = (8 joule/degC) : Define my own R and mole and compute myRnew and myJ based on them + (mymole) = (6+23) + myRnew = (myR) (1) + myJ = (myR-mymole) (1) } )"; - static const std::string output_nmodl = R"( - nA 0.00000000: sec-1 coul1 - mA 0.00100000: sec-1 coul1 - mV 0.00100000: m2 kg1 sec-2 coul-1 - uS 0.00000100: m-2 kg-1 sec1 coul2 - nS 0.00000000: m-2 kg-1 sec1 coul2 - pS 0.00000000: m-2 kg-1 sec1 coul2 - umho 0.00000100: m-2 kg-1 sec1 coul2 - um 0.00000100: m1 - mM 1.00000000: m-3 - uM 0.00100000: m-3 - msM 0.00100000: m-3 sec1 - fAm 0.00000000: m1 sec-1 coul1 - mol 1.00000000: constant - M 1000.00000000: m-3 - uM1 0.00100000: m-3 - mA/cm2 0.00001000: m-2 sec-1 coul1 - molar 1000.00000000: m-3 - S 1.00000000: m-2 kg-1 sec1 coul2 - mse-1 1000.00000000: sec-1 - um3 0.00100000: m3 - molar1 1000.00000000: m-3 - degK 1.00000000: K1 - FARADAY1 0x1.78e555060882cp+16: coul1 - FARADAY2 0x1.81f0fae775425p+6: coul1 - FARADAY3 0x1.34c0c8b92a9b7p+3: coul1 - PI 0x1.921fb54442d18p+1: constant - R1 0x1.0a1013e8990bep+3: m2 kg1 sec-2 K-1 - R2 8.314: m2 kg1 sec-2 K-1 - R3 0x1.03d3b37125759p+13: m2 kg1 sec-2 K-1 - R4 8.314: m2 kg1 sec-2 K-1 - R5 8.314500000000001: m2 kg1 sec-2 K-1 - dummy1 123.45: m1 sec-2 - dummy2 123.45e3: m1 sec-2 - dummy3 12345e-2: m1 sec-2 - KTOMV 0.0853: m2 kg1 sec-2 coul-1 K-1 - B 0.26: m-1 coul-1 - TEMP 25: K1 - toyfuzz 1: constant - numbertwo 2: constant + static const std::string unit_definitions = R"( + nA 1e-09: sec-1 coul1 + mA 0.001: sec-1 coul1 + mV 0.001: m2 kg1 sec-2 coul-1 + uS 1e-06: m-2 kg-1 sec1 coul2 + nS 1e-09: m-2 kg-1 sec1 coul2 + pS 1e-12: m-2 kg-1 sec1 coul2 + umho 1e-06: m-2 kg-1 sec1 coul2 + um 1e-06: m1 + mM 1: m-3 + uM 0.001: m-3 + msM 0.001: m-3 sec1 + fAm 1e-15: m1 sec-1 coul1 + newmol 1: constant + M 1000: m-3 + uM1 0.001: m-3 + mA/cm2 1e-05: m-2 sec-1 coul1 + molar 1000: m-3 + S 1: m-2 kg-1 sec1 coul2 + mse-1 1000: sec-1 + um3 0.001: m3 + molar1 1000: m-3 + newdegK 1: K1 + myR 8: m2 kg1 sec-2 K-1 + mymole 6e+23: constant + )"; + + static const std::string factor_definitions = R"( + FARADAY1 0x1.78e555060882cp+16 + FARADAY2 0x1.81f0fae775425p+6 + FARADAY3 0x1.34c0c8b92a9b7p+3 + pi 0x1.921fb54442d18p+1 + R1 0x1.0a1013e8990bep+3 + R2 8.314 + R3 0x1.03d3b37125759p+13 + R4 8.314 + R5 8.314500000000001 + dummy1 123.45 + dummy2 123.45e3 + dummy3 12345e-2 + KTOMV 0.0853 + B 0.26 + TEMP 25 + toyfuzz 1 + numbertwo 2 + oldJ 0x1.0912acba81b67p+82 + R 8 + J 0x1.0912acba81b67p+82 + myRnew 8 + myJ 0x1.fc3842bd1f072p+81 )"; THEN("Print the units that were added") { const std::string input(reindent_text(nmodl_text)); - auto expected_result = reindent_text(output_nmodl); - auto result = run_units_visitor(input); - auto reindented_result = reindent_text(result); - REQUIRE(reindented_result == expected_result); + auto expected_result_unit_definitions = reindent_text(unit_definitions); + auto expected_result_factor_definitions = reindent_text(factor_definitions); + const auto& [ast, unit_table] = run_units_visitor(input); + const auto& generated_unit_definitions = get_unit_definitions(*ast, *unit_table); + const auto& generated_factor_definitions = get_factor_definitions(*ast); + auto reindented_result_unit_definitions = reindent_text(generated_unit_definitions); + auto reindented_result_factor_definitions = reindent_text(generated_factor_definitions); + REQUIRE(reindented_result_unit_definitions == expected_result_unit_definitions); + REQUIRE(reindented_result_factor_definitions == expected_result_factor_definitions); + } + } + GIVEN("UNITS block with Unit definition which is already defined") { + static const std::string nmodl_text = R"( + UNITS { + (R) = (8 joule/degC) + } + )"; + THEN("Throw redefinition exception") { + const std::string input(reindent_text(nmodl_text)); + REQUIRE_THROWS(run_units_visitor(input)); } } } From ef11d2ee8d493c4f21371c2f2fa4f03bfb1cc392 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 19 Apr 2023 14:55:01 +0200 Subject: [PATCH 505/871] Fix euler method resolution by computing in two times (BlueBrain/nmodl#1031) First time, compute the derivative part. Second time, compute the value. This way if there is inter-depends it will still be correct. BREAKPOINT { SOLVE states METHOD euler } DERIVATIVE states { a' = a/n_tau n' = (n - a)/n_tau } Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@f5bc7832a6dce9eef79dbc9bdd23ce084cd13b51 --- src/nmodl/parser/diffeq_context.cpp | 6 ++-- src/nmodl/parser/diffeq_context.hpp | 4 +-- src/nmodl/visitors/neuron_solve_visitor.cpp | 32 +++++++++++++---- src/nmodl/visitors/neuron_solve_visitor.hpp | 2 ++ .../unit/codegen/codegen_cpp_visitor.cpp | 35 +++++++++++++++++++ .../unit/utils/nmodl_constructs.cpp | 18 +++++----- 6 files changed, 76 insertions(+), 21 deletions(-) diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 7aa4478795..5c3a9f6963 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -139,8 +139,8 @@ std::string DiffEqContext::get_cnexp_solution() const { /** * Return solution for euler method */ -std::string DiffEqContext::get_euler_solution() const { - return state + " = " + state + "+dt*(" + rhs + ")"; +std::string DiffEqContext::get_euler_derivate() const { + return "D" + state + " = " + rhs; } @@ -171,7 +171,7 @@ std::string DiffEqContext::get_solution(bool& cnexp_possible) { std::string solution; if (method == "euler") { cnexp_possible = false; - solution = get_euler_solution(); + solution = get_euler_derivate(); } else if (method == "cnexp" && !(deriv_invalid && eqn_invalid)) { cnexp_possible = true; solution = get_cnexp_solution(); diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index caec0a5ad5..6fc6c13693 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -81,8 +81,8 @@ class DiffEqContext { /// return solution for cnexp method std::string get_cnexp_solution() const; - /// return solution for euler method - std::string get_euler_solution() const; + /// return only the derivate for euler method + std::string get_euler_derivate() const; /// return solution for non-cnexp method std::string get_non_cnexp_solution() const; diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index a687cd9160..80a1c05b2d 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -31,6 +31,12 @@ void NeuronSolveVisitor::visit_derivative_block(ast::DerivativeBlock& node) { derivative_block = true; node.visit_children(*this); derivative_block = false; + if (solve_blocks[derivative_block_name] == codegen::naming::EULER_METHOD) { + auto& statement_block = node.get_statement_block(); + for (auto& e: euler_solution_expressions) { + statement_block->emplace_back_statement(e); + } + } } @@ -70,13 +76,25 @@ void NeuronSolveVisitor::visit_binary_expression(ast::BinaryExpression& node) { to_nmodl(node)); } } else if (solve_method == codegen::naming::EULER_METHOD) { - std::string solution = parser::DiffeqDriver::solve(equation, solve_method); - auto statement = create_statement(solution); - auto expr_statement = std::dynamic_pointer_cast(statement); - const auto bin_expr = std::dynamic_pointer_cast( - expr_statement->get_expression()); - node.set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); - node.set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); + // computation of the derivative in place + { + std::string solution = parser::DiffeqDriver::solve(equation, solve_method); + auto statement = create_statement(solution); + auto expr_statement = std::dynamic_pointer_cast( + statement); + const auto bin_expr = std::dynamic_pointer_cast( + expr_statement->get_expression()); + node.set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); + node.set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); + } + + // create a new statement to compute the value based on the derivative + // this statement will be pushed at the end of the derivative block + { + std::string n = name->get_node_name(); + auto statement = create_statement(fmt::format("{} = {} + dt * D{}", n, n, n)); + euler_solution_expressions.emplace_back(statement); + } } else if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { auto varname = "D" + name->get_node_name(); node.set_lhs(std::make_shared(new ast::String(varname))); diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index 1d8410af0b..6d4bd77b6a 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -59,6 +59,8 @@ class NeuronSolveVisitor: public AstVisitor { /// the derivative name currently being visited std::string derivative_block_name; + std::vector> euler_solution_expressions; + public: NeuronSolveVisitor() = default; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index cfbb4e85e2..5e6d72cda1 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -1083,6 +1083,41 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { } +SCENARIO("Some tests on euler solver", "[codegen][euler_solver]") { + GIVEN("A mod file with euler") { + std::string const nmodl_text = R"( + NEURON { + RANGE inf + } + INITIAL { + inf = 2 + } + STATE { + n + m + } + BREAKPOINT { + SOLVE state METHOD euler + } + DERIVATIVE state { + m' = 2 * m + inf = inf * 3 + n' = (2 + m - inf) * n + } + )"; + THEN("Correct code is generated") { + auto const generated = get_cpp_code(nmodl_text); + std::string nrn_state_expected_code = R"(inst->Dm[id] = 2.0 * inst->m[id]; + inf = inf * 3.0; + inst->Dn[id] = (2.0 + inst->m[id] - inf) * inst->n[id]; + inst->m[id] = inst->m[id] + nt->_dt * inst->Dm[id]; + inst->n[id] = inst->n[id] + nt->_dt * inst->Dn[id];)"; + REQUIRE_THAT(generated, Contains(nrn_state_expected_code)); + } + } +} + + SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { GIVEN("A mod file containing MUTEX & PROTECT") { std::string const nmodl_text = R"( diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 1bad61e079..05b61eb35d 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -1468,7 +1468,7 @@ std::vector const diff_eq_constructs{ { "GluSynapse.mod", "A_AMPA' = A_AMPA*A_AMPA", - "A_AMPA = A_AMPA+dt*(A_AMPA*A_AMPA)", + "DA_AMPA = A_AMPA*A_AMPA", "euler" }, @@ -1525,42 +1525,42 @@ std::vector const diff_eq_constructs{ { "GluSynapse.mod", "A_AMPA' = -A_AMPA/tau_r_AMPA", - "A_AMPA = A_AMPA+dt*(-A_AMPA/tau_r_AMPA)", + "DA_AMPA = -A_AMPA/tau_r_AMPA", "euler" }, { "GluSynapse.mod", "m_VDCC' = (minf_VDCC-m_VDCC)/mtau_VDCC", - "m_VDCC = m_VDCC+dt*((minf_VDCC-m_VDCC)/mtau_VDCC)", + "Dm_VDCC = (minf_VDCC-m_VDCC)/mtau_VDCC", "euler" }, { "GluSynapse.mod", "cai_CR' = -(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR", - "cai_CR = cai_CR+dt*(-(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR)", + "Dcai_CR = -(1e-9)*(ica_NMDA + ica_VDCC)*gamma_ca_CR/((1e-15)*volume_CR*2*FARADAY) - (cai_CR - min_ca_CR)/tau_ca_CR", "euler" }, { "GluSynapse.mod", "effcai_GB' = -0.005*effcai_GB + (cai_CR - min_ca_CR)", - "effcai_GB = effcai_GB+dt*(-0.005*effcai_GB + (cai_CR - min_ca_CR))", + "Deffcai_GB = -0.005*effcai_GB + (cai_CR - min_ca_CR)", "euler" }, { "GluSynapse.mod", "Rho_GB' = ( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB)", - "Rho_GB = Rho_GB+dt*(( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB))", + "DRho_GB = ( - Rho_GB*(1-Rho_GB)*(rho_star_GB-Rho_GB) + potentiate_GB*gamma_p_GB*(1-Rho_GB) - depress_GB*gamma_d_GB*Rho_GB ) / ((1e3)*tau_GB)", "euler" }, { "GluSynapse.mod", "Use_GB' = (Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB)", - "Use_GB = Use_GB+dt*((Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB))", + "DUse_GB = (Use_d_GB + Rho_GB*(Use_p_GB-Use_d_GB) - Use_GB) / ((1e3)*tau_Use_GB)", "euler" }, @@ -1620,14 +1620,14 @@ std::vector const diff_eq_constructs{ { "syn_bip_gan.mod", "s' = (s_inf-s)/((1-s_inf)*tau*s)", - "s = s+dt*((s_inf-s)/((1-s_inf)*tau*s))", + "Ds = (s_inf-s)/((1-s_inf)*tau*s)", "euler" }, { "syn_rod_bip.mod", "s' = (s_inf-s)/((1-s_inf)*tau*s)", - "s = s+dt*((s_inf-s)/((1-s_inf)*tau*s))", + "Ds = (s_inf-s)/((1-s_inf)*tau*s)", "euler" }, From 93059c6a10e8aaefe034eb83873d6be706d5b58b Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 21 Apr 2023 12:18:53 +0200 Subject: [PATCH 506/871] Print prototypes before functors (BlueBrain/nmodl#1034) NMODL Repo SHA: BlueBrain/nmodl@f729744ef92891a0417b8298e8a6978f20017f90 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index f51522c641..1cc56e1314 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -3562,10 +3562,12 @@ void CodegenCVisitor::print_nrn_destructor() { void CodegenCVisitor::print_functors_definitions() { + codegen = true; for (const auto& functor_name: info.functor_names) { printer->add_newline(2); print_functor_definition(*functor_name.first); } + codegen = false; } @@ -4585,7 +4587,6 @@ void CodegenCVisitor::print_g_unused() const { void CodegenCVisitor::print_compute_functions() { print_top_verbatim_blocks(); - print_function_prototypes(); for (const auto& procedure: info.procedures) { print_procedure(*procedure); } @@ -4633,6 +4634,7 @@ void CodegenCVisitor::print_codegen_routines() { print_nrn_alloc(); print_nrn_constructor(); print_nrn_destructor(); + print_function_prototypes(); print_functors_definitions(); print_compute_functions(); print_check_table_thread_function(); From e5acb080afbd405b3a620ee7efd64646aba7580f Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 1 May 2023 14:29:08 +0200 Subject: [PATCH 507/871] Remove Travis from build_wheels (BlueBrain/nmodl#1036) NMODL Repo SHA: BlueBrain/nmodl@309dc619e6576e124678b04b774e8488600d009f --- setup.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/setup.py b/setup.py index 91e98938d0..e0a6fa59e5 100644 --- a/setup.py +++ b/setup.py @@ -75,29 +75,6 @@ def run(self, *args, **kwargs): self.run_command("buildhtml") -def _config_exe(exe_name): - """Sets the environment to run the real executable (returned)""" - - package_name = "nmodl" - - assert ( - package_name in working_set.by_key - ), "NMODL package not found! Verify PYTHONPATH" - NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, "nmodl") - NMODL_PREFIX_DATA = os.path.join(NMODL_PREFIX, ".data") - if sys.platform == "darwin": - os.environ["NMODL_WRAPLIB"] = os.path.join( - NMODL_PREFIX_DATA, "libpywrapper.dylib" - ) - else: - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_PREFIX_DATA, "libpywrapper.so") - - # find libpython*.so in the system - os.environ["NMODL_PYLIB"] = find_libpython() - - return os.path.join(NMODL_PREFIX_DATA, exe_name) - - install_requirements = [ "PyYAML>=3.13", "sympy>=1.3", From 06866ba279666088f19ef28028166ee10622e0ab Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 1 May 2023 15:55:46 +0200 Subject: [PATCH 508/871] Minimum version of python is now 3.8 (BlueBrain/nmodl#1038) NMODL Repo SHA: BlueBrain/nmodl@c4b218e4fba171bf43ab5e895d509d2901131189 --- INSTALL.md | 2 +- cmake/nmodl/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 1d83a37895..838b830f99 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -22,7 +22,7 @@ To build the project from source, a modern C++ compiler with C++14 support is ne - flex (>=2.6) - bison (>=3.0) - CMake (>=3.15) -- Python (>=3.7) +- Python (>=3.8) - Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap ### On OS X diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index e9c5942c33..2ac8a17a8c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -166,7 +166,7 @@ endif() # Find required python packages # ============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp 3.7 REQUIRED) +find_package(PythonInterp 3.8 REQUIRED) include(cmake/hpc-coding-conventions/cpp/cmake/bbp-find-python-module.cmake) cpp_cc_find_python_module(jinja2 2.9.3 REQUIRED) cpp_cc_find_python_module(pytest 3.3.0 REQUIRED) From 5c828a1cae445c08b521d984afa91beef689ec8b Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 2 May 2023 12:37:17 +0200 Subject: [PATCH 509/871] Replace travis CI build status from README with github workflow (BlueBrain/nmodl#1039) NMODL Repo SHA: BlueBrain/nmodl@db74d356de00ffb0e26719cd6a88686ee1e64962 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2480de137d..c8c03ec144 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ ## The NMODL Framework -[![Build Status](https://travis-ci.org/BlueBrain/nmodl.svg?branch=master)](https://travis-ci.org/BlueBrain/nmodl) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB)](https://codecov.io/gh/BlueBrain/nmodl) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4467/badge)](https://bestpractices.coreinfrastructure.org/projects/4467) +![github workflow](https://github.com/BlueBrain/nmodl/actions/workflows/nmodl-ci.yml/badge.svg?branch=master) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB)](https://codecov.io/gh/BlueBrain/nmodl) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4467/badge)](https://bestpractices.coreinfrastructure.org/projects/4467) The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L**anguage ([NMODL](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html)). It is designed with modern compiler and code generation techniques to: From ea4af0e63444663b45aadd17e90ff7ab4dd66518 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Tue, 2 May 2023 17:22:20 +0200 Subject: [PATCH 510/871] CI / submodule improvements (BlueBrain/nmodl#1040) * coding-conventions: update to latest master * Set {NEURON,LIBSONATA_REPORT}_BRANCH=master by default NMODL Repo SHA: BlueBrain/nmodl@631793d7becc90d16d1887bdb4c847eb65273bd7 --- cmake/nmodl/hpc-coding-conventions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index be06dee210..f8f8d69a66 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit be06dee210c01a86b1510a6618d0df0a1c1b8bc1 +Subproject commit f8f8d69a66c23978d1c9c5dce62de79466f26e5d From fdaa9d1807df2875020712e53a2ab63421e07bfa Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 3 May 2023 20:33:03 +0200 Subject: [PATCH 511/871] Use NMODLHOME for NMODL_WRAPLIB (BlueBrain/nmodl#1041) NMODL Repo SHA: BlueBrain/nmodl@f1c32db6e3a3a5b4cd5c8ae6e61e295195f40d6e --- INSTALL.md | 4 ---- pywheel/shim/_binwrapper.py | 7 ------- src/nmodl/config/config.cpp.in | 2 ++ src/nmodl/config/config.h | 4 ++++ src/nmodl/pybind/CMakeLists.txt | 3 ++- src/nmodl/pybind/pyembed.cpp | 18 ++++++++++++------ test/nmodl/transpiler/unit/CMakeLists.txt | 2 -- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 838b830f99..b15bbe35da 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -124,10 +124,6 @@ for example: ````sh export NMODL_PYLIB=/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/Python ```` -* 'NMODL_WRAPLIB': This variable should point to the `libpywrapper.so` built as part of NMODL, for example: -```sh -export NMODL_WRAPLIB=/opt/nmodl/lib/libpywrapper.so -``` **Note**: In order for all unit tests to function correctly when building without linking against libpython we must set `NMODL_PYLIB` before running cmake! diff --git a/pywheel/shim/_binwrapper.py b/pywheel/shim/_binwrapper.py index fe2a920e06..c495e037f3 100755 --- a/pywheel/shim/_binwrapper.py +++ b/pywheel/shim/_binwrapper.py @@ -27,13 +27,6 @@ def _config_exe(exe_name): NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, "nmodl") NMODL_HOME = os.path.join(NMODL_PREFIX, ".data") NMODL_BIN = os.path.join(NMODL_HOME, "bin") - NMODL_LIB = os.path.join(NMODL_HOME, "lib") - - # add pywrapper path to environment - if sys.platform == "darwin": - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_LIB, "libpywrapper.dylib") - else: - os.environ["NMODL_WRAPLIB"] = os.path.join(NMODL_LIB, "libpywrapper.so") # add libpython*.so path to environment os.environ["NMODL_PYLIB"] = find_libpython() diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index d962184f73..3abdda0290 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -13,6 +13,8 @@ const std::string nmodl::Version::GIT_REVISION = "@NMODL_GIT_REVISION@"; /// NMODL version const std::string nmodl::Version::NMODL_VERSION = "@PROJECT_VERSION@"; +const std::string nmodl::CMakeInfo::SHARED_LIBRARY_SUFFIX = "@CMAKE_SHARED_LIBRARY_SUFFIX@"; + /** * \brief Path of nrnutils.lib file * diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index 513bb64041..7173672ce6 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -74,4 +74,8 @@ struct NrnUnitsLib { } }; +struct CMakeInfo { + static const std::string SHARED_LIBRARY_SUFFIX; +}; + } // namespace nmodl diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index a78174cefd..02dbd4d743 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -30,7 +30,8 @@ target_link_libraries(pyembed PRIVATE util) if(NOT LINK_AGAINST_PYTHON) add_library(pywrapper SHARED ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) - set_target_properties(pywrapper PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + set_target_properties(pywrapper PROPERTIES LIBRARY_OUTPUT_DIRECTORY + ${NMODL_PROJECT_BINARY_DIR}/lib) else() add_library(pywrapper ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) set_property(TARGET pywrapper PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index b882cc4ad4..ba17acf7ff 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -7,10 +7,14 @@ #include #include +#include +#include "config/config.h" #include "pybind/pyembed.hpp" #include "utils/logger.hpp" +namespace fs = std::filesystem; + namespace nmodl { namespace pybind_wrappers { @@ -41,16 +45,18 @@ void EmbeddedPythonLoader::load_libraries() { logger->critical(errstr); throw std::runtime_error("Failed to dlopen"); } - const auto pybind_wraplib_env = std::getenv("NMODL_WRAPLIB"); - if (!pybind_wraplib_env) { + auto pybind_wraplib_env = fs::path(std::getenv("NMODLHOME")) / "lib" / "libpywrapper"; + pybind_wraplib_env.concat(CMakeInfo::SHARED_LIBRARY_SUFFIX); + if (!fs::exists(pybind_wraplib_env)) { logger->critical( - "NMODL_WRAPLIB environment variable must be set to load the pybind wrapper library"); - throw std::runtime_error("NMODL_WRAPLIB not set"); + "NMODLHOME environment variable must be set to load the pybind " + "wrapper library"); + throw std::runtime_error("NMODLHOME not set"); } - pybind_wrapper_handle = dlopen(pybind_wraplib_env, dlopen_opts); + pybind_wrapper_handle = dlopen(pybind_wraplib_env.c_str(), dlopen_opts); if (!pybind_wrapper_handle) { const auto errstr = dlerror(); - logger->critical("Tried but failed to load {}", pybind_wraplib_env); + logger->critical("Tried but failed to load {}", pybind_wraplib_env.string()); logger->critical(errstr); throw std::runtime_error("Failed to dlopen"); } diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index c3ea1f8ebc..7b434ac869 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -114,8 +114,6 @@ set(test_env ${NMODL_SANITIZER_ENABLE_ENVIRONMENT}) set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") if(NOT LINK_AGAINST_PYTHON) list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") - list(APPEND testvisitor_env - "NMODL_WRAPLIB=${PROJECT_BINARY_DIR}/lib/nmodl/libpywrapper${CMAKE_SHARED_LIBRARY_SUFFIX}") endif() foreach( From 4883f1f79b8190c92585f314a646be422b8feb9d Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Thu, 4 May 2023 23:15:30 +0200 Subject: [PATCH 512/871] Drop fast_math and move CI from %intel to %oneapi (BlueBrain/nmodl#1042) NMODL Repo SHA: BlueBrain/nmodl@f8a1ee69028f189847de8f1af332262561948db3 --- src/nmodl/codegen/CMakeLists.txt | 4 - src/nmodl/codegen/codegen_cpp_visitor.cpp | 1 - src/nmodl/codegen/fast_math.hpp | 258 ------------------ test/nmodl/transpiler/unit/CMakeLists.txt | 2 - .../transpiler/unit/fast_math/fast_math.cpp | 126 --------- 5 files changed, 391 deletions(-) delete mode 100644 src/nmodl/codegen/fast_math.hpp delete mode 100644 test/nmodl/transpiler/unit/fast_math/fast_math.cpp diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index cb48dd3034..f77e34e34e 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -12,7 +12,3 @@ add_library( codegen_utils.cpp) add_dependencies(codegen lexer util visitor) target_link_libraries(codegen PRIVATE util) - -# copy to build directory to make usable from build directory -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fast_math.hpp - ${CMAKE_BINARY_DIR}/include/nmodl/fast_math.hpp COPYONLY) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 1cc56e1314..84288f0706 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -2450,7 +2450,6 @@ void CodegenCVisitor::print_backend_info() { void CodegenCVisitor::print_standard_includes() { printer->add_newline(); printer->add_line("#include "); - printer->add_line("#include \"nmodl/fast_math.hpp\" // extend math with some useful functions"); printer->add_line("#include "); printer->add_line("#include "); printer->add_line("#include "); diff --git a/src/nmodl/codegen/fast_math.hpp b/src/nmodl/codegen/fast_math.hpp deleted file mode 100644 index 82f27c7d5e..0000000000 --- a/src/nmodl/codegen/fast_math.hpp +++ /dev/null @@ -1,258 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#pragma once - -#include -#include -#include - -/** - * \file - * \brief Implementation of different math functions - */ - -namespace nmodl { -namespace fast_math { - -static inline double uint642dp(uint64_t x) { - static_assert(sizeof(double) == sizeof(uint64_t), - "nmodl::fast_math::uint642dp requires sizeof(double) == sizeof(uint64_t)"); - double v; - std::memcpy(&v, &x, sizeof(double)); - return v; -} - -static inline uint64_t dp2uint64(double x) { - static_assert(sizeof(double) == sizeof(uint64_t), - "nmodl::fast_math::dp2uint64 requires sizeof(double) == sizeof(uint64_t)"); - uint64_t v; - std::memcpy(&v, &x, sizeof(uint64_t)); - return v; -} - -static inline float int322sp(int32_t x) { - static_assert(sizeof(float) == sizeof(int32_t), - "nmodl::fast_math::int322sp requires sizeof(float) == sizeof(int32_t)"); - float v; - std::memcpy(&v, &x, sizeof(float)); - return v; -} - -static inline unsigned int sp2uint32(float x) { - static_assert(sizeof(float) == sizeof(unsigned int), - "nmodl::fast_math::sp2uint32 requires sizeof(float) == sizeof(unsigned int)"); - unsigned int v; - std::memcpy(&v, &x, sizeof(unsigned int)); - return v; -} - -static inline float f_inf() { - static_assert(sizeof(float) == sizeof(uint32_t), - "nmodl::fast_math::f_inf requires sizeof(float) == sizeof(uint32_t)"); - float v; - uint32_t int_val{0x7F800000}; - std::memcpy(&v, &int_val, sizeof(float)); - return v; -} - -static inline double inf() { - static_assert(sizeof(double) == sizeof(uint64_t), - "nmodl::fast_math::inf requires sizeof(double) == sizeof(uint64_t)"); - double v; - uint64_t int_val{0x7FF0000000000000}; - std::memcpy(&v, &int_val, sizeof(double)); - return v; -} - - -static constexpr double EXP_LIMIT = 708.0; - -static constexpr double C1 = 6.93145751953125E-1; -static constexpr double C2 = 1.42860682030941723212E-6; - -static constexpr double PX1exp = 1.26177193074810590878E-4; -static constexpr double PX2exp = 3.02994407707441961300E-2; -static constexpr double PX3exp = 9.99999999999999999910E-1; -static constexpr double QX1exp = 3.00198505138664455042E-6; -static constexpr double QX2exp = 2.52448340349684104192E-3; -static constexpr double QX3exp = 2.27265548208155028766E-1; -static constexpr double QX4exp = 2.00000000000000000009; - -static constexpr double LOG2E = 1.4426950408889634073599; // 1/ln(2) - -static constexpr float MAXLOGF = 88.72283905206835f; -static constexpr float MINLOGF = -88.f; - -static constexpr float C1F = 0.693359375f; -static constexpr float C2F = -2.12194440e-4f; - -static constexpr float PX1expf = 1.9875691500E-4f; -static constexpr float PX2expf = 1.3981999507E-3f; -static constexpr float PX3expf = 8.3334519073E-3f; -static constexpr float PX4expf = 4.1665795894E-2f; -static constexpr float PX5expf = 1.6666665459E-1f; -static constexpr float PX6expf = 5.0000001201E-1f; - -static constexpr float LOG2EF = 1.44269504088896341f; // 1/ln(2) - -static constexpr double LOG10E = 0.4342944819032518; // 1/log(10) -static constexpr float LOG10F = 0.4342945f; // 1/log(10) - -static inline double egm1(double x, double n) { - // this cannot be reordered for the double-double trick to work - // i.e., it cannot be re-written as g = x - n * (C1+C2) - // the loss of accuracy comes from the different magnitudes of ln(2) and n - // max(|n|) ~ 2^9 - // ln(2) ~ 2^-1 - volatile double g = x - n * C1; - g -= n * C2; - - const double gg = g * g; - - double px = PX1exp; - px *= gg; - px += PX2exp; - px *= gg; - px += PX3exp; - px *= g; - - double qx = QX1exp; - qx *= gg; - qx += QX2exp; - qx *= gg; - qx += QX3exp; - qx *= gg; - qx += QX4exp; - - return 2.0 * px / (qx - px); -} - -/// double precision exp function -static inline double vexp(double initial_x) { - double x = initial_x; - double px = std::floor(LOG2E * x + 0.5); - const int32_t n = px; - - x = 1.0 + egm1(x, px); - x *= uint642dp((((uint64_t) n) + 1023) << 52); - - if (initial_x > EXP_LIMIT) - x = inf(); - if (initial_x < -EXP_LIMIT) - x = 0.0; - - return x; -} - -/// double precision exp(x) - 1 function, used for small x. -static inline double vexpm1(double initial_x) { - double x = initial_x; - double px = std::floor(LOG2E * x + 0.5); - const int32_t n = px; - - const uint64_t twopnm1 = (static_cast(n - 1) + 1023) << 52; - x = 2 * (uint642dp(twopnm1) * egm1(x, px) + uint642dp(twopnm1)) - 1; - - if (initial_x > EXP_LIMIT) - x = inf(); - if (initial_x < -EXP_LIMIT) - x = -1.0; - - return x; -} - - -/// double precision exprelr function () -static inline double exprelr(double initial_x) { - if (1.0 + initial_x == 1.0) { - return 1.0; - } - - return initial_x / vexpm1(initial_x); -} - -static inline float egm1(float x) { - float z = x * PX1expf; - z += PX2expf; - z *= x; - z += PX3expf; - z *= x; - z += PX4expf; - z *= x; - z += PX5expf; - z *= x; - z += PX6expf; - z *= x * x; - z += x; - - return z; -} - -/// single precision exp function -static inline float vexp(float x) { - float z = std::floor(LOG2EF * x + 0.5f); - - // this cannot be reordered for the double-double trick to work - float volatile g = x - z * C1F; - g -= z * C2F; - const int32_t n = z; - - z = 1.0f + egm1(g); - - z *= int322sp((n + 0x7f) << 23); - - if (x > MAXLOGF) - z = f_inf(); - if (x < MINLOGF) - z = 0.f; - - return z; -} - -/// single precision exp function -static inline float vexpm1(float x) { - float z = std::floor(LOG2EF * x + 0.5f); - - // this cannot be reordered for the double-double trick to work - volatile float g = x - z * C1F; - g -= z * C2F; - - const int32_t n = z; - - const int32_t twopnm1 = ((n - 1) + 0x7f) << 23; - float ret = 2 * (int322sp(twopnm1) * egm1(g) + int322sp(twopnm1)) - 1; - - if (x > MAXLOGF) - ret = f_inf(); - if (x < MINLOGF) - ret = -1.0f; - - return ret; -} - -/// single precision exprelr function -static inline float exprelr(float x) { - if (1.0 + x == 1.0) { - return 1.0; - } - - return x / vexpm1(x); -} - -/// double precision log10 function -static inline double log10(double f) { - return std::log(f) * LOG10E; -} - -/// single precision log10 function -static inline float log10(float f) { - return std::log(f) * LOG10F; -} - -} // namespace fast_math -} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 7b434ac869..24e0b4d891 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -63,7 +63,6 @@ add_executable(testprinter printer/printer.cpp) add_executable(testsymtab symtab/symbol_table.cpp) add_executable(testnewton newton/newton.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testcrout crout/crout.cpp ${CROUT_SOLVER_SOURCE_FILES}) -add_executable(testfast_math fast_math/fast_math.cpp ${NEWTON_SOLVER_SOURCE_FILES}) add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) add_executable( @@ -127,7 +126,6 @@ foreach( testsymtab testnewton testcrout - testfast_math testunitlexer testunitparser) diff --git a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp b/test/nmodl/transpiler/unit/fast_math/fast_math.cpp deleted file mode 100644 index 606ebbcc7c..0000000000 --- a/test/nmodl/transpiler/unit/fast_math/fast_math.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - -#define CATCH_CONFIG_MAIN - -#include "codegen/fast_math.hpp" - -#include - -namespace nmodl { -namespace fast_math { - -template ::value>::type> -bool check_over_span(T f_ref(T), - T f_test(T), - const T low_limit, - const T high_limit, - const size_t npoints) { - constexpr uint nULP = 4; - constexpr T eps = std::numeric_limits::epsilon(); - constexpr T one_o_eps = 1.0 / std::numeric_limits::epsilon(); - T low = std::numeric_limits::min() * one_o_eps * 1e2; - - T range = high_limit - low_limit; - - bool ret = true; - for (size_t i = 0; i < npoints; ++i) { - T x = low_limit + range * i / npoints; - T ref = f_ref(x); - T test = f_test(x); - T diff = std::abs(ref - test); - T max = std::max(std::abs(ref), std::abs(test)); - T tol = max * nULP; - // normalize based on range - if (tol > low) { - tol *= eps; - } else { - diff *= one_o_eps; - } - if (diff > tol && diff != 0.0) { - ret = false; - } - } - return ret; -} - -template ::value>::type> -T exprelr_ref(const T x) { - return (1.0 + x == 1.0) ? 1.0 : x / (std::exp(x) - 1.0); -}; - -SCENARIO("Check fast_math") { - constexpr double low_limit = -708.0; - constexpr double high_limit = 708.0; - constexpr float low_limit_f = -87.0f; - constexpr float high_limit_f = 88.0f; - constexpr size_t npoints = 2000; - constexpr double min_double = std::numeric_limits::min(); - constexpr double max_double = std::numeric_limits::max(); - constexpr double min_float = std::numeric_limits::min(); - constexpr double max_float = std::numeric_limits::max(); - - GIVEN("vexp (double)") { - auto test = check_over_span(std::exp, vexp, low_limit, high_limit, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("vexp (float)") { - auto test = check_over_span(std::exp, vexp, low_limit_f, high_limit_f, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("expm1 (double)") { - auto test = check_over_span(std::expm1, vexpm1, low_limit, high_limit, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("expm1 (float)") { - auto test = check_over_span(std::expm1, vexpm1, low_limit_f, high_limit_f, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("exprelr (double)") { - auto test = check_over_span(exprelr_ref, exprelr, low_limit, high_limit, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("exprelr (float)") { - auto test = check_over_span(exprelr_ref, exprelr, low_limit_f, high_limit_f, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("log10 (double)") { - auto test = check_over_span(std::log10, log10, min_double, max_double, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } - GIVEN("log10 (float)") { - auto test = check_over_span(std::log10, log10, min_float, max_float, npoints); - - THEN("error inside threshold") { - REQUIRE(test); - } - } -} - -} // namespace fast_math -} // namespace nmodl From c6171d74af66c221d497db730040eaf0570f5861 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20S=C4=83vulescu?= Date: Sat, 6 May 2023 16:57:32 +0200 Subject: [PATCH 513/871] setup.py: upstream find_libpython (BlueBrain/nmodl#1045) * setup.py: upstream find_libpython * drop old wheel requirement NMODL Repo SHA: BlueBrain/nmodl@74332868f9abcd3337ed142baae915748d1a7d91 --- pywheel/shim/find_libpython.py | 379 --------------------------------- setup.py | 5 +- src/nmodl/pybind/pyembed.cpp | 11 +- 3 files changed, 10 insertions(+), 385 deletions(-) delete mode 100644 pywheel/shim/find_libpython.py diff --git a/pywheel/shim/find_libpython.py b/pywheel/shim/find_libpython.py deleted file mode 100644 index abab278ae3..0000000000 --- a/pywheel/shim/find_libpython.py +++ /dev/null @@ -1,379 +0,0 @@ -#!/usr/bin/env python - -""" -Locate libpython associated with this Python executable. -""" - -# License -# -# Copyright 2018, Takafumi Arakaki -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -from __future__ import print_function, absolute_import - -from logging import getLogger -import ctypes.util -import functools -import os -import sys -import sysconfig - -logger = getLogger("find_libpython") - -is_windows = os.name == "nt" -is_apple = sys.platform == "darwin" - -SHLIB_SUFFIX = sysconfig.get_config_var("SHLIB_SUFFIX") -if SHLIB_SUFFIX is None: - if is_windows: - SHLIB_SUFFIX = ".dll" - else: - SHLIB_SUFFIX = ".so" -if is_apple: - # sysconfig.get_config_var("SHLIB_SUFFIX") can be ".so" in macOS. - # Let's not use the value from sysconfig. - SHLIB_SUFFIX = ".dylib" - - -def linked_libpython(): - """ - Find the linked libpython using dladdr (in *nix). - Calling this in Windows always return `None` at the moment. - Returns - ------- - path : str or None - A path to linked libpython. Return `None` if statically linked. - """ - if is_windows: - return None - return _linked_libpython_unix() - - -class Dl_info(ctypes.Structure): - _fields_ = [ - ("dli_fname", ctypes.c_char_p), - ("dli_fbase", ctypes.c_void_p), - ("dli_sname", ctypes.c_char_p), - ("dli_saddr", ctypes.c_void_p), - ] - - -def _linked_libpython_unix(): - libdl = ctypes.CDLL(ctypes.util.find_library("dl")) - libdl.dladdr.argtypes = [ctypes.c_void_p, ctypes.POINTER(Dl_info)] - libdl.dladdr.restype = ctypes.c_int - - dlinfo = Dl_info() - retcode = libdl.dladdr( - ctypes.cast(ctypes.pythonapi.Py_GetVersion, ctypes.c_void_p), - ctypes.pointer(dlinfo), - ) - if retcode == 0: # means error - return None - path = os.path.realpath(dlinfo.dli_fname.decode()) - if path == os.path.realpath(sys.executable): - return None - return path - - -def library_name(name, suffix=SHLIB_SUFFIX, is_windows=is_windows): - """ - Convert a file basename `name` to a library name (no "lib" and ".so" etc.) - >>> library_name("libpython3.7m.so") # doctest: +SKIP - 'python3.7m' - >>> library_name("libpython3.7m.so", suffix=".so", is_windows=False) - 'python3.7m' - >>> library_name("libpython3.7m.dylib", suffix=".dylib", is_windows=False) - 'python3.7m' - >>> library_name("python37.dll", suffix=".dll", is_windows=True) - 'python37' - """ - if not is_windows and name.startswith("lib"): - name = name[len("lib") :] - if suffix and name.endswith(suffix): - name = name[: -len(suffix)] - return name - - -def append_truthy(list, item): - if item: - list.append(item) - - -def uniquifying(items): - """ - Yield items while excluding the duplicates and preserving the order. - >>> list(uniquifying([1, 2, 1, 2, 3])) - [1, 2, 3] - """ - seen = set() - for x in items: - if x not in seen: - yield x - seen.add(x) - - -def uniquified(func): - """ Wrap iterator returned from `func` by `uniquifying`. """ - - @functools.wraps(func) - def wrapper(*args, **kwds): - return uniquifying(func(*args, **kwds)) - - return wrapper - - -@uniquified -def candidate_names(suffix=SHLIB_SUFFIX): - """ - Iterate over candidate file names of libpython. - Yields - ------ - name : str - Candidate name libpython. - """ - LDLIBRARY = sysconfig.get_config_var("LDLIBRARY") - if LDLIBRARY: - yield LDLIBRARY - - LIBRARY = sysconfig.get_config_var("LIBRARY") - if LIBRARY: - yield os.path.splitext(LIBRARY)[0] + suffix - - dlprefix = "" if is_windows else "lib" - sysdata = dict( - v=sys.version_info, - # VERSION is X.Y in Linux/macOS and XY in Windows: - VERSION=( - sysconfig.get_config_var("VERSION") - or "{v.major}.{v.minor}".format(v=sys.version_info) - ), - ABIFLAGS=( - sysconfig.get_config_var("ABIFLAGS") - or sysconfig.get_config_var("abiflags") - or "" - ), - ) - - for stem in [ - "python{VERSION}{ABIFLAGS}".format(**sysdata), - "python{VERSION}".format(**sysdata), - "python{v.major}".format(**sysdata), - "python", - ]: - yield dlprefix + stem + suffix - - -@uniquified -def candidate_paths(suffix=SHLIB_SUFFIX): - """ - Iterate over candidate paths of libpython. - Yields - ------ - path : str or None - Candidate path to libpython. The path may not be a fullpath - and may not exist. - """ - - yield linked_libpython() - - # List candidates for directories in which libpython may exist - lib_dirs = [] - append_truthy(lib_dirs, sysconfig.get_config_var("LIBPL")) - append_truthy(lib_dirs, sysconfig.get_config_var("srcdir")) - append_truthy(lib_dirs, sysconfig.get_config_var("LIBDIR")) - - # LIBPL seems to be the right config_var to use. It is the one - # used in python-config when shared library is not enabled: - # https://github.com/python/cpython/blob/v3.7.0/Misc/python-config.in#L55-L57 - # - # But we try other places just in case. - - if is_windows: - lib_dirs.append(os.path.join(os.path.dirname(sys.executable))) - else: - lib_dirs.append( - os.path.join(os.path.dirname(os.path.dirname(sys.executable)), "lib") - ) - - # For macOS: - append_truthy(lib_dirs, sysconfig.get_config_var("PYTHONFRAMEWORKPREFIX")) - - lib_dirs.append(sys.exec_prefix) - lib_dirs.append(os.path.join(sys.exec_prefix, "lib")) - - lib_basenames = list(candidate_names(suffix=suffix)) - - for directory in lib_dirs: - for basename in lib_basenames: - yield os.path.join(directory, basename) - - # In macOS and Windows, ctypes.util.find_library returns a full path: - for basename in lib_basenames: - yield ctypes.util.find_library(library_name(basename)) - - -# Possibly useful links: -# * https://packages.ubuntu.com/bionic/amd64/libpython3.6/filelist -# * https://github.com/Valloric/ycmd/issues/518 -# * https://github.com/Valloric/ycmd/pull/519 - - -def normalize_path(path, suffix=SHLIB_SUFFIX, is_apple=is_apple): - """ - Normalize shared library `path` to a real path. - If `path` is not a full path, `None` is returned. If `path` does - not exists, append `SHLIB_SUFFIX` and check if it exists. - Finally, the path is canonicalized by following the symlinks. - Parameters - ---------- - path : str ot None - A candidate path to a shared library. - """ - if not path: - return None - if not os.path.isabs(path): - return None - if os.path.exists(path): - return os.path.realpath(path) - if os.path.exists(path + suffix): - return os.path.realpath(path + suffix) - if is_apple: - return normalize_path(_remove_suffix_apple(path), suffix=".so", is_apple=False) - return None - - -def _remove_suffix_apple(path): - """ - Strip off .so or .dylib. - >>> _remove_suffix_apple("libpython.so") - 'libpython' - >>> _remove_suffix_apple("libpython.dylib") - 'libpython' - >>> _remove_suffix_apple("libpython3.7") - 'libpython3.7' - """ - if path.endswith(".dylib"): - return path[: -len(".dylib")] - if path.endswith(".so"): - return path[: -len(".so")] - return path - - -@uniquified -def finding_libpython(): - """ - Iterate over existing libpython paths. - The first item is likely to be the best one. - Yields - ------ - path : str - Existing path to a libpython. - """ - logger.debug("is_windows = %s", is_windows) - logger.debug("is_apple = %s", is_apple) - for path in candidate_paths(): - logger.debug("Candidate: %s", path) - normalized = normalize_path(path) - if normalized: - logger.debug("Found: %s", normalized) - yield normalized - else: - logger.debug("Not found.") - - -def find_libpython(): - """ - Return a path (`str`) to libpython or `None` if not found. - Parameters - ---------- - path : str or None - Existing path to the (supposedly) correct libpython. - """ - for path in finding_libpython(): - return os.path.realpath(path) - - -def print_all(items): - for x in items: - print(x) - - -def cli_find_libpython(cli_op, verbose): - import logging - - # Importing `logging` module here so that using `logging.debug` - # instead of `logger.debug` outside of this function becomes an - # error. - - if verbose: - logging.basicConfig(format="%(levelname)s %(message)s", level=logging.DEBUG) - - if cli_op == "list-all": - print_all(finding_libpython()) - elif cli_op == "candidate-names": - print_all(candidate_names()) - elif cli_op == "candidate-paths": - print_all(p for p in candidate_paths() if p and os.path.isabs(p)) - else: - path = find_libpython() - if path is None: - return 1 - print(path, end="") - - -def main(args=None): - import argparse - - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "--verbose", "-v", action="store_true", help="Print debugging information." - ) - - group = parser.add_mutually_exclusive_group() - group.add_argument( - "--list-all", - action="store_const", - dest="cli_op", - const="list-all", - help="Print list of all paths found.", - ) - group.add_argument( - "--candidate-names", - action="store_const", - dest="cli_op", - const="candidate-names", - help="Print list of candidate names of libpython.", - ) - group.add_argument( - "--candidate-paths", - action="store_const", - dest="cli_op", - const="candidate-paths", - help="Print list of candidate paths of libpython.", - ) - - ns = parser.parse_args(args) - parser.exit(cli_find_libpython(**vars(ns))) - - -if __name__ == "__main__": - main() diff --git a/setup.py b/setup.py index e0a6fa59e5..4e064a6cab 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ """ import stat from pkg_resources import working_set -from pywheel.shim.find_libpython import find_libpython +from find_libpython import find_libpython # Main source of the version. Dont rename, used by Cmake @@ -78,6 +78,7 @@ def run(self, *args, **kwargs): install_requirements = [ "PyYAML>=3.13", "sympy>=1.3", + "find_libpython" ] @@ -103,7 +104,7 @@ def run(self, *args, **kwargs): long_description=long_description, long_description_content_type='text/markdown', packages=["nmodl"], - scripts=["pywheel/shim/nmodl", "pywheel/shim/find_libpython.py"], + scripts=["pywheel/shim/nmodl"], include_package_data=True, cmake_minimum_required_version="3.15.0", cmake_args=cmake_args, diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index ba17acf7ff..81fa641b22 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -45,13 +45,16 @@ void EmbeddedPythonLoader::load_libraries() { logger->critical(errstr); throw std::runtime_error("Failed to dlopen"); } + if (std::getenv("NMODLHOME") == nullptr) { + logger->critical("NMODLHOME environment variable must be set to load embedded python"); + throw std::runtime_error("NMODLHOME not set"); + } auto pybind_wraplib_env = fs::path(std::getenv("NMODLHOME")) / "lib" / "libpywrapper"; pybind_wraplib_env.concat(CMakeInfo::SHARED_LIBRARY_SUFFIX); if (!fs::exists(pybind_wraplib_env)) { - logger->critical( - "NMODLHOME environment variable must be set to load the pybind " - "wrapper library"); - throw std::runtime_error("NMODLHOME not set"); + logger->critical("NMODLHOME doesn't contain libpywrapper{} library", + CMakeInfo::SHARED_LIBRARY_SUFFIX); + throw std::runtime_error("NMODLHOME doesn't have lib/libpywrapper library"); } pybind_wrapper_handle = dlopen(pybind_wraplib_env.c_str(), dlopen_opts); if (!pybind_wrapper_handle) { From a61586d2dd1428642ccf51efbc57108c774dd5b7 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 19 May 2023 15:25:16 +0200 Subject: [PATCH 514/871] Update Catch 2 to a v3 version (BlueBrain/nmodl#973) NMODL Repo SHA: BlueBrain/nmodl@74d7dbe996d00301635cf701559f60ba20bbd426 --- cmake/nmodl/CMakeLists.txt | 18 ++- setup.py | 1 + test/nmodl/transpiler/unit/CMakeLists.txt | 74 +++++---- .../codegen/codegen_compatibility_visitor.cpp | 5 +- .../unit/codegen/codegen_cpp_visitor.cpp | 151 ++++++++++-------- .../unit/codegen/codegen_helper.cpp | 2 +- .../transpiler/unit/codegen/codegen_utils.cpp | 2 +- test/nmodl/transpiler/unit/codegen/main.cpp | 5 +- .../transpiler/unit/codegen/transform.cpp | 2 +- test/nmodl/transpiler/unit/crout/crout.cpp | 4 +- test/nmodl/transpiler/unit/lexer/tokens.cpp | 4 +- .../transpiler/unit/modtoken/modtoken.cpp | 4 +- test/nmodl/transpiler/unit/newton/newton.cpp | 13 +- test/nmodl/transpiler/unit/parser/parser.cpp | 31 ++-- .../nmodl/transpiler/unit/printer/printer.cpp | 5 +- .../transpiler/unit/symtab/symbol_table.cpp | 32 ++-- test/nmodl/transpiler/unit/units/lexer.cpp | 4 +- test/nmodl/transpiler/unit/units/parser.cpp | 41 ++--- .../unit/visitor/after_cvode_to_cnexp.cpp | 2 +- .../unit/visitor/constant_folder.cpp | 2 +- .../unit/visitor/defuse_analyze.cpp | 3 +- .../unit/visitor/global_to_range.cpp | 2 +- .../unit/visitor/implicit_argument.cpp | 15 +- test/nmodl/transpiler/unit/visitor/inline.cpp | 3 +- test/nmodl/transpiler/unit/visitor/json.cpp | 2 +- .../transpiler/unit/visitor/kinetic_block.cpp | 2 +- .../unit/visitor/local_to_assigned.cpp | 2 +- .../transpiler/unit/visitor/localize.cpp | 2 +- test/nmodl/transpiler/unit/visitor/lookup.cpp | 2 +- .../transpiler/unit/visitor/loop_unroll.cpp | 2 +- test/nmodl/transpiler/unit/visitor/main.cpp | 5 +- test/nmodl/transpiler/unit/visitor/misc.cpp | 2 +- .../transpiler/unit/visitor/neuron_solve.cpp | 2 +- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 2 +- .../transpiler/unit/visitor/node_index.cpp | 2 +- test/nmodl/transpiler/unit/visitor/perf.cpp | 6 +- test/nmodl/transpiler/unit/visitor/rename.cpp | 2 +- .../unit/visitor/semantic_analysis.cpp | 2 +- .../transpiler/unit/visitor/solve_block.cpp | 2 +- .../transpiler/unit/visitor/steadystate.cpp | 2 +- .../unit/visitor/sympy_conductance.cpp | 2 +- .../transpiler/unit/visitor/sympy_solver.cpp | 22 +-- test/nmodl/transpiler/unit/visitor/units.cpp | 2 +- .../transpiler/unit/visitor/var_usage.cpp | 2 +- .../transpiler/unit/visitor/verbatim.cpp | 2 +- 45 files changed, 263 insertions(+), 231 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 2ac8a17a8c..bb59cb4ff3 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -22,6 +22,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # ============================================================================= option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) option(NMODL_ENABLE_LEGACY_UNITS "Use original faraday, R, etc. instead of 2019 nist constants" OFF) +option(NMODL_ENABLE_TESTS "Enable build of tests" ON) if(NMODL_ENABLE_LEGACY_UNITS) add_definitions(-DUSE_LEGACY_UNITS) endif() @@ -99,13 +100,6 @@ list(APPEND NMODL_EXTRA_CXX_FLAGS ${NMODL_SANITIZER_COMPILER_FLAGS}) # ============================================================================= set(NMODL_3RDPARTY_DIR ext) include(cmake/hpc-coding-conventions/cpp/cmake/3rdparty.cmake) -cpp_cc_git_submodule(catch2 BUILD PACKAGE Catch2 REQUIRED) -if(NMODL_3RDPARTY_USE_CATCH2) - # If we're using the submodule then make sure the Catch.cmake helper can be found. In newer - # versions of Catch2, and with hpc-coding-conventions#130, this should just work... - list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ext/catch2/contrib") -endif() -include(Catch) # If we're being built as a submodule of CoreNEURON then CoreNEURON may have already found/loaded a # CLI11 submodule. if(NOT TARGET CLI11::CLI11) @@ -205,7 +199,14 @@ set(MEMORYCHECK_COMMAND_OPTIONS --track-origins=yes \ --show-possibly-lost=no") # do not enable tests if nmodl is used as submodule -if(NOT NMODL_AS_SUBPROJECT) +if(NOT NMODL_AS_SUBPROJECT AND NMODL_ENABLE_TESTS) + cpp_cc_git_submodule(catch2 BUILD PACKAGE Catch2 REQUIRED) + if(NMODL_3RDPARTY_USE_CATCH2) + # If we're using the submodule then make sure the Catch.cmake helper can be found. In newer + # versions of Catch2, and with hpc-coding-conventions#130, this should just work... + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/ext/catch2/extras") + endif() + include(Catch) include(CTest) add_subdirectory(test/unit) add_subdirectory(test/integration) @@ -271,6 +272,7 @@ message(STATUS "Python Bindings | ${NMODL_ENABLE_PYTHON_BINDINGS}") message(STATUS "Flex | ${FLEX_EXECUTABLE}") message(STATUS "Bison | ${BISON_EXECUTABLE}") message(STATUS "Python | ${PYTHON_EXECUTABLE}") +message(STATUS " Linked against | ${LINK_AGAINST_PYTHON}") message(STATUS "--------------+--------------------------------------------------------------") message(STATUS " See documentation : https://github.com/BlueBrain/nmodl/") message(STATUS "--------------+--------------------------------------------------------------") diff --git a/setup.py b/setup.py index 4e064a6cab..c35ae3fc1d 100644 --- a/setup.py +++ b/setup.py @@ -85,6 +85,7 @@ def run(self, *args, **kwargs): cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable] if "bdist_wheel" in sys.argv: cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE") + cmake_args.append("-DNMODL_ENABLE_TESTS=FALSE") # For CI, we want to build separate wheel package_name = "NMODL" diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 24e0b4d891..bf27da5cb4 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -70,41 +70,41 @@ add_executable( codegen/main.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp codegen/codegen_cpp_visitor.cpp codegen/transform.cpp codegen/codegen_compatibility_visitor.cpp) -target_link_libraries(testmodtoken lexer util) -target_link_libraries(testlexer lexer util) +target_link_libraries(testmodtoken PRIVATE lexer util) +target_link_libraries(testlexer PRIVATE lexer util) target_link_libraries( testparser - visitor - symtab - lexer - util - test_util - printer - ${NMODL_WRAPPER_LIBS}) + PRIVATE visitor + symtab + lexer + util + test_util + printer + ${NMODL_WRAPPER_LIBS}) target_link_libraries( testvisitor - visitor - symtab - lexer - util - test_util - printer - codegen - ${NMODL_WRAPPER_LIBS}) + PRIVATE visitor + symtab + lexer + util + test_util + printer + codegen + ${NMODL_WRAPPER_LIBS}) target_link_libraries( testcodegen - codegen - visitor - symtab - lexer - util - test_util - printer - ${NMODL_WRAPPER_LIBS}) -target_link_libraries(testprinter printer util) -target_link_libraries(testsymtab symtab lexer util) -target_link_libraries(testunitlexer lexer util) -target_link_libraries(testunitparser lexer test_util config) + PRIVATE codegen + visitor + symtab + lexer + util + test_util + printer + ${NMODL_WRAPPER_LIBS}) +target_link_libraries(testprinter PRIVATE printer util) +target_link_libraries(testsymtab PRIVATE symtab lexer util) +target_link_libraries(testunitlexer PRIVATE lexer util) +target_link_libraries(testunitparser PRIVATE lexer test_util config) # ============================================================================= # Use catch_discover instead of add_test for granular test result reporting. @@ -115,6 +115,21 @@ if(NOT LINK_AGAINST_PYTHON) list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") endif() +# Without main from Catch2 +target_link_libraries(testcodegen PRIVATE Catch2::Catch2) +target_link_libraries(testvisitor PRIVATE Catch2::Catch2) + +# With main from Catch2 +target_link_libraries(testmodtoken PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testlexer PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testparser PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testprinter PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testsymtab PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testnewton PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testcrout PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testunitlexer PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testunitparser PRIVATE Catch2::Catch2WithMain) + foreach( test_name testcodegen @@ -129,7 +144,6 @@ foreach( testunitlexer testunitparser) - target_link_libraries(${test_name} Catch2::Catch2) cpp_cc_configure_sanitizers(TARGET ${test_name}) set(env ${test_env}) list(APPEND env ${${test_name}_env}) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp index fc087cac1d..1f4c7bce3b 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp @@ -5,7 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include +#include #include "ast/program.hpp" #include "codegen/codegen_compatibility_visitor.hpp" @@ -14,7 +15,7 @@ #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" -using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 +using Catch::Matchers::ContainsSubstring; using namespace nmodl; using namespace visitor; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 5e6d72cda1..80cb62334f 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -5,7 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include +#include #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" @@ -21,7 +22,7 @@ #include "visitors/sympy_solver_visitor.hpp" #include "visitors/symtab_visitor.hpp" -using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 +using Catch::Matchers::ContainsSubstring; using namespace nmodl; using namespace visitor; @@ -148,7 +149,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { )"; auto const expected = reindent_text(generated_code); auto const result = get_instance_var_setup_function(nmodl_text); - REQUIRE_THAT(result, Contains(expected)); + REQUIRE_THAT(result, ContainsSubstring(expected)); } } @@ -197,7 +198,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { auto const expected = reindent_text(generated_code); auto const result = get_instance_var_setup_function(nmodl_text); - REQUIRE_THAT(result, Contains(expected)); + REQUIRE_THAT(result, ContainsSubstring(expected)); } } @@ -282,7 +283,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { auto const expected = reindent_text(generated_code); auto const result = get_instance_var_setup_function(nmodl_text); - REQUIRE_THAT(result, Contains(expected)); + REQUIRE_THAT(result, ContainsSubstring(expected)); } } } @@ -358,9 +359,10 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", )"; THEN("The instance struct should contain these variables") { auto const generated = get_instance_structure(nmodl_text); - REQUIRE_THAT(generated, Contains("double* celsius{&coreneuron::celsius}")); - REQUIRE_THAT(generated, Contains("double* pi{&coreneuron::pi}")); - REQUIRE_THAT(generated, Contains("int* secondorder{&coreneuron::secondorder}")); + REQUIRE_THAT(generated, ContainsSubstring("double* celsius{&coreneuron::celsius}")); + REQUIRE_THAT(generated, ContainsSubstring("double* pi{&coreneuron::pi}")); + REQUIRE_THAT(generated, + ContainsSubstring("int* secondorder{&coreneuron::secondorder}")); } } GIVEN("A MOD file that implicitly uses global variables") { @@ -375,7 +377,7 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", )"; THEN("The instance struct should contain celsius for the implicit 5th argument") { auto const generated = get_instance_structure(nmodl_text); - REQUIRE_THAT(generated, Contains("celsius")); + REQUIRE_THAT(generated, ContainsSubstring("celsius")); } } GIVEN("A MOD file that does not touch celsius, secondorder or pi") { @@ -386,9 +388,9 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", )"; THEN("The instance struct should not contain those variables") { auto const generated = get_instance_structure(nmodl_text); - REQUIRE_THAT(generated, !Contains("celsius")); - REQUIRE_THAT(generated, !Contains("pi")); - REQUIRE_THAT(generated, !Contains("secondorder")); + REQUIRE_THAT(generated, !ContainsSubstring("celsius")); + REQUIRE_THAT(generated, !ContainsSubstring("pi")); + REQUIRE_THAT(generated, !ContainsSubstring("secondorder")); } } } @@ -430,19 +432,25 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable )"; THEN("Array and global variables should be correctly generated") { auto const generated = get_cpp_code(nmodl_text); - REQUIRE_THAT(generated, Contains("double t_inf[2][201]{};")); - REQUIRE_THAT(generated, Contains("double t_tau[201]{};")); + REQUIRE_THAT(generated, ContainsSubstring("double t_inf[2][201]{};")); + REQUIRE_THAT(generated, ContainsSubstring("double t_tau[201]{};")); - REQUIRE_THAT(generated, Contains("inst->global->t_inf[0][i] = (inst->inf+id*2)[0];")); - REQUIRE_THAT(generated, Contains("inst->global->t_inf[1][i] = (inst->inf+id*2)[1];")); - REQUIRE_THAT(generated, Contains("inst->global->t_tau[i] = inst->global->tau;")); + REQUIRE_THAT(generated, + ContainsSubstring("inst->global->t_inf[0][i] = (inst->inf+id*2)[0];")); + REQUIRE_THAT(generated, + ContainsSubstring("inst->global->t_inf[1][i] = (inst->inf+id*2)[1];")); + REQUIRE_THAT(generated, + ContainsSubstring("inst->global->t_tau[i] = inst->global->tau;")); REQUIRE_THAT(generated, - Contains("(inst->inf+id*2)[0] = inst->global->t_inf[0][index];")); + ContainsSubstring("(inst->inf+id*2)[0] = inst->global->t_inf[0][index];")); - REQUIRE_THAT(generated, Contains("(inst->inf+id*2)[0] = inst->global->t_inf[0][i]")); - REQUIRE_THAT(generated, Contains("(inst->inf+id*2)[1] = inst->global->t_inf[1][i]")); - REQUIRE_THAT(generated, Contains("inst->global->tau = inst->global->t_tau[i]")); + REQUIRE_THAT(generated, + ContainsSubstring("(inst->inf+id*2)[0] = inst->global->t_inf[0][i]")); + REQUIRE_THAT(generated, + ContainsSubstring("(inst->inf+id*2)[1] = inst->global->t_inf[1][i]")); + REQUIRE_THAT(generated, + ContainsSubstring("inst->global->tau = inst->global->t_tau[i]")); } } GIVEN("A MOD file with two table statements") { @@ -499,8 +507,8 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a // BEFORE BREAKPOINT { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, " - "BAType::Before + BAType::Breakpoint);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, " + "BAType::Before + BAType::Breakpoint);")); // in case of PROTECT, there should not be simd or ivdep pragma std::string generated_code = R"( @@ -517,13 +525,13 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } })"; auto const expected = generated_code; - REQUIRE_THAT(generated, Contains(expected)); + REQUIRE_THAT(generated, ContainsSubstring(expected)); } // AFTER SOLVE { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, BAType::After " - "+ BAType::Solve);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, " + "BAType::After + BAType::Solve);")); // in case of MUTEXLOCK/MUTEXUNLOCK, there should not be simd or ivdep pragma std::string generated_code = R"( @@ -542,13 +550,13 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } })"; auto const expected = generated_code; - REQUIRE_THAT(generated, Contains(expected)); + REQUIRE_THAT(generated, ContainsSubstring(expected)); } // BEFORE INITIAL { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, " - "BAType::Before + BAType::Initial);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, " + "BAType::Before + BAType::Initial);")); std::string generated_code = R"( #pragma ivdep #pragma omp simd @@ -564,13 +572,13 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } })"; auto const expected = generated_code; - REQUIRE_THAT(generated, Contains(expected)); + REQUIRE_THAT(generated, ContainsSubstring(expected)); } // AFTER INITIAL { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, BAType::After " - "+ BAType::Initial);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, " + "BAType::After + BAType::Initial);")); std::string generated_code = R"( #pragma ivdep #pragma omp simd @@ -586,13 +594,13 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } })"; auto const expected = generated_code; - REQUIRE_THAT(generated, Contains(expected)); + REQUIRE_THAT(generated, ContainsSubstring(expected)); } // BEFORE STEP { REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_4_ba1, " - "BAType::Before + BAType::Step);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_4_ba1, " + "BAType::Before + BAType::Step);")); std::string generated_code = R"( #pragma ivdep #pragma omp simd @@ -608,7 +616,7 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } })"; auto const expected = generated_code; - REQUIRE_THAT(generated, Contains(expected)); + REQUIRE_THAT(generated, ContainsSubstring(expected)); } } } @@ -626,17 +634,17 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a THEN("They should be all registered") { auto const generated = get_cpp_code(nmodl_text); REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, BAType::Before + " - "BAType::Step);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, " + "BAType::Before + BAType::Step);")); REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, BAType::After + " - "BAType::Solve);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_1_ba1, " + "BAType::After + BAType::Solve);")); REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, BAType::Before + " - "BAType::Step);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, " + "BAType::Before + BAType::Step);")); REQUIRE_THAT(generated, - Contains("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, BAType::After + " - "BAType::Solve);")); + ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, " + "BAType::After + BAType::Solve);")); } } } @@ -669,7 +677,8 @@ SCENARIO("Check CONSTANT variables are added to global variable structure", double kB{1.38065e-23}; double q10Fluo{1.67}; };)"; - REQUIRE_THAT(generated, Contains(reindent_text(stringutils::trim(expected_code)))); + REQUIRE_THAT(generated, + ContainsSubstring(reindent_text(stringutils::trim(expected_code)))); } } } @@ -683,12 +692,14 @@ SCENARIO("Check code generation for FUNCTION_TABLE block", "[codegen][function_t )"; THEN("Code should be generated correctly") { auto const generated = get_cpp_code(nmodl_text); - REQUIRE_THAT(generated, Contains("double ttt_glia(")); - REQUIRE_THAT(generated, Contains("double table_ttt_glia(")); - REQUIRE_THAT(generated, Contains("hoc_spec_table(&inst->global->_ptable_ttt, 1")); - REQUIRE_THAT(generated, Contains("double uuu_glia(")); - REQUIRE_THAT(generated, Contains("double table_uuu_glia(")); - REQUIRE_THAT(generated, Contains("hoc_func_table(inst->global->_ptable_uuu, 2")); + REQUIRE_THAT(generated, ContainsSubstring("double ttt_glia(")); + REQUIRE_THAT(generated, ContainsSubstring("double table_ttt_glia(")); + REQUIRE_THAT(generated, + ContainsSubstring("hoc_spec_table(&inst->global->_ptable_ttt, 1")); + REQUIRE_THAT(generated, ContainsSubstring("double uuu_glia(")); + REQUIRE_THAT(generated, ContainsSubstring("double table_uuu_glia(")); + REQUIRE_THAT(generated, + ContainsSubstring("hoc_func_table(inst->global->_ptable_uuu, 2")); } } } @@ -730,7 +741,7 @@ SCENARIO("Check that loops are well generated", "[codegen][loops]") { for (int a = 1; a <= 10; a += 2) { b = b + 1.0; })"; - REQUIRE_THAT(generated, Contains(expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); } } } @@ -760,7 +771,7 @@ SCENARIO("Check that top verbatim blocks are well generated", "[codegen][top ver foo_(nt); &tqitem; pnodecount+id;)"; - REQUIRE_THAT(generated, Contains(expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); } } } @@ -798,7 +809,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even nsb->_nsb_flag[i] = flag; } })"; - REQUIRE_THAT(generated, Contains(cpu_net_send_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(cpu_net_send_expected_code)); auto const gpu_generated = get_cpp_code(nmodl_text, true); std::string gpu_net_send_expected_code = R"(static inline void net_send_buffering(const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { @@ -819,7 +830,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even nsb->_nsb_flag[i] = flag; } })"; - REQUIRE_THAT(gpu_generated, Contains(gpu_net_send_expected_code)); + REQUIRE_THAT(gpu_generated, ContainsSubstring(gpu_net_send_expected_code)); std::string net_receive_kernel_expected_code = R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { int tid = pnt->_tid; @@ -842,7 +853,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } } })"; - REQUIRE_THAT(generated, Contains(net_receive_kernel_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(net_receive_kernel_expected_code)); std::string net_receive_expected_code = R"(static void net_receive_(Point_process* pnt, int weight_index, double flag) { NrnThread* nt = nrn_threads + pnt->_tid; @@ -858,7 +869,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even nrb->_nrb_flag[id] = flag; nrb->_cnt++; })"; - REQUIRE_THAT(generated, Contains(net_receive_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(net_receive_expected_code)); std::string net_buf_receive_expected_code = R"(void net_buf_receive_(NrnThread* nt) { Memb_list* ml = get_memb_list(nt); if (!ml) { @@ -899,15 +910,15 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } nsb->_cnt = 0; })"; - REQUIRE_THAT(generated, Contains(net_buf_receive_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(net_buf_receive_expected_code)); std::string net_init_expected_code = R"(static void net_init(Point_process* pnt, int weight_index, double flag) { // do nothing })"; - REQUIRE_THAT(generated, Contains(net_init_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(net_init_expected_code)); std::string set_pnt_receive_expected_code = "set_pnt_receive(mech_type, net_receive_, net_init, num_net_receive_args());"; - REQUIRE_THAT(generated, Contains(set_pnt_receive_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(set_pnt_receive_expected_code)); } } GIVEN("A mod file with an INITIAL inside NET_RECEIVE") { @@ -938,7 +949,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even a = 1.0; auto& nsb = ml->_net_send_buffer; })"; - REQUIRE_THAT(generated, Contains(expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); } } @@ -955,7 +966,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even std::string expected_code( "net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], " "weight_index, point_process, nt->_t+5.0, 1.0);"); - REQUIRE_THAT(generated, Contains(expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); } } @@ -970,7 +981,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even std::string expected_code( "net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], 0, " "point_process, nt->_t+5.0, 1.0);"); - REQUIRE_THAT(generated, Contains(expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); } } @@ -1009,9 +1020,9 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } })"; - REQUIRE_THAT(generated, Contains(net_receive_kernel_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(net_receive_kernel_expected_code)); std::string registration_expected_code = "add_nrn_fornetcons(mech_type, 0);"; - REQUIRE_THAT(generated, Contains(registration_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(registration_expected_code)); } } GIVEN("A mod file with a net_move outside NET_RECEIVE") { @@ -1063,7 +1074,7 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { } }; })"; - REQUIRE_THAT(generated, Contains(newton_state_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(newton_state_expected_code)); std::string state_expected_code = R"(int state_(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) { auto* const inst = static_cast<_Instance*>(ml->instance); @@ -1077,7 +1088,7 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { int reset = nrn_newton_thread(static_cast(*newtonspace1(thread)), 1, slist2, _newton_state_{}, dlist2, id, pnodecount, data, indexes, thread, nt, ml, v); return reset; })"; - REQUIRE_THAT(generated, Contains(state_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(state_expected_code)); } } } @@ -1112,7 +1123,7 @@ SCENARIO("Some tests on euler solver", "[codegen][euler_solver]") { inst->Dn[id] = (2.0 + inst->m[id] - inf) * inst->n[id]; inst->m[id] = inst->m[id] + nt->_dt * inst->Dm[id]; inst->n[id] = inst->n[id] + nt->_dt * inst->Dn[id];)"; - REQUIRE_THAT(generated, Contains(nrn_state_expected_code)); + REQUIRE_THAT(generated, ContainsSubstring(nrn_state_expected_code)); } } } @@ -1154,8 +1165,8 @@ SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { std::string expected_code_proc = R"(#pragma omp atomic update inst->foo[id] = inst->foo[id] - 21.0;)"; - REQUIRE_THAT(generated, Contains(expected_code_initial)); - REQUIRE_THAT(generated, Contains(expected_code_proc)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code_initial)); + REQUIRE_THAT(generated, ContainsSubstring(expected_code_proc)); } } } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 9aa296efcd..669e54a242 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "codegen/codegen_helper_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index 6b57249261..1e576425bb 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_utils.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp index 9c75cacfd2..60bfd84120 100644 --- a/test/nmodl/transpiler/unit/codegen/main.cpp +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -5,9 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_RUNNER - -#include +#include +#include #include "pybind/pyembed.hpp" #include "utils/logger.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/transform.cpp b/test/nmodl/transpiler/unit/codegen/transform.cpp index 234eec6a68..feffa88b88 100644 --- a/test/nmodl/transpiler/unit/codegen/transform.cpp +++ b/test/nmodl/transpiler/unit/codegen/transform.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "codegen/codegen_transform_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/crout/crout.cpp b/test/nmodl/transpiler/unit/crout/crout.cpp index e23a642be6..75e15a3bdf 100644 --- a/test/nmodl/transpiler/unit/crout/crout.cpp +++ b/test/nmodl/transpiler/unit/crout/crout.cpp @@ -5,11 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include "nmodl.hpp" -#include +#include #include #include diff --git a/test/nmodl/transpiler/unit/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp index 7747c8cc96..d21429f551 100644 --- a/test/nmodl/transpiler/unit/lexer/tokens.cpp +++ b/test/nmodl/transpiler/unit/lexer/tokens.cpp @@ -5,11 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include -#include +#include #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index 0a11436add..c10211af6f 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -5,12 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include #include -#include +#include #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 8035a4d060..15c0493311 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -5,11 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include "nmodl.hpp" -#include +#include +#include #include @@ -36,7 +35,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer CAPTURE(iter_newton); CAPTURE(X); REQUIRE(iter_newton > 0); - REQUIRE(X[0] == Approx(1.0)); + REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(1.0, 0.01)); REQUIRE(F.norm() < max_error_norm); } } @@ -57,7 +56,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer CAPTURE(iter_newton); CAPTURE(X); REQUIRE(iter_newton > 0); - REQUIRE(X[0] == Approx(2.19943987001206)); + REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(2.19943987001206, 0.01)); REQUIRE(F.norm() < max_error_norm); } } @@ -224,7 +223,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") CAPTURE(iter_newton); CAPTURE(X); REQUIRE(iter_newton > 0); - REQUIRE(X[0] == Approx(1.0)); + REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(1.0, 0.01)); REQUIRE(F.norm() < max_error_norm); } } @@ -248,7 +247,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") CAPTURE(iter_newton); CAPTURE(X); REQUIRE(iter_newton > 0); - REQUIRE(X[0] == Approx(2.19943987001206)); + REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(2.19943987001206, 0.01)); REQUIRE(F.norm() < max_error_norm); } } diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 42e9a7b511..6e022a1bd2 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -5,12 +5,11 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include #include -#include +#include +#include #include "ast/program.hpp" #include "lexer/modtoken.hpp" @@ -34,8 +33,8 @@ bool is_valid_construct(const std::string& construct) { } -SCENARIO("NMODL can accept \\r as return char for one line comment", "[parser]") { - GIVEN("A comment defined with \\r as return char") { +SCENARIO("NMODL can accept CR as return char for one line comment", "[parser]") { + GIVEN("A comment defined with CR as return char") { WHEN("parsing") { THEN("success") { REQUIRE(is_valid_construct(R"(: see you next line @@ -64,7 +63,7 @@ SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { WHEN("DEFINE SIX 6 DEFINE NSTEP SIX") { THEN("parser throws an error") { REQUIRE_THROWS_WITH(is_valid_construct("DEFINE SIX 6 DEFINE NSTEP SIX"), - Catch::Contains("unexpected INVALID_TOKEN")); + Catch::Matchers::ContainsSubstring("unexpected INVALID_TOKEN")); } } } @@ -73,7 +72,7 @@ SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { WHEN("DEFINE NSTEP 6.0") { THEN("parser throws an exception") { REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP 6.0"), - Catch::Contains("unexpected REAL")); + Catch::Matchers::ContainsSubstring("unexpected REAL")); } } } @@ -82,7 +81,7 @@ SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { WHEN("DEFINE NSTEP") { THEN("parser throws an exception") { REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP"), - Catch::Contains("expecting INTEGER")); + Catch::Matchers::ContainsSubstring("expecting INTEGER")); } } } @@ -91,7 +90,7 @@ SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { WHEN("DEFINE NSTEP SIX") { THEN("parser throws an exception") { REQUIRE_THROWS_WITH(is_valid_construct("DEFINE NSTEP SIX"), - Catch::Contains("expecting INTEGER")); + Catch::Matchers::ContainsSubstring("expecting INTEGER")); } } } @@ -100,7 +99,7 @@ SCENARIO("NMODL can define macros using DEFINE keyword", "[parser]") { WHEN("DEFINE 6") { THEN("parser throws an exception") { REQUIRE_THROWS_WITH(is_valid_construct("DEFINE 6"), - Catch::Contains("expecting NAME")); + Catch::Matchers::ContainsSubstring("expecting NAME")); } } } @@ -162,18 +161,20 @@ SCENARIO("NMODL parser running number of invalid NMODL constructs") { SCENARIO("Check that the parser doesn't crash when passing invalid INCLUDE constructs") { GIVEN("An empty filename") { - REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"\""), Catch::Contains("empty filename")); + REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"\""), + Catch::Matchers::ContainsSubstring("empty filename")); } GIVEN("An missing included file") { REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"unknown.file\""), - Catch::Contains("can not open file : \"unknown.file\"")); + Catch::Matchers::ContainsSubstring( + "can not open file : \"unknown.file\"")); } GIVEN("An invalid included file") { TempFile included("included.file", nmodl_invalid_constructs.at("title_1").input); REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"included.file\""), - Catch::Contains("unexpected End of file")); + Catch::Matchers::ContainsSubstring("unexpected End of file")); } } @@ -188,9 +189,9 @@ SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { GIVEN("Incomplete CURIE information statement") { THEN("parser throws an error") { REQUIRE_THROWS_WITH(is_valid_construct("NEURON { REPRESENTS }"), - Catch::Contains("Lexer Error")); + Catch::Matchers::ContainsSubstring("Lexer Error")); REQUIRE_THROWS_WITH(is_valid_construct("NEURON { REPRESENTS NCIT}"), - Catch::Contains("Lexer Error")); + Catch::Matchers::ContainsSubstring("Lexer Error")); } } } diff --git a/test/nmodl/transpiler/unit/printer/printer.cpp b/test/nmodl/transpiler/unit/printer/printer.cpp index f419bc4acc..0d130c2aa7 100644 --- a/test/nmodl/transpiler/unit/printer/printer.cpp +++ b/test/nmodl/transpiler/unit/printer/printer.cpp @@ -5,11 +5,10 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - +#include #include -#include +#include #include "printer/json_printer.hpp" diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index 0524259f79..2f90a02cf3 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -5,12 +5,11 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include #include -#include +#include +#include #include "ast/float.hpp" #include "ast/program.hpp" @@ -63,9 +62,9 @@ SCENARIO("Symbol properties can be added and converted to string") { NmodlType result = prop1 | prop2 | prop3; result |= NmodlType::write_ion_var; THEN("to_string returns all added properties") { - REQUIRE_THAT(to_string(result), Catch::Contains("local")); - REQUIRE_THAT(to_string(result), Catch::Contains("global")); - REQUIRE_THAT(to_string(result), Catch::Contains("write_ion")); + REQUIRE_THAT(to_string(result), Catch::Matchers::ContainsSubstring("local")); + REQUIRE_THAT(to_string(result), Catch::Matchers::ContainsSubstring("global")); + REQUIRE_THAT(to_string(result), Catch::Matchers::ContainsSubstring("write_ion")); } WHEN("checked for property") { THEN("has all added properties") { @@ -173,9 +172,10 @@ SCENARIO("Symbol table allows operations like insert, lookup") { WHEN("checked methods and member variables") { THEN("all members are initialized") { REQUIRE(table->under_global_scope()); - REQUIRE_THAT(table->name(), Catch::Contains("Na")); - REQUIRE_THAT(table->get_parent_table_name(), Catch::Contains("None")); - REQUIRE_THAT(table->position(), Catch::Contains("UNKNOWN")); + REQUIRE_THAT(table->name(), Catch::Matchers::ContainsSubstring("Na")); + REQUIRE_THAT(table->get_parent_table_name(), + Catch::Matchers::ContainsSubstring("None")); + REQUIRE_THAT(table->position(), Catch::Matchers::ContainsSubstring("UNKNOWN")); } } WHEN("insert symbol") { @@ -189,7 +189,8 @@ SCENARIO("Symbol table allows operations like insert, lookup") { } WHEN("re-inserting the same symbol") { THEN("throws an exception") { - REQUIRE_THROWS_WITH(table->insert(symbol), Catch::Contains("re-insert")); + REQUIRE_THROWS_WITH(table->insert(symbol), + Catch::Matchers::ContainsSubstring("re-insert")); } } WHEN("inserting another symbol") { @@ -295,19 +296,21 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { WHEN("trying to exit scope without entering") { THEN("throws an exception") { - REQUIRE_THROWS_WITH(mod_symtab.leave_scope(), Catch::Contains("without entering")); + REQUIRE_THROWS_WITH(mod_symtab.leave_scope(), + Catch::Matchers::ContainsSubstring("without entering")); } } WHEN("trying to enter scope without valid node") { THEN("throws an exception") { REQUIRE_THROWS_WITH(mod_symtab.enter_scope("scope", nullptr, true, old_symtab), - Catch::Contains("empty node")); + Catch::Matchers::ContainsSubstring("empty node")); } } WHEN("trying to insert without entering scope") { THEN("throws an exception") { auto symbol = std::make_shared("alpha"); - REQUIRE_THROWS_WITH(mod_symtab.insert(symbol), Catch::Contains("Can not insert")); + REQUIRE_THROWS_WITH(mod_symtab.insert(symbol), + Catch::Matchers::ContainsSubstring("Can not insert")); } } WHEN("enter scope multipel times") { @@ -335,7 +338,8 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { mod_symtab.insert(symbol1); mod_symtab.insert(symbol2); THEN("throws an exception") { - REQUIRE_THROWS_WITH(mod_symtab.insert(symbol3), Catch::Contains("Re-declaration")); + REQUIRE_THROWS_WITH(mod_symtab.insert(symbol3), + Catch::Matchers::ContainsSubstring("Re-declaration")); } } WHEN("added same symbol in children scope") { diff --git a/test/nmodl/transpiler/unit/units/lexer.cpp b/test/nmodl/transpiler/unit/units/lexer.cpp index 0e8c88eb67..357931a3ef 100644 --- a/test/nmodl/transpiler/unit/units/lexer.cpp +++ b/test/nmodl/transpiler/unit/units/lexer.cpp @@ -5,11 +5,9 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include -#include +#include #include "lexer/unit_lexer.hpp" #include "parser/unit_driver.hpp" diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index 183691ecca..9e3fb10b01 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -5,12 +5,11 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_MAIN - #include #include -#include +#include +#include #include "config/config.h" #include "parser/diffeq_driver.hpp" @@ -22,7 +21,7 @@ //============================================================================= using namespace nmodl::test_utils; -using Catch::Matchers::Contains; +using Catch::Matchers::ContainsSubstring; // Driver is defined as global to store all the units inserted to it and to be // able to define complex units based on base units @@ -154,7 +153,7 @@ SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { THEN("parser multiply the number by the factor") { std::string parsed_unit{}; REQUIRE_NOTHROW(parsed_unit = parse_string("pew 1 1/milli")); - REQUIRE_THAT(parsed_unit, Contains("pew 0.001: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_unit, ContainsSubstring("pew 0.001: 0 0 0 0 0 0 0 0 0 0")); } } } @@ -192,20 +191,24 @@ SCENARIO("Unit parser accepting dependent/nested units definition", "[unit][pars R2 8314 mV-coul/degC )"; std::string parsed_units = parse_string(reindent_text(units_definitions)); - REQUIRE_THAT(parsed_units, Contains("mV 0.001: 2 1 -2 -1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("mM 1: -3 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("mA 0.001: 0 0 -1 1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("KTOMV 8.53e-05: 2 1 -2 -1 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("B 26: -1 0 0 -1 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy1 0.025: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy2 0.025: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy3 0.025: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy4 -0.025: -2 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("dummy5 0.025: 0 0 0 0 0 0 0 0 0 0")); - REQUIRE_THAT(parsed_units, Contains("newR 8.31446: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("R1 8.314: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("R2 8.314: 2 1 -2 0 0 0 0 0 0 -1")); - REQUIRE_THAT(parsed_units, Contains("m kg sec coul candela dollar bit erlang K")); + REQUIRE_THAT(parsed_units, ContainsSubstring("mV 0.001: 2 1 -2 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, ContainsSubstring("mM 1: -3 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, ContainsSubstring("mA 0.001: 0 0 -1 1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, + ContainsSubstring("KTOMV 8.53e-05: 2 1 -2 -1 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, ContainsSubstring("B 26: -1 0 0 -1 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, ContainsSubstring("dummy1 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, ContainsSubstring("dummy2 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, ContainsSubstring("dummy3 0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, + ContainsSubstring("dummy4 -0.025: -2 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, ContainsSubstring("dummy5 0.025: 0 0 0 0 0 0 0 0 0 0")); + REQUIRE_THAT(parsed_units, + ContainsSubstring("newR 8.31446: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, ContainsSubstring("R1 8.314: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, ContainsSubstring("R2 8.314: 2 1 -2 0 0 0 0 0 0 -1")); + REQUIRE_THAT(parsed_units, + ContainsSubstring("m kg sec coul candela dollar bit erlang K")); } } } diff --git a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp index 9b5a5da7ad..5fc109cfc5 100644 --- a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp +++ b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp index c69c152790..6de0aeb0d7 100644 --- a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 3e31361d72..1612b3b4c7 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -5,7 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include +#include #include "ast/binary_expression.hpp" #include "ast/program.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index b8ce0274d1..0b2024e37a 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp index 8726d6f6d6..1f1ad5cdb3 100644 --- a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp +++ b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp @@ -11,12 +11,13 @@ #include "visitors/symtab_visitor.hpp" #include "visitors/visitor_utils.hpp" -#include +#include +#include using namespace nmodl; using nmodl::test_utils::reindent_text; -using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 +using Catch::Matchers::ContainsSubstring; // ContainsSubstring in newer Catch2 //============================================================================= // Implicit visitor tests @@ -46,15 +47,15 @@ SCENARIO("Check insertion of implicit arguments", "[codegen][implicit_arguments] )"; auto const modified_nmodl = generate_mod_after_implicit_argument_visitor(nmodl_text); THEN("at_time should have nt as its first argument") { - REQUIRE_THAT(modified_nmodl, Contains("at_time(nt, foo)")); - REQUIRE_THAT(modified_nmodl, Contains("at_time(nt, a+b)")); - REQUIRE_THAT(modified_nmodl, Contains("at_time(nt, flop)")); + REQUIRE_THAT(modified_nmodl, ContainsSubstring("at_time(nt, foo)")); + REQUIRE_THAT(modified_nmodl, ContainsSubstring("at_time(nt, a+b)")); + REQUIRE_THAT(modified_nmodl, ContainsSubstring("at_time(nt, flop)")); } THEN("nrn_ghk should have a temperature as its last argument") { REQUIRE_THAT(modified_nmodl, - Contains("nrn_ghk(-50(mV), .001(mM), 10(mM), 2, celsius)")); + ContainsSubstring("nrn_ghk(-50(mV), .001(mM), 10(mM), 2, celsius)")); REQUIRE_THAT(modified_nmodl, - Contains("nrn_ghk(-50(mV), .001(mM), 10(mM), 2, -273.15)")); + ContainsSubstring("nrn_ghk(-50(mV), .001(mM), 10(mM), 2, -273.15)")); } } } diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 6905c3389c..597b98a8e6 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -5,7 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/json.cpp b/test/nmodl/transpiler/unit/visitor/json.cpp index 003f10fd43..ea73e0db31 100644 --- a/test/nmodl/transpiler/unit/visitor/json.cpp +++ b/test/nmodl/transpiler/unit/visitor/json.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index ef9a8f509d..39c0420666 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index 23fa6951de..b7309b08a1 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp index 5e97d89272..15db026588 100644 --- a/test/nmodl/transpiler/unit/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index de6829091d..8254a7f584 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index 60af4bf57a..e5d4fe963b 100644 --- a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp index 9c75cacfd2..60bfd84120 100644 --- a/test/nmodl/transpiler/unit/visitor/main.cpp +++ b/test/nmodl/transpiler/unit/visitor/main.cpp @@ -5,9 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#define CATCH_CONFIG_RUNNER - -#include +#include +#include #include "pybind/pyembed.hpp" #include "utils/logger.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp index 8146e7f378..16ffc3fb60 100644 --- a/test/nmodl/transpiler/unit/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 83b5a648d5..1d1ac45f02 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 263fae1f83..60099ddcee 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/node_index.cpp b/test/nmodl/transpiler/unit/visitor/node_index.cpp index 2053757153..2e8b6beee8 100644 --- a/test/nmodl/transpiler/unit/visitor/node_index.cpp +++ b/test/nmodl/transpiler/unit/visitor/node_index.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/perf.cpp b/test/nmodl/transpiler/unit/visitor/perf.cpp index aada3bf24f..be92597134 100644 --- a/test/nmodl/transpiler/unit/visitor/perf.cpp +++ b/test/nmodl/transpiler/unit/visitor/perf.cpp @@ -5,7 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" @@ -152,7 +153,8 @@ SCENARIO("Symbol table generation with Perf stat visitor", "[visitor][performanc WHEN("Perf visitor pass runs before symtab visitor") { PerfVisitor v; THEN("exception is thrown") { - REQUIRE_THROWS_WITH(v.visit_program(*ast), Catch::Contains("table not setup")); + REQUIRE_THROWS_WITH(v.visit_program(*ast), + Catch::Matchers::ContainsSubstring("table not setup")); } } } diff --git a/test/nmodl/transpiler/unit/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp index b0e47f7028..e548698883 100644 --- a/test/nmodl/transpiler/unit/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 5dc5f1a44f..e546a57e17 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index 3dc19cfc7d..90dc5704be 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index 416e143645..4051f78803 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 1e6d232d95..7590cbfef5 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 6ad2a5c17b..304f7d8125 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -5,7 +5,8 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include +#include #include "ast/program.hpp" #include "codegen/codegen_cpp_visitor.hpp" @@ -29,7 +30,7 @@ using namespace visitor; using namespace test; using namespace test_utils; -using Catch::Matchers::Contains; // ContainsSubstring in newer Catch2 +using Catch::Matchers::ContainsSubstring; // ContainsSubstring in newer Catch2 using nmodl::test_utils::reindent_text; @@ -818,8 +819,9 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", "equations") { REQUIRE_THROWS_WITH( run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::DERIVATIVE_BLOCK), - Catch::Matchers::Contains("State variable assignment(s) interleaved in system of " - "equations/differential equations") && + Catch::Matchers::ContainsSubstring( + "State variable assignment(s) interleaved in system of " + "equations/differential equations") && Catch::Matchers::StartsWith("SympyReplaceSolutionsVisitor")); } } @@ -2412,12 +2414,12 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri std::string expected_functor_cacum_2_usage = R"(functor_cacum_2 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; - REQUIRE_THAT(generated, Contains(expected_functor_cacum_0_definition)); - REQUIRE_THAT(generated, Contains(expected_functor_cacum_1_definition)); - REQUIRE_THAT(generated, Contains(expected_functor_cacum_2_definition)); - REQUIRE_THAT(generated, Contains(expected_functor_cacum_0_usage)); - REQUIRE_THAT(generated, Contains(expected_functor_cacum_1_usage)); - REQUIRE_THAT(generated, Contains(expected_functor_cacum_2_usage)); + REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_0_definition)); + REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_1_definition)); + REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_2_definition)); + REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_0_usage)); + REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_1_usage)); + REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_2_usage)); } } } diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 8543fc2823..d37e79958d 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include #include "ast/double.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/var_usage.cpp b/test/nmodl/transpiler/unit/visitor/var_usage.cpp index 787a2c464d..eadc1cea94 100644 --- a/test/nmodl/transpiler/unit/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/unit/visitor/var_usage.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/verbatim.cpp b/test/nmodl/transpiler/unit/visitor/verbatim.cpp index d2c68f602f..4594cc3860 100644 --- a/test/nmodl/transpiler/unit/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/unit/visitor/verbatim.cpp @@ -5,7 +5,7 @@ * Lesser General Public License. See top-level LICENSE file for details. *************************************************************************/ -#include +#include #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" From 9cc7e7899c1945f514f60ed75680120fb22606f6 Mon Sep 17 00:00:00 2001 From: Olli Lupton Date: Mon, 5 Jun 2023 16:57:21 +0200 Subject: [PATCH 515/871] Compatibility with SoA NEURON data structures (BlueBrain/nmodl#989) * Ion variables: be explicit to match SoA NEURON * Added test for case of "have_ionout && !have_ionin" for coverage Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@2ce4a2b91dfcfe6356b6a5003c4e99b8711564ee --- cmake/nmodl/CMakeLists.txt | 1 + src/nmodl/codegen/codegen_cpp_visitor.cpp | 27 +++++++++++++++++-- src/nmodl/codegen/codegen_info.hpp | 3 +++ .../unit/codegen/codegen_cpp_visitor.cpp | 21 +++++++++++---- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index bb59cb4ff3..9c88dc4e8d 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -161,6 +161,7 @@ endif() # ============================================================================= message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.8 REQUIRED) +cpp_cc_strip_python_shims(EXECUTABLE "${PYTHON_EXECUTABLE}" OUTPUT PYTHON_EXECUTABLE) include(cmake/hpc-coding-conventions/cpp/cmake/bbp-find-python-module.cmake) cpp_cc_find_python_module(jinja2 2.9.3 REQUIRED) cpp_cc_find_python_module(pytest 3.3.0 REQUIRED) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 84288f0706..056042e5ab 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -598,6 +598,10 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { for (const auto& ion: info.ions) { auto name = ion.name; for (const auto& var: ion.reads) { + auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var); + if (iter != ion.implicit_reads.end()) { + continue; + } auto variable_names = read_ion_variable_name(var); auto first = get_variable_name(variable_names.first); auto second = get_variable_name(variable_names.second); @@ -776,6 +780,7 @@ void CodegenCVisitor::update_index_semantics() { } } if (ion.need_style) { + info.semantics.emplace_back(index++, fmt::format("{}_ion", ion.name), 1); info.semantics.emplace_back(index++, fmt::format("#{}_ion", ion.name), 1); } } @@ -900,11 +905,28 @@ std::vector CodegenCVisitor::get_int_variables() { } } - for (const auto& ion: info.ions) { + for (auto& ion: info.ions) { bool need_style = false; std::unordered_map ion_vars; // used to keep track of the variables to // not have doubles between read/write. Same // name variables are allowed + // See if we need to add extra readion statements to match NEURON with SoA data + auto const has_var = [&ion](const char* suffix) -> bool { + auto const pred = [name = ion.name + suffix](auto const& x) { return x == name; }; + return std::any_of(ion.reads.begin(), ion.reads.end(), pred) || + std::any_of(ion.writes.begin(), ion.writes.end(), pred); + }; + auto const add_implicit_read = [&ion](const char* suffix) { + auto name = ion.name + suffix; + ion.reads.push_back(name); + ion.implicit_reads.push_back(std::move(name)); + }; + bool const have_ionin{has_var("i")}, have_ionout{has_var("o")}; + if (have_ionin && !have_ionout) { + add_implicit_read("o"); + } else if (have_ionout && !have_ionin) { + add_implicit_read("i"); + } for (const auto& var: ion.reads) { const std::string name = naming::ION_VARNAME_PREFIX + var; variables.emplace_back(make_symbol(name)); @@ -939,6 +961,7 @@ std::vector CodegenCVisitor::get_int_variables() { } if (need_style) { + variables.emplace_back(make_symbol(naming::ION_VARNAME_PREFIX + ion.name + "_erev")); variables.emplace_back(make_symbol("style_" + ion.name), false, true); variables.back().is_constant = true; } @@ -4296,7 +4319,7 @@ void CodegenCVisitor::print_nrn_state() { print_global_function_common_code(BlockType::State); print_channel_iteration_block_parallel_hint(BlockType::State, info.nrn_state_block); printer->start_block("for (int id = 0; id < nodecount; id++)"); - + printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); print_v_unused(); diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 3602fbfa83..af41ffffeb 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -51,6 +51,9 @@ struct Ion { /// ion variables that are being read std::vector reads; + /// ion variables that are being implicitly read + std::vector implicit_reads; + /// ion variables that are being written std::vector writes; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 80cb62334f..9a41b4a411 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -214,7 +214,8 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { SUFFIX ccanl USEION nca READ ncai, inca, enca WRITE enca, ncai VALENCE 2 USEION lca READ lcai, ilca, elca WRITE elca, lcai VALENCE 2 - RANGE caiinf, catau, cai, ncai, lcai, eca, elca, enca, g + USEION k WRITE ko + RANGE caiinf, catau, cai, ncai, lcai, eca, elca, enca, g, ko } UNITS { FARADAY = 96520(coul) @@ -236,6 +237,7 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { elca(mV) eca(mV) g(S/cm2) + ko(mA / cm2) } STATE { ncai(mM) @@ -266,18 +268,27 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ilca = ml->data+7*pnodecount; inst->enca = ml->data+8*pnodecount; inst->elca = ml->data+9*pnodecount; - inst->ncai = ml->data+10*pnodecount; - inst->Dncai = ml->data+11*pnodecount; - inst->lcai = ml->data+12*pnodecount; - inst->Dlcai = ml->data+13*pnodecount; + inst->ko = ml->data+10*pnodecount; + inst->ncai = ml->data+11*pnodecount; + inst->Dncai = ml->data+12*pnodecount; + inst->lcai = ml->data+13*pnodecount; + inst->Dlcai = ml->data+14*pnodecount; inst->ion_ncai = nt->_data; inst->ion_inca = nt->_data; inst->ion_enca = nt->_data; + inst->ion_ncao = nt->_data; + inst->ion_nca_erev = nt->_data; inst->style_nca = ml->pdata; inst->ion_lcai = nt->_data; inst->ion_ilca = nt->_data; inst->ion_elca = nt->_data; + inst->ion_lcao = nt->_data; + inst->ion_lca_erev = nt->_data; inst->style_lca = ml->pdata; + inst->ion_ki = nt->_data; + inst->ion_ko = nt->_data; + inst->ion_k_erev = nt->_data; + inst->style_k = ml->pdata; } )"; From 8b48be650cd266beb9fbec48d0cbd3b4d07087d5 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 20 Jun 2023 15:47:05 +0200 Subject: [PATCH 516/871] Keep myst-parser at 1.0.0 (BlueBrain/nmodl#1049) The 2.0.0 has been release but need a sphinx >= 6, we still stick on sphinx 5 for sphinxcontrib-* NMODL Repo SHA: BlueBrain/nmodl@ce25655088cb082b5c0c9dc9cc9a423739799806 --- setup.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index c35ae3fc1d..387dadd4e0 100644 --- a/setup.py +++ b/setup.py @@ -113,19 +113,22 @@ def run(self, *args, **kwargs): docs=Docs, doctest=get_sphinx_command, buildhtml=get_sphinx_command, ), zip_safe=False, + # myst_parser 2.0.0 want sphinx >= 6 but it leads to incompatibily with sphinxcontrib-applehelp and + # sphinxcontrib-htmlhelp that want PEP-517 support instead of setup.py (name clash with '-' and '.') + # So we pin low versions of packages setup_requires=[ "jinja2>=2.9.3", "jupyter-client", "jupyter", - "myst_parser", - "mistune<3", # prevents a version conflict with nbconvert + "myst_parser<2.0.0", + "mistune<3", "nbconvert", "nbsphinx>=0.3.2", "pytest>=3.7.2", - "sphinxcontrib-applehelp<1.0.3", - "sphinxcontrib-htmlhelp<=2.0.0", + "sphinxcontrib-applehelp<1.0.3", # After this version it needs a toml file to work, no more setup.py + "sphinxcontrib-htmlhelp<=2.0.0", # After this version it needs a toml file to work, no more setup.py "sphinx<6", - "sphinx-rtd-theme", + "sphinx-rtd-theme", # needs sphinx < 7 ] + install_requirements, install_requires=install_requirements, From 1ddcd6dadec465b8a049f558e6725e764549ab8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexandru=20S=C4=83vulescu?= Date: Mon, 3 Jul 2023 13:02:08 +0200 Subject: [PATCH 517/871] fmt: check if target already exists (BlueBrain/nmodl#1051) NMODL Repo SHA: BlueBrain/nmodl@2135b19de96956faf21485a040032b69f8793d56 --- cmake/nmodl/CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9c88dc4e8d..6f7c7392e2 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -107,7 +107,10 @@ if(NOT TARGET CLI11::CLI11) endif() # For the moment do not try and use an external Eigen. cpp_cc_git_submodule(eigen) -cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED) +# We could have fmt incoming from NEURON +if(NOT TARGET fmt) + cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED) +endif() # If we're building from the submodule, make sure we pass -fPIC so that we can link the code into a # shared library later. if(NMODL_3RDPARTY_USE_FMT) From f8412a1da130c1e90d6c67d8cf547909ebdc00c6 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 7 Jul 2023 20:51:03 +0200 Subject: [PATCH 518/871] Relicense with Apache License Version 2.0, January 2004 (BlueBrain/nmodl#1048) NMODL Repo SHA: BlueBrain/nmodl@a1a035ca63d1062ec22d12dabaacb538be68ebde --- LGPL.txt | 165 --------------- README.md | 2 +- cmake/nmodl/CMakeLists.txt | 7 +- cmake/nmodl/RpathHelper.cmake | 7 +- setup.py | 8 +- src/nmodl/ast/ast_common.hpp | 10 +- src/nmodl/codegen/codegen_acc_visitor.cpp | 10 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 10 +- .../codegen/codegen_compatibility_visitor.cpp | 10 +- .../codegen/codegen_compatibility_visitor.hpp | 10 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 10 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 10 +- src/nmodl/codegen/codegen_helper_visitor.cpp | 10 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 10 +- src/nmodl/codegen/codegen_info.cpp | 10 +- src/nmodl/codegen/codegen_info.hpp | 10 +- src/nmodl/codegen/codegen_naming.hpp | 10 +- .../codegen/codegen_transform_visitor.cpp | 10 +- .../codegen/codegen_transform_visitor.hpp | 10 +- src/nmodl/codegen/codegen_utils.cpp | 10 +- src/nmodl/codegen/codegen_utils.hpp | 10 +- src/nmodl/config/config.cpp.in | 10 +- src/nmodl/config/config.h | 10 +- src/nmodl/language/argument.py | 8 +- src/nmodl/language/code_generator.py | 8 +- src/nmodl/language/codegen.yaml | 10 +- src/nmodl/language/nmodl.yaml | 8 +- src/nmodl/language/node_info.py | 8 +- src/nmodl/language/nodes.py | 8 +- src/nmodl/language/parser.py | 8 +- src/nmodl/language/templates/ast/all.hpp | 10 +- src/nmodl/language/templates/ast/ast.cpp | 10 +- src/nmodl/language/templates/ast/ast.hpp | 10 +- src/nmodl/language/templates/ast/ast_decl.hpp | 10 +- src/nmodl/language/templates/ast/node.hpp | 10 +- src/nmodl/language/templates/pybind/pyast.cpp | 10 +- src/nmodl/language/templates/pybind/pyast.hpp | 10 +- .../language/templates/pybind/pynode.cpp | 10 +- .../language/templates/pybind/pysymtab.cpp | 10 +- .../language/templates/pybind/pyvisitor.cpp | 10 +- .../language/templates/pybind/pyvisitor.hpp | 10 +- .../templates/visitors/ast_visitor.cpp | 10 +- .../templates/visitors/ast_visitor.hpp | 10 +- .../visitors/checkparent_visitor.cpp | 10 +- .../visitors/checkparent_visitor.hpp | 10 +- .../templates/visitors/json_visitor.cpp | 10 +- .../templates/visitors/json_visitor.hpp | 10 +- .../templates/visitors/lookup_visitor.cpp | 10 +- .../templates/visitors/lookup_visitor.hpp | 10 +- .../templates/visitors/nmodl_visitor.cpp | 10 +- .../templates/visitors/nmodl_visitor.hpp | 10 +- .../templates/visitors/symtab_visitor.cpp | 10 +- .../templates/visitors/symtab_visitor.hpp | 10 +- .../language/templates/visitors/visitor.hpp | 10 +- src/nmodl/language/utils.py | 8 +- src/nmodl/lexer/c11.ll | 15 +- src/nmodl/lexer/c11_lexer.hpp | 10 +- src/nmodl/lexer/diffeq.ll | 13 +- src/nmodl/lexer/diffeq_lexer.hpp | 10 +- src/nmodl/lexer/main_c.cpp | 10 +- src/nmodl/lexer/main_nmodl.cpp | 10 +- src/nmodl/lexer/main_units.cpp | 11 +- src/nmodl/lexer/modl.h | 10 +- src/nmodl/lexer/modtoken.cpp | 10 +- src/nmodl/lexer/modtoken.hpp | 10 +- src/nmodl/lexer/nmodl.ll | 13 +- src/nmodl/lexer/nmodl_lexer.hpp | 10 +- src/nmodl/lexer/nmodl_utils.cpp | 10 +- src/nmodl/lexer/nmodl_utils.hpp | 10 +- src/nmodl/lexer/token_mapping.cpp | 10 +- src/nmodl/lexer/token_mapping.hpp | 10 +- src/nmodl/lexer/unit_lexer.hpp | 10 +- src/nmodl/lexer/verbatim.l | 12 +- src/nmodl/main.cpp | 10 +- src/nmodl/nmodl.hpp | 10 +- src/nmodl/nmodl/LICENSE | 194 ++++++++++++++++-- src/nmodl/parser/c11_driver.cpp | 10 +- src/nmodl/parser/c11_driver.hpp | 10 +- src/nmodl/parser/diffeq.yy | 11 +- src/nmodl/parser/diffeq_context.cpp | 10 +- src/nmodl/parser/diffeq_context.hpp | 10 +- src/nmodl/parser/diffeq_driver.cpp | 10 +- src/nmodl/parser/diffeq_driver.hpp | 10 +- src/nmodl/parser/diffeq_helper.hpp | 10 +- src/nmodl/parser/main_c.cpp | 10 +- src/nmodl/parser/main_nmodl.cpp | 11 +- src/nmodl/parser/main_units.cpp | 10 +- src/nmodl/parser/nmodl.yy | 11 +- src/nmodl/parser/nmodl_driver.cpp | 10 +- src/nmodl/parser/nmodl_driver.hpp | 10 +- src/nmodl/parser/unit_driver.cpp | 10 +- src/nmodl/parser/unit_driver.hpp | 10 +- src/nmodl/parser/verbatim.yy | 10 +- src/nmodl/parser/verbatim_driver.hpp | 10 +- src/nmodl/printer/code_printer.cpp | 10 +- src/nmodl/printer/code_printer.hpp | 10 +- src/nmodl/printer/decl.hpp | 10 +- src/nmodl/printer/json_printer.cpp | 10 +- src/nmodl/printer/json_printer.hpp | 10 +- src/nmodl/printer/nmodl_printer.cpp | 10 +- src/nmodl/printer/nmodl_printer.hpp | 10 +- src/nmodl/pybind/docstrings.hpp | 11 +- src/nmodl/pybind/pybind_utils.hpp | 10 +- src/nmodl/pybind/pyembed.cpp | 10 +- src/nmodl/pybind/pyembed.hpp | 11 +- src/nmodl/pybind/pynmodl.cpp | 11 +- src/nmodl/pybind/wrapper.cpp | 11 +- src/nmodl/solver/crout/crout.hpp | 11 +- src/nmodl/solver/newton/newton.hpp | 10 +- src/nmodl/symtab/decl.hpp | 10 +- src/nmodl/symtab/symbol.cpp | 10 +- src/nmodl/symtab/symbol.hpp | 10 +- src/nmodl/symtab/symbol_properties.cpp | 10 +- src/nmodl/symtab/symbol_properties.hpp | 10 +- src/nmodl/symtab/symbol_table.cpp | 10 +- src/nmodl/symtab/symbol_table.hpp | 10 +- src/nmodl/units/units.cpp | 11 +- src/nmodl/units/units.hpp | 10 +- src/nmodl/utils/common_utils.cpp | 10 +- src/nmodl/utils/common_utils.hpp | 10 +- src/nmodl/utils/file_library.cpp | 10 +- src/nmodl/utils/file_library.hpp | 10 +- src/nmodl/utils/logger.cpp | 10 +- src/nmodl/utils/logger.hpp | 10 +- src/nmodl/utils/perf_stat.cpp | 10 +- src/nmodl/utils/perf_stat.hpp | 10 +- src/nmodl/utils/string_utils.cpp | 11 +- src/nmodl/utils/string_utils.hpp | 10 +- src/nmodl/utils/table_data.cpp | 10 +- src/nmodl/utils/table_data.hpp | 10 +- .../visitors/after_cvode_to_cnexp_visitor.cpp | 10 +- .../visitors/after_cvode_to_cnexp_visitor.hpp | 10 +- .../visitors/constant_folder_visitor.cpp | 10 +- .../visitors/constant_folder_visitor.hpp | 10 +- src/nmodl/visitors/defuse_analyze_visitor.cpp | 10 +- src/nmodl/visitors/defuse_analyze_visitor.hpp | 10 +- src/nmodl/visitors/global_var_visitor.cpp | 10 +- src/nmodl/visitors/global_var_visitor.hpp | 10 +- .../visitors/implicit_argument_visitor.cpp | 11 +- .../visitors/implicit_argument_visitor.hpp | 11 +- src/nmodl/visitors/indexedname_visitor.cpp | 10 +- src/nmodl/visitors/indexedname_visitor.hpp | 10 +- src/nmodl/visitors/inline_visitor.cpp | 10 +- src/nmodl/visitors/inline_visitor.hpp | 10 +- src/nmodl/visitors/kinetic_block_visitor.cpp | 10 +- src/nmodl/visitors/kinetic_block_visitor.hpp | 10 +- .../visitors/local_to_assigned_visitor.cpp | 10 +- .../visitors/local_to_assigned_visitor.hpp | 10 +- .../visitors/local_var_rename_visitor.cpp | 10 +- .../visitors/local_var_rename_visitor.hpp | 10 +- src/nmodl/visitors/localize_visitor.cpp | 10 +- src/nmodl/visitors/localize_visitor.hpp | 10 +- src/nmodl/visitors/loop_unroll_visitor.cpp | 10 +- src/nmodl/visitors/loop_unroll_visitor.hpp | 10 +- src/nmodl/visitors/main.cpp | 10 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 10 +- src/nmodl/visitors/neuron_solve_visitor.hpp | 10 +- src/nmodl/visitors/nmodl_visitor_helper.ipp | 10 +- src/nmodl/visitors/perf_visitor.cpp | 10 +- src/nmodl/visitors/perf_visitor.hpp | 10 +- src/nmodl/visitors/rename_visitor.cpp | 10 +- src/nmodl/visitors/rename_visitor.hpp | 10 +- .../visitors/semantic_analysis_visitor.cpp | 7 + .../visitors/semantic_analysis_visitor.hpp | 10 +- src/nmodl/visitors/solve_block_visitor.cpp | 10 +- src/nmodl/visitors/solve_block_visitor.hpp | 10 +- src/nmodl/visitors/steadystate_visitor.cpp | 10 +- src/nmodl/visitors/steadystate_visitor.hpp | 10 +- .../visitors/sympy_conductance_visitor.cpp | 10 +- .../visitors/sympy_conductance_visitor.hpp | 10 +- .../sympy_replace_solutions_visitor.cpp | 10 +- .../sympy_replace_solutions_visitor.hpp | 10 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 10 +- src/nmodl/visitors/sympy_solver_visitor.hpp | 10 +- src/nmodl/visitors/symtab_visitor_helper.hpp | 10 +- src/nmodl/visitors/units_visitor.cpp | 10 +- src/nmodl/visitors/units_visitor.hpp | 10 +- src/nmodl/visitors/var_usage_visitor.cpp | 10 +- src/nmodl/visitors/var_usage_visitor.hpp | 10 +- .../visitors/verbatim_var_rename_visitor.cpp | 10 +- .../visitors/verbatim_var_rename_visitor.hpp | 10 +- src/nmodl/visitors/verbatim_visitor.cpp | 10 +- src/nmodl/visitors/verbatim_visitor.hpp | 10 +- src/nmodl/visitors/visitor_utils.cpp | 10 +- src/nmodl/visitors/visitor_utils.hpp | 10 +- .../codegen/codegen_compatibility_visitor.cpp | 10 +- .../unit/codegen/codegen_cpp_visitor.cpp | 10 +- .../unit/codegen/codegen_helper.cpp | 10 +- .../transpiler/unit/codegen/codegen_utils.cpp | 10 +- test/nmodl/transpiler/unit/codegen/main.cpp | 10 +- .../transpiler/unit/codegen/transform.cpp | 10 +- test/nmodl/transpiler/unit/crout/crout.cpp | 10 +- test/nmodl/transpiler/unit/lexer/tokens.cpp | 10 +- .../transpiler/unit/modtoken/modtoken.cpp | 10 +- test/nmodl/transpiler/unit/newton/newton.cpp | 10 +- test/nmodl/transpiler/unit/ode/test_ode.py | 8 +- test/nmodl/transpiler/unit/parser/parser.cpp | 10 +- .../nmodl/transpiler/unit/printer/printer.cpp | 10 +- test/nmodl/transpiler/unit/pybind/conftest.py | 8 +- test/nmodl/transpiler/unit/pybind/test_ast.py | 8 +- .../transpiler/unit/pybind/test_symtab.py | 8 +- .../transpiler/unit/pybind/test_visitor.py | 8 +- .../transpiler/unit/symtab/symbol_table.cpp | 10 +- test/nmodl/transpiler/unit/units/lexer.cpp | 10 +- test/nmodl/transpiler/unit/units/parser.cpp | 10 +- .../unit/utils/nmodl_constructs.cpp | 10 +- .../unit/utils/nmodl_constructs.hpp | 10 +- .../transpiler/unit/utils/test_utils.cpp | 10 +- .../transpiler/unit/utils/test_utils.hpp | 10 +- .../unit/visitor/after_cvode_to_cnexp.cpp | 10 +- .../unit/visitor/constant_folder.cpp | 10 +- .../unit/visitor/defuse_analyze.cpp | 10 +- .../unit/visitor/global_to_range.cpp | 10 +- .../unit/visitor/implicit_argument.cpp | 11 +- test/nmodl/transpiler/unit/visitor/inline.cpp | 10 +- test/nmodl/transpiler/unit/visitor/json.cpp | 10 +- .../transpiler/unit/visitor/kinetic_block.cpp | 10 +- .../unit/visitor/local_to_assigned.cpp | 10 +- .../transpiler/unit/visitor/localize.cpp | 10 +- test/nmodl/transpiler/unit/visitor/lookup.cpp | 10 +- .../transpiler/unit/visitor/loop_unroll.cpp | 10 +- test/nmodl/transpiler/unit/visitor/main.cpp | 10 +- test/nmodl/transpiler/unit/visitor/misc.cpp | 10 +- .../transpiler/unit/visitor/neuron_solve.cpp | 10 +- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 10 +- .../transpiler/unit/visitor/node_index.cpp | 10 +- test/nmodl/transpiler/unit/visitor/perf.cpp | 10 +- test/nmodl/transpiler/unit/visitor/rename.cpp | 10 +- .../unit/visitor/semantic_analysis.cpp | 10 +- .../transpiler/unit/visitor/solve_block.cpp | 10 +- .../transpiler/unit/visitor/steadystate.cpp | 10 +- .../unit/visitor/sympy_conductance.cpp | 10 +- .../transpiler/unit/visitor/sympy_solver.cpp | 10 +- test/nmodl/transpiler/unit/visitor/units.cpp | 10 +- .../transpiler/unit/visitor/var_usage.cpp | 10 +- .../transpiler/unit/visitor/verbatim.cpp | 10 +- 236 files changed, 1321 insertions(+), 1362 deletions(-) delete mode 100644 LGPL.txt diff --git a/LGPL.txt b/LGPL.txt deleted file mode 100644 index 65c5ca88a6..0000000000 --- a/LGPL.txt +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/README.md b/README.md index c8c03ec144..2f6b9d6d1f 100644 --- a/README.md +++ b/README.md @@ -286,4 +286,4 @@ The benchmarks used to test the performance and parsing capabilities of NMODL Fr The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government's ETH Board of the Swiss Federal Institutes of Technology. In addition, the development was supported by funding from the National Institutes of Health (NIH) under the Grant Number R01NS11613 (Yale University) and the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 785907 (Human Brain Project SGA2). -Copyright © 2017-2022 Blue Brain Project/EPFL +Copyright © 2017-2023 Blue Brain Project, EPFL diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 6f7c7392e2..744e74d67f 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -1,9 +1,6 @@ -# ============================================================================= -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. -# See top-level LICENSE file for details. -# ============================================================================= +# SPDX-License-Identifier: Apache-2.0 cmake_minimum_required(VERSION 3.15 FATAL_ERROR) diff --git a/cmake/nmodl/RpathHelper.cmake b/cmake/nmodl/RpathHelper.cmake index a008744a1a..c461c48e6e 100644 --- a/cmake/nmodl/RpathHelper.cmake +++ b/cmake/nmodl/RpathHelper.cmake @@ -1,9 +1,6 @@ -# ============================================================================= -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU Lesser General Public License. -# See top-level LICENSE file for details. -# ============================================================================= +# SPDX-License-Identifier: Apache-2.0 # ============================================================================= # Set full RPATHs in build-tree, also set RPATHs in install for non-system libs diff --git a/setup.py b/setup.py index 387dadd4e0..bf2f61458f 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 import inspect import os diff --git a/src/nmodl/ast/ast_common.hpp b/src/nmodl/ast/ast_common.hpp index 956dbc6c19..96fd6a7f7d 100644 --- a/src/nmodl/ast/ast_common.hpp +++ b/src/nmodl/ast/ast_common.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 87b54e68d1..4ca99d3aa2 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "codegen/codegen_acc_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 3bd221a870..e48c83f092 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 89076df697..f2230c15b2 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "codegen/codegen_compatibility_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 4894205c2c..140b5acb06 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 056042e5ab..e57647301c 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "codegen/codegen_cpp_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 6a47849728..6ab2f3cb0b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 5473213b1b..a8ccea09ae 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "codegen/codegen_helper_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 1031535d53..b960bdc06f 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index fb96b26e59..874f3bfe4d 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "codegen/codegen_info.hpp" diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index af41ffffeb..23a4dcc3a9 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 9739285bc4..3ef38a1c4e 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_transform_visitor.cpp b/src/nmodl/codegen/codegen_transform_visitor.cpp index aed51cbd87..f06f5fbd35 100644 --- a/src/nmodl/codegen/codegen_transform_visitor.cpp +++ b/src/nmodl/codegen/codegen_transform_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/codegen/codegen_transform_visitor.hpp b/src/nmodl/codegen/codegen_transform_visitor.hpp index 08dd1142eb..8f38474497 100644 --- a/src/nmodl/codegen/codegen_transform_visitor.hpp +++ b/src/nmodl/codegen/codegen_transform_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index 9f1fec3ea4..a41bb019f2 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "codegen/codegen_utils.hpp" diff --git a/src/nmodl/codegen/codegen_utils.hpp b/src/nmodl/codegen/codegen_utils.hpp index 8b5a01a6cf..0180b9ec3d 100644 --- a/src/nmodl/codegen/codegen_utils.hpp +++ b/src/nmodl/codegen/codegen_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/config/config.cpp.in b/src/nmodl/config/config.cpp.in index 3abdda0290..09a4da97c7 100644 --- a/src/nmodl/config/config.cpp.in +++ b/src/nmodl/config/config.cpp.in @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "config/config.h" diff --git a/src/nmodl/config/config.h b/src/nmodl/config/config.h index 7173672ce6..df5d7d379a 100644 --- a/src/nmodl/config/config.h +++ b/src/nmodl/config/config.h @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 18b26ab779..8b8f7ad594 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 class Argument: """Utility class for holding all arguments for node classes""" diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index aac339d691..14de94ee28 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 import argparse import collections diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 5511d14478..477df7fa65 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -1,9 +1,7 @@ -# ********************************************************************* -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# ********************************************************************* +# SPDX-License-Identifier: Apache-2.0 ######################### NMODL Abstract Language Definition ############################## # @@ -120,4 +118,4 @@ brief: "Value of new timestep" type: Double prefix: {value: " = "} - brief: "Statement to indicate a change in timestep in a given block" \ No newline at end of file + brief: "Statement to indicate a change in timestep in a given block" diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index f648e43e17..e2686fd9f8 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1,9 +1,7 @@ -# ********************************************************************* -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# ********************************************************************* +# SPDX-License-Identifier: Apache-2.0 ######################### NMODL Abstract Language Definition ############################## # diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index a491842357..0a719a592e 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 """Define node and data types used in parser and ast generator diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 88ad1bb000..ff1037f691 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 """Define basic classes used from parser diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/parser.py index b7626e82ea..df7d49aff9 100644 --- a/src/nmodl/language/parser.py +++ b/src/nmodl/language/parser.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 """Parser for parsing abstract NMODL language definition file diff --git a/src/nmodl/language/templates/ast/all.hpp b/src/nmodl/language/templates/ast/all.hpp index 6033652490..25719e0f44 100644 --- a/src/nmodl/language/templates/ast/all.hpp +++ b/src/nmodl/language/templates/ast/all.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 51a2139e81..87f5399540 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index b1cc75f6b0..0845807948 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/ast/ast_decl.hpp b/src/nmodl/language/templates/ast/ast_decl.hpp index a6bdae69a1..257193a89c 100644 --- a/src/nmodl/language/templates/ast/ast_decl.hpp +++ b/src/nmodl/language/templates/ast/ast_decl.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/ast/node.hpp b/src/nmodl/language/templates/ast/node.hpp index 277d03277a..b90272f998 100644 --- a/src/nmodl/language/templates/ast/node.hpp +++ b/src/nmodl/language/templates/ast/node.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/pybind/pyast.cpp b/src/nmodl/language/templates/pybind/pyast.cpp index 95601bed5c..8149dd6a97 100644 --- a/src/nmodl/language/templates/pybind/pyast.cpp +++ b/src/nmodl/language/templates/pybind/pyast.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index d54ccf95d9..f2fa13eb14 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/pybind/pynode.cpp b/src/nmodl/language/templates/pybind/pynode.cpp index a2069b30e9..9e04836560 100644 --- a/src/nmodl/language/templates/pybind/pynode.cpp +++ b/src/nmodl/language/templates/pybind/pynode.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/pybind/pysymtab.cpp b/src/nmodl/language/templates/pybind/pysymtab.cpp index cf070692d1..9e835d4b27 100644 --- a/src/nmodl/language/templates/pybind/pysymtab.cpp +++ b/src/nmodl/language/templates/pybind/pysymtab.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/pybind/pyvisitor.cpp b/src/nmodl/language/templates/pybind/pyvisitor.cpp index 115b6a7f15..459cc5ce6b 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.cpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/pybind/pyvisitor.hpp b/src/nmodl/language/templates/pybind/pyvisitor.hpp index 0d3832fa75..ebe944d1f6 100644 --- a/src/nmodl/language/templates/pybind/pyvisitor.hpp +++ b/src/nmodl/language/templates/pybind/pyvisitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/ast_visitor.cpp b/src/nmodl/language/templates/visitors/ast_visitor.cpp index bcac22a0ce..8daa95ed5f 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.cpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/ast_visitor.hpp b/src/nmodl/language/templates/visitors/ast_visitor.hpp index 483fca2ad9..a23bdad9ce 100644 --- a/src/nmodl/language/templates/visitors/ast_visitor.hpp +++ b/src/nmodl/language/templates/visitors/ast_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp index 32c205ead9..380905b14f 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.cpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp index a849d299ca..90a2abd8b5 100644 --- a/src/nmodl/language/templates/visitors/checkparent_visitor.hpp +++ b/src/nmodl/language/templates/visitors/checkparent_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/json_visitor.cpp b/src/nmodl/language/templates/visitors/json_visitor.cpp index 11bd9cf556..485f218e08 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.cpp +++ b/src/nmodl/language/templates/visitors/json_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/json_visitor.hpp b/src/nmodl/language/templates/visitors/json_visitor.hpp index 9216156b3c..d1d24381eb 100644 --- a/src/nmodl/language/templates/visitors/json_visitor.hpp +++ b/src/nmodl/language/templates/visitors/json_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.cpp b/src/nmodl/language/templates/visitors/lookup_visitor.cpp index 2bc3a877ab..64011ceea4 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.cpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/lookup_visitor.hpp b/src/nmodl/language/templates/visitors/lookup_visitor.hpp index a2f959315a..8744bb912c 100644 --- a/src/nmodl/language/templates/visitors/lookup_visitor.hpp +++ b/src/nmodl/language/templates/visitors/lookup_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 9c60bf8f87..8ee653b8b0 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index e1bdc56a4f..52a4efb2fe 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.cpp b/src/nmodl/language/templates/visitors/symtab_visitor.cpp index bf97de0e60..804b54ee47 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.cpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/symtab_visitor.hpp b/src/nmodl/language/templates/visitors/symtab_visitor.hpp index 7d5b06fe6d..c70d702a0a 100644 --- a/src/nmodl/language/templates/visitors/symtab_visitor.hpp +++ b/src/nmodl/language/templates/visitors/symtab_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/templates/visitors/visitor.hpp b/src/nmodl/language/templates/visitors/visitor.hpp index 0e5e0b233c..7efa4e1572 100644 --- a/src/nmodl/language/templates/visitors/visitor.hpp +++ b/src/nmodl/language/templates/visitors/visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /// /// THIS FILE IS GENERATED AT BUILD TIME AND SHALL NOT BE EDITED. diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index e5ab9671e1..9aeb04d6c4 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 import re diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index eec87885c1..78baf95fbc 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -1,14 +1,9 @@ -/********************************************************************************** - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - * - * CREDIT : This is based on flex specification available at - * http://www.quut.com/c/ANSI-C-grammar-l-2011.html - * - * @brief C (11) lexer based - *****************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ %{ #include diff --git a/src/nmodl/lexer/c11_lexer.hpp b/src/nmodl/lexer/c11_lexer.hpp index 680d3f17d4..b95c1baf95 100644 --- a/src/nmodl/lexer/c11_lexer.hpp +++ b/src/nmodl/lexer/c11_lexer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/diffeq.ll b/src/nmodl/lexer/diffeq.ll index a5f19d0314..776462b0b1 100755 --- a/src/nmodl/lexer/diffeq.ll +++ b/src/nmodl/lexer/diffeq.ll @@ -1,12 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * Copyright (C) 2018-2022 Michael Hines +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - * - * @brief Lexer for ODEs from NMODL - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ %{ #include diff --git a/src/nmodl/lexer/diffeq_lexer.hpp b/src/nmodl/lexer/diffeq_lexer.hpp index 2d78e73948..bf6202aac7 100644 --- a/src/nmodl/lexer/diffeq_lexer.hpp +++ b/src/nmodl/lexer/diffeq_lexer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index ad5a57ffe3..8788af66da 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index aab31e23db..600570b02c 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/lexer/main_units.cpp b/src/nmodl/lexer/main_units.cpp index dd194fee4c..64c817fb0f 100644 --- a/src/nmodl/lexer/main_units.cpp +++ b/src/nmodl/lexer/main_units.cpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #include "config/config.h" #include "lexer/unit_lexer.hpp" #include "parser/unit_driver.hpp" diff --git a/src/nmodl/lexer/modl.h b/src/nmodl/lexer/modl.h index 6f3e514ca1..2162159779 100644 --- a/src/nmodl/lexer/modl.h +++ b/src/nmodl/lexer/modl.h @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/modtoken.cpp b/src/nmodl/lexer/modtoken.cpp index 2f6c045685..3f46772340 100644 --- a/src/nmodl/lexer/modtoken.cpp +++ b/src/nmodl/lexer/modtoken.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "lexer/modtoken.hpp" diff --git a/src/nmodl/lexer/modtoken.hpp b/src/nmodl/lexer/modtoken.hpp index 41365473ea..4bb60d2295 100644 --- a/src/nmodl/lexer/modtoken.hpp +++ b/src/nmodl/lexer/modtoken.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 4b274375e3..77e0bb8fb4 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -1,12 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * Copyright (C) 2018-2022 Michael Hines +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - * - * @brief Lexer for NMODL specification - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ %{ diff --git a/src/nmodl/lexer/nmodl_lexer.hpp b/src/nmodl/lexer/nmodl_lexer.hpp index c7d31c1fe7..e89db2a04c 100644 --- a/src/nmodl/lexer/nmodl_lexer.hpp +++ b/src/nmodl/lexer/nmodl_lexer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 72695d9743..ece525c07c 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/lexer/nmodl_utils.hpp b/src/nmodl/lexer/nmodl_utils.hpp index ae1ae2b01f..a675cd75d1 100644 --- a/src/nmodl/lexer/nmodl_utils.hpp +++ b/src/nmodl/lexer/nmodl_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index 632a0e3727..c3c0fb116f 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/lexer/token_mapping.hpp b/src/nmodl/lexer/token_mapping.hpp index 6f1fb3255f..97506ac0b1 100644 --- a/src/nmodl/lexer/token_mapping.hpp +++ b/src/nmodl/lexer/token_mapping.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/unit_lexer.hpp b/src/nmodl/lexer/unit_lexer.hpp index eab16d53c2..c7651c3137 100644 --- a/src/nmodl/lexer/unit_lexer.hpp +++ b/src/nmodl/lexer/unit_lexer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index 315877a37b..d2e13764cb 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -1,11 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - * - * @brief Lexer for verbatim constructs from NMODL (e.g. VERBATIM, COMMENT) - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ %{ #include diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 1dbcc857f2..c0495b8325 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/nmodl.hpp b/src/nmodl/nmodl.hpp index a0b2271734..d51c02e604 100644 --- a/src/nmodl/nmodl.hpp +++ b/src/nmodl/nmodl.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/nmodl/LICENSE b/src/nmodl/nmodl/LICENSE index 8cff9eceff..d9a10c0d8e 100644 --- a/src/nmodl/nmodl/LICENSE +++ b/src/nmodl/nmodl/LICENSE @@ -1,18 +1,176 @@ -********************************************************************************* -* NMODL - NEURON Modeling Language Code Generation Framework -* -* Copyright (c) 2019-2022, Blue Brain Project, EPFL. -* -* NMODL is licensed under the LGPL, unless noted otherwise, e.g., for external -* dependencies. See file LGPL.txt for the full license. Examples and external -* dependencies are either LGPL or BSD-licensed. -* -* THE SOFTWARE IS PROVIDED _AS IS_, WITHOUT WARRANTY OF ANY KIND, EXPRESS -* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -* DEALINGS IN THE SOFTWARE. -* -*********************************************************************************/ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/src/nmodl/parser/c11_driver.cpp b/src/nmodl/parser/c11_driver.cpp index e2bd9a3dc7..a48799d7d8 100644 --- a/src/nmodl/parser/c11_driver.cpp +++ b/src/nmodl/parser/c11_driver.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/parser/c11_driver.hpp b/src/nmodl/parser/c11_driver.hpp index eb02d17dbf..555f6a5050 100644 --- a/src/nmodl/parser/c11_driver.hpp +++ b/src/nmodl/parser/c11_driver.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/parser/diffeq.yy b/src/nmodl/parser/diffeq.yy index c75a916cbd..60fad8a7a8 100644 --- a/src/nmodl/parser/diffeq.yy +++ b/src/nmodl/parser/diffeq.yy @@ -1,10 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * Copyright (C) 2018-2022 Michael Hines +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /********************************************************************************** * diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 5c3a9f6963..298b2185d9 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/parser/diffeq_context.hpp b/src/nmodl/parser/diffeq_context.hpp index 6fc6c13693..08fde3a300 100644 --- a/src/nmodl/parser/diffeq_context.hpp +++ b/src/nmodl/parser/diffeq_context.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 05c42b4b7a..686eb45020 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/parser/diffeq_driver.hpp b/src/nmodl/parser/diffeq_driver.hpp index bb362963a3..2162cf41c9 100644 --- a/src/nmodl/parser/diffeq_driver.hpp +++ b/src/nmodl/parser/diffeq_driver.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/parser/diffeq_helper.hpp b/src/nmodl/parser/diffeq_helper.hpp index 0ae5a2f31e..c04111d788 100644 --- a/src/nmodl/parser/diffeq_helper.hpp +++ b/src/nmodl/parser/diffeq_helper.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index 6dd2154c36..decc6268cd 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/parser/main_nmodl.cpp b/src/nmodl/parser/main_nmodl.cpp index 87b1ac7c4c..aefd58df45 100644 --- a/src/nmodl/parser/main_nmodl.cpp +++ b/src/nmodl/parser/main_nmodl.cpp @@ -1,10 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/parser/main_units.cpp b/src/nmodl/parser/main_units.cpp index 4f10512dfa..6c6966aae0 100644 --- a/src/nmodl/parser/main_units.cpp +++ b/src/nmodl/parser/main_units.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 344aa4ad63..8d9b7bd5fb 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1,10 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project - * Copyright (C) 2018-2022 Michael Hines +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /********************************************************************************** * diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 37b3979851..63f50cc3a5 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 6466d750f1..217879b053 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/parser/unit_driver.cpp b/src/nmodl/parser/unit_driver.cpp index 23d6a15340..5f709b6398 100644 --- a/src/nmodl/parser/unit_driver.cpp +++ b/src/nmodl/parser/unit_driver.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/parser/unit_driver.hpp b/src/nmodl/parser/unit_driver.hpp index 4e9c265993..31f882e731 100644 --- a/src/nmodl/parser/unit_driver.hpp +++ b/src/nmodl/parser/unit_driver.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/parser/verbatim.yy b/src/nmodl/parser/verbatim.yy index fdfdcd0778..635298316e 100644 --- a/src/nmodl/parser/verbatim.yy +++ b/src/nmodl/parser/verbatim.yy @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ /** * Bison specification for NMODL Extensions which includes diff --git a/src/nmodl/parser/verbatim_driver.hpp b/src/nmodl/parser/verbatim_driver.hpp index 3a5013533d..bd005879af 100644 --- a/src/nmodl/parser/verbatim_driver.hpp +++ b/src/nmodl/parser/verbatim_driver.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 08fb13568e..9baf8342b9 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "printer/code_printer.hpp" #include "utils/string_utils.hpp" diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index f6b703d3da..051edcc3a6 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/printer/decl.hpp b/src/nmodl/printer/decl.hpp index f83a87d150..041b2114cb 100644 --- a/src/nmodl/printer/decl.hpp +++ b/src/nmodl/printer/decl.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index e13a84e6c7..0a7477afc1 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "printer/json_printer.hpp" #include "utils/logger.hpp" diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index e4fb4b5d65..536c112352 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/printer/nmodl_printer.cpp b/src/nmodl/printer/nmodl_printer.cpp index 356f9c5e4b..2e2e4494df 100644 --- a/src/nmodl/printer/nmodl_printer.cpp +++ b/src/nmodl/printer/nmodl_printer.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "printer/nmodl_printer.hpp" #include "utils/string_utils.hpp" diff --git a/src/nmodl/printer/nmodl_printer.hpp b/src/nmodl/printer/nmodl_printer.hpp index d333b9bd2a..5d00725465 100644 --- a/src/nmodl/printer/nmodl_printer.hpp +++ b/src/nmodl/printer/nmodl_printer.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/pybind/docstrings.hpp b/src/nmodl/pybind/docstrings.hpp index c2b554df0f..7fa8274826 100644 --- a/src/nmodl/pybind/docstrings.hpp +++ b/src/nmodl/pybind/docstrings.hpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #pragma once namespace nmodl { diff --git a/src/nmodl/pybind/pybind_utils.hpp b/src/nmodl/pybind/pybind_utils.hpp index 8d02060273..43ad61dccd 100644 --- a/src/nmodl/pybind/pybind_utils.hpp +++ b/src/nmodl/pybind/pybind_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index 81fa641b22..447674a2b4 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp index 80068610ad..2199d6b04c 100644 --- a/src/nmodl/pybind/pyembed.hpp +++ b/src/nmodl/pybind/pyembed.hpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #pragma once #include diff --git a/src/nmodl/pybind/pynmodl.cpp b/src/nmodl/pybind/pynmodl.cpp index 259df6bb8b..bb58f3fc77 100644 --- a/src/nmodl/pybind/pynmodl.cpp +++ b/src/nmodl/pybind/pynmodl.cpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #include "ast/program.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 57f0f8c173..d456e5c2d0 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #include "codegen/codegen_naming.hpp" #include "pybind/pyembed.hpp" diff --git a/src/nmodl/solver/crout/crout.hpp b/src/nmodl/solver/crout/crout.hpp index c1bbb8d92a..f71c382d2e 100644 --- a/src/nmodl/solver/crout/crout.hpp +++ b/src/nmodl/solver/crout/crout.hpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #pragma once /** diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 17d89a2cb9..973b4cd332 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/symtab/decl.hpp b/src/nmodl/symtab/decl.hpp index 2894baea55..119ebf0f89 100644 --- a/src/nmodl/symtab/decl.hpp +++ b/src/nmodl/symtab/decl.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/symtab/symbol.cpp b/src/nmodl/symtab/symbol.cpp index bccda7259f..c7fe26744f 100644 --- a/src/nmodl/symtab/symbol.cpp +++ b/src/nmodl/symtab/symbol.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "symtab/symbol.hpp" #include "utils/logger.hpp" diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index 3e7bf67afb..d356abeee6 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 0ef7717df9..cddd6e56ff 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 0ca1b0ea1b..03c60c0271 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 6d3e02f49c..37d1f02ec6 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index cd52899f20..d6316a568c 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 50cd2fe267..5f1465ab23 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -1,10 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ - + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/units/units.hpp b/src/nmodl/units/units.hpp index 0165e8b822..6c770bde79 100644 --- a/src/nmodl/units/units.hpp +++ b/src/nmodl/units/units.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 7601ada626..9bcdeeb8f5 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "common_utils.hpp" diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index b78f4f8eb2..b71502f1f6 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/utils/file_library.cpp b/src/nmodl/utils/file_library.cpp index 480a0ffbf7..9293ec442b 100644 --- a/src/nmodl/utils/file_library.cpp +++ b/src/nmodl/utils/file_library.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "file_library.hpp" diff --git a/src/nmodl/utils/file_library.hpp b/src/nmodl/utils/file_library.hpp index e587799647..86bee59418 100644 --- a/src/nmodl/utils/file_library.hpp +++ b/src/nmodl/utils/file_library.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/utils/logger.cpp b/src/nmodl/utils/logger.cpp index 2087a670ed..48cba60de2 100644 --- a/src/nmodl/utils/logger.cpp +++ b/src/nmodl/utils/logger.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/utils/logger.hpp b/src/nmodl/utils/logger.hpp index b12e2f4d5c..6867c155ef 100644 --- a/src/nmodl/utils/logger.hpp +++ b/src/nmodl/utils/logger.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/utils/perf_stat.cpp b/src/nmodl/utils/perf_stat.cpp index ac4ea7845d..6156249ea7 100644 --- a/src/nmodl/utils/perf_stat.cpp +++ b/src/nmodl/utils/perf_stat.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/src/nmodl/utils/perf_stat.hpp b/src/nmodl/utils/perf_stat.hpp index 86cd423129..bcb76de474 100644 --- a/src/nmodl/utils/perf_stat.hpp +++ b/src/nmodl/utils/perf_stat.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/utils/string_utils.cpp b/src/nmodl/utils/string_utils.cpp index ae468af790..76a7c12506 100644 --- a/src/nmodl/utils/string_utils.cpp +++ b/src/nmodl/utils/string_utils.cpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #include "utils/string_utils.hpp" #include diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 72358d1a05..9903ff8924 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/utils/table_data.cpp b/src/nmodl/utils/table_data.cpp index 906309b5c2..fd6c3b56a2 100644 --- a/src/nmodl/utils/table_data.cpp +++ b/src/nmodl/utils/table_data.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/utils/table_data.hpp b/src/nmodl/utils/table_data.hpp index c689474b16..7129a8c974 100644 --- a/src/nmodl/utils/table_data.hpp +++ b/src/nmodl/utils/table_data.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp index 7d0db8ca49..13caa477ef 100644 --- a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/after_cvode_to_cnexp_visitor.hpp" diff --git a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp index e8aa847a77..182a01fb8b 100644 --- a/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp +++ b/src/nmodl/visitors/after_cvode_to_cnexp_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/constant_folder_visitor.cpp b/src/nmodl/visitors/constant_folder_visitor.cpp index d2d7f0f9ce..69fab6bf25 100644 --- a/src/nmodl/visitors/constant_folder_visitor.cpp +++ b/src/nmodl/visitors/constant_folder_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/constant_folder_visitor.hpp" diff --git a/src/nmodl/visitors/constant_folder_visitor.hpp b/src/nmodl/visitors/constant_folder_visitor.hpp index 77bcf44d9e..6c9e735c53 100644 --- a/src/nmodl/visitors/constant_folder_visitor.hpp +++ b/src/nmodl/visitors/constant_folder_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 03109e07c7..7388d090de 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/defuse_analyze_visitor.hpp" diff --git a/src/nmodl/visitors/defuse_analyze_visitor.hpp b/src/nmodl/visitors/defuse_analyze_visitor.hpp index 28f3a0a536..a36ac9e2f3 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.hpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/global_var_visitor.cpp b/src/nmodl/visitors/global_var_visitor.cpp index 979bcf0947..f11956bb45 100644 --- a/src/nmodl/visitors/global_var_visitor.cpp +++ b/src/nmodl/visitors/global_var_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/visitors/global_var_visitor.hpp b/src/nmodl/visitors/global_var_visitor.hpp index 354702830a..8cecb5e4ce 100644 --- a/src/nmodl/visitors/global_var_visitor.hpp +++ b/src/nmodl/visitors/global_var_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/implicit_argument_visitor.cpp b/src/nmodl/visitors/implicit_argument_visitor.cpp index 572a72209d..14d84c1299 100644 --- a/src/nmodl/visitors/implicit_argument_visitor.cpp +++ b/src/nmodl/visitors/implicit_argument_visitor.cpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #include "visitors/implicit_argument_visitor.hpp" #include "ast/ast_decl.hpp" #include "ast/function_call.hpp" diff --git a/src/nmodl/visitors/implicit_argument_visitor.hpp b/src/nmodl/visitors/implicit_argument_visitor.hpp index 46ec38978c..effca8793c 100644 --- a/src/nmodl/visitors/implicit_argument_visitor.hpp +++ b/src/nmodl/visitors/implicit_argument_visitor.hpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #pragma once /** diff --git a/src/nmodl/visitors/indexedname_visitor.cpp b/src/nmodl/visitors/indexedname_visitor.cpp index 1ad216b85c..c039c52558 100644 --- a/src/nmodl/visitors/indexedname_visitor.cpp +++ b/src/nmodl/visitors/indexedname_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/indexedname_visitor.hpp" #include "ast/binary_expression.hpp" diff --git a/src/nmodl/visitors/indexedname_visitor.hpp b/src/nmodl/visitors/indexedname_visitor.hpp index e5eea8ad32..9d38b6db33 100644 --- a/src/nmodl/visitors/indexedname_visitor.hpp +++ b/src/nmodl/visitors/indexedname_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 2fa0acaf1c..5820ddd2a4 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/inline_visitor.hpp" diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 72b26abc0f..9a728da04b 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 867c47f3e3..89201af401 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "kinetic_block_visitor.hpp" diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index a13bd1ddd3..0f67260594 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/local_to_assigned_visitor.cpp b/src/nmodl/visitors/local_to_assigned_visitor.cpp index 4596c3a3d9..89f7734899 100644 --- a/src/nmodl/visitors/local_to_assigned_visitor.cpp +++ b/src/nmodl/visitors/local_to_assigned_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/visitors/local_to_assigned_visitor.hpp b/src/nmodl/visitors/local_to_assigned_visitor.hpp index 3ffc76f4e5..4e757718ef 100644 --- a/src/nmodl/visitors/local_to_assigned_visitor.hpp +++ b/src/nmodl/visitors/local_to_assigned_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/local_var_rename_visitor.cpp b/src/nmodl/visitors/local_var_rename_visitor.cpp index aef8d0c089..a93a1128a6 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.cpp +++ b/src/nmodl/visitors/local_var_rename_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/local_var_rename_visitor.hpp" diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index e6c2a38c4a..a73196e194 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 47738bf291..0181940b57 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/localize_visitor.hpp" diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index e8af4878ab..821e072fe2 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 705016a331..35697f19be 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/loop_unroll_visitor.hpp" diff --git a/src/nmodl/visitors/loop_unroll_visitor.hpp b/src/nmodl/visitors/loop_unroll_visitor.hpp index 681e2ea28f..054dfb89b9 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.hpp +++ b/src/nmodl/visitors/loop_unroll_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 945f3883f5..42ba3a89d0 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index 80a1c05b2d..77af74eff3 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/neuron_solve_visitor.hpp" diff --git a/src/nmodl/visitors/neuron_solve_visitor.hpp b/src/nmodl/visitors/neuron_solve_visitor.hpp index 6d4bd77b6a..85029ed43e 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.hpp +++ b/src/nmodl/visitors/neuron_solve_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/nmodl_visitor_helper.ipp b/src/nmodl/visitors/nmodl_visitor_helper.ipp index 4faa2ca25b..a32817bfac 100644 --- a/src/nmodl/visitors/nmodl_visitor_helper.ipp +++ b/src/nmodl/visitors/nmodl_visitor_helper.ipp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 80111361b2..078c8300e0 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/perf_visitor.hpp" diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index b244589a7d..7fa0c01d3f 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 4d97785825..0328d01417 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/rename_visitor.hpp" diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index b40276ab41..ae46620a0e 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 5464398790..40d6b1667b 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -1,3 +1,10 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + #include "visitors/semantic_analysis_visitor.hpp" #include "ast/function_block.hpp" #include "ast/function_table_block.hpp" diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index bb7bdbf2dc..1deffcb154 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2021-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 59e6b92d70..7355789ad4 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/solve_block_visitor.hpp" diff --git a/src/nmodl/visitors/solve_block_visitor.hpp b/src/nmodl/visitors/solve_block_visitor.hpp index 8ef7a46200..a22509d130 100644 --- a/src/nmodl/visitors/solve_block_visitor.hpp +++ b/src/nmodl/visitors/solve_block_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/steadystate_visitor.cpp b/src/nmodl/visitors/steadystate_visitor.cpp index 4ad363529d..4c69600d1f 100644 --- a/src/nmodl/visitors/steadystate_visitor.cpp +++ b/src/nmodl/visitors/steadystate_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/steadystate_visitor.hpp" diff --git a/src/nmodl/visitors/steadystate_visitor.hpp b/src/nmodl/visitors/steadystate_visitor.hpp index b3276045ef..6b4e3432ad 100644 --- a/src/nmodl/visitors/steadystate_visitor.hpp +++ b/src/nmodl/visitors/steadystate_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index d319b1443e..30aa9bfd4e 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/sympy_conductance_visitor.hpp" diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index 8c8de7d67e..cb76bdf2fd 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index aae5ba6f2e..34ba9e3498 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/sympy_replace_solutions_visitor.hpp" #include "visitors/lookup_visitor.hpp" diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp index b810f0d1b4..371b4788da 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 339f5c6535..040bddbc69 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/sympy_solver_visitor.hpp" #include "visitors/sympy_replace_solutions_visitor.hpp" diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 50abd0ff78..b5a5b13516 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/symtab_visitor_helper.hpp b/src/nmodl/visitors/symtab_visitor_helper.hpp index 63c9399a98..cfe299d64e 100644 --- a/src/nmodl/visitors/symtab_visitor_helper.hpp +++ b/src/nmodl/visitors/symtab_visitor_helper.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 01af6a0bfd..0da2a1c6cc 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/units_visitor.hpp" #include "utils/string_utils.hpp" diff --git a/src/nmodl/visitors/units_visitor.hpp b/src/nmodl/visitors/units_visitor.hpp index 7d03e2e672..543b0655a0 100644 --- a/src/nmodl/visitors/units_visitor.hpp +++ b/src/nmodl/visitors/units_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/var_usage_visitor.cpp b/src/nmodl/visitors/var_usage_visitor.cpp index 5e6aaedb2e..5f355400da 100644 --- a/src/nmodl/visitors/var_usage_visitor.cpp +++ b/src/nmodl/visitors/var_usage_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/var_usage_visitor.hpp" diff --git a/src/nmodl/visitors/var_usage_visitor.hpp b/src/nmodl/visitors/var_usage_visitor.hpp index ff552ae20f..933f3b5f0f 100644 --- a/src/nmodl/visitors/var_usage_visitor.hpp +++ b/src/nmodl/visitors/var_usage_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index f6b5b319d7..3908a88679 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/verbatim_var_rename_visitor.hpp" diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp index f918be61a4..81baddb3fd 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.hpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/verbatim_visitor.cpp b/src/nmodl/visitors/verbatim_visitor.cpp index 25df00c480..a9c540b15f 100644 --- a/src/nmodl/visitors/verbatim_visitor.cpp +++ b/src/nmodl/visitors/verbatim_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitors/verbatim_visitor.hpp" diff --git a/src/nmodl/visitors/verbatim_visitor.hpp b/src/nmodl/visitors/verbatim_visitor.hpp index eb33c5f46c..d17c06b5dd 100644 --- a/src/nmodl/visitors/verbatim_visitor.hpp +++ b/src/nmodl/visitors/verbatim_visitor.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index d7b00c997a..7044a88a66 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "visitor_utils.hpp" diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 850d00e176..a817ad0123 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp index 1f4c7bce3b..5e686498f0 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2023 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 9a41b4a411..aa9d509c8e 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2019-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 669e54a242..7ad35844a3 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2019-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index 1e576425bb..20180ec3e8 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2019-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp index 60bfd84120..53060dd673 100644 --- a/test/nmodl/transpiler/unit/codegen/main.cpp +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/codegen/transform.cpp b/test/nmodl/transpiler/unit/codegen/transform.cpp index feffa88b88..3a34222536 100644 --- a/test/nmodl/transpiler/unit/codegen/transform.cpp +++ b/test/nmodl/transpiler/unit/codegen/transform.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/crout/crout.cpp b/test/nmodl/transpiler/unit/crout/crout.cpp index 75e15a3bdf..32a0b19ca5 100644 --- a/test/nmodl/transpiler/unit/crout/crout.cpp +++ b/test/nmodl/transpiler/unit/crout/crout.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "nmodl.hpp" diff --git a/test/nmodl/transpiler/unit/lexer/tokens.cpp b/test/nmodl/transpiler/unit/lexer/tokens.cpp index d21429f551..f3d5d62fb1 100644 --- a/test/nmodl/transpiler/unit/lexer/tokens.cpp +++ b/test/nmodl/transpiler/unit/lexer/tokens.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index c10211af6f..ceaa025510 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 15c0493311..fec200beb3 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "nmodl.hpp" diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index 9ef2f45a1f..ec92958daa 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 from nmodl.ode import differentiate2c, integrate2c diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 6e022a1bd2..67c3472718 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/printer/printer.cpp b/test/nmodl/transpiler/unit/printer/printer.cpp index 0d130c2aa7..80c9b6cf3b 100644 --- a/test/nmodl/transpiler/unit/printer/printer.cpp +++ b/test/nmodl/transpiler/unit/printer/printer.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/pybind/conftest.py b/test/nmodl/transpiler/unit/pybind/conftest.py index 3ace8bf6d3..0a4bc25947 100644 --- a/test/nmodl/transpiler/unit/pybind/conftest.py +++ b/test/nmodl/transpiler/unit/pybind/conftest.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 import pytest from nmodl.dsl import NmodlDriver diff --git a/test/nmodl/transpiler/unit/pybind/test_ast.py b/test/nmodl/transpiler/unit/pybind/test_ast.py index 6f657fff88..b0b353ebe0 100644 --- a/test/nmodl/transpiler/unit/pybind/test_ast.py +++ b/test/nmodl/transpiler/unit/pybind/test_ast.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 from nmodl.dsl import ast import nmodl.dsl as nmodl diff --git a/test/nmodl/transpiler/unit/pybind/test_symtab.py b/test/nmodl/transpiler/unit/pybind/test_symtab.py index 2493ed4b90..f79e444f26 100644 --- a/test/nmodl/transpiler/unit/pybind/test_symtab.py +++ b/test/nmodl/transpiler/unit/pybind/test_symtab.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 import io from nmodl.dsl import ast, visitor, symtab diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index 72c49bf430..f402ea0c7e 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -1,9 +1,7 @@ -# *********************************************************************** -# Copyright (C) 2018-2022 Blue Brain Project +# Copyright 2023 Blue Brain Project, EPFL. +# See the top-level LICENSE file for details. # -# This file is part of NMODL distributed under the terms of the GNU -# Lesser General Public License. See top-level LICENSE file for details. -# *********************************************************************** +# SPDX-License-Identifier: Apache-2.0 import nmodl from nmodl.dsl import ast, visitor diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index 2f90a02cf3..a180db184e 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/units/lexer.cpp b/test/nmodl/transpiler/unit/units/lexer.cpp index 357931a3ef..a1b6aaf131 100644 --- a/test/nmodl/transpiler/unit/units/lexer.cpp +++ b/test/nmodl/transpiler/unit/units/lexer.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index 9e3fb10b01..3ffd3497e1 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 05b61eb35d..09fab789dc 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "nmodl_constructs.hpp" diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp index de979535ba..5f3ba4d75d 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index 82e2a364b7..c06d95b49e 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include "test_utils.hpp" #include "utils/logger.hpp" diff --git a/test/nmodl/transpiler/unit/utils/test_utils.hpp b/test/nmodl/transpiler/unit/utils/test_utils.hpp index d2e0447b9e..a3661a177a 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.hpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.hpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once diff --git a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp index 5fc109cfc5..1cfa8b0024 100644 --- a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp +++ b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp index 6de0aeb0d7..918356018e 100644 --- a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 1612b3b4c7..cf0a36d0eb 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 0b2024e37a..64bed46a98 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp index 1f1ad5cdb3..db794cf6d4 100644 --- a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp +++ b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp @@ -1,9 +1,10 @@ -/************************************************************************* - * Copyright (C) 2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ + #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/implicit_argument_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 597b98a8e6..5eeefd74cd 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/visitor/json.cpp b/test/nmodl/transpiler/unit/visitor/json.cpp index ea73e0db31..43b4bca448 100644 --- a/test/nmodl/transpiler/unit/visitor/json.cpp +++ b/test/nmodl/transpiler/unit/visitor/json.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 39c0420666..800eafbe81 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index b7309b08a1..e8ee0bcc32 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp index 15db026588..7a19ab8376 100644 --- a/test/nmodl/transpiler/unit/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index 8254a7f584..bc7bb8aa91 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index e5d4fe963b..b0bed9341a 100644 --- a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp index 60bfd84120..53060dd673 100644 --- a/test/nmodl/transpiler/unit/visitor/main.cpp +++ b/test/nmodl/transpiler/unit/visitor/main.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp index 16ffc3fb60..95ad1825e0 100644 --- a/test/nmodl/transpiler/unit/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 1d1ac45f02..171ba01f2f 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 60099ddcee..3550239add 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/node_index.cpp b/test/nmodl/transpiler/unit/visitor/node_index.cpp index 2e8b6beee8..d03935f090 100644 --- a/test/nmodl/transpiler/unit/visitor/node_index.cpp +++ b/test/nmodl/transpiler/unit/visitor/node_index.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/perf.cpp b/test/nmodl/transpiler/unit/visitor/perf.cpp index be92597134..6149a3ba26 100644 --- a/test/nmodl/transpiler/unit/visitor/perf.cpp +++ b/test/nmodl/transpiler/unit/visitor/perf.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp index e548698883..8e705ef84a 100644 --- a/test/nmodl/transpiler/unit/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index e546a57e17..23c58d2666 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index 90dc5704be..6ca2341c89 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index 4051f78803..25cb6df37c 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 7590cbfef5..96b9ea867a 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 304f7d8125..6768438745 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index d37e79958d..06f3865cd8 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include #include diff --git a/test/nmodl/transpiler/unit/visitor/var_usage.cpp b/test/nmodl/transpiler/unit/visitor/var_usage.cpp index eadc1cea94..a5be3449db 100644 --- a/test/nmodl/transpiler/unit/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/unit/visitor/var_usage.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include diff --git a/test/nmodl/transpiler/unit/visitor/verbatim.cpp b/test/nmodl/transpiler/unit/visitor/verbatim.cpp index 4594cc3860..1c19dd03f6 100644 --- a/test/nmodl/transpiler/unit/visitor/verbatim.cpp +++ b/test/nmodl/transpiler/unit/visitor/verbatim.cpp @@ -1,9 +1,9 @@ -/************************************************************************* - * Copyright (C) 2018-2022 Blue Brain Project +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. * - * This file is part of NMODL distributed under the terms of the GNU - * Lesser General Public License. See top-level LICENSE file for details. - *************************************************************************/ + * SPDX-License-Identifier: Apache-2.0 + */ #include From 724e30945bdeb07d275692760e99fc993c4039a5 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 30 Aug 2023 11:08:19 +0200 Subject: [PATCH 519/871] Fix KINETIC block visitor in case there is an empty statement (BlueBrain/nmodl#1009) * Avoid printing ode_rhs related code when ode_rhs is empty Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@a662a453102848d83610baa7eb7c15860fccdbc2 --- src/nmodl/visitors/kinetic_block_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 89201af401..69a317a3b3 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -397,7 +397,7 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { } } // divide by COMPARTMENT factor if present - if (!compartment_factors[j].empty()) { + if (!compartment_factors[j].empty() && !ode_rhs.empty()) { ode_rhs = fmt::format("({})/({})", ode_rhs, compartment_factors[j]); } // if rhs of ODE is not empty, add to list of ODEs From ba96d4c0a0269558fef76cc5b0239116ef8f3b52 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 31 Aug 2023 13:34:37 +0200 Subject: [PATCH 520/871] Fix how solver files are copied. (BlueBrain/nmodl#1053) NMODL Repo SHA: BlueBrain/nmodl@8079c5ecfc9df787c94fdb2a54d7d0ee455bd5db --- src/nmodl/solver/CMakeLists.txt | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index c4b4370d2b..78c44c5d0d 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -1,22 +1,18 @@ -# ============================================================================= -# Solver sources -# ============================================================================= -set(NEWTON_SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) -set(CROUT_SOLVER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp) - # ============================================================================= # Copy necessary files to build directory # ============================================================================= -# Newton -file(GLOB NMODL_NEWTON_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/newton/*.h*") -file(COPY ${NMODL_NEWTON_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/newton/) -# Crout -file(GLOB NMODL_CROUT_SOLVER_HEADER_FILES "${CMAKE_CURRENT_SOURCE_DIR}/crout/*.h*") -file(COPY ${NMODL_CROUT_SOLVER_HEADER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/include/crout/) + +cpp_cc_build_time_copy(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp" OUTPUT + "${CMAKE_BINARY_DIR}/include/newton/newton.hpp") + +cpp_cc_build_time_copy(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp" OUTPUT + "${CMAKE_BINARY_DIR}/include/crout/crout.hpp") + # Eigen file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY_DIR}/include/) # ============================================================================= # Install solver headers and eigen from include # ============================================================================= + install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include) From 4cf72fca12f719b1b1cc4efa2f305d62df19940f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 31 Aug 2023 17:52:21 +0200 Subject: [PATCH 521/871] Align `+`es. (BlueBrain/nmodl#1054) NMODL Repo SHA: BlueBrain/nmodl@ee7d180bbf6e22134a747b4ef3bf557f50c5f251 --- cmake/nmodl/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 744e74d67f..20573097ad 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -274,7 +274,7 @@ message(STATUS "Flex | ${FLEX_EXECUTABLE}") message(STATUS "Bison | ${BISON_EXECUTABLE}") message(STATUS "Python | ${PYTHON_EXECUTABLE}") message(STATUS " Linked against | ${LINK_AGAINST_PYTHON}") -message(STATUS "--------------+--------------------------------------------------------------") +message(STATUS "--------------------+--------------------------------------------------------") message(STATUS " See documentation : https://github.com/BlueBrain/nmodl/") -message(STATUS "--------------+--------------------------------------------------------------") +message(STATUS "--------------------+--------------------------------------------------------") message(STATUS "") From 1b93fda9b14feb3438c2035919402d03ac6ab08c Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Fri, 1 Sep 2023 13:33:41 +0200 Subject: [PATCH 522/871] Rename CodegenCVisitor to CodegenCppVisitor (BlueBrain/nmodl#1055) The file was renamed but not the class, even though that probably should have been part of that refactoring NMODL Repo SHA: BlueBrain/nmodl@a309bc156f5a9232f340cb0d7fa8a1f8ee3e9942 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 6 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 429 +++++++++--------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 72 +-- src/nmodl/codegen/codegen_utils.cpp | 4 +- src/nmodl/codegen/codegen_utils.hpp | 4 +- src/nmodl/main.cpp | 8 +- src/nmodl/utils/common_utils.hpp | 2 +- .../unit/codegen/codegen_cpp_visitor.cpp | 10 +- .../transpiler/unit/codegen/codegen_utils.cpp | 12 +- .../transpiler/unit/visitor/sympy_solver.cpp | 16 +- 11 files changed, 283 insertions(+), 282 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 4ca99d3aa2..2d327ddbd4 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -84,7 +84,7 @@ std::string CodegenAccVisitor::backend_name() const { void CodegenAccVisitor::print_memory_allocation_routine() const { // memory for artificial cells should be allocated on CPU if (info.artificial_cell) { - CodegenCVisitor::print_memory_allocation_routine(); + CodegenCppVisitor::print_memory_allocation_routine(); return; } printer->add_newline(2); diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index e48c83f092..0246e8a379 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -27,7 +27,7 @@ namespace codegen { * \class CodegenAccVisitor * \brief %Visitor for printing C code with OpenACC backend */ -class CodegenAccVisitor: public CodegenCVisitor { +class CodegenAccVisitor: public CodegenCppVisitor { protected: /// name of the code generation backend std::string backend_name() const override; @@ -137,13 +137,13 @@ class CodegenAccVisitor: public CodegenCVisitor { const std::string& output_dir, const std::string& float_type, bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} + : CodegenCppVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} CodegenAccVisitor(const std::string& mod_file, std::ostream& stream, const std::string& float_type, bool optimize_ionvar_copies) - : CodegenCVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} + : CodegenCppVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; /** @} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index e57647301c..946fca68ff 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -50,7 +50,7 @@ namespace codegen_utils = nmodl::codegen::utils; static const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; -void CodegenCVisitor::visit_string(const String& node) { +void CodegenCppVisitor::visit_string(const String& node) { if (!codegen) { return; } @@ -62,7 +62,7 @@ void CodegenCVisitor::visit_string(const String& node) { } -void CodegenCVisitor::visit_integer(const Integer& node) { +void CodegenCppVisitor::visit_integer(const Integer& node) { if (!codegen) { return; } @@ -71,7 +71,7 @@ void CodegenCVisitor::visit_integer(const Integer& node) { } -void CodegenCVisitor::visit_float(const Float& node) { +void CodegenCppVisitor::visit_float(const Float& node) { if (!codegen) { return; } @@ -79,7 +79,7 @@ void CodegenCVisitor::visit_float(const Float& node) { } -void CodegenCVisitor::visit_double(const Double& node) { +void CodegenCppVisitor::visit_double(const Double& node) { if (!codegen) { return; } @@ -87,7 +87,7 @@ void CodegenCVisitor::visit_double(const Double& node) { } -void CodegenCVisitor::visit_boolean(const Boolean& node) { +void CodegenCppVisitor::visit_boolean(const Boolean& node) { if (!codegen) { return; } @@ -95,7 +95,7 @@ void CodegenCVisitor::visit_boolean(const Boolean& node) { } -void CodegenCVisitor::visit_name(const Name& node) { +void CodegenCppVisitor::visit_name(const Name& node) { if (!codegen) { return; } @@ -103,12 +103,12 @@ void CodegenCVisitor::visit_name(const Name& node) { } -void CodegenCVisitor::visit_unit(const ast::Unit& node) { +void CodegenCppVisitor::visit_unit(const ast::Unit& node) { // do not print units } -void CodegenCVisitor::visit_prime_name(const PrimeName& /* node */) { +void CodegenCppVisitor::visit_prime_name(const PrimeName& /* node */) { throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); } @@ -116,7 +116,7 @@ void CodegenCVisitor::visit_prime_name(const PrimeName& /* node */) { /** * \todo : Validate how @ is being handled in neuron implementation */ -void CodegenCVisitor::visit_var_name(const VarName& node) { +void CodegenCppVisitor::visit_var_name(const VarName& node) { if (!codegen) { return; } @@ -138,7 +138,7 @@ void CodegenCVisitor::visit_var_name(const VarName& node) { } -void CodegenCVisitor::visit_indexed_name(const IndexedName& node) { +void CodegenCppVisitor::visit_indexed_name(const IndexedName& node) { if (!codegen) { return; } @@ -151,7 +151,7 @@ void CodegenCVisitor::visit_indexed_name(const IndexedName& node) { } -void CodegenCVisitor::visit_local_list_statement(const LocalListStatement& node) { +void CodegenCppVisitor::visit_local_list_statement(const LocalListStatement& node) { if (!codegen) { return; } @@ -161,7 +161,7 @@ void CodegenCVisitor::visit_local_list_statement(const LocalListStatement& node) } -void CodegenCVisitor::visit_if_statement(const IfStatement& node) { +void CodegenCppVisitor::visit_if_statement(const IfStatement& node) { if (!codegen) { return; } @@ -177,7 +177,7 @@ void CodegenCVisitor::visit_if_statement(const IfStatement& node) { } -void CodegenCVisitor::visit_else_if_statement(const ElseIfStatement& node) { +void CodegenCppVisitor::visit_else_if_statement(const ElseIfStatement& node) { if (!codegen) { return; } @@ -188,7 +188,7 @@ void CodegenCVisitor::visit_else_if_statement(const ElseIfStatement& node) { } -void CodegenCVisitor::visit_else_statement(const ElseStatement& node) { +void CodegenCppVisitor::visit_else_statement(const ElseStatement& node) { if (!codegen) { return; } @@ -197,7 +197,7 @@ void CodegenCVisitor::visit_else_statement(const ElseStatement& node) { } -void CodegenCVisitor::visit_while_statement(const WhileStatement& node) { +void CodegenCppVisitor::visit_while_statement(const WhileStatement& node) { printer->add_text("while ("); node.get_condition()->accept(*this); printer->add_text(") "); @@ -205,7 +205,7 @@ void CodegenCVisitor::visit_while_statement(const WhileStatement& node) { } -void CodegenCVisitor::visit_from_statement(const ast::FromStatement& node) { +void CodegenCppVisitor::visit_from_statement(const ast::FromStatement& node) { if (!codegen) { return; } @@ -229,7 +229,7 @@ void CodegenCVisitor::visit_from_statement(const ast::FromStatement& node) { } -void CodegenCVisitor::visit_paren_expression(const ParenExpression& node) { +void CodegenCppVisitor::visit_paren_expression(const ParenExpression& node) { if (!codegen) { return; } @@ -239,7 +239,7 @@ void CodegenCVisitor::visit_paren_expression(const ParenExpression& node) { } -void CodegenCVisitor::visit_binary_expression(const BinaryExpression& node) { +void CodegenCppVisitor::visit_binary_expression(const BinaryExpression& node) { if (!codegen) { return; } @@ -260,7 +260,7 @@ void CodegenCVisitor::visit_binary_expression(const BinaryExpression& node) { } -void CodegenCVisitor::visit_binary_operator(const BinaryOperator& node) { +void CodegenCppVisitor::visit_binary_operator(const BinaryOperator& node) { if (!codegen) { return; } @@ -268,7 +268,7 @@ void CodegenCVisitor::visit_binary_operator(const BinaryOperator& node) { } -void CodegenCVisitor::visit_unary_operator(const UnaryOperator& node) { +void CodegenCppVisitor::visit_unary_operator(const UnaryOperator& node) { if (!codegen) { return; } @@ -281,7 +281,7 @@ void CodegenCVisitor::visit_unary_operator(const UnaryOperator& node) { * Sometime we want to analyse ast nodes even if code generation is * false. Hence we visit children even if code generation is false. */ -void CodegenCVisitor::visit_statement_block(const StatementBlock& node) { +void CodegenCppVisitor::visit_statement_block(const StatementBlock& node) { if (!codegen) { node.visit_children(*this); return; @@ -290,7 +290,7 @@ void CodegenCVisitor::visit_statement_block(const StatementBlock& node) { } -void CodegenCVisitor::visit_function_call(const FunctionCall& node) { +void CodegenCppVisitor::visit_function_call(const FunctionCall& node) { if (!codegen) { return; } @@ -298,7 +298,7 @@ void CodegenCVisitor::visit_function_call(const FunctionCall& node) { } -void CodegenCVisitor::visit_verbatim(const Verbatim& node) { +void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { if (!codegen) { return; } @@ -314,24 +314,24 @@ void CodegenCVisitor::visit_verbatim(const Verbatim& node) { } } -void CodegenCVisitor::visit_update_dt(const ast::UpdateDt& node) { +void CodegenCppVisitor::visit_update_dt(const ast::UpdateDt& node) { // dt change statement should be pulled outside already } -void CodegenCVisitor::visit_protect_statement(const ast::ProtectStatement& node) { +void CodegenCppVisitor::visit_protect_statement(const ast::ProtectStatement& node) { print_atomic_reduction_pragma(); printer->add_indent(); node.get_expression()->accept(*this); printer->add_text(";"); } -void CodegenCVisitor::visit_mutex_lock(const ast::MutexLock& node) { +void CodegenCppVisitor::visit_mutex_lock(const ast::MutexLock& node) { printer->fmt_line("#pragma omp critical ({})", info.mod_suffix); printer->add_indent(); printer->start_block(); } -void CodegenCVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { +void CodegenCppVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { printer->end_block(1); } @@ -346,7 +346,7 @@ void CodegenCVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { * statement and hence we have to check inner expression. It's also true * for the initial block defined inside net receive block. */ -bool CodegenCVisitor::statement_to_skip(const Statement& node) { +bool CodegenCppVisitor::statement_to_skip(const Statement& node) { // clang-format off if (node.is_unit_state() || node.is_line_comment() @@ -370,7 +370,7 @@ bool CodegenCVisitor::statement_to_skip(const Statement& node) { } -bool CodegenCVisitor::net_send_buffer_required() const noexcept { +bool CodegenCppVisitor::net_send_buffer_required() const noexcept { if (net_receive_required() && !info.artificial_cell) { if (info.net_event_used || info.net_send_used || info.is_watch_used()) { return true; @@ -380,12 +380,12 @@ bool CodegenCVisitor::net_send_buffer_required() const noexcept { } -bool CodegenCVisitor::net_receive_buffering_required() const noexcept { +bool CodegenCppVisitor::net_receive_buffering_required() const noexcept { return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; } -bool CodegenCVisitor::nrn_state_required() const noexcept { +bool CodegenCppVisitor::nrn_state_required() const noexcept { if (info.artificial_cell) { return false; } @@ -393,22 +393,22 @@ bool CodegenCVisitor::nrn_state_required() const noexcept { } -bool CodegenCVisitor::nrn_cur_required() const noexcept { +bool CodegenCppVisitor::nrn_cur_required() const noexcept { return info.breakpoint_node != nullptr && !info.currents.empty(); } -bool CodegenCVisitor::net_receive_exist() const noexcept { +bool CodegenCppVisitor::net_receive_exist() const noexcept { return info.net_receive_node != nullptr; } -bool CodegenCVisitor::breakpoint_exist() const noexcept { +bool CodegenCppVisitor::breakpoint_exist() const noexcept { return info.breakpoint_node != nullptr; } -bool CodegenCVisitor::net_receive_required() const noexcept { +bool CodegenCppVisitor::net_receive_required() const noexcept { return net_receive_exist(); } @@ -417,12 +417,12 @@ bool CodegenCVisitor::net_receive_required() const noexcept { * \details When floating point data type is not default (i.e. double) then we * have to copy old array to new type (for range variables). */ -bool CodegenCVisitor::range_variable_setup_required() const noexcept { +bool CodegenCppVisitor::range_variable_setup_required() const noexcept { return codegen::naming::DEFAULT_FLOAT_TYPE != float_data_type(); } -int CodegenCVisitor::position_of_float_var(const std::string& name) const { +int CodegenCppVisitor::position_of_float_var(const std::string& name) const { int index = 0; for (const auto& var: codegen_float_variables) { if (var->get_name() == name) { @@ -434,7 +434,7 @@ int CodegenCVisitor::position_of_float_var(const std::string& name) const { } -int CodegenCVisitor::position_of_int_var(const std::string& name) const { +int CodegenCppVisitor::position_of_int_var(const std::string& name) const { int index = 0; for (const auto& var: codegen_int_variables) { if (var.symbol->get_name() == name) { @@ -454,13 +454,13 @@ int CodegenCVisitor::position_of_int_var(const std::string& name) const { * they are represented in the mod file by user. If the value is in scientific * representation (1e+20, 1E-15) then keep it as it is. */ -std::string CodegenCVisitor::format_double_string(const std::string& s_value) { - return codegen_utils::format_double_string(s_value); +std::string CodegenCppVisitor::format_double_string(const std::string& s_value) { + return codegen_utils::format_double_string(s_value); } -std::string CodegenCVisitor::format_float_string(const std::string& s_value) { - return codegen_utils::format_float_string(s_value); +std::string CodegenCppVisitor::format_float_string(const std::string& s_value) { + return codegen_utils::format_float_string(s_value); } @@ -470,7 +470,7 @@ std::string CodegenCVisitor::format_float_string(const std::string& s_value) { * block can appear as statement using expression statement which need to * be inspected. */ -bool CodegenCVisitor::need_semicolon(Statement* node) { +bool CodegenCppVisitor::need_semicolon(Statement* node) { // clang-format off if (node->is_if_statement() || node->is_else_if_statement() @@ -501,7 +501,7 @@ bool CodegenCVisitor::need_semicolon(Statement* node) { // check if there is a function or procedure defined with given name -bool CodegenCVisitor::defined_method(const std::string& name) const { +bool CodegenCppVisitor::defined_method(const std::string& name) const { const auto& function = program_symtab->lookup(name); auto properties = NmodlType::function_block | NmodlType::procedure_block; return function && function->has_any_property(properties); @@ -516,7 +516,7 @@ bool CodegenCVisitor::defined_method(const std::string& name) const { * the variable is renamed. Note that we have to look into the symbol table * of statement block and not breakpoint. */ -std::string CodegenCVisitor::breakpoint_current(std::string current) const { +std::string CodegenCppVisitor::breakpoint_current(std::string current) const { auto breakpoint = info.breakpoint_node; if (breakpoint == nullptr) { return current; @@ -535,7 +535,7 @@ std::string CodegenCVisitor::breakpoint_current(std::string current) const { } -int CodegenCVisitor::float_variables_size() const { +int CodegenCppVisitor::float_variables_size() const { auto count_length = [](int l, const SymbolType& variable) { return l += variable->get_length(); }; @@ -574,7 +574,7 @@ int CodegenCVisitor::float_variables_size() const { } -int CodegenCVisitor::int_variables_size() const { +int CodegenCppVisitor::int_variables_size() const { int num_variables = 0; for (const auto& semantic: info.semantics) { num_variables += semantic.size; @@ -590,7 +590,7 @@ int CodegenCVisitor::int_variables_size() const { * different variable names, we rely on backend-specific read_ion_variable_name * and write_ion_variable_name method which will be overloaded. */ -std::vector CodegenCVisitor::ion_read_statements(BlockType type) { +std::vector CodegenCppVisitor::ion_read_statements(BlockType type) { if (optimize_ion_variable_copies()) { return ion_read_statements_optimized(type); } @@ -620,7 +620,7 @@ std::vector CodegenCVisitor::ion_read_statements(BlockType type) { } -std::vector CodegenCVisitor::ion_read_statements_optimized(BlockType type) { +std::vector CodegenCppVisitor::ion_read_statements_optimized(BlockType type) { std::vector statements; for (const auto& ion: info.ions) { for (const auto& var: ion.writes) { @@ -636,7 +636,7 @@ std::vector CodegenCVisitor::ion_read_statements_optimized(BlockTyp } // NOLINTNEXTLINE(readability-function-cognitive-complexity) -std::vector CodegenCVisitor::ion_write_statements(BlockType type) { +std::vector CodegenCppVisitor::ion_write_statements(BlockType type) { std::vector statements; for (const auto& ion: info.ions) { std::string concentration; @@ -693,7 +693,7 @@ std::vector CodegenCVisitor::ion_write_statements(BlockType * \details Often top level verbatim blocks use variables with old names. * Here we process if we are processing verbatim block at global scope. */ -std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { +std::string CodegenCppVisitor::process_verbatim_token(const std::string& token) { const std::string& name = token; /* @@ -724,7 +724,7 @@ std::string CodegenCVisitor::process_verbatim_token(const std::string& token) { } -bool CodegenCVisitor::ion_variable_struct_required() const { +bool CodegenCppVisitor::ion_variable_struct_required() const { return optimize_ion_variable_copies() && info.ion_has_write_variable(); } @@ -734,7 +734,7 @@ bool CodegenCVisitor::ion_variable_struct_required() const { * except in INITIAL block where they are set to 0. As initial block is/can be * executed on c/cpu backend, gpu backend can mark the parameter as constant. */ -bool CodegenCVisitor::is_constant_variable(const std::string& name) const { +bool CodegenCppVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); bool is_constant = false; if (symbol != nullptr) { @@ -758,7 +758,7 @@ bool CodegenCVisitor::is_constant_variable(const std::string& name) const { * \details Once variables are populated, update index semantics to register with coreneuron */ // NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCVisitor::update_index_semantics() { +void CodegenCppVisitor::update_index_semantics() { int index = 0; info.semantics.clear(); @@ -825,7 +825,7 @@ void CodegenCVisitor::update_index_semantics() { } -std::vector CodegenCVisitor::get_float_variables() { +std::vector CodegenCppVisitor::get_float_variables() { // sort with definition order auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { return first->get_definition_order() < second->get_definition_order(); @@ -891,7 +891,7 @@ std::vector CodegenCVisitor::get_float_variables() { * - style_ionname is index / offset */ // NOLINTNEXTLINE(readability-function-cognitive-complexity) -std::vector CodegenCVisitor::get_int_variables() { +std::vector CodegenCppVisitor::get_int_variables() { std::vector variables; if (info.point_process) { variables.emplace_back(make_symbol(naming::NODE_AREA_VARIABLE)); @@ -1014,7 +1014,7 @@ std::vector CodegenCVisitor::get_int_variables() { /* Routines must be overloaded in backend */ /****************************************************************************************/ -std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { +std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) { std::string param{}; for (auto iter = params.begin(); iter != params.end(); iter++) { param += fmt::format("{}{} {}{}", @@ -1030,31 +1030,31 @@ std::string CodegenCVisitor::get_parameter_str(const ParamVector& params) { } -void CodegenCVisitor::print_deriv_advance_flag_transfer_to_device() const { +void CodegenCppVisitor::print_deriv_advance_flag_transfer_to_device() const { // backend specific, do nothing } -void CodegenCVisitor::print_device_atomic_capture_annotation() const { +void CodegenCppVisitor::print_device_atomic_capture_annotation() const { // backend specific, do nothing } -void CodegenCVisitor::print_net_send_buf_count_update_to_host() const { +void CodegenCppVisitor::print_net_send_buf_count_update_to_host() const { // backend specific, do nothing } -void CodegenCVisitor::print_net_send_buf_update_to_host() const { +void CodegenCppVisitor::print_net_send_buf_update_to_host() const { // backend specific, do nothing } -void CodegenCVisitor::print_net_send_buf_count_update_to_device() const { +void CodegenCppVisitor::print_net_send_buf_count_update_to_device() const { // backend specific, do nothing } -void CodegenCVisitor::print_dt_update_to_device() const { +void CodegenCppVisitor::print_dt_update_to_device() const { // backend specific, do nothing } -void CodegenCVisitor::print_device_stream_wait() const { +void CodegenCppVisitor::print_device_stream_wait() const { // backend specific, do nothing } @@ -1072,20 +1072,20 @@ void CodegenCVisitor::print_device_stream_wait() const { * } * \endcode */ -void CodegenCVisitor::print_kernel_data_present_annotation_block_begin() { +void CodegenCppVisitor::print_kernel_data_present_annotation_block_begin() { // backend specific, do nothing } -void CodegenCVisitor::print_kernel_data_present_annotation_block_end() { +void CodegenCppVisitor::print_kernel_data_present_annotation_block_end() { // backend specific, do nothing } -void CodegenCVisitor::print_net_init_acc_serial_annotation_block_begin() { +void CodegenCppVisitor::print_net_init_acc_serial_annotation_block_begin() { // backend specific, do nothing } -void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { +void CodegenCppVisitor::print_net_init_acc_serial_annotation_block_end() { // backend specific, do nothing } @@ -1101,8 +1101,8 @@ void CodegenCVisitor::print_net_init_acc_serial_annotation_block_end() { * for(int id = 0; id < nodecount; id++) { * \endcode */ -void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, - const ast::Block* block) { +void CodegenCppVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, + const ast::Block* block) { // ivdep allows SIMD parallelisation of a block/loop but doesn't provide // a standard mechanism for atomics. Also, even with openmp 5.0, openmp // atomics do not enable vectorisation under "omp simd" (gives compiler @@ -1123,12 +1123,12 @@ void CodegenCVisitor::print_channel_iteration_block_parallel_hint(BlockType /* t } -bool CodegenCVisitor::nrn_cur_reduction_loop_required() { +bool CodegenCppVisitor::nrn_cur_reduction_loop_required() { return info.point_process; } -void CodegenCVisitor::print_rhs_d_shadow_variables() { +void CodegenCppVisitor::print_rhs_d_shadow_variables() { if (info.point_process) { printer->fmt_line("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); printer->fmt_line("double* shadow_d = nt->{};", naming::NTHREAD_D_SHADOW); @@ -1136,7 +1136,7 @@ void CodegenCVisitor::print_rhs_d_shadow_variables() { } -void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { +void CodegenCppVisitor::print_nrn_cur_matrix_shadow_update() { if (info.point_process) { printer->add_line("shadow_rhs[id] = rhs;"); printer->add_line("shadow_d[id] = g;"); @@ -1149,7 +1149,7 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_update() { } -void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { +void CodegenCppVisitor::print_nrn_cur_matrix_shadow_reduction() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); if (info.point_process) { @@ -1165,47 +1165,47 @@ void CodegenCVisitor::print_nrn_cur_matrix_shadow_reduction() { * only with PROTECT construct (atomic rduction requirement for other cases on CPU * is handled via separate shadow vectors). */ -void CodegenCVisitor::print_atomic_reduction_pragma() { +void CodegenCppVisitor::print_atomic_reduction_pragma() { printer->add_line("#pragma omp atomic update"); } -void CodegenCVisitor::print_device_method_annotation() { +void CodegenCppVisitor::print_device_method_annotation() { // backend specific, nothing for cpu } -void CodegenCVisitor::print_global_method_annotation() { +void CodegenCppVisitor::print_global_method_annotation() { // backend specific, nothing for cpu } -void CodegenCVisitor::print_backend_namespace_start() { +void CodegenCppVisitor::print_backend_namespace_start() { // no separate namespace for C (cpu) backend } -void CodegenCVisitor::print_backend_namespace_stop() { +void CodegenCppVisitor::print_backend_namespace_stop() { // no separate namespace for C (cpu) backend } -void CodegenCVisitor::print_backend_includes() { +void CodegenCppVisitor::print_backend_includes() { // backend specific, nothing for cpu } -std::string CodegenCVisitor::backend_name() const { +std::string CodegenCppVisitor::backend_name() const { return "C (api-compatibility)"; } -bool CodegenCVisitor::optimize_ion_variable_copies() const { +bool CodegenCppVisitor::optimize_ion_variable_copies() const { return optimize_ionvar_copies; } -void CodegenCVisitor::print_memory_allocation_routine() const { +void CodegenCppVisitor::print_memory_allocation_routine() const { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; printer->fmt_start_block("static inline void* mem_alloc({})", args); @@ -1222,7 +1222,7 @@ void CodegenCVisitor::print_memory_allocation_routine() const { } -void CodegenCVisitor::print_abort_routine() const { +void CodegenCppVisitor::print_abort_routine() const { printer->add_newline(2); printer->start_block("static inline void coreneuron_abort()"); printer->add_line("abort();"); @@ -1230,7 +1230,7 @@ void CodegenCVisitor::print_abort_routine() const { } -std::string CodegenCVisitor::compute_method_name(BlockType type) const { +std::string CodegenCppVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); } @@ -1253,11 +1253,11 @@ std::string CodegenCVisitor::compute_method_name(BlockType type) const { } -std::string CodegenCVisitor::global_var_struct_type_qualifier() { +std::string CodegenCppVisitor::global_var_struct_type_qualifier() { return ""; } -void CodegenCVisitor::print_global_var_struct_decl() { +void CodegenCppVisitor::print_global_var_struct_decl() { printer->fmt_line("{} {};", global_struct(), global_struct_instance()); } @@ -1266,15 +1266,15 @@ void CodegenCVisitor::print_global_var_struct_decl() { /****************************************************************************************/ -void CodegenCVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { +void CodegenCppVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { printer->add_text(fmt::format("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)", current_watch_statement++)); } -void CodegenCVisitor::print_statement_block(const ast::StatementBlock& node, - bool open_brace, - bool close_brace) { +void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, + bool open_brace, + bool close_brace) { if (open_brace) { printer->start_block(); } @@ -1304,7 +1304,7 @@ void CodegenCVisitor::print_statement_block(const ast::StatementBlock& node, } -void CodegenCVisitor::print_function_call(const FunctionCall& node) { +void CodegenCppVisitor::print_function_call(const FunctionCall& node) { auto name = node.get_node_name(); auto function_name = name; if (defined_method(name)) { @@ -1341,7 +1341,7 @@ void CodegenCVisitor::print_function_call(const FunctionCall& node) { } -void CodegenCVisitor::print_top_verbatim_blocks() { +void CodegenCppVisitor::print_top_verbatim_blocks() { if (info.top_verbatim_blocks.empty()) { return; } @@ -1372,7 +1372,7 @@ void CodegenCVisitor::print_top_verbatim_blocks() { * is because verbatim renaming pass has already stripped out prefixes from * the text. */ -void CodegenCVisitor::rename_function_arguments() { +void CodegenCppVisitor::rename_function_arguments() { auto default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); for (auto& arg: default_arguments) { stringutils::trim(arg); @@ -1391,7 +1391,7 @@ void CodegenCVisitor::rename_function_arguments() { } -void CodegenCVisitor::print_function_prototypes() { +void CodegenCppVisitor::print_function_prototypes() { if (info.functions.empty() && info.procedures.empty()) { return; } @@ -1426,11 +1426,11 @@ static const TableStatement* get_table_statement(const ast::Block& node) { } -std::tuple CodegenCVisitor::check_if_var_is_array(const std::string& name) { +std::tuple CodegenCppVisitor::check_if_var_is_array(const std::string& name) { auto symbol = program_symtab->lookup_in_scope(name); if (!symbol) { throw std::runtime_error( - fmt::format("CodegenCVisitor:: {} not found in symbol table!", name)); + fmt::format("CodegenCppVisitor:: {} not found in symbol table!", name)); } if (symbol->is_array()) { return {true, symbol->get_length()}; @@ -1440,7 +1440,7 @@ std::tuple CodegenCVisitor::check_if_var_is_array(const std::string& } -void CodegenCVisitor::print_table_check_function(const Block& node) { +void CodegenCppVisitor::print_table_check_function(const Block& node) { auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); auto depend_variables = statement->get_depend_vars(); @@ -1537,7 +1537,7 @@ void CodegenCVisitor::print_table_check_function(const Block& node) { } -void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { +void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) { auto name = node.get_node_name(); auto statement = get_table_statement(node); auto table_variables = statement->get_table_vars(); @@ -1647,7 +1647,7 @@ void CodegenCVisitor::print_table_replacement_function(const ast::Block& node) { } -void CodegenCVisitor::print_check_table_thread_function() { +void CodegenCppVisitor::print_check_table_thread_function() { if (info.table_count == 0) { return; } @@ -1671,7 +1671,8 @@ void CodegenCVisitor::print_check_table_thread_function() { } -void CodegenCVisitor::print_function_or_procedure(const ast::Block& node, const std::string& name) { +void CodegenCppVisitor::print_function_or_procedure(const ast::Block& node, + const std::string& name) { printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); @@ -1691,7 +1692,7 @@ void CodegenCVisitor::print_function_or_procedure(const ast::Block& node, const } -void CodegenCVisitor::print_function_procedure_helper(const ast::Block& node) { +void CodegenCppVisitor::print_function_procedure_helper(const ast::Block& node) { codegen = true; auto name = node.get_node_name(); @@ -1708,12 +1709,12 @@ void CodegenCVisitor::print_function_procedure_helper(const ast::Block& node) { } -void CodegenCVisitor::print_procedure(const ast::ProcedureBlock& node) { +void CodegenCppVisitor::print_procedure(const ast::ProcedureBlock& node) { print_function_procedure_helper(node); } -void CodegenCVisitor::print_function(const ast::FunctionBlock& node) { +void CodegenCppVisitor::print_function(const ast::FunctionBlock& node) { auto name = node.get_node_name(); // name of return variable @@ -1733,7 +1734,7 @@ void CodegenCVisitor::print_function(const ast::FunctionBlock& node) { } -void CodegenCVisitor::print_function_tables(const ast::FunctionTableBlock& node) { +void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { auto name = node.get_node_name(); const auto& p = node.get_parameters(); auto params = internal_method_parameters(); @@ -1808,7 +1809,7 @@ bool is_functor_const(const ast::StatementBlock& variable_block, return is_functor_const; } -void CodegenCVisitor::print_functor_definition(const ast::EigenNewtonSolverBlock& node) { +void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlock& node) { // functor that evaluates F(X) and J(X) for // Newton solver auto float_type = default_float_data_type(); @@ -1871,7 +1872,7 @@ void CodegenCVisitor::print_functor_definition(const ast::EigenNewtonSolverBlock printer->end_block(";"); } -void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { +void CodegenCppVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { // solution vector to store copy of state vars for Newton solver printer->add_newline(); @@ -1897,7 +1898,7 @@ void CodegenCVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolv printer->add_line("newton_functor.finalize();"); } -void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) { +void CodegenCppVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) { printer->add_newline(); const std::string float_type = default_float_data_type(); @@ -1921,7 +1922,7 @@ void CodegenCVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolv print_statement_block(*node.get_finalize_block(), false, false); } -void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { +void CodegenCppVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { if (N <= 4) { // Faster compared to LU, given the template specialization in Eigen. printer->add_line("bool invertible;"); @@ -1962,7 +1963,7 @@ void CodegenCVisitor::print_eigen_linear_solver(const std::string& float_type, i /****************************************************************************************/ -std::string CodegenCVisitor::internal_method_arguments() { +std::string CodegenCppVisitor::internal_method_arguments() { if (ion_variable_struct_required()) { return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; } @@ -1973,7 +1974,7 @@ std::string CodegenCVisitor::internal_method_arguments() { /** * @todo: figure out how to correctly handle qualifiers */ -CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { +CodegenCppVisitor::ParamVector CodegenCppVisitor::internal_method_parameters() { auto params = ParamVector(); params.emplace_back("", "int", "", "id"); params.emplace_back("", "int", "", "pnodecount"); @@ -1990,12 +1991,12 @@ CodegenCVisitor::ParamVector CodegenCVisitor::internal_method_parameters() { } -std::string CodegenCVisitor::external_method_arguments() { +std::string CodegenCppVisitor::external_method_arguments() { return "id, pnodecount, data, indexes, thread, nt, ml, v"; } -std::string CodegenCVisitor::external_method_parameters(bool table) { +std::string CodegenCppVisitor::external_method_parameters(bool table) { if (table) { return "int id, int pnodecount, double* data, Datum* indexes, " "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, int tml_id"; @@ -2005,7 +2006,7 @@ std::string CodegenCVisitor::external_method_parameters(bool table) { } -std::string CodegenCVisitor::nrn_thread_arguments() { +std::string CodegenCppVisitor::nrn_thread_arguments() { if (ion_variable_struct_required()) { return "id, pnodecount, ionvar, data, indexes, thread, nt, ml, v"; } @@ -2017,7 +2018,7 @@ std::string CodegenCVisitor::nrn_thread_arguments() { * Function call arguments when function or procedure is defined in the * same mod file itself */ -std::string CodegenCVisitor::nrn_thread_internal_arguments() { +std::string CodegenCppVisitor::nrn_thread_internal_arguments() { if (ion_variable_struct_required()) { return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; } @@ -2029,7 +2030,7 @@ std::string CodegenCVisitor::nrn_thread_internal_arguments() { * Replace commonly used variables in the verbatim blocks into their corresponding * variable name in the new code generation backend. */ -std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { +std::string CodegenCppVisitor::replace_if_verbatim_variable(std::string name) { if (naming::VERBATIM_VARIABLES_MAPPING.find(name) != naming::VERBATIM_VARIABLES_MAPPING.end()) { name = naming::VERBATIM_VARIABLES_MAPPING.at(name); } @@ -2058,7 +2059,7 @@ std::string CodegenCVisitor::replace_if_verbatim_variable(std::string name) { * @todo : this is still ad-hoc and requires re-implementation to * handle it more elegantly. */ -std::string CodegenCVisitor::process_verbatim_text(std::string const& text) { +std::string CodegenCppVisitor::process_verbatim_text(std::string const& text) { parser::CDriver driver; driver.scan_string(text); auto tokens = driver.all_tokens(); @@ -2085,7 +2086,7 @@ std::string CodegenCVisitor::process_verbatim_text(std::string const& text) { } -std::string CodegenCVisitor::register_mechanism_arguments() const { +std::string CodegenCppVisitor::register_mechanism_arguments() const { auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "nullptr"; auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr"; auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); @@ -2102,21 +2103,21 @@ std::string CodegenCVisitor::register_mechanism_arguments() const { } -std::pair CodegenCVisitor::read_ion_variable_name( +std::pair CodegenCppVisitor::read_ion_variable_name( const std::string& name) { return {name, naming::ION_VARNAME_PREFIX + name}; } -std::pair CodegenCVisitor::write_ion_variable_name( +std::pair CodegenCppVisitor::write_ion_variable_name( const std::string& name) { return {naming::ION_VARNAME_PREFIX + name, name}; } -std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) { +std::string CodegenCppVisitor::conc_write_statement(const std::string& ion_name, + const std::string& concentration, + int index) { auto conc_var_name = get_variable_name(naming::ION_VARNAME_PREFIX + concentration); auto style_var_name = get_variable_name("style_" + ion_name); return fmt::format( @@ -2142,8 +2143,8 @@ std::string CodegenCVisitor::conc_write_statement(const std::string& ion_name, * case we first update current mechanism's shadow vector and then add statement * to queue that will be used in reduction queue. */ -std::string CodegenCVisitor::process_shadow_update_statement(const ShadowUseStatement& statement, - BlockType /* type */) { +std::string CodegenCppVisitor::process_shadow_update_statement(const ShadowUseStatement& statement, + BlockType /* type */) { // when there is no operator or rhs then that statement doesn't need shadow update if (statement.op.empty() && statement.rhs.empty()) { auto text = statement.lhs + ";"; @@ -2166,7 +2167,7 @@ std::string CodegenCVisitor::process_shadow_update_statement(const ShadowUseStat * NMODL constants from unit database * */ -void CodegenCVisitor::print_nmodl_constants() { +void CodegenCppVisitor::print_nmodl_constants() { if (!info.factor_definitions.empty()) { printer->add_newline(2); printer->add_line("/** constants used in nmodl from UNITS */"); @@ -2178,7 +2179,7 @@ void CodegenCVisitor::print_nmodl_constants() { } -void CodegenCVisitor::print_first_pointer_var_index_getter() { +void CodegenCppVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int first_pointer_var_index()"); @@ -2187,7 +2188,7 @@ void CodegenCVisitor::print_first_pointer_var_index_getter() { } -void CodegenCVisitor::print_num_variable_getter() { +void CodegenCppVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int float_variables_size()"); @@ -2202,7 +2203,7 @@ void CodegenCVisitor::print_num_variable_getter() { } -void CodegenCVisitor::print_net_receive_arg_size_getter() { +void CodegenCppVisitor::print_net_receive_arg_size_getter() { if (!net_receive_exist()) { return; } @@ -2214,7 +2215,7 @@ void CodegenCVisitor::print_net_receive_arg_size_getter() { } -void CodegenCVisitor::print_mech_type_getter() { +void CodegenCppVisitor::print_mech_type_getter() { printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int get_mech_type()"); @@ -2224,7 +2225,7 @@ void CodegenCVisitor::print_mech_type_getter() { } -void CodegenCVisitor::print_memb_list_getter() { +void CodegenCppVisitor::print_memb_list_getter() { printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); @@ -2236,13 +2237,13 @@ void CodegenCVisitor::print_memb_list_getter() { } -void CodegenCVisitor::print_namespace_start() { +void CodegenCppVisitor::print_namespace_start() { printer->add_newline(2); printer->start_block("namespace coreneuron"); } -void CodegenCVisitor::print_namespace_stop() { +void CodegenCppVisitor::print_namespace_stop() { printer->end_block(1); } @@ -2259,7 +2260,7 @@ void CodegenCVisitor::print_namespace_stop() { * decide the index of thread. */ -void CodegenCVisitor::print_thread_getters() { +void CodegenCppVisitor::print_thread_getters() { if (info.vectorize && info.derivimplicit_used()) { int tid = info.derivimplicit_var_thread_id; int list = info.derivimplicit_list_num; @@ -2306,8 +2307,8 @@ void CodegenCVisitor::print_thread_getters() { /****************************************************************************************/ -std::string CodegenCVisitor::float_variable_name(const SymbolType& symbol, - bool use_instance) const { +std::string CodegenCppVisitor::float_variable_name(const SymbolType& symbol, + bool use_instance) const { auto name = symbol->get_name(); auto dimension = symbol->get_length(); auto position = position_of_float_var(name); @@ -2326,9 +2327,9 @@ std::string CodegenCVisitor::float_variable_name(const SymbolType& symbol, } -std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, - const std::string& name, - bool use_instance) const { +std::string CodegenCppVisitor::int_variable_name(const IndexVariableInfo& symbol, + const std::string& name, + bool use_instance) const { auto position = position_of_int_var(name); // clang-format off if (symbol.is_index) { @@ -2352,8 +2353,8 @@ std::string CodegenCVisitor::int_variable_name(const IndexVariableInfo& symbol, } -std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol, - bool use_instance) const { +std::string CodegenCppVisitor::global_variable_name(const SymbolType& symbol, + bool use_instance) const { if (use_instance) { return fmt::format("inst->{}->{}", naming::INST_GLOBAL_MEMBER, symbol->get_name()); } else { @@ -2362,7 +2363,7 @@ std::string CodegenCVisitor::global_variable_name(const SymbolType& symbol, } -std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name) const { +std::string CodegenCppVisitor::update_if_ion_variable_name(const std::string& name) const { std::string result(name); if (ion_variable_struct_required()) { if (info.is_ion_read_variable(name)) { @@ -2379,7 +2380,7 @@ std::string CodegenCVisitor::update_if_ion_variable_name(const std::string& name } -std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use_instance) const { +std::string CodegenCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { std::string varname = update_if_ion_variable_name(name); // clang-format off @@ -2451,7 +2452,7 @@ std::string CodegenCVisitor::get_variable_name(const std::string& name, bool use /****************************************************************************************/ -void CodegenCVisitor::print_backend_info() { +void CodegenCppVisitor::print_backend_info() { time_t tr{}; time(&tr); auto date = std::string(asctime(localtime(&tr))); @@ -2470,7 +2471,7 @@ void CodegenCVisitor::print_backend_info() { } -void CodegenCVisitor::print_standard_includes() { +void CodegenCppVisitor::print_standard_includes() { printer->add_newline(); printer->add_line("#include "); printer->add_line("#include "); @@ -2479,7 +2480,7 @@ void CodegenCVisitor::print_standard_includes() { } -void CodegenCVisitor::print_coreneuron_includes() { +void CodegenCppVisitor::print_coreneuron_includes() { printer->add_newline(); printer->add_line("#include "); printer->add_line("#include "); @@ -2526,7 +2527,7 @@ void CodegenCVisitor::print_coreneuron_includes() { * same for some variables to keep same code as neuron. */ // NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCVisitor::print_mechanism_global_var_structure(bool print_initialisers) { +void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initialisers) { const auto value_initialise = print_initialisers ? "{}" : ""; const auto qualifier = global_var_struct_type_qualifier(); @@ -2726,7 +2727,7 @@ void CodegenCVisitor::print_mechanism_global_var_structure(bool print_initialise print_global_var_struct_decl(); } -void CodegenCVisitor::print_global_var_struct_assertions() const { +void CodegenCppVisitor::print_global_var_struct_assertions() const { // Assert some things that we assume when copying instances of this struct // to the GPU and so on. printer->fmt_line("static_assert(std::is_trivially_copy_constructible_v<{}>);", @@ -2739,14 +2740,14 @@ void CodegenCVisitor::print_global_var_struct_assertions() const { } -void CodegenCVisitor::print_prcellstate_macros() const { +void CodegenCppVisitor::print_prcellstate_macros() const { printer->add_line("#ifndef NRN_PRCELLSTATE"); printer->add_line("#define NRN_PRCELLSTATE 0"); printer->add_line("#endif"); } -void CodegenCVisitor::print_mechanism_info() { +void CodegenCppVisitor::print_mechanism_info() { auto variable_printer = [&](std::vector& variables) { for (const auto& v: variables) { auto name = v->get_name(); @@ -2783,7 +2784,7 @@ void CodegenCVisitor::print_mechanism_info() { * Print structs that encapsulate information about scalar and * vector elements of type global and thread variables. */ -void CodegenCVisitor::print_global_variables_for_hoc() { +void CodegenCppVisitor::print_global_variables_for_hoc() { auto variable_printer = [&](const std::vector& variables, bool if_array, bool if_vector) { for (const auto& variable: variables) { @@ -2889,7 +2890,7 @@ static std::string get_register_type_for_ba_block(const ast::Block* block) { * compatible for cpu as well as gpu target. */ // NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCVisitor::print_mechanism_register() { +void CodegenCppVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); printer->fmt_start_block("void _{}_reg()", info.mod_file); @@ -3031,7 +3032,7 @@ void CodegenCVisitor::print_mechanism_register() { } -void CodegenCVisitor::print_thread_memory_callbacks() { +void CodegenCppVisitor::print_thread_memory_callbacks() { if (!info.thread_callback_register) { return; } @@ -3093,7 +3094,7 @@ void CodegenCVisitor::print_thread_memory_callbacks() { } -void CodegenCVisitor::print_mechanism_range_var_structure(bool print_initialisers) { +void CodegenCppVisitor::print_mechanism_range_var_structure(bool print_initialisers) { auto const value_initialise = print_initialisers ? "{}" : ""; auto float_type = default_float_data_type(); auto int_type = default_int_data_type(); @@ -3136,7 +3137,7 @@ void CodegenCVisitor::print_mechanism_range_var_structure(bool print_initialiser } -void CodegenCVisitor::print_ion_var_structure() { +void CodegenCppVisitor::print_ion_var_structure() { if (!ion_variable_struct_required()) { return; } @@ -3166,7 +3167,7 @@ void CodegenCVisitor::print_ion_var_structure() { } -void CodegenCVisitor::print_ion_var_constructor(const std::vector& members) { +void CodegenCppVisitor::print_ion_var_constructor(const std::vector& members) { // constructor printer->add_newline(); printer->add_line("IonCurVar() : ", 0); @@ -3181,17 +3182,17 @@ void CodegenCVisitor::print_ion_var_constructor(const std::vector& } -void CodegenCVisitor::print_ion_variable() { +void CodegenCppVisitor::print_ion_variable() { printer->add_line("IonCurVar ionvar;"); } -void CodegenCVisitor::print_global_variable_device_update_annotation() { +void CodegenCppVisitor::print_global_variable_device_update_annotation() { // nothing for cpu } -void CodegenCVisitor::print_setup_range_variable() { +void CodegenCppVisitor::print_setup_range_variable() { auto type = float_data_type(); printer->add_newline(2); printer->add_line("/** allocate and setup array for range variable */"); @@ -3212,7 +3213,7 @@ void CodegenCVisitor::print_setup_range_variable() { * are pointers to internal variables (e.g. ions). Hence, we check if given * variable can be safely converted to new type. If so, return new type. */ -std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) { +std::string CodegenCppVisitor::get_range_var_float_type(const SymbolType& symbol) { // clang-format off auto with = NmodlType::read_ion_var | NmodlType::write_ion_var @@ -3228,7 +3229,7 @@ std::string CodegenCVisitor::get_range_var_float_type(const SymbolType& symbol) } -void CodegenCVisitor::print_instance_variable_setup() { +void CodegenCppVisitor::print_instance_variable_setup() { if (range_variable_setup_required()) { print_setup_range_variable(); } @@ -3333,7 +3334,7 @@ void CodegenCVisitor::print_instance_variable_setup() { } -void CodegenCVisitor::print_initial_block(const InitialBlock* node) { +void CodegenCppVisitor::print_initial_block(const InitialBlock* node) { if (info.artificial_cell) { printer->add_line("double v = 0.0;"); } else { @@ -3377,8 +3378,8 @@ void CodegenCVisitor::print_initial_block(const InitialBlock* node) { } -void CodegenCVisitor::print_global_function_common_code(BlockType type, - const std::string& function_name) { +void CodegenCppVisitor::print_global_function_common_code(BlockType type, + const std::string& function_name) { std::string method; if (function_name.empty()) { method = compute_method_name(type); @@ -3425,7 +3426,7 @@ void CodegenCVisitor::print_global_function_common_code(BlockType type, printer->add_newline(1); } -void CodegenCVisitor::print_nrn_init(bool skip_init_check) { +void CodegenCppVisitor::print_nrn_init(bool skip_init_check) { codegen = true; printer->add_newline(2); printer->add_line("/** initialize channel */"); @@ -3502,7 +3503,7 @@ void CodegenCVisitor::print_nrn_init(bool skip_init_check) { codegen = false; } -void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t block_id) { +void CodegenCppVisitor::print_before_after_block(const ast::Block* node, size_t block_id) { codegen = true; std::string ba_type; @@ -3559,7 +3560,7 @@ void CodegenCVisitor::print_before_after_block(const ast::Block* node, size_t bl codegen = false; } -void CodegenCVisitor::print_nrn_constructor() { +void CodegenCppVisitor::print_nrn_constructor() { printer->add_newline(2); print_global_function_common_code(BlockType::Constructor); if (info.constructor_node != nullptr) { @@ -3571,7 +3572,7 @@ void CodegenCVisitor::print_nrn_constructor() { } -void CodegenCVisitor::print_nrn_destructor() { +void CodegenCppVisitor::print_nrn_destructor() { printer->add_newline(2); print_global_function_common_code(BlockType::Destructor); if (info.destructor_node != nullptr) { @@ -3583,7 +3584,7 @@ void CodegenCVisitor::print_nrn_destructor() { } -void CodegenCVisitor::print_functors_definitions() { +void CodegenCppVisitor::print_functors_definitions() { codegen = true; for (const auto& functor_name: info.functor_names) { printer->add_newline(2); @@ -3593,7 +3594,7 @@ void CodegenCVisitor::print_functors_definitions() { } -void CodegenCVisitor::print_nrn_alloc() { +void CodegenCppVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); printer->fmt_start_block("static void {}(double* data, Datum* indexes, int type)", method); @@ -3606,7 +3607,7 @@ void CodegenCVisitor::print_nrn_alloc() { * according to grammar. Check if this is correctly handled in neuron * and coreneuron. */ -void CodegenCVisitor::print_watch_activate() { +void CodegenCppVisitor::print_watch_activate() { if (info.watch_statements.empty()) { return; } @@ -3655,7 +3656,7 @@ void CodegenCVisitor::print_watch_activate() { * \todo Similar to print_watch_activate, we are using only * first watch. need to verify with neuron/coreneuron about rest. */ -void CodegenCVisitor::print_watch_check() { +void CodegenCppVisitor::print_watch_check() { if (info.watch_statements.empty()) { return; } @@ -3738,7 +3739,7 @@ void CodegenCVisitor::print_watch_check() { } -void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need_mech_inst) { +void CodegenCppVisitor::print_net_receive_common_code(const Block& node, bool need_mech_inst) { printer->add_line("int tid = pnt->_tid;"); printer->add_line("int id = pnt->_i_instance;"); printer->add_line("double v = 0;"); @@ -3783,7 +3784,7 @@ void CodegenCVisitor::print_net_receive_common_code(const Block& node, bool need } -void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { +void CodegenCppVisitor::print_net_send_call(const FunctionCall& node) { auto const& arguments = node.get_arguments(); auto tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; @@ -3815,7 +3816,7 @@ void CodegenCVisitor::print_net_send_call(const FunctionCall& node) { } -void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { +void CodegenCppVisitor::print_net_move_call(const FunctionCall& node) { if (!printing_net_receive && !printing_net_init) { throw std::runtime_error("Error : net_move only allowed in NET_RECEIVE block"); } @@ -3843,7 +3844,7 @@ void CodegenCVisitor::print_net_move_call(const FunctionCall& node) { } -void CodegenCVisitor::print_net_event_call(const FunctionCall& node) { +void CodegenCppVisitor::print_net_event_call(const FunctionCall& node) { const auto& arguments = node.get_arguments(); if (info.artificial_cell) { printer->add_text("net_event(pnt, "); @@ -3895,7 +3896,7 @@ static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive } -void CodegenCVisitor::print_net_init() { +void CodegenCppVisitor::print_net_init() { const auto node = info.net_receive_initial_node; if (node == nullptr) { return; @@ -3929,7 +3930,7 @@ void CodegenCVisitor::print_net_init() { } -void CodegenCVisitor::print_send_event_move() { +void CodegenCppVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); print_net_send_buf_update_to_host(); @@ -3948,12 +3949,12 @@ void CodegenCVisitor::print_send_event_move() { } -std::string CodegenCVisitor::net_receive_buffering_declaration() { +std::string CodegenCppVisitor::net_receive_buffering_declaration() { return fmt::format("void {}(NrnThread* nt)", method_name("net_buf_receive")); } -void CodegenCVisitor::print_get_memb_list() { +void CodegenCppVisitor::print_get_memb_list() { printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->start_block("if (!ml)"); printer->add_line("return;"); @@ -3961,19 +3962,19 @@ void CodegenCVisitor::print_get_memb_list() { } -void CodegenCVisitor::print_net_receive_loop_begin() { +void CodegenCppVisitor::print_net_receive_loop_begin() { printer->add_line("int count = nrb->_displ_cnt;"); print_channel_iteration_block_parallel_hint(BlockType::NetReceive, info.net_receive_node); printer->start_block("for (int i = 0; i < count; i++)"); } -void CodegenCVisitor::print_net_receive_loop_end() { +void CodegenCppVisitor::print_net_receive_loop_end() { printer->end_block(1); } -void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { +void CodegenCppVisitor::print_net_receive_buffering(bool need_mech_inst) { if (!net_receive_required() || info.artificial_cell) { return; } @@ -4016,17 +4017,17 @@ void CodegenCVisitor::print_net_receive_buffering(bool need_mech_inst) { printer->end_block(1); } -void CodegenCVisitor::print_net_send_buffering_cnt_update() const { +void CodegenCppVisitor::print_net_send_buffering_cnt_update() const { printer->add_line("i = nsb->_cnt++;"); } -void CodegenCVisitor::print_net_send_buffering_grow() { +void CodegenCppVisitor::print_net_send_buffering_grow() { printer->start_block("if (i >= nsb->_size)"); printer->add_line("nsb->grow();"); printer->end_block(1); } -void CodegenCVisitor::print_net_send_buffering() { +void CodegenCppVisitor::print_net_send_buffering() { if (!net_send_buffer_required()) { return; } @@ -4052,7 +4053,7 @@ void CodegenCVisitor::print_net_send_buffering() { } -void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { +void CodegenCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { // For_netcon should take the same arguments as net_receive and apply the operations // in the block to the weights of the netcons. Since all the weights are on the same vector, // weights, we have a mask of operations that we apply iteratively, advancing the offset @@ -4089,7 +4090,7 @@ void CodegenCVisitor::visit_for_netcon(const ast::ForNetcon& node) { printer->add_line("}"); } -void CodegenCVisitor::print_net_receive_kernel() { +void CodegenCppVisitor::print_net_receive_kernel() { if (!net_receive_required()) { return; } @@ -4150,7 +4151,7 @@ void CodegenCVisitor::print_net_receive_kernel() { } -void CodegenCVisitor::print_net_receive() { +void CodegenCppVisitor::print_net_receive() { if (!net_receive_required()) { return; } @@ -4190,7 +4191,7 @@ void CodegenCVisitor::print_net_receive() { * actual variable names? [resolved now?] * slist needs to added as local variable */ -void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { +void CodegenCppVisitor::print_derivimplicit_kernel(Block* block) { auto ext_args = external_method_arguments(); auto ext_params = external_method_parameters(); auto suffix = info.mod_suffix; @@ -4277,12 +4278,12 @@ void CodegenCVisitor::print_derivimplicit_kernel(Block* block) { } -void CodegenCVisitor::print_newtonspace_transfer_to_device() const { +void CodegenCppVisitor::print_newtonspace_transfer_to_device() const { // nothing to do on cpu } -void CodegenCVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { +void CodegenCppVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { if (!codegen) { return; } @@ -4292,7 +4293,7 @@ void CodegenCVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallb external_method_arguments()); } -void CodegenCVisitor::visit_solution_expression(const SolutionExpression& node) { +void CodegenCppVisitor::visit_solution_expression(const SolutionExpression& node) { auto block = node.get_node_to_solve().get(); if (block->is_statement_block()) { auto statement_block = dynamic_cast(block); @@ -4308,7 +4309,7 @@ void CodegenCVisitor::visit_solution_expression(const SolutionExpression& node) /****************************************************************************************/ -void CodegenCVisitor::print_nrn_state() { +void CodegenCppVisitor::print_nrn_state() { if (!nrn_state_required()) { return; } @@ -4365,7 +4366,7 @@ void CodegenCVisitor::print_nrn_state() { /****************************************************************************************/ -void CodegenCVisitor::print_nrn_current(const BreakpointBlock& node) { +void CodegenCppVisitor::print_nrn_current(const BreakpointBlock& node) { auto args = internal_method_parameters(); const auto& block = node.get_statement_block(); printer->add_newline(2); @@ -4384,7 +4385,7 @@ void CodegenCVisitor::print_nrn_current(const BreakpointBlock& node) { } -void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { +void CodegenCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { const auto& block = node.get_statement_block(); print_statement_block(*block, false, false); if (!info.currents.empty()) { @@ -4421,7 +4422,7 @@ void CodegenCVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& no } -void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { +void CodegenCppVisitor::print_nrn_cur_non_conductance_kernel() { printer->fmt_line("double g = nrn_current_{}({}+0.001);", info.mod_suffix, internal_method_arguments()); @@ -4455,7 +4456,7 @@ void CodegenCVisitor::print_nrn_cur_non_conductance_kernel() { } -void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { +void CodegenCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); print_v_unused(); @@ -4490,7 +4491,7 @@ void CodegenCVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { print_g_unused(); } -void CodegenCVisitor::print_fast_imem_calculation() { +void CodegenCppVisitor::print_fast_imem_calculation() { if (!info.electrode_current) { return; } @@ -4518,7 +4519,7 @@ void CodegenCVisitor::print_fast_imem_calculation() { printer->end_block(1); } -void CodegenCVisitor::print_nrn_cur() { +void CodegenCppVisitor::print_nrn_cur() { if (!nrn_cur_required()) { return; } @@ -4557,26 +4558,26 @@ void CodegenCVisitor::print_nrn_cur() { /* Main code printing entry points */ /****************************************************************************************/ -void CodegenCVisitor::print_headers_include() { +void CodegenCppVisitor::print_headers_include() { print_standard_includes(); print_backend_includes(); print_coreneuron_includes(); } -void CodegenCVisitor::print_namespace_begin() { +void CodegenCppVisitor::print_namespace_begin() { print_namespace_start(); print_backend_namespace_start(); } -void CodegenCVisitor::print_namespace_end() { +void CodegenCppVisitor::print_namespace_end() { print_backend_namespace_stop(); print_namespace_stop(); } -void CodegenCVisitor::print_common_getters() { +void CodegenCppVisitor::print_common_getters() { print_first_pointer_var_index_getter(); print_net_receive_arg_size_getter(); print_thread_getters(); @@ -4586,13 +4587,13 @@ void CodegenCVisitor::print_common_getters() { } -void CodegenCVisitor::print_data_structures(bool print_initialisers) { +void CodegenCppVisitor::print_data_structures(bool print_initialisers) { print_mechanism_global_var_structure(print_initialisers); print_mechanism_range_var_structure(print_initialisers); print_ion_var_structure(); } -void CodegenCVisitor::print_v_unused() const { +void CodegenCppVisitor::print_v_unused() const { if (!info.vectorize) { return; } @@ -4601,13 +4602,13 @@ void CodegenCVisitor::print_v_unused() const { printer->add_line("#endif"); } -void CodegenCVisitor::print_g_unused() const { +void CodegenCppVisitor::print_g_unused() const { printer->add_line("#if NRN_PRCELLSTATE"); printer->add_line("inst->g_unused[id] = g;"); printer->add_line("#endif"); } -void CodegenCVisitor::print_compute_functions() { +void CodegenCppVisitor::print_compute_functions() { print_top_verbatim_blocks(); for (const auto& procedure: info.procedures) { print_procedure(*procedure); @@ -4638,7 +4639,7 @@ void CodegenCVisitor::print_compute_functions() { } -void CodegenCVisitor::print_codegen_routines() { +void CodegenCppVisitor::print_codegen_routines() { codegen = true; print_backend_info(); print_headers_include(); @@ -4666,17 +4667,17 @@ void CodegenCVisitor::print_codegen_routines() { } -void CodegenCVisitor::print_wrapper_routines() { +void CodegenCppVisitor::print_wrapper_routines() { // nothing to do } -void CodegenCVisitor::set_codegen_global_variables(std::vector& global_vars) { +void CodegenCppVisitor::set_codegen_global_variables(std::vector& global_vars) { codegen_global_variables = global_vars; } -void CodegenCVisitor::setup(const Program& node) { +void CodegenCppVisitor::setup(const Program& node) { program_symtab = node.get_symbol_table(); CodegenHelperVisitor v; @@ -4684,7 +4685,7 @@ void CodegenCVisitor::setup(const Program& node) { info.mod_file = mod_filename; if (!info.vectorize) { - logger->warn("CodegenCVisitor : MOD file uses non-thread safe constructs of NMODL"); + logger->warn("CodegenCppVisitor : MOD file uses non-thread safe constructs of NMODL"); } codegen_float_variables = get_float_variables(); @@ -4695,7 +4696,7 @@ void CodegenCVisitor::setup(const Program& node) { } -void CodegenCVisitor::visit_program(const Program& node) { +void CodegenCppVisitor::visit_program(const Program& node) { setup(node); print_codegen_routines(); print_wrapper_routines(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 6ab2f3cb0b..09e6f0bd0a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -12,7 +12,7 @@ * \brief Code generation backend implementations for CoreNEURON * * \file - * \brief \copybrief nmodl::codegen::CodegenCVisitor + * \brief \copybrief nmodl::codegen::CodegenCppVisitor */ #include @@ -167,7 +167,7 @@ using printer::CodePrinter; */ /** - * \class CodegenCVisitor + * \class CodegenCppVisitor * \brief %Visitor for printing C code compatible with legacy api of CoreNEURON * * \todo @@ -177,7 +177,7 @@ using printer::CodePrinter; * error checking. For example, see netstim.mod where we * have removed return from verbatim block. */ -class CodegenCVisitor: public visitor::ConstAstVisitor { +class CodegenCppVisitor: public visitor::ConstAstVisitor { protected: using SymbolType = std::shared_ptr; @@ -1428,7 +1428,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * Print the \c nrn\_cur kernel with NMODL \c conductance keyword provisions * * If the NMODL \c conductance keyword is used in the \c breakpoint block, then - * CodegenCVisitor::print_nrn_cur_kernel will use this printer + * CodegenCppVisitor::print_nrn_cur_kernel will use this printer * * \param node the AST node representing the NMODL breakpoint block */ @@ -1439,7 +1439,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * Print the \c nrn\_cur kernel without NMODL \c conductance keyword provisions * * If the NMODL \c conductance keyword is \b not used in the \c breakpoint block, then - * CodegenCVisitor::print_nrn_cur_kernel will use this printer + * CodegenCppVisitor::print_nrn_cur_kernel will use this printer */ void print_nrn_cur_non_conductance_kernel(); @@ -1577,12 +1577,12 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { virtual void print_wrapper_routines(); - CodegenCVisitor(const std::string& mod_filename, - const std::string& output_dir, - const std::string& float_type, - const bool optimize_ionvar_copies, - const std::string& extension, - const std::string& wrapper_ext) + CodegenCppVisitor(const std::string& mod_filename, + const std::string& output_dir, + const std::string& float_type, + const bool optimize_ionvar_copies, + const std::string& extension, + const std::string& wrapper_ext) : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , wrapper_printer(new CodePrinter(output_dir + "/" + mod_filename + wrapper_ext)) , printer(target_printer) @@ -1590,12 +1590,12 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { , float_type(float_type) , optimize_ionvar_copies(optimize_ionvar_copies) {} - CodegenCVisitor(const std::string& mod_filename, - std::ostream& stream, - const std::string& float_type, - const bool optimize_ionvar_copies, - const std::string& extension, - const std::string& wrapper_ext) + CodegenCppVisitor(const std::string& mod_filename, + std::ostream& stream, + const std::string& float_type, + const bool optimize_ionvar_copies, + const std::string& extension, + const std::string& wrapper_ext) : target_printer(new CodePrinter(stream)) , wrapper_printer(new CodePrinter(stream)) , printer(target_printer) @@ -1622,11 +1622,11 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * as-is in the target code. This defaults to \c double. * \param extension The file extension to use. This defaults to \c .cpp . */ - CodegenCVisitor(const std::string& mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies, - const std::string& extension = ".cpp") + CodegenCppVisitor(const std::string& mod_filename, + const std::string& output_dir, + std::string float_type, + const bool optimize_ionvar_copies, + const std::string& extension = ".cpp") : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) , printer(target_printer) , mod_filename(mod_filename) @@ -1634,7 +1634,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { , optimize_ionvar_copies(optimize_ionvar_copies) {} /** - * \copybrief nmodl::codegen::CodegenCVisitor + * \copybrief nmodl::codegen::CodegenCppVisitor * * This constructor instantiates an NMODL C code generator and allows writing generated code * into an output stream. @@ -1649,10 +1649,10 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. */ - CodegenCVisitor(const std::string& mod_filename, - std::ostream& stream, - const std::string& float_type, - const bool optimize_ionvar_copies) + CodegenCppVisitor(const std::string& mod_filename, + std::ostream& stream, + const std::string& float_type, + const bool optimize_ionvar_copies) : target_printer(new CodePrinter(stream)) , printer(target_printer) , mod_filename(mod_filename) @@ -1661,7 +1661,7 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { /** - * \copybrief nmodl::codegen::CodegenCVisitor + * \copybrief nmodl::codegen::CodegenCppVisitor * * This constructor instantiates an NMODL C code generator and allows writing generated code * using an nmodl::printer::CodePrinter defined elsewhere. @@ -1677,10 +1677,10 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { * \param target_printer A printer defined outside this visitor to be used for the code * generation */ - CodegenCVisitor(std::string mod_filename, - std::string float_type, - const bool optimize_ionvar_copies, - std::shared_ptr& target_printer) + CodegenCppVisitor(std::string mod_filename, + std::string float_type, + const bool optimize_ionvar_copies, + std::shared_ptr& target_printer) : target_printer(target_printer) , printer(target_printer) , mod_filename(mod_filename) @@ -1866,9 +1866,9 @@ class CodegenCVisitor: public visitor::ConstAstVisitor { template -void CodegenCVisitor::print_vector_elements(const std::vector& elements, - const std::string& separator, - const std::string& prefix) { +void CodegenCppVisitor::print_vector_elements(const std::vector& elements, + const std::string& separator, + const std::string& prefix) { for (auto iter = elements.begin(); iter != elements.end(); iter++) { printer->add_text(prefix); (*iter)->accept(*this); @@ -1906,7 +1906,7 @@ bool has_parameter_of_name(const T& node, const std::string& name) { * different in case of table statement. */ template -void CodegenCVisitor::print_function_declaration(const T& node, const std::string& name) { +void CodegenCppVisitor::print_function_declaration(const T& node, const std::string& name) { enable_variable_name_lookup = false; auto type = default_float_data_type(); diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index a41bb019f2..5f7b623239 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -21,7 +21,7 @@ namespace utils { * representation (1e+20, 1E-15) then keep it as it is. */ template <> -std::string format_double_string(const std::string& s_value) { +std::string format_double_string(const std::string& s_value) { double value = std::stod(s_value); if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { return fmt::format("{:.1f}", value); @@ -31,7 +31,7 @@ std::string format_double_string(const std::string& s_value) { template <> -std::string format_float_string(const std::string& s_value) { +std::string format_float_string(const std::string& s_value) { float value = std::stof(s_value); if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { return fmt::format("{:.1f}", value); diff --git a/src/nmodl/codegen/codegen_utils.hpp b/src/nmodl/codegen/codegen_utils.hpp index 0180b9ec3d..4c910b36b5 100644 --- a/src/nmodl/codegen/codegen_utils.hpp +++ b/src/nmodl/codegen/codegen_utils.hpp @@ -24,7 +24,7 @@ namespace utils { * * It takes care of printing the values with the correct floating point precision * for each backend, similar to mod2c and Neuron. - * This function can be called using as template `CodegenCVisitor` + * This function can be called using as template `CodegenCppVisitor` * * \param s_value The double constant as string * \return The proper string to be printed in the generated file. @@ -38,7 +38,7 @@ std::string format_double_string(const std::string& s_value); * * It takes care of printing the values with the correct floating point precision * for each backend, similar to mod2c and Neuron. - * This function can be called using as template `CodegenCVisitor` + * This function can be called using as template `CodegenCppVisitor` * * \param s_value The double constant as string * \return The proper string to be printed in the generated file. diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index c0495b8325..2f31f99f56 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -530,10 +530,10 @@ int main(int argc, const char* argv[]) { else if (c_backend) { logger->info("Running C backend code generator"); - CodegenCVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); + CodegenCppVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } } diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index b71502f1f6..2508990917 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -63,7 +63,7 @@ std::string generate_random_string(int len, UseNumbersInString use_numbers); * Singleton class for random strings that are appended to the * Eigen matrices names that are used in the solutions of * nmodl::visitor::SympySolverVisitor and need to be the same to - * be printed by the nmodl::codegen::CodegenCVisitor + * be printed by the nmodl::codegen::CodegenCppVisitor */ template class SingletonRandomString { diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index aa9d509c8e..e2ca7291ae 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -32,9 +32,9 @@ using nmodl::parser::NmodlDriver; using nmodl::test_utils::reindent_text; /// Helper for creating C codegen visitor -std::shared_ptr create_c_visitor(const std::shared_ptr& ast, - const std::string& /* text */, - std::stringstream& ss) { +std::shared_ptr create_c_visitor(const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss) { /// construct symbol table SymtabVisitor().visit_program(*ast); @@ -44,7 +44,7 @@ std::shared_ptr create_c_visitor(const std::shared_ptr("temp.mod", ss, "double", false); + auto cv = std::make_shared("temp.mod", ss, "double", false); cv->setup(*ast); return cv; } @@ -311,7 +311,7 @@ std::string get_instance_structure(std::string nmodl_text) { PerfVisitor{}.visit_program(*ast); // setup codegen std::stringstream ss{}; - CodegenCVisitor cv{"temp.mod", ss, "double", false}; + CodegenCppVisitor cv{"temp.mod", ss, "double", false}; cv.setup(*ast); cv.print_mechanism_range_var_structure(true); return ss.str(); diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index 20180ec3e8..0438cf38c3 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -21,7 +21,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string double_constant = "0.012345678901234567"; THEN("Codegen C Visitor prints double with same precision") { - auto nmodl_constant_result = codegen::utils::format_double_string( + auto nmodl_constant_result = codegen::utils::format_double_string( double_constant); REQUIRE(nmodl_constant_result == double_constant); } @@ -33,7 +33,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string codegen_output = "1.0"; THEN("Codegen C Visitor prints integer as double number") { - auto nmodl_constant_result = codegen::utils::format_double_string( + auto nmodl_constant_result = codegen::utils::format_double_string( double_constant); REQUIRE(nmodl_constant_result == codegen_output); } @@ -44,7 +44,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { THEN("Codegen C Visitor prints doubles with scientific notation") { for (const auto& test: tests) { - REQUIRE(codegen::utils::format_double_string(test.first) == + REQUIRE(codegen::utils::format_double_string(test.first) == test.second); } } @@ -54,7 +54,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string float_constant = "0.01234567"; THEN("Codegen C Visitor prints float with same precision") { - auto nmodl_constant_result = codegen::utils::format_float_string( + auto nmodl_constant_result = codegen::utils::format_float_string( float_constant); REQUIRE(nmodl_constant_result == float_constant); } @@ -66,7 +66,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string codegen_output = "1.0"; THEN("Codegen C Visitor prints integer as double number") { - auto nmodl_constant_result = codegen::utils::format_float_string( + auto nmodl_constant_result = codegen::utils::format_float_string( float_constant); REQUIRE(nmodl_constant_result == codegen_output); } @@ -77,7 +77,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { THEN("Codegen C Visitor prints doubles with scientific notation") { for (const auto& test: tests) { - REQUIRE(codegen::utils::format_float_string(test.first) == + REQUIRE(codegen::utils::format_float_string(test.first) == test.second); } } diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 6768438745..18fb307b39 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -2237,12 +2237,12 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym } /// Helper for creating C codegen visitor -std::shared_ptr create_c_visitor(const std::shared_ptr& ast, - const std::string& /* text */, - std::stringstream& ss, - bool inline_visitor = true, - bool pade = false, - bool cse = false) { +std::shared_ptr create_c_visitor(const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss, + bool inline_visitor = true, + bool pade = false, + bool cse = false) { /// construct symbol table SymtabVisitor().visit_program(*ast); @@ -2264,14 +2264,14 @@ std::shared_ptr create_c_visitor(const std::shared_ptr("temp.mod", ss, "double", false); + auto cv = std::make_shared("temp.mod", ss, "double", false); cv->setup(*ast); return cv; } From b444b5e5a6c4ea41f0bfd5a4935a5f3c11a81b1d Mon Sep 17 00:00:00 2001 From: David McDougall Date: Fri, 1 Sep 2023 08:49:08 -0400 Subject: [PATCH 523/871] Improve the example in the python tutorial. (BlueBrain/nmodl#1019) The python code-generation example previously failed to handle wrapped expressions like: "(a * (b + c))". Fix typo in nmodl python tutorial. NMODL Repo SHA: BlueBrain/nmodl@638b8706d52c69ac5b9ae8f1cbeb8291da34cb96 --- .../nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb index 1ae6802f0c..62e663f0a2 100644 --- a/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb +++ b/docs/nmodl/transpiler/notebooks/nmodl-python-tutorial.ipynb @@ -682,6 +682,11 @@ " self.pycode += \" \" * 4 * self.indent + \"else:\\n\"\n", " node.get_statement_block().accept(self)\n", "\n", + " def visit_wrapped_expression(self, node):\n", + " self.pycode += \"(\"\n", + " node.visit_children(self)\n", + " self.pycode += \")\"\n", + "\n", " def visit_binary_expression(self, node):\n", " lhs = node.lhs\n", " rhs = node.rhs\n", @@ -701,7 +706,7 @@ " self.pycode += node.name.get_node_name()\n", "\n", " def visit_integer(self, node):\n", - " self.pycode += nmod.to_nmodl(node)\n", + " self.pycode += nmodl.to_nmodl(node)\n", "\n", " def visit_double(self, node):\n", " self.pycode += nmodl.to_nmodl(node)" From b8d4ea9f15aaafd7f8f5c5c1e2a94c7cc53c67a2 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 4 Sep 2023 11:27:56 +0200 Subject: [PATCH 524/871] Fix file copying for Python helper files. (BlueBrain/nmodl#1057) * Copy Python files once. * Remove redundant code. * Glob with CONFIGURE_DEPEND. NMODL Repo SHA: BlueBrain/nmodl@c4ab15e2bd977cb5b6bc87177e5a9f22697fe8bb --- src/nmodl/pybind/CMakeLists.txt | 30 ++++++------------- .../transpiler/integration/CMakeLists.txt | 2 +- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 02dbd4d743..9600515e11 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -19,11 +19,6 @@ endif() # build nmodl python module under lib/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl) -foreach(file ast.py dsl.py ode.py symtab.py visitor.py __init__.py) - list(APPEND NMODL_PYTHON_FILES_IN ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file}) - list(APPEND NMODL_PYTHON_FILES_OUT ${PROJECT_BINARY_DIR}/lib/nmodl/${file}) -endforeach() - add_library(pyembed pyembed.cpp) set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(pyembed PRIVATE util) @@ -68,27 +63,20 @@ if(NMODL_ENABLE_PYTHON_BINDINGS) if(LINK_AGAINST_PYTHON) target_link_libraries(_nmodl PRIVATE pywrapper) endif() - - add_custom_command( - OUTPUT ${PROJECT_BINARY_DIR}/lib/nmodl - COMMAND ${CMAKE_COMMAND} -E copy_if_different $ ${PROJECT_BINARY_DIR}/nmodl - DEPENDS ${NMODL_PYTHON_FILES_IN} _nmodl - COMMENT "-- COPYING NMODL PYTHON MODULE --") endif() -add_custom_target(copy_python_files ALL DEPENDS ${NMODL_PYTHON_FILES_OUT}) -add_custom_command( - OUTPUT ${NMODL_PYTHON_FILES_OUT} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${NMODL_PROJECT_SOURCE_DIR}/nmodl - ${PROJECT_BINARY_DIR}/lib/nmodl - DEPENDS ${NMODL_PYTHON_FILES_IN} - COMMENT "-- COPYING NMODL PYTHON FILES --") - # ============================================================================= # Copy python binding components and examples into build directory # ============================================================================= -file(GLOB NMODL_PYTHON_HELPER_FILES "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") -file(COPY ${NMODL_PYTHON_HELPER_FILES} DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) +file( + GLOB NMODL_PYTHON_FILES + RELATIVE "${NMODL_PROJECT_SOURCE_DIR}/nmodl/" + CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") + +foreach(file IN LISTS NMODL_PYTHON_FILES) + cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file} OUTPUT + ${CMAKE_BINARY_DIR}/lib/nmodl/${file}) +endforeach() file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) # ============================================================================= diff --git a/test/nmodl/transpiler/integration/CMakeLists.txt b/test/nmodl/transpiler/integration/CMakeLists.txt index 82c72a6a40..29bbd41ae3 100644 --- a/test/nmodl/transpiler/integration/CMakeLists.txt +++ b/test/nmodl/transpiler/integration/CMakeLists.txt @@ -7,7 +7,7 @@ add_link_options(${NMODL_EXTRA_CXX_FLAGS}) # ============================================================================= # translation of mod files # ============================================================================= -file(GLOB modfiles "${NMODL_PROJECT_SOURCE_DIR}/test/integration/mod/*.mod") +file(GLOB modfiles CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/test/integration/mod/*.mod") foreach(modfile ${modfiles}) get_filename_component(modfile_name "${modfile}" NAME) add_test(NAME ${modfile_name} COMMAND ${CMAKE_BINARY_DIR}/bin/nmodl ${modfile}) From e605496159d0546665192e015e36835346d96dae Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 5 Sep 2023 14:35:36 +0200 Subject: [PATCH 525/871] Revert order of CPU vectorization pragmas (BlueBrain/nmodl#1062) * Revert order of CPU vectorization pragmas to support newer Intel compiler * Fix tests NMODL Repo SHA: BlueBrain/nmodl@bb1ce0ceb6805c04370ecb20e3041347f17de4d6 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- .../nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 946fca68ff..3bb71a0c98 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1117,8 +1117,8 @@ void CodegenCppVisitor::print_channel_iteration_block_parallel_hint(BlockType /* ast::AstNodeType::MUTEX_UNLOCK}); } if (nodes.empty()) { - printer->add_line("#pragma ivdep"); printer->add_line("#pragma omp simd"); + printer->add_line("#pragma ivdep"); } } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index e2ca7291ae..967c5d3267 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -569,8 +569,8 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_2_ba1, " "BAType::Before + BAType::Initial);")); std::string generated_code = R"( - #pragma ivdep #pragma omp simd + #pragma ivdep for (int id = 0; id < nodecount; id++) { int node_id = node_index[id]; double v = voltage[node_id]; @@ -591,8 +591,8 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_3_ba1, " "BAType::After + BAType::Initial);")); std::string generated_code = R"( - #pragma ivdep #pragma omp simd + #pragma ivdep for (int id = 0; id < nodecount; id++) { int node_id = node_index[id]; double v = voltage[node_id]; @@ -613,8 +613,8 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_4_ba1, " "BAType::Before + BAType::Step);")); std::string generated_code = R"( - #pragma ivdep #pragma omp simd + #pragma ivdep for (int id = 0; id < nodecount; id++) { int node_id = node_index[id]; double v = voltage[node_id]; @@ -890,8 +890,8 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even NetReceiveBuffer_t* nrb = ml->_net_receive_buffer; auto* const inst = static_cast<_Instance*>(ml->instance); int count = nrb->_displ_cnt; - #pragma ivdep #pragma omp simd + #pragma ivdep for (int i = 0; i < count; i++) { int start = nrb->_displ[i]; int end = nrb->_displ[i+1]; From 0a942218a6272c48d075829904e4b0b6142ad7e5 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 5 Sep 2023 16:33:52 +0200 Subject: [PATCH 526/871] Generated files have regular read-write permissions (BlueBrain/nmodl#1056) Prior to this commit generated files were created read-only. When updating files, one would add write permissions and then overwrite the file. Even if it was read-only. This commit changes the behaviour to generate files with regular (read & write) permissions and doesn't change the permissions of files, e.g. it wont overwrite read-only files. NMODL Repo SHA: BlueBrain/nmodl@52ce1b3859707b1556364fa39205a03688ad4c26 --- src/nmodl/language/code_generator.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 14de94ee28..63fa9743dd 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -303,7 +303,7 @@ def language(self): raise Exception("Unexpected output file extension: " + suffix) def render(self): - """Call Jinja renderer to create the output file and mark it read-only + """Call Jinja renderer to create the output file. The output file is updated only if missing or if the new content is different. @@ -322,10 +322,6 @@ def render(self): self.format_output(Path(tmp_path)) if not filecmp.cmp(str(self.output), tmp_path, shallow=False): self.logger.debug("previous output differs, updating it") - # ensure destination file has write permissions - mode = self.output.stat().st_mode - rm_write_mask = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH - self.output.chmod(mode | rm_write_mask) shutil.copy2(tmp_path, self.output) updated = True else: @@ -333,10 +329,7 @@ def render(self): fd.write(content) self.format_output(self.output) updated = True - # remove write permissions on the generated file - mode = self.output.stat().st_mode - rm_write_mask = ~(stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH) - self.output.chmod(mode & rm_write_mask) + return updated From 9f8b0773c9b6a58f5ccc212994f75454d3993abc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 6 Sep 2023 09:32:14 +0200 Subject: [PATCH 527/871] Fix reference to local variable (BlueBrain/nmodl#1060) * Fix new dangling references. NMODL Repo SHA: BlueBrain/nmodl@d7c1dc6afdf1492834a8ad679b3e21939606ea4d --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/language/nodes.py | 6 +++++- src/nmodl/language/templates/ast/ast.cpp | 2 +- src/nmodl/language/templates/ast/ast.hpp | 2 +- src/nmodl/language/templates/pybind/pyast.hpp | 4 ++-- src/nmodl/visitors/global_var_visitor.cpp | 2 +- src/nmodl/visitors/neuron_solve_visitor.cpp | 2 +- .../visitors/sympy_replace_solutions_visitor.cpp | 16 ++++++++-------- .../visitors/sympy_replace_solutions_visitor.hpp | 4 ++-- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 3bb71a0c98..a6e2fd2424 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -4060,7 +4060,7 @@ void CodegenCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { // to the next netcon. const auto& args = node.get_parameters(); RenameVisitor v; - auto& statement_block = node.get_statement_block(); + const auto& statement_block = node.get_statement_block(); for (size_t i_arg = 0; i_arg < args.size(); ++i_arg) { // sanitize node_name since we want to substitute names like (*w) as they are auto old_name = diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index ff1037f691..7c6d77f144 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -234,11 +234,15 @@ def member_typename(self): return type_name + @property + def _is_member_type_wrapped_as_shared_pointer(self): + return not (self.is_vector or self.is_base_type_node or self.is_ptr_excluded_node) + @property def member_rvalue_typename(self): """returns rvalue reference type when used as returned or parameter type""" typename = self.member_typename - if not self.is_integral_type_node: + if not self.is_integral_type_node and not self._is_member_type_wrapped_as_shared_pointer: return "const " + typename + "&" return typename diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 87f5399540..04ff475381 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -30,7 +30,7 @@ std::string Ast::get_node_name() const { throw std::logic_error("get_node_name() not implemented"); } -const std::shared_ptr& Ast::get_statement_block() const { +std::shared_ptr Ast::get_statement_block() const { throw std::runtime_error("get_statement_block not implemented"); } diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 0845807948..6e9e4b18a4 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -273,7 +273,7 @@ struct Ast: public std::enable_shared_from_this { * * \sa ast::StatementBlock */ - virtual const std::shared_ptr& get_statement_block() const; + virtual std::shared_ptr get_statement_block() const; /** * \brief Set symbol table for the AST node diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index f2fa13eb14..e70ffe3aca 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -114,8 +114,8 @@ struct PyAst: public Ast { PYBIND11_OVERRIDE(symtab::SymbolTable*, Ast, get_symbol_table, ); } - const std::shared_ptr& get_statement_block() const override { - PYBIND11_OVERRIDE(const std::shared_ptr&, Ast, get_statement_block, ); + std::shared_ptr get_statement_block() const override { + PYBIND11_OVERRIDE(std::shared_ptr, Ast, get_statement_block, ); } void set_symbol_table(symtab::SymbolTable* newsymtab) override { diff --git a/src/nmodl/visitors/global_var_visitor.cpp b/src/nmodl/visitors/global_var_visitor.cpp index f11956bb45..ae84720597 100644 --- a/src/nmodl/visitors/global_var_visitor.cpp +++ b/src/nmodl/visitors/global_var_visitor.cpp @@ -25,7 +25,7 @@ void GlobalToRangeVisitor::visit_neuron_block(ast::NeuronBlock& node) { std::unordered_set global_variables_to_remove; std::unordered_set global_statements_to_remove; - auto& statement_block = node.get_statement_block(); + auto const& statement_block = node.get_statement_block(); auto& statements = (*statement_block).get_statements(); const auto& symbol_table = ast.get_symbol_table(); diff --git a/src/nmodl/visitors/neuron_solve_visitor.cpp b/src/nmodl/visitors/neuron_solve_visitor.cpp index 77af74eff3..d8da1e03c2 100644 --- a/src/nmodl/visitors/neuron_solve_visitor.cpp +++ b/src/nmodl/visitors/neuron_solve_visitor.cpp @@ -32,7 +32,7 @@ void NeuronSolveVisitor::visit_derivative_block(ast::DerivativeBlock& node) { node.visit_children(*this); derivative_block = false; if (solve_blocks[derivative_block_name] == codegen::naming::EULER_METHOD) { - auto& statement_block = node.get_statement_block(); + const auto& statement_block = node.get_statement_block(); for (auto& e: euler_solution_expressions) { statement_block->emplace_back_statement(e); } diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index 34ba9e3498..9906c5577e 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -161,8 +161,8 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( const ast::Node& node, - const std::shared_ptr& get_lhs(const ast::Node& node), - const std::shared_ptr& get_rhs(const ast::Node& node)) { + std::shared_ptr get_lhs(const ast::Node& node), + std::shared_ptr get_rhs(const ast::Node& node)) { interleaves_counter.new_equation(true); const auto& statement = std::static_pointer_cast( @@ -212,11 +212,11 @@ void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); - auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { + auto get_lhs = [](const ast::Node& node) -> std::shared_ptr { return dynamic_cast(node).get_expression()->get_lhs(); }; - auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { + auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { return dynamic_cast(node).get_expression()->get_rhs(); }; @@ -225,11 +225,11 @@ void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpressio void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); - auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { + auto get_lhs = [](const ast::Node& node) -> std::shared_ptr { return dynamic_cast(node).get_left_linxpression(); }; - auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { + auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { return dynamic_cast(node).get_left_linxpression(); }; @@ -239,11 +239,11 @@ void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { void SympyReplaceSolutionsVisitor::visit_non_lin_equation(ast::NonLinEquation& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); - auto get_lhs = [](const ast::Node& node) -> const std::shared_ptr& { + auto get_lhs = [](const ast::Node& node) -> std::shared_ptr { return dynamic_cast(node).get_lhs(); }; - auto get_rhs = [](const ast::Node& node) -> const std::shared_ptr& { + auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { return dynamic_cast(node).get_rhs(); }; diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp index 371b4788da..42bc4da2d0 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -253,8 +253,8 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { */ void try_replace_tagged_statement( const ast::Node& node, - const std::shared_ptr& get_lhs(const ast::Node& node), - const std::shared_ptr& get_rhs(const ast::Node& node)); + std::shared_ptr get_lhs(const ast::Node& node), + std::shared_ptr get_rhs(const ast::Node& node)); /** * \struct InterleavesCounter From d69a686063e8bde6001f18e9a5a32c9eff660c2b Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 6 Sep 2023 15:24:40 +0200 Subject: [PATCH 528/871] Avoid duplicate calculation of number of float and int variables NMODL Repo SHA: BlueBrain/nmodl@9df03fe44ae678f756a138774d501440b791b2ab --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 52 +---------------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 12 ------ 2 files changed, 2 insertions(+), 62 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index a6e2fd2424..f956ca1523 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -535,54 +535,6 @@ std::string CodegenCppVisitor::breakpoint_current(std::string current) const { } -int CodegenCppVisitor::float_variables_size() const { - auto count_length = [](int l, const SymbolType& variable) { - return l += variable->get_length(); - }; - - int float_size = std::accumulate(info.range_parameter_vars.begin(), - info.range_parameter_vars.end(), - 0, - count_length); - float_size += std::accumulate(info.range_assigned_vars.begin(), - info.range_assigned_vars.end(), - 0, - count_length); - float_size += std::accumulate(info.range_state_vars.begin(), - info.range_state_vars.end(), - 0, - count_length); - float_size += - std::accumulate(info.assigned_vars.begin(), info.assigned_vars.end(), 0, count_length); - - /// all state variables for which we add Dstate variables - float_size += std::accumulate(info.state_vars.begin(), info.state_vars.end(), 0, count_length); - - /// for v_unused variable - if (info.vectorize) { - float_size++; - } - /// for g_unused variable - if (breakpoint_exist()) { - float_size++; - } - /// for tsave variable - if (net_receive_exist()) { - float_size++; - } - return float_size; -} - - -int CodegenCppVisitor::int_variables_size() const { - int num_variables = 0; - for (const auto& semantic: info.semantics) { - num_variables += semantic.size; - } - return num_variables; -} - - /** * \details Depending upon the block type, we have to print read/write ion variables * during code generation. Depending on block/procedure being printed, this @@ -2192,13 +2144,13 @@ void CodegenCppVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int float_variables_size()"); - printer->fmt_line("return {};", float_variables_size()); + printer->fmt_line("return {};", codegen_float_variables.size()); printer->end_block(1); printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int int_variables_size()"); - printer->fmt_line("return {};", int_variables_size()); + printer->fmt_line("return {};", codegen_int_variables.size()); printer->end_block(1); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 09e6f0bd0a..96802f8d02 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -503,18 +503,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { } - /** - * Number of float variables in the model - */ - int float_variables_size() const; - - - /** - * Number of integer variables in the model - */ - int int_variables_size() const; - - /** * Determine the position in the data array for a given float variable * \param name The name of a float variable From b692dc052873473b2d799de74982fc7510a9fc0f Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 6 Sep 2023 15:38:21 +0200 Subject: [PATCH 529/871] Revert "Avoid duplicate calculation of number of float and int variables" This reverts commit 9df03fe44ae678f756a138774d501440b791b2ab. Accidental commit to master NMODL Repo SHA: BlueBrain/nmodl@9db0f46d694de6b350a620ba58804feb832809b1 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 52 ++++++++++++++++++++++- src/nmodl/codegen/codegen_cpp_visitor.hpp | 12 ++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index f956ca1523..a6e2fd2424 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -535,6 +535,54 @@ std::string CodegenCppVisitor::breakpoint_current(std::string current) const { } +int CodegenCppVisitor::float_variables_size() const { + auto count_length = [](int l, const SymbolType& variable) { + return l += variable->get_length(); + }; + + int float_size = std::accumulate(info.range_parameter_vars.begin(), + info.range_parameter_vars.end(), + 0, + count_length); + float_size += std::accumulate(info.range_assigned_vars.begin(), + info.range_assigned_vars.end(), + 0, + count_length); + float_size += std::accumulate(info.range_state_vars.begin(), + info.range_state_vars.end(), + 0, + count_length); + float_size += + std::accumulate(info.assigned_vars.begin(), info.assigned_vars.end(), 0, count_length); + + /// all state variables for which we add Dstate variables + float_size += std::accumulate(info.state_vars.begin(), info.state_vars.end(), 0, count_length); + + /// for v_unused variable + if (info.vectorize) { + float_size++; + } + /// for g_unused variable + if (breakpoint_exist()) { + float_size++; + } + /// for tsave variable + if (net_receive_exist()) { + float_size++; + } + return float_size; +} + + +int CodegenCppVisitor::int_variables_size() const { + int num_variables = 0; + for (const auto& semantic: info.semantics) { + num_variables += semantic.size; + } + return num_variables; +} + + /** * \details Depending upon the block type, we have to print read/write ion variables * during code generation. Depending on block/procedure being printed, this @@ -2144,13 +2192,13 @@ void CodegenCppVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int float_variables_size()"); - printer->fmt_line("return {};", codegen_float_variables.size()); + printer->fmt_line("return {};", float_variables_size()); printer->end_block(1); printer->add_newline(2); print_device_method_annotation(); printer->start_block("static inline int int_variables_size()"); - printer->fmt_line("return {};", codegen_int_variables.size()); + printer->fmt_line("return {};", int_variables_size()); printer->end_block(1); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 96802f8d02..09e6f0bd0a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -503,6 +503,18 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { } + /** + * Number of float variables in the model + */ + int float_variables_size() const; + + + /** + * Number of integer variables in the model + */ + int int_variables_size() const; + + /** * Determine the position in the data array for a given float variable * \param name The name of a float variable From 29a414b2734bf60a057a52754719ccc7c0fc76d6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 8 Sep 2023 14:02:04 +0200 Subject: [PATCH 530/871] Simplify flat index computation. (BlueBrain/nmodl#1069) NMODL Repo SHA: BlueBrain/nmodl@e124ae75e431b8ac9d183e00f4aadbf1e5bee700 --- nmodl/ode.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nmodl/ode.py b/nmodl/ode.py index 283038f7ec..e25da9337b 100644 --- a/nmodl/ode.py +++ b/nmodl/ode.py @@ -7,6 +7,7 @@ from importlib import import_module +import itertools import sympy as sp import re @@ -310,13 +311,14 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): vecFcode = [] for i, eq in enumerate(eqs): vecFcode.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}") + vecJcode = [] - for i, jac in enumerate(jacobian): - # todo: fix indexing to be ascending order - flat_index = jacobian.rows * (i % jacobian.rows) + (i // jacobian.rows) - vecJcode.append( - f"J[{flat_index}] = {sp.ccode(jac.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}" - ) + for i, j in itertools.product(range(jacobian.rows), range(jacobian.cols)): + flat_index = i + jacobian.rows * j + + rhs = sp.ccode(jacobian[i,j].simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts) + vecJcode.append(f"J[{flat_index}] = {rhs}") + # interweave code = _interweave_eqs(vecFcode, vecJcode) From 8dc6b60d2284f9051589e795abff82af2a62ff83 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 8 Sep 2023 14:19:05 +0200 Subject: [PATCH 531/871] Improve const-correctness of Crout. (BlueBrain/nmodl#1068) NMODL Repo SHA: BlueBrain/nmodl@f62fa77b1c10767942e4fe14c81b7a6f38e9dc23 --- src/nmodl/solver/crout/crout.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nmodl/solver/crout/crout.hpp b/src/nmodl/solver/crout/crout.hpp index f71c382d2e..cf45043b72 100644 --- a/src/nmodl/solver/crout/crout.hpp +++ b/src/nmodl/solver/crout/crout.hpp @@ -38,7 +38,7 @@ nrn_pragma_acc(routine seq) nrn_pragma_omp(declare target) #endif template -EIGEN_DEVICE_FUNC inline int Crout(int n, T* a, int* perm, double* rowmax) { +EIGEN_DEVICE_FUNC inline int Crout(int n, T* const a, int* const perm, double* const rowmax) { // roundoff is the minimal value for a pivot element without its being considered too close to // zero double roundoff = 1.e-20; @@ -137,7 +137,12 @@ nrn_pragma_acc(routine seq) nrn_pragma_omp(declare target) #endif template -EIGEN_DEVICE_FUNC inline int solveCrout(int n, T* a, T* b, T* p, int* perm, int* y = (int*) 0) { +EIGEN_DEVICE_FUNC inline int solveCrout(int n, + T const* const a, + T const* const b, + T* const p, + int const* const perm, + int const* const y = nullptr) { int i, j, pivot; T sum; From 3546645bb1b8a292dea4cc6beb90b150dd880e4d Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Fri, 8 Sep 2023 17:33:44 +0200 Subject: [PATCH 532/871] make nmodl::stringutils methods more consistent (BlueBrain/nmodl#1071) * The prototypes of these functions now follow these 2 principles: 1. the input string is kept intact 2. the transformed text is returned in a new `std::string` instance * Improve the Doxygen documentation Also removed some useless work along the way while updating the calls to these functions. Fixes BlueBrain/nmodl#1066 Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@8af222d0b3f6617fba71c9cf1d1724a6b80f8b8c --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 22 ++-- src/nmodl/lexer/c11.ll | 2 +- src/nmodl/lexer/nmodl.ll | 6 +- src/nmodl/lexer/nmodl_utils.cpp | 2 +- src/nmodl/parser/diffeq_driver.cpp | 2 +- src/nmodl/symtab/symbol_properties.hpp | 13 +- src/nmodl/utils/string_utils.hpp | 116 ++++++++++++------ src/nmodl/visitors/rename_visitor.hpp | 16 +-- .../transpiler/unit/utils/test_utils.cpp | 7 +- 9 files changed, 117 insertions(+), 69 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index a6e2fd2424..bfd23bb468 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -302,14 +302,14 @@ void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { if (!codegen) { return; } - auto text = node.get_statement()->eval(); - auto result = process_verbatim_text(text); - - auto statements = stringutils::split_string(result, '\n'); - for (auto& statement: statements) { - stringutils::trim_newline(statement); - if (statement.find_first_not_of(' ') != std::string::npos) { - printer->add_line(statement); + const auto& text = node.get_statement()->eval(); + const auto& result = process_verbatim_text(text); + + const auto& statements = stringutils::split_string(result, '\n'); + for (const auto& statement: statements) { + const auto& trimed_stmt = stringutils::trim_newline(statement); + if (trimed_stmt.find_first_not_of(' ') != std::string::npos) { + printer->add_line(trimed_stmt); } } } @@ -1373,9 +1373,9 @@ void CodegenCppVisitor::print_top_verbatim_blocks() { * the text. */ void CodegenCppVisitor::rename_function_arguments() { - auto default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); - for (auto& arg: default_arguments) { - stringutils::trim(arg); + const auto& default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); + for (const auto& dirty_arg: default_arguments) { + const auto& arg = stringutils::trim(dirty_arg); RenameVisitor v(arg, "arg_" + arg); for (const auto& function: info.functions) { if (has_parameter_of_name(function, arg)) { diff --git a/src/nmodl/lexer/c11.ll b/src/nmodl/lexer/c11.ll index 78baf95fbc..f48958527a 100644 --- a/src/nmodl/lexer/c11.ll +++ b/src/nmodl/lexer/c11.ll @@ -668,7 +668,7 @@ WS [ \t\v\n\f] {WS}+ { driver.add_token(yytext); std::string str(yytext); - stringutils::remove_character(str, ' '); + str = stringutils::remove_character(str, ' '); if (str == "\n") { loc.lines(1); } else { diff --git a/src/nmodl/lexer/nmodl.ll b/src/nmodl/lexer/nmodl.ll index 77e0bb8fb4..0b460ae368 100755 --- a/src/nmodl/lexer/nmodl.ll +++ b/src/nmodl/lexer/nmodl.ll @@ -392,11 +392,11 @@ ELSE { * for using lexer program. */ std::string str(yytext); cur_line = str; - stringutils::trim(str); + str = stringutils::trim(str); if (driver.is_verbose()) { if(str.length()) { - stringutils::trim_newline(str); + str = stringutils::trim_newline(str); std::cout << "LINE "<< yylineno << ": " << str << std::endl; } else { std::cout << "LINE " << yylineno << ": " << std::endl; @@ -472,7 +472,7 @@ ELSE { /** For title return string without new line character */ loc.lines(1); std::string str(yytext); - stringutils::trim_newline(str); + str = stringutils::trim_newline(str); BEGIN(INITIAL); return NmodlParser::make_LINE_PART(str, loc); } diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index ece525c07c..6ba9bffd09 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -93,7 +93,7 @@ SymbolType name_symbol(const std::string& text, PositionType& pos, TokenType typ SymbolType prime_symbol(std::string text, PositionType& pos) { ModToken token(text, Token::PRIME, pos); auto order = std::count(text.begin(), text.end(), '\''); - stringutils::remove_character(text, '\''); + text = stringutils::remove_character(text, '\''); auto prime_name = new ast::String(text); assert(order <= std::numeric_limits::max()); diff --git a/src/nmodl/parser/diffeq_driver.cpp b/src/nmodl/parser/diffeq_driver.cpp index 686eb45020..5cf92a1ca6 100644 --- a/src/nmodl/parser/diffeq_driver.cpp +++ b/src/nmodl/parser/diffeq_driver.cpp @@ -27,7 +27,7 @@ void DiffeqDriver::parse_equation(const std::string& equation, auto const wide_order = std::count(state.begin(), state.end(), '\''); assert(wide_order >= 0 && wide_order <= std::numeric_limits::max()); order = static_cast(wide_order); - stringutils::remove_character(state, '\''); + state = stringutils::remove_character(state, '\''); /// error if no prime in equation or not an assignment statement if (order == 0 || state.empty()) { diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 03c60c0271..dad35d8b60 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -272,13 +272,16 @@ std::vector to_string_vector(const syminfo::Status& obj); template std::string to_string(const T& obj) { - auto elements = to_string_vector(obj); std::string text; - for (const auto& element: elements) { - text += element + " "; + bool is_first{true}; + for (const auto& element: to_string_vector(obj)) { + if (is_first) { + text += element; + is_first = false; + } else { + text += " " + element; + } } - // remove extra whitespace at the end - stringutils::trim(text); return text; } diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 9903ff8924..c11ab18b20 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -27,62 +27,95 @@ namespace nmodl { namespace stringutils { /** - * @addtogroup utils - * @{ + * \addtogroup utils + * \{ */ /// text alignment when printing in the tabular form enum class text_alignment { left, right, center }; -/// Trim from start -static inline std::string& ltrim(std::string& s) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int c) { return !std::isspace(c); })); - return s; +/** + * \param text the string to manipulate + * \return a copy of the given string with leading ASCII space characters removed + */ +[[nodiscard]] static inline std::string ltrim(std::string text) { + text.erase(text.begin(), + std::find_if(text.begin(), text.end(), [](int c) { return !std::isspace(c); })); + return text; } -/// Trim from end -static inline std::string& rtrim(std::string& s) { - s.erase(std::find_if(s.rbegin(), s.rend(), [](int c) { return !std::isspace(c); }).base(), - s.end()); - return s; +/** + * \param text the string to manipulate + * \return a copy of the given string with trailing characters removed. + */ +[[nodiscard]] static inline std::string rtrim(std::string text) { + text.erase( + std::find_if(text.rbegin(), text.rend(), [](int c) { return !std::isspace(c); }).base(), + text.end()); + return text; } -/// Trim from both ends -static inline std::string& trim(std::string& s) { - return ltrim(rtrim(s)); +/** + * + * \param text the string to manipulate + * \return a copy of the given string with both leading and trailing ASCII space characters removed + */ +[[nodiscard]] static inline std::string trim(std::string text) { + return ltrim(rtrim(std::move(text))); } -static inline void remove_character(std::string& str, const char c) { - str.erase(std::remove(str.begin(), str.end(), c), str.end()); +/** + * Remove all occurrences of a given character in a text + * \param text the string to manipulate + * \param c the character to remove + * @return a copy the modified text + */ +[[nodiscard]] static inline std::string remove_character(std::string text, const char c) { + text.erase(std::remove(text.begin(), text.end(), c), text.end()); + return text; } -/// Remove leading newline for the string read by grammar -static inline std::string& trim_newline(std::string& s) { - remove_character(s, '\n'); - return s; +/** + * + * \param text the string to manipulate + * \return a copy of the given string with all occurrences of the ASCII newline character removed + */ +[[nodiscard]] static inline std::string trim_newline(std::string text) { + return remove_character(std::move(text), '\n'); } -/// for printing json, we have to escape double quotes -static inline std::string escape_quotes(const std::string& before) { - std::string after; +/** + * Escape double-quote in a text, useful for JSON pretty printer. + * \param text the string to manipulate + * \return a copy of the given string with every " and \ characters prefixed with an extra \ + */ +[[nodiscard]] static inline std::string escape_quotes(const std::string& text) { + std::ostringstream oss; + std::string result; - for (auto c: before) { + for (auto c: text) { switch (c) { case '"': case '\\': - after += '\\'; + result += '\\'; /// don't break here as we want to append actual character default: - after += c; + result += c; } } - return after; + return result; } -/// Spilt string with given delimiter and returns vector -static inline std::vector split_string(const std::string& text, char delimiter) { +/** + * Split a text in a list of words, using a given delimiter character + * \param text the string to manipulate + * \param delimiter the delimiter character + * \return a container holding copies of the substrings + */ +[[nodiscard]] static inline std::vector split_string(const std::string& text, + char delimiter) { std::vector elements; std::stringstream ss(text); std::string item; @@ -94,13 +127,22 @@ static inline std::vector split_string(const std::string& text, cha return elements; } -/// Left/Right/Center-aligns string within a field of width "width" -static inline std::string align_text(std::string text, int width, text_alignment type) { +/// +/** + * Aligns a text within a field of width \a width + * \param text the string to manipulate + * \param width the width of the field + * \param type the kind of alignment Left, Right, or Center + * \return a copy of the string aligned + */ +[[nodiscard]] static inline std::string align_text(const std::string& text, + int width, + text_alignment type) { /// left and right spacing std::string left, right; /// count excess room to pad - int padding = width - text.size(); + const int padding = width - static_cast(text.size()); if (padding > 0) { if (type == text_alignment::left) { @@ -111,7 +153,7 @@ static inline std::string align_text(std::string text, int width, text_alignment left = std::string(padding / 2, ' '); right = std::string(padding / 2, ' '); /// if odd #, add one more space - if (padding > 0 && padding % 2 != 0) { + if (padding % 2 != 0) { right += " "; } } @@ -120,7 +162,11 @@ static inline std::string align_text(std::string text, int width, text_alignment } /// To lower case -static inline std::string tolower(std::string text) { +/** + * \param text the string to manipulate + * \return a copy of string converted to lowercase + */ +[[nodiscard]] static inline std::string tolower(std::string text) { std::transform(text.begin(), text.end(), text.begin(), ::tolower); return text; } @@ -135,7 +181,7 @@ static inline std::string tolower(std::string text) { */ std::string to_string(double value, const std::string& format_spec = "{:.16g}"); -/** @} */ // end of utils +/** \} */ // end of utils } // namespace stringutils } // namespace nmodl diff --git a/src/nmodl/visitors/rename_visitor.hpp b/src/nmodl/visitors/rename_visitor.hpp index ae46620a0e..3bd62fb016 100644 --- a/src/nmodl/visitors/rename_visitor.hpp +++ b/src/nmodl/visitors/rename_visitor.hpp @@ -70,19 +70,19 @@ class RenameVisitor: public ConstAstVisitor { public: RenameVisitor() = default; - RenameVisitor(std::string old_name, std::string new_name) - : var_name_regex(std::move(old_name)) + RenameVisitor(const std::string& old_name, std::string new_name) + : var_name_regex(old_name) , new_var_name(std::move(new_name)) {} RenameVisitor(std::shared_ptr ast, - std::string old_name, + const std::string& old_name, std::string new_var_name_or_prefix, bool add_prefix, bool add_random_suffix) : ast(std::move(ast)) - , var_name_regex(std::move(old_name)) - , add_prefix(std::move(add_prefix)) - , add_random_suffix(std::move(add_random_suffix)) { + , var_name_regex(old_name) + , add_prefix(add_prefix) + , add_random_suffix(add_random_suffix) { if (add_prefix) { new_var_name_prefix = std::move(new_var_name_or_prefix); } else { @@ -94,8 +94,8 @@ class RenameVisitor: public ConstAstVisitor { /// to the renamed_variables map std::string new_name_generator(const std::string& old_name); - void set(std::string old_name, std::string new_name) { - var_name_regex = std::move(old_name); + void set(const std::string& old_name, std::string new_name) { + var_name_regex = old_name; new_var_name = std::move(new_name); } diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index c06d95b49e..8f06671beb 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -20,16 +20,15 @@ namespace test_utils { int count_leading_spaces(std::string text) { auto const length = text.size(); - nmodl::stringutils::ltrim(text); + text = nmodl::stringutils::ltrim(text); auto const num_whitespaces = length - text.size(); assert(num_whitespaces <= std::numeric_limits::max()); return static_cast(num_whitespaces); } /// check if string has only whitespaces -bool is_empty(std::string text) { - nmodl::stringutils::trim(text); - return text.empty(); +bool is_empty(const std::string& text) { + return nmodl::stringutils::trim(text).empty(); } /** Reindent nmodl text for text-to-text comparison From f1deb97881f995f2154202aa72de9b645c5c1496 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 12 Sep 2023 14:01:30 +0200 Subject: [PATCH 533/871] Avoid duplicate calculation of number of float variables (BlueBrain/nmodl#1067) * Refactor `float_variables_size` and `int_variables_size` NMODL Repo SHA: BlueBrain/nmodl@0d1ffea04365d46ff73355112b5a4d98cbbce0c4 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 43 ++--------------------- 1 file changed, 3 insertions(+), 40 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index bfd23bb468..450139cd82 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -536,50 +536,13 @@ std::string CodegenCppVisitor::breakpoint_current(std::string current) const { int CodegenCppVisitor::float_variables_size() const { - auto count_length = [](int l, const SymbolType& variable) { - return l += variable->get_length(); - }; - - int float_size = std::accumulate(info.range_parameter_vars.begin(), - info.range_parameter_vars.end(), - 0, - count_length); - float_size += std::accumulate(info.range_assigned_vars.begin(), - info.range_assigned_vars.end(), - 0, - count_length); - float_size += std::accumulate(info.range_state_vars.begin(), - info.range_state_vars.end(), - 0, - count_length); - float_size += - std::accumulate(info.assigned_vars.begin(), info.assigned_vars.end(), 0, count_length); - - /// all state variables for which we add Dstate variables - float_size += std::accumulate(info.state_vars.begin(), info.state_vars.end(), 0, count_length); - - /// for v_unused variable - if (info.vectorize) { - float_size++; - } - /// for g_unused variable - if (breakpoint_exist()) { - float_size++; - } - /// for tsave variable - if (net_receive_exist()) { - float_size++; - } - return float_size; + return codegen_float_variables.size(); } int CodegenCppVisitor::int_variables_size() const { - int num_variables = 0; - for (const auto& semantic: info.semantics) { - num_variables += semantic.size; - } - return num_variables; + const auto count_semantics = [](int sum, const IndexSemantics& sem) { return sum += sem.size; }; + return std::accumulate(info.semantics.begin(), info.semantics.end(), 0, count_semantics); } From 27baa130e064b1255b2110985bd1f3a385543b39 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 18 Sep 2023 11:31:19 +0200 Subject: [PATCH 534/871] Fix codegen_cpp_visitor.*pp based on cppcheck (BlueBrain/nmodl#1073) * Trying to fix various aspects of codegen_cpp_visitor.*pp based on suggestions from cppcheck * Add vscode specific file to ignored files * Improved time and date printing * Renaming of arguments * Improve CodegenCppVisitor constructors * Use std::any_of with lamda instead of for loops NMODL Repo SHA: BlueBrain/nmodl@441a3b1be1588a77c4642d7d54393ca1c4b8a8e6 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 71 ++++++------ src/nmodl/codegen/codegen_cpp_visitor.hpp | 132 +++++++++------------- 2 files changed, 85 insertions(+), 118 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 450139cd82..88fd8985dc 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -8,6 +8,7 @@ #include "codegen/codegen_cpp_visitor.hpp" #include +#include #include #include #include @@ -1433,9 +1434,9 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { } for (const auto& variable: depend_variables) { - auto name = variable->get_node_name(); - auto instance_name = get_variable_name(name); - printer->fmt_start_block("if (save_{} != {})", name, instance_name); + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + printer->fmt_start_block("if (save_{} != {})", var_name, instance_name); printer->add_line("make_table = true;"); printer->end_block(1); } @@ -1466,10 +1467,10 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { if (node.is_procedure_block()) { printer->fmt_line("{}({}, x);", function, internal_method_arguments()); for (const auto& variable: table_variables) { - auto name = variable->get_node_name(); - auto instance_name = get_variable_name(name); - auto table_name = get_variable_name("t_" + name); - auto [is_array, array_length] = check_if_var_is_array(name); + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var_name); if (is_array) { for (int j = 0; j < array_length; j++) { printer->fmt_line( @@ -1489,9 +1490,9 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { printer->end_block(1); for (const auto& variable: depend_variables) { - auto name = variable->get_node_name(); - auto instance_name = get_variable_name(name); - printer->fmt_line("save_{} = {};", name, instance_name); + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + printer->fmt_line("save_{} = {};", var_name, instance_name); } } printer->end_block(1); @@ -1506,7 +1507,6 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) auto table_variables = statement->get_table_vars(); auto with = statement->get_with()->eval(); auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); - auto float_type = default_float_data_type(); auto tmin_name = get_variable_name("tmin_" + name); auto mfac_name = get_variable_name("mfac_" + name); auto function_name = method_name("f_" + name); @@ -1538,14 +1538,14 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) printer->start_block("if (isnan(xi))"); if (node.is_procedure_block()) { for (const auto& var: table_variables) { - auto name = get_variable_name(var->get_node_name()); + auto var_name = get_variable_name(var->get_node_name()); auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); if (is_array) { for (int j = 0; j < array_length; j++) { - printer->fmt_line("{}[{}] = xi;", name, j); + printer->fmt_line("{}[{}] = xi;", var_name, j); } } else { - printer->fmt_line("{} = xi;", name); + printer->fmt_line("{} = xi;", var_name); } } printer->add_line("return 0;"); @@ -1558,10 +1558,10 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); if (node.is_procedure_block()) { for (const auto& variable: table_variables) { - auto name = variable->get_node_name(); - auto instance_name = get_variable_name(name); - auto table_name = get_variable_name("t_" + name); - auto [is_array, array_length] = check_if_var_is_array(name); + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var_name); if (is_array) { for (int j = 0; j < array_length; j++) { printer->fmt_line( @@ -1582,9 +1582,9 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) printer->add_line("double theta = xi - double(i);"); if (node.is_procedure_block()) { for (const auto& var: table_variables) { - auto name = var->get_node_name(); - auto instance_name = get_variable_name(name); - auto table_name = get_variable_name("t_" + name); + auto var_name = var->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); if (is_array) { for (size_t j = 0; j < array_length; j++) { @@ -1625,9 +1625,9 @@ void CodegenCppVisitor::print_check_table_thread_function() { printer->add_line("double v = 0;"); for (const auto& function: info.functions_with_table) { - auto name = method_name("check_" + function->get_node_name()); + auto method_name_str = method_name("check_" + function->get_node_name()); auto arguments = internal_method_arguments(); - printer->fmt_line("{}({});", name, arguments); + printer->fmt_line("{}({});", method_name_str, arguments); } printer->end_block(1); @@ -1736,9 +1736,6 @@ void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& nod */ bool is_functor_const(const ast::StatementBlock& variable_block, const ast::StatementBlock& functor_block) { - // Save DUChain for every variable in variable_block - std::unordered_map chains; - // Create complete_block with both variable declarations (done in variable_block) and solver // part (done in functor_block) to be able to run the SymtabVisitor and DefUseAnalyzeVisitor // then and get the proper DUChains for the variables defined in the variable_block @@ -2416,9 +2413,9 @@ std::string CodegenCppVisitor::get_variable_name(const std::string& name, bool u void CodegenCppVisitor::print_backend_info() { - time_t tr{}; - time(&tr); - auto date = std::string(asctime(localtime(&tr))); + time_t current_time{}; + time(¤t_time); + std::string data_time_str{std::ctime(¤t_time)}; auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; printer->add_line("/*********************************************************"); @@ -2427,7 +2424,7 @@ void CodegenCppVisitor::print_backend_info() { printer->fmt_line("NMODL Version : {}", nmodl_version()); printer->fmt_line("Vectorized : {}", info.vectorize); printer->fmt_line("Threadsafe : {}", info.thread_safe); - printer->fmt_line("Created : {}", stringutils::trim(date)); + printer->fmt_line("Created : {}", stringutils::trim(data_time_str)); printer->fmt_line("Backend : {}", backend_name()); printer->fmt_line("NMODL Compiler : {}", version); printer->add_line("*********************************************************/"); @@ -2711,7 +2708,7 @@ void CodegenCppVisitor::print_prcellstate_macros() const { void CodegenCppVisitor::print_mechanism_info() { - auto variable_printer = [&](std::vector& variables) { + auto variable_printer = [&](const std::vector& variables) { for (const auto& v: variables) { auto name = v->get_name(); if (!info.point_process) { @@ -2871,18 +2868,18 @@ void CodegenCppVisitor::print_mechanism_register() { printer->add_line("_nrn_layout_reg(mech_type, 0);"); // 0 for SoA // register mechanism - auto args = register_mechanism_arguments(); - auto nobjects = num_thread_objects(); + const auto mech_arguments = register_mechanism_arguments(); + const auto number_of_thread_objects = num_thread_objects(); if (info.point_process) { printer->fmt_line("point_register_mech({}, {}, {}, {});", - args, + mech_arguments, info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) : "nullptr", info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) : "nullptr", - nobjects); + number_of_thread_objects); } else { - printer->fmt_line("register_mech({}, {});", args, nobjects); + printer->fmt_line("register_mech({}, {});", mech_arguments, number_of_thread_objects); if (info.constructor_node) { printer->fmt_line("register_constructor({});", method_name(naming::NRN_CONSTRUCTOR_METHOD)); @@ -3059,7 +3056,6 @@ void CodegenCppVisitor::print_thread_memory_callbacks() { void CodegenCppVisitor::print_mechanism_range_var_structure(bool print_initialisers) { auto const value_initialise = print_initialisers ? "{}" : ""; - auto float_type = default_float_data_type(); auto int_type = default_int_data_type(); printer->add_newline(2); printer->add_line("/** all mechanism instance variables and global variables */"); @@ -3797,7 +3793,6 @@ void CodegenCppVisitor::print_net_move_call(const FunctionCall& node) { printer->add_text(")"); } else { auto point_process = get_variable_name("point_process"); - std::string t = get_variable_name("t"); printer->add_text("net_send_buffering("); printer->fmt_text("nt, ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); print_vector_elements(arguments, ", "); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 09e6f0bd0a..0cb44081f5 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -127,10 +127,10 @@ struct IndexVariableInfo { /// if the variable is qualified as constant (this is property of IndexVariable) bool is_constant = false; - IndexVariableInfo(std::shared_ptr symbol, - bool is_vdata = false, - bool is_index = false, - bool is_integer = false) + explicit IndexVariableInfo(std::shared_ptr symbol, + bool is_vdata = false, + bool is_index = false, + bool is_integer = false) : symbol(std::move(symbol)) , is_vdata(is_vdata) , is_index(is_index) @@ -191,21 +191,41 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ using ParamVector = std::vector>; + /** + * Code printer object for target (C++) + */ + std::shared_ptr target_printer; + + /** + * Code printer object for wrappers + */ + std::shared_ptr wrapper_printer; + + /** + * Pointer to active code printer + */ + std::shared_ptr printer; + /** * Name of mod file (without .mod suffix) */ std::string mod_filename; /** - * Flag to indicate if visitor should print the visited nodes + * Data type of floating point variables */ - bool codegen = false; + std::string float_type = codegen::naming::DEFAULT_FLOAT_TYPE; /** * Flag to indicate if visitor should avoid ion variable copies */ bool optimize_ionvar_copies = true; + /** + * Flag to indicate if visitor should print the visited nodes + */ + bool codegen = false; + /** * Variable name should be converted to instance name (but not for function arguments) */ @@ -258,31 +278,11 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ int current_watch_statement = 0; - /** - * Data type of floating point variables - */ - std::string float_type = codegen::naming::DEFAULT_FLOAT_TYPE; - /** * All ast information for code generation */ codegen::CodegenInfo info; - /** - * Code printer object for target (C) - */ - std::shared_ptr target_printer; - - /** - * Code printer object for wrappers - */ - std::shared_ptr wrapper_printer; - - /** - * Pointer to active code printer - */ - std::shared_ptr printer; - /** * Return Nmodl language version * \return A version @@ -1577,30 +1577,31 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_wrapper_routines(); - CodegenCppVisitor(const std::string& mod_filename, + CodegenCppVisitor(std::string mod_filename, const std::string& output_dir, - const std::string& float_type, + std::string float_type, const bool optimize_ionvar_copies, const std::string& extension, const std::string& wrapper_ext) - : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) - , wrapper_printer(new CodePrinter(output_dir + "/" + mod_filename + wrapper_ext)) + : target_printer(std::make_shared(output_dir + "/" + mod_filename + extension)) + , wrapper_printer( + std::make_shared(output_dir + "/" + mod_filename + wrapper_ext)) , printer(target_printer) - , mod_filename(mod_filename) - , float_type(float_type) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} - CodegenCppVisitor(const std::string& mod_filename, + CodegenCppVisitor(std::string mod_filename, std::ostream& stream, - const std::string& float_type, + std::string float_type, const bool optimize_ionvar_copies, const std::string& extension, const std::string& wrapper_ext) - : target_printer(new CodePrinter(stream)) - , wrapper_printer(new CodePrinter(stream)) + : target_printer(std::make_shared(stream)) + , wrapper_printer(std::make_shared(stream)) , printer(target_printer) - , mod_filename(mod_filename) - , float_type(float_type) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1622,14 +1623,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * as-is in the target code. This defaults to \c double. * \param extension The file extension to use. This defaults to \c .cpp . */ - CodegenCppVisitor(const std::string& mod_filename, + CodegenCppVisitor(std::string mod_filename, const std::string& output_dir, std::string float_type, const bool optimize_ionvar_copies, const std::string& extension = ".cpp") - : target_printer(new CodePrinter(output_dir + "/" + mod_filename + extension)) + : target_printer(std::make_shared(output_dir + "/" + mod_filename + extension)) , printer(target_printer) - , mod_filename(mod_filename) + , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1649,42 +1650,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. */ - CodegenCppVisitor(const std::string& mod_filename, - std::ostream& stream, - const std::string& float_type, - const bool optimize_ionvar_copies) - : target_printer(new CodePrinter(stream)) - , printer(target_printer) - , mod_filename(mod_filename) - , float_type(float_type) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - - /** - * \copybrief nmodl::codegen::CodegenCppVisitor - * - * This constructor instantiates an NMODL C code generator and allows writing generated code - * using an nmodl::printer::CodePrinter defined elsewhere. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - * \param target_printer A printer defined outside this visitor to be used for the code - * generation - */ CodegenCppVisitor(std::string mod_filename, + std::ostream& stream, std::string float_type, - const bool optimize_ionvar_copies, - std::shared_ptr& target_printer) - : target_printer(target_printer) + const bool optimize_ionvar_copies) + : target_printer(std::make_shared(stream)) , printer(target_printer) - , mod_filename(mod_filename) - , float_type(float_type) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1890,12 +1863,11 @@ void CodegenCppVisitor::print_vector_elements(const std::vector& elements, template bool has_parameter_of_name(const T& node, const std::string& name) { auto parameters = node->get_parameters(); - for (const auto& parameter: parameters) { - if (parameter->get_node_name() == name) { - return true; - } - } - return false; + return std::any_of(parameters.begin(), + parameters.end(), + [&name](const decltype(*parameters.begin()) arg) { + return arg->get_node_name() == name; + }); } From 2adf046584513364163c56e3ba9ac90f9a0f5eea Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 21 Sep 2023 15:51:40 +0200 Subject: [PATCH 535/871] First proposal of improvements on C++ generator (BlueBrain/nmodl#1065) * First proposal of improvements on C++ generator * reduce number of copies * `CodePrinter::add_text`: * now supports multiple arguments * use it instead of trivial but expensive call to `fmt::format`. * `CodePrinter::add_multi_line`: * rework to work with C++ raw string literals * used it instead of 3 or more consecutive `add_line` * `CodePrinter::start_block`: rename to `push_block` * `CodePrinter::end_block`: rename to pop_block * CodegenCppVisitor::visit_program more visible * Try removing python3-importlib-metadata Co-authored-by: Nicolas Cornu Co-authored-by: Ioannis Magkanaris Co-authored-by: Erik Heeren NMODL Repo SHA: BlueBrain/nmodl@4a6d5119b4652b6167a2e3ebfed0caf18a3e2cca --- src/nmodl/codegen/codegen_acc_visitor.cpp | 106 ++- src/nmodl/codegen/codegen_acc_visitor.hpp | 25 +- .../codegen/codegen_compatibility_visitor.cpp | 18 +- .../codegen/codegen_compatibility_visitor.hpp | 46 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 836 +++++++++--------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 104 ++- src/nmodl/codegen/codegen_helper_visitor.cpp | 6 +- src/nmodl/codegen/codegen_info.cpp | 16 +- src/nmodl/codegen/codegen_info.hpp | 37 +- src/nmodl/printer/code_printer.cpp | 72 +- src/nmodl/printer/code_printer.hpp | 44 +- src/nmodl/symtab/symbol.hpp | 6 +- src/nmodl/symtab/symbol_properties.cpp | 6 +- src/nmodl/symtab/symbol_table.cpp | 74 +- src/nmodl/symtab/symbol_table.hpp | 24 +- src/nmodl/utils/string_utils.hpp | 1 - .../transpiler/unit/symtab/symbol_table.cpp | 29 +- 17 files changed, 779 insertions(+), 671 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 2d327ddbd4..647efd6a27 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -88,18 +88,20 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { return; } printer->add_newline(2); - auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->fmt_start_block("static inline void* mem_alloc({})", args); - printer->add_line("void* ptr;"); - printer->add_line("cudaMallocManaged(&ptr, num*size);"); - printer->add_line("cudaMemset(ptr, 0, num*size);"); - printer->add_line("return ptr;"); - printer->end_block(1); + printer->push_block( + "static inline void* mem_alloc(size_t num, size_t size, size_t alignment = 16)"); + printer->add_multi_line(R"CODE( + void* ptr; + cudaMallocManaged(&ptr, num*size); + cudaMemset(ptr, 0, num*size); + return ptr; + )CODE"); + printer->pop_block(); printer->add_newline(2); - printer->start_block("static inline void mem_free(void* ptr)"); + printer->push_block("static inline void mem_free(void* ptr)"); printer->add_line("cudaFree(ptr);"); - printer->end_block(1); + printer->pop_block(); } /** @@ -114,23 +116,23 @@ void CodegenAccVisitor::print_memory_allocation_routine() const { */ void CodegenAccVisitor::print_abort_routine() const { printer->add_newline(2); - printer->start_block("static inline void coreneuron_abort()"); - printer->add_line("printf(\"Error : Issue while running OpenACC kernel \\n\");"); + printer->push_block("static inline void coreneuron_abort()"); + printer->add_line(R"(printf("Error : Issue while running OpenACC kernel \n");)"); printer->add_line("assert(0==1);"); - printer->end_block(1); + printer->pop_block(); } void CodegenAccVisitor::print_net_send_buffering_cnt_update() const { - printer->fmt_start_block("if (nt->compute_gpu)"); + printer->push_block("if (nt->compute_gpu)"); print_device_atomic_capture_annotation(); printer->add_line("i = nsb->_cnt++;"); - printer->restart_block("else"); + printer->chain_block("else"); printer->add_line("i = nsb->_cnt++;"); - printer->end_block(1); + printer->pop_block(); } void CodegenAccVisitor::print_net_send_buffering_grow() { - // can not grow buffer during gpu execution + // no-op since can not grow buffer during gpu execution } /** @@ -174,7 +176,7 @@ void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_begin() { void CodegenAccVisitor::print_net_init_acc_serial_annotation_block_end() { if (!info.artificial_cell) { - printer->end_block(1); + printer->pop_block(); } } @@ -198,7 +200,7 @@ void CodegenAccVisitor::print_fast_imem_calculation() { auto rhs_op = operator_for_rhs(); auto d_op = operator_for_d(); - printer->start_block("if (nt->nrn_fast_imem)"); + printer->push_block("if (nt->nrn_fast_imem)"); if (info.point_process) { print_atomic_reduction_pragma(); } @@ -207,7 +209,7 @@ void CodegenAccVisitor::print_fast_imem_calculation() { print_atomic_reduction_pragma(); } printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} g;", d_op); - printer->end_block(1); + printer->pop_block(); } void CodegenAccVisitor::print_nrn_cur_matrix_shadow_reduction() { @@ -220,7 +222,7 @@ void CodegenAccVisitor::print_nrn_cur_matrix_shadow_reduction() { */ void CodegenAccVisitor::print_kernel_data_present_annotation_block_end() { if (!info.artificial_cell) { - printer->end_block(1); + printer->pop_block(); } } @@ -237,25 +239,27 @@ bool CodegenAccVisitor::nrn_cur_reduction_loop_required() { void CodegenAccVisitor::print_global_variable_device_update_annotation() { if (!info.artificial_cell) { - printer->start_block("if (nt->compute_gpu)"); + printer->push_block("if (nt->compute_gpu)"); printer->fmt_line("nrn_pragma_acc(update device ({}))", global_struct_instance()); printer->fmt_line("nrn_pragma_omp(target update to({}))", global_struct_instance()); - printer->end_block(1); + printer->pop_block(); } } void CodegenAccVisitor::print_newtonspace_transfer_to_device() const { int list_num = info.derivimplicit_list_num; - printer->start_block("if(nt->compute_gpu)"); - printer->add_line("double* device_vec = cnrn_target_copyin(vec, vec_size / sizeof(double));"); - printer->add_line("void* device_ns = cnrn_target_deviceptr(*ns);"); - printer->add_line("ThreadDatum* device_thread = cnrn_target_deviceptr(thread);"); + printer->push_block("if(nt->compute_gpu)"); + printer->add_multi_line(R"CODE( + double* device_vec = cnrn_target_copyin(vec, vec_size / sizeof(double)); + void* device_ns = cnrn_target_deviceptr(*ns); + ThreadDatum* device_thread = cnrn_target_deviceptr(thread); + )CODE"); printer->fmt_line("cnrn_target_memcpy_to_device(&(device_thread[{}]._pvoid), &device_ns);", info.thread_data_index - 1); printer->fmt_line("cnrn_target_memcpy_to_device(&(device_thread[dith{}()].pval), &device_vec);", list_num); - printer->end_block(1); + printer->pop_block(); } @@ -276,32 +280,36 @@ void CodegenAccVisitor::print_instance_struct_transfer_routines( if (info.artificial_cell) { return; } - printer->fmt_start_block( + printer->fmt_push_block( "static inline void copy_instance_to_device(NrnThread* nt, Memb_list* ml, {} const* inst)", instance_struct()); - printer->start_block("if (!nt->compute_gpu)"); + printer->push_block("if (!nt->compute_gpu)"); printer->add_line("return;"); - printer->end_block(1); + printer->pop_block(); printer->fmt_line("auto tmp = *inst;"); printer->add_line("auto* d_inst = cnrn_target_is_present(inst);"); - printer->start_block("if (!d_inst)"); + printer->push_block("if (!d_inst)"); printer->add_line("d_inst = cnrn_target_copyin(inst);"); - printer->end_block(1); + printer->pop_block(); for (auto const& ptr_mem: ptr_members) { printer->fmt_line("tmp.{0} = cnrn_target_deviceptr(tmp.{0});", ptr_mem); } - printer->add_line("cnrn_target_memcpy_to_device(d_inst, &tmp);"); - printer->add_line("auto* d_ml = cnrn_target_deviceptr(ml);"); - printer->add_line("void* d_inst_void = d_inst;"); - printer->add_line("cnrn_target_memcpy_to_device(&(d_ml->instance), &d_inst_void);"); - printer->end_block(2); // copy_instance_to_device - - printer->fmt_start_block("static inline void delete_instance_from_device({}* inst)", - instance_struct()); - printer->start_block("if (cnrn_target_is_present(inst))"); + printer->add_multi_line(R"CODE( + cnrn_target_memcpy_to_device(d_inst, &tmp); + auto* d_ml = cnrn_target_deviceptr(ml); + void* d_inst_void = d_inst; + cnrn_target_memcpy_to_device(&(d_ml->instance), &d_inst_void); + )CODE"); + printer->pop_block(); // copy_instance_to_device + printer->add_newline(); + + printer->fmt_push_block("static inline void delete_instance_from_device({}* inst)", + instance_struct()); + printer->push_block("if (cnrn_target_is_present(inst))"); printer->add_line("cnrn_target_delete(inst);"); - printer->end_block(1); - printer->end_block(2); // delete_instance_from_device + printer->pop_block(); + printer->pop_block(); // delete_instance_from_device + printer->add_newline(); } @@ -334,9 +342,9 @@ void CodegenAccVisitor::print_device_atomic_capture_annotation() const { void CodegenAccVisitor::print_device_stream_wait() const { - printer->start_block("if(nt->compute_gpu)"); + printer->push_block("if(nt->compute_gpu)"); printer->add_line("nrn_pragma_acc(wait(nt->stream_id))"); - printer->end_block(1); + printer->pop_block(); } @@ -348,18 +356,18 @@ void CodegenAccVisitor::print_net_send_buf_count_update_to_host() const { void CodegenAccVisitor::print_net_send_buf_update_to_host() const { print_device_stream_wait(); - printer->start_block("if (nsb && nt->compute_gpu)"); + printer->push_block("if (nsb && nt->compute_gpu)"); print_net_send_buf_count_update_to_host(); printer->add_line("update_net_send_buffer_on_host(nt, nsb);"); - printer->end_block(1); + printer->pop_block(); } void CodegenAccVisitor::print_net_send_buf_count_update_to_device() const { - printer->start_block("if (nt->compute_gpu)"); + printer->push_block("if (nt->compute_gpu)"); printer->add_line("nrn_pragma_acc(update device(nsb->_cnt))"); printer->add_line("nrn_pragma_omp(target update to(nsb->_cnt))"); - printer->end_block(1); + printer->pop_block(); } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 0246e8a379..1bf9e9a5bc 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -19,8 +19,8 @@ namespace nmodl { namespace codegen { /** - * @addtogroup codegen_backends - * @{ + * \addtogroup codegen_backends + * \{ */ /** @@ -97,7 +97,8 @@ class CodegenAccVisitor: public CodegenCppVisitor { void print_instance_struct_transfer_routine_declarations() override; /// define helper functions for copying the instance struct to the device - void print_instance_struct_transfer_routines(std::vector const&) override; + void print_instance_struct_transfer_routines( + const std::vector& ptr_members) override; /// call helper function for copying the instance struct to the device void print_instance_struct_copy_to_device() override; @@ -105,30 +106,32 @@ class CodegenAccVisitor: public CodegenCppVisitor { /// call helper function that deletes the instance struct from the device void print_instance_struct_delete_from_device() override; - // update derivimplicit advance flag on the gpu device + /// update derivimplicit advance flag on the gpu device void print_deriv_advance_flag_transfer_to_device() const override; - // update NetSendBuffer_t count from device to host + /// update NetSendBuffer_t count from device to host void print_net_send_buf_count_update_to_host() const override; - // update NetSendBuffer_t from device to host + /// update NetSendBuffer_t from device to host void print_net_send_buf_update_to_host() const override; - // update NetSendBuffer_t count from host to device + /// update NetSendBuffer_t count from host to device virtual void print_net_send_buf_count_update_to_device() const override; - // update dt from host to device + /// update dt from host to device virtual void print_dt_update_to_device() const override; // synchronise/wait on stream specific to NrnThread virtual void print_device_stream_wait() const override; - // print atomic capture pragma + /// print atomic capture pragma void print_device_atomic_capture_annotation() const override; - // print atomic update of NetSendBuffer_t cnt + /// print atomic update of NetSendBuffer_t cnt void print_net_send_buffering_cnt_update() const override; + /// Replace default implementation by a no-op + /// since the buffer cannot be grown up during gpu execution void print_net_send_buffering_grow() override; @@ -146,7 +149,7 @@ class CodegenAccVisitor: public CodegenCppVisitor { : CodegenCppVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; -/** @} */ // end of codegen_backends +/** \} */ // end of codegen_backends } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index f2230c15b2..5ed3a41963 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -31,7 +31,7 @@ const std::map std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( ast::Ast& /* node */, - const std::shared_ptr& ast_node) { + const std::shared_ptr& ast_node) const { auto solve_block_ast_node = std::dynamic_pointer_cast(ast_node); std::stringstream unhandled_method_error_message; auto method = solve_block_ast_node->get_method(); @@ -53,7 +53,7 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_global_var( ast::Ast& node, - const std::shared_ptr& ast_node) { + const std::shared_ptr& ast_node) const { auto global_var = std::dynamic_pointer_cast(ast_node); std::stringstream error_message_global_var; if (node.get_symbol_table()->lookup(global_var->get_node_name())->get_write_count() > 0) { @@ -69,7 +69,7 @@ std::string CodegenCompatibilityVisitor::return_error_global_var( // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_param_var( ast::Ast& node, - const std::shared_ptr& ast_node) { + const std::shared_ptr& ast_node) const { auto param_assign = std::dynamic_pointer_cast(ast_node); std::stringstream error_message_global_var; auto symbol = node.get_symbol_table()->lookup(param_assign->get_node_name()); @@ -85,7 +85,7 @@ std::string CodegenCompatibilityVisitor::return_error_param_var( // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( ast::Ast& node, - const std::shared_ptr& /* ast_node */) { + const std::shared_ptr& /* ast_node */) const { std::stringstream error_message_no_bbcore_read_write; const auto& verbatim_nodes = collect_nodes(node, {AstNodeType::VERBATIM}); auto found_bbcore_read = false; @@ -129,15 +129,15 @@ std::string CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write( * some kind of incompatibility return false. */ -bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) { +bool CodegenCompatibilityVisitor::find_unhandled_ast_nodes(Ast& node) const { std::vector unhandled_ast_types; unhandled_ast_types.reserve(unhandled_ast_types_func.size()); - for (auto kv: unhandled_ast_types_func) { - unhandled_ast_types.push_back(kv.first); + for (auto [node_type, _]: unhandled_ast_types_func) { + unhandled_ast_types.push_back(node_type); } - unhandled_ast_nodes = collect_nodes(node, unhandled_ast_types); + const auto& unhandled_ast_nodes = collect_nodes(node, unhandled_ast_types); - std::stringstream ss; + std::ostringstream ss; for (const auto& it: unhandled_ast_nodes) { auto node_type = it->get_node_type(); ss << (this->*unhandled_ast_types_func.find(node_type)->second)(node, it); diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 140b5acb06..311c540efb 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -28,8 +28,8 @@ namespace codegen { using namespace ast; /** - * @addtogroup codegen_backends - * @{ + * \addtogroup codegen_backends + * \{ */ /** @@ -44,22 +44,18 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// function needed to be called for every kind of error typedef std::string (CodegenCompatibilityVisitor::*FunctionPointer)( ast::Ast& node, - const std::shared_ptr&); + const std::shared_ptr&) const; /// associated container to find the function needed to be called in /// for every ast::AstNodeType that is unsupported static const std::map unhandled_ast_types_func; /// Set of handled solvers by the NMODL \c C++ code generator - const std::set handled_solvers{codegen::naming::CNEXP_METHOD, - codegen::naming::EULER_METHOD, - codegen::naming::DERIVIMPLICIT_METHOD, - codegen::naming::SPARSE_METHOD, - codegen::naming::AFTER_CVODE_METHOD}; - - /// Vector that stores all the ast::Node that are unhandled - /// by the NMODL \c C++ code generator - std::vector> unhandled_ast_nodes; + static const inline std::set handled_solvers{codegen::naming::CNEXP_METHOD, + codegen::naming::EULER_METHOD, + codegen::naming::DERIVIMPLICIT_METHOD, + codegen::naming::SPARSE_METHOD, + codegen::naming::AFTER_CVODE_METHOD}; public: /// \name Ctor & dtor @@ -70,12 +66,12 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \} - /// Function that searches the ast::Ast for nodes that + /// Search the ast::Ast for nodes that /// are incompatible with NMODL \c C++ code generator /// /// \param node Ast /// \return bool if there are unhandled nodes or not - bool find_unhandled_ast_nodes(Ast& node); + bool find_unhandled_ast_nodes(Ast& node) const; private: /// Takes as parameter an std::shared_ptr, @@ -87,7 +83,7 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \return std::string error std::string return_error_if_solve_method_is_unhandled( ast::Ast& node, - const std::shared_ptr& ast_node); + const std::shared_ptr& ast_node) const; /// Takes as parameter an std::shared_ptr node /// and returns a relative error with the name, the type @@ -98,7 +94,8 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param ast_node Ast node which is checked /// \return std::string error template - std::string return_error_with_name(ast::Ast& node, const std::shared_ptr& ast_node) { + std::string return_error_with_name(ast::Ast& /* node */, + const std::shared_ptr& ast_node) const { auto real_type_block = std::dynamic_pointer_cast(ast_node); auto token = real_type_block->get_token(); try { @@ -122,8 +119,8 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param ast_node Ast node which is checked /// \return std::string error template - std::string return_error_without_name(ast::Ast& node, - const std::shared_ptr& ast_node) { + std::string return_error_without_name(ast::Ast& /* node */, + const std::shared_ptr& ast_node) const { auto real_type_block = std::dynamic_pointer_cast(ast_node); return fmt::format("{}construct found at [{}] is not handled\n", real_type_block->get_nmodl_name(), @@ -138,9 +135,11 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param node Ast /// \param ast_node Ast node which is checked /// \return std::string error - std::string return_error_global_var(ast::Ast& node, const std::shared_ptr& ast_node); + std::string return_error_global_var(ast::Ast& node, + const std::shared_ptr& ast_node) const; - std::string return_error_param_var(ast::Ast& node, const std::shared_ptr& ast_node); + std::string return_error_param_var(ast::Ast& node, + const std::shared_ptr& ast_node) const; /// Takes as parameter the ast::Ast and checks if the /// functions "bbcore_read" and "bbcore_write" are defined @@ -151,11 +150,12 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { /// \param node Ast /// \param ast_node Not used by the function /// \return std::string error - std::string return_error_if_no_bbcore_read_write(ast::Ast& node, - const std::shared_ptr& ast_node); + std::string return_error_if_no_bbcore_read_write( + ast::Ast& node, + const std::shared_ptr& ast_node) const; }; -/** @} */ // end of codegen_backends +/** \} */ // end of codegen_backends } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 88fd8985dc..9337d4c2c5 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -34,16 +34,12 @@ namespace codegen { using namespace ast; using visitor::DefUseAnalyzeVisitor; -using visitor::DUChain; using visitor::DUState; using visitor::RenameVisitor; using visitor::SymtabVisitor; using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; -using SymbolType = std::shared_ptr; - -namespace codegen_utils = nmodl::codegen::utils; /****************************************************************************************/ /* Overloaded visitor routines */ @@ -156,8 +152,7 @@ void CodegenCppVisitor::visit_local_list_statement(const LocalListStatement& nod if (!codegen) { return; } - auto type = local_var_type() + " "; - printer->add_text(type); + printer->add_text(local_var_type(), ' '); print_vector_elements(node.get_variables(), ", "); } @@ -329,11 +324,11 @@ void CodegenCppVisitor::visit_protect_statement(const ast::ProtectStatement& nod void CodegenCppVisitor::visit_mutex_lock(const ast::MutexLock& node) { printer->fmt_line("#pragma omp critical ({})", info.mod_suffix); printer->add_indent(); - printer->start_block(); + printer->push_block(); } void CodegenCppVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { - printer->end_block(1); + printer->pop_block(); } /****************************************************************************************/ @@ -456,12 +451,12 @@ int CodegenCppVisitor::position_of_int_var(const std::string& name) const { * representation (1e+20, 1E-15) then keep it as it is. */ std::string CodegenCppVisitor::format_double_string(const std::string& s_value) { - return codegen_utils::format_double_string(s_value); + return utils::format_double_string(s_value); } std::string CodegenCppVisitor::format_float_string(const std::string& s_value) { - return codegen_utils::format_float_string(s_value); + return utils::format_float_string(s_value); } @@ -471,23 +466,23 @@ std::string CodegenCppVisitor::format_float_string(const std::string& s_value) { * block can appear as statement using expression statement which need to * be inspected. */ -bool CodegenCppVisitor::need_semicolon(Statement* node) { +bool CodegenCppVisitor::need_semicolon(const Statement& node) { // clang-format off - if (node->is_if_statement() - || node->is_else_if_statement() - || node->is_else_statement() - || node->is_from_statement() - || node->is_verbatim() - || node->is_from_statement() - || node->is_conductance_hint() - || node->is_while_statement() - || node->is_protect_statement() - || node->is_mutex_lock() - || node->is_mutex_unlock()) { + if (node.is_if_statement() + || node.is_else_if_statement() + || node.is_else_statement() + || node.is_from_statement() + || node.is_verbatim() + || node.is_from_statement() + || node.is_conductance_hint() + || node.is_while_statement() + || node.is_protect_statement() + || node.is_mutex_lock() + || node.is_mutex_unlock()) { return false; } - if (node->is_expression_statement()) { - auto expression = dynamic_cast(node)->get_expression(); + if (node.is_expression_statement()) { + auto expression = dynamic_cast(node).get_expression(); if (expression->is_statement_block() || expression->is_eigen_newton_solver_block() || expression->is_eigen_linear_solver_block() @@ -554,7 +549,7 @@ int CodegenCppVisitor::int_variables_size() const { * different variable names, we rely on backend-specific read_ion_variable_name * and write_ion_variable_name method which will be overloaded. */ -std::vector CodegenCppVisitor::ion_read_statements(BlockType type) { +std::vector CodegenCppVisitor::ion_read_statements(BlockType type) const { if (optimize_ion_variable_copies()) { return ion_read_statements_optimized(type); } @@ -584,14 +579,14 @@ std::vector CodegenCppVisitor::ion_read_statements(BlockType type) } -std::vector CodegenCppVisitor::ion_read_statements_optimized(BlockType type) { +std::vector CodegenCppVisitor::ion_read_statements_optimized(BlockType type) const { std::vector statements; for (const auto& ion: info.ions) { for (const auto& var: ion.writes) { if (ion.is_ionic_conc(var)) { auto variables = read_ion_variable_name(var); auto first = "ionvar." + variables.first; - auto second = get_variable_name(variables.second); + const auto& second = get_variable_name(variables.second); statements.push_back(fmt::format("{} = {};", first, second)); } } @@ -789,7 +784,7 @@ void CodegenCppVisitor::update_index_semantics() { } -std::vector CodegenCppVisitor::get_float_variables() { +std::vector CodegenCppVisitor::get_float_variables() const { // sort with definition order auto comparator = [](const SymbolType& first, const SymbolType& second) -> bool { return first->get_definition_order() < second->get_definition_order(); @@ -799,7 +794,7 @@ std::vector CodegenCppVisitor::get_float_variables() { auto states = info.state_vars; // each state variable has corresponding Dstate variable - for (auto& state: states) { + for (const auto& state: states) { auto name = "D" + state->get_name(); auto symbol = make_symbol(name); if (state->is_array()) { @@ -979,18 +974,21 @@ std::vector CodegenCppVisitor::get_int_variables() { /****************************************************************************************/ std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) { - std::string param{}; - for (auto iter = params.begin(); iter != params.end(); iter++) { - param += fmt::format("{}{} {}{}", - std::get<0>(*iter), - std::get<1>(*iter), - std::get<2>(*iter), - std::get<3>(*iter)); - if (!nmodl::utils::is_last(iter, params)) { - param += ", "; + std::string str; + bool is_first = true; + for (const auto& param: params) { + if (is_first) { + is_first = false; + } else { + str += ", "; } + str += fmt::format("{}{} {}{}", + std::get<0>(param), + std::get<1>(param), + std::get<2>(param), + std::get<3>(param)); } - return param; + return str; } @@ -1172,25 +1170,25 @@ bool CodegenCppVisitor::optimize_ion_variable_copies() const { void CodegenCppVisitor::print_memory_allocation_routine() const { printer->add_newline(2); auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->fmt_start_block("static inline void* mem_alloc({})", args); + printer->fmt_push_block("static inline void* mem_alloc({})", args); printer->add_line("void* ptr;"); printer->add_line("posix_memalign(&ptr, alignment, num*size);"); printer->add_line("memset(ptr, 0, size);"); printer->add_line("return ptr;"); - printer->end_block(1); + printer->pop_block(); printer->add_newline(2); - printer->start_block("static inline void mem_free(void* ptr)"); + printer->push_block("static inline void mem_free(void* ptr)"); printer->add_line("free(ptr);"); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_abort_routine() const { printer->add_newline(2); - printer->start_block("static inline void coreneuron_abort()"); + printer->push_block("static inline void coreneuron_abort()"); printer->add_line("abort();"); - printer->end_block(1); + printer->pop_block(); } @@ -1222,7 +1220,7 @@ std::string CodegenCppVisitor::global_var_struct_type_qualifier() { } void CodegenCppVisitor::print_global_var_struct_decl() { - printer->fmt_line("{} {};", global_struct(), global_struct_instance()); + printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); } /****************************************************************************************/ @@ -1240,10 +1238,10 @@ void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, bool open_brace, bool close_brace) { if (open_brace) { - printer->start_block(); + printer->push_block(); } - auto statements = node.get_statements(); + const auto& statements = node.get_statements(); for (const auto& statement: statements) { if (statement_to_skip(*statement)) { continue; @@ -1254,8 +1252,8 @@ void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, printer->add_indent(); } statement->accept(*this); - if (need_semicolon(statement.get())) { - printer->add_text(";"); + if (need_semicolon(*statement)) { + printer->add_text(';'); } if (!statement->is_mutex_lock() && !statement->is_mutex_unlock()) { printer->add_newline(); @@ -1263,13 +1261,13 @@ void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, } if (close_brace) { - printer->end_block(); + printer->pop_block_nl(0); } } void CodegenCppVisitor::print_function_call(const FunctionCall& node) { - auto name = node.get_node_name(); + const auto& name = node.get_node_name(); auto function_name = name; if (defined_method(name)) { function_name = method_name(name); @@ -1290,8 +1288,8 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { return; } - auto arguments = node.get_arguments(); - printer->fmt_text("{}(", function_name); + const auto& arguments = node.get_arguments(); + printer->add_text(function_name, '('); if (defined_method(name)) { printer->add_text(internal_method_arguments()); @@ -1301,7 +1299,7 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { } print_vector_elements(arguments, ", "); - printer->add_text(")"); + printer->add_text(')'); } @@ -1363,12 +1361,12 @@ void CodegenCppVisitor::print_function_prototypes() { printer->add_newline(2); for (const auto& node: info.functions) { print_function_declaration(*node, node->get_node_name()); - printer->add_text(";"); + printer->add_text(';'); printer->add_newline(); } for (const auto& node: info.procedures) { print_function_declaration(*node, node->get_node_name()); - printer->add_text(";"); + printer->add_text(';'); printer->add_newline(); } codegen = false; @@ -1420,13 +1418,13 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { printer->add_newline(2); print_device_method_annotation(); - printer->fmt_start_block("void check_{}({})", - method_name(name), - get_parameter_str(internal_params)); + printer->fmt_push_block("void check_{}({})", + method_name(name), + get_parameter_str(internal_params)); { - printer->fmt_start_block("if ({} == 0)", use_table_var); + printer->fmt_push_block("if ({} == 0)", use_table_var); printer->add_line("return;"); - printer->end_block(1); + printer->pop_block(); printer->add_line("static bool make_table = true;"); for (const auto& variable: depend_variables) { @@ -1434,27 +1432,27 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { } for (const auto& variable: depend_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - printer->fmt_start_block("if (save_{} != {})", var_name, instance_name); + const auto& var_name = variable->get_node_name(); + const auto& instance_name = get_variable_name(var_name); + printer->fmt_push_block("if (save_{} != {})", var_name, instance_name); printer->add_line("make_table = true;"); - printer->end_block(1); + printer->pop_block(); } - printer->start_block("if (make_table)"); + printer->push_block("if (make_table)"); { printer->add_line("make_table = false;"); printer->add_indent(); - printer->fmt_text("{} = ", tmin_name); + printer->add_text(tmin_name, " = "); from->accept(*this); - printer->add_text(";"); + printer->add_text(';'); printer->add_newline(); printer->add_indent(); printer->add_text("double tmax = "); to->accept(*this); - printer->add_text(";"); + printer->add_text(';'); printer->add_newline(); @@ -1462,7 +1460,7 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { printer->fmt_line("{} = 1./dx;", mfac_name); printer->fmt_line("double x = {};", tmin_name); - printer->fmt_start_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); + printer->fmt_push_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); auto function = method_name("f_" + name); if (node.is_procedure_block()) { printer->fmt_line("{}({}, x);", function, internal_method_arguments()); @@ -1487,7 +1485,7 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { function, internal_method_arguments()); } - printer->end_block(1); + printer->pop_block(); for (const auto& variable: depend_variables) { auto var_name = variable->get_node_name(); @@ -1495,9 +1493,9 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { printer->fmt_line("save_{} = {};", var_name, instance_name); } } - printer->end_block(1); + printer->pop_block(); } - printer->end_block(1); + printer->pop_block(); } @@ -1513,10 +1511,10 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) printer->add_newline(2); print_function_declaration(node, name); - printer->start_block(); + printer->push_block(); { const auto& params = node.get_parameters(); - printer->fmt_start_block("if ({} == 0)", use_table_var); + printer->fmt_push_block("if ({} == 0)", use_table_var); if (node.is_procedure_block()) { printer->fmt_line("{}({}, {});", function_name, @@ -1529,13 +1527,13 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) internal_method_arguments(), params[0].get()->get_node_name()); } - printer->end_block(1); + printer->pop_block(); printer->fmt_line("double xi = {} * ({} - {});", mfac_name, params[0].get()->get_node_name(), tmin_name); - printer->start_block("if (isnan(xi))"); + printer->push_block("if (isnan(xi))"); if (node.is_procedure_block()) { for (const auto& var: table_variables) { auto var_name = get_variable_name(var->get_node_name()); @@ -1552,9 +1550,9 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) } else { printer->add_line("return xi;"); } - printer->end_block(1); + printer->pop_block(); - printer->fmt_start_block("if (xi <= 0. || xi >= {}.)", with); + printer->fmt_push_block("if (xi <= 0. || xi >= {}.)", with); printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); if (node.is_procedure_block()) { for (const auto& variable: table_variables) { @@ -1576,7 +1574,7 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) auto table_name = get_variable_name("t_" + name); printer->fmt_line("return {}[index];", table_name); } - printer->end_block(1); + printer->pop_block(); printer->add_line("int i = int(xi);"); printer->add_line("double theta = xi - double(i);"); @@ -1606,7 +1604,7 @@ void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) printer->fmt_line("return {0}[i] + theta * ({0}[i+1] - {0}[i]);", table_name); } } - printer->end_block(1); + printer->pop_block(); } @@ -1619,7 +1617,7 @@ void CodegenCppVisitor::print_check_table_thread_function() { auto name = method_name("check_table_thread"); auto parameters = external_method_parameters(true); - printer->fmt_start_block("static void {} ({})", name, parameters); + printer->fmt_push_block("static void {} ({})", name, parameters); printer->add_line("setup_instance(nt, ml);"); printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); printer->add_line("double v = 0;"); @@ -1630,7 +1628,7 @@ void CodegenCppVisitor::print_check_table_thread_function() { printer->fmt_line("{}({});", method_name_str, arguments); } - printer->end_block(1); + printer->pop_block(); } @@ -1639,7 +1637,7 @@ void CodegenCppVisitor::print_function_or_procedure(const ast::Block& node, printer->add_newline(2); print_function_declaration(node, name); printer->add_text(" "); - printer->start_block(); + printer->push_block(); // function requires return variable declaration if (node.is_function_block()) { @@ -1651,7 +1649,7 @@ void CodegenCppVisitor::print_function_or_procedure(const ast::Block& node, print_statement_block(*node.get_statement_block(), false, false); printer->fmt_line("return ret_{};", name); - printer->end_block(1); + printer->pop_block(); } @@ -1705,7 +1703,7 @@ void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& nod params.emplace_back("", "double", "", i->get_node_name()); } printer->fmt_line("double {}({})", method_name(name), get_parameter_str(params)); - printer->start_block(); + printer->push_block(); printer->fmt_line("double _arg[{}];", p.size()); for (size_t i = 0; i < p.size(); ++i) { printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); @@ -1713,14 +1711,14 @@ void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& nod printer->fmt_line("return hoc_func_table({}, {}, _arg);", get_variable_name(std::string("_ptable_" + name), true), p.size()); - printer->end_block(1); + printer->pop_block(); - printer->fmt_start_block("double table_{}()", method_name(name)); + printer->fmt_push_block("double table_{}()", method_name(name)); printer->fmt_line("hoc_spec_table(&{}, {});", get_variable_name(std::string("_ptable_" + name)), p.size()); printer->add_line("return 0.;"); - printer->end_block(1); + printer->pop_block(); } /** @@ -1776,9 +1774,9 @@ void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlo int N = node.get_n_state_vars()->get_value(); const auto functor_name = info.functor_names[&node]; - printer->fmt_start_block("struct {0}", functor_name); + printer->fmt_push_block("struct {0}", functor_name); printer->add_line("NrnThread* nt;"); - printer->fmt_line("{0}* inst;", instance_struct()); + printer->add_line(instance_struct(), "* inst;"); printer->add_line("int id, pnodecount;"); printer->add_line("double v;"); printer->add_line("const Datum* indexes;"); @@ -1792,9 +1790,10 @@ void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlo print_statement_block(*node.get_variable_block(), false, false); printer->add_newline(); - printer->start_block("void initialize()"); + printer->push_block("void initialize()"); print_statement_block(*node.get_initialize_block(), false, false); - printer->end_block(2); + printer->pop_block(); + printer->add_newline(); printer->fmt_line( "{0}(NrnThread* nt, {1}* inst, int id, int pnodecount, double v, const Datum* indexes, " @@ -1817,19 +1816,20 @@ void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlo float_type, N, is_functor_const(variable_block, functor_block) ? "const " : ""); - printer->start_block(); + printer->push_block(); printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); print_statement_block(functor_block, false, false); - printer->end_block(2); + printer->pop_block(); + printer->add_newline(); // assign newton solver results in matrix X to state vars - printer->start_block("void finalize()"); + printer->push_block("void finalize()"); print_statement_block(*node.get_finalize_block(), false, false); - printer->end_block(1); + printer->pop_block(); - printer->end_block(";"); + printer->pop_block(";"); } void CodegenCppVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { @@ -1885,12 +1885,12 @@ void CodegenCppVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSo void CodegenCppVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { if (N <= 4) { // Faster compared to LU, given the template specialization in Eigen. - printer->add_line("bool invertible;"); - printer->add_line("nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible);"); - printer->add_line("nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm;"); - printer->add_line( - "if (!invertible) assert(false && \"Singular or ill-conditioned matrix " - "(Eigen::inverse)!\");"); + printer->add_multi_line(R"CODE( + bool invertible; + nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible); + nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm; + if (!invertible) assert(false && "Singular or ill-conditioned matrix (Eigen::inverse)!"); + )CODE"); } else { // In Eigen the default storage order is ColMajor. // Crout's implementation requires matrices stored in RowMajor order (C-style arrays). @@ -1935,7 +1935,7 @@ std::string CodegenCppVisitor::internal_method_arguments() { * @todo: figure out how to correctly handle qualifiers */ CodegenCppVisitor::ParamVector CodegenCppVisitor::internal_method_parameters() { - auto params = ParamVector(); + ParamVector params; params.emplace_back("", "int", "", "id"); params.emplace_back("", "int", "", "pnodecount"); params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); @@ -1951,12 +1951,12 @@ CodegenCppVisitor::ParamVector CodegenCppVisitor::internal_method_parameters() { } -std::string CodegenCppVisitor::external_method_arguments() { +const char* CodegenCppVisitor::external_method_arguments() noexcept { return "id, pnodecount, data, indexes, thread, nt, ml, v"; } -std::string CodegenCppVisitor::external_method_parameters(bool table) { +const char* CodegenCppVisitor::external_method_parameters(bool table) noexcept { if (table) { return "int id, int pnodecount, double* data, Datum* indexes, " "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, int tml_id"; @@ -1966,7 +1966,7 @@ std::string CodegenCppVisitor::external_method_parameters(bool table) { } -std::string CodegenCppVisitor::nrn_thread_arguments() { +std::string CodegenCppVisitor::nrn_thread_arguments() const { if (ion_variable_struct_required()) { return "id, pnodecount, ionvar, data, indexes, thread, nt, ml, v"; } @@ -2142,24 +2142,24 @@ void CodegenCppVisitor::print_nmodl_constants() { void CodegenCppVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline int first_pointer_var_index()"); + printer->push_block("static inline int first_pointer_var_index()"); printer->fmt_line("return {};", info.first_pointer_var_index); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline int float_variables_size()"); + printer->push_block("static inline int float_variables_size()"); printer->fmt_line("return {};", float_variables_size()); - printer->end_block(1); + printer->pop_block(); printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline int int_variables_size()"); + printer->push_block("static inline int int_variables_size()"); printer->fmt_line("return {};", int_variables_size()); - printer->end_block(1); + printer->pop_block(); } @@ -2169,42 +2169,42 @@ void CodegenCppVisitor::print_net_receive_arg_size_getter() { } printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline int num_net_receive_args()"); + printer->push_block("static inline int num_net_receive_args()"); printer->fmt_line("return {};", info.num_net_receive_parameters); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_mech_type_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline int get_mech_type()"); + printer->push_block("static inline int get_mech_type()"); // false => get it from the host-only global struct, not the instance structure printer->fmt_line("return {};", get_variable_name("mech_type", false)); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_memb_list_getter() { printer->add_newline(2); print_device_method_annotation(); - printer->start_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); - printer->start_block("if (!nt->_ml_list)"); + printer->push_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); + printer->push_block("if (!nt->_ml_list)"); printer->add_line("return nullptr;"); - printer->end_block(1); + printer->pop_block(); printer->add_line("return nt->_ml_list[get_mech_type()];"); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_namespace_start() { printer->add_newline(2); - printer->start_block("namespace coreneuron"); + printer->push_block("namespace coreneuron"); } void CodegenCppVisitor::print_namespace_stop() { - printer->end_block(1); + printer->pop_block(); } @@ -2230,33 +2230,35 @@ void CodegenCppVisitor::print_thread_getters() { printer->add_line("/** thread specific helper routines for derivimplicit */"); printer->add_newline(1); - printer->fmt_start_block("static inline int* deriv{}_advance(ThreadDatum* thread)", list); + printer->fmt_push_block("static inline int* deriv{}_advance(ThreadDatum* thread)", list); printer->fmt_line("return &(thread[{}].i);", tid); - printer->end_block(2); + printer->pop_block(); + printer->add_newline(); - printer->fmt_start_block("static inline int dith{}()", list); + printer->fmt_push_block("static inline int dith{}()", list); printer->fmt_line("return {};", tid+1); - printer->end_block(2); + printer->pop_block(); + printer->add_newline(); - printer->fmt_start_block("static inline void** newtonspace{}(ThreadDatum* thread)", list); + printer->fmt_push_block("static inline void** newtonspace{}(ThreadDatum* thread)", list); printer->fmt_line("return &(thread[{}]._pvoid);", tid+2); - printer->end_block(1); + printer->pop_block(); } if (info.vectorize && !info.thread_variables.empty()) { printer->add_newline(2); printer->add_line("/** tid for thread variables */"); - printer->start_block("static inline int thread_var_tid()"); + printer->push_block("static inline int thread_var_tid()"); printer->fmt_line("return {};", info.thread_var_thread_id); - printer->end_block(1); + printer->pop_block(); } if (info.vectorize && !info.top_local_variables.empty()) { printer->add_newline(2); printer->add_line("/** tid for top local tread variables */"); - printer->start_block("static inline int top_local_var_tid()"); + printer->push_block("static inline int top_local_var_tid()"); printer->fmt_line("return {};", info.top_local_thread_id); - printer->end_block(1); + printer->pop_block(); } // clang-format on } @@ -2341,7 +2343,7 @@ std::string CodegenCppVisitor::update_if_ion_variable_name(const std::string& na std::string CodegenCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { - std::string varname = update_if_ion_variable_name(name); + const std::string& varname = update_if_ion_variable_name(name); // clang-format off auto symbol_comparator = [&varname](const SymbolType& sym) { @@ -2419,39 +2421,43 @@ void CodegenCppVisitor::print_backend_info() { auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; printer->add_line("/*********************************************************"); - printer->fmt_line("Model Name : {}", info.mod_suffix); - printer->fmt_line("Filename : {}", info.mod_file + ".mod"); - printer->fmt_line("NMODL Version : {}", nmodl_version()); + printer->add_line("Model Name : ", info.mod_suffix); + printer->add_line("Filename : ", info.mod_file, ".mod"); + printer->add_line("NMODL Version : ", nmodl_version()); printer->fmt_line("Vectorized : {}", info.vectorize); printer->fmt_line("Threadsafe : {}", info.thread_safe); - printer->fmt_line("Created : {}", stringutils::trim(data_time_str)); - printer->fmt_line("Backend : {}", backend_name()); - printer->fmt_line("NMODL Compiler : {}", version); + printer->add_line("Created : ", stringutils::trim(data_time_str)); + printer->add_line("Backend : ", backend_name()); + printer->add_line("NMODL Compiler : ", version); printer->add_line("*********************************************************/"); } void CodegenCppVisitor::print_standard_includes() { printer->add_newline(); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); + printer->add_multi_line(R"CODE( + #include + #include + #include + #include + )CODE"); } void CodegenCppVisitor::print_coreneuron_includes() { printer->add_newline(); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); - printer->add_line("#include "); + printer->add_multi_line(R"CODE( + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + )CODE"); if (info.eigen_newton_solver_exist) { printer->add_line("#include "); } @@ -2487,23 +2493,23 @@ void CodegenCppVisitor::print_coreneuron_includes() { * same for some variables to keep same code as neuron. */ // NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initialisers) { - const auto value_initialise = print_initialisers ? "{}" : ""; +void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { + const auto value_initialize = print_initializers ? "{}" : ""; const auto qualifier = global_var_struct_type_qualifier(); auto float_type = default_float_data_type(); printer->add_newline(2); printer->add_line("/** all global variables */"); - printer->fmt_start_block("struct {}", global_struct()); + printer->fmt_push_block("struct {}", global_struct()); for (const auto& ion: info.ions) { auto name = fmt::format("{}_type", ion.name); - printer->fmt_line("{}int {}{};", qualifier, name, value_initialise); + printer->fmt_line("{}int {}{};", qualifier, name, value_initialize); codegen_global_variables.push_back(make_symbol(name)); } if (info.point_process) { - printer->fmt_line("{}int point_type{};", qualifier, value_initialise); + printer->fmt_line("{}int point_type{};", qualifier, value_initialize); codegen_global_variables.push_back(make_symbol("point_type")); } @@ -2511,7 +2517,7 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali auto name = var->get_name() + "0"; auto symbol = program_symtab->lookup(name); if (symbol == nullptr) { - printer->fmt_line("{}{} {}{};", qualifier, float_type, name, value_initialise); + printer->fmt_line("{}{} {}{};", qualifier, float_type, name, value_initialize); codegen_global_variables.push_back(make_symbol(name)); } } @@ -2542,7 +2548,7 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali } if (!info.thread_variables.empty()) { - printer->fmt_line("{}int thread_data_in_use{};", qualifier, value_initialise); + printer->fmt_line("{}int thread_data_in_use{};", qualifier, value_initialize); printer->fmt_line("{}{} thread_data[{}] /* TODO init thread_data */;", qualifier, float_type, @@ -2554,10 +2560,10 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali } // TODO: remove this entirely? - printer->fmt_line("{}int reset{};", qualifier, value_initialise); + printer->fmt_line("{}int reset{};", qualifier, value_initialize); codegen_global_variables.push_back(make_symbol("reset")); - printer->fmt_line("{}int mech_type{};", qualifier, value_initialise); + printer->fmt_line("{}int mech_type{};", qualifier, value_initialize); codegen_global_variables.push_back(make_symbol("mech_type")); for (const auto& var: info.global_variables) { @@ -2575,7 +2581,7 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali qualifier, float_type, name, - print_initialisers ? fmt::format("{{{:g}}}", value) : std::string{}); + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); } codegen_global_variables.push_back(var); } @@ -2588,7 +2594,7 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali qualifier, float_type, name, - print_initialisers ? fmt::format("{{{:g}}}", value) : std::string{}); + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); codegen_global_variables.push_back(var); } @@ -2601,7 +2607,7 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali info.prime_variables_by_order.size())}; } auto const initializer_list = [&](auto const& primes, const char* prefix) -> std::string { - if (!print_initialisers) { + if (!print_initializers) { return {}; } std::string list{"{"}; @@ -2637,13 +2643,13 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali } if (info.table_count > 0) { - printer->fmt_line("{}double usetable{};", qualifier, print_initialisers ? "{1}" : ""); + printer->fmt_line("{}double usetable{};", qualifier, print_initializers ? "{1}" : ""); codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); for (const auto& block: info.functions_with_table) { - auto name = block->get_node_name(); - printer->fmt_line("{}{} tmin_{}{};", qualifier, float_type, name, value_initialise); - printer->fmt_line("{}{} mfac_{}{};", qualifier, float_type, name, value_initialise); + const auto& name = block->get_node_name(); + printer->fmt_line("{}{} tmin_{}{};", qualifier, float_type, name, value_initialize); + printer->fmt_line("{}{} mfac_{}{};", qualifier, float_type, name, value_initialize); codegen_global_variables.push_back(make_symbol("tmin_" + name)); codegen_global_variables.push_back(make_symbol("mfac_" + name)); } @@ -2659,10 +2665,10 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali name, array_len, num_values, - value_initialise); + value_initialize); } else { printer->fmt_line( - "{}{} {}[{}]{};", qualifier, float_type, name, num_values, value_initialise); + "{}{} {}[{}]{};", qualifier, float_type, name, num_values, value_initialize); } codegen_global_variables.push_back(make_symbol(name)); } @@ -2677,11 +2683,11 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali printer->fmt_line("{}ThreadDatum ext_call_thread[{}]{};", qualifier, info.thread_data_index, - value_initialise); + value_initialize); codegen_global_variables.push_back(make_symbol("ext_call_thread")); } - printer->end_block(";"); + printer->pop_block(";"); print_global_var_struct_assertions(); print_global_var_struct_decl(); @@ -2717,7 +2723,7 @@ void CodegenCppVisitor::print_mechanism_info() { if (v->is_array()) { name += fmt::format("[{}]", v->get_length()); } - printer->add_line(add_escape_quote(name) + ","); + printer->add_line(add_escape_quote(name), ","); } }; @@ -2725,8 +2731,8 @@ void CodegenCppVisitor::print_mechanism_info() { printer->add_line("/** channel information */"); printer->add_line("static const char *mechanism[] = {"); printer->increase_indent(); - printer->add_line(add_escape_quote(nmodl_version()) + ","); - printer->add_line(add_escape_quote(info.mod_suffix) + ","); + printer->add_line(add_escape_quote(nmodl_version()), ","); + printer->add_line(add_escape_quote(info.mod_suffix), ","); variable_printer(info.range_parameter_vars); printer->add_line("0,"); variable_printer(info.range_assigned_vars); @@ -2853,16 +2859,16 @@ static std::string get_register_type_for_ba_block(const ast::Block* block) { void CodegenCppVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); - printer->fmt_start_block("void _{}_reg()", info.mod_file); + printer->fmt_push_block("void _{}_reg()", info.mod_file); // type related information auto suffix = add_escape_quote(info.mod_suffix); printer->add_newline(); printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); printer->fmt_line("{} = mech_type;", get_variable_name("mech_type", false)); - printer->start_block("if (mech_type == -1)"); + printer->push_block("if (mech_type == -1)"); printer->add_line("return;"); - printer->end_block(1); + printer->pop_block(); printer->add_newline(); printer->add_line("_nrn_layout_reg(mech_type, 0);"); // 0 for SoA @@ -2988,7 +2994,7 @@ void CodegenCppVisitor::print_mechanism_register() { // register variables for hoc printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); - printer->end_block(1); + printer->pop_block(); } @@ -3000,7 +3006,7 @@ void CodegenCppVisitor::print_thread_memory_callbacks() { // thread_mem_init callback printer->add_newline(2); printer->add_line("/** thread memory allocation callback */"); - printer->start_block("static void thread_mem_init(ThreadDatum* thread) "); + printer->push_block("static void thread_mem_init(ThreadDatum* thread) "); if (info.vectorize && info.derivimplicit_used()) { printer->fmt_line("thread[dith{}()].pval = nullptr;", info.derivimplicit_list_num); @@ -3015,19 +3021,20 @@ void CodegenCppVisitor::print_thread_memory_callbacks() { auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); - printer->fmt_start_block("if ({})", thread_data_in_use); + printer->fmt_push_block("if ({})", thread_data_in_use); printer->fmt_line("thread[thread_var_tid()].pval = {};", allocation); - printer->restart_block("else"); + printer->chain_block("else"); printer->fmt_line("thread[thread_var_tid()].pval = {};", thread_data); printer->fmt_line("{} = 1;", thread_data_in_use); - printer->end_block(1); + printer->pop_block(); } - printer->end_block(3); + printer->pop_block(); + printer->add_newline(2); // thread_mem_cleanup callback printer->add_line("/** thread memory cleanup callback */"); - printer->start_block("static void thread_mem_cleanup(ThreadDatum* thread) "); + printer->push_block("static void thread_mem_cleanup(ThreadDatum* thread) "); // clang-format off if (info.vectorize && info.derivimplicit_used()) { @@ -3044,55 +3051,55 @@ void CodegenCppVisitor::print_thread_memory_callbacks() { if (info.thread_var_data_size != 0) { auto thread_data = get_variable_name("thread_data"); auto thread_data_in_use = get_variable_name("thread_data_in_use"); - printer->fmt_start_block("if (thread[thread_var_tid()].pval == {})", thread_data); + printer->fmt_push_block("if (thread[thread_var_tid()].pval == {})", thread_data); printer->fmt_line("{} = 0;", thread_data_in_use); - printer->restart_block("else"); + printer->chain_block("else"); printer->add_line("free(thread[thread_var_tid()].pval);"); - printer->end_block(1); + printer->pop_block(); } - printer->end_block(1); + printer->pop_block(); } -void CodegenCppVisitor::print_mechanism_range_var_structure(bool print_initialisers) { - auto const value_initialise = print_initialisers ? "{}" : ""; +void CodegenCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { + auto const value_initialize = print_initializers ? "{}" : ""; auto int_type = default_int_data_type(); printer->add_newline(2); printer->add_line("/** all mechanism instance variables and global variables */"); - printer->fmt_start_block("struct {} ", instance_struct()); + printer->fmt_push_block("struct {} ", instance_struct()); for (auto const& [var, type]: info.neuron_global_variables) { auto const name = var->get_name(); printer->fmt_line("{}* {}{};", type, name, - print_initialisers ? fmt::format("{{&coreneuron::{}}}", name) + print_initializers ? fmt::format("{{&coreneuron::{}}}", name) : std::string{}); } for (auto& var: codegen_float_variables) { - auto name = var->get_name(); + const auto& name = var->get_name(); auto type = get_range_var_float_type(var); auto qualifier = is_constant_variable(name) ? "const " : ""; - printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialise); + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); } for (auto& var: codegen_int_variables) { - auto name = var.symbol->get_name(); + const auto& name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? "const " : ""; - printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialise); + printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialize); } else { auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialise); + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); } } printer->fmt_line("{}* {}{};", global_struct(), naming::INST_GLOBAL_MEMBER, - print_initialisers ? fmt::format("{{&{}}}", global_struct_instance()) + print_initializers ? fmt::format("{{&{}}}", global_struct_instance()) : std::string{}); - printer->end_block(";"); + printer->pop_block(";"); } @@ -3102,7 +3109,7 @@ void CodegenCppVisitor::print_ion_var_structure() { } printer->add_newline(2); printer->add_line("/** ion write variables */"); - printer->start_block("struct IonCurVar"); + printer->push_block("struct IonCurVar"); std::string float_type = default_float_data_type(); std::vector members; @@ -3122,14 +3129,15 @@ void CodegenCppVisitor::print_ion_var_structure() { print_ion_var_constructor(members); - printer->end_block(";"); + printer->pop_block(";"); } void CodegenCppVisitor::print_ion_var_constructor(const std::vector& members) { // constructor printer->add_newline(); - printer->add_line("IonCurVar() : ", 0); + printer->add_indent(); + printer->add_text("IonCurVar() : "); for (int i = 0; i < members.size(); i++) { printer->fmt_text("{}(0)", members[i]); if (i + 1 < members.size()) { @@ -3155,14 +3163,14 @@ void CodegenCppVisitor::print_setup_range_variable() { auto type = float_data_type(); printer->add_newline(2); printer->add_line("/** allocate and setup array for range variable */"); - printer->fmt_start_block("static inline {}* setup_range_variable(double* variable, int n)", - type); + printer->fmt_push_block("static inline {}* setup_range_variable(double* variable, int n)", + type); printer->fmt_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));", type); - printer->start_block("for(size_t i = 0; i < n; i++)"); + printer->push_block("for(size_t i = 0; i < n; i++)"); printer->add_line("data[i] = variable[i];"); - printer->end_block(1); + printer->pop_block(); printer->add_line("return data;"); - printer->end_block(1); + printer->pop_block(); } @@ -3195,8 +3203,8 @@ void CodegenCppVisitor::print_instance_variable_setup() { printer->add_newline(); printer->add_line("// Allocate instance structure"); - printer->fmt_start_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", - method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD)); + printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", + method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD)); printer->add_line("assert(!ml->instance);"); printer->add_line("assert(!ml->global_variables);"); printer->add_line("assert(ml->global_variables_size == 0);"); @@ -3207,7 +3215,8 @@ void CodegenCppVisitor::print_instance_variable_setup() { printer->add_line("ml->instance = inst;"); printer->fmt_line("ml->global_variables = inst->{};", naming::INST_GLOBAL_MEMBER); printer->fmt_line("ml->global_variables_size = sizeof({});", global_struct()); - printer->end_block(2); + printer->pop_block(); + printer->add_newline(); auto const cast_inst_and_assert_validity = [&]() { printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); @@ -3225,19 +3234,22 @@ void CodegenCppVisitor::print_instance_variable_setup() { print_instance_struct_transfer_routine_declarations(); printer->add_line("// Deallocate the instance structure"); - printer->fmt_start_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", - method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); + printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", + method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); cast_inst_and_assert_validity(); print_instance_struct_delete_from_device(); - printer->add_line("delete inst;"); - printer->add_line("ml->instance = nullptr;"); - printer->add_line("ml->global_variables = nullptr;"); - printer->add_line("ml->global_variables_size = 0;"); - printer->end_block(2); + printer->add_multi_line(R"CODE( + delete inst; + ml->instance = nullptr; + ml->global_variables = nullptr; + ml->global_variables_size = 0; + )CODE"); + printer->pop_block(); + printer->add_newline(); printer->add_line("/** initialize mechanism instance variables */"); - printer->start_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml)"); + printer->push_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml)"); cast_inst_and_assert_validity(); std::string stride; @@ -3287,7 +3299,8 @@ void CodegenCppVisitor::print_instance_variable_setup() { ptr_members.push_back(std::move(name)); } print_instance_struct_copy_to_device(); - printer->end_block(2); // setup_instance + printer->pop_block(); // setup_instance + printer->add_newline(); print_instance_struct_transfer_routines(ptr_members); } @@ -3353,7 +3366,7 @@ void CodegenCppVisitor::print_global_function_common_code(BlockType type, } print_global_method_annotation(); - printer->fmt_start_block("void {}({})", method, args); + printer->fmt_push_block("void {}({})", method, args); if (type != BlockType::Destructor && type != BlockType::Constructor) { // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks // running anything on the GPU. @@ -3363,11 +3376,13 @@ void CodegenCppVisitor::print_global_function_common_code(BlockType type, /// Related to https://github.com/BlueBrain/nmodl/issues/692 printer->add_line("#ifndef CORENEURON_BUILD"); } - printer->add_line("int nodecount = ml->nodecount;"); - printer->add_line("int pnodecount = ml->_nodecount_padded;"); - printer->add_line("const int* node_index = ml->nodeindices;"); - printer->add_line("double* data = ml->data;"); - printer->add_line("const double* voltage = nt->_actual_v;"); + printer->add_multi_line(R"CODE( + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + const int* node_index = ml->nodeindices; + double* data = ml->data; + const double* voltage = nt->_actual_v; + )CODE"); if (type == BlockType::Equation) { printer->add_line("double* vec_rhs = nt->_actual_rhs;"); @@ -3401,13 +3416,13 @@ void CodegenCppVisitor::print_nrn_init(bool skip_init_check) { print_deriv_advance_flag_transfer_to_device(); printer->fmt_line("auto ns = newtonspace{}(thread);", list_num); printer->fmt_line("auto& th = thread[dith{}()];", list_num); - printer->start_block("if (*ns == nullptr)"); + printer->push_block("if (*ns == nullptr)"); printer->fmt_line("int vec_size = 2*{}*pnodecount*sizeof(double);", nequation); printer->fmt_line("double* vec = makevector(vec_size);", nequation); printer->fmt_line("th.pval = vec;", list_num); printer->fmt_line("*ns = nrn_cons_newtonspace({}, pnodecount);", nequation); print_newtonspace_transfer_to_device(); - printer->end_block(1); + printer->pop_block(); // clang-format on } @@ -3417,7 +3432,7 @@ void CodegenCppVisitor::print_nrn_init(bool skip_init_check) { print_global_variable_device_update_annotation(); if (skip_init_check) { - printer->start_block("if (_nrn_skip_initmodel == 0)"); + printer->push_block("if (_nrn_skip_initmodel == 0)"); } if (!info.changed_dt.empty()) { @@ -3430,21 +3445,21 @@ void CodegenCppVisitor::print_nrn_init(bool skip_init_check) { } print_channel_iteration_block_parallel_hint(BlockType::Initial, info.initial_node); - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); if (info.net_receive_node != nullptr) { printer->fmt_line("{} = -1e20;", get_variable_name("tsave")); } print_initial_block(info.initial_node); - printer->end_block(1); + printer->pop_block(); if (!info.changed_dt.empty()) { printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); print_dt_update_to_device(); } - printer->end_block(1); + printer->pop_block(); if (info.derivimplicit_used()) { printer->add_line("deriv_advance_flag = 1;"); @@ -3457,7 +3472,7 @@ void CodegenCppVisitor::print_nrn_init(bool skip_init_check) { print_kernel_data_present_annotation_block_end(); if (skip_init_check) { - printer->end_block(1); + printer->pop_block(); } codegen = false; } @@ -3487,7 +3502,7 @@ void CodegenCppVisitor::print_before_after_block(const ast::Block* node, size_t print_global_function_common_code(BlockType::BeforeAfter, function_name); print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter, node); - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); @@ -3512,8 +3527,8 @@ void CodegenCppVisitor::print_before_after_block(const ast::Block* node, size_t } /// loop end including data annotation block - printer->end_block(1); - printer->end_block(1); + printer->pop_block(); + printer->pop_block(); print_kernel_data_present_annotation_block_end(); codegen = false; @@ -3527,7 +3542,7 @@ void CodegenCppVisitor::print_nrn_constructor() { print_statement_block(*block, false, false); } printer->add_line("#endif"); - printer->end_block(1); + printer->pop_block(); } @@ -3539,7 +3554,7 @@ void CodegenCppVisitor::print_nrn_destructor() { print_statement_block(*block, false, false); } printer->add_line("#endif"); - printer->end_block(1); + printer->pop_block(); } @@ -3556,9 +3571,9 @@ void CodegenCppVisitor::print_functors_definitions() { void CodegenCppVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); - printer->fmt_start_block("static void {}(double* data, Datum* indexes, int type)", method); + printer->fmt_push_block("static void {}(double* data, Datum* indexes, int type)", method); printer->add_line("// do nothing"); - printer->end_block(1); + printer->pop_block(); } /** @@ -3574,19 +3589,19 @@ void CodegenCppVisitor::print_watch_activate() { printer->add_newline(2); auto inst = fmt::format("{}* inst", instance_struct()); - printer->fmt_start_block( + printer->fmt_push_block( "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, " "double v, bool &watch_remove)", inst); // initialize all variables only during first watch statement - printer->start_block("if (watch_remove == false)"); + printer->push_block("if (watch_remove == false)"); for (int i = 0; i < info.watch_count; i++) { auto name = get_variable_name(fmt::format("watch{}", i + 1)); printer->fmt_line("{} = 0;", name); } printer->add_line("watch_remove = true;"); - printer->end_block(1); + printer->pop_block(); /** * \todo Similar to neuron/coreneuron we are using @@ -3594,7 +3609,7 @@ void CodegenCppVisitor::print_watch_activate() { */ for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; - printer->fmt_start_block("if (watch_id == {})", i); + printer->fmt_push_block("if (watch_id == {})", i); auto varname = get_variable_name(fmt::format("watch{}", i + 1)); printer->add_indent(); @@ -3604,9 +3619,9 @@ void CodegenCppVisitor::print_watch_activate() { printer->add_text(");"); printer->add_newline(); - printer->end_block(1); + printer->pop_block(); } - printer->end_block(1); + printer->pop_block(); codegen = false; } @@ -3630,7 +3645,7 @@ void CodegenCppVisitor::print_watch_check() { // we don't need to have ivdep pragma related check print_channel_iteration_block_parallel_hint(BlockType::Watch, nullptr); - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); if (info.is_voltage_used_by_watch_statements()) { printer->add_line("int node_id = node_index[id];"); @@ -3643,11 +3658,11 @@ void CodegenCppVisitor::print_watch_check() { for (int i = 0; i < info.watch_statements.size(); i++) { auto statement = info.watch_statements[i]; - auto watch = statement->get_statements().front(); - auto varname = get_variable_name(fmt::format("watch{}", i + 1)); + const auto& watch = statement->get_statements().front(); + const auto& varname = get_variable_name(fmt::format("watch{}", i + 1)); // start block 1 - printer->fmt_start_block("if ({}&2 && watch_untriggered)", varname); + printer->fmt_push_block("if ({}&2 && watch_untriggered)", varname); // start block 2 printer->add_indent(); @@ -3658,15 +3673,15 @@ void CodegenCppVisitor::print_watch_check() { printer->increase_indent(); // start block 3 - printer->fmt_start_block("if (({}&1) == 0)", varname); + printer->fmt_push_block("if (({}&1) == 0)", varname); printer->add_line("watch_untriggered = false;"); - auto tqitem = get_variable_name("tqitem"); - auto point_process = get_variable_name("point_process"); + const auto& tqitem = get_variable_name("tqitem"); + const auto& point_process = get_variable_name("point_process"); printer->add_indent(); printer->add_text("net_send_buffering("); - auto t = get_variable_name("t"); + const auto& t = get_variable_name("t"); printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, -1, {}, {}+0.0, ", tqitem, point_process, @@ -3674,34 +3689,37 @@ void CodegenCppVisitor::print_watch_check() { watch->get_value()->accept(*this); printer->add_text(");"); printer->add_newline(); - printer->end_block(1); + printer->pop_block(); - printer->fmt_line("{} = 3;", varname); + printer->add_line(varname, " = 3;"); // end block 3 // start block 3 printer->decrease_indent(); - printer->start_block("} else"); - printer->fmt_line("{} = 2;", varname); - printer->end_block(1); + printer->push_block("} else"); + printer->add_line(varname, " = 2;"); + printer->pop_block(); // end block 3 - printer->end_block(1); + printer->pop_block(); // end block 1 } - printer->end_block(1); + printer->pop_block(); print_send_event_move(); print_kernel_data_present_annotation_block_end(); - printer->end_block(1); + printer->pop_block(); codegen = false; } void CodegenCppVisitor::print_net_receive_common_code(const Block& node, bool need_mech_inst) { - printer->add_line("int tid = pnt->_tid;"); - printer->add_line("int id = pnt->_i_instance;"); - printer->add_line("double v = 0;"); + printer->add_multi_line(R"CODE( + int tid = pnt->_tid; + int id = pnt->_i_instance; + double v = 0; + )CODE"); + if (info.artificial_cell || node.is_initial_block()) { printer->add_line("NrnThread* nt = nrn_threads + tid;"); printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); @@ -3710,12 +3728,14 @@ void CodegenCppVisitor::print_net_receive_common_code(const Block& node, bool ne print_kernel_data_present_annotation_block_begin(); } - printer->add_line("int nodecount = ml->nodecount;"); - printer->add_line("int pnodecount = ml->_nodecount_padded;"); - printer->add_line("double* data = ml->data;"); - printer->add_line("double* weights = nt->weights;"); - printer->add_line("Datum* indexes = ml->pdata;"); - printer->add_line("ThreadDatum* thread = ml->_thread;"); + printer->add_multi_line(R"CODE( + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + double* data = ml->data; + double* weights = nt->weights; + Datum* indexes = ml->pdata; + ThreadDatum* thread = ml->_thread; + )CODE"); if (need_mech_inst) { printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); } @@ -3745,7 +3765,7 @@ void CodegenCppVisitor::print_net_receive_common_code(const Block& node, bool ne void CodegenCppVisitor::print_net_send_call(const FunctionCall& node) { auto const& arguments = node.get_arguments(); - auto tqitem = get_variable_name("tqitem"); + const auto& tqitem = get_variable_name("tqitem"); std::string weight_index = "weight_index"; std::string pnt = "pnt"; @@ -3764,14 +3784,14 @@ void CodegenCppVisitor::print_net_send_call(const FunctionCall& node) { if (info.artificial_cell) { printer->fmt_text("artcell_net_send(&{}, {}, {}, nt->_t+", tqitem, weight_index, pnt); } else { - auto point_process = get_variable_name("point_process"); - std::string t = get_variable_name("t"); + const auto& point_process = get_variable_name("point_process"); + const auto& t = get_variable_name("t"); printer->add_text("net_send_buffering("); printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t); } // clang-format off print_vector_elements(arguments, ", "); - printer->add_text(")"); + printer->add_text(')'); } @@ -3781,7 +3801,7 @@ void CodegenCppVisitor::print_net_move_call(const FunctionCall& node) { } auto const& arguments = node.get_arguments(); - auto tqitem = get_variable_name("tqitem"); + const auto& tqitem = get_variable_name("tqitem"); std::string weight_index = "-1"; std::string pnt = "pnt"; @@ -3792,7 +3812,7 @@ void CodegenCppVisitor::print_net_move_call(const FunctionCall& node) { print_vector_elements(arguments, ", "); printer->add_text(")"); } else { - auto point_process = get_variable_name("point_process"); + const auto& point_process = get_variable_name("point_process"); printer->add_text("net_send_buffering("); printer->fmt_text("nt, ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); print_vector_elements(arguments, ", "); @@ -3808,7 +3828,7 @@ void CodegenCppVisitor::print_net_event_call(const FunctionCall& node) { printer->add_text("net_event(pnt, "); print_vector_elements(arguments, ", "); } else { - auto point_process = get_variable_name("point_process"); + const auto& point_process = get_variable_name("point_process"); printer->add_text("net_send_buffering("); printer->fmt_text("nt, ml->_net_send_buffer, 1, -1, -1, {}, ", point_process); print_vector_elements(arguments, ", "); @@ -3842,9 +3862,9 @@ void CodegenCppVisitor::print_net_event_call(const FunctionCall& node) { * So, the `R` in AST needs to be renamed with `(*R)`. */ static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive_node, const ast::Node& node) { - auto parameters = net_receive_node.get_parameters(); + const auto& parameters = net_receive_node.get_parameters(); for (auto& parameter: parameters) { - auto name = parameter->get_node_name(); + const auto& name = parameter->get_node_name(); auto var_used = VarUsageVisitor().variable_used(node, name); if (var_used) { RenameVisitor vr(name, "(*" + name + ")"); @@ -3868,7 +3888,7 @@ void CodegenCppVisitor::print_net_init() { auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); printer->add_line("/** initialize block for net receive */"); - printer->fmt_start_block("static void net_init({})", args); + printer->fmt_push_block("static void net_init({})", args); auto block = node->get_statement_block().get(); if (block->get_statements().empty()) { printer->add_line("// do nothing"); @@ -3882,7 +3902,7 @@ void CodegenCppVisitor::print_net_init() { print_net_send_buf_update_to_host(); } } - printer->end_block(1); + printer->pop_block(); codegen = false; printing_net_init = false; } @@ -3892,16 +3912,18 @@ void CodegenCppVisitor::print_send_event_move() { printer->add_newline(); printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); print_net_send_buf_update_to_host(); - printer->start_block("for (int i=0; i < nsb->_cnt; i++)"); - printer->add_line("int type = nsb->_sendtype[i];"); - printer->add_line("int tid = nt->id;"); - printer->add_line("double t = nsb->_nsb_t[i];"); - printer->add_line("double flag = nsb->_nsb_flag[i];"); - printer->add_line("int vdata_index = nsb->_vdata_index[i];"); - printer->add_line("int weight_index = nsb->_weight_index[i];"); - printer->add_line("int point_index = nsb->_pnt_index[i];"); - printer->add_line("net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag);"); - printer->end_block(1); + printer->push_block("for (int i=0; i < nsb->_cnt; i++)"); + printer->add_multi_line(R"CODE( + int type = nsb->_sendtype[i]; + int tid = nt->id; + double t = nsb->_nsb_t[i]; + double flag = nsb->_nsb_flag[i]; + int vdata_index = nsb->_vdata_index[i]; + int weight_index = nsb->_weight_index[i]; + int point_index = nsb->_pnt_index[i]; + net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag); + )CODE"); + printer->pop_block(); printer->add_line("nsb->_cnt = 0;"); print_net_send_buf_count_update_to_device(); } @@ -3914,21 +3936,22 @@ std::string CodegenCppVisitor::net_receive_buffering_declaration() { void CodegenCppVisitor::print_get_memb_list() { printer->add_line("Memb_list* ml = get_memb_list(nt);"); - printer->start_block("if (!ml)"); + printer->push_block("if (!ml)"); printer->add_line("return;"); - printer->end_block(2); + printer->pop_block(); + printer->add_newline(); } void CodegenCppVisitor::print_net_receive_loop_begin() { printer->add_line("int count = nrb->_displ_cnt;"); print_channel_iteration_block_parallel_hint(BlockType::NetReceive, info.net_receive_node); - printer->start_block("for (int i = 0; i < count; i++)"); + printer->push_block("for (int i = 0; i < count; i++)"); } void CodegenCppVisitor::print_net_receive_loop_end() { - printer->end_block(1); + printer->pop_block(); } @@ -3937,11 +3960,11 @@ void CodegenCppVisitor::print_net_receive_buffering(bool need_mech_inst) { return; } printer->add_newline(2); - printer->start_block(net_receive_buffering_declaration()); + printer->push_block(net_receive_buffering_declaration()); print_get_memb_list(); - auto net_receive = method_name("net_receive_kernel"); + const auto& net_receive = method_name("net_receive_kernel"); print_kernel_data_present_annotation_block_begin(); @@ -3952,15 +3975,17 @@ void CodegenCppVisitor::print_net_receive_buffering(bool need_mech_inst) { print_net_receive_loop_begin(); printer->add_line("int start = nrb->_displ[i];"); printer->add_line("int end = nrb->_displ[i+1];"); - printer->start_block("for (int j = start; j < end; j++)"); - printer->add_line("int index = nrb->_nrb_index[j];"); - printer->add_line("int offset = nrb->_pnt_index[index];"); - printer->add_line("double t = nrb->_nrb_t[index];"); - printer->add_line("int weight_index = nrb->_weight_index[index];"); - printer->add_line("double flag = nrb->_nrb_flag[index];"); - printer->add_line("Point_process* point_process = nt->pntprocs + offset;"); - printer->fmt_line("{}(t, point_process, inst, nt, ml, weight_index, flag);", net_receive); - printer->end_block(1); + printer->push_block("for (int j = start; j < end; j++)"); + printer->add_multi_line(R"CODE( + int index = nrb->_nrb_index[j]; + int offset = nrb->_pnt_index[index]; + double t = nrb->_nrb_t[index]; + int weight_index = nrb->_weight_index[index]; + double flag = nrb->_nrb_flag[index]; + Point_process* point_process = nt->pntprocs + offset; + )CODE"); + printer->add_line(net_receive, "(t, point_process, inst, nt, ml, weight_index, flag);"); + printer->pop_block(); print_net_receive_loop_end(); print_device_stream_wait(); @@ -3972,7 +3997,7 @@ void CodegenCppVisitor::print_net_receive_buffering(bool need_mech_inst) { } print_kernel_data_present_annotation_block_end(); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_net_send_buffering_cnt_update() const { @@ -3980,9 +4005,9 @@ void CodegenCppVisitor::print_net_send_buffering_cnt_update() const { } void CodegenCppVisitor::print_net_send_buffering_grow() { - printer->start_block("if (i >= nsb->_size)"); + printer->push_block("if (i >= nsb->_size)"); printer->add_line("nsb->grow();"); - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_net_send_buffering() { @@ -3995,19 +4020,21 @@ void CodegenCppVisitor::print_net_send_buffering() { auto args = "const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; - printer->fmt_start_block("static inline void net_send_buffering({})", args); + printer->fmt_push_block("static inline void net_send_buffering({})", args); printer->add_line("int i = 0;"); print_net_send_buffering_cnt_update(); print_net_send_buffering_grow(); - printer->start_block("if (i < nsb->_size)"); - printer->add_line("nsb->_sendtype[i] = type;"); - printer->add_line("nsb->_vdata_index[i] = vdata_index;"); - printer->add_line("nsb->_weight_index[i] = weight_index;"); - printer->add_line("nsb->_pnt_index[i] = point_index;"); - printer->add_line("nsb->_nsb_t[i] = t;"); - printer->add_line("nsb->_nsb_flag[i] = flag;"); - printer->end_block(1); - printer->end_block(1); + printer->push_block("if (i < nsb->_size)"); + printer->add_multi_line(R"CODE( + nsb->_sendtype[i] = type; + nsb->_vdata_index[i] = vdata_index; + nsb->_weight_index[i] = weight_index; + nsb->_pnt_index[i] = point_index; + nsb->_nsb_t[i] = t; + nsb->_nsb_flag[i] = flag; + )CODE"); + printer->pop_block(); + printer->pop_block(); } @@ -4023,7 +4050,7 @@ void CodegenCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { // sanitize node_name since we want to substitute names like (*w) as they are auto old_name = std::regex_replace(args[i_arg]->get_node_name(), regex_special_chars, R"(\$&)"); - auto new_name = fmt::format("weights[{} + nt->_fornetcon_weight_perm[i]]", i_arg); + const auto& new_name = fmt::format("weights[{} + nt->_fornetcon_weight_perm[i]]", i_arg); v.set(old_name, new_name); statement_block->accept(v); } @@ -4060,7 +4087,7 @@ void CodegenCppVisitor::print_net_receive_kernel() { rename_net_receive_arguments(*info.net_receive_node, *node); std::string name; - auto params = ParamVector(); + ParamVector params; if (!info.artificial_cell) { name = method_name("net_receive_kernel"); params.emplace_back("", "double", "", "t"); @@ -4079,7 +4106,7 @@ void CodegenCppVisitor::print_net_receive_kernel() { } printer->add_newline(2); - printer->fmt_start_block("static inline void {}({})", name, get_parameter_str(params)); + printer->fmt_push_block("static inline void {}({})", name, get_parameter_str(params)); print_net_receive_common_code(*node, info.artificial_cell); if (info.artificial_cell) { printer->add_line("double t = nt->_t;"); @@ -4101,8 +4128,7 @@ void CodegenCppVisitor::print_net_receive_kernel() { printer->add_indent(); node->get_statement_block()->accept(*this); printer->add_newline(); - printer->end_block(); - printer->add_newline(); + printer->pop_block(); printing_net_receive = false; codegen = false; @@ -4116,26 +4142,28 @@ void CodegenCppVisitor::print_net_receive() { codegen = true; printing_net_receive = true; if (!info.artificial_cell) { - std::string name = method_name("net_receive"); - auto params = ParamVector(); + const auto& name = method_name("net_receive"); + ParamVector params; params.emplace_back("", "Point_process*", "", "pnt"); params.emplace_back("", "int", "", "weight_index"); params.emplace_back("", "double", "", "flag"); printer->add_newline(2); - printer->fmt_start_block("static void {}({})", name, get_parameter_str(params)); + printer->fmt_push_block("static void {}({})", name, get_parameter_str(params)); printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); printer->add_line("Memb_list* ml = get_memb_list(nt);"); printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); - printer->start_block("if (nrb->_cnt >= nrb->_size)"); + printer->push_block("if (nrb->_cnt >= nrb->_size)"); printer->add_line("realloc_net_receive_buffer(nt, ml);"); - printer->end_block(1); - printer->add_line("int id = nrb->_cnt;"); - printer->add_line("nrb->_pnt_index[id] = pnt-nt->pntprocs;"); - printer->add_line("nrb->_weight_index[id] = weight_index;"); - printer->add_line("nrb->_nrb_t[id] = nt->_t;"); - printer->add_line("nrb->_nrb_flag[id] = flag;"); - printer->add_line("nrb->_cnt++;"); - printer->end_block(1); + printer->pop_block(); + printer->add_multi_line(R"CODE( + int id = nrb->_cnt; + nrb->_pnt_index[id] = pnt-nt->pntprocs; + nrb->_weight_index[id] = weight_index; + nrb->_nrb_t[id] = nt->_t; + nrb->_nrb_flag[id] = flag; + nrb->_cnt++; + )CODE"); + printer->pop_block(); } printing_net_receive = false; codegen = false; @@ -4149,20 +4177,20 @@ void CodegenCppVisitor::print_net_receive() { * actual variable names? [resolved now?] * slist needs to added as local variable */ -void CodegenCppVisitor::print_derivimplicit_kernel(Block* block) { +void CodegenCppVisitor::print_derivimplicit_kernel(const Block& block) { auto ext_args = external_method_arguments(); auto ext_params = external_method_parameters(); auto suffix = info.mod_suffix; auto list_num = info.derivimplicit_list_num; - auto block_name = block->get_node_name(); + auto block_name = block.get_node_name(); auto primes_size = info.primes_size; auto stride = "*pnodecount+id"; printer->add_newline(2); - printer->start_block("namespace"); - printer->fmt_start_block("struct _newton_{}_{}", block_name, info.mod_suffix); - printer->fmt_start_block("int operator()({}) const", external_method_parameters()); + printer->push_block("namespace"); + printer->fmt_push_block("struct _newton_{}_{}", block_name, info.mod_suffix); + printer->fmt_push_block("int operator()({}) const", external_method_parameters()); auto const instance = fmt::format("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); auto const slist1 = fmt::format("auto const& slist{} = {};", @@ -4190,37 +4218,38 @@ void CodegenCppVisitor::print_derivimplicit_kernel(Block* block) { printer->add_line(dlist1); printer->add_line(dlist2); codegen = true; - print_statement_block(*block->get_statement_block(), false, false); + print_statement_block(*block.get_statement_block(), false, false); codegen = false; printer->add_line("int counter = -1;"); - printer->fmt_start_block("for (int i=0; i<{}; i++)", info.num_primes); - printer->fmt_start_block("if (*deriv{}_advance(thread))", list_num); + printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); + printer->fmt_push_block("if (*deriv{}_advance(thread))", list_num); printer->fmt_line( "dlist{0}[(++counter){1}] = " "data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;", list_num + 1, stride, list_num); - printer->restart_block("else"); + printer->chain_block("else"); printer->fmt_line("dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];", list_num + 1, stride, list_num); - printer->end_block(1); - printer->end_block(1); + printer->pop_block(); + printer->pop_block(); printer->add_line("return 0;"); - printer->end_block(1); // operator() - printer->end_block(";"); // struct - printer->end_block(2); // namespace - printer->fmt_start_block("int {}_{}({})", block_name, suffix, ext_params); + printer->pop_block(); // operator() + printer->pop_block(";"); // struct + printer->pop_block(); // namespace + printer->add_newline(); + printer->fmt_push_block("int {}_{}({})", block_name, suffix, ext_params); printer->add_line(instance); printer->fmt_line("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num); printer->add_line(slist1); printer->add_line(slist2); printer->add_line(dlist2); - printer->fmt_start_block("for (int i=0; i<{}; i++)", info.num_primes); + printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); printer->fmt_line("savstate{}[i{}] = data[slist{}[i]{}];", list_num, stride, list_num, stride); - printer->end_block(1); + printer->pop_block(); printer->fmt_line( "int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {}, " "slist{}, _newton_{}_{}{{}}, dlist{}, {});", @@ -4232,7 +4261,8 @@ void CodegenCppVisitor::print_derivimplicit_kernel(Block* block) { list_num + 1, ext_args); printer->add_line("return reset;"); - printer->end_block(3); + printer->pop_block(); + printer->add_newline(2); } @@ -4277,7 +4307,7 @@ void CodegenCppVisitor::print_nrn_state() { printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); print_channel_iteration_block_parallel_hint(BlockType::State, info.nrn_state_block); - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); printer->add_line("double v = voltage[node_id];"); @@ -4305,16 +4335,16 @@ void CodegenCppVisitor::print_nrn_state() { print_statement_block(*block, false, false); } - auto write_statements = ion_write_statements(BlockType::State); + const auto& write_statements = ion_write_statements(BlockType::State); for (auto& statement: write_statements) { - auto text = process_shadow_update_statement(statement, BlockType::State); + const auto& text = process_shadow_update_statement(statement, BlockType::State); printer->add_line(text); } - printer->end_block(1); + printer->pop_block(); print_kernel_data_present_annotation_block_end(); - printer->end_block(1); + printer->pop_block(); codegen = false; } @@ -4325,21 +4355,21 @@ void CodegenCppVisitor::print_nrn_state() { void CodegenCppVisitor::print_nrn_current(const BreakpointBlock& node) { - auto args = internal_method_parameters(); + const auto& args = internal_method_parameters(); const auto& block = node.get_statement_block(); printer->add_newline(2); print_device_method_annotation(); - printer->fmt_start_block("inline double nrn_current_{}({})", - info.mod_suffix, - get_parameter_str(args)); + printer->fmt_push_block("inline double nrn_current_{}({})", + info.mod_suffix, + get_parameter_str(args)); printer->add_line("double current = 0.0;"); print_statement_block(*block, false, false); for (auto& current: info.currents) { - auto name = get_variable_name(current); + const auto& name = get_variable_name(current); printer->fmt_line("current += {};", name); } printer->add_line("return current;"); - printer->end_block(1); + printer->pop_block(); } @@ -4370,10 +4400,10 @@ void CodegenCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& for (const auto& conductance: info.conductances) { if (!conductance.ion.empty()) { - auto lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + conductance.ion + "dv"; - auto rhs = get_variable_name(conductance.variable); - ShadowUseStatement statement{lhs, "+=", rhs}; - auto text = process_shadow_update_statement(statement, BlockType::Equation); + const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + conductance.ion + "dv"; + const auto& rhs = get_variable_name(conductance.variable); + const ShadowUseStatement statement{lhs, "+=", rhs}; + const auto& text = process_shadow_update_statement(statement, BlockType::Equation); printer->add_line(text); } } @@ -4387,7 +4417,7 @@ void CodegenCppVisitor::print_nrn_cur_non_conductance_kernel() { for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { - auto name = get_variable_name(var); + const auto& name = get_variable_name(var); printer->fmt_line("double di{} = {};", ion.name, name); } } @@ -4399,14 +4429,14 @@ void CodegenCppVisitor::print_nrn_cur_non_conductance_kernel() { for (auto& ion: info.ions) { for (auto& var: ion.writes) { if (ion.is_ionic_current(var)) { - auto lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; + const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; auto rhs = fmt::format("(di{}-{})/0.001", ion.name, get_variable_name(var)); if (info.point_process) { auto area = get_variable_name(naming::NODE_AREA_VARIABLE); rhs += fmt::format("*1.e2/{}", area); } - ShadowUseStatement statement{lhs, "+=", rhs}; - auto text = process_shadow_update_statement(statement, BlockType::Equation); + const ShadowUseStatement statement{lhs, "+=", rhs}; + const auto& text = process_shadow_update_statement(statement, BlockType::Equation); printer->add_line(text); } } @@ -4422,7 +4452,7 @@ void CodegenCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { print_ion_variable(); } - auto read_statements = ion_read_statements(BlockType::Equation); + const auto& read_statements = ion_read_statements(BlockType::Equation); for (auto& statement: read_statements) { printer->add_line(statement); } @@ -4433,14 +4463,14 @@ void CodegenCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { print_nrn_cur_conductance_kernel(node); } - auto write_statements = ion_write_statements(BlockType::Equation); + const auto& write_statements = ion_write_statements(BlockType::Equation); for (auto& statement: write_statements) { auto text = process_shadow_update_statement(statement, BlockType::Equation); printer->add_line(text); } if (info.point_process) { - auto area = get_variable_name(naming::NODE_AREA_VARIABLE); + const auto& area = get_variable_name(naming::NODE_AREA_VARIABLE); printer->fmt_line("double mfactor = 1.e2/{};", area); printer->add_line("g = g*mfactor;"); printer->add_line("rhs = rhs*mfactor;"); @@ -4464,17 +4494,17 @@ void CodegenCppVisitor::print_fast_imem_calculation() { d = "g"; } - printer->start_block("if (nt->nrn_fast_imem)"); + printer->push_block("if (nt->nrn_fast_imem)"); if (nrn_cur_reduction_loop_required()) { - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); } printer->fmt_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs); printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d); if (nrn_cur_reduction_loop_required()) { - printer->end_block(1); + printer->pop_block(); } - printer->end_block(1); + printer->pop_block(); } void CodegenCppVisitor::print_nrn_cur() { @@ -4491,23 +4521,23 @@ void CodegenCppVisitor::print_nrn_cur() { printer->add_line("/** update current */"); print_global_function_common_code(BlockType::Equation); print_channel_iteration_block_parallel_hint(BlockType::Equation, info.breakpoint_node); - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_kernel(*info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); if (!nrn_cur_reduction_loop_required()) { print_fast_imem_calculation(); } - printer->end_block(1); + printer->pop_block(); if (nrn_cur_reduction_loop_required()) { - printer->start_block("for (int id = 0; id < nodecount; id++)"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_matrix_shadow_reduction(); - printer->end_block(1); + printer->pop_block(); print_fast_imem_calculation(); } print_kernel_data_present_annotation_block_end(); - printer->end_block(1); + printer->pop_block(); codegen = false; } @@ -4545,9 +4575,9 @@ void CodegenCppVisitor::print_common_getters() { } -void CodegenCppVisitor::print_data_structures(bool print_initialisers) { - print_mechanism_global_var_structure(print_initialisers); - print_mechanism_range_var_structure(print_initialisers); +void CodegenCppVisitor::print_data_structures(bool print_initializers) { + print_mechanism_global_var_structure(print_initializers); + print_mechanism_range_var_structure(print_initializers); print_ion_var_structure(); } @@ -4555,15 +4585,19 @@ void CodegenCppVisitor::print_v_unused() const { if (!info.vectorize) { return; } - printer->add_line("#if NRN_PRCELLSTATE"); - printer->add_line("inst->v_unused[id] = v;"); - printer->add_line("#endif"); + printer->add_multi_line(R"CODE( + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + )CODE"); } void CodegenCppVisitor::print_g_unused() const { - printer->add_line("#if NRN_PRCELLSTATE"); - printer->add_line("inst->g_unused[id] = g;"); - printer->add_line("#endif"); + printer->add_multi_line(R"CODE( + #if NRN_PRCELLSTATE + inst->g_unused[id] = g; + #endif + )CODE"); } void CodegenCppVisitor::print_compute_functions() { @@ -4581,7 +4615,7 @@ void CodegenCppVisitor::print_compute_functions() { print_before_after_block(info.before_after_blocks[i], i); } for (const auto& callback: info.derivimplicit_callbacks) { - auto block = callback->get_node_to_solve().get(); + const auto& block = *callback->get_node_to_solve(); print_derivimplicit_kernel(block); } print_net_send_buffering(); @@ -4630,7 +4664,7 @@ void CodegenCppVisitor::print_wrapper_routines() { } -void CodegenCppVisitor::set_codegen_global_variables(std::vector& global_vars) { +void CodegenCppVisitor::set_codegen_global_variables(const std::vector& global_vars) { codegen_global_variables = global_vars; } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 0cb44081f5..a0d54a29a6 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -32,18 +32,19 @@ #include "visitors/ast_visitor.hpp" -namespace nmodl { /// encapsulates code generation backend implementations +namespace nmodl { + namespace codegen { /** - * @defgroup codegen Code Generation Implementation - * @brief Implementations of code generation backends + * \defgroup codegen Code Generation Implementation + * \brief Implementations of code generation backends * - * @defgroup codegen_details Codegen Helpers - * @ingroup codegen - * @brief Helper routines/types for code generation - * @{ + * \defgroup codegen_details Codegen Helpers + * \ingroup codegen + * \brief Helper routines/types for code generation + * \{ */ /** @@ -53,7 +54,7 @@ namespace codegen { * Note: do not assign integers to these enums * */ -enum BlockType { +enum class BlockType { /// initial block Initial, @@ -112,7 +113,7 @@ struct IndexVariableInfo { /// symbol for the variable const std::shared_ptr symbol; - /// if variable reside in vdata field of NrnThread + /// if variable resides in vdata field of NrnThread /// typically true for bbcore pointer bool is_vdata = false; @@ -153,7 +154,7 @@ struct ShadowUseStatement { std::string rhs; }; -/** @} */ // end of codegen_details +/** \} */ // end of codegen_details using printer::CodePrinter; @@ -188,6 +189,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * - type (e.g. \c double) * - pointer qualifier (e.g. \c \_\_restrict\_\_) * - parameter name (e.g. \c data) + * */ using ParamVector = std::vector>; @@ -305,7 +307,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Operator for rhs vector update (matrix update) */ - std::string operator_for_rhs() const noexcept { + const char* operator_for_rhs() const noexcept { return info.electrode_current ? "+=" : "-="; } @@ -313,7 +315,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Operator for diagonal vector update (matrix update) */ - std::string operator_for_d() const noexcept { + const char* operator_for_d() const noexcept { return info.electrode_current ? "-=" : "+="; } @@ -321,7 +323,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Data type for the local variables */ - std::string local_var_type() const noexcept { + const char* local_var_type() const noexcept { return codegen::naming::DEFAULT_LOCAL_VAR_TYPE; } @@ -329,7 +331,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Default data type for floating point elements */ - std::string default_float_data_type() const noexcept { + const char* default_float_data_type() const noexcept { return codegen::naming::DEFAULT_FLOAT_TYPE; } @@ -345,7 +347,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Default data type for integer (offset) elements */ - std::string default_int_data_type() const noexcept { + const char* default_int_data_type() const noexcept { return codegen::naming::DEFAULT_INTEGER_TYPE; } @@ -492,7 +494,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param node The AST Statement node to check * \return \c true if this Statement requires a semicolon */ - static bool need_semicolon(ast::Statement* node); + static bool need_semicolon(const ast::Statement& node); /** @@ -634,7 +636,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Determine all \c float variables required during code generation * \return A \c vector of \c float variables */ - std::vector get_float_variables(); + std::vector get_float_variables() const; /** @@ -651,10 +653,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * current printer. Elements are expected to be of type nmodl::ast::Ast and are printed by being * visited. Care is taken to omit the separator after the the last element. * - * \tparam The element type in the vector, which must be of type nmodl::ast::Ast + * \tparam T The element type in the vector, which must be of type nmodl::ast::Ast * \param elements The vector of elements to be printed - * \param prefix A prefix string to printed before each element - * \param separator The seperator string to be printed between all elements + * \param separator The separator string to print between all elements + * \param prefix A prefix string to print before each element */ template void print_vector_elements(const std::vector& elements, @@ -667,7 +669,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. * * \param params The parameters that should be concatenated into the function parameter - * declaration \return The string representing the declaration of function parameters + * declaration + * \return The string representing the declaration of function parameters */ static std::string get_parameter_str(const ParamVector& params); @@ -722,7 +725,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param type The type of code block being generated * \return A \c vector of strings representing the reading of ion variables */ - std::vector ion_read_statements(BlockType type); + std::vector ion_read_statements(BlockType type) const; /** @@ -731,7 +734,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param type The type of code block being generated * \return A \c vector of strings representing the reading of ion variables */ - std::vector ion_read_statements_optimized(BlockType type); + std::vector ion_read_statements_optimized(BlockType type) const; /** @@ -789,7 +792,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - static std::string external_method_arguments(); + static const char* external_method_arguments() noexcept; /** @@ -801,7 +804,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param table * \return A string representing the parameters of the function */ - static std::string external_method_parameters(bool table = false); + static const char* external_method_parameters(bool table = false) noexcept; /** @@ -813,7 +816,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Arguments for "_threadargs_" macro in neuron implementation */ - std::string nrn_thread_arguments(); + std::string nrn_thread_arguments() const; /** @@ -980,10 +983,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print the structure that wraps all global variables used in the NMODL * - * @param print_initialisers Whether to include default values in the struct + * \param print_initializers Whether to include default values in the struct * definition (true: int foo{42}; false: int foo;) */ - void print_mechanism_global_var_structure(bool print_initialisers); + void print_mechanism_global_var_structure(bool print_initializers); /** @@ -1374,9 +1377,9 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print derivative kernel when \c derivimplicit method is used * - * \param block The corresponding AST node represening an NMODL \c derivimplicit block + * \param block The corresponding AST node representing an NMODL \c derivimplicit block */ - void print_derivimplicit_kernel(ast::Block* block); + void print_derivimplicit_kernel(const ast::Block& block); /** @@ -1540,9 +1543,9 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print all classes - * @param print_initialisers Whether to include default values. + * \param print_initializers Whether to include default values. */ - void print_data_structures(bool print_initialisers); + void print_data_structures(bool print_initializers); /** @@ -1577,6 +1580,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_wrapper_routines(); + /// This constructor is private, see the public section below to find how to create an instance + /// of this class. CodegenCppVisitor(std::string mod_filename, const std::string& output_dir, std::string float_type, @@ -1591,12 +1596,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} + /// This constructor is private, see the public section below to find how to create an instance + /// of this class. CodegenCppVisitor(std::string mod_filename, std::ostream& stream, std::string float_type, const bool optimize_ionvar_copies, - const std::string& extension, - const std::string& wrapper_ext) + const std::string& /* extension */, + const std::string& /* wrapper_ext */) : target_printer(std::make_shared(stream)) , wrapper_printer(std::make_shared(stream)) , printer(target_printer) @@ -1660,11 +1667,15 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} + /** + * Main and only member function to call after creating an instance of this class. + * \param program the AST to translate to C++ code + */ + void visit_program(const ast::Program& program) override; /** * Print the \c nrn\_init function definition - * \param skip_init_check \c true if we want the generated code to execute the initialization - * conditionally + * \param skip_init_check \c true to generate code executing the initialization conditionally */ void print_nrn_init(bool skip_init_check = true); @@ -1744,8 +1755,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print NMODL before / after block in target backend code - * @param node AST node of type before/after type being printed - * @param block_id Index of the before/after block + * \param node AST node of type before/after type being printed + * \param block_id Index of the before/after block */ virtual void print_before_after_block(const ast::Block* node, size_t block_id); @@ -1761,23 +1772,23 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Set the global variables to be generated in target backend code * \param global_vars */ - void set_codegen_global_variables(std::vector& global_vars); + void set_codegen_global_variables(const std::vector& global_vars); /** * Find unique variable name defined in nmodl::utils::SingletonRandomString by the * nmodl::visitor::SympySolverVisitor - * @param original_name Original name of variable to change - * @return std::string Unique name produced as [original_name]_[random_string] + * \param original_name Original name of variable to change + * \return std::string Unique name produced as [original_name]_[random_string] */ std::string find_var_unique_name(const std::string& original_name) const; /** * Print the structure that wraps all range and int variables required for the NMODL * - * @param print_initialisers Whether or not default values for variables + * \param print_initializers Whether or not default values for variables * be included in the struct declaration. */ - void print_mechanism_range_var_structure(bool print_initialisers); + void print_mechanism_range_var_structure(bool print_initializers); /** * Print the function that initialize instance structure @@ -1793,10 +1804,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void print_functors_definitions(); /** - * @brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its + * \brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its * functor * - * @param node \c EigenNewtonSolverBlock for which to print the functor + * \param node \c EigenNewtonSolverBlock for which to print the functor */ void print_functor_definition(const ast::EigenNewtonSolverBlock& node); @@ -1819,7 +1830,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_name(const ast::Name& node) override; void visit_paren_expression(const ast::ParenExpression& node) override; void visit_prime_name(const ast::PrimeName& node) override; - void visit_program(const ast::Program& node) override; void visit_statement_block(const ast::StatementBlock& node) override; void visit_string(const ast::String& node) override; void visit_solution_expression(const ast::SolutionExpression& node) override; @@ -1890,7 +1900,7 @@ void CodegenCppVisitor::print_function_declaration(const T& node, const std::str } // procedures have "int" return type by default - std::string return_type = "int"; + const char* return_type = "int"; if (node.is_function_block()) { return_type = default_float_data_type(); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index a8ccea09ae..fa9e4438b6 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -464,9 +464,9 @@ void CodegenHelperVisitor::find_table_variables() { void CodegenHelperVisitor::find_neuron_global_variables() { // TODO: it would be nicer not to have this hardcoded list using pair = std::pair; - for (auto [var, type]: {pair{naming::CELSIUS_VARIABLE, "double"}, - pair{"secondorder", "int"}, - pair{"pi", "double"}}) { + for (const auto& [var, type]: {pair{naming::CELSIUS_VARIABLE, "double"}, + pair{"secondorder", "int"}, + pair{"pi", "double"}}) { auto sym = psymtab->lookup(var); if (sym && (sym->get_read_count() || sym->get_write_count())) { info.neuron_global_variables.emplace_back(std::move(sym), type); diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index 874f3bfe4d..b7d40125a6 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -18,7 +18,7 @@ namespace codegen { using visitor::VarUsageVisitor; /// if any ion has write variable -bool CodegenInfo::ion_has_write_variable() const { +bool CodegenInfo::ion_has_write_variable() const noexcept { return std::any_of(ions.begin(), ions.end(), [](auto const& ion) { return !ion.writes.empty(); }); @@ -26,7 +26,7 @@ bool CodegenInfo::ion_has_write_variable() const { /// if given variable is ion write variable -bool CodegenInfo::is_ion_write_variable(const std::string& name) const { +bool CodegenInfo::is_ion_write_variable(const std::string& name) const noexcept { return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { return std::any_of(ion.writes.begin(), ion.writes.end(), [&name](auto const& var) { return var == name; @@ -36,7 +36,7 @@ bool CodegenInfo::is_ion_write_variable(const std::string& name) const { /// if given variable is ion read variable -bool CodegenInfo::is_ion_read_variable(const std::string& name) const { +bool CodegenInfo::is_ion_read_variable(const std::string& name) const noexcept { return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { return std::any_of(ion.reads.begin(), ion.reads.end(), [&name](auto const& var) { return var == name; @@ -46,13 +46,13 @@ bool CodegenInfo::is_ion_read_variable(const std::string& name) const { /// if either read or write variable -bool CodegenInfo::is_ion_variable(const std::string& name) const { +bool CodegenInfo::is_ion_variable(const std::string& name) const noexcept { return is_ion_read_variable(name) || is_ion_write_variable(name); } /// if a current (ionic or non-specific) -bool CodegenInfo::is_current(const std::string& name) const { +bool CodegenInfo::is_current(const std::string& name) const noexcept { return std::any_of(currents.begin(), currents.end(), [&name](auto const& var) { return var == name; }); @@ -60,20 +60,20 @@ bool CodegenInfo::is_current(const std::string& name) const { /// true is a given variable name if a ionic current /// (i.e. currents excluding non-specific current) -bool CodegenInfo::is_ionic_current(const std::string& name) const { +bool CodegenInfo::is_ionic_current(const std::string& name) const noexcept { return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { return ion.is_ionic_current(name); }); } /// true if given variable name is a ionic concentration -bool CodegenInfo::is_ionic_conc(const std::string& name) const { +bool CodegenInfo::is_ionic_conc(const std::string& name) const noexcept { return std::any_of(ions.begin(), ions.end(), [&name](auto const& ion) { return ion.is_ionic_conc(name); }); } -bool CodegenInfo::function_uses_table(std::string& name) const { +bool CodegenInfo::function_uses_table(const std::string& name) const noexcept { return std::any_of(functions_with_table.begin(), functions_with_table.end(), [&name](auto const& function) { return name == function->get_node_name(); }); diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 23a4dcc3a9..67cf6c9bb4 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -9,12 +9,13 @@ /** * \file - * \brief Variour types to store code generation specific information + * \brief Various types to store code generation specific information */ #include #include #include +#include #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" @@ -23,8 +24,8 @@ namespace nmodl { namespace codegen { /** - * @addtogroup codegen_details - * @{ + * \addtogroup codegen_details + * \{ */ /** @@ -62,7 +63,7 @@ struct Ion { Ion() = delete; - Ion(std::string name) + explicit Ion(std::string name) : name(std::move(name)) {} /** @@ -134,7 +135,7 @@ struct IndexSemantics { IndexSemantics() = delete; IndexSemantics(int index, std::string name, int size) : index(index) - , name(name) + , name(std::move(name)) , size(size) {} }; @@ -201,7 +202,7 @@ struct CodegenInfo { /// number of watch expressions int watch_count = 0; - // number of table statements + /// number of table statements int table_count = 0; /** @@ -302,7 +303,7 @@ struct CodegenInfo { /// range variables which are assigned variables as well std::vector range_assigned_vars; - /// reamining assigned variables + /// remaining assigned variables std::vector assigned_vars; /// all state variables @@ -326,8 +327,8 @@ struct CodegenInfo { /// note that if tqitem doesn't exist then the default value should be 0 int tqitem_index = 0; - // updated dt to use with steadystate solver (in initial block) - // empty string means no change in dt + /// updated dt to use with steadystate solver (in initial block) + /// empty string means no change in dt std::string changed_dt; /// global variables @@ -390,25 +391,25 @@ struct CodegenInfo { bool eigen_linear_solver_exist = false; /// if any ion has write variable - bool ion_has_write_variable() const; + bool ion_has_write_variable() const noexcept; /// if given variable is ion write variable - bool is_ion_write_variable(const std::string& name) const; + bool is_ion_write_variable(const std::string& name) const noexcept; /// if given variable is ion read variable - bool is_ion_read_variable(const std::string& name) const; + bool is_ion_read_variable(const std::string& name) const noexcept; /// if either read or write variable - bool is_ion_variable(const std::string& name) const; + bool is_ion_variable(const std::string& name) const noexcept; /// if given variable is a current - bool is_current(const std::string& name) const; + bool is_current(const std::string& name) const noexcept; /// if given variable is a ionic current - bool is_ionic_current(const std::string& name) const; + bool is_ionic_current(const std::string& name) const noexcept; /// if given variable is a ionic concentration - bool is_ionic_conc(const std::string& name) const; + bool is_ionic_conc(const std::string& name) const noexcept; /// if watch statements are used bool is_watch_used() const noexcept { @@ -424,7 +425,7 @@ struct CodegenInfo { return !derivimplicit_callbacks.empty(); } - bool function_uses_table(std::string& name) const; + bool function_uses_table(const std::string& name) const noexcept; /// true if EigenNewtonSolver is used in nrn_state block bool nrn_state_has_eigen_solver_block() const; @@ -436,7 +437,7 @@ struct CodegenInfo { bool require_wrote_conc = false; }; -/** @} */ // end of codegen_backends +/** \} */ // end of codegen_backends } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 9baf8342b9..b2e5f6d865 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -24,23 +24,23 @@ CodePrinter::CodePrinter(const std::string& filename) { } sbuf = ofs.rdbuf(); - result = std::make_shared(sbuf); + result = std::make_unique(sbuf); } -void CodePrinter::start_block() { - *result << "{"; +void CodePrinter::push_block() { + *result << '{'; add_newline(); indent_level++; } -void CodePrinter::start_block(std::string&& expression) { +void CodePrinter::push_block(const std::string& expression) { add_indent(); *result << expression << " {"; add_newline(); indent_level++; } -void CodePrinter::restart_block(std::string const& expression) { +void CodePrinter::chain_block(std::string const& expression) { --indent_level; add_indent(); *result << "} " << expression << " {"; @@ -49,41 +49,63 @@ void CodePrinter::restart_block(std::string const& expression) { } void CodePrinter::add_indent() { - *result << std::string(indent_level * NUM_SPACES, ' '); -} - -void CodePrinter::add_text(const std::string& text) { - *result << text; -} - -void CodePrinter::add_line(const std::string& text, int num_new_lines) { - add_indent(); - *result << text; - add_newline(num_new_lines); + for (std::size_t i = 0; i < indent_level * NUM_SPACES; ++i) { + *result << ' '; + } } void CodePrinter::add_multi_line(const std::string& text) { - auto lines = stringutils::split_string(text, '\n'); + const auto& lines = stringutils::split_string(text, '\n'); + + int prefix_length{}; + int start_line{}; + while (start_line < lines.size()) { + const auto& line = lines[start_line]; + // skip first empty line, if any + if (line.empty()) { + ++start_line; + continue; + } + // The common indentation of all blocks if the number of spaces + // at the beginning of the first non-empty line. + for (auto line_it = line.begin(); line_it != line.end() && *line_it == ' '; ++line_it) { + prefix_length += 1; + } + break; + } + for (const auto& line: lines) { - add_line(line); + if (line.size() < prefix_length) { + // ignore lines made of ' ' characters + if (std::find_if_not(line.begin(), line.end(), [](char c) { return c == ' '; }) != + line.end()) { + throw std::invalid_argument("Indentation mismatch"); + } + } else { + add_line(line.substr(prefix_length)); + } } } -void CodePrinter::add_newline(int n) { - for (int i = 0; i < n; i++) { - *result << std::endl; +void CodePrinter::add_newline(std::size_t n) { + for (std::size_t i{}; i < n; ++i) { + *result << '\n'; } } -void CodePrinter::end_block(int num_newlines) { +void CodePrinter::pop_block() { + pop_block_nl(1); +} + +void CodePrinter::pop_block_nl(std::size_t num_newlines) { indent_level--; add_indent(); - *result << "}"; + *result << '}'; add_newline(num_newlines); } -void CodePrinter::end_block(std::string_view suffix, std::size_t num_newlines) { - end_block(0); +void CodePrinter::pop_block(const std::string_view& suffix, std::size_t num_newlines) { + pop_block_nl(0); *result << suffix; add_newline(num_newlines); } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 051edcc3a6..226562fff1 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -43,16 +43,16 @@ class CodePrinter { private: std::ofstream ofs; std::streambuf* sbuf = nullptr; - std::shared_ptr result; + std::unique_ptr result; size_t indent_level = 0; - const int NUM_SPACES = 4; + const size_t NUM_SPACES = 4; public: CodePrinter() - : result(std::make_shared(std::cout.rdbuf())) {} + : result(std::make_unique(std::cout.rdbuf())) {} CodePrinter(std::ostream& stream) - : result(std::make_shared(stream.rdbuf())) {} + : result(std::make_unique(stream.rdbuf())) {} CodePrinter(const std::string& filename); @@ -64,17 +64,25 @@ class CodePrinter { void add_indent(); /// start a block scope without indentation (i.e. "{\n") - void start_block(); + void push_block(); /// start a block scope with an expression (i.e. "[indent][expression] {\n") - void start_block(std::string&& expression); + void push_block(const std::string& expression); /// end a block and immediately start a new one (i.e. "[indent-1]} [expression] {\n") - void restart_block(std::string const& expression); + void chain_block(std::string const& expression); - void add_text(const std::string&); + template + void add_text(Args&&... args) { + (operator<<(*result, args), ...); + } - void add_line(const std::string&, int num_new_lines = 1); + template + void add_line(Args&&... args) { + add_indent(); + add_text(std::forward(args)...); + add_newline(1); + } /// fmt_line(x, y, z) is just shorthand for add_line(fmt::format(x, y, z)) template @@ -82,10 +90,10 @@ class CodePrinter { add_line(fmt::format(std::forward(args)...)); } - /// fmt_start_block(args...) is just shorthand for start_block(fmt::format(args...)) + /// fmt_push_block(args...) is just shorthand for push_block(fmt::format(args...)) template - void fmt_start_block(Args&&... args) { - start_block(fmt::format(std::forward(args)...)); + void fmt_push_block(Args&&... args) { + push_block(fmt::format(std::forward(args)...)); } /// fmt_text(args...) is just shorthand for add_text(fmt::format(args...)) @@ -96,7 +104,7 @@ class CodePrinter { void add_multi_line(const std::string&); - void add_newline(int n = 1); + void add_newline(std::size_t n = 1); void increase_indent() { indent_level++; @@ -106,11 +114,15 @@ class CodePrinter { indent_level--; } - /// end of current block scope (i.e. end with "}") - void end_block(int num_newlines = 0); + /// end of current block scope (i.e. end with "}") and adds one NL character + void pop_block(); + + /// same as \a pop_block but control the number of NL characters (0 or more) with \a + /// num_newlines parameter + void pop_block_nl(std::size_t num_newlines = 0); /// end a block with `suffix` before the newline(s) (i.e. [indent]}[suffix]\n*num_newlines) - void end_block(std::string_view suffix, std::size_t num_newlines = 1); + void pop_block(const std::string_view& suffix, std::size_t num_newlines = 1); int indent_spaces() { return NUM_SPACES * indent_level; diff --git a/src/nmodl/symtab/symbol.hpp b/src/nmodl/symtab/symbol.hpp index d356abeee6..55d5db63ed 100644 --- a/src/nmodl/symtab/symbol.hpp +++ b/src/nmodl/symtab/symbol.hpp @@ -111,7 +111,7 @@ class Symbol { Symbol() = delete; - Symbol(std::string name) + explicit Symbol(std::string name) : name(std::move(name)) {} Symbol(std::string name, ast::Ast* node) @@ -244,14 +244,14 @@ class Symbol { nodes.push_back(node); } - std::vector get_nodes() const noexcept { + const std::vector& get_nodes() const noexcept { return nodes; } std::vector get_nodes_by_type( std::initializer_list l) const noexcept; - ModToken get_token() const noexcept { + const ModToken& get_token() const noexcept { return token; } diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index cddd6e56ff..8782332ee2 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -199,13 +199,11 @@ std::vector to_string_vector(const Status& obj) { } std::ostream& operator<<(std::ostream& os, const NmodlType& obj) { - os << to_string(obj); - return os; + return os << to_string(obj); } std::ostream& operator<<(std::ostream& os, const Status& obj) { - os << to_string(obj); - return os; + return os << to_string(obj); } } // namespace syminfo diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 37d1f02ec6..638141f082 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -28,7 +28,7 @@ int SymbolTable::Table::counter = 0; // NOLINT(cppcoreguidelines-avoid-non-cons * cases where we were getting re-insertion errors. */ void SymbolTable::Table::insert(const std::shared_ptr& symbol) { - std::string name = symbol->get_name(); + const auto& name = symbol->get_name(); if (lookup(name) != nullptr) { throw std::runtime_error("Trying to re-insert symbol " + name); } @@ -54,11 +54,11 @@ SymbolTable::SymbolTable(const SymbolTable& table) , parent{nullptr} {} bool SymbolTable::is_method_defined(const std::string& name) const { - auto symbol = lookup_in_scope(name); + const auto& symbol = lookup_in_scope(name); if (symbol == nullptr) { return false; } - auto nodes = symbol->get_nodes_by_type( + const auto& nodes = symbol->get_nodes_by_type( {AstNodeType::FUNCTION_BLOCK, AstNodeType::PROCEDURE_BLOCK}); return !nodes.empty(); } @@ -76,7 +76,7 @@ std::string SymbolTable::position() const { } -void SymbolTable::insert_table(const std::string& name, std::shared_ptr table) { +void SymbolTable::insert_table(const std::string& name, const std::shared_ptr& table) { if (children.find(name) != children.end()) { throw std::runtime_error("Trying to re-insert SymbolTable " + name); } @@ -106,8 +106,8 @@ std::vector> SymbolTable::get_variables_with_properties( /// return all symbol which has all "with" properties and none of the "without" properties std::vector> SymbolTable::get_variables(NmodlType with, NmodlType without) const { - auto variables = get_variables_with_properties(with, true); - decltype(variables) result; + const auto& variables = get_variables_with_properties(with, true); + std::decay_t result; for (auto& variable: variables) { if (!variable->has_any_property(without)) { result.push_back(variable); @@ -169,25 +169,24 @@ std::shared_ptr SymbolTable::lookup_in_scope(const std::string& name) co return symbol; } -/// lookup in current sytab as well as all parent symbol tables +/// lookup in current symtab as well as all parent symbol tables std::shared_ptr ModelSymbolTable::lookup(const std::string& name) { if (current_symtab == nullptr) { throw std::logic_error("Lookup with previous symtab = nullptr "); } auto symbol = current_symtab->lookup(name); - if (symbol) { - return symbol; - } - // check into all parent symbol tables - auto parent = current_symtab->get_parent_table(); - while (parent != nullptr) { - symbol = parent->lookup(name); - if (symbol) { - break; + if (!symbol) { + // check into all parent symbol tables + auto parent = current_symtab->get_parent_table(); + while (parent != nullptr) { + symbol = parent->lookup(name); + if (symbol) { + break; + } + parent = parent->get_parent_table(); } - parent = parent->get_parent_table(); } return symbol; } @@ -200,8 +199,8 @@ std::shared_ptr ModelSymbolTable::lookup(const std::string& name) { void ModelSymbolTable::emit_message(const std::shared_ptr& first, const std::shared_ptr& second, bool redefinition) { - auto nodes = first->get_nodes(); - std::string name = first->get_name(); + const auto& nodes = first->get_nodes(); + const auto& name = first->get_name(); auto properties = to_string(second->get_properties()); std::string type = "UNKNOWN"; if (!nodes.empty()) { @@ -247,7 +246,7 @@ std::shared_ptr ModelSymbolTable::update_mode_insert( symbol->set_scope(current_symtab->name()); symbol->mark_created(); - std::string name = symbol->get_name(); + const auto& name = symbol->get_name(); auto search_symbol = lookup(name); /// if no symbol found then safe to insert @@ -271,10 +270,11 @@ std::shared_ptr ModelSymbolTable::update_mode_insert( void ModelSymbolTable::update_order(const std::shared_ptr& present_symbol, const std::shared_ptr& new_symbol) { - auto symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; + const auto& symbol = (present_symbol != nullptr) ? present_symbol : new_symbol; - bool is_parameter = new_symbol->has_any_property(NmodlType::param_assign); - bool is_assigned_definition = new_symbol->has_any_property(NmodlType::assigned_definition); + const bool is_parameter = new_symbol->has_any_property(NmodlType::param_assign); + const bool is_assigned_definition = new_symbol->has_any_property( + NmodlType::assigned_definition); if (symbol->get_definition_order() == -1) { if (is_parameter || is_assigned_definition) { @@ -288,7 +288,7 @@ std::shared_ptr ModelSymbolTable::insert(const std::shared_ptr& throw std::logic_error("Can not insert symbol without entering scope"); } - auto search_symbol = lookup(symbol->get_name()); + const auto& search_symbol = lookup(symbol->get_name()); update_order(search_symbol, symbol); /// handle update mode insertion @@ -396,7 +396,7 @@ SymbolTable* ModelSymbolTable::enter_scope(const std::string& name, } if (node_symtab == nullptr || !update_table) { - auto new_name = get_unique_name(name, node, global); + const auto& new_name = get_unique_name(name, node, global); auto new_symtab = std::make_shared(new_name, node, global); new_symtab->set_parent_table(current_symtab); if (symtab == nullptr) { @@ -421,9 +421,7 @@ void ModelSymbolTable::leave_scope() { if (current_symtab == nullptr) { throw std::logic_error("Trying leave scope without entering"); } - if (current_symtab != nullptr) { - current_symtab = current_symtab->get_parent_table(); - } + current_symtab = current_symtab->get_parent_table(); if (current_symtab == nullptr) { current_symtab = symtab.get(); } @@ -481,13 +479,13 @@ void SymbolTable::Table::print(std::ostream& stream, std::string title, int inde if (symbol->is_array()) { name += "[" + std::to_string(symbol->get_length()) + "]"; } - auto position = symbol->get_token().position(); - auto properties = syminfo::to_string(symbol->get_properties()); - auto status = syminfo::to_string(symbol->get_status()); - auto reads = std::to_string(symbol->get_read_count()); - auto nodes = std::to_string(symbol->get_nodes().size()); + const auto& position = symbol->get_token().position(); + const auto& properties = syminfo::to_string(symbol->get_properties()); + const auto status = syminfo::to_string(symbol->get_status()); + const auto reads = std::to_string(symbol->get_read_count()); + const auto nodes = std::to_string(symbol->get_nodes().size()); std::string value; - auto sym_value = symbol->get_value(); + const auto& sym_value = symbol->get_value(); if (sym_value) { value = std::to_string(*sym_value); } @@ -501,10 +499,10 @@ void SymbolTable::Table::print(std::ostream& stream, std::string title, int inde /// construct title for symbol table std::string SymbolTable::title() const { - auto node_type = node->get_node_type_name(); - auto name = symtab_name + " [" + node_type + " IN " + get_parent_table_name() + "] "; - auto location = "POSITION : " + position(); - auto scope = global ? "GLOBAL" : "LOCAL"; + const auto& node_type = node->get_node_type_name(); + const auto& name = symtab_name + " [" + node_type + " IN " + get_parent_table_name() + "] "; + const auto& location = "POSITION : " + position(); + const auto scope = global ? "GLOBAL" : "LOCAL"; return name + location + " SCOPE : " + scope; } diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index d6316a568c..9b64157f8a 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -84,13 +84,13 @@ class SymbolTable { }; /// name of the block - std::string symtab_name; + const std::string symtab_name; /// table holding all symbols in the current block Table table; /// pointer to ast node for which current symbol table created - ast::Ast* node = nullptr; + const ast::Ast* node = nullptr; /// true if current symbol table is global. blocks like NEURON, /// PARAMETER defines global variables and hence they go into @@ -110,8 +110,8 @@ class SymbolTable { /// \name Ctor & dtor /// \{ - SymbolTable(std::string name, ast::Ast* node, bool global = false) - : symtab_name(name) + SymbolTable(std::string name, const ast::Ast* node, bool global = false) + : symtab_name(std::move(name)) , node(node) , global(global) {} @@ -123,7 +123,7 @@ class SymbolTable { /// \name Getter /// \{ - SymbolTable* get_parent_table() const { + SymbolTable* get_parent_table() const noexcept { return parent; } @@ -161,21 +161,21 @@ class SymbolTable { /// \} /// convert symbol table to string - std::string to_string() { - std::stringstream s; + std::string to_string() const { + std::ostringstream s; print(s, 0); return s.str(); } - std::string name() const { + const std::string& name() const noexcept { return symtab_name; } - bool global_scope() const { + bool global_scope() const noexcept { return global; } - void insert(std::shared_ptr symbol) { + void insert(const std::shared_ptr& symbol) { table.insert(symbol); } @@ -191,7 +191,7 @@ class SymbolTable { * Create a copy of symbol table * \todo Revisit the usage as tokens will be pointing to old nodes */ - SymbolTable* clone() { + SymbolTable* clone() const { return new SymbolTable(*this); } @@ -207,7 +207,7 @@ class SymbolTable { bool under_global_scope(); /// insert new symbol table as one of the children block - void insert_table(const std::string& name, std::shared_ptr table); + void insert_table(const std::string& name, const std::shared_ptr& table); void print(std::ostream& ss, int level) const; diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index c11ab18b20..749ee84c0f 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -90,7 +90,6 @@ enum class text_alignment { left, right, center }; * \return a copy of the given string with every " and \ characters prefixed with an extra \ */ [[nodiscard]] static inline std::string escape_quotes(const std::string& text) { - std::ostringstream oss; std::string result; for (auto c: text) { diff --git a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp index a180db184e..007f25c87c 100644 --- a/test/nmodl/transpiler/unit/symtab/symbol_table.cpp +++ b/test/nmodl/transpiler/unit/symtab/symbol_table.cpp @@ -183,7 +183,7 @@ SCENARIO("Symbol table allows operations like insert, lookup") { THEN("table size increases") { REQUIRE(table->symbol_count() == 1); } - THEN("lookup returns a inserted symbol") { + THEN("lookup returns an inserted symbol") { REQUIRE(table->lookup("alpha") != nullptr); REQUIRE(table->lookup("beta") == nullptr); } @@ -228,6 +228,29 @@ SCENARIO("Symbol table allows operations like insert, lookup") { REQUIRE(next_table->lookup("alpha") == nullptr); REQUIRE(next_table->lookup_in_scope("alpha") != nullptr); } + THEN("children can figure if it is in global scope or not") { + REQUIRE(next_table->global_scope() == table->global_scope()); + } + } + WHEN("pretty-printing a symbol table to a stream") { + std::ostringstream oss; + table->print(oss, 0); + auto text = oss.str(); + THEN("nothing is written when the table is empty") { + REQUIRE(text.empty()); + } + table->insert(symbol); + table->print(oss, 0); + text = oss.str(); + THEN("the symbol present in the table can be found in the written string") { + REQUIRE(text.find(symbol->get_name()) != std::string::npos); + } + } + WHEN("creating a clone of symbol table") { + const std::unique_ptr clone(table->clone()); + THEN("clone has the same name") { + REQUIRE(clone->name() == table->name()); + } } WHEN("query for symbol with and without properties") { auto symbol1 = std::make_shared("alpha"); @@ -313,7 +336,7 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { Catch::Matchers::ContainsSubstring("Can not insert")); } } - WHEN("enter scope multipel times") { + WHEN("enter scope multiple times") { auto program1 = std::make_shared(); auto program2 = std::make_shared(); mod_symtab.enter_scope("scope1", program1.get(), false, old_symtab); @@ -333,7 +356,7 @@ SCENARIO("Global symbol table (ModelSymbol) allows scope based operations") { REQUIRE(symbol->get_properties() == properties); } } - WHEN("added same symbol with exisiting property") { + WHEN("added same symbol with existing property") { mod_symtab.enter_scope("scope", program.get(), true, old_symtab); mod_symtab.insert(symbol1); mod_symtab.insert(symbol2); From bf209238f3b91816a3b56ab69d58e4eb8ef54dd1 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Thu, 21 Sep 2023 17:15:39 +0200 Subject: [PATCH 536/871] Simplify CodegenCppVisitor (BlueBrain/nmodl#1076) * Remove wrapper_printer and target_printer since we only print a single C++ file now for all the backends * Rename C to C++ in multiple places NMODL Repo SHA: BlueBrain/nmodl@f180a2d45aabc0d8630b2a7fc8d90c1d8fd07e26 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 4 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 12 ++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 69 +++++++++-------------- src/nmodl/codegen/codegen_naming.hpp | 2 +- 5 files changed, 36 insertions(+), 53 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 647efd6a27..03a7589ff7 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -77,7 +77,7 @@ void CodegenAccVisitor::print_backend_includes() { std::string CodegenAccVisitor::backend_name() const { - return "C-OpenAcc (api-compatibility)"; + return "C++-OpenAcc (api-compatibility)"; } diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 1bf9e9a5bc..1a3f60503f 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -25,7 +25,7 @@ namespace codegen { /** * \class CodegenAccVisitor - * \brief %Visitor for printing C code with OpenACC backend + * \brief %Visitor for printing C++ code with OpenACC backend */ class CodegenAccVisitor: public CodegenCppVisitor { protected: @@ -33,7 +33,7 @@ class CodegenAccVisitor: public CodegenCppVisitor { std::string backend_name() const override; - /// common includes : standard c/c++, coreneuron and backend specific + /// common includes : standard c++, coreneuron and backend specific void print_backend_includes() override; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 9337d4c2c5..2e22ddf5de 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -691,7 +691,7 @@ bool CodegenCppVisitor::ion_variable_struct_required() const { /** * \details This can be override in the backend. For example, parameters can be constant * except in INITIAL block where they are set to 0. As initial block is/can be - * executed on c/cpu backend, gpu backend can mark the parameter as constant. + * executed on c++/cpu backend, gpu backend can mark the parameter as constant. */ bool CodegenCppVisitor::is_constant_variable(const std::string& name) const { auto symbol = program_symtab->lookup_in_scope(name); @@ -1143,12 +1143,12 @@ void CodegenCppVisitor::print_global_method_annotation() { void CodegenCppVisitor::print_backend_namespace_start() { - // no separate namespace for C (cpu) backend + // no separate namespace for C++ (cpu) backend } void CodegenCppVisitor::print_backend_namespace_stop() { - // no separate namespace for C (cpu) backend + // no separate namespace for C++ (cpu) backend } @@ -1158,7 +1158,7 @@ void CodegenCppVisitor::print_backend_includes() { std::string CodegenCppVisitor::backend_name() const { - return "C (api-compatibility)"; + return "C++ (api-compatibility)"; } @@ -1893,7 +1893,7 @@ void CodegenCppVisitor::print_eigen_linear_solver(const std::string& float_type, )CODE"); } else { // In Eigen the default storage order is ColMajor. - // Crout's implementation requires matrices stored in RowMajor order (C-style arrays). + // Crout's implementation requires matrices stored in RowMajor order (C++-style arrays). // Therefore, the transposeInPlace is critical such that the data() method to give the rows // instead of the columns. printer->add_line("if (!nmodl_eigen_jm.IsRowMajor) nmodl_eigen_jm.transposeInPlace();"); @@ -2479,7 +2479,7 @@ void CodegenCppVisitor::print_coreneuron_includes() { /** * \details Variables required for type of ion, type of point process etc. are - * of static int type. For any backend type (C,C++), it's ok to have + * of static int type. For the C++ backend type, it's ok to have * these variables as file scoped static variables. * * Initial values of state variables (h0) are also defined as static diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index a0d54a29a6..1e25a6ca84 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -169,7 +169,7 @@ using printer::CodePrinter; /** * \class CodegenCppVisitor - * \brief %Visitor for printing C code compatible with legacy api of CoreNEURON + * \brief %Visitor for printing C++ code compatible with legacy api of CoreNEURON * * \todo * - Handle define statement (i.e. macros) @@ -196,17 +196,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Code printer object for target (C++) */ - std::shared_ptr target_printer; - - /** - * Code printer object for wrappers - */ - std::shared_ptr wrapper_printer; - - /** - * Pointer to active code printer - */ - std::shared_ptr printer; + std::unique_ptr printer; /** * Name of mod file (without .mod suffix) @@ -568,7 +558,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * * This function typically returns the accessor expression in backend code for the given symbol. * Since the model variables are stored in data arrays and accessed by offset, this function - * will return the C string representing the array access at the correct offset + * will return the C++ string representing the array access at the correct offset * * \param symbol The symbol of a variable for which we want to obtain its name * \param use_instance Should the variable be accessed via instance or data array @@ -583,7 +573,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * * This function typically returns the accessor expression in backend code for the given symbol. * Since the model variables are stored in data arrays and accessed by offset, this function - * will return the C string representing the array access at the correct offset + * will return the C++ string representing the array access at the correct offset * * \param symbol The symbol of a variable for which we want to obtain its name * \param name The name of the index variable @@ -601,7 +591,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param symbol The symbol of a variable for which we want to obtain its name * \param use_instance Should the variable be accessed via the (host-only) * global variable or the instance-specific copy (also available on GPU). - * \return The C string representing the access to the global variable + * \return The C++ string representing the access to the global variable */ std::string global_variable_name(const SymbolType& symbol, bool use_instance = true) const; @@ -611,8 +601,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * * \param name Variable name that is being printed * \param use_instance Should the variable be accessed via instance or data array - * \return The C string representing the access to the variable in the neuron thread - * structure + * \return The C++ string representing the access to the variable in the neuron + * thread structure */ std::string get_variable_name(const std::string& name, bool use_instance = true) const; @@ -621,7 +611,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Determine the variable name for the "current" used in breakpoint block taking into account * intermediate code transformations. * \param current The variable name for the current used in the model - * \return The name for the current to be printed in C + * \return The name for the current to be printed in C++ */ std::string breakpoint_current(std::string current) const; @@ -843,7 +833,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * The used global type qualifier * - * For C code generation this is empty + * For C++ code generation this is empty * \return "" * * \return "uniform " @@ -853,7 +843,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Instantiate global var instance * - * For C code generation this is empty + * For C++ code generation this is empty * \return "" */ virtual void print_global_var_struct_decl(); @@ -878,7 +868,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Prints the start of namespace for the backend-specific code * - * For the C backend no additional namespace is required + * For the C++ backend no additional namespace is required */ virtual void print_backend_namespace_start(); @@ -886,7 +876,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Prints the end of namespace for the backend-specific code * - * For the C backend no additional namespace is required + * For the C++ backend no additional namespace is required */ virtual void print_backend_namespace_stop(); @@ -937,7 +927,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** - * Print backend specific includes (none needed for C backend) + * Print backend specific includes (none needed for C++ backend) */ virtual void print_backend_includes(); @@ -1142,7 +1132,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print the pragma annotation to update global variables from host to the device * - * \note This is not used for the C backend + * \note This is not used for the C++ backend */ virtual void print_global_variable_device_update_annotation(); @@ -1157,7 +1147,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print the backend specific device method annotation * - * \note This is not used for the C backend + * \note This is not used for the C++ backend */ virtual void print_device_method_annotation(); @@ -1165,7 +1155,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print backend specific global method annotation * - * \note This is not used for the C backend + * \note This is not used for the C++ backend */ virtual void print_global_method_annotation(); @@ -1588,10 +1578,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { const bool optimize_ionvar_copies, const std::string& extension, const std::string& wrapper_ext) - : target_printer(std::make_shared(output_dir + "/" + mod_filename + extension)) - , wrapper_printer( - std::make_shared(output_dir + "/" + mod_filename + wrapper_ext)) - , printer(target_printer) + : printer(std::make_unique(output_dir + "/" + mod_filename + extension)) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1604,9 +1591,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { const bool optimize_ionvar_copies, const std::string& /* extension */, const std::string& /* wrapper_ext */) - : target_printer(std::make_shared(stream)) - , wrapper_printer(std::make_shared(stream)) - , printer(target_printer) + : printer(std::make_unique(stream)) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1614,18 +1599,18 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { public: /** - * \brief Constructs the C code generator visitor + * \brief Constructs the C++ code generator visitor * - * This constructor instantiates an NMODL C code generator and allows writing generated code + * This constructor instantiates an NMODL C++ code generator and allows writing generated code * directly to a file in \c [output_dir]/[mod_filename].[extension]. * * \note No code generation is performed at this stage. Since the code * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C code corresponding to the AST. + * visit_program in order to generate the C++ code corresponding to the AST. * * \param mod_filename The name of the model for which code should be generated. * It is used for constructing an output filename. - * \param output_dir The directory where target C file should be generated. + * \param output_dir The directory where target C++ file should be generated. * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. * \param extension The file extension to use. This defaults to \c .cpp . @@ -1635,8 +1620,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::string float_type, const bool optimize_ionvar_copies, const std::string& extension = ".cpp") - : target_printer(std::make_shared(output_dir + "/" + mod_filename + extension)) - , printer(target_printer) + : printer(std::make_unique(output_dir + "/" + mod_filename + extension)) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -1644,12 +1628,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * \copybrief nmodl::codegen::CodegenCppVisitor * - * This constructor instantiates an NMODL C code generator and allows writing generated code + * This constructor instantiates an NMODL C++ code generator and allows writing generated code * into an output stream. * * \note No code generation is performed at this stage. Since the code * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C code corresponding to the AST. + * visit_program in order to generate the C++ code corresponding to the AST. * * \param mod_filename The name of the model for which code should be generated. * It is used for constructing an output filename. @@ -1661,8 +1645,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::ostream& stream, std::string float_type, const bool optimize_ionvar_copies) - : target_printer(std::make_shared(stream)) - , printer(target_printer) + : printer(std::make_unique(stream)) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 3ef38a1c4e..3af8a49e88 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -152,7 +152,7 @@ static constexpr char NRN_STATE_METHOD[] = "nrn_state"; /// nrn_cur method in generated code static constexpr char NRN_CUR_METHOD[] = "nrn_cur"; -/// nrn_watch_check method in generated c file +/// nrn_watch_check method in generated c++ file static constexpr char NRN_WATCH_CHECK_METHOD[] = "nrn_watch_check"; /// verbatim name of the variable for nrn thread arguments From 5c82cec37a59a91a1548fca927b49f9ddf855749 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 25 Sep 2023 09:24:30 -0400 Subject: [PATCH 537/871] Small improvements in CodegenCppVisitor constructors (BlueBrain/nmodl#1079) * Remove protected constructor for CodegenCppVisitor and simplify the public constructors * Renaming output log for codegen referring to C NMODL Repo SHA: BlueBrain/nmodl@bb20e8b94c24eb1a06155c7aa819dbeb122b4060 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 6 ---- src/nmodl/codegen/codegen_cpp_visitor.hpp | 39 ++--------------------- src/nmodl/main.cpp | 2 +- 3 files changed, 3 insertions(+), 44 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 2e22ddf5de..a83aad5ce7 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -4659,11 +4659,6 @@ void CodegenCppVisitor::print_codegen_routines() { } -void CodegenCppVisitor::print_wrapper_routines() { - // nothing to do -} - - void CodegenCppVisitor::set_codegen_global_variables(const std::vector& global_vars) { codegen_global_variables = global_vars; } @@ -4691,7 +4686,6 @@ void CodegenCppVisitor::setup(const Program& node) { void CodegenCppVisitor::visit_program(const Program& node) { setup(node); print_codegen_routines(); - print_wrapper_routines(); } } // namespace codegen diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 1e25a6ca84..ee0344058c 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1564,39 +1564,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_codegen_routines(); - /** - * Print entry point to code generation for wrappers - */ - virtual void print_wrapper_routines(); - - - /// This constructor is private, see the public section below to find how to create an instance - /// of this class. - CodegenCppVisitor(std::string mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies, - const std::string& extension, - const std::string& wrapper_ext) - : printer(std::make_unique(output_dir + "/" + mod_filename + extension)) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - /// This constructor is private, see the public section below to find how to create an instance - /// of this class. - CodegenCppVisitor(std::string mod_filename, - std::ostream& stream, - std::string float_type, - const bool optimize_ionvar_copies, - const std::string& /* extension */, - const std::string& /* wrapper_ext */) - : printer(std::make_unique(stream)) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - public: /** * \brief Constructs the C++ code generator visitor @@ -1613,14 +1580,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param output_dir The directory where target C++ file should be generated. * \param float_type The float type to use in the generated code. The string will be used * as-is in the target code. This defaults to \c double. - * \param extension The file extension to use. This defaults to \c .cpp . */ CodegenCppVisitor(std::string mod_filename, const std::string& output_dir, std::string float_type, - const bool optimize_ionvar_copies, - const std::string& extension = ".cpp") - : printer(std::make_unique(output_dir + "/" + mod_filename + extension)) + const bool optimize_ionvar_copies) + : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp")) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 2f31f99f56..4afd1268c4 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -529,7 +529,7 @@ int main(int argc, const char* argv[]) { } else if (c_backend) { - logger->info("Running C backend code generator"); + logger->info("Running C++ backend code generator"); CodegenCppVisitor visitor(modfile, output_dir, data_type, From 91fafc5b3259cdac116c6b35c6b6ce537ad813f6 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 27 Sep 2023 12:32:15 +0200 Subject: [PATCH 538/871] Remove legacy units support (BlueBrain/nmodl#1082) NMODL Repo SHA: BlueBrain/nmodl@38b48dfc1b0e6718895de7ead6df85ff2b3ff19a --- cmake/nmodl/CMakeLists.txt | 18 +--------------- share/{nrnunits.lib.in => nmodl/nrnunits.lib} | 21 +++++++------------ src/nmodl/visitors/units_visitor.cpp | 6 ------ 3 files changed, 8 insertions(+), 37 deletions(-) rename share/{nrnunits.lib.in => nmodl/nrnunits.lib} (95%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 20573097ad..9a98839831 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -18,11 +18,7 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # Build options for NMODL # ============================================================================= option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) -option(NMODL_ENABLE_LEGACY_UNITS "Use original faraday, R, etc. instead of 2019 nist constants" OFF) option(NMODL_ENABLE_TESTS "Enable build of tests" ON) -if(NMODL_ENABLE_LEGACY_UNITS) - add_definitions(-DUSE_LEGACY_UNITS) -endif() set(NMODL_EXTRA_CXX_FLAGS "" CACHE STRING "Add extra compile flags for NMODL sources") @@ -220,21 +216,10 @@ include(${PROJECT_SOURCE_DIR}/src/language/code_generator.cmake) add_subdirectory(src) -# ============================================================================= -# Prepare units database file from nrnunits.lib.in -# ============================================================================= -if(NMODL_ENABLE_LEGACY_UNITS) - set(LegacyY "") - set(LegacyN "/") -else() - set(LegacyY "/") - set(LegacyN "") -endif() -configure_file(share/nrnunits.lib.in ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib @ONLY) - # ============================================================================= # Install unit database to share # ============================================================================= +configure_file(share/nrnunits.lib ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib COPYONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share/nmodl) @@ -268,7 +253,6 @@ message(STATUS "--------------------+------------------------------------------- message(STATUS "CXX COMPILER | ${CMAKE_CXX_COMPILER}") message(STATUS "COMPILE FLAGS | ${COMPILER_FLAGS}") message(STATUS "Build Type | ${CMAKE_BUILD_TYPE}") -message(STATUS "Legacy Units | ${NMODL_ENABLE_LEGACY_UNITS}") message(STATUS "Python Bindings | ${NMODL_ENABLE_PYTHON_BINDINGS}") message(STATUS "Flex | ${FLEX_EXECUTABLE}") message(STATUS "Bison | ${BISON_EXECUTABLE}") diff --git a/share/nrnunits.lib.in b/share/nmodl/nrnunits.lib similarity index 95% rename from share/nrnunits.lib.in rename to share/nmodl/nrnunits.lib index 8af235ee58..8fb81e7f56 100755 --- a/share/nrnunits.lib.in +++ b/share/nmodl/nrnunits.lib @@ -68,15 +68,13 @@ pi 3.14159265358979323846 c 2.99792458+8 m/sec fuzz g 9.80665 m/sec2 au 1.49597871+11 m fuzz -@LegacyY@mole 6.022169+23 fuzz -@LegacyN@mole 6.02214076+23 fuzz +mole 6.02214076+23 fuzz mol 1 / mol is explicitly defined as a constant / with value 1 to avoid "undefined unit" / error with mod files that don't define / it themselves -@LegacyY@e 1.6021917-19 coul fuzz -@LegacyN@e 1.602176634-19 coul fuzz +e 1.602176634-19 coul fuzz energy c2 force g mercury 1.33322+5 kg/m2-sec2 @@ -399,8 +397,7 @@ ev e-volt / faraday 9.652000+04 coul / faraday from host: physics.nist.gov / path: /PhysRefData/fundconst/html/keywords.html -@LegacyY@faraday 9.6485309+4 coul -@LegacyN@faraday e-mole +faraday e-mole fathom 6 ft fermi 1-15 m fifth 4|5 qt @@ -436,8 +433,7 @@ hyl gm force sec2/m hz /sec imaginarycubicfoot 1.4 ft3 jeroboam 4|5 gal -@LegacyY@boltzmann 1.38064852-23 joule/K -@LegacyN@boltzmann 1.380649-23 joule/K +boltzmann 1.380649-23 joule/K k boltzmann karat 1|24 kcal kilocal @@ -506,8 +502,7 @@ quarter 9 in quartersection 1|4 mi2 quintal 100 kg quire 25 -@LegacyY@gasconstant 8.3144598 joule/K -@LegacyN@gasconstant k-mole +gasconstant k-mole R gasconstant rad 100 erg/gm ream 500 @@ -581,10 +576,8 @@ tex .001 gram / m englishell 45 inch scottishell 37.2 inch flemishell 27 inch -@LegacyY@planck 6.626-34 joule-sec -@LegacyN@planck 6.62607015-34 joule-sec -@LegacyY@hbar 1.055-34 joule-sec -@LegacyN@hbar planck/two-pi +planck 6.62607015-34 joule-sec +hbar planck/two-pi electronmass 9.1095-31 kg protonmass 1.6726-27 kg neutronmass 1.6606-27 kg diff --git a/src/nmodl/visitors/units_visitor.cpp b/src/nmodl/visitors/units_visitor.cpp index 0da2a1c6cc..2ad612d0ea 100644 --- a/src/nmodl/visitors/units_visitor.cpp +++ b/src/nmodl/visitors/units_visitor.cpp @@ -128,13 +128,7 @@ void UnitsVisitor::visit_factor_def(ast::FactorDef& node) { auto node_unit_name = node.get_node_name(); auto unit1_factor = units_driver.table->get_unit(node_unit_name + "_unit1")->get_factor(); auto unit2_factor = units_driver.table->get_unit(node_unit_name + "_unit2")->get_factor(); - -#ifdef USE_LEGACY_UNITS - auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:g}"); -#else auto unit_factor = stringutils::to_string(unit1_factor / unit2_factor, "{:a}"); -#endif - auto double_value_ptr = std::make_shared(ast::Double(unit_factor)); node.set_value(std::move(double_value_ptr)); } From 13b30275f250b65af0f4676788ab6a5cae465c6d Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 27 Sep 2023 14:02:19 -0400 Subject: [PATCH 539/871] Convert Markdown files to reStructuredText (BlueBrain/nmodl#1084) * Add warning regarding NMODL python bindings when building with CMake NMODL Repo SHA: BlueBrain/nmodl@10bf2d21a8402143863aa081a5a6257e8678ba82 --- CONTRIBUTING.md | 168 -------- INSTALL.md | 185 --------- README.md | 289 ------------- docs/nmodl/transpiler/Doxyfile.in | 6 +- docs/nmodl/transpiler/INSTALL.rst | 227 +++++++++++ docs/nmodl/transpiler/contributing.rst | 2 +- docs/nmodl/transpiler/install.rst | 2 +- .../nmodl/transpiler/CONTRIBUTING.rst | 217 ++++++++++ docs/nmodl/transpiler/notebooks/README.md | 16 - docs/nmodl/transpiler/notebooks/README.rst | 41 ++ docs/nmodl/transpiler/readme.rst | 2 +- setup.py | 4 +- src/nmodl/nmodl/README.rst | 378 ++++++++++++++++++ 13 files changed, 871 insertions(+), 666 deletions(-) delete mode 100644 CONTRIBUTING.md delete mode 100644 INSTALL.md delete mode 100644 README.md create mode 100644 docs/nmodl/transpiler/INSTALL.rst create mode 100644 docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst delete mode 100644 docs/nmodl/transpiler/notebooks/README.md create mode 100644 docs/nmodl/transpiler/notebooks/README.rst create mode 100644 src/nmodl/nmodl/README.rst diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index f31cebeb6a..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,168 +0,0 @@ -# Contributing to the NMODL Framework - -We would love for you to contribute to the NMODL Framework and help make it better than it is today. As a -contributor, here are the guidelines we would like you to follow: - - [Question or Problem?](#question) - - [Issues and Bugs](#issue) - - [Feature Requests](#feature) - - [Submission Guidelines](#submit) - - [Development Conventions](#devconv) - -## Got a Question? - -Please do not hesitate to raise an issue on [github project page][github]. - -## Found a Bug? - -If you find a bug in the source code, you can help us by [submitting an issue](#submit-issue) to our [GitHub Repository][github]. Even better, you can [submit a Pull Request](#submit-pr) with a fix. - -## Missing a Feature? - -You can *request* a new feature by [submitting an issue](#submit-issue) to our GitHub Repository. If you would like to *implement* a new feature, please submit an issue with a proposal for your work first, to be sure that we can use it. - -Please consider what kind of change it is: - -* For a **Major Feature**, first open an issue and outline your proposal so that it can be -discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, -and help you to craft the change so that it is successfully accepted into the project. -* **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). - -## Submission Guidelines - -### Submitting an Issue - -Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the -discussion might inform you of workarounds readily available. - -We want to fix all the issues as soon as possible, but before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will need as much information as possible, and preferably a sample MOD file or Python example. - -### Submitting a Pull Request (PR) - -When you wish to contribute to the code base, please consider the following guidelines: - -* Make a [fork](https://guides.github.com/activities/forking/) of this repository. -* Make your changes in your fork, in a new git branch: - - ```shell - git checkout -b my-fix-branch master - ``` -* Create your patch, **including appropriate test cases**. -* Enable `NMODL_TEST_FORMATTING` CMake variable - to ensure that your change follows the coding conventions of this project when running the tests. - The formatting utility can also be used directly: - * to format CMake and C++ files: `cmake/hpc-coding-conventions/bin/format` - * to format only the C++ files: `cmake/hpc-coding-conventions/bin/format --lang c++` - * to format a subset of files or directories: `cmake/hpc-coding-conventions/bin/format src/codegen/ src/main.cpp` - * to check the formatting of CMake files: `cmake/hpc-coding-conventions/bin/format --dry-run --lang cmake` -* Run the full test suite, and ensure that all tests pass. -* Commit your changes using a descriptive commit message. - - ```shell - git commit -a - ``` -* Push your branch to GitHub: - - ```shell - git push origin my-fix-branch - ``` -* In GitHub, send a Pull Request to the `master` branch of the upstream repository of the relevant component. -* If we suggest changes then: - * Make the required updates. - * Re-run the test suites to ensure tests are still passing. - * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): - - ```shell - git rebase master -i - git push -f - ``` - -That’s it! Thank you for your contribution! - -#### After your pull request is merged - -After your pull request is merged, you can safely delete your branch and pull the changes from the main (upstream) -repository: - -* Delete the remote branch on GitHub either through the GitHub web UI or your local shell as follows: - - ```shell - git push origin --delete my-fix-branch - ``` -* Check out the master branch: - - ```shell - git checkout master -f - ``` -* Delete the local branch: - - ```shell - git branch -D my-fix-branch - ``` -* Update your master with the latest upstream version: - - ```shell - git pull --ff upstream master - ``` - -[github]: https://github.com/BlueBrain/nmodl - -## Development Conventions - -If you are developing NMODL, make sure to enable both `NMODL_FORMATTING` and `NMODL_PRECOMMIT` -CMake variables to ensure that your contributions follow the coding conventions of this project: - -```cmake -cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON -``` - -The first variable provides the following additional targets to format -C, C++, and CMake files: - -``` -make clang-format cmake-format -``` - -The second option activates Git hooks that will discard commits that -do not comply with coding conventions of this project. These 2 CMake variables require additional utilities: - -* [ClangFormat 7](https://releases.llvm.org/7.0.0/tools/clang/docs/ClangFormat.html) -* [cmake-format](https://github.com/cheshirekow/cmake_format) -* [pre-commit](https://pre-commit.com/) - -clang-format can be installed on Linux thanks -to [LLVM apt page](http://apt.llvm.org/). On MacOS, you can simply install llvm with brew: -`brew install llvm`. -_cmake-format_ and _pre-commit_ utilities can be installed with *pip*. - -### Validate the Python package - -You may run the Python test-suites if your contribution has an impact -on the Python API: - -1. setup a sandbox environment with either _virtualenv_, - _pyenv_, or _pipenv_. For instance with _virtualenv_: - `python -m venv .venv && source .venv/bin/activate` -1. build the Python package with the command: `python setup.py build` -1. install _pytest_ Python package: `pip install pytest` -1. execute the unit-tests: `pytest` - -### Memory Leaks and clang-tidy - -If you want to test for memory leaks, do : - -``` -valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer -``` - -Or using CTest as: - -``` -ctest -T memcheck -``` - -If you want to enable `clang-tidy` checks with CMake, make sure to have `CMake >= 3.15` and use following cmake option: - -``` -cmake .. -DENABLE_CLANG_TIDY=ON -``` - diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100644 index b15bbe35da..0000000000 --- a/INSTALL.md +++ /dev/null @@ -1,185 +0,0 @@ -# Installing the NMODL Framework - -## Getting Started - -These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. - -## Cloning Source - -The NMODL Framework is maintained on github. The best way to get the sources is to simply clone the repository. - -**Note**: This project uses git submodules which must be cloned along with the repository itself: - -```sh -git clone --recursive https://github.com/BlueBrain/nmodl.git -cd nmodl -``` - -## Prerequisites - -To build the project from source, a modern C++ compiler with C++14 support is necessary. Make sure you have following packages available: - -- flex (>=2.6) -- bison (>=3.0) -- CMake (>=3.15) -- Python (>=3.8) -- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap - -### On OS X - -Typically the versions of bison and flex provided by the system are outdated and not compatible with our requirements. -To get recent version of all dependencies we recommend using [homebrew](https://brew.sh/): - -```sh -brew install flex bison cmake python3 -``` - -The necessary Python packages can then easily be added using the pip3 command. - -```sh -pip3 install --user -r requirements.txt -``` - -Make sure to have latest flex/bison in $PATH : - -```sh -export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:/usr/local/bin/:$PATH -``` - -On Apple M1, corresponding brew paths are under `/opt/homebrew/opt/`: - -```sh -export PATH=/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin:$PATH -``` - -### On Ubuntu - -On Ubuntu (>=18.04) flex/bison versions are recent enough and are installed along with the system toolchain: - -```sh -apt-get install flex bison gcc python3 python3-pip -``` - -The Python dependencies are installed using: - -```sh -pip3 install --user -r requirements.txt -``` - -## Build Project - -### Using CMake - -Once all dependencies are in place, build project as: - -```sh -mkdir -p nmodl/build -cd nmodl/build -cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -make -j && make install -``` - -And set PYTHONPATH as: - -```sh -export PYTHONPATH=$HOME/nmodl/lib:$PYTHONPATH -``` - -#### Flex / Bison Paths - -If flex / bison are not in your default $PATH, you can provide the path to cmake as: - -```sh -cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ - -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ - -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -``` - -### Using Python setuptools - -If you are mainly interested in the NMODL Framework parsing and analysis tools and wish to use them from Python, we -recommend building and installing using Python. - -```sh -pip3 install --user . -``` - -This should build the NMODL framework and install it into your pip user `site-packages` folder such that it becomes -available as a Python module. - -### When building without linking against libpython - -NMODL uses an embedded python to symbolically evaluate differential equations. For this to work we would usually link -against libpython, which is automatically taken care of by pybind11. In some cases, for instance when building a -python wheel, we cannot link against libpython, because we cannot know where it will be at runtime. Instead, we load -the python library (along with a wrapper library that manages calls to embedded python) at runtime. -To disable linking against python and enabling dynamic loading of libpython at runtime we need to configure the build -with the cmake option `-DLINK_AGAINST_PYTHON=False`. - -In order for NMODL binaries to know where to find libpython and our own libpywrapper two environment variables need to -be present: - -* `NMODL_PYLIB`: This variable should point to the libpython shared-object (or dylib) file. On macos this could be -for example: -````sh -export NMODL_PYLIB=/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/Python -```` - -**Note**: In order for all unit tests to function correctly when building without linking against libpython we must -set `NMODL_PYLIB` before running cmake! - - -## Testing the Installed Module - -If you have installed the NMODL Framework using CMake, you can now run tests from the build directory as: - -```bash -$ make test -Running tests... -Test project /Users/kumbhar/workarena/repos/bbp/incubator/nocmodl/cmake-build-debug - Start 1: testmodtoken/NMODL Lexer returning valid ModToken object - 1/60 Test #1: testmodtoken/NMODL Lexer returning valid ModToken object ................................... Passed 0.01 sec - Start 2: testlexer/NMODL Lexer returning valid token types - 2/60 Test #2: testlexer/NMODL Lexer returning valid token types .......................................... Passed 0.00 sec - Start 3: testparser/Scenario: NMODL can define macros using DEFINE keyword - 3/60 Test #3: testparser/Scenario: NMODL can define macros using DEFINE keyword .......................... Passed 0.01 sec - Start 4: testparser/Scenario: Macros can be used anywhere in the mod file - 4/60 Test #4: testparser/Scenario: Macros can be used anywhere in the mod file ........................... Passed 0.01 sec - Start 5: testparser/Scenario: NMODL parser accepts empty unit specification - 5/60 Test #5: testparser/Scenario: NMODL parser accepts empty unit specification ......................... Passed 0.01 sec - Start 6: testparser/Scenario: NMODL parser running number of valid NMODL constructs - 6/60 Test #6: testparser/Scenario: NMODL parser running number of valid NMODL constructs ................. Passed 0.04 sec - Start 7: testparser/Scenario: NMODL parser running number of invalid NMODL constructs - 7/60 Test #7: testparser/Scenario: NMODL parser running number of invalid NMODL constructs ............... Passed 0.01 sec - Start 8: testparser/Scenario: Legacy differential equation solver from NEURON solve number of ODE - 8/60 Test #8: testparser/Scenario: Legacy differential equation solver from NEURON solve number of ODE ... Passed 0.00 sec - ... -``` - -To test the NMODL Framework python bindings, you can try a minimal example in your Python 3 interpeter as follows: - -```python ->>> import nmodl.dsl as nmodl ->>> driver = nmodl.NmodlDriver() ->>> modast = driver.parse_string("NEURON { SUFFIX hh }") ->>> print ('%s' % modast) -{"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]},{"Name":[{"String":[{"name":"hh"}]}]}]}]}]}]} ->>> print (nmodl.to_nmodl(modast)) -NEURON { - SUFFIX hh -} -``` - -NMODL is now setup correctly! - - -## Generating Documentation - -In order to build the documentation you must have additionally `pandoc` installed. Use your -system's package manager to do this (e.g. `sudo apt-get install pandoc`). - -You can build the entire documentation simply by using sphinx from `setup.py`: - -```sh -python3 setup.py build_ext --inplace docs -G "Unix Makefiles" -``` diff --git a/README.md b/README.md deleted file mode 100644 index 2f6b9d6d1f..0000000000 --- a/README.md +++ /dev/null @@ -1,289 +0,0 @@ -## The NMODL Framework -![github workflow](https://github.com/BlueBrain/nmodl/actions/workflows/nmodl-ci.yml/badge.svg?branch=master) [![Build Status](https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master)](https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master) [![codecov](https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB)](https://codecov.io/gh/BlueBrain/nmodl) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4467/badge)](https://bestpractices.coreinfrastructure.org/projects/4467) - -The NMODL Framework is a code generation engine for **N**EURON **MOD**eling **L**anguage ([NMODL](https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/mechanisms/nmodl.html)). It is designed with modern compiler and code generation techniques to: - -* Provide **modular tools** for parsing, analysing and transforming NMODL -* Provide **easy to use**, high level Python API -* Generate **optimised code** for modern compute architectures including CPUs, GPUs -* **Flexibility** to implement new simulator backends -* Support for **full** NMODL specification - -### About NMODL - -Simulators like [NEURON](https://www.neuron.yale.edu/neuron/) use NMODL as a domain specific language (DSL) to describe a wide range of membrane and intracellular submodels. Here is an example of exponential synapse specified in NMODL: - -```python -NEURON { - POINT_PROCESS ExpSyn - RANGE tau, e, i - NONSPECIFIC_CURRENT i -} -UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (uS) = (microsiemens) -} -PARAMETER { - tau = 0.1 (ms) <1e-9,1e9> - e = 0 (mV) -} -ASSIGNED { - v (mV) - i (nA) -} -STATE { - g (uS) -} -INITIAL { - g = 0 -} -BREAKPOINT { - SOLVE state METHOD cnexp - i = g*(v - e) -} -DERIVATIVE state { - g' = -g/tau -} -NET_RECEIVE(weight (uS)) { - g = g + weight -} -``` - -### Installation - -See [INSTALL.md](https://github.com/BlueBrain/nmodl/blob/master/INSTALL.md) for detailed instructions to build the NMODL from source. - -### Try NMODL with Docker - -To quickly test the NMODL Framework's analysis capabilities we provide a -[docker](https://www.docker.com) image, which includes the NMODL Framework python library and a -fully functional Jupyter notebook environment. After installing [docker](https://docs.docker.com/compose/install/) and [docker-compose](https://docs.docker.com/compose/install/) you can pull and run the NMODL image from your terminal. - -To try Python interface directly from CLI, you can run docker image as: - -``` -docker run -it --entrypoint=/bin/sh bluebrain/nmodl -``` - -And try NMODL Python API discussed later in this README as: - -``` -$ python3 -Python 3.6.8 (default, Apr 8 2019, 18:17:52) ->>> from nmodl import dsl ->>> import os ->>> examples = dsl.list_examples() ->>> nmodl_string = dsl.load_example(examples[-1]) -... -``` - -To try Jupyter notebooks you can download docker compose file and run it as: - -```sh -wget "https://raw.githubusercontent.com/BlueBrain/nmodl/master/docker/docker-compose.yml" -DUID=$(id -u) DGID=$(id -g) HOSTNAME=$(hostname) docker-compose up -``` - -If all goes well you should see at the end status messages similar to these: - -``` -[I 09:49:53.923 NotebookApp] The Jupyter Notebook is running at: -[I 09:49:53.923 NotebookApp] http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 -[I 09:49:53.923 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). - To access the notebook, open this file in a browser: - file:///root/.local/share/jupyter/runtime/nbserver-1-open.html - Or copy and paste one of these URLs: - http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 -``` - -Based on the example above you should then open your browser and navigate to the URL `http://127.0.0.1:8888/?token=a7902983bad430a11935`. - -You can open and run all example notebooks provided in the `examples` folder. You can also create -new notebooks in `my_notebooks`, which will be stored in a subfolder `notebooks` at your current -working directory. - -### Using the Python API - -Once the NMODL Framework is installed, you can use the Python parsing API to load NMOD file as: - -```python -from nmodl import dsl - -examples = dsl.list_examples() -nmodl_string = dsl.load_example(examples[-1]) -driver = dsl.NmodlDriver() -modast = driver.parse_string(nmodl_string) -``` - -The `parse_file` API returns Abstract Syntax Tree ([AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree)) representation of input NMODL file. One can look at the AST by converting to JSON form as: - -```python ->>> print (dsl.to_json(modast)) -{ - "Program": [ - { - "NeuronBlock": [ - { - "StatementBlock": [ - { - "Suffix": [ - { - "Name": [ - { - "String": [ - { - "name": "POINT_PROCESS" - } - ... -``` -Every key in the JSON form represent a node in the AST. You can also use visualization API to look at the details of AST as: - -``` -from nmodl import ast -ast.view(modast) -``` - -which will open AST view in web browser: - -![ast_viz](https://user-images.githubusercontent.com/666852/57329449-12c9a400-7114-11e9-8da5-0042590044ec.gif "AST representation of expsyn.mod") - -The central *Program* node represents the whole MOD file and each of it's children represent the block in the input NMODL file. Note that this requires X-forwarding if you are using Docker image. - -Once the AST is created, one can use exisiting visitors to perform various analysis/optimisations. One can also easily write his own custom visitor using Python Visitor API. See [Python API tutorial](docs/notebooks/nmodl-python-tutorial.ipynb) for details. - -NMODL Frameowrk also allows to transform AST representation back to NMODL form as: - -```python ->>> print (dsl.to_nmodl(modast)) -NEURON { - POINT_PROCESS ExpSyn - RANGE tau, e, i - NONSPECIFIC_CURRENT i -} - -UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (uS) = (microsiemens) -} - -PARAMETER { - tau = 0.1 (ms) <1e-09,1000000000> - e = 0 (mV) -} -... -``` - -### High Level Analysis and Code Generation - -The NMODL Framework provides rich model introspection and analysis capabilities using [various visitors](https://bluebrain.github.io/nmodl/html/doxygen/group__visitor__classes.html). Here is an example of theoretical performance characterisation of channels and synapses from rat neocortical column microcircuit [published in 2015](https://www.cell.com/abstract/S0092-8674%2815%2901191-5): - -![nmodl-perf-stats](https://user-images.githubusercontent.com/666852/57336711-2cc0b200-7127-11e9-8053-8f662e2ec191.png "Example of performance characterisation") - -To understand how you can write your own introspection and analysis tool, see [this tutorial](docs/notebooks/nmodl-python-tutorial.ipynb). - -Once analysis and optimization passes are performed, the NMODL Framework can generate optimised code for modern compute architectures including CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, C++, OpenACC and OpenMP backends are implemented and one can choose these backends on command line as: - -``` -$ nmodl expsyn.mod sympy --analytic -``` - -To know more about code generation backends, [see here](https://bluebrain.github.io/nmodl/html/doxygen/group__codegen__backends.html). NMODL Framework provides number of options (for code generation, optimization passes and ODE solver) which can be listed as: - -``` -$ nmodl -H -NMODL : Source-to-Source Code Generation Framework [version] -Usage: /path/<>/nmodl [OPTIONS] file... [SUBCOMMAND] - -Positionals: - file TEXT:FILE ... REQUIRED One or more MOD files to process - -Options: - -h,--help Print this help message and exit - -H,--help-all Print this help message including all sub-commands - --verbose=info Verbose logger output (trace, debug, info, warning, error, critical, off) - -o,--output TEXT=. Directory for backend code output - --scratch TEXT=tmp Directory for intermediate code output - --units TEXT=/path/<>/nrnunits.lib - Directory of units lib file - -Subcommands: -host - HOST/CPU code backends - Options: - --c C/C++ backend (true) - -acc - Accelerator code backends - Options: - --oacc C/C++ backend with OpenACC (false) - -sympy - SymPy based analysis and optimizations - Options: - --analytic Solve ODEs using SymPy analytic integration (false) - --pade Pade approximation in SymPy analytic integration (false) - --cse CSE (Common Subexpression Elimination) in SymPy analytic integration (false) - --conductance Add CONDUCTANCE keyword in BREAKPOINT (false) - -passes - Analyse/Optimization passes - Options: - --inline Perform inlining at NMODL level (false) - --unroll Perform loop unroll at NMODL level (false) - --const-folding Perform constant folding at NMODL level (false) - --localize Convert RANGE variables to LOCAL (false) - --global-to-range Convert GLOBAL variables to RANGE (false) - --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist (false) - --local-rename Rename LOCAL variable if variable of same name exist in global scope (false) - --verbatim-inline Inline even if verbatim block exist (false) - --verbatim-rename Rename variables in verbatim block (true) - --json-ast Write AST to JSON file (false) - --nmodl-ast Write AST to NMODL file (false) - --json-perf Write performance statistics to JSON file (false) - --show-symtab Write symbol table to stdout (false) - -codegen - Code generation options - Options: - --layout TEXT:{aos,soa}=soa Memory layout for code generation - --datatype TEXT:{float,double}=soa Data type for floating point variables - --force Force code generation even if there is any incompatibility - --only-check-compatibility Check compatibility and return without generating code - --opt-ionvar-copy Optimize copies of ion variables (false) -``` - -### Documentation - -We are working on user documentation, you can find current drafts of : - -* [User Documentation](https://bluebrain.github.io/nmodl/) -* [Developer / API Documentation](https://bluebrain.github.io/nmodl/html/doxygen/index.html) - - -### Citation - -If you would like to know more about the the NMODL Framework, see following paper: - -* Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform source-to-source compiler framework for the NEURON MODeling Language. In Eprint : [arXiv:1905.02241](https://arxiv.org/pdf/1905.02241.pdf) - - -### Support / Contribuition - -If you see any issue, feel free to [raise a ticket](https://github.com/BlueBrain/nmodl/issues/new). If you would like to improve this framework, see [open issues](https://github.com/BlueBrain/nmodl/issues) and [contribution guidelines](CONTRIBUTING.md). - - -### Examples / Benchmarks - -The benchmarks used to test the performance and parsing capabilities of NMODL Framework are currently being migrated to GitHub. These benchmarks will be published soon in following repositories: - -* [NMODL Benchmark](https://github.com/BlueBrain/nmodlbench) -* [NMODL Database](https://github.com/BlueBrain/nmodldb) - - -## Funding & Acknowledgment - -The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de Lausanne (EPFL), from the Swiss government's ETH Board of the Swiss Federal Institutes of Technology. In addition, the development was supported by funding from the National Institutes of Health (NIH) under the Grant Number R01NS11613 (Yale University) and the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreement No. 785907 (Human Brain Project SGA2). - -Copyright © 2017-2023 Blue Brain Project, EPFL diff --git a/docs/nmodl/transpiler/Doxyfile.in b/docs/nmodl/transpiler/Doxyfile.in index 96ffffcda3..65bf1a315f 100644 --- a/docs/nmodl/transpiler/Doxyfile.in +++ b/docs/nmodl/transpiler/Doxyfile.in @@ -868,7 +868,7 @@ INPUT = @NMODL_PROJECT_SOURCE_DIR@/src \ @NMODL_PROJECT_SOURCE_DIR@/test \ @PROJECT_BINARY_DIR@/src/ast \ @PROJECT_BINARY_DIR@/src/visitors \ - ../README.md + ../README.rst # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -909,7 +909,7 @@ FILE_PATTERNS = *.c \ *.hpp \ *.h++ \ *.markdown \ - *.md \ + *.rst \ *.mm \ *.dox \ *.yaml @@ -1036,7 +1036,7 @@ FILTER_SOURCE_PATTERNS = # (index.html). This can be useful if you have a project on for instance GitHub # and want to reuse the introduction page also for the doxygen output. -USE_MDFILE_AS_MAINPAGE = ../README.md +USE_MDFILE_AS_MAINPAGE = ../README.rst #--------------------------------------------------------------------------- # Configuration options related to source browsing diff --git a/docs/nmodl/transpiler/INSTALL.rst b/docs/nmodl/transpiler/INSTALL.rst new file mode 100644 index 0000000000..28098deab1 --- /dev/null +++ b/docs/nmodl/transpiler/INSTALL.rst @@ -0,0 +1,227 @@ +Installing the NMODL Framework +============================== + +Getting Started +--------------- + +These instructions will get you a copy of the project up and running on +your local machine for development and testing purposes. + +Cloning Source +-------------- + +The NMODL Framework is maintained on github. The best way to get the +sources is to simply clone the repository. + +**Note**: This project uses git submodules which must be cloned along +with the repository itself: + +.. code:: sh + + git clone --recursive https://github.com/BlueBrain/nmodl.git + cd nmodl + +Prerequisites +------------- + +To build the project from source, a modern C++ compiler with C++14 +support is necessary. Make sure you have following packages available: + +- flex (>=2.6) +- bison (>=3.0) +- CMake (>=3.15) +- Python (>=3.8) +- Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), + sympy (>=1.3), textwrap + +On OS X +~~~~~~~ + +Typically the versions of bison and flex provided by the system are +outdated and not compatible with our requirements. To get recent version +of all dependencies we recommend using `homebrew `__: + +.. code:: sh + + brew install flex bison cmake python3 + +The necessary Python packages can then easily be added using the pip3 +command. + +.. code:: sh + + pip3 install --user -r requirements.txt + +Make sure to have latest flex/bison in $PATH : + +.. code:: sh + + export PATH=/usr/local/opt/flex/bin:/usr/local/opt/bison/bin:/usr/local/bin/:$PATH + +On Apple M1, corresponding brew paths are under ``/opt/homebrew/opt/``: + +.. code:: sh + + export PATH=/opt/homebrew/opt/flex/bin:/opt/homebrew/opt/bison/bin:$PATH + +On Ubuntu +~~~~~~~~~ + +On Ubuntu (>=18.04) flex/bison versions are recent enough and are +installed along with the system toolchain: + +.. code:: sh + + apt-get install flex bison gcc python3 python3-pip + +The Python dependencies are installed using: + +.. code:: sh + + pip3 install --user -r requirements.txt + +Build Project +------------- + +Using CMake +~~~~~~~~~~~ + +Once all dependencies are in place, build project as: + +.. code:: sh + + mkdir -p nmodl/build + cd nmodl/build + cmake .. -DCMAKE_INSTALL_PREFIX=$HOME/nmodl + cmake --build . --parallel 4 --target install + +.. hint:: + + By default ``NMODL`` is build with the ``CMake`` option + ``NMODL_ENABLE_PYTHON_BINDINGS`` set to ``ON`` which increases a lot + the compilation complexity and memory requirements. For that purpose + it’s recommended to either disable this option if the Python bindings + are not needed or restrict the number of parallel jobs running in + parallel in the ``cmake`` command using + ``cmake --parallel ``. i.e. in a machine + with 8 threads do ``cmake --parallel 4``. + +And set PYTHONPATH as: + +.. code:: sh + + export PYTHONPATH=$HOME/nmodl/lib:$PYTHONPATH + +Flex / Bison Paths +^^^^^^^^^^^^^^^^^^ + +If flex / bison are not in your default $PATH, you can provide the path +to cmake as: + +.. code:: sh + + cmake .. -DFLEX_EXECUTABLE=/usr/local/opt/flex/bin/flex \ + -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ + -DCMAKE_INSTALL_PREFIX=$HOME/nmodl + +Using Python setuptools +~~~~~~~~~~~~~~~~~~~~~~~ + +If you are mainly interested in the NMODL Framework parsing and analysis +tools and wish to use them from Python, we recommend building and +installing using Python. + +.. code:: sh + + pip3 install --user . + +This should build the NMODL framework and install it into your pip user +``site-packages`` folder such that it becomes available as a Python +module. + +When building without linking against libpython +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +NMODL uses an embedded python to symbolically evaluate differential +equations. For this to work we would usually link against libpython, +which is automatically taken care of by pybind11. In some cases, for +instance when building a python wheel, we cannot link against libpython, +because we cannot know where it will be at runtime. Instead, we load the +python library (along with a wrapper library that manages calls to +embedded python) at runtime. To disable linking against python and +enabling dynamic loading of libpython at runtime we need to configure +the build with the cmake option ``-DLINK_AGAINST_PYTHON=False``. + +In order for NMODL binaries to know where to find libpython and our own +libpywrapper two environment variables need to be present: + +- ``NMODL_PYLIB``: This variable should point to the libpython + shared-object (or dylib) file. On macos this could be for example: + +.. code:: sh + + export NMODL_PYLIB=/usr/local/Cellar/python/3.7.7/Frameworks/Python.framework/Versions/3.7/Python + +**Note**: In order for all unit tests to function correctly when +building without linking against libpython we must set ``NMODL_PYLIB`` +before running cmake! + +Testing the Installed Module +---------------------------- + +If you have installed the NMODL Framework using CMake, you can now run +tests from the build directory as: + +.. code:: bash + + $ make test + Running tests... + Test project /Users/kumbhar/workarena/repos/bbp/incubator/nocmodl/cmake-build-debug + Start 1: testmodtoken/NMODL Lexer returning valid ModToken object + 1/60 Test #1: testmodtoken/NMODL Lexer returning valid ModToken object ................................... Passed 0.01 sec + Start 2: testlexer/NMODL Lexer returning valid token types + 2/60 Test #2: testlexer/NMODL Lexer returning valid token types .......................................... Passed 0.00 sec + Start 3: testparser/Scenario: NMODL can define macros using DEFINE keyword + 3/60 Test #3: testparser/Scenario: NMODL can define macros using DEFINE keyword .......................... Passed 0.01 sec + Start 4: testparser/Scenario: Macros can be used anywhere in the mod file + 4/60 Test #4: testparser/Scenario: Macros can be used anywhere in the mod file ........................... Passed 0.01 sec + Start 5: testparser/Scenario: NMODL parser accepts empty unit specification + 5/60 Test #5: testparser/Scenario: NMODL parser accepts empty unit specification ......................... Passed 0.01 sec + Start 6: testparser/Scenario: NMODL parser running number of valid NMODL constructs + 6/60 Test #6: testparser/Scenario: NMODL parser running number of valid NMODL constructs ................. Passed 0.04 sec + Start 7: testparser/Scenario: NMODL parser running number of invalid NMODL constructs + 7/60 Test #7: testparser/Scenario: NMODL parser running number of invalid NMODL constructs ............... Passed 0.01 sec + Start 8: testparser/Scenario: Legacy differential equation solver from NEURON solve number of ODE + 8/60 Test #8: testparser/Scenario: Legacy differential equation solver from NEURON solve number of ODE ... Passed 0.00 sec + ... + +To test the NMODL Framework python bindings, you can try a minimal +example in your Python 3 interpeter as follows: + +.. code:: python + + >>> import nmodl.dsl as nmodl + >>> driver = nmodl.NmodlDriver() + >>> modast = driver.parse_string("NEURON { SUFFIX hh }") + >>> print ('%s' % modast) + {"Program":[{"NeuronBlock":[{"StatementBlock":[{"Suffix":[{"Name":[{"String":[{"name":"SUFFIX"}]}]},{"Name":[{"String":[{"name":"hh"}]}]}]}]}]}]} + >>> print (nmodl.to_nmodl(modast)) + NEURON { + SUFFIX hh + } + +NMODL is now setup correctly! + +Generating Documentation +------------------------ + +In order to build the documentation you must have additionally +``pandoc`` installed. Use your system’s package manager to do this +(e.g. ``sudo apt-get install pandoc``). + +You can build the entire documentation simply by using sphinx from +``setup.py``: + +.. code:: sh + + python3 setup.py build_ext --inplace docs -G "Unix Makefiles" diff --git a/docs/nmodl/transpiler/contributing.rst b/docs/nmodl/transpiler/contributing.rst index 3dbffa8ee7..54a68f95f2 100644 --- a/docs/nmodl/transpiler/contributing.rst +++ b/docs/nmodl/transpiler/contributing.rst @@ -1,3 +1,3 @@ -.. include:: ../CONTRIBUTING.md +.. include:: ../CONTRIBUTING.rst :parser: myst_parser.sphinx_ diff --git a/docs/nmodl/transpiler/install.rst b/docs/nmodl/transpiler/install.rst index 71f9383876..16233b2c00 100644 --- a/docs/nmodl/transpiler/install.rst +++ b/docs/nmodl/transpiler/install.rst @@ -1,4 +1,4 @@ -.. include:: ../INSTALL.md +.. include:: ../INSTALL.rst :parser: myst_parser.sphinx_ diff --git a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst new file mode 100644 index 0000000000..09adb0d90e --- /dev/null +++ b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst @@ -0,0 +1,217 @@ +Contributing to the NMODL Framework +=================================== + +We would love for you to contribute to the NMODL Framework and help make +it better than it is today. As a contributor, here are the guidelines we +would like you to follow: - `Question or Problem? <#question>`__ - +`Issues and Bugs <#issue>`__ - `Feature Requests <#feature>`__ - +`Submission Guidelines <#submit>`__ - `Development +Conventions <#devconv>`__ + + Got a Question? +---------------- + +Please do not hesitate to raise an issue on `github project +page `__. + + Found a Bug? +------------- + +If you find a bug in the source code, you can help us by `submitting an +issue <#submit-issue>`__ to our `GitHub +Repository `__. Even better, you can +`submit a Pull Request <#submit-pr>`__ with a fix. + + Missing a Feature? +------------------- + +You can *request* a new feature by `submitting an +issue <#submit-issue>`__ to our GitHub Repository. If you would like to +*implement* a new feature, please submit an issue with a proposal for +your work first, to be sure that we can use it. + +Please consider what kind of change it is: + +- For a **Major Feature**, first open an issue and outline your + proposal so that it can be discussed. This will also allow us to + better coordinate our efforts, prevent duplication of work, and help + you to craft the change so that it is successfully accepted into the + project. +- **Small Features** can be crafted and directly `submitted as a Pull + Request <#submit-pr>`__. + + Submission Guidelines +---------------------- + + Submitting an Issue +~~~~~~~~~~~~~~~~~~~~ + +Before you submit an issue, please search the issue tracker, maybe an +issue for your problem already exists and the discussion might inform +you of workarounds readily available. + +We want to fix all the issues as soon as possible, but before fixing a +bug we need to reproduce and confirm it. In order to reproduce bugs we +will need as much information as possible, and preferably a sample MOD +file or Python example. + + Submitting a Pull Request (PR) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When you wish to contribute to the code base, please consider the +following guidelines: + +- Make a `fork `__ of + this repository. + +- Make your changes in your fork, in a new git branch: + + .. code:: shell + + git checkout -b my-fix-branch master + +- Create your patch, **including appropriate test cases**. + +- Enable ``NMODL_TEST_FORMATTING`` CMake variable to ensure that your + change follows the coding conventions of this project when running + the tests. The formatting utility can also be used directly: + + - to format CMake and C++ files: + ``cmake/hpc-coding-conventions/bin/format`` + - to format only the C++ files: + ``cmake/hpc-coding-conventions/bin/format --lang c++`` + - to format a subset of files or directories: + ``cmake/hpc-coding-conventions/bin/format src/codegen/ src/main.cpp`` + - to check the formatting of CMake files: + ``cmake/hpc-coding-conventions/bin/format --dry-run --lang cmake`` + +- Run the full test suite, and ensure that all tests pass. + +- Commit your changes using a descriptive commit message. + + .. code:: shell + + git commit -a + +- Push your branch to GitHub: + + .. code:: shell + + git push origin my-fix-branch + +- In GitHub, send a Pull Request to the ``master`` branch of the + upstream repository of the relevant component. + +- If we suggest changes then: + + - Make the required updates. + + - Re-run the test suites to ensure tests are still passing. + + - Rebase your branch and force push to your GitHub repository (this + will update your Pull Request): + + .. code:: shell + + git rebase master -i + git push -f + +That’s it! Thank you for your contribution! + +After your pull request is merged +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +After your pull request is merged, you can safely delete your branch and +pull the changes from the main (upstream) repository: + +- Delete the remote branch on GitHub either through the GitHub web UI + or your local shell as follows: + + .. code:: shell + + git push origin --delete my-fix-branch + +- Check out the master branch: + + .. code:: shell + + git checkout master -f + +- Delete the local branch: + + .. code:: shell + + git branch -D my-fix-branch + +- Update your master with the latest upstream version: + + .. code:: shell + + git pull --ff upstream master + + Development Conventions +------------------------ + +If you are developing NMODL, make sure to enable both +``NMODL_FORMATTING`` and ``NMODL_PRECOMMIT`` CMake variables to ensure +that your contributions follow the coding conventions of this project: + +.. code:: cmake + + cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON + +The first variable provides the following additional targets to format +C, C++, and CMake files: + +:: + + make clang-format cmake-format + +The second option activates Git hooks that will discard commits that do +not comply with coding conventions of this project. These 2 CMake +variables require additional utilities: + +- `ClangFormat + 7 `__ +- `cmake-format `__ +- `pre-commit `__ + +clang-format can be installed on Linux thanks to `LLVM apt +page `__. On MacOS, you can simply install llvm +with brew: ``brew install llvm``. *cmake-format* and *pre-commit* +utilities can be installed with *pip*. + +Validate the Python package +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You may run the Python test-suites if your contribution has an impact on +the Python API: + +1. setup a sandbox environment with either *virtualenv*, *pyenv*, or + *pipenv*. For instance with *virtualenv*: + ``python -m venv .venv && source .venv/bin/activate`` +2. build the Python package with the command: ``python setup.py build`` +3. install *pytest* Python package: ``pip install pytest`` +4. execute the unit-tests: ``pytest`` + +Memory Leaks and clang-tidy +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you want to test for memory leaks, do : + +:: + + valgrind --leak-check=full --track-origins=yes ./bin/nmodl_lexer + +Or using CTest as: + +:: + + ctest -T memcheck + +If you want to enable ``clang-tidy`` checks with CMake, make sure to +have ``CMake >= 3.15`` and use following cmake option: + +:: + + cmake .. -DENABLE_CLANG_TIDY=ON diff --git a/docs/nmodl/transpiler/notebooks/README.md b/docs/nmodl/transpiler/notebooks/README.md deleted file mode 100644 index 76cbf981f5..0000000000 --- a/docs/nmodl/transpiler/notebooks/README.md +++ /dev/null @@ -1,16 +0,0 @@ -## NMODL jupyter notebooks - -To get started with the NMODL python interface: - - [nmodl-python-tutorial.ipynb](nmodl-python-tutorial.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-python-tutorial.ipynb) - -For an overview of ODEs in NODL: - - [nmodl-odes-overview.ipynb](nmodl-odes-overview.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-odes-overview.ipynb) - -For more specific implementation details: - - [nmodl-kinetic-schemes.ipynb](nmodl-kinetic-schemes.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-kinetic-schemes.ipynb) - - [nmodl-sympy-solver-cnexp.ipynb](nmodl-sympy-solver-cnexp.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-cnexp.ipynb) - - [nmodl-sympy-solver-sparse.ipynb](nmodl-sympy-solver-sparse.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-sparse.ipynb) - - [nmodl-sympy-solver-derivimplicit.ipynb](nmodl-sympy-solver-derivimplicit.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-derivimplicit.ipynb) - - [nmodl-linear-solver.ipynb](nmodl-linear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-linear-solver.ipynb) - - [nmodl-nonlinear-solver.ipynb](nmodl-nonlinear-solver.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-nonlinear-solver.ipynb) - - [nmodl-sympy-conductance.ipynb](nmodl-sympy-conductance.ipynb) [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-conductance.ipynb) diff --git a/docs/nmodl/transpiler/notebooks/README.rst b/docs/nmodl/transpiler/notebooks/README.rst new file mode 100644 index 0000000000..e15d82c353 --- /dev/null +++ b/docs/nmodl/transpiler/notebooks/README.rst @@ -0,0 +1,41 @@ +NMODL jupyter notebooks +======================= + +To get started with the NMODL python interface: - +`nmodl-python-tutorial.ipynb `__ |Open In +Colab| + +For an overview of ODEs in NODL: - +`nmodl-odes-overview.ipynb `__ |image1| + +For more specific implementation details: - +`nmodl-kinetic-schemes.ipynb `__ |image2| - +`nmodl-sympy-solver-cnexp.ipynb `__ +|image3| - +`nmodl-sympy-solver-sparse.ipynb `__ +|image4| - +`nmodl-sympy-solver-derivimplicit.ipynb `__ +|image5| - `nmodl-linear-solver.ipynb `__ +|image6| - +`nmodl-nonlinear-solver.ipynb `__ |image7| +- `nmodl-sympy-conductance.ipynb `__ +|image8| + +.. |Open In Colab| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-python-tutorial.ipynb +.. |image1| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-odes-overview.ipynb +.. |image2| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-kinetic-schemes.ipynb +.. |image3| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-cnexp.ipynb +.. |image4| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-sparse.ipynb +.. |image5| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-solver-derivimplicit.ipynb +.. |image6| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-linear-solver.ipynb +.. |image7| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-nonlinear-solver.ipynb +.. |image8| image:: https://colab.research.google.com/assets/colab-badge.svg + :target: https://colab.research.google.com/github/BlueBrain/nmodl/blob/master/docs/notebooks/nmodl-sympy-conductance.ipynb diff --git a/docs/nmodl/transpiler/readme.rst b/docs/nmodl/transpiler/readme.rst index 6a9465b82b..c29a05db90 100644 --- a/docs/nmodl/transpiler/readme.rst +++ b/docs/nmodl/transpiler/readme.rst @@ -1,6 +1,6 @@ Introduction ============ -.. include:: ../README.md +.. include:: ../README.rst :parser: myst_parser.sphinx_ diff --git a/setup.py b/setup.py index bf2f61458f..b3a9b66658 100644 --- a/setup.py +++ b/setup.py @@ -90,8 +90,8 @@ def run(self, *args, **kwargs): if "NMODL_NIGHTLY_TAG" in os.environ: package_name += os.environ["NMODL_NIGHTLY_TAG"] -# Parse long description from README.md -with open('README.md', 'r', encoding='utf-8') as f: +# Parse long description from README.rst +with open('README.rst', 'r', encoding='utf-8') as f: long_description = f.read() setup( diff --git a/src/nmodl/nmodl/README.rst b/src/nmodl/nmodl/README.rst new file mode 100644 index 0000000000..1225788901 --- /dev/null +++ b/src/nmodl/nmodl/README.rst @@ -0,0 +1,378 @@ +The NMODL Framework +=================== + +|github workflow| |Build Status| |codecov| |CII Best Practices| + +The NMODL Framework is a code generation engine for **N**\ EURON +**MOD**\ eling **L**\ anguage +(`NMODL `__). +It is designed with modern compiler and code generation techniques to: + +- Provide **modular tools** for parsing, analysing and transforming + NMODL +- Provide **easy to use**, high level Python API +- Generate **optimised code** for modern compute architectures + including CPUs, GPUs +- **Flexibility** to implement new simulator backends +- Support for **full** NMODL specification + +About NMODL +----------- + +Simulators like `NEURON `__ use +NMODL as a domain specific language (DSL) to describe a wide range of +membrane and intracellular submodels. Here is an example of exponential +synapse specified in NMODL: + +.. code:: python + + NEURON { + POINT_PROCESS ExpSyn + RANGE tau, e, i + NONSPECIFIC_CURRENT i + } + UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) + } + PARAMETER { + tau = 0.1 (ms) <1e-9,1e9> + e = 0 (mV) + } + ASSIGNED { + v (mV) + i (nA) + } + STATE { + g (uS) + } + INITIAL { + g = 0 + } + BREAKPOINT { + SOLVE state METHOD cnexp + i = g*(v - e) + } + DERIVATIVE state { + g' = -g/tau + } + NET_RECEIVE(weight (uS)) { + g = g + weight + } + +Installation +------------ + +See +`INSTALL.rst `__ +for detailed instructions to build the NMODL from source. + +Try NMODL with Docker +--------------------- + +To quickly test the NMODL Framework’s analysis capabilities we provide a +`docker `__ image, which includes the NMODL +Framework python library and a fully functional Jupyter notebook +environment. After installing +`docker `__ and +`docker-compose `__ you can +pull and run the NMODL image from your terminal. + +To try Python interface directly from CLI, you can run docker image as: + +:: + + docker run -it --entrypoint=/bin/sh bluebrain/nmodl + +And try NMODL Python API discussed later in this README as: + +:: + + $ python3 + Python 3.6.8 (default, Apr 8 2019, 18:17:52) + >>> from nmodl import dsl + >>> import os + >>> examples = dsl.list_examples() + >>> nmodl_string = dsl.load_example(examples[-1]) + ... + +To try Jupyter notebooks you can download docker compose file and run it +as: + +.. code:: sh + + wget "https://raw.githubusercontent.com/BlueBrain/nmodl/master/docker/docker-compose.yml" + DUID=$(id -u) DGID=$(id -g) HOSTNAME=$(hostname) docker-compose up + +If all goes well you should see at the end status messages similar to +these: + +:: + + [I 09:49:53.923 NotebookApp] The Jupyter Notebook is running at: + [I 09:49:53.923 NotebookApp] http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 + [I 09:49:53.923 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). + To access the notebook, open this file in a browser: + file:///root/.local/share/jupyter/runtime/nbserver-1-open.html + Or copy and paste one of these URLs: + http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 + +Based on the example above you should then open your browser and +navigate to the URL +``http://127.0.0.1:8888/?token=a7902983bad430a11935``. + +You can open and run all example notebooks provided in the ``examples`` +folder. You can also create new notebooks in ``my_notebooks``, which +will be stored in a subfolder ``notebooks`` at your current working +directory. + +Using the Python API +-------------------- + +Once the NMODL Framework is installed, you can use the Python parsing +API to load NMOD file as: + +.. code:: python + + from nmodl import dsl + + examples = dsl.list_examples() + nmodl_string = dsl.load_example(examples[-1]) + driver = dsl.NmodlDriver() + modast = driver.parse_string(nmodl_string) + +The ``parse_file`` API returns Abstract Syntax Tree +(`AST `__) +representation of input NMODL file. One can look at the AST by +converting to JSON form as: + +.. code:: python + + >>> print (dsl.to_json(modast)) + { + "Program": [ + { + "NeuronBlock": [ + { + "StatementBlock": [ + { + "Suffix": [ + { + "Name": [ + { + "String": [ + { + "name": "POINT_PROCESS" + } + ... + +Every key in the JSON form represent a node in the AST. You can also use +visualization API to look at the details of AST as: + +:: + + from nmodl import ast + ast.view(modast) + +which will open AST view in web browser: + +.. figure:: + https://user-images.githubusercontent.com/666852/57329449-12c9a400-7114-11e9-8da5-0042590044ec.gif + :alt: ast_viz + + ast_viz + +The central *Program* node represents the whole MOD file and each of +it’s children represent the block in the input NMODL file. Note that +this requires X-forwarding if you are using Docker image. + +Once the AST is created, one can use exisiting visitors to perform +various analysis/optimisations. One can also easily write his own custom +visitor using Python Visitor API. See `Python API +tutorial `__ for details. + +NMODL Frameowrk also allows to transform AST representation back to +NMODL form as: + +.. code:: python + + >>> print (dsl.to_nmodl(modast)) + NEURON { + POINT_PROCESS ExpSyn + RANGE tau, e, i + NONSPECIFIC_CURRENT i + } + + UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) + } + + PARAMETER { + tau = 0.1 (ms) <1e-09,1000000000> + e = 0 (mV) + } + ... + +High Level Analysis and Code Generation +--------------------------------------- + +The NMODL Framework provides rich model introspection and analysis +capabilities using `various +visitors `__. +Here is an example of theoretical performance characterisation of +channels and synapses from rat neocortical column microcircuit +`published in +2015 `__: + +.. figure:: + https://user-images.githubusercontent.com/666852/57336711-2cc0b200-7127-11e9-8053-8f662e2ec191.png + :alt: nmodl-perf-stats + + nmodl-perf-stats + +To understand how you can write your own introspection and analysis +tool, see `this +tutorial `__. + +Once analysis and optimization passes are performed, the NMODL Framework +can generate optimised code for modern compute architectures including +CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, +C++, OpenACC and OpenMP backends are implemented and one can choose +these backends on command line as: + +:: + + $ nmodl expsyn.mod sympy --analytic + +To know more about code generation backends, `see +here `__. +NMODL Framework provides number of options (for code generation, +optimization passes and ODE solver) which can be listed as: + +:: + + $ nmodl -H + NMODL : Source-to-Source Code Generation Framework [version] + Usage: /path/<>/nmodl [OPTIONS] file... [SUBCOMMAND] + + Positionals: + file TEXT:FILE ... REQUIRED One or more MOD files to process + + Options: + -h,--help Print this help message and exit + -H,--help-all Print this help message including all sub-commands + --verbose=info Verbose logger output (trace, debug, info, warning, error, critical, off) + -o,--output TEXT=. Directory for backend code output + --scratch TEXT=tmp Directory for intermediate code output + --units TEXT=/path/<>/nrnunits.lib + Directory of units lib file + + Subcommands: + host + HOST/CPU code backends + Options: + --c C/C++ backend (true) + + acc + Accelerator code backends + Options: + --oacc C/C++ backend with OpenACC (false) + + sympy + SymPy based analysis and optimizations + Options: + --analytic Solve ODEs using SymPy analytic integration (false) + --pade Pade approximation in SymPy analytic integration (false) + --cse CSE (Common Subexpression Elimination) in SymPy analytic integration (false) + --conductance Add CONDUCTANCE keyword in BREAKPOINT (false) + + passes + Analyse/Optimization passes + Options: + --inline Perform inlining at NMODL level (false) + --unroll Perform loop unroll at NMODL level (false) + --const-folding Perform constant folding at NMODL level (false) + --localize Convert RANGE variables to LOCAL (false) + --global-to-range Convert GLOBAL variables to RANGE (false) + --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist (false) + --local-rename Rename LOCAL variable if variable of same name exist in global scope (false) + --verbatim-inline Inline even if verbatim block exist (false) + --verbatim-rename Rename variables in verbatim block (true) + --json-ast Write AST to JSON file (false) + --nmodl-ast Write AST to NMODL file (false) + --json-perf Write performance statistics to JSON file (false) + --show-symtab Write symbol table to stdout (false) + + codegen + Code generation options + Options: + --layout TEXT:{aos,soa}=soa Memory layout for code generation + --datatype TEXT:{float,double}=soa Data type for floating point variables + --force Force code generation even if there is any incompatibility + --only-check-compatibility Check compatibility and return without generating code + --opt-ionvar-copy Optimize copies of ion variables (false) + +Documentation +------------- + +We are working on user documentation, you can find current drafts of : + +- `User Documentation `__ +- `Developer / API + Documentation `__ + +Citation +-------- + +If you would like to know more about the the NMODL Framework, see +following paper: + +- Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, + Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform + source-to-source compiler framework for the NEURON MODeling Language. + In Eprint : + `arXiv:1905.02241 `__ + +Support / Contribuition +----------------------- + +If you see any issue, feel free to `raise a +ticket `__. If you would +like to improve this framework, see `open +issues `__ and `contribution +guidelines `__. + +Examples / Benchmarks +--------------------- + +The benchmarks used to test the performance and parsing capabilities of +NMODL Framework are currently being migrated to GitHub. These benchmarks +will be published soon in following repositories: + +- `NMODL Benchmark `__ +- `NMODL Database `__ + +Funding & Acknowledgment +======================== + +The development of this software was supported by funding to the Blue +Brain Project, a research center of the École polytechnique fédérale de +Lausanne (EPFL), from the Swiss government’s ETH Board of the Swiss +Federal Institutes of Technology. In addition, the development was +supported by funding from the National Institutes of Health (NIH) under +the Grant Number R01NS11613 (Yale University) and the European Union’s +Horizon 2020 Framework Programme for Research and Innovation under the +Specific Grant Agreement No. 785907 (Human Brain Project SGA2). + +Copyright © 2017-2023 Blue Brain Project, EPFL + +.. |github workflow| image:: https://github.com/BlueBrain/nmodl/actions/workflows/nmodl-ci.yml/badge.svg?branch=master +.. |Build Status| image:: https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master + :target: https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master +.. |codecov| image:: https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB + :target: https://codecov.io/gh/BlueBrain/nmodl +.. |CII Best Practices| image:: https://bestpractices.coreinfrastructure.org/projects/4467/badge + :target: https://bestpractices.coreinfrastructure.org/projects/4467 From 686ed4a13a27a624f38f4092d4fac156e9c6ee2a Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 28 Sep 2023 18:05:31 +0200 Subject: [PATCH 540/871] Improve generated C++ header files in the `nmodl::ast` namespace (BlueBrain/nmodl#1080) * Improve generated C++ headers: * Use nested namespace definitions * Improve indentation for readability * Prefer passing by reference rather than copy when possible for `std::shared_ptr` and `std::vector` * CI: * CodeCov: ignore jinja templates * SonarSource: ignore a couple of directories / files (submodules, ...) * in bison parser, catch exception by reference, not copy NMODL Repo SHA: BlueBrain/nmodl@2355a6ebbae5864cd3cc2a2a6d90107fb278832c --- codecov.yaml | 2 + docs/nmodl/transpiler/DoxygenLayout.xml | 1 - src/nmodl/codegen/codegen_cpp_visitor.cpp | 1 - .../codegen/codegen_transform_visitor.cpp | 3 +- src/nmodl/language/nodes.py | 160 ++++++------ src/nmodl/language/templates/ast/ast.cpp | 9 +- src/nmodl/language/templates/ast/ast.hpp | 20 +- .../templates/ast/node_class.template | 245 ++++++++---------- src/nmodl/language/templates/pybind/pyast.hpp | 4 +- src/nmodl/parser/unit.yy | 2 +- 10 files changed, 206 insertions(+), 241 deletions(-) create mode 100644 codecov.yaml diff --git a/codecov.yaml b/codecov.yaml new file mode 100644 index 0000000000..43ee99ef88 --- /dev/null +++ b/codecov.yaml @@ -0,0 +1,2 @@ +ignore: +- src/language/templates/ diff --git a/docs/nmodl/transpiler/DoxygenLayout.xml b/docs/nmodl/transpiler/DoxygenLayout.xml index 258ae9d3b0..97ed605034 100644 --- a/docs/nmodl/transpiler/DoxygenLayout.xml +++ b/docs/nmodl/transpiler/DoxygenLayout.xml @@ -4,7 +4,6 @@ - diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index a83aad5ce7..e661f5685b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -473,7 +473,6 @@ bool CodegenCppVisitor::need_semicolon(const Statement& node) { || node.is_else_statement() || node.is_from_statement() || node.is_verbatim() - || node.is_from_statement() || node.is_conductance_hint() || node.is_while_statement() || node.is_protect_statement() diff --git a/src/nmodl/codegen/codegen_transform_visitor.cpp b/src/nmodl/codegen/codegen_transform_visitor.cpp index f06f5fbd35..6d95bd7f52 100644 --- a/src/nmodl/codegen/codegen_transform_visitor.cpp +++ b/src/nmodl/codegen/codegen_transform_visitor.cpp @@ -21,7 +21,8 @@ void CodegenTransformVisitor::visit_function_block(FunctionBlock& node) { auto table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); for (auto t: table_statements) { auto t_ = std::dynamic_pointer_cast(t); - t_->set_table_vars({std::make_shared(new String(node.get_node_name()))}); + t_->set_table_vars( + {std::make_shared(std::make_shared(node.get_node_name()))}); } } } // namespace nmodl diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 7c6d77f144..fc45edf181 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -199,6 +199,8 @@ def get_typename(self): def get_rvalue_typename(self): """returns rvalue reference type of the node""" typename = self.get_typename() + if self.is_vector: + return "const " + typename + "&" if self.is_base_type_node and not self.is_integral_type_node or self.is_ptr_excluded_node: return "const " + typename + "&" return typename @@ -242,7 +244,7 @@ def _is_member_type_wrapped_as_shared_pointer(self): def member_rvalue_typename(self): """returns rvalue reference type when used as returned or parameter type""" typename = self.member_typename - if not self.is_integral_type_node and not self._is_member_type_wrapped_as_shared_pointer: + if not self.is_integral_type_node: return "const " + typename + "&" return typename @@ -250,55 +252,55 @@ def get_add_methods_declaration(self): s = '' if self.add_method: method = f""" - /** - * \\brief Add member to {self.varname} by raw pointer - */ - void emplace_back_{to_snake_case(self.class_name)}({self.class_name} *n); - - /** - * \\brief Add member to {self.varname} by shared_ptr - */ - void emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n); - - /** - * \\brief Erase member to {self.varname} - */ - {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first); - - /** - * \\brief Erase members to {self.varname} - */ - {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last); - - /** - * \\brief Erase non-consecutive members to {self.varname} - * - * loosely following the cpp reference of remove_if - */ - size_t erase_{to_snake_case(self.class_name)}(std::unordered_set<{self.class_name}*>& to_be_erased); - - /** - * \\brief Insert member to {self.varname} - */ - {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n); - - /** - * \\brief Insert members to {self.varname} - */ - template - void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, NodeType& to, InputIterator first, InputIterator last); - - /** - * \\brief Reset member to {self.varname} - */ - void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n); - - /** - * \\brief Reset member to {self.varname} - */ - void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n); - """ - s = textwrap.dedent(method) + /** + * \\brief Add member to {self.varname} by raw pointer + */ + void emplace_back_{to_snake_case(self.class_name)}({self.class_name} *n); + + /** + * \\brief Add member to {self.varname} by shared_ptr + */ + void emplace_back_{to_snake_case(self.class_name)}(std::shared_ptr<{self.class_name}> n); + + /** + * \\brief Erase member to {self.varname} + */ + {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first); + + /** + * \\brief Erase members to {self.varname} + */ + {self.class_name}Vector::const_iterator erase_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator first, {self.class_name}Vector::const_iterator last); + + /** + * \\brief Erase non-consecutive members to {self.varname} + * + * loosely following the cpp reference of remove_if + */ + size_t erase_{to_snake_case(self.class_name)}(std::unordered_set<{self.class_name}*>& to_be_erased); + + /** + * \\brief Insert member to {self.varname} + */ + {self.class_name}Vector::const_iterator insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, const std::shared_ptr<{self.class_name}>& n); + + /** + * \\brief Insert members to {self.varname} + */ + template + void insert_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, NodeType& to, InputIterator first, InputIterator last); + + /** + * \\brief Reset member to {self.varname} + */ + void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, {self.class_name}* n); + + /** + * \\brief Reset member to {self.varname} + */ + void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n); + """ + s = textwrap.indent(textwrap.dedent(method), ' ') return s def get_add_methods_definition(self, parent): @@ -440,12 +442,12 @@ def get_node_name_method_declaration(self): * in case of this ast::{self.class_name} has {self.varname} designated as a * node name. * - * @return name of the node as std::string + * \\return name of the node as std::string * * \\sa Ast::get_node_type_name */ std::string get_node_name() const override;""" - s = textwrap.dedent(method) + s = textwrap.indent(textwrap.dedent(method), ' ') return s def get_node_name_method_definition(self, parent): @@ -461,42 +463,40 @@ def get_node_name_method_definition(self, parent): def get_getter_method(self, class_name): getter_method = self.getter_method if self.getter_method else "get_" + to_snake_case(self.varname) - getter_override = " override" if self.getter_override else "" + getter_override = "override" if self.getter_override else "" return_type = self.member_rvalue_typename - return f""" - /** - * \\brief Getter for member variable \\ref {class_name}.{self.varname} - */ - {return_type} {getter_method}() const noexcept {getter_override} {{ - return {self.varname}; - }} - """ + return textwrap.indent(textwrap.dedent(f""" + /** + * \\brief Getter for member variable \\ref {class_name}.{self.varname} + */ + {return_type} {getter_method}() const noexcept {getter_override} {{ + return {self.varname}; + }} + """), ' ') def get_setter_method_declaration(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename reference = "" if self.is_base_type_node else "&&" if self.is_base_type_node: - return f""" - /** - * \\brief Setter for member variable \\ref {class_name}.{self.varname} - */ - void {setter_method}({setter_type} {self.varname}); - """ + return textwrap.indent(textwrap.dedent(f""" + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} + */ + void {setter_method}({setter_type} {self.varname}); + """), ' ') else: - return f""" - /** - * \\brief Setter for member variable \\ref {class_name}.{self.varname} (rvalue reference) - */ - void {setter_method}({setter_type}&& {self.varname}); - - /** - * \\brief Setter for member variable \\ref {class_name}.{self.varname} - */ - void {setter_method}(const {setter_type}& {self.varname}); - """ - - + return textwrap.indent(textwrap.dedent(f""" + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} (rvalue reference) + */ + void {setter_method}({setter_type}&& {self.varname}); + + /** + * \\brief Setter for member variable \\ref {class_name}.{self.varname} + */ + void {setter_method}(const {setter_type}& {self.varname}); + """), ' ') def get_setter_method_definition(self, class_name): setter_method = "set_" + to_snake_case(self.varname) diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 04ff475381..360d37dc97 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -17,8 +17,7 @@ * \brief Auto generated AST classes implementations */ -namespace nmodl { -namespace ast { +namespace nmodl::ast { /// /// Ast member function definition @@ -30,7 +29,7 @@ std::string Ast::get_node_name() const { throw std::logic_error("get_node_name() not implemented"); } -std::shared_ptr Ast::get_statement_block() const { +const std::shared_ptr& Ast::get_statement_block() const { throw std::runtime_error("get_statement_block not implemented"); } @@ -219,7 +218,5 @@ void Ast::set_parent(Ast* p) { {% endfor %} {% endfor %} - -} // namespace ast -} // namespace nmodl +} // namespace nmodl::ast diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 6e9e4b18a4..5af0e551aa 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -29,8 +29,7 @@ #include "utils/common_utils.hpp" #include "visitors/visitor.hpp" -namespace nmodl { -namespace ast { +namespace nmodl::ast { /** * \page ast_design Design of Abstract Syntax Tree (AST) @@ -50,10 +49,10 @@ namespace ast { */ /** - * @defgroup ast_class AST Classes - * @ingroup ast - * @brief Classes for implementing Abstract Syntax Tree (AST) - * @{ + * \defgroup ast_class AST Classes + * \ingroup ast + * \brief Classes for implementing Abstract Syntax Tree (AST) + * \{ */ /** @@ -122,7 +121,7 @@ struct Ast: public std::enable_shared_from_this { * This type name can be returned as a std::string for printing * ast to text/json form. * - * @return name of the node type as a string + * \return name of the node type as a string * * \sa Ast::get_node_name */ @@ -273,7 +272,7 @@ struct Ast: public std::enable_shared_from_this { * * \sa ast::StatementBlock */ - virtual std::shared_ptr get_statement_block() const; + virtual const std::shared_ptr& get_statement_block() const; /** * \brief Set symbol table for the AST node @@ -342,7 +341,7 @@ struct Ast: public std::enable_shared_from_this { /** *\brief Parent setter * - * Usually, the parent parent pointer cannot be set in the constructor + * Usually, the parent pointer cannot be set in the constructor * because children are generally build BEFORE the parent. Conversely, * we set children parents directly in the parent constructor using * set_parent_in_children() @@ -352,5 +351,4 @@ struct Ast: public std::enable_shared_from_this { virtual void set_parent(Ast* p); }; -} // namespace ast -} // namespace nmodl +} // namespace nmodl::ast diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index 736601591d..87c6c9f590 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -2,19 +2,16 @@ this Jinja template is not used to directly generate a file but included by other templates. #} - {# add virtual qualifier if node is an abstract class #} {% macro virtual(node) -%} {% if node.is_abstract %} virtual {% endif %} {% endmacro %} - -namespace nmodl { -namespace ast { +namespace nmodl::ast { /** - * @addtogroup ast_class - * @ingroup ast - * @{ + * \addtogroup ast_class + * \ingroup ast + * \{ */ /** @@ -28,61 +25,52 @@ class {{ node.class_name }} : public {{ node.base_class }} { {% for member in node.private_members() %} {{ '/// ' + member[3] }} {% if member[2] is none %} - {{ member[0] }} {{ member[1] }}; + {{ member[0] }} {{ member[1] }}; {% else %} - {{ member[0] }} {{ member[1] }} = {{ member[2] }}; + {{ member[0] }} {{ member[1] }} = {{ member[2] }}; {% endif %} {% endfor %} {% endif %} public: - {% for member in node.public_members() %} + {%- for member in node.public_members() %} {{ '/// ' + member[3] }} {% if member[2] is none %} - {{ member[0] }} {{ member[1] }}; + {{ member[0] }} {{ member[1] }}; {% else %} - {{ member[0] }} {{ member[1] }} = {{ member[2] }}; + {{ member[0] }} {{ member[1] }} = {{ member[2] }}; {% endif %} - {% endfor %} + {%- endfor %} /// \name Ctor & dtor /// \{ - {% if node.children %} {{ node.ctor_declaration() }} {% if node.has_ptr_children() %} - {{ node.ctor_shrptr_declaration() }} + {{ node.ctor_shrptr_declaration() }} {% endif %} {{ node.class_name }}(const {{ node.class_name }}& obj); {% endif %} - {% if node.requires_default_constructor %} {{ node.class_name}}() = default; {% endif %} - - virtual ~{{ node.class_name }}() = default; - + virtual ~{{ node.class_name }}() = default; /// \} - {% if node.is_base_block_node or node.is_number_node %} /// \name Not implemented /// \{ {% endif %} - {% if node.is_base_block_node %} virtual const ArgumentVector& get_parameters() const { throw std::runtime_error("get_parameters not implemented"); } {% endif %} - {% if node.is_number_node %} {{ virtual(node) }}double to_double() { throw std::runtime_error("to_double not implemented"); } {% endif %} - {{ "/// \}" if node.is_base_block_node or node.is_number_node else "" }} - /** * \brief Check if the ast node is an instance of ast::{{ node.class_name }} * \return true as object is of type ast::{{ node.class_name }} @@ -99,7 +87,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * passes like nmodl::visitor::InlineVisitor where nodes are cloned in the * ast. * - * @return pointer to the clone/copy of the current node + * \return pointer to the clone/copy of the current node */ // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) {{ virtual(node) }} {{ node.class_name }}* clone() const override { @@ -171,120 +159,105 @@ class {{ node.class_name }} : public {{ node.base_class }} { } {% if node.has_token %} - /** - * \brief Return associated token for the current ast node - * - * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor - * can insert new nodes in the ast as a solution of ODEs. In this case, we return - * nullptr to store in the nmodl::symtab::SymbolTable. - * - * \return pointer to token if exist otherwise nullptr - */ - const {{ virtual(node) }}ModToken* get_token() const noexcept override { - return token.get(); - } - {% endif %} - - {% if node.is_symtab_needed %} - /** - * \brief Return associated symbol table for the current ast node - * - * Only certain ast nodes (e.g. inherited from ast::Block) have associated - * symbol table. These nodes have nmodl::symtab::SymbolTable as member - * and it can be accessed using this method. - * - * \return pointer to the symbol table - * - * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor - */ - symtab::SymbolTable* get_symbol_table() const override { - return symtab; - } - - {% endif %} - - {% if node.is_program_node %} - /** - * \brief Return global symbol table for the mod file - */ - symtab::ModelSymbolTable* get_model_symbol_table() { - return &model_symtab; - } - {% endif %} - - {# doxygen for these methods is handled by nodes.py #} - {% for child in node.children %} - {{ child.get_add_methods_declaration() }} - - {{ child.get_node_name_method_declaration() }} - - {{ child.get_getter_method(node.class_name) }} - - {% endfor %} - + /** + * \brief Return associated token for the current ast node + * + * Not all ast nodes have token information. For example, nmodl::visitor::NeuronSolveVisitor + * can insert new nodes in the ast as a solution of ODEs. In this case, we return + * nullptr to store in the nmodl::symtab::SymbolTable. + * + * \return pointer to token if exist otherwise nullptr + */ + const {{ virtual(node) }}ModToken* get_token() const noexcept override { + return token.get(); + } +{% endif %} +{%- if node.is_symtab_needed %} + /** + * \brief Return associated symbol table for the current ast node + * + * Only certain ast nodes (e.g. inherited from ast::Block) have associated + * symbol table. These nodes have nmodl::symtab::SymbolTable as member + * and it can be accessed using this method. + * + * \return pointer to the symbol table + * + * \sa nmodl::symtab::SymbolTable nmodl::visitor::SymtabVisitor + */ + symtab::SymbolTable* get_symbol_table() const override { + return symtab; + } +{% endif %} +{% if node.is_program_node %} + /** + * \brief Return global symbol table for the mod file + */ + symtab::ModelSymbolTable* get_model_symbol_table() { + return &model_symtab; + } +{% endif %} +{# doxygen for these methods is handled by nodes.py #} +{% for child in node.children %} + {{ child.get_add_methods_declaration() }} + {{ child.get_node_name_method_declaration() }} + {{ child.get_getter_method(node.class_name) }} +{%- endfor %} /// \} - {% if node.has_setters %} +{% if node.has_setters %} /// \name Setters /// \{ - {% endif %} - - {% if node.is_name_node %} - /** - * \brief Set name for the current ast node - * - * Some ast nodes have a member marked designated as node name (e.g. nodes - * derived from ast::Identifier). This method is used to set new name for those - * nodes. This useful for passes like nmodl::visitor::RenameVisitor. - * - * \sa Ast::get_node_type_name Ast::get_node_name - */ - {{ virtual(node) }}void set_name(const std::string& name) override; - {% endif %} - - {% if node.has_token %} - /** - * \brief Set token for the current ast node - */ - void set_token(const ModToken& tok) { token = std::make_shared(tok); } - {% endif %} - - {% if node.is_symtab_needed %} - /** - * \brief Set symbol table for the current ast node - * - * Top level, block scoped nodes store symbol table in the ast node. - * nmodl::visitor::SymtabVisitor then used this method to setup symbol table - * for every node in the ast. - * - * \sa nmodl::visitor::SymtabVisitor - */ - void set_symbol_table(symtab::SymbolTable* newsymtab) override { - symtab = newsymtab; - } - {% endif %} - - {# if node is base data type but not enum then add set method #} - {% if node.is_data_type_node and not node.is_enum_node %} - /** - * \brief Set new value to the current ast node - * \sa {{ node.class_name }}::eval - */ - void set({{ node.get_data_type_name() }} _value) { - value = _value; - } - {% endif %} - - {# doxygen for these methods is handled by nodes.py #} - {% for child in node.children %} - {{ child.get_setter_method_declaration(node.class_name) }} - {% endfor %} - - {{ "/// \}" if node.has_setters else "" }} +{% endif %} +{% if node.is_name_node %} + /** + * \brief Set name for the current ast node + * + * Some ast nodes have a member marked designated as node name (e.g. nodes + * derived from ast::Identifier). This method is used to set new name for those + * nodes. This useful for passes like nmodl::visitor::RenameVisitor. + * + * \sa Ast::get_node_type_name Ast::get_node_name + */ + {{ virtual(node) }}void set_name(const std::string& name) override; +{% endif %} +{% if node.has_token %} + /** + * \brief Set token for the current ast node + */ + void set_token(const ModToken& tok) { token = std::make_shared(tok); } +{% endif %} +{% if node.is_symtab_needed %} + /** + * \brief Set symbol table for the current ast node + * + * Top level, block scoped nodes store symbol table in the ast node. + * nmodl::visitor::SymtabVisitor then used this method to setup symbol table + * for every node in the ast. + * + * \sa nmodl::visitor::SymtabVisitor + */ + void set_symbol_table(symtab::SymbolTable* newsymtab) override { + symtab = newsymtab; + } +{% endif %} +{# if node is base data type but not enum then add set method #} +{% if node.is_data_type_node and not node.is_enum_node %} + /** + * \brief Set new value to the current ast node + * \sa {{ node.class_name }}::eval + */ + void set({{ node.get_data_type_name() }} _value) { + value = _value; + } +{% endif %} +{# doxygen for these methods is handled by nodes.py #} +{% for child in node.children %} + {{ child.get_setter_method_declaration(node.class_name) }} +{% endfor %} +{{ " /// \}" if node.has_setters else "" }} /// \name Visitor /// \{ - /** * \brief visit children i.e. member variables of current node using provided visitor * @@ -326,7 +299,6 @@ class {{ node.class_name }} : public {{ node.base_class }} { * \copydoc accept(visitor::Visitor&) */ {{ virtual(node) }} void accept(visitor::ConstVisitor& v) const override; - /// \} {% if node.is_base_class_number_node %} @@ -356,7 +328,6 @@ class {{ node.class_name }} : public {{ node.base_class }} { {% endif %} } {% endif %} - {% if node.is_data_type_node %} {# if node is of enum type then return enum value #} {% if node.is_enum_node %} @@ -387,7 +358,6 @@ class {{ node.class_name }} : public {{ node.base_class }} { } {% endif %} {% endif %} - {% if node.children %} private: /** @@ -401,11 +371,10 @@ class {{ node.class_name }} : public {{ node.base_class }} { {% endif %} }; -/** @} */ // end of ast_class +/** \} */ // end of ast_class {% for child in node.children %} {{ child.get_add_methods_inline_definition(node) }} {% endfor %} -} // namespace ast -} // namespace nmodl +} // namespace nmodl::ast diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index e70ffe3aca..f2fa13eb14 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -114,8 +114,8 @@ struct PyAst: public Ast { PYBIND11_OVERRIDE(symtab::SymbolTable*, Ast, get_symbol_table, ); } - std::shared_ptr get_statement_block() const override { - PYBIND11_OVERRIDE(std::shared_ptr, Ast, get_statement_block, ); + const std::shared_ptr& get_statement_block() const override { + PYBIND11_OVERRIDE(const std::shared_ptr&, Ast, get_statement_block, ); } void set_symbol_table(symtab::SymbolTable* newsymtab) override { diff --git a/src/nmodl/parser/unit.yy b/src/nmodl/parser/unit.yy index 34febc1bdf..03ebf31ed2 100644 --- a/src/nmodl/parser/unit.yy +++ b/src/nmodl/parser/unit.yy @@ -107,7 +107,7 @@ table_insertion try { $1->insert($2); } - catch (std::runtime_error e) { + catch (const std::runtime_error& e) { error(scanner.loc, e.what()); } $$ = $1; From ea426b2993b8322f08b6aafd83873ad964cc097b Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 17 Oct 2023 21:35:57 +0200 Subject: [PATCH 541/871] Remove mol definition in nrnunits.lib (BlueBrain/nmodl#1090) NMODL Repo SHA: BlueBrain/nmodl@420fb2d46306c74ba3ac935d08dd79a3aac81336 --- share/nmodl/nrnunits.lib | 5 ----- 1 file changed, 5 deletions(-) diff --git a/share/nmodl/nrnunits.lib b/share/nmodl/nrnunits.lib index 8fb81e7f56..3abc601834 100755 --- a/share/nmodl/nrnunits.lib +++ b/share/nmodl/nrnunits.lib @@ -69,11 +69,6 @@ c 2.99792458+8 m/sec fuzz g 9.80665 m/sec2 au 1.49597871+11 m fuzz mole 6.02214076+23 fuzz -mol 1 -/ mol is explicitly defined as a constant -/ with value 1 to avoid "undefined unit" -/ error with mod files that don't define -/ it themselves e 1.602176634-19 coul fuzz energy c2 force g From 7902d5b5542414d9cf47fba5292b51d35a0682a5 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 18 Oct 2023 12:34:48 +0200 Subject: [PATCH 542/871] Count properly the prime variables by order in Codegen visitor (BlueBrain/nmodl#1091) NMODL Repo SHA: BlueBrain/nmodl@22d2c966e232d104a2159194ef709cd1666957b6 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index e661f5685b..004477ab5e 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -2598,7 +2598,15 @@ void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initiali } if (info.primes_size != 0) { - if (info.primes_size != info.prime_variables_by_order.size()) { + const auto count_prime_variables = [](auto size, const SymbolType& symbol) { + return size += symbol->get_length(); + }; + const auto prime_variables_by_order_size = + std::accumulate(info.prime_variables_by_order.begin(), + info.prime_variables_by_order.end(), + 0, + count_prime_variables); + if (info.primes_size != prime_variables_by_order_size) { throw std::runtime_error{ fmt::format("primes_size = {} differs from prime_variables_by_order.size() = {}, " "this should not happen.", From 6e7476348b69a4fd221764d9d8202f6aeba45b4a Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 18 Oct 2023 12:36:19 +0200 Subject: [PATCH 543/871] Fix instantiation of array state variables (BlueBrain/nmodl#1081) * Fix instantiation of array STATE variables by adding an unrolled loop for instantiating all their elements with certain value * Added codegen unit test NMODL Repo SHA: BlueBrain/nmodl@c594621da096799f5a3452549033680d3322ba4b --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 8 ++- .../unit/codegen/codegen_cpp_visitor.cpp | 54 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 004477ab5e..a3e9111acb 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -3338,7 +3338,13 @@ void CodegenCppVisitor::print_initial_block(const InitialBlock* node) { if (!info.is_ionic_conc(name)) { auto lhs = get_variable_name(name); auto rhs = get_variable_name(name + "0"); - printer->fmt_line("{} = {};", lhs, rhs); + if (var->is_array()) { + for (int i = 0; i < var->get_length(); ++i) { + printer->fmt_line("{}[{}] = {};", lhs, i, rhs); + } + } else { + printer->fmt_line("{} = {};", lhs, rhs); + } } } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp index 967c5d3267..be30ce24d6 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp @@ -1181,3 +1181,57 @@ SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { } } } + + +SCENARIO("Array STATE variable", "[codegen][array_state]") { + GIVEN("A mod file containing an array STATE variable") { + std::string const nmodl_text = R"( + DEFINE NANN 4 + + NEURON { + SUFFIX ca_test + } + STATE { + ca[NANN] + k + } + )"; + + THEN("nrn_init is printed with proper initialization of the whole array") { + auto const generated = get_cpp_code(nmodl_text); + std::string expected_code_init = + R"(/** initialize channel */ + void nrn_init_ca_test(NrnThread* nt, Memb_list* ml, int type) { + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + const int* node_index = ml->nodeindices; + double* data = ml->data; + const double* voltage = nt->_actual_v; + Datum* indexes = ml->pdata; + ThreadDatum* thread = ml->_thread; + + setup_instance(nt, ml); + auto* const inst = static_cast(ml->instance); + + if (_nrn_skip_initmodel == 0) { + #pragma omp simd + #pragma ivdep + for (int id = 0; id < nodecount; id++) { + int node_id = node_index[id]; + double v = voltage[node_id]; + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + (inst->ca+id*4)[0] = inst->global->ca0; + (inst->ca+id*4)[1] = inst->global->ca0; + (inst->ca+id*4)[2] = inst->global->ca0; + (inst->ca+id*4)[3] = inst->global->ca0; + inst->k[id] = inst->global->k0; + } + } + })"; + + REQUIRE_THAT(generated, ContainsSubstring(expected_code_init)); + } + } +} From 20861b7ab7747b29ab68a85209f9270e363feb25 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Fri, 20 Oct 2023 15:10:33 +0200 Subject: [PATCH 544/871] Bugfix: ASSIGNED variables are not considered for Localizer but STATE were considered (BlueBrain/nmodl#1088) - previously only RANGE and GLOBAL variables were considered for localiser pass - exclude STATE variables as they are not usually a good candidate for such optimisations NMODL Repo SHA: BlueBrain/nmodl@6f6db3b6f25e066db46d8a5fc85a1697363b995c --- src/nmodl/visitors/defuse_analyze_visitor.cpp | 5 +++- src/nmodl/visitors/localize_visitor.cpp | 8 ++++- .../unit/visitor/defuse_analyze.cpp | 29 +++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/nmodl/visitors/defuse_analyze_visitor.cpp b/src/nmodl/visitors/defuse_analyze_visitor.cpp index 7388d090de..859e981e55 100644 --- a/src/nmodl/visitors/defuse_analyze_visitor.cpp +++ b/src/nmodl/visitors/defuse_analyze_visitor.cpp @@ -422,10 +422,13 @@ DUChain DefUseAnalyzeVisitor::analyze(const ast::Ast& node, const std::string& n visiting_lhs = false; current_symtab = global_symtab; unsupported_node = false; + + // variable types that we try to localise: RANGE, GLOBAL and ASSIGNED + auto global_symbol_properties = NmodlType::global_var | NmodlType::range_var | + NmodlType::assigned_definition; auto global_symbol = global_symtab->lookup_in_scope(variable_name); // If global_symbol exists in the global_symtab then search for a global variable. Otherwise the // variable can only be local if it exists - auto global_symbol_properties = NmodlType::global_var | NmodlType::range_var; if (global_symbol != nullptr && global_symbol->has_any_property(global_symbol_properties)) { variable_type = DUVariableType::Global; } else { diff --git a/src/nmodl/visitors/localize_visitor.cpp b/src/nmodl/visitors/localize_visitor.cpp index 0181940b57..a9f71d71d7 100644 --- a/src/nmodl/visitors/localize_visitor.cpp +++ b/src/nmodl/visitors/localize_visitor.cpp @@ -77,7 +77,8 @@ std::vector LocalizeVisitor::variables_to_optimize() const { | NmodlType::nonspecific_cur_var | NmodlType::pointer_var | NmodlType::bbcore_pointer_var - | NmodlType::electrode_cur_var; + | NmodlType::electrode_cur_var + | NmodlType::state_var; const NmodlType global_var_properties = NmodlType::range_var | NmodlType::assigned_definition @@ -112,6 +113,8 @@ void LocalizeVisitor::visit_program(const ast::Program& node) { const auto& blocks = node.get_blocks(); std::map>> block_usage; + logger->debug("LocalizeVisitor: Checking DU chains for {}", varname); + /// compute def use chains for (const auto& block: blocks) { if (node_for_def_use_analysis(*block)) { @@ -119,6 +122,9 @@ void LocalizeVisitor::visit_program(const ast::Program& node) { const auto& usages = v.analyze(*block, varname); auto result = usages.eval(); block_usage[result].push_back(block); + logger->debug("\tDU chain in block {} is {}", + block->get_node_type_name(), + usages.to_string()); } } diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index cf0a36d0eb..4c9a0b64dd 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -445,6 +445,35 @@ SCENARIO("Perform DefUse analysis on NMODL constructs") { } } + GIVEN("Simple check of assigned variables") { + const std::string nmodl_text = R"( + NEURON { + SUFFIX foo + } + + ASSIGNED { + y + } + + DERIVATIVE states { + y = 1 + y = y + 2 + } + )"; + + const std::string expected_text_y = + R"({"DerivativeBlock":[{"name":"D"},{"name":"U"},{"name":"D"}]})"; + + THEN("assigned variables are correctly analyzed") { + const std::string input = reindent_text(nmodl_text); + // Assigned variable "y" should be DU as it's defined and used as well + const auto& chains_y = run_defuse_visitor(input, "y"); + REQUIRE(chains_y[0].to_string() == expected_text_y); + REQUIRE(chains_y[0].eval() == DUState::D); + } + } + + GIVEN("global variable definition in if-else block") { std::string nmodl_text = R"( NEURON { From de2631f140507b4f2df88ac4f749dc724bc88917 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 14 Nov 2023 17:03:50 +0100 Subject: [PATCH 545/871] Update Symbol Table after Inline Visitor (BlueBrain/nmodl#1097) * Update Symbol Table after Inline Visitor which invalidates it for the inlined blocks * Added unit test NMODL Repo SHA: BlueBrain/nmodl@40a3fa712167121a8a4ab3b0fca16c87c583ab70 --- src/nmodl/main.cpp | 1 + src/nmodl/visitors/inline_visitor.cpp | 1 - test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../transpiler/unit/visitor/localrename.cpp | 90 +++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 test/nmodl/transpiler/unit/visitor/localrename.cpp diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 4afd1268c4..87a682b2c0 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -443,6 +443,7 @@ int main(int argc, const char* argv[]) { if (nmodl_inline) { logger->info("Running nmodl inline visitor"); InlineVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); ast_to_nmodl(*ast, filepath("inline")); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 5820ddd2a4..1dd35629f2 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -173,7 +173,6 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, RenameVisitor visitor(function_name, new_varname); inlined_block->visit_children(visitor); - /// \todo Have to re-run symtab visitor pass to update symbol table inlined_block->set_symbol_table(nullptr); /// each argument is added as new assignment statement diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index bf27da5cb4..43da4cfa2c 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -42,6 +42,7 @@ add_executable( visitor/json.cpp visitor/kinetic_block.cpp visitor/localize.cpp + visitor/localrename.cpp visitor/local_to_assigned.cpp visitor/lookup.cpp visitor/loop_unroll.cpp diff --git a/test/nmodl/transpiler/unit/visitor/localrename.cpp b/test/nmodl/transpiler/unit/visitor/localrename.cpp new file mode 100644 index 0000000000..92ab2054ee --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/localrename.cpp @@ -0,0 +1,90 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/local_var_rename_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + + +using namespace nmodl; +using namespace visitor; +using namespace test; +using namespace test_utils; + +using Catch::Matchers::Equals; +using nmodl::parser::NmodlDriver; + +//============================================================================= +// Procedure/Function inlining tests +//============================================================================= + +std::string run_inline_localvarrename_visitor(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + + SymtabVisitor().visit_program(*ast); + InlineVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); + LocalVarRenameVisitor().visit_program(*ast); + SymtabVisitor().visit_program(*ast); + std::stringstream stream; + NmodlPrintVisitor(stream).visit_program(*ast); + + + // check that, after visitor rearrangement, parents are still up-to-date + CheckParentVisitor().check_ast(*ast); + + return stream.str(); +} + +SCENARIO("LocalVarRenameVisitor works with InlineVisitor", "[visitor][localvarrename]") { + GIVEN("A FUNCTION that gets inlined with a LOCAL variable") { + std::string nmodl_text = R"( + FUNCTION rates_1(Vm (mV)) { + LOCAL v + v = Vm + 5 + rates_1 = v + } + + FUNCTION rates_2(Vm (mV)) { + rates_2 = rates_1(Vm) + } + )"; + std::string output_nmodl = R"( + FUNCTION rates_1(Vm(mV)) { + LOCAL v_r_0 + v_r_0 = Vm+5 + rates_1 = v_r_0 + } + + FUNCTION rates_2(Vm(mV)) { + LOCAL rates_1_in_0 + { + LOCAL v_r_1, Vm_in_0 + Vm_in_0 = Vm + v_r_1 = Vm_in_0+5 + rates_1_in_0 = v_r_1 + } + rates_2 = rates_1_in_0 + } + )"; + THEN("LOCAL variable in inlined function gets renamed") { + std::string input = reindent_text(nmodl_text); + auto expected_result = reindent_text(output_nmodl); + auto result = run_inline_localvarrename_visitor(input); + REQUIRE(result == expected_result); + } + } +} From 555069994edbf071fe371ca61e7e2dbb6063d1d1 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 16 Nov 2023 16:56:44 +0100 Subject: [PATCH 546/871] SemanticAnalysisVisitor: detect if several solve blocks are given (BlueBrain/nmodl#1094) This fixes BlueBrain/nmodl#1092 NMODL Repo SHA: BlueBrain/nmodl@f57d0d75dffc92aa2c5cbaf00672ef485721b9c2 --- .../visitors/semantic_analysis_visitor.cpp | 21 ++++++++++ .../visitors/semantic_analysis_visitor.hpp | 8 ++++ .../unit/visitor/semantic_analysis.cpp | 38 +++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 40d6b1667b..365fac7ad1 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -6,6 +6,7 @@ */ #include "visitors/semantic_analysis_visitor.hpp" +#include "ast/breakpoint_block.hpp" #include "ast/function_block.hpp" #include "ast/function_table_block.hpp" #include "ast/independent_block.hpp" @@ -170,5 +171,25 @@ void SemanticAnalysisVisitor::visit_mutex_unlock(const ast::MutexUnlock& /* node /// --> } +void SemanticAnalysisVisitor::visit_breakpoint_block(const ast::BreakpointBlock& node) { + /// <-- This code is for check 8 + solve_block_found_in_this_breakpoint_block = false; + node.visit_children(*this); + solve_block_found_in_this_breakpoint_block = false; + /// --> +} + +void SemanticAnalysisVisitor::visit_solve_block(const ast::SolveBlock& /* node */) { + /// <-- This code is for check 8 + if (solve_block_found_in_this_breakpoint_block) { + logger->critical( + "It is not allowed to have several solve blocks in the same breakpoint block"); + check_fail = true; + } else { + solve_block_found_in_this_breakpoint_block = true; + } + /// --> +} + } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 1deffcb154..f51b10dfed 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -31,6 +31,7 @@ * 5. Check if an independent variable is not 't'. * 6. Check that mutex are not badly use * 7. Check than function table got at least one argument. + * 8. Check that at most one solve block is present per breakpoint block. */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -54,6 +55,8 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { bool is_point_process = false; /// true if we are inside a mutex locked part bool in_mutex = false; + /// true if we already found a solve block + bool solve_block_found_in_this_breakpoint_block = false; /// Store if we are in a procedure and if the arity of this is 1 void visit_procedure_block(const ast::ProcedureBlock& node) override; @@ -82,6 +85,11 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// Look if MUTEXUNLOCK is outside a locked block void visit_mutex_unlock(const ast::MutexUnlock& node) override; + void visit_breakpoint_block(const ast::BreakpointBlock& node) override; + + /// Check how many solve block we got + void visit_solve_block(const ast::SolveBlock& node) override; + public: SemanticAnalysisVisitor(bool accel_backend = false) : accel_backend(accel_backend) {} diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 23c58d2666..0d51aeb758 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -165,3 +165,41 @@ SCENARIO("FUNCTION_TABLE block", "[visitor][semantic_analysis]") { } } } + + +SCENARIO("At most one solve block per breakpoint block", "[visitor][semantic_analysis]") { + GIVEN("A breakpoint block with only one solve block") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE dX METHOD cnexp + } + )"; + THEN("Semantic analysis should success") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("2 breakpoints block with one solve block each") { + std::string nmodl_text = R"( + PROCEDURE foo() { + SOLVE dX METHOD cnexp + } + BREAKPOINT { + SOLVE dY METHOD cnexp + } + )"; + THEN("Semantic analysis should success") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("A breakpoint block with two solve blocks") { + std::string nmodl_text = R"( + BREAKPOINT { + SOLVE dX METHOD cnexp + SOLVE dY METHOD cnexp + } + )"; + THEN("Semantic analysis should fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From 31a20bf9165a17c11d2723cdded20eb3464dff99 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 22 Nov 2023 08:34:22 +0100 Subject: [PATCH 547/871] Semantic: check there is at most one DERIVATIVE block (BlueBrain/nmodl#1098) This is not forbidden to have more, but due to a bug this fail in nrn. Disable shalow clone as git describe --tags might fail with > fatal: No names found, cannot describe anything. --------- Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@6c89dc639c1ee59e8cfdd69719780e0185f1ec08 --- .../visitors/semantic_analysis_visitor.cpp | 31 +++++++------------ .../visitors/semantic_analysis_visitor.hpp | 12 +++---- .../unit/visitor/semantic_analysis.cpp | 31 ++++++------------- 3 files changed, 25 insertions(+), 49 deletions(-) diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 365fac7ad1..22220f3b8d 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -60,6 +60,17 @@ bool SemanticAnalysisVisitor::check(const ast::Program& node) { return check_fail; } +void SemanticAnalysisVisitor::visit_program(const ast::Program& node) { + /// <-- This code is for check 8 + const auto& derivative_block_nodes = collect_nodes(node, {ast::AstNodeType::DERIVATIVE_BLOCK}); + if (derivative_block_nodes.size() > 1) { + logger->critical("It is not supported to have several DERIVATIVE blocks"); + check_fail = true; + } + /// --> + node.visit_children(*this); +} + void SemanticAnalysisVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { /// <-- This code is for check 1 in_procedure = true; @@ -171,25 +182,5 @@ void SemanticAnalysisVisitor::visit_mutex_unlock(const ast::MutexUnlock& /* node /// --> } -void SemanticAnalysisVisitor::visit_breakpoint_block(const ast::BreakpointBlock& node) { - /// <-- This code is for check 8 - solve_block_found_in_this_breakpoint_block = false; - node.visit_children(*this); - solve_block_found_in_this_breakpoint_block = false; - /// --> -} - -void SemanticAnalysisVisitor::visit_solve_block(const ast::SolveBlock& /* node */) { - /// <-- This code is for check 8 - if (solve_block_found_in_this_breakpoint_block) { - logger->critical( - "It is not allowed to have several solve blocks in the same breakpoint block"); - check_fail = true; - } else { - solve_block_found_in_this_breakpoint_block = true; - } - /// --> -} - } // namespace visitor } // namespace nmodl diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index f51b10dfed..1f9c2b7e98 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -31,7 +31,7 @@ * 5. Check if an independent variable is not 't'. * 6. Check that mutex are not badly use * 7. Check than function table got at least one argument. - * 8. Check that at most one solve block is present per breakpoint block. + * 8. Check that at most one derivative block is present. */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -55,8 +55,9 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { bool is_point_process = false; /// true if we are inside a mutex locked part bool in_mutex = false; - /// true if we already found a solve block - bool solve_block_found_in_this_breakpoint_block = false; + + /// Check number of DERIVATIVE blocks + void visit_program(const ast::Program& node) override; /// Store if we are in a procedure and if the arity of this is 1 void visit_procedure_block(const ast::ProcedureBlock& node) override; @@ -85,11 +86,6 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// Look if MUTEXUNLOCK is outside a locked block void visit_mutex_unlock(const ast::MutexUnlock& node) override; - void visit_breakpoint_block(const ast::BreakpointBlock& node) override; - - /// Check how many solve block we got - void visit_solve_block(const ast::SolveBlock& node) override; - public: SemanticAnalysisVisitor(bool accel_backend = false) : accel_backend(accel_backend) {} diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 0d51aeb758..df3a6f4452 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -167,38 +167,27 @@ SCENARIO("FUNCTION_TABLE block", "[visitor][semantic_analysis]") { } -SCENARIO("At most one solve block per breakpoint block", "[visitor][semantic_analysis]") { - GIVEN("A breakpoint block with only one solve block") { +SCENARIO("At most one DERIVATIVE block", "[visitor][semantic_analysis]") { + GIVEN("Only one DERIVATIVE block") { std::string nmodl_text = R"( - BREAKPOINT { - SOLVE dX METHOD cnexp + DERIVATIVE states { + m' = m/mTau } )"; THEN("Semantic analysis should success") { REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); } } - GIVEN("2 breakpoints block with one solve block each") { + GIVEN("2 DERIVATIVE blocks") { std::string nmodl_text = R"( - PROCEDURE foo() { - SOLVE dX METHOD cnexp + DERIVATIVE states1 { + m' = m/mTau } - BREAKPOINT { - SOLVE dY METHOD cnexp + DERIVATIVE states2 { + h' = h/hTau } )"; - THEN("Semantic analysis should success") { - REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); - } - } - GIVEN("A breakpoint block with two solve blocks") { - std::string nmodl_text = R"( - BREAKPOINT { - SOLVE dX METHOD cnexp - SOLVE dY METHOD cnexp - } - )"; - THEN("Semantic analysis should fail") { + THEN("Semantic analysis should failed") { REQUIRE(run_semantic_analysis_visitor(nmodl_text)); } } From cd8aeaac401021a426ebce4dc21469ca8e8bbe15 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 29 Nov 2023 12:08:22 +0100 Subject: [PATCH 548/871] Restrict docutils version to fix documenation CI (BlueBrain/nmodl#1100) * Installed `sphinx` in the CI needs `docutils<0.20` NMODL Repo SHA: BlueBrain/nmodl@11ec824977090e2e6a728c7cffdcd26c44ea0a79 --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index b3a9b66658..5059f51502 100644 --- a/setup.py +++ b/setup.py @@ -127,6 +127,7 @@ def run(self, *args, **kwargs): "sphinxcontrib-htmlhelp<=2.0.0", # After this version it needs a toml file to work, no more setup.py "sphinx<6", "sphinx-rtd-theme", # needs sphinx < 7 + "docutils<0.20", # needed by sphinx ] + install_requirements, install_requires=install_requirements, From a859e78b463b103ec812bd56d769bd900c7a5803 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Wed, 29 Nov 2023 14:24:57 +0100 Subject: [PATCH 549/871] Initial code generation for NEURON (BlueBrain/nmodl#1078) * Renamed CodegenCppVisitor to CodegenCoreneuronCppVisitor * Added NEURON code generator (CodegenNeuronCppVisitor) with comments and scaffold of the needed functions * Created abstract class CodegenCppVisitor for common functionality between CodegenNeuronCppVisitor and CodegenCoreneuronCppVisitor * Rearrange functions in CodegenCoreneuronCppVisitor to match CodegenNeuronCppVisitor and CodegenCppVisitor * Added unit test for the code generated for NEURON at the moment * Introduce CLI options for NEURON code generation and removed the references to C NMODL Repo SHA: BlueBrain/nmodl@2c4c8e255ed1afa71dafdc36ce4c9bce2716cbed --- src/nmodl/codegen/CMakeLists.txt | 2 + src/nmodl/codegen/codegen_acc_visitor.cpp | 2 +- src/nmodl/codegen/codegen_acc_visitor.hpp | 8 +- .../codegen_coreneuron_cpp_visitor.cpp | 3900 ++++++++++++++ .../codegen_coreneuron_cpp_visitor.hpp | 1345 +++++ src/nmodl/codegen/codegen_cpp_visitor.cpp | 4675 ++--------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 1452 ++--- src/nmodl/codegen/codegen_info.hpp | 4 + .../codegen/codegen_neuron_cpp_visitor.cpp | 687 +++ .../codegen/codegen_neuron_cpp_visitor.hpp | 643 +++ src/nmodl/main.cpp | 51 +- src/nmodl/utils/common_utils.hpp | 2 +- test/nmodl/transpiler/unit/CMakeLists.txt | 9 +- ...cpp => codegen_coreneuron_cpp_visitor.cpp} | 58 +- .../codegen/codegen_neuron_cpp_visitor.cpp | 255 + .../transpiler/unit/visitor/sympy_solver.cpp | 21 +- 16 files changed, 7733 insertions(+), 5381 deletions(-) create mode 100644 src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp create mode 100644 src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp create mode 100644 src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp create mode 100644 src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp rename test/nmodl/transpiler/unit/codegen/{codegen_cpp_visitor.cpp => codegen_coreneuron_cpp_visitor.cpp} (95%) create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp diff --git a/src/nmodl/codegen/CMakeLists.txt b/src/nmodl/codegen/CMakeLists.txt index f77e34e34e..bbafaf44dd 100644 --- a/src/nmodl/codegen/CMakeLists.txt +++ b/src/nmodl/codegen/CMakeLists.txt @@ -5,6 +5,8 @@ add_library( codegen STATIC codegen_acc_visitor.cpp codegen_transform_visitor.cpp + codegen_coreneuron_cpp_visitor.cpp + codegen_neuron_cpp_visitor.cpp codegen_cpp_visitor.cpp codegen_compatibility_visitor.cpp codegen_helper_visitor.cpp diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 03a7589ff7..68ff009471 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -84,7 +84,7 @@ std::string CodegenAccVisitor::backend_name() const { void CodegenAccVisitor::print_memory_allocation_routine() const { // memory for artificial cells should be allocated on CPU if (info.artificial_cell) { - CodegenCppVisitor::print_memory_allocation_routine(); + CodegenCoreneuronCppVisitor::print_memory_allocation_routine(); return; } printer->add_newline(2); diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 1a3f60503f..80bcba9d68 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -12,7 +12,7 @@ * \brief \copybrief nmodl::codegen::CodegenAccVisitor */ -#include "codegen/codegen_cpp_visitor.hpp" +#include "codegen/codegen_coreneuron_cpp_visitor.hpp" namespace nmodl { @@ -27,7 +27,7 @@ namespace codegen { * \class CodegenAccVisitor * \brief %Visitor for printing C++ code with OpenACC backend */ -class CodegenAccVisitor: public CodegenCppVisitor { +class CodegenAccVisitor: public CodegenCoreneuronCppVisitor { protected: /// name of the code generation backend std::string backend_name() const override; @@ -140,13 +140,13 @@ class CodegenAccVisitor: public CodegenCppVisitor { const std::string& output_dir, const std::string& float_type, bool optimize_ionvar_copies) - : CodegenCppVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} + : CodegenCoreneuronCppVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} CodegenAccVisitor(const std::string& mod_file, std::ostream& stream, const std::string& float_type, bool optimize_ionvar_copies) - : CodegenCppVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} + : CodegenCoreneuronCppVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; /** \} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp new file mode 100644 index 0000000000..177b0b50c1 --- /dev/null +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -0,0 +1,3900 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "codegen/codegen_coreneuron_cpp_visitor.hpp" + +#include +#include +#include +#include +#include + +#include "ast/all.hpp" +#include "codegen/codegen_helper_visitor.hpp" +#include "codegen/codegen_naming.hpp" +#include "codegen/codegen_utils.hpp" +#include "config/config.h" +#include "lexer/token_mapping.hpp" +#include "parser/c11_driver.hpp" +#include "utils/logger.hpp" +#include "utils/string_utils.hpp" +#include "visitors/defuse_analyze_visitor.hpp" +#include "visitors/rename_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/var_usage_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace codegen { + +using namespace ast; + +using visitor::DefUseAnalyzeVisitor; +using visitor::DUState; +using visitor::RenameVisitor; +using visitor::SymtabVisitor; +using visitor::VarUsageVisitor; + +using symtab::syminfo::NmodlType; + +extern const std::regex regex_special_chars; + +/****************************************************************************************/ +/* Generic information getters */ +/****************************************************************************************/ + + +std::string CodegenCoreneuronCppVisitor::backend_name() const { + return "C++ (api-compatibility)"; +} + + +std::string CodegenCoreneuronCppVisitor::simulator_name() { + return "CoreNEURON"; +} + + +/****************************************************************************************/ +/* Common helper routines accross codegen functions */ +/****************************************************************************************/ + + +int CodegenCoreneuronCppVisitor::position_of_float_var(const std::string& name) const { + int index = 0; + for (const auto& var: codegen_float_variables) { + if (var->get_name() == name) { + return index; + } + index += var->get_length(); + } + throw std::logic_error(name + " variable not found"); +} + + +int CodegenCoreneuronCppVisitor::position_of_int_var(const std::string& name) const { + int index = 0; + for (const auto& var: codegen_int_variables) { + if (var.symbol->get_name() == name) { + return index; + } + index += var.symbol->get_length(); + } + throw std::logic_error(name + " variable not found"); +} + + +/** + * \details Current variable used in breakpoint block could be local variable. + * In this case, neuron has already renamed the variable name by prepending + * "_l". In our implementation, the variable could have been renamed by + * one of the pass. And hence, we search all local variables and check if + * the variable is renamed. Note that we have to look into the symbol table + * of statement block and not breakpoint. + */ +std::string CodegenCoreneuronCppVisitor::breakpoint_current(std::string current) const { + auto breakpoint = info.breakpoint_node; + if (breakpoint == nullptr) { + return current; + } + auto symtab = breakpoint->get_statement_block()->get_symbol_table(); + auto variables = symtab->get_variables_with_properties(NmodlType::local_var); + for (const auto& var: variables) { + auto renamed_name = var->get_name(); + auto original_name = var->get_original_name(); + if (current == original_name) { + current = renamed_name; + break; + } + } + return current; +} + + +/** + * \details Depending upon the block type, we have to print read/write ion variables + * during code generation. Depending on block/procedure being printed, this + * method return statements as vector. As different code backends could have + * different variable names, we rely on backend-specific read_ion_variable_name + * and write_ion_variable_name method which will be overloaded. + */ +std::vector CodegenCoreneuronCppVisitor::ion_read_statements(BlockType type) const { + if (optimize_ion_variable_copies()) { + return ion_read_statements_optimized(type); + } + std::vector statements; + for (const auto& ion: info.ions) { + auto name = ion.name; + for (const auto& var: ion.reads) { + auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var); + if (iter != ion.implicit_reads.end()) { + continue; + } + auto variable_names = read_ion_variable_name(var); + auto first = get_variable_name(variable_names.first); + auto second = get_variable_name(variable_names.second); + statements.push_back(fmt::format("{} = {};", first, second)); + } + for (const auto& var: ion.writes) { + if (ion.is_ionic_conc(var)) { + auto variables = read_ion_variable_name(var); + auto first = get_variable_name(variables.first); + auto second = get_variable_name(variables.second); + statements.push_back(fmt::format("{} = {};", first, second)); + } + } + } + return statements; +} + + +std::vector CodegenCoreneuronCppVisitor::ion_read_statements_optimized( + BlockType type) const { + std::vector statements; + for (const auto& ion: info.ions) { + for (const auto& var: ion.writes) { + if (ion.is_ionic_conc(var)) { + auto variables = read_ion_variable_name(var); + auto first = "ionvar." + variables.first; + const auto& second = get_variable_name(variables.second); + statements.push_back(fmt::format("{} = {};", first, second)); + } + } + } + return statements; +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +std::vector CodegenCoreneuronCppVisitor::ion_write_statements(BlockType type) { + std::vector statements; + for (const auto& ion: info.ions) { + std::string concentration; + auto name = ion.name; + for (const auto& var: ion.writes) { + auto variable_names = write_ion_variable_name(var); + if (ion.is_ionic_current(var)) { + if (type == BlockType::Equation) { + auto current = breakpoint_current(var); + auto lhs = variable_names.first; + auto op = "+="; + auto rhs = get_variable_name(current); + if (info.point_process) { + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); + rhs += fmt::format("*(1.e2/{})", area); + } + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + } + } else { + if (!ion.is_rev_potential(var)) { + concentration = var; + } + auto lhs = variable_names.first; + auto op = "="; + auto rhs = get_variable_name(variable_names.second); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + } + } + + if (type == BlockType::Initial && !concentration.empty()) { + int index = 0; + if (ion.is_intra_cell_conc(concentration)) { + index = 1; + } else if (ion.is_extra_cell_conc(concentration)) { + index = 2; + } else { + /// \todo Unhandled case in neuron implementation + throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); + } + auto ion_type_name = fmt::format("{}_type", ion.name); + auto lhs = fmt::format("int {}", ion_type_name); + auto op = "="; + auto rhs = get_variable_name(ion_type_name); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + auto statement = conc_write_statement(ion.name, concentration, index); + statements.push_back(ShadowUseStatement{statement, "", ""}); + } + } + return statements; +} + + +/** + * \details Often top level verbatim blocks use variables with old names. + * Here we process if we are processing verbatim block at global scope. + */ +std::string CodegenCoreneuronCppVisitor::process_verbatim_token(const std::string& token) { + const std::string& name = token; + + /* + * If given token is procedure name and if it's defined + * in the current mod file then it must be replaced + */ + if (program_symtab->is_method_defined(token)) { + return method_name(token); + } + + /* + * Check if token is commongly used variable name in + * verbatim block like nt, \c \_threadargs etc. If so, replace + * it and return. + */ + auto new_name = replace_if_verbatim_variable(name); + if (new_name != name) { + return get_variable_name(new_name, false); + } + + /* + * For top level verbatim blocks we shouldn't replace variable + * names with Instance because arguments are provided from coreneuron + * and they are missing inst. + */ + auto use_instance = !printing_top_verbatim_blocks; + return get_variable_name(token, use_instance); +} + + +bool CodegenCoreneuronCppVisitor::ion_variable_struct_required() const { + return optimize_ion_variable_copies() && info.ion_has_write_variable(); +} + + +/** + * \details This can be override in the backend. For example, parameters can be constant + * except in INITIAL block where they are set to 0. As initial block is/can be + * executed on c++/cpu backend, gpu backend can mark the parameter as constant. + */ +bool CodegenCoreneuronCppVisitor::is_constant_variable(const std::string& name) const { + auto symbol = program_symtab->lookup_in_scope(name); + bool is_constant = false; + if (symbol != nullptr) { + // per mechanism ion variables needs to be updated from neuron/coreneuron values + if (info.is_ion_variable(name)) { + is_constant = false; + } + // for parameter variable to be const, make sure it's write count is 0 + // and it's not used in the verbatim block + else if (symbol->has_any_property(NmodlType::param_assign) && + info.variables_in_verbatim.find(name) == info.variables_in_verbatim.end() && + symbol->get_write_count() == 0) { + is_constant = true; + } + } + return is_constant; +} + + +/****************************************************************************************/ +/* Backend specific routines */ +/****************************************************************************************/ + +std::string CodegenCoreneuronCppVisitor::get_parameter_str(const ParamVector& params) { + std::string str; + bool is_first = true; + for (const auto& param: params) { + if (is_first) { + is_first = false; + } else { + str += ", "; + } + str += fmt::format("{}{} {}{}", + std::get<0>(param), + std::get<1>(param), + std::get<2>(param), + std::get<3>(param)); + } + return str; +} + + +void CodegenCoreneuronCppVisitor::print_deriv_advance_flag_transfer_to_device() const { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_device_atomic_capture_annotation() const { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_net_send_buf_count_update_to_host() const { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_net_send_buf_update_to_host() const { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_net_send_buf_count_update_to_device() const { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_dt_update_to_device() const { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_device_stream_wait() const { + // backend specific, do nothing +} + + +/** + * \details Each kernel such as \c nrn\_init, \c nrn\_state and \c nrn\_cur could be offloaded + * to accelerator. In this case, at very top level, we print pragma + * for data present. For example: + * + * \code{.cpp} + * void nrn_state(...) { + * #pragma acc data present (nt, ml...) + * { + * + * } + * } + * \endcode + */ +void CodegenCoreneuronCppVisitor::print_kernel_data_present_annotation_block_begin() { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_kernel_data_present_annotation_block_end() { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_net_init_acc_serial_annotation_block_begin() { + // backend specific, do nothing +} + + +void CodegenCoreneuronCppVisitor::print_net_init_acc_serial_annotation_block_end() { + // backend specific, do nothing +} + + +/** + * \details Depending programming model and compiler, we print compiler hint + * for parallelization. For example: + * + * \code + * #pragma omp simd + * for(int id = 0; id < nodecount; id++) { + * + * #pragma acc parallel loop + * for(int id = 0; id < nodecount; id++) { + * \endcode + */ +void CodegenCoreneuronCppVisitor::print_channel_iteration_block_parallel_hint( + BlockType /* type */, + const ast::Block* block) { + // ivdep allows SIMD parallelisation of a block/loop but doesn't provide + // a standard mechanism for atomics. Also, even with openmp 5.0, openmp + // atomics do not enable vectorisation under "omp simd" (gives compiler + // error with gcc < 9 if atomic and simd pragmas are nested). So, emit + // ivdep/simd pragma when no MUTEXLOCK/MUTEXUNLOCK/PROTECT statements + // are used in the given block. + std::vector> nodes; + if (block) { + nodes = collect_nodes(*block, + {ast::AstNodeType::PROTECT_STATEMENT, + ast::AstNodeType::MUTEX_LOCK, + ast::AstNodeType::MUTEX_UNLOCK}); + } + if (nodes.empty()) { + printer->add_line("#pragma omp simd"); + printer->add_line("#pragma ivdep"); + } +} + + +bool CodegenCoreneuronCppVisitor::nrn_cur_reduction_loop_required() { + return info.point_process; +} + + +void CodegenCoreneuronCppVisitor::print_rhs_d_shadow_variables() { + if (info.point_process) { + printer->fmt_line("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); + printer->fmt_line("double* shadow_d = nt->{};", naming::NTHREAD_D_SHADOW); + } +} + + +void CodegenCoreneuronCppVisitor::print_nrn_cur_matrix_shadow_update() { + if (info.point_process) { + printer->add_line("shadow_rhs[id] = rhs;"); + printer->add_line("shadow_d[id] = g;"); + } else { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + printer->fmt_line("vec_rhs[node_id] {} rhs;", rhs_op); + printer->fmt_line("vec_d[node_id] {} g;", d_op); + } +} + + +void CodegenCoreneuronCppVisitor::print_nrn_cur_matrix_shadow_reduction() { + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + if (info.point_process) { + printer->add_line("int node_id = node_index[id];"); + printer->fmt_line("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op); + printer->fmt_line("vec_d[node_id] {} shadow_d[id];", d_op); + } +} + + +/** + * In the current implementation of CPU/CPP backend we need to emit atomic pragma + * only with PROTECT construct (atomic rduction requirement for other cases on CPU + * is handled via separate shadow vectors). + */ +void CodegenCoreneuronCppVisitor::print_atomic_reduction_pragma() { + printer->add_line("#pragma omp atomic update"); +} + + +void CodegenCoreneuronCppVisitor::print_device_method_annotation() { + // backend specific, nothing for cpu +} + + +void CodegenCoreneuronCppVisitor::print_global_method_annotation() { + // backend specific, nothing for cpu +} + + +void CodegenCoreneuronCppVisitor::print_backend_namespace_start() { + // no separate namespace for C++ (cpu) backend +} + + +void CodegenCoreneuronCppVisitor::print_backend_namespace_stop() { + // no separate namespace for C++ (cpu) backend +} + + +void CodegenCoreneuronCppVisitor::print_backend_includes() { + // backend specific, nothing for cpu +} + + +bool CodegenCoreneuronCppVisitor::optimize_ion_variable_copies() const { + return optimize_ionvar_copies; +} + + +void CodegenCoreneuronCppVisitor::print_memory_allocation_routine() const { + printer->add_newline(2); + auto args = "size_t num, size_t size, size_t alignment = 16"; + printer->fmt_push_block("static inline void* mem_alloc({})", args); + printer->add_line("void* ptr;"); + printer->add_line("posix_memalign(&ptr, alignment, num*size);"); + printer->add_line("memset(ptr, 0, size);"); + printer->add_line("return ptr;"); + printer->pop_block(); + + printer->add_newline(2); + printer->push_block("static inline void mem_free(void* ptr)"); + printer->add_line("free(ptr);"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_abort_routine() const { + printer->add_newline(2); + printer->push_block("static inline void coreneuron_abort()"); + printer->add_line("abort();"); + printer->pop_block(); +} + + +std::string CodegenCoreneuronCppVisitor::compute_method_name(BlockType type) const { + if (type == BlockType::Initial) { + return method_name(naming::NRN_INIT_METHOD); + } + if (type == BlockType::Constructor) { + return method_name(naming::NRN_CONSTRUCTOR_METHOD); + } + if (type == BlockType::Destructor) { + return method_name(naming::NRN_DESTRUCTOR_METHOD); + } + if (type == BlockType::State) { + return method_name(naming::NRN_STATE_METHOD); + } + if (type == BlockType::Equation) { + return method_name(naming::NRN_CUR_METHOD); + } + if (type == BlockType::Watch) { + return method_name(naming::NRN_WATCH_CHECK_METHOD); + } + throw std::logic_error("compute_method_name not implemented"); +} + + +void CodegenCoreneuronCppVisitor::print_global_var_struct_decl() { + printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); +} + + +/****************************************************************************************/ +/* Printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenCoreneuronCppVisitor::print_function_call(const FunctionCall& node) { + const auto& name = node.get_node_name(); + auto function_name = name; + if (defined_method(name)) { + function_name = method_name(name); + } + + if (is_net_send(name)) { + print_net_send_call(node); + return; + } + + if (is_net_move(name)) { + print_net_move_call(node); + return; + } + + if (is_net_event(name)) { + print_net_event_call(node); + return; + } + + const auto& arguments = node.get_arguments(); + printer->add_text(function_name, '('); + + if (defined_method(name)) { + printer->add_text(internal_method_arguments()); + if (!arguments.empty()) { + printer->add_text(", "); + } + } + + print_vector_elements(arguments, ", "); + printer->add_text(')'); +} + + +void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { + if (info.top_verbatim_blocks.empty()) { + return; + } + print_namespace_stop(); + + printer->add_newline(2); + printer->add_line("using namespace coreneuron;"); + codegen = true; + printing_top_verbatim_blocks = true; + + for (const auto& block: info.top_blocks) { + if (block->is_verbatim()) { + printer->add_newline(2); + block->accept(*this); + } + } + + printing_top_verbatim_blocks = false; + codegen = false; + print_namespace_start(); +} + + +void CodegenCoreneuronCppVisitor::print_function_prototypes() { + if (info.functions.empty() && info.procedures.empty()) { + return; + } + codegen = true; + printer->add_newline(2); + for (const auto& node: info.functions) { + print_function_declaration(*node, node->get_node_name()); + printer->add_text(';'); + printer->add_newline(); + } + for (const auto& node: info.procedures) { + print_function_declaration(*node, node->get_node_name()); + printer->add_text(';'); + printer->add_newline(); + } + codegen = false; +} + + +static const TableStatement* get_table_statement(const ast::Block& node) { + // TableStatementVisitor v; + + const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); + + if (table_statements.size() != 1) { + auto message = fmt::format("One table statement expected in {} found {}", + node.get_node_name(), + table_statements.size()); + throw std::runtime_error(message); + } + return dynamic_cast(table_statements.front().get()); +} + + +std::tuple CodegenCoreneuronCppVisitor::check_if_var_is_array(const std::string& name) { + auto symbol = program_symtab->lookup_in_scope(name); + if (!symbol) { + throw std::runtime_error( + fmt::format("CodegenCoreneuronCppVisitor:: {} not found in symbol table!", name)); + } + if (symbol->is_array()) { + return {true, symbol->get_length()}; + } else { + return {false, 0}; + } +} + + +void CodegenCoreneuronCppVisitor::print_table_check_function(const Block& node) { + auto statement = get_table_statement(node); + auto table_variables = statement->get_table_vars(); + auto depend_variables = statement->get_depend_vars(); + const auto& from = statement->get_from(); + const auto& to = statement->get_to(); + auto name = node.get_node_name(); + auto internal_params = internal_method_parameters(); + auto with = statement->get_with()->eval(); + auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); + auto tmin_name = get_variable_name("tmin_" + name); + auto mfac_name = get_variable_name("mfac_" + name); + auto float_type = default_float_data_type(); + + printer->add_newline(2); + print_device_method_annotation(); + printer->fmt_push_block("void check_{}({})", + method_name(name), + get_parameter_str(internal_params)); + { + printer->fmt_push_block("if ({} == 0)", use_table_var); + printer->add_line("return;"); + printer->pop_block(); + + printer->add_line("static bool make_table = true;"); + for (const auto& variable: depend_variables) { + printer->fmt_line("static {} save_{};", float_type, variable->get_node_name()); + } + + for (const auto& variable: depend_variables) { + const auto& var_name = variable->get_node_name(); + const auto& instance_name = get_variable_name(var_name); + printer->fmt_push_block("if (save_{} != {})", var_name, instance_name); + printer->add_line("make_table = true;"); + printer->pop_block(); + } + + printer->push_block("if (make_table)"); + { + printer->add_line("make_table = false;"); + + printer->add_indent(); + printer->add_text(tmin_name, " = "); + from->accept(*this); + printer->add_text(';'); + printer->add_newline(); + + printer->add_indent(); + printer->add_text("double tmax = "); + to->accept(*this); + printer->add_text(';'); + printer->add_newline(); + + + printer->fmt_line("double dx = (tmax-{}) / {}.;", tmin_name, with); + printer->fmt_line("{} = 1./dx;", mfac_name); + + printer->fmt_line("double x = {};", tmin_name); + printer->fmt_push_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); + auto function = method_name("f_" + name); + if (node.is_procedure_block()) { + printer->fmt_line("{}({}, x);", function, internal_method_arguments()); + for (const auto& variable: table_variables) { + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var_name); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line( + "{}[{}][i] = {}[{}];", table_name, j, instance_name, j); + } + } else { + printer->fmt_line("{}[i] = {};", table_name, instance_name); + } + } + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("{}[i] = {}({}, x);", + table_name, + function, + internal_method_arguments()); + } + printer->pop_block(); + + for (const auto& variable: depend_variables) { + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + printer->fmt_line("save_{} = {};", var_name, instance_name); + } + } + printer->pop_block(); + } + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_table_replacement_function(const ast::Block& node) { + auto name = node.get_node_name(); + auto statement = get_table_statement(node); + auto table_variables = statement->get_table_vars(); + auto with = statement->get_with()->eval(); + auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); + auto tmin_name = get_variable_name("tmin_" + name); + auto mfac_name = get_variable_name("mfac_" + name); + auto function_name = method_name("f_" + name); + + printer->add_newline(2); + print_function_declaration(node, name); + printer->push_block(); + { + const auto& params = node.get_parameters(); + printer->fmt_push_block("if ({} == 0)", use_table_var); + if (node.is_procedure_block()) { + printer->fmt_line("{}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name()); + printer->add_line("return 0;"); + } else { + printer->fmt_line("return {}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name()); + } + printer->pop_block(); + + printer->fmt_line("double xi = {} * ({} - {});", + mfac_name, + params[0].get()->get_node_name(), + tmin_name); + printer->push_block("if (isnan(xi))"); + if (node.is_procedure_block()) { + for (const auto& var: table_variables) { + auto var_name = get_variable_name(var->get_node_name()); + auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line("{}[{}] = xi;", var_name, j); + } + } else { + printer->fmt_line("{} = xi;", var_name); + } + } + printer->add_line("return 0;"); + } else { + printer->add_line("return xi;"); + } + printer->pop_block(); + + printer->fmt_push_block("if (xi <= 0. || xi >= {}.)", with); + printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); + if (node.is_procedure_block()) { + for (const auto& variable: table_variables) { + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var_name); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line( + "{}[{}] = {}[{}][index];", instance_name, j, table_name, j); + } + } else { + printer->fmt_line("{} = {}[index];", instance_name, table_name); + } + } + printer->add_line("return 0;"); + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("return {}[index];", table_name); + } + printer->pop_block(); + + printer->add_line("int i = int(xi);"); + printer->add_line("double theta = xi - double(i);"); + if (node.is_procedure_block()) { + for (const auto& var: table_variables) { + auto var_name = var->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); + if (is_array) { + for (size_t j = 0; j < array_length; j++) { + printer->fmt_line( + "{0}[{1}] = {2}[{1}][i] + theta*({2}[{1}][i+1]-{2}[{1}][i]);", + instance_name, + j, + table_name); + } + } else { + printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", + instance_name, + table_name); + } + } + printer->add_line("return 0;"); + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("return {0}[i] + theta * ({0}[i+1] - {0}[i]);", table_name); + } + } + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_check_table_thread_function() { + if (info.table_count == 0) { + return; + } + + printer->add_newline(2); + auto name = method_name("check_table_thread"); + auto parameters = external_method_parameters(true); + + printer->fmt_push_block("static void {} ({})", name, parameters); + printer->add_line("setup_instance(nt, ml);"); + printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); + printer->add_line("double v = 0;"); + + for (const auto& function: info.functions_with_table) { + auto method_name_str = method_name("check_" + function->get_node_name()); + auto arguments = internal_method_arguments(); + printer->fmt_line("{}({});", method_name_str, arguments); + } + + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_function_or_procedure(const ast::Block& node, + const std::string& name) { + printer->add_newline(2); + print_function_declaration(node, name); + printer->add_text(" "); + printer->push_block(); + + // function requires return variable declaration + if (node.is_function_block()) { + auto type = default_float_data_type(); + printer->fmt_line("{} ret_{} = 0.0;", type, name); + } else { + printer->fmt_line("int ret_{} = 0;", name); + } + + print_statement_block(*node.get_statement_block(), false, false); + printer->fmt_line("return ret_{};", name); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_function_procedure_helper(const ast::Block& node) { + codegen = true; + auto name = node.get_node_name(); + + if (info.function_uses_table(name)) { + auto new_name = "f_" + name; + print_function_or_procedure(node, new_name); + print_table_check_function(node); + print_table_replacement_function(node); + } else { + print_function_or_procedure(node, name); + } + + codegen = false; +} + + +void CodegenCoreneuronCppVisitor::print_procedure(const ast::ProcedureBlock& node) { + print_function_procedure_helper(node); +} + + +void CodegenCoreneuronCppVisitor::print_function(const ast::FunctionBlock& node) { + auto name = node.get_node_name(); + + // name of return variable + std::string return_var; + if (info.function_uses_table(name)) { + return_var = "ret_f_" + name; + } else { + return_var = "ret_" + name; + } + + // first rename return variable name + auto block = node.get_statement_block().get(); + RenameVisitor v(name, return_var); + block->accept(v); + + print_function_procedure_helper(node); +} + + +void CodegenCoreneuronCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { + auto name = node.get_node_name(); + const auto& p = node.get_parameters(); + auto params = internal_method_parameters(); + for (const auto& i: p) { + params.emplace_back("", "double", "", i->get_node_name()); + } + printer->fmt_line("double {}({})", method_name(name), get_parameter_str(params)); + printer->push_block(); + printer->fmt_line("double _arg[{}];", p.size()); + for (size_t i = 0; i < p.size(); ++i) { + printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); + } + printer->fmt_line("return hoc_func_table({}, {}, _arg);", + get_variable_name(std::string("_ptable_" + name), true), + p.size()); + printer->pop_block(); + + printer->fmt_push_block("double table_{}()", method_name(name)); + printer->fmt_line("hoc_spec_table(&{}, {});", + get_variable_name(std::string("_ptable_" + name)), + p.size()); + printer->add_line("return 0.;"); + printer->pop_block(); +} + + +/** + * @brief Checks whether the functor_block generated by sympy solver modifies any variable outside + * its scope. If it does then return false, so that the operator() of the struct functor of the + * Eigen Newton solver doesn't have const qualifier. + * + * @param variable_block Statement Block of the variables declarations used in the functor struct of + * the solver + * @param functor_block Actual code being printed in the operator() of the functor struct of the + * solver + * @return True if operator() is const else False + */ +bool CodegenCoreneuronCppVisitor::is_functor_const(const ast::StatementBlock& variable_block, + const ast::StatementBlock& functor_block) { + // Create complete_block with both variable declarations (done in variable_block) and solver + // part (done in functor_block) to be able to run the SymtabVisitor and DefUseAnalyzeVisitor + // then and get the proper DUChains for the variables defined in the variable_block + ast::StatementBlock complete_block(functor_block); + // Typically variable_block has only one statement, a statement containing the declaration + // of the local variables + for (const auto& statement: variable_block.get_statements()) { + complete_block.insert_statement(complete_block.get_statements().begin(), statement); + } + + // Create Symbol Table for complete_block + auto model_symbol_table = std::make_shared(); + SymtabVisitor(model_symbol_table.get()).visit_statement_block(complete_block); + // Initialize DefUseAnalyzeVisitor to generate the DUChains for the variables defined in the + // variable_block + DefUseAnalyzeVisitor v(*complete_block.get_symbol_table()); + + // Check the DUChains for all the variables in the variable_block + // If variable is defined in complete_block don't add const quilifier in operator() + auto is_functor_const = true; + const auto& variables = collect_nodes(variable_block, {ast::AstNodeType::LOCAL_VAR}); + for (const auto& variable: variables) { + const auto& chain = v.analyze(complete_block, variable->get_node_name()); + is_functor_const = !(chain.eval() == DUState::D || chain.eval() == DUState::LD || + chain.eval() == DUState::CD); + if (!is_functor_const) { + break; + } + } + + return is_functor_const; +} + + +void CodegenCoreneuronCppVisitor::print_functor_definition( + const ast::EigenNewtonSolverBlock& node) { + // functor that evaluates F(X) and J(X) for + // Newton solver + auto float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + + const auto functor_name = info.functor_names[&node]; + printer->fmt_push_block("struct {0}", functor_name); + printer->add_line("NrnThread* nt;"); + printer->add_line(instance_struct(), "* inst;"); + printer->add_line("int id, pnodecount;"); + printer->add_line("double v;"); + printer->add_line("const Datum* indexes;"); + printer->add_line("double* data;"); + printer->add_line("ThreadDatum* thread;"); + + if (ion_variable_struct_required()) { + print_ion_variable(); + } + + print_statement_block(*node.get_variable_block(), false, false); + printer->add_newline(); + + printer->push_block("void initialize()"); + print_statement_block(*node.get_initialize_block(), false, false); + printer->pop_block(); + printer->add_newline(); + + printer->fmt_line( + "{0}(NrnThread* nt, {1}* inst, int id, int pnodecount, double v, const Datum* indexes, " + "double* data, ThreadDatum* thread) : " + "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}}, " + "data{{data}}, thread{{thread}} " + "{{}}", + functor_name, + instance_struct()); + + printer->add_indent(); + + const auto& variable_block = *node.get_variable_block(); + const auto& functor_block = *node.get_functor_block(); + + printer->fmt_text( + "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " + "1>& nmodl_eigen_fm, " + "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", + float_type, + N, + is_functor_const(variable_block, functor_block) ? "const " : ""); + printer->push_block(); + printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); + print_statement_block(functor_block, false, false); + printer->pop_block(); + printer->add_newline(); + + // assign newton solver results in matrix X to state vars + printer->push_block("void finalize()"); + print_statement_block(*node.get_finalize_block(), false, false); + printer->pop_block(); + + printer->pop_block(";"); +} + + +void CodegenCoreneuronCppVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { + if (N <= 4) { + // Faster compared to LU, given the template specialization in Eigen. + printer->add_multi_line(R"CODE( + bool invertible; + nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible); + nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm; + if (!invertible) assert(false && "Singular or ill-conditioned matrix (Eigen::inverse)!"); + )CODE"); + } else { + // In Eigen the default storage order is ColMajor. + // Crout's implementation requires matrices stored in RowMajor order (C++-style arrays). + // Therefore, the transposeInPlace is critical such that the data() method to give the rows + // instead of the columns. + printer->add_line("if (!nmodl_eigen_jm.IsRowMajor) nmodl_eigen_jm.transposeInPlace();"); + + // pivot vector + printer->fmt_line("Eigen::Matrix pivot;", N); + printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> rowmax;", float_type, N); + + // In-place LU-Decomposition (Crout Algo) : Jm is replaced by its LU-decomposition + printer->fmt_line( + "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data(), rowmax.data()) " + "< 0) assert(false && \"Singular or ill-conditioned matrix (nmodl::crout)!\");", + float_type, + N); + + // Solve the linear system : Forward/Backward substitution part + printer->fmt_line( + "nmodl::crout::solveCrout<{0}>({1}, nmodl_eigen_jm.data(), nmodl_eigen_fm.data(), " + "nmodl_eigen_xm.data(), pivot.data());", + float_type, + N); + } +} + + +/****************************************************************************************/ +/* Code-specific helper routines */ +/****************************************************************************************/ + + +std::string CodegenCoreneuronCppVisitor::internal_method_arguments() { + if (ion_variable_struct_required()) { + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; + } + return "id, pnodecount, inst, data, indexes, thread, nt, v"; +} + + +/** + * @todo: figure out how to correctly handle qualifiers + */ +CodegenCoreneuronCppVisitor::ParamVector CodegenCoreneuronCppVisitor::internal_method_parameters() { + ParamVector params; + params.emplace_back("", "int", "", "id"); + params.emplace_back("", "int", "", "pnodecount"); + params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); + if (ion_variable_struct_required()) { + params.emplace_back("", "IonCurVar&", "", "ionvar"); + } + params.emplace_back("", "double*", "", "data"); + params.emplace_back("const ", "Datum*", "", "indexes"); + params.emplace_back("", "ThreadDatum*", "", "thread"); + params.emplace_back("", "NrnThread*", "", "nt"); + params.emplace_back("", "double", "", "v"); + return params; +} + + +const char* CodegenCoreneuronCppVisitor::external_method_arguments() noexcept { + return "id, pnodecount, data, indexes, thread, nt, ml, v"; +} + + +const char* CodegenCoreneuronCppVisitor::external_method_parameters(bool table) noexcept { + if (table) { + return "int id, int pnodecount, double* data, Datum* indexes, " + "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, int tml_id"; + } + return "int id, int pnodecount, double* data, Datum* indexes, " + "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v"; +} + + +std::string CodegenCoreneuronCppVisitor::nrn_thread_arguments() const { + if (ion_variable_struct_required()) { + return "id, pnodecount, ionvar, data, indexes, thread, nt, ml, v"; + } + return "id, pnodecount, data, indexes, thread, nt, ml, v"; +} + + +/** + * Function call arguments when function or procedure is defined in the + * same mod file itself + */ +std::string CodegenCoreneuronCppVisitor::nrn_thread_internal_arguments() { + if (ion_variable_struct_required()) { + return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; + } + return "id, pnodecount, inst, data, indexes, thread, nt, v"; +} + + +/** + * Replace commonly used variables in the verbatim blocks into their corresponding + * variable name in the new code generation backend. + */ +std::string CodegenCoreneuronCppVisitor::replace_if_verbatim_variable(std::string name) { + if (naming::VERBATIM_VARIABLES_MAPPING.find(name) != naming::VERBATIM_VARIABLES_MAPPING.end()) { + name = naming::VERBATIM_VARIABLES_MAPPING.at(name); + } + + /** + * if function is defined the same mod file then the arguments must + * contain mechanism instance as well. + */ + if (name == naming::THREAD_ARGS) { + if (internal_method_call_encountered) { + name = nrn_thread_internal_arguments(); + internal_method_call_encountered = false; + } else { + name = nrn_thread_arguments(); + } + } + if (name == naming::THREAD_ARGS_PROTO) { + name = external_method_parameters(); + } + return name; +} + + +/** + * Processing commonly used constructs in the verbatim blocks. + * @todo : this is still ad-hoc and requires re-implementation to + * handle it more elegantly. + */ +std::string CodegenCoreneuronCppVisitor::process_verbatim_text(std::string const& text) { + parser::CDriver driver; + driver.scan_string(text); + auto tokens = driver.all_tokens(); + std::string result; + for (size_t i = 0; i < tokens.size(); i++) { + auto token = tokens[i]; + + // check if we have function call in the verbatim block where + // function is defined in the same mod file + if (program_symtab->is_method_defined(token) && tokens[i + 1] == "(") { + internal_method_call_encountered = true; + } + auto name = process_verbatim_token(token); + + if (token == (std::string("_") + naming::TQITEM_VARIABLE)) { + name.insert(0, 1, '&'); + } + if (token == "_STRIDE") { + name = "pnodecount+id"; + } + result += name; + } + return result; +} + + +std::string CodegenCoreneuronCppVisitor::register_mechanism_arguments() const { + auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "nullptr"; + auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr"; + auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); + auto nrn_init = method_name(naming::NRN_INIT_METHOD); + auto const nrn_private_constructor = method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD); + auto const nrn_private_destructor = method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD); + return fmt::format("mechanism, {}, {}, nullptr, {}, {}, {}, {}, first_pointer_var_index()", + nrn_alloc, + nrn_cur, + nrn_state, + nrn_init, + nrn_private_constructor, + nrn_private_destructor); +} + + +std::pair CodegenCoreneuronCppVisitor::read_ion_variable_name( + const std::string& name) { + return {name, naming::ION_VARNAME_PREFIX + name}; +} + + +std::pair CodegenCoreneuronCppVisitor::write_ion_variable_name( + const std::string& name) { + return {naming::ION_VARNAME_PREFIX + name, name}; +} + + +std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& ion_name, + const std::string& concentration, + int index) { + auto conc_var_name = get_variable_name(naming::ION_VARNAME_PREFIX + concentration); + auto style_var_name = get_variable_name("style_" + ion_name); + return fmt::format( + "nrn_wrote_conc({}_type," + " &({})," + " {}," + " {}," + " nrn_ion_global_map," + " {}," + " nt->_ml_list[{}_type]->_nodecount_padded)", + ion_name, + conc_var_name, + index, + style_var_name, + get_variable_name(naming::CELSIUS_VARIABLE), + ion_name); +} + + +/** + * If mechanisms dependency level execution is enabled then certain updates + * like ionic current contributions needs to be atomically updated. In this + * case we first update current mechanism's shadow vector and then add statement + * to queue that will be used in reduction queue. + */ +std::string CodegenCoreneuronCppVisitor::process_shadow_update_statement( + const ShadowUseStatement& statement, + BlockType /* type */) { + // when there is no operator or rhs then that statement doesn't need shadow update + if (statement.op.empty() && statement.rhs.empty()) { + auto text = statement.lhs + ";"; + return text; + } + + // return regular statement + auto lhs = get_variable_name(statement.lhs); + auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs); + return text; +} + + +/****************************************************************************************/ +/* Code-specific printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenCoreneuronCppVisitor::print_first_pointer_var_index_getter() { + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline int first_pointer_var_index()"); + printer->fmt_line("return {};", info.first_pointer_var_index); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_num_variable_getter() { + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline int float_variables_size()"); + printer->fmt_line("return {};", float_variables_size()); + printer->pop_block(); + + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline int int_variables_size()"); + printer->fmt_line("return {};", int_variables_size()); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_net_receive_arg_size_getter() { + if (!net_receive_exist()) { + return; + } + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline int num_net_receive_args()"); + printer->fmt_line("return {};", info.num_net_receive_parameters); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_mech_type_getter() { + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline int get_mech_type()"); + // false => get it from the host-only global struct, not the instance structure + printer->fmt_line("return {};", get_variable_name("mech_type", false)); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_memb_list_getter() { + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); + printer->push_block("if (!nt->_ml_list)"); + printer->add_line("return nullptr;"); + printer->pop_block(); + printer->add_line("return nt->_ml_list[get_mech_type()];"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_namespace_start() { + printer->add_newline(2); + printer->push_block("namespace coreneuron"); +} + + +void CodegenCoreneuronCppVisitor::print_namespace_stop() { + printer->pop_block(); +} + + +/** + * \details There are three types of thread variables currently considered: + * - top local thread variables + * - thread variables in the mod file + * - thread variables for solver + * + * These variables are allocated into different thread structures and have + * corresponding thread ids. Thread id start from 0. In mod2c implementation, + * thread_data_index is increased at various places and it is used to + * decide the index of thread. + */ +void CodegenCoreneuronCppVisitor::print_thread_getters() { + if (info.vectorize && info.derivimplicit_used()) { + int tid = info.derivimplicit_var_thread_id; + int list = info.derivimplicit_list_num; + + // clang-format off + printer->add_newline(2); + printer->add_line("/** thread specific helper routines for derivimplicit */"); + + printer->add_newline(1); + printer->fmt_push_block("static inline int* deriv{}_advance(ThreadDatum* thread)", list); + printer->fmt_line("return &(thread[{}].i);", tid); + printer->pop_block(); + printer->add_newline(); + + printer->fmt_push_block("static inline int dith{}()", list); + printer->fmt_line("return {};", tid+1); + printer->pop_block(); + printer->add_newline(); + + printer->fmt_push_block("static inline void** newtonspace{}(ThreadDatum* thread)", list); + printer->fmt_line("return &(thread[{}]._pvoid);", tid+2); + printer->pop_block(); + } + + if (info.vectorize && !info.thread_variables.empty()) { + printer->add_newline(2); + printer->add_line("/** tid for thread variables */"); + printer->push_block("static inline int thread_var_tid()"); + printer->fmt_line("return {};", info.thread_var_thread_id); + printer->pop_block(); + } + + if (info.vectorize && !info.top_local_variables.empty()) { + printer->add_newline(2); + printer->add_line("/** tid for top local tread variables */"); + printer->push_block("static inline int top_local_var_tid()"); + printer->fmt_line("return {};", info.top_local_thread_id); + printer->pop_block(); + } + // clang-format on +} + + +/****************************************************************************************/ +/* Routines for returning variable name */ +/****************************************************************************************/ + + +std::string CodegenCoreneuronCppVisitor::float_variable_name(const SymbolType& symbol, + bool use_instance) const { + auto name = symbol->get_name(); + auto dimension = symbol->get_length(); + auto position = position_of_float_var(name); + // clang-format off + if (symbol->is_array()) { + if (use_instance) { + return fmt::format("(inst->{}+id*{})", name, dimension); + } + return fmt::format("(data + {}*pnodecount + id*{})", position, dimension); + } + if (use_instance) { + return fmt::format("inst->{}[id]", name); + } + return fmt::format("data[{}*pnodecount + id]", position); + // clang-format on +} + + +std::string CodegenCoreneuronCppVisitor::int_variable_name(const IndexVariableInfo& symbol, + const std::string& name, + bool use_instance) const { + auto position = position_of_int_var(name); + // clang-format off + if (symbol.is_index) { + if (use_instance) { + return fmt::format("inst->{}[{}]", name, position); + } + return fmt::format("indexes[{}]", position); + } + if (symbol.is_integer) { + if (use_instance) { + return fmt::format("inst->{}[{}*pnodecount+id]", name, position); + } + return fmt::format("indexes[{}*pnodecount+id]", position); + } + if (use_instance) { + return fmt::format("inst->{}[indexes[{}*pnodecount + id]]", name, position); + } + auto data = symbol.is_vdata ? "_vdata" : "_data"; + return fmt::format("nt->{}[indexes[{}*pnodecount + id]]", data, position); + // clang-format on +} + + +std::string CodegenCoreneuronCppVisitor::global_variable_name(const SymbolType& symbol, + bool use_instance) const { + if (use_instance) { + return fmt::format("inst->{}->{}", naming::INST_GLOBAL_MEMBER, symbol->get_name()); + } else { + return fmt::format("{}.{}", global_struct_instance(), symbol->get_name()); + } +} + + +std::string CodegenCoreneuronCppVisitor::update_if_ion_variable_name( + const std::string& name) const { + std::string result(name); + if (ion_variable_struct_required()) { + if (info.is_ion_read_variable(name)) { + result = naming::ION_VARNAME_PREFIX + name; + } + if (info.is_ion_write_variable(name)) { + result = "ionvar." + name; + } + if (info.is_current(name)) { + result = "ionvar." + name; + } + } + return result; +} + + +std::string CodegenCoreneuronCppVisitor::get_variable_name(const std::string& name, + bool use_instance) const { + const std::string& varname = update_if_ion_variable_name(name); + + // clang-format off + auto symbol_comparator = [&varname](const SymbolType& sym) { + return varname == sym->get_name(); + }; + + auto index_comparator = [&varname](const IndexVariableInfo& var) { + return varname == var.symbol->get_name(); + }; + // clang-format on + + // float variable + auto f = std::find_if(codegen_float_variables.begin(), + codegen_float_variables.end(), + symbol_comparator); + if (f != codegen_float_variables.end()) { + return float_variable_name(*f, use_instance); + } + + // integer variable + auto i = + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); + if (i != codegen_int_variables.end()) { + return int_variable_name(*i, varname, use_instance); + } + + // global variable + auto g = std::find_if(codegen_global_variables.begin(), + codegen_global_variables.end(), + symbol_comparator); + if (g != codegen_global_variables.end()) { + return global_variable_name(*g, use_instance); + } + + if (varname == naming::NTHREAD_DT_VARIABLE) { + return std::string("nt->_") + naming::NTHREAD_DT_VARIABLE; + } + + // t in net_receive method is an argument to function and hence it should + // ne used instead of nt->_t which is current time of thread + if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { + return std::string("nt->_") + naming::NTHREAD_T_VARIABLE; + } + + auto const iter = + std::find_if(info.neuron_global_variables.begin(), + info.neuron_global_variables.end(), + [&varname](auto const& entry) { return entry.first->get_name() == varname; }); + if (iter != info.neuron_global_variables.end()) { + std::string ret; + if (use_instance) { + ret = "*(inst->"; + } + ret.append(varname); + if (use_instance) { + ret.append(")"); + } + return ret; + } + + // otherwise return original name + return varname; +} + + +/****************************************************************************************/ +/* Main printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenCoreneuronCppVisitor::print_backend_info() { + time_t current_time{}; + time(¤t_time); + std::string data_time_str{std::ctime(¤t_time)}; + auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; + + printer->add_line("/*********************************************************"); + printer->add_line("Model Name : ", info.mod_suffix); + printer->add_line("Filename : ", info.mod_file, ".mod"); + printer->add_line("NMODL Version : ", nmodl_version()); + printer->fmt_line("Vectorized : {}", info.vectorize); + printer->fmt_line("Threadsafe : {}", info.thread_safe); + printer->add_line("Created : ", stringutils::trim(data_time_str)); + printer->add_line("Simulator : ", simulator_name()); + printer->add_line("Backend : ", backend_name()); + printer->add_line("NMODL Compiler : ", version); + printer->add_line("*********************************************************/"); +} + + +void CodegenCoreneuronCppVisitor::print_standard_includes() { + printer->add_newline(); + printer->add_multi_line(R"CODE( + #include + #include + #include + #include + )CODE"); +} + + +void CodegenCoreneuronCppVisitor::print_coreneuron_includes() { + printer->add_newline(); + printer->add_multi_line(R"CODE( + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + )CODE"); + if (info.eigen_newton_solver_exist) { + printer->add_line("#include "); + } + if (info.eigen_linear_solver_exist) { + if (std::accumulate(info.state_vars.begin(), + info.state_vars.end(), + 0, + [](int l, const SymbolType& variable) { + return l += variable->get_length(); + }) > 4) { + printer->add_line("#include "); + } else { + printer->add_line("#include "); + printer->add_line("#include "); + } + } +} + + +void CodegenCoreneuronCppVisitor::print_sdlists_init(bool print_initializers) { + if (info.primes_size == 0) { + return; + } + const auto count_prime_variables = [](auto size, const SymbolType& symbol) { + return size += symbol->get_length(); + }; + const auto prime_variables_by_order_size = + std::accumulate(info.prime_variables_by_order.begin(), + info.prime_variables_by_order.end(), + 0, + count_prime_variables); + if (info.primes_size != prime_variables_by_order_size) { + throw std::runtime_error{ + fmt::format("primes_size = {} differs from prime_variables_by_order.size() = {}, " + "this should not happen.", + info.primes_size, + info.prime_variables_by_order.size())}; + } + auto const initializer_list = [&](auto const& primes, const char* prefix) -> std::string { + if (!print_initializers) { + return {}; + } + std::string list{"{"}; + for (auto iter = primes.begin(); iter != primes.end(); ++iter) { + auto const& prime = *iter; + list.append(std::to_string(position_of_float_var(prefix + prime->get_name()))); + if (std::next(iter) != primes.end()) { + list.append(", "); + } + } + list.append("}"); + return list; + }; + printer->fmt_line("int slist1[{}]{};", + info.primes_size, + initializer_list(info.prime_variables_by_order, "")); + printer->fmt_line("int dlist1[{}]{};", + info.primes_size, + initializer_list(info.prime_variables_by_order, "D")); + codegen_global_variables.push_back(make_symbol("slist1")); + codegen_global_variables.push_back(make_symbol("dlist1")); + // additional list for derivimplicit method + if (info.derivimplicit_used()) { + auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); + printer->fmt_line("int slist2[{}]{};", info.primes_size, initializer_list(primes, "")); + codegen_global_variables.push_back(make_symbol("slist2")); + } +} + + +/** + * \details Variables required for type of ion, type of point process etc. are + * of static int type. For the C++ backend type, it's ok to have + * these variables as file scoped static variables. + * + * Initial values of state variables (h0) are also defined as static + * variables. Note that the state could be ion variable and it could + * be also range variable. Hence lookup into symbol table before. + * + * When model is not vectorized (shouldn't be the case in coreneuron) + * the top local variables become static variables. + * + * Note that static variables are already initialized to 0. We do the + * same for some variables to keep same code as neuron. + */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +void CodegenCoreneuronCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { + const auto value_initialize = print_initializers ? "{}" : ""; + + auto float_type = default_float_data_type(); + printer->add_newline(2); + printer->add_line("/** all global variables */"); + printer->fmt_push_block("struct {}", global_struct()); + + for (const auto& ion: info.ions) { + auto name = fmt::format("{}_type", ion.name); + printer->fmt_line("int {}{};", name, value_initialize); + codegen_global_variables.push_back(make_symbol(name)); + } + + if (info.point_process) { + printer->fmt_line("int point_type{};", value_initialize); + codegen_global_variables.push_back(make_symbol("point_type")); + } + + for (const auto& var: info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + printer->fmt_line("{} {}{};", float_type, name, value_initialize); + codegen_global_variables.push_back(make_symbol(name)); + } + } + + // Neuron and Coreneuron adds "v" to global variables when vectorize + // is false. But as v is always local variable and passed as argument, + // we don't need to use global variable v + + auto& top_locals = info.top_local_variables; + if (!info.vectorize && !top_locals.empty()) { + for (const auto& var: top_locals) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->fmt_line("{} {}[{}] /* TODO init top-local-array */;", + float_type, + name, + length); + } else { + printer->fmt_line("{} {} /* TODO init top-local */;", float_type, name); + } + codegen_global_variables.push_back(var); + } + } + + if (!info.thread_variables.empty()) { + printer->fmt_line("int thread_data_in_use{};", value_initialize); + printer->fmt_line("{} thread_data[{}] /* TODO init thread_data */;", + float_type, + info.thread_var_data_size); + codegen_global_variables.push_back(make_symbol("thread_data_in_use")); + auto symbol = make_symbol("thread_data"); + symbol->set_as_array(info.thread_var_data_size); + codegen_global_variables.push_back(symbol); + } + + // TODO: remove this entirely? + printer->fmt_line("int reset{};", value_initialize); + codegen_global_variables.push_back(make_symbol("reset")); + + printer->fmt_line("int mech_type{};", value_initialize); + codegen_global_variables.push_back(make_symbol("mech_type")); + + for (const auto& var: info.global_variables) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->fmt_line("{} {}[{}] /* TODO init const-array */;", float_type, name, length); + } else { + double value{}; + if (auto const& value_ptr = var->get_value()) { + value = *value_ptr; + } + printer->fmt_line("{} {}{};", + float_type, + name, + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); + } + codegen_global_variables.push_back(var); + } + + for (const auto& var: info.constant_variables) { + auto const name = var->get_name(); + auto* const value_ptr = var->get_value().get(); + double const value{value_ptr ? *value_ptr : 0}; + printer->fmt_line("{} {}{};", + float_type, + name, + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); + codegen_global_variables.push_back(var); + } + + print_sdlists_init(print_initializers); + + if (info.table_count > 0) { + printer->fmt_line("double usetable{};", print_initializers ? "{1}" : ""); + codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); + + for (const auto& block: info.functions_with_table) { + const auto& name = block->get_node_name(); + printer->fmt_line("{} tmin_{}{};", float_type, name, value_initialize); + printer->fmt_line("{} mfac_{}{};", float_type, name, value_initialize); + codegen_global_variables.push_back(make_symbol("tmin_" + name)); + codegen_global_variables.push_back(make_symbol("mfac_" + name)); + } + + for (const auto& variable: info.table_statement_variables) { + auto const name = "t_" + variable->get_name(); + auto const num_values = variable->get_num_values(); + if (variable->is_array()) { + int array_len = variable->get_length(); + printer->fmt_line( + "{} {}[{}][{}]{};", float_type, name, array_len, num_values, value_initialize); + } else { + printer->fmt_line("{} {}[{}]{};", float_type, name, num_values, value_initialize); + } + codegen_global_variables.push_back(make_symbol(name)); + } + } + + for (const auto& f: info.function_tables) { + printer->fmt_line("void* _ptable_{}{{}};", f->get_node_name()); + codegen_global_variables.push_back(make_symbol("_ptable_" + f->get_node_name())); + } + + if (info.vectorize && info.thread_data_index) { + printer->fmt_line("ThreadDatum ext_call_thread[{}]{};", + info.thread_data_index, + value_initialize); + codegen_global_variables.push_back(make_symbol("ext_call_thread")); + } + + printer->pop_block(";"); + + print_global_var_struct_assertions(); + print_global_var_struct_decl(); +} + + +void CodegenCoreneuronCppVisitor::print_global_var_struct_assertions() const { + // Assert some things that we assume when copying instances of this struct + // to the GPU and so on. + printer->fmt_line("static_assert(std::is_trivially_copy_constructible_v<{}>);", + global_struct()); + printer->fmt_line("static_assert(std::is_trivially_move_constructible_v<{}>);", + global_struct()); + printer->fmt_line("static_assert(std::is_trivially_copy_assignable_v<{}>);", global_struct()); + printer->fmt_line("static_assert(std::is_trivially_move_assignable_v<{}>);", global_struct()); + printer->fmt_line("static_assert(std::is_trivially_destructible_v<{}>);", global_struct()); +} + + +/** + * Print structs that encapsulate information about scalar and + * vector elements of type global and thread variables. + */ +void CodegenCoreneuronCppVisitor::print_global_variables_for_hoc() { + auto variable_printer = + [&](const std::vector& variables, bool if_array, bool if_vector) { + for (const auto& variable: variables) { + if (variable->is_array() == if_array) { + // false => do not use the instance struct, which is not + // defined in the global declaration that we are printing + auto name = get_variable_name(variable->get_name(), false); + auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); + auto length = variable->get_length(); + if (if_vector) { + printer->fmt_line("{{{}, {}, {}}},", ename, name, length); + } else { + printer->fmt_line("{{{}, &{}}},", ename, name); + } + } + } + }; + + auto globals = info.global_variables; + auto thread_vars = info.thread_variables; + + if (info.table_count > 0) { + globals.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); + } + + printer->add_newline(2); + printer->add_line("/** connect global (scalar) variables to hoc -- */"); + printer->add_line("static DoubScal hoc_scalar_double[] = {"); + printer->increase_indent(); + variable_printer(globals, false, false); + variable_printer(thread_vars, false, false); + printer->add_line("{nullptr, nullptr}"); + printer->decrease_indent(); + printer->add_line("};"); + + printer->add_newline(2); + printer->add_line("/** connect global (array) variables to hoc -- */"); + printer->add_line("static DoubVec hoc_vector_double[] = {"); + printer->increase_indent(); + variable_printer(globals, true, true); + variable_printer(thread_vars, true, true); + printer->add_line("{nullptr, nullptr, 0}"); + printer->decrease_indent(); + printer->add_line("};"); +} + + +/** + * Return registration type for a given BEFORE/AFTER block + * /param block A BEFORE/AFTER block being registered + * + * Depending on a block type i.e. BEFORE or AFTER and also type + * of it's associated block i.e. BREAKPOINT, INITIAL, SOLVE and + * STEP, the registration type (as an integer) is calculated. + * These values are then interpreted by CoreNEURON internally. + */ +static std::string get_register_type_for_ba_block(const ast::Block* block) { + std::string register_type{}; + BAType ba_type{}; + /// before block have value 10 and after block 20 + if (block->is_before_block()) { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + register_type = "BAType::Before"; + ba_type = + dynamic_cast(block)->get_bablock()->get_type()->get_value(); + } else { + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) + register_type = "BAType::After"; + ba_type = + dynamic_cast(block)->get_bablock()->get_type()->get_value(); + } + + /// associated blocks have different values (1 to 4) based on type. + /// These values are based on neuron/coreneuron implementation details. + if (ba_type == BATYPE_BREAKPOINT) { + register_type += " + BAType::Breakpoint"; + } else if (ba_type == BATYPE_SOLVE) { + register_type += " + BAType::Solve"; + } else if (ba_type == BATYPE_INITIAL) { + register_type += " + BAType::Initial"; + } else if (ba_type == BATYPE_STEP) { + register_type += " + BAType::Step"; + } else { + throw std::runtime_error("Unhandled Before/After type encountered during code generation"); + } + return register_type; +} + + +/** + * \details Every mod file has register function to connect with the simulator. + * Various information about mechanism and callbacks get registered with + * the simulator using suffix_reg() function. + * + * Here are details: + * - We should exclude that callback based on the solver, watch statements. + * - If nrn_get_mechtype is < -1 means that mechanism is not used in the + * context of neuron execution and hence could be ignored in coreneuron + * execution. + * - Ions are internally defined and their types can be queried similar to + * other mechanisms. + * - hoc_register_var may not be needed in the context of coreneuron + * - We assume net receive buffer is on. This is because generated code is + * compatible for cpu as well as gpu target. + */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +void CodegenCoreneuronCppVisitor::print_mechanism_register() { + printer->add_newline(2); + printer->add_line("/** register channel with the simulator */"); + printer->fmt_push_block("void _{}_reg()", info.mod_file); + + // type related information + auto suffix = add_escape_quote(info.mod_suffix); + printer->add_newline(); + printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); + printer->fmt_line("{} = mech_type;", get_variable_name("mech_type", false)); + printer->push_block("if (mech_type == -1)"); + printer->add_line("return;"); + printer->pop_block(); + + printer->add_newline(); + printer->add_line("_nrn_layout_reg(mech_type, 0);"); // 0 for SoA + + // register mechanism + const auto mech_arguments = register_mechanism_arguments(); + const auto number_of_thread_objects = num_thread_objects(); + if (info.point_process) { + printer->fmt_line("point_register_mech({}, {}, {}, {});", + mech_arguments, + info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) + : "nullptr", + info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) + : "nullptr", + number_of_thread_objects); + } else { + printer->fmt_line("register_mech({}, {});", mech_arguments, number_of_thread_objects); + if (info.constructor_node) { + printer->fmt_line("register_constructor({});", + method_name(naming::NRN_CONSTRUCTOR_METHOD)); + } + } + + // types for ion + for (const auto& ion: info.ions) { + printer->fmt_line("{} = nrn_get_mechtype({});", + get_variable_name(ion.name + "_type", false), + add_escape_quote(ion.name + "_ion")); + } + printer->add_newline(); + + /* + * Register callbacks for thread allocation and cleanup. Note that thread_data_index + * represent total number of thread used minus 1 (i.e. index of last thread). + */ + if (info.vectorize && (info.thread_data_index != 0)) { + // false to avoid getting the copy from the instance structure + printer->fmt_line("thread_mem_init({});", get_variable_name("ext_call_thread", false)); + } + + if (!info.thread_variables.empty()) { + printer->fmt_line("{} = 0;", get_variable_name("thread_data_in_use")); + } + + if (info.thread_callback_register) { + printer->add_line("_nrn_thread_reg0(mech_type, thread_mem_cleanup);"); + printer->add_line("_nrn_thread_reg1(mech_type, thread_mem_init);"); + } + + if (info.emit_table_thread()) { + auto name = method_name("check_table_thread"); + printer->fmt_line("_nrn_thread_table_reg(mech_type, {});", name); + } + + // register read/write callbacks for pointers + if (info.bbcore_pointer_used) { + printer->add_line("hoc_reg_bbcore_read(mech_type, bbcore_read);"); + printer->add_line("hoc_reg_bbcore_write(mech_type, bbcore_write);"); + } + + // register size of double and int elements + // clang-format off + printer->add_line("hoc_register_prop_size(mech_type, float_variables_size(), int_variables_size());"); + // clang-format on + + // register semantics for index variables + for (auto& semantic: info.semantics) { + auto args = + fmt::format("mech_type, {}, {}", semantic.index, add_escape_quote(semantic.name)); + printer->fmt_line("hoc_register_dparam_semantics({});", args); + } + + if (info.is_watch_used()) { + auto watch_fun = compute_method_name(BlockType::Watch); + printer->fmt_line("hoc_register_watch_check({}, mech_type);", watch_fun); + } + + if (info.write_concentration) { + printer->add_line("nrn_writes_conc(mech_type, 0);"); + } + + // register various information for point process type + if (info.net_event_used) { + printer->add_line("add_nrn_has_net_event(mech_type);"); + } + if (info.artificial_cell) { + printer->fmt_line("add_nrn_artcell(mech_type, {});", info.tqitem_index); + } + if (net_receive_buffering_required()) { + printer->fmt_line("hoc_register_net_receive_buffering({}, mech_type);", + method_name("net_buf_receive")); + } + if (info.num_net_receive_parameters != 0) { + auto net_recv_init_arg = "nullptr"; + if (info.net_receive_initial_node != nullptr) { + net_recv_init_arg = "net_init"; + } + printer->fmt_line("set_pnt_receive(mech_type, {}, {}, num_net_receive_args());", + method_name("net_receive"), + net_recv_init_arg); + } + if (info.for_netcon_used) { + // index where information about FOR_NETCON is stored in the integer array + const auto index = + std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { + return a.name == naming::FOR_NETCON_SEMANTIC; + })->index; + printer->fmt_line("add_nrn_fornetcons(mech_type, {});", index); + } + + if (info.net_event_used || info.net_send_used) { + printer->add_line("hoc_register_net_send_buffering(mech_type);"); + } + + /// register all before/after blocks + for (size_t i = 0; i < info.before_after_blocks.size(); i++) { + // register type and associated function name for the block + const auto& block = info.before_after_blocks[i]; + std::string register_type = get_register_type_for_ba_block(block); + std::string function_name = method_name(fmt::format("nrn_before_after_{}", i)); + printer->fmt_line("hoc_reg_ba(mech_type, {}, {});", function_name, register_type); + } + + // register variables for hoc + printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_thread_memory_callbacks() { + if (!info.thread_callback_register) { + return; + } + + // thread_mem_init callback + printer->add_newline(2); + printer->add_line("/** thread memory allocation callback */"); + printer->push_block("static void thread_mem_init(ThreadDatum* thread) "); + + if (info.vectorize && info.derivimplicit_used()) { + printer->fmt_line("thread[dith{}()].pval = nullptr;", info.derivimplicit_list_num); + } + if (info.vectorize && (info.top_local_thread_size != 0)) { + auto length = info.top_local_thread_size; + auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); + printer->fmt_line("thread[top_local_var_tid()].pval = {};", allocation); + } + if (info.thread_var_data_size != 0) { + auto length = info.thread_var_data_size; + auto thread_data = get_variable_name("thread_data"); + auto thread_data_in_use = get_variable_name("thread_data_in_use"); + auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); + printer->fmt_push_block("if ({})", thread_data_in_use); + printer->fmt_line("thread[thread_var_tid()].pval = {};", allocation); + printer->chain_block("else"); + printer->fmt_line("thread[thread_var_tid()].pval = {};", thread_data); + printer->fmt_line("{} = 1;", thread_data_in_use); + printer->pop_block(); + } + printer->pop_block(); + printer->add_newline(2); + + + // thread_mem_cleanup callback + printer->add_line("/** thread memory cleanup callback */"); + printer->push_block("static void thread_mem_cleanup(ThreadDatum* thread) "); + + // clang-format off + if (info.vectorize && info.derivimplicit_used()) { + int n = info.derivimplicit_list_num; + printer->fmt_line("free(thread[dith{}()].pval);", n); + printer->fmt_line("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));", n); + } + // clang-format on + + if (info.top_local_thread_size != 0) { + auto line = "free(thread[top_local_var_tid()].pval);"; + printer->add_line(line); + } + if (info.thread_var_data_size != 0) { + auto thread_data = get_variable_name("thread_data"); + auto thread_data_in_use = get_variable_name("thread_data_in_use"); + printer->fmt_push_block("if (thread[thread_var_tid()].pval == {})", thread_data); + printer->fmt_line("{} = 0;", thread_data_in_use); + printer->chain_block("else"); + printer->add_line("free(thread[thread_var_tid()].pval);"); + printer->pop_block(); + } + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { + auto const value_initialize = print_initializers ? "{}" : ""; + auto int_type = default_int_data_type(); + printer->add_newline(2); + printer->add_line("/** all mechanism instance variables and global variables */"); + printer->fmt_push_block("struct {} ", instance_struct()); + + for (auto const& [var, type]: info.neuron_global_variables) { + auto const name = var->get_name(); + printer->fmt_line("{}* {}{};", + type, + name, + print_initializers ? fmt::format("{{&coreneuron::{}}}", name) + : std::string{}); + } + for (auto& var: codegen_float_variables) { + const auto& name = var->get_name(); + auto type = get_range_var_float_type(var); + auto qualifier = is_constant_variable(name) ? "const " : ""; + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); + } + for (auto& var: codegen_int_variables) { + const auto& name = var.symbol->get_name(); + if (var.is_index || var.is_integer) { + auto qualifier = var.is_constant ? "const " : ""; + printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialize); + } else { + auto qualifier = var.is_constant ? "const " : ""; + auto type = var.is_vdata ? "void*" : default_float_data_type(); + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); + } + } + + printer->fmt_line("{}* {}{};", + global_struct(), + naming::INST_GLOBAL_MEMBER, + print_initializers ? fmt::format("{{&{}}}", global_struct_instance()) + : std::string{}); + printer->pop_block(";"); +} + + +void CodegenCoreneuronCppVisitor::print_ion_var_structure() { + if (!ion_variable_struct_required()) { + return; + } + printer->add_newline(2); + printer->add_line("/** ion write variables */"); + printer->push_block("struct IonCurVar"); + + std::string float_type = default_float_data_type(); + std::vector members; + + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { + printer->fmt_line("{} {};", float_type, var); + members.push_back(var); + } + } + for (auto& var: info.currents) { + if (!info.is_ion_variable(var)) { + printer->fmt_line("{} {};", float_type, var); + members.push_back(var); + } + } + + print_ion_var_constructor(members); + + printer->pop_block(";"); +} + + +void CodegenCoreneuronCppVisitor::print_ion_var_constructor( + const std::vector& members) { + // constructor + printer->add_newline(); + printer->add_indent(); + printer->add_text("IonCurVar() : "); + for (int i = 0; i < members.size(); i++) { + printer->fmt_text("{}(0)", members[i]); + if (i + 1 < members.size()) { + printer->add_text(", "); + } + } + printer->add_text(" {}"); + printer->add_newline(); +} + + +void CodegenCoreneuronCppVisitor::print_ion_variable() { + printer->add_line("IonCurVar ionvar;"); +} + + +void CodegenCoreneuronCppVisitor::print_global_variable_device_update_annotation() { + // nothing for cpu +} + + +void CodegenCoreneuronCppVisitor::print_setup_range_variable() { + auto type = float_data_type(); + printer->add_newline(2); + printer->add_line("/** allocate and setup array for range variable */"); + printer->fmt_push_block("static inline {}* setup_range_variable(double* variable, int n)", + type); + printer->fmt_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));", type); + printer->push_block("for(size_t i = 0; i < n; i++)"); + printer->add_line("data[i] = variable[i];"); + printer->pop_block(); + printer->add_line("return data;"); + printer->pop_block(); +} + + +/** + * \details If floating point type like "float" is specified on command line then + * we can't turn all variables to new type. This is because certain variables + * are pointers to internal variables (e.g. ions). Hence, we check if given + * variable can be safely converted to new type. If so, return new type. + */ +std::string CodegenCoreneuronCppVisitor::get_range_var_float_type(const SymbolType& symbol) { + // clang-format off + auto with = NmodlType::read_ion_var + | NmodlType::write_ion_var + | NmodlType::pointer_var + | NmodlType::bbcore_pointer_var + | NmodlType::extern_neuron_variable; + // clang-format on + bool need_default_type = symbol->has_any_property(with); + if (need_default_type) { + return default_float_data_type(); + } + return float_data_type(); +} + + +void CodegenCoreneuronCppVisitor::print_instance_variable_setup() { + if (range_variable_setup_required()) { + print_setup_range_variable(); + } + + printer->add_newline(); + printer->add_line("// Allocate instance structure"); + printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", + method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD)); + printer->add_line("assert(!ml->instance);"); + printer->add_line("assert(!ml->global_variables);"); + printer->add_line("assert(ml->global_variables_size == 0);"); + printer->fmt_line("auto* const inst = new {}{{}};", instance_struct()); + printer->fmt_line("assert(inst->{} == &{});", + naming::INST_GLOBAL_MEMBER, + global_struct_instance()); + printer->add_line("ml->instance = inst;"); + printer->fmt_line("ml->global_variables = inst->{};", naming::INST_GLOBAL_MEMBER); + printer->fmt_line("ml->global_variables_size = sizeof({});", global_struct()); + printer->pop_block(); + printer->add_newline(); + + auto const cast_inst_and_assert_validity = [&]() { + printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); + printer->add_line("assert(inst);"); + printer->fmt_line("assert(inst->{});", naming::INST_GLOBAL_MEMBER); + printer->fmt_line("assert(inst->{} == &{});", + naming::INST_GLOBAL_MEMBER, + global_struct_instance()); + printer->fmt_line("assert(inst->{} == ml->global_variables);", naming::INST_GLOBAL_MEMBER); + printer->fmt_line("assert(ml->global_variables_size == sizeof({}));", global_struct()); + }; + + // Must come before print_instance_struct_copy_to_device and + // print_instance_struct_delete_from_device + print_instance_struct_transfer_routine_declarations(); + + printer->add_line("// Deallocate the instance structure"); + printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", + method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); + cast_inst_and_assert_validity(); + print_instance_struct_delete_from_device(); + printer->add_multi_line(R"CODE( + delete inst; + ml->instance = nullptr; + ml->global_variables = nullptr; + ml->global_variables_size = 0; + )CODE"); + printer->pop_block(); + printer->add_newline(); + + + printer->add_line("/** initialize mechanism instance variables */"); + printer->push_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml)"); + cast_inst_and_assert_validity(); + + std::string stride; + printer->add_line("int pnodecount = ml->_nodecount_padded;"); + stride = "*pnodecount"; + + printer->add_line("Datum* indexes = ml->pdata;"); + + auto const float_type = default_float_data_type(); + + int id = 0; + std::vector ptr_members{naming::INST_GLOBAL_MEMBER}; + for (auto const& [var, type]: info.neuron_global_variables) { + ptr_members.push_back(var->get_name()); + } + ptr_members.reserve(ptr_members.size() + codegen_float_variables.size() + + codegen_int_variables.size()); + for (auto& var: codegen_float_variables) { + auto name = var->get_name(); + auto range_var_type = get_range_var_float_type(var); + if (float_type == range_var_type) { + auto const variable = fmt::format("ml->data+{}{}", id, stride); + printer->fmt_line("inst->{} = {};", name, variable); + } else { + // TODO what MOD file exercises this? + printer->fmt_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);", + name, + id, + stride); + } + ptr_members.push_back(std::move(name)); + id += var->get_length(); + } + + for (auto& var: codegen_int_variables) { + auto name = var.symbol->get_name(); + auto const variable = [&var]() { + if (var.is_index || var.is_integer) { + return "ml->pdata"; + } else if (var.is_vdata) { + return "nt->_vdata"; + } else { + return "nt->_data"; + } + }(); + printer->fmt_line("inst->{} = {};", name, variable); + ptr_members.push_back(std::move(name)); + } + print_instance_struct_copy_to_device(); + printer->pop_block(); // setup_instance + printer->add_newline(); + + print_instance_struct_transfer_routines(ptr_members); +} + + +void CodegenCoreneuronCppVisitor::print_initial_block(const InitialBlock* node) { + if (info.artificial_cell) { + printer->add_line("double v = 0.0;"); + } else { + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + print_v_unused(); + } + + if (ion_variable_struct_required()) { + printer->add_line("IonCurVar ionvar;"); + } + + // read ion statements + auto read_statements = ion_read_statements(BlockType::Initial); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + + // initialize state variables (excluding ion state) + for (auto& var: info.state_vars) { + auto name = var->get_name(); + if (!info.is_ionic_conc(name)) { + auto lhs = get_variable_name(name); + auto rhs = get_variable_name(name + "0"); + if (var->is_array()) { + for (int i = 0; i < var->get_length(); ++i) { + printer->fmt_line("{}[{}] = {};", lhs, i, rhs); + } + } else { + printer->fmt_line("{} = {};", lhs, rhs); + } + } + } + + // initial block + if (node != nullptr) { + const auto& block = node->get_statement_block(); + print_statement_block(*block, false, false); + } + + // write ion statements + auto write_statements = ion_write_statements(BlockType::Initial); + for (auto& statement: write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Initial); + printer->add_line(text); + } +} + + +void CodegenCoreneuronCppVisitor::print_global_function_common_code( + BlockType type, + const std::string& function_name) { + std::string method; + if (function_name.empty()) { + method = compute_method_name(type); + } else { + method = function_name; + } + auto args = "NrnThread* nt, Memb_list* ml, int type"; + + // watch statement function doesn't have type argument + if (type == BlockType::Watch) { + args = "NrnThread* nt, Memb_list* ml"; + } + + print_global_method_annotation(); + printer->fmt_push_block("void {}({})", method, args); + if (type != BlockType::Destructor && type != BlockType::Constructor) { + // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks + // running anything on the GPU. + print_kernel_data_present_annotation_block_begin(); + } else { + /// TODO: Remove this when the code generation is propery done + /// Related to https://github.com/BlueBrain/nmodl/issues/692 + printer->add_line("#ifndef CORENEURON_BUILD"); + } + printer->add_multi_line(R"CODE( + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + const int* node_index = ml->nodeindices; + double* data = ml->data; + const double* voltage = nt->_actual_v; + )CODE"); + + if (type == BlockType::Equation) { + printer->add_line("double* vec_rhs = nt->_actual_rhs;"); + printer->add_line("double* vec_d = nt->_actual_d;"); + print_rhs_d_shadow_variables(); + } + printer->add_line("Datum* indexes = ml->pdata;"); + printer->add_line("ThreadDatum* thread = ml->_thread;"); + + if (type == BlockType::Initial) { + printer->add_newline(); + printer->add_line("setup_instance(nt, ml);"); + } + printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); + printer->add_newline(1); +} + +void CodegenCoreneuronCppVisitor::print_nrn_init(bool skip_init_check) { + codegen = true; + printer->add_newline(2); + printer->add_line("/** initialize channel */"); + + print_global_function_common_code(BlockType::Initial); + if (info.derivimplicit_used()) { + printer->add_newline(); + int nequation = info.num_equations; + int list_num = info.derivimplicit_list_num; + // clang-format off + printer->fmt_line("int& deriv_advance_flag = *deriv{}_advance(thread);", list_num); + printer->add_line("deriv_advance_flag = 0;"); + print_deriv_advance_flag_transfer_to_device(); + printer->fmt_line("auto ns = newtonspace{}(thread);", list_num); + printer->fmt_line("auto& th = thread[dith{}()];", list_num); + printer->push_block("if (*ns == nullptr)"); + printer->fmt_line("int vec_size = 2*{}*pnodecount*sizeof(double);", nequation); + printer->fmt_line("double* vec = makevector(vec_size);", nequation); + printer->fmt_line("th.pval = vec;", list_num); + printer->fmt_line("*ns = nrn_cons_newtonspace({}, pnodecount);", nequation); + print_newtonspace_transfer_to_device(); + printer->pop_block(); + // clang-format on + } + + // update global variable as those might be updated via python/hoc API + // NOTE: CoreNEURON has enough information to do this on its own, which + // would be neater. + print_global_variable_device_update_annotation(); + + if (skip_init_check) { + printer->push_block("if (_nrn_skip_initmodel == 0)"); + } + + if (!info.changed_dt.empty()) { + printer->fmt_line("double _save_prev_dt = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE)); + printer->fmt_line("{} = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE), + info.changed_dt); + print_dt_update_to_device(); + } + + print_channel_iteration_block_parallel_hint(BlockType::Initial, info.initial_node); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + + if (info.net_receive_node != nullptr) { + printer->fmt_line("{} = -1e20;", get_variable_name("tsave")); + } + + print_initial_block(info.initial_node); + printer->pop_block(); + + if (!info.changed_dt.empty()) { + printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); + print_dt_update_to_device(); + } + + printer->pop_block(); + + if (info.derivimplicit_used()) { + printer->add_line("deriv_advance_flag = 1;"); + print_deriv_advance_flag_transfer_to_device(); + } + + if (info.net_send_used && !info.artificial_cell) { + print_send_event_move(); + } + + print_kernel_data_present_annotation_block_end(); + if (skip_init_check) { + printer->pop_block(); + } + codegen = false; +} + +void CodegenCoreneuronCppVisitor::print_before_after_block(const ast::Block* node, + size_t block_id) { + codegen = true; + + std::string ba_type; + std::shared_ptr ba_block; + + if (node->is_before_block()) { + ba_block = dynamic_cast(node)->get_bablock(); + ba_type = "BEFORE"; + } else { + ba_block = dynamic_cast(node)->get_bablock(); + ba_type = "AFTER"; + } + + std::string ba_block_type = ba_block->get_type()->eval(); + + /// name of the before/after function + std::string function_name = method_name(fmt::format("nrn_before_after_{}", block_id)); + + /// print common function code like init/state/current + printer->add_newline(2); + printer->fmt_line("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id); + print_global_function_common_code(BlockType::BeforeAfter, function_name); + + print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter, node); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + print_v_unused(); + + // read ion statements + const auto& read_statements = ion_read_statements(BlockType::Equation); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + + /// print main body + printer->add_indent(); + print_statement_block(*ba_block->get_statement_block()); + printer->add_newline(); + + // write ion statements + const auto& write_statements = ion_write_statements(BlockType::Equation); + for (auto& statement: write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + + /// loop end including data annotation block + printer->pop_block(); + printer->pop_block(); + print_kernel_data_present_annotation_block_end(); + + codegen = false; +} + +void CodegenCoreneuronCppVisitor::print_nrn_constructor() { + printer->add_newline(2); + print_global_function_common_code(BlockType::Constructor); + if (info.constructor_node != nullptr) { + const auto& block = info.constructor_node->get_statement_block(); + print_statement_block(*block, false, false); + } + printer->add_line("#endif"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_nrn_destructor() { + printer->add_newline(2); + print_global_function_common_code(BlockType::Destructor); + if (info.destructor_node != nullptr) { + const auto& block = info.destructor_node->get_statement_block(); + print_statement_block(*block, false, false); + } + printer->add_line("#endif"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_functors_definitions() { + codegen = true; + for (const auto& functor_name: info.functor_names) { + printer->add_newline(2); + print_functor_definition(*functor_name.first); + } + codegen = false; +} + + +void CodegenCoreneuronCppVisitor::print_nrn_alloc() { + printer->add_newline(2); + auto method = method_name(naming::NRN_ALLOC_METHOD); + printer->fmt_push_block("static void {}(double* data, Datum* indexes, int type)", method); + printer->add_line("// do nothing"); + printer->pop_block(); +} + +/** + * \todo Number of watch could be more than number of statements + * according to grammar. Check if this is correctly handled in neuron + * and coreneuron. + */ +void CodegenCoreneuronCppVisitor::print_watch_activate() { + if (info.watch_statements.empty()) { + return; + } + codegen = true; + printer->add_newline(2); + auto inst = fmt::format("{}* inst", instance_struct()); + + printer->fmt_push_block( + "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, " + "double v, bool &watch_remove)", + inst); + + // initialize all variables only during first watch statement + printer->push_block("if (watch_remove == false)"); + for (int i = 0; i < info.watch_count; i++) { + auto name = get_variable_name(fmt::format("watch{}", i + 1)); + printer->fmt_line("{} = 0;", name); + } + printer->add_line("watch_remove = true;"); + printer->pop_block(); + + /** + * \todo Similar to neuron/coreneuron we are using + * first watch and ignoring rest. + */ + for (int i = 0; i < info.watch_statements.size(); i++) { + auto statement = info.watch_statements[i]; + printer->fmt_push_block("if (watch_id == {})", i); + + auto varname = get_variable_name(fmt::format("watch{}", i + 1)); + printer->add_indent(); + printer->fmt_text("{} = 2 + (", varname); + auto watch = statement->get_statements().front(); + watch->get_expression()->visit_children(*this); + printer->add_text(");"); + printer->add_newline(); + + printer->pop_block(); + } + printer->pop_block(); + codegen = false; +} + + +/** + * \todo Similar to print_watch_activate, we are using only + * first watch. need to verify with neuron/coreneuron about rest. + */ +void CodegenCoreneuronCppVisitor::print_watch_check() { + if (info.watch_statements.empty()) { + return; + } + codegen = true; + printer->add_newline(2); + printer->add_line("/** routine to check watch activation */"); + print_global_function_common_code(BlockType::Watch); + + // WATCH statements appears in NET_RECEIVE block and while printing + // net_receive function we already check if it contains any MUTEX/PROTECT + // constructs. As WATCH is not a top level block but list of statements, + // we don't need to have ivdep pragma related check + print_channel_iteration_block_parallel_hint(BlockType::Watch, nullptr); + + printer->push_block("for (int id = 0; id < nodecount; id++)"); + + if (info.is_voltage_used_by_watch_statements()) { + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + print_v_unused(); + } + + // flat to make sure only one WATCH statement can be triggered at a time + printer->add_line("bool watch_untriggered = true;"); + + for (int i = 0; i < info.watch_statements.size(); i++) { + auto statement = info.watch_statements[i]; + const auto& watch = statement->get_statements().front(); + const auto& varname = get_variable_name(fmt::format("watch{}", i + 1)); + + // start block 1 + printer->fmt_push_block("if ({}&2 && watch_untriggered)", varname); + + // start block 2 + printer->add_indent(); + printer->add_text("if ("); + watch->get_expression()->accept(*this); + printer->add_text(") {"); + printer->add_newline(); + printer->increase_indent(); + + // start block 3 + printer->fmt_push_block("if (({}&1) == 0)", varname); + + printer->add_line("watch_untriggered = false;"); + + const auto& tqitem = get_variable_name("tqitem"); + const auto& point_process = get_variable_name("point_process"); + printer->add_indent(); + printer->add_text("net_send_buffering("); + const auto& t = get_variable_name("t"); + printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, -1, {}, {}+0.0, ", + tqitem, + point_process, + t); + watch->get_value()->accept(*this); + printer->add_text(");"); + printer->add_newline(); + printer->pop_block(); + + printer->add_line(varname, " = 3;"); + // end block 3 + + // start block 3 + printer->decrease_indent(); + printer->push_block("} else"); + printer->add_line(varname, " = 2;"); + printer->pop_block(); + // end block 3 + + printer->pop_block(); + // end block 1 + } + + printer->pop_block(); + print_send_event_move(); + print_kernel_data_present_annotation_block_end(); + printer->pop_block(); + codegen = false; +} + + +void CodegenCoreneuronCppVisitor::print_net_receive_common_code(const Block& node, + bool need_mech_inst) { + printer->add_multi_line(R"CODE( + int tid = pnt->_tid; + int id = pnt->_i_instance; + double v = 0; + )CODE"); + + if (info.artificial_cell || node.is_initial_block()) { + printer->add_line("NrnThread* nt = nrn_threads + tid;"); + printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); + } + if (node.is_initial_block()) { + print_kernel_data_present_annotation_block_begin(); + } + + printer->add_multi_line(R"CODE( + int nodecount = ml->nodecount; + int pnodecount = ml->_nodecount_padded; + double* data = ml->data; + double* weights = nt->weights; + Datum* indexes = ml->pdata; + ThreadDatum* thread = ml->_thread; + )CODE"); + if (need_mech_inst) { + printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); + } + + if (node.is_initial_block()) { + print_net_init_acc_serial_annotation_block_begin(); + } + + // rename variables but need to see if they are actually used + auto parameters = info.net_receive_node->get_parameters(); + if (!parameters.empty()) { + int i = 0; + printer->add_newline(); + for (auto& parameter: parameters) { + auto name = parameter->get_node_name(); + bool var_used = VarUsageVisitor().variable_used(node, "(*" + name + ")"); + if (var_used) { + printer->fmt_line("double* {} = weights + weight_index + {};", name, i); + RenameVisitor vr(name, "*" + name); + node.visit_children(vr); + } + i++; + } + } +} + + +void CodegenCoreneuronCppVisitor::print_net_send_call(const FunctionCall& node) { + auto const& arguments = node.get_arguments(); + const auto& tqitem = get_variable_name("tqitem"); + std::string weight_index = "weight_index"; + std::string pnt = "pnt"; + + // for functions not generated from NET_RECEIVE blocks (i.e. top level INITIAL block) + // the weight_index argument is 0. + if (!printing_net_receive && !printing_net_init) { + weight_index = "0"; + auto var = get_variable_name("point_process"); + if (info.artificial_cell) { + pnt = "(Point_process*)" + var; + } + } + + // artificial cells don't use spike buffering + // clang-format off + if (info.artificial_cell) { + printer->fmt_text("artcell_net_send(&{}, {}, {}, nt->_t+", tqitem, weight_index, pnt); + } else { + const auto& point_process = get_variable_name("point_process"); + const auto& t = get_variable_name("t"); + printer->add_text("net_send_buffering("); + printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t); + } + // clang-format off + print_vector_elements(arguments, ", "); + printer->add_text(')'); +} + + +void CodegenCoreneuronCppVisitor::print_net_move_call(const FunctionCall& node) { + if (!printing_net_receive && !printing_net_init) { + throw std::runtime_error("Error : net_move only allowed in NET_RECEIVE block"); + } + + auto const& arguments = node.get_arguments(); + const auto& tqitem = get_variable_name("tqitem"); + std::string weight_index = "-1"; + std::string pnt = "pnt"; + + // artificial cells don't use spike buffering + // clang-format off + if (info.artificial_cell) { + printer->fmt_text("artcell_net_move(&{}, {}, ", tqitem, pnt); + print_vector_elements(arguments, ", "); + printer->add_text(")"); + } else { + const auto& point_process = get_variable_name("point_process"); + printer->add_text("net_send_buffering("); + printer->fmt_text("nt, ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); + print_vector_elements(arguments, ", "); + printer->add_text(", 0.0"); + printer->add_text(")"); + } +} + + +void CodegenCoreneuronCppVisitor::print_net_event_call(const FunctionCall& node) { + const auto& arguments = node.get_arguments(); + if (info.artificial_cell) { + printer->add_text("net_event(pnt, "); + print_vector_elements(arguments, ", "); + } else { + const auto& point_process = get_variable_name("point_process"); + printer->add_text("net_send_buffering("); + printer->fmt_text("nt, ml->_net_send_buffer, 1, -1, -1, {}, ", point_process); + print_vector_elements(arguments, ", "); + printer->add_text(", 0.0"); + } + printer->add_text(")"); +} + +/** + * Rename arguments to NET_RECEIVE block with corresponding pointer variable + * + * Arguments to NET_RECEIVE block are packed and passed via weight vector. These + * variables need to be replaced with corresponding pointer variable. For example, + * if mod file is like + * + * \code{.mod} + * NET_RECEIVE (weight, R){ + * INITIAL { + * R=1 + * } + * } + * \endcode + * + * then generated code for initial block should be: + * + * \code{.cpp} + * double* R = weights + weight_index + 0; + * (*R) = 1.0; + * \endcode + * + * So, the `R` in AST needs to be renamed with `(*R)`. + */ +static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive_node, const ast::Node& node) { + const auto& parameters = net_receive_node.get_parameters(); + for (auto& parameter: parameters) { + const auto& name = parameter->get_node_name(); + auto var_used = VarUsageVisitor().variable_used(node, name); + if (var_used) { + RenameVisitor vr(name, "(*" + name + ")"); + node.get_statement_block()->visit_children(vr); + } + } +} + + +void CodegenCoreneuronCppVisitor::print_net_init() { + const auto node = info.net_receive_initial_node; + if (node == nullptr) { + return; + } + + // rename net_receive arguments used in the initial block of net_receive + rename_net_receive_arguments(*info.net_receive_node, *node); + + codegen = true; + printing_net_init = true; + auto args = "Point_process* pnt, int weight_index, double flag"; + printer->add_newline(2); + printer->add_line("/** initialize block for net receive */"); + printer->fmt_push_block("static void net_init({})", args); + auto block = node->get_statement_block().get(); + if (block->get_statements().empty()) { + printer->add_line("// do nothing"); + } else { + print_net_receive_common_code(*node); + print_statement_block(*block, false, false); + if (node->is_initial_block()) { + print_net_init_acc_serial_annotation_block_end(); + print_kernel_data_present_annotation_block_end(); + printer->add_line("auto& nsb = ml->_net_send_buffer;"); + print_net_send_buf_update_to_host(); + } + } + printer->pop_block(); + codegen = false; + printing_net_init = false; +} + + +void CodegenCoreneuronCppVisitor::print_send_event_move() { + printer->add_newline(); + printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); + print_net_send_buf_update_to_host(); + printer->push_block("for (int i=0; i < nsb->_cnt; i++)"); + printer->add_multi_line(R"CODE( + int type = nsb->_sendtype[i]; + int tid = nt->id; + double t = nsb->_nsb_t[i]; + double flag = nsb->_nsb_flag[i]; + int vdata_index = nsb->_vdata_index[i]; + int weight_index = nsb->_weight_index[i]; + int point_index = nsb->_pnt_index[i]; + net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag); + )CODE"); + printer->pop_block(); + printer->add_line("nsb->_cnt = 0;"); + print_net_send_buf_count_update_to_device(); +} + + +std::string CodegenCoreneuronCppVisitor::net_receive_buffering_declaration() { + return fmt::format("void {}(NrnThread* nt)", method_name("net_buf_receive")); +} + + +void CodegenCoreneuronCppVisitor::print_get_memb_list() { + printer->add_line("Memb_list* ml = get_memb_list(nt);"); + printer->push_block("if (!ml)"); + printer->add_line("return;"); + printer->pop_block(); + printer->add_newline(); +} + + +void CodegenCoreneuronCppVisitor::print_net_receive_loop_begin() { + printer->add_line("int count = nrb->_displ_cnt;"); + print_channel_iteration_block_parallel_hint(BlockType::NetReceive, info.net_receive_node); + printer->push_block("for (int i = 0; i < count; i++)"); +} + + +void CodegenCoreneuronCppVisitor::print_net_receive_loop_end() { + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_net_receive_buffering(bool need_mech_inst) { + if (!net_receive_required() || info.artificial_cell) { + return; + } + printer->add_newline(2); + printer->push_block(net_receive_buffering_declaration()); + + print_get_memb_list(); + + const auto& net_receive = method_name("net_receive_kernel"); + + print_kernel_data_present_annotation_block_begin(); + + printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); + if (need_mech_inst) { + printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); + } + print_net_receive_loop_begin(); + printer->add_line("int start = nrb->_displ[i];"); + printer->add_line("int end = nrb->_displ[i+1];"); + printer->push_block("for (int j = start; j < end; j++)"); + printer->add_multi_line(R"CODE( + int index = nrb->_nrb_index[j]; + int offset = nrb->_pnt_index[index]; + double t = nrb->_nrb_t[index]; + int weight_index = nrb->_weight_index[index]; + double flag = nrb->_nrb_flag[index]; + Point_process* point_process = nt->pntprocs + offset; + )CODE"); + printer->add_line(net_receive, "(t, point_process, inst, nt, ml, weight_index, flag);"); + printer->pop_block(); + print_net_receive_loop_end(); + + print_device_stream_wait(); + printer->add_line("nrb->_displ_cnt = 0;"); + printer->add_line("nrb->_cnt = 0;"); + + if (info.net_send_used || info.net_event_used) { + print_send_event_move(); + } + + print_kernel_data_present_annotation_block_end(); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_net_send_buffering_cnt_update() const { + printer->add_line("i = nsb->_cnt++;"); +} + + +void CodegenCoreneuronCppVisitor::print_net_send_buffering_grow() { + printer->push_block("if (i >= nsb->_size)"); + printer->add_line("nsb->grow();"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_net_send_buffering() { + if (!net_send_buffer_required()) { + return; + } + + printer->add_newline(2); + print_device_method_annotation(); + auto args = + "const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, " + "int weight_index, int point_index, double t, double flag"; + printer->fmt_push_block("static inline void net_send_buffering({})", args); + printer->add_line("int i = 0;"); + print_net_send_buffering_cnt_update(); + print_net_send_buffering_grow(); + printer->push_block("if (i < nsb->_size)"); + printer->add_multi_line(R"CODE( + nsb->_sendtype[i] = type; + nsb->_vdata_index[i] = vdata_index; + nsb->_weight_index[i] = weight_index; + nsb->_pnt_index[i] = point_index; + nsb->_nsb_t[i] = t; + nsb->_nsb_flag[i] = flag; + )CODE"); + printer->pop_block(); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_net_receive_kernel() { + if (!net_receive_required()) { + return; + } + codegen = true; + printing_net_receive = true; + const auto node = info.net_receive_node; + + // rename net_receive arguments used in the block itself + rename_net_receive_arguments(*info.net_receive_node, *node); + + std::string name; + ParamVector params; + if (!info.artificial_cell) { + name = method_name("net_receive_kernel"); + params.emplace_back("", "double", "", "t"); + params.emplace_back("", "Point_process*", "", "pnt"); + params.emplace_back("", fmt::format("{}*", instance_struct()), + "", "inst"); + params.emplace_back("", "NrnThread*", "", "nt"); + params.emplace_back("", "Memb_list*", "", "ml"); + params.emplace_back("", "int", "", "weight_index"); + params.emplace_back("", "double", "", "flag"); + } else { + name = method_name("net_receive"); + params.emplace_back("", "Point_process*", "", "pnt"); + params.emplace_back("", "int", "", "weight_index"); + params.emplace_back("", "double", "", "flag"); + } + + printer->add_newline(2); + printer->fmt_push_block("static inline void {}({})", name, get_parameter_str(params)); + print_net_receive_common_code(*node, info.artificial_cell); + if (info.artificial_cell) { + printer->add_line("double t = nt->_t;"); + } + + // set voltage variable if it is used in the block (e.g. for WATCH statement) + auto v_used = VarUsageVisitor().variable_used(*node->get_statement_block(), "v"); + if (v_used) { + printer->add_line("int node_id = ml->nodeindices[id];"); + printer->add_line("v = nt->_actual_v[node_id];"); + } + + printer->fmt_line("{} = t;", get_variable_name("tsave")); + + if (info.is_watch_used()) { + printer->add_line("bool watch_remove = false;"); + } + + printer->add_indent(); + node->get_statement_block()->accept(*this); + printer->add_newline(); + printer->pop_block(); + + printing_net_receive = false; + codegen = false; +} + + +void CodegenCoreneuronCppVisitor::print_net_receive() { + if (!net_receive_required()) { + return; + } + codegen = true; + printing_net_receive = true; + if (!info.artificial_cell) { + const auto& name = method_name("net_receive"); + ParamVector params; + params.emplace_back("", "Point_process*", "", "pnt"); + params.emplace_back("", "int", "", "weight_index"); + params.emplace_back("", "double", "", "flag"); + printer->add_newline(2); + printer->fmt_push_block("static void {}({})", name, get_parameter_str(params)); + printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); + printer->add_line("Memb_list* ml = get_memb_list(nt);"); + printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); + printer->push_block("if (nrb->_cnt >= nrb->_size)"); + printer->add_line("realloc_net_receive_buffer(nt, ml);"); + printer->pop_block(); + printer->add_multi_line(R"CODE( + int id = nrb->_cnt; + nrb->_pnt_index[id] = pnt-nt->pntprocs; + nrb->_weight_index[id] = weight_index; + nrb->_nrb_t[id] = nt->_t; + nrb->_nrb_flag[id] = flag; + nrb->_cnt++; + )CODE"); + printer->pop_block(); + } + printing_net_receive = false; + codegen = false; +} + + +/** + * \todo Data is not derived. Need to add instance into instance struct? + * data used here is wrong in AoS because as in original implementation, + * data is not incremented every iteration for AoS. May be better to derive + * actual variable names? [resolved now?] + * slist needs to added as local variable + */ +void CodegenCoreneuronCppVisitor::print_derivimplicit_kernel(const Block& block) { + auto ext_args = external_method_arguments(); + auto ext_params = external_method_parameters(); + auto suffix = info.mod_suffix; + auto list_num = info.derivimplicit_list_num; + auto block_name = block.get_node_name(); + auto primes_size = info.primes_size; + auto stride = "*pnodecount+id"; + + printer->add_newline(2); + + printer->push_block("namespace"); + printer->fmt_push_block("struct _newton_{}_{}", block_name, info.mod_suffix); + printer->fmt_push_block("int operator()({}) const", external_method_parameters()); + auto const instance = fmt::format("auto* const inst = static_cast<{0}*>(ml->instance);", + instance_struct()); + auto const slist1 = fmt::format("auto const& slist{} = {};", + list_num, + get_variable_name(fmt::format("slist{}", list_num))); + auto const slist2 = fmt::format("auto& slist{} = {};", + list_num + 1, + get_variable_name(fmt::format("slist{}", list_num + 1))); + auto const dlist1 = fmt::format("auto const& dlist{} = {};", + list_num, + get_variable_name(fmt::format("dlist{}", list_num))); + auto const dlist2 = fmt::format( + "double* dlist{} = static_cast(thread[dith{}()].pval) + ({}*pnodecount);", + list_num + 1, + list_num, + info.primes_size); + printer->add_line(instance); + if (ion_variable_struct_required()) { + print_ion_variable(); + } + printer->fmt_line("double* savstate{} = static_cast(thread[dith{}()].pval);", + list_num, + list_num); + printer->add_line(slist1); + printer->add_line(dlist1); + printer->add_line(dlist2); + codegen = true; + print_statement_block(*block.get_statement_block(), false, false); + codegen = false; + printer->add_line("int counter = -1;"); + printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); + printer->fmt_push_block("if (*deriv{}_advance(thread))", list_num); + printer->fmt_line( + "dlist{0}[(++counter){1}] = " + "data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;", + list_num + 1, + stride, + list_num); + printer->chain_block("else"); + printer->fmt_line("dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];", + list_num + 1, + stride, + list_num); + printer->pop_block(); + printer->pop_block(); + printer->add_line("return 0;"); + printer->pop_block(); // operator() + printer->pop_block(";"); // struct + printer->pop_block(); // namespace + printer->add_newline(); + printer->fmt_push_block("int {}_{}({})", block_name, suffix, ext_params); + printer->add_line(instance); + printer->fmt_line("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num); + printer->add_line(slist1); + printer->add_line(slist2); + printer->add_line(dlist2); + printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); + printer->fmt_line("savstate{}[i{}] = data[slist{}[i]{}];", list_num, stride, list_num, stride); + printer->pop_block(); + printer->fmt_line( + "int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {}, " + "slist{}, _newton_{}_{}{{}}, dlist{}, {});", + list_num, + primes_size, + list_num + 1, + block_name, + suffix, + list_num + 1, + ext_args); + printer->add_line("return reset;"); + printer->pop_block(); + printer->add_newline(2); +} + + +void CodegenCoreneuronCppVisitor::print_newtonspace_transfer_to_device() const { + // nothing to do on cpu +} + + +/****************************************************************************************/ +/* Print nrn_state routine */ +/****************************************************************************************/ + + +void CodegenCoreneuronCppVisitor::print_nrn_state() { + if (!nrn_state_required()) { + return; + } + codegen = true; + + printer->add_newline(2); + printer->add_line("/** update state */"); + print_global_function_common_code(BlockType::State); + print_channel_iteration_block_parallel_hint(BlockType::State, info.nrn_state_block); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + print_v_unused(); + + /** + * \todo Eigen solver node also emits IonCurVar variable in the functor + * but that shouldn't update ions in derivative block + */ + if (ion_variable_struct_required()) { + print_ion_variable(); + } + + auto read_statements = ion_read_statements(BlockType::State); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + + if (info.nrn_state_block) { + info.nrn_state_block->visit_children(*this); + } + + if (info.currents.empty() && info.breakpoint_node != nullptr) { + auto block = info.breakpoint_node->get_statement_block(); + print_statement_block(*block, false, false); + } + + const auto& write_statements = ion_write_statements(BlockType::State); + for (auto& statement: write_statements) { + const auto& text = process_shadow_update_statement(statement, BlockType::State); + printer->add_line(text); + } + printer->pop_block(); + + print_kernel_data_present_annotation_block_end(); + + printer->pop_block(); + codegen = false; +} + + +/****************************************************************************************/ +/* Print nrn_cur related routines */ +/****************************************************************************************/ + + +void CodegenCoreneuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { + const auto& args = internal_method_parameters(); + const auto& block = node.get_statement_block(); + printer->add_newline(2); + print_device_method_annotation(); + printer->fmt_push_block("inline double nrn_current_{}({})", + info.mod_suffix, + get_parameter_str(args)); + printer->add_line("double current = 0.0;"); + print_statement_block(*block, false, false); + for (auto& current: info.currents) { + const auto& name = get_variable_name(current); + printer->fmt_line("current += {};", name); + } + printer->add_line("return current;"); + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { + const auto& block = node.get_statement_block(); + print_statement_block(*block, false, false); + if (!info.currents.empty()) { + std::string sum; + for (const auto& current: info.currents) { + auto var = breakpoint_current(current); + sum += get_variable_name(var); + if (¤t != &info.currents.back()) { + sum += "+"; + } + } + printer->fmt_line("double rhs = {};", sum); + } + + std::string sum; + for (const auto& conductance: info.conductances) { + auto var = breakpoint_current(conductance.variable); + sum += get_variable_name(var); + if (&conductance != &info.conductances.back()) { + sum += "+"; + } + } + printer->fmt_line("double g = {};", sum); + + for (const auto& conductance: info.conductances) { + if (!conductance.ion.empty()) { + const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + conductance.ion + "dv"; + const auto& rhs = get_variable_name(conductance.variable); + const ShadowUseStatement statement{lhs, "+=", rhs}; + const auto& text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + } +} + + +void CodegenCoreneuronCppVisitor::print_nrn_cur_non_conductance_kernel() { + printer->fmt_line("double g = nrn_current_{}({}+0.001);", + info.mod_suffix, + internal_method_arguments()); + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { + if (ion.is_ionic_current(var)) { + const auto& name = get_variable_name(var); + printer->fmt_line("double di{} = {};", ion.name, name); + } + } + } + printer->fmt_line("double rhs = nrn_current_{}({});", + info.mod_suffix, + internal_method_arguments()); + printer->add_line("g = (g-rhs)/0.001;"); + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { + if (ion.is_ionic_current(var)) { + const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; + auto rhs = fmt::format("(di{}-{})/0.001", ion.name, get_variable_name(var)); + if (info.point_process) { + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); + rhs += fmt::format("*1.e2/{}", area); + } + const ShadowUseStatement statement{lhs, "+=", rhs}; + const auto& text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + } + } +} + + +void CodegenCoreneuronCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { + printer->add_line("int node_id = node_index[id];"); + printer->add_line("double v = voltage[node_id];"); + print_v_unused(); + if (ion_variable_struct_required()) { + print_ion_variable(); + } + + const auto& read_statements = ion_read_statements(BlockType::Equation); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + + if (info.conductances.empty()) { + print_nrn_cur_non_conductance_kernel(); + } else { + print_nrn_cur_conductance_kernel(node); + } + + const auto& write_statements = ion_write_statements(BlockType::Equation); + for (auto& statement: write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + + if (info.point_process) { + const auto& area = get_variable_name(naming::NODE_AREA_VARIABLE); + printer->fmt_line("double mfactor = 1.e2/{};", area); + printer->add_line("g = g*mfactor;"); + printer->add_line("rhs = rhs*mfactor;"); + } + + print_g_unused(); +} + + +void CodegenCoreneuronCppVisitor::print_fast_imem_calculation() { + if (!info.electrode_current) { + return; + } + std::string rhs, d; + auto rhs_op = operator_for_rhs(); + auto d_op = operator_for_d(); + if (info.point_process) { + rhs = "shadow_rhs[id]"; + d = "shadow_d[id]"; + } else { + rhs = "rhs"; + d = "g"; + } + + printer->push_block("if (nt->nrn_fast_imem)"); + if (nrn_cur_reduction_loop_required()) { + printer->push_block("for (int id = 0; id < nodecount; id++)"); + printer->add_line("int node_id = node_index[id];"); + } + printer->fmt_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs); + printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d); + if (nrn_cur_reduction_loop_required()) { + printer->pop_block(); + } + printer->pop_block(); +} + + +void CodegenCoreneuronCppVisitor::print_nrn_cur() { + if (!nrn_cur_required()) { + return; + } + + codegen = true; + if (info.conductances.empty()) { + print_nrn_current(*info.breakpoint_node); + } + + printer->add_newline(2); + printer->add_line("/** update current */"); + print_global_function_common_code(BlockType::Equation); + print_channel_iteration_block_parallel_hint(BlockType::Equation, info.breakpoint_node); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + print_nrn_cur_kernel(*info.breakpoint_node); + print_nrn_cur_matrix_shadow_update(); + if (!nrn_cur_reduction_loop_required()) { + print_fast_imem_calculation(); + } + printer->pop_block(); + + if (nrn_cur_reduction_loop_required()) { + printer->push_block("for (int id = 0; id < nodecount; id++)"); + print_nrn_cur_matrix_shadow_reduction(); + printer->pop_block(); + print_fast_imem_calculation(); + } + + print_kernel_data_present_annotation_block_end(); + printer->pop_block(); + codegen = false; +} + + +/****************************************************************************************/ +/* Main code printing entry points */ +/****************************************************************************************/ + +void CodegenCoreneuronCppVisitor::print_headers_include() { + print_standard_includes(); + print_backend_includes(); + print_coreneuron_includes(); +} + + +void CodegenCoreneuronCppVisitor::print_namespace_begin() { + print_namespace_start(); + print_backend_namespace_start(); +} + + +void CodegenCoreneuronCppVisitor::print_namespace_end() { + print_backend_namespace_stop(); + print_namespace_stop(); +} + + +void CodegenCoreneuronCppVisitor::print_common_getters() { + print_first_pointer_var_index_getter(); + print_net_receive_arg_size_getter(); + print_thread_getters(); + print_num_variable_getter(); + print_mech_type_getter(); + print_memb_list_getter(); +} + + +void CodegenCoreneuronCppVisitor::print_data_structures(bool print_initializers) { + print_mechanism_global_var_structure(print_initializers); + print_mechanism_range_var_structure(print_initializers); + print_ion_var_structure(); +} + + +void CodegenCoreneuronCppVisitor::print_v_unused() const { + if (!info.vectorize) { + return; + } + printer->add_multi_line(R"CODE( + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + )CODE"); +} + + +void CodegenCoreneuronCppVisitor::print_g_unused() const { + printer->add_multi_line(R"CODE( + #if NRN_PRCELLSTATE + inst->g_unused[id] = g; + #endif + )CODE"); +} + + +void CodegenCoreneuronCppVisitor::print_compute_functions() { + print_top_verbatim_blocks(); + for (const auto& procedure: info.procedures) { + print_procedure(*procedure); + } + for (const auto& function: info.functions) { + print_function(*function); + } + for (const auto& function: info.function_tables) { + print_function_tables(*function); + } + for (size_t i = 0; i < info.before_after_blocks.size(); i++) { + print_before_after_block(info.before_after_blocks[i], i); + } + for (const auto& callback: info.derivimplicit_callbacks) { + const auto& block = *callback->get_node_to_solve(); + print_derivimplicit_kernel(block); + } + print_net_send_buffering(); + print_net_init(); + print_watch_activate(); + print_watch_check(); + print_net_receive_kernel(); + print_net_receive(); + print_net_receive_buffering(); + print_nrn_init(); + print_nrn_cur(); + print_nrn_state(); +} + + +void CodegenCoreneuronCppVisitor::print_codegen_routines() { + codegen = true; + print_backend_info(); + print_headers_include(); + print_namespace_begin(); + print_nmodl_constants(); + print_prcellstate_macros(); + print_mechanism_info(); + print_data_structures(true); + print_global_variables_for_hoc(); + print_common_getters(); + print_memory_allocation_routine(); + print_abort_routine(); + print_thread_memory_callbacks(); + print_instance_variable_setup(); + print_nrn_alloc(); + print_nrn_constructor(); + print_nrn_destructor(); + print_function_prototypes(); + print_functors_definitions(); + print_compute_functions(); + print_check_table_thread_function(); + print_mechanism_register(); + print_namespace_end(); + codegen = false; +} + + +/****************************************************************************************/ +/* Overloaded visitor routines */ +/****************************************************************************************/ + + +void CodegenCoreneuronCppVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { + if (!codegen) { + return; + } + printer->fmt_line("{}_{}({});", + node.get_node_to_solve()->get_node_name(), + info.mod_suffix, + external_method_arguments()); +} + + +void CodegenCoreneuronCppVisitor::visit_eigen_newton_solver_block( + const ast::EigenNewtonSolverBlock& node) { + // solution vector to store copy of state vars for Newton solver + printer->add_newline(); + + auto float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + + print_statement_block(*node.get_setup_x_block(), false, false); + + // call newton solver with functor and X matrix that contains state vars + printer->add_line("// call newton solver"); + printer->fmt_line("{} newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);", + info.functor_names[&node]); + printer->add_line("newton_functor.initialize();"); + printer->add_line( + "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); + printer->add_line( + "if (newton_iterations < 0) assert(false && \"Newton solver did not converge!\");"); + + // assign newton solver results in matrix X to state vars + print_statement_block(*node.get_update_states_block(), false, false); + printer->add_line("newton_functor.finalize();"); +} + + +void CodegenCoreneuronCppVisitor::visit_eigen_linear_solver_block( + const ast::EigenLinearSolverBlock& node) { + printer->add_newline(); + + const std::string float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); + printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); + if (N <= 4) + printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm_inv;", float_type, N); + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); + print_statement_block(*node.get_variable_block(), false, false); + print_statement_block(*node.get_initialize_block(), false, false); + print_statement_block(*node.get_setup_x_block(), false, false); + + printer->add_newline(); + print_eigen_linear_solver(float_type, N); + printer->add_newline(); + + print_statement_block(*node.get_update_states_block(), false, false); + print_statement_block(*node.get_finalize_block(), false, false); +} + + +void CodegenCoreneuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { + // For_netcon should take the same arguments as net_receive and apply the operations + // in the block to the weights of the netcons. Since all the weights are on the same vector, + // weights, we have a mask of operations that we apply iteratively, advancing the offset + // to the next netcon. + const auto& args = node.get_parameters(); + RenameVisitor v; + const auto& statement_block = node.get_statement_block(); + for (size_t i_arg = 0; i_arg < args.size(); ++i_arg) { + // sanitize node_name since we want to substitute names like (*w) as they are + auto old_name = + std::regex_replace(args[i_arg]->get_node_name(), regex_special_chars, R"(\$&)"); + const auto& new_name = fmt::format("weights[{} + nt->_fornetcon_weight_perm[i]]", i_arg); + v.set(old_name, new_name); + statement_block->accept(v); + } + + const auto index = + std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { + return a.name == naming::FOR_NETCON_SEMANTIC; + })->index; + + printer->fmt_text("const size_t offset = {}*pnodecount + id;", index); + printer->add_newline(); + printer->add_line( + "const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]];"); + printer->add_line( + "const size_t for_netcon_end = nt->_fornetcon_perm_indices[indexes[offset] + 1];"); + + printer->add_line("for (auto i = for_netcon_start; i < for_netcon_end; ++i) {"); + printer->increase_indent(); + print_statement_block(*statement_block, false, false); + printer->decrease_indent(); + + printer->add_line("}"); +} + + +void CodegenCoreneuronCppVisitor::visit_solution_expression(const SolutionExpression& node) { + auto block = node.get_node_to_solve().get(); + if (block->is_statement_block()) { + auto statement_block = dynamic_cast(block); + print_statement_block(*statement_block, false, false); + } else { + block->accept(*this); + } +} + + +void CodegenCoreneuronCppVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { + printer->add_text(fmt::format("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)", + current_watch_statement++)); +} + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp new file mode 100644 index 0000000000..cc59a2a77e --- /dev/null +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -0,0 +1,1345 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/** + * \dir + * \brief Code generation backend implementations for CoreNEURON + * + * \file + * \brief \copybrief nmodl::codegen::CodegenCoreneuronCppVisitor + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codegen/codegen_cpp_visitor.hpp" +#include "codegen/codegen_info.hpp" +#include "codegen/codegen_naming.hpp" +#include "printer/code_printer.hpp" +#include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" +#include "visitors/ast_visitor.hpp" + + +namespace nmodl { + +namespace codegen { + + +using printer::CodePrinter; + + +/** + * \defgroup codegen_backends Codegen Backends + * \ingroup codegen + * \brief Code generation backends for CoreNEURON + * \{ + */ + +/** + * \class CodegenCoreneuronCppVisitor + * \brief %Visitor for printing C++ code compatible with legacy api of CoreNEURON + * + * \todo + * - Handle define statement (i.e. macros) + * - If there is a return statement in the verbatim block + * of inlined function then it will be error. Need better + * error checking. For example, see netstim.mod where we + * have removed return from verbatim block. + */ +class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { + protected: + /****************************************************************************************/ + /* Member variables */ + /****************************************************************************************/ + + + /****************************************************************************************/ + /* Generic information getters */ + /****************************************************************************************/ + + + /** + * Name of the simulator the code was generated for + */ + std::string simulator_name() override; + + + /** + * Name of the code generation backend + */ + virtual std::string backend_name() const override; + + + /** + * Name of structure that wraps range variables + */ + std::string instance_struct() const { + return fmt::format("{}_Instance", info.mod_suffix); + } + + + /** + * Name of structure that wraps global variables + */ + std::string global_struct() const { + return fmt::format("{}_Store", info.mod_suffix); + } + + + /** + * Name of the (host-only) global instance of `global_struct` + */ + std::string global_struct_instance() const { + return info.mod_suffix + "_global"; + } + + + /** + * Determine the number of threads to allocate + */ + int num_thread_objects() const noexcept { + return info.vectorize ? (info.thread_data_index + 1) : 0; + } + + + /****************************************************************************************/ + /* Common helper routines accross codegen functions */ + /****************************************************************************************/ + + + /** + * Determine the position in the data array for a given float variable + * \param name The name of a float variable + * \return The position index in the data array + */ + int position_of_float_var(const std::string& name) const override; + + + /** + * Determine the position in the data array for a given int variable + * \param name The name of an int variable + * \return The position index in the data array + */ + int position_of_int_var(const std::string& name) const override; + + + /** + * Determine the variable name for the "current" used in breakpoint block taking into account + * intermediate code transformations. + * \param current The variable name for the current used in the model + * \return The name for the current to be printed in C++ + */ + std::string breakpoint_current(std::string current) const; + + + /** + * For a given output block type, return statements for all read ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the reading of ion variables + */ + std::vector ion_read_statements(BlockType type) const; + + + /** + * For a given output block type, return minimal statements for all read ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the reading of ion variables + */ + std::vector ion_read_statements_optimized(BlockType type) const; + + + /** + * For a given output block type, return statements for writing back ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the write-back of ion variables + */ + std::vector ion_write_statements(BlockType type); + + + /** + * Process a token in a verbatim block for possible variable renaming + * \param token The verbatim token to be processed + * \return The code after variable renaming + */ + std::string process_verbatim_token(const std::string& token); + + + /** + * Check if a structure for ion variables is required + * \return \c true if a structure fot ion variables must be generated + */ + bool ion_variable_struct_required() const; + + + /** + * Check if variable is qualified as constant + * \param name The name of variable + * \return \c true if it is constant + */ + virtual bool is_constant_variable(const std::string& name) const; + + + /****************************************************************************************/ + /* Backend specific routines */ + /****************************************************************************************/ + + + /** + * Generate the string representing the procedure parameter declaration + * + * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. + * + * \param params The parameters that should be concatenated into the function parameter + * declaration + * \return The string representing the declaration of function parameters + */ + static std::string get_parameter_str(const ParamVector& params); + + + /** + * Print the code to copy derivative advance flag to device + */ + virtual void print_deriv_advance_flag_transfer_to_device() const; + + + /** + * Print pragma annotation for increase and capture of variable in automatic way + */ + virtual void print_device_atomic_capture_annotation() const; + + + /** + * Print the code to update NetSendBuffer_t count from device to host + */ + virtual void print_net_send_buf_count_update_to_host() const; + + + /** + * Print the code to update NetSendBuffer_t from device to host + */ + virtual void print_net_send_buf_update_to_host() const; + + + /** + * Print the code to update NetSendBuffer_t count from host to device + */ + virtual void print_net_send_buf_count_update_to_device() const; + + /** + * Print the code to update dt from host to device + */ + virtual void print_dt_update_to_device() const; + + /** + * Print the code to synchronise/wait on stream specific to NrnThread + */ + virtual void print_device_stream_wait() const; + + + /** + * Print accelerator annotations indicating data presence on device + */ + virtual void print_kernel_data_present_annotation_block_begin(); + + + /** + * Print matching block end of accelerator annotations for data presence on device + */ + virtual void print_kernel_data_present_annotation_block_end(); + + + /** + * Print accelerator kernels begin annotation for net_init kernel + */ + virtual void print_net_init_acc_serial_annotation_block_begin(); + + + /** + * Print accelerator kernels end annotation for net_init kernel + */ + virtual void print_net_init_acc_serial_annotation_block_end(); + + + /** + * Print pragma annotations for channel iterations + * + * This can be overriden by backends to provide additonal annotations or pragmas to enable + * for example SIMD code generation (e.g. through \c ivdep) + * The default implementation prints + * + * \code + * #pragma ivdep + * \endcode + * + * \param type The block type + */ + virtual void print_channel_iteration_block_parallel_hint(BlockType type, + const ast::Block* block); + + + /** + * Check if reduction block in \c nrn\_cur required + */ + virtual bool nrn_cur_reduction_loop_required(); + + + /** + * Print the setup method for setting matrix shadow vectors + * + */ + virtual void print_rhs_d_shadow_variables(); + + + /** + * Print the update to matrix elements with/without shadow vectors + * + */ + virtual void print_nrn_cur_matrix_shadow_update(); + + + /** + * Print the reduction to matrix elements from shadow vectors + * + */ + virtual void print_nrn_cur_matrix_shadow_reduction(); + + + /** + * Print atomic update pragma for reduction statements + */ + virtual void print_atomic_reduction_pragma() override; + + + /** + * Print the backend specific device method annotation + * + * \note This is not used for the C++ backend + */ + virtual void print_device_method_annotation(); + + + /** + * Print backend specific global method annotation + * + * \note This is not used for the C++ backend + */ + virtual void print_global_method_annotation(); + + + /** + * Prints the start of namespace for the backend-specific code + * + * For the C++ backend no additional namespace is required + */ + virtual void print_backend_namespace_start(); + + + /** + * Prints the end of namespace for the backend-specific code + * + * For the C++ backend no additional namespace is required + */ + virtual void print_backend_namespace_stop(); + + + /** + * Print backend specific includes (none needed for C++ backend) + */ + virtual void print_backend_includes(); + + + /** + * Check if ion variable copies should be avoided + */ + bool optimize_ion_variable_copies() const; + + + /** + * Print memory allocation routine + */ + virtual void print_memory_allocation_routine() const; + + + /** + * Print backend specific abort routine + */ + virtual void print_abort_routine() const; + + + /** + * Return the name of main compute kernels + * \param type A block type + */ + virtual std::string compute_method_name(BlockType type) const; + + + /** + * Instantiate global var instance + * + * For C++ code generation this is empty + * \return "" + */ + virtual void print_global_var_struct_decl(); + + + /** + * Print declarations of the functions used by \ref + * print_instance_struct_copy_to_device and \ref + * print_instance_struct_delete_from_device. + */ + virtual void print_instance_struct_transfer_routine_declarations() {} + + /** + * Print the definitions of the functions used by \ref + * print_instance_struct_copy_to_device and \ref + * print_instance_struct_delete_from_device. Declarations of these functions + * are printed by \ref print_instance_struct_transfer_routine_declarations. + * + * This updates the (pointer) member variables in the device copy of the + * instance struct to contain device pointers, which is why you must pass a + * list of names of those member variables. + * + * \param ptr_members List of instance struct member names. + */ + virtual void print_instance_struct_transfer_routines( + std::vector const& /* ptr_members */) {} + + + /** + * Transfer the instance struct to the device. This calls a function + * declared by \ref print_instance_struct_transfer_routine_declarations. + */ + virtual void print_instance_struct_copy_to_device() {} + + + /** + * Delete the instance struct from the device. This calls a function + * declared by \ref print_instance_struct_transfer_routine_declarations. + */ + virtual void print_instance_struct_delete_from_device() {} + + + /****************************************************************************************/ + /* Printing routines for code generation */ + /****************************************************************************************/ + + + /** + * Print call to internal or external function + * \param node The AST node representing a function call + */ + void print_function_call(const ast::FunctionCall& node) override; + + + /** + * Print top level (global scope) verbatim blocks + */ + void print_top_verbatim_blocks(); + + + /** + * Print function and procedures prototype declaration + */ + void print_function_prototypes() override; + + + /** + * Check if the given name exist in the symbol + * \return \c return a tuple if variable + * is an array otherwise + */ + std::tuple check_if_var_is_array(const std::string& name); + + + /** + * Print \c check\_function() for functions or procedure using table + * \param node The AST node representing a function or procedure block + */ + void print_table_check_function(const ast::Block& node); + + + /** + * Print replacement function for function or procedure using table + * \param node The AST node representing a function or procedure block + */ + void print_table_replacement_function(const ast::Block& node); + + + /** + * Print check_table functions + */ + void print_check_table_thread_function(); + + + /** + * Print nmodl function or procedure (common code) + * \param node the AST node representing the function or procedure in NMODL + * \param name the name of the function or procedure + */ + void print_function_or_procedure(const ast::Block& node, const std::string& name) override; + + + /** + * Common helper function to help printing function or procedure blocks + * \param node the AST node representing the function or procedure in NMODL + */ + void print_function_procedure_helper(const ast::Block& node) override; + + + /** + * Print NMODL procedure in target backend code + * \param node + */ + virtual void print_procedure(const ast::ProcedureBlock& node) override; + + + /** + * Print NMODL function in target backend code + * \param node + */ + void print_function(const ast::FunctionBlock& node) override; + + + /** + * Print NMODL function_table in target backend code + * \param node + */ + void print_function_tables(const ast::FunctionTableBlock& node); + + + bool is_functor_const(const ast::StatementBlock& variable_block, + const ast::StatementBlock& functor_block); + + + /** + * \brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its + * functor + * + * \param node \c EigenNewtonSolverBlock for which to print the functor + */ + void print_functor_definition(const ast::EigenNewtonSolverBlock& node); + + + virtual void print_eigen_linear_solver(const std::string& float_type, int N); + + + /****************************************************************************************/ + /* Code-specific helper routines */ + /****************************************************************************************/ + + + /** + * Arguments for functions that are defined and used internally. + * \return the method arguments + */ + std::string internal_method_arguments() override; + + + /** + * Parameters for internally defined functions + * \return the method parameters + */ + ParamVector internal_method_parameters() override; + + + /** + * Arguments for external functions called from generated code + * \return A string representing the arguments passed to an external function + */ + const char* external_method_arguments() noexcept override; + + + /** + * Parameters for functions in generated code that are called back from external code + * + * Functions registered in NEURON during initialization for callback must adhere to a prescribed + * calling convention. This method generates the string representing the function parameters for + * these externally called functions. + * \param table + * \return A string representing the parameters of the function + */ + const char* external_method_parameters(bool table = false) noexcept override; + + + /** + * Arguments for "_threadargs_" macro in neuron implementation + */ + std::string nrn_thread_arguments() const override; + + + /** + * Arguments for "_threadargs_" macro in neuron implementation + */ + std::string nrn_thread_internal_arguments() override; + + + /** + * Replace commonly used verbatim variables + * \param name A variable name to be checked and possibly updated + * \return The possibly replace variable name + */ + std::string replace_if_verbatim_variable(std::string name); + + + /** + * Process a verbatim block for possible variable renaming + * \param text The verbatim code to be processed + * \return The code with all variables renamed as needed + */ + std::string process_verbatim_text(std::string const& text) override; + + + /** + * Arguments for register_mech or point_register_mech function + */ + std::string register_mechanism_arguments() const override; + + + /** + * Return ion variable name and corresponding ion read variable name + * \param name The ion variable name + * \return The ion read variable name + */ + static std::pair read_ion_variable_name(const std::string& name); + + + /** + * Return ion variable name and corresponding ion write variable name + * \param name The ion variable name + * \return The ion write variable name + */ + static std::pair write_ion_variable_name(const std::string& name); + + + /** + * Generate Function call statement for nrn_wrote_conc + * \param ion_name The name of the ion variable + * \param concentration The name of the concentration variable + * \param index + * \return The string representing the function call + */ + std::string conc_write_statement(const std::string& ion_name, + const std::string& concentration, + int index); + + /** + * Process shadow update statement + * + * If the statement requires reduction then add it to vector of reduction statement and return + * statement using shadow update + * + * \param statement The statement that might require shadow updates + * \param type The target backend code block type + * \return The generated target backend code + */ + std::string process_shadow_update_statement(const ShadowUseStatement& statement, + BlockType type); + + + /****************************************************************************************/ + /* Code-specific printing routines for code generations */ + /****************************************************************************************/ + + + /** + * Print the getter method for index position of first pointer variable + * + */ + void print_first_pointer_var_index_getter(); + + + /** + * Print the getter methods for float and integer variables count + * + */ + void print_num_variable_getter(); + + + /** + * Print the getter method for getting number of arguments for net_receive + * + */ + void print_net_receive_arg_size_getter(); + + + /** + * Print the getter method for returning mechtype + * + */ + void print_mech_type_getter(); + + + /** + * Print the getter method for returning membrane list from NrnThread + * + */ + void print_memb_list_getter(); + + + /** + * Prints the start of the \c coreneuron namespace + */ + void print_namespace_start() override; + + + /** + * Prints the end of the \c coreneuron namespace + */ + void print_namespace_stop() override; + + + /** + * Print the getter method for thread variables and ids + * + */ + void print_thread_getters(); + + + /****************************************************************************************/ + /* Routines for returning variable name */ + /****************************************************************************************/ + + + /** + * Determine the updated name if the ion variable has been optimized + * \param name The ion variable name + * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena) + */ + std::string update_if_ion_variable_name(const std::string& name) const; + + + /** + * Determine the name of a \c float variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C++ string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol + */ + std::string float_variable_name(const SymbolType& symbol, bool use_instance) const override; + + + /** + * Determine the name of an \c int variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C++ string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param name The name of the index variable + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol + */ + std::string int_variable_name(const IndexVariableInfo& symbol, + const std::string& name, + bool use_instance) const override; + + + /** + * Determine the variable name for a global variable given its symbol + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via the (host-only) + * global variable or the instance-specific copy (also available on GPU). + * \return The C++ string representing the access to the global variable + */ + std::string global_variable_name(const SymbolType& symbol, + bool use_instance = true) const override; + + + /** + * Determine variable name in the structure of mechanism properties + * + * \param name Variable name that is being printed + * \param use_instance Should the variable be accessed via instance or data array + * \return The C++ string representing the access to the variable in the neuron + * thread structure + */ + std::string get_variable_name(const std::string& name, bool use_instance = true) const override; + + + /****************************************************************************************/ + /* Main printing routines for code generation */ + /****************************************************************************************/ + + + /** + * Print top file header printed in generated code + */ + void print_backend_info() override; + + + /** + * Print standard C/C++ includes + */ + void print_standard_includes() override; + + + /** + * Print includes from coreneuron + */ + void print_coreneuron_includes(); + + + void print_sdlists_init(bool print_initializers) override; + + + /** + * Print the structure that wraps all global variables used in the NMODL + * + * \param print_initializers Whether to include default values in the struct + * definition (true: int foo{42}; false: int foo;) + */ + void print_mechanism_global_var_structure(bool print_initializers) override; + + + /** + * Print static assertions about the global variable struct. + */ + virtual void print_global_var_struct_assertions() const; + + + /** + * Print byte arrays that register scalar and vector variables for hoc interface + * + */ + void print_global_variables_for_hoc() override; + + + /** + * Print the mechanism registration function + * + */ + void print_mechanism_register() override; + + + /** + * Print thread related memory allocation and deallocation callbacks + */ + void print_thread_memory_callbacks(); + + + /** + * Print structure of ion variables used for local copies + */ + void print_ion_var_structure(); + + + /** + * Print constructor of ion variables + * \param members The ion variable names + */ + virtual void print_ion_var_constructor(const std::vector& members); + + + /** + * Print the ion variable struct + */ + virtual void print_ion_variable(); + + + /** + * Print the pragma annotation to update global variables from host to the device + * + * \note This is not used for the C++ backend + */ + virtual void print_global_variable_device_update_annotation(); + + + /** + * Print the function that initialize range variable with different data type + */ + void print_setup_range_variable(); + + + /** + * Returns floating point type for given range variable symbol + * \param symbol A range variable symbol + */ + std::string get_range_var_float_type(const SymbolType& symbol); + + + /** + * Print initial block statements + * + * Generate the target backend code corresponding to the NMODL initial block statements + * + * \param node The AST Node representing a NMODL initial block + */ + void print_initial_block(const ast::InitialBlock* node); + + + /** + * Print common code for global functions like nrn_init, nrn_cur and nrn_state + * \param type The target backend code block type + */ + virtual void print_global_function_common_code(BlockType type, + const std::string& function_name = "") override; + + + /** + * Print the \c nrn\_init function definition + * \param skip_init_check \c true to generate code executing the initialization conditionally + */ + void print_nrn_init(bool skip_init_check = true); + + + /** + * Print NMODL before / after block in target backend code + * \param node AST node of type before/after type being printed + * \param block_id Index of the before/after block + */ + virtual void print_before_after_block(const ast::Block* node, size_t block_id); + + + /** + * Print nrn_constructor function definition + * + */ + void print_nrn_constructor() override; + + + /** + * Print nrn_destructor function definition + * + */ + void print_nrn_destructor() override; + + + /** + * Go through the map of \c EigenNewtonSolverBlock s and their corresponding functor names + * and print the functor definitions before the definitions of the functions of the generated + * file + * + */ + void print_functors_definitions(); + + + /** + * Print nrn_alloc function definition + * + */ + void print_nrn_alloc() override; + + + /** + * Print watch activate function + * + */ + void print_watch_activate(); + + + /** + * Print watch activate function + */ + void print_watch_check(); + + + /** + * Print the common code section for net receive related methods + * + * \param node The AST node representing the corresponding NMODL block + * \param need_mech_inst \c true if a local \c inst variable needs to be defined in generated + * code + */ + void print_net_receive_common_code(const ast::Block& node, bool need_mech_inst = true); + + + /** + * Print call to \c net\_send + * \param node The AST node representing the function call + */ + void print_net_send_call(const ast::FunctionCall& node); + + + /** + * Print call to net\_move + * \param node The AST node representing the function call + */ + void print_net_move_call(const ast::FunctionCall& node); + + + /** + * Print call to net\_event + * \param node The AST node representing the function call + */ + void print_net_event_call(const ast::FunctionCall& node); + + + /** + * Print initial block in the net receive block + */ + void print_net_init(); + + + /** + * Print send event move block used in net receive as well as watch + */ + void print_send_event_move(); + + + /** + * Generate the target backend code for the \c net\_receive\_buffering function delcaration + * \return The target code string + */ + virtual std::string net_receive_buffering_declaration(); + + + /** + * Print the target backend code for defining and checking a local \c Memb\_list variable + */ + virtual void print_get_memb_list(); + + + /** + * Print the code for the main \c net\_receive loop + */ + virtual void print_net_receive_loop_begin(); + + + /** + * Print the code for closing the main \c net\_receive loop + */ + virtual void print_net_receive_loop_end(); + + + /** + * Print kernel for buffering net_receive events + * + * This kernel is only needed for accelerator backends where \c net\_receive needs to be + * executed in two stages as the actual communication must be done in the host code. \param + * need_mech_inst \c true if the generated code needs a local inst variable to be defined + */ + void print_net_receive_buffering(bool need_mech_inst = true); + + + /** + * Print the code related to the update of NetSendBuffer_t cnt. For GPU this needs to be done + * with atomic operation, on CPU it's not needed. + * + */ + virtual void print_net_send_buffering_cnt_update() const; + + + /** + * Print statement that grows NetSendBuffering_t structure if needed. + * This function should be overridden for backends that cannot dynamically reallocate the buffer + */ + virtual void print_net_send_buffering_grow(); + + + /** + * Print kernel for buffering net_send events + * + * This kernel is only needed for accelerator backends where \c net\_send needs to be executed + * in two stages as the actual communication must be done in the host code. + */ + void print_net_send_buffering(); + + + /** + * Print \c net\_receive kernel function definition + */ + void print_net_receive_kernel(); + + + /** + * Print \c net\_receive function definition + */ + void print_net_receive(); + + + /** + * Print derivative kernel when \c derivimplicit method is used + * + * \param block The corresponding AST node representing an NMODL \c derivimplicit block + */ + void print_derivimplicit_kernel(const ast::Block& block); + + + /** + * Print code block to transfer newtonspace structure to device + */ + virtual void print_newtonspace_transfer_to_device() const; + + + /****************************************************************************************/ + /* Print nrn_state routine */ + /****************************************************************************************/ + + + /** + * Print nrn_state / state update function definition + */ + void print_nrn_state() override; + + + /****************************************************************************************/ + /* Print nrn_cur related routines */ + /****************************************************************************************/ + + + /** + * Print the \c nrn_current kernel + * + * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified + * \param node the AST node representing the NMODL breakpoint block + */ + void print_nrn_current(const ast::BreakpointBlock& node) override; + + + /** + * Print the \c nrn\_cur kernel with NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is used in the \c breakpoint block, then + * CodegenCoreneuronCppVisitor::print_nrn_cur_kernel will use this printer + * + * \param node the AST node representing the NMODL breakpoint block + */ + void print_nrn_cur_conductance_kernel(const ast::BreakpointBlock& node) override; + + + /** + * Print the \c nrn\_cur kernel without NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is \b not used in the \c breakpoint block, then + * CodegenCoreneuronCppVisitor::print_nrn_cur_kernel will use this printer + */ + void print_nrn_cur_non_conductance_kernel() override; + + + /** + * Print main body of nrn_cur function + * \param node the AST node representing the NMODL breakpoint block + */ + void print_nrn_cur_kernel(const ast::BreakpointBlock& node) override; + + + /** + * Print fast membrane current calculation code + */ + virtual void print_fast_imem_calculation() override; + + + /** + * Print nrn_cur / current update function definition + */ + void print_nrn_cur() override; + + + /****************************************************************************************/ + /* Main code printing entry points */ + /****************************************************************************************/ + + + /** + * Print all includes + * + */ + void print_headers_include() override; + + + /** + * Print start of namespaces + * + */ + void print_namespace_begin() override; + + + /** + * Print end of namespaces + * + */ + void print_namespace_end() override; + + + /** + * Print common getters + * + */ + void print_common_getters(); + + + /** + * Print all classes + * \param print_initializers Whether to include default values. + */ + void print_data_structures(bool print_initializers) override; + + + /** + * Set v_unused (voltage) for NRN_PRCELLSTATE feature + */ + void print_v_unused() const override; + + + /** + * Set g_unused (conductance) for NRN_PRCELLSTATE feature + */ + void print_g_unused() const override; + + + /** + * Print all compute functions for every backend + * + */ + virtual void print_compute_functions() override; + + + /** + * Print entry point to code generation + * + */ + virtual void print_codegen_routines() override; + + + /****************************************************************************************/ + /* Overloaded visitor routines */ + /****************************************************************************************/ + + + void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; + void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; + void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; + void visit_for_netcon(const ast::ForNetcon& node) override; + virtual void visit_solution_expression(const ast::SolutionExpression& node) override; + virtual void visit_watch_statement(const ast::WatchStatement& node) override; + + + /** + * Print prototype declarations of functions or procedures + * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) + * \param node The AST node representing the function or procedure block + * \param name A user defined name for the function + */ + template + void print_function_declaration(const T& node, const std::string& name); + + + public: + /** + * \brief Constructs the C++ code generator visitor + * + * This constructor instantiates an NMODL C++ code generator and allows writing generated code + * directly to a file in \c [output_dir]/[mod_filename].cpp. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C++ code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param output_dir The directory where target C++ file should be generated. + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ + CodegenCoreneuronCppVisitor(std::string mod_filename, + const std::string& output_dir, + std::string float_type, + const bool optimize_ionvar_copies) + : CodegenCppVisitor(mod_filename, output_dir, float_type, optimize_ionvar_copies) {} + + /** + * \copybrief nmodl::codegen::CodegenCoreneuronCppVisitor + * + * This constructor instantiates an NMODL C++ code generator and allows writing generated code + * into an output stream. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C++ code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param stream The output stream onto which to write the generated code + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ + CodegenCoreneuronCppVisitor(std::string mod_filename, + std::ostream& stream, + std::string float_type, + const bool optimize_ionvar_copies) + : CodegenCppVisitor(mod_filename, stream, float_type, optimize_ionvar_copies) {} + + + /****************************************************************************************/ + /* Public printing routines for code generation for use in unit tests */ + /****************************************************************************************/ + + + /** + * Print the function that initialize instance structure + */ + void print_instance_variable_setup(); + + + /** + * Print the structure that wraps all range and int variables required for the NMODL + * + * \param print_initializers Whether or not default values for variables + * be included in the struct declaration. + */ + void print_mechanism_range_var_structure(bool print_initializers) override; +}; + + +/** + * \details If there is an argument with name (say alpha) same as range variable (say alpha), + * we want to avoid it being printed as instance->alpha. And hence we disable variable + * name lookup during prototype declaration. Note that the name of procedure can be + * different in case of table statement. + */ +template +void CodegenCoreneuronCppVisitor::print_function_declaration(const T& node, + const std::string& name) { + enable_variable_name_lookup = false; + auto type = default_float_data_type(); + + // internal and user provided arguments + auto internal_params = internal_method_parameters(); + const auto& params = node.get_parameters(); + for (const auto& param: params) { + internal_params.emplace_back("", type, "", param.get()->get_node_name()); + } + + // procedures have "int" return type by default + const char* return_type = "int"; + if (node.is_function_block()) { + return_type = default_float_data_type(); + } + + print_device_method_annotation(); + printer->add_indent(); + printer->fmt_text("inline {} {}({})", + return_type, + method_name(name), + get_parameter_str(internal_params)); + + enable_variable_name_lookup = true; +} + +/** \} */ // end of codegen_backends + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index a3e9111acb..21909a8b90 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -4,723 +4,628 @@ * * SPDX-License-Identifier: Apache-2.0 */ - #include "codegen/codegen_cpp_visitor.hpp" -#include -#include -#include -#include -#include - #include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" -#include "codegen/codegen_naming.hpp" #include "codegen/codegen_utils.hpp" -#include "config/config.h" -#include "lexer/token_mapping.hpp" -#include "parser/c11_driver.hpp" -#include "utils/logger.hpp" -#include "utils/string_utils.hpp" -#include "visitors/defuse_analyze_visitor.hpp" #include "visitors/rename_visitor.hpp" -#include "visitors/symtab_visitor.hpp" -#include "visitors/var_usage_visitor.hpp" -#include "visitors/visitor_utils.hpp" namespace nmodl { namespace codegen { using namespace ast; -using visitor::DefUseAnalyzeVisitor; -using visitor::DUState; using visitor::RenameVisitor; -using visitor::SymtabVisitor; -using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; + /****************************************************************************************/ -/* Overloaded visitor routines */ +/* Common helper routines accross codegen functions */ /****************************************************************************************/ -static const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; -void CodegenCppVisitor::visit_string(const String& node) { - if (!codegen) { - return; - } - std::string name = node.eval(); - if (enable_variable_name_lookup) { - name = get_variable_name(name); - } - printer->add_text(name); +template +bool CodegenCppVisitor::has_parameter_of_name(const T& node, const std::string& name) { + auto parameters = node->get_parameters(); + return std::any_of(parameters.begin(), + parameters.end(), + [&name](const decltype(*parameters.begin()) arg) { + return arg->get_node_name() == name; + }); } -void CodegenCppVisitor::visit_integer(const Integer& node) { - if (!codegen) { - return; +/** + * \details Certain statements like unit, comment, solve can/need to be skipped + * during code generation. Note that solve block is wrapped in expression + * statement and hence we have to check inner expression. It's also true + * for the initial block defined inside net receive block. + */ +bool CodegenCppVisitor::statement_to_skip(const Statement& node) { + // clang-format off + if (node.is_unit_state() + || node.is_line_comment() + || node.is_block_comment() + || node.is_solve_block() + || node.is_conductance_hint() + || node.is_table_statement()) { + return true; } - const auto& value = node.get_value(); - printer->add_text(std::to_string(value)); + // clang-format on + if (node.is_expression_statement()) { + auto expression = dynamic_cast(&node)->get_expression(); + if (expression->is_solve_block()) { + return true; + } + if (expression->is_initial_block()) { + return true; + } + } + return false; } -void CodegenCppVisitor::visit_float(const Float& node) { - if (!codegen) { - return; +bool CodegenCppVisitor::net_send_buffer_required() const noexcept { + if (net_receive_required() && !info.artificial_cell) { + if (info.net_event_used || info.net_send_used || info.is_watch_used()) { + return true; + } } - printer->add_text(format_float_string(node.get_value())); + return false; } -void CodegenCppVisitor::visit_double(const Double& node) { - if (!codegen) { - return; - } - printer->add_text(format_double_string(node.get_value())); +bool CodegenCppVisitor::net_receive_buffering_required() const noexcept { + return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; } -void CodegenCppVisitor::visit_boolean(const Boolean& node) { - if (!codegen) { - return; +bool CodegenCppVisitor::nrn_state_required() const noexcept { + if (info.artificial_cell) { + return false; } - printer->add_text(std::to_string(static_cast(node.eval()))); + return info.nrn_state_block != nullptr || breakpoint_exist(); } -void CodegenCppVisitor::visit_name(const Name& node) { - if (!codegen) { - return; - } - node.visit_children(*this); +bool CodegenCppVisitor::nrn_cur_required() const noexcept { + return info.breakpoint_node != nullptr && !info.currents.empty(); } -void CodegenCppVisitor::visit_unit(const ast::Unit& node) { - // do not print units +bool CodegenCppVisitor::net_receive_exist() const noexcept { + return info.net_receive_node != nullptr; } -void CodegenCppVisitor::visit_prime_name(const PrimeName& /* node */) { - throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); +bool CodegenCppVisitor::breakpoint_exist() const noexcept { + return info.breakpoint_node != nullptr; } -/** - * \todo : Validate how @ is being handled in neuron implementation - */ -void CodegenCppVisitor::visit_var_name(const VarName& node) { - if (!codegen) { - return; - } - const auto& name = node.get_name(); - const auto& at_index = node.get_at(); - const auto& index = node.get_index(); - name->accept(*this); - if (at_index) { - printer->add_text("@"); - at_index->accept(*this); - } - if (index) { - printer->add_text("["); - printer->add_text("static_cast("); - index->accept(*this); - printer->add_text(")"); - printer->add_text("]"); - } +bool CodegenCppVisitor::net_receive_required() const noexcept { + return net_receive_exist(); } -void CodegenCppVisitor::visit_indexed_name(const IndexedName& node) { - if (!codegen) { - return; - } - node.get_name()->accept(*this); - printer->add_text("["); - printer->add_text("static_cast("); - node.get_length()->accept(*this); - printer->add_text(")"); - printer->add_text("]"); +/** + * \details When floating point data type is not default (i.e. double) then we + * have to copy old array to new type (for range variables). + */ +bool CodegenCppVisitor::range_variable_setup_required() const noexcept { + return codegen::naming::DEFAULT_FLOAT_TYPE != float_data_type(); } -void CodegenCppVisitor::visit_local_list_statement(const LocalListStatement& node) { - if (!codegen) { - return; - } - printer->add_text(local_var_type(), ' '); - print_vector_elements(node.get_variables(), ", "); +// check if there is a function or procedure defined with given name +bool CodegenCppVisitor::defined_method(const std::string& name) const { + const auto& function = program_symtab->lookup(name); + auto properties = NmodlType::function_block | NmodlType::procedure_block; + return function && function->has_any_property(properties); } -void CodegenCppVisitor::visit_if_statement(const IfStatement& node) { - if (!codegen) { - return; - } - printer->add_text("if ("); - node.get_condition()->accept(*this); - printer->add_text(") "); - node.get_statement_block()->accept(*this); - print_vector_elements(node.get_elseifs(), ""); - const auto& elses = node.get_elses(); - if (elses) { - elses->accept(*this); - } +int CodegenCppVisitor::float_variables_size() const { + return codegen_float_variables.size(); } -void CodegenCppVisitor::visit_else_if_statement(const ElseIfStatement& node) { - if (!codegen) { - return; - } - printer->add_text(" else if ("); - node.get_condition()->accept(*this); - printer->add_text(") "); - node.get_statement_block()->accept(*this); +int CodegenCppVisitor::int_variables_size() const { + const auto count_semantics = [](int sum, const IndexSemantics& sem) { return sum += sem.size; }; + return std::accumulate(info.semantics.begin(), info.semantics.end(), 0, count_semantics); } -void CodegenCppVisitor::visit_else_statement(const ElseStatement& node) { - if (!codegen) { - return; - } - printer->add_text(" else "); - node.visit_children(*this); +/** + * \details We can directly print value but if user specify value as integer then + * then it gets printed as an integer. To avoid this, we use below wrapper. + * If user has provided integer then it gets printed as 1.0 (similar to mod2c + * and neuron where ".0" is appended). Otherwise we print double variables as + * they are represented in the mod file by user. If the value is in scientific + * representation (1e+20, 1E-15) then keep it as it is. + */ +std::string CodegenCppVisitor::format_double_string(const std::string& s_value) { + return utils::format_double_string(s_value); } -void CodegenCppVisitor::visit_while_statement(const WhileStatement& node) { - printer->add_text("while ("); - node.get_condition()->accept(*this); - printer->add_text(") "); - node.get_statement_block()->accept(*this); +std::string CodegenCppVisitor::format_float_string(const std::string& s_value) { + return utils::format_float_string(s_value); } -void CodegenCppVisitor::visit_from_statement(const ast::FromStatement& node) { - if (!codegen) { - return; +/** + * \details Statements like if, else etc. don't need semicolon at the end. + * (Note that it's valid to have "extraneous" semicolon). Also, statement + * block can appear as statement using expression statement which need to + * be inspected. + */ +bool CodegenCppVisitor::need_semicolon(const Statement& node) { + // clang-format off + if (node.is_if_statement() + || node.is_else_if_statement() + || node.is_else_statement() + || node.is_from_statement() + || node.is_verbatim() + || node.is_conductance_hint() + || node.is_while_statement() + || node.is_protect_statement() + || node.is_mutex_lock() + || node.is_mutex_unlock()) { + return false; } - auto name = node.get_node_name(); - const auto& from = node.get_from(); - const auto& to = node.get_to(); - const auto& inc = node.get_increment(); - const auto& block = node.get_statement_block(); - printer->fmt_text("for (int {} = ", name); - from->accept(*this); - printer->fmt_text("; {} <= ", name); - to->accept(*this); - if (inc) { - printer->fmt_text("; {} += ", name); - inc->accept(*this); - } else { - printer->fmt_text("; {}++", name); + if (node.is_expression_statement()) { + auto expression = dynamic_cast(node).get_expression(); + if (expression->is_statement_block() + || expression->is_eigen_newton_solver_block() + || expression->is_eigen_linear_solver_block() + || expression->is_solution_expression() + || expression->is_for_netcon()) { + return false; + } } - printer->add_text(") "); - block->accept(*this); + // clang-format on + return true; } -void CodegenCppVisitor::visit_paren_expression(const ParenExpression& node) { - if (!codegen) { - return; - } - printer->add_text("("); - node.get_expression()->accept(*this); - printer->add_text(")"); +/****************************************************************************************/ +/* Main printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenCppVisitor::print_prcellstate_macros() const { + printer->add_line("#ifndef NRN_PRCELLSTATE"); + printer->add_line("#define NRN_PRCELLSTATE 0"); + printer->add_line("#endif"); } -void CodegenCppVisitor::visit_binary_expression(const BinaryExpression& node) { - if (!codegen) { - return; - } - auto op = node.get_op().eval(); - const auto& lhs = node.get_lhs(); - const auto& rhs = node.get_rhs(); - if (op == "^") { - printer->add_text("pow("); - lhs->accept(*this); - printer->add_text(", "); - rhs->accept(*this); - printer->add_text(")"); - } else { - lhs->accept(*this); - printer->add_text(" " + op + " "); - rhs->accept(*this); - } -} - - -void CodegenCppVisitor::visit_binary_operator(const BinaryOperator& node) { - if (!codegen) { - return; - } - printer->add_text(node.eval()); -} - - -void CodegenCppVisitor::visit_unary_operator(const UnaryOperator& node) { - if (!codegen) { - return; - } - printer->add_text(" " + node.eval()); -} - +void CodegenCppVisitor::print_mechanism_info() { + auto variable_printer = [&](const std::vector& variables) { + for (const auto& v: variables) { + auto name = v->get_name(); + if (!info.point_process) { + name += "_" + info.mod_suffix; + } + if (v->is_array()) { + name += fmt::format("[{}]", v->get_length()); + } + printer->add_line(add_escape_quote(name), ","); + } + }; -/** - * \details Statement block is top level construct (for every nmodl block). - * Sometime we want to analyse ast nodes even if code generation is - * false. Hence we visit children even if code generation is false. - */ -void CodegenCppVisitor::visit_statement_block(const StatementBlock& node) { - if (!codegen) { - node.visit_children(*this); - return; - } - print_statement_block(node); + printer->add_newline(2); + printer->add_line("/** channel information */"); + printer->add_line("static const char *mechanism[] = {"); + printer->increase_indent(); + printer->add_line(add_escape_quote(nmodl_version()), ","); + printer->add_line(add_escape_quote(info.mod_suffix), ","); + variable_printer(info.range_parameter_vars); + printer->add_line("0,"); + variable_printer(info.range_assigned_vars); + printer->add_line("0,"); + variable_printer(info.range_state_vars); + printer->add_line("0,"); + variable_printer(info.pointer_variables); + printer->add_line("0"); + printer->decrease_indent(); + printer->add_line("};"); } -void CodegenCppVisitor::visit_function_call(const FunctionCall& node) { - if (!codegen) { - return; - } - print_function_call(node); -} +/****************************************************************************************/ +/* Printing routines for code generation */ +/****************************************************************************************/ -void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { - if (!codegen) { - return; +void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, + bool open_brace, + bool close_brace) { + if (open_brace) { + printer->push_block(); } - const auto& text = node.get_statement()->eval(); - const auto& result = process_verbatim_text(text); - const auto& statements = stringutils::split_string(result, '\n'); + const auto& statements = node.get_statements(); for (const auto& statement: statements) { - const auto& trimed_stmt = stringutils::trim_newline(statement); - if (trimed_stmt.find_first_not_of(' ') != std::string::npos) { - printer->add_line(trimed_stmt); + if (statement_to_skip(*statement)) { + continue; + } + /// not necessary to add indent for verbatim block (pretty-printing) + if (!statement->is_verbatim() && !statement->is_mutex_lock() && + !statement->is_mutex_unlock() && !statement->is_protect_statement()) { + printer->add_indent(); + } + statement->accept(*this); + if (need_semicolon(*statement)) { + printer->add_text(';'); + } + if (!statement->is_mutex_lock() && !statement->is_mutex_unlock()) { + printer->add_newline(); } } -} -void CodegenCppVisitor::visit_update_dt(const ast::UpdateDt& node) { - // dt change statement should be pulled outside already + if (close_brace) { + printer->pop_block_nl(0); + } } -void CodegenCppVisitor::visit_protect_statement(const ast::ProtectStatement& node) { - print_atomic_reduction_pragma(); - printer->add_indent(); - node.get_expression()->accept(*this); - printer->add_text(";"); -} -void CodegenCppVisitor::visit_mutex_lock(const ast::MutexLock& node) { - printer->fmt_line("#pragma omp critical ({})", info.mod_suffix); - printer->add_indent(); - printer->push_block(); +/** + * \todo Issue with verbatim renaming. e.g. pattern.mod has info struct with + * index variable. If we use "index" instead of "indexes" as default argument + * then during verbatim replacement we don't know the index is which one. This + * is because verbatim renaming pass has already stripped out prefixes from + * the text. + */ +void CodegenCppVisitor::rename_function_arguments() { + const auto& default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); + for (const auto& dirty_arg: default_arguments) { + const auto& arg = stringutils::trim(dirty_arg); + RenameVisitor v(arg, "arg_" + arg); + for (const auto& function: info.functions) { + if (has_parameter_of_name(function, arg)) { + function->accept(v); + } + } + for (const auto& function: info.procedures) { + if (has_parameter_of_name(function, arg)) { + function->accept(v); + } + } + } } -void CodegenCppVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { - printer->pop_block(); -} /****************************************************************************************/ -/* Common helper routines */ +/* Main code printing entry points */ /****************************************************************************************/ /** - * \details Certain statements like unit, comment, solve can/need to be skipped - * during code generation. Note that solve block is wrapped in expression - * statement and hence we have to check inner expression. It's also true - * for the initial block defined inside net receive block. + * NMODL constants from unit database + * */ -bool CodegenCppVisitor::statement_to_skip(const Statement& node) { - // clang-format off - if (node.is_unit_state() - || node.is_line_comment() - || node.is_block_comment() - || node.is_solve_block() - || node.is_conductance_hint() - || node.is_table_statement()) { - return true; - } - // clang-format on - if (node.is_expression_statement()) { - auto expression = dynamic_cast(&node)->get_expression(); - if (expression->is_solve_block()) { - return true; - } - if (expression->is_initial_block()) { - return true; +void CodegenCppVisitor::print_nmodl_constants() { + if (!info.factor_definitions.empty()) { + printer->add_newline(2); + printer->add_line("/** constants used in nmodl from UNITS */"); + for (const auto& it: info.factor_definitions) { + const std::string format_string = "static const double {} = {};"; + printer->fmt_line(format_string, it->get_node_name(), it->get_value()->get_value()); } } - return false; } -bool CodegenCppVisitor::net_send_buffer_required() const noexcept { - if (net_receive_required() && !info.artificial_cell) { - if (info.net_event_used || info.net_send_used || info.is_watch_used()) { - return true; - } - } - return false; -} +/****************************************************************************************/ +/* Overloaded visitor routines */ +/****************************************************************************************/ -bool CodegenCppVisitor::net_receive_buffering_required() const noexcept { - return info.point_process && !info.artificial_cell && info.net_receive_node != nullptr; -} +extern const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; -bool CodegenCppVisitor::nrn_state_required() const noexcept { - if (info.artificial_cell) { - return false; +void CodegenCppVisitor::visit_string(const String& node) { + if (!codegen) { + return; } - return info.nrn_state_block != nullptr || breakpoint_exist(); + std::string name = node.eval(); + if (enable_variable_name_lookup) { + name = get_variable_name(name); + } + printer->add_text(name); } -bool CodegenCppVisitor::nrn_cur_required() const noexcept { - return info.breakpoint_node != nullptr && !info.currents.empty(); +void CodegenCppVisitor::visit_integer(const Integer& node) { + if (!codegen) { + return; + } + const auto& value = node.get_value(); + printer->add_text(std::to_string(value)); } -bool CodegenCppVisitor::net_receive_exist() const noexcept { - return info.net_receive_node != nullptr; +void CodegenCppVisitor::visit_float(const Float& node) { + if (!codegen) { + return; + } + printer->add_text(format_float_string(node.get_value())); } -bool CodegenCppVisitor::breakpoint_exist() const noexcept { - return info.breakpoint_node != nullptr; +void CodegenCppVisitor::visit_double(const Double& node) { + if (!codegen) { + return; + } + printer->add_text(format_double_string(node.get_value())); } -bool CodegenCppVisitor::net_receive_required() const noexcept { - return net_receive_exist(); +void CodegenCppVisitor::visit_boolean(const Boolean& node) { + if (!codegen) { + return; + } + printer->add_text(std::to_string(static_cast(node.eval()))); } -/** - * \details When floating point data type is not default (i.e. double) then we - * have to copy old array to new type (for range variables). - */ -bool CodegenCppVisitor::range_variable_setup_required() const noexcept { - return codegen::naming::DEFAULT_FLOAT_TYPE != float_data_type(); +void CodegenCppVisitor::visit_name(const Name& node) { + if (!codegen) { + return; + } + node.visit_children(*this); } -int CodegenCppVisitor::position_of_float_var(const std::string& name) const { - int index = 0; - for (const auto& var: codegen_float_variables) { - if (var->get_name() == name) { - return index; - } - index += var->get_length(); - } - throw std::logic_error(name + " variable not found"); +void CodegenCppVisitor::visit_unit(const ast::Unit& node) { + // do not print units } -int CodegenCppVisitor::position_of_int_var(const std::string& name) const { - int index = 0; - for (const auto& var: codegen_int_variables) { - if (var.symbol->get_name() == name) { - return index; - } - index += var.symbol->get_length(); - } - throw std::logic_error(name + " variable not found"); +void CodegenCppVisitor::visit_prime_name(const PrimeName& /* node */) { + throw std::runtime_error("PRIME encountered during code generation, ODEs not solved?"); } /** - * \details We can directly print value but if user specify value as integer then - * then it gets printed as an integer. To avoid this, we use below wrapper. - * If user has provided integer then it gets printed as 1.0 (similar to mod2c - * and neuron where ".0" is appended). Otherwise we print double variables as - * they are represented in the mod file by user. If the value is in scientific - * representation (1e+20, 1E-15) then keep it as it is. + * \todo : Validate how @ is being handled in neuron implementation */ -std::string CodegenCppVisitor::format_double_string(const std::string& s_value) { - return utils::format_double_string(s_value); +void CodegenCppVisitor::visit_var_name(const VarName& node) { + if (!codegen) { + return; + } + const auto& name = node.get_name(); + const auto& at_index = node.get_at(); + const auto& index = node.get_index(); + name->accept(*this); + if (at_index) { + printer->add_text("@"); + at_index->accept(*this); + } + if (index) { + printer->add_text("["); + printer->add_text("static_cast("); + index->accept(*this); + printer->add_text(")"); + printer->add_text("]"); + } } -std::string CodegenCppVisitor::format_float_string(const std::string& s_value) { - return utils::format_float_string(s_value); +void CodegenCppVisitor::visit_indexed_name(const IndexedName& node) { + if (!codegen) { + return; + } + node.get_name()->accept(*this); + printer->add_text("["); + printer->add_text("static_cast("); + node.get_length()->accept(*this); + printer->add_text(")"); + printer->add_text("]"); } -/** - * \details Statements like if, else etc. don't need semicolon at the end. - * (Note that it's valid to have "extraneous" semicolon). Also, statement - * block can appear as statement using expression statement which need to - * be inspected. - */ -bool CodegenCppVisitor::need_semicolon(const Statement& node) { - // clang-format off - if (node.is_if_statement() - || node.is_else_if_statement() - || node.is_else_statement() - || node.is_from_statement() - || node.is_verbatim() - || node.is_conductance_hint() - || node.is_while_statement() - || node.is_protect_statement() - || node.is_mutex_lock() - || node.is_mutex_unlock()) { - return false; - } - if (node.is_expression_statement()) { - auto expression = dynamic_cast(node).get_expression(); - if (expression->is_statement_block() - || expression->is_eigen_newton_solver_block() - || expression->is_eigen_linear_solver_block() - || expression->is_solution_expression() - || expression->is_for_netcon()) { - return false; - } +void CodegenCppVisitor::visit_local_list_statement(const LocalListStatement& node) { + if (!codegen) { + return; } - // clang-format on - return true; + printer->add_text(local_var_type(), ' '); + print_vector_elements(node.get_variables(), ", "); } -// check if there is a function or procedure defined with given name -bool CodegenCppVisitor::defined_method(const std::string& name) const { - const auto& function = program_symtab->lookup(name); - auto properties = NmodlType::function_block | NmodlType::procedure_block; - return function && function->has_any_property(properties); +void CodegenCppVisitor::visit_if_statement(const IfStatement& node) { + if (!codegen) { + return; + } + printer->add_text("if ("); + node.get_condition()->accept(*this); + printer->add_text(") "); + node.get_statement_block()->accept(*this); + print_vector_elements(node.get_elseifs(), ""); + const auto& elses = node.get_elses(); + if (elses) { + elses->accept(*this); + } } -/** - * \details Current variable used in breakpoint block could be local variable. - * In this case, neuron has already renamed the variable name by prepending - * "_l". In our implementation, the variable could have been renamed by - * one of the pass. And hence, we search all local variables and check if - * the variable is renamed. Note that we have to look into the symbol table - * of statement block and not breakpoint. - */ -std::string CodegenCppVisitor::breakpoint_current(std::string current) const { - auto breakpoint = info.breakpoint_node; - if (breakpoint == nullptr) { - return current; +void CodegenCppVisitor::visit_else_if_statement(const ElseIfStatement& node) { + if (!codegen) { + return; } - auto symtab = breakpoint->get_statement_block()->get_symbol_table(); - auto variables = symtab->get_variables_with_properties(NmodlType::local_var); - for (const auto& var: variables) { - auto renamed_name = var->get_name(); - auto original_name = var->get_original_name(); - if (current == original_name) { - current = renamed_name; - break; - } + printer->add_text(" else if ("); + node.get_condition()->accept(*this); + printer->add_text(") "); + node.get_statement_block()->accept(*this); +} + + +void CodegenCppVisitor::visit_else_statement(const ElseStatement& node) { + if (!codegen) { + return; } - return current; + printer->add_text(" else "); + node.visit_children(*this); } -int CodegenCppVisitor::float_variables_size() const { - return codegen_float_variables.size(); +void CodegenCppVisitor::visit_while_statement(const WhileStatement& node) { + printer->add_text("while ("); + node.get_condition()->accept(*this); + printer->add_text(") "); + node.get_statement_block()->accept(*this); } -int CodegenCppVisitor::int_variables_size() const { - const auto count_semantics = [](int sum, const IndexSemantics& sem) { return sum += sem.size; }; - return std::accumulate(info.semantics.begin(), info.semantics.end(), 0, count_semantics); +void CodegenCppVisitor::visit_from_statement(const ast::FromStatement& node) { + if (!codegen) { + return; + } + auto name = node.get_node_name(); + const auto& from = node.get_from(); + const auto& to = node.get_to(); + const auto& inc = node.get_increment(); + const auto& block = node.get_statement_block(); + printer->fmt_text("for (int {} = ", name); + from->accept(*this); + printer->fmt_text("; {} <= ", name); + to->accept(*this); + if (inc) { + printer->fmt_text("; {} += ", name); + inc->accept(*this); + } else { + printer->fmt_text("; {}++", name); + } + printer->add_text(") "); + block->accept(*this); } -/** - * \details Depending upon the block type, we have to print read/write ion variables - * during code generation. Depending on block/procedure being printed, this - * method return statements as vector. As different code backends could have - * different variable names, we rely on backend-specific read_ion_variable_name - * and write_ion_variable_name method which will be overloaded. - */ -std::vector CodegenCppVisitor::ion_read_statements(BlockType type) const { - if (optimize_ion_variable_copies()) { - return ion_read_statements_optimized(type); +void CodegenCppVisitor::visit_paren_expression(const ParenExpression& node) { + if (!codegen) { + return; } - std::vector statements; - for (const auto& ion: info.ions) { - auto name = ion.name; - for (const auto& var: ion.reads) { - auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var); - if (iter != ion.implicit_reads.end()) { - continue; - } - auto variable_names = read_ion_variable_name(var); - auto first = get_variable_name(variable_names.first); - auto second = get_variable_name(variable_names.second); - statements.push_back(fmt::format("{} = {};", first, second)); - } - for (const auto& var: ion.writes) { - if (ion.is_ionic_conc(var)) { - auto variables = read_ion_variable_name(var); - auto first = get_variable_name(variables.first); - auto second = get_variable_name(variables.second); - statements.push_back(fmt::format("{} = {};", first, second)); - } - } + printer->add_text("("); + node.get_expression()->accept(*this); + printer->add_text(")"); +} + + +void CodegenCppVisitor::visit_binary_expression(const BinaryExpression& node) { + if (!codegen) { + return; + } + auto op = node.get_op().eval(); + const auto& lhs = node.get_lhs(); + const auto& rhs = node.get_rhs(); + if (op == "^") { + printer->add_text("pow("); + lhs->accept(*this); + printer->add_text(", "); + rhs->accept(*this); + printer->add_text(")"); + } else { + lhs->accept(*this); + printer->add_text(" " + op + " "); + rhs->accept(*this); } - return statements; } -std::vector CodegenCppVisitor::ion_read_statements_optimized(BlockType type) const { - std::vector statements; - for (const auto& ion: info.ions) { - for (const auto& var: ion.writes) { - if (ion.is_ionic_conc(var)) { - auto variables = read_ion_variable_name(var); - auto first = "ionvar." + variables.first; - const auto& second = get_variable_name(variables.second); - statements.push_back(fmt::format("{} = {};", first, second)); - } - } +void CodegenCppVisitor::visit_binary_operator(const BinaryOperator& node) { + if (!codegen) { + return; } - return statements; + printer->add_text(node.eval()); } -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -std::vector CodegenCppVisitor::ion_write_statements(BlockType type) { - std::vector statements; - for (const auto& ion: info.ions) { - std::string concentration; - auto name = ion.name; - for (const auto& var: ion.writes) { - auto variable_names = write_ion_variable_name(var); - if (ion.is_ionic_current(var)) { - if (type == BlockType::Equation) { - auto current = breakpoint_current(var); - auto lhs = variable_names.first; - auto op = "+="; - auto rhs = get_variable_name(current); - if (info.point_process) { - auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - rhs += fmt::format("*(1.e2/{})", area); - } - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - } - } else { - if (!ion.is_rev_potential(var)) { - concentration = var; - } - auto lhs = variable_names.first; - auto op = "="; - auto rhs = get_variable_name(variable_names.second); - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - } - } - if (type == BlockType::Initial && !concentration.empty()) { - int index = 0; - if (ion.is_intra_cell_conc(concentration)) { - index = 1; - } else if (ion.is_extra_cell_conc(concentration)) { - index = 2; - } else { - /// \todo Unhandled case in neuron implementation - throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); - } - auto ion_type_name = fmt::format("{}_type", ion.name); - auto lhs = fmt::format("int {}", ion_type_name); - auto op = "="; - auto rhs = get_variable_name(ion_type_name); - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - auto statement = conc_write_statement(ion.name, concentration, index); - statements.push_back(ShadowUseStatement{statement, "", ""}); - } +void CodegenCppVisitor::visit_unary_operator(const UnaryOperator& node) { + if (!codegen) { + return; } - return statements; + printer->add_text(" " + node.eval()); } /** - * \details Often top level verbatim blocks use variables with old names. - * Here we process if we are processing verbatim block at global scope. + * \details Statement block is top level construct (for every nmodl block). + * Sometime we want to analyse ast nodes even if code generation is + * false. Hence we visit children even if code generation is false. */ -std::string CodegenCppVisitor::process_verbatim_token(const std::string& token) { - const std::string& name = token; +void CodegenCppVisitor::visit_statement_block(const StatementBlock& node) { + if (!codegen) { + node.visit_children(*this); + return; + } + print_statement_block(node); +} - /* - * If given token is procedure name and if it's defined - * in the current mod file then it must be replaced - */ - if (program_symtab->is_method_defined(token)) { - return method_name(token); + +void CodegenCppVisitor::visit_function_call(const FunctionCall& node) { + if (!codegen) { + return; } + print_function_call(node); +} - /* - * Check if token is commongly used variable name in - * verbatim block like nt, \c \_threadargs etc. If so, replace - * it and return. - */ - auto new_name = replace_if_verbatim_variable(name); - if (new_name != name) { - return get_variable_name(new_name, false); + +void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { + if (!codegen) { + return; } + const auto& text = node.get_statement()->eval(); + const auto& result = process_verbatim_text(text); - /* - * For top level verbatim blocks we shouldn't replace variable - * names with Instance because arguments are provided from coreneuron - * and they are missing inst. - */ - auto use_instance = !printing_top_verbatim_blocks; - return get_variable_name(token, use_instance); + const auto& statements = stringutils::split_string(result, '\n'); + for (const auto& statement: statements) { + const auto& trimed_stmt = stringutils::trim_newline(statement); + if (trimed_stmt.find_first_not_of(' ') != std::string::npos) { + printer->add_line(trimed_stmt); + } + } } -bool CodegenCppVisitor::ion_variable_struct_required() const { - return optimize_ion_variable_copies() && info.ion_has_write_variable(); +void CodegenCppVisitor::visit_update_dt(const ast::UpdateDt& node) { + // dt change statement should be pulled outside already } -/** - * \details This can be override in the backend. For example, parameters can be constant - * except in INITIAL block where they are set to 0. As initial block is/can be - * executed on c++/cpu backend, gpu backend can mark the parameter as constant. - */ -bool CodegenCppVisitor::is_constant_variable(const std::string& name) const { - auto symbol = program_symtab->lookup_in_scope(name); - bool is_constant = false; - if (symbol != nullptr) { - // per mechanism ion variables needs to be updated from neuron/coreneuron values - if (info.is_ion_variable(name)) { - is_constant = false; - } - // for parameter variable to be const, make sure it's write count is 0 - // and it's not used in the verbatim block - else if (symbol->has_any_property(NmodlType::param_assign) && - info.variables_in_verbatim.find(name) == info.variables_in_verbatim.end() && - symbol->get_write_count() == 0) { - is_constant = true; - } - } - return is_constant; +void CodegenCppVisitor::visit_protect_statement(const ast::ProtectStatement& node) { + print_atomic_reduction_pragma(); + printer->add_indent(); + node.get_expression()->accept(*this); + printer->add_text(";"); } -/** - * \details Once variables are populated, update index semantics to register with coreneuron - */ -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCppVisitor::update_index_semantics() { - int index = 0; - info.semantics.clear(); - - if (info.point_process) { +void CodegenCppVisitor::visit_mutex_lock(const ast::MutexLock& node) { + printer->fmt_line("#pragma omp critical ({})", info.mod_suffix); + printer->add_indent(); + printer->push_block(); +} + + +void CodegenCppVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { + printer->pop_block(); +} + + +/** + * \details Once variables are populated, update index semantics to register with coreneuron + */ +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +void CodegenCppVisitor::update_index_semantics() { + int index = 0; + info.semantics.clear(); + + if (info.point_process) { info.semantics.emplace_back(index++, naming::AREA_SEMANTIC, 1); info.semantics.emplace_back(index++, naming::POINT_PROCESS_SEMANTIC, 1); } @@ -968,3715 +873,6 @@ std::vector CodegenCppVisitor::get_int_variables() { } -/****************************************************************************************/ -/* Routines must be overloaded in backend */ -/****************************************************************************************/ - -std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) { - std::string str; - bool is_first = true; - for (const auto& param: params) { - if (is_first) { - is_first = false; - } else { - str += ", "; - } - str += fmt::format("{}{} {}{}", - std::get<0>(param), - std::get<1>(param), - std::get<2>(param), - std::get<3>(param)); - } - return str; -} - - -void CodegenCppVisitor::print_deriv_advance_flag_transfer_to_device() const { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_device_atomic_capture_annotation() const { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_net_send_buf_count_update_to_host() const { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_net_send_buf_update_to_host() const { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_net_send_buf_count_update_to_device() const { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_dt_update_to_device() const { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_device_stream_wait() const { - // backend specific, do nothing -} - -/** - * \details Each kernel such as \c nrn\_init, \c nrn\_state and \c nrn\_cur could be offloaded - * to accelerator. In this case, at very top level, we print pragma - * for data present. For example: - * - * \code{.cpp} - * void nrn_state(...) { - * #pragma acc data present (nt, ml...) - * { - * - * } - * } - * \endcode - */ -void CodegenCppVisitor::print_kernel_data_present_annotation_block_begin() { - // backend specific, do nothing -} - - -void CodegenCppVisitor::print_kernel_data_present_annotation_block_end() { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_net_init_acc_serial_annotation_block_begin() { - // backend specific, do nothing -} - -void CodegenCppVisitor::print_net_init_acc_serial_annotation_block_end() { - // backend specific, do nothing -} - -/** - * \details Depending programming model and compiler, we print compiler hint - * for parallelization. For example: - * - * \code - * #pragma omp simd - * for(int id = 0; id < nodecount; id++) { - * - * #pragma acc parallel loop - * for(int id = 0; id < nodecount; id++) { - * \endcode - */ -void CodegenCppVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, - const ast::Block* block) { - // ivdep allows SIMD parallelisation of a block/loop but doesn't provide - // a standard mechanism for atomics. Also, even with openmp 5.0, openmp - // atomics do not enable vectorisation under "omp simd" (gives compiler - // error with gcc < 9 if atomic and simd pragmas are nested). So, emit - // ivdep/simd pragma when no MUTEXLOCK/MUTEXUNLOCK/PROTECT statements - // are used in the given block. - std::vector> nodes; - if (block) { - nodes = collect_nodes(*block, - {ast::AstNodeType::PROTECT_STATEMENT, - ast::AstNodeType::MUTEX_LOCK, - ast::AstNodeType::MUTEX_UNLOCK}); - } - if (nodes.empty()) { - printer->add_line("#pragma omp simd"); - printer->add_line("#pragma ivdep"); - } -} - - -bool CodegenCppVisitor::nrn_cur_reduction_loop_required() { - return info.point_process; -} - - -void CodegenCppVisitor::print_rhs_d_shadow_variables() { - if (info.point_process) { - printer->fmt_line("double* shadow_rhs = nt->{};", naming::NTHREAD_RHS_SHADOW); - printer->fmt_line("double* shadow_d = nt->{};", naming::NTHREAD_D_SHADOW); - } -} - - -void CodegenCppVisitor::print_nrn_cur_matrix_shadow_update() { - if (info.point_process) { - printer->add_line("shadow_rhs[id] = rhs;"); - printer->add_line("shadow_d[id] = g;"); - } else { - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - printer->fmt_line("vec_rhs[node_id] {} rhs;", rhs_op); - printer->fmt_line("vec_d[node_id] {} g;", d_op); - } -} - - -void CodegenCppVisitor::print_nrn_cur_matrix_shadow_reduction() { - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - if (info.point_process) { - printer->add_line("int node_id = node_index[id];"); - printer->fmt_line("vec_rhs[node_id] {} shadow_rhs[id];", rhs_op); - printer->fmt_line("vec_d[node_id] {} shadow_d[id];", d_op); - } -} - - -/** - * In the current implementation of CPU/CPP backend we need to emit atomic pragma - * only with PROTECT construct (atomic rduction requirement for other cases on CPU - * is handled via separate shadow vectors). - */ -void CodegenCppVisitor::print_atomic_reduction_pragma() { - printer->add_line("#pragma omp atomic update"); -} - - -void CodegenCppVisitor::print_device_method_annotation() { - // backend specific, nothing for cpu -} - - -void CodegenCppVisitor::print_global_method_annotation() { - // backend specific, nothing for cpu -} - - -void CodegenCppVisitor::print_backend_namespace_start() { - // no separate namespace for C++ (cpu) backend -} - - -void CodegenCppVisitor::print_backend_namespace_stop() { - // no separate namespace for C++ (cpu) backend -} - - -void CodegenCppVisitor::print_backend_includes() { - // backend specific, nothing for cpu -} - - -std::string CodegenCppVisitor::backend_name() const { - return "C++ (api-compatibility)"; -} - - -bool CodegenCppVisitor::optimize_ion_variable_copies() const { - return optimize_ionvar_copies; -} - - -void CodegenCppVisitor::print_memory_allocation_routine() const { - printer->add_newline(2); - auto args = "size_t num, size_t size, size_t alignment = 16"; - printer->fmt_push_block("static inline void* mem_alloc({})", args); - printer->add_line("void* ptr;"); - printer->add_line("posix_memalign(&ptr, alignment, num*size);"); - printer->add_line("memset(ptr, 0, size);"); - printer->add_line("return ptr;"); - printer->pop_block(); - - printer->add_newline(2); - printer->push_block("static inline void mem_free(void* ptr)"); - printer->add_line("free(ptr);"); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_abort_routine() const { - printer->add_newline(2); - printer->push_block("static inline void coreneuron_abort()"); - printer->add_line("abort();"); - printer->pop_block(); -} - - -std::string CodegenCppVisitor::compute_method_name(BlockType type) const { - if (type == BlockType::Initial) { - return method_name(naming::NRN_INIT_METHOD); - } - if (type == BlockType::Constructor) { - return method_name(naming::NRN_CONSTRUCTOR_METHOD); - } - if (type == BlockType::Destructor) { - return method_name(naming::NRN_DESTRUCTOR_METHOD); - } - if (type == BlockType::State) { - return method_name(naming::NRN_STATE_METHOD); - } - if (type == BlockType::Equation) { - return method_name(naming::NRN_CUR_METHOD); - } - if (type == BlockType::Watch) { - return method_name(naming::NRN_WATCH_CHECK_METHOD); - } - throw std::logic_error("compute_method_name not implemented"); -} - - -std::string CodegenCppVisitor::global_var_struct_type_qualifier() { - return ""; -} - -void CodegenCppVisitor::print_global_var_struct_decl() { - printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); -} - -/****************************************************************************************/ -/* printing routines for code generation */ -/****************************************************************************************/ - - -void CodegenCppVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { - printer->add_text(fmt::format("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)", - current_watch_statement++)); -} - - -void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, - bool open_brace, - bool close_brace) { - if (open_brace) { - printer->push_block(); - } - - const auto& statements = node.get_statements(); - for (const auto& statement: statements) { - if (statement_to_skip(*statement)) { - continue; - } - /// not necessary to add indent for verbatim block (pretty-printing) - if (!statement->is_verbatim() && !statement->is_mutex_lock() && - !statement->is_mutex_unlock() && !statement->is_protect_statement()) { - printer->add_indent(); - } - statement->accept(*this); - if (need_semicolon(*statement)) { - printer->add_text(';'); - } - if (!statement->is_mutex_lock() && !statement->is_mutex_unlock()) { - printer->add_newline(); - } - } - - if (close_brace) { - printer->pop_block_nl(0); - } -} - - -void CodegenCppVisitor::print_function_call(const FunctionCall& node) { - const auto& name = node.get_node_name(); - auto function_name = name; - if (defined_method(name)) { - function_name = method_name(name); - } - - if (is_net_send(name)) { - print_net_send_call(node); - return; - } - - if (is_net_move(name)) { - print_net_move_call(node); - return; - } - - if (is_net_event(name)) { - print_net_event_call(node); - return; - } - - const auto& arguments = node.get_arguments(); - printer->add_text(function_name, '('); - - if (defined_method(name)) { - printer->add_text(internal_method_arguments()); - if (!arguments.empty()) { - printer->add_text(", "); - } - } - - print_vector_elements(arguments, ", "); - printer->add_text(')'); -} - - -void CodegenCppVisitor::print_top_verbatim_blocks() { - if (info.top_verbatim_blocks.empty()) { - return; - } - print_namespace_stop(); - - printer->add_newline(2); - printer->add_line("using namespace coreneuron;"); - codegen = true; - printing_top_verbatim_blocks = true; - - for (const auto& block: info.top_blocks) { - if (block->is_verbatim()) { - printer->add_newline(2); - block->accept(*this); - } - } - - printing_top_verbatim_blocks = false; - codegen = false; - print_namespace_start(); -} - - -/** - * \todo Issue with verbatim renaming. e.g. pattern.mod has info struct with - * index variable. If we use "index" instead of "indexes" as default argument - * then during verbatim replacement we don't know the index is which one. This - * is because verbatim renaming pass has already stripped out prefixes from - * the text. - */ -void CodegenCppVisitor::rename_function_arguments() { - const auto& default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); - for (const auto& dirty_arg: default_arguments) { - const auto& arg = stringutils::trim(dirty_arg); - RenameVisitor v(arg, "arg_" + arg); - for (const auto& function: info.functions) { - if (has_parameter_of_name(function, arg)) { - function->accept(v); - } - } - for (const auto& function: info.procedures) { - if (has_parameter_of_name(function, arg)) { - function->accept(v); - } - } - } -} - - -void CodegenCppVisitor::print_function_prototypes() { - if (info.functions.empty() && info.procedures.empty()) { - return; - } - codegen = true; - printer->add_newline(2); - for (const auto& node: info.functions) { - print_function_declaration(*node, node->get_node_name()); - printer->add_text(';'); - printer->add_newline(); - } - for (const auto& node: info.procedures) { - print_function_declaration(*node, node->get_node_name()); - printer->add_text(';'); - printer->add_newline(); - } - codegen = false; -} - - -static const TableStatement* get_table_statement(const ast::Block& node) { - // TableStatementVisitor v; - - const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); - - if (table_statements.size() != 1) { - auto message = fmt::format("One table statement expected in {} found {}", - node.get_node_name(), - table_statements.size()); - throw std::runtime_error(message); - } - return dynamic_cast(table_statements.front().get()); -} - - -std::tuple CodegenCppVisitor::check_if_var_is_array(const std::string& name) { - auto symbol = program_symtab->lookup_in_scope(name); - if (!symbol) { - throw std::runtime_error( - fmt::format("CodegenCppVisitor:: {} not found in symbol table!", name)); - } - if (symbol->is_array()) { - return {true, symbol->get_length()}; - } else { - return {false, 0}; - } -} - - -void CodegenCppVisitor::print_table_check_function(const Block& node) { - auto statement = get_table_statement(node); - auto table_variables = statement->get_table_vars(); - auto depend_variables = statement->get_depend_vars(); - const auto& from = statement->get_from(); - const auto& to = statement->get_to(); - auto name = node.get_node_name(); - auto internal_params = internal_method_parameters(); - auto with = statement->get_with()->eval(); - auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); - auto tmin_name = get_variable_name("tmin_" + name); - auto mfac_name = get_variable_name("mfac_" + name); - auto float_type = default_float_data_type(); - - printer->add_newline(2); - print_device_method_annotation(); - printer->fmt_push_block("void check_{}({})", - method_name(name), - get_parameter_str(internal_params)); - { - printer->fmt_push_block("if ({} == 0)", use_table_var); - printer->add_line("return;"); - printer->pop_block(); - - printer->add_line("static bool make_table = true;"); - for (const auto& variable: depend_variables) { - printer->fmt_line("static {} save_{};", float_type, variable->get_node_name()); - } - - for (const auto& variable: depend_variables) { - const auto& var_name = variable->get_node_name(); - const auto& instance_name = get_variable_name(var_name); - printer->fmt_push_block("if (save_{} != {})", var_name, instance_name); - printer->add_line("make_table = true;"); - printer->pop_block(); - } - - printer->push_block("if (make_table)"); - { - printer->add_line("make_table = false;"); - - printer->add_indent(); - printer->add_text(tmin_name, " = "); - from->accept(*this); - printer->add_text(';'); - printer->add_newline(); - - printer->add_indent(); - printer->add_text("double tmax = "); - to->accept(*this); - printer->add_text(';'); - printer->add_newline(); - - - printer->fmt_line("double dx = (tmax-{}) / {}.;", tmin_name, with); - printer->fmt_line("{} = 1./dx;", mfac_name); - - printer->fmt_line("double x = {};", tmin_name); - printer->fmt_push_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); - auto function = method_name("f_" + name); - if (node.is_procedure_block()) { - printer->fmt_line("{}({}, x);", function, internal_method_arguments()); - for (const auto& variable: table_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - auto table_name = get_variable_name("t_" + var_name); - auto [is_array, array_length] = check_if_var_is_array(var_name); - if (is_array) { - for (int j = 0; j < array_length; j++) { - printer->fmt_line( - "{}[{}][i] = {}[{}];", table_name, j, instance_name, j); - } - } else { - printer->fmt_line("{}[i] = {};", table_name, instance_name); - } - } - } else { - auto table_name = get_variable_name("t_" + name); - printer->fmt_line("{}[i] = {}({}, x);", - table_name, - function, - internal_method_arguments()); - } - printer->pop_block(); - - for (const auto& variable: depend_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - printer->fmt_line("save_{} = {};", var_name, instance_name); - } - } - printer->pop_block(); - } - printer->pop_block(); -} - - -void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) { - auto name = node.get_node_name(); - auto statement = get_table_statement(node); - auto table_variables = statement->get_table_vars(); - auto with = statement->get_with()->eval(); - auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); - auto tmin_name = get_variable_name("tmin_" + name); - auto mfac_name = get_variable_name("mfac_" + name); - auto function_name = method_name("f_" + name); - - printer->add_newline(2); - print_function_declaration(node, name); - printer->push_block(); - { - const auto& params = node.get_parameters(); - printer->fmt_push_block("if ({} == 0)", use_table_var); - if (node.is_procedure_block()) { - printer->fmt_line("{}({}, {});", - function_name, - internal_method_arguments(), - params[0].get()->get_node_name()); - printer->add_line("return 0;"); - } else { - printer->fmt_line("return {}({}, {});", - function_name, - internal_method_arguments(), - params[0].get()->get_node_name()); - } - printer->pop_block(); - - printer->fmt_line("double xi = {} * ({} - {});", - mfac_name, - params[0].get()->get_node_name(), - tmin_name); - printer->push_block("if (isnan(xi))"); - if (node.is_procedure_block()) { - for (const auto& var: table_variables) { - auto var_name = get_variable_name(var->get_node_name()); - auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); - if (is_array) { - for (int j = 0; j < array_length; j++) { - printer->fmt_line("{}[{}] = xi;", var_name, j); - } - } else { - printer->fmt_line("{} = xi;", var_name); - } - } - printer->add_line("return 0;"); - } else { - printer->add_line("return xi;"); - } - printer->pop_block(); - - printer->fmt_push_block("if (xi <= 0. || xi >= {}.)", with); - printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); - if (node.is_procedure_block()) { - for (const auto& variable: table_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - auto table_name = get_variable_name("t_" + var_name); - auto [is_array, array_length] = check_if_var_is_array(var_name); - if (is_array) { - for (int j = 0; j < array_length; j++) { - printer->fmt_line( - "{}[{}] = {}[{}][index];", instance_name, j, table_name, j); - } - } else { - printer->fmt_line("{} = {}[index];", instance_name, table_name); - } - } - printer->add_line("return 0;"); - } else { - auto table_name = get_variable_name("t_" + name); - printer->fmt_line("return {}[index];", table_name); - } - printer->pop_block(); - - printer->add_line("int i = int(xi);"); - printer->add_line("double theta = xi - double(i);"); - if (node.is_procedure_block()) { - for (const auto& var: table_variables) { - auto var_name = var->get_node_name(); - auto instance_name = get_variable_name(var_name); - auto table_name = get_variable_name("t_" + var_name); - auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); - if (is_array) { - for (size_t j = 0; j < array_length; j++) { - printer->fmt_line( - "{0}[{1}] = {2}[{1}][i] + theta*({2}[{1}][i+1]-{2}[{1}][i]);", - instance_name, - j, - table_name); - } - } else { - printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", - instance_name, - table_name); - } - } - printer->add_line("return 0;"); - } else { - auto table_name = get_variable_name("t_" + name); - printer->fmt_line("return {0}[i] + theta * ({0}[i+1] - {0}[i]);", table_name); - } - } - printer->pop_block(); -} - - -void CodegenCppVisitor::print_check_table_thread_function() { - if (info.table_count == 0) { - return; - } - - printer->add_newline(2); - auto name = method_name("check_table_thread"); - auto parameters = external_method_parameters(true); - - printer->fmt_push_block("static void {} ({})", name, parameters); - printer->add_line("setup_instance(nt, ml);"); - printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); - printer->add_line("double v = 0;"); - - for (const auto& function: info.functions_with_table) { - auto method_name_str = method_name("check_" + function->get_node_name()); - auto arguments = internal_method_arguments(); - printer->fmt_line("{}({});", method_name_str, arguments); - } - - printer->pop_block(); -} - - -void CodegenCppVisitor::print_function_or_procedure(const ast::Block& node, - const std::string& name) { - printer->add_newline(2); - print_function_declaration(node, name); - printer->add_text(" "); - printer->push_block(); - - // function requires return variable declaration - if (node.is_function_block()) { - auto type = default_float_data_type(); - printer->fmt_line("{} ret_{} = 0.0;", type, name); - } else { - printer->fmt_line("int ret_{} = 0;", name); - } - - print_statement_block(*node.get_statement_block(), false, false); - printer->fmt_line("return ret_{};", name); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_function_procedure_helper(const ast::Block& node) { - codegen = true; - auto name = node.get_node_name(); - - if (info.function_uses_table(name)) { - auto new_name = "f_" + name; - print_function_or_procedure(node, new_name); - print_table_check_function(node); - print_table_replacement_function(node); - } else { - print_function_or_procedure(node, name); - } - - codegen = false; -} - - -void CodegenCppVisitor::print_procedure(const ast::ProcedureBlock& node) { - print_function_procedure_helper(node); -} - - -void CodegenCppVisitor::print_function(const ast::FunctionBlock& node) { - auto name = node.get_node_name(); - - // name of return variable - std::string return_var; - if (info.function_uses_table(name)) { - return_var = "ret_f_" + name; - } else { - return_var = "ret_" + name; - } - - // first rename return variable name - auto block = node.get_statement_block().get(); - RenameVisitor v(name, return_var); - block->accept(v); - - print_function_procedure_helper(node); -} - - -void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { - auto name = node.get_node_name(); - const auto& p = node.get_parameters(); - auto params = internal_method_parameters(); - for (const auto& i: p) { - params.emplace_back("", "double", "", i->get_node_name()); - } - printer->fmt_line("double {}({})", method_name(name), get_parameter_str(params)); - printer->push_block(); - printer->fmt_line("double _arg[{}];", p.size()); - for (size_t i = 0; i < p.size(); ++i) { - printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); - } - printer->fmt_line("return hoc_func_table({}, {}, _arg);", - get_variable_name(std::string("_ptable_" + name), true), - p.size()); - printer->pop_block(); - - printer->fmt_push_block("double table_{}()", method_name(name)); - printer->fmt_line("hoc_spec_table(&{}, {});", - get_variable_name(std::string("_ptable_" + name)), - p.size()); - printer->add_line("return 0.;"); - printer->pop_block(); -} - -/** - * @brief Checks whether the functor_block generated by sympy solver modifies any variable outside - * its scope. If it does then return false, so that the operator() of the struct functor of the - * Eigen Newton solver doesn't have const qualifier. - * - * @param variable_block Statement Block of the variables declarations used in the functor struct of - * the solver - * @param functor_block Actual code being printed in the operator() of the functor struct of the - * solver - * @return True if operator() is const else False - */ -bool is_functor_const(const ast::StatementBlock& variable_block, - const ast::StatementBlock& functor_block) { - // Create complete_block with both variable declarations (done in variable_block) and solver - // part (done in functor_block) to be able to run the SymtabVisitor and DefUseAnalyzeVisitor - // then and get the proper DUChains for the variables defined in the variable_block - ast::StatementBlock complete_block(functor_block); - // Typically variable_block has only one statement, a statement containing the declaration - // of the local variables - for (const auto& statement: variable_block.get_statements()) { - complete_block.insert_statement(complete_block.get_statements().begin(), statement); - } - - // Create Symbol Table for complete_block - auto model_symbol_table = std::make_shared(); - SymtabVisitor(model_symbol_table.get()).visit_statement_block(complete_block); - // Initialize DefUseAnalyzeVisitor to generate the DUChains for the variables defined in the - // variable_block - DefUseAnalyzeVisitor v(*complete_block.get_symbol_table()); - - // Check the DUChains for all the variables in the variable_block - // If variable is defined in complete_block don't add const quilifier in operator() - auto is_functor_const = true; - const auto& variables = collect_nodes(variable_block, {ast::AstNodeType::LOCAL_VAR}); - for (const auto& variable: variables) { - const auto& chain = v.analyze(complete_block, variable->get_node_name()); - is_functor_const = !(chain.eval() == DUState::D || chain.eval() == DUState::LD || - chain.eval() == DUState::CD); - if (!is_functor_const) { - break; - } - } - - return is_functor_const; -} - -void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlock& node) { - // functor that evaluates F(X) and J(X) for - // Newton solver - auto float_type = default_float_data_type(); - int N = node.get_n_state_vars()->get_value(); - - const auto functor_name = info.functor_names[&node]; - printer->fmt_push_block("struct {0}", functor_name); - printer->add_line("NrnThread* nt;"); - printer->add_line(instance_struct(), "* inst;"); - printer->add_line("int id, pnodecount;"); - printer->add_line("double v;"); - printer->add_line("const Datum* indexes;"); - printer->add_line("double* data;"); - printer->add_line("ThreadDatum* thread;"); - - if (ion_variable_struct_required()) { - print_ion_variable(); - } - - print_statement_block(*node.get_variable_block(), false, false); - printer->add_newline(); - - printer->push_block("void initialize()"); - print_statement_block(*node.get_initialize_block(), false, false); - printer->pop_block(); - printer->add_newline(); - - printer->fmt_line( - "{0}(NrnThread* nt, {1}* inst, int id, int pnodecount, double v, const Datum* indexes, " - "double* data, ThreadDatum* thread) : " - "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}}, " - "data{{data}}, thread{{thread}} " - "{{}}", - functor_name, - instance_struct()); - - printer->add_indent(); - - const auto& variable_block = *node.get_variable_block(); - const auto& functor_block = *node.get_functor_block(); - - printer->fmt_text( - "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " - "1>& nmodl_eigen_fm, " - "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", - float_type, - N, - is_functor_const(variable_block, functor_block) ? "const " : ""); - printer->push_block(); - printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); - print_statement_block(functor_block, false, false); - printer->pop_block(); - printer->add_newline(); - - // assign newton solver results in matrix X to state vars - printer->push_block("void finalize()"); - print_statement_block(*node.get_finalize_block(), false, false); - printer->pop_block(); - - printer->pop_block(";"); -} - -void CodegenCppVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { - // solution vector to store copy of state vars for Newton solver - printer->add_newline(); - - auto float_type = default_float_data_type(); - int N = node.get_n_state_vars()->get_value(); - printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); - printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - - print_statement_block(*node.get_setup_x_block(), false, false); - - // call newton solver with functor and X matrix that contains state vars - printer->add_line("// call newton solver"); - printer->fmt_line("{} newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);", - info.functor_names[&node]); - printer->add_line("newton_functor.initialize();"); - printer->add_line( - "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); - printer->add_line( - "if (newton_iterations < 0) assert(false && \"Newton solver did not converge!\");"); - - // assign newton solver results in matrix X to state vars - print_statement_block(*node.get_update_states_block(), false, false); - printer->add_line("newton_functor.finalize();"); -} - -void CodegenCppVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) { - printer->add_newline(); - - const std::string float_type = default_float_data_type(); - int N = node.get_n_state_vars()->get_value(); - printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); - printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); - if (N <= 4) - printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm_inv;", float_type, N); - printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); - print_statement_block(*node.get_variable_block(), false, false); - print_statement_block(*node.get_initialize_block(), false, false); - print_statement_block(*node.get_setup_x_block(), false, false); - - printer->add_newline(); - print_eigen_linear_solver(float_type, N); - printer->add_newline(); - - print_statement_block(*node.get_update_states_block(), false, false); - print_statement_block(*node.get_finalize_block(), false, false); -} - -void CodegenCppVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { - if (N <= 4) { - // Faster compared to LU, given the template specialization in Eigen. - printer->add_multi_line(R"CODE( - bool invertible; - nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible); - nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm; - if (!invertible) assert(false && "Singular or ill-conditioned matrix (Eigen::inverse)!"); - )CODE"); - } else { - // In Eigen the default storage order is ColMajor. - // Crout's implementation requires matrices stored in RowMajor order (C++-style arrays). - // Therefore, the transposeInPlace is critical such that the data() method to give the rows - // instead of the columns. - printer->add_line("if (!nmodl_eigen_jm.IsRowMajor) nmodl_eigen_jm.transposeInPlace();"); - - // pivot vector - printer->fmt_line("Eigen::Matrix pivot;", N); - printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> rowmax;", float_type, N); - - // In-place LU-Decomposition (Crout Algo) : Jm is replaced by its LU-decomposition - printer->fmt_line( - "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data(), rowmax.data()) " - "< 0) assert(false && \"Singular or ill-conditioned matrix (nmodl::crout)!\");", - float_type, - N); - - // Solve the linear system : Forward/Backward substitution part - printer->fmt_line( - "nmodl::crout::solveCrout<{0}>({1}, nmodl_eigen_jm.data(), nmodl_eigen_fm.data(), " - "nmodl_eigen_xm.data(), pivot.data());", - float_type, - N); - } -} - -/****************************************************************************************/ -/* Code-specific helper routines */ -/****************************************************************************************/ - - -std::string CodegenCppVisitor::internal_method_arguments() { - if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; - } - return "id, pnodecount, inst, data, indexes, thread, nt, v"; -} - - -/** - * @todo: figure out how to correctly handle qualifiers - */ -CodegenCppVisitor::ParamVector CodegenCppVisitor::internal_method_parameters() { - ParamVector params; - params.emplace_back("", "int", "", "id"); - params.emplace_back("", "int", "", "pnodecount"); - params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); - if (ion_variable_struct_required()) { - params.emplace_back("", "IonCurVar&", "", "ionvar"); - } - params.emplace_back("", "double*", "", "data"); - params.emplace_back("const ", "Datum*", "", "indexes"); - params.emplace_back("", "ThreadDatum*", "", "thread"); - params.emplace_back("", "NrnThread*", "", "nt"); - params.emplace_back("", "double", "", "v"); - return params; -} - - -const char* CodegenCppVisitor::external_method_arguments() noexcept { - return "id, pnodecount, data, indexes, thread, nt, ml, v"; -} - - -const char* CodegenCppVisitor::external_method_parameters(bool table) noexcept { - if (table) { - return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, int tml_id"; - } - return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v"; -} - - -std::string CodegenCppVisitor::nrn_thread_arguments() const { - if (ion_variable_struct_required()) { - return "id, pnodecount, ionvar, data, indexes, thread, nt, ml, v"; - } - return "id, pnodecount, data, indexes, thread, nt, ml, v"; -} - - -/** - * Function call arguments when function or procedure is defined in the - * same mod file itself - */ -std::string CodegenCppVisitor::nrn_thread_internal_arguments() { - if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; - } - return "id, pnodecount, inst, data, indexes, thread, nt, v"; -} - - -/** - * Replace commonly used variables in the verbatim blocks into their corresponding - * variable name in the new code generation backend. - */ -std::string CodegenCppVisitor::replace_if_verbatim_variable(std::string name) { - if (naming::VERBATIM_VARIABLES_MAPPING.find(name) != naming::VERBATIM_VARIABLES_MAPPING.end()) { - name = naming::VERBATIM_VARIABLES_MAPPING.at(name); - } - - /** - * if function is defined the same mod file then the arguments must - * contain mechanism instance as well. - */ - if (name == naming::THREAD_ARGS) { - if (internal_method_call_encountered) { - name = nrn_thread_internal_arguments(); - internal_method_call_encountered = false; - } else { - name = nrn_thread_arguments(); - } - } - if (name == naming::THREAD_ARGS_PROTO) { - name = external_method_parameters(); - } - return name; -} - - -/** - * Processing commonly used constructs in the verbatim blocks. - * @todo : this is still ad-hoc and requires re-implementation to - * handle it more elegantly. - */ -std::string CodegenCppVisitor::process_verbatim_text(std::string const& text) { - parser::CDriver driver; - driver.scan_string(text); - auto tokens = driver.all_tokens(); - std::string result; - for (size_t i = 0; i < tokens.size(); i++) { - auto token = tokens[i]; - - // check if we have function call in the verbatim block where - // function is defined in the same mod file - if (program_symtab->is_method_defined(token) && tokens[i + 1] == "(") { - internal_method_call_encountered = true; - } - auto name = process_verbatim_token(token); - - if (token == (std::string("_") + naming::TQITEM_VARIABLE)) { - name.insert(0, 1, '&'); - } - if (token == "_STRIDE") { - name = "pnodecount+id"; - } - result += name; - } - return result; -} - - -std::string CodegenCppVisitor::register_mechanism_arguments() const { - auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "nullptr"; - auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr"; - auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); - auto nrn_init = method_name(naming::NRN_INIT_METHOD); - auto const nrn_private_constructor = method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD); - auto const nrn_private_destructor = method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD); - return fmt::format("mechanism, {}, {}, nullptr, {}, {}, {}, {}, first_pointer_var_index()", - nrn_alloc, - nrn_cur, - nrn_state, - nrn_init, - nrn_private_constructor, - nrn_private_destructor); -} - - -std::pair CodegenCppVisitor::read_ion_variable_name( - const std::string& name) { - return {name, naming::ION_VARNAME_PREFIX + name}; -} - - -std::pair CodegenCppVisitor::write_ion_variable_name( - const std::string& name) { - return {naming::ION_VARNAME_PREFIX + name, name}; -} - - -std::string CodegenCppVisitor::conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) { - auto conc_var_name = get_variable_name(naming::ION_VARNAME_PREFIX + concentration); - auto style_var_name = get_variable_name("style_" + ion_name); - return fmt::format( - "nrn_wrote_conc({}_type," - " &({})," - " {}," - " {}," - " nrn_ion_global_map," - " {}," - " nt->_ml_list[{}_type]->_nodecount_padded)", - ion_name, - conc_var_name, - index, - style_var_name, - get_variable_name(naming::CELSIUS_VARIABLE), - ion_name); -} - - -/** - * If mechanisms dependency level execution is enabled then certain updates - * like ionic current contributions needs to be atomically updated. In this - * case we first update current mechanism's shadow vector and then add statement - * to queue that will be used in reduction queue. - */ -std::string CodegenCppVisitor::process_shadow_update_statement(const ShadowUseStatement& statement, - BlockType /* type */) { - // when there is no operator or rhs then that statement doesn't need shadow update - if (statement.op.empty() && statement.rhs.empty()) { - auto text = statement.lhs + ";"; - return text; - } - - // return regular statement - auto lhs = get_variable_name(statement.lhs); - auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs); - return text; -} - - -/****************************************************************************************/ -/* Code-specific printing routines for code generation */ -/****************************************************************************************/ - - -/** - * NMODL constants from unit database - * - */ -void CodegenCppVisitor::print_nmodl_constants() { - if (!info.factor_definitions.empty()) { - printer->add_newline(2); - printer->add_line("/** constants used in nmodl from UNITS */"); - for (const auto& it: info.factor_definitions) { - const std::string format_string = "static const double {} = {};"; - printer->fmt_line(format_string, it->get_node_name(), it->get_value()->get_value()); - } - } -} - - -void CodegenCppVisitor::print_first_pointer_var_index_getter() { - printer->add_newline(2); - print_device_method_annotation(); - printer->push_block("static inline int first_pointer_var_index()"); - printer->fmt_line("return {};", info.first_pointer_var_index); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_num_variable_getter() { - printer->add_newline(2); - print_device_method_annotation(); - printer->push_block("static inline int float_variables_size()"); - printer->fmt_line("return {};", float_variables_size()); - printer->pop_block(); - - printer->add_newline(2); - print_device_method_annotation(); - printer->push_block("static inline int int_variables_size()"); - printer->fmt_line("return {};", int_variables_size()); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_net_receive_arg_size_getter() { - if (!net_receive_exist()) { - return; - } - printer->add_newline(2); - print_device_method_annotation(); - printer->push_block("static inline int num_net_receive_args()"); - printer->fmt_line("return {};", info.num_net_receive_parameters); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_mech_type_getter() { - printer->add_newline(2); - print_device_method_annotation(); - printer->push_block("static inline int get_mech_type()"); - // false => get it from the host-only global struct, not the instance structure - printer->fmt_line("return {};", get_variable_name("mech_type", false)); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_memb_list_getter() { - printer->add_newline(2); - print_device_method_annotation(); - printer->push_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); - printer->push_block("if (!nt->_ml_list)"); - printer->add_line("return nullptr;"); - printer->pop_block(); - printer->add_line("return nt->_ml_list[get_mech_type()];"); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_namespace_start() { - printer->add_newline(2); - printer->push_block("namespace coreneuron"); -} - - -void CodegenCppVisitor::print_namespace_stop() { - printer->pop_block(); -} - - -/** - * \details There are three types of thread variables currently considered: - * - top local thread variables - * - thread variables in the mod file - * - thread variables for solver - * - * These variables are allocated into different thread structures and have - * corresponding thread ids. Thread id start from 0. In mod2c implementation, - * thread_data_index is increased at various places and it is used to - * decide the index of thread. - */ - -void CodegenCppVisitor::print_thread_getters() { - if (info.vectorize && info.derivimplicit_used()) { - int tid = info.derivimplicit_var_thread_id; - int list = info.derivimplicit_list_num; - - // clang-format off - printer->add_newline(2); - printer->add_line("/** thread specific helper routines for derivimplicit */"); - - printer->add_newline(1); - printer->fmt_push_block("static inline int* deriv{}_advance(ThreadDatum* thread)", list); - printer->fmt_line("return &(thread[{}].i);", tid); - printer->pop_block(); - printer->add_newline(); - - printer->fmt_push_block("static inline int dith{}()", list); - printer->fmt_line("return {};", tid+1); - printer->pop_block(); - printer->add_newline(); - - printer->fmt_push_block("static inline void** newtonspace{}(ThreadDatum* thread)", list); - printer->fmt_line("return &(thread[{}]._pvoid);", tid+2); - printer->pop_block(); - } - - if (info.vectorize && !info.thread_variables.empty()) { - printer->add_newline(2); - printer->add_line("/** tid for thread variables */"); - printer->push_block("static inline int thread_var_tid()"); - printer->fmt_line("return {};", info.thread_var_thread_id); - printer->pop_block(); - } - - if (info.vectorize && !info.top_local_variables.empty()) { - printer->add_newline(2); - printer->add_line("/** tid for top local tread variables */"); - printer->push_block("static inline int top_local_var_tid()"); - printer->fmt_line("return {};", info.top_local_thread_id); - printer->pop_block(); - } - // clang-format on -} - - -/****************************************************************************************/ -/* Routines for returning variable name */ -/****************************************************************************************/ - - -std::string CodegenCppVisitor::float_variable_name(const SymbolType& symbol, - bool use_instance) const { - auto name = symbol->get_name(); - auto dimension = symbol->get_length(); - auto position = position_of_float_var(name); - // clang-format off - if (symbol->is_array()) { - if (use_instance) { - return fmt::format("(inst->{}+id*{})", name, dimension); - } - return fmt::format("(data + {}*pnodecount + id*{})", position, dimension); - } - if (use_instance) { - return fmt::format("inst->{}[id]", name); - } - return fmt::format("data[{}*pnodecount + id]", position); - // clang-format on -} - - -std::string CodegenCppVisitor::int_variable_name(const IndexVariableInfo& symbol, - const std::string& name, - bool use_instance) const { - auto position = position_of_int_var(name); - // clang-format off - if (symbol.is_index) { - if (use_instance) { - return fmt::format("inst->{}[{}]", name, position); - } - return fmt::format("indexes[{}]", position); - } - if (symbol.is_integer) { - if (use_instance) { - return fmt::format("inst->{}[{}*pnodecount+id]", name, position); - } - return fmt::format("indexes[{}*pnodecount+id]", position); - } - if (use_instance) { - return fmt::format("inst->{}[indexes[{}*pnodecount + id]]", name, position); - } - auto data = symbol.is_vdata ? "_vdata" : "_data"; - return fmt::format("nt->{}[indexes[{}*pnodecount + id]]", data, position); - // clang-format on -} - - -std::string CodegenCppVisitor::global_variable_name(const SymbolType& symbol, - bool use_instance) const { - if (use_instance) { - return fmt::format("inst->{}->{}", naming::INST_GLOBAL_MEMBER, symbol->get_name()); - } else { - return fmt::format("{}.{}", global_struct_instance(), symbol->get_name()); - } -} - - -std::string CodegenCppVisitor::update_if_ion_variable_name(const std::string& name) const { - std::string result(name); - if (ion_variable_struct_required()) { - if (info.is_ion_read_variable(name)) { - result = naming::ION_VARNAME_PREFIX + name; - } - if (info.is_ion_write_variable(name)) { - result = "ionvar." + name; - } - if (info.is_current(name)) { - result = "ionvar." + name; - } - } - return result; -} - - -std::string CodegenCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { - const std::string& varname = update_if_ion_variable_name(name); - - // clang-format off - auto symbol_comparator = [&varname](const SymbolType& sym) { - return varname == sym->get_name(); - }; - - auto index_comparator = [&varname](const IndexVariableInfo& var) { - return varname == var.symbol->get_name(); - }; - // clang-format on - - // float variable - auto f = std::find_if(codegen_float_variables.begin(), - codegen_float_variables.end(), - symbol_comparator); - if (f != codegen_float_variables.end()) { - return float_variable_name(*f, use_instance); - } - - // integer variable - auto i = - std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); - if (i != codegen_int_variables.end()) { - return int_variable_name(*i, varname, use_instance); - } - - // global variable - auto g = std::find_if(codegen_global_variables.begin(), - codegen_global_variables.end(), - symbol_comparator); - if (g != codegen_global_variables.end()) { - return global_variable_name(*g, use_instance); - } - - if (varname == naming::NTHREAD_DT_VARIABLE) { - return std::string("nt->_") + naming::NTHREAD_DT_VARIABLE; - } - - // t in net_receive method is an argument to function and hence it should - // ne used instead of nt->_t which is current time of thread - if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { - return std::string("nt->_") + naming::NTHREAD_T_VARIABLE; - } - - auto const iter = - std::find_if(info.neuron_global_variables.begin(), - info.neuron_global_variables.end(), - [&varname](auto const& entry) { return entry.first->get_name() == varname; }); - if (iter != info.neuron_global_variables.end()) { - std::string ret; - if (use_instance) { - ret = "*(inst->"; - } - ret.append(varname); - if (use_instance) { - ret.append(")"); - } - return ret; - } - - // otherwise return original name - return varname; -} - - -/****************************************************************************************/ -/* Main printing routines for code generation */ -/****************************************************************************************/ - - -void CodegenCppVisitor::print_backend_info() { - time_t current_time{}; - time(¤t_time); - std::string data_time_str{std::ctime(¤t_time)}; - auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; - - printer->add_line("/*********************************************************"); - printer->add_line("Model Name : ", info.mod_suffix); - printer->add_line("Filename : ", info.mod_file, ".mod"); - printer->add_line("NMODL Version : ", nmodl_version()); - printer->fmt_line("Vectorized : {}", info.vectorize); - printer->fmt_line("Threadsafe : {}", info.thread_safe); - printer->add_line("Created : ", stringutils::trim(data_time_str)); - printer->add_line("Backend : ", backend_name()); - printer->add_line("NMODL Compiler : ", version); - printer->add_line("*********************************************************/"); -} - - -void CodegenCppVisitor::print_standard_includes() { - printer->add_newline(); - printer->add_multi_line(R"CODE( - #include - #include - #include - #include - )CODE"); -} - - -void CodegenCppVisitor::print_coreneuron_includes() { - printer->add_newline(); - printer->add_multi_line(R"CODE( - #include - #include - #include - #include - #include - #include - #include - #include - #include - #include - )CODE"); - if (info.eigen_newton_solver_exist) { - printer->add_line("#include "); - } - if (info.eigen_linear_solver_exist) { - if (std::accumulate(info.state_vars.begin(), - info.state_vars.end(), - 0, - [](int l, const SymbolType& variable) { - return l += variable->get_length(); - }) > 4) { - printer->add_line("#include "); - } else { - printer->add_line("#include "); - printer->add_line("#include "); - } - } -} - - -/** - * \details Variables required for type of ion, type of point process etc. are - * of static int type. For the C++ backend type, it's ok to have - * these variables as file scoped static variables. - * - * Initial values of state variables (h0) are also defined as static - * variables. Note that the state could be ion variable and it could - * be also range variable. Hence lookup into symbol table before. - * - * When model is not vectorized (shouldn't be the case in coreneuron) - * the top local variables become static variables. - * - * Note that static variables are already initialized to 0. We do the - * same for some variables to keep same code as neuron. - */ -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { - const auto value_initialize = print_initializers ? "{}" : ""; - const auto qualifier = global_var_struct_type_qualifier(); - - auto float_type = default_float_data_type(); - printer->add_newline(2); - printer->add_line("/** all global variables */"); - printer->fmt_push_block("struct {}", global_struct()); - - for (const auto& ion: info.ions) { - auto name = fmt::format("{}_type", ion.name); - printer->fmt_line("{}int {}{};", qualifier, name, value_initialize); - codegen_global_variables.push_back(make_symbol(name)); - } - - if (info.point_process) { - printer->fmt_line("{}int point_type{};", qualifier, value_initialize); - codegen_global_variables.push_back(make_symbol("point_type")); - } - - for (const auto& var: info.state_vars) { - auto name = var->get_name() + "0"; - auto symbol = program_symtab->lookup(name); - if (symbol == nullptr) { - printer->fmt_line("{}{} {}{};", qualifier, float_type, name, value_initialize); - codegen_global_variables.push_back(make_symbol(name)); - } - } - - // Neuron and Coreneuron adds "v" to global variables when vectorize - // is false. But as v is always local variable and passed as argument, - // we don't need to use global variable v - - auto& top_locals = info.top_local_variables; - if (!info.vectorize && !top_locals.empty()) { - for (const auto& var: top_locals) { - auto name = var->get_name(); - auto length = var->get_length(); - if (var->is_array()) { - printer->fmt_line("{}{} {}[{}] /* TODO init top-local-array */;", - qualifier, - float_type, - name, - length); - } else { - printer->fmt_line("{}{} {} /* TODO init top-local */;", - qualifier, - float_type, - name); - } - codegen_global_variables.push_back(var); - } - } - - if (!info.thread_variables.empty()) { - printer->fmt_line("{}int thread_data_in_use{};", qualifier, value_initialize); - printer->fmt_line("{}{} thread_data[{}] /* TODO init thread_data */;", - qualifier, - float_type, - info.thread_var_data_size); - codegen_global_variables.push_back(make_symbol("thread_data_in_use")); - auto symbol = make_symbol("thread_data"); - symbol->set_as_array(info.thread_var_data_size); - codegen_global_variables.push_back(symbol); - } - - // TODO: remove this entirely? - printer->fmt_line("{}int reset{};", qualifier, value_initialize); - codegen_global_variables.push_back(make_symbol("reset")); - - printer->fmt_line("{}int mech_type{};", qualifier, value_initialize); - codegen_global_variables.push_back(make_symbol("mech_type")); - - for (const auto& var: info.global_variables) { - auto name = var->get_name(); - auto length = var->get_length(); - if (var->is_array()) { - printer->fmt_line( - "{}{} {}[{}] /* TODO init const-array */;", qualifier, float_type, name, length); - } else { - double value{}; - if (auto const& value_ptr = var->get_value()) { - value = *value_ptr; - } - printer->fmt_line("{}{} {}{};", - qualifier, - float_type, - name, - print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); - } - codegen_global_variables.push_back(var); - } - - for (const auto& var: info.constant_variables) { - auto const name = var->get_name(); - auto* const value_ptr = var->get_value().get(); - double const value{value_ptr ? *value_ptr : 0}; - printer->fmt_line("{}{} {}{};", - qualifier, - float_type, - name, - print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); - codegen_global_variables.push_back(var); - } - - if (info.primes_size != 0) { - const auto count_prime_variables = [](auto size, const SymbolType& symbol) { - return size += symbol->get_length(); - }; - const auto prime_variables_by_order_size = - std::accumulate(info.prime_variables_by_order.begin(), - info.prime_variables_by_order.end(), - 0, - count_prime_variables); - if (info.primes_size != prime_variables_by_order_size) { - throw std::runtime_error{ - fmt::format("primes_size = {} differs from prime_variables_by_order.size() = {}, " - "this should not happen.", - info.primes_size, - info.prime_variables_by_order.size())}; - } - auto const initializer_list = [&](auto const& primes, const char* prefix) -> std::string { - if (!print_initializers) { - return {}; - } - std::string list{"{"}; - for (auto iter = primes.begin(); iter != primes.end(); ++iter) { - auto const& prime = *iter; - list.append(std::to_string(position_of_float_var(prefix + prime->get_name()))); - if (std::next(iter) != primes.end()) { - list.append(", "); - } - } - list.append("}"); - return list; - }; - printer->fmt_line("{}int slist1[{}]{};", - qualifier, - info.primes_size, - initializer_list(info.prime_variables_by_order, "")); - printer->fmt_line("{}int dlist1[{}]{};", - qualifier, - info.primes_size, - initializer_list(info.prime_variables_by_order, "D")); - codegen_global_variables.push_back(make_symbol("slist1")); - codegen_global_variables.push_back(make_symbol("dlist1")); - // additional list for derivimplicit method - if (info.derivimplicit_used()) { - auto primes = program_symtab->get_variables_with_properties(NmodlType::prime_name); - printer->fmt_line("{}int slist2[{}]{};", - qualifier, - info.primes_size, - initializer_list(primes, "")); - codegen_global_variables.push_back(make_symbol("slist2")); - } - } - - if (info.table_count > 0) { - printer->fmt_line("{}double usetable{};", qualifier, print_initializers ? "{1}" : ""); - codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); - - for (const auto& block: info.functions_with_table) { - const auto& name = block->get_node_name(); - printer->fmt_line("{}{} tmin_{}{};", qualifier, float_type, name, value_initialize); - printer->fmt_line("{}{} mfac_{}{};", qualifier, float_type, name, value_initialize); - codegen_global_variables.push_back(make_symbol("tmin_" + name)); - codegen_global_variables.push_back(make_symbol("mfac_" + name)); - } - - for (const auto& variable: info.table_statement_variables) { - auto const name = "t_" + variable->get_name(); - auto const num_values = variable->get_num_values(); - if (variable->is_array()) { - int array_len = variable->get_length(); - printer->fmt_line("{}{} {}[{}][{}]{};", - qualifier, - float_type, - name, - array_len, - num_values, - value_initialize); - } else { - printer->fmt_line( - "{}{} {}[{}]{};", qualifier, float_type, name, num_values, value_initialize); - } - codegen_global_variables.push_back(make_symbol(name)); - } - } - - for (const auto& f: info.function_tables) { - printer->fmt_line("void* _ptable_{}{{}};", f->get_node_name()); - codegen_global_variables.push_back(make_symbol("_ptable_" + f->get_node_name())); - } - - if (info.vectorize && info.thread_data_index) { - printer->fmt_line("{}ThreadDatum ext_call_thread[{}]{};", - qualifier, - info.thread_data_index, - value_initialize); - codegen_global_variables.push_back(make_symbol("ext_call_thread")); - } - - printer->pop_block(";"); - - print_global_var_struct_assertions(); - print_global_var_struct_decl(); -} - -void CodegenCppVisitor::print_global_var_struct_assertions() const { - // Assert some things that we assume when copying instances of this struct - // to the GPU and so on. - printer->fmt_line("static_assert(std::is_trivially_copy_constructible_v<{}>);", - global_struct()); - printer->fmt_line("static_assert(std::is_trivially_move_constructible_v<{}>);", - global_struct()); - printer->fmt_line("static_assert(std::is_trivially_copy_assignable_v<{}>);", global_struct()); - printer->fmt_line("static_assert(std::is_trivially_move_assignable_v<{}>);", global_struct()); - printer->fmt_line("static_assert(std::is_trivially_destructible_v<{}>);", global_struct()); -} - - -void CodegenCppVisitor::print_prcellstate_macros() const { - printer->add_line("#ifndef NRN_PRCELLSTATE"); - printer->add_line("#define NRN_PRCELLSTATE 0"); - printer->add_line("#endif"); -} - - -void CodegenCppVisitor::print_mechanism_info() { - auto variable_printer = [&](const std::vector& variables) { - for (const auto& v: variables) { - auto name = v->get_name(); - if (!info.point_process) { - name += "_" + info.mod_suffix; - } - if (v->is_array()) { - name += fmt::format("[{}]", v->get_length()); - } - printer->add_line(add_escape_quote(name), ","); - } - }; - - printer->add_newline(2); - printer->add_line("/** channel information */"); - printer->add_line("static const char *mechanism[] = {"); - printer->increase_indent(); - printer->add_line(add_escape_quote(nmodl_version()), ","); - printer->add_line(add_escape_quote(info.mod_suffix), ","); - variable_printer(info.range_parameter_vars); - printer->add_line("0,"); - variable_printer(info.range_assigned_vars); - printer->add_line("0,"); - variable_printer(info.range_state_vars); - printer->add_line("0,"); - variable_printer(info.pointer_variables); - printer->add_line("0"); - printer->decrease_indent(); - printer->add_line("};"); -} - - -/** - * Print structs that encapsulate information about scalar and - * vector elements of type global and thread variables. - */ -void CodegenCppVisitor::print_global_variables_for_hoc() { - auto variable_printer = - [&](const std::vector& variables, bool if_array, bool if_vector) { - for (const auto& variable: variables) { - if (variable->is_array() == if_array) { - // false => do not use the instance struct, which is not - // defined in the global declaration that we are printing - auto name = get_variable_name(variable->get_name(), false); - auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); - auto length = variable->get_length(); - if (if_vector) { - printer->fmt_line("{{{}, {}, {}}},", ename, name, length); - } else { - printer->fmt_line("{{{}, &{}}},", ename, name); - } - } - } - }; - - auto globals = info.global_variables; - auto thread_vars = info.thread_variables; - - if (info.table_count > 0) { - globals.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); - } - - printer->add_newline(2); - printer->add_line("/** connect global (scalar) variables to hoc -- */"); - printer->add_line("static DoubScal hoc_scalar_double[] = {"); - printer->increase_indent(); - variable_printer(globals, false, false); - variable_printer(thread_vars, false, false); - printer->add_line("{nullptr, nullptr}"); - printer->decrease_indent(); - printer->add_line("};"); - - printer->add_newline(2); - printer->add_line("/** connect global (array) variables to hoc -- */"); - printer->add_line("static DoubVec hoc_vector_double[] = {"); - printer->increase_indent(); - variable_printer(globals, true, true); - variable_printer(thread_vars, true, true); - printer->add_line("{nullptr, nullptr, 0}"); - printer->decrease_indent(); - printer->add_line("};"); -} - -/** - * Return registration type for a given BEFORE/AFTER block - * /param block A BEFORE/AFTER block being registered - * - * Depending on a block type i.e. BEFORE or AFTER and also type - * of it's associated block i.e. BREAKPOINT, INITIAL, SOLVE and - * STEP, the registration type (as an integer) is calculated. - * These values are then interpreted by CoreNEURON internally. - */ -static std::string get_register_type_for_ba_block(const ast::Block* block) { - std::string register_type{}; - BAType ba_type{}; - /// before block have value 10 and after block 20 - if (block->is_before_block()) { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - register_type = "BAType::Before"; - ba_type = - dynamic_cast(block)->get_bablock()->get_type()->get_value(); - } else { - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) - register_type = "BAType::After"; - ba_type = - dynamic_cast(block)->get_bablock()->get_type()->get_value(); - } - - /// associated blocks have different values (1 to 4) based on type. - /// These values are based on neuron/coreneuron implementation details. - if (ba_type == BATYPE_BREAKPOINT) { - register_type += " + BAType::Breakpoint"; - } else if (ba_type == BATYPE_SOLVE) { - register_type += " + BAType::Solve"; - } else if (ba_type == BATYPE_INITIAL) { - register_type += " + BAType::Initial"; - } else if (ba_type == BATYPE_STEP) { - register_type += " + BAType::Step"; - } else { - throw std::runtime_error("Unhandled Before/After type encountered during code generation"); - } - return register_type; -} - - -/** - * \details Every mod file has register function to connect with the simulator. - * Various information about mechanism and callbacks get registered with - * the simulator using suffix_reg() function. - * - * Here are details: - * - We should exclude that callback based on the solver, watch statements. - * - If nrn_get_mechtype is < -1 means that mechanism is not used in the - * context of neuron execution and hence could be ignored in coreneuron - * execution. - * - Ions are internally defined and their types can be queried similar to - * other mechanisms. - * - hoc_register_var may not be needed in the context of coreneuron - * - We assume net receive buffer is on. This is because generated code is - * compatible for cpu as well as gpu target. - */ -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -void CodegenCppVisitor::print_mechanism_register() { - printer->add_newline(2); - printer->add_line("/** register channel with the simulator */"); - printer->fmt_push_block("void _{}_reg()", info.mod_file); - - // type related information - auto suffix = add_escape_quote(info.mod_suffix); - printer->add_newline(); - printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); - printer->fmt_line("{} = mech_type;", get_variable_name("mech_type", false)); - printer->push_block("if (mech_type == -1)"); - printer->add_line("return;"); - printer->pop_block(); - - printer->add_newline(); - printer->add_line("_nrn_layout_reg(mech_type, 0);"); // 0 for SoA - - // register mechanism - const auto mech_arguments = register_mechanism_arguments(); - const auto number_of_thread_objects = num_thread_objects(); - if (info.point_process) { - printer->fmt_line("point_register_mech({}, {}, {}, {});", - mech_arguments, - info.constructor_node ? method_name(naming::NRN_CONSTRUCTOR_METHOD) - : "nullptr", - info.destructor_node ? method_name(naming::NRN_DESTRUCTOR_METHOD) - : "nullptr", - number_of_thread_objects); - } else { - printer->fmt_line("register_mech({}, {});", mech_arguments, number_of_thread_objects); - if (info.constructor_node) { - printer->fmt_line("register_constructor({});", - method_name(naming::NRN_CONSTRUCTOR_METHOD)); - } - } - - // types for ion - for (const auto& ion: info.ions) { - printer->fmt_line("{} = nrn_get_mechtype({});", - get_variable_name(ion.name + "_type", false), - add_escape_quote(ion.name + "_ion")); - } - printer->add_newline(); - - /* - * Register callbacks for thread allocation and cleanup. Note that thread_data_index - * represent total number of thread used minus 1 (i.e. index of last thread). - */ - if (info.vectorize && (info.thread_data_index != 0)) { - // false to avoid getting the copy from the instance structure - printer->fmt_line("thread_mem_init({});", get_variable_name("ext_call_thread", false)); - } - - if (!info.thread_variables.empty()) { - printer->fmt_line("{} = 0;", get_variable_name("thread_data_in_use")); - } - - if (info.thread_callback_register) { - printer->add_line("_nrn_thread_reg0(mech_type, thread_mem_cleanup);"); - printer->add_line("_nrn_thread_reg1(mech_type, thread_mem_init);"); - } - - if (info.emit_table_thread()) { - auto name = method_name("check_table_thread"); - printer->fmt_line("_nrn_thread_table_reg(mech_type, {});", name); - } - - // register read/write callbacks for pointers - if (info.bbcore_pointer_used) { - printer->add_line("hoc_reg_bbcore_read(mech_type, bbcore_read);"); - printer->add_line("hoc_reg_bbcore_write(mech_type, bbcore_write);"); - } - - // register size of double and int elements - // clang-format off - printer->add_line("hoc_register_prop_size(mech_type, float_variables_size(), int_variables_size());"); - // clang-format on - - // register semantics for index variables - for (auto& semantic: info.semantics) { - auto args = - fmt::format("mech_type, {}, {}", semantic.index, add_escape_quote(semantic.name)); - printer->fmt_line("hoc_register_dparam_semantics({});", args); - } - - if (info.is_watch_used()) { - auto watch_fun = compute_method_name(BlockType::Watch); - printer->fmt_line("hoc_register_watch_check({}, mech_type);", watch_fun); - } - - if (info.write_concentration) { - printer->add_line("nrn_writes_conc(mech_type, 0);"); - } - - // register various information for point process type - if (info.net_event_used) { - printer->add_line("add_nrn_has_net_event(mech_type);"); - } - if (info.artificial_cell) { - printer->fmt_line("add_nrn_artcell(mech_type, {});", info.tqitem_index); - } - if (net_receive_buffering_required()) { - printer->fmt_line("hoc_register_net_receive_buffering({}, mech_type);", - method_name("net_buf_receive")); - } - if (info.num_net_receive_parameters != 0) { - auto net_recv_init_arg = "nullptr"; - if (info.net_receive_initial_node != nullptr) { - net_recv_init_arg = "net_init"; - } - printer->fmt_line("set_pnt_receive(mech_type, {}, {}, num_net_receive_args());", - method_name("net_receive"), - net_recv_init_arg); - } - if (info.for_netcon_used) { - // index where information about FOR_NETCON is stored in the integer array - const auto index = - std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { - return a.name == naming::FOR_NETCON_SEMANTIC; - })->index; - printer->fmt_line("add_nrn_fornetcons(mech_type, {});", index); - } - - if (info.net_event_used || info.net_send_used) { - printer->add_line("hoc_register_net_send_buffering(mech_type);"); - } - - /// register all before/after blocks - for (size_t i = 0; i < info.before_after_blocks.size(); i++) { - // register type and associated function name for the block - const auto& block = info.before_after_blocks[i]; - std::string register_type = get_register_type_for_ba_block(block); - std::string function_name = method_name(fmt::format("nrn_before_after_{}", i)); - printer->fmt_line("hoc_reg_ba(mech_type, {}, {});", function_name, register_type); - } - - // register variables for hoc - printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, NULL);"); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_thread_memory_callbacks() { - if (!info.thread_callback_register) { - return; - } - - // thread_mem_init callback - printer->add_newline(2); - printer->add_line("/** thread memory allocation callback */"); - printer->push_block("static void thread_mem_init(ThreadDatum* thread) "); - - if (info.vectorize && info.derivimplicit_used()) { - printer->fmt_line("thread[dith{}()].pval = nullptr;", info.derivimplicit_list_num); - } - if (info.vectorize && (info.top_local_thread_size != 0)) { - auto length = info.top_local_thread_size; - auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); - printer->fmt_line("thread[top_local_var_tid()].pval = {};", allocation); - } - if (info.thread_var_data_size != 0) { - auto length = info.thread_var_data_size; - auto thread_data = get_variable_name("thread_data"); - auto thread_data_in_use = get_variable_name("thread_data_in_use"); - auto allocation = fmt::format("(double*)mem_alloc({}, sizeof(double))", length); - printer->fmt_push_block("if ({})", thread_data_in_use); - printer->fmt_line("thread[thread_var_tid()].pval = {};", allocation); - printer->chain_block("else"); - printer->fmt_line("thread[thread_var_tid()].pval = {};", thread_data); - printer->fmt_line("{} = 1;", thread_data_in_use); - printer->pop_block(); - } - printer->pop_block(); - printer->add_newline(2); - - - // thread_mem_cleanup callback - printer->add_line("/** thread memory cleanup callback */"); - printer->push_block("static void thread_mem_cleanup(ThreadDatum* thread) "); - - // clang-format off - if (info.vectorize && info.derivimplicit_used()) { - int n = info.derivimplicit_list_num; - printer->fmt_line("free(thread[dith{}()].pval);", n); - printer->fmt_line("nrn_destroy_newtonspace(static_cast(*newtonspace{}(thread)));", n); - } - // clang-format on - - if (info.top_local_thread_size != 0) { - auto line = "free(thread[top_local_var_tid()].pval);"; - printer->add_line(line); - } - if (info.thread_var_data_size != 0) { - auto thread_data = get_variable_name("thread_data"); - auto thread_data_in_use = get_variable_name("thread_data_in_use"); - printer->fmt_push_block("if (thread[thread_var_tid()].pval == {})", thread_data); - printer->fmt_line("{} = 0;", thread_data_in_use); - printer->chain_block("else"); - printer->add_line("free(thread[thread_var_tid()].pval);"); - printer->pop_block(); - } - printer->pop_block(); -} - - -void CodegenCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { - auto const value_initialize = print_initializers ? "{}" : ""; - auto int_type = default_int_data_type(); - printer->add_newline(2); - printer->add_line("/** all mechanism instance variables and global variables */"); - printer->fmt_push_block("struct {} ", instance_struct()); - - for (auto const& [var, type]: info.neuron_global_variables) { - auto const name = var->get_name(); - printer->fmt_line("{}* {}{};", - type, - name, - print_initializers ? fmt::format("{{&coreneuron::{}}}", name) - : std::string{}); - } - for (auto& var: codegen_float_variables) { - const auto& name = var->get_name(); - auto type = get_range_var_float_type(var); - auto qualifier = is_constant_variable(name) ? "const " : ""; - printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); - } - for (auto& var: codegen_int_variables) { - const auto& name = var.symbol->get_name(); - if (var.is_index || var.is_integer) { - auto qualifier = var.is_constant ? "const " : ""; - printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialize); - } else { - auto qualifier = var.is_constant ? "const " : ""; - auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); - } - } - - printer->fmt_line("{}* {}{};", - global_struct(), - naming::INST_GLOBAL_MEMBER, - print_initializers ? fmt::format("{{&{}}}", global_struct_instance()) - : std::string{}); - printer->pop_block(";"); -} - - -void CodegenCppVisitor::print_ion_var_structure() { - if (!ion_variable_struct_required()) { - return; - } - printer->add_newline(2); - printer->add_line("/** ion write variables */"); - printer->push_block("struct IonCurVar"); - - std::string float_type = default_float_data_type(); - std::vector members; - - for (auto& ion: info.ions) { - for (auto& var: ion.writes) { - printer->fmt_line("{} {};", float_type, var); - members.push_back(var); - } - } - for (auto& var: info.currents) { - if (!info.is_ion_variable(var)) { - printer->fmt_line("{} {};", float_type, var); - members.push_back(var); - } - } - - print_ion_var_constructor(members); - - printer->pop_block(";"); -} - - -void CodegenCppVisitor::print_ion_var_constructor(const std::vector& members) { - // constructor - printer->add_newline(); - printer->add_indent(); - printer->add_text("IonCurVar() : "); - for (int i = 0; i < members.size(); i++) { - printer->fmt_text("{}(0)", members[i]); - if (i + 1 < members.size()) { - printer->add_text(", "); - } - } - printer->add_text(" {}"); - printer->add_newline(); -} - - -void CodegenCppVisitor::print_ion_variable() { - printer->add_line("IonCurVar ionvar;"); -} - - -void CodegenCppVisitor::print_global_variable_device_update_annotation() { - // nothing for cpu -} - - -void CodegenCppVisitor::print_setup_range_variable() { - auto type = float_data_type(); - printer->add_newline(2); - printer->add_line("/** allocate and setup array for range variable */"); - printer->fmt_push_block("static inline {}* setup_range_variable(double* variable, int n)", - type); - printer->fmt_line("{0}* data = ({0}*) mem_alloc(n, sizeof({0}));", type); - printer->push_block("for(size_t i = 0; i < n; i++)"); - printer->add_line("data[i] = variable[i];"); - printer->pop_block(); - printer->add_line("return data;"); - printer->pop_block(); -} - - -/** - * \details If floating point type like "float" is specified on command line then - * we can't turn all variables to new type. This is because certain variables - * are pointers to internal variables (e.g. ions). Hence, we check if given - * variable can be safely converted to new type. If so, return new type. - */ -std::string CodegenCppVisitor::get_range_var_float_type(const SymbolType& symbol) { - // clang-format off - auto with = NmodlType::read_ion_var - | NmodlType::write_ion_var - | NmodlType::pointer_var - | NmodlType::bbcore_pointer_var - | NmodlType::extern_neuron_variable; - // clang-format on - bool need_default_type = symbol->has_any_property(with); - if (need_default_type) { - return default_float_data_type(); - } - return float_data_type(); -} - - -void CodegenCppVisitor::print_instance_variable_setup() { - if (range_variable_setup_required()) { - print_setup_range_variable(); - } - - printer->add_newline(); - printer->add_line("// Allocate instance structure"); - printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", - method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD)); - printer->add_line("assert(!ml->instance);"); - printer->add_line("assert(!ml->global_variables);"); - printer->add_line("assert(ml->global_variables_size == 0);"); - printer->fmt_line("auto* const inst = new {}{{}};", instance_struct()); - printer->fmt_line("assert(inst->{} == &{});", - naming::INST_GLOBAL_MEMBER, - global_struct_instance()); - printer->add_line("ml->instance = inst;"); - printer->fmt_line("ml->global_variables = inst->{};", naming::INST_GLOBAL_MEMBER); - printer->fmt_line("ml->global_variables_size = sizeof({});", global_struct()); - printer->pop_block(); - printer->add_newline(); - - auto const cast_inst_and_assert_validity = [&]() { - printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); - printer->add_line("assert(inst);"); - printer->fmt_line("assert(inst->{});", naming::INST_GLOBAL_MEMBER); - printer->fmt_line("assert(inst->{} == &{});", - naming::INST_GLOBAL_MEMBER, - global_struct_instance()); - printer->fmt_line("assert(inst->{} == ml->global_variables);", naming::INST_GLOBAL_MEMBER); - printer->fmt_line("assert(ml->global_variables_size == sizeof({}));", global_struct()); - }; - - // Must come before print_instance_struct_copy_to_device and - // print_instance_struct_delete_from_device - print_instance_struct_transfer_routine_declarations(); - - printer->add_line("// Deallocate the instance structure"); - printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", - method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); - cast_inst_and_assert_validity(); - print_instance_struct_delete_from_device(); - printer->add_multi_line(R"CODE( - delete inst; - ml->instance = nullptr; - ml->global_variables = nullptr; - ml->global_variables_size = 0; - )CODE"); - printer->pop_block(); - printer->add_newline(); - - - printer->add_line("/** initialize mechanism instance variables */"); - printer->push_block("static inline void setup_instance(NrnThread* nt, Memb_list* ml)"); - cast_inst_and_assert_validity(); - - std::string stride; - printer->add_line("int pnodecount = ml->_nodecount_padded;"); - stride = "*pnodecount"; - - printer->add_line("Datum* indexes = ml->pdata;"); - - auto const float_type = default_float_data_type(); - - int id = 0; - std::vector ptr_members{naming::INST_GLOBAL_MEMBER}; - for (auto const& [var, type]: info.neuron_global_variables) { - ptr_members.push_back(var->get_name()); - } - ptr_members.reserve(ptr_members.size() + codegen_float_variables.size() + - codegen_int_variables.size()); - for (auto& var: codegen_float_variables) { - auto name = var->get_name(); - auto range_var_type = get_range_var_float_type(var); - if (float_type == range_var_type) { - auto const variable = fmt::format("ml->data+{}{}", id, stride); - printer->fmt_line("inst->{} = {};", name, variable); - } else { - // TODO what MOD file exercises this? - printer->fmt_line("inst->{} = setup_range_variable(ml->data+{}{}, pnodecount);", - name, - id, - stride); - } - ptr_members.push_back(std::move(name)); - id += var->get_length(); - } - - for (auto& var: codegen_int_variables) { - auto name = var.symbol->get_name(); - auto const variable = [&var]() { - if (var.is_index || var.is_integer) { - return "ml->pdata"; - } else if (var.is_vdata) { - return "nt->_vdata"; - } else { - return "nt->_data"; - } - }(); - printer->fmt_line("inst->{} = {};", name, variable); - ptr_members.push_back(std::move(name)); - } - print_instance_struct_copy_to_device(); - printer->pop_block(); // setup_instance - printer->add_newline(); - - print_instance_struct_transfer_routines(ptr_members); -} - - -void CodegenCppVisitor::print_initial_block(const InitialBlock* node) { - if (info.artificial_cell) { - printer->add_line("double v = 0.0;"); - } else { - printer->add_line("int node_id = node_index[id];"); - printer->add_line("double v = voltage[node_id];"); - print_v_unused(); - } - - if (ion_variable_struct_required()) { - printer->add_line("IonCurVar ionvar;"); - } - - // read ion statements - auto read_statements = ion_read_statements(BlockType::Initial); - for (auto& statement: read_statements) { - printer->add_line(statement); - } - - // initialize state variables (excluding ion state) - for (auto& var: info.state_vars) { - auto name = var->get_name(); - if (!info.is_ionic_conc(name)) { - auto lhs = get_variable_name(name); - auto rhs = get_variable_name(name + "0"); - if (var->is_array()) { - for (int i = 0; i < var->get_length(); ++i) { - printer->fmt_line("{}[{}] = {};", lhs, i, rhs); - } - } else { - printer->fmt_line("{} = {};", lhs, rhs); - } - } - } - - // initial block - if (node != nullptr) { - const auto& block = node->get_statement_block(); - print_statement_block(*block, false, false); - } - - // write ion statements - auto write_statements = ion_write_statements(BlockType::Initial); - for (auto& statement: write_statements) { - auto text = process_shadow_update_statement(statement, BlockType::Initial); - printer->add_line(text); - } -} - - -void CodegenCppVisitor::print_global_function_common_code(BlockType type, - const std::string& function_name) { - std::string method; - if (function_name.empty()) { - method = compute_method_name(type); - } else { - method = function_name; - } - auto args = "NrnThread* nt, Memb_list* ml, int type"; - - // watch statement function doesn't have type argument - if (type == BlockType::Watch) { - args = "NrnThread* nt, Memb_list* ml"; - } - - print_global_method_annotation(); - printer->fmt_push_block("void {}({})", method, args); - if (type != BlockType::Destructor && type != BlockType::Constructor) { - // We do not (currently) support DESTRUCTOR and CONSTRUCTOR blocks - // running anything on the GPU. - print_kernel_data_present_annotation_block_begin(); - } else { - /// TODO: Remove this when the code generation is propery done - /// Related to https://github.com/BlueBrain/nmodl/issues/692 - printer->add_line("#ifndef CORENEURON_BUILD"); - } - printer->add_multi_line(R"CODE( - int nodecount = ml->nodecount; - int pnodecount = ml->_nodecount_padded; - const int* node_index = ml->nodeindices; - double* data = ml->data; - const double* voltage = nt->_actual_v; - )CODE"); - - if (type == BlockType::Equation) { - printer->add_line("double* vec_rhs = nt->_actual_rhs;"); - printer->add_line("double* vec_d = nt->_actual_d;"); - print_rhs_d_shadow_variables(); - } - printer->add_line("Datum* indexes = ml->pdata;"); - printer->add_line("ThreadDatum* thread = ml->_thread;"); - - if (type == BlockType::Initial) { - printer->add_newline(); - printer->add_line("setup_instance(nt, ml);"); - } - printer->fmt_line("auto* const inst = static_cast<{}*>(ml->instance);", instance_struct()); - printer->add_newline(1); -} - -void CodegenCppVisitor::print_nrn_init(bool skip_init_check) { - codegen = true; - printer->add_newline(2); - printer->add_line("/** initialize channel */"); - - print_global_function_common_code(BlockType::Initial); - if (info.derivimplicit_used()) { - printer->add_newline(); - int nequation = info.num_equations; - int list_num = info.derivimplicit_list_num; - // clang-format off - printer->fmt_line("int& deriv_advance_flag = *deriv{}_advance(thread);", list_num); - printer->add_line("deriv_advance_flag = 0;"); - print_deriv_advance_flag_transfer_to_device(); - printer->fmt_line("auto ns = newtonspace{}(thread);", list_num); - printer->fmt_line("auto& th = thread[dith{}()];", list_num); - printer->push_block("if (*ns == nullptr)"); - printer->fmt_line("int vec_size = 2*{}*pnodecount*sizeof(double);", nequation); - printer->fmt_line("double* vec = makevector(vec_size);", nequation); - printer->fmt_line("th.pval = vec;", list_num); - printer->fmt_line("*ns = nrn_cons_newtonspace({}, pnodecount);", nequation); - print_newtonspace_transfer_to_device(); - printer->pop_block(); - // clang-format on - } - - // update global variable as those might be updated via python/hoc API - // NOTE: CoreNEURON has enough information to do this on its own, which - // would be neater. - print_global_variable_device_update_annotation(); - - if (skip_init_check) { - printer->push_block("if (_nrn_skip_initmodel == 0)"); - } - - if (!info.changed_dt.empty()) { - printer->fmt_line("double _save_prev_dt = {};", - get_variable_name(naming::NTHREAD_DT_VARIABLE)); - printer->fmt_line("{} = {};", - get_variable_name(naming::NTHREAD_DT_VARIABLE), - info.changed_dt); - print_dt_update_to_device(); - } - - print_channel_iteration_block_parallel_hint(BlockType::Initial, info.initial_node); - printer->push_block("for (int id = 0; id < nodecount; id++)"); - - if (info.net_receive_node != nullptr) { - printer->fmt_line("{} = -1e20;", get_variable_name("tsave")); - } - - print_initial_block(info.initial_node); - printer->pop_block(); - - if (!info.changed_dt.empty()) { - printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); - print_dt_update_to_device(); - } - - printer->pop_block(); - - if (info.derivimplicit_used()) { - printer->add_line("deriv_advance_flag = 1;"); - print_deriv_advance_flag_transfer_to_device(); - } - - if (info.net_send_used && !info.artificial_cell) { - print_send_event_move(); - } - - print_kernel_data_present_annotation_block_end(); - if (skip_init_check) { - printer->pop_block(); - } - codegen = false; -} - -void CodegenCppVisitor::print_before_after_block(const ast::Block* node, size_t block_id) { - codegen = true; - - std::string ba_type; - std::shared_ptr ba_block; - - if (node->is_before_block()) { - ba_block = dynamic_cast(node)->get_bablock(); - ba_type = "BEFORE"; - } else { - ba_block = dynamic_cast(node)->get_bablock(); - ba_type = "AFTER"; - } - - std::string ba_block_type = ba_block->get_type()->eval(); - - /// name of the before/after function - std::string function_name = method_name(fmt::format("nrn_before_after_{}", block_id)); - - /// print common function code like init/state/current - printer->add_newline(2); - printer->fmt_line("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id); - print_global_function_common_code(BlockType::BeforeAfter, function_name); - - print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter, node); - printer->push_block("for (int id = 0; id < nodecount; id++)"); - - printer->add_line("int node_id = node_index[id];"); - printer->add_line("double v = voltage[node_id];"); - print_v_unused(); - - // read ion statements - const auto& read_statements = ion_read_statements(BlockType::Equation); - for (auto& statement: read_statements) { - printer->add_line(statement); - } - - /// print main body - printer->add_indent(); - print_statement_block(*ba_block->get_statement_block()); - printer->add_newline(); - - // write ion statements - const auto& write_statements = ion_write_statements(BlockType::Equation); - for (auto& statement: write_statements) { - auto text = process_shadow_update_statement(statement, BlockType::Equation); - printer->add_line(text); - } - - /// loop end including data annotation block - printer->pop_block(); - printer->pop_block(); - print_kernel_data_present_annotation_block_end(); - - codegen = false; -} - -void CodegenCppVisitor::print_nrn_constructor() { - printer->add_newline(2); - print_global_function_common_code(BlockType::Constructor); - if (info.constructor_node != nullptr) { - const auto& block = info.constructor_node->get_statement_block(); - print_statement_block(*block, false, false); - } - printer->add_line("#endif"); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_nrn_destructor() { - printer->add_newline(2); - print_global_function_common_code(BlockType::Destructor); - if (info.destructor_node != nullptr) { - const auto& block = info.destructor_node->get_statement_block(); - print_statement_block(*block, false, false); - } - printer->add_line("#endif"); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_functors_definitions() { - codegen = true; - for (const auto& functor_name: info.functor_names) { - printer->add_newline(2); - print_functor_definition(*functor_name.first); - } - codegen = false; -} - - -void CodegenCppVisitor::print_nrn_alloc() { - printer->add_newline(2); - auto method = method_name(naming::NRN_ALLOC_METHOD); - printer->fmt_push_block("static void {}(double* data, Datum* indexes, int type)", method); - printer->add_line("// do nothing"); - printer->pop_block(); -} - -/** - * \todo Number of watch could be more than number of statements - * according to grammar. Check if this is correctly handled in neuron - * and coreneuron. - */ -void CodegenCppVisitor::print_watch_activate() { - if (info.watch_statements.empty()) { - return; - } - codegen = true; - printer->add_newline(2); - auto inst = fmt::format("{}* inst", instance_struct()); - - printer->fmt_push_block( - "static void nrn_watch_activate({}, int id, int pnodecount, int watch_id, " - "double v, bool &watch_remove)", - inst); - - // initialize all variables only during first watch statement - printer->push_block("if (watch_remove == false)"); - for (int i = 0; i < info.watch_count; i++) { - auto name = get_variable_name(fmt::format("watch{}", i + 1)); - printer->fmt_line("{} = 0;", name); - } - printer->add_line("watch_remove = true;"); - printer->pop_block(); - - /** - * \todo Similar to neuron/coreneuron we are using - * first watch and ignoring rest. - */ - for (int i = 0; i < info.watch_statements.size(); i++) { - auto statement = info.watch_statements[i]; - printer->fmt_push_block("if (watch_id == {})", i); - - auto varname = get_variable_name(fmt::format("watch{}", i + 1)); - printer->add_indent(); - printer->fmt_text("{} = 2 + (", varname); - auto watch = statement->get_statements().front(); - watch->get_expression()->visit_children(*this); - printer->add_text(");"); - printer->add_newline(); - - printer->pop_block(); - } - printer->pop_block(); - codegen = false; -} - - -/** - * \todo Similar to print_watch_activate, we are using only - * first watch. need to verify with neuron/coreneuron about rest. - */ -void CodegenCppVisitor::print_watch_check() { - if (info.watch_statements.empty()) { - return; - } - codegen = true; - printer->add_newline(2); - printer->add_line("/** routine to check watch activation */"); - print_global_function_common_code(BlockType::Watch); - - // WATCH statements appears in NET_RECEIVE block and while printing - // net_receive function we already check if it contains any MUTEX/PROTECT - // constructs. As WATCH is not a top level block but list of statements, - // we don't need to have ivdep pragma related check - print_channel_iteration_block_parallel_hint(BlockType::Watch, nullptr); - - printer->push_block("for (int id = 0; id < nodecount; id++)"); - - if (info.is_voltage_used_by_watch_statements()) { - printer->add_line("int node_id = node_index[id];"); - printer->add_line("double v = voltage[node_id];"); - print_v_unused(); - } - - // flat to make sure only one WATCH statement can be triggered at a time - printer->add_line("bool watch_untriggered = true;"); - - for (int i = 0; i < info.watch_statements.size(); i++) { - auto statement = info.watch_statements[i]; - const auto& watch = statement->get_statements().front(); - const auto& varname = get_variable_name(fmt::format("watch{}", i + 1)); - - // start block 1 - printer->fmt_push_block("if ({}&2 && watch_untriggered)", varname); - - // start block 2 - printer->add_indent(); - printer->add_text("if ("); - watch->get_expression()->accept(*this); - printer->add_text(") {"); - printer->add_newline(); - printer->increase_indent(); - - // start block 3 - printer->fmt_push_block("if (({}&1) == 0)", varname); - - printer->add_line("watch_untriggered = false;"); - - const auto& tqitem = get_variable_name("tqitem"); - const auto& point_process = get_variable_name("point_process"); - printer->add_indent(); - printer->add_text("net_send_buffering("); - const auto& t = get_variable_name("t"); - printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, -1, {}, {}+0.0, ", - tqitem, - point_process, - t); - watch->get_value()->accept(*this); - printer->add_text(");"); - printer->add_newline(); - printer->pop_block(); - - printer->add_line(varname, " = 3;"); - // end block 3 - - // start block 3 - printer->decrease_indent(); - printer->push_block("} else"); - printer->add_line(varname, " = 2;"); - printer->pop_block(); - // end block 3 - - printer->pop_block(); - // end block 1 - } - - printer->pop_block(); - print_send_event_move(); - print_kernel_data_present_annotation_block_end(); - printer->pop_block(); - codegen = false; -} - - -void CodegenCppVisitor::print_net_receive_common_code(const Block& node, bool need_mech_inst) { - printer->add_multi_line(R"CODE( - int tid = pnt->_tid; - int id = pnt->_i_instance; - double v = 0; - )CODE"); - - if (info.artificial_cell || node.is_initial_block()) { - printer->add_line("NrnThread* nt = nrn_threads + tid;"); - printer->add_line("Memb_list* ml = nt->_ml_list[pnt->_type];"); - } - if (node.is_initial_block()) { - print_kernel_data_present_annotation_block_begin(); - } - - printer->add_multi_line(R"CODE( - int nodecount = ml->nodecount; - int pnodecount = ml->_nodecount_padded; - double* data = ml->data; - double* weights = nt->weights; - Datum* indexes = ml->pdata; - ThreadDatum* thread = ml->_thread; - )CODE"); - if (need_mech_inst) { - printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); - } - - if (node.is_initial_block()) { - print_net_init_acc_serial_annotation_block_begin(); - } - - // rename variables but need to see if they are actually used - auto parameters = info.net_receive_node->get_parameters(); - if (!parameters.empty()) { - int i = 0; - printer->add_newline(); - for (auto& parameter: parameters) { - auto name = parameter->get_node_name(); - bool var_used = VarUsageVisitor().variable_used(node, "(*" + name + ")"); - if (var_used) { - printer->fmt_line("double* {} = weights + weight_index + {};", name, i); - RenameVisitor vr(name, "*" + name); - node.visit_children(vr); - } - i++; - } - } -} - - -void CodegenCppVisitor::print_net_send_call(const FunctionCall& node) { - auto const& arguments = node.get_arguments(); - const auto& tqitem = get_variable_name("tqitem"); - std::string weight_index = "weight_index"; - std::string pnt = "pnt"; - - // for functions not generated from NET_RECEIVE blocks (i.e. top level INITIAL block) - // the weight_index argument is 0. - if (!printing_net_receive && !printing_net_init) { - weight_index = "0"; - auto var = get_variable_name("point_process"); - if (info.artificial_cell) { - pnt = "(Point_process*)" + var; - } - } - - // artificial cells don't use spike buffering - // clang-format off - if (info.artificial_cell) { - printer->fmt_text("artcell_net_send(&{}, {}, {}, nt->_t+", tqitem, weight_index, pnt); - } else { - const auto& point_process = get_variable_name("point_process"); - const auto& t = get_variable_name("t"); - printer->add_text("net_send_buffering("); - printer->fmt_text("nt, ml->_net_send_buffer, 0, {}, {}, {}, {}+", tqitem, weight_index, point_process, t); - } - // clang-format off - print_vector_elements(arguments, ", "); - printer->add_text(')'); -} - - -void CodegenCppVisitor::print_net_move_call(const FunctionCall& node) { - if (!printing_net_receive && !printing_net_init) { - throw std::runtime_error("Error : net_move only allowed in NET_RECEIVE block"); - } - - auto const& arguments = node.get_arguments(); - const auto& tqitem = get_variable_name("tqitem"); - std::string weight_index = "-1"; - std::string pnt = "pnt"; - - // artificial cells don't use spike buffering - // clang-format off - if (info.artificial_cell) { - printer->fmt_text("artcell_net_move(&{}, {}, ", tqitem, pnt); - print_vector_elements(arguments, ", "); - printer->add_text(")"); - } else { - const auto& point_process = get_variable_name("point_process"); - printer->add_text("net_send_buffering("); - printer->fmt_text("nt, ml->_net_send_buffer, 2, {}, {}, {}, ", tqitem, weight_index, point_process); - print_vector_elements(arguments, ", "); - printer->add_text(", 0.0"); - printer->add_text(")"); - } -} - - -void CodegenCppVisitor::print_net_event_call(const FunctionCall& node) { - const auto& arguments = node.get_arguments(); - if (info.artificial_cell) { - printer->add_text("net_event(pnt, "); - print_vector_elements(arguments, ", "); - } else { - const auto& point_process = get_variable_name("point_process"); - printer->add_text("net_send_buffering("); - printer->fmt_text("nt, ml->_net_send_buffer, 1, -1, -1, {}, ", point_process); - print_vector_elements(arguments, ", "); - printer->add_text(", 0.0"); - } - printer->add_text(")"); -} - -/** - * Rename arguments to NET_RECEIVE block with corresponding pointer variable - * - * Arguments to NET_RECEIVE block are packed and passed via weight vector. These - * variables need to be replaced with corresponding pointer variable. For example, - * if mod file is like - * - * \code{.mod} - * NET_RECEIVE (weight, R){ - * INITIAL { - * R=1 - * } - * } - * \endcode - * - * then generated code for initial block should be: - * - * \code{.cpp} - * double* R = weights + weight_index + 0; - * (*R) = 1.0; - * \endcode - * - * So, the `R` in AST needs to be renamed with `(*R)`. - */ -static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive_node, const ast::Node& node) { - const auto& parameters = net_receive_node.get_parameters(); - for (auto& parameter: parameters) { - const auto& name = parameter->get_node_name(); - auto var_used = VarUsageVisitor().variable_used(node, name); - if (var_used) { - RenameVisitor vr(name, "(*" + name + ")"); - node.get_statement_block()->visit_children(vr); - } - } -} - - -void CodegenCppVisitor::print_net_init() { - const auto node = info.net_receive_initial_node; - if (node == nullptr) { - return; - } - - // rename net_receive arguments used in the initial block of net_receive - rename_net_receive_arguments(*info.net_receive_node, *node); - - codegen = true; - printing_net_init = true; - auto args = "Point_process* pnt, int weight_index, double flag"; - printer->add_newline(2); - printer->add_line("/** initialize block for net receive */"); - printer->fmt_push_block("static void net_init({})", args); - auto block = node->get_statement_block().get(); - if (block->get_statements().empty()) { - printer->add_line("// do nothing"); - } else { - print_net_receive_common_code(*node); - print_statement_block(*block, false, false); - if (node->is_initial_block()) { - print_net_init_acc_serial_annotation_block_end(); - print_kernel_data_present_annotation_block_end(); - printer->add_line("auto& nsb = ml->_net_send_buffer;"); - print_net_send_buf_update_to_host(); - } - } - printer->pop_block(); - codegen = false; - printing_net_init = false; -} - - -void CodegenCppVisitor::print_send_event_move() { - printer->add_newline(); - printer->add_line("NetSendBuffer_t* nsb = ml->_net_send_buffer;"); - print_net_send_buf_update_to_host(); - printer->push_block("for (int i=0; i < nsb->_cnt; i++)"); - printer->add_multi_line(R"CODE( - int type = nsb->_sendtype[i]; - int tid = nt->id; - double t = nsb->_nsb_t[i]; - double flag = nsb->_nsb_flag[i]; - int vdata_index = nsb->_vdata_index[i]; - int weight_index = nsb->_weight_index[i]; - int point_index = nsb->_pnt_index[i]; - net_sem_from_gpu(type, vdata_index, weight_index, tid, point_index, t, flag); - )CODE"); - printer->pop_block(); - printer->add_line("nsb->_cnt = 0;"); - print_net_send_buf_count_update_to_device(); -} - - -std::string CodegenCppVisitor::net_receive_buffering_declaration() { - return fmt::format("void {}(NrnThread* nt)", method_name("net_buf_receive")); -} - - -void CodegenCppVisitor::print_get_memb_list() { - printer->add_line("Memb_list* ml = get_memb_list(nt);"); - printer->push_block("if (!ml)"); - printer->add_line("return;"); - printer->pop_block(); - printer->add_newline(); -} - - -void CodegenCppVisitor::print_net_receive_loop_begin() { - printer->add_line("int count = nrb->_displ_cnt;"); - print_channel_iteration_block_parallel_hint(BlockType::NetReceive, info.net_receive_node); - printer->push_block("for (int i = 0; i < count; i++)"); -} - - -void CodegenCppVisitor::print_net_receive_loop_end() { - printer->pop_block(); -} - - -void CodegenCppVisitor::print_net_receive_buffering(bool need_mech_inst) { - if (!net_receive_required() || info.artificial_cell) { - return; - } - printer->add_newline(2); - printer->push_block(net_receive_buffering_declaration()); - - print_get_memb_list(); - - const auto& net_receive = method_name("net_receive_kernel"); - - print_kernel_data_present_annotation_block_begin(); - - printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); - if (need_mech_inst) { - printer->fmt_line("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); - } - print_net_receive_loop_begin(); - printer->add_line("int start = nrb->_displ[i];"); - printer->add_line("int end = nrb->_displ[i+1];"); - printer->push_block("for (int j = start; j < end; j++)"); - printer->add_multi_line(R"CODE( - int index = nrb->_nrb_index[j]; - int offset = nrb->_pnt_index[index]; - double t = nrb->_nrb_t[index]; - int weight_index = nrb->_weight_index[index]; - double flag = nrb->_nrb_flag[index]; - Point_process* point_process = nt->pntprocs + offset; - )CODE"); - printer->add_line(net_receive, "(t, point_process, inst, nt, ml, weight_index, flag);"); - printer->pop_block(); - print_net_receive_loop_end(); - - print_device_stream_wait(); - printer->add_line("nrb->_displ_cnt = 0;"); - printer->add_line("nrb->_cnt = 0;"); - - if (info.net_send_used || info.net_event_used) { - print_send_event_move(); - } - - print_kernel_data_present_annotation_block_end(); - printer->pop_block(); -} - -void CodegenCppVisitor::print_net_send_buffering_cnt_update() const { - printer->add_line("i = nsb->_cnt++;"); -} - -void CodegenCppVisitor::print_net_send_buffering_grow() { - printer->push_block("if (i >= nsb->_size)"); - printer->add_line("nsb->grow();"); - printer->pop_block(); -} - -void CodegenCppVisitor::print_net_send_buffering() { - if (!net_send_buffer_required()) { - return; - } - - printer->add_newline(2); - print_device_method_annotation(); - auto args = - "const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, " - "int weight_index, int point_index, double t, double flag"; - printer->fmt_push_block("static inline void net_send_buffering({})", args); - printer->add_line("int i = 0;"); - print_net_send_buffering_cnt_update(); - print_net_send_buffering_grow(); - printer->push_block("if (i < nsb->_size)"); - printer->add_multi_line(R"CODE( - nsb->_sendtype[i] = type; - nsb->_vdata_index[i] = vdata_index; - nsb->_weight_index[i] = weight_index; - nsb->_pnt_index[i] = point_index; - nsb->_nsb_t[i] = t; - nsb->_nsb_flag[i] = flag; - )CODE"); - printer->pop_block(); - printer->pop_block(); -} - - -void CodegenCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { - // For_netcon should take the same arguments as net_receive and apply the operations - // in the block to the weights of the netcons. Since all the weights are on the same vector, - // weights, we have a mask of operations that we apply iteratively, advancing the offset - // to the next netcon. - const auto& args = node.get_parameters(); - RenameVisitor v; - const auto& statement_block = node.get_statement_block(); - for (size_t i_arg = 0; i_arg < args.size(); ++i_arg) { - // sanitize node_name since we want to substitute names like (*w) as they are - auto old_name = - std::regex_replace(args[i_arg]->get_node_name(), regex_special_chars, R"(\$&)"); - const auto& new_name = fmt::format("weights[{} + nt->_fornetcon_weight_perm[i]]", i_arg); - v.set(old_name, new_name); - statement_block->accept(v); - } - - const auto index = - std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { - return a.name == naming::FOR_NETCON_SEMANTIC; - })->index; - - printer->fmt_text("const size_t offset = {}*pnodecount + id;", index); - printer->add_newline(); - printer->add_line( - "const size_t for_netcon_start = nt->_fornetcon_perm_indices[indexes[offset]];"); - printer->add_line( - "const size_t for_netcon_end = nt->_fornetcon_perm_indices[indexes[offset] + 1];"); - - printer->add_line("for (auto i = for_netcon_start; i < for_netcon_end; ++i) {"); - printer->increase_indent(); - print_statement_block(*statement_block, false, false); - printer->decrease_indent(); - - printer->add_line("}"); -} - -void CodegenCppVisitor::print_net_receive_kernel() { - if (!net_receive_required()) { - return; - } - codegen = true; - printing_net_receive = true; - const auto node = info.net_receive_node; - - // rename net_receive arguments used in the block itself - rename_net_receive_arguments(*info.net_receive_node, *node); - - std::string name; - ParamVector params; - if (!info.artificial_cell) { - name = method_name("net_receive_kernel"); - params.emplace_back("", "double", "", "t"); - params.emplace_back("", "Point_process*", "", "pnt"); - params.emplace_back("", fmt::format("{}*", instance_struct()), - "", "inst"); - params.emplace_back("", "NrnThread*", "", "nt"); - params.emplace_back("", "Memb_list*", "", "ml"); - params.emplace_back("", "int", "", "weight_index"); - params.emplace_back("", "double", "", "flag"); - } else { - name = method_name("net_receive"); - params.emplace_back("", "Point_process*", "", "pnt"); - params.emplace_back("", "int", "", "weight_index"); - params.emplace_back("", "double", "", "flag"); - } - - printer->add_newline(2); - printer->fmt_push_block("static inline void {}({})", name, get_parameter_str(params)); - print_net_receive_common_code(*node, info.artificial_cell); - if (info.artificial_cell) { - printer->add_line("double t = nt->_t;"); - } - - // set voltage variable if it is used in the block (e.g. for WATCH statement) - auto v_used = VarUsageVisitor().variable_used(*node->get_statement_block(), "v"); - if (v_used) { - printer->add_line("int node_id = ml->nodeindices[id];"); - printer->add_line("v = nt->_actual_v[node_id];"); - } - - printer->fmt_line("{} = t;", get_variable_name("tsave")); - - if (info.is_watch_used()) { - printer->add_line("bool watch_remove = false;"); - } - - printer->add_indent(); - node->get_statement_block()->accept(*this); - printer->add_newline(); - printer->pop_block(); - - printing_net_receive = false; - codegen = false; -} - - -void CodegenCppVisitor::print_net_receive() { - if (!net_receive_required()) { - return; - } - codegen = true; - printing_net_receive = true; - if (!info.artificial_cell) { - const auto& name = method_name("net_receive"); - ParamVector params; - params.emplace_back("", "Point_process*", "", "pnt"); - params.emplace_back("", "int", "", "weight_index"); - params.emplace_back("", "double", "", "flag"); - printer->add_newline(2); - printer->fmt_push_block("static void {}({})", name, get_parameter_str(params)); - printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); - printer->add_line("Memb_list* ml = get_memb_list(nt);"); - printer->add_line("NetReceiveBuffer_t* nrb = ml->_net_receive_buffer;"); - printer->push_block("if (nrb->_cnt >= nrb->_size)"); - printer->add_line("realloc_net_receive_buffer(nt, ml);"); - printer->pop_block(); - printer->add_multi_line(R"CODE( - int id = nrb->_cnt; - nrb->_pnt_index[id] = pnt-nt->pntprocs; - nrb->_weight_index[id] = weight_index; - nrb->_nrb_t[id] = nt->_t; - nrb->_nrb_flag[id] = flag; - nrb->_cnt++; - )CODE"); - printer->pop_block(); - } - printing_net_receive = false; - codegen = false; -} - - -/** - * \todo Data is not derived. Need to add instance into instance struct? - * data used here is wrong in AoS because as in original implementation, - * data is not incremented every iteration for AoS. May be better to derive - * actual variable names? [resolved now?] - * slist needs to added as local variable - */ -void CodegenCppVisitor::print_derivimplicit_kernel(const Block& block) { - auto ext_args = external_method_arguments(); - auto ext_params = external_method_parameters(); - auto suffix = info.mod_suffix; - auto list_num = info.derivimplicit_list_num; - auto block_name = block.get_node_name(); - auto primes_size = info.primes_size; - auto stride = "*pnodecount+id"; - - printer->add_newline(2); - - printer->push_block("namespace"); - printer->fmt_push_block("struct _newton_{}_{}", block_name, info.mod_suffix); - printer->fmt_push_block("int operator()({}) const", external_method_parameters()); - auto const instance = fmt::format("auto* const inst = static_cast<{0}*>(ml->instance);", - instance_struct()); - auto const slist1 = fmt::format("auto const& slist{} = {};", - list_num, - get_variable_name(fmt::format("slist{}", list_num))); - auto const slist2 = fmt::format("auto& slist{} = {};", - list_num + 1, - get_variable_name(fmt::format("slist{}", list_num + 1))); - auto const dlist1 = fmt::format("auto const& dlist{} = {};", - list_num, - get_variable_name(fmt::format("dlist{}", list_num))); - auto const dlist2 = fmt::format( - "double* dlist{} = static_cast(thread[dith{}()].pval) + ({}*pnodecount);", - list_num + 1, - list_num, - info.primes_size); - printer->add_line(instance); - if (ion_variable_struct_required()) { - print_ion_variable(); - } - printer->fmt_line("double* savstate{} = static_cast(thread[dith{}()].pval);", - list_num, - list_num); - printer->add_line(slist1); - printer->add_line(dlist1); - printer->add_line(dlist2); - codegen = true; - print_statement_block(*block.get_statement_block(), false, false); - codegen = false; - printer->add_line("int counter = -1;"); - printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); - printer->fmt_push_block("if (*deriv{}_advance(thread))", list_num); - printer->fmt_line( - "dlist{0}[(++counter){1}] = " - "data[dlist{2}[i]{1}]-(data[slist{2}[i]{1}]-savstate{2}[i{1}])/nt->_dt;", - list_num + 1, - stride, - list_num); - printer->chain_block("else"); - printer->fmt_line("dlist{0}[(++counter){1}] = data[slist{2}[i]{1}]-savstate{2}[i{1}];", - list_num + 1, - stride, - list_num); - printer->pop_block(); - printer->pop_block(); - printer->add_line("return 0;"); - printer->pop_block(); // operator() - printer->pop_block(";"); // struct - printer->pop_block(); // namespace - printer->add_newline(); - printer->fmt_push_block("int {}_{}({})", block_name, suffix, ext_params); - printer->add_line(instance); - printer->fmt_line("double* savstate{} = (double*) thread[dith{}()].pval;", list_num, list_num); - printer->add_line(slist1); - printer->add_line(slist2); - printer->add_line(dlist2); - printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); - printer->fmt_line("savstate{}[i{}] = data[slist{}[i]{}];", list_num, stride, list_num, stride); - printer->pop_block(); - printer->fmt_line( - "int reset = nrn_newton_thread(static_cast(*newtonspace{}(thread)), {}, " - "slist{}, _newton_{}_{}{{}}, dlist{}, {});", - list_num, - primes_size, - list_num + 1, - block_name, - suffix, - list_num + 1, - ext_args); - printer->add_line("return reset;"); - printer->pop_block(); - printer->add_newline(2); -} - - -void CodegenCppVisitor::print_newtonspace_transfer_to_device() const { - // nothing to do on cpu -} - - -void CodegenCppVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { - if (!codegen) { - return; - } - printer->fmt_line("{}_{}({});", - node.get_node_to_solve()->get_node_name(), - info.mod_suffix, - external_method_arguments()); -} - -void CodegenCppVisitor::visit_solution_expression(const SolutionExpression& node) { - auto block = node.get_node_to_solve().get(); - if (block->is_statement_block()) { - auto statement_block = dynamic_cast(block); - print_statement_block(*statement_block, false, false); - } else { - block->accept(*this); - } -} - - -/****************************************************************************************/ -/* Print nrn_state routine */ -/****************************************************************************************/ - - -void CodegenCppVisitor::print_nrn_state() { - if (!nrn_state_required()) { - return; - } - codegen = true; - - printer->add_newline(2); - printer->add_line("/** update state */"); - print_global_function_common_code(BlockType::State); - print_channel_iteration_block_parallel_hint(BlockType::State, info.nrn_state_block); - printer->push_block("for (int id = 0; id < nodecount; id++)"); - - printer->add_line("int node_id = node_index[id];"); - printer->add_line("double v = voltage[node_id];"); - print_v_unused(); - - /** - * \todo Eigen solver node also emits IonCurVar variable in the functor - * but that shouldn't update ions in derivative block - */ - if (ion_variable_struct_required()) { - print_ion_variable(); - } - - auto read_statements = ion_read_statements(BlockType::State); - for (auto& statement: read_statements) { - printer->add_line(statement); - } - - if (info.nrn_state_block) { - info.nrn_state_block->visit_children(*this); - } - - if (info.currents.empty() && info.breakpoint_node != nullptr) { - auto block = info.breakpoint_node->get_statement_block(); - print_statement_block(*block, false, false); - } - - const auto& write_statements = ion_write_statements(BlockType::State); - for (auto& statement: write_statements) { - const auto& text = process_shadow_update_statement(statement, BlockType::State); - printer->add_line(text); - } - printer->pop_block(); - - print_kernel_data_present_annotation_block_end(); - - printer->pop_block(); - codegen = false; -} - - -/****************************************************************************************/ -/* Print nrn_cur related routines */ -/****************************************************************************************/ - - -void CodegenCppVisitor::print_nrn_current(const BreakpointBlock& node) { - const auto& args = internal_method_parameters(); - const auto& block = node.get_statement_block(); - printer->add_newline(2); - print_device_method_annotation(); - printer->fmt_push_block("inline double nrn_current_{}({})", - info.mod_suffix, - get_parameter_str(args)); - printer->add_line("double current = 0.0;"); - print_statement_block(*block, false, false); - for (auto& current: info.currents) { - const auto& name = get_variable_name(current); - printer->fmt_line("current += {};", name); - } - printer->add_line("return current;"); - printer->pop_block(); -} - - -void CodegenCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { - const auto& block = node.get_statement_block(); - print_statement_block(*block, false, false); - if (!info.currents.empty()) { - std::string sum; - for (const auto& current: info.currents) { - auto var = breakpoint_current(current); - sum += get_variable_name(var); - if (¤t != &info.currents.back()) { - sum += "+"; - } - } - printer->fmt_line("double rhs = {};", sum); - } - - std::string sum; - for (const auto& conductance: info.conductances) { - auto var = breakpoint_current(conductance.variable); - sum += get_variable_name(var); - if (&conductance != &info.conductances.back()) { - sum += "+"; - } - } - printer->fmt_line("double g = {};", sum); - - for (const auto& conductance: info.conductances) { - if (!conductance.ion.empty()) { - const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + conductance.ion + "dv"; - const auto& rhs = get_variable_name(conductance.variable); - const ShadowUseStatement statement{lhs, "+=", rhs}; - const auto& text = process_shadow_update_statement(statement, BlockType::Equation); - printer->add_line(text); - } - } -} - - -void CodegenCppVisitor::print_nrn_cur_non_conductance_kernel() { - printer->fmt_line("double g = nrn_current_{}({}+0.001);", - info.mod_suffix, - internal_method_arguments()); - for (auto& ion: info.ions) { - for (auto& var: ion.writes) { - if (ion.is_ionic_current(var)) { - const auto& name = get_variable_name(var); - printer->fmt_line("double di{} = {};", ion.name, name); - } - } - } - printer->fmt_line("double rhs = nrn_current_{}({});", - info.mod_suffix, - internal_method_arguments()); - printer->add_line("g = (g-rhs)/0.001;"); - for (auto& ion: info.ions) { - for (auto& var: ion.writes) { - if (ion.is_ionic_current(var)) { - const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; - auto rhs = fmt::format("(di{}-{})/0.001", ion.name, get_variable_name(var)); - if (info.point_process) { - auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - rhs += fmt::format("*1.e2/{}", area); - } - const ShadowUseStatement statement{lhs, "+=", rhs}; - const auto& text = process_shadow_update_statement(statement, BlockType::Equation); - printer->add_line(text); - } - } - } -} - - -void CodegenCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { - printer->add_line("int node_id = node_index[id];"); - printer->add_line("double v = voltage[node_id];"); - print_v_unused(); - if (ion_variable_struct_required()) { - print_ion_variable(); - } - - const auto& read_statements = ion_read_statements(BlockType::Equation); - for (auto& statement: read_statements) { - printer->add_line(statement); - } - - if (info.conductances.empty()) { - print_nrn_cur_non_conductance_kernel(); - } else { - print_nrn_cur_conductance_kernel(node); - } - - const auto& write_statements = ion_write_statements(BlockType::Equation); - for (auto& statement: write_statements) { - auto text = process_shadow_update_statement(statement, BlockType::Equation); - printer->add_line(text); - } - - if (info.point_process) { - const auto& area = get_variable_name(naming::NODE_AREA_VARIABLE); - printer->fmt_line("double mfactor = 1.e2/{};", area); - printer->add_line("g = g*mfactor;"); - printer->add_line("rhs = rhs*mfactor;"); - } - - print_g_unused(); -} - -void CodegenCppVisitor::print_fast_imem_calculation() { - if (!info.electrode_current) { - return; - } - std::string rhs, d; - auto rhs_op = operator_for_rhs(); - auto d_op = operator_for_d(); - if (info.point_process) { - rhs = "shadow_rhs[id]"; - d = "shadow_d[id]"; - } else { - rhs = "rhs"; - d = "g"; - } - - printer->push_block("if (nt->nrn_fast_imem)"); - if (nrn_cur_reduction_loop_required()) { - printer->push_block("for (int id = 0; id < nodecount; id++)"); - printer->add_line("int node_id = node_index[id];"); - } - printer->fmt_line("nt->nrn_fast_imem->nrn_sav_rhs[node_id] {} {};", rhs_op, rhs); - printer->fmt_line("nt->nrn_fast_imem->nrn_sav_d[node_id] {} {};", d_op, d); - if (nrn_cur_reduction_loop_required()) { - printer->pop_block(); - } - printer->pop_block(); -} - -void CodegenCppVisitor::print_nrn_cur() { - if (!nrn_cur_required()) { - return; - } - - codegen = true; - if (info.conductances.empty()) { - print_nrn_current(*info.breakpoint_node); - } - - printer->add_newline(2); - printer->add_line("/** update current */"); - print_global_function_common_code(BlockType::Equation); - print_channel_iteration_block_parallel_hint(BlockType::Equation, info.breakpoint_node); - printer->push_block("for (int id = 0; id < nodecount; id++)"); - print_nrn_cur_kernel(*info.breakpoint_node); - print_nrn_cur_matrix_shadow_update(); - if (!nrn_cur_reduction_loop_required()) { - print_fast_imem_calculation(); - } - printer->pop_block(); - - if (nrn_cur_reduction_loop_required()) { - printer->push_block("for (int id = 0; id < nodecount; id++)"); - print_nrn_cur_matrix_shadow_reduction(); - printer->pop_block(); - print_fast_imem_calculation(); - } - - print_kernel_data_present_annotation_block_end(); - printer->pop_block(); - codegen = false; -} - - -/****************************************************************************************/ -/* Main code printing entry points */ -/****************************************************************************************/ - -void CodegenCppVisitor::print_headers_include() { - print_standard_includes(); - print_backend_includes(); - print_coreneuron_includes(); -} - - -void CodegenCppVisitor::print_namespace_begin() { - print_namespace_start(); - print_backend_namespace_start(); -} - - -void CodegenCppVisitor::print_namespace_end() { - print_backend_namespace_stop(); - print_namespace_stop(); -} - - -void CodegenCppVisitor::print_common_getters() { - print_first_pointer_var_index_getter(); - print_net_receive_arg_size_getter(); - print_thread_getters(); - print_num_variable_getter(); - print_mech_type_getter(); - print_memb_list_getter(); -} - - -void CodegenCppVisitor::print_data_structures(bool print_initializers) { - print_mechanism_global_var_structure(print_initializers); - print_mechanism_range_var_structure(print_initializers); - print_ion_var_structure(); -} - -void CodegenCppVisitor::print_v_unused() const { - if (!info.vectorize) { - return; - } - printer->add_multi_line(R"CODE( - #if NRN_PRCELLSTATE - inst->v_unused[id] = v; - #endif - )CODE"); -} - -void CodegenCppVisitor::print_g_unused() const { - printer->add_multi_line(R"CODE( - #if NRN_PRCELLSTATE - inst->g_unused[id] = g; - #endif - )CODE"); -} - -void CodegenCppVisitor::print_compute_functions() { - print_top_verbatim_blocks(); - for (const auto& procedure: info.procedures) { - print_procedure(*procedure); - } - for (const auto& function: info.functions) { - print_function(*function); - } - for (const auto& function: info.function_tables) { - print_function_tables(*function); - } - for (size_t i = 0; i < info.before_after_blocks.size(); i++) { - print_before_after_block(info.before_after_blocks[i], i); - } - for (const auto& callback: info.derivimplicit_callbacks) { - const auto& block = *callback->get_node_to_solve(); - print_derivimplicit_kernel(block); - } - print_net_send_buffering(); - print_net_init(); - print_watch_activate(); - print_watch_check(); - print_net_receive_kernel(); - print_net_receive(); - print_net_receive_buffering(); - print_nrn_init(); - print_nrn_cur(); - print_nrn_state(); -} - - -void CodegenCppVisitor::print_codegen_routines() { - codegen = true; - print_backend_info(); - print_headers_include(); - print_namespace_begin(); - print_nmodl_constants(); - print_prcellstate_macros(); - print_mechanism_info(); - print_data_structures(true); - print_global_variables_for_hoc(); - print_common_getters(); - print_memory_allocation_routine(); - print_abort_routine(); - print_thread_memory_callbacks(); - print_instance_variable_setup(); - print_nrn_alloc(); - print_nrn_constructor(); - print_nrn_destructor(); - print_function_prototypes(); - print_functors_definitions(); - print_compute_functions(); - print_check_table_thread_function(); - print_mechanism_register(); - print_namespace_end(); - codegen = false; -} - - -void CodegenCppVisitor::set_codegen_global_variables(const std::vector& global_vars) { - codegen_global_variables = global_vars; -} - - void CodegenCppVisitor::setup(const Program& node) { program_symtab = node.get_symbol_table(); @@ -4685,7 +881,8 @@ void CodegenCppVisitor::setup(const Program& node) { info.mod_file = mod_filename; if (!info.vectorize) { - logger->warn("CodegenCppVisitor : MOD file uses non-thread safe constructs of NMODL"); + logger->warn( + "CodegenCoreneuronCppVisitor : MOD file uses non-thread safe constructs of NMODL"); } codegen_float_variables = get_float_variables(); @@ -4702,4 +899,4 @@ void CodegenCppVisitor::visit_program(const Program& node) { } } // namespace codegen -} // namespace nmodl +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index ee0344058c..66016af917 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -31,7 +31,6 @@ #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" - /// encapsulates code generation backend implementations namespace nmodl { @@ -182,6 +181,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { protected: using SymbolType = std::shared_ptr; + /** * A vector of parameters represented by a 4-tuple of strings: * @@ -193,62 +193,84 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ using ParamVector = std::vector>; + + /****************************************************************************************/ + /* Member variables */ + /****************************************************************************************/ + /** * Code printer object for target (C++) */ std::unique_ptr printer; + /** * Name of mod file (without .mod suffix) */ std::string mod_filename; + /** * Data type of floating point variables */ std::string float_type = codegen::naming::DEFAULT_FLOAT_TYPE; + /** * Flag to indicate if visitor should avoid ion variable copies */ bool optimize_ionvar_copies = true; - /** - * Flag to indicate if visitor should print the visited nodes - */ - bool codegen = false; /** - * Variable name should be converted to instance name (but not for function arguments) + * All ast information for code generation */ - bool enable_variable_name_lookup = true; + codegen::CodegenInfo info; + /** * Symbol table for the program */ symtab::SymbolTable* program_symtab = nullptr; + /** * All float variables for the model */ std::vector codegen_float_variables; + /** * All int variables for the model */ std::vector codegen_int_variables; + /** * All global variables for the model * \todo: this has become different than CodegenInfo */ std::vector codegen_global_variables; + + /** + * Flag to indicate if visitor should print the visited nodes + */ + bool codegen = false; + + + /** + * Variable name should be converted to instance name (but not for function arguments) + */ + bool enable_variable_name_lookup = true; + + /** * \c true if currently net_receive block being printed */ bool printing_net_receive = false; + /** * \c true if currently initial block of net_receive being printed */ @@ -260,20 +282,22 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ bool printing_top_verbatim_blocks = false; + /** * \c true if internal method call was encountered while processing verbatim block */ bool internal_method_call_encountered = false; + /** * Index of watch statement being printed */ int current_watch_statement = 0; - /** - * All ast information for code generation - */ - codegen::CodegenInfo info; + + /****************************************************************************************/ + /* Generic information getters */ + /****************************************************************************************/ /** * Return Nmodl language version @@ -283,31 +307,17 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return codegen::naming::NMODL_VERSION; } - /** - * Add quotes to string to be output - * - * \param text The string to be quoted - * \return The same string with double-quotes pre- and postfixed - */ - std::string add_escape_quote(const std::string& text) const { - return "\"" + text + "\""; - } - /** - * Operator for rhs vector update (matrix update) + * Name of the simulator the code was generated for */ - const char* operator_for_rhs() const noexcept { - return info.electrode_current ? "+=" : "-="; - } + virtual std::string simulator_name() = 0; /** - * Operator for diagonal vector update (matrix update) + * Name of the code generation backend */ - const char* operator_for_d() const noexcept { - return info.electrode_current ? "-=" : "+="; - } + virtual std::string backend_name() const = 0; /** @@ -318,6 +328,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { } + /** + * Check if a semicolon is required at the end of given statement + * \param node The AST Statement node to check + * \return \c true if this Statement requires a semicolon + */ + static bool need_semicolon(const ast::Statement& node); + + /** * Default data type for floating point elements */ @@ -343,75 +361,50 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** - * Checks if given function name is \c net_send - * \param name The function name to check - * \return \c true if the function is net_send - */ - bool is_net_send(const std::string& name) const noexcept { - return name == codegen::naming::NET_SEND_METHOD; - } - - /** - * Checks if given function name is \c net_move - * \param name The function name to check - * \return \c true if the function is net_move - */ - bool is_net_move(const std::string& name) const noexcept { - return name == codegen::naming::NET_MOVE_METHOD; - } - - /** - * Checks if given function name is \c net_event - * \param name The function name to check - * \return \c true if the function is net_event + * Operator for rhs vector update (matrix update) */ - bool is_net_event(const std::string& name) const noexcept { - return name == codegen::naming::NET_EVENT_METHOD; + const char* operator_for_rhs() const noexcept { + return info.electrode_current ? "+=" : "-="; } /** - * Name of structure that wraps range variables + * Operator for diagonal vector update (matrix update) */ - std::string instance_struct() const { - return fmt::format("{}_Instance", info.mod_suffix); + const char* operator_for_d() const noexcept { + return info.electrode_current ? "-=" : "+="; } - /** - * Name of structure that wraps global variables - */ - std::string global_struct() const { - return fmt::format("{}_Store", info.mod_suffix); - } + /****************************************************************************************/ + /* Common helper routines accross codegen functions */ + /****************************************************************************************/ /** - * Name of the (host-only) global instance of `global_struct` + * Check if function or procedure node has parameter with given name + * + * \tparam T Node type (either procedure or function) + * \param node AST node (either procedure or function) + * \param name Name of parameter + * \return True if argument with name exist */ - std::string global_struct_instance() const { - return info.mod_suffix + "_global"; - } + template + bool has_parameter_of_name(const T& node, const std::string& name); /** - * Constructs the name of a function or procedure - * \param name The name of the function or procedure - * \return The name of the function or procedure postfixed with the model name + * Check if given statement should be skipped during code generation + * \param node The AST Statement node to check + * \return \c true if this Statement is to be skipped */ - std::string method_name(const std::string& name) const { - return name + "_" + info.mod_suffix; - } + static bool statement_to_skip(const ast::Statement& node); /** - * Creates a temporary symbol - * \param name The name of the symbol - * \return A symbol based on the given name + * Check if net_send_buffer is required */ - SymbolType make_symbol(const std::string& name) const { - return std::make_shared(name, ModToken()); - } + bool net_send_buffer_required() const noexcept; /** @@ -438,12 +431,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { bool net_receive_required() const noexcept; - /** - * Check if net_send_buffer is required - */ - bool net_send_buffer_required() const noexcept; - - /** * Check if setup_range_variable function is required * \return @@ -472,39 +459,33 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** - * Check if given statement should be skipped during code generation - * \param node The AST Statement node to check - * \return \c true if this Statement is to be skipped - */ - static bool statement_to_skip(const ast::Statement& node); - - - /** - * Check if a semicolon is required at the end of given statement - * \param node The AST Statement node to check - * \return \c true if this Statement requires a semicolon - */ - static bool need_semicolon(const ast::Statement& node); - - - /** - * Determine the number of threads to allocate + * Checks if given function name is \c net_send + * \param name The function name to check + * \return \c true if the function is net_send */ - int num_thread_objects() const noexcept { - return info.vectorize ? (info.thread_data_index + 1) : 0; + bool is_net_send(const std::string& name) const noexcept { + return name == codegen::naming::NET_SEND_METHOD; } /** - * Number of float variables in the model + * Checks if given function name is \c net_move + * \param name The function name to check + * \return \c true if the function is net_move */ - int float_variables_size() const; + bool is_net_move(const std::string& name) const noexcept { + return name == codegen::naming::NET_MOVE_METHOD; + } /** - * Number of integer variables in the model + * Checks if given function name is \c net_event + * \param name The function name to check + * \return \c true if the function is net_event */ - int int_variables_size() const; + bool is_net_event(const std::string& name) const noexcept { + return name == codegen::naming::NET_EVENT_METHOD; + } /** @@ -512,7 +493,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param name The name of a float variable * \return The position index in the data array */ - int position_of_float_var(const std::string& name) const; + virtual int position_of_float_var(const std::string& name) const = 0; /** @@ -520,21 +501,19 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param name The name of an int variable * \return The position index in the data array */ - int position_of_int_var(const std::string& name) const; + virtual int position_of_int_var(const std::string& name) const = 0; /** - * Determine the updated name if the ion variable has been optimized - * \param name The ion variable name - * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena) + * Number of float variables in the model */ - std::string update_if_ion_variable_name(const std::string& name) const; + int float_variables_size() const; /** - * Name of the code generation backend + * Number of integer variables in the model */ - virtual std::string backend_name() const; + int int_variables_size() const; /** @@ -542,7 +521,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param value The number to convert given as string as it is parsed by the modfile * \return Its string representation */ - virtual std::string format_double_string(const std::string& value); + std::string format_double_string(const std::string& value); /** @@ -550,70 +529,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param value The number to convert given as string as it is parsed by the modfile * \return Its string representation */ - virtual std::string format_float_string(const std::string& value); - - - /** - * Determine the name of a \c float variable given its symbol - * - * This function typically returns the accessor expression in backend code for the given symbol. - * Since the model variables are stored in data arrays and accessed by offset, this function - * will return the C++ string representing the array access at the correct offset - * - * \param symbol The symbol of a variable for which we want to obtain its name - * \param use_instance Should the variable be accessed via instance or data array - * \return The backend code string representing the access to the given variable - * symbol - */ - std::string float_variable_name(const SymbolType& symbol, bool use_instance) const; - - - /** - * Determine the name of an \c int variable given its symbol - * - * This function typically returns the accessor expression in backend code for the given symbol. - * Since the model variables are stored in data arrays and accessed by offset, this function - * will return the C++ string representing the array access at the correct offset - * - * \param symbol The symbol of a variable for which we want to obtain its name - * \param name The name of the index variable - * \param use_instance Should the variable be accessed via instance or data array - * \return The backend code string representing the access to the given variable - * symbol - */ - std::string int_variable_name(const IndexVariableInfo& symbol, - const std::string& name, - bool use_instance) const; - - - /** - * Determine the variable name for a global variable given its symbol - * \param symbol The symbol of a variable for which we want to obtain its name - * \param use_instance Should the variable be accessed via the (host-only) - * global variable or the instance-specific copy (also available on GPU). - * \return The C++ string representing the access to the global variable - */ - std::string global_variable_name(const SymbolType& symbol, bool use_instance = true) const; - - - /** - * Determine variable name in the structure of mechanism properties - * - * \param name Variable name that is being printed - * \param use_instance Should the variable be accessed via instance or data array - * \return The C++ string representing the access to the variable in the neuron - * thread structure - */ - std::string get_variable_name(const std::string& name, bool use_instance = true) const; - - - /** - * Determine the variable name for the "current" used in breakpoint block taking into account - * intermediate code transformations. - * \param current The variable name for the current used in the model - * \return The name for the current to be printed in C++ - */ - std::string breakpoint_current(std::string current) const; + std::string format_float_string(const std::string& value); /** @@ -636,33 +552,20 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::vector get_int_variables(); - /** - * Print the items in a vector as a list - * - * This function prints a given vector of elements as a list with given separator onto the - * current printer. Elements are expected to be of type nmodl::ast::Ast and are printed by being - * visited. Care is taken to omit the separator after the the last element. - * - * \tparam T The element type in the vector, which must be of type nmodl::ast::Ast - * \param elements The vector of elements to be printed - * \param separator The separator string to print between all elements - * \param prefix A prefix string to print before each element - */ - template - void print_vector_elements(const std::vector& elements, - const std::string& separator, - const std::string& prefix = ""); + /****************************************************************************************/ + /* Backend specific routines */ + /****************************************************************************************/ + /** - * Generate the string representing the procedure parameter declaration - * - * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. - * - * \param params The parameters that should be concatenated into the function parameter - * declaration - * \return The string representing the declaration of function parameters + * Print atomic update pragma for reduction statements */ - static std::string get_parameter_str(const ParamVector& params); + virtual void print_atomic_reduction_pragma() = 0; + + + /****************************************************************************************/ + /* Printing routines for code generation */ + /****************************************************************************************/ /** @@ -681,108 +584,95 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** - * Check if a structure for ion variables is required - * \return \c true if a structure fot ion variables must be generated + * Print call to internal or external function + * \param node The AST node representing a function call */ - bool ion_variable_struct_required() const; + virtual void print_function_call(const ast::FunctionCall& node) = 0; /** - * Process a verbatim block for possible variable renaming - * \param text The verbatim code to be processed - * \return The code with all variables renamed as needed + * Print function and procedures prototype declaration */ - std::string process_verbatim_text(std::string const& text); + virtual void print_function_prototypes() = 0; /** - * Process a token in a verbatim block for possible variable renaming - * \param token The verbatim token to be processed - * \return The code after variable renaming + * Print nmodl function or procedure (common code) + * \param node the AST node representing the function or procedure in NMODL + * \param name the name of the function or procedure */ - std::string process_verbatim_token(const std::string& token); + virtual void print_function_or_procedure(const ast::Block& node, const std::string& name) = 0; /** - * Rename function/procedure arguments that conflict with default arguments + * Common helper function to help printing function or procedure blocks + * \param node the AST node representing the function or procedure in NMODL */ - void rename_function_arguments(); + virtual void print_function_procedure_helper(const ast::Block& node) = 0; /** - * For a given output block type, return statements for all read ion variables - * - * \param type The type of code block being generated - * \return A \c vector of strings representing the reading of ion variables - */ - std::vector ion_read_statements(BlockType type) const; - - - /** - * For a given output block type, return minimal statements for all read ion variables - * - * \param type The type of code block being generated - * \return A \c vector of strings representing the reading of ion variables + * Print NMODL procedure in target backend code + * \param node */ - std::vector ion_read_statements_optimized(BlockType type) const; + virtual void print_procedure(const ast::ProcedureBlock& node) = 0; /** - * For a given output block type, return statements for writing back ion variables - * - * \param type The type of code block being generated - * \return A \c vector of strings representing the write-back of ion variables + * Print NMODL function in target backend code + * \param node */ - std::vector ion_write_statements(BlockType type); + virtual void print_function(const ast::FunctionBlock& node) = 0; /** - * Return ion variable name and corresponding ion read variable name - * \param name The ion variable name - * \return The ion read variable name + * Rename function/procedure arguments that conflict with default arguments */ - static std::pair read_ion_variable_name(const std::string& name); + void rename_function_arguments(); /** - * Return ion variable name and corresponding ion write variable name - * \param name The ion variable name - * \return The ion write variable name + * Print the items in a vector as a list + * + * This function prints a given vector of elements as a list with given separator onto the + * current printer. Elements are expected to be of type nmodl::ast::Ast and are printed by being + * visited. Care is taken to omit the separator after the the last element. + * + * \tparam T The element type in the vector, which must be of type nmodl::ast::Ast + * \param elements The vector of elements to be printed + * \param separator The separator string to print between all elements + * \param prefix A prefix string to print before each element */ - static std::pair write_ion_variable_name(const std::string& name); + template + void print_vector_elements(const std::vector& elements, + const std::string& separator, + const std::string& prefix = ""); - /** - * Generate Function call statement for nrn_wrote_conc - * \param ion_name The name of the ion variable - * \param concentration The name of the concentration variable - * \param index - * \return The string representing the function call - */ - std::string conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index); + /****************************************************************************************/ + /* Code-specific helper routines */ + /****************************************************************************************/ /** * Arguments for functions that are defined and used internally. * \return the method arguments */ - std::string internal_method_arguments(); + virtual std::string internal_method_arguments() = 0; /** * Parameters for internally defined functions * \return the method parameters */ - ParamVector internal_method_parameters(); + virtual ParamVector internal_method_parameters() = 0; /** * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - static const char* external_method_arguments() noexcept; + virtual const char* external_method_arguments() noexcept = 0; /** @@ -794,180 +684,161 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param table * \return A string representing the parameters of the function */ - static const char* external_method_parameters(bool table = false) noexcept; - - - /** - * Arguments for register_mech or point_register_mech function - */ - std::string register_mechanism_arguments() const; + virtual const char* external_method_parameters(bool table = false) noexcept = 0; /** * Arguments for "_threadargs_" macro in neuron implementation */ - std::string nrn_thread_arguments() const; + virtual std::string nrn_thread_arguments() const = 0; /** * Arguments for "_threadargs_" macro in neuron implementation */ - std::string nrn_thread_internal_arguments(); - + virtual std::string nrn_thread_internal_arguments() = 0; /** - * Replace commonly used verbatim variables - * \param name A variable name to be checked and possibly updated - * \return The possibly replace variable name + * Process a verbatim block for possible variable renaming + * \param text The verbatim code to be processed + * \return The code with all variables renamed as needed */ - std::string replace_if_verbatim_variable(std::string name); + virtual std::string process_verbatim_text(std::string const& text) = 0; /** - * Return the name of main compute kernels - * \param type A block type + * Arguments for register_mech or point_register_mech function */ - virtual std::string compute_method_name(BlockType type) const; + virtual std::string register_mechanism_arguments() const = 0; /** - * The used global type qualifier - * - * For C++ code generation this is empty - * \return "" - * - * \return "uniform " - */ - virtual std::string global_var_struct_type_qualifier(); - - /** - * Instantiate global var instance + * Add quotes to string to be output * - * For C++ code generation this is empty - * \return "" - */ - virtual void print_global_var_struct_decl(); - - /** - * Print static assertions about the global variable struct. - */ - virtual void print_global_var_struct_assertions() const; - - /** - * Prints the start of the \c coreneuron namespace + * \param text The string to be quoted + * \return The same string with double-quotes pre- and postfixed */ - void print_namespace_start(); + std::string add_escape_quote(const std::string& text) const { + return "\"" + text + "\""; + } /** - * Prints the end of the \c coreneuron namespace + * Constructs the name of a function or procedure + * \param name The name of the function or procedure + * \return The name of the function or procedure postfixed with the model name */ - void print_namespace_stop(); + std::string method_name(const std::string& name) const { + return name + "_" + info.mod_suffix; + } /** - * Prints the start of namespace for the backend-specific code - * - * For the C++ backend no additional namespace is required + * Creates a temporary symbol + * \param name The name of the symbol + * \return A symbol based on the given name */ - virtual void print_backend_namespace_start(); + SymbolType make_symbol(const std::string& name) const { + return std::make_shared(name, ModToken()); + } - /** - * Prints the end of namespace for the backend-specific code - * - * For the C++ backend no additional namespace is required - */ - virtual void print_backend_namespace_stop(); + /****************************************************************************************/ + /* Code-specific printing routines for code generations */ + /****************************************************************************************/ /** - * Print the nmodl constants used in backend code - * - * Currently we define three basic constants, which are assumed to be present in NMODL, directly - * in the backend code: - * - * \code - * static const double FARADAY = 96485.3; - * static const double PI = 3.14159; - * static const double R = 8.3145; - * \endcode + * Prints the start of the simulator namespace */ - virtual void print_nmodl_constants(); + virtual void print_namespace_start() = 0; /** - * Print top file header printed in generated code + * Prints the end of the simulator namespace */ - void print_backend_info(); + virtual void print_namespace_stop() = 0; - /** - * Print memory allocation routine - */ - virtual void print_memory_allocation_routine() const; + /****************************************************************************************/ + /* Routines for returning variable name */ + /****************************************************************************************/ /** - * Print backend specific abort routine + * Determine the name of a \c float variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C++ string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol */ - virtual void print_abort_routine() const; + virtual std::string float_variable_name(const SymbolType& symbol, bool use_instance) const = 0; /** - * Print standard C/C++ includes + * Determine the name of an \c int variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C++ string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param name The name of the index variable + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol */ - void print_standard_includes(); + virtual std::string int_variable_name(const IndexVariableInfo& symbol, + const std::string& name, + bool use_instance) const = 0; /** - * Print includes from coreneuron + * Determine the variable name for a global variable given its symbol + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via the (host-only) + * global variable or the instance-specific copy (also available on GPU). + * \return The C++ string representing the access to the global variable */ - void print_coreneuron_includes(); + virtual std::string global_variable_name(const SymbolType& symbol, + bool use_instance = true) const = 0; /** - * Print backend specific includes (none needed for C++ backend) + * Determine variable name in the structure of mechanism properties + * + * \param name Variable name that is being printed + * \param use_instance Should the variable be accessed via instance or data array + * \return The C++ string representing the access to the variable in the neuron + * thread structure */ - virtual void print_backend_includes(); + virtual std::string get_variable_name(const std::string& name, + bool use_instance = true) const = 0; - /** - * Check if ion variable copies should be avoided - */ - bool optimize_ion_variable_copies() const; + /****************************************************************************************/ + /* Main printing routines for code generation */ + /****************************************************************************************/ /** - * Check if reduction block in \c nrn\_cur required + * Print top file header printed in generated code */ - virtual bool nrn_cur_reduction_loop_required(); + virtual void print_backend_info() = 0; /** - * Check if variable is qualified as constant - * \param name The name of variable - * \return \c true if it is constant - */ - virtual bool is_constant_variable(const std::string& name) const; - - /** - * Check if the given name exist in the symbol - * \return \c return a tuple if variable - * is an array otherwise + * Print standard C/C++ includes */ - std::tuple check_if_var_is_array(const std::string& name); + virtual void print_standard_includes() = 0; - /** - * Print declaration of macro NRN_PRCELLSTATE for debugging - */ - void print_prcellstate_macros() const; - /** - * Print backend code for byte array that has mechanism information (to be registered - * with coreneuron) - */ - void print_mechanism_info(); + virtual void print_sdlists_init(bool print_initializers) = 0; /** @@ -976,788 +847,233 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param print_initializers Whether to include default values in the struct * definition (true: int foo{42}; false: int foo;) */ - void print_mechanism_global_var_structure(bool print_initializers); + virtual void print_mechanism_global_var_structure(bool print_initializers) = 0; /** - * Print structure of ion variables used for local copies + * Print declaration of macro NRN_PRCELLSTATE for debugging */ - void print_ion_var_structure(); + void print_prcellstate_macros() const; /** - * Print constructor of ion variables - * \param members The ion variable names + * Print backend code for byte array that has mechanism information (to be registered + * with NEURON/CoreNEURON) */ - virtual void print_ion_var_constructor(const std::vector& members); + void print_mechanism_info(); /** - * Print the ion variable struct + * Print byte arrays that register scalar and vector variables for hoc interface + * */ - virtual void print_ion_variable(); + virtual void print_global_variables_for_hoc() = 0; /** - * Returns floating point type for given range variable symbol - * \param symbol A range variable symbol + * Print the mechanism registration function + * */ - std::string get_range_var_float_type(const SymbolType& symbol); + virtual void print_mechanism_register() = 0; /** - * Print the function that initialize range variable with different data type + * Print common code for global functions like nrn_init, nrn_cur and nrn_state + * \param type The target backend code block type */ - void print_setup_range_variable(); - + virtual void print_global_function_common_code(BlockType type, + const std::string& function_name = "") = 0; - /** - * Print declarations of the functions used by \ref - * print_instance_struct_copy_to_device and \ref - * print_instance_struct_delete_from_device. - */ - virtual void print_instance_struct_transfer_routine_declarations() {} /** - * Print the definitions of the functions used by \ref - * print_instance_struct_copy_to_device and \ref - * print_instance_struct_delete_from_device. Declarations of these functions - * are printed by \ref print_instance_struct_transfer_routine_declarations. - * - * This updates the (pointer) member variables in the device copy of the - * instance struct to contain device pointers, which is why you must pass a - * list of names of those member variables. + * Print nrn_constructor function definition * - * \param ptr_members List of instance struct member names. */ - virtual void print_instance_struct_transfer_routines( - std::vector const& /* ptr_members */) {} + virtual void print_nrn_constructor() = 0; /** - * Transfer the instance struct to the device. This calls a function - * declared by \ref print_instance_struct_transfer_routine_declarations. - */ - virtual void print_instance_struct_copy_to_device() {} - - /** - * Delete the instance struct from the device. This calls a function - * declared by \ref print_instance_struct_transfer_routine_declarations. + * Print nrn_destructor function definition + * */ - virtual void print_instance_struct_delete_from_device() {} + virtual void print_nrn_destructor() = 0; /** - * Print the code to copy derivative advance flag to device + * Print nrn_alloc function definition + * */ - virtual void print_deriv_advance_flag_transfer_to_device() const; - + virtual void print_nrn_alloc() = 0; - /** - * Print the code to update NetSendBuffer_t count from device to host - */ - virtual void print_net_send_buf_count_update_to_host() const; - /** - * Print the code to update NetSendBuffer_t from device to host - */ - virtual void print_net_send_buf_update_to_host() const; + /****************************************************************************************/ + /* Print nrn_state routine */ + /****************************************************************************************/ /** - * Print the code to update NetSendBuffer_t count from host to device + * Print nrn_state / state update function definition */ - virtual void print_net_send_buf_count_update_to_device() const; + virtual void print_nrn_state() = 0; - /** - * Print the code to update dt from host to device - */ - virtual void print_dt_update_to_device() const; - /** - * Print the code to synchronise/wait on stream specific to NrnThread - */ - virtual void print_device_stream_wait() const; + /****************************************************************************************/ + /* Print nrn_cur related routines */ + /****************************************************************************************/ /** - * Print byte arrays that register scalar and vector variables for hoc interface + * Print the \c nrn_current kernel * + * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified + * \param node the AST node representing the NMODL breakpoint block */ - void print_global_variables_for_hoc(); + virtual void print_nrn_current(const ast::BreakpointBlock& node) = 0; /** - * Print the getter method for thread variables and ids + * Print the \c nrn\_cur kernel with NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is used in the \c breakpoint block, then + * CodegenCoreneuronCppVisitor::print_nrn_cur_kernel will use this printer * + * \param node the AST node representing the NMODL breakpoint block */ - void print_thread_getters(); + virtual void print_nrn_cur_conductance_kernel(const ast::BreakpointBlock& node) = 0; /** - * Print the getter method for index position of first pointer variable + * Print the \c nrn\_cur kernel without NMODL \c conductance keyword provisions * + * If the NMODL \c conductance keyword is \b not used in the \c breakpoint block, then + * CodegenCoreneuronCppVisitor::print_nrn_cur_kernel will use this printer */ - void print_first_pointer_var_index_getter(); + virtual void print_nrn_cur_non_conductance_kernel() = 0; /** - * Print the getter methods for float and integer variables count - * + * Print main body of nrn_cur function + * \param node the AST node representing the NMODL breakpoint block */ - void print_num_variable_getter(); + virtual void print_nrn_cur_kernel(const ast::BreakpointBlock& node) = 0; /** - * Print the getter method for getting number of arguments for net_receive - * + * Print fast membrane current calculation code */ - void print_net_receive_arg_size_getter(); + virtual void print_fast_imem_calculation() = 0; /** - * Print the getter method for returning membrane list from NrnThread - * + * Print nrn_cur / current update function definition */ - void print_memb_list_getter(); + virtual void print_nrn_cur() = 0; - /** - * Print the getter method for returning mechtype - * - */ - void print_mech_type_getter(); + /****************************************************************************************/ + /* Main code printing entry points */ + /****************************************************************************************/ /** - * Print the pragma annotation to update global variables from host to the device + * Print all includes * - * \note This is not used for the C++ backend */ - virtual void print_global_variable_device_update_annotation(); + virtual void print_headers_include() = 0; /** - * Print the setup method for setting matrix shadow vectors + * Print start of namespaces * */ - virtual void print_rhs_d_shadow_variables(); + virtual void print_namespace_begin() = 0; /** - * Print the backend specific device method annotation + * Print end of namespaces * - * \note This is not used for the C++ backend */ - virtual void print_device_method_annotation(); + virtual void print_namespace_end() = 0; /** - * Print backend specific global method annotation - * - * \note This is not used for the C++ backend + * Print all classes + * \param print_initializers Whether to include default values. */ - virtual void print_global_method_annotation(); + virtual void print_data_structures(bool print_initializers) = 0; /** - * Print call to internal or external function - * \param node The AST node representing a function call + * Set v_unused (voltage) for NRN_PRCELLSTATE feature */ - void print_function_call(const ast::FunctionCall& node); + virtual void print_v_unused() const = 0; /** - * Print call to \c net\_send - * \param node The AST node representing the function call + * Set g_unused (conductance) for NRN_PRCELLSTATE feature */ - void print_net_send_call(const ast::FunctionCall& node); + virtual void print_g_unused() const = 0; /** - * Print call to net\_move - * \param node The AST node representing the function call + * Print all compute functions for every backend + * */ - void print_net_move_call(const ast::FunctionCall& node); + virtual void print_compute_functions() = 0; /** - * Print call to net\_event - * \param node The AST node representing the function call + * Print entry point to code generation + * */ - void print_net_event_call(const ast::FunctionCall& node); + virtual void print_codegen_routines() = 0; /** - * Print pragma annotations for channel iterations + * Print the nmodl constants used in backend code * - * This can be overriden by backends to provide additonal annotations or pragmas to enable - * for example SIMD code generation (e.g. through \c ivdep) - * The default implementation prints + * Currently we define three basic constants, which are assumed to be present in NMODL, directly + * in the backend code: * * \code - * #pragma ivdep + * static const double FARADAY = 96485.3; + * static const double PI = 3.14159; + * static const double R = 8.3145; * \endcode - * - * \param type The block type - */ - virtual void print_channel_iteration_block_parallel_hint(BlockType type, - const ast::Block* block); - - - /** - * Print accelerator annotations indicating data presence on device - */ - virtual void print_kernel_data_present_annotation_block_begin(); - - - /** - * Print matching block end of accelerator annotations for data presence on device - */ - virtual void print_kernel_data_present_annotation_block_end(); - - - /** - * Print accelerator kernels begin annotation for net_init kernel - */ - virtual void print_net_init_acc_serial_annotation_block_begin(); - - - /** - * Print accelerator kernels end annotation for net_init kernel - */ - virtual void print_net_init_acc_serial_annotation_block_end(); - - - /** - * Print function and procedures prototype declaration */ - void print_function_prototypes(); + void print_nmodl_constants(); - /** - * Print check_table functions - */ - void print_check_table_thread_function(); + /****************************************************************************************/ + /* Protected constructors */ + /****************************************************************************************/ - /** - * Print nmodl function or procedure (common code) - * \param node the AST node representing the function or procedure in NMODL - * \param name the name of the function or procedure - */ - void print_function_or_procedure(const ast::Block& node, const std::string& name); + /// This constructor is private, only the derived classes' public constructors are public + CodegenCppVisitor(std::string mod_filename, + const std::string& output_dir, + std::string float_type, + const bool optimize_ionvar_copies) + : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp")) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) + , optimize_ionvar_copies(optimize_ionvar_copies) {} - /** - * Common helper function to help printing function or procedure blocks - * \param node the AST node representing the function or procedure in NMODL - */ - void print_function_procedure_helper(const ast::Block& node); + /// This constructor is private, only the derived classes' public constructors are public + CodegenCppVisitor(std::string mod_filename, + std::ostream& stream, + std::string float_type, + const bool optimize_ionvar_copies) + : printer(std::make_unique(stream)) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) + , optimize_ionvar_copies(optimize_ionvar_copies) {} - /** - * Print thread related memory allocation and deallocation callbacks - */ - void print_thread_memory_callbacks(); - - /** - * Print top level (global scope) verbatim blocks - */ - void print_top_verbatim_blocks(); - - - /** - * Print prototype declarations of functions or procedures - * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) - * \param node The AST node representing the function or procedure block - * \param name A user defined name for the function - */ - template - void print_function_declaration(const T& node, const std::string& name); - - - /** - * Print initial block statements - * - * Generate the target backend code corresponding to the NMODL initial block statements - * - * \param node The AST Node representing a NMODL initial block - */ - void print_initial_block(const ast::InitialBlock* node); - - - /** - * Print initial block in the net receive block - */ - void print_net_init(); - - - /** - * Print the common code section for net receive related methods - * - * \param node The AST node representing the corresponding NMODL block - * \param need_mech_inst \c true if a local \c inst variable needs to be defined in generated - * code - */ - void print_net_receive_common_code(const ast::Block& node, bool need_mech_inst = true); - - - /** - * Print the code related to the update of NetSendBuffer_t cnt. For GPU this needs to be done - * with atomic operation, on CPU it's not needed. - * - */ - virtual void print_net_send_buffering_cnt_update() const; - - - /** - * Print statement that grows NetSendBuffering_t structure if needed. - * This function should be overridden for backends that cannot dynamically reallocate the buffer - */ - virtual void print_net_send_buffering_grow(); - - - /** - * Print kernel for buffering net_send events - * - * This kernel is only needed for accelerator backends where \c net\_send needs to be executed - * in two stages as the actual communication must be done in the host code. - */ - void print_net_send_buffering(); - - - /** - * Print send event move block used in net receive as well as watch - */ - void print_send_event_move(); - - - /** - * Generate the target backend code for the \c net\_receive\_buffering function delcaration - * \return The target code string - */ - virtual std::string net_receive_buffering_declaration(); - - - /** - * Print the target backend code for defining and checking a local \c Memb\_list variable - */ - virtual void print_get_memb_list(); - - - /** - * Print the code for the main \c net\_receive loop - */ - virtual void print_net_receive_loop_begin(); - - - /** - * Print the code for closing the main \c net\_receive loop - */ - virtual void print_net_receive_loop_end(); - - - /** - * Print \c net\_receive function definition - */ - void print_net_receive(); - - - /** - * Print derivative kernel when \c derivimplicit method is used - * - * \param block The corresponding AST node representing an NMODL \c derivimplicit block - */ - void print_derivimplicit_kernel(const ast::Block& block); - - - /** - * Print code block to transfer newtonspace structure to device - */ - virtual void print_newtonspace_transfer_to_device() const; - - - /** - * Print pragma annotation for increase and capture of variable in automatic way - */ - virtual void print_device_atomic_capture_annotation() const; - - /** - * Print atomic update pragma for reduction statements - */ - virtual void print_atomic_reduction_pragma(); - - - /** - * Print all reduction statements - * - */ - void print_shadow_reduction_statements(); - - - /** - * Process shadow update statement - * - * If the statement requires reduction then add it to vector of reduction statement and return - * statement using shadow update - * - * \param statement The statement that might require shadow updates - * \param type The target backend code block type - * \return The generated target backend code - */ - std::string process_shadow_update_statement(const ShadowUseStatement& statement, - BlockType type); - - - /** - * Print main body of nrn_cur function - * \param node the AST node representing the NMODL breakpoint block - */ - void print_nrn_cur_kernel(const ast::BreakpointBlock& node); - - - /** - * Print the \c nrn\_cur kernel with NMODL \c conductance keyword provisions - * - * If the NMODL \c conductance keyword is used in the \c breakpoint block, then - * CodegenCppVisitor::print_nrn_cur_kernel will use this printer - * - * \param node the AST node representing the NMODL breakpoint block - */ - void print_nrn_cur_conductance_kernel(const ast::BreakpointBlock& node); - - - /** - * Print the \c nrn\_cur kernel without NMODL \c conductance keyword provisions - * - * If the NMODL \c conductance keyword is \b not used in the \c breakpoint block, then - * CodegenCppVisitor::print_nrn_cur_kernel will use this printer - */ - void print_nrn_cur_non_conductance_kernel(); - - - /** - * Print the \c nrn_current kernel - * - * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified - * \param node the AST node representing the NMODL breakpoint block - */ - void print_nrn_current(const ast::BreakpointBlock& node); - - - /** - * Print the update to matrix elements with/without shadow vectors - * - */ - virtual void print_nrn_cur_matrix_shadow_update(); - - - /** - * Print the reduction to matrix elements from shadow vectors - * - */ - virtual void print_nrn_cur_matrix_shadow_reduction(); - - - /** - * Print nrn_constructor function definition - * - */ - void print_nrn_constructor(); - - - /** - * Print nrn_destructor function definition - * - */ - void print_nrn_destructor(); - - - /** - * Print nrn_alloc function definition - * - */ - void print_nrn_alloc(); - - - /** - * Print common code for global functions like nrn_init, nrn_cur and nrn_state - * \param type The target backend code block type - */ - virtual void print_global_function_common_code(BlockType type, - const std::string& function_name = ""); - - - /** - * Print the mechanism registration function - * - */ - void print_mechanism_register(); - - - /** - * Print watch activate function - * - */ - void print_watch_activate(); - - - /** - * Print all includes - * - */ - virtual void print_headers_include(); - - - /** - * Print start of namespaces - * - */ - void print_namespace_begin(); - - - /** - * Print end of namespaces - * - */ - void print_namespace_end(); - - - /** - * Print common getters - * - */ - void print_common_getters(); - - - /** - * Print all classes - * \param print_initializers Whether to include default values. - */ - void print_data_structures(bool print_initializers); - - - /** - * Set v_unused (voltage) for NRN_PRCELLSTATE feature - */ - void print_v_unused() const; - - - /** - * Set g_unused (conductance) for NRN_PRCELLSTATE feature - */ - void print_g_unused() const; - - - /** - * Print all compute functions for every backend - * - */ - virtual void print_compute_functions(); - - - /** - * Print entry point to code generation - * - */ - virtual void print_codegen_routines(); - - - public: - /** - * \brief Constructs the C++ code generator visitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * directly to a file in \c [output_dir]/[mod_filename].[extension]. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param output_dir The directory where target C++ file should be generated. - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenCppVisitor(std::string mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies) - : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp")) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - /** - * \copybrief nmodl::codegen::CodegenCppVisitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * into an output stream. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param stream The output stream onto which to write the generated code - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenCppVisitor(std::string mod_filename, - std::ostream& stream, - std::string float_type, - const bool optimize_ionvar_copies) - : printer(std::make_unique(stream)) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - /** - * Main and only member function to call after creating an instance of this class. - * \param program the AST to translate to C++ code - */ - void visit_program(const ast::Program& program) override; - - /** - * Print the \c nrn\_init function definition - * \param skip_init_check \c true to generate code executing the initialization conditionally - */ - void print_nrn_init(bool skip_init_check = true); - - - /** - * Print nrn_state / state update function definition - */ - void print_nrn_state(); - - - /** - * Print nrn_cur / current update function definition - */ - void print_nrn_cur(); - - /** - * Print fast membrane current calculation code - */ - virtual void print_fast_imem_calculation(); - - - /** - * Print kernel for buffering net_receive events - * - * This kernel is only needed for accelerator backends where \c net\_receive needs to be - * executed in two stages as the actual communication must be done in the host code. \param - * need_mech_inst \c true if the generated code needs a local inst variable to be defined - */ - void print_net_receive_buffering(bool need_mech_inst = true); - - - /** - * Print \c net\_receive kernel function definition - */ - void print_net_receive_kernel(); - - - /** - * Print watch activate function - */ - void print_watch_check(); - - - /** - * Print \c check\_function() for functions or procedure using table - * \param node The AST node representing a function or procedure block - */ - void print_table_check_function(const ast::Block& node); - - - /** - * Print replacement function for function or procedure using table - * \param node The AST node representing a function or procedure block - */ - void print_table_replacement_function(const ast::Block& node); - - - /** - * Print NMODL function in target backend code - * \param node - */ - void print_function(const ast::FunctionBlock& node); - - - /** - * Print NMODL function_table in target backend code - * \param node - */ - void print_function_tables(const ast::FunctionTableBlock& node); - - - /** - * Print NMODL procedure in target backend code - * \param node - */ - virtual void print_procedure(const ast::ProcedureBlock& node); - - /** - * Print NMODL before / after block in target backend code - * \param node AST node of type before/after type being printed - * \param block_id Index of the before/after block - */ - virtual void print_before_after_block(const ast::Block* node, size_t block_id); - - /** Setup the target backend code generator - * - * Typically called from within \c visit\_program but may be called from - * specialized targets to setup this Code generator as fallback. - */ - void setup(const ast::Program& node); - - - /** - * Set the global variables to be generated in target backend code - * \param global_vars - */ - void set_codegen_global_variables(const std::vector& global_vars); - - /** - * Find unique variable name defined in nmodl::utils::SingletonRandomString by the - * nmodl::visitor::SympySolverVisitor - * \param original_name Original name of variable to change - * \return std::string Unique name produced as [original_name]_[random_string] - */ - std::string find_var_unique_name(const std::string& original_name) const; - - /** - * Print the structure that wraps all range and int variables required for the NMODL - * - * \param print_initializers Whether or not default values for variables - * be included in the struct declaration. - */ - void print_mechanism_range_var_structure(bool print_initializers); - - /** - * Print the function that initialize instance structure - */ - void print_instance_variable_setup(); - - /** - * Go through the map of \c EigenNewtonSolverBlock s and their corresponding functor names - * and print the functor definitions before the definitions of the functions of the generated - * file - * - */ - void print_functors_definitions(); - - /** - * \brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its - * functor - * - * \param node \c EigenNewtonSolverBlock for which to print the functor - */ - void print_functor_definition(const ast::EigenNewtonSolverBlock& node); + /****************************************************************************************/ + /* Overloaded visitor routines */ + /****************************************************************************************/ void visit_binary_expression(const ast::BinaryExpression& node) override; void visit_binary_operator(const ast::BinaryOperator& node) override; @@ -1768,9 +1084,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_float(const ast::Float& node) override; void visit_from_statement(const ast::FromStatement& node) override; void visit_function_call(const ast::FunctionCall& node) override; - void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; - void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; - virtual void print_eigen_linear_solver(const std::string& float_type, int N); void visit_if_statement(const ast::IfStatement& node) override; void visit_indexed_name(const ast::IndexedName& node) override; void visit_integer(const ast::Integer& node) override; @@ -1780,22 +1093,48 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_prime_name(const ast::PrimeName& node) override; void visit_statement_block(const ast::StatementBlock& node) override; void visit_string(const ast::String& node) override; - void visit_solution_expression(const ast::SolutionExpression& node) override; void visit_unary_operator(const ast::UnaryOperator& node) override; void visit_unit(const ast::Unit& node) override; void visit_var_name(const ast::VarName& node) override; void visit_verbatim(const ast::Verbatim& node) override; - void visit_watch_statement(const ast::WatchStatement& node) override; void visit_while_statement(const ast::WhileStatement& node) override; - void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; - void visit_for_netcon(const ast::ForNetcon& node) override; void visit_update_dt(const ast::UpdateDt& node) override; void visit_protect_statement(const ast::ProtectStatement& node) override; void visit_mutex_lock(const ast::MutexLock& node) override; void visit_mutex_unlock(const ast::MutexUnlock& node) override; -}; + public: + /** Setup the target backend code generator + * + * Typically called from within \c visit\_program but may be called from + * specialized targets to setup this Code generator as fallback. + */ + void setup(const ast::Program& node); + + + /** + * Main and only member function to call after creating an instance of this class. + * \param program the AST to translate to C++ code + */ + void visit_program(const ast::Program& program) override; + + + /****************************************************************************************/ + /* Public printing routines for code generation for use in unit tests */ + /****************************************************************************************/ + + + /** + * Print the structure that wraps all range and int variables required for the NMODL + * + * \param print_initializers Whether or not default values for variables + * be included in the struct declaration. + */ + virtual void print_mechanism_range_var_structure(bool print_initializers) = 0; +}; + +/* Templated functions need to be defined in header file */ template void CodegenCppVisitor::print_vector_elements(const std::vector& elements, const std::string& separator, @@ -1810,60 +1149,7 @@ void CodegenCppVisitor::print_vector_elements(const std::vector& elements, } -/** - * Check if function or procedure node has parameter with given name - * - * \tparam T Node type (either procedure or function) - * \param node AST node (either procedure or function) - * \param name Name of parameter - * \return True if argument with name exist - */ -template -bool has_parameter_of_name(const T& node, const std::string& name) { - auto parameters = node->get_parameters(); - return std::any_of(parameters.begin(), - parameters.end(), - [&name](const decltype(*parameters.begin()) arg) { - return arg->get_node_name() == name; - }); -} - - -/** - * \details If there is an argument with name (say alpha) same as range variable (say alpha), - * we want to avoid it being printed as instance->alpha. And hence we disable variable - * name lookup during prototype declaration. Note that the name of procedure can be - * different in case of table statement. - */ -template -void CodegenCppVisitor::print_function_declaration(const T& node, const std::string& name) { - enable_variable_name_lookup = false; - auto type = default_float_data_type(); - - // internal and user provided arguments - auto internal_params = internal_method_parameters(); - const auto& params = node.get_parameters(); - for (const auto& param: params) { - internal_params.emplace_back("", type, "", param.get()->get_node_name()); - } - - // procedures have "int" return type by default - const char* return_type = "int"; - if (node.is_function_block()) { - return_type = default_float_data_type(); - } - - print_device_method_annotation(); - printer->add_indent(); - printer->fmt_text("inline {} {}({})", - return_type, - method_name(name), - get_parameter_str(internal_params)); - - enable_variable_name_lookup = true; -} - /** \} */ // end of codegen_backends } // namespace codegen -} // namespace nmodl +} // namespace nmodl \ No newline at end of file diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 67cf6c9bb4..6627c05776 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -253,6 +253,10 @@ struct CodegenInfo { /// typically equal to number of primes int num_equations = 0; + /// True if we have to emit CVODE code + /// TODO: Figure out when this needs to be true + bool emit_cvode = false; + /// derivative block const ast::BreakpointBlock* breakpoint_node = nullptr; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp new file mode 100644 index 0000000000..54abb3b87e --- /dev/null +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -0,0 +1,687 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "codegen/codegen_neuron_cpp_visitor.hpp" + +#include +#include +#include +#include +#include + +#include "ast/all.hpp" +#include "codegen/codegen_utils.hpp" +#include "config/config.h" +#include "utils/string_utils.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace codegen { + +using namespace ast; + +using symtab::syminfo::NmodlType; + + +/****************************************************************************************/ +/* Generic information getters */ +/****************************************************************************************/ + + +std::string CodegenNeuronCppVisitor::simulator_name() { + return "NEURON"; +} + + +std::string CodegenNeuronCppVisitor::backend_name() const { + return "C++ (api-compatibility)"; +} + + +/****************************************************************************************/ +/* Common helper routines accross codegen functions */ +/****************************************************************************************/ + + +int CodegenNeuronCppVisitor::position_of_float_var(const std::string& name) const { + const auto has_name = [&name](const SymbolType& symbol) { return symbol->get_name() == name; }; + const auto var_iter = + std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), has_name); + if (var_iter != codegen_float_variables.end()) { + return var_iter - codegen_float_variables.begin(); + } else { + throw std::logic_error(name + " variable not found"); + } +} + + +int CodegenNeuronCppVisitor::position_of_int_var(const std::string& name) const { + const auto has_name = [&name](const IndexVariableInfo& index_var_symbol) { + return index_var_symbol.symbol->get_name() == name; + }; + const auto var_iter = + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), has_name); + if (var_iter != codegen_int_variables.end()) { + return var_iter - codegen_int_variables.begin(); + } else { + throw std::logic_error(name + " variable not found"); + } +} + + +/****************************************************************************************/ +/* Backend specific routines */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_atomic_reduction_pragma() { + return; +} + + +/****************************************************************************************/ +/* Printing routines for code generation */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_function_call(const FunctionCall& node) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_function_prototypes() { + if (info.functions.empty() && info.procedures.empty()) { + return; + } + codegen = true; + /// TODO: Fill in + codegen = false; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node, + const std::string& name) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_function_procedure_helper(const ast::Block& node) { + codegen = true; + /// TODO: Fill in + codegen = false; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_procedure(const ast::ProcedureBlock& node) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_function(const ast::FunctionBlock& node) { + return; +} + + +/****************************************************************************************/ +/* Code-specific helper routines */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::internal_method_arguments() { + return {}; +} + + +/// TODO: Edit for NEURON +CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { + return {}; +} + + +/// TODO: Edit for NEURON +const char* CodegenNeuronCppVisitor::external_method_arguments() noexcept { + return {}; +} + + +/// TODO: Edit for NEURON +const char* CodegenNeuronCppVisitor::external_method_parameters(bool table) noexcept { + return {}; +} + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::nrn_thread_arguments() const { + return {}; +} + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::nrn_thread_internal_arguments() { + return {}; +} + + +/// TODO: Write for NEURON +std::string CodegenNeuronCppVisitor::process_verbatim_text(std::string const& text) { + return {}; +} + + +/// TODO: Write for NEURON +std::string CodegenNeuronCppVisitor::register_mechanism_arguments() const { + return {}; +}; + + +/****************************************************************************************/ +/* Code-specific printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenNeuronCppVisitor::print_namespace_start() { + printer->add_newline(2); + printer->push_block("namespace neuron"); +} + + +void CodegenNeuronCppVisitor::print_namespace_stop() { + printer->pop_block(); +} + + +/****************************************************************************************/ +/* Routines for returning variable name */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbol, + bool use_instance) const { + return symbol->get_name(); +} + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& symbol, + const std::string& name, + bool use_instance) const { + return name; +} + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symbol, + bool use_instance) const { + return symbol->get_name(); +} + + +/// TODO: Edit for NEURON +std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, + bool use_instance) const { + return name; +} + + +/****************************************************************************************/ +/* Main printing routines for code generation */ +/****************************************************************************************/ + + +void CodegenNeuronCppVisitor::print_backend_info() { + time_t current_time{}; + time(¤t_time); + std::string data_time_str{std::ctime(¤t_time)}; + auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; + + printer->add_line("/*********************************************************"); + printer->add_line("Model Name : ", info.mod_suffix); + printer->add_line("Filename : ", info.mod_file, ".mod"); + printer->add_line("NMODL Version : ", nmodl_version()); + printer->fmt_line("Vectorized : {}", info.vectorize); + printer->fmt_line("Threadsafe : {}", info.thread_safe); + printer->add_line("Created : ", stringutils::trim(data_time_str)); + printer->add_line("Simulator : ", simulator_name()); + printer->add_line("Backend : ", backend_name()); + printer->add_line("NMODL Compiler : ", version); + printer->add_line("*********************************************************/"); +} + + +void CodegenNeuronCppVisitor::print_standard_includes() { + printer->add_newline(); + printer->add_multi_line(R"CODE( + #include + #include + #include + )CODE"); + if (!info.vectorize) { + printer->add_line("#include "); + } +} + + +void CodegenNeuronCppVisitor::print_neuron_includes() { + printer->add_newline(); + printer->add_multi_line(R"CODE( + #include "mech_api.h" + #include "neuron/cache/mechanism_range.hpp" + #include "nrniv_mf.h" + #include "section_fwd.hpp" + )CODE"); +} + + +void CodegenNeuronCppVisitor::print_sdlists_init(bool print_initializers) { + for (auto i = 0; i < info.prime_variables_by_order.size(); ++i) { + const auto& prime_var = info.prime_variables_by_order[i]; + /// TODO: Something similar needs to happen for slist/dlist2 but I don't know their usage at + // the moment + /// TODO: We have to do checks and add errors similar to nocmodl in the + // SemanticAnalysisVisitor + if (prime_var->is_array()) { + /// TODO: Needs a for loop here. Look at + // https://github.com/neuronsimulator/nrn/blob/df001a436bcb4e23d698afe66c2a513819a6bfe8/src/nmodl/deriv.cpp#L524 + /// TODO: Also needs a test + printer->fmt_push_block("for (int _i = 0; _i < {}; ++_i)", prime_var->get_length()); + printer->fmt_line("/* {}[{}] */", prime_var->get_name(), prime_var->get_length()); + printer->fmt_line("_slist1[{}+_i] = {{{}, _i}}", + i, + position_of_float_var(prime_var->get_name())); + const auto prime_var_deriv_name = "D" + prime_var->get_name(); + printer->fmt_line("/* {}[{}] */", prime_var_deriv_name, prime_var->get_length()); + printer->fmt_line("_dlist1[{}+_i] = {{{}, _i}}", + i, + position_of_float_var(prime_var_deriv_name)); + printer->pop_block(); + } else { + printer->fmt_line("/* {} */", prime_var->get_name()); + printer->fmt_line("_slist1[{}] = {{{}, 0}}", + i, + position_of_float_var(prime_var->get_name())); + const auto prime_var_deriv_name = "D" + prime_var->get_name(); + printer->fmt_line("/* {} */", prime_var_deriv_name); + printer->fmt_line("_dlist1[{}] = {{{}, 0}}", + i, + position_of_float_var(prime_var_deriv_name)); + } + } +} + + +void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { + /// TODO: Print only global variables printed in NEURON + printer->add_line(); + printer->add_line("/* NEURON global variables */"); + if (info.primes_size != 0) { + printer->fmt_line("static neuron::container::field_index _slist1[{0}], _dlist1[{0}];", + info.primes_size); + } +} + + +/// TODO: Same as CoreNEURON? +void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { + /// TODO: Write HocParmLimits and other HOC global variables (delta_t) + // Probably needs more changes + auto variable_printer = + [&](const std::vector& variables, bool if_array, bool if_vector) { + for (const auto& variable: variables) { + if (variable->is_array() == if_array) { + // false => do not use the instance struct, which is not + // defined in the global declaration that we are printing + auto name = get_variable_name(variable->get_name(), false); + auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); + auto length = variable->get_length(); + if (if_vector) { + printer->fmt_line("{{{}, {}, {}}},", ename, name, length); + } else { + printer->fmt_line("{{{}, &{}}},", ename, name); + } + } + } + }; + + auto globals = info.global_variables; + auto thread_vars = info.thread_variables; + + if (info.table_count > 0) { + globals.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); + } + + printer->add_newline(2); + printer->add_line("/** connect global (scalar) variables to hoc -- */"); + printer->add_line("static DoubScal hoc_scalar_double[] = {"); + printer->increase_indent(); + variable_printer(globals, false, false); + variable_printer(thread_vars, false, false); + printer->add_line("{nullptr, nullptr}"); + printer->decrease_indent(); + printer->add_line("};"); + + printer->add_newline(2); + printer->add_line("/** connect global (array) variables to hoc -- */"); + printer->add_line("static DoubVec hoc_vector_double[] = {"); + printer->increase_indent(); + variable_printer(globals, true, true); + variable_printer(thread_vars, true, true); + printer->add_line("{nullptr, nullptr, 0}"); + printer->decrease_indent(); + printer->add_line("};"); +} + + +void CodegenNeuronCppVisitor::print_mechanism_register() { + /// TODO: Write this according to NEURON + printer->add_newline(2); + printer->add_line("/** register channel with the simulator */"); + printer->fmt_push_block("void _{}_reg()", info.mod_file); + print_sdlists_init(true); + // type related information + auto suffix = add_escape_quote(info.mod_suffix); + printer->add_newline(); + printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); + + // More things to add here + printer->add_line("_nrn_mechanism_register_data_fields(_mechtype,"); + printer->increase_indent(); + const auto codegen_float_variables_size = codegen_float_variables.size(); + for (int i = 0; i < codegen_float_variables_size; ++i) { + const auto& float_var = codegen_float_variables[i]; + const auto print_comma = i < codegen_float_variables_size - 1 || info.emit_cvode; + if (float_var->is_array()) { + printer->fmt_line("_nrn_mechanism_field{{\"{}\", {}}} /* {} */{}", + float_var->get_name(), + float_var->get_length(), + i, + print_comma ? "," : ""); + } else { + printer->fmt_line("_nrn_mechanism_field{{\"{}\"}} /* {} */{}", + float_var->get_name(), + i, + print_comma ? "," : ""); + } + } + if (info.emit_cvode) { + printer->add_line("_nrn_mechanism_field{\"_cvode_ieq\", \"cvodeieq\"} /* 0 */"); + } + printer->decrease_indent(); + printer->add_line(");"); + printer->add_newline(); + printer->pop_block(); +} + + +void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { + printer->add_line("/* NEURON RANGE variables macro definitions */"); + for (auto i = 0; i < codegen_float_variables.size(); ++i) { + const auto float_var = codegen_float_variables[i]; + if (float_var->is_array()) { + printer->add_line("#define ", + float_var->get_name(), + "(id) _ml->template data_array<", + std::to_string(i), + ", ", + std::to_string(float_var->get_length()), + ">(id)"); + } else { + printer->add_line("#define ", + float_var->get_name(), + "(id) _ml->template fpfield<", + std::to_string(i), + ">(id)"); + } + } +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, + const std::string& function_name) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_constructor() { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_destructor() { + return; +} + + +/// TODO: Print the equivalent of `nrn_alloc_` +void CodegenNeuronCppVisitor::print_nrn_alloc() { + return; +} + + +/****************************************************************************************/ +/* Print nrn_state routine */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_state() { + if (!nrn_state_required()) { + return; + } + codegen = true; + + printer->add_line("void nrn_state() {}"); + /// TODO: Fill in + + codegen = false; +} + + +/****************************************************************************************/ +/* Print nrn_cur related routines */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_cur_non_conductance_kernel() { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_fast_imem_calculation() { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_nrn_cur() { + if (!nrn_cur_required()) { + return; + } + + codegen = true; + + printer->add_line("void nrn_cur() {}"); + /// TODO: Fill in + + codegen = false; +} + + +/****************************************************************************************/ +/* Main code printing entry points */ +/****************************************************************************************/ + +void CodegenNeuronCppVisitor::print_headers_include() { + print_standard_includes(); + print_neuron_includes(); +} + + +void CodegenNeuronCppVisitor::print_macro_definitions() { + print_global_macros(); + print_mechanism_variables_macros(); +} + + +void CodegenNeuronCppVisitor::print_global_macros() { + printer->add_newline(); + printer->add_line("/* NEURON global macro definitions */"); + if (info.vectorize) { + printer->add_multi_line(R"CODE( + /* VECTORIZED */ + #define NRN_VECTORIZED 1 + )CODE"); + } else { + printer->add_multi_line(R"CODE( + /* NOT VECTORIZED */ + #define NRN_VECTORIZED 0 + )CODE"); + } +} + + +void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { + printer->add_newline(); + printer->add_line("static constexpr auto number_of_datum_variables = ", + std::to_string(int_variables_size()), + ";"); + printer->add_line("static constexpr auto number_of_floating_point_variables = ", + std::to_string(float_variables_size()), + ";"); + printer->add_newline(); + printer->add_multi_line(R"CODE( + namespace { + template + using _nrn_mechanism_std_vector = std::vector; + using _nrn_model_sorted_token = neuron::model_sorted_token; + using _nrn_mechanism_cache_range = neuron::cache::MechanismRange; + using _nrn_mechanism_cache_instance = neuron::cache::MechanismInstance; + template + using _nrn_mechanism_field = neuron::mechanism::field; + template + void _nrn_mechanism_register_data_fields(Args&&... args) { + neuron::mechanism::register_data_fields(std::forward(args)...); + } + } // namespace + )CODE"); + /// TODO: More prints here? +} + + +void CodegenNeuronCppVisitor::print_namespace_begin() { + print_namespace_start(); +} + + +void CodegenNeuronCppVisitor::print_namespace_end() { + print_namespace_stop(); +} + + +void CodegenNeuronCppVisitor::print_data_structures(bool print_initializers) { + print_mechanism_global_var_structure(print_initializers); + print_mechanism_range_var_structure(print_initializers); +} + + +void CodegenNeuronCppVisitor::print_v_unused() const { + if (!info.vectorize) { + return; + } + printer->add_multi_line(R"CODE( + #if NRN_PRCELLSTATE + inst->v_unused[id] = v; + #endif + )CODE"); +} + + +void CodegenNeuronCppVisitor::print_g_unused() const { + printer->add_multi_line(R"CODE( + #if NRN_PRCELLSTATE + inst->g_unused[id] = g; + #endif + )CODE"); +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_compute_functions() { + print_nrn_cur(); + print_nrn_state(); +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_codegen_routines() { + codegen = true; + print_backend_info(); + print_headers_include(); + print_macro_definitions(); + print_namespace_begin(); + print_nmodl_constants(); + print_prcellstate_macros(); + print_mechanism_info(); + print_data_structures(true); + print_global_variables_for_hoc(); + print_compute_functions(); // only nrn_cur and nrn_state + print_mechanism_register(); + print_namespace_end(); + codegen = false; +} + + +/****************************************************************************************/ +/* Overloaded visitor routines */ +/****************************************************************************************/ + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::visit_solution_expression(const SolutionExpression& node) { + return; +} + + +/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { + return; +} + + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp new file mode 100644 index 0000000000..e8bf7e36af --- /dev/null +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -0,0 +1,643 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/** + * \dir + * \brief Code generation backend implementations for NEURON + * + * \file + * \brief \copybrief nmodl::codegen::CodegenNeuronCppVisitor + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "codegen/codegen_info.hpp" +#include "codegen/codegen_naming.hpp" +#include "printer/code_printer.hpp" +#include "symtab/symbol_table.hpp" +#include "utils/logger.hpp" +#include "visitors/ast_visitor.hpp" +#include + + +/// encapsulates code generation backend implementations +namespace nmodl { + +namespace codegen { + + +using printer::CodePrinter; + + +/** + * \defgroup codegen_backends Codegen Backends + * \ingroup codegen + * \brief Code generation backends for NEURON + * \{ + */ + +/** + * \class CodegenNeuronCppVisitor + * \brief %Visitor for printing C++ code compatible with legacy api of NEURON + * + * \todo + * - Handle define statement (i.e. macros) + * - If there is a return statement in the verbatim block + * of inlined function then it will be error. Need better + * error checking. For example, see netstim.mod where we + * have removed return from verbatim block. + */ +class CodegenNeuronCppVisitor: public CodegenCppVisitor { + protected: + /****************************************************************************************/ + /* Member variables */ + /****************************************************************************************/ + + + /****************************************************************************************/ + /* Generic information getters */ + /****************************************************************************************/ + + + /** + * Name of the simulator the code was generated for + */ + std::string simulator_name() override; + + + /** + * Name of the code generation backend + */ + virtual std::string backend_name() const override; + + + /****************************************************************************************/ + /* Common helper routines accross codegen functions */ + /****************************************************************************************/ + + + /** + * Determine the position in the data array for a given float variable + * \param name The name of a float variable + * \return The position index in the data array + */ + int position_of_float_var(const std::string& name) const override; + + + /** + * Determine the position in the data array for a given int variable + * \param name The name of an int variable + * \return The position index in the data array + */ + int position_of_int_var(const std::string& name) const override; + + + /****************************************************************************************/ + /* Backend specific routines */ + /****************************************************************************************/ + + + /** + * Print atomic update pragma for reduction statements + */ + virtual void print_atomic_reduction_pragma() override; + + + /****************************************************************************************/ + /* Printing routines for code generation */ + /****************************************************************************************/ + + + /** + * Print call to internal or external function + * \param node The AST node representing a function call + */ + void print_function_call(const ast::FunctionCall& node) override; + + + /** + * Print function and procedures prototype declaration + */ + void print_function_prototypes() override; + + + /** + * Print nmodl function or procedure (common code) + * \param node the AST node representing the function or procedure in NMODL + * \param name the name of the function or procedure + */ + void print_function_or_procedure(const ast::Block& node, const std::string& name) override; + + + /** + * Common helper function to help printing function or procedure blocks + * \param node the AST node representing the function or procedure in NMODL + */ + void print_function_procedure_helper(const ast::Block& node) override; + + + /** + * Print NMODL procedure in target backend code + * \param node + */ + virtual void print_procedure(const ast::ProcedureBlock& node) override; + + + /** + * Print NMODL function in target backend code + * \param node + */ + void print_function(const ast::FunctionBlock& node) override; + + + /****************************************************************************************/ + /* Code-specific helper routines */ + /****************************************************************************************/ + + + /** + * Arguments for functions that are defined and used internally. + * \return the method arguments + */ + std::string internal_method_arguments() override; + + + /** + * Parameters for internally defined functions + * \return the method parameters + */ + ParamVector internal_method_parameters() override; + + + /** + * Arguments for external functions called from generated code + * \return A string representing the arguments passed to an external function + */ + const char* external_method_arguments() noexcept override; + + + /** + * Parameters for functions in generated code that are called back from external code + * + * Functions registered in NEURON during initialization for callback must adhere to a prescribed + * calling convention. This method generates the string representing the function parameters for + * these externally called functions. + * \param table + * \return A string representing the parameters of the function + */ + const char* external_method_parameters(bool table = false) noexcept override; + + + /** + * Arguments for "_threadargs_" macro in neuron implementation + */ + std::string nrn_thread_arguments() const override; + + + /** + * Arguments for "_threadargs_" macro in neuron implementation + */ + std::string nrn_thread_internal_arguments() override; + + + /** + * Process a verbatim block for possible variable renaming + * \param text The verbatim code to be processed + * \return The code with all variables renamed as needed + */ + std::string process_verbatim_text(std::string const& text) override; + + + /** + * Arguments for register_mech or point_register_mech function + */ + std::string register_mechanism_arguments() const override; + + + /****************************************************************************************/ + /* Code-specific printing routines for code generations */ + /****************************************************************************************/ + + + /** + * Prints the start of the \c neuron namespace + */ + void print_namespace_start() override; + + + /** + * Prints the end of the \c neuron namespace + */ + void print_namespace_stop() override; + + + /****************************************************************************************/ + /* Routines for returning variable name */ + /****************************************************************************************/ + + + /** + * Determine the name of a \c float variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C++ string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol + */ + std::string float_variable_name(const SymbolType& symbol, bool use_instance) const override; + + + /** + * Determine the name of an \c int variable given its symbol + * + * This function typically returns the accessor expression in backend code for the given symbol. + * Since the model variables are stored in data arrays and accessed by offset, this function + * will return the C++ string representing the array access at the correct offset + * + * \param symbol The symbol of a variable for which we want to obtain its name + * \param name The name of the index variable + * \param use_instance Should the variable be accessed via instance or data array + * \return The backend code string representing the access to the given variable + * symbol + */ + std::string int_variable_name(const IndexVariableInfo& symbol, + const std::string& name, + bool use_instance) const override; + + + /** + * Determine the variable name for a global variable given its symbol + * \param symbol The symbol of a variable for which we want to obtain its name + * \param use_instance Should the variable be accessed via the (host-only) + * global variable or the instance-specific copy (also available on GPU). + * \return The C++ string representing the access to the global variable + */ + std::string global_variable_name(const SymbolType& symbol, + bool use_instance = true) const override; + + + /** + * Determine variable name in the structure of mechanism properties + * + * \param name Variable name that is being printed + * \param use_instance Should the variable be accessed via instance or data array + * \return The C++ string representing the access to the variable in the neuron + * thread structure + */ + std::string get_variable_name(const std::string& name, bool use_instance = true) const override; + + + /****************************************************************************************/ + /* Main printing routines for code generation */ + /****************************************************************************************/ + + + /** + * Print top file header printed in generated code + */ + void print_backend_info() override; + + + /** + * Print standard C/C++ includes + */ + void print_standard_includes() override; + + + /** + * Print includes from NEURON + */ + void print_neuron_includes(); + + + void print_sdlists_init(bool print_initializers) override; + + + /** + * Print the structure that wraps all global variables used in the NMODL + * + * \param print_initializers Whether to include default values in the struct + * definition (true: int foo{42}; false: int foo;) + */ + void print_mechanism_global_var_structure(bool print_initializers) override; + + + /** + * Print byte arrays that register scalar and vector variables for hoc interface + * + */ + void print_global_variables_for_hoc() override; + + + /** + * Print the mechanism registration function + * + */ + void print_mechanism_register() override; + + + /** + * Print common code for global functions like nrn_init, nrn_cur and nrn_state + * \param type The target backend code block type + */ + virtual void print_global_function_common_code(BlockType type, + const std::string& function_name = "") override; + + + /** + * Print nrn_constructor function definition + * + */ + void print_nrn_constructor() override; + + + /** + * Print nrn_destructor function definition + * + */ + void print_nrn_destructor() override; + + + /** + * Print nrn_alloc function definition + * + */ + void print_nrn_alloc() override; + + + /****************************************************************************************/ + /* Print nrn_state routine */ + /****************************************************************************************/ + + + /** + * Print nrn_state / state update function definition + */ + void print_nrn_state() override; + + + /****************************************************************************************/ + /* Print nrn_cur related routines */ + /****************************************************************************************/ + + + /** + * Print the \c nrn_current kernel + * + * \note nrn_cur_kernel will have two calls to nrn_current if no conductance keywords specified + * \param node the AST node representing the NMODL breakpoint block + */ + void print_nrn_current(const ast::BreakpointBlock& node) override; + + + /** + * Print the \c nrn\_cur kernel with NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is used in the \c breakpoint block, then + * CodegenCoreneuronCppVisitor::print_nrn_cur_kernel will use this printer + * + * \param node the AST node representing the NMODL breakpoint block + */ + void print_nrn_cur_conductance_kernel(const ast::BreakpointBlock& node) override; + + + /** + * Print the \c nrn\_cur kernel without NMODL \c conductance keyword provisions + * + * If the NMODL \c conductance keyword is \b not used in the \c breakpoint block, then + * CodegenCoreneuronCppVisitor::print_nrn_cur_kernel will use this printer + */ + void print_nrn_cur_non_conductance_kernel() override; + + + /** + * Print main body of nrn_cur function + * \param node the AST node representing the NMODL breakpoint block + */ + void print_nrn_cur_kernel(const ast::BreakpointBlock& node) override; + + + /** + * Print fast membrane current calculation code + */ + virtual void print_fast_imem_calculation() override; + + + /** + * Print nrn_cur / current update function definition + */ + void print_nrn_cur() override; + + + /****************************************************************************************/ + /* Main code printing entry points */ + /****************************************************************************************/ + + + /** + * Print all includes + * + */ + void print_headers_include() override; + + + /** + * Print all NEURON macros + * + */ + void print_macro_definitions(); + + + /** + * Print NEURON global variable macros + * + */ + void print_global_macros(); + + + /** + * Print mechanism variables' related macros + * + */ + void print_mechanism_variables_macros(); + + + /** + * Print start of namespaces + * + */ + void print_namespace_begin() override; + + + /** + * Print end of namespaces + * + */ + void print_namespace_end() override; + + + /** + * Print all classes + * \param print_initializers Whether to include default values. + */ + void print_data_structures(bool print_initializers) override; + + + /** + * Set v_unused (voltage) for NRN_PRCELLSTATE feature + */ + void print_v_unused() const override; + + + /** + * Set g_unused (conductance) for NRN_PRCELLSTATE feature + */ + void print_g_unused() const override; + + + /** + * Print all compute functions for every backend + * + */ + virtual void print_compute_functions() override; + + + /** + * Print entry point to code generation + * + */ + void print_codegen_routines() override; + + + /****************************************************************************************/ + /* Overloaded visitor routines */ + /****************************************************************************************/ + + + virtual void visit_solution_expression(const ast::SolutionExpression& node) override; + virtual void visit_watch_statement(const ast::WatchStatement& node) override; + + + /** + * Print prototype declarations of functions or procedures + * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) + * \param node The AST node representing the function or procedure block + * \param name A user defined name for the function + */ + template + void print_function_declaration(const T& node, const std::string& name); + + + public: + /** + * \brief Constructs the C++ code generator visitor + * + * This constructor instantiates an NMODL C++ code generator and allows writing generated code + * directly to a file in \c [output_dir]/[mod_filename].cpp. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C++ code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param output_dir The directory where target C++ file should be generated. + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ + CodegenNeuronCppVisitor(std::string mod_filename, + const std::string& output_dir, + std::string float_type, + const bool optimize_ionvar_copies) + : CodegenCppVisitor(mod_filename, output_dir, float_type, optimize_ionvar_copies) {} + + /** + * \copybrief nmodl::codegen::CodegenNeuronCppVisitor + * + * This constructor instantiates an NMODL C++ code generator and allows writing generated code + * into an output stream. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C++ code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param stream The output stream onto which to write the generated code + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ + CodegenNeuronCppVisitor(std::string mod_filename, + std::ostream& stream, + std::string float_type, + const bool optimize_ionvar_copies) + : CodegenCppVisitor(mod_filename, stream, float_type, optimize_ionvar_copies) {} + + + /****************************************************************************************/ + /* Public printing routines for code generation for use in unit tests */ + /****************************************************************************************/ + + + /** + * Print the structure that wraps all range and int variables required for the NMODL + * + * \param print_initializers Whether or not default values for variables + * be included in the struct declaration. + */ + void print_mechanism_range_var_structure(bool print_initializers) override; +}; + + +/** + * \details If there is an argument with name (say alpha) same as range variable (say alpha), + * we want to avoid it being printed as instance->alpha. And hence we disable variable + * name lookup during prototype declaration. Note that the name of procedure can be + * different in case of table statement. + */ +template +void CodegenNeuronCppVisitor::print_function_declaration(const T& node, const std::string& name) { + enable_variable_name_lookup = false; + auto type = default_float_data_type(); + + // internal and user provided arguments + auto internal_params = internal_method_parameters(); + const auto& params = node.get_parameters(); + for (const auto& param: params) { + internal_params.emplace_back("", type, "", param.get()->get_node_name()); + } + + // procedures have "int" return type by default + const char* return_type = "int"; + if (node.is_function_block()) { + return_type = default_float_data_type(); + } + + /// TODO: Edit for NEURON + printer->add_indent(); + printer->fmt_text("inline {} {}({})", return_type, method_name(name), "params"); + + enable_variable_name_lookup = true; +} + +/** \} */ // end of codegen_backends + +} // namespace codegen +} // namespace nmodl diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 87a682b2c0..cbd1e62c37 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -14,7 +14,8 @@ #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" #include "codegen/codegen_compatibility_visitor.hpp" -#include "codegen/codegen_cpp_visitor.hpp" +#include "codegen/codegen_coreneuron_cpp_visitor.hpp" +#include "codegen/codegen_neuron_cpp_visitor.hpp" #include "codegen/codegen_transform_visitor.hpp" #include "config/config.h" #include "parser/nmodl_driver.hpp" @@ -69,8 +70,14 @@ int main(int argc, const char* argv[]) { /// true if debug logger statements should be shown std::string verbose("info"); - /// true if serial c code to be generated - bool c_backend(true); + /// true if code is to be generated for NEURON + bool neuron_code(false); + + /// true if code is to be generated for CoreNEURON + bool coreneuron_code(true); + + /// true if serial C++ code to be generated + bool cpp_backend(true); /// true if c code with openacc to be generated bool oacc_backend(false); @@ -174,16 +181,18 @@ int main(int argc, const char* argv[]) { app.add_option("--units", units_dir, "Directory of units lib file") ->capture_default_str() ->ignore_case(); + app.add_flag("--neuron", neuron_code, "Generate C++ code for NEURON"); + app.add_flag("--coreneuron", coreneuron_code, "Generate C++ code for CoreNEURON (Default)"); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); - host_opt->add_flag("--c", c_backend, fmt::format("C/C++ backend ({})", c_backend)) + host_opt->add_flag("--c,--cpp", cpp_backend, fmt::format("C++ backend ({})", cpp_backend)) ->ignore_case(); auto acc_opt = app.add_subcommand("acc", "Accelerator code backends")->ignore_case(); acc_opt ->add_flag("--oacc", oacc_backend, - fmt::format("C/C++ backend with OpenACC ({})", oacc_backend)) + fmt::format("C++ backend with OpenACC ({})", oacc_backend)) ->ignore_case(); // clang-format off @@ -520,8 +529,8 @@ int main(int argc, const char* argv[]) { } { - if (oacc_backend) { - logger->info("Running OpenACC backend code generator"); + if (coreneuron_code && oacc_backend) { + logger->info("Running OpenACC backend code generator for CoreNEURON"); CodegenAccVisitor visitor(modfile, output_dir, data_type, @@ -529,14 +538,30 @@ int main(int argc, const char* argv[]) { visitor.visit_program(*ast); } - else if (c_backend) { - logger->info("Running C++ backend code generator"); - CodegenCppVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); + else if (coreneuron_code && !neuron_code && cpp_backend) { + logger->info("Running C++ backend code generator for CoreNEURON"); + CodegenCoreneuronCppVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); visitor.visit_program(*ast); } + + else if (neuron_code && cpp_backend) { + logger->info("Running C++ backend code generator for NEURON"); + CodegenNeuronCppVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen); + visitor.visit_program(*ast); + } + + else { + throw std::runtime_error( + "Non valid code generation configuration. Code generation with NMODL is " + "supported for NEURON with C++ backend or CoreNEURON with C++/OpenACC " + "backends"); + } } } diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 2508990917..147d46099e 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -63,7 +63,7 @@ std::string generate_random_string(int len, UseNumbersInString use_numbers); * Singleton class for random strings that are appended to the * Eigen matrices names that are used in the solutions of * nmodl::visitor::SympySolverVisitor and need to be the same to - * be printed by the nmodl::codegen::CodegenCppVisitor + * be printed by the nmodl::codegen::CodegenCoreneuronCppVisitor */ template class SingletonRandomString { diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 43da4cfa2c..7d09cb9b45 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -68,8 +68,13 @@ add_executable(testunitlexer units/lexer.cpp) add_executable(testunitparser units/parser.cpp) add_executable( testcodegen - codegen/main.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp - codegen/codegen_cpp_visitor.cpp codegen/transform.cpp codegen/codegen_compatibility_visitor.cpp) + codegen/main.cpp + codegen/codegen_helper.cpp + codegen/codegen_utils.cpp + codegen/codegen_coreneuron_cpp_visitor.cpp + codegen/codegen_neuron_cpp_visitor.cpp + codegen/transform.cpp + codegen/codegen_compatibility_visitor.cpp) target_link_libraries(testmodtoken PRIVATE lexer util) target_link_libraries(testlexer PRIVATE lexer util) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp similarity index 95% rename from test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp rename to test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp index be30ce24d6..ec98b5b076 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -10,7 +10,7 @@ #include "ast/program.hpp" #include "codegen/codegen_acc_visitor.hpp" -#include "codegen/codegen_cpp_visitor.hpp" +#include "codegen/codegen_coreneuron_cpp_visitor.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" @@ -32,9 +32,10 @@ using nmodl::parser::NmodlDriver; using nmodl::test_utils::reindent_text; /// Helper for creating C codegen visitor -std::shared_ptr create_c_visitor(const std::shared_ptr& ast, - const std::string& /* text */, - std::stringstream& ss) { +std::shared_ptr create_coreneuron_cpp_visitor( + const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss) { /// construct symbol table SymtabVisitor().visit_program(*ast); @@ -44,7 +45,7 @@ std::shared_ptr create_c_visitor(const std::shared_ptr("temp.mod", ss, "double", false); + auto cv = std::make_shared("temp.mod", ss, "double", false); cv->setup(*ast); return cv; } @@ -71,20 +72,21 @@ std::shared_ptr create_acc_visitor(const std::shared_ptrprint_instance_variable_setup(); return reindent_text(ss.str()); } /// print entire code -std::string get_cpp_code(const std::string& nmodl_text, const bool generate_gpu_code = false) { +std::string get_coreneuron_cpp_code(const std::string& nmodl_text, + const bool generate_gpu_code = false) { const auto& ast = NmodlDriver().parse_string(nmodl_text); std::stringstream ss; if (generate_gpu_code) { auto accvisitor = create_acc_visitor(ast, nmodl_text, ss); accvisitor->visit_program(*ast); } else { - auto cvisitor = create_c_visitor(ast, nmodl_text, ss); + auto cvisitor = create_coreneuron_cpp_visitor(ast, nmodl_text, ss); cvisitor->visit_program(*ast); } return reindent_text(ss.str()); @@ -311,7 +313,7 @@ std::string get_instance_structure(std::string nmodl_text) { PerfVisitor{}.visit_program(*ast); // setup codegen std::stringstream ss{}; - CodegenCppVisitor cv{"temp.mod", ss, "double", false}; + CodegenCoreneuronCppVisitor cv{"temp.mod", ss, "double", false}; cv.setup(*ast); cv.print_mechanism_range_var_structure(true); return ss.str(); @@ -442,7 +444,7 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable } )"; THEN("Array and global variables should be correctly generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(generated, ContainsSubstring("double t_inf[2][201]{};")); REQUIRE_THAT(generated, ContainsSubstring("double t_tau[201]{};")); @@ -479,7 +481,7 @@ SCENARIO("Check code generation for TABLE statements", "[codegen][array_variable } )"; THEN("It should throw") { - REQUIRE_THROWS(get_cpp_code(nmodl_text)); + REQUIRE_THROWS(get_coreneuron_cpp_code(nmodl_text)); } } } @@ -514,7 +516,7 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a } )"; THEN("They should be well registered") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); // BEFORE BREAKPOINT { REQUIRE_THAT(generated, @@ -643,7 +645,7 @@ SCENARIO("Check that BEFORE/AFTER block are well generated", "[codegen][before/a AFTER SOLVE {} )"; THEN("They should be all registered") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(generated, ContainsSubstring("hoc_reg_ba(mech_type, nrn_before_after_0_ba1, " "BAType::Before + BAType::Step);")); @@ -678,7 +680,7 @@ SCENARIO("Check CONSTANT variables are added to global variable structure", } )"; THEN("The global struct should contain these variables") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code = R"( struct CONST_Store { int reset{}; @@ -702,7 +704,7 @@ SCENARIO("Check code generation for FUNCTION_TABLE block", "[codegen][function_t FUNCTION_TABLE uuu(l, k) )"; THEN("Code should be generated correctly") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(generated, ContainsSubstring("double ttt_glia(")); REQUIRE_THAT(generated, ContainsSubstring("double table_ttt_glia(")); REQUIRE_THAT(generated, @@ -737,7 +739,7 @@ SCENARIO("Check that loops are well generated", "[codegen][loops]") { })"; THEN("Correct code is generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code = R"(double a, b; if (a == 1.0) { b = 5.0; @@ -774,7 +776,7 @@ SCENARIO("Check that top verbatim blocks are well generated", "[codegen][top ver )"; THEN("Correct code is generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code = R"(using namespace coreneuron; @@ -803,7 +805,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even )"; THEN("Correct code is generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string cpu_net_send_expected_code = R"(static inline void net_send_buffering(const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { int i = 0; @@ -821,7 +823,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } })"; REQUIRE_THAT(generated, ContainsSubstring(cpu_net_send_expected_code)); - auto const gpu_generated = get_cpp_code(nmodl_text, true); + auto const gpu_generated = get_coreneuron_cpp_code(nmodl_text, true); std::string gpu_net_send_expected_code = R"(static inline void net_send_buffering(const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, int weight_index, int point_index, double t, double flag) { int i = 0; @@ -941,7 +943,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } )"; THEN("It should generate a net_init") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code = R"(static void net_init(Point_process* pnt, int weight_index, double flag) { int tid = pnt->_tid; @@ -973,7 +975,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } )"; THEN("It should generate a net_send_buffering with weight_index as parameter variable") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code( "net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], " "weight_index, point_process, nt->_t+5.0, 1.0);"); @@ -988,7 +990,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } )"; THEN("It should generate a net_send_buffering with weight_index parameter as 0") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code( "net_send_buffering(nt, ml->_net_send_buffer, 0, inst->tqitem[0*pnodecount+id], 0, " "point_process, nt->_t+5.0, 1.0);"); @@ -1005,7 +1007,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } )"; THEN("New code is generated for for_netcons") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string net_receive_kernel_expected_code = R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { int tid = pnt->_tid; @@ -1043,7 +1045,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even } )"; THEN("It should throw") { - REQUIRE_THROWS(get_cpp_code(nmodl_text)); + REQUIRE_THROWS(get_coreneuron_cpp_code(nmodl_text)); } } } @@ -1063,7 +1065,7 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { } )"; THEN("Correct code is generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string newton_state_expected_code = R"(namespace { struct _newton_state_ { int operator()(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) const { @@ -1128,7 +1130,7 @@ SCENARIO("Some tests on euler solver", "[codegen][euler_solver]") { } )"; THEN("Correct code is generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string nrn_state_expected_code = R"(inst->Dm[id] = 2.0 * inst->m[id]; inf = inf * 3.0; inst->Dn[id] = (2.0 + inst->m[id] - inf) * inst->n[id]; @@ -1163,7 +1165,7 @@ SCENARIO("Check codegen for MUTEX and PROTECT", "[codegen][mutex_protect]") { )"; THEN("Code with OpenMP critical sections is generated") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); // critical section for the mutex block std::string expected_code_initial = R"(#pragma omp critical (TEST) { @@ -1198,7 +1200,7 @@ SCENARIO("Array STATE variable", "[codegen][array_state]") { )"; THEN("nrn_init is printed with proper initialization of the whole array") { - auto const generated = get_cpp_code(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code_init = R"(/** initialize channel */ void nrn_init_ca_test(NrnThread* nt, Memb_list* ml, int type) { diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp new file mode 100644 index 0000000000..b1d2a08a23 --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -0,0 +1,255 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include "ast/program.hpp" +#include "codegen/codegen_neuron_cpp_visitor.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/inline_visitor.hpp" +#include "visitors/neuron_solve_visitor.hpp" +#include "visitors/solve_block_visitor.hpp" +#include "visitors/symtab_visitor.hpp" + +using Catch::Matchers::ContainsSubstring; + +using namespace nmodl; +using namespace visitor; +using namespace codegen; + +using nmodl::parser::NmodlDriver; +using nmodl::test_utils::reindent_text; + +/// Helper for creating C codegen visitor +std::shared_ptr create_neuron_cpp_visitor( + const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss) { + /// construct symbol table + SymtabVisitor().visit_program(*ast); + + /// run all necessary pass + InlineVisitor().visit_program(*ast); + NeuronSolveVisitor().visit_program(*ast); + SolveBlockVisitor().visit_program(*ast); + + /// create C code generation visitor + auto cv = std::make_shared("_test", ss, "double", false); + cv->setup(*ast); + return cv; +} + + +/// print entire code +std::string get_neuron_cpp_code(const std::string& nmodl_text, + const bool generate_gpu_code = false) { + const auto& ast = NmodlDriver().parse_string(nmodl_text); + std::stringstream ss; + auto cvisitor = create_neuron_cpp_visitor(ast, nmodl_text, ss); + cvisitor->visit_program(*ast); + return reindent_text(ss.str()); +} + + +SCENARIO("Check NEURON codegen for simple MOD file", "[codegen][neuron_boilerplate]") { + GIVEN("A simple mod file with RANGE, ARRAY and ION variables") { + std::string const nmodl_text = R"( + TITLE unit test based on passive membrane channel + + UNITS { + (mV) = (millivolt) + (mA) = (milliamp) + (S) = (siemens) + } + + NEURON { + SUFFIX pas_test + USEION na READ ena WRITE ina + NONSPECIFIC_CURRENT i + RANGE g, e + RANGE ar + } + + PARAMETER { + g = .001 (S/cm2) <0,1e9> + e = -70 (mV) + } + + ASSIGNED { + v (mV) + i (mA/cm2) + ena (mV) + ina (mA/cm2) + ar[2] + } + + INITIAL { + ar[0] = 1 + } + + BREAKPOINT { + SOLVE states METHOD cnexp + i = g*(v - e) + ina = g*(v - ena) + } + + STATE { + s + } + + DERIVATIVE states { + s' = ar[0] + } + )"; + auto const reindent_and_trim_text = [](const auto& text) { + return reindent_text(stringutils::trim(text)); + }; + auto const generated = reindent_and_trim_text(get_neuron_cpp_code(nmodl_text)); + THEN("Correct includes are printed") { + std::string expected_includes = R"(#include +#include +#include + +#include "mech_api.h" +#include "neuron/cache/mechanism_range.hpp" +#include "nrniv_mf.h" +#include "section_fwd.hpp")"; + + REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_includes))); + } + THEN("Correct number of variables are printed") { + std::string expected_num_variables = + R"(static constexpr auto number_of_datum_variables = 3; +static constexpr auto number_of_floating_point_variables = 10;)"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_num_variables))); + } + THEN("Correct using-directives are printed ") { + std::string expected_using_directives = R"(namespace { +template +using _nrn_mechanism_std_vector = std::vector; +using _nrn_model_sorted_token = neuron::model_sorted_token; +using _nrn_mechanism_cache_range = neuron::cache::MechanismRange; +using _nrn_mechanism_cache_instance = neuron::cache::MechanismInstance; +template +using _nrn_mechanism_field = neuron::mechanism::field; +template +void _nrn_mechanism_register_data_fields(Args&&... args) { + neuron::mechanism::register_data_fields(std::forward(args)...); +} +} // namespace)"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_using_directives))); + } + THEN("Correct namespace is printed") { + std::string expected_namespace = R"(namespace neuron {)"; + + REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_namespace))); + } + THEN("Correct channel information are printed") { + std::string expected_channel_info = R"(/** channel information */ + static const char *mechanism[] = { + "6.2.0", + "pas_test", + "g_pas_test", + "e_pas_test", + 0, + "i_pas_test", + "ar_pas_test[2]", + 0, + "s_pas_test", + 0, + 0 + };)"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_channel_info))); + } + THEN("Correct global variables are printed") { + std::string expected_global_variables = + R"(static neuron::container::field_index _slist1[1], _dlist1[1];)"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_global_variables))); + } + THEN("Correct range variables' macros are printed") { + std::string expected_range_macros = R"(/* NEURON RANGE variables macro definitions */ + #define g(id) _ml->template fpfield<0>(id) + #define e(id) _ml->template fpfield<1>(id) + #define i(id) _ml->template fpfield<2>(id) + #define ar(id) _ml->template data_array<3, 2>(id) + #define s(id) _ml->template fpfield<4>(id) + #define ena(id) _ml->template fpfield<5>(id) + #define ina(id) _ml->template fpfield<6>(id) + #define Ds(id) _ml->template fpfield<7>(id) + #define v_unused(id) _ml->template fpfield<8>(id) + #define g_unused(id) _ml->template fpfield<9>(id))"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_range_macros))); + } + THEN("Correct HOC global variables are printed") { + std::string expected_hoc_global_variables = + R"(/** connect global (scalar) variables to hoc -- */ + static DoubScal hoc_scalar_double[] = { + {nullptr, nullptr} + }; + + + /** connect global (array) variables to hoc -- */ + static DoubVec hoc_vector_double[] = { + {nullptr, nullptr, 0} + };)"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_hoc_global_variables))); + } + THEN("Placeholder nrn_cur function is printed") { + std::string expected_placeholder_nrn_cur = R"(void nrn_cur() {})"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_cur))); + } + THEN("Placeholder nrn_state function is printed") { + std::string expected_placeholder_nrn_state = R"(void nrn_state() {})"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_state))); + } + THEN("Placeholder registration function is printed") { + std::string expected_placeholder_reg = R"(/** register channel with the simulator */ + void __test_reg() { + /* s */ + _slist1[0] = {4, 0} + /* Ds */ + _dlist1[0] = {7, 0} + + int mech_type = nrn_get_mechtype("pas_test"); + _nrn_mechanism_register_data_fields(_mechtype, + _nrn_mechanism_field{"g"} /* 0 */, + _nrn_mechanism_field{"e"} /* 1 */, + _nrn_mechanism_field{"i"} /* 2 */, + _nrn_mechanism_field{"ar", 2} /* 3 */, + _nrn_mechanism_field{"s"} /* 4 */, + _nrn_mechanism_field{"ena"} /* 5 */, + _nrn_mechanism_field{"ina"} /* 6 */, + _nrn_mechanism_field{"Ds"} /* 7 */, + _nrn_mechanism_field{"v_unused"} /* 8 */, + _nrn_mechanism_field{"g_unused"} /* 9 */ + ); + + })"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_placeholder_reg))); + } + } +} diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 18fb307b39..456f897564 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -9,7 +9,7 @@ #include #include "ast/program.hpp" -#include "codegen/codegen_cpp_visitor.hpp" +#include "codegen/codegen_coreneuron_cpp_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" @@ -2237,12 +2237,13 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym } /// Helper for creating C codegen visitor -std::shared_ptr create_c_visitor(const std::shared_ptr& ast, - const std::string& /* text */, - std::stringstream& ss, - bool inline_visitor = true, - bool pade = false, - bool cse = false) { +std::shared_ptr create_coreneuron_cpp_visitor( + const std::shared_ptr& ast, + const std::string& /* text */, + std::stringstream& ss, + bool inline_visitor = true, + bool pade = false, + bool cse = false) { /// construct symbol table SymtabVisitor().visit_program(*ast); @@ -2264,14 +2265,14 @@ std::shared_ptr create_c_visitor(const std::shared_ptr("temp.mod", ss, "double", false); + auto cv = std::make_shared("temp.mod", ss, "double", false); cv->setup(*ast); return cv; } @@ -2280,7 +2281,7 @@ std::shared_ptr create_c_visitor(const std::shared_ptrvisit_program(*ast); auto generated_string = ss.str(); return reindent_text(generated_string); From b9866cb4bfb8a343b37d752beb50be4d8538bd41 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Thu, 30 Nov 2023 14:50:44 +0100 Subject: [PATCH 550/871] Fix compilation of generated MOD file for NEURON (BlueBrain/nmodl#1101) * Fix slist and dlist instantiation * Change channel info variable name in NEURON and CoreNEURON * Fix mech_type in NEURON codegen NMODL Repo SHA: BlueBrain/nmodl@5bae4d0fe1a5b17dbc9f1fa63e76e7bf66c90820 --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 4 +++- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 8 ++++++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 13 ++++++------- .../unit/codegen/codegen_neuron_cpp_visitor.cpp | 10 +++++----- 5 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 177b0b50c1..cedfb1cf1c 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -1258,13 +1258,15 @@ std::string CodegenCoreneuronCppVisitor::process_verbatim_text(std::string const std::string CodegenCoreneuronCppVisitor::register_mechanism_arguments() const { + auto nrn_channel_info_var_name = get_channel_info_var_name(); auto nrn_cur = nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "nullptr"; auto nrn_state = nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr"; auto nrn_alloc = method_name(naming::NRN_ALLOC_METHOD); auto nrn_init = method_name(naming::NRN_INIT_METHOD); auto const nrn_private_constructor = method_name(naming::NRN_PRIVATE_CONSTRUCTOR_METHOD); auto const nrn_private_destructor = method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD); - return fmt::format("mechanism, {}, {}, nullptr, {}, {}, {}, {}, first_pointer_var_index()", + return fmt::format("{}, {}, {}, nullptr, {}, {}, {}, {}, first_pointer_var_index()", + nrn_channel_info_var_name, nrn_alloc, nrn_cur, nrn_state, diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 21909a8b90..92ead3ca06 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -219,7 +219,7 @@ void CodegenCppVisitor::print_mechanism_info() { printer->add_newline(2); printer->add_line("/** channel information */"); - printer->add_line("static const char *mechanism[] = {"); + printer->fmt_line("static const char *{}[] = {{", get_channel_info_var_name()); printer->increase_indent(); printer->add_line(add_escape_quote(nmodl_version()), ","); printer->add_line(add_escape_quote(info.mod_suffix), ","); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 66016af917..6f9fe29feb 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -375,6 +375,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return info.electrode_current ? "-=" : "+="; } + /** + * Name of channel info variable + * + */ + std::string get_channel_info_var_name() const noexcept { + return std::string("mechanism_info"); + } + /****************************************************************************************/ /* Common helper routines accross codegen functions */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 54abb3b87e..f4b3747ce0 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -298,23 +298,23 @@ void CodegenNeuronCppVisitor::print_sdlists_init(bool print_initializers) { /// TODO: Also needs a test printer->fmt_push_block("for (int _i = 0; _i < {}; ++_i)", prime_var->get_length()); printer->fmt_line("/* {}[{}] */", prime_var->get_name(), prime_var->get_length()); - printer->fmt_line("_slist1[{}+_i] = {{{}, _i}}", + printer->fmt_line("_slist1[{}+_i] = {{{}, _i}};", i, position_of_float_var(prime_var->get_name())); const auto prime_var_deriv_name = "D" + prime_var->get_name(); printer->fmt_line("/* {}[{}] */", prime_var_deriv_name, prime_var->get_length()); - printer->fmt_line("_dlist1[{}+_i] = {{{}, _i}}", + printer->fmt_line("_dlist1[{}+_i] = {{{}, _i}};", i, position_of_float_var(prime_var_deriv_name)); printer->pop_block(); } else { printer->fmt_line("/* {} */", prime_var->get_name()); - printer->fmt_line("_slist1[{}] = {{{}, 0}}", + printer->fmt_line("_slist1[{}] = {{{}, 0}};", i, position_of_float_var(prime_var->get_name())); const auto prime_var_deriv_name = "D" + prime_var->get_name(); printer->fmt_line("/* {} */", prime_var_deriv_name); - printer->fmt_line("_dlist1[{}] = {{{}, 0}}", + printer->fmt_line("_dlist1[{}] = {{{}, 0}};", i, position_of_float_var(prime_var_deriv_name)); } @@ -391,12 +391,11 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_push_block("void _{}_reg()", info.mod_file); print_sdlists_init(true); // type related information - auto suffix = add_escape_quote(info.mod_suffix); printer->add_newline(); - printer->fmt_line("int mech_type = nrn_get_mechtype({});", suffix); + printer->fmt_line("int mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); // More things to add here - printer->add_line("_nrn_mechanism_register_data_fields(_mechtype,"); + printer->add_line("_nrn_mechanism_register_data_fields(mech_type,"); printer->increase_indent(); const auto codegen_float_variables_size = codegen_float_variables.size(); for (int i = 0; i < codegen_float_variables_size; ++i) { diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index b1d2a08a23..50dbf9ca9c 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -156,7 +156,7 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { } THEN("Correct channel information are printed") { std::string expected_channel_info = R"(/** channel information */ - static const char *mechanism[] = { + static const char *mechanism_info[] = { "6.2.0", "pas_test", "g_pas_test", @@ -228,12 +228,12 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { std::string expected_placeholder_reg = R"(/** register channel with the simulator */ void __test_reg() { /* s */ - _slist1[0] = {4, 0} + _slist1[0] = {4, 0}; /* Ds */ - _dlist1[0] = {7, 0} + _dlist1[0] = {7, 0}; - int mech_type = nrn_get_mechtype("pas_test"); - _nrn_mechanism_register_data_fields(_mechtype, + int mech_type = nrn_get_mechtype(mechanism_info[1]); + _nrn_mechanism_register_data_fields(mech_type, _nrn_mechanism_field{"g"} /* 0 */, _nrn_mechanism_field{"e"} /* 1 */, _nrn_mechanism_field{"i"} /* 2 */, From 43c27aa52d3515a2a2ec24035dd275eae25b6791 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 1 Dec 2023 16:46:23 +0100 Subject: [PATCH 551/871] Informational codecov. (BlueBrain/nmodl#1104) NMODL Repo SHA: BlueBrain/nmodl@de7d13f099300d88cd216a8ef4b7e872d49e756b --- codecov.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codecov.yaml b/codecov.yaml index 43ee99ef88..d65b40af13 100644 --- a/codecov.yaml +++ b/codecov.yaml @@ -1,2 +1,11 @@ ignore: - src/language/templates/ + +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true From 7b8b62afe4a4c62ce5ce20b20bbb3b0be1f8fb64 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 4 Dec 2023 14:24:55 +0100 Subject: [PATCH 552/871] NRN needs this magic number to be 7.7.0. (BlueBrain/nmodl#1105) NMODL Repo SHA: BlueBrain/nmodl@6abad06859512aec515d3c010d77939c6451c826 --- src/nmodl/codegen/codegen_naming.hpp | 2 +- .../transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 3af8a49e88..2a0aa190f6 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -18,7 +18,7 @@ namespace naming { /// nmodl language version /// @todo : should be moved from codegen to global scope -static constexpr char NMODL_VERSION[] = "6.2.0"; +static constexpr char NMODL_VERSION[] = "7.7.0"; /// derivimplicit method in nmodl static constexpr char DERIVIMPLICIT_METHOD[] = "derivimplicit"; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 50dbf9ca9c..d56141010b 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -157,7 +157,7 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { THEN("Correct channel information are printed") { std::string expected_channel_info = R"(/** channel information */ static const char *mechanism_info[] = { - "6.2.0", + "7.7.0", "pas_test", "g_pas_test", "e_pas_test", From 1f5a82999ea47f0077bb3faffe4ee42399f095ed Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 4 Dec 2023 21:55:15 +0100 Subject: [PATCH 553/871] Fixing function declarations in NEURON codegen (BlueBrain/nmodl#1109) NMODL Repo SHA: BlueBrain/nmodl@03b748506822dce62dedd4694fcaf3b62ca42a4c --- .../codegen_coreneuron_cpp_visitor.cpp | 18 ------------------ .../codegen_coreneuron_cpp_visitor.hpp | 12 ------------ src/nmodl/codegen/codegen_cpp_visitor.cpp | 19 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 12 ++++++++++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 5 ++++- 5 files changed, 35 insertions(+), 31 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index cedfb1cf1c..d11e2f53fb 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -290,24 +290,6 @@ bool CodegenCoreneuronCppVisitor::is_constant_variable(const std::string& name) /* Backend specific routines */ /****************************************************************************************/ -std::string CodegenCoreneuronCppVisitor::get_parameter_str(const ParamVector& params) { - std::string str; - bool is_first = true; - for (const auto& param: params) { - if (is_first) { - is_first = false; - } else { - str += ", "; - } - str += fmt::format("{}{} {}{}", - std::get<0>(param), - std::get<1>(param), - std::get<2>(param), - std::get<3>(param)); - } - return str; -} - void CodegenCoreneuronCppVisitor::print_deriv_advance_flag_transfer_to_device() const { // backend specific, do nothing diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index cc59a2a77e..1fc5d8d4ad 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -200,18 +200,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Generate the string representing the procedure parameter declaration - * - * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. - * - * \param params The parameters that should be concatenated into the function parameter - * declaration - * \return The string representing the declaration of function parameters - */ - static std::string get_parameter_str(const ParamVector& params); - - /** * Print the code to copy derivative advance flag to device */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 92ead3ca06..906ea43576 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -26,6 +26,25 @@ using symtab::syminfo::NmodlType; /****************************************************************************************/ +std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) { + std::string str; + bool is_first = true; + for (const auto& param: params) { + if (is_first) { + is_first = false; + } else { + str += ", "; + } + str += fmt::format("{}{} {}{}", + std::get<0>(param), + std::get<1>(param), + std::get<2>(param), + std::get<3>(param)); + } + return str; +} + + template bool CodegenCppVisitor::has_parameter_of_name(const T& node, const std::string& name) { auto parameters = node->get_parameters(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 6f9fe29feb..710f14c879 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -389,6 +389,18 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /****************************************************************************************/ + /** + * Generate the string representing the procedure parameter declaration + * + * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. + * + * \param params The parameters that should be concatenated into the function parameter + * declaration + * \return The string representing the declaration of function parameters + */ + static std::string get_parameter_str(const ParamVector& params); + + /** * Check if function or procedure node has parameter with given name * diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index e8bf7e36af..c5036a8828 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -632,7 +632,10 @@ void CodegenNeuronCppVisitor::print_function_declaration(const T& node, const st /// TODO: Edit for NEURON printer->add_indent(); - printer->fmt_text("inline {} {}({})", return_type, method_name(name), "params"); + printer->fmt_text("inline {} {}({})", + return_type, + method_name(name), + get_parameter_str(internal_params)); enable_variable_name_lookup = true; } From 078b941015e569e543fb3260bdd1efa16a466179 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 5 Dec 2023 08:55:07 +0100 Subject: [PATCH 554/871] Fix returning reference to local object. (BlueBrain/nmodl#1102) NMODL Repo SHA: BlueBrain/nmodl@474c3fe261b0ac89c057b73caa7574fa5f2e3640 --- src/nmodl/language/nodes.py | 2 +- src/nmodl/language/templates/ast/ast.cpp | 2 +- src/nmodl/language/templates/ast/ast.hpp | 2 +- src/nmodl/language/templates/pybind/pyast.hpp | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index fc45edf181..9184976364 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -244,7 +244,7 @@ def _is_member_type_wrapped_as_shared_pointer(self): def member_rvalue_typename(self): """returns rvalue reference type when used as returned or parameter type""" typename = self.member_typename - if not self.is_integral_type_node: + if not self.is_integral_type_node and not self._is_member_type_wrapped_as_shared_pointer: return "const " + typename + "&" return typename diff --git a/src/nmodl/language/templates/ast/ast.cpp b/src/nmodl/language/templates/ast/ast.cpp index 360d37dc97..143e60fcb2 100644 --- a/src/nmodl/language/templates/ast/ast.cpp +++ b/src/nmodl/language/templates/ast/ast.cpp @@ -29,7 +29,7 @@ std::string Ast::get_node_name() const { throw std::logic_error("get_node_name() not implemented"); } -const std::shared_ptr& Ast::get_statement_block() const { +std::shared_ptr Ast::get_statement_block() const { throw std::runtime_error("get_statement_block not implemented"); } diff --git a/src/nmodl/language/templates/ast/ast.hpp b/src/nmodl/language/templates/ast/ast.hpp index 5af0e551aa..8e1fc4d814 100644 --- a/src/nmodl/language/templates/ast/ast.hpp +++ b/src/nmodl/language/templates/ast/ast.hpp @@ -272,7 +272,7 @@ struct Ast: public std::enable_shared_from_this { * * \sa ast::StatementBlock */ - virtual const std::shared_ptr& get_statement_block() const; + virtual std::shared_ptr get_statement_block() const; /** * \brief Set symbol table for the AST node diff --git a/src/nmodl/language/templates/pybind/pyast.hpp b/src/nmodl/language/templates/pybind/pyast.hpp index f2fa13eb14..e70ffe3aca 100644 --- a/src/nmodl/language/templates/pybind/pyast.hpp +++ b/src/nmodl/language/templates/pybind/pyast.hpp @@ -114,8 +114,8 @@ struct PyAst: public Ast { PYBIND11_OVERRIDE(symtab::SymbolTable*, Ast, get_symbol_table, ); } - const std::shared_ptr& get_statement_block() const override { - PYBIND11_OVERRIDE(const std::shared_ptr&, Ast, get_statement_block, ); + std::shared_ptr get_statement_block() const override { + PYBIND11_OVERRIDE(std::shared_ptr, Ast, get_statement_block, ); } void set_symbol_table(symtab::SymbolTable* newsymtab) override { From 1e6163a045a2c974a87efcff703cbb7cdc25fcb9 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Tue, 5 Dec 2023 11:08:32 +0100 Subject: [PATCH 555/871] Fix NEURON mechanism registration function for non POINT_PROCESSes (BlueBrain/nmodl#1111) * Added empty definitions for `nrn_{alloc,init,state,cur}` functions * Makes generated code compilable and loadable for simple MOD files NMODL Repo SHA: BlueBrain/nmodl@7265ec96069360580b8773f105319cb1c615ef3e --- src/nmodl/codegen/codegen_naming.hpp | 6 ++ .../codegen/codegen_neuron_cpp_visitor.cpp | 86 +++++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.hpp | 14 +++ .../codegen/codegen_neuron_cpp_visitor.cpp | 19 ++-- 4 files changed, 111 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 2a0aa190f6..e8240b7df2 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -152,6 +152,9 @@ static constexpr char NRN_STATE_METHOD[] = "nrn_state"; /// nrn_cur method in generated code static constexpr char NRN_CUR_METHOD[] = "nrn_cur"; +/// nrn_jacob method in generated code +static constexpr char NRN_JACOB_METHOD[] = "nrn_jacob"; + /// nrn_watch_check method in generated c++ file static constexpr char NRN_WATCH_CHECK_METHOD[] = "nrn_watch_check"; @@ -164,6 +167,9 @@ static constexpr char THREAD_ARGS_PROTO[] = "_threadargsproto_"; /// prefix for ion variable static constexpr char ION_VARNAME_PREFIX[] = "ion_"; +/// hoc_nrnpointerindex name +static constexpr char NRN_POINTERINDEX[] = "hoc_nrnpointerindex"; + /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index f4b3747ce0..5933c5a1d2 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -324,12 +324,19 @@ void CodegenNeuronCppVisitor::print_sdlists_init(bool print_initializers) { void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { /// TODO: Print only global variables printed in NEURON - printer->add_line(); + printer->add_newline(2); printer->add_line("/* NEURON global variables */"); if (info.primes_size != 0) { printer->fmt_line("static neuron::container::field_index _slist1[{0}], _dlist1[{0}];", info.primes_size); } + printer->add_line("static int mech_type;"); + + printer->fmt_line("static int {} = {};", + naming::NRN_POINTERINDEX, + info.pointer_variables.size() > 0 + ? static_cast(info.pointer_variables.size()) + : -1); } @@ -388,11 +395,29 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { /// TODO: Write this according to NEURON printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); - printer->fmt_push_block("void _{}_reg()", info.mod_file); + printer->fmt_push_block("extern \"C\" void _{}_reg()", info.mod_file); print_sdlists_init(true); + printer->add_newline(); + + const auto compute_functions_parameters = + breakpoint_exist() + ? fmt::format("{}, {}, {}", + nrn_cur_required() ? method_name(naming::NRN_CUR_METHOD) : "nullptr", + method_name(naming::NRN_JACOB_METHOD), + nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr") + : "nullptr, nullptr, nullptr"; + const auto register_mech_args = fmt::format("{}, {}, {}, {}, {}, {}", + get_channel_info_var_name(), + method_name(naming::NRN_ALLOC_METHOD), + compute_functions_parameters, + method_name(naming::NRN_INIT_METHOD), + naming::NRN_POINTERINDEX, + 1 + info.thread_data_index); + printer->fmt_line("register_mech({});", register_mech_args); + // type related information printer->add_newline(); - printer->fmt_line("int mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); + printer->fmt_line("mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); // More things to add here printer->add_line("_nrn_mechanism_register_data_fields(mech_type,"); @@ -425,6 +450,7 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { + printer->add_newline(2); printer->add_line("/* NEURON RANGE variables macro definitions */"); for (auto i = 0; i < codegen_float_variables.size(); ++i) { const auto float_var = codegen_float_variables[i]; @@ -454,6 +480,34 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, } +void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { + codegen = true; + printer->add_newline(2); + printer->add_line("/** initialize channel */"); + + printer->fmt_line( + "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* " + "_ml_arg, int _type) {{}}", + method_name(naming::NRN_INIT_METHOD)); + + codegen = false; +} + + +void CodegenNeuronCppVisitor::print_nrn_jacob() { + codegen = true; + printer->add_newline(2); + printer->add_line("/** nrn_jacob function */"); + + printer->fmt_line( + "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* " + "_nt, Memb_list* _ml_arg, int _type) {{}}", + method_name(naming::NRN_JACOB_METHOD)); + + codegen = false; +} + + /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_constructor() { return; @@ -468,7 +522,11 @@ void CodegenNeuronCppVisitor::print_nrn_destructor() { /// TODO: Print the equivalent of `nrn_alloc_` void CodegenNeuronCppVisitor::print_nrn_alloc() { - return; + printer->add_newline(2); + auto method = method_name(naming::NRN_ALLOC_METHOD); + printer->fmt_push_block("static void {}(Prop* _prop)", method); + printer->add_line("// do nothing"); + printer->pop_block(); } @@ -483,8 +541,13 @@ void CodegenNeuronCppVisitor::print_nrn_state() { return; } codegen = true; + printer->add_newline(2); + + printer->fmt_line( + "void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* " + "_ml_arg, int _type) {{}}", + method_name(naming::NRN_STATE_METHOD)); - printer->add_line("void nrn_state() {}"); /// TODO: Fill in codegen = false; @@ -533,8 +596,13 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { } codegen = true; + printer->add_newline(2); + + printer->fmt_line( + "void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, " + "int _type) {{}}", + method_name(naming::NRN_CUR_METHOD)); - printer->add_line("void nrn_cur() {}"); /// TODO: Fill in codegen = false; @@ -542,7 +610,7 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { /****************************************************************************************/ -/* Main code printing entry points */ +/* Main code printing entry points */ /****************************************************************************************/ void CodegenNeuronCppVisitor::print_headers_include() { @@ -590,6 +658,7 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { using _nrn_model_sorted_token = neuron::model_sorted_token; using _nrn_mechanism_cache_range = neuron::cache::MechanismRange; using _nrn_mechanism_cache_instance = neuron::cache::MechanismInstance; + using _nrn_non_owning_id_without_container = neuron::container::non_owning_identifier_without_container; template using _nrn_mechanism_field = neuron::mechanism::field; template @@ -641,8 +710,10 @@ void CodegenNeuronCppVisitor::print_g_unused() const { /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_compute_functions() { + print_nrn_init(); print_nrn_cur(); print_nrn_state(); + print_nrn_jacob(); } @@ -657,6 +728,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_prcellstate_macros(); print_mechanism_info(); print_data_structures(true); + print_nrn_alloc(); print_global_variables_for_hoc(); print_compute_functions(); // only nrn_cur and nrn_state print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c5036a8828..6d7bf0f1f3 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -361,6 +361,13 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { const std::string& function_name = "") override; + /** + * Print the \c nrn\_init function definition + * \param skip_init_check \c true to generate code executing the initialization conditionally + */ + void print_nrn_init(bool skip_init_check = true); + + /** * Print nrn_constructor function definition * @@ -382,6 +389,13 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_nrn_alloc() override; + /** + * Print nrn_jacob function definition + * + */ + void print_nrn_jacob(); + + /****************************************************************************************/ /* Print nrn_state routine */ /****************************************************************************************/ diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index d56141010b..20fc2c4cb8 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -53,7 +53,7 @@ std::string get_neuron_cpp_code(const std::string& nmodl_text, std::stringstream ss; auto cvisitor = create_neuron_cpp_visitor(ast, nmodl_text, ss); cvisitor->visit_program(*ast); - return reindent_text(ss.str()); + return ss.str(); } @@ -138,6 +138,7 @@ using _nrn_mechanism_std_vector = std::vector; using _nrn_model_sorted_token = neuron::model_sorted_token; using _nrn_mechanism_cache_range = neuron::cache::MechanismRange; using _nrn_mechanism_cache_instance = neuron::cache::MechanismInstance; +using _nrn_non_owning_id_without_container = neuron::container::non_owning_identifier_without_container; template using _nrn_mechanism_field = neuron::mechanism::field; template @@ -213,26 +214,30 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { ContainsSubstring(reindent_and_trim_text(expected_hoc_global_variables))); } THEN("Placeholder nrn_cur function is printed") { - std::string expected_placeholder_nrn_cur = R"(void nrn_cur() {})"; + std::string expected_placeholder_nrn_cur = + R"(void nrn_cur_pas_test(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int _type) {})"; REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_cur))); } THEN("Placeholder nrn_state function is printed") { - std::string expected_placeholder_nrn_state = R"(void nrn_state() {})"; + std::string expected_placeholder_nrn_state = + R"(void nrn_state_pas_test(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int _type) {})"; REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_state))); } THEN("Placeholder registration function is printed") { - std::string expected_placeholder_reg = R"(/** register channel with the simulator */ - void __test_reg() { + std::string expected_placeholder_reg = R"CODE(/** register channel with the simulator */ + extern "C" void __test_reg() { /* s */ _slist1[0] = {4, 0}; /* Ds */ _dlist1[0] = {7, 0}; - int mech_type = nrn_get_mechtype(mechanism_info[1]); + register_mech(mechanism_info, nrn_alloc_pas_test, nrn_cur_pas_test, nrn_jacob_pas_test, nrn_state_pas_test, nrn_init_pas_test, hoc_nrnpointerindex, 1); + + mech_type = nrn_get_mechtype(mechanism_info[1]); _nrn_mechanism_register_data_fields(mech_type, _nrn_mechanism_field{"g"} /* 0 */, _nrn_mechanism_field{"e"} /* 1 */, @@ -246,7 +251,7 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { _nrn_mechanism_field{"g_unused"} /* 9 */ ); - })"; + })CODE"; REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_placeholder_reg))); From 1245994ac21de3a04bde8892a50e55fca10fe792 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Thu, 7 Dec 2023 14:11:19 +0100 Subject: [PATCH 556/871] Refactor slist and dlist according to NEURON (BlueBrain/nmodl#1110) * Added test for `_initlists()` NMODL Repo SHA: BlueBrain/nmodl@85b0dfc28e0cb310c1b12f4d5c1ab9ba67d31f17 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 13 ++++++++++--- .../unit/codegen/codegen_neuron_cpp_visitor.cpp | 14 ++++++++++---- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 5933c5a1d2..d73b89d6e9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -285,7 +285,11 @@ void CodegenNeuronCppVisitor::print_neuron_includes() { } -void CodegenNeuronCppVisitor::print_sdlists_init(bool print_initializers) { +void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_initializers) { + /// _initlists() should only be called once by the mechanism registration function + /// (__reg()) + printer->add_newline(2); + printer->push_block("static void _initlists()"); for (auto i = 0; i < info.prime_variables_by_order.size(); ++i) { const auto& prime_var = info.prime_variables_by_order[i]; /// TODO: Something similar needs to happen for slist/dlist2 but I don't know their usage at @@ -319,6 +323,7 @@ void CodegenNeuronCppVisitor::print_sdlists_init(bool print_initializers) { position_of_float_var(prime_var_deriv_name)); } } + printer->pop_block(); } @@ -396,7 +401,7 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); printer->fmt_push_block("extern \"C\" void _{}_reg()", info.mod_file); - print_sdlists_init(true); + printer->add_line("_initlists();"); printer->add_newline(); const auto compute_functions_parameters = @@ -449,7 +454,8 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { } -void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { +void CodegenNeuronCppVisitor::print_mechanism_range_var_structure( + [[maybe_unused]] bool print_initializers) { printer->add_newline(2); printer->add_line("/* NEURON RANGE variables macro definitions */"); for (auto i = 0; i < codegen_float_variables.size(); ++i) { @@ -731,6 +737,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_nrn_alloc(); print_global_variables_for_hoc(); print_compute_functions(); // only nrn_cur and nrn_state + print_sdlists_init(true); print_mechanism_register(); print_namespace_end(); codegen = false; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 20fc2c4cb8..9f759a5cef 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -227,13 +227,19 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_state))); } + THEN("Initialization function for slist/dlist is printed correctly") { + std::string expected_initlists_s_var = "_slist1[0] = {4, 0};"; + std::string expected_initlists_Ds_var = "_dlist1[0] = {7, 0};"; + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_initlists_s_var))); + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_initlists_Ds_var))); + } THEN("Placeholder registration function is printed") { std::string expected_placeholder_reg = R"CODE(/** register channel with the simulator */ extern "C" void __test_reg() { - /* s */ - _slist1[0] = {4, 0}; - /* Ds */ - _dlist1[0] = {7, 0}; + _initlists(); register_mech(mechanism_info, nrn_alloc_pas_test, nrn_cur_pas_test, nrn_jacob_pas_test, nrn_state_pas_test, nrn_init_pas_test, hoc_nrnpointerindex, 1); From b77ea962984aedc1f4a854bbb049d224489d91d6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 8 Dec 2023 09:47:14 +0100 Subject: [PATCH 557/871] Move `compute_method_name` to `CodegenCppVisitor`. (BlueBrain/nmodl#1114) NMODL Repo SHA: BlueBrain/nmodl@b6801af3982b806f4158ab6b09e9eb866a1d326e --- .../codegen_coreneuron_cpp_visitor.cpp | 23 ------------------- .../codegen_coreneuron_cpp_visitor.hpp | 7 ------ src/nmodl/codegen/codegen_cpp_visitor.cpp | 22 ++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 1 + 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index d11e2f53fb..126921c35b 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -497,29 +497,6 @@ void CodegenCoreneuronCppVisitor::print_abort_routine() const { } -std::string CodegenCoreneuronCppVisitor::compute_method_name(BlockType type) const { - if (type == BlockType::Initial) { - return method_name(naming::NRN_INIT_METHOD); - } - if (type == BlockType::Constructor) { - return method_name(naming::NRN_CONSTRUCTOR_METHOD); - } - if (type == BlockType::Destructor) { - return method_name(naming::NRN_DESTRUCTOR_METHOD); - } - if (type == BlockType::State) { - return method_name(naming::NRN_STATE_METHOD); - } - if (type == BlockType::Equation) { - return method_name(naming::NRN_CUR_METHOD); - } - if (type == BlockType::Watch) { - return method_name(naming::NRN_WATCH_CHECK_METHOD); - } - throw std::logic_error("compute_method_name not implemented"); -} - - void CodegenCoreneuronCppVisitor::print_global_var_struct_decl() { printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); } diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 1fc5d8d4ad..f411e18366 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -370,13 +370,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual void print_abort_routine() const; - /** - * Return the name of main compute kernels - * \param type A block type - */ - virtual std::string compute_method_name(BlockType type) const; - - /** * Instantiate global var instance * diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 906ea43576..779d045a7b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -911,6 +911,28 @@ void CodegenCppVisitor::setup(const Program& node) { rename_function_arguments(); } +std::string CodegenCppVisitor::compute_method_name(BlockType type) const { + if (type == BlockType::Initial) { + return method_name(naming::NRN_INIT_METHOD); + } + if (type == BlockType::Constructor) { + return method_name(naming::NRN_CONSTRUCTOR_METHOD); + } + if (type == BlockType::Destructor) { + return method_name(naming::NRN_DESTRUCTOR_METHOD); + } + if (type == BlockType::State) { + return method_name(naming::NRN_STATE_METHOD); + } + if (type == BlockType::Equation) { + return method_name(naming::NRN_CUR_METHOD); + } + if (type == BlockType::Watch) { + return method_name(naming::NRN_WATCH_CHECK_METHOD); + } + throw std::logic_error("compute_method_name not implemented"); +} + void CodegenCppVisitor::visit_program(const Program& node) { setup(node); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 710f14c879..b611aec9e7 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1123,6 +1123,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_mutex_lock(const ast::MutexLock& node) override; void visit_mutex_unlock(const ast::MutexUnlock& node) override; + std::string compute_method_name(BlockType type) const; public: /** Setup the target backend code generator From 1f82401e4b15dbdbe2145c3f1ef37bbc17df2dbc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 8 Dec 2023 12:58:25 +0100 Subject: [PATCH 558/871] Avoid toggling codegen. (BlueBrain/nmodl#1116) * Missing newline at end of file. * Only top-level `print_` toggles `codegen`. NMODL Repo SHA: BlueBrain/nmodl@1b60785cfb35634474d12e5dd2b96aa1db16399a --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 2 +- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 13 +------------ 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 779d045a7b..2a2c95bff8 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -940,4 +940,4 @@ void CodegenCppVisitor::visit_program(const Program& node) { } } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index b611aec9e7..3bff88bacb 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1173,4 +1173,4 @@ void CodegenCppVisitor::print_vector_elements(const std::vector& elements, /** \} */ // end of codegen_backends } // namespace codegen -} // namespace nmodl \ No newline at end of file +} // namespace nmodl diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index d73b89d6e9..c89b62083d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -100,9 +100,6 @@ void CodegenNeuronCppVisitor::print_function_prototypes() { if (info.functions.empty() && info.procedures.empty()) { return; } - codegen = true; - /// TODO: Fill in - codegen = false; } @@ -115,9 +112,7 @@ void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function_procedure_helper(const ast::Block& node) { - codegen = true; - /// TODO: Fill in - codegen = false; + return; } @@ -501,7 +496,6 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { void CodegenNeuronCppVisitor::print_nrn_jacob() { - codegen = true; printer->add_newline(2); printer->add_line("/** nrn_jacob function */"); @@ -509,8 +503,6 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* " "_nt, Memb_list* _ml_arg, int _type) {{}}", method_name(naming::NRN_JACOB_METHOD)); - - codegen = false; } @@ -601,7 +593,6 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { return; } - codegen = true; printer->add_newline(2); printer->fmt_line( @@ -610,8 +601,6 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { method_name(naming::NRN_CUR_METHOD)); /// TODO: Fill in - - codegen = false; } From d9cbcd1dd409c1d693edc9d6c31865a61235a2be Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 8 Dec 2023 15:23:22 +0100 Subject: [PATCH 559/871] Move `print_function_call` to `CodegenCppVisitor`. (BlueBrain/nmodl#1115) Additionally, adds stubs for `print_net_{send,move,event}_call` to `CodegenNeuronCppVisitor`. NMODL Repo SHA: BlueBrain/nmodl@7dbdd2331ae8fb6c63482a1eead44018dff21612 --- .../codegen_coreneuron_cpp_visitor.cpp | 37 ------------------- .../codegen_coreneuron_cpp_visitor.hpp | 13 ++----- src/nmodl/codegen/codegen_cpp_visitor.cpp | 36 ++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 21 ++++++++++- .../codegen/codegen_neuron_cpp_visitor.cpp | 18 ++++++--- .../codegen/codegen_neuron_cpp_visitor.hpp | 20 ++++++++-- 6 files changed, 88 insertions(+), 57 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 126921c35b..bf22efb29c 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -507,43 +507,6 @@ void CodegenCoreneuronCppVisitor::print_global_var_struct_decl() { /****************************************************************************************/ -void CodegenCoreneuronCppVisitor::print_function_call(const FunctionCall& node) { - const auto& name = node.get_node_name(); - auto function_name = name; - if (defined_method(name)) { - function_name = method_name(name); - } - - if (is_net_send(name)) { - print_net_send_call(node); - return; - } - - if (is_net_move(name)) { - print_net_move_call(node); - return; - } - - if (is_net_event(name)) { - print_net_event_call(node); - return; - } - - const auto& arguments = node.get_arguments(); - printer->add_text(function_name, '('); - - if (defined_method(name)) { - printer->add_text(internal_method_arguments()); - if (!arguments.empty()) { - printer->add_text(", "); - } - } - - print_vector_elements(arguments, ", "); - printer->add_text(')'); -} - - void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { if (info.top_verbatim_blocks.empty()) { return; diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index f411e18366..5693cb245f 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -421,13 +421,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Print call to internal or external function - * \param node The AST node representing a function call - */ - void print_function_call(const ast::FunctionCall& node) override; - - /** * Print top level (global scope) verbatim blocks */ @@ -951,21 +944,21 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { * Print call to \c net\_send * \param node The AST node representing the function call */ - void print_net_send_call(const ast::FunctionCall& node); + void print_net_send_call(const ast::FunctionCall& node) override; /** * Print call to net\_move * \param node The AST node representing the function call */ - void print_net_move_call(const ast::FunctionCall& node); + void print_net_move_call(const ast::FunctionCall& node) override; /** * Print call to net\_event * \param node The AST node representing the function call */ - void print_net_event_call(const ast::FunctionCall& node); + void print_net_event_call(const ast::FunctionCall& node) override; /** diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 2a2c95bff8..11547ca2dc 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -214,6 +214,42 @@ bool CodegenCppVisitor::need_semicolon(const Statement& node) { /* Main printing routines for code generation */ /****************************************************************************************/ +void CodegenCppVisitor::print_function_call(const FunctionCall& node) { + const auto& name = node.get_node_name(); + auto function_name = name; + if (defined_method(name)) { + function_name = method_name(name); + } + + if (is_net_send(name)) { + print_net_send_call(node); + return; + } + + if (is_net_move(name)) { + print_net_move_call(node); + return; + } + + if (is_net_event(name)) { + print_net_event_call(node); + return; + } + + const auto& arguments = node.get_arguments(); + printer->add_text(function_name, '('); + + if (defined_method(name)) { + printer->add_text(internal_method_arguments()); + if (!arguments.empty()) { + printer->add_text(", "); + } + } + + print_vector_elements(arguments, ", "); + printer->add_text(')'); +} + void CodegenCppVisitor::print_prcellstate_macros() const { printer->add_line("#ifndef NRN_PRCELLSTATE"); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 3bff88bacb..7392089adb 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -607,8 +607,27 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Print call to internal or external function * \param node The AST node representing a function call */ - virtual void print_function_call(const ast::FunctionCall& node) = 0; + virtual void print_function_call(const ast::FunctionCall& node); + /** + * Print call to \c net\_send + * \param node The AST node representing the function call + */ + virtual void print_net_send_call(const ast::FunctionCall& node) = 0; + + + /** + * Print call to net\_move + * \param node The AST node representing the function call + */ + virtual void print_net_move_call(const ast::FunctionCall& node) = 0; + + + /** + * Print call to net\_event + * \param node The AST node representing the function call + */ + virtual void print_net_event_call(const ast::FunctionCall& node) = 0; /** * Print function and procedures prototype declaration diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index c89b62083d..23cea875a2 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -89,12 +89,6 @@ void CodegenNeuronCppVisitor::print_atomic_reduction_pragma() { /****************************************************************************************/ -/// TODO: Edit for NEURON -void CodegenNeuronCppVisitor::print_function_call(const FunctionCall& node) { - return; -} - - /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function_prototypes() { if (info.functions.empty() && info.procedures.empty()) { @@ -732,6 +726,18 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { codegen = false; } +void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) { + throw std::runtime_error("Not implemented."); +} + +void CodegenNeuronCppVisitor::print_net_move_call(const ast::FunctionCall& node) { + throw std::runtime_error("Not implemented."); +} + +void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& node) { + throw std::runtime_error("Not implemented."); +} + /****************************************************************************************/ /* Overloaded visitor routines */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 6d7bf0f1f3..e44f30f238 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -122,10 +122,24 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** - * Print call to internal or external function - * \param node The AST node representing a function call + * Print call to \c net\_send + * \param node The AST node representing the function call */ - void print_function_call(const ast::FunctionCall& node) override; + void print_net_send_call(const ast::FunctionCall& node) override; + + + /** + * Print call to net\_move + * \param node The AST node representing the function call + */ + void print_net_move_call(const ast::FunctionCall& node) override; + + + /** + * Print call to net\_event + * \param node The AST node representing the function call + */ + void print_net_event_call(const ast::FunctionCall& node) override; /** From 88571a9f84824421d679b9a6bf3005b60f83f9b6 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Mon, 11 Dec 2023 19:23:27 +0100 Subject: [PATCH 560/871] Remove `codegen` flag from `Codegen` visitors (BlueBrain/nmodl#1118) NMODL Repo SHA: BlueBrain/nmodl@960d8da1c9165b5d7d6b2d036374d8fc6a5f6e64 --- .../codegen_coreneuron_cpp_visitor.cpp | 45 +++----------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 61 ------------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 6 -- .../codegen/codegen_neuron_cpp_visitor.cpp | 9 +-- 4 files changed, 10 insertions(+), 111 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index bf22efb29c..e8daa28018 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -515,7 +515,7 @@ void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { printer->add_newline(2); printer->add_line("using namespace coreneuron;"); - codegen = true; + printing_top_verbatim_blocks = true; for (const auto& block: info.top_blocks) { @@ -526,7 +526,7 @@ void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { } printing_top_verbatim_blocks = false; - codegen = false; + print_namespace_start(); } @@ -535,7 +535,7 @@ void CodegenCoreneuronCppVisitor::print_function_prototypes() { if (info.functions.empty() && info.procedures.empty()) { return; } - codegen = true; + printer->add_newline(2); for (const auto& node: info.functions) { print_function_declaration(*node, node->get_node_name()); @@ -547,7 +547,6 @@ void CodegenCoreneuronCppVisitor::print_function_prototypes() { printer->add_text(';'); printer->add_newline(); } - codegen = false; } @@ -832,7 +831,6 @@ void CodegenCoreneuronCppVisitor::print_function_or_procedure(const ast::Block& void CodegenCoreneuronCppVisitor::print_function_procedure_helper(const ast::Block& node) { - codegen = true; auto name = node.get_node_name(); if (info.function_uses_table(name)) { @@ -843,8 +841,6 @@ void CodegenCoreneuronCppVisitor::print_function_procedure_helper(const ast::Blo } else { print_function_or_procedure(node, name); } - - codegen = false; } @@ -2486,7 +2482,6 @@ void CodegenCoreneuronCppVisitor::print_global_function_common_code( } void CodegenCoreneuronCppVisitor::print_nrn_init(bool skip_init_check) { - codegen = true; printer->add_newline(2); printer->add_line("/** initialize channel */"); @@ -2559,13 +2554,10 @@ void CodegenCoreneuronCppVisitor::print_nrn_init(bool skip_init_check) { if (skip_init_check) { printer->pop_block(); } - codegen = false; } void CodegenCoreneuronCppVisitor::print_before_after_block(const ast::Block* node, size_t block_id) { - codegen = true; - std::string ba_type; std::shared_ptr ba_block; @@ -2616,8 +2608,6 @@ void CodegenCoreneuronCppVisitor::print_before_after_block(const ast::Block* nod printer->pop_block(); printer->pop_block(); print_kernel_data_present_annotation_block_end(); - - codegen = false; } void CodegenCoreneuronCppVisitor::print_nrn_constructor() { @@ -2645,12 +2635,10 @@ void CodegenCoreneuronCppVisitor::print_nrn_destructor() { void CodegenCoreneuronCppVisitor::print_functors_definitions() { - codegen = true; for (const auto& functor_name: info.functor_names) { printer->add_newline(2); print_functor_definition(*functor_name.first); } - codegen = false; } @@ -2671,7 +2659,7 @@ void CodegenCoreneuronCppVisitor::print_watch_activate() { if (info.watch_statements.empty()) { return; } - codegen = true; + printer->add_newline(2); auto inst = fmt::format("{}* inst", instance_struct()); @@ -2708,7 +2696,6 @@ void CodegenCoreneuronCppVisitor::print_watch_activate() { printer->pop_block(); } printer->pop_block(); - codegen = false; } @@ -2720,7 +2707,7 @@ void CodegenCoreneuronCppVisitor::print_watch_check() { if (info.watch_statements.empty()) { return; } - codegen = true; + printer->add_newline(2); printer->add_line("/** routine to check watch activation */"); print_global_function_common_code(BlockType::Watch); @@ -2795,7 +2782,6 @@ void CodegenCoreneuronCppVisitor::print_watch_check() { print_send_event_move(); print_kernel_data_present_annotation_block_end(); printer->pop_block(); - codegen = false; } @@ -2970,7 +2956,6 @@ void CodegenCoreneuronCppVisitor::print_net_init() { // rename net_receive arguments used in the initial block of net_receive rename_net_receive_arguments(*info.net_receive_node, *node); - codegen = true; printing_net_init = true; auto args = "Point_process* pnt, int weight_index, double flag"; printer->add_newline(2); @@ -2990,7 +2975,6 @@ void CodegenCoreneuronCppVisitor::print_net_init() { } } printer->pop_block(); - codegen = false; printing_net_init = false; } @@ -3132,7 +3116,7 @@ void CodegenCoreneuronCppVisitor::print_net_receive_kernel() { if (!net_receive_required()) { return; } - codegen = true; + printing_net_receive = true; const auto node = info.net_receive_node; @@ -3184,7 +3168,6 @@ void CodegenCoreneuronCppVisitor::print_net_receive_kernel() { printer->pop_block(); printing_net_receive = false; - codegen = false; } @@ -3192,7 +3175,7 @@ void CodegenCoreneuronCppVisitor::print_net_receive() { if (!net_receive_required()) { return; } - codegen = true; + printing_net_receive = true; if (!info.artificial_cell) { const auto& name = method_name("net_receive"); @@ -3219,7 +3202,6 @@ void CodegenCoreneuronCppVisitor::print_net_receive() { printer->pop_block(); } printing_net_receive = false; - codegen = false; } @@ -3270,9 +3252,9 @@ void CodegenCoreneuronCppVisitor::print_derivimplicit_kernel(const Block& block) printer->add_line(slist1); printer->add_line(dlist1); printer->add_line(dlist2); - codegen = true; + print_statement_block(*block.get_statement_block(), false, false); - codegen = false; + printer->add_line("int counter = -1;"); printer->fmt_push_block("for (int i=0; i<{}; i++)", info.num_primes); printer->fmt_push_block("if (*deriv{}_advance(thread))", list_num); @@ -3333,7 +3315,6 @@ void CodegenCoreneuronCppVisitor::print_nrn_state() { if (!nrn_state_required()) { return; } - codegen = true; printer->add_newline(2); printer->add_line("/** update state */"); @@ -3377,7 +3358,6 @@ void CodegenCoreneuronCppVisitor::print_nrn_state() { print_kernel_data_present_annotation_block_end(); printer->pop_block(); - codegen = false; } @@ -3546,7 +3526,6 @@ void CodegenCoreneuronCppVisitor::print_nrn_cur() { return; } - codegen = true; if (info.conductances.empty()) { print_nrn_current(*info.breakpoint_node); } @@ -3572,7 +3551,6 @@ void CodegenCoreneuronCppVisitor::print_nrn_cur() { print_kernel_data_present_annotation_block_end(); printer->pop_block(); - codegen = false; } @@ -3669,7 +3647,6 @@ void CodegenCoreneuronCppVisitor::print_compute_functions() { void CodegenCoreneuronCppVisitor::print_codegen_routines() { - codegen = true; print_backend_info(); print_headers_include(); print_namespace_begin(); @@ -3692,7 +3669,6 @@ void CodegenCoreneuronCppVisitor::print_codegen_routines() { print_check_table_thread_function(); print_mechanism_register(); print_namespace_end(); - codegen = false; } @@ -3702,9 +3678,6 @@ void CodegenCoreneuronCppVisitor::print_codegen_routines() { void CodegenCoreneuronCppVisitor::visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) { - if (!codegen) { - return; - } printer->fmt_line("{}_{}({});", node.get_node_to_solve()->get_node_name(), info.mod_suffix, diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 11547ca2dc..8c4f24ce44 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -384,9 +384,6 @@ extern const std::regex regex_special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; void CodegenCppVisitor::visit_string(const String& node) { - if (!codegen) { - return; - } std::string name = node.eval(); if (enable_variable_name_lookup) { name = get_variable_name(name); @@ -396,42 +393,27 @@ void CodegenCppVisitor::visit_string(const String& node) { void CodegenCppVisitor::visit_integer(const Integer& node) { - if (!codegen) { - return; - } const auto& value = node.get_value(); printer->add_text(std::to_string(value)); } void CodegenCppVisitor::visit_float(const Float& node) { - if (!codegen) { - return; - } printer->add_text(format_float_string(node.get_value())); } void CodegenCppVisitor::visit_double(const Double& node) { - if (!codegen) { - return; - } printer->add_text(format_double_string(node.get_value())); } void CodegenCppVisitor::visit_boolean(const Boolean& node) { - if (!codegen) { - return; - } printer->add_text(std::to_string(static_cast(node.eval()))); } void CodegenCppVisitor::visit_name(const Name& node) { - if (!codegen) { - return; - } node.visit_children(*this); } @@ -450,9 +432,6 @@ void CodegenCppVisitor::visit_prime_name(const PrimeName& /* node */) { * \todo : Validate how @ is being handled in neuron implementation */ void CodegenCppVisitor::visit_var_name(const VarName& node) { - if (!codegen) { - return; - } const auto& name = node.get_name(); const auto& at_index = node.get_at(); const auto& index = node.get_index(); @@ -472,9 +451,6 @@ void CodegenCppVisitor::visit_var_name(const VarName& node) { void CodegenCppVisitor::visit_indexed_name(const IndexedName& node) { - if (!codegen) { - return; - } node.get_name()->accept(*this); printer->add_text("["); printer->add_text("static_cast("); @@ -485,18 +461,12 @@ void CodegenCppVisitor::visit_indexed_name(const IndexedName& node) { void CodegenCppVisitor::visit_local_list_statement(const LocalListStatement& node) { - if (!codegen) { - return; - } printer->add_text(local_var_type(), ' '); print_vector_elements(node.get_variables(), ", "); } void CodegenCppVisitor::visit_if_statement(const IfStatement& node) { - if (!codegen) { - return; - } printer->add_text("if ("); node.get_condition()->accept(*this); printer->add_text(") "); @@ -510,9 +480,6 @@ void CodegenCppVisitor::visit_if_statement(const IfStatement& node) { void CodegenCppVisitor::visit_else_if_statement(const ElseIfStatement& node) { - if (!codegen) { - return; - } printer->add_text(" else if ("); node.get_condition()->accept(*this); printer->add_text(") "); @@ -521,9 +488,6 @@ void CodegenCppVisitor::visit_else_if_statement(const ElseIfStatement& node) { void CodegenCppVisitor::visit_else_statement(const ElseStatement& node) { - if (!codegen) { - return; - } printer->add_text(" else "); node.visit_children(*this); } @@ -538,9 +502,6 @@ void CodegenCppVisitor::visit_while_statement(const WhileStatement& node) { void CodegenCppVisitor::visit_from_statement(const ast::FromStatement& node) { - if (!codegen) { - return; - } auto name = node.get_node_name(); const auto& from = node.get_from(); const auto& to = node.get_to(); @@ -562,9 +523,6 @@ void CodegenCppVisitor::visit_from_statement(const ast::FromStatement& node) { void CodegenCppVisitor::visit_paren_expression(const ParenExpression& node) { - if (!codegen) { - return; - } printer->add_text("("); node.get_expression()->accept(*this); printer->add_text(")"); @@ -572,9 +530,6 @@ void CodegenCppVisitor::visit_paren_expression(const ParenExpression& node) { void CodegenCppVisitor::visit_binary_expression(const BinaryExpression& node) { - if (!codegen) { - return; - } auto op = node.get_op().eval(); const auto& lhs = node.get_lhs(); const auto& rhs = node.get_rhs(); @@ -593,17 +548,11 @@ void CodegenCppVisitor::visit_binary_expression(const BinaryExpression& node) { void CodegenCppVisitor::visit_binary_operator(const BinaryOperator& node) { - if (!codegen) { - return; - } printer->add_text(node.eval()); } void CodegenCppVisitor::visit_unary_operator(const UnaryOperator& node) { - if (!codegen) { - return; - } printer->add_text(" " + node.eval()); } @@ -614,26 +563,16 @@ void CodegenCppVisitor::visit_unary_operator(const UnaryOperator& node) { * false. Hence we visit children even if code generation is false. */ void CodegenCppVisitor::visit_statement_block(const StatementBlock& node) { - if (!codegen) { - node.visit_children(*this); - return; - } print_statement_block(node); } void CodegenCppVisitor::visit_function_call(const FunctionCall& node) { - if (!codegen) { - return; - } print_function_call(node); } void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { - if (!codegen) { - return; - } const auto& text = node.get_statement()->eval(); const auto& result = process_verbatim_text(text); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 7392089adb..c018161f11 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -253,12 +253,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::vector codegen_global_variables; - /** - * Flag to indicate if visitor should print the visited nodes - */ - bool codegen = false; - - /** * Variable name should be converted to instance name (but not for function arguments) */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 23cea875a2..2e99e3af99 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -476,7 +476,6 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { - codegen = true; printer->add_newline(2); printer->add_line("/** initialize channel */"); @@ -484,8 +483,6 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* " "_ml_arg, int _type) {{}}", method_name(naming::NRN_INIT_METHOD)); - - codegen = false; } @@ -532,7 +529,7 @@ void CodegenNeuronCppVisitor::print_nrn_state() { if (!nrn_state_required()) { return; } - codegen = true; + printer->add_newline(2); printer->fmt_line( @@ -541,8 +538,6 @@ void CodegenNeuronCppVisitor::print_nrn_state() { method_name(naming::NRN_STATE_METHOD)); /// TODO: Fill in - - codegen = false; } @@ -708,7 +703,6 @@ void CodegenNeuronCppVisitor::print_compute_functions() { /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_codegen_routines() { - codegen = true; print_backend_info(); print_headers_include(); print_macro_definitions(); @@ -723,7 +717,6 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_sdlists_init(true); print_mechanism_register(); print_namespace_end(); - codegen = false; } void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) { From a3205a32786da39185d5deb6d191b1aed396f9cc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 12 Dec 2023 08:30:51 +0100 Subject: [PATCH 561/871] Move to `CodegenCppVisitor::visit_solution_expression`. (BlueBrain/nmodl#1117) NMODL Repo SHA: BlueBrain/nmodl@9dd08522c7d1978609bc5ebd6e677ec6f491d31b --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 11 ----------- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp | 1 - src/nmodl/codegen/codegen_cpp_visitor.cpp | 11 +++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 1 + src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 7 ------- src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 1 - 6 files changed, 12 insertions(+), 20 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index e8daa28018..909371c336 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -3777,17 +3777,6 @@ void CodegenCoreneuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { } -void CodegenCoreneuronCppVisitor::visit_solution_expression(const SolutionExpression& node) { - auto block = node.get_node_to_solve().get(); - if (block->is_statement_block()) { - auto statement_block = dynamic_cast(block); - print_statement_block(*statement_block, false, false); - } else { - block->accept(*this); - } -} - - void CodegenCoreneuronCppVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { printer->add_text(fmt::format("nrn_watch_activate(inst, id, pnodecount, {}, v, watch_remove)", current_watch_statement++)); diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 5693cb245f..90365e6007 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -1197,7 +1197,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; - virtual void visit_solution_expression(const ast::SolutionExpression& node) override; virtual void visit_watch_statement(const ast::WatchStatement& node) override; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 8c4f24ce44..17fc70d240 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -611,6 +611,17 @@ void CodegenCppVisitor::visit_mutex_unlock(const ast::MutexUnlock& node) { } +void CodegenCppVisitor::visit_solution_expression(const SolutionExpression& node) { + auto block = node.get_node_to_solve().get(); + if (block->is_statement_block()) { + auto statement_block = dynamic_cast(block); + print_statement_block(*statement_block, false, false); + } else { + block->accept(*this); + } +} + + /** * \details Once variables are populated, update index semantics to register with coreneuron */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index c018161f11..e3491611e1 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1135,6 +1135,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_protect_statement(const ast::ProtectStatement& node) override; void visit_mutex_lock(const ast::MutexLock& node) override; void visit_mutex_unlock(const ast::MutexUnlock& node) override; + void visit_solution_expression(const ast::SolutionExpression& node) override; std::string compute_method_name(BlockType type) const; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2e99e3af99..4a4339649b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -736,13 +736,6 @@ void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& node /* Overloaded visitor routines */ /****************************************************************************************/ - -/// TODO: Edit for NEURON -void CodegenNeuronCppVisitor::visit_solution_expression(const SolutionExpression& node) { - return; -} - - /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::visit_watch_statement(const ast::WatchStatement& /* node */) { return; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index e44f30f238..f4ce633355 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -559,7 +559,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - virtual void visit_solution_expression(const ast::SolutionExpression& node) override; virtual void visit_watch_statement(const ast::WatchStatement& node) override; From bf56fb86d816cfae48e91b283ad39e68ce7e233b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 13 Dec 2023 12:03:22 +0100 Subject: [PATCH 562/871] Solve simple ODE, setup mech instance and added CI tests (BlueBrain/nmodl#1119) * Solve simple ODE in SOLVE block * Added logic for mechanism instance declaration and instantiation * Rearranged needed code around CodegenCpp visitors * Added Python integration testing with NEURON compilation for the `usecase`s tests * Added CI for the `usecase` tests --------- Co-authored-by: Pramod Kumbhar Co-authored-by: Ioannis Magkanaris NMODL Repo SHA: BlueBrain/nmodl@0f9371940ce59a6d0a61458ef42fa130014658af --- cmake/nmodl/CMakeLists.txt | 6 + .../codegen/codegen_neuron_cpp_visitor.cpp | 200 +++++++++++++++--- .../codegen/codegen_neuron_cpp_visitor.hpp | 13 +- .../codegen/codegen_neuron_cpp_visitor.cpp | 34 +-- test/nmodl/transpiler/usecases/CMakeLists.txt | 7 + .../usecases/cnexp_scalar/leonhard.mod | 16 ++ .../usecases/cnexp_scalar/simulate.py | 27 +++ test/nmodl/transpiler/usecases/run_test.sh | 15 ++ 8 files changed, 268 insertions(+), 50 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/CMakeLists.txt create mode 100644 test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod create mode 100644 test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py create mode 100755 test/nmodl/transpiler/usecases/run_test.sh diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 9a98839831..3c186e6e33 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -19,6 +19,8 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # ============================================================================= option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) option(NMODL_ENABLE_TESTS "Enable build of tests" ON) +option(NMODL_ENABLE_USECASES + "If building tests, additionally enable build of usecase tests. Requires neuron." OFF) set(NMODL_EXTRA_CXX_FLAGS "" CACHE STRING "Add extra compile flags for NMODL sources") @@ -207,6 +209,10 @@ if(NOT NMODL_AS_SUBPROJECT AND NMODL_ENABLE_TESTS) include(CTest) add_subdirectory(test/unit) add_subdirectory(test/integration) + + if(NMODL_ENABLE_USECASES) + add_subdirectory(test/usecases) + endif() endif() # ============================================================================= diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 4a4339649b..1f3fb893f5 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -199,7 +199,21 @@ void CodegenNeuronCppVisitor::print_namespace_stop() { /// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbol, bool use_instance) const { - return symbol->get_name(); + auto name = symbol->get_name(); + auto dimension = symbol->get_length(); + // auto position = position_of_float_var(name); + if (symbol->is_array()) { + if (use_instance) { + return fmt::format("(inst.{}+id*{})", name, dimension); + } + throw std::runtime_error("Printing non-instance variables is not implemented."); + // return fmt::format("(data + {}*pnodecount + id*{})", position, dimension); + } + if (use_instance) { + return fmt::format("inst.{}[id]", name); + } + throw std::runtime_error("Not implemented."); + // return fmt::format("data[{}*pnodecount + id]", position); } @@ -218,10 +232,70 @@ std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symb } -/// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { - return name; + // const std::string& varname = update_if_ion_variable_name(name); + const std::string& varname = name; + + auto symbol_comparator = [&varname](const SymbolType& sym) { + return varname == sym->get_name(); + }; + + auto index_comparator = [&varname](const IndexVariableInfo& var) { + return varname == var.symbol->get_name(); + }; + + // float variable + auto f = std::find_if(codegen_float_variables.begin(), + codegen_float_variables.end(), + symbol_comparator); + if (f != codegen_float_variables.end()) { + return float_variable_name(*f, use_instance); + } + + // integer variable + auto i = + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); + if (i != codegen_int_variables.end()) { + return int_variable_name(*i, varname, use_instance); + } + + // global variable + auto g = std::find_if(codegen_global_variables.begin(), + codegen_global_variables.end(), + symbol_comparator); + if (g != codegen_global_variables.end()) { + return global_variable_name(*g, use_instance); + } + + if (varname == naming::NTHREAD_DT_VARIABLE) { + return std::string("_nt->_") + naming::NTHREAD_DT_VARIABLE; + } + + // t in net_receive method is an argument to function and hence it should + // be used instead of nt->_t which is current time of thread + if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { + return std::string("_nt->_") + naming::NTHREAD_T_VARIABLE; + } + + auto const iter = + std::find_if(info.neuron_global_variables.begin(), + info.neuron_global_variables.end(), + [&varname](auto const& entry) { return entry.first->get_name() == varname; }); + if (iter != info.neuron_global_variables.end()) { + std::string ret; + if (use_instance) { + ret = "*(inst->"; + } + ret.append(varname); + if (use_instance) { + ret.append(")"); + } + return ret; + } + + // otherwise return original name + return varname; } @@ -384,6 +458,23 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_line("};"); } +void CodegenNeuronCppVisitor::print_make_instance() const { + printer->add_newline(2); + printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range& _ml)", + instance_struct(), + info.mod_suffix); + printer->fmt_push_block("return {}", instance_struct()); + + const auto codegen_float_variables_size = codegen_float_variables.size(); + for (int i = 0; i < codegen_float_variables_size; ++i) { + const auto& float_var = codegen_float_variables[i]; + printer->fmt_line("&_ml.template fpfield<{}>(0){}", + i, + i < codegen_float_variables_size - 1 ? "," : ""); + } + printer->pop_block(";"); + printer->pop_block(); +} void CodegenNeuronCppVisitor::print_mechanism_register() { /// TODO: Write this according to NEURON @@ -443,48 +534,80 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { } -void CodegenNeuronCppVisitor::print_mechanism_range_var_structure( - [[maybe_unused]] bool print_initializers) { +void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { + auto const value_initialize = print_initializers ? "{}" : ""; + auto int_type = default_int_data_type(); printer->add_newline(2); - printer->add_line("/* NEURON RANGE variables macro definitions */"); - for (auto i = 0; i < codegen_float_variables.size(); ++i) { - const auto float_var = codegen_float_variables[i]; - if (float_var->is_array()) { - printer->add_line("#define ", - float_var->get_name(), - "(id) _ml->template data_array<", - std::to_string(i), - ", ", - std::to_string(float_var->get_length()), - ">(id)"); + printer->add_line("/** all mechanism instance variables and global variables */"); + printer->fmt_push_block("struct {} ", instance_struct()); + + for (auto const& [var, type]: info.neuron_global_variables) { + auto const name = var->get_name(); + printer->fmt_line("{}* {}{};", + type, + name, + print_initializers ? fmt::format("{{&coreneuron::{}}}", name) + : std::string{}); + } + for (auto& var: codegen_float_variables) { + const auto& name = var->get_name(); + printer->fmt_line("double* {}{};", name, value_initialize); + } + for (auto& var: codegen_int_variables) { + const auto& name = var.symbol->get_name(); + if (var.is_index || var.is_integer) { + auto qualifier = var.is_constant ? "const " : ""; + printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialize); } else { - printer->add_line("#define ", - float_var->get_name(), - "(id) _ml->template fpfield<", - std::to_string(i), - ">(id)"); + auto qualifier = var.is_constant ? "const " : ""; + auto type = var.is_vdata ? "void*" : default_float_data_type(); + printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); } } + + // printer->fmt_line("{}* {}{};", + // global_struct(), + // naming::INST_GLOBAL_MEMBER, + // print_initializers ? fmt::format("{{&{}}}", global_struct_instance()) + // : std::string{}); + printer->pop_block(";"); +} + + +void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { + // initial block + if (node != nullptr) { + const auto& block = node->get_statement_block(); + print_statement_block(*block, false, false); + } } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, const std::string& function_name) { - return; + std::string method = function_name.empty() ? compute_method_name(type) : function_name; + std::string args = + "_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int " + "_type"; + printer->fmt_push_block("void {}({})", method, args); + + printer->add_line("_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); + printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + printer->add_line("auto nodecount = _ml_arg->nodecount;"); } void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { printer->add_newline(2); - printer->add_line("/** initialize channel */"); - printer->fmt_line( - "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* " - "_ml_arg, int _type) {{}}", - method_name(naming::NRN_INIT_METHOD)); -} + print_global_function_common_code(BlockType::Initial); + + printer->push_block("for (int id = 0; id < nodecount; id++)"); + print_initial_block(info.initial_node); + printer->pop_block(); + printer->pop_block(); +} void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_newline(2); @@ -531,13 +654,21 @@ void CodegenNeuronCppVisitor::print_nrn_state() { } printer->add_newline(2); + print_global_function_common_code(BlockType::State); - printer->fmt_line( - "void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* " - "_ml_arg, int _type) {{}}", - method_name(naming::NRN_STATE_METHOD)); + printer->push_block("for (int id = 0; id < nodecount; id++)"); - /// TODO: Fill in + if (info.nrn_state_block) { + info.nrn_state_block->visit_children(*this); + } + + if (info.currents.empty() && info.breakpoint_node != nullptr) { + auto block = info.breakpoint_node->get_statement_block(); + print_statement_block(*block, false, false); + } + + printer->pop_block(); + printer->pop_block(); } @@ -668,6 +799,7 @@ void CodegenNeuronCppVisitor::print_namespace_end() { void CodegenNeuronCppVisitor::print_data_structures(bool print_initializers) { print_mechanism_global_var_structure(print_initializers); print_mechanism_range_var_structure(print_initializers); + print_make_instance(); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index f4ce633355..0d581d694d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -24,13 +24,13 @@ #include #include +#include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_info.hpp" #include "codegen/codegen_naming.hpp" #include "printer/code_printer.hpp" #include "symtab/symbol_table.hpp" #include "utils/logger.hpp" #include "visitors/ast_visitor.hpp" -#include /// encapsulates code generation backend implementations @@ -83,6 +83,12 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ virtual std::string backend_name() const override; + /** + * Name of structure that wraps range variables + */ + std::string instance_struct() const { + return fmt::format("{}_Instance", info.mod_suffix); + } /****************************************************************************************/ /* Common helper routines accross codegen functions */ @@ -381,6 +387,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_nrn_init(bool skip_init_check = true); + /** Print the initial block. */ + void print_initial_block(const ast::InitialBlock* node); /** * Print nrn_constructor function definition @@ -527,6 +535,9 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_data_structures(bool print_initializers) override; + /** Print `make_*_instance`. + */ + void print_make_instance() const; /** * Set v_unused (voltage) for NRN_PRCELLSTATE feature diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 9f759a5cef..0f5894fb12 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -181,21 +181,25 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_global_variables))); } - THEN("Correct range variables' macros are printed") { - std::string expected_range_macros = R"(/* NEURON RANGE variables macro definitions */ - #define g(id) _ml->template fpfield<0>(id) - #define e(id) _ml->template fpfield<1>(id) - #define i(id) _ml->template fpfield<2>(id) - #define ar(id) _ml->template data_array<3, 2>(id) - #define s(id) _ml->template fpfield<4>(id) - #define ena(id) _ml->template fpfield<5>(id) - #define ina(id) _ml->template fpfield<6>(id) - #define Ds(id) _ml->template fpfield<7>(id) - #define v_unused(id) _ml->template fpfield<8>(id) - #define g_unused(id) _ml->template fpfield<9>(id))"; + THEN("Correct pas_test_Instance") { + std::string expected = + R"( struct pas_test_Instance { + double* g{}; + double* e{}; + double* i{}; + double* ar{}; + double* s{}; + double* ena{}; + double* ina{}; + double* Ds{}; + double* v_unused{}; + double* g_unused{}; + const double* ion_ena{}; + double* ion_ina{}; + double* ion_dinadv{}; + };)"; - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_range_macros))); + REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected))); } THEN("Correct HOC global variables are printed") { std::string expected_hoc_global_variables = @@ -222,7 +226,7 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { } THEN("Placeholder nrn_state function is printed") { std::string expected_placeholder_nrn_state = - R"(void nrn_state_pas_test(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int _type) {})"; + R"(void nrn_state_pas_test(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int _type) {)"; REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_state))); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt new file mode 100644 index 0000000000..82e53102d7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -0,0 +1,7 @@ +set(NMODL_USECASE_DIRS cnexp_scalar) + +foreach(usecase ${NMODL_USECASE_DIRS}) + add_test(NAME usecase_${usecase} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh ${CMAKE_BINARY_DIR}/bin/nmodl + ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) +endforeach() diff --git a/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod b/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod new file mode 100644 index 0000000000..eef4b5476f --- /dev/null +++ b/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod @@ -0,0 +1,16 @@ +NEURON { + SUFFIX leonhard +} + +STATE { x } + +INITIAL { + x = 42 +} + +BREAKPOINT { + SOLVE dX METHOD cnexp +} + +DERIVATIVE dX { x' = -x } + diff --git a/test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py b/test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py new file mode 100644 index 0000000000..3245670ca9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py @@ -0,0 +1,27 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("leonhard") +s.nseg = nseg + +x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +x = np.array(x_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +x0 = 42.0 +x_exact = 42.0 * np.exp(-t) +rel_err = np.abs(x - x_exact) / x_exact + +assert np.all(rel_err < 1e-12) +print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh new file mode 100755 index 0000000000..1ee2981454 --- /dev/null +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -0,0 +1,15 @@ +#! /usr/bin/env bash +set -e + +nmodl="$1" +output_dir="$(uname -m)" +usecase_dir="$2" + +pushd "${usecase_dir}" + +rm -r "${output_dir}" tmp || true + +nrnivmodl -nmodl "${nmodl}" +"$(uname -m)/special" simulate.py + +popd From facfc07f5d6f0582141e1c9e2965be83a92ccc73 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 14 Dec 2023 23:06:50 +0100 Subject: [PATCH 563/871] Skip printing of INDEPENDENT block from AST to NMODL (BlueBrain/nmodl#1124) * As INDEPENDENT is deprecated and has no impact/role in NEURON/NMODL models, we have removed the AST nodes related to this construct. * When user provide an input with INDEPENDENT {} block then just add a comment and simply skip printing it. * Update test accordingly fixes BlueBrain/nmodl#1123 NMODL Repo SHA: BlueBrain/nmodl@e35b3dfb9741629d7b55ee02eeb0c356c67c160b --- src/nmodl/language/node_info.py | 1 + src/nmodl/language/nodes.py | 4 ++++ .../language/templates/visitors/nmodl_visitor.cpp | 4 ++++ .../language/templates/visitors/nmodl_visitor.hpp | 12 ++++++++---- .../nmodl/transpiler/unit/utils/nmodl_constructs.cpp | 3 +-- 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 0a719a592e..466895b3f9 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -155,6 +155,7 @@ STATEMENT_BLOCK_NODE = "StatementBlock" STRING_NODE = "String" UNIT_BLOCK = "UnitBlock" +INDEPENDENT_BLOCK_NODE = "IndependentBlock" # name of variable in prime node which represent order of derivative ORDER_VAR_NAME = "order" diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 9184976364..9e37a40c59 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -59,6 +59,10 @@ def is_global_block_node(self): def is_prime_node(self): return self.class_name == node_info.PRIME_NAME_NODE + @property + def is_independent_node(self): + return self.class_name == node_info.INDEPENDENT_BLOCK_NODE + @property def is_program_node(self): """ diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp index 8ee653b8b0..a4ab5cb96f 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.cpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.cpp @@ -89,6 +89,10 @@ using namespace ast; {%- for node in nodes %} void NmodlPrintVisitor::visit_{{ node.class_name|snake_case}}(const {{ node.class_name }}& node) { + {% if node.is_independent_node %} + printer->add_element(": INDEPENDENT block is deprecated and has no effect in the NEURON model. Skipped!"); + return; + {%- endif %} if (is_exclude_type(node.get_node_type())) { return; } diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index 52a4efb2fe..dbfa32eaa7 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -32,11 +32,15 @@ namespace visitor { /** * \class NmodlPrintVisitor * \brief %Visitor for printing AST back to NMODL - * \todo Note that AstNodeType::INDEPENDENT_BLOCK is now trimmed-down - * in the AST. So if we need to make provide something like - * `nmodl-format` then we should exclude this node type i.e. - * add that in the exclude_types. + * + * \note AstNodeType::INDEPENDENT_BLOCK representation in the AST has + * been trimmed as the `INDEPENDENT {}` block is now deprecated + * and considered an unused construct in MOD files. If a user + * attempts to print a MOD file containing an INDEPENDENT block, + * it will be skipped, and a comment will be added to indicate + * the deprecation. */ + class NmodlPrintVisitor: public ConstVisitor { private: std::unique_ptr printer; diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 09fab789dc..4faa30d843 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -433,8 +433,7 @@ std::map const nmodl_valid_constructs{ } )", R"( - INDEPENDENT { - t u} + : INDEPENDENT block is deprecated and has no effect in the NEURON model. Skipped! )" } }, From e81d35f1f84b7835e74301182be1753d75f6f912 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Fri, 5 Jan 2024 15:42:41 +0100 Subject: [PATCH 564/871] Upgrade spdlog and fmt (BlueBrain/nmodl#1132) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fmt from 9.1.0 to 10.2.1 spdlog from v1.10.0 to v1.12.0 NMODL Repo SHA: BlueBrain/nmodl@8821cdb9859175ed0f4961a31744e47144d2fc4a --- cmake/nmodl/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 3c186e6e33..c4bc770421 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -124,11 +124,12 @@ cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED) # Tell spdlog not to use its bundled fmt, it should either use the fmt submodule or a truly external # installation for consistency. This line should be harmless if we use an external spdlog. set(SPDLOG_FMT_EXTERNAL ON) -cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) +set(SPDLOG_SYSTEM_INCLUDE ON) if(NMODL_3RDPARTY_USE_SPDLOG) # See above, same logic as fmt - set_property(TARGET spdlog PROPERTY POSITION_INDEPENDENT_CODE ON) + set(SPDLOG_BUILD_PIC ON) endif() +cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) From a6ad831133e03ca37175f3933b9181f36b55f7e0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 8 Jan 2024 10:55:53 +0100 Subject: [PATCH 565/871] Array variables in DERIVATIVE block. (BlueBrain/nmodl#1126) * Array variables in DERIVATIVE block. * Check RANGE array variables. NMODL Repo SHA: BlueBrain/nmodl@58bb7fd938aee2d0066eb3ba47159ad7fe9419fe --- .../codegen/codegen_neuron_cpp_visitor.cpp | 13 ++++++-- test/nmodl/transpiler/usecases/CMakeLists.txt | 2 +- .../usecases/cnexp_array/leonhard.mod | 30 +++++++++++++++++++ .../usecases/cnexp_array/simulate.py | 27 +++++++++++++++++ 4 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod create mode 100644 test/nmodl/transpiler/usecases/cnexp_array/simulate.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 1f3fb893f5..edd93d473c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -468,9 +468,16 @@ void CodegenNeuronCppVisitor::print_make_instance() const { const auto codegen_float_variables_size = codegen_float_variables.size(); for (int i = 0; i < codegen_float_variables_size; ++i) { const auto& float_var = codegen_float_variables[i]; - printer->fmt_line("&_ml.template fpfield<{}>(0){}", - i, - i < codegen_float_variables_size - 1 ? "," : ""); + if (float_var->is_array()) { + printer->fmt_line("_ml.template data_array<{}, {}>(0){}", + i, + float_var->get_length(), + i < codegen_float_variables_size - 1 ? "," : ""); + } else { + printer->fmt_line("&_ml.template fpfield<{}>(0){}", + i, + i < codegen_float_variables_size - 1 ? "," : ""); + } } printer->pop_block(";"); printer->pop_block(); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 82e53102d7..244dd822cd 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,4 +1,4 @@ -set(NMODL_USECASE_DIRS cnexp_scalar) +set(NMODL_USECASE_DIRS cnexp_scalar cnexp_array) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod b/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod new file mode 100644 index 0000000000..35d208d5e8 --- /dev/null +++ b/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod @@ -0,0 +1,30 @@ +NEURON { + SUFFIX leonhard + RANGE z +} + +ASSIGNED { + z[3] +} + +STATE { + x + y[2] +} + +INITIAL { + x = 42.0 + y[0] = 0.1 + y[1] = -1.0 + z[0] = 0.7 + z[1] = 0.8 + z[2] = 0.9 +} + +BREAKPOINT { + SOLVE dX METHOD cnexp +} + +DERIVATIVE dX { + x' = (y[0] + y[1])*(z[0]*z[1]*z[2])*x +} diff --git a/test/nmodl/transpiler/usecases/cnexp_array/simulate.py b/test/nmodl/transpiler/usecases/cnexp_array/simulate.py new file mode 100644 index 0000000000..fdcd1ea75f --- /dev/null +++ b/test/nmodl/transpiler/usecases/cnexp_array/simulate.py @@ -0,0 +1,27 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("leonhard") +s.nseg = nseg + +x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +x = np.array(x_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +rate = (0.1 - 1.0) * (0.7 * 0.8 * 0.9) +x_exact = 42.0 * np.exp(rate*t) +rel_err = np.abs(x - x_exact) / x_exact + +assert np.all(rel_err < 1e-12) +print("leonhard: success") From 797ad44421463a42e729f83b2704b9ebd6ab0da1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 10 Jan 2024 08:56:29 +0100 Subject: [PATCH 566/871] No more flickering. (BlueBrain/nmodl#1134) NMODL Repo SHA: BlueBrain/nmodl@e520d768f5e3e13c618153e767137dacaad7f0c5 --- test/nmodl/transpiler/usecases/run_test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index 1ee2981454..7c19f27f6d 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -10,6 +10,6 @@ pushd "${usecase_dir}" rm -r "${output_dir}" tmp || true nrnivmodl -nmodl "${nmodl}" -"$(uname -m)/special" simulate.py +"$(uname -m)/special" -nogui simulate.py popd From 866350e46be684a8fe5ef6c482a6ab26dc889bc3 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 11 Jan 2024 09:58:22 +0100 Subject: [PATCH 567/871] Workaround 'nocmodl` naming clash. (BlueBrain/nmodl#1135) See: https://github.com/neuronsimulator/nrn/issues/2658 NMODL Repo SHA: BlueBrain/nmodl@369dff82632157a6a44f7820cc2a19a0ae5b4480 --- test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod b/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod index 35d208d5e8..e2aa3e6444 100644 --- a/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod +++ b/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod @@ -9,13 +9,13 @@ ASSIGNED { STATE { x - y[2] + s[2] } INITIAL { x = 42.0 - y[0] = 0.1 - y[1] = -1.0 + s[0] = 0.1 + s[1] = -1.0 z[0] = 0.7 z[1] = 0.8 z[2] = 0.9 @@ -26,5 +26,5 @@ BREAKPOINT { } DERIVATIVE dX { - x' = (y[0] + y[1])*(z[0]*z[1]*z[2])*x + x' = (s[0] + s[1])*(z[0]*z[1]*z[2])*x } From 92bf3fe43fe67bb9ff5b36ba6580aec5428c40c9 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 11 Jan 2024 11:14:47 +0100 Subject: [PATCH 568/871] Cross-check against NRN+nocmodl. (BlueBrain/nmodl#1136) NMODL Repo SHA: BlueBrain/nmodl@72af0a3d0eeca44d86da3a82f34ba5a99b70dd6f --- test/nmodl/transpiler/usecases/run_test.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index 7c19f27f6d..858751c890 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -7,8 +7,13 @@ usecase_dir="$2" pushd "${usecase_dir}" +# NRN + nocmodl rm -r "${output_dir}" tmp || true +nrnivmodl +"$(uname -m)/special" -nogui simulate.py +# NRN + NMODL +rm -r "${output_dir}" tmp || true nrnivmodl -nmodl "${nmodl}" "$(uname -m)/special" -nogui simulate.py From b2d36536af8c2b7110d4f2652da5cfcc2c484594 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 12 Jan 2024 10:23:08 +0100 Subject: [PATCH 569/871] Move GLOBAL related code. (BlueBrain/nmodl#1137) The methods: - print_global_var_struct_decl - print_global_var_struct_assertions - instance_struct - global_struct - global_struct_instance have been moved from the CoreNEURON C++ printer to the shared C++ printer. NMODL Repo SHA: BlueBrain/nmodl@b01e5f7be9c280b3e270cf27975763a3aa2274e7 --- .../codegen_coreneuron_cpp_visitor.cpp | 18 --------- .../codegen_coreneuron_cpp_visitor.hpp | 39 ------------------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 19 +++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 39 +++++++++++++++++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 6 --- 5 files changed, 58 insertions(+), 63 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 909371c336..08ec11a658 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -497,11 +497,6 @@ void CodegenCoreneuronCppVisitor::print_abort_routine() const { } -void CodegenCoreneuronCppVisitor::print_global_var_struct_decl() { - printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); -} - - /****************************************************************************************/ /* Printing routines for code generation */ /****************************************************************************************/ @@ -1805,19 +1800,6 @@ void CodegenCoreneuronCppVisitor::print_mechanism_global_var_structure(bool prin } -void CodegenCoreneuronCppVisitor::print_global_var_struct_assertions() const { - // Assert some things that we assume when copying instances of this struct - // to the GPU and so on. - printer->fmt_line("static_assert(std::is_trivially_copy_constructible_v<{}>);", - global_struct()); - printer->fmt_line("static_assert(std::is_trivially_move_constructible_v<{}>);", - global_struct()); - printer->fmt_line("static_assert(std::is_trivially_copy_assignable_v<{}>);", global_struct()); - printer->fmt_line("static_assert(std::is_trivially_move_assignable_v<{}>);", global_struct()); - printer->fmt_line("static_assert(std::is_trivially_destructible_v<{}>);", global_struct()); -} - - /** * Print structs that encapsulate information about scalar and * vector elements of type global and thread variables. diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 90365e6007..f09c5bf9c2 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -83,30 +83,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual std::string backend_name() const override; - /** - * Name of structure that wraps range variables - */ - std::string instance_struct() const { - return fmt::format("{}_Instance", info.mod_suffix); - } - - - /** - * Name of structure that wraps global variables - */ - std::string global_struct() const { - return fmt::format("{}_Store", info.mod_suffix); - } - - - /** - * Name of the (host-only) global instance of `global_struct` - */ - std::string global_struct_instance() const { - return info.mod_suffix + "_global"; - } - - /** * Determine the number of threads to allocate */ @@ -370,15 +346,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual void print_abort_routine() const; - /** - * Instantiate global var instance - * - * For C++ code generation this is empty - * \return "" - */ - virtual void print_global_var_struct_decl(); - - /** * Print declarations of the functions used by \ref * print_instance_struct_copy_to_device and \ref @@ -788,12 +755,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_mechanism_global_var_structure(bool print_initializers) override; - /** - * Print static assertions about the global variable struct. - */ - virtual void print_global_var_struct_assertions() const; - - /** * Print byte arrays that register scalar and vector variables for hoc interface * diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 17fc70d240..b148e0f189 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -214,6 +214,25 @@ bool CodegenCppVisitor::need_semicolon(const Statement& node) { /* Main printing routines for code generation */ /****************************************************************************************/ + +void CodegenCppVisitor::print_global_var_struct_assertions() const { + // Assert some things that we assume when copying instances of this struct + // to the GPU and so on. + printer->fmt_line("static_assert(std::is_trivially_copy_constructible_v<{}>);", + global_struct()); + printer->fmt_line("static_assert(std::is_trivially_move_constructible_v<{}>);", + global_struct()); + printer->fmt_line("static_assert(std::is_trivially_copy_assignable_v<{}>);", global_struct()); + printer->fmt_line("static_assert(std::is_trivially_move_assignable_v<{}>);", global_struct()); + printer->fmt_line("static_assert(std::is_trivially_destructible_v<{}>);", global_struct()); +} + + +void CodegenCppVisitor::print_global_var_struct_decl() { + printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); +} + + void CodegenCppVisitor::print_function_call(const FunctionCall& node) { const auto& name = node.get_node_name(); auto function_name = name; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index e3491611e1..549519d396 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -308,6 +308,30 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual std::string simulator_name() = 0; + /** + * Name of structure that wraps range variables + */ + std::string instance_struct() const { + return fmt::format("{}_Instance", info.mod_suffix); + } + + + /** + * Name of structure that wraps global variables + */ + std::string global_struct() const { + return fmt::format("{}_Store", info.mod_suffix); + } + + + /** + * Name of the (host-only) global instance of `global_struct` + */ + std::string global_struct_instance() const { + return info.mod_suffix + "_global"; + } + + /** * Name of the code generation backend */ @@ -577,6 +601,15 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_atomic_reduction_pragma() = 0; + /** + * Instantiate global var instance + * + * For C++ code generation this is empty + * \return "" + */ + virtual void print_global_var_struct_decl(); + + /****************************************************************************************/ /* Printing routines for code generation */ /****************************************************************************************/ @@ -883,6 +916,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_mechanism_global_var_structure(bool print_initializers) = 0; + /** + * Print static assertions about the global variable struct. + */ + virtual void print_global_var_struct_assertions() const; + + /** * Print declaration of macro NRN_PRCELLSTATE for debugging */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 0d581d694d..7827ef64f6 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -83,12 +83,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ virtual std::string backend_name() const override; - /** - * Name of structure that wraps range variables - */ - std::string instance_struct() const { - return fmt::format("{}_Instance", info.mod_suffix); - } /****************************************************************************************/ /* Common helper routines accross codegen functions */ From c495092837caa62aaec7ad7f3b6b534ec1a845ae Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 15 Jan 2024 11:26:06 +0100 Subject: [PATCH 570/871] Enable trivial cases of global state variables. (BlueBrain/nmodl#1130) NMODL Repo SHA: BlueBrain/nmodl@ef8fcbf4157e48a9ddfa63ab9df177b1aecb02ff --- .../codegen/codegen_neuron_cpp_visitor.cpp | 86 +++++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.cpp | 1 + test/nmodl/transpiler/usecases/CMakeLists.txt | 2 +- .../usecases/global_breakpoint/leonhard.mod | 20 +++++ .../usecases/global_breakpoint/simulate.py | 27 ++++++ 5 files changed, 128 insertions(+), 8 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod create mode 100644 test/nmodl/transpiler/usecases/global_breakpoint/simulate.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index edd93d473c..6ea76459c3 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -225,10 +225,13 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& } -/// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symbol, bool use_instance) const { - return symbol->get_name(); + if (use_instance) { + return fmt::format("inst.{}->{}", naming::INST_GLOBAL_MEMBER, symbol->get_name()); + } else { + return fmt::format("{}.{}", global_struct_instance(), symbol->get_name()); + } } @@ -391,6 +394,8 @@ void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_ini void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { + const auto value_initialize = print_initializers ? "{}" : ""; + /// TODO: Print only global variables printed in NEURON printer->add_newline(2); printer->add_line("/* NEURON global variables */"); @@ -405,6 +410,73 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in info.pointer_variables.size() > 0 ? static_cast(info.pointer_variables.size()) : -1); + + // Start printing the CNRN-style global variables. + auto float_type = default_float_data_type(); + printer->add_newline(2); + printer->add_line("/** all global variables */"); + printer->fmt_push_block("struct {}", global_struct()); + + if (!info.ions.empty()) { + // TODO implement these when needed. + } + + if (info.point_process) { + throw std::runtime_error("Not implemented, global point process."); + } + + if (!info.vectorize && !info.top_local_variables.empty()) { + throw std::runtime_error("Not implemented, global vectorize something."); + } + + if (!info.thread_variables.empty()) { + throw std::runtime_error("Not implemented, global thread variables."); + } + + if (info.table_count > 0) { + throw std::runtime_error("Not implemented, global table count."); + } + + for (const auto& var: info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + printer->fmt_line("{} {}{};", float_type, name, value_initialize); + codegen_global_variables.push_back(make_symbol(name)); + } + } + + for (const auto& var: info.global_variables) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->fmt_line("{} {}[{}] /* TODO init const-array */;", float_type, name, length); + } else { + double value{}; + if (auto const& value_ptr = var->get_value()) { + value = *value_ptr; + } + printer->fmt_line("{} {}{};", + float_type, + name, + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); + } + codegen_global_variables.push_back(var); + } + + + for (const auto& f: info.function_tables) { + throw std::runtime_error("Not implemented, global function tables."); + } + + if (info.vectorize && info.thread_data_index) { + throw std::runtime_error("Not implemented, global vectorize something else."); + } + + printer->pop_block(";"); + + print_global_var_struct_assertions(); + print_global_var_struct_decl(); } @@ -572,11 +644,11 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini } } - // printer->fmt_line("{}* {}{};", - // global_struct(), - // naming::INST_GLOBAL_MEMBER, - // print_initializers ? fmt::format("{{&{}}}", global_struct_instance()) - // : std::string{}); + printer->fmt_line("{}* {}{};", + global_struct(), + naming::INST_GLOBAL_MEMBER, + print_initializers ? fmt::format("{{&{}}}", global_struct_instance()) + : std::string{}); printer->pop_block(";"); } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 0f5894fb12..d9e93e19a6 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -197,6 +197,7 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { const double* ion_ena{}; double* ion_ina{}; double* ion_dinadv{}; + pas_test_Store* global{&pas_test_global}; };)"; REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected))); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 244dd822cd..f8bf1f71bc 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,4 +1,4 @@ -set(NMODL_USECASE_DIRS cnexp_scalar cnexp_array) +set(NMODL_USECASE_DIRS cnexp_scalar cnexp_array global_breakpoint) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod b/test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod new file mode 100644 index 0000000000..a78a93ce88 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod @@ -0,0 +1,20 @@ +NEURON { + SUFFIX leonhard + GLOBAL c +} + +PARAMETER { + c = 2.0 +} + +STATE { + x +} + +INITIAL { + x = 42 +} + +BREAKPOINT { + x = c +} diff --git a/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py b/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py new file mode 100644 index 0000000000..1aabec09d7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py @@ -0,0 +1,27 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("leonhard") +s.nseg = nseg + +x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 1.0 * ms +h.run() + +x = np.array(x_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +x_exact = 2.0 * np.ones_like(t) +x_exact[0] = 42; +abs_err = np.abs(x - x_exact) + +assert np.all(abs_err < 1e-12), f"{abs_err=}" +print("leonhard: success") From f00ff5de0210e25ccca15eab374aebab1992ef17 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Sat, 27 Jan 2024 11:12:54 +0100 Subject: [PATCH 571/871] Remove usage of "-DISABLE_OPENACC" for artificial cells (BlueBrain/nmodl#1133) * See neuronsimulator/nrn/pull/2653 for details/motivation * We now always use unified memory for all Random123 usage NMODL Repo SHA: BlueBrain/nmodl@bb4dfd2fbc5209bb1893d3c681157db743ca1dfc --- src/nmodl/codegen/codegen_acc_visitor.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 68ff009471..20624b6df7 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -62,17 +62,8 @@ void CodegenAccVisitor::print_atomic_reduction_pragma() { void CodegenAccVisitor::print_backend_includes() { - /** - * Artificial cells are executed on CPU. As Random123 is allocated on GPU by default, - * we have to disable GPU allocations using `DISABLE_OPENACC` macro. - */ - if (info.artificial_cell) { - printer->add_line("#undef DISABLE_OPENACC"); - printer->add_line("#define DISABLE_OPENACC"); - } else { - printer->add_line("#include "); - printer->add_line("#include "); - } + printer->add_line("#include "); + printer->add_line("#include "); } From 85005a033807d13df4142bb0987ef568a7b5be94 Mon Sep 17 00:00:00 2001 From: Nilesh Patra <37436956+nileshpatra@users.noreply.github.com> Date: Tue, 6 Feb 2024 15:35:28 +0530 Subject: [PATCH 572/871] Fixup bitshift issue on 32-bit (BlueBrain/nmodl#1143) NMODL Repo SHA: BlueBrain/nmodl@3e3f62af3d0320b44ee1ea5b3d2411e2a82ac3f4 --- src/nmodl/symtab/symbol_properties.hpp | 86 +++++++++++++------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index dad35d8b60..2458e9b2e9 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -55,25 +55,25 @@ enum class Status : enum_type { empty = 0, /// converted to local - localized = 1L << 0, + localized = 1LL << 0, /// converted to global - globalized = 1L << 1, + globalized = 1LL << 1, /// inlined - inlined = 1L << 2, + inlined = 1LL << 2, /// renamed - renamed = 1L << 3, + renamed = 1LL << 3, /// created - created = 1L << 4, + created = 1LL << 4, /// derived from state - from_state = 1L << 5, + from_state = 1LL << 5, /// variable marked as thread safe - thread_safe = 1L << 6 + thread_safe = 1LL << 6 }; /// usage of mod file as array or scalar @@ -88,10 +88,10 @@ enum class VariableType : enum_type { /// variable usage within a mod file enum class Access : enum_type { /// variable is ready only - read = 1L << 0, + read = 1LL << 0, /// variable is written only - write = 1L << 1 + write = 1LL << 1 }; @@ -118,106 +118,106 @@ enum class NmodlType : enum_type { empty = 0, /// Local Variable - local_var = 1L << 0, + local_var = 1LL << 0, /// Global Variable - global_var = 1L << 1, + global_var = 1LL << 1, /// Range Variable - range_var = 1L << 2, + range_var = 1LL << 2, /// Parameter Variable - param_assign = 1L << 3, + param_assign = 1LL << 3, /// Pointer Type - pointer_var = 1L << 4, + pointer_var = 1LL << 4, /// Bbcorepointer Type - bbcore_pointer_var = 1L << 5, + bbcore_pointer_var = 1LL << 5, /// Extern Type - extern_var = 1L << 6, + extern_var = 1LL << 6, /// Prime Type - prime_name = 1L << 7, + prime_name = 1LL << 7, /// Assigned Definition - assigned_definition = 1L << 8, + assigned_definition = 1LL << 8, /// Unit Def - unit_def = 1L << 9, + unit_def = 1LL << 9, /// Read Ion - read_ion_var = 1L << 10, + read_ion_var = 1LL << 10, /// Write Ion - write_ion_var = 1L << 11, + write_ion_var = 1LL << 11, /// Non Specific Current - nonspecific_cur_var = 1L << 12, + nonspecific_cur_var = 1LL << 12, /// Electrode Current - electrode_cur_var = 1L << 13, + electrode_cur_var = 1LL << 13, /// Argument Type - argument = 1L << 14, + argument = 1LL << 14, /// Function Type - function_block = 1L << 15, + function_block = 1LL << 15, /// Procedure Type - procedure_block = 1L << 16, + procedure_block = 1LL << 16, /// Derivative Block - derivative_block = 1L << 17, + derivative_block = 1LL << 17, /// Linear Block - linear_block = 1L << 18, + linear_block = 1LL << 18, /// NonLinear Block - non_linear_block = 1L << 19, + non_linear_block = 1LL << 19, /// constant variable - constant_var = 1L << 20, + constant_var = 1LL << 20, /// Kinetic Block - kinetic_block = 1L << 21, + kinetic_block = 1LL << 21, /// FunctionTable Block - function_table_block = 1L << 22, + function_table_block = 1LL << 22, /// factor in unit block - factor_def = 1L << 23, + factor_def = 1LL << 23, /// neuron variable accessible in mod file - extern_neuron_variable = 1L << 24, + extern_neuron_variable = 1LL << 24, /// neuron solver methods and math functions - extern_method = 1L << 25, + extern_method = 1LL << 25, /// state variable - state_var = 1L << 26, + state_var = 1LL << 26, /// need to solve : used in solve statement - to_solve = 1L << 27, + to_solve = 1LL << 27, /// ion type - useion = 1L << 28, + useion = 1LL << 28, /// variable is used in table statement - table_statement_var = 1L << 29, + table_statement_var = 1LL << 29, /// variable is used in table as assigned - table_assigned_var = 1L << 30, + table_assigned_var = 1LL << 30, /// Discrete Block - discrete_block = 1L << 31, + discrete_block = 1LL << 31, /// Define variable / macro - define = 1L << 32, + define = 1LL << 32, /// Codegen specific variable - codegen_var = 1L << 33 + codegen_var = 1LL << 33 }; template From 08aed9aaf4e16754f689c57938ad832d72a7ecd4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 7 Feb 2024 14:51:36 +0100 Subject: [PATCH 573/871] Fix instruction for building documentation. (BlueBrain/nmodl#1149) NMODL Repo SHA: BlueBrain/nmodl@9b8aa633e80fe889b591932269294cc3e6ac954f --- docs/nmodl/transpiler/INSTALL.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/nmodl/transpiler/INSTALL.rst b/docs/nmodl/transpiler/INSTALL.rst index 28098deab1..72fff92bde 100644 --- a/docs/nmodl/transpiler/INSTALL.rst +++ b/docs/nmodl/transpiler/INSTALL.rst @@ -224,4 +224,4 @@ You can build the entire documentation simply by using sphinx from .. code:: sh - python3 setup.py build_ext --inplace docs -G "Unix Makefiles" + python3 setup.py build_ext --inplace docs From 4eb7874e194de66667540694ce6021408bfc6d99 Mon Sep 17 00:00:00 2001 From: nrnhines Date: Thu, 8 Feb 2024 02:03:16 -0500 Subject: [PATCH 574/871] RANDOM construct: support for NEURON {RANDOM ranvar1, ranvar2, ...} statement (BlueBrain/nmodl#1125) * Support for new RANDOM construct in NMODL, see neuronsimulator/nrnBlueBrain/nmodl#2627 * Added necessary changes to the parser, visitors, symbol table and code generation * Added unit tests Co-authored-by: Pramod S Kumbhar NMODL Repo SHA: BlueBrain/nmodl@8f7eb99fd36ab886eac5c1ab050272fd2c46fa04 --- .../codegen_coreneuron_cpp_visitor.cpp | 23 +++++ .../codegen_coreneuron_cpp_visitor.hpp | 7 ++ src/nmodl/codegen/codegen_cpp_visitor.cpp | 38 +++++++- src/nmodl/codegen/codegen_helper_visitor.cpp | 6 ++ src/nmodl/codegen/codegen_info.hpp | 6 ++ src/nmodl/codegen/codegen_naming.hpp | 19 +++- src/nmodl/language/code_generator.cmake | 2 + src/nmodl/language/nmodl.yaml | 30 ++++++- src/nmodl/language/node_info.py | 1 + src/nmodl/lexer/nmodl_utils.cpp | 2 + src/nmodl/lexer/token_mapping.cpp | 1 + src/nmodl/parser/nmodl.yy | 24 +++++ src/nmodl/symtab/symbol_properties.cpp | 6 ++ src/nmodl/symtab/symbol_properties.hpp | 6 +- src/nmodl/visitors/perf_visitor.cpp | 10 +++ src/nmodl/visitors/perf_visitor.hpp | 3 + .../visitors/semantic_analysis_visitor.cpp | 89 +++++++++++++++++++ .../visitors/semantic_analysis_visitor.hpp | 11 ++- src/nmodl/visitors/visitor_utils.cpp | 5 ++ src/nmodl/visitors/visitor_utils.hpp | 3 + .../transpiler/unit/modtoken/modtoken.cpp | 6 +- .../unit/utils/nmodl_constructs.cpp | 2 + .../unit/visitor/semantic_analysis.cpp | 66 ++++++++++++++ 23 files changed, 354 insertions(+), 12 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 08ec11a658..bf257390ee 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -1259,6 +1259,15 @@ void CodegenCoreneuronCppVisitor::print_first_pointer_var_index_getter() { } +void CodegenCoreneuronCppVisitor::print_first_random_var_index_getter() { + printer->add_newline(2); + print_device_method_annotation(); + printer->push_block("static inline int first_random_var_index()"); + printer->fmt_line("return {};", info.first_random_var_index); + printer->pop_block(); +} + + void CodegenCoreneuronCppVisitor::print_num_variable_getter() { printer->add_newline(2); print_device_method_annotation(); @@ -2293,6 +2302,19 @@ void CodegenCoreneuronCppVisitor::print_instance_variable_setup() { printer->fmt_push_block("static void {}(NrnThread* nt, Memb_list* ml, int type)", method_name(naming::NRN_PRIVATE_DESTRUCTOR_METHOD)); cast_inst_and_assert_validity(); + + // delete random streams + if (info.random_variables.size()) { + printer->add_line("int pnodecount = ml->_nodecount_padded;"); + printer->add_line("int nodecount = ml->nodecount;"); + printer->add_line("Datum* indexes = ml->pdata;"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + for (const auto& var: info.random_variables) { + const auto& name = get_variable_name(var->get_name()); + printer->fmt_line("nrnran123_deletestream((nrnran123_State*){});", name); + } + printer->pop_block(); + } print_instance_struct_delete_from_device(); printer->add_multi_line(R"CODE( delete inst; @@ -3561,6 +3583,7 @@ void CodegenCoreneuronCppVisitor::print_namespace_end() { void CodegenCoreneuronCppVisitor::print_common_getters() { print_first_pointer_var_index_getter(); + print_first_random_var_index_getter(); print_net_receive_arg_size_getter(); print_thread_getters(); print_num_variable_getter(); diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index f09c5bf9c2..88c1142b20 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -605,6 +605,13 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_first_pointer_var_index_getter(); + /** + * Print the getter method for index position of first RANDOM variable + * + */ + void print_first_random_var_index_getter(); + + /** * Print the getter methods for float and integer variables count * diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index b148e0f189..93b009ee15 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -10,6 +10,7 @@ #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_utils.hpp" #include "visitors/rename_visitor.hpp" +#include "visitors/visitor_utils.hpp" namespace nmodl { namespace codegen { @@ -145,7 +146,6 @@ bool CodegenCppVisitor::defined_method(const std::string& name) const { return function && function->has_any_property(properties); } - int CodegenCppVisitor::float_variables_size() const { return codegen_float_variables.size(); } @@ -235,7 +235,20 @@ void CodegenCppVisitor::print_global_var_struct_decl() { void CodegenCppVisitor::print_function_call(const FunctionCall& node) { const auto& name = node.get_node_name(); - auto function_name = name; + + // return C++ function name for RANDOM construct function + // e.g. nrnran123_negexp for random_negexp + auto get_renamed_random_function = + [&](const std::string& name) -> std::pair { + if (codegen::naming::RANDOM_FUNCTIONS_MAPPING.count(name)) { + return {codegen::naming::RANDOM_FUNCTIONS_MAPPING[name], true}; + } + return {name, false}; + }; + std::string function_name; + bool is_random_function; + std::tie(function_name, is_random_function) = get_renamed_random_function(name); + if (defined_method(name)) { function_name = method_name(name); } @@ -265,6 +278,12 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { } } + // first argument to random functions need to be type casted + // from void* to nrnran123_State*. + if (is_random_function && !arguments.empty()) { + printer->add_text("(nrnran123_State*)"); + } + print_vector_elements(arguments, ", "); printer->add_text(')'); } @@ -684,6 +703,15 @@ void CodegenCppVisitor::update_index_semantics() { index += size; } + for (auto& var: info.random_variables) { + if (info.first_random_var_index == -1) { + info.first_random_var_index = index; + } + int size = var->get_length(); + info.semantics.emplace_back(index, naming::RANDOM_SEMANTIC, size); + index += size; + } + if (info.diam_used) { info.semantics.emplace_back(index++, naming::DIAM_VARIABLE, 1); } @@ -863,6 +891,12 @@ std::vector CodegenCppVisitor::get_int_variables() { } } + for (const auto& var: info.random_variables) { + auto name = var->get_name(); + variables.emplace_back(make_symbol(name), true); + variables.back().symbol->add_properties(NmodlType::random_var); + } + if (info.diam_used) { variables.emplace_back(make_symbol(naming::DIAM_VARIABLE)); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index fa9e4438b6..72dc2508f2 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -269,6 +269,12 @@ void CodegenHelperVisitor::find_non_range_variables() { // clang-format on info.pointer_variables = psymtab->get_variables_with_properties(properties); + /// find RANDOM variables + // clang-format off + properties = NmodlType::random_var; + // clang-format on + info.random_variables = psymtab->get_variables_with_properties(properties); + // find special variables like diam, area // clang-format off properties = NmodlType::assigned_definition diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 6627c05776..b9a542abdb 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -324,9 +324,15 @@ struct CodegenInfo { /// pointer or bbcore pointer variables std::vector pointer_variables; + /// RANDOM variables + std::vector random_variables; + /// index/offset for first pointer variable if exist int first_pointer_var_index = -1; + /// index/offset for first RANDOM variable if exist + int first_random_var_index = -1; + /// tqitem index in integer variables /// note that if tqitem doesn't exist then the default value should be 0 int tqitem_index = 0; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index e8240b7df2..78f2b25ead 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -7,8 +7,8 @@ #pragma once -#include #include +#include namespace nmodl { @@ -119,6 +119,9 @@ static constexpr char POINTER_SEMANTIC[] = "pointer"; /// semantic type for core pointer variable static constexpr char CORE_POINTER_SEMANTIC[] = "bbcorepointer"; +/// semantic type for RANDOM variable +static constexpr char RANDOM_SEMANTIC[] = "random"; + /// semantic type for net send call static constexpr char NET_SEND_SEMANTIC[] = "netsend"; @@ -174,7 +177,7 @@ static constexpr char NRN_POINTERINDEX[] = "hoc_nrnpointerindex"; /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends // clang-format off - const std::map VERBATIM_VARIABLES_MAPPING{ + static const std::unordered_map VERBATIM_VARIABLES_MAPPING{ {"_nt", "nt"}, {"_p", "data"}, {"_ppvar", "indexes"}, @@ -183,8 +186,18 @@ static constexpr char NRN_POINTERINDEX[] = "hoc_nrnpointerindex"; {"_cntml_padded", "pnodecount"}, {"_cntml", "nodecount"}, {"_tqitem", "tqitem"}}; -// clang-format on + // Functions available in NMODL with RANDOM construct and their mapping to + // C++ functions for Random123 interface. + static std::unordered_map RANDOM_FUNCTIONS_MAPPING{ + {"random_setseq", "nrnran123_setseq"}, + {"random_setids", "nrnran123_setids"}, + {"random_uniform", "nrnran123_uniform"}, + {"random_negexp", "nrnran123_negexp"}, + {"random_normal", "nrnran123_normal"}, + {"random_ipick", "nrnran123_ipick"}, + {"random_dpick", "nrnran123_dblpick"}}; +// clang-format on } // namespace naming } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 9d9705edc1..90705ac3df 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -139,6 +139,8 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/procedure_block.hpp ${PROJECT_BINARY_DIR}/src/ast/program.hpp ${PROJECT_BINARY_DIR}/src/ast/protect_statement.hpp + ${PROJECT_BINARY_DIR}/src/ast/random_var.hpp + ${PROJECT_BINARY_DIR}/src/ast/random_var_list.hpp ${PROJECT_BINARY_DIR}/src/ast/range.hpp ${PROJECT_BINARY_DIR}/src/ast/range_var.hpp ${PROJECT_BINARY_DIR}/src/ast/react_var_name.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index e2686fd9f8..60919df1b1 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -394,6 +394,14 @@ type: Name node_name: true + - RandomVar: + brief: "Single variable of type RANDOM. pointer to a nrnran123_State" + members: + - name: + brief: "Name of the a RANDOM variable" + type: Name + node_name: true + - BbcorePointerVar: members: - name: @@ -415,7 +423,7 @@ - Block: brief: "Base class for all block scoped nodes" description: | - NMODL has different local and global block scoped nodes like + NMODL has different local and globals block scoped nodes like ast::NeuronBlock, ast::ParamBlock, ast::IfStatement etc. Ast::Block is base class and defines common interface for these nodes. @@ -1731,6 +1739,25 @@ separator: ", " brief: "Represents GLOBAL statement in NMODL" + - RandomVarList: + brief: "Represents RANDOM statement in NMODL" + nmodl: "RANDOM " + members: + - variables: + brief: "Vector of random variables" + type: RandomVar + vector: true + separator: ", " + description: | + Here is an example of RANDOM statement + + \code{.mod} + NEURON { + THREADSAFE + POINT_PROCESS NetStim + RANDOM ranvar + \endcode + - Pointer: nmodl: "POINTER " members: @@ -1738,6 +1765,7 @@ brief: "Vector of pointer variables" type: PointerVar vector: true + add: true separator: ", " brief: "Represents POINTER statement in NMODL" diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index 466895b3f9..ff6d72faf5 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -66,6 +66,7 @@ "GlobalVar", "PointerVar", "BbcorePointerVar", + "RandomVar", "ExternVar", "PrimeName", "ConstantVar", diff --git a/src/nmodl/lexer/nmodl_utils.cpp b/src/nmodl/lexer/nmodl_utils.cpp index 6ba9bffd09..7b6941cc06 100644 --- a/src/nmodl/lexer/nmodl_utils.cpp +++ b/src/nmodl/lexer/nmodl_utils.cpp @@ -233,6 +233,8 @@ SymbolType token_symbol(const std::string& key, PositionType& pos, TokenType typ return Parser::make_PROCEDURE(token, pos); case Token::PROTECT: return Parser::make_PROTECT(token, pos); + case Token::RANDOM: + return Parser::make_RANDOM(token, pos); case Token::RANGE: return Parser::make_RANGE(token, pos); case Token::READ: diff --git a/src/nmodl/lexer/token_mapping.cpp b/src/nmodl/lexer/token_mapping.cpp index c3c0fb116f..734d965837 100644 --- a/src/nmodl/lexer/token_mapping.cpp +++ b/src/nmodl/lexer/token_mapping.cpp @@ -97,6 +97,7 @@ const static std::map keywords = { {"CHARGE", Token::VALENCE}, {"GLOBAL", Token::GLOBAL}, {"POINTER", Token::POINTER}, + {"RANDOM", Token::RANDOM}, {"BBCOREPOINTER", Token::BBCOREPOINTER}, {"EXTERNAL", Token::EXTERNAL}, {"INCLUDE", Token::INCLUDE1}, diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 8d9b7bd5fb..c07bd961a3 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -127,6 +127,7 @@ %token PROCEDURE %token PROTECT %token RANGE +%token RANDOM %token REACT1 %token REACTION %token READ @@ -293,6 +294,7 @@ %type global_var_list %type pointer_var_list %type bbcore_pointer_var_list +%type random_var_list %type external_var_list %type valence %type initial_statement @@ -1987,6 +1989,11 @@ neuron_statement : $1.emplace_back(new ast::BbcorePointer($3)); $$ = $1; } + | neuron_statement RANDOM random_var_list + { + $1.emplace_back(new ast::RandomVarList($3)); + $$ = $1; + } | neuron_statement EXTERNAL external_var_list { $1.emplace_back(new ast::External($3)); @@ -2195,6 +2202,23 @@ bbcore_pointer_var_list : NAME_PTR ; +random_var_list : NAME_PTR + { + $$ = ast::RandomVarVector(); + $$.emplace_back(new ast::RandomVar($1)); + } + | random_var_list "," NAME_PTR + { + $1.emplace_back(new ast::RandomVar($3)); + $$ = $1; + } + | error + { + error(scanner.loc, "random_var_list"); + } + ; + + external_var_list : NAME_PTR { $$ = ast::ExternVarVector(); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 8782332ee2..95123ef76c 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -6,8 +6,10 @@ */ #include +#include #include +#include "codegen/codegen_naming.hpp" #include "symtab/symbol_properties.hpp" #include "utils/string_utils.hpp" @@ -159,6 +161,10 @@ std::vector to_string_vector(const NmodlType& obj) { properties.emplace_back("codegen_var"); } + if (has_property(obj, NmodlType::random_var)) { + properties.emplace_back("random_var"); + } + return properties; } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 2458e9b2e9..6bf2e0fa04 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -217,7 +217,10 @@ enum class NmodlType : enum_type { define = 1LL << 32, /// Codegen specific variable - codegen_var = 1LL << 33 + codegen_var = 1LL << 33, + + /// Randomvar Type + random_var = 1LL << 34 }; template @@ -250,7 +253,6 @@ inline T& operator&=(T& lhs, T rhs) { return lhs; } - /// check if any property is set inline bool has_property(const NmodlType& obj, NmodlType property) { return static_cast(obj & property); diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 078c8300e0..4e83faa0fc 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -256,6 +256,11 @@ void PerfVisitor::count_variables() { variables = current_symtab->get_variables_with_properties(property); num_pointer_variables = static_cast(variables.size()); + /// RANDOM variables have NmodlType::random_var + property = NmodlType::random_var; + variables = current_symtab->get_variables_with_properties(property); + num_random_variables = static_cast(variables.size()); + /// number of global variables : parameters and pointers could appear also /// as range variables and hence need to filter out. But if anything declared @@ -293,6 +298,7 @@ void PerfVisitor::print_memory_usage() { stream << " STATE : " << num_state_variables; stream << " POINTER : " << num_pointer_variables << std::endl; + stream << " RANDOM : " << num_random_variables << std::endl; if (printer) { printer->push_block("MemoryInfo"); @@ -317,6 +323,10 @@ void PerfVisitor::print_memory_usage() { printer->add_node(std::to_string(num_pointer_variables), "total"); printer->pop_block(); + printer->push_block("RANDOM"); + printer->add_node(std::to_string(num_random_variables), "total"); + printer->pop_block(); + printer->pop_block(); } } diff --git a/src/nmodl/visitors/perf_visitor.hpp b/src/nmodl/visitors/perf_visitor.hpp index 7fa0c01d3f..a503391971 100644 --- a/src/nmodl/visitors/perf_visitor.hpp +++ b/src/nmodl/visitors/perf_visitor.hpp @@ -119,6 +119,9 @@ class PerfVisitor: public ConstAstVisitor { /// count of pointer / bbcorepointer variables int num_pointer_variables = 0; + /// count of RANDOM variables + int num_random_variables = 0; + /// keys used in map to track var usage std::string const_memr_key = "cm_r_u"; std::string const_memw_key = "cm_w_u"; diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 22220f3b8d..ff83a36ead 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -8,10 +8,12 @@ #include "visitors/semantic_analysis_visitor.hpp" #include "ast/breakpoint_block.hpp" #include "ast/function_block.hpp" +#include "ast/function_call.hpp" #include "ast/function_table_block.hpp" #include "ast/independent_block.hpp" #include "ast/procedure_block.hpp" #include "ast/program.hpp" +#include "ast/statement_block.hpp" #include "ast/string.hpp" #include "ast/suffix.hpp" #include "ast/table_statement.hpp" @@ -24,6 +26,7 @@ namespace visitor { bool SemanticAnalysisVisitor::check(const ast::Program& node) { check_fail = false; + program_symtab = node.get_symbol_table(); /// <-- This code is for check 2 const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX}); @@ -89,6 +92,92 @@ void SemanticAnalysisVisitor::visit_function_block(const ast::FunctionBlock& nod /// --> } +void SemanticAnalysisVisitor::visit_name(const ast::Name& node) { + /// <-- This code is a portion of check 9 + // There are only two contexts where a random_var is allowed. As the first arg of a random + // function or as an item in the RANDOM declaration. + // Only the former needs checking. + bool ok = true; + auto name = node.get_node_name(); + + // only check for variables exist in the symbol table (e.g. SUFFIX has type Name but it's not + // variable) + // if variable is not RANDOM then nothing to check for it + auto symbol = program_symtab->lookup(name); + if (!symbol || !symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) { + return; + } + + auto parent = node.get_parent(); + + // if it's RANDOM var declaration in NEURON block then nothing to do + if (parent && parent->is_random_var()) { + return; + } + + if (parent && parent->is_var_name()) { + parent = parent->get_parent(); + if (parent && parent->is_function_call()) { + auto fname = parent->get_node_name(); + // if function is a random function then check if the current + // name is the function's first argument + if (is_random_construct_function(fname)) { + auto rfun = dynamic_cast(parent); + const auto& arguments = rfun->get_arguments(); + if (!arguments.empty() && arguments.front()->is_var_name() && + arguments.front()->get_node_name() == name) { + // if this is a first argument to function then there + // is no problem + node.visit_children(*this); + return; + } + } + } + } + + // Otherwise, we have an error + auto position = node.get_token()->position(); + logger->critical( + fmt::format("SemanticAnalysisVisitor :: RANDOM variable {} at {}" + " can be used only as the first arg of a random function", + node.get_node_name(), + position)); + check_fail = true; + + node.visit_children(*this); + /// --> +} + +void SemanticAnalysisVisitor::visit_function_call(const ast::FunctionCall& node) { + /// <-- This code is a portion of check 9 + // The first arg of a RANDOM function must be a random_var + // Otherwise it's an error + auto fname = node.get_node_name(); + if (is_random_construct_function(fname)) { + const auto& arguments = node.get_arguments(); + if (!arguments.empty()) { + auto arg0 = arguments.front(); + if (arg0->is_var_name()) { + auto name = arg0->get_node_name(); + auto symbol = program_symtab->lookup(name); + if (symbol->has_any_property(symtab::syminfo::NmodlType::random_var)) { + node.visit_children(*this); + return; + } + } + } + auto position = node.get_name()->get_token()->position(); + logger->critical( + fmt::format("SemanticAnalysisVisitor :: random function {} at {} :: The first arg must " + "be a random variable", + fname, + position)); + check_fail = true; + } + node.visit_children(*this); + /// --> +} + void SemanticAnalysisVisitor::visit_table_statement(const ast::TableStatement& tableStmt) { /// <-- This code is for check 1 if ((in_function || in_procedure) && !one_arg_in_procedure_function) { diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 1f9c2b7e98..93c0958cef 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -32,6 +32,7 @@ * 6. Check that mutex are not badly use * 7. Check than function table got at least one argument. * 8. Check that at most one derivative block is present. + * 9. Check that RANDOM variable is mentioned only as first arg in random function. */ #include "ast/ast.hpp" #include "visitors/ast_visitor.hpp" @@ -41,8 +42,10 @@ namespace visitor { class SemanticAnalysisVisitor: public ConstAstVisitor { private: + // if semantic analysis check has failed bool check_fail = false; - + // symbol table for the program + symtab::SymbolTable* program_symtab = nullptr; /// true if accelerator backend is used for code generation bool accel_backend = false; /// true if the procedure or the function contains only one argument @@ -86,6 +89,12 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// Look if MUTEXUNLOCK is outside a locked block void visit_mutex_unlock(const ast::MutexUnlock& node) override; + /// Only use of random_var is as first arg in random function. + void visit_name(const ast::Name& node) override; + + /// random function first arg must be random_var + void visit_function_call(const ast::FunctionCall& node) override; + public: SemanticAnalysisVisitor(bool accel_backend = false) : accel_backend(accel_backend) {} diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 7044a88a66..8bc49703c9 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -12,6 +12,7 @@ #include #include "ast/all.hpp" +#include "codegen/codegen_naming.hpp" #include "parser/nmodl_driver.hpp" #include "utils/string_utils.hpp" #include "visitors/json_visitor.hpp" @@ -281,4 +282,8 @@ std::string get_full_var_name(const ast::VarName& node) { return full_var_name; } +bool is_random_construct_function(const std::string& name) { + return codegen::naming::RANDOM_FUNCTIONS_MAPPING.count(name) != 0; +} + } // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index a817ad0123..9e7163fdb0 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -139,4 +139,7 @@ std::string get_indexed_name(const ast::IndexedName& node); /// Given a VarName node, return the full var name including index std::string get_full_var_name(const ast::VarName& node); +/// Is given name a one of the function for RANDOM construct +bool is_random_construct_function(const std::string& name); + } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index ceaa025510..d473b3307b 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -47,14 +47,14 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " text at [1.1-4] type 342"); + REQUIRE(ss.str() == " text at [1.1-4] type 343"); } { std::stringstream ss; symbol_type(" some_text", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " some_text at [1.3-11] type 342"); + REQUIRE(ss.str() == " some_text at [1.3-11] type 343"); } } @@ -64,7 +64,7 @@ TEST_CASE("NMODL Lexer returning valid ModToken object", "[token][modtoken]") { std::stringstream ss; symbol_type("h'' = ", value); ss << *(value.get_token()); - REQUIRE(ss.str() == " h'' at [1.1-3] type 349"); + REQUIRE(ss.str() == " h'' at [1.1-3] type 350"); REQUIRE(value.get_order()->eval() == 2); } } diff --git a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp index 4faa30d843..8079fb5e47 100644 --- a/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +++ b/test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp @@ -478,6 +478,8 @@ std::map const nmodl_valid_constructs{ POINTER rng1, rng2 BBCOREPOINTER rng3 EXTERNAL extvar + RANDOM r1 + RANDOM r2, r3 THREADSAFE } )" diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index df3a6f4452..210b5ec63c 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -192,3 +192,69 @@ SCENARIO("At most one DERIVATIVE block", "[visitor][semantic_analysis]") { } } } + +SCENARIO("RANDOM Construct", "[visitor][semantic_analysis]") { + GIVEN("A mod file with correct RANDOM variable usage") { + std::string nmodl_text = R"( + NEURON { + RANDOM r + } + PROCEDURE rates() { + LOCAL x + random_setseq(r, 1) + x = 1 + random_negexp(r) + x = x + exp(random_negexp(r)) + } + FUNCTION erand() { + erand = random_negexp(r) + } + )"; + THEN("Semantic analysis should pass") { + REQUIRE_FALSE(run_semantic_analysis_visitor(nmodl_text)); + } + } + + GIVEN("A mod file with incorrect usage of RANDOM variable as function arguments") { + std::string nmodl_text = R"( + NEURON { + RANDOM r + } + PROCEDURE rates() { + random_setseq(1, r) + } + )"; + THEN("Semantic analysis should faial") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } + + GIVEN("A mod file with incorrect usage of RANDOM variable in an expression") { + std::string nmodl_text = R"( + NEURON { + RANDOM r + } + PROCEDURE rates() { + LOCAL x + x = r + 1 + } + )"; + THEN("Semantic analysis should fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } + + GIVEN("A mod file with incorrect usage of RANDOM variable in non-random function") { + std::string nmodl_text = R"( + NEURON { + RANDOM r + } + PROCEDURE rates() { + LOCAL x + x = exp(r) + 1 + } + )"; + THEN("Semantic analysis should fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From 12bce5ea7a93737435b9a792b8ed2d1cdc337dde Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 8 Feb 2024 11:03:35 +0100 Subject: [PATCH 575/871] Move ion-related code. (BlueBrain/nmodl#1148) This commits move various ion-related code from the CoreNEURON C++ printer to the common base of both C++ printers. There are some small changes whenever stubbing is needed. NMODL Repo SHA: BlueBrain/nmodl@9cb6d978423141ae36bfd059d643c7fd39f4a1f9 --- .../codegen_coreneuron_cpp_visitor.cpp | 191 ------------------ .../codegen_coreneuron_cpp_visitor.hpp | 85 +------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 189 +++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 101 +++++++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 13 ++ .../codegen/codegen_neuron_cpp_visitor.hpp | 8 + 6 files changed, 313 insertions(+), 274 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index bf257390ee..ed919fa887 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -87,140 +87,6 @@ int CodegenCoreneuronCppVisitor::position_of_int_var(const std::string& name) co } -/** - * \details Current variable used in breakpoint block could be local variable. - * In this case, neuron has already renamed the variable name by prepending - * "_l". In our implementation, the variable could have been renamed by - * one of the pass. And hence, we search all local variables and check if - * the variable is renamed. Note that we have to look into the symbol table - * of statement block and not breakpoint. - */ -std::string CodegenCoreneuronCppVisitor::breakpoint_current(std::string current) const { - auto breakpoint = info.breakpoint_node; - if (breakpoint == nullptr) { - return current; - } - auto symtab = breakpoint->get_statement_block()->get_symbol_table(); - auto variables = symtab->get_variables_with_properties(NmodlType::local_var); - for (const auto& var: variables) { - auto renamed_name = var->get_name(); - auto original_name = var->get_original_name(); - if (current == original_name) { - current = renamed_name; - break; - } - } - return current; -} - - -/** - * \details Depending upon the block type, we have to print read/write ion variables - * during code generation. Depending on block/procedure being printed, this - * method return statements as vector. As different code backends could have - * different variable names, we rely on backend-specific read_ion_variable_name - * and write_ion_variable_name method which will be overloaded. - */ -std::vector CodegenCoreneuronCppVisitor::ion_read_statements(BlockType type) const { - if (optimize_ion_variable_copies()) { - return ion_read_statements_optimized(type); - } - std::vector statements; - for (const auto& ion: info.ions) { - auto name = ion.name; - for (const auto& var: ion.reads) { - auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var); - if (iter != ion.implicit_reads.end()) { - continue; - } - auto variable_names = read_ion_variable_name(var); - auto first = get_variable_name(variable_names.first); - auto second = get_variable_name(variable_names.second); - statements.push_back(fmt::format("{} = {};", first, second)); - } - for (const auto& var: ion.writes) { - if (ion.is_ionic_conc(var)) { - auto variables = read_ion_variable_name(var); - auto first = get_variable_name(variables.first); - auto second = get_variable_name(variables.second); - statements.push_back(fmt::format("{} = {};", first, second)); - } - } - } - return statements; -} - - -std::vector CodegenCoreneuronCppVisitor::ion_read_statements_optimized( - BlockType type) const { - std::vector statements; - for (const auto& ion: info.ions) { - for (const auto& var: ion.writes) { - if (ion.is_ionic_conc(var)) { - auto variables = read_ion_variable_name(var); - auto first = "ionvar." + variables.first; - const auto& second = get_variable_name(variables.second); - statements.push_back(fmt::format("{} = {};", first, second)); - } - } - } - return statements; -} - -// NOLINTNEXTLINE(readability-function-cognitive-complexity) -std::vector CodegenCoreneuronCppVisitor::ion_write_statements(BlockType type) { - std::vector statements; - for (const auto& ion: info.ions) { - std::string concentration; - auto name = ion.name; - for (const auto& var: ion.writes) { - auto variable_names = write_ion_variable_name(var); - if (ion.is_ionic_current(var)) { - if (type == BlockType::Equation) { - auto current = breakpoint_current(var); - auto lhs = variable_names.first; - auto op = "+="; - auto rhs = get_variable_name(current); - if (info.point_process) { - auto area = get_variable_name(naming::NODE_AREA_VARIABLE); - rhs += fmt::format("*(1.e2/{})", area); - } - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - } - } else { - if (!ion.is_rev_potential(var)) { - concentration = var; - } - auto lhs = variable_names.first; - auto op = "="; - auto rhs = get_variable_name(variable_names.second); - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - } - } - - if (type == BlockType::Initial && !concentration.empty()) { - int index = 0; - if (ion.is_intra_cell_conc(concentration)) { - index = 1; - } else if (ion.is_extra_cell_conc(concentration)) { - index = 2; - } else { - /// \todo Unhandled case in neuron implementation - throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); - } - auto ion_type_name = fmt::format("{}_type", ion.name); - auto lhs = fmt::format("int {}", ion_type_name); - auto op = "="; - auto rhs = get_variable_name(ion_type_name); - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - auto statement = conc_write_statement(ion.name, concentration, index); - statements.push_back(ShadowUseStatement{statement, "", ""}); - } - } - return statements; -} - - /** * \details Often top level verbatim blocks use variables with old names. * Here we process if we are processing verbatim block at global scope. @@ -256,11 +122,6 @@ std::string CodegenCoreneuronCppVisitor::process_verbatim_token(const std::strin } -bool CodegenCoreneuronCppVisitor::ion_variable_struct_required() const { - return optimize_ion_variable_copies() && info.ion_has_write_variable(); -} - - /** * \details This can be override in the backend. For example, parameters can be constant * except in INITIAL block where they are set to 0. As initial block is/can be @@ -1189,18 +1050,6 @@ std::string CodegenCoreneuronCppVisitor::register_mechanism_arguments() const { } -std::pair CodegenCoreneuronCppVisitor::read_ion_variable_name( - const std::string& name) { - return {name, naming::ION_VARNAME_PREFIX + name}; -} - - -std::pair CodegenCoreneuronCppVisitor::write_ion_variable_name( - const std::string& name) { - return {naming::ION_VARNAME_PREFIX + name, name}; -} - - std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& ion_name, const std::string& concentration, int index) { @@ -1223,28 +1072,6 @@ std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& } -/** - * If mechanisms dependency level execution is enabled then certain updates - * like ionic current contributions needs to be atomically updated. In this - * case we first update current mechanism's shadow vector and then add statement - * to queue that will be used in reduction queue. - */ -std::string CodegenCoreneuronCppVisitor::process_shadow_update_statement( - const ShadowUseStatement& statement, - BlockType /* type */) { - // when there is no operator or rhs then that statement doesn't need shadow update - if (statement.op.empty() && statement.rhs.empty()) { - auto text = statement.lhs + ";"; - return text; - } - - // return regular statement - auto lhs = get_variable_name(statement.lhs); - auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs); - return text; -} - - /****************************************************************************************/ /* Code-specific printing routines for code generation */ /****************************************************************************************/ @@ -1444,24 +1271,6 @@ std::string CodegenCoreneuronCppVisitor::global_variable_name(const SymbolType& } -std::string CodegenCoreneuronCppVisitor::update_if_ion_variable_name( - const std::string& name) const { - std::string result(name); - if (ion_variable_struct_required()) { - if (info.is_ion_read_variable(name)) { - result = naming::ION_VARNAME_PREFIX + name; - } - if (info.is_ion_write_variable(name)) { - result = "ionvar." + name; - } - if (info.is_current(name)) { - result = "ionvar." + name; - } - } - return result; -} - - std::string CodegenCoreneuronCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { const std::string& varname = update_if_ion_variable_name(name); diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 88c1142b20..9c43a4cbe0 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -112,42 +112,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { int position_of_int_var(const std::string& name) const override; - /** - * Determine the variable name for the "current" used in breakpoint block taking into account - * intermediate code transformations. - * \param current The variable name for the current used in the model - * \return The name for the current to be printed in C++ - */ - std::string breakpoint_current(std::string current) const; - - - /** - * For a given output block type, return statements for all read ion variables - * - * \param type The type of code block being generated - * \return A \c vector of strings representing the reading of ion variables - */ - std::vector ion_read_statements(BlockType type) const; - - - /** - * For a given output block type, return minimal statements for all read ion variables - * - * \param type The type of code block being generated - * \return A \c vector of strings representing the reading of ion variables - */ - std::vector ion_read_statements_optimized(BlockType type) const; - - - /** - * For a given output block type, return statements for writing back ion variables - * - * \param type The type of code block being generated - * \return A \c vector of strings representing the write-back of ion variables - */ - std::vector ion_write_statements(BlockType type); - - /** * Process a token in a verbatim block for possible variable renaming * \param token The verbatim token to be processed @@ -156,13 +120,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { std::string process_verbatim_token(const std::string& token); - /** - * Check if a structure for ion variables is required - * \return \c true if a structure fot ion variables must be generated - */ - bool ion_variable_struct_required() const; - - /** * Check if variable is qualified as constant * \param name The name of variable @@ -331,7 +288,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /** * Check if ion variable copies should be avoided */ - bool optimize_ion_variable_copies() const; + bool optimize_ion_variable_copies() const override; /** @@ -552,22 +509,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { std::string register_mechanism_arguments() const override; - /** - * Return ion variable name and corresponding ion read variable name - * \param name The ion variable name - * \return The ion read variable name - */ - static std::pair read_ion_variable_name(const std::string& name); - - - /** - * Return ion variable name and corresponding ion write variable name - * \param name The ion variable name - * \return The ion write variable name - */ - static std::pair write_ion_variable_name(const std::string& name); - - /** * Generate Function call statement for nrn_wrote_conc * \param ion_name The name of the ion variable @@ -577,21 +518,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { */ std::string conc_write_statement(const std::string& ion_name, const std::string& concentration, - int index); - - /** - * Process shadow update statement - * - * If the statement requires reduction then add it to vector of reduction statement and return - * statement using shadow update - * - * \param statement The statement that might require shadow updates - * \param type The target backend code block type - * \return The generated target backend code - */ - std::string process_shadow_update_statement(const ShadowUseStatement& statement, - BlockType type); - + int index) override; /****************************************************************************************/ /* Code-specific printing routines for code generations */ @@ -664,14 +591,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Determine the updated name if the ion variable has been optimized - * \param name The ion variable name - * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena) - */ - std::string update_if_ion_variable_name(const std::string& name) const; - - /** * Determine the name of a \c float variable given its symbol * diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 93b009ee15..9ae481fa8b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -26,6 +26,10 @@ using symtab::syminfo::NmodlType; /* Common helper routines accross codegen functions */ /****************************************************************************************/ +bool CodegenCppVisitor::ion_variable_struct_required() const { + return optimize_ion_variable_copies() && info.ion_has_write_variable(); +} + std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) { std::string str; @@ -209,6 +213,191 @@ bool CodegenCppVisitor::need_semicolon(const Statement& node) { return true; } +/** + * \details Depending upon the block type, we have to print read/write ion variables + * during code generation. Depending on block/procedure being printed, this + * method return statements as vector. As different code backends could have + * different variable names, we rely on backend-specific read_ion_variable_name + * and write_ion_variable_name method which will be overloaded. + */ +std::vector CodegenCppVisitor::ion_read_statements(BlockType type) const { + if (optimize_ion_variable_copies()) { + return ion_read_statements_optimized(type); + } + std::vector statements; + for (const auto& ion: info.ions) { + auto name = ion.name; + for (const auto& var: ion.reads) { + auto const iter = std::find(ion.implicit_reads.begin(), ion.implicit_reads.end(), var); + if (iter != ion.implicit_reads.end()) { + continue; + } + auto variable_names = read_ion_variable_name(var); + auto first = get_variable_name(variable_names.first); + auto second = get_variable_name(variable_names.second); + statements.push_back(fmt::format("{} = {};", first, second)); + } + for (const auto& var: ion.writes) { + if (ion.is_ionic_conc(var)) { + auto variables = read_ion_variable_name(var); + auto first = get_variable_name(variables.first); + auto second = get_variable_name(variables.second); + statements.push_back(fmt::format("{} = {};", first, second)); + } + } + } + return statements; +} + + +std::vector CodegenCppVisitor::ion_read_statements_optimized(BlockType type) const { + std::vector statements; + for (const auto& ion: info.ions) { + for (const auto& var: ion.writes) { + if (ion.is_ionic_conc(var)) { + auto variables = read_ion_variable_name(var); + auto first = "ionvar." + variables.first; + const auto& second = get_variable_name(variables.second); + statements.push_back(fmt::format("{} = {};", first, second)); + } + } + } + return statements; +} + +// NOLINTNEXTLINE(readability-function-cognitive-complexity) +std::vector CodegenCppVisitor::ion_write_statements(BlockType type) { + std::vector statements; + for (const auto& ion: info.ions) { + std::string concentration; + auto name = ion.name; + for (const auto& var: ion.writes) { + auto variable_names = write_ion_variable_name(var); + if (ion.is_ionic_current(var)) { + if (type == BlockType::Equation) { + auto current = breakpoint_current(var); + auto lhs = variable_names.first; + auto op = "+="; + auto rhs = get_variable_name(current); + if (info.point_process) { + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); + rhs += fmt::format("*(1.e2/{})", area); + } + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + } + } else { + if (!ion.is_rev_potential(var)) { + concentration = var; + } + auto lhs = variable_names.first; + auto op = "="; + auto rhs = get_variable_name(variable_names.second); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + } + } + + if (type == BlockType::Initial && !concentration.empty()) { + int index = 0; + if (ion.is_intra_cell_conc(concentration)) { + index = 1; + } else if (ion.is_extra_cell_conc(concentration)) { + index = 2; + } else { + /// \todo Unhandled case in neuron implementation + throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); + } + auto ion_type_name = fmt::format("{}_type", ion.name); + auto lhs = fmt::format("int {}", ion_type_name); + auto op = "="; + auto rhs = get_variable_name(ion_type_name); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + auto statement = conc_write_statement(ion.name, concentration, index); + statements.push_back(ShadowUseStatement{statement, "", ""}); + } + } + return statements; +} + +/** + * If mechanisms dependency level execution is enabled then certain updates + * like ionic current contributions needs to be atomically updated. In this + * case we first update current mechanism's shadow vector and then add statement + * to queue that will be used in reduction queue. + */ +std::string CodegenCppVisitor::process_shadow_update_statement(const ShadowUseStatement& statement, + BlockType /* type */) { + // when there is no operator or rhs then that statement doesn't need shadow update + if (statement.op.empty() && statement.rhs.empty()) { + auto text = statement.lhs + ";"; + return text; + } + + // return regular statement + auto lhs = get_variable_name(statement.lhs); + auto text = fmt::format("{} {} {};", lhs, statement.op, statement.rhs); + return text; +} + + +/** + * \details Current variable used in breakpoint block could be local variable. + * In this case, neuron has already renamed the variable name by prepending + * "_l". In our implementation, the variable could have been renamed by + * one of the pass. And hence, we search all local variables and check if + * the variable is renamed. Note that we have to look into the symbol table + * of statement block and not breakpoint. + */ +std::string CodegenCppVisitor::breakpoint_current(std::string current) const { + auto breakpoint = info.breakpoint_node; + if (breakpoint == nullptr) { + return current; + } + auto symtab = breakpoint->get_statement_block()->get_symbol_table(); + auto variables = symtab->get_variables_with_properties(NmodlType::local_var); + for (const auto& var: variables) { + auto renamed_name = var->get_name(); + auto original_name = var->get_original_name(); + if (current == original_name) { + current = renamed_name; + break; + } + } + return current; +} + + +/****************************************************************************************/ +/* Routines for returning variable name */ +/****************************************************************************************/ + +std::string CodegenCppVisitor::update_if_ion_variable_name(const std::string& name) const { + std::string result(name); + if (ion_variable_struct_required()) { + if (info.is_ion_read_variable(name)) { + result = naming::ION_VARNAME_PREFIX + name; + } + if (info.is_ion_write_variable(name)) { + result = "ionvar." + name; + } + if (info.is_current(name)) { + result = "ionvar." + name; + } + } + return result; +} + + +std::pair CodegenCppVisitor::read_ion_variable_name( + const std::string& name) { + return {name, naming::ION_VARNAME_PREFIX + name}; +} + + +std::pair CodegenCppVisitor::write_ion_variable_name( + const std::string& name) { + return {naming::ION_VARNAME_PREFIX + name, name}; +} + /****************************************************************************************/ /* Main printing routines for code generation */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 549519d396..b3d5511da3 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -406,6 +406,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /* Common helper routines accross codegen functions */ /****************************************************************************************/ + /** + * Check if a structure for ion variables is required + * \return \c true if a structure fot ion variables must be generated + */ + bool ion_variable_struct_required() const; + /** * Generate the string representing the procedure parameter declaration @@ -590,6 +596,56 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::vector get_int_variables(); + /** + * For a given output block type, return statements for all read ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the reading of ion variables + */ + std::vector ion_read_statements(BlockType type) const; + + + /** + * For a given output block type, return minimal statements for all read ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the reading of ion variables + */ + std::vector ion_read_statements_optimized(BlockType type) const; + + + /** + * For a given output block type, return statements for writing back ion variables + * + * \param type The type of code block being generated + * \return A \c vector of strings representing the write-back of ion variables + */ + std::vector ion_write_statements(BlockType type); + + + /** + * Process shadow update statement + * + * If the statement requires reduction then add it to vector of reduction statement and return + * statement using shadow update + * + * \param statement The statement that might require shadow updates + * \param type The target backend code block type + * \return The generated target backend code + */ + std::string process_shadow_update_statement(const ShadowUseStatement& statement, + BlockType type); + + + /** + * Determine the variable name for the "current" used in breakpoint block taking into account + * intermediate code transformations. + * \param current The variable name for the current used in the model + * \return The name for the current to be printed in C++ + */ + std::string breakpoint_current(std::string current) const; + + /****************************************************************************************/ /* Backend specific routines */ /****************************************************************************************/ @@ -609,6 +665,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ virtual void print_global_var_struct_decl(); + /** + * Check if ion variable copies should be avoided + */ + virtual bool optimize_ion_variable_copies() const = 0; /****************************************************************************************/ /* Printing routines for code generation */ @@ -808,6 +868,16 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return std::make_shared(name, ModToken()); } + /** + * Generate Function call statement for nrn_wrote_conc + * \param ion_name The name of the ion variable + * \param concentration The name of the concentration variable + * \param index + * \return The string representing the function call + */ + virtual std::string conc_write_statement(const std::string& ion_name, + const std::string& concentration, + int index) = 0; /****************************************************************************************/ /* Code-specific printing routines for code generations */ @@ -830,6 +900,13 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /* Routines for returning variable name */ /****************************************************************************************/ + /** + * Determine the updated name if the ion variable has been optimized + * \param name The ion variable name + * \return The updated name of the variable has been optimized (e.g. \c ena --> \c ion_ena) + */ + std::string update_if_ion_variable_name(const std::string& name) const; + /** * Determine the name of a \c float variable given its symbol @@ -887,6 +964,30 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { bool use_instance = true) const = 0; + /** + * Return ion variable name and corresponding ion read variable name. + * + * Example: + * {"ena", "ion_ena"} = read_ion_variable_name("ena"); + * + * \param name The ion variable name + * \return The ion read variable name + */ + static std::pair read_ion_variable_name(const std::string& name); + + + /** + * Return ion variable name and corresponding ion write variable name + * + * Example: + * {"ion_ena", "ena"} = write_ion_variable_name("ena"); + * + * \param name The ion variable name + * \return The ion write variable name + */ + static std::pair write_ion_variable_name(const std::string& name); + + /****************************************************************************************/ /* Main printing routines for code generation */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 6ea76459c3..b5532a28ff 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -83,6 +83,13 @@ void CodegenNeuronCppVisitor::print_atomic_reduction_pragma() { return; } +bool CodegenNeuronCppVisitor::optimize_ion_variable_copies() const { + if (optimize_ionvar_copies) { + throw std::runtime_error("Not implemented."); + } + return false; +} + /****************************************************************************************/ /* Printing routines for code generation */ @@ -191,6 +198,12 @@ void CodegenNeuronCppVisitor::print_namespace_stop() { } +std::string CodegenNeuronCppVisitor::conc_write_statement(const std::string& ion_name, + const std::string& concentration, + int index) { + throw std::runtime_error("Not implemented."); +} + /****************************************************************************************/ /* Routines for returning variable name */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 7827ef64f6..8674809a65 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -116,6 +116,11 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { virtual void print_atomic_reduction_pragma() override; + /** + * Check if ion variable copies should be avoided + */ + bool optimize_ion_variable_copies() const override; + /****************************************************************************************/ /* Printing routines for code generation */ /****************************************************************************************/ @@ -240,6 +245,9 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ std::string register_mechanism_arguments() const override; + std::string conc_write_statement(const std::string& ion_name, + const std::string& concentration, + int index) override; /****************************************************************************************/ /* Code-specific printing routines for code generations */ From c8edf0f9d92e989ec820f1e5d2c2e1691c5fcf22 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 8 Feb 2024 20:30:20 +0100 Subject: [PATCH 576/871] Docs: Start documenting pointers (BlueBrain/nmodl#1151) NMODL Repo SHA: BlueBrain/nmodl@f7e7e87aeee4a8e11cbff691c2170f0a8c77beef --- docs/nmodl/transpiler/contents/pointers.rst | 30 +++++++++++++++++++++ docs/nmodl/transpiler/index.rst | 1 + 2 files changed, 31 insertions(+) create mode 100644 docs/nmodl/transpiler/contents/pointers.rst diff --git a/docs/nmodl/transpiler/contents/pointers.rst b/docs/nmodl/transpiler/contents/pointers.rst new file mode 100644 index 0000000000..9add4b8e32 --- /dev/null +++ b/docs/nmodl/transpiler/contents/pointers.rst @@ -0,0 +1,30 @@ +NMODL "pointers" +================ + +Mechanisms can refer to values in other mechanisms, e.g. the sodium current +``ina``. Therefore, it supports a notion of "pointer", called ``Datum``. A datum +can store a pointer to a double, a stable pointer to a double, integers, or +pointers to anything else. + +Integer Variables +----------------- +One important subset of Datum are pointers to RANGE variables. Meaning they are +pointers to parameters in other mechanisms or pointers to the parameters +associated with each node, e.g. the voltage. Since the storage of RANGE +variable is controlled by NEURON/CoreNEURON, these pointers have stronger +semantics than a ``double*``. + +These make up the majority of usecases for Datum; and are considered the +well-mannered subset. + +In CoreNEURON this subset of Datums are treated differently for other Datums. +Because CoreNEURON stores the values these Datums can point to in a single +contiguous array of doubles, the "pointers" can be expressed as indices into +this array. + +Therefore, this subset of Datums is referred to as "integer variables". + +In NEURON these pointers are a ``data_handle`` to the value they point to. +Before the simulation phase they are "resolved" and a cache stores a list of +``double*`` to the appropriate values. + diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 4a494781f7..8c5f5e07db 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -19,6 +19,7 @@ About NMODL :caption: Contents: contents/visitors + contents/pointers .. toctree:: :maxdepth: 3 From c4277d2a22071bec2eccab2cee5c357d8e17721c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 9 Feb 2024 08:58:42 +0100 Subject: [PATCH 577/871] Add documentation about ions (BlueBrain/nmodl#1150) NMODL Repo SHA: BlueBrain/nmodl@c796cae8bee2458694a6bdf55842af1e8e88ea74 --- docs/nmodl/transpiler/contents/ions.rst | 109 +++ .../transpiler/images/ion_storage-opt.svg | 516 +++++++++++ docs/nmodl/transpiler/images/ion_storage.svg | 829 ++++++++++++++++++ docs/nmodl/transpiler/index.rst | 1 + 4 files changed, 1455 insertions(+) create mode 100644 docs/nmodl/transpiler/contents/ions.rst create mode 100644 docs/nmodl/transpiler/images/ion_storage-opt.svg create mode 100644 docs/nmodl/transpiler/images/ion_storage.svg diff --git a/docs/nmodl/transpiler/contents/ions.rst b/docs/nmodl/transpiler/contents/ions.rst new file mode 100644 index 0000000000..898bad69e5 --- /dev/null +++ b/docs/nmodl/transpiler/contents/ions.rst @@ -0,0 +1,109 @@ +Ions +==== + +NEURON supports computing the ion currents and ion concentrations. For each segment +there can be separate current for separate ions, i.e. one for sodium ions and +another for calcium ions. + +There are five variables associated with ions: the current (``ina``), the +concentration inside the segment adjacent to the membrane (``nai``), the +concentration outside the segment adjacent to the membrane (``nao``), the +reversal potential (``ena``) and the derivative if the current w.r.t. the +voltage (``dinadv``). The names should be split as ``i{na}`` and therefore +refer to the value for sodium, for calcium it would have been ``ica``. + +These variables are physical properties of the segment. Therefore, there exists +one mechanism per ion. MOD files can include code to read or write these +variables. + +NDMOL Keywords +-------------- +A MOD file seeking to use ions should use ``USEION`` as follows: + +.. code:: + + NEURON { + USEION na READ ina WRITE ena + } + + ASSIGNED { + ena (mV) + ina (mA / cm2) + } + +Multiple ions are expressed by one line of ``USEION`` per ion. + +The ``{ion_name}`` is a string giving the ion a name. For sodium it's ``na`` +and for calcium ``ca``. If the no other mechanisms have defined an ion with +this name a new ion mechanism is created. + +Both ``READ`` and ``WRITE`` are optional and are followed by a comma separated +list of ion variable names, e.g. ``ina, ena``. + +Keyword: WRITE +~~~~~~~~~~~~~~ + +Writing Ion Currents +^^^^^^^^^^^^^^^^^^^^ + +In MOD file one can set the value of ion variables. + +.. code:: + + BREAKPOINT { + ina = gna*(v - ena) + } + +Semantically, this states that the contribution of the Hodgkin-Huxley model to +the overall sodium current in that segment is ``gna*(v - ena)``. Since +everything is at the level of segment, we'll not keep repeating "for that +segment". Similarly, each mechanism computes a `local` contribution, i.e. the +contribution due to this mechanism to the actual `global` ion current. + +Therefore, code for the following must be generated: + +1. Compute the local contribution to the sodium current. +2. Increment the total, local, current contribution by ``ina``. +3. Increment the global sodium current by ``ina``. +4. Compute local derivative of ``ina`` w.r.t. the voltage. +5. Increment the global derivative of the sodium current w.r.t. the voltage. + +The global current must also be updated as usual. However, this isn't ion +specific and hence omitted. + + +Storage +------- + +Each mechanism that specifies ``USEION na`` contains a copy of all used ion +variables and pointers to the shared values in the ion mechanism, see Figure 1. + +The pointer to the variable in the ion mechanism is prefixed with ``ion_``, +e.g. during initialization we might copy the shared value ``*ion_ena[i]`` to +``ena[i]`` (the copy local to the mechanism using the ion). + +.. figure:: ../images/ion_storage.svg + + Figure 1: Ion mechanism for sodium (``na``) and its use in the + Hodgkin-Huxley mechanism. This figure shows the NEURON memory layout. + + +Optimizing Storage +~~~~~~~~~~~~~~~~~~ + +Since the common pattern is to only access the values of a particular instance, +the local copy isn't needed. I might facilitate SIMD, but it could be replaces +by local variables, see Figure 2. + +.. figure:: ../images/ion_storage-opt.svg + + Figure 2: Optimized ion storage layout. + + +This optimization is implemented in NMODL. It can be activated on the CLI via + +.. code:: sh + + nmodl ... codegen --opt-ionvar-copy + + diff --git a/docs/nmodl/transpiler/images/ion_storage-opt.svg b/docs/nmodl/transpiler/images/ion_storage-opt.svg new file mode 100644 index 0000000000..d933b4eaab --- /dev/null +++ b/docs/nmodl/transpiler/images/ion_storage-opt.svg @@ -0,0 +1,516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + hh + ina + ena + + + + + + + + + + + + + + + + + ion_ina + ion_ena + nrn_init_hh + + no-op + +nrn_cur_hh + + ions.ena = *ion_ena[i] + ions.ina = f(*ion_ena[i], v+dv) + ions.ina = f(*ion_ena[i], v) + + *ion_dinadv[i] += f(ions) + *ion_ina[i] += ions.ina + +nrn_state_hh + + no-op + + + + + + + + + Allocated, but not used + + + + RANGE variable + Pointer to RANGE + + + diff --git a/docs/nmodl/transpiler/images/ion_storage.svg b/docs/nmodl/transpiler/images/ion_storage.svg new file mode 100644 index 0000000000..28af536e69 --- /dev/null +++ b/docs/nmodl/transpiler/images/ion_storage.svg @@ -0,0 +1,829 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0: ena + 1: nai + 2: nao + 3: ina + 4: dinadv + hh + ina + ena + ion_ina + ion_ena + nrn_init_hh + + ena[i] = *ion_ena[i] + +nrn_cur_hh + + ena[i] = *ion_ena[i] + ina[i] = f(*ion_ena[i], v+dv) + ina[i] = f(*ion_ena[i], v) + + *ion_dinadv[i] += f(ina[i]) + *ion_ina[i] += ions.ina + +nrn_state_hh + + ena[i] = *ion_ena[i] + + + + + + + + + RANGE variable + Pointer to RANGE + + na + + + diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 8c5f5e07db..f923c6723b 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -19,6 +19,7 @@ About NMODL :caption: Contents: contents/visitors + contents/ions contents/pointers .. toctree:: From bee52b012b2f3f6d5007017a74c5a5ac863fcc1f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 9 Feb 2024 09:30:48 +0100 Subject: [PATCH 578/871] Implement trivial cases of reading/writing ions. (BlueBrain/nmodl#1152) This implements a trivial case of reading an ion variable `ena` and writing `42.0` to `ina`; along with the functionality required to be able to record `ina`. NMODL Repo SHA: BlueBrain/nmodl@d70c51ba742ff3022b2fcd290970b0d6cadefaaa --- src/nmodl/codegen/codegen_info.hpp | 45 +++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 190 +++++++++++++++--- .../codegen/codegen_neuron_cpp_visitor.cpp | 15 +- .../nmodl/transpiler/usecases/ionic/ionic.mod | 13 ++ .../transpiler/usecases/ionic/simulate.py | 28 +++ 5 files changed, 262 insertions(+), 29 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/ionic/ionic.mod create mode 100644 test/nmodl/transpiler/usecases/ionic/simulate.py diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index b9a542abdb..2340c9a79f 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -12,6 +12,7 @@ * \brief Various types to store code generation specific information */ +#include #include #include #include @@ -20,6 +21,7 @@ #include "ast/ast.hpp" #include "symtab/symbol_table.hpp" + namespace nmodl { namespace codegen { @@ -109,12 +111,55 @@ struct Ion { return is_intra_cell_conc(text) || is_extra_cell_conc(text); } + /// Is the variable name `text` related to this ion? + /// + /// Example: For sodium this is true for any of `"ena"`, `"ina"`, `"nai"` + /// and `"nao"`; but not `ion_ina`, etc. + bool is_ionic_variable(const std::string& text) const { + return is_ionic_conc(text) || is_ionic_current(text) || is_rev_potential(text); + } + + bool is_current_derivative(const std::string& text) const { + return text == ("di" + name + "dv"); + } + /// for a given ion, return different variable names/properties /// like internal/external concentration, reversial potential, /// ionic current etc. static std::vector get_possible_variables(const std::string& ion_name) { return {"i" + ion_name, ion_name + "i", ion_name + "o", "e" + ion_name}; } + + /// Variable index in the ion mechanism. + /// + /// For sodium (na), the `var_name` must be one of `ina`, `ena`, `nai`, + /// `nao` or `dinadv`. Replace `na` with the analogous for other ions. + /// + /// In NRN the order is: + /// 0: ena + /// 1: nai + /// 2: nao + /// 3: ina + /// 4: dinadv + int variable_index(const std::string& var_name) const { + if (is_rev_potential(var_name)) { + return 0; + } + if (is_intra_cell_conc(var_name)) { + return 1; + } + if (is_extra_cell_conc(var_name)) { + return 2; + } + if (is_ionic_current(var_name)) { + return 3; + } + if (is_current_derivative(var_name)) { + return 4; + } + + throw std::runtime_error(fmt::format("Invalid `var_name == {}`.", var_name)); + } }; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index b5532a28ff..20dc4ac8d4 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -201,7 +201,8 @@ void CodegenNeuronCppVisitor::print_namespace_stop() { std::string CodegenNeuronCppVisitor::conc_write_statement(const std::string& ion_name, const std::string& concentration, int index) { - throw std::runtime_error("Not implemented."); + // throw std::runtime_error("Not implemented."); + return ""; } /****************************************************************************************/ @@ -234,7 +235,30 @@ std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbo std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& symbol, const std::string& name, bool use_instance) const { - return name; + auto position = position_of_int_var(name); + if (symbol.is_index) { + if (use_instance) { + throw std::runtime_error("Not implemented. [wiejo]"); + // return fmt::format("inst->{}[{}]", name, position); + } + throw std::runtime_error("Not implemented. [ncuwi]"); + // return fmt::format("indexes[{}]", position); + } + if (symbol.is_integer) { + if (use_instance) { + throw std::runtime_error("Not implemented. [cnuoe]"); + // return fmt::format("inst->{}[{}*pnodecount+id]", name, position); + } + throw std::runtime_error("Not implemented. [u32ow]"); + // return fmt::format("indexes[{}*pnodecount+id]", position); + } + if (use_instance) { + return fmt::format("(*inst.{}[id])", name); + } + + throw std::runtime_error("Not implemented. [nvueir]"); + // auto data = symbol.is_vdata ? "_vdata" : "_data"; + // return fmt::format("nt->{}[indexes[{}*pnodecount + id]]", data, position); } @@ -250,8 +274,7 @@ std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symb std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { - // const std::string& varname = update_if_ion_variable_name(name); - const std::string& varname = name; + const std::string& varname = update_if_ion_variable_name(name); auto symbol_comparator = [&varname](const SymbolType& sym) { return varname == sym->get_name(); @@ -550,20 +573,39 @@ void CodegenNeuronCppVisitor::print_make_instance() const { info.mod_suffix); printer->fmt_push_block("return {}", instance_struct()); + std::vector make_instance_args; + const auto codegen_float_variables_size = codegen_float_variables.size(); for (int i = 0; i < codegen_float_variables_size; ++i) { const auto& float_var = codegen_float_variables[i]; if (float_var->is_array()) { - printer->fmt_line("_ml.template data_array<{}, {}>(0){}", - i, - float_var->get_length(), - i < codegen_float_variables_size - 1 ? "," : ""); + make_instance_args.push_back( + fmt::format("_ml.template data_array_ptr<{}, {}>()", i, float_var->get_length())); } else { - printer->fmt_line("&_ml.template fpfield<{}>(0){}", - i, - i < codegen_float_variables_size - 1 ? "," : ""); + make_instance_args.push_back(fmt::format("_ml.template fpfield_ptr<{}>()", i)); + } + } + + const auto codegen_int_variables_size = codegen_int_variables.size(); + for (size_t i = 0; i < codegen_int_variables_size; ++i) { + const auto& var = codegen_int_variables[i]; + auto name = var.symbol->get_name(); + auto const variable = [&var, i]() -> std::string { + if (var.is_index || var.is_integer) { + return ""; + } else if (var.is_vdata) { + return ""; + } else { + return fmt::format("_ml.template dptr_field_ptr<{}>()", i); + } + }(); + if (variable != "") { + make_instance_args.push_back(variable); } } + + printer->add_multi_line(fmt::format("{}", fmt::join(make_instance_args, ",\n"))); + printer->pop_block(";"); printer->pop_block(); } @@ -600,28 +642,63 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_line("_nrn_mechanism_register_data_fields(mech_type,"); printer->increase_indent(); const auto codegen_float_variables_size = codegen_float_variables.size(); + + std::vector mech_register_args; for (int i = 0; i < codegen_float_variables_size; ++i) { const auto& float_var = codegen_float_variables[i]; - const auto print_comma = i < codegen_float_variables_size - 1 || info.emit_cvode; if (float_var->is_array()) { - printer->fmt_line("_nrn_mechanism_field{{\"{}\", {}}} /* {} */{}", - float_var->get_name(), - float_var->get_length(), - i, - print_comma ? "," : ""); + mech_register_args.push_back( + fmt::format("_nrn_mechanism_field{{\"{}\", {}}} /* {} */", + float_var->get_name(), + float_var->get_length(), + i)); } else { - printer->fmt_line("_nrn_mechanism_field{{\"{}\"}} /* {} */{}", - float_var->get_name(), - i, - print_comma ? "," : ""); + mech_register_args.push_back(fmt::format( + "_nrn_mechanism_field{{\"{}\"}} /* {} */", float_var->get_name(), i)); } } + + const auto codegen_int_variables_size = codegen_int_variables.size(); + for (int i = 0; i < codegen_int_variables_size; ++i) { + const auto& int_var = codegen_int_variables[i]; + const auto& name = int_var.symbol->get_name(); + if (i != info.semantics[i].index) { + throw std::runtime_error("Broken logic."); + } + + mech_register_args.push_back( + fmt::format("_nrn_mechanism_field{{\"{}\", \"{}\"}} /* {} */", + name, + info.semantics[i].name, + i)); + } if (info.emit_cvode) { - printer->add_line("_nrn_mechanism_field{\"_cvode_ieq\", \"cvodeieq\"} /* 0 */"); + mech_register_args.push_back( + "_nrn_mechanism_field{\"_cvode_ieq\", \"cvodeieq\"} /* 0 */"); } + + printer->add_multi_line(fmt::format("{}", fmt::join(mech_register_args, ",\n"))); + printer->decrease_indent(); printer->add_line(");"); printer->add_newline(); + + printer->fmt_line("hoc_register_prop_size(mech_type, {}, {});", + codegen_float_variables_size, + codegen_int_variables_size); + + for (int i = 0; i < codegen_int_variables_size; ++i) { + const auto& int_var = codegen_int_variables[i]; + const auto& name = int_var.symbol->get_name(); + if (i != info.semantics[i].index) { + throw std::runtime_error("Broken logic."); + } + + printer->fmt_line("hoc_register_dparam_semantics(mech_type, {}, \"{}\");", + i, + info.semantics[i].name); + } + printer->pop_block(); } @@ -649,11 +726,11 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini const auto& name = var.symbol->get_name(); if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? "const " : ""; - printer->fmt_line("{}{}* {}{};", qualifier, int_type, name, value_initialize); + printer->fmt_line("{}{}* const* {}{};", qualifier, int_type, name, value_initialize); } else { auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); - printer->fmt_line("{}{}* {}{};", qualifier, type, name, value_initialize); + printer->fmt_line("{}{}* const* {}{};", qualifier, type, name, value_initialize); } } @@ -667,11 +744,24 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { + // read ion statements + auto read_statements = ion_read_statements(BlockType::Initial); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + // initial block if (node != nullptr) { const auto& block = node->get_statement_block(); print_statement_block(*block, false, false); } + + // write ion statements + auto write_statements = ion_write_statements(BlockType::Initial); + for (auto& statement: write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Initial); + printer->add_line(text); + } } @@ -729,7 +819,38 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); printer->fmt_push_block("static void {}(Prop* _prop)", method); - printer->add_line("// do nothing"); + + const auto codegen_int_variables_size = codegen_int_variables.size(); + + // TODO number of datum is the number of integer vars. + printer->fmt_line("Datum *_ppvar = nrn_prop_datum_alloc(mech_type, {}, _prop);", + codegen_int_variables_size); + printer->fmt_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); + + for (const auto& ion: info.ions) { + printer->fmt_line("Symbol * {}_sym = hoc_lookup(\"{}_ion\");", ion.name, ion.name); + printer->fmt_line("Prop * {}_prop = need_memb({}_sym);", ion.name, ion.name); + + for (size_t i = 0; i < codegen_int_variables_size; ++i) { + const auto& var = codegen_int_variables[i]; + + // if(var.symbol->has_any_property(NmodlType::useion)) { + const std::string& var_name = var.symbol->get_name(); + if (var_name.rfind("ion_", 0) != 0) { + continue; + } + + std::string ion_var_name = std::string(var_name.begin() + 4, var_name.end()); + if (ion.is_ionic_variable(ion_var_name)) { + printer->fmt_line("_ppvar[{}] = _nrn_mechanism_get_param_handle({}_prop, {});", + i, + ion.name, + ion.variable_index(ion_var_name)); + } + // } + } + } + printer->pop_block(); } @@ -750,6 +871,19 @@ void CodegenNeuronCppVisitor::print_nrn_state() { printer->push_block("for (int id = 0; id < nodecount; id++)"); + /** + * \todo Eigen solver node also emits IonCurVar variable in the functor + * but that shouldn't update ions in derivative block + */ + if (ion_variable_struct_required()) { + throw std::runtime_error("Not implemented."); + } + + auto read_statements = ion_read_statements(BlockType::State); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + if (info.nrn_state_block) { info.nrn_state_block->visit_children(*this); } @@ -759,6 +893,12 @@ void CodegenNeuronCppVisitor::print_nrn_state() { print_statement_block(*block, false, false); } + const auto& write_statements = ion_write_statements(BlockType::State); + for (auto& statement: write_statements) { + const auto& text = process_shadow_update_statement(statement, BlockType::State); + printer->add_line(text); + } + printer->pop_block(); printer->pop_block(); } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index d9e93e19a6..2544319f95 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -194,9 +194,9 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { double* Ds{}; double* v_unused{}; double* g_unused{}; - const double* ion_ena{}; - double* ion_ina{}; - double* ion_dinadv{}; + const double* const* ion_ena{}; + double* const* ion_ina{}; + double* const* ion_dinadv{}; pas_test_Store* global{&pas_test_global}; };)"; @@ -259,9 +259,16 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { _nrn_mechanism_field{"ina"} /* 6 */, _nrn_mechanism_field{"Ds"} /* 7 */, _nrn_mechanism_field{"v_unused"} /* 8 */, - _nrn_mechanism_field{"g_unused"} /* 9 */ + _nrn_mechanism_field{"g_unused"} /* 9 */, + _nrn_mechanism_field{"ion_ena", "na_ion"} /* 0 */, + _nrn_mechanism_field{"ion_ina", "na_ion"} /* 1 */, + _nrn_mechanism_field{"ion_dinadv", "na_ion"} /* 2 */ ); + hoc_register_prop_size(mech_type, 10, 3); + hoc_register_dparam_semantics(mech_type, 0, "na_ion"); + hoc_register_dparam_semantics(mech_type, 1, "na_ion"); + hoc_register_dparam_semantics(mech_type, 2, "na_ion"); })CODE"; REQUIRE_THAT(generated, diff --git a/test/nmodl/transpiler/usecases/ionic/ionic.mod b/test/nmodl/transpiler/usecases/ionic/ionic.mod new file mode 100644 index 0000000000..a0075b716d --- /dev/null +++ b/test/nmodl/transpiler/usecases/ionic/ionic.mod @@ -0,0 +1,13 @@ +NEURON { + SUFFIX ionic + USEION na READ ina WRITE ena +} + +ASSIGNED { + ina (mA/cm2) + ena (mV) +} + +BREAKPOINT { + ena = 42.0 +} diff --git a/test/nmodl/transpiler/usecases/ionic/simulate.py b/test/nmodl/transpiler/usecases/ionic/simulate.py new file mode 100644 index 0000000000..fcbd4d37b7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/ionic/simulate.py @@ -0,0 +1,28 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("ionic") +s.nseg = nseg + +x_hoc = h.Vector().record(s(0.5)._ref_ena) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +x = np.array(x_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +x_exact = np.full(t.shape, 42.0) +x_exact[0] = 0.0 + +abs_err = np.abs(x - x_exact) + +assert np.all(abs_err < 1e-12), abs_err +print("ionic: success") From e4c56dd02927a234d91281aab98f578bcf252744 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Fri, 9 Feb 2024 18:48:40 +0100 Subject: [PATCH 579/871] Setting `use_range_ptr_var` NmodlType in SymTab (BlueBrain/nmodl#1139) Created FunctionCallpathVisitor that traverses the function calls and sets `use_range_ptr_var` property NMODL Repo SHA: BlueBrain/nmodl@d1290e06916f92956c0500d4627ee31fbc7d1edf --- src/nmodl/main.cpp | 7 ++ src/nmodl/symtab/symbol_properties.cpp | 4 + src/nmodl/symtab/symbol_properties.hpp | 5 +- src/nmodl/visitors/CMakeLists.txt | 1 + .../visitors/function_callpath_visitor.cpp | 95 +++++++++++++++++++ .../visitors/function_callpath_visitor.hpp | 65 +++++++++++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 50 +++++++++- 7 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 src/nmodl/visitors/function_callpath_visitor.cpp create mode 100644 src/nmodl/visitors/function_callpath_visitor.hpp diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index cbd1e62c37..eed968bef1 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -25,6 +25,7 @@ #include "visitors/after_cvode_to_cnexp_visitor.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" +#include "visitors/function_callpath_visitor.hpp" #include "visitors/global_var_visitor.hpp" #include "visitors/implicit_argument_visitor.hpp" #include "visitors/indexedname_visitor.hpp" @@ -528,6 +529,12 @@ int main(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(*ast); } + { + FunctionCallpathVisitor{}.visit_program(*ast); + ast_to_nmodl(*ast, filepath("FunctionCallpathVisitor")); + SymtabVisitor(update_symtab).visit_program(*ast); + } + { if (coreneuron_code && oacc_backend) { logger->info("Running OpenACC backend code generator for CoreNEURON"); diff --git a/src/nmodl/symtab/symbol_properties.cpp b/src/nmodl/symtab/symbol_properties.cpp index 95123ef76c..c65a8bdd19 100644 --- a/src/nmodl/symtab/symbol_properties.cpp +++ b/src/nmodl/symtab/symbol_properties.cpp @@ -161,6 +161,10 @@ std::vector to_string_vector(const NmodlType& obj) { properties.emplace_back("codegen_var"); } + if (has_property(obj, NmodlType::use_range_ptr_var)) { + properties.emplace_back("use_range_ptr_var"); + } + if (has_property(obj, NmodlType::random_var)) { properties.emplace_back("random_var"); } diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 6bf2e0fa04..8bf6359194 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -220,7 +220,10 @@ enum class NmodlType : enum_type { codegen_var = 1LL << 33, /// Randomvar Type - random_var = 1LL << 34 + random_var = 1LL << 34, + + /// FUNCTION or PROCEDURE needs setdata check + use_range_ptr_var = 1LL << 35 }; template diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 8871c9f80d..03b98d228c 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -12,6 +12,7 @@ add_library( after_cvode_to_cnexp_visitor.cpp constant_folder_visitor.cpp defuse_analyze_visitor.cpp + function_callpath_visitor.cpp global_var_visitor.cpp implicit_argument_visitor.cpp indexedname_visitor.cpp diff --git a/src/nmodl/visitors/function_callpath_visitor.cpp b/src/nmodl/visitors/function_callpath_visitor.cpp new file mode 100644 index 0000000000..e9dc69ce42 --- /dev/null +++ b/src/nmodl/visitors/function_callpath_visitor.cpp @@ -0,0 +1,95 @@ + +/* + * Copyright 2024 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "visitors/function_callpath_visitor.hpp" + +namespace nmodl { +namespace visitor { + +using symtab::Symbol; +using symtab::syminfo::NmodlType; + +void FunctionCallpathVisitor::visit_var_name(const ast::VarName& node) { + if (visited_functions_or_procedures.empty()) { + return; + } + /// If node is either a RANGE var, a POINTER or a BBCOREPOINTER then + /// the FUNCTION or PROCEDURE it's used in should have the `use_range_ptr_var` + /// property + auto sym = psymtab->lookup(node.get_node_name()); + const auto properties = NmodlType::range_var | NmodlType::pointer_var | + NmodlType::bbcore_pointer_var; + if (sym && sym->has_any_property(properties)) { + const auto top = visited_functions_or_procedures.back(); + const auto caller_func_name = + top->is_function_block() + ? dynamic_cast(top)->get_node_name() + : dynamic_cast(top)->get_node_name(); + auto caller_func_proc_sym = psymtab->lookup(caller_func_name); + caller_func_proc_sym->add_properties(NmodlType::use_range_ptr_var); + } +} + +void FunctionCallpathVisitor::visit_function_call(const ast::FunctionCall& node) { + if (visited_functions_or_procedures.empty()) { + return; + } + const auto name = node.get_node_name(); + const auto func_symbol = psymtab->lookup(name); + if (!func_symbol || + !func_symbol->has_any_property(NmodlType::function_block | NmodlType::procedure_block) || + func_symbol->get_nodes().empty()) { + return; + } + /// Visit the called FUNCTION/PROCEDURE AST node to check whether + /// it has `use_range_ptr_var` property. If it does the currently called + /// function needs to have it too. + const auto func_block = func_symbol->get_nodes()[0]; + func_block->accept(*this); + if (func_symbol->has_any_property(NmodlType::use_range_ptr_var)) { + const auto top = visited_functions_or_procedures.back(); + auto caller_func_name = + top->is_function_block() + ? dynamic_cast(top)->get_node_name() + : dynamic_cast(top)->get_node_name(); + auto caller_func_proc_sym = psymtab->lookup(caller_func_name); + caller_func_proc_sym->add_properties(NmodlType::use_range_ptr_var); + } +} + +void FunctionCallpathVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { + /// Avoid recursive calls + if (std::find(visited_functions_or_procedures.begin(), + visited_functions_or_procedures.end(), + &node) != visited_functions_or_procedures.end()) { + return; + } + visited_functions_or_procedures.push_back(&node); + node.visit_children(*this); + visited_functions_or_procedures.pop_back(); +} + +void FunctionCallpathVisitor::visit_function_block(const ast::FunctionBlock& node) { + // Avoid recursive calls + if (std::find(visited_functions_or_procedures.begin(), + visited_functions_or_procedures.end(), + &node) != visited_functions_or_procedures.end()) { + return; + } + visited_functions_or_procedures.push_back(&node); + node.visit_children(*this); + visited_functions_or_procedures.pop_back(); +} + +void FunctionCallpathVisitor::visit_program(const ast::Program& node) { + psymtab = node.get_symbol_table(); + node.visit_children(*this); +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/function_callpath_visitor.hpp b/src/nmodl/visitors/function_callpath_visitor.hpp new file mode 100644 index 0000000000..92666841b1 --- /dev/null +++ b/src/nmodl/visitors/function_callpath_visitor.hpp @@ -0,0 +1,65 @@ +/* + * Copyright 2024 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::FunctionCallpathVisitor + */ + +#include "ast/all.hpp" +#include "symtab/decl.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * \addtogroup visitor_classes + * \{ + */ + +/** + * \class FunctionCallpathVisitor + * \brief %Visitor for traversing \c FunctionBlock s and \c ProcedureBlocks through + * their \c FunctionCall s + * + * This visitor is used to traverse the \c FUNCTION s and \c PROCEDURE s in the NMODL files. + * It visits the \c FunctionBlock s and \c ProcedureBlock s and if there is a \c FunctionCall + * in those, it visits the \c FunctionBlock or \c ProcedureBlock of the \c FunctionCall. + * Currently it only checks whether in this path of function calls there is any use of \c RANGE , + * \c POINTER or \c BBCOREPOINTER variable. In case there is it adds the \c use_range_ptr_var + * property in the \c Symbol of the function or procedure in the program \c SymbolTable and does the + * same recursively for all the caller functions. The \c use_range_ptr_var property is used later in + * the \c CodegenNeuronCppVisitor . + * + */ +class FunctionCallpathVisitor: public ConstAstVisitor { + private: + /// Vector of currently visited functions or procedures (used as a searchable stack) + std::vector visited_functions_or_procedures; + + /// symbol table for the program + symtab::SymbolTable* psymtab = nullptr; + + public: + void visit_var_name(const ast::VarName& node) override; + + void visit_function_call(const ast::FunctionCall& node) override; + + void visit_function_block(const ast::FunctionBlock& node) override; + + void visit_procedure_block(const ast::ProcedureBlock& node) override; + + void visit_program(const ast::Program& node) override; +}; + +/** \} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 2544319f95..dbda263863 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -12,6 +12,7 @@ #include "codegen/codegen_neuron_cpp_visitor.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" +#include "visitors/function_callpath_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/solve_block_visitor.hpp" @@ -25,6 +26,7 @@ using namespace codegen; using nmodl::parser::NmodlDriver; using nmodl::test_utils::reindent_text; +using symtab::syminfo::NmodlType; /// Helper for creating C codegen visitor std::shared_ptr create_neuron_cpp_visitor( @@ -38,6 +40,7 @@ std::shared_ptr create_neuron_cpp_visitor( InlineVisitor().visit_program(*ast); NeuronSolveVisitor().visit_program(*ast); SolveBlockVisitor().visit_program(*ast); + FunctionCallpathVisitor().visit_program(*ast); /// create C code generation visitor auto cv = std::make_shared("_test", ss, "double", false); @@ -47,8 +50,7 @@ std::shared_ptr create_neuron_cpp_visitor( /// print entire code -std::string get_neuron_cpp_code(const std::string& nmodl_text, - const bool generate_gpu_code = false) { +std::string get_neuron_cpp_code(const std::string& nmodl_text) { const auto& ast = NmodlDriver().parse_string(nmodl_text); std::stringstream ss; auto cvisitor = create_neuron_cpp_visitor(ast, nmodl_text, ss); @@ -276,3 +278,47 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { } } } + + +SCENARIO("Check whether PROCEDURE and FUNCTION need setdata call", "[codegen][needsetdata]") { + GIVEN("mod file with GLOBAL and RANGE variables used in FUNC and PROC") { + std::string input_nmodl = R"( + NEURON { + SUFFIX test + RANGE x + GLOBAL s + } + PARAMETER { + s = 2 + } + ASSIGNED { + x + } + PROCEDURE a() { + x = get_42() + } + FUNCTION b() { + a() + } + FUNCTION get_42() { + get_42 = 42 + } + )"; + const auto& ast = NmodlDriver().parse_string(input_nmodl); + std::stringstream ss; + auto cvisitor = create_neuron_cpp_visitor(ast, input_nmodl, ss); + cvisitor->visit_program(*ast); + const auto symtab = ast->get_symbol_table(); + THEN("use_range_ptr_var property is added to needed FUNC and PROC") { + auto use_range_ptr_var_funcs = symtab->get_variables_with_properties( + NmodlType::use_range_ptr_var); + REQUIRE(use_range_ptr_var_funcs.size() == 2); + const auto a = symtab->lookup("a"); + REQUIRE(a->has_any_property(NmodlType::use_range_ptr_var)); + const auto b = symtab->lookup("b"); + REQUIRE(b->has_any_property(NmodlType::use_range_ptr_var)); + const auto get_42 = symtab->lookup("get_42"); + REQUIRE(!get_42->has_any_property(NmodlType::use_range_ptr_var)); + } + } +} From f08d3a1d12d30dbf6b7e88a941fb1219211fba42 Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Sat, 10 Feb 2024 09:29:20 +0100 Subject: [PATCH 580/871] Add more logic for POINT_PROCESS registration function (BlueBrain/nmodl#1106) * Added hoc_register_prop_size and hoc_register_dparam_semantics * Implemented big part of the nrn_alloc to make POINT_PROCESS work * Added test for POINT_PROCESS location * Added test for parameters NMODL Repo SHA: BlueBrain/nmodl@545b7b321409effa613ce907b86d774912f8aa37 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 + src/nmodl/codegen/codegen_info.hpp | 3 + .../codegen/codegen_neuron_cpp_visitor.cpp | 253 ++++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.hpp | 14 + .../codegen/codegen_neuron_cpp_visitor.cpp | 28 +- test/nmodl/transpiler/usecases/CMakeLists.txt | 2 +- .../transpiler/usecases/parameter/simulate.py | 12 + .../usecases/parameter/test_parameter.mod | 8 + .../usecases/point_process/simulate.py | 15 ++ .../usecases/point_process/test_pp.mod | 3 + 10 files changed, 317 insertions(+), 23 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/parameter/simulate.py create mode 100644 test/nmodl/transpiler/usecases/parameter/test_parameter.mod create mode 100644 test/nmodl/transpiler/usecases/point_process/simulate.py create mode 100644 test/nmodl/transpiler/usecases/point_process/test_pp.mod diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 9ae481fa8b..e22abc221c 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1137,6 +1137,8 @@ void CodegenCppVisitor::setup(const Program& node) { update_index_semantics(); rename_function_arguments(); + + info.semantic_variable_count = int_variables_size(); } std::string CodegenCppVisitor::compute_method_name(BlockType type) const { diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 2340c9a79f..2f626e031b 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -298,6 +298,9 @@ struct CodegenInfo { /// typically equal to number of primes int num_equations = 0; + /// number of semantic variables + int semantic_variable_count; + /// True if we have to emit CVODE code /// TODO: Figure out when this needs to be true bool emit_cvode = false; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 20dc4ac8d4..731af4d03f 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -96,11 +96,99 @@ bool CodegenNeuronCppVisitor::optimize_ion_variable_copies() const { /****************************************************************************************/ +void CodegenNeuronCppVisitor::print_point_process_function_definitions() { + if (info.point_process) { + printer->add_line("/* Point Process specific functions */"); + printer->add_multi_line(R"CODE( + static void* _hoc_create_pnt(Object* _ho) { + return create_point_process(_pointtype, _ho); + } + )CODE"); + printer->push_block("static void _hoc_destroy_pnt(void* _vptr)"); + if (info.is_watch_used() || info.for_netcon_used) { + printer->add_line("Prop* _prop = ((Point_process*)_vptr)->prop;"); + } + if (info.is_watch_used()) { + printer->push_block("if (_prop)"); + printer->fmt_line("_nrn_free_watch(_nrn_mechanism_access_dparam(_prop), {}, {});", + info.watch_count, + info.is_watch_used()); + printer->pop_block(); + } + if (info.for_netcon_used) { + printer->push_block("if (_prop)"); + printer->fmt_line( + "_nrn_free_fornetcon(&(_nrn_mechanism_access_dparam(_prop)[_fnc_index].literal_" + "value()));"); + printer->pop_block(); + } + printer->add_line("destroy_point_process(_vptr);"); + printer->pop_block(); + printer->add_multi_line(R"CODE( + static double _hoc_loc_pnt(void* _vptr) { + return loc_point_process(_pointtype, _vptr); + } + )CODE"); + printer->add_multi_line(R"CODE( + static double _hoc_has_loc(void* _vptr) { + return has_loc_point(_vptr); + } + )CODE"); + printer->add_multi_line(R"CODE( + static double _hoc_get_loc_pnt(void* _vptr) { + return (get_loc_point_process(_vptr)); + } + )CODE"); + } +} + + +void CodegenNeuronCppVisitor::print_setdata_functions() { + printer->add_line("/* Neuron setdata functions */"); + printer->add_line("extern void _nrn_setdata_reg(int, void(*)(Prop*));"); + printer->push_block("static void _setdata(Prop* _prop)"); + if (!info.point_process) { + printer->add_multi_line(R"CODE( + _extcall_prop = _prop; + _prop_id = _nrn_get_prop_id(_prop); + )CODE"); + } + if (!info.vectorize) { + printer->add_multi_line(R"CODE( + neuron::legacy::set_globals_from_prop(_prop, _ml_real, _ml, _iml); + _ppvar = _nrn_mechanism_access_dparam(_prop); + )CODE"); + } + printer->pop_block(); + + if (info.point_process) { + printer->push_block("static void _hoc_setdata(void* _vptr)"); + printer->add_multi_line(R"CODE( + Prop* _prop; + _prop = ((Point_process*)_vptr)->prop; + _setdata(_prop); + )CODE"); + } else { + printer->push_block("static void _hoc_setdata()"); + printer->add_multi_line(R"CODE( + Prop *_prop, *hoc_getdata_range(int); + _prop = hoc_getdata_range(mech_type); + _setdata(_prop); + hoc_retpushx(1.); + )CODE"); + } + printer->pop_block(); +} + + /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function_prototypes() { - if (info.functions.empty() && info.procedures.empty()) { - return; - } + printer->add_newline(2); + + print_point_process_function_definitions(); + print_setdata_functions(); + + /// TODO: Add mechanism function and procedures declarations } @@ -439,8 +527,22 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in printer->fmt_line("static neuron::container::field_index _slist1[{0}], _dlist1[{0}];", info.primes_size); } + + for (const auto& ion: info.ions) { + printer->fmt_line("static Symbol* _{}_sym;", ion.name); + } + printer->add_line("static int mech_type;"); + if (info.point_process) { + printer->add_line("static int _pointtype;"); + } else { + printer->add_multi_line(R"CODE( + static Prop* _extcall_prop; + /* _prop_id kind of shadows _extcall_prop to allow validity checking. */ + static _nrn_non_owning_id_without_container _prop_id{};)CODE"); + } + printer->fmt_line("static int {} = {};", naming::NRN_POINTERINDEX, info.pointer_variables.size() > 0 @@ -457,10 +559,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in // TODO implement these when needed. } - if (info.point_process) { - throw std::runtime_error("Not implemented, global point process."); - } - if (!info.vectorize && !info.top_local_variables.empty()) { throw std::runtime_error("Not implemented, global vectorize something."); } @@ -564,6 +662,30 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_line("{nullptr, nullptr, 0}"); printer->decrease_indent(); printer->add_line("};"); + + printer->add_newline(2); + printer->add_line("/* connect user functions to hoc names */"); + printer->add_line("static VoidFunc hoc_intfunc[] = {"); + printer->increase_indent(); + if (info.point_process) { + printer->add_line("{0, 0}"); + printer->decrease_indent(); + printer->add_line("};"); + printer->add_line("static Member_func _member_func[] = {"); + printer->increase_indent(); + printer->add_multi_line(R"CODE( + {"loc", _hoc_loc_pnt}, + {"has_loc", _hoc_has_loc}, + {"get_loc", _hoc_get_loc_pnt},)CODE"); + } else { + printer->fmt_line("{{\"setdata_{}\", _hoc_setdata}},", info.mod_suffix); + } + + /// TODO: Add _hoc_procedures and _hoc_functions + + printer->add_line("{0, 0}"); + printer->decrease_indent(); + printer->add_line("};"); } void CodegenNeuronCppVisitor::print_make_instance() const { @@ -616,6 +738,23 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_line("/** register channel with the simulator */"); printer->fmt_push_block("extern \"C\" void _{}_reg()", info.mod_file); printer->add_line("_initlists();"); + + printer->add_newline(); + + for (const auto& ion: info.ions) { + printer->fmt_line("ion_reg(\"{}\", {});", ion.name, "-10000."); + } + printer->add_newline(); + + if (info.diam_used) { + printer->add_line("_morphology_sym = hoc_lookup(\"morphology\");"); + printer->add_newline(); + } + + for (const auto& ion: info.ions) { + printer->fmt_line("_{0}_sym = hoc_lookup(\"{0}_ion\");", ion.name); + } + printer->add_newline(); const auto compute_functions_parameters = @@ -632,18 +771,34 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { method_name(naming::NRN_INIT_METHOD), naming::NRN_POINTERINDEX, 1 + info.thread_data_index); - printer->fmt_line("register_mech({});", register_mech_args); + if (info.point_process) { + printer->fmt_line( + "_pointtype = point_register_mech({}, _hoc_create_pnt, _hoc_destroy_pnt, " + "_member_func);", + register_mech_args); + } else { + printer->fmt_line("register_mech({});", register_mech_args); + } - // type related information + /// type related information printer->add_newline(); printer->fmt_line("mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); - // More things to add here + /// Call _nrn_mechanism_register_data_fields() with the correct arguments + /// Geenerated code follows the style underneath + /// + /// _nrn_mechanism_register_data_fields(mech_type, + /// _nrn_mechanism_field{"var_name"}, /* float var index 0 */ + /// ... + /// ); + /// + /// TODO: More things to add here printer->add_line("_nrn_mechanism_register_data_fields(mech_type,"); printer->increase_indent(); - const auto codegen_float_variables_size = codegen_float_variables.size(); + const auto codegen_float_variables_size = codegen_float_variables.size(); std::vector mech_register_args; + for (int i = 0; i < codegen_float_variables_size; ++i) { const auto& float_var = codegen_float_variables[i]; if (float_var->is_array()) { @@ -666,8 +821,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { throw std::runtime_error("Broken logic."); } + auto type = (name == naming::POINT_PROCESS_VARIABLE) ? "Point_process*" : "double*"; mech_register_args.push_back( - fmt::format("_nrn_mechanism_field{{\"{}\", \"{}\"}} /* {} */", + fmt::format("_nrn_mechanism_field<{}>{{\"{}\", \"{}\"}} /* {} */", + type, name, info.semantics[i].name, i)); @@ -724,7 +881,9 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini } for (auto& var: codegen_int_variables) { const auto& name = var.symbol->get_name(); - if (var.is_index || var.is_integer) { + if (name == naming::POINT_PROCESS_VARIABLE) { + continue; + } else if (var.is_index || var.is_integer) { auto qualifier = var.is_constant ? "const " : ""; printer->fmt_line("{}{}* const* {}{};", qualifier, int_type, name, value_initialize); } else { @@ -817,15 +976,64 @@ void CodegenNeuronCppVisitor::print_nrn_destructor() { /// TODO: Print the equivalent of `nrn_alloc_` void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->add_newline(2); + auto method = method_name(naming::NRN_ALLOC_METHOD); printer->fmt_push_block("static void {}(Prop* _prop)", method); + printer->add_multi_line(R"CODE( + Prop *prop_ion{}; + Datum *_ppvar{}; + )CODE"); - const auto codegen_int_variables_size = codegen_int_variables.size(); + if (info.point_process) { + printer->push_block("if (nrn_point_prop_)"); + printer->add_multi_line(R"CODE( + _nrn_mechanism_access_alloc_seq(_prop) = _nrn_mechanism_access_alloc_seq(nrn_point_prop_); + _ppvar = _nrn_mechanism_access_dparam(nrn_point_prop_); + )CODE"); + printer->chain_block("else"); + } + if (info.semantic_variable_count) { + printer->fmt_line("_ppvar = nrn_prop_datum_alloc(mech_type, {}, _prop);", + info.semantic_variable_count); + printer->add_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); + } + printer->add_multi_line(R"CODE( + _nrn_mechanism_cache_instance _ml_real{_prop}; + auto* const _ml = &_ml_real; + size_t const _iml{}; + )CODE"); + printer->fmt_line("assert(_nrn_mechanism_get_num_vars(_prop) == {});", + codegen_float_variables.size()); + if (float_variables_size()) { + printer->add_line("/*initialize range parameters*/"); + for (const auto& var: info.range_parameter_vars) { + if (var->is_array()) { + continue; + } + const auto& var_name = var->get_name(); + printer->fmt_line("_ml->template fpfield<{}>(_iml) = {}; /* {} */", + position_of_float_var(var_name), + *var->get_value(), + var_name); + } + } + if (info.point_process) { + printer->pop_block(); + } - // TODO number of datum is the number of integer vars. - printer->fmt_line("Datum *_ppvar = nrn_prop_datum_alloc(mech_type, {}, _prop);", - codegen_int_variables_size); - printer->fmt_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); + if (info.semantic_variable_count) { + printer->add_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); + } + + if (info.diam_used) { + throw std::runtime_error("Diam allocation not implemented."); + } + + if (info.area_used) { + throw std::runtime_error("Area allocation not implemented."); + } + + const auto codegen_int_variables_size = codegen_int_variables.size(); for (const auto& ion: info.ions) { printer->fmt_line("Symbol * {}_sym = hoc_lookup(\"{}_ion\");", ion.name, ion.name); @@ -847,10 +1055,12 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { ion.name, ion.variable_index(ion_var_name)); } - // } + //} } } + /// TODO: CONSTRUCTOR call + printer->pop_block(); } @@ -1014,6 +1224,10 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { } } // namespace )CODE"); + + if (info.point_process) { + printer->add_line("extern Prop* nrn_point_prop_;"); + } /// TODO: More prints here? } @@ -1076,6 +1290,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_mechanism_info(); print_data_structures(true); print_nrn_alloc(); + print_function_prototypes(); print_global_variables_for_hoc(); print_compute_functions(); // only nrn_cur and nrn_state print_sdlists_init(true); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 8674809a65..c15c36d68e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -147,6 +147,20 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_net_event_call(const ast::FunctionCall& node) override; + /** + * Print POINT_PROCESS related functions + * Wrap external NEURON functions related to POINT_PROCESS mechanisms + * + */ + void print_point_process_function_definitions(); + + /** + * Print NEURON functions related to setting global variables of the mechanism + * + */ + void print_setdata_functions(); + + /** * Print function and procedures prototype declaration */ diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index dbda263863..525e9fba84 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -58,6 +58,9 @@ std::string get_neuron_cpp_code(const std::string& nmodl_text) { return ss.str(); } +std::string reindent_and_trim_text(const std::string& text) { + return reindent_text(stringutils::trim(text)); +}; SCENARIO("Check NEURON codegen for simple MOD file", "[codegen][neuron_boilerplate]") { GIVEN("A simple mod file with RANGE, ARRAY and ION variables") { @@ -109,9 +112,6 @@ SCENARIO("Check NEURON codegen for simple MOD file", "[codegen][neuron_boilerpla s' = ar[0] } )"; - auto const reindent_and_trim_text = [](const auto& text) { - return reindent_text(stringutils::trim(text)); - }; auto const generated = reindent_and_trim_text(get_neuron_cpp_code(nmodl_text)); THEN("Correct includes are printed") { std::string expected_includes = R"(#include @@ -248,6 +248,10 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { extern "C" void __test_reg() { _initlists(); + ion_reg("na", -10000.); + + _na_sym = hoc_lookup("na_ion"); + register_mech(mechanism_info, nrn_alloc_pas_test, nrn_cur_pas_test, nrn_jacob_pas_test, nrn_state_pas_test, nrn_init_pas_test, hoc_nrnpointerindex, 1); mech_type = nrn_get_mechtype(mechanism_info[1]); @@ -277,6 +281,24 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { ContainsSubstring(reindent_and_trim_text(expected_placeholder_reg))); } } + GIVEN("A simple point process mod file") { + std::string const nmodl_text = R"( + NEURON { + POINT_PROCESS test_pp + } + )"; + THEN("Correct mechanism registration function is called") { + std::string expected_placeholder_point_reg = + "_pointtype = point_register_mech(mechanism_info, nrn_alloc_test_pp, nullptr, " + "nullptr, nullptr, nrn_init_test_pp, hoc_nrnpointerindex, 1, _hoc_create_pnt, " + "_hoc_destroy_pnt, _member_func);"; + + auto const generated = reindent_and_trim_text(get_neuron_cpp_code(nmodl_text)); + + REQUIRE_THAT(generated, + ContainsSubstring(reindent_and_trim_text(expected_placeholder_point_reg))); + } + } } diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index f8bf1f71bc..8d85339114 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,4 +1,4 @@ -set(NMODL_USECASE_DIRS cnexp_scalar cnexp_array global_breakpoint) +set(NMODL_USECASE_DIRS cnexp_scalar cnexp_array global_breakpoint point_process parameter) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/parameter/simulate.py b/test/nmodl/transpiler/usecases/parameter/simulate.py new file mode 100644 index 0000000000..7fe9ad6f1f --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/simulate.py @@ -0,0 +1,12 @@ +from neuron import h + +s = h.Section() + +test_parameter_pp = h.test_parameter(s(0.5)) + +assert test_parameter_pp.x == 42. + +test_parameter_pp.x = 42.1 + +assert test_parameter_pp.x == 42.1 + diff --git a/test/nmodl/transpiler/usecases/parameter/test_parameter.mod b/test/nmodl/transpiler/usecases/parameter/test_parameter.mod new file mode 100644 index 0000000000..417ec90fe1 --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/test_parameter.mod @@ -0,0 +1,8 @@ +NEURON { + POINT_PROCESS test_parameter + RANGE x +} + +PARAMETER { + x = 42 +} diff --git a/test/nmodl/transpiler/usecases/point_process/simulate.py b/test/nmodl/transpiler/usecases/point_process/simulate.py new file mode 100644 index 0000000000..f9d910e032 --- /dev/null +++ b/test/nmodl/transpiler/usecases/point_process/simulate.py @@ -0,0 +1,15 @@ +from neuron import h + +test_pp = h.test_pp() + +assert test_pp.has_loc() == 0 + +nseg = 1 + +s = h.Section() + +test_pp_s = h.test_pp(s(0.5)) + +assert test_pp_s.has_loc() == 1 + +assert test_pp_s.get_segment() == s(0.5) diff --git a/test/nmodl/transpiler/usecases/point_process/test_pp.mod b/test/nmodl/transpiler/usecases/point_process/test_pp.mod new file mode 100644 index 0000000000..d9ca5cdfe8 --- /dev/null +++ b/test/nmodl/transpiler/usecases/point_process/test_pp.mod @@ -0,0 +1,3 @@ +NEURON { + POINT_PROCESS test_pp +} From 8248d593e08d7cbe28f16b72bc93005dd9f89f7a Mon Sep 17 00:00:00 2001 From: Ioannis Magkanaris Date: Sun, 11 Feb 2024 09:30:03 +0100 Subject: [PATCH 581/871] Support for FUNCTION and PROCEDURE in NEURON code generation (BlueBrain/nmodl#1141) * Added support for the usage of FUNCTION and PROCEDURES * hoc and python wrappers registration also done * Corresponding tests added NMODL Repo SHA: BlueBrain/nmodl@0b39872fc41c17b09d1be198106e3a324a8e90de --- src/nmodl/codegen/codegen_helper_visitor.cpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 + .../codegen/codegen_neuron_cpp_visitor.cpp | 286 ++++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.hpp | 40 +++ .../codegen/codegen_neuron_cpp_visitor.cpp | 2 + test/nmodl/transpiler/usecases/CMakeLists.txt | 9 +- .../usecases/func_proc/func_proc.mod | 25 ++ .../transpiler/usecases/func_proc/simulate.py | 15 + .../usecases/func_proc_pnt/func_proc_pnt.mod | 20 ++ .../usecases/func_proc_pnt/simulate.py | 15 + 10 files changed, 394 insertions(+), 22 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/func_proc/func_proc.mod create mode 100644 test/nmodl/transpiler/usecases/func_proc/simulate.py create mode 100644 test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod create mode 100644 test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 72dc2508f2..8655e28b86 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -491,6 +491,7 @@ void CodegenHelperVisitor::visit_suffix(const Suffix& node) { info.point_process = true; } info.mod_suffix = node.get_node_name(); + info.rsuffix = info.point_process ? "" : "_" + info.mod_suffix; } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 2f626e031b..3a0d4b1662 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -201,6 +201,9 @@ struct CodegenInfo { /// name of the suffix std::string mod_suffix; + /// point process range and functions don't have suffix + std::string rsuffix; + /// true if mod file is vectorizable (which should be always true for coreneuron) /// But there are some blocks like LINEAR are not thread safe in neuron or mod2c /// context. In this case vectorize is used to determine number of float variable diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 731af4d03f..6f06aada85 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -17,6 +17,7 @@ #include "codegen/codegen_utils.hpp" #include "config/config.h" #include "utils/string_utils.hpp" +#include "visitors/rename_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { @@ -24,6 +25,8 @@ namespace codegen { using namespace ast; +using visitor::RenameVisitor; + using symtab::syminfo::NmodlType; @@ -98,8 +101,8 @@ bool CodegenNeuronCppVisitor::optimize_ion_variable_copies() const { void CodegenNeuronCppVisitor::print_point_process_function_definitions() { if (info.point_process) { - printer->add_line("/* Point Process specific functions */"); printer->add_multi_line(R"CODE( + /* Point Process specific functions */ static void* _hoc_create_pnt(Object* _ho) { return create_point_process(_pointtype, _ho); } @@ -128,13 +131,11 @@ void CodegenNeuronCppVisitor::print_point_process_function_definitions() { static double _hoc_loc_pnt(void* _vptr) { return loc_point_process(_pointtype, _vptr); } - )CODE"); - printer->add_multi_line(R"CODE( + static double _hoc_has_loc(void* _vptr) { return has_loc_point(_vptr); } - )CODE"); - printer->add_multi_line(R"CODE( + static double _hoc_get_loc_pnt(void* _vptr) { return (get_loc_point_process(_vptr)); } @@ -171,13 +172,24 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { } else { printer->push_block("static void _hoc_setdata()"); printer->add_multi_line(R"CODE( - Prop *_prop, *hoc_getdata_range(int); - _prop = hoc_getdata_range(mech_type); + Prop *_prop = hoc_getdata_range(mech_type); _setdata(_prop); hoc_retpushx(1.); )CODE"); } printer->pop_block(); + + printer->add_line("/* Mechanism procedures and functions */"); + for (const auto& node: info.functions) { + print_function_declaration(*node, node->get_node_name()); + printer->add_text(';'); + printer->add_newline(); + } + for (const auto& node: info.procedures) { + print_function_declaration(*node, node->get_node_name()); + printer->add_text(';'); + printer->add_newline(); + } } @@ -192,28 +204,169 @@ void CodegenNeuronCppVisitor::print_function_prototypes() { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node, const std::string& name) { - return; + printer->add_newline(2); + print_function_declaration(node, name); + printer->add_text(" "); + printer->push_block(); + + printer->fmt_line("auto inst = make_instance_{}(*_ml);", info.mod_suffix); + + // function requires return variable declaration + if (node.is_function_block()) { + auto type = default_float_data_type(); + printer->fmt_line("{} ret_{} = 0.0;", type, name); + } else { + printer->fmt_line("int ret_{} = 0;", name); + } + + print_statement_block(*node.get_statement_block(), false, false); + printer->fmt_line("return ret_{};", name); + printer->pop_block(); } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function_procedure_helper(const ast::Block& node) { - return; + auto name = node.get_node_name(); + + if (info.function_uses_table(name)) { + throw std::runtime_error("Function tables not implemented."); + } else { + print_function_or_procedure(node, name); + } } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_procedure(const ast::ProcedureBlock& node) { - return; + print_function_procedure_helper(node); } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function(const ast::FunctionBlock& node) { - return; + auto name = node.get_node_name(); + + // name of return variable + std::string return_var; + if (info.function_uses_table(name)) { + throw std::runtime_error("Function tables not implemented."); + } else { + return_var = "ret_" + name; + } + + // first rename return variable name + auto block = node.get_statement_block().get(); + RenameVisitor v(name, return_var); + block->accept(v); + + print_function_procedure_helper(node); +} + +void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( + const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type) { + if (info.point_process && wrapper_type == InterpreterWrapper::Python) { + return; + } + const auto block_name = function_or_procedure_block->get_node_name(); + if (info.point_process) { + printer->fmt_push_block("static double _hoc_{}(void* _vptr)", block_name); + } else if (wrapper_type == InterpreterWrapper::HOC) { + printer->fmt_push_block("static void _hoc_{}(void)", block_name); + } else { + printer->fmt_push_block("static double _npy_{}(Prop* _prop)", block_name); + } + printer->add_multi_line(R"CODE( + double _r{}; + Datum* _ppvar; + Datum* _thread; + NrnThread* _nt; + )CODE"); + if (info.point_process) { + printer->add_multi_line(R"CODE( + auto* const _pnt = static_cast(_vptr); + auto* const _p = _pnt->prop; + if (!_p) { + hoc_execerror("POINT_PROCESS data instance not valid", NULL); + } + _nrn_mechanism_cache_instance _ml_real{_p}; + auto* const _ml = &_ml_real; + size_t const id{}; + _ppvar = _nrn_mechanism_access_dparam(_p); + _thread = _extcall_thread.data(); + _nt = static_cast(_pnt->_vnt); + )CODE"); + } else if (wrapper_type == InterpreterWrapper::HOC) { + if (program_symtab->lookup(block_name)->has_all_properties(NmodlType::use_range_ptr_var)) { + printer->push_block("if (!_prop_id)"); + printer->fmt_line( + "hoc_execerror(\"No data for {}_{}. Requires prior call to setdata_{} and that the " + "specified mechanism instance still be in existence.\", NULL);", + function_or_procedure_block->get_node_name(), + info.mod_suffix, + info.mod_suffix); + printer->pop_block(); + printer->add_line("Prop* _local_prop = _extcall_prop;"); + } else { + printer->add_line("Prop* _local_prop = _prop_id ? _extcall_prop : nullptr;"); + } + printer->add_multi_line(R"CODE( + _nrn_mechanism_cache_instance _ml_real{_local_prop}; + auto* const _ml = &_ml_real; + size_t const id{}; + _ppvar = _local_prop ? _nrn_mechanism_access_dparam(_local_prop) : nullptr; + _thread = _extcall_thread.data(); + _nt = nrn_threads; + )CODE"); + } else { // wrapper_type == InterpreterWrapper::Python + printer->add_multi_line(R"CODE( + _nrn_mechanism_cache_instance _ml_real{_prop}; + auto* const _ml = &_ml_real; + size_t const id{}; + _ppvar = _nrn_mechanism_access_dparam(_prop); + _thread = _extcall_thread.data(); + _nt = nrn_threads; + )CODE"); + } + if (info.function_uses_table(block_name)) { + printer->fmt_line("_check_{}({})", block_name, internal_method_arguments()); + } + const auto get_func_call_str = [&]() { + const auto params = function_or_procedure_block->get_parameters(); + const auto func_proc_name = block_name + "_" + info.mod_suffix; + auto func_call = fmt::format("{}({}", func_proc_name, internal_method_arguments()); + for (int i = 0; i < params.size(); ++i) { + func_call.append(fmt::format(", *getarg({})", i + 1)); + } + func_call.append(")"); + return func_call; + }; + if (function_or_procedure_block->is_function_block()) { + printer->add_indent(); + printer->fmt_text("_r = {};", get_func_call_str()); + printer->add_newline(); + } else { + printer->add_line("_r = 1.;"); + printer->fmt_line("{};", get_func_call_str()); + } + if (info.point_process || wrapper_type != InterpreterWrapper::HOC) { + printer->add_line("return(_r);"); + } else if (wrapper_type == InterpreterWrapper::HOC) { + printer->add_line("hoc_retpushx(_r);"); + } + printer->pop_block(); +} + + +void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { + for (const auto& procedure: info.procedures) { + print_hoc_py_wrapper_function_body(procedure, InterpreterWrapper::HOC); + print_hoc_py_wrapper_function_body(procedure, InterpreterWrapper::Python); + } + for (const auto& function: info.functions) { + print_hoc_py_wrapper_function_body(function, InterpreterWrapper::HOC); + print_hoc_py_wrapper_function_body(function, InterpreterWrapper::Python); + } } @@ -222,15 +375,19 @@ void CodegenNeuronCppVisitor::print_function(const ast::FunctionBlock& node) { /****************************************************************************************/ -/// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::internal_method_arguments() { - return {}; + return "_ml, id, _ppvar, _thread, _nt"; } -/// TODO: Edit for NEURON CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { - return {}; + ParamVector params; + params.emplace_back("", "_nrn_mechanism_cache_range*", "", "_ml"); + params.emplace_back("", "size_t", "", "id"); + params.emplace_back("", "Datum*", "", "_ppvar"); + params.emplace_back("", "Datum*", "", "_thread"); + params.emplace_back("", "NrnThread*", "", "_nt"); + return params; } @@ -270,6 +427,33 @@ std::string CodegenNeuronCppVisitor::register_mechanism_arguments() const { }; +std::string CodegenNeuronCppVisitor::hoc_function_name( + const std::string& function_or_procedure_name) const { + return fmt::format("_hoc_{}", function_or_procedure_name); +} + + +std::string CodegenNeuronCppVisitor::hoc_function_signature( + const std::string& function_or_procedure_name) const { + return fmt::format("static {} {}(void{})", + info.point_process ? "double" : "void", + hoc_function_name(function_or_procedure_name), + info.point_process ? "*" : ""); +} + + +std::string CodegenNeuronCppVisitor::py_function_name( + const std::string& function_or_procedure_name) const { + return fmt::format("_npy_{}", function_or_procedure_name); +} + + +std::string CodegenNeuronCppVisitor::py_function_signature( + const std::string& function_or_procedure_name) const { + return fmt::format("static double {}(Prop*)", py_function_name(function_or_procedure_name)); +} + + /****************************************************************************************/ /* Code-specific printing routines for code generation */ /****************************************************************************************/ @@ -549,6 +733,8 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in ? static_cast(info.pointer_variables.size()) : -1); + printer->add_line("static _nrn_mechanism_std_vector _extcall_thread;"); + // Start printing the CNRN-style global variables. auto float_type = default_float_data_type(); printer->add_newline(2); @@ -613,7 +799,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in print_global_var_struct_decl(); } - /// TODO: Same as CoreNEURON? void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { /// TODO: Write HocParmLimits and other HOC global variables (delta_t) @@ -663,6 +848,27 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->decrease_indent(); printer->add_line("};"); + printer->add_newline(2); + printer->add_line("/* declaration of user functions */"); + for (const auto& procedure: info.procedures) { + const auto proc_name = procedure->get_node_name(); + printer->fmt_line("{};", hoc_function_signature(proc_name)); + } + for (const auto& function: info.functions) { + const auto func_name = function->get_node_name(); + printer->fmt_line("{};", hoc_function_signature(func_name)); + } + if (!info.point_process) { + for (const auto& procedure: info.procedures) { + const auto proc_name = procedure->get_node_name(); + printer->fmt_line("{};", py_function_signature(proc_name)); + } + for (const auto& function: info.functions) { + const auto func_name = function->get_node_name(); + printer->fmt_line("{};", py_function_signature(func_name)); + } + } + printer->add_newline(2); printer->add_line("/* connect user functions to hoc names */"); printer->add_line("static VoidFunc hoc_intfunc[] = {"); @@ -681,11 +887,36 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->fmt_line("{{\"setdata_{}\", _hoc_setdata}},", info.mod_suffix); } - /// TODO: Add _hoc_procedures and _hoc_functions + for (const auto& procedure: info.procedures) { + const auto proc_name = procedure->get_node_name(); + printer->fmt_line("{{\"{}{}\", {}}},", + proc_name, + info.rsuffix, + hoc_function_name(proc_name)); + } + for (const auto& function: info.functions) { + const auto func_name = function->get_node_name(); + printer->fmt_line("{{\"{}{}\", {}}},", + func_name, + info.rsuffix, + hoc_function_name(func_name)); + } printer->add_line("{0, 0}"); printer->decrease_indent(); printer->add_line("};"); + if (!info.point_process) { + printer->push_block("static NPyDirectMechFunc npy_direct_func_proc[] ="); + for (const auto& procedure: info.procedures) { + const auto proc_name = procedure->get_node_name(); + printer->fmt_line("{{\"{}\", {}}},", proc_name, py_function_name(proc_name)); + } + for (const auto& function: info.functions) { + const auto func_name = function->get_node_name(); + printer->fmt_line("{{\"{}\", {}}},", func_name, py_function_name(func_name)); + } + printer->pop_block(";"); + } } void CodegenNeuronCppVisitor::print_make_instance() const { @@ -856,6 +1087,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { info.semantics[i].name); } + printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc);"); + if (!info.point_process) { + printer->add_line("hoc_register_npy_direct(mech_type, npy_direct_func_proc);"); + } printer->pop_block(); } @@ -1227,6 +1462,8 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { if (info.point_process) { printer->add_line("extern Prop* nrn_point_prop_;"); + } else { + printer->add_line("Prop* hoc_getdata_range(int type);"); } /// TODO: More prints here? } @@ -1272,6 +1509,13 @@ void CodegenNeuronCppVisitor::print_g_unused() const { /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_compute_functions() { + print_hoc_py_wrapper_function_definitions(); + for (const auto& procedure: info.procedures) { + print_procedure(*procedure); + } + for (const auto& function: info.functions) { + print_function(*function); + } print_nrn_init(); print_nrn_cur(); print_nrn_state(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c15c36d68e..730e9ceec9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -42,6 +42,14 @@ namespace codegen { using printer::CodePrinter; +/** + * @brief Enum to switch between HOC and Python wrappers for functions and + * procedures defined in mechanisms + * + */ +enum InterpreterWrapper { HOC, Python }; + + /** * \defgroup codegen_backends Codegen Backends * \ingroup codegen @@ -196,6 +204,13 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_function(const ast::FunctionBlock& node) override; + void print_hoc_py_wrapper_function_body(const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type); + + + void print_hoc_py_wrapper_function_definitions(); + + /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ @@ -263,6 +278,31 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { const std::string& concentration, int index) override; + /** + * All functions and procedures need a \c _hoc_ to be available to the HOC + * interpreter + */ + std::string hoc_function_name(const std::string& function_or_procedure_name) const; + + + /** + * Get the signature of the \c _hoc_ function + */ + std::string hoc_function_signature(const std::string& function_or_procedure_name) const; + + + /** + * In non POINT_PROCESS mechanisms all functions and procedures need a \c + * _py_ to be available to the HOC interpreter + */ + std::string py_function_name(const std::string& function_or_procedure_name) const; + + /** + * Get the signature of the \c _npy_ function + */ + std::string py_function_signature(const std::string& function_or_procedure_name) const; + + /****************************************************************************************/ /* Code-specific printing routines for code generations */ /****************************************************************************************/ diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 525e9fba84..9cd576f514 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -275,6 +275,8 @@ void _nrn_mechanism_register_data_fields(Args&&... args) { hoc_register_dparam_semantics(mech_type, 0, "na_ion"); hoc_register_dparam_semantics(mech_type, 1, "na_ion"); hoc_register_dparam_semantics(mech_type, 2, "na_ion"); + hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc); + hoc_register_npy_direct(mech_type, npy_direct_func_proc); })CODE"; REQUIRE_THAT(generated, diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 8d85339114..d18ef2a762 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,4 +1,11 @@ -set(NMODL_USECASE_DIRS cnexp_scalar cnexp_array global_breakpoint point_process parameter) +set(NMODL_USECASE_DIRS + cnexp_scalar + cnexp_array + global_breakpoint + point_process + parameter + func_proc + func_proc_pnt) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod new file mode 100644 index 0000000000..b8ad6b46ce --- /dev/null +++ b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod @@ -0,0 +1,25 @@ +NEURON { + SUFFIX test_func_proc + RANGE x +} + +ASSIGNED { + x +} + +PROCEDURE set_x_42() { + set_x_a(42) +} + +PROCEDURE set_x_a(a) { + x = a +} + +FUNCTION get_a_42(a) { + get_a_42 = a + 42 +} + +PROCEDURE set_a_x() { + LOCAL a + a = x +} diff --git a/test/nmodl/transpiler/usecases/func_proc/simulate.py b/test/nmodl/transpiler/usecases/func_proc/simulate.py new file mode 100644 index 0000000000..261ec54076 --- /dev/null +++ b/test/nmodl/transpiler/usecases/func_proc/simulate.py @@ -0,0 +1,15 @@ +from neuron import h + +s = h.Section() + +s.insert("test_func_proc") + +s(0.5).test_func_proc.set_x_42() + +assert s(0.5).test_func_proc.x == 42 + +s(0.5).test_func_proc.set_x_a(13.7) + +assert s(0.5).test_func_proc.x == 13.7 + +assert s(0.5).test_func_proc.get_a_42(42) == 84 diff --git a/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod b/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod new file mode 100644 index 0000000000..b97ccd03a0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod @@ -0,0 +1,20 @@ +NEURON { + POINT_PROCESS test_func_proc_pnt + RANGE x +} + +ASSIGNED { + x +} + +PROCEDURE set_x_42() { + x = 42 +} + +PROCEDURE set_x_a(a) { + x = a +} + +FUNCTION get_a_42(a) { + get_a_42 = a + 42 +} diff --git a/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py b/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py new file mode 100644 index 0000000000..41c22b4601 --- /dev/null +++ b/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py @@ -0,0 +1,15 @@ +from neuron import h + +s = h.Section() + +pnt_proc = h.test_func_proc_pnt(s(0.5)) + +pnt_proc.set_x_42() + +assert pnt_proc.x == 42 + +pnt_proc.set_x_a(13.7) + +assert pnt_proc.x == 13.7 + +assert pnt_proc.get_a_42(42) == 84 From da1764bc14a4abffb3fdf0e46ad43382e20da1cd Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 15 Feb 2024 15:49:30 +0100 Subject: [PATCH 582/871] Remove POSIX-specific headers (BlueBrain/nmodl#1156) NMODL Repo SHA: BlueBrain/nmodl@778e81e2fd8e4bd53f567b9c327036869045d52e --- src/nmodl/utils/common_utils.cpp | 1 - src/nmodl/utils/common_utils.hpp | 2 -- src/nmodl/utils/file_library.cpp | 2 -- 3 files changed, 5 deletions(-) diff --git a/src/nmodl/utils/common_utils.cpp b/src/nmodl/utils/common_utils.cpp index 9bcdeeb8f5..b9f79923a3 100644 --- a/src/nmodl/utils/common_utils.cpp +++ b/src/nmodl/utils/common_utils.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) #define IS_WINDOWS diff --git a/src/nmodl/utils/common_utils.hpp b/src/nmodl/utils/common_utils.hpp index 147d46099e..539046416d 100644 --- a/src/nmodl/utils/common_utils.hpp +++ b/src/nmodl/utils/common_utils.hpp @@ -10,8 +10,6 @@ #include #include #include -#include -#include #include diff --git a/src/nmodl/utils/file_library.cpp b/src/nmodl/utils/file_library.cpp index 9293ec442b..9fb6b27170 100644 --- a/src/nmodl/utils/file_library.cpp +++ b/src/nmodl/utils/file_library.cpp @@ -9,8 +9,6 @@ #include #include -#include -#include #include "utils/common_utils.hpp" #include "utils/string_utils.hpp" From 4263a7d0bbf42baecaf0351ed1818b7358d92938 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 15 Feb 2024 16:54:56 +0100 Subject: [PATCH 583/871] Rename `parser.py` to `language_parser.py` (BlueBrain/nmodl#1157) This is to avoid confusion with the built-in `parser` module (removed since Python 3.10). NMODL Repo SHA: BlueBrain/nmodl@ec0db70590e72e89ae56ae5e2cd992abb3b40f3f --- src/nmodl/language/code_generator.cmake | 2 +- src/nmodl/language/code_generator.py | 2 +- src/nmodl/language/{parser.py => language_parser.py} | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename src/nmodl/language/{parser.py => language_parser.py} (100%) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 90705ac3df..0c6ccae181 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -36,7 +36,7 @@ set(CODE_GENERATOR_PY_FILES ${PROJECT_SOURCE_DIR}/src/language/code_generator.py ${PROJECT_SOURCE_DIR}/src/language/node_info.py ${PROJECT_SOURCE_DIR}/src/language/nodes.py - ${PROJECT_SOURCE_DIR}/src/language/parser.py + ${PROJECT_SOURCE_DIR}/src/language/language_parser.py ${PROJECT_SOURCE_DIR}/src/language/utils.py ) diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 63fa9743dd..f2d09ae49a 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -18,7 +18,7 @@ import jinja2 -from parser import LanguageParser +from language_parser import LanguageParser import node_info import utils diff --git a/src/nmodl/language/parser.py b/src/nmodl/language/language_parser.py similarity index 100% rename from src/nmodl/language/parser.py rename to src/nmodl/language/language_parser.py From b1605bbc2da10c717cfdde2f0d1b0b1c5e4f3e93 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 22 Feb 2024 06:54:57 +0100 Subject: [PATCH 584/871] Update CONTRIBUTING.rst and docs. (BlueBrain/nmodl#1159) NMODL Repo SHA: BlueBrain/nmodl@1626deedb16bbf25f45fc9f69ca4eb30e9dabe84 --- .../nmodl/transpiler/CONTRIBUTING.rst | 30 +++++-------------- src/nmodl/nmodl/README.rst | 2 +- 2 files changed, 8 insertions(+), 24 deletions(-) diff --git a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst index 09adb0d90e..abb1c7584b 100644 --- a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst +++ b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst @@ -152,34 +152,18 @@ pull the changes from the main (upstream) repository: Development Conventions ------------------------ -If you are developing NMODL, make sure to enable both -``NMODL_FORMATTING`` and ``NMODL_PRECOMMIT`` CMake variables to ensure -that your contributions follow the coding conventions of this project: +Formatting +~~~~~~~~~~ -.. code:: cmake +Run the HPC coding conventions formatter to format all source files: - cmake -DNMODL_FORMATTING:BOOL=ON -DNMODL_PRECOMMIT:BOOL=ON +.. code:: bash -The first variable provides the following additional targets to format -C, C++, and CMake files: + cmake/hpc-coding-conventions/bin/format -:: - - make clang-format cmake-format - -The second option activates Git hooks that will discard commits that do -not comply with coding conventions of this project. These 2 CMake -variables require additional utilities: - -- `ClangFormat - 7 `__ -- `cmake-format `__ -- `pre-commit `__ +The HPC coding conventions formatter installs any dependencies into a Python +virtual environment. -clang-format can be installed on Linux thanks to `LLVM apt -page `__. On MacOS, you can simply install llvm -with brew: ``brew install llvm``. *cmake-format* and *pre-commit* -utilities can be installed with *pip*. Validate the Python package ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/src/nmodl/nmodl/README.rst b/src/nmodl/nmodl/README.rst index 1225788901..a2299ef10d 100644 --- a/src/nmodl/nmodl/README.rst +++ b/src/nmodl/nmodl/README.rst @@ -24,7 +24,7 @@ NMODL as a domain specific language (DSL) to describe a wide range of membrane and intracellular submodels. Here is an example of exponential synapse specified in NMODL: -.. code:: python +.. code:: NEURON { POINT_PROCESS ExpSyn From df4b0643bc99666337acf0b6d87b876ee0b00572 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 22 Feb 2024 11:27:59 +0100 Subject: [PATCH 585/871] Empty circle ci config. (BlueBrain/nmodl#1163) NMODL Repo SHA: BlueBrain/nmodl@9df433bf7473d9ef908f9ea0645d7296d340437a --- .circleci/config.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..32f6af387a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,19 @@ +# NOTE this file serves one purpose: to silence Circle CI. +# Delete at will. +# +# Copied&modified from: https://circleci.com/docs/sample-config/ +version: 2.1 + +# Define the jobs we want to run for this project +jobs: + build: + docker: + - image: cimg/base:2023.03 + steps: + - run: echo "this is the build job" + +# Orchestrate our job run sequence +workflows: + build_and_test: + jobs: + - build From abfa540481d312423627f28a56c861b404865aca Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 23 Feb 2024 10:25:06 +0100 Subject: [PATCH 586/871] Fix rendering LaTeX in documentation. (BlueBrain/nmodl#1167) Use `imgmath_embed = True` to work around a path bug for files/notebooks located in subfolders. Use `imgmath_image_format = 'svg'` because PNG results in pixalated images. NMODL Repo SHA: BlueBrain/nmodl@c0eb1ac07bcfcb80189170819c52e4d7645f7c72 --- docs/nmodl/transpiler/conf.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index 8bfe193232..e2695fb9e0 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -185,6 +185,10 @@ (master_doc, "nmodl.tex", "nmodl Documentation", "BlueBrain HPC team", "manual") ] +imgmath_image_format = 'svg' +imgmath_embed = True +imgmath_font_size = 14 + # -- Options for manual page output --------------------------------------- From 9e302173d9bf15831d7faacdec3e908499bcf301 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 23 Feb 2024 16:58:51 +0100 Subject: [PATCH 587/871] Simplify Codegen ctors. (BlueBrain/nmodl#1171) NMODL Repo SHA: BlueBrain/nmodl@bc913ef90b340e69cb35636f99866a220ac2a9d9 --- src/nmodl/codegen/codegen_acc_visitor.hpp | 18 +---- .../codegen_coreneuron_cpp_visitor.hpp | 48 +---------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 80 ++++++++++++------- .../codegen/codegen_neuron_cpp_visitor.hpp | 48 +---------- 4 files changed, 63 insertions(+), 131 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 80bcba9d68..959b00aeb0 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -28,7 +28,11 @@ namespace codegen { * \brief %Visitor for printing C++ code with OpenACC backend */ class CodegenAccVisitor: public CodegenCoreneuronCppVisitor { + public: + using CodegenCoreneuronCppVisitor::CodegenCoreneuronCppVisitor; + protected: + /// name of the code generation backend std::string backend_name() const override; @@ -133,20 +137,6 @@ class CodegenAccVisitor: public CodegenCoreneuronCppVisitor { /// Replace default implementation by a no-op /// since the buffer cannot be grown up during gpu execution void print_net_send_buffering_grow() override; - - - public: - CodegenAccVisitor(const std::string& mod_file, - const std::string& output_dir, - const std::string& float_type, - bool optimize_ionvar_copies) - : CodegenCoreneuronCppVisitor(mod_file, output_dir, float_type, optimize_ionvar_copies) {} - - CodegenAccVisitor(const std::string& mod_file, - std::ostream& stream, - const std::string& float_type, - bool optimize_ionvar_copies) - : CodegenCoreneuronCppVisitor(mod_file, stream, float_type, optimize_ionvar_copies) {} }; /** \} */ // end of codegen_backends diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 9c43a4cbe0..43d2b45e87 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -60,6 +60,9 @@ using printer::CodePrinter; * have removed return from verbatim block. */ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { + public: + using CodegenCppVisitor::CodegenCppVisitor; + protected: /****************************************************************************************/ /* Member variables */ @@ -1098,51 +1101,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { public: - /** - * \brief Constructs the C++ code generator visitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * directly to a file in \c [output_dir]/[mod_filename].cpp. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param output_dir The directory where target C++ file should be generated. - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenCoreneuronCppVisitor(std::string mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies) - : CodegenCppVisitor(mod_filename, output_dir, float_type, optimize_ionvar_copies) {} - - /** - * \copybrief nmodl::codegen::CodegenCoreneuronCppVisitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * into an output stream. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param stream The output stream onto which to write the generated code - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenCoreneuronCppVisitor(std::string mod_filename, - std::ostream& stream, - std::string float_type, - const bool optimize_ionvar_copies) - : CodegenCppVisitor(mod_filename, stream, float_type, optimize_ionvar_copies) {} - - /****************************************************************************************/ /* Public printing routines for code generation for use in unit tests */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index b3d5511da3..e089e9b6b9 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -178,6 +178,59 @@ using printer::CodePrinter; * have removed return from verbatim block. */ class CodegenCppVisitor: public visitor::ConstAstVisitor { + public: + /** + * \brief Constructs the C++ code generator visitor + * + * This constructor instantiates an NMODL C++ code generator and allows writing generated code + * directly to a file in \c [output_dir]/[mod_filename].cpp. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C++ code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param output_dir The directory where target C++ file should be generated. + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ + CodegenCppVisitor(std::string mod_filename, + const std::string& output_dir, + std::string float_type, + const bool optimize_ionvar_copies) + : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp")) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) + , optimize_ionvar_copies(optimize_ionvar_copies) {} + + + /** + * \brief Constructs the C++ code generator visitor + * + * This constructor instantiates an NMODL C++ code generator and allows writing generated code + * into an output stream. + * + * \note No code generation is performed at this stage. Since the code + * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c + * visit_program in order to generate the C++ code corresponding to the AST. + * + * \param mod_filename The name of the model for which code should be generated. + * It is used for constructing an output filename. + * \param stream The output stream onto which to write the generated code + * \param float_type The float type to use in the generated code. The string will be used + * as-is in the target code. This defaults to \c double. + */ + CodegenCppVisitor(std::string mod_filename, + std::ostream& stream, + std::string float_type, + const bool optimize_ionvar_copies) + : printer(std::make_unique(stream)) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) + , optimize_ionvar_copies(optimize_ionvar_copies) {} + + protected: using SymbolType = std::shared_ptr; @@ -1217,33 +1270,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void print_nmodl_constants(); - /****************************************************************************************/ - /* Protected constructors */ - /****************************************************************************************/ - - - /// This constructor is private, only the derived classes' public constructors are public - CodegenCppVisitor(std::string mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies) - : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp")) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - - /// This constructor is private, only the derived classes' public constructors are public - CodegenCppVisitor(std::string mod_filename, - std::ostream& stream, - std::string float_type, - const bool optimize_ionvar_copies) - : printer(std::make_unique(stream)) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - /****************************************************************************************/ /* Overloaded visitor routines */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 730e9ceec9..0d30fa2111 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -69,6 +69,9 @@ enum InterpreterWrapper { HOC, Python }; * have removed return from verbatim block. */ class CodegenNeuronCppVisitor: public CodegenCppVisitor { + public: + using CodegenCppVisitor::CodegenCppVisitor; + protected: /****************************************************************************************/ /* Member variables */ @@ -640,51 +643,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { public: - /** - * \brief Constructs the C++ code generator visitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * directly to a file in \c [output_dir]/[mod_filename].cpp. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param output_dir The directory where target C++ file should be generated. - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenNeuronCppVisitor(std::string mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies) - : CodegenCppVisitor(mod_filename, output_dir, float_type, optimize_ionvar_copies) {} - - /** - * \copybrief nmodl::codegen::CodegenNeuronCppVisitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * into an output stream. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param stream The output stream onto which to write the generated code - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenNeuronCppVisitor(std::string mod_filename, - std::ostream& stream, - std::string float_type, - const bool optimize_ionvar_copies) - : CodegenCppVisitor(mod_filename, stream, float_type, optimize_ionvar_copies) {} - - /****************************************************************************************/ /* Public printing routines for code generation for use in unit tests */ /****************************************************************************************/ From a3abb220b03136a0c5b4aef4f9e800cc3e54c2df Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 26 Feb 2024 10:44:16 +0100 Subject: [PATCH 588/871] Document NONSPECIFIC_CURRENT. (BlueBrain/nmodl#1162) NMODL Repo SHA: BlueBrain/nmodl@af265f3a0a996837848607f4a27b8ee125a1d514 --- docs/nmodl/transpiler/contents/currents.rst | 61 +++ .../transpiler/images/currents_circuit.svg | 482 ++++++++++++++++++ .../transpiler/images/currents_types.svg | 251 +++++++++ docs/nmodl/transpiler/index.rst | 1 + 4 files changed, 795 insertions(+) create mode 100644 docs/nmodl/transpiler/contents/currents.rst create mode 100644 docs/nmodl/transpiler/images/currents_circuit.svg create mode 100644 docs/nmodl/transpiler/images/currents_types.svg diff --git a/docs/nmodl/transpiler/contents/currents.rst b/docs/nmodl/transpiler/contents/currents.rst new file mode 100644 index 0000000000..f320391458 --- /dev/null +++ b/docs/nmodl/transpiler/contents/currents.rst @@ -0,0 +1,61 @@ +Currents +======== + +Current refer to the part of the membrane currents due to mechanisms. Meaning +the represent the rate at which charge crosses the membrane, e.g. due to ions +passing through ion channels. In Figure 1 we show the electrical circuit for a +couple of segments near a branch point. The effect of the capacitor (when +present) is handled by NEURON internally. In MOD files we compute the current +represented by the white box, labeled ``I``. This total current is the sum of +one current per mechanism. The current per mechanism in turn is the sum of +non-specific currents and ion currents, see Figure 2. + + +.. figure:: ../images/currents_circuit.svg + + Figure 1: Wiring diagram of four segments near a branch point. The circuit for computing the total trans-membrane current. For nodes representing a segment with non-zero aread the circuit for total trans-membrane current consists of a capacitor and currents due to mechanisms (labelled ``I``). The nodes with zero-area, the there's no capacitor, but point-processes, can still introduce current at that point. + + +.. figure:: ../images/currents_types.svg + + Figure 2: A detailed view of the the current ``I`` due to mechanisms. The current ``I`` is the sum of one total current per mechanism, illustrated as boxes of different colors. For each mechanism the total current consists of nonspecific currents (``i1``) and ion currents (``ina`` and ``ica``), illustrated only in the light yellow box. + + +There's a total current, required by the cable equation, which is discussed +here. The rate at which ions cross the membrane, called ion currents, can be +computed for individual ions, separately. Please check the documentation on +Ions. + + +NMODL Keywords +-------------- + +NONSPECIFIC_CURRENT +~~~~~~~~~~~~~~~~~~~ + +A nonspecific current is how an additive current contribution is expressed that's not accociated with any specific ion. It has the following syntax + +.. code-block:: + + NEURON { + NONSPECIFIC_CURRENT + } + +We'll assume the name is ``i1``. + +Since it needs to be assigned a value in the MOD file, it needs to also be listed in the ``ASSIGNED`` block, e.g. as follows (units show are for a density mechanism). + +.. code-block:: + + ASSIGNED { + (mA/cm2) + } + +The nonspecific current is local to the mechanism and can be stored as a +regular range variable. + +The generated code must ensure the following: + +1. The total current contribution is updated by ``i1``. +2. Continue with the remaining current related computations, such as the finite + difference for computing ``dI/dv``. diff --git a/docs/nmodl/transpiler/images/currents_circuit.svg b/docs/nmodl/transpiler/images/currents_circuit.svg new file mode 100644 index 0000000000..9769758947 --- /dev/null +++ b/docs/nmodl/transpiler/images/currents_circuit.svg @@ -0,0 +1,482 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + I + + + + + + diff --git a/docs/nmodl/transpiler/images/currents_types.svg b/docs/nmodl/transpiler/images/currents_types.svg new file mode 100644 index 0000000000..4029536e8b --- /dev/null +++ b/docs/nmodl/transpiler/images/currents_types.svg @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + i1 + + ina + + ica + + diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index f923c6723b..33a6f992c6 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -19,6 +19,7 @@ About NMODL :caption: Contents: contents/visitors + contents/currents contents/ions contents/pointers From 1502719244ebc071987f0b5e72afe878b78816f4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 26 Feb 2024 10:44:26 +0100 Subject: [PATCH 589/871] Document ions at zero-area nodes. (BlueBrain/nmodl#1164) NMODL Repo SHA: BlueBrain/nmodl@b10adb67a7c20484a41d8c9526c226870f887466 --- docs/nmodl/transpiler/contents/ions.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/nmodl/transpiler/contents/ions.rst b/docs/nmodl/transpiler/contents/ions.rst index 898bad69e5..729c108ba7 100644 --- a/docs/nmodl/transpiler/contents/ions.rst +++ b/docs/nmodl/transpiler/contents/ions.rst @@ -107,3 +107,10 @@ This optimization is implemented in NMODL. It can be activated on the CLI via nmodl ... codegen --opt-ionvar-copy +Special Case: zero-area nodes +----------------------------- + +Ions are not defined for nodes that represent a segment with zero area. This +means point processes can't use ions at nodes with surface zero-area. +Therefore, this should be asserted in generated code. It also allows converting +ion currents to ion current densities by dividing by the area. From 5f84c3c5faaf710a101211cdf64560c4e197bd34 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 26 Feb 2024 12:58:58 +0100 Subject: [PATCH 590/871] Remove brittle unit-test. (BlueBrain/nmodl#1172) The removed test only checks if certain substrings are present. The golden testing automatically adds these tests for any usecase. Therefore, this test is not needed anymore. The usecase for implementing non-specific current, will add a version of a passive current; and test the values against the analytical reference. Ions are already covered by the `ionic` usecase. Array variables are covered by the `cnexp_array` usecase. Parameters coved by the `parameters` usecase. Simple ODEs are covered by `cnexp_*` usecases. NMODL Repo SHA: BlueBrain/nmodl@2ea7f367099626515edcb3bb9ed125a475a85ebb --- .../codegen/codegen_neuron_cpp_visitor.cpp | 256 ------------------ 1 file changed, 256 deletions(-) diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 9cd576f514..46386455a8 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -48,262 +48,6 @@ std::shared_ptr create_neuron_cpp_visitor( return cv; } - -/// print entire code -std::string get_neuron_cpp_code(const std::string& nmodl_text) { - const auto& ast = NmodlDriver().parse_string(nmodl_text); - std::stringstream ss; - auto cvisitor = create_neuron_cpp_visitor(ast, nmodl_text, ss); - cvisitor->visit_program(*ast); - return ss.str(); -} - -std::string reindent_and_trim_text(const std::string& text) { - return reindent_text(stringutils::trim(text)); -}; - -SCENARIO("Check NEURON codegen for simple MOD file", "[codegen][neuron_boilerplate]") { - GIVEN("A simple mod file with RANGE, ARRAY and ION variables") { - std::string const nmodl_text = R"( - TITLE unit test based on passive membrane channel - - UNITS { - (mV) = (millivolt) - (mA) = (milliamp) - (S) = (siemens) - } - - NEURON { - SUFFIX pas_test - USEION na READ ena WRITE ina - NONSPECIFIC_CURRENT i - RANGE g, e - RANGE ar - } - - PARAMETER { - g = .001 (S/cm2) <0,1e9> - e = -70 (mV) - } - - ASSIGNED { - v (mV) - i (mA/cm2) - ena (mV) - ina (mA/cm2) - ar[2] - } - - INITIAL { - ar[0] = 1 - } - - BREAKPOINT { - SOLVE states METHOD cnexp - i = g*(v - e) - ina = g*(v - ena) - } - - STATE { - s - } - - DERIVATIVE states { - s' = ar[0] - } - )"; - auto const generated = reindent_and_trim_text(get_neuron_cpp_code(nmodl_text)); - THEN("Correct includes are printed") { - std::string expected_includes = R"(#include -#include -#include - -#include "mech_api.h" -#include "neuron/cache/mechanism_range.hpp" -#include "nrniv_mf.h" -#include "section_fwd.hpp")"; - - REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_includes))); - } - THEN("Correct number of variables are printed") { - std::string expected_num_variables = - R"(static constexpr auto number_of_datum_variables = 3; -static constexpr auto number_of_floating_point_variables = 10;)"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_num_variables))); - } - THEN("Correct using-directives are printed ") { - std::string expected_using_directives = R"(namespace { -template -using _nrn_mechanism_std_vector = std::vector; -using _nrn_model_sorted_token = neuron::model_sorted_token; -using _nrn_mechanism_cache_range = neuron::cache::MechanismRange; -using _nrn_mechanism_cache_instance = neuron::cache::MechanismInstance; -using _nrn_non_owning_id_without_container = neuron::container::non_owning_identifier_without_container; -template -using _nrn_mechanism_field = neuron::mechanism::field; -template -void _nrn_mechanism_register_data_fields(Args&&... args) { - neuron::mechanism::register_data_fields(std::forward(args)...); -} -} // namespace)"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_using_directives))); - } - THEN("Correct namespace is printed") { - std::string expected_namespace = R"(namespace neuron {)"; - - REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected_namespace))); - } - THEN("Correct channel information are printed") { - std::string expected_channel_info = R"(/** channel information */ - static const char *mechanism_info[] = { - "7.7.0", - "pas_test", - "g_pas_test", - "e_pas_test", - 0, - "i_pas_test", - "ar_pas_test[2]", - 0, - "s_pas_test", - 0, - 0 - };)"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_channel_info))); - } - THEN("Correct global variables are printed") { - std::string expected_global_variables = - R"(static neuron::container::field_index _slist1[1], _dlist1[1];)"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_global_variables))); - } - THEN("Correct pas_test_Instance") { - std::string expected = - R"( struct pas_test_Instance { - double* g{}; - double* e{}; - double* i{}; - double* ar{}; - double* s{}; - double* ena{}; - double* ina{}; - double* Ds{}; - double* v_unused{}; - double* g_unused{}; - const double* const* ion_ena{}; - double* const* ion_ina{}; - double* const* ion_dinadv{}; - pas_test_Store* global{&pas_test_global}; - };)"; - - REQUIRE_THAT(generated, ContainsSubstring(reindent_and_trim_text(expected))); - } - THEN("Correct HOC global variables are printed") { - std::string expected_hoc_global_variables = - R"(/** connect global (scalar) variables to hoc -- */ - static DoubScal hoc_scalar_double[] = { - {nullptr, nullptr} - }; - - - /** connect global (array) variables to hoc -- */ - static DoubVec hoc_vector_double[] = { - {nullptr, nullptr, 0} - };)"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_hoc_global_variables))); - } - THEN("Placeholder nrn_cur function is printed") { - std::string expected_placeholder_nrn_cur = - R"(void nrn_cur_pas_test(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int _type) {})"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_cur))); - } - THEN("Placeholder nrn_state function is printed") { - std::string expected_placeholder_nrn_state = - R"(void nrn_state_pas_test(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int _type) {)"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_placeholder_nrn_state))); - } - THEN("Initialization function for slist/dlist is printed correctly") { - std::string expected_initlists_s_var = "_slist1[0] = {4, 0};"; - std::string expected_initlists_Ds_var = "_dlist1[0] = {7, 0};"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_initlists_s_var))); - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_initlists_Ds_var))); - } - THEN("Placeholder registration function is printed") { - std::string expected_placeholder_reg = R"CODE(/** register channel with the simulator */ - extern "C" void __test_reg() { - _initlists(); - - ion_reg("na", -10000.); - - _na_sym = hoc_lookup("na_ion"); - - register_mech(mechanism_info, nrn_alloc_pas_test, nrn_cur_pas_test, nrn_jacob_pas_test, nrn_state_pas_test, nrn_init_pas_test, hoc_nrnpointerindex, 1); - - mech_type = nrn_get_mechtype(mechanism_info[1]); - _nrn_mechanism_register_data_fields(mech_type, - _nrn_mechanism_field{"g"} /* 0 */, - _nrn_mechanism_field{"e"} /* 1 */, - _nrn_mechanism_field{"i"} /* 2 */, - _nrn_mechanism_field{"ar", 2} /* 3 */, - _nrn_mechanism_field{"s"} /* 4 */, - _nrn_mechanism_field{"ena"} /* 5 */, - _nrn_mechanism_field{"ina"} /* 6 */, - _nrn_mechanism_field{"Ds"} /* 7 */, - _nrn_mechanism_field{"v_unused"} /* 8 */, - _nrn_mechanism_field{"g_unused"} /* 9 */, - _nrn_mechanism_field{"ion_ena", "na_ion"} /* 0 */, - _nrn_mechanism_field{"ion_ina", "na_ion"} /* 1 */, - _nrn_mechanism_field{"ion_dinadv", "na_ion"} /* 2 */ - ); - - hoc_register_prop_size(mech_type, 10, 3); - hoc_register_dparam_semantics(mech_type, 0, "na_ion"); - hoc_register_dparam_semantics(mech_type, 1, "na_ion"); - hoc_register_dparam_semantics(mech_type, 2, "na_ion"); - hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc); - hoc_register_npy_direct(mech_type, npy_direct_func_proc); - })CODE"; - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_placeholder_reg))); - } - } - GIVEN("A simple point process mod file") { - std::string const nmodl_text = R"( - NEURON { - POINT_PROCESS test_pp - } - )"; - THEN("Correct mechanism registration function is called") { - std::string expected_placeholder_point_reg = - "_pointtype = point_register_mech(mechanism_info, nrn_alloc_test_pp, nullptr, " - "nullptr, nullptr, nrn_init_test_pp, hoc_nrnpointerindex, 1, _hoc_create_pnt, " - "_hoc_destroy_pnt, _member_func);"; - - auto const generated = reindent_and_trim_text(get_neuron_cpp_code(nmodl_text)); - - REQUIRE_THAT(generated, - ContainsSubstring(reindent_and_trim_text(expected_placeholder_point_reg))); - } - } -} - - SCENARIO("Check whether PROCEDURE and FUNCTION need setdata call", "[codegen][needsetdata]") { GIVEN("mod file with GLOBAL and RANGE variables used in FUNC and PROC") { std::string input_nmodl = R"( From 1c10bea7b58a8c4a67528c7c494b6b670ee9dc03 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 27 Feb 2024 12:56:35 +0100 Subject: [PATCH 591/871] Blame NMODL for lines it's generating. (BlueBrain/nmodl#1176) Adds nmodl MOD_FILE blame --line LINE which will cause NMODL to print a backtrace every time it writes to line `LINE`. This is very helpful when trying to find the NMODL code responsible for printing a (faulty) line of code. NMODL Repo SHA: BlueBrain/nmodl@4f9d101b983c2414833227cce17a5b6bfcf93320 --- cmake/nmodl/CMakeLists.txt | 5 +++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 11 ++++++---- src/nmodl/main.cpp | 26 ++++++++++++----------- src/nmodl/printer/CMakeLists.txt | 5 +++++ src/nmodl/printer/code_printer.cpp | 26 +++++++++++++++++++++-- src/nmodl/printer/code_printer.hpp | 19 ++++++++++++----- 6 files changed, 69 insertions(+), 23 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index c4bc770421..0053fd90db 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -21,6 +21,7 @@ option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) option(NMODL_ENABLE_TESTS "Enable build of tests" ON) option(NMODL_ENABLE_USECASES "If building tests, additionally enable build of usecase tests. Requires neuron." OFF) +option(NMODL_ENABLE_BACKWARD "Use backward, enables blame." OFF) set(NMODL_EXTRA_CXX_FLAGS "" CACHE STRING "Add extra compile flags for NMODL sources") @@ -131,6 +132,10 @@ if(NMODL_3RDPARTY_USE_SPDLOG) endif() cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) +if(NMODL_ENABLE_BACKWARD) + cpp_cc_git_submodule(backward BUILD PACKAGE backward REQUIRED) +endif() + # ============================================================================= # Format & execute ipynb notebooks in place (pip install nbconvert clean-ipynb) # ============================================================================= diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index e089e9b6b9..671af52e84 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -198,8 +198,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { CodegenCppVisitor(std::string mod_filename, const std::string& output_dir, std::string float_type, - const bool optimize_ionvar_copies) - : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp")) + const bool optimize_ionvar_copies, + size_t blame_line = 0) + : printer( + std::make_unique(output_dir + "/" + mod_filename + ".cpp", blame_line)) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -224,8 +226,9 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { CodegenCppVisitor(std::string mod_filename, std::ostream& stream, std::string float_type, - const bool optimize_ionvar_copies) - : printer(std::make_unique(stream)) + const bool optimize_ionvar_copies, + size_t blame_line = 0) + : printer(std::make_unique(stream, blame_line)) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index eed968bef1..ad06cab003 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -159,6 +159,9 @@ int main(int argc, const char* argv[]) { /// floating point data type std::string data_type("double"); + /// which line to run blame for + size_t blame_line = 0; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) app.get_formatter()->column_width(40); app.set_help_all_flag("-H,--help-all", "Print this help message including all sub-commands"); @@ -269,6 +272,11 @@ int main(int argc, const char* argv[]) { optimize_ionvar_copies_codegen, fmt::format("Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case(); +#if NMODL_ENABLE_BACKWARD + auto blame_opt = app.add_subcommand("blame", "Blame NMODL code that generated some code."); + blame_opt->add_option("--line", blame_line, "Justify why this line was generated."); +#endif + // clang-format on CLI11_PARSE(app, argc, argv); @@ -538,28 +546,22 @@ int main(int argc, const char* argv[]) { { if (coreneuron_code && oacc_backend) { logger->info("Running OpenACC backend code generator for CoreNEURON"); - CodegenAccVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); + CodegenAccVisitor visitor( + modfile, output_dir, data_type, optimize_ionvar_copies_codegen, blame_line); visitor.visit_program(*ast); } else if (coreneuron_code && !neuron_code && cpp_backend) { logger->info("Running C++ backend code generator for CoreNEURON"); - CodegenCoreneuronCppVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); + CodegenCoreneuronCppVisitor visitor( + modfile, output_dir, data_type, optimize_ionvar_copies_codegen, blame_line); visitor.visit_program(*ast); } else if (neuron_code && cpp_backend) { logger->info("Running C++ backend code generator for NEURON"); - CodegenNeuronCppVisitor visitor(modfile, - output_dir, - data_type, - optimize_ionvar_copies_codegen); + CodegenNeuronCppVisitor visitor( + modfile, output_dir, data_type, optimize_ionvar_copies_codegen, blame_line); visitor.visit_program(*ast); } diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 8a3e02da23..0d4666542f 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -4,3 +4,8 @@ add_library(printer OBJECT code_printer.cpp json_printer.cpp nmodl_printer.cpp) set_property(TARGET printer PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(printer PRIVATE util) + +if(NMODL_ENABLE_BACKWARD) + target_link_libraries(printer PRIVATE Backward::Interface) + target_compile_definitions(printer PUBLIC NMODL_ENABLE_BACKWARD=1) +endif() diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index b2e5f6d865..9680430fe2 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -8,10 +8,15 @@ #include "printer/code_printer.hpp" #include "utils/string_utils.hpp" +#if NMODL_ENABLE_BACKWARD +#include +#endif + namespace nmodl { namespace printer { -CodePrinter::CodePrinter(const std::string& filename) { +CodePrinter::CodePrinter(const std::string& filename, size_t blame_line) + : blame_line(blame_line) { if (filename.empty()) { throw std::runtime_error("Empty filename for CodePrinter"); } @@ -88,8 +93,9 @@ void CodePrinter::add_multi_line(const std::string& text) { } void CodePrinter::add_newline(std::size_t n) { - for (std::size_t i{}; i < n; ++i) { + for (std::size_t i = 0; i < n; ++i) { *result << '\n'; + ++current_line; } } @@ -110,5 +116,21 @@ void CodePrinter::pop_block(const std::string_view& suffix, std::size_t num_newl add_newline(num_newlines); } +void CodePrinter::blame() { +#if NMODL_ENABLE_BACKWARD + if (current_line == blame_line) { + *result << std::flush; + + std::cout << "\n\n== Blame =======================================================\n"; + + backward::StackTrace st; + st.load_here(32); + backward::Printer p; + p.print(st, std::cout); + } +#endif +} + + } // namespace printer } // namespace nmodl diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 226562fff1..2867d95b78 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -44,17 +44,21 @@ class CodePrinter { std::ofstream ofs; std::streambuf* sbuf = nullptr; std::unique_ptr result; + size_t current_line = 1; + size_t blame_line = 0; size_t indent_level = 0; const size_t NUM_SPACES = 4; public: - CodePrinter() - : result(std::make_unique(std::cout.rdbuf())) {} + CodePrinter(size_t blame_line = 0) + : result(std::make_unique(std::cout.rdbuf())) + , blame_line(blame_line) {} - CodePrinter(std::ostream& stream) - : result(std::make_unique(stream.rdbuf())) {} + CodePrinter(std::ostream& stream, size_t blame_line = 0) + : result(std::make_unique(stream.rdbuf())) + , blame_line(blame_line) {} - CodePrinter(const std::string& filename); + CodePrinter(const std::string& filename, size_t blame_line = 0); ~CodePrinter() { ofs.close(); @@ -74,6 +78,7 @@ class CodePrinter { template void add_text(Args&&... args) { + blame(); (operator<<(*result, args), ...); } @@ -127,6 +132,10 @@ class CodePrinter { int indent_spaces() { return NUM_SPACES * indent_level; } + + private: + /// Blame when on the requested line. + void blame(); }; /** @} */ // end of printer From 5e9667ae70620dfc72c75792432897429f7a3655 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 27 Feb 2024 14:44:11 +0100 Subject: [PATCH 592/871] Fix bug in `blame`. (BlueBrain/nmodl#1180) Since the codeprinter often writes to the stream directly, `blame` isn't run when needed. This commit ensures that CodePrinter always writes via `add_text`. NMODL Repo SHA: BlueBrain/nmodl@143b40a17035304ff8943babb027045dc0d475bb --- src/nmodl/printer/code_printer.cpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 9680430fe2..8c3c5f0e77 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -33,14 +33,14 @@ CodePrinter::CodePrinter(const std::string& filename, size_t blame_line) } void CodePrinter::push_block() { - *result << '{'; + add_text('{'); add_newline(); indent_level++; } void CodePrinter::push_block(const std::string& expression) { add_indent(); - *result << expression << " {"; + add_text(expression, " {"); add_newline(); indent_level++; } @@ -48,15 +48,13 @@ void CodePrinter::push_block(const std::string& expression) { void CodePrinter::chain_block(std::string const& expression) { --indent_level; add_indent(); - *result << "} " << expression << " {"; + add_text("} ", expression, " {"); add_newline(); ++indent_level; } void CodePrinter::add_indent() { - for (std::size_t i = 0; i < indent_level * NUM_SPACES; ++i) { - *result << ' '; - } + add_text(std::string(indent_level * NUM_SPACES, ' ')); } void CodePrinter::add_multi_line(const std::string& text) { @@ -94,7 +92,7 @@ void CodePrinter::add_multi_line(const std::string& text) { void CodePrinter::add_newline(std::size_t n) { for (std::size_t i = 0; i < n; ++i) { - *result << '\n'; + add_text('\n'); ++current_line; } } @@ -106,13 +104,13 @@ void CodePrinter::pop_block() { void CodePrinter::pop_block_nl(std::size_t num_newlines) { indent_level--; add_indent(); - *result << '}'; + add_text('}'); add_newline(num_newlines); } void CodePrinter::pop_block(const std::string_view& suffix, std::size_t num_newlines) { pop_block_nl(0); - *result << suffix; + add_text(suffix); add_newline(num_newlines); } From c170c257c9534a82d9aa4baeecbfaaa4b3d883f1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 27 Feb 2024 16:59:04 +0100 Subject: [PATCH 593/871] Include compiled mod files. (BlueBrain/nmodl#1131) Add a script for generating the reference values of compiled mod files. These are included with the sources to enable the following: * during review the reviewer can see what was generated. * these can serve as golden tests, flagging changes in unrelated mod files. --------- Co-authored-by: JCGoran NMODL Repo SHA: BlueBrain/nmodl@ff4780d216d5af4aa0560ea875afb78bb9393bdb --- .../nmodl/transpiler/CONTRIBUTING.rst | 27 +++++++++++++++ test/nmodl/transpiler/usecases/CMakeLists.txt | 29 ++++++++++++++++ .../transpiler/usecases/check_references.sh | 18 ++++++++++ .../usecases/generate_references.sh | 33 +++++++++++++++++++ test/nmodl/transpiler/usecases/references | 1 + test/nmodl/transpiler/usecases/run_test.sh | 7 +++- 6 files changed, 114 insertions(+), 1 deletion(-) create mode 100755 test/nmodl/transpiler/usecases/check_references.sh create mode 100755 test/nmodl/transpiler/usecases/generate_references.sh create mode 160000 test/nmodl/transpiler/usecases/references diff --git a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst index abb1c7584b..c7ad6e594b 100644 --- a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst +++ b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst @@ -165,6 +165,33 @@ The HPC coding conventions formatter installs any dependencies into a Python virtual environment. +Updating Golden References +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Run + +.. code:: bash + + cmake --build --target generate_references + +to regenerate the golden references. They are saved in a submodule +``tests/usecases/references``, which points to ``BlueBrain/nmodl-references``. + +Create a PR for the changes to the references and update the SHA in the NMODL +repo. It might be useful to change to SSH authentication: + +.. code:: bash + + git remote set-url origin ssh://git@github.com/BlueBrain/nmodl-references + +(from inside ``tests/usecases/references``). + +Remember the rules of submodules: They're checked out on a specific commit, +i.e. detached HEAD. If you want to modify the submodule, it's usual best to +checkout ``main`` from then on the submodule will behave much like a Git repo +that happens to be located inside a Git repo. + + Validate the Python package ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index d18ef2a762..3f555d9db2 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -7,8 +7,37 @@ set(NMODL_USECASE_DIRS func_proc func_proc_pnt) +file(GLOB NMODL_GOLDEN_REFERENCES "${CMAKE_CURRENT_SOURCE_DIR}/references/*") +if(NMODL_GOLDEN_REFERENCES STREQUAL "") + cpp_cc_init_git_submodule(${CMAKE_CURRENT_SOURCE_DIR}/references) +endif() +unset(NMODL_GOLDEN_REFERNCES) + +add_custom_target(generate_references) foreach(usecase ${NMODL_USECASE_DIRS}) + # Non-existant dependencies are a way of unconditionally running commands in CMake. + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge) + message( + FATAL_ERROR + "The file: '${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge' must not exist." + ) + endif() + add_test(NAME usecase_${usecase} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh ${CMAKE_BINARY_DIR}/bin/nmodl ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) + + add_test(NAME golden_${usecase} + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/check_references.sh ${CMAKE_BINARY_DIR}/bin/nmodl + ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) + + add_custom_command( + OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge + COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_references.sh ${CMAKE_BINARY_DIR}/bin/nmodl + ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) + + add_custom_target( + generate_${usecase} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge) + add_dependencies(generate_references generate_${usecase}) endforeach() diff --git a/test/nmodl/transpiler/usecases/check_references.sh b/test/nmodl/transpiler/usecases/check_references.sh new file mode 100755 index 0000000000..0789afff12 --- /dev/null +++ b/test/nmodl/transpiler/usecases/check_references.sh @@ -0,0 +1,18 @@ +#! /usr/bin/env bash +set -u + +script_dir="$(dirname "$(realpath "$0")")" +nmodl="$1" +usecase_dir="$2" +references_dir="${script_dir}/references/$(basename "$2")" +output_dir="$(mktemp -d)" + +"${script_dir}"/generate_references.sh ${nmodl} "${usecase_dir}" "${output_dir}" + +diff -U 8 -r "${references_dir}" "${output_dir}" + +exit_code=$? + +rm -r "${output_dir}" + +exit $exit_code diff --git a/test/nmodl/transpiler/usecases/generate_references.sh b/test/nmodl/transpiler/usecases/generate_references.sh new file mode 100755 index 0000000000..ef21a838ea --- /dev/null +++ b/test/nmodl/transpiler/usecases/generate_references.sh @@ -0,0 +1,33 @@ +#! /usr/bin/env bash +set -eu + +nmodl="$1" +usecase_dir="$2" + +if [[ $# -eq 3 ]] +then + output_dir="$3" +else + script_dir="$(dirname "$(realpath "$0")")" + output_dir="${script_dir}/references/$(basename "$2")" +fi + +function sanitize() { + for f in "${1}"/*.cpp + do + if [[ "$(uname)" == 'Darwin' ]] + then + sed_cmd="sed -i''" + else + sed_cmd="sed -i" + fi + ${sed_cmd} "s/Created : .*$/Created : DATE/" "$f" + ${sed_cmd} "s/NMODL Compiler : .*$/NMODL Compiler : VERSION/" "$f" + done +} + +"${nmodl}" "${usecase_dir}"/*.mod --neuron -o "${output_dir}/neuron" +sanitize "${output_dir}/neuron" + +"${nmodl}" "${usecase_dir}"/*.mod -o "${output_dir}"/coreneuron +sanitize "${output_dir}/coreneuron" diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references new file mode 160000 index 0000000000..86ea3be285 --- /dev/null +++ b/test/nmodl/transpiler/usecases/references @@ -0,0 +1 @@ +Subproject commit 86ea3be28505f69fe6073498fc995c61f493326d diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index 858751c890..ff18c6a34e 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -1,5 +1,10 @@ #! /usr/bin/env bash -set -e +set -eu + +if [[ $# -ne 2 ]] +then + echo "Usage: $0 NMODL USECASE_DIR" +fi nmodl="$1" output_dir="$(uname -m)" From e0075ab0f919f263a066e041a9f5eb6dcaa86c62 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 27 Feb 2024 22:18:36 +0100 Subject: [PATCH 594/871] Fix Python lib issue with sympy solver (BlueBrain/nmodl#1165) * Fix for Python sympy solver * Add corresponding test * Update requirements --------- Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@1baa906e519b246ce83bf804c77e3a7fb443c26d --- nmodl/__init__.py | 34 +++++++++++++++++++ setup.py | 3 +- .../transpiler/unit/pybind/test_visitor.py | 15 ++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/nmodl/__init__.py b/nmodl/__init__.py index d0dfe8d4ad..dbc53cbe90 100644 --- a/nmodl/__init__.py +++ b/nmodl/__init__.py @@ -1,7 +1,41 @@ +import os +import sys + +if sys.version_info >= (3, 9): + from importlib.resources import files +else: + from importlib_resources import files + +from find_libpython import find_libpython + +# try to add libpython*.so path to environment if not already set +try: + os.environ["NMODL_PYLIB"] = os.environ.get( + "NMODL_PYLIB", + find_libpython(), + ) +except TypeError as exc: + raise RuntimeError( + "find_libpython was unable to find the Python library on this platform; " + "please make sure that the Python library is installed correctly\n" + "You can also try to manually set the NMODL_PYLIB environmental variable " + "to the Python library" + ) from exc + +# add nmodl home to environment (i.e. necessary for nrnunits.lib) if not +# already set +# `files` will automatically raise a `ModuleNotFoundError` +os.environ["NMODLHOME"] = os.environ.get( + "NMODLHOME", + str(files("nmodl") / ".data"), +) + + try: # Try importing but catch exception in case bindings are not available from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa from ._nmodl import __version__ + __all__ = ["NmodlDriver", "to_json", "to_nmodl"] except ImportError: print("[NMODL] [warning] :: Python bindings are not available") diff --git a/setup.py b/setup.py index 5059f51502..095d6e656f 100644 --- a/setup.py +++ b/setup.py @@ -76,7 +76,8 @@ def run(self, *args, **kwargs): install_requirements = [ "PyYAML>=3.13", "sympy>=1.3", - "find_libpython" + "find_libpython", + "importlib_resources;python_version<'3.9'", ] diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index f402ea0c7e..14329ba63b 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -109,3 +109,18 @@ def visit_range_var(self, node): } """ assert str(modast) == one_var_after + + +def test_sympy_conductance_visitor(): + """ + Make sure NMODL sets the correct env variables to be able to run the sympy visitor + """ + program = """NEURON { + USEION na READ ena WRITE ina + RANGE gna + } + BREAKPOINT { + ina = gna*(v - ena) + }""" + driver = nmodl.NmodlDriver() + visitor.SympyConductanceVisitor().visit_program(driver.parse_string(program)) From 77b66486a5c85fd36dc579c3e34c19dc82bdcf28 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 28 Feb 2024 10:14:37 +0100 Subject: [PATCH 595/871] Remove deprecated `pkg_resources` (BlueBrain/nmodl#1166) * Replace `pkg_resources` with `importlib` * Update requirements * Add test to make sure we can read examples from README NMODL Repo SHA: BlueBrain/nmodl@cd4edec63bc85a69b57a1bce74daa27cad28052a --- nmodl/ast.py | 46 +++++++++++-------- nmodl/dsl.py | 30 +++++++----- pywheel/shim/_binwrapper.py | 39 +++++++++------- setup.py | 2 +- .../transpiler/unit/pybind/test_examples.py | 21 +++++++++ 5 files changed, 89 insertions(+), 49 deletions(-) create mode 100644 test/nmodl/transpiler/unit/pybind/test_examples.py diff --git a/nmodl/ast.py b/nmodl/ast.py index f16bbf2d92..b8f990f592 100644 --- a/nmodl/ast.py +++ b/nmodl/ast.py @@ -1,5 +1,23 @@ +""" +Module for vizualization of NMODL abstract syntax trees (ASTs). +""" +import getpass +import json +import os +import sys +import tempfile +import webbrowser +from shutil import copytree + + +if sys.version_info >= (3, 9): + from importlib.resources import files +else: + from importlib_resources import files + +from ._nmodl import to_json from ._nmodl.ast import * # noqa -from pkg_resources import * + def view(nmodl_ast): """Visualize given NMODL AST in web browser @@ -14,31 +32,21 @@ def view(nmodl_ast): Returns: None """ - from ._nmodl import to_json - from distutils.dir_util import copy_tree - import getpass - import json - import os - import tempfile - import webbrowser - - resource = "ext/viz" - if resource_exists(__name__, resource) and resource_isdir(__name__, resource): - installed_viz_tool = resource_filename(__name__, resource) - else: + + path = files("nmodl") / "ext/viz" + if not path.is_dir(): raise FileNotFoundError("Could not find sample mod files") work_dir = os.path.join(tempfile.gettempdir(), getpass.getuser(), "nmodl") # first copy necessary files to temp work directory - copy_tree(installed_viz_tool, work_dir) + copytree(path, work_dir, dirs_exist_ok=True) # prepare json data - with open(os.path.join(work_dir, 'ast.js'), 'w') as outfile: - json_data = json.loads(to_json(nmodl_ast, True, True, True)) - outfile.write('var astRoot = %s;' % json.dumps(json_data)) + json_data = json.loads(to_json(nmodl_ast, True, True, True)) + with open(os.path.join(work_dir, "ast.js"), "w", encoding="utf-8") as outfile: + outfile.write(f"var astRoot = {json.dumps(json_data)};") # open browser with ast - url = 'file://' + os.path.join(work_dir, "index.html") + url = "file://" + os.path.join(work_dir, "index.html") webbrowser.open(url, new=1, autoraise=True) - diff --git a/nmodl/dsl.py b/nmodl/dsl.py index 746150fa85..7b50d78a00 100644 --- a/nmodl/dsl.py +++ b/nmodl/dsl.py @@ -1,10 +1,15 @@ -import os.path as osp -from pkg_resources import * +import sys + +if sys.version_info >= (3, 9): + from importlib.resources import files +else: + from importlib_resources import files from ._nmodl import * RESOURCE_DIR = "ext/example" + def list_examples(): """Returns a list of examples available @@ -13,10 +18,11 @@ def list_examples(): Returns: List of available examples """ - if resource_exists(__name__, RESOURCE_DIR) and resource_isdir(__name__, RESOURCE_DIR): - return resource_listdir(__name__, RESOURCE_DIR) - else: - raise FileNotFoundError("Could not find sample directory") + path = files("nmodl") / RESOURCE_DIR + if path.exists() and path.is_dir(): + return [result.name for result in path.glob("*.mod")] + + raise FileNotFoundError("Could not find sample directory") def load_example(example): @@ -29,10 +35,10 @@ def load_example(example): Args: example: Filename of an example as provided by `list_examples()` Returns: - List of available examples + An path to the example as a string """ - resource = osp.join(RESOURCE_DIR, example) - if resource_exists(__name__, resource): - return resource_string(__name__, resource) - else: - raise FileNotFoundError("Could not find sample mod files") + path = files("nmodl") / RESOURCE_DIR / example + if path.exists(): + return path.read_text() + + raise FileNotFoundError(f"Could not find sample mod file {example}") diff --git a/pywheel/shim/_binwrapper.py b/pywheel/shim/_binwrapper.py index c495e037f3..f850864314 100755 --- a/pywheel/shim/_binwrapper.py +++ b/pywheel/shim/_binwrapper.py @@ -3,39 +3,44 @@ A generic wrapper to access nmodl binaries from a python installation Please create a softlink with the binary name to be called. """ - import os -import sys import stat -from pkg_resources import working_set +import sys + + +if sys.version_info >= (3, 9): + from importlib.metadata import metadata, PackageNotFoundError + from importlib.resources import files +else: + from importlib_metadata import metadata, PackageNotFoundError + from importlib_resources import files + from find_libpython import find_libpython def _config_exe(exe_name): """Sets the environment to run the real executable (returned)""" - package_name = "nmodl" - - if package_name not in working_set.by_key: - print ("INFO : Using nmodl-nightly Package (Developer Version)") - package_name = 'nmodl-nightly' - - assert ( - package_name in working_set.by_key - ), "NMODL package not found! Verify PYTHONPATH" + try: + metadata("nmodl-nightly") + print("INFO : Using nmodl-nightly Package (Developer Version)") + except PackageNotFoundError: + pass - NMODL_PREFIX = os.path.join(working_set.by_key[package_name].location, "nmodl") - NMODL_HOME = os.path.join(NMODL_PREFIX, ".data") - NMODL_BIN = os.path.join(NMODL_HOME, "bin") + NMODL_PREFIX = files("nmodl") + NMODL_HOME = NMODL_PREFIX / ".data" + NMODL_BIN = NMODL_HOME / "bin" # add libpython*.so path to environment os.environ["NMODL_PYLIB"] = find_libpython() # add nmodl home to environment (i.e. necessary for nrnunits.lib) - os.environ["NMODLHOME"] = NMODL_HOME + os.environ["NMODLHOME"] = str(NMODL_HOME) # set PYTHONPATH for embedded python to properly find the nmodl module - os.environ["PYTHONPATH"] = working_set.by_key[package_name].location + ':' + os.environ.get("PYTHONPATH", "") + os.environ["PYTHONPATH"] = ( + str(NMODL_PREFIX.parent) + ":" + os.environ.get("PYTHONPATH", "") + ) return os.path.join(NMODL_BIN, exe_name) diff --git a/setup.py b/setup.py index 095d6e656f..c55bdc2a7a 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,6 @@ Please create a softlink with the binary name to be called. """ import stat -from pkg_resources import working_set from find_libpython import find_libpython @@ -78,6 +77,7 @@ def run(self, *args, **kwargs): "sympy>=1.3", "find_libpython", "importlib_resources;python_version<'3.9'", + "importlib_metadata;python_version<'3.9'", ] diff --git a/test/nmodl/transpiler/unit/pybind/test_examples.py b/test/nmodl/transpiler/unit/pybind/test_examples.py new file mode 100644 index 0000000000..7f8486c030 --- /dev/null +++ b/test/nmodl/transpiler/unit/pybind/test_examples.py @@ -0,0 +1,21 @@ +from json import loads +from pathlib import Path + +from nmodl import dsl + + +def test_example(): + """ + Test for the Python API from example + """ + + examples = dsl.list_examples() + + # ordering may be off so we use a set + assert set(examples) == {"exp2syn.mod", "expsyn.mod", "hh.mod", "passive.mod"} + + driver = dsl.NmodlDriver() + for example in examples: + nmodl_string = dsl.load_example(example) + # make sure we can parse the string + assert driver.parse_string(nmodl_string) From 32c78848eac37d57a30a64bf360f335990043bbd Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 28 Feb 2024 11:53:27 +0100 Subject: [PATCH 596/871] Improve usecases 'func_proc{,_pnt}'. (BlueBrain/nmodl#1182) Check that if there's multiple instances of the mechanisms the functions and procedures use the values for the correct instance. NMODL Repo SHA: BlueBrain/nmodl@25eb4a055fee4ba7a2e77a621b36834f9d028312 --- .../usecases/func_proc/func_proc.mod | 4 ++-- .../transpiler/usecases/func_proc/simulate.py | 18 ++++++++++---- .../usecases/func_proc_pnt/func_proc_pnt.mod | 4 ++-- .../usecases/func_proc_pnt/simulate.py | 24 ++++++++++++++----- test/nmodl/transpiler/usecases/references | 2 +- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod index b8ad6b46ce..c11f662ebd 100644 --- a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod +++ b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod @@ -15,8 +15,8 @@ PROCEDURE set_x_a(a) { x = a } -FUNCTION get_a_42(a) { - get_a_42 = a + 42 +FUNCTION x_plus_a(a) { + x_plus_a = x + a } PROCEDURE set_a_x() { diff --git a/test/nmodl/transpiler/usecases/func_proc/simulate.py b/test/nmodl/transpiler/usecases/func_proc/simulate.py index 261ec54076..ca1ffc73b2 100644 --- a/test/nmodl/transpiler/usecases/func_proc/simulate.py +++ b/test/nmodl/transpiler/usecases/func_proc/simulate.py @@ -1,15 +1,23 @@ from neuron import h +nseg = 5 s = h.Section() +s.nseg = nseg s.insert("test_func_proc") -s(0.5).test_func_proc.set_x_42() +coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] +values = [ 0.1 + k for k in range(nseg)] -assert s(0.5).test_func_proc.x == 42 +for x in coords: + s(x).test_func_proc.set_x_42() + assert s(x).test_func_proc.x == 42 -s(0.5).test_func_proc.set_x_a(13.7) +for x, value in zip(coords, values): + s(x).test_func_proc.set_x_a(value) -assert s(0.5).test_func_proc.x == 13.7 +for x, value in zip(coords, values): + assert s(x).test_func_proc.x == value -assert s(0.5).test_func_proc.get_a_42(42) == 84 +for x, value in zip(coords, values): + assert s(x).test_func_proc.x_plus_a(100.0) == 100.0 + value diff --git a/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod b/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod index b97ccd03a0..c39f846fb1 100644 --- a/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod +++ b/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod @@ -15,6 +15,6 @@ PROCEDURE set_x_a(a) { x = a } -FUNCTION get_a_42(a) { - get_a_42 = a + 42 +FUNCTION x_plus_a(a) { + x_plus_a = x + a } diff --git a/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py b/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py index 41c22b4601..20d6760062 100644 --- a/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py +++ b/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py @@ -1,15 +1,27 @@ from neuron import h +nseg = 5 s = h.Section() +s.nseg = nseg -pnt_proc = h.test_func_proc_pnt(s(0.5)) +point_processes = [] +for k in range(nseg): + x = (0.5 + k) * 1.0 / nseg + point_processes.append(h.test_func_proc_pnt(s(x))) -pnt_proc.set_x_42() +for k in range(nseg): + point_processes[k].set_x_42() -assert pnt_proc.x == 42 +for k in range(nseg): + assert point_processes[k].x == 42 -pnt_proc.set_x_a(13.7) +for k in range(nseg): + value = 0.1 + k + point_processes[k].set_x_a(value) -assert pnt_proc.x == 13.7 +for k in range(nseg): + value = 0.1 + k + assert point_processes[k].x == value + assert point_processes[k].x_plus_a(1000.0) == 1000.0 + value -assert pnt_proc.get_a_42(42) == 84 +print([point_processes[k].x for k in range(nseg)]) diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 86ea3be285..25e2d91dea 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 86ea3be28505f69fe6073498fc995c61f493326d +Subproject commit 25e2d91deabe4115ada0edb91b99e27416c3ff7c From ad845964f40a8357099d64d82dbb2ac01e352866 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 28 Feb 2024 14:53:49 +0100 Subject: [PATCH 597/871] Pass inst when passing id. (BlueBrain/nmodl#1184) For functions that are meant to be called "in a loop", i.e. function which accept `id`, we also pass `inst`. NMODL Repo SHA: BlueBrain/nmodl@fbf9de61d02b252a1c8da54ef3c7e98175920cdf --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 +++--- test/nmodl/transpiler/usecases/references | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 6f06aada85..c3ed7567d1 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -211,8 +211,6 @@ void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node printer->add_text(" "); printer->push_block(); - printer->fmt_line("auto inst = make_instance_{}(*_ml);", info.mod_suffix); - // function requires return variable declaration if (node.is_function_block()) { auto type = default_float_data_type(); @@ -328,6 +326,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( _nt = nrn_threads; )CODE"); } + printer->fmt_line("auto inst = make_instance_{}(_ml_real);", info.mod_suffix); if (info.function_uses_table(block_name)) { printer->fmt_line("_check_{}({})", block_name, internal_method_arguments()); } @@ -376,13 +375,14 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { std::string CodegenNeuronCppVisitor::internal_method_arguments() { - return "_ml, id, _ppvar, _thread, _nt"; + return "_ml, inst, id, _ppvar, _thread, _nt"; } CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { ParamVector params; params.emplace_back("", "_nrn_mechanism_cache_range*", "", "_ml"); + params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); params.emplace_back("", "size_t", "", "id"); params.emplace_back("", "Datum*", "", "_ppvar"); params.emplace_back("", "Datum*", "", "_thread"); diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 25e2d91dea..e508c4bb21 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 25e2d91deabe4115ada0edb91b99e27416c3ff7c +Subproject commit e508c4bb21af8ee5f962ad33b0c101d21f8cd8df From 17b12c8801c39d3a6e7b5ce159908d3764a75d2a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Feb 2024 09:19:09 +0100 Subject: [PATCH 598/871] Document cable equations. (BlueBrain/nmodl#1175) Starts write up of the underlying equations as needed in NMODL. NMODL Repo SHA: BlueBrain/nmodl@31a9bc260fd658e74c80401de868f5e90a968be0 --- .../transpiler/contents/cable_equations.rst | 97 ++++ .../transpiler/images/cable-eqn_circuit.svg | 544 ++++++++++++++++++ .../transpiler/images/cable-eqn_nodes.svg | 385 +++++++++++++ docs/nmodl/transpiler/index.rst | 1 + 4 files changed, 1027 insertions(+) create mode 100644 docs/nmodl/transpiler/contents/cable_equations.rst create mode 100644 docs/nmodl/transpiler/images/cable-eqn_circuit.svg create mode 100644 docs/nmodl/transpiler/images/cable-eqn_nodes.svg diff --git a/docs/nmodl/transpiler/contents/cable_equations.rst b/docs/nmodl/transpiler/contents/cable_equations.rst new file mode 100644 index 0000000000..2b192e6100 --- /dev/null +++ b/docs/nmodl/transpiler/contents/cable_equations.rst @@ -0,0 +1,97 @@ +Cable Equation +============== + +.. note:: + + NEURON has a sophisticated system for allowing users to describe the + geometry of neurons. Here we'll try to derive the equations in a manner that + hides those details whenever they're not relevant to NMODL. Please consult + its geometry related documentation or one of its publications, e.g. `The + NEURON Simulation Environment`_. + +.. _The NEURON Simulation Environment: https://doi.org/10.1162/neco.1997.9.6.1179 + +In order to derive the `cable equations` we model a neuron as an electrical +circuit. We first pick points along the neuron at which we model the voltage. +We'll call them nodes and connect the nodes to form a graph. At every branch +point we place a node, see Figure 1. + +.. figure:: ../images/cable-eqn_nodes.svg + + Figure 1: Illustration of the placement of node along a neurite. + +Two adjacent nodes are connected by a resistor. The interesting behaviour comes +from a difference in ion concentrations across the membrane. This difference is +upheld by three processes: a) the membrane which is largely impermeable to +ions, effectively creating a barrier for ions; b) (voltage-gated) ion channels +that conditionally allow ions to quickly cross the membrane; and c) ion pumps +which continuously pump ions across the membrane to restore a resting state. + +The fact that the membrane is (mostly) impermeable to ions means that it +behaves like a dielectric material and can therefore be modeled by a capacitor. +The ion pumps and channels we simply model by a current :math:`I`. + +This model gives rise to the circuit shown in Figure 2. + +.. figure:: ../images/cable-eqn_circuit.svg + + Figure 2: Illustration of the circuit near one node. The total trans-membrane + current is :math:`I_M`, the current due to the dielectric property of the + membrane is :math:`I_C`, and all mechanism specific currents are represented + by :math:`I`. + +We can start writing down equations. Let's recall the formula for a capacitor +and Ohm's Law: + +.. math:: + + I = C \frac{dV}{dt}, \qquad + \Delta V = R I + +Using Kirchoff's Law we can write down two equations for the trans-membrane +current: + +.. math:: + + I_M &= I_C + I(V_1) \\ + I_{0,1} &= I_{1, 2} + I_M + +which leads to + +.. math:: + + I_C + I = I_{0,1} - I_{1, 2} \\ + I_C + I_{1,2} - I_{0, 1} = -I + +which can be rewritten in terms of the voltage as follows: + +.. math:: + + C \frac{dV_1}{dt} + R_{1,2}^{-1} (V_{2} - V{1}) - R_{0,1}^{-1} (V_{1} - V_{0}) = - I(V_1) + +This can be discretized by implicit Euler: + +.. math:: + + C \frac{V_1^{n+1} - V_1^{n}}{\Delta t} + R_{1,2}^{-1} \left(V_{2}^{n+1} - V_{1}^{n+1}\right) - R_{0,1}^{-1} \left(V_{1}^{n+1} - V_{0}^{n+1}\right) = - I(V_1^{n+1}) + +We collect terms as follows: + +.. math:: + + R_{0,1}^{-1} V_{0}^{n+1} + + \left(\frac{C}{\Delta t} + R_{0,1}^{-1} - R_{1,2}^{-1}\right) V_1^{n+1} + + R_{1,2}^{-1} V_{2}^{n+1} + = \frac{C}{\Delta t} V_1^{n} - I(V_1^{n+1}) + +The unpleasant term is :math:`I(V_1^{n+1})` since it makes the system non-linear. +Therefore, it's linearized as follows: + +.. math:: + + I(V_1^{n+1}) + &\approx I_1^{n} + \left(V^{n+1} - V^{n}\right) \frac{dI_1}{dV_1} \\ + &=: I_1^{n} + \left(V^{n+1} - V^{n}\right) g_i^{n} + +where :math:`g_i^{n}` is the mechanism dependent (differential) conductance. + diff --git a/docs/nmodl/transpiler/images/cable-eqn_circuit.svg b/docs/nmodl/transpiler/images/cable-eqn_circuit.svg new file mode 100644 index 0000000000..83415be5d9 --- /dev/null +++ b/docs/nmodl/transpiler/images/cable-eqn_circuit.svg @@ -0,0 +1,544 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/nmodl/transpiler/images/cable-eqn_nodes.svg b/docs/nmodl/transpiler/images/cable-eqn_nodes.svg new file mode 100644 index 0000000000..2736930961 --- /dev/null +++ b/docs/nmodl/transpiler/images/cable-eqn_nodes.svg @@ -0,0 +1,385 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + V1 + V0 + V2 + V3 + V4 + V5 + V6 + V7 + V8 + V9 + V10 + V11 + V12 + V13 + + diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 33a6f992c6..b9c49d4586 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -22,6 +22,7 @@ About NMODL contents/currents contents/ions contents/pointers + contents/cable_equations .. toctree:: :maxdepth: 3 From 46fd7845625ea1143ed65a7ed53c9d60f503e301 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Feb 2024 11:08:15 +0100 Subject: [PATCH 599/871] Implement NONSPECIFIC_CURRENT. (BlueBrain/nmodl#1185) This commits adds a test for NONSPECIFIC_CURRENT and implements the codegen for NEURON required to pass the test. It does not set the conductances. NMODL Repo SHA: BlueBrain/nmodl@ee517e4b282dc54cf6cbd5a2b9d65dd616a7ba89 --- src/nmodl/codegen/codegen_cpp_visitor.hpp | 7 + .../codegen/codegen_neuron_cpp_visitor.cpp | 289 ++++++++++++++---- .../codegen/codegen_neuron_cpp_visitor.hpp | 14 + test/nmodl/transpiler/usecases/CMakeLists.txt | 3 +- .../usecases/nonspecific_current/leonhard.mod | 16 + .../usecases/nonspecific_current/simulate.py | 29 ++ test/nmodl/transpiler/usecases/references | 2 +- 7 files changed, 305 insertions(+), 55 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod create mode 100644 test/nmodl/transpiler/usecases/nonspecific_current/simulate.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 671af52e84..19883cc2e7 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -371,6 +371,13 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return fmt::format("{}_Instance", info.mod_suffix); } + /** + * Name of structure that wraps node variables + */ + std::string node_data_struct() const { + return fmt::format("{}_NodeData", info.mod_suffix); + } + /** * Name of structure that wraps global variables diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index c3ed7567d1..64ba56dbae 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -919,50 +919,6 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { } } -void CodegenNeuronCppVisitor::print_make_instance() const { - printer->add_newline(2); - printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range& _ml)", - instance_struct(), - info.mod_suffix); - printer->fmt_push_block("return {}", instance_struct()); - - std::vector make_instance_args; - - const auto codegen_float_variables_size = codegen_float_variables.size(); - for (int i = 0; i < codegen_float_variables_size; ++i) { - const auto& float_var = codegen_float_variables[i]; - if (float_var->is_array()) { - make_instance_args.push_back( - fmt::format("_ml.template data_array_ptr<{}, {}>()", i, float_var->get_length())); - } else { - make_instance_args.push_back(fmt::format("_ml.template fpfield_ptr<{}>()", i)); - } - } - - const auto codegen_int_variables_size = codegen_int_variables.size(); - for (size_t i = 0; i < codegen_int_variables_size; ++i) { - const auto& var = codegen_int_variables[i]; - auto name = var.symbol->get_name(); - auto const variable = [&var, i]() -> std::string { - if (var.is_index || var.is_integer) { - return ""; - } else if (var.is_vdata) { - return ""; - } else { - return fmt::format("_ml.template dptr_field_ptr<{}>()", i); - } - }(); - if (variable != "") { - make_instance_args.push_back(variable); - } - } - - printer->add_multi_line(fmt::format("{}", fmt::join(make_instance_args, ",\n"))); - - printer->pop_block(";"); - printer->pop_block(); -} - void CodegenNeuronCppVisitor::print_mechanism_register() { /// TODO: Write this according to NEURON printer->add_newline(2); @@ -1136,6 +1092,83 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini printer->pop_block(";"); } +void CodegenNeuronCppVisitor::print_make_instance() const { + printer->add_newline(2); + printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range& _ml)", + instance_struct(), + info.mod_suffix); + printer->fmt_push_block("return {}", instance_struct()); + + std::vector make_instance_args; + + const auto codegen_float_variables_size = codegen_float_variables.size(); + for (int i = 0; i < codegen_float_variables_size; ++i) { + const auto& float_var = codegen_float_variables[i]; + if (float_var->is_array()) { + make_instance_args.push_back( + fmt::format("_ml.template data_array_ptr<{}, {}>()", i, float_var->get_length())); + } else { + make_instance_args.push_back(fmt::format("_ml.template fpfield_ptr<{}>()", i)); + } + } + + const auto codegen_int_variables_size = codegen_int_variables.size(); + for (size_t i = 0; i < codegen_int_variables_size; ++i) { + const auto& var = codegen_int_variables[i]; + auto name = var.symbol->get_name(); + auto const variable = [&var, i]() -> std::string { + if (var.is_index || var.is_integer) { + return ""; + } else if (var.is_vdata) { + return ""; + } else { + return fmt::format("_ml.template dptr_field_ptr<{}>()", i); + } + }(); + if (variable != "") { + make_instance_args.push_back(variable); + } + } + + printer->add_multi_line(fmt::format("{}", fmt::join(make_instance_args, ",\n"))); + + printer->pop_block(";"); + printer->pop_block(); +} + +void CodegenNeuronCppVisitor::print_node_data_structure(bool print_initializers) { + auto const value_initialize = print_initializers ? "{}" : ""; + printer->add_newline(2); + printer->fmt_push_block("struct {} ", node_data_struct()); + + // Pointers to node variables + printer->add_line("int const * nodeindices;"); + printer->add_line("double const * node_voltages;"); + printer->add_line("double * node_rhs;"); + printer->add_line("int nodecount;"); + + printer->pop_block(";"); +} + +void CodegenNeuronCppVisitor::print_make_node_data() const { + printer->add_newline(2); + printer->fmt_push_block("static {} make_node_data_{}(NrnThread& _nt, Memb_list& _ml_arg)", + node_data_struct(), + info.mod_suffix); + + std::vector make_node_data_args; + make_node_data_args.push_back("_ml_arg.nodeindices"); + make_node_data_args.push_back("_nt.node_voltage_storage()"); + make_node_data_args.push_back("_nt.node_rhs_storage()"); + make_node_data_args.push_back("_ml_arg.nodecount"); + + printer->fmt_push_block("return {}", node_data_struct()); + printer->add_multi_line(fmt::format("{}", fmt::join(make_node_data_args, ",\n"))); + + printer->pop_block(";"); + printer->pop_block(); +} + void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { // read ion statements @@ -1169,6 +1202,8 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, printer->add_line("_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); + printer->add_line("auto nodecount = _ml_arg->nodecount;"); } @@ -1353,28 +1388,152 @@ void CodegenNeuronCppVisitor::print_nrn_state() { /* Print nrn_cur related routines */ /****************************************************************************************/ +std::string CodegenNeuronCppVisitor::nrn_current_arguments() { + if (ion_variable_struct_required()) { + throw std::runtime_error("Not implemented."); + } + return "id, inst, node_data, v"; +} + + +CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parameters() { + if (ion_variable_struct_required()) { + throw std::runtime_error("Not implemented."); + } + + ParamVector params; + params.emplace_back("", "size_t", "", "id"); + params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); + params.emplace_back("", fmt::format("{}&", node_data_struct()), "", "node_data"); + params.emplace_back("", "double", "", "v"); + return params; +} + /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { - return; + const auto& args = nrn_current_parameters(); + const auto& block = node.get_statement_block(); + printer->add_newline(2); + // print_device_method_annotation(); + printer->fmt_push_block("inline double nrn_current_{}({})", + info.mod_suffix, + get_parameter_str(args)); + printer->add_line("double current = 0.0;"); + print_statement_block(*block, false, false); + for (auto& current: info.currents) { + const auto& name = get_variable_name(current); + printer->fmt_line("current += {};", name); + } + printer->add_line("return current;"); + printer->pop_block(); } /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { - return; + const auto& block = node.get_statement_block(); + print_statement_block(*block, false, false); + if (!info.currents.empty()) { + std::string sum; + for (const auto& current: info.currents) { + auto var = breakpoint_current(current); + sum += get_variable_name(var); + if (¤t != &info.currents.back()) { + sum += "+"; + } + } + printer->fmt_line("double rhs = {};", sum); + } + + std::string sum; + for (const auto& conductance: info.conductances) { + auto var = breakpoint_current(conductance.variable); + sum += get_variable_name(var); + if (&conductance != &info.conductances.back()) { + sum += "+"; + } + } + printer->fmt_line("double g = {};", sum); + + for (const auto& conductance: info.conductances) { + if (!conductance.ion.empty()) { + const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + conductance.ion + + "dv"; + const auto& rhs = get_variable_name(conductance.variable); + const ShadowUseStatement statement{lhs, "+=", rhs}; + const auto& text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + } } /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_cur_non_conductance_kernel() { - return; + printer->fmt_line("double I1 = nrn_current_{}({}+0.001);", + info.mod_suffix, + nrn_current_arguments()); + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { + if (ion.is_ionic_current(var)) { + const auto& name = get_variable_name(var); + printer->fmt_line("double di{} = {};", ion.name, name); + } + } + } + printer->fmt_line("double I0 = nrn_current_{}({});", info.mod_suffix, nrn_current_arguments()); + printer->add_line("double rhs = I0;"); + + printer->add_line("double g = (I1-I0)/0.001;"); + for (auto& ion: info.ions) { + for (auto& var: ion.writes) { + if (ion.is_ionic_current(var)) { + const auto& lhs = std::string(naming::ION_VARNAME_PREFIX) + "di" + ion.name + "dv"; + auto rhs = fmt::format("(di{}-{})/0.001", ion.name, get_variable_name(var)); + if (info.point_process) { + auto area = get_variable_name(naming::NODE_AREA_VARIABLE); + rhs += fmt::format("*1.e2/{}", area); + } + const ShadowUseStatement statement{lhs, "+=", rhs}; + const auto& text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + } + } } /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { - return; + printer->add_line("int node_id = node_data.nodeindices[id];"); + printer->add_line("double v = node_data.node_voltages[node_id];"); + + const auto& read_statements = ion_read_statements(BlockType::Equation); + for (auto& statement: read_statements) { + printer->add_line(statement); + } + + if (info.conductances.empty()) { + print_nrn_cur_non_conductance_kernel(); + } else { + print_nrn_cur_conductance_kernel(node); + } + + const auto& write_statements = ion_write_statements(BlockType::Equation); + for (auto& statement: write_statements) { + auto text = process_shadow_update_statement(statement, BlockType::Equation); + printer->add_line(text); + } + + if (info.point_process) { + const auto& area = get_variable_name(naming::NODE_AREA_VARIABLE); + printer->fmt_line("double mfactor = 1.e2/{};", area); + printer->add_line("g = g*mfactor;"); + printer->add_line("rhs = rhs*mfactor;"); + } + + // print_g_unused(); } @@ -1390,14 +1549,36 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { return; } + if (info.conductances.empty()) { + print_nrn_current(*info.breakpoint_node); + } + printer->add_newline(2); + printer->add_line("/** update current */"); + print_global_function_common_code(BlockType::Equation); + // print_channel_iteration_block_parallel_hint(BlockType::Equation, info.breakpoint_node); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + print_nrn_cur_kernel(*info.breakpoint_node); + // print_nrn_cur_matrix_shadow_update(); + // if (!nrn_cur_reduction_loop_required()) { + // print_fast_imem_calculation(); + // } - printer->fmt_line( - "void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, " - "int _type) {{}}", - method_name(naming::NRN_CUR_METHOD)); - /// TODO: Fill in + printer->add_line("node_data.node_rhs[node_id] -= rhs;"); + + + printer->pop_block(); + + // if (nrn_cur_reduction_loop_required()) { + // printer->push_block("for (int id = 0; id < nodecount; id++)"); + // print_nrn_cur_matrix_shadow_reduction(); + // printer->pop_block(); + // print_fast_imem_calculation(); + // } + + // print_kernel_data_present_annotation_block_end(); + printer->pop_block(); } @@ -1482,7 +1663,9 @@ void CodegenNeuronCppVisitor::print_namespace_end() { void CodegenNeuronCppVisitor::print_data_structures(bool print_initializers) { print_mechanism_global_var_structure(print_initializers); print_mechanism_range_var_structure(print_initializers); + print_node_data_structure(print_initializers); print_make_instance(); + print_make_node_data(); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 0d30fa2111..b680431a7b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -492,6 +492,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Print nrn_cur related routines */ /****************************************************************************************/ + std::string nrn_current_arguments(); + ParamVector nrn_current_parameters(); /** * Print the \c nrn_current kernel @@ -598,6 +600,10 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_make_instance() const; + /** Print `make_*_node_data`. + */ + void print_make_node_data() const; + /** * Set v_unused (voltage) for NRN_PRCELLSTATE feature */ @@ -655,6 +661,14 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * be included in the struct declaration. */ void print_mechanism_range_var_structure(bool print_initializers) override; + + /** + * Print the structure that wraps all node variables required for the NMODL. + * + * \param print_initializers Whether or not default values for variables + * be included in the struct declaration. + */ + void print_node_data_structure(bool print_initializers); }; diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 3f555d9db2..cd204c1153 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -5,7 +5,8 @@ set(NMODL_USECASE_DIRS point_process parameter func_proc - func_proc_pnt) + func_proc_pnt + nonspecific_current) file(GLOB NMODL_GOLDEN_REFERENCES "${CMAKE_CURRENT_SOURCE_DIR}/references/*") if(NMODL_GOLDEN_REFERENCES STREQUAL "") diff --git a/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod b/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod new file mode 100644 index 0000000000..bec601666b --- /dev/null +++ b/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod @@ -0,0 +1,16 @@ +UNITS { + (mA) = (milliamp) +} + +NEURON { + SUFFIX leonhard + NONSPECIFIC_CURRENT il +} + +ASSIGNED { + il (mA/cm2) +} + +BREAKPOINT { + il = 0.005 * (v - 1.5) +} diff --git a/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py b/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py new file mode 100644 index 0000000000..ffa4048322 --- /dev/null +++ b/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py @@ -0,0 +1,29 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("leonhard") +s.nseg = nseg + +v_hoc = h.Vector().record(s(0.5)._ref_v) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +v = np.array(v_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +erev = 1.5 +rate = 0.005 / 1e-3 +v0 = -65.0 +v_exact = erev + (v0 - erev)*np.exp(-rate*t) +rel_err = np.abs(v - v_exact) / np.max(np.abs(v_exact)) + +assert np.all(rel_err < 1e-1), f"rel_err = {rel_err}" +print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index e508c4bb21..9893f6e7dd 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit e508c4bb21af8ee5f962ad33b0c101d21f8cd8df +Subproject commit 9893f6e7dd0466ba339281c1b412809e50136f63 From 25083e0a2fedc4ce4843eed9d48013e0a276af57 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Feb 2024 13:03:28 +0100 Subject: [PATCH 600/871] Document `nmodl blame`. (BlueBrain/nmodl#1189) NMODL Repo SHA: BlueBrain/nmodl@3bb39d207212a7ba5159883de74395ff1cb16777 --- .../nmodl/transpiler/CONTRIBUTING.rst | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst index c7ad6e594b..e61710636a 100644 --- a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst +++ b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst @@ -152,6 +152,15 @@ pull the changes from the main (upstream) repository: Development Conventions ------------------------ +New Lines +~~~~~~~~~ + +When generating/printing code it's important to use ``add_newline`` to +start a new line of code. When printing a string containing multiple lines, +i.e. one that contains a ``"\n"`` one must use ``add_multi_line``. + +It's important that NMODL knows the line number it's currently on. + Formatting ~~~~~~~~~~ @@ -226,3 +235,27 @@ have ``CMake >= 3.15`` and use following cmake option: :: cmake .. -DENABLE_CLANG_TIDY=ON + +Blaming NMODL +~~~~~~~~~~~~~ + +While developing NMODL one may want to know which line of code in NMODL +produced a particular line of code in the generated file, e.g. when faced with +a compiler error such as + +.. code-block:: + + hodhux.cpp:105:26: error: ‘coreneuron’ has not been declared + 105 | double* celsius{&coreneuron::celsius}; + | ^~~~~~~~~~ + +One can find the line by doing: + +.. code-block:: + + $ nmodl hodhux.mod ... blame --line 105 + +which will print a backtrace every time NMODL writes to line 105. While this is +useful for finding the line responsible for printing, i.e. convert AST to C++, +that line it doesn't immediately explain why the AST ended up that way. +Currently, we don't have a tool for the latter. From 72cedcd2c9782a20961841e6642a1b0a4291e36d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 1 Mar 2024 13:38:36 +0100 Subject: [PATCH 601/871] Don't install fmt and backward. (BlueBrain/nmodl#1193) NMODL Repo SHA: BlueBrain/nmodl@85a4906f3458f29843ba57381a946aa4fccefdb1 --- cmake/nmodl/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 0053fd90db..4b628c4d3d 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -105,7 +105,7 @@ endif() cpp_cc_git_submodule(eigen) # We could have fmt incoming from NEURON if(NOT TARGET fmt) - cpp_cc_git_submodule(fmt BUILD PACKAGE fmt REQUIRED) + cpp_cc_git_submodule(fmt BUILD EXCLUDE_FROM_ALL PACKAGE fmt REQUIRED) endif() # If we're building from the submodule, make sure we pass -fPIC so that we can link the code into a # shared library later. @@ -133,7 +133,7 @@ endif() cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) if(NMODL_ENABLE_BACKWARD) - cpp_cc_git_submodule(backward BUILD PACKAGE backward REQUIRED) + cpp_cc_git_submodule(backward BUILD EXCLUDE_FROM_ALL PACKAGE backward REQUIRED) endif() # ============================================================================= From e42ce3e82ae700023441ef041451268537428a4a Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Tue, 5 Mar 2024 15:20:07 +0100 Subject: [PATCH 602/871] Update to latest hpc-coding-conventions (BlueBrain/nmodl#1198) * Update to latest hpc-coding-conventions * Pin version 13 for clang-format to avoid code reformatting NMODL Repo SHA: BlueBrain/nmodl@f930357ae20b84f95f8fd327b10cae61e332effd --- cmake/nmodl/hpc-coding-conventions | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index f8f8d69a66..8f81155978 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit f8f8d69a66c23978d1c9c5dce62de79466f26e5d +Subproject commit 8f8115597817365c5c4fa39e217b3ab0b3640cb2 From 071dc0737ccebbaa64f64e092a207ea3fa50004d Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 6 Mar 2024 09:17:59 +0100 Subject: [PATCH 603/871] Transition to `pyproject.toml` (BlueBrain/nmodl#1147) * replace `setup.py` with `pyproject.toml` * move all Python package requirements to single `requirements.txt` * remove checking Python package requirements in `CMakeLists.txt` * move Python bindings to own dir (python/nmodl), fixes BlueBrain/nmodl#462 * add separate script for generating docs * add `packaging/change_name.py` script to as workaround to enable both NMODL and NMODL-nightly wheels in CI * update documentation and CI to reflect above changes --------- Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@3acc935d90b68afb346c4461db131842bb22d4cd --- cmake/nmodl/CMakeLists.txt | 12 +- docs/nmodl/transpiler/INSTALL.rst | 45 ++++-- docs/nmodl/transpiler/conf.py | 23 +-- docs/nmodl/transpiler/generate_docs.sh | 44 ++++++ .../nmodl/transpiler/CONTRIBUTING.rst | 5 +- pywheel/shim/nmodl | 1 - setup.py | 135 ------------------ src/nmodl/language/code_generator.cmake | 2 +- .../nmodl/nmodl/python/nmodl}/__init__.py | 0 .../nmodl/nmodl/python/nmodl}/_binwrapper.py | 10 +- .../nmodl/nmodl/python/nmodl}/ast.py | 0 .../nmodl/nmodl/python/nmodl}/dsl.py | 0 .../python/nmodl}/ext/example/exp2syn.mod | 0 .../python/nmodl}/ext/example/expsyn.mod | 0 .../nmodl/python/nmodl}/ext/example/hh.mod | 0 .../python/nmodl}/ext/example/passive.mod | 0 .../nmodl/python/nmodl}/ext/viz/css/tree.css | 0 .../nmodl/python/nmodl}/ext/viz/index.html | 0 .../nmodl/python/nmodl}/ext/viz/js/d3.min.js | 0 .../nmodl/python/nmodl}/ext/viz/js/tree.js | 0 .../nmodl/nmodl/python/nmodl}/ode.py | 0 .../nmodl/nmodl/python/nmodl}/symtab.py | 0 .../nmodl/nmodl/python/nmodl}/visitor.py | 0 src/nmodl/pybind/CMakeLists.txt | 8 +- 24 files changed, 100 insertions(+), 185 deletions(-) create mode 100755 docs/nmodl/transpiler/generate_docs.sh delete mode 120000 pywheel/shim/nmodl delete mode 100644 setup.py rename {nmodl => src/nmodl/nmodl/python/nmodl}/__init__.py (100%) rename {pywheel/shim => src/nmodl/nmodl/python/nmodl}/_binwrapper.py (86%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ast.py (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/dsl.py (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/example/exp2syn.mod (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/example/expsyn.mod (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/example/hh.mod (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/example/passive.mod (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/viz/css/tree.css (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/viz/index.html (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/viz/js/d3.min.js (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ext/viz/js/tree.js (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/ode.py (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/symtab.py (100%) rename {nmodl => src/nmodl/nmodl/python/nmodl}/visitor.py (100%) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4b628c4d3d..4754ca8222 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -26,6 +26,12 @@ set(NMODL_EXTRA_CXX_FLAGS "" CACHE STRING "Add extra compile flags for NMODL sources") separate_arguments(NMODL_EXTRA_CXX_FLAGS) +option(LINK_AGAINST_PYTHON "Should the Python library be linked or not" ON) +option(NMODL_BUILD_WHEEL "Flag to signal we are building a wheel" OFF) +if(NMODL_BUILD_WHEEL) + set(LINK_AGAINST_PYTHON OFF) + set(NMODL_ENABLE_TESTS OFF) +endif() # ============================================================================= # Settings to enable project as submodule @@ -166,12 +172,6 @@ endif() message(STATUS "CHECKING FOR PYTHON") find_package(PythonInterp 3.8 REQUIRED) cpp_cc_strip_python_shims(EXECUTABLE "${PYTHON_EXECUTABLE}" OUTPUT PYTHON_EXECUTABLE) -include(cmake/hpc-coding-conventions/cpp/cmake/bbp-find-python-module.cmake) -cpp_cc_find_python_module(jinja2 2.9.3 REQUIRED) -cpp_cc_find_python_module(pytest 3.3.0 REQUIRED) -cpp_cc_find_python_module(sympy 1.3 REQUIRED) -cpp_cc_find_python_module(textwrap 0.9 REQUIRED) -cpp_cc_find_python_module(yaml 3.12 REQUIRED) # ============================================================================= # Compiler specific flags for external submodules diff --git a/docs/nmodl/transpiler/INSTALL.rst b/docs/nmodl/transpiler/INSTALL.rst index 72fff92bde..9c4fc6af46 100644 --- a/docs/nmodl/transpiler/INSTALL.rst +++ b/docs/nmodl/transpiler/INSTALL.rst @@ -45,8 +45,8 @@ of all dependencies we recommend using `homebrew `__: brew install flex bison cmake python3 -The necessary Python packages can then easily be added using the pip3 -command. +All of the Python dependencies (build, run, and development) can be installed +using: .. code:: sh @@ -74,7 +74,8 @@ installed along with the system toolchain: apt-get install flex bison gcc python3 python3-pip -The Python dependencies are installed using: +All of the Python dependencies (build, run, and development) can be installed +using: .. code:: sh @@ -124,8 +125,8 @@ to cmake as: -DBISON_EXECUTABLE=/usr/local/opt/bison/bin/bison \ -DCMAKE_INSTALL_PREFIX=$HOME/nmodl -Using Python setuptools -~~~~~~~~~~~~~~~~~~~~~~~ +Using the Python build system +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you are mainly interested in the NMODL Framework parsing and analysis tools and wish to use them from Python, we recommend building and @@ -139,6 +140,19 @@ This should build the NMODL framework and install it into your pip user ``site-packages`` folder such that it becomes available as a Python module. +Building a wheel +~~~~~~~~~~~~~~~~ + +You can also build a wheel you can test and install in another environment using: + +.. code:: sh + + pip3 wheel . --no-deps [-C OPTION1=VALUE1 -C OPTION2=VALUE2...] [--wheel-dir DIRECTORY] + +where the various ``OPTION`` values describe the build options (for a list of +all available options, please consult the `reference `_). +Notably, due to a bug in CMake, on MacOS one should pass ``-C build-dir=DIRECTORY`` to the above. + When building without linking against libpython ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -210,6 +224,16 @@ example in your Python 3 interpeter as follows: SUFFIX hh } +You can also run all of the Python tests for a given wheel using: + +.. code:: sh + + bash packaging/test_wheel.bash PYTHON_EXECUTABLE WHEEL + +where ``PYTHON_EXECUTABLE`` should be replaced by the path to the Python +executable, and ``WHEEL`` should be replaced by the path to the wheel you wish +to test. + NMODL is now setup correctly! Generating Documentation @@ -219,9 +243,14 @@ In order to build the documentation you must have additionally ``pandoc`` installed. Use your system’s package manager to do this (e.g. ``sudo apt-get install pandoc``). -You can build the entire documentation simply by using sphinx from -``setup.py``: +You can build the entire documentation simply by using the ``generate_docs.sh`` +script: .. code:: sh - python3 setup.py build_ext --inplace docs + bash docs/generate_docs.sh DIRECTORY [PYTHON_EXECUTABLE] + +where ``DIRECTORY`` is where you want to put the output files. The HTML +documentation will then be available in ``DIRECTORY/docs``, and the temporary +build will be stored in ``DIRECTORY/build``. You can also specify the path to +the Python executable if it is not picked up automatically. diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index e2695fb9e0..ff9c2afbce 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -12,29 +12,14 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # - - - -import os -import subprocess -import sys import textwrap -# The project needs to be built before documentation in the usual build folder -sys.path.insert(0, os.path.abspath('..')) - import nmodl # isort:skip -os.environ['PYTHONPATH'] = ':'.join(sys.path) - -# Run doxygen -subprocess.call('doxygen Doxyfile', shell=True) - # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. @@ -134,11 +119,11 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -#html_static_path = ["_static"] +# html_static_path = ["_static"] -# A list of paths that contain extra files not directly related to the -# documentation, such as robots.txt or .htaccess. Relative paths are taken -# as relative to the configuration directory. They are copied to the output +# A list of paths that contain extra files not directly related to the +# documentation, such as robots.txt or .htaccess. Relative paths are taken +# as relative to the configuration directory. They are copied to the output # directory. They will overwrite any existing file of the same name. html_extra_path = ["sphinx_doxygen"] diff --git a/docs/nmodl/transpiler/generate_docs.sh b/docs/nmodl/transpiler/generate_docs.sh new file mode 100755 index 0000000000..c0a756d326 --- /dev/null +++ b/docs/nmodl/transpiler/generate_docs.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# script for generating documentation for NMODL + +set -eu + +if [ $# -lt 1 ] +then + echo "Usage: $(basename "$0") output_dir [python_exe]" + exit 1 +fi + +# the dir where we put the temporary build and the docs +output_dir="$1" +# path to the Python executable +python_exe="${2:-"$(command -v python3)"}" + +if ! [ -d "${output_dir}" ] +then + mkdir -p "${output_dir}" +fi + +build_dir="build" +docs_dir="docs" + +echo "== Building documentation files in: ${output_dir}/${docs_dir} ==" +echo "== Temporary project build directory is: ${output_dir}/${build_dir} ==" + +venv_name="${output_dir}/env" +${python_exe} -m venv "${venv_name}" +. "${venv_name}/bin/activate" +python_exe="$(command -v python)" +${python_exe} -m pip install -U pip +${python_exe} -m pip install ".[docs]" -C build-dir="${output_dir}/${build_dir}" + +# the abs dir where this script is located (so we can call it from wherever) +script_dir="$(cd "$(dirname "$0")"; pwd -P)" + +cd "${script_dir}" +doxygen Doxyfile +cd - +sphinx-build docs/ "${output_dir}/${docs_dir}" + +deactivate diff --git a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst index e61710636a..6f21b8aaa4 100644 --- a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst +++ b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst @@ -210,9 +210,8 @@ the Python API: 1. setup a sandbox environment with either *virtualenv*, *pyenv*, or *pipenv*. For instance with *virtualenv*: ``python -m venv .venv && source .venv/bin/activate`` -2. build the Python package with the command: ``python setup.py build`` -3. install *pytest* Python package: ``pip install pytest`` -4. execute the unit-tests: ``pytest`` +2. build the Python wheel with the command: ``python -m pip wheel . --no-deps`` +3. execute the unit-tests for the wheel: ``bash packaging/test_wheel.bash $(command -v python) WHEEL``, where ``WHEEL`` is the path to the wheel generated in the previous step. Memory Leaks and clang-tidy ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/pywheel/shim/nmodl b/pywheel/shim/nmodl deleted file mode 120000 index cf9ff4fba4..0000000000 --- a/pywheel/shim/nmodl +++ /dev/null @@ -1 +0,0 @@ -_binwrapper.py \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index c55bdc2a7a..0000000000 --- a/setup.py +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright 2023 Blue Brain Project, EPFL. -# See the top-level LICENSE file for details. -# -# SPDX-License-Identifier: Apache-2.0 - -import inspect -import os -import subprocess -import sys - -from setuptools import Command -from skbuild import setup - -""" -A generic wrapper to access nmodl binaries from a python installation -Please create a softlink with the binary name to be called. -""" -import stat -from find_libpython import find_libpython - - -# Main source of the version. Dont rename, used by Cmake -try: - v = ( - subprocess.run(["git", "describe", "--tags"], stdout=subprocess.PIPE) - .stdout.strip() - .decode() - ) - __version__ = v[: v.rfind("-")].replace("-", ".") if "-" in v else v - # allow to override version during development/testing - if "NMODL_WHEEL_VERSION" in os.environ: - __version__ = os.environ['NMODL_WHEEL_VERSION'] -except Exception as e: - raise RuntimeError("Could not get version from Git repo") from e - - -class lazy_dict(dict): - """When the value associated to a key is a function, then returns - the function call instead of the function. - """ - - def __getitem__(self, item): - value = dict.__getitem__(self, item) - if inspect.isfunction(value): - return value() - return value - - -def get_sphinx_command(): - """Lazy load of Sphinx distutils command class - """ - # If nbconvert is installed to .eggs on the fly when running setup.py then - # templates from it will not be found. This is a workaround. - if 'JUPYTER_PATH' not in os.environ: - import nbconvert - os.environ['JUPYTER_PATH'] = os.path.realpath(os.path.join(os.path.dirname(nbconvert.__file__), '..', 'share', 'jupyter')) - print("Setting JUPYTER_PATH={}".format(os.environ['JUPYTER_PATH'])) - - from sphinx.setup_command import BuildDoc - - return BuildDoc - - -class Docs(Command): - description = "Generate & optionally upload documentation to docs server" - user_options = [] - finalize_options = lambda self: None - initialize_options = lambda self: None - - def run(self, *args, **kwargs): - self.run_command("doctest") - self.run_command("buildhtml") - - -install_requirements = [ - "PyYAML>=3.13", - "sympy>=1.3", - "find_libpython", - "importlib_resources;python_version<'3.9'", - "importlib_metadata;python_version<'3.9'", -] - - -cmake_args = ["-DPYTHON_EXECUTABLE=" + sys.executable] -if "bdist_wheel" in sys.argv: - cmake_args.append("-DLINK_AGAINST_PYTHON=FALSE") - cmake_args.append("-DNMODL_ENABLE_TESTS=FALSE") - -# For CI, we want to build separate wheel -package_name = "NMODL" -if "NMODL_NIGHTLY_TAG" in os.environ: - package_name += os.environ["NMODL_NIGHTLY_TAG"] - -# Parse long description from README.rst -with open('README.rst', 'r', encoding='utf-8') as f: - long_description = f.read() - -setup( - name=package_name, - version=__version__, - author="Blue Brain Project", - author_email="bbp-ou-hpc@groupes.epfl.ch", - description="NEURON Modeling Language Source-to-Source Compiler Framework", - long_description=long_description, - long_description_content_type='text/markdown', - packages=["nmodl"], - scripts=["pywheel/shim/nmodl"], - include_package_data=True, - cmake_minimum_required_version="3.15.0", - cmake_args=cmake_args, - cmdclass=lazy_dict( - docs=Docs, doctest=get_sphinx_command, buildhtml=get_sphinx_command, - ), - zip_safe=False, - # myst_parser 2.0.0 want sphinx >= 6 but it leads to incompatibily with sphinxcontrib-applehelp and - # sphinxcontrib-htmlhelp that want PEP-517 support instead of setup.py (name clash with '-' and '.') - # So we pin low versions of packages - setup_requires=[ - "jinja2>=2.9.3", - "jupyter-client", - "jupyter", - "myst_parser<2.0.0", - "mistune<3", - "nbconvert", - "nbsphinx>=0.3.2", - "pytest>=3.7.2", - "sphinxcontrib-applehelp<1.0.3", # After this version it needs a toml file to work, no more setup.py - "sphinxcontrib-htmlhelp<=2.0.0", # After this version it needs a toml file to work, no more setup.py - "sphinx<6", - "sphinx-rtd-theme", # needs sphinx < 7 - "docutils<0.20", # needed by sphinx - ] - + install_requirements, - install_requires=install_requirements, -) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 0c6ccae181..a3dea0767f 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -34,9 +34,9 @@ set(CODE_GENERATOR_JINJA_FILES set(CODE_GENERATOR_PY_FILES ${PROJECT_SOURCE_DIR}/src/language/argument.py ${PROJECT_SOURCE_DIR}/src/language/code_generator.py + ${PROJECT_SOURCE_DIR}/src/language/language_parser.py ${PROJECT_SOURCE_DIR}/src/language/node_info.py ${PROJECT_SOURCE_DIR}/src/language/nodes.py - ${PROJECT_SOURCE_DIR}/src/language/language_parser.py ${PROJECT_SOURCE_DIR}/src/language/utils.py ) diff --git a/nmodl/__init__.py b/src/nmodl/nmodl/python/nmodl/__init__.py similarity index 100% rename from nmodl/__init__.py rename to src/nmodl/nmodl/python/nmodl/__init__.py diff --git a/pywheel/shim/_binwrapper.py b/src/nmodl/nmodl/python/nmodl/_binwrapper.py similarity index 86% rename from pywheel/shim/_binwrapper.py rename to src/nmodl/nmodl/python/nmodl/_binwrapper.py index f850864314..036334ceb6 100755 --- a/pywheel/shim/_binwrapper.py +++ b/src/nmodl/nmodl/python/nmodl/_binwrapper.py @@ -7,7 +7,6 @@ import stat import sys - if sys.version_info >= (3, 9): from importlib.metadata import metadata, PackageNotFoundError from importlib.resources import files @@ -18,7 +17,7 @@ from find_libpython import find_libpython -def _config_exe(exe_name): +def main(): """Sets the environment to run the real executable (returned)""" try: @@ -42,12 +41,7 @@ def _config_exe(exe_name): str(NMODL_PREFIX.parent) + ":" + os.environ.get("PYTHONPATH", "") ) - return os.path.join(NMODL_BIN, exe_name) - - -if __name__ == "__main__": - """Set the pointed file as executable""" - exe = _config_exe(os.path.basename(sys.argv[0])) + exe = NMODL_BIN / os.path.basename(sys.argv[0]) st = os.stat(exe) os.chmod(exe, st.st_mode | stat.S_IEXEC) os.execv(exe, sys.argv) diff --git a/nmodl/ast.py b/src/nmodl/nmodl/python/nmodl/ast.py similarity index 100% rename from nmodl/ast.py rename to src/nmodl/nmodl/python/nmodl/ast.py diff --git a/nmodl/dsl.py b/src/nmodl/nmodl/python/nmodl/dsl.py similarity index 100% rename from nmodl/dsl.py rename to src/nmodl/nmodl/python/nmodl/dsl.py diff --git a/nmodl/ext/example/exp2syn.mod b/src/nmodl/nmodl/python/nmodl/ext/example/exp2syn.mod similarity index 100% rename from nmodl/ext/example/exp2syn.mod rename to src/nmodl/nmodl/python/nmodl/ext/example/exp2syn.mod diff --git a/nmodl/ext/example/expsyn.mod b/src/nmodl/nmodl/python/nmodl/ext/example/expsyn.mod similarity index 100% rename from nmodl/ext/example/expsyn.mod rename to src/nmodl/nmodl/python/nmodl/ext/example/expsyn.mod diff --git a/nmodl/ext/example/hh.mod b/src/nmodl/nmodl/python/nmodl/ext/example/hh.mod similarity index 100% rename from nmodl/ext/example/hh.mod rename to src/nmodl/nmodl/python/nmodl/ext/example/hh.mod diff --git a/nmodl/ext/example/passive.mod b/src/nmodl/nmodl/python/nmodl/ext/example/passive.mod similarity index 100% rename from nmodl/ext/example/passive.mod rename to src/nmodl/nmodl/python/nmodl/ext/example/passive.mod diff --git a/nmodl/ext/viz/css/tree.css b/src/nmodl/nmodl/python/nmodl/ext/viz/css/tree.css similarity index 100% rename from nmodl/ext/viz/css/tree.css rename to src/nmodl/nmodl/python/nmodl/ext/viz/css/tree.css diff --git a/nmodl/ext/viz/index.html b/src/nmodl/nmodl/python/nmodl/ext/viz/index.html similarity index 100% rename from nmodl/ext/viz/index.html rename to src/nmodl/nmodl/python/nmodl/ext/viz/index.html diff --git a/nmodl/ext/viz/js/d3.min.js b/src/nmodl/nmodl/python/nmodl/ext/viz/js/d3.min.js similarity index 100% rename from nmodl/ext/viz/js/d3.min.js rename to src/nmodl/nmodl/python/nmodl/ext/viz/js/d3.min.js diff --git a/nmodl/ext/viz/js/tree.js b/src/nmodl/nmodl/python/nmodl/ext/viz/js/tree.js similarity index 100% rename from nmodl/ext/viz/js/tree.js rename to src/nmodl/nmodl/python/nmodl/ext/viz/js/tree.js diff --git a/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py similarity index 100% rename from nmodl/ode.py rename to src/nmodl/nmodl/python/nmodl/ode.py diff --git a/nmodl/symtab.py b/src/nmodl/nmodl/python/nmodl/symtab.py similarity index 100% rename from nmodl/symtab.py rename to src/nmodl/nmodl/python/nmodl/symtab.py diff --git a/nmodl/visitor.py b/src/nmodl/nmodl/python/nmodl/visitor.py similarity index 100% rename from nmodl/visitor.py rename to src/nmodl/nmodl/python/nmodl/visitor.py diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 9600515e11..0d7516874b 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -70,14 +70,14 @@ endif() # ============================================================================= file( GLOB NMODL_PYTHON_FILES - RELATIVE "${NMODL_PROJECT_SOURCE_DIR}/nmodl/" - CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/nmodl/*.py") + RELATIVE "${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/" + CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/*.py") foreach(file IN LISTS NMODL_PYTHON_FILES) - cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/nmodl/${file} OUTPUT + cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/${file} OUTPUT ${CMAKE_BINARY_DIR}/lib/nmodl/${file}) endforeach() -file(COPY ${NMODL_PROJECT_SOURCE_DIR}/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) +file(COPY ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) # ============================================================================= # Install python binding components From 74c30e4479f73a7c8473468299122b1cafc38d12 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 6 Mar 2024 12:58:11 +0100 Subject: [PATCH 604/871] Add building wheels using cibuildwheel (BlueBrain/nmodl#1155) * use cibuildwheel for creating redistributable wheels * simplify CI pipeline * update packaging docs --------- Co-authored-by: Nicolas Cornu Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@347f786d694fe35ea40696948bf7ecea1daf4fb3 --- docs/nmodl/transpiler/INSTALL.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/nmodl/transpiler/INSTALL.rst b/docs/nmodl/transpiler/INSTALL.rst index 9c4fc6af46..65bf1e2ca8 100644 --- a/docs/nmodl/transpiler/INSTALL.rst +++ b/docs/nmodl/transpiler/INSTALL.rst @@ -180,6 +180,8 @@ libpywrapper two environment variables need to be present: building without linking against libpython we must set ``NMODL_PYLIB`` before running cmake! +.. _testing-installed-module: + Testing the Installed Module ---------------------------- From 121bf58e18bc36dba50577ae36d895f92b2cafc7 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 7 Mar 2024 17:32:28 +0100 Subject: [PATCH 605/871] Fix parsing directories (BlueBrain/nmodl#1203) * Throw error if passing directory instead of file to `parse_file` * Add test for it NMODL Repo SHA: BlueBrain/nmodl@f8c8d2393e0c1d3692fd651f92b46eceefb3ab0d --- src/nmodl/parser/nmodl_driver.cpp | 4 ++++ test/nmodl/transpiler/unit/pybind/test_parser.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 test/nmodl/transpiler/unit/pybind/test_parser.py diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 63f50cc3a5..1a563f37b6 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -35,6 +35,10 @@ std::shared_ptr NmodlDriver::parse_stream(std::istream& in) { std::shared_ptr NmodlDriver::parse_file(const fs::path& filename, const location* loc) { + if (fs::is_directory(filename)) { + throw std::runtime_error("NMODL Parser Error : path " + filename.string() + + " appears to be a directory, please provide a file instead"); + } std::ifstream in(filename); if (!in.good()) { std::ostringstream oss; diff --git a/test/nmodl/transpiler/unit/pybind/test_parser.py b/test/nmodl/transpiler/unit/pybind/test_parser.py new file mode 100644 index 0000000000..6863a9c875 --- /dev/null +++ b/test/nmodl/transpiler/unit/pybind/test_parser.py @@ -0,0 +1,12 @@ +import nmodl +from pathlib import Path +import pytest + + +def test_parse_directory(): + """ + Make sure we raise an error when parsing a directory instead of a file + """ + + with pytest.raises(RuntimeError): + nmodl.NmodlDriver().parse_file(str(Path(__file__).parent)) From e24c4ab429e2954923316eac762d5296ebd564ad Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 8 Mar 2024 10:38:35 +0100 Subject: [PATCH 606/871] Improve CLI help message. (BlueBrain/nmodl#1205) NMODL Repo SHA: BlueBrain/nmodl@b99ad625537b74dd4a2c3390fef5be65ff71bca9 --- src/nmodl/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index ad06cab003..4dd4225edc 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -250,7 +250,7 @@ int main(int argc, const char* argv[]) { fmt::format("Write AST to JSON file ({})", json_ast))->ignore_case(); passes_opt->add_flag("--nmodl-ast", nmodl_ast, - fmt::format("Write AST to NMODL file ({})", nmodl_ast))->ignore_case(); + fmt::format("Write the intermediate AST after each pass as a NMODL file to the scratch directory ({})", nmodl_ast))->ignore_case(); passes_opt->add_flag("--json-perf", json_perfstat, fmt::format("Write performance statistics to JSON file ({})", json_perfstat))->ignore_case(); From d29cd06cce1b8aa2d1e7be2884ed69a8c6db22f6 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 11 Mar 2024 08:56:26 +0100 Subject: [PATCH 607/871] Add `--version` flag to CLI (BlueBrain/nmodl#1210) Fixes BlueBrain/nmodl#1178 NMODL Repo SHA: BlueBrain/nmodl@26899cc38331a73e3044732dcaf0fb9ae731e43d --- src/nmodl/main.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 4dd4225edc..70a26a66ae 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -187,7 +187,13 @@ int main(int argc, const char* argv[]) { ->ignore_case(); app.add_flag("--neuron", neuron_code, "Generate C++ code for NEURON"); app.add_flag("--coreneuron", coreneuron_code, "Generate C++ code for CoreNEURON (Default)"); - + app.add_flag( + "--version", + [](std::size_t count) { + std::cout << Version::to_string() << std::endl; + exit(0); + }, + "Print the version and exit"); auto host_opt = app.add_subcommand("host", "HOST/CPU code backends")->ignore_case(); host_opt->add_flag("--c,--cpp", cpp_backend, fmt::format("C++ backend ({})", cpp_backend)) ->ignore_case(); From 46f4d688f639e1867b5f5803bb2f2fc66824fe4b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 11 Mar 2024 11:15:32 +0100 Subject: [PATCH 608/871] Move `IndexRemover` to its own .hpp and .cpp file. (BlueBrain/nmodl#1206) NMODL Repo SHA: BlueBrain/nmodl@b9d3033484b496b853da5534cc208ad3a7dde370 --- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/visitors/CMakeLists.txt | 1 + src/nmodl/visitors/index_remover.cpp | 47 ++++++++++++++++ src/nmodl/visitors/index_remover.hpp | 51 +++++++++++++++++ src/nmodl/visitors/loop_unroll_visitor.cpp | 65 +--------------------- 5 files changed, 102 insertions(+), 64 deletions(-) create mode 100644 src/nmodl/visitors/index_remover.cpp create mode 100644 src/nmodl/visitors/index_remover.hpp diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 8f81155978..f8f8d69a66 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 8f8115597817365c5c4fa39e217b3ab0b3640cb2 +Subproject commit f8f8d69a66c23978d1c9c5dce62de79466f26e5d diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 03b98d228c..c798c83254 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -16,6 +16,7 @@ add_library( global_var_visitor.cpp implicit_argument_visitor.cpp indexedname_visitor.cpp + index_remover.cpp inline_visitor.cpp kinetic_block_visitor.cpp local_to_assigned_visitor.cpp diff --git a/src/nmodl/visitors/index_remover.cpp b/src/nmodl/visitors/index_remover.cpp new file mode 100644 index 0000000000..82db82ccf7 --- /dev/null +++ b/src/nmodl/visitors/index_remover.cpp @@ -0,0 +1,47 @@ +#include "index_remover.hpp" + +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" + +namespace nmodl { +namespace visitor { + +IndexRemover::IndexRemover(std::string index, int value) + : index(std::move(index)) + , value(value) {} + +/// if expression we are visiting is `Name` then return new `Integer` node +std::shared_ptr IndexRemover::replace_for_name( + const std::shared_ptr& node) const { + if (node->is_name()) { + auto name = std::dynamic_pointer_cast(node); + if (name->get_node_name() == index) { + return std::make_shared(value, nullptr); + } + } + return node; +} + +void IndexRemover::visit_binary_expression(ast::BinaryExpression& node) { + node.visit_children(*this); + if (under_indexed_name) { + /// first recursively replaces children + /// replace lhs & rhs if they have matching index variable + auto lhs = replace_for_name(node.get_lhs()); + auto rhs = replace_for_name(node.get_rhs()); + node.set_lhs(std::move(lhs)); + node.set_rhs(std::move(rhs)); + } +} + +void IndexRemover::visit_indexed_name(ast::IndexedName& node) { + under_indexed_name = true; + node.visit_children(*this); + /// once all children are replaced, do the same for index + auto length = replace_for_name(node.get_length()); + node.set_length(std::move(length)); + under_indexed_name = false; +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/index_remover.hpp b/src/nmodl/visitors/index_remover.hpp new file mode 100644 index 0000000000..17b4c013e2 --- /dev/null +++ b/src/nmodl/visitors/index_remover.hpp @@ -0,0 +1,51 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "ast/all.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +/** + * \class IndexRemover + * \brief Helper visitor to replace index of array variable with integer + * + * When loop is unrolled, the index variable like `i` : + * + * ca[i] <-> ca[i+1] + * + * has type `Name` in the AST. This needs to be replaced with `Integer` + * for optimizations like constant folding. This pass look at name and + * binary expressions under index variables. + */ +class IndexRemover: public AstVisitor { + private: + /// index variable name + std::string index; + + /// integer value of index variable + int value; + + /// true if we are visiting index variable + bool under_indexed_name = false; + + public: + IndexRemover(std::string index, int value); + + /// if expression we are visiting is `Name` then return new `Integer` node + std::shared_ptr replace_for_name( + const std::shared_ptr& node) const; + + void visit_binary_expression(ast::BinaryExpression& node) override; + void visit_indexed_name(ast::IndexedName& node) override; +}; + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/loop_unroll_visitor.cpp b/src/nmodl/visitors/loop_unroll_visitor.cpp index 35697f19be..6369fd7379 100644 --- a/src/nmodl/visitors/loop_unroll_visitor.cpp +++ b/src/nmodl/visitors/loop_unroll_visitor.cpp @@ -7,77 +7,16 @@ #include "visitors/loop_unroll_visitor.hpp" + #include "ast/all.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" +#include "visitors/index_remover.hpp" #include "visitors/visitor_utils.hpp" - namespace nmodl { namespace visitor { -/** - * \class IndexRemover - * \brief Helper visitor to replace index of array variable with integer - * - * When loop is unrolled, the index variable like `i` : - * - * ca[i] <-> ca[i+1] - * - * has type `Name` in the AST. This needs to be replaced with `Integer` - * for optimizations like constant folding. This pass look at name and - * binary expressions under index variables. - */ -class IndexRemover: public AstVisitor { - private: - /// index variable name - std::string index; - - /// integer value of index variable - int value; - - /// true if we are visiting index variable - bool under_indexed_name = false; - - public: - IndexRemover(std::string index, int value) - : index(std::move(index)) - , value(value) {} - - /// if expression we are visiting is `Name` then return new `Integer` node - std::shared_ptr replace_for_name( - const std::shared_ptr& node) const { - if (node->is_name()) { - auto name = std::dynamic_pointer_cast(node); - if (name->get_node_name() == index) { - return std::make_shared(value, nullptr); - } - } - return node; - } - - void visit_binary_expression(ast::BinaryExpression& node) override { - node.visit_children(*this); - if (under_indexed_name) { - /// first recursively replaces children - /// replace lhs & rhs if they have matching index variable - auto lhs = replace_for_name(node.get_lhs()); - auto rhs = replace_for_name(node.get_rhs()); - node.set_lhs(std::move(lhs)); - node.set_rhs(std::move(rhs)); - } - } - - void visit_indexed_name(ast::IndexedName& node) override { - under_indexed_name = true; - node.visit_children(*this); - /// once all children are replaced, do the same for index - auto length = replace_for_name(node.get_length()); - node.set_length(std::move(length)); - under_indexed_name = false; - } -}; - /// return underlying expression wrapped by WrappedExpression static std::shared_ptr unwrap(const std::shared_ptr& expr) { From 82a503dec1b3e15b53c3715be9953c26a1066108 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 11 Mar 2024 11:26:46 +0100 Subject: [PATCH 609/871] Fail for invalid `<<` reaction equation. (BlueBrain/nmodl#1204) The previous behaviour for KINETIC states { ~ x + y << 42.0 } was to print a warning and continue as if the line didn't exist. The new behaviour is to throw an error, because it's unclear why ignoring the line could ever lead to a correct translation of the MOD file. NMODL Repo SHA: BlueBrain/nmodl@9292c18027154292984bc75095081a107fffdb12 --- src/nmodl/visitors/kinetic_block_visitor.cpp | 4 ++-- test/nmodl/transpiler/unit/visitor/kinetic_block.cpp | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 69a317a3b3..a8d78dc4c4 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -222,10 +222,10 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement& node) } } if (!lhs->is_react_var_name() || !single_state_var) { - logger->warn( + throw std::runtime_error(fmt::format( "KineticBlockVisitor :: LHS of \"<<\" reaction statement must be a single state " "var, but instead found {}: ignoring this statement", - to_nmodl(lhs)); + to_nmodl(lhs))); return; } const auto& rhs = node.get_expression1(); diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 800eafbe81..7d52ae86bb 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -129,13 +129,8 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ KINETIC states { ~ x + y << (2*z) })"; - std::string output_nmodl_text = R"( - DERIVATIVE states { - })"; - THEN("Emit warning & do not process statement") { - auto result = run_kinetic_block_visitor(input_nmodl_text); - CAPTURE(input_nmodl_text); - REQUIRE(result[0] == reindent_text(output_nmodl_text)); + THEN("Fail by throwing an error.") { + CHECK_THROWS(run_kinetic_block_visitor(input_nmodl_text)); } } GIVEN("KINETIC block with -> reaction statement, 1 state var, flux vars") { From 59c8e09ae616194792754aebc6d6ab723b10cf3e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 11 Mar 2024 11:29:43 +0100 Subject: [PATCH 610/871] Enable formatting Python files. (BlueBrain/nmodl#1207) * Format Python files with `black`. * Enable `black` formatter. NMODL Repo SHA: BlueBrain/nmodl@40aa0bf7b4e9aef489e12dfccd3950b97b68ce13 --- docs/nmodl/transpiler/conf.py | 2 +- src/nmodl/language/argument.py | 1 + src/nmodl/language/code_generator.py | 22 +- src/nmodl/language/language_parser.py | 102 +++++---- src/nmodl/language/nodes.py | 212 ++++++++++++------ src/nmodl/language/utils.py | 4 +- src/nmodl/nmodl/python/nmodl/ast.py | 1 + src/nmodl/nmodl/python/nmodl/ode.py | 79 +++++-- test/nmodl/transpiler/unit/ode/test_ode.py | 7 +- test/nmodl/transpiler/unit/pybind/test_ast.py | 15 +- .../transpiler/unit/pybind/test_symtab.py | 10 +- .../transpiler/unit/pybind/test_visitor.py | 26 ++- .../usecases/cnexp_array/simulate.py | 2 +- .../usecases/cnexp_non_trivial/demo.py | 24 ++ .../usecases/cnexp_non_trivial/simulate.py | 27 +++ .../transpiler/usecases/comments/simulate.py | 1 + .../transpiler/usecases/func_proc/simulate.py | 2 +- .../usecases/global_breakpoint/simulate.py | 2 +- .../usecases/nonspecific_current/simulate.py | 2 +- .../transpiler/usecases/parameter/simulate.py | 3 +- 20 files changed, 373 insertions(+), 171 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py create mode 100644 test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py create mode 100644 test/nmodl/transpiler/usecases/comments/simulate.py diff --git a/docs/nmodl/transpiler/conf.py b/docs/nmodl/transpiler/conf.py index ff9c2afbce..2fd3e52303 100644 --- a/docs/nmodl/transpiler/conf.py +++ b/docs/nmodl/transpiler/conf.py @@ -170,7 +170,7 @@ (master_doc, "nmodl.tex", "nmodl Documentation", "BlueBrain HPC team", "manual") ] -imgmath_image_format = 'svg' +imgmath_image_format = "svg" imgmath_embed = True imgmath_font_size = 14 diff --git a/src/nmodl/language/argument.py b/src/nmodl/language/argument.py index 8b8f7ad594..5745b193d6 100644 --- a/src/nmodl/language/argument.py +++ b/src/nmodl/language/argument.py @@ -3,6 +3,7 @@ # # SPDX-License-Identifier: Apache-2.0 + class Argument: """Utility class for holding all arguments for node classes""" diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index f2d09ae49a..90113d0126 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -111,7 +111,7 @@ def jinja_template(self, path): return self.jinja_env.get_template(name) def _cmake_deps_task(self, tasks): - """"Construct the JinjaTask generating the CMake file exporting all dependencies + """ "Construct the JinjaTask generating the CMake file exporting all dependencies Args: tasks: list of JinjaTask objects @@ -196,12 +196,18 @@ def workload(self): task = JinjaTask( app=self, input=filepath, - output=self.base_dir / sub_dir / "pynode_{}.cpp".format(chunk_k), + output=self.base_dir + / sub_dir + / "pynode_{}.cpp".format(chunk_k), context=dict( nodes=self.nodes[ - chunk_k * chunk_length : (chunk_k + 1) * chunk_length + chunk_k + * chunk_length : (chunk_k + 1) + * chunk_length ], - setup_pybind_method="init_pybind_classes_{}".format(chunk_k), + setup_pybind_method="init_pybind_classes_{}".format( + chunk_k + ), ), extradeps=extradeps[filepath], ) @@ -212,7 +218,11 @@ def workload(self): app=self, input=filepath, output=self.base_dir / sub_dir / filepath.name, - context=dict(nodes=self.nodes, node_info=node_info, **extracontext[filepath]), + context=dict( + nodes=self.nodes, + node_info=node_info, + **extracontext[filepath], + ), extradeps=extradeps[filepath], ) tasks.append(task) @@ -235,7 +245,7 @@ class JinjaTask( """ def execute(self): - """"Perform the Jinja task + """ "Perform the Jinja task Execute Jinja renderer if the output file is out-of-date. diff --git a/src/nmodl/language/language_parser.py b/src/nmodl/language/language_parser.py index df7d49aff9..5f8e1e3869 100644 --- a/src/nmodl/language/language_parser.py +++ b/src/nmodl/language/language_parser.py @@ -26,7 +26,6 @@ def __init__(self, filename, debug=False): self.filename = filename self.debug = debug - def parse_child_rule(self, child): """parse child specification and return argument as properties @@ -40,72 +39,72 @@ def parse_child_rule(self, child): args.varname = varname # type i.e. class of the variable - args.class_name = properties['type'] + args.class_name = properties["type"] if self.debug: - print('Child {}, {}'.format(args.varname, args.class_name)) + print("Child {}, {}".format(args.varname, args.class_name)) # if there is add method for member in the class - if 'add' in properties: - args.add_method = properties['add'] + if "add" in properties: + args.add_method = properties["add"] # if variable is an optional member - if 'optional' in properties: - args.is_optional = properties['optional'] + if "optional" in properties: + args.is_optional = properties["optional"] # if variable is vector, the separator to use while # printing back to nmodl - if 'separator' in properties: - args.separator = properties['separator'] + if "separator" in properties: + args.separator = properties["separator"] # if variable is public member - if 'public' in properties: - args.is_public = properties['public'] + if "public" in properties: + args.is_public = properties["public"] # if variable if of vector type - if 'vector' in properties: - args.is_vector = properties['vector'] + if "vector" in properties: + args.is_vector = properties["vector"] # if get_node_name method required - if 'node_name' in properties: - args.get_node_name = properties['node_name'] + if "node_name" in properties: + args.get_node_name = properties["node_name"] # brief description of member variable - if 'brief' in properties: - args.brief = properties['brief'] + if "brief" in properties: + args.brief = properties["brief"] # description of member variable - if 'description' in properties: - args.description = properties['description'] + if "description" in properties: + args.description = properties["description"] # if getter method required - if 'getter' in properties: - if 'name' in properties['getter']: - args.getter_method = properties['getter']['name'] - if 'override' in properties['getter']: - args.getter_override = properties['getter']['override'] + if "getter" in properties: + if "name" in properties["getter"]: + args.getter_method = properties["getter"]["name"] + if "override" in properties["getter"]: + args.getter_override = properties["getter"]["override"] # if there is nmodl name - if 'nmodl' in properties: - args.nmodl_name = properties['nmodl'] + if "nmodl" in properties: + args.nmodl_name = properties["nmodl"] # prefix while printing back to NMODL - if 'prefix' in properties: - args.prefix = properties['prefix']['value'] + if "prefix" in properties: + args.prefix = properties["prefix"]["value"] # if prefix is compulsory to print in NMODL then make suffix empty - if 'force' in properties['prefix']: - if properties['prefix']['force']: + if "force" in properties["prefix"]: + if properties["prefix"]["force"]: args.force_prefix = args.prefix args.prefix = "" # suffix while printing back to NMODL - if 'suffix' in properties: - args.suffix = properties['suffix']['value'] + if "suffix" in properties: + args.suffix = properties["suffix"]["value"] # if suffix is compulsory to print in NMODL then make suffix empty - if 'force' in properties['suffix']: - if properties['suffix']['force']: + if "force" in properties["suffix"]: + if properties["suffix"]["force"]: args.force_suffix = args.suffix args.suffix = "" @@ -124,15 +123,17 @@ def parse_yaml_rules(self, nodelist, base_class=None): continue args = Argument() - args.url = properties.get('url', None) + args.url = properties.get("url", None) args.class_name = class_name - args.brief = properties.get('brief', '') - args.description = properties.get('description', '') + args.brief = properties.get("brief", "") + args.description = properties.get("description", "") # yaml file has abstract classes and their subclasses with children as a property - if 'children' in properties: + if "children" in properties: # recursively parse all sub-classes of current abstract class - child_abstract_nodes, child_nodes = self.parse_yaml_rules(properties['children'], class_name) + child_abstract_nodes, child_nodes = self.parse_yaml_rules( + properties["children"], class_name + ) # append all parsed subclasses abstract_nodes.extend(child_abstract_nodes) @@ -146,26 +147,26 @@ def parse_yaml_rules(self, nodelist, base_class=None): abstract_nodes.append(node) nodes.insert(0, node) if self.debug: - print('Abstract {}'.format(node)) + print("Abstract {}".format(node)) else: - args.base_class = base_class if base_class else 'Ast' + args.base_class = base_class if base_class else "Ast" # store token in every node args.has_token = True # name of the node while printing back to NMODL - args.nmodl_name = properties['nmodl'] if 'nmodl' in properties else None + args.nmodl_name = properties["nmodl"] if "nmodl" in properties else None # create tree node and add to the list node = Node(args) nodes.append(node) if self.debug: - print('Class {}'.format(node)) + print("Class {}".format(node)) # now process all children specification - if 'members' in properties: - for child in properties['members']: + if "members" in properties: + for child in properties["members"]: args = self.parse_child_rule(child) node.add_child(args) @@ -179,15 +180,18 @@ def parse_yaml_rules(self, nodelist, base_class=None): return abstract_nodes, nodes def parse_file(self): - """ parse nmodl YAML specification file for AST creation """ + """parse nmodl YAML specification file for AST creation""" - with open(self.filename, 'r') as stream: + with open(self.filename, "r") as stream: try: rules = yaml.safe_load(stream) _, nodes = self.parse_yaml_rules(rules) except yaml.YAMLError as e: - print("Error while parsing YAML definition file {0} : {1}".format( - self.filename, e.strerror)) + print( + "Error while parsing YAML definition file {0} : {1}".format( + self.filename, e.strerror + ) + ) sys.exit(1) return nodes diff --git a/src/nmodl/language/nodes.py b/src/nmodl/language/nodes.py index 9e37a40c59..c1644e4a28 100644 --- a/src/nmodl/language/nodes.py +++ b/src/nmodl/language/nodes.py @@ -17,7 +17,7 @@ class BaseNode: - """base class for all node types (parent + child) """ + """base class for all node types (parent + child)""" def __init__(self, args): self.class_name = args.class_name @@ -35,7 +35,7 @@ def __lt__(self, other): return self.class_name < other.class_name def get_data_type_name(self): - """ return type name for the node """ + """return type name for the node""" return node_info.DATA_TYPES[self.class_name] @property @@ -145,8 +145,9 @@ def is_enum_node(self): @property def is_pointer_node(self): - return not (self.class_name in node_info.PTR_EXCLUDE_TYPES or - self.is_base_type_node) + return not ( + self.class_name in node_info.PTR_EXCLUDE_TYPES or self.is_base_type_node + ) @property def is_ptr_excluded_node(self): @@ -154,9 +155,11 @@ def is_ptr_excluded_node(self): @property def requires_default_constructor(self): - return (self.class_name in node_info.LEXER_DATA_TYPES or - self.is_program_node or - self.is_ptr_excluded_node) + return ( + self.class_name in node_info.LEXER_DATA_TYPES + or self.is_program_node + or self.is_ptr_excluded_node + ) @property def has_template_methods(self): @@ -205,7 +208,11 @@ def get_rvalue_typename(self): typename = self.get_typename() if self.is_vector: return "const " + typename + "&" - if self.is_base_type_node and not self.is_integral_type_node or self.is_ptr_excluded_node: + if ( + self.is_base_type_node + and not self.is_integral_type_node + or self.is_ptr_excluded_node + ): return "const " + typename + "&" return typename @@ -242,18 +249,23 @@ def member_typename(self): @property def _is_member_type_wrapped_as_shared_pointer(self): - return not (self.is_vector or self.is_base_type_node or self.is_ptr_excluded_node) + return not ( + self.is_vector or self.is_base_type_node or self.is_ptr_excluded_node + ) @property def member_rvalue_typename(self): """returns rvalue reference type when used as returned or parameter type""" typename = self.member_typename - if not self.is_integral_type_node and not self._is_member_type_wrapped_as_shared_pointer: + if ( + not self.is_integral_type_node + and not self._is_member_type_wrapped_as_shared_pointer + ): return "const " + typename + "&" return typename def get_add_methods_declaration(self): - s = '' + s = "" if self.add_method: method = f""" /** @@ -304,11 +316,11 @@ def get_add_methods_declaration(self): */ void reset_{to_snake_case(self.class_name)}({self.class_name}Vector::const_iterator position, std::shared_ptr<{self.class_name}> n); """ - s = textwrap.indent(textwrap.dedent(method), ' ') + s = textwrap.indent(textwrap.dedent(method), " ") return s def get_add_methods_definition(self, parent): - s = '' + s = "" if self.add_method: set_parent = "n->set_parent(this); " if self.optional: @@ -407,7 +419,7 @@ def get_add_methods_definition(self, parent): return s def get_add_methods_inline_definition(self, parent): - s = '' + s = "" if self.add_method: set_parent = "n->set_parent(this); " if self.optional: @@ -435,7 +447,7 @@ def get_add_methods_inline_definition(self, parent): return s def get_node_name_method_declaration(self): - s = '' + s = "" if self.get_node_name: # string node should be evaluated and hence eval() method method = f""" @@ -451,11 +463,11 @@ def get_node_name_method_declaration(self): * \\sa Ast::get_node_type_name */ std::string get_node_name() const override;""" - s = textwrap.indent(textwrap.dedent(method), ' ') + s = textwrap.indent(textwrap.dedent(method), " ") return s def get_node_name_method_definition(self, parent): - s = '' + s = "" if self.get_node_name: # string node should be evaluated and hence eval() method method_name = "eval" if self.is_string_node else "get_node_name" @@ -466,31 +478,47 @@ def get_node_name_method_definition(self, parent): return s def get_getter_method(self, class_name): - getter_method = self.getter_method if self.getter_method else "get_" + to_snake_case(self.varname) + getter_method = ( + self.getter_method + if self.getter_method + else "get_" + to_snake_case(self.varname) + ) getter_override = "override" if self.getter_override else "" return_type = self.member_rvalue_typename - return textwrap.indent(textwrap.dedent(f""" + return textwrap.indent( + textwrap.dedent( + f""" /** * \\brief Getter for member variable \\ref {class_name}.{self.varname} */ {return_type} {getter_method}() const noexcept {getter_override} {{ return {self.varname}; }} - """), ' ') + """ + ), + " ", + ) def get_setter_method_declaration(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename reference = "" if self.is_base_type_node else "&&" if self.is_base_type_node: - return textwrap.indent(textwrap.dedent(f""" + return textwrap.indent( + textwrap.dedent( + f""" /** * \\brief Setter for member variable \\ref {class_name}.{self.varname} */ void {setter_method}({setter_type} {self.varname}); - """), ' ') + """ + ), + " ", + ) else: - return textwrap.indent(textwrap.dedent(f""" + return textwrap.indent( + textwrap.dedent( + f""" /** * \\brief Setter for member variable \\ref {class_name}.{self.varname} (rvalue reference) */ @@ -500,14 +528,16 @@ def get_setter_method_declaration(self, class_name): * \\brief Setter for member variable \\ref {class_name}.{self.varname} */ void {setter_method}(const {setter_type}& {self.varname}); - """), ' ') + """ + ), + " ", + ) def get_setter_method_definition(self, class_name): setter_method = "set_" + to_snake_case(self.varname) setter_type = self.member_typename reference = "" if self.is_base_type_node else "&&" - if self.is_base_type_node: return f""" void {class_name}::{setter_method}({setter_type} {self.varname}) {{ @@ -563,12 +593,10 @@ def get_setter_method_definition(self, class_name): }} """ - - - def __repr__(self): return "ChildNode(class_name='{}', nmodl_name='{}')".format( - self.class_name, self.nmodl_name) + self.class_name, self.nmodl_name + ) __str__ = __repr__ @@ -595,7 +623,9 @@ def cpp_header_deps(self): for child in self.children: if child.is_ptr_excluded_node or child.is_vector: dependent_classes.add(child.class_name) - return sorted(["ast/{}.hpp".format(to_snake_case(clazz)) for clazz in dependent_classes]) + return sorted( + ["ast/{}.hpp".format(to_snake_case(clazz)) for clazz in dependent_classes] + ) @property def ast_enum_name(self): @@ -631,12 +661,14 @@ def has_parent_block_node(self): @property def has_setters(self): """returns True if the class has at least one setter member method""" - return any([ - self.is_name_node, - self.has_token, - self.is_symtab_needed, - self.is_data_type_node and not self.is_enum_node - ]) + return any( + [ + self.is_name_node, + self.has_token, + self.is_symtab_needed, + self.is_data_type_node and not self.is_enum_node, + ] + ) @property def is_base_block_node(self): @@ -669,13 +701,13 @@ def is_symtab_method_required(self): :return: True if need to print visit method for node in symtabjsonvisitor otherwise False """ - return (self.has_children() and - (self.is_symbol_var_node or - self.is_symbol_block_node or - self.is_symbol_helper_node or - self.is_program_node or - self.has_parent_block_node() - )) + return self.has_children() and ( + self.is_symbol_var_node + or self.is_symbol_block_node + or self.is_symbol_helper_node + or self.is_program_node + or self.has_parent_block_node() + ) @property def is_base_class_number_node(self): @@ -685,12 +717,12 @@ def is_base_class_number_node(self): return self.base_class == node_info.NUMBER_NODE def ctor_declaration(self): - args = [f'{c.get_rvalue_typename()} {c.varname}' for c in self.children] + args = [f"{c.get_rvalue_typename()} {c.varname}" for c in self.children] return f"explicit {self.class_name}({', '.join(args)});" def ctor_definition(self): - args = [f'{c.get_rvalue_typename()} {c.varname}' for c in self.children] - initlist = [f'{c.varname}({c.varname})' for c in self.children] + args = [f"{c.get_rvalue_typename()} {c.varname}" for c in self.children] + initlist = [f"{c.varname}({c.varname})" for c in self.children] s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) : {', '.join(initlist)} {{ set_parent_in_children(); }} @@ -698,12 +730,12 @@ def ctor_definition(self): return textwrap.dedent(s) def ctor_shrptr_declaration(self): - args = [f'{c.member_rvalue_typename} {c.varname}' for c in self.children] + args = [f"{c.member_rvalue_typename} {c.varname}" for c in self.children] return f"explicit {self.class_name}({', '.join(args)});" def ctor_shrptr_definition(self): - args = [f'{c.member_rvalue_typename} {c.varname}' for c in self.children] - initlist = [f'{c.varname}({c.varname})' for c in self.children] + args = [f"{c.member_rvalue_typename} {c.varname}" for c in self.children] + initlist = [f"{c.varname}({c.varname})" for c in self.children] s = f"""{self.class_name}::{self.class_name}({', '.join(args)}) : {', '.join(initlist)} {{ set_parent_in_children(); }} @@ -711,16 +743,20 @@ def ctor_shrptr_definition(self): return textwrap.dedent(s) def has_ptr_children(self): - return any(not (c.is_vector or c.is_base_type_node or c.is_ptr_excluded_node) - for c in self.children) + return any( + not (c.is_vector or c.is_base_type_node or c.is_ptr_excluded_node) + for c in self.children + ) def public_members(self): """ Return public members of the node """ - members = [[child.member_typename, child.varname, None, child.brief] - for child in self.children - if child.is_public] + members = [ + [child.member_typename, child.varname, None, child.brief] + for child in self.children + if child.is_public + ] return members @@ -728,18 +764,41 @@ def private_members(self): """ Return private members of the node """ - members = [[child.member_typename, child.varname, None, child.brief] - for child in self.children - if not child.is_public] + members = [ + [child.member_typename, child.varname, None, child.brief] + for child in self.children + if not child.is_public + ] if self.has_token: - members.append(["std::shared_ptr", "token", None, "token with location information"]) + members.append( + [ + "std::shared_ptr", + "token", + None, + "token with location information", + ] + ) if self.is_symtab_needed: - members.append(["symtab::SymbolTable*", "symtab", "nullptr", "symbol table for a block"]) + members.append( + [ + "symtab::SymbolTable*", + "symtab", + "nullptr", + "symbol table for a block", + ] + ) if self.is_program_node: - members.append(["symtab::ModelSymbolTable", "model_symtab", None, "global symbol table for model"]) + members.append( + [ + "symtab::ModelSymbolTable", + "model_symtab", + None, + "global symbol table for model", + ] + ) return members @@ -747,12 +806,28 @@ def properties(self): """ Return private members of the node destined to be pybind properties """ - members = [[child.member_typename, child.varname, child.is_base_type_node, None, child.brief] - for child in self.children - if not child.is_public] + members = [ + [ + child.member_typename, + child.varname, + child.is_base_type_node, + None, + child.brief, + ] + for child in self.children + if not child.is_public + ] if self.has_token: - members.append(["std::shared_ptr", "token", True, None, "token with location information"]) + members.append( + [ + "std::shared_ptr", + "token", + True, + None, + "token with location information", + ] + ) return members @@ -764,18 +839,19 @@ def get_description(self): """ Return description for the node in doxygen form """ - lines = self.description.split('\n') + lines = self.description.split("\n") description = "" for i, line in enumerate(lines): if i == 0: - description = ' ' + line + '\n' + description = " " + line + "\n" else: - description += ' * ' + line + '\n' + description += " * " + line + "\n" return description def __repr__(self): return "Node(class_name='{}', base_class='{}', nmodl_name='{}')".format( - self.class_name, self.base_class, self.nmodl_name) + self.class_name, self.base_class, self.nmodl_name + ) def __eq__(self, other): """ diff --git a/src/nmodl/language/utils.py b/src/nmodl/language/utils.py index 9aeb04d6c4..82dd3a0334 100644 --- a/src/nmodl/language/utils.py +++ b/src/nmodl/language/utils.py @@ -8,8 +8,8 @@ def camel_case_to_underscore(name): """convert string from 'AaaBbbbCccDdd' -> 'Aaa_Bbbb_Ccc_Ddd'""" - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name) - typename = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1) + s1 = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) + typename = re.sub("([a-z0-9])([A-Z])", r"\1_\2", s1) return typename diff --git a/src/nmodl/nmodl/python/nmodl/ast.py b/src/nmodl/nmodl/python/nmodl/ast.py index b8f990f592..1963e1ae25 100644 --- a/src/nmodl/nmodl/python/nmodl/ast.py +++ b/src/nmodl/nmodl/python/nmodl/ast.py @@ -1,6 +1,7 @@ """ Module for vizualization of NMODL abstract syntax trees (ASTs). """ + import getpass import json import os diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index e25da9337b..cbb848839a 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -16,11 +16,11 @@ # accessed through regular imports major, minor = (int(v) for v in sp.__version__.split(".")[:2]) if major >= 1 and minor >= 7: - known_functions = import_module('sympy.printing.c').known_functions_C99 + known_functions = import_module("sympy.printing.c").known_functions_C99 else: - known_functions = import_module('sympy.printing.ccode').known_functions_C99 -known_functions.pop('Abs') -known_functions['abs'] = 'fabs' + known_functions = import_module("sympy.printing.ccode").known_functions_C99 +known_functions.pop("Abs") +known_functions["abs"] = "fabs" if not ((major >= 1) and (minor >= 2)): @@ -29,7 +29,18 @@ # Some functions are protected inside sympy, if user has declared such a function, it will fail # because sympy will try to use its own internal one. # Rename it before and after to a single name -forbidden_var = ["beta", "gamma", "uppergamma", "lowergamma", "polygamma", "loggamma", "digamma", "trigamma"] +forbidden_var = [ + "beta", + "gamma", + "uppergamma", + "lowergamma", + "polygamma", + "loggamma", + "digamma", + "trigamma", +] + + def search_and_replace_protected_functions_to_sympy(eqs, function_calls): for c in function_calls: if c in forbidden_var: @@ -38,6 +49,7 @@ def search_and_replace_protected_functions_to_sympy(eqs, function_calls): eqs = [re.sub(r, f, x) for x in eqs] return eqs + def search_and_replace_protected_functions_from_sympy(eqs, function_calls): for c in function_calls: if c in forbidden_var: @@ -45,6 +57,7 @@ def search_and_replace_protected_functions_from_sympy(eqs, function_calls): eqs = [re.sub(r, f"{c}", x) for x in eqs] return eqs + def _get_custom_functions(fcts): custom_functions = {} for f in fcts: @@ -143,13 +156,16 @@ def _sympify_eqs(eq_strings, state_vars, vars): for state_var in state_vars: sympy_state_vars.append(sp.sympify(state_var, locals=sympy_vars)) eqs = [ - (sp.sympify(eq.split("=", 1)[1], locals=sympy_vars) - - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars)).expand() + ( + sp.sympify(eq.split("=", 1)[1], locals=sympy_vars) + - sp.sympify(eq.split("=", 1)[0], locals=sympy_vars) + ).expand() for eq in eq_strings ] return eqs, sympy_state_vars, sympy_vars + def _interweave_eqs(F, J): """Interweave F and J equations so that they are printed in code rowwise from the equation J x = F. For example: @@ -199,13 +215,21 @@ def _interweave_eqs(F, J): n = len(F) for i, expr in enumerate(F): code.append(expr) - for j in range(i * n, (i+1) * n): + for j in range(i * n, (i + 1) * n): code.append(J[j]) return code -def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_prefix, small_system=False, do_cse=False): +def solve_lin_system( + eq_strings, + vars, + constants, + function_calls, + tmp_unique_prefix, + small_system=False, + do_cse=False, +): """Solve linear system of equations, return solution as C code. If system is small (small_system=True, typically N<=3): @@ -233,7 +257,9 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre vars: list of strings containing new local variables """ - eq_strings = search_and_replace_protected_functions_to_sympy(eq_strings, function_calls) + eq_strings = search_and_replace_protected_functions_to_sympy( + eq_strings, function_calls + ) eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) custom_fcts = _get_custom_functions(function_calls) @@ -246,7 +272,9 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre solution_vector = sp.linsolve(eqs, state_vars).args[0] if do_cse: # generate prefix for new local vars that avoids clashes - my_symbols = sp.utilities.iterables.numbered_symbols(prefix=tmp_unique_prefix + '_') + my_symbols = sp.utilities.iterables.numbered_symbols( + prefix=tmp_unique_prefix + "_" + ) sub_exprs, simplified_solution_vector = sp.cse( solution_vector, symbols=my_symbols, @@ -255,10 +283,14 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre ) for var, expr in sub_exprs: new_local_vars.append(sp.ccode(var)) - code.append(f"{var} = {sp.ccode(expr.evalf(), user_functions=custom_fcts)}") + code.append( + f"{var} = {sp.ccode(expr.evalf(), user_functions=custom_fcts)}" + ) solution_vector = simplified_solution_vector[0] for var, expr in zip(state_vars, solution_vector): - code.append(f"{sp.ccode(var)} = {sp.ccode(expr.evalf(), contract=False, user_functions=custom_fcts)}") + code.append( + f"{sp.ccode(var)} = {sp.ccode(expr.evalf(), contract=False, user_functions=custom_fcts)}" + ) else: # large linear system: construct and return matrix J, vector F such that # J X = F is the linear system to be solved for X by e.g. LU factorization @@ -267,13 +299,17 @@ def solve_lin_system(eq_strings, vars, constants, function_calls, tmp_unique_pre # construct vector F vecFcode = [] for i, expr in enumerate(vecF): - vecFcode.append(f"F[{i}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}") + vecFcode.append( + f"F[{i}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}" + ) # construct matrix J vecJcode = [] for i, expr in enumerate(matJ): # todo: fix indexing to be ascending order flat_index = matJ.rows * (i % matJ.rows) + (i // matJ.rows) - vecJcode.append(f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}") + vecJcode.append( + f"J[{flat_index}] = {sp.ccode(expr.simplify().evalf(), user_functions=custom_fcts)}" + ) # interweave code = _interweave_eqs(vecFcode, vecJcode) @@ -299,7 +335,9 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): List of strings containing assignment statements """ - eq_strings = search_and_replace_protected_functions_to_sympy(eq_strings, function_calls) + eq_strings = search_and_replace_protected_functions_to_sympy( + eq_strings, function_calls + ) eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) custom_fcts = _get_custom_functions(function_calls) @@ -310,13 +348,18 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): vecFcode = [] for i, eq in enumerate(eqs): - vecFcode.append(f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}") + vecFcode.append( + f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}" + ) vecJcode = [] for i, j in itertools.product(range(jacobian.rows), range(jacobian.cols)): flat_index = i + jacobian.rows * j - rhs = sp.ccode(jacobian[i,j].simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts) + rhs = sp.ccode( + jacobian[i, j].simplify().subs(X_vec_map).evalf(), + user_functions=custom_fcts, + ) vecJcode.append(f"J[{flat_index}] = {rhs}") # interweave diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index ec92958daa..387cfb801f 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -67,7 +67,8 @@ def test_differentiate2c(): # multiple prev_eqs to substitute # (these statements should be in the same order as in the mod file) assert _equivalent( - differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2*x*x"]), "-6*x*x+2" + differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x", "a = -2*x*x"]), + "-6*x*x+2", ) assert _equivalent( differentiate2c("a*x + b", "x", {"a", "b"}, ["b = 2*x*x", "a = -2*x"]), "0" @@ -113,7 +114,7 @@ def test_integrate2c(): ("a*x", "x*exp(a*dt)"), ("a*x+b", "(-b + (a*x + b)*exp(a*dt))/a"), ] - for (eq, sol) in test_cases: + for eq, sol in test_cases: assert _equivalent( integrate2c(f"x'={eq}", "dt", var_list, use_pade_approx=False), f"x = {sol}" ) @@ -125,7 +126,7 @@ def test_integrate2c(): ("a*x", "-x*(a*dt+2)/(a*dt-2)"), ("a*x+b", "-(a*dt*x+2*b*dt+2*x)/(a*dt-2)"), ] - for (eq, sol) in pade_test_cases: + for eq, sol in pade_test_cases: assert _equivalent( integrate2c(f"x'={eq}", "dt", var_list, use_pade_approx=True), f"x = {sol}" ) diff --git a/test/nmodl/transpiler/unit/pybind/test_ast.py b/test/nmodl/transpiler/unit/pybind/test_ast.py index b0b353ebe0..e387895b6e 100644 --- a/test/nmodl/transpiler/unit/pybind/test_ast.py +++ b/test/nmodl/transpiler/unit/pybind/test_ast.py @@ -7,36 +7,37 @@ import nmodl.dsl as nmodl import pytest + class TestAst(object): def test_empty_program(self): pnode = ast.Program() - assert str(pnode) == '' + assert str(pnode) == "" def test_ast_construction(self): string = ast.String("tau") name = ast.Name(string) - assert nmodl.to_nmodl(name) == 'tau' + assert nmodl.to_nmodl(name) == "tau" int_macro = nmodl.ast.Integer(1, ast.Name(ast.String("x"))) - assert nmodl.to_nmodl(int_macro) == 'x' + assert nmodl.to_nmodl(int_macro) == "x" statements = [] block = ast.StatementBlock(statements) neuron_block = ast.NeuronBlock(block) - assert nmodl.to_nmodl(neuron_block) == 'NEURON {\n}' + assert nmodl.to_nmodl(neuron_block) == "NEURON {\n}" def test_get_parent(self): x_name = ast.Name(ast.String("x")) int_macro = nmodl.ast.Integer(1, x_name) - assert x_name.parent == int_macro # getting the parent + assert x_name.parent == int_macro # getting the parent def test_set_parent(self): x_name = ast.Name(ast.String("x")) y_name = ast.Name(ast.String("y")) int_macro = nmodl.ast.Integer(1, x_name) - y_name.parent = int_macro # setting the parent + y_name.parent = int_macro # setting the parent int_macro.macro = y_name - assert nmodl.to_nmodl(int_macro) == 'y' + assert nmodl.to_nmodl(int_macro) == "y" def test_ast_node_repr(self): string = ast.String("tau") diff --git a/test/nmodl/transpiler/unit/pybind/test_symtab.py b/test/nmodl/transpiler/unit/pybind/test_symtab.py index f79e444f26..f444854830 100644 --- a/test/nmodl/transpiler/unit/pybind/test_symtab.py +++ b/test/nmodl/transpiler/unit/pybind/test_symtab.py @@ -6,16 +6,20 @@ import io from nmodl.dsl import ast, visitor, symtab + def test_symtab(ch_ast): v = symtab.SymtabVisitor() v.visit_program(ch_ast) s = ch_ast.get_symbol_table() - m = s.lookup('m') + m = s.lookup("m") assert m is not None assert m.get_name() == "m" - assert m.has_all_properties(symtab.NmodlType.state_var | symtab.NmodlType.prime_name) is True + assert ( + m.has_all_properties(symtab.NmodlType.state_var | symtab.NmodlType.prime_name) + is True + ) - mInf = s.lookup('mInf') + mInf = s.lookup("mInf") assert mInf is not None assert mInf.get_name() == "mInf" assert mInf.has_any_property(symtab.NmodlType.range_var) is True diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index 14329ba63b..2ba566666e 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -40,20 +40,29 @@ def test_json_visitor(ch_ast): # test compact json prime_str = nmodl.dsl.to_nmodl(primes[0]) prime_json = nmodl.dsl.to_json(primes[0], True) - assert prime_json == '{"PrimeName":[{"String":[{"name":"m"}]},{"Integer":[{"name":"1"}]}]}' + assert ( + prime_json + == '{"PrimeName":[{"String":[{"name":"m"}]},{"Integer":[{"name":"1"}]}]}' + ) # test json with expanded keys result_json = nmodl.dsl.to_json(primes[0], compact=True, expand=True) - expected_json = ('{"children":[{"children":[{"name":"m"}],' - '"name":"String"},{"children":[{"name":"1"}],' - '"name":"Integer"}],"name":"PrimeName"}') + expected_json = ( + '{"children":[{"children":[{"name":"m"}],' + '"name":"String"},{"children":[{"name":"1"}],' + '"name":"Integer"}],"name":"PrimeName"}' + ) assert result_json == expected_json # test json with nmodl embedded - result_json = nmodl.dsl.to_json(primes[0], compact=True, expand=True, add_nmodl=True) - expected_json = ('{"children":[{"children":[{"name":"m"}],"name":"String","nmodl":"m"},' - '{"children":[{"name":"1"}],"name":"Integer","nmodl":"1"}],' - '"name":"PrimeName","nmodl":"m\'"}') + result_json = nmodl.dsl.to_json( + primes[0], compact=True, expand=True, add_nmodl=True + ) + expected_json = ( + '{"children":[{"children":[{"name":"m"}],"name":"String","nmodl":"m"},' + '{"children":[{"name":"1"}],"name":"Integer","nmodl":"1"}],' + '"name":"PrimeName","nmodl":"m\'"}' + ) assert result_json == expected_json @@ -88,6 +97,7 @@ def test_modify_ast(): RANGE x } """ + class ModifyVisitor(visitor.AstVisitor): def __init__(self, old_name, new_name): visitor.AstVisitor.__init__(self) diff --git a/test/nmodl/transpiler/usecases/cnexp_array/simulate.py b/test/nmodl/transpiler/usecases/cnexp_array/simulate.py index fdcd1ea75f..04f4fa031b 100644 --- a/test/nmodl/transpiler/usecases/cnexp_array/simulate.py +++ b/test/nmodl/transpiler/usecases/cnexp_array/simulate.py @@ -20,7 +20,7 @@ t = np.array(t_hoc.as_numpy()) rate = (0.1 - 1.0) * (0.7 * 0.8 * 0.9) -x_exact = 42.0 * np.exp(rate*t) +x_exact = 42.0 * np.exp(rate * t) rel_err = np.abs(x - x_exact) / x_exact assert np.all(rel_err < 1e-12) diff --git a/test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py b/test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py new file mode 100644 index 0000000000..ca316ce4ae --- /dev/null +++ b/test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py @@ -0,0 +1,24 @@ +import numpy as np +import matplotlib.pyplot as plt + + +t_end = 4.0 + +n = 10000 +x_approx = np.empty(n) +x_approx[0] = 0.1 + +x_exact = np.empty(n) +x_exact[0] = 0.1 + +dt = t_end / n +t = np.linspace(0.0, t_end, n) + +for i in range(1, n): + x_approx[i] = 1.4142135623730951 * np.sqrt(dt + 0.5 * x_approx[i - 1] ** 2.0) + x_exact[i] = x_exact[i - 1] + dt * 1 / x_exact[i - 1] + +plt.plot(t, x_approx) +plt.plot(t, x_exact) + +plt.show() diff --git a/test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py b/test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py new file mode 100644 index 0000000000..3245670ca9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py @@ -0,0 +1,27 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("leonhard") +s.nseg = nseg + +x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +x = np.array(x_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +x0 = 42.0 +x_exact = 42.0 * np.exp(-t) +rel_err = np.abs(x - x_exact) / x_exact + +assert np.all(rel_err < 1e-12) +print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/comments/simulate.py b/test/nmodl/transpiler/usecases/comments/simulate.py new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/test/nmodl/transpiler/usecases/comments/simulate.py @@ -0,0 +1 @@ + diff --git a/test/nmodl/transpiler/usecases/func_proc/simulate.py b/test/nmodl/transpiler/usecases/func_proc/simulate.py index ca1ffc73b2..69162fef4c 100644 --- a/test/nmodl/transpiler/usecases/func_proc/simulate.py +++ b/test/nmodl/transpiler/usecases/func_proc/simulate.py @@ -7,7 +7,7 @@ s.insert("test_func_proc") coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] -values = [ 0.1 + k for k in range(nseg)] +values = [0.1 + k for k in range(nseg)] for x in coords: s(x).test_func_proc.set_x_42() diff --git a/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py b/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py index 1aabec09d7..d7fc2bd052 100644 --- a/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py +++ b/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py @@ -20,7 +20,7 @@ t = np.array(t_hoc.as_numpy()) x_exact = 2.0 * np.ones_like(t) -x_exact[0] = 42; +x_exact[0] = 42 abs_err = np.abs(x - x_exact) assert np.all(abs_err < 1e-12), f"{abs_err=}" diff --git a/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py b/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py index ffa4048322..6a192ef71e 100644 --- a/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py +++ b/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py @@ -22,7 +22,7 @@ erev = 1.5 rate = 0.005 / 1e-3 v0 = -65.0 -v_exact = erev + (v0 - erev)*np.exp(-rate*t) +v_exact = erev + (v0 - erev) * np.exp(-rate * t) rel_err = np.abs(v - v_exact) / np.max(np.abs(v_exact)) assert np.all(rel_err < 1e-1), f"rel_err = {rel_err}" diff --git a/test/nmodl/transpiler/usecases/parameter/simulate.py b/test/nmodl/transpiler/usecases/parameter/simulate.py index 7fe9ad6f1f..9dadf17f2e 100644 --- a/test/nmodl/transpiler/usecases/parameter/simulate.py +++ b/test/nmodl/transpiler/usecases/parameter/simulate.py @@ -4,9 +4,8 @@ test_parameter_pp = h.test_parameter(s(0.5)) -assert test_parameter_pp.x == 42. +assert test_parameter_pp.x == 42.0 test_parameter_pp.x = 42.1 assert test_parameter_pp.x == 42.1 - From 2fc51fb313cf4925872d7eb0fde057c3a9b6e758 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 11 Mar 2024 13:02:55 +0100 Subject: [PATCH 611/871] Implement access to neuron global variables. (BlueBrain/nmodl#1192) What's meant is things like `celsius`. NMODL Repo SHA: BlueBrain/nmodl@c8818c8fb13cc32b92ded4477a00e9a3b636a00f --- .../codegen/codegen_neuron_cpp_visitor.cpp | 19 ++++++++++++++++--- .../codegen/codegen_neuron_cpp_visitor.hpp | 8 ++++++++ .../usecases/parameter/neuron_variables.mod | 16 ++++++++++++++++ .../transpiler/usecases/parameter/simulate.py | 16 ++++++++++++++-- test/nmodl/transpiler/usecases/references | 2 +- 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/parameter/neuron_variables.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 64ba56dbae..829afff315 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -596,7 +596,7 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, if (iter != info.neuron_global_variables.end()) { std::string ret; if (use_instance) { - ret = "*(inst->"; + ret = "*(inst."; } ret.append(varname); if (use_instance) { @@ -1050,6 +1050,12 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->pop_block(); } +void CodegenNeuronCppVisitor::print_neuron_global_variable_declarations() { + for (auto const& [var, type]: info.neuron_global_variables) { + auto const name = var->get_name(); + printer->fmt_line("extern {} {};", type, name); + } +} void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { auto const value_initialize = print_initializers ? "{}" : ""; @@ -1063,8 +1069,7 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini printer->fmt_line("{}* {}{};", type, name, - print_initializers ? fmt::format("{{&coreneuron::{}}}", name) - : std::string{}); + print_initializers ? fmt::format("{{&::{}}}", name) : std::string{}); } for (auto& var: codegen_float_variables) { const auto& name = var->get_name(); @@ -1101,6 +1106,13 @@ void CodegenNeuronCppVisitor::print_make_instance() const { std::vector make_instance_args; + + for (auto const& [var, type]: info.neuron_global_variables) { + auto const name = var->get_name(); + make_instance_args.push_back(fmt::format("&::{}", name)); + } + + const auto codegen_float_variables_size = codegen_float_variables.size(); for (int i = 0; i < codegen_float_variables_size; ++i) { const auto& float_var = codegen_float_variables[i]; @@ -1711,6 +1723,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_backend_info(); print_headers_include(); print_macro_definitions(); + print_neuron_global_variable_declarations(); print_namespace_begin(); print_nmodl_constants(); print_prcellstate_macros(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index b680431a7b..5783456bc4 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -562,6 +562,14 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_macro_definitions(); + /** + * Print extern declarations for neuron global variables. + * + * Examples include `celsius`. + */ + void print_neuron_global_variable_declarations(); + + /** * Print NEURON global variable macros * diff --git a/test/nmodl/transpiler/usecases/parameter/neuron_variables.mod b/test/nmodl/transpiler/usecases/parameter/neuron_variables.mod new file mode 100644 index 0000000000..d2188cb6d5 --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/neuron_variables.mod @@ -0,0 +1,16 @@ +NEURON { + SUFFIX NeuronVariables + RANGE range_celsius +} + +PARAMETER { + celsius = 123.456 +} + +ASSIGNED { + range_celsius +} + +BREAKPOINT { + range_celsius = celsius +} diff --git a/test/nmodl/transpiler/usecases/parameter/simulate.py b/test/nmodl/transpiler/usecases/parameter/simulate.py index 9dadf17f2e..5777b55368 100644 --- a/test/nmodl/transpiler/usecases/parameter/simulate.py +++ b/test/nmodl/transpiler/usecases/parameter/simulate.py @@ -1,4 +1,7 @@ -from neuron import h +import numpy as np + +from neuron import h, gui +from neuron.units import ms s = h.Section() @@ -7,5 +10,14 @@ assert test_parameter_pp.x == 42.0 test_parameter_pp.x = 42.1 - assert test_parameter_pp.x == 42.1 + +s.insert("NeuronVariables") +celsius_hoc = h.Vector().record(s(0.5)._ref_range_celsius_NeuronVariables) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +celsius = np.array(celsius_hoc.as_numpy()) +assert celsius[-1] == 6.3, f"{celsius[-1]=}" diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 9893f6e7dd..e9e4ca99f2 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 9893f6e7dd0466ba339281c1b412809e50136f63 +Subproject commit e9e4ca99f2e16874a6c5b36ccaa5029fca43c949 From e354d3ec89bb597213e34cea31243a015bcfeb6a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 12 Mar 2024 16:41:02 +0100 Subject: [PATCH 612/871] Fix indexed COMPARTMENT block. (BlueBrain/nmodl#1209) When encountering an indexed `COMPARTMENT` block: COMPARTMENT i, volume_expr { species } All state variables are searched if they match they match the pattern `"{species}[%d]"`. For each matching state variables, we obtain the value of the array index and substitute the name of the array index with its value in `volume_expr`. This is then stored in the array of compartment factors. Check that the resulting derivatives are divided by the volume of the compartment. NMODL Repo SHA: BlueBrain/nmodl@219a3edcbf0bd25257657b965785eeba0728c309 --- src/nmodl/visitors/kinetic_block_visitor.cpp | 74 +++++++++++++++---- src/nmodl/visitors/kinetic_block_visitor.hpp | 4 + .../transpiler/unit/visitor/kinetic_block.cpp | 20 +++++ 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index a8d78dc4c4..af208a1336 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -8,11 +8,14 @@ #include "kinetic_block_visitor.hpp" #include "ast/all.hpp" +#include "index_remover.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" #include "visitor_utils.hpp" +#include + namespace nmodl { namespace visitor { @@ -141,24 +144,69 @@ void KineticBlockVisitor::visit_conserve(ast::Conserve& node) { logger->debug("KineticBlockVisitor :: --> {}", to_nmodl(node)); } +void KineticBlockVisitor::set_compartment_factor(int var_index, const std::string& factor) { + if (compartment_factors[var_index] != "") { + throw std::runtime_error("Setting compartment volume twice."); + } + + compartment_factors[var_index] = factor; + logger->debug("KineticBlockVisitor :: COMPARTMENT factor {} for state var {} (index {})", + factor, + state_var[var_index], + var_index); +} + +void KineticBlockVisitor::compute_compartment_factor(ast::Compartment& node, + const ast::Name& name) { + const auto& var_name = name.get_node_name(); + const auto it = state_var_index.find(var_name); + if (it != state_var_index.cend()) { + int var_index = it->second; + auto expr = node.get_expression(); + std::string expression = to_nmodl(expr); + + set_compartment_factor(var_index, expression); + } else { + logger->debug( + "KineticBlockVisitor :: COMPARTMENT specified volume for non-state variable {}", + var_name); + } +} + +void KineticBlockVisitor::compute_indexed_compartment_factor(ast::Compartment& node, + const ast::Name& name) { + auto array_var_name = name.get_node_name(); + auto index_name = node.get_name()->get_node_name(); + + auto pattern = fmt::format("^{}\\[([0-9]*)\\]$", array_var_name); + std::regex re(pattern); + std::smatch m; + + for (size_t var_index = 0; var_index < state_var.size(); ++var_index) { + auto matches = std::regex_match(state_var[var_index], m, re); + + if (matches) { + int index_value = std::stoi(m[1]); + auto volume_expr = node.get_expression(); + auto expr = std::shared_ptr(node.get_expression()->clone()); + IndexRemover(index_name, index_value).visit_expression(*expr); + + std::string expression = to_nmodl(*expr); + set_compartment_factor(var_index, expression); + } + } +} + void KineticBlockVisitor::visit_compartment(ast::Compartment& node) { // COMPARTMENT block has an expression, and a list of state vars it applies to. // For each state var, the rhs of the differential eq should be divided by the expression. // Here we store the expressions in the compartment_factors vector - auto expr = node.get_expression(); - std::string expression = to_nmodl(expr); - logger->debug("KineticBlockVisitor :: COMPARTMENT expr: {}", expression); + logger->debug("KineticBlockVisitor :: COMPARTMENT expr: {}", to_nmodl(node.get_expression())); for (const auto& name_ptr: node.get_names()) { - const auto& var_name = name_ptr->get_node_name(); - const auto it = state_var_index.find(var_name); - if (it != state_var_index.cend()) { - int var_index = it->second; - compartment_factors[var_index] = expression; - logger->debug( - "KineticBlockVisitor :: COMPARTMENT factor {} for state var {} (index {})", - expression, - var_name, - var_index); + if (node.get_name() == nullptr) { + compute_compartment_factor(node, *name_ptr); + } else { + compute_indexed_compartment_factor(node, *name_ptr); } } // add COMPARTMENT state to list of statements to remove diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 0f67260594..7c2fae5c4d 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -52,6 +52,10 @@ class KineticBlockVisitor: public AstVisitor { /// update CONSERVE statement with reaction var term void process_conserve_reac_var(const std::string& varname, int count = 1); + void set_compartment_factor(int var_index, const std::string& factor); + void compute_compartment_factor(ast::Compartment& node, const ast::Name& name); + void compute_indexed_compartment_factor(ast::Compartment& node, const ast::Name& name); + /// stochiometric matrices nu_L, nu_R /// forwards/backwards fluxes k_f, k_b /// (see kinetic_schemes.ipynb notebook for details) diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 7d52ae86bb..abcf1f89aa 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -239,6 +239,26 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with -> reaction statement, indexed COMPARTMENT") { + std::string input_nmodl_text = R"( + STATE { + x[2] + } + KINETIC states { + COMPARTMENT i, vol[i] { x } + ~ x[0] + x[1] -> (f(v)) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + x'[0] = ((-1*(f(v)*x[0]*x[1])))/(vol[0]) + x'[1] = ((-1*(f(v)*x[0]*x[1])))/(vol[1]) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with one reaction statement, 1 state var, 1 non-state var, flux vars") { // Here c is NOT a state variable // see 9.9.2.1 of NEURON book From 2b3cb12f0fd74050dc6a7748fee2448208b685d6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 14 Mar 2024 08:44:23 +0100 Subject: [PATCH 613/871] Cosmetic change. (BlueBrain/nmodl#1215) NMODL Repo SHA: BlueBrain/nmodl@9873e06f98067e00e21550c87910aa2931b560ce --- src/nmodl/visitors/kinetic_block_visitor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index af208a1336..88d53023c6 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -274,7 +274,6 @@ void KineticBlockVisitor::visit_reaction_statement(ast::ReactionStatement& node) "KineticBlockVisitor :: LHS of \"<<\" reaction statement must be a single state " "var, but instead found {}: ignoring this statement", to_nmodl(lhs))); - return; } const auto& rhs = node.get_expression1(); std::string varname = to_nmodl(lhs); From 514113da180d1bd8d0dc54554709f31bb3268fc7 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 18 Mar 2024 13:33:15 +0100 Subject: [PATCH 614/871] Update docstring of `SemanticAnalysisVisitor` (BlueBrain/nmodl#1220) NMODL Repo SHA: BlueBrain/nmodl@2c7c3a9a390f543c7ea8bd5170504812648231c8 --- src/nmodl/visitors/semantic_analysis_visitor.hpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 93c0958cef..bcf69f204c 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -12,6 +12,12 @@ * \brief \copybrief nmodl::visitor::SemanticAnalysisVisitor */ +#include "ast/ast.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + /** * \addtogroup visitor_classes * \{ @@ -19,7 +25,7 @@ /** * \class SemanticAnalysisVisitor - * \brief %Visitor to check some semantic rules on the ast + * \brief %Visitor to check some semantic rules on the AST * * Current checks: * @@ -34,11 +40,6 @@ * 8. Check that at most one derivative block is present. * 9. Check that RANDOM variable is mentioned only as first arg in random function. */ -#include "ast/ast.hpp" -#include "visitors/ast_visitor.hpp" - -namespace nmodl { -namespace visitor { class SemanticAnalysisVisitor: public ConstAstVisitor { private: From c8d48a49d4940ede7c63cb6674b132f3a19249d0 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 18 Mar 2024 16:01:08 +0100 Subject: [PATCH 615/871] Make default log level "warning" (BlueBrain/nmodl#1222) NMODL Repo SHA: BlueBrain/nmodl@dd1c7515b88f2589d016a7a4a0cdd3b113de81e0 --- src/nmodl/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 70a26a66ae..0a678789d2 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -69,7 +69,7 @@ int main(int argc, const char* argv[]) { std::vector mod_files; /// true if debug logger statements should be shown - std::string verbose("info"); + std::string verbose("warning"); /// true if code is to be generated for NEURON bool neuron_code(false); From ad66ca4c3e4c02af46c80e1f6cc26ecf5d1d52c2 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 25 Mar 2024 09:11:20 +0100 Subject: [PATCH 616/871] Add minor fixes to generate_references on MacOS (BlueBrain/nmodl#1228) NMODL Repo SHA: BlueBrain/nmodl@4a15447dcf3db3a202803b65ffda47eac0a527d6 --- .../transpiler/usecases/generate_references.sh | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/test/nmodl/transpiler/usecases/generate_references.sh b/test/nmodl/transpiler/usecases/generate_references.sh index ef21a838ea..5a41ef9661 100755 --- a/test/nmodl/transpiler/usecases/generate_references.sh +++ b/test/nmodl/transpiler/usecases/generate_references.sh @@ -8,21 +8,20 @@ if [[ $# -eq 3 ]] then output_dir="$3" else - script_dir="$(dirname "$(realpath "$0")")" + script_dir="$(cd "$(dirname "$0")"; pwd -P)" output_dir="${script_dir}/references/$(basename "$2")" fi function sanitize() { for f in "${1}"/*.cpp do - if [[ "$(uname)" == 'Darwin' ]] - then - sed_cmd="sed -i''" + if [[ "$(uname)" == 'Darwin' ]]; then + sed_cmd=("sed" "-i" "") else - sed_cmd="sed -i" + sed_cmd=("sed" "-i") fi - ${sed_cmd} "s/Created : .*$/Created : DATE/" "$f" - ${sed_cmd} "s/NMODL Compiler : .*$/NMODL Compiler : VERSION/" "$f" + "${sed_cmd[@]}" "s/Created : .*$/Created : DATE/" "$f" + "${sed_cmd[@]}" "s/NMODL Compiler : .*$/NMODL Compiler : VERSION/" "$f" done } From b15b611b13042bf6d3dc29f3166861af3b810423 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Tue, 26 Mar 2024 15:05:29 +0700 Subject: [PATCH 617/871] Fix current warnings (BlueBrain/nmodl#1230) Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@c818f16bf2543705536b7e93e1da48afda39c4b8 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 7 +++---- src/nmodl/visitors/semantic_analysis_visitor.cpp | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 829afff315..17380af463 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -507,7 +507,7 @@ std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbo std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& symbol, const std::string& name, bool use_instance) const { - auto position = position_of_int_var(name); + // auto position = position_of_int_var(name); if (symbol.is_index) { if (use_instance) { throw std::runtime_error("Not implemented. [wiejo]"); @@ -785,7 +785,8 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } - for (const auto& f: info.function_tables) { + // for (const auto& f: info.function_tables) { + if (!info.function_tables.empty()) { throw std::runtime_error("Not implemented, global function tables."); } @@ -1033,7 +1034,6 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { for (int i = 0; i < codegen_int_variables_size; ++i) { const auto& int_var = codegen_int_variables[i]; - const auto& name = int_var.symbol->get_name(); if (i != info.semantics[i].index) { throw std::runtime_error("Broken logic."); } @@ -1149,7 +1149,6 @@ void CodegenNeuronCppVisitor::print_make_instance() const { } void CodegenNeuronCppVisitor::print_node_data_structure(bool print_initializers) { - auto const value_initialize = print_initializers ? "{}" : ""; printer->add_newline(2); printer->fmt_push_block("struct {} ", node_data_struct()); diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index ff83a36ead..ef46605945 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -97,7 +97,6 @@ void SemanticAnalysisVisitor::visit_name(const ast::Name& node) { // There are only two contexts where a random_var is allowed. As the first arg of a random // function or as an item in the RANDOM declaration. // Only the former needs checking. - bool ok = true; auto name = node.get_node_name(); // only check for variables exist in the symbol table (e.g. SUFFIX has type Name but it's not From 0ec7f33b1124079801436cd64f53bb5e7e6da709 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 5 Apr 2024 10:14:46 +0200 Subject: [PATCH 618/871] Unroll kinetic blocks (BlueBrain/nmodl#1214) NMODL Repo SHA: BlueBrain/nmodl@98e6df2b67baf1a59a255239ac16df016451b31d --- src/nmodl/visitors/kinetic_block_visitor.cpp | 36 +++++++++++++++++++ src/nmodl/visitors/kinetic_block_visitor.hpp | 7 ++++ .../transpiler/unit/visitor/kinetic_block.cpp | 10 +++--- 3 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 88d53023c6..981aa84cd9 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -8,7 +8,9 @@ #include "kinetic_block_visitor.hpp" #include "ast/all.hpp" +#include "constant_folder_visitor.hpp" #include "index_remover.hpp" +#include "loop_unroll_visitor.hpp" #include "symtab/symbol.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" @@ -476,7 +478,41 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { kinetic_blocks.push_back(&node); } +void KineticBlockVisitor::unroll_kinetic_blocks(ast::Program& node) { + const auto& kineticBlockNodes = collect_nodes(node, {ast::AstNodeType::KINETIC_BLOCK}); + + // Before: FROM i = 0 TO N-1 + // After: FROM i = 0 TO 2 + visitor::ConstantFolderVisitor const_folder; + for (const auto& ii: kineticBlockNodes) { + ii->accept(const_folder); + } + + // Before: + // FROM i = 0 TO 2 { + // ~ ca[i] <-> ca[i+1] (a, b) + // } + // + // After: + // ~ ca[0] <-> ca[0+1] (a, b) + // ~ ca[1] <-> ca[1+1] (a, b) + // ~ ca[2] <-> ca[2+1] (a, b) + // + visitor::LoopUnrollVisitor unroller; + for (const auto& ii: kineticBlockNodes) { + ii->accept(unroller); + } + + // Before: ca[0+1] + // After: ca[1] + for (const auto& ii: kineticBlockNodes) { + ii->accept(const_folder); + } +} + void KineticBlockVisitor::visit_program(ast::Program& node) { + unroll_kinetic_blocks(node); + conserve_statement_count = 0; statements_to_remove.clear(); current_statement_block = nullptr; diff --git a/src/nmodl/visitors/kinetic_block_visitor.hpp b/src/nmodl/visitors/kinetic_block_visitor.hpp index 7c2fae5c4d..8fade4fb65 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.hpp +++ b/src/nmodl/visitors/kinetic_block_visitor.hpp @@ -56,6 +56,13 @@ class KineticBlockVisitor: public AstVisitor { void compute_compartment_factor(ast::Compartment& node, const ast::Name& name); void compute_indexed_compartment_factor(ast::Compartment& node, const ast::Name& name); + /// Unroll loops in KINETIC blocks. + /// + /// The subsequent processing in KineticBlockVisitor fails if there's any + /// reaction equation inside a `FROM .. TO ..` block. The solution is to + /// unroll any loops first to satisfy the assumption. + void unroll_kinetic_blocks(ast::Program& node); + /// stochiometric matrices nu_L, nu_R /// forwards/backwards fluxes k_f, k_b /// (see kinetic_schemes.ipynb notebook for details) diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index abcf1f89aa..55ab32094c 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -39,15 +39,13 @@ std::vector run_kinetic_block_visitor(const std::string& text) { // construct symbol table from AST SymtabVisitor().visit_program(*ast); - // unroll loops and fold constants - ConstantFolderVisitor().visit_program(*ast); - LoopUnrollVisitor().visit_program(*ast); - ConstantFolderVisitor().visit_program(*ast); - SymtabVisitor().visit_program(*ast); - // run KineticBlock visitor on AST KineticBlockVisitor().visit_program(*ast); + // Since KineticBlockVisitor internally performs const-folding and + // loop unrolling, we run CheckParentVisitor. + CheckParentVisitor().check_ast(*ast); + // run lookup visitor to extract DERIVATIVE block(s) from AST const auto& blocks = collect_nodes(*ast, {AstNodeType::DERIVATIVE_BLOCK}); results.reserve(blocks.size()); From e291895d7b4af950770013b5dca81889240b57bb Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 5 Apr 2024 14:46:35 +0200 Subject: [PATCH 619/871] Add dependency of `generate_references` on `nmodl`. (BlueBrain/nmodl#1234) This can be reproduced by: cmake -B build && cmake --build build --target=generate_references i.e. call `--target=generate_references` without building it first. It would complain about missing `bin/nmodl`. NMODL Repo SHA: BlueBrain/nmodl@735b11dba9a494a5139be2ad5b82ca15e12859db --- test/nmodl/transpiler/usecases/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index cd204c1153..6f37cf7d88 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -15,6 +15,7 @@ endif() unset(NMODL_GOLDEN_REFERNCES) add_custom_target(generate_references) + foreach(usecase ${NMODL_USECASE_DIRS}) # Non-existant dependencies are a way of unconditionally running commands in CMake. if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge) @@ -34,6 +35,7 @@ foreach(usecase ${NMODL_USECASE_DIRS}) add_custom_command( OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge + DEPENDS nmodl COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_references.sh ${CMAKE_BINARY_DIR}/bin/nmodl ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) From cb5ad5d50339d986f2ca69fe4860dc9d25630dd7 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 8 Apr 2024 11:52:50 +0200 Subject: [PATCH 620/871] Set conductances when using NONSPECIFIC_CURRENT (BlueBrain/nmodl#1218) NMODL Repo SHA: BlueBrain/nmodl@593f38e5a6b62abbe2e47f7af84dddf51f17895b --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 1 + .../codegen/codegen_neuron_cpp_visitor.cpp | 34 ++++++++++++++++--- test/nmodl/transpiler/usecases/references | 2 +- 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index e22abc221c..994b2e4165 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -977,6 +977,7 @@ std::vector CodegenCppVisitor::get_float_variable if (net_receive_exist()) { variables.push_back(make_symbol(naming::T_SAVE_VARIABLE)); } + return variables; } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 17380af463..947588c778 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1155,6 +1155,7 @@ void CodegenNeuronCppVisitor::print_node_data_structure(bool print_initializers) // Pointers to node variables printer->add_line("int const * nodeindices;"); printer->add_line("double const * node_voltages;"); + printer->add_line("double * node_diagonal;"); printer->add_line("double * node_rhs;"); printer->add_line("int nodecount;"); @@ -1170,6 +1171,7 @@ void CodegenNeuronCppVisitor::print_make_node_data() const { std::vector make_node_data_args; make_node_data_args.push_back("_ml_arg.nodeindices"); make_node_data_args.push_back("_nt.node_voltage_storage()"); + make_node_data_args.push_back("_nt.node_d_storage()"); make_node_data_args.push_back("_nt.node_rhs_storage()"); make_node_data_args.push_back("_ml_arg.nodecount"); @@ -1235,10 +1237,29 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_newline(2); printer->add_line("/** nrn_jacob function */"); - printer->fmt_line( + printer->fmt_push_block( "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* " - "_nt, Memb_list* _ml_arg, int _type) {{}}", - method_name(naming::NRN_JACOB_METHOD)); + "_nt, Memb_list* _ml_arg, int _type)", + method_name(naming::NRN_JACOB_METHOD)); // begin function + + printer->add_multi_line( + "_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); + + printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); + printer->fmt_line("auto nodecount = _ml_arg->nodecount;"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); // begin for + + if (breakpoint_exist()) { + printer->add_line("// set conductances properly"); + printer->add_line("int node_id = node_data.nodeindices[id];"); + printer->fmt_line("node_data.node_diagonal[node_id] += inst.{}[id];", + info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE + : naming::CONDUCTANCE_VARIABLE); + } + + printer->pop_block(); // end for + printer->pop_block(); // end function } @@ -1578,7 +1599,12 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { printer->add_line("node_data.node_rhs[node_id] -= rhs;"); - + if (breakpoint_exist()) { + printer->add_line("// remember the conductances so we can set them later"); + printer->fmt_line("inst.{}[id] = g;", + info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE + : naming::CONDUCTANCE_VARIABLE); + } printer->pop_block(); // if (nrn_cur_reduction_loop_required()) { diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index e9e4ca99f2..61b4aec4d5 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit e9e4ca99f2e16874a6c5b36ccaa5029fca43c949 +Subproject commit 61b4aec4d569c0c3dfa4906d1a32304e22b1c8db From da6f3fba1a534e2a014171f7e0f847de61734895 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 9 Apr 2024 17:10:11 +0200 Subject: [PATCH 621/871] Localize kinetic reaction rates. (BlueBrain/nmodl#1232) Store the forward and reverse rates in local variables, e.g.: ``` rate = 42.0 ~ a <-> b (1.0*rate, 2.0*rate) ``` is converted to: ``` LOCAL kf0_, kb0_ rate = 42.0 kf0_ = 1.0*rate kb0_ = 2.0*rate ~ a <-> b (kf0_, kb0_) ``` This solves a bug that assigning to `rate` between two reaction equation statements would mean the first line sees the value meant for the second line. NMODL Repo SHA: BlueBrain/nmodl@868ce133ffc241f19acfbe9bb4c9cd1cced399fe --- src/nmodl/visitors/kinetic_block_visitor.cpp | 169 ++++++++++++ .../transpiler/unit/visitor/kinetic_block.cpp | 247 +++++++++++++----- .../transpiler/unit/visitor/sympy_solver.cpp | 32 ++- 3 files changed, 369 insertions(+), 79 deletions(-) diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 981aa84cd9..0ec8b55f72 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -478,6 +478,172 @@ void KineticBlockVisitor::visit_kinetic_block(ast::KineticBlock& node) { kinetic_blocks.push_back(&node); } +class LocalRateNames { + static constexpr auto kf_stem = "kf"; + static constexpr auto kb_stem = "kb"; + static constexpr auto source_stem = "source"; + + std::shared_ptr generate_local_name(const std::string& stem) { + std::string unmangled_name = fmt::format("{}{}_", stem, n_equations); + std::string mangled_name = unmangled_name; + + size_t mangle_attempt = 0; + while (symtab->lookup_in_scope(mangled_name)) { + mangled_name = fmt::format("{}{:04d}", unmangled_name, mangle_attempt++); + + if (mangle_attempt >= 10000) { + throw std::runtime_error("Failed to find unique local name."); + } + } + + auto name = std::make_shared(std::make_shared(mangled_name)); + local_names.push_back(mangled_name); + + return name; + } + + public: + LocalRateNames() = default; + LocalRateNames(const LocalRateNames&) = default; + LocalRateNames(LocalRateNames&&) = default; + + LocalRateNames(symtab::SymbolTable const* symtab) + : symtab(symtab) {} + + LocalRateNames& operator=(const LocalRateNames&) = default; + LocalRateNames& operator=(LocalRateNames&&) = default; + + std::shared_ptr generate_forward_rate_name() { + auto kf = generate_local_name(kf_stem); + ++n_equations; + + return kf; + } + + std::pair, std::shared_ptr> generate_rate_names() { + auto kf = generate_local_name(kf_stem); + auto kb = generate_local_name(kb_stem); + ++n_equations; + + return {kf, kb}; + } + + std::shared_ptr generate_source_name() { + auto source = generate_local_name(source_stem); + ++n_equations; + + return source; + } + + std::vector get_local_variable_names() { + return local_names; + } + + private: + size_t n_equations = 0; + std::vector local_names; + symtab::SymbolTable const* symtab = nullptr; +}; + +class LocalizeKineticRatesVisitor: public AstVisitor { + public: + LocalRateNames local_names; + + private: + std::shared_ptr localize_expression( + const std::shared_ptr& name, + const std::shared_ptr& expression) { + auto local = std::make_shared(name, + ast::BinaryOperator(ast::BOP_ASSIGN), + expression); + return std::make_shared(local); + } + + template + std::shared_ptr clone(const Node& node) { + return std::shared_ptr(node.clone()); + } + + public: + void visit_kinetic_block(ast::KineticBlock& node) { + auto stmt_block = node.get_statement_block(); + local_names = LocalRateNames(stmt_block->get_symbol_table()); + + // process the statement block first. + node.visit_children(*this); + + // We now know the names of the created LOCAL variables. If a LOCAL + // block exists, append to that, otherwise create a new one. + auto locals = local_names.get_local_variable_names(); + + for (const auto& local: locals) { + add_local_variable(*stmt_block, local); + } + } + + + void visit_statement_block(ast::StatementBlock& node) { + // process any nested statement blocks first: + node.visit_children(*this); + + // process all reaction equations: + const auto& statements = node.get_statements(); + for (auto iter = statements.begin(); iter != statements.end(); ++iter) { + if ((*iter)->is_reaction_statement()) { + auto reaction_equation = std::dynamic_pointer_cast(*iter); + auto op = reaction_equation->get_op(); + + ast::StatementVector localized_statements; + + auto local_expr1 = reaction_equation->get_expression1(); + auto local_expr2 = reaction_equation->get_expression2(); + + std::shared_ptr expr1_name = nullptr; + std::shared_ptr expr2_name = nullptr; + + if (op.get_value() == ast::LTLT) { + expr1_name = local_names.generate_source_name(); + } else if (op.get_value() == ast::LTMINUSGT) { + std::tie(expr1_name, expr2_name) = local_names.generate_rate_names(); + } else if (op.get_value() == ast::MINUSGT) { + expr1_name = local_names.generate_forward_rate_name(); + } + + if (local_expr1) { + auto assignment = localize_expression(clone(*expr1_name), + reaction_equation->get_expression1()); + localized_statements.push_back(assignment); + local_expr1 = clone(*expr1_name); + } + + if (local_expr2) { + auto assignment = localize_expression(clone(*expr2_name), + reaction_equation->get_expression2()); + localized_statements.push_back(assignment); + local_expr2 = clone(*expr2_name); + } + + auto local_reaction = + std::make_shared(reaction_equation->get_reaction1(), + reaction_equation->get_op(), + reaction_equation->get_reaction2(), + local_expr1, + local_expr2); + localized_statements.push_back(local_reaction); + + iter = node.erase_statement(iter); + for (const auto& stmt: localized_statements) { + // `iter` points to the element after the one + // we've inserted. + iter = ++node.insert_statement(iter, stmt); + } + --iter; + } + } + } +}; + + void KineticBlockVisitor::unroll_kinetic_blocks(ast::Program& node) { const auto& kineticBlockNodes = collect_nodes(node, {ast::AstNodeType::KINETIC_BLOCK}); @@ -508,6 +674,9 @@ void KineticBlockVisitor::unroll_kinetic_blocks(ast::Program& node) { for (const auto& ii: kineticBlockNodes) { ii->accept(const_folder); } + + auto visitor = LocalizeKineticRatesVisitor{}; + node.accept(visitor); } void KineticBlockVisitor::visit_program(ast::Program& node) { diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index 55ab32094c..c6920705e3 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -71,7 +71,9 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; static const std::string output_nmodl_text = R"( DERIVATIVE states { - x' = (a*c/3.2) + LOCAL source0_ + source0_ = a*c/3.2 + x' = (source0_) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -89,7 +91,9 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x'[0] = (a*c/3.2) + LOCAL source0_ + source0_ = a*c/3.2 + x'[0] = (source0_) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -109,9 +113,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL source0_ + source0_ = a*c/3.2 f0 = 0*2 f1 = 0+0 - x'[0] = (a*c/3.2) + x'[0] = (source0_) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -143,9 +149,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - zf = a*x + LOCAL kf0_ + kf0_ = a + zf = kf0_*x zb = 0 - x' = (-1*(a*x)) + x' = (-1*(kf0_*x)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -163,8 +171,10 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = (-1*(f(v)*x*y)) - y' = (-1*(f(v)*x*y)) + LOCAL kf0_ + kf0_ = f(v) + x' = (-1*(kf0_*x*y)) + y' = (-1*(kf0_*x*y)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -183,9 +193,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL kf0_ + kf0_ = f(v) CONSERVE y = 1-x - x' = (-1*(f(v)*x*y)) - y' = (-1*(f(v)*x*y)) + x' = (-1*(kf0_*x*y)) + y' = (-1*(kf0_*x*y)) })"; THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -206,9 +218,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL kf0_ + kf0_ = f(v) CONSERVE y = (1-(a*1*x))/(b*1) - x' = ((-1*(f(v)*x*y)))/(a) - y' = ((-1*(f(v)*x*y)))/(b) + x' = ((-1*(kf0_*x*y)))/(a) + y' = ((-1*(kf0_*x*y)))/(b) })"; THEN( "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement inc COMPARTMENT " @@ -228,8 +242,10 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x'[0] = (-1*(f(v)*x[0]*x[1])) - x'[1] = (-1*(f(v)*x[0]*x[1])) + LOCAL kf0_ + kf0_ = f(v) + x'[0] = (-1*(kf0_*x[0]*x[1])) + x'[1] = (-1*(kf0_*x[0]*x[1])) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -248,8 +264,10 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x'[0] = ((-1*(f(v)*x[0]*x[1])))/(vol[0]) - x'[1] = ((-1*(f(v)*x[0]*x[1])))/(vol[1]) + LOCAL kf0_ + kf0_ = f(v) + x'[0] = ((-1*(kf0_*x[0]*x[1])))/(vol[0]) + x'[1] = ((-1*(kf0_*x[0]*x[1])))/(vol[1]) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -273,8 +291,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - c1 = r*x-r*c - x' = (-1*(r*x-r*c)) + LOCAL kf0_, kb0_ + kf0_ = r + kb0_ = r + c1 = kf0_*x-kb0_*c + x' = (-1*(kf0_*x-kb0_*c)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -292,8 +313,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y)) + LOCAL kf0_, kb0_ + kf0_ = a + kb0_ = b + x' = (-1*(kf0_*x-kb0_*y)) + y' = (1*(kf0_*x-kb0_*y)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -312,9 +336,12 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL kf0_, kb0_ + kf0_ = a + kb0_ = b CONSERVE y = 0-x - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y)) + x' = (-1*(kf0_*x-kb0_*y)) + y' = (1*(kf0_*x-kb0_*y)) })"; THEN("Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -336,11 +363,16 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL kf0_, kb0_, kf1_, kb1_ + kf0_ = a + kb0_ = b + kf1_ = c + kb1_ = d CONSERVE x[2] = 1-y-x[0]-x[1] - x'[0] = (-1*(a*x[0]-b*x[1])) - x'[1] = (1*(a*x[0]-b*x[1])) - x'[2] = (-1*(c*x[2]-d*y)) - y' = (1*(c*x[2]-d*y)) + x'[0] = (-1*(kf0_*x[0]-kb0_*x[1])) + x'[1] = (1*(kf0_*x[0]-kb0_*x[1])) + x'[2] = (-1*(kf1_*x[2]-kb1_*y)) + y' = (1*(kf1_*x[2]-kb1_*y)) })"; THEN( "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " @@ -364,11 +396,16 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL kf0_, kb0_, kf1_, kb1_ + kf0_ = a + kb0_ = b + kf1_ = c + kb1_ = d CONSERVE y = 1-x[0]-x[1]-x[2] - x'[0] = (-1*(a*x[0]-b*x[1])) - x'[1] = (1*(a*x[0]-b*x[1])) - x'[2] = (-1*(c*x[2]-d*y)) - y' = (1*(c*x[2]-d*y)) + x'[0] = (-1*(kf0_*x[0]-kb0_*x[1])) + x'[1] = (1*(kf0_*x[0]-kb0_*x[1])) + x'[2] = (-1*(kf1_*x[2]-kb1_*y)) + y' = (1*(kf1_*x[2]-kb1_*y)) })"; THEN( "Convert to equivalent DERIVATIVE block, rewrite CONSERVE statement after summing over " @@ -389,8 +426,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = ((-1*(a*x-b*y)))/(c-d) - y' = ((1*(a*x-b*y)))/(c-d) + LOCAL kf0_, kb0_ + kf0_ = a + kb0_ = b + x' = ((-1*(kf0_*x-kb0_*y)))/(c-d) + y' = ((1*(kf0_*x-kb0_*y)))/(c-d) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -413,14 +453,21 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE ihkin { + LOCAL kf0_, kb0_, kf1_, kb1_, kf2_, kb2_ evaluate_fct(v, cai) + kf0_ = alpha + kb0_ = beta + kf1_ = k1ca + kb1_ = k2 + kf2_ = k3p + kb2_ = k4 CONSERVE p1 = 1-p0 CONSERVE o2 = 1-c1-o1 - c1' = (-1*(alpha*c1-beta*o1)) - o1' = (1*(alpha*c1-beta*o1))+(-1*(k3p*o1-k4*o2)) - o2' = (1*(k3p*o1-k4*o2)) - p0' = (-1*(k1ca*p0-k2*p1)) - p1' = (1*(k1ca*p0-k2*p1)) + c1' = (-1*(kf0_*c1-kb0_*o1)) + o1' = (1*(kf0_*c1-kb0_*o1))+(-1*(kf2_*o1-kb2_*o2)) + o2' = (1*(kf2_*o1-kb2_*o2)) + p0' = (-1*(kf1_*p0-kb1_*p1)) + p1' = (1*(kf1_*p0-kb1_*p1)) })"; THEN("Convert to equivalent DERIVATIVE block, re-order both CONSERVE statements") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -428,6 +475,32 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ REQUIRE(result[0] == reindent_text(output_nmodl_text)); } } + GIVEN("KINETIC block with one reaction statement & clashing local") { + std::string input_nmodl_text = R"( + ASSIGNED { + kf0_ + } + STATE { + x y + } + KINETIC states { + LOCAL kf0_0000, kb0_0000 + ~ x <-> y (kf0_, b) + })"; + std::string output_nmodl_text = R"( + DERIVATIVE states { + LOCAL kf0_0000, kb0_0000, kf0_0001, kb0_ + kf0_0001 = kf0_ + kb0_ = b + x' = (-1*(kf0_0001*x-kb0_*y)) + y' = (1*(kf0_0001*x-kb0_*y)) + })"; + THEN("Convert to equivalent DERIVATIVE block") { + auto result = run_kinetic_block_visitor(input_nmodl_text); + CAPTURE(input_nmodl_text); + REQUIRE(result[0] == reindent_text(output_nmodl_text)); + } + } GIVEN("KINETIC block with one reaction statement & 2 COMPARTMENT statements") { std::string input_nmodl_text = R"( STATE { @@ -440,8 +513,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = ((-1*(a*x-b*y)))/(cx) - y' = ((1*(a*x-b*y)))/(cy) + LOCAL kf0_, kb0_ + kf0_ = a + kb0_ = b + x' = ((-1*(kf0_*x-kb0_*y)))/(cx) + y' = ((1*(kf0_*x-kb0_*y)))/(cy) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -460,10 +536,15 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - w' = (-1*(c*w-d*z)) - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y)) - z' = (1*(c*w-d*z)) + LOCAL kf0_, kb0_, kf1_, kb1_ + kf0_ = a + kb0_ = b + kf1_ = c + kb1_ = d + w' = (-1*(kf1_*w-kb1_*z)) + x' = (-1*(kf0_*x-kb0_*y)) + y' = (1*(kf0_*x-kb0_*y)) + z' = (1*(kf1_*w-kb1_*z)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -482,9 +563,14 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) - z' = (1*(c*y-d*z)) + LOCAL kf0_, kb0_, kf1_, kb1_ + kf0_ = a + kb0_ = b + kf1_ = c + kb1_ = d + x' = (-1*(kf0_*x-kb0_*y)) + y' = (1*(kf0_*x-kb0_*y))+(-1*(kf1_*y-kb1_*z)) + z' = (1*(kf1_*y-kb1_*z)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -506,12 +592,17 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { + LOCAL kf0_, kb0_, kf1_, kb1_ c0 = 0 - c1 = a*x+b*y - c2 = c*y-2*d*z - x' = (-1*(a*x-b*y)) - y' = (1*(a*x-b*y))+(-1*(c*y-d*z)) - z' = (1*(c*y-d*z)) + kf0_ = a + kb0_ = b + c1 = kf0_*x+kb0_*y + kf1_ = c + kb1_ = d + c2 = kf1_*y-2*kb1_*z + x' = (-1*(kf0_*x-kb0_*y)) + y' = (1*(kf0_*x-kb0_*y))+(-1*(kf1_*y-kb1_*z)) + z' = (1*(kf1_*y-kb1_*z)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -529,8 +620,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = (-2*(a*x*x-b*y)) - y' = (1*(a*x*x-b*y)) + LOCAL kf0_, kb0_ + kf0_ = a + kb0_ = b + x' = (-2*(kf0_*x*x-kb0_*y)) + y' = (1*(kf0_*x*x-kb0_*y)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -548,8 +642,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - x' = (-2*(a*x*x-b*y)) - y' = (1*(a*x*x-b*y)) + LOCAL kf0_, kb0_ + kf0_ = a + kb0_ = b + x' = (-2*(kf0_*x*x-kb0_*y)) + y' = (1*(kf0_*x*x-kb0_*y)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -568,8 +665,11 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - mc' = (-1*(a(v)*mc-b(v)*m)) - m' = (1*(a(v)*mc-b(v)*m)) + LOCAL kf0_, kb0_ + kf0_ = a(v) + kb0_ = b(v) + mc' = (-1*(kf0_*mc-kb0_*m)) + m' = (1*(kf0_*mc-kb0_*m)) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); @@ -589,11 +689,17 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE states { - A' = (-2*(k1*A*A*B-k2*C))+(1*(k3*C*D-k4*A*B*B)) - B' = (-1*(k1*A*A*B-k2*C))+(2*(k3*C*D-k4*A*B*B)) - C' = (1*(k1*A*A*B-k2*C))+(-1*(k3*C*D-k4*A*B*B)) - D' = (-1*(k3*C*D-k4*A*B*B)) + LOCAL kf0_, kb0_, kf1_, kb1_ + kf0_ = k1 + kb0_ = k2 + kf1_ = k3 + kb1_ = k4 + A' = (-2*(kf0_*A*A*B-kb0_*C))+(1*(kf1_*C*D-kb1_*A*B*B)) + B' = (-1*(kf0_*A*A*B-kb0_*C))+(2*(kf1_*C*D-kb1_*A*B*B)) + C' = (1*(kf0_*A*A*B-kb0_*C))+(-1*(kf1_*C*D-kb1_*A*B*B)) + D' = (-1*(kf1_*C*D-kb1_*A*B*B)) })"; + THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); CAPTURE(input_nmodl_text); @@ -621,13 +727,24 @@ SCENARIO("Convert KINETIC to DERIVATIVE using KineticBlock visitor", "[kinetic][ })"; std::string output_nmodl_text = R"( DERIVATIVE kin { + LOCAL kf0_, kb0_, kf1_, kb1_, kf2_, kb2_, kf3_, kb3_, source4_, kf5_ + source4_ = a { + kf0_ = b[0] + kb0_ = c[0] + kf1_ = b[1] + kb1_ = c[1] + kf2_ = b[2] + kb2_ = c[2] + kf3_ = b[3] + kb3_ = c[3] } - x'[0] = (a)+(-1*(b[0]*x[0]-c[0]*x[1])) - x'[1] = (1*(b[0]*x[0]-c[0]*x[1]))+(-1*(b[1]*x[1]-c[1]*x[2])) - x'[2] = (1*(b[1]*x[1]-c[1]*x[2]))+(-1*(b[2]*x[2]-c[2]*x[3])) - x'[3] = (1*(b[2]*x[2]-c[2]*x[3]))+(-1*(b[3]*x[3]-c[3]*x[4])) - x'[4] = (1*(b[3]*x[3]-c[3]*x[4]))+(-1*(d*x[4])) + kf5_ = d + x'[0] = (source4_)+(-1*(kf0_*x[0]-kb0_*x[1])) + x'[1] = (1*(kf0_*x[0]-kb0_*x[1]))+(-1*(kf1_*x[1]-kb1_*x[2])) + x'[2] = (1*(kf1_*x[1]-kb1_*x[2]))+(-1*(kf2_*x[2]-kb2_*x[3])) + x'[3] = (1*(kf2_*x[2]-kb2_*x[3]))+(-1*(kf3_*x[3]-kb3_*x[4])) + x'[4] = (1*(kf3_*x[3]-kb3_*x[4]))+(-1*(kf5_*x[4])) })"; THEN("Convert to equivalent DERIVATIVE block") { auto result = run_kinetic_block_visitor(input_nmodl_text); diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 456f897564..4aa4335a1e 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -2159,20 +2159,22 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym std::string expected_text = R"( DERIVATIVE kstates { EIGEN_NEWTON_SOLVE[2]{ - LOCAL old_C1, old_C2 + LOCAL kf0_, kb0_, old_C1, old_C2 }{ + kb0_ = alfa(v) + kf0_ = alfa(v) old_C1 = C1 old_C2 = C2 }{ nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*alfa(v)-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*alfa(v)+old_C1 - nmodl_eigen_j[0] = -dt*alfa(v)-1.0 - nmodl_eigen_j[2] = dt*alfa(v) - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*alfa(v)-nmodl_eigen_x[1]*dt*alfa(v)-nmodl_eigen_x[1]+old_C2 - nmodl_eigen_j[1] = dt*alfa(v) - nmodl_eigen_j[3] = -dt*alfa(v)-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 + nmodl_eigen_j[0] = -dt*kf0_-1.0 + nmodl_eigen_j[2] = dt*kb0_ + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 + nmodl_eigen_j[1] = dt*kf0_ + nmodl_eigen_j[3] = -dt*kb0_-1.0 }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] @@ -2207,20 +2209,22 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym std::string expected_text = R"( DERIVATIVE kstates { EIGEN_NEWTON_SOLVE[2]{ - LOCAL old_C1, old_C2 + LOCAL kf0_, kb0_, old_C1, old_C2 }{ + kb0_ = lowergamma(v) + kf0_ = beta(v) old_C1 = C1 old_C2 = C2 }{ nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*beta(v)-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*lowergamma(v)+old_C1 - nmodl_eigen_j[0] = -dt*beta(v)-1.0 - nmodl_eigen_j[2] = dt*lowergamma(v) - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*beta(v)-nmodl_eigen_x[1]*dt*lowergamma(v)-nmodl_eigen_x[1]+old_C2 - nmodl_eigen_j[1] = dt*beta(v) - nmodl_eigen_j[3] = -dt*lowergamma(v)-1.0 + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 + nmodl_eigen_j[0] = -dt*kf0_-1.0 + nmodl_eigen_j[2] = dt*kb0_ + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 + nmodl_eigen_j[1] = dt*kf0_ + nmodl_eigen_j[3] = -dt*kb0_-1.0 }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] From 7aa91cad039bfe55fdd1f8e43847b2bad0897180 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 17 Apr 2024 18:29:35 +0200 Subject: [PATCH 622/871] Check python compile-time vs runtime (BlueBrain/nmodl#1242) NMODL Repo SHA: BlueBrain/nmodl@bb5355ff1558c1b4ae7115a4254be9244ac9df8c --- src/nmodl/pybind/pyembed.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index 447674a2b4..fd20bb101e 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -13,6 +13,9 @@ #include "pybind/pyembed.hpp" #include "utils/logger.hpp" +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) + namespace fs = std::filesystem; namespace nmodl { @@ -45,6 +48,28 @@ void EmbeddedPythonLoader::load_libraries() { logger->critical(errstr); throw std::runtime_error("Failed to dlopen"); } + + // This code is imported from PyBind11 because this is primarly in details for internal usage + // License of PyBind11 is BSD-style + { + std::string compiled_ver = fmt::format("{}.{}", PY_MAJOR_VERSION, PY_MINOR_VERSION); + const char* (*fun)(void) = (const char* (*) (void) ) dlsym(pylib_handle, "Py_GetVersion"); + if (fun == nullptr) { + logger->critical("Unable to find the function `Py_GetVersion`"); + throw std::runtime_error("Unable to find the function `Py_GetVersion`"); + } + const char* runtime_ver = fun(); + std::size_t len = compiled_ver.size(); + if (std::strncmp(runtime_ver, compiled_ver.c_str(), len) != 0 || + (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) { + logger->critical( + "nmodl has been compiled with python {} and is being run with python {}", + compiled_ver, + runtime_ver); + throw std::runtime_error("Python version mismatch between compile-time and runtime."); + } + } + if (std::getenv("NMODLHOME") == nullptr) { logger->critical("NMODLHOME environment variable must be set to load embedded python"); throw std::runtime_error("NMODLHOME not set"); From ba0bf9c56c1a736ce0dbc852bc0f02aa2cae3d49 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 18 Apr 2024 11:06:55 +0200 Subject: [PATCH 623/871] Fix dependency issues when included in NRN. (BlueBrain/nmodl#1243) In NRN we depend on the target `nmodl` to ensure that when we run `nrnivmodl -coreneuron` the binary `nmodl` exists. We need to also ensure that everything `nmodl` needs to works is present. This fixes a dependency bug on certain copied files by making the target for copying the files a dependency of `nmodl`. NMODL Repo SHA: BlueBrain/nmodl@b78c3f3490c0f405d0c3b3da4329ff1db0d77f6c --- cmake/nmodl/hpc-coding-conventions | 2 +- src/nmodl/CMakeLists.txt | 1 + src/nmodl/pybind/CMakeLists.txt | 2 ++ src/nmodl/solver/CMakeLists.txt | 4 ++++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index f8f8d69a66..8f81155978 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit f8f8d69a66c23978d1c9c5dce62de79466f26e5d +Subproject commit 8f8115597817365c5c4fa39e217b3ab0b3640cb2 diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 7a81895ff3..55998fd95a 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -34,6 +34,7 @@ target_link_libraries( util lexer ${NMODL_WRAPPER_LIBS}) +add_dependencies(nmodl nmodl_copy_python_files nmodl_copy_solver_files) cpp_cc_configure_sanitizers(TARGET nmodl) # ============================================================================= diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 0d7516874b..c23d8e6a2a 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -76,7 +76,9 @@ file( foreach(file IN LISTS NMODL_PYTHON_FILES) cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/${file} OUTPUT ${CMAKE_BINARY_DIR}/lib/nmodl/${file}) + list(APPEND nmodl_python_binary_dir_files "${CMAKE_BINARY_DIR}/lib/nmodl/${file}") endforeach() +add_custom_target(nmodl_copy_python_files ALL DEPENDS ${nmodl_python_binary_dir_files}) file(COPY ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ext DESTINATION ${CMAKE_BINARY_DIR}/lib/nmodl/) # ============================================================================= diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index 78c44c5d0d..7d221c4159 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -8,6 +8,10 @@ cpp_cc_build_time_copy(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp" OUT cpp_cc_build_time_copy(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp" OUTPUT "${CMAKE_BINARY_DIR}/include/crout/crout.hpp") +add_custom_target( + nmodl_copy_solver_files ALL DEPENDS "${CMAKE_BINARY_DIR}/include/newton/newton.hpp" + "${CMAKE_BINARY_DIR}/include/crout/crout.hpp") + # Eigen file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY_DIR}/include/) From dcfb6d82d63a2f017e138626df1464f16cd29fdd Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 18 Apr 2024 14:05:54 +0200 Subject: [PATCH 624/871] Launch Python embedded interpreter only if using sympy (BlueBrain/nmodl#1241) NMODL Repo SHA: BlueBrain/nmodl@df2343ee54b703d20979ff6f6d4f1a518b3597ac --- src/nmodl/main.cpp | 47 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 0a678789d2..3d4aeb24f9 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -290,12 +290,6 @@ int main(int argc, const char* argv[]) { fs::create_directories(output_dir); fs::create_directories(scratch_dir); - if (sympy_opt) { - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() - .api() - ->initialize_interpreter(); - } - logger->set_level(spdlog::level::from_str(verbose)); /// write ast to nmodl @@ -487,22 +481,31 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("localize")); } - if (sympy_conductance) { - logger->info("Running sympy conductance visitor"); - SympyConductanceVisitor().visit_program(*ast); - SymtabVisitor(update_symtab).visit_program(*ast); - ast_to_nmodl(*ast, filepath("sympy_conductance")); - } + if (sympy_conductance || sympy_analytic || sparse_solver_exists(*ast)) { + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() + .api() + ->initialize_interpreter(); + if (sympy_conductance) { + logger->info("Running sympy conductance visitor"); + SympyConductanceVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("sympy_conductance")); + } - if (sympy_analytic || sparse_solver_exists(*ast)) { - if (!sympy_analytic) { - logger->info( - "Automatically enable sympy_analytic because it exists solver of type sparse"); + if (sympy_analytic || sparse_solver_exists(*ast)) { + if (!sympy_analytic) { + logger->info( + "Automatically enable sympy_analytic because it exists solver of type " + "sparse"); + } + logger->info("Running sympy solve visitor"); + SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("sympy_solve")); } - logger->info("Running sympy solve visitor"); - SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast); - SymtabVisitor(update_symtab).visit_program(*ast); - ast_to_nmodl(*ast, filepath("sympy_solve")); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() + .api() + ->finalize_interpreter(); } { @@ -579,8 +582,4 @@ int main(int argc, const char* argv[]) { } } } - - if (sympy_opt) { - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); - } } From 06213b0b2b1b0a42fdb95fdd9cc7034e78e0702f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 18 Apr 2024 18:01:15 +0200 Subject: [PATCH 625/871] Fix array variables. (BlueBrain/nmodl#1233) * Fix array variables. These are the code generation changes required for: https://github.com/neuronsimulator/nrn/pull/2779 The solution is to fill `nrn_prop_param_size` with the number of doubles, not number of variables. NMODL Repo SHA: BlueBrain/nmodl@f6821cea1af81eb3061fbfff6552f0c776ef7626 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 7 ++++++- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 +++--- test/nmodl/transpiler/usecases/CMakeLists.txt | 2 +- test/nmodl/transpiler/usecases/references | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 994b2e4165..f33aa90611 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -151,7 +151,12 @@ bool CodegenCppVisitor::defined_method(const std::string& name) const { } int CodegenCppVisitor::float_variables_size() const { - return codegen_float_variables.size(); + int n_floats = 0; + for (const auto& var: codegen_float_variables) { + n_floats += var->get_length(); + } + + return n_floats; } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 947588c778..dad632c05c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1029,8 +1029,8 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(); printer->fmt_line("hoc_register_prop_size(mech_type, {}, {});", - codegen_float_variables_size, - codegen_int_variables_size); + float_variables_size(), + int_variables_size()); for (int i = 0; i < codegen_int_variables_size; ++i) { const auto& int_var = codegen_int_variables[i]; @@ -1658,7 +1658,7 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { std::to_string(int_variables_size()), ";"); printer->add_line("static constexpr auto number_of_floating_point_variables = ", - std::to_string(float_variables_size()), + std::to_string(codegen_float_variables.size()), ";"); printer->add_newline(); printer->add_multi_line(R"CODE( diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 6f37cf7d88..267f51f408 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,6 +1,6 @@ set(NMODL_USECASE_DIRS cnexp_scalar - cnexp_array + # cnexp_array global_breakpoint point_process parameter diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 61b4aec4d5..c9f0aaa927 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 61b4aec4d569c0c3dfa4906d1a32304e22b1c8db +Subproject commit c9f0aaa9270e9a49598c5424ad03eaa48a7eec47 From 3fc802d397cdc9a1376fdc969257f60418c2ed45 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 23 Apr 2024 15:57:11 +0200 Subject: [PATCH 626/871] Improvements for handling python bindings CMake option (BlueBrain/nmodl#1244) - If `-DNMODL_ENABLE_PYTHON_BINDINGS=OFF` then there is no need to generate AST and other wrapper classes for Pybind11. - Using sympy-based solvers does not require Python bindings to be enabled. Avoid confusing warning when using nmodl binary. - When someone tries to use the nmodl module via Python, the warning is still preserved. ```console $ nmodl mod_examples/sparse.mod [NMODL] [warning] :: Python bindings are not available with this installation .. $ nmodl mod_examples/sparse.mod $ python3.11 -c "import nmodl" [NMODL] [warning] :: Python bindings are not available with this installation ``` NMODL Repo SHA: BlueBrain/nmodl@8eb88e4c8668fe9451d1ee5bb09223f42669f163 --- src/nmodl/language/CMakeLists.txt | 4 ++++ src/nmodl/language/code_generator.py | 22 ++++++++++++++++++-- src/nmodl/nmodl/python/nmodl/__init__.py | 26 +++++++++++++++++------- src/nmodl/pybind/wrapper.cpp | 10 +++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 06522f522f..92a8ce46b4 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -14,6 +14,10 @@ if(NMODL_CLANG_FORMAT OR NMODL_FORMATTING) endforeach() endif() +if(NOT NMODL_ENABLE_PYTHON_BINDINGS) + list(APPEND CODE_GENERATOR_OPTS "--disable-pybind") +endif() + add_custom_command( OUTPUT ${NMODL_GENERATED_SOURCES} COMMAND ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/code_generator.py diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 90113d0126..e160039ed8 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -33,6 +33,7 @@ class CodeGenerator( [ "base_dir", "clang_format", + "disable_pybind", "jinja_env", "jinja_templates_dir", "modification_date", @@ -49,6 +50,7 @@ class CodeGenerator( Attributes: base_dir: output root directory where Jinja templates are rendered clang_format: clang-format command line if C++ files have to be formatted, `None` otherwise + disable_pybind: disable python bindings related code generation py_files: list of Path objects to the Python files used by this program yaml_files: list of Path object to YAML files describing the NMODL language modification_date: most recent modification date of the Python and YAML files in this directory @@ -58,7 +60,7 @@ class CodeGenerator( temp_dir: path to the directory where to create temporary files """ - def __new__(cls, base_dir, clang_format=None): + def __new__(cls, base_dir, clang_format=None, disable_pybind=False): this_dir = Path(__file__).parent.resolve() jinja_templates_dir = this_dir / "templates" py_files = [Path(p).relative_to(this_dir) for p in this_dir.glob("*.py")] @@ -67,6 +69,7 @@ def __new__(cls, base_dir, clang_format=None): cls, base_dir=base_dir, clang_format=clang_format, + disable_pybind=disable_pybind, this_dir=this_dir, jinja_templates_dir=jinja_templates_dir, jinja_env=jinja2.Environment( @@ -174,6 +177,11 @@ def workload(self): tasks = [] for path in self.jinja_templates_dir.iterdir(): sub_dir = PurePath(path).name + + # skip pybind directory as it's needed for python bindings only + if self.disable_pybind and sub_dir == "pybind": + continue + # create output directory if missing (self.base_dir / sub_dir).mkdir(parents=True, exist_ok=True) for filepath in path.glob("*.[ch]pp"): @@ -358,6 +366,12 @@ def parse_args(args=None): parser.add_argument( "-v", "--verbosity", action="count", default=0, help="increase output verbosity" ) + parser.add_argument( + "--disable-pybind", + action="store_true", + help="Do not generate code related to python bindigs", + ) + args = parser.parse_args(args=args) # construct clang-format command line to use, if provided @@ -393,7 +407,11 @@ def main(args=None): args = parse_args(args) configure_logger(args.verbosity) - codegen = CodeGenerator(clang_format=args.clang_format, base_dir=args.base_dir) + codegen = CodeGenerator( + clang_format=args.clang_format, + base_dir=args.base_dir, + disable_pybind=args.disable_pybind, + ) num_tasks = 0 tasks_performed = [] for task in codegen.workload(): diff --git a/src/nmodl/nmodl/python/nmodl/__init__.py b/src/nmodl/nmodl/python/nmodl/__init__.py index dbc53cbe90..b427d14af7 100644 --- a/src/nmodl/nmodl/python/nmodl/__init__.py +++ b/src/nmodl/nmodl/python/nmodl/__init__.py @@ -31,11 +31,23 @@ ) -try: - # Try importing but catch exception in case bindings are not available - from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa - from ._nmodl import __version__ +import builtins + +nmodl_binding_check = True + +# attribute is set from `pybind/wrapper.cpp` when nmodl module is used +# for sympy based solvers +if hasattr(builtins, "nmodl_python_binding_check"): + nmodl_binding_check = builtins.nmodl_python_binding_check + +if nmodl_binding_check: + try: + # Try importing but catch exception in case bindings are not available + from ._nmodl import NmodlDriver, to_json, to_nmodl # noqa + from ._nmodl import __version__ - __all__ = ["NmodlDriver", "to_json", "to_nmodl"] -except ImportError: - print("[NMODL] [warning] :: Python bindings are not available") + __all__ = ["NmodlDriver", "to_json", "to_nmodl"] + except ImportError: + print( + "[NMODL] [warning] :: Python bindings are not available with this installation" + ) diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index d456e5c2d0..52f9913f6d 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -29,6 +29,8 @@ void SolveLinearSystemExecutor::operator()() { "function_calls"_a = function_calls, "tmp_unique_prefix"_a = tmp_unique_prefix); py::exec(R"( + import builtins + builtins.nmodl_python_binding_check = False from nmodl.ode import solve_lin_system exception_message = "" try: @@ -62,6 +64,8 @@ void SolveNonLinearSystemExecutor::operator()() { "vars"_a = vars, "function_calls"_a = function_calls); py::exec(R"( + import builtins + builtins.nmodl_python_binding_check = False from nmodl.ode import solve_non_lin_system exception_message = "" try: @@ -95,6 +99,8 @@ void DiffeqSolverExecutor::operator()() { // with forwards Euler timestep: // x = x + f(x) * dt py::exec(R"( + import builtins + builtins.nmodl_python_binding_check = False from nmodl.ode import forwards_euler2c exception_message = "" try: @@ -111,6 +117,8 @@ void DiffeqSolverExecutor::operator()() { // with analytic solution for x(t+dt) in terms of x(t) // x = ... py::exec(R"( + import builtins + builtins.nmodl_python_binding_check = False from nmodl.ode import integrate2c exception_message = "" try: @@ -134,6 +142,8 @@ void DiffeqSolverExecutor::operator()() { void AnalyticDiffExecutor::operator()() { auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block); py::exec(R"( + import builtins + builtins.nmodl_python_binding_check = False from nmodl.ode import differentiate2c exception_message = "" try: From bca91b1f4d59e69b2ba5c08c9a8df7d9fd7d0399 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Thu, 25 Apr 2024 07:25:27 +0200 Subject: [PATCH 627/871] Bug fix for NEURON wheel creation (BlueBrain/nmodl#1245) - when neuron is built with nmodl then nmodl related files should be installed in / and not in /nmodl/.data - /nmodl/.data is used when we build standalone wheels - add one mod file using sparse solver - nmodl will automatically use sympy solver NMODL Repo SHA: BlueBrain/nmodl@2fb037e1f8d60d522ba16bd968c69a318ce5a827 --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/pybind/CMakeLists.txt | 2 +- .../transpiler/integration/CMakeLists.txt | 2 + .../integration/mod/glia_sparse.mod | 38 +++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/integration/mod/glia_sparse.mod diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4754ca8222..82321120de 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -162,7 +162,7 @@ add_custom_target( # ============================================================================= # Adjust install prefix for wheel # ============================================================================= -if(NOT LINK_AGAINST_PYTHON) +if(NOT LINK_AGAINST_PYTHON AND NOT NMODL_AS_SUBPROJECT) set(NMODL_INSTALL_DIR_SUFFIX "nmodl/.data/") endif() diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index c23d8e6a2a..7517c4dd95 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -89,7 +89,7 @@ file(COPY ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ext DESTINATION ${CMAKE_BINAR # things are installed twice with the wheel and in weird places. Let's just # move the .so libs # ~~~ -if(NOT LINK_AGAINST_PYTHON) +if(NOT LINK_AGAINST_PYTHON AND NOT NMODL_AS_SUBPROJECT) install(TARGETS pywrapper DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib) if(NMODL_ENABLE_PYTHON_BINDINGS) install(TARGETS _nmodl DESTINATION nmodl/) diff --git a/test/nmodl/transpiler/integration/CMakeLists.txt b/test/nmodl/transpiler/integration/CMakeLists.txt index 29bbd41ae3..eb78fbc1f3 100644 --- a/test/nmodl/transpiler/integration/CMakeLists.txt +++ b/test/nmodl/transpiler/integration/CMakeLists.txt @@ -11,5 +11,7 @@ file(GLOB modfiles CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/test/integrati foreach(modfile ${modfiles}) get_filename_component(modfile_name "${modfile}" NAME) add_test(NAME ${modfile_name} COMMAND ${CMAKE_BINARY_DIR}/bin/nmodl ${modfile}) + set_tests_properties(${modfile_name} + PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) cpp_cc_configure_sanitizers(TEST ${modfile_name}) endforeach() diff --git a/test/nmodl/transpiler/integration/mod/glia_sparse.mod b/test/nmodl/transpiler/integration/mod/glia_sparse.mod new file mode 100644 index 0000000000..ebc58072e7 --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/glia_sparse.mod @@ -0,0 +1,38 @@ +: Example of MOD file using sparse solver + +NEURON { + SUFFIX glia + RANGE alfa, beta + RANGE Aalfa, Valfa, Abeta, Vbeta +} + +PARAMETER { + Aalfa = 353.91 ( /ms) + Valfa = 13.99 ( /mV) + Abeta = 1.272 ( /ms) + Vbeta = 13.99 ( /mV) + n1 = 5.422 + n4 = 0.738 +} + +STATE { + C1 + C2 +} + +BREAKPOINT { + SOLVE kstates METHOD sparse +} + +FUNCTION alfa(v(mV)) { + alfa = Aalfa*exp(v/Valfa) +} + +FUNCTION beta(v(mV)) { + beta = Abeta*exp(-v/Vbeta) +} + +KINETIC kstates { + ~ C1 <-> C2 (n1*alfa(v),n4*beta(v)) +} + From b66198fbd16f57a67c1dfb94eca619060d279588 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 7 May 2024 08:49:36 +0200 Subject: [PATCH 628/871] Update test for nonspecific current (BlueBrain/nmodl#1235) NMODL Repo SHA: BlueBrain/nmodl@2dc296dba574540783cb736374190a04abbe2add --- .../usecases/nonspecific_current/leonhard.mod | 7 +++- .../usecases/nonspecific_current/simulate.py | 37 +++++++++++++++++-- test/nmodl/transpiler/usecases/references | 2 +- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod b/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod index bec601666b..cd2af1ad31 100644 --- a/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod +++ b/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod @@ -5,12 +5,17 @@ UNITS { NEURON { SUFFIX leonhard NONSPECIFIC_CURRENT il + RANGE c } ASSIGNED { il (mA/cm2) } +PARAMETER { + c = 0.005 +} + BREAKPOINT { - il = 0.005 * (v - 1.5) + il = c * (v - 1.5) } diff --git a/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py b/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py index 6a192ef71e..1eab2f586f 100644 --- a/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py +++ b/test/nmodl/transpiler/usecases/nonspecific_current/simulate.py @@ -1,13 +1,15 @@ import numpy as np -from neuron import h, gui +from neuron import gui, h from neuron.units import ms -nseg = 1 +# Test the accuracy of the simulation output to the analytical result +nseg = 1 s = h.Section() s.insert("leonhard") s.nseg = nseg +s.c_leonhard = 0.005 v_hoc = h.Vector().record(s(0.5)._ref_v) t_hoc = h.Vector().record(h._ref_t) @@ -20,10 +22,39 @@ t = np.array(t_hoc.as_numpy()) erev = 1.5 -rate = 0.005 / 1e-3 +rate = s.c_leonhard / 1e-3 v0 = -65.0 v_exact = erev + (v0 - erev) * np.exp(-rate * t) rel_err = np.abs(v - v_exact) / np.max(np.abs(v_exact)) assert np.all(rel_err < 1e-1), f"rel_err = {rel_err}" + + +# Test the stability of the simulation at final time using a single timestep +nseg = 1 +s = h.Section() +s.insert("leonhard") +s.nseg = nseg +s.c_leonhard = 0.5 + +v_hoc = h.Vector().record(s(0.5)._ref_v) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 1000.0 * ms +h.dt = 1000.0 * ms +h.run() + +v = np.array(v_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +erev = 1.5 +rate = s.c_leonhard / 1e-3 +v0 = -65.0 +v_exact = erev + (v0 - erev) * np.exp(-rate * t) +rel_err = np.abs(v - v_exact) / np.max(np.abs(v_exact)) + +assert np.allclose(v[-1], v_exact[-1], atol=0.0), f"rel_err = {rel_err}" + + print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index c9f0aaa927..33fb74dc17 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit c9f0aaa9270e9a49598c5424ad03eaa48a7eec47 +Subproject commit 33fb74dc17fa924dfce2ac109c8b0e36d04edd8d From b1f2da55a897d4f661d5324c260be607bde7db7c Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 7 May 2024 08:57:25 +0200 Subject: [PATCH 629/871] Remove `print_device_method_annotation` (BlueBrain/nmodl#1249) It was dead code NMODL Repo SHA: BlueBrain/nmodl@8285f261fffd7508a8fca992f6aa59151e2c69e9 --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 15 --------------- .../codegen/codegen_coreneuron_cpp_visitor.hpp | 9 --------- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 1 - 3 files changed, 25 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index ed919fa887..3ac2bbf8b4 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -303,11 +303,6 @@ void CodegenCoreneuronCppVisitor::print_atomic_reduction_pragma() { } -void CodegenCoreneuronCppVisitor::print_device_method_annotation() { - // backend specific, nothing for cpu -} - - void CodegenCoreneuronCppVisitor::print_global_method_annotation() { // backend specific, nothing for cpu } @@ -450,7 +445,6 @@ void CodegenCoreneuronCppVisitor::print_table_check_function(const Block& node) auto float_type = default_float_data_type(); printer->add_newline(2); - print_device_method_annotation(); printer->fmt_push_block("void check_{}({})", method_name(name), get_parameter_str(internal_params)); @@ -1079,7 +1073,6 @@ std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& void CodegenCoreneuronCppVisitor::print_first_pointer_var_index_getter() { printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline int first_pointer_var_index()"); printer->fmt_line("return {};", info.first_pointer_var_index); printer->pop_block(); @@ -1088,7 +1081,6 @@ void CodegenCoreneuronCppVisitor::print_first_pointer_var_index_getter() { void CodegenCoreneuronCppVisitor::print_first_random_var_index_getter() { printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline int first_random_var_index()"); printer->fmt_line("return {};", info.first_random_var_index); printer->pop_block(); @@ -1097,13 +1089,11 @@ void CodegenCoreneuronCppVisitor::print_first_random_var_index_getter() { void CodegenCoreneuronCppVisitor::print_num_variable_getter() { printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline int float_variables_size()"); printer->fmt_line("return {};", float_variables_size()); printer->pop_block(); printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline int int_variables_size()"); printer->fmt_line("return {};", int_variables_size()); printer->pop_block(); @@ -1115,7 +1105,6 @@ void CodegenCoreneuronCppVisitor::print_net_receive_arg_size_getter() { return; } printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline int num_net_receive_args()"); printer->fmt_line("return {};", info.num_net_receive_parameters); printer->pop_block(); @@ -1124,7 +1113,6 @@ void CodegenCoreneuronCppVisitor::print_net_receive_arg_size_getter() { void CodegenCoreneuronCppVisitor::print_mech_type_getter() { printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline int get_mech_type()"); // false => get it from the host-only global struct, not the instance structure printer->fmt_line("return {};", get_variable_name("mech_type", false)); @@ -1134,7 +1122,6 @@ void CodegenCoreneuronCppVisitor::print_mech_type_getter() { void CodegenCoreneuronCppVisitor::print_memb_list_getter() { printer->add_newline(2); - print_device_method_annotation(); printer->push_block("static inline Memb_list* get_memb_list(NrnThread* nt)"); printer->push_block("if (!nt->_ml_list)"); printer->add_line("return nullptr;"); @@ -2903,7 +2890,6 @@ void CodegenCoreneuronCppVisitor::print_net_send_buffering() { } printer->add_newline(2); - print_device_method_annotation(); auto args = "const NrnThread* nt, NetSendBuffer_t* nsb, int type, int vdata_index, " "int weight_index, int point_index, double t, double flag"; @@ -3183,7 +3169,6 @@ void CodegenCoreneuronCppVisitor::print_nrn_current(const BreakpointBlock& node) const auto& args = internal_method_parameters(); const auto& block = node.get_statement_block(); printer->add_newline(2); - print_device_method_annotation(); printer->fmt_push_block("inline double nrn_current_{}({})", info.mod_suffix, get_parameter_str(args)); diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 43d2b45e87..21b03c9334 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -250,14 +250,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual void print_atomic_reduction_pragma() override; - /** - * Print the backend specific device method annotation - * - * \note This is not used for the C++ backend - */ - virtual void print_device_method_annotation(); - - /** * Print backend specific global method annotation * @@ -1147,7 +1139,6 @@ void CodegenCoreneuronCppVisitor::print_function_declaration(const T& node, return_type = default_float_data_type(); } - print_device_method_annotation(); printer->add_indent(); printer->fmt_text("inline {} {}({})", return_type, diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index dad632c05c..5e48aa3c48 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1447,7 +1447,6 @@ void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { const auto& args = nrn_current_parameters(); const auto& block = node.get_statement_block(); printer->add_newline(2); - // print_device_method_annotation(); printer->fmt_push_block("inline double nrn_current_{}({})", info.mod_suffix, get_parameter_str(args)); From 00f3d8c168afa1ccff23ffba8b919df786ddb5e7 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 7 May 2024 14:32:42 +0200 Subject: [PATCH 630/871] Remove unused variable in NEURON codegen (BlueBrain/nmodl#1252) NMODL Repo SHA: BlueBrain/nmodl@0ba94b70c2764f2973e41f62c5f0db7ef5315735 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 5e48aa3c48..2f5ddbf5cd 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1033,7 +1033,6 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { int_variables_size()); for (int i = 0; i < codegen_int_variables_size; ++i) { - const auto& int_var = codegen_int_variables[i]; if (i != info.semantics[i].index) { throw std::runtime_error("Broken logic."); } From 945fc27d0b3c461a56820e63bdf54a86d43c135e Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 7 May 2024 14:55:21 +0200 Subject: [PATCH 631/871] Fix missing variables when calling functions or procedures in INITIAL block (BlueBrain/nmodl#1248) NMODL Repo SHA: BlueBrain/nmodl@b3dd6f1b937fa7cbccc1bc30352802c230dee557 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 3 +++ test/nmodl/transpiler/usecases/func_proc/func_proc.mod | 4 ++++ test/nmodl/transpiler/usecases/references | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2f5ddbf5cd..e1bc16897e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1217,6 +1217,8 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); printer->add_line("auto nodecount = _ml_arg->nodecount;"); + printer->add_line("auto* const _ml = &_lmr;"); + printer->add_line("auto* _thread = _ml_arg->_thread;"); } @@ -1226,6 +1228,7 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { print_global_function_common_code(BlockType::Initial); printer->push_block("for (int id = 0; id < nodecount; id++)"); + printer->add_line("auto& _ppvar = _ml_arg->pdata[id];"); print_initial_block(info.initial_node); printer->pop_block(); diff --git a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod index c11f662ebd..0f95d04c29 100644 --- a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod +++ b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod @@ -23,3 +23,7 @@ PROCEDURE set_a_x() { LOCAL a a = x } + +INITIAL { + set_a_x() +} diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 33fb74dc17..763ca26935 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 33fb74dc17fa924dfce2ac109c8b0e36d04edd8d +Subproject commit 763ca269359d2d5ae9c2fec56c643f0f082d6f8b From 8ab8033412fc0926e0812c85d114db585c96fa32 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 7 May 2024 17:01:38 +0200 Subject: [PATCH 632/871] Implement simplified expsyn. (BlueBrain/nmodl#1238) Instead of setting the conductance as: g = g + g0 * exp(-(t - t0)/tau) we set it to g += g0 where `g0` is the argument passed to NET_RECEIVE, i.e. the weight. This allows for an analytic solution, that can be tested. NMODL Repo SHA: BlueBrain/nmodl@ada65dfce0bfad210b48cc385c4963e59e09a14b --- .../codegen/codegen_neuron_cpp_visitor.cpp | 108 ++++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.hpp | 9 ++ test/nmodl/transpiler/usecases/CMakeLists.txt | 3 +- .../usecases/net_receive/simulate.py | 49 ++++++++ .../usecases/net_receive/snapsyn.mod | 33 ++++++ test/nmodl/transpiler/usecases/references | 2 +- 6 files changed, 192 insertions(+), 12 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/net_receive/simulate.py create mode 100644 test/nmodl/transpiler/usecases/net_receive/snapsyn.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e1bc16897e..dff58c655d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -15,9 +15,11 @@ #include "ast/all.hpp" #include "codegen/codegen_utils.hpp" +#include "codegen_naming.hpp" #include "config/config.h" #include "utils/string_utils.hpp" #include "visitors/rename_visitor.hpp" +#include "visitors/var_usage_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { @@ -26,6 +28,7 @@ namespace codegen { using namespace ast; using visitor::RenameVisitor; +using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; @@ -507,7 +510,7 @@ std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbo std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& symbol, const std::string& name, bool use_instance) const { - // auto position = position_of_int_var(name); + auto position = position_of_int_var(name); if (symbol.is_index) { if (use_instance) { throw std::runtime_error("Not implemented. [wiejo]"); @@ -518,11 +521,9 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& } if (symbol.is_integer) { if (use_instance) { - throw std::runtime_error("Not implemented. [cnuoe]"); - // return fmt::format("inst->{}[{}*pnodecount+id]", name, position); + return fmt::format("inst.{}[id]", name); } - throw std::runtime_error("Not implemented. [u32ow]"); - // return fmt::format("indexes[{}*pnodecount+id]", position); + return fmt::format("_ppvar[{}]", position); } if (use_instance) { return fmt::format("(*inst.{}[id])", name); @@ -583,9 +584,7 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, return std::string("_nt->_") + naming::NTHREAD_DT_VARIABLE; } - // t in net_receive method is an argument to function and hence it should - // be used instead of nt->_t which is current time of thread - if (varname == naming::NTHREAD_T_VARIABLE && !printing_net_receive) { + if (varname == naming::NTHREAD_T_VARIABLE) { return std::string("_nt->_") + naming::NTHREAD_T_VARIABLE; } @@ -1009,7 +1008,12 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { throw std::runtime_error("Broken logic."); } - auto type = (name == naming::POINT_PROCESS_VARIABLE) ? "Point_process*" : "double*"; + auto type = "double*"; + if (name == naming::POINT_PROCESS_VARIABLE) { + type = "Point_process*"; + } else if (name == naming::TQITEM_VARIABLE) { + type = "void*"; + } mech_register_args.push_back( fmt::format("_nrn_mechanism_field<{}>{{\"{}\", \"{}\"}} /* {} */", type, @@ -1046,6 +1050,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { if (!info.point_process) { printer->add_line("hoc_register_npy_direct(mech_type, npy_direct_func_proc);"); } + if (info.net_receive_node) { + printer->fmt_line("pnt_receive[mech_type] = nrn_net_receive_{};", info.mod_suffix); + printer->fmt_line("pnt_receive_size[mech_type] = {};", info.num_net_receive_parameters); + } printer->pop_block(); } @@ -1741,6 +1749,7 @@ void CodegenNeuronCppVisitor::print_compute_functions() { print_nrn_cur(); print_nrn_state(); print_nrn_jacob(); + print_net_receive(); } @@ -1765,7 +1774,22 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { } void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) { - throw std::runtime_error("Not implemented."); + auto const& arguments = node.get_arguments(); + + if (printing_net_receive || printing_net_init) { + throw std::runtime_error("Not implemented. [jfiwoei]"); + } + + std::string weight_pointer = "nullptr"; + const auto& point_process = get_variable_name("point_process", /* use_instance */ false); + const auto& tqitem = get_variable_name("tqitem", /* use_instance */ false); + + printer->fmt_text("net_send(/* tqitem */ &{}, {}, {}.get(), t + ", + tqitem, + weight_pointer, + point_process); + print_vector_elements(arguments, ", "); + printer->add_text(')'); } void CodegenNeuronCppVisitor::print_net_move_call(const ast::FunctionCall& node) { @@ -1776,6 +1800,70 @@ void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& node throw std::runtime_error("Not implemented."); } +/** + * Rename arguments to NET_RECEIVE block with corresponding pointer variable + * + * \code{.mod} + * NET_RECEIVE (weight, R){ + * x = R + * } + * \endcode + * + * then generated code should be: + * + * \code{.cpp} + * x[id] = _args[1]; + * \endcode + * + * So, the `R` in AST needs to be renamed with `_args[1]`. + */ +static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive_node, + const ast::Node& node) { + const auto& parameters = net_receive_node.get_parameters(); + + auto n_parameters = parameters.size(); + for (size_t i = 0; i < n_parameters; ++i) { + const auto& name = parameters[i]->get_node_name(); + auto var_used = VarUsageVisitor().variable_used(node, name); + if (var_used) { + RenameVisitor vr(name, fmt::format("_args[{}]", i)); + node.get_statement_block()->visit_children(vr); + } + } +} + +void CodegenNeuronCppVisitor::print_net_receive() { + auto node = info.net_receive_node; + if (!node) { + return; + } + + ParamVector args; + args.emplace_back("", "Point_process*", "", "_pnt"); + args.emplace_back("", "double*", "", "_args"); + args.emplace_back("", "double", "", "_lflag"); + + printer->fmt_push_block("static void nrn_net_receive_{}({})", + info.mod_suffix, + get_parameter_str(args)); + + rename_net_receive_arguments(*node, *node); + + printer->add_line("_nrn_mechanism_cache_instance _ml_obj{_pnt->prop};"); + printer->add_line("auto * _nt = static_cast(_pnt->_vnt);"); + printer->add_line("auto * _ml = &_ml_obj;"); + + printer->fmt_line("auto inst = make_instance_{}(_ml_obj);", info.mod_suffix); + + printer->add_line("size_t id = 0;"); + printer->add_line("double t = _nt->_t;"); + + print_statement_block(*node->get_statement_block(), false, false); + + printer->add_newline(); + printer->pop_block(); +} + /****************************************************************************************/ /* Overloaded visitor routines */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 5783456bc4..f8b9aa5e24 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -157,6 +157,15 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_net_event_call(const ast::FunctionCall& node) override; + /** + * Print `net_receive` call-back. + */ + void print_net_receive(); + + /** + * Print code to register the call-back for the NET_RECEIVE block. + */ + void print_net_receive_registration(); /** * Print POINT_PROCESS related functions diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 267f51f408..32f5cb2ad7 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -6,7 +6,8 @@ set(NMODL_USECASE_DIRS parameter func_proc func_proc_pnt - nonspecific_current) + nonspecific_current + net_receive) file(GLOB NMODL_GOLDEN_REFERENCES "${CMAKE_CURRENT_SOURCE_DIR}/references/*") if(NMODL_GOLDEN_REFERENCES STREQUAL "") diff --git a/test/nmodl/transpiler/usecases/net_receive/simulate.py b/test/nmodl/transpiler/usecases/net_receive/simulate.py new file mode 100644 index 0000000000..a86a396a31 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/simulate.py @@ -0,0 +1,49 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +soma = h.Section() +soma.nseg = nseg +soma.cm = 1.0 + +syn = h.SnapSyn(soma(0.5)) + +v0 = -65.0 +erev = 10.0 +delay = 0.75 +dg = 0.8 +area = soma(0.5).area() +rate = dg * 1e5 / area + +stim = h.NetStim() +stim.interval = 1.0 +stim.number = 1 +stim.start = delay + +netcon = h.NetCon(stim, syn, 0, 0.0, dg) + +v_hoc = h.Vector().record(soma(0.5)._ref_v) +t_hoc = h.Vector().record(h._ref_t) + +h.dt = 0.01 +h.stdinit() +h.tstop = 10.0 * ms +h.run() + + +def v(t): + v = np.empty_like(t) + v[t <= delay] = v0 + v[t >= delay] = erev + (v0 - erev) * np.exp(-rate * (t[t >= delay] - delay)) + + return v + + +t = np.array(t_hoc.as_numpy()) +v_approx = np.array(v_hoc.as_numpy()) +v_exact = v(t) + +assert np.all(np.abs(v_approx - v_exact)) < 1e-10 diff --git a/test/nmodl/transpiler/usecases/net_receive/snapsyn.mod b/test/nmodl/transpiler/usecases/net_receive/snapsyn.mod new file mode 100644 index 0000000000..1b8257b1b8 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/snapsyn.mod @@ -0,0 +1,33 @@ +NEURON { + POINT_PROCESS SnapSyn + RANGE e, i + NONSPECIFIC_CURRENT i +} + +UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) +} + +PARAMETER { + e = 10 (mV) +} + +ASSIGNED { + v (mV) + i (nA) + g (uS) +} + +INITIAL { + g=0 +} + +BREAKPOINT { + i = g*(v - e) +} + +NET_RECEIVE(weight (uS)) { + g = g + weight +} diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 763ca26935..7fce7545e4 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 763ca269359d2d5ae9c2fec56c643f0f082d6f8b +Subproject commit 7fce7545e4e7654c5a6b9e043ceb7630c63f0382 From 51161dc5e4ae721b2ae8bd02fdfee7ab1e7142e0 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 10 May 2024 14:30:16 +0200 Subject: [PATCH 633/871] Functioning hh.mod (BlueBrain/nmodl#1237) Random collection of fixups to make hh.mod work. --------- Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@b451d3906da4dea7d3d7102aa74ea96aea20a8c3 --- cmake/nmodl/hpc-coding-conventions | 2 +- .../codegen/codegen_neuron_cpp_visitor.cpp | 30 ++++- test/nmodl/transpiler/usecases/CMakeLists.txt | 11 +- .../usecases/hodgkin_huxley/hodhux.mod | 114 ++++++++++++++++++ .../usecases/hodgkin_huxley/simulate.py | 35 ++++++ test/nmodl/transpiler/usecases/references | 2 +- 6 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod create mode 100644 test/nmodl/transpiler/usecases/hodgkin_huxley/simulate.py diff --git a/cmake/nmodl/hpc-coding-conventions b/cmake/nmodl/hpc-coding-conventions index 8f81155978..f8f8d69a66 160000 --- a/cmake/nmodl/hpc-coding-conventions +++ b/cmake/nmodl/hpc-coding-conventions @@ -1 +1 @@ -Subproject commit 8f8115597817365c5c4fa39e217b3ab0b3640cb2 +Subproject commit f8f8d69a66c23978d1c9c5dce62de79466f26e5d diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index dff58c655d..fecc1a18c9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -222,6 +222,16 @@ void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node printer->fmt_line("int ret_{} = 0;", name); } + if (node.is_procedure_block() || node.is_function_block()) { + const auto& parameters = node.get_parameters(); + auto result = std::find_if(parameters.begin(), parameters.end(), [](auto var) { + return var.get()->get_node_name() == "v"; + }); + if (result == parameters.end()) { + printer->fmt_line("auto v = inst.{}[id];", naming::VOLTAGE_UNUSED_VARIABLE); + } + } + print_statement_block(*node.get_statement_block(), false, false); printer->fmt_line("return ret_{};", name); printer->pop_block(); @@ -1236,7 +1246,13 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { print_global_function_common_code(BlockType::Initial); printer->push_block("for (int id = 0; id < nodecount; id++)"); - printer->add_line("auto& _ppvar = _ml_arg->pdata[id];"); + printer->add_multi_line(R"CODE( +int node_id = node_data.nodeindices[id]; +auto* _ppvar = _ml_arg->pdata[id]; +auto v = node_data.node_voltages[node_id]; +)CODE"); + printer->fmt_line("inst.{}[id] = v;", naming::VOLTAGE_UNUSED_VARIABLE); + print_initial_block(info.initial_node); printer->pop_block(); @@ -1367,6 +1383,13 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { ion.name, ion.variable_index(ion_var_name)); } + // assign derivatives of the current as well + else if (ion.is_current_derivative(ion_var_name)) { + printer->fmt_line("_ppvar[{}] = _nrn_mechanism_get_param_handle({}_prop, {});", + i, + ion.name, + ion.variable_index(ion_var_name)); + } //} } } @@ -1392,6 +1415,11 @@ void CodegenNeuronCppVisitor::print_nrn_state() { print_global_function_common_code(BlockType::State); printer->push_block("for (int id = 0; id < nodecount; id++)"); + printer->add_multi_line(R"CODE( +int node_id = node_data.nodeindices[id]; +auto* _ppvar = _ml_arg->pdata[id]; +auto v = node_data.node_voltages[node_id]; +)CODE"); /** * \todo Eigen solver node also emits IonCurVar variable in the functor diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 32f5cb2ad7..c77c00dce8 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,13 +1,14 @@ set(NMODL_USECASE_DIRS cnexp_scalar - # cnexp_array - global_breakpoint - point_process - parameter + cnexp_array func_proc func_proc_pnt + global_breakpoint + hodgkin_huxley nonspecific_current - net_receive) + net_receive + point_process + parameter) file(GLOB NMODL_GOLDEN_REFERENCES "${CMAKE_CURRENT_SOURCE_DIR}/references/*") if(NMODL_GOLDEN_REFERENCES STREQUAL "") diff --git a/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod b/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod new file mode 100644 index 0000000000..969a25c433 --- /dev/null +++ b/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod @@ -0,0 +1,114 @@ +TITLE hodhux.mod squid sodium, potassium, and leak channels + +COMMENT + This is the original Hodgkin-Huxley treatment for the set of sodium, + potassium, and leakage channels found in the squid giant axon membrane. + ("A quantitative description of membrane current and its application + conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).) + Membrane voltage is in absolute mV and has been reversed in polarity + from the original HH convention and shifted to reflect a resting potential + of -65 mV. + Initialize this mechanism to steady-state voltage by calling + rates_gsquid(v) from HOC, then setting m_gsquid=minf_gsquid, etc. + Remember to set celsius=6.3 (or whatever) in your HOC file. + See hh1.hoc for an example of a simulation using this model. + SW Jaslove 6 March, 1992 +ENDCOMMENT + +UNITS { + (mA) = (milliamp) + (mV) = (millivolt) +} + +NEURON { + SUFFIX hodhux + USEION na READ ena WRITE ina + USEION k READ ek WRITE ik + NONSPECIFIC_CURRENT il + RANGE gnabar, gkbar, gl, el + RANGE minf, hinf, ninf, mexp, hexp, nexp +} + +PARAMETER { + v (mV) + celsius = 6.3 (degC) + dt (ms) + gnabar = .12 (mho/cm2) + ena = 50 (mV) + gkbar = .036 (mho/cm2) + ek = -77.5 (mV) + gl = .0003 (mho/cm2) + el = -54.3 (mV) +} + +STATE { + m h n +} + +ASSIGNED { + ina (mA/cm2) + ik (mA/cm2) + il (mA/cm2) + minf hinf ninf mexp hexp nexp +} + +BREAKPOINT { + SOLVE states + ina = gnabar*m*m*m*h*(v - ena) + ik = gkbar*n*n*n*n*(v - ek) + il = gl*(v - el) +} + +UNITSOFF + +INITIAL { + rates(v) + m = minf + h = hinf + n = ninf +} + +PROCEDURE states() { :Computes state variables m, h, and n + rates(v) : at the current v and dt. + m = m + mexp*(minf-m) + h = h + hexp*(hinf-h) + n = n + nexp*(ninf-n) +} + +PROCEDURE rates(v) { :Computes rate and other constants at current v. + :Call once from HOC to initialize inf at resting v. + LOCAL q10, tinc, alpha, beta, sum + : TABLE minf, mexp, hinf, hexp, ninf, nexp DEPEND dt, celsius FROM -100 TO 100 WITH 200 + q10 = 3^((celsius - 6.3)/10) + tinc = -dt * q10 + :"m" sodium activation system + alpha = .1 * vtrap(-(v+40),10) + beta = 4 * exp(-(v+65)/18) + sum = alpha + beta + minf = alpha/sum + mexp = 1 - exp(tinc*sum) + :"h" sodium inactivation system + alpha = .07 * exp(-(v+65)/20) + beta = 1 / (exp(-(v+35)/10) + 1) + sum = alpha + beta + hinf = alpha/sum + hexp = 1 - exp(tinc*sum) + :"n" potassium activation system + alpha = .01*vtrap(-(v+55),10) + beta = .125*exp(-(v+65)/80) + sum = alpha + beta + ninf = alpha/sum + nexp = 1 - exp(tinc*sum) +} + +FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. + if (fabs(x/y) < 1e-6) { + vtrap = y*(1 - x/y/2) + }else{ + vtrap = x/(exp(x/y) - 1) + } +} + +UNITSON + + diff --git a/test/nmodl/transpiler/usecases/hodgkin_huxley/simulate.py b/test/nmodl/transpiler/usecases/hodgkin_huxley/simulate.py new file mode 100644 index 0000000000..3c0aaf0ddc --- /dev/null +++ b/test/nmodl/transpiler/usecases/hodgkin_huxley/simulate.py @@ -0,0 +1,35 @@ +import numpy as np + +from neuron import gui, h +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("hodhux") +ic = h.IClamp(s(0.5)) +ic.delay = 0 +ic.dur = 1e9 +ic.amp = 10 +s.nseg = nseg + +v_hoc = h.Vector().record(s(0.5)._ref_v) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 100.0 * ms +h.run() + +v = np.array(v_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +v_l1 = np.sum(np.abs(v)) / v.size +v_tv = np.sum(np.abs(np.diff(v))) + +# (Nearly) exact values of the L1 and TV (semi-)norms, computed with +# `dt = 0.0002, 0.00005`. +v_l1_exact = 58.673 +v_tv_exact = 1270.8 + +assert abs(v_l1 - v_l1_exact) < 0.004 * v_l1_exact +assert abs(v_l1 - v_l1_exact) < 0.01 * v_tv_exact diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 7fce7545e4..4e574fec1f 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 7fce7545e4e7654c5a6b9e043ceb7630c63f0382 +Subproject commit 4e574fec1f95e3fc24a18c690949a0b93706b175 From cadefe2c011bfdaacee9a1b1fd4c18a2c114316f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 13 May 2024 08:06:23 +0200 Subject: [PATCH 634/871] Implement `net_send`. (BlueBrain/nmodl#1239) NMODL Repo SHA: BlueBrain/nmodl@299c2490987a40c553ec7eaea3a90be103f9e98d --- .../codegen/codegen_neuron_cpp_visitor.cpp | 5 ++-- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/net_send/simulate.py | 25 +++++++++++++++++++ .../transpiler/usecases/net_send/toggle.mod | 20 +++++++++++++++ test/nmodl/transpiler/usecases/references | 2 +- 5 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/net_send/simulate.py create mode 100644 test/nmodl/transpiler/usecases/net_send/toggle.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index fecc1a18c9..7f18b131f8 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1812,10 +1812,11 @@ void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) const auto& point_process = get_variable_name("point_process", /* use_instance */ false); const auto& tqitem = get_variable_name("tqitem", /* use_instance */ false); - printer->fmt_text("net_send(/* tqitem */ &{}, {}, {}.get(), t + ", + printer->fmt_text("net_send(/* tqitem */ &{}, {}, {}.get(), {} + ", tqitem, weight_pointer, - point_process); + point_process, + get_variable_name("t")); print_vector_elements(arguments, ", "); printer->add_text(')'); } diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index c77c00dce8..aa9988e13b 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -7,6 +7,7 @@ set(NMODL_USECASE_DIRS hodgkin_huxley nonspecific_current net_receive + net_send point_process parameter) diff --git a/test/nmodl/transpiler/usecases/net_send/simulate.py b/test/nmodl/transpiler/usecases/net_send/simulate.py new file mode 100644 index 0000000000..76a689d14f --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_send/simulate.py @@ -0,0 +1,25 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.nseg = nseg + +toggle = h.toggle(s(0.5)) + +y_hoc = h.Vector().record(toggle._ref_y) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +t = np.array(t_hoc.as_numpy()) +y = np.array(y_hoc.as_numpy()) + +y_exact = np.array(t >= 2.0, np.float64) + +assert np.all(np.abs(y - y_exact) == 0), f"{y} != {y_exact}, delta: {y - y_exact}" diff --git a/test/nmodl/transpiler/usecases/net_send/toggle.mod b/test/nmodl/transpiler/usecases/net_send/toggle.mod new file mode 100644 index 0000000000..c3d22a4a36 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_send/toggle.mod @@ -0,0 +1,20 @@ +NEURON { + POINT_PROCESS toggle + RANGE y +} + +UNITS { +} + +ASSIGNED { + y +} + +INITIAL { + y = 0 + net_send(2.0, 1) +} + +NET_RECEIVE(w) { + y = 1 +} diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references index 4e574fec1f..553af6be6a 160000 --- a/test/nmodl/transpiler/usecases/references +++ b/test/nmodl/transpiler/usecases/references @@ -1 +1 @@ -Subproject commit 4e574fec1f95e3fc24a18c690949a0b93706b175 +Subproject commit 553af6be6aa756576ef51df2a0ada6c5824c7a06 From 529f703ca09bdde0af38c41776e162858d2f43c0 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 13 May 2024 15:51:18 +0200 Subject: [PATCH 635/871] Move common codegen code to parent class `CodegenCppVisitor` (BlueBrain/nmodl#1251) NMODL Repo SHA: BlueBrain/nmodl@41d558aa17ca89cb26c217928a18a13031ecb6dd --- .../codegen_coreneuron_cpp_visitor.cpp | 234 ------------------ .../codegen_coreneuron_cpp_visitor.hpp | 65 ----- src/nmodl/codegen/codegen_cpp_visitor.cpp | 230 +++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 66 +++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 42 ---- 5 files changed, 296 insertions(+), 341 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 3ac2bbf8b4..38fab6aee5 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -401,240 +401,6 @@ void CodegenCoreneuronCppVisitor::print_function_prototypes() { } -static const TableStatement* get_table_statement(const ast::Block& node) { - // TableStatementVisitor v; - - const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); - - if (table_statements.size() != 1) { - auto message = fmt::format("One table statement expected in {} found {}", - node.get_node_name(), - table_statements.size()); - throw std::runtime_error(message); - } - return dynamic_cast(table_statements.front().get()); -} - - -std::tuple CodegenCoreneuronCppVisitor::check_if_var_is_array(const std::string& name) { - auto symbol = program_symtab->lookup_in_scope(name); - if (!symbol) { - throw std::runtime_error( - fmt::format("CodegenCoreneuronCppVisitor:: {} not found in symbol table!", name)); - } - if (symbol->is_array()) { - return {true, symbol->get_length()}; - } else { - return {false, 0}; - } -} - - -void CodegenCoreneuronCppVisitor::print_table_check_function(const Block& node) { - auto statement = get_table_statement(node); - auto table_variables = statement->get_table_vars(); - auto depend_variables = statement->get_depend_vars(); - const auto& from = statement->get_from(); - const auto& to = statement->get_to(); - auto name = node.get_node_name(); - auto internal_params = internal_method_parameters(); - auto with = statement->get_with()->eval(); - auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); - auto tmin_name = get_variable_name("tmin_" + name); - auto mfac_name = get_variable_name("mfac_" + name); - auto float_type = default_float_data_type(); - - printer->add_newline(2); - printer->fmt_push_block("void check_{}({})", - method_name(name), - get_parameter_str(internal_params)); - { - printer->fmt_push_block("if ({} == 0)", use_table_var); - printer->add_line("return;"); - printer->pop_block(); - - printer->add_line("static bool make_table = true;"); - for (const auto& variable: depend_variables) { - printer->fmt_line("static {} save_{};", float_type, variable->get_node_name()); - } - - for (const auto& variable: depend_variables) { - const auto& var_name = variable->get_node_name(); - const auto& instance_name = get_variable_name(var_name); - printer->fmt_push_block("if (save_{} != {})", var_name, instance_name); - printer->add_line("make_table = true;"); - printer->pop_block(); - } - - printer->push_block("if (make_table)"); - { - printer->add_line("make_table = false;"); - - printer->add_indent(); - printer->add_text(tmin_name, " = "); - from->accept(*this); - printer->add_text(';'); - printer->add_newline(); - - printer->add_indent(); - printer->add_text("double tmax = "); - to->accept(*this); - printer->add_text(';'); - printer->add_newline(); - - - printer->fmt_line("double dx = (tmax-{}) / {}.;", tmin_name, with); - printer->fmt_line("{} = 1./dx;", mfac_name); - - printer->fmt_line("double x = {};", tmin_name); - printer->fmt_push_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); - auto function = method_name("f_" + name); - if (node.is_procedure_block()) { - printer->fmt_line("{}({}, x);", function, internal_method_arguments()); - for (const auto& variable: table_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - auto table_name = get_variable_name("t_" + var_name); - auto [is_array, array_length] = check_if_var_is_array(var_name); - if (is_array) { - for (int j = 0; j < array_length; j++) { - printer->fmt_line( - "{}[{}][i] = {}[{}];", table_name, j, instance_name, j); - } - } else { - printer->fmt_line("{}[i] = {};", table_name, instance_name); - } - } - } else { - auto table_name = get_variable_name("t_" + name); - printer->fmt_line("{}[i] = {}({}, x);", - table_name, - function, - internal_method_arguments()); - } - printer->pop_block(); - - for (const auto& variable: depend_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - printer->fmt_line("save_{} = {};", var_name, instance_name); - } - } - printer->pop_block(); - } - printer->pop_block(); -} - - -void CodegenCoreneuronCppVisitor::print_table_replacement_function(const ast::Block& node) { - auto name = node.get_node_name(); - auto statement = get_table_statement(node); - auto table_variables = statement->get_table_vars(); - auto with = statement->get_with()->eval(); - auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); - auto tmin_name = get_variable_name("tmin_" + name); - auto mfac_name = get_variable_name("mfac_" + name); - auto function_name = method_name("f_" + name); - - printer->add_newline(2); - print_function_declaration(node, name); - printer->push_block(); - { - const auto& params = node.get_parameters(); - printer->fmt_push_block("if ({} == 0)", use_table_var); - if (node.is_procedure_block()) { - printer->fmt_line("{}({}, {});", - function_name, - internal_method_arguments(), - params[0].get()->get_node_name()); - printer->add_line("return 0;"); - } else { - printer->fmt_line("return {}({}, {});", - function_name, - internal_method_arguments(), - params[0].get()->get_node_name()); - } - printer->pop_block(); - - printer->fmt_line("double xi = {} * ({} - {});", - mfac_name, - params[0].get()->get_node_name(), - tmin_name); - printer->push_block("if (isnan(xi))"); - if (node.is_procedure_block()) { - for (const auto& var: table_variables) { - auto var_name = get_variable_name(var->get_node_name()); - auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); - if (is_array) { - for (int j = 0; j < array_length; j++) { - printer->fmt_line("{}[{}] = xi;", var_name, j); - } - } else { - printer->fmt_line("{} = xi;", var_name); - } - } - printer->add_line("return 0;"); - } else { - printer->add_line("return xi;"); - } - printer->pop_block(); - - printer->fmt_push_block("if (xi <= 0. || xi >= {}.)", with); - printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); - if (node.is_procedure_block()) { - for (const auto& variable: table_variables) { - auto var_name = variable->get_node_name(); - auto instance_name = get_variable_name(var_name); - auto table_name = get_variable_name("t_" + var_name); - auto [is_array, array_length] = check_if_var_is_array(var_name); - if (is_array) { - for (int j = 0; j < array_length; j++) { - printer->fmt_line( - "{}[{}] = {}[{}][index];", instance_name, j, table_name, j); - } - } else { - printer->fmt_line("{} = {}[index];", instance_name, table_name); - } - } - printer->add_line("return 0;"); - } else { - auto table_name = get_variable_name("t_" + name); - printer->fmt_line("return {}[index];", table_name); - } - printer->pop_block(); - - printer->add_line("int i = int(xi);"); - printer->add_line("double theta = xi - double(i);"); - if (node.is_procedure_block()) { - for (const auto& var: table_variables) { - auto var_name = var->get_node_name(); - auto instance_name = get_variable_name(var_name); - auto table_name = get_variable_name("t_" + var_name); - auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); - if (is_array) { - for (size_t j = 0; j < array_length; j++) { - printer->fmt_line( - "{0}[{1}] = {2}[{1}][i] + theta*({2}[{1}][i+1]-{2}[{1}][i]);", - instance_name, - j, - table_name); - } - } else { - printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", - instance_name, - table_name); - } - } - printer->add_line("return 0;"); - } else { - auto table_name = get_variable_name("t_" + name); - printer->fmt_line("return {0}[i] + theta * ({0}[i+1] - {0}[i]);", table_name); - } - } - printer->pop_block(); -} - - void CodegenCoreneuronCppVisitor::print_check_table_thread_function() { if (info.table_count == 0) { return; diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 21b03c9334..cae873bcbb 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -352,28 +352,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_function_prototypes() override; - /** - * Check if the given name exist in the symbol - * \return \c return a tuple if variable - * is an array otherwise - */ - std::tuple check_if_var_is_array(const std::string& name); - - - /** - * Print \c check\_function() for functions or procedure using table - * \param node The AST node representing a function or procedure block - */ - void print_table_check_function(const ast::Block& node); - - - /** - * Print replacement function for function or procedure using table - * \param node The AST node representing a function or procedure block - */ - void print_table_replacement_function(const ast::Block& node); - - /** * Print check_table functions */ @@ -1082,15 +1060,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual void visit_watch_statement(const ast::WatchStatement& node) override; - /** - * Print prototype declarations of functions or procedures - * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) - * \param node The AST node representing the function or procedure block - * \param name A user defined name for the function - */ - template - void print_function_declaration(const T& node, const std::string& name); - public: /****************************************************************************************/ @@ -1114,40 +1083,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { }; -/** - * \details If there is an argument with name (say alpha) same as range variable (say alpha), - * we want to avoid it being printed as instance->alpha. And hence we disable variable - * name lookup during prototype declaration. Note that the name of procedure can be - * different in case of table statement. - */ -template -void CodegenCoreneuronCppVisitor::print_function_declaration(const T& node, - const std::string& name) { - enable_variable_name_lookup = false; - auto type = default_float_data_type(); - - // internal and user provided arguments - auto internal_params = internal_method_parameters(); - const auto& params = node.get_parameters(); - for (const auto& param: params) { - internal_params.emplace_back("", type, "", param.get()->get_node_name()); - } - - // procedures have "int" return type by default - const char* return_type = "int"; - if (node.is_function_block()) { - return_type = default_float_data_type(); - } - - printer->add_indent(); - printer->fmt_text("inline {} {}({})", - return_type, - method_name(name), - get_parameter_str(internal_params)); - - enable_variable_name_lookup = true; -} - /** \} */ // end of codegen_backends } // namespace codegen diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index f33aa90611..15ca3d7699 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1175,5 +1175,235 @@ void CodegenCppVisitor::visit_program(const Program& node) { print_codegen_routines(); } + +void CodegenCppVisitor::print_table_replacement_function(const ast::Block& node) { + auto name = node.get_node_name(); + auto statement = get_table_statement(node); + auto table_variables = statement->get_table_vars(); + auto with = statement->get_with()->eval(); + auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); + auto tmin_name = get_variable_name("tmin_" + name); + auto mfac_name = get_variable_name("mfac_" + name); + auto function_name = method_name("f_" + name); + + printer->add_newline(2); + print_function_declaration(node, name); + printer->push_block(); + { + const auto& params = node.get_parameters(); + printer->fmt_push_block("if ({} == 0)", use_table_var); + if (node.is_procedure_block()) { + printer->fmt_line("{}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name()); + printer->add_line("return 0;"); + } else { + printer->fmt_line("return {}({}, {});", + function_name, + internal_method_arguments(), + params[0].get()->get_node_name()); + } + printer->pop_block(); + + printer->fmt_line("double xi = {} * ({} - {});", + mfac_name, + params[0].get()->get_node_name(), + tmin_name); + printer->push_block("if (isnan(xi))"); + if (node.is_procedure_block()) { + for (const auto& var: table_variables) { + auto var_name = get_variable_name(var->get_node_name()); + auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line("{}[{}] = xi;", var_name, j); + } + } else { + printer->fmt_line("{} = xi;", var_name); + } + } + printer->add_line("return 0;"); + } else { + printer->add_line("return xi;"); + } + printer->pop_block(); + + printer->fmt_push_block("if (xi <= 0. || xi >= {}.)", with); + printer->fmt_line("int index = (xi <= 0.) ? 0 : {};", with); + if (node.is_procedure_block()) { + for (const auto& variable: table_variables) { + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var_name); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line( + "{}[{}] = {}[{}][index];", instance_name, j, table_name, j); + } + } else { + printer->fmt_line("{} = {}[index];", instance_name, table_name); + } + } + printer->add_line("return 0;"); + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("return {}[index];", table_name); + } + printer->pop_block(); + + printer->add_line("int i = int(xi);"); + printer->add_line("double theta = xi - double(i);"); + if (node.is_procedure_block()) { + for (const auto& var: table_variables) { + auto var_name = var->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var->get_node_name()); + if (is_array) { + for (size_t j = 0; j < array_length; j++) { + printer->fmt_line( + "{0}[{1}] = {2}[{1}][i] + theta*({2}[{1}][i+1]-{2}[{1}][i]);", + instance_name, + j, + table_name); + } + } else { + printer->fmt_line("{0} = {1}[i] + theta*({1}[i+1]-{1}[i]);", + instance_name, + table_name); + } + } + printer->add_line("return 0;"); + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("return {0}[i] + theta * ({0}[i+1] - {0}[i]);", table_name); + } + } + printer->pop_block(); +} + + +void CodegenCppVisitor::print_table_check_function(const Block& node) { + auto statement = get_table_statement(node); + auto table_variables = statement->get_table_vars(); + auto depend_variables = statement->get_depend_vars(); + const auto& from = statement->get_from(); + const auto& to = statement->get_to(); + auto name = node.get_node_name(); + auto internal_params = internal_method_parameters(); + auto with = statement->get_with()->eval(); + auto use_table_var = get_variable_name(naming::USE_TABLE_VARIABLE); + auto tmin_name = get_variable_name("tmin_" + name); + auto mfac_name = get_variable_name("mfac_" + name); + auto float_type = default_float_data_type(); + + printer->add_newline(2); + printer->fmt_push_block("void check_{}({})", + method_name(name), + get_parameter_str(internal_params)); + { + printer->fmt_push_block("if ({} == 0)", use_table_var); + printer->add_line("return;"); + printer->pop_block(); + + printer->add_line("static bool make_table = true;"); + for (const auto& variable: depend_variables) { + printer->fmt_line("static {} save_{};", float_type, variable->get_node_name()); + } + + for (const auto& variable: depend_variables) { + const auto& var_name = variable->get_node_name(); + const auto& instance_name = get_variable_name(var_name); + printer->fmt_push_block("if (save_{} != {})", var_name, instance_name); + printer->add_line("make_table = true;"); + printer->pop_block(); + } + + printer->push_block("if (make_table)"); + { + printer->add_line("make_table = false;"); + + printer->add_indent(); + printer->add_text(tmin_name, " = "); + from->accept(*this); + printer->add_text(';'); + printer->add_newline(); + + printer->add_indent(); + printer->add_text("double tmax = "); + to->accept(*this); + printer->add_text(';'); + printer->add_newline(); + + + printer->fmt_line("double dx = (tmax-{}) / {}.;", tmin_name, with); + printer->fmt_line("{} = 1./dx;", mfac_name); + + printer->fmt_line("double x = {};", tmin_name); + printer->fmt_push_block("for (std::size_t i = 0; i < {}; x += dx, i++)", with + 1); + auto function = method_name("f_" + name); + if (node.is_procedure_block()) { + printer->fmt_line("{}({}, x);", function, internal_method_arguments()); + for (const auto& variable: table_variables) { + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + auto table_name = get_variable_name("t_" + var_name); + auto [is_array, array_length] = check_if_var_is_array(var_name); + if (is_array) { + for (int j = 0; j < array_length; j++) { + printer->fmt_line( + "{}[{}][i] = {}[{}];", table_name, j, instance_name, j); + } + } else { + printer->fmt_line("{}[i] = {};", table_name, instance_name); + } + } + } else { + auto table_name = get_variable_name("t_" + name); + printer->fmt_line("{}[i] = {}({}, x);", + table_name, + function, + internal_method_arguments()); + } + printer->pop_block(); + + for (const auto& variable: depend_variables) { + auto var_name = variable->get_node_name(); + auto instance_name = get_variable_name(var_name); + printer->fmt_line("save_{} = {};", var_name, instance_name); + } + } + printer->pop_block(); + } + printer->pop_block(); +} + +const ast::TableStatement* CodegenCppVisitor::get_table_statement(const ast::Block& node) { + const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); + + if (table_statements.size() != 1) { + auto message = fmt::format("One table statement expected in {} found {}", + node.get_node_name(), + table_statements.size()); + throw std::runtime_error(message); + } + return dynamic_cast(table_statements.front().get()); +} + + +std::tuple CodegenCppVisitor::check_if_var_is_array(const std::string& name) { + auto symbol = program_symtab->lookup_in_scope(name); + if (!symbol) { + throw std::runtime_error( + fmt::format("CodegenCppVisitor:: {} not found in symbol table!", name)); + } + if (symbol->is_array()) { + return {true, symbol->get_length()}; + } else { + return {false, 0}; + } +} } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 19883cc2e7..86b87f488e 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -921,6 +921,13 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return name + "_" + info.mod_suffix; } + /** + * Check if the given name exist in the symbol + * \return \c return a tuple if variable + * is an array otherwise + */ + std::tuple check_if_var_is_array(const std::string& name); + /** * Creates a temporary symbol @@ -1343,6 +1350,32 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * be included in the struct declaration. */ virtual void print_mechanism_range_var_structure(bool print_initializers) = 0; + + + /** + * Print replacement function for function or procedure using table + * \param node The AST node representing a function or procedure block + */ + void print_table_replacement_function(const ast::Block&); + + + /** + * Print \c check\_function() for functions or procedure using table + * \param node The AST node representing a function or procedure block + */ + void print_table_check_function(const ast::Block&); + + + const ast::TableStatement* get_table_statement(const ast::Block&); + + /** + * Print prototype declarations of functions or procedures + * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) + * \param node The AST node representing the function or procedure block + * \param name A user defined name for the function + */ + template + void print_function_declaration(const T& node, const std::string& name); }; /* Templated functions need to be defined in header file */ @@ -1360,6 +1393,39 @@ void CodegenCppVisitor::print_vector_elements(const std::vector& elements, } +/** + * \details If there is an argument with name (say alpha) same as range variable (say alpha), + * we want to avoid it being printed as instance->alpha. And hence we disable variable + * name lookup during prototype declaration. Note that the name of procedure can be + * different in case of table statement. + */ +template +void CodegenCppVisitor::print_function_declaration(const T& node, const std::string& name) { + enable_variable_name_lookup = false; + auto type = default_float_data_type(); + + // internal and user provided arguments + auto internal_params = internal_method_parameters(); + const auto& params = node.get_parameters(); + for (const auto& param: params) { + internal_params.emplace_back("", type, "", param.get()->get_node_name()); + } + + // procedures have "int" return type by default + const char* return_type = "int"; + if (node.is_function_block()) { + return_type = default_float_data_type(); + } + + printer->add_indent(); + printer->fmt_text("inline {} {}({})", + return_type, + method_name(name), + get_parameter_str(internal_params)); + + enable_variable_name_lookup = true; +} + /** \} */ // end of codegen_backends } // namespace codegen diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index f8b9aa5e24..a68bee037e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -655,14 +655,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { virtual void visit_watch_statement(const ast::WatchStatement& node) override; - /** - * Print prototype declarations of functions or procedures - * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) - * \param node The AST node representing the function or procedure block - * \param name A user defined name for the function - */ - template - void print_function_declaration(const T& node, const std::string& name); public: @@ -689,40 +681,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { }; -/** - * \details If there is an argument with name (say alpha) same as range variable (say alpha), - * we want to avoid it being printed as instance->alpha. And hence we disable variable - * name lookup during prototype declaration. Note that the name of procedure can be - * different in case of table statement. - */ -template -void CodegenNeuronCppVisitor::print_function_declaration(const T& node, const std::string& name) { - enable_variable_name_lookup = false; - auto type = default_float_data_type(); - - // internal and user provided arguments - auto internal_params = internal_method_parameters(); - const auto& params = node.get_parameters(); - for (const auto& param: params) { - internal_params.emplace_back("", type, "", param.get()->get_node_name()); - } - - // procedures have "int" return type by default - const char* return_type = "int"; - if (node.is_function_block()) { - return_type = default_float_data_type(); - } - - /// TODO: Edit for NEURON - printer->add_indent(); - printer->fmt_text("inline {} {}({})", - return_type, - method_name(name), - get_parameter_str(internal_params)); - - enable_variable_name_lookup = true; -} - /** \} */ // end of codegen_backends } // namespace codegen From 57d562c8c6003373c7eb17a6a6a61d22e15b5593 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 14 May 2024 11:05:29 +0200 Subject: [PATCH 636/871] Rework references. (BlueBrain/nmodl#1254) The idea is to generate the references, but not check that there are no differences. Doing so allows us to push the changes automatically to `nmodl-references`. NMODL Repo SHA: BlueBrain/nmodl@0e69948f9a48eabc3b5eb5f149b879792d512d66 --- test/nmodl/transpiler/usecases/CMakeLists.txt | 31 ------------------- .../usecases/cnexp_non_trivial/demo.py | 24 -------------- .../usecases/cnexp_non_trivial/simulate.py | 27 ---------------- .../transpiler/usecases/comments/simulate.py | 1 - .../usecases/generate_references.sh | 15 +++++---- test/nmodl/transpiler/usecases/references | 1 - 6 files changed, 7 insertions(+), 92 deletions(-) delete mode 100644 test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py delete mode 100644 test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py delete mode 100644 test/nmodl/transpiler/usecases/comments/simulate.py delete mode 160000 test/nmodl/transpiler/usecases/references diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index aa9988e13b..4897b16385 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -11,39 +11,8 @@ set(NMODL_USECASE_DIRS point_process parameter) -file(GLOB NMODL_GOLDEN_REFERENCES "${CMAKE_CURRENT_SOURCE_DIR}/references/*") -if(NMODL_GOLDEN_REFERENCES STREQUAL "") - cpp_cc_init_git_submodule(${CMAKE_CURRENT_SOURCE_DIR}/references) -endif() -unset(NMODL_GOLDEN_REFERNCES) - -add_custom_target(generate_references) - foreach(usecase ${NMODL_USECASE_DIRS}) - # Non-existant dependencies are a way of unconditionally running commands in CMake. - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge) - message( - FATAL_ERROR - "The file: '${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge' must not exist." - ) - endif() - add_test(NAME usecase_${usecase} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/run_test.sh ${CMAKE_BINARY_DIR}/bin/nmodl ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) - - add_test(NAME golden_${usecase} - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/check_references.sh ${CMAKE_BINARY_DIR}/bin/nmodl - ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) - - add_custom_command( - OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge - DEPENDS nmodl - COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/generate_references.sh ${CMAKE_BINARY_DIR}/bin/nmodl - ${CMAKE_CURRENT_SOURCE_DIR}/${usecase}) - - add_custom_target( - generate_${usecase} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/references/${usecase}/_does_not_exist_weiohbge) - add_dependencies(generate_references generate_${usecase}) endforeach() diff --git a/test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py b/test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py deleted file mode 100644 index ca316ce4ae..0000000000 --- a/test/nmodl/transpiler/usecases/cnexp_non_trivial/demo.py +++ /dev/null @@ -1,24 +0,0 @@ -import numpy as np -import matplotlib.pyplot as plt - - -t_end = 4.0 - -n = 10000 -x_approx = np.empty(n) -x_approx[0] = 0.1 - -x_exact = np.empty(n) -x_exact[0] = 0.1 - -dt = t_end / n -t = np.linspace(0.0, t_end, n) - -for i in range(1, n): - x_approx[i] = 1.4142135623730951 * np.sqrt(dt + 0.5 * x_approx[i - 1] ** 2.0) - x_exact[i] = x_exact[i - 1] + dt * 1 / x_exact[i - 1] - -plt.plot(t, x_approx) -plt.plot(t, x_exact) - -plt.show() diff --git a/test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py b/test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py deleted file mode 100644 index 3245670ca9..0000000000 --- a/test/nmodl/transpiler/usecases/cnexp_non_trivial/simulate.py +++ /dev/null @@ -1,27 +0,0 @@ -import numpy as np - -from neuron import h, gui -from neuron.units import ms - -nseg = 1 - -s = h.Section() -s.insert("leonhard") -s.nseg = nseg - -x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) -t_hoc = h.Vector().record(h._ref_t) - -h.stdinit() -h.tstop = 5.0 * ms -h.run() - -x = np.array(x_hoc.as_numpy()) -t = np.array(t_hoc.as_numpy()) - -x0 = 42.0 -x_exact = 42.0 * np.exp(-t) -rel_err = np.abs(x - x_exact) / x_exact - -assert np.all(rel_err < 1e-12) -print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/comments/simulate.py b/test/nmodl/transpiler/usecases/comments/simulate.py deleted file mode 100644 index 8b13789179..0000000000 --- a/test/nmodl/transpiler/usecases/comments/simulate.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/nmodl/transpiler/usecases/generate_references.sh b/test/nmodl/transpiler/usecases/generate_references.sh index 5a41ef9661..7a99b41933 100755 --- a/test/nmodl/transpiler/usecases/generate_references.sh +++ b/test/nmodl/transpiler/usecases/generate_references.sh @@ -1,17 +1,16 @@ #! /usr/bin/env bash set -eu -nmodl="$1" -usecase_dir="$2" - -if [[ $# -eq 3 ]] +if [[ $# -ne 3 ]] then - output_dir="$3" -else - script_dir="$(cd "$(dirname "$0")"; pwd -P)" - output_dir="${script_dir}/references/$(basename "$2")" + echo "Usage: $0 NMODL USECASE_DIR OUTPUT_DIR" + exit -1 fi +nmodl="$1" +usecase_dir="$2" +output_dir="$3" + function sanitize() { for f in "${1}"/*.cpp do diff --git a/test/nmodl/transpiler/usecases/references b/test/nmodl/transpiler/usecases/references deleted file mode 160000 index 553af6be6a..0000000000 --- a/test/nmodl/transpiler/usecases/references +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 553af6be6aa756576ef51df2a0ada6c5824c7a06 From c296508e097361ca20360b99d2b7fe381367cb0e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 14 May 2024 16:27:15 +0200 Subject: [PATCH 637/871] Simulate spike running a section. (BlueBrain/nmodl#1261) This adds a test that consists of the following: 1. Two sections. 2. One current clamp at he beginning of the first section. 3. A synapse connecting the ends of the sections. We record the voltage at a few locations along the two sections and assert (and optionally plot) that the values haven't changed a lot. NMODL Repo SHA: BlueBrain/nmodl@81262949f959e0bd2f5b338dc94dc63ca23eeaa6 --- .../usecases/spike_travel/expsyn2.mod | 42 +++++++ .../usecases/spike_travel/hodhux.mod | 114 ++++++++++++++++++ .../usecases/spike_travel/simulate.py | 104 ++++++++++++++++ 3 files changed, 260 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod create mode 100644 test/nmodl/transpiler/usecases/spike_travel/hodhux.mod create mode 100644 test/nmodl/transpiler/usecases/spike_travel/simulate.py diff --git a/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod b/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod new file mode 100644 index 0000000000..ff761d36e6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod @@ -0,0 +1,42 @@ +NEURON { + POINT_PROCESS ExpSyn2 + RANGE tau, e, i + NONSPECIFIC_CURRENT i +} + +UNITS { + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) +} + +PARAMETER { + tau = 0.1 (ms) <1e-9,1e9> + e = 0 (mV) +} + +ASSIGNED { + v (mV) + i (nA) +} + +STATE { + g (uS) +} + +INITIAL { + g=0 +} + +BREAKPOINT { + SOLVE state METHOD cnexp + i = g*(v - e) +} + +DERIVATIVE state { + g' = -g/tau +} + +NET_RECEIVE(weight (uS)) { + g = g + weight +} diff --git a/test/nmodl/transpiler/usecases/spike_travel/hodhux.mod b/test/nmodl/transpiler/usecases/spike_travel/hodhux.mod new file mode 100644 index 0000000000..969a25c433 --- /dev/null +++ b/test/nmodl/transpiler/usecases/spike_travel/hodhux.mod @@ -0,0 +1,114 @@ +TITLE hodhux.mod squid sodium, potassium, and leak channels + +COMMENT + This is the original Hodgkin-Huxley treatment for the set of sodium, + potassium, and leakage channels found in the squid giant axon membrane. + ("A quantitative description of membrane current and its application + conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).) + Membrane voltage is in absolute mV and has been reversed in polarity + from the original HH convention and shifted to reflect a resting potential + of -65 mV. + Initialize this mechanism to steady-state voltage by calling + rates_gsquid(v) from HOC, then setting m_gsquid=minf_gsquid, etc. + Remember to set celsius=6.3 (or whatever) in your HOC file. + See hh1.hoc for an example of a simulation using this model. + SW Jaslove 6 March, 1992 +ENDCOMMENT + +UNITS { + (mA) = (milliamp) + (mV) = (millivolt) +} + +NEURON { + SUFFIX hodhux + USEION na READ ena WRITE ina + USEION k READ ek WRITE ik + NONSPECIFIC_CURRENT il + RANGE gnabar, gkbar, gl, el + RANGE minf, hinf, ninf, mexp, hexp, nexp +} + +PARAMETER { + v (mV) + celsius = 6.3 (degC) + dt (ms) + gnabar = .12 (mho/cm2) + ena = 50 (mV) + gkbar = .036 (mho/cm2) + ek = -77.5 (mV) + gl = .0003 (mho/cm2) + el = -54.3 (mV) +} + +STATE { + m h n +} + +ASSIGNED { + ina (mA/cm2) + ik (mA/cm2) + il (mA/cm2) + minf hinf ninf mexp hexp nexp +} + +BREAKPOINT { + SOLVE states + ina = gnabar*m*m*m*h*(v - ena) + ik = gkbar*n*n*n*n*(v - ek) + il = gl*(v - el) +} + +UNITSOFF + +INITIAL { + rates(v) + m = minf + h = hinf + n = ninf +} + +PROCEDURE states() { :Computes state variables m, h, and n + rates(v) : at the current v and dt. + m = m + mexp*(minf-m) + h = h + hexp*(hinf-h) + n = n + nexp*(ninf-n) +} + +PROCEDURE rates(v) { :Computes rate and other constants at current v. + :Call once from HOC to initialize inf at resting v. + LOCAL q10, tinc, alpha, beta, sum + : TABLE minf, mexp, hinf, hexp, ninf, nexp DEPEND dt, celsius FROM -100 TO 100 WITH 200 + q10 = 3^((celsius - 6.3)/10) + tinc = -dt * q10 + :"m" sodium activation system + alpha = .1 * vtrap(-(v+40),10) + beta = 4 * exp(-(v+65)/18) + sum = alpha + beta + minf = alpha/sum + mexp = 1 - exp(tinc*sum) + :"h" sodium inactivation system + alpha = .07 * exp(-(v+65)/20) + beta = 1 / (exp(-(v+35)/10) + 1) + sum = alpha + beta + hinf = alpha/sum + hexp = 1 - exp(tinc*sum) + :"n" potassium activation system + alpha = .01*vtrap(-(v+55),10) + beta = .125*exp(-(v+65)/80) + sum = alpha + beta + ninf = alpha/sum + nexp = 1 - exp(tinc*sum) +} + +FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. + if (fabs(x/y) < 1e-6) { + vtrap = y*(1 - x/y/2) + }else{ + vtrap = x/(exp(x/y) - 1) + } +} + +UNITSON + + diff --git a/test/nmodl/transpiler/usecases/spike_travel/simulate.py b/test/nmodl/transpiler/usecases/spike_travel/simulate.py new file mode 100644 index 0000000000..ee6a7f44da --- /dev/null +++ b/test/nmodl/transpiler/usecases/spike_travel/simulate.py @@ -0,0 +1,104 @@ +import numpy as np + +from neuron import gui, h +from neuron.units import ms + + +def simulate(): + nseg = 100 + + s0 = h.Section() + s0.nseg = nseg + s0.L = 300000 + s0.insert("hodhux") + ic = h.IClamp(s0(0.001)) + ic.delay = 0 + ic.dur = 1e9 + ic.amp = 1000 + + s1 = h.Section() + s1.nseg = nseg + s1.L = 300000 + s1.insert("hodhux") + + syn = h.ExpSyn2(s1(0.99)) + + threshold = 10 + delay = 10 + weight = 1000 + nc = h.NetCon(s0(0.9)._ref_v, syn, threshold, delay, weight) + + xs = 0.2 * np.arange(5) + 0.1 + + v0_hoc = [h.Vector().record(s0(x)._ref_v) for x in xs] + v1_hoc = [h.Vector().record(s1(x)._ref_v) for x in xs] + t_hoc = h.Vector().record(h._ref_t) + + h.stdinit() + h.tstop = 100.0 * ms + h.run() + + v0 = [np.array(vv.as_numpy()) for vv in v0_hoc] + v1 = [np.array(vv.as_numpy()) for vv in v1_hoc] + t = np.array(t_hoc.as_numpy()) + + return t, xs, v0, v1 + + +def tv(v): + return np.sum(np.abs(np.diff(v))) + + +def l1(v): + return np.sum(np.abs(v)) / v.size + + +def arg_minmax(t, v): + tmin = t[np.argmin(v)] + tmax = t[np.argmax(v)] + + return tmin, tmax + + +def assert_close(approx, expected, rtol): + assert ( + np.abs(approx - expected) < rtol * expected + ), f"{approx=}, {expected=}, delta = {approx - expected}" + + +def check_single_solution(t, v, ref): + assert_close(tv(v), ref["tv"], 0.01) + assert_close(l1(v), ref["l1"], 0.01) + + for approx, expected in zip(arg_minmax(t, v), ref["arg_minmax"]): + assert_close(approx, expected, 0.1) + + +def check_solution(t, v0, v1): + v0_ref = {"tv": 227.68, "l1": 64.606, "arg_minmax": (8.1130, 4.8256)} + check_single_solution(t, v0, v0_ref) + + v1_ref = {"tv": 227.92, "l1": 64.629, "arg_minmax": (59.6799, 56.3917)} + check_single_solution(t, v1, v1_ref) + + +def plot_solution(t, v0, v1): + import matplotlib.pyplot as plt + + for xx, vv in zip(x, v0): + plt.plot(t, vv, linewidth=3, label=f"v0({xx:.1f})") + + for xx, vv in zip(x, v1): + plt.plot(t, vv, "--", linewidth=3, label=f"v1({xx:.1f})") + + plt.legend() + plt.xlabel("Time") + plt.ylabel("Voltage") + plt.show() + + +if __name__ == "__main__": + t, x, v0, v1 = simulate() + check_solution(t, v0[0], v1[0]) + + # plot_solution(t, v0, v1) From 034abb62d6d6b941aa4526804ab982ae895dffdc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 15 May 2024 08:56:34 +0200 Subject: [PATCH 638/871] Tests for voltage dependency function/procedures. (BlueBrain/nmodl#1253) Adds tests the demonstrates how the voltage `v` behaves when passed to functions or procedures. NMODL Repo SHA: BlueBrain/nmodl@49da6863667a32a6f6e8b5303c44ccc0bca3d502 --- .../usecases/func_proc/func_proc.mod | 20 +++++++++++++ .../transpiler/usecases/func_proc/simulate.py | 29 +++++++++++++++++++ test/nmodl/transpiler/usecases/run_test.sh | 3 ++ 3 files changed, 52 insertions(+) diff --git a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod index 0f95d04c29..0009928310 100644 --- a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod +++ b/test/nmodl/transpiler/usecases/func_proc/func_proc.mod @@ -24,6 +24,26 @@ PROCEDURE set_a_x() { a = x } +FUNCTION v_plus_a(a) { + v_plus_a = v + a +} + +PROCEDURE set_x_v() { + x = v +} + +FUNCTION just_v(v) { + just_v = v +} + +PROCEDURE set_x_just_v() { + x = just_v(v) +} + +PROCEDURE set_x_just_vv(v) { + x = just_v(v) +} + INITIAL { set_a_x() } diff --git a/test/nmodl/transpiler/usecases/func_proc/simulate.py b/test/nmodl/transpiler/usecases/func_proc/simulate.py index 69162fef4c..c27aa45402 100644 --- a/test/nmodl/transpiler/usecases/func_proc/simulate.py +++ b/test/nmodl/transpiler/usecases/func_proc/simulate.py @@ -21,3 +21,32 @@ for x, value in zip(coords, values): assert s(x).test_func_proc.x_plus_a(100.0) == 100.0 + value + + +x = coords[0] +v0 = -42.0 +h.finitialize(v0) + +# Check `x = v`. +s(x).test_func_proc.set_x_v() +actual = s(x).test_func_proc.x +expected = v0 + +assert actual == expected, f"{actual} == {expected}" + +# Check `f(v)`. +expected = 42.0 +actual = s(x).test_func_proc.just_v(expected) +assert actual == expected, f"{actual} == {expected}" + +# Check g = lambda: f(v) +s(x).test_func_proc.set_x_just_v() +actual = s(x).test_func_proc.x +expected = v0 +assert actual == expected, f"{actual} == {expected}" + +# Check g = lambda v: f(v) +expected = 42.0 +s(x).test_func_proc.set_x_just_vv(expected) +actual = s(x).test_func_proc.x +assert actual == expected, f"{actual} == {expected}" diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index ff18c6a34e..85214086c9 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -13,11 +13,14 @@ usecase_dir="$2" pushd "${usecase_dir}" # NRN + nocmodl +echo "-- Running NRN+nocmodl ------" rm -r "${output_dir}" tmp || true nrnivmodl "$(uname -m)/special" -nogui simulate.py + # NRN + NMODL +echo "-- Running NRN+NMODL --------" rm -r "${output_dir}" tmp || true nrnivmodl -nmodl "${nmodl}" "$(uname -m)/special" -nogui simulate.py From 8629b11fe7fe02c3b86b38dbb55536f6ce29b770 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 16 May 2024 07:11:44 +0200 Subject: [PATCH 639/871] Fix missing variables when calling a FUNCTION or PROCEDURE in BREAKPOINT block (BlueBrain/nmodl#1260) NMODL Repo SHA: BlueBrain/nmodl@a8f1829501d0d3a4e6680823b0f0bb4fb718ca30 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 8 ++++-- test/nmodl/transpiler/usecases/CMakeLists.txt | 3 ++- .../func_in_breakpoint/func_in_breakpoint.mod | 27 +++++++++++++++++++ .../usecases/func_in_breakpoint/simulate.py | 1 + 4 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/func_in_breakpoint/func_in_breakpoint.mod create mode 100644 test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 7f18b131f8..8d2c1feaf6 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1462,7 +1462,7 @@ std::string CodegenNeuronCppVisitor::nrn_current_arguments() { if (ion_variable_struct_required()) { throw std::runtime_error("Not implemented."); } - return "id, inst, node_data, v"; + return "_ml, _nt, _ppvar, _thread, id, inst, node_data, v"; } @@ -1472,6 +1472,10 @@ CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parame } ParamVector params; + params.emplace_back("", "_nrn_mechanism_cache_range*", "", "_ml"); + params.emplace_back("", "NrnThread*", "", "_nt"); + params.emplace_back("", "Datum*", "", "_ppvar"); + params.emplace_back("", "Datum*", "", "_thread"); params.emplace_back("", "size_t", "", "id"); params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); params.emplace_back("", fmt::format("{}&", node_data_struct()), "", "node_data"); @@ -1577,7 +1581,7 @@ void CodegenNeuronCppVisitor::print_nrn_cur_non_conductance_kernel() { void CodegenNeuronCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { printer->add_line("int node_id = node_data.nodeindices[id];"); printer->add_line("double v = node_data.node_voltages[node_id];"); - + printer->add_line("auto* _ppvar = _ml_arg->pdata[id];"); const auto& read_statements = ion_read_statements(BlockType::Equation); for (auto& statement: read_statements) { printer->add_line(statement); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 4897b16385..49891865d2 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -9,7 +9,8 @@ set(NMODL_USECASE_DIRS net_receive net_send point_process - parameter) + parameter + func_in_breakpoint) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/func_in_breakpoint/func_in_breakpoint.mod b/test/nmodl/transpiler/usecases/func_in_breakpoint/func_in_breakpoint.mod new file mode 100644 index 0000000000..067b8dd7dc --- /dev/null +++ b/test/nmodl/transpiler/usecases/func_in_breakpoint/func_in_breakpoint.mod @@ -0,0 +1,27 @@ +NEURON { + SUFFIX func_in_breakpoint + NONSPECIFIC_CURRENT il +} + +ASSIGNED { + il +} + +PARAMETER { + c = 1 +} + +PROCEDURE func() { +} + +PROCEDURE func_with_v(v) { +} + +PROCEDURE func_with_other(q) { +} + +BREAKPOINT { + func() + func_with_v(v) + func_with_other(c) +} diff --git a/test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py b/test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py new file mode 100644 index 0000000000..b06c8f6a44 --- /dev/null +++ b/test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py @@ -0,0 +1 @@ +print("success: func_in_breakpoint") From 7b827aa9c0c481dae5bd8b57f615c233fa3750e6 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 16 May 2024 08:27:10 +0300 Subject: [PATCH 640/871] Bump to PyBind11 2.12 (BlueBrain/nmodl#1267) Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@8ff93bedd96f68b28fdcd27e5435fd9d26de190e --- cmake/nmodl/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 82321120de..4fb5b31881 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -130,11 +130,11 @@ cpp_cc_git_submodule(json BUILD PACKAGE nlohmann_json REQUIRED) cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED) # Tell spdlog not to use its bundled fmt, it should either use the fmt submodule or a truly external # installation for consistency. This line should be harmless if we use an external spdlog. -set(SPDLOG_FMT_EXTERNAL ON) -set(SPDLOG_SYSTEM_INCLUDE ON) +option(SPDLOG_FMT_EXTERNAL "Force to use an external {{fmt}}" ON) +option(SPDLOG_SYSTEM_INCLUDE "Include spdlog as a system lib" ON) if(NMODL_3RDPARTY_USE_SPDLOG) # See above, same logic as fmt - set(SPDLOG_BUILD_PIC ON) + option(SPDLOG_BUILD_PIC "Needed to be PIC to be put compiled as a lib" ON) endif() cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) @@ -170,7 +170,7 @@ endif() # Find required python packages # ============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(PythonInterp 3.8 REQUIRED) +find_package(Python 3.8 REQUIRED COMPONENTS Interpreter) cpp_cc_strip_python_shims(EXECUTABLE "${PYTHON_EXECUTABLE}" OUTPUT PYTHON_EXECUTABLE) # ============================================================================= From 805f2dfa22df7db1c6e86060de7e5be0b8b4777f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 16 May 2024 11:28:28 +0200 Subject: [PATCH 641/871] Assign default value in PARAMETER block. (BlueBrain/nmodl#1262) * Assign default value in PARAMETER block. A parameter can be assigned a value inside the PARAMETER block. If no value is assigned the default is `0.0`. This commit fixes a bug when no value was specified. * Improve test for PARAMETER. NMODL Repo SHA: BlueBrain/nmodl@322bbf0345dfc7e84ee5db35112c83dab4e87bf5 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 7 +++++-- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../neuron_variables.mod | 0 .../usecases/neuron_variables/simulate.py | 16 ++++++++++++++++ .../transpiler/usecases/parameter/simulate.py | 11 ++++++----- .../usecases/parameter/test_parameter.mod | 7 ++++++- 6 files changed, 34 insertions(+), 8 deletions(-) rename test/nmodl/transpiler/usecases/{parameter => neuron_variables}/neuron_variables.mod (100%) create mode 100644 test/nmodl/transpiler/usecases/neuron_variables/simulate.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 8d2c1feaf6..a27a330522 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1339,9 +1339,12 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { continue; } const auto& var_name = var->get_name(); + auto var_pos = position_of_float_var(var_name); + double var_value = var->get_value() == nullptr ? 0.0 : *var->get_value(); + printer->fmt_line("_ml->template fpfield<{}>(_iml) = {}; /* {} */", - position_of_float_var(var_name), - *var->get_value(), + var_pos, + var_value, var_name); } } diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 49891865d2..ba4f015437 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -6,6 +6,7 @@ set(NMODL_USECASE_DIRS global_breakpoint hodgkin_huxley nonspecific_current + neuron_variables net_receive net_send point_process diff --git a/test/nmodl/transpiler/usecases/parameter/neuron_variables.mod b/test/nmodl/transpiler/usecases/neuron_variables/neuron_variables.mod similarity index 100% rename from test/nmodl/transpiler/usecases/parameter/neuron_variables.mod rename to test/nmodl/transpiler/usecases/neuron_variables/neuron_variables.mod diff --git a/test/nmodl/transpiler/usecases/neuron_variables/simulate.py b/test/nmodl/transpiler/usecases/neuron_variables/simulate.py new file mode 100644 index 0000000000..d557b8c935 --- /dev/null +++ b/test/nmodl/transpiler/usecases/neuron_variables/simulate.py @@ -0,0 +1,16 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +s = h.Section() + +s.insert("NeuronVariables") +celsius_hoc = h.Vector().record(s(0.5)._ref_range_celsius_NeuronVariables) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +celsius = np.array(celsius_hoc.as_numpy()) +assert celsius[-1] == 6.3, f"{celsius[-1]=}" diff --git a/test/nmodl/transpiler/usecases/parameter/simulate.py b/test/nmodl/transpiler/usecases/parameter/simulate.py index 5777b55368..250bf26855 100644 --- a/test/nmodl/transpiler/usecases/parameter/simulate.py +++ b/test/nmodl/transpiler/usecases/parameter/simulate.py @@ -7,17 +7,18 @@ test_parameter_pp = h.test_parameter(s(0.5)) +# Defaults set in the PARAMETER block: assert test_parameter_pp.x == 42.0 +assert test_parameter_pp.y == 0.0 +# Assignable: test_parameter_pp.x = 42.1 assert test_parameter_pp.x == 42.1 -s.insert("NeuronVariables") -celsius_hoc = h.Vector().record(s(0.5)._ref_range_celsius_NeuronVariables) - h.stdinit() h.tstop = 5.0 * ms h.run() -celsius = np.array(celsius_hoc.as_numpy()) -assert celsius[-1] == 6.3, f"{celsius[-1]=}" +# Values (not) set during the INITIAL block: +assert test_parameter_pp.x == 42.1 +assert test_parameter_pp.y == 43.0 diff --git a/test/nmodl/transpiler/usecases/parameter/test_parameter.mod b/test/nmodl/transpiler/usecases/parameter/test_parameter.mod index 417ec90fe1..dbe064160e 100644 --- a/test/nmodl/transpiler/usecases/parameter/test_parameter.mod +++ b/test/nmodl/transpiler/usecases/parameter/test_parameter.mod @@ -1,8 +1,13 @@ NEURON { POINT_PROCESS test_parameter - RANGE x + RANGE x, y } PARAMETER { x = 42 + y +} + +INITIAL { + y = 43 } From 832f8ff2369c10a05fa6337f82d43cc2e9a224e9 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 21 May 2024 08:44:48 +0200 Subject: [PATCH 642/871] Remove `virtual` keyword where it's not needed (BlueBrain/nmodl#1250) --------- Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@f828be9540943535376303f7bf17a5b0b9332245 --- src/nmodl/codegen/codegen_acc_visitor.hpp | 6 ++--- .../codegen_coreneuron_cpp_visitor.hpp | 14 +++++------ .../codegen/codegen_neuron_cpp_visitor.hpp | 16 ++++++------- .../templates/ast/node_class.template | 24 +++++++++---------- .../templates/visitors/nmodl_visitor.hpp | 2 +- src/nmodl/pybind/pyembed.hpp | 8 +++---- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 959b00aeb0..8b0ecf687d 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -120,13 +120,13 @@ class CodegenAccVisitor: public CodegenCoreneuronCppVisitor { void print_net_send_buf_update_to_host() const override; /// update NetSendBuffer_t count from host to device - virtual void print_net_send_buf_count_update_to_device() const override; + void print_net_send_buf_count_update_to_device() const override; /// update dt from host to device - virtual void print_dt_update_to_device() const override; + void print_dt_update_to_device() const override; // synchronise/wait on stream specific to NrnThread - virtual void print_device_stream_wait() const override; + void print_device_stream_wait() const override; /// print atomic capture pragma void print_device_atomic_capture_annotation() const override; diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index cae873bcbb..41026f1fb2 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -83,7 +83,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /** * Name of the code generation backend */ - virtual std::string backend_name() const override; + std::string backend_name() const override; /** @@ -247,7 +247,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /** * Print atomic update pragma for reduction statements */ - virtual void print_atomic_reduction_pragma() override; + void print_atomic_reduction_pragma() override; /** @@ -377,7 +377,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { * Print NMODL procedure in target backend code * \param node */ - virtual void print_procedure(const ast::ProcedureBlock& node) override; + void print_procedure(const ast::ProcedureBlock& node) override; /** @@ -973,7 +973,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /** * Print fast membrane current calculation code */ - virtual void print_fast_imem_calculation() override; + void print_fast_imem_calculation() override; /** @@ -1038,14 +1038,14 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { * Print all compute functions for every backend * */ - virtual void print_compute_functions() override; + void print_compute_functions() override; /** * Print entry point to code generation * */ - virtual void print_codegen_routines() override; + void print_codegen_routines() override; /****************************************************************************************/ @@ -1057,7 +1057,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; - virtual void visit_watch_statement(const ast::WatchStatement& node) override; + void visit_watch_statement(const ast::WatchStatement& node) override; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index a68bee037e..f366796b2c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -92,7 +92,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** * Name of the code generation backend */ - virtual std::string backend_name() const override; + std::string backend_name() const override; /****************************************************************************************/ @@ -124,7 +124,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** * Print atomic update pragma for reduction statements */ - virtual void print_atomic_reduction_pragma() override; + void print_atomic_reduction_pragma() override; /** @@ -206,7 +206,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * Print NMODL procedure in target backend code * \param node */ - virtual void print_procedure(const ast::ProcedureBlock& node) override; + void print_procedure(const ast::ProcedureBlock& node) override; /** @@ -445,8 +445,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * Print common code for global functions like nrn_init, nrn_cur and nrn_state * \param type The target backend code block type */ - virtual void print_global_function_common_code(BlockType type, - const std::string& function_name = "") override; + void print_global_function_common_code(BlockType type, + const std::string& function_name = "") override; /** @@ -543,7 +543,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** * Print fast membrane current calculation code */ - virtual void print_fast_imem_calculation() override; + void print_fast_imem_calculation() override; /** @@ -637,7 +637,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * Print all compute functions for every backend * */ - virtual void print_compute_functions() override; + void print_compute_functions() override; /** @@ -652,7 +652,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - virtual void visit_watch_statement(const ast::WatchStatement& node) override; + void visit_watch_statement(const ast::WatchStatement& node) override; diff --git a/src/nmodl/language/templates/ast/node_class.template b/src/nmodl/language/templates/ast/node_class.template index 87c6c9f590..39dfa3c520 100644 --- a/src/nmodl/language/templates/ast/node_class.template +++ b/src/nmodl/language/templates/ast/node_class.template @@ -90,7 +90,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * \return pointer to the clone/copy of the current node */ // NOLINTBEGIN(clang-analyzer-cplusplus.NewDeleteLeaks) - {{ virtual(node) }} {{ node.class_name }}* clone() const override { + {{ node.class_name }}* clone() const override { return new {{ node.class_name }}(*this); } // NOLINTEND(clang-analyzer-cplusplus.NewDeleteLeaks) @@ -108,7 +108,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::get_node_type_name */ - {{ virtual(node) }} AstNodeType get_node_type() const noexcept override { + AstNodeType get_node_type() const noexcept override { return AstNodeType::{{ node.ast_enum_name }}; } @@ -123,7 +123,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::get_node_name */ - {{ virtual(node) }} std::string get_node_type_name() const noexcept override { + std::string get_node_type_name() const noexcept override { return "{{ node.class_name }}"; } @@ -139,7 +139,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::get_nmodl_name */ - {{ virtual(node) }} std::string get_nmodl_name() const noexcept override { + std::string get_nmodl_name() const noexcept override { return "{{ node.nmodl_name }}"; } {% endif %} @@ -147,14 +147,14 @@ class {{ node.class_name }} : public {{ node.base_class }} { /** * \brief Get std::shared_ptr from `this` pointer of the current ast node */ - {{ virtual(node) }} std::shared_ptr get_shared_ptr() override { + std::shared_ptr get_shared_ptr() override { return std::static_pointer_cast<{{ node.class_name }}>(shared_from_this()); } /** * \brief Get std::shared_ptr from `this` pointer of the current ast node */ - {{ virtual(node) }} std::shared_ptr get_shared_ptr() const override { + std::shared_ptr get_shared_ptr() const override { return std::static_pointer_cast(shared_from_this()); } @@ -168,7 +168,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \return pointer to token if exist otherwise nullptr */ - const {{ virtual(node) }}ModToken* get_token() const noexcept override { + const ModToken* get_token() const noexcept override { return token.get(); } {% endif %} @@ -218,7 +218,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::get_node_type_name Ast::get_node_name */ - {{ virtual(node) }}void set_name(const std::string& name) override; + void set_name(const std::string& name) override; {% endif %} {% if node.has_token %} /** @@ -268,7 +268,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::visit_children for example. */ - {{ virtual(node) }} void visit_children(visitor::Visitor& v) override; + void visit_children(visitor::Visitor& v) override; /** * \brief visit children i.e. member variables of current node using provided visitor @@ -280,7 +280,7 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::visit_children for example. */ - {{ virtual(node) }} void visit_children(visitor::ConstVisitor& v) const override; + void visit_children(visitor::ConstVisitor& v) const override; /** * \brief accept (or visit) the current AST node using provided visitor @@ -293,12 +293,12 @@ class {{ node.class_name }} : public {{ node.base_class }} { * * \sa Ast::accept for example. */ - {{ virtual(node) }} void accept(visitor::Visitor& v) override; + void accept(visitor::Visitor& v) override; /** * \copydoc accept(visitor::Visitor&) */ - {{ virtual(node) }} void accept(visitor::ConstVisitor& v) const override; + void accept(visitor::ConstVisitor& v) const override; /// \} {% if node.is_base_class_number_node %} diff --git a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp index dbfa32eaa7..4dd38ff29c 100644 --- a/src/nmodl/language/templates/visitors/nmodl_visitor.hpp +++ b/src/nmodl/language/templates/visitors/nmodl_visitor.hpp @@ -69,7 +69,7 @@ class NmodlPrintVisitor: public ConstVisitor { // clang-format off {% for node in nodes %} - virtual void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; + void visit_{{ node.class_name|snake_case }}(const ast::{{ node.class_name }}& node) override; {% endfor %} // clang-format on diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp index 2199d6b04c..851ff9240c 100644 --- a/src/nmodl/pybind/pyembed.hpp +++ b/src/nmodl/pybind/pyembed.hpp @@ -43,7 +43,7 @@ struct SolveLinearSystemExecutor: public PythonExecutor { // may also return a python exception message: std::string exception_message; // executor function - virtual void operator()() override; + void operator()() override; }; @@ -60,7 +60,7 @@ struct SolveNonLinearSystemExecutor: public PythonExecutor { std::string exception_message; // executor function - virtual void operator()() override; + void operator()() override; }; @@ -79,7 +79,7 @@ struct DiffeqSolverExecutor: public PythonExecutor { std::string exception_message; // executor function - virtual void operator()() override; + void operator()() override; }; @@ -94,7 +94,7 @@ struct AnalyticDiffExecutor: public PythonExecutor { std::string exception_message; // executor function - virtual void operator()() override; + void operator()() override; }; From 9bb7dc09b7a6f7f5fcb21a470fd387fb5039f0aa Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 22 May 2024 09:44:16 +0200 Subject: [PATCH 643/871] Fix bug with state named 'is'. (BlueBrain/nmodl#1263) Rename a small number of Python keywords appearing in generated strings we forward to sympy. * Don't use 'pi'. NMODL Repo SHA: BlueBrain/nmodl@f28a335a798b7b12b9bdc2027be3f37e32f3eb29 --- src/nmodl/nmodl/python/nmodl/ode.py | 67 ++++++++++++++++--- .../integration/mod/variable_names.mod | 56 ++++++++++++++++ 2 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 test/nmodl/transpiler/integration/mod/variable_names.mod diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index cbb848839a..3e14d174d2 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -26,10 +26,20 @@ if not ((major >= 1) and (minor >= 2)): raise ImportError(f"Requires SympPy version >= 1.2, found {major}.{minor}") -# Some functions are protected inside sympy, if user has declared such a function, it will fail -# because sympy will try to use its own internal one. -# Rename it before and after to a single name +# Some identifiers are protected inside sympy, if user has declared such a function, it will fail +# because sympy will try to use its own internal one; or error out for invalid variables. +# Rename it before and after to a unique name. forbidden_var = [ + # Selected Python keywords + "is", + "as", + "count", + "del", + "elif", + "in", + "lambda", + "pass", + # SymPy functions "beta", "gamma", "uppergamma", @@ -41,20 +51,55 @@ ] -def search_and_replace_protected_functions_to_sympy(eqs, function_calls): +def search_and_replace_protected_identifiers_to_sympy(eqs, vars, function_calls): + eqs = _search_and_replace_protected_functions_to_sympy(eqs, function_calls) + eqs, vars = _search_and_replace_protected_variables_to_sympy(eqs, vars) + + return eqs, vars + + +def search_and_replace_protected_identifiers_from_sympy(eqs, function_calls): + eqs = _search_and_replace_protected_functions_from_sympy(eqs, function_calls) + eqs = _search_and_replace_protected_variables_from_sympy(eqs) + + return eqs + + +def _search_and_replace_protected_variables_to_sympy(eqs, vars): + for c in forbidden_var: + r = re.compile(r"\b{}\b".format(c)) + f = f"_sympy_{c}_var" + eqs = [re.sub(r, f, x) for x in eqs] + vars = [re.sub(r, f, x) for x in vars] + + return eqs, vars + + +def _search_and_replace_protected_variables_from_sympy(eqs): + for c in forbidden_var: + r = re.compile(r"\b_sympy_{}_var\b".format(c)) + f = c + eqs = [re.sub(r, f, x) for x in eqs] + + return eqs + + +def _search_and_replace_protected_functions_to_sympy(eqs, function_calls): for c in function_calls: if c in forbidden_var: r = re.compile(r"\b{}\b\s*\(".format(c)) f = f"_sympy_{c}_fun(" eqs = [re.sub(r, f, x) for x in eqs] + return eqs -def search_and_replace_protected_functions_from_sympy(eqs, function_calls): +def _search_and_replace_protected_functions_from_sympy(eqs, function_calls): for c in function_calls: if c in forbidden_var: r = f"_sympy_{c}_fun" eqs = [re.sub(r, f"{c}", x) for x in eqs] + return eqs @@ -257,8 +302,8 @@ def solve_lin_system( vars: list of strings containing new local variables """ - eq_strings = search_and_replace_protected_functions_to_sympy( - eq_strings, function_calls + eq_strings, vars = search_and_replace_protected_identifiers_to_sympy( + eq_strings, vars, function_calls ) eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) @@ -313,7 +358,7 @@ def solve_lin_system( # interweave code = _interweave_eqs(vecFcode, vecJcode) - code = search_and_replace_protected_functions_from_sympy(code, function_calls) + code = search_and_replace_protected_identifiers_from_sympy(code, function_calls) return code, new_local_vars @@ -335,8 +380,8 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): List of strings containing assignment statements """ - eq_strings = search_and_replace_protected_functions_to_sympy( - eq_strings, function_calls + eq_strings, vars = search_and_replace_protected_identifiers_to_sympy( + eq_strings, vars, function_calls ) eqs, state_vars, sympy_vars = _sympify_eqs(eq_strings, vars, constants) @@ -365,7 +410,7 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): # interweave code = _interweave_eqs(vecFcode, vecJcode) - code = search_and_replace_protected_functions_from_sympy(code, function_calls) + code = search_and_replace_protected_identifiers_from_sympy(code, function_calls) return code diff --git a/test/nmodl/transpiler/integration/mod/variable_names.mod b/test/nmodl/transpiler/integration/mod/variable_names.mod new file mode 100644 index 0000000000..43601db476 --- /dev/null +++ b/test/nmodl/transpiler/integration/mod/variable_names.mod @@ -0,0 +1,56 @@ +: Collection of tricky variable names. + +NEURON { + SUFFIX variable_names +} + +STATE { + is + be + count + as + del + elif + in + pass + + alpha + beta + gamma + delta + epsilon + zeta + eta + theta + iota + kappa + lambda + mu + nu + xi + omicron + : pi + chi + psi + omega +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + ~ is <-> be (1, 1) + ~ count <-> as (1, 1) + ~ elif <-> in (1, 1) + ~ lambda <-> pass (1, 1) + ~ alpha <-> beta (1, 1) + ~ gamma <-> delta (1, 1) + ~ epsilon <-> zeta (1, 1) + ~ eta <-> theta (1, 1) + ~ iota <-> kappa (1, 1) + ~ lambda <-> mu (1, 1) + ~ nu <-> xi (1, 1) + ~ omicron <-> chi (1, 1) + ~ psi <-> omega (1, 1) +} From d63b17c29d2a6728c756bb36a4718a2290f05f50 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 22 May 2024 14:24:13 +0200 Subject: [PATCH 644/871] Refactor `nmodl ... blame --line N`. (BlueBrain/nmodl#1265) The revised format only prints the first trace, before entering the `CodePrinter`. It also refactors the code to prepare for injecting a detailed blame printer, when needed. NMODL Repo SHA: BlueBrain/nmodl@24706b5395b87249c1e18007b53f16bc5e996921 --- src/nmodl/printer/CMakeLists.txt | 5 - src/nmodl/printer/code_printer.cpp | 19 +--- src/nmodl/utils/CMakeLists.txt | 7 ++ src/nmodl/utils/blame.cpp | 107 ++++++++++++++++++ src/nmodl/utils/blame.hpp | 29 +++++ src/nmodl/utils/string_utils.hpp | 21 ++++ test/nmodl/transpiler/unit/CMakeLists.txt | 4 + .../transpiler/unit/utils/string_utils.cpp | 31 +++++ 8 files changed, 202 insertions(+), 21 deletions(-) create mode 100644 src/nmodl/utils/blame.cpp create mode 100644 src/nmodl/utils/blame.hpp create mode 100644 test/nmodl/transpiler/unit/utils/string_utils.cpp diff --git a/src/nmodl/printer/CMakeLists.txt b/src/nmodl/printer/CMakeLists.txt index 0d4666542f..8a3e02da23 100644 --- a/src/nmodl/printer/CMakeLists.txt +++ b/src/nmodl/printer/CMakeLists.txt @@ -4,8 +4,3 @@ add_library(printer OBJECT code_printer.cpp json_printer.cpp nmodl_printer.cpp) set_property(TARGET printer PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(printer PRIVATE util) - -if(NMODL_ENABLE_BACKWARD) - target_link_libraries(printer PRIVATE Backward::Interface) - target_compile_definitions(printer PUBLIC NMODL_ENABLE_BACKWARD=1) -endif() diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 8c3c5f0e77..6a369139ca 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -6,12 +6,9 @@ */ #include "printer/code_printer.hpp" +#include "utils/blame.hpp" #include "utils/string_utils.hpp" -#if NMODL_ENABLE_BACKWARD -#include -#endif - namespace nmodl { namespace printer { @@ -115,18 +112,8 @@ void CodePrinter::pop_block(const std::string_view& suffix, std::size_t num_newl } void CodePrinter::blame() { -#if NMODL_ENABLE_BACKWARD - if (current_line == blame_line) { - *result << std::flush; - - std::cout << "\n\n== Blame =======================================================\n"; - - backward::StackTrace st; - st.load_here(32); - backward::Printer p; - p.print(st, std::cout); - } -#endif + auto blame_printer = utils::make_blame(blame_line); + (*blame_printer)(std::cout, current_line); } diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index cab0dbfbf4..aff5d0d499 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -3,6 +3,7 @@ # ============================================================================= add_library( util STATIC + blame.cpp common_utils.cpp file_library.cpp logger.cpp @@ -10,5 +11,11 @@ add_library( string_utils.cpp table_data.cpp ${PROJECT_BINARY_DIR}/src/config/config.cpp) + set_property(TARGET util PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(util PUBLIC fmt::fmt nlohmann_json::nlohmann_json spdlog::spdlog_header_only) + +if(NMODL_ENABLE_BACKWARD) + target_link_libraries(util PRIVATE Backward::Interface) + target_compile_definitions(util PUBLIC NMODL_ENABLE_BACKWARD=1) +endif() diff --git a/src/nmodl/utils/blame.cpp b/src/nmodl/utils/blame.cpp new file mode 100644 index 0000000000..7490dfd835 --- /dev/null +++ b/src/nmodl/utils/blame.cpp @@ -0,0 +1,107 @@ +#include "blame.hpp" + +#include "string_utils.hpp" +#include +#include + +#if NMODL_ENABLE_BACKWARD +#include +#endif + +namespace nmodl { +namespace utils { + +class NoBlame: public Blame { + public: + using Blame::Blame; + + protected: + std::string format() const override { + return ""; + } +}; + +#if NMODL_ENABLE_BACKWARD +size_t first_relevant_trace(backward::TraceResolver& tr, const backward::StackTrace& st) { + std::vector stop_at{"printer/code_printer.cpp", "printer/code_printer.hpp"}; + int start_from = int(st.size()) - 2; + for (int i = start_from; i >= 0; --i) { + backward::ResolvedTrace trace = tr.resolve(st[i]); + const std::string& filename = trace.source.filename; + + for (const auto& f: stop_at) { + if (stringutils::ends_with(filename, f)) { + return i + 1; + } + } + } + + throw std::runtime_error("Failed to determine relevant trace."); +} + +class BackwardTracePrinter { + public: + std::string format(const backward::ResolvedTrace& trace, const std::string& trace_label) const { + const std::string& filename = trace.source.filename; + size_t linenumber = trace.source.line; + + auto pad = std::string(trace_label.size(), ' '); + + std::stringstream sout; + + sout << fmt::format("{} Source: \"{}\", line {}, in {}\n", + trace_label, + filename, + linenumber, + trace.source.function); + + if (std::filesystem::exists(trace.source.filename) && trace.source.line != 0) { + auto snippet = snippets.get_snippet(filename, linenumber, 3); + for (auto line: snippet) { + sout << fmt::format("{} {}{:>5d}: {}\n", + pad, + linenumber == line.first ? ">" : " ", + line.first, + line.second); + } + } + + return sout.str(); + } + + mutable backward::SnippetFactory snippets; +}; + +class ShortBlame: public Blame { + public: + using Blame::Blame; + + protected: + std::string format() const override { + backward::StackTrace st; + st.load_here(32); + + backward::TraceResolver tr; + + size_t trace_id = first_relevant_trace(tr, st); + backward::ResolvedTrace trace = tr.resolve(st[trace_id]); + + std::string trace_label = ""; + return trace_printer.format(trace, ""); + } + + BackwardTracePrinter trace_printer; +}; +#else +class ShortBlame: public NoBlame { + public: + using NoBlame::NoBlame; +}; +#endif + +std::unique_ptr make_blame(size_t blame_line) { + return std::make_unique(blame_line); +} + +} // namespace utils +} // namespace nmodl diff --git a/src/nmodl/utils/blame.hpp b/src/nmodl/utils/blame.hpp new file mode 100644 index 0000000000..7c5d26f0c2 --- /dev/null +++ b/src/nmodl/utils/blame.hpp @@ -0,0 +1,29 @@ +#include +#include + +namespace nmodl { +namespace utils { + +class Blame { + public: + Blame(size_t blame_line) + : blame_line(blame_line) {} + + template + void operator()(OStream& os, size_t current_line) const { + if (blame_line == current_line) { + os << this->format(); + } + } + + protected: + virtual std::string format() const = 0; + + private: + size_t blame_line = 0; +}; + +std::unique_ptr make_blame(size_t blame_line); + +} // namespace utils +} // namespace nmodl diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 749ee84c0f..4f9dc464dc 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -126,6 +126,27 @@ enum class text_alignment { left, right, center }; return elements; } + +/** + * Check if `haystack` ends with `needle`. + * + * Every string ends with the empty string. + */ +static inline bool ends_with(const std::string& haystack, const std::string& needle) { + if (needle.size() == 0) { + return true; + } + + auto n_chars = needle.size(); + if (haystack.size() < n_chars) { + return false; + } + + auto haystack_begin = haystack.begin() + haystack.size() - n_chars; + return std::equal(haystack_begin, haystack.end(), needle.begin(), needle.end()); +}; + + /// /** * Aligns a text within a field of width \a width diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 7d09cb9b45..62d6a0ad10 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -27,6 +27,7 @@ add_library(config STATIC ${PROJECT_BINARY_DIR}/src/config/config.cpp) # ============================================================================= # Test executables # ============================================================================= +add_executable(testutils utils/string_utils.cpp) add_executable(testmodtoken modtoken/modtoken.cpp) add_executable(testlexer lexer/tokens.cpp) add_executable(testparser parser/parser.cpp) @@ -76,6 +77,7 @@ add_executable( codegen/transform.cpp codegen/codegen_compatibility_visitor.cpp) +target_link_libraries(testutils PRIVATE lexer util) target_link_libraries(testmodtoken PRIVATE lexer util) target_link_libraries(testlexer PRIVATE lexer util) target_link_libraries( @@ -126,6 +128,7 @@ target_link_libraries(testcodegen PRIVATE Catch2::Catch2) target_link_libraries(testvisitor PRIVATE Catch2::Catch2) # With main from Catch2 +target_link_libraries(testutils PRIVATE Catch2::Catch2WithMain) target_link_libraries(testmodtoken PRIVATE Catch2::Catch2WithMain) target_link_libraries(testlexer PRIVATE Catch2::Catch2WithMain) target_link_libraries(testparser PRIVATE Catch2::Catch2WithMain) @@ -138,6 +141,7 @@ target_link_libraries(testunitparser PRIVATE Catch2::Catch2WithMain) foreach( test_name + testutils testcodegen testmodtoken testlexer diff --git a/test/nmodl/transpiler/unit/utils/string_utils.cpp b/test/nmodl/transpiler/unit/utils/string_utils.cpp new file mode 100644 index 0000000000..c79e2d2f2e --- /dev/null +++ b/test/nmodl/transpiler/unit/utils/string_utils.cpp @@ -0,0 +1,31 @@ +#include +#include + +#include "utils/string_utils.hpp" + +using namespace nmodl; + +TEST_CASE("ends_with") { + SECTION("empty substring") { + REQUIRE(stringutils::ends_with("abcde", "")); + } + + SECTION("empty str") { + REQUIRE(!stringutils::ends_with("", "abc")); + } + + SECTION("both empty") { + REQUIRE(stringutils::ends_with("", "")); + } + SECTION("match") { + REQUIRE(stringutils::ends_with("abcde", "de")); + } + + SECTION("mismatch") { + REQUIRE(!stringutils::ends_with("abcde", "d")); + } + + SECTION("oversized") { + REQUIRE(!stringutils::ends_with("abcde", "--abcde")); + } +} From acb94c65ad0accb3b4a9b309239927119b2f5564 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 22 May 2024 16:11:10 +0200 Subject: [PATCH 645/871] Move POINT_PROCESS and TQITEM code. (BlueBrain/nmodl#1272) This code is shared between NEURON and CoreNEURON. Therefore, it's moved the common base class. NMODL Repo SHA: BlueBrain/nmodl@c0994d756b59f99c0446f144bb941acf647a82e2 --- .../codegen_coreneuron_cpp_visitor.cpp | 24 +++++++++++++++++++ .../codegen_coreneuron_cpp_visitor.hpp | 2 ++ src/nmodl/codegen/codegen_cpp_visitor.cpp | 21 +++------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 9 +++++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 13 ++++++++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 2 ++ 6 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 38fab6aee5..5be832c0fa 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -667,6 +667,30 @@ void CodegenCoreneuronCppVisitor::print_eigen_linear_solver(const std::string& f /* Code-specific helper routines */ /****************************************************************************************/ +void CodegenCoreneuronCppVisitor::add_variable_tqitem(std::vector& variables) { + // for non-artificial cell, when net_receive buffering is enabled + // then tqitem is an offset + if (info.net_send_used) { + if (info.artificial_cell) { + variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), true); + } else { + variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), false, false, true); + variables.back().is_constant = true; + } + info.tqitem_index = static_cast(variables.size() - 1); + } +} + +void CodegenCoreneuronCppVisitor::add_variable_point_process( + std::vector& variables) { + /// note that this variable is not printed in neuron implementation + if (info.artificial_cell) { + variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), true); + } else { + variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), false, false, true); + variables.back().is_constant = true; + } +} std::string CodegenCoreneuronCppVisitor::internal_method_arguments() { if (ion_variable_struct_required()) { diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 41026f1fb2..2d123ff66f 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -414,6 +414,8 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /* Code-specific helper routines */ /****************************************************************************************/ + void add_variable_tqitem(std::vector& variables) override; + void add_variable_point_process(std::vector& variables) override; /** * Arguments for functions that are defined and used internally. diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 15ca3d7699..3b8e900c90 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1006,13 +1006,8 @@ std::vector CodegenCppVisitor::get_int_variables() { if (info.point_process) { variables.emplace_back(make_symbol(naming::NODE_AREA_VARIABLE)); variables.back().is_constant = true; - /// note that this variable is not printed in neuron implementation - if (info.artificial_cell) { - variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), true); - } else { - variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), false, false, true); - variables.back().is_constant = true; - } + + add_variable_point_process(variables); } for (auto& ion: info.ions) { @@ -1100,17 +1095,7 @@ std::vector CodegenCppVisitor::get_int_variables() { variables.emplace_back(make_symbol(naming::AREA_VARIABLE)); } - // for non-artificial cell, when net_receive buffering is enabled - // then tqitem is an offset - if (info.net_send_used) { - if (info.artificial_cell) { - variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), true); - } else { - variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), false, false, true); - variables.back().is_constant = true; - } - info.tqitem_index = static_cast(variables.size() - 1); - } + add_variable_tqitem(variables); /** * \note Variables for watch statements : there is one extra variable diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 86b87f488e..dc419e199a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -842,6 +842,15 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /* Code-specific helper routines */ /****************************************************************************************/ + /** + * Add the variable tqitem during `get_int_variables`. + */ + virtual void add_variable_tqitem(std::vector& variables) = 0; + + /** + * Add the variable point_process during `get_int_variables`. + */ + virtual void add_variable_point_process(std::vector& variables) = 0; /** * Arguments for functions that are defined and used internally. diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index a27a330522..90cff26a9e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -386,6 +386,19 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { /* Code-specific helper routines */ /****************************************************************************************/ +void CodegenNeuronCppVisitor::add_variable_tqitem(std::vector& variables) { + if (info.net_send_used) { + variables.emplace_back(make_symbol(naming::TQITEM_VARIABLE), false, false, true); + variables.back().is_constant = true; + info.tqitem_index = static_cast(variables.size() - 1); + } +} + +void CodegenNeuronCppVisitor::add_variable_point_process( + std::vector& variables) { + variables.emplace_back(make_symbol(naming::POINT_PROCESS_VARIABLE), false, false, true); + variables.back().is_constant = true; +} std::string CodegenNeuronCppVisitor::internal_method_arguments() { return "_ml, inst, id, _ppvar, _thread, _nt"; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index f366796b2c..0f2392b7e9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -227,6 +227,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Code-specific helper routines */ /****************************************************************************************/ + void add_variable_tqitem(std::vector& variables) override; + void add_variable_point_process(std::vector& variables) override; /** * Arguments for functions that are defined and used internally. From ca3f9473ad51ccc8235c2dd6c1deda6cb385ebd5 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 24 May 2024 12:48:01 +0200 Subject: [PATCH 646/871] Fix ARTIFICIAL_CELL net_send. (BlueBrain/nmodl#1273) NMODL Repo SHA: BlueBrain/nmodl@0edbed91f16bbc1e96794b99c1263860acf116ca --- .../codegen/codegen_neuron_cpp_visitor.cpp | 19 +++--- .../usecases/net_send/art_toggle.mod | 17 ++++++ .../transpiler/usecases/net_send/simulate.py | 60 +++++++++++++++---- 3 files changed, 76 insertions(+), 20 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/net_send/art_toggle.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 90cff26a9e..8864e68da8 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1055,6 +1055,7 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_line(");"); printer->add_newline(); + printer->fmt_line("hoc_register_prop_size(mech_type, {}, {});", float_variables_size(), int_variables_size()); @@ -1069,6 +1070,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { info.semantics[i].name); } + if (info.artificial_cell) { + printer->fmt_line("add_nrn_artcell(mech_type, {});", info.tqitem_index); + } + printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc);"); if (!info.point_process) { printer->add_line("hoc_register_npy_direct(mech_type, npy_direct_func_proc);"); @@ -1259,12 +1264,13 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { print_global_function_common_code(BlockType::Initial); printer->push_block("for (int id = 0; id < nodecount; id++)"); - printer->add_multi_line(R"CODE( -int node_id = node_data.nodeindices[id]; -auto* _ppvar = _ml_arg->pdata[id]; -auto v = node_data.node_voltages[node_id]; -)CODE"); - printer->fmt_line("inst.{}[id] = v;", naming::VOLTAGE_UNUSED_VARIABLE); + + printer->add_line("auto* _ppvar = _ml_arg->pdata[id];"); + if (!info.artificial_cell) { + printer->add_line("int node_id = node_data.nodeindices[id];"); + printer->add_line("auto v = node_data.node_voltages[node_id];"); + printer->fmt_line("inst.{}[id] = v;", naming::VOLTAGE_UNUSED_VARIABLE); + } print_initial_block(info.initial_node); printer->pop_block(); @@ -1274,7 +1280,6 @@ auto v = node_data.node_voltages[node_id]; void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_newline(2); - printer->add_line("/** nrn_jacob function */"); printer->fmt_push_block( "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* " diff --git a/test/nmodl/transpiler/usecases/net_send/art_toggle.mod b/test/nmodl/transpiler/usecases/net_send/art_toggle.mod new file mode 100644 index 0000000000..64a869d452 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_send/art_toggle.mod @@ -0,0 +1,17 @@ +NEURON { + ARTIFICIAL_CELL art_toggle + RANGE y +} + +PARAMETER { + y = 0.0 +} + +INITIAL { + y = 0 + net_send(1.5, 1) +} + +NET_RECEIVE(w) { + y = 1.0 +} diff --git a/test/nmodl/transpiler/usecases/net_send/simulate.py b/test/nmodl/transpiler/usecases/net_send/simulate.py index 76a689d14f..fb64aa5bf0 100644 --- a/test/nmodl/transpiler/usecases/net_send/simulate.py +++ b/test/nmodl/transpiler/usecases/net_send/simulate.py @@ -3,23 +3,57 @@ from neuron import h, gui from neuron.units import ms -nseg = 1 +# In the INITIAL block an event is sent to itself using `net_send`. Once for +# POINT_PROCESS (toggle.mod) and once for ARTIFICIAL_CELL (art_toggle.mod). +# +# When the event is received the variable `y` is "toggled" to 1. This happens +# at different times for `toggle` and `art_toggle`. -s = h.Section() -s.nseg = nseg -toggle = h.toggle(s(0.5)) +def simulate(): + nseg = 1 -y_hoc = h.Vector().record(toggle._ref_y) -t_hoc = h.Vector().record(h._ref_t) + s = h.Section() + s.nseg = nseg -h.stdinit() -h.tstop = 5.0 * ms -h.run() + toggle = h.toggle(s(0.5)) + art_toggle = h.art_toggle() -t = np.array(t_hoc.as_numpy()) -y = np.array(y_hoc.as_numpy()) + t_hoc = h.Vector().record(h._ref_t) + y_hoc = h.Vector().record(toggle._ref_y) + art_y_hoc = h.Vector().record(art_toggle._ref_y) -y_exact = np.array(t >= 2.0, np.float64) + h.stdinit() + h.tstop = 5.0 * ms + h.run() -assert np.all(np.abs(y - y_exact) == 0), f"{y} != {y_exact}, delta: {y - y_exact}" + t = np.array(t_hoc.as_numpy()) + y = np.array(y_hoc.as_numpy()) + art_y = np.array(art_y_hoc.as_numpy()) + + return t, y, art_y + + +def check_solution(t, y, art_y): + y_exact = np.array(t >= 2.0, np.float64) + art_y_exact = np.array(t >= 1.5, np.float64) + + assert np.all(np.abs(y - y_exact) == 0), f"{y} != {y_exact}, delta: {y - y_exact}" + assert np.all( + np.abs(art_y - art_y_exact) == 0 + ), f"{art_y} != {art_y_exact}, delta: {art_y - art_y_exact}" + + +def plot_solution(t, y, art_y): + import matplotlib.pyplot as plt + + plt.plot(t, y) + plt.plot(t, art_y) + plt.show() + + +if __name__ == "__main__": + t, y, art_y = simulate() + check_solution(t, y, art_y) + + # plot_solution(t, y, art_y) From 00a32f5e587f787b96a9c2ce1137ebd20e263672 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 27 May 2024 08:05:03 +0200 Subject: [PATCH 647/871] Fix `net_send` from NET_RECEIVE block. (BlueBrain/nmodl#1276) NMODL Repo SHA: BlueBrain/nmodl@dbea43133a64f229b573c02da32ddb6a248e01c2 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 29 ++++++++--- .../usecases/net_send/art_toggle.mod | 12 +++-- .../transpiler/usecases/net_send/simulate.py | 49 ++++++++++--------- .../transpiler/usecases/net_send/toggle.mod | 11 +++-- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 8864e68da8..763928166b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -499,7 +499,6 @@ void CodegenNeuronCppVisitor::print_namespace_stop() { std::string CodegenNeuronCppVisitor::conc_write_statement(const std::string& ion_name, const std::string& concentration, int index) { - // throw std::runtime_error("Not implemented."); return ""; } @@ -529,7 +528,6 @@ std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbo } -/// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& symbol, const std::string& name, bool use_instance) const { @@ -580,6 +578,16 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, return varname == var.symbol->get_name(); }; + if (name == naming::POINT_PROCESS_VARIABLE) { + if (printing_net_receive) { + // In net_receive blocks, the point process is passed in as an + // argument called: + return "_pnt"; + } + // The "integer variable" branch will pick up the correct `_ppvar` when + // not printing a NET_RECEIVE block. + } + // float variable auto f = std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), @@ -1829,15 +1837,19 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) { auto const& arguments = node.get_arguments(); - if (printing_net_receive || printing_net_init) { + if (printing_net_init) { throw std::runtime_error("Not implemented. [jfiwoei]"); } std::string weight_pointer = "nullptr"; - const auto& point_process = get_variable_name("point_process", /* use_instance */ false); + auto point_process = get_variable_name(naming::POINT_PROCESS_VARIABLE, + /* use_instance */ false); + if (!printing_net_receive) { + point_process += ".get()"; + } const auto& tqitem = get_variable_name("tqitem", /* use_instance */ false); - printer->fmt_text("net_send(/* tqitem */ &{}, {}, {}.get(), {} + ", + printer->fmt_text("net_send(/* tqitem */ &{}, {}, {}, {} + ", tqitem, weight_pointer, point_process, @@ -1851,7 +1863,9 @@ void CodegenNeuronCppVisitor::print_net_move_call(const ast::FunctionCall& node) } void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& node) { - throw std::runtime_error("Not implemented."); + const auto& point_process = get_variable_name(naming::POINT_PROCESS_VARIABLE, + /* use_instance */ false); + printer->fmt_text("net_event({}, t)", point_process); } /** @@ -1887,6 +1901,7 @@ static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive } void CodegenNeuronCppVisitor::print_net_receive() { + printing_net_receive = true; auto node = info.net_receive_node; if (!node) { return; @@ -1906,6 +1921,7 @@ void CodegenNeuronCppVisitor::print_net_receive() { printer->add_line("_nrn_mechanism_cache_instance _ml_obj{_pnt->prop};"); printer->add_line("auto * _nt = static_cast(_pnt->_vnt);"); printer->add_line("auto * _ml = &_ml_obj;"); + printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); printer->fmt_line("auto inst = make_instance_{}(_ml_obj);", info.mod_suffix); @@ -1916,6 +1932,7 @@ void CodegenNeuronCppVisitor::print_net_receive() { printer->add_newline(); printer->pop_block(); + printing_net_receive = false; } diff --git a/test/nmodl/transpiler/usecases/net_send/art_toggle.mod b/test/nmodl/transpiler/usecases/net_send/art_toggle.mod index 64a869d452..4f490e67c5 100644 --- a/test/nmodl/transpiler/usecases/net_send/art_toggle.mod +++ b/test/nmodl/transpiler/usecases/net_send/art_toggle.mod @@ -3,15 +3,19 @@ NEURON { RANGE y } -PARAMETER { - y = 0.0 +ASSIGNED { + y } INITIAL { y = 0 - net_send(1.5, 1) + net_send(2.501, 1) } NET_RECEIVE(w) { - y = 1.0 + y = y + 1.0 + + if(t < 3.7) { + net_send(4.501 - t, 1) + } } diff --git a/test/nmodl/transpiler/usecases/net_send/simulate.py b/test/nmodl/transpiler/usecases/net_send/simulate.py index fb64aa5bf0..cab186d149 100644 --- a/test/nmodl/transpiler/usecases/net_send/simulate.py +++ b/test/nmodl/transpiler/usecases/net_send/simulate.py @@ -8,20 +8,30 @@ # # When the event is received the variable `y` is "toggled" to 1. This happens # at different times for `toggle` and `art_toggle`. +# +# To test `net_send` inside a NET_RECEIVE block, after toggling we send another +# event (to ourselves) which will cause `y` to increment once more. + +# Recording variables affected by events happens as follows: +# 1. Apply all events in `[t_i, t_i + 0.5 *dt]`. +# 2. Integrate: t_i - 0.5*dt -> t_i + 0.5*dt +# 3. Record variables. +# 4. Apply all events in `[t_i+0.5*dt, t_{i+1}]`. +# +# Therefore, we make sure that the events happen in the first half of a +# time-step. -def simulate(): +def simulate(create_toggle): nseg = 1 s = h.Section() s.nseg = nseg - toggle = h.toggle(s(0.5)) - art_toggle = h.art_toggle() + toggle = create_toggle(s) t_hoc = h.Vector().record(h._ref_t) y_hoc = h.Vector().record(toggle._ref_y) - art_y_hoc = h.Vector().record(art_toggle._ref_y) h.stdinit() h.tstop = 5.0 * ms @@ -29,31 +39,22 @@ def simulate(): t = np.array(t_hoc.as_numpy()) y = np.array(y_hoc.as_numpy()) - art_y = np.array(art_y_hoc.as_numpy()) - - return t, y, art_y - - -def check_solution(t, y, art_y): - y_exact = np.array(t >= 2.0, np.float64) - art_y_exact = np.array(t >= 1.5, np.float64) - assert np.all(np.abs(y - y_exact) == 0), f"{y} != {y_exact}, delta: {y - y_exact}" - assert np.all( - np.abs(art_y - art_y_exact) == 0 - ), f"{art_y} != {art_y_exact}, delta: {art_y - art_y_exact}" + return t, y -def plot_solution(t, y, art_y): - import matplotlib.pyplot as plt +def check_solution(t, y, arrival_times): + eps = 1e-8 + y_exact = np.zeros(y.shape) + for t_arrival in arrival_times: + y_exact[t > t_arrival - eps] += 1 - plt.plot(t, y) - plt.plot(t, art_y) - plt.show() + assert np.all(y == y_exact), f"{y} != {y_exact}, delta = {y - y_exact}" if __name__ == "__main__": - t, y, art_y = simulate() - check_solution(t, y, art_y) + t, y = simulate(lambda s: h.toggle(s(0.5))) + check_solution(t, y, [2.001, 4.001]) - # plot_solution(t, y, art_y) + t, y = simulate(lambda s: h.art_toggle(s(0.5))) + check_solution(t, y, [2.501, 4.501]) diff --git a/test/nmodl/transpiler/usecases/net_send/toggle.mod b/test/nmodl/transpiler/usecases/net_send/toggle.mod index c3d22a4a36..21f8aedaae 100644 --- a/test/nmodl/transpiler/usecases/net_send/toggle.mod +++ b/test/nmodl/transpiler/usecases/net_send/toggle.mod @@ -3,18 +3,19 @@ NEURON { RANGE y } -UNITS { -} - ASSIGNED { y } INITIAL { y = 0 - net_send(2.0, 1) + net_send(2.001, 1) } NET_RECEIVE(w) { - y = 1 + y = y + 1.0 + + if(t < 3.7) { + net_send(4.001 - t, 1) + } } From a24c6d0d83fa33bc4f460e5a9afdc2de8448cce2 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 27 May 2024 09:47:42 +0200 Subject: [PATCH 648/871] Add `net_event`. (BlueBrain/nmodl#1275) NMODL Repo SHA: BlueBrain/nmodl@55131b591f50c0f0bba8690b947083658acbd9fa --- .../usecases/net_event/receiver.mod | 19 +++++ .../transpiler/usecases/net_event/simulate.py | 70 +++++++++++++++++++ .../transpiler/usecases/net_event/spiker.mod | 24 +++++++ 3 files changed, 113 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/net_event/receiver.mod create mode 100644 test/nmodl/transpiler/usecases/net_event/simulate.py create mode 100644 test/nmodl/transpiler/usecases/net_event/spiker.mod diff --git a/test/nmodl/transpiler/usecases/net_event/receiver.mod b/test/nmodl/transpiler/usecases/net_event/receiver.mod new file mode 100644 index 0000000000..c26b091c54 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_event/receiver.mod @@ -0,0 +1,19 @@ +NEURON { + POINT_PROCESS receiver + RANGE y +} + +UNITS { +} + +ASSIGNED { + y +} + +INITIAL { + y = 0.0 +} + +NET_RECEIVE(w) { + y = y + 0.1 +} diff --git a/test/nmodl/transpiler/usecases/net_event/simulate.py b/test/nmodl/transpiler/usecases/net_event/simulate.py new file mode 100644 index 0000000000..e247085738 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_event/simulate.py @@ -0,0 +1,70 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +# Send a message from `spiker` to `receiver` using a +# `net_event` in a NET_RECEIVE block. +# +# Since the NET_RECEIVE block in the `spiker` needs to +# be triggered we add a `stim`. + + +def simulate(): + nseg = 1 + + s1 = h.Section() + s1.nseg = nseg + + s2 = h.Section() + s2.nseg = nseg + + spiker = h.spiker(s1(0.5)) + receiver = h.receiver(s2(0.5)) + + stim = h.NetStim() + stim.interval = 0.1 + stim.number = 100 + stim.start = 0.01 + + nc1 = h.NetCon(stim, spiker, 0, 0.0, 0.0) + nc2 = h.NetCon(spiker, receiver, 0, 0.0, 0.0) + + t_hoc = h.Vector().record(h._ref_t) + y_hoc = h.Vector().record(receiver._ref_y) + + h.stdinit() + h.tstop = 5.0 * ms + h.run() + + t = np.array(t_hoc.as_numpy()) + y = np.array(y_hoc.as_numpy()) + + return t, y + + +def check_solution(t, y, arrival_times): + eps = 1e-8 + y_exact = np.zeros(y.shape) + for t_arrival in arrival_times: + # Spikes arrive on the next time step after t_arrival. + y_exact[t > t_arrival + eps] += 0.1 + + assert np.all( + np.abs(y - y_exact) < 1e-12 + ), f"{y} != {y_exact}, delta: {y - y_exact}" + + +def plot_solution(t, y): + import matplotlib.pyplot as plt + + plt.plot(t, y) + plt.show() + + +if __name__ == "__main__": + t, y = simulate() + arrival_times = np.arange(1, 5) + check_solution(t, y, arrival_times) + + # plot_solution(t, y) diff --git a/test/nmodl/transpiler/usecases/net_event/spiker.mod b/test/nmodl/transpiler/usecases/net_event/spiker.mod new file mode 100644 index 0000000000..0cb625149f --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_event/spiker.mod @@ -0,0 +1,24 @@ +NEURON { + POINT_PROCESS spiker + RANGE tnext +} + +UNITS { +} + +ASSIGNED { + tnext +} + +INITIAL { + tnext = 1.001 +} + +NET_RECEIVE(w) { + LOCAL tt + tt = tnext + if(t >= tt) { + net_event(t) + tnext = tnext + 1.0 + } +} From d93bcbb506e5bcff73194bae708bcf0815d28ea0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 27 May 2024 09:47:57 +0200 Subject: [PATCH 649/871] Implement `net_move`. (BlueBrain/nmodl#1277) Add the code for printing `net_move` for NEURON+nmodl. Also adds a usecase demonstrating the behaviour of `net_move`. NMODL Repo SHA: BlueBrain/nmodl@c96434a5ec78f27b9dd35d21df8873a9c37dc8cb --- .../codegen/codegen_neuron_cpp_visitor.cpp | 10 ++- .../usecases/net_move/art_spiker.mod | 25 ++++++ .../transpiler/usecases/net_move/simulate.py | 90 +++++++++++++++++++ .../transpiler/usecases/net_move/spiker.mod | 25 ++++++ 4 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/net_move/art_spiker.mod create mode 100644 test/nmodl/transpiler/usecases/net_move/simulate.py create mode 100644 test/nmodl/transpiler/usecases/net_move/spiker.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 763928166b..b87c95c3e8 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1859,7 +1859,13 @@ void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) } void CodegenNeuronCppVisitor::print_net_move_call(const ast::FunctionCall& node) { - throw std::runtime_error("Not implemented."); + const auto& point_process = get_variable_name("point_process", /* use_instance */ false); + const auto& tqitem = get_variable_name("tqitem", /* use_instance */ false); + + printer->fmt_text("net_move(/* tqitem */ &{}, {}, ", tqitem, point_process); + + print_vector_elements(node.get_arguments(), ", "); + printer->add_text(')'); } void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& node) { @@ -1910,7 +1916,7 @@ void CodegenNeuronCppVisitor::print_net_receive() { ParamVector args; args.emplace_back("", "Point_process*", "", "_pnt"); args.emplace_back("", "double*", "", "_args"); - args.emplace_back("", "double", "", "_lflag"); + args.emplace_back("", "double", "", "flag"); printer->fmt_push_block("static void nrn_net_receive_{}({})", info.mod_suffix, diff --git a/test/nmodl/transpiler/usecases/net_move/art_spiker.mod b/test/nmodl/transpiler/usecases/net_move/art_spiker.mod new file mode 100644 index 0000000000..e5a9b4fc3f --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_move/art_spiker.mod @@ -0,0 +1,25 @@ +NEURON { + ARTIFICIAL_CELL art_spiker + RANGE y, z +} + +ASSIGNED { + y + z +} + +INITIAL { + y = 0.0 + z = 0.0 + net_send(1.8, 1) +} + +NET_RECEIVE(w) { + if(flag == 0) { + y = y + 1 + net_move(t + 0.1) + } else { + z = z + 1 + net_send(2.0, 1) + } +} diff --git a/test/nmodl/transpiler/usecases/net_move/simulate.py b/test/nmodl/transpiler/usecases/net_move/simulate.py new file mode 100644 index 0000000000..0b9212673b --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_move/simulate.py @@ -0,0 +1,90 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +# A test that demonstrates the behaviour of `net_move` and +# `flag`. +# +# The `flag == 0` means that an external event was received. Therefore, we send +# 2 external events at times `1.001` and `2.001`. External events increment the +# counter `y`; and cause a self event 0.1 after receiving the external event. A +# self event will cause another self event 2.0 later and increments `z`. +# +# The function `net_move` changes the delivery times of the running/existing +# self event to the new value. +# +# External events happen at: [1.001, 2.001] +# Self events happen at: [1.101, 2.101, 4.101] +# +# Note, the offsetting by `0.001` implies that the event arrives in the first +# half of the time-step. Therefore, the change is recorded in time for the next +# time-step. + + +def simulate(create_spiker): + nseg = 1 + + s = h.Section() + s.nseg = nseg + + stim = h.NetStim() + stim.interval = 1.0 + stim.number = 2 + stim.start = 0.001 + + spiker = create_spiker(s) + nc = h.NetCon(stim, spiker) + + t_hoc = h.Vector().record(h._ref_t) + y_hoc = h.Vector().record(spiker._ref_y) + z_hoc = h.Vector().record(spiker._ref_z) + + h.stdinit() + h.tstop = 5.0 * ms + h.run() + + t = np.array(t_hoc.as_numpy()) + y = np.array(y_hoc.as_numpy()) + z = np.array(z_hoc.as_numpy()) + + return t, y, z + + +def check_solution(t, y, z): + y_exact = np.zeros(t.shape) + y_exact[t > 1.001] += 1.0 + y_exact[t > 2.001] += 1.0 + + z_exact = np.zeros(t.shape) + z_exact[t > 1.101] += 1 + z_exact[t > 2.101] += 1 + z_exact[t > 4.101] += 1 + + assert np.all( + np.abs(y - y_exact) < 1e-12 + ), f"{y} != {y_exact}, delta: {y - y_exact}" + + assert np.all( + np.abs(z - z_exact) < 1e-12 + ), f"{z} != {z_exact}, delta: {z - z_exact}" + + +def plot_solution(t, y, z): + import matplotlib.pyplot as plt + + plt.plot(t, y) + plt.plot(t, z) + plt.show() + + +if __name__ == "__main__": + t, y, z = simulate(lambda s: h.art_spiker(s(0.5))) + check_solution(t, y, z) + + # plot_solution(t, y, z) + + t, y, z = simulate(lambda s: h.spiker(s(0.5))) + check_solution(t, y, z) + + # plot_solution(t, y, z) diff --git a/test/nmodl/transpiler/usecases/net_move/spiker.mod b/test/nmodl/transpiler/usecases/net_move/spiker.mod new file mode 100644 index 0000000000..cfbadea769 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_move/spiker.mod @@ -0,0 +1,25 @@ +NEURON { + POINT_PROCESS spiker + RANGE y, z +} + +ASSIGNED { + y + z +} + +INITIAL { + y = 0.0 + z = 0.0 + net_send(1.8, 1) +} + +NET_RECEIVE(w) { + if(flag == 0) { + y = y + 1 + net_move(t + 0.1) + } else { + z = z + 1 + net_send(2.0, 1) + } +} From 04d90c4468582ac218cf0938bb6f9aa097cb95d8 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 27 May 2024 16:03:49 +0200 Subject: [PATCH 650/871] Fix recursive FUNCTION calls (BlueBrain/nmodl#1278) NMODL Repo SHA: BlueBrain/nmodl@a61bebf0040f0f5dba587a207e965ca998e51231 --- src/nmodl/visitors/rename_visitor.cpp | 3 ++- test/nmodl/transpiler/usecases/CMakeLists.txt | 3 ++- .../usecases/recursion/recursion.mod | 11 ++++++++ .../transpiler/usecases/recursion/simulate.py | 26 +++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/recursion/recursion.mod create mode 100644 test/nmodl/transpiler/usecases/recursion/simulate.py diff --git a/src/nmodl/visitors/rename_visitor.cpp b/src/nmodl/visitors/rename_visitor.cpp index 0328d01417..f5a6875020 100644 --- a/src/nmodl/visitors/rename_visitor.cpp +++ b/src/nmodl/visitors/rename_visitor.cpp @@ -47,7 +47,8 @@ std::string RenameVisitor::new_name_generator(const std::string& old_name) { /// rename matching variable void RenameVisitor::visit_name(const ast::Name& node) { const auto& name = node.get_node_name(); - if (std::regex_match(name, var_name_regex)) { + if (std::regex_match(name, var_name_regex) && node.get_parent() && + !node.get_parent()->is_function_call()) { std::string new_name = new_name_generator(name); node.get_value()->set(new_name); std::string token_string = node.get_token() != nullptr diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index ba4f015437..9483b3126f 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -11,7 +11,8 @@ set(NMODL_USECASE_DIRS net_send point_process parameter - func_in_breakpoint) + func_in_breakpoint + recursion) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/recursion/recursion.mod b/test/nmodl/transpiler/usecases/recursion/recursion.mod new file mode 100644 index 0000000000..4570d106fa --- /dev/null +++ b/test/nmodl/transpiler/usecases/recursion/recursion.mod @@ -0,0 +1,11 @@ +NEURON { + SUFFIX recursion +} + +FUNCTION myfactorial(n) { + if (n == 0 || n == 1) { + myfactorial = 1 + } else { + myfactorial = n * myfactorial(n - 1) + } +} diff --git a/test/nmodl/transpiler/usecases/recursion/simulate.py b/test/nmodl/transpiler/usecases/recursion/simulate.py new file mode 100644 index 0000000000..b6eff51f0d --- /dev/null +++ b/test/nmodl/transpiler/usecases/recursion/simulate.py @@ -0,0 +1,26 @@ +import math + +import numpy as np +from neuron import gui +from neuron import h + + +def simulate(): + s = h.Section(name="soma") + s.L = 10 + s.diam = 10 + s.insert("recursion") + fact = s(0.5).recursion.myfactorial + + return fact + + +def check_solution(f, reference): + for n in range(10): + exact, expected = reference(n), f(n) + assert np.isclose(exact, expected), f"{expected} != {exact}" + + +if __name__ == "__main__": + fact = simulate() + check_solution(fact, math.factorial) From 4bb92517a0ec8847defe9a0f27ae9b11fc57d363 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 29 May 2024 10:55:48 +0200 Subject: [PATCH 651/871] Disallow PROCEDUREs using TABLEs containing the same var (BlueBrain/nmodl#1280) NMODL Repo SHA: BlueBrain/nmodl@bd470491cde5828dbfd4a4d32ef90d9f3007ac71 --- .../visitors/semantic_analysis_visitor.cpp | 21 +++++++++++++++++++ .../unit/visitor/semantic_analysis.cpp | 17 +++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index ef46605945..29fc944cb9 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -37,6 +37,27 @@ bool SemanticAnalysisVisitor::check(const ast::Program& node) { } /// --> + // check that we do not have any duplicate TABLE statement variables in PROCEDUREs + const auto& procedure_nodes = collect_nodes(node, {ast::AstNodeType::PROCEDURE_BLOCK}); + std::unordered_set procedure_vars{}; + for (const auto& proc_node: procedure_nodes) { + const auto& table_nodes = collect_nodes(*proc_node, {ast::AstNodeType::TABLE_STATEMENT}); + for (const auto& table_node: table_nodes) { + const auto& table_vars = + std::dynamic_pointer_cast(table_node)->get_table_vars(); + for (const auto& table_var: table_vars) { + const auto& [var_name, + inserted] = procedure_vars.insert(table_var->get_node_name()); + if (!inserted) { + logger->critical( + fmt::format("SemanticAnalysisVisitor :: TABLE statement variable {} used " + "in multiple tables", + *var_name)); + check_fail = true; + } + } + } + } /// <-- This code is for check 4 using namespace symtab::syminfo; const auto& with_prop = NmodlType::read_ion_var | NmodlType::write_ion_var; diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 210b5ec63c..49e5c4992e 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -65,6 +65,23 @@ SCENARIO("TABLE stmt", "[visitor][semantic_analysis]") { REQUIRE(run_semantic_analysis_visitor(nmodl_text)); } } + GIVEN("Two procedures which use a table and have the same variable") { + std::string nmodl_text = R"( + PROCEDURE p1(arg) { + TABLE var FROM 0 TO 1 WITH 10 + var = arg + } + PROCEDURE p2(arg) { + TABLE var FROM 0 TO 1 WITH 10 + var = 2 * arg + } + )"; + THEN("fail") { + // This is asserted because `nocmodl` generated code fails to + // compile in these cases, not because it must not work. + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } } SCENARIO("Destructor block", "[visitor][semantic_analysis]") { From c57439db9f6228abc0450889bd806ce97ab49079 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 29 May 2024 13:42:29 +0200 Subject: [PATCH 652/871] Add `-DNMODL_MAX_ERRORS=3` (default: 1). (BlueBrain/nmodl#1282) NMODL Repo SHA: BlueBrain/nmodl@341b89e0709bbebb2ecdca494a17e4c82d83c4cb --- cmake/nmodl/CMakeLists.txt | 4 ++++ cmake/nmodl/CompilerHelper.cmake | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 4fb5b31881..c3a127b606 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -17,6 +17,9 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) # ============================================================================= # Build options for NMODL # ============================================================================= +set(NMODL_MAX_ERRORS + 1 + CACHE STRING "Number of errors before Clang/GCC stop.") option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) option(NMODL_ENABLE_TESTS "Enable build of tests" ON) option(NMODL_ENABLE_USECASES @@ -72,6 +75,7 @@ include(cmake/ExternalProjectHelper.cmake) # This should apply to all NMODL targets but should not leak out when NMODL is built as a submodule. add_compile_options(${NMODL_COMPILER_WARNING_SUPPRESSIONS}) +add_compile_options(${NMODL_COMPILER_MAX_ERRORS_FLAG}) # ============================================================================= # Set the project version now using git diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 27377956f2..c726f207a3 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -46,3 +46,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") set(NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=177) endif() endif() + +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(NMODL_COMPILER_MAX_ERROR_FLAG -fmax-errors=${NMODL_MAX_ERRORS}) +endif() From c6a0d2cd5f153d857027f02c2f858b2f0241f4da Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 30 May 2024 10:40:48 +0200 Subject: [PATCH 653/871] Deduplicate `print_backend_info`. (BlueBrain/nmodl#1284) NMODL Repo SHA: BlueBrain/nmodl@6426652bba919a208b785054e3fd12c42587bb68 --- .../codegen_coreneuron_cpp_visitor.cpp | 20 ----------------- .../codegen_coreneuron_cpp_visitor.hpp | 6 ----- src/nmodl/codegen/codegen_cpp_visitor.cpp | 22 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 2 +- .../codegen/codegen_neuron_cpp_visitor.cpp | 20 ----------------- .../codegen/codegen_neuron_cpp_visitor.hpp | 6 ----- 6 files changed, 23 insertions(+), 53 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 5be832c0fa..e4c347dbcc 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -1121,26 +1121,6 @@ std::string CodegenCoreneuronCppVisitor::get_variable_name(const std::string& na /****************************************************************************************/ -void CodegenCoreneuronCppVisitor::print_backend_info() { - time_t current_time{}; - time(¤t_time); - std::string data_time_str{std::ctime(¤t_time)}; - auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; - - printer->add_line("/*********************************************************"); - printer->add_line("Model Name : ", info.mod_suffix); - printer->add_line("Filename : ", info.mod_file, ".mod"); - printer->add_line("NMODL Version : ", nmodl_version()); - printer->fmt_line("Vectorized : {}", info.vectorize); - printer->fmt_line("Threadsafe : {}", info.thread_safe); - printer->add_line("Created : ", stringutils::trim(data_time_str)); - printer->add_line("Simulator : ", simulator_name()); - printer->add_line("Backend : ", backend_name()); - printer->add_line("NMODL Compiler : ", version); - printer->add_line("*********************************************************/"); -} - - void CodegenCoreneuronCppVisitor::print_standard_includes() { printer->add_newline(); printer->add_multi_line(R"CODE( diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 2d123ff66f..55e2ff84cb 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -626,12 +626,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Print top file header printed in generated code - */ - void print_backend_info() override; - - /** * Print standard C/C++ includes */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 3b8e900c90..786f950c45 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -6,6 +6,8 @@ */ #include "codegen/codegen_cpp_visitor.hpp" +#include "config/config.h" + #include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_utils.hpp" @@ -409,6 +411,26 @@ std::pair CodegenCppVisitor::write_ion_variable_name( /****************************************************************************************/ +void CodegenCppVisitor::print_backend_info() { + time_t current_time{}; + time(¤t_time); + std::string data_time_str{std::ctime(¤t_time)}; + auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; + + printer->add_line("/*********************************************************"); + printer->add_line("Model Name : ", info.mod_suffix); + printer->add_line("Filename : ", info.mod_file, ".mod"); + printer->add_line("NMODL Version : ", nmodl_version()); + printer->fmt_line("Vectorized : {}", info.vectorize); + printer->fmt_line("Threadsafe : {}", info.thread_safe); + printer->add_line("Created : ", stringutils::trim(data_time_str)); + printer->add_line("Simulator : ", simulator_name()); + printer->add_line("Backend : ", backend_name()); + printer->add_line("NMODL Compiler : ", version); + printer->add_line("*********************************************************/"); +} + + void CodegenCppVisitor::print_global_var_struct_assertions() const { // Assert some things that we assume when copying instances of this struct // to the GPU and so on. diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index dc419e199a..9cd0b0c233 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1075,7 +1075,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Print top file header printed in generated code */ - virtual void print_backend_info() = 0; + void print_backend_info(); /** diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index b87c95c3e8..a228a0113b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -645,26 +645,6 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, /****************************************************************************************/ -void CodegenNeuronCppVisitor::print_backend_info() { - time_t current_time{}; - time(¤t_time); - std::string data_time_str{std::ctime(¤t_time)}; - auto version = nmodl::Version::NMODL_VERSION + " [" + nmodl::Version::GIT_REVISION + "]"; - - printer->add_line("/*********************************************************"); - printer->add_line("Model Name : ", info.mod_suffix); - printer->add_line("Filename : ", info.mod_file, ".mod"); - printer->add_line("NMODL Version : ", nmodl_version()); - printer->fmt_line("Vectorized : {}", info.vectorize); - printer->fmt_line("Threadsafe : {}", info.thread_safe); - printer->add_line("Created : ", stringutils::trim(data_time_str)); - printer->add_line("Simulator : ", simulator_name()); - printer->add_line("Backend : ", backend_name()); - printer->add_line("NMODL Compiler : ", version); - printer->add_line("*********************************************************/"); -} - - void CodegenNeuronCppVisitor::print_standard_includes() { printer->add_newline(); printer->add_multi_line(R"CODE( diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 0f2392b7e9..c1dd010eea 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -399,12 +399,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Print top file header printed in generated code - */ - void print_backend_info() override; - - /** * Print standard C/C++ includes */ From 2fcc599096687dad154431e38c6c077a2d51e0f0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 31 May 2024 15:59:27 +0200 Subject: [PATCH 654/871] Make `generate_references.py` configurable. (BlueBrain/nmodl#1286) Adds the ability to customize with which flags `nmodl` is run to generate the references. The default is `""` (for CoreNEURON) and `"--neuron"` (for NEURON). These choices can be overridden by adding a file `references.json` to the usecase directory. Since the logic is slightly more involved, the Bash script is reimplemented as a Python script. NMODL Repo SHA: BlueBrain/nmodl@491945e7d596b7dc140caa20389d2e0b44f43494 --- .../usecases/generate_references.py | 68 +++++++++++++++++++ .../usecases/generate_references.sh | 31 --------- 2 files changed, 68 insertions(+), 31 deletions(-) create mode 100755 test/nmodl/transpiler/usecases/generate_references.py delete mode 100755 test/nmodl/transpiler/usecases/generate_references.sh diff --git a/test/nmodl/transpiler/usecases/generate_references.py b/test/nmodl/transpiler/usecases/generate_references.py new file mode 100755 index 0000000000..c401b50dce --- /dev/null +++ b/test/nmodl/transpiler/usecases/generate_references.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +import sys +import glob +import os +import json +import subprocess + + +def substitute_line(lines, starts_with, replacement): + for k, line in enumerate(lines): + if line.startswith(starts_with): + lines[k] = replacement + break + + +def generate_references(nmodl, usecase_dir, output_dir, nmodl_flags): + mod_files = glob.glob(os.path.join(usecase_dir, "*.mod")) + for mod_file in mod_files: + try: + subprocess.run( + ["nmodl", mod_file, *nmodl_flags, "-o", output_dir], check=True + ) + except subprocess.CalledProcessError as e: + print(e.output) + raise e + + cxx_files = glob.glob(os.path.join(output_dir, "*.cpp")) + + for cxx_file in cxx_files: + with open(cxx_file, "r") as f: + cxx = f.readlines() + + sub_date = "Created : ", "Created : DATE\n" + substitute_line(cxx, *sub_date) + + sub_version = "NMODL Compiler : ", "NMODL Compiler : VERSION\n" + substitute_line(cxx, *sub_version) + + with open(cxx_file, "w") as f: + f.writelines(cxx) + + +def load_config(script_dir, usecase_dir): + config = os.path.join(usecase_dir, "references.json") + if os.path.exists(config): + with open(config, "r") as f: + return json.load(f) + else: + return [ + {"output_reldir": "coreneuron", "nmodl_flags": []}, + {"output_reldir": "neuron", "nmodl_flags": ["--neuron"]}, + ] + + +if __name__ == "__main__": + if len(sys.argv) != 4: + print(f"Usage: {sys.argv[0]} NMODL USECASE_DIR OUTPUT_DIR") + sys.exit(-1) + + nmodl = sys.argv[1] + usecase_dir = sys.argv[2] + output_basedir = sys.argv[3] + script_dir = os.path.dirname(sys.argv[0]) + + config = load_config(script_dir=script_dir, usecase_dir=usecase_dir) + for c in config: + output_dir = os.path.join(output_basedir, c["output_reldir"]) + generate_references(nmodl, usecase_dir, output_dir, c["nmodl_flags"]) diff --git a/test/nmodl/transpiler/usecases/generate_references.sh b/test/nmodl/transpiler/usecases/generate_references.sh deleted file mode 100755 index 7a99b41933..0000000000 --- a/test/nmodl/transpiler/usecases/generate_references.sh +++ /dev/null @@ -1,31 +0,0 @@ -#! /usr/bin/env bash -set -eu - -if [[ $# -ne 3 ]] -then - echo "Usage: $0 NMODL USECASE_DIR OUTPUT_DIR" - exit -1 -fi - -nmodl="$1" -usecase_dir="$2" -output_dir="$3" - -function sanitize() { - for f in "${1}"/*.cpp - do - if [[ "$(uname)" == 'Darwin' ]]; then - sed_cmd=("sed" "-i" "") - else - sed_cmd=("sed" "-i") - fi - "${sed_cmd[@]}" "s/Created : .*$/Created : DATE/" "$f" - "${sed_cmd[@]}" "s/NMODL Compiler : .*$/NMODL Compiler : VERSION/" "$f" - done -} - -"${nmodl}" "${usecase_dir}"/*.mod --neuron -o "${output_dir}/neuron" -sanitize "${output_dir}/neuron" - -"${nmodl}" "${usecase_dir}"/*.mod -o "${output_dir}"/coreneuron -sanitize "${output_dir}/coreneuron" From b49dd09f8cc2f86019a64441d184dd066f9ecbd0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 3 Jun 2024 08:52:53 +0200 Subject: [PATCH 655/871] Implement GLOBALs. (BlueBrain/nmodl#1285) * Make `generate_references.py` configurable. Adds the ability to customize with which flags `nmodl` is run to generate the references. The default is `""` (for CoreNEURON) and `"--neuron"` (for NEURON). These choices can be overridden by adding a file `references.json` to the usecase directory. Since the logic is slightly more involved, the Bash script is reimplemented as a Python script. * Add 'global/references.json'. --------- Co-authored-by: Pramod Kumbhar NMODL Repo SHA: BlueBrain/nmodl@4379e655c532584b0980d50f7fb4546265ae254d --- .../codegen/codegen_compatibility_visitor.cpp | 8 + .../codegen/codegen_compatibility_visitor.hpp | 9 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 4 + .../codegen/codegen_neuron_cpp_visitor.cpp | 146 +++++++++++++++++- .../codegen/codegen_neuron_cpp_visitor.hpp | 43 +++++- src/nmodl/main.cpp | 6 +- .../usecases/global/references.json | 3 + .../transpiler/usecases/global/simulate.py | 79 ++++++++++ .../usecases/global/thread_variable.mod | 35 +++++ 9 files changed, 326 insertions(+), 7 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/global/references.json create mode 100644 test/nmodl/transpiler/usecases/global/simulate.py create mode 100644 test/nmodl/transpiler/usecases/global/thread_variable.mod diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 5ed3a41963..325f6195db 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -54,6 +54,14 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl std::string CodegenCompatibilityVisitor::return_error_global_var( ast::Ast& node, const std::shared_ptr& ast_node) const { + if (simulator == "neuron") { + // When generating code for NEURON, NMODL support all GLOBALS, same as + // nocmodl. + return ""; + } + + // When generating code for CoreNEURON only read-only GLOBALs are + // supported. auto global_var = std::dynamic_pointer_cast(ast_node); std::stringstream error_message_global_var; if (node.get_symbol_table()->lookup(global_var->get_node_name())->get_write_count() > 0) { diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 311c540efb..346886f2e7 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -57,12 +57,19 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { codegen::naming::SPARSE_METHOD, codegen::naming::AFTER_CVODE_METHOD}; + const std::string simulator = "coreneuron"; + public: /// \name Ctor & dtor /// \{ /// Default CodegenCompatibilityVisitor constructor - CodegenCompatibilityVisitor() = default; + /// + /// The argument `simulator` must be `"neuron"` for NEURON, everything else + /// refers to CoreNEURON. The compatibility will be checked as if + /// generating code for the specified simulator. + CodegenCompatibilityVisitor(const std::string& simulator = "coreneuron") + : simulator(simulator) {} /// \} diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 9cd0b0c233..cae6650eb2 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -378,6 +378,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return fmt::format("{}_NodeData", info.mod_suffix); } + std::string thread_variables_struct() const { + return fmt::format("{}_ThreadVariables", info.mod_suffix); + } + /** * Name of structure that wraps global variables diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index a228a0113b..64f79d38b0 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -556,6 +556,27 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& } +std::string CodegenNeuronCppVisitor::thread_variable_name(const ThreadVariableInfo& var_info, + bool use_instance) const { + auto i_var = var_info.offset; + auto var_name = var_info.symbol->get_name(); + + if (use_instance) { + if (var_info.symbol->is_array()) { + return fmt::format("(_thread_vars.{}_ptr(id))", var_name); + } else { + return fmt::format("_thread_vars.{}(id)", var_name); + } + } else { + if (var_info.symbol->is_array()) { + return fmt::format("({}.thread_data + {})", global_struct_instance(), i_var); + } else { + return fmt::format("{}.thread_data[{}]", global_struct_instance(), i_var); + } + } +} + + std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symbol, bool use_instance) const { if (use_instance) { @@ -578,6 +599,10 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, return varname == var.symbol->get_name(); }; + auto thread_comparator = [&varname](const ThreadVariableInfo& var) { + return varname == var.symbol->get_name(); + }; + if (name == naming::POINT_PROCESS_VARIABLE) { if (printing_net_receive) { // In net_receive blocks, the point process is passed in as an @@ -603,6 +628,14 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, return int_variable_name(*i, varname, use_instance); } + // thread variable + auto t = std::find_if(codegen_thread_variables.begin(), + codegen_thread_variables.end(), + thread_comparator); + if (t != codegen_thread_variables.end()) { + return thread_variable_name(*t, use_instance); + } + // global variable auto g = std::find_if(codegen_global_variables.begin(), codegen_global_variables.end(), @@ -760,9 +793,26 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } if (!info.thread_variables.empty()) { - throw std::runtime_error("Not implemented, global thread variables."); + printer->fmt_line("int thread_data_in_use{};", value_initialize); + printer->fmt_line("{} thread_data[{}] /* TODO init thread_data */;", + float_type, + info.thread_var_data_size); + + size_t prefix_sum = 0; + for (size_t i = 0; i < info.thread_variables.size(); ++i) { + const auto& var = info.thread_variables[i]; + codegen_thread_variables.push_back({var, i, prefix_sum}); + + prefix_sum += var->get_length(); + } + + codegen_global_variables.push_back(make_symbol("thread_data_in_use")); + auto symbol = make_symbol("thread_data"); + symbol->set_as_array(info.thread_var_data_size); + codegen_global_variables.push_back(symbol); } + if (info.table_count > 0) { throw std::runtime_error("Not implemented, global table count."); } @@ -801,7 +851,8 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } if (info.vectorize && info.thread_data_index) { - throw std::runtime_error("Not implemented, global vectorize something else."); + // TODO compare CoreNEURON something extcall stuff. + // throw std::runtime_error("Not implemented, global vectorize something else."); } printer->pop_block(";"); @@ -1070,9 +1121,50 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_line("pnt_receive[mech_type] = nrn_net_receive_{};", info.mod_suffix); printer->fmt_line("pnt_receive_size[mech_type] = {};", info.num_net_receive_parameters); } + + if (info.thread_callback_register) { + printer->add_line("_nrn_thread_reg(mech_type, 1, thread_mem_init);"); + printer->add_line("_nrn_thread_reg(mech_type, 0, thread_mem_cleanup);"); + } + printer->pop_block(); } + +void CodegenNeuronCppVisitor::print_thread_memory_callbacks() { + if (!info.thread_callback_register) { + return; + } + + auto static_thread_data = get_variable_name("thread_data", false); + auto inuse = get_variable_name("thread_data_in_use", false); + auto thread_data_index = info.thread_var_thread_id; + printer->push_block("static void thread_mem_init(Datum* _thread) "); + printer->push_block(fmt::format("if({})", inuse)); + printer->fmt_line("_thread[{}] = {{neuron::container::do_not_search, new double[{}]{{}}}};", + thread_data_index, + info.thread_var_data_size); + printer->pop_block(); + printer->push_block("else"); + printer->fmt_line("_thread[{}] = {{neuron::container::do_not_search, {}}};", + thread_data_index, + static_thread_data); + printer->fmt_line("{} = 1;", inuse); + printer->pop_block(); + printer->pop_block(); + + printer->push_block("static void thread_mem_cleanup(Datum* _thread) "); + printer->fmt_line("double * _thread_data_ptr = _thread[{}].get();", thread_data_index); + printer->push_block(fmt::format("if(_thread_data_ptr == {})", static_thread_data)); + printer->fmt_line("{} = 0;", inuse); + printer->pop_block(); + printer->push_block("else"); + printer->add_line("delete[] _thread_data_ptr;"); + printer->pop_block(); + printer->pop_block(); +} + + void CodegenNeuronCppVisitor::print_neuron_global_variable_declarations() { for (auto const& [var, type]: info.neuron_global_variables) { auto const name = var->get_name(); @@ -1205,6 +1297,37 @@ void CodegenNeuronCppVisitor::print_make_node_data() const { printer->pop_block(); } +void CodegenNeuronCppVisitor::print_thread_variables_structure(bool print_initializers) { + if (codegen_thread_variables.empty()) { + return; + } + + printer->add_newline(2); + printer->fmt_push_block("struct {} ", thread_variables_struct()); + printer->add_line("double * thread_data;"); + printer->add_newline(); + + std::string simd_width = "1"; + + + for (const auto& var_info: codegen_thread_variables) { + printer->fmt_push_block("double * {}_ptr(size_t id)", var_info.symbol->get_name()); + printer->fmt_line("return thread_data + {} + (id % {});", var_info.offset, simd_width); + printer->pop_block(); + + printer->fmt_push_block("double & {}(size_t id)", var_info.symbol->get_name()); + printer->fmt_line("return thread_data[{} + (id % {})];", var_info.offset, simd_width); + printer->pop_block(); + } + printer->add_newline(); + + printer->push_block(fmt::format("{}(double * const thread_data)", thread_variables_struct())); + printer->fmt_line("this->thread_data = thread_data;"); + printer->pop_block(); + + printer->pop_block(";"); +} + void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { // read ion statements @@ -1243,6 +1366,11 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, printer->add_line("auto nodecount = _ml_arg->nodecount;"); printer->add_line("auto* const _ml = &_lmr;"); printer->add_line("auto* _thread = _ml_arg->_thread;"); + if (!codegen_thread_variables.empty()) { + printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", + thread_variables_struct(), + info.thread_var_thread_id); + } } @@ -1471,7 +1599,8 @@ std::string CodegenNeuronCppVisitor::nrn_current_arguments() { if (ion_variable_struct_required()) { throw std::runtime_error("Not implemented."); } - return "_ml, _nt, _ppvar, _thread, id, inst, node_data, v"; + std::string thread_globals = info.thread_callback_register ? " _thread_vars," : ""; + return "_ml, _nt, _ppvar, _thread," + thread_globals + " id, inst, node_data, v"; } @@ -1485,6 +1614,11 @@ CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parame params.emplace_back("", "NrnThread*", "", "_nt"); params.emplace_back("", "Datum*", "", "_ppvar"); params.emplace_back("", "Datum*", "", "_thread"); + + if (info.thread_callback_register) { + auto type_name = fmt::format("{}&", thread_variables_struct()); + params.emplace_back("", type_name, "", "_thread_vars"); + } params.emplace_back("", "size_t", "", "id"); params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); params.emplace_back("", fmt::format("{}&", node_data_struct()), "", "node_data"); @@ -1676,6 +1810,10 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { void CodegenNeuronCppVisitor::print_headers_include() { print_standard_includes(); print_neuron_includes(); + + if (info.thread_callback_register) { + printer->add_line("extern void _nrn_thread_reg(int, int, void(*)(Datum*));"); + } } @@ -1751,6 +1889,7 @@ void CodegenNeuronCppVisitor::print_data_structures(bool print_initializers) { print_mechanism_global_var_structure(print_initializers); print_mechanism_range_var_structure(print_initializers); print_node_data_structure(print_initializers); + print_thread_variables_structure(print_initializers); print_make_instance(); print_make_node_data(); } @@ -1808,6 +1947,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_nrn_alloc(); print_function_prototypes(); print_global_variables_for_hoc(); + print_thread_memory_callbacks(); print_compute_functions(); // only nrn_cur and nrn_state print_sdlists_init(true); print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c1dd010eea..39d7b68e5c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -57,6 +57,20 @@ enum InterpreterWrapper { HOC, Python }; * \{ */ +struct ThreadVariableInfo { + const std::shared_ptr symbol; + + /** There `index` global variables ahead of this one. If one counts array + * global variables as one variable. + */ + size_t index; + + /** The global variables ahead of this one require `offset` doubles to + * store. + */ + size_t offset; +}; + /** * \class CodegenNeuronCppVisitor * \brief %Visitor for printing C++ code compatible with legacy api of NEURON @@ -77,6 +91,12 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Member variables */ /****************************************************************************************/ + /** + * GLOBAL variables in THREADSAFE MOD files that are not read-only are + * converted to thread variables. This is the list of all such variables. + */ + std::vector codegen_thread_variables; + /****************************************************************************************/ /* Generic information getters */ @@ -383,12 +403,23 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { bool use_instance = true) const override; + /** + * Determine the C++ string to print for thread variables. + * + * \param var_info Identifies the thread variable, typically an instance of + * `codegen_thread_variables`. + * \param use_instance Should the variable be accessed via instance or data array + */ + std::string thread_variable_name(const ThreadVariableInfo& var_info, + bool use_instance = true) const; + + /** * Determine variable name in the structure of mechanism properties * * \param name Variable name that is being printed * \param use_instance Should the variable be accessed via instance or data array - * \return The C++ string representing the access to the variable in the neuron + * \return The C++ string representing the variable. * thread structure */ std::string get_variable_name(const std::string& name, bool use_instance = true) const override; @@ -436,6 +467,11 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_mechanism_register() override; + /** + * Print thread variable (de-)initialization functions. + */ + void print_thread_memory_callbacks(); + /** * Print common code for global functions like nrn_init, nrn_cur and nrn_state @@ -674,6 +710,11 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * be included in the struct declaration. */ void print_node_data_structure(bool print_initializers); + + /** + * Print the data structure used to access thread variables. + */ + void print_thread_variables_structure(bool print_initializers); }; diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 3d4aeb24f9..96a8ec964f 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -374,13 +374,15 @@ int main(int argc, const char* argv[]) { // run perfvisitor to update read/write counts PerfVisitor().visit_program(*ast); + auto compatibility_visitor = CodegenCompatibilityVisitor(neuron_code ? "neuron" + : "coreneuron"); // If we want to just check compatibility we return the result if (only_check_compatibility) { - return CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast); + return compatibility_visitor.find_unhandled_ast_nodes(*ast); } // If there is an incompatible construct and code generation is not forced exit NMODL - if (CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast) && !force_codegen) { + if (compatibility_visitor.find_unhandled_ast_nodes(*ast) && !force_codegen) { return 1; } } diff --git a/test/nmodl/transpiler/usecases/global/references.json b/test/nmodl/transpiler/usecases/global/references.json new file mode 100644 index 0000000000..0c732c6752 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/references.json @@ -0,0 +1,3 @@ +[ + { "output_reldir": "neuron", "nmodl_flags": ["--neuron"]} +] diff --git a/test/nmodl/transpiler/usecases/global/simulate.py b/test/nmodl/transpiler/usecases/global/simulate.py new file mode 100644 index 0000000000..d8bf22a6e1 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/simulate.py @@ -0,0 +1,79 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +# The tests "thread variables". Thread variables are how global variables in +# THREADSAFE code are implemented if they're not read-only. The idea is that +# each thread has it's own copy of the global, with zero synchronization. +# +# The test works be creating two sections, with one segment each. Each section +# is assigned to a different section. +# +# Next, we assign values to `g_w` and use the thread variables to assign to a +# value to `y` (different values at different times during the simulation). + +nseg = 1 + +s0 = h.Section() +s0.insert("shared_global") +s0.nseg = nseg + +s1 = h.Section() +s1.insert("shared_global") +s1.nseg = nseg + +pc = h.ParallelContext() +pc.nthread(2) +pc.partition(0, h.SectionList([s0])) +pc.partition(1, h.SectionList([s1])) + +t = h.Vector().record(h._ref_t) +y0 = h.Vector().record(s0(0.5).shared_global._ref_y) +y1 = h.Vector().record(s1(0.5).shared_global._ref_y) + +# Bunch of arbitrary values: +z0, z1 = 3.0, 4.0 +g_w0, g_w1 = 7.0, 2.0 +g_w_init = 48.0 + +# Ensure that the two threads will set different value to `g_w`. +s0(0.5).shared_global.z = z0 +s1(0.5).shared_global.z = z1 + +h.g_w_shared_global = g_w0 +assert h.g_w_shared_global == g_w0 +h.stdinit() +assert h.g_w_shared_global == g_w_init +h.g_w_shared_global = g_w1 +assert h.g_w_shared_global == g_w1 +h.continuerun(1.0 * ms) + +# Arguably the value is unspecified, but currently it's the value of +# on thread 0. +assert h.g_w_shared_global == z0 + +t = np.array(t.as_numpy()) +y0 = np.array(y0.as_numpy()) +y1 = np.array(y1.as_numpy()) +w = h.g_w_shared_global + +# The solution is piecewise constant. These are the pieces: +i0 = [0] +i1 = np.logical_and(0 < t, t < 0.33) +i2 = np.logical_and(0.33 < t, t < 0.66) +i3 = 0.66 < t + +# The values on thread 0: +assert np.all(y0[i0] == g_w_init) +assert np.all(y0[i1] == g_w1) +assert np.all(y0[i2] == 33.3) +assert np.all(y0[i3] == z0) + +# The values on thread 1: +assert y1[i0] == g_w_init +# The value of `g_w` on thread 1 is not specified, after setting +# `g_w` from Python. +# assert np.all(y0[i1] == g_w1) +assert np.all(y1[i2] == 34.3) +assert np.all(y1[i3] == z1) diff --git a/test/nmodl/transpiler/usecases/global/thread_variable.mod b/test/nmodl/transpiler/usecases/global/thread_variable.mod new file mode 100644 index 0000000000..e5da59305d --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/thread_variable.mod @@ -0,0 +1,35 @@ +NEURON { + SUFFIX shared_global + NONSPECIFIC_CURRENT il + RANGE y, z + GLOBAL g_w, g_arr + THREADSAFE +} + +ASSIGNED { + y + z + g_w + g_arr[3] + il +} + +INITIAL { + g_w = 48.0 + g_arr[0] = 10.0 + z + g_arr[1] = 10.1 + g_arr[2] = 10.2 + y = 10.0 +} + +BREAKPOINT { + if(t > 0.33) { + g_w = g_arr[0] + g_arr[1] + g_arr[2] + } + + if(t > 0.66) { + g_w = z + } + y = g_w + il = 0.0000001 * (v - 10.0) +} From b60d841fede4d8a523a1e59abc66626dd9db1ff9 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 3 Jun 2024 10:32:15 +0200 Subject: [PATCH 656/871] Add TABLE statement for NEURON codegen (BlueBrain/nmodl#1247) NMODL Repo SHA: BlueBrain/nmodl@b4ef5372edf1419f7c11f148d262a3b943a371f1 --- .../codegen_coreneuron_cpp_visitor.cpp | 10 +- .../codegen_coreneuron_cpp_visitor.hpp | 10 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 20 ++- src/nmodl/codegen/codegen_cpp_visitor.hpp | 55 ++++++- .../codegen/codegen_neuron_cpp_visitor.cpp | 113 +++++++++++-- .../codegen/codegen_neuron_cpp_visitor.hpp | 17 +- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/table/simulate.py | 148 ++++++++++++++++++ .../nmodl/transpiler/usecases/table/table.mod | 46 ++++++ 9 files changed, 386 insertions(+), 34 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/table/simulate.py create mode 100644 test/nmodl/transpiler/usecases/table/table.mod diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index e4c347dbcc..421ae172ac 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -416,7 +416,7 @@ void CodegenCoreneuronCppVisitor::print_check_table_thread_function() { printer->add_line("double v = 0;"); for (const auto& function: info.functions_with_table) { - auto method_name_str = method_name("check_" + function->get_node_name()); + auto method_name_str = method_name(table_function_prefix() + function->get_node_name()); auto arguments = internal_method_arguments(); printer->fmt_line("{}({});", method_name_str, arguments); } @@ -425,10 +425,12 @@ void CodegenCoreneuronCppVisitor::print_check_table_thread_function() { } -void CodegenCoreneuronCppVisitor::print_function_or_procedure(const ast::Block& node, - const std::string& name) { +void CodegenCoreneuronCppVisitor::print_function_or_procedure( + const ast::Block& node, + const std::string& name, + const std::unordered_set& specifiers) { printer->add_newline(2); - print_function_declaration(node, name); + print_function_declaration(node, name, specifiers); printer->add_text(" "); printer->push_block(); diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 55e2ff84cb..3cabec68ff 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -358,12 +358,10 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_check_table_thread_function(); - /** - * Print nmodl function or procedure (common code) - * \param node the AST node representing the function or procedure in NMODL - * \param name the name of the function or procedure - */ - void print_function_or_procedure(const ast::Block& node, const std::string& name) override; + void print_function_or_procedure(const ast::Block& node, + const std::string& name, + const std::unordered_set& specifiers = { + CppObjectSpecifier::Inline}) override; /** diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 786f950c45..22d519a72c 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -63,6 +63,11 @@ bool CodegenCppVisitor::has_parameter_of_name(const T& node, const std::string& } +std::string CodegenCppVisitor::table_function_prefix() const { + return "lazy_update_"; +} + + /** * \details Certain statements like unit, comment, solve can/need to be skipped * during code generation. Note that solve block is wrapped in expression @@ -1307,7 +1312,8 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { auto float_type = default_float_data_type(); printer->add_newline(2); - printer->fmt_push_block("void check_{}({})", + printer->fmt_push_block("void {}{}({})", + table_function_prefix(), method_name(name), get_parameter_str(internal_params)); { @@ -1387,6 +1393,18 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { printer->pop_block(); } +std::string CodegenCppVisitor::get_object_specifiers( + const std::unordered_set& specifiers) { + std::string result; + for (const auto& specifier: specifiers) { + if (!result.empty()) { + result += " "; + } + result += object_specifier_map[specifier]; + } + return result; +} + const ast::TableStatement* CodegenCppVisitor::get_table_statement(const ast::Block& node) { const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index cae6650eb2..729fe07003 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -103,6 +103,22 @@ enum class MemberType { }; +/// various specifiers (mostly for function codegen) +enum class CppObjectSpecifier { + Inline, + Static, + Virtual, + Explicit, + Friend, + Constexpr, + Extern, + ExternC, + ThreadLocal, + Const, + Volatile +}; + + /** * \class IndexVariableInfo * \brief Helper to represent information about index/int variables @@ -793,8 +809,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Print nmodl function or procedure (common code) * \param node the AST node representing the function or procedure in NMODL * \param name the name of the function or procedure + * \param hidden whether the function should be declared `static` */ - virtual void print_function_or_procedure(const ast::Block& node, const std::string& name) = 0; + virtual void print_function_or_procedure( + const ast::Block& node, + const std::string& name, + const std::unordered_set& specifiers) = 0; /** @@ -1046,6 +1066,11 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual std::string get_variable_name(const std::string& name, bool use_instance = true) const = 0; + /** + * Prefix used for the function that performs the lazy update + */ + std::string table_function_prefix() const; + /** * Return ion variable name and corresponding ion read variable name. @@ -1284,6 +1309,19 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ virtual void print_codegen_routines() = 0; + std::unordered_map object_specifier_map = { + {CppObjectSpecifier::Inline, "inline"}, + {CppObjectSpecifier::Static, "static"}, + {CppObjectSpecifier::Constexpr, "constexpr"}, + {CppObjectSpecifier::Volatile, "volatile"}, + {CppObjectSpecifier::Virtual, "virtual"}, + {CppObjectSpecifier::Explicit, "explicit"}, + {CppObjectSpecifier::Friend, "friend"}, + {CppObjectSpecifier::Extern, "extern"}, + {CppObjectSpecifier::ExternC, "extern \"C\""}, + {CppObjectSpecifier::ThreadLocal, "thread_local"}, + {CppObjectSpecifier::Const, "const"}}; + /** * Print the nmodl constants used in backend code @@ -1381,6 +1419,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { const ast::TableStatement* get_table_statement(const ast::Block&); + std::string get_object_specifiers(const std::unordered_set&); + /** * Print prototype declarations of functions or procedures * \tparam T The AST node type of the node (must be of nmodl::ast::Ast or subclass) @@ -1388,7 +1428,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param name A user defined name for the function */ template - void print_function_declaration(const T& node, const std::string& name); + void print_function_declaration(const T& node, + const std::string& name, + const std::unordered_set& = { + CppObjectSpecifier::Inline}); }; /* Templated functions need to be defined in header file */ @@ -1413,7 +1456,10 @@ void CodegenCppVisitor::print_vector_elements(const std::vector& elements, * different in case of table statement. */ template -void CodegenCppVisitor::print_function_declaration(const T& node, const std::string& name) { +void CodegenCppVisitor::print_function_declaration( + const T& node, + const std::string& name, + const std::unordered_set& specifiers) { enable_variable_name_lookup = false; auto type = default_float_data_type(); @@ -1431,7 +1477,8 @@ void CodegenCppVisitor::print_function_declaration(const T& node, const std::str } printer->add_indent(); - printer->fmt_text("inline {} {}({})", + printer->fmt_text("{} {} {}({})", + get_object_specifiers(specifiers), return_type, method_name(name), get_parameter_str(internal_params)); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 64f79d38b0..04da0f6c96 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -48,6 +48,11 @@ std::string CodegenNeuronCppVisitor::backend_name() const { } +std::string CodegenNeuronCppVisitor::table_thread_function_name() const { + return "_check_table_thread"; +} + + /****************************************************************************************/ /* Common helper routines accross codegen functions */ /****************************************************************************************/ @@ -147,6 +152,46 @@ void CodegenNeuronCppVisitor::print_point_process_function_definitions() { } +void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { + if (info.table_count == 0) { + return; + } + + // print declarations of `check_*` functions + for (const auto& function: info.functions_with_table) { + auto name = function->get_node_name(); + auto internal_params = internal_method_parameters(); + printer->fmt_line("void {}{}({});", + table_function_prefix(), + method_name(name), + get_parameter_str(internal_params)); + } + + ParamVector args = {{"", "Memb_list*", "", "_ml"}, + {"", "size_t", "", "id"}, + {"", "Datum*", "", "_ppvar"}, + {"", "Datum*", "", "_thread"}, + {"", "NrnThread*", "", "_nt"}, + {"", "int", "", "_type"}, + {"", "const _nrn_model_sorted_token&", "", "_sorted_token"}}; + + // definition of `_check_table_thread` function + // signature must be same as the `nrn_thread_table_check_t` type + printer->fmt_line("static void {}({})", table_thread_function_name(), get_parameter_str(args)); + printer->push_block(); + printer->add_line("_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml, _type};"); + printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + + for (const auto& function: info.functions_with_table) { + auto method_name_str = function->get_node_name(); + printer->fmt_line("{}{}{}(&_lmr, inst, id, _ppvar, _thread, _nt);", + table_function_prefix(), + method_name_str, + info.rsuffix); + } + printer->pop_block(); +} + void CodegenNeuronCppVisitor::print_setdata_functions() { printer->add_line("/* Neuron setdata functions */"); printer->add_line("extern void _nrn_setdata_reg(int, void(*)(Prop*));"); @@ -202,15 +247,18 @@ void CodegenNeuronCppVisitor::print_function_prototypes() { print_point_process_function_definitions(); print_setdata_functions(); + print_check_table_function_prototypes(); /// TODO: Add mechanism function and procedures declarations } -void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node, - const std::string& name) { +void CodegenNeuronCppVisitor::print_function_or_procedure( + const ast::Block& node, + const std::string& name, + const std::unordered_set& specifiers) { printer->add_newline(2); - print_function_declaration(node, name); + print_function_declaration(node, name, specifiers); printer->add_text(" "); printer->push_block(); @@ -240,9 +288,13 @@ void CodegenNeuronCppVisitor::print_function_or_procedure(const ast::Block& node void CodegenNeuronCppVisitor::print_function_procedure_helper(const ast::Block& node) { auto name = node.get_node_name(); - if (info.function_uses_table(name)) { - throw std::runtime_error("Function tables not implemented."); + auto new_name = "f_" + name; + print_function_or_procedure(node, + new_name, + {CppObjectSpecifier::Static, CppObjectSpecifier::Inline}); + print_table_check_function(node); + print_table_replacement_function(node); } else { print_function_or_procedure(node, name); } @@ -260,7 +312,7 @@ void CodegenNeuronCppVisitor::print_function(const ast::FunctionBlock& node) { // name of return variable std::string return_var; if (info.function_uses_table(name)) { - throw std::runtime_error("Function tables not implemented."); + return_var = "ret_f_" + name; } else { return_var = "ret_" + name; } @@ -341,7 +393,10 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( } printer->fmt_line("auto inst = make_instance_{}(_ml_real);", info.mod_suffix); if (info.function_uses_table(block_name)) { - printer->fmt_line("_check_{}({})", block_name, internal_method_arguments()); + printer->fmt_line("{}{}({});", + table_function_prefix(), + method_name(block_name), + internal_method_arguments()); } const auto get_func_call_str = [&]() { const auto params = function_or_procedure_block->get_parameters(); @@ -814,7 +869,30 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in if (info.table_count > 0) { - throw std::runtime_error("Not implemented, global table count."); + // basically the same code as coreNEURON uses + printer->fmt_line("double usetable{};", print_initializers ? "{1}" : ""); + codegen_global_variables.push_back(make_symbol(naming::USE_TABLE_VARIABLE)); + + for (const auto& block: info.functions_with_table) { + const auto& name = block->get_node_name(); + printer->fmt_line("{} tmin_{}{};", float_type, name, value_initialize); + printer->fmt_line("{} mfac_{}{};", float_type, name, value_initialize); + codegen_global_variables.push_back(make_symbol("tmin_" + name)); + codegen_global_variables.push_back(make_symbol("mfac_" + name)); + } + + for (const auto& variable: info.table_statement_variables) { + auto const name = "t_" + variable->get_name(); + auto const num_values = variable->get_num_values(); + if (variable->is_array()) { + int array_len = variable->get_length(); + printer->fmt_line( + "{} {}[{}][{}]{};", float_type, name, array_len, num_values, value_initialize); + } else { + printer->fmt_line("{} {}[{}]{};", float_type, name, num_values, value_initialize); + } + codegen_global_variables.push_back(make_symbol(name)); + } } for (const auto& var: info.state_vars) { @@ -1033,6 +1111,11 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(); printer->fmt_line("mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); + // register the table-checking function + if (info.table_count > 0) { + printer->fmt_line("_nrn_thread_table_reg(mech_type, {});", table_thread_function_name()); + } + /// Call _nrn_mechanism_register_data_fields() with the correct arguments /// Geenerated code follows the style underneath /// @@ -1411,7 +1494,6 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->push_block("for (int id = 0; id < nodecount; id++)"); // begin for if (breakpoint_exist()) { - printer->add_line("// set conductances properly"); printer->add_line("int node_id = node_data.nodeindices[id];"); printer->fmt_line("node_data.node_diagonal[node_id] += inst.{}[id];", info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE @@ -1552,11 +1634,9 @@ void CodegenNeuronCppVisitor::print_nrn_state() { print_global_function_common_code(BlockType::State); printer->push_block("for (int id = 0; id < nodecount; id++)"); - printer->add_multi_line(R"CODE( -int node_id = node_data.nodeindices[id]; -auto* _ppvar = _ml_arg->pdata[id]; -auto v = node_data.node_voltages[node_id]; -)CODE"); + printer->add_line("int node_id = node_data.nodeindices[id];"); + printer->add_line("auto* _ppvar = _ml_arg->pdata[id];"); + printer->add_line("auto v = node_data.node_voltages[node_id];"); /** * \todo Eigen solver node also emits IonCurVar variable in the functor @@ -1784,7 +1864,6 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { printer->add_line("node_data.node_rhs[node_id] -= rhs;"); if (breakpoint_exist()) { - printer->add_line("// remember the conductances so we can set them later"); printer->fmt_line("inst.{}[id] = g;", info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE : naming::CONDUCTANCE_VARIABLE); @@ -1872,6 +1951,10 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { printer->add_line("Prop* hoc_getdata_range(int type);"); } /// TODO: More prints here? + // for registration of tables + if (info.table_count > 0) { + printer->add_line("void _nrn_thread_table_reg(int, nrn_thread_table_check_t);"); + } } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 39d7b68e5c..a1a1bfae6b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -114,6 +114,11 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ std::string backend_name() const override; + /** + * Name of the threaded table checking function + */ + std::string table_thread_function_name() const; + /****************************************************************************************/ /* Common helper routines accross codegen functions */ @@ -208,11 +213,15 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** - * Print nmodl function or procedure (common code) - * \param node the AST node representing the function or procedure in NMODL - * \param name the name of the function or procedure + * Print all `check_*` function declarations */ - void print_function_or_procedure(const ast::Block& node, const std::string& name) override; + void print_check_table_function_prototypes(); + + + void print_function_or_procedure(const ast::Block& node, + const std::string& name, + const std::unordered_set& specifiers = { + CppObjectSpecifier::Inline}) override; /** diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 9483b3126f..5147056538 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -12,6 +12,7 @@ set(NMODL_USECASE_DIRS point_process parameter func_in_breakpoint + table recursion) foreach(usecase ${NMODL_USECASE_DIRS}) diff --git a/test/nmodl/transpiler/usecases/table/simulate.py b/test/nmodl/transpiler/usecases/table/simulate.py new file mode 100644 index 0000000000..5d8ac69c39 --- /dev/null +++ b/test/nmodl/transpiler/usecases/table/simulate.py @@ -0,0 +1,148 @@ +import numpy as np +from neuron import gui +from neuron import h + + +def setup_sim(): + section = h.Section() + section.insert("tbl") + + return section + + +def check_solution(y_no_table, y_table, rtol1=1e-2, rtol2=1e-8): + assert np.allclose(y_no_table, y_table, rtol=rtol1), f"{y_no_table} != {y_table}" + # if the table is not used, we should get identical results, but we don't, + # hence the assert below + assert not np.allclose( + y_no_table, y_table, rtol=rtol2 + ), f"Broken test logic: {y_no_table} == {y_table}" + + +def test_function(): + section = setup_sim() + + x = np.linspace(-3, 5, 500) + + func = section(0.5).tbl.quadratic + + h.c1_tbl = 1 + h.c2_tbl = 2 + + h.usetable_tbl = 0 + y_no_table = np.array([func(i) for i in x]) + + h.usetable_tbl = 1 + y_table = np.array([func(i) for i in x]) + + check_solution(y_table, y_no_table, rtol1=1e-4) + + # verify that the table just "clips" the values outside of the range + assert func(x[0] - 10) == y_table[0] + assert func(x[-1] + 10) == y_table[-1] + + # change parameters and verify + h.c1_tbl = 3 + h.c2_tbl = 4 + + h.usetable_tbl = 0 + y_params_no_table = np.array([func(i) for i in x]) + + h.usetable_tbl = 1 + y_params_table = np.array([func(i) for i in x]) + + check_solution(y_params_table, y_params_no_table, rtol1=1e-4) + + +def test_procedure(): + section = setup_sim() + + x = np.linspace(-4, 6, 300) + + proc = section(0.5).tbl.sinusoidal + + def call_proc_return_values(arg): + proc(arg) + return section(0.5).tbl.v1, section(0.5).tbl.v2 + + def check_table(procedure, **kwargs): + for key, value in kwargs.items(): + setattr(h, f"{key}_tbl", value) + + h.usetable_tbl = 0 + values_no_table = np.array([procedure(i) for i in x]) + + h.usetable_tbl = 1 + values_table = np.array([procedure(i) for i in x]) + + assert np.allclose( + values_no_table, + values_table, + rtol=1e-3, + ), f"{values_no_table} != {values_table}" + + assert not np.allclose( + values_no_table, + values_table, + rtol=1e-8, + ), f"Broken test logic: {values_no_table} == {values_table}" + + check_table(call_proc_return_values, c1=1, c2=2) + check_table(call_proc_return_values, c1=0.1, c2=0.3) + + +def simulate(): + s = setup_sim() + + h.k_tbl = -0.1 + h.d_tbl = -40 + s(0.5).tbl.gmax = 0.001 + + vvec = h.Vector().record(s(0.5)._ref_v) + tvec = h.Vector().record(h._ref_t) + + # run without a table + h.usetable_tbl = 0 + h.run() + + t = np.array(tvec.as_numpy()) + v_exact = np.array(vvec.as_numpy()) + + # run with a table + h.usetable_tbl = 1 + h.run() + + v_table = np.array(vvec.as_numpy()) + + check_solution(v_table, v_exact, rtol1=1e-2) + + # run without a table, and changing params + h.usetable_tbl = 0 + h.k_tbl = -0.05 + h.d_tbl = -45 + + h.run() + + v_params = np.array(vvec.as_numpy()) + + # run with a table (same params as above) + h.usetable_tbl = 1 + h.run() + + v_params_table = np.array(vvec.as_numpy()) + + check_solution(v_params, v_params_table, rtol1=1e-2) + + +def plot_solution(t, y_exact, y): + import matplotlib.pyplot as plt + + plt.plot(t, y_exact, label="exact") + plt.plot(t, y, label="table", ls="--") + plt.show() + + +if __name__ == "__main__": + test_function() + test_procedure() + simulate() diff --git a/test/nmodl/transpiler/usecases/table/table.mod b/test/nmodl/transpiler/usecases/table/table.mod new file mode 100644 index 0000000000..d43774690c --- /dev/null +++ b/test/nmodl/transpiler/usecases/table/table.mod @@ -0,0 +1,46 @@ +NEURON { + SUFFIX tbl + NONSPECIFIC_CURRENT i + RANGE e, g, gmax, v1, v2 + GLOBAL k, d, c1, c2 +} + +PARAMETER { + e = 0 + gmax = 0 + k = .1 + d = -50 + c1 = 1 + c2 = 2 +} + +ASSIGNED { + g + i + v + sig + v1 + v2 +} + +BREAKPOINT { + sigmoid1(v) + g = gmax * sig + i = g*(v - e) +} + +PROCEDURE sigmoid1(v) { + TABLE sig DEPEND k, d FROM -127 TO 128 WITH 155 + sig = 1/(1 + exp(k*(v - d))) +} + +FUNCTION quadratic(arg) { + TABLE DEPEND c1, c2 FROM -3 TO 5 WITH 500 + quadratic = c1 * arg * arg + c2 +} + +PROCEDURE sinusoidal(arg) { + TABLE v1, v2 DEPEND c1, c2 FROM -4 TO 6 WITH 300 + v1 = sin(c1 * arg) + 2 + v2 = cos(c2 * arg) + 2 +} From e086d1db61729e926783d9bf5dd2fedcaafd7240 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 3 Jun 2024 11:07:32 +0200 Subject: [PATCH 657/871] Minor cleanup. (BlueBrain/nmodl#1281) NMODL Repo SHA: BlueBrain/nmodl@5366532ee2ca2f4961e635672f18043c69e23443 --- cmake/nmodl/CompilerHelper.cmake | 2 +- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 10 ++++++---- .../transpiler/usecases/cnexp_scalar/leonhard.mod | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index c726f207a3..899a245eba 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -48,5 +48,5 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(NMODL_COMPILER_MAX_ERROR_FLAG -fmax-errors=${NMODL_MAX_ERRORS}) + set(NMODL_COMPILER_MAX_ERRORS_FLAG -fmax-errors=${NMODL_MAX_ERRORS}) endif() diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 04da0f6c96..ab01972853 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1065,13 +1065,14 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_line("/** register channel with the simulator */"); printer->fmt_push_block("extern \"C\" void _{}_reg()", info.mod_file); printer->add_line("_initlists();"); - printer->add_newline(); for (const auto& ion: info.ions) { printer->fmt_line("ion_reg(\"{}\", {});", ion.name, "-10000."); } - printer->add_newline(); + if (!info.ions.empty()) { + printer->add_newline(); + } if (info.diam_used) { printer->add_line("_morphology_sym = hoc_lookup(\"morphology\");"); @@ -1081,8 +1082,9 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { for (const auto& ion: info.ions) { printer->fmt_line("_{0}_sym = hoc_lookup(\"{0}_ion\");", ion.name); } - - printer->add_newline(); + if (!info.ions.empty()) { + printer->add_newline(); + } const auto compute_functions_parameters = breakpoint_exist() diff --git a/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod b/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod index eef4b5476f..eac8cab540 100644 --- a/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod +++ b/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX leonhard + SUFFIX leonhard } STATE { x } @@ -9,7 +9,7 @@ INITIAL { } BREAKPOINT { - SOLVE dX METHOD cnexp + SOLVE dX METHOD cnexp } DERIVATIVE dX { x' = -x } From 766d2758f72173085b563907a8b7507fc61e243e Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 4 Jun 2024 10:39:57 +0200 Subject: [PATCH 658/871] Replace `-fmax-errors` with `-ferror-limit` on Clang (BlueBrain/nmodl#1297) NMODL Repo SHA: BlueBrain/nmodl@ed8d6b8330db085bdb4b77fcf95c429b2f3c787b --- cmake/nmodl/CompilerHelper.cmake | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 899a245eba..1c14b43787 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -47,6 +47,8 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") endif() endif() -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") +if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(NMODL_COMPILER_MAX_ERRORS_FLAG -fmax-errors=${NMODL_MAX_ERRORS}) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(NMODL_COMPILER_MAX_ERRORS_FLAG -ferror-limit=${NMODL_MAX_ERRORS}) endif() From 45e25823f5d083f594761209f631c78c1c88d91e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 4 Jun 2024 13:56:06 +0200 Subject: [PATCH 659/871] Fix missing null-terminator. (BlueBrain/nmodl#1291) NMODL Repo SHA: BlueBrain/nmodl@a422ada397a8e0cae7ac6710427d74403e1bdc44 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index ab01972853..e2717bceac 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1042,7 +1042,7 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { hoc_function_name(func_name)); } - printer->add_line("{0, 0}"); + printer->add_line("{nullptr, nullptr}"); printer->decrease_indent(); printer->add_line("};"); if (!info.point_process) { @@ -1055,6 +1055,7 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { const auto func_name = function->get_node_name(); printer->fmt_line("{{\"{}\", {}}},", func_name, py_function_name(func_name)); } + printer->add_line("{nullptr, nullptr}"); printer->pop_block(";"); } } From 82f8ba698410607f0926661f5b5fd6987b5d21cf Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 4 Jun 2024 13:56:41 +0200 Subject: [PATCH 660/871] Fix missing SUFFIX. (BlueBrain/nmodl#1293) NMODL Repo SHA: BlueBrain/nmodl@73bf3582d44c57a05715e24a521a65b1848f9747 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 7 +++++ src/nmodl/codegen/codegen_helper_visitor.cpp | 2 -- .../codegen_coreneuron_cpp_visitor.cpp | 29 ++++++++++--------- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/suffix/no_suffix.mod | 12 ++++++++ .../usecases/suffix/point_suffix.mod | 13 +++++++++ .../transpiler/usecases/suffix/simulate.py | 14 +++++++++ 7 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/suffix/no_suffix.mod create mode 100644 test/nmodl/transpiler/usecases/suffix/point_suffix.mod create mode 100644 test/nmodl/transpiler/usecases/suffix/simulate.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 22d519a72c..004399c2dc 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -6,6 +6,8 @@ */ #include "codegen/codegen_cpp_visitor.hpp" +#include + #include "config/config.h" #include "ast/all.hpp" @@ -1145,6 +1147,11 @@ void CodegenCppVisitor::setup(const Program& node) { info = v.analyze(node); info.mod_file = mod_filename; + if (info.mod_suffix == "") { + info.mod_suffix = std::filesystem::path(mod_filename).stem(); + } + info.rsuffix = info.point_process ? "" : "_" + info.mod_suffix; + if (!info.vectorize) { logger->warn( "CodegenCoreneuronCppVisitor : MOD file uses non-thread safe constructs of NMODL"); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 8655e28b86..417eca131e 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -491,10 +491,8 @@ void CodegenHelperVisitor::visit_suffix(const Suffix& node) { info.point_process = true; } info.mod_suffix = node.get_node_name(); - info.rsuffix = info.point_process ? "" : "_" + info.mod_suffix; } - void CodegenHelperVisitor::visit_electrode_current(const ElectrodeCurrent& /* node */) { info.electrode_current = true; } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp index ec98b5b076..f049119270 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -781,7 +781,7 @@ SCENARIO("Check that top verbatim blocks are well generated", "[codegen][top ver double a = 2.; - foo_(nt); + foo_temp(nt); &tqitem; pnodecount+id;)"; REQUIRE_THAT(generated, ContainsSubstring(expected_code)); @@ -845,7 +845,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even })"; REQUIRE_THAT(gpu_generated, ContainsSubstring(gpu_net_send_expected_code)); std::string net_receive_kernel_expected_code = - R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { + R"(static inline void net_receive_kernel_temp(double t, Point_process* pnt, temp_Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { int tid = pnt->_tid; int id = pnt->_i_instance; double v = 0; @@ -868,7 +868,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even })"; REQUIRE_THAT(generated, ContainsSubstring(net_receive_kernel_expected_code)); std::string net_receive_expected_code = - R"(static void net_receive_(Point_process* pnt, int weight_index, double flag) { + R"(static void net_receive_temp(Point_process* pnt, int weight_index, double flag) { NrnThread* nt = nrn_threads + pnt->_tid; Memb_list* ml = get_memb_list(nt); NetReceiveBuffer_t* nrb = ml->_net_receive_buffer; @@ -883,14 +883,15 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even nrb->_cnt++; })"; REQUIRE_THAT(generated, ContainsSubstring(net_receive_expected_code)); - std::string net_buf_receive_expected_code = R"(void net_buf_receive_(NrnThread* nt) { + std::string net_buf_receive_expected_code = + R"(void net_buf_receive_temp(NrnThread* nt) { Memb_list* ml = get_memb_list(nt); if (!ml) { return; } NetReceiveBuffer_t* nrb = ml->_net_receive_buffer; - auto* const inst = static_cast<_Instance*>(ml->instance); + auto* const inst = static_cast(ml->instance); int count = nrb->_displ_cnt; #pragma omp simd #pragma ivdep @@ -904,7 +905,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even int weight_index = nrb->_weight_index[index]; double flag = nrb->_nrb_flag[index]; Point_process* point_process = nt->pntprocs + offset; - net_receive_kernel_(t, point_process, inst, nt, ml, weight_index, flag); + net_receive_kernel_temp(t, point_process, inst, nt, ml, weight_index, flag); } } nrb->_displ_cnt = 0; @@ -930,7 +931,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even })"; REQUIRE_THAT(generated, ContainsSubstring(net_init_expected_code)); std::string set_pnt_receive_expected_code = - "set_pnt_receive(mech_type, net_receive_, net_init, num_net_receive_args());"; + "set_pnt_receive(mech_type, net_receive_temp, net_init, num_net_receive_args());"; REQUIRE_THAT(generated, ContainsSubstring(set_pnt_receive_expected_code)); } } @@ -957,7 +958,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even double* weights = nt->weights; Datum* indexes = ml->pdata; ThreadDatum* thread = ml->_thread; - auto* const inst = static_cast<_Instance*>(ml->instance); + auto* const inst = static_cast(ml->instance); a = 1.0; auto& nsb = ml->_net_send_buffer; @@ -1009,7 +1010,7 @@ SCENARIO("Check that codegen generate event functions well", "[codegen][net_even THEN("New code is generated for for_netcons") { auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string net_receive_kernel_expected_code = - R"(static inline void net_receive_kernel_(double t, Point_process* pnt, _Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { + R"(static inline void net_receive_kernel_temp(double t, Point_process* pnt, temp_Instance* inst, NrnThread* nt, Memb_list* ml, int weight_index, double flag) { int tid = pnt->_tid; int id = pnt->_i_instance; double v = 0; @@ -1067,9 +1068,9 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { THEN("Correct code is generated") { auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string newton_state_expected_code = R"(namespace { - struct _newton_state_ { + struct _newton_state_temp { int operator()(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) const { - auto* const inst = static_cast<_Instance*>(ml->instance); + auto* const inst = static_cast(ml->instance); double* savstate1 = static_cast(thread[dith1()].pval); auto const& slist1 = inst->global->slist1; auto const& dlist1 = inst->global->dlist1; @@ -1089,8 +1090,8 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { })"; REQUIRE_THAT(generated, ContainsSubstring(newton_state_expected_code)); std::string state_expected_code = - R"(int state_(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) { - auto* const inst = static_cast<_Instance*>(ml->instance); + R"(int state_temp(int id, int pnodecount, double* data, Datum* indexes, ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v) { + auto* const inst = static_cast(ml->instance); double* savstate1 = (double*) thread[dith1()].pval; auto const& slist1 = inst->global->slist1; auto& slist2 = inst->global->slist2; @@ -1098,7 +1099,7 @@ SCENARIO("Some tests on derivimplicit", "[codegen][derivimplicit_solver]") { for (int i=0; i<1; i++) { savstate1[i*pnodecount+id] = data[slist1[i]*pnodecount+id]; } - int reset = nrn_newton_thread(static_cast(*newtonspace1(thread)), 1, slist2, _newton_state_{}, dlist2, id, pnodecount, data, indexes, thread, nt, ml, v); + int reset = nrn_newton_thread(static_cast(*newtonspace1(thread)), 1, slist2, _newton_state_temp{}, dlist2, id, pnodecount, data, indexes, thread, nt, ml, v); return reset; })"; REQUIRE_THAT(generated, ContainsSubstring(state_expected_code)); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 5147056538..3c0e3017db 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -12,6 +12,7 @@ set(NMODL_USECASE_DIRS point_process parameter func_in_breakpoint + suffix table recursion) diff --git a/test/nmodl/transpiler/usecases/suffix/no_suffix.mod b/test/nmodl/transpiler/usecases/suffix/no_suffix.mod new file mode 100644 index 0000000000..a582654f29 --- /dev/null +++ b/test/nmodl/transpiler/usecases/suffix/no_suffix.mod @@ -0,0 +1,12 @@ +: The name of this mod file should be `no_suffix`. +NEURON { + RANGE x +} + +ASSIGNED { + x +} + +INITIAL { + x = 42 +} diff --git a/test/nmodl/transpiler/usecases/suffix/point_suffix.mod b/test/nmodl/transpiler/usecases/suffix/point_suffix.mod new file mode 100644 index 0000000000..ed14c1cab9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/suffix/point_suffix.mod @@ -0,0 +1,13 @@ +: The name of this mod file should be `point_suffix`. +NEURON { + POINT_PROCESS point_suffix + RANGE x +} + +ASSIGNED { + x +} + +INITIAL { + x = 42 +} diff --git a/test/nmodl/transpiler/usecases/suffix/simulate.py b/test/nmodl/transpiler/usecases/suffix/simulate.py new file mode 100644 index 0000000000..7b26d81dfd --- /dev/null +++ b/test/nmodl/transpiler/usecases/suffix/simulate.py @@ -0,0 +1,14 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +s = h.Section() +s.insert("no_suffix") + +pp = h.point_suffix(s(0.5)) + +h.stdinit() + +assert s(0.25).no_suffix.x == 42.0, f"{s(0.25).no_suffix.x=}" +assert pp.x == 42.0, f"{pp.x=}" From 7d723f921052a32d452498181f82e276a45ffa3f Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 4 Jun 2024 14:07:55 +0200 Subject: [PATCH 661/871] Remove generation of INCLUDE file in the tests (BlueBrain/nmodl#1295) * Remove INCLUDE mod file generation in tests - include mod files generation cause race condition with parallel tests, see BlueBrain/nmodl#1290 - the included files are trivial examples for testing and they don't need to be generated from the code - could be copied via configure_file - cmake could generate those at a configure time Considering the use case and it's triviality, I thought 2nd option is better. That also allows to remove some extra code/logic. NMODL Repo SHA: BlueBrain/nmodl@844e6bdc4b18b33317acc2b6aa1cfc1d4ecc4093 --- test/nmodl/transpiler/unit/CMakeLists.txt | 4 ++++ test/nmodl/transpiler/unit/parser/parser.cpp | 3 --- .../transpiler/unit/utils/test_utils.cpp | 19 ------------------- .../transpiler/unit/utils/test_utils.hpp | 12 ------------ test/nmodl/transpiler/unit/visitor/nmodl.cpp | 1 - 5 files changed, 4 insertions(+), 35 deletions(-) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 62d6a0ad10..7c554474da 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -167,6 +167,10 @@ foreach( "${_environment_vars_list}") endforeach() +# Generate include files used in test input, see test/unit/utils/nmodl_constructs.cpp +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/Unit.inc" "UNITSON \n UNITSOFF \n UNITSON \n UNITSOFF") +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/included.file" "TITLE") + # ============================================================================= # pybind11 tests # ============================================================================= diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 67c3472718..036a965a62 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -133,7 +133,6 @@ SCENARIO("NMODL parser accepts empty unit specification") { } SCENARIO("NMODL parser running number of valid NMODL constructs") { - TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; GIVEN(test_case.name) { @@ -172,7 +171,6 @@ SCENARIO("Check that the parser doesn't crash when passing invalid INCLUDE const } GIVEN("An invalid included file") { - TempFile included("included.file", nmodl_invalid_constructs.at("title_1").input); REQUIRE_THROWS_WITH(is_valid_construct("INCLUDE \"included.file\""), Catch::Matchers::ContainsSubstring("unexpected End of file")); } @@ -199,7 +197,6 @@ SCENARIO("NEURON block can add CURIE information", "[parser][represents]") { SCENARIO("Check parents in valid NMODL constructs") { nmodl::parser::NmodlDriver driver; - TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { // parse the string and get the ast const auto ast = driver.parse_string(construct.second.input); diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index 8f06671beb..c9d9ee3679 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -10,10 +10,6 @@ #include "utils/string_utils.hpp" #include -#include -#include - -namespace fs = std::filesystem; namespace nmodl { namespace test_utils { @@ -85,20 +81,5 @@ std::string reindent_text(const std::string& text) { return indented_text; } -TempFile::TempFile(fs::path path, const std::string& content) - : path_(std::move(path)) { - std::ofstream output(path_); - output << content; -} - -TempFile::~TempFile() { - try { - fs::remove(path_); - } catch (...) { - // TODO: remove .string() once spdlog use fmt 9.1.0 - logger->error("Cannot delete temporary file {}", path_.string()); - } -} - } // namespace test_utils } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/utils/test_utils.hpp b/test/nmodl/transpiler/unit/utils/test_utils.hpp index a3661a177a..22355b7618 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.hpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.hpp @@ -7,7 +7,6 @@ #pragma once -#include #include namespace nmodl { @@ -15,16 +14,5 @@ namespace test_utils { std::string reindent_text(const std::string& text); -/** - * \brief Create an empty file which is then removed when the C++ object is destructed - */ -struct TempFile { - TempFile(std::filesystem::path path, const std::string& content); - ~TempFile(); - - private: - std::filesystem::path path_; -}; - } // namespace test_utils } // namespace nmodl diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 3550239add..51caf0ade9 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -41,7 +41,6 @@ std::string run_nmodl_visitor(const std::string& text) { } SCENARIO("Convert AST back to NMODL form", "[visitor][nmodl]") { - TempFile unit("Unit.inc", nmodl_valid_constructs.at("unit_statement_1").input); for (const auto& construct: nmodl_valid_constructs) { auto test_case = construct.second; const std::string& input_nmodl_text = reindent_text(test_case.input); From 9e2adea1240fd875b1126b11571ff0dcdb3b6153 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 4 Jun 2024 16:06:41 +0200 Subject: [PATCH 662/871] Fix rendering of docs (BlueBrain/nmodl#1298) --------- Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@751377d998c156c3655d0ab9f2297160bb9ce370 --- docs/nmodl/transpiler/INSTALL.rst | 10 ++-- docs/nmodl/transpiler/contributing.rst | 4 +- docs/nmodl/transpiler/install.rst | 5 +- .../nmodl/transpiler/CONTRIBUTING.rst | 55 +++++-------------- docs/nmodl/transpiler/readme.rst | 7 +-- src/nmodl/nmodl/README.rst | 12 ++-- 6 files changed, 28 insertions(+), 65 deletions(-) mode change 100644 => 120000 docs/nmodl/transpiler/contributing.rst mode change 100644 => 120000 docs/nmodl/transpiler/install.rst mode change 100644 => 120000 docs/nmodl/transpiler/readme.rst diff --git a/docs/nmodl/transpiler/INSTALL.rst b/docs/nmodl/transpiler/INSTALL.rst index 65bf1e2ca8..08262235ed 100644 --- a/docs/nmodl/transpiler/INSTALL.rst +++ b/docs/nmodl/transpiler/INSTALL.rst @@ -34,8 +34,8 @@ support is necessary. Make sure you have following packages available: - Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap -On OS X -~~~~~~~ +On MacOS +~~~~~~~~ Typically the versions of bison and flex provided by the system are outdated and not compatible with our requirements. To get recent version @@ -52,7 +52,7 @@ using: pip3 install --user -r requirements.txt -Make sure to have latest flex/bison in $PATH : +Make sure to have latest flex/bison in your ``PATH``: .. code:: sh @@ -107,7 +107,7 @@ Once all dependencies are in place, build project as: ``cmake --parallel ``. i.e. in a machine with 8 threads do ``cmake --parallel 4``. -And set PYTHONPATH as: +And set ``PYTHONPATH`` as: .. code:: sh @@ -116,7 +116,7 @@ And set PYTHONPATH as: Flex / Bison Paths ^^^^^^^^^^^^^^^^^^ -If flex / bison are not in your default $PATH, you can provide the path +If flex / bison are not in your default ``PATH``, you can provide the path to cmake as: .. code:: sh diff --git a/docs/nmodl/transpiler/contributing.rst b/docs/nmodl/transpiler/contributing.rst deleted file mode 100644 index 54a68f95f2..0000000000 --- a/docs/nmodl/transpiler/contributing.rst +++ /dev/null @@ -1,3 +0,0 @@ - -.. include:: ../CONTRIBUTING.rst - :parser: myst_parser.sphinx_ diff --git a/docs/nmodl/transpiler/contributing.rst b/docs/nmodl/transpiler/contributing.rst new file mode 120000 index 0000000000..798f2aa2fc --- /dev/null +++ b/docs/nmodl/transpiler/contributing.rst @@ -0,0 +1 @@ +../CONTRIBUTING.rst \ No newline at end of file diff --git a/docs/nmodl/transpiler/install.rst b/docs/nmodl/transpiler/install.rst deleted file mode 100644 index 16233b2c00..0000000000 --- a/docs/nmodl/transpiler/install.rst +++ /dev/null @@ -1,4 +0,0 @@ - -.. include:: ../INSTALL.rst - :parser: myst_parser.sphinx_ - diff --git a/docs/nmodl/transpiler/install.rst b/docs/nmodl/transpiler/install.rst new file mode 120000 index 0000000000..356deb7f26 --- /dev/null +++ b/docs/nmodl/transpiler/install.rst @@ -0,0 +1 @@ +../INSTALL.rst \ No newline at end of file diff --git a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst index 6f21b8aaa4..121543d5c7 100644 --- a/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst +++ b/docs/nmodl/transpiler/nmodl/transpiler/CONTRIBUTING.rst @@ -8,22 +8,22 @@ would like you to follow: - `Question or Problem? <#question>`__ - `Submission Guidelines <#submit>`__ - `Development Conventions <#devconv>`__ - Got a Question? ----------------- +Got a Question? +--------------- Please do not hesitate to raise an issue on `github project page `__. - Found a Bug? -------------- +Found a Bug? +------------ If you find a bug in the source code, you can help us by `submitting an issue <#submit-issue>`__ to our `GitHub Repository `__. Even better, you can `submit a Pull Request <#submit-pr>`__ with a fix. - Missing a Feature? -------------------- +Missing a Feature? +------------------ You can *request* a new feature by `submitting an issue <#submit-issue>`__ to our GitHub Repository. If you would like to @@ -40,11 +40,11 @@ Please consider what kind of change it is: - **Small Features** can be crafted and directly `submitted as a Pull Request <#submit-pr>`__. - Submission Guidelines ----------------------- +Submission Guidelines +--------------------- - Submitting an Issue -~~~~~~~~~~~~~~~~~~~~ +Submitting an Issue +~~~~~~~~~~~~~~~~~~~ Before you submit an issue, please search the issue tracker, maybe an issue for your problem already exists and the discussion might inform @@ -55,8 +55,8 @@ bug we need to reproduce and confirm it. In order to reproduce bugs we will need as much information as possible, and preferably a sample MOD file or Python example. - Submitting a Pull Request (PR) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Submitting a Pull Request (PR) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When you wish to contribute to the code base, please consider the following guidelines: @@ -149,8 +149,8 @@ pull the changes from the main (upstream) repository: git pull --ff upstream master - Development Conventions ------------------------- +Development Conventions +----------------------- New Lines ~~~~~~~~~ @@ -174,33 +174,6 @@ The HPC coding conventions formatter installs any dependencies into a Python virtual environment. -Updating Golden References -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Run - -.. code:: bash - - cmake --build --target generate_references - -to regenerate the golden references. They are saved in a submodule -``tests/usecases/references``, which points to ``BlueBrain/nmodl-references``. - -Create a PR for the changes to the references and update the SHA in the NMODL -repo. It might be useful to change to SSH authentication: - -.. code:: bash - - git remote set-url origin ssh://git@github.com/BlueBrain/nmodl-references - -(from inside ``tests/usecases/references``). - -Remember the rules of submodules: They're checked out on a specific commit, -i.e. detached HEAD. If you want to modify the submodule, it's usual best to -checkout ``main`` from then on the submodule will behave much like a Git repo -that happens to be located inside a Git repo. - - Validate the Python package ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/nmodl/transpiler/readme.rst b/docs/nmodl/transpiler/readme.rst deleted file mode 100644 index c29a05db90..0000000000 --- a/docs/nmodl/transpiler/readme.rst +++ /dev/null @@ -1,6 +0,0 @@ -Introduction -============ - -.. include:: ../README.rst - :parser: myst_parser.sphinx_ - diff --git a/docs/nmodl/transpiler/readme.rst b/docs/nmodl/transpiler/readme.rst new file mode 120000 index 0000000000..89a0106941 --- /dev/null +++ b/docs/nmodl/transpiler/readme.rst @@ -0,0 +1 @@ +../README.rst \ No newline at end of file diff --git a/src/nmodl/nmodl/README.rst b/src/nmodl/nmodl/README.rst index a2299ef10d..08ee4ef94b 100644 --- a/src/nmodl/nmodl/README.rst +++ b/src/nmodl/nmodl/README.rst @@ -181,18 +181,18 @@ which will open AST view in web browser: https://user-images.githubusercontent.com/666852/57329449-12c9a400-7114-11e9-8da5-0042590044ec.gif :alt: ast_viz - ast_viz + Vizualisation of the AST in the NMODL Framework The central *Program* node represents the whole MOD file and each of it’s children represent the block in the input NMODL file. Note that -this requires X-forwarding if you are using Docker image. +this requires X-forwarding if you are using the Docker image. Once the AST is created, one can use exisiting visitors to perform various analysis/optimisations. One can also easily write his own custom visitor using Python Visitor API. See `Python API tutorial `__ for details. -NMODL Frameowrk also allows to transform AST representation back to +The NMODL Framework also allows us to transform the AST representation back to NMODL form as: .. code:: python @@ -225,13 +225,13 @@ visitors `__: +2015 `__: .. figure:: https://user-images.githubusercontent.com/666852/57336711-2cc0b200-7127-11e9-8053-8f662e2ec191.png :alt: nmodl-perf-stats - nmodl-perf-stats + Performance results of the NMODL Framework To understand how you can write your own introspection and analysis tool, see `this @@ -356,7 +356,7 @@ will be published soon in following repositories: - `NMODL Database `__ Funding & Acknowledgment -======================== +------------------------ The development of this software was supported by funding to the Blue Brain Project, a research center of the École polytechnique fédérale de From 5ae44c31132cad17ce5f6d8f53b4a0dc04ae5f11 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 4 Jun 2024 16:47:31 +0200 Subject: [PATCH 663/871] Use `ParamVector` in codegen (BlueBrain/nmodl#1289) NMODL Repo SHA: BlueBrain/nmodl@3754a71af34180633bb7261fd52648f277fd93f7 --- .../codegen_coreneuron_cpp_visitor.cpp | 66 ++++++++--------- .../codegen_coreneuron_cpp_visitor.hpp | 6 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 28 ++++---- src/nmodl/codegen/codegen_cpp_visitor.hpp | 16 ++++- .../codegen/codegen_neuron_cpp_visitor.cpp | 72 ++++++++++--------- .../codegen/codegen_neuron_cpp_visitor.hpp | 4 +- 6 files changed, 106 insertions(+), 86 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 421ae172ac..b9aa2bcfa1 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -408,7 +408,7 @@ void CodegenCoreneuronCppVisitor::print_check_table_thread_function() { printer->add_newline(2); auto name = method_name("check_table_thread"); - auto parameters = external_method_parameters(true); + auto parameters = get_parameter_str(external_method_parameters(true)); printer->fmt_push_block("static void {} ({})", name, parameters); printer->add_line("setup_instance(nt, ml);"); @@ -695,10 +695,7 @@ void CodegenCoreneuronCppVisitor::add_variable_point_process( } std::string CodegenCoreneuronCppVisitor::internal_method_arguments() { - if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; - } - return "id, pnodecount, inst, data, indexes, thread, nt, v"; + return get_arg_str(internal_method_parameters()); } @@ -706,34 +703,42 @@ std::string CodegenCoreneuronCppVisitor::internal_method_arguments() { * @todo: figure out how to correctly handle qualifiers */ CodegenCoreneuronCppVisitor::ParamVector CodegenCoreneuronCppVisitor::internal_method_parameters() { - ParamVector params; - params.emplace_back("", "int", "", "id"); - params.emplace_back("", "int", "", "pnodecount"); - params.emplace_back("", fmt::format("{}*", instance_struct()), "", "inst"); + ParamVector params = {{"", "int", "", "id"}, + {"", "int", "", "pnodecount"}, + {"", fmt::format("{}*", instance_struct()), "", "inst"}}; if (ion_variable_struct_required()) { params.emplace_back("", "IonCurVar&", "", "ionvar"); } - params.emplace_back("", "double*", "", "data"); - params.emplace_back("const ", "Datum*", "", "indexes"); - params.emplace_back("", "ThreadDatum*", "", "thread"); - params.emplace_back("", "NrnThread*", "", "nt"); - params.emplace_back("", "double", "", "v"); + ParamVector other_params = {{"", "double*", "", "data"}, + {"const ", "Datum*", "", "indexes"}, + {"", "ThreadDatum*", "", "thread"}, + {"", "NrnThread*", "", "nt"}, + {"", "double", "", "v"}}; + params.insert(params.end(), other_params.begin(), other_params.end()); return params; } -const char* CodegenCoreneuronCppVisitor::external_method_arguments() noexcept { - return "id, pnodecount, data, indexes, thread, nt, ml, v"; +const std::string CodegenCoreneuronCppVisitor::external_method_arguments() noexcept { + return get_arg_str(external_method_parameters()); } -const char* CodegenCoreneuronCppVisitor::external_method_parameters(bool table) noexcept { +const CodegenCppVisitor::ParamVector CodegenCoreneuronCppVisitor::external_method_parameters( + bool table) noexcept { + ParamVector args = {{"", "int", "", "id"}, + {"", "int", "", "pnodecount"}, + {"", "double*", "", "data"}, + {"", "Datum*", "", "indexes"}, + {"", "ThreadDatum*", "", "thread"}, + {"", "NrnThread*", "", "nt"}, + {"", "Memb_list*", "", "ml"}}; if (table) { - return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, int tml_id"; + args.emplace_back("", "int", "", "tml_id"); + } else { + args.emplace_back("", "double", "", "v"); } - return "int id, int pnodecount, double* data, Datum* indexes, " - "ThreadDatum* thread, NrnThread* nt, Memb_list* ml, double v"; + return args; } @@ -750,10 +755,7 @@ std::string CodegenCoreneuronCppVisitor::nrn_thread_arguments() const { * same mod file itself */ std::string CodegenCoreneuronCppVisitor::nrn_thread_internal_arguments() { - if (ion_variable_struct_required()) { - return "id, pnodecount, inst, ionvar, data, indexes, thread, nt, v"; - } - return "id, pnodecount, inst, data, indexes, thread, nt, v"; + return get_arg_str(internal_method_parameters()); } @@ -779,7 +781,7 @@ std::string CodegenCoreneuronCppVisitor::replace_if_verbatim_variable(std::strin } } if (name == naming::THREAD_ARGS_PROTO) { - name = external_method_parameters(); + name = get_parameter_str(external_method_parameters()); } return name; } @@ -2750,10 +2752,10 @@ void CodegenCoreneuronCppVisitor::print_net_receive() { printing_net_receive = true; if (!info.artificial_cell) { const auto& name = method_name("net_receive"); - ParamVector params; - params.emplace_back("", "Point_process*", "", "pnt"); - params.emplace_back("", "int", "", "weight_index"); - params.emplace_back("", "double", "", "flag"); + ParamVector params = { + {"", "Point_process*", "", "pnt"}, + {"", "int", "", "weight_index"}, + {"", "double", "", "flag"}}; printer->add_newline(2); printer->fmt_push_block("static void {}({})", name, get_parameter_str(params)); printer->add_line("NrnThread* nt = nrn_threads + pnt->_tid;"); @@ -2785,7 +2787,7 @@ void CodegenCoreneuronCppVisitor::print_net_receive() { */ void CodegenCoreneuronCppVisitor::print_derivimplicit_kernel(const Block& block) { auto ext_args = external_method_arguments(); - auto ext_params = external_method_parameters(); + auto ext_params = get_parameter_str(external_method_parameters()); auto suffix = info.mod_suffix; auto list_num = info.derivimplicit_list_num; auto block_name = block.get_node_name(); @@ -2796,7 +2798,7 @@ void CodegenCoreneuronCppVisitor::print_derivimplicit_kernel(const Block& block) printer->push_block("namespace"); printer->fmt_push_block("struct _newton_{}_{}", block_name, info.mod_suffix); - printer->fmt_push_block("int operator()({}) const", external_method_parameters()); + printer->fmt_push_block("int operator()({}) const", get_parameter_str(external_method_parameters())); auto const instance = fmt::format("auto* const inst = static_cast<{0}*>(ml->instance);", instance_struct()); auto const slist1 = fmt::format("auto const& slist{} = {};", diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 3cabec68ff..f6d4b7ba39 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -433,7 +433,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - const char* external_method_arguments() noexcept override; + const std::string external_method_arguments() noexcept override; /** @@ -443,9 +443,9 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { * calling convention. This method generates the string representing the function parameters for * these externally called functions. * \param table - * \return A string representing the parameters of the function + * \return A ParamVector representing the parameters of the function */ - const char* external_method_parameters(bool table = false) noexcept override; + const ParamVector external_method_parameters(bool table = false) noexcept override; /** diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 004399c2dc..832be834c4 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -34,23 +34,25 @@ bool CodegenCppVisitor::ion_variable_struct_required() const { return optimize_ion_variable_copies() && info.ion_has_write_variable(); } +std::string CodegenCppVisitor::get_arg_str(const ParamVector& params) { + std::vector variables; + for (const auto& param: params) { + variables.push_back(std::get<3>(param)); + } + return fmt::format("{}", fmt::join(variables, ", ")); +} + std::string CodegenCppVisitor::get_parameter_str(const ParamVector& params) { - std::string str; - bool is_first = true; + std::vector variables; for (const auto& param: params) { - if (is_first) { - is_first = false; - } else { - str += ", "; - } - str += fmt::format("{}{} {}{}", - std::get<0>(param), - std::get<1>(param), - std::get<2>(param), - std::get<3>(param)); + variables.push_back(fmt::format("{}{} {}{}", + std::get<0>(param), + std::get<1>(param), + std::get<2>(param), + std::get<3>(param))); } - return str; + return fmt::format("{}", fmt::join(variables, ", ")); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 729fe07003..33cf0b9015 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -508,6 +508,18 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { static std::string get_parameter_str(const ParamVector& params); + /** + * Generate the string representing the parameters in a function call + * + * The procedure parameters are stored in a vector of 4-tuples each representing a parameter. + * + * \param params The parameters that should be concatenated into the function parameter + * declaration + * \return The string representing the function call parameters + */ + static std::string get_arg_str(const ParamVector& params); + + /** * Check if function or procedure node has parameter with given name * @@ -894,7 +906,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - virtual const char* external_method_arguments() noexcept = 0; + virtual const std::string external_method_arguments() noexcept = 0; /** @@ -906,7 +918,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * \param table * \return A string representing the parameters of the function */ - virtual const char* external_method_parameters(bool table = false) noexcept = 0; + virtual const ParamVector external_method_parameters(bool table = false) noexcept = 0; /** diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e2717bceac..8ea091f02c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -456,30 +456,31 @@ void CodegenNeuronCppVisitor::add_variable_point_process( } std::string CodegenNeuronCppVisitor::internal_method_arguments() { - return "_ml, inst, id, _ppvar, _thread, _nt"; + const auto& args = internal_method_parameters(); + return get_arg_str(args); } -CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { - ParamVector params; - params.emplace_back("", "_nrn_mechanism_cache_range*", "", "_ml"); - params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); - params.emplace_back("", "size_t", "", "id"); - params.emplace_back("", "Datum*", "", "_ppvar"); - params.emplace_back("", "Datum*", "", "_thread"); - params.emplace_back("", "NrnThread*", "", "_nt"); +CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { + ParamVector params = {{"", "_nrn_mechanism_cache_range*", "", "_ml"}, + {"", fmt::format("{}&", instance_struct()), "", "inst"}, + {"", "size_t", "", "id"}, + {"", "Datum*", "", "_ppvar"}, + {"", "Datum*", "", "_thread"}, + {"", "NrnThread*", "", "_nt"}}; return params; } /// TODO: Edit for NEURON -const char* CodegenNeuronCppVisitor::external_method_arguments() noexcept { +const std::string CodegenNeuronCppVisitor::external_method_arguments() noexcept { return {}; } /// TODO: Edit for NEURON -const char* CodegenNeuronCppVisitor::external_method_parameters(bool table) noexcept { +const CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::external_method_parameters( + bool table) noexcept { return {}; } @@ -1369,12 +1370,11 @@ void CodegenNeuronCppVisitor::print_make_node_data() const { node_data_struct(), info.mod_suffix); - std::vector make_node_data_args; - make_node_data_args.push_back("_ml_arg.nodeindices"); - make_node_data_args.push_back("_nt.node_voltage_storage()"); - make_node_data_args.push_back("_nt.node_d_storage()"); - make_node_data_args.push_back("_nt.node_rhs_storage()"); - make_node_data_args.push_back("_ml_arg.nodecount"); + std::vector make_node_data_args = {"_ml_arg.nodeindices", + "_nt.node_voltage_storage()", + "_nt.node_d_storage()", + "_nt.node_rhs_storage()", + "_ml_arg.nodecount"}; printer->fmt_push_block("return {}", node_data_struct()); printer->add_multi_line(fmt::format("{}", fmt::join(make_node_data_args, ",\n"))); @@ -1440,10 +1440,11 @@ void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, const std::string& function_name) { std::string method = function_name.empty() ? compute_method_name(type) : function_name; - std::string args = - "_nrn_model_sorted_token const& _sorted_token, NrnThread* _nt, Memb_list* _ml_arg, int " - "_type"; - printer->fmt_push_block("void {}({})", method, args); + ParamVector args = {{"", "const _nrn_model_sorted_token&", "", "_sorted_token"}, + {"", "NrnThread*", "", "_nt"}, + {"", "Memb_list*", "", "_ml_arg"}, + {"", "int", "", "_type"}}; + printer->fmt_push_block("void {}({})", method, get_parameter_str(args)); printer->add_line("_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); @@ -1483,10 +1484,15 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_newline(2); - printer->fmt_push_block( - "static void {}(_nrn_model_sorted_token const& _sorted_token, NrnThread* " - "_nt, Memb_list* _ml_arg, int _type)", - method_name(naming::NRN_JACOB_METHOD)); // begin function + ParamVector args = {{"", "const _nrn_model_sorted_token&", "", "_sorted_token"}, + {"", "NrnThread*", "", "_nt"}, + {"", "Memb_list*", "", "_ml_arg"}, + {"", "int", "", "_type"}}; + + printer->fmt_push_block("static void {}({})", + method_name(naming::NRN_JACOB_METHOD), + get_parameter_str(args)); // begin function + printer->add_multi_line( "_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); @@ -1692,11 +1698,10 @@ CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parame throw std::runtime_error("Not implemented."); } - ParamVector params; - params.emplace_back("", "_nrn_mechanism_cache_range*", "", "_ml"); - params.emplace_back("", "NrnThread*", "", "_nt"); - params.emplace_back("", "Datum*", "", "_ppvar"); - params.emplace_back("", "Datum*", "", "_thread"); + ParamVector params = {{"", "_nrn_mechanism_cache_range*", "", "_ml"}, + {"", "NrnThread*", "", "_nt"}, + {"", "Datum*", "", "_ppvar"}, + {"", "Datum*", "", "_thread"}}; if (info.thread_callback_register) { auto type_name = fmt::format("{}&", thread_variables_struct()); @@ -2119,10 +2124,9 @@ void CodegenNeuronCppVisitor::print_net_receive() { return; } - ParamVector args; - args.emplace_back("", "Point_process*", "", "_pnt"); - args.emplace_back("", "double*", "", "_args"); - args.emplace_back("", "double", "", "flag"); + ParamVector args = {{"", "Point_process*", "", "_pnt"}, + {"", "double*", "", "_args"}, + {"", "double", "", "flag"}}; printer->fmt_push_block("static void nrn_net_receive_{}({})", info.mod_suffix, diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index a1a1bfae6b..32ed78e626 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -277,7 +277,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * Arguments for external functions called from generated code * \return A string representing the arguments passed to an external function */ - const char* external_method_arguments() noexcept override; + const std::string external_method_arguments() noexcept override; /** @@ -289,7 +289,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * \param table * \return A string representing the parameters of the function */ - const char* external_method_parameters(bool table = false) noexcept override; + const ParamVector external_method_parameters(bool table = false) noexcept override; /** From 432037e85c0a2e38c8c3190a0a4baa5b3ff0b1f3 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 10 Jun 2024 14:13:30 +0200 Subject: [PATCH 664/871] Cleanup 'usecases'. (BlueBrain/nmodl#1294) The new structure is to have one directory per keyword. Tests can be organized in separate test files, named test_*.py (or simulate.py if that's a better name). Numerous tests have been polished to make them easier to read, using better names and functions to extract common functionality. NMODL Repo SHA: BlueBrain/nmodl@cc44ecbd4045eb10b0d6762c85822324aa521fa4 --- test/nmodl/transpiler/usecases/CMakeLists.txt | 14 +- .../leonhard.mod => cnexp/cnexp_array.mod} | 2 +- .../leonhard.mod => cnexp/cnexp_scalar.mod} | 2 +- .../simulate.py => cnexp/test_array.py} | 5 +- .../simulate.py => cnexp/test_scalar.py} | 5 +- .../usecases/func_in_breakpoint/simulate.py | 1 - .../transpiler/usecases/func_proc/simulate.py | 52 ------- .../usecases/func_proc_pnt/func_proc_pnt.mod | 20 --- .../usecases/func_proc_pnt/simulate.py | 27 ---- .../compile_only.mod} | 0 .../usecases/function/functions.mod | 24 ++++ .../usecases/function/point_functions.mod | 24 ++++ .../usecases/function/recursion.mod | 11 ++ .../usecases/function/test_functions.py | 40 ++++++ .../usecases/function/test_recursion.py | 15 ++ .../leonhard.mod => global/read_only.mod} | 2 +- .../simulate.py => global/test_read_only.py} | 5 +- ...test_parameter.mod => range_parameter.mod} | 2 +- .../transpiler/usecases/parameter/simulate.py | 24 ---- .../parameter/test_range_parameter.py | 24 ++++ .../transpiler/usecases/point_process/pp.mod | 3 + .../usecases/point_process/simulate.py | 15 -- .../point_process/test_point_process.py | 12 ++ .../usecases/point_process/test_pp.mod | 3 - .../point_procedures.mod} | 22 +-- .../usecases/procedure/procedures.mod | 41 ++++++ .../usecases/procedure/test_procedures.py | 56 ++++++++ .../usecases/recursion/recursion.mod | 11 -- .../transpiler/usecases/recursion/simulate.py | 26 ---- test/nmodl/transpiler/usecases/run_test.sh | 24 +++- .../suffix/{simulate.py => test_suffix.py} | 0 .../transpiler/usecases/table/simulate.py | 134 +++++------------- .../nmodl/transpiler/usecases/table/table.mod | 24 ++-- .../usecases/{ionic => useion}/ionic.mod | 0 .../usecases/{ionic => useion}/simulate.py | 1 - 35 files changed, 340 insertions(+), 331 deletions(-) rename test/nmodl/transpiler/usecases/{cnexp_array/leonhard.mod => cnexp/cnexp_array.mod} (92%) rename test/nmodl/transpiler/usecases/{cnexp_scalar/leonhard.mod => cnexp/cnexp_scalar.mod} (82%) rename test/nmodl/transpiler/usecases/{cnexp_array/simulate.py => cnexp/test_array.py} (80%) rename test/nmodl/transpiler/usecases/{cnexp_scalar/simulate.py => cnexp/test_scalar.py} (78%) delete mode 100644 test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py delete mode 100644 test/nmodl/transpiler/usecases/func_proc/simulate.py delete mode 100644 test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod delete mode 100644 test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py rename test/nmodl/transpiler/usecases/{func_in_breakpoint/func_in_breakpoint.mod => function/compile_only.mod} (100%) create mode 100644 test/nmodl/transpiler/usecases/function/functions.mod create mode 100644 test/nmodl/transpiler/usecases/function/point_functions.mod create mode 100644 test/nmodl/transpiler/usecases/function/recursion.mod create mode 100644 test/nmodl/transpiler/usecases/function/test_functions.py create mode 100644 test/nmodl/transpiler/usecases/function/test_recursion.py rename test/nmodl/transpiler/usecases/{global_breakpoint/leonhard.mod => global/read_only.mod} (83%) rename test/nmodl/transpiler/usecases/{global_breakpoint/simulate.py => global/test_read_only.py} (79%) rename test/nmodl/transpiler/usecases/parameter/{test_parameter.mod => range_parameter.mod} (70%) delete mode 100644 test/nmodl/transpiler/usecases/parameter/simulate.py create mode 100644 test/nmodl/transpiler/usecases/parameter/test_range_parameter.py create mode 100644 test/nmodl/transpiler/usecases/point_process/pp.mod delete mode 100644 test/nmodl/transpiler/usecases/point_process/simulate.py create mode 100644 test/nmodl/transpiler/usecases/point_process/test_point_process.py delete mode 100644 test/nmodl/transpiler/usecases/point_process/test_pp.mod rename test/nmodl/transpiler/usecases/{func_proc/func_proc.mod => procedure/point_procedures.mod} (60%) create mode 100644 test/nmodl/transpiler/usecases/procedure/procedures.mod create mode 100644 test/nmodl/transpiler/usecases/procedure/test_procedures.py delete mode 100644 test/nmodl/transpiler/usecases/recursion/recursion.mod delete mode 100644 test/nmodl/transpiler/usecases/recursion/simulate.py rename test/nmodl/transpiler/usecases/suffix/{simulate.py => test_suffix.py} (100%) rename test/nmodl/transpiler/usecases/{ionic => useion}/ionic.mod (100%) rename test/nmodl/transpiler/usecases/{ionic => useion}/simulate.py (94%) diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 3c0e3017db..31f363e6d7 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,20 +1,20 @@ set(NMODL_USECASE_DIRS - cnexp_scalar - cnexp_array - func_proc - func_proc_pnt - global_breakpoint + cnexp + function + procedure + global hodgkin_huxley nonspecific_current neuron_variables + net_event + net_move net_receive net_send point_process parameter - func_in_breakpoint suffix table - recursion) + useion) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod b/test/nmodl/transpiler/usecases/cnexp/cnexp_array.mod similarity index 92% rename from test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod rename to test/nmodl/transpiler/usecases/cnexp/cnexp_array.mod index e2aa3e6444..f652ceac6c 100644 --- a/test/nmodl/transpiler/usecases/cnexp_array/leonhard.mod +++ b/test/nmodl/transpiler/usecases/cnexp/cnexp_array.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX leonhard + SUFFIX cnexp_array RANGE z } diff --git a/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod b/test/nmodl/transpiler/usecases/cnexp/cnexp_scalar.mod similarity index 82% rename from test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod rename to test/nmodl/transpiler/usecases/cnexp/cnexp_scalar.mod index eac8cab540..4740338547 100644 --- a/test/nmodl/transpiler/usecases/cnexp_scalar/leonhard.mod +++ b/test/nmodl/transpiler/usecases/cnexp/cnexp_scalar.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX leonhard + SUFFIX cnexp_scalar } STATE { x } diff --git a/test/nmodl/transpiler/usecases/cnexp_array/simulate.py b/test/nmodl/transpiler/usecases/cnexp/test_array.py similarity index 80% rename from test/nmodl/transpiler/usecases/cnexp_array/simulate.py rename to test/nmodl/transpiler/usecases/cnexp/test_array.py index 04f4fa031b..5c6ae46cb2 100644 --- a/test/nmodl/transpiler/usecases/cnexp_array/simulate.py +++ b/test/nmodl/transpiler/usecases/cnexp/test_array.py @@ -6,10 +6,10 @@ nseg = 1 s = h.Section() -s.insert("leonhard") +s.insert("cnexp_array") s.nseg = nseg -x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +x_hoc = h.Vector().record(s(0.5)._ref_x_cnexp_array) t_hoc = h.Vector().record(h._ref_t) h.stdinit() @@ -24,4 +24,3 @@ rel_err = np.abs(x - x_exact) / x_exact assert np.all(rel_err < 1e-12) -print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py b/test/nmodl/transpiler/usecases/cnexp/test_scalar.py similarity index 78% rename from test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py rename to test/nmodl/transpiler/usecases/cnexp/test_scalar.py index 3245670ca9..9c451e9c2c 100644 --- a/test/nmodl/transpiler/usecases/cnexp_scalar/simulate.py +++ b/test/nmodl/transpiler/usecases/cnexp/test_scalar.py @@ -6,10 +6,10 @@ nseg = 1 s = h.Section() -s.insert("leonhard") +s.insert("cnexp_scalar") s.nseg = nseg -x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +x_hoc = h.Vector().record(s(0.5)._ref_x_cnexp_scalar) t_hoc = h.Vector().record(h._ref_t) h.stdinit() @@ -24,4 +24,3 @@ rel_err = np.abs(x - x_exact) / x_exact assert np.all(rel_err < 1e-12) -print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py b/test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py deleted file mode 100644 index b06c8f6a44..0000000000 --- a/test/nmodl/transpiler/usecases/func_in_breakpoint/simulate.py +++ /dev/null @@ -1 +0,0 @@ -print("success: func_in_breakpoint") diff --git a/test/nmodl/transpiler/usecases/func_proc/simulate.py b/test/nmodl/transpiler/usecases/func_proc/simulate.py deleted file mode 100644 index c27aa45402..0000000000 --- a/test/nmodl/transpiler/usecases/func_proc/simulate.py +++ /dev/null @@ -1,52 +0,0 @@ -from neuron import h - -nseg = 5 -s = h.Section() -s.nseg = nseg - -s.insert("test_func_proc") - -coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] -values = [0.1 + k for k in range(nseg)] - -for x in coords: - s(x).test_func_proc.set_x_42() - assert s(x).test_func_proc.x == 42 - -for x, value in zip(coords, values): - s(x).test_func_proc.set_x_a(value) - -for x, value in zip(coords, values): - assert s(x).test_func_proc.x == value - -for x, value in zip(coords, values): - assert s(x).test_func_proc.x_plus_a(100.0) == 100.0 + value - - -x = coords[0] -v0 = -42.0 -h.finitialize(v0) - -# Check `x = v`. -s(x).test_func_proc.set_x_v() -actual = s(x).test_func_proc.x -expected = v0 - -assert actual == expected, f"{actual} == {expected}" - -# Check `f(v)`. -expected = 42.0 -actual = s(x).test_func_proc.just_v(expected) -assert actual == expected, f"{actual} == {expected}" - -# Check g = lambda: f(v) -s(x).test_func_proc.set_x_just_v() -actual = s(x).test_func_proc.x -expected = v0 -assert actual == expected, f"{actual} == {expected}" - -# Check g = lambda v: f(v) -expected = 42.0 -s(x).test_func_proc.set_x_just_vv(expected) -actual = s(x).test_func_proc.x -assert actual == expected, f"{actual} == {expected}" diff --git a/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod b/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod deleted file mode 100644 index c39f846fb1..0000000000 --- a/test/nmodl/transpiler/usecases/func_proc_pnt/func_proc_pnt.mod +++ /dev/null @@ -1,20 +0,0 @@ -NEURON { - POINT_PROCESS test_func_proc_pnt - RANGE x -} - -ASSIGNED { - x -} - -PROCEDURE set_x_42() { - x = 42 -} - -PROCEDURE set_x_a(a) { - x = a -} - -FUNCTION x_plus_a(a) { - x_plus_a = x + a -} diff --git a/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py b/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py deleted file mode 100644 index 20d6760062..0000000000 --- a/test/nmodl/transpiler/usecases/func_proc_pnt/simulate.py +++ /dev/null @@ -1,27 +0,0 @@ -from neuron import h - -nseg = 5 -s = h.Section() -s.nseg = nseg - -point_processes = [] -for k in range(nseg): - x = (0.5 + k) * 1.0 / nseg - point_processes.append(h.test_func_proc_pnt(s(x))) - -for k in range(nseg): - point_processes[k].set_x_42() - -for k in range(nseg): - assert point_processes[k].x == 42 - -for k in range(nseg): - value = 0.1 + k - point_processes[k].set_x_a(value) - -for k in range(nseg): - value = 0.1 + k - assert point_processes[k].x == value - assert point_processes[k].x_plus_a(1000.0) == 1000.0 + value - -print([point_processes[k].x for k in range(nseg)]) diff --git a/test/nmodl/transpiler/usecases/func_in_breakpoint/func_in_breakpoint.mod b/test/nmodl/transpiler/usecases/function/compile_only.mod similarity index 100% rename from test/nmodl/transpiler/usecases/func_in_breakpoint/func_in_breakpoint.mod rename to test/nmodl/transpiler/usecases/function/compile_only.mod diff --git a/test/nmodl/transpiler/usecases/function/functions.mod b/test/nmodl/transpiler/usecases/function/functions.mod new file mode 100644 index 0000000000..5187360432 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/functions.mod @@ -0,0 +1,24 @@ +NEURON { + SUFFIX functions + RANGE x +} + +ASSIGNED { + x +} + +FUNCTION x_plus_a(a) { + x_plus_a = x + a +} + +FUNCTION v_plus_a(a) { + v_plus_a = v + a +} + +FUNCTION identity(v) { + identity = v +} + +INITIAL { + x = 1.0 +} diff --git a/test/nmodl/transpiler/usecases/function/point_functions.mod b/test/nmodl/transpiler/usecases/function/point_functions.mod new file mode 100644 index 0000000000..6c7f119917 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/point_functions.mod @@ -0,0 +1,24 @@ +NEURON { + POINT_PROCESS point_functions + RANGE x +} + +ASSIGNED { + x +} + +FUNCTION x_plus_a(a) { + x_plus_a = x + a +} + +FUNCTION v_plus_a(a) { + v_plus_a = v + a +} + +FUNCTION identity(v) { + identity = v +} + +INITIAL { + x = 1.0 +} diff --git a/test/nmodl/transpiler/usecases/function/recursion.mod b/test/nmodl/transpiler/usecases/function/recursion.mod new file mode 100644 index 0000000000..d80fb3c4c4 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/recursion.mod @@ -0,0 +1,11 @@ +NEURON { + SUFFIX recursion +} + +FUNCTION fibonacci(n) { + if (n == 0 || n == 1) { + fibonacci = 1 + } else { + fibonacci = fibonacci(n-1) + fibonacci(n-2) + } +} diff --git a/test/nmodl/transpiler/usecases/function/test_functions.py b/test/nmodl/transpiler/usecases/function/test_functions.py new file mode 100644 index 0000000000..197245bbc9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/test_functions.py @@ -0,0 +1,40 @@ +from neuron import h + + +def check_functions(get_instance): + for x, value in zip(coords, values): + get_instance(x).x = value + + for x, value in zip(coords, values): + expected = 100.0 + value + actual = get_instance(x).x_plus_a(100.0) + assert actual == expected, f"{expected} != {actual}" + + x = coords[0] + v0 = -42.0 + h.finitialize(v0) + + # Check `f(v)`. + expected = 42.0 + actual = get_instance(x).identity(expected) + assert actual == expected, f"{actual} == {expected}" + + # Check `f` using `v`. + expected = -2.0 + actual = get_instance(x).v_plus_a(40.0) + assert actual == expected, f"{actual} == {expected}" + + +nseg = 5 +s = h.Section() +s.nseg = nseg + +s.insert("functions") + +coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] +values = [0.1 + k for k in range(nseg)] + +point_processes = {x: h.point_functions(s(x)) for x in coords} + +check_functions(lambda x: s(x).functions) +check_functions(lambda x: point_processes[x]) diff --git a/test/nmodl/transpiler/usecases/function/test_recursion.py b/test/nmodl/transpiler/usecases/function/test_recursion.py new file mode 100644 index 0000000000..ea59e27153 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/test_recursion.py @@ -0,0 +1,15 @@ +import math + +import numpy as np +from neuron import gui +from neuron import h + + +s = h.Section(name="soma") +s.insert("recursion") + +n = 6 +actual = [s(0.5).recursion.fibonacci(i) for i in range(n)] +expected = [1, 1, 2, 3, 5, 8] + +assert actual == expected, f"{actual} != {expected}" diff --git a/test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod b/test/nmodl/transpiler/usecases/global/read_only.mod similarity index 83% rename from test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod rename to test/nmodl/transpiler/usecases/global/read_only.mod index a78a93ce88..32b550333b 100644 --- a/test/nmodl/transpiler/usecases/global_breakpoint/leonhard.mod +++ b/test/nmodl/transpiler/usecases/global/read_only.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX leonhard + SUFFIX read_only GLOBAL c } diff --git a/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py b/test/nmodl/transpiler/usecases/global/test_read_only.py similarity index 79% rename from test/nmodl/transpiler/usecases/global_breakpoint/simulate.py rename to test/nmodl/transpiler/usecases/global/test_read_only.py index d7fc2bd052..2cfe375876 100644 --- a/test/nmodl/transpiler/usecases/global_breakpoint/simulate.py +++ b/test/nmodl/transpiler/usecases/global/test_read_only.py @@ -6,10 +6,10 @@ nseg = 1 s = h.Section() -s.insert("leonhard") +s.insert("read_only") s.nseg = nseg -x_hoc = h.Vector().record(s(0.5)._ref_x_leonhard) +x_hoc = h.Vector().record(s(0.5)._ref_x_read_only) t_hoc = h.Vector().record(h._ref_t) h.stdinit() @@ -24,4 +24,3 @@ abs_err = np.abs(x - x_exact) assert np.all(abs_err < 1e-12), f"{abs_err=}" -print("leonhard: success") diff --git a/test/nmodl/transpiler/usecases/parameter/test_parameter.mod b/test/nmodl/transpiler/usecases/parameter/range_parameter.mod similarity index 70% rename from test/nmodl/transpiler/usecases/parameter/test_parameter.mod rename to test/nmodl/transpiler/usecases/parameter/range_parameter.mod index dbe064160e..ef904f6b57 100644 --- a/test/nmodl/transpiler/usecases/parameter/test_parameter.mod +++ b/test/nmodl/transpiler/usecases/parameter/range_parameter.mod @@ -1,5 +1,5 @@ NEURON { - POINT_PROCESS test_parameter + POINT_PROCESS range_parameter RANGE x, y } diff --git a/test/nmodl/transpiler/usecases/parameter/simulate.py b/test/nmodl/transpiler/usecases/parameter/simulate.py deleted file mode 100644 index 250bf26855..0000000000 --- a/test/nmodl/transpiler/usecases/parameter/simulate.py +++ /dev/null @@ -1,24 +0,0 @@ -import numpy as np - -from neuron import h, gui -from neuron.units import ms - -s = h.Section() - -test_parameter_pp = h.test_parameter(s(0.5)) - -# Defaults set in the PARAMETER block: -assert test_parameter_pp.x == 42.0 -assert test_parameter_pp.y == 0.0 - -# Assignable: -test_parameter_pp.x = 42.1 -assert test_parameter_pp.x == 42.1 - -h.stdinit() -h.tstop = 5.0 * ms -h.run() - -# Values (not) set during the INITIAL block: -assert test_parameter_pp.x == 42.1 -assert test_parameter_pp.y == 43.0 diff --git a/test/nmodl/transpiler/usecases/parameter/test_range_parameter.py b/test/nmodl/transpiler/usecases/parameter/test_range_parameter.py new file mode 100644 index 0000000000..6d3eab40d6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/test_range_parameter.py @@ -0,0 +1,24 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +s = h.Section() + +pp = h.range_parameter(s(0.5)) + +# Defaults set in the PARAMETER block: +assert pp.x == 42.0 +assert pp.y == 0.0 + +# Assignable: +pp.x = 42.1 +assert pp.x == 42.1 + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +# Values (not) set during the INITIAL block: +assert pp.x == 42.1 +assert pp.y == 43.0 diff --git a/test/nmodl/transpiler/usecases/point_process/pp.mod b/test/nmodl/transpiler/usecases/point_process/pp.mod new file mode 100644 index 0000000000..4b5f60455a --- /dev/null +++ b/test/nmodl/transpiler/usecases/point_process/pp.mod @@ -0,0 +1,3 @@ +NEURON { + POINT_PROCESS pp +} diff --git a/test/nmodl/transpiler/usecases/point_process/simulate.py b/test/nmodl/transpiler/usecases/point_process/simulate.py deleted file mode 100644 index f9d910e032..0000000000 --- a/test/nmodl/transpiler/usecases/point_process/simulate.py +++ /dev/null @@ -1,15 +0,0 @@ -from neuron import h - -test_pp = h.test_pp() - -assert test_pp.has_loc() == 0 - -nseg = 1 - -s = h.Section() - -test_pp_s = h.test_pp(s(0.5)) - -assert test_pp_s.has_loc() == 1 - -assert test_pp_s.get_segment() == s(0.5) diff --git a/test/nmodl/transpiler/usecases/point_process/test_point_process.py b/test/nmodl/transpiler/usecases/point_process/test_point_process.py new file mode 100644 index 0000000000..5a17233a81 --- /dev/null +++ b/test/nmodl/transpiler/usecases/point_process/test_point_process.py @@ -0,0 +1,12 @@ +from neuron import h + +instance = h.pp() +assert instance.has_loc() == 0 + +nseg = 1 + +s = h.Section() +instance = h.pp(s(0.5)) + +assert instance.has_loc() == 1 +assert instance.get_segment() == s(0.5) diff --git a/test/nmodl/transpiler/usecases/point_process/test_pp.mod b/test/nmodl/transpiler/usecases/point_process/test_pp.mod deleted file mode 100644 index d9ca5cdfe8..0000000000 --- a/test/nmodl/transpiler/usecases/point_process/test_pp.mod +++ /dev/null @@ -1,3 +0,0 @@ -NEURON { - POINT_PROCESS test_pp -} diff --git a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod b/test/nmodl/transpiler/usecases/procedure/point_procedures.mod similarity index 60% rename from test/nmodl/transpiler/usecases/func_proc/func_proc.mod rename to test/nmodl/transpiler/usecases/procedure/point_procedures.mod index 0009928310..aaf2a5474e 100644 --- a/test/nmodl/transpiler/usecases/func_proc/func_proc.mod +++ b/test/nmodl/transpiler/usecases/procedure/point_procedures.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX test_func_proc + POINT_PROCESS point_procedures RANGE x } @@ -7,6 +7,10 @@ ASSIGNED { x } +FUNCTION identity(v) { + identity = v +} + PROCEDURE set_x_42() { set_x_a(42) } @@ -15,33 +19,21 @@ PROCEDURE set_x_a(a) { x = a } -FUNCTION x_plus_a(a) { - x_plus_a = x + a -} - PROCEDURE set_a_x() { LOCAL a a = x } -FUNCTION v_plus_a(a) { - v_plus_a = v + a -} - PROCEDURE set_x_v() { x = v } -FUNCTION just_v(v) { - just_v = v -} - PROCEDURE set_x_just_v() { - x = just_v(v) + x = identity(v) } PROCEDURE set_x_just_vv(v) { - x = just_v(v) + x = identity(v) } INITIAL { diff --git a/test/nmodl/transpiler/usecases/procedure/procedures.mod b/test/nmodl/transpiler/usecases/procedure/procedures.mod new file mode 100644 index 0000000000..7a06d5c808 --- /dev/null +++ b/test/nmodl/transpiler/usecases/procedure/procedures.mod @@ -0,0 +1,41 @@ +NEURON { + SUFFIX procedures + RANGE x +} + +ASSIGNED { + x +} + +FUNCTION identity(v) { + identity = v +} + +PROCEDURE set_x_42() { + set_x_a(42) +} + +PROCEDURE set_x_a(a) { + x = a +} + +PROCEDURE set_a_x() { + LOCAL a + a = x +} + +PROCEDURE set_x_v() { + x = v +} + +PROCEDURE set_x_just_v() { + x = identity(v) +} + +PROCEDURE set_x_just_vv(v) { + x = identity(v) +} + +INITIAL { + set_a_x() +} diff --git a/test/nmodl/transpiler/usecases/procedure/test_procedures.py b/test/nmodl/transpiler/usecases/procedure/test_procedures.py new file mode 100644 index 0000000000..d73446b7d1 --- /dev/null +++ b/test/nmodl/transpiler/usecases/procedure/test_procedures.py @@ -0,0 +1,56 @@ +from neuron import h + + +def check_procedures(get_instance): + for x in coords: + get_instance(x).set_x_42() + assert get_instance(x).x == 42 + + for x, value in zip(coords, values): + get_instance(x).set_x_a(value) + + for x, value in zip(coords, values): + assert get_instance(x).x == value + + x = coords[0] + v0 = -42.0 + h.finitialize(v0) + + # Check `x = v`. + get_instance(x).set_x_v() + actual = get_instance(x).x + expected = v0 + + assert actual == expected, f"{actual} == {expected}" + + # Check `f(v)`. + expected = 42.0 + actual = get_instance(x).identity(expected) + assert actual == expected, f"{actual} == {expected}" + + # Check g = lambda: f(v) + get_instance(x).set_x_just_v() + actual = get_instance(x).x + expected = v0 + assert actual == expected, f"{actual} == {expected}" + + # Check g = lambda v: f(v) + expected = 42.0 + get_instance(x).set_x_just_vv(expected) + actual = get_instance(x).x + assert actual == expected, f"{actual} == {expected}" + + +nseg = 5 +s = h.Section() +s.nseg = nseg + +s.insert("procedures") + +coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] +values = [0.1 + k for k in range(nseg)] + +point_processes = {x: h.point_procedures(s(x)) for x in coords} + +check_procedures(lambda x: s(x).procedures) +check_procedures(lambda x: point_processes[x]) diff --git a/test/nmodl/transpiler/usecases/recursion/recursion.mod b/test/nmodl/transpiler/usecases/recursion/recursion.mod deleted file mode 100644 index 4570d106fa..0000000000 --- a/test/nmodl/transpiler/usecases/recursion/recursion.mod +++ /dev/null @@ -1,11 +0,0 @@ -NEURON { - SUFFIX recursion -} - -FUNCTION myfactorial(n) { - if (n == 0 || n == 1) { - myfactorial = 1 - } else { - myfactorial = n * myfactorial(n - 1) - } -} diff --git a/test/nmodl/transpiler/usecases/recursion/simulate.py b/test/nmodl/transpiler/usecases/recursion/simulate.py deleted file mode 100644 index b6eff51f0d..0000000000 --- a/test/nmodl/transpiler/usecases/recursion/simulate.py +++ /dev/null @@ -1,26 +0,0 @@ -import math - -import numpy as np -from neuron import gui -from neuron import h - - -def simulate(): - s = h.Section(name="soma") - s.L = 10 - s.diam = 10 - s.insert("recursion") - fact = s(0.5).recursion.myfactorial - - return fact - - -def check_solution(f, reference): - for n in range(10): - exact, expected = reference(n), f(n) - assert np.isclose(exact, expected), f"{expected} != {exact}" - - -if __name__ == "__main__": - fact = simulate() - check_solution(fact, math.factorial) diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index 85214086c9..d8bcc34b28 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -1,28 +1,42 @@ #! /usr/bin/env bash set -eu +function run_tests() { + echo "" + echo "Running tests:" + for f in test_*.py simulate.py + do + if [[ -f "$f" ]] + then + echo "${usecase_dir}/$f: started." + python "$f" + echo "${usecase_dir}/$f: success." + fi + done + echo "All tests were successful." +} + if [[ $# -ne 2 ]] then echo "Usage: $0 NMODL USECASE_DIR" + exit -1 fi nmodl="$1" output_dir="$(uname -m)" usecase_dir="$2" -pushd "${usecase_dir}" +pushd "${usecase_dir}" > /dev/null # NRN + nocmodl echo "-- Running NRN+nocmodl ------" rm -r "${output_dir}" tmp || true nrnivmodl -"$(uname -m)/special" -nogui simulate.py +run_tests # NRN + NMODL echo "-- Running NRN+NMODL --------" rm -r "${output_dir}" tmp || true nrnivmodl -nmodl "${nmodl}" -"$(uname -m)/special" -nogui simulate.py - -popd +run_tests diff --git a/test/nmodl/transpiler/usecases/suffix/simulate.py b/test/nmodl/transpiler/usecases/suffix/test_suffix.py similarity index 100% rename from test/nmodl/transpiler/usecases/suffix/simulate.py rename to test/nmodl/transpiler/usecases/suffix/test_suffix.py diff --git a/test/nmodl/transpiler/usecases/table/simulate.py b/test/nmodl/transpiler/usecases/table/simulate.py index 5d8ac69c39..a4aa5e3978 100644 --- a/test/nmodl/transpiler/usecases/table/simulate.py +++ b/test/nmodl/transpiler/usecases/table/simulate.py @@ -3,143 +3,81 @@ from neuron import h -def setup_sim(): - section = h.Section() - section.insert("tbl") +def check_solution(y_no_table, y_table, rtol): + assert np.allclose( + y_no_table, y_table, rtol=rtol + ), f"{y_no_table} != {y_table}, delta: {y_table - y_no_table}" - return section - - -def check_solution(y_no_table, y_table, rtol1=1e-2, rtol2=1e-8): - assert np.allclose(y_no_table, y_table, rtol=rtol1), f"{y_no_table} != {y_table}" - # if the table is not used, we should get identical results, but we don't, - # hence the assert below + # The approximation by a linear lookup table can't be exact. assert not np.allclose( - y_no_table, y_table, rtol=rtol2 - ), f"Broken test logic: {y_no_table} == {y_table}" - - -def test_function(): - section = setup_sim() - - x = np.linspace(-3, 5, 500) + y_no_table, y_table, rtol=1e-10 + ), f"{y_no_table} == {y_table}" - func = section(0.5).tbl.quadratic +def check_table(c1, c2, x, evaluate_table): h.c1_tbl = 1 h.c2_tbl = 2 h.usetable_tbl = 0 - y_no_table = np.array([func(i) for i in x]) + y_no_table = np.array([evaluate_table(i) for i in x]) h.usetable_tbl = 1 - y_table = np.array([func(i) for i in x]) + y_table = np.array([evaluate_table(i) for i in x]) - check_solution(y_table, y_no_table, rtol1=1e-4) + check_solution(y_table, y_no_table, rtol=1e-4) # verify that the table just "clips" the values outside of the range - assert func(x[0] - 10) == y_table[0] - assert func(x[-1] + 10) == y_table[-1] + assert np.all(evaluate_table(x[0] - 10) == y_table[0]) + assert np.all(evaluate_table(x[-1] + 10) == y_table[-1]) - # change parameters and verify - h.c1_tbl = 3 - h.c2_tbl = 4 - h.usetable_tbl = 0 - y_params_no_table = np.array([func(i) for i in x]) +def test_function(): + s = h.Section() + s.insert("tbl") - h.usetable_tbl = 1 - y_params_table = np.array([func(i) for i in x]) + x = np.linspace(-3, 5, 18) + assert x[0] == -3.0 + assert x[-1] == 5.0 - check_solution(y_params_table, y_params_no_table, rtol1=1e-4) + check_table(1, 2, x, s(0.5).tbl.quadratic) + check_table(2, 2, x, s(0.5).tbl.quadratic) + check_table(2, 3, x, s(0.5).tbl.quadratic) def test_procedure(): - section = setup_sim() - - x = np.linspace(-4, 6, 300) - - proc = section(0.5).tbl.sinusoidal - - def call_proc_return_values(arg): - proc(arg) - return section(0.5).tbl.v1, section(0.5).tbl.v2 + s = h.Section() + s.insert("tbl") - def check_table(procedure, **kwargs): - for key, value in kwargs.items(): - setattr(h, f"{key}_tbl", value) + def evaluate_table(x): + s(0.5).tbl.sinusoidal(x) + return np.array((s(0.5).tbl.v1, s(0.5).tbl.v2)) - h.usetable_tbl = 0 - values_no_table = np.array([procedure(i) for i in x]) + x = np.linspace(-4, 6, 18) + assert x[0] == -4.0 + assert x[-1] == 6.0 - h.usetable_tbl = 1 - values_table = np.array([procedure(i) for i in x]) - - assert np.allclose( - values_no_table, - values_table, - rtol=1e-3, - ), f"{values_no_table} != {values_table}" - - assert not np.allclose( - values_no_table, - values_table, - rtol=1e-8, - ), f"Broken test logic: {values_no_table} == {values_table}" - - check_table(call_proc_return_values, c1=1, c2=2) - check_table(call_proc_return_values, c1=0.1, c2=0.3) + check_table(1, 2, x, evaluate_table) + check_table(2, 2, x, evaluate_table) + check_table(2, 3, x, evaluate_table) def simulate(): - s = setup_sim() - - h.k_tbl = -0.1 - h.d_tbl = -40 - s(0.5).tbl.gmax = 0.001 + s = h.Section() + s.insert("tbl") vvec = h.Vector().record(s(0.5)._ref_v) - tvec = h.Vector().record(h._ref_t) # run without a table h.usetable_tbl = 0 h.run() - - t = np.array(tvec.as_numpy()) v_exact = np.array(vvec.as_numpy()) # run with a table h.usetable_tbl = 1 h.run() - v_table = np.array(vvec.as_numpy()) - check_solution(v_table, v_exact, rtol1=1e-2) - - # run without a table, and changing params - h.usetable_tbl = 0 - h.k_tbl = -0.05 - h.d_tbl = -45 - - h.run() - - v_params = np.array(vvec.as_numpy()) - - # run with a table (same params as above) - h.usetable_tbl = 1 - h.run() - - v_params_table = np.array(vvec.as_numpy()) - - check_solution(v_params, v_params_table, rtol1=1e-2) - - -def plot_solution(t, y_exact, y): - import matplotlib.pyplot as plt - - plt.plot(t, y_exact, label="exact") - plt.plot(t, y, label="table", ls="--") - plt.show() + check_solution(v_table, v_exact, rtol=1e-2) if __name__ == "__main__": diff --git a/test/nmodl/transpiler/usecases/table/table.mod b/test/nmodl/transpiler/usecases/table/table.mod index d43774690c..94b6b34cb0 100644 --- a/test/nmodl/transpiler/usecases/table/table.mod +++ b/test/nmodl/transpiler/usecases/table/table.mod @@ -1,13 +1,11 @@ NEURON { SUFFIX tbl NONSPECIFIC_CURRENT i - RANGE e, g, gmax, v1, v2 + RANGE g, v1, v2 GLOBAL k, d, c1, c2 } PARAMETER { - e = 0 - gmax = 0 k = .1 d = -50 c1 = 1 @@ -24,23 +22,23 @@ ASSIGNED { } BREAKPOINT { - sigmoid1(v) - g = gmax * sig - i = g*(v - e) + sigmoidal(v) + g = 0.001 * sig + i = g*(v - 30.0) } -PROCEDURE sigmoid1(v) { +PROCEDURE sigmoidal(v) { TABLE sig DEPEND k, d FROM -127 TO 128 WITH 155 sig = 1/(1 + exp(k*(v - d))) } -FUNCTION quadratic(arg) { +FUNCTION quadratic(x) { TABLE DEPEND c1, c2 FROM -3 TO 5 WITH 500 - quadratic = c1 * arg * arg + c2 + quadratic = c1 * x * x + c2 } -PROCEDURE sinusoidal(arg) { - TABLE v1, v2 DEPEND c1, c2 FROM -4 TO 6 WITH 300 - v1 = sin(c1 * arg) + 2 - v2 = cos(c2 * arg) + 2 +PROCEDURE sinusoidal(x) { + TABLE v1, v2 DEPEND c1, c2 FROM -4 TO 6 WITH 800 + v1 = sin(c1 * x) + 2 + v2 = cos(c2 * x) + 2 } diff --git a/test/nmodl/transpiler/usecases/ionic/ionic.mod b/test/nmodl/transpiler/usecases/useion/ionic.mod similarity index 100% rename from test/nmodl/transpiler/usecases/ionic/ionic.mod rename to test/nmodl/transpiler/usecases/useion/ionic.mod diff --git a/test/nmodl/transpiler/usecases/ionic/simulate.py b/test/nmodl/transpiler/usecases/useion/simulate.py similarity index 94% rename from test/nmodl/transpiler/usecases/ionic/simulate.py rename to test/nmodl/transpiler/usecases/useion/simulate.py index fcbd4d37b7..3468a3b703 100644 --- a/test/nmodl/transpiler/usecases/ionic/simulate.py +++ b/test/nmodl/transpiler/usecases/useion/simulate.py @@ -25,4 +25,3 @@ abs_err = np.abs(x - x_exact) assert np.all(abs_err < 1e-12), abs_err -print("ionic: success") From 3f9438ac9e40ab529eda3363379e825e41ac95c7 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 10 Jun 2024 14:50:05 +0200 Subject: [PATCH 665/871] Micro-improve documentation. (BlueBrain/nmodl#1304) NMODL Repo SHA: BlueBrain/nmodl@56b507c005600835bbf8b0707b07fcf47ab1185b --- src/nmodl/nmodl/python/nmodl/ode.py | 27 +++------------------------ 1 file changed, 3 insertions(+), 24 deletions(-) diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index 3e14d174d2..38b46bb134 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -215,9 +215,7 @@ def _interweave_eqs(F, J): """Interweave F and J equations so that they are printed in code rowwise from the equation J x = F. For example: - F = [F_0, - F_1, - F_2] + F = [F_0, F_1, F_2] (Jmat is not the actual J in the argument, it is here to the sake of clarity) @@ -225,29 +223,10 @@ def _interweave_eqs(F, J): J_1, J_4, J_7 J_2, J_5, J_8] (J is the actual input with the following ordering) - J = [J_0, - J_3, - J_6, - J_1, - J_4, - J_7, - J_2, - J_5, - J_8] + J = [J_0, J_3, J_6, J_1, J_4, J_7, J_2, J_5, J_8] What we want is: - code = [F_0, - J_0, - J_3, - J_6, - F_1, - J_1, - J_4, - J_7, - F_2, - J_2, - J_5, - J_8] + code = [F_0, J_0, J_3, J_6, F_1, J_1, J_4, J_7, F_2, J_2, J_5, J_8] Args: F: F vector From 281d90a07b187f9020eb8e0847bd8566471fef4a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 10 Jun 2024 16:25:23 +0200 Subject: [PATCH 666/871] Make `nmodl` independent of `nmodl`. (BlueBrain/nmodl#1305) The executable from the Python library. NMODL Repo SHA: BlueBrain/nmodl@c5ecfea39cbd53ffcc248b1dcf732c1e0ae1ffc4 --- src/nmodl/nmodl/python/nmodl/ode.py | 6 +- src/nmodl/pybind/CMakeLists.txt | 10 +- src/nmodl/pybind/ode_py.hpp.inc | 19 ++++ src/nmodl/pybind/wrapper.cpp | 161 +++++++++++++--------------- 4 files changed, 106 insertions(+), 90 deletions(-) create mode 100644 src/nmodl/pybind/ode_py.hpp.inc diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index 38b46bb134..e5cc926d1e 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -19,8 +19,10 @@ known_functions = import_module("sympy.printing.c").known_functions_C99 else: known_functions = import_module("sympy.printing.ccode").known_functions_C99 -known_functions.pop("Abs") -known_functions["abs"] = "fabs" + +if "Abs" in known_functions: + known_functions.pop("Abs") + known_functions["abs"] = "fabs" if not ((major >= 1) and (minor >= 2)): diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 7517c4dd95..eb2ce323a5 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -19,6 +19,14 @@ endif() # build nmodl python module under lib/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl) +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py NMODL_ODE_PY) +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ode_py.hpp.inc ${CMAKE_CURRENT_BINARY_DIR}/ode_py.hpp + @ONLY@) + add_library(pyembed pyembed.cpp) set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(pyembed PRIVATE util) @@ -35,7 +43,7 @@ endif() target_include_directories(pyembed PRIVATE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) target_include_directories(pywrapper PRIVATE ${pybind11_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) - +target_include_directories(pywrapper PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # ~~~ # pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to # libpython, we can use `module` interface library from pybind11. diff --git a/src/nmodl/pybind/ode_py.hpp.inc b/src/nmodl/pybind/ode_py.hpp.inc new file mode 100644 index 0000000000..b6eef4e21e --- /dev/null +++ b/src/nmodl/pybind/ode_py.hpp.inc @@ -0,0 +1,19 @@ +#include + +// This file is generated from `ode.py`. +// +// During code-generation NMODL needs to call SymPy, e.g. to compute +// derivatives symbolically. The code to do this can be found in `ode.py`. +// +// To avoid a dependency of the `nmodl` binary on the Python library `nmodl`. +// We embed `ode.py` like this. +// +// However, because we want to be able to test `ode.py` via pytest we can't +// move it here. + +namespace nmodl::pybind_wrappers { +const std::string ode_py = R"jiowi( +@NMODL_ODE_PY@ +)jiowi"; + +} diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 52f9913f6d..2d21859201 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -14,6 +14,8 @@ #include #include +#include "ode_py.hpp" + namespace py = pybind11; using namespace py::literals; @@ -28,27 +30,24 @@ void SolveLinearSystemExecutor::operator()() { "do_cse"_a = elimination, "function_calls"_a = function_calls, "tmp_unique_prefix"_a = tmp_unique_prefix); - py::exec(R"( - import builtins - builtins.nmodl_python_binding_check = False - from nmodl.ode import solve_lin_system - exception_message = "" - try: - solutions, new_local_vars = solve_lin_system(eq_strings, - state_vars, - vars, - function_calls, - tmp_unique_prefix, - small_system, - do_cse) - except Exception as e: - # if we fail, fail silently and return empty string - solutions = [""] - new_local_vars = [""] - exception_message = str(e) - )", - py::globals(), - locals); + std::string script = R"( +exception_message = "" +try: + solutions, new_local_vars = solve_lin_system(eq_strings, + state_vars, + vars, + function_calls, + tmp_unique_prefix, + small_system, + do_cse) +except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) +)"; + + py::exec(nmodl::pybind_wrappers::ode_py + script, locals); // returns a vector of solutions, i.e. new statements to add to block: solutions = locals["solutions"].cast>(); // and a vector of new local variables that need to be declared in the block: @@ -63,24 +62,21 @@ void SolveNonLinearSystemExecutor::operator()() { "state_vars"_a = state_vars, "vars"_a = vars, "function_calls"_a = function_calls); - py::exec(R"( - import builtins - builtins.nmodl_python_binding_check = False - from nmodl.ode import solve_non_lin_system - exception_message = "" - try: - solutions = solve_non_lin_system(equation_strings, - state_vars, - vars, - function_calls) - except Exception as e: - # if we fail, fail silently and return empty string - solutions = [""] - new_local_vars = [""] - exception_message = str(e) - )", - py::globals(), - locals); + std::string script = R"( +exception_message = "" +try: + solutions = solve_non_lin_system(equation_strings, + state_vars, + vars, + function_calls) +except Exception as e: + # if we fail, fail silently and return empty string + solutions = [""] + new_local_vars = [""] + exception_message = str(e) +)"; + + py::exec(nmodl::pybind_wrappers::ode_py + script, locals); // returns a vector of solutions, i.e. new statements to add to block: solutions = locals["solutions"].cast>(); // may also return a python exception message: @@ -98,39 +94,33 @@ void DiffeqSolverExecutor::operator()() { // replace x' = f(x) differential equation // with forwards Euler timestep: // x = x + f(x) * dt - py::exec(R"( - import builtins - builtins.nmodl_python_binding_check = False - from nmodl.ode import forwards_euler2c - exception_message = "" - try: - solution = forwards_euler2c(equation_string, dt_var, vars, function_calls) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), - locals); + std::string script = R"( +exception_message = "" +try: + solution = forwards_euler2c(equation_string, dt_var, vars, function_calls) +except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) +)"; + + py::exec(nmodl::pybind_wrappers::ode_py + script, locals); } else if (method == codegen::naming::CNEXP_METHOD) { // replace x' = f(x) differential equation // with analytic solution for x(t+dt) in terms of x(t) // x = ... - py::exec(R"( - import builtins - builtins.nmodl_python_binding_check = False - from nmodl.ode import integrate2c - exception_message = "" - try: - solution = integrate2c(equation_string, dt_var, vars, - use_pade_approx) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), - locals); + std::string script = R"( +exception_message = "" +try: + solution = integrate2c(equation_string, dt_var, vars, + use_pade_approx) +except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) +)"; + + py::exec(nmodl::pybind_wrappers::ode_py + script, locals); } else { // nothing to do, but the caller should know. return; @@ -141,25 +131,22 @@ void DiffeqSolverExecutor::operator()() { void AnalyticDiffExecutor::operator()() { auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block); - py::exec(R"( - import builtins - builtins.nmodl_python_binding_check = False - from nmodl.ode import differentiate2c - exception_message = "" - try: - rhs = expressions[-1].split("=", 1)[1] - solution = differentiate2c(rhs, - "v", - vars, - expressions[:-1] - ) - except Exception as e: - # if we fail, fail silently and return empty string - solution = "" - exception_message = str(e) - )", - py::globals(), - locals); + std::string script = R"( +exception_message = "" +try: + rhs = expressions[-1].split("=", 1)[1] + solution = differentiate2c(rhs, + "v", + vars, + expressions[:-1] + ) +except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) +)"; + + py::exec(nmodl::pybind_wrappers::ode_py + script, locals); solution = locals["solution"].cast(); exception_message = locals["exception_message"].cast(); } From 73bcd42dcd9d704d85cecd3e7b54fe198ce41c1b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 12 Jun 2024 10:05:34 +0200 Subject: [PATCH 667/871] Modernize NMODL -> Python layer. (BlueBrain/nmodl#1306) The changes are: * Don't forcefully set the PYTHONPATH. * Replace false object oriented code with a function (each). * Replace a global variable, with a function to initialize. * Use a reference for a pointer that's should never be a nullptr. NMODL Repo SHA: BlueBrain/nmodl@2636b4e67e4333b0c92985284917805506923e1c --- src/nmodl/main.cpp | 4 +- src/nmodl/pybind/CMakeLists.txt | 3 + src/nmodl/pybind/pyembed.cpp | 80 +++++++----- src/nmodl/pybind/pyembed.hpp | 120 +----------------- src/nmodl/pybind/wrapper.cpp | 119 ++++++++--------- src/nmodl/pybind/wrapper.hpp | 62 +++++++++ src/nmodl/visitors/main.cpp | 4 +- .../visitors/sympy_conductance_visitor.cpp | 10 +- src/nmodl/visitors/sympy_solver_visitor.cpp | 60 +++------ test/nmodl/transpiler/unit/codegen/main.cpp | 4 +- test/nmodl/transpiler/unit/visitor/main.cpp | 4 +- 11 files changed, 198 insertions(+), 272 deletions(-) create mode 100644 src/nmodl/pybind/wrapper.hpp diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 96a8ec964f..f90c7d5879 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -486,7 +486,7 @@ int main(int argc, const char* argv[]) { if (sympy_conductance || sympy_analytic || sparse_solver_exists(*ast)) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() .api() - ->initialize_interpreter(); + .initialize_interpreter(); if (sympy_conductance) { logger->info("Running sympy conductance visitor"); SympyConductanceVisitor().visit_program(*ast); @@ -507,7 +507,7 @@ int main(int argc, const char* argv[]) { } nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() .api() - ->finalize_interpreter(); + .finalize_interpreter(); } { diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index eb2ce323a5..9439ea4f32 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -30,6 +30,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ode_py.hpp.inc ${CMAKE_CURRENT_BINARY add_library(pyembed pyembed.cpp) set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(pyembed PRIVATE util) +target_link_libraries(pyembed PRIVATE fmt::fmt) if(NOT LINK_AGAINST_PYTHON) add_library(pywrapper SHARED ${CMAKE_CURRENT_SOURCE_DIR}/wrapper.cpp) @@ -41,6 +42,8 @@ else() target_compile_definitions(pyembed PRIVATE NMODL_STATIC_PYWRAPPER=1) endif() +target_link_libraries(pywrapper PRIVATE fmt::fmt) + target_include_directories(pyembed PRIVATE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) target_include_directories(pywrapper PRIVATE ${pybind11_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) target_include_directories(pywrapper PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index fd20bb101e..7e4bfb8dcc 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -4,13 +4,15 @@ * * SPDX-License-Identifier: Apache-2.0 */ +#include "pybind/pyembed.hpp" #include #include #include +#include + #include "config/config.h" -#include "pybind/pyembed.hpp" #include "utils/logger.hpp" #define STRINGIFY(x) #x @@ -22,15 +24,43 @@ namespace nmodl { namespace pybind_wrappers { +using nmodl_init_pybind_wrapper_api_fpointer = decltype(&nmodl_init_pybind_wrapper_api); + bool EmbeddedPythonLoader::have_wrappers() { #if defined(NMODL_STATIC_PYWRAPPER) - static auto wrapper_api = nmodl::pybind_wrappers::init_pybind_wrap_api(); - wrappers = &wrapper_api; - return true; + auto* init = &nmodl_init_pybind_wrapper_api; #else - wrappers = static_cast(dlsym(RTLD_DEFAULT, "nmodl_wrapper_api")); - return wrappers != nullptr; + auto* init = (nmodl_init_pybind_wrapper_api_fpointer) (dlsym(RTLD_DEFAULT, + "nmodl_init_pybind_wrapper_api")); #endif + + if (init != nullptr) { + wrappers = init(); + } + + return init != nullptr; +} + +void assert_compatible_python_versions() { + // This code is imported and slightly modified from PyBind11 because this + // is primarly in details for internal usage + // License of PyBind11 is BSD-style + + std::string compiled_ver = fmt::format("{}.{}", PY_MAJOR_VERSION, PY_MINOR_VERSION); + auto pPy_GetVersion = (const char* (*) (void) ) dlsym(RTLD_DEFAULT, "Py_GetVersion"); + if (pPy_GetVersion == nullptr) { + throw std::runtime_error("Unable to find the function `Py_GetVersion`"); + } + const char* runtime_ver = pPy_GetVersion(); + std::size_t len = compiled_ver.size(); + if (std::strncmp(runtime_ver, compiled_ver.c_str(), len) != 0 || + (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) { + throw std::runtime_error( + fmt::format("Python version mismatch. nmodl has been compiled with python {} and is " + "being run with python {}", + compiled_ver, + runtime_ver)); + } } void EmbeddedPythonLoader::load_libraries() { @@ -49,26 +79,7 @@ void EmbeddedPythonLoader::load_libraries() { throw std::runtime_error("Failed to dlopen"); } - // This code is imported from PyBind11 because this is primarly in details for internal usage - // License of PyBind11 is BSD-style - { - std::string compiled_ver = fmt::format("{}.{}", PY_MAJOR_VERSION, PY_MINOR_VERSION); - const char* (*fun)(void) = (const char* (*) (void) ) dlsym(pylib_handle, "Py_GetVersion"); - if (fun == nullptr) { - logger->critical("Unable to find the function `Py_GetVersion`"); - throw std::runtime_error("Unable to find the function `Py_GetVersion`"); - } - const char* runtime_ver = fun(); - std::size_t len = compiled_ver.size(); - if (std::strncmp(runtime_ver, compiled_ver.c_str(), len) != 0 || - (runtime_ver[len] >= '0' && runtime_ver[len] <= '9')) { - logger->critical( - "nmodl has been compiled with python {} and is being run with python {}", - compiled_ver, - runtime_ver); - throw std::runtime_error("Python version mismatch between compile-time and runtime."); - } - } + assert_compatible_python_versions(); if (std::getenv("NMODLHOME") == nullptr) { logger->critical("NMODLHOME environment variable must be set to load embedded python"); @@ -91,25 +102,36 @@ void EmbeddedPythonLoader::load_libraries() { } void EmbeddedPythonLoader::populate_symbols() { - wrappers = static_cast(dlsym(pybind_wrapper_handle, "nmodl_wrapper_api")); - if (!wrappers) { +#if defined(NMODL_STATIC_PYWRAPPER) + auto* init = &nmodl_init_pybind_wrapper_api; +#else + // By now it's been dynamically loaded with `RTLD_GLOBAL`. + auto* init = (nmodl_init_pybind_wrapper_api_fpointer) (dlsym(RTLD_DEFAULT, + "nmodl_init_pybind_wrapper_api")); +#endif + + if (!init) { const auto errstr = dlerror(); logger->critical("Tried but failed to load pybind wrapper symbols"); logger->critical(errstr); throw std::runtime_error("Failed to dlsym"); } + + wrappers = init(); } void EmbeddedPythonLoader::unload() { if (pybind_wrapper_handle) { dlclose(pybind_wrapper_handle); + pybind_wrapper_handle = nullptr; } if (pylib_handle) { dlclose(pylib_handle); + pylib_handle = nullptr; } } -const pybind_wrap_api* EmbeddedPythonLoader::api() { +const pybind_wrap_api& EmbeddedPythonLoader::api() { return wrappers; } diff --git a/src/nmodl/pybind/pyembed.hpp b/src/nmodl/pybind/pyembed.hpp index 851ff9240c..5c0acf4819 100644 --- a/src/nmodl/pybind/pyembed.hpp +++ b/src/nmodl/pybind/pyembed.hpp @@ -7,123 +7,11 @@ #pragma once -#include - -#include -#include -#include -#include +#include "wrapper.hpp" namespace nmodl { namespace pybind_wrappers { - -struct PythonExecutor { - virtual ~PythonExecutor() {} - - virtual void operator()() = 0; -}; - - -struct SolveLinearSystemExecutor: public PythonExecutor { - // input - std::vector eq_system; - std::vector state_vars; - std::set vars; - bool small_system; - bool elimination; - // This is used only if elimination is true. It gives the root for the tmp variables - std::string tmp_unique_prefix; - std::set function_calls; - // output - // returns a vector of solutions, i.e. new statements to add to block: - std::vector solutions; - // and a vector of new local variables that need to be declared in the block: - std::vector new_local_vars; - // may also return a python exception message: - std::string exception_message; - // executor function - void operator()() override; -}; - - -struct SolveNonLinearSystemExecutor: public PythonExecutor { - // input - std::vector eq_system; - std::vector state_vars; - std::set vars; - std::set function_calls; - // output - // returns a vector of solutions, i.e. new statements to add to block: - std::vector solutions; - // may also return a python exception message: - std::string exception_message; - - // executor function - void operator()() override; -}; - - -struct DiffeqSolverExecutor: public PythonExecutor { - // input - std::string node_as_nmodl; - std::string dt_var; - std::set vars; - bool use_pade_approx; - std::set function_calls; - std::string method; - // output - // returns solution, i.e. new statement to add to block: - std::string solution; - // may also return a python exception message: - std::string exception_message; - - // executor function - void operator()() override; -}; - - -struct AnalyticDiffExecutor: public PythonExecutor { - // input - std::vector expressions; - std::set used_names_in_block; - // output - // returns solution, i.e. new statement to add to block: - std::string solution; - // may also return a python exception message: - std::string exception_message; - - // executor function - void operator()() override; -}; - - -SolveLinearSystemExecutor* create_sls_executor_func(); -SolveNonLinearSystemExecutor* create_nsls_executor_func(); -DiffeqSolverExecutor* create_des_executor_func(); -AnalyticDiffExecutor* create_ads_executor_func(); -void destroy_sls_executor_func(SolveLinearSystemExecutor* exec); -void destroy_nsls_executor_func(SolveNonLinearSystemExecutor* exec); -void destroy_des_executor_func(DiffeqSolverExecutor* exec); -void destroy_ads_executor_func(AnalyticDiffExecutor* exec); - -void initialize_interpreter_func(); -void finalize_interpreter_func(); - -struct pybind_wrap_api { - decltype(&initialize_interpreter_func) initialize_interpreter; - decltype(&finalize_interpreter_func) finalize_interpreter; - decltype(&create_sls_executor_func) create_sls_executor; - decltype(&create_nsls_executor_func) create_nsls_executor; - decltype(&create_des_executor_func) create_des_executor; - decltype(&create_ads_executor_func) create_ads_executor; - decltype(&destroy_sls_executor_func) destroy_sls_executor; - decltype(&destroy_nsls_executor_func) destroy_nsls_executor; - decltype(&destroy_des_executor_func) destroy_des_executor; - decltype(&destroy_ads_executor_func) destroy_ads_executor; -}; - - /** * A singleton class handling access to the pybind_wrap_api struct * @@ -154,14 +42,14 @@ class EmbeddedPythonLoader { * Get access to the container struct for the pointers to the functions in the wrapper library. * @return a pybind_wrap_api pointer */ - const pybind_wrap_api* api(); + const pybind_wrap_api& api(); ~EmbeddedPythonLoader() { unload(); } private: - pybind_wrap_api* wrappers = nullptr; + pybind_wrap_api wrappers; void* pylib_handle = nullptr; void* pybind_wrapper_handle = nullptr; @@ -180,7 +68,5 @@ class EmbeddedPythonLoader { }; -pybind_wrap_api init_pybind_wrap_api() noexcept; - } // namespace pybind_wrappers } // namespace nmodl diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 2d21859201..3c9f2661a5 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -5,6 +5,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "wrapper.hpp" + #include "codegen/codegen_naming.hpp" #include "pybind/pyembed.hpp" @@ -22,7 +24,14 @@ using namespace py::literals; namespace nmodl { namespace pybind_wrappers { -void SolveLinearSystemExecutor::operator()() { +std::tuple, std::vector, std::string> +call_solve_linear_system(const std::vector& eq_system, + const std::vector& state_vars, + const std::set& vars, + bool small_system, + bool elimination, + const std::string& tmp_unique_prefix, + const std::set& function_calls) { const auto locals = py::dict("eq_strings"_a = eq_system, "state_vars"_a = state_vars, "vars"_a = vars, @@ -49,15 +58,21 @@ except Exception as e: py::exec(nmodl::pybind_wrappers::ode_py + script, locals); // returns a vector of solutions, i.e. new statements to add to block: - solutions = locals["solutions"].cast>(); + auto solutions = locals["solutions"].cast>(); // and a vector of new local variables that need to be declared in the block: - new_local_vars = locals["new_local_vars"].cast>(); + auto new_local_vars = locals["new_local_vars"].cast>(); // may also return a python exception message: - exception_message = locals["exception_message"].cast(); + auto exception_message = locals["exception_message"].cast(); + + return {std::move(solutions), std::move(new_local_vars), std::move(exception_message)}; } -void SolveNonLinearSystemExecutor::operator()() { +std::tuple, std::string> call_solve_nonlinear_system( + const std::vector& eq_system, + const std::vector& state_vars, + const std::set& vars, + const std::set& function_calls) { const auto locals = py::dict("equation_strings"_a = eq_system, "state_vars"_a = state_vars, "vars"_a = vars, @@ -78,12 +93,20 @@ except Exception as e: py::exec(nmodl::pybind_wrappers::ode_py + script, locals); // returns a vector of solutions, i.e. new statements to add to block: - solutions = locals["solutions"].cast>(); + auto solutions = locals["solutions"].cast>(); // may also return a python exception message: - exception_message = locals["exception_message"].cast(); + auto exception_message = locals["exception_message"].cast(); + + return {std::move(solutions), std::move(exception_message)}; } -void DiffeqSolverExecutor::operator()() { + +std::tuple call_diffeq_solver(const std::string& node_as_nmodl, + const std::string& dt_var, + const std::set& vars, + bool use_pade_approx, + const std::set& function_calls, + const std::string& method) { const auto locals = py::dict("equation_string"_a = node_as_nmodl, "dt_var"_a = dt_var, "vars"_a = vars, @@ -123,13 +146,18 @@ except Exception as e: py::exec(nmodl::pybind_wrappers::ode_py + script, locals); } else { // nothing to do, but the caller should know. - return; + return {}; } - solution = locals["solution"].cast(); - exception_message = locals["exception_message"].cast(); + auto solution = locals["solution"].cast(); + auto exception_message = locals["exception_message"].cast(); + + return {std::move(solution), std::move(exception_message)}; } -void AnalyticDiffExecutor::operator()() { + +std::tuple call_analytic_diff( + const std::vector& expressions, + const std::set& used_names_in_block) { auto locals = py::dict("expressions"_a = expressions, "vars"_a = used_names_in_block); std::string script = R"( exception_message = "" @@ -147,74 +175,33 @@ except Exception as e: )"; py::exec(nmodl::pybind_wrappers::ode_py + script, locals); - solution = locals["solution"].cast(); - exception_message = locals["exception_message"].cast(); -} - -SolveLinearSystemExecutor* create_sls_executor_func() { - return new SolveLinearSystemExecutor(); -} -SolveNonLinearSystemExecutor* create_nsls_executor_func() { - return new SolveNonLinearSystemExecutor(); -} + auto solution = locals["solution"].cast(); + auto exception_message = locals["exception_message"].cast(); -DiffeqSolverExecutor* create_des_executor_func() { - return new DiffeqSolverExecutor(); + return {std::move(solution), std::move(exception_message)}; } -AnalyticDiffExecutor* create_ads_executor_func() { - return new AnalyticDiffExecutor(); -} - -void destroy_sls_executor_func(SolveLinearSystemExecutor* exec) { - delete exec; -} - -void destroy_nsls_executor_func(SolveNonLinearSystemExecutor* exec) { - delete exec; -} - -void destroy_des_executor_func(DiffeqSolverExecutor* exec) { - delete exec; -} - -void destroy_ads_executor_func(AnalyticDiffExecutor* exec) { - delete exec; -} void initialize_interpreter_func() { pybind11::initialize_interpreter(true); - const auto python_path_cstr = std::getenv("PYTHONPATH"); - if (python_path_cstr) { - pybind11::module::import("sys").attr("path").cast().insert( - 0, python_path_cstr); - } } void finalize_interpreter_func() { pybind11::finalize_interpreter(); } -pybind_wrap_api init_pybind_wrap_api() noexcept { - return { - &nmodl::pybind_wrappers::initialize_interpreter_func, - &nmodl::pybind_wrappers::finalize_interpreter_func, - &nmodl::pybind_wrappers::create_sls_executor_func, - &nmodl::pybind_wrappers::create_nsls_executor_func, - &nmodl::pybind_wrappers::create_des_executor_func, - &nmodl::pybind_wrappers::create_ads_executor_func, - &nmodl::pybind_wrappers::destroy_sls_executor_func, - &nmodl::pybind_wrappers::destroy_nsls_executor_func, - &nmodl::pybind_wrappers::destroy_des_executor_func, - &nmodl::pybind_wrappers::destroy_ads_executor_func, - }; +// Prevent mangling for easier `dlsym`. +extern "C" { +__attribute__((visibility("default"))) pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept { + return {&nmodl::pybind_wrappers::initialize_interpreter_func, + &nmodl::pybind_wrappers::finalize_interpreter_func, + &call_solve_nonlinear_system, + &call_solve_linear_system, + &call_diffeq_solver, + &call_analytic_diff}; +} } } // namespace pybind_wrappers } // namespace nmodl - - -// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) -__attribute__((visibility("default"))) nmodl::pybind_wrappers::pybind_wrap_api nmodl_wrapper_api = - nmodl::pybind_wrappers::init_pybind_wrap_api(); diff --git a/src/nmodl/pybind/wrapper.hpp b/src/nmodl/pybind/wrapper.hpp new file mode 100644 index 0000000000..2a51da014e --- /dev/null +++ b/src/nmodl/pybind/wrapper.hpp @@ -0,0 +1,62 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include + +namespace nmodl { +namespace pybind_wrappers { + + +void initialize_interpreter_func(); +void finalize_interpreter_func(); + +std::tuple, std::vector, std::string> +call_solve_linear_system(const std::vector& eq_system, + const std::vector& state_vars, + const std::set& vars, + bool small_system, + bool elimination, + const std::string& tmp_unique_prefix, + const std::set& function_calls); + +std::tuple, std::string> call_solve_nonlinear_system( + const std::vector& eq_system, + const std::vector& state_vars, + const std::set& vars, + const std::set& function_calls); + +std::tuple call_diffeq_solver(const std::string& node_as_nmodl, + const std::string& dt_var, + const std::set& vars, + bool use_pade_approx, + const std::set& function_calls, + const std::string& method); + +std::tuple call_analytic_diff( + const std::vector& expressions, + const std::set& used_names_in_block); + +struct pybind_wrap_api { + decltype(&initialize_interpreter_func) initialize_interpreter; + decltype(&finalize_interpreter_func) finalize_interpreter; + decltype(&call_solve_nonlinear_system) solve_nonlinear_system; + decltype(&call_solve_linear_system) solve_linear_system; + decltype(&call_diffeq_solver) diffeq_solver; + decltype(&call_analytic_diff) analytic_diff; +}; + +extern "C" { +__attribute__((visibility("default"))) pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept; +} + + +} // namespace pybind_wrappers +} // namespace nmodl diff --git a/src/nmodl/visitors/main.cpp b/src/nmodl/visitors/main.cpp index 42ba3a89d0..9a6b969663 100644 --- a/src/nmodl/visitors/main.cpp +++ b/src/nmodl/visitors/main.cpp @@ -106,7 +106,7 @@ int main(int argc, const char* argv[]) { {std::make_shared(), "verbatim", "VerbatimVisitor"}, }; - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api().initialize_interpreter(); for (const auto& filename: files) { logger->info("Processing {}", filename.string()); @@ -128,7 +128,7 @@ int main(int argc, const char* argv[]) { } } - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api().finalize_interpreter(); return 0; } diff --git a/src/nmodl/visitors/sympy_conductance_visitor.cpp b/src/nmodl/visitors/sympy_conductance_visitor.cpp index 30aa9bfd4e..7d161e352f 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.cpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.cpp @@ -74,14 +74,8 @@ std::vector SympyConductanceVisitor::generate_statement_strings( binary_expr_index[lhs_str]) + 1); // differentiate dI/dV - auto analytic_diff = - pywrap::EmbeddedPythonLoader::get_instance().api()->create_ads_executor(); - analytic_diff->expressions = expressions; - analytic_diff->used_names_in_block = used_names_in_block; - (*analytic_diff)(); - auto dIdV = analytic_diff->solution; - auto exception_message = analytic_diff->exception_message; - pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_ads_executor(analytic_diff); + auto analytic_diff = pywrap::EmbeddedPythonLoader::get_instance().api().analytic_diff; + auto [dIdV, exception_message] = analytic_diff(expressions, used_names_in_block); if (!exception_message.empty()) { logger->warn("SympyConductance :: python exception: {}", exception_message); } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 040bddbc69..68dfcff28d 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -288,25 +288,16 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre init_state_vars_vector(); // call sympy linear solver bool small_system = (eq_system.size() <= SMALL_LINEAR_SYSTEM_MAX_STATES); - auto solver = pywrap::EmbeddedPythonLoader::get_instance().api()->create_sls_executor(); - solver->eq_system = eq_system; - solver->state_vars = state_vars; - solver->vars = vars; - solver->small_system = small_system; - solver->elimination = elimination; + auto solver = pywrap::EmbeddedPythonLoader::get_instance().api().solve_linear_system; // this is necessary after we destroy the solver const auto tmp_unique_prefix = suffix_random_string(vars, "tmp"); - solver->tmp_unique_prefix = tmp_unique_prefix; - solver->function_calls = function_calls; - (*solver)(); - // returns a vector of solutions, i.e. new statements to add to block: - auto solutions = solver->solutions; - // and a vector of new local variables that need to be declared in the block: - auto new_local_vars = solver->new_local_vars; + + // returns a vector of solutions, i.e. new statements to add to block; + // and a vector of new local variables that need to be declared in the block; // may also return a python exception message: - auto exception_message = solver->exception_message; - // destroy solver - pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_sls_executor(solver); + auto [solutions, new_local_vars, exception_message] = solver( + eq_system, state_vars, vars, small_system, elimination, tmp_unique_prefix, function_calls); + if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: solve_lin_system python exception: " + exception_message); @@ -344,19 +335,10 @@ void SympySolverVisitor::solve_non_linear_system( const std::vector& pre_solve_statements) { // construct ordered vector of state vars used in non-linear system init_state_vars_vector(); - // call sympy non-linear solver - - auto solver = pywrap::EmbeddedPythonLoader::get_instance().api()->create_nsls_executor(); - solver->eq_system = eq_system; - solver->state_vars = state_vars; - solver->vars = vars; - solver->function_calls = function_calls; - (*solver)(); - // returns a vector of solutions, i.e. new statements to add to block: - auto solutions = solver->solutions; - // may also return a python exception message: - auto exception_message = solver->exception_message; - pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_nsls_executor(solver); + + auto solver = pywrap::EmbeddedPythonLoader::get_instance().api().solve_nonlinear_system; + auto [solutions, exception_message] = solver(eq_system, state_vars, vars, function_calls); + if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: solve_non_lin_system python exception: " + exception_message); @@ -404,19 +386,11 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { check_expr_statements_in_same_block(); const auto node_as_nmodl = to_nmodl_for_sympy(node); - const auto deleter = [](nmodl::pybind_wrappers::DiffeqSolverExecutor* ptr) { - pywrap::EmbeddedPythonLoader::get_instance().api()->destroy_des_executor(ptr); - }; - std::unique_ptr diffeq_solver{ - pywrap::EmbeddedPythonLoader::get_instance().api()->create_des_executor(), deleter}; - - diffeq_solver->node_as_nmodl = node_as_nmodl; - diffeq_solver->dt_var = codegen::naming::NTHREAD_DT_VARIABLE; - diffeq_solver->vars = vars; - diffeq_solver->use_pade_approx = use_pade_approx; - diffeq_solver->function_calls = function_calls; - diffeq_solver->method = solve_method; - (*diffeq_solver)(); + auto diffeq_solver = pywrap::EmbeddedPythonLoader::get_instance().api().diffeq_solver; + + auto dt_var = codegen::naming::NTHREAD_DT_VARIABLE; + auto [solution, exception_message] = (*diffeq_solver)( + node_as_nmodl, dt_var, vars, use_pade_approx, function_calls, solve_method); if (solve_method == codegen::naming::EULER_METHOD) { // replace x' = f(x) differential equation // with forwards Euler timestep: @@ -449,10 +423,8 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { } // replace ODE with solution in AST - auto solution = diffeq_solver->solution; logger->debug("SympySolverVisitor :: -> solution: {}", solution); - auto exception_message = diffeq_solver->exception_message; if (!exception_message.empty()) { logger->warn("SympySolverVisitor :: python exception: " + exception_message); return; diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp index 53060dd673..d8de60edbd 100644 --- a/test/nmodl/transpiler/unit/codegen/main.cpp +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -15,11 +15,11 @@ using namespace nmodl; int main(int argc, char* argv[]) { // initialize python interpreter once for entire catch executable - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api().initialize_interpreter(); // enable verbose logger output logger->set_level(spdlog::level::debug); // run all catch tests int result = Catch::Session().run(argc, argv); - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api().finalize_interpreter(); return result; } diff --git a/test/nmodl/transpiler/unit/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp index 53060dd673..d8de60edbd 100644 --- a/test/nmodl/transpiler/unit/visitor/main.cpp +++ b/test/nmodl/transpiler/unit/visitor/main.cpp @@ -15,11 +15,11 @@ using namespace nmodl; int main(int argc, char* argv[]) { // initialize python interpreter once for entire catch executable - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->initialize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api().initialize_interpreter(); // enable verbose logger output logger->set_level(spdlog::level::debug); // run all catch tests int result = Catch::Session().run(argc, argv); - nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api()->finalize_interpreter(); + nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance().api().finalize_interpreter(); return result; } From 45b78c9b92679b0426d7570f5ff33ef9773bae29 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 12 Jun 2024 10:12:06 +0200 Subject: [PATCH 668/871] Consistently use `_lmc` and references. (BlueBrain/nmodl#1309) Frequently `_lmr` is passed by pointer, which requires printing: ``` foo(&_lmr, ...); ``` the `&` is annoying, because it prevents us from using `get_arg_str`. It also sometimes leads us to do: ``` auto * _ml = &_lmr; ``` which is bad, because it's not a `Memb_list`. * Rename '_lmr' to '_lmc'. NMODL Repo SHA: BlueBrain/nmodl@13550d71d172809e67ea5ccc7b4a9b1e6427f12c --- .../codegen/codegen_neuron_cpp_visitor.cpp | 61 ++++++++----------- 1 file changed, 25 insertions(+), 36 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 8ea091f02c..c013f72df7 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -179,15 +179,14 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { // signature must be same as the `nrn_thread_table_check_t` type printer->fmt_line("static void {}({})", table_thread_function_name(), get_parameter_str(args)); printer->push_block(); - printer->add_line("_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml, _type};"); - printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml, _type};"); + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); for (const auto& function: info.functions_with_table) { auto method_name_str = function->get_node_name(); - printer->fmt_line("{}{}{}(&_lmr, inst, id, _ppvar, _thread, _nt);", - table_function_prefix(), - method_name_str, - info.rsuffix); + auto method_args_str = get_arg_str(internal_method_parameters()); + printer->fmt_line( + "{}{}{}({});", table_function_prefix(), method_name_str, info.rsuffix, method_args_str); } printer->pop_block(); } @@ -204,7 +203,7 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { } if (!info.vectorize) { printer->add_multi_line(R"CODE( - neuron::legacy::set_globals_from_prop(_prop, _ml_real, _ml, _iml); + neuron::legacy::set_globals_from_prop(_prop, _lmc, _ml, _iml); _ppvar = _nrn_mechanism_access_dparam(_prop); )CODE"); } @@ -352,8 +351,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( if (!_p) { hoc_execerror("POINT_PROCESS data instance not valid", NULL); } - _nrn_mechanism_cache_instance _ml_real{_p}; - auto* const _ml = &_ml_real; + _nrn_mechanism_cache_instance _lmc{_p}; size_t const id{}; _ppvar = _nrn_mechanism_access_dparam(_p); _thread = _extcall_thread.data(); @@ -374,8 +372,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( printer->add_line("Prop* _local_prop = _prop_id ? _extcall_prop : nullptr;"); } printer->add_multi_line(R"CODE( - _nrn_mechanism_cache_instance _ml_real{_local_prop}; - auto* const _ml = &_ml_real; + _nrn_mechanism_cache_instance _lmc{_local_prop}; size_t const id{}; _ppvar = _local_prop ? _nrn_mechanism_access_dparam(_local_prop) : nullptr; _thread = _extcall_thread.data(); @@ -383,15 +380,14 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( )CODE"); } else { // wrapper_type == InterpreterWrapper::Python printer->add_multi_line(R"CODE( - _nrn_mechanism_cache_instance _ml_real{_prop}; - auto* const _ml = &_ml_real; + _nrn_mechanism_cache_instance _lmc{_prop}; size_t const id{}; _ppvar = _nrn_mechanism_access_dparam(_prop); _thread = _extcall_thread.data(); _nt = nrn_threads; )CODE"); } - printer->fmt_line("auto inst = make_instance_{}(_ml_real);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); if (info.function_uses_table(block_name)) { printer->fmt_line("{}{}({});", table_function_prefix(), @@ -462,7 +458,7 @@ std::string CodegenNeuronCppVisitor::internal_method_arguments() { CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { - ParamVector params = {{"", "_nrn_mechanism_cache_range*", "", "_ml"}, + ParamVector params = {{"", "_nrn_mechanism_cache_range&", "", "_lmc"}, {"", fmt::format("{}&", instance_struct()), "", "inst"}, {"", "size_t", "", "id"}, {"", "Datum*", "", "_ppvar"}, @@ -1301,7 +1297,7 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini void CodegenNeuronCppVisitor::print_make_instance() const { printer->add_newline(2); - printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range& _ml)", + printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range& _lmc)", instance_struct(), info.mod_suffix); printer->fmt_push_block("return {}", instance_struct()); @@ -1320,9 +1316,9 @@ void CodegenNeuronCppVisitor::print_make_instance() const { const auto& float_var = codegen_float_variables[i]; if (float_var->is_array()) { make_instance_args.push_back( - fmt::format("_ml.template data_array_ptr<{}, {}>()", i, float_var->get_length())); + fmt::format("_lmc.template data_array_ptr<{}, {}>()", i, float_var->get_length())); } else { - make_instance_args.push_back(fmt::format("_ml.template fpfield_ptr<{}>()", i)); + make_instance_args.push_back(fmt::format("_lmc.template fpfield_ptr<{}>()", i)); } } @@ -1336,7 +1332,7 @@ void CodegenNeuronCppVisitor::print_make_instance() const { } else if (var.is_vdata) { return ""; } else { - return fmt::format("_ml.template dptr_field_ptr<{}>()", i); + return fmt::format("_lmc.template dptr_field_ptr<{}>()", i); } }(); if (variable != "") { @@ -1446,12 +1442,11 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, {"", "int", "", "_type"}}; printer->fmt_push_block("void {}({})", method, get_parameter_str(args)); - printer->add_line("_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); - printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml_arg, _type};"); + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); printer->add_line("auto nodecount = _ml_arg->nodecount;"); - printer->add_line("auto* const _ml = &_lmr;"); printer->add_line("auto* _thread = _ml_arg->_thread;"); if (!codegen_thread_variables.empty()) { printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", @@ -1495,9 +1490,9 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_multi_line( - "_nrn_mechanism_cache_range _lmr{_sorted_token, *_nt, *_ml_arg, _type};"); + "_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml_arg, _type};"); - printer->fmt_line("auto inst = make_instance_{}(_lmr);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); printer->fmt_line("auto nodecount = _ml_arg->nodecount;"); printer->push_block("for (int id = 0; id < nodecount; id++)"); // begin for @@ -1551,8 +1546,7 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->add_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); } printer->add_multi_line(R"CODE( - _nrn_mechanism_cache_instance _ml_real{_prop}; - auto* const _ml = &_ml_real; + _nrn_mechanism_cache_instance _lmc{_prop}; size_t const _iml{}; )CODE"); printer->fmt_line("assert(_nrn_mechanism_get_num_vars(_prop) == {});", @@ -1567,7 +1561,7 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { auto var_pos = position_of_float_var(var_name); double var_value = var->get_value() == nullptr ? 0.0 : *var->get_value(); - printer->fmt_line("_ml->template fpfield<{}>(_iml) = {}; /* {} */", + printer->fmt_line("_lmc.template fpfield<{}>(_iml) = {}; /* {} */", var_pos, var_value, var_name); @@ -1685,11 +1679,7 @@ void CodegenNeuronCppVisitor::print_nrn_state() { /****************************************************************************************/ std::string CodegenNeuronCppVisitor::nrn_current_arguments() { - if (ion_variable_struct_required()) { - throw std::runtime_error("Not implemented."); - } - std::string thread_globals = info.thread_callback_register ? " _thread_vars," : ""; - return "_ml, _nt, _ppvar, _thread," + thread_globals + " id, inst, node_data, v"; + return get_arg_str(nrn_current_parameters()); } @@ -1698,7 +1688,7 @@ CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parame throw std::runtime_error("Not implemented."); } - ParamVector params = {{"", "_nrn_mechanism_cache_range*", "", "_ml"}, + ParamVector params = {{"", "_nrn_mechanism_cache_range&", "", "_lmc"}, {"", "NrnThread*", "", "_nt"}, {"", "Datum*", "", "_ppvar"}, {"", "Datum*", "", "_thread"}}; @@ -2134,12 +2124,11 @@ void CodegenNeuronCppVisitor::print_net_receive() { rename_net_receive_arguments(*node, *node); - printer->add_line("_nrn_mechanism_cache_instance _ml_obj{_pnt->prop};"); + printer->add_line("_nrn_mechanism_cache_instance _lmc{_pnt->prop};"); printer->add_line("auto * _nt = static_cast(_pnt->_vnt);"); - printer->add_line("auto * _ml = &_ml_obj;"); printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); - printer->fmt_line("auto inst = make_instance_{}(_ml_obj);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); printer->add_line("size_t id = 0;"); printer->add_line("double t = _nt->_t;"); From 9deb54e0e2f445606372a02078ef1a7c5328787d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 12 Jun 2024 12:07:51 +0200 Subject: [PATCH 669/871] Fix thread_variables in functions and tables. (BlueBrain/nmodl#1310) NMODL Repo SHA: BlueBrain/nmodl@956289316ca6f3fcf6c6c325d15f1ab984c51503 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 26 ++++++++++++++----- .../transpiler/usecases/global/simulate.py | 4 +-- .../usecases/global/thread_variable.mod | 24 ++++++++++++++--- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index c013f72df7..ba9da2372c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -181,6 +181,11 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { printer->push_block(); printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!codegen_thread_variables.empty()) { + printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", + thread_variables_struct(), + info.thread_var_thread_id); + } for (const auto& function: info.functions_with_table) { auto method_name_str = function->get_node_name(); @@ -388,6 +393,11 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( )CODE"); } printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!codegen_thread_variables.empty()) { + printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", + thread_variables_struct(), + info.thread_var_thread_id); + } if (info.function_uses_table(block_name)) { printer->fmt_line("{}{}({});", table_function_prefix(), @@ -458,12 +468,16 @@ std::string CodegenNeuronCppVisitor::internal_method_arguments() { CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { - ParamVector params = {{"", "_nrn_mechanism_cache_range&", "", "_lmc"}, - {"", fmt::format("{}&", instance_struct()), "", "inst"}, - {"", "size_t", "", "id"}, - {"", "Datum*", "", "_ppvar"}, - {"", "Datum*", "", "_thread"}, - {"", "NrnThread*", "", "_nt"}}; + ParamVector params; + params.emplace_back("", "_nrn_mechanism_cache_range&", "", "_lmc"); + params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); + params.emplace_back("", "size_t", "", "id"); + params.emplace_back("", "Datum*", "", "_ppvar"); + params.emplace_back("", "Datum*", "", "_thread"); + if (!codegen_thread_variables.empty()) { + params.emplace_back("", fmt::format("{}&", thread_variables_struct()), "", "_thread_vars"); + } + params.emplace_back("", "NrnThread*", "", "_nt"); return params; } diff --git a/test/nmodl/transpiler/usecases/global/simulate.py b/test/nmodl/transpiler/usecases/global/simulate.py index d8bf22a6e1..8de8df686f 100644 --- a/test/nmodl/transpiler/usecases/global/simulate.py +++ b/test/nmodl/transpiler/usecases/global/simulate.py @@ -68,7 +68,7 @@ assert np.all(y0[i0] == g_w_init) assert np.all(y0[i1] == g_w1) assert np.all(y0[i2] == 33.3) -assert np.all(y0[i3] == z0) +assert np.all(y0[i3] == z0 + z0**2) # The values on thread 1: assert y1[i0] == g_w_init @@ -76,4 +76,4 @@ # `g_w` from Python. # assert np.all(y0[i1] == g_w1) assert np.all(y1[i2] == 34.3) -assert np.all(y1[i3] == z1) +assert np.all(y1[i3] == z1 + z1**2) diff --git a/test/nmodl/transpiler/usecases/global/thread_variable.mod b/test/nmodl/transpiler/usecases/global/thread_variable.mod index e5da59305d..19478428a3 100644 --- a/test/nmodl/transpiler/usecases/global/thread_variable.mod +++ b/test/nmodl/transpiler/usecases/global/thread_variable.mod @@ -2,7 +2,7 @@ NEURON { SUFFIX shared_global NONSPECIFIC_CURRENT il RANGE y, z - GLOBAL g_w, g_arr + GLOBAL g_v1, g_w, g_arr THREADSAFE } @@ -16,6 +16,7 @@ ASSIGNED { INITIAL { g_w = 48.0 + g_v1 = 0.0 g_arr[0] = 10.0 + z g_arr[1] = 10.1 g_arr[2] = 10.2 @@ -24,12 +25,27 @@ INITIAL { BREAKPOINT { if(t > 0.33) { - g_w = g_arr[0] + g_arr[1] + g_arr[2] + g_w = sum_arr() } if(t > 0.66) { - g_w = z + set_g_w(z) + compute_g_v1(z) } - y = g_w + + y = g_w + g_v1 il = 0.0000001 * (v - 10.0) } + +FUNCTION sum_arr() { + sum_arr = g_arr[0] + g_arr[1] + g_arr[2] +} + +PROCEDURE set_g_w(zz) { + g_w = zz +} + +PROCEDURE compute_g_v1(zz) { + TABLE g_v1 FROM 3 TO 4 WITH 8 + g_v1 = zz * zz +} From 04ab2298bc007b2f4fc021dc10df7ddeb0b456db Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 12 Jun 2024 13:07:10 +0200 Subject: [PATCH 670/871] Lift shared code for KINETIC. (BlueBrain/nmodl#1312) NMODL Repo SHA: BlueBrain/nmodl@8679d7ef5f1c60535400b8ecb229b560c9908c46 --- .../codegen_coreneuron_cpp_visitor.cpp | 225 +----------------- .../codegen_coreneuron_cpp_visitor.hpp | 31 +-- src/nmodl/codegen/codegen_cpp_visitor.cpp | 205 ++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 36 ++- .../codegen/codegen_neuron_cpp_visitor.cpp | 20 +- .../codegen/codegen_neuron_cpp_visitor.hpp | 5 +- .../transpiler/unit/visitor/sympy_solver.cpp | 25 +- .../nmodl/transpiler/usecases/kinetic/X2Y.mod | 30 +++ .../transpiler/usecases/kinetic/simulate.py | 37 +++ 9 files changed, 363 insertions(+), 251 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/kinetic/X2Y.mod create mode 100644 test/nmodl/transpiler/usecases/kinetic/simulate.py diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index b9aa2bcfa1..a0b3a79b28 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -514,157 +514,6 @@ void CodegenCoreneuronCppVisitor::print_function_tables(const ast::FunctionTable } -/** - * @brief Checks whether the functor_block generated by sympy solver modifies any variable outside - * its scope. If it does then return false, so that the operator() of the struct functor of the - * Eigen Newton solver doesn't have const qualifier. - * - * @param variable_block Statement Block of the variables declarations used in the functor struct of - * the solver - * @param functor_block Actual code being printed in the operator() of the functor struct of the - * solver - * @return True if operator() is const else False - */ -bool CodegenCoreneuronCppVisitor::is_functor_const(const ast::StatementBlock& variable_block, - const ast::StatementBlock& functor_block) { - // Create complete_block with both variable declarations (done in variable_block) and solver - // part (done in functor_block) to be able to run the SymtabVisitor and DefUseAnalyzeVisitor - // then and get the proper DUChains for the variables defined in the variable_block - ast::StatementBlock complete_block(functor_block); - // Typically variable_block has only one statement, a statement containing the declaration - // of the local variables - for (const auto& statement: variable_block.get_statements()) { - complete_block.insert_statement(complete_block.get_statements().begin(), statement); - } - - // Create Symbol Table for complete_block - auto model_symbol_table = std::make_shared(); - SymtabVisitor(model_symbol_table.get()).visit_statement_block(complete_block); - // Initialize DefUseAnalyzeVisitor to generate the DUChains for the variables defined in the - // variable_block - DefUseAnalyzeVisitor v(*complete_block.get_symbol_table()); - - // Check the DUChains for all the variables in the variable_block - // If variable is defined in complete_block don't add const quilifier in operator() - auto is_functor_const = true; - const auto& variables = collect_nodes(variable_block, {ast::AstNodeType::LOCAL_VAR}); - for (const auto& variable: variables) { - const auto& chain = v.analyze(complete_block, variable->get_node_name()); - is_functor_const = !(chain.eval() == DUState::D || chain.eval() == DUState::LD || - chain.eval() == DUState::CD); - if (!is_functor_const) { - break; - } - } - - return is_functor_const; -} - - -void CodegenCoreneuronCppVisitor::print_functor_definition( - const ast::EigenNewtonSolverBlock& node) { - // functor that evaluates F(X) and J(X) for - // Newton solver - auto float_type = default_float_data_type(); - int N = node.get_n_state_vars()->get_value(); - - const auto functor_name = info.functor_names[&node]; - printer->fmt_push_block("struct {0}", functor_name); - printer->add_line("NrnThread* nt;"); - printer->add_line(instance_struct(), "* inst;"); - printer->add_line("int id, pnodecount;"); - printer->add_line("double v;"); - printer->add_line("const Datum* indexes;"); - printer->add_line("double* data;"); - printer->add_line("ThreadDatum* thread;"); - - if (ion_variable_struct_required()) { - print_ion_variable(); - } - - print_statement_block(*node.get_variable_block(), false, false); - printer->add_newline(); - - printer->push_block("void initialize()"); - print_statement_block(*node.get_initialize_block(), false, false); - printer->pop_block(); - printer->add_newline(); - - printer->fmt_line( - "{0}(NrnThread* nt, {1}* inst, int id, int pnodecount, double v, const Datum* indexes, " - "double* data, ThreadDatum* thread) : " - "nt{{nt}}, inst{{inst}}, id{{id}}, pnodecount{{pnodecount}}, v{{v}}, indexes{{indexes}}, " - "data{{data}}, thread{{thread}} " - "{{}}", - functor_name, - instance_struct()); - - printer->add_indent(); - - const auto& variable_block = *node.get_variable_block(); - const auto& functor_block = *node.get_functor_block(); - - printer->fmt_text( - "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " - "1>& nmodl_eigen_fm, " - "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", - float_type, - N, - is_functor_const(variable_block, functor_block) ? "const " : ""); - printer->push_block(); - printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); - print_statement_block(functor_block, false, false); - printer->pop_block(); - printer->add_newline(); - - // assign newton solver results in matrix X to state vars - printer->push_block("void finalize()"); - print_statement_block(*node.get_finalize_block(), false, false); - printer->pop_block(); - - printer->pop_block(";"); -} - - -void CodegenCoreneuronCppVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { - if (N <= 4) { - // Faster compared to LU, given the template specialization in Eigen. - printer->add_multi_line(R"CODE( - bool invertible; - nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible); - nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm; - if (!invertible) assert(false && "Singular or ill-conditioned matrix (Eigen::inverse)!"); - )CODE"); - } else { - // In Eigen the default storage order is ColMajor. - // Crout's implementation requires matrices stored in RowMajor order (C++-style arrays). - // Therefore, the transposeInPlace is critical such that the data() method to give the rows - // instead of the columns. - printer->add_line("if (!nmodl_eigen_jm.IsRowMajor) nmodl_eigen_jm.transposeInPlace();"); - - // pivot vector - printer->fmt_line("Eigen::Matrix pivot;", N); - printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> rowmax;", float_type, N); - - // In-place LU-Decomposition (Crout Algo) : Jm is replaced by its LU-decomposition - printer->fmt_line( - "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data(), rowmax.data()) " - "< 0) assert(false && \"Singular or ill-conditioned matrix (nmodl::crout)!\");", - float_type, - N); - - // Solve the linear system : Forward/Backward substitution part - printer->fmt_line( - "nmodl::crout::solveCrout<{0}>({1}, nmodl_eigen_jm.data(), nmodl_eigen_fm.data(), " - "nmodl_eigen_xm.data(), pivot.data());", - float_type, - N); - } -} - - /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ @@ -1220,6 +1069,18 @@ void CodegenCoreneuronCppVisitor::print_sdlists_init(bool print_initializers) { } +CodegenCppVisitor::ParamVector CodegenCoreneuronCppVisitor::functor_params() { + return ParamVector{{"", "NrnThread*", "", "nt"}, + {"", fmt::format("{}*", instance_struct()), "", "inst"}, + {"", "int", "", "id"}, + {"", "int", "", "pnodecount"}, + {"", "double", "", "v"}, + {"const ", "Datum*", "", "indexes"}, + {"", "double*", "", "data"}, + {"", "ThreadDatum*", "", "thread"}}; +} + + /** * \details Variables required for type of ion, type of point process etc. are * of static int type. For the C++ backend type, it's ok to have @@ -2208,14 +2069,6 @@ void CodegenCoreneuronCppVisitor::print_nrn_destructor() { } -void CodegenCoreneuronCppVisitor::print_functors_definitions() { - for (const auto& functor_name: info.functor_names) { - printer->add_newline(2); - print_functor_definition(*functor_name.first); - } -} - - void CodegenCoreneuronCppVisitor::print_nrn_alloc() { printer->add_newline(2); auto method = method_name(naming::NRN_ALLOC_METHOD); @@ -3258,60 +3111,6 @@ void CodegenCoreneuronCppVisitor::visit_derivimplicit_callback(const ast::Derivi } -void CodegenCoreneuronCppVisitor::visit_eigen_newton_solver_block( - const ast::EigenNewtonSolverBlock& node) { - // solution vector to store copy of state vars for Newton solver - printer->add_newline(); - - auto float_type = default_float_data_type(); - int N = node.get_n_state_vars()->get_value(); - printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); - printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - - print_statement_block(*node.get_setup_x_block(), false, false); - - // call newton solver with functor and X matrix that contains state vars - printer->add_line("// call newton solver"); - printer->fmt_line("{} newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);", - info.functor_names[&node]); - printer->add_line("newton_functor.initialize();"); - printer->add_line( - "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); - printer->add_line( - "if (newton_iterations < 0) assert(false && \"Newton solver did not converge!\");"); - - // assign newton solver results in matrix X to state vars - print_statement_block(*node.get_update_states_block(), false, false); - printer->add_line("newton_functor.finalize();"); -} - - -void CodegenCoreneuronCppVisitor::visit_eigen_linear_solver_block( - const ast::EigenLinearSolverBlock& node) { - printer->add_newline(); - - const std::string float_type = default_float_data_type(); - int N = node.get_n_state_vars()->get_value(); - printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); - printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); - if (N <= 4) - printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm_inv;", float_type, N); - printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); - printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); - print_statement_block(*node.get_variable_block(), false, false); - print_statement_block(*node.get_initialize_block(), false, false); - print_statement_block(*node.get_setup_x_block(), false, false); - - printer->add_newline(); - print_eigen_linear_solver(float_type, N); - printer->add_newline(); - - print_statement_block(*node.get_update_states_block(), false, false); - print_statement_block(*node.get_finalize_block(), false, false); -} - - void CodegenCoreneuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { // For_netcon should take the same arguments as net_receive and apply the operations // in the block to the weights of the netcons. Since all the weights are on the same vector, diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index f6d4b7ba39..1433b0c6a2 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -392,22 +392,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_function_tables(const ast::FunctionTableBlock& node); - bool is_functor_const(const ast::StatementBlock& variable_block, - const ast::StatementBlock& functor_block); - - - /** - * \brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its - * functor - * - * \param node \c EigenNewtonSolverBlock for which to print the functor - */ - void print_functor_definition(const ast::EigenNewtonSolverBlock& node); - - - virtual void print_eigen_linear_solver(const std::string& float_type, int N); - - /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ @@ -684,7 +668,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /** * Print the ion variable struct */ - virtual void print_ion_variable(); + void print_ion_variable() override; /** @@ -755,15 +739,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_nrn_destructor() override; - /** - * Go through the map of \c EigenNewtonSolverBlock s and their corresponding functor names - * and print the functor definitions before the definitions of the functions of the generated - * file - * - */ - void print_functors_definitions(); - - /** * Print nrn_alloc function definition * @@ -1048,12 +1023,10 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; - void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; - void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; void visit_watch_statement(const ast::WatchStatement& node) override; - + ParamVector functor_params() override; public: /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 832be834c4..711fff48b8 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -13,7 +13,9 @@ #include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_utils.hpp" +#include "visitors/defuse_analyze_visitor.hpp" #include "visitors/rename_visitor.hpp" +#include "visitors/symtab_visitor.hpp" #include "visitors/visitor_utils.hpp" namespace nmodl { @@ -21,7 +23,10 @@ namespace codegen { using namespace ast; +using visitor::DefUseAnalyzeVisitor; +using visitor::DUState; using visitor::RenameVisitor; +using visitor::SymtabVisitor; using symtab::syminfo::NmodlType; @@ -617,6 +622,152 @@ void CodegenCppVisitor::rename_function_arguments() { } +bool CodegenCppVisitor::is_functor_const(const ast::StatementBlock& variable_block, + const ast::StatementBlock& functor_block) { + // Create complete_block with both variable declarations (done in variable_block) and solver + // part (done in functor_block) to be able to run the SymtabVisitor and DefUseAnalyzeVisitor + // then and get the proper DUChains for the variables defined in the variable_block + ast::StatementBlock complete_block(functor_block); + // Typically variable_block has only one statement, a statement containing the declaration + // of the local variables + for (const auto& statement: variable_block.get_statements()) { + complete_block.insert_statement(complete_block.get_statements().begin(), statement); + } + + // Create Symbol Table for complete_block + auto model_symbol_table = std::make_shared(); + SymtabVisitor(model_symbol_table.get()).visit_statement_block(complete_block); + // Initialize DefUseAnalyzeVisitor to generate the DUChains for the variables defined in the + // variable_block + DefUseAnalyzeVisitor v(*complete_block.get_symbol_table()); + + // Check the DUChains for all the variables in the variable_block + // If variable is defined in complete_block don't add const quilifier in operator() + auto is_functor_const = true; + const auto& variables = collect_nodes(variable_block, {ast::AstNodeType::LOCAL_VAR}); + for (const auto& variable: variables) { + const auto& chain = v.analyze(complete_block, variable->get_node_name()); + is_functor_const = !(chain.eval() == DUState::D || chain.eval() == DUState::LD || + chain.eval() == DUState::CD); + if (!is_functor_const) { + break; + } + } + + return is_functor_const; +} + + +void CodegenCppVisitor::print_functors_definitions() { + for (const auto& functor_name: info.functor_names) { + printer->add_newline(2); + print_functor_definition(*functor_name.first); + } +} + +void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlock& node) { + // functor that evaluates F(X) and J(X) for + // Newton solver + auto float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + + const auto functor_name = info.functor_names[&node]; + printer->fmt_push_block("struct {}", functor_name); + + auto params = functor_params(); + for (const auto& param: params) { + printer->fmt_line("{}{} {};", std::get<0>(param), std::get<1>(param), std::get<3>(param)); + } + + if (ion_variable_struct_required()) { + print_ion_variable(); + } + + print_statement_block(*node.get_variable_block(), false, false); + printer->add_newline(); + + printer->push_block("void initialize()"); + print_statement_block(*node.get_initialize_block(), false, false); + printer->pop_block(); + printer->add_newline(); + + printer->fmt_line("{}({})", functor_name, get_parameter_str(params)); + printer->increase_indent(); + auto initializers = std::vector(); + for (const auto& param: params) { + initializers.push_back(fmt::format("{0}({0})", std::get<3>(param))); + } + + printer->add_multi_line(": " + fmt::format("{}", fmt::join(initializers, ", "))); + printer->decrease_indent(); + printer->add_line("{}"); + + printer->add_indent(); + + const auto& variable_block = *node.get_variable_block(); + const auto& functor_block = *node.get_functor_block(); + + printer->fmt_text( + "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " + "1>& nmodl_eigen_fm, " + "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", + float_type, + N, + is_functor_const(variable_block, functor_block) ? "const " : ""); + printer->push_block(); + printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); + print_statement_block(functor_block, false, false); + printer->pop_block(); + printer->add_newline(); + + // assign newton solver results in matrix X to state vars + printer->push_block("void finalize()"); + print_statement_block(*node.get_finalize_block(), false, false); + printer->pop_block(); + + printer->pop_block(";"); +} + + +void CodegenCppVisitor::print_eigen_linear_solver(const std::string& float_type, int N) { + if (N <= 4) { + // Faster compared to LU, given the template specialization in Eigen. + printer->add_multi_line(R"CODE( + bool invertible; + nmodl_eigen_jm.computeInverseWithCheck(nmodl_eigen_jm_inv,invertible); + nmodl_eigen_xm = nmodl_eigen_jm_inv*nmodl_eigen_fm; + if (!invertible) assert(false && "Singular or ill-conditioned matrix (Eigen::inverse)!"); + )CODE"); + } else { + // In Eigen the default storage order is ColMajor. + // Crout's implementation requires matrices stored in RowMajor order (C++-style arrays). + // Therefore, the transposeInPlace is critical such that the data() method to give the rows + // instead of the columns. + printer->add_line("if (!nmodl_eigen_jm.IsRowMajor) nmodl_eigen_jm.transposeInPlace();"); + + // pivot vector + printer->fmt_line("Eigen::Matrix pivot;", N); + printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> rowmax;", float_type, N); + + // In-place LU-Decomposition (Crout Algo) : Jm is replaced by its LU-decomposition + printer->fmt_line( + "if (nmodl::crout::Crout<{0}>({1}, nmodl_eigen_jm.data(), pivot.data(), rowmax.data()) " + "< 0) assert(false && \"Singular or ill-conditioned matrix (nmodl::crout)!\");", + float_type, + N); + + // Solve the linear system : Forward/Backward substitution part + printer->fmt_line( + "nmodl::crout::solveCrout<{0}>({1}, nmodl_eigen_jm.data(), nmodl_eigen_fm.data(), " + "nmodl_eigen_xm.data(), pivot.data());", + float_type, + N); + } +} + + /****************************************************************************************/ /* Main code printing entry points */ /****************************************************************************************/ @@ -885,6 +1036,60 @@ void CodegenCppVisitor::visit_solution_expression(const SolutionExpression& node } +void CodegenCppVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) { + // solution vector to store copy of state vars for Newton solver + printer->add_newline(); + + auto float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + printer->fmt_line("Eigen::Matrix<{}, {}, 1> nmodl_eigen_xm;", float_type, N); + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + + print_statement_block(*node.get_setup_x_block(), false, false); + + // call newton solver with functor and X matrix that contains state vars + printer->add_line("// call newton solver"); + printer->fmt_line("{} newton_functor({});", + info.functor_names[&node], + get_arg_str(functor_params())); + printer->add_line("newton_functor.initialize();"); + printer->add_line( + "int newton_iterations = nmodl::newton::newton_solver(nmodl_eigen_xm, newton_functor);"); + printer->add_line( + "if (newton_iterations < 0) assert(false && \"Newton solver did not converge!\");"); + + // assign newton solver results in matrix X to state vars + print_statement_block(*node.get_update_states_block(), false, false); + printer->add_line("newton_functor.finalize();"); +} + + +void CodegenCppVisitor::visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) { + printer->add_newline(); + + const std::string float_type = default_float_data_type(); + int N = node.get_n_state_vars()->get_value(); + printer->fmt_line("Eigen::Matrix<{0}, {1}, 1> nmodl_eigen_xm, nmodl_eigen_fm;", float_type, N); + printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm;", float_type, N); + if (N <= 4) { + printer->fmt_line("Eigen::Matrix<{0}, {1}, {1}> nmodl_eigen_jm_inv;", float_type, N); + } + printer->fmt_line("{}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); + print_statement_block(*node.get_variable_block(), false, false); + print_statement_block(*node.get_initialize_block(), false, false); + print_statement_block(*node.get_setup_x_block(), false, false); + + printer->add_newline(); + print_eigen_linear_solver(float_type, N); + printer->add_newline(); + + print_statement_block(*node.get_update_states_block(), false, false); + print_statement_block(*node.get_finalize_block(), false, false); +} + + /** * \details Once variables are populated, update index semantics to register with coreneuron */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 33cf0b9015..45596655fd 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -855,6 +855,38 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ void rename_function_arguments(); + /** + * @brief Checks whether the functor_block generated by sympy solver modifies any variable + * outside its scope. If it does then return false, so that the operator() of the struct functor + * of the Eigen Newton solver doesn't have const qualifier. + * + * @param variable_block Statement Block of the variables declarations used in the functor + * struct of the solver + * @param functor_block Actual code being printed in the operator() of the functor struct of the + * solver + * @return True if operator() is const else False + */ + bool is_functor_const(const ast::StatementBlock& variable_block, + const ast::StatementBlock& functor_block); + + + /** The parameters of the Newton solver "functor". */ + virtual ParamVector functor_params() = 0; + + /** + * \brief Based on the \c EigenNewtonSolverBlock passed print the definition needed for its + * functor + * + * \param node \c EigenNewtonSolverBlock for which to print the functor + */ + void print_functor_definition(const ast::EigenNewtonSolverBlock& node); + + /** \brief Print all Newton functor structs. */ + void print_functors_definitions(); + + /** Print linear solver using Eigen. + */ + void print_eigen_linear_solver(const std::string& float_type, int N); /** * Print the items in a vector as a list @@ -873,6 +905,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { const std::string& separator, const std::string& prefix = ""); + virtual void print_ion_variable() = 0; /****************************************************************************************/ /* Code-specific helper routines */ @@ -1353,7 +1386,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /****************************************************************************************/ /* Overloaded visitor routines */ /****************************************************************************************/ - void visit_binary_expression(const ast::BinaryExpression& node) override; void visit_binary_operator(const ast::BinaryOperator& node) override; void visit_boolean(const ast::Boolean& node) override; @@ -1382,6 +1414,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_mutex_lock(const ast::MutexLock& node) override; void visit_mutex_unlock(const ast::MutexUnlock& node) override; void visit_solution_expression(const ast::SolutionExpression& node) override; + void visit_eigen_newton_solver_block(const ast::EigenNewtonSolverBlock& node) override; + void visit_eigen_linear_solver_block(const ast::EigenLinearSolverBlock& node) override; std::string compute_method_name(BlockType type) const; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index ba9da2372c..d649eb481d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -12,8 +12,10 @@ #include #include #include +#include #include "ast/all.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_utils.hpp" #include "codegen_naming.hpp" #include "config/config.h" @@ -747,7 +749,11 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, void CodegenNeuronCppVisitor::print_standard_includes() { printer->add_newline(); printer->add_multi_line(R"CODE( + #include + #include + #include #include + #include #include #include )CODE"); @@ -809,6 +815,13 @@ void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_ini printer->pop_block(); } +CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::functor_params() { + return ParamVector{{"", "NrnThread*", "", "_nt"}, + {"", fmt::format("{}&", instance_struct()), "", "inst"}, + {"", "int", "", "id"}, + {"", "double", "", "v"}, + {"", "Datum*", "", "_thread"}}; +} void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { const auto value_initialize = print_initializers ? "{}" : ""; @@ -1640,7 +1653,6 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { /* Print nrn_state routine */ /****************************************************************************************/ - /// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_state() { if (!nrn_state_required()) { @@ -2041,6 +2053,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_data_structures(true); print_nrn_alloc(); print_function_prototypes(); + print_functors_definitions(); print_global_variables_for_hoc(); print_thread_memory_callbacks(); print_compute_functions(); // only nrn_cur and nrn_state @@ -2049,6 +2062,11 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_namespace_end(); } +void CodegenNeuronCppVisitor::print_ion_variable() { + throw std::runtime_error("Not implemented."); +} + + void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) { auto const& arguments = node.get_arguments(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 32ed78e626..4136db3b2c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -688,6 +688,9 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_codegen_routines() override; + void print_ion_variable() override; + + /****************************************************************************************/ /* Overloaded visitor routines */ /****************************************************************************************/ @@ -702,7 +705,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ /* Public printing routines for code generation for use in unit tests */ /****************************************************************************************/ - + ParamVector functor_params() override; /** * Print the structure that wraps all range and int variables required for the NMODL diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 4aa4335a1e..0db6156b2c 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -2349,7 +2349,8 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri R"(struct functor_cacum_0 { NrnThread* nt; cacum_Instance* inst; - int id, pnodecount; + int id; + int pnodecount; double v; const Datum* indexes; double* data; @@ -2360,18 +2361,23 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri old_cai = inst->cai[id]; } - functor_cacum_0(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) : nt{nt}, inst{inst}, id{id}, pnodecount{pnodecount}, v{v}, indexes{indexes}, data{data}, thread{thread} {} + functor_cacum_0(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) + : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes), data(data), thread(thread) + {} void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { const double* nmodl_eigen_x = nmodl_eigen_xm.data(); double* nmodl_eigen_j = nmodl_eigen_jm.data(); double* nmodl_eigen_f = nmodl_eigen_fm.data(); nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); nmodl_eigen_j[static_cast(0)] =)"; + + std::string expected_functor_cacum_1_definition = R"(struct functor_cacum_1 { NrnThread* nt; cacum_Instance* inst; - int id, pnodecount; + int id; + int pnodecount; double v; const Datum* indexes; double* data; @@ -2382,18 +2388,23 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri old_cai = inst->cai[id]; } - functor_cacum_1(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) : nt{nt}, inst{inst}, id{id}, pnodecount{pnodecount}, v{v}, indexes{indexes}, data{data}, thread{thread} {} + functor_cacum_1(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) + : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes), data(data), thread(thread) + {} void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { const double* nmodl_eigen_x = nmodl_eigen_xm.data(); double* nmodl_eigen_j = nmodl_eigen_jm.data(); double* nmodl_eigen_f = nmodl_eigen_fm.data(); nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); nmodl_eigen_j[static_cast(0)] =)"; + + std::string expected_functor_cacum_2_definition = R"(struct functor_cacum_2 { NrnThread* nt; cacum_Instance* inst; - int id, pnodecount; + int id; + int pnodecount; double v; const Datum* indexes; double* data; @@ -2404,7 +2415,9 @@ SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][deri old_cai = inst->cai[id]; } - functor_cacum_2(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) : nt{nt}, inst{inst}, id{id}, pnodecount{pnodecount}, v{v}, indexes{indexes}, data{data}, thread{thread} {} + functor_cacum_2(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) + : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes), data(data), thread(thread) + {} void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { const double* nmodl_eigen_x = nmodl_eigen_xm.data(); double* nmodl_eigen_j = nmodl_eigen_jm.data(); diff --git a/test/nmodl/transpiler/usecases/kinetic/X2Y.mod b/test/nmodl/transpiler/usecases/kinetic/X2Y.mod new file mode 100644 index 0000000000..a3db62985e --- /dev/null +++ b/test/nmodl/transpiler/usecases/kinetic/X2Y.mod @@ -0,0 +1,30 @@ +NEURON { + SUFFIX pump + RANGE X, Y + NONSPECIFIC_CURRENT il +} + +STATE { + X + Y +} + +ASSIGNED { + il + i +} + +BREAKPOINT { + SOLVE state METHOD sparse + il = i +} + +INITIAL { + X = 0 + Y = 1 +} + +KINETIC state { + ~ X <-> Y (0.4, 0.5) + i = (f_flux-b_flux) +} diff --git a/test/nmodl/transpiler/usecases/kinetic/simulate.py b/test/nmodl/transpiler/usecases/kinetic/simulate.py new file mode 100644 index 0000000000..3d1c9dfd36 --- /dev/null +++ b/test/nmodl/transpiler/usecases/kinetic/simulate.py @@ -0,0 +1,37 @@ +import numpy as np +import scipy + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 + +s = h.Section() +s.insert("X2Y") +s.nseg = nseg + +t_hoc = h.Vector().record(h._ref_t) +X_hoc = h.Vector().record(s(0.5)._ref_X_X2Y) +Y_hoc = h.Vector().record(s(0.5)._ref_Y_X2Y) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +t = np.array(t_hoc.as_numpy()) +X = np.array(X_hoc.as_numpy()) +Y = np.array(Y_hoc.as_numpy()) +Z0 = np.array([0, 1]) +Z = np.array(list(zip(X, Y))) + +A = np.array([[-0.4, 0.5], [0.4, -0.5]]) + + +def f(Z, t): + return A @ Z + + +Z_exact = scipy.integrate.odeint(f, Z0, t) + +abs_err = np.abs(Z - Z_exact) +assert np.all(abs_err < 0.005), abs_err From 0b3d6d5293bfdbd9b36687b650890227023ec276 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 12 Jun 2024 13:10:30 +0200 Subject: [PATCH 671/871] Fix integration tests. (BlueBrain/nmodl#1313) There must not be a clash of FUNCTION and RANGE names. NMODL Repo SHA: BlueBrain/nmodl@d226bef887d86d95a0d324464883f9167cecb050 --- test/nmodl/transpiler/integration/mod/glia_sparse.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/test/nmodl/transpiler/integration/mod/glia_sparse.mod b/test/nmodl/transpiler/integration/mod/glia_sparse.mod index ebc58072e7..ab325573d5 100644 --- a/test/nmodl/transpiler/integration/mod/glia_sparse.mod +++ b/test/nmodl/transpiler/integration/mod/glia_sparse.mod @@ -2,7 +2,6 @@ NEURON { SUFFIX glia - RANGE alfa, beta RANGE Aalfa, Valfa, Abeta, Vbeta } From 06e84d3f742f3ec0a97416bf621ad9bbaa2e9ece Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 12 Jun 2024 14:49:58 +0200 Subject: [PATCH 672/871] Fix typo in pybind CMakeLists (BlueBrain/nmodl#1317) NMODL Repo SHA: BlueBrain/nmodl@fd2cd161ca6dc90fc55129db213105b3554be8a0 --- src/nmodl/pybind/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 9439ea4f32..ecc9447c0a 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -25,7 +25,7 @@ set_property( APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ode_py.hpp.inc ${CMAKE_CURRENT_BINARY_DIR}/ode_py.hpp - @ONLY@) + @ONLY) add_library(pyembed pyembed.cpp) set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) From a32b6a92dd0493ecf4d08a27cc685431491a34a5 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 13 Jun 2024 08:40:22 +0200 Subject: [PATCH 673/871] Register `kinetic`. (BlueBrain/nmodl#1319) NMODL Repo SHA: BlueBrain/nmodl@e5964fe818816b0023a49304d2954bb35362b68f --- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + test/nmodl/transpiler/usecases/kinetic/X2Y.mod | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 31f363e6d7..a9293fbd41 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -4,6 +4,7 @@ set(NMODL_USECASE_DIRS procedure global hodgkin_huxley + kinetic nonspecific_current neuron_variables net_event diff --git a/test/nmodl/transpiler/usecases/kinetic/X2Y.mod b/test/nmodl/transpiler/usecases/kinetic/X2Y.mod index a3db62985e..5ef3f853ac 100644 --- a/test/nmodl/transpiler/usecases/kinetic/X2Y.mod +++ b/test/nmodl/transpiler/usecases/kinetic/X2Y.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX pump + SUFFIX X2Y RANGE X, Y NONSPECIFIC_CURRENT il } From a9b184ede4b313ac3a1d9fc8427840266741ea5e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 13 Jun 2024 08:41:20 +0200 Subject: [PATCH 674/871] Detailed blame. (BlueBrain/nmodl#1320) NMODL Repo SHA: BlueBrain/nmodl@64cf627ea1482e447637833108ab3b400b8bdd11 --- src/nmodl/codegen/codegen_cpp_visitor.hpp | 10 +++--- src/nmodl/main.cpp | 27 ++++++++++---- src/nmodl/printer/code_printer.cpp | 10 +++--- src/nmodl/printer/code_printer.hpp | 14 ++++---- src/nmodl/utils/blame.cpp | 44 +++++++++++++++++++---- src/nmodl/utils/blame.hpp | 6 +++- 6 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 45596655fd..cc7b0275e1 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -215,9 +215,9 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { const std::string& output_dir, std::string float_type, const bool optimize_ionvar_copies, - size_t blame_line = 0) - : printer( - std::make_unique(output_dir + "/" + mod_filename + ".cpp", blame_line)) + std::unique_ptr blame) + : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp", + std::move(blame))) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} @@ -243,8 +243,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::ostream& stream, std::string float_type, const bool optimize_ionvar_copies, - size_t blame_line = 0) - : printer(std::make_unique(stream, blame_line)) + std::unique_ptr blame = nullptr) + : printer(std::make_unique(stream, std::move(blame))) , mod_filename(std::move(mod_filename)) , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index f90c7d5879..be322c0e99 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -160,7 +160,8 @@ int main(int argc, const char* argv[]) { std::string data_type("double"); /// which line to run blame for - size_t blame_line = 0; + size_t blame_line = 0; // lines are 1-based. + bool detailed_blame = false; // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) app.get_formatter()->column_width(40); @@ -281,6 +282,7 @@ int main(int argc, const char* argv[]) { #if NMODL_ENABLE_BACKWARD auto blame_opt = app.add_subcommand("blame", "Blame NMODL code that generated some code."); blame_opt->add_option("--line", blame_line, "Justify why this line was generated."); + blame_opt->add_flag("--detailed", detailed_blame, "Justify by printing full backtraces."); #endif // clang-format on @@ -555,24 +557,35 @@ int main(int argc, const char* argv[]) { } { + auto blame_level = detailed_blame ? utils::BlameLevel::Detailed + : utils::BlameLevel::Short; if (coreneuron_code && oacc_backend) { logger->info("Running OpenACC backend code generator for CoreNEURON"); - CodegenAccVisitor visitor( - modfile, output_dir, data_type, optimize_ionvar_copies_codegen, blame_line); + CodegenAccVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen, + utils::make_blame(blame_line, blame_level)); visitor.visit_program(*ast); } else if (coreneuron_code && !neuron_code && cpp_backend) { logger->info("Running C++ backend code generator for CoreNEURON"); - CodegenCoreneuronCppVisitor visitor( - modfile, output_dir, data_type, optimize_ionvar_copies_codegen, blame_line); + CodegenCoreneuronCppVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen, + utils::make_blame(blame_line, blame_level)); visitor.visit_program(*ast); } else if (neuron_code && cpp_backend) { logger->info("Running C++ backend code generator for NEURON"); - CodegenNeuronCppVisitor visitor( - modfile, output_dir, data_type, optimize_ionvar_copies_codegen, blame_line); + CodegenNeuronCppVisitor visitor(modfile, + output_dir, + data_type, + optimize_ionvar_copies_codegen, + utils::make_blame(blame_line, blame_level)); visitor.visit_program(*ast); } diff --git a/src/nmodl/printer/code_printer.cpp b/src/nmodl/printer/code_printer.cpp index 6a369139ca..b14c4c6c5b 100644 --- a/src/nmodl/printer/code_printer.cpp +++ b/src/nmodl/printer/code_printer.cpp @@ -8,12 +8,13 @@ #include "printer/code_printer.hpp" #include "utils/blame.hpp" #include "utils/string_utils.hpp" +#include namespace nmodl { namespace printer { -CodePrinter::CodePrinter(const std::string& filename, size_t blame_line) - : blame_line(blame_line) { +CodePrinter::CodePrinter(const std::string& filename, std::unique_ptr blame) + : blame_printer(std::move(blame)) { if (filename.empty()) { throw std::runtime_error("Empty filename for CodePrinter"); } @@ -112,8 +113,9 @@ void CodePrinter::pop_block(const std::string_view& suffix, std::size_t num_newl } void CodePrinter::blame() { - auto blame_printer = utils::make_blame(blame_line); - (*blame_printer)(std::cout, current_line); + if (blame_printer != nullptr) { + (*blame_printer)(std::cout, current_line); + } } diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index 2867d95b78..d7df67a3b0 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -22,6 +22,8 @@ #include #include +#include "utils/blame.hpp" + namespace nmodl { /// implementation of various printers namespace printer { @@ -45,20 +47,20 @@ class CodePrinter { std::streambuf* sbuf = nullptr; std::unique_ptr result; size_t current_line = 1; - size_t blame_line = 0; + std::unique_ptr blame_printer; size_t indent_level = 0; const size_t NUM_SPACES = 4; public: - CodePrinter(size_t blame_line = 0) + CodePrinter(std::unique_ptr blame) : result(std::make_unique(std::cout.rdbuf())) - , blame_line(blame_line) {} + , blame_printer(std::move(blame)) {} - CodePrinter(std::ostream& stream, size_t blame_line = 0) + CodePrinter(std::ostream& stream, std::unique_ptr blame) : result(std::make_unique(stream.rdbuf())) - , blame_line(blame_line) {} + , blame_printer(std::move(blame)) {} - CodePrinter(const std::string& filename, size_t blame_line = 0); + CodePrinter(const std::string& filename, std::unique_ptr blame); ~CodePrinter() { ofs.close(); diff --git a/src/nmodl/utils/blame.cpp b/src/nmodl/utils/blame.cpp index 7490dfd835..ea0fcffd0a 100644 --- a/src/nmodl/utils/blame.cpp +++ b/src/nmodl/utils/blame.cpp @@ -3,6 +3,8 @@ #include "string_utils.hpp" #include #include +#include +#include #if NMODL_ENABLE_BACKWARD #include @@ -86,21 +88,51 @@ class ShortBlame: public Blame { size_t trace_id = first_relevant_trace(tr, st); backward::ResolvedTrace trace = tr.resolve(st[trace_id]); - std::string trace_label = ""; return trace_printer.format(trace, ""); } BackwardTracePrinter trace_printer; }; -#else -class ShortBlame: public NoBlame { + + +class DetailedBlame: public Blame { public: - using NoBlame::NoBlame; + using Blame::Blame; + + protected: + std::string format() const override { + backward::StackTrace st; + st.load_here(32); + + backward::TraceResolver tr; + + size_t first_trace_id = first_relevant_trace(tr, st); + + std::string full_trace = "\n\n --- Backtrace ----------------\n"; + for (int i = st.size() - 1; i >= first_trace_id; --i) { + backward::ResolvedTrace trace = tr.resolve(st[i]); + full_trace += trace_printer.format(trace, std::to_string(i) + ":"); + } + return full_trace; + } + + BackwardTracePrinter trace_printer; }; + + +#else +using ShortBlame = NoBlame; +using DetailedBlame = NoBlame; #endif -std::unique_ptr make_blame(size_t blame_line) { - return std::make_unique(blame_line); +std::unique_ptr make_blame(size_t blame_line, BlameLevel blame_level) { + if (blame_level == BlameLevel::Short) { + return std::make_unique(blame_line); + } else if (blame_level == BlameLevel::Detailed) { + return std::make_unique(blame_line); + } else { + throw std::runtime_error("Unknown blame_level."); + } } } // namespace utils diff --git a/src/nmodl/utils/blame.hpp b/src/nmodl/utils/blame.hpp index 7c5d26f0c2..114e69e2f1 100644 --- a/src/nmodl/utils/blame.hpp +++ b/src/nmodl/utils/blame.hpp @@ -1,3 +1,5 @@ +#pragma once + #include #include @@ -23,7 +25,9 @@ class Blame { size_t blame_line = 0; }; -std::unique_ptr make_blame(size_t blame_line); +enum class BlameLevel { Short, Detailed }; + +std::unique_ptr make_blame(size_t blame_line, BlameLevel = BlameLevel::Short); } // namespace utils } // namespace nmodl From 900a9cc0131fcc74cee235e316dfb69dda2a91bc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 13 Jun 2024 09:39:23 +0200 Subject: [PATCH 675/871] Fix calls from NET_RECEIVE. (BlueBrain/nmodl#1311) NMODL Repo SHA: BlueBrain/nmodl@635b31f0115dd2e2cfd80cd33de88c248a012969 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 4 +++ .../usecases/net_receive/NetReceiveCalls.mod | 27 +++++++++++++++++ .../usecases/net_receive/test_calls.py | 30 +++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/net_receive/NetReceiveCalls.mod create mode 100644 test/nmodl/transpiler/usecases/net_receive/test_calls.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index d649eb481d..dabed7af4e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2161,6 +2161,10 @@ void CodegenNeuronCppVisitor::print_net_receive() { printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + printer->fmt_line("// nocmodl has a nullptr dereference for thread variables."); + printer->fmt_line("// NMODL will fail to compile at a later point, because of"); + printer->fmt_line("// missing '_thread_vars'."); + printer->fmt_line("Datum * _thread = nullptr;"); printer->add_line("size_t id = 0;"); printer->add_line("double t = _nt->_t;"); diff --git a/test/nmodl/transpiler/usecases/net_receive/NetReceiveCalls.mod b/test/nmodl/transpiler/usecases/net_receive/NetReceiveCalls.mod new file mode 100644 index 0000000000..69fabe6f46 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/NetReceiveCalls.mod @@ -0,0 +1,27 @@ +NEURON { + POINT_PROCESS NetReceiveCalls + RANGE c1, c2 +} + +ASSIGNED { + c1 + c2 +} + +INITIAL { + c1 = 0 + c2 = 0 +} + +FUNCTION one() { + one = 1 +} + +PROCEDURE increment_c2() { + c2 = c2 + 2 +} + +NET_RECEIVE(w) { + c1 = c1 + one() + increment_c2() +} diff --git a/test/nmodl/transpiler/usecases/net_receive/test_calls.py b/test/nmodl/transpiler/usecases/net_receive/test_calls.py new file mode 100644 index 0000000000..4fc776bef1 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/test_calls.py @@ -0,0 +1,30 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + + +def test_calls(): + nseg = 1 + + soma = h.Section() + soma.nseg = nseg + + syn = h.NetReceiveCalls(soma(0.5)) + + stim = h.NetStim() + stim.interval = 0.2 + stim.number = 3 + stim.start = 0.1 + + netcon = h.NetCon(stim, syn) + + h.stdinit() + h.run() + + assert syn.c1 == 3 + assert syn.c2 == 6 + + +if __name__ == "__main__": + test_calls() From 31819bab8522d8556da683bbf4eb39f630acb64c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 13 Jun 2024 09:48:18 +0200 Subject: [PATCH 676/871] Implement top-level LOCAL variables. (BlueBrain/nmodl#1315) These are global variables, and when THREAD_SAFE act like thread variables, without being exposed to HOC/Python. NMODL Repo SHA: BlueBrain/nmodl@e9e127aff50146213679c8418428a183c98f26a7 --- .../codegen_coreneuron_cpp_visitor.cpp | 2 - .../codegen/codegen_neuron_cpp_visitor.cpp | 27 ++++++++--- .../usecases/global/test_top_local.py | 47 +++++++++++++++++++ .../transpiler/usecases/global/top_local.mod | 25 ++++++++++ 4 files changed, 92 insertions(+), 9 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/global/test_top_local.py create mode 100644 test/nmodl/transpiler/usecases/global/top_local.mod diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index a0b3a79b28..eeaf247840 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -850,7 +850,6 @@ std::string CodegenCoreneuronCppVisitor::float_variable_name(const SymbolType& s auto name = symbol->get_name(); auto dimension = symbol->get_length(); auto position = position_of_float_var(name); - // clang-format off if (symbol->is_array()) { if (use_instance) { return fmt::format("(inst->{}+id*{})", name, dimension); @@ -861,7 +860,6 @@ std::string CodegenCoreneuronCppVisitor::float_variable_name(const SymbolType& s return fmt::format("inst->{}[id]", name); } return fmt::format("data[{}*pnodecount + id]", position); - // clang-format on } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index dabed7af4e..137202a780 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -872,11 +872,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } if (!info.thread_variables.empty()) { - printer->fmt_line("int thread_data_in_use{};", value_initialize); - printer->fmt_line("{} thread_data[{}] /* TODO init thread_data */;", - float_type, - info.thread_var_data_size); - size_t prefix_sum = 0; for (size_t i = 0; i < info.thread_variables.size(); ++i) { const auto& var = info.thread_variables[i]; @@ -884,10 +879,28 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in prefix_sum += var->get_length(); } + } + + if (!info.top_local_variables.empty()) { + size_t prefix_sum = info.thread_var_data_size; + size_t n_thread_vars = codegen_thread_variables.size(); + for (size_t i = 0; i < info.top_local_variables.size(); ++i) { + const auto& var = info.top_local_variables[i]; + codegen_thread_variables.push_back({var, n_thread_vars + i, prefix_sum}); + + prefix_sum += var->get_length(); + } + } + + if (!codegen_thread_variables.empty()) { + auto thread_data_size = info.thread_var_data_size + info.top_local_thread_size; + printer->fmt_line("int thread_data_in_use{};", value_initialize); + printer->fmt_line("{} thread_data[{}];", float_type, thread_data_size); codegen_global_variables.push_back(make_symbol("thread_data_in_use")); + auto symbol = make_symbol("thread_data"); - symbol->set_as_array(info.thread_var_data_size); + symbol->set_as_array(thread_data_size); codegen_global_variables.push_back(symbol); } @@ -1253,7 +1266,7 @@ void CodegenNeuronCppVisitor::print_thread_memory_callbacks() { printer->push_block(fmt::format("if({})", inuse)); printer->fmt_line("_thread[{}] = {{neuron::container::do_not_search, new double[{}]{{}}}};", thread_data_index, - info.thread_var_data_size); + info.thread_var_data_size + info.top_local_thread_size); printer->pop_block(); printer->push_block("else"); printer->fmt_line("_thread[{}] = {{neuron::container::do_not_search, {}}};", diff --git a/test/nmodl/transpiler/usecases/global/test_top_local.py b/test/nmodl/transpiler/usecases/global/test_top_local.py new file mode 100644 index 0000000000..57fc75e6e7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/test_top_local.py @@ -0,0 +1,47 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + + +def test_top_local(): + nseg = 1 + + s0 = h.Section() + s0.insert("top_local") + s0.nseg = nseg + + s1 = h.Section() + s1.insert("top_local") + s1.nseg = nseg + + pc = h.ParallelContext() + pc.nthread(2) + pc.partition(0, h.SectionList([s0])) + pc.partition(1, h.SectionList([s1])) + + t = h.Vector().record(h._ref_t) + y0 = h.Vector().record(s0(0.5).top_local._ref_y) + y1 = h.Vector().record(s1(0.5).top_local._ref_y) + + h.stdinit() + h.continuerun(1.0 * ms) + + t = np.array(t.as_numpy()) + y0 = np.array(y0.as_numpy()) + y1 = np.array(y1.as_numpy()) + + i0 = t < 0.33 + i1 = 0.33 <= t + + # The values on thread 0: + assert np.all(y0[i0] == 2.0) + assert np.all(y0[i1] == 3.0) + + # The values on thread 1: + assert np.all(y1[i0] == 2.0) + assert np.all(y1[i1] == 3.0) + + +if __name__ == "__main__": + test_top_local() diff --git a/test/nmodl/transpiler/usecases/global/top_local.mod b/test/nmodl/transpiler/usecases/global/top_local.mod new file mode 100644 index 0000000000..1350246ea7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/top_local.mod @@ -0,0 +1,25 @@ +NEURON { + SUFFIX top_local + NONSPECIFIC_CURRENT il + RANGE y +} + +ASSIGNED { + y + il +} + +LOCAL gbl + +INITIAL { + gbl = 2.0 +} + +BREAKPOINT { + if(t > 0.33) { + gbl = 3.0 + } + + y = gbl + il = 0.0000001 * (v - 10.0) +} From 53b7a6b3d47e0956c6f44b126959e630397f38cb Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 13 Jun 2024 10:45:27 +0200 Subject: [PATCH 677/871] Fix RANGE and FUNCTION/PROCEDURE name conflict (BlueBrain/nmodl#1318) NMODL Repo SHA: BlueBrain/nmodl@a7bf7d97f05c286194bcc7d88d0df7082f7ea5cb --- .../visitors/semantic_analysis_visitor.cpp | 58 +++++++++++++++---- .../visitors/semantic_analysis_visitor.hpp | 3 + .../unit/visitor/semantic_analysis.cpp | 28 +++++++++ 3 files changed, 77 insertions(+), 12 deletions(-) diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 29fc944cb9..6d005e1389 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -13,6 +13,7 @@ #include "ast/independent_block.hpp" #include "ast/procedure_block.hpp" #include "ast/program.hpp" +#include "ast/range_var.hpp" #include "ast/statement_block.hpp" #include "ast/string.hpp" #include "ast/suffix.hpp" @@ -24,19 +25,8 @@ namespace nmodl { namespace visitor { -bool SemanticAnalysisVisitor::check(const ast::Program& node) { - check_fail = false; - program_symtab = node.get_symbol_table(); - - /// <-- This code is for check 2 - const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX}); - if (!suffix_node.empty()) { - const auto& suffix = std::dynamic_pointer_cast(suffix_node[0]); - const auto& type = suffix->get_type()->get_node_name(); - is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL"); - } - /// --> +bool SemanticAnalysisVisitor::check_table_vars(const ast::Program& node) { // check that we do not have any duplicate TABLE statement variables in PROCEDUREs const auto& procedure_nodes = collect_nodes(node, {ast::AstNodeType::PROCEDURE_BLOCK}); std::unordered_set procedure_vars{}; @@ -58,6 +48,50 @@ bool SemanticAnalysisVisitor::check(const ast::Program& node) { } } } + + return check_fail; +} + + +bool SemanticAnalysisVisitor::check_name_conflict(const ast::Program& node) { + // check that there are no RANGE variables which have the same name as a FUNCTION or PROCEDURE + const auto& range_nodes = collect_nodes(node, {ast::AstNodeType::RANGE_VAR}); + std::unordered_set range_vars{}; + for (const auto& range_node: range_nodes) { + range_vars.insert(range_node->get_node_name()); + } + + const auto& function_nodes = + collect_nodes(node, {ast::AstNodeType::FUNCTION_BLOCK, ast::AstNodeType::PROCEDURE_BLOCK}); + std::unordered_set func_vars{}; + for (const auto& function_node: function_nodes) { + func_vars.insert(function_node->get_node_name()); + } + + std::vector result; + std::set_intersection(func_vars.begin(), + func_vars.end(), + range_vars.begin(), + range_vars.end(), + std::back_inserter(result)); + return !result.empty(); +} + +bool SemanticAnalysisVisitor::check(const ast::Program& node) { + check_fail = false; + program_symtab = node.get_symbol_table(); + + /// <-- This code is for check 2 + const auto& suffix_node = collect_nodes(node, {ast::AstNodeType::SUFFIX}); + if (!suffix_node.empty()) { + const auto& suffix = std::dynamic_pointer_cast(suffix_node[0]); + const auto& type = suffix->get_type()->get_node_name(); + is_point_process = (type == "POINT_PROCESS" || type == "ARTIFICIAL_CELL"); + } + /// --> + + check_fail = check_fail || check_table_vars(node) || check_name_conflict(node); + /// <-- This code is for check 4 using namespace symtab::syminfo; const auto& with_prop = NmodlType::read_ion_var | NmodlType::write_ion_var; diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index bcf69f204c..06165614fa 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -96,6 +96,9 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { /// random function first arg must be random_var void visit_function_call(const ast::FunctionCall& node) override; + bool check_name_conflict(const ast::Program& node); + + bool check_table_vars(const ast::Program& node); public: SemanticAnalysisVisitor(bool accel_backend = false) : accel_backend(accel_backend) {} diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 49e5c4992e..6d96fcf1fc 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -275,3 +275,31 @@ SCENARIO("RANDOM Construct", "[visitor][semantic_analysis]") { } } } + +SCENARIO("RANGE and FUNCTION/PROCEDURE block", "[visitor][semantic_analysis]") { + GIVEN("A mod file with same RANGE var name and a FUNCTION name") { + std::string nmodl_text = R"( + NEURON { + RANGE f + } + FUNCTION f(arg) { + f = 1 + } + )"; + THEN("Semantic analysis should fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } + GIVEN("A mod file with same RANGE var name and a PROCEDURE name") { + std::string nmodl_text = R"( + NEURON { + RANGE f + } + PROCEDURE f(arg) { + } + )"; + THEN("Semantic analysis should fail") { + REQUIRE(run_semantic_analysis_visitor(nmodl_text)); + } + } +} From 98925d52d3a808b32c1740827d9da97e56a91f46 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 17 Jun 2024 14:14:14 +0200 Subject: [PATCH 678/871] Add debug logging to parser (BlueBrain/nmodl#1229) NMODL Repo SHA: BlueBrain/nmodl@bf67f44118bdaa539de0b9776e6c6dc42fbe9196 --- src/nmodl/parser/nmodl_driver.cpp | 9 +++++++++ src/nmodl/parser/nmodl_driver.hpp | 16 +++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/nmodl/parser/nmodl_driver.cpp b/src/nmodl/parser/nmodl_driver.cpp index 1a563f37b6..a8f57392b8 100644 --- a/src/nmodl/parser/nmodl_driver.cpp +++ b/src/nmodl/parser/nmodl_driver.cpp @@ -27,9 +27,16 @@ std::shared_ptr NmodlDriver::parse_stream(std::istream& in) { NmodlLexer scanner(*this, &in); NmodlParser parser(scanner, *this); + parser_stream << "parser trace:" << std::endl; + scanner.set_debug(trace_scanner); parser.set_debug_level(trace_parser); + parser.set_debug_stream(parser_stream); + parser.parse(); + + logger->trace(parser_stream.str()); + return astRoot; } @@ -153,6 +160,8 @@ void NmodlDriver::parse_error(const NmodlLexer& scanner, oss << scanner.get_curr_line() << '\n'; oss << std::string(location.begin.column - 1, '-'); oss << "^\n"; + // output the logs if running with `trace` verbosity + logger->trace(parser_stream.str()); throw std::runtime_error(oss.str()); } diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 217879b053..1d0b4b059f 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -69,7 +69,7 @@ class NmodlDriver { bool trace_scanner = false; /// enable debug output in the bison parser - bool trace_parser = false; + bool trace_parser = true; /// print messages from lexer/parser bool verbose = false; @@ -84,6 +84,9 @@ class NmodlDriver { /// \a nullptr is pushed as location for the top NMODL file std::unordered_map open_files; + /// The stream where Bison will dump its logs + std::ostringstream parser_stream; + public: /// file or input stream name (used by scanner for position), see todo std::string stream_name; @@ -139,15 +142,15 @@ class NmodlDriver { * Emit a parsing error * \throw std::runtime_error */ - static void parse_error(const location& location, const std::string& message); + void parse_error(const location& location, const std::string& message); /** * Emit a parsing error. Takes additionally a Lexer instance to print code context * \throw std::runtime_error */ - static void parse_error(const NmodlLexer& scanner, - const location& location, - const std::string& message); + void parse_error(const NmodlLexer& scanner, + const location& location, + const std::string& message); /** * Ensure \a file argument given to the INCLUDE directive is valid: @@ -156,8 +159,7 @@ class NmodlDriver { * * \return unquoted string */ - static std::string check_include_argument(const location& location, - const std::string& filename); + std::string check_include_argument(const location& location, const std::string& filename); }; /** \} */ // end of parser From e32604c16058df478492c5702725af1d13487623 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 17 Jun 2024 14:15:49 +0200 Subject: [PATCH 679/871] Document global variables (BlueBrain/nmodl#1321) NMODL Repo SHA: BlueBrain/nmodl@5ff221d045c92389b4cab5ee159d46770aa12665 --- docs/nmodl/transpiler/contents/globals.rst | 181 +++++++++++++++++++++ docs/nmodl/transpiler/index.rst | 1 + 2 files changed, 182 insertions(+) create mode 100644 docs/nmodl/transpiler/contents/globals.rst diff --git a/docs/nmodl/transpiler/contents/globals.rst b/docs/nmodl/transpiler/contents/globals.rst new file mode 100644 index 0000000000..418b478df1 --- /dev/null +++ b/docs/nmodl/transpiler/contents/globals.rst @@ -0,0 +1,181 @@ +Global Variables +---------------- + +The following global variables exist: + + * ``GLOBAL`` visible from HOC/Python. + * ``LOCAL`` at file scope, called top-locals. + +GLOBAL variables +================ + + +GLOBAL variables behave in one of three different ways: + + * **read-only** if the variable is not written to from inside the MOD file, + is considered read-only. It behaves like there's a single instance that's + shared across all threads. Currently, they're implemented as a static + ``double``, i.e. a regular C/C++ global variable. This MOD file is (still) + thread-safe. + + * **read-write** Setting a value via ``PARAMETER`` is not considered a + write-access. While setting the value from within the MOD file in all other + contexts, including ``INITIAL``, counts as a write-access. + + * **THREAD_SAFE:**, if the MOD file is marked thread-safe, then the + assumption is that it's safe to create multiple instances of the global + variable, e.g. one per thread. We call these *thread-variables*. This MOD + file is considered thread-safe. + + * **Not THREAD_SAFE:** if the MOD file is not stated to be ``THREADSAFE``, + then the assumption is that the global variables must behave like a + single instance would. As a result, these MOD files are not thread-safe. + +The visibility of GLOBAL variables it the following: + + * **ASSIGNED:** any GLOBAL variable that appears in an ASSIGNED block + is not visible from HOC/Python. + + * **PARAMETER:** any GLOBAL variable that appears in a PARAMETER block + is visible (read/write) from HOC/Python. Any PARAMETER that's not + explicitly made a RANGE variable is considered a GLOBAL. + + * **undefined:** any GLOBAL variable that's not listed in either an ASSIGNED + or PARAMETER block is treated as if it were ASSIGNED, i.e. it's not visible + from HOC/Python. + +Top-LOCAL variables +=================== + +Top-LOCAL variables are LOCAL variables at file-scope. They are never visible +from HOC/Python and always treated as top-local variables. + +Since top-locals can't be assigned a value from HOC/Python and assignment in +INITIAL blocks counts as a write-access, the only way of assigning a value to a +read-only top-local would be in the PARAMETER block. However, variables +mentioned in PARAMETER are always RANGE or GLOBAL variables; and it's not +allowed to have two global variables with the same name. Therefore, read-only +top-locals aren't possible. + +Note that ``nocmodl`` promotes all top-local variables to thread-variables, even +if the MOD file isn't marked with ``THREADSAFE``. Hence, top-local variables are +always thread-variables. + +Thread Variables +================ +Thread variables can be safely used as scratch-pad memory. Historically, +they've been advertised as an optimization technique that reduces the memory +footprint. + +The canonical example is ``hh.mod``. The common pattern is that they're used as +return values from a PROCEDURE: + +.. code-block:: + + DERIVATIVE states { + rates(v) + m' = (minf-m)/mtau + } + + PROCEDURE rates(v(mV)) { + TABLE minf, mtau DEPEND celsius FROM -100 TO 100 WITH 200 + + minf = ... + mtau = ... + } + +What we see is that for every instance we compute the value ``minf`` and +``mtau``, before we use them in ``states``. Technically there's no need for one +copy per instance of the mechanism. For example in Python one could write: + +.. code-block:: + + minf, mtau = rates(v[i]) + dm[i] = (minf-m[i])/mtau + +Therefore, if the author doesn't need to record the value of ``minf`` and +``mtau``, then using RANGE variables might be considered wasting memory. Under +these circumstances, and before multi-threading existed, the solution was to use +a GLOBAL. When multi-core processors arrived, these MOD files were suddenly not +thread-safe. The solution was to introduce a keyword ``THREADSAFE`` and create +one copy of the global per thread. + + +Initial Values +~~~~~~~~~~~~~~ +Note that thread-variables ignore the initial value set in the PARAMETER block +entirely. + +For INITIAL blocks the requirement is that: + +.. code-block:: + + INITIAL { + gbl = 2.0 + } + +guarantees that all copies of ``gbl`` are assigned the value ``2.0``. + +HOC/Python Access +~~~~~~~~~~~~~~~~~ + +Note that there's no synchronization when setting or writing to +thread-variables. What happens is that is acts the (or a) value of thread 0. +The value on other threads is either left unchanged when writing or ignored +when reading the global variable. + + +Implementation Details for NEURON +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +NMODL distinguishes between top-local variables and GLOBAL variables at the +level of the AST. Then for code-generation we introduce the concept of +"thread-variables". All top-locals are considered thread-variables. GLOBAL +variables that are **read-write** and THREADSAFE can be converted to thread +variables. + +Since thread-variables are permitted to have multiple copies per thread, we can +generalize this to be multiple copies per thread and SIMD lane; or one copy per +instance of the mechanism for GPUs (effectively a RANGE variable similar to +what CoreNEURON does). + +Registering GLOBAL variables for access from HOC/Python happens via + +.. code-block:: + + static DoubScal hoc_scdoub[] = { + {"g_w_shared_global", &g_w_shared_global}, + {0, 0} + }; + + static DoubVec hoc_vdoub[] = { + {"g_arr_shared_global", g_arr_shared_global, 3}, + {0, 0, 0} + }; + + hoc_register_var(hoc_scdoub, hoc_vdoub, hoc_intfunc); + + +which means for each global we register a stable address (e.g. the address of +some static variable) individually. The elements of ARRAY valued globals must +be stored contiguously. + +The strategy is the following: each instance of the mechanism is associated +with a specific, not necessarily unique, copy of the thread-variable. For SIMD +this allows us to compute the copy of the thread-variable using modulo +arithmetic; on a GPU one could either assign a copy to each variable; or use +scratch pad memory (e.g. ``__shared__`` memory when using CUDA). + +Quirks +~~~~~~ + +Collection of slightly surprising behaviour: + + * Thread variables effectively can't be use in NET_RECEIVE blocks, because + the code ``nocmodl`` produces will cause a SEGFAULT. + + +What Does CoreNEURON support? +============================= +CoreNEURON only supports read-only GLOBAL variables. Anything else needs to be +converted to a RANGE variable manually. diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index b9c49d4586..9c4b0105ee 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -23,6 +23,7 @@ About NMODL contents/ions contents/pointers contents/cable_equations + contents/globals .. toctree:: :maxdepth: 3 From e5b492eb707324adbea6088ef73c00ebad693022 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 20 Jun 2024 12:02:51 +0200 Subject: [PATCH 680/871] Fix (NON)LINEAR block with explicit SOLVEFOR (BlueBrain/nmodl#1326) NMODL Repo SHA: BlueBrain/nmodl@de8e623df60c910359b7bad6dc8557b9cabc706b --- src/nmodl/visitors/sympy_solver_visitor.cpp | 37 +++++++++++++++---- src/nmodl/visitors/sympy_solver_visitor.hpp | 8 ++-- .../transpiler/unit/visitor/sympy_solver.cpp | 22 +++++++++++ 3 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 68dfcff28d..d944f94460 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -52,13 +52,32 @@ void SympySolverVisitor::init_block_data(ast::Node* node) { } } -void SympySolverVisitor::init_state_vars_vector() { +void SympySolverVisitor::init_state_vars_vector(const ast::Node* node) { state_vars.clear(); for (const auto& state_var: all_state_vars) { if (state_vars_in_block.find(state_var) != state_vars_in_block.cend()) { state_vars.push_back(state_var); } } + + // in case we have a SOLVEFOR in the block, we need to set `state_vars` to those instead + if (node->is_linear_block()) { + const auto& solvefor_vars = dynamic_cast(node)->get_solvefor(); + if (!solvefor_vars.empty()) { + state_vars.clear(); + for (const auto& solvefor_var: solvefor_vars) { + state_vars.push_back(solvefor_var->get_node_name()); + } + } + } else if (node->is_non_linear_block()) { + const auto& solvefor_vars = dynamic_cast(node)->get_solvefor(); + if (!solvefor_vars.empty()) { + state_vars.clear(); + for (const auto& solvefor_var: solvefor_vars) { + state_vars.push_back(solvefor_var->get_node_name()); + } + } + } } void SympySolverVisitor::replace_diffeq_expression(ast::DiffEqExpression& expr, @@ -283,9 +302,12 @@ void SympySolverVisitor::construct_eigen_solver_block( } -void SympySolverVisitor::solve_linear_system(const std::vector& pre_solve_statements) { +void SympySolverVisitor::solve_linear_system(const ast::Node& node, + const std::vector& pre_solve_statements + +) { // construct ordered vector of state vars used in linear system - init_state_vars_vector(); + init_state_vars_vector(&node); // call sympy linear solver bool small_system = (eq_system.size() <= SMALL_LINEAR_SYSTEM_MAX_STATES); auto solver = pywrap::EmbeddedPythonLoader::get_instance().api().solve_linear_system; @@ -332,9 +354,10 @@ void SympySolverVisitor::solve_linear_system(const std::vector& pre } void SympySolverVisitor::solve_non_linear_system( + const ast::Node& node, const std::vector& pre_solve_statements) { // construct ordered vector of state vars used in non-linear system - init_state_vars_vector(); + init_state_vars_vector(&node); auto solver = pywrap::EmbeddedPythonLoader::get_instance().api().solve_nonlinear_system; auto [solutions, exception_message] = solver(eq_system, state_vars, vars, function_calls); @@ -535,7 +558,7 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { if (solve_method == codegen::naming::SPARSE_METHOD || solve_method == codegen::naming::DERIVIMPLICIT_METHOD) { - solve_non_linear_system(pre_solve_statements); + solve_non_linear_system(node, pre_solve_statements); } else { logger->error("SympySolverVisitor :: Solve method {} not supported", solve_method); } @@ -566,7 +589,7 @@ void SympySolverVisitor::visit_linear_block(ast::LinearBlock& node) { node.visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { - solve_linear_system(); + solve_linear_system(node); } } @@ -594,7 +617,7 @@ void SympySolverVisitor::visit_non_linear_block(ast::NonLinearBlock& node) { node.visit_children(*this); if (eq_system_is_valid && !eq_system.empty()) { - solve_non_linear_system(); + solve_non_linear_system(node); } } diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index b5a5b13516..ecb326ab63 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -61,7 +61,7 @@ class SympySolverVisitor: public AstVisitor { void init_block_data(ast::Node* node); /// construct vector from set of state vars in correct order - void init_state_vars_vector(); + void init_state_vars_vector(const ast::Node* node); /// replace binary expression with new expression provided as string static void replace_diffeq_expression(ast::DiffEqExpression& expr, const std::string& new_expr); @@ -79,10 +79,12 @@ class SympySolverVisitor: public AstVisitor { bool linear); /// solve linear system (for "LINEAR") - void solve_linear_system(const std::vector& pre_solve_statements = {}); + void solve_linear_system(const ast::Node& node, + const std::vector& pre_solve_statements = {}); /// solve non-linear system (for "derivimplicit", "sparse" and "NONLINEAR") - void solve_non_linear_system(const std::vector& pre_solve_statements = {}); + void solve_non_linear_system(const ast::Node& node, + const std::vector& pre_solve_statements = {}); /// return NMODL string version of node, excluding any units static std::string to_nmodl_for_sympy(ast::Ast& node) { diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 0db6156b2c..3bcdccd9c6 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -2059,6 +2059,28 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } + GIVEN("LINEAR solve block with an explicit SOLVEFOR statement") { + std::string nmodl_text = R"( + STATE { + x + y + z + } + LINEAR lin SOLVEFOR x, y { + ~ 3 * x = v - y + ~ x = z * y - 5 + })"; + std::string expected_text = R"( + LINEAR lin SOLVEFOR x,y{ + y = (v+15.0)/(3.0*z+1.0) + x = (v*z-5.0)/(3.0*z+1.0) + })"; + THEN("solve analytically") { + auto result = + run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); + REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); + } + } } //============================================================================= From 4f13f113d88ba7a8943856ed81fef20d148dcd86 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 20 Jun 2024 13:38:57 +0200 Subject: [PATCH 681/871] check_table_function got a new _globals parameter (BlueBrain/nmodl#1328) NMODL Repo SHA: BlueBrain/nmodl@c8d0833b5a2410bcfdf0d7450b80321361fd3d24 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 137202a780..b82867c850 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -173,6 +173,7 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { {"", "size_t", "", "id"}, {"", "Datum*", "", "_ppvar"}, {"", "Datum*", "", "_thread"}, + {"", "double*", "", "_globals"}, {"", "NrnThread*", "", "_nt"}, {"", "int", "", "_type"}, {"", "const _nrn_model_sorted_token&", "", "_sorted_token"}}; From e0b64f839434a3dae31a1bf3fc2582630b764b88 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 27 Jun 2024 10:09:54 +0200 Subject: [PATCH 682/871] Fix implicit argument handling (BlueBrain/nmodl#1325) NMODL Repo SHA: BlueBrain/nmodl@7beaee9751e233f775a177c819d727abde7e4495 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 46 +++++++++---------- test/nmodl/transpiler/usecases/CMakeLists.txt | 3 +- .../transpiler/usecases/at_time/example.mod | 7 +++ .../transpiler/usecases/at_time/simulate.py | 30 ++++++++++++ 4 files changed, 62 insertions(+), 24 deletions(-) create mode 100755 test/nmodl/transpiler/usecases/at_time/example.mod create mode 100644 test/nmodl/transpiler/usecases/at_time/simulate.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index b82867c850..0e3891f775 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -174,7 +174,7 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { {"", "Datum*", "", "_ppvar"}, {"", "Datum*", "", "_thread"}, {"", "double*", "", "_globals"}, - {"", "NrnThread*", "", "_nt"}, + {"", "NrnThread*", "", "nt"}, {"", "int", "", "_type"}, {"", "const _nrn_model_sorted_token&", "", "_sorted_token"}}; @@ -182,7 +182,7 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { // signature must be same as the `nrn_thread_table_check_t` type printer->fmt_line("static void {}({})", table_thread_function_name(), get_parameter_str(args)); printer->push_block(); - printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml, _type};"); + printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); if (!codegen_thread_variables.empty()) { printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", @@ -350,7 +350,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( double _r{}; Datum* _ppvar; Datum* _thread; - NrnThread* _nt; + NrnThread* nt; )CODE"); if (info.point_process) { printer->add_multi_line(R"CODE( @@ -363,7 +363,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( size_t const id{}; _ppvar = _nrn_mechanism_access_dparam(_p); _thread = _extcall_thread.data(); - _nt = static_cast(_pnt->_vnt); + nt = static_cast(_pnt->_vnt); )CODE"); } else if (wrapper_type == InterpreterWrapper::HOC) { if (program_symtab->lookup(block_name)->has_all_properties(NmodlType::use_range_ptr_var)) { @@ -384,7 +384,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( size_t const id{}; _ppvar = _local_prop ? _nrn_mechanism_access_dparam(_local_prop) : nullptr; _thread = _extcall_thread.data(); - _nt = nrn_threads; + nt = nrn_threads; )CODE"); } else { // wrapper_type == InterpreterWrapper::Python printer->add_multi_line(R"CODE( @@ -392,7 +392,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( size_t const id{}; _ppvar = _nrn_mechanism_access_dparam(_prop); _thread = _extcall_thread.data(); - _nt = nrn_threads; + nt = nrn_threads; )CODE"); } printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); @@ -480,7 +480,7 @@ CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_paramete if (!codegen_thread_variables.empty()) { params.emplace_back("", fmt::format("{}&", thread_variables_struct()), "", "_thread_vars"); } - params.emplace_back("", "NrnThread*", "", "_nt"); + params.emplace_back("", "NrnThread*", "", "nt"); return params; } @@ -714,11 +714,11 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, } if (varname == naming::NTHREAD_DT_VARIABLE) { - return std::string("_nt->_") + naming::NTHREAD_DT_VARIABLE; + return std::string("nt->_") + naming::NTHREAD_DT_VARIABLE; } if (varname == naming::NTHREAD_T_VARIABLE) { - return std::string("_nt->_") + naming::NTHREAD_T_VARIABLE; + return std::string("nt->_") + naming::NTHREAD_T_VARIABLE; } auto const iter = @@ -817,7 +817,7 @@ void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_ini } CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::functor_params() { - return ParamVector{{"", "NrnThread*", "", "_nt"}, + return ParamVector{{"", "NrnThread*", "", "nt"}, {"", fmt::format("{}&", instance_struct()), "", "inst"}, {"", "int", "", "id"}, {"", "double", "", "v"}, @@ -1403,14 +1403,14 @@ void CodegenNeuronCppVisitor::print_node_data_structure(bool print_initializers) void CodegenNeuronCppVisitor::print_make_node_data() const { printer->add_newline(2); - printer->fmt_push_block("static {} make_node_data_{}(NrnThread& _nt, Memb_list& _ml_arg)", + printer->fmt_push_block("static {} make_node_data_{}(NrnThread& nt, Memb_list& _ml_arg)", node_data_struct(), info.mod_suffix); std::vector make_node_data_args = {"_ml_arg.nodeindices", - "_nt.node_voltage_storage()", - "_nt.node_d_storage()", - "_nt.node_rhs_storage()", + "nt.node_voltage_storage()", + "nt.node_d_storage()", + "nt.node_rhs_storage()", "_ml_arg.nodecount"}; printer->fmt_push_block("return {}", node_data_struct()); @@ -1478,14 +1478,14 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, const std::string& function_name) { std::string method = function_name.empty() ? compute_method_name(type) : function_name; ParamVector args = {{"", "const _nrn_model_sorted_token&", "", "_sorted_token"}, - {"", "NrnThread*", "", "_nt"}, + {"", "NrnThread*", "", "nt"}, {"", "Memb_list*", "", "_ml_arg"}, {"", "int", "", "_type"}}; printer->fmt_push_block("void {}({})", method, get_parameter_str(args)); - printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml_arg, _type};"); + printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); - printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); + printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml_arg);", info.mod_suffix); printer->add_line("auto nodecount = _ml_arg->nodecount;"); printer->add_line("auto* _thread = _ml_arg->_thread;"); @@ -1521,7 +1521,7 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_newline(2); ParamVector args = {{"", "const _nrn_model_sorted_token&", "", "_sorted_token"}, - {"", "NrnThread*", "", "_nt"}, + {"", "NrnThread*", "", "nt"}, {"", "Memb_list*", "", "_ml_arg"}, {"", "int", "", "_type"}}; @@ -1531,10 +1531,10 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { printer->add_multi_line( - "_nrn_mechanism_cache_range _lmc{_sorted_token, *_nt, *_ml_arg, _type};"); + "_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); - printer->fmt_line("auto node_data = make_node_data_{}(*_nt, *_ml_arg);", info.mod_suffix); + printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml_arg);", info.mod_suffix); printer->fmt_line("auto nodecount = _ml_arg->nodecount;"); printer->push_block("for (int id = 0; id < nodecount; id++)"); // begin for @@ -1729,7 +1729,7 @@ CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parame } ParamVector params = {{"", "_nrn_mechanism_cache_range&", "", "_lmc"}, - {"", "NrnThread*", "", "_nt"}, + {"", "NrnThread*", "", "nt"}, {"", "Datum*", "", "_ppvar"}, {"", "Datum*", "", "_thread"}}; @@ -2171,7 +2171,7 @@ void CodegenNeuronCppVisitor::print_net_receive() { rename_net_receive_arguments(*node, *node); printer->add_line("_nrn_mechanism_cache_instance _lmc{_pnt->prop};"); - printer->add_line("auto * _nt = static_cast(_pnt->_vnt);"); + printer->add_line("auto * nt = static_cast(_pnt->_vnt);"); printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); @@ -2181,7 +2181,7 @@ void CodegenNeuronCppVisitor::print_net_receive() { printer->fmt_line("Datum * _thread = nullptr;"); printer->add_line("size_t id = 0;"); - printer->add_line("double t = _nt->_t;"); + printer->add_line("double t = nt->_t;"); print_statement_block(*node->get_statement_block(), false, false); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index a9293fbd41..6a3511c5c8 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -15,7 +15,8 @@ set(NMODL_USECASE_DIRS parameter suffix table - useion) + useion + at_time) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/at_time/example.mod b/test/nmodl/transpiler/usecases/at_time/example.mod new file mode 100755 index 0000000000..afe1842f57 --- /dev/null +++ b/test/nmodl/transpiler/usecases/at_time/example.mod @@ -0,0 +1,7 @@ +NEURON { + SUFFIX example +} + +FUNCTION f(x) { + f = at_time(x) +} diff --git a/test/nmodl/transpiler/usecases/at_time/simulate.py b/test/nmodl/transpiler/usecases/at_time/simulate.py new file mode 100644 index 0000000000..f89028e2f0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/at_time/simulate.py @@ -0,0 +1,30 @@ +import numpy as np +from neuron import gui, h +from neuron.units import ms + + +def simulate(): + nseg = 1 + + s = h.Section() + s.insert("example") + s.nseg = nseg + + v_hoc = h.Vector().record(s(0.5)._ref_v) + t_hoc = h.Vector().record(h._ref_t) + + f = s(0.5).example.f + h.stdinit() + h.tstop = 1000 * ms + h.dt = 100 * ms + h.run() + + def at_time_exact(te): + return h.t - h.dt < te - 1e-11 <= h.t + + for t in [-5, -1, -1e-13, 0, 1e-13, 1e-5, 0.5, 1, 2, 50]: + assert np.allclose(f(t), at_time_exact(t)) + + +if __name__ == "__main__": + simulate() From d549a61cbafb74679a42acf712af0090a0f2654e Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 5 Jul 2024 10:59:19 +0200 Subject: [PATCH 683/871] Fix docstring of `print_function_or_procedure` (BlueBrain/nmodl#1330) NMODL Repo SHA: BlueBrain/nmodl@ef3ec39b6c5cd8bff0350f014b179e5e949e2eed --- src/nmodl/codegen/codegen_cpp_visitor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index cc7b0275e1..1e317eee7a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -821,7 +821,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Print nmodl function or procedure (common code) * \param node the AST node representing the function or procedure in NMODL * \param name the name of the function or procedure - * \param hidden whether the function should be declared `static` + * \param specifiers the set of C++ specifiers to apply to the function signature */ virtual void print_function_or_procedure( const ast::Block& node, From d7b7dbe92f412ba8a1a613f1019109d92a0df02c Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Fri, 5 Jul 2024 10:14:29 -0400 Subject: [PATCH 684/871] Include solvers to avoid include path dependency. (BlueBrain/nmodl#1329) Similar to BlueBrain/nmodl#1305, include the solver headers in variables in a C++ header, and include them in the code generated for the MOD files. This should allow to substitute different NMODL binaries in NEURON builds without confusion as to which solver headers are going to be included in the MOD file compilation. NMODL Repo SHA: BlueBrain/nmodl@79101467912f9690b6c40a1d3eed7315e3ea9bc4 --- cmake/nmodl/CMakeLists.txt | 3 +- src/nmodl/CMakeLists.txt | 3 +- .../codegen_coreneuron_cpp_visitor.cpp | 5 ++- .../codegen/codegen_neuron_cpp_visitor.cpp | 4 +- src/nmodl/nmodl.hpp | 20 ---------- src/nmodl/solver/CMakeLists.txt | 37 ++++++++----------- src/nmodl/solver/solver.hpp.inc | 20 ++++++++++ test/nmodl/transpiler/unit/crout/crout.cpp | 2 +- test/nmodl/transpiler/unit/newton/newton.cpp | 2 +- 9 files changed, 44 insertions(+), 52 deletions(-) delete mode 100644 src/nmodl/nmodl.hpp create mode 100644 src/nmodl/solver/solver.hpp.inc diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index c3a127b606..1247d866a5 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -111,8 +111,6 @@ include(cmake/hpc-coding-conventions/cpp/cmake/3rdparty.cmake) if(NOT TARGET CLI11::CLI11) cpp_cc_git_submodule(cli11 BUILD PACKAGE CLI11 REQUIRED) endif() -# For the moment do not try and use an external Eigen. -cpp_cc_git_submodule(eigen) # We could have fmt incoming from NEURON if(NOT TARGET fmt) cpp_cc_git_submodule(fmt BUILD EXCLUDE_FROM_ALL PACKAGE fmt REQUIRED) @@ -209,6 +207,7 @@ set(MEMORYCHECK_COMMAND_OPTIONS --show-possibly-lost=no") # do not enable tests if nmodl is used as submodule if(NOT NMODL_AS_SUBPROJECT AND NMODL_ENABLE_TESTS) + cpp_cc_git_submodule(eigen) cpp_cc_git_submodule(catch2 BUILD PACKAGE Catch2 REQUIRED) if(NMODL_3RDPARTY_USE_CATCH2) # If we're using the submodule then make sure the Catch.cmake helper can be found. In newer diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 55998fd95a..5560b8f77d 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -38,7 +38,7 @@ add_dependencies(nmodl nmodl_copy_python_files nmodl_copy_solver_files) cpp_cc_configure_sanitizers(TARGET nmodl) # ============================================================================= -# Add dependency with nmodl pytnon module (for consumer projects) +# Add dependency with nmodl python module (for consumer projects) # ============================================================================= add_dependencies(nmodl pywrapper) @@ -50,4 +50,3 @@ endif() # Install executable # ============================================================================= install(TARGETS nmodl DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin) -install(FILES nmodl.hpp DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index eeaf247840..571b7c82a0 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -20,6 +20,7 @@ #include "config/config.h" #include "lexer/token_mapping.hpp" #include "parser/c11_driver.hpp" +#include "solver/solver.hpp" #include "utils/logger.hpp" #include "utils/string_utils.hpp" #include "visitors/defuse_analyze_visitor.hpp" @@ -998,7 +999,7 @@ void CodegenCoreneuronCppVisitor::print_coreneuron_includes() { #include )CODE"); if (info.eigen_newton_solver_exist) { - printer->add_line("#include "); + printer->add_multi_line(nmodl::solvers::newton_hpp); } if (info.eigen_linear_solver_exist) { if (std::accumulate(info.state_vars.begin(), @@ -1007,7 +1008,7 @@ void CodegenCoreneuronCppVisitor::print_coreneuron_includes() { [](int l, const SymbolType& variable) { return l += variable->get_length(); }) > 4) { - printer->add_line("#include "); + printer->add_multi_line(nmodl::solvers::crout_hpp); } else { printer->add_line("#include "); printer->add_line("#include "); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 0e3891f775..032fcbce9c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -19,6 +19,7 @@ #include "codegen/codegen_utils.hpp" #include "codegen_naming.hpp" #include "config/config.h" +#include "solver/solver.hpp" #include "utils/string_utils.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" @@ -752,12 +753,11 @@ void CodegenNeuronCppVisitor::print_standard_includes() { printer->add_multi_line(R"CODE( #include #include - #include #include - #include #include #include )CODE"); + printer->add_multi_line(nmodl::solvers::newton_hpp); if (!info.vectorize) { printer->add_line("#include "); } diff --git a/src/nmodl/nmodl.hpp b/src/nmodl/nmodl.hpp deleted file mode 100644 index d51c02e604..0000000000 --- a/src/nmodl/nmodl.hpp +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2023 Blue Brain Project, EPFL. - * See the top-level LICENSE file for details. - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - - -/// no parallelization using openmp -#define EIGEN_DONT_PARALLELIZE - -/// keep host and CUDA code compatible -#ifndef EIGEN_DEFAULT_DENSE_INDEX_TYPE -#define EIGEN_DEFAULT_DENSE_INDEX_TYPE int -#endif - -#include "crout/crout.hpp" -#include "newton/newton.hpp" diff --git a/src/nmodl/solver/CMakeLists.txt b/src/nmodl/solver/CMakeLists.txt index 7d221c4159..cbb40784f2 100644 --- a/src/nmodl/solver/CMakeLists.txt +++ b/src/nmodl/solver/CMakeLists.txt @@ -1,22 +1,15 @@ -# ============================================================================= -# Copy necessary files to build directory -# ============================================================================= - -cpp_cc_build_time_copy(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp" OUTPUT - "${CMAKE_BINARY_DIR}/include/newton/newton.hpp") - -cpp_cc_build_time_copy(INPUT "${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp" OUTPUT - "${CMAKE_BINARY_DIR}/include/crout/crout.hpp") - -add_custom_target( - nmodl_copy_solver_files ALL DEPENDS "${CMAKE_BINARY_DIR}/include/newton/newton.hpp" - "${CMAKE_BINARY_DIR}/include/crout/crout.hpp") - -# Eigen -file(COPY ${NMODL_PROJECT_SOURCE_DIR}/ext/eigen/Eigen DESTINATION ${CMAKE_BINARY_DIR}/include/) - -# ============================================================================= -# Install solver headers and eigen from include -# ============================================================================= - -install(DIRECTORY ${CMAKE_BINARY_DIR}/include/ DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}include) +# Read headers, remove everything up to and including "#pragma once", then remove header includes. +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp NMODL_CROUT_HPP_RAW) +string(REGEX REPLACE ".*#pragma once[ \t\r\n]*" "" NMODL_CROUT_HPP "${NMODL_CROUT_HPP_RAW}") +file(READ ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp NMODL_NEWTON_HPP_RAW) +string(REGEX REPLACE ".*#pragma once[ \t\r\n]*" "" NMODL_NEWTON_HPP_TMP "${NMODL_NEWTON_HPP_RAW}") +string(REGEX REPLACE "#include [ \t\r\n]*" "" NMODL_NEWTON_HPP + "${NMODL_NEWTON_HPP_TMP}") +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/crout/crout.hpp + ${CMAKE_CURRENT_SOURCE_DIR}/newton/newton.hpp) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/solver.hpp.inc ${CMAKE_CURRENT_BINARY_DIR}/solver.hpp) + +add_custom_target(nmodl_copy_solver_files ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/solver.hpp") diff --git a/src/nmodl/solver/solver.hpp.inc b/src/nmodl/solver/solver.hpp.inc new file mode 100644 index 0000000000..a73c8ee40d --- /dev/null +++ b/src/nmodl/solver/solver.hpp.inc @@ -0,0 +1,20 @@ +#include + +// This file is generated from `crout/crout.hpp` and `newton/newton.hpp`. +// +// To avoid a dependency of the `nmodl` binary on a specific header include directory and +// to avoid mod file compilation using wrong headers, we include them like this. +// +// However, because we want to be able to test the headers separately we can't +// move them here. + +namespace nmodl::solvers { +const std::string crout_hpp = R"jiowi( +@NMODL_CROUT_HPP@ +)jiowi"; +const std::string newton_hpp = R"jiowi( +@NMODL_CROUT_HPP@ +@NMODL_NEWTON_HPP@ +)jiowi"; + +} diff --git a/test/nmodl/transpiler/unit/crout/crout.cpp b/test/nmodl/transpiler/unit/crout/crout.cpp index 32a0b19ca5..cb90e9162e 100644 --- a/test/nmodl/transpiler/unit/crout/crout.cpp +++ b/test/nmodl/transpiler/unit/crout/crout.cpp @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "nmodl.hpp" +#include "crout/crout.hpp" #include diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index fec200beb3..5b5e0e47ed 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -5,7 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "nmodl.hpp" +#include "newton/newton.hpp" #include #include From a812d1e8978c5e238a354a34cc039863d723e01e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 8 Jul 2024 16:11:48 +0200 Subject: [PATCH 685/871] Implement `diam` and `area`. (BlueBrain/nmodl#1323) NMODL Repo SHA: BlueBrain/nmodl@a209d0a2ded3ced432c90bdc1765832b59d8427e --- .../codegen/codegen_neuron_cpp_visitor.cpp | 30 ++++++---- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/morphology/test_morphology.py | 60 +++++++++++++++++++ .../usecases/morphology/two_radii.mod | 28 +++++++++ 4 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/morphology/test_morphology.py create mode 100644 test/nmodl/transpiler/usecases/morphology/two_radii.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 032fcbce9c..ee0f2e2c25 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1113,11 +1113,6 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(); } - if (info.diam_used) { - printer->add_line("_morphology_sym = hoc_lookup(\"morphology\");"); - printer->add_newline(); - } - for (const auto& ion: info.ions) { printer->fmt_line("_{0}_sym = hoc_lookup(\"{0}_ion\");", ion.name); } @@ -1616,15 +1611,24 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->add_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); } - if (info.diam_used) { - throw std::runtime_error("Diam allocation not implemented."); - } + const auto codegen_int_variables_size = codegen_int_variables.size(); - if (info.area_used) { - throw std::runtime_error("Area allocation not implemented."); - } + if (info.diam_used || info.area_used) { + for (size_t i = 0; i < codegen_int_variables.size(); ++i) { + auto var_info = codegen_int_variables[i]; + if (var_info.symbol->get_name() == naming::DIAM_VARIABLE) { + printer->add_line("Symbol * morphology_sym = hoc_lookup(\"morphology\");"); + printer->fmt_line("Prop * morphology_prop = need_memb(morphology_sym);"); - const auto codegen_int_variables_size = codegen_int_variables.size(); + printer->fmt_line( + "_ppvar[{}] = _nrn_mechanism_get_param_handle(morphology_prop, 0);", i); + } + if (var_info.symbol->get_name() == naming::AREA_VARIABLE) { + printer->fmt_line("_ppvar[{}] = _nrn_mechanism_get_area_handle(nrn_alloc_node_);", + i); + } + } + } for (const auto& ion: info.ions) { printer->fmt_line("Symbol * {}_sym = hoc_lookup(\"{}_ion\");", ion.name, ion.name); @@ -1937,6 +1941,8 @@ void CodegenNeuronCppVisitor::print_headers_include() { void CodegenNeuronCppVisitor::print_macro_definitions() { print_global_macros(); print_mechanism_variables_macros(); + + printer->add_line("extern Node* nrn_alloc_node_;"); } diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 6a3511c5c8..41a13dcb16 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -5,6 +5,7 @@ set(NMODL_USECASE_DIRS global hodgkin_huxley kinetic + morphology nonspecific_current neuron_variables net_event diff --git a/test/nmodl/transpiler/usecases/morphology/test_morphology.py b/test/nmodl/transpiler/usecases/morphology/test_morphology.py new file mode 100644 index 0000000000..4f2e8a8fde --- /dev/null +++ b/test/nmodl/transpiler/usecases/morphology/test_morphology.py @@ -0,0 +1,60 @@ +import numpy as np +from neuron import gui +from neuron import h + +# This checks the use of `diam` and `area` in MOD files. + + +def check_morphology(sections): + d2_expected = np.array([s(0.5).diam ** 2 for s in sections]) + a2_expected = np.array([s(0.5).area() ** 2 for s in sections]) + + d2_actual = np.array([s(0.5).two_radii.square_diam() for s in sections]) + a2_actual = np.array([s(0.5).two_radii.square_area() for s in sections]) + + assert np.all(d2_actual == d2_expected), f"delta: {d2_actual - d2_expected}" + assert np.all(a2_actual == a2_expected), f"delta: {a2_actual - a2_expected}" + + +def check_voltage(t, v, sections): + v0 = -65.0 + erev = 20.0 + rates = [s(0.5).two_radii.square_diam() + s(0.5).area() for s in sections] + + v_exact = np.array([erev + (v0 - erev) * np.exp(-c * t) for c in rates]) + assert np.all( + np.abs(v - v_exact) < 0.01 * np.abs(v0) + ), f"{np.max(np.abs(v - v_exact))=}" + + +def simulate(): + nsec = 10 + + # Independent sections are needed to create independent ODEs. + diams = 1.0 + 0.1 * np.arange(1, nsec + 1) + sections = [h.Section() for _ in range(nsec)] + for d, s in zip(diams, sections): + s.nseg = 1 + s.insert("two_radii") + s.pt3dadd(0.0, 0.0, 0.0, d) + s.pt3dadd(1.0, 2.0, 0.0, d) + + t_hoc = h.Vector().record(h._ref_t) + v_hoc = [h.Vector().record(s(0.5)._ref_v) for s in sections] + + check_morphology(sections) + + h.stdinit() + h.dt = 0.001 + h.run() + + check_morphology(sections) + + t = np.array(t_hoc.as_numpy()) + v = np.array([vv.as_numpy() for vv in v_hoc]) + + check_voltage(t, v, sections) + + +if __name__ == "__main__": + simulate() diff --git a/test/nmodl/transpiler/usecases/morphology/two_radii.mod b/test/nmodl/transpiler/usecases/morphology/two_radii.mod new file mode 100644 index 0000000000..d1ac66575a --- /dev/null +++ b/test/nmodl/transpiler/usecases/morphology/two_radii.mod @@ -0,0 +1,28 @@ +NEURON { + SUFFIX two_radii + NONSPECIFIC_CURRENT il + RANGE inv +} + +ASSIGNED { + il + inv + diam + area +} + +INITIAL { + inv = 1.0 / (square_diam() + area) +} + +BREAKPOINT { + il = (square_diam() + area) * 0.001 * (v - 20.0) +} + +FUNCTION square_diam() { + square_diam = diam * diam +} + +FUNCTION square_area() { + square_area = area * area +} From bf38cb00acbd3ef8cbc351ed423d70727689f592 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 9 Jul 2024 15:20:52 +0200 Subject: [PATCH 686/871] Fix thread variables with Newton code. (BlueBrain/nmodl#1332) Solving non-linear system causes a function object to be present in the generated code. This function object was missing the required code to handle thread-variables. NMODL Repo SHA: BlueBrain/nmodl@87e11cb02f32ceac5f2fb4e2f5c4b2a49f13b07c --- .../codegen/codegen_neuron_cpp_visitor.cpp | 16 ++++++---- .../usecases/global/test_thread_newton.py | 26 +++++++++++++++++ .../usecases/global/thread_newton.mod | 29 +++++++++++++++++++ 3 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/global/test_thread_newton.py create mode 100644 test/nmodl/transpiler/usecases/global/thread_newton.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index ee0f2e2c25..7adf5ed86f 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -817,11 +817,17 @@ void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_ini } CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::functor_params() { - return ParamVector{{"", "NrnThread*", "", "nt"}, - {"", fmt::format("{}&", instance_struct()), "", "inst"}, - {"", "int", "", "id"}, - {"", "double", "", "v"}, - {"", "Datum*", "", "_thread"}}; + auto params = ParamVector{}; + params.push_back({"", "NrnThread*", "", "nt"}); + params.push_back({"", fmt::format("{}&", instance_struct()), "", "inst"}); + params.push_back({"", "int", "", "id"}); + params.push_back({"", "double", "", "v"}); + params.push_back({"", "Datum*", "", "_thread"}); + if (!codegen_thread_variables.empty()) { + params.push_back({"", fmt::format("{}&", thread_variables_struct()), "", "_thread_vars"}); + } + + return params; } void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_initializers) { diff --git a/test/nmodl/transpiler/usecases/global/test_thread_newton.py b/test/nmodl/transpiler/usecases/global/test_thread_newton.py new file mode 100644 index 0000000000..0a7f1cac9a --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/test_thread_newton.py @@ -0,0 +1,26 @@ +import numpy as np +from neuron import gui +from neuron import h + + +def simulate(): + s = h.Section() + s.insert("thread_newton") + + t_hoc = h.Vector().record(h._ref_t) + x_hoc = h.Vector().record(s(0.5).thread_newton._ref_x) + + h.stdinit() + h.run() + + t = np.array(t_hoc.as_numpy()) + x = h.Vector(x_hoc.as_numpy()) + + # The ODE for X is `dX/dt = 42.0`: + x_exact = 42.0 * t + + assert np.all(np.abs(x - x_exact) < 1e-10) + + +if __name__ == "__main__": + simulate() diff --git a/test/nmodl/transpiler/usecases/global/thread_newton.mod b/test/nmodl/transpiler/usecases/global/thread_newton.mod new file mode 100644 index 0000000000..0cd39d659d --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/thread_newton.mod @@ -0,0 +1,29 @@ +NEURON { + SUFFIX thread_newton + RANGE x + GLOBAL c + THREADSAFE +} + +ASSIGNED { + c + x +} + +STATE { + X +} + +INITIAL {LOCAL total + X = 0.0 + c = 42.0 +} + +BREAKPOINT { + SOLVE state METHOD sparse + x = X +} + +KINETIC state { + ~ X << (c) +} From 845cb70aaf20e938a8ee53363a23614d561d2c63 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 11 Jul 2024 13:22:27 +0200 Subject: [PATCH 687/871] Implement `wrote_conc`. (BlueBrain/nmodl#1334) NMODL Repo SHA: BlueBrain/nmodl@332c8b37cee59cc8b9777ef7194d7bc2a0c73646 --- docs/nmodl/transpiler/contents/ions.rst | 59 ++++++++- .../codegen_coreneuron_cpp_visitor.cpp | 28 ++++- .../codegen_coreneuron_cpp_visitor.hpp | 13 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 34 +++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 12 +- src/nmodl/codegen/codegen_info.hpp | 109 ++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.cpp | 86 +++++++++---- .../codegen/codegen_neuron_cpp_visitor.hpp | 7 +- src/nmodl/utils/string_utils.hpp | 13 +- test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../unit/codegen/codegen_helper.cpp | 81 ++++++++++++ .../transpiler/unit/codegen/codegen_info.cpp | 30 +++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 119 ++++++++++++++++++ .../transpiler/unit/utils/string_utils.cpp | 25 ++++ .../transpiler/usecases/useion/read_cai.mod | 14 +++ .../transpiler/usecases/useion/read_cao.mod | 14 +++ .../transpiler/usecases/useion/read_eca.mod | 14 +++ .../transpiler/usecases/useion/simulate.py | 11 +- .../transpiler/usecases/useion/style_ion.py | 22 ++++ .../usecases/useion/test_read_cai.py | 4 + .../usecases/useion/test_read_cao.py | 4 + .../usecases/useion/test_write_cai.py | 4 + .../usecases/useion/test_write_cao.py | 4 + .../usecases/useion/test_write_eca.py | 4 + .../transpiler/usecases/useion/write_cai.mod | 12 ++ .../transpiler/usecases/useion/write_cao.mod | 12 ++ .../transpiler/usecases/useion/write_eca.mod | 12 ++ 27 files changed, 664 insertions(+), 84 deletions(-) create mode 100644 test/nmodl/transpiler/unit/codegen/codegen_info.cpp create mode 100644 test/nmodl/transpiler/usecases/useion/read_cai.mod create mode 100644 test/nmodl/transpiler/usecases/useion/read_cao.mod create mode 100644 test/nmodl/transpiler/usecases/useion/read_eca.mod create mode 100644 test/nmodl/transpiler/usecases/useion/style_ion.py create mode 100644 test/nmodl/transpiler/usecases/useion/test_read_cai.py create mode 100644 test/nmodl/transpiler/usecases/useion/test_read_cao.py create mode 100644 test/nmodl/transpiler/usecases/useion/test_write_cai.py create mode 100644 test/nmodl/transpiler/usecases/useion/test_write_cao.py create mode 100644 test/nmodl/transpiler/usecases/useion/test_write_eca.py create mode 100644 test/nmodl/transpiler/usecases/useion/write_cai.mod create mode 100644 test/nmodl/transpiler/usecases/useion/write_cao.mod create mode 100644 test/nmodl/transpiler/usecases/useion/write_eca.mod diff --git a/docs/nmodl/transpiler/contents/ions.rst b/docs/nmodl/transpiler/contents/ions.rst index 729c108ba7..4a28c85185 100644 --- a/docs/nmodl/transpiler/contents/ions.rst +++ b/docs/nmodl/transpiler/contents/ions.rst @@ -92,8 +92,8 @@ Optimizing Storage ~~~~~~~~~~~~~~~~~~ Since the common pattern is to only access the values of a particular instance, -the local copy isn't needed. I might facilitate SIMD, but it could be replaces -by local variables, see Figure 2. +the local copy isn't needed. It might facilitate SIMD, but it could be replaced +by local variables to save memory, see Figure 2. .. figure:: ../images/ion_storage-opt.svg @@ -107,6 +107,61 @@ This optimization is implemented in NMODL. It can be activated on the CLI via nmodl ... codegen --opt-ionvar-copy +Concentrations +-------------- + +Concentrations adjacent to the inside and outside of the membrane are computed. +MOD files can change the value by setting ``nai`` or ``nao``. It's considered +an error if two MOD files write the same concentration, which result in a +warning. + +The reversal potential ``ena`` is a function of the two concentrations. +Therefore, when writing to either concentration, we must allow ``ena`` to be +recomputed. + +Writing Concentrations +~~~~~~~~~~~~~~~~~~~~~~ +When writing concentrations we must notify NEURON of three things: a) the fact +that this mechanism writes some concentration; b) that this instance of the +mechanism does so; c) that we've written the concentration. + +The first notification happens when registering the mechanism and controls the +position of the mechanism relative to others, i.e. mechanisms that write the +concentration must appear in the ``Memb_list`` before mechanisms that only read +the concentration, that way all mechanisms get to see the updated values. We +must call ``nrn_writes_conc`` to inform NEURON that this mechanisms intends to +write to some concentration. + +The second notification happens as part of ``nrn_alloc``, i.e. when allocating +the ``Prop``, by calling ``nrn_check_conc_write``. This enables NEURON to check +that there's no conflict and modifies the style of the ion. We pass ``0`` for +exterior and ``1`` for interior. + +The third notification happens in the INITIAL block after setting the +concentrations. We use ``nrn_wrote_conc``. This enables NEURON to conditionally +trigger recomputing the reversal potential via the Nernst equation (depending +on the ion style). + + +Ion Styles +~~~~~~~~~~ +The *style* of an ion is a 12-bit wide bitfield it stores information whether +MOD files write to the concentrations or reversal potential, if the reversal +potential should be recomputed after every timestep, etc. The style is part of +the ion mechanism. + +It's stored in the ``dparam`` array of the ion ``Prop`` as an ``int`` at +location ``0`` (variable name in NEURON: ``iontype_index_dparam``). A mechanism +using the style of an ion stores a ``generic_data_handle`` in it's ``dparam`` +array (not necessarily at location ``0``) that points to the relevant ``style`` +in the ion ``Prop``. + +From MOD files we need to notify NEURON if we're reading/writing a concentration +or reversal potential. This is done by calling ``nrn_promote(., conc, rev)``. +The magic value for ``rev`` and ``conc`` is ``3`` when writing, ``1`` when only +reading and ``0`` if we do neither. + + Special Case: zero-area nodes ----------------------------- diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 571b7c82a0..2735d30000 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -14,6 +14,7 @@ #include #include "ast/all.hpp" +#include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_naming.hpp" #include "codegen/codegen_utils.hpp" @@ -688,12 +689,29 @@ std::string CodegenCoreneuronCppVisitor::register_mechanism_arguments() const { } -std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) { +void CodegenCoreneuronCppVisitor::append_conc_write_statements( + std::vector& statements, + const Ion& ion, + const std::string& concentration) { + int index = 0; + if (ion.is_intra_cell_conc(concentration)) { + index = 1; + } else if (ion.is_extra_cell_conc(concentration)) { + index = 2; + } else { + /// \todo Unhandled case in neuron implementation + throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); + } + auto ion_type_name = fmt::format("{}_type", ion.name); + auto lhs = fmt::format("int {}", ion_type_name); + auto op = "="; + auto rhs = get_variable_name(ion_type_name); + statements.push_back(ShadowUseStatement{lhs, op, rhs}); + + auto ion_name = ion.name; auto conc_var_name = get_variable_name(naming::ION_VARNAME_PREFIX + concentration); auto style_var_name = get_variable_name("style_" + ion_name); - return fmt::format( + auto statement = fmt::format( "nrn_wrote_conc({}_type," " &({})," " {}," @@ -707,6 +725,8 @@ std::string CodegenCoreneuronCppVisitor::conc_write_statement(const std::string& style_var_name, get_variable_name(naming::CELSIUS_VARIABLE), ion_name); + + statements.push_back(ShadowUseStatement{statement, "", ""}); } diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 1433b0c6a2..33c4e311d8 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -466,16 +466,9 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { std::string register_mechanism_arguments() const override; - /** - * Generate Function call statement for nrn_wrote_conc - * \param ion_name The name of the ion variable - * \param concentration The name of the concentration variable - * \param index - * \return The string representing the function call - */ - std::string conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) override; + void append_conc_write_statements(std::vector& statements, + const Ion& ion, + const std::string& concentration) override; /****************************************************************************************/ /* Code-specific printing routines for code generations */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 711fff48b8..e87e6aa534 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -291,7 +291,6 @@ std::vector CodegenCppVisitor::ion_write_statements(BlockTyp std::vector statements; for (const auto& ion: info.ions) { std::string concentration; - auto name = ion.name; for (const auto& var: ion.writes) { auto variable_names = write_ion_variable_name(var); if (ion.is_ionic_current(var)) { @@ -318,22 +317,7 @@ std::vector CodegenCppVisitor::ion_write_statements(BlockTyp } if (type == BlockType::Initial && !concentration.empty()) { - int index = 0; - if (ion.is_intra_cell_conc(concentration)) { - index = 1; - } else if (ion.is_extra_cell_conc(concentration)) { - index = 2; - } else { - /// \todo Unhandled case in neuron implementation - throw std::logic_error(fmt::format("codegen error for {} ion", ion.name)); - } - auto ion_type_name = fmt::format("{}_type", ion.name); - auto lhs = fmt::format("int {}", ion_type_name); - auto op = "="; - auto rhs = get_variable_name(ion_type_name); - statements.push_back(ShadowUseStatement{lhs, op, rhs}); - auto statement = conc_write_statement(ion.name, concentration, index); - statements.push_back(ShadowUseStatement{statement, "", ""}); + append_conc_write_statements(statements, ion, concentration); } } return statements; @@ -386,7 +370,6 @@ std::string CodegenCppVisitor::breakpoint_current(std::string current) const { return current; } - /****************************************************************************************/ /* Routines for returning variable name */ /****************************************************************************************/ @@ -420,6 +403,21 @@ std::pair CodegenCppVisitor::write_ion_variable_name( } +int CodegenCppVisitor::get_int_variable_index(const std::string& var_name) { + auto it = std::find_if(codegen_int_variables.cbegin(), + codegen_int_variables.cend(), + [&var_name](const auto& int_var) { + return int_var.symbol->get_name() == var_name; + }); + + if (it == codegen_int_variables.cend()) { + throw std::runtime_error(fmt::format("Unknown int variable: {}", var_name)); + } + + return static_cast(it - codegen_int_variables.cbegin()); +} + + /****************************************************************************************/ /* Main printing routines for code generation */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 1e317eee7a..875587acd8 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1018,14 +1018,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /** * Generate Function call statement for nrn_wrote_conc - * \param ion_name The name of the ion variable + * \param statements Statements are appended to this vector. + * \param ion The ion variable. * \param concentration The name of the concentration variable - * \param index * \return The string representing the function call */ - virtual std::string conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) = 0; + virtual void append_conc_write_statements(std::vector& statements, + const Ion& ion, + const std::string& concentration) = 0; /****************************************************************************************/ /* Code-specific printing routines for code generations */ @@ -1141,6 +1141,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { static std::pair write_ion_variable_name(const std::string& name); + int get_int_variable_index(const std::string& var_name); + /****************************************************************************************/ /* Main printing routines for code generation */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 3a0d4b1662..c3274ba598 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -19,6 +19,7 @@ #include #include "ast/ast.hpp" +#include "codegen/codegen_naming.hpp" #include "symtab/symbol_table.hpp" @@ -68,6 +69,48 @@ struct Ion { explicit Ion(std::string name) : name(std::move(name)) {} + bool is_read(const std::string& name) const { + return std::find(reads.cbegin(), reads.cend(), name) != reads.cend() || + std::find(implicit_reads.cbegin(), implicit_reads.cend(), name) != + implicit_reads.cend(); + } + + bool is_conc_read() const { + return is_interior_conc_read() || is_exterior_conc_read(); + } + + bool is_interior_conc_read() const { + return is_read(fmt::format("{}i", name)); + } + + bool is_exterior_conc_read() const { + return is_read(fmt::format("{}o", name)); + } + + bool is_written(const std::string& name) const { + return std::find(writes.cbegin(), writes.cend(), name) != writes.cend(); + } + + bool is_conc_written() const { + return is_interior_conc_written() || is_exterior_conc_written(); + } + + bool is_interior_conc_written() const { + return is_written(fmt::format("{}i", name)); + } + + bool is_exterior_conc_written() const { + return is_written(fmt::format("{}o", name)); + } + + bool is_rev_read() const { + return is_read(fmt::format("e{}", name)); + } + + bool is_rev_written() const { + return is_written(fmt::format("e{}", name)); + } + /** * Check if variable name is a ionic current * @@ -76,7 +119,7 @@ struct Ion { * If it is write variables then also get NRNCUROUT flag. */ bool is_ionic_current(const std::string& text) const { - return text == ("i" + name); + return text == ionic_current_name(); } /** @@ -85,7 +128,7 @@ struct Ion { * This is equivalent of IONIN flag in mod2c. */ bool is_intra_cell_conc(const std::string& text) const { - return text == (name + "i"); + return text == intra_conc_name(); } /** @@ -94,16 +137,17 @@ struct Ion { * This is equivalent of IONOUT flag in mod2c. */ bool is_extra_cell_conc(const std::string& text) const { - return text == (name + "o"); + return text == extra_conc_name(); } /** * Check if variable name is reveral potential * - * This is equivalent of IONEREV flag in mod2c. + * Matches `ena` and `na_erev`. */ bool is_rev_potential(const std::string& text) const { - return text == ("e" + name); + return text == rev_potential_name() || + stringutils::ends_with(rev_potential_pointer_name(), text); } /// check if it is either internal or external concentration @@ -119,15 +163,66 @@ struct Ion { return is_ionic_conc(text) || is_ionic_current(text) || is_rev_potential(text); } + /// Is the variable name `text` the style of this ion? + /// + /// Example: For sodium this is true for `"style_na"`. + bool is_style(const std::string& text) const { + return text == fmt::format("style_{}", name); + } + bool is_current_derivative(const std::string& text) const { return text == ("di" + name + "dv"); } + std::string intra_conc_name() const { + return name + "i"; + } + + std::string intra_conc_pointer_name() const { + return naming::ION_VARNAME_PREFIX + intra_conc_name(); + } + + std::string extra_conc_name() const { + return name + "o"; + } + + std::string extra_conc_pointer_name() const { + return naming::ION_VARNAME_PREFIX + extra_conc_name(); + } + + std::string rev_potential_name() const { + return "e" + name; + } + + std::string rev_potential_pointer_name() const { + return naming::ION_VARNAME_PREFIX + name + "_erev"; + } + + std::string ionic_current_name() const { + return "i" + name; + } + + std::string ionic_current_pointer_name() const { + return naming::ION_VARNAME_PREFIX + ionic_current_name(); + } + + std::string current_derivative_name() const { + return "di" + name + "dv"; + } + + std::string current_derivative_pointer_name() const { + return naming::ION_VARNAME_PREFIX + current_derivative_name(); + } + /// for a given ion, return different variable names/properties - /// like internal/external concentration, reversial potential, + /// like internal/external concentration, reversal potential, /// ionic current etc. static std::vector get_possible_variables(const std::string& ion_name) { - return {"i" + ion_name, ion_name + "i", ion_name + "o", "e" + ion_name}; + auto ion = Ion(ion_name); + return {ion.ionic_current_name(), + ion.intra_conc_name(), + ion.extra_conc_name(), + ion.rev_potential_name()}; } /// Variable index in the ion mechanism. diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 7adf5ed86f..d3688e984e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -565,11 +565,25 @@ void CodegenNeuronCppVisitor::print_namespace_stop() { printer->pop_block(); } +void CodegenNeuronCppVisitor::append_conc_write_statements( + std::vector& statements, + const Ion& ion, + const std::string& /* concentration */) { + auto ion_name = ion.name; + int dparam_index = get_int_variable_index(fmt::format("style_{}", ion_name)); -std::string CodegenNeuronCppVisitor::conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) { - return ""; + auto style_name = fmt::format("_style_{}", ion_name); + auto style_stmt = fmt::format("int {} = *(_ppvar[{}].get())", style_name, dparam_index); + statements.push_back(ShadowUseStatement{style_stmt, "", ""}); + + + auto wrote_conc_stmt = fmt::format("nrn_wrote_conc(_{}_sym, {}, {}, {}, {})", + ion_name, + get_variable_name(ion.rev_potential_pointer_name()), + get_variable_name(ion.intra_conc_pointer_name()), + get_variable_name(ion.extra_conc_pointer_name()), + style_name); + statements.push_back(ShadowUseStatement{wrote_conc_stmt, "", ""}); } /****************************************************************************************/ @@ -1200,7 +1214,12 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { type = "Point_process*"; } else if (name == naming::TQITEM_VARIABLE) { type = "void*"; + } else if (stringutils::starts_with(name, "style_") && + stringutils::starts_with(info.semantics[i].name, "#") && + stringutils::ends_with(info.semantics[i].name, "_ion")) { + type = "int*"; } + mech_register_args.push_back( fmt::format("_nrn_mechanism_field<{}>{{\"{}\", \"{}\"}} /* {} */", type, @@ -1234,6 +1253,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { info.semantics[i].name); } + if (info.write_concentration) { + printer->fmt_line("nrn_writes_conc(mech_type, 0);"); + } + if (info.artificial_cell) { printer->fmt_line("add_nrn_artcell(mech_type, {});", info.tqitem_index); } @@ -1569,10 +1592,7 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { auto method = method_name(naming::NRN_ALLOC_METHOD); printer->fmt_push_block("static void {}(Prop* _prop)", method); - printer->add_multi_line(R"CODE( - Prop *prop_ion{}; - Datum *_ppvar{}; - )CODE"); + printer->add_line("Datum *_ppvar = nullptr;"); if (info.point_process) { printer->push_block("if (nrn_point_prop_)"); @@ -1589,7 +1609,7 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { } printer->add_multi_line(R"CODE( _nrn_mechanism_cache_instance _lmc{_prop}; - size_t const _iml{}; + size_t const _iml = 0; )CODE"); printer->fmt_line("assert(_nrn_mechanism_get_num_vars(_prop) == {});", codegen_float_variables.size()); @@ -1640,30 +1660,42 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->fmt_line("Symbol * {}_sym = hoc_lookup(\"{}_ion\");", ion.name, ion.name); printer->fmt_line("Prop * {}_prop = need_memb({}_sym);", ion.name, ion.name); + if (ion.is_exterior_conc_written()) { + printer->fmt_line("nrn_check_conc_write(_prop, {}_prop, 0);", ion.name); + } + + if (ion.is_interior_conc_written()) { + printer->fmt_line("nrn_check_conc_write(_prop, {}_prop, 1);", ion.name); + } + + int conc = ion.is_conc_written() ? 3 : int(ion.is_conc_read()); + int rev = ion.is_rev_written() ? 3 : int(ion.is_rev_read()); + + printer->fmt_line("nrn_promote({}_prop, {}, {});", ion.name, conc, rev); + for (size_t i = 0; i < codegen_int_variables_size; ++i) { const auto& var = codegen_int_variables[i]; - // if(var.symbol->has_any_property(NmodlType::useion)) { const std::string& var_name = var.symbol->get_name(); - if (var_name.rfind("ion_", 0) != 0) { - continue; - } - std::string ion_var_name = std::string(var_name.begin() + 4, var_name.end()); - if (ion.is_ionic_variable(ion_var_name)) { - printer->fmt_line("_ppvar[{}] = _nrn_mechanism_get_param_handle({}_prop, {});", - i, - ion.name, - ion.variable_index(ion_var_name)); - } - // assign derivatives of the current as well - else if (ion.is_current_derivative(ion_var_name)) { - printer->fmt_line("_ppvar[{}] = _nrn_mechanism_get_param_handle({}_prop, {});", - i, - ion.name, - ion.variable_index(ion_var_name)); + if (stringutils::starts_with(var_name, "ion_")) { + std::string ion_var_name = std::string(var_name.begin() + 4, var_name.end()); + if (ion.is_ionic_variable(ion_var_name) || + ion.is_current_derivative(ion_var_name) || ion.is_rev_potential(ion_var_name)) { + printer->fmt_line("_ppvar[{}] = _nrn_mechanism_get_param_handle({}_prop, {});", + i, + ion.name, + ion.variable_index(ion_var_name)); + } + } else { + if (ion.is_style(var_name)) { + printer->fmt_line( + "_ppvar[{}] = {{neuron::container::do_not_search, " + "&(_nrn_mechanism_access_dparam({}_prop)[0].literal_value())}};", + i, + ion.name); + } } - //} } } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 4136db3b2c..7cac67c2f9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -317,9 +317,10 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ std::string register_mechanism_arguments() const override; - std::string conc_write_statement(const std::string& ion_name, - const std::string& concentration, - int index) override; + + void append_conc_write_statements(std::vector& statements, + const Ion& ion, + const std::string& concentration) override; /** * All functions and procedures need a \c _hoc_ to be available to the HOC diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 4f9dc464dc..2b0d81d120 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -130,10 +130,10 @@ enum class text_alignment { left, right, center }; /** * Check if `haystack` ends with `needle`. * - * Every string ends with the empty string. + * The empty string is a suffix of every string. */ static inline bool ends_with(const std::string& haystack, const std::string& needle) { - if (needle.size() == 0) { + if (needle.empty()) { return true; } @@ -146,6 +146,15 @@ static inline bool ends_with(const std::string& haystack, const std::string& nee return std::equal(haystack_begin, haystack.end(), needle.begin(), needle.end()); }; +/** + * Check if `haystack` starts with `needle`. + * + * The empty string is a prefix of every string. + */ +static inline bool starts_with(const std::string& haystack, const std::string& needle) { + return haystack.rfind(needle, 0) == 0; +} + /// /** diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 7c554474da..a9bf75ee8c 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -70,6 +70,7 @@ add_executable(testunitparser units/parser.cpp) add_executable( testcodegen codegen/main.cpp + codegen/codegen_info.cpp codegen/codegen_helper.cpp codegen/codegen_utils.cpp codegen/codegen_coreneuron_cpp_visitor.cpp diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 7ad35844a3..a8da8c9b88 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -9,6 +9,7 @@ #include "ast/program.hpp" #include "codegen/codegen_helper_visitor.hpp" +#include "codegen/codegen_info.hpp" #include "parser/nmodl_driver.hpp" #include "visitors/kinetic_block_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" @@ -212,3 +213,83 @@ SCENARIO("Check global variable setup", "[codegen][global_variables]") { } } } + +CodegenInfo make_codegen_info(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + + SymtabVisitor().visit_program(*ast); + CodegenHelperVisitor v; + + return v.analyze(*ast); +} + +TEST_CASE("Check ion write/read checks") { + std::string input_nmodl = R"( + NEURON { + SUFFIX test + USEION ca READ cai WRITE cai, eca + USEION na WRITE nao, ena + USEION K READ Ki, eK + RANGE x + } + ASSIGNED { + x + cai + eca + nai + nao + ena + Ki + } + INITIAL { + x = cai + cai = 42.0 + x = nao + Ki = 42.0 + } + BREAKPOINT { + eca = 42.0 + x = ena + eK = 42.0 + } + )"; + + auto info = make_codegen_info(input_nmodl); + + for (const auto& ion: info.ions) { + if (ion.name == "ca") { + REQUIRE(ion.is_conc_read()); + REQUIRE(ion.is_interior_conc_read()); + REQUIRE(!ion.is_exterior_conc_read()); + REQUIRE(!ion.is_rev_read()); + + REQUIRE(ion.is_conc_written()); + REQUIRE(ion.is_interior_conc_written()); + REQUIRE(!ion.is_exterior_conc_written()); + REQUIRE(ion.is_rev_written()); + } + if (ion.name == "na") { + REQUIRE(!ion.is_conc_read()); + REQUIRE(!ion.is_interior_conc_read()); + REQUIRE(!ion.is_exterior_conc_read()); + REQUIRE(!ion.is_rev_read()); + + REQUIRE(ion.is_conc_written()); + REQUIRE(!ion.is_interior_conc_written()); + REQUIRE(ion.is_exterior_conc_written()); + REQUIRE(ion.is_rev_written()); + } + if (ion.name == "K") { + REQUIRE(ion.is_conc_read()); + REQUIRE(ion.is_interior_conc_read()); + REQUIRE(!ion.is_exterior_conc_read()); + REQUIRE(ion.is_rev_read()); + + REQUIRE(!ion.is_conc_written()); + REQUIRE(!ion.is_interior_conc_written()); + REQUIRE(!ion.is_exterior_conc_written()); + REQUIRE(!ion.is_rev_written()); + } + } +} diff --git a/test/nmodl/transpiler/unit/codegen/codegen_info.cpp b/test/nmodl/transpiler/unit/codegen/codegen_info.cpp new file mode 100644 index 0000000000..b0b584b5d1 --- /dev/null +++ b/test/nmodl/transpiler/unit/codegen/codegen_info.cpp @@ -0,0 +1,30 @@ +#include + +#include "ast/program.hpp" +#include "codegen/codegen_info.hpp" +#include "parser/nmodl_driver.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace codegen; + +using nmodl::parser::NmodlDriver; + +TEST_CASE("Check ion variable names") { + auto ion = Ion("na"); + + REQUIRE(ion.intra_conc_name() == "nai"); + REQUIRE(ion.intra_conc_pointer_name() == "ion_nai"); + + REQUIRE(ion.extra_conc_name() == "nao"); + REQUIRE(ion.extra_conc_pointer_name() == "ion_nao"); + + REQUIRE(ion.rev_potential_name() == "ena"); + REQUIRE(ion.rev_potential_pointer_name() == "ion_na_erev"); + + REQUIRE(ion.ionic_current_name() == "ina"); + REQUIRE(ion.ionic_current_pointer_name() == "ion_ina"); + + REQUIRE(ion.current_derivative_name() == "dinadv"); + REQUIRE(ion.current_derivative_pointer_name() == "ion_dinadv"); +} diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 46386455a8..2189ed076d 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -90,3 +90,122 @@ SCENARIO("Check whether PROCEDURE and FUNCTION need setdata call", "[codegen][ne } } } + +std::string create_mod_file_write(const std::string& var) { + std::string pattern = R"( + NEURON {{ + SUFFIX test + USEION ca WRITE {0} + }} + ASSIGNED {{ + {0} + }} + INITIAL {{ + {0} = 124.5 + }} + )"; + + return fmt::format(pattern, var); +} + +std::string create_mod_file_read(const std::string& var) { + std::string pattern = R"( + NEURON {{ + SUFFIX test + USEION ca READ {0} + RANGE x + }} + ASSIGNED {{ + x + {0} + }} + INITIAL {{ + x = {0} + }} + )"; + + return fmt::format(pattern, var); +} + +std::string transpile(const std::string& nmodl) { + const auto& ast = NmodlDriver().parse_string(nmodl); + std::stringstream ss; + auto cvisitor = create_neuron_cpp_visitor(ast, nmodl, ss); + cvisitor->visit_program(*ast); + + return ss.str(); +} + +SCENARIO("Write `cao`.", "[codegen]") { + GIVEN("mod file that writes to `cao`") { + std::string cpp = transpile(create_mod_file_write("cao")); + + THEN("it contains") { + REQUIRE_THAT(cpp, ContainsSubstring("nrn_check_conc_write(_prop, ca_prop, 0);")); + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_check_conc_write(_prop, ca_prop, 1);")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_promote(ca_prop, 3, 0);")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_wrote_conc(")); + } + } +} + +SCENARIO("Write `cai`.", "[codegen]") { + GIVEN("mod file that writes to `cai`") { + std::string cpp = transpile(create_mod_file_write("cai")); + + THEN("it contains") { + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_check_conc_write(_prop, ca_prop, 0);")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_check_conc_write(_prop, ca_prop, 1);")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_promote(ca_prop, 3, 0);")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_wrote_conc(")); + } + } +} + +SCENARIO("Write `eca`.", "[codegen]") { + GIVEN("mod file that writes to `eca`") { + std::string cpp = transpile(create_mod_file_write("eca")); + + THEN("it contains") { + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_check_conc_write(_prop,")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_promote(ca_prop, 0, 3);")); + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_wrote_conc(")); + } + } +} + +SCENARIO("Read `cao`.", "[codegen]") { + GIVEN("mod file that reads to `cao`") { + std::string cpp = transpile(create_mod_file_read("cao")); + + THEN("it contains") { + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_check_conc_write(_prop,")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_promote(ca_prop, 1, 0);")); + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_wrote_conc(")); + } + } +} + +SCENARIO("Read `cai`.", "[codegen]") { + GIVEN("mod file that reads to `cai`") { + std::string cpp = transpile(create_mod_file_read("cai")); + + THEN("it contains") { + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_check_conc_write(_prop,")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_promote(ca_prop, 1, 0);")); + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_wrote_conc(")); + } + } +} + +SCENARIO("Read `eca`.", "[codegen]") { + GIVEN("mod file that reads to `eca`") { + std::string cpp = transpile(create_mod_file_read("eca")); + + THEN("it contains") { + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_check_conc_write(_prop,")); + REQUIRE_THAT(cpp, ContainsSubstring("nrn_promote(ca_prop, 0, 1);")); + REQUIRE_THAT(cpp, !ContainsSubstring("nrn_wrote_conc(")); + } + } +} diff --git a/test/nmodl/transpiler/unit/utils/string_utils.cpp b/test/nmodl/transpiler/unit/utils/string_utils.cpp index c79e2d2f2e..e55631fc3b 100644 --- a/test/nmodl/transpiler/unit/utils/string_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/string_utils.cpp @@ -29,3 +29,28 @@ TEST_CASE("ends_with") { REQUIRE(!stringutils::ends_with("abcde", "--abcde")); } } + +TEST_CASE("starts_with") { + SECTION("empty substring") { + REQUIRE(stringutils::starts_with("abcde", "")); + } + + SECTION("empty str") { + REQUIRE(!stringutils::starts_with("", "abc")); + } + + SECTION("both empty") { + REQUIRE(stringutils::starts_with("", "")); + } + SECTION("match") { + REQUIRE(stringutils::starts_with("abcde", "ab")); + } + + SECTION("mismatch") { + REQUIRE(!stringutils::starts_with("abcde", "b")); + } + + SECTION("oversized") { + REQUIRE(!stringutils::starts_with("abcde", "abcde++")); + } +} diff --git a/test/nmodl/transpiler/usecases/useion/read_cai.mod b/test/nmodl/transpiler/usecases/useion/read_cai.mod new file mode 100644 index 0000000000..364e54571c --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/read_cai.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX read_cai + USEION ca READ cai + RANGE x +} + +ASSIGNED { + x + cai +} + +INITIAL { + x = cai +} diff --git a/test/nmodl/transpiler/usecases/useion/read_cao.mod b/test/nmodl/transpiler/usecases/useion/read_cao.mod new file mode 100644 index 0000000000..5b3db76800 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/read_cao.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX read_cao + USEION ca READ cao + RANGE x +} + +ASSIGNED { + x + cao +} + +INITIAL { + x = cao +} diff --git a/test/nmodl/transpiler/usecases/useion/read_eca.mod b/test/nmodl/transpiler/usecases/useion/read_eca.mod new file mode 100644 index 0000000000..6d45bb579f --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/read_eca.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX read_eca + USEION ca READ eca + RANGE x +} + +ASSIGNED { + x + eca +} + +INITIAL { + x = eca +} diff --git a/test/nmodl/transpiler/usecases/useion/simulate.py b/test/nmodl/transpiler/usecases/useion/simulate.py index 3468a3b703..b05533821e 100644 --- a/test/nmodl/transpiler/usecases/useion/simulate.py +++ b/test/nmodl/transpiler/usecases/useion/simulate.py @@ -9,19 +9,18 @@ s.insert("ionic") s.nseg = nseg -x_hoc = h.Vector().record(s(0.5)._ref_ena) +ena_hoc = h.Vector().record(s(0.5)._ref_ena) t_hoc = h.Vector().record(h._ref_t) h.stdinit() h.tstop = 5.0 * ms h.run() -x = np.array(x_hoc.as_numpy()) +ena = np.array(ena_hoc.as_numpy()) t = np.array(t_hoc.as_numpy()) -x_exact = np.full(t.shape, 42.0) -x_exact[0] = 0.0 - -abs_err = np.abs(x - x_exact) +ena_exact = np.full(t.shape, 42.0) +ena_exact[0] = 0.0 +abs_err = np.abs(ena - ena_exact) assert np.all(abs_err < 1e-12), abs_err diff --git a/test/nmodl/transpiler/usecases/useion/style_ion.py b/test/nmodl/transpiler/usecases/useion/style_ion.py new file mode 100644 index 0000000000..d87d6b8b58 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/style_ion.py @@ -0,0 +1,22 @@ +import numpy as np +from neuron import gui +from neuron import h + + +def nernst(ca): + return h.nernst(ca.cai, ca.cao, h.ion_charge("ca_ion")) + + +def simulate(mech_name, compute_eca=nernst): + s = h.Section() + s.insert(mech_name) + s.nseg = 1 + + h.stdinit() + + ca = s(0.5).ca_ion + eca_expected = compute_eca(ca) + + assert ( + np.abs(ca.eca - eca_expected) < 1e-10 + ), f"{ca.eca} - {eca_expected} = {ca.eca - eca_expected}" diff --git a/test/nmodl/transpiler/usecases/useion/test_read_cai.py b/test/nmodl/transpiler/usecases/useion/test_read_cai.py new file mode 100644 index 0000000000..a2238cec4f --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/test_read_cai.py @@ -0,0 +1,4 @@ +from style_ion import simulate + +if __name__ == "__main__": + simulate("read_cai") diff --git a/test/nmodl/transpiler/usecases/useion/test_read_cao.py b/test/nmodl/transpiler/usecases/useion/test_read_cao.py new file mode 100644 index 0000000000..bd4be7e7c6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/test_read_cao.py @@ -0,0 +1,4 @@ +from style_ion import simulate + +if __name__ == "__main__": + simulate("read_cao") diff --git a/test/nmodl/transpiler/usecases/useion/test_write_cai.py b/test/nmodl/transpiler/usecases/useion/test_write_cai.py new file mode 100644 index 0000000000..a9c45da10d --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/test_write_cai.py @@ -0,0 +1,4 @@ +from style_ion import simulate + +if __name__ == "__main__": + simulate("write_cai") diff --git a/test/nmodl/transpiler/usecases/useion/test_write_cao.py b/test/nmodl/transpiler/usecases/useion/test_write_cao.py new file mode 100644 index 0000000000..35dbf89ac0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/test_write_cao.py @@ -0,0 +1,4 @@ +from style_ion import simulate + +if __name__ == "__main__": + simulate("write_cao") diff --git a/test/nmodl/transpiler/usecases/useion/test_write_eca.py b/test/nmodl/transpiler/usecases/useion/test_write_eca.py new file mode 100644 index 0000000000..2e13cf6588 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/test_write_eca.py @@ -0,0 +1,4 @@ +from style_ion import simulate + +if __name__ == "__main__": + simulate("write_eca", lambda _: 1124.0) diff --git a/test/nmodl/transpiler/usecases/useion/write_cai.mod b/test/nmodl/transpiler/usecases/useion/write_cai.mod new file mode 100644 index 0000000000..d2a0343fb8 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/write_cai.mod @@ -0,0 +1,12 @@ +NEURON { + SUFFIX write_cai + USEION ca WRITE cai +} + +ASSIGNED { + cai +} + +INITIAL { + cai = 1124.0 +} diff --git a/test/nmodl/transpiler/usecases/useion/write_cao.mod b/test/nmodl/transpiler/usecases/useion/write_cao.mod new file mode 100644 index 0000000000..f67991ad97 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/write_cao.mod @@ -0,0 +1,12 @@ +NEURON { + SUFFIX write_cao + USEION ca WRITE cao +} + +ASSIGNED { + cao +} + +INITIAL { + cao = 1124.0 +} diff --git a/test/nmodl/transpiler/usecases/useion/write_eca.mod b/test/nmodl/transpiler/usecases/useion/write_eca.mod new file mode 100644 index 0000000000..ce5e1c7d88 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/write_eca.mod @@ -0,0 +1,12 @@ +NEURON { + SUFFIX write_eca + USEION ca WRITE eca +} + +ASSIGNED { + eca +} + +INITIAL { + eca = 1124.0 +} From 2d3a10b621e9b745027fc6679ef2a477eb3b65a6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 12 Jul 2024 15:54:56 +0200 Subject: [PATCH 688/871] Implement printing for CONSTANT. (BlueBrain/nmodl#1339) * Implement printing for CONSTANT. * Document CONSTANT. * Register `constant` usecase group. NMODL Repo SHA: BlueBrain/nmodl@6be1a19b2858e7819c67c8eb12a466cff2a7e260 --- docs/nmodl/transpiler/contents/globals.rst | 16 ++++++++++++++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 11 +++++++++++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/constant/constant.mod | 11 +++++++++++ .../usecases/constant/test_constant.py | 15 +++++++++++++++ 5 files changed, 54 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/constant/constant.mod create mode 100644 test/nmodl/transpiler/usecases/constant/test_constant.py diff --git a/docs/nmodl/transpiler/contents/globals.rst b/docs/nmodl/transpiler/contents/globals.rst index 418b478df1..ce3fa3247b 100644 --- a/docs/nmodl/transpiler/contents/globals.rst +++ b/docs/nmodl/transpiler/contents/globals.rst @@ -5,6 +5,7 @@ The following global variables exist: * ``GLOBAL`` visible from HOC/Python. * ``LOCAL`` at file scope, called top-locals. + * ``CONSTANT`` not visible from HOC/Python. GLOBAL variables ================ @@ -175,6 +176,21 @@ Collection of slightly surprising behaviour: the code ``nocmodl`` produces will cause a SEGFAULT. +CONSTANT variables +================== + +These are comparatively simple. In NOCMODL they're implemented as non-const +static doubles. They're not accessible from HOC/Python (which makes them +simple). + +Quirks +~~~~~~ + +In certain versions of NOCMODL around `9.0` and before (and NMODL) it's +possible to change the value of CONSTANT variables. The MOD file will still be +considered "thread-safe" (even if it might not be). + + What Does CoreNEURON support? ============================= CoreNEURON only supports read-only GLOBAL variables. Anything else needs to be diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index d3688e984e..e6db2c953c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -980,6 +980,17 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in codegen_global_variables.push_back(var); } + for (const auto& var: info.constant_variables) { + auto const name = var->get_name(); + auto* const value_ptr = var->get_value().get(); + double const value{value_ptr ? *value_ptr : 0}; + printer->fmt_line("{} {}{};", + float_type, + name, + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); + codegen_global_variables.push_back(var); + } + // for (const auto& f: info.function_tables) { if (!info.function_tables.empty()) { diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 41a13dcb16..3923727b48 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,5 +1,6 @@ set(NMODL_USECASE_DIRS cnexp + constant function procedure global diff --git a/test/nmodl/transpiler/usecases/constant/constant.mod b/test/nmodl/transpiler/usecases/constant/constant.mod new file mode 100644 index 0000000000..f1b6cc854a --- /dev/null +++ b/test/nmodl/transpiler/usecases/constant/constant.mod @@ -0,0 +1,11 @@ +NEURON { + SUFFIX constant_mod +} + +CONSTANT { + a = 2.3 +} + +FUNCTION foo() { + foo = a +} diff --git a/test/nmodl/transpiler/usecases/constant/test_constant.py b/test/nmodl/transpiler/usecases/constant/test_constant.py new file mode 100644 index 0000000000..857566f089 --- /dev/null +++ b/test/nmodl/transpiler/usecases/constant/test_constant.py @@ -0,0 +1,15 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + +nseg = 1 +s = h.Section() +s.insert("constant_mod") + +expected = 2.3 +assert s(0.5).constant_mod.foo() == expected + +h.stdinit() + +assert s(0.5).constant_mod.foo() == expected From 3caf31fc6b605df04012fd0f61ed8504ef0b2315 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 12 Jul 2024 15:55:16 +0200 Subject: [PATCH 689/871] Don't print `int*` range variables. (BlueBrain/nmodl#1340) * Add regression test. * Don't print `int*` range variables. NMODL Repo SHA: BlueBrain/nmodl@2af37abb8ca3a5c62718cacc8a853aa448cac462 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 3 +-- .../transpiler/usecases/useion/style_ion.mod | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/useion/style_ion.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e6db2c953c..42e2f5d846 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1354,8 +1354,7 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini if (name == naming::POINT_PROCESS_VARIABLE) { continue; } else if (var.is_index || var.is_integer) { - auto qualifier = var.is_constant ? "const " : ""; - printer->fmt_line("{}{}* const* {}{};", qualifier, int_type, name, value_initialize); + // In NEURON we don't create caches for `int*`. Hence, do nothing. } else { auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); diff --git a/test/nmodl/transpiler/usecases/useion/style_ion.mod b/test/nmodl/transpiler/usecases/useion/style_ion.mod new file mode 100644 index 0000000000..4e6ba180ff --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/style_ion.mod @@ -0,0 +1,18 @@ +: This checks for a code-generation bug that resulted in a faulty ctor +: call. It requires an ion to have a "style", followed by other ions. + +NEURON { + SUFFIX style_ion + USEION ca WRITE cai, eca + USEION na READ nai +} + +ASSIGNED { + cai + eca + nai +} + +INITIAL { + cai = 42.0 +} From 3935a3d5ad7b8b04850244e8d1d86f7d9b8820fe Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 15 Jul 2024 10:27:57 +0200 Subject: [PATCH 690/871] Implement VALENCE. (BlueBrain/nmodl#1341) NMODL Repo SHA: BlueBrain/nmodl@9a430a0d1ced88ebfcdbe1a7cf5dd08964f2011d --- src/nmodl/codegen/codegen_helper_visitor.cpp | 12 +++++++++++- src/nmodl/codegen/codegen_info.hpp | 4 ++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 4 +++- .../transpiler/usecases/useion/test_valence.py | 9 +++++++++ test/nmodl/transpiler/usecases/useion/valence.mod | 14 ++++++++++++++ 5 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/useion/test_valence.py create mode 100644 test/nmodl/transpiler/usecases/useion/valence.mod diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 417eca131e..5a2880b778 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -73,16 +73,22 @@ void CodegenHelperVisitor::find_ion_variables(const ast::Program& node) { std::vector ion_vars; std::vector read_ion_vars; std::vector write_ion_vars; + std::map valences; for (const auto& ion_node: ion_nodes) { const auto& ion = std::dynamic_pointer_cast(ion_node); - ion_vars.push_back(ion->get_node_name()); + auto ion_name = ion->get_node_name(); + ion_vars.push_back(ion_name); for (const auto& var: ion->get_readlist()) { read_ion_vars.push_back(var->get_node_name()); } for (const auto& var: ion->get_writelist()) { write_ion_vars.push_back(var->get_node_name()); } + + if (ion->get_valence() != nullptr) { + valences[ion_name] = ion->get_valence()->get_value()->to_double(); + } } /** @@ -112,6 +118,10 @@ void CodegenHelperVisitor::find_ion_variables(const ast::Program& node) { } } } + if (auto it = valences.find(ion_name); it != valences.end()) { + ion.valence = it->second; + } + info.ions.push_back(std::move(ion)); } diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index c3274ba598..4f392cc683 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -13,6 +13,7 @@ */ #include +#include #include #include #include @@ -61,6 +62,9 @@ struct Ion { /// ion variables that are being written std::vector writes; + /// ion valence + std::optional valence; + /// if style semantic needed bool need_style = false; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 42e2f5d846..cacaf6dcc7 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -1138,7 +1139,8 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(); for (const auto& ion: info.ions) { - printer->fmt_line("ion_reg(\"{}\", {});", ion.name, "-10000."); + double valence = ion.valence.value_or(-10000.0); + printer->fmt_line("ion_reg(\"{}\", {});", ion.name, valence); } if (!info.ions.empty()) { printer->add_newline(); diff --git a/test/nmodl/transpiler/usecases/useion/test_valence.py b/test/nmodl/transpiler/usecases/useion/test_valence.py new file mode 100644 index 0000000000..d24611f980 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/test_valence.py @@ -0,0 +1,9 @@ +from neuron import h + + +def test_valence(): + assert h.ion_charge("K_ion") == 222.0 + + +if __name__ == "__main__": + test_valence() diff --git a/test/nmodl/transpiler/usecases/useion/valence.mod b/test/nmodl/transpiler/usecases/useion/valence.mod new file mode 100644 index 0000000000..6ca8b857f0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/useion/valence.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX valence_mod + USEION K READ Ki VALENCE 222 + RANGE x +} + +ASSIGNED { + x + Ki +} + +INITIAL { + x = Ki +} From 3c027a1eeaca6085e3454aeda809eb20cd62ac98 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 15 Jul 2024 10:28:13 +0200 Subject: [PATCH 691/871] Fix bug in TABLE for POINT_PROCESS. (BlueBrain/nmodl#1342) * Regression test. * Improve update_table name. Change the name to `update_table_*`. To avoid inconsistencies in the name (and it's suffix) we introduce a function `table_update_function_name`. Fixes a bug related to incorrect use of `rsuffix`, which caused the function name to be inconsistent for POINT_PROCESSES. NMODL Repo SHA: BlueBrain/nmodl@2387ba72e61400d22cea66cc663f59134a5969a8 --- .../codegen_coreneuron_cpp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cpp_visitor.cpp | 9 ++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 8 +-- .../codegen/codegen_neuron_cpp_visitor.cpp | 17 +++--- .../transpiler/usecases/table/simulate.py | 53 +++++++++++++------ .../usecases/table/table_point_process.mod | 44 +++++++++++++++ 6 files changed, 97 insertions(+), 36 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/table/table_point_process.mod diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 2735d30000..74581bbaa2 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -418,7 +418,7 @@ void CodegenCoreneuronCppVisitor::print_check_table_thread_function() { printer->add_line("double v = 0;"); for (const auto& function: info.functions_with_table) { - auto method_name_str = method_name(table_function_prefix() + function->get_node_name()); + auto method_name_str = table_update_function_name(function->get_node_name()); auto arguments = internal_method_arguments(); printer->fmt_line("{}({});", method_name_str, arguments); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index e87e6aa534..611ff26b47 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -72,8 +72,8 @@ bool CodegenCppVisitor::has_parameter_of_name(const T& node, const std::string& } -std::string CodegenCppVisitor::table_function_prefix() const { - return "lazy_update_"; +std::string CodegenCppVisitor::table_update_function_name(const std::string& block_name) const { + return "update_table_" + method_name(block_name); } @@ -1524,9 +1524,8 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { auto float_type = default_float_data_type(); printer->add_newline(2); - printer->fmt_push_block("void {}{}({})", - table_function_prefix(), - method_name(name), + printer->fmt_push_block("void {}({})", + table_update_function_name(name), get_parameter_str(internal_params)); { printer->fmt_push_block("if ({} == 0)", use_table_var); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 875587acd8..8a1415c232 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1112,10 +1112,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { bool use_instance = true) const = 0; /** - * Prefix used for the function that performs the lazy update + * The name of the function that updates the table value if the parameters + * changed. + * + * \param block_name The name of the block that contains the TABLE. */ - std::string table_function_prefix() const; - + std::string table_update_function_name(const std::string& block_name) const; /** * Return ion variable name and corresponding ion read variable name. diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index cacaf6dcc7..9b8ddbba24 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -165,9 +165,8 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { for (const auto& function: info.functions_with_table) { auto name = function->get_node_name(); auto internal_params = internal_method_parameters(); - printer->fmt_line("void {}{}({});", - table_function_prefix(), - method_name(name), + printer->fmt_line("void {}({});", + table_update_function_name(name), get_parameter_str(internal_params)); } @@ -193,10 +192,9 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { } for (const auto& function: info.functions_with_table) { - auto method_name_str = function->get_node_name(); - auto method_args_str = get_arg_str(internal_method_parameters()); - printer->fmt_line( - "{}{}{}({});", table_function_prefix(), method_name_str, info.rsuffix, method_args_str); + auto method_name = function->get_node_name(); + auto method_args = get_arg_str(internal_method_parameters()); + printer->fmt_line("{}({});", table_update_function_name(method_name), method_args); } printer->pop_block(); } @@ -404,9 +402,8 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( info.thread_var_thread_id); } if (info.function_uses_table(block_name)) { - printer->fmt_line("{}{}({});", - table_function_prefix(), - method_name(block_name), + printer->fmt_line("{}({});", + table_update_function_name(block_name), internal_method_arguments()); } const auto get_func_call_str = [&]() { diff --git a/test/nmodl/transpiler/usecases/table/simulate.py b/test/nmodl/transpiler/usecases/table/simulate.py index a4aa5e3978..190e4ff288 100644 --- a/test/nmodl/transpiler/usecases/table/simulate.py +++ b/test/nmodl/transpiler/usecases/table/simulate.py @@ -14,14 +14,14 @@ def check_solution(y_no_table, y_table, rtol): ), f"{y_no_table} == {y_table}" -def check_table(c1, c2, x, evaluate_table): - h.c1_tbl = 1 - h.c2_tbl = 2 +def check_table(c1, c2, x, mech_name, evaluate_table): + setattr(h, f"c1_{mech_name}", 1) + setattr(h, f"c2_{mech_name}", 2) - h.usetable_tbl = 0 + setattr(h, f"usetable_{mech_name}", 0) y_no_table = np.array([evaluate_table(i) for i in x]) - h.usetable_tbl = 1 + setattr(h, f"usetable_{mech_name}", 1) y_table = np.array([evaluate_table(i) for i in x]) check_solution(y_table, y_no_table, rtol=1e-4) @@ -31,34 +31,52 @@ def check_table(c1, c2, x, evaluate_table): assert np.all(evaluate_table(x[-1] + 10) == y_table[-1]) -def test_function(): +def check_function(mech_name, make_instance): s = h.Section() - s.insert("tbl") + obj = make_instance(s) x = np.linspace(-3, 5, 18) assert x[0] == -3.0 assert x[-1] == 5.0 - check_table(1, 2, x, s(0.5).tbl.quadratic) - check_table(2, 2, x, s(0.5).tbl.quadratic) - check_table(2, 3, x, s(0.5).tbl.quadratic) + check_table(1, 2, x, mech_name, obj.quadratic) + check_table(2, 2, x, mech_name, obj.quadratic) + check_table(2, 3, x, mech_name, obj.quadratic) -def test_procedure(): - s = h.Section() +def make_density_instance(s): s.insert("tbl") + return s(0.5).tbl + + +def make_point_instance(s): + return h.tbl_point_process(s(0.5)) + + +def test_function(): + check_function("tbl", make_density_instance) + check_function("tbl_point_process", make_point_instance) + + +def check_procedure(mech_name, make_instance): + s = h.Section() + obj = make_instance(s) def evaluate_table(x): - s(0.5).tbl.sinusoidal(x) - return np.array((s(0.5).tbl.v1, s(0.5).tbl.v2)) + obj.sinusoidal(x) + return np.array((obj.v1, obj.v2)) x = np.linspace(-4, 6, 18) assert x[0] == -4.0 assert x[-1] == 6.0 - check_table(1, 2, x, evaluate_table) - check_table(2, 2, x, evaluate_table) - check_table(2, 3, x, evaluate_table) + check_table(1, 2, x, mech_name, evaluate_table) + check_table(2, 2, x, mech_name, evaluate_table) + check_table(2, 3, x, mech_name, evaluate_table) + + +def test_procedure(): + check_procedure("tbl", make_density_instance) def simulate(): @@ -83,4 +101,5 @@ def simulate(): if __name__ == "__main__": test_function() test_procedure() + simulate() diff --git a/test/nmodl/transpiler/usecases/table/table_point_process.mod b/test/nmodl/transpiler/usecases/table/table_point_process.mod new file mode 100644 index 0000000000..50da1aeb1d --- /dev/null +++ b/test/nmodl/transpiler/usecases/table/table_point_process.mod @@ -0,0 +1,44 @@ +NEURON { + POINT_PROCESS tbl_point_process + NONSPECIFIC_CURRENT i + RANGE g, v1, v2 + GLOBAL k, d, c1, c2 +} + +PARAMETER { + k = .1 + d = -50 + c1 = 1 + c2 = 2 +} + +ASSIGNED { + g + i + v + sig + v1 + v2 +} + +BREAKPOINT { + sigmoidal(v) + g = 0.001 * sig + i = g*(v - 30.0) +} + +PROCEDURE sigmoidal(v) { + TABLE sig DEPEND k, d FROM -127 TO 128 WITH 155 + sig = 1/(1 + exp(k*(v - d))) +} + +FUNCTION quadratic(x) { + TABLE DEPEND c1, c2 FROM -3 TO 5 WITH 500 + quadratic = c1 * x * x + c2 +} + +PROCEDURE sinusoidal(x) { + TABLE v1, v2 DEPEND c1, c2 FROM -4 TO 6 WITH 800 + v1 = sin(c1 * x) + 2 + v2 = cos(c2 * x) + 2 +} From 36c036f0087fcbf6f64c4eff5cfa3c8d94ab58c1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 15 Jul 2024 10:28:27 +0200 Subject: [PATCH 692/871] Fix FUNCTION, PROCEDURE with Newton. (BlueBrain/nmodl#1343) NMODL Repo SHA: BlueBrain/nmodl@094a6abfb42e711e3b5b1ba578fca9727e07e8c4 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 9 +-------- test/nmodl/transpiler/usecases/kinetic/X2Y.mod | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 9b8ddbba24..f1e25affea 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -829,15 +829,8 @@ void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_ini } CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::functor_params() { - auto params = ParamVector{}; - params.push_back({"", "NrnThread*", "", "nt"}); - params.push_back({"", fmt::format("{}&", instance_struct()), "", "inst"}); - params.push_back({"", "int", "", "id"}); + auto params = internal_method_parameters(); params.push_back({"", "double", "", "v"}); - params.push_back({"", "Datum*", "", "_thread"}); - if (!codegen_thread_variables.empty()) { - params.push_back({"", fmt::format("{}&", thread_variables_struct()), "", "_thread_vars"}); - } return params; } diff --git a/test/nmodl/transpiler/usecases/kinetic/X2Y.mod b/test/nmodl/transpiler/usecases/kinetic/X2Y.mod index 5ef3f853ac..5d9c72a49a 100644 --- a/test/nmodl/transpiler/usecases/kinetic/X2Y.mod +++ b/test/nmodl/transpiler/usecases/kinetic/X2Y.mod @@ -1,6 +1,6 @@ NEURON { SUFFIX X2Y - RANGE X, Y + RANGE X, Y, c1, c2 NONSPECIFIC_CURRENT il } @@ -12,6 +12,8 @@ STATE { ASSIGNED { il i + c1 + c2 } BREAKPOINT { @@ -22,9 +24,17 @@ BREAKPOINT { INITIAL { X = 0 Y = 1 + c1 = 0.0 + c2 = 0.0 } KINETIC state { - ~ X <-> Y (0.4, 0.5) + rates() + ~ X <-> Y (c1, c2) i = (f_flux-b_flux) } + +PROCEDURE rates() { + c1 = 0.4 + c2 = 0.5 +} From e0672d35b2aaee9aa0a77472df9f0a1e317d6e7c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 15 Jul 2024 17:34:53 +0200 Subject: [PATCH 693/871] Conditionally print Newton boiler plate. (BlueBrain/nmodl#1345) NMODL Repo SHA: BlueBrain/nmodl@0193a99b58ada3afa5b211190fb55ee7c754c88c --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index f1e25affea..444813c209 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -768,10 +768,10 @@ void CodegenNeuronCppVisitor::print_standard_includes() { #include #include #include + #include )CODE"); - printer->add_multi_line(nmodl::solvers::newton_hpp); - if (!info.vectorize) { - printer->add_line("#include "); + if (info.eigen_newton_solver_exist) { + printer->add_multi_line(nmodl::solvers::newton_hpp); } } From 672fa3cb8ffd1d10744bbc23e36b9384b35cab29 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 16 Jul 2024 16:26:11 +0200 Subject: [PATCH 694/871] Fix RANGE PARAMETER defaults. (BlueBrain/nmodl#1346) * Add documentation of PARAMETER. * Fix RANGE PARAMETER defaults. PARAMETERs that are RANGE variables need to register their default values, because NEURON needs to be able to initialize the parameters with their default values. This commits adds the registration code, and sets defaults in `nrn_alloc` from the same array. * Add units®ression tests. NMODL Repo SHA: BlueBrain/nmodl@dd9a52d5cb64eab4659f7d58a16fc96a4249b972 --- docs/nmodl/transpiler/contents/globals.rst | 25 +++++++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 22 +++++++- .../codegen/codegen_neuron_cpp_visitor.hpp | 3 + .../usecases/parameter/default_parameter.mod | 14 +++++ .../usecases/parameter/test_default.py | 55 +++++++++++++++++++ 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/parameter/default_parameter.mod create mode 100644 test/nmodl/transpiler/usecases/parameter/test_default.py diff --git a/docs/nmodl/transpiler/contents/globals.rst b/docs/nmodl/transpiler/contents/globals.rst index ce3fa3247b..3825d8ba63 100644 --- a/docs/nmodl/transpiler/contents/globals.rst +++ b/docs/nmodl/transpiler/contents/globals.rst @@ -176,6 +176,31 @@ Collection of slightly surprising behaviour: the code ``nocmodl`` produces will cause a SEGFAULT. +PARAMETER variables +=================== + +These can be either RANGE or not RANGE. They can be both read and write. If +they're written to, they're converted to thread-variables. Therefore, the rest +of this section will only describe read-only PARAMETERs. + +Additionally, parameters optionally have: a) a default value, b) units and c) a +valid range. + +Default Values +~~~~~~~~~~~~~~ +This section only applies to read-only PARAMETERs. + +The behaviour differs for RANGE variables and non-RANGE variables. For RANGE +variables, default values need to be registered with NEURON for all PARAMETERs +that are RANGE variables. The function is called ``hoc_register_parm_default``. + +Note, that NOCMODL uses it in the ``nrn_alloc`` in the generated `.cpp` files +and also in ``ndatclas.cpp``. Therefore, it seems registering the values isn't +optional. + +Non-RANGE variables are semantically equivalent to ``static double``. They're +simply assigned their value in the definition of the global variable. + CONSTANT variables ================== diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 444813c209..bea1f104e2 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -997,6 +997,21 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in print_global_var_struct_assertions(); print_global_var_struct_decl(); + + print_global_param_default_values(); +} + +void CodegenNeuronCppVisitor::print_global_param_default_values() { + printer->push_block("static std::vector _parameter_defaults ="); + + std::vector defaults; + for (const auto& p: info.range_parameter_vars) { + double value = p->get_value() == nullptr ? 0.0 : *p->get_value(); + defaults.push_back(fmt::format("{:g} /* {} */", value, p->get_name())); + } + + printer->add_multi_line(fmt::format("{}", fmt::join(defaults, ",\n"))); + printer->pop_block(";"); } /// TODO: Same as CoreNEURON? @@ -1170,6 +1185,8 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(); printer->fmt_line("mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); + printer->add_line("hoc_register_parm_default(mech_type, &_parameter_defaults);"); + // register the table-checking function if (info.table_count > 0) { printer->fmt_line("_nrn_thread_table_reg(mech_type, {});", table_thread_function_name()); @@ -1617,7 +1634,8 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { codegen_float_variables.size()); if (float_variables_size()) { printer->add_line("/*initialize range parameters*/"); - for (const auto& var: info.range_parameter_vars) { + for (size_t i_param = 0; i_param < info.range_parameter_vars.size(); ++i_param) { + const auto var = info.range_parameter_vars[i_param]; if (var->is_array()) { continue; } @@ -1627,7 +1645,7 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->fmt_line("_lmc.template fpfield<{}>(_iml) = {}; /* {} */", var_pos, - var_value, + fmt::format("_parameter_defaults[{}]", i_param), var_name); } } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 7cac67c2f9..f23aa9b1cc 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -470,6 +470,9 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_global_variables_for_hoc() override; + /** Print global struct with default value of RANGE PARAMETERs. + */ + void print_global_param_default_values(); /** * Print the mechanism registration function diff --git a/test/nmodl/transpiler/usecases/parameter/default_parameter.mod b/test/nmodl/transpiler/usecases/parameter/default_parameter.mod new file mode 100644 index 0000000000..d31d91aa0a --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/default_parameter.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX default_parameter + RANGE x, y, z + GLOBAL a +} + +PARAMETER { + a + b = 0.1 + + x + y = 2.1 + z +} diff --git a/test/nmodl/transpiler/usecases/parameter/test_default.py b/test/nmodl/transpiler/usecases/parameter/test_default.py new file mode 100644 index 0000000000..400f533d65 --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/test_default.py @@ -0,0 +1,55 @@ +from neuron import h + + +def check_defaults(obj, parameters): + defaults = { + "b": 0.1, + "y": 2.1, + } + + for p in parameters: + actual = getattr(obj, f"{p}_default_parameter") + expected = defaults.get(p, 0.0) + assert actual == expected + + +def test_default_values(): + nseg = 10000 + + s = h.Section() + s.nseg = nseg + s.insert("default_parameter") + + static_parameters = ["a", "b"] + range_parameters = ["x", "y", "z"] + + check_defaults(h, static_parameters) + for seg in s: + check_defaults(seg, range_parameters) + + h.finitialize() + + check_defaults(h, static_parameters) + for seg in s: + check_defaults(seg, range_parameters) + + +def test_parcom_regression(): + # This is a roundabout way of triggering a codepath leading to + # NrnProperyImpl. In NrnProperyImpl we iterate over the std::vector of + # default values. Hence, it requires that the default values are registered + # correctly. + + nseg = 5 + + s = h.Section() + s.nseg = nseg + s.insert("default_parameter") + + # Caused a SEGFAULT: + h.load_file("parcom.hoc") + + +if __name__ == "__main__": + test_default_values() + test_parcom_regression() From e34442042133ce80c52649ae51aadae708e44345 Mon Sep 17 00:00:00 2001 From: Pramod Kumbhar Date: Tue, 16 Jul 2024 18:03:14 +0200 Subject: [PATCH 695/871] Remove unnecessary warning messages (BlueBrain/nmodl#1350) - Looking at the typical warnings produced by NMODL, two that I find spurious: * Defining a variable of the same name in a new scope ("shadowing"). * Unable to inline a function or procedure. - Both of these should not be warnings. The first is perfectly acceptable, and the second can occur in cases where TABLE is used. Emitting these warnings is more misleading than helpful IMO. - We are converting these two warnings to debug level messages as it might be helpful to know these situations while debugging. - An example from the Snudda model: ``` [NMODL] [warning] :: SYMTAB :: ca [Argument] in rate shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: ca [Argument] in rate shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: cai [Argument] in h2 shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: celsius [Argument] in KTF shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: cai [Argument] in h2 shadows definition in NMODL_GLOBAL [NMODL] [warning] :: Can not inline function call to alp [NMODL] [warning] :: Can not inline function call to alp [NMODL] [warning] :: Can not inline function call to exptable [NMODL] [warning] :: Can not inline function call to exptable [NMODL] [warning] :: Can not inline function call to exptable [NMODL] [warning] :: SYMTAB :: t [Argument] in init_sequence shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: ca [Argument] in rate shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: ca [Argument] in rate shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: ca [Argument] in rate shadows definition in NMODL_GLOBAL [NMODL] [warning] :: CVode solver of icur in 64.20-30 replaced with cnexp solver [NMODL] [warning] :: SYMTAB :: mggate [LocalVar] in StatementBlock1 shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: mggate [LocalVar] in StatementBlock18 shadows definition in NMODL_GLOBAL [NMODL] [warning] :: SYMTAB :: itot_nmda [LocalVar] in StatementBlock1 shadows definition in NMODL_GLOBAL ... ``` - Use fmt instead of string concatenation (BlueBrain/nmodl#1351) Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@84744113efb93294f83cbd9660d52da98b75426f --- src/nmodl/symtab/symbol_table.cpp | 23 ++++++++++++----------- src/nmodl/visitors/inline_visitor.cpp | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 638141f082..06dfab5a53 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -192,10 +192,7 @@ std::shared_ptr ModelSymbolTable::lookup(const std::string& name) { } -/** - * Emit warning message for shadowing definition or throw an exception - * if variable is being redefined in the same block. - */ +// show symbol table related message for debugging purposes void ModelSymbolTable::emit_message(const std::shared_ptr& first, const std::shared_ptr& second, bool redefinition) { @@ -209,15 +206,19 @@ void ModelSymbolTable::emit_message(const std::shared_ptr& first, } if (redefinition) { - std::string msg = "Re-declaration of " + name + " [" + type + "]"; - msg += "<" + properties + "> in " + current_symtab->name(); - msg += " with one in " + second->get_scope(); + auto msg = fmt::format("Re-declaration of {} [{}] <{}> in {} with one in {}", + name, + type, + properties, + current_symtab->name(), + second->get_scope()); throw std::runtime_error(msg); } - std::string msg = "SYMTAB :: " + name + " [" + type + "] in "; - msg += current_symtab->name() + " shadows <" + properties; - msg += "> definition in " + second->get_scope(); - logger->warn(msg); + logger->debug("SYMTAB :: {} [{}] in {} shadows <{}> definition in {}", + name, + type, + current_symtab->name(), + properties); } diff --git a/src/nmodl/visitors/inline_visitor.cpp b/src/nmodl/visitors/inline_visitor.cpp index 1dd35629f2..5678395eef 100644 --- a/src/nmodl/visitors/inline_visitor.cpp +++ b/src/nmodl/visitors/inline_visitor.cpp @@ -132,7 +132,7 @@ bool InlineVisitor::inline_function_call(ast::Block& callee, /// do nothing if we can't inline given procedure/function if (!can_inline_block(*callee.get_statement_block())) { - logger->warn("Can not inline function call to {}", function_name); + logger->debug("Can not inline function call to {}", function_name); return false; } From de78b61a052617b667da0f967cccb668d5f9c3c1 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 16 Jul 2024 20:09:39 +0200 Subject: [PATCH 696/871] Remove usage of `unistd` from Flex (BlueBrain/nmodl#1348) NMODL Repo SHA: BlueBrain/nmodl@9b59909e0eaeb8fef77002ee471bba7e7b05f9d2 --- src/nmodl/lexer/verbatim.l | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nmodl/lexer/verbatim.l b/src/nmodl/lexer/verbatim.l index d2e13764cb..79e1fba5ed 100755 --- a/src/nmodl/lexer/verbatim.l +++ b/src/nmodl/lexer/verbatim.l @@ -49,6 +49,9 @@ */ %option yymore +%option nounistd +%option never-interactive + /** lexer header file */ %option header-file="verbatim_lexer.hpp" From 90803960577220b0635845de91e20cf9fc3c1e49 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 17 Jul 2024 10:13:53 +0200 Subject: [PATCH 697/871] Simplify internal code. (BlueBrain/nmodl#1347) NMODL Repo SHA: BlueBrain/nmodl@8052b40cca7061377d607f0db985bec20b3a3412 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index bea1f104e2..bf1f396251 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1014,27 +1014,23 @@ void CodegenNeuronCppVisitor::print_global_param_default_values() { printer->pop_block(";"); } -/// TODO: Same as CoreNEURON? void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { - /// TODO: Write HocParmLimits and other HOC global variables (delta_t) - // Probably needs more changes - auto variable_printer = - [&](const std::vector& variables, bool if_array, bool if_vector) { - for (const auto& variable: variables) { - if (variable->is_array() == if_array) { - // false => do not use the instance struct, which is not - // defined in the global declaration that we are printing - auto name = get_variable_name(variable->get_name(), false); - auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); + auto variable_printer = [&](const std::vector& variables, bool if_array) { + for (const auto& variable: variables) { + if (variable->is_array() == if_array) { + // false => do not use the instance struct, which is not + // defined in the global declaration that we are printing + auto name = get_variable_name(variable->get_name(), false); + auto ename = add_escape_quote(variable->get_name() + "_" + info.mod_suffix); + if (if_array) { auto length = variable->get_length(); - if (if_vector) { - printer->fmt_line("{{{}, {}, {}}},", ename, name, length); - } else { - printer->fmt_line("{{{}, &{}}},", ename, name); - } + printer->fmt_line("{{{}, {}, {}}},", ename, name, length); + } else { + printer->fmt_line("{{{}, &{}}},", ename, name); } } - }; + } + }; auto globals = info.global_variables; auto thread_vars = info.thread_variables; @@ -1047,8 +1043,8 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_line("/** connect global (scalar) variables to hoc -- */"); printer->add_line("static DoubScal hoc_scalar_double[] = {"); printer->increase_indent(); - variable_printer(globals, false, false); - variable_printer(thread_vars, false, false); + variable_printer(globals, false); + variable_printer(thread_vars, false); printer->add_line("{nullptr, nullptr}"); printer->decrease_indent(); printer->add_line("};"); @@ -1057,8 +1053,8 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_line("/** connect global (array) variables to hoc -- */"); printer->add_line("static DoubVec hoc_vector_double[] = {"); printer->increase_indent(); - variable_printer(globals, true, true); - variable_printer(thread_vars, true, true); + variable_printer(globals, true); + variable_printer(thread_vars, true); printer->add_line("{nullptr, nullptr, 0}"); printer->decrease_indent(); printer->add_line("};"); From 3e76e5850ce483939f3d6795c43c13941b80bf34 Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Wed, 17 Jul 2024 23:04:08 +0200 Subject: [PATCH 698/871] Smaller changes for MSVC compatibility. (BlueBrain/nmodl#1355) - These fixes come from BlueBrain/nmodl#1354, and should not be controversial. - Generated sources are already split into dir, filename NMODL Repo SHA: BlueBrain/nmodl@4f47074140fb810a09847c3879c9566c57e4abd9 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/language/code_generator.py | 2 +- src/nmodl/language/templates/code_generator.cmake | 6 +++--- src/nmodl/parser/diffeq_context.cpp | 2 +- src/nmodl/visitors/inline_visitor.hpp | 1 + src/nmodl/visitors/local_var_rename_visitor.hpp | 1 + src/nmodl/visitors/localize_visitor.hpp | 1 + src/nmodl/visitors/sympy_conductance_visitor.hpp | 1 + 8 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 611ff26b47..9026488668 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1353,7 +1353,7 @@ void CodegenCppVisitor::setup(const Program& node) { info.mod_file = mod_filename; if (info.mod_suffix == "") { - info.mod_suffix = std::filesystem::path(mod_filename).stem(); + info.mod_suffix = std::filesystem::path(mod_filename).stem().string(); } info.rsuffix = info.point_process ? "" : "_" + info.mod_suffix; diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index e160039ed8..23bf8ed1dc 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -110,7 +110,7 @@ def jinja_template(self, path): Returns: A jinja template object """ - name = str(path.relative_to(self.jinja_templates_dir)) + name = str(path.relative_to(self.jinja_templates_dir).as_posix()) return self.jinja_env.get_template(name) def _cmake_deps_task(self, tasks): diff --git a/src/nmodl/language/templates/code_generator.cmake b/src/nmodl/language/templates/code_generator.cmake index 4e462fe48d..45d150bc19 100644 --- a/src/nmodl/language/templates/code_generator.cmake +++ b/src/nmodl/language/templates/code_generator.cmake @@ -5,19 +5,19 @@ # cmake-format: off set(CODE_GENERATOR_JINJA_FILES {% for template in templates | sort %} - ${PROJECT_SOURCE_DIR}/src/language/templates/{{ template }} + ${PROJECT_SOURCE_DIR}/src/language/templates/{{ template.as_posix() }} {% endfor %} ) set(CODE_GENERATOR_PY_FILES {% for file in py_files | sort %} - ${PROJECT_SOURCE_DIR}/src/language/{{ file }} + ${PROJECT_SOURCE_DIR}/src/language/{{ file.as_posix() }} {% endfor %} ) set(CODE_GENERATOR_YAML_FILES {% for file in yaml_files | sort %} - ${PROJECT_SOURCE_DIR}/src/language/{{ file }} + ${PROJECT_SOURCE_DIR}/src/language/{{ file.as_posix() }} {% endfor %} ) diff --git a/src/nmodl/parser/diffeq_context.cpp b/src/nmodl/parser/diffeq_context.cpp index 298b2185d9..24dbbd8c7b 100644 --- a/src/nmodl/parser/diffeq_context.cpp +++ b/src/nmodl/parser/diffeq_context.cpp @@ -151,7 +151,7 @@ std::string DiffEqContext::get_non_cnexp_solution() const { std::string result; if (!deriv_invalid) { result = get_cvode_linear_diffeq(); - } else if (deriv_invalid and eqn_invalid) { + } else if (deriv_invalid && eqn_invalid) { result = get_cvode_nonlinear_diffeq(); } else { throw std::runtime_error("Error in differential equation solver with non-cnexp"); diff --git a/src/nmodl/visitors/inline_visitor.hpp b/src/nmodl/visitors/inline_visitor.hpp index 9a728da04b..e934b31596 100644 --- a/src/nmodl/visitors/inline_visitor.hpp +++ b/src/nmodl/visitors/inline_visitor.hpp @@ -14,6 +14,7 @@ #include #include +#include #include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/visitors/local_var_rename_visitor.hpp b/src/nmodl/visitors/local_var_rename_visitor.hpp index a73196e194..4b46953e99 100644 --- a/src/nmodl/visitors/local_var_rename_visitor.hpp +++ b/src/nmodl/visitors/local_var_rename_visitor.hpp @@ -14,6 +14,7 @@ #include #include +#include #include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/visitors/localize_visitor.hpp b/src/nmodl/visitors/localize_visitor.hpp index 821e072fe2..4dcd998398 100644 --- a/src/nmodl/visitors/localize_visitor.hpp +++ b/src/nmodl/visitors/localize_visitor.hpp @@ -13,6 +13,7 @@ */ #include +#include #include "symtab/decl.hpp" #include "visitors/ast_visitor.hpp" diff --git a/src/nmodl/visitors/sympy_conductance_visitor.hpp b/src/nmodl/visitors/sympy_conductance_visitor.hpp index cb76bdf2fd..d217320298 100644 --- a/src/nmodl/visitors/sympy_conductance_visitor.hpp +++ b/src/nmodl/visitors/sympy_conductance_visitor.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include "visitors/ast_visitor.hpp" From 6dc117de6dc0c01958712f6f571bc92174cbe82a Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Fri, 19 Jul 2024 10:05:13 +0200 Subject: [PATCH 699/871] Implement CI to check for STL misuse (BlueBrain/nmodl#1358) Compiles and runs NMODL with glibc debug macros enabled to check the inputs to STL algorithms etc. NMODL Repo SHA: BlueBrain/nmodl@38f90fff816e8124a42f16275fbd1add0fa5181a --- src/nmodl/visitors/semantic_analysis_visitor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index 6d005e1389..a2f8526646 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -56,14 +56,14 @@ bool SemanticAnalysisVisitor::check_table_vars(const ast::Program& node) { bool SemanticAnalysisVisitor::check_name_conflict(const ast::Program& node) { // check that there are no RANGE variables which have the same name as a FUNCTION or PROCEDURE const auto& range_nodes = collect_nodes(node, {ast::AstNodeType::RANGE_VAR}); - std::unordered_set range_vars{}; + std::set range_vars{}; for (const auto& range_node: range_nodes) { range_vars.insert(range_node->get_node_name()); } const auto& function_nodes = collect_nodes(node, {ast::AstNodeType::FUNCTION_BLOCK, ast::AstNodeType::PROCEDURE_BLOCK}); - std::unordered_set func_vars{}; + std::set func_vars{}; for (const auto& function_node: function_nodes) { func_vars.insert(function_node->get_node_name()); } From eeeacdd84155206b2cbcca51b638767bf9bb9d54 Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Mon, 22 Jul 2024 08:38:23 +0200 Subject: [PATCH 700/871] Provide both end() iterators to std::mismatch to prevent seeking beyond the end of std::string (BlueBrain/nmodl#1357) Became apparent with MSVC, which in this case crashes hard comparing `it.first = "yotta"` and `denomitator_name = "sec2"`. NMODL Repo SHA: BlueBrain/nmodl@9ae0783dadd5cef8bd6b0f7b50d7498e1ab086e9 --- src/nmodl/units/units.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index 5f1465ab23..c1f391bfa1 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -132,7 +132,10 @@ void UnitTable::calc_nominator_dims(const std::shared_ptr& unit, std::stri while (changed_nominator_name) { changed_nominator_name = 0; for (const auto& it: prefixes) { - auto res = std::mismatch(it.first.begin(), it.first.end(), nominator_name.begin()); + auto res = std::mismatch(it.first.begin(), + it.first.end(), + nominator_name.begin(), + nominator_name.end()); if (res.first == it.first.end()) { changed_nominator_name = 1; nominator_prefix_factor *= it.second; @@ -207,8 +210,10 @@ void UnitTable::calc_denominator_dims(const std::shared_ptr& unit, while (changed_denominator_name) { changed_denominator_name = false; for (const auto& it: prefixes) { - auto res = - std::mismatch(it.first.begin(), it.first.end(), denominator_name.begin()); + auto res = std::mismatch(it.first.begin(), + it.first.end(), + denominator_name.begin(), + denominator_name.end()); if (res.first == it.first.end()) { changed_denominator_name = true; denominator_prefix_factor *= it.second; From 566856743778145f684ec1c4bc3f4f56a53c5dde Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 22 Jul 2024 13:38:28 +0200 Subject: [PATCH 701/871] Accept negative number for limits (BlueBrain/nmodl#1352) * Accept negative number for limits * Add regression test. --------- Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@3f8da3a52d60ba91fc39bf4eded70f97049f4693 --- src/nmodl/language/nmodl.yaml | 4 ++-- src/nmodl/parser/nmodl.yy | 2 +- .../nmodl/transpiler/usecases/parameter/limits.mod | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/parameter/limits.mod diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 60919df1b1..7895ba1c18 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1027,12 +1027,12 @@ members: - min: brief: "TODO" - type: Double + type: Number prefix: {value: "<"} suffix: {value: ","} - max: brief: "TODO" - type: Double + type: Number suffix: {value: ">"} - NumberRange: diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index c07bd961a3..8d910e0ca2 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -588,7 +588,7 @@ unit_state : UNITSON limits : { $$ = nullptr; } - | LT double "," double GT + | LT number "," number GT { $$ = new ast::Limits($2, $4); } diff --git a/test/nmodl/transpiler/usecases/parameter/limits.mod b/test/nmodl/transpiler/usecases/parameter/limits.mod new file mode 100644 index 0000000000..23bd6e2dd7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/parameter/limits.mod @@ -0,0 +1,14 @@ +: Only test if all of these result in compilable code. + +NEURON { + SUFFIX limits_mod +} + +PARAMETER { + a = 24. (mV) <-2, 3> + b = 24. (mV) <-2.1, 3> + c = 24. (mV) <-4.1,-3> + x = 24. (mV) < -2, 3> + y = 24. (mV) < -2.1, 3> + z = 24. (mV) < -4.1, -3> +} From d4c87ec320aacc3db3a94cfee629fbff8e9b09d4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 22 Jul 2024 14:55:52 +0200 Subject: [PATCH 702/871] Deduplicate integer magic code. (BlueBrain/nmodl#1362) * Deduplicate code for finding the index given the name. * Deduplicate computing prefixsum. * Deduplicate `get_int_variable_index`. NMODL Repo SHA: BlueBrain/nmodl@7face1d5fdc8d5e109d44cea15e27b7d44156fc9 --- .../codegen_coreneuron_cpp_visitor.cpp | 18 +------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 12 +----- src/nmodl/codegen/codegen_cpp_visitor.hpp | 41 +++++++++++++++++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 21 +--------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 74581bbaa2..b477a58912 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -66,26 +66,12 @@ std::string CodegenCoreneuronCppVisitor::simulator_name() { int CodegenCoreneuronCppVisitor::position_of_float_var(const std::string& name) const { - int index = 0; - for (const auto& var: codegen_float_variables) { - if (var->get_name() == name) { - return index; - } - index += var->get_length(); - } - throw std::logic_error(name + " variable not found"); + return get_prefixsum_from_name(codegen_float_variables, name); } int CodegenCoreneuronCppVisitor::position_of_int_var(const std::string& name) const { - int index = 0; - for (const auto& var: codegen_int_variables) { - if (var.symbol->get_name() == name) { - return index; - } - index += var.symbol->get_length(); - } - throw std::logic_error(name + " variable not found"); + return get_prefixsum_from_name(codegen_int_variables, name); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 9026488668..63eb9f3bcf 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -404,17 +404,7 @@ std::pair CodegenCppVisitor::write_ion_variable_name( int CodegenCppVisitor::get_int_variable_index(const std::string& var_name) { - auto it = std::find_if(codegen_int_variables.cbegin(), - codegen_int_variables.cend(), - [&var_name](const auto& int_var) { - return int_var.symbol->get_name() == var_name; - }); - - if (it == codegen_int_variables.cend()) { - throw std::runtime_error(fmt::format("Unknown int variable: {}", var_name)); - } - - return static_cast(it - codegen_int_variables.cbegin()); + return get_index_from_name(codegen_int_variables, var_name); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 8a1415c232..7c24a3f6db 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -169,6 +169,47 @@ struct ShadowUseStatement { std::string rhs; }; +inline std::string get_name(const std::shared_ptr& sym) { + return sym->get_name(); +} + +inline std::string get_name(const IndexVariableInfo& var) { + return var.symbol->get_name(); +} + +template +int get_index_from_name(const std::vector& variables, const std::string& name) { + auto it = std::find_if(variables.cbegin(), variables.cend(), [&name](const auto& var) { + return get_name(var) == name; + }); + + if (it == variables.cend()) { + throw std::runtime_error(fmt::format("Unknown variable: {}", name)); + } + + return static_cast(it - variables.cbegin()); +} + +inline int get_length(const std::shared_ptr& sym) { + return sym->get_length(); +} + +inline int get_length(const IndexVariableInfo& var) { + return var.symbol->get_length(); +} + +template +int get_prefixsum_from_name(const std::vector& variables, const std::string& name) { + int index = 0; + for (const auto& var: variables) { + if (get_name(var) == name) { + return index; + } + index += get_length(var); + } + throw std::logic_error(name + " variable not found"); +} + /** \} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index bf1f396251..fc7a3d69e7 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -61,30 +61,13 @@ std::string CodegenNeuronCppVisitor::table_thread_function_name() const { /* Common helper routines accross codegen functions */ /****************************************************************************************/ - int CodegenNeuronCppVisitor::position_of_float_var(const std::string& name) const { - const auto has_name = [&name](const SymbolType& symbol) { return symbol->get_name() == name; }; - const auto var_iter = - std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), has_name); - if (var_iter != codegen_float_variables.end()) { - return var_iter - codegen_float_variables.begin(); - } else { - throw std::logic_error(name + " variable not found"); - } + return get_index_from_name(codegen_float_variables, name); } int CodegenNeuronCppVisitor::position_of_int_var(const std::string& name) const { - const auto has_name = [&name](const IndexVariableInfo& index_var_symbol) { - return index_var_symbol.symbol->get_name() == name; - }; - const auto var_iter = - std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), has_name); - if (var_iter != codegen_int_variables.end()) { - return var_iter - codegen_int_variables.begin(); - } else { - throw std::logic_error(name + " variable not found"); - } + return get_index_from_name(codegen_int_variables, name); } From 16c9f1e6d3ead7f2490ea63c9b7da55d9aa0bab5 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 22 Jul 2024 14:56:28 +0200 Subject: [PATCH 703/871] Fix `net_send` and `net_move` for ARTIFICIAL_CELLs. (BlueBrain/nmodl#1360) * Fix `net_send` for ARTIFICIAL_CELLs. * Fix `net_move` for ARTIFICIAL_CELL. * Add tests. NMODL Repo SHA: BlueBrain/nmodl@c2320b809259b82120979519f7459b7d984381c7 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 8 +++-- .../codegen/codegen_neuron_cpp_visitor.cpp | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index fc7a3d69e7..3ee0053610 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2139,7 +2139,8 @@ void CodegenNeuronCppVisitor::print_net_send_call(const ast::FunctionCall& node) } const auto& tqitem = get_variable_name("tqitem", /* use_instance */ false); - printer->fmt_text("net_send(/* tqitem */ &{}, {}, {}, {} + ", + printer->fmt_text("{}(/* tqitem */ &{}, {}, {}, {} + ", + info.artificial_cell ? "artcell_net_send" : "net_send", tqitem, weight_pointer, point_process, @@ -2152,7 +2153,10 @@ void CodegenNeuronCppVisitor::print_net_move_call(const ast::FunctionCall& node) const auto& point_process = get_variable_name("point_process", /* use_instance */ false); const auto& tqitem = get_variable_name("tqitem", /* use_instance */ false); - printer->fmt_text("net_move(/* tqitem */ &{}, {}, ", tqitem, point_process); + printer->fmt_text("{}(/* tqitem */ &{}, {}, ", + info.artificial_cell ? "artcell_net_move" : "net_move", + tqitem, + point_process); print_vector_elements(node.get_arguments(), ", "); printer->add_text(')'); diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 2189ed076d..8268eb22d0 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -209,3 +209,39 @@ SCENARIO("Read `eca`.", "[codegen]") { } } } + +SCENARIO("ARTIFICIAL_CELL with `net_send`") { + GIVEN("a mod file") { + std::string nmodl = R"( + NEURON { + ARTIFICIAL_CELL test + } + NET_RECEIVE(w) { + net_send(t+1, 1) + } + )"; + std::string cpp = transpile(nmodl); + + THEN("it contains") { + REQUIRE_THAT(cpp, ContainsSubstring("artcell_net_send")); + } + } +} + +SCENARIO("ARTIFICIAL_CELL with `net_move`") { + GIVEN("a mod file") { + std::string nmodl = R"( + NEURON { + ARTIFICIAL_CELL test + } + NET_RECEIVE(w) { + net_move(t+1) + } + )"; + std::string cpp = transpile(nmodl); + + THEN("it contains") { + REQUIRE_THAT(cpp, ContainsSubstring("artcell_net_move")); + } + } +} From 5e7152fb6df10aaac61a1db4c4e0c61ec6339aee Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 22 Jul 2024 14:56:55 +0200 Subject: [PATCH 704/871] Simplify by using structured binding. (BlueBrain/nmodl#1361) NMODL Repo SHA: BlueBrain/nmodl@2f0b280cc860d5fad606514fc2e05a32ad5bf33e --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 63eb9f3bcf..c65408fc06 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -463,9 +463,7 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { } return {name, false}; }; - std::string function_name; - bool is_random_function; - std::tie(function_name, is_random_function) = get_renamed_random_function(name); + auto [function_name, is_random_function] = get_renamed_random_function(name); if (defined_method(name)) { function_name = method_name(name); From cc50e523024829f05feee95bd27053e706976017 Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Mon, 22 Jul 2024 14:57:31 +0200 Subject: [PATCH 705/871] Build with MSVC (BlueBrain/nmodl#1354) Compilation works for me with: cmake -B build -S . -DFLEX_INCLUDE_PATH=C:\ProgramData\chocolatey\lib\winflexbison3\tools -DBUILD_SHARED_LIBS=OFF I used choco to install winflexbison, the remaining dependencies came from winget. Shared libraries should be disabled, otherwise dl, fmt and spdlog leave DLLs scattered around and the Python module will need additional search paths. NMODL Repo SHA: BlueBrain/nmodl@b91c0eae37bb26d97300471f2057874b429a7bec --- cmake/nmodl/CMakeLists.txt | 5 +++++ cmake/nmodl/FlexHelper.cmake | 6 ++++-- src/nmodl/lexer/main_c.cpp | 6 ++++-- src/nmodl/lexer/main_nmodl.cpp | 18 ++++++++++-------- src/nmodl/parser/main_c.cpp | 3 ++- src/nmodl/pybind/CMakeLists.txt | 15 ++++++++++++++- src/nmodl/pybind/pyembed.cpp | 3 ++- src/nmodl/pybind/wrapper.cpp | 2 +- src/nmodl/pybind/wrapper.hpp | 8 +++++++- .../transpiler/integration/CMakeLists.txt | 8 ++++++-- test/nmodl/transpiler/unit/CMakeLists.txt | 12 ++++++++---- test/nmodl/transpiler/unit/newton/newton.cpp | 18 +++++++++--------- 12 files changed, 72 insertions(+), 32 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 1247d866a5..84e01c07a8 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -130,6 +130,11 @@ if(NOT NMODL_3RDPARTY_USE_FMT endif() cpp_cc_git_submodule(json BUILD PACKAGE nlohmann_json REQUIRED) cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED) +if(WIN32) + cpp_cc_git_submodule(dlfcn-win32 BUILD) + add_library(dlfcn-win32::dl ALIAS dl) + set(CMAKE_DL_LIBS dlfcn-win32::dl) +endif() # Tell spdlog not to use its bundled fmt, it should either use the fmt submodule or a truly external # installation for consistency. This line should be harmless if we use an external spdlog. option(SPDLOG_FMT_EXTERNAL "Force to use an external {{fmt}}" ON) diff --git a/cmake/nmodl/FlexHelper.cmake b/cmake/nmodl/FlexHelper.cmake index 6b7c2bc755..a6c75818ec 100644 --- a/cmake/nmodl/FlexHelper.cmake +++ b/cmake/nmodl/FlexHelper.cmake @@ -6,8 +6,10 @@ get_filename_component(FLEX_BIN_DIR ${FLEX_EXECUTABLE} DIRECTORY) if(NOT FLEX_BIN_DIR MATCHES "/usr/bin") - get_filename_component(FLEX_INCLUDE_PATH ${FLEX_BIN_DIR} PATH) - set(FLEX_INCLUDE_PATH ${FLEX_INCLUDE_PATH}/include/) + if(NOT FLEX_INCLUDE_PATH) + get_filename_component(FLEX_INCLUDE_PATH ${FLEX_BIN_DIR} PATH) + set(FLEX_INCLUDE_PATH ${FLEX_INCLUDE_PATH}/include/) + endif() if(EXISTS "${FLEX_INCLUDE_PATH}/FlexLexer.h") message(STATUS " Adding Flex include path as : ${FLEX_INCLUDE_PATH}") include_directories(${FLEX_INCLUDE_PATH}) diff --git a/src/nmodl/lexer/main_c.cpp b/src/nmodl/lexer/main_c.cpp index 8788af66da..1724554dde 100644 --- a/src/nmodl/lexer/main_c.cpp +++ b/src/nmodl/lexer/main_c.cpp @@ -7,13 +7,15 @@ #include -#include - #include "config/config.h" #include "lexer/c11_lexer.hpp" #include "parser/c11_driver.hpp" #include "utils/logger.hpp" +// CLI11 includes Windows specific headers with macros that will interfere with the lexer, include +// it last. +#include + /** * \file * \brief Example of standalone lexer program for C code diff --git a/src/nmodl/lexer/main_nmodl.cpp b/src/nmodl/lexer/main_nmodl.cpp index 600570b02c..5f75bef2fd 100644 --- a/src/nmodl/lexer/main_nmodl.cpp +++ b/src/nmodl/lexer/main_nmodl.cpp @@ -26,7 +26,7 @@ * it's value and location. */ -using namespace nmodl; +namespace nmodl { using parser::NmodlDriver; using parser::NmodlLexer; @@ -106,10 +106,12 @@ void tokenize(const std::string& mod_text) { } } +} // namespace nmodl + int main(int argc, const char* argv[]) { - CLI::App app{ - fmt::format("NMODL-Lexer : Standalone Lexer for NMODL Code({})", Version::to_string())}; + CLI::App app{fmt::format("NMODL-Lexer : Standalone Lexer for NMODL Code({})", + nmodl::Version::to_string())}; std::vector mod_files; std::vector mod_texts; @@ -120,16 +122,16 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); for (const auto& file: mod_files) { - logger->info("Processing file : {}", file); + nmodl::logger->info("Processing file : {}", file); std::ifstream f(file); std::string mod{std::istreambuf_iterator{f}, {}}; - tokenize(mod); + nmodl::tokenize(mod); } for (const auto& text: mod_texts) { - logger->info("Processing text : {}", text); - tokenize(text); + nmodl::logger->info("Processing text : {}", text); + nmodl::tokenize(text); } return 0; -} +} \ No newline at end of file diff --git a/src/nmodl/parser/main_c.cpp b/src/nmodl/parser/main_c.cpp index decc6268cd..ab84d7bc09 100644 --- a/src/nmodl/parser/main_c.cpp +++ b/src/nmodl/parser/main_c.cpp @@ -8,9 +8,10 @@ #include #include "config/config.h" -#include "parser/c11_driver.hpp" #include "utils/logger.hpp" +#include "parser/c11_driver.hpp" + /** * \file * Standalone parser program for C. This demonstrate basic diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index ecc9447c0a..b9d561b943 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -24,10 +24,15 @@ set_property( DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py) +if(WIN32) + # MSVC can't handle long string literals, even if the documentation claims so See + # https://developercommunity.visualstudio.com/t/c-string-literal-max-length-much-shorter-than-docu/758957 + string(REGEX REPLACE "\n\n" "\n)jiowi\" R\"jiowi(\n" NMODL_ODE_PY "${NMODL_ODE_PY}") +endif() configure_file(${CMAKE_CURRENT_SOURCE_DIR}/ode_py.hpp.inc ${CMAKE_CURRENT_BINARY_DIR}/ode_py.hpp @ONLY) -add_library(pyembed pyembed.cpp) +add_library(pyembed STATIC pyembed.cpp) set_property(TARGET pyembed PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(pyembed PRIVATE util) target_link_libraries(pyembed PRIVATE fmt::fmt) @@ -51,9 +56,11 @@ target_include_directories(pywrapper PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) # pybind11::embed adds PYTHON_LIBRARIES to target_link_libraries. To avoid link to # libpython, we can use `module` interface library from pybind11. # ~~~ +target_link_libraries(pyembed PRIVATE ${CMAKE_DL_LIBS}) if(NOT LINK_AGAINST_PYTHON) target_link_libraries(pywrapper PRIVATE pybind11::module) else() + target_link_libraries(pyembed PRIVATE ${PYTHON_LIBRARIES} pywrapper) target_link_libraries(pywrapper PRIVATE pybind11::embed) endif() @@ -69,6 +76,12 @@ if(NMODL_ENABLE_PYTHON_BINDINGS) ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp ${PYBIND_GENERATED_SOURCES}) add_dependencies(_nmodl lexer pyastgen util) target_link_libraries(_nmodl PRIVATE printer symtab visitor pyembed) + set_target_properties(_nmodl PROPERTIES LIBRARY_OUTPUT_DIRECTORY_DEBUG + ${CMAKE_BINARY_DIR}/lib/nmodl) + + if(MSVC) + target_compile_options(_nmodl PRIVATE /bigobj) + endif() # in case of wheel, python module shouldn't link to wrapper library if(LINK_AGAINST_PYTHON) diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index 7e4bfb8dcc..f6e9b5e49c 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -92,7 +92,8 @@ void EmbeddedPythonLoader::load_libraries() { CMakeInfo::SHARED_LIBRARY_SUFFIX); throw std::runtime_error("NMODLHOME doesn't have lib/libpywrapper library"); } - pybind_wrapper_handle = dlopen(pybind_wraplib_env.c_str(), dlopen_opts); + std::string env_str = pybind_wraplib_env.string(); + pybind_wrapper_handle = dlopen(env_str.c_str(), dlopen_opts); if (!pybind_wrapper_handle) { const auto errstr = dlerror(); logger->critical("Tried but failed to load {}", pybind_wraplib_env.string()); diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 3c9f2661a5..74f4a2cd59 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -193,7 +193,7 @@ void finalize_interpreter_func() { // Prevent mangling for easier `dlsym`. extern "C" { -__attribute__((visibility("default"))) pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept { +NMODL_EXPORT pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept { return {&nmodl::pybind_wrappers::initialize_interpreter_func, &nmodl::pybind_wrappers::finalize_interpreter_func, &call_solve_nonlinear_system, diff --git a/src/nmodl/pybind/wrapper.hpp b/src/nmodl/pybind/wrapper.hpp index 2a51da014e..725f9f8113 100644 --- a/src/nmodl/pybind/wrapper.hpp +++ b/src/nmodl/pybind/wrapper.hpp @@ -53,8 +53,14 @@ struct pybind_wrap_api { decltype(&call_analytic_diff) analytic_diff; }; +#ifdef _WIN32 +#define NMODL_EXPORT +#else +#define NMODL_EXPORT __attribute__((visibility("default"))) +#endif + extern "C" { -__attribute__((visibility("default"))) pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept; +pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept; } diff --git a/test/nmodl/transpiler/integration/CMakeLists.txt b/test/nmodl/transpiler/integration/CMakeLists.txt index eb78fbc1f3..3070f13756 100644 --- a/test/nmodl/transpiler/integration/CMakeLists.txt +++ b/test/nmodl/transpiler/integration/CMakeLists.txt @@ -11,7 +11,11 @@ file(GLOB modfiles CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/test/integrati foreach(modfile ${modfiles}) get_filename_component(modfile_name "${modfile}" NAME) add_test(NAME ${modfile_name} COMMAND ${CMAKE_BINARY_DIR}/bin/nmodl ${modfile}) - set_tests_properties(${modfile_name} - PROPERTIES ENVIRONMENT PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) + if(WIN32) + set(NMODL_TEST_PYTHONPATH "${PROJECT_BINARY_DIR}/lib;$ENV{PYTHONPATH}") + else() + set(NMODL_TEST_PYTHONPATH "${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") + endif() + set_tests_properties(${modfile_name} PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") cpp_cc_configure_sanitizers(TEST ${modfile_name}) endforeach() diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index a9bf75ee8c..1845367288 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -172,17 +172,21 @@ endforeach() file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/Unit.inc" "UNITSON \n UNITSOFF \n UNITSON \n UNITSOFF") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/included.file" "TITLE") +if(WIN32) + set(NMODL_TEST_PYTHONPATH "${PROJECT_BINARY_DIR}/lib;$ENV{PYTHONPATH}") +else() + set(NMODL_TEST_PYTHONPATH "${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") +endif() + # ============================================================================= # pybind11 tests # ============================================================================= add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/unit/ode) -set_tests_properties(Ode PROPERTIES ENVIRONMENT - PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) +set_tests_properties(Ode PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") if(NMODL_ENABLE_PYTHON_BINDINGS) add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) - set_tests_properties(Pybind PROPERTIES ENVIRONMENT - PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}) + set_tests_properties(Pybind PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") cpp_cc_configure_sanitizers(TEST Pybind PRELOAD) endif() diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 5b5e0e47ed..33f4086980 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -29,7 +29,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer Eigen::Matrix X{22.2536}; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<1, functor>(X, fn); fn(X, F); THEN("find the solution") { CAPTURE(iter_newton); @@ -50,7 +50,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer Eigen::Matrix X{-0.21421}; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<1, functor>(X, fn); fn(X, F); THEN("find the solution") { CAPTURE(iter_newton); @@ -72,7 +72,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer Eigen::Matrix X{0.2, 0.4}; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<2, functor>(X, fn); fn(X, F); THEN("find a solution") { CAPTURE(iter_newton); @@ -103,7 +103,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer Eigen::Matrix X{0.21231, 0.4435, -0.11537}; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<3, functor>(X, fn); fn(X, F); THEN("find a solution") { CAPTURE(iter_newton); @@ -131,7 +131,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer Eigen::Matrix X{0.21231, 0.4435, -0.11537, -0.8124312}; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<4, functor>(X, fn); fn(X, F); THEN("find a solution") { CAPTURE(iter_newton); @@ -157,7 +157,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer X << 8.234, -245.46, 123.123, 0.8343, 5.555; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<5, functor>(X, fn); fn(X, F); THEN("find a solution") { CAPTURE(iter_newton); @@ -190,7 +190,7 @@ SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numer X << 8.234, -5.46, 1.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; Eigen::Matrix F; functor fn; - int iter_newton = newton::newton_numerical_diff_solver(X, fn); + int iter_newton = newton::newton_numerical_diff_solver<10, functor>(X, fn); fn(X, F); THEN("find a solution") { CAPTURE(iter_newton); @@ -411,7 +411,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") Eigen::Matrix F; Eigen::Matrix J; functor fn; - int iter_newton = newton::newton_solver(X, fn); + int iter_newton = newton::newton_solver<5, functor>(X, fn); fn(X, F, J); THEN("find a solution") { CAPTURE(iter_newton); @@ -547,7 +547,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") Eigen::Matrix F; Eigen::Matrix J; functor fn; - int iter_newton = newton::newton_solver(X, fn); + int iter_newton = newton::newton_solver<10, functor>(X, fn); fn(X, F, J); THEN("find a solution") { CAPTURE(iter_newton); From 373ce2011ebf50107c40a571052c60c09bb0d0f2 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 23 Jul 2024 17:00:46 +0200 Subject: [PATCH 706/871] Formatting. (BlueBrain/nmodl#1368) NMODL Repo SHA: BlueBrain/nmodl@696d8989be1daa8c20f9e54baa69ff0437666ba4 --- src/nmodl/codegen/codegen_naming.hpp | 40 ++++++++++++++-------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 78f2b25ead..df99e82e1e 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -177,26 +177,26 @@ static constexpr char NRN_POINTERINDEX[] = "hoc_nrnpointerindex"; /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends // clang-format off - static const std::unordered_map VERBATIM_VARIABLES_MAPPING{ - {"_nt", "nt"}, - {"_p", "data"}, - {"_ppvar", "indexes"}, - {"_thread", "thread"}, - {"_iml", "id"}, - {"_cntml_padded", "pnodecount"}, - {"_cntml", "nodecount"}, - {"_tqitem", "tqitem"}}; - - // Functions available in NMODL with RANDOM construct and their mapping to - // C++ functions for Random123 interface. - static std::unordered_map RANDOM_FUNCTIONS_MAPPING{ - {"random_setseq", "nrnran123_setseq"}, - {"random_setids", "nrnran123_setids"}, - {"random_uniform", "nrnran123_uniform"}, - {"random_negexp", "nrnran123_negexp"}, - {"random_normal", "nrnran123_normal"}, - {"random_ipick", "nrnran123_ipick"}, - {"random_dpick", "nrnran123_dblpick"}}; +static const std::unordered_map VERBATIM_VARIABLES_MAPPING{ + {"_nt", "nt"}, + {"_p", "data"}, + {"_ppvar", "indexes"}, + {"_thread", "thread"}, + {"_iml", "id"}, + {"_cntml_padded", "pnodecount"}, + {"_cntml", "nodecount"}, + {"_tqitem", "tqitem"}}; + +// Functions available in NMODL with RANDOM construct and their mapping to +// C++ functions for Random123 interface. +static std::unordered_map RANDOM_FUNCTIONS_MAPPING{ + {"random_setseq", "nrnran123_setseq"}, + {"random_setids", "nrnran123_setids"}, + {"random_uniform", "nrnran123_uniform"}, + {"random_negexp", "nrnran123_negexp"}, + {"random_normal", "nrnran123_normal"}, + {"random_ipick", "nrnran123_ipick"}, + {"random_dpick", "nrnran123_dblpick"}}; // clang-format on } // namespace naming } // namespace codegen From a2b83cc6a395269b24d819f05930a9e5c4ea78cf Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 26 Jul 2024 17:16:17 +0200 Subject: [PATCH 707/871] Use SymPy solver when using `derivimplicit` method with NEURON codegen (BlueBrain/nmodl#1366) * rename `sparse_solver_exists` to `solver_exists` * add `name` argument to `solver_exists` to keep function generic * rename `cnexp` usecase to `solve` * add tests for `derivimplicit` method NMODL Repo SHA: BlueBrain/nmodl@10788d7b39304b95ea038be51284fa04d2864f23 --- src/nmodl/main.cpp | 7 ++-- src/nmodl/visitors/visitor_utils.cpp | 7 ++-- src/nmodl/visitors/visitor_utils.hpp | 3 +- test/nmodl/transpiler/usecases/CMakeLists.txt | 2 +- .../transpiler/usecases/cnexp/test_array.py | 26 --------------- .../transpiler/usecases/cnexp/test_scalar.py | 26 --------------- .../usecases/{cnexp => solve}/cnexp_array.mod | 0 .../{cnexp => solve}/cnexp_scalar.mod | 0 .../usecases/solve/derivimplicit_array.mod | 30 +++++++++++++++++ .../usecases/solve/derivimplicit_scalar.mod | 16 ++++++++++ .../transpiler/usecases/solve/test_array.py | 32 +++++++++++++++++++ .../transpiler/usecases/solve/test_scalar.py | 31 ++++++++++++++++++ 12 files changed, 121 insertions(+), 59 deletions(-) delete mode 100644 test/nmodl/transpiler/usecases/cnexp/test_array.py delete mode 100644 test/nmodl/transpiler/usecases/cnexp/test_scalar.py rename test/nmodl/transpiler/usecases/{cnexp => solve}/cnexp_array.mod (100%) rename test/nmodl/transpiler/usecases/{cnexp => solve}/cnexp_scalar.mod (100%) create mode 100644 test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod create mode 100644 test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod create mode 100644 test/nmodl/transpiler/usecases/solve/test_array.py create mode 100644 test/nmodl/transpiler/usecases/solve/test_scalar.py diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index be322c0e99..3e8d596a03 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -485,7 +485,10 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("localize")); } - if (sympy_conductance || sympy_analytic || sparse_solver_exists(*ast)) { + const bool sympy_derivimplicit = neuron_code && solver_exists(*ast, "derivimplicit"); + + if (sympy_conductance || sympy_analytic || solver_exists(*ast, "sparse") || + sympy_derivimplicit) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() .api() .initialize_interpreter(); @@ -496,7 +499,7 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("sympy_conductance")); } - if (sympy_analytic || sparse_solver_exists(*ast)) { + if (sympy_analytic || solver_exists(*ast, "sparse") || sympy_derivimplicit) { if (!sympy_analytic) { logger->info( "Automatically enable sympy_analytic because it exists solver of type " diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 8bc49703c9..2a13a38af2 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -215,15 +215,16 @@ std::vector> collect_nodes(ast::Ast& node, return visitor.lookup(node, types); } -bool sparse_solver_exists(const ast::Ast& node) { +bool solver_exists(const ast::Ast& node, const std::string& name) { const auto solve_blocks = collect_nodes(node, {ast::AstNodeType::SOLVE_BLOCK}); - return std::any_of(solve_blocks.begin(), solve_blocks.end(), [](auto const& solve_block) { + return std::any_of(solve_blocks.begin(), solve_blocks.end(), [&name](auto const& solve_block) { assert(solve_block); const auto& method = dynamic_cast(*solve_block).get_method(); - return method && method->get_node_name() == "sparse"; + return method && method->get_node_name() == name; }); } + std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types) { std::stringstream stream; visitor::NmodlPrintVisitor v(stream, exclude_types); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 9e7163fdb0..7105fdfd54 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -106,7 +106,8 @@ std::vector> collect_nodes( ast::Ast& node, const std::vector& types = {}); -bool sparse_solver_exists(const ast::Ast& node); +/// Whether or not a solver of type name exists in the AST +bool solver_exists(const ast::Ast& node, const std::string& name); /// Given AST node, return the NMODL string representation std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types = {}); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 3923727b48..e72f7c9492 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,5 +1,5 @@ set(NMODL_USECASE_DIRS - cnexp + solve constant function procedure diff --git a/test/nmodl/transpiler/usecases/cnexp/test_array.py b/test/nmodl/transpiler/usecases/cnexp/test_array.py deleted file mode 100644 index 5c6ae46cb2..0000000000 --- a/test/nmodl/transpiler/usecases/cnexp/test_array.py +++ /dev/null @@ -1,26 +0,0 @@ -import numpy as np - -from neuron import h, gui -from neuron.units import ms - -nseg = 1 - -s = h.Section() -s.insert("cnexp_array") -s.nseg = nseg - -x_hoc = h.Vector().record(s(0.5)._ref_x_cnexp_array) -t_hoc = h.Vector().record(h._ref_t) - -h.stdinit() -h.tstop = 5.0 * ms -h.run() - -x = np.array(x_hoc.as_numpy()) -t = np.array(t_hoc.as_numpy()) - -rate = (0.1 - 1.0) * (0.7 * 0.8 * 0.9) -x_exact = 42.0 * np.exp(rate * t) -rel_err = np.abs(x - x_exact) / x_exact - -assert np.all(rel_err < 1e-12) diff --git a/test/nmodl/transpiler/usecases/cnexp/test_scalar.py b/test/nmodl/transpiler/usecases/cnexp/test_scalar.py deleted file mode 100644 index 9c451e9c2c..0000000000 --- a/test/nmodl/transpiler/usecases/cnexp/test_scalar.py +++ /dev/null @@ -1,26 +0,0 @@ -import numpy as np - -from neuron import h, gui -from neuron.units import ms - -nseg = 1 - -s = h.Section() -s.insert("cnexp_scalar") -s.nseg = nseg - -x_hoc = h.Vector().record(s(0.5)._ref_x_cnexp_scalar) -t_hoc = h.Vector().record(h._ref_t) - -h.stdinit() -h.tstop = 5.0 * ms -h.run() - -x = np.array(x_hoc.as_numpy()) -t = np.array(t_hoc.as_numpy()) - -x0 = 42.0 -x_exact = 42.0 * np.exp(-t) -rel_err = np.abs(x - x_exact) / x_exact - -assert np.all(rel_err < 1e-12) diff --git a/test/nmodl/transpiler/usecases/cnexp/cnexp_array.mod b/test/nmodl/transpiler/usecases/solve/cnexp_array.mod similarity index 100% rename from test/nmodl/transpiler/usecases/cnexp/cnexp_array.mod rename to test/nmodl/transpiler/usecases/solve/cnexp_array.mod diff --git a/test/nmodl/transpiler/usecases/cnexp/cnexp_scalar.mod b/test/nmodl/transpiler/usecases/solve/cnexp_scalar.mod similarity index 100% rename from test/nmodl/transpiler/usecases/cnexp/cnexp_scalar.mod rename to test/nmodl/transpiler/usecases/solve/cnexp_scalar.mod diff --git a/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod b/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod new file mode 100644 index 0000000000..e43e343135 --- /dev/null +++ b/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod @@ -0,0 +1,30 @@ +NEURON { + SUFFIX derivimplicit_array + RANGE z +} + +ASSIGNED { + z[3] +} + +STATE { + x + s[2] +} + +INITIAL { + x = 42.0 + s[0] = 0.1 + s[1] = -1.0 + z[0] = 0.7 + z[1] = 0.8 + z[2] = 0.9 +} + +BREAKPOINT { + SOLVE dX METHOD derivimplicit +} + +DERIVATIVE dX { + x' = (s[0] + s[1])*(z[0]*z[1]*z[2])*x +} diff --git a/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod b/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod new file mode 100644 index 0000000000..05dfeab97b --- /dev/null +++ b/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod @@ -0,0 +1,16 @@ +NEURON { + SUFFIX derivimplicit_scalar +} + +STATE { x } + +INITIAL { + x = 42 +} + +BREAKPOINT { + SOLVE dX METHOD derivimplicit +} + +DERIVATIVE dX { x' = -x } + diff --git a/test/nmodl/transpiler/usecases/solve/test_array.py b/test/nmodl/transpiler/usecases/solve/test_array.py new file mode 100644 index 0000000000..856145dfd7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/solve/test_array.py @@ -0,0 +1,32 @@ +import numpy as np +from neuron import h, gui +from neuron.units import ms + + +def simulate(method, rtol, dt=None): + nseg = 1 + + s = h.Section() + s.insert(f"{method}_array") + s.nseg = nseg + + x_hoc = h.Vector().record(getattr(s(0.5), f"_ref_x_{method}_array")) + t_hoc = h.Vector().record(h._ref_t) + + h.stdinit() + if dt is not None: + h.dt = dt * ms + h.tstop = 5.0 * ms + h.run() + + x = np.array(x_hoc.as_numpy()) + t = np.array(t_hoc.as_numpy()) + + rate = (0.1 - 1.0) * (0.7 * 0.8 * 0.9) + x_exact = 42.0 * np.exp(rate * t) + np.testing.assert_allclose(x, x_exact, rtol=rtol) + + +if __name__ == "__main__": + simulate("cnexp", rtol=1e-12) + simulate("derivimplicit", rtol=1e-4, dt=1e-4) diff --git a/test/nmodl/transpiler/usecases/solve/test_scalar.py b/test/nmodl/transpiler/usecases/solve/test_scalar.py new file mode 100644 index 0000000000..2abd3fd777 --- /dev/null +++ b/test/nmodl/transpiler/usecases/solve/test_scalar.py @@ -0,0 +1,31 @@ +import numpy as np +from neuron import h, gui +from neuron.units import ms + + +def simulate(method, rtol, dt=None): + nseg = 1 + + s = h.Section() + s.insert(f"{method}_scalar") + s.nseg = nseg + + x_hoc = h.Vector().record(getattr(s(0.5), f"_ref_x_{method}_scalar")) + t_hoc = h.Vector().record(h._ref_t) + + h.stdinit() + if dt is not None: + h.dt = dt * ms + h.tstop = 5.0 * ms + h.run() + + x = np.array(x_hoc.as_numpy()) + t = np.array(t_hoc.as_numpy()) + + x_exact = 42.0 * np.exp(-t) + np.testing.assert_allclose(x, x_exact, rtol=rtol) + + +if __name__ == "__main__": + simulate("cnexp", rtol=1e-12) + simulate("derivimplicit", rtol=1e-3, dt=1e-4) From a41c1687dd7c76bcc1d858cc926f655180477038 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 30 Jul 2024 13:09:17 +0200 Subject: [PATCH 708/871] Downgrade warning to info. (BlueBrain/nmodl#1371) This warning is unlikely to be followed by an error. It might be good to know information. NMODL Repo SHA: BlueBrain/nmodl@56f4f4d6b086ba4ff872042f29fa2d95976c541a --- src/nmodl/visitors/verbatim_var_rename_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 3908a88679..7faf414aba 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -69,7 +69,7 @@ std::string VerbatimVarRenameVisitor::rename_variable(const std::string& name) { if (symbol != nullptr) { return new_name; } - logger->warn("could not find {} definition in nmodl", name); + logger->info("could not find {} definition in nmodl", name); } return name; } From 2264153c8c78f1ce2a0d725447f6442e021eaf27 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 30 Jul 2024 13:09:37 +0200 Subject: [PATCH 709/871] Use `info.top_verbatim_blocks`. (BlueBrain/nmodl#1372) NMODL Repo SHA: BlueBrain/nmodl@ac865062a4b211cfe9a6edec01d1d003050f2d50 --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index b477a58912..e2c37f0256 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -357,11 +357,9 @@ void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { printing_top_verbatim_blocks = true; - for (const auto& block: info.top_blocks) { - if (block->is_verbatim()) { - printer->add_newline(2); - block->accept(*this); - } + for (const auto& block: info.top_verbatim_blocks) { + printer->add_newline(2); + block->accept(*this); } printing_top_verbatim_blocks = false; From 7934678356fe0e0d370d50b034332cd6f41477de Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 30 Jul 2024 13:10:00 +0200 Subject: [PATCH 710/871] Isolate all VERBATIM token renaming. (BlueBrain/nmodl#1373) NMODL Repo SHA: BlueBrain/nmodl@0947aa4a1439495b73058582e0047bbbe0837634 --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 18 ++++++++---------- src/nmodl/codegen/codegen_naming.hpp | 4 +++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index e2c37f0256..ce3bf06248 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -97,7 +97,13 @@ std::string CodegenCoreneuronCppVisitor::process_verbatim_token(const std::strin */ auto new_name = replace_if_verbatim_variable(name); if (new_name != name) { - return get_variable_name(new_name, false); + new_name = get_variable_name(new_name, false); + + if (name == (std::string("_") + naming::TQITEM_VARIABLE)) { + new_name.insert(0, 1, '&'); + } + + return new_name; } /* @@ -640,15 +646,7 @@ std::string CodegenCoreneuronCppVisitor::process_verbatim_text(std::string const if (program_symtab->is_method_defined(token) && tokens[i + 1] == "(") { internal_method_call_encountered = true; } - auto name = process_verbatim_token(token); - - if (token == (std::string("_") + naming::TQITEM_VARIABLE)) { - name.insert(0, 1, '&'); - } - if (token == "_STRIDE") { - name = "pnodecount+id"; - } - result += name; + result += process_verbatim_token(token); } return result; } diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index df99e82e1e..c7c10eb91e 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -185,7 +185,9 @@ static const std::unordered_map VERBATIM_VARIABLES_MAP {"_iml", "id"}, {"_cntml_padded", "pnodecount"}, {"_cntml", "nodecount"}, - {"_tqitem", "tqitem"}}; + {"_tqitem", "tqitem"}, + {"_STRIDE", "pnodecount+id"}, +}; // Functions available in NMODL with RANDOM construct and their mapping to // C++ functions for Random123 interface. From 6b3ea45747c24c67073f1c0fc2a2174aeb2baf07 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 30 Jul 2024 13:10:22 +0200 Subject: [PATCH 711/871] Add VERBATIM markers. (BlueBrain/nmodl#1374) NMODL Repo SHA: BlueBrain/nmodl@3e9e024daadb07fa0c4b005ac9a5366b2c654ee6 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 ++ .../unit/codegen/codegen_coreneuron_cpp_visitor.cpp | 11 +++++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index c65408fc06..03ac81884b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -974,6 +974,7 @@ void CodegenCppVisitor::visit_function_call(const FunctionCall& node) { void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { const auto& text = node.get_statement()->eval(); + printer->add_line("// VERBATIM"); const auto& result = process_verbatim_text(text); const auto& statements = stringutils::split_string(result, '\n'); @@ -983,6 +984,7 @@ void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { printer->add_line(trimed_stmt); } } + printer->add_line("// ENDVERBATIM"); } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp index f049119270..365ddf7087 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -775,11 +775,14 @@ SCENARIO("Check that top verbatim blocks are well generated", "[codegen][top ver ENDVERBATIM )"; - THEN("Correct code is generated") { + THEN("A using namespace is generated") { auto const generated = get_coreneuron_cpp_code(nmodl_text); - std::string expected_code = R"(using namespace coreneuron; - - + std::string expected_code = "using namespace coreneuron;"; + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); + } + THEN("Selected identifiers are expanded") { + auto const generated = get_coreneuron_cpp_code(nmodl_text); + std::string expected_code = R"( double a = 2.; foo_temp(nt); &tqitem; From 62a7d0a9ac6552486d7e889c2100df17dfd70fc6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 30 Jul 2024 13:10:50 +0200 Subject: [PATCH 712/871] Print `add_nrn_has_net_event`. (BlueBrain/nmodl#1375) NMODL Repo SHA: BlueBrain/nmodl@92f176ea43737d49347e39a706dfcc6cf8e34681 --- src/nmodl/codegen/codegen_info.hpp | 2 +- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 4f392cc683..a96f24cb80 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -334,7 +334,7 @@ struct CodegenInfo { /// if net_send function is used bool net_send_used = false; - /// if net_even function is used + /// if net_event function is used bool net_event_used = false; /// if diam is used diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 3ee0053610..2f95540a90 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1260,6 +1260,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_line("add_nrn_artcell(mech_type, {});", info.tqitem_index); } + if (info.net_event_used) { + printer->fmt_line("add_nrn_has_net_event(mech_type);"); + } + printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc);"); if (!info.point_process) { printer->add_line("hoc_register_npy_direct(mech_type, npy_direct_func_proc);"); From 4013b428c949a42ef5c3810cfc482fc97f6fcd6c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 30 Jul 2024 14:54:17 +0200 Subject: [PATCH 713/871] Reduce comparator duplication. (BlueBrain/nmodl#1377) NMODL Repo SHA: BlueBrain/nmodl@d5b0e08ed535de1fec9ecdf2fbda6c9eacab640e --- .../codegen/codegen_neuron_cpp_visitor.cpp | 20 +++++-------------- .../codegen/codegen_neuron_cpp_visitor.hpp | 5 +++++ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2f95540a90..2cccf9052e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -656,17 +656,7 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { const std::string& varname = update_if_ion_variable_name(name); - auto symbol_comparator = [&varname](const SymbolType& sym) { - return varname == sym->get_name(); - }; - - auto index_comparator = [&varname](const IndexVariableInfo& var) { - return varname == var.symbol->get_name(); - }; - - auto thread_comparator = [&varname](const ThreadVariableInfo& var) { - return varname == var.symbol->get_name(); - }; + auto name_comparator = [&varname](const auto& sym) { return varname == get_name(sym); }; if (name == naming::POINT_PROCESS_VARIABLE) { if (printing_net_receive) { @@ -681,14 +671,14 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, // float variable auto f = std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), - symbol_comparator); + name_comparator); if (f != codegen_float_variables.end()) { return float_variable_name(*f, use_instance); } // integer variable auto i = - std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), name_comparator); if (i != codegen_int_variables.end()) { return int_variable_name(*i, varname, use_instance); } @@ -696,7 +686,7 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, // thread variable auto t = std::find_if(codegen_thread_variables.begin(), codegen_thread_variables.end(), - thread_comparator); + name_comparator); if (t != codegen_thread_variables.end()) { return thread_variable_name(*t, use_instance); } @@ -704,7 +694,7 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, // global variable auto g = std::find_if(codegen_global_variables.begin(), codegen_global_variables.end(), - symbol_comparator); + name_comparator); if (g != codegen_global_variables.end()) { return global_variable_name(*g, use_instance); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index f23aa9b1cc..c66748a9bb 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -71,6 +71,11 @@ struct ThreadVariableInfo { size_t offset; }; +inline std::string get_name(const ThreadVariableInfo& var) { + return var.symbol->get_name(); +} + + /** * \class CodegenNeuronCppVisitor * \brief %Visitor for printing C++ code compatible with legacy api of NEURON From b85cc1aa87b4bde8d13a04d0d9dae1b93e2a3189 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 31 Jul 2024 09:40:47 +0200 Subject: [PATCH 714/871] Simplify `float_variable_name` (NEURON version). (BlueBrain/nmodl#1376) This commit cleans up the left over dead code from cloning the CoreNEURON method. NMODL Repo SHA: BlueBrain/nmodl@575a724f9db1e2556afcd2f418e1fc070c8cbfd5 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2cccf9052e..484707f9f0 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -575,21 +575,17 @@ void CodegenNeuronCppVisitor::append_conc_write_statements( /// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbol, bool use_instance) const { + if (!use_instance) { + throw std::runtime_error("Printing non-instance variables is not implemented."); + } + auto name = symbol->get_name(); auto dimension = symbol->get_length(); - // auto position = position_of_float_var(name); if (symbol->is_array()) { - if (use_instance) { - return fmt::format("(inst.{}+id*{})", name, dimension); - } - throw std::runtime_error("Printing non-instance variables is not implemented."); - // return fmt::format("(data + {}*pnodecount + id*{})", position, dimension); - } - if (use_instance) { + return fmt::format("(inst.{}+id*{})", name, dimension); + } else { return fmt::format("inst.{}[id]", name); } - throw std::runtime_error("Not implemented."); - // return fmt::format("data[{}*pnodecount + id]", position); } From 25507a6bf50784994da22f2ffc0fc41b574fed0f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 31 Jul 2024 15:58:27 +0200 Subject: [PATCH 715/871] Remove `print_backend_namespace_{start,stop}`. (BlueBrain/nmodl#1378) NMODL Repo SHA: BlueBrain/nmodl@62268bbb8451b2b6035c34bddb5c808147b03e7b --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 12 ------------ .../codegen/codegen_coreneuron_cpp_visitor.hpp | 16 ---------------- 2 files changed, 28 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index ce3bf06248..81421fffa4 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -302,16 +302,6 @@ void CodegenCoreneuronCppVisitor::print_global_method_annotation() { } -void CodegenCoreneuronCppVisitor::print_backend_namespace_start() { - // no separate namespace for C++ (cpu) backend -} - - -void CodegenCoreneuronCppVisitor::print_backend_namespace_stop() { - // no separate namespace for C++ (cpu) backend -} - - void CodegenCoreneuronCppVisitor::print_backend_includes() { // backend specific, nothing for cpu } @@ -2993,12 +2983,10 @@ void CodegenCoreneuronCppVisitor::print_headers_include() { void CodegenCoreneuronCppVisitor::print_namespace_begin() { print_namespace_start(); - print_backend_namespace_start(); } void CodegenCoreneuronCppVisitor::print_namespace_end() { - print_backend_namespace_stop(); print_namespace_stop(); } diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 33c4e311d8..630a4127c8 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -258,22 +258,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual void print_global_method_annotation(); - /** - * Prints the start of namespace for the backend-specific code - * - * For the C++ backend no additional namespace is required - */ - virtual void print_backend_namespace_start(); - - - /** - * Prints the end of namespace for the backend-specific code - * - * For the C++ backend no additional namespace is required - */ - virtual void print_backend_namespace_stop(); - - /** * Print backend specific includes (none needed for C++ backend) */ From e24c304e1f6f54c3c8f2231b58a47d4719b786bf Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 5 Aug 2024 11:12:29 +0200 Subject: [PATCH 716/871] Remove `print_namespace_{begin,end}`. (BlueBrain/nmodl#1379) These are new synonyms for `print_namespace_{start,stop}` and can therefore be removed. NMODL Repo SHA: BlueBrain/nmodl@f18b9d1acaaa697885659219810eb93ac967854c --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 14 ++------------ .../codegen/codegen_coreneuron_cpp_visitor.hpp | 14 -------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 14 -------------- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 14 ++------------ src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 14 -------------- 5 files changed, 4 insertions(+), 66 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 81421fffa4..160caeea24 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -2981,16 +2981,6 @@ void CodegenCoreneuronCppVisitor::print_headers_include() { } -void CodegenCoreneuronCppVisitor::print_namespace_begin() { - print_namespace_start(); -} - - -void CodegenCoreneuronCppVisitor::print_namespace_end() { - print_namespace_stop(); -} - - void CodegenCoreneuronCppVisitor::print_common_getters() { print_first_pointer_var_index_getter(); print_first_random_var_index_getter(); @@ -3064,7 +3054,7 @@ void CodegenCoreneuronCppVisitor::print_compute_functions() { void CodegenCoreneuronCppVisitor::print_codegen_routines() { print_backend_info(); print_headers_include(); - print_namespace_begin(); + print_namespace_start(); print_nmodl_constants(); print_prcellstate_macros(); print_mechanism_info(); @@ -3083,7 +3073,7 @@ void CodegenCoreneuronCppVisitor::print_codegen_routines() { print_compute_functions(); print_check_table_thread_function(); print_mechanism_register(); - print_namespace_end(); + print_namespace_stop(); } diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 630a4127c8..d137236657 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -940,20 +940,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_headers_include() override; - /** - * Print start of namespaces - * - */ - void print_namespace_begin() override; - - - /** - * Print end of namespaces - * - */ - void print_namespace_end() override; - - /** * Print common getters * diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 7c24a3f6db..b546be83b4 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1353,20 +1353,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_headers_include() = 0; - /** - * Print start of namespaces - * - */ - virtual void print_namespace_begin() = 0; - - - /** - * Print end of namespaces - * - */ - virtual void print_namespace_end() = 0; - - /** * Print all classes * \param print_initializers Whether to include default values. diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 484707f9f0..919785dc42 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2029,16 +2029,6 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { } -void CodegenNeuronCppVisitor::print_namespace_begin() { - print_namespace_start(); -} - - -void CodegenNeuronCppVisitor::print_namespace_end() { - print_namespace_stop(); -} - - void CodegenNeuronCppVisitor::print_data_structures(bool print_initializers) { print_mechanism_global_var_structure(print_initializers); print_mechanism_range_var_structure(print_initializers); @@ -2093,7 +2083,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_headers_include(); print_macro_definitions(); print_neuron_global_variable_declarations(); - print_namespace_begin(); + print_namespace_start(); print_nmodl_constants(); print_prcellstate_macros(); print_mechanism_info(); @@ -2106,7 +2096,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_compute_functions(); // only nrn_cur and nrn_state print_sdlists_init(true); print_mechanism_register(); - print_namespace_end(); + print_namespace_stop(); } void CodegenNeuronCppVisitor::print_ion_variable() { diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c66748a9bb..3c8b28d367 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -643,20 +643,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_mechanism_variables_macros(); - /** - * Print start of namespaces - * - */ - void print_namespace_begin() override; - - - /** - * Print end of namespaces - * - */ - void print_namespace_end() override; - - /** * Print all classes * \param print_initializers Whether to include default values. From 0de3febb6b01d776542581391525fe0f8a2b3ae6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 5 Aug 2024 14:52:05 +0200 Subject: [PATCH 717/871] Remove pointless check. (BlueBrain/nmodl#1382) The container is an `unordered_set`, which means we can insert without causing duplication. Hence, we can avoid the find code. NMODL Repo SHA: BlueBrain/nmodl@de969a3b712bf03be3df51fcfcfa78bec27821b6 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 5a2880b778..1c24e2e5b7 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -772,11 +772,9 @@ void CodegenHelperVisitor::visit_verbatim(const Verbatim& node) { // check if the token exist in the symbol table for (auto& token: tokens) { - if (info.variables_in_verbatim.find(token) == info.variables_in_verbatim.end()) { - auto symbol = psymtab->lookup(token); - if (symbol != nullptr) { - info.variables_in_verbatim.insert(token); - } + auto symbol = psymtab->lookup(token); + if (symbol != nullptr) { + info.variables_in_verbatim.insert(token); } } } From 91b17d4bc1baba4f7f88f00d897277c0b5a0b2ca Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 5 Aug 2024 14:53:02 +0200 Subject: [PATCH 718/871] Move `print_{function,procedure}` upwards. (BlueBrain/nmodl#1381) The code has completely converged and can be moved to the common base. NMODL Repo SHA: BlueBrain/nmodl@41c3a15ba7b6a2b000a6aa06f564d367aaa5faba --- .../codegen_coreneuron_cpp_visitor.cpp | 25 ------------------- .../codegen_coreneuron_cpp_visitor.hpp | 14 ----------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 25 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 4 +-- .../codegen/codegen_neuron_cpp_visitor.cpp | 24 ------------------ .../codegen/codegen_neuron_cpp_visitor.hpp | 14 ----------- 6 files changed, 27 insertions(+), 79 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 160caeea24..01bd95adea 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -444,31 +444,6 @@ void CodegenCoreneuronCppVisitor::print_function_procedure_helper(const ast::Blo } -void CodegenCoreneuronCppVisitor::print_procedure(const ast::ProcedureBlock& node) { - print_function_procedure_helper(node); -} - - -void CodegenCoreneuronCppVisitor::print_function(const ast::FunctionBlock& node) { - auto name = node.get_node_name(); - - // name of return variable - std::string return_var; - if (info.function_uses_table(name)) { - return_var = "ret_f_" + name; - } else { - return_var = "ret_" + name; - } - - // first rename return variable name - auto block = node.get_statement_block().get(); - RenameVisitor v(name, return_var); - block->accept(v); - - print_function_procedure_helper(node); -} - - void CodegenCoreneuronCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { auto name = node.get_node_name(); const auto& p = node.get_parameters(); diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index d137236657..cfc47e1143 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -355,20 +355,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_function_procedure_helper(const ast::Block& node) override; - /** - * Print NMODL procedure in target backend code - * \param node - */ - void print_procedure(const ast::ProcedureBlock& node) override; - - - /** - * Print NMODL function in target backend code - * \param node - */ - void print_function(const ast::FunctionBlock& node) override; - - /** * Print NMODL function_table in target backend code * \param node diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 03ac81884b..0da8604d82 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -505,6 +505,31 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { } +void CodegenCppVisitor::print_procedure(const ast::ProcedureBlock& node) { + print_function_procedure_helper(node); +} + + +void CodegenCppVisitor::print_function(const ast::FunctionBlock& node) { + auto name = node.get_node_name(); + + // name of return variable + std::string return_var; + if (info.function_uses_table(name)) { + return_var = "ret_f_" + name; + } else { + return_var = "ret_" + name; + } + + // first rename return variable name + auto block = node.get_statement_block().get(); + RenameVisitor v(name, return_var); + block->accept(v); + + print_function_procedure_helper(node); +} + + void CodegenCppVisitor::print_prcellstate_macros() const { printer->add_line("#ifndef NRN_PRCELLSTATE"); printer->add_line("#define NRN_PRCELLSTATE 0"); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index b546be83b4..8bae8ea043 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -881,14 +881,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * Print NMODL procedure in target backend code * \param node */ - virtual void print_procedure(const ast::ProcedureBlock& node) = 0; + void print_procedure(const ast::ProcedureBlock& node); /** * Print NMODL function in target backend code * \param node */ - virtual void print_function(const ast::FunctionBlock& node) = 0; + void print_function(const ast::FunctionBlock& node); /** diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 919785dc42..3012028111 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -291,30 +291,6 @@ void CodegenNeuronCppVisitor::print_function_procedure_helper(const ast::Block& } -void CodegenNeuronCppVisitor::print_procedure(const ast::ProcedureBlock& node) { - print_function_procedure_helper(node); -} - - -void CodegenNeuronCppVisitor::print_function(const ast::FunctionBlock& node) { - auto name = node.get_node_name(); - - // name of return variable - std::string return_var; - if (info.function_uses_table(name)) { - return_var = "ret_f_" + name; - } else { - return_var = "ret_" + name; - } - - // first rename return variable name - auto block = node.get_statement_block().get(); - RenameVisitor v(name, return_var); - block->accept(v); - - print_function_procedure_helper(node); -} - void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( const ast::Block* function_or_procedure_block, InterpreterWrapper wrapper_type) { diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 3c8b28d367..62621c4946 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -236,20 +236,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_function_procedure_helper(const ast::Block& node) override; - /** - * Print NMODL procedure in target backend code - * \param node - */ - void print_procedure(const ast::ProcedureBlock& node) override; - - - /** - * Print NMODL function in target backend code - * \param node - */ - void print_function(const ast::FunctionBlock& node) override; - - void print_hoc_py_wrapper_function_body(const ast::Block* function_or_procedure_block, InterpreterWrapper wrapper_type); From 071e441492893f25ad73d61fc4ad71b47f9b1202 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 7 Aug 2024 16:16:04 +0200 Subject: [PATCH 719/871] Refactor `print_namespace_{start,stop}.` (BlueBrain/nmodl#1380) NMODL Repo SHA: BlueBrain/nmodl@35c5c16b9277ebe0f1fc11cd71373630cb65c75c --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 13 +++---------- .../codegen/codegen_coreneuron_cpp_visitor.hpp | 11 +---------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 14 ++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 11 +++++++++-- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 10 ++-------- src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 11 +---------- 6 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 01bd95adea..6e72deb0ee 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -349,7 +349,7 @@ void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { print_namespace_stop(); printer->add_newline(2); - printer->add_line("using namespace coreneuron;"); + print_using_namespace(); printing_top_verbatim_blocks = true; @@ -742,17 +742,10 @@ void CodegenCoreneuronCppVisitor::print_memb_list_getter() { } -void CodegenCoreneuronCppVisitor::print_namespace_start() { - printer->add_newline(2); - printer->push_block("namespace coreneuron"); -} - - -void CodegenCoreneuronCppVisitor::print_namespace_stop() { - printer->pop_block(); +std::string CodegenCoreneuronCppVisitor::namespace_name() { + return "coreneuron"; } - /** * \details There are three types of thread variables currently considered: * - top local thread variables diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index cfc47e1143..b23052d974 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -487,16 +487,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_memb_list_getter(); - /** - * Prints the start of the \c coreneuron namespace - */ - void print_namespace_start() override; - - - /** - * Prints the end of the \c coreneuron namespace - */ - void print_namespace_stop() override; + virtual std::string namespace_name() override; /** diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 0da8604d82..106f15fb7e 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -569,6 +569,20 @@ void CodegenCppVisitor::print_mechanism_info() { printer->add_line("};"); } +void CodegenCppVisitor::print_using_namespace() { + printer->fmt_line("using namespace {};", namespace_name()); +} + +void CodegenCppVisitor::print_namespace_start() { + printer->add_newline(2); + printer->fmt_push_block("namespace {}", namespace_name()); +} + + +void CodegenCppVisitor::print_namespace_stop() { + printer->pop_block(); +} + /****************************************************************************************/ /* Printing routines for code generation */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 8bae8ea043..7b6bed7ca9 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1072,18 +1072,25 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /* Code-specific printing routines for code generations */ /****************************************************************************************/ + /** Name of "our" namespace. + */ + virtual std::string namespace_name() = 0; /** * Prints the start of the simulator namespace */ - virtual void print_namespace_start() = 0; + void print_namespace_start(); /** * Prints the end of the simulator namespace */ - virtual void print_namespace_stop() = 0; + void print_namespace_stop(); + /** + * Prints f"using namespace {namespace_name()}". + */ + void print_using_namespace(); /****************************************************************************************/ /* Routines for returning variable name */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 3012028111..c31f306262 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -511,17 +511,11 @@ std::string CodegenNeuronCppVisitor::py_function_signature( /* Code-specific printing routines for code generation */ /****************************************************************************************/ - -void CodegenNeuronCppVisitor::print_namespace_start() { - printer->add_newline(2); - printer->push_block("namespace neuron"); +std::string CodegenNeuronCppVisitor::namespace_name() { + return "neuron"; } -void CodegenNeuronCppVisitor::print_namespace_stop() { - printer->pop_block(); -} - void CodegenNeuronCppVisitor::append_conc_write_statements( std::vector& statements, const Ion& ion, diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 62621c4946..2404d68f42 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -343,16 +343,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Prints the start of the \c neuron namespace - */ - void print_namespace_start() override; - - - /** - * Prints the end of the \c neuron namespace - */ - void print_namespace_stop() override; + std::string namespace_name() override; /****************************************************************************************/ From ee69b6a66acb83197c64a9258a50efc8192dfa3b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 9 Aug 2024 13:15:33 +0200 Subject: [PATCH 720/871] Remove unhelpful comment. (BlueBrain/nmodl#1385) NMODL Repo SHA: BlueBrain/nmodl@dbecc204330d7c3ae0d9d2cdaa13998d497d77dd --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index c31f306262..336ca36c14 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1127,15 +1127,6 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_line("_nrn_thread_table_reg(mech_type, {});", table_thread_function_name()); } - /// Call _nrn_mechanism_register_data_fields() with the correct arguments - /// Geenerated code follows the style underneath - /// - /// _nrn_mechanism_register_data_fields(mech_type, - /// _nrn_mechanism_field{"var_name"}, /* float var index 0 */ - /// ... - /// ); - /// - /// TODO: More things to add here printer->add_line("_nrn_mechanism_register_data_fields(mech_type,"); printer->increase_indent(); From 056421141df24c66e04001e53e6496055fbf58d9 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 9 Aug 2024 13:15:50 +0200 Subject: [PATCH 721/871] Pass code-generator name to usecase scripts. (BlueBrain/nmodl#1386) By passing the name of the code-generator to usecase scipts, we can store data during the `nocmodl` run; and compare during the `nmodl` run. NMODL Repo SHA: BlueBrain/nmodl@df2b862087edeea9ef91c96e840d772784d89a46 --- test/nmodl/transpiler/usecases/run_test.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index d8bcc34b28..99dde68797 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -9,7 +9,7 @@ function run_tests() { if [[ -f "$f" ]] then echo "${usecase_dir}/$f: started." - python "$f" + python "$f" $1 echo "${usecase_dir}/$f: success." fi done @@ -32,11 +32,11 @@ pushd "${usecase_dir}" > /dev/null echo "-- Running NRN+nocmodl ------" rm -r "${output_dir}" tmp || true nrnivmodl -run_tests +run_tests nocmodl # NRN + NMODL echo "-- Running NRN+NMODL --------" rm -r "${output_dir}" tmp || true nrnivmodl -nmodl "${nmodl}" -run_tests +run_tests nmodl From 94d381cc8febe8aa52efe7d9d07432b808e601d4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 9 Aug 2024 14:24:50 +0200 Subject: [PATCH 722/871] Initialize STATE variables. (BlueBrain/nmodl#1387) Before the INITIAL block, initialize STATE variable from the global, `0`-suffixed, global variable. All elements of the array-valued STATE variable are set to the same initial value, defined by the `0`-name. * Test default values of STATE variables. NMODL Repo SHA: BlueBrain/nmodl@c5052b928656f0072af5ff6216ab48f30243c779 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 16 ++++++++++++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/state/default_values.mod | 25 +++++++++++++++++++ .../usecases/state/test_default_values.py | 25 +++++++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/state/default_values.mod create mode 100644 test/nmodl/transpiler/usecases/state/test_default_values.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 336ca36c14..e7fe5e57ff 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1484,6 +1484,22 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { printer->fmt_line("inst.{}[id] = v;", naming::VOLTAGE_UNUSED_VARIABLE); } + for (auto state: info.state_vars) { + auto state_name = state->get_name(); + auto lhs_name = get_variable_name(state_name); + auto rhs_name = get_variable_name(state_name + "0"); + + if (!state->is_array()) { + auto state_name = state->get_name(); + printer->fmt_line("{} = {};", lhs_name, rhs_name); + } else { + auto n_elements = state->get_length(); + printer->fmt_push_block("for(size_t _i = 0; _i < {}; ++_i)", n_elements); + printer->fmt_line("{}[_i] = {};", lhs_name, rhs_name); + printer->pop_block(); + } + } + print_initial_block(info.initial_node); printer->pop_block(); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index e72f7c9492..711893d402 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -16,6 +16,7 @@ set(NMODL_USECASE_DIRS point_process parameter suffix + state table useion at_time) diff --git a/test/nmodl/transpiler/usecases/state/default_values.mod b/test/nmodl/transpiler/usecases/state/default_values.mod new file mode 100644 index 0000000000..d95af8485f --- /dev/null +++ b/test/nmodl/transpiler/usecases/state/default_values.mod @@ -0,0 +1,25 @@ +NEURON { + SUFFIX default_values + RANGE Z, B + GLOBAL X0, Z0, A0, B0 +} + +STATE { + X + Y + Z + A[3] + B[2] +} + +PARAMETER { + X0 = 2.0 + Z0 = 3.0 + A0 = 4.0 + B0 = 5.0 +} + +INITIAL { + Z = 7.0 + B[1] = 8.0 +} diff --git a/test/nmodl/transpiler/usecases/state/test_default_values.py b/test/nmodl/transpiler/usecases/state/test_default_values.py new file mode 100644 index 0000000000..1687a31911 --- /dev/null +++ b/test/nmodl/transpiler/usecases/state/test_default_values.py @@ -0,0 +1,25 @@ +import numpy as np + +from neuron import h, gui + + +def test_default_values(): + s = h.Section() + s.insert("default_values") + mech = s(0.5).default_values + + h.stdinit() + + assert mech.X == 2.0 + assert mech.Y == 0.0 + assert mech.Z == 7.0 + + for i in range(3): + assert mech.A[i] == 4.0 + + assert mech.B[0] == 5.0 + assert mech.B[1] == 8.0 + + +if __name__ == "__main__": + test_default_values() From 04fa81e40f29d0033a33aa3ae7cacd2482daaf91 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 12 Aug 2024 10:08:13 +0200 Subject: [PATCH 723/871] Assert side effects in KINETIC blocks. (BlueBrain/nmodl#1392) Effectively the side effects in KINETIC blocks, such as assigning a STATE to a RANGE variable happen (once more) after the state has been updated. This commit adds test for this; and "fixes" the bug by running the functor method `initialize` again after running Newton. NMODL Repo SHA: BlueBrain/nmodl@a8c5755f6cc859791434df90fe33bd09de1f9098 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 1 + .../usecases/kinetic/side_effects.mod | 36 +++++++++++ .../usecases/kinetic/test_side_effects.py | 59 +++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/kinetic/side_effects.mod create mode 100644 test/nmodl/transpiler/usecases/kinetic/test_side_effects.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 106f15fb7e..ac0cbc4789 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1087,6 +1087,7 @@ void CodegenCppVisitor::visit_eigen_newton_solver_block(const ast::EigenNewtonSo // assign newton solver results in matrix X to state vars print_statement_block(*node.get_update_states_block(), false, false); + printer->add_line("newton_functor.initialize(); // TODO mimic calling F again."); printer->add_line("newton_functor.finalize();"); } diff --git a/test/nmodl/transpiler/usecases/kinetic/side_effects.mod b/test/nmodl/transpiler/usecases/kinetic/side_effects.mod new file mode 100644 index 0000000000..253a9aa6a4 --- /dev/null +++ b/test/nmodl/transpiler/usecases/kinetic/side_effects.mod @@ -0,0 +1,36 @@ +NEURON { + SUFFIX side_effects + RANGE x, forward_flux, backward_flux + NONSPECIFIC_CURRENT il +} + +ASSIGNED { + il + x + forward_flux + backward_flux +} + +STATE { + X + Y +} + +INITIAL { + X = 1 + Y = 2 +} + +BREAKPOINT { + SOLVE state METHOD sparse + + il = forward_flux - backward_flux +} + +KINETIC state { + ~ X <-> Y (0.4, 0.5) + forward_flux = f_flux + backward_flux = b_flux + + x = X +} diff --git a/test/nmodl/transpiler/usecases/kinetic/test_side_effects.py b/test/nmodl/transpiler/usecases/kinetic/test_side_effects.py new file mode 100644 index 0000000000..81ea151e25 --- /dev/null +++ b/test/nmodl/transpiler/usecases/kinetic/test_side_effects.py @@ -0,0 +1,59 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + + +def run_simulation(): + s = h.Section() + s.insert("side_effects") + + mech = s(0.5).side_effects + t_hoc = h.Vector().record(h._ref_t) + x_hoc = h.Vector().record(mech._ref_x) + X_hoc = h.Vector().record(mech._ref_X) + Y_hoc = h.Vector().record(mech._ref_Y) + forward_flux_hoc = h.Vector().record(mech._ref_forward_flux) + backward_flux_hoc = h.Vector().record(mech._ref_backward_flux) + + h.stdinit() + h.tstop = 5.0 * ms + h.run() + + timeseries = { + "t": np.array(t_hoc.as_numpy()), + "x": np.array(x_hoc.as_numpy()), + "X": np.array(X_hoc.as_numpy()), + "Y": np.array(Y_hoc.as_numpy()), + "forward_flux": np.array(forward_flux_hoc.as_numpy()), + "backward_flux": np.array(backward_flux_hoc.as_numpy()), + } + + return timeseries + + +def check_assignment(x, X): + # At time t = 0, the side effects aren't applied. + np.testing.assert_array_equal(x[1:], X[1:]) + + +def check_flux(actual_flux, expected_flux): + # At time t = 0, the side effects aren't applied. + np.testing.assert_array_almost_equal_nulp( + actual_flux[1:], expected_flux[1:], nulp=8 + ) + + +def check_forward_flux(X, actual_flux): + check_flux(actual_flux, 0.4 * X) + + +def check_backward_flux(Y, actual_flux): + check_flux(actual_flux, 0.5 * Y) + + +if __name__ == "__main__": + timeseries = run_simulation() + check_assignment(timeseries["x"], timeseries["X"]) + check_forward_flux(timeseries["X"], timeseries["forward_flux"]) + check_backward_flux(timeseries["Y"], timeseries["backward_flux"]) From 9ac8db5759647f7fda3863b5a13f26ee699aa53c Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Mon, 12 Aug 2024 10:09:46 +0200 Subject: [PATCH 724/871] Load 'morphology' symbol during registration (BlueBrain/nmodl#1393) This way if someone define again this symbol (in hoc file for example), neuron will use the right one during allocation of the module. This is how it is currently done in neuron. This behaviour has been spotted with nmodl benchmark. NMODL Repo SHA: BlueBrain/nmodl@86c2c6623f0b327c4f9f6764736bd9845a132ae4 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e7fe5e57ff..30c75f24a6 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -932,6 +932,10 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in // throw std::runtime_error("Not implemented, global vectorize something else."); } + if (info.diam_used) { + printer->fmt_line("Symbol* _morphology_sym;"); + } + printer->pop_block(";"); print_global_var_struct_assertions(); @@ -1225,6 +1229,11 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_line("_nrn_thread_reg(mech_type, 0, thread_mem_cleanup);"); } + if (info.diam_used) { + printer->fmt_line("{}._morphology_sym = hoc_lookup(\"morphology\");", + global_struct_instance()); + } + printer->pop_block(); } @@ -1609,9 +1618,8 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { for (size_t i = 0; i < codegen_int_variables.size(); ++i) { auto var_info = codegen_int_variables[i]; if (var_info.symbol->get_name() == naming::DIAM_VARIABLE) { - printer->add_line("Symbol * morphology_sym = hoc_lookup(\"morphology\");"); - printer->fmt_line("Prop * morphology_prop = need_memb(morphology_sym);"); - + printer->fmt_line("Prop * morphology_prop = need_memb({}._morphology_sym);", + global_struct_instance()); printer->fmt_line( "_ppvar[{}] = _nrn_mechanism_get_param_handle(morphology_prop, 0);", i); } From 536597572a9bc8729beeaa8fbf3bca1294c95e0c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 12 Aug 2024 12:41:05 +0200 Subject: [PATCH 725/871] Implement RANDOM for NEURON. (BlueBrain/nmodl#1367) NMODL Repo SHA: BlueBrain/nmodl@22dc559ebb23ac3d001a002aeaa0043554b32573 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 30 ++++++++++++---- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/random/random_variable.mod | 9 +++++ .../usecases/random/test_random_variable.py | 35 +++++++++++++++++++ 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/random/random_variable.mod create mode 100644 test/nmodl/transpiler/usecases/random/test_random_variable.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 30c75f24a6..176414c559 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -563,6 +563,11 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& const std::string& name, bool use_instance) const { auto position = position_of_int_var(name); + + if (info.semantics[position].name == naming::RANDOM_SEMANTIC) { + return fmt::format("_ppvar[{}].literal_value()", position); + } + if (symbol.is_index) { if (use_instance) { throw std::runtime_error("Not implemented. [wiejo]"); @@ -581,9 +586,8 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& return fmt::format("(*inst.{}[id])", name); } + throw std::runtime_error("Not implemented. [nvueir]"); - // auto data = symbol.is_vdata ? "_vdata" : "_data"; - // return fmt::format("nt->{}[indexes[{}*pnodecount + id]]", data, position); } @@ -1554,13 +1558,19 @@ void CodegenNeuronCppVisitor::print_nrn_constructor() { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_destructor() { - return; + printer->fmt_push_block("void {}(Prop* _prop)", method_name(naming::NRN_DESTRUCTOR_METHOD)); + printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(_prop);"); + + for (const auto& rv: info.random_variables) { + printer->fmt_line("nrnran123_deletestream((nrnran123_State*) {});", + get_variable_name(get_name(rv), false)); + } + + printer->pop_block(); } -/// TODO: Print the equivalent of `nrn_alloc_` void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->add_newline(2); @@ -1673,7 +1683,14 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { } } - /// TODO: CONSTRUCTOR call + if (!info.random_variables.empty()) { + for (const auto& rv: info.random_variables) { + printer->fmt_line("{} = nrnran123_newstream();", + get_variable_name(get_name(rv), false)); + } + printer->fmt_line("nrn_mech_inst_destruct[mech_type] = {};", + method_name(naming::NRN_DESTRUCTOR_METHOD)); + } printer->pop_block(); } @@ -2073,6 +2090,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_prcellstate_macros(); print_mechanism_info(); print_data_structures(true); + print_nrn_destructor(); print_nrn_alloc(); print_function_prototypes(); print_functors_definitions(); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 711893d402..3ce12e04fb 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -15,6 +15,7 @@ set(NMODL_USECASE_DIRS net_send point_process parameter + random suffix state table diff --git a/test/nmodl/transpiler/usecases/random/random_variable.mod b/test/nmodl/transpiler/usecases/random/random_variable.mod new file mode 100644 index 0000000000..647a357c13 --- /dev/null +++ b/test/nmodl/transpiler/usecases/random/random_variable.mod @@ -0,0 +1,9 @@ +NEURON { + SUFFIX random_variable + RANDOM rng +} + + +FUNCTION negexp() { + negexp = random_negexp(rng) +} diff --git a/test/nmodl/transpiler/usecases/random/test_random_variable.py b/test/nmodl/transpiler/usecases/random/test_random_variable.py new file mode 100644 index 0000000000..4030aa7252 --- /dev/null +++ b/test/nmodl/transpiler/usecases/random/test_random_variable.py @@ -0,0 +1,35 @@ +import numpy as np +import scipy as sp +import hashlib + +from neuron import h + + +def hash_array(x): + + return hashlib.sha256(" ".join([f"{xx:.10e}" for xx in x]).encode()).hexdigest() + + +def test_negexp(): + nseg = 10 + s = h.Section() + + s.nseg = nseg + s.insert("random_variable") + + n_samples = 1000 + samples = [ + np.array([s(x).random_variable.negexp() for _ in range(n_samples)]) + for x in [0.34, 0.74] + ] + expected_hashes = [ + "3b66d7f83dc81ea485929aa8a4f347a3befee7170971dfb9c35a1a1d40ed0407", + "896c59d129cc0a248e57c548c8bf4be7ae4d39fbd92113e37f59c332be61a2b7", + ] + + assert hash_array(samples[0]) == expected_hashes[0] + assert hash_array(samples[1]) == expected_hashes[1] + + +if __name__ == "__main__": + test_negexp() From 60091f72d8a703c0fa71335327d886625f864a99 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 12 Aug 2024 15:38:51 +0200 Subject: [PATCH 726/871] Throw an error when using `derivimplicit` with `PROCEDURE` block (BlueBrain/nmodl#1383) NMODL Repo SHA: BlueBrain/nmodl@509416eac48a33794e152b322e25e1fc0fb814e3 --- src/nmodl/visitors/solve_block_visitor.cpp | 7 ++++++- .../transpiler/unit/visitor/solve_block.cpp | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 7355789ad4..2871ff93c6 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -56,7 +56,12 @@ ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( if (solve_method == codegen::naming::DERIVIMPLICIT_METHOD && !has_sympy_solution(*node_to_solve)) { /// typically derivimplicit is used for derivative block only - assert(node_to_solve->get_node_type() == ast::AstNodeType::DERIVATIVE_BLOCK); + if (node_to_solve->get_node_type() != ast::AstNodeType::DERIVATIVE_BLOCK) { + const std::string node_name = node_to_solve->get_node_name(); + const std::string node_type = node_to_solve->get_node_type_name(); + throw std::runtime_error(fmt::format( + "Method {} cannot be used for {} {}", solve_method, node_type, node_name)); + } auto derivative_block = dynamic_cast(node_to_solve); auto callback_expr = new ast::DerivimplicitCallback(derivative_block->clone()); return new ast::SolutionExpression(solve_block.clone(), callback_expr); diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index 6ca2341c89..af6c339b1d 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -123,3 +123,24 @@ TEST_CASE("Solve ODEs using legacy NeuronSolveVisitor", "[visitor][solver]") { } } } + +TEST_CASE("Throw errors on invalid methods", "[visitor][solver]") { + SECTION("PROCEDURE cannot be SOLVEd with derivimplicit METHOD") { + GIVEN("Breakpoint block with a PROCEDURE being solved using the derivimplcit METHOD") { + std::string nmodl_text = R"( + NEURON { + SUFFIX example + } + + BREAKPOINT { + SOLVE myfunc METHOD derivimplicit + } + + PROCEDURE myfunc() {} +)"; + THEN("A runtime error should be thrown") { + REQUIRE_THROWS_AS(run_solve_block_visitor(nmodl_text), std::runtime_error); + } + } + } +} From 537a5d51ffe4e66836a83e6cb66323db293353d1 Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 14 Aug 2024 10:46:31 +0200 Subject: [PATCH 727/871] Add a virtual destructor to ~Blame (BlueBrain/nmodl#1401) NMODL Repo SHA: BlueBrain/nmodl@aca88b3cc4cd4b906326ab92c7612f8c994251b1 --- src/nmodl/utils/blame.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nmodl/utils/blame.hpp b/src/nmodl/utils/blame.hpp index 114e69e2f1..97e42c2288 100644 --- a/src/nmodl/utils/blame.hpp +++ b/src/nmodl/utils/blame.hpp @@ -11,6 +11,8 @@ class Blame { Blame(size_t blame_line) : blame_line(blame_line) {} + virtual ~Blame() = default; + template void operator()(OStream& os, size_t current_line) const { if (blame_line == current_line) { From 76f0a8671f2b2b206daa2d0bd61d1ad301f8e585 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 14 Aug 2024 11:21:56 +0200 Subject: [PATCH 728/871] Remove state TODOs. (BlueBrain/nmodl#1397) NMODL Repo SHA: BlueBrain/nmodl@32d7e486b69ef3e36d9a17b930f661c5414ea57f --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 176414c559..36e138a204 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -231,15 +231,12 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_function_prototypes() { printer->add_newline(2); print_point_process_function_definitions(); print_setdata_functions(); print_check_table_function_prototypes(); - - /// TODO: Add mechanism function and procedures declarations } @@ -542,7 +539,6 @@ void CodegenNeuronCppVisitor::append_conc_write_statements( /****************************************************************************************/ -/// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::float_variable_name(const SymbolType& symbol, bool use_instance) const { if (!use_instance) { @@ -1079,7 +1075,6 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { } void CodegenNeuronCppVisitor::print_mechanism_register() { - /// TODO: Write this according to NEURON printer->add_newline(2); printer->add_line("/** register channel with the simulator */"); printer->fmt_push_block("extern \"C\" void _{}_reg()", info.mod_file); @@ -1700,7 +1695,6 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { /* Print nrn_state routine */ /****************************************************************************************/ -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_state() { if (!nrn_state_required()) { return; @@ -1778,7 +1772,6 @@ CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::nrn_current_parame } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { const auto& args = nrn_current_parameters(); const auto& block = node.get_statement_block(); @@ -1797,7 +1790,6 @@ void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointBlock& node) { const auto& block = node.get_statement_block(); print_statement_block(*block, false, false); @@ -1836,7 +1828,6 @@ void CodegenNeuronCppVisitor::print_nrn_cur_conductance_kernel(const BreakpointB } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_cur_non_conductance_kernel() { printer->fmt_line("double I1 = nrn_current_{}({}+0.001);", info.mod_suffix, @@ -1871,7 +1862,6 @@ void CodegenNeuronCppVisitor::print_nrn_cur_non_conductance_kernel() { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_nrn_cur_kernel(const BreakpointBlock& node) { printer->add_line("int node_id = node_data.nodeindices[id];"); printer->add_line("double v = node_data.node_voltages[node_id];"); @@ -2023,7 +2013,6 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { } else { printer->add_line("Prop* hoc_getdata_range(int type);"); } - /// TODO: More prints here? // for registration of tables if (info.table_count > 0) { printer->add_line("void _nrn_thread_table_reg(int, nrn_thread_table_check_t);"); @@ -2062,7 +2051,6 @@ void CodegenNeuronCppVisitor::print_g_unused() const { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_compute_functions() { print_hoc_py_wrapper_function_definitions(); for (const auto& procedure: info.procedures) { @@ -2079,7 +2067,6 @@ void CodegenNeuronCppVisitor::print_compute_functions() { } -/// TODO: Edit for NEURON void CodegenNeuronCppVisitor::print_codegen_routines() { print_backend_info(); print_headers_include(); From 5154362990179f59a241cd30af416a0a8f029233 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 14 Aug 2024 11:22:07 +0200 Subject: [PATCH 729/871] Surround oneliners with `{}`. (BlueBrain/nmodl#1398) NMODL Repo SHA: BlueBrain/nmodl@db16425025991a5e92955a792c2f77b07f8ad7e9 --- src/nmodl/solver/crout/crout.hpp | 21 ++++++++++++++------- src/nmodl/solver/newton/newton.hpp | 17 +++++++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/nmodl/solver/crout/crout.hpp b/src/nmodl/solver/crout/crout.hpp index cf45043b72..06766ebe55 100644 --- a/src/nmodl/solver/crout/crout.hpp +++ b/src/nmodl/solver/crout/crout.hpp @@ -50,9 +50,11 @@ EIGEN_DEVICE_FUNC inline int Crout(int n, T* const a, int* const perm, double* c for (i = 0; i < n; i++) { perm[i] = i; k = 0; - for (j = 1; j < n; j++) - if (std::fabs(a[i * n + j]) > std::fabs(a[i * n + k])) + for (j = 1; j < n; j++) { + if (std::fabs(a[i * n + j]) > std::fabs(a[i * n + k])) { k = j; + } + } rowmax[i] = a[i * n + k]; } @@ -99,8 +101,9 @@ EIGEN_DEVICE_FUNC inline int Crout(int n, T* const a, int* const perm, double* c /* Check that pivot element is not too small */ - if (std::fabs(a[pivot * n + r]) < roundoff) + if (std::fabs(a[pivot * n + r]) < roundoff) { return -1; + } /* * Operate on row in rth position. This produces the upper @@ -151,8 +154,9 @@ EIGEN_DEVICE_FUNC inline int solveCrout(int n, for (i = 0; i < n; i++) { pivot = perm[i]; sum = 0.0; - for (j = 0; j < i; j++) + for (j = 0; j < i; j++) { sum += a[pivot * n + j] * (y_(j)); + } y_(i) = (b_(pivot) - sum) / a[pivot * n + i]; } @@ -166,16 +170,18 @@ EIGEN_DEVICE_FUNC inline int solveCrout(int n, for (i = n - 1; i >= 0; i--) { pivot = perm[i]; sum = 0.0; - for (j = i + 1; j < n; j++) + for (j = i + 1; j < n; j++) { sum += a[pivot * n + j] * (y_(j)); + } y_(i) -= sum; } } else { for (i = 0; i < n; i++) { pivot = perm[i]; sum = 0.0; - for (j = 0; j < i; j++) + for (j = 0; j < i; j++) { sum += a[pivot * n + j] * (p[j]); + } p[i] = (b_(pivot) - sum) / a[pivot * n + i]; } @@ -189,8 +195,9 @@ EIGEN_DEVICE_FUNC inline int solveCrout(int n, for (i = n - 1; i >= 0; i--) { pivot = perm[i]; sum = 0.0; - for (j = i + 1; j < n; j++) + for (j = i + 1; j < n; j++) { sum += a[pivot * n + j] * (p[j]); + } p[i] -= sum; } } diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 973b4cd332..6fa9025438 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -76,13 +76,15 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, // Crout's implementation requires matrices stored in RowMajor order (C-style arrays). // Therefore, the transposeInPlace is critical such that the data() method to give the rows // instead of the columns. - if (!J.IsRowMajor) + if (!J.IsRowMajor) { J.transposeInPlace(); + } Eigen::Matrix pivot; Eigen::Matrix rowmax; // Check if J is singular - if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) + if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) { return -1; + } Eigen::Matrix X_solve; nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); X -= X_solve; @@ -156,13 +158,15 @@ EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix& // restore X X[i] += dX; } - if (!J.IsRowMajor) + if (!J.IsRowMajor) { J.transposeInPlace(); + } Eigen::Matrix pivot; Eigen::Matrix rowmax; // Check if J is singular - if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) + if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) { return -1; + } Eigen::Matrix X_solve; nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); X -= X_solve; @@ -195,10 +199,11 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, // The inverse can be called from within OpenACC regions without any issue, as opposed to // Eigen::PartialPivLU. J.computeInverseWithCheck(J_inv, invertible); - if (invertible) + if (invertible) { X -= J_inv * F; - else + } else { return -1; + } } return -1; } From 3bb801a0806d18344f0f84828a0b1f769d37fc1a Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Wed, 14 Aug 2024 13:42:59 +0200 Subject: [PATCH 730/871] Deduplicate renaming of state vars (BlueBrain/nmodl#1400) NMODL Repo SHA: BlueBrain/nmodl@4168b3ca16a374aa358d7667471a20df32672da5 --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 16 +--------------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 17 +++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 2 ++ .../codegen/codegen_neuron_cpp_visitor.cpp | 16 +--------------- 4 files changed, 21 insertions(+), 30 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 6e72deb0ee..fbf87baa47 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -1793,21 +1793,7 @@ void CodegenCoreneuronCppVisitor::print_initial_block(const InitialBlock* node) printer->add_line(statement); } - // initialize state variables (excluding ion state) - for (auto& var: info.state_vars) { - auto name = var->get_name(); - if (!info.is_ionic_conc(name)) { - auto lhs = get_variable_name(name); - auto rhs = get_variable_name(name + "0"); - if (var->is_array()) { - for (int i = 0; i < var->get_length(); ++i) { - printer->fmt_line("{}[{}] = {};", lhs, i, rhs); - } - } else { - printer->fmt_line("{} = {};", lhs, rhs); - } - } - } + print_rename_state_vars(); // initial block if (node != nullptr) { diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index ac0cbc4789..8b28a533aa 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1671,5 +1671,22 @@ std::tuple CodegenCppVisitor::check_if_var_is_array(const std::string return {false, 0}; } } + +void CodegenCppVisitor::print_rename_state_vars() const { + for (const auto& state: info.state_vars) { + auto state_name = state->get_name(); + auto lhs = get_variable_name(state_name); + auto rhs = get_variable_name(state_name + "0"); + + if (state->is_array()) { + auto size = state->get_length(); + for (int i = 0; i < state->get_length(); ++i) { + printer->fmt_line("{}[{}] = {};", lhs, i, rhs); + } + } else { + printer->fmt_line("{} = {};", lhs, rhs); + } + } +} } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 7b6bed7ca9..5f4546e9a4 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1516,6 +1516,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { const std::string& name, const std::unordered_set& = { CppObjectSpecifier::Inline}); + + void print_rename_state_vars() const; }; /* Templated functions need to be defined in header file */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 36e138a204..8e0f1de5f4 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1492,21 +1492,7 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { printer->fmt_line("inst.{}[id] = v;", naming::VOLTAGE_UNUSED_VARIABLE); } - for (auto state: info.state_vars) { - auto state_name = state->get_name(); - auto lhs_name = get_variable_name(state_name); - auto rhs_name = get_variable_name(state_name + "0"); - - if (!state->is_array()) { - auto state_name = state->get_name(); - printer->fmt_line("{} = {};", lhs_name, rhs_name); - } else { - auto n_elements = state->get_length(); - printer->fmt_push_block("for(size_t _i = 0; _i < {}; ++_i)", n_elements); - printer->fmt_line("{}[_i] = {};", lhs_name, rhs_name); - printer->pop_block(); - } - } + print_rename_state_vars(); print_initial_block(info.initial_node); printer->pop_block(); From 393f6634e2edfa5093700a650ca6637e013e691d Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Wed, 14 Aug 2024 15:15:31 +0200 Subject: [PATCH 731/871] Install pywrapper even as a submodule. (BlueBrain/nmodl#1337) Should avoid the upstream CMake having to deal with installing NMODL internals. NMODL Repo SHA: BlueBrain/nmodl@14ca6a4cd0d840896b08ec2a95f4f6f8a7927bfb --- src/nmodl/pybind/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index b9d561b943..25f80d1c10 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -113,7 +113,7 @@ file(COPY ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ext DESTINATION ${CMAKE_BINAR # things are installed twice with the wheel and in weird places. Let's just # move the .so libs # ~~~ -if(NOT LINK_AGAINST_PYTHON AND NOT NMODL_AS_SUBPROJECT) +if(NOT LINK_AGAINST_PYTHON) install(TARGETS pywrapper DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib) if(NMODL_ENABLE_PYTHON_BINDINGS) install(TARGETS _nmodl DESTINATION nmodl/) From 386afe1abdeb06c994d993ac775d71becce4e316 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 16 Aug 2024 10:29:52 +0200 Subject: [PATCH 732/871] Add toggling of `emit_cvode` flag for codegen (BlueBrain/nmodl#1384) NMODL Repo SHA: BlueBrain/nmodl@6d3b83066cf2c48c045d0953e346b98483aaeab8 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 73 ++++++++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 2 +- src/nmodl/codegen/codegen_naming.hpp | 6 + .../codegen/codegen_neuron_cpp_visitor.cpp | 4 - .../unit/codegen/codegen_helper.cpp | 130 ++++++++++++++++++ 5 files changed, 210 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 1c24e2e5b7..88d4e424e6 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -15,6 +15,7 @@ #include "parser/c11_driver.hpp" #include "visitors/visitor_utils.hpp" +#include "utils/logger.hpp" namespace nmodl { namespace codegen { @@ -24,6 +25,24 @@ using namespace ast; using symtab::syminfo::NmodlType; using symtab::syminfo::Status; +/** + * Check whether a given SOLVE block solves a PROCEDURE with any of the CVode methods + */ +static bool check_procedure_has_cvode(const std::shared_ptr& solve_node, + const std::shared_ptr& procedure_node) { + const auto& solve_block = std::dynamic_pointer_cast(solve_node); + const auto& method = solve_block->get_method(); + if (!method) { + return false; + } + const auto& method_name = method->get_node_name(); + + return procedure_node->get_node_name() == solve_block->get_block_name()->get_node_name() && + (method_name == codegen::naming::AFTER_CVODE_METHOD || + method_name == codegen::naming::CVODE_T_METHOD || + method_name == codegen::naming::CVODE_T_V_METHOD); +} + /** * How symbols are stored in NEURON? See notes written in markdown file. * @@ -152,6 +171,59 @@ void CodegenHelperVisitor::find_ion_variables(const ast::Program& node) { } } +/** + * Find whether or not we need to emit CVODE-related code for NEURON + * Notes: we generate CVODE-related code if and only if: + * - there is exactly ONE block being SOLVEd + * - the block is one of the following types: + * - DERIVATIVE + * - KINETIC + * - PROCEDURE being solved with the `after_cvode`, `cvode_t`, or `cvode_t_v` methods + */ +void CodegenHelperVisitor::check_cvode_codegen(const ast::Program& node) { + // find the breakpoint block + const auto& breakpoint_nodes = collect_nodes(node, {AstNodeType::BREAKPOINT_BLOCK}); + + // do nothing if there are no BREAKPOINT nodes + if (breakpoint_nodes.empty()) { + return; + } + + // there can only be one BREAKPOINT block in the entire program + assert(breakpoint_nodes.size() == 1); + + const auto& breakpoint_node = std::dynamic_pointer_cast( + breakpoint_nodes[0]); + + // all (global) kinetic/derivative nodes + const auto& kinetic_or_derivative_nodes = + collect_nodes(node, {AstNodeType::KINETIC_BLOCK, AstNodeType::DERIVATIVE_BLOCK}); + + // all (global) procedure nodes + const auto& procedure_nodes = collect_nodes(node, {AstNodeType::PROCEDURE_BLOCK}); + + // find all SOLVE blocks in that BREAKPOINT block + const auto& solve_nodes = collect_nodes(*breakpoint_node, {AstNodeType::SOLVE_BLOCK}); + + // check whether any of the SOLVE blocks are solving any PROCEDURE with `after_cvode`, + // `cvode_t`, or `cvode_t_v` methods + const auto using_cvode = std::any_of( + solve_nodes.begin(), solve_nodes.end(), [&procedure_nodes](const auto& solve_node) { + return std::any_of(procedure_nodes.begin(), + procedure_nodes.end(), + [&solve_node](const auto& procedure_node) { + return check_procedure_has_cvode(solve_node, procedure_node); + }); + }); + + // only case when we emit CVODE code is if we have exactly one block, and + // that block is either a KINETIC/DERIVATIVE with any method, or a + // PROCEDURE with `after_cvode` method + if (solve_nodes.size() == 1 && (kinetic_or_derivative_nodes.size() || using_cvode)) { + logger->debug("Will emit code for CVODE"); + info.emit_cvode = true; + } +} /** * Find non-range variables i.e. ones that are not belong to per instance allocation @@ -738,6 +810,7 @@ void CodegenHelperVisitor::visit_program(const ast::Program& node) { find_non_range_variables(); find_table_variables(); find_neuron_global_variables(); + check_cvode_codegen(node); } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index b960bdc06f..9258f1cf7f 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -75,7 +75,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void find_non_range_variables(); void find_neuron_global_variables(); static void sort_with_mod2c_symbol_order(std::vector& symbols); - + void check_cvode_codegen(const ast::Program& node); public: CodegenHelperVisitor() = default; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index c7c10eb91e..bc657e6f7b 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -32,6 +32,12 @@ static constexpr char CNEXP_METHOD[] = "cnexp"; /// cvode method in nmodl static constexpr char AFTER_CVODE_METHOD[] = "after_cvode"; +/// cvode_t method in nmodl +static constexpr char CVODE_T_METHOD[] = "cvode_t"; + +/// cvode_t_v method in nmodl +static constexpr char CVODE_T_V_METHOD[] = "cvode_t_v"; + /// sparse method in nmodl static constexpr char SPARSE_METHOD[] = "sparse"; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 8e0f1de5f4..b99b451011 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1176,10 +1176,6 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { info.semantics[i].name, i)); } - if (info.emit_cvode) { - mech_register_args.push_back( - "_nrn_mechanism_field{\"_cvode_ieq\", \"cvodeieq\"} /* 0 */"); - } printer->add_multi_line(fmt::format("{}", fmt::join(mech_register_args, ",\n"))); diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index a8da8c9b88..5e5006f5d5 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -57,6 +57,24 @@ std::string run_codegen_helper_visitor(const std::string& text) { return variables; } +CodegenInfo run_codegen_helper_get_info(const std::string& text) { + const auto& ast = NmodlDriver().parse_string(text); + /// construct symbol table and run codegen helper visitor + SymtabVisitor{}.visit_program(*ast); + KineticBlockVisitor{}.visit_program(*ast); + SymtabVisitor{}.visit_program(*ast); + SteadystateVisitor{}.visit_program(*ast); + SymtabVisitor{}.visit_program(*ast); + NeuronSolveVisitor{}.visit_program(*ast); + SolveBlockVisitor{}.visit_program(*ast); + SymtabVisitor{true}.visit_program(*ast); + + CodegenHelperVisitor v; + const auto info = v.analyze(*ast); + + return info; +} + SCENARIO("unusual / failing mod files", "[codegen][var_order]") { GIVEN("cal_mig.mod : USEION variables declared as RANGE") { std::string nmodl_text = R"( @@ -293,3 +311,115 @@ TEST_CASE("Check ion write/read checks") { } } } + +SCENARIO("CVODE codegen") { + GIVEN("a mod file with a single KINETIC block") { + std::string input_nmodl = R"( + STATE { + x + } + KINETIC states { + ~ x << (a*c/3.2) + } + BREAKPOINT { + SOLVE states METHOD cnexp + })"; + + const auto& info = run_codegen_helper_get_info(input_nmodl); + THEN("Emit CVODE") { + REQUIRE(info.emit_cvode); + } + } + GIVEN("a mod file with a single DERIVATIVE block") { + std::string input_nmodl = R"( + STATE { + m + } + BREAKPOINT { + SOLVE state METHOD derivimplicit + } + DERIVATIVE state { + m' = 2 * m + } + )"; + const auto& info = run_codegen_helper_get_info(input_nmodl); + + THEN("Emit CVODE") { + REQUIRE(info.emit_cvode); + } + } + GIVEN("a mod file with a single PROCEDURE block solved with method `after_cvode`") { + std::string input_nmodl = R"( + BREAKPOINT { + SOLVE state METHOD after_cvode + } + PROCEDURE state() {} + )"; + + const auto& info = run_codegen_helper_get_info(input_nmodl); + + THEN("Emit CVODE") { + REQUIRE(info.emit_cvode); + } + } + GIVEN("a mod file with a single PROCEDURE block NOT solved with method `after_cvode`") { + std::string input_nmodl = R"( + BREAKPOINT { + SOLVE state METHOD cnexp + } + PROCEDURE state() {} + )"; + + const auto& info = run_codegen_helper_get_info(input_nmodl); + + THEN("Do not emit CVODE") { + REQUIRE(!info.emit_cvode); + } + } + GIVEN("a mod file with a DERIVATIVE and a KINETIC block") { + std::string input_nmodl = R"( + STATE { + m + x + } + BREAKPOINT { + SOLVE der METHOD derivimplicit + SOLVE kin METHOD cnexp + } + DERIVATIVE der { + m' = 2 * m + } + KINETIC kin { + ~ x << (a*c/3.2) + } + )"; + + const auto& info = run_codegen_helper_get_info(input_nmodl); + + THEN("Do not emit CVODE") { + REQUIRE(!info.emit_cvode); + } + } + GIVEN("a mod file with a PROCEDURE and a DERIVATIVE block") { + std::string input_nmodl = R"( + STATE { + m + } + BREAKPOINT { + SOLVE der METHOD derivimplicit + SOLVE func METHOD cnexp + } + DERIVATIVE der { + m' = 2 * m + } + PROCEDURE func() { + } + )"; + + const auto& info = run_codegen_helper_get_info(input_nmodl); + + THEN("Do not emit CVODE") { + REQUIRE(!info.emit_cvode); + } + } +} From 730a7ab938487e04f211afe5c87a9ab7896b099e Mon Sep 17 00:00:00 2001 From: Matthias Wolf Date: Wed, 21 Aug 2024 13:33:43 +0200 Subject: [PATCH 733/871] Add CMake export infrastructure (BlueBrain/nmodl#1288) NMODL Repo SHA: BlueBrain/nmodl@a5f27a14d05250a3c4466f4ef147e7096e6c077e --- cmake/nmodl/CMakeLists.txt | 20 ++++++++++++++++++++ cmake/nmodl/Config.cmake.in | 7 +++++++ src/nmodl/CMakeLists.txt | 7 +++++-- 3 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 cmake/nmodl/Config.cmake.in diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 84e01c07a8..5850e6d1bb 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -243,6 +243,26 @@ configure_file(share/nrnunits.lib ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnuni install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share/nmodl) +set(nmodl_BINARY bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) +add_executable(nmodl::nmodl ALIAS nmodl) + +install( + EXPORT nmodlTargets + FILE nmodlTargets.cmake + NAMESPACE nmodl:: + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfig.cmake" + INSTALL_DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/nmodlConfigVersion.cmake" + COMPATIBILITY AnyNewerVersion) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfigVersion.cmake" + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) + # to print compiler flags in the build status if(CMAKE_BUILD_TYPE) string(TOUPPER ${CMAKE_BUILD_TYPE} BUILD_TYPE_UPPER) diff --git a/cmake/nmodl/Config.cmake.in b/cmake/nmodl/Config.cmake.in new file mode 100644 index 0000000000..5ef7f0eb30 --- /dev/null +++ b/cmake/nmodl/Config.cmake.in @@ -0,0 +1,7 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/nmodlTargets.cmake") + +set(nmodl_BINARY ${PACKAGE_PREFIX_DIR}/@nmodl_BINARY@) + +check_required_components(nmodl) diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 5560b8f77d..14051dba96 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -38,7 +38,7 @@ add_dependencies(nmodl nmodl_copy_python_files nmodl_copy_solver_files) cpp_cc_configure_sanitizers(TARGET nmodl) # ============================================================================= -# Add dependency with nmodl python module (for consumer projects) +# Add dependency with nmodl Python module (for consumer projects) # ============================================================================= add_dependencies(nmodl pywrapper) @@ -49,4 +49,7 @@ endif() # ============================================================================= # Install executable # ============================================================================= -install(TARGETS nmodl DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin) +install( + TARGETS nmodl + EXPORT nmodlTargets + RUNTIME DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin) From 5ea0f96001fda33fd3036e3483512cab13f1991e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 22 Aug 2024 12:47:25 +0200 Subject: [PATCH 734/871] Remove `newton_diff_solver`. (BlueBrain/nmodl#1402) NMODL Repo SHA: BlueBrain/nmodl@7f62bf3fbc33853662a3dde662146425856c14a5 --- src/nmodl/solver/newton/newton.hpp | 85 +-------- test/nmodl/transpiler/unit/newton/newton.cpp | 184 ------------------- 2 files changed, 1 insertion(+), 268 deletions(-) diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 6fa9025438..77d6470e1f 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -29,8 +29,7 @@ namespace newton { * @brief Solver implementation details * * Implementation of Newton method for solving system of non-linear equations using Eigen - * - newton::newton_solver is the preferred option: requires user to provide Jacobian - * - newton::newton_numerical_diff_solver is the fallback option: Jacobian not required + * - newton::newton_solver with user, e.g. SymPy, provided Jacobian * * @{ */ @@ -93,88 +92,6 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, return -1; } -static constexpr double SQUARE_ROOT_ULP = 1e-7; -static constexpr double CUBIC_ROOT_ULP = 1e-5; - -/** - * \brief Newton method without user-provided Jacobian - * - * Newton method without user-provided Jacobian: given initial vector X and a - * functor that calculates `F(X)`, solves for \f$F(X) = 0\f$, starting with - * initial value of `X` by iterating: - * - * \f[ - * X_{n+1} = X_n - J(X_n)^{-1} F(X_n) - * \f] - * - * where `J(X)` is the Jacobian of `F(X)`, which is approximated numerically - * using a symmetric finite difference approximation to the derivative - * when \f$|F|^2 < eps^2\f$, solution has converged/ - * - * @return number of iterations (-1 if failed to converge) - */ -template -EIGEN_DEVICE_FUNC int newton_numerical_diff_solver(Eigen::Matrix& X, - FUNC functor, - double eps = EPS, - int max_iter = MAX_ITER) { - // Vector to store result of function F(X): - Eigen::Matrix F; - // Temporary storage for F(X+dx) - Eigen::Matrix F_p; - // Temporary storage for F(X-dx) - Eigen::Matrix F_m; - // Matrix to store jacobian of F(X): - Eigen::Matrix J; - // Solver iteration count: - int iter = 0; - while (iter < max_iter) { - // calculate F from X using user-supplied functor - functor(X, F); - // get error norm: here we use sqrt(|F|^2) - double error = F.norm(); - if (error < eps) { - // we have converged: return iteration count - return iter; - } - ++iter; - // calculate approximate Jacobian - for (int i = 0; i < N; ++i) { - // symmetric finite difference approximation to derivative - // df/dx ~= ( f(x+dx) - f(x-dx) ) / (2*dx) - // choose dx to be ~(ULP)^{1/3}*X[i] - // https://aip.scitation.org/doi/pdf/10.1063/1.4822971 - // also enforce a lower bound ~sqrt(ULP) to avoid dx being too small - double dX = std::max(CUBIC_ROOT_ULP * X[i], SQUARE_ROOT_ULP); - // F(X + dX) - X[i] += dX; - functor(X, F_p); - // F(X - dX) - X[i] -= 2.0 * dX; - functor(X, F_m); - F_p -= F_m; - // J = (F(X + dX) - F(X - dX)) / (2*dX) - J.col(i) = F_p / (2.0 * dX); - // restore X - X[i] += dX; - } - if (!J.IsRowMajor) { - J.transposeInPlace(); - } - Eigen::Matrix pivot; - Eigen::Matrix rowmax; - // Check if J is singular - if (nmodl::crout::Crout(N, J.data(), pivot.data(), rowmax.data()) < 0) { - return -1; - } - Eigen::Matrix X_solve; - nmodl::crout::solveCrout(N, J.data(), F.data(), X_solve.data(), pivot.data()); - X -= X_solve; - } - // If we fail to converge after max_iter iterations, return -1 - return -1; -} - /** * Newton method template specializations for \f$N <= 4\f$ Use explicit inverse * of `F` instead of LU decomposition. This is faster, as there is no pivoting diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 33f4086980..7ed95df892 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -17,190 +17,6 @@ using namespace nmodl; constexpr double max_error_norm = 1e-12; // NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers) -SCENARIO("Non-linear system to solve with Newton Numerical Diff Solver", "[numerical][solver]") { - GIVEN("1 linear eq") { - struct functor { - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - // Function F(X) to find F(X)=0 solution - F[0] = -3.0 * X[0] + 3.0; - } - }; - Eigen::Matrix X{22.2536}; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<1, functor>(X, fn); - fn(X, F); - THEN("find the solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(1.0, 0.01)); - REQUIRE(F.norm() < max_error_norm); - } - } - - GIVEN("1 non-linear eq") { - struct functor { - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - F[0] = -3.0 * X[0] + std::sin(X[0]) + std::log(X[0] * X[0] + 11.435243) + 3.0; - } - }; - Eigen::Matrix X{-0.21421}; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<1, functor>(X, fn); - fn(X, F); - THEN("find the solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(2.19943987001206, 0.01)); - REQUIRE(F.norm() < max_error_norm); - } - } - - GIVEN("system of 2 non-linear eqs") { - struct functor { - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1] - 1.0; - F[1] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[1] + 0.4; - } - }; - Eigen::Matrix X{0.2, 0.4}; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<2, functor>(X, fn); - fn(X, F); - THEN("find a solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE(F.norm() < max_error_norm); - } - } - - GIVEN("system of 3 non-linear eqs") { - struct functor { - double _x_old = 0.5; - double _y_old = -231.5; - double _z_old = 1.4565; - double dt = 0.2; - double a = 0.2354; - double b = 436.2; - double d = 23.1; - double e = 0.01; - double z = 0.99; - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - F(0) = -(-_x_old - dt * (a * std::pow(X[0], 2) + X[1]) + X[0]); - F(1) = -(_y_old - dt * (a * X[0] + b * X[1] + d) + X[1]); - F(2) = -(_z_old - dt * (e * z - 3.0 * X[0] + 2.0 * X[1]) + X[2]); - } - }; - Eigen::Matrix X{0.21231, 0.4435, -0.11537}; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<3, functor>(X, fn); - fn(X, F); - THEN("find a solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE(F.norm() < max_error_norm); - } - } - - GIVEN("system of 4 non-linear eqs") { - struct functor { - double X0_old = 1.2345; - double X1_old = 1.2345; - double X2_old = 1.2345; - double X3_old = 1.2345; - double dt = 0.2; - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - X0_old + 2.0 * dt / X[1]); - F[1] = -(X[1] - X1_old + dt * (4.0 * X[0] - 6.2 * X[1] + X[3])); - F[2] = -((X[2] * (X[2] - X2_old) - dt * (X[2] * (-1.2 * X[1] + 3.0) + 0.3)) / X[2]); - F[3] = -(-4.0 * X[0] * X[1] * X[2] * dt + X[3] - X3_old + 6.0 * dt / X[2]); - } - }; - Eigen::Matrix X{0.21231, 0.4435, -0.11537, -0.8124312}; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<4, functor>(X, fn); - fn(X, F); - THEN("find a solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE(F.norm() < max_error_norm); - } - } - - GIVEN("system of 5 non-linear eqs") { - struct functor { - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - F[0] = -3.0 * X[0] * X[2] + X[0] + 2.0 / X[1]; - F[1] = 4.0 * X[0] - 5.2 * X[1] + X[3]; - F[2] = 1.2 * X[1] + X[2] - 3.0 - 0.3 / X[2]; - F[3] = -4.0 * X[0] * X[1] * X[2] + X[3] + 6.0 / X[2]; - F[4] = (-4.0 * X[0] + (X[4] + cos(X[1])) * (X[1] * X[2] - X[3] * X[4])) / - (X[1] * X[2] - X[3] * X[4]); - } - }; - Eigen::Matrix X; - X << 8.234, -245.46, 123.123, 0.8343, 5.555; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<5, functor>(X, fn); - fn(X, F); - THEN("find a solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE(F.norm() < max_error_norm); - } - } - - GIVEN("system of 10 non-linear eqs") { - struct functor { - void operator()(const Eigen::Matrix& X, - Eigen::Matrix& F) const { - F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1]; - F[1] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[1]; - F[2] = 2.0 * X[1] + X[2] + 2.0 * X[3] * X[5] * X[7] - 3.0 * X[4] * X[8] - X[5]; - F[3] = 4.0 * X[0] - 0.29999999999999999 * std::pow(X[1], 2) + X[3] - - X[4] * X[6] * X[7]; - F[4] = -3.0 * X[0] * X[7] + 2.0 * X[1] - 4.0 * X[3] * X[8] + X[4]; - F[5] = -X[2] * X[5] * X[8] + 4.0 * X[3] - 0.29999999999999999 * X[4] * X[9] + X[5]; - F[6] = -3.0 * X[0] * X[1] - 2.1000000000000001 * X[3] * X[4] * X[5] + X[6] + - 2.0 * X[8]; - F[7] = 4.0 * X[0] - 0.29999999999999999 * X[6] * X[7] + X[7]; - F[8] = -3.0 * X[0] * X[1] - X[2] * X[3] * X[4] * std::pow(X[5], 2) + 2.0 * X[5] + - X[8]; - F[9] = -0.29999999999999999 * X[2] * X[4] + 4.0 * std::pow(X[9], 2) + X[9]; - } - }; - Eigen::Matrix X; - X << 8.234, -5.46, 1.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; - Eigen::Matrix F; - functor fn; - int iter_newton = newton::newton_numerical_diff_solver<10, functor>(X, fn); - fn(X, F); - THEN("find a solution") { - CAPTURE(iter_newton); - CAPTURE(X); - REQUIRE(iter_newton > 0); - REQUIRE(F.norm() < max_error_norm); - } - } -} - SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") { GIVEN("1 linear eq") { struct functor { From 4896b037b579d47c37217fa616ed8e6682b4b309 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 23 Aug 2024 11:06:07 +0200 Subject: [PATCH 735/871] Initialize _extcall_thread. (BlueBrain/nmodl#1408) Comparing with NOCMODL, we need `_extcall_thread` to be initialized early during mechanism registration. We also need it to share it's memory with the first thread. * Add test. NMODL Repo SHA: BlueBrain/nmodl@5343aa07cac0fae2c1a9cb073dd7a874ddaa76da --- .../codegen/codegen_neuron_cpp_visitor.cpp | 7 ++++++ .../usecases/global/test_callables.py | 25 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/global/test_callables.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index b99b451011..8249621466 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1119,6 +1119,13 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_line("register_mech({});", register_mech_args); } + if (info.thread_callback_register) { + printer->fmt_line("_extcall_thread.resize({});", info.thread_data_index + 1); + printer->fmt_line("thread_mem_init(_extcall_thread.data());"); + printer->fmt_line("{} = 0;", get_variable_name("thread_data_in_use", false)); + } + + /// type related information printer->add_newline(); printer->fmt_line("mech_type = nrn_get_mechtype({}[1]);", get_channel_info_var_name()); diff --git a/test/nmodl/transpiler/usecases/global/test_callables.py b/test/nmodl/transpiler/usecases/global/test_callables.py new file mode 100644 index 0000000000..ef7b544112 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/test_callables.py @@ -0,0 +1,25 @@ +from neuron import h, gui + + +def test_function(): + s = h.Section() + s.insert("shared_global") + + h.finitialize() + + assert s(0.5).shared_global.sum_arr() == 30.3 + + +def test_procedure(): + s = h.Section() + s.insert("shared_global") + + h.finitialize() + + s(0.5).shared_global.set_g_w(44.4) + assert h.g_w_shared_global == 44.4 + + +if __name__ == "__main__": + test_function() + test_procedure() From 1e4498d18677e1c0307b5ffbe71497bb1e211c0d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 23 Aug 2024 11:07:53 +0200 Subject: [PATCH 736/871] Rename FUNCTION/PROCEDURE arguments. (BlueBrain/nmodl#1407) The NMODL language supports hiding variable names, e.g. FUNCTION foo(cai) { printf("%e", cai); } should print the first argument passed to `foo`. Even if the the MOD files uses the `ca` ion and the interior concentration in particular. To avoid the issue safely, we rename all function arguments, by prefixing them with `_l`. Since that provides good compatibility with VERBATIM code. * Remove `CodegenCppVisitor::rename_function_arguments`. * Remove CodegenNeuronCppVisitor argument renaming. * Add usecase. NMODL Repo SHA: BlueBrain/nmodl@9ee32267c52412a90e2e3bd3382281a1d270ebbe --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 27 --------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 5 -- .../codegen/codegen_neuron_cpp_visitor.cpp | 10 +--- src/nmodl/main.cpp | 6 ++ src/nmodl/visitors/CMakeLists.txt | 1 + .../visitors/rename_function_arguments.cpp | 32 ++++++++++ .../visitors/rename_function_arguments.hpp | 19 ++++++ test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../visitor/rename_function_arguments.cpp | 57 ++++++++++++++++++ .../usecases/function/localize_arguments.mod | 60 +++++++++++++++++++ .../function/test_localize_arguments.py | 17 ++++++ 11 files changed, 194 insertions(+), 41 deletions(-) create mode 100644 src/nmodl/visitors/rename_function_arguments.cpp create mode 100644 src/nmodl/visitors/rename_function_arguments.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp create mode 100644 test/nmodl/transpiler/usecases/function/localize_arguments.mod create mode 100644 test/nmodl/transpiler/usecases/function/test_localize_arguments.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 8b28a533aa..11aa3349f7 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -621,32 +621,6 @@ void CodegenCppVisitor::print_statement_block(const ast::StatementBlock& node, } -/** - * \todo Issue with verbatim renaming. e.g. pattern.mod has info struct with - * index variable. If we use "index" instead of "indexes" as default argument - * then during verbatim replacement we don't know the index is which one. This - * is because verbatim renaming pass has already stripped out prefixes from - * the text. - */ -void CodegenCppVisitor::rename_function_arguments() { - const auto& default_arguments = stringutils::split_string(nrn_thread_arguments(), ','); - for (const auto& dirty_arg: default_arguments) { - const auto& arg = stringutils::trim(dirty_arg); - RenameVisitor v(arg, "arg_" + arg); - for (const auto& function: info.functions) { - if (has_parameter_of_name(function, arg)) { - function->accept(v); - } - } - for (const auto& function: info.procedures) { - if (has_parameter_of_name(function, arg)) { - function->accept(v); - } - } - } -} - - bool CodegenCppVisitor::is_functor_const(const ast::StatementBlock& variable_block, const ast::StatementBlock& functor_block) { // Create complete_block with both variable declarations (done in variable_block) and solver @@ -1396,7 +1370,6 @@ void CodegenCppVisitor::setup(const Program& node) { codegen_int_variables = get_int_variables(); update_index_semantics(); - rename_function_arguments(); info.semantic_variable_count = int_variables_size(); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 5f4546e9a4..64e5d587d1 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -891,11 +891,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void print_function(const ast::FunctionBlock& node); - /** - * Rename function/procedure arguments that conflict with default arguments - */ - void rename_function_arguments(); - /** * @brief Checks whether the functor_block generated by sympy solver modifies any variable * outside its scope. If it does then return false, so that the operator() of the struct functor diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 8249621466..661abea1df 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -257,15 +257,7 @@ void CodegenNeuronCppVisitor::print_function_or_procedure( printer->fmt_line("int ret_{} = 0;", name); } - if (node.is_procedure_block() || node.is_function_block()) { - const auto& parameters = node.get_parameters(); - auto result = std::find_if(parameters.begin(), parameters.end(), [](auto var) { - return var.get()->get_node_name() == "v"; - }); - if (result == parameters.end()) { - printer->fmt_line("auto v = inst.{}[id];", naming::VOLTAGE_UNUSED_VARIABLE); - } - } + printer->fmt_line("auto v = inst.{}[id];", naming::VOLTAGE_UNUSED_VARIABLE); print_statement_block(*node.get_statement_block(), false, false); printer->fmt_line("return ret_{};", name); diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 3e8d596a03..8decd3f582 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -39,6 +39,7 @@ #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" +#include "visitors/rename_function_arguments.hpp" #include "visitors/semantic_analysis_visitor.hpp" #include "visitors/solve_block_visitor.hpp" #include "visitors/steadystate_visitor.hpp" @@ -327,6 +328,11 @@ int main(int argc, const char* argv[]) { /// just visit the ast AstVisitor().visit_program(*ast); + { + logger->info("Running argument renaming visitor"); + RenameFunctionArgumentsVisitor().visit_program(*ast); + } + /// construct symbol table { logger->info("Running symtab visitor"); diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index c798c83254..262b6a623a 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -26,6 +26,7 @@ add_library( neuron_solve_visitor.cpp perf_visitor.cpp rename_visitor.cpp + rename_function_arguments.cpp semantic_analysis_visitor.cpp solve_block_visitor.cpp steadystate_visitor.cpp diff --git a/src/nmodl/visitors/rename_function_arguments.cpp b/src/nmodl/visitors/rename_function_arguments.cpp new file mode 100644 index 0000000000..97e8c4bd4c --- /dev/null +++ b/src/nmodl/visitors/rename_function_arguments.cpp @@ -0,0 +1,32 @@ +#include "rename_function_arguments.hpp" + +#include "rename_visitor.hpp" + +namespace nmodl { +namespace visitor { + +template +void RenameFunctionArgumentsVisitor::rename_arguments(Block& block) const { + auto parameters = block.get_parameters(); + auto parameter_names = std::vector{}; + + for (const auto& parameter: parameters) { + parameter_names.push_back(parameter->get_node_name()); + } + + for (const auto& parameter_name: parameter_names) { + auto v = RenameVisitor(parameter_name, "_l" + parameter_name); + block.accept(v); + } +} + +void RenameFunctionArgumentsVisitor::visit_function_block(ast::FunctionBlock& block) { + rename_arguments(block); +} + +void RenameFunctionArgumentsVisitor::visit_procedure_block(ast::ProcedureBlock& block) { + rename_arguments(block); +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/rename_function_arguments.hpp b/src/nmodl/visitors/rename_function_arguments.hpp new file mode 100644 index 0000000000..146d330c8d --- /dev/null +++ b/src/nmodl/visitors/rename_function_arguments.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "ast/function_block.hpp" +#include "ast/procedure_block.hpp" +#include "visitors/ast_visitor.hpp" + +namespace nmodl { +namespace visitor { + +class RenameFunctionArgumentsVisitor: public AstVisitor { + template + void rename_arguments(Block& block) const; + + void visit_function_block(ast::FunctionBlock& block) override; + void visit_procedure_block(ast::ProcedureBlock& block) override; +}; + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 1845367288..c5574453fc 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -52,6 +52,7 @@ add_executable( visitor/nmodl.cpp visitor/perf.cpp visitor/rename.cpp + visitor/rename_function_arguments.cpp visitor/semantic_analysis.cpp visitor/solve_block.cpp visitor/steadystate.cpp diff --git a/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp b/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp new file mode 100644 index 0000000000..c635c71b29 --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp @@ -0,0 +1,57 @@ +#include +#include + +#include "parser/nmodl_driver.hpp" +#include "visitors/rename_function_arguments.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace nmodl; +using namespace visitor; + +using Catch::Matchers::ContainsSubstring; +using nmodl::parser::NmodlDriver; + + +TEST_CASE("Rename function arguments") { + std::string mod_code = R"( +NEURON { + RANGE tau +} + +FUNCTION foo(a, v, cai) { + exp(tau + a + v*cai) +} + +PROCEDURE bar(tau, a, b, d) { + foo(tau, a*b, d) +} +)"; + + NmodlDriver driver; + const auto& ast = driver.parse_string(mod_code); + + auto visitor = nmodl::visitor::RenameFunctionArgumentsVisitor(); + visitor.visit_program(*ast); + + auto transformed_mod = nmodl::to_nmodl(*ast); + + SECTION("FUNCTION") { + std::string expected = R"( +FUNCTION foo(_la, _lv, _lcai) { + exp(tau+_la+_lv*_lcai) +} +)"; + + REQUIRE_THAT(transformed_mod, ContainsSubstring(expected)); + } + + SECTION("PROCEDURE") { + std::string expected = R"( +PROCEDURE bar(_ltau, _la, _lb, _ld) { + foo(_ltau, _la*_lb, _ld) +} +)"; + + REQUIRE_THAT(transformed_mod, ContainsSubstring(expected)); + } +} diff --git a/test/nmodl/transpiler/usecases/function/localize_arguments.mod b/test/nmodl/transpiler/usecases/function/localize_arguments.mod new file mode 100644 index 0000000000..0ad17cea06 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/localize_arguments.mod @@ -0,0 +1,60 @@ +NEURON { + SUFFIX localize_arguments + USEION na READ ina, nai + RANGE x + GLOBAL g +} + +ASSIGNED { + x + g + ina + nai +} + +STATE { + s +} + +PARAMETER { + p = 42.0 +} + +FUNCTION id_v(v) { + id_v = v +} + +FUNCTION id_nai(nai) { + id_nai = nai +} + +FUNCTION id_ina(ina) { + id_ina = ina +} + +FUNCTION id_x(x) { + id_x = x +} + +FUNCTION id_g(g) { + id_g = g +} + +FUNCTION id_s(s) { + id_s = s +} + +FUNCTION id_p(p) { + id_p = p +} + +: The name of top-LOCAL variables can't be used as +: a function argument. +: FUNCTION id_l(l) { +: id_l = l +: } + +INITIAL { + x = 42.0 + s = 42.0 +} diff --git a/test/nmodl/transpiler/usecases/function/test_localize_arguments.py b/test/nmodl/transpiler/usecases/function/test_localize_arguments.py new file mode 100644 index 0000000000..9ece8a9fc4 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/test_localize_arguments.py @@ -0,0 +1,17 @@ +import numpy as np + +from neuron import h, gui + + +s = h.Section() +s.insert("localize_arguments") + +h.finitialize() + +assert s(0.5).localize_arguments.id_v(22.0) == 22.0 +assert s(0.5).localize_arguments.id_nai(22.0) == 22.0 +assert s(0.5).localize_arguments.id_ina(22.0) == 22.0 +assert s(0.5).localize_arguments.id_x(22.0) == 22.0 +assert s(0.5).localize_arguments.id_g(22.0) == 22.0 +assert s(0.5).localize_arguments.id_s(22.0) == 22.0 +assert s(0.5).localize_arguments.id_p(22.0) == 22.0 From a15da668e1a925e6f484a45eb86e657095f529d5 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Aug 2024 09:41:21 +0200 Subject: [PATCH 737/871] Remove several bad tests. (BlueBrain/nmodl#1410) * Remove brittle string comparison test. This test adds very little value, but prevents many sensible changes. * Duplicated & bad test. It's bad because there's no reason the statements need to be in that order. * Already covered by 2-state variables. * Duplicated with the test below. * Duplicate of the test above. * Don't mix spaces and tabs. * Remove pointlessly verbose test. NMODL Repo SHA: BlueBrain/nmodl@198eabdf3bd96d534350d29708d883ad4472cbcd --- .../transpiler/unit/visitor/sympy_solver.cpp | 537 +----------------- 1 file changed, 3 insertions(+), 534 deletions(-) diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 3bcdccd9c6..5a0e4c42b1 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -1525,24 +1525,6 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", //============================================================================= SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { - GIVEN("1 state-var numeric LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x - } - LINEAR lin { - ~ x = 5 - })"; - std::string expected_text = R"( - LINEAR lin { - x = 5.0 - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); - } - } GIVEN("1 state-var symbolic LINEAR solve block") { std::string nmodl_text = R"( STATE { @@ -1581,28 +1563,6 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { REQUIRE(reindent_text(result[0]) == reindent_text(expected_text)); } } - GIVEN("Linear block, print in order") { - std::string nmodl_text = R"( - STATE { - x y - } - LINEAR lin { - ~ y = x + 1 - ~ x = 2 - })"; - std::string expected_result = R"( - LINEAR lin { - y = 3.0 - x = 2.0 - })"; - - THEN("Construct & solve linear system") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - - compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); - } - } GIVEN("Linear block, print in order, vectors") { std::string nmodl_text = R"( STATE { @@ -1625,36 +1585,6 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); } } - GIVEN("Linear block, greedy replacement, interleaved") { - std::string nmodl_text = R"( - STATE { - x y - } - LINEAR lin { - LOCAL a - a = 0 - ~ x + y = 1 - a = 1 - ~ y - x = 3 - a = 2 - })"; - std::string expected_result = R"( - LINEAR lin { - LOCAL a - a = 0 - x = -1.0 - a = 1 - y = 2.0 - a = 2 - })"; - - THEN("Construct & solve linear system") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - - compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); - } - } GIVEN("Linear block, by value replacement, interleaved") { std::string nmodl_text = R"( STATE { @@ -1722,8 +1652,8 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { LOCAL a ~ x = y + a if (a == 1) { - a = a + 1 - x = a + 1 + a = a + 1 + x = a + 1 } ~ y = a })"; @@ -1745,52 +1675,6 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { compare_blocks(reindent_text(result[0]), reindent_text(expected_result)); } } - GIVEN("3 state-var LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - x y z - } - LINEAR lin { - ~ x + 4*c*y = -6*a - ~ a + x/b + z - y = 1*b*b - ~ 10*x + 13*y - z/(a*a*b) = 14/c - })"; - std::string expected_text = R"( - LINEAR lin { - x = 2*b*(39*pow(a, 3)*b+28*pow(a, 2)*b-2*a*c-3*a+2*pow(b, 2)*c)/(40*pow(a, 2)*pow(b, 2)*c-13*pow(a, 2)*pow(b, 2)+b+4*c) - y = (-60*pow(a, 3)*pow(b, 2)*c-14*pow(a, 2)*pow(b, 2)+a*b*c-6*a*c-pow(b, 3)*c)/(c*(40*pow(a, 2)*pow(b, 2)*c-13*pow(a, 2)*pow(b, 2)+b+4*c)) - z = pow(a, 2)*b*(-40*a*b*pow(c, 2)-47*a*b*c-78*a*c+40*pow(b, 3)*pow(c, 2)-13*pow(b, 3)*c-14*b-56*c)/(c*(40*pow(a, 2)*pow(b, 2)*c-13*pow(a, 2)*pow(b,2)+b+4*c)) - })"; - - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - - compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); - } - } - GIVEN("array state-var numeric LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - s[3] - } - LINEAR lin { - ~ s[0] = 1 - ~ s[1] = 3 - ~ s[2] + s[1] = s[0] - })"; - std::string expected_text = R"( - LINEAR lin { - s[0] = 1.0 - s[1] = 3.0 - s[2] = -2.0 - })"; - THEN("solve analytically") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); - } - } GIVEN("4 state-var LINEAR solve block") { std::string nmodl_text = R"( STATE { @@ -1845,220 +1729,7 @@ SCENARIO("LINEAR solve block (SympySolver Visitor)", "[sympy][linear]") { compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); } } - GIVEN("12 state-var LINEAR solve block") { - std::string nmodl_text = R"( - STATE { - C1 C2 C3 C4 C5 I1 I2 I3 I4 I5 I6 O - } - LINEAR seqinitial { - ~ I1*bi1 + C2*b01 - C1*( fi1+f01) = 0 - ~ C1*f01 + I2*bi2 + C3*b02 - C2*(b01+fi2+f02) = 0 - ~ C2*f02 + I3*bi3 + C4*b03 - C3*(b02+fi3+f03) = 0 - ~ C3*f03 + I4*bi4 + C5*b04 - C4*(b03+fi4+f04) = 0 - ~ C4*f04 + I5*bi5 + O*b0O - C5*(b04+fi5+f0O) = 0 - ~ C5*f0O + I6*bin0 - O*(b0O+fin0) = 0 - ~ C1*fi1 + I2*b11 - I1*( bi1+f11) = 0 - ~ I1*f11 + C2*fi2 + I3*b12 - I2*(b11+bi2+f12) = 0 - ~ I2*f12 + C3*fi3 + I4*bi3 - I3*(b12+bi3+f13) = 0 - ~ I3*f13 + C4*fi4 + I5*b14 - I4*(b13+bi4+f14) = 0 - ~ I4*f14 + C5*fi5 + I6*b1n - I5*(b14+bi5+f1n) = 0 - ~ C1 + C2 + C3 + C4 + C5 + O + I1 + I2 + I3 + I4 + I5 + I6 = 1 - })"; - std::string expected_text = R"( - LINEAR seqinitial { - EIGEN_LINEAR_SOLVE[12]{ - }{ - }{ - nmodl_eigen_x[0] = C1 - nmodl_eigen_x[1] = C2 - nmodl_eigen_x[2] = C3 - nmodl_eigen_x[3] = C4 - nmodl_eigen_x[4] = C5 - nmodl_eigen_x[5] = I1 - nmodl_eigen_x[6] = I2 - nmodl_eigen_x[7] = I3 - nmodl_eigen_x[8] = I4 - nmodl_eigen_x[9] = I5 - nmodl_eigen_x[10] = I6 - nmodl_eigen_x[11] = O - nmodl_eigen_f[0] = 0 - nmodl_eigen_f[1] = 0 - nmodl_eigen_f[2] = 0 - nmodl_eigen_f[3] = 0 - nmodl_eigen_f[4] = 0 - nmodl_eigen_f[5] = 0 - nmodl_eigen_f[6] = 0 - nmodl_eigen_f[7] = 0 - nmodl_eigen_f[8] = 0 - nmodl_eigen_f[9] = 0 - nmodl_eigen_f[10] = 0 - nmodl_eigen_f[11] = -1.0 - nmodl_eigen_j[0] = f01+fi1 - nmodl_eigen_j[12] = -b01 - nmodl_eigen_j[24] = 0 - nmodl_eigen_j[36] = 0 - nmodl_eigen_j[48] = 0 - nmodl_eigen_j[60] = -bi1 - nmodl_eigen_j[72] = 0 - nmodl_eigen_j[84] = 0 - nmodl_eigen_j[96] = 0 - nmodl_eigen_j[108] = 0 - nmodl_eigen_j[120] = 0 - nmodl_eigen_j[132] = 0 - nmodl_eigen_j[1] = -f01 - nmodl_eigen_j[13] = b01+f02+fi2 - nmodl_eigen_j[25] = -b02 - nmodl_eigen_j[37] = 0 - nmodl_eigen_j[49] = 0 - nmodl_eigen_j[61] = 0 - nmodl_eigen_j[73] = -bi2 - nmodl_eigen_j[85] = 0 - nmodl_eigen_j[97] = 0 - nmodl_eigen_j[109] = 0 - nmodl_eigen_j[121] = 0 - nmodl_eigen_j[133] = 0 - nmodl_eigen_j[2] = 0 - nmodl_eigen_j[14] = -f02 - nmodl_eigen_j[26] = b02+f03+fi3 - nmodl_eigen_j[38] = -b03 - nmodl_eigen_j[50] = 0 - nmodl_eigen_j[62] = 0 - nmodl_eigen_j[74] = 0 - nmodl_eigen_j[86] = -bi3 - nmodl_eigen_j[98] = 0 - nmodl_eigen_j[110] = 0 - nmodl_eigen_j[122] = 0 - nmodl_eigen_j[134] = 0 - nmodl_eigen_j[3] = 0 - nmodl_eigen_j[15] = 0 - nmodl_eigen_j[27] = -f03 - nmodl_eigen_j[39] = b03+f04+fi4 - nmodl_eigen_j[51] = -b04 - nmodl_eigen_j[63] = 0 - nmodl_eigen_j[75] = 0 - nmodl_eigen_j[87] = 0 - nmodl_eigen_j[99] = -bi4 - nmodl_eigen_j[111] = 0 - nmodl_eigen_j[123] = 0 - nmodl_eigen_j[135] = 0 - nmodl_eigen_j[4] = 0 - nmodl_eigen_j[16] = 0 - nmodl_eigen_j[28] = 0 - nmodl_eigen_j[40] = -f04 - nmodl_eigen_j[52] = b04+f0O+fi5 - nmodl_eigen_j[64] = 0 - nmodl_eigen_j[76] = 0 - nmodl_eigen_j[88] = 0 - nmodl_eigen_j[100] = 0 - nmodl_eigen_j[112] = -bi5 - nmodl_eigen_j[124] = 0 - nmodl_eigen_j[136] = -b0O - nmodl_eigen_j[5] = 0 - nmodl_eigen_j[17] = 0 - nmodl_eigen_j[29] = 0 - nmodl_eigen_j[41] = 0 - nmodl_eigen_j[53] = -f0O - nmodl_eigen_j[65] = 0 - nmodl_eigen_j[77] = 0 - nmodl_eigen_j[89] = 0 - nmodl_eigen_j[101] = 0 - nmodl_eigen_j[113] = 0 - nmodl_eigen_j[125] = -bin0 - nmodl_eigen_j[137] = b0O+fin0 - nmodl_eigen_j[6] = -fi1 - nmodl_eigen_j[18] = 0 - nmodl_eigen_j[30] = 0 - nmodl_eigen_j[42] = 0 - nmodl_eigen_j[54] = 0 - nmodl_eigen_j[66] = bi1+f11 - nmodl_eigen_j[78] = -b11 - nmodl_eigen_j[90] = 0 - nmodl_eigen_j[102] = 0 - nmodl_eigen_j[114] = 0 - nmodl_eigen_j[126] = 0 - nmodl_eigen_j[138] = 0 - nmodl_eigen_j[7] = 0 - nmodl_eigen_j[19] = -fi2 - nmodl_eigen_j[31] = 0 - nmodl_eigen_j[43] = 0 - nmodl_eigen_j[55] = 0 - nmodl_eigen_j[67] = -f11 - nmodl_eigen_j[79] = b11+bi2+f12 - nmodl_eigen_j[91] = -b12 - nmodl_eigen_j[103] = 0 - nmodl_eigen_j[115] = 0 - nmodl_eigen_j[127] = 0 - nmodl_eigen_j[139] = 0 - nmodl_eigen_j[8] = 0 - nmodl_eigen_j[20] = 0 - nmodl_eigen_j[32] = -fi3 - nmodl_eigen_j[44] = 0 - nmodl_eigen_j[56] = 0 - nmodl_eigen_j[68] = 0 - nmodl_eigen_j[80] = -f12 - nmodl_eigen_j[92] = b12+bi3+f13 - nmodl_eigen_j[104] = -bi3 - nmodl_eigen_j[116] = 0 - nmodl_eigen_j[128] = 0 - nmodl_eigen_j[140] = 0 - nmodl_eigen_j[9] = 0 - nmodl_eigen_j[21] = 0 - nmodl_eigen_j[33] = 0 - nmodl_eigen_j[45] = -fi4 - nmodl_eigen_j[57] = 0 - nmodl_eigen_j[69] = 0 - nmodl_eigen_j[81] = 0 - nmodl_eigen_j[93] = -f13 - nmodl_eigen_j[105] = b13+bi4+f14 - nmodl_eigen_j[117] = -b14 - nmodl_eigen_j[129] = 0 - nmodl_eigen_j[141] = 0 - nmodl_eigen_j[10] = 0 - nmodl_eigen_j[22] = 0 - nmodl_eigen_j[34] = 0 - nmodl_eigen_j[46] = 0 - nmodl_eigen_j[58] = -fi5 - nmodl_eigen_j[70] = 0 - nmodl_eigen_j[82] = 0 - nmodl_eigen_j[94] = 0 - nmodl_eigen_j[106] = -f14 - nmodl_eigen_j[118] = b14+bi5+f1n - nmodl_eigen_j[130] = -b1n - nmodl_eigen_j[142] = 0 - nmodl_eigen_j[11] = -1.0 - nmodl_eigen_j[23] = -1.0 - nmodl_eigen_j[35] = -1.0 - nmodl_eigen_j[47] = -1.0 - nmodl_eigen_j[59] = -1.0 - nmodl_eigen_j[71] = -1.0 - nmodl_eigen_j[83] = -1.0 - nmodl_eigen_j[95] = -1.0 - nmodl_eigen_j[107] = -1.0 - nmodl_eigen_j[119] = -1.0 - nmodl_eigen_j[131] = -1.0 - nmodl_eigen_j[143] = -1.0 - }{ - C1 = nmodl_eigen_x[0] - C2 = nmodl_eigen_x[1] - C3 = nmodl_eigen_x[2] - C4 = nmodl_eigen_x[3] - C5 = nmodl_eigen_x[4] - I1 = nmodl_eigen_x[5] - I2 = nmodl_eigen_x[6] - I3 = nmodl_eigen_x[7] - I4 = nmodl_eigen_x[8] - I5 = nmodl_eigen_x[9] - I6 = nmodl_eigen_x[10] - O = nmodl_eigen_x[11] - }{ - } - })"; - THEN("return matrix system to be solved") { - auto result = - run_sympy_solver_visitor(nmodl_text, false, false, AstNodeType::LINEAR_BLOCK); - compare_blocks(reindent_text(result[0]), reindent_text(expected_text)); - } - } + GIVEN("LINEAR solve block with an explicit SOLVEFOR statement") { std::string nmodl_text = R"( STATE { @@ -2261,205 +1932,3 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym } } } - -/// Helper for creating C codegen visitor -std::shared_ptr create_coreneuron_cpp_visitor( - const std::shared_ptr& ast, - const std::string& /* text */, - std::stringstream& ss, - bool inline_visitor = true, - bool pade = false, - bool cse = false) { - /// construct symbol table - SymtabVisitor().visit_program(*ast); - - /// run all necessary pass - if (inline_visitor) { - InlineVisitor().visit_program(*ast); - } - // unroll loops and fold constants - ConstantFolderVisitor().visit_program(*ast); - LoopUnrollVisitor().visit_program(*ast); - ConstantFolderVisitor().visit_program(*ast); - SymtabVisitor().visit_program(*ast); - - // run SympySolver on AST - SympySolverVisitor(pade, cse).visit_program(*ast); - SymtabVisitor(true).visit_program(*ast); - - // Solve states - NeuronSolveVisitor().visit_program(*ast); - SolveBlockVisitor().visit_program(*ast); - - // Update symtab before CodegenCoreneuronCppVisitor - SymtabVisitor(true).visit_program(*ast); - - // check that, after visitor rearrangement, parents are still up-to-date - CheckParentVisitor().check_ast(*ast); - - /// create C code generation visitor - auto cv = std::make_shared("temp.mod", ss, "double", false); - cv->setup(*ast); - return cv; -} - -/// print entire code -std::string get_cpp_code(const std::string& nmodl_text) { - const auto& ast = NmodlDriver().parse_string(nmodl_text); - std::stringstream ss; - auto cvisitor = create_coreneuron_cpp_visitor(ast, nmodl_text, ss); - cvisitor->visit_program(*ast); - auto generated_string = ss.str(); - return reindent_text(generated_string); -} - -SCENARIO("Code generation for EigenNewtonSolver", "[visitor][solver][sympy][derivimplicit]") { - GIVEN("A mod file containing two SOLVE statements") { - std::string const nmodl_text = R"( - NEURON { - SUFFIX cacum - USEION ca READ ica WRITE cai - RANGE depth, tau, cai0 - } - - UNITS { - (mM) = (milli/liter) - (mA) = (milliamp) - F = 96485.3 (coulombs) - } - - PARAMETER { - depth = 1 (nm) : assume volume = area*depth - tau = 10 (ms) - cai0 = 50e-6 (mM) : Requires explicit use in INITIAL - : block for it to take precedence over cai0_ca_ion - : Do not forget to initialize in hoc if different - : from this default. - } - - ASSIGNED { - ica (mA/cm2) - } - - STATE { - cai (mM) - } - - INITIAL { - cai = cai0 - extra_solve() - } - - BREAKPOINT { - SOLVE integrate METHOD derivimplicit - } - - DERIVATIVE integrate { - cai' = -ica/depth/F/2 * (1e7) + (cai0 - cai)/tau - } - - PROCEDURE extra_solve() { - SOLVE integrate - } - )"; - - THEN("Three different functor structs defined and used") { - auto const generated = get_cpp_code(nmodl_text); - - // Expected functor definitions - std::string expected_functor_cacum_0_definition = - R"(struct functor_cacum_0 { - NrnThread* nt; - cacum_Instance* inst; - int id; - int pnodecount; - double v; - const Datum* indexes; - double* data; - ThreadDatum* thread; - double old_cai; - - void initialize() { - old_cai = inst->cai[id]; - } - - functor_cacum_0(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) - : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes), data(data), thread(thread) - {} - void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { - const double* nmodl_eigen_x = nmodl_eigen_xm.data(); - double* nmodl_eigen_j = nmodl_eigen_jm.data(); - double* nmodl_eigen_f = nmodl_eigen_fm.data(); - nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); - nmodl_eigen_j[static_cast(0)] =)"; - - - std::string expected_functor_cacum_1_definition = - R"(struct functor_cacum_1 { - NrnThread* nt; - cacum_Instance* inst; - int id; - int pnodecount; - double v; - const Datum* indexes; - double* data; - ThreadDatum* thread; - double old_cai; - - void initialize() { - old_cai = inst->cai[id]; - } - - functor_cacum_1(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) - : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes), data(data), thread(thread) - {} - void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { - const double* nmodl_eigen_x = nmodl_eigen_xm.data(); - double* nmodl_eigen_j = nmodl_eigen_jm.data(); - double* nmodl_eigen_f = nmodl_eigen_fm.data(); - nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); - nmodl_eigen_j[static_cast(0)] =)"; - - - std::string expected_functor_cacum_2_definition = - R"(struct functor_cacum_2 { - NrnThread* nt; - cacum_Instance* inst; - int id; - int pnodecount; - double v; - const Datum* indexes; - double* data; - ThreadDatum* thread; - double old_cai; - - void initialize() { - old_cai = inst->cai[id]; - } - - functor_cacum_2(NrnThread* nt, cacum_Instance* inst, int id, int pnodecount, double v, const Datum* indexes, double* data, ThreadDatum* thread) - : nt(nt), inst(inst), id(id), pnodecount(pnodecount), v(v), indexes(indexes), data(data), thread(thread) - {} - void operator()(const Eigen::Matrix& nmodl_eigen_xm, Eigen::Matrix& nmodl_eigen_fm, Eigen::Matrix& nmodl_eigen_jm) const { - const double* nmodl_eigen_x = nmodl_eigen_xm.data(); - double* nmodl_eigen_j = nmodl_eigen_jm.data(); - double* nmodl_eigen_f = nmodl_eigen_fm.data(); - nmodl_eigen_f[static_cast(0)] = -nmodl_eigen_x[static_cast(0)] * nt->_dt / inst->tau[id] - nmodl_eigen_x[static_cast(0)] + inst->cai0[id] * nt->_dt / inst->tau[id] + old_cai - 5000000.0 * nt->_dt * inst->ica[id] / (F * inst->depth[id]); - nmodl_eigen_j[static_cast(0)] =)"; - // Expected functor usages - std::string expected_functor_cacum_0_usage = - R"(functor_cacum_0 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; - std::string expected_functor_cacum_1_usage = - R"(functor_cacum_1 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; - std::string expected_functor_cacum_2_usage = - R"(functor_cacum_2 newton_functor(nt, inst, id, pnodecount, v, indexes, data, thread);)"; - - REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_0_definition)); - REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_1_definition)); - REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_2_definition)); - REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_0_usage)); - REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_1_usage)); - REQUIRE_THAT(generated, ContainsSubstring(expected_functor_cacum_2_usage)); - } - } -} From f0d9f649991ed6be4b241bd155c3dd221e64f7a0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Aug 2024 13:36:22 +0200 Subject: [PATCH 738/871] Remove broken code. (BlueBrain/nmodl#1411) For code that has `info.vectorize == false` the removed lines generated code, that fails to compile. NMODL Repo SHA: BlueBrain/nmodl@e4cb3d8e79991dddd420f87ab69629145f2aefae --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 661abea1df..15c1fc0fac 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -192,12 +192,6 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { _prop_id = _nrn_get_prop_id(_prop); )CODE"); } - if (!info.vectorize) { - printer->add_multi_line(R"CODE( - neuron::legacy::set_globals_from_prop(_prop, _lmc, _ml, _iml); - _ppvar = _nrn_mechanism_access_dparam(_prop); - )CODE"); - } printer->pop_block(); if (info.point_process) { From 125bfdbefc856764a8d1918e536b10de14f4a6a6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Aug 2024 13:39:21 +0200 Subject: [PATCH 739/871] Refactor `codegen_global_variables`. (BlueBrain/nmodl#1412) First decide (as much as possible) what's going to be a "global variable". Then print "all" elements of the global struct in one go. The exception are tables, since they require 2D C-arrays. NMODL Repo SHA: BlueBrain/nmodl@b4604109d923576e00989174fe868b2c14f9d301 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 78 +++++++++---------- 1 file changed, 35 insertions(+), 43 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 15c1fc0fac..a4b54e3b98 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -818,7 +818,12 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } } - if (!info.top_local_variables.empty()) { + + for (const auto& var: info.global_variables) { + codegen_global_variables.push_back(var); + } + + if (info.vectorize && !info.top_local_variables.empty()) { size_t prefix_sum = info.thread_var_data_size; size_t n_thread_vars = codegen_thread_variables.size(); for (size_t i = 0; i < info.top_local_variables.size(); ++i) { @@ -830,17 +835,42 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } if (!codegen_thread_variables.empty()) { - auto thread_data_size = info.thread_var_data_size + info.top_local_thread_size; - printer->fmt_line("int thread_data_in_use{};", value_initialize); - printer->fmt_line("{} thread_data[{}];", float_type, thread_data_size); - codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); + auto thread_data_size = info.thread_var_data_size + info.top_local_thread_size; symbol->set_as_array(thread_data_size); codegen_global_variables.push_back(symbol); } + for (const auto& var: info.state_vars) { + auto name = var->get_name() + "0"; + auto symbol = program_symtab->lookup(name); + if (symbol == nullptr) { + codegen_global_variables.push_back(make_symbol(name)); + } + } + + for (const auto& var: info.constant_variables) { + codegen_global_variables.push_back(var); + } + + for (const auto& var: codegen_global_variables) { + auto name = var->get_name(); + auto length = var->get_length(); + if (var->is_array()) { + printer->fmt_line("{} {}[{}] /* TODO init const-array */;", float_type, name, length); + } else { + double value{}; + if (auto const& value_ptr = var->get_value()) { + value = *value_ptr; + } + printer->fmt_line("{} {}{};", + float_type, + name, + print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); + } + } if (info.table_count > 0) { // basically the same code as coreNEURON uses @@ -869,44 +899,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } } - for (const auto& var: info.state_vars) { - auto name = var->get_name() + "0"; - auto symbol = program_symtab->lookup(name); - if (symbol == nullptr) { - printer->fmt_line("{} {}{};", float_type, name, value_initialize); - codegen_global_variables.push_back(make_symbol(name)); - } - } - - for (const auto& var: info.global_variables) { - auto name = var->get_name(); - auto length = var->get_length(); - if (var->is_array()) { - printer->fmt_line("{} {}[{}] /* TODO init const-array */;", float_type, name, length); - } else { - double value{}; - if (auto const& value_ptr = var->get_value()) { - value = *value_ptr; - } - printer->fmt_line("{} {}{};", - float_type, - name, - print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); - } - codegen_global_variables.push_back(var); - } - - for (const auto& var: info.constant_variables) { - auto const name = var->get_name(); - auto* const value_ptr = var->get_value().get(); - double const value{value_ptr ? *value_ptr : 0}; - printer->fmt_line("{} {}{};", - float_type, - name, - print_initializers ? fmt::format("{{{:g}}}", value) : std::string{}); - codegen_global_variables.push_back(var); - } - // for (const auto& f: info.function_tables) { if (!info.function_tables.empty()) { From 428b8cf7f99470e161a65b9e564749f3ca28ef99 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 29 Aug 2024 14:47:45 +0200 Subject: [PATCH 740/871] Newton error propagation based convergence check. (BlueBrain/nmodl#1403) * Set `dt` if needed. * Use `(X - X_old) / dt = dX`. Instead of `(X - X_old) = dt * dX`. The implemented scaling is the one implemented in NOCMODL. It should prevent the residual from growing linearly with `dt`. * Reduce MAX_ITER to 50 and adjust tolerance. * Newton error propagation based convergence check. * Add tests. Includes a test that solves the pump equation ~ X + Y <-> Z with extreme compartment sizes and steady state calculation. NMODL Repo SHA: BlueBrain/nmodl@7984a454d6bcde955975ffc4ce33766fd0884dba --- .../codegen/codegen_neuron_cpp_visitor.cpp | 14 +- src/nmodl/solver/newton/newton.hpp | 28 ++- src/nmodl/visitors/sympy_solver_visitor.cpp | 10 +- .../transpiler/unit/visitor/sympy_solver.cpp | 230 +++++++++--------- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/steady_state/minipump.mod | 43 ++++ .../usecases/steady_state/test_minipump.py | 86 +++++++ 7 files changed, 282 insertions(+), 130 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/steady_state/minipump.mod create mode 100644 test/nmodl/transpiler/usecases/steady_state/test_minipump.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index a4b54e3b98..28b44da042 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1475,9 +1475,21 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { print_rename_state_vars(); + if (!info.changed_dt.empty()) { + printer->fmt_line("double _save_prev_dt = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE)); + printer->fmt_line("{} = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE), + info.changed_dt); + } + print_initial_block(info.initial_node); - printer->pop_block(); + if (!info.changed_dt.empty()) { + printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); + } + + printer->pop_block(); printer->pop_block(); } diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 77d6470e1f..9107962441 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -34,8 +34,22 @@ namespace newton { * @{ */ -static constexpr int MAX_ITER = 1e3; -static constexpr double EPS = 1e-12; +static constexpr int MAX_ITER = 50; +static constexpr double EPS = 1e-13; + +template +bool is_converged(const Eigen::Matrix& X, + const Eigen::Matrix& J, + const Eigen::Matrix& F, + double eps) { + for (Eigen::Index i = 0; i < N; ++i) { + double square_error = J(i, Eigen::all).cwiseAbs2() * (eps * X).cwiseAbs2(); + if (F(i) * F(i) > square_error) { + return false; + } + } + return true; +} /** * \brief Newton method with user-provided Jacobian @@ -58,17 +72,14 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, int max_iter = MAX_ITER) { // Vector to store result of function F(X): Eigen::Matrix F; - // Matrix to store jacobian of F(X): + // Matrix to store Jacobian of F(X): Eigen::Matrix J; // Solver iteration count: int iter = -1; while (++iter < max_iter) { // calculate F, J from X using user-supplied functor functor(X, F, J); - // get error norm: here we use sqrt(|F|^2) - double error = F.norm(); - if (error < eps) { - // we have converged: return iteration count + if (is_converged(X, J, F, eps)) { return iter; } // In Eigen the default storage order is ColMajor. @@ -109,8 +120,7 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, int iter = -1; while (++iter < max_iter) { functor(X, F, J); - double error = F.norm(); - if (error < eps) { + if (is_converged(X, J, F, eps)) { return iter; } // The inverse can be called from within OpenACC regions without any issue, as opposed to diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index d944f94460..206145efbc 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -543,15 +543,15 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { pre_solve_statements.push_back(std::move(expression)); } // replace ODE with Euler equation - eq = x; + eq = "("; + eq.append(x); eq.append(x_array_index); - eq.append(" = "); + eq.append(" - "); eq.append(old_x); - eq.append(" + "); + eq.append(") / "); eq.append(codegen::naming::NTHREAD_DT_VARIABLE); - eq.append(" * ("); + eq.append(" = "); eq.append(dxdt); - eq.append(")"); logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); } } diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 5a0e4c42b1..486a243113 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -647,8 +647,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = m }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*mInf+mTau*(-nmodl_eigen_x[0]+old_m))/mTau - nmodl_eigen_j[0] = -(dt+mTau)/mTau + nmodl_eigen_f[0] = (dt*(-nmodl_eigen_x[0]+mInf)+mTau*(-nmodl_eigen_x[0]+old_m))/(dt*mTau) + nmodl_eigen_j[0] = (-dt-mTau)/(dt*mTau) }{ m = nmodl_eigen_x[0] }{ @@ -686,11 +686,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_y + nmodl_eigen_f[0] = (-nmodl_eigen_x[1]+a*dt+old_y)/dt nmodl_eigen_j[0] = 0 - nmodl_eigen_j[2] = -1.0 - nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_x - nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[2] = -1/dt + nmodl_eigen_f[1] = (-nmodl_eigen_x[0]+b*dt+old_x)/dt + nmodl_eigen_j[1] = -1/dt nmodl_eigen_j[3] = 0 }{ x = nmodl_eigen_x[0] @@ -730,11 +730,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = M[0] nmodl_eigen_x[1] = M[1] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_M_1 + nmodl_eigen_f[0] = (-nmodl_eigen_x[1]+a*dt+old_M_1)/dt nmodl_eigen_j[0] = 0 - nmodl_eigen_j[2] = -1.0 - nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_M_0 - nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[2] = -1/dt + nmodl_eigen_f[1] = (-nmodl_eigen_x[0]+b*dt+old_M_0)/dt + nmodl_eigen_j[1] = -1/dt nmodl_eigen_j[3] = 0 }{ M[0] = nmodl_eigen_x[0] @@ -775,13 +775,13 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+a*dt+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[2] = 0 b = b+1 - nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+b*dt+old_y)/dt nmodl_eigen_j[1] = 0 - nmodl_eigen_j[3] = -1.0 + nmodl_eigen_j[3] = -1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -853,12 +853,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+a*dt+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[2] = 0 - nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+b*dt+old_y)/dt nmodl_eigen_j[1] = 0 - nmodl_eigen_j[3] = -1.0 + nmodl_eigen_j[3] = -1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -903,15 +903,15 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x - nmodl_eigen_j[0] = -1.0 - nmodl_eigen_j[2] = a*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[1]*a+b)+old_x)/dt + nmodl_eigen_j[0] = -1/dt + nmodl_eigen_j[2] = a IF (b == 1) { a = a+1 } - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y - nmodl_eigen_j[1] = dt - nmodl_eigen_j[3] = a*dt-1.0 + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]+nmodl_eigen_x[1]*a)+old_y)/dt + nmodl_eigen_j[1] = 1.0 + nmodl_eigen_j[3] = a-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -929,15 +929,15 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x - nmodl_eigen_j[0] = -1.0 - nmodl_eigen_j[2] = a*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[1]*a+b)+old_x)/dt + nmodl_eigen_j[0] = -1/dt + nmodl_eigen_j[2] = a IF (b == 1) { a = a+1 } - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y - nmodl_eigen_j[1] = dt - nmodl_eigen_j[3] = a*dt-1.0 + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]+nmodl_eigen_x[1]*a)+old_y)/dt + nmodl_eigen_j[1] = 1.0 + nmodl_eigen_j[3] = a-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -984,18 +984,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = y nmodl_eigen_x[2] = z }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[2]*a+b*h)+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[3] = 0 - nmodl_eigen_j[6] = a*dt - nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y - nmodl_eigen_j[1] = 2.0*dt - nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[6] = a + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(2.0*nmodl_eigen_x[0]+c)+old_y)/dt + nmodl_eigen_j[1] = 2.0 + nmodl_eigen_j[4] = -1/dt nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z + nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(-nmodl_eigen_x[1]+nmodl_eigen_x[2]*d)+old_z)/dt nmodl_eigen_j[2] = 0 - nmodl_eigen_j[5] = -dt - nmodl_eigen_j[8] = d*dt-1.0 + nmodl_eigen_j[5] = -1.0 + nmodl_eigen_j[8] = d-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -1016,18 +1016,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = y nmodl_eigen_x[2] = z }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[2]*a+b*h)+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[3] = 0 - nmodl_eigen_j[6] = a*dt - nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y - nmodl_eigen_j[1] = 2.0*dt - nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[6] = a + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(2.0*nmodl_eigen_x[0]+c)+old_y)/dt + nmodl_eigen_j[1] = 2.0 + nmodl_eigen_j[4] = -1/dt nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z + nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(-nmodl_eigen_x[1]+nmodl_eigen_x[2]*d)+old_z)/dt nmodl_eigen_j[2] = 0 - nmodl_eigen_j[5] = -dt - nmodl_eigen_j[8] = d*dt-1.0 + nmodl_eigen_j[5] = -1.0 + nmodl_eigen_j[8] = d-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -1070,12 +1070,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc - nmodl_eigen_j[0] = -a*dt-1.0 - nmodl_eigen_j[2] = b*dt - nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m - nmodl_eigen_j[1] = a*dt - nmodl_eigen_j[3] = -b*dt-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt + nmodl_eigen_j[0] = -a-1/dt + nmodl_eigen_j[2] = b + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt + nmodl_eigen_j[1] = a + nmodl_eigen_j[3] = -b-1/dt }{ mc = nmodl_eigen_x[0] m = nmodl_eigen_x[1] @@ -1113,9 +1113,9 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc - nmodl_eigen_j[0] = -a*dt-1.0 - nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt + nmodl_eigen_j[0] = -a-1/dt + nmodl_eigen_j[2] = b nmodl_eigen_f[1] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]+1.0 nmodl_eigen_j[1] = -1.0 nmodl_eigen_j[3] = -1.0 @@ -1159,12 +1159,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc - nmodl_eigen_j[0] = -a*dt-1.0 - nmodl_eigen_j[2] = b*dt - nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m - nmodl_eigen_j[1] = a*dt - nmodl_eigen_j[3] = -b*dt-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt + nmodl_eigen_j[0] = -a-1/dt + nmodl_eigen_j[2] = b + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt + nmodl_eigen_j[1] = a + nmodl_eigen_j[3] = -b-1/dt }{ mc = nmodl_eigen_x[0] m = nmodl_eigen_x[1] @@ -1213,16 +1213,16 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[3] = p0 nmodl_eigen_x[4] = p1 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*beta*dt+old_c1 - nmodl_eigen_j[0] = -alpha*dt-1.0 - nmodl_eigen_j[5] = beta*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*alpha+nmodl_eigen_x[1]*beta)+old_c1)/dt + nmodl_eigen_j[0] = -alpha-1/dt + nmodl_eigen_j[5] = beta nmodl_eigen_j[10] = 0 nmodl_eigen_j[15] = 0 nmodl_eigen_j[20] = 0 - nmodl_eigen_f[1] = nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[1]*beta*dt-nmodl_eigen_x[1]*dt*k3p-nmodl_eigen_x[1]+nmodl_eigen_x[2]*dt*k4+old_o1 - nmodl_eigen_j[1] = alpha*dt - nmodl_eigen_j[6] = -beta*dt-dt*k3p-1.0 - nmodl_eigen_j[11] = dt*k4 + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*alpha-nmodl_eigen_x[1]*beta-nmodl_eigen_x[1]*k3p+nmodl_eigen_x[2]*k4)+old_o1)/dt + nmodl_eigen_j[1] = alpha + nmodl_eigen_j[6] = -beta-k3p-1/dt + nmodl_eigen_j[11] = k4 nmodl_eigen_j[16] = 0 nmodl_eigen_j[21] = 0 nmodl_eigen_f[2] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]-nmodl_eigen_x[2]+1.0 @@ -1231,12 +1231,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_j[12] = -1.0 nmodl_eigen_j[17] = 0 nmodl_eigen_j[22] = 0 - nmodl_eigen_f[3] = -nmodl_eigen_x[3]*dt*k1ca-nmodl_eigen_x[3]+nmodl_eigen_x[4]*dt*k2+old_p0 + nmodl_eigen_f[3] = (-nmodl_eigen_x[3]+dt*(-nmodl_eigen_x[3]*k1ca+nmodl_eigen_x[4]*k2)+old_p0)/dt nmodl_eigen_j[3] = 0 nmodl_eigen_j[8] = 0 nmodl_eigen_j[13] = 0 - nmodl_eigen_j[18] = -dt*k1ca-1.0 - nmodl_eigen_j[23] = dt*k2 + nmodl_eigen_j[18] = -k1ca-1/dt + nmodl_eigen_j[23] = k2 nmodl_eigen_f[4] = -nmodl_eigen_x[3]-nmodl_eigen_x[4]+1.0 nmodl_eigen_j[4] = 0 nmodl_eigen_j[9] = 0 @@ -1286,8 +1286,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = W[0] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 - nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[0]*B[0]+3.0*A[1])+old_W_0)/dt + nmodl_eigen_j[0] = -A[0]+B[0]-1/dt }{ W[0] = nmodl_eigen_x[0] }{ @@ -1328,12 +1328,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = M[0] nmodl_eigen_x[1] = M[1] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*B[0]+old_M_0 - nmodl_eigen_j[0] = -dt*A[0]-1.0 - nmodl_eigen_j[2] = dt*B[0] - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*A[1]-nmodl_eigen_x[1]*dt*B[1]-nmodl_eigen_x[1]+old_M_1 - nmodl_eigen_j[1] = dt*A[1] - nmodl_eigen_j[3] = -dt*B[1]-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[1]*B[0])+old_M_0)/dt + nmodl_eigen_j[0] = -A[0]-1/dt + nmodl_eigen_j[2] = B[0] + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*A[1]-nmodl_eigen_x[1]*B[1])+old_M_1)/dt + nmodl_eigen_j[1] = A[1] + nmodl_eigen_j[3] = -B[1]-1/dt }{ M[0] = nmodl_eigen_x[0] M[1] = nmodl_eigen_x[1] @@ -1372,8 +1372,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = W[0] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 - nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[0]*B[0]+3.0*A[1])+old_W_0)/dt + nmodl_eigen_j[0] = -A[0]+B[0]-1/dt }{ W[0] = nmodl_eigen_x[0] }{ @@ -1416,18 +1416,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = h nmodl_eigen_x[2] = n }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]-3.0*nmodl_eigen_x[1]*dt+old_m))/mtau - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau - nmodl_eigen_f[2] = (-nmodl_eigen_x[2]*dt+dt*ninf+ntau*(-nmodl_eigen_x[2]+old_n))/ntau - nmodl_eigen_j[0] = -(dt+mtau)/mtau - nmodl_eigen_j[3] = -3.0*dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]/mtau-nmodl_eigen_x[0]/dt-3.0*nmodl_eigen_x[1]+minf/mtau+old_m/dt + nmodl_eigen_j[0] = (-dt-mtau)/(dt*mtau) + nmodl_eigen_j[3] = -3.0 nmodl_eigen_j[6] = 0 - nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt - nmodl_eigen_j[4] = -(dt+htau)/htau + nmodl_eigen_f[1] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau-nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0] + nmodl_eigen_j[4] = (-dt-htau)/(dt*htau) nmodl_eigen_j[7] = 0 + nmodl_eigen_f[2] = (dt*(-nmodl_eigen_x[2]+ninf)+ntau*(-nmodl_eigen_x[2]+old_n))/(dt*ntau) nmodl_eigen_j[2] = 0 nmodl_eigen_j[5] = 0 - nmodl_eigen_j[8] = -(dt+ntau)/ntau + nmodl_eigen_j[8] = (-dt-ntau)/(dt*ntau) }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1474,12 +1474,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = m nmodl_eigen_x[1] = h }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+old_m))/mtau - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau - nmodl_eigen_j[0] = -(dt+mtau)/mtau + nmodl_eigen_f[0] = (dt*(-nmodl_eigen_x[0]+minf)+mtau*(-nmodl_eigen_x[0]+old_m))/(dt*mtau) + nmodl_eigen_j[0] = (-dt-mtau)/(dt*mtau) nmodl_eigen_j[2] = 0 - nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt - nmodl_eigen_j[3] = -(dt+htau)/htau + nmodl_eigen_f[1] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau- nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0] + nmodl_eigen_j[3] = (-dt-htau)/(dt*htau) }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1497,12 +1497,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = m nmodl_eigen_x[1] = h }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau - nmodl_eigen_f[1] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt+old_m))/mtau - nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0]*dt - nmodl_eigen_j[2] = -(dt+htau)/htau - nmodl_eigen_j[1] = -(dt+mtau)/mtau - nmodl_eigen_j[3] = dt + nmodl_eigen_f[0] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau-nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt + nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0] + nmodl_eigen_j[2] = (-dt-htau)/(dt*htau) + nmodl_eigen_f[1] = -nmodl_eigen_x[0]/mtau-nmodl_eigen_x[0]/dt+nmodl_eigen_x[1]+minf/mtau+old_m/dt + nmodl_eigen_j[1] = (-dt-mtau)/(dt*mtau) + nmodl_eigen_j[3] = 1.0 }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1862,12 +1862,12 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 - nmodl_eigen_j[0] = -dt*kf0_-1.0 - nmodl_eigen_j[2] = dt*kb0_ - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 - nmodl_eigen_j[1] = dt*kf0_ - nmodl_eigen_j[3] = -dt*kb0_-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*kf0_+nmodl_eigen_x[1]*kb0_)+old_C1)/dt + nmodl_eigen_j[0] = -kf0_-1/dt + nmodl_eigen_j[2] = kb0_ + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*kf0_-nmodl_eigen_x[1]*kb0_)+old_C2)/dt + nmodl_eigen_j[1] = kf0_ + nmodl_eigen_j[3] = -kb0_-1/dt }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] @@ -1904,20 +1904,20 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym EIGEN_NEWTON_SOLVE[2]{ LOCAL kf0_, kb0_, old_C1, old_C2 }{ - kb0_ = lowergamma(v) kf0_ = beta(v) + kb0_ = lowergamma(v) old_C1 = C1 old_C2 = C2 }{ nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 - nmodl_eigen_j[0] = -dt*kf0_-1.0 - nmodl_eigen_j[2] = dt*kb0_ - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 - nmodl_eigen_j[1] = dt*kf0_ - nmodl_eigen_j[3] = -dt*kb0_-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*kf0_+nmodl_eigen_x[1]*kb0_)+old_C1)/dt + nmodl_eigen_j[0] = -kf0_-1/dt + nmodl_eigen_j[2] = kb0_ + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*kf0_-nmodl_eigen_x[1]*kb0_)+old_C2)/dt + nmodl_eigen_j[1] = kf0_ + nmodl_eigen_j[3] = -kb0_-1/dt }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 3ce12e04fb..c5c12306ee 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -18,6 +18,7 @@ set(NMODL_USECASE_DIRS random suffix state + steady_state table useion at_time) diff --git a/test/nmodl/transpiler/usecases/steady_state/minipump.mod b/test/nmodl/transpiler/usecases/steady_state/minipump.mod new file mode 100644 index 0000000000..2027aecc85 --- /dev/null +++ b/test/nmodl/transpiler/usecases/steady_state/minipump.mod @@ -0,0 +1,43 @@ +NEURON { + SUFFIX minipump +} + +PARAMETER { + volA = 1e9 + volB = 1e9 + volC = 13.0 + kf = 3.0 + kb = 4.0 + + run_steady_state = 0.0 +} + +STATE { + X + Y + Z +} + +INITIAL { + X = 40.0 + Y = 8.0 + Z = 1.0 + + if(run_steady_state > 0.0) { + SOLVE state STEADYSTATE sparse + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + COMPARTMENT volA {X} + COMPARTMENT volB {Y} + COMPARTMENT volC {Z} + + ~ X + Y <-> Z (kf, kb) + + CONSERVE Y + Z = 8.0*volB + 1.0*volC +} diff --git a/test/nmodl/transpiler/usecases/steady_state/test_minipump.py b/test/nmodl/transpiler/usecases/steady_state/test_minipump.py new file mode 100644 index 0000000000..4521bb92a9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/steady_state/test_minipump.py @@ -0,0 +1,86 @@ +import sys +import pickle + +import numpy as np + +from neuron import h, gui + + +def run(steady_state): + s = h.Section() + + s.insert("minipump") + s.diam = 1.0 + + t_hoc = h.Vector().record(h._ref_t) + X_hoc = h.Vector().record(s(0.5).minipump._ref_X) + Y_hoc = h.Vector().record(s(0.5).minipump._ref_Y) + Z_hoc = h.Vector().record(s(0.5).minipump._ref_Z) + + h.run_steady_state_minipump = 1.0 if steady_state else 0.0 + + h.stdinit() + h.continuerun(1.0) + + t = np.array(t_hoc.as_numpy()) + X = np.array(X_hoc.as_numpy()) + Y = np.array(Y_hoc.as_numpy()) + Z = np.array(Z_hoc.as_numpy()) + + return t, X, Y, Z + + +def traces_filename(steady_state): + return "test_minipump{}.pkl".format("-steady_state" if steady_state else "") + + +def save_traces(t, X, Y, Z, steady_state): + with open(traces_filename(steady_state), "bw") as f: + pickle.dump({"t": t, "X": X, "Y": Y, "Z": Z}, f) + + +def load_traces(steady_state): + with open(traces_filename(steady_state), "br") as f: + d = pickle.load(f) + + return d["t"], d["X"], d["Y"], d["Z"] + + +def assert_almost_equal(actual, expected, rtol): + decimal = np.ceil(-np.log10(rtol * np.max(expected))) + np.testing.assert_almost_equal(actual, expected, decimal=decimal) + + +def check_traces(t, X, Y, Z, steady_state): + if len(sys.argv) < 2: + return + + codegen = sys.argv[1] + if codegen == "nocmodl": + save_traces(t, X, Y, Z, steady_state) + + else: + t_ref, X_ref, Y_ref, Z_ref = load_traces(steady_state) + + assert_almost_equal(t, t_ref, rtol=1e-8) + assert_almost_equal(X, X_ref, rtol=1e-8) + assert_almost_equal(Y, Y_ref, rtol=1e-8) + assert_almost_equal(Z, Z_ref, rtol=1e-8) + + +def check_solution(steady_state): + t, X, Y, Z = run(steady_state) + check_traces(t, X, Y, Z, steady_state=steady_state) + + +def test_steady_state(): + check_solution(steady_state=True) + + +def test_no_steady_state(): + check_solution(steady_state=False) + + +if __name__ == "__main__": + test_steady_state() + test_no_steady_state() From 8d42bddff16ac3e7e5c51fb3e387eb2255ec0976 Mon Sep 17 00:00:00 2001 From: Omar Awile Date: Thu, 29 Aug 2024 16:23:53 +0200 Subject: [PATCH 741/871] Use aligned_alloc as we do in CoreNEURON too (BlueBrain/nmodl#933) --------- Co-authored-by: Omar Awile Co-authored-by: Luc Grosheintz NMODL Repo SHA: BlueBrain/nmodl@3eab851dd4a31e6f8e082faf79d83d69b6482e2b --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index fbf87baa47..06022125ea 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -314,11 +314,12 @@ bool CodegenCoreneuronCppVisitor::optimize_ion_variable_copies() const { void CodegenCoreneuronCppVisitor::print_memory_allocation_routine() const { printer->add_newline(2); - auto args = "size_t num, size_t size, size_t alignment = 16"; + auto args = "size_t num, size_t size, size_t alignment = 64"; printer->fmt_push_block("static inline void* mem_alloc({})", args); - printer->add_line("void* ptr;"); - printer->add_line("posix_memalign(&ptr, alignment, num*size);"); - printer->add_line("memset(ptr, 0, size);"); + printer->add_line( + "size_t aligned_size = ((num*size + alignment - 1) / alignment) * alignment;"); + printer->add_line("void* ptr = aligned_alloc(alignment, aligned_size);"); + printer->add_line("memset(ptr, 0, aligned_size);"); printer->add_line("return ptr;"); printer->pop_block(); From 5e54b5023dc4b927436960e53a4daf67a7d54408 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 30 Aug 2024 09:01:36 +0200 Subject: [PATCH 742/871] Remove CodeCov. (BlueBrain/nmodl#1416) NMODL Repo SHA: BlueBrain/nmodl@3df436c2177b1ef46a42929f1b5e2577e74fbefb --- codecov.yaml | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 codecov.yaml diff --git a/codecov.yaml b/codecov.yaml deleted file mode 100644 index d65b40af13..0000000000 --- a/codecov.yaml +++ /dev/null @@ -1,11 +0,0 @@ -ignore: -- src/language/templates/ - -coverage: - status: - project: - default: - informational: true - patch: - default: - informational: true From b4ba87ac28ca5789c0abe0879fa567de57192b6d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 30 Aug 2024 11:58:18 +0200 Subject: [PATCH 743/871] Revert "Newton error propagation based convergence check. (BlueBrain/nmodl#1403)" (BlueBrain/nmodl#1417) This reverts commit 7984a454d6bcde955975ffc4ce33766fd0884dba. NMODL Repo SHA: BlueBrain/nmodl@d2441056232de083f66e55435b92d553a0555f6c --- .../codegen/codegen_neuron_cpp_visitor.cpp | 14 +- src/nmodl/solver/newton/newton.hpp | 28 +-- src/nmodl/visitors/sympy_solver_visitor.cpp | 10 +- .../transpiler/unit/visitor/sympy_solver.cpp | 230 +++++++++--------- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 - .../usecases/steady_state/minipump.mod | 43 ---- .../usecases/steady_state/test_minipump.py | 86 ------- 7 files changed, 130 insertions(+), 282 deletions(-) delete mode 100644 test/nmodl/transpiler/usecases/steady_state/minipump.mod delete mode 100644 test/nmodl/transpiler/usecases/steady_state/test_minipump.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 28b44da042..a4b54e3b98 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1475,21 +1475,9 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { print_rename_state_vars(); - if (!info.changed_dt.empty()) { - printer->fmt_line("double _save_prev_dt = {};", - get_variable_name(naming::NTHREAD_DT_VARIABLE)); - printer->fmt_line("{} = {};", - get_variable_name(naming::NTHREAD_DT_VARIABLE), - info.changed_dt); - } - print_initial_block(info.initial_node); - - if (!info.changed_dt.empty()) { - printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); - } - printer->pop_block(); + printer->pop_block(); } diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 9107962441..77d6470e1f 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -34,22 +34,8 @@ namespace newton { * @{ */ -static constexpr int MAX_ITER = 50; -static constexpr double EPS = 1e-13; - -template -bool is_converged(const Eigen::Matrix& X, - const Eigen::Matrix& J, - const Eigen::Matrix& F, - double eps) { - for (Eigen::Index i = 0; i < N; ++i) { - double square_error = J(i, Eigen::all).cwiseAbs2() * (eps * X).cwiseAbs2(); - if (F(i) * F(i) > square_error) { - return false; - } - } - return true; -} +static constexpr int MAX_ITER = 1e3; +static constexpr double EPS = 1e-12; /** * \brief Newton method with user-provided Jacobian @@ -72,14 +58,17 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, int max_iter = MAX_ITER) { // Vector to store result of function F(X): Eigen::Matrix F; - // Matrix to store Jacobian of F(X): + // Matrix to store jacobian of F(X): Eigen::Matrix J; // Solver iteration count: int iter = -1; while (++iter < max_iter) { // calculate F, J from X using user-supplied functor functor(X, F, J); - if (is_converged(X, J, F, eps)) { + // get error norm: here we use sqrt(|F|^2) + double error = F.norm(); + if (error < eps) { + // we have converged: return iteration count return iter; } // In Eigen the default storage order is ColMajor. @@ -120,7 +109,8 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, int iter = -1; while (++iter < max_iter) { functor(X, F, J); - if (is_converged(X, J, F, eps)) { + double error = F.norm(); + if (error < eps) { return iter; } // The inverse can be called from within OpenACC regions without any issue, as opposed to diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 206145efbc..d944f94460 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -543,15 +543,15 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { pre_solve_statements.push_back(std::move(expression)); } // replace ODE with Euler equation - eq = "("; - eq.append(x); + eq = x; eq.append(x_array_index); - eq.append(" - "); + eq.append(" = "); eq.append(old_x); - eq.append(") / "); + eq.append(" + "); eq.append(codegen::naming::NTHREAD_DT_VARIABLE); - eq.append(" = "); + eq.append(" * ("); eq.append(dxdt); + eq.append(")"); logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); } } diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 486a243113..5a0e4c42b1 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -647,8 +647,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = m }{ - nmodl_eigen_f[0] = (dt*(-nmodl_eigen_x[0]+mInf)+mTau*(-nmodl_eigen_x[0]+old_m))/(dt*mTau) - nmodl_eigen_j[0] = (-dt-mTau)/(dt*mTau) + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*mInf+mTau*(-nmodl_eigen_x[0]+old_m))/mTau + nmodl_eigen_j[0] = -(dt+mTau)/mTau }{ m = nmodl_eigen_x[0] }{ @@ -686,11 +686,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[1]+a*dt+old_y)/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_y nmodl_eigen_j[0] = 0 - nmodl_eigen_j[2] = -1/dt - nmodl_eigen_f[1] = (-nmodl_eigen_x[0]+b*dt+old_x)/dt - nmodl_eigen_j[1] = -1/dt + nmodl_eigen_j[2] = -1.0 + nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_x + nmodl_eigen_j[1] = -1.0 nmodl_eigen_j[3] = 0 }{ x = nmodl_eigen_x[0] @@ -730,11 +730,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = M[0] nmodl_eigen_x[1] = M[1] }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[1]+a*dt+old_M_1)/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_M_1 nmodl_eigen_j[0] = 0 - nmodl_eigen_j[2] = -1/dt - nmodl_eigen_f[1] = (-nmodl_eigen_x[0]+b*dt+old_M_0)/dt - nmodl_eigen_j[1] = -1/dt + nmodl_eigen_j[2] = -1.0 + nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_M_0 + nmodl_eigen_j[1] = -1.0 nmodl_eigen_j[3] = 0 }{ M[0] = nmodl_eigen_x[0] @@ -775,13 +775,13 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+a*dt+old_x)/dt - nmodl_eigen_j[0] = -1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x + nmodl_eigen_j[0] = -1.0 nmodl_eigen_j[2] = 0 b = b+1 - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+b*dt+old_y)/dt + nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y nmodl_eigen_j[1] = 0 - nmodl_eigen_j[3] = -1/dt + nmodl_eigen_j[3] = -1.0 }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -853,12 +853,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+a*dt+old_x)/dt - nmodl_eigen_j[0] = -1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x + nmodl_eigen_j[0] = -1.0 nmodl_eigen_j[2] = 0 - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+b*dt+old_y)/dt + nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y nmodl_eigen_j[1] = 0 - nmodl_eigen_j[3] = -1/dt + nmodl_eigen_j[3] = -1.0 }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -903,15 +903,15 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[1]*a+b)+old_x)/dt - nmodl_eigen_j[0] = -1/dt - nmodl_eigen_j[2] = a + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[2] = a*dt IF (b == 1) { a = a+1 } - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]+nmodl_eigen_x[1]*a)+old_y)/dt - nmodl_eigen_j[1] = 1.0 - nmodl_eigen_j[3] = a-1/dt + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y + nmodl_eigen_j[1] = dt + nmodl_eigen_j[3] = a*dt-1.0 }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -929,15 +929,15 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[1]*a+b)+old_x)/dt - nmodl_eigen_j[0] = -1/dt - nmodl_eigen_j[2] = a + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x + nmodl_eigen_j[0] = -1.0 + nmodl_eigen_j[2] = a*dt IF (b == 1) { a = a+1 } - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]+nmodl_eigen_x[1]*a)+old_y)/dt - nmodl_eigen_j[1] = 1.0 - nmodl_eigen_j[3] = a-1/dt + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y + nmodl_eigen_j[1] = dt + nmodl_eigen_j[3] = a*dt-1.0 }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -984,18 +984,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = y nmodl_eigen_x[2] = z }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[2]*a+b*h)+old_x)/dt - nmodl_eigen_j[0] = -1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x + nmodl_eigen_j[0] = -1.0 nmodl_eigen_j[3] = 0 - nmodl_eigen_j[6] = a - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(2.0*nmodl_eigen_x[0]+c)+old_y)/dt - nmodl_eigen_j[1] = 2.0 - nmodl_eigen_j[4] = -1/dt + nmodl_eigen_j[6] = a*dt + nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y + nmodl_eigen_j[1] = 2.0*dt + nmodl_eigen_j[4] = -1.0 nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(-nmodl_eigen_x[1]+nmodl_eigen_x[2]*d)+old_z)/dt + nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z nmodl_eigen_j[2] = 0 - nmodl_eigen_j[5] = -1.0 - nmodl_eigen_j[8] = d-1/dt + nmodl_eigen_j[5] = -dt + nmodl_eigen_j[8] = d*dt-1.0 }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -1016,18 +1016,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = y nmodl_eigen_x[2] = z }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[2]*a+b*h)+old_x)/dt - nmodl_eigen_j[0] = -1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x + nmodl_eigen_j[0] = -1.0 nmodl_eigen_j[3] = 0 - nmodl_eigen_j[6] = a - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(2.0*nmodl_eigen_x[0]+c)+old_y)/dt - nmodl_eigen_j[1] = 2.0 - nmodl_eigen_j[4] = -1/dt + nmodl_eigen_j[6] = a*dt + nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y + nmodl_eigen_j[1] = 2.0*dt + nmodl_eigen_j[4] = -1.0 nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(-nmodl_eigen_x[1]+nmodl_eigen_x[2]*d)+old_z)/dt + nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z nmodl_eigen_j[2] = 0 - nmodl_eigen_j[5] = -1.0 - nmodl_eigen_j[8] = d-1/dt + nmodl_eigen_j[5] = -dt + nmodl_eigen_j[8] = d*dt-1.0 }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -1070,12 +1070,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt - nmodl_eigen_j[0] = -a-1/dt - nmodl_eigen_j[2] = b - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt - nmodl_eigen_j[1] = a - nmodl_eigen_j[3] = -b-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc + nmodl_eigen_j[0] = -a*dt-1.0 + nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m + nmodl_eigen_j[1] = a*dt + nmodl_eigen_j[3] = -b*dt-1.0 }{ mc = nmodl_eigen_x[0] m = nmodl_eigen_x[1] @@ -1113,9 +1113,9 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt - nmodl_eigen_j[0] = -a-1/dt - nmodl_eigen_j[2] = b + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc + nmodl_eigen_j[0] = -a*dt-1.0 + nmodl_eigen_j[2] = b*dt nmodl_eigen_f[1] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]+1.0 nmodl_eigen_j[1] = -1.0 nmodl_eigen_j[3] = -1.0 @@ -1159,12 +1159,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt - nmodl_eigen_j[0] = -a-1/dt - nmodl_eigen_j[2] = b - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt - nmodl_eigen_j[1] = a - nmodl_eigen_j[3] = -b-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc + nmodl_eigen_j[0] = -a*dt-1.0 + nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m + nmodl_eigen_j[1] = a*dt + nmodl_eigen_j[3] = -b*dt-1.0 }{ mc = nmodl_eigen_x[0] m = nmodl_eigen_x[1] @@ -1213,16 +1213,16 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[3] = p0 nmodl_eigen_x[4] = p1 }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*alpha+nmodl_eigen_x[1]*beta)+old_c1)/dt - nmodl_eigen_j[0] = -alpha-1/dt - nmodl_eigen_j[5] = beta + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*beta*dt+old_c1 + nmodl_eigen_j[0] = -alpha*dt-1.0 + nmodl_eigen_j[5] = beta*dt nmodl_eigen_j[10] = 0 nmodl_eigen_j[15] = 0 nmodl_eigen_j[20] = 0 - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*alpha-nmodl_eigen_x[1]*beta-nmodl_eigen_x[1]*k3p+nmodl_eigen_x[2]*k4)+old_o1)/dt - nmodl_eigen_j[1] = alpha - nmodl_eigen_j[6] = -beta-k3p-1/dt - nmodl_eigen_j[11] = k4 + nmodl_eigen_f[1] = nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[1]*beta*dt-nmodl_eigen_x[1]*dt*k3p-nmodl_eigen_x[1]+nmodl_eigen_x[2]*dt*k4+old_o1 + nmodl_eigen_j[1] = alpha*dt + nmodl_eigen_j[6] = -beta*dt-dt*k3p-1.0 + nmodl_eigen_j[11] = dt*k4 nmodl_eigen_j[16] = 0 nmodl_eigen_j[21] = 0 nmodl_eigen_f[2] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]-nmodl_eigen_x[2]+1.0 @@ -1231,12 +1231,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_j[12] = -1.0 nmodl_eigen_j[17] = 0 nmodl_eigen_j[22] = 0 - nmodl_eigen_f[3] = (-nmodl_eigen_x[3]+dt*(-nmodl_eigen_x[3]*k1ca+nmodl_eigen_x[4]*k2)+old_p0)/dt + nmodl_eigen_f[3] = -nmodl_eigen_x[3]*dt*k1ca-nmodl_eigen_x[3]+nmodl_eigen_x[4]*dt*k2+old_p0 nmodl_eigen_j[3] = 0 nmodl_eigen_j[8] = 0 nmodl_eigen_j[13] = 0 - nmodl_eigen_j[18] = -k1ca-1/dt - nmodl_eigen_j[23] = k2 + nmodl_eigen_j[18] = -dt*k1ca-1.0 + nmodl_eigen_j[23] = dt*k2 nmodl_eigen_f[4] = -nmodl_eigen_x[3]-nmodl_eigen_x[4]+1.0 nmodl_eigen_j[4] = 0 nmodl_eigen_j[9] = 0 @@ -1286,8 +1286,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = W[0] }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[0]*B[0]+3.0*A[1])+old_W_0)/dt - nmodl_eigen_j[0] = -A[0]+B[0]-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 + nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 }{ W[0] = nmodl_eigen_x[0] }{ @@ -1328,12 +1328,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = M[0] nmodl_eigen_x[1] = M[1] }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[1]*B[0])+old_M_0)/dt - nmodl_eigen_j[0] = -A[0]-1/dt - nmodl_eigen_j[2] = B[0] - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*A[1]-nmodl_eigen_x[1]*B[1])+old_M_1)/dt - nmodl_eigen_j[1] = A[1] - nmodl_eigen_j[3] = -B[1]-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*B[0]+old_M_0 + nmodl_eigen_j[0] = -dt*A[0]-1.0 + nmodl_eigen_j[2] = dt*B[0] + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*A[1]-nmodl_eigen_x[1]*dt*B[1]-nmodl_eigen_x[1]+old_M_1 + nmodl_eigen_j[1] = dt*A[1] + nmodl_eigen_j[3] = -dt*B[1]-1.0 }{ M[0] = nmodl_eigen_x[0] M[1] = nmodl_eigen_x[1] @@ -1372,8 +1372,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = W[0] }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[0]*B[0]+3.0*A[1])+old_W_0)/dt - nmodl_eigen_j[0] = -A[0]+B[0]-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 + nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 }{ W[0] = nmodl_eigen_x[0] }{ @@ -1416,18 +1416,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = h nmodl_eigen_x[2] = n }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]/mtau-nmodl_eigen_x[0]/dt-3.0*nmodl_eigen_x[1]+minf/mtau+old_m/dt - nmodl_eigen_j[0] = (-dt-mtau)/(dt*mtau) - nmodl_eigen_j[3] = -3.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]-3.0*nmodl_eigen_x[1]*dt+old_m))/mtau + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau + nmodl_eigen_f[2] = (-nmodl_eigen_x[2]*dt+dt*ninf+ntau*(-nmodl_eigen_x[2]+old_n))/ntau + nmodl_eigen_j[0] = -(dt+mtau)/mtau + nmodl_eigen_j[3] = -3.0*dt nmodl_eigen_j[6] = 0 - nmodl_eigen_f[1] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau-nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt - nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0] - nmodl_eigen_j[4] = (-dt-htau)/(dt*htau) + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt + nmodl_eigen_j[4] = -(dt+htau)/htau nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = (dt*(-nmodl_eigen_x[2]+ninf)+ntau*(-nmodl_eigen_x[2]+old_n))/(dt*ntau) nmodl_eigen_j[2] = 0 nmodl_eigen_j[5] = 0 - nmodl_eigen_j[8] = (-dt-ntau)/(dt*ntau) + nmodl_eigen_j[8] = -(dt+ntau)/ntau }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1474,12 +1474,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = m nmodl_eigen_x[1] = h }{ - nmodl_eigen_f[0] = (dt*(-nmodl_eigen_x[0]+minf)+mtau*(-nmodl_eigen_x[0]+old_m))/(dt*mtau) - nmodl_eigen_j[0] = (-dt-mtau)/(dt*mtau) + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+old_m))/mtau + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau + nmodl_eigen_j[0] = -(dt+mtau)/mtau nmodl_eigen_j[2] = 0 - nmodl_eigen_f[1] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau- nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt - nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0] - nmodl_eigen_j[3] = (-dt-htau)/(dt*htau) + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt + nmodl_eigen_j[3] = -(dt+htau)/htau }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1497,12 +1497,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = m nmodl_eigen_x[1] = h }{ - nmodl_eigen_f[0] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau-nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt - nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0] - nmodl_eigen_j[2] = (-dt-htau)/(dt*htau) - nmodl_eigen_f[1] = -nmodl_eigen_x[0]/mtau-nmodl_eigen_x[0]/dt+nmodl_eigen_x[1]+minf/mtau+old_m/dt - nmodl_eigen_j[1] = (-dt-mtau)/(dt*mtau) - nmodl_eigen_j[3] = 1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau + nmodl_eigen_f[1] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt+old_m))/mtau + nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0]*dt + nmodl_eigen_j[2] = -(dt+htau)/htau + nmodl_eigen_j[1] = -(dt+mtau)/mtau + nmodl_eigen_j[3] = dt }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1862,12 +1862,12 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*kf0_+nmodl_eigen_x[1]*kb0_)+old_C1)/dt - nmodl_eigen_j[0] = -kf0_-1/dt - nmodl_eigen_j[2] = kb0_ - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*kf0_-nmodl_eigen_x[1]*kb0_)+old_C2)/dt - nmodl_eigen_j[1] = kf0_ - nmodl_eigen_j[3] = -kb0_-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 + nmodl_eigen_j[0] = -dt*kf0_-1.0 + nmodl_eigen_j[2] = dt*kb0_ + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 + nmodl_eigen_j[1] = dt*kf0_ + nmodl_eigen_j[3] = -dt*kb0_-1.0 }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] @@ -1904,20 +1904,20 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym EIGEN_NEWTON_SOLVE[2]{ LOCAL kf0_, kb0_, old_C1, old_C2 }{ - kf0_ = beta(v) kb0_ = lowergamma(v) + kf0_ = beta(v) old_C1 = C1 old_C2 = C2 }{ nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*kf0_+nmodl_eigen_x[1]*kb0_)+old_C1)/dt - nmodl_eigen_j[0] = -kf0_-1/dt - nmodl_eigen_j[2] = kb0_ - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*kf0_-nmodl_eigen_x[1]*kb0_)+old_C2)/dt - nmodl_eigen_j[1] = kf0_ - nmodl_eigen_j[3] = -kb0_-1/dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 + nmodl_eigen_j[0] = -dt*kf0_-1.0 + nmodl_eigen_j[2] = dt*kb0_ + nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 + nmodl_eigen_j[1] = dt*kf0_ + nmodl_eigen_j[3] = -dt*kb0_-1.0 }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index c5c12306ee..3ce12e04fb 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -18,7 +18,6 @@ set(NMODL_USECASE_DIRS random suffix state - steady_state table useion at_time) diff --git a/test/nmodl/transpiler/usecases/steady_state/minipump.mod b/test/nmodl/transpiler/usecases/steady_state/minipump.mod deleted file mode 100644 index 2027aecc85..0000000000 --- a/test/nmodl/transpiler/usecases/steady_state/minipump.mod +++ /dev/null @@ -1,43 +0,0 @@ -NEURON { - SUFFIX minipump -} - -PARAMETER { - volA = 1e9 - volB = 1e9 - volC = 13.0 - kf = 3.0 - kb = 4.0 - - run_steady_state = 0.0 -} - -STATE { - X - Y - Z -} - -INITIAL { - X = 40.0 - Y = 8.0 - Z = 1.0 - - if(run_steady_state > 0.0) { - SOLVE state STEADYSTATE sparse - } -} - -BREAKPOINT { - SOLVE state METHOD sparse -} - -KINETIC state { - COMPARTMENT volA {X} - COMPARTMENT volB {Y} - COMPARTMENT volC {Z} - - ~ X + Y <-> Z (kf, kb) - - CONSERVE Y + Z = 8.0*volB + 1.0*volC -} diff --git a/test/nmodl/transpiler/usecases/steady_state/test_minipump.py b/test/nmodl/transpiler/usecases/steady_state/test_minipump.py deleted file mode 100644 index 4521bb92a9..0000000000 --- a/test/nmodl/transpiler/usecases/steady_state/test_minipump.py +++ /dev/null @@ -1,86 +0,0 @@ -import sys -import pickle - -import numpy as np - -from neuron import h, gui - - -def run(steady_state): - s = h.Section() - - s.insert("minipump") - s.diam = 1.0 - - t_hoc = h.Vector().record(h._ref_t) - X_hoc = h.Vector().record(s(0.5).minipump._ref_X) - Y_hoc = h.Vector().record(s(0.5).minipump._ref_Y) - Z_hoc = h.Vector().record(s(0.5).minipump._ref_Z) - - h.run_steady_state_minipump = 1.0 if steady_state else 0.0 - - h.stdinit() - h.continuerun(1.0) - - t = np.array(t_hoc.as_numpy()) - X = np.array(X_hoc.as_numpy()) - Y = np.array(Y_hoc.as_numpy()) - Z = np.array(Z_hoc.as_numpy()) - - return t, X, Y, Z - - -def traces_filename(steady_state): - return "test_minipump{}.pkl".format("-steady_state" if steady_state else "") - - -def save_traces(t, X, Y, Z, steady_state): - with open(traces_filename(steady_state), "bw") as f: - pickle.dump({"t": t, "X": X, "Y": Y, "Z": Z}, f) - - -def load_traces(steady_state): - with open(traces_filename(steady_state), "br") as f: - d = pickle.load(f) - - return d["t"], d["X"], d["Y"], d["Z"] - - -def assert_almost_equal(actual, expected, rtol): - decimal = np.ceil(-np.log10(rtol * np.max(expected))) - np.testing.assert_almost_equal(actual, expected, decimal=decimal) - - -def check_traces(t, X, Y, Z, steady_state): - if len(sys.argv) < 2: - return - - codegen = sys.argv[1] - if codegen == "nocmodl": - save_traces(t, X, Y, Z, steady_state) - - else: - t_ref, X_ref, Y_ref, Z_ref = load_traces(steady_state) - - assert_almost_equal(t, t_ref, rtol=1e-8) - assert_almost_equal(X, X_ref, rtol=1e-8) - assert_almost_equal(Y, Y_ref, rtol=1e-8) - assert_almost_equal(Z, Z_ref, rtol=1e-8) - - -def check_solution(steady_state): - t, X, Y, Z = run(steady_state) - check_traces(t, X, Y, Z, steady_state=steady_state) - - -def test_steady_state(): - check_solution(steady_state=True) - - -def test_no_steady_state(): - check_solution(steady_state=False) - - -if __name__ == "__main__": - test_steady_state() - test_no_steady_state() From 5e8174c3e9eb926d24e9790912894a86d0703bc6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 4 Sep 2024 14:04:26 +0200 Subject: [PATCH 744/871] Not vectorized MOD files don't have thread data. (BlueBrain/nmodl#1413) When the MOD file is not thread safe or not vectorized, then global variables should be implemented as static variables and never promoted to thread variables. NMODL Repo SHA: BlueBrain/nmodl@b68792fabe56801e2fb415e075d29a1ed70bed26 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 4 ++-- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 88d4e424e6..f9d963c095 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -249,7 +249,7 @@ void CodegenHelperVisitor::find_non_range_variables() { auto vars = psymtab->get_variables_with_properties(NmodlType::global_var); for (auto& var: vars) { - if (info.thread_safe && var->get_write_count() > 0) { + if (info.vectorize && info.thread_safe && var->get_write_count() > 0) { var->mark_thread_safe(); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); @@ -287,7 +287,7 @@ void CodegenHelperVisitor::find_non_range_variables() { // if model is thread safe and if parameter is being written then // those variables should be promoted to thread safe variable - if (info.thread_safe && var->get_write_count() > 0) { + if (info.vectorize && info.thread_safe && var->get_write_count() > 0) { var->mark_thread_safe(); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index a4b54e3b98..cb8cb39f9f 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -835,6 +835,11 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } if (!codegen_thread_variables.empty()) { + if (!info.vectorize) { + // MOD files that aren't "VECTORIZED" don't have thread data. + throw std::runtime_error("Found thread variables with `vectorize == false`."); + } + codegen_global_variables.push_back(make_symbol("thread_data_in_use")); auto symbol = make_symbol("thread_data"); From b6704c9199f6b32083de079621803c14eed323ca Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 4 Sep 2024 15:20:35 +0200 Subject: [PATCH 745/871] On failure `generate_references.py` save error message. (BlueBrain/nmodl#1420) NMODL Repo SHA: BlueBrain/nmodl@8c834760e4eace07d3b14c3592d10f137201d97a --- .../usecases/generate_references.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/test/nmodl/transpiler/usecases/generate_references.py b/test/nmodl/transpiler/usecases/generate_references.py index c401b50dce..dbfc244c3d 100755 --- a/test/nmodl/transpiler/usecases/generate_references.py +++ b/test/nmodl/transpiler/usecases/generate_references.py @@ -13,16 +13,31 @@ def substitute_line(lines, starts_with, replacement): break +def ensure_directory_exists(filename=None, dirname=None): + if dirname is None: + dirname = os.path.dirname(filename) + + if dirname and not os.path.exists(dirname): + os.makedirs(dirname) + + def generate_references(nmodl, usecase_dir, output_dir, nmodl_flags): mod_files = glob.glob(os.path.join(usecase_dir, "*.mod")) for mod_file in mod_files: try: subprocess.run( - ["nmodl", mod_file, *nmodl_flags, "-o", output_dir], check=True + [nmodl, mod_file, *nmodl_flags, "-o", output_dir], + capture_output=True, + check=True, ) except subprocess.CalledProcessError as e: - print(e.output) - raise e + if e.output: + cxx_file = os.path.join( + output_dir, os.path.basename(mod_file[:-4] + ".cpp") + ) + ensure_directory_exists(filename=cxx_file) + with open(cxx_file, "w") as cxx: + cxx.write(e.output.decode("utf-8")) cxx_files = glob.glob(os.path.join(output_dir, "*.cpp")) From 1cd5a7ef6d4d5e67bbb0b19691d8070e2e7f691e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 9 Sep 2024 12:09:36 +0200 Subject: [PATCH 746/871] Get voltage via `prop->node`. (BlueBrain/nmodl#1414) * Get voltage via `prop->node`. When calling functions directly, don't have access to the `NrnThread` and therefore can't get node properties, like the voltage from there. The solution is to create a link from the Prop to the Node. Then use that link to figure out the node properties. The trick to make the two cases uniform is the same as we use for instance data, we create pointers to array of length one (by taking the address of the element) and setting `_iml`/`id` to `0`. * Artificial cells aren't associated with a node. * Support top LOCALs in non-vectorized MOD files. * Test function calls with non-threadsafe MOD files. * Test globals in non-VECTORIZED MOD files. * Improve function calling coverage. * Test function calls for ARTIFICIAL_CELLs. * Remove debugging output. NMODL Repo SHA: BlueBrain/nmodl@1c4cb6c4124cbf7156a94979edc8e5e62bcc9a95 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 58 ++++++++++++++++--- .../function/artificial_functions.mod | 34 +++++++++++ .../usecases/function/non_threadsafe.mod | 38 ++++++++++++ .../function/point_non_threadsafe.mod | 38 ++++++++++++ .../usecases/function/test_functions.py | 22 ++++--- .../usecases/global/non_threadsafe.mod | 43 ++++++++++++++ .../usecases/global/test_non_threadsafe.py | 33 +++++++++++ 7 files changed, 252 insertions(+), 14 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/function/artificial_functions.mod create mode 100644 test/nmodl/transpiler/usecases/function/non_threadsafe.mod create mode 100644 test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod create mode 100644 test/nmodl/transpiler/usecases/global/non_threadsafe.mod create mode 100644 test/nmodl/transpiler/usecases/global/test_non_threadsafe.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index cb8cb39f9f..116b8d0546 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -168,6 +168,9 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { printer->push_block(); printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml, _type};"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!info.artificial_cell) { + printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml);", info.mod_suffix); + } if (!codegen_thread_variables.empty()) { printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", thread_variables_struct(), @@ -251,7 +254,9 @@ void CodegenNeuronCppVisitor::print_function_or_procedure( printer->fmt_line("int ret_{} = 0;", name); } - printer->fmt_line("auto v = inst.{}[id];", naming::VOLTAGE_UNUSED_VARIABLE); + if (!info.artificial_cell) { + printer->add_line("auto v = node_data.node_voltages[node_data.nodeindices[id]];"); + } print_statement_block(*node.get_statement_block(), false, false); printer->fmt_line("return ret_{};", name); @@ -294,6 +299,8 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( Datum* _thread; NrnThread* nt; )CODE"); + + std::string prop_name; if (info.point_process) { printer->add_multi_line(R"CODE( auto* const _pnt = static_cast(_vptr); @@ -307,6 +314,8 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( _thread = _extcall_thread.data(); nt = static_cast(_pnt->_vnt); )CODE"); + + prop_name = "_p"; } else if (wrapper_type == InterpreterWrapper::HOC) { if (program_symtab->lookup(block_name)->has_all_properties(NmodlType::use_range_ptr_var)) { printer->push_block("if (!_prop_id)"); @@ -328,16 +337,22 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( _thread = _extcall_thread.data(); nt = nrn_threads; )CODE"); + prop_name = "_local_prop"; } else { // wrapper_type == InterpreterWrapper::Python printer->add_multi_line(R"CODE( _nrn_mechanism_cache_instance _lmc{_prop}; - size_t const id{}; + size_t const id = 0; _ppvar = _nrn_mechanism_access_dparam(_prop); _thread = _extcall_thread.data(); nt = nrn_threads; )CODE"); + prop_name = "_prop"; } + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!info.artificial_cell) { + printer->fmt_line("auto node_data = make_node_data_{}({});", info.mod_suffix, prop_name); + } if (!codegen_thread_variables.empty()) { printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", thread_variables_struct(), @@ -415,6 +430,9 @@ CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_paramete ParamVector params; params.emplace_back("", "_nrn_mechanism_cache_range&", "", "_lmc"); params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); + if (!info.artificial_cell) { + params.emplace_back("", fmt::format("{}&", node_data_struct()), "", "node_data"); + } params.emplace_back("", "size_t", "", "id"); params.emplace_back("", "Datum*", "", "_ppvar"); params.emplace_back("", "Datum*", "", "_thread"); @@ -804,10 +822,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in // TODO implement these when needed. } - if (!info.vectorize && !info.top_local_variables.empty()) { - throw std::runtime_error("Not implemented, global vectorize something."); - } - if (!info.thread_variables.empty()) { size_t prefix_sum = 0; for (size_t i = 0; i < info.thread_variables.size(); ++i) { @@ -834,6 +848,14 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } } + if (!info.vectorize && !info.top_local_variables.empty()) { + for (size_t i = 0; i < info.top_local_variables.size(); ++i) { + const auto& var = info.top_local_variables[i]; + codegen_global_variables.push_back(var); + } + } + + if (!codegen_thread_variables.empty()) { if (!info.vectorize) { // MOD files that aren't "VECTORIZED" don't have thread data. @@ -1385,6 +1407,26 @@ void CodegenNeuronCppVisitor::print_make_node_data() const { printer->pop_block(";"); printer->pop_block(); + + + printer->fmt_push_block("static {} make_node_data_{}(Prop * _prop)", + node_data_struct(), + info.mod_suffix); + printer->add_line("static std::vector node_index{0};"); + printer->add_line("Node* _node = _nrn_mechanism_access_node(_prop);"); + + make_node_data_args = {"node_index.data()", + "&_nrn_mechanism_access_voltage(_node)", + "&_nrn_mechanism_access_d(_node)", + "&_nrn_mechanism_access_rhs(_node)", + "1"}; + + printer->fmt_push_block("return {}", node_data_struct()); + printer->add_multi_line(fmt::format("{}", fmt::join(make_node_data_args, ",\n"))); + + printer->pop_block(";"); + printer->pop_block(); + printer->add_newline(); } void CodegenNeuronCppVisitor::print_thread_variables_structure(bool print_initializers) { @@ -1475,7 +1517,6 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { if (!info.artificial_cell) { printer->add_line("int node_id = node_data.nodeindices[id];"); printer->add_line("auto v = node_data.node_voltages[node_id];"); - printer->fmt_line("inst.{}[id] = v;", naming::VOLTAGE_UNUSED_VARIABLE); } print_rename_state_vars(); @@ -2164,6 +2205,9 @@ void CodegenNeuronCppVisitor::print_net_receive() { printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!info.artificial_cell) { + printer->fmt_line("auto node_data = make_node_data_{}(_pnt->prop);", info.mod_suffix); + } printer->fmt_line("// nocmodl has a nullptr dereference for thread variables."); printer->fmt_line("// NMODL will fail to compile at a later point, because of"); printer->fmt_line("// missing '_thread_vars'."); diff --git a/test/nmodl/transpiler/usecases/function/artificial_functions.mod b/test/nmodl/transpiler/usecases/function/artificial_functions.mod new file mode 100644 index 0000000000..a6d574fde6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/artificial_functions.mod @@ -0,0 +1,34 @@ +NEURON { + ARTIFICIAL_CELL art_functions + RANGE x + GLOBAL gbl +} + +ASSIGNED { + gbl + v + x +} + +FUNCTION x_plus_a(a) { + x_plus_a = x + a +} + +FUNCTION identity(v) { + identity = v +} + +INITIAL { + x = 1.0 + gbl = 42.0 +} + +: A LINEAR block makes a MOD file not VECTORIZED. +STATE { + z +} + +LINEAR lin { + ~ z = 2 +} + diff --git a/test/nmodl/transpiler/usecases/function/non_threadsafe.mod b/test/nmodl/transpiler/usecases/function/non_threadsafe.mod new file mode 100644 index 0000000000..8c84056427 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/non_threadsafe.mod @@ -0,0 +1,38 @@ +NEURON { + SUFFIX non_threadsafe + RANGE x + GLOBAL gbl +} + +ASSIGNED { + gbl + v + x +} + +FUNCTION x_plus_a(a) { + x_plus_a = x + a +} + +FUNCTION v_plus_a(a) { + v_plus_a = v + a +} + +FUNCTION identity(v) { + identity = v +} + +INITIAL { + x = 1.0 + gbl = 42.0 +} + +: A LINEAR block makes a MOD file not VECTORIZED. +STATE { + z +} + +LINEAR lin { + ~ z = 2 +} + diff --git a/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod b/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod new file mode 100644 index 0000000000..27326822e0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod @@ -0,0 +1,38 @@ +NEURON { + POINT_PROCESS point_non_threadsafe + RANGE x + GLOBAL gbl +} + +ASSIGNED { + gbl + v + x +} + +FUNCTION x_plus_a(a) { + x_plus_a = x + a +} + +FUNCTION v_plus_a(a) { + v_plus_a = v + a +} + +FUNCTION identity(v) { + identity = v +} + +INITIAL { + x = 1.0 + gbl = 42.0 +} + +: A LINEAR block makes a MOD file not VECTORIZED. +STATE { + z +} + +LINEAR lin { + ~ z = 2 +} + diff --git a/test/nmodl/transpiler/usecases/function/test_functions.py b/test/nmodl/transpiler/usecases/function/test_functions.py index 197245bbc9..046cb8555b 100644 --- a/test/nmodl/transpiler/usecases/function/test_functions.py +++ b/test/nmodl/transpiler/usecases/function/test_functions.py @@ -1,7 +1,7 @@ from neuron import h -def check_functions(get_instance): +def check_callable(get_instance, has_voltage=True): for x, value in zip(coords, values): get_instance(x).x = value @@ -19,10 +19,11 @@ def check_functions(get_instance): actual = get_instance(x).identity(expected) assert actual == expected, f"{actual} == {expected}" - # Check `f` using `v`. - expected = -2.0 - actual = get_instance(x).v_plus_a(40.0) - assert actual == expected, f"{actual} == {expected}" + if has_voltage: + # Check `f` using `v`. + expected = -2.0 + actual = get_instance(x).v_plus_a(40.0) + assert actual == expected, f"{actual} == {expected}" nseg = 5 @@ -30,11 +31,18 @@ def check_functions(get_instance): s.nseg = nseg s.insert("functions") +s.insert("non_threadsafe") coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] values = [0.1 + k for k in range(nseg)] point_processes = {x: h.point_functions(s(x)) for x in coords} +point_non_threadsafe = {x: h.point_non_threadsafe(s(x)) for x in coords} + +art_cells = {x: h.art_functions() for x in coords} -check_functions(lambda x: s(x).functions) -check_functions(lambda x: point_processes[x]) +check_callable(lambda x: s(x).functions) +check_callable(lambda x: s(x).non_threadsafe) +check_callable(lambda x: point_processes[x]) +check_callable(lambda x: point_non_threadsafe[x]) +check_callable(lambda x: art_cells[x], has_voltage=False) diff --git a/test/nmodl/transpiler/usecases/global/non_threadsafe.mod b/test/nmodl/transpiler/usecases/global/non_threadsafe.mod new file mode 100644 index 0000000000..8605782486 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/non_threadsafe.mod @@ -0,0 +1,43 @@ +NEURON { + SUFFIX non_threadsafe + GLOBAL gbl +} + +LOCAL top_local + +PARAMETER { + parameter = 41.0 +} + +ASSIGNED { + gbl +} + +FUNCTION get_gbl() { + get_gbl = gbl +} + +FUNCTION get_top_local() { + get_top_local = top_local +} + +FUNCTION get_parameter() { + get_parameter = parameter +} + +INITIAL { + gbl = 42.0 + top_local = 43.0 +} + +: A LINEAR block makes the MOD file not thread-safe and not +: vectorized. We don't otherwise care about anything below +: this comment. +STATE { + z +} + +LINEAR lin { + ~ z = 2 +} + diff --git a/test/nmodl/transpiler/usecases/global/test_non_threadsafe.py b/test/nmodl/transpiler/usecases/global/test_non_threadsafe.py new file mode 100644 index 0000000000..fcd413fb76 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/test_non_threadsafe.py @@ -0,0 +1,33 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + + +def test_non_threadsafe(): + nseg = 1 + + s = h.Section() + s.insert("non_threadsafe") + s.nseg = nseg + + h.finitialize() + + instance = s(0.5).non_threadsafe + + # Check INITIAL values. + assert instance.get_parameter() == 41.0 + assert instance.get_gbl() == 42.0 + assert instance.get_top_local() == 43.0 + + # Check reassigning a value. Top LOCAL variables + # are not exposed to HOC/Python. + h.parameter_non_threadsafe = 32.1 + h.gbl_non_threadsafe = 33.2 + + assert instance.get_parameter() == 32.1 + assert instance.get_gbl() == 33.2 + + +if __name__ == "__main__": + test_non_threadsafe() From 99d567e5107c12f64555a4275d1e370260fee174 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 9 Sep 2024 14:50:11 +0200 Subject: [PATCH 747/871] LINEAR requires `sympy --analytic` to work. (BlueBrain/nmodl#1422) NMODL Repo SHA: BlueBrain/nmodl@0bc960c25ad4223c52cde43951b15c049360cf56 --- src/nmodl/main.cpp | 24 +++++++++++---- src/nmodl/visitors/visitor_utils.cpp | 5 ++++ src/nmodl/visitors/visitor_utils.hpp | 3 ++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + test/nmodl/transpiler/usecases/linear/lin.mod | 26 +++++++++++++++++ .../transpiler/usecases/linear/test_lin.py | 29 +++++++++++++++++++ 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/linear/lin.mod create mode 100644 test/nmodl/transpiler/usecases/linear/test_lin.py diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 8decd3f582..1cb7bbc38d 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -49,6 +49,7 @@ #include "visitors/units_visitor.hpp" #include "visitors/verbatim_var_rename_visitor.hpp" #include "visitors/verbatim_visitor.hpp" +#include "visitors/visitor_utils.hpp" /** * \dir @@ -492,9 +493,11 @@ int main(int argc, const char* argv[]) { } const bool sympy_derivimplicit = neuron_code && solver_exists(*ast, "derivimplicit"); + const bool sympy_linear = node_exists(*ast, ast::AstNodeType::LINEAR_BLOCK); + const bool sympy_sparse = solver_exists(*ast, "sparse"); - if (sympy_conductance || sympy_analytic || solver_exists(*ast, "sparse") || - sympy_derivimplicit) { + if (sympy_conductance || sympy_analytic || sympy_sparse || sympy_derivimplicit || + sympy_linear) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() .api() .initialize_interpreter(); @@ -505,11 +508,20 @@ int main(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("sympy_conductance")); } - if (sympy_analytic || solver_exists(*ast, "sparse") || sympy_derivimplicit) { + if (sympy_analytic || sympy_sparse || sympy_derivimplicit || sympy_linear) { if (!sympy_analytic) { - logger->info( - "Automatically enable sympy_analytic because it exists solver of type " - "sparse"); + logger->info("Automatically enabling sympy_analytic."); + if (sympy_sparse) { + logger->info("Required by 'SOLVE ... METHOD sparse'."); + } + + if (sympy_derivimplicit) { + logger->info("Required by 'SOLVE ... METHOD derivimplicit'."); + } + + if (sympy_linear) { + logger->info("Required by 'LINEAR' block."); + } } logger->info("Running sympy solve visitor"); SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast); diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 2a13a38af2..a7da67f852 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -215,6 +215,11 @@ std::vector> collect_nodes(ast::Ast& node, return visitor.lookup(node, types); } +bool node_exists(const ast::Ast& node, ast::AstNodeType ast_type) { + const auto blocks = collect_nodes(node, {ast_type}); + return !blocks.empty(); +} + bool solver_exists(const ast::Ast& node, const std::string& name) { const auto solve_blocks = collect_nodes(node, {ast::AstNodeType::SOLVE_BLOCK}); return std::any_of(solve_blocks.begin(), solve_blocks.end(), [&name](auto const& solve_block) { diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 7105fdfd54..ce8393b7a3 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -109,6 +109,9 @@ std::vector> collect_nodes( /// Whether or not a solver of type name exists in the AST bool solver_exists(const ast::Ast& node, const std::string& name); +/// Whether a node of type `ast_type` exists as a subnode of `node`. +bool node_exists(const ast::Ast& node, ast::AstNodeType ast_type); + /// Given AST node, return the NMODL string representation std::string to_nmodl(const ast::Ast& node, const std::set& exclude_types = {}); diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 3ce12e04fb..6db80aabea 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -6,6 +6,7 @@ set(NMODL_USECASE_DIRS global hodgkin_huxley kinetic + linear morphology nonspecific_current neuron_variables diff --git a/test/nmodl/transpiler/usecases/linear/lin.mod b/test/nmodl/transpiler/usecases/linear/lin.mod new file mode 100644 index 0000000000..44930eecd1 --- /dev/null +++ b/test/nmodl/transpiler/usecases/linear/lin.mod @@ -0,0 +1,26 @@ +NEURON { + SUFFIX lin +} + +STATE { + xx + yy +} + +PARAMETER { + a = 2.0 + b = 3.0 + c = 4.0 + d = 5.0 +} + + +INITIAL { + SOLVE system +} + +LINEAR system { + ~ a*xx + b*yy = 0 + ~ c*xx + d*yy = 0 +} + diff --git a/test/nmodl/transpiler/usecases/linear/test_lin.py b/test/nmodl/transpiler/usecases/linear/test_lin.py new file mode 100644 index 0000000000..3c18ffda65 --- /dev/null +++ b/test/nmodl/transpiler/usecases/linear/test_lin.py @@ -0,0 +1,29 @@ +from neuron import h, gui + +import numpy as np + + +def test_lin(): + s = h.Section() + + s.insert("lin") + + h.finitialize() + + a = h.a_lin + b = h.b_lin + c = h.c_lin + d = h.d_lin + + A = np.array([[a, b], [c, d]]) + exact = np.linalg.solve(A, [0, 0]) + + x = s(0.5).lin.xx + y = s(0.5).lin.yy + + assert x == exact[0] + assert y == exact[1] + + +if __name__ == "__main__": + test_lin() From dea2b3c877f7fe0298c60e1a93ca14687915df04 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Sep 2024 15:00:35 +0200 Subject: [PATCH 748/871] Prepare for fmt v11. (BlueBrain/nmodl#1423) * Update `fmt@11.0.2`. * Python 3.12 does `size_t(-1) + 1 == 0`. NMODL Repo SHA: BlueBrain/nmodl@e6250014dd2c5ae5d9cb30af6920668835b31137 --- src/nmodl/codegen/codegen_info.hpp | 3 ++- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 2 -- src/nmodl/printer/code_printer.hpp | 2 +- src/nmodl/units/units.cpp | 2 +- src/nmodl/utils/blame.cpp | 2 +- src/nmodl/utils/fmt.h | 4 ++++ src/nmodl/utils/string_utils.cpp | 2 +- src/nmodl/visitors/solve_block_visitor.cpp | 2 +- src/nmodl/visitors/visitor_utils.cpp | 2 +- test/nmodl/transpiler/unit/visitor/units.cpp | 2 +- 10 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 src/nmodl/utils/fmt.h diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index a96f24cb80..c18a2945cd 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -12,7 +12,8 @@ * \brief Various types to store code generation specific information */ -#include +#include "utils/fmt.h" + #include #include #include diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 116b8d0546..88e0f3ba84 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1288,7 +1288,6 @@ void CodegenNeuronCppVisitor::print_neuron_global_variable_declarations() { void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_initializers) { auto const value_initialize = print_initializers ? "{}" : ""; - auto int_type = default_int_data_type(); printer->add_newline(2); printer->add_line("/** all mechanism instance variables and global variables */"); printer->fmt_push_block("struct {} ", instance_struct()); @@ -1614,7 +1613,6 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { } const auto& var_name = var->get_name(); auto var_pos = position_of_float_var(var_name); - double var_value = var->get_value() == nullptr ? 0.0 : *var->get_value(); printer->fmt_line("_lmc.template fpfield<{}>(_iml) = {}; /* {} */", var_pos, diff --git a/src/nmodl/printer/code_printer.hpp b/src/nmodl/printer/code_printer.hpp index d7df67a3b0..44b60b61e9 100644 --- a/src/nmodl/printer/code_printer.hpp +++ b/src/nmodl/printer/code_printer.hpp @@ -14,7 +14,7 @@ * \file * \brief \copybrief nmodl::printer::CodePrinter */ -#include +#include "utils/fmt.h" #include #include diff --git a/src/nmodl/units/units.cpp b/src/nmodl/units/units.cpp index c1f391bfa1..75c5196699 100644 --- a/src/nmodl/units/units.cpp +++ b/src/nmodl/units/units.cpp @@ -17,7 +17,7 @@ #include #include "units.hpp" -#include +#include "utils/fmt.h" /** * \file diff --git a/src/nmodl/utils/blame.cpp b/src/nmodl/utils/blame.cpp index ea0fcffd0a..593fbb5f0d 100644 --- a/src/nmodl/utils/blame.cpp +++ b/src/nmodl/utils/blame.cpp @@ -1,8 +1,8 @@ #include "blame.hpp" #include "string_utils.hpp" +#include "utils/fmt.h" #include -#include #include #include diff --git a/src/nmodl/utils/fmt.h b/src/nmodl/utils/fmt.h new file mode 100644 index 0000000000..cf1fdf2ff3 --- /dev/null +++ b/src/nmodl/utils/fmt.h @@ -0,0 +1,4 @@ +#pragma once + +#include +#include diff --git a/src/nmodl/utils/string_utils.cpp b/src/nmodl/utils/string_utils.cpp index 76a7c12506..51947eebd0 100644 --- a/src/nmodl/utils/string_utils.cpp +++ b/src/nmodl/utils/string_utils.cpp @@ -7,7 +7,7 @@ #include "utils/string_utils.hpp" -#include +#include "utils/fmt.h" #include #include diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 2871ff93c6..62ff7d0853 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -7,8 +7,8 @@ #include "visitors/solve_block_visitor.hpp" +#include "utils/fmt.h" #include -#include #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index a7da67f852..1c2115c81c 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -19,7 +19,7 @@ #include "visitors/lookup_visitor.hpp" #include "visitors/nmodl_visitor.hpp" -#include +#include "utils/fmt.h" namespace nmodl { namespace visitor { diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 06f3865cd8..1e96964603 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -5,8 +5,8 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "utils/fmt.h" #include -#include #include "ast/double.hpp" #include "ast/factor_def.hpp" From 4b616aa88b0e70ed02cbc211c4280dfe59c0de1c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 11 Sep 2024 12:52:13 +0200 Subject: [PATCH 749/871] Implement ELECTRODE_CURRENT. (BlueBrain/nmodl#1425) NMODL Repo SHA: BlueBrain/nmodl@f697c120f8966397f1a946d1f2574e0401150279 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 5 +- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/electrode_current/leonhard.mod | 21 +++++++ .../usecases/electrode_current/simulate.py | 60 +++++++++++++++++++ 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/electrode_current/leonhard.mod create mode 100644 test/nmodl/transpiler/usecases/electrode_current/simulate.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 88e0f3ba84..e098e79f04 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1549,7 +1549,8 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { if (breakpoint_exist()) { printer->add_line("int node_id = node_data.nodeindices[id];"); - printer->fmt_line("node_data.node_diagonal[node_id] += inst.{}[id];", + printer->fmt_line("node_data.node_diagonal[node_id] {} inst.{}[id];", + operator_for_d(), info.vectorize ? naming::CONDUCTANCE_UNUSED_VARIABLE : naming::CONDUCTANCE_VARIABLE); } @@ -1933,7 +1934,7 @@ void CodegenNeuronCppVisitor::print_nrn_cur() { // } - printer->add_line("node_data.node_rhs[node_id] -= rhs;"); + printer->fmt_line("node_data.node_rhs[node_id] {} rhs;", operator_for_rhs()); if (breakpoint_exist()) { printer->fmt_line("inst.{}[id] = g;", diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 6db80aabea..362982fce4 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,6 +1,7 @@ set(NMODL_USECASE_DIRS solve constant + electrode_current function procedure global diff --git a/test/nmodl/transpiler/usecases/electrode_current/leonhard.mod b/test/nmodl/transpiler/usecases/electrode_current/leonhard.mod new file mode 100644 index 0000000000..3fd380d1c6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/electrode_current/leonhard.mod @@ -0,0 +1,21 @@ +UNITS { + (mA) = (milliamp) +} + +NEURON { + SUFFIX leonhard + ELECTRODE_CURRENT il + RANGE c +} + +ASSIGNED { + il (mA/cm2) +} + +PARAMETER { + c = 0.005 +} + +BREAKPOINT { + il = - c * (v - 1.5) +} diff --git a/test/nmodl/transpiler/usecases/electrode_current/simulate.py b/test/nmodl/transpiler/usecases/electrode_current/simulate.py new file mode 100644 index 0000000000..1eab2f586f --- /dev/null +++ b/test/nmodl/transpiler/usecases/electrode_current/simulate.py @@ -0,0 +1,60 @@ +import numpy as np + +from neuron import gui, h +from neuron.units import ms + + +# Test the accuracy of the simulation output to the analytical result +nseg = 1 +s = h.Section() +s.insert("leonhard") +s.nseg = nseg +s.c_leonhard = 0.005 + +v_hoc = h.Vector().record(s(0.5)._ref_v) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 5.0 * ms +h.run() + +v = np.array(v_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +erev = 1.5 +rate = s.c_leonhard / 1e-3 +v0 = -65.0 +v_exact = erev + (v0 - erev) * np.exp(-rate * t) +rel_err = np.abs(v - v_exact) / np.max(np.abs(v_exact)) + +assert np.all(rel_err < 1e-1), f"rel_err = {rel_err}" + + +# Test the stability of the simulation at final time using a single timestep +nseg = 1 +s = h.Section() +s.insert("leonhard") +s.nseg = nseg +s.c_leonhard = 0.5 + +v_hoc = h.Vector().record(s(0.5)._ref_v) +t_hoc = h.Vector().record(h._ref_t) + +h.stdinit() +h.tstop = 1000.0 * ms +h.dt = 1000.0 * ms +h.run() + +v = np.array(v_hoc.as_numpy()) +t = np.array(t_hoc.as_numpy()) + +erev = 1.5 +rate = s.c_leonhard / 1e-3 +v0 = -65.0 +v_exact = erev + (v0 - erev) * np.exp(-rate * t) +rel_err = np.abs(v - v_exact) / np.max(np.abs(v_exact)) + +assert np.allclose(v[-1], v_exact[-1], atol=0.0), f"rel_err = {rel_err}" + + +print("leonhard: success") From fd784b624231dec20a06b0ec56b7a505c6e850fb Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 11 Sep 2024 13:55:03 +0200 Subject: [PATCH 750/871] Implement CONSTRUCTOR/DESTRUCTOR. (BlueBrain/nmodl#1424) NMODL Repo SHA: BlueBrain/nmodl@2c376eab77eb677ef94093055f56bd1470de24dc --- .../codegen/codegen_neuron_cpp_visitor.cpp | 58 +++++++++++++++++-- .../codegen/codegen_neuron_cpp_visitor.hpp | 4 ++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/constructor/art_ctor.mod | 17 ++++++ .../transpiler/usecases/constructor/ctor.mod | 17 ++++++ .../usecases/constructor/test_ctor.py | 36 ++++++++++++ 6 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/constructor/art_ctor.mod create mode 100644 test/nmodl/transpiler/usecases/constructor/ctor.mod create mode 100644 test/nmodl/transpiler/usecases/constructor/test_ctor.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e098e79f04..e4759b4461 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1120,10 +1120,16 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { "_pointtype = point_register_mech({}, _hoc_create_pnt, _hoc_destroy_pnt, " "_member_func);", register_mech_args); + + if (info.destructor_node) { + printer->fmt_line("register_destructor({});", + method_name(naming::NRN_DESTRUCTOR_METHOD)); + } } else { printer->fmt_line("register_mech({});", register_mech_args); } + if (info.thread_callback_register) { printer->fmt_line("_extcall_thread.resize({});", info.thread_data_index + 1); printer->fmt_line("thread_mem_init(_extcall_thread.data());"); @@ -1560,21 +1566,55 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { } -/// TODO: Edit for NEURON +void CodegenNeuronCppVisitor::print_callable_preamble_from_prop() { + printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(prop);"); + printer->add_line("_nrn_mechanism_cache_instance _lmc{prop};"); + printer->add_line("const size_t id = 0;"); + + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!info.artificial_cell) { + printer->fmt_line("auto node_data = make_node_data_{}(prop);", info.mod_suffix); + } + + if (!codegen_thread_variables.empty()) { + printer->fmt_line("auto _thread_vars = {}({}_global.thread_data);", + thread_variables_struct(), + info.mod_suffix); + } + + printer->add_newline(); +} + + void CodegenNeuronCppVisitor::print_nrn_constructor() { - return; + if (info.constructor_node) { + printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_CONSTRUCTOR_METHOD)); + + print_callable_preamble_from_prop(); + + auto block = info.constructor_node->get_statement_block(); + print_statement_block(*block, false, false); + + printer->pop_block(); + } } void CodegenNeuronCppVisitor::print_nrn_destructor() { - printer->fmt_push_block("void {}(Prop* _prop)", method_name(naming::NRN_DESTRUCTOR_METHOD)); - printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(_prop);"); + printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_DESTRUCTOR_METHOD)); + print_callable_preamble_from_prop(); for (const auto& rv: info.random_variables) { printer->fmt_line("nrnran123_deletestream((nrnran123_State*) {});", get_variable_name(get_name(rv), false)); } + + if (info.destructor_node) { + auto block = info.destructor_node->get_statement_block(); + print_statement_block(*block, false, false); + } + printer->pop_block(); } @@ -1699,6 +1739,15 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { method_name(naming::NRN_DESTRUCTOR_METHOD)); } + if (info.point_process || info.artificial_cell) { + printer->fmt_push_block("if(!nrn_point_prop_)"); + + if (info.constructor_node) { + printer->fmt_line("{}(_prop);", method_name(naming::NRN_CONSTRUCTOR_METHOD)); + } + printer->pop_block(); + } + printer->pop_block(); } @@ -2089,6 +2138,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_prcellstate_macros(); print_mechanism_info(); print_data_structures(true); + print_nrn_constructor(); print_nrn_destructor(); print_nrn_alloc(); print_function_prototypes(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 2404d68f42..825173c89e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -491,6 +491,10 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_nrn_constructor() override; + /** + * Print the set of common variables from a `Prop` only. + */ + void print_callable_preamble_from_prop(); /** * Print nrn_destructor function definition diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 362982fce4..cc08856bf2 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -2,6 +2,7 @@ set(NMODL_USECASE_DIRS solve constant electrode_current + constructor function procedure global diff --git a/test/nmodl/transpiler/usecases/constructor/art_ctor.mod b/test/nmodl/transpiler/usecases/constructor/art_ctor.mod new file mode 100644 index 0000000000..9b89aed766 --- /dev/null +++ b/test/nmodl/transpiler/usecases/constructor/art_ctor.mod @@ -0,0 +1,17 @@ +NEURON { + ARTIFICIAL_CELL art_ctor + GLOBAL ctor_calls, dtor_calls +} + +ASSIGNED { + ctor_calls + dtor_calls +} + +CONSTRUCTOR { + ctor_calls = ctor_calls + 1 +} + +DESTRUCTOR { + dtor_calls = dtor_calls + 1 +} diff --git a/test/nmodl/transpiler/usecases/constructor/ctor.mod b/test/nmodl/transpiler/usecases/constructor/ctor.mod new file mode 100644 index 0000000000..9d4c5dc5d0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/constructor/ctor.mod @@ -0,0 +1,17 @@ +NEURON { + POINT_PROCESS ctor + GLOBAL ctor_calls, dtor_calls +} + +ASSIGNED { + ctor_calls + dtor_calls +} + +CONSTRUCTOR { + ctor_calls = ctor_calls + 1 +} + +DESTRUCTOR { + dtor_calls = dtor_calls + 1 +} diff --git a/test/nmodl/transpiler/usecases/constructor/test_ctor.py b/test/nmodl/transpiler/usecases/constructor/test_ctor.py new file mode 100644 index 0000000000..1a71626dc6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/constructor/test_ctor.py @@ -0,0 +1,36 @@ +import gc + +from neuron import h, gui + + +def allocate_props(n_instances, mech_name): + s = h.Section() + s.nseg = 1 + + for _ in range(n_instances): + getattr(h, mech_name)(s(0.5)) + + +def assert_equal(actual, expected): + assert actual == expected, f"{actual} != {expected}" + + +def check_ctor(n_instances, mech_name): + allocate_props(n_instances, mech_name) + gc.collect() + + assert_equal(getattr(h, f"ctor_calls_{mech_name}"), n_instances) + assert_equal(getattr(h, f"dtor_calls_{mech_name}"), n_instances) + + +def test_ctor(): + check_ctor(12, "ctor") + + +def test_art_ctor(): + check_ctor(32, "art_ctor") + + +if __name__ == "__main__": + test_ctor() + test_art_ctor() From cff6c5cb2b2630bc041fb29bdbe15d9b206757f8 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 13 Sep 2024 15:14:17 +0200 Subject: [PATCH 751/871] Reapply "Newton error propagation based convergence check." (BlueBrain/nmodl#1419) * Set `dt` if needed. * Use `(X - X_old) / dt = dX`. Instead of `(X - X_old) = dt * dX`. The implemented scaling is the one implemented in NOCMODL. It should prevent the residual from growing linearly with `dt`. * Reduce MAX_ITER to 50 and adjust tolerance. * Newton error propagation based convergence check. * Add tests. Includes a test that solves the pump equation ~ X + Y <-> Z with extreme compartment sizes and steady state calculation. * Fix codegen issues specific to NVHPC. NMODL Repo SHA: BlueBrain/nmodl@5abff4fb49b8d00b8903e0b745f805f8169ff6c0 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 14 +- src/nmodl/solver/newton/newton.hpp | 39 ++- src/nmodl/visitors/sympy_solver_visitor.cpp | 10 +- .../transpiler/unit/visitor/sympy_solver.cpp | 230 +++++++++--------- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/steady_state/minipump.mod | 43 ++++ .../usecases/steady_state/test_minipump.py | 86 +++++++ 7 files changed, 293 insertions(+), 130 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/steady_state/minipump.mod create mode 100644 test/nmodl/transpiler/usecases/steady_state/test_minipump.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e4759b4461..770bc534d6 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1526,9 +1526,21 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { print_rename_state_vars(); + if (!info.changed_dt.empty()) { + printer->fmt_line("double _save_prev_dt = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE)); + printer->fmt_line("{} = {};", + get_variable_name(naming::NTHREAD_DT_VARIABLE), + info.changed_dt); + } + print_initial_block(info.initial_node); - printer->pop_block(); + if (!info.changed_dt.empty()) { + printer->fmt_line("{} = _save_prev_dt;", get_variable_name(naming::NTHREAD_DT_VARIABLE)); + } + + printer->pop_block(); printer->pop_block(); } diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index 77d6470e1f..bd627d0dbb 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -34,8 +34,33 @@ namespace newton { * @{ */ -static constexpr int MAX_ITER = 1e3; -static constexpr double EPS = 1e-12; +static constexpr int MAX_ITER = 50; +static constexpr double EPS = 1e-13; + +template +EIGEN_DEVICE_FUNC bool is_converged(const Eigen::Matrix& X, + const Eigen::Matrix& J, + const Eigen::Matrix& F, + double eps) { + bool converged = true; + double square_eps = eps * eps; + for (Eigen::Index i = 0; i < N; ++i) { + double square_error = 0.0; + for (Eigen::Index j = 0; j < N; ++j) { + double JX = J(i, j) * X(j); + square_error += JX * JX; + } + + if (F(i) * F(i) > square_eps * square_error) { + converged = false; +// The NVHPC is buggy and wont allow us to short-circuit. +#ifndef __NVCOMPILER + return converged; +#endif + } + } + return converged; +} /** * \brief Newton method with user-provided Jacobian @@ -58,17 +83,14 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, int max_iter = MAX_ITER) { // Vector to store result of function F(X): Eigen::Matrix F; - // Matrix to store jacobian of F(X): + // Matrix to store Jacobian of F(X): Eigen::Matrix J; // Solver iteration count: int iter = -1; while (++iter < max_iter) { // calculate F, J from X using user-supplied functor functor(X, F, J); - // get error norm: here we use sqrt(|F|^2) - double error = F.norm(); - if (error < eps) { - // we have converged: return iteration count + if (is_converged(X, J, F, eps)) { return iter; } // In Eigen the default storage order is ColMajor. @@ -109,8 +131,7 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, int iter = -1; while (++iter < max_iter) { functor(X, F, J); - double error = F.norm(); - if (error < eps) { + if (is_converged(X, J, F, eps)) { return iter; } // The inverse can be called from within OpenACC regions without any issue, as opposed to diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index d944f94460..206145efbc 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -543,15 +543,15 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { pre_solve_statements.push_back(std::move(expression)); } // replace ODE with Euler equation - eq = x; + eq = "("; + eq.append(x); eq.append(x_array_index); - eq.append(" = "); + eq.append(" - "); eq.append(old_x); - eq.append(" + "); + eq.append(") / "); eq.append(codegen::naming::NTHREAD_DT_VARIABLE); - eq.append(" * ("); + eq.append(" = "); eq.append(dxdt); - eq.append(")"); logger->debug("SympySolverVisitor :: -> constructed Euler eq: {}", eq); } } diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 5a0e4c42b1..486a243113 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -647,8 +647,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = m }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*mInf+mTau*(-nmodl_eigen_x[0]+old_m))/mTau - nmodl_eigen_j[0] = -(dt+mTau)/mTau + nmodl_eigen_f[0] = (dt*(-nmodl_eigen_x[0]+mInf)+mTau*(-nmodl_eigen_x[0]+old_m))/(dt*mTau) + nmodl_eigen_j[0] = (-dt-mTau)/(dt*mTau) }{ m = nmodl_eigen_x[0] }{ @@ -686,11 +686,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_y + nmodl_eigen_f[0] = (-nmodl_eigen_x[1]+a*dt+old_y)/dt nmodl_eigen_j[0] = 0 - nmodl_eigen_j[2] = -1.0 - nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_x - nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[2] = -1/dt + nmodl_eigen_f[1] = (-nmodl_eigen_x[0]+b*dt+old_x)/dt + nmodl_eigen_j[1] = -1/dt nmodl_eigen_j[3] = 0 }{ x = nmodl_eigen_x[0] @@ -730,11 +730,11 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = M[0] nmodl_eigen_x[1] = M[1] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[1]+a*dt+old_M_1 + nmodl_eigen_f[0] = (-nmodl_eigen_x[1]+a*dt+old_M_1)/dt nmodl_eigen_j[0] = 0 - nmodl_eigen_j[2] = -1.0 - nmodl_eigen_f[1] = -nmodl_eigen_x[0]+b*dt+old_M_0 - nmodl_eigen_j[1] = -1.0 + nmodl_eigen_j[2] = -1/dt + nmodl_eigen_f[1] = (-nmodl_eigen_x[0]+b*dt+old_M_0)/dt + nmodl_eigen_j[1] = -1/dt nmodl_eigen_j[3] = 0 }{ M[0] = nmodl_eigen_x[0] @@ -775,13 +775,13 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+a*dt+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[2] = 0 b = b+1 - nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+b*dt+old_y)/dt nmodl_eigen_j[1] = 0 - nmodl_eigen_j[3] = -1.0 + nmodl_eigen_j[3] = -1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -853,12 +853,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+a*dt+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+a*dt+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[2] = 0 - nmodl_eigen_f[1] = -nmodl_eigen_x[1]+b*dt+old_y + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+b*dt+old_y)/dt nmodl_eigen_j[1] = 0 - nmodl_eigen_j[3] = -1.0 + nmodl_eigen_j[3] = -1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -903,15 +903,15 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x - nmodl_eigen_j[0] = -1.0 - nmodl_eigen_j[2] = a*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[1]*a+b)+old_x)/dt + nmodl_eigen_j[0] = -1/dt + nmodl_eigen_j[2] = a IF (b == 1) { a = a+1 } - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y - nmodl_eigen_j[1] = dt - nmodl_eigen_j[3] = a*dt-1.0 + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]+nmodl_eigen_x[1]*a)+old_y)/dt + nmodl_eigen_j[1] = 1.0 + nmodl_eigen_j[3] = a-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -929,15 +929,15 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = x nmodl_eigen_x[1] = y }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[1]*a*dt+b*dt+old_x - nmodl_eigen_j[0] = -1.0 - nmodl_eigen_j[2] = a*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[1]*a+b)+old_x)/dt + nmodl_eigen_j[0] = -1/dt + nmodl_eigen_j[2] = a IF (b == 1) { a = a+1 } - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt+nmodl_eigen_x[1]*a*dt-nmodl_eigen_x[1]+old_y - nmodl_eigen_j[1] = dt - nmodl_eigen_j[3] = a*dt-1.0 + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]+nmodl_eigen_x[1]*a)+old_y)/dt + nmodl_eigen_j[1] = 1.0 + nmodl_eigen_j[3] = a-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -984,18 +984,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = y nmodl_eigen_x[2] = z }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[2]*a+b*h)+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[3] = 0 - nmodl_eigen_j[6] = a*dt - nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y - nmodl_eigen_j[1] = 2.0*dt - nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[6] = a + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(2.0*nmodl_eigen_x[0]+c)+old_y)/dt + nmodl_eigen_j[1] = 2.0 + nmodl_eigen_j[4] = -1/dt nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z + nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(-nmodl_eigen_x[1]+nmodl_eigen_x[2]*d)+old_z)/dt nmodl_eigen_j[2] = 0 - nmodl_eigen_j[5] = -dt - nmodl_eigen_j[8] = d*dt-1.0 + nmodl_eigen_j[5] = -1.0 + nmodl_eigen_j[8] = d-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -1016,18 +1016,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = y nmodl_eigen_x[2] = z }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]+nmodl_eigen_x[2]*a*dt+b*dt*h+old_x - nmodl_eigen_j[0] = -1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(nmodl_eigen_x[2]*a+b*h)+old_x)/dt + nmodl_eigen_j[0] = -1/dt nmodl_eigen_j[3] = 0 - nmodl_eigen_j[6] = a*dt - nmodl_eigen_f[1] = 2.0*nmodl_eigen_x[0]*dt-nmodl_eigen_x[1]+c*dt+old_y - nmodl_eigen_j[1] = 2.0*dt - nmodl_eigen_j[4] = -1.0 + nmodl_eigen_j[6] = a + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(2.0*nmodl_eigen_x[0]+c)+old_y)/dt + nmodl_eigen_j[1] = 2.0 + nmodl_eigen_j[4] = -1/dt nmodl_eigen_j[7] = 0 - nmodl_eigen_f[2] = -nmodl_eigen_x[1]*dt+nmodl_eigen_x[2]*d*dt-nmodl_eigen_x[2]+old_z + nmodl_eigen_f[2] = (-nmodl_eigen_x[2]+dt*(-nmodl_eigen_x[1]+nmodl_eigen_x[2]*d)+old_z)/dt nmodl_eigen_j[2] = 0 - nmodl_eigen_j[5] = -dt - nmodl_eigen_j[8] = d*dt-1.0 + nmodl_eigen_j[5] = -1.0 + nmodl_eigen_j[8] = d-1/dt }{ x = nmodl_eigen_x[0] y = nmodl_eigen_x[1] @@ -1070,12 +1070,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc - nmodl_eigen_j[0] = -a*dt-1.0 - nmodl_eigen_j[2] = b*dt - nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m - nmodl_eigen_j[1] = a*dt - nmodl_eigen_j[3] = -b*dt-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt + nmodl_eigen_j[0] = -a-1/dt + nmodl_eigen_j[2] = b + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt + nmodl_eigen_j[1] = a + nmodl_eigen_j[3] = -b-1/dt }{ mc = nmodl_eigen_x[0] m = nmodl_eigen_x[1] @@ -1113,9 +1113,9 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc - nmodl_eigen_j[0] = -a*dt-1.0 - nmodl_eigen_j[2] = b*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt + nmodl_eigen_j[0] = -a-1/dt + nmodl_eigen_j[2] = b nmodl_eigen_f[1] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]+1.0 nmodl_eigen_j[1] = -1.0 nmodl_eigen_j[3] = -1.0 @@ -1159,12 +1159,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = mc nmodl_eigen_x[1] = m }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*b*dt+old_mc - nmodl_eigen_j[0] = -a*dt-1.0 - nmodl_eigen_j[2] = b*dt - nmodl_eigen_f[1] = nmodl_eigen_x[0]*a*dt-nmodl_eigen_x[1]*b*dt-nmodl_eigen_x[1]+old_m - nmodl_eigen_j[1] = a*dt - nmodl_eigen_j[3] = -b*dt-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*a+nmodl_eigen_x[1]*b)+old_mc)/dt + nmodl_eigen_j[0] = -a-1/dt + nmodl_eigen_j[2] = b + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*a-nmodl_eigen_x[1]*b)+old_m)/dt + nmodl_eigen_j[1] = a + nmodl_eigen_j[3] = -b-1/dt }{ mc = nmodl_eigen_x[0] m = nmodl_eigen_x[1] @@ -1213,16 +1213,16 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[3] = p0 nmodl_eigen_x[4] = p1 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[0]+nmodl_eigen_x[1]*beta*dt+old_c1 - nmodl_eigen_j[0] = -alpha*dt-1.0 - nmodl_eigen_j[5] = beta*dt + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*alpha+nmodl_eigen_x[1]*beta)+old_c1)/dt + nmodl_eigen_j[0] = -alpha-1/dt + nmodl_eigen_j[5] = beta nmodl_eigen_j[10] = 0 nmodl_eigen_j[15] = 0 nmodl_eigen_j[20] = 0 - nmodl_eigen_f[1] = nmodl_eigen_x[0]*alpha*dt-nmodl_eigen_x[1]*beta*dt-nmodl_eigen_x[1]*dt*k3p-nmodl_eigen_x[1]+nmodl_eigen_x[2]*dt*k4+old_o1 - nmodl_eigen_j[1] = alpha*dt - nmodl_eigen_j[6] = -beta*dt-dt*k3p-1.0 - nmodl_eigen_j[11] = dt*k4 + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*alpha-nmodl_eigen_x[1]*beta-nmodl_eigen_x[1]*k3p+nmodl_eigen_x[2]*k4)+old_o1)/dt + nmodl_eigen_j[1] = alpha + nmodl_eigen_j[6] = -beta-k3p-1/dt + nmodl_eigen_j[11] = k4 nmodl_eigen_j[16] = 0 nmodl_eigen_j[21] = 0 nmodl_eigen_f[2] = -nmodl_eigen_x[0]-nmodl_eigen_x[1]-nmodl_eigen_x[2]+1.0 @@ -1231,12 +1231,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_j[12] = -1.0 nmodl_eigen_j[17] = 0 nmodl_eigen_j[22] = 0 - nmodl_eigen_f[3] = -nmodl_eigen_x[3]*dt*k1ca-nmodl_eigen_x[3]+nmodl_eigen_x[4]*dt*k2+old_p0 + nmodl_eigen_f[3] = (-nmodl_eigen_x[3]+dt*(-nmodl_eigen_x[3]*k1ca+nmodl_eigen_x[4]*k2)+old_p0)/dt nmodl_eigen_j[3] = 0 nmodl_eigen_j[8] = 0 nmodl_eigen_j[13] = 0 - nmodl_eigen_j[18] = -dt*k1ca-1.0 - nmodl_eigen_j[23] = dt*k2 + nmodl_eigen_j[18] = -k1ca-1/dt + nmodl_eigen_j[23] = k2 nmodl_eigen_f[4] = -nmodl_eigen_x[3]-nmodl_eigen_x[4]+1.0 nmodl_eigen_j[4] = 0 nmodl_eigen_j[9] = 0 @@ -1286,8 +1286,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = W[0] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 - nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[0]*B[0]+3.0*A[1])+old_W_0)/dt + nmodl_eigen_j[0] = -A[0]+B[0]-1/dt }{ W[0] = nmodl_eigen_x[0] }{ @@ -1328,12 +1328,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = M[0] nmodl_eigen_x[1] = M[1] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*B[0]+old_M_0 - nmodl_eigen_j[0] = -dt*A[0]-1.0 - nmodl_eigen_j[2] = dt*B[0] - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*A[1]-nmodl_eigen_x[1]*dt*B[1]-nmodl_eigen_x[1]+old_M_1 - nmodl_eigen_j[1] = dt*A[1] - nmodl_eigen_j[3] = -dt*B[1]-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[1]*B[0])+old_M_0)/dt + nmodl_eigen_j[0] = -A[0]-1/dt + nmodl_eigen_j[2] = B[0] + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*A[1]-nmodl_eigen_x[1]*B[1])+old_M_1)/dt + nmodl_eigen_j[1] = A[1] + nmodl_eigen_j[3] = -B[1]-1/dt }{ M[0] = nmodl_eigen_x[0] M[1] = nmodl_eigen_x[1] @@ -1372,8 +1372,8 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", }{ nmodl_eigen_x[0] = W[0] }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*A[0]+nmodl_eigen_x[0]*dt*B[0]-nmodl_eigen_x[0]+3.0*dt*A[1]+old_W_0 - nmodl_eigen_j[0] = -dt*A[0]+dt*B[0]-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*A[0]+nmodl_eigen_x[0]*B[0]+3.0*A[1])+old_W_0)/dt + nmodl_eigen_j[0] = -A[0]+B[0]-1/dt }{ W[0] = nmodl_eigen_x[0] }{ @@ -1416,18 +1416,18 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[1] = h nmodl_eigen_x[2] = n }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]-3.0*nmodl_eigen_x[1]*dt+old_m))/mtau - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau - nmodl_eigen_f[2] = (-nmodl_eigen_x[2]*dt+dt*ninf+ntau*(-nmodl_eigen_x[2]+old_n))/ntau - nmodl_eigen_j[0] = -(dt+mtau)/mtau - nmodl_eigen_j[3] = -3.0*dt + nmodl_eigen_f[0] = -nmodl_eigen_x[0]/mtau-nmodl_eigen_x[0]/dt-3.0*nmodl_eigen_x[1]+minf/mtau+old_m/dt + nmodl_eigen_j[0] = (-dt-mtau)/(dt*mtau) + nmodl_eigen_j[3] = -3.0 nmodl_eigen_j[6] = 0 - nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt - nmodl_eigen_j[4] = -(dt+htau)/htau + nmodl_eigen_f[1] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau-nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0] + nmodl_eigen_j[4] = (-dt-htau)/(dt*htau) nmodl_eigen_j[7] = 0 + nmodl_eigen_f[2] = (dt*(-nmodl_eigen_x[2]+ninf)+ntau*(-nmodl_eigen_x[2]+old_n))/(dt*ntau) nmodl_eigen_j[2] = 0 nmodl_eigen_j[5] = 0 - nmodl_eigen_j[8] = -(dt+ntau)/ntau + nmodl_eigen_j[8] = (-dt-ntau)/(dt*ntau) }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1474,12 +1474,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = m nmodl_eigen_x[1] = h }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+old_m))/mtau - nmodl_eigen_f[1] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau - nmodl_eigen_j[0] = -(dt+mtau)/mtau + nmodl_eigen_f[0] = (dt*(-nmodl_eigen_x[0]+minf)+mtau*(-nmodl_eigen_x[0]+old_m))/(dt*mtau) + nmodl_eigen_j[0] = (-dt-mtau)/(dt*mtau) nmodl_eigen_j[2] = 0 - nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0]*dt - nmodl_eigen_j[3] = -(dt+htau)/htau + nmodl_eigen_f[1] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau- nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt + nmodl_eigen_j[1] = 2.0*nmodl_eigen_x[0] + nmodl_eigen_j[3] = (-dt-htau)/(dt*htau) }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1497,12 +1497,12 @@ SCENARIO("Solve ODEs with derivimplicit method using SympySolverVisitor", nmodl_eigen_x[0] = m nmodl_eigen_x[1] = h }{ - nmodl_eigen_f[0] = (-nmodl_eigen_x[1]*dt+dt*hinf+htau*(pow(nmodl_eigen_x[0], 2)*dt-nmodl_eigen_x[1]+old_h))/htau - nmodl_eigen_f[1] = (-nmodl_eigen_x[0]*dt+dt*minf+mtau*(-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt+old_m))/mtau - nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0]*dt - nmodl_eigen_j[2] = -(dt+htau)/htau - nmodl_eigen_j[1] = -(dt+mtau)/mtau - nmodl_eigen_j[3] = dt + nmodl_eigen_f[0] = pow(nmodl_eigen_x[0], 2)-nmodl_eigen_x[1]/htau-nmodl_eigen_x[1]/dt+hinf/htau+old_h/dt + nmodl_eigen_j[0] = 2.0*nmodl_eigen_x[0] + nmodl_eigen_j[2] = (-dt-htau)/(dt*htau) + nmodl_eigen_f[1] = -nmodl_eigen_x[0]/mtau-nmodl_eigen_x[0]/dt+nmodl_eigen_x[1]+minf/mtau+old_m/dt + nmodl_eigen_j[1] = (-dt-mtau)/(dt*mtau) + nmodl_eigen_j[3] = 1.0 }{ m = nmodl_eigen_x[0] h = nmodl_eigen_x[1] @@ -1862,12 +1862,12 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 - nmodl_eigen_j[0] = -dt*kf0_-1.0 - nmodl_eigen_j[2] = dt*kb0_ - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 - nmodl_eigen_j[1] = dt*kf0_ - nmodl_eigen_j[3] = -dt*kb0_-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*kf0_+nmodl_eigen_x[1]*kb0_)+old_C1)/dt + nmodl_eigen_j[0] = -kf0_-1/dt + nmodl_eigen_j[2] = kb0_ + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*kf0_-nmodl_eigen_x[1]*kb0_)+old_C2)/dt + nmodl_eigen_j[1] = kf0_ + nmodl_eigen_j[3] = -kb0_-1/dt }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] @@ -1904,20 +1904,20 @@ SCENARIO("Solve KINETIC block using SympySolver Visitor", "[visitor][solver][sym EIGEN_NEWTON_SOLVE[2]{ LOCAL kf0_, kb0_, old_C1, old_C2 }{ - kb0_ = lowergamma(v) kf0_ = beta(v) + kb0_ = lowergamma(v) old_C1 = C1 old_C2 = C2 }{ nmodl_eigen_x[0] = C1 nmodl_eigen_x[1] = C2 }{ - nmodl_eigen_f[0] = -nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[0]+nmodl_eigen_x[1]*dt*kb0_+old_C1 - nmodl_eigen_j[0] = -dt*kf0_-1.0 - nmodl_eigen_j[2] = dt*kb0_ - nmodl_eigen_f[1] = nmodl_eigen_x[0]*dt*kf0_-nmodl_eigen_x[1]*dt*kb0_-nmodl_eigen_x[1]+old_C2 - nmodl_eigen_j[1] = dt*kf0_ - nmodl_eigen_j[3] = -dt*kb0_-1.0 + nmodl_eigen_f[0] = (-nmodl_eigen_x[0]+dt*(-nmodl_eigen_x[0]*kf0_+nmodl_eigen_x[1]*kb0_)+old_C1)/dt + nmodl_eigen_j[0] = -kf0_-1/dt + nmodl_eigen_j[2] = kb0_ + nmodl_eigen_f[1] = (-nmodl_eigen_x[1]+dt*(nmodl_eigen_x[0]*kf0_-nmodl_eigen_x[1]*kb0_)+old_C2)/dt + nmodl_eigen_j[1] = kf0_ + nmodl_eigen_j[3] = -kb0_-1/dt }{ C1 = nmodl_eigen_x[0] C2 = nmodl_eigen_x[1] diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index cc08856bf2..ba5bf0fc6f 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -21,6 +21,7 @@ set(NMODL_USECASE_DIRS random suffix state + steady_state table useion at_time) diff --git a/test/nmodl/transpiler/usecases/steady_state/minipump.mod b/test/nmodl/transpiler/usecases/steady_state/minipump.mod new file mode 100644 index 0000000000..2027aecc85 --- /dev/null +++ b/test/nmodl/transpiler/usecases/steady_state/minipump.mod @@ -0,0 +1,43 @@ +NEURON { + SUFFIX minipump +} + +PARAMETER { + volA = 1e9 + volB = 1e9 + volC = 13.0 + kf = 3.0 + kb = 4.0 + + run_steady_state = 0.0 +} + +STATE { + X + Y + Z +} + +INITIAL { + X = 40.0 + Y = 8.0 + Z = 1.0 + + if(run_steady_state > 0.0) { + SOLVE state STEADYSTATE sparse + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + COMPARTMENT volA {X} + COMPARTMENT volB {Y} + COMPARTMENT volC {Z} + + ~ X + Y <-> Z (kf, kb) + + CONSERVE Y + Z = 8.0*volB + 1.0*volC +} diff --git a/test/nmodl/transpiler/usecases/steady_state/test_minipump.py b/test/nmodl/transpiler/usecases/steady_state/test_minipump.py new file mode 100644 index 0000000000..4521bb92a9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/steady_state/test_minipump.py @@ -0,0 +1,86 @@ +import sys +import pickle + +import numpy as np + +from neuron import h, gui + + +def run(steady_state): + s = h.Section() + + s.insert("minipump") + s.diam = 1.0 + + t_hoc = h.Vector().record(h._ref_t) + X_hoc = h.Vector().record(s(0.5).minipump._ref_X) + Y_hoc = h.Vector().record(s(0.5).minipump._ref_Y) + Z_hoc = h.Vector().record(s(0.5).minipump._ref_Z) + + h.run_steady_state_minipump = 1.0 if steady_state else 0.0 + + h.stdinit() + h.continuerun(1.0) + + t = np.array(t_hoc.as_numpy()) + X = np.array(X_hoc.as_numpy()) + Y = np.array(Y_hoc.as_numpy()) + Z = np.array(Z_hoc.as_numpy()) + + return t, X, Y, Z + + +def traces_filename(steady_state): + return "test_minipump{}.pkl".format("-steady_state" if steady_state else "") + + +def save_traces(t, X, Y, Z, steady_state): + with open(traces_filename(steady_state), "bw") as f: + pickle.dump({"t": t, "X": X, "Y": Y, "Z": Z}, f) + + +def load_traces(steady_state): + with open(traces_filename(steady_state), "br") as f: + d = pickle.load(f) + + return d["t"], d["X"], d["Y"], d["Z"] + + +def assert_almost_equal(actual, expected, rtol): + decimal = np.ceil(-np.log10(rtol * np.max(expected))) + np.testing.assert_almost_equal(actual, expected, decimal=decimal) + + +def check_traces(t, X, Y, Z, steady_state): + if len(sys.argv) < 2: + return + + codegen = sys.argv[1] + if codegen == "nocmodl": + save_traces(t, X, Y, Z, steady_state) + + else: + t_ref, X_ref, Y_ref, Z_ref = load_traces(steady_state) + + assert_almost_equal(t, t_ref, rtol=1e-8) + assert_almost_equal(X, X_ref, rtol=1e-8) + assert_almost_equal(Y, Y_ref, rtol=1e-8) + assert_almost_equal(Z, Z_ref, rtol=1e-8) + + +def check_solution(steady_state): + t, X, Y, Z = run(steady_state) + check_traces(t, X, Y, Z, steady_state=steady_state) + + +def test_steady_state(): + check_solution(steady_state=True) + + +def test_no_steady_state(): + check_solution(steady_state=False) + + +if __name__ == "__main__": + test_steady_state() + test_no_steady_state() From 4d671556d6ad2cef39fd4ddd1d547f9d984ee801 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 17 Sep 2024 08:16:02 +0000 Subject: [PATCH 752/871] Remove pointless comment. (BlueBrain/nmodl#1429) NMODL Repo SHA: BlueBrain/nmodl@c19643bdf38c2d905562f2a501092a5387f9aeb5 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 770bc534d6..90d3afd4b8 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -926,8 +926,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } } - - // for (const auto& f: info.function_tables) { if (!info.function_tables.empty()) { throw std::runtime_error("Not implemented, global function tables."); } From 47735090d7d30fcaa12362f67292038bbe276e40 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 17 Sep 2024 09:09:36 +0000 Subject: [PATCH 753/871] Fix `nrn_ghk`. (BlueBrain/nmodl#1432) No implicit arguments for `nrn_ghk` for NEURON. * Create usecase group for builtin_functions. NMODL Repo SHA: BlueBrain/nmodl@54b51993f76cb644e175ed446fd301f9991a8bd4 --- src/nmodl/main.cpp | 7 ++++--- src/nmodl/visitors/implicit_argument_visitor.cpp | 3 +++ src/nmodl/visitors/implicit_argument_visitor.hpp | 8 ++++++++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/builtin_functions/compile_only.mod | 7 +++++++ 5 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 1cb7bbc38d..604f850799 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -291,6 +291,8 @@ int main(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); + std::string simulator_name = neuron_code ? "neuron" : "coreneuron"; + fs::create_directories(output_dir); fs::create_directories(scratch_dir); @@ -383,8 +385,7 @@ int main(int argc, const char* argv[]) { // run perfvisitor to update read/write counts PerfVisitor().visit_program(*ast); - auto compatibility_visitor = CodegenCompatibilityVisitor(neuron_code ? "neuron" - : "coreneuron"); + auto compatibility_visitor = CodegenCompatibilityVisitor(simulator_name); // If we want to just check compatibility we return the result if (only_check_compatibility) { return compatibility_visitor.find_unhandled_ast_nodes(*ast); @@ -556,7 +557,7 @@ int main(int argc, const char* argv[]) { // Add implicit arguments (like celsius, nt) to NEURON functions (like // nrn_ghk, at_time) whose signatures we have to massage. - ImplicitArgumentVisitor{}.visit_program(*ast); + ImplicitArgumentVisitor{simulator_name}.visit_program(*ast); SymtabVisitor(update_symtab).visit_program(*ast); { diff --git a/src/nmodl/visitors/implicit_argument_visitor.cpp b/src/nmodl/visitors/implicit_argument_visitor.cpp index 14d84c1299..4d54775b1d 100644 --- a/src/nmodl/visitors/implicit_argument_visitor.cpp +++ b/src/nmodl/visitors/implicit_argument_visitor.cpp @@ -19,6 +19,9 @@ void ImplicitArgumentVisitor::visit_function_call(ast::FunctionCall& node) { auto function_name = node.get_node_name(); auto const& arguments = node.get_arguments(); if (function_name == "nrn_ghk") { + if (simulator == "neuron") { + return; + } // This function is traditionally used in MOD files with four arguments, but // its value also depends on the global celsius variable so the real // function in CoreNEURON has a 5th argument for that. diff --git a/src/nmodl/visitors/implicit_argument_visitor.hpp b/src/nmodl/visitors/implicit_argument_visitor.hpp index effca8793c..0f152d9415 100644 --- a/src/nmodl/visitors/implicit_argument_visitor.hpp +++ b/src/nmodl/visitors/implicit_argument_visitor.hpp @@ -14,6 +14,8 @@ #include "visitors/ast_visitor.hpp" +#include + namespace nmodl { namespace visitor { @@ -33,7 +35,13 @@ namespace visitor { * that they can be pure functions. */ struct ImplicitArgumentVisitor: public AstVisitor { + ImplicitArgumentVisitor(const std::string& simulator = "coreneuron") + : simulator(simulator) {} + void visit_function_call(ast::FunctionCall& node) override; + + private: + std::string simulator; }; /** \} */ // end of visitor_classes diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index ba5bf0fc6f..aa9877e840 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,5 +1,6 @@ set(NMODL_USECASE_DIRS solve + builtin_functions constant electrode_current constructor diff --git a/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod b/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod new file mode 100644 index 0000000000..a35dba9095 --- /dev/null +++ b/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod @@ -0,0 +1,7 @@ +NEURON { + SUFFIX compile_only +} + +FUNCTION call_nrn_ghk() { + call_nrn_ghk = nrn_ghk(1.0, 2.0, 3.0, 4.0) +} From 63edc516c51528162c92574327bc09e68ea21142 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 17 Sep 2024 09:39:57 +0000 Subject: [PATCH 754/871] Fiddle CONSTRUCTOR/DESTRUCTOR. (BlueBrain/nmodl#1431) NMODL Repo SHA: BlueBrain/nmodl@f71bcfedc634d2c14ce0fefdf9c705434346bf42 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 17 ++++++++++++++--- .../codegen/codegen_neuron_cpp_visitor.hpp | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 90d3afd4b8..391325f59c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1595,6 +1595,11 @@ void CodegenNeuronCppVisitor::print_callable_preamble_from_prop() { printer->add_newline(); } +void CodegenNeuronCppVisitor::print_nrn_constructor_declaration() { + if (info.constructor_node) { + printer->fmt_line("void {}(Prop* prop);", method_name(naming::NRN_CONSTRUCTOR_METHOD)); + } +} void CodegenNeuronCppVisitor::print_nrn_constructor() { if (info.constructor_node) { @@ -1610,6 +1615,10 @@ void CodegenNeuronCppVisitor::print_nrn_constructor() { } +void CodegenNeuronCppVisitor::print_nrn_destructor_declaration() { + printer->fmt_line("void {}(Prop* prop);", method_name(naming::NRN_DESTRUCTOR_METHOD)); +} + void CodegenNeuronCppVisitor::print_nrn_destructor() { printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_DESTRUCTOR_METHOD)); print_callable_preamble_from_prop(); @@ -1745,7 +1754,7 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { printer->fmt_line("{} = nrnran123_newstream();", get_variable_name(get_name(rv), false)); } - printer->fmt_line("nrn_mech_inst_destruct[mech_type] = {};", + printer->fmt_line("nrn_mech_inst_destruct[mech_type] = neuron::{};", method_name(naming::NRN_DESTRUCTOR_METHOD)); } @@ -2148,14 +2157,16 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_prcellstate_macros(); print_mechanism_info(); print_data_structures(true); - print_nrn_constructor(); - print_nrn_destructor(); + print_nrn_constructor_declaration(); + print_nrn_destructor_declaration(); print_nrn_alloc(); print_function_prototypes(); print_functors_definitions(); print_global_variables_for_hoc(); print_thread_memory_callbacks(); print_compute_functions(); // only nrn_cur and nrn_state + print_nrn_constructor(); + print_nrn_destructor(); print_sdlists_init(true); print_mechanism_register(); print_namespace_stop(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 825173c89e..3c2f8ebd30 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -490,6 +490,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * */ void print_nrn_constructor() override; + void print_nrn_constructor_declaration(); /** * Print the set of common variables from a `Prop` only. @@ -501,6 +502,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * */ void print_nrn_destructor() override; + void print_nrn_destructor_declaration(); /** From 96b5969a43a25e9c46a067447b6ac519e13493c1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 18 Sep 2024 06:44:38 +0000 Subject: [PATCH 755/871] Implement EXTERNAL. (BlueBrain/nmodl#1430) NMODL Repo SHA: BlueBrain/nmodl@ea16a0ce3a336a07a6d5a5587175062355fcf5f3 --- .../codegen/codegen_compatibility_visitor.cpp | 18 +++++- .../codegen/codegen_compatibility_visitor.hpp | 4 ++ src/nmodl/codegen/codegen_cpp_visitor.cpp | 11 +++- src/nmodl/codegen/codegen_helper_visitor.cpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 + .../codegen/codegen_neuron_cpp_visitor.cpp | 33 ++++++++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 6 ++ src/nmodl/parser/nmodl.yy | 10 ++- test/nmodl/transpiler/unit/CMakeLists.txt | 1 + .../codegen/codegen_compatibility_visitor.cpp | 14 +++-- .../transpiler/unit/visitor/external.cpp | 62 +++++++++++++++++++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/external/dst.mod | 18 ++++++ .../transpiler/usecases/external/src.mod | 12 ++++ .../usecases/external/test_external.py | 22 +++++++ 15 files changed, 207 insertions(+), 9 deletions(-) create mode 100644 test/nmodl/transpiler/unit/visitor/external.cpp create mode 100644 test/nmodl/transpiler/usecases/external/dst.mod create mode 100644 test/nmodl/transpiler/usecases/external/src.mod create mode 100644 test/nmodl/transpiler/usecases/external/test_external.py diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.cpp b/src/nmodl/codegen/codegen_compatibility_visitor.cpp index 325f6195db..6bd3e7e6bd 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.cpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.cpp @@ -26,7 +26,7 @@ const std::map {AstNodeType::PARAM_ASSIGN, &CodegenCompatibilityVisitor::return_error_param_var}, {AstNodeType::BBCORE_POINTER_VAR, &CodegenCompatibilityVisitor::return_error_if_no_bbcore_read_write}, - {AstNodeType::EXTERNAL, &CodegenCompatibilityVisitor::return_error_with_name}}); + {AstNodeType::EXTERNAL, &CodegenCompatibilityVisitor::return_error_extern}}); std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandled( @@ -50,6 +50,22 @@ std::string CodegenCompatibilityVisitor::return_error_if_solve_method_is_unhandl return unhandled_method_error_message.str(); } +// NOLINTNEXTLINE(readability-convert-member-functions-to-static) +std::string CodegenCompatibilityVisitor::return_error_extern( + ast::Ast& node, + const std::shared_ptr& ast_node) const { + if (simulator == "neuron") { + // When generating code for NEURON, NMODL supports EXTERNAL variables, + // same as nocmodl. + return ""; + } + + // When generating code for CoreNEURON EXTERNAL variables aren't permitted. + auto external = std::dynamic_pointer_cast(ast_node); + return fmt::format("Found EXTERNAL at [{}] while generating code for CoreNEURON.\n", + external->get_token()->position()); +} + // NOLINTNEXTLINE(readability-convert-member-functions-to-static) std::string CodegenCompatibilityVisitor::return_error_global_var( ast::Ast& node, diff --git a/src/nmodl/codegen/codegen_compatibility_visitor.hpp b/src/nmodl/codegen/codegen_compatibility_visitor.hpp index 346886f2e7..8d018f0da1 100644 --- a/src/nmodl/codegen/codegen_compatibility_visitor.hpp +++ b/src/nmodl/codegen/codegen_compatibility_visitor.hpp @@ -134,6 +134,10 @@ class CodegenCompatibilityVisitor: public visitor::AstVisitor { real_type_block->get_token()->position()); } + /// Callback when detecting EXTERNAL. + std::string return_error_extern(ast::Ast& node, + const std::shared_ptr& ast_node) const; + /// Takes as parameter the ast::Ast to read the symbol table /// and an std::shared_ptr node and returns relative /// error if a variable that is writen in the mod file is diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 11aa3349f7..87dafa7d8d 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1198,7 +1198,16 @@ std::vector CodegenCppVisitor::get_float_variable info.range_assigned_vars.begin(), info.range_assigned_vars.end()); variables.insert(variables.end(), info.range_state_vars.begin(), info.range_state_vars.end()); - variables.insert(variables.end(), assigned.begin(), assigned.end()); + + for (const auto& v: assigned) { + auto it = std::find_if(info.external_variables.begin(), + info.external_variables.end(), + [&v](auto it) { return it->get_name() == get_name(v); }); + + if (it == info.external_variables.end()) { + variables.push_back(v); + } + } if (info.vectorize) { variables.push_back(make_symbol(naming::VOLTAGE_UNUSED_VARIABLE)); diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index f9d963c095..ad9dd393de 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -239,6 +239,7 @@ void CodegenHelperVisitor::find_non_range_variables() { */ info.constant_variables = psymtab->get_variables_with_properties(NmodlType::constant_var); info.top_local_variables = psymtab->get_variables_with_properties(NmodlType::local_var); + info.external_variables = psymtab->get_variables_with_properties(NmodlType::extern_var); /** * All global variables remain global if mod file is not marked thread safe. diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index c18a2945cd..6d39479e1a 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -501,6 +501,9 @@ struct CodegenInfo { /// thread variables (e.g. global variables promoted to thread) std::vector thread_variables; + /// external variables + std::vector external_variables; + /// new one used in print_ion_types std::vector use_ion_variables; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 391325f59c..1d9cb9a815 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -677,6 +677,14 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, return std::string("nt->_") + naming::NTHREAD_T_VARIABLE; } + // external variable + auto e = std::find_if(info.external_variables.begin(), + info.external_variables.end(), + name_comparator); + if (e != info.external_variables.end()) { + return fmt::format("{}()", varname); + } + auto const iter = std::find_if(info.neuron_global_variables.begin(), info.neuron_global_variables.end(), @@ -943,10 +951,35 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in print_global_var_struct_assertions(); print_global_var_struct_decl(); + print_global_var_external_access(); print_global_param_default_values(); } +void CodegenNeuronCppVisitor::print_global_var_external_access() { + for (const auto& var: codegen_global_variables) { + auto var_name = get_name(var); + auto var_expr = get_variable_name(var_name, false); + + printer->fmt_push_block("auto {}() -> std::decay::type ", + method_name(var_name), + var_expr); + printer->fmt_line("return {};", var_expr); + printer->pop_block(); + } + if (!codegen_global_variables.empty()) { + printer->add_newline(); + } + + for (const auto& var: info.external_variables) { + auto var_name = get_name(var); + printer->fmt_line("double {}();", var_name); + } + if (!info.external_variables.empty()) { + printer->add_newline(); + } +} + void CodegenNeuronCppVisitor::print_global_param_default_values() { printer->push_block("static std::vector _parameter_defaults ="); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 3c2f8ebd30..c771d84ffe 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -452,6 +452,12 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_global_variables_for_hoc() override; + /** + * Print functions for EXTERNAL use. + * + */ + void print_global_var_external_access(); + /** Print global struct with default value of RANGE PARAMETERs. */ void print_global_param_default_values(); diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index 8d910e0ca2..c20aca8e91 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -1996,7 +1996,9 @@ neuron_statement : } | neuron_statement EXTERNAL external_var_list { - $1.emplace_back(new ast::External($3)); + auto external = new ast::External($3); + external->set_token($2); + $1.emplace_back(external); $$ = $1; } | neuron_statement THREADSAFE @@ -2222,11 +2224,13 @@ random_var_list : NAME_PTR external_var_list : NAME_PTR { $$ = ast::ExternVarVector(); - $$.emplace_back(new ast::ExternVar($1)); + auto var = new ast::ExternVar($1); + $$.emplace_back(var); } | external_var_list "," NAME_PTR { - $1.emplace_back(new ast::ExternVar($3)); + auto var = new ast::ExternVar($3); + $1.emplace_back(var); $$ = $1; } | error diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index c5574453fc..44d57fe91f 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -36,6 +36,7 @@ add_executable( visitor/main.cpp visitor/after_cvode_to_cnexp.cpp visitor/constant_folder.cpp + visitor/external.cpp visitor/defuse_analyze.cpp visitor/global_to_range.cpp visitor/implicit_argument.cpp diff --git a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp index 5e686498f0..1af02a04fa 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp @@ -24,11 +24,12 @@ using namespace codegen; using nmodl::parser::NmodlDriver; /// Return true if it failed and false otherwise -bool runCompatibilityVisitor(const std::string& nmodl_text) { +bool runCompatibilityVisitor(const std::string& nmodl_text, + const std::string& simulator = "coreneuron") { const auto& ast = NmodlDriver().parse_string(nmodl_text); SymtabVisitor().visit_program(*ast); PerfVisitor().visit_program(*ast); - return CodegenCompatibilityVisitor().find_unhandled_ast_nodes(*ast); + return CodegenCompatibilityVisitor(simulator).find_unhandled_ast_nodes(*ast); } SCENARIO("Uncompatible constructs should failed", "[codegen][compatibility_visitor]") { @@ -39,10 +40,15 @@ SCENARIO("Uncompatible constructs should failed", "[codegen][compatibility_visit } )"; - THEN("should failed") { - bool failed = runCompatibilityVisitor(nmodl_text); + THEN("should fail for CoreNEURON") { + bool failed = runCompatibilityVisitor(nmodl_text, "coreneuron"); REQUIRE(failed); } + + THEN("but succeed for NEURON") { + bool failed = runCompatibilityVisitor(nmodl_text, "neuron"); + REQUIRE(!failed); + } } GIVEN("A mod file containing a written GLOBAL var") { std::string const nmodl_text = R"( diff --git a/test/nmodl/transpiler/unit/visitor/external.cpp b/test/nmodl/transpiler/unit/visitor/external.cpp new file mode 100644 index 0000000000..71541a9d80 --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/external.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2024 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "ast/program.hpp" +#include "codegen/codegen_helper_visitor.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/symtab_visitor.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test_utils; + +using ast::AstNodeType; +using nmodl::codegen::CodegenHelperVisitor; +using nmodl::codegen::CodegenInfo; +using nmodl::parser::NmodlDriver; + +CodegenInfo compute_code_info(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + + /// construct symbol table and run codegen helper visitor + SymtabVisitor().visit_program(*ast); + CodegenHelperVisitor v; + + /// symbols/variables are collected in info object + return v.analyze(*ast); +} + + +TEST_CASE("EXTERNAL variables") { + std::string mod = R"( +NEURON { + EXTERNAL gbl_foo, param_bar +} + +ASSIGNED { + foo + gbl_foo + param_bar +} +)"; + + auto info = compute_code_info(mod); + + std::vector actual; + for (const auto& var: info.external_variables) { + actual.push_back(var->get_name()); + } + std::sort(actual.begin(), actual.end()); + + std::vector expected{"gbl_foo", "param_bar"}; + + REQUIRE(actual == expected); +} diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index aa9877e840..4ac885896c 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -3,6 +3,7 @@ set(NMODL_USECASE_DIRS builtin_functions constant electrode_current + external constructor function procedure diff --git a/test/nmodl/transpiler/usecases/external/dst.mod b/test/nmodl/transpiler/usecases/external/dst.mod new file mode 100644 index 0000000000..f5e85e68d4 --- /dev/null +++ b/test/nmodl/transpiler/usecases/external/dst.mod @@ -0,0 +1,18 @@ +NEURON { + SUFFIX dst + + EXTERNAL gbl_src, param_src +} + +ASSIGNED { + gbl_src + param_src +} + +FUNCTION get_gbl() { + get_gbl = gbl_src +} + +FUNCTION get_param() { + get_param = param_src +} diff --git a/test/nmodl/transpiler/usecases/external/src.mod b/test/nmodl/transpiler/usecases/external/src.mod new file mode 100644 index 0000000000..c340f08b31 --- /dev/null +++ b/test/nmodl/transpiler/usecases/external/src.mod @@ -0,0 +1,12 @@ +NEURON { + SUFFIX src + GLOBAL gbl +} + +PARAMETER { + param = 123.0 +} + +ASSIGNED { + gbl +} diff --git a/test/nmodl/transpiler/usecases/external/test_external.py b/test/nmodl/transpiler/usecases/external/test_external.py new file mode 100644 index 0000000000..0bc98c8ee7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/external/test_external.py @@ -0,0 +1,22 @@ +from neuron import h, gui + + +def assert_equal(a, b): + assert a == b, f"{a} != {b}" + + +def test_external(): + s = h.Section() + s.nseg = 5 + + s.insert("dst") + s.insert("src") + + h.gbl_src = 321.0 + + assert_equal(s(0.5).dst.get_gbl(), h.gbl_src) + assert_equal(s(0.5).dst.get_param(), h.param_src) + + +if __name__ == "__main__": + test_external() From 19d490ca522b7ed52e055c9d3e973e863c8683a1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 19 Sep 2024 08:47:28 +0000 Subject: [PATCH 756/871] Extract NET_RECEIVE arguments and common code. (BlueBrain/nmodl#1436) NMODL Repo SHA: BlueBrain/nmodl@e521151e6b5feecf34d6c36526143f725ed76756 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 37 +++++++++++-------- .../codegen/codegen_neuron_cpp_visitor.hpp | 2 + 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 1d9cb9a815..dda6d6695e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2286,23 +2286,14 @@ static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive } } -void CodegenNeuronCppVisitor::print_net_receive() { - printing_net_receive = true; - auto node = info.net_receive_node; - if (!node) { - return; - } - - ParamVector args = {{"", "Point_process*", "", "_pnt"}, - {"", "double*", "", "_args"}, - {"", "double", "", "flag"}}; - - printer->fmt_push_block("static void nrn_net_receive_{}({})", - info.mod_suffix, - get_parameter_str(args)); +CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::net_receive_args() { + return {{"", "Point_process*", "", "_pnt"}, + {"", "double*", "", "_args"}, + {"", "double", "", "flag"}}; +} - rename_net_receive_arguments(*node, *node); +void CodegenNeuronCppVisitor::print_net_receive_common_code() { printer->add_line("_nrn_mechanism_cache_instance _lmc{_pnt->prop};"); printer->add_line("auto * nt = static_cast(_pnt->_vnt);"); printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); @@ -2318,6 +2309,22 @@ void CodegenNeuronCppVisitor::print_net_receive() { printer->add_line("size_t id = 0;"); printer->add_line("double t = nt->_t;"); +} + +void CodegenNeuronCppVisitor::print_net_receive() { + printing_net_receive = true; + auto node = info.net_receive_node; + if (!node) { + return; + } + + printer->fmt_push_block("static void nrn_net_receive_{}({})", + info.mod_suffix, + get_parameter_str(net_receive_args())); + + rename_net_receive_arguments(*node, *node); + print_net_receive_common_code(); + print_statement_block(*node->get_statement_block(), false, false); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c771d84ffe..0e12308414 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -191,6 +191,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { * Print `net_receive` call-back. */ void print_net_receive(); + void print_net_receive_common_code(); + ParamVector net_receive_args(); /** * Print code to register the call-back for the NET_RECEIVE block. From 28719da4b00882e19bea02eeeaf83f329dea4c9c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 19 Sep 2024 08:59:46 +0000 Subject: [PATCH 757/871] Cosmetic changes to netcon related code. (BlueBrain/nmodl#1439) NMODL Repo SHA: BlueBrain/nmodl@d72a25ee12c6bb1fe307eaa5ce0dabb70ef5fea4 --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 7 ++----- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 5 +++-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 06022125ea..03429a1370 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -3074,12 +3074,9 @@ void CodegenCoreneuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { printer->add_line( "const size_t for_netcon_end = nt->_fornetcon_perm_indices[indexes[offset] + 1];"); - printer->add_line("for (auto i = for_netcon_start; i < for_netcon_end; ++i) {"); - printer->increase_indent(); + printer->push_block("for (auto i = for_netcon_start; i < for_netcon_end; ++i)"); print_statement_block(*statement_block, false, false); - printer->decrease_indent(); - - printer->add_line("}"); + printer->pop_block(); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index dda6d6695e..d498c9f0c7 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1206,6 +1206,7 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { if (i != info.semantics[i].index) { throw std::runtime_error("Broken logic."); } + const auto& semantic = info.semantics[i].name; auto type = "double*"; if (name == naming::POINT_PROCESS_VARIABLE) { @@ -1213,8 +1214,8 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { } else if (name == naming::TQITEM_VARIABLE) { type = "void*"; } else if (stringutils::starts_with(name, "style_") && - stringutils::starts_with(info.semantics[i].name, "#") && - stringutils::ends_with(info.semantics[i].name, "_ion")) { + stringutils::starts_with(semantic, "#") && + stringutils::ends_with(semantic, "_ion")) { type = "int*"; } From 0d68dd51e5828290fd3eba117d1b30787f0c9cea Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 19 Sep 2024 13:17:11 +0000 Subject: [PATCH 758/871] Support `NET_RECEIVE{ INITIAL { ... }}`. (BlueBrain/nmodl#1437) NMODL Repo SHA: BlueBrain/nmodl@44c649d489eb3214e79589700932ec2349b0f5fe --- .../codegen/codegen_neuron_cpp_visitor.cpp | 27 +++++++++++++++++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 4 +++ .../usecases/net_receive/initsyn.mod | 14 ++++++++++ .../usecases/net_receive/test_initial.py | 27 +++++++++++++++++++ 4 files changed, 72 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/net_receive/initsyn.mod create mode 100644 test/nmodl/transpiler/usecases/net_receive/test_initial.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index d498c9f0c7..5f646fb511 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1269,6 +1269,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_line("pnt_receive_size[mech_type] = {};", info.num_net_receive_parameters); } + if (info.net_receive_initial_node) { + printer->fmt_line("pnt_receive_init[mech_type] = net_init;"); + } + if (info.thread_callback_register) { printer->add_line("_nrn_thread_reg(mech_type, 1, thread_mem_init);"); printer->add_line("_nrn_thread_reg(mech_type, 0, thread_mem_cleanup);"); @@ -2178,6 +2182,7 @@ void CodegenNeuronCppVisitor::print_compute_functions() { print_nrn_state(); print_nrn_jacob(); print_net_receive(); + print_net_init(); } @@ -2334,6 +2339,28 @@ void CodegenNeuronCppVisitor::print_net_receive() { printing_net_receive = false; } +void CodegenNeuronCppVisitor::print_net_init() { + const auto node = info.net_receive_initial_node; + if (node == nullptr) { + return; + } + + // rename net_receive arguments used in the initial block of net_receive + rename_net_receive_arguments(*info.net_receive_node, *node); + + printing_net_init = true; + printer->add_newline(2); + printer->fmt_push_block("static void net_init({})", get_parameter_str(net_receive_args())); + + auto block = node->get_statement_block().get(); + if (!block->get_statements().empty()) { + print_net_receive_common_code(); + print_statement_block(*block, false, false); + } + printer->pop_block(); + printing_net_init = false; +} + /****************************************************************************************/ /* Overloaded visitor routines */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 0e12308414..c5104d5d99 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -166,6 +166,10 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Printing routines for code generation */ /****************************************************************************************/ + /** + * Print NET_RECEIVE{ INITIAL{ ... }} block. + */ + void print_net_init(); /** * Print call to \c net\_send diff --git a/test/nmodl/transpiler/usecases/net_receive/initsyn.mod b/test/nmodl/transpiler/usecases/net_receive/initsyn.mod new file mode 100644 index 0000000000..431ee1bff5 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/initsyn.mod @@ -0,0 +1,14 @@ +NEURON { + POINT_PROCESS InitSyn + RANGE a0 +} + +ASSIGNED { + a0 +} + +NET_RECEIVE(w, a) { + INITIAL { + a = a0 + } +} diff --git a/test/nmodl/transpiler/usecases/net_receive/test_initial.py b/test/nmodl/transpiler/usecases/net_receive/test_initial.py new file mode 100644 index 0000000000..177f2fffbc --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/test_initial.py @@ -0,0 +1,27 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + + +def test_net_receive_initial(): + nseg = 1 + + soma = h.Section() + soma.nseg = nseg + + syn = h.InitSyn(soma(0.5)) + syn.a0 = 329.012 + + stim = h.NetStim() + nc = h.NetCon(stim, syn, 0, 0.0, 0.1) + + h.stdinit() + + assert nc.wcnt() == 2 + assert nc.weight[0] == 0.1 + assert nc.weight[1] == syn.a0 + + +if __name__ == "__main__": + test_net_receive_initial() From dfdfd47a2cbb684fd5a920b0a5ae85b61ac24374 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 19 Sep 2024 13:33:34 +0000 Subject: [PATCH 759/871] Print more information on failure. (BlueBrain/nmodl#1441) Currently, on failure it will output: ``` terminate called after throwing an instance of 'std::runtime_error' what(): NMODL Parser Error : syntax error, unexpected / [Location : 1.40] ---------------------------------------^ ``` but it's entirely unclear what it was processing when it crashed. It's also not obvious from the surrounding context when building NEURON. This PR changes it such that we have: ``` [FATAL] NMODL encountered an unhandled exception. cwd = "build/test/nrnivmodl/53c4" nmodl Aradi_CadepK.mod --neuron -o build/test/nrnivmodl/53c4/x86_64 ``` followed by the exception. NMODL Repo SHA: BlueBrain/nmodl@a6d6d276565caccdd652f754fcee3b816a9b4d83 --- src/nmodl/main.cpp | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 604f850799..f12bfe35dd 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -63,7 +63,7 @@ using namespace visitor; using nmodl::parser::NmodlDriver; // NOLINTNEXTLINE(readability-function-cognitive-complexity) -int main(int argc, const char* argv[]) { +int run_nmodl(int argc, const char* argv[]) { CLI::App app{fmt::format("NMODL : Source-to-Source Code Generation Framework [{}]", Version::to_string())}; @@ -619,4 +619,23 @@ int main(int argc, const char* argv[]) { } } } + return EXIT_SUCCESS; +} + +int main(int argc, const char* argv[]) { + try { + return run_nmodl(argc, argv); + } catch (const std::runtime_error& e) { + std::cerr << "[FATAL] NMODL encountered an unhandled exception.\n"; + std::cerr << " cwd = " << std::filesystem::current_path() << "\n"; + std::cerr << " "; + for (int i = 0; i < argc; ++i) { + std::cerr << argv[i] << " "; + } + std::cerr << std::endl; + + throw e; + } + + return EXIT_SUCCESS; } From fce3ed4a65024d392c7f99245c6884bbeec5e0d7 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 19 Sep 2024 15:13:35 +0000 Subject: [PATCH 760/871] Print tracebacks of Python exceptions. (BlueBrain/nmodl#1442) The SymPy solvers are meant to be run again on code that has already been transformed. However, since the output of the SymPy solvers isn't valid input for the SymPy solver they crash the second time around. Failure is interpreted as: "it already ran" and not considered a failure. Naturally, this is rarely the case while developing and it's always a bug. The workaround here is to print the traceback as a separate log message of severity "info". Unfortunately, `pybind11` injects a line: # -*- coding: utf-8 -*- Hence, all line numbers are off by two; because `ode_py` starts with an empty line. Old output: [NMODL] [warning] :: SympySolverVisitor :: solve_non_lin_system python exception: name 'x' is not defined New output: [NMODL] [warning] :: SympySolverVisitor :: solve_non_lin_system python exception. (--verbose=info) [NMODL] [info] :: Traceback (most recent call last): File "", line 691, in File "", line 416, in solve_non_lin_system File "", line 385, in finite_difference_variables File "", line 382, in recurse File "", line 352, in finite_difference_step_variable NameError: name 'x' is not defined NMODL Repo SHA: BlueBrain/nmodl@ba8cf99cdc466ae89b5615be0ff0609ecce96544 --- src/nmodl/pybind/wrapper.cpp | 15 ++++++++++----- src/nmodl/visitors/sympy_solver_visitor.cpp | 16 +++++++++++----- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 74f4a2cd59..92f679a6a1 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -51,9 +51,10 @@ exception_message = "" do_cse) except Exception as e: # if we fail, fail silently and return empty string + import traceback solutions = [""] new_local_vars = [""] - exception_message = str(e) + exception_message = traceback.format_exc() )"; py::exec(nmodl::pybind_wrappers::ode_py + script, locals); @@ -86,9 +87,10 @@ exception_message = "" function_calls) except Exception as e: # if we fail, fail silently and return empty string + import traceback solutions = [""] new_local_vars = [""] - exception_message = str(e) + exception_message = traceback.format_exc() )"; py::exec(nmodl::pybind_wrappers::ode_py + script, locals); @@ -123,8 +125,9 @@ exception_message = "" solution = forwards_euler2c(equation_string, dt_var, vars, function_calls) except Exception as e: # if we fail, fail silently and return empty string + import traceback solution = "" - exception_message = str(e) + exception_message = traceback.format_exc() )"; py::exec(nmodl::pybind_wrappers::ode_py + script, locals); @@ -139,8 +142,9 @@ exception_message = "" use_pade_approx) except Exception as e: # if we fail, fail silently and return empty string + import traceback solution = "" - exception_message = str(e) + exception_message = traceback.format_exc() )"; py::exec(nmodl::pybind_wrappers::ode_py + script, locals); @@ -170,8 +174,9 @@ exception_message = "" ) except Exception as e: # if we fail, fail silently and return empty string + import traceback solution = "" - exception_message = str(e) + exception_message = traceback.format_exc() )"; py::exec(nmodl::pybind_wrappers::ode_py + script, locals); diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 206145efbc..d4a0d34d39 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -321,8 +321,10 @@ void SympySolverVisitor::solve_linear_system(const ast::Node& node, eq_system, state_vars, vars, small_system, elimination, tmp_unique_prefix, function_calls); if (!exception_message.empty()) { - logger->warn("SympySolverVisitor :: solve_lin_system python exception: " + - exception_message); + logger->warn( + "SympySolverVisitor :: solve_lin_system python exception occured. (--verbose=info)"); + logger->info(exception_message + + "\n (Note: line numbers are of by a few compared to `ode.py`.)"); return; } // find out where to insert solutions in statement block @@ -363,8 +365,10 @@ void SympySolverVisitor::solve_non_linear_system( auto [solutions, exception_message] = solver(eq_system, state_vars, vars, function_calls); if (!exception_message.empty()) { - logger->warn("SympySolverVisitor :: solve_non_lin_system python exception: " + - exception_message); + logger->warn( + "SympySolverVisitor :: solve_non_lin_system python exception. (--verbose=info)"); + logger->info(exception_message + + "\n (Note: line numbers are of by a few compared to `ode.py`.)"); return; } logger->debug("SympySolverVisitor :: Constructing eigen newton solve block"); @@ -449,7 +453,9 @@ void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { logger->debug("SympySolverVisitor :: -> solution: {}", solution); if (!exception_message.empty()) { - logger->warn("SympySolverVisitor :: python exception: " + exception_message); + logger->warn("SympySolverVisitor :: python exception. (--verbose=info)"); + logger->info(exception_message + + "\n (Note: line numbers are of by a few compared to `ode.py`.)"); return; } From 335800c1e289895f8cda98321f5ba838cfac2ffd Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 20 Sep 2024 07:57:38 +0000 Subject: [PATCH 761/871] Add the missing `fornetcon_data` "integer" variable. (BlueBrain/nmodl#1438) NMODL Repo SHA: BlueBrain/nmodl@db0347efd9ead7abae58c06f0321d12bcbfee0b9 --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 11 ++--------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 4 ++++ src/nmodl/codegen/codegen_naming.hpp | 3 +++ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 03429a1370..1002b886fd 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -1426,11 +1426,7 @@ void CodegenCoreneuronCppVisitor::print_mechanism_register() { net_recv_init_arg); } if (info.for_netcon_used) { - // index where information about FOR_NETCON is stored in the integer array - const auto index = - std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { - return a.name == naming::FOR_NETCON_SEMANTIC; - })->index; + const auto index = position_of_int_var(naming::FOR_NETCON_VARIABLE); printer->fmt_line("add_nrn_fornetcons(mech_type, {});", index); } @@ -3062,10 +3058,7 @@ void CodegenCoreneuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { statement_block->accept(v); } - const auto index = - std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { - return a.name == naming::FOR_NETCON_SEMANTIC; - })->index; + const auto index = position_of_int_var(naming::FOR_NETCON_VARIABLE); printer->fmt_text("const size_t offset = {}*pnodecount + id;", index); printer->add_newline(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 87dafa7d8d..09bfbea852 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1354,6 +1354,10 @@ std::vector CodegenCppVisitor::get_int_variables() { variables.emplace_back(make_symbol(fmt::format("watch{}", i)), false, false, true); } } + + if (info.for_netcon_used) { + variables.emplace_back(make_symbol(naming::FOR_NETCON_VARIABLE), false, false, true); + } return variables; } diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index bc657e6f7b..89cfcc7da3 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -137,6 +137,9 @@ static constexpr char WATCH_SEMANTIC[] = "watch"; /// semantic type for for_netcon statement static constexpr char FOR_NETCON_SEMANTIC[] = "fornetcon"; +/// name of the integer variabe to store FOR_NETCON info. +static constexpr char FOR_NETCON_VARIABLE[] = "fornetcon_data"; + /// nrn_init method in generated code static constexpr char NRN_INIT_METHOD[] = "nrn_init"; From 64dba273c8c25c5613f47524e1dc22d7acb41b9b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 20 Sep 2024 07:57:53 +0000 Subject: [PATCH 762/871] Refactor statement splitting code. (BlueBrain/nmodl#1443) NMODL Repo SHA: BlueBrain/nmodl@3584a03198ed8cc1efeb17c53fe534af5181acb2 --- src/nmodl/visitors/sympy_solver_visitor.cpp | 29 +++++++++++---------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index d4a0d34d39..8e3c8fdc11 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -233,20 +233,21 @@ void SympySolverVisitor::construct_eigen_solver_block( } if (sr_begin != statements.size()) { - initialize_statements.insert(initialize_statements.end(), - statements.begin() + sr_begin, - statements.begin() + sr_begin + - static_cast(pre_solve_statements.size())); - setup_x_statements = ast::StatementVector( - statements.begin() + sr_begin + - static_cast(pre_solve_statements.size()), - statements.begin() + sr_begin + - static_cast(pre_solve_statements.size() + state_vars.size())); - functor_statements = ast::StatementVector( - statements.begin() + sr_begin + - static_cast(pre_solve_statements.size() + state_vars.size()), - statements.begin() + sr_end); - finalize_statements = ast::StatementVector(statements.begin() + sr_end, statements.end()); + auto init_begin = statements.begin() + sr_begin; + auto init_end = init_begin + static_cast(pre_solve_statements.size()); + initialize_statements.insert(initialize_statements.end(), init_begin, init_end); + + auto setup_x_begin = init_end; + auto setup_x_end = setup_x_begin + static_cast(state_vars.size()); + setup_x_statements = ast::StatementVector(setup_x_begin, setup_x_end); + + auto functor_begin = setup_x_end; + auto functor_end = statements.begin() + sr_end; + functor_statements = ast::StatementVector(functor_begin, functor_end); + + auto finalize_begin = functor_end; + auto finalize_end = statements.end(); + finalize_statements = ast::StatementVector(finalize_begin, finalize_end); } const size_t total_statements_size = variable_statements.size() + initialize_statements.size() + From 46b9ab967df59ade608e76a31c0559ec4797c813 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 20 Sep 2024 09:21:11 +0000 Subject: [PATCH 763/871] Implement FOR_NETCONS. (BlueBrain/nmodl#1440) NMODL Repo SHA: BlueBrain/nmodl@4d3848e7c30f7c2308f2a88549e9d05001d8f53c --- .../codegen/codegen_neuron_cpp_visitor.cpp | 97 ++++++++++++++++--- .../codegen/codegen_neuron_cpp_visitor.hpp | 1 + .../usecases/net_receive/for_netcons_syn.mod | 18 ++++ .../usecases/net_receive/test_for_netcons.py | 59 +++++++++++ 4 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/net_receive/for_netcons_syn.mod create mode 100644 test/nmodl/transpiler/usecases/net_receive/test_for_netcons.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 5f646fb511..9e7933c6ef 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -105,19 +105,17 @@ void CodegenNeuronCppVisitor::print_point_process_function_definitions() { printer->push_block("static void _hoc_destroy_pnt(void* _vptr)"); if (info.is_watch_used() || info.for_netcon_used) { printer->add_line("Prop* _prop = ((Point_process*)_vptr)->prop;"); - } - if (info.is_watch_used()) { - printer->push_block("if (_prop)"); - printer->fmt_line("_nrn_free_watch(_nrn_mechanism_access_dparam(_prop), {}, {});", - info.watch_count, - info.is_watch_used()); - printer->pop_block(); - } - if (info.for_netcon_used) { printer->push_block("if (_prop)"); - printer->fmt_line( - "_nrn_free_fornetcon(&(_nrn_mechanism_access_dparam(_prop)[_fnc_index].literal_" - "value()));"); + printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(_prop);"); + if (info.is_watch_used()) { + printer->fmt_line("_nrn_free_watch(_ppvar, {}, {});", + info.watch_count, + info.is_watch_used()); + } + if (info.for_netcon_used) { + auto fornetcon_data = get_variable_name("fornetcon_data", false); + printer->fmt_line("_nrn_free_fornetcon(&{});", fornetcon_data); + } printer->pop_block(); } printer->add_line("destroy_point_process(_vptr);"); @@ -568,6 +566,10 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& return fmt::format("_ppvar[{}].literal_value()", position); } + if (info.semantics[position].name == naming::FOR_NETCON_SEMANTIC) { + return fmt::format("_ppvar[{}].literal_value()", position); + } + if (symbol.is_index) { if (use_instance) { throw std::runtime_error("Not implemented. [wiejo]"); @@ -1217,6 +1219,8 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { stringutils::starts_with(semantic, "#") && stringutils::ends_with(semantic, "_ion")) { type = "int*"; + } else if (semantic == naming::FOR_NETCON_SEMANTIC) { + type = "void*"; } mech_register_args.push_back( @@ -1257,7 +1261,20 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { } if (info.net_event_used) { - printer->fmt_line("add_nrn_has_net_event(mech_type);"); + printer->add_line("add_nrn_has_net_event(mech_type);"); + } + + if (info.for_netcon_used) { + auto dparam_it = + std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { + return a.name == naming::FOR_NETCON_SEMANTIC; + }); + if (dparam_it == info.semantics.end()) { + throw std::runtime_error("Couldn't find `fornetcon` variable."); + } + + int dparam_index = dparam_it->index; + printer->fmt_line("add_nrn_fornetcons(mech_type, {});", dparam_index); } printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc);"); @@ -2135,6 +2152,9 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { if (info.table_count > 0) { printer->add_line("void _nrn_thread_table_reg(int, nrn_thread_table_check_t);"); } + if (info.for_netcon_used) { + printer->add_line("int _nrn_netcon_args(void*, double***);"); + } } @@ -2371,6 +2391,57 @@ void CodegenNeuronCppVisitor::visit_watch_statement(const ast::WatchStatement& / return; } +void CodegenNeuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { + // The setup for enabling this loop is: + // double ** _fornetcon_data = ...; + // for(size_t i = 0; i < n_netcons; ++i) { + // double * _netcon_data = _fornetcon_data[i]; + // + // // loop body. + // } + // + // Where `_fornetcon_data` is an array of pointers to the arguments, one + // for each netcon; and `_netcon_data` points to the arguments for the + // current netcon. + // + // Similar to the CoreNEURON solution, we replace all arguments with the + // C++ string that implements them, i.e. `_netcon_data[{}]`. The arguments + // are positional and thus simply numbered through. + const auto& args = node.get_parameters(); + RenameVisitor v; + const auto& statement_block = node.get_statement_block(); + for (size_t i_arg = 0; i_arg < args.size(); ++i_arg) { + auto old_name = args[i_arg]->get_node_name(); + auto new_name = fmt::format("_netcon_data[{}]", i_arg); + v.set(old_name, new_name); + statement_block->accept(v); + } + + auto dparam_it = + std::find_if(info.semantics.begin(), info.semantics.end(), [](const IndexSemantics& a) { + return a.name == naming::FOR_NETCON_SEMANTIC; + }); + if (dparam_it == info.semantics.end()) { + throw std::runtime_error("Couldn't find `fornetcon` variable."); + } + + int dparam_index = dparam_it->index; + auto netcon_var = get_name(codegen_int_variables[dparam_index]); + + // This is called from `print_statement_block` which pre-indents the + // current line. Hence `add_text` only. + printer->add_text("double ** _fornetcon_data;"); + printer->add_newline(); + + printer->fmt_line("int _n_netcons = _nrn_netcon_args({}, &_fornetcon_data);", + get_variable_name(netcon_var, false)); + + printer->push_block("for (size_t _i = 0; _i < _n_netcons; ++_i)"); + printer->add_line("double * _netcon_data = _fornetcon_data[_i];"); + print_statement_block(*statement_block, false, false); + printer->pop_block(); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c5104d5d99..07a0529141 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -687,6 +687,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void visit_watch_statement(const ast::WatchStatement& node) override; + void visit_for_netcon(const ast::ForNetcon& node) override; diff --git a/test/nmodl/transpiler/usecases/net_receive/for_netcons_syn.mod b/test/nmodl/transpiler/usecases/net_receive/for_netcons_syn.mod new file mode 100644 index 0000000000..529b9b81f2 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/for_netcons_syn.mod @@ -0,0 +1,18 @@ +NEURON { + POINT_PROCESS ForNetconsSyn + RANGE a0 +} + +ASSIGNED { + a0 +} + +NET_RECEIVE(w, a) { + INITIAL { + a = a0 + } + + FOR_NETCONS(wi, ai) { + ai = 2.0*ai + } +} diff --git a/test/nmodl/transpiler/usecases/net_receive/test_for_netcons.py b/test/nmodl/transpiler/usecases/net_receive/test_for_netcons.py new file mode 100644 index 0000000000..a6b354dc05 --- /dev/null +++ b/test/nmodl/transpiler/usecases/net_receive/test_for_netcons.py @@ -0,0 +1,59 @@ +import numpy as np + +from neuron import h, gui +from neuron.units import ms + + +def assert_equal(a, b): + assert a == b, f"{a} != {b}" + + +def test_for_netcons(): + nseg = 1 + + soma = h.Section() + soma.nseg = nseg + + syn1 = h.ForNetconsSyn(soma(0.5)) + syn1.a0 = 1.0 + + syn2 = h.ForNetconsSyn(soma(0.5)) + syn2.a0 = -1.0 + + stim = h.NetStim() + stim.interval = 1.0 + stim.number = 3 + stim.start = 0.2 + + nc1 = h.NetCon(stim, syn1, 0, 0.0, 0.1) + nc2 = h.NetCon(stim, syn2, 0, 0.0, 0.2) + nc3 = h.NetCon(stim, syn1, 0, 0.0, 0.3) + + all_ncs = [nc1, nc2, nc3] + + h.stdinit() + + for nc in all_ncs: + # w and a + assert_equal(nc.wcnt(), 2) + + assert_equal(nc1.weight[0], 0.1) + assert_equal(nc1.weight[1], 1.0) + + assert_equal(nc2.weight[0], 0.2) + assert_equal(nc2.weight[1], -1.0) + + assert_equal(nc3.weight[0], 0.3) + assert_equal(nc3.weight[1], 1.0) + + h.continuerun(10.0 * ms) + + # Each event doubles the value of all connected + # NetCons. + assert_equal(nc1.weight[1], 2**6 * syn1.a0) + assert_equal(nc3.weight[1], 2**6 * syn1.a0) + assert_equal(nc2.weight[1], 2**3 * syn2.a0) + + +if __name__ == "__main__": + test_for_netcons() From 77f0507c8a791e1e5421f52532b205936bd16812 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 08:40:21 +0000 Subject: [PATCH 764/871] Format MOD files. (BlueBrain/nmodl#1445) and sort `test/usecases/CMakeLists.txt`. NMODL Repo SHA: BlueBrain/nmodl@232c2126a666b15f32f751ead6a3c4324e8a710c --- test/nmodl/transpiler/usecases/CMakeLists.txt | 18 +- .../transpiler/usecases/at_time/example.mod | 2 +- .../builtin_functions/compile_only.mod | 4 +- .../transpiler/usecases/constant/constant.mod | 6 +- .../usecases/constructor/art_ctor.mod | 8 +- .../transpiler/usecases/constructor/ctor.mod | 8 +- .../transpiler/usecases/external/dst.mod | 12 +- .../transpiler/usecases/external/src.mod | 8 +- .../function/artificial_functions.mod | 4 +- .../usecases/function/compile_only.mod | 11 +- .../usecases/function/non_threadsafe.mod | 4 +- .../function/point_non_threadsafe.mod | 4 +- .../usecases/global/non_threadsafe.mod | 4 +- .../transpiler/usecases/global/read_only.mod | 8 +- .../usecases/global/thread_variable.mod | 54 +++--- .../transpiler/usecases/global/top_local.mod | 22 +-- .../usecases/hodgkin_huxley/hodhux.mod | 179 +++++++++--------- test/nmodl/transpiler/usecases/linear/lin.mod | 4 +- .../usecases/morphology/two_radii.mod | 22 +-- .../usecases/net_event/receiver.mod | 13 +- .../transpiler/usecases/net_event/spiker.mod | 23 +-- .../usecases/net_move/art_spiker.mod | 28 +-- .../transpiler/usecases/net_move/spiker.mod | 28 +-- .../usecases/net_send/art_toggle.mod | 18 +- .../transpiler/usecases/net_send/toggle.mod | 18 +- .../usecases/nonspecific_current/leonhard.mod | 12 +- .../transpiler/usecases/parameter/limits.mod | 2 +- .../transpiler/usecases/solve/cnexp_array.mod | 6 +- .../usecases/solve/cnexp_scalar.mod | 12 +- .../usecases/solve/derivimplicit_array.mod | 6 +- .../usecases/solve/derivimplicit_scalar.mod | 12 +- .../usecases/spike_travel/expsyn2.mod | 32 ++-- .../usecases/state/default_values.mod | 18 +- .../usecases/steady_state/minipump.mod | 44 ++--- .../transpiler/usecases/useion/ionic.mod | 10 +- .../transpiler/usecases/useion/read_cai.mod | 4 +- .../transpiler/usecases/useion/read_cao.mod | 4 +- .../transpiler/usecases/useion/read_eca.mod | 4 +- .../transpiler/usecases/useion/style_ion.mod | 6 +- .../transpiler/usecases/useion/valence.mod | 4 +- 40 files changed, 345 insertions(+), 341 deletions(-) diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 4ac885896c..66cfc73d5f 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -1,32 +1,32 @@ set(NMODL_USECASE_DIRS - solve + at_time builtin_functions constant + constructor electrode_current external - constructor function - procedure global hodgkin_huxley kinetic linear morphology - nonspecific_current - neuron_variables net_event net_move net_receive net_send - point_process + neuron_variables + nonspecific_current parameter + point_process + procedure random - suffix + solve state steady_state + suffix table - useion - at_time) + useion) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/at_time/example.mod b/test/nmodl/transpiler/usecases/at_time/example.mod index afe1842f57..a370eafdf4 100755 --- a/test/nmodl/transpiler/usecases/at_time/example.mod +++ b/test/nmodl/transpiler/usecases/at_time/example.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX example + SUFFIX example } FUNCTION f(x) { diff --git a/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod b/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod index a35dba9095..1bab56b13b 100644 --- a/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod +++ b/test/nmodl/transpiler/usecases/builtin_functions/compile_only.mod @@ -1,7 +1,7 @@ NEURON { - SUFFIX compile_only + SUFFIX compile_only } FUNCTION call_nrn_ghk() { - call_nrn_ghk = nrn_ghk(1.0, 2.0, 3.0, 4.0) + call_nrn_ghk = nrn_ghk(1.0, 2.0, 3.0, 4.0) } diff --git a/test/nmodl/transpiler/usecases/constant/constant.mod b/test/nmodl/transpiler/usecases/constant/constant.mod index f1b6cc854a..c8fa276697 100644 --- a/test/nmodl/transpiler/usecases/constant/constant.mod +++ b/test/nmodl/transpiler/usecases/constant/constant.mod @@ -1,11 +1,11 @@ NEURON { - SUFFIX constant_mod + SUFFIX constant_mod } CONSTANT { - a = 2.3 + a = 2.3 } FUNCTION foo() { - foo = a + foo = a } diff --git a/test/nmodl/transpiler/usecases/constructor/art_ctor.mod b/test/nmodl/transpiler/usecases/constructor/art_ctor.mod index 9b89aed766..1348a61655 100644 --- a/test/nmodl/transpiler/usecases/constructor/art_ctor.mod +++ b/test/nmodl/transpiler/usecases/constructor/art_ctor.mod @@ -4,14 +4,14 @@ NEURON { } ASSIGNED { - ctor_calls - dtor_calls + ctor_calls + dtor_calls } CONSTRUCTOR { - ctor_calls = ctor_calls + 1 + ctor_calls = ctor_calls + 1 } DESTRUCTOR { - dtor_calls = dtor_calls + 1 + dtor_calls = dtor_calls + 1 } diff --git a/test/nmodl/transpiler/usecases/constructor/ctor.mod b/test/nmodl/transpiler/usecases/constructor/ctor.mod index 9d4c5dc5d0..749c276d1b 100644 --- a/test/nmodl/transpiler/usecases/constructor/ctor.mod +++ b/test/nmodl/transpiler/usecases/constructor/ctor.mod @@ -4,14 +4,14 @@ NEURON { } ASSIGNED { - ctor_calls - dtor_calls + ctor_calls + dtor_calls } CONSTRUCTOR { - ctor_calls = ctor_calls + 1 + ctor_calls = ctor_calls + 1 } DESTRUCTOR { - dtor_calls = dtor_calls + 1 + dtor_calls = dtor_calls + 1 } diff --git a/test/nmodl/transpiler/usecases/external/dst.mod b/test/nmodl/transpiler/usecases/external/dst.mod index f5e85e68d4..3a321a6fae 100644 --- a/test/nmodl/transpiler/usecases/external/dst.mod +++ b/test/nmodl/transpiler/usecases/external/dst.mod @@ -1,18 +1,18 @@ NEURON { - SUFFIX dst + SUFFIX dst - EXTERNAL gbl_src, param_src + EXTERNAL gbl_src, param_src } ASSIGNED { - gbl_src - param_src + gbl_src + param_src } FUNCTION get_gbl() { - get_gbl = gbl_src + get_gbl = gbl_src } FUNCTION get_param() { - get_param = param_src + get_param = param_src } diff --git a/test/nmodl/transpiler/usecases/external/src.mod b/test/nmodl/transpiler/usecases/external/src.mod index c340f08b31..f3154856db 100644 --- a/test/nmodl/transpiler/usecases/external/src.mod +++ b/test/nmodl/transpiler/usecases/external/src.mod @@ -1,12 +1,12 @@ NEURON { - SUFFIX src - GLOBAL gbl + SUFFIX src + GLOBAL gbl } PARAMETER { - param = 123.0 + param = 123.0 } ASSIGNED { - gbl + gbl } diff --git a/test/nmodl/transpiler/usecases/function/artificial_functions.mod b/test/nmodl/transpiler/usecases/function/artificial_functions.mod index a6d574fde6..e684591f83 100644 --- a/test/nmodl/transpiler/usecases/function/artificial_functions.mod +++ b/test/nmodl/transpiler/usecases/function/artificial_functions.mod @@ -25,10 +25,10 @@ INITIAL { : A LINEAR block makes a MOD file not VECTORIZED. STATE { - z + z } LINEAR lin { - ~ z = 2 + ~ z = 2 } diff --git a/test/nmodl/transpiler/usecases/function/compile_only.mod b/test/nmodl/transpiler/usecases/function/compile_only.mod index 067b8dd7dc..c802228c54 100644 --- a/test/nmodl/transpiler/usecases/function/compile_only.mod +++ b/test/nmodl/transpiler/usecases/function/compile_only.mod @@ -11,14 +11,9 @@ PARAMETER { c = 1 } -PROCEDURE func() { -} - -PROCEDURE func_with_v(v) { -} - -PROCEDURE func_with_other(q) { -} +PROCEDURE func() { } +PROCEDURE func_with_v(v) { } +PROCEDURE func_with_other(q) { } BREAKPOINT { func() diff --git a/test/nmodl/transpiler/usecases/function/non_threadsafe.mod b/test/nmodl/transpiler/usecases/function/non_threadsafe.mod index 8c84056427..ee1789a12a 100644 --- a/test/nmodl/transpiler/usecases/function/non_threadsafe.mod +++ b/test/nmodl/transpiler/usecases/function/non_threadsafe.mod @@ -29,10 +29,10 @@ INITIAL { : A LINEAR block makes a MOD file not VECTORIZED. STATE { - z + z } LINEAR lin { - ~ z = 2 + ~ z = 2 } diff --git a/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod b/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod index 27326822e0..13ce35cca2 100644 --- a/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod +++ b/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod @@ -29,10 +29,10 @@ INITIAL { : A LINEAR block makes a MOD file not VECTORIZED. STATE { - z + z } LINEAR lin { - ~ z = 2 + ~ z = 2 } diff --git a/test/nmodl/transpiler/usecases/global/non_threadsafe.mod b/test/nmodl/transpiler/usecases/global/non_threadsafe.mod index 8605782486..54eb96aed9 100644 --- a/test/nmodl/transpiler/usecases/global/non_threadsafe.mod +++ b/test/nmodl/transpiler/usecases/global/non_threadsafe.mod @@ -34,10 +34,10 @@ INITIAL { : vectorized. We don't otherwise care about anything below : this comment. STATE { - z + z } LINEAR lin { - ~ z = 2 + ~ z = 2 } diff --git a/test/nmodl/transpiler/usecases/global/read_only.mod b/test/nmodl/transpiler/usecases/global/read_only.mod index 32b550333b..9fc3ac43c9 100644 --- a/test/nmodl/transpiler/usecases/global/read_only.mod +++ b/test/nmodl/transpiler/usecases/global/read_only.mod @@ -4,17 +4,17 @@ NEURON { } PARAMETER { - c = 2.0 + c = 2.0 } STATE { - x + x } INITIAL { - x = 42 + x = 42 } BREAKPOINT { - x = c + x = c } diff --git a/test/nmodl/transpiler/usecases/global/thread_variable.mod b/test/nmodl/transpiler/usecases/global/thread_variable.mod index 19478428a3..8383258b0e 100644 --- a/test/nmodl/transpiler/usecases/global/thread_variable.mod +++ b/test/nmodl/transpiler/usecases/global/thread_variable.mod @@ -1,48 +1,48 @@ NEURON { - SUFFIX shared_global - NONSPECIFIC_CURRENT il - RANGE y, z - GLOBAL g_v1, g_w, g_arr - THREADSAFE + SUFFIX shared_global + NONSPECIFIC_CURRENT il + RANGE y, z + GLOBAL g_v1, g_w, g_arr + THREADSAFE } ASSIGNED { - y - z - g_w - g_arr[3] - il + y + z + g_w + g_arr[3] + il } INITIAL { - g_w = 48.0 - g_v1 = 0.0 - g_arr[0] = 10.0 + z - g_arr[1] = 10.1 - g_arr[2] = 10.2 - y = 10.0 + g_w = 48.0 + g_v1 = 0.0 + g_arr[0] = 10.0 + z + g_arr[1] = 10.1 + g_arr[2] = 10.2 + y = 10.0 } BREAKPOINT { - if(t > 0.33) { - g_w = sum_arr() - } + if(t > 0.33) { + g_w = sum_arr() + } - if(t > 0.66) { - set_g_w(z) - compute_g_v1(z) - } + if(t > 0.66) { + set_g_w(z) + compute_g_v1(z) + } - y = g_w + g_v1 - il = 0.0000001 * (v - 10.0) + y = g_w + g_v1 + il = 0.0000001 * (v - 10.0) } FUNCTION sum_arr() { - sum_arr = g_arr[0] + g_arr[1] + g_arr[2] + sum_arr = g_arr[0] + g_arr[1] + g_arr[2] } PROCEDURE set_g_w(zz) { - g_w = zz + g_w = zz } PROCEDURE compute_g_v1(zz) { diff --git a/test/nmodl/transpiler/usecases/global/top_local.mod b/test/nmodl/transpiler/usecases/global/top_local.mod index 1350246ea7..ad4d36e7e4 100644 --- a/test/nmodl/transpiler/usecases/global/top_local.mod +++ b/test/nmodl/transpiler/usecases/global/top_local.mod @@ -1,25 +1,25 @@ NEURON { - SUFFIX top_local - NONSPECIFIC_CURRENT il - RANGE y + SUFFIX top_local + NONSPECIFIC_CURRENT il + RANGE y } ASSIGNED { - y - il + y + il } LOCAL gbl INITIAL { - gbl = 2.0 + gbl = 2.0 } BREAKPOINT { - if(t > 0.33) { - gbl = 3.0 - } + if(t > 0.33) { + gbl = 3.0 + } - y = gbl - il = 0.0000001 * (v - 10.0) + y = gbl + il = 0.0000001 * (v - 10.0) } diff --git a/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod b/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod index 969a25c433..08fe24bb79 100644 --- a/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod +++ b/test/nmodl/transpiler/usecases/hodgkin_huxley/hodhux.mod @@ -1,114 +1,121 @@ TITLE hodhux.mod squid sodium, potassium, and leak channels - + COMMENT - This is the original Hodgkin-Huxley treatment for the set of sodium, - potassium, and leakage channels found in the squid giant axon membrane. - ("A quantitative description of membrane current and its application - conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).) - Membrane voltage is in absolute mV and has been reversed in polarity - from the original HH convention and shifted to reflect a resting potential - of -65 mV. - Initialize this mechanism to steady-state voltage by calling - rates_gsquid(v) from HOC, then setting m_gsquid=minf_gsquid, etc. - Remember to set celsius=6.3 (or whatever) in your HOC file. - See hh1.hoc for an example of a simulation using this model. - SW Jaslove 6 March, 1992 +This is the original Hodgkin-Huxley treatment for the set of sodium, potassium, +and leakage channels found in the squid giant axon membrane. +("A quantitative description of membrane current and its application +conduction and excitation in nerve" J.Physiol. (Lond.) 117:500-544 (1952).) + +Membrane voltage is in absolute mV and has been reversed in polarity +from the original HH convention and shifted to reflect a resting potential +of -65 mV. + +Initialize this mechanism to steady-state voltage by calling rates_gsquid(v) +from HOC, then setting m_gsquid=minf_gsquid, etc. + +Remember to set celsius=6.3 (or whatever) in your HOC file. + +See hh1.hoc for an example of a simulation using this model. +SW Jaslove 6 March, 1992 ENDCOMMENT - + UNITS { - (mA) = (milliamp) - (mV) = (millivolt) + (mA) = (milliamp) + (mV) = (millivolt) } - + NEURON { - SUFFIX hodhux - USEION na READ ena WRITE ina - USEION k READ ek WRITE ik - NONSPECIFIC_CURRENT il - RANGE gnabar, gkbar, gl, el - RANGE minf, hinf, ninf, mexp, hexp, nexp + SUFFIX hodhux + USEION na READ ena WRITE ina + USEION k READ ek WRITE ik + NONSPECIFIC_CURRENT il + RANGE gnabar, gkbar, gl, el + RANGE minf, hinf, ninf, mexp, hexp, nexp } - + PARAMETER { - v (mV) - celsius = 6.3 (degC) - dt (ms) - gnabar = .12 (mho/cm2) - ena = 50 (mV) - gkbar = .036 (mho/cm2) - ek = -77.5 (mV) - gl = .0003 (mho/cm2) - el = -54.3 (mV) + v (mV) + celsius = 6.3 (degC) + dt (ms) + gnabar = .12 (mho/cm2) + ena = 50 (mV) + gkbar = .036 (mho/cm2) + ek = -77.5 (mV) + gl = .0003 (mho/cm2) + el = -54.3 (mV) } - + STATE { - m h n + m h n } - + ASSIGNED { - ina (mA/cm2) - ik (mA/cm2) - il (mA/cm2) - minf hinf ninf mexp hexp nexp + ina (mA/cm2) + ik (mA/cm2) + il (mA/cm2) + minf hinf ninf mexp hexp nexp } - + BREAKPOINT { - SOLVE states - ina = gnabar*m*m*m*h*(v - ena) - ik = gkbar*n*n*n*n*(v - ek) - il = gl*(v - el) + SOLVE states + ina = gnabar*m*m*m*h*(v - ena) + ik = gkbar*n*n*n*n*(v - ek) + il = gl*(v - el) } - + UNITSOFF - + INITIAL { - rates(v) - m = minf - h = hinf - n = ninf + rates(v) + m = minf + h = hinf + n = ninf } -PROCEDURE states() { :Computes state variables m, h, and n - rates(v) : at the current v and dt. - m = m + mexp*(minf-m) - h = h + hexp*(hinf-h) - n = n + nexp*(ninf-n) +PROCEDURE states() { :Computes state variables m, h, and n + rates(v) : at the current v and dt. + m = m + mexp*(minf-m) + h = h + hexp*(hinf-h) + n = n + nexp*(ninf-n) } - + PROCEDURE rates(v) { :Computes rate and other constants at current v. :Call once from HOC to initialize inf at resting v. - LOCAL q10, tinc, alpha, beta, sum - : TABLE minf, mexp, hinf, hexp, ninf, nexp DEPEND dt, celsius FROM -100 TO 100 WITH 200 - q10 = 3^((celsius - 6.3)/10) - tinc = -dt * q10 - :"m" sodium activation system - alpha = .1 * vtrap(-(v+40),10) - beta = 4 * exp(-(v+65)/18) - sum = alpha + beta - minf = alpha/sum - mexp = 1 - exp(tinc*sum) - :"h" sodium inactivation system - alpha = .07 * exp(-(v+65)/20) - beta = 1 / (exp(-(v+35)/10) + 1) - sum = alpha + beta - hinf = alpha/sum - hexp = 1 - exp(tinc*sum) - :"n" potassium activation system - alpha = .01*vtrap(-(v+55),10) - beta = .125*exp(-(v+65)/80) - sum = alpha + beta - ninf = alpha/sum - nexp = 1 - exp(tinc*sum) + LOCAL q10, tinc, alpha, beta, sum + : TABLE minf, mexp, hinf, hexp, ninf, nexp DEPEND dt, celsius FROM -100 TO 100 WITH 200 + q10 = 3^((celsius - 6.3)/10) + tinc = -dt * q10 + + :"m" sodium activation system + alpha = .1 * vtrap(-(v+40),10) + beta = 4 * exp(-(v+65)/18) + sum = alpha + beta + minf = alpha/sum + mexp = 1 - exp(tinc*sum) + + :"h" sodium inactivation system + alpha = .07 * exp(-(v+65)/20) + beta = 1 / (exp(-(v+35)/10) + 1) + sum = alpha + beta + hinf = alpha/sum + hexp = 1 - exp(tinc*sum) + + :"n" potassium activation system + alpha = .01*vtrap(-(v+55),10) + beta = .125*exp(-(v+65)/80) + sum = alpha + beta + ninf = alpha/sum + nexp = 1 - exp(tinc*sum) } - + FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. - if (fabs(x/y) < 1e-6) { - vtrap = y*(1 - x/y/2) - }else{ - vtrap = x/(exp(x/y) - 1) - } + if(fabs(x/y) < 1e-6) { + vtrap = y*(1 - x/y/2) + } else { + vtrap = x/(exp(x/y) - 1) + } } - + UNITSON diff --git a/test/nmodl/transpiler/usecases/linear/lin.mod b/test/nmodl/transpiler/usecases/linear/lin.mod index 44930eecd1..e2f371aa1b 100644 --- a/test/nmodl/transpiler/usecases/linear/lin.mod +++ b/test/nmodl/transpiler/usecases/linear/lin.mod @@ -3,8 +3,8 @@ NEURON { } STATE { - xx - yy + xx + yy } PARAMETER { diff --git a/test/nmodl/transpiler/usecases/morphology/two_radii.mod b/test/nmodl/transpiler/usecases/morphology/two_radii.mod index d1ac66575a..d01d9b516c 100644 --- a/test/nmodl/transpiler/usecases/morphology/two_radii.mod +++ b/test/nmodl/transpiler/usecases/morphology/two_radii.mod @@ -1,28 +1,28 @@ NEURON { - SUFFIX two_radii - NONSPECIFIC_CURRENT il - RANGE inv + SUFFIX two_radii + NONSPECIFIC_CURRENT il + RANGE inv } ASSIGNED { - il - inv - diam - area + il + inv + diam + area } INITIAL { - inv = 1.0 / (square_diam() + area) + inv = 1.0 / (square_diam() + area) } BREAKPOINT { - il = (square_diam() + area) * 0.001 * (v - 20.0) + il = (square_diam() + area) * 0.001 * (v - 20.0) } FUNCTION square_diam() { - square_diam = diam * diam + square_diam = diam * diam } FUNCTION square_area() { - square_area = area * area + square_area = area * area } diff --git a/test/nmodl/transpiler/usecases/net_event/receiver.mod b/test/nmodl/transpiler/usecases/net_event/receiver.mod index c26b091c54..86b2fa8ec3 100644 --- a/test/nmodl/transpiler/usecases/net_event/receiver.mod +++ b/test/nmodl/transpiler/usecases/net_event/receiver.mod @@ -1,19 +1,16 @@ NEURON { - POINT_PROCESS receiver - RANGE y -} - -UNITS { + POINT_PROCESS receiver + RANGE y } ASSIGNED { - y + y } INITIAL { - y = 0.0 + y = 0.0 } NET_RECEIVE(w) { - y = y + 0.1 + y = y + 0.1 } diff --git a/test/nmodl/transpiler/usecases/net_event/spiker.mod b/test/nmodl/transpiler/usecases/net_event/spiker.mod index 0cb625149f..172fa33595 100644 --- a/test/nmodl/transpiler/usecases/net_event/spiker.mod +++ b/test/nmodl/transpiler/usecases/net_event/spiker.mod @@ -1,24 +1,21 @@ NEURON { - POINT_PROCESS spiker - RANGE tnext -} - -UNITS { + POINT_PROCESS spiker + RANGE tnext } ASSIGNED { - tnext + tnext } INITIAL { - tnext = 1.001 + tnext = 1.001 } NET_RECEIVE(w) { - LOCAL tt - tt = tnext - if(t >= tt) { - net_event(t) - tnext = tnext + 1.0 - } + LOCAL tt + tt = tnext + if(t >= tt) { + net_event(t) + tnext = tnext + 1.0 + } } diff --git a/test/nmodl/transpiler/usecases/net_move/art_spiker.mod b/test/nmodl/transpiler/usecases/net_move/art_spiker.mod index e5a9b4fc3f..d64c12f62c 100644 --- a/test/nmodl/transpiler/usecases/net_move/art_spiker.mod +++ b/test/nmodl/transpiler/usecases/net_move/art_spiker.mod @@ -1,25 +1,25 @@ NEURON { - ARTIFICIAL_CELL art_spiker - RANGE y, z + ARTIFICIAL_CELL art_spiker + RANGE y, z } ASSIGNED { - y - z + y + z } INITIAL { - y = 0.0 - z = 0.0 - net_send(1.8, 1) + y = 0.0 + z = 0.0 + net_send(1.8, 1) } NET_RECEIVE(w) { - if(flag == 0) { - y = y + 1 - net_move(t + 0.1) - } else { - z = z + 1 - net_send(2.0, 1) - } + if(flag == 0) { + y = y + 1 + net_move(t + 0.1) + } else { + z = z + 1 + net_send(2.0, 1) + } } diff --git a/test/nmodl/transpiler/usecases/net_move/spiker.mod b/test/nmodl/transpiler/usecases/net_move/spiker.mod index cfbadea769..27bd320964 100644 --- a/test/nmodl/transpiler/usecases/net_move/spiker.mod +++ b/test/nmodl/transpiler/usecases/net_move/spiker.mod @@ -1,25 +1,25 @@ NEURON { - POINT_PROCESS spiker - RANGE y, z + POINT_PROCESS spiker + RANGE y, z } ASSIGNED { - y - z + y + z } INITIAL { - y = 0.0 - z = 0.0 - net_send(1.8, 1) + y = 0.0 + z = 0.0 + net_send(1.8, 1) } NET_RECEIVE(w) { - if(flag == 0) { - y = y + 1 - net_move(t + 0.1) - } else { - z = z + 1 - net_send(2.0, 1) - } + if(flag == 0) { + y = y + 1 + net_move(t + 0.1) + } else { + z = z + 1 + net_send(2.0, 1) + } } diff --git a/test/nmodl/transpiler/usecases/net_send/art_toggle.mod b/test/nmodl/transpiler/usecases/net_send/art_toggle.mod index 4f490e67c5..313d10c940 100644 --- a/test/nmodl/transpiler/usecases/net_send/art_toggle.mod +++ b/test/nmodl/transpiler/usecases/net_send/art_toggle.mod @@ -1,21 +1,21 @@ NEURON { - ARTIFICIAL_CELL art_toggle - RANGE y + ARTIFICIAL_CELL art_toggle + RANGE y } ASSIGNED { - y + y } INITIAL { - y = 0 - net_send(2.501, 1) + y = 0 + net_send(2.501, 1) } NET_RECEIVE(w) { - y = y + 1.0 + y = y + 1.0 - if(t < 3.7) { - net_send(4.501 - t, 1) - } + if(t < 3.7) { + net_send(4.501 - t, 1) + } } diff --git a/test/nmodl/transpiler/usecases/net_send/toggle.mod b/test/nmodl/transpiler/usecases/net_send/toggle.mod index 21f8aedaae..105f9a8f14 100644 --- a/test/nmodl/transpiler/usecases/net_send/toggle.mod +++ b/test/nmodl/transpiler/usecases/net_send/toggle.mod @@ -1,21 +1,21 @@ NEURON { - POINT_PROCESS toggle - RANGE y + POINT_PROCESS toggle + RANGE y } ASSIGNED { - y + y } INITIAL { - y = 0 - net_send(2.001, 1) + y = 0 + net_send(2.001, 1) } NET_RECEIVE(w) { - y = y + 1.0 + y = y + 1.0 - if(t < 3.7) { - net_send(4.001 - t, 1) - } + if(t < 3.7) { + net_send(4.001 - t, 1) + } } diff --git a/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod b/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod index cd2af1ad31..3dea7fe735 100644 --- a/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod +++ b/test/nmodl/transpiler/usecases/nonspecific_current/leonhard.mod @@ -1,15 +1,15 @@ UNITS { - (mA) = (milliamp) + (mA) = (milliamp) } NEURON { - SUFFIX leonhard - NONSPECIFIC_CURRENT il - RANGE c + SUFFIX leonhard + NONSPECIFIC_CURRENT il + RANGE c } ASSIGNED { - il (mA/cm2) + il (mA/cm2) } PARAMETER { @@ -17,5 +17,5 @@ PARAMETER { } BREAKPOINT { - il = c * (v - 1.5) + il = c * (v - 1.5) } diff --git a/test/nmodl/transpiler/usecases/parameter/limits.mod b/test/nmodl/transpiler/usecases/parameter/limits.mod index 23bd6e2dd7..3e90de02e4 100644 --- a/test/nmodl/transpiler/usecases/parameter/limits.mod +++ b/test/nmodl/transpiler/usecases/parameter/limits.mod @@ -1,7 +1,7 @@ : Only test if all of these result in compilable code. NEURON { - SUFFIX limits_mod + SUFFIX limits_mod } PARAMETER { diff --git a/test/nmodl/transpiler/usecases/solve/cnexp_array.mod b/test/nmodl/transpiler/usecases/solve/cnexp_array.mod index f652ceac6c..08a86e54d6 100644 --- a/test/nmodl/transpiler/usecases/solve/cnexp_array.mod +++ b/test/nmodl/transpiler/usecases/solve/cnexp_array.mod @@ -4,12 +4,12 @@ NEURON { } ASSIGNED { - z[3] + z[3] } STATE { - x - s[2] + x + s[2] } INITIAL { diff --git a/test/nmodl/transpiler/usecases/solve/cnexp_scalar.mod b/test/nmodl/transpiler/usecases/solve/cnexp_scalar.mod index 4740338547..6779f332f4 100644 --- a/test/nmodl/transpiler/usecases/solve/cnexp_scalar.mod +++ b/test/nmodl/transpiler/usecases/solve/cnexp_scalar.mod @@ -2,15 +2,19 @@ NEURON { SUFFIX cnexp_scalar } -STATE { x } +STATE { + x +} INITIAL { - x = 42 + x = 42 } BREAKPOINT { - SOLVE dX METHOD cnexp + SOLVE dX METHOD cnexp } -DERIVATIVE dX { x' = -x } +DERIVATIVE dX { + x' = -x +} diff --git a/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod b/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod index e43e343135..321ab69b0d 100644 --- a/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod +++ b/test/nmodl/transpiler/usecases/solve/derivimplicit_array.mod @@ -4,12 +4,12 @@ NEURON { } ASSIGNED { - z[3] + z[3] } STATE { - x - s[2] + x + s[2] } INITIAL { diff --git a/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod b/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod index 05dfeab97b..5062ac01c2 100644 --- a/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod +++ b/test/nmodl/transpiler/usecases/solve/derivimplicit_scalar.mod @@ -2,15 +2,19 @@ NEURON { SUFFIX derivimplicit_scalar } -STATE { x } +STATE { + x +} INITIAL { - x = 42 + x = 42 } BREAKPOINT { - SOLVE dX METHOD derivimplicit + SOLVE dX METHOD derivimplicit } -DERIVATIVE dX { x' = -x } +DERIVATIVE dX { + x' = -x +} diff --git a/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod b/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod index ff761d36e6..fb428884ca 100644 --- a/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod +++ b/test/nmodl/transpiler/usecases/spike_travel/expsyn2.mod @@ -1,42 +1,42 @@ NEURON { - POINT_PROCESS ExpSyn2 - RANGE tau, e, i - NONSPECIFIC_CURRENT i + POINT_PROCESS ExpSyn2 + RANGE tau, e, i + NONSPECIFIC_CURRENT i } UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (uS) = (microsiemens) + (nA) = (nanoamp) + (mV) = (millivolt) + (uS) = (microsiemens) } PARAMETER { - tau = 0.1 (ms) <1e-9,1e9> - e = 0 (mV) + tau = 0.1 (ms) <1e-9,1e9> + e = 0 (mV) } ASSIGNED { - v (mV) - i (nA) + v (mV) + i (nA) } STATE { - g (uS) + g (uS) } INITIAL { - g=0 + g=0 } BREAKPOINT { - SOLVE state METHOD cnexp - i = g*(v - e) + SOLVE state METHOD cnexp + i = g*(v - e) } DERIVATIVE state { - g' = -g/tau + g' = -g/tau } NET_RECEIVE(weight (uS)) { - g = g + weight + g = g + weight } diff --git a/test/nmodl/transpiler/usecases/state/default_values.mod b/test/nmodl/transpiler/usecases/state/default_values.mod index d95af8485f..e9576081d3 100644 --- a/test/nmodl/transpiler/usecases/state/default_values.mod +++ b/test/nmodl/transpiler/usecases/state/default_values.mod @@ -1,7 +1,7 @@ NEURON { - SUFFIX default_values - RANGE Z, B - GLOBAL X0, Z0, A0, B0 + SUFFIX default_values + RANGE Z, B + GLOBAL X0, Z0, A0, B0 } STATE { @@ -13,13 +13,13 @@ STATE { } PARAMETER { - X0 = 2.0 - Z0 = 3.0 - A0 = 4.0 - B0 = 5.0 + X0 = 2.0 + Z0 = 3.0 + A0 = 4.0 + B0 = 5.0 } INITIAL { - Z = 7.0 - B[1] = 8.0 + Z = 7.0 + B[1] = 8.0 } diff --git a/test/nmodl/transpiler/usecases/steady_state/minipump.mod b/test/nmodl/transpiler/usecases/steady_state/minipump.mod index 2027aecc85..2912605707 100644 --- a/test/nmodl/transpiler/usecases/steady_state/minipump.mod +++ b/test/nmodl/transpiler/usecases/steady_state/minipump.mod @@ -1,43 +1,43 @@ NEURON { - SUFFIX minipump + SUFFIX minipump } PARAMETER { - volA = 1e9 - volB = 1e9 - volC = 13.0 - kf = 3.0 - kb = 4.0 + volA = 1e9 + volB = 1e9 + volC = 13.0 + kf = 3.0 + kb = 4.0 - run_steady_state = 0.0 + run_steady_state = 0.0 } STATE { - X - Y - Z + X + Y + Z } INITIAL { - X = 40.0 - Y = 8.0 - Z = 1.0 + X = 40.0 + Y = 8.0 + Z = 1.0 - if(run_steady_state > 0.0) { - SOLVE state STEADYSTATE sparse - } + if(run_steady_state > 0.0) { + SOLVE state STEADYSTATE sparse + } } BREAKPOINT { - SOLVE state METHOD sparse + SOLVE state METHOD sparse } KINETIC state { - COMPARTMENT volA {X} - COMPARTMENT volB {Y} - COMPARTMENT volC {Z} + COMPARTMENT volA {X} + COMPARTMENT volB {Y} + COMPARTMENT volC {Z} - ~ X + Y <-> Z (kf, kb) + ~ X + Y <-> Z (kf, kb) - CONSERVE Y + Z = 8.0*volB + 1.0*volC + CONSERVE Y + Z = 8.0*volB + 1.0*volC } diff --git a/test/nmodl/transpiler/usecases/useion/ionic.mod b/test/nmodl/transpiler/usecases/useion/ionic.mod index a0075b716d..1b7aec8352 100644 --- a/test/nmodl/transpiler/usecases/useion/ionic.mod +++ b/test/nmodl/transpiler/usecases/useion/ionic.mod @@ -1,13 +1,13 @@ NEURON { - SUFFIX ionic - USEION na READ ina WRITE ena + SUFFIX ionic + USEION na READ ina WRITE ena } ASSIGNED { - ina (mA/cm2) - ena (mV) + ina (mA/cm2) + ena (mV) } BREAKPOINT { - ena = 42.0 + ena = 42.0 } diff --git a/test/nmodl/transpiler/usecases/useion/read_cai.mod b/test/nmodl/transpiler/usecases/useion/read_cai.mod index 364e54571c..ac854d4c5c 100644 --- a/test/nmodl/transpiler/usecases/useion/read_cai.mod +++ b/test/nmodl/transpiler/usecases/useion/read_cai.mod @@ -5,8 +5,8 @@ NEURON { } ASSIGNED { - x - cai + x + cai } INITIAL { diff --git a/test/nmodl/transpiler/usecases/useion/read_cao.mod b/test/nmodl/transpiler/usecases/useion/read_cao.mod index 5b3db76800..ad01512aca 100644 --- a/test/nmodl/transpiler/usecases/useion/read_cao.mod +++ b/test/nmodl/transpiler/usecases/useion/read_cao.mod @@ -5,8 +5,8 @@ NEURON { } ASSIGNED { - x - cao + x + cao } INITIAL { diff --git a/test/nmodl/transpiler/usecases/useion/read_eca.mod b/test/nmodl/transpiler/usecases/useion/read_eca.mod index 6d45bb579f..336e59a0d0 100644 --- a/test/nmodl/transpiler/usecases/useion/read_eca.mod +++ b/test/nmodl/transpiler/usecases/useion/read_eca.mod @@ -5,8 +5,8 @@ NEURON { } ASSIGNED { - x - eca + x + eca } INITIAL { diff --git a/test/nmodl/transpiler/usecases/useion/style_ion.mod b/test/nmodl/transpiler/usecases/useion/style_ion.mod index 4e6ba180ff..b9f5fd1d13 100644 --- a/test/nmodl/transpiler/usecases/useion/style_ion.mod +++ b/test/nmodl/transpiler/usecases/useion/style_ion.mod @@ -8,9 +8,9 @@ NEURON { } ASSIGNED { - cai - eca - nai + cai + eca + nai } INITIAL { diff --git a/test/nmodl/transpiler/usecases/useion/valence.mod b/test/nmodl/transpiler/usecases/useion/valence.mod index 6ca8b857f0..4f335e46d1 100644 --- a/test/nmodl/transpiler/usecases/useion/valence.mod +++ b/test/nmodl/transpiler/usecases/useion/valence.mod @@ -5,8 +5,8 @@ NEURON { } ASSIGNED { - x - Ki + x + Ki } INITIAL { From 086b403222a2ce858af35e3592b00c3c2ecb5479 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 14:39:02 +0000 Subject: [PATCH 765/871] Minor cosmetic improvements. (BlueBrain/nmodl#1446) * Refactor print_function_tables. * Silence unused warning. * Prefer `nullptr` over `NULL`. * Appease linter. NMODL Repo SHA: BlueBrain/nmodl@37485544638c9cbc654d7ab0f1ed31b0682343ba --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 3 +-- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 1002b886fd..12e83c6e45 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -452,8 +452,7 @@ void CodegenCoreneuronCppVisitor::print_function_tables(const ast::FunctionTable for (const auto& i: p) { params.emplace_back("", "double", "", i->get_node_name()); } - printer->fmt_line("double {}({})", method_name(name), get_parameter_str(params)); - printer->push_block(); + printer->fmt_push_block("double {}({})", method_name(name), get_parameter_str(params)); printer->fmt_line("double _arg[{}];", p.size()); for (size_t i = 0; i < p.size(); ++i) { printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 9e7933c6ef..aa05ded1a9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -304,7 +304,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( auto* const _pnt = static_cast(_vptr); auto* const _p = _pnt->prop; if (!_p) { - hoc_execerror("POINT_PROCESS data instance not valid", NULL); + hoc_execerror("POINT_PROCESS data instance not valid", nullptr); } _nrn_mechanism_cache_instance _lmc{_p}; size_t const id{}; @@ -319,7 +319,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( printer->push_block("if (!_prop_id)"); printer->fmt_line( "hoc_execerror(\"No data for {}_{}. Requires prior call to setdata_{} and that the " - "specified mechanism instance still be in existence.\", NULL);", + "specified mechanism instance still be in existence.\", nullptr);", function_or_procedure_block->get_node_name(), info.mod_suffix, info.mod_suffix); @@ -362,7 +362,7 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( internal_method_arguments()); } const auto get_func_call_str = [&]() { - const auto params = function_or_procedure_block->get_parameters(); + const auto& params = function_or_procedure_block->get_parameters(); const auto func_proc_name = block_name + "_" + info.mod_suffix; auto func_call = fmt::format("{}({}", func_proc_name, internal_method_arguments()); for (int i = 0; i < params.size(); ++i) { @@ -2274,7 +2274,7 @@ void CodegenNeuronCppVisitor::print_net_move_call(const ast::FunctionCall& node) printer->add_text(')'); } -void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& node) { +void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& /* node */) { const auto& point_process = get_variable_name(naming::POINT_PROCESS_VARIABLE, /* use_instance */ false); printer->fmt_text("net_event({}, t)", point_process); From d442e742a57db1e7f92a1b47c71f83efe471bcf2 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 14:39:22 +0000 Subject: [PATCH 766/871] Refactor `print_setdata_functions`. (BlueBrain/nmodl#1447) NMODL Repo SHA: BlueBrain/nmodl@0d9cf9554eb81b9e79712d86beea55d18cbce407 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index aa05ded1a9..8f245e8d0b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -183,6 +183,7 @@ void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { printer->pop_block(); } + void CodegenNeuronCppVisitor::print_setdata_functions() { printer->add_line("/* Neuron setdata functions */"); printer->add_line("extern void _nrn_setdata_reg(int, void(*)(Prop*));"); @@ -211,6 +212,11 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { )CODE"); } printer->pop_block(); +} + + +void CodegenNeuronCppVisitor::print_function_prototypes() { + printer->add_newline(2); printer->add_line("/* Mechanism procedures and functions */"); for (const auto& node: info.functions) { @@ -223,11 +229,6 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { printer->add_text(';'); printer->add_newline(); } -} - - -void CodegenNeuronCppVisitor::print_function_prototypes() { - printer->add_newline(2); print_point_process_function_definitions(); print_setdata_functions(); From 5c606f55a8338444fa07658b618cdd577adce948 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 14:42:50 +0000 Subject: [PATCH 767/871] Rename `print_check_table_entrypoint`. (BlueBrain/nmodl#1448) NMODL Repo SHA: BlueBrain/nmodl@e062cac6cb59394bb81cbf1410126e2c48e45037 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 4 ++-- src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 8f245e8d0b..fd6d30b0d9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -137,7 +137,7 @@ void CodegenNeuronCppVisitor::print_point_process_function_definitions() { } -void CodegenNeuronCppVisitor::print_check_table_function_prototypes() { +void CodegenNeuronCppVisitor::print_check_table_entrypoint() { if (info.table_count == 0) { return; } @@ -232,7 +232,7 @@ void CodegenNeuronCppVisitor::print_function_prototypes() { print_point_process_function_definitions(); print_setdata_functions(); - print_check_table_function_prototypes(); + print_check_table_entrypoint(); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 07a0529141..41e3bf4a29 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -226,7 +226,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** * Print all `check_*` function declarations */ - void print_check_table_function_prototypes(); + void print_check_table_entrypoint(); void print_function_or_procedure(const ast::Block& node, From 7e8360f366e0ac5d6f856b2d9a5b7309db09f8fd Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 14:47:19 +0000 Subject: [PATCH 768/871] Refactor `print_wrapper`. (BlueBrain/nmodl#1449) NMODL Repo SHA: BlueBrain/nmodl@1e0db745c82f78ca06bfbb18716eccda8cd5353a --- .../codegen/codegen_neuron_cpp_visitor.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index fd6d30b0d9..2497780ccb 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -390,14 +390,15 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { - for (const auto& procedure: info.procedures) { - print_hoc_py_wrapper_function_body(procedure, InterpreterWrapper::HOC); - print_hoc_py_wrapper_function_body(procedure, InterpreterWrapper::Python); - } - for (const auto& function: info.functions) { - print_hoc_py_wrapper_function_body(function, InterpreterWrapper::HOC); - print_hoc_py_wrapper_function_body(function, InterpreterWrapper::Python); - } + auto print_wrappers = [this](const auto& callables) { + for (const auto& callable: callables) { + print_hoc_py_wrapper_function_body(callable, InterpreterWrapper::HOC); + print_hoc_py_wrapper_function_body(callable, InterpreterWrapper::Python); + } + }; + + print_wrappers(info.procedures); + print_wrappers(info.functions); } From 167a35dd3936c7d30b3716c420e1e6177b5db8f3 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 15:13:45 +0000 Subject: [PATCH 769/871] Refactor registering HOC/Python entrypoints. (BlueBrain/nmodl#1451) NMODL Repo SHA: BlueBrain/nmodl@507c616c404c3bd4ae18b546cd8fe6a6b4d1475b --- .../codegen/codegen_neuron_cpp_visitor.cpp | 30 ++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2497780ccb..b1ca63f081 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1044,24 +1044,20 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_newline(2); printer->add_line("/* declaration of user functions */"); - for (const auto& procedure: info.procedures) { - const auto proc_name = procedure->get_node_name(); - printer->fmt_line("{};", hoc_function_signature(proc_name)); - } - for (const auto& function: info.functions) { - const auto func_name = function->get_node_name(); - printer->fmt_line("{};", hoc_function_signature(func_name)); - } - if (!info.point_process) { - for (const auto& procedure: info.procedures) { - const auto proc_name = procedure->get_node_name(); - printer->fmt_line("{};", py_function_signature(proc_name)); - } - for (const auto& function: info.functions) { - const auto func_name = function->get_node_name(); - printer->fmt_line("{};", py_function_signature(func_name)); + + auto print_entrypoint_decl = [this](const auto& callables) { + for (const auto& node: callables) { + const auto name = node->get_node_name(); + printer->fmt_line("{};", hoc_function_signature(name)); + + if (!info.point_process) { + printer->fmt_line("{};", py_function_signature(name)); + } } - } + }; + + print_entrypoint_decl(info.functions); + print_entrypoint_decl(info.procedures); printer->add_newline(2); printer->add_line("/* connect user functions to hoc names */"); From ddfbd27016fd2f3e92a405b94f057965c28c9844 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 23 Sep 2024 15:46:03 +0000 Subject: [PATCH 770/871] Allow `derivimplicit` to use finite differences. (BlueBrain/nmodl#1444) * Allow `derivimplicit` to use finite differences. In NOCMODL `derivimplicit` uses finite differences to compute each element of the Jacobian. In stead we try to use SymPy, however, if it fails, e.g. because it encounters an opaque function, we allow it to use a finite difference instead. * Add test. NMODL Repo SHA: BlueBrain/nmodl@5798e948b5634118a980ba9af854950dd8d66a02 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 8 +++ src/nmodl/nmodl/python/nmodl/ode.py | 53 ++++++++++++++++--- src/nmodl/pybind/wrapper.cpp | 7 ++- src/nmodl/solver/newton/newton.hpp | 7 ++- src/nmodl/visitors/sympy_solver_visitor.cpp | 19 ++++--- test/nmodl/transpiler/unit/newton/newton.cpp | 30 ++++++++--- .../usecases/solve/finite_difference.mod | 30 +++++++++++ .../usecases/solve/test_finite_difference.py | 30 +++++++++++ 8 files changed, 154 insertions(+), 30 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/solve/finite_difference.mod create mode 100644 test/nmodl/transpiler/usecases/solve/test_finite_difference.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 09bfbea852..c07a2e49e5 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -708,6 +708,7 @@ void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlo printer->fmt_text( "void operator()(const Eigen::Matrix<{0}, {1}, 1>& nmodl_eigen_xm, Eigen::Matrix<{0}, {1}, " + "1>& nmodl_eigen_dxm, Eigen::Matrix<{0}, {1}, " "1>& nmodl_eigen_fm, " "Eigen::Matrix<{0}, {1}, {1}>& nmodl_eigen_jm) {2}", float_type, @@ -715,8 +716,15 @@ void CodegenCppVisitor::print_functor_definition(const ast::EigenNewtonSolverBlo is_functor_const(variable_block, functor_block) ? "const " : ""); printer->push_block(); printer->fmt_line("const {}* nmodl_eigen_x = nmodl_eigen_xm.data();", float_type); + printer->fmt_line("{}* nmodl_eigen_dx = nmodl_eigen_dxm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_j = nmodl_eigen_jm.data();", float_type); printer->fmt_line("{}* nmodl_eigen_f = nmodl_eigen_fm.data();", float_type); + + for (size_t i = 0; i < N; ++i) { + printer->fmt_line( + "nmodl_eigen_dx[{0}] = std::max(1e-6, 0.02*std::fabs(nmodl_eigen_x[{0}]));", i); + } + print_statement_block(functor_block, false, false); printer->pop_block(); printer->add_newline(); diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index e5cc926d1e..3fe769e596 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -344,6 +344,40 @@ def solve_lin_system( return code, new_local_vars +def finite_difference_step_variable(sym): + return f"{sym}_delta_" + + +def discretize_derivative(expr): + if isinstance(expr, sp.Derivative): + x = expr.args[1][0] + dx = sp.symbols(finite_difference_step_variable(x)) + return expr.as_finite_difference(dx) + else: + return expr + + +def transform_expression(expr, transform): + if not expr.args: + return expr + + args = (transform_expression(transform(arg), transform) for arg in expr.args) + return expr.func(*args) + + +def transform_matrix_elements(mat, transform): + return sp.Matrix( + [ + [transform_expression(mat[i, j], transform) for j in range(mat.rows)] + for i in range(mat.cols) + ] + ) + + +def needs_finite_differences(mat): + return any(isinstance(expr, sp.Derivative) for expr in sp.preorder_traversal(mat)) + + def solve_non_lin_system(eq_strings, vars, constants, function_calls): """Solve non-linear system of equations, return solution as C code. @@ -369,28 +403,31 @@ def solve_non_lin_system(eq_strings, vars, constants, function_calls): custom_fcts = _get_custom_functions(function_calls) jacobian = sp.Matrix(eqs).jacobian(state_vars) + if needs_finite_differences(jacobian): + jacobian = transform_matrix_elements(jacobian, discretize_derivative) X_vec_map = {x: sp.symbols(f"X[{i}]") for i, x in enumerate(state_vars)} + dX_vec_map = { + finite_difference_step_variable(x): sp.symbols(f"dX_[{i}]") + for i, x in enumerate(state_vars) + } vecFcode = [] for i, eq in enumerate(eqs): - vecFcode.append( - f"F[{i}] = {sp.ccode(eq.simplify().subs(X_vec_map).evalf(), user_functions=custom_fcts)}" - ) + expr = eq.simplify().subs(X_vec_map).evalf() + rhs = sp.ccode(expr, user_functions=custom_fcts) + vecFcode.append(f"F[{i}] = {rhs}") vecJcode = [] for i, j in itertools.product(range(jacobian.rows), range(jacobian.cols)): flat_index = i + jacobian.rows * j - rhs = sp.ccode( - jacobian[i, j].simplify().subs(X_vec_map).evalf(), - user_functions=custom_fcts, - ) + Jij = jacobian[i, j].simplify().subs({**X_vec_map, **dX_vec_map}).evalf() + rhs = sp.ccode(Jij, user_functions=custom_fcts) vecJcode.append(f"J[{flat_index}] = {rhs}") # interweave code = _interweave_eqs(vecFcode, vecJcode) - code = search_and_replace_protected_identifiers_from_sympy(code, function_calls) return code diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 92f679a6a1..32c390736c 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -82,14 +82,13 @@ std::tuple, std::string> call_solve_nonlinear_system( exception_message = "" try: solutions = solve_non_lin_system(equation_strings, - state_vars, - vars, - function_calls) + state_vars, + vars, + function_calls) except Exception as e: # if we fail, fail silently and return empty string import traceback solutions = [""] - new_local_vars = [""] exception_message = traceback.format_exc() )"; diff --git a/src/nmodl/solver/newton/newton.hpp b/src/nmodl/solver/newton/newton.hpp index bd627d0dbb..5d4e051826 100644 --- a/src/nmodl/solver/newton/newton.hpp +++ b/src/nmodl/solver/newton/newton.hpp @@ -81,6 +81,8 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, FUNC functor, double eps = EPS, int max_iter = MAX_ITER) { + // If finite differences are needed, this is stores the stepwidth. + Eigen::Matrix dX; // Vector to store result of function F(X): Eigen::Matrix F; // Matrix to store Jacobian of F(X): @@ -89,7 +91,7 @@ EIGEN_DEVICE_FUNC int newton_solver(Eigen::Matrix& X, int iter = -1; while (++iter < max_iter) { // calculate F, J from X using user-supplied functor - functor(X, F, J); + functor(X, dX, F, J); if (is_converged(X, J, F, eps)) { return iter; } @@ -127,10 +129,11 @@ EIGEN_DEVICE_FUNC int newton_solver_small_N(Eigen::Matrix& X, int max_iter) { bool invertible; Eigen::Matrix F; + Eigen::Matrix dX; Eigen::Matrix J, J_inv; int iter = -1; while (++iter < max_iter) { - functor(X, F, J); + functor(X, dX, F, J); if (is_converged(X, J, F, eps)) { return iter; } diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 8e3c8fdc11..f2d6260c21 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -179,6 +179,7 @@ void SympySolverVisitor::construct_eigen_solver_block( const std::vector& solutions, bool linear) { auto solutions_filtered = filter_string_vector(solutions, "X[", "nmodl_eigen_x["); + solutions_filtered = filter_string_vector(solutions_filtered, "dX_[", "nmodl_eigen_dx["); solutions_filtered = filter_string_vector(solutions_filtered, "J[", "nmodl_eigen_j["); solutions_filtered = filter_string_vector(solutions_filtered, "Jm[", "nmodl_eigen_jm["); solutions_filtered = filter_string_vector(solutions_filtered, "F[", "nmodl_eigen_f["); @@ -187,16 +188,19 @@ void SympySolverVisitor::construct_eigen_solver_block( logger->debug("SympySolverVisitor :: -> adding statement: {}", sol); } - std::vector pre_solve_statements_and_setup_x_eqs(pre_solve_statements); + std::vector pre_solve_statements_and_setup_x_eqs = pre_solve_statements; std::vector update_statements; + for (int i = 0; i < state_vars.size(); i++) { - auto update_state = state_vars[i] + " = nmodl_eigen_x[" + std::to_string(i) + "]"; - auto setup_x = "nmodl_eigen_x[" + std::to_string(i) + "] = " + state_vars[i]; + auto eigen_name = fmt::format("nmodl_eigen_x[{}]", i); - pre_solve_statements_and_setup_x_eqs.push_back(setup_x); + auto update_state = fmt::format("{} = {}", state_vars[i], eigen_name); update_statements.push_back(update_state); - logger->debug("SympySolverVisitor :: setup_x_eigen: {}", setup_x); logger->debug("SympySolverVisitor :: update_state: {}", update_state); + + auto setup_x = fmt::format("{} = {}", eigen_name, state_vars[i]); + pre_solve_statements_and_setup_x_eqs.push_back(setup_x); + logger->debug("SympySolverVisitor :: setup_x_eigen: {}", setup_x); } visitor::SympyReplaceSolutionsVisitor solution_replacer( @@ -304,9 +308,7 @@ void SympySolverVisitor::construct_eigen_solver_block( void SympySolverVisitor::solve_linear_system(const ast::Node& node, - const std::vector& pre_solve_statements - -) { + const std::vector& pre_solve_statements) { // construct ordered vector of state vars used in linear system init_state_vars_vector(&node); // call sympy linear solver @@ -373,6 +375,7 @@ void SympySolverVisitor::solve_non_linear_system( return; } logger->debug("SympySolverVisitor :: Constructing eigen newton solve block"); + construct_eigen_solver_block(pre_solve_statements, solutions, false); } diff --git a/test/nmodl/transpiler/unit/newton/newton.cpp b/test/nmodl/transpiler/unit/newton/newton.cpp index 7ed95df892..cd2de7d3fe 100644 --- a/test/nmodl/transpiler/unit/newton/newton.cpp +++ b/test/nmodl/transpiler/unit/newton/newton.cpp @@ -21,6 +21,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") GIVEN("1 linear eq") { struct functor { void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { // Function F(X) to find F(X)=0 solution @@ -30,15 +31,16 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") } }; Eigen::Matrix X{22.2536}; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find the solution") { CAPTURE(iter_newton); CAPTURE(X); - REQUIRE(iter_newton > 0); + REQUIRE(iter_newton == 1); REQUIRE_THAT(X[0], Catch::Matchers::WithinRel(1.0, 0.01)); REQUIRE(F.norm() < max_error_norm); } @@ -47,6 +49,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") GIVEN("1 non-linear eq") { struct functor { void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { F[0] = -3.0 * X[0] + std::sin(X[0]) + std::log(X[0] * X[0] + 11.435243) + 3.0; @@ -54,11 +57,12 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") } }; Eigen::Matrix X{-0.21421}; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find the solution") { CAPTURE(iter_newton); CAPTURE(X); @@ -71,6 +75,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") GIVEN("system of 2 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1] - 1.0; @@ -82,11 +87,12 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") } }; Eigen::Matrix X{0.2, 0.4}; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); @@ -107,6 +113,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") double e = 0.01; double z = 0.99; void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { F(0) = -(-_x_old - dt * (a * std::pow(X[0], 2) + X[1]) + X[0]); @@ -124,11 +131,12 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") } }; Eigen::Matrix X{0.21231, 0.4435, -0.11537}; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); @@ -145,6 +153,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") double X3_old = 1.2345; double dt = 0.2; void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { F[0] = -(-3.0 * X[0] * X[2] * dt + X[0] - X0_old + 2.0 * dt / X[1]); @@ -170,11 +179,12 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") } }; Eigen::Matrix X{0.21231, 0.4435, -0.11537, -0.8124312}; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); @@ -186,6 +196,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") GIVEN("system of 5 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { F[0] = -3.0 * X[0] * X[2] + X[0] + 2.0 / X[1]; @@ -224,11 +235,12 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") }; Eigen::Matrix X; X << 8.234, -245.46, 123.123, 0.8343, 5.555; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver<5, functor>(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); @@ -240,6 +252,7 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") GIVEN("system of 10 non-linear eqs") { struct functor { void operator()(const Eigen::Matrix& X, + Eigen::Matrix& /* dX */, Eigen::Matrix& F, Eigen::Matrix& J) const { F[0] = -3.0 * X[0] * X[1] + X[0] + 2.0 * X[1]; @@ -360,11 +373,12 @@ SCENARIO("Non-linear system to solve with Newton Solver", "[analytic][solver]") Eigen::Matrix X; X << 8.234, -5.46, 1.123, 0.8343, 5.555, 18.234, -2.46, 0.123, 10.8343, -4.685; + Eigen::Matrix dX; Eigen::Matrix F; Eigen::Matrix J; functor fn; int iter_newton = newton::newton_solver<10, functor>(X, fn); - fn(X, F, J); + fn(X, dX, F, J); THEN("find a solution") { CAPTURE(iter_newton); CAPTURE(X); diff --git a/test/nmodl/transpiler/usecases/solve/finite_difference.mod b/test/nmodl/transpiler/usecases/solve/finite_difference.mod new file mode 100644 index 0000000000..2c0f94e86d --- /dev/null +++ b/test/nmodl/transpiler/usecases/solve/finite_difference.mod @@ -0,0 +1,30 @@ +NEURON { + SUFFIX finite_difference + GLOBAL a + THREADSAFE +} + +ASSIGNED { + a +} + +STATE { + x +} + +INITIAL { + x = 42.0 + a = 0.1 +} + +BREAKPOINT { + SOLVE dX METHOD derivimplicit +} + +DERIVATIVE dX { + x' = -f(x) +} + +FUNCTION f(x) { + f = a*x +} diff --git a/test/nmodl/transpiler/usecases/solve/test_finite_difference.py b/test/nmodl/transpiler/usecases/solve/test_finite_difference.py new file mode 100644 index 0000000000..b0c22af6b7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/solve/test_finite_difference.py @@ -0,0 +1,30 @@ +import numpy as np +from neuron import h, gui +from neuron.units import ms + + +def test_finite_difference(): + nseg = 1 + + s = h.Section() + s.insert("finite_difference") + s.nseg = nseg + + x_hoc = h.Vector().record(s(0.5)._ref_x_finite_difference) + t_hoc = h.Vector().record(h._ref_t) + + h.stdinit() + h.dt = 0.001 + h.tstop = 5.0 * ms + h.run() + + x = np.array(x_hoc.as_numpy()) + t = np.array(t_hoc.as_numpy()) + + a = h.a_finite_difference + x_exact = 42.0 * np.exp(-a * t) + np.testing.assert_allclose(x, x_exact, rtol=1e-4) + + +if __name__ == "__main__": + test_finite_difference() From 6f6f5e4ff13d5812233127049a9d5415e86c8f21 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 06:45:06 +0000 Subject: [PATCH 771/871] Extract global struct printing for FUNCTION_TABLE. (BlueBrain/nmodl#1455) NMODL Repo SHA: BlueBrain/nmodl@38fa1e79a44a4271d4de267c2cbb58a23376e24e --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 5 +---- src/nmodl/codegen/codegen_cpp_visitor.cpp | 8 ++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 8 ++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 12e83c6e45..5ca8aa6c29 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -1180,10 +1180,7 @@ void CodegenCoreneuronCppVisitor::print_mechanism_global_var_structure(bool prin } } - for (const auto& f: info.function_tables) { - printer->fmt_line("void* _ptable_{}{{}};", f->get_node_name()); - codegen_global_variables.push_back(make_symbol("_ptable_" + f->get_node_name())); - } + print_global_struct_function_table_ptrs(); if (info.vectorize && info.thread_data_index) { printer->fmt_line("ThreadDatum ext_call_thread[{}]{};", diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index c07a2e49e5..fbc0f01f1e 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -433,6 +433,14 @@ void CodegenCppVisitor::print_backend_info() { } +void CodegenCppVisitor::print_global_struct_function_table_ptrs() { + for (const auto& f: info.function_tables) { + printer->fmt_line("void* _ptable_{}{{}};", f->get_node_name()); + codegen_global_variables.push_back(make_symbol("_ptable_" + f->get_node_name())); + } +} + + void CodegenCppVisitor::print_global_var_struct_assertions() const { // Assert some things that we assume when copying instances of this struct // to the GPU and so on. diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 64e5d587d1..a891989956 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1223,6 +1223,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void print_global_var_struct_assertions() const; + /** + * Print the entries of for FUNCTION_TABLEs in the global struct. + * + * And creates the required GLOBAL variables. + */ + virtual void print_global_struct_function_table_ptrs(); + + /** * Print declaration of macro NRN_PRCELLSTATE for debugging */ From 36c69ab227d2cf960ee3069255fe9de8cc1f44fc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 07:49:12 +0000 Subject: [PATCH 772/871] Lift FUNCTION_TABLE code. (BlueBrain/nmodl#1454) NMODL Repo SHA: BlueBrain/nmodl@4c94b958b34684e6d2476e4cfea97f62a572b87f --- .../codegen_coreneuron_cpp_visitor.cpp | 26 ------------------- .../codegen_coreneuron_cpp_visitor.hpp | 7 ----- src/nmodl/codegen/codegen_cpp_visitor.cpp | 26 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 8 ++++++ 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 5ca8aa6c29..8b282c3ecf 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -445,32 +445,6 @@ void CodegenCoreneuronCppVisitor::print_function_procedure_helper(const ast::Blo } -void CodegenCoreneuronCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { - auto name = node.get_node_name(); - const auto& p = node.get_parameters(); - auto params = internal_method_parameters(); - for (const auto& i: p) { - params.emplace_back("", "double", "", i->get_node_name()); - } - printer->fmt_push_block("double {}({})", method_name(name), get_parameter_str(params)); - printer->fmt_line("double _arg[{}];", p.size()); - for (size_t i = 0; i < p.size(); ++i) { - printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); - } - printer->fmt_line("return hoc_func_table({}, {}, _arg);", - get_variable_name(std::string("_ptable_" + name), true), - p.size()); - printer->pop_block(); - - printer->fmt_push_block("double table_{}()", method_name(name)); - printer->fmt_line("hoc_spec_table(&{}, {});", - get_variable_name(std::string("_ptable_" + name)), - p.size()); - printer->add_line("return 0.;"); - printer->pop_block(); -} - - /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index b23052d974..7afe71fa0d 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -355,13 +355,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void print_function_procedure_helper(const ast::Block& node) override; - /** - * Print NMODL function_table in target backend code - * \param node - */ - void print_function_tables(const ast::FunctionTableBlock& node); - - /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index fbc0f01f1e..c7fd79b159 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -538,6 +538,32 @@ void CodegenCppVisitor::print_function(const ast::FunctionBlock& node) { } +void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { + auto name = node.get_node_name(); + const auto& p = node.get_parameters(); + auto params = internal_method_parameters(); + for (const auto& i: p) { + params.emplace_back("", "double", "", i->get_node_name()); + } + printer->fmt_push_block("double {}({})", method_name(name), get_parameter_str(params)); + printer->fmt_line("double _arg[{}];", p.size()); + for (size_t i = 0; i < p.size(); ++i) { + printer->fmt_line("_arg[{}] = {};", i, p[i]->get_node_name()); + } + printer->fmt_line("return hoc_func_table({}, {}, _arg);", + get_variable_name(std::string("_ptable_" + name), true), + p.size()); + printer->pop_block(); + + printer->fmt_push_block("double table_{}()", method_name(name)); + printer->fmt_line("hoc_spec_table(&{}, {});", + get_variable_name(std::string("_ptable_" + name)), + p.size()); + printer->add_line("return 0.;"); + printer->pop_block(); +} + + void CodegenCppVisitor::print_prcellstate_macros() const { printer->add_line("#ifndef NRN_PRCELLSTATE"); printer->add_line("#define NRN_PRCELLSTATE 0"); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index a891989956..f1e730bbfd 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -890,6 +890,14 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ void print_function(const ast::FunctionBlock& node); + /** + * Print the internal function for FUNCTION_TABLES. + * + * Here internal refers to the function called from BREAKPOINT, INITIAL and + * other FUNCTIONs. Therefore this function doesn't print the functions required + * to call function tables from HOC/Python. + */ + void print_function_tables(const ast::FunctionTableBlock& node); /** * @brief Checks whether the functor_block generated by sympy solver modifies any variable From 55188bea0183883acc44dc72df54d459404b6c1d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 12:02:43 +0000 Subject: [PATCH 773/871] Use `print_function_table_call`. (BlueBrain/nmodl#1460) NMODL Repo SHA: BlueBrain/nmodl@84e618de5f6e984a1d9334fca8b4677ef7d483f0 --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 14 ++++++++++++++ .../codegen/codegen_coreneuron_cpp_visitor.hpp | 2 ++ src/nmodl/codegen/codegen_cpp_visitor.cpp | 12 ++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 7 +++++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 4 ++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 2 ++ 6 files changed, 41 insertions(+) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 8b282c3ecf..ed32c26967 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -2249,6 +2249,20 @@ void CodegenCoreneuronCppVisitor::print_net_event_call(const FunctionCall& node) printer->add_text(")"); } +void CodegenCoreneuronCppVisitor::print_function_table_call(const FunctionCall& node) { + auto name = node.get_node_name(); + const auto& arguments = node.get_arguments(); + printer->add_text(method_name(name), '('); + + printer->add_text(internal_method_arguments()); + if (!arguments.empty()) { + printer->add_text(", "); + } + + print_vector_elements(arguments, ", "); + printer->add_text(')'); +} + /** * Rename arguments to NET_RECEIVE block with corresponding pointer variable * diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 7afe71fa0d..9dce990773 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -736,6 +736,8 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { */ void print_net_event_call(const ast::FunctionCall& node) override; + void print_function_table_call(const ast::FunctionCall& node) override; + /** * Print initial block in the net receive block diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index c7fd79b159..b130e31bd8 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -166,6 +166,13 @@ bool CodegenCppVisitor::defined_method(const std::string& name) const { return function && function->has_any_property(properties); } +bool CodegenCppVisitor::is_function_table_call(const std::string& name) const { + auto it = std::find_if(info.function_tables.begin(), + info.function_tables.end(), + [name](const auto& node) { return node->get_node_name() == name; }); + return it != info.function_tables.end(); +} + int CodegenCppVisitor::float_variables_size() const { int n_floats = 0; for (const auto& var: codegen_float_variables) { @@ -492,6 +499,11 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { return; } + if (is_function_table_call(name)) { + print_function_table_call(node); + return; + } + const auto& arguments = node.get_arguments(); printer->add_text(function_name, '('); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index f1e730bbfd..1821b5b5fc 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -668,6 +668,9 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { } + bool is_function_table_call(const std::string& name) const; + + /** * Determine the position in the data array for a given float variable * \param name The name of a float variable @@ -852,6 +855,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ virtual void print_net_event_call(const ast::FunctionCall& node) = 0; + /** Print special code when calling FUNCTION_TABLEs. + */ + virtual void print_function_table_call(const ast::FunctionCall& node) = 0; + /** * Print function and procedures prototype declaration */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index b1ca63f081..47f2969dc9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2278,6 +2278,10 @@ void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& /* n printer->fmt_text("net_event({}, t)", point_process); } +void CodegenNeuronCppVisitor::print_function_table_call(const FunctionCall& node) { + throw std::runtime_error("Not implemented."); +} + /** * Rename arguments to NET_RECEIVE block with corresponding pointer variable * diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 41e3bf4a29..8d76503f4b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -191,6 +191,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_net_event_call(const ast::FunctionCall& node) override; + void print_function_table_call(const ast::FunctionCall& node) override; + /** * Print `net_receive` call-back. */ From d91922b7d49d3fb3c7e2622bd8ede88c35e4d028 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 12:03:11 +0000 Subject: [PATCH 774/871] Introduce `function_table_parameter`. (BlueBrain/nmodl#1459) This allows NMODL to generate different signatures when generating code for NEURON and CoreNEURON. NMODL Repo SHA: BlueBrain/nmodl@8fba98bbef00aaabe9970175c8baf82d37088dbe --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 9 +++++++++ src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp | 4 ++++ src/nmodl/codegen/codegen_cpp_visitor.cpp | 9 ++++----- src/nmodl/codegen/codegen_cpp_visitor.hpp | 6 ++++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 4 ++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 3 +++ 6 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index ed32c26967..8b1001fcf9 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -538,6 +538,15 @@ std::string CodegenCoreneuronCppVisitor::nrn_thread_internal_arguments() { return get_arg_str(internal_method_parameters()); } +std::pair +CodegenCoreneuronCppVisitor::function_table_parameters(const ast::FunctionTableBlock& node) { + auto params = internal_method_parameters(); + for (const auto& i: node.get_parameters()) { + params.emplace_back("", "double", "", i->get_node_name()); + } + return {params, internal_method_parameters()}; +} + /** * Replace commonly used variables in the verbatim blocks into their corresponding diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 9dce990773..6db14a8e15 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -407,6 +407,10 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { std::string nrn_thread_internal_arguments() override; + std::pair function_table_parameters( + const ast::FunctionTableBlock& node) override; + + /** * Replace commonly used verbatim variables * \param name A variable name to be checked and possibly updated diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index b130e31bd8..006f2703c9 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -553,10 +553,7 @@ void CodegenCppVisitor::print_function(const ast::FunctionBlock& node) { void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& node) { auto name = node.get_node_name(); const auto& p = node.get_parameters(); - auto params = internal_method_parameters(); - for (const auto& i: p) { - params.emplace_back("", "double", "", i->get_node_name()); - } + auto [params, table_params] = function_table_parameters(node); printer->fmt_push_block("double {}({})", method_name(name), get_parameter_str(params)); printer->fmt_line("double _arg[{}];", p.size()); for (size_t i = 0; i < p.size(); ++i) { @@ -567,7 +564,9 @@ void CodegenCppVisitor::print_function_tables(const ast::FunctionTableBlock& nod p.size()); printer->pop_block(); - printer->fmt_push_block("double table_{}()", method_name(name)); + printer->fmt_push_block("double table_{}({})", + method_name(name), + get_parameter_str(table_params)); printer->fmt_line("hoc_spec_table(&{}, {});", get_variable_name(std::string("_ptable_" + name)), p.size()); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 1821b5b5fc..5f72a2557f 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -906,6 +906,12 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ void print_function_tables(const ast::FunctionTableBlock& node); + /** + * Parameters of the function itself `"{}"` and `"table_{}"`. + */ + virtual std::pair function_table_parameters( + const ast::FunctionTableBlock& node) = 0; + /** * @brief Checks whether the functor_block generated by sympy solver modifies any variable * outside its scope. If it does then return false, so that the operator() of the struct functor diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 47f2969dc9..d8294d3a15 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -468,6 +468,10 @@ std::string CodegenNeuronCppVisitor::nrn_thread_internal_arguments() { return {}; } +std::pair +CodegenNeuronCppVisitor::function_table_parameters(const ast::FunctionTableBlock& node) { + throw std::runtime_error("Not implemented."); +} /// TODO: Write for NEURON std::string CodegenNeuronCppVisitor::process_verbatim_text(std::string const& text) { diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 8d76503f4b..b1a02fc7d3 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -302,6 +302,9 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ std::string nrn_thread_internal_arguments() override; + std::pair function_table_parameters( + const ast::FunctionTableBlock& /* node */) override; + /** * Process a verbatim block for possible variable renaming From cce4be59c83cadb6ac3a16158b8bc8b220185bd6 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 14:53:43 +0000 Subject: [PATCH 775/871] Fix misleading comments. (BlueBrain/nmodl#1458) NMODL Repo SHA: BlueBrain/nmodl@2e0c8c07c113a18ff7a6ff74b125c7b70f4fffb6 --- test/nmodl/transpiler/integration/mod/var_init.inc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/nmodl/transpiler/integration/mod/var_init.inc b/test/nmodl/transpiler/integration/mod/var_init.inc index a708ba9b24..e8a07fab87 100644 --- a/test/nmodl/transpiler/integration/mod/var_init.inc +++ b/test/nmodl/transpiler/integration/mod/var_init.inc @@ -1,10 +1,12 @@ COMMENT Example file to test INCLUDE keyword in NMODL. This file is included in - cabpump.mod and takes care of initializing - the dummy variable named "var" + cabpump.mod ENDCOMMENT FUNCTION var_init(var(mV)) (mV) { + : Sets the value of the local variable `var` to `1`. + : Because the arguments are passed by value, this + : function has no effect at all. var = 1 } From 7284d94be7a16042310ce062a33edc0a5de74fb4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 14:54:14 +0000 Subject: [PATCH 776/871] Refactor registering callables. (BlueBrain/nmodl#1453) NMODL Repo SHA: BlueBrain/nmodl@5ff1dea7c33ddd048193996bf6a088e5bec881e3 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 23 ++++++++----------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index d8294d3a15..35676e601a 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1081,20 +1081,15 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->fmt_line("{{\"setdata_{}\", _hoc_setdata}},", info.mod_suffix); } - for (const auto& procedure: info.procedures) { - const auto proc_name = procedure->get_node_name(); - printer->fmt_line("{{\"{}{}\", {}}},", - proc_name, - info.rsuffix, - hoc_function_name(proc_name)); - } - for (const auto& function: info.functions) { - const auto func_name = function->get_node_name(); - printer->fmt_line("{{\"{}{}\", {}}},", - func_name, - info.rsuffix, - hoc_function_name(func_name)); - } + auto print_callable_reg = [this](const auto& callables) { + for (const auto& node: callables) { + const auto name = node->get_node_name(); + printer->fmt_line("{{\"{}{}\", {}}},", name, info.rsuffix, hoc_function_name(name)); + } + }; + + print_callable_reg(info.procedures); + print_callable_reg(info.functions); printer->add_line("{nullptr, nullptr}"); printer->decrease_indent(); From 7f281b6215a9ac750be3dd198238524f08a0f5cc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 14:54:30 +0000 Subject: [PATCH 777/871] Refactor print_function_prototypes. (BlueBrain/nmodl#1452) NMODL Repo SHA: BlueBrain/nmodl@c9baaa6dc30182fed0c82250f0ea28c544f1ac77 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 35676e601a..4e01d92ad0 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -229,10 +229,6 @@ void CodegenNeuronCppVisitor::print_function_prototypes() { printer->add_text(';'); printer->add_newline(); } - - print_point_process_function_definitions(); - print_setdata_functions(); - print_check_table_entrypoint(); } @@ -2217,6 +2213,9 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_nrn_destructor_declaration(); print_nrn_alloc(); print_function_prototypes(); + print_point_process_function_definitions(); + print_setdata_functions(); + print_check_table_entrypoint(); print_functors_definitions(); print_global_variables_for_hoc(); print_thread_memory_callbacks(); From a4377c65de9516a8a61f659076d798688b3c29fa Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 24 Sep 2024 16:16:35 +0000 Subject: [PATCH 778/871] Refactor declaring FUNCTION/PROCEDURES. (BlueBrain/nmodl#1456) NMODL Repo SHA: BlueBrain/nmodl@f6059c7854dbf3d5c8a9de1635866fc18802f5fe --- .../codegen/codegen_neuron_cpp_visitor.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 4e01d92ad0..7c5d881214 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -218,17 +218,17 @@ void CodegenNeuronCppVisitor::print_setdata_functions() { void CodegenNeuronCppVisitor::print_function_prototypes() { printer->add_newline(2); + auto print_decl = [this](const auto& callables) { + for (const auto& node: callables) { + print_function_declaration(*node, node->get_node_name()); + printer->add_text(';'); + printer->add_newline(); + } + }; + printer->add_line("/* Mechanism procedures and functions */"); - for (const auto& node: info.functions) { - print_function_declaration(*node, node->get_node_name()); - printer->add_text(';'); - printer->add_newline(); - } - for (const auto& node: info.procedures) { - print_function_declaration(*node, node->get_node_name()); - printer->add_text(';'); - printer->add_newline(); - } + print_decl(info.functions); + print_decl(info.procedures); } From 3f6951ac88d159b299f4e34f8aa84ba58d4071cf Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 06:53:51 +0000 Subject: [PATCH 779/871] Remove CircleCI stub. (BlueBrain/nmodl#1465) NMODL Repo SHA: BlueBrain/nmodl@2f448528977bd8b60e552123fc7195bd35cecf2b --- .circleci/config.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 32f6af387a..0000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,19 +0,0 @@ -# NOTE this file serves one purpose: to silence Circle CI. -# Delete at will. -# -# Copied&modified from: https://circleci.com/docs/sample-config/ -version: 2.1 - -# Define the jobs we want to run for this project -jobs: - build: - docker: - - image: cimg/base:2023.03 - steps: - - run: echo "this is the build job" - -# Orchestrate our job run sequence -workflows: - build_and_test: - jobs: - - build From bc400a559cde3f2d374b5f42e575fc3bb990b39f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 07:54:19 +0000 Subject: [PATCH 780/871] Implement FUNCTION_TABLE. (BlueBrain/nmodl#1457) NMODL Repo SHA: BlueBrain/nmodl@2273cdd86a962ec39cb72f084616492dd4307ce5 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 92 ++++++++++++++++--- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../function_table/function_table.mod | 12 +++ .../function_table/test_function_table.py | 85 +++++++++++++++++ 4 files changed, 176 insertions(+), 14 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/function_table/function_table.mod create mode 100644 test/nmodl/transpiler/usecases/function_table/test_function_table.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 7c5d881214..eb1c8bf08a 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -229,6 +229,16 @@ void CodegenNeuronCppVisitor::print_function_prototypes() { printer->add_line("/* Mechanism procedures and functions */"); print_decl(info.functions); print_decl(info.procedures); + + for (const auto& node: info.function_tables) { + auto [params, table_params] = function_table_parameters(*node); + printer->fmt_line("double {}({});", + method_name(node->get_node_name()), + get_parameter_str(params)); + printer->fmt_line("double {}({});", + method_name("table_" + node->get_node_name()), + get_parameter_str(table_params)); + } } @@ -395,8 +405,36 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { print_wrappers(info.procedures); print_wrappers(info.functions); -} + for (const auto& node: info.function_tables) { + auto name = node->get_node_name(); + auto table_name = "table_" + node->get_node_name(); + + + auto args = std::vector{}; + for (size_t i = 0; i < node->get_parameters().size(); ++i) { + args.push_back(fmt::format("*getarg({})", i + 1)); + } + + // HOC + printer->fmt_push_block("static void {}()", hoc_function_name(name)); + printer->fmt_line("hoc_retpushx({}({}));", method_name(name), fmt::join(args, ", ")); + printer->pop_block(); + + printer->fmt_push_block("static void {}()", hoc_function_name(table_name)); + printer->fmt_line("hoc_retpushx({}());", method_name(table_name)); + printer->pop_block(); + + // Python + printer->fmt_push_block("static double {}(Prop* _prop)", py_function_name(name)); + printer->fmt_line("return {}({});", method_name(name), fmt::join(args, ", ")); + printer->pop_block(); + + printer->fmt_push_block("static double {}(Prop* _prop)", py_function_name(table_name)); + printer->fmt_line("return {}();", method_name(table_name)); + printer->pop_block(); + } +} /****************************************************************************************/ /* Code-specific helper routines */ @@ -466,7 +504,12 @@ std::string CodegenNeuronCppVisitor::nrn_thread_internal_arguments() { std::pair CodegenNeuronCppVisitor::function_table_parameters(const ast::FunctionTableBlock& node) { - throw std::runtime_error("Not implemented."); + auto params = ParamVector{}; + + for (const auto& i: node.get_parameters()) { + params.emplace_back("", "double", "", i->get_node_name()); + } + return {params, {}}; } /// TODO: Write for NEURON @@ -642,6 +685,10 @@ std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, // not printing a NET_RECEIVE block. } + if (stringutils::starts_with(name, "_ptable_")) { + return fmt::format("{}.{}", global_struct_instance(), name); + } + // float variable auto f = std::find_if(codegen_float_variables.begin(), codegen_float_variables.end(), @@ -938,9 +985,7 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in } } - if (!info.function_tables.empty()) { - throw std::runtime_error("Not implemented, global function tables."); - } + print_global_struct_function_table_ptrs(); if (info.vectorize && info.thread_data_index) { // TODO compare CoreNEURON something extcall stuff. @@ -1045,9 +1090,9 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_newline(2); printer->add_line("/* declaration of user functions */"); - auto print_entrypoint_decl = [this](const auto& callables) { + auto print_entrypoint_decl = [this](const auto& callables, auto get_name) { for (const auto& node: callables) { - const auto name = node->get_node_name(); + const auto name = get_name(node); printer->fmt_line("{};", hoc_function_signature(name)); if (!info.point_process) { @@ -1056,8 +1101,14 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { } }; - print_entrypoint_decl(info.functions); - print_entrypoint_decl(info.procedures); + auto get_name = [](const auto& node) { return node->get_node_name(); }; + print_entrypoint_decl(info.functions, get_name); + print_entrypoint_decl(info.procedures, get_name); + print_entrypoint_decl(info.function_tables, get_name); + print_entrypoint_decl(info.function_tables, [](const auto& node) { + auto node_name = node->get_node_name(); + return "table_" + node_name; + }); printer->add_newline(2); printer->add_line("/* connect user functions to hoc names */"); @@ -1077,15 +1128,20 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->fmt_line("{{\"setdata_{}\", _hoc_setdata}},", info.mod_suffix); } - auto print_callable_reg = [this](const auto& callables) { + auto print_callable_reg = [this](const auto& callables, auto get_name) { for (const auto& node: callables) { - const auto name = node->get_node_name(); + const auto name = get_name(node); printer->fmt_line("{{\"{}{}\", {}}},", name, info.rsuffix, hoc_function_name(name)); } }; - print_callable_reg(info.procedures); - print_callable_reg(info.functions); + print_callable_reg(info.procedures, get_name); + print_callable_reg(info.functions, get_name); + print_callable_reg(info.function_tables, get_name); + print_callable_reg(info.function_tables, [](const auto& node) { + auto node_name = node->get_node_name(); + return "table_" + node_name; + }); printer->add_line("{nullptr, nullptr}"); printer->decrease_indent(); @@ -2190,6 +2246,10 @@ void CodegenNeuronCppVisitor::print_compute_functions() { for (const auto& function: info.functions) { print_function(*function); } + for (const auto& function_table: info.function_tables) { + print_function_tables(*function_table); + } + print_nrn_init(); print_nrn_cur(); print_nrn_state(); @@ -2277,7 +2337,11 @@ void CodegenNeuronCppVisitor::print_net_event_call(const ast::FunctionCall& /* n } void CodegenNeuronCppVisitor::print_function_table_call(const FunctionCall& node) { - throw std::runtime_error("Not implemented."); + auto name = node.get_node_name(); + const auto& arguments = node.get_arguments(); + printer->add_text(method_name(name), "("); + print_vector_elements(arguments, ", "); + printer->add_text(')'); } /** diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 66cfc73d5f..6d487e9720 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -6,6 +6,7 @@ set(NMODL_USECASE_DIRS electrode_current external function + function_table global hodgkin_huxley kinetic diff --git a/test/nmodl/transpiler/usecases/function_table/function_table.mod b/test/nmodl/transpiler/usecases/function_table/function_table.mod new file mode 100644 index 0000000000..5000c147fb --- /dev/null +++ b/test/nmodl/transpiler/usecases/function_table/function_table.mod @@ -0,0 +1,12 @@ +NEURON { + SUFFIX function_table +} + +FUNCTION_TABLE cnst1(v) +FUNCTION_TABLE cnst2(v, x) +FUNCTION_TABLE tau1(v) +FUNCTION_TABLE tau2(v, x) + +FUNCTION use_tau2(v, x) { + use_tau2 = tau2(v, x) +} diff --git a/test/nmodl/transpiler/usecases/function_table/test_function_table.py b/test/nmodl/transpiler/usecases/function_table/test_function_table.py new file mode 100644 index 0000000000..cd8dfb7ea7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function_table/test_function_table.py @@ -0,0 +1,85 @@ +from neuron import h, gui + +import numpy as np +import scipy + + +def test_constant_1d(): + s = h.Section() + s.insert("function_table") + + c = 42.0 + h.table_cnst1_function_table(c) + + for vv in np.linspace(-10.0, 10.0, 14): + np.testing.assert_equal(h.cnst1_function_table(vv), c) + + +def test_constant_2d(): + s = h.Section() + s.insert("function_table") + + c = 42.0 + h.table_cnst2_function_table(c) + + for vv in np.linspace(-10.0, 10.0, 7): + for xx in np.linspace(-20.0, 10.0, 9): + np.testing.assert_equal(h.cnst2_function_table(vv, xx), c) + + +def test_1d(): + v = np.array([0.0, 1.0]) + tau1 = np.array([1.0, 2.0]) + + h.table_tau1_function_table(h.Vector(tau1), h.Vector(v)) + + for vv in np.linspace(v[0], v[-1], 20): + expected = np.interp(vv, v, tau1) + actual = h.tau1_function_table(vv) + + np.testing.assert_approx_equal(actual, expected, significant=11) + + +def test_2d(): + v = np.array([0.0, 1.0]) + x = np.array([1.0, 2.0, 3.0]) + + tau2 = np.array([[1.0, 2.0, 3.0], [20.0, 30.0, 40.0]]) + + # h.Matrix seems to be column major. + hoc_tau2 = h.Matrix(*tau2.shape) + hoc_tau2.from_vector(h.Vector(tau2.transpose().reshape(-1))) + + h.table_tau2_function_table( + hoc_tau2._ref_x[0][0], v.size, v[0], v[-1], x.size, x[0], x[-1] + ) + + for vv in np.linspace(v[0], v[-1], 20): + for xx in np.linspace(x[0], x[-1], 20): + expected = scipy.interpolate.interpn((v, x), tau2, (vv, xx)) + actual = h.tau2_function_table(vv, xx) + + np.testing.assert_approx_equal(actual, expected, significant=11) + + +def test_use_table(): + s = h.Section() + s.insert("function_table") + + h.setdata_function_table(s(0.5)) + + vv, xx = 0.33, 2.24 + + expected = h.tau2_function_table(vv, xx) + actual = h.use_tau2_function_table(vv, xx) + np.testing.assert_approx_equal(actual, expected, significant=11) + + +if __name__ == "__main__": + test_constant_1d() + test_constant_2d() + + test_1d() + test_2d() + + test_use_table() From 3b7ad7ba5ad17d413b6e2a011db3350470ba38da Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 07:54:42 +0000 Subject: [PATCH 781/871] Fix POINTER variables. (BlueBrain/nmodl#1462) NMODL Repo SHA: BlueBrain/nmodl@c0b10ee38ca3ecf8bd648007e2d339a063c8f18a --- .../codegen/codegen_neuron_cpp_visitor.cpp | 8 +++- .../usecases/pointer/basic_pointer.mod | 38 +++++++++++++++++ .../usecases/pointer/point_basic.mod | 38 +++++++++++++++++ .../usecases/pointer/test_basic_pointer.py | 42 +++++++++++++++++++ 4 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 test/nmodl/transpiler/usecases/pointer/basic_pointer.mod create mode 100644 test/nmodl/transpiler/usecases/pointer/point_basic.mod create mode 100644 test/nmodl/transpiler/usecases/pointer/test_basic_pointer.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index eb1c8bf08a..bc766d1652 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -615,6 +615,10 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& return fmt::format("_ppvar[{}].literal_value()", position); } + if (info.semantics[position].name == naming::POINTER_SEMANTIC) { + return fmt::format("(*_ppvar[{}].get())", position); + } + if (symbol.is_index) { if (use_instance) { throw std::runtime_error("Not implemented. [wiejo]"); @@ -1190,12 +1194,14 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { method_name(naming::NRN_JACOB_METHOD), nrn_state_required() ? method_name(naming::NRN_STATE_METHOD) : "nullptr") : "nullptr, nullptr, nullptr"; + + const auto register_mech_args = fmt::format("{}, {}, {}, {}, {}, {}", get_channel_info_var_name(), method_name(naming::NRN_ALLOC_METHOD), compute_functions_parameters, method_name(naming::NRN_INIT_METHOD), - naming::NRN_POINTERINDEX, + info.first_pointer_var_index, 1 + info.thread_data_index); if (info.point_process) { printer->fmt_line( diff --git a/test/nmodl/transpiler/usecases/pointer/basic_pointer.mod b/test/nmodl/transpiler/usecases/pointer/basic_pointer.mod new file mode 100644 index 0000000000..0c1e12c63b --- /dev/null +++ b/test/nmodl/transpiler/usecases/pointer/basic_pointer.mod @@ -0,0 +1,38 @@ +: POINTER variables are part of the `dparam` array. They're also "known" to +: NEURON and it recomputes their index into `dparam`. Naturally, the two don't +: have to agree, but if they don't it's a bug. +: +: These tests check that the index in MOD files agrees with the index used +: inside of NEURON. + +NEURON { + SUFFIX basic_pointer + RANGE x1, x2, ignore + : The ion pointers immediately preceed the POINTER variables + : in the dparam array. + USEION ca READ ica + POINTER p1, p2 +} + +ASSIGNED { + x1 + x2 + p1 + p2 + ica + ignore +} + +INITIAL { + ignore = ica + x1 = 0.0 + x2 = 0.0 +} + +FUNCTION read_p1() { + read_p1 = p1 +} + +FUNCTION read_p2() { + read_p2 = p2 +} diff --git a/test/nmodl/transpiler/usecases/pointer/point_basic.mod b/test/nmodl/transpiler/usecases/pointer/point_basic.mod new file mode 100644 index 0000000000..fa3ccb8a14 --- /dev/null +++ b/test/nmodl/transpiler/usecases/pointer/point_basic.mod @@ -0,0 +1,38 @@ +: POINTER variables are part of the `dparam` array. They're also "known" to +: NEURON and it recomputes their index into `dparam`. Naturally, the two don't +: have to agree, but if they don't it's a bug. +: +: These tests check that the index in MOD files agrees with the index used +: inside of NEURON. + +NEURON { + POINT_PROCESS point_basic + RANGE x1, x2, ignore + : The ion pointers immediately preceed the POINTER variables + : in the dparam array. + USEION ca READ ica + POINTER p1, p2 +} + +ASSIGNED { + x1 + x2 + p1 + p2 + ica + ignore +} + +INITIAL { + ignore = ica + x1 = 0.0 + x2 = 0.0 +} + +FUNCTION read_p1() { + read_p1 = p1 +} + +FUNCTION read_p2() { + read_p2 = p2 +} diff --git a/test/nmodl/transpiler/usecases/pointer/test_basic_pointer.py b/test/nmodl/transpiler/usecases/pointer/test_basic_pointer.py new file mode 100644 index 0000000000..89951e95dc --- /dev/null +++ b/test/nmodl/transpiler/usecases/pointer/test_basic_pointer.py @@ -0,0 +1,42 @@ +from neuron import h, gui + + +def check_basic_pointer(make_instance): + s = h.Section() + s.nseg = 2 + + s.insert("basic_pointer") + + s025 = make_instance(s, 0.25) + s075 = make_instance(s, 0.75) + + s025._ref_p1 = s025._ref_x1 + s025._ref_p2 = s025._ref_x2 + + s075._ref_p1 = s(0.25)._ref_v + s075._ref_p2 = s(0.75)._ref_v + + s025.x1 = 123.0 + s025.x2 = 0.123 + + assert s025.read_p1() == s025.x1 + assert s025.read_p2() == s025.x2 + + s(0.25).v = 83.9 + s(0.75).v = -83.9 + + assert s075.read_p1() == s(0.25).v + assert s075.read_p2() == s(0.75).v + + +def test_basic_pointer(): + check_basic_pointer(lambda s, x: s(x).basic_pointer) + + +def test_point_basic(): + check_basic_pointer(lambda s, x: h.point_basic(x)) + + +if __name__ == "__main__": + test_basic_pointer() + test_point_basic() From 623dad5356d545447f246f558f0b750673128cb0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 08:38:32 +0000 Subject: [PATCH 782/871] Remove `hoc_nrnpointerindex`. (BlueBrain/nmodl#1464) NMODL Repo SHA: BlueBrain/nmodl@0cbc0a623e8965fb7df86311ab9a64708a5b3e52 --- src/nmodl/codegen/codegen_naming.hpp | 4 ---- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 ------ 2 files changed, 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 89cfcc7da3..fa36c79a02 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -179,10 +179,6 @@ static constexpr char THREAD_ARGS_PROTO[] = "_threadargsproto_"; /// prefix for ion variable static constexpr char ION_VARNAME_PREFIX[] = "ion_"; -/// hoc_nrnpointerindex name -static constexpr char NRN_POINTERINDEX[] = "hoc_nrnpointerindex"; - - /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends // clang-format off diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index bc766d1652..7e012f6b08 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -867,12 +867,6 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in static _nrn_non_owning_id_without_container _prop_id{};)CODE"); } - printer->fmt_line("static int {} = {};", - naming::NRN_POINTERINDEX, - info.pointer_variables.size() > 0 - ? static_cast(info.pointer_variables.size()) - : -1); - printer->add_line("static _nrn_mechanism_std_vector _extcall_thread;"); // Start printing the CNRN-style global variables. From 431058d8a9418a769c1a9d7b2b5e9dc35d13d2a4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 09:08:37 +0000 Subject: [PATCH 783/871] Implement `nrn_pointing`. (BlueBrain/nmodl#1463) NMODL Repo SHA: BlueBrain/nmodl@2a0b26275a7d8a3a5ff051be244cb7adca043c3e --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 10 ++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 9 +++++++++ src/nmodl/codegen/codegen_naming.hpp | 3 +++ src/nmodl/visitors/semantic_analysis_visitor.cpp | 12 +++++++++++- src/nmodl/visitors/visitor_utils.cpp | 5 +++++ src/nmodl/visitors/visitor_utils.hpp | 3 +++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/pointer/pointing.mod | 16 ++++++++++++++++ .../transpiler/usecases/pointer/test_pointing.py | 16 ++++++++++++++++ 9 files changed, 74 insertions(+), 1 deletion(-) create mode 100755 test/nmodl/transpiler/usecases/pointer/pointing.mod create mode 100644 test/nmodl/transpiler/usecases/pointer/test_pointing.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 006f2703c9..37f0c4a609 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -484,6 +484,11 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { function_name = method_name(name); } + if (is_nrn_pointing(name)) { + print_nrn_pointing(node); + return; + } + if (is_net_send(name)) { print_net_send_call(node); return; @@ -524,6 +529,11 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { printer->add_text(')'); } +void CodegenCppVisitor::print_nrn_pointing(const ast::FunctionCall& node) { + printer->add_text("nrn_pointing(&"); + print_vector_elements(node.get_arguments(), ", "); + printer->add_text(")"); +} void CodegenCppVisitor::print_procedure(const ast::ProcedureBlock& node) { print_function_procedure_helper(node); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 5f72a2557f..bc959ba694 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -647,6 +647,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { return name == codegen::naming::NET_SEND_METHOD; } + bool is_nrn_pointing(const std::string& name) const noexcept { + return name == codegen::naming::NRN_POINTING_METHOD; + } + /** * Checks if given function name is \c net_move @@ -841,6 +845,11 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ virtual void print_net_send_call(const ast::FunctionCall& node) = 0; + /** + * Print \c nrn\_pointing. + */ + virtual void print_nrn_pointing(const ast::FunctionCall& node); + /** * Print call to net\_move diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index fa36c79a02..cd0403c223 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -50,6 +50,9 @@ static constexpr char NET_MOVE_METHOD[] = "net_move"; /// net_send function call in nmodl static constexpr char NET_SEND_METHOD[] = "net_send"; +/// nrn_pointing function in nmodl +static constexpr char NRN_POINTING_METHOD[] = "nrn_pointing"; + /// artificial cell keyword in nmodl static constexpr char ARTIFICIAL_CELL[] = "ARTIFICIAL_CELL"; diff --git a/src/nmodl/visitors/semantic_analysis_visitor.cpp b/src/nmodl/visitors/semantic_analysis_visitor.cpp index a2f8526646..540bca2008 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.cpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.cpp @@ -207,6 +207,8 @@ void SemanticAnalysisVisitor::visit_function_call(const ast::FunctionCall& node) // The first arg of a RANDOM function must be a random_var // Otherwise it's an error auto fname = node.get_node_name(); + auto position = node.get_name()->get_token()->position(); + if (is_random_construct_function(fname)) { const auto& arguments = node.get_arguments(); if (!arguments.empty()) { @@ -220,7 +222,6 @@ void SemanticAnalysisVisitor::visit_function_call(const ast::FunctionCall& node) } } } - auto position = node.get_name()->get_token()->position(); logger->critical( fmt::format("SemanticAnalysisVisitor :: random function {} at {} :: The first arg must " "be a random variable", @@ -228,6 +229,15 @@ void SemanticAnalysisVisitor::visit_function_call(const ast::FunctionCall& node) position)); check_fail = true; } + + if (is_nrn_pointing(fname)) { + if (size_t args_size = node.get_arguments().size(); args_size != 1) { + logger->critical( + fmt::format("nrn_pointing excepts exactly one argument, got: {}", args_size)); + check_fail = true; + } + } + node.visit_children(*this); /// --> } diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 1c2115c81c..4282aaf183 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -292,4 +292,9 @@ bool is_random_construct_function(const std::string& name) { return codegen::naming::RANDOM_FUNCTIONS_MAPPING.count(name) != 0; } +bool is_nrn_pointing(const std::string& name) { + return name == codegen::naming::NRN_POINTING_METHOD; +} + + } // namespace nmodl diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index ce8393b7a3..aa89d3b840 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -146,4 +146,7 @@ std::string get_full_var_name(const ast::VarName& node); /// Is given name a one of the function for RANDOM construct bool is_random_construct_function(const std::string& name); +/// Is given name `nrn_pointing`. +bool is_nrn_pointing(const std::string& name); + } // namespace nmodl diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 6d487e9720..feaa81ef7a 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -20,6 +20,7 @@ set(NMODL_USECASE_DIRS nonspecific_current parameter point_process + pointer procedure random solve diff --git a/test/nmodl/transpiler/usecases/pointer/pointing.mod b/test/nmodl/transpiler/usecases/pointer/pointing.mod new file mode 100755 index 0000000000..afb762c2de --- /dev/null +++ b/test/nmodl/transpiler/usecases/pointer/pointing.mod @@ -0,0 +1,16 @@ +NEURON { + SUFFIX pointing + POINTER ptr +} + +ASSIGNED { + ptr +} + +FUNCTION is_valid() { + if (nrn_pointing(ptr)) { + is_valid = 1.0 + } else { + is_valid = 0.0 + } +} diff --git a/test/nmodl/transpiler/usecases/pointer/test_pointing.py b/test/nmodl/transpiler/usecases/pointer/test_pointing.py new file mode 100644 index 0000000000..da0d05f3b7 --- /dev/null +++ b/test/nmodl/transpiler/usecases/pointer/test_pointing.py @@ -0,0 +1,16 @@ +from neuron import h, gui + + +def test_pointing(): + s = h.Section() + s.nseg = 5 + s.insert("pointing") + + s(0.1)._ref_ptr_pointing = s(0.1)._ref_v + + assert s(0.1).pointing.is_valid() + assert not s(0.9).pointing.is_valid() + + +if __name__ == "__main__": + test_pointing() From 5c66906378e881bba6aa730a9ae1c448b7be5caa Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 09:50:43 +0000 Subject: [PATCH 784/871] Remove comment. (BlueBrain/nmodl#1468) NMODL Repo SHA: BlueBrain/nmodl@1d2c5bb8f5621f6eaec76d4c68ebfa979ab12b32 --- src/nmodl/codegen/codegen_cpp_visitor.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index bc959ba694..d09e7f452a 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1553,7 +1553,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void print_rename_state_vars() const; }; -/* Templated functions need to be defined in header file */ template void CodegenCppVisitor::print_vector_elements(const std::vector& elements, const std::string& separator, From eafe8f12e4dc7bf50ca29ccb4862f215da09c9e1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 25 Sep 2024 09:50:57 +0000 Subject: [PATCH 785/871] Make a single argument ctor explicit. (BlueBrain/nmodl#1470) NMODL Repo SHA: BlueBrain/nmodl@2cc6cc150fc4b0796399a6d8d6d3b8ad89e96f7a --- src/nmodl/visitors/implicit_argument_visitor.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/visitors/implicit_argument_visitor.hpp b/src/nmodl/visitors/implicit_argument_visitor.hpp index 0f152d9415..0900d3206d 100644 --- a/src/nmodl/visitors/implicit_argument_visitor.hpp +++ b/src/nmodl/visitors/implicit_argument_visitor.hpp @@ -35,7 +35,7 @@ namespace visitor { * that they can be pure functions. */ struct ImplicitArgumentVisitor: public AstVisitor { - ImplicitArgumentVisitor(const std::string& simulator = "coreneuron") + explicit ImplicitArgumentVisitor(const std::string& simulator = "coreneuron") : simulator(simulator) {} void visit_function_call(ast::FunctionCall& node) override; From 74edb3560a8cf7d1f9b73067ec653341db680225 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 30 Sep 2024 08:38:07 +0000 Subject: [PATCH 786/871] Use ``. (BlueBrain/nmodl#1472) We identified recompiling JSON templates to contribute noticably to the compile time: **** Templates that took longest to instantiate: 16889 ms: nlohmann::basic_json<>::parse (42 times, avg 402 ms) 12363 ms: nlohmann::basic_json<>::basic_json (127 times, avg 97 ms) 10129 ms: nlohmann::detail::parser, nlohmann::detail::i... (42 times, avg 241 ms) 7934 ms: nlohmann::detail::parser, nlohmann::detail::i... (42 times, avg 188 ms) 6145 ms: nlohmann::basic_json<>::json_value::json_value (211 times, avg 29 ms) Fortunately, we can avoid the issue by using a forward declaration. NMODL Repo SHA: BlueBrain/nmodl@6df0d13b76b0a259833b7776eb81c510d34aecde --- src/nmodl/printer/json_printer.cpp | 1 + src/nmodl/printer/json_printer.hpp | 2 +- src/nmodl/visitors/perf_visitor.cpp | 1 + src/nmodl/visitors/visitor_utils.cpp | 1 + test/nmodl/transpiler/unit/visitor/json.cpp | 2 ++ 5 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/nmodl/printer/json_printer.cpp b/src/nmodl/printer/json_printer.cpp index 0a7477afc1..3bb7b71e37 100644 --- a/src/nmodl/printer/json_printer.cpp +++ b/src/nmodl/printer/json_printer.cpp @@ -8,6 +8,7 @@ #include "printer/json_printer.hpp" #include "utils/logger.hpp" +#include namespace nmodl { namespace printer { diff --git a/src/nmodl/printer/json_printer.hpp b/src/nmodl/printer/json_printer.hpp index 536c112352..0f83e9a2c2 100644 --- a/src/nmodl/printer/json_printer.hpp +++ b/src/nmodl/printer/json_printer.hpp @@ -12,7 +12,7 @@ * \brief \copybrief nmodl::printer::JSONPrinter */ -#include +#include #include #include diff --git a/src/nmodl/visitors/perf_visitor.cpp b/src/nmodl/visitors/perf_visitor.cpp index 4e83faa0fc..d185cea6a7 100644 --- a/src/nmodl/visitors/perf_visitor.cpp +++ b/src/nmodl/visitors/perf_visitor.cpp @@ -7,6 +7,7 @@ #include "visitors/perf_visitor.hpp" +#include #include #include "ast/all.hpp" diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index 4282aaf183..ca9e1a0b61 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "ast/all.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/json.cpp b/test/nmodl/transpiler/unit/visitor/json.cpp index 43b4bca448..16f199893b 100644 --- a/test/nmodl/transpiler/unit/visitor/json.cpp +++ b/test/nmodl/transpiler/unit/visitor/json.cpp @@ -12,6 +12,8 @@ #include "visitors/json_visitor.hpp" #include "visitors/visitor_utils.hpp" +#include + using json = nlohmann::json; using namespace nmodl; From a0a5dfc97e0beffb5cd086e63e7bc4eb3d448fe3 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 30 Sep 2024 08:38:35 +0000 Subject: [PATCH 787/871] Use forward declaration for nmodl lexer. (BlueBrain/nmodl#1473) * Use forward declaration for nmodl lexer. We identified `nmodl_lexer.hpp` and `nmodl_driver.hpp` to be expensive to parse. **** Expensive headers: 37155 ms: src/lexer/nmodl_lexer.hpp (included 47 times, avg 790 ms), included via: 41x: nmodl_driver.hpp 6x: 34970 ms: src/parser/nmodl_driver.hpp (included 47 times, avg 744 ms), included via: 47x: The problem is easily avoided by using a forward declaration. * More thorough variation. NMODL Repo SHA: BlueBrain/nmodl@c997e340cc6c5bdb21deda9660acab8123413462 --- src/nmodl/parser/nmodl_driver.hpp | 10 +++++++--- .../transpiler/unit/visitor/implicit_argument.cpp | 1 + .../unit/visitor/rename_function_arguments.cpp | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/nmodl/parser/nmodl_driver.hpp b/src/nmodl/parser/nmodl_driver.hpp index 1d0b4b059f..cc2a0ab3d7 100644 --- a/src/nmodl/parser/nmodl_driver.hpp +++ b/src/nmodl/parser/nmodl_driver.hpp @@ -16,9 +16,7 @@ #include #include -#include "ast/ast.hpp" -#include "lexer/nmodl_lexer.hpp" -#include "parser/nmodl_driver.hpp" +#include "ast/include.hpp" #include "utils/file_library.hpp" @@ -27,6 +25,12 @@ namespace nmodl { /// encapsulate lexer and parsers implementations namespace parser { +// Declared in: lexer/nmodl_lexer.hpp +class NmodlLexer; + +// Declared in generated file: ${BUILD_DIR}/src/parser/nmodl/location.hh +class location; + /** * \defgroup parser Parser Implementation * \brief All parser and driver classes implementation diff --git a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp index db794cf6d4..cf6e30a708 100644 --- a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp +++ b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp @@ -5,6 +5,7 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "test/unit/utils/test_utils.hpp" #include "visitors/implicit_argument_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp b/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp index c635c71b29..966efca064 100644 --- a/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename_function_arguments.cpp @@ -1,6 +1,7 @@ #include #include +#include "ast/program.hpp" #include "parser/nmodl_driver.hpp" #include "visitors/rename_function_arguments.hpp" #include "visitors/visitor_utils.hpp" From 07c90e23fa9d9adb6146d5b862f79c542b0a8be3 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 30 Sep 2024 08:55:05 +0000 Subject: [PATCH 788/871] Reduce usage of Pybind11 headers. (BlueBrain/nmodl#1474) NMODL Repo SHA: BlueBrain/nmodl@744392c5b094b1bbb397f414d69652506252ba71 --- src/nmodl/visitors/sympy_solver_visitor.hpp | 2 -- test/nmodl/transpiler/unit/visitor/sympy_solver.cpp | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index ecb326ab63..8675110041 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -12,8 +12,6 @@ * \brief \copybrief nmodl::visitor::SympySolverVisitor */ -#include -#include #include #include #include diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 486a243113..8ca1d226f1 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include + #include "ast/program.hpp" #include "codegen/codegen_coreneuron_cpp_visitor.hpp" #include "parser/nmodl_driver.hpp" From 45967e8c014892ab0aa8653b16d428c0cb0f8f15 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 30 Sep 2024 15:36:02 +0000 Subject: [PATCH 789/871] Avoid name collision `is_valid_construct`. (BlueBrain/nmodl#1475) NMODL Repo SHA: BlueBrain/nmodl@870c24c14f31256c510998c5a33ef6bb29c3f04c --- test/nmodl/transpiler/unit/units/parser.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index 3ffd3497e1..b121782440 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -27,6 +27,7 @@ using Catch::Matchers::ContainsSubstring; // able to define complex units based on base units nmodl::parser::UnitDriver driver; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +namespace { bool is_valid_construct(const std::string& construct) { return driver.parse_string(construct); } @@ -40,6 +41,7 @@ std::string parse_string(const std::string& unit_definition) { correctness_driver.table->print_base_units(ss); return ss.str(); } +} // namespace SCENARIO("Unit parser accepting valid units definition", "[unit][parser]") { GIVEN("A base unit") { From 8f7881eb7f948cbdde3d8d7a1ef3c2e8f552e51b Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 1 Oct 2024 09:32:12 +0200 Subject: [PATCH 790/871] Remove unused variable (BlueBrain/nmodl#1485) NMODL Repo SHA: BlueBrain/nmodl@3539c834f00cadc4cda3b9c9314fb51f4de04468 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 37f0c4a609..66a9d696ba 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1728,7 +1728,6 @@ void CodegenCppVisitor::print_rename_state_vars() const { auto rhs = get_variable_name(state_name + "0"); if (state->is_array()) { - auto size = state->get_length(); for (int i = 0; i < state->get_length(); ++i) { printer->fmt_line("{}[{}] = {};", lhs, i, rhs); } From ef7368a0630814f2d0986030fd2c602f5bc6d59b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 1 Oct 2024 08:36:07 +0000 Subject: [PATCH 791/871] Remove useless template. (BlueBrain/nmodl#1477) NMODL Repo SHA: BlueBrain/nmodl@0e1cf7cc03a15c6c0d7a0c7ece34e0c442f4821b --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 4 ++-- src/nmodl/codegen/codegen_utils.cpp | 6 ++---- src/nmodl/codegen/codegen_utils.hpp | 2 -- .../transpiler/unit/codegen/codegen_utils.cpp | 18 ++++++------------ 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 66a9d696ba..febc1ba157 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -198,12 +198,12 @@ int CodegenCppVisitor::int_variables_size() const { * representation (1e+20, 1E-15) then keep it as it is. */ std::string CodegenCppVisitor::format_double_string(const std::string& s_value) { - return utils::format_double_string(s_value); + return utils::format_double_string(s_value); } std::string CodegenCppVisitor::format_float_string(const std::string& s_value) { - return utils::format_float_string(s_value); + return utils::format_float_string(s_value); } diff --git a/src/nmodl/codegen/codegen_utils.cpp b/src/nmodl/codegen/codegen_utils.cpp index 5f7b623239..c068b87b03 100644 --- a/src/nmodl/codegen/codegen_utils.cpp +++ b/src/nmodl/codegen/codegen_utils.cpp @@ -20,8 +20,7 @@ namespace utils { * they are represented in the mod file by user. If the value is in scientific * representation (1e+20, 1E-15) then keep it as it is. */ -template <> -std::string format_double_string(const std::string& s_value) { +std::string format_double_string(const std::string& s_value) { double value = std::stod(s_value); if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { return fmt::format("{:.1f}", value); @@ -30,8 +29,7 @@ std::string format_double_string(const std::string& s_value) } -template <> -std::string format_float_string(const std::string& s_value) { +std::string format_float_string(const std::string& s_value) { float value = std::stof(s_value); if (std::ceil(value) == value && s_value.find_first_of("eE") == std::string::npos) { return fmt::format("{:.1f}", value); diff --git a/src/nmodl/codegen/codegen_utils.hpp b/src/nmodl/codegen/codegen_utils.hpp index 4c910b36b5..489ca326fb 100644 --- a/src/nmodl/codegen/codegen_utils.hpp +++ b/src/nmodl/codegen/codegen_utils.hpp @@ -29,7 +29,6 @@ namespace utils { * \param s_value The double constant as string * \return The proper string to be printed in the generated file. */ -template std::string format_double_string(const std::string& s_value); @@ -43,7 +42,6 @@ std::string format_double_string(const std::string& s_value); * \param s_value The double constant as string * \return The proper string to be printed in the generated file. */ -template std::string format_float_string(const std::string& s_value); } // namespace utils diff --git a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp index 0438cf38c3..a125f6185f 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_utils.cpp @@ -21,8 +21,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string double_constant = "0.012345678901234567"; THEN("Codegen C Visitor prints double with same precision") { - auto nmodl_constant_result = codegen::utils::format_double_string( - double_constant); + auto nmodl_constant_result = codegen::utils::format_double_string(double_constant); REQUIRE(nmodl_constant_result == double_constant); } } @@ -33,8 +32,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string codegen_output = "1.0"; THEN("Codegen C Visitor prints integer as double number") { - auto nmodl_constant_result = codegen::utils::format_double_string( - double_constant); + auto nmodl_constant_result = codegen::utils::format_double_string(double_constant); REQUIRE(nmodl_constant_result == codegen_output); } } @@ -44,8 +42,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { THEN("Codegen C Visitor prints doubles with scientific notation") { for (const auto& test: tests) { - REQUIRE(codegen::utils::format_double_string(test.first) == - test.second); + REQUIRE(codegen::utils::format_double_string(test.first) == test.second); } } } @@ -54,8 +51,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string float_constant = "0.01234567"; THEN("Codegen C Visitor prints float with same precision") { - auto nmodl_constant_result = codegen::utils::format_float_string( - float_constant); + auto nmodl_constant_result = codegen::utils::format_float_string(float_constant); REQUIRE(nmodl_constant_result == float_constant); } } @@ -66,8 +62,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { std::string codegen_output = "1.0"; THEN("Codegen C Visitor prints integer as double number") { - auto nmodl_constant_result = codegen::utils::format_float_string( - float_constant); + auto nmodl_constant_result = codegen::utils::format_float_string(float_constant); REQUIRE(nmodl_constant_result == codegen_output); } } @@ -77,8 +72,7 @@ SCENARIO("C codegen utility functions", "[codegen][util][c]") { THEN("Codegen C Visitor prints doubles with scientific notation") { for (const auto& test: tests) { - REQUIRE(codegen::utils::format_float_string(test.first) == - test.second); + REQUIRE(codegen::utils::format_float_string(test.first) == test.second); } } } From aeaebfe9d6b70a80791bc7de32b0702626323fff Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 1 Oct 2024 09:07:02 +0000 Subject: [PATCH 792/871] Use compiled version of spdlog. (BlueBrain/nmodl#1476) We see that in NMODL using the header-only version of SPDLOG contributes noticeably to the compile time: ``` **** Template sets that took longest to instantiate: 52766 ms: spdlog::pattern_formatter::handle_flag_<$> (92 times, avg 573 ms) ``` However, this can be easily avoided by using a non-headeronly version of SPDLOG. When using an external copy of spdlog it'll pick up the shared library. When built from the submodule it creates a static library. NMODL Repo SHA: BlueBrain/nmodl@1d9ea2ed8f51ebc96dcf07876100a545c4aacc94 --- cmake/nmodl/CMakeLists.txt | 4 ++-- src/nmodl/utils/CMakeLists.txt | 2 +- test/nmodl/transpiler/unit/CMakeLists.txt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 5850e6d1bb..fb3c334ac9 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -139,11 +139,11 @@ endif() # installation for consistency. This line should be harmless if we use an external spdlog. option(SPDLOG_FMT_EXTERNAL "Force to use an external {{fmt}}" ON) option(SPDLOG_SYSTEM_INCLUDE "Include spdlog as a system lib" ON) +cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) if(NMODL_3RDPARTY_USE_SPDLOG) # See above, same logic as fmt - option(SPDLOG_BUILD_PIC "Needed to be PIC to be put compiled as a lib" ON) + set_target_properties(spdlog PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() -cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) if(NMODL_ENABLE_BACKWARD) cpp_cc_git_submodule(backward BUILD EXCLUDE_FROM_ALL PACKAGE backward REQUIRED) diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index aff5d0d499..24cf674559 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -13,7 +13,7 @@ add_library( ${PROJECT_BINARY_DIR}/src/config/config.cpp) set_property(TARGET util PROPERTY POSITION_INDEPENDENT_CODE ON) -target_link_libraries(util PUBLIC fmt::fmt nlohmann_json::nlohmann_json spdlog::spdlog_header_only) +target_link_libraries(util PUBLIC fmt::fmt nlohmann_json::nlohmann_json spdlog::spdlog) if(NMODL_ENABLE_BACKWARD) target_link_libraries(util PRIVATE Backward::Interface) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 44d57fe91f..bdd7d51081 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -17,7 +17,7 @@ include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) # Common input data library # ============================================================================= add_library(test_util STATIC utils/nmodl_constructs.cpp utils/test_utils.cpp) -target_link_libraries(test_util PUBLIC spdlog::spdlog_header_only) +target_link_libraries(test_util PUBLIC spdlog::spdlog) # ============================================================================= # Common input data library From f864d34e18b121e3a78215a118eb4e336531af15 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 3 Oct 2024 11:46:55 +0200 Subject: [PATCH 793/871] Use finite differences in `differentiate2c` for implicit functions (BlueBrain/nmodl#1484) NMODL Repo SHA: BlueBrain/nmodl@8f4fc420702fffb33b09851f1b326fdc2b3e0993 --- src/nmodl/nmodl/python/nmodl/ode.py | 30 +++++++++++++++++--- test/nmodl/transpiler/unit/ode/test_ode.py | 33 ++++++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index 3fe769e596..e40cb47c62 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -568,7 +568,13 @@ def forwards_euler2c(diff_string, dt_var, vars, function_calls): return f"{sp.ccode(x)} = {sp.ccode(solution, user_functions=custom_fcts)}" -def differentiate2c(expression, dependent_var, vars, prev_expressions=None): +def differentiate2c( + expression, + dependent_var, + vars, + prev_expressions=None, + stepsize=1e-3, +): """Analytically differentiate supplied expression, return solution as C code. Expression should be of the form "f(x)", where "x" is @@ -595,11 +601,15 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): vars: set of all other variables used in expression, e.g. {"a", "b", "c"} prev_expressions: time-ordered list of preceeding expressions to evaluate & substitute, e.g. ["b = x + c", "a = 12*b"] + stepsize: in case an analytic expression is not possible, finite differences are used; + this argument sets the step size Returns: string containing analytic derivative of expression (including any substitutions of variables from supplied prev_expressions) w.r.t. dependent_var as C code. """ + if stepsize <= 0: + raise ValueError("arg `stepsize` must be > 0") prev_expressions = prev_expressions or [] # every symbol (a.k.a variable) that SymPy # is going to manipulate needs to be declared @@ -643,15 +653,27 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): # differentiate w.r.t. x diff = expr.diff(x).simplify() + # could be something generic like f'(x), in which case we use finite differences + if needs_finite_differences(diff): + diff = ( + transform_expression(diff, discretize_derivative) + .subs({finite_difference_step_variable(x): stepsize}) + .evalf() + ) + + # the codegen method does not like undefined function calls, so we extract + # them here + custom_fcts = {str(f.func): str(f.func) for f in diff.atoms(sp.Function)} + # try to simplify expression in terms of existing variables # ignore any exceptions here, since we already have a valid solution # so if this further simplification step fails the error is not fatal try: # if expression is equal to one of the supplied vars, replace with this var # can do a simple string comparison here since a var cannot be further simplified - diff_as_string = sp.ccode(diff) + diff_as_string = sp.ccode(diff, user_functions=custom_fcts) for v in sympy_vars: - if diff_as_string == sp.ccode(sympy_vars[v]): + if diff_as_string == sp.ccode(sympy_vars[v], user_functions=custom_fcts): diff = sympy_vars[v] # or if equal to rhs of one of the supplied equations, replace with lhs @@ -672,4 +694,4 @@ def differentiate2c(expression, dependent_var, vars, prev_expressions=None): pass # return result as C code in NEURON format - return sp.ccode(diff.evalf()) + return sp.ccode(diff.evalf(), user_functions=custom_fcts) diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index 387cfb801f..df5f6c4f0a 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 from nmodl.ode import differentiate2c, integrate2c +import pytest import sympy as sp @@ -100,6 +101,38 @@ def test_differentiate2c(): "g", ) + result = differentiate2c( + "-f(x)", + "x", + {}, + ) + # instead of comparing the expression as a string, we convert the string + # back to an expression and compare with an explicit function + size = 100 + for index in range(size): + a, b = -5, 5 + value = (b - a) * index / size + a + pytest.approx( + float( + sp.sympify(result) + .subs(sp.Function("f"), sp.sin) + .subs({"x": value}) + .evalf() + ) + ) == float( + -sp.Derivative(sp.sin("x")) + .as_finite_difference(1e-3) + .subs({"x": value}) + .evalf() + ) + with pytest.raises(ValueError): + differentiate2c( + "-f(x)", + "x", + {}, + stepsize=-1, + ) + def test_integrate2c(): From 09a2bac536fc53f7c97b88a959bb55a5a79c81ac Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 3 Oct 2024 11:47:41 +0200 Subject: [PATCH 794/871] Safely join paths in `main.cpp`. (BlueBrain/nmodl#1494) NMODL Repo SHA: BlueBrain/nmodl@43e730f6788c0fd7c9cef0d28643f3943109d066 --- src/nmodl/main.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index f12bfe35dd..c36f1d19b9 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -314,8 +314,10 @@ int run_nmodl(int argc, const char* argv[]) { /// create file path for nmodl file auto filepath = [scratch_dir, modfile](const std::string& suffix) { static int count = 0; - return fmt::format( - "{}/{}.{}.{}.mod", scratch_dir, modfile, std::to_string(count++), suffix); + + auto filename = fmt::format("{}.{}.{}.mod", modfile, std::to_string(count++), suffix); + + return (std::filesystem::path(scratch_dir) / filename).string(); }; /// driver object creates lexer and parser, just call parser method @@ -406,12 +408,10 @@ int run_nmodl(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("ast")); if (json_ast) { - std::string file{scratch_dir}; - file += "/"; - file += modfile; - file += ".ast.json"; - logger->info("Writing AST into {}", file); - JSONVisitor(file).write(*ast); + std::filesystem::path file{scratch_dir}; + file /= modfile + ".ast.json"; + logger->info("Writing AST into {}", file.string()); + JSONVisitor(file.string()).write(*ast); } if (verbatim_rename) { From a94dfdf16f3012af67fcd86e0d97461835f7a69e Mon Sep 17 00:00:00 2001 From: Nicolas Cornu Date: Thu, 3 Oct 2024 14:05:10 +0200 Subject: [PATCH 795/871] Use target fmt::fmt instead of fmt (BlueBrain/nmodl#1495) NMODL Repo SHA: BlueBrain/nmodl@c5b3031efd5f8240566a2dee8d0e010c20923c15 --- cmake/nmodl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index fb3c334ac9..47b0484443 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -112,7 +112,7 @@ if(NOT TARGET CLI11::CLI11) cpp_cc_git_submodule(cli11 BUILD PACKAGE CLI11 REQUIRED) endif() # We could have fmt incoming from NEURON -if(NOT TARGET fmt) +if(NOT TARGET fmt::fmt) cpp_cc_git_submodule(fmt BUILD EXCLUDE_FROM_ALL PACKAGE fmt REQUIRED) endif() # If we're building from the submodule, make sure we pass -fPIC so that we can link the code into a From 2f777291362bc2a9418755f31ede0a26cdc332c8 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 3 Oct 2024 14:47:45 +0200 Subject: [PATCH 796/871] Remove empty visitor from `main.cpp`. (BlueBrain/nmodl#1496) NMODL Repo SHA: BlueBrain/nmodl@65a70941f852d3b428b9ea396d363b7916cc51a7 --- src/nmodl/main.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index c36f1d19b9..61fbb3003d 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -330,9 +330,6 @@ int run_nmodl(int argc, const char* argv[]) { /// one whenever we run symtab visitor. bool update_symtab = false; - /// just visit the ast - AstVisitor().visit_program(*ast); - { logger->info("Running argument renaming visitor"); RenameFunctionArgumentsVisitor().visit_program(*ast); From cfd23ff4a07f38fa390988551c2eeb5cd4efacfc Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 3 Oct 2024 16:47:57 +0200 Subject: [PATCH 797/871] Improve COMPARTMENT names & documentation. (BlueBrain/nmodl#1498) NMODL Repo SHA: BlueBrain/nmodl@c5fb9fece26bb491cb633714203970f6f9557a53 --- src/nmodl/language/nmodl.yaml | 12 ++++++------ src/nmodl/visitors/kinetic_block_visitor.cpp | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 7895ba1c18..35f70602ae 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1527,18 +1527,18 @@ - Compartment: nmodl: COMPARTMENT members: - - name: - brief: "TODO" + - index_name: + brief: "Name of the index variable in volume expression" type: Name optional: true prefix: {value: " "} suffix: {value: ","} - - expression: - brief: "TODO" + - volume: + brief: "The volume of the compartment" type: Expression prefix: {value: " "} - - names: - brief: "TODO" + - species: + brief: "The names of the species that reside in this compartment" type: Name vector: true prefix: {value: " {"} diff --git a/src/nmodl/visitors/kinetic_block_visitor.cpp b/src/nmodl/visitors/kinetic_block_visitor.cpp index 0ec8b55f72..6b2154858b 100644 --- a/src/nmodl/visitors/kinetic_block_visitor.cpp +++ b/src/nmodl/visitors/kinetic_block_visitor.cpp @@ -164,7 +164,7 @@ void KineticBlockVisitor::compute_compartment_factor(ast::Compartment& node, const auto it = state_var_index.find(var_name); if (it != state_var_index.cend()) { int var_index = it->second; - auto expr = node.get_expression(); + auto expr = node.get_volume(); std::string expression = to_nmodl(expr); set_compartment_factor(var_index, expression); @@ -178,7 +178,7 @@ void KineticBlockVisitor::compute_compartment_factor(ast::Compartment& node, void KineticBlockVisitor::compute_indexed_compartment_factor(ast::Compartment& node, const ast::Name& name) { auto array_var_name = name.get_node_name(); - auto index_name = node.get_name()->get_node_name(); + auto index_name = node.get_index_name()->get_node_name(); auto pattern = fmt::format("^{}\\[([0-9]*)\\]$", array_var_name); std::regex re(pattern); @@ -189,8 +189,8 @@ void KineticBlockVisitor::compute_indexed_compartment_factor(ast::Compartment& n if (matches) { int index_value = std::stoi(m[1]); - auto volume_expr = node.get_expression(); - auto expr = std::shared_ptr(node.get_expression()->clone()); + auto volume_expr = node.get_volume(); + auto expr = std::shared_ptr(volume_expr->clone()); IndexRemover(index_name, index_value).visit_expression(*expr); std::string expression = to_nmodl(*expr); @@ -203,9 +203,9 @@ void KineticBlockVisitor::visit_compartment(ast::Compartment& node) { // COMPARTMENT block has an expression, and a list of state vars it applies to. // For each state var, the rhs of the differential eq should be divided by the expression. // Here we store the expressions in the compartment_factors vector - logger->debug("KineticBlockVisitor :: COMPARTMENT expr: {}", to_nmodl(node.get_expression())); - for (const auto& name_ptr: node.get_names()) { - if (node.get_name() == nullptr) { + logger->debug("KineticBlockVisitor :: COMPARTMENT expr: {}", to_nmodl(node.get_volume())); + for (const auto& name_ptr: node.get_species()) { + if (node.get_index_name() == nullptr) { compute_compartment_factor(node, *name_ptr); } else { compute_indexed_compartment_factor(node, *name_ptr); From 90181101c4d19ce7d9a55258782b85f15954a37b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 3 Oct 2024 17:22:08 +0200 Subject: [PATCH 798/871] Improve `LONGITUDINAL_DIFFUSION`. (BlueBrain/nmodl#1497) Clean up code comments, improve naming and fix a typo. NMODL Repo SHA: BlueBrain/nmodl@a731cc1eca08cc1354deb5d222a08cdd3d8027dd --- src/nmodl/language/code_generator.cmake | 2 +- src/nmodl/language/nmodl.yaml | 14 +++++++------- src/nmodl/parser/nmodl.yy | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index a3dea0767f..822030c1d5 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -114,7 +114,7 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/linear_block.hpp ${PROJECT_BINARY_DIR}/src/ast/local_list_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/local_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/lon_difuse.hpp + ${PROJECT_BINARY_DIR}/src/ast/lon_diffuse.hpp ${PROJECT_BINARY_DIR}/src/ast/model.hpp ${PROJECT_BINARY_DIR}/src/ast/mutex_lock.hpp ${PROJECT_BINARY_DIR}/src/ast/mutex_unlock.hpp diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 35f70602ae..dbfbaea6e2 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1546,21 +1546,21 @@ separator: " " brief: "Represent COMPARTMENT statement in NMODL" - - LonDifuse: + - LonDiffuse: nmodl: LONGITUDINAL_DIFFUSION members: - - name: - brief: "TODO" + - index_name: + brief: "Index variable name" type: Name optional: true prefix: {value: " "} suffix: {value: ","} - - expression: - brief: "TODO" + - rate: + brief: "Diffusion coefficient/rate" type: Expression prefix: {value: " "} - - names: - brief: "TODO" + - species: + brief: "Names of the diffusing species" type: Name vector: true prefix: {value: " {"} diff --git a/src/nmodl/parser/nmodl.yy b/src/nmodl/parser/nmodl.yy index c20aca8e91..3ce4704f8b 100644 --- a/src/nmodl/parser/nmodl.yy +++ b/src/nmodl/parser/nmodl.yy @@ -268,7 +268,7 @@ %type conserve %type react %type compartment -%type longitudinal_diffusion +%type longitudinal_diffusion %type name_list %type unit_block_body %type unit_definition @@ -1723,11 +1723,11 @@ compartment : COMPARTMENT NAME_PTR "," expression "{" name_list "}" longitudinal_diffusion : LONGDIFUS NAME_PTR "," expression "{" name_list "}" { - $$ = new ast::LonDifuse($2, $4, $6); + $$ = new ast::LonDiffuse($2, $4, $6); } | LONGDIFUS expression "{" name_list "}" { - $$ = new ast::LonDifuse(NULL, $2, $4); + $$ = new ast::LonDiffuse(NULL, $2, $4); } ; From d36953e6601d97894c0873a57bd7475cea586132 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 3 Oct 2024 17:28:11 +0200 Subject: [PATCH 799/871] Number files lexicographically. (BlueBrain/nmodl#1499) * Number through logically * Reduce formatting toggles NMODL Repo SHA: BlueBrain/nmodl@1a97ed159eba00aec765fcebcdac2e813f8cbe16 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 14 +++----------- src/nmodl/main.cpp | 3 +-- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index ad9dd393de..6f8f85d019 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -346,24 +346,17 @@ void CodegenHelperVisitor::find_non_range_variables() { } /// find pointer or bbcore pointer variables - // clang-format off - auto properties = NmodlType::pointer_var - | NmodlType::bbcore_pointer_var; - // clang-format on + auto properties = NmodlType::pointer_var | NmodlType::bbcore_pointer_var; info.pointer_variables = psymtab->get_variables_with_properties(properties); /// find RANDOM variables - // clang-format off properties = NmodlType::random_var; - // clang-format on info.random_variables = psymtab->get_variables_with_properties(properties); // find special variables like diam, area - // clang-format off - properties = NmodlType::assigned_definition - | NmodlType::param_assign; + properties = NmodlType::assigned_definition | NmodlType::param_assign; vars = psymtab->get_variables_with_properties(properties); - for (auto& var : vars) { + for (auto& var: vars) { if (var->get_name() == naming::AREA_VARIABLE) { info.area_used = true; } @@ -371,7 +364,6 @@ void CodegenHelperVisitor::find_non_range_variables() { info.diam_used = true; } } - // clang-format on } /** diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 61fbb3003d..bf6128bac2 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -315,8 +315,7 @@ int run_nmodl(int argc, const char* argv[]) { auto filepath = [scratch_dir, modfile](const std::string& suffix) { static int count = 0; - auto filename = fmt::format("{}.{}.{}.mod", modfile, std::to_string(count++), suffix); - + auto filename = fmt::format("{}.{:02d}.{}.mod", modfile, count++, suffix); return (std::filesystem::path(scratch_dir) / filename).string(); }; From 4b5f49ec0871a9a3bd1604cef1ab1d07687d80a3 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 7 Oct 2024 09:37:37 +0200 Subject: [PATCH 800/871] Add support for diffing expressions with indexed vars in `differentiate2c` (BlueBrain/nmodl#1483) NMODL Repo SHA: BlueBrain/nmodl@1909448515434bb01af7794614e45836e276f36d --- src/nmodl/nmodl/python/nmodl/ode.py | 7 ++++++- test/nmodl/transpiler/unit/ode/test_ode.py | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index e40cb47c62..cd6b2b27ae 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -247,6 +247,11 @@ def _interweave_eqs(F, J): return code +def make_symbol(var, /): + """Create SymPy symbol from a variable.""" + return sp.Symbol(var, real=True) if isinstance(var, str) else var + + def solve_lin_system( eq_strings, vars, @@ -618,7 +623,7 @@ def differentiate2c( vars = set(vars) vars.discard(dependent_var) # declare all other supplied variables - sympy_vars = {var: sp.symbols(var, real=True) for var in vars} + sympy_vars = {str(var): make_symbol(var) for var in vars} sympy_vars[dependent_var] = x # parse string into SymPy equation diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index df5f6c4f0a..6eae706998 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -3,7 +3,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from nmodl.ode import differentiate2c, integrate2c +from nmodl.ode import differentiate2c, integrate2c, make_symbol import pytest import sympy as sp @@ -29,7 +29,7 @@ def _equivalent( """ lhs = lhs.replace("pow(", "Pow(") rhs = rhs.replace("pow(", "Pow(") - sympy_vars = {var: sp.symbols(var, real=True) for var in vars} + sympy_vars = {str(var): make_symbol(var) for var in vars} for l, r in zip(lhs.split("=", 1), rhs.split("=", 1)): eq_l = sp.sympify(l, locals=sympy_vars) eq_r = sp.sympify(r, locals=sympy_vars) @@ -101,6 +101,16 @@ def test_differentiate2c(): "g", ) + assert _equivalent( + differentiate2c( + "(s[0] + s[1])*(z[0]*z[1]*z[2])*x", + "x", + {sp.IndexedBase("s", shape=[1]), sp.IndexedBase("z", shape=[1])}, + ), + "(s[0] + s[1])*(z[0]*z[1]*z[2])", + {sp.IndexedBase("s", shape=[1]), sp.IndexedBase("z", shape=[1])}, + ) + result = differentiate2c( "-f(x)", "x", From 026845058ef35fcd63039328533ad394d8a1a1bb Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 8 Oct 2024 14:32:38 +0200 Subject: [PATCH 801/871] Refactor printing setup code in entrypoints. (BlueBrain/nmodl#1502) The entrypoints into the MOD file from NEURON typically require setting up some variable, e.g. `_lmr`, `inst`, `node_data`, `_thread_vars`, etc. The code has roughly two flavours: - From fully initialized NEURON, i.e. a Memb_list exists and the model is sorted. All the compute function have this type of entrypoint. - From a `Prop`. Usually this happens when we don't know that the model is sorted, or that initialization has happened. The HOC/Python bindings of FUNCTION/PROCEDUREs use this type of entrypoint. NMODL Repo SHA: BlueBrain/nmodl@0d428cf7921deff7653c9d7be90c9a0c7e3c965c --- .../codegen/codegen_neuron_cpp_visitor.cpp | 76 ++++++++++--------- .../codegen/codegen_neuron_cpp_visitor.hpp | 25 ++++-- 2 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 7e012f6b08..2b629f0b48 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1589,6 +1589,41 @@ void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { } } +void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_memb_list() { + printer->add_line( + "_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _ml_arg->type()};"); + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!info.artificial_cell) { + printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml_arg);", info.mod_suffix); + } + printer->add_line("auto* _thread = _ml_arg->_thread;"); + if (!codegen_thread_variables.empty()) { + printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", + thread_variables_struct(), + info.thread_var_thread_id); + } +} + + +void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_prop() { + printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(prop);"); + printer->add_line("_nrn_mechanism_cache_instance _lmc{prop};"); + printer->add_line("const size_t id = 0;"); + + printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + if (!info.artificial_cell) { + printer->fmt_line("auto node_data = make_node_data_{}(prop);", info.mod_suffix); + } + + if (!codegen_thread_variables.empty()) { + printer->fmt_line("auto _thread_vars = {}({}_global.thread_data);", + thread_variables_struct(), + info.mod_suffix); + } + + printer->add_newline(); +} + void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, const std::string& function_name) { @@ -1598,18 +1633,8 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, {"", "Memb_list*", "", "_ml_arg"}, {"", "int", "", "_type"}}; printer->fmt_push_block("void {}({})", method, get_parameter_str(args)); - - printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _type};"); - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); - printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml_arg);", info.mod_suffix); - + print_entrypoint_setup_code_from_memb_list(); printer->add_line("auto nodecount = _ml_arg->nodecount;"); - printer->add_line("auto* _thread = _ml_arg->_thread;"); - if (!codegen_thread_variables.empty()) { - printer->fmt_line("auto _thread_vars = {}(_thread[{}].get());", - thread_variables_struct(), - info.thread_var_thread_id); - } } @@ -1659,11 +1684,7 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { get_parameter_str(args)); // begin function - printer->add_multi_line( - "_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _type};"); - - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); - printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml_arg);", info.mod_suffix); + print_entrypoint_setup_code_from_memb_list(); printer->fmt_line("auto nodecount = _ml_arg->nodecount;"); printer->push_block("for (int id = 0; id < nodecount; id++)"); // begin for @@ -1680,25 +1701,6 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { } -void CodegenNeuronCppVisitor::print_callable_preamble_from_prop() { - printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(prop);"); - printer->add_line("_nrn_mechanism_cache_instance _lmc{prop};"); - printer->add_line("const size_t id = 0;"); - - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); - if (!info.artificial_cell) { - printer->fmt_line("auto node_data = make_node_data_{}(prop);", info.mod_suffix); - } - - if (!codegen_thread_variables.empty()) { - printer->fmt_line("auto _thread_vars = {}({}_global.thread_data);", - thread_variables_struct(), - info.mod_suffix); - } - - printer->add_newline(); -} - void CodegenNeuronCppVisitor::print_nrn_constructor_declaration() { if (info.constructor_node) { printer->fmt_line("void {}(Prop* prop);", method_name(naming::NRN_CONSTRUCTOR_METHOD)); @@ -1709,7 +1711,7 @@ void CodegenNeuronCppVisitor::print_nrn_constructor() { if (info.constructor_node) { printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_CONSTRUCTOR_METHOD)); - print_callable_preamble_from_prop(); + print_entrypoint_setup_code_from_prop(); auto block = info.constructor_node->get_statement_block(); print_statement_block(*block, false, false); @@ -1725,7 +1727,7 @@ void CodegenNeuronCppVisitor::print_nrn_destructor_declaration() { void CodegenNeuronCppVisitor::print_nrn_destructor() { printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_DESTRUCTOR_METHOD)); - print_callable_preamble_from_prop(); + print_entrypoint_setup_code_from_prop(); for (const auto& rv: info.random_variables) { printer->fmt_line("nrnran123_deletestream((nrnran123_State*) {});", diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index b1a02fc7d3..4be6fc09b6 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -492,6 +492,26 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_global_function_common_code(BlockType type, const std::string& function_name = "") override; + /** + * Prints setup code for entrypoints from NEURON. + * + * The entrypoints typically receive a `sorted_token` and a bunch of other things, which then + * need to be converted into the default arguments for functions called (recursively) from the + * entrypoint. + * + * This variation prints the fast entrypoint, where NEURON is fully initialized and setup. + */ + void print_entrypoint_setup_code_from_memb_list(); + + + /** + * Prints setup code for entrypoints NEURON. + * + * See `print_entrypoint_setup_code_from_memb_list`. This variation should be used when one only + * has access to a `Prop`, but not the full `Memb_list`. + */ + void print_entrypoint_setup_code_from_prop(); + /** * Print the \c nrn\_init function definition @@ -509,11 +529,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_nrn_constructor() override; void print_nrn_constructor_declaration(); - /** - * Print the set of common variables from a `Prop` only. - */ - void print_callable_preamble_from_prop(); - /** * Print nrn_destructor function definition * From 10a9721bfac8b52827d69f4bd99ad1be27300d25 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 10 Oct 2024 08:51:49 +0200 Subject: [PATCH 802/871] Improve comments. (BlueBrain/nmodl#1505) NMODL Repo SHA: BlueBrain/nmodl@22ea0108d7ba316d6f6598043de79265fed4ed18 --- src/nmodl/symtab/symbol_table.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/nmodl/symtab/symbol_table.hpp b/src/nmodl/symtab/symbol_table.hpp index 9b64157f8a..e4c100ccf6 100644 --- a/src/nmodl/symtab/symbol_table.hpp +++ b/src/nmodl/symtab/symbol_table.hpp @@ -134,10 +134,10 @@ class SymbolTable { /** * get variables * - * \param with variables with properties. 0 matches everything - * \param without variables without properties. 0 matches nothing + * \param with variables with properties. `syminfo::NmodlType::empty` matches everything + * \param without variables without properties. `syminfo::NmodlType::empty` matches nothing * - * The two different behaviors for 0 depend on the fact that we get + * The two different behaviors for `syminfo::NmodlType::empty` depend on the fact that we get * get variables with ALL the with properties and without ANY of the * without properties */ From 3e84a8c0af4bcb8df202258947a326ec1817a266 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 10 Oct 2024 09:22:36 +0200 Subject: [PATCH 803/871] Remove declaration of `remove_statements_from_block` method (BlueBrain/nmodl#1508) The definition was removed in 7e0b1672bac284f9908e4dfc90da4295990c4cdd NMODL Repo SHA: BlueBrain/nmodl@c4910641308e06396f831ff3dad5a6c99622fdc2 --- src/nmodl/visitors/visitor_utils.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index aa89d3b840..97d8fa0d33 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -82,11 +82,6 @@ std::shared_ptr create_statement_block( const std::vector& code_statements); -/// Remove statements from given statement block if they exist -void remove_statements_from_block(ast::StatementBlock& block, - const std::set& statements); - - /// Return set of strings with the names of all global variables std::set get_global_vars(const ast::Program& node); From b2536e9e4bd76310300c6915cf9416e8aedabe2c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 10 Oct 2024 13:41:49 +0200 Subject: [PATCH 804/871] Improve naming of `LinEquation`. (BlueBrain/nmodl#1509) NMODL Repo SHA: BlueBrain/nmodl@b0d96ba23e65dbe4dc2be2d368cb6d4a08571345 --- src/nmodl/language/nmodl.yaml | 10 +++++----- src/nmodl/visitors/sympy_replace_solutions_visitor.cpp | 4 ++-- src/nmodl/visitors/sympy_solver_visitor.cpp | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index dbfbaea6e2..6c9124ac83 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1152,15 +1152,15 @@ type: Expression - LinEquation: - brief: "TODO" + brief: "One equation in a system of equations tha collectively form a LINEAR block." nmodl: "~ " members: - - left_linxpression: - brief: "TODO" + - lhs: + brief: "Left-hand-side of the equation." type: Expression suffix: {value: " = "} - - linxpression: - brief: "TODO" + - rhs: + brief: "Right-hand-side of the equation." type: Expression - FunctionCall: diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index 9906c5577e..fb0838e8a7 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -226,11 +226,11 @@ void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpressio void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { logger->debug("SympyReplaceSolutionsVisitor :: visit {}", to_nmodl(node)); auto get_lhs = [](const ast::Node& node) -> std::shared_ptr { - return dynamic_cast(node).get_left_linxpression(); + return dynamic_cast(node).get_lhs(); }; auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { - return dynamic_cast(node).get_left_linxpression(); + return dynamic_cast(node).get_rhs(); }; try_replace_tagged_statement(node, get_lhs, get_rhs); diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index f2d6260c21..0de8d98578 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -577,9 +577,9 @@ void SympySolverVisitor::visit_derivative_block(ast::DerivativeBlock& node) { void SympySolverVisitor::visit_lin_equation(ast::LinEquation& node) { check_expr_statements_in_same_block(); - std::string lin_eq = to_nmodl_for_sympy(*node.get_left_linxpression()); + std::string lin_eq = to_nmodl_for_sympy(*node.get_lhs()); lin_eq += " = "; - lin_eq += to_nmodl_for_sympy(*node.get_linxpression()); + lin_eq += to_nmodl_for_sympy(*node.get_rhs()); eq_system.push_back(lin_eq); expression_statements.insert(current_expression_statement); last_expression_statement = current_expression_statement; From 50159f69af5f09b347379d7a7e969eb2d1e5ca1a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 10 Oct 2024 14:07:03 +0200 Subject: [PATCH 805/871] Simplify `try_replace_tagged_statement`. (BlueBrain/nmodl#1510) NMODL Repo SHA: BlueBrain/nmodl@b37866816730d0e16746a0cc62ae2da0e5050308 --- .../sympy_replace_solutions_visitor.cpp | 24 ++++--------------- .../sympy_replace_solutions_visitor.hpp | 4 +--- src/nmodl/visitors/visitor_utils.cpp | 15 ++++++++---- src/nmodl/visitors/visitor_utils.hpp | 3 +++ 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp index fb0838e8a7..a30e8f00e2 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.cpp @@ -161,8 +161,7 @@ void SympyReplaceSolutionsVisitor::visit_statement_block(ast::StatementBlock& no void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( const ast::Node& node, - std::shared_ptr get_lhs(const ast::Node& node), - std::shared_ptr get_rhs(const ast::Node& node)) { + std::shared_ptr get_lhs(const ast::Node& node)) { interleaves_counter.new_equation(true); const auto& statement = std::static_pointer_cast( @@ -176,8 +175,7 @@ void SympyReplaceSolutionsVisitor::try_replace_tagged_statement( switch (policy) { case ReplacePolicy::VALUE: { - const auto dependencies = statement_dependencies(get_lhs(node), get_rhs(node)); - const auto& key = dependencies.first; + const auto key = statement_dependencies_key(get_lhs(node)); if (solution_statements.is_var_assigned_here(key)) { logger->debug("SympyReplaceSolutionsVisitor :: marking for replacement {}", @@ -216,11 +214,7 @@ void SympyReplaceSolutionsVisitor::visit_diff_eq_expression(ast::DiffEqExpressio return dynamic_cast(node).get_expression()->get_lhs(); }; - auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { - return dynamic_cast(node).get_expression()->get_rhs(); - }; - - try_replace_tagged_statement(node, get_lhs, get_rhs); + try_replace_tagged_statement(node, get_lhs); } void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { @@ -229,11 +223,7 @@ void SympyReplaceSolutionsVisitor::visit_lin_equation(ast::LinEquation& node) { return dynamic_cast(node).get_lhs(); }; - auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { - return dynamic_cast(node).get_rhs(); - }; - - try_replace_tagged_statement(node, get_lhs, get_rhs); + try_replace_tagged_statement(node, get_lhs); } @@ -243,11 +233,7 @@ void SympyReplaceSolutionsVisitor::visit_non_lin_equation(ast::NonLinEquation& n return dynamic_cast(node).get_lhs(); }; - auto get_rhs = [](const ast::Node& node) -> std::shared_ptr { - return dynamic_cast(node).get_rhs(); - }; - - try_replace_tagged_statement(node, get_lhs, get_rhs); + try_replace_tagged_statement(node, get_lhs); } diff --git a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp index 42bc4da2d0..c055d57c05 100644 --- a/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp +++ b/src/nmodl/visitors/sympy_replace_solutions_visitor.hpp @@ -249,12 +249,10 @@ class SympyReplaceSolutionsVisitor: public AstVisitor { * * \param node it can be Diff_Eq_Expression/LinEquation/NonLinEquation * \param get_lhs method with witch we may get the lhs (in case we need it) - * \param get_rhs method with witch we may get the rhs (in case we need it) */ void try_replace_tagged_statement( const ast::Node& node, - std::shared_ptr get_lhs(const ast::Node& node), - std::shared_ptr get_rhs(const ast::Node& node)); + std::shared_ptr get_lhs(const ast::Node& node)); /** * \struct InterleavesCounter diff --git a/src/nmodl/visitors/visitor_utils.cpp b/src/nmodl/visitors/visitor_utils.cpp index ca9e1a0b61..b279e20871 100644 --- a/src/nmodl/visitors/visitor_utils.cpp +++ b/src/nmodl/visitors/visitor_utils.cpp @@ -250,19 +250,24 @@ std::string to_json(const ast::Ast& node, bool compact, bool expand, bool add_nm return stream.str(); } +std::string statement_dependencies_key(const std::shared_ptr& lhs) { + if (!lhs->is_var_name()) { + return ""; + } + + const auto& lhs_var_name = std::dynamic_pointer_cast(lhs); + return get_full_var_name(*lhs_var_name); +} + std::pair> statement_dependencies( const std::shared_ptr& lhs, const std::shared_ptr& rhs) { - std::string key; + std::string key = statement_dependencies_key(lhs); std::unordered_set out; - if (!lhs->is_var_name()) { return {key, out}; } - const auto& lhs_var_name = std::dynamic_pointer_cast(lhs); - key = get_full_var_name(*lhs_var_name); - visitor::AstLookupVisitor lookup_visitor; lookup_visitor.lookup(*rhs, ast::AstNodeType::VAR_NAME); auto rhs_nodes = lookup_visitor.get_nodes(); diff --git a/src/nmodl/visitors/visitor_utils.hpp b/src/nmodl/visitors/visitor_utils.hpp index 97d8fa0d33..e1e7c99896 100644 --- a/src/nmodl/visitors/visitor_utils.hpp +++ b/src/nmodl/visitors/visitor_utils.hpp @@ -124,6 +124,9 @@ std::string to_json(const ast::Ast& node, bool expand = false, bool add_nmodl = false); +/// The `result.first` of `statement_dependencies`. +std::string statement_dependencies_key(const std::shared_ptr& lhs); + /// If \p lhs and \p rhs combined represent an assignment (we assume to have an "=" in between them) /// we extract the variables on which the assigned variable depends on. We provide the input with /// lhs and rhs because there are a few nodes that have this similar structure but slightly From e7c66f856d390225fd0fcaa4a75e0b5d9006f9a9 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 10 Oct 2024 18:30:11 +0200 Subject: [PATCH 806/871] Implement LONGITUDINAL_DIFFUSION. (BlueBrain/nmodl#1504) NMODL Repo SHA: BlueBrain/nmodl@22e45f1cb9d0d3a0b313c55d48b40694c08c7ce1 --- .../contents/longitudinal_diffusion.rst | 33 ++++ docs/nmodl/transpiler/index.rst | 1 + src/nmodl/codegen/codegen_helper_visitor.cpp | 60 ++++++++ src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.cpp | 44 ++++++ src/nmodl/codegen/codegen_info.hpp | 41 +++++ .../codegen/codegen_neuron_cpp_visitor.cpp | 89 ++++++++++- .../codegen/codegen_neuron_cpp_visitor.hpp | 19 ++- src/nmodl/language/code_generator.cmake | 1 + src/nmodl/language/codegen.yaml | 19 ++- src/nmodl/language/nmodl.yaml | 7 +- src/nmodl/language/node_info.py | 1 + src/nmodl/main.cpp | 8 + src/nmodl/symtab/symbol_properties.hpp | 5 +- src/nmodl/visitors/CMakeLists.txt | 1 + .../longitudinal_diffusion_visitor.cpp | 51 ++++++ .../longitudinal_diffusion_visitor.hpp | 20 +++ .../longitudinal_diffusion/heat_eqn_array.mod | 47 ++++++ .../heat_eqn_function.mod | 56 +++++++ .../heat_eqn_global.mod | 38 +++++ .../heat_eqn_scalar.mod | 38 +++++ .../heat_eqn_thread_vars.mod | 40 +++++ .../longitudinal_diffusion/test_heat_eqn.py | 145 ++++++++++++++++++ 23 files changed, 757 insertions(+), 8 deletions(-) create mode 100644 docs/nmodl/transpiler/contents/longitudinal_diffusion.rst create mode 100644 src/nmodl/visitors/longitudinal_diffusion_visitor.cpp create mode 100644 src/nmodl/visitors/longitudinal_diffusion_visitor.hpp create mode 100644 test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_array.mod create mode 100644 test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_function.mod create mode 100644 test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_global.mod create mode 100644 test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_scalar.mod create mode 100644 test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_thread_vars.mod create mode 100644 test/nmodl/transpiler/usecases/longitudinal_diffusion/test_heat_eqn.py diff --git a/docs/nmodl/transpiler/contents/longitudinal_diffusion.rst b/docs/nmodl/transpiler/contents/longitudinal_diffusion.rst new file mode 100644 index 0000000000..305ddc2d14 --- /dev/null +++ b/docs/nmodl/transpiler/contents/longitudinal_diffusion.rst @@ -0,0 +1,33 @@ +Longitudinal Diffusion +====================== + +The idea behind ``LONGITUDINAL_DIFFUSION`` is to allow a ``STATE`` variable to +diffuse along a section, i.e. from one segment into a neighbouring segment. + +This problem is solved by registering callbacks. In particular, NEURON needs to +be informed of the volume and diffusion rate. Additionally, the implicit +time-stepping requires information about certain derivatives. + +Implementation in NMODL +----------------------- + +The following ``KINETIC`` block + + .. code-block:: + + KINETIC state { + COMPARTMENT vol {X} + LONGITUDINAL_DIFFUSION mu {X} + + ~ X << (ica) + } + +Will undergo two transformations. The first is to create a system of ODEs that +can be solved. This consumed the AST node. However, to print the code for +longitudinal diffusion we require information from the ``COMPARTMENT`` and +``LONGITUDINAL_DIFFUSION`` statements. This is why there's a second +transformation, that runs before the other transformation, to extract the +required information and store it a AST node called +``LONGITUDINAL_DIFFUSION_BLOCK``. This block can then be converted into an +"info" object, which is then used to print the callbacks. + diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 9c4b0105ee..06574b8cec 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -24,6 +24,7 @@ About NMODL contents/pointers contents/cable_equations contents/globals + contents/longitudinal_diffusion .. toctree:: :maxdepth: 3 diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 6f8f85d019..391dbc6d36 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -9,8 +9,10 @@ #include #include +#include #include "ast/all.hpp" +#include "ast/constant_var.hpp" #include "codegen/codegen_naming.hpp" #include "parser/c11_driver.hpp" #include "visitors/visitor_utils.hpp" @@ -853,5 +855,63 @@ void CodegenHelperVisitor::visit_after_block(const ast::AfterBlock& node) { info.before_after_blocks.push_back(&node); } +static std::shared_ptr find_compartment( + const ast::LongitudinalDiffusionBlock& node, + const std::string& var_name) { + const auto& compartment_block = node.get_compartment_statements(); + for (const auto& stmt: compartment_block->get_statements()) { + auto comp = std::dynamic_pointer_cast(stmt); + + auto species = comp->get_species(); + auto it = std::find_if(species.begin(), species.end(), [&var_name](auto var) { + return var->get_node_name() == var_name; + }); + + if (it != species.end()) { + return comp; + } + } + + return nullptr; +} + +void CodegenHelperVisitor::visit_longitudinal_diffusion_block( + const ast::LongitudinalDiffusionBlock& node) { + auto longitudinal_diffusion_block = node.get_longitudinal_diffusion_statements(); + for (auto stmt: longitudinal_diffusion_block->get_statements()) { + auto diffusion = std::dynamic_pointer_cast(stmt); + auto rate_index_name = diffusion->get_index_name(); + auto rate_expr = diffusion->get_rate(); + auto species = diffusion->get_species(); + + auto process_compartment = [](const std::shared_ptr& compartment) + -> std::pair, std::shared_ptr> { + std::shared_ptr volume_expr; + std::shared_ptr volume_index_name; + if (!compartment) { + volume_index_name = nullptr; + volume_expr = std::make_shared("1.0"); + } else { + volume_index_name = compartment->get_index_name(); + volume_expr = std::shared_ptr(compartment->get_volume()->clone()); + } + return {std::move(volume_index_name), std::move(volume_expr)}; + }; + + for (auto var: species) { + std::string state_name = var->get_value()->get_value(); + auto compartment = find_compartment(node, state_name); + auto [volume_index_name, volume_expr] = process_compartment(compartment); + + info.longitudinal_diffusion_info.insert( + {state_name, + LongitudinalDiffusionInfo(volume_index_name, + std::shared_ptr(volume_expr), + rate_index_name, + std::shared_ptr(rate_expr->clone()))}); + } + } +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 9258f1cf7f..3468802c69 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -115,6 +115,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_verbatim(const ast::Verbatim& node) override; void visit_before_block(const ast::BeforeBlock& node) override; void visit_after_block(const ast::AfterBlock& node) override; + void visit_longitudinal_diffusion_block(const ast::LongitudinalDiffusionBlock& node) override; }; /** @} */ // end of codegen_details diff --git a/src/nmodl/codegen/codegen_info.cpp b/src/nmodl/codegen/codegen_info.cpp index b7d40125a6..10dec0ddd2 100644 --- a/src/nmodl/codegen/codegen_info.cpp +++ b/src/nmodl/codegen/codegen_info.cpp @@ -8,6 +8,8 @@ #include "codegen/codegen_info.hpp" #include "ast/all.hpp" +#include "ast/longitudinal_diffusion_block.hpp" +#include "visitors/rename_visitor.hpp" #include "visitors/var_usage_visitor.hpp" #include "visitors/visitor_utils.hpp" @@ -17,6 +19,48 @@ namespace codegen { using visitor::VarUsageVisitor; +LongitudinalDiffusionInfo::LongitudinalDiffusionInfo( + const std::shared_ptr& volume_index_name, + std::shared_ptr volume_expr, + const std::shared_ptr& rate_index_name, + std::shared_ptr rate_expr) + : volume_index_name(volume_index_name ? volume_index_name->get_node_name() : std::string{}) + , volume_expr(std::move(volume_expr)) + , rate_index_name(rate_index_name ? rate_index_name->get_node_name() : std::string{}) + , rate_expr(std::move(rate_expr)) {} + +std::shared_ptr LongitudinalDiffusionInfo::volume( + const std::string& index_name) const { + return substitute_index(index_name, volume_index_name, volume_expr); +} +std::shared_ptr LongitudinalDiffusionInfo::diffusion_rate( + const std::string& index_name) const { + return substitute_index(index_name, rate_index_name, rate_expr); +} + +double LongitudinalDiffusionInfo::dfcdc(const std::string& /* index_name */) const { + // Needed as part of the Jacobian to stabalize + // the implicit time-integration. However, + // currently, it's set to `0.0` for simplicity. + return 0.0; +} + +std::shared_ptr LongitudinalDiffusionInfo::substitute_index( + const std::string& index_name, + const std::string& old_index_name, + const std::shared_ptr& old_expr) const { + if (old_index_name == "") { + // The expression doesn't contain an index that needs substituting. + return old_expr; + } + auto new_expr = old_expr->clone(); + + auto v = visitor::RenameVisitor(old_index_name, index_name); + new_expr->accept(v); + + return std::shared_ptr(dynamic_cast(new_expr)); +} + /// if any ion has write variable bool CodegenInfo::ion_has_write_variable() const noexcept { return std::any_of(ions.begin(), ions.end(), [](auto const& ion) { diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 6d39479e1a..0e86ac0c30 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -284,6 +284,44 @@ struct IndexSemantics { , size(size) {} }; +/** + * \brief Information required to print LONGITUDINAL_DIFFUSION callbacks. + */ +class LongitudinalDiffusionInfo { + public: + LongitudinalDiffusionInfo(const std::shared_ptr& index_name, + std::shared_ptr volume_expr, + const std::shared_ptr& rate_index_name, + std::shared_ptr rate_expr); + /// Volume of this species. + /// + /// If the volume expression is an indexed expression, the index in the + /// expression is substituted with `index_name`. + std::shared_ptr volume(const std::string& index_name) const; + + /// Difusion rate of this species. + /// + /// If the diffusion expression is an indexed expression, the index in the + /// expression is substituted with `index_name`. + std::shared_ptr diffusion_rate(const std::string& index_name) const; + + /// The value of what NEURON calls `dfcdc`. + double dfcdc(const std::string& /* index_name */) const; + + protected: + std::shared_ptr substitute_index( + const std::string& index_name, + const std::string& old_index_name, + const std::shared_ptr& old_expr) const; + + private: + std::string volume_index_name; + std::shared_ptr volume_expr; + + std::string rate_index_name; + std::shared_ptr rate_expr; +}; + /** * \class CodegenInfo @@ -447,6 +485,9 @@ struct CodegenInfo { /// all factors defined in the mod file std::vector factor_definitions; + /// for each state, the information needed to print the callbacks. + std::map longitudinal_diffusion_info; + /// ions used in the mod file std::vector ions; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2b629f0b48..55933e2a58 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -36,7 +36,6 @@ using visitor::VarUsageVisitor; using symtab::syminfo::NmodlType; - /****************************************************************************************/ /* Generic information getters */ /****************************************************************************************/ @@ -436,6 +435,79 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { } } +CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::ldifusfunc1_parameters() const { + return ParamVector{{"", "ldifusfunc2_t", "", "_f"}, + {"", "const _nrn_model_sorted_token&", "", "_sorted_token"}, + {"", "NrnThread&", "", "_nt"}}; +} + + +CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::ldifusfunc3_parameters() const { + return ParamVector{{"", "int", "", "_i"}, + {"", "Memb_list*", "", "_ml_arg"}, + {"", "size_t", "", "id"}, + {"", "Datum*", "", "_ppvar"}, + {"", "double*", "", "_pdvol"}, + {"", "double*", "", "_pdfcdc"}, + {"", "Datum*", "", "/* _thread */"}, + {"", "NrnThread*", "", "nt"}, + {"", "const _nrn_model_sorted_token&", "", "_sorted_token"}}; +} + +void CodegenNeuronCppVisitor::print_longitudinal_diffusion_callbacks() { + auto coeff_callback_name = [](const std::string& var_name) { + return fmt::format("_diffusion_coefficient_{}", var_name); + }; + + auto space_name = [](const std::string& var_name) { + return fmt::format("_diffusion_space_{}", var_name); + }; + + for (auto [var_name, values]: info.longitudinal_diffusion_info) { + printer->fmt_line("static void* {};", space_name(var_name)); + printer->fmt_push_block("static double {}({})", + coeff_callback_name(var_name), + get_parameter_str(ldifusfunc3_parameters())); + + print_entrypoint_setup_code_from_memb_list(); + + auto volume_expr = values.volume("_i"); + auto mu_expr = values.diffusion_rate("_i"); + + printer->add_indent(); + printer->add_text("*_pdvol= "); + volume_expr->accept(*this); + printer->add_text(";"); + printer->add_newline(); + + printer->add_line("*_pdfcdc = 0.0;"); + printer->add_indent(); + printer->add_text("return "); + mu_expr->accept(*this); + printer->add_text(";"); + printer->add_newline(); + + printer->pop_block(); + } + + printer->fmt_push_block("static void _apply_diffusion_function({})", + get_parameter_str(ldifusfunc1_parameters())); + for (auto [var_name, values]: info.longitudinal_diffusion_info) { + auto var = program_symtab->lookup(var_name); + size_t array_size = var->get_length(); + printer->fmt_push_block("for(size_t _i = 0; _i < {}; ++_i)", array_size); + printer->fmt_line( + "(*_f)(mech_type, {}, &{}, _i, /* x pos */ {}, /* Dx pos */ {}, _sorted_token, _nt);", + coeff_callback_name(var_name), + space_name(var_name), + position_of_float_var(var_name), + position_of_float_var("D" + var_name)); + printer->pop_block(); + } + printer->pop_block(); + printer->add_newline(); +} + /****************************************************************************************/ /* Code-specific helper routines */ /****************************************************************************************/ @@ -1301,6 +1373,11 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { info.semantics[i].name); } + if (!info.longitudinal_diffusion_info.empty()) { + printer->fmt_line("hoc_register_ldifus1(_apply_diffusion_function);"); + } + + if (info.write_concentration) { printer->fmt_line("nrn_writes_conc(mech_type, 0);"); } @@ -2275,6 +2352,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_nrn_destructor_declaration(); print_nrn_alloc(); print_function_prototypes(); + print_longitudinal_diffusion_callbacks(); print_point_process_function_definitions(); print_setdata_functions(); print_check_table_entrypoint(); @@ -2457,6 +2535,15 @@ void CodegenNeuronCppVisitor::visit_watch_statement(const ast::WatchStatement& / return; } +void CodegenNeuronCppVisitor::visit_longitudinal_diffusion_block( + const ast::LongitudinalDiffusionBlock& /* node */) { + // These are handled via `print_longitdudinal_*`. +} + +void CodegenNeuronCppVisitor::visit_lon_diffuse(const ast::LonDiffuse& /* node */) { + // These are handled via `print_longitdudinal_*`. +} + void CodegenNeuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { // The setup for enabling this loop is: // double ** _fornetcon_data = ...; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 4be6fc09b6..794c7dfbfa 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -250,6 +250,21 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_hoc_py_wrapper_function_definitions(); + /** + * Prints the callbacks required for LONGITUDINAL_DIFFUSION. + */ + void print_longitudinal_diffusion_callbacks(); + + /** + * Parameters for what NEURON calls `ldifusfunc1_t`. + */ + ParamVector ldifusfunc1_parameters() const; + + /** + * Parameters for what NEURON calls `ldifusfunc3_t`. + */ + ParamVector ldifusfunc3_parameters() const; + /****************************************************************************************/ /* Code-specific helper routines */ @@ -708,8 +723,8 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void visit_watch_statement(const ast::WatchStatement& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; - - + void visit_longitudinal_diffusion_block(const ast::LongitudinalDiffusionBlock& node) override; + void visit_lon_diffuse(const ast::LonDiffuse& node) override; public: diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index 822030c1d5..bfb5fd933c 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -115,6 +115,7 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/local_list_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/local_var.hpp ${PROJECT_BINARY_DIR}/src/ast/lon_diffuse.hpp + ${PROJECT_BINARY_DIR}/src/ast/longitudinal_diffusion_block.hpp ${PROJECT_BINARY_DIR}/src/ast/model.hpp ${PROJECT_BINARY_DIR}/src/ast/mutex_lock.hpp ${PROJECT_BINARY_DIR}/src/ast/mutex_unlock.hpp diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 477df7fa65..12bd660862 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -87,7 +87,24 @@ type: StatementBlock - finalize_block: brief: "Statement block to be executed after calling linear solver" - type: StatementBlock + type: StatementBlock + - LongitudinalDiffusionBlock: + brief: "Extracts information required for LONGITUDINAL_DIFFUSION for each KINETIC block." + nmodl: "LONGITUDINAL_DIFFUSION_BLOCK" + members: + - name: + brief: "Name of the longitudinal diffusion block" + type: Name + node_name: true + prefix: { value: " "} + suffix: { value: " "} + - longitudinal_diffusion_statements: + brief: "All LONGITUDINAL_DIFFUSION statements in the KINETIC block." + type: StatementBlock + - compartment_statements: + brief: "All (required) COMPARTMENT statements in the KINETIC block." + type: StatementBlock + - WrappedExpression: brief: "Wrap any other expression type" members: diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 6c9124ac83..0f76b01a30 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1568,6 +1568,7 @@ separator: " " brief: "Represent LONGITUDINAL_DIFFUSION statement in NMODL" + - ReactionStatement: brief: "TODO" nmodl: "~ " @@ -1750,14 +1751,14 @@ separator: ", " description: | Here is an example of RANDOM statement - - \code{.mod} + + \code{.mod} NEURON { THREADSAFE POINT_PROCESS NetStim RANDOM ranvar \endcode - + - Pointer: nmodl: "POINTER " members: diff --git a/src/nmodl/language/node_info.py b/src/nmodl/language/node_info.py index ff6d72faf5..d2cfe50c9f 100644 --- a/src/nmodl/language/node_info.py +++ b/src/nmodl/language/node_info.py @@ -80,6 +80,7 @@ "ProcedureBlock", "DerivativeBlock", "LinearBlock", + "LongitudinalDiffusionBlock", "NonLinearBlock", "DiscreteBlock", "KineticBlock", diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index bf6128bac2..9cc7b4989f 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -35,6 +35,7 @@ #include "visitors/local_to_assigned_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/localize_visitor.hpp" +#include "visitors/longitudinal_diffusion_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" @@ -430,6 +431,13 @@ int run_nmodl(int argc, const char* argv[]) { SymtabVisitor(update_symtab).visit_program(*ast); } + if (neuron_code) { + CreateLongitudinalDiffusionBlocks().visit_program(*ast); + ast_to_nmodl(*ast, filepath("londifus")); + SymtabVisitor(update_symtab).visit_program(*ast); + } + + /// note that we can not symtab visitor in update mode as we /// replace kinetic block with derivative block of same name /// in global scope diff --git a/src/nmodl/symtab/symbol_properties.hpp b/src/nmodl/symtab/symbol_properties.hpp index 8bf6359194..7cc57c7b7f 100644 --- a/src/nmodl/symtab/symbol_properties.hpp +++ b/src/nmodl/symtab/symbol_properties.hpp @@ -223,7 +223,10 @@ enum class NmodlType : enum_type { random_var = 1LL << 34, /// FUNCTION or PROCEDURE needs setdata check - use_range_ptr_var = 1LL << 35 + use_range_ptr_var = 1LL << 35, + + /// Internal LONGITUDINAL_DIFFUSION block + longitudinal_diffusion_block = 1LL << 36 }; template diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 262b6a623a..055f4c212c 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -22,6 +22,7 @@ add_library( local_to_assigned_visitor.cpp local_var_rename_visitor.cpp localize_visitor.cpp + longitudinal_diffusion_visitor.cpp loop_unroll_visitor.cpp neuron_solve_visitor.cpp perf_visitor.cpp diff --git a/src/nmodl/visitors/longitudinal_diffusion_visitor.cpp b/src/nmodl/visitors/longitudinal_diffusion_visitor.cpp new file mode 100644 index 0000000000..104affc710 --- /dev/null +++ b/src/nmodl/visitors/longitudinal_diffusion_visitor.cpp @@ -0,0 +1,51 @@ +#include "longitudinal_diffusion_visitor.hpp" + +#include "ast/ast_decl.hpp" +#include "ast/kinetic_block.hpp" +#include "ast/longitudinal_diffusion_block.hpp" +#include "ast/name.hpp" +#include "ast/program.hpp" +#include "ast/statement.hpp" +#include "ast/statement_block.hpp" +#include "ast/string.hpp" +#include "visitor_utils.hpp" + +#include + + +namespace nmodl { +namespace visitor { + +static std::shared_ptr make_statement_block( + ast::KineticBlock& kinetic_block, + nmodl::ast::AstNodeType node_type) { + auto nodes = collect_nodes(kinetic_block, {node_type}); + + ast::StatementVector statements; + statements.reserve(nodes.size()); + for (auto& node: nodes) { + statements.push_back(std::dynamic_pointer_cast(node)); + } + + return std::make_shared(std::move(statements)); +} + + +static std::shared_ptr create_block(ast::KineticBlock& node) { + return std::make_shared( + std::make_shared(std::make_shared("ld_" + node.get_node_name())), + make_statement_block(node, nmodl::ast::AstNodeType::LON_DIFFUSE), + make_statement_block(node, nmodl::ast::AstNodeType::COMPARTMENT)); +} + +void CreateLongitudinalDiffusionBlocks::visit_program(ast::Program& node) { + auto kinetic_blocks = collect_nodes(node, {nmodl::ast::AstNodeType::KINETIC_BLOCK}); + + for (const auto& ast_node: kinetic_blocks) { + auto kinetic_block = std::dynamic_pointer_cast(ast_node); + node.emplace_back_node(create_block(*kinetic_block)); + } +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp b/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp new file mode 100644 index 0000000000..7caec2a6fd --- /dev/null +++ b/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "visitors/ast_visitor.hpp" + + +namespace nmodl { +namespace ast { +class Program; +} + +namespace visitor { + +class CreateLongitudinalDiffusionBlocks: public AstVisitor { + public: + + void visit_program(ast::Program& node) override; +}; + +} // namespace visitor +} // namespace nmodl diff --git a/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_array.mod b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_array.mod new file mode 100644 index 0000000000..74117389f6 --- /dev/null +++ b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_array.mod @@ -0,0 +1,47 @@ +NEURON { + SUFFIX heat_eqn_array + RANGE x +} + +DEFINE N 4 + +PARAMETER { + kf = 0.0 + kb = 0.0 +} + +ASSIGNED { + x + mu[N] + vol[N] +} + +STATE { + X[N] +} + +INITIAL { + FROM i=0 TO N-1 { + mu[i] = 1.0 + i + vol[i] = 0.01 / (i + 1.0) + + if(x < 0.5) { + X[i] = 1.0 + i + } else { + X[i] = 0.0 + } + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + COMPARTMENT i, vol[i] {X} + LONGITUDINAL_DIFFUSION i, mu[i] {X} + + FROM i=0 TO N-2 { + ~ X[i] <-> X[i+1] (kf, kb) + } +} diff --git a/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_function.mod b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_function.mod new file mode 100644 index 0000000000..70cf7e0099 --- /dev/null +++ b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_function.mod @@ -0,0 +1,56 @@ +NEURON { + SUFFIX heat_eqn_function + RANGE x + GLOBAL g_mu, g_vol + THREADSAFE +} + +ASSIGNED { + x + g_mu + g_vol +} + +STATE { + X +} + +INITIAL { + g_mu = 1.1 + g_vol = 0.01 + + if(x < 0.5) { + X = 1.0 + } else { + X = 0.0 + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +FUNCTION factor(x) { + if(x < 0.25) { + factor = 0.0 + } else { + factor = 10*(x - 0.25) + } +} + +FUNCTION vol(x) { + vol = (1 + x) * g_vol +} + +FUNCTION mu(x) { + mu = x * g_mu +} + +KINETIC state { + COMPARTMENT vol(x) {X} + LONGITUDINAL_DIFFUSION mu(factor(x)) {X} + + : There must be a reaction equation, but + : we only want to test diffusion. + ~ X << (0.0) +} diff --git a/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_global.mod b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_global.mod new file mode 100644 index 0000000000..8e88428566 --- /dev/null +++ b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_global.mod @@ -0,0 +1,38 @@ +NEURON { + SUFFIX heat_eqn_global + RANGE x +} + +PARAMETER { + mu = 2.0 + vol = 0.01 +} + +ASSIGNED { + x +} + +STATE { + X +} + +INITIAL { + if(x < 0.5) { + X = 1.0 + } else { + X = 0.0 + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + COMPARTMENT vol {X} + LONGITUDINAL_DIFFUSION mu {X} + + : There must be a reaction equation, but + : we only want to test diffusion. + ~ X << (0.0) +} diff --git a/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_scalar.mod b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_scalar.mod new file mode 100644 index 0000000000..5d337c631c --- /dev/null +++ b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_scalar.mod @@ -0,0 +1,38 @@ +NEURON { + SUFFIX heat_eqn_scalar + RANGE x +} + +ASSIGNED { + x + mu + vol +} + +STATE { + X +} + +INITIAL { + mu = 1.1 + vol = 0.01 + + if(x < 0.5) { + X = 1.0 + } else { + X = 0.0 + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + COMPARTMENT vol {X} + LONGITUDINAL_DIFFUSION mu {X} + + : There must be a reaction equation, but + : we only want to test diffusion. + ~ X << (0.0) +} diff --git a/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_thread_vars.mod b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_thread_vars.mod new file mode 100644 index 0000000000..b90599f0e1 --- /dev/null +++ b/test/nmodl/transpiler/usecases/longitudinal_diffusion/heat_eqn_thread_vars.mod @@ -0,0 +1,40 @@ +NEURON { + SUFFIX heat_eqn_thread_vars + RANGE x + GLOBAL mu, vol + THREADSAFE +} + +ASSIGNED { + x + mu + vol +} + +STATE { + X +} + +INITIAL { + mu = 1.1 + vol = 0.01 + + if(x < 0.5) { + X = 1.0 + } else { + X = 0.0 + } +} + +BREAKPOINT { + SOLVE state METHOD sparse +} + +KINETIC state { + COMPARTMENT vol {X} + LONGITUDINAL_DIFFUSION mu {X} + + : There must be a reaction equation, but + : we only want to test diffusion. + ~ X << (0.0) +} diff --git a/test/nmodl/transpiler/usecases/longitudinal_diffusion/test_heat_eqn.py b/test/nmodl/transpiler/usecases/longitudinal_diffusion/test_heat_eqn.py new file mode 100644 index 0000000000..b454371bab --- /dev/null +++ b/test/nmodl/transpiler/usecases/longitudinal_diffusion/test_heat_eqn.py @@ -0,0 +1,145 @@ +import os +import sys +import pickle + +from neuron import h, gui + +import numpy as np + + +def reference_filename(mech_name): + return f"diffuse-{mech_name}.pkl" + + +def save_state(mech_name, t, X): + with open(reference_filename(mech_name), "wb") as f: + pickle.dump((t, X), f) + + +def load_state(mech_name): + filename = reference_filename(mech_name) + if not os.path.exists(filename): + raise RuntimeError("References unavailable. Try running with NOCMODL first.") + + with open(filename, "rb") as f: + return pickle.load(f) + + +def simulator_name(): + return sys.argv[1] if len(sys.argv) >= 2 else None + + +def run_simulation(mech_name, record_states): + nseg = 50 + + s = h.Section() + s.nseg = nseg + s.insert(mech_name) + + t_hoc = h.Vector().record(h._ref_t) + X_hoc = [] + for i in range(nseg): + x = (0.5 + i) / nseg + inst = getattr(s(x), mech_name) + + inst.x = x + X_hoc.append(record_states(inst)) + + h.finitialize() + h.continuerun(1.0) + + t = np.array(t_hoc.as_numpy()) + X = np.array([[np.array(xx.as_numpy()) for xx in x] for x in X_hoc]) + + # The axes are: + # time, spatial position, state variable + X = np.transpose(X, axes=(2, 0, 1)) + + return t, X + + +def check_timeseries(mech_name, t, X): + t_noc, X_noc = load_state(mech_name) + + np.testing.assert_allclose(t, t_noc, atol=1e-10, rtol=0.0) + np.testing.assert_allclose(X, X_noc, atol=1e-10, rtol=0.0) + + +def plot_timeseries(mech_name, t, X, i_state): + try: + import matplotlib.pyplot as plt + except ImportError: + return + + nseg = X.shape[1] + frames_with_label = [0, 1, len(t) - 1] + + fig = plt.figure() + for i_time, t in enumerate(t): + kwargs = {"label": f"t = {t:.3f}"} if i_time in frames_with_label else dict() + + x = [(0.5 + i) / nseg for i in range(nseg)] + plt.plot(x, X[i_time, :, i_state], **kwargs) + + plt.xlabel("Spatial position") + plt.ylabel(f"STATE value: X[{i_state}]") + plt.title(f"Simulator: {simulator_name()}") + plt.legend() + plt.savefig(f"diffusion-{mech_name}-{simulator_name()}-state{i_state}.png", dpi=300) + plt.close(fig) + + +def check_heat_equation(mech_name, record_states): + t, X = run_simulation(mech_name, record_states) + + for i_state in range(X.shape[2]): + plot_timeseries(mech_name, t, X, i_state) + + simulator = sys.argv[1] + if simulator == "nocmodl": + save_state(mech_name, t, X) + + else: + check_timeseries(mech_name, t, X) + + +def record_states_factory(array_size, get_reference): + return lambda inst: [ + h.Vector().record(get_reference(inst, k)) for k in range(array_size) + ] + + +def check_heat_equation_scalar(mech_name): + check_heat_equation( + mech_name, record_states_factory(1, lambda inst, k: inst._ref_X) + ) + + +def test_heat_equation_scalar(): + check_heat_equation_scalar("heat_eqn_scalar") + + +def test_heat_equation_global(): + check_heat_equation_scalar("heat_eqn_global") + + +def test_heat_equation_function(): + check_heat_equation_scalar("heat_eqn_function") + + +def test_heat_equation_thread_vars(): + check_heat_equation_scalar("heat_eqn_thread_vars") + + +def test_heat_equation_array(): + check_heat_equation( + "heat_eqn_array", record_states_factory(4, lambda inst, k: inst._ref_X[k]) + ) + + +if __name__ == "__main__": + test_heat_equation_scalar() + test_heat_equation_global() + test_heat_equation_thread_vars() + test_heat_equation_function() + test_heat_equation_array() From 1916a370953444fc7f149df59df3cf3e67e85ec4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 14 Oct 2024 11:49:26 +0200 Subject: [PATCH 807/871] Refactor forcing `sympy --analytic`. (BlueBrain/nmodl#1511) NMODL Repo SHA: BlueBrain/nmodl@86e470d25353325b9a543bd5b4d21b01b9a939ce --- src/nmodl/main.cpp | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 9cc7b4989f..436ec2c4e0 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -497,12 +497,30 @@ int run_nmodl(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("localize")); } - const bool sympy_derivimplicit = neuron_code && solver_exists(*ast, "derivimplicit"); - const bool sympy_linear = node_exists(*ast, ast::AstNodeType::LINEAR_BLOCK); - const bool sympy_sparse = solver_exists(*ast, "sparse"); + // Even if `sympy --analytic` wasn't requested by the user, some constructs can't be + // implemented without. If they're present we assume that SymPy is present; and force + // `sympy --analytic`. + if (!sympy_analytic) { + auto enable_sympy = [&sympy_analytic](bool enable, const std::string& reason) { + if (!enable) { + return; + } + + if (!sympy_analytic) { + logger->info("Automatically enabling sympy_analytic."); + logger->info("Required by: {}.", reason); + } + + sympy_analytic = true; + }; + + enable_sympy(solver_exists(*ast, "derivimplicit"), "'SOLVE ... METHOD derivimplicit'"); + enable_sympy(node_exists(*ast, ast::AstNodeType::LINEAR_BLOCK), "'LINEAR' block"); + enable_sympy(solver_exists(*ast, "sparse"), "'SOLVE ... METHOD sparse'"); + } + - if (sympy_conductance || sympy_analytic || sympy_sparse || sympy_derivimplicit || - sympy_linear) { + if (sympy_conductance || sympy_analytic) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() .api() .initialize_interpreter(); @@ -513,21 +531,7 @@ int run_nmodl(int argc, const char* argv[]) { ast_to_nmodl(*ast, filepath("sympy_conductance")); } - if (sympy_analytic || sympy_sparse || sympy_derivimplicit || sympy_linear) { - if (!sympy_analytic) { - logger->info("Automatically enabling sympy_analytic."); - if (sympy_sparse) { - logger->info("Required by 'SOLVE ... METHOD sparse'."); - } - - if (sympy_derivimplicit) { - logger->info("Required by 'SOLVE ... METHOD derivimplicit'."); - } - - if (sympy_linear) { - logger->info("Required by 'LINEAR' block."); - } - } + if (sympy_analytic) { logger->info("Running sympy solve visitor"); SympySolverVisitor(sympy_pade, sympy_cse).visit_program(*ast); SymtabVisitor(update_symtab).visit_program(*ast); From 575f7075d0d980e8cf4f9bf841ecd2d2f97a5c77 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 14 Oct 2024 13:22:55 +0200 Subject: [PATCH 808/871] Register `longitudinal_diffusion` usecase group (BlueBrain/nmodl#1515) NMODL Repo SHA: BlueBrain/nmodl@43dca3de104840ee469b7997220064fa1f6cb441 --- test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index feaa81ef7a..764b907cf8 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -11,6 +11,7 @@ set(NMODL_USECASE_DIRS hodgkin_huxley kinetic linear + longitudinal_diffusion morphology net_event net_move From 2bf69165918b3cc601a5dcfed281a4edce24e806 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 14 Oct 2024 14:04:20 +0200 Subject: [PATCH 809/871] NONLINEAR requires `sympy --analytic`. (BlueBrain/nmodl#1512) NMODL Repo SHA: BlueBrain/nmodl@9c543ef65e4a091acb6f96023ef8d1d697ef20da --- src/nmodl/language/nmodl.yaml | 6 +++--- src/nmodl/main.cpp | 2 ++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/nonlinear/nonlin.mod | 21 +++++++++++++++++++ .../usecases/nonlinear/test_nonlin.py | 15 +++++++++++++ 5 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/nonlinear/nonlin.mod create mode 100644 test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py diff --git a/src/nmodl/language/nmodl.yaml b/src/nmodl/language/nmodl.yaml index 0f76b01a30..de598cec30 100644 --- a/src/nmodl/language/nmodl.yaml +++ b/src/nmodl/language/nmodl.yaml @@ -1140,15 +1140,15 @@ type: Expression - NonLinEquation: - brief: "TODO" + brief: "One equation in a system of equations that collectively make a NONLINEAR block." nmodl: "~ " members: - lhs: - brief: "TODO" + brief: "Left-hand-side of the equation." type: Expression suffix: {value: " = "} - rhs: - brief: "TODO" + brief: "Right-hand-side of the equation." type: Expression - LinEquation: diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 436ec2c4e0..ba9de4a19d 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -516,6 +516,8 @@ int run_nmodl(int argc, const char* argv[]) { enable_sympy(solver_exists(*ast, "derivimplicit"), "'SOLVE ... METHOD derivimplicit'"); enable_sympy(node_exists(*ast, ast::AstNodeType::LINEAR_BLOCK), "'LINEAR' block"); + enable_sympy(node_exists(*ast, ast::AstNodeType::NON_LINEAR_BLOCK), + "'NONLINEAR' block"); enable_sympy(solver_exists(*ast, "sparse"), "'SOLVE ... METHOD sparse'"); } diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 764b907cf8..22a806a1c8 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -18,6 +18,7 @@ set(NMODL_USECASE_DIRS net_receive net_send neuron_variables + nonlinear nonspecific_current parameter point_process diff --git a/test/nmodl/transpiler/usecases/nonlinear/nonlin.mod b/test/nmodl/transpiler/usecases/nonlinear/nonlin.mod new file mode 100644 index 0000000000..ec0bed30fe --- /dev/null +++ b/test/nmodl/transpiler/usecases/nonlinear/nonlin.mod @@ -0,0 +1,21 @@ +NEURON { + SUFFIX nonlin +} + +STATE { x } + +FUNCTION solve() { + : Iterative solvers need a starting guess. + x = 1.0 + SOLVE eq + + solve = x +} + +NONLINEAR eq { + ~ x*x = 4.0 +} + +FUNCTION residual(x) { + residual = x - 2.0 +} diff --git a/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py b/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py new file mode 100644 index 0000000000..eef75edbdb --- /dev/null +++ b/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py @@ -0,0 +1,15 @@ +from neuron import h, gui + + +def test_nonlin(): + s = h.Section() + s.insert("nonlin") + inst = s(0.5).nonlin + + x = inst.solve() + error = inst.residual(x) + assert abs(error) < 1e-9, f"{x = }, {error = }" + + +if __name__ == "__main__": + test_nonlin() From 0d36af5ef0e104177f27db9e4708ac0adae98237 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 15 Oct 2024 10:19:28 +0200 Subject: [PATCH 810/871] Eigen solvers in ARTIFICIAL_CELLS can't use voltage. (BlueBrain/nmodl#1516) NMODL Repo SHA: BlueBrain/nmodl@186e550e9d490b47ef2944a0f4de5b90a061e4e1 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 4 +++- .../usecases/nonlinear/art_nonlin.mod | 21 +++++++++++++++++++ .../usecases/nonlinear/test_nonlin.py | 14 +++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/nonlinear/art_nonlin.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 55933e2a58..2cbd397b3a 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -908,7 +908,9 @@ void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_ini CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::functor_params() { auto params = internal_method_parameters(); - params.push_back({"", "double", "", "v"}); + if (!info.artificial_cell) { + params.push_back({"", "double", "", "v"}); + } return params; } diff --git a/test/nmodl/transpiler/usecases/nonlinear/art_nonlin.mod b/test/nmodl/transpiler/usecases/nonlinear/art_nonlin.mod new file mode 100644 index 0000000000..79caadcc63 --- /dev/null +++ b/test/nmodl/transpiler/usecases/nonlinear/art_nonlin.mod @@ -0,0 +1,21 @@ +NEURON { + ARTIFICIAL_CELL art_nonlin +} + +STATE { x } + +FUNCTION solve() { + : Iterative solvers need a starting guess. + x = 1.0 + SOLVE eq + + solve = x +} + +NONLINEAR eq { + ~ x*x = 4.0 +} + +FUNCTION residual(x) { + residual = x - 2.0 +} diff --git a/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py b/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py index eef75edbdb..55cf1e5004 100644 --- a/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py +++ b/test/nmodl/transpiler/usecases/nonlinear/test_nonlin.py @@ -1,15 +1,25 @@ from neuron import h, gui -def test_nonlin(): +def check_solve(make_inst): s = h.Section() s.insert("nonlin") - inst = s(0.5).nonlin + + inst = make_inst(s) x = inst.solve() error = inst.residual(x) assert abs(error) < 1e-9, f"{x = }, {error = }" +def test_nonlin(): + check_solve(lambda s: s(0.5).nonlin) + + +def test_art_nonlin(): + check_solve(lambda s: h.art_nonlin()) + + if __name__ == "__main__": test_nonlin() + test_art_nonlin() From 85fcd726e00160da1a81caf7a264a0ac35043e3b Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 15 Oct 2024 13:10:44 +0200 Subject: [PATCH 811/871] Add support for differentiating array variables (BlueBrain/nmodl#1517) NMODL Repo SHA: BlueBrain/nmodl@a3957f56f035452e1ce0badd88d69fa1f84818be --- src/nmodl/nmodl/python/nmodl/ode.py | 8 +++++++- test/nmodl/transpiler/unit/ode/test_ode.py | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index cd6b2b27ae..c1c907eae9 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -595,10 +595,16 @@ def differentiate2c( If the result coincides with one of the vars, or the LHS of one of the prev_expressions, then it is simplified to this expression. + Note that, in order to differentiate against indexed variables (such as + ``x[0]``), you must pass an instance of ``sympy.Indexed`` to + ``dependent_var`` (_not_ an instance of ``sympy.IndexedBase``), as well as + an instance of ``sympy.IndexedBase`` to ``vars``. + Some simple examples of use: - ``nmodl.ode.differentiate2c ("a*x", "x", {"a"}) == "a"`` - ``differentiate2c ("cos(y) + b*y**2", "y", {"a","b"}) == "Dy = 2*b*y - sin(y)"`` + - ``differentiate2c("a * x[0]", sympy.IndexedBase("x", shape=[1])[0], {"a", sympy.IndexedBase("x", shape=[1])}) == "a"`` Args: expression: expression to be differentiated e.g. "a*x + b" @@ -619,7 +625,7 @@ def differentiate2c( # every symbol (a.k.a variable) that SymPy # is going to manipulate needs to be declared # explicitly - x = sp.symbols(dependent_var, real=True) + x = make_symbol(dependent_var) vars = set(vars) vars.discard(dependent_var) # declare all other supplied variables diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index 6eae706998..82e0358d22 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -111,6 +111,19 @@ def test_differentiate2c(): {sp.IndexedBase("s", shape=[1]), sp.IndexedBase("z", shape=[1])}, ) + # make sure we can diff against indexed vars as well + var = sp.IndexedBase("x", shape=[1]) + + assert _equivalent( + differentiate2c( + "a * x[0]", + var[0], + {"a", var}, + ), + "a", + {"a"}, + ) + result = differentiate2c( "-f(x)", "x", From 3dc0ffc28ecf4b7a2a485e3631b6d6f6b20b3c80 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 17 Oct 2024 09:30:30 +0200 Subject: [PATCH 812/871] Hide most functions/global variables. (BlueBrain/nmodl#1520) Global struct instance must be `static`. One reason is `SUFFIX nothing` which would otherwise cause symbols to clash. Similarly, any function that isn't `dlsym`ed or eligible for `EXTERNAL` should have internal linkage. NMODL Repo SHA: BlueBrain/nmodl@a0719820ddbaffa0feb4f909f639cc2a04893840 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 4 ++-- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 15 +++++++++------ 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index febc1ba157..f1d8158728 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -462,7 +462,7 @@ void CodegenCppVisitor::print_global_var_struct_assertions() const { void CodegenCppVisitor::print_global_var_struct_decl() { - printer->add_line(global_struct(), ' ', global_struct_instance(), ';'); + printer->fmt_line("static {} {};", global_struct(), global_struct_instance()); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index d09e7f452a..96378a8592 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1547,8 +1547,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { template void print_function_declaration(const T& node, const std::string& name, - const std::unordered_set& = { - CppObjectSpecifier::Inline}); + const std::unordered_set& = + {CppObjectSpecifier::Static, CppObjectSpecifier::Inline}); void print_rename_state_vars() const; }; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2cbd397b3a..d1c0073f69 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1711,7 +1711,7 @@ void CodegenNeuronCppVisitor::print_global_function_common_code(BlockType type, {"", "NrnThread*", "", "nt"}, {"", "Memb_list*", "", "_ml_arg"}, {"", "int", "", "_type"}}; - printer->fmt_push_block("void {}({})", method, get_parameter_str(args)); + printer->fmt_push_block("static void {}({})", method, get_parameter_str(args)); print_entrypoint_setup_code_from_memb_list(); printer->add_line("auto nodecount = _ml_arg->nodecount;"); } @@ -1782,13 +1782,15 @@ void CodegenNeuronCppVisitor::print_nrn_jacob() { void CodegenNeuronCppVisitor::print_nrn_constructor_declaration() { if (info.constructor_node) { - printer->fmt_line("void {}(Prop* prop);", method_name(naming::NRN_CONSTRUCTOR_METHOD)); + printer->fmt_line("static void {}(Prop* prop);", + method_name(naming::NRN_CONSTRUCTOR_METHOD)); } } void CodegenNeuronCppVisitor::print_nrn_constructor() { if (info.constructor_node) { - printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_CONSTRUCTOR_METHOD)); + printer->fmt_push_block("static void {}(Prop* prop)", + method_name(naming::NRN_CONSTRUCTOR_METHOD)); print_entrypoint_setup_code_from_prop(); @@ -1801,11 +1803,12 @@ void CodegenNeuronCppVisitor::print_nrn_constructor() { void CodegenNeuronCppVisitor::print_nrn_destructor_declaration() { - printer->fmt_line("void {}(Prop* prop);", method_name(naming::NRN_DESTRUCTOR_METHOD)); + printer->fmt_line("static void {}(Prop* prop);", method_name(naming::NRN_DESTRUCTOR_METHOD)); } void CodegenNeuronCppVisitor::print_nrn_destructor() { - printer->fmt_push_block("void {}(Prop* prop)", method_name(naming::NRN_DESTRUCTOR_METHOD)); + printer->fmt_push_block("static void {}(Prop* prop)", + method_name(naming::NRN_DESTRUCTOR_METHOD)); print_entrypoint_setup_code_from_prop(); for (const auto& rv: info.random_variables) { @@ -2041,7 +2044,7 @@ void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { const auto& args = nrn_current_parameters(); const auto& block = node.get_statement_block(); printer->add_newline(2); - printer->fmt_push_block("inline double nrn_current_{}({})", + printer->fmt_push_block("static inline double nrn_current_{}({})", info.mod_suffix, get_parameter_str(args)); printer->add_line("double current = 0.0;"); From 49eee16cb3ac4ace66dbd8cbc1841f549e9f6f1d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 17 Oct 2024 14:46:41 +0200 Subject: [PATCH 813/871] Fix FUNCTION_TABLE with POINT_PROCESS/ARTIFICIAL_CELL. (BlueBrain/nmodl#1513) NMODL Repo SHA: BlueBrain/nmodl@6bcaceac9560b1a6524158e0f6ffe1b1c08cff79 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 57 ++++++----- .../function_table/art_function_table.mod | 6 ++ .../function_table/function_table.inc | 8 ++ .../function_table/function_table.mod | 8 +- .../function_table/point_function_table.mod | 6 ++ .../function_table/test_function_table.py | 94 +++++++++++++------ 6 files changed, 120 insertions(+), 59 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/function_table/art_function_table.mod create mode 100644 test/nmodl/transpiler/usecases/function_table/function_table.inc create mode 100644 test/nmodl/transpiler/usecases/function_table/point_function_table.mod diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index d1c0073f69..dc8c392666 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -290,12 +290,10 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( return; } const auto block_name = function_or_procedure_block->get_node_name(); - if (info.point_process) { - printer->fmt_push_block("static double _hoc_{}(void* _vptr)", block_name); - } else if (wrapper_type == InterpreterWrapper::HOC) { - printer->fmt_push_block("static void _hoc_{}(void)", block_name); + if (wrapper_type == InterpreterWrapper::HOC) { + printer->fmt_push_block("{}", hoc_function_signature(block_name)); } else { - printer->fmt_push_block("static double _npy_{}(Prop* _prop)", block_name); + printer->fmt_push_block("{}", py_function_signature(block_name)); } printer->add_multi_line(R"CODE( double _r{}; @@ -416,20 +414,24 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { } // HOC - printer->fmt_push_block("static void {}()", hoc_function_name(name)); - printer->fmt_line("hoc_retpushx({}({}));", method_name(name), fmt::join(args, ", ")); + std::string return_statement = info.point_process ? "return _ret;" : "hoc_retpushx(_ret);"; + + printer->fmt_push_block("{}", hoc_function_signature(name)); + printer->fmt_line("double _ret = {}({});", method_name(name), fmt::join(args, ", ")); + printer->add_line(return_statement); printer->pop_block(); - printer->fmt_push_block("static void {}()", hoc_function_name(table_name)); - printer->fmt_line("hoc_retpushx({}());", method_name(table_name)); + printer->fmt_push_block("{}", hoc_function_signature(table_name)); + printer->fmt_line("double _ret = {}();", method_name(table_name)); + printer->add_line(return_statement); printer->pop_block(); // Python - printer->fmt_push_block("static double {}(Prop* _prop)", py_function_name(name)); + printer->fmt_push_block("{}", py_function_signature(name)); printer->fmt_line("return {}({});", method_name(name), fmt::join(args, ", ")); printer->pop_block(); - printer->fmt_push_block("static double {}(Prop* _prop)", py_function_name(table_name)); + printer->fmt_push_block("{}", py_function_signature(table_name)); printer->fmt_line("return {}();", method_name(table_name)); printer->pop_block(); } @@ -604,10 +606,10 @@ std::string CodegenNeuronCppVisitor::hoc_function_name( std::string CodegenNeuronCppVisitor::hoc_function_signature( const std::string& function_or_procedure_name) const { - return fmt::format("static {} {}(void{})", + return fmt::format("static {} {}({})", info.point_process ? "double" : "void", hoc_function_name(function_or_procedure_name), - info.point_process ? "*" : ""); + info.point_process ? "void * _vptr" : ""); } @@ -619,7 +621,8 @@ std::string CodegenNeuronCppVisitor::py_function_name( std::string CodegenNeuronCppVisitor::py_function_signature( const std::string& function_or_procedure_name) const { - return fmt::format("static double {}(Prop*)", py_function_name(function_or_procedure_name)); + return fmt::format("static double {}(Prop* _prop)", + py_function_name(function_or_procedure_name)); } @@ -1218,16 +1221,26 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { printer->add_line("{nullptr, nullptr}"); printer->decrease_indent(); printer->add_line("};"); + + + auto print_py_callable_reg = [this](const auto& callables, auto get_name) { + for (const auto& callable: callables) { + const auto name = get_name(callable); + printer->fmt_line("{{\"{}\", {}}},", name, py_function_name(name)); + } + }; + if (!info.point_process) { printer->push_block("static NPyDirectMechFunc npy_direct_func_proc[] ="); - for (const auto& procedure: info.procedures) { - const auto proc_name = procedure->get_node_name(); - printer->fmt_line("{{\"{}\", {}}},", proc_name, py_function_name(proc_name)); - } - for (const auto& function: info.functions) { - const auto func_name = function->get_node_name(); - printer->fmt_line("{{\"{}\", {}}},", func_name, py_function_name(func_name)); - } + print_py_callable_reg(info.procedures, + [](const auto& callable) { return callable->get_node_name(); }); + print_py_callable_reg(info.functions, + [](const auto& callable) { return callable->get_node_name(); }); + print_py_callable_reg(info.function_tables, + [](const auto& callable) { return callable->get_node_name(); }); + print_py_callable_reg(info.function_tables, [](const auto& callable) { + return "table_" + callable->get_node_name(); + }); printer->add_line("{nullptr, nullptr}"); printer->pop_block(";"); } diff --git a/test/nmodl/transpiler/usecases/function_table/art_function_table.mod b/test/nmodl/transpiler/usecases/function_table/art_function_table.mod new file mode 100644 index 0000000000..c330a4de7a --- /dev/null +++ b/test/nmodl/transpiler/usecases/function_table/art_function_table.mod @@ -0,0 +1,6 @@ +NEURON { + ARTIFICIAL_CELL art_function_table +} + +INCLUDE "function_table.inc" + diff --git a/test/nmodl/transpiler/usecases/function_table/function_table.inc b/test/nmodl/transpiler/usecases/function_table/function_table.inc new file mode 100644 index 0000000000..6f842b259d --- /dev/null +++ b/test/nmodl/transpiler/usecases/function_table/function_table.inc @@ -0,0 +1,8 @@ +FUNCTION_TABLE cnst1(v) +FUNCTION_TABLE cnst2(v, x) +FUNCTION_TABLE tau1(v) +FUNCTION_TABLE tau2(v, x) + +FUNCTION use_tau2(v, x) { + use_tau2 = tau2(v, x) +} diff --git a/test/nmodl/transpiler/usecases/function_table/function_table.mod b/test/nmodl/transpiler/usecases/function_table/function_table.mod index 5000c147fb..d5ace275bf 100644 --- a/test/nmodl/transpiler/usecases/function_table/function_table.mod +++ b/test/nmodl/transpiler/usecases/function_table/function_table.mod @@ -2,11 +2,5 @@ NEURON { SUFFIX function_table } -FUNCTION_TABLE cnst1(v) -FUNCTION_TABLE cnst2(v, x) -FUNCTION_TABLE tau1(v) -FUNCTION_TABLE tau2(v, x) +INCLUDE "function_table.inc" -FUNCTION use_tau2(v, x) { - use_tau2 = tau2(v, x) -} diff --git a/test/nmodl/transpiler/usecases/function_table/point_function_table.mod b/test/nmodl/transpiler/usecases/function_table/point_function_table.mod new file mode 100644 index 0000000000..5d9ffc73e0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/function_table/point_function_table.mod @@ -0,0 +1,6 @@ +NEURON { + POINT_PROCESS point_function_table +} + +INCLUDE "function_table.inc" + diff --git a/test/nmodl/transpiler/usecases/function_table/test_function_table.py b/test/nmodl/transpiler/usecases/function_table/test_function_table.py index cd8dfb7ea7..bb24b98eaa 100644 --- a/test/nmodl/transpiler/usecases/function_table/test_function_table.py +++ b/test/nmodl/transpiler/usecases/function_table/test_function_table.py @@ -4,43 +4,80 @@ import scipy -def test_constant_1d(): +def make_callable(inst, name, mech_name): + if inst is None: + return getattr(h, f"{name}_{mech_name}") + else: + return getattr(inst, f"{name}") + + +def make_callbacks(inst, name, mech_name): + set_table = make_callable(inst, f"table_{name}", mech_name) + eval_table = make_callable(inst, name, mech_name) + + return set_table, eval_table + + +def check_constant_1d(make_inst, mech_name): s = h.Section() s.insert("function_table") + inst = make_inst(s) + set_table, eval_table = make_callbacks(inst, "cnst1", mech_name) + c = 42.0 - h.table_cnst1_function_table(c) + set_table(c) for vv in np.linspace(-10.0, 10.0, 14): - np.testing.assert_equal(h.cnst1_function_table(vv), c) + np.testing.assert_equal(eval_table(vv), c) -def test_constant_2d(): +def check_constant_2d(make_inst, mech_name): s = h.Section() s.insert("function_table") + inst = make_inst(s) + set_table, eval_table = make_callbacks(inst, "cnst2", mech_name) + c = 42.0 - h.table_cnst2_function_table(c) + set_table(c) for vv in np.linspace(-10.0, 10.0, 7): for xx in np.linspace(-20.0, 10.0, 9): - np.testing.assert_equal(h.cnst2_function_table(vv, xx), c) + np.testing.assert_equal(eval_table(vv, xx), c) + + +def check_1d(make_inst, mech_name): + s = h.Section() + s.insert("function_table") + inst = make_inst(s) + set_table, eval_table = make_callbacks(inst, "tau1", mech_name) -def test_1d(): v = np.array([0.0, 1.0]) tau1 = np.array([1.0, 2.0]) - h.table_tau1_function_table(h.Vector(tau1), h.Vector(v)) + set_table(h.Vector(tau1), h.Vector(v)) for vv in np.linspace(v[0], v[-1], 20): expected = np.interp(vv, v, tau1) - actual = h.tau1_function_table(vv) + actual = eval_table(vv) np.testing.assert_approx_equal(actual, expected, significant=11) -def test_2d(): +def check_2d(make_inst, mech_name): + s = h.Section() + s.insert("function_table") + + inst = make_inst(s) + set_table, eval_table = make_callbacks(inst, "tau2", mech_name) + eval_use_table = make_callable(inst, "use_tau2", mech_name) + + if inst is None: + setdata = getattr(h, f"setdata_{mech_name}") + setdata(s(0.5)) + v = np.array([0.0, 1.0]) x = np.array([1.0, 2.0, 3.0]) @@ -50,36 +87,33 @@ def test_2d(): hoc_tau2 = h.Matrix(*tau2.shape) hoc_tau2.from_vector(h.Vector(tau2.transpose().reshape(-1))) - h.table_tau2_function_table( - hoc_tau2._ref_x[0][0], v.size, v[0], v[-1], x.size, x[0], x[-1] - ) + set_table(hoc_tau2._ref_x[0][0], v.size, v[0], v[-1], x.size, x[0], x[-1]) for vv in np.linspace(v[0], v[-1], 20): for xx in np.linspace(x[0], x[-1], 20): expected = scipy.interpolate.interpn((v, x), tau2, (vv, xx)) - actual = h.tau2_function_table(vv, xx) + actual = eval_table(vv, xx) + actual_indirect = eval_use_table(vv, xx) np.testing.assert_approx_equal(actual, expected, significant=11) + np.testing.assert_approx_equal(actual_indirect, expected, significant=11) -def test_use_table(): - s = h.Section() - s.insert("function_table") - - h.setdata_function_table(s(0.5)) +def test_function_table(): + variations = [ + (lambda s: None, "function_table"), + (lambda s: s(0.5).function_table, "function_table"), + (lambda s: h.point_function_table(s(0.5)), "point_function_table"), + (lambda s: h.art_function_table(s(0.5)), "art_function_table"), + ] - vv, xx = 0.33, 2.24 + for make_instance, mech_name in variations: + check_constant_1d(make_instance, mech_name) + check_constant_2d(make_instance, mech_name) - expected = h.tau2_function_table(vv, xx) - actual = h.use_tau2_function_table(vv, xx) - np.testing.assert_approx_equal(actual, expected, significant=11) + check_1d(make_instance, mech_name) + check_2d(make_instance, mech_name) if __name__ == "__main__": - test_constant_1d() - test_constant_2d() - - test_1d() - test_2d() - - test_use_table() + test_function_table() From 99f473d33203b55aa518953f71d94887c607d386 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 18 Oct 2024 16:12:28 +0200 Subject: [PATCH 814/871] Refactor printing FUNCTIONS. (BlueBrain/nmodl#1523) Adds a method to print the definitions for FUNCTIONs and PROCEDUREs. More precisely, the implementation function, not their wrappers for HOC/Python. NMODL Repo SHA: BlueBrain/nmodl@a24d77b4440c211a63b6d9ec0f97635e840ad412 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 ++++-- src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 8 ++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index dc8c392666..2d66b07b1c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2334,8 +2334,7 @@ void CodegenNeuronCppVisitor::print_g_unused() const { )CODE"); } - -void CodegenNeuronCppVisitor::print_compute_functions() { +void CodegenNeuronCppVisitor::print_function_definitions() { print_hoc_py_wrapper_function_definitions(); for (const auto& procedure: info.procedures) { print_procedure(*procedure); @@ -2346,7 +2345,9 @@ void CodegenNeuronCppVisitor::print_compute_functions() { for (const auto& function_table: info.function_tables) { print_function_tables(*function_table); } +} +void CodegenNeuronCppVisitor::print_compute_functions() { print_nrn_init(); print_nrn_cur(); print_nrn_state(); @@ -2377,6 +2378,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_functors_definitions(); print_global_variables_for_hoc(); print_thread_memory_callbacks(); + print_function_definitions(); print_compute_functions(); // only nrn_cur and nrn_state print_nrn_constructor(); print_nrn_destructor(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 794c7dfbfa..6aecf60fc8 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -225,6 +225,14 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_function_prototypes() override; + /** + * Print function and procedures prototype definitions. + * + * This includes the HOC/Python wrappers. + */ + void print_function_definitions(); + + /** * Print all `check_*` function declarations */ From 81c536deda6356328fa3114affafc043be147589 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 21 Oct 2024 09:30:07 +0200 Subject: [PATCH 815/871] SOLVE procedure. (BlueBrain/nmodl#1521) From inspecting NOCMODL generated code it seems that: SOLVE proc where `proc` is some procedure, simply calls the procedure and checks if it returned an error code. This PR only implements the call part and leaves the checking part for the future. NMODL Repo SHA: BlueBrain/nmodl@b2002759be131faac22aeecac246b6e0e40b8300 --- src/nmodl/visitors/solve_block_visitor.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/nmodl/visitors/solve_block_visitor.cpp b/src/nmodl/visitors/solve_block_visitor.cpp index 62ff7d0853..4412a0b443 100644 --- a/src/nmodl/visitors/solve_block_visitor.cpp +++ b/src/nmodl/visitors/solve_block_visitor.cpp @@ -9,6 +9,7 @@ #include "utils/fmt.h" #include +#include #include "ast/all.hpp" #include "codegen/codegen_naming.hpp" @@ -67,6 +68,14 @@ ast::SolutionExpression* SolveBlockVisitor::create_solution_expression( return new ast::SolutionExpression(solve_block.clone(), callback_expr); } + if (node_to_solve->get_node_type() == ast::AstNodeType::PROCEDURE_BLOCK) { + auto procedure_call = new ast::FunctionCall(solve_block.get_block_name()->clone(), {}); + auto statement = std::make_shared(procedure_call); + auto statement_block = new ast::StatementBlock({statement}); + + return new ast::SolutionExpression(solve_block.clone(), statement_block); + } + auto block_to_solve = node_to_solve->get_statement_block(); return new ast::SolutionExpression(solve_block.clone(), block_to_solve->clone()); } From 0de788d8ec6877ddb1532d676ea563f185ccce0f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 21 Oct 2024 09:35:06 +0200 Subject: [PATCH 816/871] Implement `stringutils::join_arguments`. (BlueBrain/nmodl#1524) Co-authored-by: Nicolas Cornu NMODL Repo SHA: BlueBrain/nmodl@8438f61ae1390d73f390ae26645a81bed4a8b537 --- src/nmodl/utils/string_utils.cpp | 11 +++++++++++ src/nmodl/utils/string_utils.hpp | 6 ++++++ .../transpiler/unit/utils/string_utils.cpp | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/src/nmodl/utils/string_utils.cpp b/src/nmodl/utils/string_utils.cpp index 51947eebd0..e804817a84 100644 --- a/src/nmodl/utils/string_utils.cpp +++ b/src/nmodl/utils/string_utils.cpp @@ -27,5 +27,16 @@ std::string to_string(double value, const std::string& format_spec) { return fmt::format(format_spec, value); } +std::string join_arguments(const std::string& lhs, const std::string& rhs) { + if (lhs.empty()) { + return rhs; + } else if (rhs.empty()) { + return lhs; + } else { + return fmt::format("{}", fmt::join({lhs, rhs}, ", ")); + } +} + + } // namespace stringutils } // namespace nmodl diff --git a/src/nmodl/utils/string_utils.hpp b/src/nmodl/utils/string_utils.hpp index 2b0d81d120..5099550d66 100644 --- a/src/nmodl/utils/string_utils.hpp +++ b/src/nmodl/utils/string_utils.hpp @@ -210,6 +210,12 @@ static inline bool starts_with(const std::string& haystack, const std::string& n */ std::string to_string(double value, const std::string& format_spec = "{:.16g}"); +/** Joint two (list of) arguments. + * + * The tricks is to not add a ',' when either side is empty. + */ +std::string join_arguments(const std::string& lhs, const std::string& rhs); + /** \} */ // end of utils } // namespace stringutils diff --git a/test/nmodl/transpiler/unit/utils/string_utils.cpp b/test/nmodl/transpiler/unit/utils/string_utils.cpp index e55631fc3b..1d4bf3b62c 100644 --- a/test/nmodl/transpiler/unit/utils/string_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/string_utils.cpp @@ -54,3 +54,21 @@ TEST_CASE("starts_with") { REQUIRE(!stringutils::starts_with("abcde", "abcde++")); } } + +TEST_CASE("join_arguments") { + SECTION("both empty") { + REQUIRE(stringutils::join_arguments("", "") == ""); + } + + SECTION("lhs emtpy") { + REQUIRE(stringutils::join_arguments("", "foo, bar") == "foo, bar"); + } + + SECTION("rhs empty") { + REQUIRE(stringutils::join_arguments("foo", "") == "foo"); + } + + SECTION("neither empty") { + REQUIRE(stringutils::join_arguments("foo", "bar") == "foo, bar"); + } +} From 91bce599e8a4f802e6cfdf30b959a7bc71df416c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 21 Oct 2024 18:03:32 +0200 Subject: [PATCH 817/871] Ignore '.pkl' and '.png' in 'test/usecases'. (BlueBrain/nmodl#1527) NMODL Repo SHA: BlueBrain/nmodl@a18950cf8b6979a4ef1274f5510ee08bff414777 --- test/nmodl/transpiler/usecases/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 test/nmodl/transpiler/usecases/.gitignore diff --git a/test/nmodl/transpiler/usecases/.gitignore b/test/nmodl/transpiler/usecases/.gitignore new file mode 100644 index 0000000000..f7f4af1434 --- /dev/null +++ b/test/nmodl/transpiler/usecases/.gitignore @@ -0,0 +1,2 @@ +*.pkl +*.png From e84b61cf73fe3b73c793c9d035ac4c675d7eeda4 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 22 Oct 2024 09:28:55 +0200 Subject: [PATCH 818/871] Rename 'shared_global'. (BlueBrain/nmodl#1528) NMODL Repo SHA: BlueBrain/nmodl@3cb122d78b585617babc14a7d5a6bcf8163c23c4 --- .../transpiler/usecases/global/simulate.py | 26 +++++++++---------- .../usecases/global/test_callables.py | 10 +++---- ...ead_variable.mod => threading_effects.mod} | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) rename test/nmodl/transpiler/usecases/global/{thread_variable.mod => threading_effects.mod} (95%) diff --git a/test/nmodl/transpiler/usecases/global/simulate.py b/test/nmodl/transpiler/usecases/global/simulate.py index 8de8df686f..793a7dce05 100644 --- a/test/nmodl/transpiler/usecases/global/simulate.py +++ b/test/nmodl/transpiler/usecases/global/simulate.py @@ -16,11 +16,11 @@ nseg = 1 s0 = h.Section() -s0.insert("shared_global") +s0.insert("threading_effects") s0.nseg = nseg s1 = h.Section() -s1.insert("shared_global") +s1.insert("threading_effects") s1.nseg = nseg pc = h.ParallelContext() @@ -29,8 +29,8 @@ pc.partition(1, h.SectionList([s1])) t = h.Vector().record(h._ref_t) -y0 = h.Vector().record(s0(0.5).shared_global._ref_y) -y1 = h.Vector().record(s1(0.5).shared_global._ref_y) +y0 = h.Vector().record(s0(0.5).threading_effects._ref_y) +y1 = h.Vector().record(s1(0.5).threading_effects._ref_y) # Bunch of arbitrary values: z0, z1 = 3.0, 4.0 @@ -38,25 +38,25 @@ g_w_init = 48.0 # Ensure that the two threads will set different value to `g_w`. -s0(0.5).shared_global.z = z0 -s1(0.5).shared_global.z = z1 +s0(0.5).threading_effects.z = z0 +s1(0.5).threading_effects.z = z1 -h.g_w_shared_global = g_w0 -assert h.g_w_shared_global == g_w0 +h.g_w_threading_effects = g_w0 +assert h.g_w_threading_effects == g_w0 h.stdinit() -assert h.g_w_shared_global == g_w_init -h.g_w_shared_global = g_w1 -assert h.g_w_shared_global == g_w1 +assert h.g_w_threading_effects == g_w_init +h.g_w_threading_effects = g_w1 +assert h.g_w_threading_effects == g_w1 h.continuerun(1.0 * ms) # Arguably the value is unspecified, but currently it's the value of # on thread 0. -assert h.g_w_shared_global == z0 +assert h.g_w_threading_effects == z0 t = np.array(t.as_numpy()) y0 = np.array(y0.as_numpy()) y1 = np.array(y1.as_numpy()) -w = h.g_w_shared_global +w = h.g_w_threading_effects # The solution is piecewise constant. These are the pieces: i0 = [0] diff --git a/test/nmodl/transpiler/usecases/global/test_callables.py b/test/nmodl/transpiler/usecases/global/test_callables.py index ef7b544112..ba0a38133d 100644 --- a/test/nmodl/transpiler/usecases/global/test_callables.py +++ b/test/nmodl/transpiler/usecases/global/test_callables.py @@ -3,21 +3,21 @@ def test_function(): s = h.Section() - s.insert("shared_global") + s.insert("threading_effects") h.finitialize() - assert s(0.5).shared_global.sum_arr() == 30.3 + assert s(0.5).threading_effects.sum_arr() == 30.3 def test_procedure(): s = h.Section() - s.insert("shared_global") + s.insert("threading_effects") h.finitialize() - s(0.5).shared_global.set_g_w(44.4) - assert h.g_w_shared_global == 44.4 + s(0.5).threading_effects.set_g_w(44.4) + assert h.g_w_threading_effects == 44.4 if __name__ == "__main__": diff --git a/test/nmodl/transpiler/usecases/global/thread_variable.mod b/test/nmodl/transpiler/usecases/global/threading_effects.mod similarity index 95% rename from test/nmodl/transpiler/usecases/global/thread_variable.mod rename to test/nmodl/transpiler/usecases/global/threading_effects.mod index 8383258b0e..185abca841 100644 --- a/test/nmodl/transpiler/usecases/global/thread_variable.mod +++ b/test/nmodl/transpiler/usecases/global/threading_effects.mod @@ -1,5 +1,5 @@ NEURON { - SUFFIX shared_global + SUFFIX threading_effects NONSPECIFIC_CURRENT il RANGE y, z GLOBAL g_v1, g_w, g_arr From 9fc020fb9d371dc1795013dc535ac82088214e35 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 22 Oct 2024 09:59:39 +0200 Subject: [PATCH 819/871] Avoid premature segfaults. (BlueBrain/nmodl#1529) In NOCMODL functions that only use global data can be called without an instance. NMODL Repo SHA: BlueBrain/nmodl@0c789ec9b6e92cd999abf182d196651c941c4101 --- .../codegen/codegen_neuron_cpp_visitor.cpp | 36 ++++++++++----- .../transpiler/usecases/global/global.mod | 16 +++++++ .../transpiler/usecases/global/parameter.mod | 11 +++++ .../usecases/global/test_without_instance.py | 44 +++++++++++++++++++ .../transpiler/usecases/global/top_local.mod | 8 ++++ 5 files changed, 104 insertions(+), 11 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/global/global.mod create mode 100644 test/nmodl/transpiler/usecases/global/parameter.mod create mode 100644 test/nmodl/transpiler/usecases/global/test_without_instance.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 2d66b07b1c..ebd5b5d79f 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -164,7 +164,7 @@ void CodegenNeuronCppVisitor::print_check_table_entrypoint() { printer->fmt_line("static void {}({})", table_thread_function_name(), get_parameter_str(args)); printer->push_block(); printer->add_line("_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml, _type};"); - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(&_lmc);", info.mod_suffix); if (!info.artificial_cell) { printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml);", info.mod_suffix); } @@ -259,7 +259,9 @@ void CodegenNeuronCppVisitor::print_function_or_procedure( } if (!info.artificial_cell) { - printer->add_line("auto v = node_data.node_voltages[node_data.nodeindices[id]];"); + printer->add_line( + "double v = node_data.node_voltages ? " + "node_data.node_voltages[node_data.nodeindices[id]] : 0.0;"); } print_statement_block(*node.get_statement_block(), false, false); @@ -351,7 +353,9 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( prop_name = "_prop"; } - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}({} ? &_lmc : nullptr);", + info.mod_suffix, + prop_name); if (!info.artificial_cell) { printer->fmt_line("auto node_data = make_node_data_{}({});", info.mod_suffix, prop_name); } @@ -868,7 +872,7 @@ void CodegenNeuronCppVisitor::print_neuron_includes() { } -void CodegenNeuronCppVisitor::print_sdlists_init([[maybe_unused]] bool print_initializers) { +void CodegenNeuronCppVisitor::print_sdlists_init(bool /* print_initializers */) { /// _initlists() should only be called once by the mechanism registration function /// (__reg()) printer->add_newline(2); @@ -1526,9 +1530,14 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini void CodegenNeuronCppVisitor::print_make_instance() const { printer->add_newline(2); - printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range& _lmc)", + printer->fmt_push_block("static {} make_instance_{}(_nrn_mechanism_cache_range* _lmc)", instance_struct(), info.mod_suffix); + + printer->push_block("if(_lmc == nullptr)"); + printer->fmt_line("return {}();", instance_struct()); + printer->pop_block_nl(2); + printer->fmt_push_block("return {}", instance_struct()); std::vector make_instance_args; @@ -1545,9 +1554,9 @@ void CodegenNeuronCppVisitor::print_make_instance() const { const auto& float_var = codegen_float_variables[i]; if (float_var->is_array()) { make_instance_args.push_back( - fmt::format("_lmc.template data_array_ptr<{}, {}>()", i, float_var->get_length())); + fmt::format("_lmc->template data_array_ptr<{}, {}>()", i, float_var->get_length())); } else { - make_instance_args.push_back(fmt::format("_lmc.template fpfield_ptr<{}>()", i)); + make_instance_args.push_back(fmt::format("_lmc->template fpfield_ptr<{}>()", i)); } } @@ -1561,7 +1570,7 @@ void CodegenNeuronCppVisitor::print_make_instance() const { } else if (var.is_vdata) { return ""; } else { - return fmt::format("_lmc.template dptr_field_ptr<{}>()", i); + return fmt::format("_lmc->template dptr_field_ptr<{}>()", i); } }(); if (variable != "") { @@ -1611,6 +1620,11 @@ void CodegenNeuronCppVisitor::print_make_node_data() const { printer->fmt_push_block("static {} make_node_data_{}(Prop * _prop)", node_data_struct(), info.mod_suffix); + + printer->push_block("if(!_prop)"); + printer->fmt_line("return {}();", node_data_struct()); + printer->pop_block_nl(2); + printer->add_line("static std::vector node_index{0};"); printer->add_line("Node* _node = _nrn_mechanism_access_node(_prop);"); @@ -1684,7 +1698,7 @@ void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_memb_list() { printer->add_line( "_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _ml_arg->type()};"); - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(&_lmc);", info.mod_suffix); if (!info.artificial_cell) { printer->fmt_line("auto node_data = make_node_data_{}(*nt, *_ml_arg);", info.mod_suffix); } @@ -1702,7 +1716,7 @@ void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_prop() { printer->add_line("_nrn_mechanism_cache_instance _lmc{prop};"); printer->add_line("const size_t id = 0;"); - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(prop ? &_lmc : nullptr);", info.mod_suffix); if (!info.artificial_cell) { printer->fmt_line("auto node_data = make_node_data_{}(prop);", info.mod_suffix); } @@ -2488,7 +2502,7 @@ void CodegenNeuronCppVisitor::print_net_receive_common_code() { printer->add_line("auto * nt = static_cast(_pnt->_vnt);"); printer->add_line("auto * _ppvar = _nrn_mechanism_access_dparam(_pnt->prop);"); - printer->fmt_line("auto inst = make_instance_{}(_lmc);", info.mod_suffix); + printer->fmt_line("auto inst = make_instance_{}(&_lmc);", info.mod_suffix); if (!info.artificial_cell) { printer->fmt_line("auto node_data = make_node_data_{}(_pnt->prop);", info.mod_suffix); } diff --git a/test/nmodl/transpiler/usecases/global/global.mod b/test/nmodl/transpiler/usecases/global/global.mod new file mode 100644 index 0000000000..3f2e17a148 --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/global.mod @@ -0,0 +1,16 @@ +NEURON { + SUFFIX global + GLOBAL gbl +} + +ASSIGNED { + gbl +} + +FUNCTION get_gbl() { + get_gbl = gbl +} + +PROCEDURE set_gbl(value) { + gbl = value +} diff --git a/test/nmodl/transpiler/usecases/global/parameter.mod b/test/nmodl/transpiler/usecases/global/parameter.mod new file mode 100644 index 0000000000..4bbab617cb --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/parameter.mod @@ -0,0 +1,11 @@ +NEURON { + SUFFIX parameter +} + +PARAMETER { + gbl = 42.0 +} + +FUNCTION get_gbl() { + get_gbl = gbl +} diff --git a/test/nmodl/transpiler/usecases/global/test_without_instance.py b/test/nmodl/transpiler/usecases/global/test_without_instance.py new file mode 100644 index 0000000000..3f32af085a --- /dev/null +++ b/test/nmodl/transpiler/usecases/global/test_without_instance.py @@ -0,0 +1,44 @@ +from neuron import h, gui + + +def make_accessors(mech_name, read_only): + get = getattr(h, f"get_gbl_{mech_name}") + + if read_only: + return get, None + + set = getattr(h, f"set_gbl_{mech_name}") + return get, set + + +def check_write_read_cycle(mech_name, read_only=False): + get, set = make_accessors(mech_name, read_only) + + if read_only: + expected = 42.0 + else: + expected = 278.045 + set(expected) + + actual = get() + assert ( + actual == expected + ), f"{actual = }, {expected = }, delta = {actual - expected}" + + +def test_top_local(): + check_write_read_cycle("top_local") + + +def test_global(): + check_write_read_cycle("global") + + +def test_parameter(): + check_write_read_cycle("parameter", True) + + +if __name__ == "__main__": + test_top_local() + test_global() + test_parameter() diff --git a/test/nmodl/transpiler/usecases/global/top_local.mod b/test/nmodl/transpiler/usecases/global/top_local.mod index ad4d36e7e4..f40789960e 100644 --- a/test/nmodl/transpiler/usecases/global/top_local.mod +++ b/test/nmodl/transpiler/usecases/global/top_local.mod @@ -23,3 +23,11 @@ BREAKPOINT { y = gbl il = 0.0000001 * (v - 10.0) } + +FUNCTION get_gbl() { + get_gbl = gbl +} + +PROCEDURE set_gbl(value) { + gbl = value +} From 08465a90386f8080d27a07972ed788e4963025ab Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 22 Oct 2024 10:46:45 +0200 Subject: [PATCH 820/871] Refactor HOC/Python wrapper printing. (BlueBrain/nmodl#1522) * Split HOC/Py wrapper code printing. * Move printing return variable. * Rename HOC/Py wrapper function name. * Extract HOC/Py signature. NMODL Repo SHA: BlueBrain/nmodl@380207ba339ca0b9f6d868d578ce8b723eedf94f --- .../codegen/codegen_neuron_cpp_visitor.cpp | 86 ++++++++++++------- .../codegen/codegen_neuron_cpp_visitor.hpp | 32 ++++++- 2 files changed, 85 insertions(+), 33 deletions(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index ebd5b5d79f..e921902594 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -284,21 +284,43 @@ void CodegenNeuronCppVisitor::print_function_procedure_helper(const ast::Block& } } - -void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( +void CodegenNeuronCppVisitor::print_hoc_py_wrapper_call_impl( const ast::Block* function_or_procedure_block, InterpreterWrapper wrapper_type) { - if (info.point_process && wrapper_type == InterpreterWrapper::Python) { - return; - } const auto block_name = function_or_procedure_block->get_node_name(); - if (wrapper_type == InterpreterWrapper::HOC) { - printer->fmt_push_block("{}", hoc_function_signature(block_name)); + + const auto get_func_call_str = [&]() { + const auto& params = function_or_procedure_block->get_parameters(); + const auto func_proc_name = block_name + "_" + info.mod_suffix; + auto func_call = fmt::format("{}({}", func_proc_name, internal_method_arguments()); + for (int i = 0; i < params.size(); ++i) { + func_call.append(fmt::format(", *getarg({})", i + 1)); + } + func_call.append(")"); + return func_call; + }; + + printer->add_line("double _r = 0.0;"); + if (function_or_procedure_block->is_function_block()) { + printer->add_indent(); + printer->fmt_text("_r = {};", get_func_call_str()); + printer->add_newline(); } else { - printer->fmt_push_block("{}", py_function_signature(block_name)); + printer->add_line("_r = 1.;"); + printer->fmt_line("{};", get_func_call_str()); } + if (info.point_process || wrapper_type != InterpreterWrapper::HOC) { + printer->add_line("return(_r);"); + } else if (wrapper_type == InterpreterWrapper::HOC) { + printer->add_line("hoc_retpushx(_r);"); + } +} + +void CodegenNeuronCppVisitor::print_hoc_py_wrapper_setup( + const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type) { + const auto block_name = function_or_procedure_block->get_node_name(); printer->add_multi_line(R"CODE( - double _r{}; Datum* _ppvar; Datum* _thread; NrnThread* nt; @@ -369,29 +391,31 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( table_update_function_name(block_name), internal_method_arguments()); } - const auto get_func_call_str = [&]() { - const auto& params = function_or_procedure_block->get_parameters(); - const auto func_proc_name = block_name + "_" + info.mod_suffix; - auto func_call = fmt::format("{}({}", func_proc_name, internal_method_arguments()); - for (int i = 0; i < params.size(); ++i) { - func_call.append(fmt::format(", *getarg({})", i + 1)); - } - func_call.append(")"); - return func_call; - }; - if (function_or_procedure_block->is_function_block()) { - printer->add_indent(); - printer->fmt_text("_r = {};", get_func_call_str()); - printer->add_newline(); +} + + +std::string CodegenNeuronCppVisitor::hoc_py_wrapper_signature( + const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type) { + const auto block_name = function_or_procedure_block->get_node_name(); + if (wrapper_type == InterpreterWrapper::HOC) { + return hoc_function_signature(block_name); } else { - printer->add_line("_r = 1.;"); - printer->fmt_line("{};", get_func_call_str()); + return py_function_signature(block_name); } - if (info.point_process || wrapper_type != InterpreterWrapper::HOC) { - printer->add_line("return(_r);"); - } else if (wrapper_type == InterpreterWrapper::HOC) { - printer->add_line("hoc_retpushx(_r);"); +} + +void CodegenNeuronCppVisitor::print_hoc_py_wrapper(const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type) { + if (info.point_process && wrapper_type == InterpreterWrapper::Python) { + return; } + + printer->push_block(hoc_py_wrapper_signature(function_or_procedure_block, wrapper_type)); + + print_hoc_py_wrapper_setup(function_or_procedure_block, wrapper_type); + print_hoc_py_wrapper_call_impl(function_or_procedure_block, wrapper_type); + printer->pop_block(); } @@ -399,8 +423,8 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_body( void CodegenNeuronCppVisitor::print_hoc_py_wrapper_function_definitions() { auto print_wrappers = [this](const auto& callables) { for (const auto& callable: callables) { - print_hoc_py_wrapper_function_body(callable, InterpreterWrapper::HOC); - print_hoc_py_wrapper_function_body(callable, InterpreterWrapper::Python); + print_hoc_py_wrapper(callable, InterpreterWrapper::HOC); + print_hoc_py_wrapper(callable, InterpreterWrapper::Python); } }; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 6aecf60fc8..3168dda6e2 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -252,9 +252,37 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_function_procedure_helper(const ast::Block& node) override; - void print_hoc_py_wrapper_function_body(const ast::Block* function_or_procedure_block, - InterpreterWrapper wrapper_type); + /** Print the wrapper for calling FUNCION/PROCEDURES from HOC/Py. + * + * Usually the function is made up of the following parts: + * * Print setup code `inst`, etc. + * * Print code to call the function and return. + */ + void print_hoc_py_wrapper(const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type); + + /** Print the setup code for HOC/Py wrapper. + */ + void print_hoc_py_wrapper_setup(const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type); + + /** Print the code that calls the impl from the HOC/Py wrapper. + */ + void print_hoc_py_wrapper_call_impl(const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type); + + /** Return the wrapper signature. + * + * Everything without the `{` or `;`. Roughly, as an example: + * (, ) + * + * were ` is the list of arguments required by the + * codegen to be passed along, while are the arguments of + * of the function as they appear in the MOD file. + */ + std::string hoc_py_wrapper_signature(const ast::Block* function_or_procedure_block, + InterpreterWrapper wrapper_type); void print_hoc_py_wrapper_function_definitions(); From 7a687911b129eb1f50f37c41a4c75545d34e5eeb Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 22 Oct 2024 13:20:02 +0200 Subject: [PATCH 821/871] Support `SUFFIX nothing`. (BlueBrain/nmodl#1525) NMODL Repo SHA: BlueBrain/nmodl@edaa090288c3180d7d79e9728d763a0ff28dd5d6 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 8 ++- .../codegen/codegen_neuron_cpp_visitor.cpp | 71 ++++++++++++++++--- .../codegen/codegen_neuron_cpp_visitor.hpp | 13 +++- .../usecases/suffix/suffix_nothing.mod | 11 +++ .../usecases/suffix/suffix_nothing_again.mod | 7 ++ .../usecases/suffix/test_suffix_nothing.py | 12 ++++ 6 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/suffix/suffix_nothing.mod create mode 100644 test/nmodl/transpiler/usecases/suffix/suffix_nothing_again.mod create mode 100644 test/nmodl/transpiler/usecases/suffix/test_suffix_nothing.py diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index f1d8158728..312ea2a89f 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -513,8 +513,9 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { printer->add_text(function_name, '('); if (defined_method(name)) { - printer->add_text(internal_method_arguments()); - if (!arguments.empty()) { + auto internal_args = internal_method_arguments(); + printer->add_text(internal_args); + if (!arguments.empty() && !internal_args.empty()) { printer->add_text(", "); } } @@ -1436,6 +1437,9 @@ void CodegenCppVisitor::setup(const Program& node) { info.mod_suffix = std::filesystem::path(mod_filename).stem().string(); } info.rsuffix = info.point_process ? "" : "_" + info.mod_suffix; + if (info.mod_suffix == "nothing") { + info.rsuffix = ""; + } if (!info.vectorize) { logger->warn( diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e921902594..97004d601d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -258,7 +258,7 @@ void CodegenNeuronCppVisitor::print_function_or_procedure( printer->fmt_line("int ret_{} = 0;", name); } - if (!info.artificial_cell) { + if (info.mod_suffix != "nothing" && !info.artificial_cell) { printer->add_line( "double v = node_data.node_voltages ? " "node_data.node_voltages[node_data.nodeindices[id]] : 0.0;"); @@ -292,12 +292,17 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_call_impl( const auto get_func_call_str = [&]() { const auto& params = function_or_procedure_block->get_parameters(); const auto func_proc_name = block_name + "_" + info.mod_suffix; - auto func_call = fmt::format("{}({}", func_proc_name, internal_method_arguments()); + std::vector args; + args.reserve(params.size()); for (int i = 0; i < params.size(); ++i) { - func_call.append(fmt::format(", *getarg({})", i + 1)); + args.push_back(fmt::format("*getarg({})", i + 1)); } - func_call.append(")"); - return func_call; + + auto internal_args = internal_method_arguments(); + return fmt::format("{}({})", + func_proc_name, + stringutils::join_arguments(internal_args, + fmt::format("{}", fmt::join(args, ", ")))); }; printer->add_line("double _r = 0.0;"); @@ -319,6 +324,10 @@ void CodegenNeuronCppVisitor::print_hoc_py_wrapper_call_impl( void CodegenNeuronCppVisitor::print_hoc_py_wrapper_setup( const ast::Block* function_or_procedure_block, InterpreterWrapper wrapper_type) { + if (info.mod_suffix == "nothing") { + return; + } + const auto block_name = function_or_procedure_block->get_node_name(); printer->add_multi_line(R"CODE( Datum* _ppvar; @@ -563,6 +572,10 @@ std::string CodegenNeuronCppVisitor::internal_method_arguments() { CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internal_method_parameters() { + if (info.mod_suffix == "nothing") { + return {}; + } + ParamVector params; params.emplace_back("", "_nrn_mechanism_cache_range&", "", "_lmc"); params.emplace_back("", fmt::format("{}&", instance_struct()), "", "inst"); @@ -1228,7 +1241,9 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { {"has_loc", _hoc_has_loc}, {"get_loc", _hoc_get_loc_pnt},)CODE"); } else { - printer->fmt_line("{{\"setdata_{}\", _hoc_setdata}},", info.mod_suffix); + if (info.mod_suffix != "nothing") { + printer->fmt_line("{{\"setdata_{}\", _hoc_setdata}},", info.mod_suffix); + } } auto print_callable_reg = [this](const auto& callables, auto get_name) { @@ -1276,8 +1291,16 @@ void CodegenNeuronCppVisitor::print_global_variables_for_hoc() { void CodegenNeuronCppVisitor::print_mechanism_register() { printer->add_newline(2); - printer->add_line("/** register channel with the simulator */"); printer->fmt_push_block("extern \"C\" void _{}_reg()", info.mod_file); + if (info.mod_suffix == "nothing") { + print_mechanism_register_nothing(); + } else { + print_mechanism_register_regular(); + } + printer->pop_block(); +} + +void CodegenNeuronCppVisitor::print_mechanism_register_regular() { printer->add_line("_initlists();"); printer->add_newline(); @@ -1468,8 +1491,10 @@ void CodegenNeuronCppVisitor::print_mechanism_register() { printer->fmt_line("{}._morphology_sym = hoc_lookup(\"morphology\");", global_struct_instance()); } +} - printer->pop_block(); +void CodegenNeuronCppVisitor::print_mechanism_register_nothing() { + printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc);"); } @@ -1720,6 +1745,10 @@ void CodegenNeuronCppVisitor::print_initial_block(const InitialBlock* node) { } void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_memb_list() { + if (info.mod_suffix == "nothing") { + return; + } + printer->add_line( "_nrn_mechanism_cache_range _lmc{_sorted_token, *nt, *_ml_arg, _ml_arg->type()};"); printer->fmt_line("auto inst = make_instance_{}(&_lmc);", info.mod_suffix); @@ -1736,6 +1765,10 @@ void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_memb_list() { void CodegenNeuronCppVisitor::print_entrypoint_setup_code_from_prop() { + if (info.mod_suffix == "nothing") { + return; + } + printer->add_line("Datum* _ppvar = _nrn_mechanism_access_dparam(prop);"); printer->add_line("_nrn_mechanism_cache_instance _lmc{prop};"); printer->add_line("const size_t id = 0;"); @@ -2394,8 +2427,7 @@ void CodegenNeuronCppVisitor::print_compute_functions() { print_net_init(); } - -void CodegenNeuronCppVisitor::print_codegen_routines() { +void CodegenNeuronCppVisitor::print_codegen_routines_regular() { print_backend_info(); print_headers_include(); print_macro_definitions(); @@ -2425,6 +2457,25 @@ void CodegenNeuronCppVisitor::print_codegen_routines() { print_namespace_stop(); } +void CodegenNeuronCppVisitor::print_codegen_routines_nothing() { + print_backend_info(); + print_headers_include(); + print_namespace_start(); + print_function_prototypes(); + print_global_variables_for_hoc(); + print_function_definitions(); + print_mechanism_register(); + print_namespace_stop(); +} + +void CodegenNeuronCppVisitor::print_codegen_routines() { + if (info.mod_suffix == "nothing") { + print_codegen_routines_nothing(); + } else { + print_codegen_routines_regular(); + } +} + void CodegenNeuronCppVisitor::print_ion_variable() { throw std::runtime_error("Not implemented."); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 3168dda6e2..de56f061ca 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -530,12 +530,17 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_mechanism_register() override; + /** Function body for anything not SUFFIX nothing. */ + void print_mechanism_register_regular(); + + /** Function body for SUFFIX nothing. */ + void print_mechanism_register_nothing(); + /** * Print thread variable (de-)initialization functions. */ void print_thread_memory_callbacks(); - /** * Print common code for global functions like nrn_init, nrn_cur and nrn_state * \param type The target backend code block type @@ -748,6 +753,12 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ void print_codegen_routines() override; + /** Anything not SUFFIX nothing. */ + void print_codegen_routines_regular(); + + /** SUFFIX nothing is special. */ + void print_codegen_routines_nothing(); + void print_ion_variable() override; diff --git a/test/nmodl/transpiler/usecases/suffix/suffix_nothing.mod b/test/nmodl/transpiler/usecases/suffix/suffix_nothing.mod new file mode 100644 index 0000000000..b4bd9cf7f8 --- /dev/null +++ b/test/nmodl/transpiler/usecases/suffix/suffix_nothing.mod @@ -0,0 +1,11 @@ +NEURON { + SUFFIX nothing +} + +FUNCTION forty_two_plus_x(x) { + forty_two_plus_x = 42.0 + x +} + +FUNCTION twice_forty_two_plus_x(x) { + twice_forty_two_plus_x = 2.0 * forty_two_plus_x(x) +} diff --git a/test/nmodl/transpiler/usecases/suffix/suffix_nothing_again.mod b/test/nmodl/transpiler/usecases/suffix/suffix_nothing_again.mod new file mode 100644 index 0000000000..7054e5dacf --- /dev/null +++ b/test/nmodl/transpiler/usecases/suffix/suffix_nothing_again.mod @@ -0,0 +1,7 @@ +NEURON { + SUFFIX nothing +} + +FUNCTION forty_three() { + forty_three = 43.0 +} diff --git a/test/nmodl/transpiler/usecases/suffix/test_suffix_nothing.py b/test/nmodl/transpiler/usecases/suffix/test_suffix_nothing.py new file mode 100644 index 0000000000..9cbc6d8463 --- /dev/null +++ b/test/nmodl/transpiler/usecases/suffix/test_suffix_nothing.py @@ -0,0 +1,12 @@ +from neuron import h, gui + + +def test_nothing(): + x = 15.0 + assert h.forty_two_plus_x(x) == 42.0 + x + assert h.twice_forty_two_plus_x(x) == 2.0 * (42.0 + x) + assert h.forty_three() == 43.0 + + +if __name__ == "__main__": + test_nothing() From 72c2465e62246045c227478b3cfe4cbb5e7c63fa Mon Sep 17 00:00:00 2001 From: JCGoran Date: Mon, 28 Oct 2024 08:57:53 +0100 Subject: [PATCH 822/871] Add `CvodeBlock` and `CvodeVisitor` (BlueBrain/nmodl#1467) NMODL Repo SHA: BlueBrain/nmodl@6b6a63074d512348df62ab2de49b16468a519e85 --- docs/nmodl/transpiler/contents/cvode.rst | 75 +++++++ docs/nmodl/transpiler/index.rst | 1 + src/nmodl/language/code_generator.cmake | 1 + src/nmodl/language/codegen.yaml | 20 ++ src/nmodl/main.cpp | 11 + src/nmodl/pybind/wrapper.cpp | 49 +++- src/nmodl/pybind/wrapper.hpp | 14 ++ src/nmodl/visitors/CMakeLists.txt | 1 + src/nmodl/visitors/cvode_visitor.cpp | 225 +++++++++++++++++++ src/nmodl/visitors/cvode_visitor.hpp | 40 ++++ src/nmodl/visitors/sympy_solver_visitor.cpp | 3 + src/nmodl/visitors/sympy_solver_visitor.hpp | 1 + test/nmodl/transpiler/unit/CMakeLists.txt | 1 + test/nmodl/transpiler/unit/visitor/cvode.cpp | 89 ++++++++ 14 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 docs/nmodl/transpiler/contents/cvode.rst create mode 100644 src/nmodl/visitors/cvode_visitor.cpp create mode 100644 src/nmodl/visitors/cvode_visitor.hpp create mode 100644 test/nmodl/transpiler/unit/visitor/cvode.cpp diff --git a/docs/nmodl/transpiler/contents/cvode.rst b/docs/nmodl/transpiler/contents/cvode.rst new file mode 100644 index 0000000000..6179d824c2 --- /dev/null +++ b/docs/nmodl/transpiler/contents/cvode.rst @@ -0,0 +1,75 @@ +Variable timestep integration (CVODE) +===================================== + +As opposed to fixed timestep integration, variable timestep integration (CVODE +in NEURON parlance) uses the SUNDIALS package to solve a ``DERIVATIVE`` or +``KINETIC`` block using a variable timestep. This allows for faster computation +times if the function in question does not vary too wildly. + +Implementation in NMODL +----------------------- + +The code generation for CVODE is activated only if exactly one of the following +is satisfied: + +1. there is one ``KINETIC`` block in the mod file +2. there is one ``DERIVATIVE`` block in the mod file +3. a ``PROCEDURE`` block is solved with the ``after_cvode``, ``cvode_t``, or + ``cvode_t_v`` methods + +In NMODL, all ``KINETIC`` blocks are internally first converted to +``DERIVATIVE`` blocks. The ``DERIVATIVE`` block is then converted to a +``CVODE`` block, which contains two parts; the first part contains the update +step for non-stiff systems (functional iteration), while the second part +contains the update step for stiff systems (additional step using the +Jacobian). For more information, see `CVODES documentation`_, eqs. (4.8) and +(4.9). Given a ``DERIVATIVE`` block of the form: + +.. _CVODES documentation: https://sundials.readthedocs.io/en/latest/cvodes/Mathematics_link.html + +.. code-block:: + + DERIVATIVE state { + x_i' = f(x_1, ..., x_n) + } + +the structure of the ``CVODE`` block is then roughly: + +.. code-block:: + + CVODE state[n] { + Dx_i = f_i(x_1, ..., x_n) + }{ + Dx_i = Dx_i / (1 - dt * J_ii(f)) + } + +where ``N`` is the total number of ODEs to solve, and ``J_ii(f)`` is the +diagonal part of the Jacobian, i.e. + +.. math:: + + J_{ii}(f) = \frac{ \partial f_i(x_1, \ldots, x_n) }{\partial x_i} + +As an example, consider the following ``DERIVATIVE`` block: + +.. code-block:: + + DERIVATIVE state { + X' = - X + } + +Where ``X`` is a ``STATE`` variable with some initial value, specified in the +``INITIAL`` block. The corresponding ``CVODE`` block is then: + +.. code-block:: + + CVODE state[1] { + DX = - X + }{ + DX = DX / (1 - dt * (-1)) + } + + +**NOTE**: in case there are ``CONSERVE`` statements in ``KINETIC`` blocks, as +they are merely hints to NMODL, and have no impact on the results, they are +removed from ``CVODE`` blocks before the codegen stage. diff --git a/docs/nmodl/transpiler/index.rst b/docs/nmodl/transpiler/index.rst index 06574b8cec..8ed98af9b0 100644 --- a/docs/nmodl/transpiler/index.rst +++ b/docs/nmodl/transpiler/index.rst @@ -25,6 +25,7 @@ About NMODL contents/cable_equations contents/globals contents/longitudinal_diffusion + contents/cvode .. toctree:: :maxdepth: 3 diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index bfb5fd933c..ae8d5e292e 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -72,6 +72,7 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/constant_statement.hpp ${PROJECT_BINARY_DIR}/src/ast/constant_var.hpp ${PROJECT_BINARY_DIR}/src/ast/constructor_block.hpp + ${PROJECT_BINARY_DIR}/src/ast/cvode_block.hpp ${PROJECT_BINARY_DIR}/src/ast/define.hpp ${PROJECT_BINARY_DIR}/src/ast/derivative_block.hpp ${PROJECT_BINARY_DIR}/src/ast/derivimplicit_callback.hpp diff --git a/src/nmodl/language/codegen.yaml b/src/nmodl/language/codegen.yaml index 12bd660862..c7ce97c5b8 100644 --- a/src/nmodl/language/codegen.yaml +++ b/src/nmodl/language/codegen.yaml @@ -88,6 +88,26 @@ - finalize_block: brief: "Statement block to be executed after calling linear solver" type: StatementBlock + - CvodeBlock: + nmodl: "CVODE_BLOCK " + members: + - name: + brief: "Name of the block" + type: Name + node_name: true + suffix: {value: " "} + - n_odes: + brief: "number of ODEs to solve" + type: Integer + prefix: {value: "["} + suffix: {value: "]"} + - non_stiff_block: + brief: "Block with statements of the form Dvar = f(var), used for updating non-stiff systems" + type: StatementBlock + - stiff_block: + brief: "Block with statements of the form Dvar = Dvar / (1 - dt * J(f)), used for updating stiff systems" + type: StatementBlock + brief: "Represents a block used for variable timestep integration (CVODE) of DERIVATIVE blocks" - LongitudinalDiffusionBlock: brief: "Extracts information required for LONGITUDINAL_DIFFUSION for each KINETIC block." nmodl: "LONGITUDINAL_DIFFUSION_BLOCK" diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index ba9de4a19d..b63b7b0c70 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -25,6 +25,7 @@ #include "visitors/after_cvode_to_cnexp_visitor.hpp" #include "visitors/ast_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" +#include "visitors/cvode_visitor.hpp" #include "visitors/function_callpath_visitor.hpp" #include "visitors/global_var_visitor.hpp" #include "visitors/implicit_argument_visitor.hpp" @@ -516,6 +517,8 @@ int run_nmodl(int argc, const char* argv[]) { enable_sympy(solver_exists(*ast, "derivimplicit"), "'SOLVE ... METHOD derivimplicit'"); enable_sympy(node_exists(*ast, ast::AstNodeType::LINEAR_BLOCK), "'LINEAR' block"); + enable_sympy(node_exists(*ast, ast::AstNodeType::DERIVATIVE_BLOCK), + "'DERIVATIVE' block"); enable_sympy(node_exists(*ast, ast::AstNodeType::NON_LINEAR_BLOCK), "'NONLINEAR' block"); enable_sympy(solver_exists(*ast, "sparse"), "'SOLVE ... METHOD sparse'"); @@ -526,6 +529,14 @@ int run_nmodl(int argc, const char* argv[]) { nmodl::pybind_wrappers::EmbeddedPythonLoader::get_instance() .api() .initialize_interpreter(); + + if (neuron_code) { + logger->info("Running CVODE visitor"); + CvodeVisitor().visit_program(*ast); + SymtabVisitor(update_symtab).visit_program(*ast); + ast_to_nmodl(*ast, filepath("cvode")); + } + if (sympy_conductance) { logger->info("Running sympy conductance visitor"); SympyConductanceVisitor().visit_program(*ast); diff --git a/src/nmodl/pybind/wrapper.cpp b/src/nmodl/pybind/wrapper.cpp index 32c390736c..d59b579d97 100644 --- a/src/nmodl/pybind/wrapper.cpp +++ b/src/nmodl/pybind/wrapper.cpp @@ -9,7 +9,8 @@ #include "codegen/codegen_naming.hpp" #include "pybind/pyembed.hpp" - +#include +#include #include #include @@ -186,6 +187,49 @@ except Exception as e: return {std::move(solution), std::move(exception_message)}; } +std::tuple call_diff2c( + const std::string& expression, + const std::pair>& variable, + const std::unordered_set& indexed_vars) { + std::string statements; + // only indexed variables require special treatment + for (const auto& var: indexed_vars) { + statements += fmt::format("_allvars.append(sp.IndexedBase('{}', shape=[1]))\n", var); + } + auto [name, property] = variable; + if (property.has_value()) { + name = fmt::format("sp.IndexedBase('{}', shape=[1])", name); + statements += fmt::format("_allvars.append({})", name); + } else { + name = fmt::format("'{}'", name); + } + auto locals = py::dict("expression"_a = expression); + std::string script = + fmt::format(R"( +_allvars = [] +{} +variable = {} +exception_message = "" +try: + solution = differentiate2c(expression, + variable, + _allvars, + ) +except Exception as e: + # if we fail, fail silently and return empty string + solution = "" + exception_message = str(e) +)", + statements, + property.has_value() ? fmt::format("{}[{}]", name, property.value()) : name); + + py::exec(nmodl::pybind_wrappers::ode_py + script, locals); + + auto solution = locals["solution"].cast(); + auto exception_message = locals["exception_message"].cast(); + + return {std::move(solution), std::move(exception_message)}; +} void initialize_interpreter_func() { pybind11::initialize_interpreter(true); @@ -203,7 +247,8 @@ NMODL_EXPORT pybind_wrap_api nmodl_init_pybind_wrapper_api() noexcept { &call_solve_nonlinear_system, &call_solve_linear_system, &call_diffeq_solver, - &call_analytic_diff}; + &call_analytic_diff, + &call_diff2c}; } } diff --git a/src/nmodl/pybind/wrapper.hpp b/src/nmodl/pybind/wrapper.hpp index 725f9f8113..e93cca51f5 100644 --- a/src/nmodl/pybind/wrapper.hpp +++ b/src/nmodl/pybind/wrapper.hpp @@ -7,8 +7,10 @@ #pragma once +#include #include #include +#include #include namespace nmodl { @@ -44,6 +46,17 @@ std::tuple call_analytic_diff( const std::vector& expressions, const std::set& used_names_in_block); + +/// \brief Differentiates an expression with respect to a variable +/// \param expression The expression we want to differentiate +/// \param variable The name of the independent variable we are differentiating against +/// \param index_vars A set of array (indexable) variables that appear in \ref expression +/// \return The tuple (solution, exception) +std::tuple call_diff2c( + const std::string& expression, + const std::pair>& variable, + const std::unordered_set& indexed_vars = {}); + struct pybind_wrap_api { decltype(&initialize_interpreter_func) initialize_interpreter; decltype(&finalize_interpreter_func) finalize_interpreter; @@ -51,6 +64,7 @@ struct pybind_wrap_api { decltype(&call_solve_linear_system) solve_linear_system; decltype(&call_diffeq_solver) diffeq_solver; decltype(&call_analytic_diff) analytic_diff; + decltype(&call_diff2c) diff2c; }; #ifdef _WIN32 diff --git a/src/nmodl/visitors/CMakeLists.txt b/src/nmodl/visitors/CMakeLists.txt index 055f4c212c..10a1ded543 100644 --- a/src/nmodl/visitors/CMakeLists.txt +++ b/src/nmodl/visitors/CMakeLists.txt @@ -11,6 +11,7 @@ add_library( visitor STATIC after_cvode_to_cnexp_visitor.cpp constant_folder_visitor.cpp + cvode_visitor.cpp defuse_analyze_visitor.cpp function_callpath_visitor.cpp global_var_visitor.cpp diff --git a/src/nmodl/visitors/cvode_visitor.cpp b/src/nmodl/visitors/cvode_visitor.cpp new file mode 100644 index 0000000000..617e1e6c65 --- /dev/null +++ b/src/nmodl/visitors/cvode_visitor.cpp @@ -0,0 +1,225 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "visitors/cvode_visitor.hpp" + +#include "ast/all.hpp" +#include "lexer/token_mapping.hpp" +#include "pybind/pyembed.hpp" +#include "utils/logger.hpp" +#include "visitors/visitor_utils.hpp" +#include +#include + +namespace pywrap = nmodl::pybind_wrappers; + +namespace nmodl { +namespace visitor { + +static int get_index(const ast::IndexedName& node) { + return std::stoi(to_nmodl(node.get_length())); +} + +static void remove_conserve_statements(ast::StatementBlock& node) { + auto conserve_equations = collect_nodes(node, {ast::AstNodeType::CONSERVE}); + if (!conserve_equations.empty()) { + std::unordered_set eqs; + for (const auto& item: conserve_equations) { + eqs.insert(std::dynamic_pointer_cast(item).get()); + } + node.erase_statement(eqs); + } +} + +static std::pair> parse_independent_var( + std::shared_ptr node) { + auto variable = std::make_pair(node->get_node_name(), std::optional()); + if (node->is_indexed_name()) { + variable.second = std::optional( + get_index(*std::dynamic_pointer_cast(node))); + } + return variable; +} + +/// set of all indexed variables not equal to ``ignored_name`` +static std::unordered_set get_indexed_variables(const ast::Expression& node, + const std::string& ignored_name) { + std::unordered_set indexed_variables; + // all of the "reserved" vars + auto reserved_symbols = get_external_functions(); + // all indexed vars + auto indexed_vars = collect_nodes(node, {ast::AstNodeType::INDEXED_NAME}); + for (const auto& var: indexed_vars) { + const auto& varname = var->get_node_name(); + // skip if it's a reserved var + auto varname_not_reserved = + std::none_of(reserved_symbols.begin(), + reserved_symbols.end(), + [&varname](const auto item) { return varname == item; }); + if (indexed_variables.count(varname) == 0 && varname != ignored_name && + varname_not_reserved) { + indexed_variables.insert(varname); + } + } + return indexed_variables; +} + +static std::string cvode_set_lhs(ast::BinaryExpression& node) { + const auto& lhs = node.get_lhs(); + + auto name = std::dynamic_pointer_cast(lhs)->get_name(); + + std::string varname; + if (name->is_prime_name()) { + varname = "D" + name->get_node_name(); + node.set_lhs(std::make_shared(new ast::String(varname))); + } else if (name->is_indexed_name()) { + auto nodes = collect_nodes(*name, {ast::AstNodeType::PRIME_NAME}); + // make sure the LHS isn't just a plain indexed var + if (!nodes.empty()) { + varname = "D" + stringutils::remove_character(to_nmodl(node.get_lhs()), '\''); + auto statement = fmt::format("{} = {}", varname, varname); + auto expr_statement = std::dynamic_pointer_cast( + create_statement(statement)); + const auto bin_expr = std::dynamic_pointer_cast( + expr_statement->get_expression()); + node.set_lhs(std::shared_ptr(bin_expr->get_lhs()->clone())); + } + } + return varname; +} + + +class CvodeHelperVisitor: public AstVisitor { + protected: + symtab::SymbolTable* program_symtab = nullptr; + bool in_differential_equation = false; + public: + inline void visit_diff_eq_expression(ast::DiffEqExpression& node) { + in_differential_equation = true; + node.visit_children(*this); + in_differential_equation = false; + } +}; + +class NonStiffVisitor: public CvodeHelperVisitor { + public: + explicit NonStiffVisitor(symtab::SymbolTable* symtab) { + program_symtab = symtab; + } + + inline void visit_binary_expression(ast::BinaryExpression& node) { + const auto& lhs = node.get_lhs(); + + if (!in_differential_equation || !lhs->is_var_name()) { + return; + } + + auto name = std::dynamic_pointer_cast(lhs)->get_name(); + auto varname = cvode_set_lhs(node); + + if (program_symtab->lookup(varname) == nullptr) { + auto symbol = std::make_shared(varname, ModToken()); + symbol->set_original_name(name->get_node_name()); + program_symtab->insert(symbol); + } + } +}; + +class StiffVisitor: public CvodeHelperVisitor { + public: + explicit StiffVisitor(symtab::SymbolTable* symtab) { + program_symtab = symtab; + } + + inline void visit_binary_expression(ast::BinaryExpression& node) { + const auto& lhs = node.get_lhs(); + + if (!in_differential_equation || !lhs->is_var_name()) { + return; + } + + auto name = std::dynamic_pointer_cast(lhs)->get_name(); + auto varname = cvode_set_lhs(node); + + if (program_symtab->lookup(varname) == nullptr) { + auto symbol = std::make_shared(varname, ModToken()); + symbol->set_original_name(name->get_node_name()); + program_symtab->insert(symbol); + } + + auto rhs = node.get_rhs(); + // all indexed variables (need special treatment in SymPy) + auto indexed_variables = get_indexed_variables(*rhs, name->get_node_name()); + auto diff2c = pywrap::EmbeddedPythonLoader::get_instance().api().diff2c; + auto [jacobian, exception_message] = + diff2c(to_nmodl(*rhs), parse_independent_var(name), indexed_variables); + if (!exception_message.empty()) { + logger->warn("CvodeVisitor :: python exception: {}", exception_message); + } + // NOTE: LHS can be anything here, the equality is to keep `create_statement` from + // complaining, we discard the LHS later + auto statement = fmt::format("{} = {} / (1 - dt * ({}))", varname, varname, jacobian); + logger->debug("CvodeVisitor :: replacing statement {} with {}", to_nmodl(node), statement); + auto expr_statement = std::dynamic_pointer_cast( + create_statement(statement)); + const auto bin_expr = std::dynamic_pointer_cast( + expr_statement->get_expression()); + node.set_rhs(std::shared_ptr(bin_expr->get_rhs()->clone())); + } +}; + +static std::shared_ptr get_derivative_block(ast::Program& node) { + auto derivative_blocks = collect_nodes(node, {ast::AstNodeType::DERIVATIVE_BLOCK}); + if (derivative_blocks.empty()) { + return nullptr; + } + + // steady state adds a DERIVATIVE block with a `_steadystate` suffix + auto not_steadystate = [](const auto& item) { + auto name = std::dynamic_pointer_cast(item)->get_node_name(); + return !stringutils::ends_with(name, "_steadystate"); + }; + decltype(derivative_blocks) derivative_blocks_copy; + std::copy_if(derivative_blocks.begin(), + derivative_blocks.end(), + std::back_inserter(derivative_blocks_copy), + not_steadystate); + if (derivative_blocks_copy.size() > 1) { + auto message = "CvodeVisitor :: cannot have multiple DERIVATIVE blocks"; + logger->error(message); + throw std::runtime_error(message); + } + + return std::dynamic_pointer_cast(derivative_blocks_copy[0]); +} + + +void CvodeVisitor::visit_program(ast::Program& node) { + auto derivative_block = get_derivative_block(node); + if (derivative_block == nullptr) { + return; + } + + auto non_stiff_block = derivative_block->get_statement_block()->clone(); + remove_conserve_statements(*non_stiff_block); + + auto stiff_block = derivative_block->get_statement_block()->clone(); + remove_conserve_statements(*stiff_block); + + NonStiffVisitor(node.get_symbol_table()).visit_statement_block(*non_stiff_block); + StiffVisitor(node.get_symbol_table()).visit_statement_block(*stiff_block); + auto prime_vars = collect_nodes(*derivative_block, {ast::AstNodeType::PRIME_NAME}); + node.emplace_back_node(new ast::CvodeBlock( + derivative_block->get_name(), + std::shared_ptr(new ast::Integer(prime_vars.size(), nullptr)), + std::shared_ptr(non_stiff_block), + std::shared_ptr(stiff_block))); +} + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/cvode_visitor.hpp b/src/nmodl/visitors/cvode_visitor.hpp new file mode 100644 index 0000000000..c8d37f6a61 --- /dev/null +++ b/src/nmodl/visitors/cvode_visitor.hpp @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Blue Brain Project, EPFL. + * See the top-level LICENSE file for details. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +/** + * \file + * \brief \copybrief nmodl::visitor::CvodeVisitor + */ + +#include "symtab/decl.hpp" +#include "visitors/ast_visitor.hpp" +#include +#include + +namespace nmodl { +namespace visitor { + +/** + * \addtogroup visitor_classes + * \{ + */ + +/** + * \class CvodeVisitor + * \brief Visitor used for generating the necessary AST nodes for CVODE + */ +class CvodeVisitor: public AstVisitor { + public: + void visit_program(ast::Program& node) override; +}; + +/** \} */ // end of visitor_classes + +} // namespace visitor +} // namespace nmodl diff --git a/src/nmodl/visitors/sympy_solver_visitor.cpp b/src/nmodl/visitors/sympy_solver_visitor.cpp index 0de8d98578..f57a70aaba 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.cpp +++ b/src/nmodl/visitors/sympy_solver_visitor.cpp @@ -399,6 +399,9 @@ void SympySolverVisitor::visit_var_name(ast::VarName& node) { } } +// Skip visiting CVODE block +void SympySolverVisitor::visit_cvode_block(ast::CvodeBlock& node) {} + void SympySolverVisitor::visit_diff_eq_expression(ast::DiffEqExpression& node) { const auto& lhs = node.get_expression()->get_lhs(); diff --git a/src/nmodl/visitors/sympy_solver_visitor.hpp b/src/nmodl/visitors/sympy_solver_visitor.hpp index 8675110041..a77373e2d4 100644 --- a/src/nmodl/visitors/sympy_solver_visitor.hpp +++ b/src/nmodl/visitors/sympy_solver_visitor.hpp @@ -183,6 +183,7 @@ class SympySolverVisitor: public AstVisitor { void visit_expression_statement(ast::ExpressionStatement& node) override; void visit_statement_block(ast::StatementBlock& node) override; void visit_program(ast::Program& node) override; + void visit_cvode_block(ast::CvodeBlock& node) override; }; /** @} */ // end of visitor_classes diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index bdd7d51081..ea6757a0e6 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -45,6 +45,7 @@ add_executable( visitor/kinetic_block.cpp visitor/localize.cpp visitor/localrename.cpp + visitor/cvode.cpp visitor/local_to_assigned.cpp visitor/lookup.cpp visitor/loop_unroll.cpp diff --git a/test/nmodl/transpiler/unit/visitor/cvode.cpp b/test/nmodl/transpiler/unit/visitor/cvode.cpp new file mode 100644 index 0000000000..96d09549ab --- /dev/null +++ b/test/nmodl/transpiler/unit/visitor/cvode.cpp @@ -0,0 +1,89 @@ +#include + +#include "ast/program.hpp" +#include "parser/nmodl_driver.hpp" +#include "test/unit/utils/test_utils.hpp" +#include "visitors/checkparent_visitor.hpp" +#include "visitors/cvode_visitor.hpp" +#include "visitors/nmodl_visitor.hpp" +#include "visitors/symtab_visitor.hpp" +#include "visitors/visitor_utils.hpp" + +using namespace nmodl; +using namespace visitor; +using namespace test; +using namespace test_utils; + +using nmodl::parser::NmodlDriver; + + +auto run_cvode_visitor(const std::string& text) { + NmodlDriver driver; + const auto& ast = driver.parse_string(text); + SymtabVisitor().visit_program(*ast); + CvodeVisitor().visit_program(*ast); + + return ast; +} + + +TEST_CASE("Make sure CVODE block is generated properly", "[visitor][cvode]") { + GIVEN("No DERIVATIVE block") { + auto nmodl_text = "NEURON { SUFFIX example }"; + auto ast = run_cvode_visitor(nmodl_text); + THEN("No CVODE block is added") { + auto blocks = collect_nodes(*ast, {ast::AstNodeType::CVODE_BLOCK}); + REQUIRE(blocks.empty()); + } + } + GIVEN("DERIVATIVE block") { + auto nmodl_text = R"( + NEURON { + SUFFIX example + } + + STATE {X Y[2] Z} + + DERIVATIVE equation { + CONSERVE X + Z = 5 + X' = -X + Z * Z + Z' = Z * X + Y'[1] = -Y[0] + Y'[0] = -Y[1] + } +)"; + auto ast = run_cvode_visitor(nmodl_text); + THEN("CVODE block is added") { + auto blocks = collect_nodes(*ast, {ast::AstNodeType::CVODE_BLOCK}); + REQUIRE(blocks.size() == 1); + THEN("No primed variables exist in the CVODE block") { + auto primed_vars = collect_nodes(*blocks[0], {ast::AstNodeType::PRIME_NAME}); + REQUIRE(primed_vars.empty()); + } + THEN("No CONSERVE statements are present in the CVODE block") { + auto conserved_stmts = collect_nodes(*blocks[0], {ast::AstNodeType::CONSERVE}); + REQUIRE(conserved_stmts.empty()); + } + } + } + GIVEN("Multiple DERIVATIVE blocks") { + auto nmodl_text = R"( + NEURON { + SUFFIX example + } + + STATE {X} + + DERIVATIVE equation { + X' = -X + } + + DERIVATIVE equation2 { + X' = -X * X + } +)"; + THEN("An error is raised") { + REQUIRE_THROWS_AS(run_cvode_visitor(nmodl_text), std::runtime_error); + } + } +} From 3c238454b45392c3ccb3a40d2ea28315de99ca37 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 28 Oct 2024 10:04:08 +0100 Subject: [PATCH 823/871] Prepare for deprecating `vectorize`. (BlueBrain/nmodl#1537) Adds virtual methods `needs_v_unused` that returns: * true for `--neuron`, * and the previous choice for CoreNEURON. This will make CoreNEURON incompatible with NMODL generated MOD files. However, it will allow NMODL to generate code for NRN that doesn't use globals to handle the voltage `v` (and instead stores a cpoy as a regular range variable). NMODL Repo SHA: BlueBrain/nmodl@750ec884dd554556218c3066b1dd7d0b4c9a392d --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 3 +++ src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp | 1 + src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 2 ++ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 4 ++++ src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 1 + 6 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 8b1001fcf9..dfe26b1c19 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -59,6 +59,9 @@ std::string CodegenCoreneuronCppVisitor::simulator_name() { return "CoreNEURON"; } +bool CodegenCoreneuronCppVisitor::needs_v_unused() const { + return info.vectorize; +} /****************************************************************************************/ /* Common helper routines accross codegen functions */ diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 6db14a8e15..dc67cca21f 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -93,6 +93,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { return info.vectorize ? (info.thread_data_index + 1) : 0; } + bool needs_v_unused() const override; /****************************************************************************************/ /* Common helper routines accross codegen functions */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 312ea2a89f..7c824b67f2 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1273,7 +1273,7 @@ std::vector CodegenCppVisitor::get_float_variable } } - if (info.vectorize) { + if (needs_v_unused()) { variables.push_back(make_symbol(naming::VOLTAGE_UNUSED_VARIABLE)); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 96378a8592..986dedbff4 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -731,6 +731,8 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ std::vector get_float_variables() const; + virtual bool needs_v_unused() const = 0; + /** * Determine all \c int variables required during code generation diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 97004d601d..5e05a6bc5d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -55,6 +55,10 @@ std::string CodegenNeuronCppVisitor::table_thread_function_name() const { return "_check_table_thread"; } +bool CodegenNeuronCppVisitor::needs_v_unused() const { + return true; +} + /****************************************************************************************/ /* Common helper routines accross codegen functions */ diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index de56f061ca..b98951f944 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -124,6 +124,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ std::string table_thread_function_name() const; + bool needs_v_unused() const override; /****************************************************************************************/ /* Common helper routines accross codegen functions */ From 286597594a4402b5f53944a28324013b95a29e65 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 28 Oct 2024 11:58:03 +0100 Subject: [PATCH 824/871] Adjust thread-variable promotion condition. (BlueBrain/nmodl#1534) NMODL Repo SHA: BlueBrain/nmodl@ef0db544029e5bcc84f15084d3b1da690996a8d5 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 6 +++++- src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 6 ++++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 391dbc6d36..3ccee518d5 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -252,7 +252,7 @@ void CodegenHelperVisitor::find_non_range_variables() { auto vars = psymtab->get_variables_with_properties(NmodlType::global_var); for (auto& var: vars) { - if (info.vectorize && info.thread_safe && var->get_write_count() > 0) { + if (info.vectorize && info.declared_thread_safe && var->get_write_count() > 0) { var->mark_thread_safe(); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); @@ -767,6 +767,10 @@ void CodegenHelperVisitor::visit_bbcore_pointer(const BbcorePointer& /* node */) info.bbcore_pointer_used = true; } +void CodegenHelperVisitor::visit_thread_safe(const ast::ThreadSafe&) { + info.declared_thread_safe = true; +} + void CodegenHelperVisitor::visit_watch(const ast::Watch& /* node */) { info.watch_count++; diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 3468802c69..24d074f7a2 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -101,6 +101,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; void visit_net_receive_block(const ast::NetReceiveBlock& node) override; void visit_bbcore_pointer(const ast::BbcorePointer& node) override; + void visit_thread_safe(const ast::ThreadSafe&) override; void visit_watch(const ast::Watch& node) override; void visit_watch_statement(const ast::WatchStatement& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index 0e86ac0c30..eaea56467c 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -352,6 +352,12 @@ struct CodegenInfo { /// if mod file is thread safe (always true for coreneuron) bool thread_safe = true; + /// A mod file can be declared to be thread safe using the keyword THREADSAFE. + /// This boolean is true if and only if the mod file was declared thread safe + /// by the user. For example thread variables require the mod file to be declared + /// thread safe. + bool declared_thread_safe = false; + /// if mod file is point process bool point_process = false; From 2a2f7af453a45faef28b6ef6afd5151e820e5c47 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 28 Oct 2024 13:22:15 +0100 Subject: [PATCH 825/871] Don't have instance copies of POINTER. (BlueBrain/nmodl#1538) The NRN unit-tests fail with a segfault when we try to get cached copy of the dereferenced Datums. Since we don't need access via the cache but only through `_ppvar` we don't need to make them part of the instance structure. NMODL Repo SHA: BlueBrain/nmodl@01d218d443f6e0f7967cef50f11125b9d85e6e99 --- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 5e05a6bc5d..1d546f6231 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1562,10 +1562,14 @@ void CodegenNeuronCppVisitor::print_mechanism_range_var_structure(bool print_ini } for (auto& var: codegen_int_variables) { const auto& name = var.symbol->get_name(); + auto position = position_of_int_var(name); + if (name == naming::POINT_PROCESS_VARIABLE) { continue; } else if (var.is_index || var.is_integer) { // In NEURON we don't create caches for `int*`. Hence, do nothing. + } else if (info.semantics[position].name == naming::POINTER_SEMANTIC) { + // we don't need these either. } else { auto qualifier = var.is_constant ? "const " : ""; auto type = var.is_vdata ? "void*" : default_float_data_type(); @@ -1617,11 +1621,14 @@ void CodegenNeuronCppVisitor::print_make_instance() const { for (size_t i = 0; i < codegen_int_variables_size; ++i) { const auto& var = codegen_int_variables[i]; auto name = var.symbol->get_name(); - auto const variable = [&var, i]() -> std::string { + auto sem = info.semantics[i].name; + auto const variable = [&var, &sem, i]() -> std::string { if (var.is_index || var.is_integer) { return ""; } else if (var.is_vdata) { return ""; + } else if (sem == naming::POINTER_SEMANTIC) { + return ""; } else { return fmt::format("_lmc->template dptr_field_ptr<{}>()", i); } From ac12364dc7d3784aa3616825b19dbf0f00b12aef Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 28 Oct 2024 15:21:54 +0100 Subject: [PATCH 826/871] Consider NONLINEAR vectorizable. (BlueBrain/nmodl#1533) NMODL Repo SHA: BlueBrain/nmodl@ffe4f68d642a007a8abdf0dc6cf4dbffcc9ae47f --- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 3ccee518d5..88f71369fd 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -823,7 +823,7 @@ void CodegenHelperVisitor::visit_linear_block(const ast::LinearBlock& /* node */ } void CodegenHelperVisitor::visit_non_linear_block(const ast::NonLinearBlock& /* node */) { - info.vectorize = false; + info.vectorize = true; } void CodegenHelperVisitor::visit_discrete_block(const ast::DiscreteBlock& /* node */) { From a60b28d014673b0391bab298dc226499f6970480 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 14:21:10 +0100 Subject: [PATCH 827/871] Remove tests for not `vectorized` code. (BlueBrain/nmodl#1535) NMODL Repo SHA: BlueBrain/nmodl@49e1eff77b336f170611324c86e6faafdbaf8653 --- .../function/artificial_functions.mod | 10 ----- .../usecases/function/non_threadsafe.mod | 38 ---------------- .../function/point_non_threadsafe.mod | 38 ---------------- .../usecases/function/test_functions.py | 4 -- .../usecases/global/non_threadsafe.mod | 43 ------------------- .../usecases/global/test_non_threadsafe.py | 33 -------------- 6 files changed, 166 deletions(-) delete mode 100644 test/nmodl/transpiler/usecases/function/non_threadsafe.mod delete mode 100644 test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod delete mode 100644 test/nmodl/transpiler/usecases/global/non_threadsafe.mod delete mode 100644 test/nmodl/transpiler/usecases/global/test_non_threadsafe.py diff --git a/test/nmodl/transpiler/usecases/function/artificial_functions.mod b/test/nmodl/transpiler/usecases/function/artificial_functions.mod index e684591f83..ee25e3474d 100644 --- a/test/nmodl/transpiler/usecases/function/artificial_functions.mod +++ b/test/nmodl/transpiler/usecases/function/artificial_functions.mod @@ -22,13 +22,3 @@ INITIAL { x = 1.0 gbl = 42.0 } - -: A LINEAR block makes a MOD file not VECTORIZED. -STATE { - z -} - -LINEAR lin { - ~ z = 2 -} - diff --git a/test/nmodl/transpiler/usecases/function/non_threadsafe.mod b/test/nmodl/transpiler/usecases/function/non_threadsafe.mod deleted file mode 100644 index ee1789a12a..0000000000 --- a/test/nmodl/transpiler/usecases/function/non_threadsafe.mod +++ /dev/null @@ -1,38 +0,0 @@ -NEURON { - SUFFIX non_threadsafe - RANGE x - GLOBAL gbl -} - -ASSIGNED { - gbl - v - x -} - -FUNCTION x_plus_a(a) { - x_plus_a = x + a -} - -FUNCTION v_plus_a(a) { - v_plus_a = v + a -} - -FUNCTION identity(v) { - identity = v -} - -INITIAL { - x = 1.0 - gbl = 42.0 -} - -: A LINEAR block makes a MOD file not VECTORIZED. -STATE { - z -} - -LINEAR lin { - ~ z = 2 -} - diff --git a/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod b/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod deleted file mode 100644 index 13ce35cca2..0000000000 --- a/test/nmodl/transpiler/usecases/function/point_non_threadsafe.mod +++ /dev/null @@ -1,38 +0,0 @@ -NEURON { - POINT_PROCESS point_non_threadsafe - RANGE x - GLOBAL gbl -} - -ASSIGNED { - gbl - v - x -} - -FUNCTION x_plus_a(a) { - x_plus_a = x + a -} - -FUNCTION v_plus_a(a) { - v_plus_a = v + a -} - -FUNCTION identity(v) { - identity = v -} - -INITIAL { - x = 1.0 - gbl = 42.0 -} - -: A LINEAR block makes a MOD file not VECTORIZED. -STATE { - z -} - -LINEAR lin { - ~ z = 2 -} - diff --git a/test/nmodl/transpiler/usecases/function/test_functions.py b/test/nmodl/transpiler/usecases/function/test_functions.py index 046cb8555b..b033a958f6 100644 --- a/test/nmodl/transpiler/usecases/function/test_functions.py +++ b/test/nmodl/transpiler/usecases/function/test_functions.py @@ -31,18 +31,14 @@ def check_callable(get_instance, has_voltage=True): s.nseg = nseg s.insert("functions") -s.insert("non_threadsafe") coords = [(0.5 + k) * 1.0 / nseg for k in range(nseg)] values = [0.1 + k for k in range(nseg)] point_processes = {x: h.point_functions(s(x)) for x in coords} -point_non_threadsafe = {x: h.point_non_threadsafe(s(x)) for x in coords} art_cells = {x: h.art_functions() for x in coords} check_callable(lambda x: s(x).functions) -check_callable(lambda x: s(x).non_threadsafe) check_callable(lambda x: point_processes[x]) -check_callable(lambda x: point_non_threadsafe[x]) check_callable(lambda x: art_cells[x], has_voltage=False) diff --git a/test/nmodl/transpiler/usecases/global/non_threadsafe.mod b/test/nmodl/transpiler/usecases/global/non_threadsafe.mod deleted file mode 100644 index 54eb96aed9..0000000000 --- a/test/nmodl/transpiler/usecases/global/non_threadsafe.mod +++ /dev/null @@ -1,43 +0,0 @@ -NEURON { - SUFFIX non_threadsafe - GLOBAL gbl -} - -LOCAL top_local - -PARAMETER { - parameter = 41.0 -} - -ASSIGNED { - gbl -} - -FUNCTION get_gbl() { - get_gbl = gbl -} - -FUNCTION get_top_local() { - get_top_local = top_local -} - -FUNCTION get_parameter() { - get_parameter = parameter -} - -INITIAL { - gbl = 42.0 - top_local = 43.0 -} - -: A LINEAR block makes the MOD file not thread-safe and not -: vectorized. We don't otherwise care about anything below -: this comment. -STATE { - z -} - -LINEAR lin { - ~ z = 2 -} - diff --git a/test/nmodl/transpiler/usecases/global/test_non_threadsafe.py b/test/nmodl/transpiler/usecases/global/test_non_threadsafe.py deleted file mode 100644 index fcd413fb76..0000000000 --- a/test/nmodl/transpiler/usecases/global/test_non_threadsafe.py +++ /dev/null @@ -1,33 +0,0 @@ -import numpy as np - -from neuron import h, gui -from neuron.units import ms - - -def test_non_threadsafe(): - nseg = 1 - - s = h.Section() - s.insert("non_threadsafe") - s.nseg = nseg - - h.finitialize() - - instance = s(0.5).non_threadsafe - - # Check INITIAL values. - assert instance.get_parameter() == 41.0 - assert instance.get_gbl() == 42.0 - assert instance.get_top_local() == 43.0 - - # Check reassigning a value. Top LOCAL variables - # are not exposed to HOC/Python. - h.parameter_non_threadsafe = 32.1 - h.gbl_non_threadsafe = 33.2 - - assert instance.get_parameter() == 32.1 - assert instance.get_gbl() == 33.2 - - -if __name__ == "__main__": - test_non_threadsafe() From 8a624d16ba4a327214d11fb97a4bfdc2b25f7e7a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 14:55:33 +0100 Subject: [PATCH 828/871] Consider LINEAR as vectorizeable. (BlueBrain/nmodl#1536) NMODL Repo SHA: BlueBrain/nmodl@4560899d15ab636bb9dbd8b51591eee7c213c237 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 88f71369fd..02a49040d0 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -819,7 +819,7 @@ CodegenInfo CodegenHelperVisitor::analyze(const ast::Program& node) { } void CodegenHelperVisitor::visit_linear_block(const ast::LinearBlock& /* node */) { - info.vectorize = false; + info.vectorize = true; } void CodegenHelperVisitor::visit_non_linear_block(const ast::NonLinearBlock& /* node */) { From 4fe0a8fcbd40f999b7c71d1b9b168dff2a020d6d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 14:56:10 +0100 Subject: [PATCH 829/871] Handle voltage via instance specific copy. (BlueBrain/nmodl#1532) NMODL Repo SHA: BlueBrain/nmodl@31a6c488548f2a94d71da5beb9d797424284600b --- .../codegen/codegen_neuron_cpp_visitor.cpp | 15 +++-- .../transpiler/usecases/voltage/accessors.mod | 18 ++++++ .../nmodl/transpiler/usecases/voltage/ode.mod | 17 ++++++ .../transpiler/usecases/voltage/state_ode.mod | 31 +++++++++++ .../usecases/voltage/test_voltage.py | 55 +++++++++++++++++++ 5 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/voltage/accessors.mod create mode 100644 test/nmodl/transpiler/usecases/voltage/ode.mod create mode 100644 test/nmodl/transpiler/usecases/voltage/state_ode.mod create mode 100644 test/nmodl/transpiler/usecases/voltage/test_voltage.py diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 1d546f6231..6d8c3c9307 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -795,7 +795,10 @@ std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symb std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { - const std::string& varname = update_if_ion_variable_name(name); + std::string varname = update_if_ion_variable_name(name); + if (!info.artificial_cell && varname == "v") { + varname = naming::VOLTAGE_UNUSED_VARIABLE; + } auto name_comparator = [&varname](const auto& sym) { return varname == get_name(sym); }; @@ -956,9 +959,6 @@ void CodegenNeuronCppVisitor::print_sdlists_init(bool /* print_initializers */) CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::functor_params() { auto params = internal_method_parameters(); - if (!info.artificial_cell) { - params.push_back({"", "double", "", "v"}); - } return params; } @@ -1822,7 +1822,7 @@ void CodegenNeuronCppVisitor::print_nrn_init(bool skip_init_check) { printer->add_line("auto* _ppvar = _ml_arg->pdata[id];"); if (!info.artificial_cell) { printer->add_line("int node_id = node_data.nodeindices[id];"); - printer->add_line("auto v = node_data.node_voltages[node_id];"); + printer->add_line("inst.v_unused[id] = node_data.node_voltages[node_id];"); } print_rename_state_vars(); @@ -2069,7 +2069,9 @@ void CodegenNeuronCppVisitor::print_nrn_state() { printer->push_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_data.nodeindices[id];"); printer->add_line("auto* _ppvar = _ml_arg->pdata[id];"); - printer->add_line("auto v = node_data.node_voltages[node_id];"); + if (!info.artificial_cell) { + printer->add_line("inst.v_unused[id] = node_data.node_voltages[node_id];"); + } /** * \todo Eigen solver node also emits IonCurVar variable in the functor @@ -2142,6 +2144,7 @@ void CodegenNeuronCppVisitor::print_nrn_current(const BreakpointBlock& node) { printer->fmt_push_block("static inline double nrn_current_{}({})", info.mod_suffix, get_parameter_str(args)); + printer->add_line("inst.v_unused[id] = v;"); printer->add_line("double current = 0.0;"); print_statement_block(*block, false, false); for (auto& current: info.currents) { diff --git a/test/nmodl/transpiler/usecases/voltage/accessors.mod b/test/nmodl/transpiler/usecases/voltage/accessors.mod new file mode 100644 index 0000000000..2a3a726162 --- /dev/null +++ b/test/nmodl/transpiler/usecases/voltage/accessors.mod @@ -0,0 +1,18 @@ +NEURON { + SUFFIX accessors + NONSPECIFIC_CURRENT il +} + +ASSIGNED { + v + il +} + +BREAKPOINT { + il = 0.003 +} + + +FUNCTION get_voltage() { + get_voltage = v +} diff --git a/test/nmodl/transpiler/usecases/voltage/ode.mod b/test/nmodl/transpiler/usecases/voltage/ode.mod new file mode 100644 index 0000000000..d0e6368513 --- /dev/null +++ b/test/nmodl/transpiler/usecases/voltage/ode.mod @@ -0,0 +1,17 @@ +NEURON { + SUFFIX ode + NONSPECIFIC_CURRENT il +} + +ASSIGNED { + il + v +} + +FUNCTION voltage() { + voltage = 0.001 * v +} + +BREAKPOINT { + il = voltage() +} diff --git a/test/nmodl/transpiler/usecases/voltage/state_ode.mod b/test/nmodl/transpiler/usecases/voltage/state_ode.mod new file mode 100644 index 0000000000..909645f329 --- /dev/null +++ b/test/nmodl/transpiler/usecases/voltage/state_ode.mod @@ -0,0 +1,31 @@ +NEURON { + SUFFIX state_ode + NONSPECIFIC_CURRENT il +} + +STATE { + X +} + +ASSIGNED { + il + v +} + +INITIAL { + X = v +} + +BREAKPOINT { + SOLVE eqn + il = 0.001 * X +} + +NONLINEAR eqn { LOCAL c + c = rate() + ~ X = c +} + +FUNCTION rate() { + rate = v +} diff --git a/test/nmodl/transpiler/usecases/voltage/test_voltage.py b/test/nmodl/transpiler/usecases/voltage/test_voltage.py new file mode 100644 index 0000000000..59828de6bf --- /dev/null +++ b/test/nmodl/transpiler/usecases/voltage/test_voltage.py @@ -0,0 +1,55 @@ +from neuron import h, gui + +import numpy as np + + +def test_voltage_access(): + s = h.Section() + s.insert("accessors") + + h.finitialize() + v = s(0.5).v + vinst = s(0.5).accessors.get_voltage() + # The voltage will be consistent right after + # finitialize. + assert vinst == v + + for _ in range(4): + v = s(0.5).v + h.fadvance() + vinst = s(0.5).accessors.get_voltage() + + # During timestepping the internal copy + # of the voltage lags behind the current + # voltage by some timestep. + assert vinst == v, f"{vinst = }, {v = }, delta = {vinst - v}" + + +def check_ode(mech_name, step): + s = h.Section() + s.insert(mech_name) + + h.finitialize() + + c = -0.001 / 1e-3 + + for _ in range(4): + v_expected = step(s(0.5).v, c) + h.fadvance() + np.testing.assert_approx_equal(s(0.5).v, v_expected, significant=10) + + +def test_breakpoint(): + # Results in backward Euler. + check_ode("ode", lambda v, c: (1.0 - c * h.dt) ** (-1.0) * v) + + +def test_state(): + # Effectively, the timing when states are computed results in backward Euler. + check_ode("state_ode", lambda v, c: (1.0 + c * h.dt) * v) + + +if __name__ == "__main__": + test_voltage_access() + test_breakpoint() + test_state() From de92e70c67ad6084e7f33801e05cca0ba181eec1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 16:37:23 +0100 Subject: [PATCH 830/871] Whitespace. (BlueBrain/nmodl#1541) NMODL Repo SHA: BlueBrain/nmodl@e5444ddbcee0b3545d852aef7f67e37e6fd7d9bd --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 7c824b67f2..79e6f3c5b2 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1454,6 +1454,7 @@ void CodegenCppVisitor::setup(const Program& node) { info.semantic_variable_count = int_variables_size(); } + std::string CodegenCppVisitor::compute_method_name(BlockType type) const { if (type == BlockType::Initial) { return method_name(naming::NRN_INIT_METHOD); @@ -1687,6 +1688,7 @@ void CodegenCppVisitor::print_table_check_function(const Block& node) { printer->pop_block(); } + std::string CodegenCppVisitor::get_object_specifiers( const std::unordered_set& specifiers) { std::string result; @@ -1699,6 +1701,7 @@ std::string CodegenCppVisitor::get_object_specifiers( return result; } + const ast::TableStatement* CodegenCppVisitor::get_table_statement(const ast::Block& node) { const auto& table_statements = collect_nodes(node, {AstNodeType::TABLE_STATEMENT}); @@ -1725,6 +1728,7 @@ std::tuple CodegenCppVisitor::check_if_var_is_array(const std::string } } + void CodegenCppVisitor::print_rename_state_vars() const { for (const auto& state: info.state_vars) { auto state_name = state->get_name(); From 0c46d4ba0e44acb101bef5a2855e77cdd5dd04f7 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 16:37:56 +0100 Subject: [PATCH 831/871] Move shared parallel hint code. (BlueBrain/nmodl#1542) NMODL Repo SHA: BlueBrain/nmodl@e74ebce0c165191ce720b9d0dc4b8b7108a7f7ae --- .../codegen_coreneuron_cpp_visitor.cpp | 35 ------------------- .../codegen_coreneuron_cpp_visitor.hpp | 17 --------- src/nmodl/codegen/codegen_cpp_visitor.cpp | 35 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 17 +++++++++ 4 files changed, 52 insertions(+), 52 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index dfe26b1c19..850d52cc1f 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -218,41 +218,6 @@ void CodegenCoreneuronCppVisitor::print_net_init_acc_serial_annotation_block_end } -/** - * \details Depending programming model and compiler, we print compiler hint - * for parallelization. For example: - * - * \code - * #pragma omp simd - * for(int id = 0; id < nodecount; id++) { - * - * #pragma acc parallel loop - * for(int id = 0; id < nodecount; id++) { - * \endcode - */ -void CodegenCoreneuronCppVisitor::print_channel_iteration_block_parallel_hint( - BlockType /* type */, - const ast::Block* block) { - // ivdep allows SIMD parallelisation of a block/loop but doesn't provide - // a standard mechanism for atomics. Also, even with openmp 5.0, openmp - // atomics do not enable vectorisation under "omp simd" (gives compiler - // error with gcc < 9 if atomic and simd pragmas are nested). So, emit - // ivdep/simd pragma when no MUTEXLOCK/MUTEXUNLOCK/PROTECT statements - // are used in the given block. - std::vector> nodes; - if (block) { - nodes = collect_nodes(*block, - {ast::AstNodeType::PROTECT_STATEMENT, - ast::AstNodeType::MUTEX_LOCK, - ast::AstNodeType::MUTEX_UNLOCK}); - } - if (nodes.empty()) { - printer->add_line("#pragma omp simd"); - printer->add_line("#pragma ivdep"); - } -} - - bool CodegenCoreneuronCppVisitor::nrn_cur_reduction_loop_required() { return info.point_process; } diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index dc67cca21f..989374e007 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -201,23 +201,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { virtual void print_net_init_acc_serial_annotation_block_end(); - /** - * Print pragma annotations for channel iterations - * - * This can be overriden by backends to provide additonal annotations or pragmas to enable - * for example SIMD code generation (e.g. through \c ivdep) - * The default implementation prints - * - * \code - * #pragma ivdep - * \endcode - * - * \param type The block type - */ - virtual void print_channel_iteration_block_parallel_hint(BlockType type, - const ast::Block* block); - - /** * Check if reduction block in \c nrn\_cur required */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 79e6f3c5b2..de955706e1 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -377,6 +377,41 @@ std::string CodegenCppVisitor::breakpoint_current(std::string current) const { return current; } + +/** + * \details Depending programming model and compiler, we print compiler hint + * for parallelization. For example: + * + * \code + * #pragma omp simd + * for(int id = 0; id < nodecount; id++) { + * + * #pragma acc parallel loop + * for(int id = 0; id < nodecount; id++) { + * \endcode + */ +void CodegenCppVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, + const ast::Block* block) { + // ivdep allows SIMD parallelisation of a block/loop but doesn't provide + // a standard mechanism for atomics. Also, even with openmp 5.0, openmp + // atomics do not enable vectorisation under "omp simd" (gives compiler + // error with gcc < 9 if atomic and simd pragmas are nested). So, emit + // ivdep/simd pragma when no MUTEXLOCK/MUTEXUNLOCK/PROTECT statements + // are used in the given block. + std::vector> nodes; + if (block) { + nodes = collect_nodes(*block, + {ast::AstNodeType::PROTECT_STATEMENT, + ast::AstNodeType::MUTEX_LOCK, + ast::AstNodeType::MUTEX_UNLOCK}); + } + if (nodes.empty()) { + printer->add_line("#pragma omp simd"); + printer->add_line("#pragma ivdep"); + } +} + + /****************************************************************************************/ /* Routines for returning variable name */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 986dedbff4..86d1ba6f78 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -791,6 +791,23 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::string breakpoint_current(std::string current) const; + /** + * Print pragma annotations for channel iterations + * + * This can be overriden by backends to provide additonal annotations or pragmas to enable + * for example SIMD code generation (e.g. through \c ivdep) + * The default implementation prints + * + * \code + * #pragma ivdep + * \endcode + * + * \param type The block type + */ + virtual void print_channel_iteration_block_parallel_hint(BlockType type, + const ast::Block* block); + + /****************************************************************************************/ /* Backend specific routines */ /****************************************************************************************/ From 0b950041ba3b154d9adf196fc35d8b828a17b8c1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 16:38:59 +0100 Subject: [PATCH 832/871] Lower printing of VERBATIM code. (BlueBrain/nmodl#1543) Lower all of `process_verbatim_text` and the implementation of `visit_verbatim` to the CoreNEURON visitor. The NEURON version will use a different system. NMODL Repo SHA: BlueBrain/nmodl@d4a43e93e4fb66478a8956cf17489593cedb13c2 --- .../codegen/codegen_coreneuron_cpp_visitor.cpp | 15 +++++++++++++++ .../codegen/codegen_coreneuron_cpp_visitor.hpp | 3 ++- src/nmodl/codegen/codegen_cpp_visitor.cpp | 16 ---------------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 9 --------- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 +++--- src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 11 +---------- 6 files changed, 21 insertions(+), 39 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 850d52cc1f..b8ce415f62 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -118,6 +118,21 @@ std::string CodegenCoreneuronCppVisitor::process_verbatim_token(const std::strin return get_variable_name(token, use_instance); } +void CodegenCoreneuronCppVisitor::visit_verbatim(const Verbatim& node) { + const auto& text = node.get_statement()->eval(); + printer->add_line("// VERBATIM"); + const auto& result = process_verbatim_text(text); + + const auto& statements = stringutils::split_string(result, '\n'); + for (const auto& statement: statements) { + const auto& trimed_stmt = stringutils::trim_newline(statement); + if (trimed_stmt.find_first_not_of(' ') != std::string::npos) { + printer->add_line(trimed_stmt); + } + } + printer->add_line("// ENDVERBATIM"); +} + /** * \details This can be override in the backend. For example, parameters can be constant diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 989374e007..65f26953d6 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -408,7 +408,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { * \param text The verbatim code to be processed * \return The code with all variables renamed as needed */ - std::string process_verbatim_text(std::string const& text) override; + std::string process_verbatim_text(std::string const& text); /** @@ -947,6 +947,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void visit_derivimplicit_callback(const ast::DerivimplicitCallback& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; + void visit_verbatim(const ast::Verbatim& node) override; void visit_watch_statement(const ast::WatchStatement& node) override; ParamVector functor_params() override; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index de955706e1..5c0ffb36a3 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1084,22 +1084,6 @@ void CodegenCppVisitor::visit_function_call(const FunctionCall& node) { } -void CodegenCppVisitor::visit_verbatim(const Verbatim& node) { - const auto& text = node.get_statement()->eval(); - printer->add_line("// VERBATIM"); - const auto& result = process_verbatim_text(text); - - const auto& statements = stringutils::split_string(result, '\n'); - for (const auto& statement: statements) { - const auto& trimed_stmt = stringutils::trim_newline(statement); - if (trimed_stmt.find_first_not_of(' ') != std::string::npos) { - printer->add_line(trimed_stmt); - } - } - printer->add_line("// ENDVERBATIM"); -} - - void CodegenCppVisitor::visit_update_dt(const ast::UpdateDt& node) { // dt change statement should be pulled outside already } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 86d1ba6f78..c00b5cba6e 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1050,14 +1050,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ virtual std::string nrn_thread_internal_arguments() = 0; - /** - * Process a verbatim block for possible variable renaming - * \param text The verbatim code to be processed - * \return The code with all variables renamed as needed - */ - virtual std::string process_verbatim_text(std::string const& text) = 0; - - /** * Arguments for register_mech or point_register_mech function */ @@ -1497,7 +1489,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_unary_operator(const ast::UnaryOperator& node) override; void visit_unit(const ast::Unit& node) override; void visit_var_name(const ast::VarName& node) override; - void visit_verbatim(const ast::Verbatim& node) override; void visit_while_statement(const ast::WhileStatement& node) override; void visit_update_dt(const ast::UpdateDt& node) override; void visit_protect_statement(const ast::ProtectStatement& node) override; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 6d8c3c9307..c449f8bc33 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -631,9 +631,9 @@ CodegenNeuronCppVisitor::function_table_parameters(const ast::FunctionTableBlock return {params, {}}; } -/// TODO: Write for NEURON -std::string CodegenNeuronCppVisitor::process_verbatim_text(std::string const& text) { - return {}; + +void CodegenNeuronCppVisitor::visit_verbatim(const Verbatim& node) { + // Not implemented yet. } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index b98951f944..823055b303 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -358,14 +358,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { const ast::FunctionTableBlock& /* node */) override; - /** - * Process a verbatim block for possible variable renaming - * \param text The verbatim code to be processed - * \return The code with all variables renamed as needed - */ - std::string process_verbatim_text(std::string const& text) override; - - /** * Arguments for register_mech or point_register_mech function */ @@ -768,13 +760,12 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Overloaded visitor routines */ /****************************************************************************************/ - + void visit_verbatim(const ast::Verbatim& node) override; void visit_watch_statement(const ast::WatchStatement& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; void visit_longitudinal_diffusion_block(const ast::LongitudinalDiffusionBlock& node) override; void visit_lon_diffuse(const ast::LonDiffuse& node) override; - public: /****************************************************************************************/ /* Public printing routines for code generation for use in unit tests */ From 97d461501c13f63d8e1b16691f0706d88d652116 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 29 Oct 2024 16:39:44 +0100 Subject: [PATCH 833/871] Lift shared `print_top_verbatim_blocks` code. (BlueBrain/nmodl#1544) Both the NEURON and CoreNEURON codegen need the same code for printing all top-level verbatim blocks. NMODL Repo SHA: BlueBrain/nmodl@41f0815eef036efe00644b87456680277dc925f1 --- .../codegen_coreneuron_cpp_visitor.cpp | 22 ------------------- .../codegen_coreneuron_cpp_visitor.hpp | 6 ----- src/nmodl/codegen/codegen_cpp_visitor.cpp | 22 +++++++++++++++++++ src/nmodl/codegen/codegen_cpp_visitor.hpp | 5 +++++ 4 files changed, 27 insertions(+), 28 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index b8ce415f62..81ed29c968 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -326,28 +326,6 @@ void CodegenCoreneuronCppVisitor::print_abort_routine() const { /****************************************************************************************/ -void CodegenCoreneuronCppVisitor::print_top_verbatim_blocks() { - if (info.top_verbatim_blocks.empty()) { - return; - } - print_namespace_stop(); - - printer->add_newline(2); - print_using_namespace(); - - printing_top_verbatim_blocks = true; - - for (const auto& block: info.top_verbatim_blocks) { - printer->add_newline(2); - block->accept(*this); - } - - printing_top_verbatim_blocks = false; - - print_namespace_start(); -} - - void CodegenCoreneuronCppVisitor::print_function_prototypes() { if (info.functions.empty() && info.procedures.empty()) { return; diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index 65f26953d6..fefd60df52 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -308,12 +308,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /****************************************************************************************/ - /** - * Print top level (global scope) verbatim blocks - */ - void print_top_verbatim_blocks(); - - /** * Print function and procedures prototype declaration */ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 5c0ffb36a3..9f618bb732 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -675,6 +675,28 @@ void CodegenCppVisitor::print_namespace_stop() { } +void CodegenCppVisitor::print_top_verbatim_blocks() { + if (info.top_verbatim_blocks.empty()) { + return; + } + print_namespace_stop(); + + printer->add_newline(2); + print_using_namespace(); + + printing_top_verbatim_blocks = true; + + for (const auto& block: info.top_verbatim_blocks) { + printer->add_newline(2); + block->accept(*this); + } + + printing_top_verbatim_blocks = false; + + print_namespace_start(); +} + + /****************************************************************************************/ /* Printing routines for code generation */ /****************************************************************************************/ diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index c00b5cba6e..b492d75ec3 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1464,6 +1464,11 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { */ void print_nmodl_constants(); + /** + * Print top level (global scope) verbatim blocks + */ + void print_top_verbatim_blocks(); + /****************************************************************************************/ /* Overloaded visitor routines */ From feec10428376caef439dcc2d13cd5f88789fd59b Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 30 Oct 2024 14:55:39 +0100 Subject: [PATCH 834/871] Refactor variable name of random variables. (BlueBrain/nmodl#1546) NMODL Repo SHA: BlueBrain/nmodl@311c8143635bddbd0483fae1ffe13c298fae9b52 --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 10 ++++++++-- src/nmodl/codegen/codegen_cpp_visitor.cpp | 6 ------ src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 7 ++++--- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index 81ed29c968..820c5bb742 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -830,7 +830,13 @@ std::string CodegenCoreneuronCppVisitor::get_variable_name(const std::string& na auto i = std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), index_comparator); if (i != codegen_int_variables.end()) { - return int_variable_name(*i, varname, use_instance); + auto full_name = int_variable_name(*i, varname, use_instance); + auto pos = position_of_int_var(varname); + + if (info.semantics[pos].name == naming::RANDOM_SEMANTIC) { + return "(nrnran123_State*) " + full_name; + } + return full_name; } // global variable @@ -1638,7 +1644,7 @@ void CodegenCoreneuronCppVisitor::print_instance_variable_setup() { printer->push_block("for (int id = 0; id < nodecount; id++)"); for (const auto& var: info.random_variables) { const auto& name = get_variable_name(var->get_name()); - printer->fmt_line("nrnran123_deletestream((nrnran123_State*){});", name); + printer->fmt_line("nrnran123_deletestream({});", name); } printer->pop_block(); } diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 9f618bb732..50f9d950cf 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -555,12 +555,6 @@ void CodegenCppVisitor::print_function_call(const FunctionCall& node) { } } - // first argument to random functions need to be type casted - // from void* to nrnran123_State*. - if (is_random_function && !arguments.empty()) { - printer->add_text("(nrnran123_State*)"); - } - print_vector_elements(arguments, ", "); printer->add_text(')'); } diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index c449f8bc33..a7e2807056 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -728,7 +728,7 @@ std::string CodegenNeuronCppVisitor::int_variable_name(const IndexVariableInfo& auto position = position_of_int_var(name); if (info.semantics[position].name == naming::RANDOM_SEMANTIC) { - return fmt::format("_ppvar[{}].literal_value()", position); + return fmt::format("(nrnran123_State*) _ppvar[{}].literal_value()", position); } if (info.semantics[position].name == naming::FOR_NETCON_SEMANTIC) { @@ -2034,8 +2034,9 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { if (!info.random_variables.empty()) { for (const auto& rv: info.random_variables) { - printer->fmt_line("{} = nrnran123_newstream();", - get_variable_name(get_name(rv), false)); + auto position = position_of_int_var(get_name(rv)); + printer->fmt_line("_ppvar[{}].literal_value() = nrnran123_newstream();", + position); } printer->fmt_line("nrn_mech_inst_destruct[mech_type] = neuron::{};", method_name(naming::NRN_DESTRUCTOR_METHOD)); From 46eef5f550bb18afab2bb068dbd91eb9c38013ef Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 31 Oct 2024 09:22:42 +0100 Subject: [PATCH 835/871] For CoreNEURON, DERIVATIVE doesn't need sympy. (BlueBrain/nmodl#1547) NMODL Repo SHA: BlueBrain/nmodl@800d098cc3bf658cf7eed314df5870aaf96d9dd8 --- src/nmodl/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index b63b7b0c70..d38c6bef46 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -517,7 +517,7 @@ int run_nmodl(int argc, const char* argv[]) { enable_sympy(solver_exists(*ast, "derivimplicit"), "'SOLVE ... METHOD derivimplicit'"); enable_sympy(node_exists(*ast, ast::AstNodeType::LINEAR_BLOCK), "'LINEAR' block"); - enable_sympy(node_exists(*ast, ast::AstNodeType::DERIVATIVE_BLOCK), + enable_sympy(neuron_code && node_exists(*ast, ast::AstNodeType::DERIVATIVE_BLOCK), "'DERIVATIVE' block"); enable_sympy(node_exists(*ast, ast::AstNodeType::NON_LINEAR_BLOCK), "'NONLINEAR' block"); From 1d3005226b8904705498f9fe12a0f0397f30ef93 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 31 Oct 2024 11:39:34 +0100 Subject: [PATCH 836/871] Codegen for CVODE (BlueBrain/nmodl#1493) NMODL Repo SHA: BlueBrain/nmodl@3df34b988eccdaae03ec324c78d26330950b80ee --- src/nmodl/codegen/codegen_helper_visitor.cpp | 5 + src/nmodl/codegen/codegen_helper_visitor.hpp | 1 + src/nmodl/codegen/codegen_info.hpp | 3 + src/nmodl/codegen/codegen_naming.hpp | 21 +++ .../codegen/codegen_neuron_cpp_visitor.cpp | 156 +++++++++++++++++- .../codegen/codegen_neuron_cpp_visitor.hpp | 42 +++++ test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../transpiler/usecases/cvode/derivative.mod | 37 +++++ .../transpiler/usecases/cvode/test_cvode.py | 52 ++++++ 9 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/cvode/derivative.mod create mode 100644 test/nmodl/transpiler/usecases/cvode/test_cvode.py diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 02a49040d0..000966c9e2 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -630,6 +630,11 @@ void CodegenHelperVisitor::visit_nrn_state_block(const ast::NrnStateBlock& node) node.visit_children(*this); } +void CodegenHelperVisitor::visit_cvode_block(const ast::CvodeBlock& node) { + info.cvode_block = &node; + node.visit_children(*this); +} + void CodegenHelperVisitor::visit_procedure_block(const ast::ProcedureBlock& node) { info.procedures.push_back(&node); diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 24d074f7a2..1e31fef464 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -109,6 +109,7 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void visit_program(const ast::Program& node) override; void visit_factor_def(const ast::FactorDef& node) override; void visit_nrn_state_block(const ast::NrnStateBlock& node) override; + void visit_cvode_block(const ast::CvodeBlock& node) override; void visit_linear_block(const ast::LinearBlock& node) override; void visit_non_linear_block(const ast::NonLinearBlock& node) override; void visit_discrete_block(const ast::DiscreteBlock& node) override; diff --git a/src/nmodl/codegen/codegen_info.hpp b/src/nmodl/codegen/codegen_info.hpp index eaea56467c..4de8d20947 100644 --- a/src/nmodl/codegen/codegen_info.hpp +++ b/src/nmodl/codegen/codegen_info.hpp @@ -458,6 +458,9 @@ struct CodegenInfo { /// nrn_state block const ast::NrnStateBlock* nrn_state_block = nullptr; + /// the CVODE block + const ast::CvodeBlock* cvode_block = nullptr; + /// net receive block for point process const ast::NetReceiveBlock* net_receive_node = nullptr; diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index cd0403c223..397edc49e1 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -182,6 +182,27 @@ static constexpr char THREAD_ARGS_PROTO[] = "_threadargsproto_"; /// prefix for ion variable static constexpr char ION_VARNAME_PREFIX[] = "ion_"; +/// name of CVODE method for counting # of ODEs +static constexpr char CVODE_COUNT_NAME[] = "ode_count"; + +/// name of CVODE method for updating non-stiff systems +static constexpr char CVODE_UPDATE_NON_STIFF_NAME[] = "ode_update_nonstiff"; + +/// name of CVODE method for updating stiff systems +static constexpr char CVODE_UPDATE_STIFF_NAME[] = "ode_update_stiff"; + +/// name of CVODE method for setting up non-stiff systems +static constexpr char CVODE_SETUP_NON_STIFF_NAME[] = "ode_setup_nonstiff"; + +/// name of CVODE method for setting up stiff systems +static constexpr char CVODE_SETUP_STIFF_NAME[] = "ode_setup_stiff"; + +/// name of CVODE method for setting up tolerances +static constexpr char CVODE_SETUP_TOLERANCES_NAME[] = "ode_setup_tolerances"; + +/// name of the CVODE variable (can be arbitrary) +static constexpr char CVODE_VARIABLE_NAME[] = "cvode_ieq"; + /// commonly used variables in verbatim block and how they /// should be mapped to new code generation backends // clang-format off diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index a7e2807056..3db35f7e25 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -978,6 +978,13 @@ void CodegenNeuronCppVisitor::print_mechanism_global_var_structure(bool print_in printer->fmt_line("static Symbol* _{}_sym;", ion.name); } + if (info.emit_cvode) { + printer->add_line("static Symbol** _atollist;"); + printer->push_block("static HocStateTolerance _hoc_state_tol[] ="); + printer->add_line("{0, 0}"); + printer->pop_block(";"); + } + printer->add_line("static int mech_type;"); if (info.point_process) { @@ -1422,16 +1429,22 @@ void CodegenNeuronCppVisitor::print_mechanism_register_regular() { i)); } + if (info.emit_cvode) { + mech_register_args.push_back( + fmt::format("_nrn_mechanism_field{{\"{}\", \"cvodeieq\"}} /* {} */", + naming::CVODE_VARIABLE_NAME, + codegen_int_variables_size)); + } + printer->add_multi_line(fmt::format("{}", fmt::join(mech_register_args, ",\n"))); printer->decrease_indent(); printer->add_line(");"); printer->add_newline(); - printer->fmt_line("hoc_register_prop_size(mech_type, {}, {});", float_variables_size(), - int_variables_size()); + int_variables_size() + static_cast(info.emit_cvode)); for (int i = 0; i < codegen_int_variables_size; ++i) { if (i != info.semantics[i].index) { @@ -1495,8 +1508,20 @@ void CodegenNeuronCppVisitor::print_mechanism_register_regular() { printer->fmt_line("{}._morphology_sym = hoc_lookup(\"morphology\");", global_struct_instance()); } + + if (info.emit_cvode) { + printer->fmt_line("hoc_register_dparam_semantics(mech_type, {}, \"cvodeieq\");", + codegen_int_variables_size); + printer->fmt_line("hoc_register_cvode(mech_type, {}, {}, {}, {});", + method_name(naming::CVODE_COUNT_NAME), + method_name(naming::CVODE_SETUP_TOLERANCES_NAME), + method_name(naming::CVODE_SETUP_NON_STIFF_NAME), + method_name(naming::CVODE_SETUP_STIFF_NAME)); + printer->fmt_line("hoc_register_tolerance(mech_type, _hoc_state_tol, &_atollist);"); + } } + void CodegenNeuronCppVisitor::print_mechanism_register_nothing() { printer->add_line("hoc_register_var(hoc_scalar_double, hoc_vector_double, hoc_intfunc);"); } @@ -1936,9 +1961,9 @@ void CodegenNeuronCppVisitor::print_nrn_alloc() { )CODE"); printer->chain_block("else"); } - if (info.semantic_variable_count) { + if (info.semantic_variable_count || info.emit_cvode) { printer->fmt_line("_ppvar = nrn_prop_datum_alloc(mech_type, {}, _prop);", - info.semantic_variable_count); + info.semantic_variable_count + static_cast(info.emit_cvode)); printer->add_line("_nrn_mechanism_access_dparam(_prop) = _ppvar;"); } printer->add_multi_line(R"CODE( @@ -2384,6 +2409,8 @@ void CodegenNeuronCppVisitor::print_mechanism_variables_macros() { if (info.table_count > 0) { printer->add_line("void _nrn_thread_table_reg(int, nrn_thread_table_check_t);"); } + // for CVODE + printer->add_line("extern void _cvode_abstol(Symbol**, double*, int);"); if (info.for_netcon_used) { printer->add_line("int _nrn_netcon_args(void*, double***);"); } @@ -2457,6 +2484,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines_regular() { print_nrn_alloc(); print_function_prototypes(); print_longitudinal_diffusion_callbacks(); + print_cvode_definitions(); print_point_process_function_definitions(); print_setdata_functions(); print_check_table_entrypoint(); @@ -2605,6 +2633,126 @@ void CodegenNeuronCppVisitor::print_net_receive_common_code() { printer->add_line("double t = nt->_t;"); } +CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::cvode_setup_parameters() { + return {{"", "const _nrn_model_sorted_token&", "", "_sorted_token"}, + {"", "NrnThread*", "", "nt"}, + {"", "Memb_list*", "", "_ml_arg"}, + {"", "int", "", "_type"}}; +} + +CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::cvode_update_parameters() { + ParamVector args = {{"", "_nrn_mechanism_cache_range&", "", "_lmc"}, + {"", fmt::format("{}_Instance&", info.mod_suffix), "", "inst"}, + {"", fmt::format("{}_NodeData&", info.mod_suffix), "", "node_data"}, + {"", "size_t", "", "id"}, + {"", "Datum*", "", "_ppvar"}, + {"", "Datum*", "", "_thread"}, + {"", "NrnThread*", "", "nt"}}; + + if (info.thread_callback_register) { + auto type_name = fmt::format("{}&", thread_variables_struct()); + args.emplace_back("", type_name, "", "_thread_vars"); + } + return args; +} + + +void CodegenNeuronCppVisitor::print_cvode_count() { + printer->fmt_push_block("static constexpr int {}(int _type)", + method_name(naming::CVODE_COUNT_NAME)); + printer->fmt_line("return {};", info.cvode_block->get_n_odes()->get_value()); + printer->pop_block(); + printer->add_newline(2); +} + +void CodegenNeuronCppVisitor::print_cvode_tolerances() { + const CodegenNeuronCppVisitor::ParamVector tolerances_parameters = { + {"", "Prop*", "", "_prop"}, + {"", "int", "", "equation_index"}, + {"", "neuron::container::data_handle*", "", "_pv"}, + {"", "neuron::container::data_handle*", "", "_pvdot"}, + {"", "double*", "", "_atol"}, + {"", "int", "", "_type"}}; + + auto get_param_name = [](const auto& item) { return std::get<3>(item); }; + + auto prop_name = get_param_name(tolerances_parameters[0]); + auto eqindex_name = get_param_name(tolerances_parameters[1]); + auto pv_name = get_param_name(tolerances_parameters[2]); + auto pvdot_name = get_param_name(tolerances_parameters[3]); + auto atol_name = get_param_name(tolerances_parameters[4]); + + printer->fmt_push_block("static void {}({})", + method_name(naming::CVODE_SETUP_TOLERANCES_NAME), + get_parameter_str(tolerances_parameters)); + printer->fmt_line("auto* _ppvar = _nrn_mechanism_access_dparam({});", prop_name); + printer->fmt_line("_ppvar[{}].literal_value() = {};", int_variables_size(), eqindex_name); + printer->fmt_push_block("for (int i = 0; i < {}(0); i++)", + method_name(naming::CVODE_COUNT_NAME)); + printer->fmt_line("{}[i] = _nrn_mechanism_get_param_handle({}, _slist1[i]);", + pv_name, + prop_name); + printer->fmt_line("{}[i] = _nrn_mechanism_get_param_handle({}, _dlist1[i]);", + pvdot_name, + prop_name); + printer->fmt_line("_cvode_abstol(_atollist, {}, i);", atol_name); + printer->pop_block(); + printer->pop_block(); + printer->add_newline(2); +} + +void CodegenNeuronCppVisitor::print_cvode_update(const std::string& name, + const ast::StatementBlock& block) { + printer->fmt_push_block("static int {}({})", + method_name(name), + get_parameter_str(cvode_update_parameters())); + printer->add_line( + "auto v = node_data.node_voltages ? " + "node_data.node_voltages[node_data.nodeindices[id]] : 0.0;"); + + print_statement_block(block, false, false); + + printer->add_line("return 0;"); + printer->pop_block(); + printer->add_newline(2); +} + +void CodegenNeuronCppVisitor::print_cvode_setup(const std::string& setup_name, + const std::string& update_name) { + printer->fmt_push_block("static void {}({})", + method_name(setup_name), + get_parameter_str(cvode_setup_parameters())); + print_entrypoint_setup_code_from_memb_list(); + printer->fmt_line("auto nodecount = _ml_arg->nodecount;"); + printer->push_block("for (int id = 0; id < nodecount; id++)"); + printer->add_line("auto* _ppvar = _ml_arg->pdata[id];"); + printer->add_line( + "auto v = node_data.node_voltages ? " + "node_data.node_voltages[node_data.nodeindices[id]] : 0.0;"); + printer->fmt_line("{}({});", method_name(update_name), get_arg_str(cvode_update_parameters())); + + printer->pop_block(); + printer->pop_block(); + + printer->add_newline(2); +} + +void CodegenNeuronCppVisitor::print_cvode_definitions() { + if (!info.emit_cvode) { + return; + } + + printer->add_newline(2); + printer->add_line("/* Functions related to CVODE codegen */"); + print_cvode_count(); + print_cvode_tolerances(); + print_cvode_update(naming::CVODE_UPDATE_NON_STIFF_NAME, + *info.cvode_block->get_non_stiff_block()); + print_cvode_update(naming::CVODE_UPDATE_STIFF_NAME, *info.cvode_block->get_stiff_block()); + print_cvode_setup(naming::CVODE_SETUP_NON_STIFF_NAME, naming::CVODE_UPDATE_NON_STIFF_NAME); + print_cvode_setup(naming::CVODE_SETUP_STIFF_NAME, naming::CVODE_UPDATE_STIFF_NAME); +} + void CodegenNeuronCppVisitor::print_net_receive() { printing_net_receive = true; auto node = info.net_receive_node; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 823055b303..9325a8b8c9 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -756,6 +756,48 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void print_ion_variable() override; + /** + * Get the parameters for functions that setup (initialize) CVODE + * + */ + ParamVector cvode_setup_parameters(); + + + /** + * Get the parameters for functions that update state at given timestep in CVODE + * + */ + ParamVector cvode_update_parameters(); + + + /** + * Print all callbacks for CVODE + * + */ + void print_cvode_definitions(); + + /** + * Print the CVODE function returning the number of ODEs to solve + */ + void print_cvode_count(); + + /** + * Print the CVODE function for setup of tolerances + */ + void print_cvode_tolerances(); + + /** + * Print the CVODE update function \c name contained in \c block + */ + void print_cvode_update(const std::string& name, const ast::StatementBlock& block); + + /** + * Print the CVODE setup function \c setup_name that calls the CVODE update function + * \c update_name + */ + void print_cvode_setup(const std::string& setup_name, const std::string& update_name); + + /****************************************************************************************/ /* Overloaded visitor routines */ /****************************************************************************************/ diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 22a806a1c8..8efd4372e9 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -3,6 +3,7 @@ set(NMODL_USECASE_DIRS builtin_functions constant constructor + cvode electrode_current external function diff --git a/test/nmodl/transpiler/usecases/cvode/derivative.mod b/test/nmodl/transpiler/usecases/cvode/derivative.mod new file mode 100644 index 0000000000..d3715352ff --- /dev/null +++ b/test/nmodl/transpiler/usecases/cvode/derivative.mod @@ -0,0 +1,37 @@ +NEURON { + SUFFIX scalar +} + +PARAMETER { + freq = 10 + a = 5 + v1 = -1 + v2 = 5 + v3 = 15 + v4 = 0.8 + v5 = 0.3 + r = 3 + k = 0.2 +} + +STATE {var1 var2 var3} + +INITIAL { + var1 = v1 + var2 = v2 + var3 = v3 +} + +BREAKPOINT { + SOLVE equation METHOD derivimplicit +} + + +DERIVATIVE equation { + : eq with a function on RHS + var1' = -sin(freq * t) + : simple ODE (nonzero Jacobian) + var2' = -var2 * a + : logistic ODE + var3' = r * var3 * (1 - var3 / k) +} diff --git a/test/nmodl/transpiler/usecases/cvode/test_cvode.py b/test/nmodl/transpiler/usecases/cvode/test_cvode.py new file mode 100644 index 0000000000..49f148b14a --- /dev/null +++ b/test/nmodl/transpiler/usecases/cvode/test_cvode.py @@ -0,0 +1,52 @@ +import numpy as np +from neuron import gui +from neuron import h +from neuron.units import ms + + +def test_cvode(rtol): + nseg = 1 + mech = "scalar" + + s = h.Section() + cvode = h.CVode() + cvode.active(True) + cvode.atol(1e-10) + s.insert(mech) + s.nseg = nseg + + t_hoc = h.Vector().record(h._ref_t) + var1_hoc = h.Vector().record(getattr(s(0.5), f"_ref_var1_{mech}")) + var2_hoc = h.Vector().record(getattr(s(0.5), f"_ref_var2_{mech}")) + var3_hoc = h.Vector().record(getattr(s(0.5), f"_ref_var3_{mech}")) + + h.stdinit() + h.tstop = 2.0 * ms + h.run() + + freq = getattr(h, f"freq_{mech}") + a = getattr(h, f"a_{mech}") + v1 = getattr(h, f"v1_{mech}") + v2 = getattr(h, f"v2_{mech}") + v3 = getattr(h, f"v3_{mech}") + r = getattr(h, f"r_{mech}") + k = getattr(h, f"k_{mech}") + + t = np.array(t_hoc.as_numpy()) + var1 = np.array(var1_hoc.as_numpy()) + var2 = np.array(var2_hoc.as_numpy()) + var3 = np.array(var3_hoc.as_numpy()) + + var1_exact = (np.cos(t * freq) + v1 * freq - 1) / freq + var2_exact = v2 * np.exp(-t * a) + var3_exact = k * v3 / (v3 + (k - v3) * np.exp(-r * t)) + + np.testing.assert_allclose(var1, var1_exact, rtol=rtol) + np.testing.assert_allclose(var2, var2_exact, rtol=rtol) + np.testing.assert_allclose(var3, var3_exact, rtol=rtol) + + return t, var1, var2, var3 + + +if __name__ == "__main__": + t, *x = test_cvode(rtol=1e-5) From c6450b38b861218306bfcf2dc5cd5a15d34c5f5a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Fri, 1 Nov 2024 09:29:52 +0100 Subject: [PATCH 837/871] Rename `print_parallel_iteration_hint`. (BlueBrain/nmodl#1545) NMODL Repo SHA: BlueBrain/nmodl@095fc757fb822cb165186e9db3d9ff4e0366fa66 --- src/nmodl/codegen/codegen_acc_visitor.cpp | 3 +-- src/nmodl/codegen/codegen_acc_visitor.hpp | 3 +-- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 12 ++++++------ src/nmodl/codegen/codegen_cpp_visitor.cpp | 4 ++-- src/nmodl/codegen/codegen_cpp_visitor.hpp | 3 +-- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/nmodl/codegen/codegen_acc_visitor.cpp b/src/nmodl/codegen/codegen_acc_visitor.cpp index 20624b6df7..3ea644eb1e 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.cpp +++ b/src/nmodl/codegen/codegen_acc_visitor.cpp @@ -31,8 +31,7 @@ namespace codegen { * for(int id=0; idpush_block("for (int id = 0; id < nodecount; id++)"); if (info.net_receive_node != nullptr) { @@ -1902,7 +1902,7 @@ void CodegenCoreneuronCppVisitor::print_before_after_block(const ast::Block* nod printer->fmt_line("/** {} of block type {} # {} */", ba_type, ba_block_type, block_id); print_global_function_common_code(BlockType::BeforeAfter, function_name); - print_channel_iteration_block_parallel_hint(BlockType::BeforeAfter, node); + print_parallel_iteration_hint(BlockType::BeforeAfter, node); printer->push_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); @@ -2031,7 +2031,7 @@ void CodegenCoreneuronCppVisitor::print_watch_check() { // net_receive function we already check if it contains any MUTEX/PROTECT // constructs. As WATCH is not a top level block but list of statements, // we don't need to have ivdep pragma related check - print_channel_iteration_block_parallel_hint(BlockType::Watch, nullptr); + print_parallel_iteration_hint(BlockType::Watch, nullptr); printer->push_block("for (int id = 0; id < nodecount; id++)"); @@ -2345,7 +2345,7 @@ void CodegenCoreneuronCppVisitor::print_get_memb_list() { void CodegenCoreneuronCppVisitor::print_net_receive_loop_begin() { printer->add_line("int count = nrb->_displ_cnt;"); - print_channel_iteration_block_parallel_hint(BlockType::NetReceive, info.net_receive_node); + print_parallel_iteration_hint(BlockType::NetReceive, info.net_receive_node); printer->push_block("for (int i = 0; i < count; i++)"); } @@ -2647,7 +2647,7 @@ void CodegenCoreneuronCppVisitor::print_nrn_state() { printer->add_newline(2); printer->add_line("/** update state */"); print_global_function_common_code(BlockType::State); - print_channel_iteration_block_parallel_hint(BlockType::State, info.nrn_state_block); + print_parallel_iteration_hint(BlockType::State, info.nrn_state_block); printer->push_block("for (int id = 0; id < nodecount; id++)"); printer->add_line("int node_id = node_index[id];"); @@ -2860,7 +2860,7 @@ void CodegenCoreneuronCppVisitor::print_nrn_cur() { printer->add_newline(2); printer->add_line("/** update current */"); print_global_function_common_code(BlockType::Equation); - print_channel_iteration_block_parallel_hint(BlockType::Equation, info.breakpoint_node); + print_parallel_iteration_hint(BlockType::Equation, info.breakpoint_node); printer->push_block("for (int id = 0; id < nodecount; id++)"); print_nrn_cur_kernel(*info.breakpoint_node); print_nrn_cur_matrix_shadow_update(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 50f9d950cf..004c7d959c 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -390,8 +390,8 @@ std::string CodegenCppVisitor::breakpoint_current(std::string current) const { * for(int id = 0; id < nodecount; id++) { * \endcode */ -void CodegenCppVisitor::print_channel_iteration_block_parallel_hint(BlockType /* type */, - const ast::Block* block) { +void CodegenCppVisitor::print_parallel_iteration_hint(BlockType /* type */, + const ast::Block* block) { // ivdep allows SIMD parallelisation of a block/loop but doesn't provide // a standard mechanism for atomics. Also, even with openmp 5.0, openmp // atomics do not enable vectorisation under "omp simd" (gives compiler diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index b492d75ec3..5fbe2fe4fd 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -804,8 +804,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { * * \param type The block type */ - virtual void print_channel_iteration_block_parallel_hint(BlockType type, - const ast::Block* block); + virtual void print_parallel_iteration_hint(BlockType type, const ast::Block* block); /****************************************************************************************/ From 04bd752e8713b247e2ee0fd28c37c8b46e76c10f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 4 Nov 2024 14:25:18 +0100 Subject: [PATCH 838/871] Initial attempt at implementing VERBATIM. (BlueBrain/nmodl#1364) NMODL Repo SHA: BlueBrain/nmodl@af7c88227631e0f0f3f7f457bea9684d508cb4ae --- .../codegen_coreneuron_cpp_visitor.hpp | 1 - src/nmodl/codegen/codegen_cpp_visitor.cpp | 1 + src/nmodl/codegen/codegen_cpp_visitor.hpp | 4 + src/nmodl/codegen/codegen_naming.hpp | 18 ++ .../codegen/codegen_neuron_cpp_visitor.cpp | 203 +++++++++++++++++- .../codegen/codegen_neuron_cpp_visitor.hpp | 41 +++- src/nmodl/main.cpp | 1 + src/nmodl/symtab/symbol_table.cpp | 1 - test/nmodl/transpiler/usecases/CMakeLists.txt | 3 +- .../transpiler/usecases/verbatim/globals.mod | 14 ++ .../usecases/verbatim/internal_function.mod | 14 ++ .../transpiler/usecases/verbatim/locals.mod | 26 +++ .../transpiler/usecases/verbatim/nothing.mod | 11 + .../usecases/verbatim/pointer_in_double.mod | 36 ++++ .../usecases/verbatim/test_globals.py | 14 ++ .../verbatim/test_internal_function.py | 11 + .../usecases/verbatim/test_locals.py | 13 ++ .../usecases/verbatim/test_nothing.py | 9 + .../verbatim/test_pointer_in_double.py | 23 ++ 19 files changed, 439 insertions(+), 5 deletions(-) create mode 100644 test/nmodl/transpiler/usecases/verbatim/globals.mod create mode 100644 test/nmodl/transpiler/usecases/verbatim/internal_function.mod create mode 100644 test/nmodl/transpiler/usecases/verbatim/locals.mod create mode 100644 test/nmodl/transpiler/usecases/verbatim/nothing.mod create mode 100644 test/nmodl/transpiler/usecases/verbatim/pointer_in_double.mod create mode 100644 test/nmodl/transpiler/usecases/verbatim/test_globals.py create mode 100644 test/nmodl/transpiler/usecases/verbatim/test_internal_function.py create mode 100644 test/nmodl/transpiler/usecases/verbatim/test_locals.py create mode 100644 test/nmodl/transpiler/usecases/verbatim/test_nothing.py create mode 100644 test/nmodl/transpiler/usecases/verbatim/test_pointer_in_double.py diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index fefd60df52..c43c2516f9 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -123,7 +123,6 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { */ std::string process_verbatim_token(const std::string& token); - /** * Check if variable is qualified as constant * \param name The name of variable diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 004c7d959c..73c32ce788 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -13,6 +13,7 @@ #include "ast/all.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "codegen/codegen_utils.hpp" +#include "utils/string_utils.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/rename_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 5fbe2fe4fd..24791b3a80 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -169,6 +169,10 @@ struct ShadowUseStatement { std::string rhs; }; +inline std::string get_name(ast::Ast const* sym) { + return sym->get_node_name(); +} + inline std::string get_name(const std::shared_ptr& sym) { return sym->get_name(); } diff --git a/src/nmodl/codegen/codegen_naming.hpp b/src/nmodl/codegen/codegen_naming.hpp index 397edc49e1..53d63095c2 100644 --- a/src/nmodl/codegen/codegen_naming.hpp +++ b/src/nmodl/codegen/codegen_naming.hpp @@ -176,9 +176,27 @@ static constexpr char NRN_WATCH_CHECK_METHOD[] = "nrn_watch_check"; /// verbatim name of the variable for nrn thread arguments static constexpr char THREAD_ARGS[] = "_threadargs_"; +/// verbatim name of the variable for nrn thread arguments, sometimes with trailing comma +static constexpr char THREAD_ARGS_COMMA[] = "_threadargscomma_"; + /// verbatim name of the variable for nrn thread arguments in prototype static constexpr char THREAD_ARGS_PROTO[] = "_threadargsproto_"; +/// verbatim name of the variable for nrn thread arguments in prototype and a comma +static constexpr char THREAD_ARGS_PROTO_COMMA[] = "_threadargsprotocomma_"; + +/// variation of `_threadargs_` for "internal" functions. +static constexpr char INTERNAL_THREAD_ARGS[] = "_internalthreadargs_"; + +/// variation of `_threadargs_` for "internal" functions, with comma (maybe). +static constexpr char INTERNAL_THREAD_ARGS_COMMA[] = "_internalthreadargscomma_"; + +/// variation of `_threadargsproto_` for "internal" functions. +static constexpr char INTERNAL_THREAD_ARGS_PROTO[] = "_internalthreadargsproto_"; + +/// variation of `_threadargsproto_` for "internal" functions, possibly with comma. +static constexpr char INTERNAL_THREAD_ARGS_PROTO_COMMA[] = "_internalthreadargsprotocomma_"; + /// prefix for ion variable static constexpr char ION_VARNAME_PREFIX[] = "ion_"; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 3db35f7e25..1113c7ad6e 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -16,10 +16,12 @@ #include #include "ast/all.hpp" +#include "ast/procedure_block.hpp" #include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_utils.hpp" #include "codegen_naming.hpp" #include "config/config.h" +#include "parser/c11_driver.hpp" #include "solver/solver.hpp" #include "utils/string_utils.hpp" #include "visitors/rename_visitor.hpp" @@ -610,6 +612,21 @@ const CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::external_method_pa } +CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::internalthreadargs_parameters() { + return internal_method_parameters(); +} + + +CodegenCppVisitor::ParamVector CodegenNeuronCppVisitor::threadargs_parameters() { + return {{"", "Memb_list*", "", "_ml"}, + {"", "size_t", "", "_iml"}, + {"", "Datum*", "", "_ppvar"}, + {"", "Datum*", "", "_thread"}, + {"", "double*", "", "_globals"}, + {"", "NrnThread*", "", "_nt"}}; +} + + /// TODO: Edit for NEURON std::string CodegenNeuronCppVisitor::nrn_thread_arguments() const { return {}; @@ -632,8 +649,176 @@ CodegenNeuronCppVisitor::function_table_parameters(const ast::FunctionTableBlock } +/** Map of the non-(global/top-local) LOCAL variables. + * + * The map associates the name in the MOD file, e.g. `a` with + * the current name of that LOCAL variable, e.g. `a_r_4`. + * + * auto map = get_nonglobal_local_variable_names(); + * assert map["a"] == "a_r_4"; + * + * The two names can differ, because an early pass makes all + * names unique by renaming local variables. + */ +std::unordered_map get_nonglobal_local_variable_names( + const symtab::SymbolTable& symtab) { + if (symtab.global_scope()) { + return {}; + } + + auto local_variables = symtab.get_variables(NmodlType::local_var); + auto parent_symtab = symtab.get_parent_table(); + if (parent_symtab == nullptr) { + throw std::runtime_error( + "Internal NMODL error: non top-level symbol table doesn't have a parent."); + } + + auto variable_names = get_nonglobal_local_variable_names(*parent_symtab); + + for (const auto& symbol: local_variables) { + auto status = symbol->get_status(); + bool is_renamed = (status & symtab::syminfo::Status::renamed) != + symtab::syminfo::Status::empty; + auto current_name = symbol->get_name(); + auto mod_name = is_renamed ? symbol->get_original_name() : current_name; + + variable_names[mod_name] = current_name; + } + + return variable_names; +} + + +std::vector CodegenNeuronCppVisitor::print_verbatim_setup( + const ast::Verbatim& node, + const std::string& verbatim) { + // Note, the logic for reducing the number of macros printed, aims to + // improve legibility of the generated code by reducing number of lines of + // code. It would be correct to print all macros, because that's + // essentially what NOCMODL does. Therefore, the logic isn't sharp (and + // doesn't have to be). + + std::vector macros_defined; + auto print_macro = [this, &verbatim, ¯os_defined](const std::string& macro_name, + const std::string& macro_value) { + if (verbatim.find(macro_name) != std::string::npos) { + printer->fmt_line("#define {} {}", macro_name, macro_value); + macros_defined.push_back(macro_name); + } + }; + + printer->add_line("// Setup for VERBATIM"); + for (const auto& var: codegen_float_variables) { + auto name = get_name(var); + print_macro(name, get_variable_name(name)); + } + + for (const auto& var: codegen_int_variables) { + auto name = get_name(var); + std::string macro_value = get_variable_name(name); + print_macro(name, macro_value); + if (verbatim.find("_p_" + name) != std::string::npos) { + print_macro("_p_" + name, get_pointer_name(name)); + } + } + + for (const auto& var: codegen_global_variables) { + auto name = get_name(var); + print_macro(name, get_variable_name(name)); + } + + for (const auto& var: codegen_thread_variables) { + auto name = get_name(var); + print_macro(name, get_variable_name(name)); + } + + for (const auto& func: info.functions) { + auto name = get_name(func); + print_macro(name, method_name(name)); + print_macro(fmt::format("_l{}", name), fmt::format("ret_{}", name)); + } + + for (const auto& proc: info.procedures) { + auto name = get_name(proc); + print_macro(name, method_name(name)); + } + + + auto symtab = node.get_parent()->get_symbol_table(); + auto locals = get_nonglobal_local_variable_names(*symtab); + for (const auto& [mod_name, current_name]: locals) { + print_macro(fmt::format("_l{}", mod_name), get_variable_name(current_name)); + } + + print_macro(naming::NTHREAD_T_VARIABLE, "nt->_t"); + print_macro("_nt", "nt"); + print_macro("_tqitem", "tqitem"); + + auto print_args_macro = [this, print_macro](const std::string& macro_basename, + const ParamVector& params) { + print_macro("_" + macro_basename + "_", get_arg_str(params)); + print_macro("_" + macro_basename + "comma_", get_arg_str(params) + ","); + print_macro("_" + macro_basename + "proto_", get_parameter_str(params)); + print_macro("_" + macro_basename + "protocomma_", get_parameter_str(params) + ","); + }; + + print_args_macro("internalthreadargs", internalthreadargs_parameters()); + print_args_macro("threadargs", threadargs_parameters()); + + return macros_defined; +} + + +void CodegenNeuronCppVisitor::print_verbatim_cleanup( + const std::vector& macros_defined) { + for (const auto& macro: macros_defined) { + printer->fmt_line("#undef {}", macro); + } + printer->add_line("// End of cleanup for VERBATIM"); +} + + +std::string CodegenNeuronCppVisitor::process_verbatim_text(const std::string& verbatim) { + parser::CDriver driver; + driver.scan_string(verbatim); + auto tokens = driver.all_tokens(); + std::string result; + for (size_t i = 0; i < tokens.size(); i++) { + auto token = tokens[i]; + + // check if we have function call in the verbatim block where + // function is defined in the same mod file + if (program_symtab->is_method_defined(token) && tokens[i + 1] == "(") { + result += token + "("; + if (tokens[i + 2] == naming::THREAD_ARGS) { + result += naming::INTERNAL_THREAD_ARGS; + } else if (tokens[i + 2] == naming::THREAD_ARGS_COMMA) { + result += naming::INTERNAL_THREAD_ARGS_COMMA; + } else { + result += tokens[i + 2]; + } + + i += 2; + } else { + result += token; + } + } + return result; +} + + void CodegenNeuronCppVisitor::visit_verbatim(const Verbatim& node) { - // Not implemented yet. + const auto& verbatim_code = node.get_statement()->eval(); + auto massaged_verbatim = process_verbatim_text(verbatim_code); + + auto macros_defined = print_verbatim_setup(node, massaged_verbatim); + printer->add_line("// Begin VERBATIM"); + const auto& lines = stringutils::split_string(massaged_verbatim, '\n'); + for (const auto& line: lines) { + printer->add_line(line); + } + printer->add_line("// End VERBATIM"); + print_verbatim_cleanup(macros_defined); } @@ -793,6 +978,20 @@ std::string CodegenNeuronCppVisitor::global_variable_name(const SymbolType& symb } +std::string CodegenNeuronCppVisitor::get_pointer_name(const std::string& name) const { + auto name_comparator = [&name](const auto& sym) { return name == get_name(sym); }; + + auto var = + std::find_if(codegen_int_variables.begin(), codegen_int_variables.end(), name_comparator); + + if (var == codegen_int_variables.end()) { + throw std::runtime_error("Only integer variables have a 'pointer name'."); + } + auto position = position_of_int_var(name); + return fmt::format("_ppvar[{}].literal_value()", position); +} + + std::string CodegenNeuronCppVisitor::get_variable_name(const std::string& name, bool use_instance) const { std::string varname = update_if_ion_variable_name(name); @@ -2488,6 +2687,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines_regular() { print_point_process_function_definitions(); print_setdata_functions(); print_check_table_entrypoint(); + print_top_verbatim_blocks(); print_functors_definitions(); print_global_variables_for_hoc(); print_thread_memory_callbacks(); @@ -2505,6 +2705,7 @@ void CodegenNeuronCppVisitor::print_codegen_routines_nothing() { print_headers_include(); print_namespace_start(); print_function_prototypes(); + print_top_verbatim_blocks(); print_global_variables_for_hoc(); print_function_definitions(); print_mechanism_register(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 9325a8b8c9..c0aa55f830 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -24,6 +24,8 @@ #include #include +#include "ast/function_block.hpp" +#include "ast/procedure_block.hpp" #include "codegen/codegen_cpp_visitor.hpp" #include "codegen/codegen_info.hpp" #include "codegen/codegen_naming.hpp" @@ -343,6 +345,14 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { const ParamVector external_method_parameters(bool table = false) noexcept override; + /** The parameters for the four macros `_internalthreadargs*_`. */ + ParamVector internalthreadargs_parameters(); + + + /** The parameters for the four macros `_threadargs*_`. */ + ParamVector threadargs_parameters(); + + /** * Arguments for "_threadargs_" macro in neuron implementation */ @@ -358,6 +368,19 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { const ast::FunctionTableBlock& /* node */) override; + /** Print compatibility macros required for VERBATIM blocks. + * + * Returns the names of all macros introduced. + */ + std::vector print_verbatim_setup(const ast::Verbatim& node, + const std::string& verbatim); + + + /** Print `#undef`s to erase all compatibility macros. + */ + void print_verbatim_cleanup(const std::vector& macros_defined); + + /** * Arguments for register_mech or point_register_mech function */ @@ -462,7 +485,10 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /** - * Determine variable name in the structure of mechanism properties + * Determine the C++ string to replace variable names with. + * + * Given a variable name such as `ion_cai` or `v`, return the C++ code + * required to get the value. * * \param name Variable name that is being printed * \param use_instance Should the variable be accessed via instance or data array @@ -471,6 +497,18 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { */ std::string get_variable_name(const std::string& name, bool use_instance = true) const override; + /** + * Determine the C++ string to replace pointer names with. + * + * Given a variable name such as `_p_ptr` or `_p_rng`, return the C++ code + * required to get a pointer to `ptr` (or `rng`). + * + * \param name Variable name that is being printed + * \return The C++ string representing the variable. + * thread structure + */ + std::string get_pointer_name(const std::string& name) const; + /****************************************************************************************/ /* Main printing routines for code generation */ @@ -802,6 +840,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Overloaded visitor routines */ /****************************************************************************************/ + std::string process_verbatim_text(const std::string& verbatim); void visit_verbatim(const ast::Verbatim& node) override; void visit_watch_statement(const ast::WatchStatement& node) override; void visit_for_netcon(const ast::ForNetcon& node) override; diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index d38c6bef46..9481d2527b 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -294,6 +294,7 @@ int run_nmodl(int argc, const char* argv[]) { CLI11_PARSE(app, argc, argv); std::string simulator_name = neuron_code ? "neuron" : "coreneuron"; + verbatim_rename = neuron_code ? false : verbatim_rename; fs::create_directories(output_dir); fs::create_directories(scratch_dir); diff --git a/src/nmodl/symtab/symbol_table.cpp b/src/nmodl/symtab/symbol_table.cpp index 06dfab5a53..c313672c01 100644 --- a/src/nmodl/symtab/symbol_table.cpp +++ b/src/nmodl/symtab/symbol_table.cpp @@ -116,7 +116,6 @@ std::vector> SymbolTable::get_variables(NmodlType with, return result; } - std::vector> SymbolTable::get_variables_with_status(Status status, bool all) const { std::vector> variables; diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index 8efd4372e9..f776c578dd 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -31,7 +31,8 @@ set(NMODL_USECASE_DIRS steady_state suffix table - useion) + useion + verbatim) foreach(usecase ${NMODL_USECASE_DIRS}) add_test(NAME usecase_${usecase} diff --git a/test/nmodl/transpiler/usecases/verbatim/globals.mod b/test/nmodl/transpiler/usecases/verbatim/globals.mod new file mode 100644 index 0000000000..7d72932171 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/globals.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX globals + GLOBAL gbl +} + +ASSIGNED { + gbl +} + +FUNCTION get_gbl() { +VERBATIM +_lget_gbl = gbl; +ENDVERBATIM +} diff --git a/test/nmodl/transpiler/usecases/verbatim/internal_function.mod b/test/nmodl/transpiler/usecases/verbatim/internal_function.mod new file mode 100644 index 0000000000..eff6756c8e --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/internal_function.mod @@ -0,0 +1,14 @@ +NEURON { + SUFFIX internal_function + THREADSAFE +} + +FUNCTION f() { +VERBATIM + return g(_threadargs_); +ENDVERBATIM +} + +FUNCTION g() { + g = 42.0 +} diff --git a/test/nmodl/transpiler/usecases/verbatim/locals.mod b/test/nmodl/transpiler/usecases/verbatim/locals.mod new file mode 100644 index 0000000000..c5fd8b29b3 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/locals.mod @@ -0,0 +1,26 @@ +NEURON { + SUFFIX locals +} + +PARAMETER { + a = -1.0 +} + +FUNCTION get_a() { LOCAL a + a = 32.0 +VERBATIM + _lget_a = _la; +ENDVERBATIM +} + +FUNCTION get_b() { LOCAL a, b + a = -1.0 + b = 32.0 + { LOCAL a + a = 100.0 + b = b + a + VERBATIM + _lget_b = _lb; + ENDVERBATIM + } +} diff --git a/test/nmodl/transpiler/usecases/verbatim/nothing.mod b/test/nmodl/transpiler/usecases/verbatim/nothing.mod new file mode 100644 index 0000000000..e100e9ddb0 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/nothing.mod @@ -0,0 +1,11 @@ +NEURON { SUFFIX nothing } + +VERBATIM +double foo = 42.0; +ENDVERBATIM + +FUNCTION get_foo() { +VERBATIM + return foo; +ENDVERBATIM +} diff --git a/test/nmodl/transpiler/usecases/verbatim/pointer_in_double.mod b/test/nmodl/transpiler/usecases/verbatim/pointer_in_double.mod new file mode 100644 index 0000000000..033f840773 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/pointer_in_double.mod @@ -0,0 +1,36 @@ +NEURON { + SUFFIX pointer_in_double + THREADSAFE +} + +ASSIGNED { + ptr +} + +VERBATIM +struct SomeDouble { + SomeDouble(double _value) : _value(_value) {} + + double value() const { + return _value; + } + + double _value; +}; + +static std::vector some_doubles; +ENDVERBATIM + +INITIAL { +VERBATIM + some_doubles.reserve(10); + some_doubles.push_back(SomeDouble(double(some_doubles.size()))); + *reinterpret_cast(&ptr) = &some_doubles.back(); +ENDVERBATIM +} + +FUNCTION use_pointer() { +VERBATIM + return (*reinterpret_cast(&ptr))->value(); +ENDVERBATIM +} diff --git a/test/nmodl/transpiler/usecases/verbatim/test_globals.py b/test/nmodl/transpiler/usecases/verbatim/test_globals.py new file mode 100644 index 0000000000..d9f530da93 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/test_globals.py @@ -0,0 +1,14 @@ +from neuron import h, gui + + +def test_globals(): + s = h.Section() + s.insert("globals") + + h.gbl_globals = 654.0 + + assert s(0.5).globals.get_gbl() == h.gbl_globals + + +if __name__ == "__main__": + test_globals() diff --git a/test/nmodl/transpiler/usecases/verbatim/test_internal_function.py b/test/nmodl/transpiler/usecases/verbatim/test_internal_function.py new file mode 100644 index 0000000000..b75e9a5558 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/test_internal_function.py @@ -0,0 +1,11 @@ +from neuron import h + + +def test_internal_function(): + s = h.Section() + s.nseg = 4 + + s.insert("internal_function") + + assert s.internal_function.g() == 42.0 + assert s.internal_function.f() == 42.0 diff --git a/test/nmodl/transpiler/usecases/verbatim/test_locals.py b/test/nmodl/transpiler/usecases/verbatim/test_locals.py new file mode 100644 index 0000000000..b0c6730526 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/test_locals.py @@ -0,0 +1,13 @@ +from neuron import h, gui + + +def test_locals(): + s = h.Section() + s.insert("locals") + + assert s(0.5).locals.get_a() == 32.0 + assert s(0.5).locals.get_b() == 132.0 + + +if __name__ == "__main__": + test_locals() diff --git a/test/nmodl/transpiler/usecases/verbatim/test_nothing.py b/test/nmodl/transpiler/usecases/verbatim/test_nothing.py new file mode 100644 index 0000000000..207d38cbde --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/test_nothing.py @@ -0,0 +1,9 @@ +from neuron import h, gui + + +def test_nothing(): + assert h.get_foo() == 42.0 + + +if __name__ == "__main__": + test_nothing() diff --git a/test/nmodl/transpiler/usecases/verbatim/test_pointer_in_double.py b/test/nmodl/transpiler/usecases/verbatim/test_pointer_in_double.py new file mode 100644 index 0000000000..3bf76f4907 --- /dev/null +++ b/test/nmodl/transpiler/usecases/verbatim/test_pointer_in_double.py @@ -0,0 +1,23 @@ +from neuron import h, gui + +# Since a double is 64-bits and a pointer is also 32 or 64-bits, we can store +# the bits of a pointer in the memory that was allocated for use as doubles. +# Concretely, we, using VERBATIM, safe pointers in ASSIGNED or RANGE variables. +# +# The pattern is found in the builtin mod file: `apcount.mod`. + + +def test_pointer_in_double(): + s = h.Section() + s.nseg = 3 + s.insert("pointer_in_double") + + h.finitialize() + for i, x in enumerate([0.25, 0.5, 0.75]): + actual = s(x).pointer_in_double.use_pointer() + expected = i + assert actual == expected, f"{actual} != {expected}" + + +if __name__ == "__main__": + test_pointer_in_double() From 0215885b789a1bca601cccdea754e44acdf8980a Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 6 Nov 2024 09:31:20 +0100 Subject: [PATCH 839/871] Remove superfluous Codegen ctor. (BlueBrain/nmodl#1548) NMODL Repo SHA: BlueBrain/nmodl@65a6678413905a5177d3a41be799e8bdbc269ebb --- src/nmodl/codegen/codegen_cpp_visitor.hpp | 28 ----------------------- src/nmodl/main.cpp | 8 ++++--- 2 files changed, 5 insertions(+), 31 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 24791b3a80..74a8593410 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -240,34 +240,6 @@ using printer::CodePrinter; */ class CodegenCppVisitor: public visitor::ConstAstVisitor { public: - /** - * \brief Constructs the C++ code generator visitor - * - * This constructor instantiates an NMODL C++ code generator and allows writing generated code - * directly to a file in \c [output_dir]/[mod_filename].cpp. - * - * \note No code generation is performed at this stage. Since the code - * generator classes are all based on \c AstVisitor the AST must be visited using e.g. \c - * visit_program in order to generate the C++ code corresponding to the AST. - * - * \param mod_filename The name of the model for which code should be generated. - * It is used for constructing an output filename. - * \param output_dir The directory where target C++ file should be generated. - * \param float_type The float type to use in the generated code. The string will be used - * as-is in the target code. This defaults to \c double. - */ - CodegenCppVisitor(std::string mod_filename, - const std::string& output_dir, - std::string float_type, - const bool optimize_ionvar_copies, - std::unique_ptr blame) - : printer(std::make_unique(output_dir + "/" + mod_filename + ".cpp", - std::move(blame))) - , mod_filename(std::move(mod_filename)) - , float_type(std::move(float_type)) - , optimize_ionvar_copies(optimize_ionvar_copies) {} - - /** * \brief Constructs the C++ code generator visitor * diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 9481d2527b..393021e7b7 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -601,12 +601,14 @@ int run_nmodl(int argc, const char* argv[]) { } { + auto output_stream = std::ofstream(std::filesystem::path(output_dir) / + (modfile + ".cpp")); auto blame_level = detailed_blame ? utils::BlameLevel::Detailed : utils::BlameLevel::Short; if (coreneuron_code && oacc_backend) { logger->info("Running OpenACC backend code generator for CoreNEURON"); CodegenAccVisitor visitor(modfile, - output_dir, + output_stream, data_type, optimize_ionvar_copies_codegen, utils::make_blame(blame_line, blame_level)); @@ -616,7 +618,7 @@ int run_nmodl(int argc, const char* argv[]) { else if (coreneuron_code && !neuron_code && cpp_backend) { logger->info("Running C++ backend code generator for CoreNEURON"); CodegenCoreneuronCppVisitor visitor(modfile, - output_dir, + output_stream, data_type, optimize_ionvar_copies_codegen, utils::make_blame(blame_line, blame_level)); @@ -626,7 +628,7 @@ int run_nmodl(int argc, const char* argv[]) { else if (neuron_code && cpp_backend) { logger->info("Running C++ backend code generator for NEURON"); CodegenNeuronCppVisitor visitor(modfile, - output_dir, + output_stream, data_type, optimize_ionvar_copies_codegen, utils::make_blame(blame_line, blame_level)); From e5f73d507738f8ca29ccbf708adb4378b8d3571f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 6 Nov 2024 09:31:40 +0100 Subject: [PATCH 840/871] Extend `reindent_text(..., indent_level)`. (BlueBrain/nmodl#1549) NMODL Repo SHA: BlueBrain/nmodl@fc640975b8f3659b5f10b63fbac46a8ca6bf6dbb --- test/nmodl/transpiler/unit/utils/test_utils.cpp | 5 +++-- test/nmodl/transpiler/unit/utils/test_utils.hpp | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/test/nmodl/transpiler/unit/utils/test_utils.cpp b/test/nmodl/transpiler/unit/utils/test_utils.cpp index c9d9ee3679..431bbac027 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.cpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.cpp @@ -50,12 +50,13 @@ NEURON { * nmodl, the nmodl output is without "extra" whitespaces in the provided input. */ -std::string reindent_text(const std::string& text) { +std::string reindent_text(const std::string& text, int indent_level) { std::string indented_text; int num_whitespaces = 0; bool flag = false; std::string line; std::stringstream stream(text); + std::string indent(4 * indent_level, ' '); while (std::getline(stream, line)) { if (!line.empty()) { @@ -71,7 +72,7 @@ std::string reindent_text(const std::string& text) { } line.erase(0, num_whitespaces); - indented_text += line; + indented_text += indent + line; } /// discard empty lines at very beginning if (!stream.eof() && flag) { diff --git a/test/nmodl/transpiler/unit/utils/test_utils.hpp b/test/nmodl/transpiler/unit/utils/test_utils.hpp index 22355b7618..5bc9b7adbf 100644 --- a/test/nmodl/transpiler/unit/utils/test_utils.hpp +++ b/test/nmodl/transpiler/unit/utils/test_utils.hpp @@ -12,7 +12,7 @@ namespace nmodl { namespace test_utils { -std::string reindent_text(const std::string& text); +std::string reindent_text(const std::string& text, int indent_level = 0); } // namespace test_utils } // namespace nmodl From 51b2b90b9f4dfba8c29debe0dece1fd7eddc399e Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 6 Nov 2024 10:01:29 +0100 Subject: [PATCH 841/871] Hide `CodegenCppVisitor::setup`. (BlueBrain/nmodl#1550) NMODL Repo SHA: BlueBrain/nmodl@da2ae9ff7ebf3a419f175e10a3d28e977b6945f3 --- src/nmodl/codegen/codegen_cpp_visitor.hpp | 14 ++-- .../codegen_coreneuron_cpp_visitor.cpp | 71 ++++++------------- .../codegen/codegen_neuron_cpp_visitor.cpp | 1 - 3 files changed, 25 insertions(+), 61 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 74a8593410..5a958ad5bf 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1480,14 +1480,11 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::string compute_method_name(BlockType type) const; - public: - /** Setup the target backend code generator - * - * Typically called from within \c visit\_program but may be called from - * specialized targets to setup this Code generator as fallback. - */ + // Automatically called as part of `visit_program`. void setup(const ast::Program& node); + public: + /** * Main and only member function to call after creating an instance of this class. @@ -1496,10 +1493,7 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_program(const ast::Program& program) override; - /****************************************************************************************/ - /* Public printing routines for code generation for use in unit tests */ - /****************************************************************************************/ - + protected: /** * Print the structure that wraps all range and int variables required for the NMODL diff --git a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp index 365ddf7087..21f5e2742f 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -36,17 +36,18 @@ std::shared_ptr create_coreneuron_cpp_visitor( const std::shared_ptr& ast, const std::string& /* text */, std::stringstream& ss) { + ImplicitArgumentVisitor().visit_program(*ast); /// construct symbol table SymtabVisitor().visit_program(*ast); /// run all necessary pass + PerfVisitor().visit_program(*ast); InlineVisitor().visit_program(*ast); NeuronSolveVisitor().visit_program(*ast); SolveBlockVisitor().visit_program(*ast); /// create C code generation visitor auto cv = std::make_shared("temp.mod", ss, "double", false); - cv->setup(*ast); return cv; } @@ -64,19 +65,9 @@ std::shared_ptr create_acc_visitor(const std::shared_ptr("temp.mod", ss, "double", false); - cv->setup(*ast); return cv; } -/// print instance structure for testing purpose -std::string get_instance_var_setup_function(std::string& nmodl_text) { - const auto& ast = NmodlDriver().parse_string(nmodl_text); - std::stringstream ss; - auto cvisitor = create_coreneuron_cpp_visitor(ast, nmodl_text, ss); - cvisitor->print_instance_variable_setup(); - return reindent_text(ss.str()); -} - /// print entire code std::string get_coreneuron_cpp_code(const std::string& nmodl_text, const bool generate_gpu_code = false) { @@ -147,10 +138,9 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_cao = nt->_data; inst->ion_ica = nt->_data; inst->ion_dicadv = nt->_data; - } - )"; - auto const expected = reindent_text(generated_code); - auto const result = get_instance_var_setup_function(nmodl_text); + })"; + auto const expected = reindent_text(generated_code, 1); + auto const result = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(result, ContainsSubstring(expected)); } } @@ -195,11 +185,10 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->v_unused = ml->data+4*pnodecount; inst->ion_cai = nt->_data; inst->ion_cao = nt->_data; - } - )"; + })"; - auto const expected = reindent_text(generated_code); - auto const result = get_instance_var_setup_function(nmodl_text); + auto const expected = reindent_text(generated_code, 1); + auto const result = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(result, ContainsSubstring(expected)); } } @@ -291,33 +280,15 @@ SCENARIO("Check instance variable definition order", "[codegen][var_order]") { inst->ion_ko = nt->_data; inst->ion_k_erev = nt->_data; inst->style_k = ml->pdata; - } - )"; + })"; - auto const expected = reindent_text(generated_code); - auto const result = get_instance_var_setup_function(nmodl_text); + auto const expected = reindent_text(generated_code, 1); + auto const result = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(result, ContainsSubstring(expected)); } } } -std::string get_instance_structure(std::string nmodl_text) { - // parse mod file & print mechanism structure - auto const ast = NmodlDriver{}.parse_string(nmodl_text); - // add implicit arguments - ImplicitArgumentVisitor{}.visit_program(*ast); - // update the symbol table for PerfVisitor - SymtabVisitor{}.visit_program(*ast); - // we need the read/write counts so the codegen knows whether or not - // global variables are used - PerfVisitor{}.visit_program(*ast); - // setup codegen - std::stringstream ss{}; - CodegenCoreneuronCppVisitor cv{"temp.mod", ss, "double", false}; - cv.setup(*ast); - cv.print_mechanism_range_var_structure(true); - return ss.str(); -} SCENARIO("Check parameter constness with VERBATIM block", "[codegen][verbatim_variable_constness]") { @@ -343,7 +314,7 @@ SCENARIO("Check parameter constness with VERBATIM block", )"; THEN("Variable used in VERBATIM shouldn't be marked as const") { - auto const generated = get_instance_structure(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); std::string expected_code = R"( /** all mechanism instance variables and global variables */ struct IntervalFire_Instance { @@ -351,9 +322,9 @@ SCENARIO("Check parameter constness with VERBATIM block", const double* burst_start{}; double* v_unused{}; IntervalFire_Store* global{&IntervalFire_global}; - }; - )"; - REQUIRE(reindent_text(generated) == reindent_text(expected_code)); + };)"; + expected_code = reindent_text(expected_code, 1); + REQUIRE_THAT(generated, ContainsSubstring(expected_code)); } } } @@ -371,7 +342,7 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", } )"; THEN("The instance struct should contain these variables") { - auto const generated = get_instance_structure(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(generated, ContainsSubstring("double* celsius{&coreneuron::celsius}")); REQUIRE_THAT(generated, ContainsSubstring("double* pi{&coreneuron::pi}")); REQUIRE_THAT(generated, @@ -389,7 +360,7 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", } )"; THEN("The instance struct should contain celsius for the implicit 5th argument") { - auto const generated = get_instance_structure(nmodl_text); + auto const generated = get_coreneuron_cpp_code(nmodl_text); REQUIRE_THAT(generated, ContainsSubstring("celsius")); } } @@ -400,10 +371,10 @@ SCENARIO("Check NEURON globals are added to the instance struct on demand", } )"; THEN("The instance struct should not contain those variables") { - auto const generated = get_instance_structure(nmodl_text); - REQUIRE_THAT(generated, !ContainsSubstring("celsius")); - REQUIRE_THAT(generated, !ContainsSubstring("pi")); - REQUIRE_THAT(generated, !ContainsSubstring("secondorder")); + auto const generated = get_coreneuron_cpp_code(nmodl_text); + REQUIRE_THAT(generated, !ContainsSubstring("double* celsius")); + REQUIRE_THAT(generated, !ContainsSubstring("double* pi")); + REQUIRE_THAT(generated, !ContainsSubstring("int* secondorder")); } } } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 8268eb22d0..860e23cae0 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -44,7 +44,6 @@ std::shared_ptr create_neuron_cpp_visitor( /// create C code generation visitor auto cv = std::make_shared("_test", ss, "double", false); - cv->setup(*ast); return cv; } From 3c101848d280a9d2cf1521cd8b32b201cfe9f6b7 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 6 Nov 2024 11:09:10 +0100 Subject: [PATCH 842/871] Fix NMODL_GENERATED_SOURCES without Python. (BlueBrain/nmodl#1553) NMODL Repo SHA: BlueBrain/nmodl@0186d193832208a6465f31e737d481fe35495f3f --- src/nmodl/language/code_generator.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake index ae8d5e292e..2bec777654 100644 --- a/src/nmodl/language/code_generator.cmake +++ b/src/nmodl/language/code_generator.cmake @@ -176,6 +176,7 @@ set(AST_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/ast/write_ion_var.hpp ) +if(NMODL_ENABLE_PYTHON_BINDINGS) set(PYBIND_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp @@ -185,6 +186,7 @@ set(PYBIND_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp ) +endif() set(VISITORS_GENERATED_SOURCES ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp From 6ed454ecbc1d8cce77fbf214ffc34aa01e0d0bbf Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 6 Nov 2024 12:15:10 +0100 Subject: [PATCH 843/871] Fix typo (BlueBrain/nmodl#1552) NMODL Repo SHA: BlueBrain/nmodl@80d7b8f233affa3d2377d178bf5b780d976118ea --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 73c32ce788..76f20f3351 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1478,8 +1478,7 @@ void CodegenCppVisitor::setup(const Program& node) { } if (!info.vectorize) { - logger->warn( - "CodegenCoreneuronCppVisitor : MOD file uses non-thread safe constructs of NMODL"); + logger->warn("CodegenCppVisitor : MOD file uses non-thread safe constructs of NMODL"); } codegen_float_variables = get_float_variables(); From c271fd30185de3082de33d1089a300950e435bfb Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 7 Nov 2024 07:49:26 +0100 Subject: [PATCH 844/871] Emit CVODE callback only on demand. (BlueBrain/nmodl#1551) Use `codegen --cvode` to enable. NMODL Repo SHA: BlueBrain/nmodl@25da002ea243df9e647f7a93c47f9ee1b8bf6dd8 --- src/nmodl/codegen/codegen_cpp_visitor.cpp | 2 +- src/nmodl/codegen/codegen_cpp_visitor.hpp | 17 +++++++++++++++-- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- src/nmodl/codegen/codegen_helper_visitor.hpp | 7 ++++++- .../codegen/codegen_neuron_cpp_visitor.cpp | 1 + src/nmodl/main.cpp | 11 +++++++++-- .../transpiler/unit/codegen/codegen_helper.cpp | 3 ++- .../unit/codegen/codegen_neuron_cpp_visitor.cpp | 6 +++++- test/nmodl/transpiler/usecases/run_test.sh | 2 +- 9 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index 76f20f3351..c395bb7dc1 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1465,7 +1465,7 @@ std::vector CodegenCppVisitor::get_int_variables() { void CodegenCppVisitor::setup(const Program& node) { program_symtab = node.get_symbol_table(); - CodegenHelperVisitor v; + CodegenHelperVisitor v(enable_cvode); info = v.analyze(node); info.mod_file = mod_filename; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 5a958ad5bf..1b2eda1e9b 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -266,6 +266,20 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { , float_type(std::move(float_type)) , optimize_ionvar_copies(optimize_ionvar_copies) {} + CodegenCppVisitor(std::string mod_filename, + std::ostream& stream, + std::string float_type, + const bool optimize_ionvar_copies, + const bool enable_cvode, + std::unique_ptr blame = nullptr) + : printer(std::make_unique(stream, std::move(blame))) + , mod_filename(std::move(mod_filename)) + , float_type(std::move(float_type)) + , optimize_ionvar_copies(optimize_ionvar_copies) + , enable_cvode(enable_cvode) {} + + private: + bool enable_cvode = false; protected: using SymbolType = std::shared_ptr; @@ -1481,11 +1495,10 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { std::string compute_method_name(BlockType type) const; // Automatically called as part of `visit_program`. - void setup(const ast::Program& node); + virtual void setup(const ast::Program& node); public: - /** * Main and only member function to call after creating an instance of this class. * \param program the AST to translate to C++ code diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index 000966c9e2..f2b5eb7ff9 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -223,7 +223,7 @@ void CodegenHelperVisitor::check_cvode_codegen(const ast::Program& node) { // PROCEDURE with `after_cvode` method if (solve_nodes.size() == 1 && (kinetic_or_derivative_nodes.size() || using_cvode)) { logger->debug("Will emit code for CVODE"); - info.emit_cvode = true; + info.emit_cvode = enable_cvode; } } diff --git a/src/nmodl/codegen/codegen_helper_visitor.hpp b/src/nmodl/codegen/codegen_helper_visitor.hpp index 1e31fef464..20e5f88e1c 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.hpp +++ b/src/nmodl/codegen/codegen_helper_visitor.hpp @@ -51,6 +51,9 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { /// holds all codegen related information codegen::CodegenInfo info; + /// Config variable for enabling/disabling CVODE, see `emit_cvode`. + bool enable_cvode; + /// if visiting net receive block bool under_net_receive_block = false; @@ -76,8 +79,10 @@ class CodegenHelperVisitor: public visitor::ConstAstVisitor { void find_neuron_global_variables(); static void sort_with_mod2c_symbol_order(std::vector& symbols); void check_cvode_codegen(const ast::Program& node); + public: - CodegenHelperVisitor() = default; + CodegenHelperVisitor(bool enable_cvode = false) + : enable_cvode(enable_cvode) {} /// run visitor and return information for code generation codegen::CodegenInfo analyze(const ast::Program& node); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 1113c7ad6e..e4aec03b53 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -2809,6 +2809,7 @@ static void rename_net_receive_arguments(const ast::NetReceiveBlock& net_receive } } + CodegenNeuronCppVisitor::ParamVector CodegenNeuronCppVisitor::net_receive_args() { return {{"", "Point_process*", "", "_pnt"}, {"", "double*", "", "_args"}, diff --git a/src/nmodl/main.cpp b/src/nmodl/main.cpp index 393021e7b7..8655270681 100644 --- a/src/nmodl/main.cpp +++ b/src/nmodl/main.cpp @@ -117,6 +117,9 @@ int run_nmodl(int argc, const char* argv[]) { /// true if top level local variables to be converted to range bool nmodl_local_to_range(false); + /// true if CVODE should be emitted + bool codegen_cvode(false); + /// true if localize variables even if verbatim block is used bool localize_verbatim(false); @@ -282,6 +285,9 @@ int run_nmodl(int argc, const char* argv[]) { codegen_opt->add_flag("--opt-ionvar-copy", optimize_ionvar_copies_codegen, fmt::format("Optimize copies of ion variables ({})", optimize_ionvar_copies_codegen))->ignore_case(); + codegen_opt->add_flag("--cvode", + codegen_cvode, + fmt::format("Print code for CVODE ({})", codegen_cvode))->ignore_case(); #if NMODL_ENABLE_BACKWARD auto blame_opt = app.add_subcommand("blame", "Blame NMODL code that generated some code."); @@ -352,7 +358,7 @@ int run_nmodl(int argc, const char* argv[]) { } /// use cnexp instead of after_cvode solve method - { + if (codegen_cvode) { logger->info("Running CVode to cnexp visitor"); AfterCVodeToCnexpVisitor().visit_program(*ast); ast_to_nmodl(*ast, filepath("after_cvode_to_cnexp")); @@ -531,7 +537,7 @@ int run_nmodl(int argc, const char* argv[]) { .api() .initialize_interpreter(); - if (neuron_code) { + if (neuron_code && codegen_cvode) { logger->info("Running CVODE visitor"); CvodeVisitor().visit_program(*ast); SymtabVisitor(update_symtab).visit_program(*ast); @@ -631,6 +637,7 @@ int run_nmodl(int argc, const char* argv[]) { output_stream, data_type, optimize_ionvar_copies_codegen, + codegen_cvode, utils::make_blame(blame_line, blame_level)); visitor.visit_program(*ast); } diff --git a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp index 5e5006f5d5..1ea3120619 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_helper.cpp @@ -69,7 +69,8 @@ CodegenInfo run_codegen_helper_get_info(const std::string& text) { SolveBlockVisitor{}.visit_program(*ast); SymtabVisitor{true}.visit_program(*ast); - CodegenHelperVisitor v; + bool enable_cvode = true; + CodegenHelperVisitor v(enable_cvode); const auto info = v.analyze(*ast); return info; diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 860e23cae0..463a201211 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -42,8 +42,12 @@ std::shared_ptr create_neuron_cpp_visitor( SolveBlockVisitor().visit_program(*ast); FunctionCallpathVisitor().visit_program(*ast); + bool optimize_ion_vars = false; + bool enable_cvode = true; + /// create C code generation visitor - auto cv = std::make_shared("_test", ss, "double", false); + auto cv = std::make_shared( + "_test", ss, "double", optimize_ion_vars, enable_cvode); return cv; } diff --git a/test/nmodl/transpiler/usecases/run_test.sh b/test/nmodl/transpiler/usecases/run_test.sh index 99dde68797..a90c916e92 100755 --- a/test/nmodl/transpiler/usecases/run_test.sh +++ b/test/nmodl/transpiler/usecases/run_test.sh @@ -38,5 +38,5 @@ run_tests nocmodl # NRN + NMODL echo "-- Running NRN+NMODL --------" rm -r "${output_dir}" tmp || true -nrnivmodl -nmodl "${nmodl}" +nrnivmodl -nmodl "${nmodl}" -nmodlflags "codegen --cvode" run_tests nmodl From 9788b67c9a865d49e6aa4269621748d5e9808802 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 7 Nov 2024 10:37:03 +0100 Subject: [PATCH 845/871] Fix race condition in CMake code. (BlueBrain/nmodl#1555) The race is cause by the fact that two targets exist to copy the Python files. The first one because of `cpp_cc_build_time_copy` without `NO_TARGET`. The second because of the: add_custom_target(nmodl_copy_python_files ALL DEPENDS ...) The solution is to not create the per file target by passing `NO_TARGET` to `cpp_cc_build_time_copy`. NMODL Repo SHA: BlueBrain/nmodl@1f0c5c107eaf3d789310971d0f95ab641da8d747 --- src/nmodl/pybind/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 25f80d1c10..b6a8b23187 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -99,7 +99,7 @@ file( foreach(file IN LISTS NMODL_PYTHON_FILES) cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/${file} OUTPUT - ${CMAKE_BINARY_DIR}/lib/nmodl/${file}) + ${CMAKE_BINARY_DIR}/lib/nmodl/${file} NO_TARGET) list(APPEND nmodl_python_binary_dir_files "${CMAKE_BINARY_DIR}/lib/nmodl/${file}") endforeach() add_custom_target(nmodl_copy_python_files ALL DEPENDS ${nmodl_python_binary_dir_files}) From 167c2ae2f16d1dab906a130646c26529638c817f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 7 Nov 2024 13:57:34 +0100 Subject: [PATCH 846/871] Fix CMake integration of NMODL generation. (BlueBrain/nmodl#1554) The code for the code-generator NMODL is itself generated from Jinja templates. This requires CMake integration to ensure it happens automatically. The following has been changed about the integration: * The CMake code needs a list of the generated files. This list is stored in a CMake file and included. This file must exist before running `code_generator.py` to generate the NMODL code, because the command to generate the files needs to know which file it will be generating. Therefore, we split the script into two phases: one to generate the list of generated files, and another to generate the files. * The list of generated files depends on the build flags. Therefore, this file (`code_generator.cmake`) can't be checked into the Git repo. The `code_generator.cmake` is created during the configure phase, and now lives in the build directory. * CMake code was lifted from `src/language/CMakeLists.txt` to the top-level `CMakeLists.txt` to avoid any scoping issues for the lists of generated files. * Removed pattern of yielding tasks while creating a list tasks in favour of returning the list of tasks directly. NMODL Repo SHA: BlueBrain/nmodl@49fdfe6c123f452c07e0dc69fbacc5ce66ea637d --- cmake/nmodl/CMakeLists.txt | 71 +++++++- src/nmodl/language/CMakeLists.txt | 35 ---- src/nmodl/language/code_generator.cmake | 212 ---------------------- src/nmodl/language/code_generator.py | 31 +++- src/nmodl/language/code_generator_opts.in | 1 + 5 files changed, 94 insertions(+), 256 deletions(-) delete mode 100644 src/nmodl/language/code_generator.cmake create mode 100644 src/nmodl/language/code_generator_opts.in diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 47b0484443..f8f631bb7c 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -230,10 +230,75 @@ if(NOT NMODL_AS_SUBPROJECT AND NMODL_ENABLE_TESTS) endif() # ============================================================================= -# list of autogenerated files -# ============================================================================= -include(${PROJECT_SOURCE_DIR}/src/language/code_generator.cmake) +# Generate NMODL code, e.g. ast & visitor base classes. +# ----------------------------------------------------------------------------- +# clang-format is handled by the HPC coding conventions scripts, which also handle generating the +# .clang-format configuration file. It's important that we only try to format code if formatting was +# enabled and NMODL's .clang-format exists, otherwise clang-format will search too far up the +# directory tree and find the wrong configuration file. This can break compilation. +if(NMODL_CLANG_FORMAT OR NMODL_FORMATTING) + set(CODE_GENERATOR_OPTS -v --clang-format=${ClangFormat_EXECUTABLE}) + foreach(clang_format_opt ${NMODL_ClangFormat_OPTIONS} --style=file) + list(APPEND CODE_GENERATOR_OPTS --clang-format-opts=${clang_format_opt}) + endforeach() +endif() +if(NOT NMODL_ENABLE_PYTHON_BINDINGS) + list(APPEND CODE_GENERATOR_OPTS "--disable-pybind") +endif() + +# ----------------------------------------------------------------------------- +# Part I: generate the list of generated files +# ----------------------------------------------------------------------------- +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} --generate-cmake --base-dir ${PROJECT_BINARY_DIR}/src + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/language) + +# Ensure that the above runs again, if its dependencies update. +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/language/code_generator.py") + +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/src/language/templates/code_generator.cmake) + +# The list should be up-to-date now. +include(${PROJECT_BINARY_DIR}/src/code_generator.cmake) + +# ----------------------------------------------------------------------------- +# Part II: generate AST/Visitor classes from language definition +# ----------------------------------------------------------------------------- +set_source_files_properties(${NMODL_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) + +# Make the codegen options available as a dependency by storing them in a file. +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/language/code_generator_opts.in + ${CMAKE_CURRENT_BINARY_DIR}/src/language/code_generator_opts) + +add_custom_command( + OUTPUT ${NMODL_GENERATED_SOURCES} + COMMAND ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/src/language/code_generator.py + ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/src/language + DEPENDS ${CODE_GENERATOR_PY_FILES} + DEPENDS ${CODE_GENERATOR_YAML_FILES} + DEPENDS ${CODE_GENERATOR_JINJA_FILES} + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/src/language/code_generator_opts + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +unset(CODE_GENERATOR_OPTS) + +# ----------------------------------------------------------------------------- +# Target to propagate dependencies properly to lexer +# ----------------------------------------------------------------------------- +add_custom_target(pyastgen DEPENDS ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) + +# ============================================================================= +# Deal with the hand-written code +# ============================================================================= add_subdirectory(src) # ============================================================================= diff --git a/src/nmodl/language/CMakeLists.txt b/src/nmodl/language/CMakeLists.txt index 92a8ce46b4..8b13789179 100644 --- a/src/nmodl/language/CMakeLists.txt +++ b/src/nmodl/language/CMakeLists.txt @@ -1,36 +1 @@ -# ============================================================================= -# Command to generate AST/Visitor classes from language definition -# ============================================================================= -set_source_files_properties(${NMODL_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) -# clang-format is handled by the HPC coding conventions scripts, which also handle generating the -# .clang-format configuration file. It's important that we only try to format code if formatting was -# enabled and NMODL's .clang-format exists, otherwise clang-format will search too far up the -# directory tree and find the wrong configuration file. This can break compilation. -if(NMODL_CLANG_FORMAT OR NMODL_FORMATTING) - set(CODE_GENERATOR_OPTS -v --clang-format=${ClangFormat_EXECUTABLE}) - foreach(clang_format_opt ${NMODL_ClangFormat_OPTIONS} --style=file) - list(APPEND CODE_GENERATOR_OPTS --clang-format-opts=${clang_format_opt}) - endforeach() -endif() - -if(NOT NMODL_ENABLE_PYTHON_BINDINGS) - list(APPEND CODE_GENERATOR_OPTS "--disable-pybind") -endif() - -add_custom_command( - OUTPUT ${NMODL_GENERATED_SOURCES} - COMMAND ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/code_generator.py - ${CODE_GENERATOR_OPTS} --base-dir ${PROJECT_BINARY_DIR}/src - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - DEPENDS ${CODE_GENERATOR_PY_FILES} - DEPENDS ${CODE_GENERATOR_YAML_FILES} - DEPENDS ${CODE_GENERATOR_JINJA_FILES} - DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/templates/code_generator.cmake - COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") -unset(CODE_GENERATOR_OPTS) - -# ============================================================================= -# Target to propagate dependencies properly to lexer -# ============================================================================= -add_custom_target(pyastgen DEPENDS ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) diff --git a/src/nmodl/language/code_generator.cmake b/src/nmodl/language/code_generator.cmake deleted file mode 100644 index 2bec777654..0000000000 --- a/src/nmodl/language/code_generator.cmake +++ /dev/null @@ -1,212 +0,0 @@ -# -# THIS FILE IS GENERATED AND SHALL NOT BE EDITED. -# - -# cmake-format: off -set(CODE_GENERATOR_JINJA_FILES - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/all.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/ast_decl.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/ast/node_class.template - ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyast.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pynode.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pysymtab.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyvisitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/pybind/pyvisitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/ast_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/ast_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/checkparent_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/checkparent_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/json_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/json_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/lookup_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/lookup_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/nmodl_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/nmodl_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/symtab_visitor.cpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/symtab_visitor.hpp - ${PROJECT_SOURCE_DIR}/src/language/templates/visitors/visitor.hpp -) - -set(CODE_GENERATOR_PY_FILES - ${PROJECT_SOURCE_DIR}/src/language/argument.py - ${PROJECT_SOURCE_DIR}/src/language/code_generator.py - ${PROJECT_SOURCE_DIR}/src/language/language_parser.py - ${PROJECT_SOURCE_DIR}/src/language/node_info.py - ${PROJECT_SOURCE_DIR}/src/language/nodes.py - ${PROJECT_SOURCE_DIR}/src/language/utils.py -) - -set(CODE_GENERATOR_YAML_FILES - ${PROJECT_SOURCE_DIR}/src/language/codegen.yaml - ${PROJECT_SOURCE_DIR}/src/language/nmodl.yaml -) - -set(AST_GENERATED_SOURCES - ${PROJECT_BINARY_DIR}/src/ast/after_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/all.hpp - ${PROJECT_BINARY_DIR}/src/ast/argument.hpp - ${PROJECT_BINARY_DIR}/src/ast/assigned_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/assigned_definition.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast.cpp - ${PROJECT_BINARY_DIR}/src/ast/ast.hpp - ${PROJECT_BINARY_DIR}/src/ast/ast_decl.hpp - ${PROJECT_BINARY_DIR}/src/ast/ba_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/ba_block_type.hpp - ${PROJECT_BINARY_DIR}/src/ast/bbcore_pointer.hpp - ${PROJECT_BINARY_DIR}/src/ast/bbcore_pointer_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/before_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/binary_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/binary_operator.hpp - ${PROJECT_BINARY_DIR}/src/ast/block.hpp - ${PROJECT_BINARY_DIR}/src/ast/block_comment.hpp - ${PROJECT_BINARY_DIR}/src/ast/boolean.hpp - ${PROJECT_BINARY_DIR}/src/ast/breakpoint_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/compartment.hpp - ${PROJECT_BINARY_DIR}/src/ast/conductance_hint.hpp - ${PROJECT_BINARY_DIR}/src/ast/conserve.hpp - ${PROJECT_BINARY_DIR}/src/ast/constant_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/constant_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/constant_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/constructor_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/cvode_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/define.hpp - ${PROJECT_BINARY_DIR}/src/ast/derivative_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/derivimplicit_callback.hpp - ${PROJECT_BINARY_DIR}/src/ast/destructor_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/diff_eq_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/discrete_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/double.hpp - ${PROJECT_BINARY_DIR}/src/ast/double_unit.hpp - ${PROJECT_BINARY_DIR}/src/ast/eigen_linear_solver_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/eigen_newton_solver_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/electrode_cur_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/electrode_current.hpp - ${PROJECT_BINARY_DIR}/src/ast/else_if_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/else_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/expression_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/extern_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/external.hpp - ${PROJECT_BINARY_DIR}/src/ast/factor_def.hpp - ${PROJECT_BINARY_DIR}/src/ast/float.hpp - ${PROJECT_BINARY_DIR}/src/ast/for_netcon.hpp - ${PROJECT_BINARY_DIR}/src/ast/from_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/function_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/function_call.hpp - ${PROJECT_BINARY_DIR}/src/ast/function_table_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/global.hpp - ${PROJECT_BINARY_DIR}/src/ast/global_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/identifier.hpp - ${PROJECT_BINARY_DIR}/src/ast/if_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/include.hpp - ${PROJECT_BINARY_DIR}/src/ast/independent_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/indexed_name.hpp - ${PROJECT_BINARY_DIR}/src/ast/initial_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/integer.hpp - ${PROJECT_BINARY_DIR}/src/ast/kinetic_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/lag_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/limits.hpp - ${PROJECT_BINARY_DIR}/src/ast/lin_equation.hpp - ${PROJECT_BINARY_DIR}/src/ast/line_comment.hpp - ${PROJECT_BINARY_DIR}/src/ast/linear_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/local_list_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/local_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/lon_diffuse.hpp - ${PROJECT_BINARY_DIR}/src/ast/longitudinal_diffusion_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/model.hpp - ${PROJECT_BINARY_DIR}/src/ast/mutex_lock.hpp - ${PROJECT_BINARY_DIR}/src/ast/mutex_unlock.hpp - ${PROJECT_BINARY_DIR}/src/ast/name.hpp - ${PROJECT_BINARY_DIR}/src/ast/net_receive_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/neuron_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/node.hpp - ${PROJECT_BINARY_DIR}/src/ast/non_lin_equation.hpp - ${PROJECT_BINARY_DIR}/src/ast/non_linear_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/nonspecific.hpp - ${PROJECT_BINARY_DIR}/src/ast/nonspecific_cur_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/nrn_state_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/number.hpp - ${PROJECT_BINARY_DIR}/src/ast/number_range.hpp - ${PROJECT_BINARY_DIR}/src/ast/ontology_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/param_assign.hpp - ${PROJECT_BINARY_DIR}/src/ast/param_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/paren_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/pointer.hpp - ${PROJECT_BINARY_DIR}/src/ast/pointer_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/prime_name.hpp - ${PROJECT_BINARY_DIR}/src/ast/procedure_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/program.hpp - ${PROJECT_BINARY_DIR}/src/ast/protect_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/random_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/random_var_list.hpp - ${PROJECT_BINARY_DIR}/src/ast/range.hpp - ${PROJECT_BINARY_DIR}/src/ast/range_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/react_var_name.hpp - ${PROJECT_BINARY_DIR}/src/ast/reaction_operator.hpp - ${PROJECT_BINARY_DIR}/src/ast/reaction_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/read_ion_var.hpp - ${PROJECT_BINARY_DIR}/src/ast/solution_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/solve_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/state_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/statement_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/string.hpp - ${PROJECT_BINARY_DIR}/src/ast/suffix.hpp - ${PROJECT_BINARY_DIR}/src/ast/table_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/thread_safe.hpp - ${PROJECT_BINARY_DIR}/src/ast/unary_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/unary_operator.hpp - ${PROJECT_BINARY_DIR}/src/ast/unit.hpp - ${PROJECT_BINARY_DIR}/src/ast/unit_block.hpp - ${PROJECT_BINARY_DIR}/src/ast/unit_def.hpp - ${PROJECT_BINARY_DIR}/src/ast/unit_state.hpp - ${PROJECT_BINARY_DIR}/src/ast/update_dt.hpp - ${PROJECT_BINARY_DIR}/src/ast/useion.hpp - ${PROJECT_BINARY_DIR}/src/ast/valence.hpp - ${PROJECT_BINARY_DIR}/src/ast/var_name.hpp - ${PROJECT_BINARY_DIR}/src/ast/verbatim.hpp - ${PROJECT_BINARY_DIR}/src/ast/watch.hpp - ${PROJECT_BINARY_DIR}/src/ast/watch_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/while_statement.hpp - ${PROJECT_BINARY_DIR}/src/ast/wrapped_expression.hpp - ${PROJECT_BINARY_DIR}/src/ast/write_ion_var.hpp -) - -if(NMODL_ENABLE_PYTHON_BINDINGS) -set(PYBIND_GENERATED_SOURCES - ${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyast.hpp - ${PROJECT_BINARY_DIR}/src/pybind/pynode_0.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pynode_1.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pysymtab.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.cpp - ${PROJECT_BINARY_DIR}/src/pybind/pyvisitor.hpp -) -endif() - -set(VISITORS_GENERATED_SOURCES - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/ast_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/checkparent_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/json_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/lookup_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/nmodl_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.cpp - ${PROJECT_BINARY_DIR}/src/visitors/symtab_visitor.hpp - ${PROJECT_BINARY_DIR}/src/visitors/visitor.hpp -) - -set(NMODL_GENERATED_SOURCES - ${AST_GENERATED_SOURCES} - ${PYBIND_GENERATED_SOURCES} - ${VISITORS_GENERATED_SOURCES} -) -# cmake-format: on diff --git a/src/nmodl/language/code_generator.py b/src/nmodl/language/code_generator.py index 23bf8ed1dc..73ff2caba3 100644 --- a/src/nmodl/language/code_generator.py +++ b/src/nmodl/language/code_generator.py @@ -34,6 +34,7 @@ class CodeGenerator( "base_dir", "clang_format", "disable_pybind", + "generate_cmake", "jinja_env", "jinja_templates_dir", "modification_date", @@ -51,6 +52,7 @@ class CodeGenerator( base_dir: output root directory where Jinja templates are rendered clang_format: clang-format command line if C++ files have to be formatted, `None` otherwise disable_pybind: disable python bindings related code generation + generate_cmake: generate the CMake include and none of the rest py_files: list of Path objects to the Python files used by this program yaml_files: list of Path object to YAML files describing the NMODL language modification_date: most recent modification date of the Python and YAML files in this directory @@ -60,7 +62,13 @@ class CodeGenerator( temp_dir: path to the directory where to create temporary files """ - def __new__(cls, base_dir, clang_format=None, disable_pybind=False): + def __new__( + cls, + base_dir, + clang_format=None, + disable_pybind=False, + generate_cmake=False, + ): this_dir = Path(__file__).parent.resolve() jinja_templates_dir = this_dir / "templates" py_files = [Path(p).relative_to(this_dir) for p in this_dir.glob("*.py")] @@ -70,6 +78,7 @@ def __new__(cls, base_dir, clang_format=None, disable_pybind=False): base_dir=base_dir, clang_format=clang_format, disable_pybind=disable_pybind, + generate_cmake=generate_cmake, this_dir=this_dir, jinja_templates_dir=jinja_templates_dir, jinja_env=jinja2.Environment( @@ -123,7 +132,7 @@ def _cmake_deps_task(self, tasks): An instance of JinjaTask """ input = self.jinja_templates_dir / "code_generator.cmake" - output = self.this_dir / input.name + output = self.base_dir / input.name inputs = set() outputs = dict() for task in tasks: @@ -176,6 +185,9 @@ def workload(self): tasks = [] for path in self.jinja_templates_dir.iterdir(): + if not path.is_dir(): + continue + sub_dir = PurePath(path).name # skip pybind directory as it's needed for python bindings only @@ -197,7 +209,6 @@ def workload(self): extradeps=extradeps[filepath], ) tasks.append(task) - yield task elif filepath == pynode_cpp_tpl: chunk_length = math.ceil(len(self.nodes) / num_pybind_files) for chunk_k in range(num_pybind_files): @@ -220,7 +231,6 @@ def workload(self): extradeps=extradeps[filepath], ) tasks.append(task) - yield task else: task = JinjaTask( app=self, @@ -234,8 +244,11 @@ def workload(self): extradeps=extradeps[filepath], ) tasks.append(task) - yield task - yield self._cmake_deps_task(tasks) + + if self.generate_cmake: + return [self._cmake_deps_task(tasks)] + else: + return tasks class JinjaTask( @@ -371,6 +384,11 @@ def parse_args(args=None): action="store_true", help="Do not generate code related to python bindigs", ) + parser.add_argument( + "--generate-cmake", + action="store_true", + help="Generate the CMake includes instead of the rest.", + ) args = parser.parse_args(args=args) @@ -411,6 +429,7 @@ def main(args=None): clang_format=args.clang_format, base_dir=args.base_dir, disable_pybind=args.disable_pybind, + generate_cmake=args.generate_cmake, ) num_tasks = 0 tasks_performed = [] diff --git a/src/nmodl/language/code_generator_opts.in b/src/nmodl/language/code_generator_opts.in new file mode 100644 index 0000000000..b0da71b0e2 --- /dev/null +++ b/src/nmodl/language/code_generator_opts.in @@ -0,0 +1 @@ +${CODE_GENERATOR_OPTS} From 8618d4935f6f145f21ebd72d7cd8cc7f05e8d405 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 13 Nov 2024 10:12:48 +0100 Subject: [PATCH 847/871] Lower PROTECT related code. (BlueBrain/nmodl#1556) * Lower `visit_protected_statement`. The code is lowered into the CoreNEURON side, because NEURON can't use the same OpenMP system. * Lower `print_atomic_reduction_pragma`. This method is specific to the CoreNEURON implementation of `visit_protected_statement`. NMODL Repo SHA: BlueBrain/nmodl@ea94748e9a36bafb805c2b9fe539e2ce4ca8c73c --- src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp | 10 ++++++++++ src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp | 3 ++- src/nmodl/codegen/codegen_cpp_visitor.cpp | 8 -------- src/nmodl/codegen/codegen_cpp_visitor.hpp | 7 ------- src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp | 6 ------ src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp | 7 ------- 6 files changed, 12 insertions(+), 29 deletions(-) diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp index c46ad2e9e4..27ca0653f5 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -3038,5 +3038,15 @@ void CodegenCoreneuronCppVisitor::visit_watch_statement(const ast::WatchStatemen current_watch_statement++)); } + +void CodegenCoreneuronCppVisitor::visit_protect_statement(const ast::ProtectStatement& node) { + print_atomic_reduction_pragma(); + printer->add_indent(); + node.get_expression()->accept(*this); + printer->add_text(";"); +} + + + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp index c43c2516f9..417c0383ec 100644 --- a/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_coreneuron_cpp_visitor.hpp @@ -230,7 +230,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { /** * Print atomic update pragma for reduction statements */ - void print_atomic_reduction_pragma() override; + virtual void print_atomic_reduction_pragma(); /** @@ -942,6 +942,7 @@ class CodegenCoreneuronCppVisitor: public CodegenCppVisitor { void visit_for_netcon(const ast::ForNetcon& node) override; void visit_verbatim(const ast::Verbatim& node) override; void visit_watch_statement(const ast::WatchStatement& node) override; + void visit_protect_statement(const ast::ProtectStatement& node) override; ParamVector functor_params() override; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.cpp b/src/nmodl/codegen/codegen_cpp_visitor.cpp index c395bb7dc1..e60e263c14 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.cpp @@ -1106,14 +1106,6 @@ void CodegenCppVisitor::visit_update_dt(const ast::UpdateDt& node) { } -void CodegenCppVisitor::visit_protect_statement(const ast::ProtectStatement& node) { - print_atomic_reduction_pragma(); - printer->add_indent(); - node.get_expression()->accept(*this); - printer->add_text(";"); -} - - void CodegenCppVisitor::visit_mutex_lock(const ast::MutexLock& node) { printer->fmt_line("#pragma omp critical ({})", info.mod_suffix); printer->add_indent(); diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 1b2eda1e9b..99c35561c3 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -802,12 +802,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { /****************************************************************************************/ - /** - * Print atomic update pragma for reduction statements - */ - virtual void print_atomic_reduction_pragma() = 0; - - /** * Instantiate global var instance * @@ -1485,7 +1479,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { void visit_var_name(const ast::VarName& node) override; void visit_while_statement(const ast::WhileStatement& node) override; void visit_update_dt(const ast::UpdateDt& node) override; - void visit_protect_statement(const ast::ProtectStatement& node) override; void visit_mutex_lock(const ast::MutexLock& node) override; void visit_mutex_unlock(const ast::MutexUnlock& node) override; void visit_solution_expression(const ast::SolutionExpression& node) override; diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index e4aec03b53..7e8420cce3 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -80,12 +80,6 @@ int CodegenNeuronCppVisitor::position_of_int_var(const std::string& name) const /* Backend specific routines */ /****************************************************************************************/ - -/// TODO: Edit for NEURON -void CodegenNeuronCppVisitor::print_atomic_reduction_pragma() { - return; -} - bool CodegenNeuronCppVisitor::optimize_ion_variable_copies() const { if (optimize_ionvar_copies) { throw std::runtime_error("Not implemented."); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index c0aa55f830..9b36b1c22c 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -153,13 +153,6 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { /* Backend specific routines */ /****************************************************************************************/ - - /** - * Print atomic update pragma for reduction statements - */ - void print_atomic_reduction_pragma() override; - - /** * Check if ion variable copies should be avoided */ From 917a61b30c445f3fbe221927dcf36344ed80886d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 13 Nov 2024 14:01:59 +0100 Subject: [PATCH 848/871] Fix for finite differences. (BlueBrain/nmodl#1558) The previous implementation would apply the transformation to children of the node in the AST. Hence, if the root was `sp.Derivative` it would fail. This commit adds a fix and the tests. NMODL Repo SHA: BlueBrain/nmodl@16b5f956c3a1bea0a9234b31be74cd86c471f075 --- src/nmodl/nmodl/python/nmodl/ode.py | 6 +++++- test/nmodl/transpiler/unit/ode/test_ode.py | 21 +++++++++++++++++++ .../usecases/solve/finite_difference.mod | 7 +++++-- .../usecases/solve/test_finite_difference.py | 5 +++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/nmodl/python/nmodl/ode.py index c1c907eae9..f34c9d0269 100644 --- a/src/nmodl/nmodl/python/nmodl/ode.py +++ b/src/nmodl/nmodl/python/nmodl/ode.py @@ -366,7 +366,11 @@ def transform_expression(expr, transform): if not expr.args: return expr - args = (transform_expression(transform(arg), transform) for arg in expr.args) + transformed_expr = transform(expr) + if transformed_expr is not expr: + return transformed_expr + + args = (transform_expression(arg, transform) for arg in expr.args) return expr.func(*args) diff --git a/test/nmodl/transpiler/unit/ode/test_ode.py b/test/nmodl/transpiler/unit/ode/test_ode.py index 82e0358d22..8be63e02e7 100644 --- a/test/nmodl/transpiler/unit/ode/test_ode.py +++ b/test/nmodl/transpiler/unit/ode/test_ode.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 from nmodl.ode import differentiate2c, integrate2c, make_symbol +from nmodl.ode import transform_expression, discretize_derivative import pytest import sympy as sp @@ -186,3 +187,23 @@ def test_integrate2c(): assert _equivalent( integrate2c(f"x'={eq}", "dt", var_list, use_pade_approx=True), f"x = {sol}" ) + + +def test_finite_difference(): + df_dx = "(f(x + x_delta_/2) - f(x - x_delta_/2))/x_delta_" + dg_dx = "(g(x + x_delta_/2) - g(x - x_delta_/2))/x_delta_" + + test_cases = [ + ("f(x)", df_dx), + ("a*f(x)", f"a*{df_dx}"), + ("a*f(x)*g(x)", f"a*({df_dx}*g(x) + f(x)*{dg_dx})"), + ("a*f(x) + b*g(x)", f"a*{df_dx} + b*{dg_dx}"), + ] + vars = ["a", "x", "x_delta_"] + + for expr, expected in test_cases: + expr = sp.diff(sp.sympify(expr), "x") + actual = transform_expression(expr, discretize_derivative) + msg = f"'{actual}' =!= '{expected}'" + + assert _equivalent(str(actual), expected, vars=vars), msg diff --git a/test/nmodl/transpiler/usecases/solve/finite_difference.mod b/test/nmodl/transpiler/usecases/solve/finite_difference.mod index 2c0f94e86d..1a1bab1038 100644 --- a/test/nmodl/transpiler/usecases/solve/finite_difference.mod +++ b/test/nmodl/transpiler/usecases/solve/finite_difference.mod @@ -10,10 +10,12 @@ ASSIGNED { STATE { x + z } INITIAL { x = 42.0 + z = 21.0 a = 0.1 } @@ -22,9 +24,10 @@ BREAKPOINT { } DERIVATIVE dX { - x' = -f(x) + x' = f(x) + z' = 2.0*f(z) } FUNCTION f(x) { - f = a*x + f = -a*x } diff --git a/test/nmodl/transpiler/usecases/solve/test_finite_difference.py b/test/nmodl/transpiler/usecases/solve/test_finite_difference.py index b0c22af6b7..e4b8f9170d 100644 --- a/test/nmodl/transpiler/usecases/solve/test_finite_difference.py +++ b/test/nmodl/transpiler/usecases/solve/test_finite_difference.py @@ -11,6 +11,7 @@ def test_finite_difference(): s.nseg = nseg x_hoc = h.Vector().record(s(0.5)._ref_x_finite_difference) + z_hoc = h.Vector().record(s(0.5)._ref_z_finite_difference) t_hoc = h.Vector().record(h._ref_t) h.stdinit() @@ -19,12 +20,16 @@ def test_finite_difference(): h.run() x = np.array(x_hoc.as_numpy()) + z = np.array(z_hoc.as_numpy()) t = np.array(t_hoc.as_numpy()) a = h.a_finite_difference x_exact = 42.0 * np.exp(-a * t) np.testing.assert_allclose(x, x_exact, rtol=1e-4) + z_exact = 21.0 * np.exp(-2.0 * a * t) + np.testing.assert_allclose(z, z_exact, rtol=1e-4) + if __name__ == "__main__": test_finite_difference() From 23679c379e45d60cb15da1ed46e1441a99190825 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 14 Nov 2024 15:17:56 +0100 Subject: [PATCH 849/871] Support PROTECT via _NMODLMUTEX{,UN}LOCK. (BlueBrain/nmodl#1557) * Fix thread_variable conditions. * Support PROTECT via `_NMODLMUTEX{,UN}LOCK`. * Add tests. NMODL Repo SHA: BlueBrain/nmodl@96299cb93a25f9ae38f7392ecb1eb3da8273f515 --- src/nmodl/codegen/codegen_helper_visitor.cpp | 2 +- .../codegen/codegen_neuron_cpp_visitor.cpp | 9 ++++++ .../codegen/codegen_neuron_cpp_visitor.hpp | 1 + test/nmodl/transpiler/usecases/CMakeLists.txt | 1 + .../usecases/protect/shared_counter.mod | 17 +++++++++++ .../usecases/protect/test_shared_counter.py | 30 +++++++++++++++++++ 6 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 test/nmodl/transpiler/usecases/protect/shared_counter.mod create mode 100644 test/nmodl/transpiler/usecases/protect/test_shared_counter.py diff --git a/src/nmodl/codegen/codegen_helper_visitor.cpp b/src/nmodl/codegen/codegen_helper_visitor.cpp index f2b5eb7ff9..4067de9b73 100644 --- a/src/nmodl/codegen/codegen_helper_visitor.cpp +++ b/src/nmodl/codegen/codegen_helper_visitor.cpp @@ -290,7 +290,7 @@ void CodegenHelperVisitor::find_non_range_variables() { // if model is thread safe and if parameter is being written then // those variables should be promoted to thread safe variable - if (info.vectorize && info.thread_safe && var->get_write_count() > 0) { + if (info.vectorize && info.declared_thread_safe && var->get_write_count() > 0) { var->mark_thread_safe(); info.thread_variables.push_back(var); info.thread_var_data_size += var->get_length(); diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp index 7e8420cce3..ebcf222c5d 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.cpp @@ -1103,6 +1103,7 @@ void CodegenNeuronCppVisitor::print_neuron_includes() { printer->add_multi_line(R"CODE( #include "mech_api.h" #include "neuron/cache/mechanism_range.hpp" + #include "nmodlmutex.h" #include "nrniv_mf.h" #include "section_fwd.hpp" )CODE"); @@ -3063,6 +3064,14 @@ void CodegenNeuronCppVisitor::visit_for_netcon(const ast::ForNetcon& node) { printer->pop_block(); } +void CodegenNeuronCppVisitor::visit_protect_statement(const ast::ProtectStatement& node) { + printer->add_line("_NMODLMUTEXLOCK"); + printer->add_indent(); + node.get_expression()->accept(*this); + printer->add_text(";"); + printer->add_line("_NMODLMUTEXUNLOCK"); +} + } // namespace codegen } // namespace nmodl diff --git a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp index 9b36b1c22c..4e0ebb106b 100644 --- a/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_neuron_cpp_visitor.hpp @@ -839,6 +839,7 @@ class CodegenNeuronCppVisitor: public CodegenCppVisitor { void visit_for_netcon(const ast::ForNetcon& node) override; void visit_longitudinal_diffusion_block(const ast::LongitudinalDiffusionBlock& node) override; void visit_lon_diffuse(const ast::LonDiffuse& node) override; + void visit_protect_statement(const ast::ProtectStatement& node) override; public: /****************************************************************************************/ diff --git a/test/nmodl/transpiler/usecases/CMakeLists.txt b/test/nmodl/transpiler/usecases/CMakeLists.txt index f776c578dd..73aa0ced38 100644 --- a/test/nmodl/transpiler/usecases/CMakeLists.txt +++ b/test/nmodl/transpiler/usecases/CMakeLists.txt @@ -25,6 +25,7 @@ set(NMODL_USECASE_DIRS point_process pointer procedure + protect random solve state diff --git a/test/nmodl/transpiler/usecases/protect/shared_counter.mod b/test/nmodl/transpiler/usecases/protect/shared_counter.mod new file mode 100644 index 0000000000..c431c7ce32 --- /dev/null +++ b/test/nmodl/transpiler/usecases/protect/shared_counter.mod @@ -0,0 +1,17 @@ +NEURON { + SUFFIX shared_counter + GLOBAL g_cnt +} + +ASSIGNED { + g_cnt +} + + +INITIAL { + PROTECT g_cnt = 0 +} + +BREAKPOINT { + PROTECT g_cnt = g_cnt + 1 +} diff --git a/test/nmodl/transpiler/usecases/protect/test_shared_counter.py b/test/nmodl/transpiler/usecases/protect/test_shared_counter.py new file mode 100644 index 0000000000..edc52ae6d9 --- /dev/null +++ b/test/nmodl/transpiler/usecases/protect/test_shared_counter.py @@ -0,0 +1,30 @@ +from neuron import h, gui + + +def test_shared_counter(): + nthreads = 32 + nseg = 10 + nsteps = 100 + + sections = [h.Section() for _ in range(nthreads)] + for s in sections: + s.insert("shared_counter") + s.nseg = nseg + + pc = h.ParallelContext() + + pc.nthread(nthreads) + for k, s in enumerate(sections): + pc.partition(k, h.SectionList([s])) + + h.finitialize() + for _ in range(nsteps): + h.step() + + expected = nthreads * nseg * nsteps + g_cnt = h.g_cnt_shared_counter + assert h.g_cnt_shared_counter == expected, f"{g_cnt}" + + +if __name__ == "__main__": + test_shared_counter() From cb23f0bec879dc79385f44813a3a196f3388c166 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Tue, 19 Nov 2024 12:07:11 +0100 Subject: [PATCH 850/871] Bump min Python version from 3.8 to 3.9 (BlueBrain/nmodl#1559) NMODL Repo SHA: BlueBrain/nmodl@a851a1b145416805a601b9a7b8ff87d0c0c76d3c --- cmake/nmodl/CMakeLists.txt | 2 +- docs/nmodl/transpiler/INSTALL.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index f8f631bb7c..1a98986d65 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -177,7 +177,7 @@ endif() # Find required python packages # ============================================================================= message(STATUS "CHECKING FOR PYTHON") -find_package(Python 3.8 REQUIRED COMPONENTS Interpreter) +find_package(Python 3.9 REQUIRED COMPONENTS Interpreter) cpp_cc_strip_python_shims(EXECUTABLE "${PYTHON_EXECUTABLE}" OUTPUT PYTHON_EXECUTABLE) # ============================================================================= diff --git a/docs/nmodl/transpiler/INSTALL.rst b/docs/nmodl/transpiler/INSTALL.rst index 08262235ed..a249ebaafc 100644 --- a/docs/nmodl/transpiler/INSTALL.rst +++ b/docs/nmodl/transpiler/INSTALL.rst @@ -30,7 +30,7 @@ support is necessary. Make sure you have following packages available: - flex (>=2.6) - bison (>=3.0) - CMake (>=3.15) -- Python (>=3.8) +- Python (>=3.9) - Python packages : jinja2 (>=2.10), pyyaml (>=3.13), pytest (>=4.0.0), sympy (>=1.3), textwrap From 82f9eacd095472ca20a59287c20965a173b1c732 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 4 Dec 2024 15:00:27 +0100 Subject: [PATCH 851/871] Move `src/nmodl/nmodl/python`. --- src/nmodl/nmodl/LICENSE | 176 -------- src/nmodl/nmodl/README.rst | 378 ------------------ .../{nmodl => }/python/nmodl/__init__.py | 0 .../{nmodl => }/python/nmodl/_binwrapper.py | 0 src/nmodl/{nmodl => }/python/nmodl/ast.py | 0 src/nmodl/{nmodl => }/python/nmodl/dsl.py | 0 .../python/nmodl/ext/example/exp2syn.mod | 0 .../python/nmodl/ext/example/expsyn.mod | 0 .../python/nmodl/ext/example/hh.mod | 0 .../python/nmodl/ext/example/passive.mod | 0 .../python/nmodl/ext/viz/css/tree.css | 0 .../python/nmodl/ext/viz/index.html | 0 .../python/nmodl/ext/viz/js/d3.min.js | 0 .../python/nmodl/ext/viz/js/tree.js | 0 src/nmodl/{nmodl => }/python/nmodl/ode.py | 0 src/nmodl/{nmodl => }/python/nmodl/symtab.py | 0 src/nmodl/{nmodl => }/python/nmodl/visitor.py | 0 17 files changed, 554 deletions(-) delete mode 100644 src/nmodl/nmodl/LICENSE delete mode 100644 src/nmodl/nmodl/README.rst rename src/nmodl/{nmodl => }/python/nmodl/__init__.py (100%) rename src/nmodl/{nmodl => }/python/nmodl/_binwrapper.py (100%) rename src/nmodl/{nmodl => }/python/nmodl/ast.py (100%) rename src/nmodl/{nmodl => }/python/nmodl/dsl.py (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/example/exp2syn.mod (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/example/expsyn.mod (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/example/hh.mod (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/example/passive.mod (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/viz/css/tree.css (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/viz/index.html (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/viz/js/d3.min.js (100%) rename src/nmodl/{nmodl => }/python/nmodl/ext/viz/js/tree.js (100%) rename src/nmodl/{nmodl => }/python/nmodl/ode.py (100%) rename src/nmodl/{nmodl => }/python/nmodl/symtab.py (100%) rename src/nmodl/{nmodl => }/python/nmodl/visitor.py (100%) diff --git a/src/nmodl/nmodl/LICENSE b/src/nmodl/nmodl/LICENSE deleted file mode 100644 index d9a10c0d8e..0000000000 --- a/src/nmodl/nmodl/LICENSE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/src/nmodl/nmodl/README.rst b/src/nmodl/nmodl/README.rst deleted file mode 100644 index 08ee4ef94b..0000000000 --- a/src/nmodl/nmodl/README.rst +++ /dev/null @@ -1,378 +0,0 @@ -The NMODL Framework -=================== - -|github workflow| |Build Status| |codecov| |CII Best Practices| - -The NMODL Framework is a code generation engine for **N**\ EURON -**MOD**\ eling **L**\ anguage -(`NMODL `__). -It is designed with modern compiler and code generation techniques to: - -- Provide **modular tools** for parsing, analysing and transforming - NMODL -- Provide **easy to use**, high level Python API -- Generate **optimised code** for modern compute architectures - including CPUs, GPUs -- **Flexibility** to implement new simulator backends -- Support for **full** NMODL specification - -About NMODL ------------ - -Simulators like `NEURON `__ use -NMODL as a domain specific language (DSL) to describe a wide range of -membrane and intracellular submodels. Here is an example of exponential -synapse specified in NMODL: - -.. code:: - - NEURON { - POINT_PROCESS ExpSyn - RANGE tau, e, i - NONSPECIFIC_CURRENT i - } - UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (uS) = (microsiemens) - } - PARAMETER { - tau = 0.1 (ms) <1e-9,1e9> - e = 0 (mV) - } - ASSIGNED { - v (mV) - i (nA) - } - STATE { - g (uS) - } - INITIAL { - g = 0 - } - BREAKPOINT { - SOLVE state METHOD cnexp - i = g*(v - e) - } - DERIVATIVE state { - g' = -g/tau - } - NET_RECEIVE(weight (uS)) { - g = g + weight - } - -Installation ------------- - -See -`INSTALL.rst `__ -for detailed instructions to build the NMODL from source. - -Try NMODL with Docker ---------------------- - -To quickly test the NMODL Framework’s analysis capabilities we provide a -`docker `__ image, which includes the NMODL -Framework python library and a fully functional Jupyter notebook -environment. After installing -`docker `__ and -`docker-compose `__ you can -pull and run the NMODL image from your terminal. - -To try Python interface directly from CLI, you can run docker image as: - -:: - - docker run -it --entrypoint=/bin/sh bluebrain/nmodl - -And try NMODL Python API discussed later in this README as: - -:: - - $ python3 - Python 3.6.8 (default, Apr 8 2019, 18:17:52) - >>> from nmodl import dsl - >>> import os - >>> examples = dsl.list_examples() - >>> nmodl_string = dsl.load_example(examples[-1]) - ... - -To try Jupyter notebooks you can download docker compose file and run it -as: - -.. code:: sh - - wget "https://raw.githubusercontent.com/BlueBrain/nmodl/master/docker/docker-compose.yml" - DUID=$(id -u) DGID=$(id -g) HOSTNAME=$(hostname) docker-compose up - -If all goes well you should see at the end status messages similar to -these: - -:: - - [I 09:49:53.923 NotebookApp] The Jupyter Notebook is running at: - [I 09:49:53.923 NotebookApp] http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 - [I 09:49:53.923 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation). - To access the notebook, open this file in a browser: - file:///root/.local/share/jupyter/runtime/nbserver-1-open.html - Or copy and paste one of these URLs: - http://(4c8edabe52e1 or 127.0.0.1):8888/?token=a7902983bad430a11935 - -Based on the example above you should then open your browser and -navigate to the URL -``http://127.0.0.1:8888/?token=a7902983bad430a11935``. - -You can open and run all example notebooks provided in the ``examples`` -folder. You can also create new notebooks in ``my_notebooks``, which -will be stored in a subfolder ``notebooks`` at your current working -directory. - -Using the Python API --------------------- - -Once the NMODL Framework is installed, you can use the Python parsing -API to load NMOD file as: - -.. code:: python - - from nmodl import dsl - - examples = dsl.list_examples() - nmodl_string = dsl.load_example(examples[-1]) - driver = dsl.NmodlDriver() - modast = driver.parse_string(nmodl_string) - -The ``parse_file`` API returns Abstract Syntax Tree -(`AST `__) -representation of input NMODL file. One can look at the AST by -converting to JSON form as: - -.. code:: python - - >>> print (dsl.to_json(modast)) - { - "Program": [ - { - "NeuronBlock": [ - { - "StatementBlock": [ - { - "Suffix": [ - { - "Name": [ - { - "String": [ - { - "name": "POINT_PROCESS" - } - ... - -Every key in the JSON form represent a node in the AST. You can also use -visualization API to look at the details of AST as: - -:: - - from nmodl import ast - ast.view(modast) - -which will open AST view in web browser: - -.. figure:: - https://user-images.githubusercontent.com/666852/57329449-12c9a400-7114-11e9-8da5-0042590044ec.gif - :alt: ast_viz - - Vizualisation of the AST in the NMODL Framework - -The central *Program* node represents the whole MOD file and each of -it’s children represent the block in the input NMODL file. Note that -this requires X-forwarding if you are using the Docker image. - -Once the AST is created, one can use exisiting visitors to perform -various analysis/optimisations. One can also easily write his own custom -visitor using Python Visitor API. See `Python API -tutorial `__ for details. - -The NMODL Framework also allows us to transform the AST representation back to -NMODL form as: - -.. code:: python - - >>> print (dsl.to_nmodl(modast)) - NEURON { - POINT_PROCESS ExpSyn - RANGE tau, e, i - NONSPECIFIC_CURRENT i - } - - UNITS { - (nA) = (nanoamp) - (mV) = (millivolt) - (uS) = (microsiemens) - } - - PARAMETER { - tau = 0.1 (ms) <1e-09,1000000000> - e = 0 (mV) - } - ... - -High Level Analysis and Code Generation ---------------------------------------- - -The NMODL Framework provides rich model introspection and analysis -capabilities using `various -visitors `__. -Here is an example of theoretical performance characterisation of -channels and synapses from rat neocortical column microcircuit -`published in -2015 `__: - -.. figure:: - https://user-images.githubusercontent.com/666852/57336711-2cc0b200-7127-11e9-8053-8f662e2ec191.png - :alt: nmodl-perf-stats - - Performance results of the NMODL Framework - -To understand how you can write your own introspection and analysis -tool, see `this -tutorial `__. - -Once analysis and optimization passes are performed, the NMODL Framework -can generate optimised code for modern compute architectures including -CPUs (Intel, AMD, ARM) and GPUs (NVIDIA, AMD) platforms. For example, -C++, OpenACC and OpenMP backends are implemented and one can choose -these backends on command line as: - -:: - - $ nmodl expsyn.mod sympy --analytic - -To know more about code generation backends, `see -here `__. -NMODL Framework provides number of options (for code generation, -optimization passes and ODE solver) which can be listed as: - -:: - - $ nmodl -H - NMODL : Source-to-Source Code Generation Framework [version] - Usage: /path/<>/nmodl [OPTIONS] file... [SUBCOMMAND] - - Positionals: - file TEXT:FILE ... REQUIRED One or more MOD files to process - - Options: - -h,--help Print this help message and exit - -H,--help-all Print this help message including all sub-commands - --verbose=info Verbose logger output (trace, debug, info, warning, error, critical, off) - -o,--output TEXT=. Directory for backend code output - --scratch TEXT=tmp Directory for intermediate code output - --units TEXT=/path/<>/nrnunits.lib - Directory of units lib file - - Subcommands: - host - HOST/CPU code backends - Options: - --c C/C++ backend (true) - - acc - Accelerator code backends - Options: - --oacc C/C++ backend with OpenACC (false) - - sympy - SymPy based analysis and optimizations - Options: - --analytic Solve ODEs using SymPy analytic integration (false) - --pade Pade approximation in SymPy analytic integration (false) - --cse CSE (Common Subexpression Elimination) in SymPy analytic integration (false) - --conductance Add CONDUCTANCE keyword in BREAKPOINT (false) - - passes - Analyse/Optimization passes - Options: - --inline Perform inlining at NMODL level (false) - --unroll Perform loop unroll at NMODL level (false) - --const-folding Perform constant folding at NMODL level (false) - --localize Convert RANGE variables to LOCAL (false) - --global-to-range Convert GLOBAL variables to RANGE (false) - --localize-verbatim Convert RANGE variables to LOCAL even if verbatim block exist (false) - --local-rename Rename LOCAL variable if variable of same name exist in global scope (false) - --verbatim-inline Inline even if verbatim block exist (false) - --verbatim-rename Rename variables in verbatim block (true) - --json-ast Write AST to JSON file (false) - --nmodl-ast Write AST to NMODL file (false) - --json-perf Write performance statistics to JSON file (false) - --show-symtab Write symbol table to stdout (false) - - codegen - Code generation options - Options: - --layout TEXT:{aos,soa}=soa Memory layout for code generation - --datatype TEXT:{float,double}=soa Data type for floating point variables - --force Force code generation even if there is any incompatibility - --only-check-compatibility Check compatibility and return without generating code - --opt-ionvar-copy Optimize copies of ion variables (false) - -Documentation -------------- - -We are working on user documentation, you can find current drafts of : - -- `User Documentation `__ -- `Developer / API - Documentation `__ - -Citation --------- - -If you would like to know more about the the NMODL Framework, see -following paper: - -- Pramod Kumbhar, Omar Awile, Liam Keegan, Jorge Alonso, James King, - Michael Hines and Felix Schürmann. 2019. An optimizing multi-platform - source-to-source compiler framework for the NEURON MODeling Language. - In Eprint : - `arXiv:1905.02241 `__ - -Support / Contribuition ------------------------ - -If you see any issue, feel free to `raise a -ticket `__. If you would -like to improve this framework, see `open -issues `__ and `contribution -guidelines `__. - -Examples / Benchmarks ---------------------- - -The benchmarks used to test the performance and parsing capabilities of -NMODL Framework are currently being migrated to GitHub. These benchmarks -will be published soon in following repositories: - -- `NMODL Benchmark `__ -- `NMODL Database `__ - -Funding & Acknowledgment ------------------------- - -The development of this software was supported by funding to the Blue -Brain Project, a research center of the École polytechnique fédérale de -Lausanne (EPFL), from the Swiss government’s ETH Board of the Swiss -Federal Institutes of Technology. In addition, the development was -supported by funding from the National Institutes of Health (NIH) under -the Grant Number R01NS11613 (Yale University) and the European Union’s -Horizon 2020 Framework Programme for Research and Innovation under the -Specific Grant Agreement No. 785907 (Human Brain Project SGA2). - -Copyright © 2017-2023 Blue Brain Project, EPFL - -.. |github workflow| image:: https://github.com/BlueBrain/nmodl/actions/workflows/nmodl-ci.yml/badge.svg?branch=master -.. |Build Status| image:: https://dev.azure.com/pramodskumbhar/nmodl/_apis/build/status/BlueBrain.nmodl?branchName=master - :target: https://dev.azure.com/pramodskumbhar/nmodl/_build/latest?definitionId=2&branchName=master -.. |codecov| image:: https://codecov.io/gh/BlueBrain/nmodl/branch/master/graph/badge.svg?token=A3NU9VbNcB - :target: https://codecov.io/gh/BlueBrain/nmodl -.. |CII Best Practices| image:: https://bestpractices.coreinfrastructure.org/projects/4467/badge - :target: https://bestpractices.coreinfrastructure.org/projects/4467 diff --git a/src/nmodl/nmodl/python/nmodl/__init__.py b/src/nmodl/python/nmodl/__init__.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/__init__.py rename to src/nmodl/python/nmodl/__init__.py diff --git a/src/nmodl/nmodl/python/nmodl/_binwrapper.py b/src/nmodl/python/nmodl/_binwrapper.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/_binwrapper.py rename to src/nmodl/python/nmodl/_binwrapper.py diff --git a/src/nmodl/nmodl/python/nmodl/ast.py b/src/nmodl/python/nmodl/ast.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ast.py rename to src/nmodl/python/nmodl/ast.py diff --git a/src/nmodl/nmodl/python/nmodl/dsl.py b/src/nmodl/python/nmodl/dsl.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/dsl.py rename to src/nmodl/python/nmodl/dsl.py diff --git a/src/nmodl/nmodl/python/nmodl/ext/example/exp2syn.mod b/src/nmodl/python/nmodl/ext/example/exp2syn.mod similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/example/exp2syn.mod rename to src/nmodl/python/nmodl/ext/example/exp2syn.mod diff --git a/src/nmodl/nmodl/python/nmodl/ext/example/expsyn.mod b/src/nmodl/python/nmodl/ext/example/expsyn.mod similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/example/expsyn.mod rename to src/nmodl/python/nmodl/ext/example/expsyn.mod diff --git a/src/nmodl/nmodl/python/nmodl/ext/example/hh.mod b/src/nmodl/python/nmodl/ext/example/hh.mod similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/example/hh.mod rename to src/nmodl/python/nmodl/ext/example/hh.mod diff --git a/src/nmodl/nmodl/python/nmodl/ext/example/passive.mod b/src/nmodl/python/nmodl/ext/example/passive.mod similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/example/passive.mod rename to src/nmodl/python/nmodl/ext/example/passive.mod diff --git a/src/nmodl/nmodl/python/nmodl/ext/viz/css/tree.css b/src/nmodl/python/nmodl/ext/viz/css/tree.css similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/viz/css/tree.css rename to src/nmodl/python/nmodl/ext/viz/css/tree.css diff --git a/src/nmodl/nmodl/python/nmodl/ext/viz/index.html b/src/nmodl/python/nmodl/ext/viz/index.html similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/viz/index.html rename to src/nmodl/python/nmodl/ext/viz/index.html diff --git a/src/nmodl/nmodl/python/nmodl/ext/viz/js/d3.min.js b/src/nmodl/python/nmodl/ext/viz/js/d3.min.js similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/viz/js/d3.min.js rename to src/nmodl/python/nmodl/ext/viz/js/d3.min.js diff --git a/src/nmodl/nmodl/python/nmodl/ext/viz/js/tree.js b/src/nmodl/python/nmodl/ext/viz/js/tree.js similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ext/viz/js/tree.js rename to src/nmodl/python/nmodl/ext/viz/js/tree.js diff --git a/src/nmodl/nmodl/python/nmodl/ode.py b/src/nmodl/python/nmodl/ode.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/ode.py rename to src/nmodl/python/nmodl/ode.py diff --git a/src/nmodl/nmodl/python/nmodl/symtab.py b/src/nmodl/python/nmodl/symtab.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/symtab.py rename to src/nmodl/python/nmodl/symtab.py diff --git a/src/nmodl/nmodl/python/nmodl/visitor.py b/src/nmodl/python/nmodl/visitor.py similarity index 100% rename from src/nmodl/nmodl/python/nmodl/visitor.py rename to src/nmodl/python/nmodl/visitor.py From a6ca9128f1dded74a838eb82ba09541f9d30b8c0 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 5 Dec 2024 07:37:18 +0100 Subject: [PATCH 852/871] Add submodule pybind11. --- .gitmodules | 3 +++ external/pybind11 | 1 + 2 files changed, 4 insertions(+) create mode 160000 external/pybind11 diff --git a/.gitmodules b/.gitmodules index 325b547740..23fcea10dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -31,3 +31,6 @@ [submodule "external/fmt"] path = external/fmt url = https://github.com/fmtlib/fmt +[submodule "external/pybind11"] + path = external/pybind11 + url = https://github.com/pybind/pybind11.git diff --git a/external/pybind11 b/external/pybind11 new file mode 160000 index 0000000000..3e9dfa2866 --- /dev/null +++ b/external/pybind11 @@ -0,0 +1 @@ +Subproject commit 3e9dfa2866941655c56877882565e7577de6fc7b From b3de1409f0eec68bf35ac1a8e3a7837436376e4f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 5 Dec 2024 07:40:02 +0100 Subject: [PATCH 853/871] Add submodule nlohmann_json. --- .gitmodules | 3 +++ external/json | 1 + 2 files changed, 4 insertions(+) create mode 160000 external/json diff --git a/.gitmodules b/.gitmodules index 23fcea10dd..6b1e9da737 100644 --- a/.gitmodules +++ b/.gitmodules @@ -34,3 +34,6 @@ [submodule "external/pybind11"] path = external/pybind11 url = https://github.com/pybind/pybind11.git +[submodule "external/json"] + path = external/json + url = https://github.com/nlohmann/json.git diff --git a/external/json b/external/json new file mode 160000 index 0000000000..9cca280a4d --- /dev/null +++ b/external/json @@ -0,0 +1 @@ +Subproject commit 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 From 5c9bf6b04425bd6a5f12824870b55664ad0225ef Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 5 Dec 2024 08:02:20 +0100 Subject: [PATCH 854/871] Add submodule spdlog. --- .gitmodules | 3 +++ external/spdlog | 1 + 2 files changed, 4 insertions(+) create mode 160000 external/spdlog diff --git a/.gitmodules b/.gitmodules index 6b1e9da737..e10d9f0e8f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -37,3 +37,6 @@ [submodule "external/json"] path = external/json url = https://github.com/nlohmann/json.git +[submodule "external/spdlog"] + path = external/spdlog + url = https://github.com/gabime/spdlog.git diff --git a/external/spdlog b/external/spdlog new file mode 160000 index 0000000000..7c02e204c9 --- /dev/null +++ b/external/spdlog @@ -0,0 +1 @@ +Subproject commit 7c02e204c92545f869e2f04edaab1f19fe8b19fd From a71929620d049b9907a63c33b272805a12c6a827 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Thu, 5 Dec 2024 13:13:31 +0100 Subject: [PATCH 855/871] Fiddle CMake code (CoreNEURON + NMODL). --- CMakeLists.txt | 14 +- cmake/nmodl/CMakeLists.txt | 24 +--- src/nmodl/CMakeLists.txt | 124 ++++++++++++++++++ .../language/templates/code_generator.cmake | 8 +- src/nmodl/lexer/CMakeLists.txt | 45 +++---- src/nmodl/pybind/CMakeLists.txt | 12 +- src/nmodl/pybind/pyembed.cpp | 1 + src/nmodl/utils/CMakeLists.txt | 2 +- .../visitors/verbatim_var_rename_visitor.cpp | 2 +- 9 files changed, 179 insertions(+), 53 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 90df4b3935..2c45629b61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -507,14 +507,24 @@ cpp_cc_git_submodule(CLI11 BUILD PACKAGE CLI11 REQUIRED) # Enable NMODL code-generator support # ============================================================================= if(NRN_ENABLE_NMODL OR NRN_ENABLE_CORENEURON) + cpp_cc_git_submodule(json BUILD PACKAGE nlohmann_json REQUIRED) + cpp_cc_git_submodule(pybind11 BUILD PACKAGE pybind11 REQUIRED) + + option(SPDLOG_FMT_EXTERNAL "Force to use an external {{fmt}}" ON) + option(SPDLOG_SYSTEM_INCLUDE "Include spdlog as a system lib" ON) + cpp_cc_git_submodule(spdlog BUILD PACKAGE spdlog REQUIRED) + if(${CODING_CONV_PREFIX}_3RDPARTY_USE_SPDLOG) + # See above, same logic as fmt + set_target_properties(spdlog PROPERTIES POSITION_INDEPENDENT_CODE ON) + endif() set(NMODL_ENABLE_PYTHON_BINDINGS OFF CACHE BOOL "Enable NMODL python bindings") - nrn_add_external_project(nmodl) + add_subdirectory(src/nmodl) set(CORENRN_NMODL_BINARY ${CMAKE_BINARY_DIR}/bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) set(NMODL_TARGET_TO_DEPEND nmodl) - set(NMODL_PROJECT_BINARY_DIR ${CMAKE_BINARY_DIR}/external/nmodl) + set(NMODL_PROJECT_BINARY_DIR ${CMAKE_BINARY_DIR}/src/nmodl) # install nrnunits.lib and libpywrapper.so from external/nmodl install( FILES ${NMODL_PROJECT_BINARY_DIR}/lib/libpywrapper${CMAKE_SHARED_LIBRARY_SUFFIX} diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 1a98986d65..bd588522b3 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -161,10 +161,10 @@ add_custom_target( --execute --inplace --ExecutePreprocessor.timeout=360 - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb" + "${PROJECT_SOURCE_DIR}/docs/nmodl/transpiler/notebooks/*.ipynb" && clean_ipynb - "${CMAKE_SOURCE_DIR}/docs/notebooks/*.ipynb") + "${PROJECT_SOURCE_DIR}/docs/nmodl/transpiler/notebooks/*.ipynb") # ============================================================================= # Adjust install prefix for wheel @@ -190,28 +190,18 @@ if(NMODL_PGI_COMPILER) add_compile_definitions(JSON_SKIP_UNSUPPORTED_COMPILER_CHECK=1) endif() -include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_SOURCE_DIR}/src - ${PROJECT_BINARY_DIR}/src) +include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_BINARY_DIR}) # generate file with version number from git and nrnunits.lib file path -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/config/config.cpp.in - ${PROJECT_BINARY_DIR}/src/config/config.cpp @ONLY) +configure_file(${NMODL_PROJECT_SOURCE_DIR}/config/config.cpp.in + ${NMODL_PROJECT_BINARY_DIR}/config/config.cpp @ONLY) # generate Doxyfile with correct source paths configure_file(${NMODL_PROJECT_SOURCE_DIR}/docs/Doxyfile.in ${NMODL_PROJECT_SOURCE_DIR}/docs/Doxyfile) -# ============================================================================= -# Memory checker options and add tests -# ============================================================================= -find_program(MEMORYCHECK_COMMAND valgrind) -set(MEMORYCHECK_COMMAND_OPTIONS - "--trace-children=yes \ - --leak-check=full \ - --track-origins=yes \ - --show-possibly-lost=no") # do not enable tests if nmodl is used as submodule -if(NOT NMODL_AS_SUBPROJECT AND NMODL_ENABLE_TESTS) +if(NMODL_ENABLE_TESTS) cpp_cc_git_submodule(eigen) cpp_cc_git_submodule(catch2 BUILD PACKAGE Catch2 REQUIRED) if(NMODL_3RDPARTY_USE_CATCH2) @@ -294,7 +284,7 @@ unset(CODE_GENERATOR_OPTS) # ----------------------------------------------------------------------------- # Target to propagate dependencies properly to lexer # ----------------------------------------------------------------------------- -add_custom_target(pyastgen DEPENDS ${PROJECT_BINARY_DIR}/src/ast/ast.cpp) +add_custom_target(pyastgen DEPENDS ${PROJECT_BINARY_DIR}/src/nmodl/ast/ast.cpp) # ============================================================================= # Deal with the hand-written code diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 14051dba96..514e634cd2 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -1,3 +1,100 @@ +# ============================================================================= +# Build options for NMODL +# ============================================================================= +set(NMODL_MAX_ERRORS + 1 + CACHE STRING "Number of errors before Clang/GCC stop.") +option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) +option(NMODL_ENABLE_TESTS "Enable build of tests" ON) +option(NMODL_ENABLE_USECASES + "If building tests, additionally enable build of usecase tests. Requires neuron." OFF) +option(NMODL_ENABLE_BACKWARD "Use backward, enables blame." OFF) +set(NMODL_EXTRA_CXX_FLAGS + "" + CACHE STRING "Add extra compile flags for NMODL sources") +separate_arguments(NMODL_EXTRA_CXX_FLAGS) +option(LINK_AGAINST_PYTHON "Should the Python library be linked or not" ON) +option(NMODL_BUILD_WHEEL "Flag to signal we are building a wheel" OFF) +if(NMODL_BUILD_WHEEL) + set(LINK_AGAINST_PYTHON OFF) + set(NMODL_ENABLE_TESTS OFF) +endif() + +include(${PROJECT_SOURCE_DIR}/cmake/nmodl/PythonLinkHelper.cmake) + +set(NMODL_PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) +set(NMODL_PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) + +include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_BINARY_DIR}) + +# ============================================================================= +# Create some random files. +# ============================================================================= + +# generate file with version number from git and nrnunits.lib file path +configure_file(${NMODL_PROJECT_SOURCE_DIR}/config/config.cpp.in + ${NMODL_PROJECT_BINARY_DIR}/config/config.cpp @ONLY) + +# generate Doxyfile with correct source paths +configure_file(${PROJECT_SOURCE_DIR}/docs/nmodl/transpiler/Doxyfile.in + ${PROJECT_BINARY_DIR}/docs/nmodl/transpiler/Doxyfile) + +# ============================================================================= +# Generate NMODL code, e.g. ast & visitor base classes. +# ----------------------------------------------------------------------------- +if(NOT NMODL_ENABLE_PYTHON_BINDINGS) + list(APPEND CODE_GENERATOR_OPTS "--disable-pybind") +endif() + +# ----------------------------------------------------------------------------- +# Part I: generate the list of generated files +# ----------------------------------------------------------------------------- +execute_process( + COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/language/code_generator.py + ${CODE_GENERATOR_OPTS} --generate-cmake --base-dir ${CMAKE_CURRENT_BINARY_DIR}/language + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/language) + +# Ensure that the above runs again, if its dependencies update. +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/language/code_generator.py") + +set_property( + DIRECTORY + APPEND + PROPERTY CMAKE_CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/language/templates/code_generator.cmake) + +# The list should be up-to-date now. +include(${CMAKE_CURRENT_BINARY_DIR}/language/code_generator.cmake) + +# ----------------------------------------------------------------------------- +# Part II: generate AST/Visitor classes from language definition +# ----------------------------------------------------------------------------- +set_source_files_properties(${NMODL_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) + +# Make the codegen options available as a dependency by storing them in a file. +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/language/code_generator_opts.in + ${CMAKE_CURRENT_BINARY_DIR}/language/code_generator_opts) + +add_custom_command( + OUTPUT ${NMODL_GENERATED_SOURCES} + COMMAND ${PYTHON_EXECUTABLE} ARGS ${CMAKE_CURRENT_SOURCE_DIR}/language/code_generator.py + ${CODE_GENERATOR_OPTS} --base-dir ${CMAKE_CURRENT_BINARY_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/language + DEPENDS ${CODE_GENERATOR_PY_FILES} + DEPENDS ${CODE_GENERATOR_YAML_FILES} + DEPENDS ${CODE_GENERATOR_JINJA_FILES} + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/language/code_generator_opts + COMMENT "-- NMODL : GENERATING AST CLASSES WITH PYTHON GENERATOR! --") +unset(CODE_GENERATOR_OPTS) + +# ----------------------------------------------------------------------------- +# Target to propagate dependencies properly to lexer +# ----------------------------------------------------------------------------- +add_custom_target(pyastgen DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/ast/ast.cpp) + # ============================================================================= # Add extra compile flags to NMODL sources # ============================================================================= @@ -37,6 +134,10 @@ target_link_libraries( add_dependencies(nmodl nmodl_copy_python_files nmodl_copy_solver_files) cpp_cc_configure_sanitizers(TARGET nmodl) +# set(nmodl_BINARY bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) +add_executable(nmodl::nmodl ALIAS nmodl) + + # ============================================================================= # Add dependency with nmodl Python module (for consumer projects) # ============================================================================= @@ -53,3 +154,26 @@ install( TARGETS nmodl EXPORT nmodlTargets RUNTIME DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin) + + +configure_file(${PROJECT_SOURCE_DIR}/share/nmodl/nrnunits.lib ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib COPYONLY) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share/nmodl) + +install( + EXPORT nmodlTargets + FILE nmodlTargets.cmake + NAMESPACE nmodl:: + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) + +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${PROJECT_SOURCE_DIR}/cmake/nmodl/Config.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfig.cmake" + INSTALL_DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/nmodlConfigVersion.cmake" + COMPATIBILITY AnyNewerVersion) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfigVersion.cmake" + DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) + diff --git a/src/nmodl/language/templates/code_generator.cmake b/src/nmodl/language/templates/code_generator.cmake index 45d150bc19..5be4f2eaf0 100644 --- a/src/nmodl/language/templates/code_generator.cmake +++ b/src/nmodl/language/templates/code_generator.cmake @@ -5,26 +5,26 @@ # cmake-format: off set(CODE_GENERATOR_JINJA_FILES {% for template in templates | sort %} - ${PROJECT_SOURCE_DIR}/src/language/templates/{{ template.as_posix() }} + ${PROJECT_SOURCE_DIR}/src/nmodl/language/templates/{{ template.as_posix() }} {% endfor %} ) set(CODE_GENERATOR_PY_FILES {% for file in py_files | sort %} - ${PROJECT_SOURCE_DIR}/src/language/{{ file.as_posix() }} + ${PROJECT_SOURCE_DIR}/src/nmodl/language/{{ file.as_posix() }} {% endfor %} ) set(CODE_GENERATOR_YAML_FILES {% for file in yaml_files | sort %} - ${PROJECT_SOURCE_DIR}/src/language/{{ file.as_posix() }} + ${PROJECT_SOURCE_DIR}/src/nmodl/language/{{ file.as_posix() }} {% endfor %} ) {% for dir, files in outputs | dictsort %} set({{ dir | upper }}_GENERATED_SOURCES {% for file in files | sort %} - ${PROJECT_BINARY_DIR}/src/{{ dir }}/{{ file }} + ${PROJECT_BINARY_DIR}/src/nmodl/{{ dir }}/{{ file }} {% endfor %} ) diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 93cab280b2..376cf67bb0 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -2,32 +2,32 @@ # Various project components and their source files # ============================================================================= set(BISON_GENERATED_SOURCE_FILES - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.cpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.cpp) + ${CMAKE_CURRENT_BINARY_DIR}/../parser/nmodl/nmodl_parser.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/verbatim_parser.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/diffeq/diffeq_parser.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/c/c11_parser.cpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/unit/unit_parser.cpp) set(BISON_GENERATED_HEADER_FILES - ${PROJECT_BINARY_DIR}/src/parser/nmodl/location.hh - ${PROJECT_BINARY_DIR}/src/parser/nmodl/nmodl_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/nmodl/position.hh - ${PROJECT_BINARY_DIR}/src/parser/verbatim_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/diffeq/diffeq_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/c/c11_parser.hpp - ${PROJECT_BINARY_DIR}/src/parser/unit/unit_parser.hpp) + ${CMAKE_CURRENT_BINARY_DIR}/../parser/nmodl/location.hh + ${CMAKE_CURRENT_BINARY_DIR}/../parser/nmodl/nmodl_parser.hpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/nmodl/position.hh + ${CMAKE_CURRENT_BINARY_DIR}/../parser/verbatim_parser.hpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/diffeq/diffeq_parser.hpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/c/c11_parser.hpp + ${CMAKE_CURRENT_BINARY_DIR}/../parser/unit/unit_parser.hpp) -set(UNIT_SOURCE_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/units/units.cpp) +set(UNIT_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../units/units.cpp) -set(NMODL_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/nmodl_driver.cpp) +set(NMODL_DRIVER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../parser/nmodl_driver.cpp) -set(DIFFEQ_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_driver.cpp - ${NMODL_PROJECT_SOURCE_DIR}/src/parser/diffeq_context.cpp) +set(DIFFEQ_DRIVER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../parser/diffeq_driver.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../parser/diffeq_context.cpp) -set(C_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/c11_driver.cpp) +set(C_DRIVER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../parser/c11_driver.cpp) set_source_files_properties(${AST_GENERATED_SOURCES} PROPERTIES GENERATED TRUE) -set(UNIT_DRIVER_FILES ${NMODL_PROJECT_SOURCE_DIR}/src/parser/unit_driver.cpp) +set(UNIT_DRIVER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/../parser/unit_driver.cpp) set(LEXER_SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/token_mapping.cpp @@ -49,11 +49,14 @@ if(NMODL_PGI_COMPILER) PROPERTIES COMPILE_FLAGS "--diag_suppress 550") endif() +set(NMODL_PARSER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../parser") +set(NMODL_PARSER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../parser") + # ============================================================================= # Directories for parsers (as they need to be in separate directories) # ============================================================================= -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR}/src/parser/diffeq - ${PROJECT_BINARY_DIR}/src/parser/c ${PROJECT_BINARY_DIR}/src/parser/unit) +file(MAKE_DIRECTORY ${NMODL_PARSER_BINARY_DIR}/nmodl ${NMODL_PARSER_BINARY_DIR}/diffeq + ${NMODL_PARSER_BINARY_DIR}/c ${NMODL_PARSER_BINARY_DIR}/unit) # ============================================================================= # Lexer & Parser commands @@ -61,8 +64,6 @@ file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/src/parser/nmodl ${PROJECT_BINARY_DIR} # Command to generate nmodl parser Construct a relative path to avoid baking any absolute paths into # the output .cpp and .hpp files (via __FILE__ and so on), which cause ccache misses when the build # prefix changes (e.g. GitLab CI). -set(NMODL_PARSER_SOURCE_DIR "${NMODL_PROJECT_SOURCE_DIR}/src/parser") -set(NMODL_PARSER_BINARY_DIR "${NMODL_PROJECT_BINARY_DIR}/src/parser") file(RELATIVE_PATH NMODL_YY_FROM_PARSER_BINARY_DIR "${NMODL_PARSER_BINARY_DIR}" "${NMODL_PARSER_SOURCE_DIR}/nmodl.yy") add_custom_command( diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index b6a8b23187..8071951a33 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -5,7 +5,7 @@ set_source_files_properties(${PYBIND_GENERATED_SOURCES} PROPERTIES GENERATED TRU # Set -fno-var-tracking-assignments on pyast.cpp with GCC to avoid a warning + double compilation if(${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU") - foreach(pybind_file "${PROJECT_BINARY_DIR}/src/pybind/pyast.cpp") + foreach(pybind_file "${NMODL_PROJECT_BINARY_DIR}/pybind/pyast.cpp") get_source_file_property(pybind_file_compile_options "${pybind_file}" COMPILE_OPTIONS) if("${pybind_file_compile_options}" STREQUAL "NOTFOUND") set(pybind_file_compile_options) @@ -19,11 +19,11 @@ endif() # build nmodl python module under lib/nmodl set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib/nmodl) -file(READ ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py NMODL_ODE_PY) +file(READ ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ode.py NMODL_ODE_PY) set_property( DIRECTORY APPEND - PROPERTY CMAKE_CONFIGURE_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../../python/nmodl/ode.py) + PROPERTY CMAKE_CONFIGURE_DEPENDS ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/ode.py) if(WIN32) # MSVC can't handle long string literals, even if the documentation claims so See # https://developercommunity.visualstudio.com/t/c-string-literal-max-length-much-shorter-than-docu/758957 @@ -71,9 +71,9 @@ if(NMODL_ENABLE_PYTHON_BINDINGS) # for pybind using NO_EXTRAS. See #266. # ~~~ pybind11_add_module( - _nmodl NO_EXTRAS ${NMODL_PROJECT_SOURCE_DIR}/src/ast/ast_common.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pybind_utils.hpp - ${NMODL_PROJECT_SOURCE_DIR}/src/pybind/pynmodl.cpp ${PYBIND_GENERATED_SOURCES}) + _nmodl NO_EXTRAS ${NMODL_PROJECT_SOURCE_DIR}/ast/ast_common.hpp + ${NMODL_PROJECT_SOURCE_DIR}/pybind/pybind_utils.hpp + ${NMODL_PROJECT_SOURCE_DIR}/pybind/pynmodl.cpp ${PYBIND_GENERATED_SOURCES}) add_dependencies(_nmodl lexer pyastgen util) target_link_libraries(_nmodl PRIVATE printer symtab visitor pyembed) set_target_properties(_nmodl PROPERTIES LIBRARY_OUTPUT_DIRECTORY_DEBUG diff --git a/src/nmodl/pybind/pyembed.cpp b/src/nmodl/pybind/pyembed.cpp index f6e9b5e49c..106d66d8bf 100644 --- a/src/nmodl/pybind/pyembed.cpp +++ b/src/nmodl/pybind/pyembed.cpp @@ -24,6 +24,7 @@ namespace nmodl { namespace pybind_wrappers { + using nmodl_init_pybind_wrapper_api_fpointer = decltype(&nmodl_init_pybind_wrapper_api); bool EmbeddedPythonLoader::have_wrappers() { diff --git a/src/nmodl/utils/CMakeLists.txt b/src/nmodl/utils/CMakeLists.txt index 24cf674559..757396050b 100644 --- a/src/nmodl/utils/CMakeLists.txt +++ b/src/nmodl/utils/CMakeLists.txt @@ -10,7 +10,7 @@ add_library( perf_stat.cpp string_utils.cpp table_data.cpp - ${PROJECT_BINARY_DIR}/src/config/config.cpp) + ${PROJECT_BINARY_DIR}/src/nmodl/config/config.cpp) set_property(TARGET util PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries(util PUBLIC fmt::fmt nlohmann_json::nlohmann_json spdlog::spdlog) diff --git a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp index 7faf414aba..bc0155d178 100644 --- a/src/nmodl/visitors/verbatim_var_rename_visitor.cpp +++ b/src/nmodl/visitors/verbatim_var_rename_visitor.cpp @@ -11,7 +11,7 @@ #include "ast/string.hpp" #include "ast/verbatim.hpp" #include "parser/c11_driver.hpp" -#include "src/utils/logger.hpp" +#include "utils/logger.hpp" namespace nmodl { From f32f9ace71e3a18c2de7e6a562294d79a5b5eef1 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 9 Dec 2024 07:40:25 +0000 Subject: [PATCH 856/871] Update src/nmodl/CMakeLists.txt Co-authored-by: JCGoran --- src/nmodl/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 514e634cd2..bdfae7c3ac 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -21,7 +21,7 @@ if(NMODL_BUILD_WHEEL) endif() include(${PROJECT_SOURCE_DIR}/cmake/nmodl/PythonLinkHelper.cmake) - +include(${PROJECT_SOURCE_DIR}/cmake/nmodl/FlexHelper.cmake) set(NMODL_PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) set(NMODL_PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) From b4a121810f5c6c79b70ab0efef312c0bcad19a74 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 9 Dec 2024 11:48:54 +0100 Subject: [PATCH 857/871] pointless stuff --- cmake/nmodl/CMakeLists.txt | 2 +- src/nmodl/CMakeLists.txt | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index bd588522b3..6d40ac51c1 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -53,7 +53,7 @@ endif() # ============================================================================= # Compile static libraries with hidden visibility # ============================================================================= -set(CMAKE_CXX_VISIBILITY_PRESET hidden) +# set(CMAKE_CXX_VISIBILITY_PRESET hidden) # ============================================================================= # Find required packages diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index bdfae7c3ac..ea47996c36 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -1,9 +1,6 @@ # ============================================================================= # Build options for NMODL # ============================================================================= -set(NMODL_MAX_ERRORS - 1 - CACHE STRING "Number of errors before Clang/GCC stop.") option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) option(NMODL_ENABLE_TESTS "Enable build of tests" ON) option(NMODL_ENABLE_USECASES @@ -27,6 +24,8 @@ set(NMODL_PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_BINARY_DIR}) +list(APPEND NMODL_EXTRA_CXX_FLAGS ${NMODL_SANITIZER_COMPILER_FLAGS}) + # ============================================================================= # Create some random files. # ============================================================================= From 3a6f65c14bb2e7baff4e42be4c8e135bfea0ce9c Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 9 Dec 2024 16:38:03 +0100 Subject: [PATCH 858/871] Add formatting ignore list. --- .bbp-project.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.bbp-project.yaml b/.bbp-project.yaml index 4dd84d1d52..de7f18e88f 100644 --- a/.bbp-project.yaml +++ b/.bbp-project.yaml @@ -9,6 +9,13 @@ tools: ClangFormat: enable: True version: == 12.0.1 + exclude: + match: + - src/nmodl/language/templates/* CMakeFormat: enable: True version: == 0.6.13 + exclude: + match: + - src/nmodl/language/templates/* + - test/nmodl/usecases/*/* From f77dec47a3cb699e97b2912db563a1040a76c268 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Mon, 9 Dec 2024 16:38:31 +0100 Subject: [PATCH 859/871] formatting. --- src/nmodl/CMakeLists.txt | 8 +++----- src/nmodl/codegen/codegen_acc_visitor.hpp | 1 - src/nmodl/codegen/codegen_cpp_visitor.hpp | 2 -- src/nmodl/lexer/CMakeLists.txt | 2 +- src/nmodl/pybind/CMakeLists.txt | 6 ++++-- src/nmodl/visitors/cvode_visitor.cpp | 1 + src/nmodl/visitors/longitudinal_diffusion_visitor.hpp | 1 - src/nmodl/visitors/semantic_analysis_visitor.hpp | 1 + test/nmodl/transpiler/unit/pybind/test_visitor.py | 1 - 9 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index ea47996c36..3930e96e20 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -50,7 +50,7 @@ endif() # ----------------------------------------------------------------------------- execute_process( COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/language/code_generator.py - ${CODE_GENERATOR_OPTS} --generate-cmake --base-dir ${CMAKE_CURRENT_BINARY_DIR}/language + ${CODE_GENERATOR_OPTS} --generate-cmake --base-dir ${CMAKE_CURRENT_BINARY_DIR}/language WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/language) # Ensure that the above runs again, if its dependencies update. @@ -136,7 +136,6 @@ cpp_cc_configure_sanitizers(TARGET nmodl) # set(nmodl_BINARY bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) add_executable(nmodl::nmodl ALIAS nmodl) - # ============================================================================= # Add dependency with nmodl Python module (for consumer projects) # ============================================================================= @@ -154,8 +153,8 @@ install( EXPORT nmodlTargets RUNTIME DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}bin) - -configure_file(${PROJECT_SOURCE_DIR}/share/nmodl/nrnunits.lib ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib COPYONLY) +configure_file(${PROJECT_SOURCE_DIR}/share/nmodl/nrnunits.lib + ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib COPYONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/share/nmodl/nrnunits.lib DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}share/nmodl) @@ -175,4 +174,3 @@ write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/nmodlConfigVersion install(FILES "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/nmodlConfigVersion.cmake" DESTINATION ${NMODL_INSTALL_DIR_SUFFIX}lib/cmake/nmodl) - diff --git a/src/nmodl/codegen/codegen_acc_visitor.hpp b/src/nmodl/codegen/codegen_acc_visitor.hpp index 15f75d5f76..d7afd6648e 100644 --- a/src/nmodl/codegen/codegen_acc_visitor.hpp +++ b/src/nmodl/codegen/codegen_acc_visitor.hpp @@ -32,7 +32,6 @@ class CodegenAccVisitor: public CodegenCoreneuronCppVisitor { using CodegenCoreneuronCppVisitor::CodegenCoreneuronCppVisitor; protected: - /// name of the code generation backend std::string backend_name() const override; diff --git a/src/nmodl/codegen/codegen_cpp_visitor.hpp b/src/nmodl/codegen/codegen_cpp_visitor.hpp index 99c35561c3..f2cbc6cdf3 100644 --- a/src/nmodl/codegen/codegen_cpp_visitor.hpp +++ b/src/nmodl/codegen/codegen_cpp_visitor.hpp @@ -1491,7 +1491,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { virtual void setup(const ast::Program& node); public: - /** * Main and only member function to call after creating an instance of this class. * \param program the AST to translate to C++ code @@ -1500,7 +1499,6 @@ class CodegenCppVisitor: public visitor::ConstAstVisitor { protected: - /** * Print the structure that wraps all range and int variables required for the NMODL * diff --git a/src/nmodl/lexer/CMakeLists.txt b/src/nmodl/lexer/CMakeLists.txt index 376cf67bb0..ded00ae123 100644 --- a/src/nmodl/lexer/CMakeLists.txt +++ b/src/nmodl/lexer/CMakeLists.txt @@ -56,7 +56,7 @@ set(NMODL_PARSER_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/../parser") # Directories for parsers (as they need to be in separate directories) # ============================================================================= file(MAKE_DIRECTORY ${NMODL_PARSER_BINARY_DIR}/nmodl ${NMODL_PARSER_BINARY_DIR}/diffeq - ${NMODL_PARSER_BINARY_DIR}/c ${NMODL_PARSER_BINARY_DIR}/unit) + ${NMODL_PARSER_BINARY_DIR}/c ${NMODL_PARSER_BINARY_DIR}/unit) # ============================================================================= # Lexer & Parser commands diff --git a/src/nmodl/pybind/CMakeLists.txt b/src/nmodl/pybind/CMakeLists.txt index 8071951a33..142f309781 100644 --- a/src/nmodl/pybind/CMakeLists.txt +++ b/src/nmodl/pybind/CMakeLists.txt @@ -98,8 +98,10 @@ file( CONFIGURE_DEPENDS "${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/*.py") foreach(file IN LISTS NMODL_PYTHON_FILES) - cpp_cc_build_time_copy(INPUT ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/${file} OUTPUT - ${CMAKE_BINARY_DIR}/lib/nmodl/${file} NO_TARGET) + cpp_cc_build_time_copy( + INPUT ${NMODL_PROJECT_SOURCE_DIR}/python/nmodl/${file} + OUTPUT ${CMAKE_BINARY_DIR}/lib/nmodl/${file} + NO_TARGET) list(APPEND nmodl_python_binary_dir_files "${CMAKE_BINARY_DIR}/lib/nmodl/${file}") endforeach() add_custom_target(nmodl_copy_python_files ALL DEPENDS ${nmodl_python_binary_dir_files}) diff --git a/src/nmodl/visitors/cvode_visitor.cpp b/src/nmodl/visitors/cvode_visitor.cpp index 617e1e6c65..5d35547d88 100644 --- a/src/nmodl/visitors/cvode_visitor.cpp +++ b/src/nmodl/visitors/cvode_visitor.cpp @@ -98,6 +98,7 @@ class CvodeHelperVisitor: public AstVisitor { protected: symtab::SymbolTable* program_symtab = nullptr; bool in_differential_equation = false; + public: inline void visit_diff_eq_expression(ast::DiffEqExpression& node) { in_differential_equation = true; diff --git a/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp b/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp index 7caec2a6fd..5cac656d9f 100644 --- a/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp +++ b/src/nmodl/visitors/longitudinal_diffusion_visitor.hpp @@ -12,7 +12,6 @@ namespace visitor { class CreateLongitudinalDiffusionBlocks: public AstVisitor { public: - void visit_program(ast::Program& node) override; }; diff --git a/src/nmodl/visitors/semantic_analysis_visitor.hpp b/src/nmodl/visitors/semantic_analysis_visitor.hpp index 06165614fa..d9901a4697 100644 --- a/src/nmodl/visitors/semantic_analysis_visitor.hpp +++ b/src/nmodl/visitors/semantic_analysis_visitor.hpp @@ -99,6 +99,7 @@ class SemanticAnalysisVisitor: public ConstAstVisitor { bool check_name_conflict(const ast::Program& node); bool check_table_vars(const ast::Program& node); + public: SemanticAnalysisVisitor(bool accel_backend = false) : accel_backend(accel_backend) {} diff --git a/test/nmodl/transpiler/unit/pybind/test_visitor.py b/test/nmodl/transpiler/unit/pybind/test_visitor.py index 2ba566666e..cbb399ba9d 100644 --- a/test/nmodl/transpiler/unit/pybind/test_visitor.py +++ b/test/nmodl/transpiler/unit/pybind/test_visitor.py @@ -67,7 +67,6 @@ def test_json_visitor(ch_ast): def test_custom_visitor(ch_ast): - class StateVisitor(visitor.AstVisitor): def __init__(self): visitor.AstVisitor.__init__(self) From 8f840a59e19c6c28bd8c9d0f1f09a1234ccd4f19 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 11:04:02 +0100 Subject: [PATCH 860/871] Enable unit and integration tests. --- CMakeLists.txt | 25 ++++++ src/nmodl/CMakeLists.txt | 23 ------ test/CMakeLists.txt | 6 ++ test/nmodl/transpiler/unit/CMakeLists.txt | 80 ++++++++++--------- .../codegen/codegen_compatibility_visitor.cpp | 2 +- .../codegen_coreneuron_cpp_visitor.cpp | 2 +- .../codegen/codegen_neuron_cpp_visitor.cpp | 2 +- test/nmodl/transpiler/unit/codegen/main.cpp | 2 +- .../transpiler/unit/codegen/transform.cpp | 2 +- .../transpiler/unit/modtoken/modtoken.cpp | 2 +- test/nmodl/transpiler/unit/parser/parser.cpp | 4 +- test/nmodl/transpiler/unit/units/parser.cpp | 2 +- .../unit/visitor/after_cvode_to_cnexp.cpp | 2 +- .../unit/visitor/constant_folder.cpp | 2 +- test/nmodl/transpiler/unit/visitor/cvode.cpp | 2 +- .../unit/visitor/defuse_analyze.cpp | 2 +- .../transpiler/unit/visitor/external.cpp | 2 +- .../unit/visitor/global_to_range.cpp | 2 +- .../unit/visitor/implicit_argument.cpp | 2 +- test/nmodl/transpiler/unit/visitor/inline.cpp | 2 +- .../transpiler/unit/visitor/kinetic_block.cpp | 2 +- .../unit/visitor/local_to_assigned.cpp | 2 +- .../transpiler/unit/visitor/localize.cpp | 2 +- .../transpiler/unit/visitor/localrename.cpp | 2 +- test/nmodl/transpiler/unit/visitor/lookup.cpp | 2 +- .../transpiler/unit/visitor/loop_unroll.cpp | 2 +- test/nmodl/transpiler/unit/visitor/main.cpp | 2 +- test/nmodl/transpiler/unit/visitor/misc.cpp | 2 +- .../transpiler/unit/visitor/neuron_solve.cpp | 2 +- test/nmodl/transpiler/unit/visitor/nmodl.cpp | 4 +- .../transpiler/unit/visitor/node_index.cpp | 2 +- test/nmodl/transpiler/unit/visitor/rename.cpp | 2 +- .../unit/visitor/semantic_analysis.cpp | 2 +- .../transpiler/unit/visitor/solve_block.cpp | 2 +- .../transpiler/unit/visitor/steadystate.cpp | 2 +- .../unit/visitor/sympy_conductance.cpp | 2 +- .../transpiler/unit/visitor/sympy_solver.cpp | 2 +- test/nmodl/transpiler/unit/visitor/units.cpp | 6 +- .../transpiler/unit/visitor/var_usage.cpp | 2 +- 39 files changed, 113 insertions(+), 99 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2c45629b61..ca7ccfabbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -209,6 +209,25 @@ set(NRN_PROFILER "${NRN_PROFILER_DEFAULT}" CACHE STRING "Set which profiler to build against ('caliper', 'likwid')") +# ============================================================================= +# Build options for NMODL +# ============================================================================= +option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) +option(NMODL_ENABLE_TESTS "Enable build of tests" ON) +option(NMODL_ENABLE_USECASES + "If building tests, additionally enable build of usecase tests. Requires neuron." OFF) +option(NMODL_ENABLE_BACKWARD "Use backward, enables blame." OFF) +set(NMODL_EXTRA_CXX_FLAGS + "" + CACHE STRING "Add extra compile flags for NMODL sources") +separate_arguments(NMODL_EXTRA_CXX_FLAGS) +option(LINK_AGAINST_PYTHON "Should the Python library be linked or not" ON) +option(NMODL_BUILD_WHEEL "Flag to signal we are building a wheel" OFF) +if(NMODL_BUILD_WHEEL) + set(LINK_AGAINST_PYTHON OFF) + set(NMODL_ENABLE_TESTS OFF) +endif() + # ============================================================================= # Include cmake modules # ============================================================================= @@ -521,6 +540,12 @@ if(NRN_ENABLE_NMODL OR NRN_ENABLE_CORENEURON) set(NMODL_ENABLE_PYTHON_BINDINGS OFF CACHE BOOL "Enable NMODL python bindings") + + include(${PROJECT_SOURCE_DIR}/cmake/nmodl/PythonLinkHelper.cmake) + + set(NMODL_PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/nmodl) + set(NMODL_PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/src/nmodl) + add_subdirectory(src/nmodl) set(CORENRN_NMODL_BINARY ${CMAKE_BINARY_DIR}/bin/nmodl${CMAKE_EXECUTABLE_SUFFIX}) set(NMODL_TARGET_TO_DEPEND nmodl) diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 3930e96e20..208e7bfc04 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -1,27 +1,4 @@ -# ============================================================================= -# Build options for NMODL -# ============================================================================= -option(NMODL_ENABLE_PYTHON_BINDINGS "Enable pybind11 based python bindings" ON) -option(NMODL_ENABLE_TESTS "Enable build of tests" ON) -option(NMODL_ENABLE_USECASES - "If building tests, additionally enable build of usecase tests. Requires neuron." OFF) -option(NMODL_ENABLE_BACKWARD "Use backward, enables blame." OFF) -set(NMODL_EXTRA_CXX_FLAGS - "" - CACHE STRING "Add extra compile flags for NMODL sources") -separate_arguments(NMODL_EXTRA_CXX_FLAGS) -option(LINK_AGAINST_PYTHON "Should the Python library be linked or not" ON) -option(NMODL_BUILD_WHEEL "Flag to signal we are building a wheel" OFF) -if(NMODL_BUILD_WHEEL) - set(LINK_AGAINST_PYTHON OFF) - set(NMODL_ENABLE_TESTS OFF) -endif() - -include(${PROJECT_SOURCE_DIR}/cmake/nmodl/PythonLinkHelper.cmake) include(${PROJECT_SOURCE_DIR}/cmake/nmodl/FlexHelper.cmake) -set(NMODL_PROJECT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) -set(NMODL_PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) - include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_BINARY_DIR}) list(APPEND NMODL_EXTRA_CXX_FLAGS ${NMODL_SANITIZER_COMPILER_FLAGS}) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6555a87b23..fa505d4e9f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -814,3 +814,9 @@ endif() # Add tests that are configured using external repositories add_subdirectory(external) + +# ============================================ +# Test NMODL +# ============================================ +add_subdirectory(nmodl/transpiler/unit) +add_subdirectory(nmodl/transpiler/integration) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index ea6757a0e6..556b1086e1 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -1,28 +1,31 @@ -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) - # ============================================================================= # Add extra compile flags to NMODL test sources # ============================================================================= -add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) -add_link_options(${NMODL_EXTRA_CXX_FLAGS}) -add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) +# add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) +# add_link_options(${NMODL_EXTRA_CXX_FLAGS}) +# add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) + +add_library(nmodl_test_flags INTERFACE) -include_directories(${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) -include_directories(${NMODL_PROJECT_SOURCE_DIR}/test) -include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/solver) -include_directories(${NMODL_PROJECT_SOURCE_DIR}/src/utils) -include_directories(${NMODL_PROJECT_SOURCE_DIR}/ext/eigen) +target_include_directories(nmodl_test_flags INTERFACE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) +target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_SOURCE_DIR}/solver) +target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_SOURCE_DIR}/utils) +target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_SOURCE_DIR}) +target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_BINARY_DIR}) +target_include_directories(nmodl_test_flags INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) +target_include_directories(nmodl_test_flags INTERFACE ${PROJECT_SOURCE_DIR}/external/eigen) # ============================================================================= # Common input data library # ============================================================================= add_library(test_util STATIC utils/nmodl_constructs.cpp utils/test_utils.cpp) -target_link_libraries(test_util PUBLIC spdlog::spdlog) +target_link_libraries(test_util PUBLIC spdlog::spdlog nmodl_test_flags) # ============================================================================= # Common input data library # ============================================================================= -add_library(config STATIC ${PROJECT_BINARY_DIR}/src/config/config.cpp) +add_library(config STATIC ${PROJECT_BINARY_DIR}/src/nmodl/config/config.cpp) +target_link_libraries(config PUBLIC nmodl_test_flags) # ============================================================================= # Test executables @@ -81,9 +84,9 @@ add_executable( codegen/transform.cpp codegen/codegen_compatibility_visitor.cpp) -target_link_libraries(testutils PRIVATE lexer util) -target_link_libraries(testmodtoken PRIVATE lexer util) -target_link_libraries(testlexer PRIVATE lexer util) +target_link_libraries(testutils PRIVATE lexer util nmodl_test_flags) +target_link_libraries(testmodtoken PRIVATE lexer util nmodl_test_flags) +target_link_libraries(testlexer PRIVATE lexer util nmodl_test_flags) target_link_libraries( testparser PRIVATE visitor @@ -92,7 +95,8 @@ target_link_libraries( util test_util printer - ${NMODL_WRAPPER_LIBS}) + ${NMODL_WRAPPER_LIBS} + nmodl_test_flags) target_link_libraries( testvisitor PRIVATE visitor @@ -102,7 +106,8 @@ target_link_libraries( test_util printer codegen - ${NMODL_WRAPPER_LIBS}) + ${NMODL_WRAPPER_LIBS} + nmodl_test_flags) target_link_libraries( testcodegen PRIVATE codegen @@ -112,11 +117,12 @@ target_link_libraries( util test_util printer - ${NMODL_WRAPPER_LIBS}) -target_link_libraries(testprinter PRIVATE printer util) -target_link_libraries(testsymtab PRIVATE symtab lexer util) -target_link_libraries(testunitlexer PRIVATE lexer util) -target_link_libraries(testunitparser PRIVATE lexer test_util config) + ${NMODL_WRAPPER_LIBS} + nmodl_test_flags) +target_link_libraries(testprinter PRIVATE printer util nmodl_test_flags) +target_link_libraries(testsymtab PRIVATE symtab lexer util nmodl_test_flags) +target_link_libraries(testunitlexer PRIVATE lexer util nmodl_test_flags) +target_link_libraries(testunitparser PRIVATE lexer test_util config nmodl_test_flags) # ============================================================================= # Use catch_discover instead of add_test for granular test result reporting. @@ -128,20 +134,20 @@ if(NOT LINK_AGAINST_PYTHON) endif() # Without main from Catch2 -target_link_libraries(testcodegen PRIVATE Catch2::Catch2) -target_link_libraries(testvisitor PRIVATE Catch2::Catch2) +target_link_libraries(testcodegen PRIVATE Catch2::Catch2 nmodl_test_flags) +target_link_libraries(testvisitor PRIVATE Catch2::Catch2 nmodl_test_flags) # With main from Catch2 -target_link_libraries(testutils PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testmodtoken PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testlexer PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testparser PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testprinter PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testsymtab PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testnewton PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testcrout PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testunitlexer PRIVATE Catch2::Catch2WithMain) -target_link_libraries(testunitparser PRIVATE Catch2::Catch2WithMain) +target_link_libraries(testutils PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testmodtoken PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testlexer PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testparser PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testprinter PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testsymtab PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testnewton PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testcrout PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testunitlexer PRIVATE Catch2::Catch2WithMain nmodl_test_flags) +target_link_libraries(testunitparser PRIVATE Catch2::Catch2WithMain nmodl_test_flags) foreach( test_name @@ -171,7 +177,7 @@ foreach( "${_environment_vars_list}") endforeach() -# Generate include files used in test input, see test/unit/utils/nmodl_constructs.cpp +# Generate include files used in test input, see test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/Unit.inc" "UNITSON \n UNITSOFF \n UNITSON \n UNITSOFF") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/included.file" "TITLE") @@ -184,12 +190,12 @@ endif() # ============================================================================= # pybind11 tests # ============================================================================= -add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${NMODL_PROJECT_SOURCE_DIR}/test/unit/ode) +add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/ode) set_tests_properties(Ode PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") if(NMODL_ENABLE_PYTHON_BINDINGS) add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest - ${NMODL_PROJECT_SOURCE_DIR}/test/unit/pybind) + ${CMAKE_CURRENT_SOURCE_DIR}/pybind) set_tests_properties(Pybind PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") cpp_cc_configure_sanitizers(TEST Pybind PRELOAD) endif() diff --git a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp index 1af02a04fa..f50fecbe1e 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_compatibility_visitor.cpp @@ -11,7 +11,7 @@ #include "ast/program.hpp" #include "codegen/codegen_compatibility_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/perf_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp index 21f5e2742f..d6e32f47d7 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_coreneuron_cpp_visitor.cpp @@ -13,7 +13,7 @@ #include "codegen/codegen_coreneuron_cpp_visitor.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/implicit_argument_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp index 463a201211..9f62c7d6f9 100644 --- a/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp +++ b/test/nmodl/transpiler/unit/codegen/codegen_neuron_cpp_visitor.cpp @@ -11,7 +11,7 @@ #include "ast/program.hpp" #include "codegen/codegen_neuron_cpp_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/function_callpath_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/codegen/main.cpp b/test/nmodl/transpiler/unit/codegen/main.cpp index d8de60edbd..999f2dd43b 100644 --- a/test/nmodl/transpiler/unit/codegen/main.cpp +++ b/test/nmodl/transpiler/unit/codegen/main.cpp @@ -9,7 +9,7 @@ #include #include "pybind/pyembed.hpp" -#include "utils/logger.hpp" +#include "nmodl/utils/logger.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/unit/codegen/transform.cpp b/test/nmodl/transpiler/unit/codegen/transform.cpp index 3a34222536..5a2f115e22 100644 --- a/test/nmodl/transpiler/unit/codegen/transform.cpp +++ b/test/nmodl/transpiler/unit/codegen/transform.cpp @@ -10,7 +10,7 @@ #include "ast/program.hpp" #include "codegen/codegen_transform_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/nmodl_visitor.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp index d473b3307b..a2a30b8e3c 100644 --- a/test/nmodl/transpiler/unit/modtoken/modtoken.cpp +++ b/test/nmodl/transpiler/unit/modtoken/modtoken.cpp @@ -13,7 +13,7 @@ #include "lexer/modtoken.hpp" #include "lexer/nmodl_lexer.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" /** @file diff --git a/test/nmodl/transpiler/unit/parser/parser.cpp b/test/nmodl/transpiler/unit/parser/parser.cpp index 036a965a62..0145e93073 100644 --- a/test/nmodl/transpiler/unit/parser/parser.cpp +++ b/test/nmodl/transpiler/unit/parser/parser.cpp @@ -15,8 +15,8 @@ #include "lexer/modtoken.hpp" #include "parser/diffeq_driver.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/nmodl_constructs.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/nmodl_constructs.hpp" +#include "utils/test_utils.hpp" #include "utils/common_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/test/nmodl/transpiler/unit/units/parser.cpp b/test/nmodl/transpiler/unit/units/parser.cpp index b121782440..c8fb0196b4 100644 --- a/test/nmodl/transpiler/unit/units/parser.cpp +++ b/test/nmodl/transpiler/unit/units/parser.cpp @@ -14,7 +14,7 @@ #include "config/config.h" #include "parser/diffeq_driver.hpp" #include "parser/unit_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" //============================================================================= // Parser tests diff --git a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp index 1cfa8b0024..631be2abc9 100644 --- a/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp +++ b/test/nmodl/transpiler/unit/visitor/after_cvode_to_cnexp.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/after_cvode_to_cnexp_visitor.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp index 918356018e..eee5a0aecd 100644 --- a/test/nmodl/transpiler/unit/visitor/constant_folder.cpp +++ b/test/nmodl/transpiler/unit/visitor/constant_folder.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/cvode.cpp b/test/nmodl/transpiler/unit/visitor/cvode.cpp index 96d09549ab..de22f29b02 100644 --- a/test/nmodl/transpiler/unit/visitor/cvode.cpp +++ b/test/nmodl/transpiler/unit/visitor/cvode.cpp @@ -2,7 +2,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/cvode_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp index 4c9a0b64dd..4c46f98188 100644 --- a/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp +++ b/test/nmodl/transpiler/unit/visitor/defuse_analyze.cpp @@ -11,7 +11,7 @@ #include "ast/binary_expression.hpp" #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/defuse_analyze_visitor.hpp" #include "visitors/inline_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/external.cpp b/test/nmodl/transpiler/unit/visitor/external.cpp index 71541a9d80..e2494d7a9a 100644 --- a/test/nmodl/transpiler/unit/visitor/external.cpp +++ b/test/nmodl/transpiler/unit/visitor/external.cpp @@ -10,7 +10,7 @@ #include "ast/program.hpp" #include "codegen/codegen_helper_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/symtab_visitor.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp index 64bed46a98..d9443ae500 100644 --- a/test/nmodl/transpiler/unit/visitor/global_to_range.cpp +++ b/test/nmodl/transpiler/unit/visitor/global_to_range.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/nmodl_constructs.hpp" +#include "utils/nmodl_constructs.hpp" #include "visitors/global_var_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp index cf6e30a708..3958496f02 100644 --- a/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp +++ b/test/nmodl/transpiler/unit/visitor/implicit_argument.cpp @@ -7,7 +7,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/implicit_argument_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/inline.cpp b/test/nmodl/transpiler/unit/visitor/inline.cpp index 5eeefd74cd..507dd3b737 100644 --- a/test/nmodl/transpiler/unit/visitor/inline.cpp +++ b/test/nmodl/transpiler/unit/visitor/inline.cpp @@ -10,7 +10,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp index c6920705e3..caadec768a 100644 --- a/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/kinetic_block.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp index e8ee0bcc32..8ccf1f2ea4 100644 --- a/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp +++ b/test/nmodl/transpiler/unit/visitor/local_to_assigned.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/nmodl_constructs.hpp" +#include "utils/nmodl_constructs.hpp" #include "visitors/local_to_assigned_visitor.hpp" #include "visitors/nmodl_visitor.hpp" #include "visitors/perf_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/localize.cpp b/test/nmodl/transpiler/unit/visitor/localize.cpp index 7a19ab8376..203b14d62c 100644 --- a/test/nmodl/transpiler/unit/visitor/localize.cpp +++ b/test/nmodl/transpiler/unit/visitor/localize.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/localrename.cpp b/test/nmodl/transpiler/unit/visitor/localrename.cpp index 92ab2054ee..14497db8eb 100644 --- a/test/nmodl/transpiler/unit/visitor/localrename.cpp +++ b/test/nmodl/transpiler/unit/visitor/localrename.cpp @@ -10,7 +10,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/lookup.cpp b/test/nmodl/transpiler/unit/visitor/lookup.cpp index bc7bb8aa91..2ae5fa5377 100644 --- a/test/nmodl/transpiler/unit/visitor/lookup.cpp +++ b/test/nmodl/transpiler/unit/visitor/lookup.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/visitor_utils.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp index b0bed9341a..37fa5bc555 100644 --- a/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp +++ b/test/nmodl/transpiler/unit/visitor/loop_unroll.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/loop_unroll_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/main.cpp b/test/nmodl/transpiler/unit/visitor/main.cpp index d8de60edbd..999f2dd43b 100644 --- a/test/nmodl/transpiler/unit/visitor/main.cpp +++ b/test/nmodl/transpiler/unit/visitor/main.cpp @@ -9,7 +9,7 @@ #include #include "pybind/pyembed.hpp" -#include "utils/logger.hpp" +#include "nmodl/utils/logger.hpp" using namespace nmodl; diff --git a/test/nmodl/transpiler/unit/visitor/misc.cpp b/test/nmodl/transpiler/unit/visitor/misc.cpp index 95ad1825e0..f6dd7b94cd 100644 --- a/test/nmodl/transpiler/unit/visitor/misc.cpp +++ b/test/nmodl/transpiler/unit/visitor/misc.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/inline_visitor.hpp" #include "visitors/localize_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp index 171ba01f2f..52c76ab5df 100644 --- a/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp +++ b/test/nmodl/transpiler/unit/visitor/neuron_solve.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/nmodl.cpp b/test/nmodl/transpiler/unit/visitor/nmodl.cpp index 51caf0ade9..df1136de6f 100644 --- a/test/nmodl/transpiler/unit/visitor/nmodl.cpp +++ b/test/nmodl/transpiler/unit/visitor/nmodl.cpp @@ -9,8 +9,8 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/nmodl_constructs.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/nmodl_constructs.hpp" +#include "utils/test_utils.hpp" #include "utils/common_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/node_index.cpp b/test/nmodl/transpiler/unit/visitor/node_index.cpp index d03935f090..d3ebb254eb 100644 --- a/test/nmodl/transpiler/unit/visitor/node_index.cpp +++ b/test/nmodl/transpiler/unit/visitor/node_index.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/indexedname_visitor.hpp" #include "visitors/visitor_utils.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/rename.cpp b/test/nmodl/transpiler/unit/visitor/rename.cpp index 8e705ef84a..f2a2cc0a9c 100644 --- a/test/nmodl/transpiler/unit/visitor/rename.cpp +++ b/test/nmodl/transpiler/unit/visitor/rename.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/local_var_rename_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp index 6d96fcf1fc..81028a1f23 100644 --- a/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp +++ b/test/nmodl/transpiler/unit/visitor/semantic_analysis.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/semantic_analysis_visitor.hpp" #include "visitors/symtab_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/solve_block.cpp b/test/nmodl/transpiler/unit/visitor/solve_block.cpp index af6c339b1d..9c310fcb94 100644 --- a/test/nmodl/transpiler/unit/visitor/solve_block.cpp +++ b/test/nmodl/transpiler/unit/visitor/solve_block.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/neuron_solve_visitor.hpp" #include "visitors/nmodl_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/steadystate.cpp b/test/nmodl/transpiler/unit/visitor/steadystate.cpp index 25cb6df37c..74b2f18aab 100644 --- a/test/nmodl/transpiler/unit/visitor/steadystate.cpp +++ b/test/nmodl/transpiler/unit/visitor/steadystate.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/kinetic_block_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp index 96b9ea867a..48055d9a8d 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_conductance.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp index 8ca1d226f1..ff6819e944 100644 --- a/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp +++ b/test/nmodl/transpiler/unit/visitor/sympy_solver.cpp @@ -14,7 +14,7 @@ #include "ast/program.hpp" #include "codegen/codegen_coreneuron_cpp_visitor.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/constant_folder_visitor.hpp" #include "visitors/inline_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/units.cpp b/test/nmodl/transpiler/unit/visitor/units.cpp index 1e96964603..36b3a5d4cd 100644 --- a/test/nmodl/transpiler/unit/visitor/units.cpp +++ b/test/nmodl/transpiler/unit/visitor/units.cpp @@ -12,9 +12,9 @@ #include "ast/factor_def.hpp" #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "src/config/config.h" -#include "test/unit/utils/nmodl_constructs.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "nmodl/config/config.h" +#include "utils/nmodl_constructs.hpp" +#include "utils/test_utils.hpp" #include "visitors/checkparent_visitor.hpp" #include "visitors/units_visitor.hpp" diff --git a/test/nmodl/transpiler/unit/visitor/var_usage.cpp b/test/nmodl/transpiler/unit/visitor/var_usage.cpp index a5be3449db..3570ea1224 100644 --- a/test/nmodl/transpiler/unit/visitor/var_usage.cpp +++ b/test/nmodl/transpiler/unit/visitor/var_usage.cpp @@ -9,7 +9,7 @@ #include "ast/program.hpp" #include "parser/nmodl_driver.hpp" -#include "test/unit/utils/test_utils.hpp" +#include "utils/test_utils.hpp" #include "visitors/var_usage_visitor.hpp" using namespace nmodl; From e961cf1b93386497167f3ccb090e361102b7029f Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 11:06:20 +0100 Subject: [PATCH 861/871] Remove `external/nmodl`. --- .gitmodules | 3 --- external/nmodl | 1 - 2 files changed, 4 deletions(-) delete mode 160000 external/nmodl diff --git a/.gitmodules b/.gitmodules index e10d9f0e8f..5b38c43a0f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,9 +16,6 @@ [submodule "external/Random123"] path = external/Random123 url = https://github.com/BlueBrain/Random123.git -[submodule "external/nmodl"] - path = external/nmodl - url = https://github.com/BlueBrain/nmodl [submodule "external/CLI11"] path = external/CLI11 url = https://github.com/CLIUtils/CLI11.git diff --git a/external/nmodl b/external/nmodl deleted file mode 160000 index 49fdfe6c12..0000000000 --- a/external/nmodl +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 49fdfe6c123f452c07e0dc69fbacc5ce66ea637d From 9267efea53e829db83a55b43cac71c5df020b370 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 14:01:53 +0100 Subject: [PATCH 862/871] Don't checkout nmodl. --- .github/workflows/neuron-ci.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/neuron-ci.yml b/.github/workflows/neuron-ci.yml index 48ec76d28e..bb5f6cce6e 100644 --- a/.github/workflows/neuron-ci.yml +++ b/.github/workflows/neuron-ci.yml @@ -157,11 +157,6 @@ jobs: with: fetch-depth: 2 - - name: Clone nmodl - working-directory: ${{runner.workspace}}/nrn - run: | - git submodule update --init --recursive --force --depth 1 -- external/nmodl - - name: Set up Python@${{ env.PY_MIN_VERSION }} if: ${{matrix.config.python_dynamic == 'ON'}} uses: actions/setup-python@v5 From ed62e2c8baf35bc7d4beb8348b4638c286b64a8d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 14:02:07 +0100 Subject: [PATCH 863/871] formatting. --- test/nmodl/transpiler/unit/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 556b1086e1..11f35e2c6d 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -195,7 +195,7 @@ set_tests_properties(Ode PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONP if(NMODL_ENABLE_PYTHON_BINDINGS) add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest - ${CMAKE_CURRENT_SOURCE_DIR}/pybind) + ${CMAKE_CURRENT_SOURCE_DIR}/pybind) set_tests_properties(Pybind PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") cpp_cc_configure_sanitizers(TEST Pybind PRELOAD) endif() From c3dfe24a7b3f84e4b017c2cd8b0936a0180b7f58 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 14:09:19 +0100 Subject: [PATCH 864/871] Add NMODL requirements.txt --- .github/workflows/coverage.yml | 7 ------ .github/workflows/neuron-ci.yml | 2 -- nmodl_requirements.txt | 27 +++++++++++++++++++++++ nrn_requirements.txt | 1 + test/nmodl/transpiler/unit/CMakeLists.txt | 12 +++++----- 5 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 nmodl_requirements.txt diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index a4e23da01c..b1117e3c7a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -79,11 +79,6 @@ jobs: with: fetch-depth: 2 - - name: Clone nmodl - working-directory: ${{runner.workspace}}/nrn - run: | - git submodule update --init --recursive --force --depth 1 -- external/nmodl - - name: Set up Python@${{ env.PY_MIN_VERSION }} uses: actions/setup-python@v5 with: @@ -92,7 +87,6 @@ jobs: - name: Install Python@${{ env.PY_MIN_VERSION }} dependencies working-directory: ${{runner.workspace}}/nrn run: | - python -m pip install --upgrade -r external/nmodl/requirements.txt python -m pip install --upgrade -r ci_requirements.txt python -m pip install --upgrade pip -r nrn_requirements.txt @@ -109,7 +103,6 @@ jobs: - name: Install Python@${{ env.PY_MAX_VERSION }} dependencies working-directory: ${{runner.workspace}}/nrn run: | - python -m pip install --upgrade -r external/nmodl/requirements.txt python -m pip install --upgrade -r ci_requirements.txt python -m pip install --upgrade pip -r nrn_requirements.txt diff --git a/.github/workflows/neuron-ci.yml b/.github/workflows/neuron-ci.yml index bb5f6cce6e..a62346ad1c 100644 --- a/.github/workflows/neuron-ci.yml +++ b/.github/workflows/neuron-ci.yml @@ -167,7 +167,6 @@ jobs: if: ${{ matrix.config.python_dynamic == 'ON' }} working-directory: ${{runner.workspace}}/nrn run: | - python -m pip install --upgrade -r external/nmodl/requirements.txt python -m pip install --upgrade -r ci_requirements.txt python -m pip install --upgrade pip -r nrn_requirements.txt @@ -179,7 +178,6 @@ jobs: - name: Install Python@${{ env.PY_MAX_VERSION }} dependencies working-directory: ${{runner.workspace}}/nrn run: | - python -m pip install --upgrade -r external/nmodl/requirements.txt python -m pip install --upgrade -r ci_requirements.txt python -m pip install --upgrade pip -r nrn_requirements.txt diff --git a/nmodl_requirements.txt b/nmodl_requirements.txt new file mode 100644 index 0000000000..dbdba4c48b --- /dev/null +++ b/nmodl_requirements.txt @@ -0,0 +1,27 @@ +# build time dependencies +scikit-build-core +setuptools-scm>=8.0 +Jinja2>=2.9.3 +PyYAML>=3.13 +# runtime dependencies +find_libpython +sympy>=1.3 +importlib-metadata;python_version<'3.9' +importlib-resources;python_version<'3.9' +# dependencies for test +pytest<=8.1.1 +pytest-cov +numpy +scipy +# dependencies for docs +jupyter-client +jupyter +myst_parser<2.0.0 +mistune<3 +nbconvert +nbsphinx>=0.3.2 +sphinxcontrib-applehelp<1.0.3 +sphinxcontrib-htmlhelp<=2.0.0 +sphinx<6 +sphinx-rtd-theme +docutils<0.20 diff --git a/nrn_requirements.txt b/nrn_requirements.txt index 8dcc5f3f10..6e9193fd7b 100644 --- a/nrn_requirements.txt +++ b/nrn_requirements.txt @@ -8,3 +8,4 @@ mpi4py find_libpython -r packaging/python/build_requirements.txt -r packaging/python/test_requirements.txt +-r nmodl_requirements.txt diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 11f35e2c6d..ae5376dc23 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -1,13 +1,13 @@ # ============================================================================= # Add extra compile flags to NMODL test sources # ============================================================================= -# add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) -# add_link_options(${NMODL_EXTRA_CXX_FLAGS}) +# add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) add_link_options(${NMODL_EXTRA_CXX_FLAGS}) # add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) add_library(nmodl_test_flags INTERFACE) -target_include_directories(nmodl_test_flags INTERFACE ${PYBIND11_INCLUDE_DIR} ${PYTHON_INCLUDE_DIRS}) +target_include_directories(nmodl_test_flags INTERFACE ${PYBIND11_INCLUDE_DIR} + ${PYTHON_INCLUDE_DIRS}) target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_SOURCE_DIR}/solver) target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_SOURCE_DIR}/utils) target_include_directories(nmodl_test_flags INTERFACE ${NMODL_PROJECT_SOURCE_DIR}) @@ -177,7 +177,8 @@ foreach( "${_environment_vars_list}") endforeach() -# Generate include files used in test input, see test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp +# Generate include files used in test input, see +# test/nmodl/transpiler/unit/utils/nmodl_constructs.cpp file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/Unit.inc" "UNITSON \n UNITSOFF \n UNITSON \n UNITSOFF") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/included.file" "TITLE") @@ -194,8 +195,7 @@ add_test(NAME Ode COMMAND ${PYTHON_EXECUTABLE} -m pytest ${CMAKE_CURRENT_SOURCE_ set_tests_properties(Ode PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") if(NMODL_ENABLE_PYTHON_BINDINGS) - add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest - ${CMAKE_CURRENT_SOURCE_DIR}/pybind) + add_test(NAME Pybind COMMAND ${PYTHON_EXECUTABLE} -m pytest ${CMAKE_CURRENT_SOURCE_DIR}/pybind) set_tests_properties(Pybind PROPERTIES ENVIRONMENT "PYTHONPATH=${NMODL_TEST_PYTHONPATH}") cpp_cc_configure_sanitizers(TEST Pybind PRELOAD) endif() From 419e7946b8b6eeb352735e8a2f6ae36ea355f388 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 14:14:43 +0100 Subject: [PATCH 865/871] Remove CI for NMODL. --- packaging/python/build_wheels.bash | 9 --------- 1 file changed, 9 deletions(-) diff --git a/packaging/python/build_wheels.bash b/packaging/python/build_wheels.bash index abea2dcb5b..7be2206d31 100755 --- a/packaging/python/build_wheels.bash +++ b/packaging/python/build_wheels.bash @@ -29,13 +29,6 @@ py_ver="" # for NEURON and its submodules python_requirements_path="$(mktemp -d)/requirements.txt" -clone_nmodl_and_add_requirements() { - git config --global --add safe.directory /root/nrn - git submodule update --init --recursive --force --depth 1 -- external/nmodl - # We only want the _build_ dependencies - sed -e '/^# runtime dependencies/,$ d' external/nmodl/requirements.txt >> "${python_requirements_path}" -} - setup_venv() { local py_bin="$1" @@ -74,7 +67,6 @@ build_wheel_linux() { if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" - clone_nmodl_and_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF,CORENRN_ENABLE_OPENMP=ON" fi @@ -129,7 +121,6 @@ build_wheel_osx() { if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" - clone_nmodl_and_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF" fi From f333a1b5169c9e6a1c1dcc76690caebd0f095f5d Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 14:44:10 +0100 Subject: [PATCH 866/871] Second attempt at reducing --- packaging/python/build_wheels.bash | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packaging/python/build_wheels.bash b/packaging/python/build_wheels.bash index 7be2206d31..cdabbc2ab4 100755 --- a/packaging/python/build_wheels.bash +++ b/packaging/python/build_wheels.bash @@ -29,6 +29,10 @@ py_ver="" # for NEURON and its submodules python_requirements_path="$(mktemp -d)/requirements.txt" +nmodl_add_requirements() { + sed -e '/^# runtime dependencies/,$ d' nmodl_requirements.txt >> "${python_requirements_path}" +} + setup_venv() { local py_bin="$1" @@ -67,6 +71,7 @@ build_wheel_linux() { if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" + nmodl_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF,CORENRN_ENABLE_OPENMP=ON" fi @@ -121,6 +126,7 @@ build_wheel_osx() { if [ "$2" == "coreneuron" ]; then setup_args="--enable-coreneuron" + nmodl_add_requirements CMAKE_DEFS="${CMAKE_DEFS},LINK_AGAINST_PYTHON=OFF" fi From 981cefec284f3413df5f02ef33c6b5718b2b7041 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 15:08:24 +0100 Subject: [PATCH 867/871] Conditionall add tests. --- test/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa505d4e9f..7997a5f86d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -818,5 +818,7 @@ add_subdirectory(external) # ============================================ # Test NMODL # ============================================ -add_subdirectory(nmodl/transpiler/unit) -add_subdirectory(nmodl/transpiler/integration) +if((NRN_ENABLE_NMODL OR NRN_ENABLE_CORENEURON) AND NMODL_ENABLE_TESTS) + add_subdirectory(nmodl/transpiler/unit) + add_subdirectory(nmodl/transpiler/integration) +endif() From 97b3fa47d721b102f3edce0fc6b598459cf1a4db Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Tue, 10 Dec 2024 16:47:58 +0100 Subject: [PATCH 868/871] Reuse NRN_SANITIZER* variables. --- cmake/nmodl/CMakeLists.txt | 4 ++-- docs/install/debug.md | 5 ++--- src/nmodl/CMakeLists.txt | 2 +- test/nmodl/transpiler/unit/CMakeLists.txt | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/cmake/nmodl/CMakeLists.txt b/cmake/nmodl/CMakeLists.txt index 6d40ac51c1..dfaf266be8 100644 --- a/cmake/nmodl/CMakeLists.txt +++ b/cmake/nmodl/CMakeLists.txt @@ -96,10 +96,10 @@ set(CODING_CONV_PREFIX NMODL) add_subdirectory(cmake/hpc-coding-conventions/cpp) # ============================================================================= -# Enable sanitizer support if the NMODL_SANITIZERS variable is set +# Enable sanitizer support if the NRN_SANITIZERS variable is set # ============================================================================= include(cmake/hpc-coding-conventions/cpp/cmake/sanitizers.cmake) -list(APPEND NMODL_EXTRA_CXX_FLAGS ${NMODL_SANITIZER_COMPILER_FLAGS}) +list(APPEND NMODL_EXTRA_CXX_FLAGS ${NRN_SANITIZER_COMPILER_FLAGS}) # ============================================================================= # Initialize external libraries as submodules diff --git a/docs/install/debug.md b/docs/install/debug.md index ae42b73779..cef5a03079 100644 --- a/docs/install/debug.md +++ b/docs/install/debug.md @@ -207,9 +207,8 @@ In addition, there is a macOS-based ASan build using AppleClang, which has the advantage that it uses `libc++` instead of `libstdc++`. NMODL supports the sanitizers in a similar way, but this has to be enabled -explicitly: `-DNRN_SANITIZERS=undefined` will not compile NMODL code with UBSan -enabled, you must additionally pass `-DNMODL_SANITIZERS=undefined` to enable -instrumentation of NMODL code. +explicitly: `-DNRN_SANITIZERS=undefined` will also compile NMODL code with UBSan +enabled. Profiling and performance benchmarking -------------------------------------- diff --git a/src/nmodl/CMakeLists.txt b/src/nmodl/CMakeLists.txt index 208e7bfc04..80924c5198 100644 --- a/src/nmodl/CMakeLists.txt +++ b/src/nmodl/CMakeLists.txt @@ -1,7 +1,7 @@ include(${PROJECT_SOURCE_DIR}/cmake/nmodl/FlexHelper.cmake) include_directories(${NMODL_PROJECT_SOURCE_DIR} ${NMODL_PROJECT_BINARY_DIR}) -list(APPEND NMODL_EXTRA_CXX_FLAGS ${NMODL_SANITIZER_COMPILER_FLAGS}) +list(APPEND NMODL_EXTRA_CXX_FLAGS ${NRN_SANITIZER_COMPILER_FLAGS}) # ============================================================================= # Create some random files. diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index ae5376dc23..48cad49459 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -127,7 +127,7 @@ target_link_libraries(testunitparser PRIVATE lexer test_util config nmodl_test_f # ============================================================================= # Use catch_discover instead of add_test for granular test result reporting. # ============================================================================= -set(test_env ${NMODL_SANITIZER_ENABLE_ENVIRONMENT}) +set(test_env ${NRN_SANITIZER_ENABLE_ENVIRONMENT}) set(testvisitor_env "PYTHONPATH=${PROJECT_BINARY_DIR}/lib:$ENV{PYTHONPATH}") if(NOT LINK_AGAINST_PYTHON) list(APPEND testvisitor_env "NMODL_PYLIB=$ENV{NMODL_PYLIB}") From 19a8ee6f349a676fa149287fcee44fc41cbcec32 Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 11 Dec 2024 11:14:33 +0100 Subject: [PATCH 869/871] Remove MAX_ERRORS related cmake code. --- cmake/nmodl/CompilerHelper.cmake | 6 ------ 1 file changed, 6 deletions(-) diff --git a/cmake/nmodl/CompilerHelper.cmake b/cmake/nmodl/CompilerHelper.cmake index 1c14b43787..27377956f2 100644 --- a/cmake/nmodl/CompilerHelper.cmake +++ b/cmake/nmodl/CompilerHelper.cmake @@ -46,9 +46,3 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "PGI" OR CMAKE_CXX_COMPILER_ID MATCHES "NVHPC") set(NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS --diag_suppress=177) endif() endif() - -if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") - set(NMODL_COMPILER_MAX_ERRORS_FLAG -fmax-errors=${NMODL_MAX_ERRORS}) -elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(NMODL_COMPILER_MAX_ERRORS_FLAG -ferror-limit=${NMODL_MAX_ERRORS}) -endif() From e3c20122a0e9dfdc4ed8881e21e9fa8f6adaecca Mon Sep 17 00:00:00 2001 From: Luc Grosheintz Date: Wed, 11 Dec 2024 11:14:52 +0100 Subject: [PATCH 870/871] more globals. --- test/nmodl/transpiler/unit/CMakeLists.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index 48cad49459..c11e01f0ec 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -1,8 +1,9 @@ # ============================================================================= # Add extra compile flags to NMODL test sources # ============================================================================= -# add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) add_link_options(${NMODL_EXTRA_CXX_FLAGS}) -# add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) +add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) +add_link_options(${NMODL_EXTRA_CXX_FLAGS}) +add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS}) add_library(nmodl_test_flags INTERFACE) From 3cab2eef4dca042b155fc50d4839640caa1b43fb Mon Sep 17 00:00:00 2001 From: Goran Jelic-Cizmek Date: Fri, 13 Dec 2024 12:55:20 +0100 Subject: [PATCH 871/871] Add missing include path for NMODL tests --- test/nmodl/transpiler/unit/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/nmodl/transpiler/unit/CMakeLists.txt b/test/nmodl/transpiler/unit/CMakeLists.txt index c11e01f0ec..6719aa022a 100644 --- a/test/nmodl/transpiler/unit/CMakeLists.txt +++ b/test/nmodl/transpiler/unit/CMakeLists.txt @@ -1,6 +1,7 @@ # ============================================================================= # Add extra compile flags to NMODL test sources # ============================================================================= +include(${PROJECT_SOURCE_DIR}/cmake/nmodl/FlexHelper.cmake) add_compile_options(${NMODL_EXTRA_CXX_FLAGS}) add_link_options(${NMODL_EXTRA_CXX_FLAGS}) add_compile_options(${NMODL_TESTS_COMPILER_WARNING_SUPPRESSIONS})